// 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. // @dart = 2.8 import 'dart:io'; import 'package:file/memory.dart'; import 'package:yaml/yaml.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/convert.dart'; import 'package:flutter_tools/src/globals.dart' as globals; import 'package:flutter_tools/src/localizations/gen_l10n.dart'; import 'package:flutter_tools/src/localizations/gen_l10n_types.dart'; import 'package:flutter_tools/src/localizations/localizations_utils.dart'; import 'package:test_api/test_api.dart' hide TypeMatcher, isInstanceOf; // ignore: deprecated_member_use // ignore: deprecated_member_use export 'package:test_core/test_core.dart' hide TypeMatcher, isInstanceOf, test; // Defines a 'package:test' shim. final String defaultL10nPathString = globals.fs.path.join('lib', 'l10n'); final String syntheticPackagePath = globals.fs.path.join('.dart_tool', 'flutter_gen'); final String syntheticL10nPackagePath = globals.fs.path.join(syntheticPackagePath, 'gen_l10n'); const String defaultTemplateArbFileName = 'app_en.arb'; const String defaultOutputFileString = 'output-localization-file.dart'; const String defaultClassNameString = 'AppLocalizations'; const String singleMessageArbFileString = ''' { "title": "Title", "@title": { "description": "Title for the application." } }'''; const String twoMessageArbFileString = ''' { "title": "Title", "@title": { "description": "Title for the application." }, "subtitle": "Subtitle", "@subtitle": { "description": "Subtitle for the application." } }'''; const String esArbFileName = 'app_es.arb'; const String singleEsMessageArbFileString = ''' { "title": "Título" }'''; const String twoEsMessageArbFileString = ''' { "title": "Título", "subtitle": "Subtitular" }'''; 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 ); precacheLanguageAndRegionTags(); }); group('Setters', () { test('setInputDirectory fails if the directory does not exist', () { final LocalizationsGenerator generator = LocalizationsGenerator(fs); try { generator.setInputDirectory('lib'); } on L10nException catch (e) { expect(e.message, contains('Make sure that the correct path was provided')); return; } fail( 'LocalizationsGenerator.setInputDirectory should fail if the ' 'directory does not exist.' ); }); test('setInputDirectory fails if input string is null', () { _standardFlutterDirectoryL10nSetup(fs); final LocalizationsGenerator generator = LocalizationsGenerator(fs); try { generator.setInputDirectory(null); } on L10nException catch (e) { expect(e.message, contains('cannot be null')); return; } fail( 'LocalizationsGenerator.setInputDirectory should fail if the ' 'input string is null.' ); }); test( 'setOutputDirectory fails if output string is null while not using the ' 'synthetic package option', () { _standardFlutterDirectoryL10nSetup(fs); final LocalizationsGenerator generator = LocalizationsGenerator(fs); try { generator.initialize(useSyntheticPackage: false); generator.setOutputDirectory(null); } on L10nException catch (e) { expect(e.message, contains('cannot be null')); return; } fail( 'LocalizationsGenerator.setOutputDirectory should fail if the ' 'input string is null.' ); }, ); test('setTemplateArbFile fails if inputDirectory is null', () { final LocalizationsGenerator generator = LocalizationsGenerator(fs); try { generator.setTemplateArbFile(defaultTemplateArbFileName); } on L10nException catch (e) { expect(e.message, contains('cannot be null')); return; } fail( 'LocalizationsGenerator.setTemplateArbFile should fail if the ' 'inputDirectory is not specified.' ); }); 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( 'LocalizationsGenerator.setTemplateArbFile should fail if the ' 'templateArbFileName passed in 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( 'LocalizationsGenerator.setTemplateArbFile should fail if the ' 'input string is null.' ); }); test('setBaseOutputFile fails if input string is null', () { _standardFlutterDirectoryL10nSetup(fs); final LocalizationsGenerator generator = LocalizationsGenerator(fs); try { generator.setBaseOutputFile(null); } on L10nException catch (e) { expect(e.message, contains('cannot be null')); return; } fail( 'LocalizationsGenerator.setBaseOutputFile should fail if 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( 'LocalizationsGenerator.className should fail if the ' 'input string is null.' ); }); test('sets absolute path of the target Flutter project', () { // Set up project directory. final Directory l10nDirectory = fs.currentDirectory .childDirectory('absolute') .childDirectory('path') .childDirectory('to') .childDirectory('flutter_project') .childDirectory('lib') .childDirectory('l10n') ..createSync(recursive: true); l10nDirectory.childFile(defaultTemplateArbFileName) .writeAsStringSync(singleMessageArbFileString); l10nDirectory.childFile(esArbFileName) .writeAsStringSync(singleEsMessageArbFileString); // Run localizations generator in specified absolute path. final LocalizationsGenerator generator = LocalizationsGenerator(fs); final String flutterProjectPath = fs.path.join('absolute', 'path', 'to', 'flutter_project'); try { generator.initialize( projectPathString: flutterProjectPath, inputPathString: defaultL10nPathString, outputPathString: defaultL10nPathString, templateArbFileName: defaultTemplateArbFileName, outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, ); generator.loadResources(); generator.writeOutputFiles(BufferLogger.test()); } on L10nException catch (e) { throw TestFailure('Unexpected failure during test setup: ${e.message}'); } on Exception catch (e) { throw TestFailure('Unexpected failure during test setup: $e'); } // Output files should be generated in the provided absolute path. expect( fs.isFileSync(fs.path.join( flutterProjectPath, '.dart_tool', 'flutter_gen', 'gen_l10n', 'output-localization-file_en.dart', )), true, ); expect( fs.isFileSync(fs.path.join( flutterProjectPath, '.dart_tool', 'flutter_gen', 'gen_l10n', 'output-localization-file_es.dart', )), true, ); }); test('throws error when directory at absolute path does not exist', () { // Set up project directory. final Directory l10nDirectory = fs.currentDirectory .childDirectory('lib') .childDirectory('l10n') ..createSync(recursive: true); l10nDirectory.childFile(defaultTemplateArbFileName) .writeAsStringSync(singleMessageArbFileString); l10nDirectory.childFile(esArbFileName) .writeAsStringSync(singleEsMessageArbFileString); // Project path should be intentionally a directory that does not exist. final LocalizationsGenerator generator = LocalizationsGenerator(fs); try { generator.initialize( projectPathString: 'absolute/path/to/flutter_project', inputPathString: defaultL10nPathString, outputPathString: defaultL10nPathString, templateArbFileName: defaultTemplateArbFileName, outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, ); } on L10nException catch (e) { expect(e.message, contains('Directory does not exist')); return; } fail( 'An exception should be thrown when the directory ' 'specified in projectPathString does not exist.' ); }); group('className should only take valid Dart class names', () { LocalizationsGenerator generator; setUp(() { _standardFlutterDirectoryL10nSetup(fs); generator = LocalizationsGenerator(fs); try { generator.setInputDirectory(defaultL10nPathString); generator.setOutputDirectory(null); generator.setTemplateArbFile(defaultTemplateArbFileName); generator.setBaseOutputFile(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 public Dart class name')); return; } fail( 'LocalizationsGenerator.className should fail if 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 public Dart class name')); return; } fail( 'LocalizationsGenerator.className should fail if the ' 'input string is not a valid public Dart class name.' ); }); test('fails on camel-case', () { try { generator.className = 'camelCaseClassName'; } on L10nException catch (e) { expect(e.message, contains('is not a valid public Dart class name')); return; } fail( 'LocalizationsGenerator.className should fail if the ' 'input string is not a valid public 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 public Dart class name')); return; } fail( 'LocalizationsGenerator.className should fail if the ' 'input string is not a valid public Dart class name.' ); }); }); }); test('correctly adds a headerString when it is set', () { _standardFlutterDirectoryL10nSetup(fs); LocalizationsGenerator generator; try { generator = LocalizationsGenerator(fs); generator.initialize( inputPathString: defaultL10nPathString, outputPathString: defaultL10nPathString, templateArbFileName: defaultTemplateArbFileName, outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, headerString: '/// Sample header', ); } on L10nException catch (e) { fail('Setting a header through a String should not fail: \n${e.message}'); } expect(generator.header, '/// Sample header'); }); test('correctly adds a headerFile when it is set', () { fs.currentDirectory.childDirectory('lib').childDirectory('l10n') ..createSync(recursive: true) ..childFile(defaultTemplateArbFileName).writeAsStringSync(singleMessageArbFileString) ..childFile(esArbFileName).writeAsStringSync(singleEsMessageArbFileString) ..childFile('header.txt').writeAsStringSync('/// Sample header in a text file'); LocalizationsGenerator generator; try { generator = LocalizationsGenerator(fs); generator.initialize( inputPathString: defaultL10nPathString, outputPathString: defaultL10nPathString, templateArbFileName: defaultTemplateArbFileName, outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, headerFile: 'header.txt', ); } on L10nException catch (e) { fail('Setting a header through a file should not fail: \n${e.message}'); } expect(generator.header, '/// Sample header in a text file'); }); test('sets templateArbFileName with more than one underscore correctly', () { final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') ..createSync(recursive: true); l10nDirectory.childFile('app_localizations_en.arb') .writeAsStringSync(singleMessageArbFileString); l10nDirectory.childFile('app_localizations_es.arb') .writeAsStringSync(singleEsMessageArbFileString); LocalizationsGenerator generator; try { generator = LocalizationsGenerator(fs); generator ..initialize( inputPathString: defaultL10nPathString, templateArbFileName: 'app_localizations_en.arb', outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, ) ..loadResources() ..writeOutputFiles(BufferLogger.test()); } on L10nException catch (e) { fail('Generating output should not fail: \n${e.message}'); } final Directory outputDirectory = fs.directory(syntheticL10nPackagePath); expect(outputDirectory.childFile('output-localization-file.dart').existsSync(), isTrue); expect(outputDirectory.childFile('output-localization-file_en.dart').existsSync(), isTrue); expect(outputDirectory.childFile('output-localization-file_es.dart').existsSync(), isTrue); }); test('filenames with invalid locales should not be recognized', () { final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') ..createSync(recursive: true); l10nDirectory.childFile('app_localizations_en.arb') .writeAsStringSync(singleMessageArbFileString); l10nDirectory.childFile('app_localizations_en_CA_foo.arb') .writeAsStringSync(singleMessageArbFileString); LocalizationsGenerator generator; try { generator = LocalizationsGenerator(fs); generator ..initialize( inputPathString: defaultL10nPathString, templateArbFileName: 'app_localizations_en.arb', outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, ) ..loadResources(); } on L10nException catch (e) { expect(e.message, contains('The following .arb file\'s locale could not be determined')); return; } fail('Using app_en_CA_foo.arb should fail as it is not a valid locale.'); }); test('correctly creates an untranslated messages file (useSyntheticPackage = true)', () { fs.currentDirectory.childDirectory('lib').childDirectory('l10n') ..createSync(recursive: true) ..childFile(defaultTemplateArbFileName).writeAsStringSync(twoMessageArbFileString) ..childFile(esArbFileName).writeAsStringSync(singleEsMessageArbFileString); LocalizationsGenerator generator; try { generator = LocalizationsGenerator(fs); generator ..initialize( inputPathString: defaultL10nPathString, outputPathString: defaultL10nPathString, templateArbFileName: defaultTemplateArbFileName, outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, untranslatedMessagesFile: fs.path.join('lib', 'l10n', 'unimplemented_message_translations.json'), ) ..loadResources() ..writeOutputFiles(BufferLogger.test()); } on L10nException catch (e) { fail('Generating output should not fail: \n${e.message}'); } final File unimplementedOutputFile = fs.file( fs.path.join('lib', 'l10n', 'unimplemented_message_translations.json'), ); final String unimplementedOutputString = unimplementedOutputFile.readAsStringSync(); try { // Since ARB file is essentially JSON, decoding it should not fail. json.decode(unimplementedOutputString); } on Exception { fail('Parsing arb file should not fail'); } expect(unimplementedOutputString, contains('es')); expect(unimplementedOutputString, contains('subtitle')); }); test('correctly creates an untranslated messages file (useSyntheticPackage = false)', () { fs.currentDirectory.childDirectory('lib').childDirectory('l10n') ..createSync(recursive: true) ..childFile(defaultTemplateArbFileName).writeAsStringSync(twoMessageArbFileString) ..childFile(esArbFileName).writeAsStringSync(singleEsMessageArbFileString); LocalizationsGenerator generator; try { generator = LocalizationsGenerator(fs); generator ..initialize( inputPathString: defaultL10nPathString, outputPathString: defaultL10nPathString, templateArbFileName: defaultTemplateArbFileName, outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, useSyntheticPackage: false, untranslatedMessagesFile: fs.path.join('lib', 'l10n', 'unimplemented_message_translations.json'), ) ..loadResources() ..writeOutputFiles(BufferLogger.test()); } on L10nException catch (e) { fail('Generating output should not fail: \n${e.message}'); } final File unimplementedOutputFile = fs.file( fs.path.join('lib', 'l10n', 'unimplemented_message_translations.json'), ); final String unimplementedOutputString = unimplementedOutputFile.readAsStringSync(); try { // Since ARB file is essentially JSON, decoding it should not fail. json.decode(unimplementedOutputString); } on Exception { fail('Parsing arb file should not fail'); } expect(unimplementedOutputString, contains('es')); expect(unimplementedOutputString, contains('subtitle')); }); test( 'untranslated messages suggestion is printed when translation is missing: ' 'command line message', () { final BufferLogger testLogger = BufferLogger.test(); fs.currentDirectory.childDirectory('lib').childDirectory('l10n') ..createSync(recursive: true) ..childFile(defaultTemplateArbFileName).writeAsStringSync(twoMessageArbFileString) ..childFile(esArbFileName).writeAsStringSync(singleEsMessageArbFileString); LocalizationsGenerator generator; try { generator = LocalizationsGenerator(fs); generator ..initialize( inputPathString: defaultL10nPathString, outputPathString: defaultL10nPathString, templateArbFileName: defaultTemplateArbFileName, outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, useSyntheticPackage: false, ) ..loadResources() ..writeOutputFiles(testLogger); } on L10nException catch (e) { fail('Generating output should not fail: \n${e.message}'); } expect( testLogger.statusText, contains('To see a detailed report, use the --untranslated-messages-file'), ); expect( testLogger.statusText, contains('flutter gen-l10n --untranslated-messages-file=desiredFileName.txt'), ); }, ); test( 'untranslated messages suggestion is printed when translation is missing: ' 'l10n.yaml message', () { final BufferLogger testLogger = BufferLogger.test(); fs.currentDirectory.childDirectory('lib').childDirectory('l10n') ..createSync(recursive: true) ..childFile(defaultTemplateArbFileName).writeAsStringSync(twoMessageArbFileString) ..childFile(esArbFileName).writeAsStringSync(singleEsMessageArbFileString); LocalizationsGenerator generator; try { generator = LocalizationsGenerator(fs); generator ..initialize( inputPathString: defaultL10nPathString, templateArbFileName: defaultTemplateArbFileName, outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, ) ..loadResources() ..writeOutputFiles(testLogger, isFromYaml: true); } on L10nException catch (e) { fail('Generating output should not fail: \n${e.message}'); } expect( testLogger.statusText, contains('To see a detailed report, use the untranslated-messages-file'), ); expect( testLogger.statusText, contains('untranslated-messages-file: desiredFileName.txt'), ); }, ); test( 'unimplemented messages suggestion is not printed when all messages ' 'are fully translated', () { final BufferLogger testLogger = BufferLogger.test(); fs.currentDirectory.childDirectory('lib').childDirectory('l10n') ..createSync(recursive: true) ..childFile(defaultTemplateArbFileName).writeAsStringSync(twoMessageArbFileString) ..childFile(esArbFileName).writeAsStringSync(twoMessageArbFileString); LocalizationsGenerator generator; try { generator = LocalizationsGenerator(fs); generator ..initialize( inputPathString: defaultL10nPathString, outputPathString: defaultL10nPathString, templateArbFileName: defaultTemplateArbFileName, outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, ) ..loadResources() ..writeOutputFiles(testLogger); } on L10nException catch (e) { fail('Generating output should not fail: \n${e.message}'); } expect(testLogger.statusText, ''); }, ); test('untranslated messages file included in generated JSON list of outputs', () { _standardFlutterDirectoryL10nSetup(fs); LocalizationsGenerator generator; try { generator = LocalizationsGenerator(fs); generator ..initialize( inputPathString: defaultL10nPathString, templateArbFileName: defaultTemplateArbFileName, outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, inputsAndOutputsListPath: syntheticL10nPackagePath, untranslatedMessagesFile: fs.path.join('lib', 'l10n', 'unimplemented_message_translations.json'), ) ..loadResources() ..writeOutputFiles(BufferLogger.test()); } on L10nException catch (e) { fail('Generating output should not fail: \n${e.message}'); } final File inputsAndOutputsList = fs.file( fs.path.join(syntheticL10nPackagePath, 'gen_l10n_inputs_and_outputs.json'), ); expect(inputsAndOutputsList.existsSync(), isTrue); final Map<String, dynamic> jsonResult = json.decode( inputsAndOutputsList.readAsStringSync(), ) as Map<String, dynamic>; expect(jsonResult.containsKey('outputs'), isTrue); final List<dynamic> outputList = jsonResult['outputs'] as List<dynamic>; expect(outputList, contains(contains('unimplemented_message_translations.json'))); }); test( 'uses inputPathString as outputPathString when the outputPathString is ' 'null while not using the synthetic package option', () { _standardFlutterDirectoryL10nSetup(fs); LocalizationsGenerator generator; try { generator = LocalizationsGenerator(fs); generator ..initialize( inputPathString: defaultL10nPathString, // outputPathString is intentionally not defined templateArbFileName: defaultTemplateArbFileName, outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, useSyntheticPackage: false, ) ..loadResources() ..writeOutputFiles(BufferLogger.test()); } on L10nException catch (e) { fail('Generating output should not fail: \n${e.message}'); } final Directory outputDirectory = fs.directory('lib').childDirectory('l10n'); expect(outputDirectory.childFile('output-localization-file.dart').existsSync(), isTrue); expect(outputDirectory.childFile('output-localization-file_en.dart').existsSync(), isTrue); expect(outputDirectory.childFile('output-localization-file_es.dart').existsSync(), isTrue); }, ); test( 'correctly generates output files in non-default output directory if it ' 'already exists while not using the synthetic package option', () { final Directory l10nDirectory = fs.currentDirectory .childDirectory('lib') .childDirectory('l10n') ..createSync(recursive: true); // Create the directory 'lib/l10n/output'. l10nDirectory.childDirectory('output'); l10nDirectory .childFile(defaultTemplateArbFileName) .writeAsStringSync(singleMessageArbFileString); l10nDirectory .childFile(esArbFileName) .writeAsStringSync(singleEsMessageArbFileString); LocalizationsGenerator generator; try { generator = LocalizationsGenerator(fs); generator ..initialize( inputPathString: defaultL10nPathString, outputPathString: fs.path.join('lib', 'l10n', 'output'), templateArbFileName: defaultTemplateArbFileName, outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, useSyntheticPackage: false, ) ..loadResources() ..writeOutputFiles(BufferLogger.test()); } on L10nException catch (e) { fail('Generating output should not fail: \n${e.message}'); } final Directory outputDirectory = fs.directory('lib').childDirectory('l10n').childDirectory('output'); expect(outputDirectory.existsSync(), isTrue); expect(outputDirectory.childFile('output-localization-file.dart').existsSync(), isTrue); expect(outputDirectory.childFile('output-localization-file_en.dart').existsSync(), isTrue); expect(outputDirectory.childFile('output-localization-file_es.dart').existsSync(), isTrue); }, ); test( 'correctly creates output directory if it does not exist and writes files ' 'in it while not using the synthetic package option', () { _standardFlutterDirectoryL10nSetup(fs); LocalizationsGenerator generator; try { generator = LocalizationsGenerator(fs); generator ..initialize( inputPathString: defaultL10nPathString, outputPathString: fs.path.join('lib', 'l10n', 'output'), templateArbFileName: defaultTemplateArbFileName, outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, useSyntheticPackage: false, ) ..loadResources() ..writeOutputFiles(BufferLogger.test()); } on L10nException catch (e) { fail('Generating output should not fail: \n${e.message}'); } final Directory outputDirectory = fs.directory('lib').childDirectory('l10n').childDirectory('output'); expect(outputDirectory.existsSync(), isTrue); expect(outputDirectory.childFile('output-localization-file.dart').existsSync(), isTrue); expect(outputDirectory.childFile('output-localization-file_en.dart').existsSync(), isTrue); expect(outputDirectory.childFile('output-localization-file_es.dart').existsSync(), isTrue); }, ); test('creates list of inputs and outputs when file path is specified', () { _standardFlutterDirectoryL10nSetup(fs); LocalizationsGenerator generator; try { generator = LocalizationsGenerator(fs); generator ..initialize( inputPathString: defaultL10nPathString, templateArbFileName: defaultTemplateArbFileName, outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, inputsAndOutputsListPath: syntheticL10nPackagePath, ) ..loadResources() ..writeOutputFiles(BufferLogger.test()); } on L10nException catch (e) { fail('Generating output should not fail: \n${e.message}'); } final File inputsAndOutputsList = fs.file( fs.path.join(syntheticL10nPackagePath, 'gen_l10n_inputs_and_outputs.json'), ); expect(inputsAndOutputsList.existsSync(), isTrue); final Map<String, dynamic> jsonResult = json.decode(inputsAndOutputsList.readAsStringSync()) as Map<String, dynamic>; expect(jsonResult.containsKey('inputs'), isTrue); final List<dynamic> inputList = jsonResult['inputs'] as List<dynamic>; expect(inputList, contains(fs.path.absolute('lib', 'l10n', 'app_en.arb'))); expect(inputList, contains(fs.path.absolute('lib', 'l10n', 'app_es.arb'))); expect(jsonResult.containsKey('outputs'), isTrue); final List<dynamic> outputList = jsonResult['outputs'] as List<dynamic>; expect(outputList, contains(fs.path.absolute(syntheticL10nPackagePath, 'output-localization-file.dart'))); expect(outputList, contains(fs.path.absolute(syntheticL10nPackagePath, 'output-localization-file_en.dart'))); expect(outputList, contains(fs.path.absolute(syntheticL10nPackagePath, 'output-localization-file_es.dart'))); }); test('setting both a headerString and a headerFile should fail', () { fs.currentDirectory.childDirectory('lib').childDirectory('l10n') ..createSync(recursive: true) ..childFile(defaultTemplateArbFileName).writeAsStringSync(singleMessageArbFileString) ..childFile(esArbFileName).writeAsStringSync(singleEsMessageArbFileString) ..childFile('header.txt').writeAsStringSync('/// Sample header in a text file'); LocalizationsGenerator generator; try { generator = LocalizationsGenerator(fs); generator.initialize( inputPathString: defaultL10nPathString, outputPathString: defaultL10nPathString, templateArbFileName: defaultTemplateArbFileName, outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, headerString: '/// Sample header for localizations file.', headerFile: 'header.txt', ); } on L10nException catch (e) { expect(e.message, contains('Cannot accept both header and header file arguments')); return; } fail('Setting both headerFile and headerString should fail'); }); test('setting a headerFile that does not exist should fail', () { final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') ..createSync(recursive: true); l10nDirectory.childFile(defaultTemplateArbFileName) .writeAsStringSync(singleMessageArbFileString); l10nDirectory.childFile(esArbFileName) .writeAsStringSync(singleEsMessageArbFileString); l10nDirectory.childFile('header.txt') .writeAsStringSync('/// Sample header in a text file'); LocalizationsGenerator generator; try { generator = LocalizationsGenerator(fs); generator.initialize( inputPathString: defaultL10nPathString, outputPathString: defaultL10nPathString, templateArbFileName: defaultTemplateArbFileName, outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, headerFile: 'header.tx', // Intentionally spelled incorrectly ); } on L10nException catch (e) { expect(e.message, contains('Failed to read header file')); return; } fail('Setting headerFile that does not exist should fail'); }); test('setting useDefferedLoading to null should fail', () { _standardFlutterDirectoryL10nSetup(fs); LocalizationsGenerator generator; try { generator = LocalizationsGenerator(fs); generator.initialize( inputPathString: defaultL10nPathString, outputPathString: defaultL10nPathString, templateArbFileName: defaultTemplateArbFileName, outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, headerString: '/// Sample header', useDeferredLoading: null, ); } on L10nException catch (e) { expect(e.message, contains('useDeferredLoading argument cannot be null.')); return; } fail('Setting useDefferedLoading to null should fail'); }); group('loadResources', () { test('correctly initializes supportedLocales and supportedLanguageCodes properties', () { _standardFlutterDirectoryL10nSetup(fs); LocalizationsGenerator generator; try { generator = LocalizationsGenerator(fs); generator.initialize( inputPathString: defaultL10nPathString, outputPathString: defaultL10nPathString, templateArbFileName: defaultTemplateArbFileName, outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, ); generator.loadResources(); } on L10nException catch (e) { fail('Setting language and locales should not fail: \n${e.message}'); } expect(generator.supportedLocales.contains(LocaleInfo.fromString('en')), 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.arb') .writeAsStringSync(singleMessageArbFileString); LocalizationsGenerator generator; try { generator = LocalizationsGenerator(fs); generator.initialize( inputPathString: defaultL10nPathString, outputPathString: defaultL10nPathString, templateArbFileName: defaultTemplateArbFileName, outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, ); generator.loadResources(); } on L10nException catch (e) { fail('Setting language and locales should not fail: \n${e.message}'); } expect(generator.supportedLocales.first, LocaleInfo.fromString('en')); 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.arb') .writeAsStringSync(singleMessageArbFileString); l10nDirectory.childFile('app_es.arb') .writeAsStringSync(singleEsMessageArbFileString); l10nDirectory.childFile('app_zh.arb') .writeAsStringSync(singleZhMessageArbFileString); const List<String> preferredSupportedLocale = <String>['zh', 'es']; LocalizationsGenerator generator; try { generator = LocalizationsGenerator(fs); generator.initialize( inputPathString: defaultL10nPathString, outputPathString: defaultL10nPathString, templateArbFileName: defaultTemplateArbFileName, outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, preferredSupportedLocales: preferredSupportedLocale, ); generator.loadResources(); } on L10nException catch (e) { fail('Setting language and locales should not fail: \n${e.message}'); } expect(generator.supportedLocales.first, LocaleInfo.fromString('zh')); expect(generator.supportedLocales.elementAt(1), LocaleInfo.fromString('es')); expect(generator.supportedLocales.elementAt(2), LocaleInfo.fromString('en')); }); 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.arb') .writeAsStringSync(singleMessageArbFileString); l10nDirectory.childFile('app_es.arb') .writeAsStringSync(singleEsMessageArbFileString); l10nDirectory.childFile('app_zh.arb') .writeAsStringSync(singleZhMessageArbFileString); const List<String> preferredSupportedLocale = <String>['am', 'es']; LocalizationsGenerator generator; try { generator = LocalizationsGenerator(fs); generator.initialize( inputPathString: defaultL10nPathString, outputPathString: defaultL10nPathString, templateArbFileName: defaultTemplateArbFileName, outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, preferredSupportedLocales: preferredSupportedLocale, ); generator.loadResources(); } 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.arb') .writeAsStringSync(singleMessageArbFileString); LocalizationsGenerator generator; try { generator = LocalizationsGenerator(fs); generator.initialize( inputPathString: defaultL10nPathString, outputPathString: defaultL10nPathString, templateArbFileName: defaultTemplateArbFileName, outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, ); generator.loadResources(); } on L10nException catch (e) { fail('Setting language and locales should not fail: \n${e.message}'); } expect(generator.arbPathStrings.first, fs.path.join('lib', 'l10n', 'app_en.arb')); expect(generator.arbPathStrings.elementAt(1), fs.path.join('lib', 'l10n', 'app_es.arb')); expect(generator.arbPathStrings.elementAt(2), fs.path.join('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( inputPathString: defaultL10nPathString, outputPathString: defaultL10nPathString, templateArbFileName: 'first_file.arb', outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, ); generator.loadResources(); } on L10nException catch (e) { fail('Setting language and locales should not fail: \n${e.message}'); } expect(generator.supportedLocales.contains(LocaleInfo.fromString('en')), true); expect(generator.supportedLocales.contains(LocaleInfo.fromString('zh')), true); }); test('correctly requires @@locale property in arb file to match the filename locale suffix', () { 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( inputPathString: defaultL10nPathString, outputPathString: defaultL10nPathString, templateArbFileName: 'app_es.arb', outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, ); generator.loadResources(); } on L10nException catch (e) { expect(e.message, contains('The locale specified in @@locale and the arb filename do not match.')); return; } fail( 'An exception should occur if the @@locale and the filename extensions are ' 'defined but not matching.' ); }); 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( inputPathString: defaultL10nPathString, outputPathString: defaultL10nPathString, templateArbFileName: 'app.arb', outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, ); generator.loadResources(); } 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( inputPathString: defaultL10nPathString, outputPathString: defaultL10nPathString, templateArbFileName: 'app_en.arb', outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, ); generator.loadResources(); } on L10nException catch (e) { expect(e.message, contains("Multiple arb files with the same 'en' locale detected")); return; } fail( 'Since en locale is specified twice, setting languages and locales ' 'should fail' ); }); test('throws when the base locale does not exist', () { final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') ..createSync(recursive: true); l10nDirectory.childFile('app_en_US.arb') .writeAsStringSync(singleMessageArbFileString); try { final LocalizationsGenerator generator = LocalizationsGenerator(fs); generator.initialize( inputPathString: defaultL10nPathString, outputPathString: defaultL10nPathString, templateArbFileName: 'app_en_US.arb', outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, ); generator.loadResources(); } on L10nException catch (e) { expect(e.message, contains('Arb file for a fallback, en, does not exist')); return; } fail( 'Since en_US.arb is specified, but en.arb is not, ' 'the tool should throw an error.' ); }); }); group('writeOutputFiles', () { test('message without placeholders - should generate code comment with description and template message translation', () { _standardFlutterDirectoryL10nSetup(fs); final LocalizationsGenerator generator = LocalizationsGenerator(fs); final BufferLogger testLogger = BufferLogger.test(); try { generator.initialize( inputPathString: defaultL10nPathString, outputPathString: defaultL10nPathString, templateArbFileName: defaultTemplateArbFileName, outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, ); generator.loadResources(); generator.writeOutputFiles(testLogger); } on Exception catch (e) { fail('Generating output files should not fail: $e'); } final File baseLocalizationsFile = fs.file( fs.path.join(syntheticL10nPackagePath, 'output-localization-file.dart') ); expect(baseLocalizationsFile.existsSync(), isTrue); final String baseLocalizationsFileContents = fs.file( fs.path.join(syntheticL10nPackagePath, 'output-localization-file.dart') ).readAsStringSync(); expect(baseLocalizationsFileContents, contains('/// Title for the application.')); expect(baseLocalizationsFileContents, contains(''' /// In en, this message translates to: /// **'Title'**''')); }); test('template message translation handles newline characters', () { final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') ..createSync(recursive: true); l10nDirectory.childFile(defaultTemplateArbFileName) .writeAsStringSync(r''' { "title": "Title \n of the application", "@title": { "description": "Title for the application." } }'''); l10nDirectory.childFile(esArbFileName) .writeAsStringSync(singleEsMessageArbFileString); final LocalizationsGenerator generator = LocalizationsGenerator(fs); final BufferLogger testLogger = BufferLogger.test(); try { generator.initialize( inputPathString: defaultL10nPathString, outputPathString: defaultL10nPathString, templateArbFileName: defaultTemplateArbFileName, outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, ); generator.loadResources(); generator.writeOutputFiles(testLogger); } on Exception catch (e) { fail('Generating output files should not fail: $e'); } final File baseLocalizationsFile = fs.file( fs.path.join(syntheticL10nPackagePath, 'output-localization-file.dart') ); expect(baseLocalizationsFile.existsSync(), isTrue); final String baseLocalizationsFileContents = fs.file( fs.path.join(syntheticL10nPackagePath, 'output-localization-file.dart') ).readAsStringSync(); expect(baseLocalizationsFileContents, contains('/// Title for the application.')); expect(baseLocalizationsFileContents, contains(r''' /// In en, this message translates to: /// **'Title \n of the application'**''')); }); test('message with placeholders - should generate code comment with description and template message translation', () { final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') ..createSync(recursive: true); l10nDirectory.childFile(defaultTemplateArbFileName) .writeAsStringSync(r''' { "price": "The price of this item is: ${price}", "@price": { "description": "The price of an online shopping cart item.", "placeholders": { "price": { "type": "double", "format": "decimalPattern" } } } }'''); l10nDirectory.childFile(esArbFileName) .writeAsStringSync(r''' { "price": "el precio de este artículo es: ${price}" }'''); final LocalizationsGenerator generator = LocalizationsGenerator(fs); final BufferLogger testLogger = BufferLogger.test(); try { generator.initialize( inputPathString: defaultL10nPathString, outputPathString: defaultL10nPathString, templateArbFileName: defaultTemplateArbFileName, outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, ); generator.loadResources(); generator.writeOutputFiles(testLogger); } on Exception catch (e) { final L10nException exception = e as L10nException; print(exception.message); fail('Generating output files should not fail: $e'); } final File baseLocalizationsFile = fs.file( fs.path.join(syntheticL10nPackagePath, 'output-localization-file.dart') ); expect(baseLocalizationsFile.existsSync(), isTrue); final String baseLocalizationsFileContents = fs.file( fs.path.join(syntheticL10nPackagePath, 'output-localization-file.dart') ).readAsStringSync(); expect(baseLocalizationsFileContents, contains('/// The price of an online shopping cart item.')); expect(baseLocalizationsFileContents, contains(r''' /// In en, this message translates to: /// **'The price of this item is: \${price}'**''')); }); test('should generate a file per language', () { const String singleEnCaMessageArbFileString = ''' { "title": "Canadian Title" }'''; fs.currentDirectory.childDirectory('lib').childDirectory('l10n')..createSync(recursive: true) ..childFile(defaultTemplateArbFileName).writeAsStringSync(singleMessageArbFileString) ..childFile('app_en_CA.arb').writeAsStringSync(singleEnCaMessageArbFileString); final LocalizationsGenerator generator = LocalizationsGenerator(fs); try { generator.initialize( inputPathString: defaultL10nPathString, outputPathString: defaultL10nPathString, templateArbFileName: defaultTemplateArbFileName, outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, ); generator.loadResources(); generator.writeOutputFiles(BufferLogger.test()); } on Exception catch (e) { fail('Generating output files should not fail: $e'); } expect(fs.isFileSync(fs.path.join(syntheticL10nPackagePath, 'output-localization-file_en.dart')), true); expect(fs.isFileSync(fs.path.join(syntheticL10nPackagePath, 'output-localization-file_en_US.dart')), false); final String englishLocalizationsFile = fs.file( fs.path.join(syntheticL10nPackagePath, 'output-localization-file_en.dart') ).readAsStringSync(); expect(englishLocalizationsFile, contains('class AppLocalizationsEnCa extends AppLocalizationsEn')); expect(englishLocalizationsFile, contains('class AppLocalizationsEn extends AppLocalizations')); }); test('language imports are sorted when preferredSupportedLocaleString is given', () { fs.currentDirectory.childDirectory('lib').childDirectory('l10n')..createSync(recursive: true) ..childFile(defaultTemplateArbFileName).writeAsStringSync(singleMessageArbFileString) ..childFile('app_zh.arb').writeAsStringSync(singleZhMessageArbFileString) ..childFile('app_es.arb').writeAsStringSync(singleEsMessageArbFileString); const List<String> preferredSupportedLocale = <String>['zh']; final LocalizationsGenerator generator = LocalizationsGenerator(fs); try { generator.initialize( inputPathString: defaultL10nPathString, outputPathString: defaultL10nPathString, templateArbFileName: defaultTemplateArbFileName, outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, preferredSupportedLocales: preferredSupportedLocale, ); generator.loadResources(); generator.writeOutputFiles(BufferLogger.test()); } on Exception catch (e) { fail('Generating output files should not fail: $e'); } final String localizationsFile = fs.file( fs.path.join(syntheticL10nPackagePath, defaultOutputFileString), ).readAsStringSync(); expect(localizationsFile, contains( ''' import 'output-localization-file_en.dart'; import 'output-localization-file_es.dart'; import 'output-localization-file_zh.dart'; ''')); }); test('imports are deferred and loaded when useDeferredImports are set', () { fs.currentDirectory.childDirectory('lib').childDirectory('l10n')..createSync(recursive: true) ..childFile(defaultTemplateArbFileName).writeAsStringSync(singleMessageArbFileString); final LocalizationsGenerator generator = LocalizationsGenerator(fs); try { generator.initialize( inputPathString: defaultL10nPathString, templateArbFileName: defaultTemplateArbFileName, outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, useDeferredLoading: true, ); generator.loadResources(); generator.writeOutputFiles(BufferLogger.test()); } on Exception catch (e) { fail('Generating output files should not fail: $e'); } final String localizationsFile = fs.file( fs.path.join(syntheticL10nPackagePath, defaultOutputFileString), ).readAsStringSync(); expect(localizationsFile, contains( ''' import 'output-localization-file_en.dart' deferred as output-localization-file_en; ''')); expect(localizationsFile, contains('output-localization-file_en.loadLibrary()')); }); group('DateTime tests', () { test('throws an exception when improperly formatted date is passed in', () { const String singleDateMessageArbFileString = ''' { "@@locale": "en", "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( inputPathString: defaultL10nPathString, templateArbFileName: defaultTemplateArbFileName, outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, ); generator.loadResources(); generator.writeOutputFiles(BufferLogger.test()); } 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( inputPathString: defaultL10nPathString, outputPathString: defaultL10nPathString, templateArbFileName: defaultTemplateArbFileName, outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, ); generator.loadResources(); generator.writeOutputFiles(BufferLogger.test()); } 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('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": "double", "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( inputPathString: defaultL10nPathString, outputPathString: defaultL10nPathString, templateArbFileName: defaultTemplateArbFileName, outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, ); generator.loadResources(); generator.writeOutputFiles(BufferLogger.test()); } 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('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( inputPathString: defaultL10nPathString, outputPathString: defaultL10nPathString, templateArbFileName: defaultTemplateArbFileName, outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, ); generator.loadResources(); generator.writeOutputFiles(BufferLogger.test()); } 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 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.", "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( inputPathString: defaultL10nPathString, outputPathString: defaultL10nPathString, templateArbFileName: defaultTemplateArbFileName, outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, ); generator.loadResources(); generator.writeOutputFiles(BufferLogger.test()); } 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( inputPathString: defaultL10nPathString, outputPathString: defaultL10nPathString, templateArbFileName: defaultTemplateArbFileName, outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, ); generator.loadResources(); generator.writeOutputFiles(BufferLogger.test()); } on L10nException catch (e) { expect(e.message, contains('Resource attribute "@helloWorlds" was not found')); return; } fail('Generating plural class method without resource attributes should not succeed'); }); 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 LocalizationsGenerator generator = LocalizationsGenerator(fs); try { generator.initialize( inputPathString: defaultL10nPathString, outputPathString: defaultL10nPathString, templateArbFileName: defaultTemplateArbFileName, outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, ); generator.loadResources(); generator.writeOutputFiles(BufferLogger.test()); } on L10nException catch (e) { expect(e.message, contains('is not properly formatted')); expect(e.message, contains('Ensure that it is a map with string valued keys')); return; } fail('Generating class methods with incorrect placeholder format should not succeed'); }); }); test( 'should throw with descriptive error message 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( inputPathString: defaultL10nPathString, outputPathString: defaultL10nPathString, templateArbFileName: defaultTemplateArbFileName, outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, ); generator.loadResources(); generator.writeOutputFiles(BufferLogger.test()); } on L10nException catch (e) { expect(e.message, contains('app_en.arb')); expect(e.message, contains('FormatException')); expect(e.message, contains('Unexpected character')); return; } fail( 'should fail with an L10nException due to a trailing comma in the ' 'arb file.' ); }, ); test('should throw when resource is missing resource attribute (isResourceAttributeRequired = true)', () { 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( inputPathString: defaultL10nPathString, outputPathString: defaultL10nPathString, templateArbFileName: defaultTemplateArbFileName, outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, areResourceAttributesRequired: true, ); generator.loadResources(); generator.writeOutputFiles(BufferLogger.test()); } 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( inputPathString: defaultL10nPathString, outputPathString: defaultL10nPathString, templateArbFileName: defaultTemplateArbFileName, outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, ); generator.loadResources(); generator.writeOutputFiles(BufferLogger.test()); } on L10nException catch (e) { expect(e.message, contains('Invalid ARB resource name')); 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( inputPathString: defaultL10nPathString, outputPathString: defaultL10nPathString, templateArbFileName: defaultTemplateArbFileName, outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, ); generator.loadResources(); generator.writeOutputFiles(BufferLogger.test()); } on L10nException catch (e) { expect(e.message, contains('Invalid ARB resource name')); 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( inputPathString: defaultL10nPathString, templateArbFileName: defaultTemplateArbFileName, outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, ); generator.loadResources(); generator.writeOutputFiles(BufferLogger.test()); } on L10nException catch (e) { expect(e.message, contains('Invalid ARB resource name')); return; } fail('should fail since key starts with a number.'); }); }); }); test('should generate a valid pubspec.yaml file when using synthetic package if it does not already exist', () { _standardFlutterDirectoryL10nSetup(fs); LocalizationsGenerator generator; try { generator = LocalizationsGenerator(fs); generator ..initialize( inputPathString: defaultL10nPathString, templateArbFileName: defaultTemplateArbFileName, outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, ) ..loadResources() ..writeOutputFiles(BufferLogger.test()); } on L10nException catch (e) { fail('Generating output should not fail: \n${e.message}'); } final Directory outputDirectory = fs.directory(syntheticPackagePath); final File pubspecFile = outputDirectory.childFile('pubspec.yaml'); expect(pubspecFile.existsSync(), isTrue); final YamlNode yamlNode = loadYamlNode(pubspecFile.readAsStringSync()); expect(yamlNode, isA<YamlMap>()); final YamlMap yamlMap = yamlNode as YamlMap; final String pubspecName = yamlMap['name'] as String; final String pubspecDescription = yamlMap['description'] as String; expect(pubspecName, 'synthetic_package'); expect(pubspecDescription, "The Flutter application's synthetic package."); }); test('should not overwrite existing pubspec.yaml file when using synthetic package', () { _standardFlutterDirectoryL10nSetup(fs); final File pubspecFile = fs.file(fs.path.join(syntheticPackagePath, 'pubspec.yaml')) ..createSync(recursive: true) ..writeAsStringSync('abcd'); LocalizationsGenerator generator; try { generator = LocalizationsGenerator(fs); generator ..initialize( inputPathString: defaultL10nPathString, templateArbFileName: defaultTemplateArbFileName, outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, ) ..loadResources() ..writeOutputFiles(BufferLogger.test()); } on L10nException catch (e) { fail('Generating output should not fail: \n${e.message}'); } // The original pubspec file should not be overwritten. expect(pubspecFile.readAsStringSync(), 'abcd'); }); }