From 67592cedcfb19856b0c0b15d74621bf142581dcf Mon Sep 17 00:00:00 2001 From: Darren Austin Date: Thu, 23 Jan 2020 13:02:30 -0800 Subject: [PATCH 1/2] Script to generate missing localizations --- .../bin/gen_missing_localizations.dart | 104 ++++++++++++++++++ .../src/material/material_localizations.dart | 18 ++- 2 files changed, 117 insertions(+), 5 deletions(-) create mode 100644 dev/tools/localization/bin/gen_missing_localizations.dart diff --git a/dev/tools/localization/bin/gen_missing_localizations.dart b/dev/tools/localization/bin/gen_missing_localizations.dart new file mode 100644 index 0000000000000..0019f412da473 --- /dev/null +++ b/dev/tools/localization/bin/gen_missing_localizations.dart @@ -0,0 +1,104 @@ +// Copyright 2020 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 program updates the language locale arb files with any missing resource +// entries that are included in the english arb files. This is useful when +// adding new resources for localization. You can just add the appropriate +// entries to the english arb file and then run this script. It will then check +// all of the other language locale arb files and update them with new 'TBD' +// entries for any missing resources. These will be picked up by the localization +// team and then translated. +// +// ## Usage +// +// Run this program from the root of the git repository. +// +// ``` +// dart dev/tools/localization/bin/gen_missing_localizations.dart +// ``` + +import 'dart:convert'; +import 'dart:io'; + +import 'package:path/path.dart' as path; + +import '../localizations_utils.dart'; +import '../localizations_validator.dart'; + +Future main(List rawArgs) async { + checkCwdIsRepoRoot('gen_missing_localizations'); + + final String l10nPath = path.join('packages', 'flutter_localizations', 'lib', 'src', 'l10n'); + updateMissingResources(l10nPath, 'material'); + updateMissingResources(l10nPath, 'cupertino'); +} + +Map loadBundle(File file) { + if (!FileSystemEntity.isFileSync(file.path)) + exitWithError('Unable to find input file: ${file.path}'); + return json.decode(file.readAsStringSync()) as Map; +} + +void writeBundle(File file, Map bundle) { + final StringBuffer contents = StringBuffer(); + contents.writeln('{'); + for (final String key in bundle.keys) { + contents.writeln(' "$key": ${json.encode(bundle[key])}${key == bundle.keys.last ? '' : ','}'); + } + contents.writeln('}'); + file.writeAsStringSync(contents.toString()); +} + +Set resourceKeys(Map bundle) { + return Set.from( + // Skip any attribute keys + bundle.keys.where((String key) => !key.startsWith('@')) + ); +} + +bool intentionallyOmitted(String key, Map bundle) { + final String attributeKey = '@$key'; + final dynamic attribute = bundle[attributeKey]; + return attribute is Map && attribute.containsKey('notUsed'); +} + +/// Whether `key` corresponds to one of the plural variations of a key with +/// the same prefix and suffix "Other". +bool isPluralVariation(String key, Map bundle) { + final Match pluralMatch = kPluralRegexp.firstMatch(key); + if (pluralMatch == null) + return false; + final String prefix = pluralMatch[1]; + return bundle.containsKey('${prefix}Other'); +} + +void updateMissingResources(String l10nPath, String groupPrefix) { + final Directory l10nDir = Directory(l10nPath); + final RegExp filenamePattern = RegExp('${groupPrefix}_(\\w+)\.arb'); + + final Set requiredKeys = resourceKeys(loadBundle(File(path.join(l10nPath, '${groupPrefix}_en.arb')))); + + for (final FileSystemEntity entity in l10nDir.listSync().toList()..sort(sortFilesByPath)) { + final String entityPath = entity.path; + if (FileSystemEntity.isFileSync(entityPath) && filenamePattern.hasMatch(entityPath)) { + final String localeString = filenamePattern.firstMatch(entityPath)[1]; + final LocaleInfo locale = LocaleInfo.fromString(localeString); + + // Only look at top-level language locales + if (locale.length == 1) { + final File arbFile = File(entityPath); + final Map localeBundle = loadBundle(arbFile); + final Set localeResources = resourceKeys(localeBundle); + final Set missingResources = requiredKeys.difference(localeResources).where( + (String key) => !isPluralVariation(key, localeBundle) && !intentionallyOmitted(key, localeBundle) + ).toSet(); + if (missingResources.isNotEmpty) { + localeBundle.addEntries(missingResources.map((String k) => MapEntry(k, 'TBD'))); + writeBundle(arbFile, localeBundle); + print('Updated $entityPath with missing entries for $missingResources'); + } + } + } + } +} diff --git a/packages/flutter/lib/src/material/material_localizations.dart b/packages/flutter/lib/src/material/material_localizations.dart index 424a01cb2ef18..bad3d05bc1176 100644 --- a/packages/flutter/lib/src/material/material_localizations.dart +++ b/packages/flutter/lib/src/material/material_localizations.dart @@ -27,11 +27,19 @@ import 'typography.dart'; // // 4. Update the flutter_localizations package. To add a new string to the // flutter_localizations package, you must first add it to the English -// translations (lib/src/l10n/material_en.arb), including a description, then -// you must add it to every other language (all the other *.arb files in that -// same directory), listing the translation as `TBD`. After that you have to -// re-generate lib/src/l10n/localizations.dart by running -// `dart dev/tools/localization/bin/gen_localizations.dart --overwrite`. +// translations (lib/src/l10n/material_en.arb), including a description. +// +// Then you need to add new `TBD` entries for the string to all of the other +// language locale files by running: +// ``` +// dart dev/tools/localization/bin/gen_missing_localizations.dart +// ``` +// +// Finally you need to re-generate lib/src/l10n/localizations.dart by running: +// ``` +// dart dev/tools/localization/bin/gen_localizations.dart --overwrite +// ``` +// // There is a README file with further information in the lib/src/l10n/ // directory. // From c62efcef26eb57f353b035baf6dcb96235dc70e7 Mon Sep 17 00:00:00 2001 From: Darren Austin Date: Thu, 23 Jan 2020 16:35:41 -0800 Subject: [PATCH 2/2] Style changes based on code review. --- .../bin/gen_missing_localizations.dart | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/dev/tools/localization/bin/gen_missing_localizations.dart b/dev/tools/localization/bin/gen_missing_localizations.dart index 0019f412da473..900f885c5406e 100644 --- a/dev/tools/localization/bin/gen_missing_localizations.dart +++ b/dev/tools/localization/bin/gen_missing_localizations.dart @@ -1,11 +1,11 @@ -// Copyright 2020 The Flutter Authors. All rights reserved. +// 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 program updates the language locale arb files with any missing resource -// entries that are included in the english arb files. This is useful when +// entries that are included in the English arb files. This is useful when // adding new resources for localization. You can just add the appropriate -// entries to the english arb file and then run this script. It will then check +// entries to the English arb file and then run this script. It will then check // all of the other language locale arb files and update them with new 'TBD' // entries for any missing resources. These will be picked up by the localization // team and then translated. @@ -29,9 +29,9 @@ import '../localizations_validator.dart'; Future main(List rawArgs) async { checkCwdIsRepoRoot('gen_missing_localizations'); - final String l10nPath = path.join('packages', 'flutter_localizations', 'lib', 'src', 'l10n'); - updateMissingResources(l10nPath, 'material'); - updateMissingResources(l10nPath, 'cupertino'); + final String localizationPath = path.join('packages', 'flutter_localizations', 'lib', 'src', 'l10n'); + updateMissingResources(localizationPath, 'material'); + updateMissingResources(localizationPath, 'cupertino'); } Map loadBundle(File file) { @@ -73,13 +73,13 @@ bool isPluralVariation(String key, Map bundle) { return bundle.containsKey('${prefix}Other'); } -void updateMissingResources(String l10nPath, String groupPrefix) { - final Directory l10nDir = Directory(l10nPath); +void updateMissingResources(String localizationPath, String groupPrefix) { + final Directory localizationDir = Directory(localizationPath); final RegExp filenamePattern = RegExp('${groupPrefix}_(\\w+)\.arb'); - final Set requiredKeys = resourceKeys(loadBundle(File(path.join(l10nPath, '${groupPrefix}_en.arb')))); + final Set requiredKeys = resourceKeys(loadBundle(File(path.join(localizationPath, '${groupPrefix}_en.arb')))); - for (final FileSystemEntity entity in l10nDir.listSync().toList()..sort(sortFilesByPath)) { + for (final FileSystemEntity entity in localizationDir.listSync().toList()..sort(sortFilesByPath)) { final String entityPath = entity.path; if (FileSystemEntity.isFileSync(entityPath) && filenamePattern.hasMatch(entityPath)) { final String localeString = filenamePattern.firstMatch(entityPath)[1];