5
5
import 'package:intl/locale.dart' ;
6
6
7
7
import '../base/file_system.dart' ;
8
+ import '../base/logger.dart' ;
8
9
import '../convert.dart' ;
9
10
import 'localizations_utils.dart' ;
10
11
import 'message_parser.dart' ;
@@ -138,17 +139,31 @@ class L10nParserException extends L10nException {
138
139
this .messageString,
139
140
this .charNumber
140
141
): super ('''
141
- $error
142
- [$ fileName :$ messageId ] $messageString
143
- ${List <String >.filled (4 + fileName . length + messageId . length + charNumber , ' ' ).join ()}^''' );
142
+ [$ fileName :$ messageId ] $error
143
+ $messageString
144
+ ${List <String >.filled (charNumber , ' ' ).join ()}^''' );
144
145
145
146
final String error;
146
147
final String fileName;
147
148
final String messageId;
148
149
final String messageString;
150
+ // Position of character within the "messageString" where the error is.
149
151
final int charNumber;
150
152
}
151
153
154
+ class L10nMissingPlaceholderException extends L10nParserException {
155
+ L10nMissingPlaceholderException (
156
+ super .error,
157
+ super .fileName,
158
+ super .messageId,
159
+ super .messageString,
160
+ super .charNumber,
161
+ this .placeholderName,
162
+ );
163
+
164
+ final String placeholderName;
165
+ }
166
+
152
167
// One optional named parameter to be used by a NumberFormat.
153
168
//
154
169
// Some of the NumberFormat factory constructors have optional named parameters.
@@ -319,7 +334,10 @@ class Message {
319
334
AppResourceBundleCollection allBundles,
320
335
this .resourceId,
321
336
bool isResourceAttributeRequired,
322
- { this .useEscaping = false }
337
+ {
338
+ this .useEscaping = false ,
339
+ this .logger,
340
+ }
323
341
) : assert (templateBundle != null ),
324
342
assert (allBundles != null ),
325
343
assert (resourceId != null && resourceId.isNotEmpty),
@@ -335,64 +353,16 @@ class Message {
335
353
filenames[bundle.locale] = bundle.file.basename;
336
354
final String ? translation = bundle.translationFor (resourceId);
337
355
messages[bundle.locale] = translation;
338
- parsedMessages[bundle.locale] = translation == null ? null : Parser (resourceId, bundle.file.basename, translation, useEscaping: useEscaping).parse ();
339
- }
340
- // Using parsed translations, attempt to infer types of placeholders used by plurals and selects.
341
- for (final LocaleInfo locale in parsedMessages.keys) {
342
- if (parsedMessages[locale] == null ) {
343
- continue ;
344
- }
345
- final List <Node > traversalStack = < Node > [parsedMessages[locale]! ];
346
- while (traversalStack.isNotEmpty) {
347
- final Node node = traversalStack.removeLast ();
348
- if (node.type == ST .pluralExpr) {
349
- final Placeholder ? placeholder = placeholders[node.children[1 ].value! ];
350
- if (placeholder == null ) {
351
- throw L10nParserException (
352
- 'Make sure that the specified plural placeholder is defined in your arb file.' ,
353
- filenames[locale]! ,
354
- resourceId,
355
- messages[locale]! ,
356
- node.children[1 ].positionInMessage
357
- );
358
- }
359
- placeholders[node.children[1 ].value! ]! .isPlural = true ;
360
- }
361
- if (node.type == ST .selectExpr) {
362
- final Placeholder ? placeholder = placeholders[node.children[1 ].value! ];
363
- if (placeholder == null ) {
364
- throw L10nParserException (
365
- 'Make sure that the specified select placeholder is defined in your arb file.' ,
366
- filenames[locale]! ,
367
- resourceId,
368
- messages[locale]! ,
369
- node.children[1 ].positionInMessage
370
- );
371
- }
372
- placeholders[node.children[1 ].value! ]! .isSelect = true ;
373
- }
374
- traversalStack.addAll (node.children);
375
- }
376
- }
377
- for (final Placeholder placeholder in placeholders.values) {
378
- if (placeholder.isPlural && placeholder.isSelect) {
379
- throw L10nException ('Placeholder is used as both a plural and select in certain languages.' );
380
- } else if (placeholder.isPlural) {
381
- if (placeholder.type == null ) {
382
- placeholder.type = 'num' ;
383
- }
384
- else if (! < String > ['num' , 'int' ].contains (placeholder.type)) {
385
- throw L10nException ("Placeholders used in plurals must be of type 'num' or 'int'" );
386
- }
387
- } else if (placeholder.isSelect) {
388
- if (placeholder.type == null ) {
389
- placeholder.type = 'String' ;
390
- } else if (placeholder.type != 'String' ) {
391
- throw L10nException ("Placeholders used in selects must be of type 'String'" );
392
- }
393
- }
394
- placeholder.type ?? = 'Object' ;
356
+ parsedMessages[bundle.locale] = translation == null ? null : Parser (
357
+ resourceId,
358
+ bundle.file.basename,
359
+ translation,
360
+ useEscaping: useEscaping,
361
+ logger: logger
362
+ ).parse ();
395
363
}
364
+ // Infer the placeholders
365
+ _inferPlaceholders (filenames);
396
366
}
397
367
398
368
final String resourceId;
@@ -402,6 +372,7 @@ class Message {
402
372
final Map <LocaleInfo , Node ?> parsedMessages;
403
373
final Map <String , Placeholder > placeholders;
404
374
final bool useEscaping;
375
+ final Logger ? logger;
405
376
406
377
bool get placeholdersRequireFormatting => placeholders.values.any ((Placeholder p) => p.requiresFormatting);
407
378
@@ -496,6 +467,63 @@ class Message {
496
467
}),
497
468
);
498
469
}
470
+
471
+ // Using parsed translations, attempt to infer types of placeholders used by plurals and selects.
472
+ // For undeclared placeholders, create a new placeholder.
473
+ void _inferPlaceholders (Map <LocaleInfo , String > filenames) {
474
+ // We keep the undeclared placeholders separate so that we can sort them alphabetically afterwards.
475
+ final Map <String , Placeholder > undeclaredPlaceholders = < String , Placeholder > {};
476
+ // Helper for getting placeholder by name.
477
+ Placeholder ? getPlaceholder (String name) => placeholders[name] ?? undeclaredPlaceholders[name];
478
+ for (final LocaleInfo locale in parsedMessages.keys) {
479
+ if (parsedMessages[locale] == null ) {
480
+ continue ;
481
+ }
482
+ final List <Node > traversalStack = < Node > [parsedMessages[locale]! ];
483
+ while (traversalStack.isNotEmpty) {
484
+ final Node node = traversalStack.removeLast ();
485
+ if (< ST > [ST .placeholderExpr, ST .pluralExpr, ST .selectExpr].contains (node.type)) {
486
+ final String identifier = node.children[1 ].value! ;
487
+ Placeholder ? placeholder = getPlaceholder (identifier);
488
+ if (placeholder == null ) {
489
+ placeholder = Placeholder (resourceId, identifier, < String , Object ? > {});
490
+ undeclaredPlaceholders[identifier] = placeholder;
491
+ }
492
+ if (node.type == ST .pluralExpr) {
493
+ placeholder.isPlural = true ;
494
+ } else if (node.type == ST .selectExpr) {
495
+ placeholder.isSelect = true ;
496
+ }
497
+ }
498
+ traversalStack.addAll (node.children);
499
+ }
500
+ }
501
+ placeholders.addEntries (
502
+ undeclaredPlaceholders.entries
503
+ .toList ()
504
+ ..sort ((MapEntry <String , Placeholder > p1, MapEntry <String , Placeholder > p2) => p1.key.compareTo (p2.key))
505
+ );
506
+
507
+ for (final Placeholder placeholder in placeholders.values) {
508
+ if (placeholder.isPlural && placeholder.isSelect) {
509
+ throw L10nException ('Placeholder is used as both a plural and select in certain languages.' );
510
+ } else if (placeholder.isPlural) {
511
+ if (placeholder.type == null ) {
512
+ placeholder.type = 'num' ;
513
+ }
514
+ else if (! < String > ['num' , 'int' ].contains (placeholder.type)) {
515
+ throw L10nException ("Placeholders used in plurals must be of type 'num' or 'int'" );
516
+ }
517
+ } else if (placeholder.isSelect) {
518
+ if (placeholder.type == null ) {
519
+ placeholder.type = 'String' ;
520
+ } else if (placeholder.type != 'String' ) {
521
+ throw L10nException ("Placeholders used in selects must be of type 'String'" );
522
+ }
523
+ }
524
+ placeholder.type ?? = 'Object' ;
525
+ }
526
+ }
499
527
}
500
528
501
529
// Represents the contents of one ARB file.
@@ -834,50 +862,3 @@ final Set<String> _iso639Languages = <String>{
834
862
'zh' ,
835
863
'zu' ,
836
864
};
837
-
838
- // Used in LocalizationsGenerator._generateMethod.generateHelperMethod.
839
- class HelperMethod {
840
- HelperMethod (this .dependentPlaceholders, {this .helper, this .placeholder, this .string }):
841
- assert ((() {
842
- // At least one of helper, placeholder, string must be nonnull.
843
- final bool a = helper == null ;
844
- final bool b = placeholder == null ;
845
- final bool c = string == null ;
846
- return (! a && b && c) || (a && ! b && c) || (a && b && ! c);
847
- })());
848
-
849
- Set <Placeholder > dependentPlaceholders;
850
- String ? helper;
851
- Placeholder ? placeholder;
852
- String ? string;
853
-
854
- String get helperOrPlaceholder {
855
- if (helper != null ) {
856
- return '$helper ($methodArguments )' ;
857
- } else if (string != null ) {
858
- return '$string ' ;
859
- } else {
860
- if (placeholder! .requiresFormatting) {
861
- return '${placeholder !.name }String' ;
862
- } else {
863
- return placeholder! .name;
864
- }
865
- }
866
- }
867
-
868
- String get methodParameters {
869
- assert (helper != null );
870
- return dependentPlaceholders.map ((Placeholder placeholder) =>
871
- (placeholder.requiresFormatting)
872
- ? 'String ${placeholder .name }String'
873
- : '${placeholder .type } ${placeholder .name }' ).join (', ' );
874
- }
875
-
876
- String get methodArguments {
877
- assert (helper != null );
878
- return dependentPlaceholders.map ((Placeholder placeholder) =>
879
- (placeholder.requiresFormatting)
880
- ? '${placeholder .name }String'
881
- : placeholder.name).join (', ' );
882
- }
883
- }
0 commit comments