| // 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 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: |
| /// |
| /// ``` |
| /// 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: |
| /// |
| /// ``` |
| /// dart dev/tools/localization/bin/gen_date_localizations.dart --overwrite |
| /// ``` |
| |
| 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'; |
| |
| // 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; |
| |
| Future<void> main(List<String> rawArgs) async { |
| checkCwdIsRepoRoot(_kCommandName); |
| |
| final bool writeToFile = parseArgs(rawArgs).writeToFile; |
| |
| final File dotPackagesFile = File(path.join('packages', 'flutter_localizations', '.packages')); |
| final bool dotPackagesExists = dotPackagesFile.existsSync(); |
| |
| if (!dotPackagesExists) { |
| exitWithError( |
| 'File not found: ${dotPackagesFile.path}. $_kCommandName must be run ' |
| 'after a successful "flutter update-packages".' |
| ); |
| } |
| |
| final String pathToIntl = dotPackagesFile |
| .readAsStringSync() |
| .split('\n') |
| .firstWhere( |
| (String line) => line.startsWith('intl:'), |
| orElse: () { |
| exitWithError('intl dependency not found in ${dotPackagesFile.path}'); |
| return ''; // unreachable |
| }, |
| ) |
| .split(':') |
| .last; |
| |
| final Directory dateSymbolsDirectory = Directory(path.join(pathToIntl, 'src', 'data', 'dates', 'symbols')); |
| final Map<String, File> symbolFiles = _listIntlData(dateSymbolsDirectory); |
| final Directory datePatternsDirectory = Directory(path.join(pathToIntl, 'src', 'data', 'dates', 'patterns')); |
| final Map<String, File> patternFiles = _listIntlData(datePatternsDirectory); |
| final StringBuffer 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 Map<String, Object?> objData = json.decode(data.readAsStringSync()) as Map<String, Object?>; |
| 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 Map<String, dynamic> 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 File dateLocalizationsFile = File(path.join('packages', 'flutter_localizations', 'lib', 'src', 'l10n', 'generated_date_localizations.dart')); |
| dateLocalizationsFile.writeAsStringSync(buffer.toString()); |
| final String 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) { |
| if (json == null || json is num || json is bool) { |
| return '$json'; |
| } |
| |
| if (json is String) { |
| return generateEncodedString(currentLocale, json); |
| } |
| |
| if (json is Iterable<Object?>) { |
| final String type = json.first.runtimeType.toString(); |
| final StringBuffer buffer = StringBuffer('const <$type>['); |
| for (final dynamic value in json) { |
| buffer.writeln('${_jsonToMap(value)},'); |
| } |
| buffer.write(']'); |
| return buffer.toString(); |
| } |
| |
| if (json is Map<String, dynamic>) { |
| final StringBuffer 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) { |
| if (json == null || json is num || json is bool) { |
| return '$json'; |
| } |
| |
| if (json is String) { |
| return generateEncodedString(currentLocale, json); |
| } |
| |
| if (json is Iterable) { |
| final StringBuffer buffer = StringBuffer('<String>['); |
| for (final dynamic value in json) { |
| buffer.writeln('${_jsonToMap(value)},'); |
| } |
| buffer.write(']'); |
| return buffer.toString(); |
| } |
| |
| if (json is Map<String, dynamic>) { |
| final StringBuffer 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 Set<String> supportedLocales = <String>{ |
| 'en_US', |
| }; |
| final RegExp filenameRE = RegExp(r'(?:material|cupertino)_(\w+)\.arb$'); |
| final Directory 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 Map<String, File> localeFiles = <String, File>{}; |
| final Iterable<File> files = directory |
| .listSync() |
| .whereType<File>() |
| .where((File file) => file.path.endsWith('.json')); |
| for (final File 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]!); |
| } |