1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'dart:async';
6import 'dart:convert';
7import 'dart:io';
8
9import 'package:path/path.dart' as path;
10
11import '../localizations_utils.dart';
12
13const String _kCommandName = 'gen_date_localizations.dart';
14
15// Used to let _jsonToMap know what locale it's date symbols converting for.
16// Date symbols for the Kannada locale ('kn') are handled specially because
17// some of the strings contain characters that can crash Emacs on Linux.
18// See packages/flutter_localizations/lib/src/l10n/README for more information.
19String? currentLocale;
20
21/// This program extracts localized date symbols and patterns from the intl
22/// package for the subset of locales supported by the flutter_localizations
23/// package.
24///
25/// The extracted data is written into:
26/// packages/flutter_localizations/lib/src/l10n/generated_date_localizations.dart
27///
28/// ## Usage
29///
30/// Run this program from the root of the git repository.
31///
32/// The following outputs the generated Dart code to the console as a dry run:
33///
34/// ```bash
35/// dart dev/tools/localization/bin/gen_date_localizations.dart
36/// ```
37///
38/// If the data looks good, use the `--overwrite` option to overwrite the
39/// lib/src/l10n/date_localizations.dart file:
40///
41/// ```bash
42/// dart dev/tools/localization/bin/gen_date_localizations.dart --overwrite
43/// ```
44Future<void> main(List<String> rawArgs) async {
45 checkCwdIsRepoRoot(_kCommandName);
46
47 final bool writeToFile = parseArgs(rawArgs).writeToFile;
48
49 final File packageConfigFile = File(path.join('.dart_tool', 'package_config.json'));
50 final bool packageConfigExists = packageConfigFile.existsSync();
51
52 if (!packageConfigExists) {
53 exitWithError(
54 'File not found: ${packageConfigFile.path}. $_kCommandName must be run '
55 'after a successful "flutter update-packages".',
56 );
57 }
58
59 final List<Object?> packages =
60 (json.decode(packageConfigFile.readAsStringSync()) as Map<String, Object?>)['packages']!
61 as List<Object?>;
62
63 String? pathToIntl;
64 for (final Object? package in packages) {
65 final Map<String, Object?> packageAsMap = package! as Map<String, Object?>;
66 if (packageAsMap['name'] == 'intl') {
67 pathToIntl = Uri.parse(packageAsMap['rootUri']! as String).toFilePath();
68 break;
69 }
70 }
71
72 if (pathToIntl == null) {
73 exitWithError(
74 'Could not find "intl" package. $_kCommandName must be run '
75 'after a successful "flutter update-packages".',
76 );
77 }
78
79 final Directory dateSymbolsDirectory = Directory(
80 path.join(pathToIntl!, 'lib', 'src', 'data', 'dates', 'symbols'),
81 );
82 final Map<String, File> symbolFiles = _listIntlData(dateSymbolsDirectory);
83 final Directory datePatternsDirectory = Directory(
84 path.join(pathToIntl, 'lib', 'src', 'data', 'dates', 'patterns'),
85 );
86 final Map<String, File> patternFiles = _listIntlData(datePatternsDirectory);
87 final StringBuffer buffer = StringBuffer();
88 final Set<String> supportedLocales = _supportedLocales();
89
90 buffer.writeln('''
91// Copyright 2014 The Flutter Authors. All rights reserved.
92// Use of this source code is governed by a BSD-style license that can be
93// found in the LICENSE file.
94
95// This file has been automatically generated. Please do not edit it manually.
96// To regenerate run (omit --overwrite to print to console instead of the file):
97// dart --enable-asserts dev/tools/localization/bin/gen_date_localizations.dart --overwrite
98
99import 'package:intl/date_symbols.dart' as intl;
100
101''');
102 buffer.writeln('''
103/// The subset of date symbols supported by the intl package which are also
104/// supported by flutter_localizations.''');
105 buffer.writeln('final Map<String, intl.DateSymbols> dateSymbols = <String, intl.DateSymbols> {');
106 symbolFiles.forEach((String locale, File data) {
107 currentLocale = locale;
108 if (supportedLocales.contains(locale)) {
109 final Map<String, Object?> objData =
110 json.decode(data.readAsStringSync()) as Map<String, Object?>;
111 buffer.writeln("'$locale': intl.DateSymbols(");
112 objData.forEach((String key, Object? value) {
113 if (value == null) {
114 return;
115 }
116 buffer.writeln(_jsonToConstructorEntry(key, value));
117 });
118 buffer.writeln('),');
119 }
120 });
121 currentLocale = null;
122 buffer.writeln('};');
123
124 // Code that uses datePatterns expects it to contain values of type
125 // Map not Map.
126 buffer.writeln('''
127/// The subset of date patterns supported by the intl package which are also
128/// supported by flutter_localizations.''');
129 buffer.writeln(
130 'const Map<String, Map<String, String>> datePatterns = <String, Map<String, String>> {',
131 );
132 patternFiles.forEach((String locale, File data) {
133 if (supportedLocales.contains(locale)) {
134 final Map<String, dynamic> patterns =
135 json.decode(data.readAsStringSync()) as Map<String, dynamic>;
136 buffer.writeln("'$locale': <String, String>{");
137 patterns.forEach((String key, dynamic value) {
138 assert(value is String);
139 buffer.writeln(_jsonToMapEntry(key, value));
140 });
141 buffer.writeln('},');
142 }
143 });
144 buffer.writeln('};');
145
146 if (writeToFile) {
147 final File dateLocalizationsFile = File(
148 path.join(
149 'packages',
150 'flutter_localizations',
151 'lib',
152 'src',
153 'l10n',
154 'generated_date_localizations.dart',
155 ),
156 );
157 dateLocalizationsFile.writeAsStringSync(buffer.toString());
158 final String extension = Platform.isWindows ? '.exe' : '';
159 final ProcessResult result = Process.runSync(
160 path.join('bin', 'cache', 'dart-sdk', 'bin', 'dart$extension'),
161 <String>['format', dateLocalizationsFile.path],
162 );
163 if (result.exitCode != 0) {
164 print(result.exitCode);
165 print(result.stdout);
166 print(result.stderr);
167 }
168 } else {
169 print(buffer);
170 }
171}
172
173String _jsonToConstructorEntry(String key, dynamic value) {
174 return '$key: ${_jsonToObject(value)},';
175}
176
177String _jsonToMapEntry(String key, dynamic value) {
178 return "'$key': ${_jsonToMap(value)},";
179}
180
181String _jsonToObject(dynamic json) {
182 switch (json) {
183 case null || num() || bool():
184 return '$json';
185 case String():
186 return generateEncodedString(currentLocale, json);
187 case Iterable<Object?>():
188 final Type type = json.first.runtimeType;
189 final StringBuffer buffer = StringBuffer('const <$type>[');
190 for (final dynamic value in json) {
191 buffer.writeln('${_jsonToMap(value)},');
192 }
193 buffer.write(']');
194 return buffer.toString();
195 case Map<String, dynamic>():
196 final StringBuffer buffer = StringBuffer('<String, Object>{');
197 json.forEach((String key, dynamic value) {
198 buffer.writeln(_jsonToMapEntry(key, value));
199 });
200 buffer.write('}');
201 return buffer.toString();
202 }
203
204 throw 'Unsupported JSON type ${json.runtimeType} of value $json.';
205}
206
207String _jsonToMap(dynamic json) {
208 switch (json) {
209 case null || num() || bool():
210 return '$json';
211 case String():
212 return generateEncodedString(currentLocale, json);
213 case Iterable<dynamic>():
214 final StringBuffer buffer = StringBuffer('<String>[');
215 for (final dynamic value in json) {
216 buffer.writeln('${_jsonToMap(value)},');
217 }
218 buffer.write(']');
219 return buffer.toString();
220 case Map<String, dynamic>():
221 final StringBuffer buffer = StringBuffer('<String, Object>{');
222 json.forEach((String key, dynamic value) {
223 buffer.writeln(_jsonToMapEntry(key, value));
224 });
225 buffer.write('}');
226 return buffer.toString();
227 }
228
229 throw 'Unsupported JSON type ${json.runtimeType} of value $json.';
230}
231
232Set<String> _supportedLocales() {
233 // Assumes that en_US is a supported locale by default. Without this, usage
234 // of the intl package APIs before Flutter populates its set of supported i18n
235 // date patterns and symbols may cause problems.
236 //
237 // For more context, see https://github.com/flutter/flutter/issues/67644.
238 final Set<String> supportedLocales = <String>{'en_US'};
239 final RegExp filenameRE = RegExp(r'(?:material|cupertino)_(\w+)\.arb$');
240 final Directory supportedLocalesDirectory = Directory(
241 path.join('packages', 'flutter_localizations', 'lib', 'src', 'l10n'),
242 );
243 for (final FileSystemEntity entity in supportedLocalesDirectory.listSync()) {
244 final String filePath = entity.path;
245 if (FileSystemEntity.isFileSync(filePath) && filenameRE.hasMatch(filePath)) {
246 supportedLocales.add(filenameRE.firstMatch(filePath)![1]!);
247 }
248 }
249
250 return supportedLocales;
251}
252
253Map<String, File> _listIntlData(Directory directory) {
254 final Map<String, File> localeFiles = <String, File>{};
255 final Iterable<File> files = directory.listSync().whereType<File>().where(
256 (File file) => file.path.endsWith('.json'),
257 );
258 for (final File file in files) {
259 final String locale = path.basenameWithoutExtension(file.path);
260 localeFiles[locale] = file;
261 }
262
263 final List<String> locales = localeFiles.keys.toList(growable: false);
264 locales.sort();
265 return Map<String, File>.fromIterable(locales, value: (dynamic locale) => localeFiles[locale]!);
266}
267