| // 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:async'; | |
| import 'dart:convert'; | |
| import 'dart:io'; | |
| import 'package:path/path.dart' as path; | |
| import '../localizations_utils.dart'; | |
| const String _kCommandName = 'gen_date_localizations.dart'; | |
| const Map<String, Map<String, Object>> _dateSymbolOverrides = <String, Map<String, Object>>{ | |
| // Preserve Flutter's historical generic Arabic DateFormat behavior until | |
| // package:intl restores ZERODIGIT for ar. | |
| // TODO(QuncCccccc): Remove this override once the upstream intl data is fixed. | |
| // See https://github.com/flutter/flutter/issues/187767. | |
| 'ar': <String, Object>{'ZERODIGIT': '\u0660'}, | |
| }; | |
| // Used to let _jsonToMap know what locale it's date symbols converting for. | |
| // Date symbols for the Kannada locale ('kn') are handled specially because | |
| // some of the strings contain characters that can crash Emacs on Linux. | |
| // See packages/flutter_localizations/lib/src/l10n/README for more information. | |
| String? currentLocale; | |
| /// This program extracts localized date symbols and patterns from the intl | |
| /// package for the subset of locales supported by the flutter_localizations | |
| /// package. | |
| /// | |
| /// The extracted data is written into: | |
| /// packages/flutter_localizations/lib/src/l10n/generated_date_localizations.dart | |
| /// | |
| /// ## Usage | |
| /// | |
| /// Run this program from the root of the git repository. | |
| /// | |
| /// The following outputs the generated Dart code to the console as a dry run: | |
| /// | |
| /// ```bash | |
| /// dart dev/tools/localization/bin/gen_date_localizations.dart | |
| /// ``` | |
| /// | |
| /// If the data looks good, use the `--overwrite` option to overwrite the | |
| /// lib/src/l10n/date_localizations.dart file: | |
| /// | |
| /// ```bash | |
| /// dart dev/tools/localization/bin/gen_date_localizations.dart --overwrite | |
| /// ``` | |
| Future<void> main(List<String> rawArgs) async { | |
| checkCwdIsRepoRoot(_kCommandName); | |
| final bool writeToFile = parseArgs(rawArgs).writeToFile; | |
| final packageConfigFile = File(path.join('.dart_tool', 'package_config.json')); | |
| final bool packageConfigExists = packageConfigFile.existsSync(); | |
| if (!packageConfigExists) { | |
| exitWithError( | |
| 'File not found: ${packageConfigFile.path}. $_kCommandName must be run ' | |
| 'after a successful "flutter update-packages".', | |
| ); | |
| } | |
| final packages = | |
| (json.decode(packageConfigFile.readAsStringSync()) as Map<String, Object?>)['packages']! | |
| as List<Object?>; | |
| String? pathToIntl; | |
| for (final package in packages) { | |
| final packageAsMap = package! as Map<String, Object?>; | |
| if (packageAsMap['name'] == 'intl') { | |
| pathToIntl = Uri.parse(packageAsMap['rootUri']! as String).toFilePath(); | |
| break; | |
| } | |
| } | |
| if (pathToIntl == null) { | |
| exitWithError( | |
| 'Could not find "intl" package. $_kCommandName must be run ' | |
| 'after a successful "flutter update-packages".', | |
| ); | |
| } | |
| final dateSymbolsDirectory = Directory( | |
| path.join(pathToIntl!, 'lib', 'src', 'data', 'dates', 'symbols'), | |
| ); | |
| final Map<String, File> symbolFiles = _listIntlData(dateSymbolsDirectory); | |
| final datePatternsDirectory = Directory( | |
| path.join(pathToIntl, 'lib', 'src', 'data', 'dates', 'patterns'), | |
| ); | |
| final Map<String, File> patternFiles = _listIntlData(datePatternsDirectory); | |
| final buffer = StringBuffer(); | |
| final Set<String> supportedLocales = _supportedLocales(); | |
| buffer.writeln(''' | |
| // 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 file has been automatically generated. Please do not edit it manually. | |
| // To regenerate run (omit --overwrite to print to console instead of the file): | |
| // dart --enable-asserts dev/tools/localization/bin/gen_date_localizations.dart --overwrite | |
| import 'package:intl/date_symbols.dart' as intl; | |
| '''); | |
| buffer.writeln(''' | |
| /// The subset of date symbols supported by the intl package which are also | |
| /// supported by flutter_localizations.'''); | |
| buffer.writeln('final Map<String, intl.DateSymbols> dateSymbols = <String, intl.DateSymbols> {'); | |
| symbolFiles.forEach((String locale, File data) { | |
| currentLocale = locale; | |
| if (supportedLocales.contains(locale)) { | |
| final objData = json.decode(data.readAsStringSync()) as Map<String, Object?>; | |
| _dateSymbolOverrides[locale]?.forEach((String key, Object value) { | |
| objData.putIfAbsent(key, () => value); | |
| }); | |
| buffer.writeln("'$locale': intl.DateSymbols("); | |
| objData.forEach((String key, Object? value) { | |
| if (value == null) { | |
| return; | |
| } | |
| buffer.writeln(_jsonToConstructorEntry(key, value)); | |
| }); | |
| buffer.writeln('),'); | |
| } | |
| }); | |
| currentLocale = null; | |
| buffer.writeln('};'); | |
| // Code that uses datePatterns expects it to contain values of type | |
| // Map<String, String> not Map<String, dynamic>. | |
| buffer.writeln(''' | |
| /// The subset of date patterns supported by the intl package which are also | |
| /// supported by flutter_localizations.'''); | |
| buffer.writeln( | |
| 'const Map<String, Map<String, String>> datePatterns = <String, Map<String, String>> {', | |
| ); | |
| patternFiles.forEach((String locale, File data) { | |
| if (supportedLocales.contains(locale)) { | |
| final patterns = json.decode(data.readAsStringSync()) as Map<String, dynamic>; | |
| buffer.writeln("'$locale': <String, String>{"); | |
| patterns.forEach((String key, dynamic value) { | |
| assert(value is String); | |
| buffer.writeln(_jsonToMapEntry(key, value)); | |
| }); | |
| buffer.writeln('},'); | |
| } | |
| }); | |
| buffer.writeln('};'); | |
| if (writeToFile) { | |
| final dateLocalizationsFile = File( | |
| path.join( | |
| 'packages', | |
| 'flutter_localizations', | |
| 'lib', | |
| 'src', | |
| 'l10n', | |
| 'generated_date_localizations.dart', | |
| ), | |
| ); | |
| dateLocalizationsFile.writeAsStringSync(buffer.toString()); | |
| final extension = Platform.isWindows ? '.exe' : ''; | |
| final ProcessResult result = Process.runSync( | |
| path.join('bin', 'cache', 'dart-sdk', 'bin', 'dart$extension'), | |
| <String>['format', dateLocalizationsFile.path], | |
| ); | |
| if (result.exitCode != 0) { | |
| print(result.exitCode); | |
| print(result.stdout); | |
| print(result.stderr); | |
| } | |
| } else { | |
| print(buffer); | |
| } | |
| } | |
| String _jsonToConstructorEntry(String key, dynamic value) { | |
| return '$key: ${_jsonToObject(value)},'; | |
| } | |
| String _jsonToMapEntry(String key, dynamic value) { | |
| return "'$key': ${_jsonToMap(value)},"; | |
| } | |
| String _jsonToObject(dynamic json) { | |
| switch (json) { | |
| case null || num() || bool(): | |
| return '$json'; | |
| case String(): | |
| return generateEncodedString(currentLocale, json); | |
| case Iterable<Object?>(): | |
| final Type type = json.first.runtimeType; | |
| final buffer = StringBuffer('const <$type>['); | |
| for (final Object? value in json) { | |
| buffer.writeln('${_jsonToMap(value)},'); | |
| } | |
| buffer.write(']'); | |
| return buffer.toString(); | |
| case Map<String, dynamic>(): | |
| final buffer = StringBuffer('<String, Object>{'); | |
| json.forEach((String key, dynamic value) { | |
| buffer.writeln(_jsonToMapEntry(key, value)); | |
| }); | |
| buffer.write('}'); | |
| return buffer.toString(); | |
| } | |
| throw 'Unsupported JSON type ${json.runtimeType} of value $json.'; | |
| } | |
| String _jsonToMap(dynamic json) { | |
| switch (json) { | |
| case null || num() || bool(): | |
| return '$json'; | |
| case String(): | |
| return generateEncodedString(currentLocale, json); | |
| case Iterable<dynamic>(): | |
| final buffer = StringBuffer('<String>['); | |
| for (final Object? value in json) { | |
| buffer.writeln('${_jsonToMap(value)},'); | |
| } | |
| buffer.write(']'); | |
| return buffer.toString(); | |
| case Map<String, dynamic>(): | |
| final buffer = StringBuffer('<String, Object>{'); | |
| json.forEach((String key, dynamic value) { | |
| buffer.writeln(_jsonToMapEntry(key, value)); | |
| }); | |
| buffer.write('}'); | |
| return buffer.toString(); | |
| } | |
| throw 'Unsupported JSON type ${json.runtimeType} of value $json.'; | |
| } | |
| Set<String> _supportedLocales() { | |
| // Assumes that en_US is a supported locale by default. Without this, usage | |
| // of the intl package APIs before Flutter populates its set of supported i18n | |
| // date patterns and symbols may cause problems. | |
| // | |
| // For more context, see https://github.com/flutter/flutter/issues/67644. | |
| final supportedLocales = <String>{'en_US'}; | |
| final filenameRE = RegExp(r'(?:material|cupertino)_(\w+)\.arb$'); | |
| final supportedLocalesDirectory = Directory( | |
| path.join('packages', 'flutter_localizations', 'lib', 'src', 'l10n'), | |
| ); | |
| for (final FileSystemEntity entity in supportedLocalesDirectory.listSync()) { | |
| final String filePath = entity.path; | |
| if (FileSystemEntity.isFileSync(filePath) && filenameRE.hasMatch(filePath)) { | |
| supportedLocales.add(filenameRE.firstMatch(filePath)![1]!); | |
| } | |
| } | |
| return supportedLocales; | |
| } | |
| Map<String, File> _listIntlData(Directory directory) { | |
| final localeFiles = <String, File>{}; | |
| final Iterable<File> files = directory.listSync().whereType<File>().where( | |
| (File file) => file.path.endsWith('.json'), | |
| ); | |
| for (final file in files) { | |
| final String locale = path.basenameWithoutExtension(file.path); | |
| localeFiles[locale] = file; | |
| } | |
| final List<String> locales = localeFiles.keys.toList(growable: false); | |
| locales.sort(); | |
| return Map<String, File>.fromIterable(locales, value: (dynamic locale) => localeFiles[locale]!); | |
| } |