[gen_l10n] Update the arb filename parsing logic (#60185)

* Update the arb filename parsing to account for underscores

* Generalize the locale searching algorithm

* Update filename and @@locale behavior to require them to match
This commit is contained in:
Shi-Hao Hong 2020-06-26 12:01:59 +08:00 committed by GitHub
parent 98d66de888
commit 1e6e7150f3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 293 additions and 14 deletions

View File

@ -5,6 +5,9 @@
import 'dart:convert';
import 'dart:io';
import 'package:intl/locale.dart';
import 'package:path/path.dart' as path;
import 'localizations_utils.dart';
// The set of date formats that can be automatically localized.
@ -374,19 +377,53 @@ class AppResourceBundle {
}
String localeString = resources['@@locale'] as String;
if (localeString == null) {
final RegExp filenameRE = RegExp(r'^[^_]*_(\w+)\.arb$');
final RegExpMatch match = filenameRE.firstMatch(file.path);
localeString = match == null ? null : match[1];
// Look for the first instance of an ISO 639-1 language code, matching exactly.
final String fileName = path.basenameWithoutExtension(file.path);
for (int index = 0; index < fileName.length; index += 1) {
// If an underscore was found, check if locale string follows.
if (fileName[index] == '_' && fileName[index + 1] != null) {
// If Locale.tryParse fails, it returns null.
final Locale parserResult = Locale.tryParse(fileName.substring(index + 1));
// If the parserResult is not an actual locale identifier, end the loop.
if (parserResult != null && _iso639Languages.contains(parserResult.languageCode)) {
// The parsed result uses dashes ('-'), but we want underscores ('_').
final String parserLocaleString = parserResult.toString().replaceAll('-', '_');
if (localeString == null) {
// If @@locale was not defined, use the filename locale suffix.
localeString = parserLocaleString;
} else {
// If the localeString was defined in @@locale and in the filename, verify to
// see if the parsed locale matches, throw an error if it does not. This
// prevents developers from confusing issues when both @@locale and
// "_{locale}" is specified in the filename.
if (localeString != parserLocaleString) {
throw L10nException(
'The locale specified in @@locale and the arb filename do not match. \n'
'Please make sure that they match, since this prevents any confusion \n'
'with which locale to use. Otherwise, specify the locale in either the \n'
'filename of the @@locale key only.\n'
'Current @@locale value: $localeString\n'
'Current filename extension: $parserLocaleString'
);
}
}
break;
}
}
}
if (localeString == null) {
throw L10nException(
"The following .arb file's locale could not be determined: \n"
'${file.path} \n'
"Make sure that the locale is specified in the file's '@@locale' "
'property or as part of the filename (e.g. file_en.arb)'
);
}
);
}
final Iterable<String> ids = resources.keys.where((String key) => !key.startsWith('@'));
return AppResourceBundle._(file, LocaleInfo.fromString(localeString), resources, ids);
@ -469,3 +506,191 @@ class AppResourceBundleCollection {
return 'AppResourceBundleCollection(${_directory.path}, ${locales.length} locales)';
}
}
// A set containing all the ISO630-1 languages. This list was pulled from https://datahub.io/core/language-codes.
final Set<String> _iso639Languages = <String>{
'aa',
'ab',
'ae',
'af',
'ak',
'am',
'an',
'ar',
'as',
'av',
'ay',
'az',
'ba',
'be',
'bg',
'bh',
'bi',
'bm',
'bn',
'bo',
'br',
'bs',
'ca',
'ce',
'ch',
'co',
'cr',
'cs',
'cu',
'cv',
'cy',
'da',
'de',
'dv',
'dz',
'ee',
'el',
'en',
'eo',
'es',
'et',
'eu',
'fa',
'ff',
'fi',
'fj',
'fo',
'fr',
'fy',
'ga',
'gd',
'gl',
'gn',
'gu',
'gv',
'ha',
'he',
'hi',
'ho',
'hr',
'ht',
'hu',
'hy',
'hz',
'ia',
'id',
'ie',
'ig',
'ii',
'ik',
'io',
'is',
'it',
'iu',
'ja',
'jv',
'ka',
'kg',
'ki',
'kj',
'kk',
'kl',
'km',
'kn',
'ko',
'kr',
'ks',
'ku',
'kv',
'kw',
'ky',
'la',
'lb',
'lg',
'li',
'ln',
'lo',
'lt',
'lu',
'lv',
'mg',
'mh',
'mi',
'mk',
'ml',
'mn',
'mr',
'ms',
'mt',
'my',
'na',
'nb',
'nd',
'ne',
'ng',
'nl',
'nn',
'no',
'nr',
'nv',
'ny',
'oc',
'oj',
'om',
'or',
'os',
'pa',
'pi',
'pl',
'ps',
'pt',
'qu',
'rm',
'rn',
'ro',
'ru',
'rw',
'sa',
'sc',
'sd',
'se',
'sg',
'si',
'sk',
'sl',
'sm',
'sn',
'so',
'sq',
'sr',
'ss',
'st',
'su',
'sv',
'sw',
'ta',
'te',
'tg',
'th',
'ti',
'tk',
'tl',
'tn',
'to',
'tr',
'ts',
'tt',
'tw',
'ty',
'ug',
'uk',
'ur',
'uz',
've',
'vi',
'vo',
'wa',
'wo',
'xh',
'yi',
'yo',
'za',
'zh',
'zu',
};

