Skip to content

Instantly share code, notes, and snippets.

@spiritinlife
Last active January 20, 2022 11:09
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save spiritinlife/49a9397768b508ecab929e7d6bbee038 to your computer and use it in GitHub Desktop.
Save spiritinlife/49a9397768b508ecab929e7d6bbee038 to your computer and use it in GitHub Desktop.
Extract language keys from your source code and merge them into existing translations.
/// This is inspired by intl_translation extract command https://github.com/dart-lang/intl_translation/blob/8a5e883d7fe07b0244adbaf3489ceb3b08385483/bin/extract_to_arb.dart
/// and is a slimed down rough version of it. Most of the code is from this package which is an excellent learning resource for anyone who wants
/// to play around with the dart analyzer.
/// This is a rough script, to extract localization keys for the easy_localization library. It will analyze the souce
/// code and find occurrences of tr and plural ( you could add other method names e.g. gender etc ) and extract the argument at index $argumentIndex
/// which should be the translation key. It then merges those keys to your current translated keys and spits the merged version where the
/// untranslated keys have a value of "MISSING".
/// Known issues
/// tr( isSomething ? "true_key" : "false_key", context ) -> will get this as key isSomething ? "true_key" : "false_key"
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:analyzer/dart/analysis/utilities.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/visitor.dart';
/// Example run
/// dart easy_localization_keys_extractor.dart <absolute path to director of source files> <absolute path to file of current translations> <method argument index>
/// date easy_localization_keys_extractor.dart "Users/user/project/lib" "Users/user/project/assets/langs/en.json" 1 > merged_en.json
///
/// Arguments
/// args[0]: directory of dart source files
/// args[1]: language json file containing current translations
/// args[2]: integer representing the index of the language key argument. e.g for easy_localization's
/// AppLocalizations.of(context).tr('key',) we should pass zero here
void main(List<String> args) async {
List<FileSystemEntity> dartFiles = await dirContents(Directory(args[0]));
Map<String, String> allMessages = Map<String, String>();
Map currentTranslations = json.decode(await (File(args[1])).readAsString());
int argumentIndex = args.length >= 3 ? int.parse(args[2]) : 0;
var extraction = new MessageExtraction(argumentIndex: argumentIndex);
for (var arg in dartFiles) {
var messages = extraction.parseFile(new File(arg.path), true);
allMessages.addAll(messages);
}
// merge new tr keys with existing translated ones
allMessages.forEach((key, value) {
if (!currentTranslations.containsKey(key)) {
currentTranslations[key] = value;
}
});
// write to file or print
print(json.encode(currentTranslations));
}
class MessageExtraction {
final int argumentIndex;
MessageExtraction({
this.argumentIndex = 0,
});
Map<String, String> parseFile(File file, [bool transformer = false]) {
String contents = file.readAsStringSync();
return parseContent(contents, file.path, transformer);
}
Map<String, String> parseContent(String fileContent, String filepath,
[bool transformer = false]) {
String contents = fileContent;
String origin = filepath;
CompilationUnit root;
// Optimization to avoid parsing files we're sure don't contain any messages.
if (contents.contains('tr') || contents.contains('plural') ) { // add here more validNames
root = _parseCompilationUnit(contents, origin);
} else {
return {};
}
var visitor = new MessageFindingVisitor(argumentIndex);
root.accept(visitor);
return visitor.messages;
}
CompilationUnit _parseCompilationUnit(String contents, String origin) {
var result = parseString(content: contents, throwIfDiagnostics: false);
if (result.errors.isNotEmpty) {
print("Error in parsing $origin, no messages extracted.");
throw ArgumentError('Parsing errors in $origin');
}
return result.unit;
}
}
class MessageFindingVisitor extends GeneralizingAstVisitor {
MessageFindingVisitor(this.argumentIndex);
final int argumentIndex;
final Map<String, String> messages = new Map<String, String>();
bool isMatch(MethodInvocation node) {
const validNames = const ["tr", "plural"]; // add here more validNames
return validNames.contains(node.methodName.name);
}
void visitMethodInvocation(MethodInvocation node) {
if (!isMatch(node)) return super.visitMethodInvocation(node);
addMessage(node);
}
void addMessage(MethodInvocation node) {
String trKey = node.argumentList.arguments[argumentIndex].toString();
// match 'key' and "key"
if ((trKey.startsWith("\"") || trKey.startsWith("'")) &&
(trKey.startsWith("\"") || trKey.startsWith("'"))) {
trKey = trKey.replaceAll("'", "");
trKey = trKey.replaceAll("\"", "");
}
messages[trKey] = "MISSING";
}
}
Future<List<FileSystemEntity>> dirContents(Directory dir) {
var files = <FileSystemEntity>[];
var completer = Completer<List<FileSystemEntity>>();
var lister = dir.list(recursive: true);
lister.listen(
(file) => file.path.endsWith(".dart") ? files.add(file) : null,
// should also register onError
onDone: () => completer.complete(files),
);
return completer.future;
}
@spiritinlife
Copy link
Author

If you want to add more method names like gender then search the snippet for // add here more validNames.

@aissat
Copy link

aissat commented Oct 13, 2020

hi @spiritinlife that's cool, I like it
Welcome back :)

@spiritinlife
Copy link
Author

spiritinlife commented Oct 13, 2020

Hey @aissat, i am always around if you need me. I just have some different opinions on the easy_localisation lib direction and the use of the preloaderWidget. Would like to raise an issue when i find some time.

Keep up the good work 🚀

@aissat
Copy link

aissat commented Oct 15, 2020

hi @spiritinlife,
First of all, Due to your contribution in this project has become what it is now and you can do whatever you want, it's your house now :), and any suggestions and changes you would like to add are welcome always

Thank you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment