Unverified Commit 37e66b21 authored by Shi-Hao Hong's avatar Shi-Hao Hong Committed by GitHub

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
parent 066b5a16
...@@ -423,6 +423,7 @@ Future<void> _runFrameworkTests() async { ...@@ -423,6 +423,7 @@ Future<void> _runFrameworkTests() async {
await _pubRunTest(path.join(flutterRoot, 'dev', 'bots'), tableData: bigqueryApi?.tabledata); 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', 'devicelab'), tableData: bigqueryApi?.tabledata);
await _pubRunTest(path.join(flutterRoot, 'dev', 'snippets'), 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', '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', 'manual_tests'), tableData: bigqueryApi?.tabledata);
await _runFlutterTest(path.join(flutterRoot, 'dev', 'tools', 'vitool'), tableData: bigqueryApi?.tabledata); await _runFlutterTest(path.join(flutterRoot, 'dev', 'tools', 'vitool'), tableData: bigqueryApi?.tabledata);
......
// 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 @@ ...@@ -2,11 +2,11 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; 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 'package:path/path.dart' as path;
import 'localizations_utils.dart'; import 'localizations_utils.dart';
...@@ -149,10 +149,6 @@ const String pluralMethodTemplate = ''' ...@@ -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) { List<String> genMethodParameters(Map<String, dynamic> bundle, String key, String type) {
final Map<String, dynamic> attributesMap = bundle['@$key'] as Map<String, dynamic>; final Map<String, dynamic> attributesMap = bundle['@$key'] as Map<String, dynamic>;
if (attributesMap != null && attributesMap.containsKey('placeholders')) { if (attributesMap != null && attributesMap.containsKey('placeholders')) {
...@@ -193,7 +189,7 @@ String genSimpleMethod(Map<String, dynamic> bundle, String key) { ...@@ -193,7 +189,7 @@ String genSimpleMethod(Map<String, dynamic> bundle, String key) {
final Map<String, dynamic> attributesMap = bundle['@$key'] as Map<String, dynamic>; final Map<String, dynamic> attributesMap = bundle['@$key'] as Map<String, dynamic>;
if (attributesMap == null) if (attributesMap == null)
exitWithError( throw L10nException(
'Resource attribute "@$key" was not found. Please ensure that each ' 'Resource attribute "@$key" was not found. Please ensure that each '
'resource id has a corresponding resource attribute.' 'resource id has a corresponding resource attribute.'
); );
...@@ -238,7 +234,7 @@ String genPluralMethod(Map<String, dynamic> bundle, String key) { ...@@ -238,7 +234,7 @@ String genPluralMethod(Map<String, dynamic> bundle, String key) {
...genIntlMethodArgs(bundle, key), ...genIntlMethodArgs(bundle, key),
]; ];
for(String pluralKey in pluralIds.keys) { for (String pluralKey in pluralIds.keys) {
final RegExp expRE = RegExp('($pluralKey){([^}]+)}'); final RegExp expRE = RegExp('($pluralKey){([^}]+)}');
final RegExpMatch match = expRE.firstMatch(message); final RegExpMatch match = expRE.firstMatch(message);
if (match != null && match.groupCount == 2) { if (match != null && match.groupCount == 2) {
...@@ -289,6 +285,19 @@ bool _isValidClassName(String className) { ...@@ -289,6 +285,19 @@ bool _isValidClassName(String className) {
return true; 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) { bool _isValidGetterAndMethodName(String name) {
// Dart getter and method name cannot contain non-alphanumeric symbols // Dart getter and method name cannot contain non-alphanumeric symbols
if (name.contains(RegExp(r'[^a-zA-Z\d]'))) if (name.contains(RegExp(r'[^a-zA-Z\d]')))
...@@ -302,184 +311,250 @@ bool _isValidGetterAndMethodName(String name) { ...@@ -302,184 +311,250 @@ bool _isValidGetterAndMethodName(String name) {
return true; return true;
} }
bool _isDirectoryReadableAndWritable(String statString) { /// The localizations generation class used to generate the localizations
if (statString[0] == '-' || statString[1] == '-') /// classes, as well as all pertinent Dart files required to internationalize a
return false; /// Flutter application.
return true; 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);
String _importFilePath(String path, String fileName) { static RegExp arbFilenameLocaleRE = RegExp(r'^[^_]*_(\w+)\.arb$');
final String replaceLib = path.replaceAll('lib/', ''); static RegExp arbFilenameRE = RegExp(r'(\w+)\.arb$');
return '$replaceLib/$fileName'; static RegExp pluralValueRE = RegExp(r'^\s*\{[\w\s,]*,\s*plural\s*,');
}
Future<void> main(List<String> arguments) async { final file.FileSystem _fs;
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; /// The reference to the project's l10n directory.
final String outputFileString = results['output-localization-file'] as String; ///
/// 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;
final Directory l10nDirectory = Directory(arbPathString); /// The input arb file which defines all of the messages that will be
final File templateArbFile = File(path.join(l10nDirectory.path, results['template-arb-file'] as String)); /// exported by the generated class that's written to [outputFile].
final File outputFile = File(path.join(l10nDirectory.path, outputFileString)); ///
final String stringsClassName = results['output-class'] as String; /// This file is specified with the [initialize] method.
File templateArbFile;
if (!l10nDirectory.existsSync()) /// The file to write the generated localizations and localizations delegate
exitWithError( /// classes to.
"The 'arb-dir' directory, $l10nDirectory, does not exist.\n" ///
'Make sure that the correct path was provided.' /// This file is specified with the [initialize] method.
); File outputFile;
final String l10nDirectoryStatModeString = l10nDirectory.statSync().modeString();
if (!_isDirectoryReadableAndWritable(l10nDirectoryStatModeString)) /// The class name to be used for the localizations class in [outputFile].
exitWithError( ///
"The 'arb-dir' directory, $l10nDirectory, doesn't allow reading and writing.\n" /// For example, if 'AppLocalizations' is passed in, a class named
'Please ensure that the user has read and write permissions.' /// AppLocalizations will be used for localized message lookups.
); ///
final String templateArbFileStatModeString = templateArbFile.statSync().modeString(); /// The class name is specified with the [initialize] method.
if (templateArbFileStatModeString[0] == '-') String get className => _className;
exitWithError( String _className;
"The 'template-arb-file', $templateArbFile, is not readable.\n" /// Sets the [className] for the localizations and localizations delegate
'Please ensure that the user has read permissions.' /// classes.
);
if (!_isValidClassName(stringsClassName))
exitWithError(
"The 'output-class', $stringsClassName, is not valid Dart class name.\n"
);
/// The list of all arb files in [l10nDirectory].
final List<String> arbFilenames = <String>[]; final List<String> arbFilenames = <String>[];
/// The supported language codes as found in the arb files located in
/// [l10nDirectory].
final Set<String> supportedLanguageCodes = <String>{}; final Set<String> supportedLanguageCodes = <String>{};
/// The supported locales as found in the arb files located in
/// [l10nDirectory].
final Set<LocaleInfo> supportedLocales = <LocaleInfo>{}; final Set<LocaleInfo> supportedLocales = <LocaleInfo>{};
for (FileSystemEntity entity in l10nDirectory.listSync().toList()..sort(sortFilesByPath)) { /// The class methods that will be generated in the localizations class
final String entityPath = entity.path; /// based on messages found in the template arb file.
final List<String> classMethods = <String>[];
if (FileSystemEntity.isFileSync(entityPath)) { /// Initializes [l10nDirectory], [templateArbFile], [outputFile] and [className].
final RegExp arbFilenameRE = RegExp(r'(\w+)\.arb$'); ///
if (arbFilenameRE.hasMatch(entityPath)) { /// Throws an [L10nException] when a provided configuration is not allowed
final File arbFile = File(entityPath); /// by [LocalizationsGenerator].
final Map<String, dynamic> arbContents = json.decode(arbFile.readAsStringSync()) as Map<String, dynamic>; ///
String localeString = arbContents['@@locale'] as String; /// 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;
}
/// 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())
throw FileSystemException(
"The 'arb-dir' directory, $l10nDirectory, does not exist.\n"
'Make sure that the correct path was provided.'
);
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] == '-' && templateArbFileStatModeString[3] == '-')
throw FileSystemException(
"The 'template-arb-file', $templateArbFile, is not readable.\n"
'Please ensure that the user has read permissions.'
);
}
/// 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;
}
/// 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) { if (localeString == null) {
final RegExp arbFilenameLocaleRE = RegExp(r'^[^_]*_(\w+)\.arb$'); final RegExpMatch arbFileMatch = arbFilenameLocaleRE.firstMatch(filePath);
final RegExpMatch arbFileMatch = arbFilenameLocaleRE.firstMatch(entityPath);
if (arbFileMatch == null) { if (arbFileMatch == null) {
exitWithError( throw L10nException(
"The following .arb file's locale could not be determined: \n" "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' " "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); final LocaleInfo localeInfo = LocaleInfo.fromString(localeString);
if (supportedLocales.contains(localeInfo)) if (localeInfoList.contains(localeInfo))
exitWithError( throw L10nException(
'Multiple arb files with the same locale detected. \n' 'Multiple arb files with the same locale detected. \n'
'Ensure that there is exactly one arb file for each locale.' 'Ensure that there is exactly one arb file for each locale.'
); );
supportedLocales.add(localeInfo); localeInfoList.add(localeInfo);
supportedLanguageCodes.add('\'${localeInfo.languageCode}\'');
} }
} }
}
final List<String> classMethods = <String>[]; localeInfoList.sort();
supportedLocales.addAll(localeInfoList);
Map<String, dynamic> bundle; supportedLanguageCodes.addAll(localeInfoList.map((LocaleInfo localeInfo) {
try { return '\'${localeInfo.languageCode}\'';
bundle = json.decode(templateArbFile.readAsStringSync()) as Map<String, dynamic>; }));
} on FileSystemException catch (e) {
exitWithError('Unable to read input arb file: $e');
} on FormatException catch (e) {
exitWithError('Unable to parse arb file: $e');
} }
final RegExp pluralValueRE = RegExp(r'^\s*\{[\w\s,]*,\s*plural\s*,'); /// 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());
} on FileSystemException catch (e) {
throw FileSystemException('Unable to read input arb file: $e');
} on FormatException catch (e) {
throw FormatException('Unable to parse arb file: $e');
}
for (String key in bundle.keys.toList()..sort()) { final List<String> sortedArbKeys = bundle.keys.toList()..sort();
if (key.startsWith('@')) for (String key in sortedArbKeys) {
continue; if (key.startsWith('@'))
if (!_isValidGetterAndMethodName(key)) continue;
exitWithError( if (!_isValidGetterAndMethodName(key))
'Invalid key format: $key \n It has to be in camel case, cannot start ' throw L10nException(
'with a number, and cannot contain non-alphanumeric characters.' '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)) );
classMethods.add(genPluralMethod(bundle, key)); if (pluralValueRE.hasMatch(bundle[key]))
else classMethods.add(genPluralMethod(bundle, key));
classMethods.add(genSimpleMethod(bundle, key)); else
classMethods.add(genSimpleMethod(bundle, key));
}
} }
outputFile.writeAsStringSync( /// Generates a file that contains the localizations class and the
defaultFileTemplate /// LocalizationsDelegate class.
.replaceAll('@className', stringsClassName) void generateOutputFile() {
.replaceAll('@classMethods', classMethods.join('\n')) final String directory = path.basename(l10nDirectory.path);
.replaceAll('@importFile', _importFilePath(arbPathString, outputFileString)) final String outputFileName = path.basename(outputFile.path);
.replaceAll('@supportedLocales', genSupportedLocaleProperty(supportedLocales)) outputFile.writeAsStringSync(
.replaceAll('@supportedLanguageCodes', supportedLanguageCodes.toList().join(', ')) defaultFileTemplate
); .replaceAll('@className', className)
.replaceAll('@classMethods', classMethods.join('\n'))
final ProcessResult pubGetResult = await Process.run('flutter', <String>['pub', 'get']); .replaceAll('@importFile', '$directory/$outputFileName')
if (pubGetResult.exitCode != 0) { .replaceAll('@supportedLocales', genSupportedLocaleProperty(supportedLocales))
stderr.write(pubGetResult.stderr); .replaceAll('@supportedLanguageCodes', supportedLanguageCodes.toList().join(', '))
exit(1); );
} }
}
final ProcessResult generateFromArbResult = await Process.run('flutter', <String>[ class L10nException implements Exception {
'pub', L10nException(this.message);
'pub',
'run', final String message;
'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);
}
} }
...@@ -29,12 +29,44 @@ dependencies: ...@@ -29,12 +29,44 @@ dependencies:
typed_data: 1.1.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.1.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
dev_dependencies: dev_dependencies:
test: 1.9.4
test_api: 0.2.11 test_api: 0.2.11
mockito: 4.1.1 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" 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" 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" 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" 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
// 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');
}
}
// 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. ...@@ -15,7 +15,7 @@ for more info.
`messages_all.dart`, and `stock_strings.dart` with the following command: `messages_all.dart`, and `stock_strings.dart` with the following command:
```dart ```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 \ --template-arb-file=stocks_en_US.arb --output-localization-file=stock_strings.dart \
--output-class=StockStrings --output-class=StockStrings
``` ```
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment