Test to ensure _kn.arb files are properly escaped (#56091)
* Run encode kn character encoding tool when gen_localizations is run * Add test that ensures *_kn.arb files are properly encoded/escaped in order to be checked in * Fix *_no.arb test to not incorrectly pass
This commit is contained in:
parent
ec93c51eaf
commit
c01c46c896
@ -2,26 +2,17 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// This program replaces the material_kn.arb and cupertino_kn.arb
|
||||
// files in flutter_localizations/packages/lib/src/l10n with versions
|
||||
// where the contents of the localized strings have been replaced by JSON
|
||||
// escapes. This is done because some of those strings contain characters
|
||||
// that can crash Emacs on Linux. There is more information
|
||||
// The utility function `encodeKnArbFiles` replaces the material_kn.arb
|
||||
// and cupertino_kn.arb files in flutter_localizations/packages/lib/src/l10n
|
||||
// with versions where the contents of the localized strings have been
|
||||
// replaced by JSON escapes. This is done because some of those strings
|
||||
// contain characters that can crash Emacs on Linux. There is more information
|
||||
// here: https://github.com/flutter/flutter/issues/36704 and in the README
|
||||
// in flutter_localizations/packages/lib/src/l10n.
|
||||
//
|
||||
// This app needs to be run by hand when material_kn.arb or cupertino_kn.arb
|
||||
// have been updated.
|
||||
//
|
||||
// ## Usage
|
||||
//
|
||||
// Run this program from the root of the git repository.
|
||||
//
|
||||
// ```
|
||||
// dart dev/tools/localization/bin/encode_kn_arb_files.dart
|
||||
// ```
|
||||
// This utility is run by `gen_localizations.dart` if --overwrite is passed
|
||||
// in as an option.
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
@ -29,13 +20,13 @@ import 'package:path/path.dart' as path;
|
||||
|
||||
import '../localizations_utils.dart';
|
||||
|
||||
Map<String, dynamic> loadBundle(File file) {
|
||||
Map<String, dynamic> _loadBundle(File file) {
|
||||
if (!FileSystemEntity.isFileSync(file.path))
|
||||
exitWithError('Unable to find input file: ${file.path}');
|
||||
return json.decode(file.readAsStringSync()) as Map<String, dynamic>;
|
||||
}
|
||||
|
||||
void encodeBundleTranslations(Map<String, dynamic> bundle) {
|
||||
void _encodeBundleTranslations(Map<String, dynamic> bundle) {
|
||||
for (final String key in bundle.keys) {
|
||||
// The ARB file resource "attributes" for foo are called @foo. Don't need
|
||||
// to encode them.
|
||||
@ -51,7 +42,7 @@ void encodeBundleTranslations(Map<String, dynamic> bundle) {
|
||||
}
|
||||
}
|
||||
|
||||
void checkEncodedTranslations(Map<String, dynamic> encodedBundle, Map<String, dynamic> bundle) {
|
||||
void _checkEncodedTranslations(Map<String, dynamic> encodedBundle, Map<String, dynamic> bundle) {
|
||||
bool errorFound = false;
|
||||
const JsonDecoder decoder = JsonDecoder();
|
||||
for (final String key in bundle.keys) {
|
||||
@ -64,7 +55,7 @@ void checkEncodedTranslations(Map<String, dynamic> encodedBundle, Map<String, dy
|
||||
exitWithError('JSON unicode translation encoding failed');
|
||||
}
|
||||
|
||||
void rewriteBundle(File file, Map<String, dynamic> bundle) {
|
||||
void _rewriteBundle(File file, Map<String, dynamic> bundle) {
|
||||
final StringBuffer contents = StringBuffer();
|
||||
contents.writeln('{');
|
||||
for (final String key in bundle.keys) {
|
||||
@ -74,22 +65,19 @@ void rewriteBundle(File file, Map<String, dynamic> bundle) {
|
||||
file.writeAsStringSync(contents.toString());
|
||||
}
|
||||
|
||||
Future<void> main(List<String> rawArgs) async {
|
||||
checkCwdIsRepoRoot('encode_kn_arb_files');
|
||||
void encodeKnArbFiles(Directory directory) {
|
||||
final File materialArbFile = File(path.join(directory.path, 'material_kn.arb'));
|
||||
final File cupertinoArbFile = File(path.join(directory.path, 'cupertino_kn.arb'));
|
||||
|
||||
final String l10nPath = path.join('packages', 'flutter_localizations', 'lib', 'src', 'l10n');
|
||||
final File materialArbFile = File(path.join(l10nPath, 'material_kn.arb'));
|
||||
final File cupertinoArbFile = File(path.join(l10nPath, 'cupertino_kn.arb'));
|
||||
final Map<String, dynamic> materialBundle = _loadBundle(materialArbFile);
|
||||
final Map<String, dynamic> cupertinoBundle = _loadBundle(cupertinoArbFile);
|
||||
|
||||
final Map<String, dynamic> materialBundle = loadBundle(materialArbFile);
|
||||
final Map<String, dynamic> cupertinoBundle = loadBundle(cupertinoArbFile);
|
||||
_encodeBundleTranslations(materialBundle);
|
||||
_encodeBundleTranslations(cupertinoBundle);
|
||||
|
||||
encodeBundleTranslations(materialBundle);
|
||||
encodeBundleTranslations(cupertinoBundle);
|
||||
_checkEncodedTranslations(materialBundle, _loadBundle(materialArbFile));
|
||||
_checkEncodedTranslations(cupertinoBundle, _loadBundle(cupertinoArbFile));
|
||||
|
||||
checkEncodedTranslations(materialBundle, loadBundle(materialArbFile));
|
||||
checkEncodedTranslations(cupertinoBundle, loadBundle(cupertinoArbFile));
|
||||
|
||||
rewriteBundle(materialArbFile, materialBundle);
|
||||
rewriteBundle(cupertinoArbFile, cupertinoBundle);
|
||||
_rewriteBundle(materialArbFile, materialBundle);
|
||||
_rewriteBundle(cupertinoArbFile, cupertinoBundle);
|
||||
}
|
||||
|
@ -50,6 +50,8 @@ import '../gen_material_localizations.dart';
|
||||
import '../localizations_utils.dart';
|
||||
import '../localizations_validator.dart';
|
||||
|
||||
import 'encode_kn_arb_files.dart';
|
||||
|
||||
/// This is the core of this script; it generates the code used for translations.
|
||||
String generateArbBasedLocalizationSubclasses({
|
||||
@required Map<LocaleInfo, Map<String, String>> localeToResources,
|
||||
@ -526,6 +528,16 @@ void main(List<String> rawArgs) {
|
||||
exitWithError('$exception');
|
||||
}
|
||||
|
||||
// Only rewrite material_kn.arb and cupertino_en.arb if overwriting the
|
||||
// Material and Cupertino localizations files.
|
||||
if (options.writeToFile) {
|
||||
// Encodes the material_kn.arb file and the cupertino_en.arb files before
|
||||
// generating localizations. This prevents a subset of Emacs users from
|
||||
// crashing when opening up the Flutter source code.
|
||||
// See https://github.com/flutter/flutter/issues/36704 for more context.
|
||||
encodeKnArbFiles(directory);
|
||||
}
|
||||
|
||||
precacheLanguageAndRegionTags();
|
||||
|
||||
// Maps of locales to resource key/value pairs for Material ARBs.
|
||||
|
@ -2,12 +2,19 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import '../test_utils.dart';
|
||||
|
||||
final String rootDirectoryPath = Directory.current.parent.path;
|
||||
|
||||
void main() {
|
||||
for (final String language in kCupertinoSupportedLanguages) {
|
||||
testWidgets('translations exist for $language', (WidgetTester tester) async {
|
||||
@ -129,8 +136,13 @@ void main() {
|
||||
|
||||
// Regression test for https://github.com/flutter/flutter/issues/53036.
|
||||
testWidgets('`nb` uses `no` as its synonym when `nb` arb file is not present', (WidgetTester tester) async {
|
||||
final File nbCupertinoArbFile = File('lib/src/l10n/cupertino_nb.arb');
|
||||
final File noCupertinoArbFile = File('lib/src/l10n/cupertino_no.arb');
|
||||
final File nbCupertinoArbFile = File(
|
||||
path.join(rootDirectoryPath, 'lib', 'src', 'l10n', 'cupertino_nb.arb'),
|
||||
);
|
||||
final File noCupertinoArbFile = File(
|
||||
path.join(rootDirectoryPath, 'lib', 'src', 'l10n', 'cupertino_no.arb'),
|
||||
);
|
||||
|
||||
|
||||
if (noCupertinoArbFile.existsSync() && !nbCupertinoArbFile.existsSync()) {
|
||||
Locale locale = const Locale.fromSubtags(languageCode: 'no', scriptCode: null, countryCode: null);
|
||||
@ -151,4 +163,31 @@ void main() {
|
||||
expect(localizations.cutButtonLabel, cutButtonLabelNo);
|
||||
}
|
||||
});
|
||||
|
||||
// Regression test for https://github.com/flutter/flutter/issues/36704.
|
||||
testWidgets('kn arb file should be properly Unicode escaped', (WidgetTester tester) async {
|
||||
final File file = File(
|
||||
path.join(rootDirectoryPath, 'lib', 'src', 'l10n', 'cupertino_kn.arb'),
|
||||
);
|
||||
|
||||
final Map<String, dynamic> bundle = json.decode(file.readAsStringSync()) as Map<String, dynamic>;
|
||||
|
||||
// Encodes the arb resource values if they have not already been
|
||||
// encoded.
|
||||
encodeBundleTranslations(bundle);
|
||||
|
||||
// Generates the encoded arb output file in as a string.
|
||||
final String encodedArbFile = generateArbString(bundle);
|
||||
|
||||
// After encoding the bundles, the generated string should match
|
||||
// the existing material_kn.arb.
|
||||
if (Platform.isWindows) {
|
||||
// On Windows, the character '\n' can output the two-character sequence
|
||||
// '\r\n' (and when reading the file back, '\r\n' is translated back
|
||||
// into a single '\n' character).
|
||||
expect(file.readAsStringSync().replaceAll('\r\n', '\n'), encodedArbFile);
|
||||
} else {
|
||||
expect(file.readAsStringSync(), encodedArbFile);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -2,12 +2,19 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import '../test_utils.dart';
|
||||
|
||||
final String rootDirectoryPath = Directory.current.parent.path;
|
||||
|
||||
void main() {
|
||||
for (final String language in kMaterialSupportedLanguages) {
|
||||
testWidgets('translations exist for $language', (WidgetTester tester) async {
|
||||
@ -467,8 +474,12 @@ void main() {
|
||||
|
||||
// Regression test for https://github.com/flutter/flutter/issues/53036.
|
||||
testWidgets('`nb` uses `no` as its synonym when `nb` arb file is not present', (WidgetTester tester) async {
|
||||
final File nbMaterialArbFile = File('lib/src/l10n/material_nb.arb');
|
||||
final File noMaterialArbFile = File('lib/src/l10n/material_no.arb');
|
||||
final File nbMaterialArbFile = File(
|
||||
path.join(rootDirectoryPath, 'lib', 'src', 'l10n', 'material_nb.arb'),
|
||||
);
|
||||
final File noMaterialArbFile = File(
|
||||
path.join(rootDirectoryPath, 'lib', 'src', 'l10n', 'material_no.arb'),
|
||||
);
|
||||
|
||||
// No need to run test if `nb` arb file exists or if `no` arb file does not exist.
|
||||
if (noMaterialArbFile.existsSync() && !nbMaterialArbFile.existsSync()) {
|
||||
@ -492,4 +503,33 @@ void main() {
|
||||
expect(localizations.okButtonLabel, okButtonLabelNo);
|
||||
}
|
||||
});
|
||||
|
||||
// Regression test for https://github.com/flutter/flutter/issues/36704.
|
||||
testWidgets('kn arb file should be properly Unicode escaped', (WidgetTester tester) async {
|
||||
final File file = File(
|
||||
path.join(rootDirectoryPath, 'lib', 'src', 'l10n', 'material_kn.arb'),
|
||||
);
|
||||
|
||||
final Map<String, dynamic> bundle = json.decode(
|
||||
file.readAsStringSync(),
|
||||
) as Map<String, dynamic>;
|
||||
|
||||
// Encodes the arb resource values if they have not already been
|
||||
// encoded.
|
||||
encodeBundleTranslations(bundle);
|
||||
|
||||
// Generates the encoded arb output file in as a string.
|
||||
final String encodedArbFile = generateArbString(bundle);
|
||||
|
||||
// After encoding the bundles, the generated string should match
|
||||
// the existing material_kn.arb.
|
||||
if (Platform.isWindows) {
|
||||
// On Windows, the character '\n' can output the two-character sequence
|
||||
// '\r\n' (and when reading the file back, '\r\n' is translated back
|
||||
// into a single '\n' character).
|
||||
expect(file.readAsStringSync().replaceAll('\r\n', '\n'), encodedArbFile);
|
||||
} else {
|
||||
expect(file.readAsStringSync(), encodedArbFile);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
30
packages/flutter_localizations/test/test_utils.dart
Normal file
30
packages/flutter_localizations/test/test_utils.dart
Normal file
@ -0,0 +1,30 @@
|
||||
// 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.
|
||||
|
||||
// Encodes ARB file resource values with Unicode escapes.
|
||||
void encodeBundleTranslations(Map<String, dynamic> bundle) {
|
||||
for (final String key in bundle.keys) {
|
||||
// The ARB file resource "attributes" for foo are called @foo. Don't need
|
||||
// to encode them.
|
||||
if (key.startsWith('@'))
|
||||
continue;
|
||||
final String translation = bundle[key] as String;
|
||||
// Rewrite the string as a series of unicode characters in JSON format.
|
||||
// Like "\u0012\u0123\u1234".
|
||||
bundle[key] = translation.runes.map((int code) {
|
||||
final String codeString = '00${code.toRadixString(16)}';
|
||||
return '\\u${codeString.substring(codeString.length - 4)}';
|
||||
}).join();
|
||||
}
|
||||
}
|
||||
|
||||
String generateArbString(Map<String, dynamic> bundle) {
|
||||
final StringBuffer contents = StringBuffer();
|
||||
contents.writeln('{');
|
||||
for (final String key in bundle.keys) {
|
||||
contents.writeln(' "$key": "${bundle[key]}"${key == bundle.keys.last ? '' : ','}');
|
||||
}
|
||||
contents.writeln('}');
|
||||
return contents.toString();
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user