forked from firka/flutter
Adds menuBarMenuLabel, and removes unneeded key localizations (#102100)
When I added localizations for shortcut keys, I added some that actually can't be shortcut keys, so I'm removing them again. These are mostly Japanese-specific keys that don't even appear on modern keyboards for the most part. Also, added menuBarMenuLabel for an accessibility label for menu bar menus. I modified the code for the localization generation scripts to add a --remove-undefined flag that will remove any localizations that don't appear in the canonical locale.
This commit is contained in:
@@ -32,6 +32,13 @@
|
||||
// dart dev/tools/localization/bin/gen_localizations.dart
|
||||
// ```
|
||||
//
|
||||
// If you have removed localizations from the canonical localizations, then
|
||||
// add the '--remove-undefined' flag to also remove them from the other files.
|
||||
//
|
||||
// ```
|
||||
// dart dev/tools/localization/bin/gen_localizations.dart --remove-undefined
|
||||
// ```
|
||||
//
|
||||
// If the data looks good, use the `-w` or `--overwrite` option to overwrite the
|
||||
// packages/flutter_localizations/lib/src/l10n/generated_material_localizations.dart
|
||||
// and packages/flutter_localizations/lib/src/l10n/generated_cupertino_localizations.dart file:
|
||||
@@ -542,12 +549,17 @@ void main(List<String> rawArgs) {
|
||||
);
|
||||
|
||||
try {
|
||||
validateLocalizations(materialLocaleToResources, materialLocaleToResourceAttributes);
|
||||
validateLocalizations(cupertinoLocaleToResources, cupertinoLocaleToResourceAttributes);
|
||||
validateLocalizations(materialLocaleToResources, materialLocaleToResourceAttributes, removeUndefined: options.removeUndefined);
|
||||
validateLocalizations(cupertinoLocaleToResources, cupertinoLocaleToResourceAttributes, removeUndefined: options.removeUndefined);
|
||||
} on ValidationError catch (exception) {
|
||||
exitWithError('$exception');
|
||||
}
|
||||
|
||||
if (options.removeUndefined) {
|
||||
removeUndefinedLocalizations(materialLocaleToResources);
|
||||
removeUndefinedLocalizations(cupertinoLocaleToResources);
|
||||
}
|
||||
|
||||
final String? materialLocalizations = options.writeToFile || !options.cupertinoOnly
|
||||
? generateArbBasedLocalizationSubclasses(
|
||||
localeToResources: materialLocaleToResources,
|
||||
|
||||
@@ -27,11 +27,15 @@ import '../localizations_utils.dart';
|
||||
import '../localizations_validator.dart';
|
||||
|
||||
Future<void> main(List<String> rawArgs) async {
|
||||
bool removeUndefined = false;
|
||||
if (rawArgs.contains('--remove-undefined')) {
|
||||
removeUndefined = true;
|
||||
}
|
||||
checkCwdIsRepoRoot('gen_missing_localizations');
|
||||
|
||||
final String localizationPath = path.join('packages', 'flutter_localizations', 'lib', 'src', 'l10n');
|
||||
updateMissingResources(localizationPath, 'material');
|
||||
updateMissingResources(localizationPath, 'cupertino');
|
||||
updateMissingResources(localizationPath, 'material', removeUndefined: removeUndefined);
|
||||
updateMissingResources(localizationPath, 'cupertino', removeUndefined: removeUndefined);
|
||||
}
|
||||
|
||||
Map<String, dynamic> loadBundle(File file) {
|
||||
@@ -73,7 +77,7 @@ bool isPluralVariation(String key, Map<String, dynamic> bundle) {
|
||||
return bundle.containsKey('${prefix}Other');
|
||||
}
|
||||
|
||||
void updateMissingResources(String localizationPath, String groupPrefix) {
|
||||
void updateMissingResources(String localizationPath, String groupPrefix, {bool removeUndefined = false}) {
|
||||
final Directory localizationDir = Directory(localizationPath);
|
||||
final RegExp filenamePattern = RegExp('${groupPrefix}_(\\w+)\\.arb');
|
||||
|
||||
@@ -91,14 +95,52 @@ void updateMissingResources(String localizationPath, String groupPrefix) {
|
||||
final File arbFile = File(entityPath);
|
||||
final Map<String, dynamic> localeBundle = loadBundle(arbFile);
|
||||
final Set<String> localeResources = resourceKeys(localeBundle);
|
||||
// Whether or not the resources were modified and need to be updated.
|
||||
bool shouldWrite = false;
|
||||
|
||||
// Remove any localizations that are not defined in the canonical
|
||||
// locale. This allows unused localizations to be removed if
|
||||
// --remove-undefined is passed.
|
||||
if (removeUndefined) {
|
||||
bool isIncluded(String key) {
|
||||
return !isPluralVariation(key, localeBundle)
|
||||
&& !intentionallyOmitted(key, localeBundle);
|
||||
}
|
||||
|
||||
// Find any resources in this locale that don't appear in the
|
||||
// canonical locale, and skipping any which should not be included
|
||||
// (plurals and intentionally omitted).
|
||||
final Set<String> extraResources = localeResources
|
||||
.difference(requiredKeys)
|
||||
.where(isIncluded)
|
||||
.toSet();
|
||||
|
||||
// Remove them.
|
||||
localeBundle.removeWhere((String key, dynamic value) {
|
||||
final bool found = extraResources.contains(key);
|
||||
if (found) {
|
||||
shouldWrite = true;
|
||||
}
|
||||
return found;
|
||||
});
|
||||
if (shouldWrite) {
|
||||
print('Updating $entityPath by removing extra entries for $extraResources');
|
||||
}
|
||||
}
|
||||
|
||||
// Add in any resources that are in the canonical locale and not present
|
||||
// in this locale.
|
||||
final Set<String> missingResources = requiredKeys.difference(localeResources).where(
|
||||
(String key) => !isPluralVariation(key, localeBundle) && !intentionallyOmitted(key, localeBundle)
|
||||
).toSet();
|
||||
if (missingResources.isNotEmpty) {
|
||||
localeBundle.addEntries(missingResources.map((String k) =>
|
||||
MapEntry<String, String>(k, englishBundle[k].toString())));
|
||||
shouldWrite = true;
|
||||
print('Updating $entityPath with missing entries for $missingResources');
|
||||
}
|
||||
if (shouldWrite) {
|
||||
writeBundle(arbFile, localeBundle);
|
||||
print('Updated $entityPath with missing entries for $missingResources');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -229,9 +229,19 @@ void checkCwdIsRepoRoot(String commandName) {
|
||||
|
||||
GeneratorOptions parseArgs(List<String> rawArgs) {
|
||||
final argslib.ArgParser argParser = argslib.ArgParser()
|
||||
..addFlag(
|
||||
'help',
|
||||
abbr: 'h',
|
||||
help: 'Print the usage message for this command',
|
||||
)
|
||||
..addFlag(
|
||||
'overwrite',
|
||||
abbr: 'w',
|
||||
help: 'Overwrite existing localizations',
|
||||
)
|
||||
..addFlag(
|
||||
'remove-undefined',
|
||||
help: 'Remove any localizations that are not defined in the canonical locale.',
|
||||
)
|
||||
..addFlag(
|
||||
'material',
|
||||
@@ -242,21 +252,33 @@ GeneratorOptions parseArgs(List<String> rawArgs) {
|
||||
help: 'Whether to print the generated classes for the Cupertino package only. Ignored when --overwrite is passed.',
|
||||
);
|
||||
final argslib.ArgResults args = argParser.parse(rawArgs);
|
||||
if (args.wasParsed('help') && args['help'] == true) {
|
||||
stderr.writeln(argParser.usage);
|
||||
exit(0);
|
||||
}
|
||||
final bool writeToFile = args['overwrite'] as bool;
|
||||
final bool removeUndefined = args['remove-undefined'] as bool;
|
||||
final bool materialOnly = args['material'] as bool;
|
||||
final bool cupertinoOnly = args['cupertino'] as bool;
|
||||
|
||||
return GeneratorOptions(writeToFile: writeToFile, materialOnly: materialOnly, cupertinoOnly: cupertinoOnly);
|
||||
return GeneratorOptions(
|
||||
writeToFile: writeToFile,
|
||||
materialOnly: materialOnly,
|
||||
cupertinoOnly: cupertinoOnly,
|
||||
removeUndefined: removeUndefined,
|
||||
);
|
||||
}
|
||||
|
||||
class GeneratorOptions {
|
||||
GeneratorOptions({
|
||||
required this.writeToFile,
|
||||
required this.removeUndefined,
|
||||
required this.materialOnly,
|
||||
required this.cupertinoOnly,
|
||||
});
|
||||
|
||||
final bool writeToFile;
|
||||
final bool removeUndefined;
|
||||
final bool materialOnly;
|
||||
final bool cupertinoOnly;
|
||||
}
|
||||
|
||||
@@ -88,6 +88,41 @@ void validateEnglishLocalizations(File file) {
|
||||
throw ValidationError(errorMessages.toString());
|
||||
}
|
||||
|
||||
/// This removes undefined localizations (localizations that aren't present in
|
||||
/// the canonical locale anymore) by:
|
||||
///
|
||||
/// 1. Looking up the canonical (English, in this case) localizations.
|
||||
/// 2. For each locale, getting the resources.
|
||||
/// 3. Determining the set of keys that aren't plural variations (we're only
|
||||
/// interested in the base terms being translated and not their variants)
|
||||
/// 4. Determining the set of invalid keys; that is those that are (non-plural)
|
||||
/// keys in the resources for this locale, but which _aren't_ keys in the
|
||||
/// canonical list.
|
||||
/// 5. Removes the invalid mappings from this resource's locale.
|
||||
void removeUndefinedLocalizations(
|
||||
Map<LocaleInfo, Map<String, String>> localeToResources,
|
||||
) {
|
||||
final Map<String, String> canonicalLocalizations = localeToResources[LocaleInfo.fromString('en')]!;
|
||||
final Set<String> canonicalKeys = Set<String>.from(canonicalLocalizations.keys);
|
||||
|
||||
localeToResources.forEach((LocaleInfo locale, Map<String, String> resources) {
|
||||
bool isPluralVariation(String key) {
|
||||
final Match? pluralMatch = kPluralRegexp.firstMatch(key);
|
||||
if (pluralMatch == null)
|
||||
return false;
|
||||
final String? prefix = pluralMatch[1];
|
||||
return resources.containsKey('${prefix}Other');
|
||||
}
|
||||
|
||||
final Set<String> keys = Set<String>.from(
|
||||
resources.keys.where((String key) => !isPluralVariation(key))
|
||||
);
|
||||
|
||||
final Set<String> invalidKeys = keys.difference(canonicalKeys);
|
||||
resources.removeWhere((String key, String value) => invalidKeys.contains(key));
|
||||
});
|
||||
}
|
||||
|
||||
/// Enforces the following invariants in our localizations:
|
||||
///
|
||||
/// - Resource keys are valid, i.e. they appear in the canonical list.
|
||||
@@ -99,8 +134,9 @@ void validateEnglishLocalizations(File file) {
|
||||
/// If validation fails, throws an exception.
|
||||
void validateLocalizations(
|
||||
Map<LocaleInfo, Map<String, String>> localeToResources,
|
||||
Map<LocaleInfo, Map<String, dynamic>> localeToAttributes,
|
||||
) {
|
||||
Map<LocaleInfo, Map<String, dynamic>> localeToAttributes, {
|
||||
bool removeUndefined = false,
|
||||
}) {
|
||||
final Map<String, String> canonicalLocalizations = localeToResources[LocaleInfo.fromString('en')]!;
|
||||
final Set<String> canonicalKeys = Set<String>.from(canonicalLocalizations.keys);
|
||||
final StringBuffer errorMessages = StringBuffer();
|
||||
@@ -128,8 +164,10 @@ void validateLocalizations(
|
||||
// Make sure keys are valid (i.e. they also exist in the canonical
|
||||
// localizations)
|
||||
final Set<String> invalidKeys = keys.difference(canonicalKeys);
|
||||
if (invalidKeys.isNotEmpty)
|
||||
if (invalidKeys.isNotEmpty && !removeUndefined) {
|
||||
errorMessages.writeln('Locale "$locale" contains invalid resource keys: ${invalidKeys.join(', ')}');
|
||||
}
|
||||
|
||||
// For language-level locales only, check that they have a complete list of
|
||||
// keys, or opted out of using certain ones.
|
||||
if (locale.length == 1) {
|
||||
|
||||
Reference in New Issue
Block a user