Skip to content

Commit

Permalink
Adds menuBarMenuLabel, and removes unneeded key localizations (#102…
Browse files Browse the repository at this point in the history
…100)

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.
  • Loading branch information
gspencergoog authored May 9, 2022
1 parent 84ffdad commit 6504f28
Show file tree
Hide file tree
Showing 86 changed files with 527 additions and 4,209 deletions.
2 changes: 2 additions & 0 deletions dev/bots/analyze.dart
Original file line number Diff line number Diff line change
Expand Up @@ -742,6 +742,7 @@ Future<void> verifyInternationalizations(String workingDirectory, String dartExe
<String>[
path.join('dev', 'tools', 'localization', 'bin', 'gen_localizations.dart'),
'--material',
'--remove-undefined',
],
workingDirectory: workingDirectory,
);
Expand All @@ -750,6 +751,7 @@ Future<void> verifyInternationalizations(String workingDirectory, String dartExe
<String>[
path.join('dev', 'tools', 'localization', 'bin', 'gen_localizations.dart'),
'--cupertino',
'--remove-undefined',
],
workingDirectory: workingDirectory,
);
Expand Down
16 changes: 14 additions & 2 deletions dev/tools/localization/bin/gen_localizations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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,
Expand Down
50 changes: 46 additions & 4 deletions dev/tools/localization/bin/gen_missing_localizations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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');

Expand All @@ -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');
}
}
}
Expand Down
24 changes: 23 additions & 1 deletion dev/tools/localization/localizations_utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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;
}
Expand Down
44 changes: 41 additions & 3 deletions dev/tools/localization/localizations_validator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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();
Expand Down Expand Up @@ -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) {
Expand Down
Loading

0 comments on commit 6504f28

Please sign in to comment.