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', '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);
|
||||||
|
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
|
// 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 = '''
|
|||||||
}
|
}
|
||||||
''';
|
''';
|
||||||
|
|
||||||
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) {
|
|||||||
|
|
||||||
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) {
|
|||||||
...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) {
|
|||||||
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) {
|
|||||||
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);
|
/// The reference to the project's l10n directory.
|
||||||
if (results['help'] == true) {
|
///
|
||||||
print(parser.usage);
|
/// It is assumed that all input files (e.g. [templateArbFile], arb files
|
||||||
exit(0);
|
/// 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 String arbPathString = results['arb-dir'] as String;
|
/// The input arb file which defines all of the messages that will be
|
||||||
final String outputFileString = results['output-localization-file'] as String;
|
/// exported by the generated class that's written to [outputFile].
|
||||||
|
///
|
||||||
|
/// This file is specified with the [initialize] method.
|
||||||
|
File templateArbFile;
|
||||||
|
|
||||||
final Directory l10nDirectory = Directory(arbPathString);
|
/// The file to write the generated localizations and localizations delegate
|
||||||
final File templateArbFile = File(path.join(l10nDirectory.path, results['template-arb-file'] as String));
|
/// classes to.
|
||||||
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 outputFile;
|
||||||
|
|
||||||
if (!l10nDirectory.existsSync())
|
/// The class name to be used for the localizations class in [outputFile].
|
||||||
exitWithError(
|
///
|
||||||
"The 'arb-dir' directory, $l10nDirectory, does not exist.\n"
|
/// For example, if 'AppLocalizations' is passed in, a class named
|
||||||
'Make sure that the correct path was provided.'
|
/// AppLocalizations will be used for localized message lookups.
|
||||||
);
|
///
|
||||||
final String l10nDirectoryStatModeString = l10nDirectory.statSync().modeString();
|
/// The class name is specified with the [initialize] method.
|
||||||
if (!_isDirectoryReadableAndWritable(l10nDirectoryStatModeString))
|
String get className => _className;
|
||||||
exitWithError(
|
String _className;
|
||||||
"The 'arb-dir' directory, $l10nDirectory, doesn't allow reading and writing.\n"
|
/// Sets the [className] for the localizations and localizations delegate
|
||||||
'Please ensure that the user has read and write permissions.'
|
/// classes.
|
||||||
);
|
|
||||||
final String templateArbFileStatModeString = templateArbFile.statSync().modeString();
|
|
||||||
if (templateArbFileStatModeString[0] == '-')
|
|
||||||
exitWithError(
|
|
||||||
"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"
|
|
||||||
);
|
|
||||||
|
|
||||||
|
/// 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}\'');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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());
|
||||||
|
} 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');
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<String> sortedArbKeys = bundle.keys.toList()..sort();
|
||||||
|
for (String key in sortedArbKeys) {
|
||||||
|
if (key.startsWith('@'))
|
||||||
|
continue;
|
||||||
|
if (!_isValidGetterAndMethodName(key))
|
||||||
|
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]))
|
||||||
|
classMethods.add(genPluralMethod(bundle, key));
|
||||||
|
else
|
||||||
|
classMethods.add(genSimpleMethod(bundle, key));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<String> classMethods = <String>[];
|
/// Generates a file that contains the localizations class and the
|
||||||
|
/// LocalizationsDelegate class.
|
||||||
Map<String, dynamic> bundle;
|
void generateOutputFile() {
|
||||||
try {
|
final String directory = path.basename(l10nDirectory.path);
|
||||||
bundle = json.decode(templateArbFile.readAsStringSync()) as Map<String, dynamic>;
|
final String outputFileName = path.basename(outputFile.path);
|
||||||
} on FileSystemException catch (e) {
|
outputFile.writeAsStringSync(
|
||||||
exitWithError('Unable to read input arb file: $e');
|
defaultFileTemplate
|
||||||
} on FormatException catch (e) {
|
.replaceAll('@className', className)
|
||||||
exitWithError('Unable to parse arb file: $e');
|
.replaceAll('@classMethods', classMethods.join('\n'))
|
||||||
}
|
.replaceAll('@importFile', '$directory/$outputFileName')
|
||||||
|
.replaceAll('@supportedLocales', genSupportedLocaleProperty(supportedLocales))
|
||||||
final RegExp pluralValueRE = RegExp(r'^\s*\{[\w\s,]*,\s*plural\s*,');
|
.replaceAll('@supportedLanguageCodes', supportedLanguageCodes.toList().join(', '))
|
||||||
|
);
|
||||||
for (String key in bundle.keys.toList()..sort()) {
|
|
||||||
if (key.startsWith('@'))
|
|
||||||
continue;
|
|
||||||
if (!_isValidGetterAndMethodName(key))
|
|
||||||
exitWithError(
|
|
||||||
'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));
|
|
||||||
else
|
|
||||||
classMethods.add(genSimpleMethod(bundle, key));
|
|
||||||
}
|
|
||||||
|
|
||||||
outputFile.writeAsStringSync(
|
|
||||||
defaultFileTemplate
|
|
||||||
.replaceAll('@className', stringsClassName)
|
|
||||||
.replaceAll('@classMethods', classMethods.join('\n'))
|
|
||||||
.replaceAll('@importFile', _importFilePath(arbPathString, outputFileString))
|
|
||||||
.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"
|
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
|
||||||
|
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:
|
`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
|
||||||
```
|
```
|
||||||
|
Loading…
x
Reference in New Issue
Block a user