Skip to content

Commit b8f6363

Browse files
committed
chore: align docs and harden cli test execution
1 parent 0df4ef5 commit b8f6363

9 files changed

Lines changed: 228 additions & 287 deletions

File tree

CHANGELOG.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Changelog
2+
3+
All notable changes to this project will be documented in this file.
4+
5+
The format is based on Keep a Changelog and this project follows Semantic Versioning while APIs remain in 0.x evolution.
6+
7+
## [0.7.0] - 2026-02-17
8+
9+
### Added
10+
- Global `--strict` mode in CLI workflows and strict-read controls in library input options.
11+
- CLI `diff` command with human-readable and JSON report output.
12+
- Plural validation enhancements and richer stats reporting.
13+
14+
### Changed
15+
- Ongoing improvements to conversion coverage across Apple, Android, CSV, and TSV formats.
16+
- Documentation and contributor guidance updates.
17+
18+
### Fixed
19+
- Multiple parser/writer correctness and conversion edge-case fixes covered by expanded tests.

README.md

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@ Universal localization toolkit: library + CLI for Apple/Android/CSV/TSV.
99

1010
## Status
1111

12-
This is a `0.6.4` release available on [crates.io](https://crates.io/crates/langcodec). As a 0.x version, APIs may evolve. Contributions and feedback are very welcome!
12+
This is a `0.7.0` release available on [crates.io](https://crates.io/crates/langcodec). As a 0.x version, APIs may evolve. Contributions and feedback are very welcome!
1313

1414
---
1515

1616
## Installation
1717

1818
- CLI: `cargo install langcodec-cli`
19-
- Lib: add `langcodec = "0.6.4"` to your `Cargo.toml`
19+
- Lib: add `langcodec = "0.7.0"` to your `Cargo.toml`
2020

2121
---
2222

@@ -152,7 +152,3 @@ This project is licensed under the MIT License.
152152
- Built with love in Rust
153153

154154
---
155-
156-
## Status and Roadmap
157-
158-
`langcodec` is now available on [crates.io](https://crates.io/crates/langcodec). As a 0.x version, the API may evolve as development continues. The current focus is on expanding format support, improving the CLI experience, and building a robust ecosystem for localization tooling. We welcome your issues, feature requests, and discussions at the project's issue tracker.

ROADMAP.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ Legend: [ ] todo, [x] done, [~] in progress
2626
- [x] CLDR‑driven required category sets per locale (few/many/etc.)
2727
- [x] Validation pass: flag missing categories per key+locale
2828
- [x] CLI: `view --check-plurals` output
29-
- [ ] Strict vs. permissive parsing
30-
- [ ] Global setting in lib; CLI `--strict` flag
29+
- [~] Strict vs. permissive parsing
30+
- [x] Global setting in lib; CLI `--strict` flag
3131
- [ ] Consistent error surfaces with actionable context
3232
- [ ] Better error context
3333
- [ ] Include file path and entry id for parse/convert errors
@@ -57,9 +57,9 @@ For each new format:
5757

5858
## M4. CLI UX
5959

60-
- [ ] `diff` subcommand
61-
- [ ] Compare two files; output added/removed/changed keys by language
62-
- [ ] Machine‑readable JSON output and pretty mode
60+
- [x] `diff` subcommand
61+
- [x] Compare two files; output added/removed/changed keys by language
62+
- [x] Machine‑readable JSON output and pretty mode
6363
- [x] `stats` subcommand
6464
- [x] Per‑language counts by `EntryStatus`
6565
- [x] Completion percent (excludes DoNotTranslate)
@@ -107,7 +107,7 @@ For each new format:
107107
## Release Checklist (per minor)
108108

109109
- [ ] Update README Supported Formats table
110-
- [ ] Changelog highlights (breaking changes, new formats, CLI flags)
110+
- [ ] Update CHANGELOG.md highlights (breaking changes, new formats, CLI flags)
111111
- [ ] Version bumps in workspace `Cargo.toml` and README
112112
- [ ] Tag + GitHub release notes
113113

langcodec-cli/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ ignore = "0.4"
2727
globset = "0.4"
2828

2929
[dev-dependencies]
30+
assert_cmd = "2.0"
3031
tempfile = "3.8"
3132

3233
[[bin]]

langcodec-cli/src/main.rs

Lines changed: 49 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,44 @@ enum EditCommands {
283283
},
284284
}
285285

286+
fn is_custom_input_extension(input: &str) -> bool {
287+
input.ends_with(".json")
288+
|| input.ends_with(".yaml")
289+
|| input.ends_with(".yml")
290+
|| input.ends_with(".langcodec")
291+
}
292+
293+
fn load_codec_for_readonly_command(
294+
input: &str,
295+
lang: &Option<String>,
296+
strict: bool,
297+
) -> Result<Codec, String> {
298+
let mut codec = Codec::new();
299+
let is_custom_ext = is_custom_input_extension(input);
300+
301+
if strict {
302+
if is_custom_ext {
303+
try_custom_format_view(input, lang.clone(), &mut codec)?;
304+
} else {
305+
codec
306+
.read_file_by_extension(input, lang.clone())
307+
.map_err(|e| format!("{}", e))?;
308+
}
309+
return Ok(codec);
310+
}
311+
312+
if codec.read_file_by_extension(input, lang.clone()).is_ok() {
313+
return Ok(codec);
314+
}
315+
316+
if is_custom_ext {
317+
try_custom_format_view(input, lang.clone(), &mut codec)?;
318+
return Ok(codec);
319+
}
320+
321+
Err("unsupported format".to_string())
322+
}
323+
286324
fn main() {
287325
let args = Args::parse();
288326
let strict = args.strict;
@@ -459,32 +497,13 @@ fn main() {
459497
std::process::exit(1);
460498
}
461499

462-
let mut codec = Codec::new();
463-
let is_custom_ext = input.ends_with(".json")
464-
|| input.ends_with(".yaml")
465-
|| input.ends_with(".yml")
466-
|| input.ends_with(".langcodec");
467-
if strict {
468-
if is_custom_ext {
469-
if let Err(e) = try_custom_format_view(&input, lang.clone(), &mut codec) {
470-
eprintln!("Failed to read file: {}", e);
471-
std::process::exit(1);
472-
}
473-
} else if let Err(e) = codec.read_file_by_extension(&input, lang.clone()) {
474-
eprintln!("Failed to read file: {}", e);
475-
std::process::exit(1);
476-
}
477-
} else if let Ok(()) = codec.read_file_by_extension(&input, lang.clone()) {
478-
// Standard format succeeded
479-
} else if is_custom_ext {
480-
if let Err(e) = try_custom_format_view(&input, lang.clone(), &mut codec) {
500+
let codec = match load_codec_for_readonly_command(&input, &lang, strict) {
501+
Ok(codec) => codec,
502+
Err(e) => {
481503
eprintln!("Failed to read file: {}", e);
482504
std::process::exit(1);
483505
}
484-
} else {
485-
eprintln!("Failed to read file: unsupported format");
486-
std::process::exit(1);
487-
}
506+
};
488507

489508
print_view(&codec, &lang, full);
490509

@@ -587,34 +606,18 @@ fn main() {
587606
std::process::exit(1);
588607
}
589608

590-
let mut codec = Codec::new();
591-
let is_custom_ext = input.ends_with(".json")
592-
|| input.ends_with(".yaml")
593-
|| input.ends_with(".yml")
594-
|| input.ends_with(".langcodec");
595-
if strict {
596-
if is_custom_ext {
597-
if let Err(e) = try_custom_format_view(&input, lang.clone(), &mut codec) {
598-
eprintln!("Failed to read file: {}", e);
599-
std::process::exit(1);
600-
}
601-
} else if let Err(e) = codec.read_file_by_extension(&input, lang.clone()) {
602-
eprintln!("Failed to read file: {}", e);
603-
std::process::exit(1);
604-
}
605-
} else if let Ok(()) = codec.read_file_by_extension(&input, lang.clone()) {
606-
// ok
607-
} else if is_custom_ext {
608-
if let Err(e) = try_custom_format_view(&input, lang.clone(), &mut codec) {
609+
let codec = match load_codec_for_readonly_command(&input, &lang, strict) {
610+
Ok(codec) => codec,
611+
Err(e) => {
609612
eprintln!("Failed to read file: {}", e);
610613
std::process::exit(1);
611614
}
612-
} else {
613-
eprintln!("Failed to read file: unsupported format");
615+
};
616+
617+
if let Err(e) = stats::print_stats(&codec, &lang, json) {
618+
eprintln!("❌ Stats failed: {}", e);
614619
std::process::exit(1);
615620
}
616-
617-
stats::print_stats(&codec, &lang, json);
618621
}
619622
}
620623
}

langcodec-cli/src/stats.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,11 @@ fn accumulate(lang_stats: &mut LangStats, status: &EntryStatus) {
2828
}
2929
}
3030

31-
pub fn print_stats(codec: &Codec, lang_filter: &Option<String>, json_output: bool) {
31+
pub fn print_stats(
32+
codec: &Codec,
33+
lang_filter: &Option<String>,
34+
json_output: bool,
35+
) -> Result<(), String> {
3236
let resources: Vec<_> = match lang_filter {
3337
Some(lang) => codec
3438
.resources
@@ -72,8 +76,10 @@ pub fn print_stats(codec: &Codec, lang_filter: &Option<String>, json_output: boo
7276
"summary": summary,
7377
"languages": per_lang,
7478
});
75-
println!("{}", serde_json::to_string_pretty(&body).unwrap());
76-
return;
79+
let rendered = serde_json::to_string_pretty(&body)
80+
.map_err(|e| format!("Failed to serialize stats JSON: {}", e))?;
81+
println!("{}", rendered);
82+
return Ok(());
7783
}
7884

7985
println!("=== Stats ===");
@@ -125,4 +131,5 @@ pub fn print_stats(codec: &Codec, lang_filter: &Option<String>, json_output: boo
125131
missing_plural_entries, missing_plural_categories_total
126132
);
127133
}
134+
Ok(())
128135
}

0 commit comments

Comments
 (0)