diff --git a/dev/tools/test/localization/gen_l10n_test.dart b/dev/tools/test/localization/gen_l10n_test.dart index 4cddf725431..bcbfb04e2c9 100644 --- a/dev/tools/test/localization/gen_l10n_test.dart +++ b/dev/tools/test/localization/gen_l10n_test.dart @@ -50,7 +50,7 @@ void main() { ); }); - group('LocalizationsGenerator setters:', () { + group('Setters', () { test('happy path', () { _standardFlutterDirectoryL10nSetup(fs); expect(() { @@ -174,7 +174,7 @@ void main() { ); }); - group('className should only take valid Dart class names:', () { + group('className should only take valid Dart class names', () { LocalizationsGenerator generator; setUp(() { _standardFlutterDirectoryL10nSetup(fs); @@ -242,7 +242,7 @@ void main() { }); }); - group('LocalizationsGenerator.parseArbFiles:', () { + group('parseArbFiles', () { test('correctly initializes supportedLocales and supportedLanguageCodes properties', () { _standardFlutterDirectoryL10nSetup(fs); @@ -484,7 +484,7 @@ void main() { expect(generator.supportedLocales.contains(LocaleInfo.fromString('zh')), true); }); - test('correctly parses @@locale property in arb file', () { + test('correctly prioritizes @@locale property in arb file over filename', () { const String arbFileWithEnLocale = '''{ "@@locale": "en", "title": "Stocks", @@ -589,8 +589,8 @@ void main() { }); }); - group('LocalizationsGenerator.generateClassMethods:', () { - test('correctly generates a simple message with getter:', () { + group('generateClassMethods', () { + test('correctly generates a simple message with getter', () { _standardFlutterDirectoryL10nSetup(fs); final LocalizationsGenerator generator = LocalizationsGenerator(fs); try { @@ -666,8 +666,9 @@ void main() { '''); }); - test('correctly generates simple message with dates', () { - const String singleDateMessageArbFileString = '''{ + group('DateTime tests', () { + test('correctly generates simple message with dates', () { + const String singleDateMessageArbFileString = '''{ "springBegins": "Spring begins on {springStartDate}", "@springBegins": { "description": "The first day of spring", @@ -679,29 +680,29 @@ void main() { } } }'''; - final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') - ..createSync(recursive: true); - l10nDirectory.childFile(defaultTemplateArbFileName) - .writeAsStringSync(singleDateMessageArbFileString); + final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') + ..createSync(recursive: true); + l10nDirectory.childFile(defaultTemplateArbFileName) + .writeAsStringSync(singleDateMessageArbFileString); - final LocalizationsGenerator generator = LocalizationsGenerator(fs); - try { - generator.initialize( - l10nDirectoryPath: defaultArbPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - ); - generator.parseArbFiles(); - generator.generateClassMethods(); - } on Exception catch (e) { - fail('Parsing template arb file should succeed: \n$e'); - } + final LocalizationsGenerator generator = LocalizationsGenerator(fs); + try { + generator.initialize( + l10nDirectoryPath: defaultArbPathString, + templateArbFileName: defaultTemplateArbFileName, + outputFileString: defaultOutputFileString, + classNameString: defaultClassNameString, + ); + generator.parseArbFiles(); + generator.generateClassMethods(); + } on Exception catch (e) { + fail('Parsing template arb file should succeed: \n$e'); + } - expect(generator.classMethods, isNotEmpty); - expect( - generator.classMethods.first, - ''' String springBegins(Object springStartDate) { + expect(generator.classMethods, isNotEmpty); + expect( + generator.classMethods.first, + ''' String springBegins(Object springStartDate) { final DateFormat springStartDateDateFormat = DateFormat.yMMMMEEEEd(_localeName); final String springStartDateString = springStartDateDateFormat.format(springStartDate); @@ -714,10 +715,10 @@ void main() { ); } '''); - }); + }); - test('throws an exception when improperly formatted date is passed in', () { - const String singleDateMessageArbFileString = '''{ + test('throws an exception when improperly formatted date is passed in', () { + const String singleDateMessageArbFileString = '''{ "springBegins": "Spring begins on {springStartDate}", "@springBegins": { "description": "The first day of spring", @@ -729,33 +730,33 @@ void main() { } } }'''; - final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') - ..createSync(recursive: true); - l10nDirectory.childFile(defaultTemplateArbFileName) - .writeAsStringSync(singleDateMessageArbFileString); + final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') + ..createSync(recursive: true); + l10nDirectory.childFile(defaultTemplateArbFileName) + .writeAsStringSync(singleDateMessageArbFileString); - final LocalizationsGenerator generator = LocalizationsGenerator(fs); - try { - generator.initialize( - l10nDirectoryPath: defaultArbPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - ); - generator.parseArbFiles(); - generator.generateClassMethods(); - } on L10nException catch (e) { - expect(e.message, contains('asdf')); - expect(e.message, contains('springStartDate')); - expect(e.message, contains('does not have a corresponding DateFormat')); - return; - } + final LocalizationsGenerator generator = LocalizationsGenerator(fs); + try { + generator.initialize( + l10nDirectoryPath: defaultArbPathString, + templateArbFileName: defaultTemplateArbFileName, + outputFileString: defaultOutputFileString, + classNameString: defaultClassNameString, + ); + generator.parseArbFiles(); + generator.generateClassMethods(); + } on L10nException catch (e) { + expect(e.message, contains('asdf')); + expect(e.message, contains('springStartDate')); + expect(e.message, contains('does not have a corresponding DateFormat')); + return; + } - fail('Improper date formatting should throw an exception'); - }); + fail('Improper date formatting should throw an exception'); + }); - test('throws an exception when no format attribute is passed in', () { - const String singleDateMessageArbFileString = '''{ + test('throws an exception when no format attribute is passed in', () { + const String singleDateMessageArbFileString = '''{ "springBegins": "Spring begins on {springStartDate}", "@springBegins": { "description": "The first day of spring", @@ -766,31 +767,31 @@ void main() { } } }'''; - final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') - ..createSync(recursive: true); - l10nDirectory.childFile(defaultTemplateArbFileName) - .writeAsStringSync(singleDateMessageArbFileString); + final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') + ..createSync(recursive: true); + l10nDirectory.childFile(defaultTemplateArbFileName) + .writeAsStringSync(singleDateMessageArbFileString); - final LocalizationsGenerator generator = LocalizationsGenerator(fs); - try { - generator.initialize( - l10nDirectoryPath: defaultArbPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - ); - generator.parseArbFiles(); - generator.generateClassMethods(); - } on L10nException catch (e) { - expect(e.message, contains('the "format" attribute needs to be set')); - return; - } + final LocalizationsGenerator generator = LocalizationsGenerator(fs); + try { + generator.initialize( + l10nDirectoryPath: defaultArbPathString, + templateArbFileName: defaultTemplateArbFileName, + outputFileString: defaultOutputFileString, + classNameString: defaultClassNameString, + ); + generator.parseArbFiles(); + generator.generateClassMethods(); + } on L10nException catch (e) { + expect(e.message, contains('the "format" attribute needs to be set')); + return; + } - fail('Improper date formatting should throw an exception'); - }); + fail('Improper date formatting should throw an exception'); + }); - test('correctly generates simple message with date along with other placeholders', () { - const String singleDateMessageArbFileString = '''{ + test('correctly generates simple message with date along with other placeholders', () { + const String singleDateMessageArbFileString = '''{ "springGreetings": "Since it's {springStartDate}, it's finally spring! {helloWorld}!", "@springGreetings": { "description": "A realization that it's finally the spring season, followed by a greeting.", @@ -803,29 +804,29 @@ void main() { } } }'''; - final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') - ..createSync(recursive: true); - l10nDirectory.childFile(defaultTemplateArbFileName) - .writeAsStringSync(singleDateMessageArbFileString); + final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') + ..createSync(recursive: true); + l10nDirectory.childFile(defaultTemplateArbFileName) + .writeAsStringSync(singleDateMessageArbFileString); - final LocalizationsGenerator generator = LocalizationsGenerator(fs); - try { - generator.initialize( - l10nDirectoryPath: defaultArbPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - ); - generator.parseArbFiles(); - generator.generateClassMethods(); - } on Exception catch (e) { - fail('Parsing template arb file should succeed: \n$e'); - } + final LocalizationsGenerator generator = LocalizationsGenerator(fs); + try { + generator.initialize( + l10nDirectoryPath: defaultArbPathString, + templateArbFileName: defaultTemplateArbFileName, + outputFileString: defaultOutputFileString, + classNameString: defaultClassNameString, + ); + generator.parseArbFiles(); + generator.generateClassMethods(); + } on Exception catch (e) { + fail('Parsing template arb file should succeed: \n$e'); + } - expect(generator.classMethods, isNotEmpty); - expect( - generator.classMethods.first, - ''' String springGreetings(Object springStartDate, Object helloWorld) { + expect(generator.classMethods, isNotEmpty); + expect( + generator.classMethods.first, + ''' String springGreetings(Object springStartDate, Object helloWorld) { final DateFormat springStartDateDateFormat = DateFormat.yMMMMEEEEd(_localeName); final String springStartDateString = springStartDateDateFormat.format(springStartDate); @@ -838,10 +839,10 @@ void main() { ); } '''); - }); + }); - test('correctly generates simple message with multiple dates', () { - const String singleDateMessageArbFileString = '''{ + test('correctly generates simple message with multiple dates', () { + const String singleDateMessageArbFileString = '''{ "springRange": "Spring begins on {springStartDate} and ends on {springEndDate}", "@springRange": { "description": "The range of dates for spring in the year", @@ -857,29 +858,29 @@ void main() { } } }'''; - final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') - ..createSync(recursive: true); - l10nDirectory.childFile(defaultTemplateArbFileName) - .writeAsStringSync(singleDateMessageArbFileString); + final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') + ..createSync(recursive: true); + l10nDirectory.childFile(defaultTemplateArbFileName) + .writeAsStringSync(singleDateMessageArbFileString); - final LocalizationsGenerator generator = LocalizationsGenerator(fs); - try { - generator.initialize( - l10nDirectoryPath: defaultArbPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - ); - generator.parseArbFiles(); - generator.generateClassMethods(); - } on Exception catch (e) { - fail('Parsing template arb file should succeed: \n$e'); - } + final LocalizationsGenerator generator = LocalizationsGenerator(fs); + try { + generator.initialize( + l10nDirectoryPath: defaultArbPathString, + templateArbFileName: defaultTemplateArbFileName, + outputFileString: defaultOutputFileString, + classNameString: defaultClassNameString, + ); + generator.parseArbFiles(); + generator.generateClassMethods(); + } on Exception catch (e) { + fail('Parsing template arb file should succeed: \n$e'); + } - expect(generator.classMethods, isNotEmpty); - expect( - generator.classMethods.first, - ''' String springRange(Object springStartDate, Object springEndDate) { + expect(generator.classMethods, isNotEmpty); + expect( + generator.classMethods.first, + ''' String springRange(Object springStartDate, Object springEndDate) { final DateFormat springStartDateDateFormat = DateFormat.yMMMMEEEEd(_localeName); final String springStartDateString = springStartDateDateFormat.format(springStartDate); @@ -895,201 +896,11 @@ void main() { ); } '''); - }); + }); - test('correctly generates a plural message:', () { - const String singlePluralMessageArbFileString = '''{ - "helloWorlds": "{count,plural, =0{Hello}=1{Hello World}=2{Hello two worlds}few{Hello {count} worlds}many{Hello all {count} worlds}other{Hello other {count} worlds}}", - "@helloWorlds": { - "placeholders": { - "count": {} - } - } -}'''; - final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') - ..createSync(recursive: true); - l10nDirectory.childFile(defaultTemplateArbFileName) - .writeAsStringSync(singlePluralMessageArbFileString); - - final LocalizationsGenerator generator = LocalizationsGenerator(fs); - try { - generator.initialize( - l10nDirectoryPath: defaultArbPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - ); - generator.parseArbFiles(); - generator.generateClassMethods(); - } on Exception catch (e) { - fail('Parsing template arb file should succeed: \n$e'); - } - - expect(generator.classMethods, isNotEmpty); - expect( - generator.classMethods.first, - ''' String helloWorlds(int count) { - return Intl.plural( - count, - locale: _localeName, - name: 'helloWorlds', - args: [count], - zero: 'Hello', - one: 'Hello World', - two: 'Hello two worlds', - few: 'Hello \$count worlds', - many: 'Hello all \$count worlds', - other: 'Hello other \$count worlds' - ); - } -''' - ); - }); - - - test('correctly generates simple message with numbers', () { - const String singleNumberMessage = '''{ - "courseCompletion": "You have completed {progress} of the course.", - "@courseCompletion": { - "description": "The amount of progress the student has made in their class.", - "placeholders": { - "progress": { - "type": "Number", - "format": "percentPattern" - } - } - } -}'''; - final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') - ..createSync(recursive: true); - l10nDirectory.childFile(defaultTemplateArbFileName) - .writeAsStringSync(singleNumberMessage); - - final LocalizationsGenerator generator = LocalizationsGenerator(fs); - try { - generator.initialize( - l10nDirectoryPath: defaultArbPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - ); - generator.parseArbFiles(); - generator.generateClassMethods(); - } on Exception catch (e) { - fail('Parsing template arb file should succeed: \n$e'); - } - - expect(generator.classMethods, isNotEmpty); - expect( - generator.classMethods.first, - ''' String courseCompletion(Object progress) { - final NumberFormat progressNumberFormat = NumberFormat.percentPattern( - locale: _localeName, - ); - final String progressString = progressNumberFormat.format(progress); - - return Intl.message( - r'You have completed \$progressString of the course.', - locale: _localeName, - name: 'courseCompletion', - desc: r'The amount of progress the student has made in their class.', - args: [progressString] - ); - } -'''); - }); - - test('throws an exception when improperly formatted number is passed in', () { - const String singleDateMessageArbFileString = '''{ - "courseCompletion": "You have completed {progress} of the course.", - "@courseCompletion": { - "description": "The amount of progress the student has made in their class.", - "placeholders": { - "progress": { - "type": "Number", - "format": "asdf" - } - } - } -}'''; - final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') - ..createSync(recursive: true); - l10nDirectory.childFile(defaultTemplateArbFileName) - .writeAsStringSync(singleDateMessageArbFileString); - - final LocalizationsGenerator generator = LocalizationsGenerator(fs); - try { - generator.initialize( - l10nDirectoryPath: defaultArbPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - ); - generator.parseArbFiles(); - generator.generateClassMethods(); - } on L10nException catch (e) { - expect(e.message, contains('asdf')); - expect(e.message, contains('progress')); - expect(e.message, contains('does not have a corresponding NumberFormat')); - return; - } - - fail('Improper date formatting should throw an exception'); - }); - - test('correctly generates a plural message with placeholders:', () { - const String pluralMessageWithMultiplePlaceholders = '''{ - "helloWorlds": "{count,plural, =0{Hello}=1{Hello {adjective} World}=2{Hello two {adjective} worlds}few{Hello {count} {adjective} worlds}many{Hello all {count} {adjective} worlds}other{Hello other {count} {adjective} worlds}}", - "@helloWorlds": { - "placeholders": { - "count": {}, - "adjective": {} - } - } -}'''; - final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') - ..createSync(recursive: true); - l10nDirectory.childFile(defaultTemplateArbFileName) - .writeAsStringSync(pluralMessageWithMultiplePlaceholders); - - final LocalizationsGenerator generator = LocalizationsGenerator(fs); - try { - generator.initialize( - l10nDirectoryPath: defaultArbPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - ); - generator.parseArbFiles(); - generator.generateClassMethods(); - } on Exception catch (e) { - fail('Parsing template arb file should succeed: \n$e'); - } - - expect(generator.classMethods, isNotEmpty); - expect( - generator.classMethods.first, - ''' String helloWorlds(int count, Object adjective) { - return Intl.plural( - count, - locale: _localeName, - name: 'helloWorlds', - args: [count, adjective], - zero: 'Hello', - one: 'Hello \$adjective World', - two: 'Hello two \$adjective worlds', - few: 'Hello \$count \$adjective worlds', - many: 'Hello all \$count \$adjective worlds', - other: 'Hello other \$count \$adjective worlds' - ); - } -''' - ); - }); - - test('correctly generates a plural message with DateTime placeholders:', () { - const String pluralMessageWithDateTimePlaceholder = '''{ - "helloWorlds": "{count,plural, =1{Hello World, today is {currentDate}}=2{Hello two worlds, today is {currentDate}}many{Hello all {count} worlds, today is {currentDate}}other{Hello other {count} worlds, today is {currentDate}}}", + test('correctly generates a plural message with DateTime placeholders', () { + const String pluralMessageWithDateTimePlaceholder = '''{ + "helloWorlds": "{count,plural, =1{Hello World, today is {currentDate}}=2{Hello two worlds, today is {currentDate}}many{Hello all {count} worlds, today is {currentDate}}other{Hello other {count} worlds, today is {currentDate}}}", "@helloWorlds": { "placeholders": { "count": {}, @@ -1100,29 +911,29 @@ void main() { } } }'''; - final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') - ..createSync(recursive: true); - l10nDirectory.childFile(defaultTemplateArbFileName) - .writeAsStringSync(pluralMessageWithDateTimePlaceholder); + final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') + ..createSync(recursive: true); + l10nDirectory.childFile(defaultTemplateArbFileName) + .writeAsStringSync(pluralMessageWithDateTimePlaceholder); - final LocalizationsGenerator generator = LocalizationsGenerator(fs); - try { - generator.initialize( - l10nDirectoryPath: defaultArbPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - ); - generator.parseArbFiles(); - generator.generateClassMethods(); - } on Exception catch (e) { - fail('Parsing template arb file should succeed: \n$e'); - } + final LocalizationsGenerator generator = LocalizationsGenerator(fs); + try { + generator.initialize( + l10nDirectoryPath: defaultArbPathString, + templateArbFileName: defaultTemplateArbFileName, + outputFileString: defaultOutputFileString, + classNameString: defaultClassNameString, + ); + generator.parseArbFiles(); + generator.generateClassMethods(); + } on Exception catch (e) { + fail('Parsing template arb file should succeed: \n$e'); + } - expect(generator.classMethods, isNotEmpty); - expect( - generator.classMethods.first, - ''' String helloWorlds(int count, Object currentDate) { + expect(generator.classMethods, isNotEmpty); + expect( + generator.classMethods.first, + ''' String helloWorlds(int count, Object currentDate) { final DateFormat currentDateDateFormat = DateFormat.yMMMMEEEEd(_localeName); final String currentDateString = currentDateDateFormat.format(currentDate); @@ -1138,11 +949,314 @@ void main() { ); } ''' - ); + ); + }); }); - test('correctly generates a plural message with number placeholders:', () { - const String pluralMessageWithDateTimePlaceholder = '''{ + group('Number tests', () { + test('correctly generates simple message with numbers', () { + const String singleNumberMessage = '''{ + "courseCompletion": "You have completed {progress} of the course.", + "@courseCompletion": { + "description": "The amount of progress the student has made in their class.", + "placeholders": { + "progress": { + "type": "Number", + "format": "percentPattern" + } + } + } +}'''; + final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') + ..createSync(recursive: true); + l10nDirectory.childFile(defaultTemplateArbFileName) + .writeAsStringSync(singleNumberMessage); + + final LocalizationsGenerator generator = LocalizationsGenerator(fs); + try { + generator.initialize( + l10nDirectoryPath: defaultArbPathString, + templateArbFileName: defaultTemplateArbFileName, + outputFileString: defaultOutputFileString, + classNameString: defaultClassNameString, + ); + generator.parseArbFiles(); + generator.generateClassMethods(); + } on Exception catch (e) { + fail('Parsing template arb file should succeed: \n$e'); + } + + expect(generator.classMethods, isNotEmpty); + expect( + generator.classMethods.first, + ''' String courseCompletion(Object progress) { + final NumberFormat progressNumberFormat = NumberFormat.percentPattern( + locale: _localeName, + ); + final String progressString = progressNumberFormat.format(progress); + + return Intl.message( + r'You have completed \$progressString of the course.', + locale: _localeName, + name: 'courseCompletion', + desc: r'The amount of progress the student has made in their class.', + args: [progressString] + ); + } +'''); + }); + + test('correctly adds optional parameters to numbers', () { + const String singleNumberMessage = '''{ + "courseCompletion": "You have completed {progress} of the course.", + "@courseCompletion": { + "description": "The amount of progress the student has made in their class.", + "placeholders": { + "progress": { + "type": "Number", + "format": "decimalPercentPattern", + "optionalParameters": { + "decimalDigits": 2 + } + } + } + } +}'''; + final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') + ..createSync(recursive: true); + l10nDirectory.childFile(defaultTemplateArbFileName) + .writeAsStringSync(singleNumberMessage); + + final LocalizationsGenerator generator = LocalizationsGenerator(fs); + try { + generator.initialize( + l10nDirectoryPath: defaultArbPathString, + templateArbFileName: defaultTemplateArbFileName, + outputFileString: defaultOutputFileString, + classNameString: defaultClassNameString, + ); + generator.parseArbFiles(); + generator.generateClassMethods(); + } on Exception catch (e) { + fail('Parsing template arb file should succeed: \n$e'); + } + + expect(generator.classMethods, isNotEmpty); + expect( + generator.classMethods.first, + ''' String courseCompletion(Object progress) { + final NumberFormat progressNumberFormat = NumberFormat.decimalPercentPattern( + locale: _localeName, + decimalDigits: 2, + ); + final String progressString = progressNumberFormat.format(progress); + + return Intl.message( + r'You have completed \$progressString of the course.', + locale: _localeName, + name: 'courseCompletion', + desc: r'The amount of progress the student has made in their class.', + args: [progressString] + ); + } +'''); + }); + + test('throws an exception when improperly formatted number is passed in', () { + const String singleDateMessageArbFileString = '''{ + "courseCompletion": "You have completed {progress} of the course.", + "@courseCompletion": { + "description": "The amount of progress the student has made in their class.", + "placeholders": { + "progress": { + "type": "Number", + "format": "asdf" + } + } + } +}'''; + final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') + ..createSync(recursive: true); + l10nDirectory.childFile(defaultTemplateArbFileName) + .writeAsStringSync(singleDateMessageArbFileString); + + final LocalizationsGenerator generator = LocalizationsGenerator(fs); + try { + generator.initialize( + l10nDirectoryPath: defaultArbPathString, + templateArbFileName: defaultTemplateArbFileName, + outputFileString: defaultOutputFileString, + classNameString: defaultClassNameString, + ); + generator.parseArbFiles(); + generator.generateClassMethods(); + } on L10nException catch (e) { + expect(e.message, contains('asdf')); + expect(e.message, contains('progress')); + expect(e.message, contains('does not have a corresponding NumberFormat')); + return; + } + + fail('Improper date formatting should throw an exception'); + }); + }); + + group('plural messages', () { + test('correctly generates a plural message', () { + const String singlePluralMessageArbFileString = '''{ + "helloWorlds": "{count,plural, =0{Hello}=1{Hello World}=2{Hello two worlds}few{Hello {count} worlds}many{Hello all {count} worlds}other{Hello other {count} worlds}}", + "@helloWorlds": { + "placeholders": { + "count": {} + } + } +}'''; + final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') + ..createSync(recursive: true); + l10nDirectory.childFile(defaultTemplateArbFileName) + .writeAsStringSync(singlePluralMessageArbFileString); + + final LocalizationsGenerator generator = LocalizationsGenerator(fs); + try { + generator.initialize( + l10nDirectoryPath: defaultArbPathString, + templateArbFileName: defaultTemplateArbFileName, + outputFileString: defaultOutputFileString, + classNameString: defaultClassNameString, + ); + generator.parseArbFiles(); + generator.generateClassMethods(); + } on Exception catch (e) { + fail('Parsing template arb file should succeed: \n$e'); + } + + expect(generator.classMethods, isNotEmpty); + expect( + generator.classMethods.first, + ''' String helloWorlds(int count) { + return Intl.plural( + count, + locale: _localeName, + name: 'helloWorlds', + args: [count], + zero: 'Hello', + one: 'Hello World', + two: 'Hello two worlds', + few: 'Hello \$count worlds', + many: 'Hello all \$count worlds', + other: 'Hello other \$count worlds' + ); + } +''' + ); + }); + + test('correctly generates a plural message with placeholders', () { + const String pluralMessageWithMultiplePlaceholders = '''{ + "helloWorlds": "{count,plural, =0{Hello}=1{Hello {adjective} World}=2{Hello two {adjective} worlds}few{Hello {count} {adjective} worlds}many{Hello all {count} {adjective} worlds}other{Hello other {count} {adjective} worlds}}", + "@helloWorlds": { + "placeholders": { + "count": {}, + "adjective": {} + } + } +}'''; + final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') + ..createSync(recursive: true); + l10nDirectory.childFile(defaultTemplateArbFileName) + .writeAsStringSync(pluralMessageWithMultiplePlaceholders); + + final LocalizationsGenerator generator = LocalizationsGenerator(fs); + try { + generator.initialize( + l10nDirectoryPath: defaultArbPathString, + templateArbFileName: defaultTemplateArbFileName, + outputFileString: defaultOutputFileString, + classNameString: defaultClassNameString, + ); + generator.parseArbFiles(); + generator.generateClassMethods(); + } on Exception catch (e) { + fail('Parsing template arb file should succeed: \n$e'); + } + + expect(generator.classMethods, isNotEmpty); + expect( + generator.classMethods.first, + ''' String helloWorlds(int count, Object adjective) { + return Intl.plural( + count, + locale: _localeName, + name: 'helloWorlds', + args: [count, adjective], + zero: 'Hello', + one: 'Hello \$adjective World', + two: 'Hello two \$adjective worlds', + few: 'Hello \$count \$adjective worlds', + many: 'Hello all \$count \$adjective worlds', + other: 'Hello other \$count \$adjective worlds' + ); + } +''' + ); + }); + + test('correctly generates a plural message with DateTime placeholders', () { + const String pluralMessageWithDateTimePlaceholder = '''{ + "helloWorlds": "{count,plural, =1{Hello World, today is {currentDate}}=2{Hello two worlds, today is {currentDate}}many{Hello all {count} worlds, today is {currentDate}}other{Hello other {count} worlds, today is {currentDate}}}", + "@helloWorlds": { + "placeholders": { + "count": {}, + "currentDate": { + "type": "DateTime", + "format": "yMMMMEEEEd" + } + } + } +}'''; + final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') + ..createSync(recursive: true); + l10nDirectory.childFile(defaultTemplateArbFileName) + .writeAsStringSync(pluralMessageWithDateTimePlaceholder); + + final LocalizationsGenerator generator = LocalizationsGenerator(fs); + try { + generator.initialize( + l10nDirectoryPath: defaultArbPathString, + templateArbFileName: defaultTemplateArbFileName, + outputFileString: defaultOutputFileString, + classNameString: defaultClassNameString, + ); + generator.parseArbFiles(); + generator.generateClassMethods(); + } on Exception catch (e) { + fail('Parsing template arb file should succeed: \n$e'); + } + + expect(generator.classMethods, isNotEmpty); + expect( + generator.classMethods.first, + ''' String helloWorlds(int count, Object currentDate) { + final DateFormat currentDateDateFormat = DateFormat.yMMMMEEEEd(_localeName); + final String currentDateString = currentDateDateFormat.format(currentDate); + + return Intl.plural( + count, + locale: _localeName, + name: 'helloWorlds', + args: [count, currentDateString], + one: 'Hello World, today is \$currentDateString', + two: 'Hello two worlds, today is \$currentDateString', + many: 'Hello all \$count worlds, today is \$currentDateString', + other: 'Hello other \$count worlds, today is \$currentDateString' + ); + } +''' + ); + }); + + test('correctly generates a plural message with number placeholders', () { + const String pluralMessageWithDateTimePlaceholder = '''{ "helloWorlds": "{count,plural, =1{Hello World of {population} citizens}=2{Hello two worlds with {population} total citizens}many{Hello all {count} worlds, with a total of {population} citizens}other{Hello other {count} worlds, with a total of {population} citizens}}", "@helloWorlds": { "placeholders": { @@ -1154,29 +1268,29 @@ void main() { } } }'''; - final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') - ..createSync(recursive: true); - l10nDirectory.childFile(defaultTemplateArbFileName) - .writeAsStringSync(pluralMessageWithDateTimePlaceholder); + final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') + ..createSync(recursive: true); + l10nDirectory.childFile(defaultTemplateArbFileName) + .writeAsStringSync(pluralMessageWithDateTimePlaceholder); - final LocalizationsGenerator generator = LocalizationsGenerator(fs); - try { - generator.initialize( - l10nDirectoryPath: defaultArbPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - ); - generator.parseArbFiles(); - generator.generateClassMethods(); - } on Exception catch (e) { - fail('Parsing template arb file should succeed: \n$e'); - } + final LocalizationsGenerator generator = LocalizationsGenerator(fs); + try { + generator.initialize( + l10nDirectoryPath: defaultArbPathString, + templateArbFileName: defaultTemplateArbFileName, + outputFileString: defaultOutputFileString, + classNameString: defaultClassNameString, + ); + generator.parseArbFiles(); + generator.generateClassMethods(); + } on Exception catch (e) { + fail('Parsing template arb file should succeed: \n$e'); + } - expect(generator.classMethods, isNotEmpty); - expect( - generator.classMethods.first, - ''' String helloWorlds(int count, Object population) { + expect(generator.classMethods, isNotEmpty); + expect( + generator.classMethods.first, + ''' String helloWorlds(int count, Object population) { final NumberFormat populationNumberFormat = NumberFormat.compactLong( locale: _localeName, ); @@ -1194,66 +1308,10 @@ void main() { ); } ''' - ); - }); - - test('correctly adds optional parameters to numbers:', () { - const String singleNumberMessage = '''{ - "courseCompletion": "You have completed {progress} of the course.", - "@courseCompletion": { - "description": "The amount of progress the student has made in their class.", - "placeholders": { - "progress": { - "type": "Number", - "format": "decimalPercentPattern", - "optionalParameters": { - "decimalDigits": 2 - } - } - } - } -}'''; - final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') - ..createSync(recursive: true); - l10nDirectory.childFile(defaultTemplateArbFileName) - .writeAsStringSync(singleNumberMessage); - - final LocalizationsGenerator generator = LocalizationsGenerator(fs); - try { - generator.initialize( - l10nDirectoryPath: defaultArbPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, ); - generator.parseArbFiles(); - generator.generateClassMethods(); - } on Exception catch (e) { - fail('Parsing template arb file should succeed: \n$e'); - } + }); - expect(generator.classMethods, isNotEmpty); - expect( - generator.classMethods.first, - ''' String courseCompletion(Object progress) { - final NumberFormat progressNumberFormat = NumberFormat.decimalPercentPattern( - locale: _localeName, - decimalDigits: 2, - ); - final String progressString = progressNumberFormat.format(progress); - - return Intl.message( - r'You have completed \$progressString of the course.', - locale: _localeName, - name: 'courseCompletion', - desc: r'The amount of progress the student has made in their class.', - args: [progressString] - ); - } -'''); - }); - - test('should throw attempting to generate a plural message without placeholders:', () { + test('should throw attempting to generate a plural message without placeholders', () { const String pluralMessageWithoutPlaceholdersAttribute = '''{ "helloWorlds": "{count,plural, =0{Hello}=1{Hello World}=2{Hello two worlds}few{Hello {count} worlds}many{Hello all {count} worlds}other{Hello other {count} worlds}}", "@helloWorlds": { @@ -1261,30 +1319,30 @@ void main() { } }'''; - final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') - ..createSync(recursive: true); - l10nDirectory.childFile(defaultTemplateArbFileName) - .writeAsStringSync(pluralMessageWithoutPlaceholdersAttribute); + final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') + ..createSync(recursive: true); + l10nDirectory.childFile(defaultTemplateArbFileName) + .writeAsStringSync(pluralMessageWithoutPlaceholdersAttribute); - final LocalizationsGenerator generator = LocalizationsGenerator(fs); - try { - generator.initialize( - l10nDirectoryPath: defaultArbPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - ); - generator.parseArbFiles(); - generator.generateClassMethods(); - } on L10nException catch (e) { - expect(e.message, contains('Check to see if the plural message is in the proper ICU syntax format')); - return; - } - fail('Generating class methods without placeholders should not succeed'); - }); + final LocalizationsGenerator generator = LocalizationsGenerator(fs); + try { + generator.initialize( + l10nDirectoryPath: defaultArbPathString, + templateArbFileName: defaultTemplateArbFileName, + outputFileString: defaultOutputFileString, + classNameString: defaultClassNameString, + ); + generator.parseArbFiles(); + generator.generateClassMethods(); + } on L10nException catch (e) { + expect(e.message, contains('Check to see if the plural message is in the proper ICU syntax format')); + return; + } + fail('Generating class methods without placeholders should not succeed'); + }); - test('should throw attempting to generate a plural message with empty placeholders map:', () { - const String pluralMessageWithEmptyPlaceholdersMap = '''{ + test('should throw attempting to generate a plural message with an empty placeholders map', () { + const String pluralMessageWithEmptyPlaceholdersMap = '''{ "helloWorlds": "{count,plural, =0{Hello}=1{Hello World}=2{Hello two worlds}few{Hello {count} worlds}many{Hello all {count} worlds}other{Hello other {count} worlds}}", "@helloWorlds": { "description": "Improperly formatted since it has no placeholder attribute.", @@ -1292,88 +1350,89 @@ void main() { } }'''; - final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') - ..createSync(recursive: true); - l10nDirectory.childFile(defaultTemplateArbFileName) - .writeAsStringSync(pluralMessageWithEmptyPlaceholdersMap); + final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') + ..createSync(recursive: true); + l10nDirectory.childFile(defaultTemplateArbFileName) + .writeAsStringSync(pluralMessageWithEmptyPlaceholdersMap); - final LocalizationsGenerator generator = LocalizationsGenerator(fs); - try { - generator.initialize( - l10nDirectoryPath: defaultArbPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - ); - generator.parseArbFiles(); - generator.generateClassMethods(); - } on L10nException catch (e) { - expect(e.message, contains('Check to see if the plural message is in the proper ICU syntax format')); - return; - } - fail('Generating class methods without placeholders should not succeed'); - }); + final LocalizationsGenerator generator = LocalizationsGenerator(fs); + try { + generator.initialize( + l10nDirectoryPath: defaultArbPathString, + templateArbFileName: defaultTemplateArbFileName, + outputFileString: defaultOutputFileString, + classNameString: defaultClassNameString, + ); + generator.parseArbFiles(); + generator.generateClassMethods(); + } on L10nException catch (e) { + expect(e.message, contains('Check to see if the plural message is in the proper ICU syntax format')); + return; + } + fail('Generating class methods without placeholders should not succeed'); + }); - test('should throw attempting to generate a plural message with no resource attributes:', () { - const String pluralMessageWithoutResourceAttributes = '''{ + test('should throw attempting to generate a plural message with no resource attributes', () { + const String pluralMessageWithoutResourceAttributes = '''{ "helloWorlds": "{count,plural, =0{Hello}=1{Hello World}=2{Hello two worlds}few{Hello {count} worlds}many{Hello all {count} worlds}other{Hello other {count} worlds}}" }'''; - final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') - ..createSync(recursive: true); - l10nDirectory.childFile(defaultTemplateArbFileName) - .writeAsStringSync(pluralMessageWithoutResourceAttributes); + final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') + ..createSync(recursive: true); + l10nDirectory.childFile(defaultTemplateArbFileName) + .writeAsStringSync(pluralMessageWithoutResourceAttributes); - final LocalizationsGenerator generator = LocalizationsGenerator(fs); - try { - generator.initialize( - l10nDirectoryPath: defaultArbPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - ); - generator.parseArbFiles(); - generator.generateClassMethods(); - } on L10nException catch (e) { - expect(e.message, contains('Resource attribute')); - expect(e.message, contains('does not exist')); - return; - } - fail('Generating plural class method without resource attributes should not succeed'); - }); + final LocalizationsGenerator generator = LocalizationsGenerator(fs); + try { + generator.initialize( + l10nDirectoryPath: defaultArbPathString, + templateArbFileName: defaultTemplateArbFileName, + outputFileString: defaultOutputFileString, + classNameString: defaultClassNameString, + ); + generator.parseArbFiles(); + generator.generateClassMethods(); + } on L10nException catch (e) { + expect(e.message, contains('Resource attribute')); + expect(e.message, contains('does not exist')); + return; + } + fail('Generating plural class method without resource attributes should not succeed'); + }); - test('should throw attempting to generate a plural message with incorrect placeholders format:', () { - const String pluralMessageWithIncorrectPlaceholderFormat = '''{ + test('should throw attempting to generate a plural message with incorrect format for placeholders', () { + const String pluralMessageWithIncorrectPlaceholderFormat = '''{ "helloWorlds": "{count,plural, =0{Hello}=1{Hello World}=2{Hello two worlds}few{Hello {count} worlds}many{Hello all {count} worlds}other{Hello other {count} worlds}}", "@helloWorlds": { "placeholders": "Incorrectly a string, should be a map." } }'''; - final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') - ..createSync(recursive: true); - l10nDirectory.childFile(defaultTemplateArbFileName) - .writeAsStringSync(pluralMessageWithIncorrectPlaceholderFormat); + final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') + ..createSync(recursive: true); + l10nDirectory.childFile(defaultTemplateArbFileName) + .writeAsStringSync(pluralMessageWithIncorrectPlaceholderFormat); - final LocalizationsGenerator generator = LocalizationsGenerator(fs); - try { - generator.initialize( - l10nDirectoryPath: defaultArbPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - ); - generator.parseArbFiles(); - generator.generateClassMethods(); - } on L10nException catch (e) { - expect(e.message, contains('is not properly formatted')); - expect(e.message, contains('Ensure that it is a map with keys that are strings')); - return; - } - fail('Generating class methods with incorrect placeholder format should not succeed'); + final LocalizationsGenerator generator = LocalizationsGenerator(fs); + try { + generator.initialize( + l10nDirectoryPath: defaultArbPathString, + templateArbFileName: defaultTemplateArbFileName, + outputFileString: defaultOutputFileString, + classNameString: defaultClassNameString, + ); + generator.parseArbFiles(); + generator.generateClassMethods(); + } on L10nException catch (e) { + expect(e.message, contains('is not properly formatted')); + expect(e.message, contains('Ensure that it is a map with keys that are strings')); + return; + } + fail('Generating class methods with incorrect placeholder format should not succeed'); + }); }); - test('should throw when failing to parse the arb file:', () { + test('should throw when failing to parse the arb file', () { const String arbFileWithTrailingComma = '''{ "title": "Stocks", "@title": { @@ -1406,7 +1465,7 @@ void main() { ); }); - test('should throw when resource is missing resource attribute:', () { + test('should throw when resource is missing resource attribute', () { const String arbFileWithMissingResourceAttribute = '''{ "title": "Stocks" }'''; @@ -1529,8 +1588,8 @@ void main() { }); }); - group('LocalizationsGenerator.generateOutputFile:', () { - test('correctly generates the localizations classes:', () { + group('generateOutputFile', () { + test('correctly generates the localizations classes', () { _standardFlutterDirectoryL10nSetup(fs); final LocalizationsGenerator generator = LocalizationsGenerator(fs); try {