From 4ede7cbd6a328955b3ec2e36a640aa00a64a58ee Mon Sep 17 00:00:00 2001 From: Felix Weuthen Date: Sat, 1 Mar 2025 10:21:10 +0100 Subject: [PATCH 1/6] Bump version to 1.2.0 and add support for Gemini-2.0-flash, Gemini-2.0-flash-lite, and gpt4o-mini. Update README and changelog accordingly. --- CHANGELOG.md | 3 + README.md | 2 +- lib/src/find_untranslated_resource_ids.dart | 8 +- lib/src/translate.dart | 78 +++--- .../translate_arg_parser.dart | 241 +++++++++--------- .../translate_options/translate_options.dart | 66 ++--- .../translate_yaml_parser.dart | 47 ++-- .../chat_gpt_translation_delegate.dart | 21 +- .../gemini_translation_delegate.dart | 81 +++--- .../translate_exception.dart | 3 +- .../translation_delegate.dart | 27 +- lib/src/write_updated_bundle.dart | 15 +- pubspec.lock | 60 ++--- pubspec.yaml | 24 +- 14 files changed, 355 insertions(+), 321 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6849aec..9d87c9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## 1.2.0 +- Added Gemini-2.0-flash, Gemini-2.0-flash-lite and gpt4o-mini Support + ## 1.1.0 - Add custom model support diff --git a/README.md b/README.md index 64a16b5..927e0b5 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ steps: argument `--model-provider: open-ai` 4. (Optional) Select model used for translation. To do it add - `arb-translate-model` to `l10n.yaml` or use command argument `--model`. The available options are `[gemini-1.0-pro (default for Gemini), gemini-1.5-pro, gemini-1.5-flash, gpt-3.5-turbo (default for OpenAI), gpt-4, gpt-4-turbo, gpt-4o]` + `arb-translate-model` to `l10n.yaml` or use command argument `--model`. The available options are `[gemini-1.0-pro (default for Gemini), gemini-1.5-pro, gemini-1.5-flash, gemini-2.0-flash, gemini-2.0-flash-lite, gpt-3.5-turbo (default for OpenAI), gpt-4, gpt-4-turbo, gpt-4o, gpt-4o-mini]` 5. (Optional) Add context of your application `arb-translate-context: {your-app-context}` eg. "sporting goods store app" diff --git a/lib/src/find_untranslated_resource_ids.dart b/lib/src/find_untranslated_resource_ids.dart index ba43586..7fc8400 100644 --- a/lib/src/find_untranslated_resource_ids.dart +++ b/lib/src/find_untranslated_resource_ids.dart @@ -4,9 +4,11 @@ List findUntranslatedResourceIds( AppResourceBundle bundle, AppResourceBundle templateBundle, ) { - final untranslatedMessageIds = templateBundle.resourceIds.where((id) => - !bundle.resources.containsKey(id) || - (bundle.resources[id] as String).isEmpty); + final untranslatedMessageIds = templateBundle.resourceIds.where( + (id) => + !bundle.resources.containsKey(id) || + (bundle.resources[id] as String).isEmpty, + ); return untranslatedMessageIds.toList(); } diff --git a/lib/src/translate.dart b/lib/src/translate.dart index a909d23..1d40346 100644 --- a/lib/src/translate.dart +++ b/lib/src/translate.dart @@ -17,53 +17,51 @@ import 'package:file/file.dart'; /// the ARB files. /// The [options] parameter contains the translation options, including the /// model provider, API key, context, safety settings, and more. -Future translate( - FileSystem fileSystem, - TranslateOptions options, -) async { +Future translate(FileSystem fileSystem, TranslateOptions options) async { final translationDelegate = switch (options.modelProvider) { ModelProvider.gemini => GeminiTranslationDelegate( - model: options.model, - apiKey: options.apiKey, - batchSize: options.batchSize, - context: options.context, - disableSafety: options.disableSafety, - useEscaping: options.useEscaping, - relaxSyntax: options.relaxSyntax, - ), + model: options.model, + apiKey: options.apiKey, + batchSize: options.batchSize, + context: options.context, + disableSafety: options.disableSafety, + useEscaping: options.useEscaping, + relaxSyntax: options.relaxSyntax, + ), ModelProvider.vertexAi => GeminiTranslationDelegate.vertexAi( - model: options.model, - apiKey: options.apiKey, - projectUrl: options.vertexAiProjectUrl!, - batchSize: options.batchSize, - context: options.context, - disableSafety: options.disableSafety, - useEscaping: options.useEscaping, - relaxSyntax: options.relaxSyntax, - ), + model: options.model, + apiKey: options.apiKey, + projectUrl: options.vertexAiProjectUrl!, + batchSize: options.batchSize, + context: options.context, + disableSafety: options.disableSafety, + useEscaping: options.useEscaping, + relaxSyntax: options.relaxSyntax, + ), ModelProvider.openAi => ChatGptTranslationDelegate( - model: options.model, - apiKey: options.apiKey, - batchSize: options.batchSize, - context: options.context, - useEscaping: options.useEscaping, - relaxSyntax: options.relaxSyntax, - ), + model: options.model, + apiKey: options.apiKey, + batchSize: options.batchSize, + context: options.context, + useEscaping: options.useEscaping, + relaxSyntax: options.relaxSyntax, + ), ModelProvider.customOpenAiCompatible => ChatGptTranslationDelegate.custom( - model: options.customModel!, - apiKey: options.apiKey, - baseUrl: options.customModelProviderBaseUrl!, - batchSize: options.batchSize, - context: options.context, - useEscaping: options.useEscaping, - relaxSyntax: options.relaxSyntax, - ), + model: options.customModel!, + apiKey: options.apiKey, + baseUrl: options.customModelProviderBaseUrl!, + batchSize: options.batchSize, + context: options.context, + useEscaping: options.useEscaping, + relaxSyntax: options.relaxSyntax, + ), }; final bundles = AppResourceBundleCollection(fileSystem.directory(options.arbDir)).bundles; final templateBundle = bundles.firstWhere( - (bundle) => bundle.file.path.endsWith(options.templateArbFile)); + (bundle) => bundle.file.path.endsWith(options.templateArbFile), + ); for (final bundle in bundles.where((bundle) => bundle != templateBundle)) { if (options.excludeLocales?.contains(bundle.locale.toString()) ?? false) { @@ -85,8 +83,10 @@ Future _translateBundle({ required AppResourceBundle templateBundle, required AppResourceBundle bundle, }) async { - final untranslatedResourceIds = - findUntranslatedResourceIds(bundle, templateBundle); + final untranslatedResourceIds = findUntranslatedResourceIds( + bundle, + templateBundle, + ); if (untranslatedResourceIds.isEmpty) { print('No terms to translate for locale ${bundle.locale}'); diff --git a/lib/src/translate_options/translate_arg_parser.dart b/lib/src/translate_options/translate_arg_parser.dart index 6de69c4..e2c5ba0 100644 --- a/lib/src/translate_options/translate_arg_parser.dart +++ b/lib/src/translate_options/translate_arg_parser.dart @@ -88,100 +88,105 @@ class TranslateArgParser { static const _excludeLocalesKey = 'exclude-locales'; static const _batchSizeKey = 'batch-size'; - final _parser = ArgParser( - usageLineLength: stdout.hasTerminal ? stdout.terminalColumns : null) - ..addFlag( - _helpKey, - abbr: 'h', - help: 'Print this usage information.', - negatable: false, - ) - ..addOption( - _modelProviderKey, - help: 'The model provider to use for translation.', - allowed: ModelProvider.values.map((provider) => provider.key), - defaultsTo: ModelProvider.gemini.key, - allowedHelp: { - for (final model in Model.values) model.key: model.name, - }, - ) - ..addOption( - _modelKey, - help: 'The model to use for translation.', - allowed: Model.values.map((model) => model.key), - defaultsTo: Model.gemini10Pro.key, - allowedHelp: { - for (final model in Model.values) - model.key: - '${model.name} (${model.providers.map((provider) => provider.name).join(', ')})', - }, - ) - ..addOption( - _customModelKey, - help: 'The model to use for translation for custom Open AI compatible ' - 'model provider.', - ) - ..addOption( - _apiKeyKey, - help: 'API key used to make translation requests.', - ) - ..addOption( - _vertexAiProjectUrlKey, - help: 'The URL of the Vertex AI project to use for translation.', - ) - ..addOption( - _customModelProviderBaseUrlKey, - help: 'The base URL of the custom model provider.', - ) - ..addFlag( - _disableSafetyKey, - help: - 'Whether or not to disable content safety settings for translation.', - ) - ..addOption( - _contextKey, - help: 'The context to use for translation.', - ) - ..addMultiOption( - _excludeLocalesKey, - help: 'Comma separated list of locales to be excluded from translation.', - ) - ..addOption( - _batchSizeKey, - help: - 'The target number of characters of messages to send to a model in a ' - 'single batch. The actual number can be higher if a single message ' - 'is too large.', - defaultsTo: '4096', - ) - ..addSeparator('ARB options:') - ..addOption( - TranslateOptions.arbDirKey, - help: 'The directory where the template and translated arb files are ' - 'located.', - ) - ..addOption( - TranslateOptions.templateArbFileKey, - help: 'The template arb file that will be used as the basis for ' - 'translation.', - ) - ..addFlag( - TranslateOptions.useEscapingKey, - help: 'Whether or not to use escaping for messages.\n' - '\n' - 'By default, this value is set to false for backwards compatibility. ' - 'Turning this flag on will cause the parser to treat any special ' - 'characters contained within pairs of single quotes as normal ' - 'strings and treat all consecutive pairs of single quotes as a ' - 'single quote character.', - ) - ..addFlag( - TranslateOptions.relaxSyntaxKey, - help: 'When specified, the syntax will be relaxed so that the special ' - 'character "{" is treated as a string if it is not followed by a ' - 'valid placeholder and "}" is treated as a string if it does not ' - 'close any previous "{" that is treated as a special character.', - ); + final _parser = + ArgParser( + usageLineLength: stdout.hasTerminal ? stdout.terminalColumns : null, + ) + ..addFlag( + _helpKey, + abbr: 'h', + help: 'Print this usage information.', + negatable: false, + ) + ..addOption( + _modelProviderKey, + help: 'The model provider to use for translation.', + allowed: ModelProvider.values.map((provider) => provider.key), + defaultsTo: ModelProvider.gemini.key, + allowedHelp: { + for (final model in Model.values) model.key: model.name, + }, + ) + ..addOption( + _modelKey, + help: 'The model to use for translation.', + allowed: Model.values.map((model) => model.key), + defaultsTo: Model.gemini10Pro.key, + allowedHelp: { + for (final model in Model.values) + model.key: + '${model.name} (${model.providers.map((provider) => provider.name).join(', ')})', + }, + ) + ..addOption( + _customModelKey, + help: + 'The model to use for translation for custom Open AI compatible ' + 'model provider.', + ) + ..addOption( + _apiKeyKey, + help: 'API key used to make translation requests.', + ) + ..addOption( + _vertexAiProjectUrlKey, + help: 'The URL of the Vertex AI project to use for translation.', + ) + ..addOption( + _customModelProviderBaseUrlKey, + help: 'The base URL of the custom model provider.', + ) + ..addFlag( + _disableSafetyKey, + help: + 'Whether or not to disable content safety settings for translation.', + ) + ..addOption(_contextKey, help: 'The context to use for translation.') + ..addMultiOption( + _excludeLocalesKey, + help: + 'Comma separated list of locales to be excluded from translation.', + ) + ..addOption( + _batchSizeKey, + help: + 'The target number of characters of messages to send to a model in a ' + 'single batch. The actual number can be higher if a single message ' + 'is too large.', + defaultsTo: '4096', + ) + ..addSeparator('ARB options:') + ..addOption( + TranslateOptions.arbDirKey, + help: + 'The directory where the template and translated arb files are ' + 'located.', + ) + ..addOption( + TranslateOptions.templateArbFileKey, + help: + 'The template arb file that will be used as the basis for ' + 'translation.', + ) + ..addFlag( + TranslateOptions.useEscapingKey, + help: + 'Whether or not to use escaping for messages.\n' + '\n' + 'By default, this value is set to false for backwards compatibility. ' + 'Turning this flag on will cause the parser to treat any special ' + 'characters contained within pairs of single quotes as normal ' + 'strings and treat all consecutive pairs of single quotes as a ' + 'single quote character.', + ) + ..addFlag( + TranslateOptions.relaxSyntaxKey, + help: + 'When specified, the syntax will be relaxed so that the special ' + 'character "{" is treated as a string if it is not followed by a ' + 'valid placeholder and "}" is treated as a string if it does not ' + 'close any previous "{" that is treated as a special character.', + ); String get usage => _parser.usage; @@ -198,22 +203,26 @@ class TranslateArgParser { ); } - final modelProvider = rawResults.wasParsed(_modelProviderKey) - ? ModelProvider.values.firstWhereOrNull( - (provider) => provider.key == rawResults[_modelProviderKey], - ) - : null; - final model = rawResults.wasParsed(_modelKey) - ? Model.values.firstWhereOrNull( - (model) => model.key == rawResults[_modelKey], - ) - : null; - final excludeLocales = rawResults.wasParsed(_excludeLocalesKey) - ? rawResults[_excludeLocalesKey] as List - : null; - final batchSize = rawResults.wasParsed(_batchSizeKey) - ? int.parse(rawResults[_batchSizeKey] as String) - : null; + final modelProvider = + rawResults.wasParsed(_modelProviderKey) + ? ModelProvider.values.firstWhereOrNull( + (provider) => provider.key == rawResults[_modelProviderKey], + ) + : null; + final model = + rawResults.wasParsed(_modelKey) + ? Model.values.firstWhereOrNull( + (model) => model.key == rawResults[_modelKey], + ) + : null; + final excludeLocales = + rawResults.wasParsed(_excludeLocalesKey) + ? rawResults[_excludeLocalesKey] as List + : null; + final batchSize = + rawResults.wasParsed(_batchSizeKey) + ? int.parse(rawResults[_batchSizeKey] as String) + : null; return TranslateArgResults( help: _getBoolIfParsed(rawResults, _helpKey), @@ -231,10 +240,14 @@ class TranslateArgParser { arbDir: rawResults[TranslateOptions.arbDirKey] as String?, templateArbFile: rawResults[TranslateOptions.templateArbFileKey] as String?, - useEscaping: - _getBoolIfParsed(rawResults, TranslateOptions.useEscapingKey), - relaxSyntax: - _getBoolIfParsed(rawResults, TranslateOptions.relaxSyntaxKey), + useEscaping: _getBoolIfParsed( + rawResults, + TranslateOptions.useEscapingKey, + ), + relaxSyntax: _getBoolIfParsed( + rawResults, + TranslateOptions.relaxSyntaxKey, + ), ); } diff --git a/lib/src/translate_options/translate_options.dart b/lib/src/translate_options/translate_options.dart index ebc1a3b..657e036 100644 --- a/lib/src/translate_options/translate_options.dart +++ b/lib/src/translate_options/translate_options.dart @@ -9,10 +9,7 @@ enum ModelProvider { gemini('gemini', 'Gemini'), vertexAi('vertex-ai', 'Vertex AI'), openAi('open-ai', 'Open AI'), - customOpenAiCompatible( - 'custom', - 'Custom Open AI compatible', - ); + customOpenAiCompatible('custom', 'Custom Open AI compatible'); const ModelProvider(this.key, this.name); @@ -25,34 +22,40 @@ enum Model { gemini10Pro('gemini-1.0-pro', 'Gemini 1.0 Pro'), gemini15Pro('gemini-1.5-pro', 'Gemini 1.5 Pro'), gemini15Flash('gemini-1.5-flash', 'Gemini 1.5 Flash'), + gemini20Flash('gemini-2.0-flash', 'Gemini 2.0 Flash'), + gemini20FlashLite('gemini-2.0-flash-lite', 'Gemini 2.0 Flash-Lite'), gpt35Turbo('gpt-3.5-turbo', 'GPT-3.5 Turbo'), gpt4('gpt-4', 'GPT-4'), gpt4Turbo('gpt-4-turbo', 'GPT-4 Turbo'), - gpt4O('gpt-4o', 'GPT-4o'); + gpt4O('gpt-4o', 'GPT-4o'), + gpt4OMini('gpt-4o-mini', 'GPT-4o mini'); const Model(this.key, this.name); final String key; final String name; - List get providers => geminiModels.contains(this) - ? [ModelProvider.gemini, ModelProvider.vertexAi] - : [ModelProvider.openAi]; + List get providers => + geminiModels.contains(this) + ? [ModelProvider.gemini, ModelProvider.vertexAi] + : [ModelProvider.openAi]; /// Returns a set of Gemini models. static Set get geminiModels => { - Model.gemini10Pro, - Model.gemini15Pro, - Model.gemini15Flash, - }; + Model.gemini10Pro, + Model.gemini15Pro, + Model.gemini15Flash, + Model.gemini20Flash, + Model.gemini20FlashLite, + }; /// Returns a set of GPT models. static Set get gptModels => { - Model.gpt35Turbo, - Model.gpt4, - Model.gpt4Turbo, - Model.gpt4O, - }; + Model.gpt35Turbo, + Model.gpt4, + Model.gpt4Turbo, + Model.gpt4O, + }; } /// Class representing the options for translation. @@ -72,10 +75,10 @@ class TranslateOptions { required this.batchSize, required bool? useEscaping, required bool? relaxSyntax, - }) : disableSafety = disableSafety ?? false, - templateArbFile = templateArbFile ?? 'app_en.arb', - useEscaping = useEscaping ?? false, - relaxSyntax = relaxSyntax ?? false; + }) : disableSafety = disableSafety ?? false, + templateArbFile = templateArbFile ?? 'app_en.arb', + useEscaping = useEscaping ?? false, + relaxSyntax = relaxSyntax ?? false; static const arbDirKey = 'arb-dir'; static const templateArbFileKey = 'template-arb-file'; @@ -106,7 +109,8 @@ class TranslateOptions { TranslateArgResults argResults, TranslateYamlResults yamlResults, ) { - final apiKey = argResults.apiKey ?? + final apiKey = + argResults.apiKey ?? yamlResults.apiKey ?? Platform.environment['ARB_TRANSLATE_API_KEY']; @@ -114,11 +118,13 @@ class TranslateOptions { throw MissingApiKeyException(); } - final modelProvider = argResults.modelProvider ?? + final modelProvider = + argResults.modelProvider ?? yamlResults.modelProvider ?? ModelProvider.gemini; - final model = argResults.model ?? + final model = + argResults.model ?? yamlResults.model ?? (modelProvider == ModelProvider.openAi ? Model.gpt35Turbo @@ -142,9 +148,10 @@ class TranslateOptions { final vertexAiProjectUrlString = argResults.vertexAiProjectUrl ?? yamlResults.vertexAiProjectUrl; - final Uri? vertexAiProjectUrl = vertexAiProjectUrlString != null - ? Uri.tryParse(vertexAiProjectUrlString) - : null; + final Uri? vertexAiProjectUrl = + vertexAiProjectUrlString != null + ? Uri.tryParse(vertexAiProjectUrlString) + : null; if (modelProvider == ModelProvider.vertexAi) { if (vertexAiProjectUrlString == null) { @@ -160,7 +167,7 @@ class TranslateOptions { final customModelProviderBaseUrlString = argResults.customModelProviderBaseUrl ?? - yamlResults.customModelProviderBaseUrl; + yamlResults.customModelProviderBaseUrl; final Uri? customModelProviderBaseUrl = customModelProviderBaseUrlString != null ? Uri.tryParse(customModelProviderBaseUrlString) @@ -191,7 +198,8 @@ class TranslateOptions { vertexAiProjectUrl: vertexAiProjectUrl, disableSafety: argResults.disableSafety ?? yamlResults.disableSafety, context: context, - arbDir: argResults.arbDir ?? + arbDir: + argResults.arbDir ?? yamlResults.arbDir ?? fileSystem.path.join('lib', 'l10n'), templateArbFile: diff --git a/lib/src/translate_options/translate_yaml_parser.dart b/lib/src/translate_options/translate_yaml_parser.dart index d134601..8a0e7a3 100644 --- a/lib/src/translate_options/translate_yaml_parser.dart +++ b/lib/src/translate_options/translate_yaml_parser.dart @@ -23,20 +23,20 @@ class TranslateYamlResults { /// Creates an empty instance of [TranslateYamlResults]. const TranslateYamlResults.empty() - : modelProvider = null, - customModelProviderBaseUrl = null, - model = null, - customModel = null, - apiKey = null, - vertexAiProjectUrl = null, - disableSafety = null, - context = null, - excludeLocales = null, - batchSize = null, - arbDir = null, - templateArbFile = null, - useEscaping = null, - relaxSyntax = null; + : modelProvider = null, + customModelProviderBaseUrl = null, + model = null, + customModel = null, + apiKey = null, + vertexAiProjectUrl = null, + disableSafety = null, + context = null, + excludeLocales = null, + batchSize = null, + arbDir = null, + templateArbFile = null, + useEscaping = null, + relaxSyntax = null; /// The model provider for translation. final ModelProvider? modelProvider; @@ -149,9 +149,11 @@ class TranslateYamlParser { return ModelProvider.values.firstWhere( (provider) => provider.key == value, - orElse: () => throw FormatException( - 'Expected "$key" to be equal to one of (${ModelProvider.values.map((provider) => provider.key).join(', ')}), instead was "$value"', - ), + orElse: + () => + throw FormatException( + 'Expected "$key" to be equal to one of (${ModelProvider.values.map((provider) => provider.key).join(', ')}), instead was "$value"', + ), ); } @@ -164,9 +166,11 @@ class TranslateYamlParser { return Model.values.firstWhere( (model) => model.key == value, - orElse: () => throw FormatException( - 'Expected "$key" to be equal to one of (${Model.values.map((model) => model.key).join(', ')}), instead was "$value"', - ), + orElse: + () => + throw FormatException( + 'Expected "$key" to be equal to one of (${Model.values.map((model) => model.key).join(', ')}), instead was "$value"', + ), ); } @@ -252,6 +256,7 @@ class TranslateYamlParser { } throw FormatException( - 'Expected "$key" to have a String or List value, instead was "$value"'); + 'Expected "$key" to have a String or List value, instead was "$value"', + ); } } diff --git a/lib/src/translation_delegates/chat_gpt_translation_delegate.dart b/lib/src/translation_delegates/chat_gpt_translation_delegate.dart index 6dff78d..9d15ddf 100644 --- a/lib/src/translation_delegates/chat_gpt_translation_delegate.dart +++ b/lib/src/translation_delegates/chat_gpt_translation_delegate.dart @@ -57,25 +57,20 @@ class ChatGptTranslationDelegate extends TranslationDelegate { '"$locale". Add other ICU plural forms according to CLDR rules if ' 'necessary. Return only raw JSON.\n\n' '$encodedResources', - ) + ), ], role: OpenAIChatMessageRole.user, ), ]; try { - final response = (await OpenAI.instance.chat.create( - model: _model, - responseFormat: - _model != Model.gpt4.key ? {"type": "json_object"} : null, - messages: prompt, - )) - .choices - .first - .message - .content! - .first - .text; + final response = + (await OpenAI.instance.chat.create( + model: _model, + responseFormat: + _model != Model.gpt4.key ? {"type": "json_object"} : null, + messages: prompt, + )).choices.first.message.content!.first.text; if (response == null) { throw NoResponseException(); diff --git a/lib/src/translation_delegates/gemini_translation_delegate.dart b/lib/src/translation_delegates/gemini_translation_delegate.dart index c5d1cda..473d3f7 100644 --- a/lib/src/translation_delegates/gemini_translation_delegate.dart +++ b/lib/src/translation_delegates/gemini_translation_delegate.dart @@ -17,14 +17,16 @@ class GeminiTranslationDelegate extends TranslationDelegate { required super.useEscaping, required super.relaxSyntax, }) : _model = GenerativeModel( - model: switch (model) { - Model.gemini10Pro => Model.gemini10Pro.key, - Model.gemini15Pro || Model.gemini15Flash => '${model.key}-latest', - _ => throw ArgumentError.value(model), - }, - apiKey: apiKey, - safetySettings: disableSafety ? _disabledSafetySettings : [], - ); + model: switch (model) { + Model.gemini10Pro => Model.gemini10Pro.key, + Model.gemini15Pro || Model.gemini15Flash => '${model.key}-latest', + Model.gemini20Flash => Model.gemini20Flash.key, + Model.gemini20FlashLite => Model.gemini20FlashLite.key, + _ => throw ArgumentError.value(model), + }, + apiKey: apiKey, + safetySettings: disableSafety ? _disabledSafetySettings : [], + ); GeminiTranslationDelegate.vertexAi({ required Model model, @@ -36,17 +38,16 @@ class GeminiTranslationDelegate extends TranslationDelegate { required super.useEscaping, required super.relaxSyntax, }) : _model = GenerativeModel( - model: switch (model) { - Model.gemini10Pro => Model.gemini10Pro.key, - Model.gemini15Pro || - Model.gemini15Flash => - '${model.key}-preview-0514', - _ => throw ArgumentError.value(model), - }, - apiKey: apiKey, - safetySettings: disableSafety ? _disabledSafetySettings : [], - httpClient: VertexHttpClient(projectUrl.toString()), - ); + model: switch (model) { + Model.gemini10Pro => Model.gemini10Pro.key, + Model.gemini15Pro || + Model.gemini15Flash => '${model.key}-preview-0514', + _ => throw ArgumentError.value(model), + }, + apiKey: apiKey, + safetySettings: disableSafety ? _disabledSafetySettings : [], + httpClient: VertexHttpClient(projectUrl.toString()), + ); @override int get maxRetryCount => 5; @@ -55,14 +56,15 @@ class GeminiTranslationDelegate extends TranslationDelegate { @override Duration get queryBackoff => Duration(seconds: 5); - static final _disabledSafetySettings = [ - HarmCategory.harassment, - HarmCategory.hateSpeech, - HarmCategory.sexuallyExplicit, - HarmCategory.dangerousContent, - ] - .map((category) => SafetySetting(category, HarmBlockThreshold.none)) - .toList(); + static final _disabledSafetySettings = + [ + HarmCategory.harassment, + HarmCategory.hateSpeech, + HarmCategory.sexuallyExplicit, + HarmCategory.dangerousContent, + ] + .map((category) => SafetySetting(category, HarmBlockThreshold.none)) + .toList(); final GenerativeModel _model; @@ -98,8 +100,9 @@ class GeminiTranslationDelegate extends TranslationDelegate { } on InvalidApiKey catch (_) { throw InvalidApiKeyException(); } on ServerException catch (e) { - if (e.message - .startsWith('Request had invalid authentication credentials.')) { + if (e.message.startsWith( + 'Request had invalid authentication credentials.', + )) { throw InvalidApiKeyException(); } else if (e.message.startsWith('Quota exceeded') || e.message.startsWith('Resource has been exhausted')) { @@ -135,9 +138,9 @@ class VertexHttpClient extends BaseClient { Object? body, Encoding? encoding, }) async { - if (!url - .toString() - .contains('https://generativelanguage.googleapis.com/v1beta/models')) { + if (!url.toString().contains( + 'https://generativelanguage.googleapis.com/v1beta/models', + )) { return _client.post( url, headers: headers, @@ -147,14 +150,18 @@ class VertexHttpClient extends BaseClient { } final response = await _client.post( - Uri.parse(url.toString().replaceAll( + Uri.parse( + url.toString().replaceAll( 'https://generativelanguage.googleapis.com/v1beta/models', - _projectUrl)), + _projectUrl, + ), + ), headers: { ...Map.fromEntries( - headers?.entries.where((entry) => entry.key != 'x-goog-api-key') ?? - []), - 'Authorization': 'Bearer ${headers?['x-goog-api-key']}' + headers?.entries.where((entry) => entry.key != 'x-goog-api-key') ?? + [], + ), + 'Authorization': 'Bearer ${headers?['x-goog-api-key']}', }, body: body, encoding: encoding, diff --git a/lib/src/translation_delegates/translate_exception.dart b/lib/src/translation_delegates/translate_exception.dart index d54c608..487ca1b 100644 --- a/lib/src/translation_delegates/translate_exception.dart +++ b/lib/src/translation_delegates/translate_exception.dart @@ -9,7 +9,8 @@ class InvalidApiKeyException implements TranslateException { class UnsupportedUserLocationException implements TranslateException { @override - String get message => 'Gemini API is not available in your location. Use ' + String get message => + 'Gemini API is not available in your location. Use ' 'Vertex AI model provider. See the documentation for more information'; } diff --git a/lib/src/translation_delegates/translation_delegate.dart b/lib/src/translation_delegates/translation_delegate.dart index 364becd..bc980fe 100644 --- a/lib/src/translation_delegates/translation_delegate.dart +++ b/lib/src/translation_delegates/translation_delegate.dart @@ -33,16 +33,14 @@ abstract class TranslationDelegate { final results = {}; for (var i = 0; i < batches.length; i += maxParallelQueries) { - final batchResults = await Future.wait( - [ - for (var j = i; j < i + maxParallelQueries && j < batches.length; j++) - _translateBatch( - resources: batches[j], - locale: locale, - batchName: '${j + 1}/${batches.length}', - ), - ], - ); + final batchResults = await Future.wait([ + for (var j = i; j < i + maxParallelQueries && j < batches.length; j++) + _translateBatch( + resources: batches[j], + locale: locale, + batchName: '${j + 1}/${batches.length}', + ), + ]); results.addAll({for (final results in batchResults) ...results}); } @@ -163,7 +161,9 @@ abstract class TranslationDelegate { } final trimmedResponse = response.substring( - response.indexOf('{'), response.lastIndexOf('}') + 1); + response.indexOf('{'), + response.lastIndexOf('}') + 1, + ); Map responseJson; @@ -173,8 +173,9 @@ abstract class TranslationDelegate { return null; } - final messageResources = - resources.keys.where((key) => !key.startsWith('@')); + final messageResources = resources.keys.where( + (key) => !key.startsWith('@'), + ); if (messageResources.any((key) => responseJson[key] is! String)) { return null; diff --git a/lib/src/write_updated_bundle.dart b/lib/src/write_updated_bundle.dart index 8f9737e..e73d45e 100644 --- a/lib/src/write_updated_bundle.dart +++ b/lib/src/write_updated_bundle.dart @@ -8,13 +8,12 @@ Future writeUpdatedBundle( Map translationResult, ) { return bundle.file.writeAsString( - JsonEncoder.withIndent(' ').convert( - { - ...Map.fromEntries(bundle.resources.entries - .where((entry) => entry.key.startsWith('@@'))), - for (final id in templateBundle.resourceIds) - id: translationResult[id] ?? bundle.resources[id] as String - }, - ), + JsonEncoder.withIndent(' ').convert({ + ...Map.fromEntries( + bundle.resources.entries.where((entry) => entry.key.startsWith('@@')), + ), + for (final id in templateBundle.resourceIds) + id: translationResult[id] ?? bundle.resources[id] as String, + }), ); } diff --git a/pubspec.lock b/pubspec.lock index 0e76dd1..ef0f033 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,31 +5,31 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "45cfa8471b89fb6643fe9bf51bd7931a76b8f5ec2d65de4fb176dba8d4f22c77" + sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab" url: "https://pub.dev" source: hosted - version: "73.0.0" + version: "76.0.0" _macros: dependency: transitive description: dart source: sdk - version: "0.3.2" + version: "0.3.3" analyzer: dependency: transitive description: name: analyzer - sha256: "4959fec185fe70cce007c57e9ab6983101dbe593d2bf8bbfb4453aaec0cf470a" + sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e" url: "https://pub.dev" source: hosted - version: "6.8.0" + version: "6.11.0" args: dependency: "direct main" description: name: args - sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" + sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6 url: "https://pub.dev" source: hosted - version: "2.5.0" + version: "2.6.0" async: dependency: transitive description: @@ -58,10 +58,10 @@ packages: dependency: "direct main" description: name: collection - sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" url: "https://pub.dev" source: hosted - version: "1.19.0" + version: "1.19.1" convert: dependency: transitive description: @@ -114,10 +114,10 @@ packages: dependency: "direct main" description: name: file - sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 url: "https://pub.dev" source: hosted - version: "7.0.0" + version: "7.0.1" frontend_server_client: dependency: transitive description: @@ -146,10 +146,10 @@ packages: dependency: "direct main" description: name: http - sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010 + sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f url: "https://pub.dev" source: hosted - version: "1.2.2" + version: "1.3.0" http_multi_server: dependency: transitive description: @@ -170,10 +170,10 @@ packages: dependency: "direct main" description: name: intl - sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf + sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" url: "https://pub.dev" source: hosted - version: "0.19.0" + version: "0.20.2" io: dependency: transitive description: @@ -194,10 +194,10 @@ packages: dependency: "direct dev" description: name: lints - sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235" + sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 url: "https://pub.dev" source: hosted - version: "4.0.0" + version: "5.1.1" logging: dependency: transitive description: @@ -210,10 +210,10 @@ packages: dependency: transitive description: name: macros - sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536" + sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656" url: "https://pub.dev" source: hosted - version: "0.1.2-main.4" + version: "0.1.3-main.0" matcher: dependency: transitive description: @@ -226,10 +226,10 @@ packages: dependency: "direct main" description: name: meta - sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c url: "https://pub.dev" source: hosted - version: "1.15.0" + version: "1.16.0" mime: dependency: transitive description: @@ -370,26 +370,26 @@ packages: dependency: "direct dev" description: name: test - sha256: "713a8789d62f3233c46b4a90b174737b2c04cb6ae4500f2aa8b1be8f03f5e67f" + sha256: "301b213cd241ca982e9ba50266bd3f5bd1ea33f1455554c5abb85d1be0e2d87e" url: "https://pub.dev" source: hosted - version: "1.25.8" + version: "1.25.15" test_api: dependency: transitive description: name: test_api - sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd url: "https://pub.dev" source: hosted - version: "0.7.3" + version: "0.7.4" test_core: dependency: transitive description: name: test_core - sha256: "12391302411737c176b0b5d6491f466b0dd56d4763e347b6714efbaa74d7953d" + sha256: "84d17c3486c8dfdbe5e12a50c8ae176d15e2a771b96909a9442b40173649ccaa" url: "https://pub.dev" source: hosted - version: "0.6.5" + version: "0.6.8" typed_data: dependency: transitive description: @@ -450,9 +450,9 @@ packages: dependency: "direct main" description: name: yaml - sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "3.1.3" sdks: - dart: ">=3.4.0 <4.0.0" + dart: ">=3.7.0 <4.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index 9776428..886350e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: arb_translate description: >- A command-line tool for automatically generating missing translations to ARB files using Google Gemini or OpenAI ChatGPT by LeanCode. -version: 1.1.0 +version: 1.2.0 repository: https://github.com/leancodepl/arb_translate issue_tracker: https://github.com/leancodepl/arb_translate/issues topics: @@ -16,19 +16,19 @@ executables: arb_translate: environment: - sdk: ^3.2.0 + sdk: ^3.7.0 dependencies: - args: ^2.4.2 - collection: ^1.18.0 + args: ^2.6.0 + collection: ^1.19.1 dart_openai: ^5.1.0 - file: ^7.0.0 - google_generative_ai: ^0.4.0 - http: ^1.2.0 - intl: ^0.19.0 - meta: ^1.14.0 - yaml: ^3.1.2 + file: ^7.0.1 + google_generative_ai: ^0.4.6 + http: ^1.3.0 + intl: ^0.20.2 + meta: ^1.16.0 + yaml: ^3.1.3 dev_dependencies: - lints: ^4.0.0 - test: ^1.24.0 + lints: ^5.1.1 + test: ^1.25.15 From 708a4c3a07ebf4f70a84632549077726c737a696 Mon Sep 17 00:00:00 2001 From: Felix Weuthen Date: Sat, 1 Mar 2025 10:23:06 +0100 Subject: [PATCH 2/6] Update default translation model to Gemini 2.0 Flash and remove Gemini 1.0 Pro references --- .../translate_arg_parser.dart | 2 +- .../translate_options/translate_options.dart | 4 +- .../gemini_translation_delegate.dart | 6 +- test/translation_options_test.dart | 263 +++++++++--------- 4 files changed, 128 insertions(+), 147 deletions(-) diff --git a/lib/src/translate_options/translate_arg_parser.dart b/lib/src/translate_options/translate_arg_parser.dart index e2c5ba0..53a9cc6 100644 --- a/lib/src/translate_options/translate_arg_parser.dart +++ b/lib/src/translate_options/translate_arg_parser.dart @@ -111,7 +111,7 @@ class TranslateArgParser { _modelKey, help: 'The model to use for translation.', allowed: Model.values.map((model) => model.key), - defaultsTo: Model.gemini10Pro.key, + defaultsTo: Model.gemini20Flash.key, allowedHelp: { for (final model in Model.values) model.key: diff --git a/lib/src/translate_options/translate_options.dart b/lib/src/translate_options/translate_options.dart index 657e036..94011fd 100644 --- a/lib/src/translate_options/translate_options.dart +++ b/lib/src/translate_options/translate_options.dart @@ -19,7 +19,6 @@ enum ModelProvider { /// Enum representing the available models. enum Model { - gemini10Pro('gemini-1.0-pro', 'Gemini 1.0 Pro'), gemini15Pro('gemini-1.5-pro', 'Gemini 1.5 Pro'), gemini15Flash('gemini-1.5-flash', 'Gemini 1.5 Flash'), gemini20Flash('gemini-2.0-flash', 'Gemini 2.0 Flash'), @@ -42,7 +41,6 @@ enum Model { /// Returns a set of Gemini models. static Set get geminiModels => { - Model.gemini10Pro, Model.gemini15Pro, Model.gemini15Flash, Model.gemini20Flash, @@ -128,7 +126,7 @@ class TranslateOptions { yamlResults.model ?? (modelProvider == ModelProvider.openAi ? Model.gpt35Turbo - : Model.gemini10Pro); + : Model.gemini20Flash); final customModel = argResults.customModel ?? yamlResults.customModel; if (modelProvider != ModelProvider.customOpenAiCompatible) { diff --git a/lib/src/translation_delegates/gemini_translation_delegate.dart b/lib/src/translation_delegates/gemini_translation_delegate.dart index 473d3f7..ee795fb 100644 --- a/lib/src/translation_delegates/gemini_translation_delegate.dart +++ b/lib/src/translation_delegates/gemini_translation_delegate.dart @@ -18,7 +18,6 @@ class GeminiTranslationDelegate extends TranslationDelegate { required super.relaxSyntax, }) : _model = GenerativeModel( model: switch (model) { - Model.gemini10Pro => Model.gemini10Pro.key, Model.gemini15Pro || Model.gemini15Flash => '${model.key}-latest', Model.gemini20Flash => Model.gemini20Flash.key, Model.gemini20FlashLite => Model.gemini20FlashLite.key, @@ -39,9 +38,8 @@ class GeminiTranslationDelegate extends TranslationDelegate { required super.relaxSyntax, }) : _model = GenerativeModel( model: switch (model) { - Model.gemini10Pro => Model.gemini10Pro.key, - Model.gemini15Pro || - Model.gemini15Flash => '${model.key}-preview-0514', + Model.gemini15Pro => model.key, + Model.gemini15Flash => model.key, _ => throw ArgumentError.value(model), }, apiKey: apiKey, diff --git a/test/translation_options_test.dart b/test/translation_options_test.dart index ef432b1..256d492 100644 --- a/test/translation_options_test.dart +++ b/test/translation_options_test.dart @@ -3,146 +3,131 @@ import 'package:file/memory.dart'; import 'package:test/test.dart'; void main() { - group( - 'TranslateOptions', - () { - test( - 'resolve returns options with values from argResults over yamlResults', - () { - const argResultsModelProvider = ModelProvider.vertexAi; - const argResultsCustomModelProviderUrl = - 'http://argResultsCustomModelProviderBaseUrl'; - const argResultsModel = Model.gemini10Pro; - const argResultsCustomModel = 'argResultsCustomModel'; - const argResultsApiKey = 'argResultsApiKey'; - const argResultsVertexAiProjectUrl = - 'https://argResultsVertexAiProjectUrl/models'; - const argResultsDisableSafety = true; - const argResultsContext = 'argResultsContext'; - const argResultsExcludeLocales = ['pl']; - const argResultsBatchSize = 4096; - const argResultsArbDir = 'argResultsArbDir'; - const argResultsTemplateArbFile = 'argResultsTemplateArbFile'; - const argResultsUseEscaping = true; - const argResultsRelaxSyntax = true; + group('TranslateOptions', () { + test( + 'resolve returns options with values from argResults over yamlResults', + () { + const argResultsModelProvider = ModelProvider.vertexAi; + const argResultsCustomModelProviderUrl = + 'http://argResultsCustomModelProviderBaseUrl'; + const argResultsModel = Model.gemini20Flash; + const argResultsCustomModel = 'argResultsCustomModel'; + const argResultsApiKey = 'argResultsApiKey'; + const argResultsVertexAiProjectUrl = + 'https://argResultsVertexAiProjectUrl/models'; + const argResultsDisableSafety = true; + const argResultsContext = 'argResultsContext'; + const argResultsExcludeLocales = ['pl']; + const argResultsBatchSize = 4096; + const argResultsArbDir = 'argResultsArbDir'; + const argResultsTemplateArbFile = 'argResultsTemplateArbFile'; + const argResultsUseEscaping = true; + const argResultsRelaxSyntax = true; - final argResults = TranslateArgResults( - help: false, - modelProvider: argResultsModelProvider, - customModelProviderBaseUrl: argResultsCustomModelProviderUrl, - model: argResultsModel, - customModel: argResultsCustomModel, - apiKey: argResultsApiKey, - vertexAiProjectUrl: argResultsVertexAiProjectUrl, - disableSafety: argResultsDisableSafety, - context: argResultsContext, - excludeLocales: argResultsExcludeLocales, - batchSize: argResultsBatchSize, - arbDir: argResultsArbDir, - templateArbFile: argResultsTemplateArbFile, - useEscaping: argResultsUseEscaping, - relaxSyntax: argResultsRelaxSyntax, - ); - final yamlResults = TranslateYamlResults( - modelProvider: ModelProvider.gemini, - customModelProviderBaseUrl: - 'http://yamlResultsCustomModelProviderBaseUrl', - model: Model.gpt35Turbo, - customModel: 'yamlResultsCustomModel', - apiKey: 'yamlResultsApiKey', - vertexAiProjectUrl: 'https://yamlResultsVertexAiProjectUrl/models', - disableSafety: !argResultsDisableSafety, - context: 'yamlResultsContext', - excludeLocales: ['en'], - batchSize: 2048, - arbDir: 'yamlResultsArbDir', - templateArbFile: 'yamlResultsTemplateArbFile', - useEscaping: !argResultsUseEscaping, - relaxSyntax: !argResultsRelaxSyntax, - ); + final argResults = TranslateArgResults( + help: false, + modelProvider: argResultsModelProvider, + customModelProviderBaseUrl: argResultsCustomModelProviderUrl, + model: argResultsModel, + customModel: argResultsCustomModel, + apiKey: argResultsApiKey, + vertexAiProjectUrl: argResultsVertexAiProjectUrl, + disableSafety: argResultsDisableSafety, + context: argResultsContext, + excludeLocales: argResultsExcludeLocales, + batchSize: argResultsBatchSize, + arbDir: argResultsArbDir, + templateArbFile: argResultsTemplateArbFile, + useEscaping: argResultsUseEscaping, + relaxSyntax: argResultsRelaxSyntax, + ); + final yamlResults = TranslateYamlResults( + modelProvider: ModelProvider.gemini, + customModelProviderBaseUrl: + 'http://yamlResultsCustomModelProviderBaseUrl', + model: Model.gpt35Turbo, + customModel: 'yamlResultsCustomModel', + apiKey: 'yamlResultsApiKey', + vertexAiProjectUrl: 'https://yamlResultsVertexAiProjectUrl/models', + disableSafety: !argResultsDisableSafety, + context: 'yamlResultsContext', + excludeLocales: ['en'], + batchSize: 2048, + arbDir: 'yamlResultsArbDir', + templateArbFile: 'yamlResultsTemplateArbFile', + useEscaping: !argResultsUseEscaping, + relaxSyntax: !argResultsRelaxSyntax, + ); - final translateOptions = TranslateOptions.resolve( - MemoryFileSystem(), - argResults, - yamlResults, - ); + final translateOptions = TranslateOptions.resolve( + MemoryFileSystem(), + argResults, + yamlResults, + ); - expect( - translateOptions, - isA() - .having( - (options) => options.modelProvider, - 'modelProvider', - argResultsModelProvider, - ) - .having( - (options) => options.customModelProviderBaseUrl, - 'customModelProviderBaseUrl', - Uri.parse(argResultsCustomModelProviderUrl), - ) - .having( - (options) => options.model, - 'model', - argResultsModel, - ) - .having( - (options) => options.customModel, - 'customModel', - argResultsCustomModel, - ) - .having( - (options) => options.apiKey, - 'apiKey', - argResultsApiKey, - ) - .having( - (options) => options.vertexAiProjectUrl, - 'vertexAiProjectUrl', - Uri.parse(argResultsVertexAiProjectUrl), - ) - .having( - (options) => options.disableSafety, - 'disableSafety', - argResultsDisableSafety, - ) - .having( - (options) => options.context, - 'context', - argResultsContext, - ) - .having( - (options) => options.excludeLocales, - 'excludeLocales', - argResultsExcludeLocales, - ) - .having( - (options) => options.batchSize, - 'batchSize', - argResultsBatchSize, - ) - .having( - (options) => options.arbDir, - 'arbDir', - argResultsArbDir, - ) - .having( - (options) => options.templateArbFile, - 'templateArbFile', - argResultsTemplateArbFile, - ) - .having( - (options) => options.useEscaping, - 'useEscaping', - argResultsUseEscaping, - ) - .having( - (options) => options.relaxSyntax, - 'relaxSyntax', - argResultsRelaxSyntax, - ), - ); - }, - ); - }, - ); + expect( + translateOptions, + isA() + .having( + (options) => options.modelProvider, + 'modelProvider', + argResultsModelProvider, + ) + .having( + (options) => options.customModelProviderBaseUrl, + 'customModelProviderBaseUrl', + Uri.parse(argResultsCustomModelProviderUrl), + ) + .having((options) => options.model, 'model', argResultsModel) + .having( + (options) => options.customModel, + 'customModel', + argResultsCustomModel, + ) + .having((options) => options.apiKey, 'apiKey', argResultsApiKey) + .having( + (options) => options.vertexAiProjectUrl, + 'vertexAiProjectUrl', + Uri.parse(argResultsVertexAiProjectUrl), + ) + .having( + (options) => options.disableSafety, + 'disableSafety', + argResultsDisableSafety, + ) + .having( + (options) => options.context, + 'context', + argResultsContext, + ) + .having( + (options) => options.excludeLocales, + 'excludeLocales', + argResultsExcludeLocales, + ) + .having( + (options) => options.batchSize, + 'batchSize', + argResultsBatchSize, + ) + .having((options) => options.arbDir, 'arbDir', argResultsArbDir) + .having( + (options) => options.templateArbFile, + 'templateArbFile', + argResultsTemplateArbFile, + ) + .having( + (options) => options.useEscaping, + 'useEscaping', + argResultsUseEscaping, + ) + .having( + (options) => options.relaxSyntax, + 'relaxSyntax', + argResultsRelaxSyntax, + ), + ); + }, + ); + }); } From 0298f56bb47e1bfd566a114bf657047125b6e354 Mon Sep 17 00:00:00 2001 From: Felix Weuthen Date: Sat, 1 Mar 2025 10:27:27 +0100 Subject: [PATCH 3/6] Update changelog and README for Gemini 2.0 support and deprecate Gemini 1.0 Pro --- CHANGELOG.md | 5 ++++- README.md | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d87c9a..5908b5d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ ## 1.2.0 -- Added Gemini-2.0-flash, Gemini-2.0-flash-lite and gpt4o-mini Support +- Added Gemini-2.0-flash (new default for Gemini), Gemini-2.0-flash-lite and gpt4o-mini Support +- Deprecated Gemini-1.0-pro +- Updated README.md +- Bump dart sdk version to require 3.7 and updated packages ## 1.1.0 - Add custom model support diff --git a/README.md b/README.md index 927e0b5..e3317fd 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ steps: argument `--model-provider: open-ai` 4. (Optional) Select model used for translation. To do it add - `arb-translate-model` to `l10n.yaml` or use command argument `--model`. The available options are `[gemini-1.0-pro (default for Gemini), gemini-1.5-pro, gemini-1.5-flash, gemini-2.0-flash, gemini-2.0-flash-lite, gpt-3.5-turbo (default for OpenAI), gpt-4, gpt-4-turbo, gpt-4o, gpt-4o-mini]` + `arb-translate-model` to `l10n.yaml` or use command argument `--model`. The available options are `[gemini-1.5-pro, gemini-1.5-flash, gemini-2.0-flash (default for Gemini), gemini-2.0-flash-lite, gpt-3.5-turbo (default for OpenAI), gpt-4, gpt-4-turbo, gpt-4o, gpt-4o-mini]` 5. (Optional) Add context of your application `arb-translate-context: {your-app-context}` eg. "sporting goods store app" From b130435fa506dcbdfd0d57c6fae973658415271f Mon Sep 17 00:00:00 2001 From: Felix Weuthen Date: Sat, 1 Mar 2025 10:30:38 +0100 Subject: [PATCH 4/6] Add gpt4OMini model to translation options --- lib/src/translate_options/translate_options.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/src/translate_options/translate_options.dart b/lib/src/translate_options/translate_options.dart index 94011fd..692d2e3 100644 --- a/lib/src/translate_options/translate_options.dart +++ b/lib/src/translate_options/translate_options.dart @@ -53,6 +53,7 @@ enum Model { Model.gpt4, Model.gpt4Turbo, Model.gpt4O, + Model.gpt4OMini, }; } From 61c4ae7a90619a0fbd869bd34a80dc492315dfec Mon Sep 17 00:00:00 2001 From: Julian Pscheid Date: Thu, 10 Jul 2025 15:41:16 -0700 Subject: [PATCH 5/6] Add support for Gemini 2.5 models MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add Gemini 2.5 Pro, Flash, and Flash-Lite models - Update default Gemini model to 2.5 Flash - Update documentation and bump version to 1.3.0 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .gitignore | 3 +++ CHANGELOG.md | 5 +++++ README.md | 2 +- lib/src/translate_options/translate_options.dart | 8 +++++++- pubspec.yaml | 2 +- 5 files changed, 17 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 3a85790..d14d9f6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ # https://dart.dev/guides/libraries/private-files # Created by `dart pub` .dart_tool/ + +# Claude Code configuration +CLAUDE.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 5908b5d..70f12d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 1.3.0 +- Added Gemini-2.5-pro, Gemini-2.5-flash (new default for Gemini), and Gemini-2.5-flash-lite Support +- Updated default Gemini model from Gemini-2.0-flash to Gemini-2.5-flash +- Updated README.md + ## 1.2.0 - Added Gemini-2.0-flash (new default for Gemini), Gemini-2.0-flash-lite and gpt4o-mini Support - Deprecated Gemini-1.0-pro diff --git a/README.md b/README.md index e3317fd..ae04bca 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ steps: argument `--model-provider: open-ai` 4. (Optional) Select model used for translation. To do it add - `arb-translate-model` to `l10n.yaml` or use command argument `--model`. The available options are `[gemini-1.5-pro, gemini-1.5-flash, gemini-2.0-flash (default for Gemini), gemini-2.0-flash-lite, gpt-3.5-turbo (default for OpenAI), gpt-4, gpt-4-turbo, gpt-4o, gpt-4o-mini]` + `arb-translate-model` to `l10n.yaml` or use command argument `--model`. The available options are `[gemini-1.5-pro, gemini-1.5-flash, gemini-2.0-flash, gemini-2.0-flash-lite, gemini-2.5-pro, gemini-2.5-flash (default for Gemini), gemini-2.5-flash-lite, gpt-3.5-turbo (default for OpenAI), gpt-4, gpt-4-turbo, gpt-4o, gpt-4o-mini]` 5. (Optional) Add context of your application `arb-translate-context: {your-app-context}` eg. "sporting goods store app" diff --git a/lib/src/translate_options/translate_options.dart b/lib/src/translate_options/translate_options.dart index 692d2e3..3477a81 100644 --- a/lib/src/translate_options/translate_options.dart +++ b/lib/src/translate_options/translate_options.dart @@ -23,6 +23,9 @@ enum Model { gemini15Flash('gemini-1.5-flash', 'Gemini 1.5 Flash'), gemini20Flash('gemini-2.0-flash', 'Gemini 2.0 Flash'), gemini20FlashLite('gemini-2.0-flash-lite', 'Gemini 2.0 Flash-Lite'), + gemini25Pro('gemini-2.5-pro', 'Gemini 2.5 Pro'), + gemini25Flash('gemini-2.5-flash', 'Gemini 2.5 Flash'), + gemini25FlashLite('gemini-2.5-flash-lite', 'Gemini 2.5 Flash-Lite'), gpt35Turbo('gpt-3.5-turbo', 'GPT-3.5 Turbo'), gpt4('gpt-4', 'GPT-4'), gpt4Turbo('gpt-4-turbo', 'GPT-4 Turbo'), @@ -45,6 +48,9 @@ enum Model { Model.gemini15Flash, Model.gemini20Flash, Model.gemini20FlashLite, + Model.gemini25Pro, + Model.gemini25Flash, + Model.gemini25FlashLite, }; /// Returns a set of GPT models. @@ -127,7 +133,7 @@ class TranslateOptions { yamlResults.model ?? (modelProvider == ModelProvider.openAi ? Model.gpt35Turbo - : Model.gemini20Flash); + : Model.gemini25Flash); final customModel = argResults.customModel ?? yamlResults.customModel; if (modelProvider != ModelProvider.customOpenAiCompatible) { diff --git a/pubspec.yaml b/pubspec.yaml index 886350e..b18ffd4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: arb_translate description: >- A command-line tool for automatically generating missing translations to ARB files using Google Gemini or OpenAI ChatGPT by LeanCode. -version: 1.2.0 +version: 1.3.0 repository: https://github.com/leancodepl/arb_translate issue_tracker: https://github.com/leancodepl/arb_translate/issues topics: From c8c0b31623956e545172e0bdf9ef0242c49e0f7d Mon Sep 17 00:00:00 2001 From: Julian Pscheid Date: Thu, 10 Jul 2025 16:02:37 -0700 Subject: [PATCH 6/6] Fix Gemini 2.5 model support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add Gemini 2.5 models to switch statements in GeminiTranslationDelegate - Update default model in argument parser to Gemini 2.5 Flash - Fix ArgumentError when using new Gemini 2.5 models 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- lib/src/translate_options/translate_arg_parser.dart | 2 +- .../gemini_translation_delegate.dart | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/src/translate_options/translate_arg_parser.dart b/lib/src/translate_options/translate_arg_parser.dart index 53a9cc6..d3bcd4f 100644 --- a/lib/src/translate_options/translate_arg_parser.dart +++ b/lib/src/translate_options/translate_arg_parser.dart @@ -111,7 +111,7 @@ class TranslateArgParser { _modelKey, help: 'The model to use for translation.', allowed: Model.values.map((model) => model.key), - defaultsTo: Model.gemini20Flash.key, + defaultsTo: Model.gemini25Flash.key, allowedHelp: { for (final model in Model.values) model.key: diff --git a/lib/src/translation_delegates/gemini_translation_delegate.dart b/lib/src/translation_delegates/gemini_translation_delegate.dart index ee795fb..05a7a42 100644 --- a/lib/src/translation_delegates/gemini_translation_delegate.dart +++ b/lib/src/translation_delegates/gemini_translation_delegate.dart @@ -21,6 +21,9 @@ class GeminiTranslationDelegate extends TranslationDelegate { Model.gemini15Pro || Model.gemini15Flash => '${model.key}-latest', Model.gemini20Flash => Model.gemini20Flash.key, Model.gemini20FlashLite => Model.gemini20FlashLite.key, + Model.gemini25Pro => Model.gemini25Pro.key, + Model.gemini25Flash => Model.gemini25Flash.key, + Model.gemini25FlashLite => Model.gemini25FlashLite.key, _ => throw ArgumentError.value(model), }, apiKey: apiKey, @@ -40,6 +43,11 @@ class GeminiTranslationDelegate extends TranslationDelegate { model: switch (model) { Model.gemini15Pro => model.key, Model.gemini15Flash => model.key, + Model.gemini20Flash => model.key, + Model.gemini20FlashLite => model.key, + Model.gemini25Pro => model.key, + Model.gemini25Flash => model.key, + Model.gemini25FlashLite => model.key, _ => throw ArgumentError.value(model), }, apiKey: apiKey,