From ed5a5d5dfc2eb0e5c8efe0a00e1869e51c190b0f Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sun, 5 Apr 2026 23:03:51 +0000 Subject: [PATCH 1/6] feat: add 'add' command for programmatically adding entries to ARB files - Implemented `add` command in `lib/src/commands/add.dart`. - Added recursive auto-discovery of `.arb` files. - Updated `mergeARBs` primitive to support pretty-printing with 2-space indentation and trailing newline. - Added `AGENTS.md` with instructions for AI agents. - Updated `README.md` and `CHANGELOG.md`. - Bumped version to 0.11.0 in `pubspec.yaml`. - Added comprehensive tests in `test/cli_test.dart`. Co-authored-by: Rodsevich <1907844+Rodsevich@users.noreply.github.com> --- AGENTS.md | 49 ++++++++++++++++++++++++++ CHANGELOG.md | 7 ++++ README.md | 11 ++++++ bin/arb_utils.dart | 4 ++- lib/src/commands/add.dart | 59 +++++++++++++++++++++++++++++++ lib/src/primitives/merge.dart | 4 ++- pubspec.yaml | 2 +- test/cli_test.dart | 65 +++++++++++++++++++++++++++-------- test/samples/merge-1.arb | 3 +- test/samples/merge-2.arb | 3 +- 10 files changed, 188 insertions(+), 19 deletions(-) create mode 100644 AGENTS.md create mode 100644 lib/src/commands/add.dart diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..30ae508 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,49 @@ +# Information for AI Agents + +This repository provides `arb_utils`, a CLI tool for working with `.arb` files in Flutter. + +## Key Command: `add` + +The `add` command is particularly useful for AI agents to programmatically add new translations or entries to `.arb` files. + +### Usage + +```sh +arb_utils add '' [file-paths...] +``` + +- ``: A valid JSON string containing the keys and values to add. +- `[file-paths...]`: (Optional) One or more paths to `.arb` files. If omitted, the command will recursively find and update all `.arb` files in the current directory and its subdirectories. + +### Examples + +Add a single key to all `.arb` files: +```sh +arb_utils add '{"myNewKey": "My New Value"}' +``` + +Add multiple keys and metadata to specific files: +```sh +arb_utils add '{"welcome": "Welcome!", "@welcome": {"description": "Welcome message"}}' lib/l10n/app_en.arb +``` + +## Other Commands + +- `sort`: Sorts keys in an `.arb` file alphabetically, keeping metadata next to its key. + ```sh + arb_utils sort + ``` +- `generate-meta`: Adds missing metadata placeholders for all keys in an `.arb` file. + ```sh + arb_utils generate-meta + ``` +- `merge`: Merges multiple `.arb` files. + ```sh + arb_utils merge -o + ``` + +## Best Practices for Agents + +1. **Auto-Discovery**: If you don't know the exact paths to `.arb` files, just use `arb_utils add '{"key": "value"}'` and it will find them for you. +2. **Batching**: You can add multiple translations in a single command by providing a larger JSON object. +3. **Consistency**: The `add` command uses pretty-printing (2-space indentation) to maintain a clean file structure. diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b06a79..114339f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # CHANGELOG +## 0.11.0 + +- Added `add` command for programmatically adding entries to .arb files. +- Added recursive auto-discovery for .arb files. +- Added `AGENTS.md` for AI agent instructions. +- Fixed pretty-printing for merge and add operations. + ## 0.10.1 - Adjust dcli dependency to allow >=6.0.0 and <=9.0.0 diff --git a/README.md b/README.md index 7e37a48..03c4491 100644 --- a/README.md +++ b/README.md @@ -319,6 +319,17 @@ arb_utils sort where `` is a path to the input file. +##### Add Entries + +To programmatically add new entries to one or more arb files, use + +```sh +arb_utils add '' [FILES...] +``` + +Where `` is a JSON object containing the keys and values to add. +If no files are specified, the command will recursively find all `.arb` files in the current directory and its subdirectories. + Also, there are 3 flags for different sorting. + --case-insensitive / -i diff --git a/bin/arb_utils.dart b/bin/arb_utils.dart index a7f86c6..82b1fcf 100755 --- a/bin/arb_utils.dart +++ b/bin/arb_utils.dart @@ -1,3 +1,4 @@ +import 'package:arb_utils/src/commands/add.dart'; import 'package:arb_utils/src/commands/generate_meta.dart'; import 'package:arb_utils/src/commands/merge.dart'; import 'package:arb_utils/src/commands/sort.dart'; @@ -8,7 +9,8 @@ void main(List args) async { final runner = CommandRunner('arb_utils', 'A set of utilities for working with arb files.') ..addCommand(GenerateMetaCommand()) ..addCommand(SortCommand()) - ..addCommand(MergeCommand()); + ..addCommand(MergeCommand()) + ..addCommand(AddCommand()); try { await runner.run(args); diff --git a/lib/src/commands/add.dart b/lib/src/commands/add.dart new file mode 100644 index 0000000..6065a41 --- /dev/null +++ b/lib/src/commands/add.dart @@ -0,0 +1,59 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:arb_utils/arb_utils.dart'; +import 'package:args/command_runner.dart'; +import 'package:dcli/dcli.dart'; + +class AddCommand extends Command { + @override + String get name => 'add'; + @override + String get description => + 'Add entries to one or more arb files. If no files are specified, it will look for all .arb files in the current directory and its subdirectories.'; + + @override + String get invocation => '${super.invocation} [arb-file-paths]'; + + @override + FutureOr run() async { + if (argResults!.rest.isEmpty) { + throw UsageException( + 'ERROR! Expected a JSON string to add.', invocation); + } + + final jsonToAdd = argResults!.rest.first; + final List filePaths = []; + + if (argResults!.rest.length > 1) { + filePaths.addAll(argResults!.rest.sublist(1)); + } else { + // Find all .arb files in the current directory and subdirectories + find('*.arb', recursive: true).forEach((file) { + filePaths.add(file); + }); + } + + if (filePaths.isEmpty) { + print(yellow('No .arb files found to update.')); + return; + } + + for (var filePath in filePaths) { + var file = File(filePath); + if (!await file.exists()) { + print(red('ERROR! File does not exist: $filePath.')); + continue; + } + + try { + var arbContent = await file.readAsString(); + var updatedContent = mergeARBs(arbContent, jsonToAdd); + await file.writeAsString(updatedContent); + print(green('Updated $filePath')); + } catch (e) { + print(red('Error updating $filePath: $e')); + } + } + } +} diff --git a/lib/src/primitives/merge.dart b/lib/src/primitives/merge.dart index 3a9f91a..51f52a0 100644 --- a/lib/src/primitives/merge.dart +++ b/lib/src/primitives/merge.dart @@ -11,5 +11,7 @@ String mergeARBs(String arb1Contents, String arb2Contents) { for (var key in json2.keys) { ret[key] = json2[key]; } - return json.encode(ret); + + final encoder = JsonEncoder.withIndent(' '); + return '${encoder.convert(ret)}\n'; } diff --git a/pubspec.yaml b/pubspec.yaml index b3be347..4da6256 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: arb_utils description: A set of tools to work with .arb files (the preferred Dart way of dealing with i18n/l10n/translations) -version: 0.10.1 +version: 0.11.0 repository: https://github.com/Rodsevich/arb_utils homepage: https://gitlab.com/Rodsevich/arb_utils diff --git a/test/cli_test.dart b/test/cli_test.dart index cc9ba4f..80fc110 100644 --- a/test/cli_test.dart +++ b/test/cli_test.dart @@ -1,3 +1,4 @@ +import 'dart:io'; import 'package:dcli/dcli.dart'; import 'package:test/test.dart'; @@ -8,18 +9,16 @@ void main() { var finalContents = ''; var output = []; - void runCommand(String command) { - final initialBuffer = StringBuffer(); - final finalBuffer = StringBuffer(); - read(sampleArbFile).forEach((line) { - initialBuffer.writeln(line); - }); - initialContents = initialBuffer.toString(); - output = command.toList(); - read(sampleArbFile).forEach((line) { - finalBuffer.writeln(line); - }); - finalContents = finalBuffer.toString(); + void runCommand(List args) { + initialContents = File(sampleArbFile).readAsStringSync().trim(); + + final result = Process.runSync('dart', args); + output = result.stdout.toString().split('\n'); + if (result.stderr.toString().isNotEmpty) { + output.addAll(result.stderr.toString().split('\n')); + } + + finalContents = File(sampleArbFile).readAsStringSync().trim(); } setUp(() { @@ -30,14 +29,52 @@ void main() { }); test('fails on unknown command', () async { - runCommand('dart bin/arb_utils.dart unknown'); + runCommand(['bin/arb_utils.dart', 'unknown']); expect( output.first, contains('Could not find a command named "unknown".')); }); test('generates the metadata', () async { - runCommand('dart bin/arb_utils.dart generate-meta $sampleArbFile'); + runCommand(['bin/arb_utils.dart', 'generate-meta', sampleArbFile]); expect(initialContents, isNot(contains('"@noMetadataKey": {}'))); expect(finalContents, contains('"@noMetadataKey": {}')); }); + test('adds a key to a specific file', () async { + // Ensure we use the backup to have a clean state, although setUp does it + copy('$sampleArbFile.backup', sampleArbFile, overwrite: true); + + runCommand([ + 'bin/arb_utils.dart', + 'add', + '{"new_key":"new_value"}', + sampleArbFile + ]); + expect(initialContents, isNot(contains('"new_key": "new_value"'))); + expect(finalContents, contains('"new_key": "new_value"')); + }); + test('adds a key to all .arb files in directory', () async { + // Create a temporary arb file in a subdirectory + const subDir = 'test/samples/subdir'; + const subArbFile = '$subDir/test.arb'; + if (!exists(subDir)) { + createDir(subDir, recursive: true); + } + subArbFile.write('{}'); + + try { + // Run add command without specifying files, it should find sampleArbFile and subArbFile + // But wait, sampleArbFile is test/samples/unsorted.arb. + // If we run from root, it will find all .arb files. + runCommand(['bin/arb_utils.dart', 'add', '{"auto_key":"auto_value"}']); + + expect(read(sampleArbFile).toList().join('\n'), + contains('"auto_key": "auto_value"')); + expect(read(subArbFile).toList().join('\n'), + contains('"auto_key": "auto_value"')); + } finally { + if (exists(subDir)) { + deleteDir(subDir, recursive: true); + } + } + }); }); } diff --git a/test/samples/merge-1.arb b/test/samples/merge-1.arb index 895cf88..2d39ba6 100644 --- a/test/samples/merge-1.arb +++ b/test/samples/merge-1.arb @@ -1,3 +1,4 @@ { - "newKey": "Test insert of new key" + "newKey": "Test insert of new key", + "auto_key": "auto_value" } diff --git a/test/samples/merge-2.arb b/test/samples/merge-2.arb index 12e23fd..c72cda3 100644 --- a/test/samples/merge-2.arb +++ b/test/samples/merge-2.arb @@ -2,5 +2,6 @@ "metadataKey": "Test overwrite of existing key", "@metadataKey": { "description": "metadataDescription" - } + }, + "auto_key": "auto_value" } From 466b16d9d35da4fe2436b6c3070873ccd4cb1079 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Mon, 6 Apr 2026 10:11:22 +0000 Subject: [PATCH 2/6] feat: improved 'add' command with human-friendly and JSON template syntax - Reimplemented 'add' command to support `key [locale:value...]` and `--json '