Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions dev/tools/localization/bin/encode_kn_arb_files.dart
Original file line number Diff line number Diff line change
Expand Up @@ -69,18 +69,23 @@ void _rewriteBundle(File file, Map<String, dynamic> bundle) {
}

void encodeKnArbFiles(Directory directory) {
final File widgetsArbFile = File(path.join(directory.path, 'widgets_kn.arb'));
final File materialArbFile = File(path.join(directory.path, 'material_kn.arb'));
final File cupertinoArbFile = File(path.join(directory.path, 'cupertino_kn.arb'));

final Map<String, dynamic> widgetsBundle = _loadBundle(widgetsArbFile);
final Map<String, dynamic> materialBundle = _loadBundle(materialArbFile);
final Map<String, dynamic> cupertinoBundle = _loadBundle(cupertinoArbFile);

_encodeBundleTranslations(widgetsBundle);
_encodeBundleTranslations(materialBundle);
_encodeBundleTranslations(cupertinoBundle);

_checkEncodedTranslations(widgetsBundle, _loadBundle(widgetsArbFile));
_checkEncodedTranslations(materialBundle, _loadBundle(materialArbFile));
_checkEncodedTranslations(cupertinoBundle, _loadBundle(cupertinoArbFile));

_rewriteBundle(widgetsArbFile, widgetsBundle);
_rewriteBundle(materialArbFile, materialBundle);
_rewriteBundle(cupertinoArbFile, cupertinoBundle);
}
78 changes: 61 additions & 17 deletions dev/tools/localization/bin/gen_localizations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import 'package:path/path.dart' as path;

