gen_l10n.dart tool testing (#44856)
* Add tests to gen_l10n.dart tool * Separate out LocalizationsGenerator class to improve testability of code * Add testing dependencies to dev/tools * Integrate dev/tools testing to flutter CI * Restructure dev/tools folder for testing * Fix license headers
This commit is contained in:
parent
066b5a1667
commit
37e66b2179
@ -423,6 +423,7 @@ Future<void> _runFrameworkTests() async {
|
||||
await _pubRunTest(path.join(flutterRoot, 'dev', 'bots'), tableData: bigqueryApi?.tabledata);
|
||||
await _pubRunTest(path.join(flutterRoot, 'dev', 'devicelab'), tableData: bigqueryApi?.tabledata);
|
||||
await _pubRunTest(path.join(flutterRoot, 'dev', 'snippets'), tableData: bigqueryApi?.tabledata);
|
||||
await _pubRunTest(path.join(flutterRoot, 'dev', 'tools'), tableData: bigqueryApi?.tabledata);
|
||||
await _runFlutterTest(path.join(flutterRoot, 'dev', 'integration_tests', 'android_semantics_testing'), tableData: bigqueryApi?.tabledata);
|
||||
await _runFlutterTest(path.join(flutterRoot, 'dev', 'manual_tests'), tableData: bigqueryApi?.tabledata);
|
||||
await _runFlutterTest(path.join(flutterRoot, 'dev', 'tools', 'vitool'), tableData: bigqueryApi?.tabledata);
|
||||
|
101
dev/tools/localization/bin/gen_l10n.dart
Normal file
101
dev/tools/localization/bin/gen_l10n.dart
Normal file
@ -0,0 +1,101 @@
|
||||
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:args/args.dart' as argslib;
|
||||
import 'package:file/local.dart' as local;
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
import '../gen_l10n.dart';
|
||||
import '../localizations_utils.dart';
|
||||
|
||||
Future<void> main(List<String> arguments) async {
|
||||
final argslib.ArgParser parser = argslib.ArgParser();
|
||||
parser.addFlag(
|
||||
'help',
|
||||
defaultsTo: false,
|
||||
negatable: false,
|
||||
help: 'Print this help message.',
|
||||
);
|
||||
parser.addOption(
|
||||
'arb-dir',
|
||||
defaultsTo: path.join('lib', 'l10n'),
|
||||
help: 'The directory where all localization files should reside. For '
|
||||
'example, the template and translated arb files should be located here. '
|
||||
'Also, the generated output messages Dart files for each locale and the '
|
||||
'generated localizations classes will be created here.',
|
||||
);
|
||||
parser.addOption(
|
||||
'template-arb-file',
|
||||
defaultsTo: 'app_en.arb',
|
||||
help: 'The template arb file that will be used as the basis for '
|
||||
'generating the Dart localization and messages files.',
|
||||
);
|
||||
parser.addOption(
|
||||
'output-localization-file',
|
||||
defaultsTo: 'app_localizations.dart',
|
||||
help: 'The filename for the output localization and localizations '
|
||||
'delegate classes.',
|
||||
);
|
||||
parser.addOption(
|
||||
'output-class',
|
||||
defaultsTo: 'AppLocalizations',
|
||||
help: 'The Dart class name to use for the output localization and '
|
||||
'localizations delegate classes.',
|
||||
);
|
||||
|
||||
final argslib.ArgResults results = parser.parse(arguments);
|
||||
if (results['help'] == true) {
|
||||
print(parser.usage);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
final String arbPathString = results['arb-dir'];
|
||||
final String outputFileString = results['output-localization-file'];
|
||||
final String templateArbFileName = results['template-arb-file'];
|
||||
final String classNameString = results['output-class'];
|
||||
|
||||
const local.LocalFileSystem fs = local.LocalFileSystem();
|
||||
final LocalizationsGenerator localizationsGenerator = LocalizationsGenerator(fs);
|
||||
try {
|
||||
localizationsGenerator
|
||||
..initialize(
|
||||
l10nDirectoryPath: arbPathString,
|
||||
templateArbFileName: templateArbFileName,
|
||||
outputFileString: outputFileString,
|
||||
classNameString: classNameString,
|
||||
)
|
||||
..parseArbFiles()
|
||||
..generateClassMethods()
|
||||
..generateOutputFile();
|
||||
} on FileSystemException catch (e) {
|
||||
exitWithError(e.message);
|
||||
} on FormatException catch (e) {
|
||||
exitWithError(e.message);
|
||||
} on L10nException catch (e) {
|
||||
exitWithError(e.message);
|
||||
}
|
||||
|
||||
final ProcessResult pubGetResult = await Process.run('flutter', <String>['pub', 'get']);
|
||||
if (pubGetResult.exitCode != 0) {
|
||||
stderr.write(pubGetResult.stderr);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
final ProcessResult generateFromArbResult = await Process.run('flutter', <String>[
|
||||
'pub',
|
||||
'run',
|
||||
'intl_translation:generate_from_arb',
|
||||
'--output-dir=${localizationsGenerator.l10nDirectory.path}',
|
||||
'--no-use-deferred-loading',
|
||||
localizationsGenerator.outputFile.path,
|
||||
...localizationsGenerator.arbFilenames,
|
||||
]);
|
||||
if (generateFromArbResult.exitCode != 0) {
|
||||
stderr.write(generateFromArbResult.stderr);
|
||||
exit(1);
|
||||
}
|
||||
}
|
@ -2,11 +2,11 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:args/args.dart' as argslib;
|
||||
import 'package:file/file.dart' as file;
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
import 'localizations_utils.dart';
|
||||
@ -149,10 +149,6 @@ const String pluralMethodTemplate = '''
|
||||
}
|
||||
''';
|
||||
|
||||
int sortFilesByPath (FileSystemEntity a, FileSystemEntity b) {
|
||||
return a.path.compareTo(b.path);
|
||||
}
|
||||
|
||||
List<String> genMethodParameters(Map<String, dynamic> bundle, String key, String type) {
|
||||
final Map<String, dynamic> attributesMap = bundle['@$key'] as Map<String, dynamic>;
|
||||
if (attributesMap != null && attributesMap.containsKey('placeholders')) {
|
||||
@ -193,7 +189,7 @@ String genSimpleMethod(Map<String, dynamic> bundle, String key) {
|
||||
|
||||
final Map<String, dynamic> attributesMap = bundle['@$key'] as Map<String, dynamic>;
|
||||
if (attributesMap == null)
|
||||
exitWithError(
|
||||
throw L10nException(
|
||||
'Resource attribute "@$key" was not found. Please ensure that each '
|
||||
'resource id has a corresponding resource attribute.'
|
||||
);
|
||||
@ -289,6 +285,19 @@ bool _isValidClassName(String className) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _isNotReadable(FileStat fileStat) {
|
||||
final String rawStatString = fileStat.modeString();
|
||||
// Removes potential prepended permission bits, such as '(suid)' and '(guid)'.
|
||||
final String statString = rawStatString.substring(rawStatString.length - 9);
|
||||
return !(statString[0] == 'r' || statString[3] == 'r' || statString[6] == 'r');
|
||||
}
|
||||
bool _isNotWritable(FileStat fileStat) {
|
||||
final String rawStatString = fileStat.modeString();
|
||||
// Removes potential prepended permission bits, such as '(suid)' and '(guid)'.
|
||||
final String statString = rawStatString.substring(rawStatString.length - 9);
|
||||
return !(statString[1] == 'w' || statString[4] == 'w' || statString[7] == 'w');
|
||||
}
|
||||
|
||||
bool _isValidGetterAndMethodName(String name) {
|
||||
// Dart getter and method name cannot contain non-alphanumeric symbols
|
||||
if (name.contains(RegExp(r'[^a-zA-Z\d]')))
|
||||
@ -302,184 +311,250 @@ bool _isValidGetterAndMethodName(String name) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _isDirectoryReadableAndWritable(String statString) {
|
||||
if (statString[0] == '-' || statString[1] == '-')
|
||||
return false;
|
||||
return true;
|
||||
/// The localizations generation class used to generate the localizations
|
||||
/// classes, as well as all pertinent Dart files required to internationalize a
|
||||
/// Flutter application.
|
||||
class LocalizationsGenerator {
|
||||
/// Creates an instance of the localizations generator class.
|
||||
///
|
||||
/// It takes in a [FileSystem] representation that the class will act upon.
|
||||
LocalizationsGenerator(this._fs);
|
||||
|
||||
static RegExp arbFilenameLocaleRE = RegExp(r'^[^_]*_(\w+)\.arb$');
|
||||
static RegExp arbFilenameRE = RegExp(r'(\w+)\.arb$');
|
||||
static RegExp pluralValueRE = RegExp(r'^\s*\{[\w\s,]*,\s*plural\s*,');
|
||||
|
||||
final file.FileSystem _fs;
|
||||
|
||||
/// The reference to the project's l10n directory.
|
||||
///
|
||||
/// It is assumed that all input files (e.g. [templateArbFile], arb files
|
||||
/// for translated messages) and output files (e.g. The localizations
|
||||
/// [outputFile], `messages_<locale>.dart` and `messages_all.dart`)
|
||||
/// will reside here.
|
||||
///
|
||||
/// This directory is specified with the [initialize] method.
|
||||
Directory l10nDirectory;
|
||||
|
||||
/// The input arb file which defines all of the messages that will be
|
||||
/// exported by the generated class that's written to [outputFile].
|
||||
///
|
||||
/// This file is specified with the [initialize] method.
|
||||
File templateArbFile;
|
||||
|
||||
/// The file to write the generated localizations and localizations delegate
|
||||
/// classes to.
|
||||
///
|
||||
/// This file is specified with the [initialize] method.
|
||||
File outputFile;
|
||||
|
||||
/// The class name to be used for the localizations class in [outputFile].
|
||||
///
|
||||
/// For example, if 'AppLocalizations' is passed in, a class named
|
||||
/// AppLocalizations will be used for localized message lookups.
|
||||
///
|
||||
/// The class name is specified with the [initialize] method.
|
||||
String get className => _className;
|
||||
String _className;
|
||||
/// Sets the [className] for the localizations and localizations delegate
|
||||
/// classes.
|
||||
|
||||
/// The list of all arb files in [l10nDirectory].
|
||||
final List<String> arbFilenames = <String>[];
|
||||
|
||||
/// The supported language codes as found in the arb files located in
|
||||
/// [l10nDirectory].
|
||||
final Set<String> supportedLanguageCodes = <String>{};
|
||||
|
||||
/// The supported locales as found in the arb files located in
|
||||
/// [l10nDirectory].
|
||||
final Set<LocaleInfo> supportedLocales = <LocaleInfo>{};
|
||||
|
||||
/// The class methods that will be generated in the localizations class
|
||||
/// based on messages found in the template arb file.
|
||||
final List<String> classMethods = <String>[];
|
||||
|
||||
/// Initializes [l10nDirectory], [templateArbFile], [outputFile] and [className].
|
||||
///
|
||||
/// Throws an [L10nException] when a provided configuration is not allowed
|
||||
/// by [LocalizationsGenerator].
|
||||
///
|
||||
/// Throws a [FileSystemException] when a file operation necessary for setting
|
||||
/// up the [LocalizationsGenerator] cannot be completed.
|
||||
void initialize({
|
||||
String l10nDirectoryPath,
|
||||
String templateArbFileName,
|
||||
String outputFileString,
|
||||
String classNameString,
|
||||
}) {
|
||||
setL10nDirectory(l10nDirectoryPath);
|
||||
setTemplateArbFile(templateArbFileName);
|
||||
setOutputFile(outputFileString);
|
||||
className = classNameString;
|
||||
}
|
||||
|
||||
String _importFilePath(String path, String fileName) {
|
||||
final String replaceLib = path.replaceAll('lib/', '');
|
||||
return '$replaceLib/$fileName';
|
||||
}
|
||||
|
||||
Future<void> main(List<String> arguments) async {
|
||||
final argslib.ArgParser parser = argslib.ArgParser();
|
||||
parser.addFlag(
|
||||
'help',
|
||||
defaultsTo: false,
|
||||
negatable: false,
|
||||
help: 'Print this help message.',
|
||||
);
|
||||
parser.addOption(
|
||||
'arb-dir',
|
||||
defaultsTo: path.join('lib', 'l10n'),
|
||||
help: 'The directory where all localization files should reside. For '
|
||||
'example, the template and translated arb files should be located here. '
|
||||
'Also, the generated output messages Dart files for each locale and the '
|
||||
'generated localizations classes will be created here.',
|
||||
);
|
||||
parser.addOption(
|
||||
'template-arb-file',
|
||||
defaultsTo: 'app_en.arb',
|
||||
help: 'The template arb file that will be used as the basis for '
|
||||
'generating the Dart localization and messages files.',
|
||||
);
|
||||
parser.addOption(
|
||||
'output-localization-file',
|
||||
defaultsTo: 'app_localizations.dart',
|
||||
help: 'The filename for the output localization and localizations '
|
||||
'delegate classes.',
|
||||
);
|
||||
parser.addOption(
|
||||
'output-class',
|
||||
defaultsTo: 'AppLocalizations',
|
||||
help: 'The Dart class name to use for the output localization and '
|
||||
'localizations delegate classes.',
|
||||
);
|
||||
|
||||
final argslib.ArgResults results = parser.parse(arguments);
|
||||
if (results['help'] == true) {
|
||||
print(parser.usage);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
final String arbPathString = results['arb-dir'] as String;
|
||||
final String outputFileString = results['output-localization-file'] as String;
|
||||
|
||||
final Directory l10nDirectory = Directory(arbPathString);
|
||||
final File templateArbFile = File(path.join(l10nDirectory.path, results['template-arb-file'] as String));
|
||||
final File outputFile = File(path.join(l10nDirectory.path, outputFileString));
|
||||
final String stringsClassName = results['output-class'] as String;
|
||||
|
||||
/// Sets the reference [Directory] for [l10nDirectory].
|
||||
@visibleForTesting
|
||||
void setL10nDirectory(String arbPathString) {
|
||||
if (arbPathString == null)
|
||||
throw L10nException('arbPathString argument cannot be null');
|
||||
l10nDirectory = _fs.directory(arbPathString);
|
||||
if (!l10nDirectory.existsSync())
|
||||
exitWithError(
|
||||
throw FileSystemException(
|
||||
"The 'arb-dir' directory, $l10nDirectory, does not exist.\n"
|
||||
'Make sure that the correct path was provided.'
|
||||
);
|
||||
final String l10nDirectoryStatModeString = l10nDirectory.statSync().modeString();
|
||||
if (!_isDirectoryReadableAndWritable(l10nDirectoryStatModeString))
|
||||
exitWithError(
|
||||
|
||||
final FileStat fileStat = l10nDirectory.statSync();
|
||||
if (_isNotReadable(fileStat) || _isNotWritable(fileStat))
|
||||
throw FileSystemException(
|
||||
"The 'arb-dir' directory, $l10nDirectory, doesn't allow reading and writing.\n"
|
||||
'Please ensure that the user has read and write permissions.'
|
||||
);
|
||||
}
|
||||
|
||||
/// Sets the reference [File] for [templateArbFile].
|
||||
@visibleForTesting
|
||||
void setTemplateArbFile(String templateArbFileName) {
|
||||
if (templateArbFileName == null)
|
||||
throw L10nException('templateArbFileName argument cannot be null');
|
||||
if (l10nDirectory == null)
|
||||
throw L10nException('l10nDirectory cannot be null when setting template arb file');
|
||||
|
||||
templateArbFile = _fs.file(path.join(l10nDirectory.path, templateArbFileName));
|
||||
final String templateArbFileStatModeString = templateArbFile.statSync().modeString();
|
||||
if (templateArbFileStatModeString[0] == '-')
|
||||
exitWithError(
|
||||
if (templateArbFileStatModeString[0] == '-' && templateArbFileStatModeString[3] == '-')
|
||||
throw FileSystemException(
|
||||
"The 'template-arb-file', $templateArbFile, is not readable.\n"
|
||||
'Please ensure that the user has read permissions.'
|
||||
);
|
||||
if (!_isValidClassName(stringsClassName))
|
||||
exitWithError(
|
||||
"The 'output-class', $stringsClassName, is not valid Dart class name.\n"
|
||||
}
|
||||
|
||||
/// Sets the reference [File] for the localizations delegate [outputFile].
|
||||
@visibleForTesting
|
||||
void setOutputFile(String outputFileString) {
|
||||
if (outputFileString == null)
|
||||
throw L10nException('outputFileString argument cannot be null');
|
||||
outputFile = _fs.file(path.join(l10nDirectory.path, outputFileString));
|
||||
}
|
||||
|
||||
@visibleForTesting
|
||||
set className(String classNameString) {
|
||||
if (classNameString == null)
|
||||
throw L10nException('classNameString argument cannot be null');
|
||||
if (!_isValidClassName(classNameString))
|
||||
throw L10nException(
|
||||
"The 'output-class', $classNameString, is not a valid Dart class name.\n"
|
||||
);
|
||||
_className = classNameString;
|
||||
}
|
||||
|
||||
final List<String> arbFilenames = <String>[];
|
||||
final Set<String> supportedLanguageCodes = <String>{};
|
||||
final Set<LocaleInfo> supportedLocales = <LocaleInfo>{};
|
||||
|
||||
for (FileSystemEntity entity in l10nDirectory.listSync().toList()..sort(sortFilesByPath)) {
|
||||
final String entityPath = entity.path;
|
||||
|
||||
if (FileSystemEntity.isFileSync(entityPath)) {
|
||||
final RegExp arbFilenameRE = RegExp(r'(\w+)\.arb$');
|
||||
if (arbFilenameRE.hasMatch(entityPath)) {
|
||||
final File arbFile = File(entityPath);
|
||||
final Map<String, dynamic> arbContents = json.decode(arbFile.readAsStringSync()) as Map<String, dynamic>;
|
||||
String localeString = arbContents['@@locale'] as String;
|
||||
/// Scans [l10nDirectory] for arb files and parses them for language and locale
|
||||
/// information.
|
||||
void parseArbFiles() {
|
||||
final List<File> fileSystemEntityList = l10nDirectory
|
||||
.listSync()
|
||||
.whereType<File>()
|
||||
.toList();
|
||||
final List<LocaleInfo> localeInfoList = <LocaleInfo>[];
|
||||
|
||||
for (File file in fileSystemEntityList) {
|
||||
final String filePath = file.path;
|
||||
if (arbFilenameRE.hasMatch(filePath)) {
|
||||
final Map<String, dynamic> arbContents = json.decode(file.readAsStringSync());
|
||||
String localeString = arbContents['@@locale'];
|
||||
if (localeString == null) {
|
||||
final RegExp arbFilenameLocaleRE = RegExp(r'^[^_]*_(\w+)\.arb$');
|
||||
final RegExpMatch arbFileMatch = arbFilenameLocaleRE.firstMatch(entityPath);
|
||||
final RegExpMatch arbFileMatch = arbFilenameLocaleRE.firstMatch(filePath);
|
||||
if (arbFileMatch == null) {
|
||||
exitWithError(
|
||||
throw L10nException(
|
||||
"The following .arb file's locale could not be determined: \n"
|
||||
'$entityPath \n'
|
||||
'$filePath \n'
|
||||
"Make sure that the locale is specified in the '@@locale' "
|
||||
'property or as part of the filename (ie. file_en.arb)'
|
||||
'property or as part of the filename (e.g. file_en.arb)'
|
||||
);
|
||||
}
|
||||
|
||||
localeString = arbFilenameLocaleRE.firstMatch(entityPath)[1];
|
||||
localeString = arbFilenameLocaleRE.firstMatch(filePath)[1];
|
||||
}
|
||||
|
||||
arbFilenames.add(entityPath);
|
||||
arbFilenames.add(filePath);
|
||||
final LocaleInfo localeInfo = LocaleInfo.fromString(localeString);
|
||||
if (supportedLocales.contains(localeInfo))
|
||||
exitWithError(
|
||||
if (localeInfoList.contains(localeInfo))
|
||||
throw L10nException(
|
||||
'Multiple arb files with the same locale detected. \n'
|
||||
'Ensure that there is exactly one arb file for each locale.'
|
||||
);
|
||||
supportedLocales.add(localeInfo);
|
||||
supportedLanguageCodes.add('\'${localeInfo.languageCode}\'');
|
||||
}
|
||||
localeInfoList.add(localeInfo);
|
||||
}
|
||||
}
|
||||
|
||||
final List<String> classMethods = <String>[];
|
||||
localeInfoList.sort();
|
||||
supportedLocales.addAll(localeInfoList);
|
||||
supportedLanguageCodes.addAll(localeInfoList.map((LocaleInfo localeInfo) {
|
||||
return '\'${localeInfo.languageCode}\'';
|
||||
}));
|
||||
}
|
||||
|
||||
/// Generates the methods for the localizations class.
|
||||
///
|
||||
/// The method parses [templateArbFile] and uses its resource ids as the
|
||||
/// Dart method and getter names. It then uses each resource id's
|
||||
/// corresponding resource value to figure out how to define these getters.
|
||||
///
|
||||
/// For example, a message with plurals will be handled differently from
|
||||
/// a simple, singular message.
|
||||
///
|
||||
/// Throws an [L10nException] when a provided configuration is not allowed
|
||||
/// by [LocalizationsGenerator].
|
||||
///
|
||||
/// Throws a [FileSystemException] when a file operation necessary for setting
|
||||
/// up the [LocalizationsGenerator] cannot be completed.
|
||||
///
|
||||
/// Throws a [FormatException] when parsing the arb file is unsuccessful.
|
||||
void generateClassMethods() {
|
||||
Map<String, dynamic> bundle;
|
||||
try {
|
||||
bundle = json.decode(templateArbFile.readAsStringSync()) as Map<String, dynamic>;
|
||||
bundle = json.decode(templateArbFile.readAsStringSync());
|
||||
} on FileSystemException catch (e) {
|
||||
exitWithError('Unable to read input arb file: $e');
|
||||
throw FileSystemException('Unable to read input arb file: $e');
|
||||
} on FormatException catch (e) {
|
||||
exitWithError('Unable to parse arb file: $e');
|
||||
throw FormatException('Unable to parse arb file: $e');
|
||||
}
|
||||
|
||||
final RegExp pluralValueRE = RegExp(r'^\s*\{[\w\s,]*,\s*plural\s*,');
|
||||
|
||||
for (String key in bundle.keys.toList()..sort()) {
|
||||
final List<String> sortedArbKeys = bundle.keys.toList()..sort();
|
||||
for (String key in sortedArbKeys) {
|
||||
if (key.startsWith('@'))
|
||||
continue;
|
||||
if (!_isValidGetterAndMethodName(key))
|
||||
exitWithError(
|
||||
throw L10nException(
|
||||
'Invalid key format: $key \n It has to be in camel case, cannot start '
|
||||
'with a number, and cannot contain non-alphanumeric characters.'
|
||||
);
|
||||
if (pluralValueRE.hasMatch(bundle[key] as String))
|
||||
if (pluralValueRE.hasMatch(bundle[key]))
|
||||
classMethods.add(genPluralMethod(bundle, key));
|
||||
else
|
||||
classMethods.add(genSimpleMethod(bundle, key));
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates a file that contains the localizations class and the
|
||||
/// LocalizationsDelegate class.
|
||||
void generateOutputFile() {
|
||||
final String directory = path.basename(l10nDirectory.path);
|
||||
final String outputFileName = path.basename(outputFile.path);
|
||||
outputFile.writeAsStringSync(
|
||||
defaultFileTemplate
|
||||
.replaceAll('@className', stringsClassName)
|
||||
.replaceAll('@className', className)
|
||||
.replaceAll('@classMethods', classMethods.join('\n'))
|
||||
.replaceAll('@importFile', _importFilePath(arbPathString, outputFileString))
|
||||
.replaceAll('@importFile', '$directory/$outputFileName')
|
||||
.replaceAll('@supportedLocales', genSupportedLocaleProperty(supportedLocales))
|
||||
.replaceAll('@supportedLanguageCodes', supportedLanguageCodes.toList().join(', '))
|
||||
);
|
||||
|
||||
final ProcessResult pubGetResult = await Process.run('flutter', <String>['pub', 'get']);
|
||||
if (pubGetResult.exitCode != 0) {
|
||||
stderr.write(pubGetResult.stderr);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
final ProcessResult generateFromArbResult = await Process.run('flutter', <String>[
|
||||
'pub',
|
||||
'pub',
|
||||
'run',
|
||||
'intl_translation:generate_from_arb',
|
||||
'--output-dir=${l10nDirectory.path}',
|
||||
'--no-use-deferred-loading',
|
||||
outputFile.path,
|
||||
...arbFilenames,
|
||||
]);
|
||||
if (generateFromArbResult.exitCode != 0) {
|
||||
stderr.write(generateFromArbResult.stderr);
|
||||
exit(1);
|
||||
}
|
||||
class L10nException implements Exception {
|
||||
L10nException(this.message);
|
||||
|
||||
final String message;
|
||||
}
|
||||
|
@ -29,12 +29,44 @@ dependencies:
|
||||
typed_data: 1.1.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
|
||||
dev_dependencies:
|
||||
test: 1.9.4
|
||||
test_api: 0.2.11
|
||||
mockito: 4.1.1
|
||||
|
||||
analyzer: 0.38.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
boolean_selector: 1.0.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
coverage: 0.13.3+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
csslib: 0.16.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
front_end: 0.1.27 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
glob: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
html: 0.14.0+3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
http_multi_server: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
io: 0.3.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
js: 0.6.1+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
kernel: 0.3.27 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
logging: 0.11.3+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
matcher: 0.12.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
mime: 0.9.6+3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
multi_server_socket: 1.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
node_interop: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
node_io: 1.0.1+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
node_preamble: 1.4.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
package_config: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
package_resolver: 1.0.10 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
pool: 1.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
pub_semver: 1.4.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
shelf: 0.7.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
shelf_packages_handler: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
shelf_static: 0.2.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
shelf_web_socket: 0.2.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
source_map_stack_trace: 1.1.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
source_maps: 0.10.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
stack_trace: 1.9.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
stream_channel: 2.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
test_core: 0.2.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
vm_service: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
watcher: 0.9.7+13 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
web_socket_channel: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
yaml: 2.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
|
||||
# PUBSPEC CHECKSUM: a9e9
|
||||
# PUBSPEC CHECKSUM: 3590
|
||||
|
27
dev/tools/test/common.dart
Normal file
27
dev/tools/test/common.dart
Normal file
@ -0,0 +1,27 @@
|
||||
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:test/test.dart' hide TypeMatcher, isInstanceOf;
|
||||
import 'package:test/test.dart' as test_package show TypeMatcher;
|
||||
|
||||
export 'package:test/test.dart' hide TypeMatcher, isInstanceOf;
|
||||
|
||||
// Defines a 'package:test' shim.
|
||||
// TODO(ianh): Remove this file once https://github.com/dart-lang/matcher/issues/98 is fixed
|
||||
|
||||
/// A matcher that compares the type of the actual value to the type argument T.
|
||||
Matcher isInstanceOf<T>() => test_package.TypeMatcher<T>();
|
||||
|
||||
void tryToDelete(Directory directory) {
|
||||
// This should not be necessary, but it turns out that
|
||||
// on Windows it's common for deletions to fail due to
|
||||
// bogus (we think) "access denied" errors.
|
||||
try {
|
||||
directory.deleteSync(recursive: true);
|
||||
} on FileSystemException catch (error) {
|
||||
print('Failed to delete ${directory.path}: $error');
|
||||
}
|
||||
}
|
716
dev/tools/test/localization/gen_l10n_test.dart
Normal file
716
dev/tools/test/localization/gen_l10n_test.dart
Normal file
@ -0,0 +1,716 @@
|
||||
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:file/file.dart';
|
||||
import 'package:file/memory.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
import '../../localization/gen_l10n.dart';
|
||||
import '../../localization/localizations_utils.dart';
|
||||
|
||||
import '../common.dart';
|
||||
|
||||
final String defaultArbPathString = path.join('lib', 'l10n');
|
||||
const String defaultTemplateArbFileName = 'app_en_US.arb';
|
||||
const String defaultOutputFileString = 'output-localization-file';
|
||||
const String defaultClassNameString = 'AppLocalizations';
|
||||
const String singleMessageArbFileString = '''{
|
||||
"title": "Stocks",
|
||||
"@title": {
|
||||
"description": "Title for the Stocks application"
|
||||
}
|
||||
}''';
|
||||
|
||||
const String esArbFileName = 'app_es.arb';
|
||||
const String singleEsMessageArbFileString = '''{
|
||||
"title": "Acciones"
|
||||
}''';
|
||||
|
||||
void _standardFlutterDirectoryL10nSetup(FileSystem fs) {
|
||||
final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n')
|
||||
..createSync(recursive: true);
|
||||
l10nDirectory.childFile(defaultTemplateArbFileName)
|
||||
.writeAsStringSync(singleMessageArbFileString);
|
||||
l10nDirectory.childFile(esArbFileName)
|
||||
.writeAsStringSync(singleEsMessageArbFileString);
|
||||
}
|
||||
|
||||
void main() {
|
||||
MemoryFileSystem fs;
|
||||
|
||||
setUp(() {
|
||||
fs = MemoryFileSystem(
|
||||
style: Platform.isWindows ? FileSystemStyle.windows : FileSystemStyle.posix
|
||||
);
|
||||
});
|
||||
|
||||
group('LocalizationsGenerator setters:', () {
|
||||
test('happy path', () {
|
||||
_standardFlutterDirectoryL10nSetup(fs);
|
||||
expect(() {
|
||||
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
|
||||
generator.initialize(
|
||||
l10nDirectoryPath: defaultArbPathString,
|
||||
templateArbFileName: defaultTemplateArbFileName,
|
||||
outputFileString: defaultOutputFileString,
|
||||
classNameString: defaultClassNameString,
|
||||
);
|
||||
}, returnsNormally);
|
||||
});
|
||||
|
||||
test('setL10nDirectory fails if the directory does not exist', () {
|
||||
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
|
||||
try {
|
||||
generator.setL10nDirectory('lib');
|
||||
} on FileSystemException catch (e) {
|
||||
expect(e.message, contains('Make sure that the correct path was provided'));
|
||||
return;
|
||||
}
|
||||
|
||||
fail(
|
||||
'Attempting to set LocalizationsGenerator.setL10nDirectory should fail if the '
|
||||
'directory does not exist.'
|
||||
);
|
||||
});
|
||||
|
||||
test('setL10nDirectory fails if input string is null', () {
|
||||
_standardFlutterDirectoryL10nSetup(fs);
|
||||
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
|
||||
try {
|
||||
generator.setL10nDirectory(null);
|
||||
} on L10nException catch (e) {
|
||||
expect(e.message, contains('cannot be null'));
|
||||
return;
|
||||
}
|
||||
|
||||
fail(
|
||||
'Attempting to set LocalizationsGenerator.setL10nDirectory should fail if the '
|
||||
'the input string is null.'
|
||||
);
|
||||
});
|
||||
|
||||
test('setTemplateArbFile fails if l10nDirectory is null', () {
|
||||
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
|
||||
try {
|
||||
generator.setTemplateArbFile(defaultTemplateArbFileName);
|
||||
} on L10nException catch (e) {
|
||||
expect(e.message, contains('cannot be null'));
|
||||
return;
|
||||
}
|
||||
|
||||
fail(
|
||||
'Attempting to set LocalizationsGenerator.setTemplateArbFile should fail if the '
|
||||
'the l10nDirectory is null.'
|
||||
);
|
||||
});
|
||||
|
||||
test('setTemplateArbFile fails if templateArbFileName is null', () {
|
||||
_standardFlutterDirectoryL10nSetup(fs);
|
||||
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
|
||||
try {
|
||||
generator.setTemplateArbFile(null);
|
||||
} on L10nException catch (e) {
|
||||
expect(e.message, contains('cannot be null'));
|
||||
return;
|
||||
}
|
||||
|
||||
fail(
|
||||
'Attempting to set LocalizationsGenerator.setTemplateArbFile should fail if the '
|
||||
'the l10nDirectory is null.'
|
||||
);
|
||||
});
|
||||
|
||||
test('setTemplateArbFile fails if input string is null', () {
|
||||
_standardFlutterDirectoryL10nSetup(fs);
|
||||
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
|
||||
try {
|
||||
generator.setTemplateArbFile(null);
|
||||
} on L10nException catch (e) {
|
||||
expect(e.message, contains('cannot be null'));
|
||||
return;
|
||||
}
|
||||
|
||||
fail(
|
||||
'Attempting to set LocalizationsGenerator.setTemplateArbFile should fail if the '
|
||||
'the input string is null.'
|
||||
);
|
||||
});
|
||||
|
||||
test('setOutputFile fails if input string is null', () {
|
||||
_standardFlutterDirectoryL10nSetup(fs);
|
||||
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
|
||||
try {
|
||||
generator.setOutputFile(null);
|
||||
} on L10nException catch (e) {
|
||||
expect(e.message, contains('cannot be null'));
|
||||
return;
|
||||
}
|
||||
|
||||
fail(
|
||||
'Attempting to set LocalizationsGenerator.setOutputFile should fail if the '
|
||||
'the input string is null.'
|
||||
);
|
||||
});
|
||||
|
||||
test('setting className fails if input string is null', () {
|
||||
_standardFlutterDirectoryL10nSetup(fs);
|
||||
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
|
||||
try {
|
||||
generator.className = null;
|
||||
} on L10nException catch (e) {
|
||||
expect(e.message, contains('cannot be null'));
|
||||
return;
|
||||
}
|
||||
|
||||
fail(
|
||||
'Attempting to set LocalizationsGenerator.className should fail if the '
|
||||
'the input string is null.'
|
||||
);
|
||||
});
|
||||
|
||||
group('className should only take valid Dart class names:', () {
|
||||
LocalizationsGenerator generator;
|
||||
setUp(() {
|
||||
_standardFlutterDirectoryL10nSetup(fs);
|
||||
generator = LocalizationsGenerator(fs);
|
||||
try {
|
||||
generator.setL10nDirectory(defaultArbPathString);
|
||||
generator.setTemplateArbFile(defaultTemplateArbFileName);
|
||||
generator.setOutputFile(defaultOutputFileString);
|
||||
} on L10nException catch (e) {
|
||||
throw TestFailure('Unexpected failure during test setup: ${e.message}');
|
||||
}
|
||||
});
|
||||
|
||||
test('fails on string with spaces', () {
|
||||
try {
|
||||
generator.className = 'String with spaces';
|
||||
} on L10nException catch (e) {
|
||||
expect(e.message, contains('is not a valid Dart class name'));
|
||||
return;
|
||||
}
|
||||
fail(
|
||||
'Attempting to set LocalizationsGenerator.className should fail if the '
|
||||
'the input string is not a valid Dart class name.'
|
||||
);
|
||||
});
|
||||
|
||||
test('fails on non-alphanumeric symbols', () {
|
||||
try {
|
||||
generator.className = 'TestClass@123';
|
||||
} on L10nException catch (e) {
|
||||
expect(e.message, contains('is not a valid Dart class name'));
|
||||
return;
|
||||
}
|
||||
fail(
|
||||
'Attempting to set LocalizationsGenerator.className should fail if the '
|
||||
'the input string is not a valid Dart class name.'
|
||||
);
|
||||
});
|
||||
|
||||
test('fails on camel-case', () {
|
||||
try {
|
||||
generator.className = 'camelCaseClassName';
|
||||
} on L10nException catch (e) {
|
||||
expect(e.message, contains('is not a valid Dart class name'));
|
||||
return;
|
||||
}
|
||||
fail(
|
||||
'Attempting to set LocalizationsGenerator.className should fail if the '
|
||||
'the input string is not a valid Dart class name.'
|
||||
);
|
||||
});
|
||||
|
||||
test('fails when starting with a number', () {
|
||||
try {
|
||||
generator.className = '123ClassName';
|
||||
} on L10nException catch (e) {
|
||||
expect(e.message, contains('is not a valid Dart class name'));
|
||||
return;
|
||||
}
|
||||
fail(
|
||||
'Attempting to set LocalizationsGenerator.className should fail if the '
|
||||
'the input string is not a valid Dart class name.'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
group('LocalizationsGenerator.parseArbFiles:', () {
|
||||
test('correctly initializes supportedLocales and supportedLanguageCodes properties', () {
|
||||
_standardFlutterDirectoryL10nSetup(fs);
|
||||
|
||||
LocalizationsGenerator generator;
|
||||
try {
|
||||
generator = LocalizationsGenerator(fs);
|
||||
generator.initialize(
|
||||
l10nDirectoryPath: defaultArbPathString,
|
||||
templateArbFileName: defaultTemplateArbFileName,
|
||||
outputFileString: defaultOutputFileString,
|
||||
classNameString: defaultClassNameString,
|
||||
);
|
||||
generator.parseArbFiles();
|
||||
} on L10nException catch (e) {
|
||||
fail('Setting language and locales should not fail: \n$e');
|
||||
}
|
||||
|
||||
expect(generator.supportedLocales.contains(LocaleInfo.fromString('en_US')), true);
|
||||
expect(generator.supportedLocales.contains(LocaleInfo.fromString('es')), true);
|
||||
});
|
||||
|
||||
test('correctly parses @@locale property in arb file', () {
|
||||
const String arbFileWithEnLocale = '''{
|
||||
"@@locale": "en",
|
||||
"title": "Stocks",
|
||||
"@title": {
|
||||
"description": "Title for the Stocks application"
|
||||
}
|
||||
}''';
|
||||
|
||||
const String arbFileWithZhLocale = '''{
|
||||
"@@locale": "zh",
|
||||
"title": "Stocks",
|
||||
"@title": {
|
||||
"description": "Title for the Stocks application"
|
||||
}
|
||||
}''';
|
||||
|
||||
final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n')
|
||||
..createSync(recursive: true);
|
||||
l10nDirectory.childFile('first_file.arb')
|
||||
.writeAsStringSync(arbFileWithEnLocale);
|
||||
l10nDirectory.childFile('second_file.arb')
|
||||
.writeAsStringSync(arbFileWithZhLocale);
|
||||
|
||||
LocalizationsGenerator generator;
|
||||
try {
|
||||
generator = LocalizationsGenerator(fs);
|
||||
generator.initialize(
|
||||
l10nDirectoryPath: defaultArbPathString,
|
||||
templateArbFileName: 'first_file.arb',
|
||||
outputFileString: defaultOutputFileString,
|
||||
classNameString: defaultClassNameString,
|
||||
);
|
||||
generator.parseArbFiles();
|
||||
} on L10nException catch (e) {
|
||||
fail('Setting language and locales should not fail: \n$e');
|
||||
}
|
||||
|
||||
expect(generator.supportedLocales.contains(LocaleInfo.fromString('en')), true);
|
||||
expect(generator.supportedLocales.contains(LocaleInfo.fromString('zh')), true);
|
||||
});
|
||||
|
||||
test('correctly parses @@locale property in arb file', () {
|
||||
const String arbFileWithEnLocale = '''{
|
||||
"@@locale": "en",
|
||||
"title": "Stocks",
|
||||
"@title": {
|
||||
"description": "Title for the Stocks application"
|
||||
}
|
||||
}''';
|
||||
|
||||
const String arbFileWithZhLocale = '''{
|
||||
"@@locale": "zh",
|
||||
"title": "Stocks",
|
||||
"@title": {
|
||||
"description": "Title for the Stocks application"
|
||||
}
|
||||
}''';
|
||||
|
||||
final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n')
|
||||
..createSync(recursive: true);
|
||||
l10nDirectory.childFile('app_es.arb')
|
||||
.writeAsStringSync(arbFileWithEnLocale);
|
||||
l10nDirectory.childFile('app_am.arb')
|
||||
.writeAsStringSync(arbFileWithZhLocale);
|
||||
|
||||
LocalizationsGenerator generator;
|
||||
try {
|
||||
generator = LocalizationsGenerator(fs);
|
||||
generator.initialize(
|
||||
l10nDirectoryPath: defaultArbPathString,
|
||||
templateArbFileName: 'app_es.arb',
|
||||
outputFileString: defaultOutputFileString,
|
||||
classNameString: defaultClassNameString,
|
||||
);
|
||||
generator.parseArbFiles();
|
||||
} on L10nException catch (e) {
|
||||
fail('Setting language and locales should not fail: \n$e');
|
||||
}
|
||||
|
||||
// @@locale property should hold higher priority
|
||||
expect(generator.supportedLocales.contains(LocaleInfo.fromString('en')), true);
|
||||
expect(generator.supportedLocales.contains(LocaleInfo.fromString('zh')), true);
|
||||
// filename should not be used since @@locale is specified
|
||||
expect(generator.supportedLocales.contains(LocaleInfo.fromString('es')), false);
|
||||
expect(generator.supportedLocales.contains(LocaleInfo.fromString('am')), false);
|
||||
});
|
||||
|
||||
test('throws when arb file\'s locale could not be determined', () {
|
||||
fs.currentDirectory.childDirectory('lib').childDirectory('l10n')
|
||||
..createSync(recursive: true)
|
||||
..childFile('app.arb')
|
||||
.writeAsStringSync(singleMessageArbFileString);
|
||||
try {
|
||||
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
|
||||
generator.initialize(
|
||||
l10nDirectoryPath: defaultArbPathString,
|
||||
templateArbFileName: 'app.arb',
|
||||
outputFileString: defaultOutputFileString,
|
||||
classNameString: defaultClassNameString,
|
||||
);
|
||||
generator.parseArbFiles();
|
||||
} on L10nException catch (e) {
|
||||
expect(e.message, contains('locale could not be determined'));
|
||||
return;
|
||||
}
|
||||
fail(
|
||||
'Since locale is not specified, setting languages and locales '
|
||||
'should fail'
|
||||
);
|
||||
});
|
||||
test('throws when the same locale is detected more than once', () {
|
||||
const String secondMessageArbFileString = '''{
|
||||
"market": "MARKET",
|
||||
"@market": {
|
||||
"description": "Label for the Market tab"
|
||||
}
|
||||
}''';
|
||||
|
||||
final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n')
|
||||
..createSync(recursive: true);
|
||||
l10nDirectory.childFile('app_en.arb')
|
||||
.writeAsStringSync(singleMessageArbFileString);
|
||||
l10nDirectory.childFile('app2_en.arb')
|
||||
.writeAsStringSync(secondMessageArbFileString);
|
||||
|
||||
try {
|
||||
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
|
||||
generator.initialize(
|
||||
l10nDirectoryPath: defaultArbPathString,
|
||||
templateArbFileName: 'app_en.arb',
|
||||
outputFileString: defaultOutputFileString,
|
||||
classNameString: defaultClassNameString,
|
||||
);
|
||||
generator.parseArbFiles();
|
||||
} on L10nException catch (e) {
|
||||
expect(e.message, contains('Multiple arb files with the same locale detected'));
|
||||
return;
|
||||
}
|
||||
|
||||
fail(
|
||||
'Since en locale is specified twice, setting languages and locales '
|
||||
'should fail'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('LocalizationsGenerator.generateClassMethods:', () {
|
||||
test('correctly generates a simple message with getter:', () {
|
||||
_standardFlutterDirectoryL10nSetup(fs);
|
||||
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
|
||||
try {
|
||||
generator.initialize(
|
||||
l10nDirectoryPath: defaultArbPathString,
|
||||
templateArbFileName: defaultTemplateArbFileName,
|
||||
outputFileString: defaultOutputFileString,
|
||||
classNameString: defaultClassNameString,
|
||||
);
|
||||
generator.parseArbFiles();
|
||||
generator.generateClassMethods();
|
||||
} on Exception catch (e) {
|
||||
fail('Parsing template arb file should succeed: \n$e');
|
||||
}
|
||||
|
||||
expect(generator.classMethods, isNotEmpty);
|
||||
expect(
|
||||
generator.classMethods.first,
|
||||
''' String get title {
|
||||
return Intl.message(
|
||||
r'Stocks',
|
||||
locale: _localeName,
|
||||
name: 'title',
|
||||
desc: r'Title for the Stocks application'
|
||||
);
|
||||
}
|
||||
''');
|
||||
});
|
||||
|
||||
test('correctly generates simple message method with parameters', () {
|
||||
const String singleSimpleMessageWithPlaceholderArbFileString = '''{
|
||||
"itemNumber": "Item {value}",
|
||||
"@itemNumber": {
|
||||
"description": "Item placement in list.",
|
||||
"placeholders": {
|
||||
"value": {
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
}
|
||||
}''';
|
||||
final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n')
|
||||
..createSync(recursive: true);
|
||||
l10nDirectory.childFile(defaultTemplateArbFileName)
|
||||
.writeAsStringSync(singleSimpleMessageWithPlaceholderArbFileString);
|
||||
|
||||
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
|
||||
try {
|
||||
generator.initialize(
|
||||
l10nDirectoryPath: defaultArbPathString,
|
||||
templateArbFileName: defaultTemplateArbFileName,
|
||||
outputFileString: defaultOutputFileString,
|
||||
classNameString: defaultClassNameString,
|
||||
);
|
||||
generator.parseArbFiles();
|
||||
generator.generateClassMethods();
|
||||
} on Exception catch (e) {
|
||||
fail('Parsing template arb file should succeed: \n$e');
|
||||
}
|
||||
|
||||
expect(generator.classMethods, isNotEmpty);
|
||||
expect(
|
||||
generator.classMethods.first,
|
||||
''' String itemNumber(Object value) {
|
||||
return Intl.message(
|
||||
r\'Item \$value\',
|
||||
locale: _localeName,
|
||||
name: 'itemNumber',
|
||||
desc: r\'Item placement in list.\',
|
||||
args: <Object>[value]
|
||||
);
|
||||
}
|
||||
''');
|
||||
});
|
||||
|
||||
test('correctly generates a plural message:', () {
|
||||
const String singlePluralMessageArbFileString = '''{
|
||||
"helloWorlds": "{count,plural, =0{Hello}=1{Hello World}=2{Hello two worlds}few{Hello {count} worlds}many{Hello all {count} worlds}other{Hello other {count} worlds}}",
|
||||
"@helloWorlds": {
|
||||
"placeholders": {
|
||||
"count": {}
|
||||
}
|
||||
}
|
||||
}''';
|
||||
final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n')
|
||||
..createSync(recursive: true);
|
||||
l10nDirectory.childFile(defaultTemplateArbFileName)
|
||||
.writeAsStringSync(singlePluralMessageArbFileString);
|
||||
|
||||
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
|
||||
try {
|
||||
generator.initialize(
|
||||
l10nDirectoryPath: defaultArbPathString,
|
||||
templateArbFileName: defaultTemplateArbFileName,
|
||||
outputFileString: defaultOutputFileString,
|
||||
classNameString: defaultClassNameString,
|
||||
);
|
||||
generator.parseArbFiles();
|
||||
generator.generateClassMethods();
|
||||
} on Exception catch (e) {
|
||||
fail('Parsing template arb file should succeed: \n$e');
|
||||
}
|
||||
|
||||
expect(generator.classMethods, isNotEmpty);
|
||||
expect(
|
||||
generator.classMethods.first,
|
||||
''' String helloWorlds(int count) {
|
||||
return Intl.plural(
|
||||
count,
|
||||
locale: _localeName,
|
||||
name: 'helloWorlds',
|
||||
args: <Object>[count],
|
||||
zero: 'Hello',
|
||||
one: 'Hello World',
|
||||
two: 'Hello two worlds',
|
||||
few: 'Hello \$count worlds',
|
||||
many: 'Hello all \$count worlds',
|
||||
other: 'Hello other \$count worlds'
|
||||
);
|
||||
}
|
||||
'''
|
||||
);
|
||||
});
|
||||
|
||||
test('should throw when failing to parse the arb file:', () {
|
||||
const String arbFileWithTrailingComma = '''{
|
||||
"title": "Stocks",
|
||||
"@title": {
|
||||
"description": "Title for the Stocks application"
|
||||
},
|
||||
}''';
|
||||
final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n')
|
||||
..createSync(recursive: true);
|
||||
l10nDirectory.childFile(defaultTemplateArbFileName)
|
||||
.writeAsStringSync(arbFileWithTrailingComma);
|
||||
|
||||
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
|
||||
try {
|
||||
generator.initialize(
|
||||
l10nDirectoryPath: defaultArbPathString,
|
||||
templateArbFileName: defaultTemplateArbFileName,
|
||||
outputFileString: defaultOutputFileString,
|
||||
classNameString: defaultClassNameString,
|
||||
);
|
||||
generator.parseArbFiles();
|
||||
generator.generateClassMethods();
|
||||
} on FormatException catch (e) {
|
||||
expect(e.message, contains('Unexpected character'));
|
||||
return;
|
||||
}
|
||||
|
||||
fail(
|
||||
'should fail with a FormatException due to a trailing comma in the '
|
||||
'arb file.'
|
||||
);
|
||||
});
|
||||
|
||||
test('should throw when resource is is missing resource attribute:', () {
|
||||
const String arbFileWithMissingResourceAttribute = '''{
|
||||
"title": "Stocks"
|
||||
}''';
|
||||
final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n')
|
||||
..createSync(recursive: true);
|
||||
l10nDirectory.childFile(defaultTemplateArbFileName)
|
||||
.writeAsStringSync(arbFileWithMissingResourceAttribute);
|
||||
|
||||
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
|
||||
try {
|
||||
generator.initialize(
|
||||
l10nDirectoryPath: defaultArbPathString,
|
||||
templateArbFileName: defaultTemplateArbFileName,
|
||||
outputFileString: defaultOutputFileString,
|
||||
classNameString: defaultClassNameString,
|
||||
);
|
||||
generator.parseArbFiles();
|
||||
generator.generateClassMethods();
|
||||
} on L10nException catch (e) {
|
||||
expect(e.message, contains('Resource attribute "@title" was not found'));
|
||||
return;
|
||||
}
|
||||
|
||||
fail(
|
||||
'should fail with a FormatException due to a trailing comma in the '
|
||||
'arb file.'
|
||||
);
|
||||
});
|
||||
|
||||
group('checks for method/getter formatting', () {
|
||||
test('cannot contain non-alphanumeric symbols', () {
|
||||
const String nonAlphaNumericArbFile = '''{
|
||||
"title!!": "Stocks",
|
||||
"@title!!": {
|
||||
"description": "Title for the Stocks application"
|
||||
}
|
||||
}''';
|
||||
final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n')
|
||||
..createSync(recursive: true);
|
||||
l10nDirectory.childFile(defaultTemplateArbFileName)
|
||||
.writeAsStringSync(nonAlphaNumericArbFile);
|
||||
|
||||
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
|
||||
try {
|
||||
generator.initialize(
|
||||
l10nDirectoryPath: defaultArbPathString,
|
||||
templateArbFileName: defaultTemplateArbFileName,
|
||||
outputFileString: defaultOutputFileString,
|
||||
classNameString: defaultClassNameString,
|
||||
);
|
||||
generator.parseArbFiles();
|
||||
generator.generateClassMethods();
|
||||
} on L10nException catch (e) {
|
||||
expect(e.message, contains('Invalid key format'));
|
||||
return;
|
||||
}
|
||||
|
||||
fail('should fail due to non-alphanumeric character.');
|
||||
});
|
||||
|
||||
test('must start with lowercase character', () {
|
||||
const String nonAlphaNumericArbFile = '''{
|
||||
"Title": "Stocks",
|
||||
"@Title": {
|
||||
"description": "Title for the Stocks application"
|
||||
}
|
||||
}''';
|
||||
final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n')
|
||||
..createSync(recursive: true);
|
||||
l10nDirectory.childFile(defaultTemplateArbFileName)
|
||||
.writeAsStringSync(nonAlphaNumericArbFile);
|
||||
|
||||
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
|
||||
try {
|
||||
generator.initialize(
|
||||
l10nDirectoryPath: defaultArbPathString,
|
||||
templateArbFileName: defaultTemplateArbFileName,
|
||||
outputFileString: defaultOutputFileString,
|
||||
classNameString: defaultClassNameString,
|
||||
);
|
||||
generator.parseArbFiles();
|
||||
generator.generateClassMethods();
|
||||
} on L10nException catch (e) {
|
||||
expect(e.message, contains('Invalid key format'));
|
||||
return;
|
||||
}
|
||||
|
||||
fail('should fail since key starts with a non-lowercase.');
|
||||
});
|
||||
|
||||
test('cannot start with a number', () {
|
||||
const String nonAlphaNumericArbFile = '''{
|
||||
"123title": "Stocks",
|
||||
"@123title": {
|
||||
"description": "Title for the Stocks application"
|
||||
}
|
||||
}''';
|
||||
final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n')
|
||||
..createSync(recursive: true);
|
||||
l10nDirectory.childFile(defaultTemplateArbFileName)
|
||||
.writeAsStringSync(nonAlphaNumericArbFile);
|
||||
|
||||
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
|
||||
try {
|
||||
generator.initialize(
|
||||
l10nDirectoryPath: defaultArbPathString,
|
||||
templateArbFileName: defaultTemplateArbFileName,
|
||||
outputFileString: defaultOutputFileString,
|
||||
classNameString: defaultClassNameString,
|
||||
);
|
||||
generator.parseArbFiles();
|
||||
generator.generateClassMethods();
|
||||
} on L10nException catch (e) {
|
||||
expect(e.message, contains('Invalid key format'));
|
||||
return;
|
||||
}
|
||||
|
||||
fail('should fail since key starts with a number.');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
group('LocalizationsGenerator.generateOutputFile:', () {
|
||||
test('correctly generates the localizations classes:', () {
|
||||
_standardFlutterDirectoryL10nSetup(fs);
|
||||
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
|
||||
try {
|
||||
generator.initialize(
|
||||
l10nDirectoryPath: defaultArbPathString,
|
||||
templateArbFileName: defaultTemplateArbFileName,
|
||||
outputFileString: defaultOutputFileString,
|
||||
classNameString: defaultClassNameString,
|
||||
);
|
||||
generator.parseArbFiles();
|
||||
generator.generateClassMethods();
|
||||
generator.generateOutputFile();
|
||||
} on Exception catch (e) {
|
||||
fail('Generating output localization file should succeed: \n$e');
|
||||
}
|
||||
|
||||
final String outputFileString = generator.outputFile.readAsStringSync();
|
||||
expect(outputFileString, contains('class AppLocalizations'));
|
||||
expect(outputFileString, contains('class _AppLocalizationsDelegate extends LocalizationsDelegate<AppLocalizations>'));
|
||||
});
|
||||
});
|
||||
}
|
@ -15,7 +15,7 @@ for more info.
|
||||
`messages_all.dart`, and `stock_strings.dart` with the following command:
|
||||
|
||||
```dart
|
||||
dart ${FLUTTER_PATH}/dev/tools/localization/gen_l10n.dart --arb-dir=lib/i18n \
|
||||
dart ${FLUTTER_PATH}/dev/tools/localization/bin/gen_l10n.dart --arb-dir=lib/i18n \
|
||||
--template-arb-file=stocks_en_US.arb --output-localization-file=stock_strings.dart \
|
||||
--output-class=StockStrings
|
||||
```
|
||||
|
Loading…
x
Reference in New Issue
Block a user