• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2017 The Chromium 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 a getMaterialTranslation() and a
6// getCupertinoTranslation() function that look up the translations provided by
7// the arb files. The returned value is a generated instance of a
8// GlobalMaterialLocalizations or a GlobalCupertinoLocalizations that
9// 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/gen_localizations.dart
33// ```
34//
35// If the data looks good, use the `-w` or `--overwrite` option to overwrite the
36// packages/flutter_localizations/lib/src/l10n/generated_material_localizations.dart
37// and packages/flutter_localizations/lib/src/l10n/generated_cupertino_localizations.dart file:
38//
39// ```
40// dart dev/tools/localization/gen_localizations.dart --overwrite
41// ```
42
43import 'dart:async';
44import 'dart:io';
45
46import 'package:path/path.dart' as path;
47import 'package:meta/meta.dart';
48
49import 'gen_cupertino_localizations.dart';
50import 'gen_material_localizations.dart';
51import 'localizations_utils.dart';
52import 'localizations_validator.dart';
53
54/// This is the core of this script; it generates the code used for translations.
55String generateArbBasedLocalizationSubclasses({
56  @required Map<LocaleInfo, Map<String, String>> localeToResources,
57  @required Map<LocaleInfo, Map<String, dynamic>> localeToResourceAttributes,
58  @required String generatedClassPrefix,
59  @required String baseClass,
60  @required HeaderGenerator generateHeader,
61  @required ConstructorGenerator generateConstructor,
62  @required String factoryName,
63  @required String factoryDeclaration,
64  @required String factoryArguments,
65  @required String supportedLanguagesConstant,
66  @required String supportedLanguagesDocMacro,
67}) {
68  assert(localeToResources != null);
69  assert(localeToResourceAttributes != null);
70  assert(generatedClassPrefix.isNotEmpty);
71  assert(baseClass.isNotEmpty);
72  assert(generateHeader != null);
73  assert(generateConstructor != null);
74  assert(factoryName.isNotEmpty);
75  assert(factoryDeclaration.isNotEmpty);
76  assert(factoryArguments.isNotEmpty);
77  assert(supportedLanguagesConstant.isNotEmpty);
78  assert(supportedLanguagesDocMacro.isNotEmpty);
79
80  final StringBuffer output = StringBuffer();
81  output.writeln(generateHeader('dart dev/tools/localization/gen_localizations.dart --overwrite'));
82
83  final StringBuffer supportedLocales = StringBuffer();
84
85  final Map<String, List<LocaleInfo>> languageToLocales = <String, List<LocaleInfo>>{};
86  final Map<String, Set<String>> languageToScriptCodes = <String, Set<String>>{};
87  // Used to calculate if there are any corresponding countries for a given language and script.
88  final Map<LocaleInfo, Set<String>> languageAndScriptToCountryCodes = <LocaleInfo, Set<String>>{};
89  final Set<String> allResourceIdentifiers = <String>{};
90  for (LocaleInfo locale in localeToResources.keys.toList()..sort()) {
91    if (locale.scriptCode != null) {
92      languageToScriptCodes[locale.languageCode] ??= <String>{};
93      languageToScriptCodes[locale.languageCode].add(locale.scriptCode);
94    }
95    if (locale.countryCode != null && locale.scriptCode != null) {
96      final LocaleInfo key = LocaleInfo.fromString(locale.languageCode + '_' + locale.scriptCode);
97      languageAndScriptToCountryCodes[key] ??= <String>{};
98      languageAndScriptToCountryCodes[key].add(locale.countryCode);
99    }
100    languageToLocales[locale.languageCode] ??= <LocaleInfo>[];
101    languageToLocales[locale.languageCode].add(locale);
102    allResourceIdentifiers.addAll(localeToResources[locale].keys);
103  }
104
105  // We generate one class per supported language (e.g.
106  // `MaterialLocalizationEn`). These implement everything that is needed by the
107  // superclass (e.g. GlobalMaterialLocalizations).
108
109  // We also generate one subclass for each locale with a script code (e.g.
110  // `MaterialLocalizationZhHant`). Their superclasses are the aforementioned
111  // language classes for the same locale but without a script code (e.g.
112  // `MaterialLocalizationZh`).
113
114  // We also generate one subclass for each locale with a country code (e.g.
115  // `MaterialLocalizationEnGb`). Their superclasses are the aforementioned
116  // language classes for the same locale but without a country code (e.g.
117  // `MaterialLocalizationEn`).
118
119  // If scriptCodes for a language are defined, we expect a scriptCode to be
120  // defined for locales that contain a countryCode. The superclass becomes
121  // the script sublcass (e.g. `MaterialLocalizationZhHant`) and the generated
122  // subclass will also contain the script code (e.g. `MaterialLocalizationZhHantTW`).
123
124  // When scriptCodes are not defined for languages that use scriptCodes to distinguish
125  // between significantly differing scripts, we assume the scriptCodes in the
126  // [LocaleInfo.fromString] factory and add it to the [LocaleInfo]. We then generate
127  // the script classes based on the first locale that we assume to use the script.
128
129  final List<String> allKeys = allResourceIdentifiers.toList()..sort();
130  final List<String> languageCodes = languageToLocales.keys.toList()..sort();
131  final LocaleInfo canonicalLocale = LocaleInfo.fromString('en');
132  for (String languageName in languageCodes) {
133    final LocaleInfo languageLocale = LocaleInfo.fromString(languageName);
134    output.writeln(generateClassDeclaration(languageLocale, generatedClassPrefix, baseClass));
135    output.writeln(generateConstructor(languageLocale));
136
137    final Map<String, String> languageResources = localeToResources[languageLocale];
138    for (String key in allKeys) {
139      final Map<String, dynamic> attributes = localeToResourceAttributes[canonicalLocale][key];
140      output.writeln(generateGetter(key, languageResources[key], attributes, languageLocale));
141    }
142    output.writeln('}');
143    int countryCodeCount = 0;
144    int scriptCodeCount = 0;
145    if (languageToScriptCodes.containsKey(languageName)) {
146      scriptCodeCount = languageToScriptCodes[languageName].length;
147      // Language has scriptCodes, so we need to properly fallback countries to corresponding
148      // script default values before language default values.
149      for (String scriptCode in languageToScriptCodes[languageName]) {
150        final LocaleInfo scriptBaseLocale = LocaleInfo.fromString(languageName + '_' + scriptCode);
151        output.writeln(generateClassDeclaration(
152          scriptBaseLocale,
153          generatedClassPrefix,
154          '$generatedClassPrefix${camelCase(languageLocale)}',
155        ));
156        output.writeln(generateConstructor(scriptBaseLocale));
157        final Map<String, String> scriptResources = localeToResources[scriptBaseLocale];
158        for (String key in scriptResources.keys) {
159          if (languageResources[key] == scriptResources[key])
160            continue;
161          final Map<String, dynamic> attributes = localeToResourceAttributes[canonicalLocale][key];
162          output.writeln(generateGetter(key, scriptResources[key], attributes, languageLocale));
163        }
164        output.writeln('}');
165
166        final List<LocaleInfo> localeCodes = languageToLocales[languageName]..sort();
167        for (LocaleInfo locale in localeCodes) {
168          if (locale.originalString == languageName)
169            continue;
170          if (locale.originalString == languageName + '_' + scriptCode)
171            continue;
172          if (locale.scriptCode != scriptCode)
173            continue;
174          countryCodeCount += 1;
175          output.writeln(generateClassDeclaration(
176            locale,
177            generatedClassPrefix,
178            '$generatedClassPrefix${camelCase(scriptBaseLocale)}',
179          ));
180          output.writeln(generateConstructor(locale));
181          final Map<String, String> localeResources = localeToResources[locale];
182          for (String key in localeResources.keys) {
183            // When script fallback contains the key, we compare to it instead of language fallback.
184            if (scriptResources.containsKey(key) ? scriptResources[key] == localeResources[key] : languageResources[key] == localeResources[key])
185              continue;
186            final Map<String, dynamic> attributes = localeToResourceAttributes[canonicalLocale][key];
187            output.writeln(generateGetter(key, localeResources[key], attributes, languageLocale));
188          }
189         output.writeln('}');
190        }
191      }
192    } else {
193      // No scriptCode. Here, we do not compare against script default (because it
194      // doesn't exist).
195      final List<LocaleInfo> localeCodes = languageToLocales[languageName]..sort();
196      for (LocaleInfo locale in localeCodes) {
197        if (locale.originalString == languageName)
198          continue;
199        countryCodeCount += 1;
200        final Map<String, String> localeResources = localeToResources[locale];
201        output.writeln(generateClassDeclaration(
202          locale,
203          generatedClassPrefix,
204          '$generatedClassPrefix${camelCase(languageLocale)}',
205        ));
206        output.writeln(generateConstructor(locale));
207        for (String key in localeResources.keys) {
208          if (languageResources[key] == localeResources[key])
209            continue;
210          final Map<String, dynamic> attributes = localeToResourceAttributes[canonicalLocale][key];
211          output.writeln(generateGetter(key, localeResources[key], attributes, languageLocale));
212        }
213       output.writeln('}');
214      }
215    }
216    final String scriptCodeMessage = scriptCodeCount == 0 ? '' : ' and $scriptCodeCount script' + (scriptCodeCount == 1 ? '' : 's');
217    if (countryCodeCount == 0) {
218      if (scriptCodeCount == 0)
219        supportedLocales.writeln('///  * `$languageName` - ${describeLocale(languageName)}');
220      else
221        supportedLocales.writeln('///  * `$languageName` - ${describeLocale(languageName)} (plus $scriptCodeCount script' + (scriptCodeCount == 1 ? '' : 's') + ')');
222
223    } else if (countryCodeCount == 1) {
224      supportedLocales.writeln('///  * `$languageName` - ${describeLocale(languageName)} (plus one country variation$scriptCodeMessage)');
225    } else {
226      supportedLocales.writeln('///  * `$languageName` - ${describeLocale(languageName)} (plus $countryCodeCount country variations$scriptCodeMessage)');
227    }
228  }
229
230  // Generate the factory function. Given a Locale it returns the corresponding
231  // base class implementation.
232  output.writeln('''
233
234/// The set of supported languages, as language code strings.
235///
236/// The [$baseClass.delegate] can generate localizations for
237/// any [Locale] with a language code from this set, regardless of the region.
238/// Some regions have specific support (e.g. `de` covers all forms of German,
239/// but there is support for `de-CH` specifically to override some of the
240/// translations for Switzerland).
241///
242/// See also:
243///
244///  * [$factoryName], whose documentation describes these values.
245final Set<String> $supportedLanguagesConstant = HashSet<String>.from(const <String>[
246${languageCodes.map<String>((String value) => "  '$value', // ${describeLocale(value)}").toList().join('\n')}
247]);
248
249/// Creates a [$baseClass] instance for the given `locale`.
250///
251/// All of the function's arguments except `locale` will be passed to the [
252/// $baseClass] constructor. (The `localeName` argument of that
253/// constructor is specified by the actual subclass constructor by this
254/// function.)
255///
256/// The following locales are supported by this package:
257///
258/// {@template $supportedLanguagesDocMacro}
259$supportedLocales/// {@endtemplate}
260///
261/// Generally speaking, this method is only intended to be used by
262/// [$baseClass.delegate].
263$factoryDeclaration
264  switch (locale.languageCode) {''');
265  for (String language in languageToLocales.keys) {
266    // Only one instance of the language.
267    if (languageToLocales[language].length == 1) {
268      output.writeln('''
269    case '$language':
270      return $generatedClassPrefix${camelCase(languageToLocales[language][0])}($factoryArguments);''');
271    } else if (!languageToScriptCodes.containsKey(language)) { // Does not distinguish between scripts. Switch on countryCode directly.
272      output.writeln('''
273    case '$language': {
274      switch (locale.countryCode) {''');
275      for (LocaleInfo locale in languageToLocales[language]) {
276        if (locale.originalString == language)
277          continue;
278        assert(locale.length > 1);
279        final String countryCode = locale.countryCode;
280        output.writeln('''
281        case '$countryCode':
282          return $generatedClassPrefix${camelCase(locale)}($factoryArguments);''');
283      }
284      output.writeln('''
285      }
286      return $generatedClassPrefix${camelCase(LocaleInfo.fromString(language))}($factoryArguments);
287    }''');
288    } else { // Language has scriptCode, add additional switch logic.
289      bool hasCountryCode = false;
290      output.writeln('''
291    case '$language': {
292      switch (locale.scriptCode) {''');
293      for (String scriptCode in languageToScriptCodes[language]) {
294        final LocaleInfo scriptLocale = LocaleInfo.fromString(language + '_' + scriptCode);
295        output.writeln('''
296        case '$scriptCode': {''');
297        if (languageAndScriptToCountryCodes.containsKey(scriptLocale)) {
298          output.writeln('''
299          switch (locale.countryCode) {''');
300          for (LocaleInfo locale in languageToLocales[language]) {
301            if (locale.countryCode == null)
302              continue;
303            else
304              hasCountryCode = true;
305            if (locale.originalString == language)
306              continue;
307            if (locale.scriptCode != scriptCode && locale.scriptCode != null)
308              continue;
309            final String countryCode = locale.countryCode;
310            output.writeln('''
311            case '$countryCode':
312              return $generatedClassPrefix${camelCase(locale)}($factoryArguments);''');
313          }
314        }
315        // Return a fallback locale that matches scriptCode, but not countryCode.
316        //
317        // Explicitly defined scriptCode fallback:
318        if (languageToLocales[language].contains(scriptLocale)) {
319          if (languageAndScriptToCountryCodes.containsKey(scriptLocale)) {
320            output.writeln('''
321          }''');
322          }
323          output.writeln('''
324          return $generatedClassPrefix${camelCase(scriptLocale)}($factoryArguments);
325        }''');
326        } else {
327          // Not Explicitly defined, fallback to first locale with the same language and
328          // script:
329          for (LocaleInfo locale in languageToLocales[language]) {
330            if (locale.scriptCode != scriptCode)
331              continue;
332            if (languageAndScriptToCountryCodes.containsKey(scriptLocale)) {
333              output.writeln('''
334          }''');
335            }
336            output.writeln('''
337          return $generatedClassPrefix${camelCase(scriptLocale)}($factoryArguments);
338        }''');
339            break;
340          }
341        }
342      }
343      output.writeln('''
344      }''');
345      if (hasCountryCode) {
346      output.writeln('''
347      switch (locale.countryCode) {''');
348        for (LocaleInfo locale in languageToLocales[language]) {
349          if (locale.originalString == language)
350            continue;
351          assert(locale.length > 1);
352          if (locale.countryCode == null)
353            continue;
354          final String countryCode = locale.countryCode;
355          output.writeln('''
356        case '$countryCode':
357          return $generatedClassPrefix${camelCase(locale)}($factoryArguments);''');
358        }
359        output.writeln('''
360      }''');
361      }
362      output.writeln('''
363      return $generatedClassPrefix${camelCase(LocaleInfo.fromString(language))}($factoryArguments);
364    }''');
365    }
366  }
367  output.writeln('''
368  }
369  assert(false, '$factoryName() called for unsupported locale "\$locale"');
370  return null;
371}''');
372
373  return output.toString();
374}
375
376/// Returns the appropriate type for getters with the given attributes.
377///
378/// Typically "String", but some (e.g. "timeOfDayFormat") return enums.
379///
380/// Used by [generateGetter] below.
381String generateType(Map<String, dynamic> attributes) {
382  if (attributes != null) {
383    switch (attributes['x-flutter-type']) {
384      case 'icuShortTimePattern':
385        return 'TimeOfDayFormat';
386      case 'scriptCategory':
387        return 'ScriptCategory';
388    }
389  }
390  return 'String';
391}
392
393/// Returns the appropriate name for getters with the given attributes.
394///
395/// Typically this is the key unmodified, but some have parameters, and
396/// the GlobalMaterialLocalizations class does the substitution, and for
397/// those we have to therefore provide an alternate name.
398///
399/// Used by [generateGetter] below.
400String generateKey(String key, Map<String, dynamic> attributes) {
401  if (attributes != null) {
402    if (attributes.containsKey('parameters'))
403      return '${key}Raw';
404    switch (attributes['x-flutter-type']) {
405      case 'icuShortTimePattern':
406        return '${key}Raw';
407    }
408  }
409  if (key == 'datePickerDateOrder')
410    return 'datePickerDateOrderString';
411  if (key == 'datePickerDateTimeOrder')
412    return 'datePickerDateTimeOrderString';
413  return key;
414}
415
416const Map<String, String> _icuTimeOfDayToEnum = <String, String>{
417  'HH:mm': 'TimeOfDayFormat.HH_colon_mm',
418  'HH.mm': 'TimeOfDayFormat.HH_dot_mm',
419  "HH 'h' mm": 'TimeOfDayFormat.frenchCanadian',
420  'HH:mm น.': 'TimeOfDayFormat.HH_colon_mm',
421  'H:mm': 'TimeOfDayFormat.H_colon_mm',
422  'h:mm a': 'TimeOfDayFormat.h_colon_mm_space_a',
423  'a h:mm': 'TimeOfDayFormat.a_space_h_colon_mm',
424  'ah:mm': 'TimeOfDayFormat.a_space_h_colon_mm',
425};
426
427const Map<String, String> _scriptCategoryToEnum = <String, String>{
428  'English-like': 'ScriptCategory.englishLike',
429  'dense': 'ScriptCategory.dense',
430  'tall': 'ScriptCategory.tall',
431};
432
433/// Returns the literal that describes the value returned by getters
434/// with the given attributes.
435///
436/// This handles cases like the value being a literal `null`, an enum, and so
437/// on. The default is to treat the value as a string and escape it and quote
438/// it.
439///
440/// Used by [generateGetter] below.
441String generateValue(String value, Map<String, dynamic> attributes, LocaleInfo locale) {
442  if (value == null)
443    return null;
444  // cupertino_en.arb doesn't use x-flutter-type.
445  if (attributes != null) {
446    switch (attributes['x-flutter-type']) {
447      case 'icuShortTimePattern':
448        if (!_icuTimeOfDayToEnum.containsKey(value)) {
449          throw Exception(
450            '"$value" is not one of the ICU short time patterns supported '
451            'by the material library. Here is the list of supported '
452            'patterns:\n  ' + _icuTimeOfDayToEnum.keys.join('\n  ')
453          );
454        }
455        return _icuTimeOfDayToEnum[value];
456      case 'scriptCategory':
457        if (!_scriptCategoryToEnum.containsKey(value)) {
458          throw Exception(
459            '"$value" is not one of the scriptCategory values supported '
460            'by the material library. Here is the list of supported '
461            'values:\n  ' + _scriptCategoryToEnum.keys.join('\n  ')
462          );
463        }
464        return _scriptCategoryToEnum[value];
465    }
466  }
467  // Localization strings for the Kannada locale ('kn') are encoded because
468  // some of the localized strings contain characters that can crash Emacs on Linux.
469  // See packages/flutter_localizations/lib/src/l10n/README for more information.
470  return locale.languageCode == 'kn' ? generateEncodedString(value) : generateString(value);
471}
472
473/// Combines [generateType], [generateKey], and [generateValue] to return
474/// the source of getters for the GlobalMaterialLocalizations subclass.
475/// The locale is the locale for which the getter is being generated.
476String generateGetter(String key, String value, Map<String, dynamic> attributes, LocaleInfo locale) {
477  final String type = generateType(attributes);
478  key = generateKey(key, attributes);
479  value = generateValue(value, attributes, locale);
480      return '''
481
482  @override
483  $type get $key => $value;''';
484}
485
486Future<void> main(List<String> rawArgs) async {
487  checkCwdIsRepoRoot('gen_localizations');
488  final GeneratorOptions options = parseArgs(rawArgs);
489
490  // filenames are assumed to end in "prefix_lc.arb" or "prefix_lc_cc.arb", where prefix
491  // is the 2nd command line argument, lc is a language code and cc is the country
492  // code. In most cases both codes are just two characters.
493
494  final Directory directory = Directory(path.join('packages', 'flutter_localizations', 'lib', 'src', 'l10n'));
495  final RegExp materialFilenameRE = RegExp(r'material_(\w+)\.arb$');
496  final RegExp cupertinoFilenameRE = RegExp(r'cupertino_(\w+)\.arb$');
497
498  try {
499    validateEnglishLocalizations(File(path.join(directory.path, 'material_en.arb')));
500    validateEnglishLocalizations(File(path.join(directory.path, 'cupertino_en.arb')));
501  } on ValidationError catch (exception) {
502    exitWithError('$exception');
503  }
504
505  await precacheLanguageAndRegionTags();
506
507  // Maps of locales to resource key/value pairs for Material ARBs.
508  final Map<LocaleInfo, Map<String, String>> materialLocaleToResources = <LocaleInfo, Map<String, String>>{};
509  // Maps of locales to resource key/attributes pairs for Material ARBs..
510  // https://github.com/googlei18n/app-resource-bundle/wiki/ApplicationResourceBundleSpecification#resource-attributes
511  final Map<LocaleInfo, Map<String, dynamic>> materialLocaleToResourceAttributes = <LocaleInfo, Map<String, dynamic>>{};
512  // Maps of locales to resource key/value pairs for Cupertino ARBs.
513  final Map<LocaleInfo, Map<String, String>> cupertinoLocaleToResources = <LocaleInfo, Map<String, String>>{};
514  // Maps of locales to resource key/attributes pairs for Cupertino ARBs..
515  // https://github.com/googlei18n/app-resource-bundle/wiki/ApplicationResourceBundleSpecification#resource-attributes
516  final Map<LocaleInfo, Map<String, dynamic>> cupertinoLocaleToResourceAttributes = <LocaleInfo, Map<String, dynamic>>{};
517
518  loadMatchingArbsIntoBundleMaps(
519    directory: directory,
520    filenamePattern: materialFilenameRE,
521    localeToResources: materialLocaleToResources,
522    localeToResourceAttributes: materialLocaleToResourceAttributes,
523  );
524  loadMatchingArbsIntoBundleMaps(
525    directory: directory,
526    filenamePattern: cupertinoFilenameRE,
527    localeToResources: cupertinoLocaleToResources,
528    localeToResourceAttributes: cupertinoLocaleToResourceAttributes,
529  );
530
531  try {
532    validateLocalizations(materialLocaleToResources, materialLocaleToResourceAttributes);
533    validateLocalizations(cupertinoLocaleToResources, cupertinoLocaleToResourceAttributes);
534  } on ValidationError catch (exception) {
535    exitWithError('$exception');
536  }
537
538  final String materialLocalizations = options.writeToFile || !options.cupertinoOnly
539      ? generateArbBasedLocalizationSubclasses(
540        localeToResources: materialLocaleToResources,
541        localeToResourceAttributes: materialLocaleToResourceAttributes,
542        generatedClassPrefix: 'MaterialLocalization',
543        baseClass: 'GlobalMaterialLocalizations',
544        generateHeader: generateMaterialHeader,
545        generateConstructor: generateMaterialConstructor,
546        factoryName: materialFactoryName,
547        factoryDeclaration: materialFactoryDeclaration,
548        factoryArguments: materialFactoryArguments,
549        supportedLanguagesConstant: materialSupportedLanguagesConstant,
550        supportedLanguagesDocMacro: materialSupportedLanguagesDocMacro,
551      )
552      : null;
553  final String cupertinoLocalizations = options.writeToFile || !options.materialOnly
554      ? generateArbBasedLocalizationSubclasses(
555        localeToResources: cupertinoLocaleToResources,
556        localeToResourceAttributes: cupertinoLocaleToResourceAttributes,
557        generatedClassPrefix: 'CupertinoLocalization',
558        baseClass: 'GlobalCupertinoLocalizations',
559        generateHeader: generateCupertinoHeader,
560        generateConstructor: generateCupertinoConstructor,
561        factoryName: cupertinoFactoryName,
562        factoryDeclaration: cupertinoFactoryDeclaration,
563        factoryArguments: cupertinoFactoryArguments,
564        supportedLanguagesConstant: cupertinoSupportedLanguagesConstant,
565        supportedLanguagesDocMacro: cupertinoSupportedLanguagesDocMacro,
566      )
567      : null;
568
569  if (options.writeToFile) {
570    final File materialLocalizationsFile = File(path.join(directory.path, 'generated_material_localizations.dart'));
571    materialLocalizationsFile.writeAsStringSync(materialLocalizations, flush: true);
572    final File cupertinoLocalizationsFile = File(path.join(directory.path, 'generated_cupertino_localizations.dart'));
573    cupertinoLocalizationsFile.writeAsStringSync(cupertinoLocalizations, flush: true);
574  } else {
575    if (!options.cupertinoOnly) {
576      stdout.write(materialLocalizations);
577    }
578    if (!options.materialOnly) {
579      stdout.write(cupertinoLocalizations);
580    }
581  }
582}
583