import '../gen_cupertino_localizations.dart';
import '../gen_material_localizations.dart';
import '../gen_widgets_localizations.dart';
import '../localizations_utils.dart';
import '../localizations_validator.dart';
import 'encode_kn_arb_files.dart';
Expand All @@ -65,8 +66,10 @@ String generateArbBasedLocalizationSubclasses({
required String baseClass,
required HeaderGenerator generateHeader,
required ConstructorGenerator generateConstructor,
ConstructorGenerator? generateConstructorForCountrySubClass,
required String factoryName,
required String factoryDeclaration,
required bool callsFactoryWithConst,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does this affect the generated code?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The const keyword in https://github.com/flutter/flutter/blob/3173f275015cfd2d35790f1c97de3a32235f130f/packages/flutter_localizations/lib/src/l10n/generated_widgets_localizations.dart#L2693

The generated material localization can't use const here because they pass in parameter here

required String factoryArguments,
required String supportedLanguagesConstant,
required String supportedLanguagesDocMacro,
Expand All @@ -78,7 +81,7 @@ String generateArbBasedLocalizationSubclasses({
assert(factoryArguments.isNotEmpty);
assert(supportedLanguagesConstant.isNotEmpty);
assert(supportedLanguagesDocMacro.isNotEmpty);

generateConstructorForCountrySubClass ??= generateConstructor;
final StringBuffer output = StringBuffer();
output.writeln(generateHeader('dart dev/tools/localization/bin/gen_localizations.dart --overwrite'));

Expand Down Expand Up @@ -133,7 +136,6 @@ String generateArbBasedLocalizationSubclasses({
final LocaleInfo canonicalLocale = LocaleInfo.fromString('en');
for (final String languageName in languageCodes) {
final LocaleInfo languageLocale = LocaleInfo.fromString(languageName);

output.writeln(generateClassDeclaration(languageLocale, generatedClassPrefix, baseClass));
output.writeln(generateConstructor(languageLocale));

Expand All @@ -156,7 +158,7 @@ String generateArbBasedLocalizationSubclasses({
generatedClassPrefix,
'$generatedClassPrefix${languageLocale.camelCase()}',
));
output.writeln(generateConstructor(scriptBaseLocale));
output.writeln(generateConstructorForCountrySubClass(scriptBaseLocale));
final Map<String, String> scriptResources = localeToResources[scriptBaseLocale]!;
for (final String key in scriptResources.keys.toList()..sort()) {
if (languageResources[key] == scriptResources[key]) {
Expand Down Expand Up @@ -184,7 +186,7 @@ String generateArbBasedLocalizationSubclasses({
generatedClassPrefix,
'$generatedClassPrefix${scriptBaseLocale.camelCase()}',
));
output.writeln(generateConstructor(locale));
output.writeln(generateConstructorForCountrySubClass(locale));
final Map<String, String> localeResources = localeToResources[locale]!;
for (final String key in localeResources.keys) {
// When script fallback contains the key, we compare to it instead of language fallback.
Expand Down Expand Up @@ -212,7 +214,7 @@ String generateArbBasedLocalizationSubclasses({
generatedClassPrefix,
'$generatedClassPrefix${languageLocale.camelCase()}',
));
output.writeln(generateConstructor(locale));
output.writeln(generateConstructorForCountrySubClass(locale));
for (final String key in localeResources.keys) {
if (languageResources[key] == localeResources[key]) {
continue;
Expand Down Expand Up @@ -279,7 +281,7 @@ $factoryDeclaration
if (languageToLocales[language]!.length == 1) {
output.writeln('''
case '$language':
return $generatedClassPrefix${languageToLocales[language]![0].camelCase()}($factoryArguments);''');
return ${callsFactoryWithConst ? 'const ': ''}$generatedClassPrefix${languageToLocales[language]![0].camelCase()}($factoryArguments);''');
} else if (!languageToScriptCodes.containsKey(language)) { // Does not distinguish between scripts. Switch on countryCode directly.
output.writeln('''
case '$language': {
Expand All @@ -292,11 +294,11 @@ $factoryDeclaration
final String countryCode = locale.countryCode!;
output.writeln('''
case '$countryCode':
return $generatedClassPrefix${locale.camelCase()}($factoryArguments);''');
return ${callsFactoryWithConst ? 'const ': ''}$generatedClassPrefix${locale.camelCase()}($factoryArguments);''');
}
output.writeln('''
}
return $generatedClassPrefix${LocaleInfo.fromString(language).camelCase()}($factoryArguments);
return ${callsFactoryWithConst ? 'const ': ''}$generatedClassPrefix${LocaleInfo.fromString(language).camelCase()}($factoryArguments);
}''');
} else { // Language has scriptCode, add additional switch logic.
bool hasCountryCode = false;
Expand Down Expand Up @@ -325,7 +327,7 @@ $factoryDeclaration
final String countryCode = locale.countryCode!;
output.writeln('''
case '$countryCode':
return $generatedClassPrefix${locale.camelCase()}($factoryArguments);''');
return ${callsFactoryWithConst ? 'const ': ''}$generatedClassPrefix${locale.camelCase()}($factoryArguments);''');
}
}
// Return a fallback locale that matches scriptCode, but not countryCode.
Expand All @@ -337,7 +339,7 @@ $factoryDeclaration
}''');
}
output.writeln('''
return $generatedClassPrefix${scriptLocale.camelCase()}($factoryArguments);
return ${callsFactoryWithConst ? 'const ': ''}$generatedClassPrefix${scriptLocale.camelCase()}($factoryArguments);
}''');
} else {
// Not Explicitly defined, fallback to first locale with the same language and
Expand All @@ -351,7 +353,7 @@ $factoryDeclaration
}''');
}
output.writeln('''
return $generatedClassPrefix${scriptLocale.camelCase()}($factoryArguments);
return ${callsFactoryWithConst ? 'const ': ''}$generatedClassPrefix${scriptLocale.camelCase()}($factoryArguments);
}''');
break;
}
Expand All @@ -373,13 +375,13 @@ $factoryDeclaration
final String countryCode = locale.countryCode!;
output.writeln('''
case '$countryCode':
return $generatedClassPrefix${locale.camelCase()}($factoryArguments);''');
return ${callsFactoryWithConst ? 'const ': ''}$generatedClassPrefix${locale.camelCase()}($factoryArguments);''');
}
output.writeln('''
}''');
}
output.writeln('''
return $generatedClassPrefix${LocaleInfo.fromString(language).camelCase()}($factoryArguments);
return ${callsFactoryWithConst ? 'const ': ''}$generatedClassPrefix${LocaleInfo.fromString(language).camelCase()}($factoryArguments);
}''');
}
}
Expand Down Expand Up @@ -515,10 +517,12 @@ void main(List<String> rawArgs) {
// code. In most cases both codes are just two characters.

final Directory directory = Directory(path.join('packages', 'flutter_localizations', 'lib', 'src', 'l10n'));
final RegExp widgetsFilenameRE = RegExp(r'widgets_(\w+)\.arb$');
final RegExp materialFilenameRE = RegExp(r'material_(\w+)\.arb$');
final RegExp cupertinoFilenameRE = RegExp(r'cupertino_(\w+)\.arb$');

try {
validateEnglishLocalizations(File(path.join(directory.path, 'widgets_en.arb')));
validateEnglishLocalizations(File(path.join(directory.path, 'material_en.arb')));
validateEnglishLocalizations(File(path.join(directory.path, 'cupertino_en.arb')));
} on ValidationError catch (exception) {
Expand All @@ -537,17 +541,30 @@ void main(List<String> rawArgs) {

precacheLanguageAndRegionTags();

// Maps of locales to resource key/value pairs for Widgets ARBs.
final Map<LocaleInfo, Map<String, String>> widgetsLocaleToResources = <LocaleInfo, Map<String, String>>{};
// Maps of locales to resource key/attributes pairs for Widgets ARBs..
// https://github.com/googlei18n/app-resource-bundle/wiki/ApplicationResourceBundleSpecification#resource-attributes
final Map<LocaleInfo, Map<String, dynamic>> widgetsLocaleToResourceAttributes = <LocaleInfo, Map<String, dynamic>>{};

// Maps of locales to resource key/value pairs for Material ARBs.
final Map<LocaleInfo, Map<String, String>> materialLocaleToResources = <LocaleInfo, Map<String, String>>{};
// Maps of locales to resource key/attributes pairs for Material ARBs..
// https://github.com/googlei18n/app-resource-bundle/wiki/ApplicationResourceBundleSpecification#resource-attributes
final Map<LocaleInfo, Map<String, dynamic>> materialLocaleToResourceAttributes = <LocaleInfo, Map<String, dynamic>>{};

// Maps of locales to resource key/value pairs for Cupertino ARBs.
final Map<LocaleInfo, Map<String, String>> cupertinoLocaleToResources = <LocaleInfo, Map<String, String>>{};
// Maps of locales to resource key/attributes pairs for Cupertino ARBs..
// https://github.com/googlei18n/app-resource-bundle/wiki/ApplicationResourceBundleSpecification#resource-attributes
final Map<LocaleInfo, Map<String, dynamic>> cupertinoLocaleToResourceAttributes = <LocaleInfo, Map<String, dynamic>>{};

loadMatchingArbsIntoBundleMaps(
directory: directory,
filenamePattern: widgetsFilenameRE,
localeToResources: widgetsLocaleToResources,
localeToResourceAttributes: widgetsLocaleToResourceAttributes,
);
loadMatchingArbsIntoBundleMaps(
directory: directory,
filenamePattern: materialFilenameRE,
Expand All @@ -562,17 +579,35 @@ void main(List<String> rawArgs) {
);

try {
validateLocalizations(widgetsLocaleToResources, widgetsLocaleToResourceAttributes, removeUndefined: options.removeUndefined);
validateLocalizations(materialLocaleToResources, materialLocaleToResourceAttributes, removeUndefined: options.removeUndefined);
validateLocalizations(cupertinoLocaleToResources, cupertinoLocaleToResourceAttributes, removeUndefined: options.removeUndefined);
} on ValidationError catch (exception) {
exitWithError('$exception');
}

if (options.removeUndefined) {
removeUndefinedLocalizations(widgetsLocaleToResources);
removeUndefinedLocalizations(materialLocaleToResources);
removeUndefinedLocalizations(cupertinoLocaleToResources);
}

final String? widgetsLocalizations = options.writeToFile || !options.cupertinoOnly
? generateArbBasedLocalizationSubclasses(
localeToResources: widgetsLocaleToResources,
localeToResourceAttributes: widgetsLocaleToResourceAttributes,
generatedClassPrefix: 'WidgetsLocalization',
baseClass: 'GlobalWidgetsLocalizations',
generateHeader: generateWidgetsHeader,
generateConstructor: generateWidgetsConstructor,
generateConstructorForCountrySubClass: generateWidgetsConstructorForCountrySubclass,
factoryName: widgetsFactoryName,
factoryDeclaration: widgetsFactoryDeclaration,
callsFactoryWithConst: true,
factoryArguments: widgetsFactoryArguments,
supportedLanguagesConstant: widgetsSupportedLanguagesConstant,
supportedLanguagesDocMacro: widgetsSupportedLanguagesDocMacro,
)
: null;
Comment on lines +594 to +610
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are the exact differences between how widgets localizations are generated and the other material/cupertino localizations? I'm seeing that we're using a different constructor generateWidgetsConstructorForCountrySubclass but I'm not sure why we need to do this.

Copy link
Contributor Author

@chunhtai chunhtai Mar 28, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

previously generateArbBasedLocalizationSubclasses forced the generated localization class constructor to have the same function signature with the base class.

It would generate something like this

class WidgetLocalizationES extends GlobalWidgetLocalization {
  class WidgetLocalizationEn(super.somparameters);
}

class WidgetLocalizationESMX extends WidgetLocalizationES {
  class WidgetLocalizationESMX(super.somparameters);
}

This makes it a bit inconvenient. that the WidgetLocalizationES and WidgetLocalizationESMX must always take in the same parameters. generateWidgetsConstructorForCountrySubclass is used when generating the constructor of WidgetLocalizationESMX so that it can have different function signature than WidgetLocalizationES.

Of curse I can make it all take the same parameter(in this case it would be TextDirection). but have it hard code directly in the WidgetLocalizationES would be more performant.

final String? materialLocalizations = options.writeToFile || !options.cupertinoOnly
? generateArbBasedLocalizationSubclasses(
localeToResources: materialLocaleToResources,
Expand All @@ -583,6 +618,7 @@ void main(List<String> rawArgs) {
generateConstructor: generateMaterialConstructor,
factoryName: materialFactoryName,
factoryDeclaration: materialFactoryDeclaration,
callsFactoryWithConst: false,
factoryArguments: materialFactoryArguments,
supportedLanguagesConstant: materialSupportedLanguagesConstant,
supportedLanguagesDocMacro: materialSupportedLanguagesDocMacro,
Expand All @@ -598,22 +634,30 @@ void main(List<String> rawArgs) {
generateConstructor: generateCupertinoConstructor,
factoryName: cupertinoFactoryName,
factoryDeclaration: cupertinoFactoryDeclaration,
callsFactoryWithConst: false,
factoryArguments: cupertinoFactoryArguments,
supportedLanguagesConstant: cupertinoSupportedLanguagesConstant,
supportedLanguagesDocMacro: cupertinoSupportedLanguagesDocMacro,
)
: null;

if (options.writeToFile) {
final File widgetsLocalizationsFile = File(path.join(directory.path, 'generated_widgets_localizations.dart'));
widgetsLocalizationsFile.writeAsStringSync(widgetsLocalizations!, flush: true);
final File materialLocalizationsFile = File(path.join(directory.path, 'generated_material_localizations.dart'));
materialLocalizationsFile.writeAsStringSync(materialLocalizations!, flush: true);
final File cupertinoLocalizationsFile = File(path.join(directory.path, 'generated_cupertino_localizations.dart'));
cupertinoLocalizationsFile.writeAsStringSync(cupertinoLocalizations!, flush: true);
} else {
if (!options.cupertinoOnly) {
if (options.cupertinoOnly) {
stdout.write(cupertinoLocalizations);
} else if (options.materialOnly) {
stdout.write(materialLocalizations);
} else if (options.widgetsOnly) {
stdout.write(widgetsLocalizations);
} else {
stdout.write(widgetsLocalizations);
stdout.write(materialLocalizations);
}
if (!options.materialOnly) {
stdout.write(cupertinoLocalizations);
}
}
Expand Down
74 changes: 74 additions & 0 deletions dev/tools/localization/gen_widgets_localizations.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// 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 'localizations_utils.dart';

// See http://en.wikipedia.org/wiki/Right-to-left
const List<String> _rtlLanguages = <String>[
'ar', // Arabic
'fa', // Farsi
'he', // Hebrew
'ps', // Pashto
'ur', // Urdu
];

String generateWidgetsHeader(String regenerateInstructions) {
return '''
// 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.

// This file has been automatically generated. Please do not edit it manually.
// To regenerate the file, use:
// $regenerateInstructions

import 'dart:collection';
import 'dart:ui';

import '../widgets_localizations.dart';

// The classes defined here encode all of the translations found in the
// `flutter_localizations/lib/src/l10n/*.arb` files.
//
// These classes are constructed by the [getWidgetsTranslation] method at the
// bottom of this file, and used by the [_WidgetsLocalizationsDelegate.load]
// method defined in `flutter_localizations/lib/src/widgets_localizations.dart`.''';
}

/// Returns the source of the constructor for a GlobalWidgetsLocalizations
/// subclass.
String generateWidgetsConstructor(LocaleInfo locale) {
final String localeName = locale.originalString;
final String language = locale.languageCode.toLowerCase();
final String textDirection = _rtlLanguages.contains(language) ? 'TextDirection.rtl' : 'TextDirection.ltr';
return '''
/// Create an instance of the translation bundle for ${describeLocale(localeName)}.
///
/// For details on the meaning of the arguments, see [GlobalWidgetsLocalizations].
const WidgetsLocalization${locale.camelCase()}() : super($textDirection);''';
}

/// Returns the source of the constructor for a GlobalWidgetsLocalizations
/// subclass.
String generateWidgetsConstructorForCountrySubclass(LocaleInfo locale) {
final String localeName = locale.originalString;
return '''
/// Create an instance of the translation bundle for ${describeLocale(localeName)}.
///
/// For details on the meaning of the arguments, see [GlobalWidgetsLocalizations].
const WidgetsLocalization${locale.camelCase()}();''';
}

const String widgetsFactoryName = 'getWidgetsTranslation';

const String widgetsFactoryDeclaration = '''
GlobalWidgetsLocalizations? getWidgetsTranslation(
Locale locale,
) {''';

const String widgetsFactoryArguments = '';

const String widgetsSupportedLanguagesConstant = 'kWidgetsSupportedLanguages';

const String widgetsSupportedLanguagesDocMacro = 'flutter.localizations.widgets.languages';
8 changes: 8 additions & 0 deletions dev/tools/localization/localizations_utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,10 @@ GeneratorOptions parseArgs(List<String> rawArgs) {
'remove-undefined',
help: 'Remove any localizations that are not defined in the canonical locale.',
)
..addFlag(
'widgets',
help: 'Whether to print the generated classes for the Widgets package only. Ignored when --overwrite is passed.',
)
..addFlag(
'material',
help: 'Whether to print the generated classes for the Material package only. Ignored when --overwrite is passed.',
Expand All @@ -254,13 +258,15 @@ GeneratorOptions parseArgs(List<String> rawArgs) {
}
final bool writeToFile = args['overwrite'] as bool;
final bool removeUndefined = args['remove-undefined'] as bool;
final bool widgetsOnly = args['widgets'] as bool;
final bool materialOnly = args['material'] as bool;
final bool cupertinoOnly = args['cupertino'] as bool;

return GeneratorOptions(
writeToFile: writeToFile,
materialOnly: materialOnly,
cupertinoOnly: cupertinoOnly,
widgetsOnly: widgetsOnly,
removeUndefined: removeUndefined,
);
}
Expand All @@ -271,12 +277,14 @@ class GeneratorOptions {
required this.removeUndefined,
required this.materialOnly,
required this.cupertinoOnly,
required this.widgetsOnly,
});

final bool writeToFile;
final bool removeUndefined;
final bool materialOnly;
final bool cupertinoOnly;
final bool widgetsOnly;
}

// See also //master/tools/gen_locale.dart in the engine repo.
Expand Down
Loading