View File

@ -308,6 +308,61 @@ void main() {
expect(generator.header, '/// Sample header in a text file');
});
test('sets templateArbFileName with more than one underscore correctly', () {
final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n')
..createSync(recursive: true);
l10nDirectory.childFile('app_localizations_en.arb')
.writeAsStringSync(singleMessageArbFileString);
l10nDirectory.childFile('app_localizations_es.arb')
.writeAsStringSync(singleEsMessageArbFileString);
LocalizationsGenerator generator;
try {
generator = LocalizationsGenerator(fs);
generator
..initialize(
inputPathString: defaultL10nPathString,
templateArbFileName: 'app_localizations_en.arb',
outputFileString: defaultOutputFileString,
classNameString: defaultClassNameString,
)
..loadResources()
..writeOutputFiles();
} on L10nException catch (e) {
fail('Generating output should not fail: \n${e.message}');
}
final Directory outputDirectory = fs.directory('lib').childDirectory('l10n');
expect(outputDirectory.childFile('output-localization-file.dart').existsSync(), isTrue);
expect(outputDirectory.childFile('output-localization-file_en.dart').existsSync(), isTrue);
expect(outputDirectory.childFile('output-localization-file_es.dart').existsSync(), isTrue);
});
test('filenames with invalid locales should not be recognized', () {
final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n')
..createSync(recursive: true);
l10nDirectory.childFile('app_localizations_en.arb')
.writeAsStringSync(singleMessageArbFileString);
l10nDirectory.childFile('app_localizations_en_CA_foo.arb')
.writeAsStringSync(singleMessageArbFileString);
LocalizationsGenerator generator;
try {
generator = LocalizationsGenerator(fs);
generator
..initialize(
inputPathString: defaultL10nPathString,
templateArbFileName: 'app_localizations_en.arb',
outputFileString: defaultOutputFileString,
classNameString: defaultClassNameString,
)
..loadResources();
} on L10nException catch (e) {
expect(e.message, contains('The following .arb file\'s locale could not be determined'));
return;
}
fail('Using app_en_CA_foo.arb should fail as it is not a valid locale.');
});
test('correctly creates an unimplemented messages file', () {
fs.currentDirectory.childDirectory('lib').childDirectory('l10n')
..createSync(recursive: true)
@ -799,7 +854,7 @@ void main() {
expect(generator.supportedLocales.contains(LocaleInfo.fromString('zh')), true);
});
test('correctly prioritizes @@locale property in arb file over filename', () {
test('correctly requires @@locale property in arb file to match the filename locale suffix', () {
const String arbFileWithEnLocale = '''
{
"@@locale": "en",
@ -837,15 +892,14 @@ void main() {
);
generator.loadResources();
} on L10nException catch (e) {
fail('Setting language and locales should not fail: \n${e.message}');
expect(e.message, contains('The locale specified in @@locale and the arb filename do not match.'));
return;
}
// @@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);
fail(
'An exception should occur if the @@locale and the filename extensions are '
'defined but not matching.'
);
});
test("throws when arb file's locale could not be determined", () {