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:
Greg Spencer
2022-05-09 09:25:52 -07:00
committed by GitHub
parent 84ffdad368
commit 6504f2896c
86 changed files with 527 additions and 4209 deletions

View File

@@ -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,

View File

@@ -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');
}
}
}

View File

@@ -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;
}

View File

@@ -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) {