mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00

* Update project.pbxproj files to say Flutter rather than Chromium Also, the templates now have an empty organization so that we don't cause people to give their apps a Flutter copyright. * Update the copyright notice checker to require a standard notice on all files * Update copyrights on Dart files. (This was a mechanical commit.) * Fix weird license headers on Dart files that deviate from our conventions; relicense Shrine. Some were already marked "The Flutter Authors", not clear why. Their dates have been normalized. Some were missing the blank line after the license. Some were randomly different in trivial ways for no apparent reason (e.g. missing the trailing period). * Clean up the copyrights in non-Dart files. (Manual edits.) Also, make sure templates don't have copyrights. * Fix some more ORGANIZATIONNAMEs
166 lines
6.2 KiB
Dart
166 lines
6.2 KiB
Dart
// 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 'dart:convert' show json;
|
|
import 'dart:io';
|
|
|
|
import 'localizations_utils.dart';
|
|
|
|
// The first suffix in kPluralSuffixes must be "Other". "Other" is special
|
|
// because it's the only one that is required.
|
|
const List<String> kPluralSuffixes = <String>['Other', 'Zero', 'One', 'Two', 'Few', 'Many'];
|
|
final RegExp kPluralRegexp = RegExp(r'(\w*)(' + kPluralSuffixes.skip(1).join(r'|') + r')$');
|
|
|
|
class ValidationError implements Exception {
|
|
ValidationError(this. message);
|
|
final String message;
|
|
@override
|
|
String toString() => message;
|
|
}
|
|
|
|
/// Sanity checking of the @foo metadata in the English translations, *_en.arb.
|
|
///
|
|
/// - For each foo, resource, there must be a corresponding @foo.
|
|
/// - For each @foo resource, there must be a corresponding foo, except
|
|
/// for plurals, for which there must be a fooOther.
|
|
/// - Each @foo resource must have a Map value with a String valued
|
|
/// description entry.
|
|
///
|
|
/// Throws an exception upon failure.
|
|
void validateEnglishLocalizations(File file) {
|
|
final StringBuffer errorMessages = StringBuffer();
|
|
|
|
if (!file.existsSync()) {
|
|
errorMessages.writeln('English localizations do not exist: $file');
|
|
throw ValidationError(errorMessages.toString());
|
|
}
|
|
|
|
final Map<String, dynamic> bundle = json.decode(file.readAsStringSync());
|
|
|
|
for (String resourceId in bundle.keys) {
|
|
if (resourceId.startsWith('@'))
|
|
continue;
|
|
|
|
if (bundle['@$resourceId'] != null)
|
|
continue;
|
|
|
|
bool checkPluralResource(String suffix) {
|
|
final int suffixIndex = resourceId.indexOf(suffix);
|
|
return suffixIndex != -1 && bundle['@${resourceId.substring(0, suffixIndex)}'] != null;
|
|
}
|
|
if (kPluralSuffixes.any(checkPluralResource))
|
|
continue;
|
|
|
|
errorMessages.writeln('A value was not specified for @$resourceId');
|
|
}
|
|
|
|
for (String atResourceId in bundle.keys) {
|
|
if (!atResourceId.startsWith('@'))
|
|
continue;
|
|
|
|
final dynamic atResourceValue = bundle[atResourceId];
|
|
final Map<String, dynamic> atResource =
|
|
atResourceValue is Map<String, dynamic> ? atResourceValue : null;
|
|
if (atResource == null) {
|
|
errorMessages.writeln('A map value was not specified for $atResourceId');
|
|
continue;
|
|
}
|
|
|
|
final String description = atResource['description'];
|
|
if (description == null)
|
|
errorMessages.writeln('No description specified for $atResourceId');
|
|
|
|
final String plural = atResource['plural'];
|
|
final String resourceId = atResourceId.substring(1);
|
|
if (plural != null) {
|
|
final String resourceIdOther = '${resourceId}Other';
|
|
if (!bundle.containsKey(resourceIdOther))
|
|
errorMessages.writeln('Default plural resource $resourceIdOther undefined');
|
|
} else {
|
|
if (!bundle.containsKey(resourceId))
|
|
errorMessages.writeln('No matching $resourceId defined for $atResourceId');
|
|
}
|
|
}
|
|
|
|
if (errorMessages.isNotEmpty)
|
|
throw ValidationError(errorMessages.toString());
|
|
}
|
|
|
|
/// Enforces the following invariants in our localizations:
|
|
///
|
|
/// - Resource keys are valid, i.e. they appear in the canonical list.
|
|
/// - Resource keys are complete for language-level locales, e.g. "es", "he".
|
|
///
|
|
/// Uses "en" localizations as the canonical source of locale keys that other
|
|
/// locales are compared against.
|
|
///
|
|
/// If validation fails, throws an exception.
|
|
void validateLocalizations(
|
|
Map<LocaleInfo, Map<String, String>> localeToResources,
|
|
Map<LocaleInfo, Map<String, dynamic>> localeToAttributes,
|
|
) {
|
|
final Map<String, String> canonicalLocalizations = localeToResources[LocaleInfo.fromString('en')];
|
|
final Set<String> canonicalKeys = Set<String>.from(canonicalLocalizations.keys);
|
|
final StringBuffer errorMessages = StringBuffer();
|
|
bool explainMissingKeys = false;
|
|
for (final LocaleInfo locale in localeToResources.keys) {
|
|
final Map<String, String> resources = localeToResources[locale];
|
|
|
|
// Whether `key` corresponds to one of the plural variations of a key with
|
|
// the same prefix and suffix "Other".
|
|
//
|
|
// Many languages require only a subset of these variations, so we do not
|
|
// require them so long as the "Other" variation exists.
|
|
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))
|
|
);
|
|
|
|
// Make sure keys are valid (i.e. they also exist in the canonical
|
|
// localizations)
|
|
final Set<String> invalidKeys = keys.difference(canonicalKeys);
|
|
if (invalidKeys.isNotEmpty)
|
|
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) {
|
|
final Map<String, dynamic> attributes = localeToAttributes[locale];
|
|
final List<String> missingKeys = <String>[];
|
|
for (final String missingKey in canonicalKeys.difference(keys)) {
|
|
final dynamic attribute = attributes[missingKey];
|
|
final bool intentionallyOmitted = attribute is Map && attribute.containsKey('notUsed');
|
|
if (!intentionallyOmitted && !isPluralVariation(missingKey))
|
|
missingKeys.add(missingKey);
|
|
}
|
|
if (missingKeys.isNotEmpty) {
|
|
explainMissingKeys = true;
|
|
errorMessages.writeln('Locale "$locale" is missing the following resource keys: ${missingKeys.join(', ')}');
|
|
}
|
|
}
|
|
}
|
|
|
|
if (errorMessages.isNotEmpty) {
|
|
if (explainMissingKeys) {
|
|
errorMessages
|
|
..writeln()
|
|
..writeln(
|
|
'If a resource key is intentionally omitted, add an attribute corresponding '
|
|
'to the key name with a "notUsed" property explaining why. Example:'
|
|
)
|
|
..writeln()
|
|
..writeln('"@anteMeridiemAbbreviation": {')
|
|
..writeln(' "notUsed": "Sindhi time format does not use a.m. indicator"')
|
|
..writeln('}');
|
|
}
|
|
throw ValidationError(errorMessages.toString());
|
|
}
|
|
}
|