// 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:io'; import 'package:file/file.dart'; import 'package:file/memory.dart'; import 'package:path/path.dart' as path; import '../../localization/gen_l10n.dart'; import '../../localization/localizations_utils.dart'; import '../common.dart'; final String defaultArbPathString = path.join('lib', 'l10n'); const String defaultTemplateArbFileName = 'app_en_US.arb'; const String defaultOutputFileString = 'output-localization-file'; const String defaultClassNameString = 'AppLocalizations'; const String singleMessageArbFileString = '''{ "title": "Title", "@title": { "description": "Title for the application" } }'''; const String esArbFileName = 'app_es.arb'; const String singleEsMessageArbFileString = '''{ "title": "Título" }'''; const String singleZhMessageArbFileString = '''{ "title": "标题" }'''; void _standardFlutterDirectoryL10nSetup(FileSystem fs) { final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') ..createSync(recursive: true); l10nDirectory.childFile(defaultTemplateArbFileName) .writeAsStringSync(singleMessageArbFileString); l10nDirectory.childFile(esArbFileName) .writeAsStringSync(singleEsMessageArbFileString); } void main() { MemoryFileSystem fs; setUp(() { fs = MemoryFileSystem( style: Platform.isWindows ? FileSystemStyle.windows : FileSystemStyle.posix ); }); group('LocalizationsGenerator setters:', () { test('happy path', () { _standardFlutterDirectoryL10nSetup(fs); expect(() { final LocalizationsGenerator generator = LocalizationsGenerator(fs); generator.initialize( l10nDirectoryPath: defaultArbPathString, templateArbFileName: defaultTemplateArbFileName, outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, ); }, returnsNormally); }); test('setL10nDirectory fails if the directory does not exist', () { final LocalizationsGenerator generator = LocalizationsGenerator(fs); try { generator.setL10nDirectory('lib'); } on FileSystemException catch (e) { expect(e.message, contains('Make sure that the correct path was provided')); return; } fail( 'Attempting to set LocalizationsGenerator.setL10nDirectory should fail if the ' 'directory does not exist.' ); }); test('setL10nDirectory fails if input string is null', () { _standardFlutterDirectoryL10nSetup(fs); final LocalizationsGenerator generator = LocalizationsGenerator(fs); try { generator.setL10nDirectory(null); } on L10nException catch (e) { expect(e.message, contains('cannot be null')); return; } fail( 'Attempting to set LocalizationsGenerator.setL10nDirectory should fail if the ' 'the input string is null.' ); }); test('setTemplateArbFile fails if l10nDirectory is null', () { final LocalizationsGenerator generator = LocalizationsGenerator(fs); try { generator.setTemplateArbFile(defaultTemplateArbFileName); } on L10nException catch (e) { expect(e.message, contains('cannot be null')); return; } fail( 'Attempting to set LocalizationsGenerator.setTemplateArbFile should fail if the ' 'the l10nDirectory is null.' ); }); test('setTemplateArbFile fails if templateArbFileName is null', () { _standardFlutterDirectoryL10nSetup(fs); final LocalizationsGenerator generator = LocalizationsGenerator(fs); try { generator.setTemplateArbFile(null); } on L10nException catch (e) { expect(e.message, contains('cannot be null')); return; } fail( 'Attempting to set LocalizationsGenerator.setTemplateArbFile should fail if the ' 'the l10nDirectory is null.' ); }); test('setTemplateArbFile fails if input string is null', () { _standardFlutterDirectoryL10nSetup(fs); final LocalizationsGenerator generator = LocalizationsGenerator(fs); try { generator.setTemplateArbFile(null); } on L10nException catch (e) { expect(e.message, contains('cannot be null')); return; } fail( 'Attempting to set LocalizationsGenerator.setTemplateArbFile should fail if the ' 'the input string is null.' ); }); test('setOutputFile fails if input string is null', () { _standardFlutterDirectoryL10nSetup(fs); final LocalizationsGenerator generator = LocalizationsGenerator(fs); try { generator.setOutputFile(null); } on L10nException catch (e) { expect(e.message, contains('cannot be null')); return; } fail( 'Attempting to set LocalizationsGenerator.setOutputFile should fail if the ' 'the input string is null.' ); }); test('setting className fails if input string is null', () { _standardFlutterDirectoryL10nSetup(fs); final LocalizationsGenerator generator = LocalizationsGenerator(fs); try { generator.className = null; } on L10nException catch (e) { expect(e.message, contains('cannot be null')); return; } fail( 'Attempting to set LocalizationsGenerator.className should fail if the ' 'the input string is null.' ); }); group('className should only take valid Dart class names:', () { LocalizationsGenerator generator; setUp(() { _standardFlutterDirectoryL10nSetup(fs); generator = LocalizationsGenerator(fs); try { generator.setL10nDirectory(defaultArbPathString); generator.setTemplateArbFile(defaultTemplateArbFileName); generator.setOutputFile(defaultOutputFileString); } on L10nException catch (e) { throw TestFailure('Unexpected failure during test setup: ${e.message}'); } }); test('fails on string with spaces', () { try { generator.className = 'String with spaces'; } on L10nException catch (e) { expect(e.message, contains('is not a valid Dart class name')); return; } fail( 'Attempting to set LocalizationsGenerator.className should fail if the ' 'the input string is not a valid Dart class name.' ); }); test('fails on non-alphanumeric symbols', () { try { generator.className = 'TestClass@123'; } on L10nException catch (e) { expect(e.message, contains('is not a valid Dart class name')); return; } fail( 'Attempting to set LocalizationsGenerator.className should fail if the ' 'the input string is not a valid Dart class name.' ); }); test('fails on camel-case', () { try { generator.className = 'camelCaseClassName'; } on L10nException catch (e) { expect(e.message, contains('is not a valid Dart class name')); return; } fail( 'Attempting to set LocalizationsGenerator.className should fail if the ' 'the input string is not a valid Dart class name.' ); }); test('fails when starting with a number', () { try { generator.className = '123ClassName'; } on L10nException catch (e) { expect(e.message, contains('is not a valid Dart class name')); return; } fail( 'Attempting to set LocalizationsGenerator.className should fail if the ' 'the input string is not a valid Dart class name.' ); }); }); }); group('LocalizationsGenerator.parseArbFiles:', () { test('correctly initializes supportedLocales and supportedLanguageCodes properties', () { _standardFlutterDirectoryL10nSetup(fs); LocalizationsGenerator generator; try { generator = LocalizationsGenerator(fs); generator.initialize( l10nDirectoryPath: defaultArbPathString, templateArbFileName: defaultTemplateArbFileName, outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, ); generator.parseArbFiles(); } on L10nException catch (e) { fail('Setting language and locales should not fail: \n$e'); } expect(generator.supportedLocales.contains(LocaleInfo.fromString('en_US')), true); expect(generator.supportedLocales.contains(LocaleInfo.fromString('es')), true); }); test('correctly sorts supportedLocales and supportedLanguageCodes alphabetically', () { final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') ..createSync(recursive: true); // Write files in non-alphabetical order so that read performs in that order l10nDirectory.childFile('app_zh.arb') .writeAsStringSync(singleZhMessageArbFileString); l10nDirectory.childFile('app_es.arb') .writeAsStringSync(singleEsMessageArbFileString); l10nDirectory.childFile('app_en_US.arb') .writeAsStringSync(singleMessageArbFileString); LocalizationsGenerator generator; try { generator = LocalizationsGenerator(fs); generator.initialize( l10nDirectoryPath: defaultArbPathString, templateArbFileName: defaultTemplateArbFileName, outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, ); generator.parseArbFiles(); } on L10nException catch (e) { fail('Setting language and locales should not fail: \n$e'); } expect(generator.supportedLocales.first, LocaleInfo.fromString('en_US')); expect(generator.supportedLocales.elementAt(1), LocaleInfo.fromString('es')); expect(generator.supportedLocales.elementAt(2), LocaleInfo.fromString('zh')); }); test('adds preferred locales to the top of supportedLocales and supportedLanguageCodes', () { final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') ..createSync(recursive: true); l10nDirectory.childFile('app_en_US.arb') .writeAsStringSync(singleMessageArbFileString); l10nDirectory.childFile('app_es.arb') .writeAsStringSync(singleEsMessageArbFileString); l10nDirectory.childFile('app_zh.arb') .writeAsStringSync(singleZhMessageArbFileString); const String preferredSupportedLocaleString = '["zh", "es"]'; LocalizationsGenerator generator; try { generator = LocalizationsGenerator(fs); generator.initialize( l10nDirectoryPath: defaultArbPathString, templateArbFileName: defaultTemplateArbFileName, outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, preferredSupportedLocaleString: preferredSupportedLocaleString, ); generator.parseArbFiles(); } on L10nException catch (e) { fail('Setting language and locales should not fail: \n$e'); } expect(generator.supportedLocales.first, LocaleInfo.fromString('zh')); expect(generator.supportedLocales.elementAt(1), LocaleInfo.fromString('es')); expect(generator.supportedLocales.elementAt(2), LocaleInfo.fromString('en_US')); }); test( 'throws an error attempting to add preferred locales ' 'with incorrect runtime type', () { final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') ..createSync(recursive: true); l10nDirectory.childFile('app_en_US.arb') .writeAsStringSync(singleMessageArbFileString); l10nDirectory.childFile('app_es.arb') .writeAsStringSync(singleEsMessageArbFileString); l10nDirectory.childFile('app_zh.arb') .writeAsStringSync(singleZhMessageArbFileString); const String preferredSupportedLocaleString = '[44, "en_US"]'; LocalizationsGenerator generator; try { generator = LocalizationsGenerator(fs); generator.initialize( l10nDirectoryPath: defaultArbPathString, templateArbFileName: defaultTemplateArbFileName, outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, preferredSupportedLocaleString: preferredSupportedLocaleString, ); generator.parseArbFiles(); } on L10nException catch (e) { expect( e.message, contains('Incorrect runtime type'), ); return; } fail( 'Should fail since an incorrect runtime type was used ' 'in the preferredSupportedLocales list.' ); }, ); test( 'throws an error attempting to add preferred locales ' 'when there is no corresponding arb file for that ' 'locale', () { final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') ..createSync(recursive: true); l10nDirectory.childFile('app_en_US.arb') .writeAsStringSync(singleMessageArbFileString); l10nDirectory.childFile('app_es.arb') .writeAsStringSync(singleEsMessageArbFileString); l10nDirectory.childFile('app_zh.arb') .writeAsStringSync(singleZhMessageArbFileString); const String preferredSupportedLocaleString = '["am", "es"]'; LocalizationsGenerator generator; try { generator = LocalizationsGenerator(fs); generator.initialize( l10nDirectoryPath: defaultArbPathString, templateArbFileName: defaultTemplateArbFileName, outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, preferredSupportedLocaleString: preferredSupportedLocaleString, ); generator.parseArbFiles(); } on L10nException catch (e) { expect( e.message, contains("The preferred supported locale, 'am', cannot be added."), ); return; } fail( 'Should fail since an unsupported locale was added ' 'to the preferredSupportedLocales list.' ); }, ); test('correctly sorts arbPathString alphabetically', () { final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') ..createSync(recursive: true); // Write files in non-alphabetical order so that read performs in that order l10nDirectory.childFile('app_zh.arb') .writeAsStringSync(singleZhMessageArbFileString); l10nDirectory.childFile('app_es.arb') .writeAsStringSync(singleEsMessageArbFileString); l10nDirectory.childFile('app_en_US.arb') .writeAsStringSync(singleMessageArbFileString); LocalizationsGenerator generator; try { generator = LocalizationsGenerator(fs); generator.initialize( l10nDirectoryPath: defaultArbPathString, templateArbFileName: defaultTemplateArbFileName, outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, ); generator.parseArbFiles(); } on L10nException catch (e) { fail('Setting language and locales should not fail: \n$e'); } if (Platform.isWindows) { expect(generator.arbPathStrings.first, 'lib\\l10n\\app_en_US.arb'); expect(generator.arbPathStrings.elementAt(1), 'lib\\l10n\\app_es.arb'); expect(generator.arbPathStrings.elementAt(2), 'lib\\l10n\\app_zh.arb'); } else { expect(generator.arbPathStrings.first, 'lib/l10n/app_en_US.arb'); expect(generator.arbPathStrings.elementAt(1), 'lib/l10n/app_es.arb'); expect(generator.arbPathStrings.elementAt(2), 'lib/l10n/app_zh.arb'); } }); test('correctly parses @@locale property in arb file', () { const String arbFileWithEnLocale = '''{ "@@locale": "en", "title": "Title", "@title": { "description": "Title for the application" } }'''; const String arbFileWithZhLocale = '''{ "@@locale": "zh", "title": "标题", "@title": { "description": "Title for the application" } }'''; final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') ..createSync(recursive: true); l10nDirectory.childFile('first_file.arb') .writeAsStringSync(arbFileWithEnLocale); l10nDirectory.childFile('second_file.arb') .writeAsStringSync(arbFileWithZhLocale); LocalizationsGenerator generator; try { generator = LocalizationsGenerator(fs); generator.initialize( l10nDirectoryPath: defaultArbPathString, templateArbFileName: 'first_file.arb', outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, ); generator.parseArbFiles(); } on L10nException catch (e) { fail('Setting language and locales should not fail: \n$e'); } expect(generator.supportedLocales.contains(LocaleInfo.fromString('en')), true); expect(generator.supportedLocales.contains(LocaleInfo.fromString('zh')), true); }); test('correctly parses @@locale property in arb file', () { const String arbFileWithEnLocale = '''{ "@@locale": "en", "title": "Stocks", "@title": { "description": "Title for the Stocks application" } }'''; const String arbFileWithZhLocale = '''{ "@@locale": "zh", "title": "标题", "@title": { "description": "Title for the Stocks application" } }'''; final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') ..createSync(recursive: true); l10nDirectory.childFile('app_es.arb') .writeAsStringSync(arbFileWithEnLocale); l10nDirectory.childFile('app_am.arb') .writeAsStringSync(arbFileWithZhLocale); LocalizationsGenerator generator; try { generator = LocalizationsGenerator(fs); generator.initialize( l10nDirectoryPath: defaultArbPathString, templateArbFileName: 'app_es.arb', outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, ); generator.parseArbFiles(); } on L10nException catch (e) { fail('Setting language and locales should not fail: \n$e'); } // @@locale property should hold higher priority expect(generator.supportedLocales.contains(LocaleInfo.fromString('en')), true); expect(generator.supportedLocales.contains(LocaleInfo.fromString('zh')), true); // filename should not be used since @@locale is specified expect(generator.supportedLocales.contains(LocaleInfo.fromString('es')), false); expect(generator.supportedLocales.contains(LocaleInfo.fromString('am')), false); }); test('throws when arb file\'s locale could not be determined', () { fs.currentDirectory.childDirectory('lib').childDirectory('l10n') ..createSync(recursive: true) ..childFile('app.arb') .writeAsStringSync(singleMessageArbFileString); try { final LocalizationsGenerator generator = LocalizationsGenerator(fs); generator.initialize( l10nDirectoryPath: defaultArbPathString, templateArbFileName: 'app.arb', outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, ); generator.parseArbFiles(); } on L10nException catch (e) { expect(e.message, contains('locale could not be determined')); return; } fail( 'Since locale is not specified, setting languages and locales ' 'should fail' ); }); test('throws when the same locale is detected more than once', () { const String secondMessageArbFileString = '''{ "market": "MARKET", "@market": { "description": "Label for the Market tab" } }'''; final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') ..createSync(recursive: true); l10nDirectory.childFile('app_en.arb') .writeAsStringSync(singleMessageArbFileString); l10nDirectory.childFile('app2_en.arb') .writeAsStringSync(secondMessageArbFileString); try { final LocalizationsGenerator generator = LocalizationsGenerator(fs); generator.initialize( l10nDirectoryPath: defaultArbPathString, templateArbFileName: 'app_en.arb', outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, ); generator.parseArbFiles(); } on L10nException catch (e) { expect(e.message, contains('Multiple arb files with the same locale detected')); return; } fail( 'Since en locale is specified twice, setting languages and locales ' 'should fail' ); }); }); group('LocalizationsGenerator.generateClassMethods:', () { test('correctly generates a simple message with getter:', () { _standardFlutterDirectoryL10nSetup(fs); 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 get title { return Intl.message( r'Title', locale: _localeName, name: 'title', desc: r'Title for the application' ); } '''); }); test('correctly generates simple message method with parameters', () { const String singleSimpleMessageWithPlaceholderArbFileString = '''{ "itemNumber": "Item {value}", "@itemNumber": { "description": "Item placement in list.", "placeholders": { "value": { "example": "1" } } } }'''; final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') ..createSync(recursive: true); l10nDirectory.childFile(defaultTemplateArbFileName) .writeAsStringSync(singleSimpleMessageWithPlaceholderArbFileString); 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 itemNumber(Object value) { return Intl.message( r\'Item \$value\', locale: _localeName, name: 'itemNumber', desc: r\'Item placement in list.\', args: <Object>[value] ); } '''); }); test('correctly generates simple message with dates', () { const String singleDateMessageArbFileString = '''{ "springBegins": "Spring begins on {springStartDate}", "@springBegins": { "description": "The first day of spring", "placeholders": { "springStartDate": { "type": "DateTime", "format": "yMMMMEEEEd" } } } }'''; 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'); } expect(generator.classMethods, isNotEmpty); expect( generator.classMethods.first, ''' String springBegins(Object springStartDate) { final DateFormat springStartDateDateFormat = DateFormat.yMMMMEEEEd(_localeName); final String springStartDateString = springStartDateDateFormat.format(springStartDate); return Intl.message( r'Spring begins on \$springStartDateString', locale: _localeName, name: 'springBegins', desc: r'The first day of spring', args: <Object>[springStartDateString] ); } '''); }); 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", "placeholders": { "springStartDate": { "type": "DateTime", "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('springStartDate')); expect(e.message, contains('does not have a corresponding DateFormat')); return; } fail('Improper date formatting should throw an exception'); }); 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", "placeholders": { "springStartDate": { "type": "DateTime" } } } }'''; 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; } fail('Improper date formatting should throw an exception'); }); 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.", "placeholders": { "springStartDate": { "type": "DateTime", "format": "yMMMMEEEEd" }, "helloWorld": {} } } }'''; 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'); } 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); return Intl.message( r\'Since it\' "\'" r\'s \$springStartDateString, it\' "\'" r\'s finally spring! \$helloWorld!\', locale: _localeName, name: 'springGreetings', desc: r\'A realization that it\' "\'" r\'s finally the spring season, followed by a greeting.\', args: <Object>[springStartDateString, helloWorld] ); } '''); }); 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", "placeholders": { "springStartDate": { "type": "DateTime", "format": "yMMMMEEEEd" }, "springEndDate": { "type": "DateTime", "format": "yMMMMEEEEd" } } } }'''; 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'); } 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); final DateFormat springEndDateDateFormat = DateFormat.yMMMMEEEEd(_localeName); final String springEndDateString = springEndDateDateFormat.format(springEndDate); return Intl.message( r\'Spring begins on \$springStartDateString and ends on \$springEndDateString\', locale: _localeName, name: 'springRange', desc: r\'The range of dates for spring in the year\', args: <Object>[springStartDateString, springEndDateString] ); } '''); }); 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: <Object>[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: <Object>[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: <Object>[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('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": { "description": "Improperly formatted since it has no placeholder attribute." } }'''; 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'); }); test('should throw attempting to generate a plural message with 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.", "placeholders": {} } }'''; 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'); }); 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 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 = '''{ "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 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:', () { const String arbFileWithTrailingComma = '''{ "title": "Stocks", "@title": { "description": "Title for the Stocks application" }, }'''; final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') ..createSync(recursive: true); l10nDirectory.childFile(defaultTemplateArbFileName) .writeAsStringSync(arbFileWithTrailingComma); final LocalizationsGenerator generator = LocalizationsGenerator(fs); try { generator.initialize( l10nDirectoryPath: defaultArbPathString, templateArbFileName: defaultTemplateArbFileName, outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, ); generator.parseArbFiles(); generator.generateClassMethods(); } on FormatException catch (e) { expect(e.message, contains('Unexpected character')); return; } fail( 'should fail with a FormatException due to a trailing comma in the ' 'arb file.' ); }); test('should throw when resource is missing resource attribute:', () { const String arbFileWithMissingResourceAttribute = '''{ "title": "Stocks" }'''; final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') ..createSync(recursive: true); l10nDirectory.childFile(defaultTemplateArbFileName) .writeAsStringSync(arbFileWithMissingResourceAttribute); 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 "@title" was not found')); return; } fail( 'should fail with a FormatException due to a trailing comma in the ' 'arb file.' ); }); group('checks for method/getter formatting', () { test('cannot contain non-alphanumeric symbols', () { const String nonAlphaNumericArbFile = '''{ "title!!": "Stocks", "@title!!": { "description": "Title for the Stocks application" } }'''; final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') ..createSync(recursive: true); l10nDirectory.childFile(defaultTemplateArbFileName) .writeAsStringSync(nonAlphaNumericArbFile); 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('Invalid key format')); return; } fail('should fail due to non-alphanumeric character.'); }); test('must start with lowercase character', () { const String nonAlphaNumericArbFile = '''{ "Title": "Stocks", "@Title": { "description": "Title for the Stocks application" } }'''; final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') ..createSync(recursive: true); l10nDirectory.childFile(defaultTemplateArbFileName) .writeAsStringSync(nonAlphaNumericArbFile); 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('Invalid key format')); return; } fail('should fail since key starts with a non-lowercase.'); }); test('cannot start with a number', () { const String nonAlphaNumericArbFile = '''{ "123title": "Stocks", "@123title": { "description": "Title for the Stocks application" } }'''; final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') ..createSync(recursive: true); l10nDirectory.childFile(defaultTemplateArbFileName) .writeAsStringSync(nonAlphaNumericArbFile); 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('Invalid key format')); return; } fail('should fail since key starts with a number.'); }); }); }); group('LocalizationsGenerator.generateOutputFile:', () { test('correctly generates the localizations classes:', () { _standardFlutterDirectoryL10nSetup(fs); final LocalizationsGenerator generator = LocalizationsGenerator(fs); try { generator.initialize( l10nDirectoryPath: defaultArbPathString, templateArbFileName: defaultTemplateArbFileName, outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, ); generator.parseArbFiles(); generator.generateClassMethods(); generator.generateOutputFile(); } on Exception catch (e) { fail('Generating output localization file should succeed: \n$e'); } final String outputFileString = generator.outputFile.readAsStringSync(); expect(outputFileString, contains('class AppLocalizations')); expect(outputFileString, contains('class _AppLocalizationsDelegate extends LocalizationsDelegate<AppLocalizations>')); }); }); }