Unverified Commit 6837b0e3 authored by Shi-Hao Hong's avatar Shi-Hao Hong Committed by GitHub

[gen_l10n] Add scriptCode handling (#53868)

* [gen_l10n] Add scriptCode handling
parent 2396616e
......@@ -248,26 +248,120 @@ String generateBaseClassMethod(Message message) {
.replaceAll('@(name)', message.resourceId);
}
String generateLookupBody(AppResourceBundleCollection allBundles, String className) {
String _generateLookupByAllCodes(AppResourceBundleCollection allBundles, String className) {
final Iterable<LocaleInfo> localesWithAllCodes = allBundles.locales.where((LocaleInfo locale) {
return locale.scriptCode != null && locale.countryCode != null;
});
if (localesWithAllCodes.isEmpty) {
return '';
}
final Iterable<String> switchClauses = localesWithAllCodes.map<String>((LocaleInfo locale) {
return switchClauseTemplate
.replaceAll('@(case)', locale.toString())
.replaceAll('@(class)', '$className${locale.camelCase()}');
});
return allCodesLookupTemplate.replaceAll(
'@(allCodesSwitchClauses)',
switchClauses.join('\n '),
);
}
String _generateLookupByScriptCode(AppResourceBundleCollection allBundles, String className) {
final Iterable<String> switchClauses = allBundles.languages.map((String language) {
final Iterable<LocaleInfo> locales = allBundles.localesForLanguage(language);
if (locales.length == 1) {
return switchClauseTemplate
.replaceAll('@(case)', language)
.replaceAll('@(class)', '$className${locales.first.camelCase()}');
}
final Iterable<LocaleInfo> localesWithScriptCodes = locales.where((LocaleInfo locale) {
return locale.scriptCode != null && locale.countryCode == null;
});
if (localesWithScriptCodes.isEmpty)
return null;
final Iterable<LocaleInfo> localesWithCountryCodes = locales.where((LocaleInfo locale) => locale.countryCode != null);
return countryCodeSwitchTemplate
return nestedSwitchTemplate
.replaceAll('@(languageCode)', language)
.replaceAll('@(code)', 'scriptCode')
.replaceAll('@(class)', '$className${LocaleInfo.fromString(language).camelCase()}')
.replaceAll('@(switchClauses)', localesWithScriptCodes.map((LocaleInfo locale) {
return switchClauseTemplate
.replaceAll('@(case)', locale.scriptCode)
.replaceAll('@(class)', '$className${locale.camelCase()}');
}).join('\n '));
}).where((String switchClause) => switchClause != null);
if (switchClauses.isEmpty) {
return '';
}
return languageCodeSwitchTemplate
.replaceAll('@(comment)', '// Lookup logic when language+script codes are specified.')
.replaceAll('@(switchClauses)', switchClauses.join('\n '),
);
}
String _generateLookupByCountryCode(AppResourceBundleCollection allBundles, String className) {
final Iterable<String> switchClauses = allBundles.languages.map((String language) {
final Iterable<LocaleInfo> locales = allBundles.localesForLanguage(language);
final Iterable<LocaleInfo> localesWithCountryCodes = locales.where((LocaleInfo locale) {
return locale.countryCode != null && locale.scriptCode == null;
});
if (localesWithCountryCodes.isEmpty)
return null;
return nestedSwitchTemplate
.replaceAll('@(languageCode)', language)
.replaceAll('@(code)', 'countryCode')
.replaceAll('@(class)', '$className${LocaleInfo.fromString(language).camelCase()}')
.replaceAll('@(switchClauses)', localesWithCountryCodes.map((LocaleInfo locale) {
return switchClauseTemplate
.replaceAll('@(case)', locale.countryCode)
.replaceAll('@(class)', '$className${locale.camelCase()}');
}).join('\n '));
});
return switchClauses.join('\n ');
}).where((String switchClause) => switchClause != null);
if (switchClauses.isEmpty) {
return '';
}
return languageCodeSwitchTemplate
.replaceAll('@(comment)', '// Lookup logic when language+country codes are specified.')
.replaceAll('@(switchClauses)', switchClauses.join('\n '));
}
String _generateLookupByLanguageCode(AppResourceBundleCollection allBundles, String className) {
final Iterable<String> switchClauses = allBundles.languages.map((String language) {
final Iterable<LocaleInfo> locales = allBundles.localesForLanguage(language);
final Iterable<LocaleInfo> localesWithLanguageCode = locales.where((LocaleInfo locale) {
return locale.countryCode == null && locale.scriptCode == null;
});
if (localesWithLanguageCode.isEmpty)
return null;
return localesWithLanguageCode.map((LocaleInfo locale) {
return switchClauseTemplate
.replaceAll('@(case)', locale.languageCode)
.replaceAll('@(class)', '$className${locale.camelCase()}');
}).join('\n ');
}).where((String switchClause) => switchClause != null);
if (switchClauses.isEmpty) {
return '';
}
return languageCodeSwitchTemplate
.replaceAll('@(comment)', '// Lookup logic when only language code is specified.')
.replaceAll('@(switchClauses)', switchClauses.join('\n '));
}
String _generateLookupBody(AppResourceBundleCollection allBundles, String className) {
return lookupBodyTemplate
.replaceAll('@(lookupAllCodesSpecified)', _generateLookupByAllCodes(allBundles, className))
.replaceAll('@(lookupScriptCodeSpecified)', _generateLookupByScriptCode(allBundles, className))
.replaceAll('@(lookupCountryCodeSpecified)', _generateLookupByCountryCode(allBundles, className))
.replaceAll('@(lookupLanguageCodeSpecified)', _generateLookupByLanguageCode(allBundles, className));
}
class LocalizationsGenerator {
......@@ -466,7 +560,9 @@ class LocalizationsGenerator {
if (localeString.runtimeType != String) {
throw L10nException('Incorrect runtime type for $localeString');
}
return LocaleInfo.fromString(localeString.toString());
return LocaleInfo.fromString(
localeString.toString(),
);
}).toList();
}
}
......@@ -566,9 +662,19 @@ class LocalizationsGenerator {
final String outputFileName = path.basename(outputFile.path);
final Iterable<String> supportedLocalesCode = supportedLocales.map((LocaleInfo locale) {
final String country = locale.countryCode;
final String countryArg = country == null ? '' : "', '$country";
return 'Locale(\'${locale.languageCode}$countryArg\')';
final String languageCode = locale.languageCode;
final String countryCode = locale.countryCode;
final String scriptCode = locale.scriptCode;
if (countryCode == null && scriptCode == null) {
return 'Locale(\'$languageCode\')';
} else if (countryCode != null && scriptCode == null) {
return 'Locale(\'$languageCode\', \'$countryCode\')';
} else if (countryCode != null && scriptCode != null) {
return 'Locale.fromSubtags(languageCode: \'$languageCode\', countryCode: \'$countryCode\', scriptCode: \'$scriptCode\')';
} else {
return 'Locale.fromSubtags(languageCode: \'$languageCode\', scriptCode: \'$scriptCode\')';
}
});
final Set<String> supportedLanguageCodes = Set<String>.from(
......@@ -618,7 +724,7 @@ class LocalizationsGenerator {
return "import '${fileName}_${locale.toString()}.dart';";
});
final String lookupBody = generateLookupBody(_allBundles, className);
final String lookupBody = _generateLookupBody(_allBundles, className);
return fileTemplate
.replaceAll('@(header)', header)
......
......@@ -120,9 +120,7 @@ class _@(class)Delegate extends LocalizationsDelegate<@(class)> {
}
@(class) @(lookupName)(Locale locale) {
switch(locale.languageCode) {
@(lookupBody)
}
@(lookupBody)
assert(false, '@(class).delegate failed to load unsupported locale "\$locale"');
return null;
}
......@@ -207,11 +205,30 @@ const String baseClassMethodTemplate = '''
String @(name)(@(parameters));
''';
// DELEGATE LOOKUP TEMPLATES
const String lookupBodyTemplate = '''@(lookupAllCodesSpecified)
@(lookupScriptCodeSpecified)
@(lookupCountryCodeSpecified)
@(lookupLanguageCodeSpecified)''';
const String switchClauseTemplate = '''case '@(case)': return @(class)();''';
const String countryCodeSwitchTemplate = '''case '@(languageCode)': {
switch (locale.countryCode) {
const String nestedSwitchTemplate = '''case '@(languageCode)': {
switch (locale.@(code)) {
@(switchClauses)
}
return @(class)();
break;
}''';
const String languageCodeSwitchTemplate = '''@(comment)
switch (locale.languageCode) {
@(switchClauses)
}
''';
const String allCodesLookupTemplate = '''// Lookup logic when language+script+country codes are specified.
switch (locale.toString()) {
@(allCodesSwitchClauses)
}
''';
......@@ -80,37 +80,49 @@ void main() {
'#l10n 1 (supportedLocales[0]: languageCode: en, countryCode: null, scriptCode: null)\n'
'#l10n 2 (supportedLocales[1]: languageCode: en, countryCode: CA, scriptCode: null)\n'
'#l10n 3 (supportedLocales[2]: languageCode: en, countryCode: GB, scriptCode: null)\n'
'#l10n 4 (--- countryCode (en_CA) tests ---)\n'
'#l10n 5 (CA Hello World)\n'
'#l10n 6 (Hello CA fallback World)\n'
'#l10n 7 (--- countryCode (en_GB) tests ---)\n'
'#l10n 8 (GB Hello World)\n'
'#l10n 9 (Hello GB fallback World)\n'
'#l10n 10 (--- General formatting tests ---)\n'
'#l10n 11 (Hello World)\n'
'#l10n 12 (Hello _NEWLINE_ World)\n'
'#l10n 13 (Hello World)\n'
'#l10n 14 (Hello World)\n'
'#l10n 15 (Hello World on Friday, January 1, 1960)\n'
'#l10n 16 (Hello world argument on 1/1/1960 at 00:00)\n'
'#l10n 17 (Hello World from 1960 to 2020)\n'
'#l10n 18 (Hello for 123)\n'
'#l10n 19 (Hello for price USD123.00)\n'
'#l10n 20 (Hello)\n'
'#l10n 21 (Hello World)\n'
'#l10n 22 (Hello two worlds)\n'
'#l10n 23 (Hello)\n'
'#l10n 24 (Hello new World)\n'
'#l10n 25 (Hello two new worlds)\n'
'#l10n 26 (Hello on Friday, January 1, 1960)\n'
'#l10n 27 (Hello World, on Friday, January 1, 1960)\n'
'#l10n 28 (Hello two worlds, on Friday, January 1, 1960)\n'
'#l10n 29 (Hello other 0 worlds, with a total of 100 citizens)\n'
'#l10n 30 (Hello World of 101 citizens)\n'
'#l10n 31 (Hello two worlds with 102 total citizens)\n'
'#l10n 32 ([Hello] -World- #123#)\n'
'#l10n 33 (Flutter\'s amazing!)\n'
'#l10n 34 (Flutter is "amazing"!)\n'
'#l10n 4 (supportedLocales[3]: languageCode: zh, countryCode: null, scriptCode: null)\n'
'#l10n 5 (supportedLocales[4]: languageCode: zh, countryCode: null, scriptCode: Hans)\n'
'#l10n 6 (supportedLocales[5]: languageCode: zh, countryCode: null, scriptCode: Hant)\n'
'#l10n 7 (supportedLocales[6]: languageCode: zh, countryCode: TW, scriptCode: Hant)\n'
'#l10n 8 (--- countryCode (en_CA) tests ---)\n'
'#l10n 9 (CA Hello World)\n'
'#l10n 10 (Hello CA fallback World)\n'
'#l10n 11 (--- countryCode (en_GB) tests ---)\n'
'#l10n 12 (GB Hello World)\n'
'#l10n 13 (Hello GB fallback World)\n'
'#l10n 14 (--- zh ---)\n'
'#l10n 15 (你好世界)\n'
'#l10n 16 (--- scriptCode: zh_Hans ---)\n'
'#l10n 17 (简体你好世界)\n'
'#l10n 18 (--- scriptCode - zh_Hant ---)\n'
'#l10n 19 (繁體你好世界)\n'
'#l10n 20 (--- scriptCode - zh_Hant_TW ---)\n'
'#l10n 21 (台灣繁體你好世界)\n'
'#l10n 22 (--- General formatting tests ---)\n'
'#l10n 23 (Hello World)\n'
'#l10n 24 (Hello _NEWLINE_ World)\n'
'#l10n 25 (Hello World)\n'
'#l10n 26 (Hello World)\n'
'#l10n 27 (Hello World on Friday, January 1, 1960)\n'
'#l10n 28 (Hello world argument on 1/1/1960 at 00:00)\n'
'#l10n 29 (Hello World from 1960 to 2020)\n'
'#l10n 30 (Hello for 123)\n'
'#l10n 31 (Hello for price USD123.00)\n'
'#l10n 32 (Hello)\n'
'#l10n 33 (Hello World)\n'
'#l10n 34 (Hello two worlds)\n'
'#l10n 35 (Hello)\n'
'#l10n 36 (Hello new World)\n'
'#l10n 37 (Hello two new worlds)\n'
'#l10n 38 (Hello on Friday, January 1, 1960)\n'
'#l10n 39 (Hello World, on Friday, January 1, 1960)\n'
'#l10n 40 (Hello two worlds, on Friday, January 1, 1960)\n'
'#l10n 41 (Hello other 0 worlds, with a total of 100 citizens)\n'
'#l10n 42 (Hello World of 101 citizens)\n'
'#l10n 43 (Hello two worlds with 102 total citizens)\n'
'#l10n 44 ([Hello] -World- #123#)\n'
'#l10n 45 (Flutter\'s amazing!)\n'
'#l10n 46 (Flutter is "amazing"!)\n'
'#l10n END\n'
);
});
......
......@@ -18,6 +18,10 @@ class GenL10nProject extends Project {
writeFile(globals.fs.path.join(dir.path, 'lib', 'l10n', 'app_en.arb'), appEn);
writeFile(globals.fs.path.join(dir.path, 'lib', 'l10n', 'app_en_CA.arb'), appEnCa);
writeFile(globals.fs.path.join(dir.path, 'lib', 'l10n', 'app_en_GB.arb'), appEnGb);
writeFile(globals.fs.path.join(dir.path, 'lib', 'l10n', 'app_zh.arb'), appZh);
writeFile(globals.fs.path.join(dir.path, 'lib', 'l10n', 'app_zh_Hant.arb'), appZhHant);
writeFile(globals.fs.path.join(dir.path, 'lib', 'l10n', 'app_zh_Hans.arb'), appZhHans);
writeFile(globals.fs.path.join(dir.path, 'lib', 'l10n', 'app_zh_Hant_TW.arb'), appZhHantTw);
return super.setUpIn(dir);
}
......@@ -116,6 +120,38 @@ class Home extends StatelessWidget {
results.add(AppLocalizations.of(context).hello("GB fallback World"));
},
),
LocaleBuilder(
locale: Locale('zh'),
test: 'zh',
callback: (BuildContext context) {
results.add('--- zh ---');
results.add(AppLocalizations.of(context).helloWorld);
},
),
LocaleBuilder(
locale: Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hans'),
test: 'zh',
callback: (BuildContext context) {
results.add('--- scriptCode: zh_Hans ---');
results.add(AppLocalizations.of(context).helloWorld);
},
),
LocaleBuilder(
locale: Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hant'),
test: 'scriptCode - zh_Hant',
callback: (BuildContext context) {
results.add('--- scriptCode - zh_Hant ---');
results.add(AppLocalizations.of(context).helloWorld);
},
),
LocaleBuilder(
locale: Locale.fromSubtags(languageCode: 'zh', countryCode: 'TW', scriptCode: 'Hant'),
test: 'scriptCode - zh_TW_Hant',
callback: (BuildContext context) {
results.add('--- scriptCode - zh_Hant_TW ---');
results.add(AppLocalizations.of(context).helloWorld);
},
),
LocaleBuilder(
locale: Locale('en'),
test: 'General formatting',
......@@ -364,5 +400,52 @@ void main() {
"@@locale": "en_GB",
"helloWorld": "GB Hello World"
}
''';
// Only tests `helloWorld`. The rest of the messages are added out of
// necessity since every base class requires an override for every
// message.
final String appZh = r'''
{
"@@locale": "zh",
"helloWorld": "你好世界",
"helloNewlineWorld": "Hello \n World",
"hello": "Hello {world}",
"greeting": "{hello} {world}",
"helloWorldOn": "Hello World on {date}",
"helloWorldDuring": "Hello World from {startDate} to {endDate}",
"helloOn": "Hello {world} on {date} at {time}",
"helloFor": "Hello for {value}",
"helloCost": "Hello for {price} {value}",
"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}}",
"helloAdjectiveWorlds": "{count,plural, =0{Hello} =1{Hello {adjective} World} =2{Hello two {adjective} worlds} other{Hello other {count} {adjective} worlds}}",
"helloWorldsOn": "{count,plural, =0{Hello on {date}} =1{Hello World, on {date}} =2{Hello two worlds, on {date}} other{Hello other {count} worlds, on {date}}}",
"helloWorldPopulation": "{count,plural, =1{Hello World of {population} citizens} =2{Hello two worlds with {population} total citizens} many{Hello all {count} worlds, with a total of {population} citizens} other{Hello other {count} worlds, with a total of {population} citizens}}",
"helloWorldInterpolation": "[{hello}] #{world}#",
"helloWorldsInterpolation": "{count,plural, other {[{hello}] -{world}- #{count}#}}",
"singleQuote": "Flutter's amazing!",
"doubleQuote": "Flutter is \"amazing\"!"
}
''';
final String appZhHans = r'''
{
"@@locale": "zh_Hans",
"helloWorld": "简体你好世界"
}
''';
final String appZhHant = r'''
{
"@@locale": "zh_Hant",
"helloWorld": "繁體你好世界"
}
''';
final String appZhHantTw = r'''
{
"@@locale": "zh_Hant_TW",
"helloWorld": "台灣繁體你好世界"
}
''';
}
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