1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5// This program generates getMaterialTranslation(), getCupertinoTranslation(),
6// and getWidgetsTranslation() functions that look up the translations provided by
7// the arb files. The returned value is a generated instance of a
8// GlobalMaterialLocalizations, GlobalCupertinoLocalizations, or
9// GlobalWidgetsLocalizations object that corresponds to a single locale.
10//
11// The *.arb files are in packages/flutter_localizations/lib/src/l10n.
12//
13// The arb (JSON) format files must contain a single map indexed by locale.
14// Each map value is itself a map with resource identifier keys and localized
15// resource string values.
16//
17// The arb filenames are expected to have the form "material_(\w+)\.arb" or
18// "cupertino_(\w+)\.arb" where the group following "_" identifies the language
19// code and the country code, e.g. "material_en.arb" or "material_en_GB.arb".
20// In most cases both codes are just two characters.
21//
22// This app is typically run by hand when a module's .arb files have been
23// updated.
24//
25// ## Usage
26//
27// Run this program from the root of the git repository.
28//
29// The following outputs the generated Dart code to the console as a dry run:
30//
31// ```
32// dart dev/tools/localization/bin/gen_localizations.dart
33// ```
34//
35// If you have removed localizations from the canonical localizations, then
36// add the '--remove-undefined' flag to also remove them from the other files.
37//
38// ```
39// dart dev/tools/localization/bin/gen_localizations.dart --remove-undefined
40// ```
41//
42// If the data looks good, use the `-w` or `--overwrite` option to overwrite the
43// generated_material_localizations.dart, generated_cupertino_localizations.dart,
44// and generated_widgets_localizations.dart files in packages/flutter_localizations/lib/src/l10n/:
45//
46// ```
47// dart dev/tools/localization/bin/gen_localizations.dart --overwrite
48// ```
49
50import 'dart:io';
51
52import 'package:path/path.dart' as path;
53
54import '../gen_cupertino_localizations.dart';
55import '../gen_material_localizations.dart';
56import '../gen_widgets_localizations.dart';
57import '../localizations_utils.dart';
58import '../localizations_validator.dart';
59import 'encode_kn_arb_files.dart';
60
61/// This is the core of this script; it generates the code used for translations.
62String generateArbBasedLocalizationSubclasses({
63 required Map<LocaleInfo, Map<String, String>> localeToResources,
64 required Map<LocaleInfo, Map<String, dynamic>> localeToResourceAttributes,
65 required String generatedClassPrefix,
66 required String baseClass,
67 required HeaderGenerator generateHeader,
68 required ConstructorGenerator generateConstructor,
69 ConstructorGenerator? generateConstructorForCountrySubClass,
70 required String factoryName,
71 required String factoryDeclaration,
72 required bool callsFactoryWithConst,
73 required String factoryArguments,
74 required String supportedLanguagesConstant,
75 required String supportedLanguagesDocMacro,
76}) {
77 assert(generatedClassPrefix.isNotEmpty);
78 assert(baseClass.isNotEmpty);
79 assert(factoryName.isNotEmpty);
80 assert(factoryDeclaration.isNotEmpty);
81 assert(factoryArguments.isNotEmpty);
82 assert(supportedLanguagesConstant.isNotEmpty);
83 assert(supportedLanguagesDocMacro.isNotEmpty);
84 generateConstructorForCountrySubClass ??= generateConstructor;
85 final StringBuffer output = StringBuffer();
86 output.writeln(
87 generateHeader('dart dev/tools/localization/bin/gen_localizations.dart --overwrite'),
88 );
89
90 final StringBuffer supportedLocales = StringBuffer();
91
92 final Map<String, List<LocaleInfo>> languageToLocales = <String, List<LocaleInfo>>{};
93 final Map<String, Set<String>> languageToScriptCodes = <String, Set<String>>{};
94 // Used to calculate if there are any corresponding countries for a given language and script.
95 final Map<LocaleInfo, Set<String>> languageAndScriptToCountryCodes = <LocaleInfo, Set<String>>{};
96 final Set<String> allResourceIdentifiers = <String>{};
97 for (final LocaleInfo locale in localeToResources.keys.toList()..sort()) {
98 if (locale.scriptCode != null) {
99 languageToScriptCodes[locale.languageCode] ??= <String>{};
100 languageToScriptCodes[locale.languageCode]!.add(locale.scriptCode!);
101 }
102 if (locale.countryCode != null && locale.scriptCode != null) {
103 final LocaleInfo key = LocaleInfo.fromString('${locale.languageCode}_${locale.scriptCode}');
104 languageAndScriptToCountryCodes[key] ??= <String>{};
105 languageAndScriptToCountryCodes[key]!.add(locale.countryCode!);
106 }
107 languageToLocales[locale.languageCode] ??= <LocaleInfo>[];
108 languageToLocales[locale.languageCode]!.add(locale);
109 allResourceIdentifiers.addAll(localeToResources[locale]!.keys.toList()..sort());
110 }
111
112 // We generate one class per supported language (e.g.
113 // `MaterialLocalizationEn`). These implement everything that is needed by the
114 // superclass (e.g. GlobalMaterialLocalizations).
115
116 // We also generate one subclass for each locale with a script code (e.g.
117 // `MaterialLocalizationZhHant`). Their superclasses are the aforementioned
118 // language classes for the same locale but without a script code (e.g.
119 // `MaterialLocalizationZh`).
120
121 // We also generate one subclass for each locale with a country code (e.g.
122 // `MaterialLocalizationEnGb`). Their superclasses are the aforementioned
123 // language classes for the same locale but without a country code (e.g.
124 // `MaterialLocalizationEn`).
125
126 // If scriptCodes for a language are defined, we expect a scriptCode to be
127 // defined for locales that contain a countryCode. The superclass becomes
128 // the script subclass (e.g. `MaterialLocalizationZhHant`) and the generated
129 // subclass will also contain the script code (e.g. `MaterialLocalizationZhHantTW`).
130
131 // When scriptCodes are not defined for languages that use scriptCodes to distinguish
132 // between significantly differing scripts, we assume the scriptCodes in the
133 // [LocaleInfo.fromString] factory and add it to the [LocaleInfo]. We then generate
134 // the script classes based on the first locale that we assume to use the script.
135
136 final List<String> allKeys = allResourceIdentifiers.toList()..sort();
137 final List<String> languageCodes = languageToLocales.keys.toList()..sort();
138 final LocaleInfo canonicalLocale = LocaleInfo.fromString('en');
139 for (final String languageName in languageCodes) {
140 final LocaleInfo languageLocale = LocaleInfo.fromString(languageName);
141 output.writeln(generateClassDeclaration(languageLocale, generatedClassPrefix, baseClass));
142 output.writeln(generateConstructor(languageLocale));
143
144 final Map<String, String> languageResources = localeToResources[languageLocale]!;
145 for (final String key in allKeys) {
146 final Map<String, dynamic>? attributes =
147 localeToResourceAttributes[canonicalLocale]![key] as Map<String, dynamic>?;
148 output.writeln(generateGetter(key, languageResources[key], attributes, languageLocale));
149 }
150 output.writeln('}');
151 int countryCodeCount = 0;
152 int scriptCodeCount = 0;
153 if (languageToScriptCodes.containsKey(languageName)) {
154 scriptCodeCount = languageToScriptCodes[languageName]!.length;
155 // Language has scriptCodes, so we need to properly fallback countries to corresponding
156 // script default values before language default values.
157 for (final String scriptCode in languageToScriptCodes[languageName]!) {
158 final LocaleInfo scriptBaseLocale = LocaleInfo.fromString('${languageName}_$scriptCode');
159 output.writeln(
160 generateClassDeclaration(
161 scriptBaseLocale,
162 generatedClassPrefix,
163 '$generatedClassPrefix${languageLocale.camelCase()}',
164 ),
165 );
166 output.writeln(generateConstructorForCountrySubClass(scriptBaseLocale));
167 final Map<String, String> scriptResources = localeToResources[scriptBaseLocale]!;
168 for (final String key in scriptResources.keys.toList()..sort()) {
169 if (languageResources[key] == scriptResources[key]) {
170 continue;
171 }
172 final Map<String, dynamic>? attributes =
173 localeToResourceAttributes[canonicalLocale]![key] as Map<String, dynamic>?;
174 output.writeln(generateGetter(key, scriptResources[key], attributes, languageLocale));
175 }
176 output.writeln('}');
177
178 final List<LocaleInfo> localeCodes = languageToLocales[languageName]!..sort();
179 for (final LocaleInfo locale in localeCodes) {
180 if (locale.originalString == languageName) {
181 continue;
182 }
183 if (locale.originalString == '${languageName}_$scriptCode') {
184 continue;
185 }
186 if (locale.scriptCode != scriptCode) {
187 continue;
188 }
189 countryCodeCount += 1;
190 output.writeln(
191 generateClassDeclaration(
192 locale,
193 generatedClassPrefix,
194 '$generatedClassPrefix${scriptBaseLocale.camelCase()}',
195 ),
196 );
197 output.writeln(generateConstructorForCountrySubClass(locale));
198 final Map<String, String> localeResources = localeToResources[locale]!;
199 for (final String key in localeResources.keys) {
200 // When script fallback contains the key, we compare to it instead of language fallback.
201 if (scriptResources.containsKey(key)
202 ? scriptResources[key] == localeResources[key]
203 : languageResources[key] == localeResources[key]) {
204 continue;
205 }
206 final Map<String, dynamic>? attributes =
207 localeToResourceAttributes[canonicalLocale]![key] as Map<String, dynamic>?;
208 output.writeln(generateGetter(key, localeResources[key], attributes, languageLocale));
209 }
210 output.writeln('}');
211 }
212 }
213 } else {
214 // No scriptCode. Here, we do not compare against script default (because it
215 // doesn't exist).
216 final List<LocaleInfo> localeCodes = languageToLocales[languageName]!..sort();
217 for (final LocaleInfo locale in localeCodes) {
218 if (locale.originalString == languageName) {
219 continue;
220 }
221 countryCodeCount += 1;
222 final Map<String, String> localeResources = localeToResources[locale]!;
223 output.writeln(
224 generateClassDeclaration(
225 locale,
226 generatedClassPrefix,
227 '$generatedClassPrefix${languageLocale.camelCase()}',
228 ),
229 );
230 output.writeln(generateConstructorForCountrySubClass(locale));
231 for (final String key in localeResources.keys) {
232 if (languageResources[key] == localeResources[key]) {
233 continue;
234 }
235 final Map<String, dynamic>? attributes =
236 localeToResourceAttributes[canonicalLocale]![key] as Map<String, dynamic>?;
237 output.writeln(generateGetter(key, localeResources[key], attributes, languageLocale));
238 }
239 output.writeln('}');
240 }
241 }
242
243 final String scriptCodeMessage =
244 scriptCodeCount == 0
245 ? ''
246 : ' and $scriptCodeCount script${scriptCodeCount == 1 ? '' : 's'}';
247 if (countryCodeCount == 0) {
248 if (scriptCodeCount == 0) {
249 supportedLocales.writeln('/// * `$languageName` - ${describeLocale(languageName)}');
250 } else {
251 supportedLocales.writeln(
252 '/// * `$languageName` - ${describeLocale(languageName)} (plus $scriptCodeCount script${scriptCodeCount == 1 ? '' : 's'})',
253 );
254 }
255 } else if (countryCodeCount == 1) {
256 supportedLocales.writeln(
257 '/// * `$languageName` - ${describeLocale(languageName)} (plus one country variation$scriptCodeMessage)',
258 );
259 } else {
260 supportedLocales.writeln(
261 '/// * `$languageName` - ${describeLocale(languageName)} (plus $countryCodeCount country variations$scriptCodeMessage)',
262 );
263 }
264 }
265
266 // Generate the factory function. Given a Locale it returns the corresponding
267 // base class implementation.
268 output.writeln('''
269
270/// The set of supported languages, as language code strings.
271///
272/// The [$baseClass.delegate] can generate localizations for
273/// any [Locale] with a language code from this set, regardless of the region.
274/// Some regions have specific support (e.g. `de` covers all forms of German,
275/// but there is support for `de-CH` specifically to override some of the
276/// translations for Switzerland).
277///
278/// See also:
279///
280/// * [$factoryName], whose documentation describes these values.
281final Set<String> $supportedLanguagesConstant = HashSet<String>.from(const <String>[
282${languageCodes.map<String>((String value) => " '$value', // ${describeLocale(value)}").toList().join('\n')}
283]);
284
285/// Creates a [$baseClass] instance for the given `locale`.
286///
287/// All of the function's arguments except `locale` will be passed to the [
288/// $baseClass] constructor. (The `localeName` argument of that
289/// constructor is specified by the actual subclass constructor by this
290/// function.)
291///
292/// The following locales are supported by this package:
293///
294/// {@template $supportedLanguagesDocMacro}
295$supportedLocales/// {@endtemplate}
296///
297/// Generally speaking, this method is only intended to be used by
298/// [$baseClass.delegate].
299$factoryDeclaration
300 switch (locale.languageCode) {''');
301 for (final String language in languageToLocales.keys) {
302 // Only one instance of the language.
303 if (languageToLocales[language]!.length == 1) {
304 output.writeln(
305 '''
306 case '$language':
307 return ${callsFactoryWithConst ? 'const ' : ''}$generatedClassPrefix${languageToLocales[language]![0].camelCase()}($factoryArguments);''',
308 );
309 } else if (!languageToScriptCodes.containsKey(language)) {
310 // Does not distinguish between scripts. Switch on countryCode directly.
311 output.writeln('''
312 case '$language': {
313 switch (locale.countryCode) {''');
314 for (final LocaleInfo locale in languageToLocales[language]!) {
315 if (locale.originalString == language) {
316 continue;
317 }
318 assert(locale.length > 1);
319 final String countryCode = locale.countryCode!;
320 output.writeln(
321 '''
322 case '$countryCode':
323 return ${callsFactoryWithConst ? 'const ' : ''}$generatedClassPrefix${locale.camelCase()}($factoryArguments);''',
324 );
325 }
326 output.writeln('''
327 }
328 return ${callsFactoryWithConst ? 'const ' : ''}$generatedClassPrefix${LocaleInfo.fromString(language).camelCase()}($factoryArguments);
329 }''');
330 } else {
331 // Language has scriptCode, add additional switch logic.
332 bool hasCountryCode = false;
333 output.writeln('''
334 case '$language': {
335 switch (locale.scriptCode) {''');
336 for (final String scriptCode in languageToScriptCodes[language]!) {
337 final LocaleInfo scriptLocale = LocaleInfo.fromString('${language}_$scriptCode');
338 output.writeln('''
339 case '$scriptCode': {''');
340 if (languageAndScriptToCountryCodes.containsKey(scriptLocale)) {
341 output.writeln('''
342 switch (locale.countryCode) {''');
343 for (final LocaleInfo locale in languageToLocales[language]!) {
344 if (locale.countryCode == null) {
345 continue;
346 } else {
347 hasCountryCode = true;
348 }
349 if (locale.originalString == language) {
350 continue;
351 }
352 if (locale.scriptCode != scriptCode && locale.scriptCode != null) {
353 continue;
354 }
355 final String countryCode = locale.countryCode!;
356 output.writeln(
357 '''
358 case '$countryCode':
359 return ${callsFactoryWithConst ? 'const ' : ''}$generatedClassPrefix${locale.camelCase()}($factoryArguments);''',
360 );
361 }
362 }
363 // Return a fallback locale that matches scriptCode, but not countryCode.
364 //
365 // Explicitly defined scriptCode fallback:
366 if (languageToLocales[language]!.contains(scriptLocale)) {
367 if (languageAndScriptToCountryCodes.containsKey(scriptLocale)) {
368 output.writeln('''
369 }''');
370 }
371 output.writeln('''
372 return ${callsFactoryWithConst ? 'const ' : ''}$generatedClassPrefix${scriptLocale.camelCase()}($factoryArguments);
373 }''');
374 } else {
375 // Not Explicitly defined, fallback to first locale with the same language and
376 // script:
377 for (final LocaleInfo locale in languageToLocales[language]!) {
378 if (locale.scriptCode != scriptCode) {
379 continue;
380 }
381 if (languageAndScriptToCountryCodes.containsKey(scriptLocale)) {
382 output.writeln('''
383 }''');
384 }
385 output.writeln('''
386 return ${callsFactoryWithConst ? 'const ' : ''}$generatedClassPrefix${scriptLocale.camelCase()}($factoryArguments);
387 }''');
388 break;
389 }
390 }
391 }
392 output.writeln('''
393 }''');
394 if (hasCountryCode) {
395 output.writeln('''
396 switch (locale.countryCode) {''');
397 for (final LocaleInfo locale in languageToLocales[language]!) {
398 if (locale.originalString == language) {
399 continue;
400 }
401 assert(locale.length > 1);
402 if (locale.countryCode == null) {
403 continue;
404 }
405 final String countryCode = locale.countryCode!;
406 output.writeln(
407 '''
408 case '$countryCode':
409 return ${callsFactoryWithConst ? 'const ' : ''}$generatedClassPrefix${locale.camelCase()}($factoryArguments);''',
410 );
411 }
412 output.writeln('''
413 }''');
414 }
415 output.writeln('''
416 return ${callsFactoryWithConst ? 'const ' : ''}$generatedClassPrefix${LocaleInfo.fromString(language).camelCase()}($factoryArguments);
417 }''');
418 }
419 }
420 output.writeln('''
421 }
422 assert(false, '$factoryName() called for unsupported locale "\$locale"');
423 return null;
424}''');
425
426 return output.toString();
427}
428
429/// Returns the appropriate type for getters with the given attributes.
430///
431/// Typically "String", but some (e.g. "timeOfDayFormat") return enums.
432///
433/// Used by [generateGetter] below.
434String generateType(Map<String, dynamic>? attributes) {
435 final bool optional = attributes?.containsKey('optional') ?? false;
436 final String type = switch (attributes?['x-flutter-type']) {
437 'icuShortTimePattern' => 'TimeOfDayFormat',
438 'scriptCategory' => 'ScriptCategory',
439 _ => 'String',
440 };
441 return type + (optional ? '?' : '');
442}
443
444/// Returns the appropriate name for getters with the given attributes.
445///
446/// Typically this is the key unmodified, but some have parameters, and
447/// the GlobalMaterialLocalizations class does the substitution, and for
448/// those we have to therefore provide an alternate name.
449///
450/// Used by [generateGetter] below.
451String generateKey(String key, Map<String, dynamic>? attributes) {
452 if (attributes != null) {
453 if (attributes.containsKey('parameters')) {
454 return '${key}Raw';
455 }
456 switch (attributes['x-flutter-type'] as String?) {
457 case 'icuShortTimePattern':
458 return '${key}Raw';
459 }
460 }
461 if (key == 'datePickerDateOrder') {
462 return 'datePickerDateOrderString';
463 }
464 if (key == 'datePickerDateTimeOrder') {
465 return 'datePickerDateTimeOrderString';
466 }
467 return key;
468}
469
470const Map<String, String> _icuTimeOfDayToEnum = <String, String>{
471 'HH:mm': 'TimeOfDayFormat.HH_colon_mm',
472 'HH.mm': 'TimeOfDayFormat.HH_dot_mm',
473 "HH 'h' mm": 'TimeOfDayFormat.frenchCanadian',
474 'HH:mm น.': 'TimeOfDayFormat.HH_colon_mm',
475 'H:mm': 'TimeOfDayFormat.H_colon_mm',
476 'h:mm a': 'TimeOfDayFormat.h_colon_mm_space_a',
477 'a h:mm': 'TimeOfDayFormat.a_space_h_colon_mm',
478 'ah:mm': 'TimeOfDayFormat.a_space_h_colon_mm',
479};
480
481const Map<String, String> _scriptCategoryToEnum = <String, String>{
482 'English-like': 'ScriptCategory.englishLike',
483 'dense': 'ScriptCategory.dense',
484 'tall': 'ScriptCategory.tall',
485};
486
487/// Returns the literal that describes the value returned by getters
488/// with the given attributes.
489///
490/// This handles cases like the value being a literal `null`, an enum, and so
491/// on. The default is to treat the value as a string and escape it and quote
492/// it.
493///
494/// Used by [generateGetter] below.
495String? generateValue(String? value, Map<String, dynamic>? attributes, LocaleInfo locale) {
496 if (value == null) {
497 return null;
498 }
499 // cupertino_en.arb doesn't use x-flutter-type.
500 if (attributes != null) {
501 switch (attributes['x-flutter-type'] as String?) {
502 case 'icuShortTimePattern':
503 if (!_icuTimeOfDayToEnum.containsKey(value)) {
504 throw Exception(
505 '"$value" is not one of the ICU short time patterns supported '
506 'by the material library. Here is the list of supported '
507 'patterns:\n ${_icuTimeOfDayToEnum.keys.join('\n ')}',
508 );
509 }
510 return _icuTimeOfDayToEnum[value];
511 case 'scriptCategory':
512 if (!_scriptCategoryToEnum.containsKey(value)) {
513 throw Exception(
514 '"$value" is not one of the scriptCategory values supported '
515 'by the material library. Here is the list of supported '
516 'values:\n ${_scriptCategoryToEnum.keys.join('\n ')}',
517 );
518 }
519 return _scriptCategoryToEnum[value];
520 }
521 }
522 return generateEncodedString(locale.languageCode, value);
523}
524
525/// Combines [generateType], [generateKey], and [generateValue] to return
526/// the source of getters for the GlobalMaterialLocalizations subclass.
527/// The locale is the locale for which the getter is being generated.
528String generateGetter(
529 String key,
530 String? value,
531 Map<String, dynamic>? attributes,
532 LocaleInfo locale,
533) {
534 final String type = generateType(attributes);
535 key = generateKey(key, attributes);
536 final String? generatedValue = generateValue(value, attributes, locale);
537 return '''
538
539 @override
540 $type get $key => $generatedValue;''';
541}
542
543void main(List<String> rawArgs) {
544 checkCwdIsRepoRoot('gen_localizations');
545 final GeneratorOptions options = parseArgs(rawArgs);
546
547 // filenames are assumed to end in "prefix_lc.arb" or "prefix_lc_cc.arb", where prefix
548 // is the 2nd command line argument, lc is a language code and cc is the country
549 // code. In most cases both codes are just two characters.
550
551 final Directory directory = Directory(
552 path.join('packages', 'flutter_localizations', 'lib', 'src', 'l10n'),
553 );
554 final RegExp widgetsFilenameRE = RegExp(r'widgets_(\w+)\.arb$');
555 final RegExp materialFilenameRE = RegExp(r'material_(\w+)\.arb$');
556 final RegExp cupertinoFilenameRE = RegExp(r'cupertino_(\w+)\.arb$');
557
558 try {
559 validateEnglishLocalizations(File(path.join(directory.path, 'widgets_en.arb')));
560 validateEnglishLocalizations(File(path.join(directory.path, 'material_en.arb')));
561 validateEnglishLocalizations(File(path.join(directory.path, 'cupertino_en.arb')));
562 } on ValidationError catch (exception) {
563 exitWithError('$exception');
564 }
565
566 // Only rewrite material_kn.arb and cupertino_en.arb if overwriting the
567 // Material and Cupertino localizations files.
568 if (options.writeToFile) {
569 // Encodes the material_kn.arb file and the cupertino_en.arb files before
570 // generating localizations. This prevents a subset of Emacs users from
571 // crashing when opening up the Flutter source code.
572 // See https://github.com/flutter/flutter/issues/36704 for more context.
573 encodeKnArbFiles(directory);
574 }
575
576 precacheLanguageAndRegionTags();
577
578 // Maps of locales to resource key/value pairs for Widgets ARBs.
579 final Map<LocaleInfo, Map<String, String>> widgetsLocaleToResources =
580 <LocaleInfo, Map<String, String>>{};
581 // Maps of locales to resource key/attributes pairs for Widgets ARBs.
582 // https://github.com/googlei18n/app-resource-bundle/wiki/ApplicationResourceBundleSpecification#resource-attributes
583 final Map<LocaleInfo, Map<String, dynamic>> widgetsLocaleToResourceAttributes =
584 <LocaleInfo, Map<String, dynamic>>{};
585
586 // Maps of locales to resource key/value pairs for Material ARBs.
587 final Map<LocaleInfo, Map<String, String>> materialLocaleToResources =
588 <LocaleInfo, Map<String, String>>{};
589 // Maps of locales to resource key/attributes pairs for Material ARBs.
590 // https://github.com/googlei18n/app-resource-bundle/wiki/ApplicationResourceBundleSpecification#resource-attributes
591 final Map<LocaleInfo, Map<String, dynamic>> materialLocaleToResourceAttributes =
592 <LocaleInfo, Map<String, dynamic>>{};
593
594 // Maps of locales to resource key/value pairs for Cupertino ARBs.
595 final Map<LocaleInfo, Map<String, String>> cupertinoLocaleToResources =
596 <LocaleInfo, Map<String, String>>{};
597 // Maps of locales to resource key/attributes pairs for Cupertino ARBs.
598 // https://github.com/googlei18n/app-resource-bundle/wiki/ApplicationResourceBundleSpecification#resource-attributes
599 final Map<LocaleInfo, Map<String, dynamic>> cupertinoLocaleToResourceAttributes =
600 <LocaleInfo, Map<String, dynamic>>{};
601
602 loadMatchingArbsIntoBundleMaps(
603 directory: directory,
604 filenamePattern: widgetsFilenameRE,
605 localeToResources: widgetsLocaleToResources,
606 localeToResourceAttributes: widgetsLocaleToResourceAttributes,
607 );
608 loadMatchingArbsIntoBundleMaps(
609 directory: directory,
610 filenamePattern: materialFilenameRE,
611 localeToResources: materialLocaleToResources,
612 localeToResourceAttributes: materialLocaleToResourceAttributes,
613 );
614 loadMatchingArbsIntoBundleMaps(
615 directory: directory,
616 filenamePattern: cupertinoFilenameRE,
617 localeToResources: cupertinoLocaleToResources,
618 localeToResourceAttributes: cupertinoLocaleToResourceAttributes,
619 );
620
621 try {
622 validateLocalizations(
623 widgetsLocaleToResources,
624 widgetsLocaleToResourceAttributes,
625 removeUndefined: options.removeUndefined,
626 );
627 validateLocalizations(
628 materialLocaleToResources,
629 materialLocaleToResourceAttributes,
630 removeUndefined: options.removeUndefined,
631 );
632 validateLocalizations(
633 cupertinoLocaleToResources,
634 cupertinoLocaleToResourceAttributes,
635 removeUndefined: options.removeUndefined,
636 );
637 } on ValidationError catch (exception) {
638 exitWithError('$exception');
639 }
640 if (options.removeUndefined) {
641 removeUndefinedLocalizations(widgetsLocaleToResources);
642 removeUndefinedLocalizations(materialLocaleToResources);
643 removeUndefinedLocalizations(cupertinoLocaleToResources);
644 }
645
646 final String? widgetsLocalizations =
647 options.writeToFile || !options.cupertinoOnly
648 ? generateArbBasedLocalizationSubclasses(
649 localeToResources: widgetsLocaleToResources,
650 localeToResourceAttributes: widgetsLocaleToResourceAttributes,
651 generatedClassPrefix: 'WidgetsLocalization',
652 baseClass: 'GlobalWidgetsLocalizations',
653 generateHeader: generateWidgetsHeader,
654 generateConstructor: generateWidgetsConstructor,
655 generateConstructorForCountrySubClass: generateWidgetsConstructorForCountrySubclass,
656 factoryName: widgetsFactoryName,
657 factoryDeclaration: widgetsFactoryDeclaration,
658 callsFactoryWithConst: true,
659 factoryArguments: widgetsFactoryArguments,
660 supportedLanguagesConstant: widgetsSupportedLanguagesConstant,
661 supportedLanguagesDocMacro: widgetsSupportedLanguagesDocMacro,
662 )
663 : null;
664 final String? materialLocalizations =
665 options.writeToFile || !options.cupertinoOnly
666 ? generateArbBasedLocalizationSubclasses(
667 localeToResources: materialLocaleToResources,
668 localeToResourceAttributes: materialLocaleToResourceAttributes,
669 generatedClassPrefix: 'MaterialLocalization',
670 baseClass: 'GlobalMaterialLocalizations',
671 generateHeader: generateMaterialHeader,
672 generateConstructor: generateMaterialConstructor,
673 factoryName: materialFactoryName,
674 factoryDeclaration: materialFactoryDeclaration,
675 callsFactoryWithConst: false,
676 factoryArguments: materialFactoryArguments,
677 supportedLanguagesConstant: materialSupportedLanguagesConstant,
678 supportedLanguagesDocMacro: materialSupportedLanguagesDocMacro,
679 )
680 : null;
681 final String? cupertinoLocalizations =
682 options.writeToFile || !options.materialOnly
683 ? generateArbBasedLocalizationSubclasses(
684 localeToResources: cupertinoLocaleToResources,
685 localeToResourceAttributes: cupertinoLocaleToResourceAttributes,
686 generatedClassPrefix: 'CupertinoLocalization',
687 baseClass: 'GlobalCupertinoLocalizations',
688 generateHeader: generateCupertinoHeader,
689 generateConstructor: generateCupertinoConstructor,
690 factoryName: cupertinoFactoryName,
691 factoryDeclaration: cupertinoFactoryDeclaration,
692 callsFactoryWithConst: false,
693 factoryArguments: cupertinoFactoryArguments,
694 supportedLanguagesConstant: cupertinoSupportedLanguagesConstant,
695 supportedLanguagesDocMacro: cupertinoSupportedLanguagesDocMacro,
696 )
697 : null;
698
699 if (options.writeToFile) {
700 final File widgetsLocalizationsFile = File(
701 path.join(directory.path, 'generated_widgets_localizations.dart'),
702 );
703 widgetsLocalizationsFile.writeAsStringSync(widgetsLocalizations!, flush: true);
704 final File materialLocalizationsFile = File(
705 path.join(directory.path, 'generated_material_localizations.dart'),
706 );
707 materialLocalizationsFile.writeAsStringSync(materialLocalizations!, flush: true);
708 final File cupertinoLocalizationsFile = File(
709 path.join(directory.path, 'generated_cupertino_localizations.dart'),
710 );
711 cupertinoLocalizationsFile.writeAsStringSync(cupertinoLocalizations!, flush: true);
712 } else {
713 if (options.cupertinoOnly) {
714 stdout.write(cupertinoLocalizations);
715 } else if (options.materialOnly) {
716 stdout.write(materialLocalizations);
717 } else if (options.widgetsOnly) {
718 stdout.write(widgetsLocalizations);
719 } else {
720 stdout.write(widgetsLocalizations);
721 stdout.write(materialLocalizations);
722 stdout.write(cupertinoLocalizations);
723 }
724 }
725}
726

Provided by KDAB

Privacy Policy
Learn more about Flutter for embedded and desktop on industrialflutter.com