* improvements in i18n_text_parser: subcommands "parse", "example" and "msg-init"
* yaml_merger and i18n_text_parser moved to directory dart_tools
pubspec.lock
tools/meta_tool
tools/yaml_merger
+dart_tools/tools/i18n_text_parser
+dart_tools/tools/yaml_merger
+tools/i18n_text_parser
+data/i18n/*.pot
+data/i18n/exhibition.lokalize
+data/i18n/main.lqa
+*~
+data/i18n/lokalize-scripts/
import 'dart:io';
+import 'package:dart_bones/dart_bones.dart';
import 'package:exhibition/meta/module_meta_data.dart';
import 'package:exhibition/meta/modules.dart';
import 'package:path/path.dart';
}
class Generator {
+ BaseLogger logger = globalLogger;
+ Generator(this.logger);
/// Appends a [string] at the end of a [buffer].
/// If [buffer] is null a new instance is created.
/// If the last line in [buffer] has a length greater than [maxLength]
ModuleMetaData? checkModule(List<String> args, int index) {
ModuleMetaData? rc;
if (index >= args.length) {
- out('+++ too few arguments. Missing MODULE.');
+ logger.error('+++ too few arguments. Missing MODULE.');
} else if ((rc = moduleByName(args[index])) == null) {
- out('+++ unknown module: ${args[index]}');
+ logger.error('+++ unknown module: ${args[index]}');
rc = null;
}
return rc;
if ((match = RegExp(r'class\s+(\w+)\s').firstMatch(contents)) != null) {
rc = match!.group(1)!;
} else {
- print('+++ missing class in $node');
+ logger.error('+++ missing class in $node');
rc = filenameToClass(node.replaceFirst('.dart', ''));
}
return rc;
/// Replaces print().
void out(String line) {
- stdout.write(line + '\n');
+ logger.log(line);
}
/// Generates all modules defined in the meta data.
void updateModules(Generator generator) {
for (var name in modules) {
ModuleMetaData? module = moduleByName(name);
if (module == null) {
- print('+++ unknown module: $name');
+ logger.error('+++ unknown module: $name');
} else {
String filename =
classToFilename(module.moduleNameSingular) + '_data.dart';
for (var name in modules) {
ModuleMetaData? module = moduleByName(name);
if (module == null) {
- print('+++ unknown module: $name');
+ logger.error('+++ unknown module: $name');
} else {
String filename = 'rest_server/data/sql/${name.toLowerCase()}.sql.yaml';
writeFile(filename, generator.createSqlStatements(module));
}
void writeFile(String filename, String contents) {
- out('creating $filename ...');
+ logger.log('creating $filename ...');
final file = File(filename);
file.writeAsStringSync(contents);
}
+++ /dev/null
-import "dart:io";
-
-import 'package:dart_bones/dart_bones.dart';
-import "package:path/path.dart";
-
-void main(List<String> args) {}
-
-class I18nTextParser {
- final globalModule = '!global';
- final errorModule = '!error';
- final BaseLogger logger;
- final mapModules = <String, Map<String, String>>{};
- final mapPlurals = <String, Map<String, PluralValue>>{};
- final foundFiles = <String>{};
- var regExpFiles = RegExp(r'.dart$');
- String currentModule = '';
- final moduleVariables = <String, String>{};
- String currentFile = '';
- String currentArguments = '';
- List<String> lines = [];
- int currentLineNo = 0;
- int currentIxLines = 0;
- // ...........................1.............1.........2
- final regExpModule =
- RegExp(r'''(\w+) = (I18N\(\)|i18N)\.module\(["'](.*?)['"]\);''');
- final regExpDelimiter = RegExp(r'''["']''');
- // ..........1.............1..2..............................2
- final regExpText = RegExp(r'(i18N|I18N\(\))\.(tr|ntr|trMulti|trArgs)\(');
- final regExpStringConstant = RegExp(r'''^\s*(r?)(["'])(.*?)\2''');
- final regExpVariable = RegExp(r'^\w+');
- final regExpEmptyString = RegExp(r'^\s*$');
-
- RegExpMatch? currentMatch;
-
- I18nTextParser(this.logger);
-
- /// Handles the state if the [currentArguments] is empty: it will be updated
- /// by the next line.
- void handleEmptyArgument() {
- if (regExpEmptyString.firstMatch(currentArguments) != null) {
- currentArguments = currentIxLines >= lines.length
- ? ''
- : lines[currentIxLines++].trimLeft();
- }
- }
-
- String handleModuleArgument() {
- String module = globalModule;
- if (hasComma()) {
- handleEmptyArgument();
- if (isStringConstant()) {
- module = currentMatch!.group(3)!;
- } else {
- if ((currentMatch = regExpVariable.firstMatch(currentArguments)) !=
- null) {
- final variable = currentMatch!.group(0);
- if (moduleVariables.containsKey(variable)) {
- module = moduleVariables[variable]!;
- } else {
- logger.error(
- '$currentFile-$currentLineNo: unknown module variable: $variable');
- module = errorModule;
- }
- }
- }
- }
- return module;
- }
-
- /// Gets the text (multiple lines) from the [currentArguments].
- /// [currentArguments] is the string behind the 'trMulti('.
- void handleMultiText() {
- logger.error('not implemented: trMulti()');
- }
-
- /// Gets the text (multiple lines) from the [currentArguments] .
- /// [currentArguments] is the string behind the 'trPlural('.
- void handlePluralText() {
- currentIxLines = currentLineNo;
- handleEmptyArgument();
- if (!isStringConstant()) {
- logger.error(
- '$currentFile-$currentLineNo: cannot analyse parameters of ntr().'
- ' Missing first string constant.');
- } else {
- String key1 = currentMatch!.group(3)!;
- if (!hasComma()) {
- logger.error(
- '$currentFile-$currentLineNo: cannot analyse parameters of ntr().'
- ' Missing second string constant.');
- } else {
- handleEmptyArgument();
- if (!isStringConstant()) {
- logger.error(
- '$currentFile-$currentLineNo: cannot analyse parameters of tr().'
- ' Second argument is not a string constant.');
- } else {
- String key2 = currentMatch!.group(3)!;
- String module = handleModuleArgument();
- putPluralKey(key1, key2, module);
- }
- }
- }
- }
-
- /// Gets the text (single line) from the [currentArguments] and store it.
- /// [currentArguments] is the string behind the 'tr('.
- void handleSimpleText() {
- currentIxLines = currentLineNo;
- handleEmptyArgument();
- if (!isStringConstant()) {
- logger.error(
- '$currentFile-$currentLineNo: cannot analyse parameters of tr().'
- ' Missing string constant.');
- } else {
- String module;
- final key = currentMatch?.group(3);
- module = handleModuleArgument();
- putText(key!, module);
- }
- }
-
- /// Gets the text (with placeholders) from the [currentArguments] and store it.
- /// [currentArguments] is the string behind the 'trArgs('.
- void handleTextWithArgs() {
- currentIxLines = currentLineNo;
- handleEmptyArgument();
- if (!isStringConstant()) {
- logger.error(
- '$currentFile-$currentLineNo: cannot analyse parameters of trArgs().'
- ' Missing string constant.');
- } else {
- String module;
- final key = currentMatch?.group(3);
- module = handleModuleArgument();
- putText(key!, module);
- }
- }
-
- /// Tests whether the [currentArguments] is preceded by a ','.
- /// If true the comma is removed from the [currentArguments].
- bool hasComma() {
- final rc = currentArguments.startsWith(',');
- if (rc) {
- currentArguments = currentArguments.substring(1).trimLeft();
- }
- return rc;
- }
-
- /// Tests whether the [currentArguments] is preceded by a string constant.
- /// If true the [currentMatch] contains the string content and the
- /// string constant is removed from the [currentArguments].
- bool isStringConstant() {
- if ((currentMatch = regExpStringConstant.firstMatch(currentArguments)) !=
- null) {
- currentArguments =
- currentArguments.substring(currentMatch!.end).trimLeft();
- }
- return currentMatch != null;
- }
-
- /// Stores the data of a plural text entry.
- void putPluralKey(String key1, String key2, String module) {
- if (!mapPlurals.containsKey(module)) {
- mapPlurals[module] = <String, PluralValue>{};
- }
- String reference = '#: $currentFile:$currentLineNo';
- if (module != errorModule && mapModules[module]?[key1] != null) {
- logger.error('$currentFile-$currentLineNo: key already defined as normal '
- 'key. Modify key with the invisible tag <#1> or <#2> or ... at the '
- 'end to make it unique.\nFirst found in ' +
- mapModules[module]![key1]!);
- putText(key1, errorModule);
- } else if (mapPlurals[module]!.containsKey(key1)) {
- if (mapPlurals[module]![key1]!.keyPlural == key2) {
- mapPlurals[module]![key1]!.references.add(reference);
- } else {
- logger.error('$currentFile-$currentLineNo: multiple definition of a '
- 'plural key but keyPlural is different. Modify key1 with the'
- ' invisible tag <#1> or <#2> or ... at the end to make it '
- 'unique.\nFirst found in ' +
- mapPlurals[module]![key1]!.references[0]);
- }
- } else {
- mapPlurals[module]![key1] = PluralValue(key2, [reference]);
- }
- }
-
- /// Stores the [key] in the [module] map with location info [filename] and
- /// [lineNo].
- void putText(String key, [String module = '']) {
- if (module.isEmpty) {
- module = currentModule;
- }
- if (!mapModules.containsKey(module)) {
- mapModules[module] = <String, String>{};
- }
- if (module != errorModule && mapPlurals[module]?[key] != null) {
- logger.error('$currentFile-$currentLineNo: key already defined as plural '
- 'key. Modify key with the invisible tag <#1> or <#2> or ... at '
- 'the end to make it unique.\nFirst found in ' +
- mapPlurals[module]![key]!.references[0]);
- putText(key, errorModule);
- } else if (mapModules[module]!.containsKey(key)) {
- mapModules[module]![key] =
- mapModules[module]![key]! + '\n#: $currentFile:$currentLineNo';
- } else {
- mapModules[module]![key] = '#: $currentFile:$currentLineNo';
- }
- }
-
- /// Scans recursively all dart files of the [directory].
- void scanDirectory(
- String directory,
- ) {
- final base = Directory(directory);
- final subDirs = [];
- try {
- for (var file in base.listSync()) {
- if (file is Directory) {
- subDirs.add(file.path);
- continue;
- }
- if (regExpFiles.firstMatch(file.path) == null) {
- continue;
- }
- if (file is File) {
- scanFile(file, directory);
- } else {
- logger.error('ignored: $directory/${file.path}');
- }
- }
- } on FileSystemException catch (exc) {
- logger.error('ignored: $directory: $exc');
- }
- for (var node in subDirs) {
- scanDirectory(join(directory, node));
- }
- }
-
- /// Scans the [file] for I18N texts.
- /// [directory]: used for logging.
- void scanFile(File file, String directory) {
- logger.log('parsing ${file.path}', LEVEL_DETAIL);
- try {
- currentFile = join(directory, file.path);
- lines = file.readAsLinesSync();
- moduleVariables.clear();
- currentLineNo = 0;
- for (var line in lines) {
- ++currentLineNo;
- RegExpMatch? match;
- if ((match = regExpModule.firstMatch(line)) != null) {
- currentModule = match!.group(3)!;
- moduleVariables[match.group(1)!] = currentModule;
- } else {
- for (match in regExpText.allMatches(line)) {
- currentArguments = line.substring(match.end);
- switch (match.group(2)!) {
- case 'tr':
- handleSimpleText();
- break;
- case 'trMulti':
- handleMultiText();
- break;
- case 'ntr':
- handlePluralText();
- break;
- case 'trArgs':
- handleTextWithArgs();
- break;
- }
- }
- }
- }
- } on FileSystemException catch (exc) {
- logger.error('ignored: $directory: $exc');
- }
- }
-
- String convertString(String key) {
- final rc = key.replaceAll('"', r'\"');
- return rc;
- }
-
- /// Writes the *.pot files to the [directory].
- void writePot(String directory) {
- FileSync().ensureDirectory(directory);
- final doneModules = <String>{};
- for (var module in mapModules.keys) {
- doneModules.add(module);
- final map = mapModules[module]!;
- final keys = map.keys.toList();
- Map<String, PluralValue>? map2;
- if (mapPlurals.containsKey(module)) {
- map2 = mapPlurals[module];
- keys.addAll(map2!.keys);
- }
- keys.sort();
- final buffer = StringBuffer(
- '# Texts of module $module, parsed by i18n_text_parser\n');
- for (var key in keys) {
- buffer.writeln();
- buffer.writeln(map.containsKey(key)
- ? map[key]
- : map2![key]!.references.join('\n'));
- var key2 = convertString(key);
- buffer.writeln('msgid "$key2"');
- if (map.containsKey(key)) {
- buffer.writeln('msgstr ""');
- } else {
- key2 = convertString(map2![key]!.keyPlural);
- buffer.writeln('msgid_plural "$key2"');
- buffer.writeln('msgstr[0] ""');
- buffer.writeln('msgstr[1] ""');
- }
- }
- final fn = join(directory, '$module.pot');
- logger.log('writing $fn', LEVEL_DETAIL);
- File(fn).writeAsStringSync(buffer.toString());
- }
- for (var module in mapPlurals.keys) {
- if (!doneModules.contains(module)) {
- final map = mapPlurals[module];
- final keys = map!.keys.toList();
- keys.sort();
- final buffer = StringBuffer(
- '# Texts of module $module, parsed by i18n_text_parser\n');
- for (var key in keys) {
- buffer.writeln();
- buffer.writeln(map[key]!.references.join('\n'));
- var key2 = convertString(key);
- buffer.writeln('msgid "$key2"');
- key2 = convertString(map[key]!.keyPlural);
- buffer.writeln('msgid_plural "$key2"');
- buffer.writeln('msgstr[0] ""');
- buffer.writeln('msgstr[1] ""');
- }
- final fn = join(directory, '$module.pot');
- logger.log('writing $fn', LEVEL_DETAIL);
- File(fn).writeAsStringSync(buffer.toString());
- }
- }
- }
-}
-
-/// Stores the data of a plural text.
-class PluralValue {
- final List<String> references;
- final String keyPlural;
- PluralValue(this.keyPlural, this.references);
-}
-import 'package:exhibition/base/i18n.dart';
+import 'package:dart_bones/dart_bones.dart';
+import 'package:exhibition/base/i18n_io.dart';
import 'package:exhibition/meta/module_meta_data.dart';
import 'package:exhibition/meta/modules.dart';
import 'generator.dart';
void main(List<String> args) {
- final generator = Generator();
- I18N.internal('data/i18n');
+ final logger = MemoryLogger(LEVEL_DETAIL);
+ final generator = Generator(logger);
+ I18nIo.internal('data/i18n', logger);
if (args.isEmpty) {
usage(generator);
generator.out('+++ missing arguments.');
+++ /dev/null
-import 'dart:io';
-
-import 'package:path/path.dart';
-import 'package:dart_bones/dart_bones.dart';
-
-/// Implements a merge tool for yaml files.
-/// @see merge() for description of merge.
-class Merger {
- RegExp regExpKey = RegExp(r'^([a-zA-Z]\w+):\s*$');
- RegExp regExpKeyValue = RegExp(r'^ ');
- final BaseLogger logger;
- final precedenceKeys = <String, String>{};
- Merger(this.logger);
-
- /// Parses the keys of a given [file] into [this.precedenceKeys].
- void parseKeys(File file){
- precedenceKeys.clear();
- final lines = file.readAsLinesSync();
- var state = 'undef';
- var lastKey = '';
- final keyValue = [];
- for (var line in lines){
- final match = regExpKey.firstMatch(line);
- if (match != null){
- if (keyValue.isNotEmpty){
- precedenceKeys[lastKey] = keyValue.join('\n');
- keyValue.clear();
- }
- state = 'inKey';
- lastKey = match.group(1)!;
- } else {
- if (regExpKeyValue.firstMatch(line) != null){
- if (state == 'inKey'){
- keyValue.add(line);
- }
- }
- }
- }
- if (keyValue.isNotEmpty){
- precedenceKeys[lastKey] = keyValue.join('\n');
- }
- }
- /// Merges the [fileSource] and [filePreference] into the [target] file.
- void mergeOneFile(File fileSource, File filePreference, String target) {
- parseKeys(filePreference);
- final output = <String>[];
- final foundKeys = <String>{};
- final lines = fileSource.readAsLinesSync();
- var state = 'undef';
- var lastKey = '';
- final keyValue = [];
- for (var line in lines){
- final match = regExpKey.firstMatch(line);
- if (match != null) {
- if (keyValue.isNotEmpty) {
- output.add(lastKey + ':');
- output.add(precedenceKeys.containsKey(lastKey)
- ? precedenceKeys[lastKey]!
- : keyValue.join('\n'));
- }
- keyValue.clear();
- state = 'inKey';
- lastKey = match.group(1)!;
- if (foundKeys.contains(lastKey)){
- logger.error('key $lastKey found multiple times');
- }
- foundKeys.add(lastKey);
- } else {
- if (regExpKeyValue.firstMatch(line) != null){
- if (state == 'inKey'){
- keyValue.add(line);
- }
- } else {
- output.add(line);
- }
- }
- }
- if (keyValue.isNotEmpty){
- output.add(lastKey + ':');
- if (precedenceKeys.containsKey(lastKey)) {
- output.add(precedenceKeys[lastKey]!);
- } else {
- output.add(keyValue.join('\n'));
- }
- }
- for (var key in precedenceKeys.keys){
- if (! foundKeys.contains(key)){
- output.add(key + ':');
- output.add(precedenceKeys[key]!);
- }
- }
- File(target).writeAsStringSync(output.join('\n') + '\n');
- }
- /// Merges or copies files from two source directories into a target directory.
- /// [pathSource] contains files with lower priorities.
- /// [pathPreferences] contains files with higher priorities.
- /// [pathTarget] is the target directory.
- /// Only files with the same node will be merged.
- /// Merging: the two source files contains keys. If a key is part of both
- /// files the key content of of the higher priority file is taken.
- void merge(String pathSource, String pathPreference, String pathTarget) {
- final dirSource = Directory(pathSource);
- final dirPreference = Directory(pathPreference);
- final dirTarget = Directory(pathTarget);
- if (!dirSource.existsSync()) {
- logger.error('source not a directory: $pathSource');
- } else if (!dirPreference.existsSync()) {
- logger.error('source not a directory: $pathPreference');
- } else if (!dirTarget.existsSync()) {
- logger.error('source not a directory: $pathTarget');
- } else {
- /// Copy/merge the files from dirSource:
- for (var file in dirSource.listSync()) {
- final node = basename(file.path);
- if (! node.endsWith('.yaml')){
- continue;
- }
- final fnPreference = join(pathPreference, node);
- final fnTarget = join(pathTarget, node);
- final filePreference = File(fnPreference);
- if (!(file is File)) {
- logger.error('not a file: ${file.path} ignoring...');
- } else if (filePreference.existsSync()) {
- mergeOneFile(file, filePreference, fnTarget);
- } else {
- logger.log('copying ${file.path}', LEVEL_DETAIL);
- file.copy(fnTarget);
- }
- }
-
- /// Copy the "single" files from preferences:
- for (var file in dirPreference.listSync()) {
- final node = basename(file.path);
- if (! node.endsWith('.yaml')){
- continue;
- }
- final fnSource = join(pathSource, node);
- final fnTarget = join(pathTarget, node);
- File fileSource = File(fnSource);
- if (file is File && !fileSource.existsSync()) {
- logger.log('copying ${file.path}', LEVEL_DETAIL);
- file.copy(fnTarget);
- }
- }
- }
- }
-}
-void main(List<String> args){
- final logger = MemoryLogger(LEVEL_DETAIL);
- if (args.length != 3){
- print('''Usage: yaml_merger DIR_SOURCE DIR_PREFERENCE DIR_OUTPUT
- Merges the yaml files from DIR_SOURCE (with lower priority) and DIR_PREFERENCE
- (with higher priority) into files in DIR_OUTPUT.
-Version: 0.1.0
-Example:
-yaml_merger data/sql data/sql/precedence /tmp/sql
-''');
- print('+++ wrong count of arguments: ${args.length} instead of 3');
- } else {
- final merger = Merger(logger);
- merger.merge(args[0], args[1], args[2]);
- }
-}
\ No newline at end of file
--- /dev/null
+import "dart:io";
+
+import 'package:dart_bones/dart_bones.dart';
+import "package:path/path.dart";
+import 'package:sprintf/sprintf.dart';
+
+void main(List<String> args) {
+ if (args.length == 0) {
+ usage('missing arguments');
+ } else {
+ switch (args[0]) {
+ case 'parse':
+ parse(args.sublist(1));
+ break;
+ case 'msg-init':
+ msgInit(args.sublist(1));
+ break;
+ }
+ }
+}
+
+/// Executes the "msg-init" sub command.
+void msgInit(List<String> args) {
+ if (args.length != 3) {
+ usage('wrong count of arguments');
+ } else if (!Directory(args[0]).existsSync()) {
+ usage('<TARGET_DIR> is not a directory: ${args[0]}');
+ } else if (!File(join(args[0], args[1] + '.pot')).existsSync()) {
+ usage('wrong <MODULE>: missing <MODULE>.pot');
+ } else if (RegExp(r'[a-z]{2}(_[A-Z]{2})?').firstMatch(args[2]) == null) {
+ usage('wrong <LOCALE>: xx or xx_YY expected, e.g. de_DE');
+ } else {
+ final logger = MemoryLogger(LEVEL_DETAIL);
+ final parser = I18nTextParser(logger);
+ parser.msgInit(args[0], args[1], args[2]);
+ }
+}
+
+/// Executes the "parse" sub command.
+void parse(List<String> args) {
+ if (args.length < 2) {
+ usage('wrong count of arguments');
+ } else if (!Directory(args[0]).existsSync()) {
+ usage('SOURCE_DIR is not a directory: ${args[0]}');
+ } else if (!Directory(args[1]).existsSync()) {
+ usage('TARGET_DIR is not a directory: ${args[1]}');
+ } else {
+ final logger = MemoryLogger(LEVEL_DETAIL);
+ final parser = I18nTextParser(logger);
+ String excluded = '*';
+ if (args.length == 3) {
+ excluded = args[2];
+ }
+ if (excluded.isNotEmpty) {
+ if (excluded == '*') {
+ excluded = r'linux|ios|web|\.\w.*|test|data';
+ }
+ if (excluded.contains(';')) {
+ excluded = RegExp.escape(excluded).replaceAll(';', '|');
+ }
+ parser.regExpExcluded = RegExp('^($excluded)\$');
+ logger.log('excluding: ' + parser.regExpExcluded!.pattern, LEVEL_FINE);
+ }
+ parser.scanDirectory(args[0], 0);
+ parser.writePot(args[1]);
+ }
+}
+
+void usage(String error) {
+ stderr.write('''Usage:
+i18n_text_parser parse <SOURCE_DIR> <TARGET_DIR> [<EXCLUDED>]
+ Parses recursively the Dart files of the <SOURCE_DIR> and create the *.pot
+ files in the <TARGET_DIR>.
+ <EXCLUDED>:
+ "": exclude nothing
+ "*" exclude all standard directories: means 'linux|ios|web|\.\w.*|test|data'
+ Otherwise: a ";" delimited list of excluded directories, e.g. "ios;linux;.git"
+ or a '|' delimited list of regular expressions, e.g. 'test.*|\.[a-z]+'
+ Default value: "*"
+i18n_text_parser msg-init <TARGET_DIR> <MODULE> <LOCALE>
+ Creates the <MODULE>.<LOCALE>.po file from the <MODULE>.pot file.
+Examples:
+i18n_text_parser parse /home/ws/flutter/exhibition /tmp/i18n_data
+i18n_text_parser parse do_it /tmp/i18n_data '\..*|test"
+i18n_text_parser parse do_it /tmp/i18n_data ""
+i18n_text_parser msg-init /tmp/i18n_data Users de_DE
+''');
+ stderr.writeln('+++ $error');
+}
+
+class I18nTextParser {
+ final globalModule = '!global';
+ final errorModule = '!error';
+ final BaseLogger logger;
+ final mapModules = <String, Map<String, String>>{};
+ final mapPlurals = <String, Map<String, PluralValue>>{};
+ final foundFiles = <String>{};
+ var regExpFiles = RegExp(r'.dart$');
+ String currentModule = '';
+ final moduleVariables = <String, String>{};
+ String currentFile = '';
+ String currentArguments = '';
+ List<String> lines = [];
+ int currentLineNo = 0;
+ int currentIxLines = 0;
+ RegExp? regExpExcluded;
+ // ...........................1.............1.........2
+ final regExpModule =
+ RegExp(r'''(\w+) = (I18N\(\)|i18N)\.module\(["'](.*?)['"]\);''');
+ final regExpDelimiter = RegExp(r'''["']''');
+ // ..........1.............1..2..............................2
+ final regExpText = RegExp(r'(i18N|I18N\(\))\.(tr|ntr|trMulti|trArgs)\(');
+ final regExpStringConstant = RegExp(r'''^\s*(r?)(["'])(.*?)\2''');
+ final regExpVariable = RegExp(r'^\w+');
+ final regExpEmptyString = RegExp(r'^\s*$');
+
+ RegExpMatch? currentMatch;
+
+ I18nTextParser(this.logger);
+
+ /// Checks the completeness of the [configuration].
+ /// [location]: description of the configuration, e.g. the filename.
+ /// [locale]: null or a needed key.
+ bool checkConfiguration(
+ BaseConfiguration configuration, String location, String? locale) {
+ bool checkKey(String key) {
+ final rc = configuration.asString(key) != null;
+ if (!rc) {
+ logger.error('missing key $key in $location');
+ }
+ return rc;
+ }
+
+ bool checkSection(String key, String section) {
+ final rc = configuration.asString(key, section: section) != null;
+ if (!rc) {
+ logger.error('missing key [$section].$key in $location');
+ }
+ return rc;
+ }
+
+ var rc = checkSection('name', 'project');
+ rc &= checkSection('version', 'project');
+ rc &= checkKey('author');
+ rc &= checkKey('translator');
+ rc &= checkSection('name', 'copyright');
+ rc &= checkSection('start', 'copyright');
+ rc &= checkKey('license');
+ rc &= checkKey('bugResponsible');
+ if (locale != null) {
+ rc &= checkSection('language', locale);
+ rc &= checkSection('name', locale);
+ }
+ return rc;
+ }
+
+ String convertString(String key) {
+ final rc = key.replaceAll('"', r'\"');
+ return rc;
+ }
+
+ /// Writes an example of the configuration file into the [directory].
+ void exampleConfiguration(String directory, [String? locale]) {
+ locale ??= 'de_DE';
+ final filename = join(directory, 'project.i18n.yaml');
+ final file = File(filename);
+ file.writeAsStringSync('''---
+# Configuration of the I18N data:
+project:
+ name: "<PROJECT>"
+ version: "1.0.0"
+author: "J. Hamatoma <author@hamatoma.de>"
+translator: "J. Hamatoma <lang@hamatoma.de>"
+$locale:
+ language: "German"
+ name: "J. Hamatoma <de@hamatoma.de>"
+copyright:
+ name: "J. Hamatoma <cr@hamatoma.de>"
+ start: 2021
+license: "CC0 1.0 Universal"
+bugResponsible: "J. Hamatoma <bug@hamatoma.de>"
+''');
+ }
+
+ /// Returns the configuration file in the [directory] and test it for
+ /// completeness.
+ /// If [locale] is not null it must be a key in the configuration.
+ /// Returns null if not found or some errors has been found.
+ BaseConfiguration? fetchConfiguration(String directory, {String? locale}) {
+ final filename = join(directory, 'project.i18n.yaml');
+ final file = File(filename);
+ BaseConfiguration? rc;
+ if (!file.existsSync()) {
+ logger.error(
+ 'abort: the new created configuration $filename must be edited.'
+ ' Than try again');
+ exampleConfiguration(directory);
+ } else {
+ rc = Configuration.fromFile(filename, logger);
+ if (!checkConfiguration(rc, filename, locale)) {
+ logger.error(
+ 'abort: configuration $filename contains errors. Please correct '
+ 'that and try again.');
+ rc = null;
+ }
+ }
+ return rc;
+ }
+
+ /// Handles the state if the [currentArguments] is empty: it will be updated
+ /// by the next line.
+ void handleEmptyArgument() {
+ if (regExpEmptyString.firstMatch(currentArguments) != null) {
+ currentArguments = currentIxLines >= lines.length
+ ? ''
+ : lines[currentIxLines++].trimLeft();
+ }
+ }
+
+ /// Handles the argument with the module (as string constant or as variable).
+ String handleModuleArgument() {
+ String module = globalModule;
+ if (hasComma()) {
+ handleEmptyArgument();
+ if (isStringConstant()) {
+ module = currentMatch!.group(3)!;
+ } else {
+ if ((currentMatch = regExpVariable.firstMatch(currentArguments)) !=
+ null) {
+ final variable = currentMatch!.group(0);
+ if (moduleVariables.containsKey(variable)) {
+ module = moduleVariables[variable]!;
+ } else {
+ logger.error(
+ '$currentFile-$currentLineNo: unknown module variable: $variable');
+ module = errorModule;
+ }
+ }
+ }
+ }
+ return module;
+ }
+
+ /// Gets the text (multiple lines) from the [currentArguments].
+ /// [currentArguments] is the string behind the 'trMulti('.
+ void handleMultiText() {
+ logger.error('not implemented: trMulti()');
+ }
+
+ /// Gets the text (multiple lines) from the [currentArguments] .
+ /// [currentArguments] is the string behind the 'trPlural('.
+ void handlePluralText() {
+ currentIxLines = currentLineNo;
+ handleEmptyArgument();
+ if (!isStringConstant()) {
+ logger.error(
+ '$currentFile-$currentLineNo: cannot analyse parameters of ntr().'
+ ' Missing first string constant.');
+ } else {
+ String key1 = currentMatch!.group(3)!;
+ if (!hasComma()) {
+ logger.error(
+ '$currentFile-$currentLineNo: cannot analyse parameters of ntr().'
+ ' Missing second string constant.');
+ } else {
+ handleEmptyArgument();
+ if (!isStringConstant()) {
+ logger.error(
+ '$currentFile-$currentLineNo: cannot analyse parameters of tr().'
+ ' Second argument is not a string constant.');
+ } else {
+ String key2 = currentMatch!.group(3)!;
+ String module = handleModuleArgument();
+ putPluralKey(key1, key2, module);
+ }
+ }
+ }
+ }
+
+ /// Gets the text (single line) from the [currentArguments] and store it.
+ /// [currentArguments] is the string behind the 'tr('.
+ void handleSimpleText() {
+ currentIxLines = currentLineNo;
+ handleEmptyArgument();
+ if (!isStringConstant()) {
+ logger.error(
+ '$currentFile-$currentLineNo: cannot analyse parameters of tr().'
+ ' Missing string constant.');
+ } else {
+ String module;
+ final key = currentMatch?.group(3);
+ module = handleModuleArgument();
+ putText(key!, module);
+ }
+ }
+
+ /// Gets the text (with placeholders) from the [currentArguments] and store it.
+ /// [currentArguments] is the string behind the 'trArgs('.
+ void handleTextWithArgs() {
+ currentIxLines = currentLineNo;
+ handleEmptyArgument();
+ if (!isStringConstant()) {
+ logger.error(
+ '$currentFile-$currentLineNo: cannot analyse parameters of trArgs().'
+ ' Missing string constant.');
+ } else {
+ String module;
+ final key = currentMatch?.group(3);
+ module = handleModuleArgument();
+ putText(key!, module);
+ }
+ }
+
+ /// Tests whether the [currentArguments] is preceded by a ','.
+ /// If true the comma is removed from the [currentArguments].
+ bool hasComma() {
+ final rc = currentArguments.startsWith(',');
+ if (rc) {
+ currentArguments = currentArguments.substring(1).trimLeft();
+ }
+ return rc;
+ }
+
+ /// Returns the introduction of a *.pot file, driven by [configuration].
+ String introduction(String module, BaseConfiguration configuration) {
+ final now = DateTime.now();
+ final year = now.year;
+ final date = '${now.year}-${now.month}-${now.day} ${now.hour}:${now.minute}'
+ '+0${now.timeZoneOffset.inHours}00';
+ final author = configuration.asString('author') ?? 'J. Hamatoma';
+ var project = configuration.asString('name', section: 'project') ??
+ '??' +
+ ' ' +
+ (configuration.asString('version', section: 'project') ?? '1.0.0');
+ final license = configuration.asString('license') ?? 'CC0 1.0 Universal';
+ var start = configuration.asString('start', section: 'copyright') ?? year;
+ var cr = configuration.asString('name', section: 'copyright') ?? author;
+ var responsible = configuration.asString('bugResponsible') ?? author;
+ final rc = '''# Texts of module $module, created by i18n_text_parser');
+# Copyright (C) $start-$year $cr
+# License: $license
+# $author, $year.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: $project\\n"
+"Report-Msgid-Bugs-To: $responsible\\n"
+"POT-Creation-Date: $date\\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n"
+"Language-Team: LANGUAGE <LL@li.org>\\n"
+"Language: LANGUAGE\\n"
+"MIME-Version: 1.0\\n"
+"Content-Type: text/plain; charset=UTF-8\\n"
+"Content-Transfer-Encoding: 8bit\\n"
+''';
+ return rc;
+ }
+
+ /// Tests whether the [currentArguments] is preceded by a string constant.
+ /// If true the [currentMatch] contains the string content and the
+ /// string constant is removed from the [currentArguments].
+ bool isStringConstant() {
+ if ((currentMatch = regExpStringConstant.firstMatch(currentArguments)) !=
+ null) {
+ currentArguments =
+ currentArguments.substring(currentMatch!.end).trimLeft();
+ }
+ return currentMatch != null;
+ }
+
+ /// Creates the first *.po file from a *.pot file for the [module] with
+ /// [locale] in the [directory].
+ void msgInit(String directory, String module, String locale) {
+ final source = File(join(directory, '$module.pot'));
+ final target = File(join(directory, '$module.$locale.po'));
+ var contents = source.readAsStringSync();
+ final configuration = fetchConfiguration(directory);
+ if (configuration != null) {
+ final now = DateTime.now();
+ final date =
+ '${now.year}-${now.month}-${now.day} ${now.hour}:${now.minute}' +
+ sprintf('+%02d00', [now.timeZoneOffset.inHours]);
+ final translator = configuration.asString('translator') ?? 'J. Hamatoma';
+ final language =
+ configuration.asString('language', section: locale) ?? '$locale';
+ final team =
+ configuration.asString('name', section: locale) ?? '$locale <author@hamatoma.de>';
+ contents = contents.replaceFirst(
+ 'Revision-Date: YEAR-MO-DA HO:MI+ZONE', 'Revision-Date: $date');
+ contents =
+ contents.replaceFirst(RegExp(r'tor: [^\\]*'), 'tor: $translator');
+ contents = contents.replaceFirst(RegExp(r'Team: [^\\]*'), 'Team: $language $team');
+ contents = contents.replaceFirst(RegExp(r'uage: [^\\]*'), 'uage: $language');
+ contents = contents.replaceFirst('charset=CHARSET', 'charset=UTF-8');
+ contents = contents.replaceFirst(
+ '8bit', '8bit\\n"\n"Plural-Forms: nplurals=2; plural=(n != 1);');
+ target.writeAsStringSync(contents);
+ }
+ }
+
+ /// Stores the data of a plural text entry.
+ void putPluralKey(String key1, String key2, String module) {
+ if (!mapPlurals.containsKey(module)) {
+ mapPlurals[module] = <String, PluralValue>{};
+ }
+ String reference = '#: $currentFile:$currentLineNo';
+ if (module != errorModule && mapModules[module]?[key1] != null) {
+ logger.error('$currentFile-$currentLineNo: key already defined as normal '
+ 'key. Modify key with the invisible tag <#1> or <#2> or ... at the '
+ 'end to make it unique.\nFirst found in ' +
+ mapModules[module]![key1]!);
+ putText(key1, errorModule);
+ } else if (mapPlurals[module]!.containsKey(key1)) {
+ if (mapPlurals[module]![key1]!.keyPlural == key2) {
+ mapPlurals[module]![key1]!.references.add(reference);
+ } else {
+ logger.error('$currentFile-$currentLineNo: multiple definition of a '
+ 'plural key but keyPlural is different. Modify key1 with the'
+ ' invisible tag <#1> or <#2> or ... at the end to make it '
+ 'unique.\nFirst found in ' +
+ mapPlurals[module]![key1]!.references[0]);
+ }
+ } else {
+ mapPlurals[module]![key1] = PluralValue(key2, [reference]);
+ }
+ }
+
+ /// Stores the [key] in the [module] map with location info [filename] and
+ /// [lineNo].
+ void putText(String key, [String module = '']) {
+ if (module.isEmpty) {
+ module = currentModule;
+ }
+ if (!mapModules.containsKey(module)) {
+ mapModules[module] = <String, String>{};
+ }
+ if (module != errorModule && mapPlurals[module]?[key] != null) {
+ logger.error('$currentFile-$currentLineNo: key already defined as plural '
+ 'key. Modify key with the invisible tag <#1> or <#2> or ... at '
+ 'the end to make it unique.\nFirst found in ' +
+ mapPlurals[module]![key]!.references[0]);
+ putText(key, errorModule);
+ } else if (mapModules[module]!.containsKey(key)) {
+ mapModules[module]![key] =
+ mapModules[module]![key]! + '\n#: $currentFile:$currentLineNo';
+ } else {
+ mapModules[module]![key] = '#: $currentFile:$currentLineNo';
+ }
+ }
+
+ /// Scans recursively all dart files of the [directory].
+ void scanDirectory(String directory, int depth) {
+ final base = Directory(directory);
+ final subDirs = [];
+ try {
+ for (var file in base.listSync()) {
+ if (file is Directory) {
+ subDirs.add(basename(file.path));
+ continue;
+ }
+ if (regExpFiles.firstMatch(file.path) == null) {
+ continue;
+ }
+ if (file is File) {
+ scanFile(file, directory);
+ } else {
+ logger.error('ignored: $directory/${file.path}');
+ }
+ }
+ } on FileSystemException catch (exc) {
+ logger.error('ignored: $directory: $exc');
+ }
+ for (var node in subDirs) {
+ if (depth == 0 &&
+ regExpExcluded != null &&
+ regExpExcluded!.firstMatch(node) != null) {
+ logger.log('ignoring $node');
+ } else {
+ scanDirectory(
+ directory == '.' ? node : join(directory, node), depth + 1);
+ }
+ }
+ }
+
+ /// Scans the [file] for I18N texts.
+ /// [directory]: used for logging.
+ void scanFile(File file, String directory) {
+ logger.log('parsing ${file.path}', LEVEL_DETAIL);
+ try {
+ currentFile = file.path;
+ lines = file.readAsLinesSync();
+ moduleVariables.clear();
+ currentLineNo = 0;
+ for (var line in lines) {
+ ++currentLineNo;
+ RegExpMatch? match;
+ if ((match = regExpModule.firstMatch(line)) != null) {
+ currentModule = match!.group(3)!;
+ moduleVariables[match.group(1)!] = currentModule;
+ } else {
+ for (match in regExpText.allMatches(line)) {
+ currentArguments = line.substring(match.end);
+ switch (match.group(2)!) {
+ case 'tr':
+ handleSimpleText();
+ break;
+ case 'trMulti':
+ handleMultiText();
+ break;
+ case 'ntr':
+ handlePluralText();
+ break;
+ case 'trArgs':
+ handleTextWithArgs();
+ break;
+ }
+ }
+ }
+ }
+ } on FileSystemException catch (exc) {
+ logger.error('ignored: $directory: $exc');
+ }
+ }
+
+ /// Writes the *.pot files to the [directory].
+ void writePot(String directory) {
+ final configuration = fetchConfiguration(directory);
+ if (configuration != null) {
+ FileSync().ensureDirectory(directory);
+ final doneModules = <String>{};
+ for (var module in mapModules.keys) {
+ doneModules.add(module);
+ final map = mapModules[module]!;
+ final keys = map.keys.toList();
+ Map<String, PluralValue>? map2;
+ if (mapPlurals.containsKey(module)) {
+ map2 = mapPlurals[module];
+ keys.addAll(map2!.keys);
+ }
+ keys.sort();
+ final buffer = StringBuffer(
+ '# Texts of module $module, parsed by i18n_text_parser\n');
+ for (var key in keys) {
+ buffer.writeln();
+ buffer.writeln(map.containsKey(key)
+ ? map[key]
+ : map2![key]!.references.join('\n'));
+ var key2 = convertString(key);
+ buffer.writeln('msgid "$key2"');
+ if (map.containsKey(key)) {
+ buffer.writeln('msgstr ""');
+ } else {
+ key2 = convertString(map2![key]!.keyPlural);
+ buffer.writeln('msgid_plural "$key2"');
+ buffer.writeln('msgstr[0] ""');
+ buffer.writeln('msgstr[1] ""');
+ }
+ }
+ final fn = join(directory, '$module.pot');
+ logger.log('writing $fn', LEVEL_DETAIL);
+ File(fn).writeAsStringSync(buffer.toString());
+ }
+ for (var module in mapPlurals.keys) {
+ if (!doneModules.contains(module)) {
+ final map = mapPlurals[module];
+ final keys = map!.keys.toList();
+ keys.sort();
+ final buffer = StringBuffer(introduction(module, configuration));
+ for (var key in keys) {
+ buffer.writeln();
+ buffer.writeln(map[key]!.references.join('\n'));
+ var key2 = convertString(key);
+ buffer.writeln('msgid "$key2"');
+ key2 = convertString(map[key]!.keyPlural);
+ buffer.writeln('msgid_plural "$key2"');
+ buffer.writeln('msgstr[0] ""');
+ buffer.writeln('msgstr[1] ""');
+ }
+ final fn = join(directory, '$module.pot');
+ logger.log('writing $fn', LEVEL_DETAIL);
+ File(fn).writeAsStringSync(buffer.toString());
+ }
+ }
+ }
+ }
+}
+
+/// Stores the data of a plural text.
+class PluralValue {
+ final List<String> references;
+ final String keyPlural;
+ PluralValue(this.keyPlural, this.references);
+}
--- /dev/null
+import 'dart:io';
+
+import 'package:path/path.dart';
+import 'package:dart_bones/dart_bones.dart';
+
+/// Implements a merge tool for yaml files.
+/// @see merge() for description of merge.
+class Merger {
+ RegExp regExpKey = RegExp(r'^([a-zA-Z]\w+):\s*$');
+ RegExp regExpKeyValue = RegExp(r'^ ');
+ final BaseLogger logger;
+ final precedenceKeys = <String, String>{};
+ Merger(this.logger);
+
+ /// Parses the keys of a given [file] into [this.precedenceKeys].
+ void parseKeys(File file){
+ precedenceKeys.clear();
+ final lines = file.readAsLinesSync();
+ var state = 'undef';
+ var lastKey = '';
+ final keyValue = [];
+ for (var line in lines){
+ final match = regExpKey.firstMatch(line);
+ if (match != null){
+ if (keyValue.isNotEmpty){
+ precedenceKeys[lastKey] = keyValue.join('\n');
+ keyValue.clear();
+ }
+ state = 'inKey';
+ lastKey = match.group(1)!;
+ } else {
+ if (regExpKeyValue.firstMatch(line) != null){
+ if (state == 'inKey'){
+ keyValue.add(line);
+ }
+ }
+ }
+ }
+ if (keyValue.isNotEmpty){
+ precedenceKeys[lastKey] = keyValue.join('\n');
+ }
+ }
+ /// Merges the [fileSource] and [filePreference] into the [target] file.
+ void mergeOneFile(File fileSource, File filePreference, String target) {
+ parseKeys(filePreference);
+ final output = <String>[];
+ final foundKeys = <String>{};
+ final lines = fileSource.readAsLinesSync();
+ var state = 'undef';
+ var lastKey = '';
+ final keyValue = [];
+ for (var line in lines){
+ final match = regExpKey.firstMatch(line);
+ if (match != null) {
+ if (keyValue.isNotEmpty) {
+ output.add(lastKey + ':');
+ output.add(precedenceKeys.containsKey(lastKey)
+ ? precedenceKeys[lastKey]!
+ : keyValue.join('\n'));
+ }
+ keyValue.clear();
+ state = 'inKey';
+ lastKey = match.group(1)!;
+ if (foundKeys.contains(lastKey)){
+ logger.error('key $lastKey found multiple times');
+ }
+ foundKeys.add(lastKey);
+ } else {
+ if (regExpKeyValue.firstMatch(line) != null){
+ if (state == 'inKey'){
+ keyValue.add(line);
+ }
+ } else {
+ output.add(line);
+ }
+ }
+ }
+ if (keyValue.isNotEmpty){
+ output.add(lastKey + ':');
+ if (precedenceKeys.containsKey(lastKey)) {
+ output.add(precedenceKeys[lastKey]!);
+ } else {
+ output.add(keyValue.join('\n'));
+ }
+ }
+ for (var key in precedenceKeys.keys){
+ if (! foundKeys.contains(key)){
+ output.add(key + ':');
+ output.add(precedenceKeys[key]!);
+ }
+ }
+ File(target).writeAsStringSync(output.join('\n') + '\n');
+ }
+ /// Merges or copies files from two source directories into a target directory.
+ /// [pathSource] contains files with lower priorities.
+ /// [pathPreferences] contains files with higher priorities.
+ /// [pathTarget] is the target directory.
+ /// Only files with the same node will be merged.
+ /// Merging: the two source files contains keys. If a key is part of both
+ /// files the key content of of the higher priority file is taken.
+ void merge(String pathSource, String pathPreference, String pathTarget) {
+ final dirSource = Directory(pathSource);
+ final dirPreference = Directory(pathPreference);
+ final dirTarget = Directory(pathTarget);
+ if (!dirSource.existsSync()) {
+ logger.error('source not a directory: $pathSource');
+ } else if (!dirPreference.existsSync()) {
+ logger.error('source not a directory: $pathPreference');
+ } else if (!dirTarget.existsSync()) {
+ logger.error('source not a directory: $pathTarget');
+ } else {
+ /// Copy/merge the files from dirSource:
+ for (var file in dirSource.listSync()) {
+ final node = basename(file.path);
+ if (! node.endsWith('.yaml')){
+ continue;
+ }
+ final fnPreference = join(pathPreference, node);
+ final fnTarget = join(pathTarget, node);
+ final filePreference = File(fnPreference);
+ if (!(file is File)) {
+ logger.error('not a file: ${file.path} ignoring...');
+ } else if (filePreference.existsSync()) {
+ mergeOneFile(file, filePreference, fnTarget);
+ } else {
+ logger.log('copying ${file.path}', LEVEL_DETAIL);
+ file.copy(fnTarget);
+ }
+ }
+
+ /// Copy the "single" files from preferences:
+ for (var file in dirPreference.listSync()) {
+ final node = basename(file.path);
+ if (! node.endsWith('.yaml')){
+ continue;
+ }
+ final fnSource = join(pathSource, node);
+ final fnTarget = join(pathTarget, node);
+ File fileSource = File(fnSource);
+ if (file is File && !fileSource.existsSync()) {
+ logger.log('copying ${file.path}', LEVEL_DETAIL);
+ file.copy(fnTarget);
+ }
+ }
+ }
+ }
+}
+void main(List<String> args){
+ final logger = MemoryLogger(LEVEL_DETAIL);
+ if (args.length != 3){
+ print('''Usage: yaml_merger DIR_SOURCE DIR_PREFERENCE DIR_OUTPUT
+ Merges the yaml files from DIR_SOURCE (with lower priority) and DIR_PREFERENCE
+ (with higher priority) into files in DIR_OUTPUT.
+Version: 0.1.0
+Example:
+yaml_merger data/sql data/sql/precedence /tmp/sql
+''');
+ print('+++ wrong count of arguments: ${args.length} instead of 3');
+ } else {
+ final merger = Merger(logger);
+ merger.merge(args[0], args[1], args[2]);
+ }
+}
\ No newline at end of file
--- /dev/null
+name: dart_tools
+description: Some tools supporting the processes in the framework exhibition.
+version: 0.1.0
+# homepage: https://www.example.com
+
+environment:
+ sdk: '>=2.13.0 <3.0.0'
+
+dependencies:
+ http: ^0.13.0
+ args: ^2.1.0
+ path: ^1.8.0
+ yaml: ^3.1.0
+ dart_bones: ^1.2.1
+ sprintf: ^6.0.0
+
+dev_dependencies:
+ lints: ^1.0.0
+ test: ^1.16.0
--- /dev/null
+#! /bin/bash
+APP=i18n_text_parser
+TRG=tools/$APP
+dart compile exe bin/$APP.dart -o $TRG
+ls -ld $TRG
--- /dev/null
+#! /bin/bash
+APP=yaml_merger
+TRG=tools/$APP
+dart compile exe bin/$APP.dart -o $TRG
+ls -ld $TRG
--- /dev/null
+# Header entry was created by Lokalize.
+#
+# Texts of module !global, parsed by i18n_text_parser
+# J. Hamatoma <de@hamatoma.de>, 2021.
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"PO-Revision-Date: 2021-08-11 10:55+0200\n"
+"Last-Translator: J. Hamatoma <de@hamatoma.de>\n"
+"Language-Team: German <>\n"
+"Language: de\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: Lokalize 20.12.0\n"
+
+# Texts of module !global, parsed by i18n_text_parser
+#: lib/meta/users_meta.dart:32 lib/meta/roles_meta.dart:25
+msgid "Changed"
+msgstr "Geändert"
+
+#: lib/meta/users_meta.dart:34 lib/meta/roles_meta.dart:27
+msgid "Changed by"
+msgstr "Geändert von"
+
+#: lib/meta/users_meta.dart:27 lib/meta/roles_meta.dart:20
+msgid "Created"
+msgstr "Erzeugt"
+
+#: lib/meta/users_meta.dart:29 lib/meta/roles_meta.dart:22
+msgid "Created by"
+msgstr "Erzeugt von"
+
+#: lib/meta/users_meta.dart:14 lib/meta/roles_meta.dart:15
+msgid "Id"
+msgstr "Id"
+
+#: lib/meta/users_meta.dart:16 lib/meta/roles_meta.dart:17
+msgid "Name"
+msgstr "Name"
+
+#: lib/meta/users_meta.dart:25
+msgid "Role"
+msgstr "Rolle"
--- /dev/null
+# Header entry was created by Lokalize.
+#
+# Texts of module Users, parsed by i18n_text_parser
+# J. Hamatoma <de@hamatoma.de>, 2021.
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"PO-Revision-Date: 2021-08-11 10:54+0200\n"
+"Last-Translator: J. Hamatoma <de@hamatoma.de>\n"
+"Language-Team: German <>\n"
+"Language: de\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: Lokalize 20.12.0\n"
+
+# Texts of module Users, parsed by i18n_text_parser
+#: lib/meta/users_meta.dart:19
+msgid "Display name"
+msgstr "Anzeigenname"
+
+#: lib/meta/users_meta.dart:22
+msgid "EMail"
+msgstr "EMail"
--- /dev/null
+---
+# Configuration of the I18N data:
+project:
+ name: "Exhibition"
+ version: "1.0.0"
+author: "J. Hamatoma <author@hamatoma.de>"
+translator: "J. Hamatoma <lang@hamatoma.de>"
+de_DE:
+ language: "German"
+ name: "J. Hamatoma <de@hamatoma.de>"
+copyright:
+ name: "J. Hamatoma <cr@hamatoma.de>"
+ start: 2021
+license: "CC0 1.0 Universal"
+bugResponsible: "J. Hamatoma <bug@hamatoma.de>"
-class I18N {
- static I18N? instance;
- Map<String, Map<String, String>> modules = {};
- String locale = 'de';
- factory I18N() {
+import 'package:dart_bones/dart_bones.dart';
+typedef MapModule = Map<String, String>;
+typedef MapPlural = Map<String, PluralInfo>;
+class I18n {
+ static I18n? instance;
+ final BaseLogger logger;
+ final globalModule = '!global';
+ Map<String, MapModule> mapModules = {};
+ Map<String, MapPlural> mapPlural = {};
+ String locale = 'de_DE';
+ String loadedLocale = '';
+ final regExpTag = RegExp(r'<#\d+>$');
+ factory I18n() {
return instance!;
}
- I18N.internal(String directory) {
+
+ I18n.internal(this.logger) {
instance = this;
}
- String module(String name) {
- return name;
+
+ /// Defines the standard module.
+ /// Returns [name].
+ String module(String name) => name;
+
+ /// Translates the [keySingular] or [keyPlural] into the local language
+ /// using the namespace [module].
+ /// If [count] is 1 the translation of [keySingular] is returned.
+ /// Otherwise the translation of [keyPlural] is returned.
+ /// Returns the translation or (if not found) the key.
+ String ntr(String keySingular, String keyPlural, String module, int count) {
+ String? rc;
+ final map = mapPlural[module];
+ if (map == null) {
+ rc = removeInvisibleTag(count == 1 ? keySingular : keyPlural);
+ } else {
+ if (map.containsKey(keySingular)) {
+ final info = map[keySingular];
+ if (info!.list != null && count < info.list!.length) {
+ rc = info.list![count];
+ } else {
+ rc = count == 1 ? info.singular : info.plural;
+ }
+ } else {
+ if (module == globalModule) {
+ rc = removeInvisibleTag(count == 1 ? keySingular : keyPlural);
+ } else {
+ rc = ntr(keySingular, keyPlural, globalModule, count);
+ }
+ }
+ }
+ return rc;
+ }
+
+ /// If a key should be multiple times in the *.po file (same key must be
+ /// translated differently) it can be extended by an invisible tag at the end.
+ /// Than the key is unique.
+ /// Example of a tag: "<#3>".
+ /// This method removes the tag from the key.
+ String removeInvisibleTag(String key) {
+ String rc = key;
+ final match = regExpTag.firstMatch(key);
+ if (match != null) {
+ rc = key.substring(0, match.start);
+ }
+ return rc;
}
/// Translates the [key] into the local language using the namespace [module].
/// Returns the translation or (if not found) the key.
String tr(String key, [String module = '!global']) {
String? rc;
- final mapModule = modules[module];
- if (mapModule != null) {
+ final mapModule = mapModules[module];
+ if (mapModule == null) {
+ rc = removeInvisibleTag(key);
+ } else {
rc = mapModule[key];
if (rc == null) {
- if (module == '!global') {
- rc = key;
+ if (module == globalModule) {
+ rc = removeInvisibleTag(key);
} else {
- rc = tr(key, '!global');
+ rc = tr(key, globalModule);
}
}
}
- return rc!;
+ return rc;
}
/// Translates the [key] into the local language using the namespace [module].
}
return rc;
}
+}
- /// Translates the [keySingular] or [keyPlural] into the local language
- /// using the namespace [module].
- /// If [count] is 1 the translation of [keySingular] is returned.
- /// Otherwise the translation of [keyPlural] is returned.
- /// Returns the translation or (if not found) the key.
- String ntr(String keySingular, String keyPlural, String module, int count) {
- String rc = tr(count == 1 ? keySingular : keyPlural, module);
- return rc;
- }
+class PluralInfo {
+ final String singular;
+ final String plural;
+ List<String>? list;
+ PluralInfo(this.singular, this.plural, this.list);
}
--- /dev/null
+import 'dart:io';
+
+import 'package:dart_bones/dart_bones.dart';
+import 'package:path/path.dart';
+
+import 'i18n.dart';
+
+class I18nIo extends I18n {
+ String dataDirectory;
+ final regExpId = RegExp(r'^msgid "(.+?)"$');
+ final regExpTranslation = RegExp(r'^msgstr "(.+?)"$');
+ final regExpPlural = RegExp(r'^msg_plural "(.+?)"$');
+ final regExpList = RegExp(r'^msgstr\[\d+\] "(.+?)"$');
+ I18nIo.internal(this.dataDirectory, BaseLogger logger) : super.internal(logger);
+
+ /// Puts a [key] with its [translation] into the [mapModule].
+ /// Returns the [module] entry of [mapModule].
+ MapModule putKey(
+ String key, String translation, String module, MapModule? map) {
+ if (map == null) {
+ if (!mapModules.containsKey(module)) {
+ map = <String, String>{};
+ mapModules[module] = map;
+ } else {
+ map = mapModules[module];
+ }
+ }
+ map![key] = translation;
+ return map;
+ }
+
+ /// Puts a [key] with its [translation] into the [mapPlural].
+ /// Returns the [module] entry of [mapPlural].
+ MapPlural putPluralKey(String key, String translation, String plural,
+ String module, MapPlural? map) {
+ if (map == null) {
+ if (!mapPlural.containsKey(module)) {
+ map = <String, PluralInfo>{};
+ mapPlural[module] = map;
+ } else {
+ map = mapPlural[module];
+ }
+ }
+ map![key] = PluralInfo(translation, plural, null);
+ return map;
+ }
+
+ /// Converts a string given from a string constant.
+ /// Example: "a: \"b\"" returns 'a: "b"'
+ String fromConstant(String value) {
+ return value.replaceAll(r'\"', '"');
+ }
+
+ void readModule(String module, [String? locale]) {
+ locale ??= this.locale;
+ var file = File(join(dataDirectory, '$module.$locale.mo'));
+ if (!file.existsSync() && locale.length > 2) {
+ locale = locale.substring(0, 2);
+ file = File(join(dataDirectory, '$module.$locale.mo'));
+ }
+ if (!file.existsSync()) {
+ final lines = file.readAsLinesSync();
+ String lastKey = '';
+ String lastTranslation = '';
+ PluralInfo? lastEntry;
+ MapModule? map1;
+ MapPlural? map2;
+ RegExpMatch? match;
+ for (var line in lines) {
+ if (line.startsWith('#') || line.isEmpty) {
+ continue;
+ }
+ if ((match = regExpId.firstMatch(line)) != null) {
+ if (lastTranslation.isNotEmpty) {
+ map1 = putKey(lastKey, lastTranslation, module, map1);
+ }
+ lastKey = fromConstant(match!.group(1)!);
+ lastTranslation = '';
+ lastEntry = null;
+ } else if ((match = regExpTranslation.firstMatch(line)) != null) {
+ lastTranslation = fromConstant(match!.group(1)!);
+ } else if ((match = regExpPlural.firstMatch(line)) != null) {
+ map2 = putPluralKey(lastKey, lastTranslation,
+ fromConstant(match!.group(1)!), module, map2);
+ lastEntry = map2[lastKey];
+ lastKey = lastTranslation = '';
+ } else if ((match = regExpList.firstMatch(line)) != null) {
+ if (lastEntry != null) {
+ if (lastEntry.list == null) {
+ lastEntry.list = <String>[];
+ int count = lastEntry.list!.length;
+ if (count != int.parse(match!.group(1)!)){
+ logger.error('wrong index in $line. Expected: $count');
+ }
+ lastEntry.list!.add(fromConstant(match.group(2)!));
+ }
+ }
+ } else {
+ logger.error('unrecognized syntax: $line');
+ }
+ }
+ }
+ }
+}
// DO NOT CHANGE. This file is created by the meta_tool
-import 'dart:io';
import 'module_meta_data.dart';
import 'users_meta.dart';
/// Returns the meta data of the module given by [name].
import 'module_meta_data.dart';
import '../base/i18n.dart';
-final i18N = I18N();
+final i18N = I18n();
final M = i18N.module("Roles");
class RoleMeta extends ModuleMetaData {
'Roles',
[
PropertyMetaData('id', DataType.reference, ':primary:', 'combo',
- i18N.tr('Id', M)),
+ i18N.tr('Id')),
PropertyMetaData(
- 'name', DataType.string, ':notnull:', '', i18N.tr('Name', M),
+ 'name', DataType.string, ':notnull:', '', i18N.tr('Name'),
size: 64),
PropertyMetaData('created', DataType.datetime, ':hidden:', '',
- i18N.tr('Created', M)),
+ i18N.tr('Created')),
PropertyMetaData('createdBy', DataType.string, ':hidden:', '',
- i18N.tr('Created by', M),
+ i18N.tr('Created by'),
size: 32),
PropertyMetaData('changed', DataType.datetime, ':hidden:', '',
- i18N.tr('Changed', M)),
+ i18N.tr('Changed')),
PropertyMetaData('changedBy', DataType.string, ':hidden:', '',
- i18N.tr('Changed by', M),
+ i18N.tr('Changed by'),
size: 32),
],
);
import '../base/defines.dart';
import 'module_meta_data.dart';
import '../base/i18n.dart';
-final i18N = I18N();
+final i18N = I18n();
final M = i18N.module("Users");
class UserMeta extends ModuleMetaData {
'Users',
[
PropertyMetaData('id', DataType.reference, ':primary:', 'combo',
- i18N.tr('Id', M)),
+ i18N.tr('Id')),
PropertyMetaData('name', DataType.string, ':notnull:', '',
- i18N.tr('Name', M),
+ i18N.tr('Name'),
size: 64),
PropertyMetaData('displayName', DataType.string, ':unique:notnull:',
'', i18N.tr('Display name', M),
i18N.tr('EMail', M),
size: 255),
PropertyMetaData('role', DataType.reference, ':notnull:', '',
- i18N.tr('Role', M), reference: 'roles.role_id'),
+ i18N.tr('Role'), reference: 'roles.role_id'),
PropertyMetaData('created', DataType.datetime, ':hidden:', '',
- i18N.tr('Created', M)),
+ i18N.tr('Created')),
PropertyMetaData('createdBy', DataType.string, ':hidden:', '',
- i18N.tr('Created by', M),
+ i18N.tr('Created by'),
size: 32),
PropertyMetaData('changed', DataType.datetime, ':hidden:', '',
- i18N.tr('Changed', M)),
+ i18N.tr('Changed')),
PropertyMetaData('changedBy', DataType.string, ':hidden:', '',
- i18N.tr('Changed by', M),
+ i18N.tr('Changed by'),
size: 32),
],
);
---
+# DO NOT CHANGE. This file is created by the meta_tool
# SQL statements of the module "Users":
+
module: Users
list:
type: list
parameters: []
- sql: select * from loginusers;
+ sql: "select * from Users;"
byId:
type: record
- parameters: [ ":id" ]
- sql: "select * from loginusers where user_id=:id;"
+ parameters: [ "id" ]
+ sql: "select * from Users where user_id=:id;"
delete:
type: delete
- parameters: [ ":id" ]
- sql: "delete * from loginusers where user_id=:id;"
+ parameters: [ "id" ]
+ sql: "delete * from Users where user_id=:id;"
update:
type: update
- parameters: [":id", ":name", ":displayname", ":email", ":changedby"]
- sql: "UPDATE loginusers SET
- user_name=:name, user_displayname=:displayname, user_email=:email, user_changed=NOW(), user_changedby=:changedby
+ parameters: [":id",":name",":displayName",":email",":role",":changedBy"]
+ sql: "UPDATE Users SET
+ user_id=:id,user_name=:name,user_displayname=:displayName,user_email=:email,
+ user_role=:role,user_changedby=:changedBy,user_changed=NOW()
WHERE user_id=:id;"
insert:
type: insert
- parameters: [":name", ":displayname", ":email", ":createdby"]
- sql: "INSERT INTO loginusers(user_name, user_displayname, user_email, user_changedby)
- VALUES(:name, :displayname, :email, NOW(), :createdby);"
-
-
-
+ parameters: [":id",":name",":displayName",":email",":role",":createdBy"]
+ sql: "INSERT INTO Users(user_id,user_name,user_displayname,user_email,
+ user_role,user_createdby,user_created)
+ VALUES(:id,:name,:displayName,:email,:role,:createdBy,NOW());"
--- /dev/null
+import 'package:dart_bones/dart_bones.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:path/path.dart';
+
+import '../dart_tools/bin/i18n_text_parser.dart';
+
+void main() {
+ final logger = MemoryLogger(LEVEL_FINE);
+ FileSync.initialize(logger);
+ final fileSync = FileSync();
+ final baseDir = init(logger);
+ final targetDir = join(baseDir, nodeTarget);
+ test('parse', () {
+ final parser = I18nTextParser(logger);
+ parser.scanDirectory(baseDir, 0);
+ parser.writePot(targetDir);
+ expect(parser.mapModules, isNotEmpty);
+ expect(parser.mapModules.containsKey('Example'), isTrue);
+ expect(parser.mapModules.containsKey('!global'), isTrue);
+ expect(parser.mapModules.containsKey('Sample'), isTrue);
+ expect(parser.mapModules['Example']!.containsKey('introduction'), isTrue);
+ expect(parser.mapModules['Sample']!.containsKey('info'), isTrue);
+ var fn = join(targetDir, 'Example.pot');
+ var content = fileSync.fileAsString(fn);
+ expect(content, '''# Texts of module Example, parsed by i18n_text_parser
+
+#: /tmp/unittest/i18n/lib/base/simple.dart:5
+msgid "description"
+msgstr ""
+
+#: /tmp/unittest/i18n/lib/base/simple.dart:5
+msgid "introduction"
+msgstr ""
+''');
+ fn = join(targetDir, 'Sample.pot');
+ content = fileSync.fileAsString(fn);
+ expect(content, '''# Texts of module Sample, parsed by i18n_text_parser
+
+#: /tmp/unittest/i18n/lib/base/args.dart:8
+msgid "Name: {0} Role: {1}"
+msgstr ""
+
+#: /tmp/unittest/i18n/lib/base/args.dart:8
+msgid "info"
+msgstr ""
+
+#: /tmp/unittest/i18n/lib/base/args.dart:4
+msgid "one piece"
+msgid_plural "%d pieces"
+msgstr[0] ""
+msgstr[1] ""
+''');
+ fn = join(targetDir, '!global.pot');
+ content = fileSync.fileAsString(fn);
+ expect(content, '''# Texts of module !global, parsed by i18n_text_parser
+
+#: /tmp/unittest/i18n/lib/base/simple.dart:11
+#: /tmp/unittest/i18n/lib/base/args.dart:12
+msgid "Status line"
+msgstr ""
+''');
+ fn = join(targetDir, 'Statistic.pot');
+ content = fileSync.fileAsString(fn);
+ expect(content, '''# Texts of module Statistic, parsed by i18n_text_parser
+
+#: /tmp/unittest/i18n/lib/base/simple.dart:9
+msgid "one piece"
+msgid_plural "%d pieces"
+msgstr[0] ""
+msgstr[1] ""
+''');
+ });
+}
+
+const nodeTarget = 'output';
+
+String init(MemoryLogger logger) {
+ final fileSync = FileSync();
+ final baseDir = fileSync.tempDirectory('i18n', subDirs: 'unittest');
+ final subDir = fileSync.tempDirectory('base', subDirs: 'unittest/i18n/lib');
+ fileSync.toFile(join(subDir, 'simple.dart'), r'''import 'dart:io';
+final i18N = I18N();
+final M = i18N.module('Example');
+String header(){
+ return i18N.tr('introduction', M) + " " + i18N.tr(
+ "description",
+ M);
+}
+String count() => i18N.ntr('one piece', '%d pieces', "Statistic", 3);
+String footer(){
+ return I18N().tr("Status line");
+}
+''');
+ fileSync.toFile(join(subDir, 'args.dart'), r'''import 'dart:io';
+final i18N = I18N();
+final M = i18N.module("Sample");
+String count() => i18N.ntr(
+ 'one piece',
+ '%d pieces', M, countItems('any'));
+String header(String user, String role){
+ return i18N.trArgs('Name: {0} Role: {1}', M, [user, role]) + " " + i18N.tr(
+ r'info', M);
+}
+String footer(){
+ return I18N().tr('Status line');
+}
+''');
+ return baseDir;
+}
+import 'dart:io';
+
import 'package:dart_bones/dart_bones.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:path/path.dart';
-import "../bin/i18n_text_parser.dart";
+import '../dart_tools/bin/i18n_text_parser.dart';
void main() {
final logger = MemoryLogger(LEVEL_FINE);
final fileSync = FileSync();
final baseDir = init(logger);
final targetDir = join(baseDir, nodeTarget);
+ fileSync.ensureDirectory(targetDir);
+ test('example-config', (){
+ final parser = I18nTextParser(logger);
+ parser.exampleConfiguration(targetDir, 'de_DE');
+ expect(File(join(targetDir, 'project.i18n.yaml')).existsSync(), isTrue);
+ expect(parser.fetchConfiguration(targetDir, locale: 'de_DE'), isNotNull);
+ });
test('parse', () {
final parser = I18nTextParser(logger);
- parser.scanDirectory(baseDir);
+ parser.scanDirectory(baseDir, 0);
parser.writePot(targetDir);
expect(parser.mapModules, isNotEmpty);
expect(parser.mapModules.containsKey('Example'), isTrue);
var content = fileSync.fileAsString(fn);
expect(content, '''# Texts of module Example, parsed by i18n_text_parser
-#: /tmp/unittest/i18n/lib/base/simple.dart:5
+#: $baseDir/lib/base/simple.dart:5
msgid "description"
msgstr ""
-#: /tmp/unittest/i18n/lib/base/simple.dart:5
+#: $baseDir/lib/base/simple.dart:5
msgid "introduction"
msgstr ""
''');
content = fileSync.fileAsString(fn);
expect(content, '''# Texts of module Sample, parsed by i18n_text_parser
-#: /tmp/unittest/i18n/lib/base/args.dart:8
+#: $baseDir/lib/base/args.dart:8
msgid "Name: {0} Role: {1}"
msgstr ""
-#: /tmp/unittest/i18n/lib/base/args.dart:8
+#: $baseDir/lib/base/args.dart:8
msgid "info"
msgstr ""
-#: /tmp/unittest/i18n/lib/base/args.dart:4
+#: $baseDir/lib/base/args.dart:4
msgid "one piece"
msgid_plural "%d pieces"
msgstr[0] ""
content = fileSync.fileAsString(fn);
expect(content, '''# Texts of module !global, parsed by i18n_text_parser
-#: /tmp/unittest/i18n/lib/base/simple.dart:11
-#: /tmp/unittest/i18n/lib/base/args.dart:12
+#: $baseDir/lib/base/simple.dart:11
+#: $baseDir/lib/base/args.dart:12
msgid "Status line"
msgstr ""
''');
fn = join(targetDir, 'Statistic.pot');
content = fileSync.fileAsString(fn);
- expect(content, '''# Texts of module Statistic, parsed by i18n_text_parser
+ expect(content.replaceAll(RegExp(r'Date: 2[-+\w2: ]+'), 'Date: *'),
+ r'''# Texts of module Statistic, created by i18n_text_parser');
+# Copyright (C) 2021-2021 J. Hamatoma <cr@hamatoma.de>
+# License: CC0 1.0 Universal
+# J. Hamatoma <author@hamatoma.de>, 2021.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: <PROJECT>\n"
+"Report-Msgid-Bugs-To: J. Hamatoma <bug@hamatoma.de>\n"
+"POT-Creation-Date: *\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: LANGUAGE\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: /tmp/unittest/i18n_parser/lib/base/simple.dart:9
+msgid "one piece"
+msgid_plural "%d pieces"
+msgstr[0] ""
+msgstr[1] ""
+''');
+ });
+ test('msg-merge', (){
+ final parser = I18nTextParser(logger);
+ parser.msgInit(targetDir, 'Statistic', 'de_DE');
+ final fn = join(targetDir, 'Statistic.de_DE.po');
+ final content = fileSync.fileAsString(fn);
+ expect(content.replaceAll(RegExp(r'Date: 2[-+\w2: ]+'), 'Date: *'),
+ r'''# Texts of module Statistic, created by i18n_text_parser');
+# Copyright (C) 2021-2021 J. Hamatoma <cr@hamatoma.de>
+# License: CC0 1.0 Universal
+# J. Hamatoma <author@hamatoma.de>, 2021.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: <PROJECT>\n"
+"Report-Msgid-Bugs-To: J. Hamatoma <bug@hamatoma.de>\n"
+"POT-Creation-Date: *\n"
+"PO-Revision-Date: *\n"
+"Last-Translator: J. Hamatoma <lang@hamatoma.de>\n"
+"Language-Team: German J. Hamatoma <de@hamatoma.de>\n"
+"Language: German\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-#: /tmp/unittest/i18n/lib/base/simple.dart:9
+#: /tmp/unittest/i18n_parser/lib/base/simple.dart:9
msgid "one piece"
msgid_plural "%d pieces"
msgstr[0] ""
String init(MemoryLogger logger) {
final fileSync = FileSync();
- final baseDir = fileSync.tempDirectory('i18n', subDirs: 'unittest');
- final subDir = fileSync.tempDirectory('base', subDirs: 'unittest/i18n/lib');
+ final baseDir = fileSync.tempDirectory('i18n_parser', subDirs: 'unittest');
+ final subDir =
+ fileSync.tempDirectory('base', subDirs: 'unittest/i18n_parser/lib');
fileSync.toFile(join(subDir, 'simple.dart'), r'''import 'dart:io';
final i18N = I18N();
final M = i18N.module('Example');
import 'package:flutter_test/flutter_test.dart';
import 'package:path/path.dart';
-import "../bin/yaml_merger.dart";
+import '../dart_tools/bin/yaml_merger.dart';
const nodePrecedence = 'precedence';
const nodeTarget = 'output';
+++ /dev/null
-#! /bin/bash
-APP=yaml_merger
-TRG=tools/$APP
-dart compile exe bin/$APP.dart -o $TRG
-ls -ld $TRG
--- /dev/null
+#! /bin/bash
+LOCALE=de_DE
+if [ -n "$1" ]; then
+ LOCALE=$1
+fi
+function Usage() {
+ echo "Usage: UpdateI18n [<locale>]"
+ echo " Creates/updates the translation data."
+ echo " Precondition: current directory must contain data/i18n"
+ echo "<locale>: the locale id, e.g. "de_DE". Default: de_DE"
+ echo "Example:"
+ echo "UpdateI18n de_DE"
+ echo "+++ $*"
+}
+function Update(){
+ local locale=$1
+ cd data/i18n
+ for potFile in *.pot; do
+ local name=${potFile/.pot/}
+ if [ ! -e $name.$LOCALE.po ]; then
+ echo "creating $name.$locale.po"
+ i18n_text_parser msg-init . $name $locale
+ else
+ echo "updating $name.$locale.po"
+ msgmerge --update $name.$locale.po $potFile
+ fi
+ done
+}
+if [ ! -d data/i18n ]; then
+ Usage "missing data/i18n. Wrong current directory?"
+elif [ $(expr $LOCALE : [a-z][a-z]) = 0 -o $(expr $LOCALE : [a-z][a-z]_[A-Z][A-Z]) = 0 ]; then
+ Usage "illegal <locale>: $LOCALE"
+else
+ Update $LOCALE
+fi
\ No newline at end of file