Skip to content

Commit 64baea2

Browse files
authored
Merge pull request #9 from WendellXY/codex/normalize-subcommand
Normalize Subcommand
2 parents f118377 + 6fa7e89 commit 64baea2

10 files changed

Lines changed: 1170 additions & 3 deletions

File tree

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,7 @@ target
22
Cargo.lock
33

44
# Editor Configurations
5-
.vscode
5+
.vscode
6+
7+
# Local planning artifacts
8+
docs/plans/*

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@ The format is based on Keep a Changelog and this project follows Semantic Versio
1010
- Global `--strict` mode in CLI workflows and strict-read controls in library input options.
1111
- CLI `diff` command with human-readable and JSON report output.
1212
- Plural validation enhancements and richer stats reporting.
13+
- CLI `normalize` command support across Apple `.strings`, `.xcstrings`, Android `strings.xml`, CSV, and TSV formats, including drift detection with `--check`.
1314

1415
### Changed
1516
- Ongoing improvements to conversion coverage across Apple, Android, CSV, and TSV formats.
1617
- Documentation and contributor guidance updates.
18+
- Normalize workflow documentation, including `--dry-run`, `--no-placeholders`, `--key-style`, multi-input `--output` constraints, and `--continue-on-error` behavior.
1719

1820
### Fixed
1921
- Multiple parser/writer correctness and conversion edge-case fixes covered by expanded tests.

README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
Universal localization toolkit: library + CLI for Apple/Android/CSV/TSV.
44

55
- Library crate (`langcodec`): parse, write, convert, merge with a unified model
6-
- CLI crate (`langcodec-cli`): convert, diff, merge, sync, view, stats, debug, edit
6+
- CLI crate (`langcodec-cli`): convert, diff, merge, sync, view, stats, debug, edit, normalize
77

88
---
99

@@ -59,6 +59,7 @@ This is a `0.7.0` release available on [crates.io](https://crates.io/crates/lang
5959
- Convert: `langcodec convert -i input.strings -o strings.xml`
6060
- Diff: `langcodec diff --source A.xcstrings --target B.xcstrings --json`
6161
- Edit (add/update/remove): `langcodec edit set -i 'locales/**/*.strings' -k welcome -v "Hello"` (use `--dry-run` to preview)
62+
- Normalize and detect drift: `langcodec normalize -i 'locales/**/*.{strings,xml,csv,tsv,xcstrings}' --check`
6263
- Sync existing keys only: `langcodec sync --source A.xcstrings --target B.xcstrings --match-lang en`
6364
- View: `langcodec view -i strings.xml --full`
6465
- Stats (JSON): `langcodec stats -i Localizable.xcstrings --json`
@@ -92,6 +93,10 @@ This is a `0.7.0` release available on [crates.io](https://crates.io/crates/lang
9293
- The convert command also supports custom JSON/YAML input formats.
9394
- The CLI will error if you try to merge files of different formats.
9495
- Edit supports multiple inputs and glob patterns. When multiple inputs are provided, edits are applied in-place and `--output` is not allowed.
96+
- Normalize supports `.strings`, Android `strings.xml`, `.csv`, `.tsv`, and `.xcstrings`.
97+
- Normalize options: `--check` (fail on drift), `--dry-run` (preview only), `--no-placeholders` (skip placeholder canonicalization), `--key-style` (`none|snake|kebab|camel`).
98+
- Normalize multi-input constraint: `--output` is single-input only; pair multi-input workflows with in-place writes.
99+
- Normalize `--continue-on-error` processes all inputs and returns non-zero if any file fails.
95100
- Android path inference: `values/strings.xml` (no qualifier) defaults to English (`en`).
96101
- When converting to `.xcstrings`, if `source_language` or `version` metadata is missing, the CLI defaults them to `en` and `1.0` respectively (overridable via flags).
97102

langcodec-cli/README.md

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
Universal CLI for converting, inspecting, merging, and editing localization files.
44

55
- Formats: Apple `.strings`, `.xcstrings`, Android `strings.xml`, CSV, TSV
6-
- Commands: convert, diff, merge, sync, view, stats, debug, edit
6+
- Commands: convert, diff, merge, sync, view, stats, debug, edit, normalize
77

88
## Install
99

@@ -121,6 +121,39 @@ Options:
121121

122122
Supported formats: .strings, .xml (Android), .xcstrings, .csv, .tsv. Custom JSON/YAML/.langcodec edit is currently not enabled.
123123

124+
### normalize
125+
126+
Normalize localization files in-place (or to `--output` in single-input mode).
127+
128+
```sh
129+
# Normalize in-place
130+
langcodec normalize -i en.lproj/Localizable.strings
131+
132+
# CI drift check (non-zero if any file would change)
133+
langcodec normalize -i 'locales/**/*.{strings,xml,csv,tsv,xcstrings}' --check
134+
135+
# Preview without writing
136+
langcodec normalize -i values/strings.xml --dry-run
137+
138+
# Disable placeholder normalization and rename keys to snake_case
139+
langcodec normalize -i Localizable.xcstrings --no-placeholders --key-style snake
140+
141+
# Keep processing remaining files and summarize failures at the end
142+
langcodec normalize -i a.strings -i b.csv -i c.tsv --continue-on-error
143+
```
144+
145+
Options and behavior:
146+
147+
- --check: Detects normalization drift and exits non-zero when a file would change.
148+
- --dry-run: Prints what would change and exits without writing files.
149+
- --no-placeholders: Skips placeholder canonicalization (for example `%@``%s`).
150+
- --key-style: Renames keys during normalization. Values: `none` (default), `snake`, `kebab`, `camel`.
151+
- --output/-o: Single-input mode only. If multiple inputs are provided, `--output` is rejected.
152+
- --continue-on-error: Continues processing all matched inputs, prints a summary, and exits non-zero if any file failed.
153+
- --inputs/-i: One or more files, including quoted glob patterns.
154+
155+
Supported normalize formats: `.strings`, Android `strings.xml`, `.csv`, `.tsv`, `.xcstrings`.
156+
124157
## Notes
125158

126159
- Android plurals `<plurals>` are supported.

langcodec-cli/src/main.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ mod diff;
44
mod edit;
55
mod formats;
66
mod merge;
7+
mod normalize;
78
mod path_glob;
89
mod stats;
910
mod sync;
@@ -16,6 +17,7 @@ use crate::debug::run_debug_command;
1617
use crate::diff::{DiffOptions, run_diff_command};
1718
use crate::edit::{EditSetOptions, run_edit_set_command};
1819
use crate::merge::{ConflictStrategy, run_merge_command};
20+
use crate::normalize::{NormalizeCliOptions, run_normalize_command};
1921
use crate::sync::{SyncOptions, run_sync_command};
2022
use crate::validation::{ValidationContext, validate_context, validate_language_code};
2123
use crate::view::print_view;
@@ -196,6 +198,37 @@ enum Commands {
196198
version: Option<String>,
197199
},
198200

201+
/// Normalize localization files.
202+
Normalize {
203+
/// The input files to normalize (supports glob patterns). Quote patterns to avoid shell expansion.
204+
#[arg(short, long, required = true, num_args = 1.., help = "Input files. Supports glob patterns. Quote patterns to avoid slow shell-side expansion (e.g., '/path/**/*/Localizable.strings').")]
205+
inputs: Vec<String>,
206+
207+
/// Optional output file (single-file mode only). If omitted, writes in-place.
208+
#[arg(short, long)]
209+
output: Option<String>,
210+
211+
/// Preview changes without writing
212+
#[arg(long, default_value_t = false)]
213+
dry_run: bool,
214+
215+
/// Exit non-zero if normalization would change the file
216+
#[arg(long, default_value_t = false)]
217+
check: bool,
218+
219+
/// Disable placeholder normalization
220+
#[arg(long, default_value_t = false)]
221+
no_placeholders: bool,
222+
223+
/// Key renaming style: none|snake|kebab|camel
224+
#[arg(long, default_value = "none")]
225+
key_style: String,
226+
227+
/// Continue processing remaining files when a file fails
228+
#[arg(long, default_value_t = false)]
229+
continue_on_error: bool,
230+
},
231+
199232
/// Show translation coverage and per-status counts.
200233
Stats {
201234
/// The input file to analyze
@@ -595,6 +628,30 @@ fn main() {
595628
cmd = cmd.bin_name("langcodec");
596629
generate(shell, &mut cmd, "langcodec", &mut std::io::stdout());
597630
}
631+
Commands::Normalize {
632+
inputs,
633+
output,
634+
dry_run,
635+
check,
636+
no_placeholders,
637+
key_style,
638+
continue_on_error,
639+
} => {
640+
let opts = NormalizeCliOptions {
641+
inputs,
642+
output,
643+
dry_run,
644+
check,
645+
no_placeholders,
646+
key_style,
647+
continue_on_error,
648+
strict,
649+
};
650+
if let Err(e) = run_normalize_command(opts) {
651+
eprintln!("❌ Normalize failed: {}", e);
652+
std::process::exit(1);
653+
}
654+
}
598655
Commands::Stats { input, lang, json } => {
599656
// Validate
600657
let mut context = ValidationContext::new().with_input_file(input.clone());

0 commit comments

Comments
 (0)