I’m building an app that is in portuguese and english. The i18n_extension is a nice i18n library, because it allows me to translate as easy as "This is some text".i18n
through extension methods (duhhh, hence the name).
But, there is no easy way (AFAIK) to automate this process, that can be very tedious.
So, me and my friend GePeTo created this little Dart CLI tool to do this:
- I write all strings in portuguese and then add the
.i18n
for those that need translation - I then run this tool with
dart run extract_i18n.dart
- The tool will search all
.dart
files looking for"text".i18n
(yes, I do use " for strings, why the hell do you guys use ’ since ’ is required in english language to that 's thing?) - It will add those keys unchanged to my
assets\translations\pt.json
file, since the original is pt - Using GitHub - soimort/translate-shell: 💬 Command-line translator using Google Translate, Bing Translator, Yandex.Translate, etc., it will translate all pt texts to english and then fill the
en.json
(the translations are crap (so is my english), but it’s a start). - Keys that already exists will be untouched, so the whole thing is idempotent
Leaving this here, so one can find it useful (or waiting for someone to tell me "hey, there is an easier way using XXX).
// ignore_for_file: avoid_print
import 'dart:convert';
import 'dart:io';
void main(List<String> arguments) {
final dartFiles = Directory(
"lib",
).listSync(recursive: true).where((entity) => entity.path.endsWith(".dart"));
final i18nStrings = <String>{};
final stringRegex = RegExp(
r'(?:"""([\s\S]*?)"""|"([^"]*)")\.i18n',
multiLine: true,
);
for (final file in dartFiles) {
final content = File(file.path).readAsStringSync();
final matches = stringRegex.allMatches(content);
if (matches.isNotEmpty) {
print("Processing ${file.path}: ${matches.length}");
for (final match in matches) {
final key = match.group(1)?.trim() ?? match.group(2)?.trim();
if (key != null) {
i18nStrings.add(key);
}
}
}
}
updateTranslationFile("en", i18nStrings);
updateTranslationFile("pt", i18nStrings);
}
Future<void> updateTranslationFile(String language, Set<String> i18nStrings) async {
final file = File("assets/translations/$language.json");
final json = file.readAsStringSync();
final translations = jsonDecode(json) as Map<String, dynamic>;
final tasks = <Future<String>>[];
var updated = false;
for (final key in i18nStrings) {
if (translations.containsKey(key) == false) {
if (language == "pt") {
translations[key] = key;
} else {
tasks.add(_translate(key).then((value) => translations[key] = value));
}
updated = true;
}
}
await Future.wait(tasks);
if (updated) {
file.writeAsStringSync(const JsonEncoder.withIndent(" ").convert(translations));
print("Updated $language with ${translations.length} keys");
}
}
Future<String> _translate(String text) async {
final result = await Process.run('./trans.sh', [
'-b',
'-e',
'bing',
':en',
text,
], runInShell: true);
if (result.exitCode != 0) {
return "";
}
return result.stdout.toString().trim();
}