Skip to content

Commit fba3618

Browse files
committed
docs: add commit message style guidelines to CONTRIBUTING.md
- Introduced a section outlining the Conventional Commits specification for commit messages. - Provided examples and detailed formatting rules to enhance clarity and consistency in commit history. - Updated the example commit command to reflect the new format.
1 parent b5b133c commit fba3618

3 files changed

Lines changed: 291 additions & 15 deletions

File tree

AGENTS.md

Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
# AGENTS.md — Automation and Navigation Guide
2+
3+
This document helps automation agents quickly understand, build, test, and operate this repository.
4+
5+
## Overview
6+
7+
- **Library crate**: `langcodec/` — universal localization toolkit (parse/convert Apple `.strings`, `.xcstrings`, Android `strings.xml`, CSV, TSV)
8+
- **CLI crate**: `langcodec-cli/` — end-user command-line tool built on the library
9+
- **Binary name**: `langcodec`
10+
11+
## Repository layout (key paths)
12+
13+
- `langcodec/src/lib.rs`: library entry; re-exports `Codec`, `convert_auto`, `FormatType`, `types::*`
14+
- `langcodec/src/formats/`: parsers/writers for `strings`, `xcstrings`, `android_strings`, `csv`, `tsv`
15+
- `langcodec/src/traits.rs`: `Parser` trait for format-agnostic IO
16+
- `langcodec-cli/src/main.rs`: CLI entry with subcommands
17+
- `langcodec-cli/src/transformers/`: one-way converters for custom JSON/YAML formats
18+
- `langcodec-cli/tests/` and `langcodec-cli/tests/fixtures/`: integration tests and sample inputs
19+
20+
## Prerequisites
21+
22+
- Rust toolchain with Edition 2024 support (install via rustup)
23+
- macOS/Linux shell environment for examples below
24+
25+
Optional tooling for contributors:
26+
27+
- `cargo fmt`, `cargo clippy`
28+
29+
## Absolute path convention in this guide
30+
31+
Replace `<repo>` with the absolute path to the repository root. Example:
32+
33+
```bash
34+
REPO="/Users/wendell/Developer/langcodec"
35+
```
36+
37+
When running commands non-interactively, prefer absolute paths like `"$REPO/target/release/langcodec"` and explicit input/output file paths.
38+
39+
## Build
40+
41+
```bash
42+
cd "$REPO"
43+
cargo build --release -p langcodec-cli
44+
```
45+
46+
- Output binary: `"$REPO/target/release/langcodec"`
47+
48+
Build just the library:
49+
50+
```bash
51+
cargo build --release -p langcodec
52+
```
53+
54+
## Test
55+
56+
```bash
57+
cd "$REPO"
58+
cargo test --all
59+
```
60+
61+
Run only CLI tests:
62+
63+
```bash
64+
cargo test -p langcodec-cli
65+
```
66+
67+
## CLI quick reference
68+
69+
Binary: `"$REPO/target/release/langcodec"`
70+
71+
- `convert`: Convert localization files between formats (auto-detect by extension)
72+
- `edit set`: Add/update/remove entries in-place (or to `--output`)
73+
- `view`: Pretty-print entries, filter by `--lang`, optional `--check_plurals`
74+
- `merge`: Merge multiple inputs to one output with conflict strategy
75+
- `stats`: Coverage and per-status counts (text or `--json`)
76+
- `debug`: Read file and emit JSON (to stdout or `--output`)
77+
- `completions`: Generate shell completion scripts
78+
79+
Show help for any subcommand:
80+
81+
```bash
82+
"$REPO/target/release/langcodec" --help | cat
83+
"$REPO/target/release/langcodec" convert --help | cat
84+
```
85+
86+
## Supported formats
87+
88+
Standard (read/write):
89+
90+
- **Apple**: `.strings`, `.xcstrings`
91+
- **Android**: `strings.xml`
92+
- **CSV**, **TSV**
93+
94+
Custom inputs (one-way into internal Resources via CLI):
95+
96+
- `json-language-map`, `json-array-language-map`, `yaml-language-map`, `langcodec-resource-array` (`.langcodec`)
97+
98+
## Common automation recipes (absolute paths)
99+
100+
- Convert `.strings` → Android XML:
101+
102+
```bash
103+
"$REPO/target/release/langcodec" convert \
104+
--input "/abs/path/Localizable.strings" \
105+
--output "/abs/path/values/strings.xml"
106+
```
107+
108+
- Convert `.xcstrings` → CSV:
109+
110+
```bash
111+
"$REPO/target/release/langcodec" convert \
112+
--input "/abs/path/Localizable.xcstrings" \
113+
--output "/abs/path/translations.csv"
114+
```
115+
116+
- Convert custom JSON language map → `.xcstrings` with overrides:
117+
118+
```bash
119+
"$REPO/target/release/langcodec" convert \
120+
--input "/abs/path/translations.json" \
121+
--output "/abs/path/Localizable.xcstrings" \
122+
--input_format json-language-map \
123+
--output_format xcstrings \
124+
--source_language en \
125+
--version 1.0
126+
```
127+
128+
- Edit in place (add/update). For single-language formats, specify `--lang` as needed:
129+
130+
```bash
131+
"$REPO/target/release/langcodec" edit set \
132+
--inputs "/abs/path/en.lproj/Localizable.strings" \
133+
--lang en \
134+
--key welcome_message \
135+
--value "Hello, World!"
136+
```
137+
138+
- Remove a key (omit or empty `--value`):
139+
140+
```bash
141+
"$REPO/target/release/langcodec" edit set \
142+
--inputs "/abs/path/values/strings.xml" \
143+
--lang en \
144+
--key obsolete_key \
145+
--value ""
146+
```
147+
148+
- Preview changes without writing:
149+
150+
```bash
151+
"$REPO/target/release/langcodec" edit set \
152+
--inputs "/abs/path/en.lproj/Localizable.strings" \
153+
--lang en \
154+
--key welcome_message \
155+
--value "Hello" \
156+
--dry_run
157+
```
158+
159+
- View entries (full values) and check plurals:
160+
161+
```bash
162+
"$REPO/target/release/langcodec" view \
163+
--input "/abs/path/Localizable.xcstrings" \
164+
--lang en \
165+
--full \
166+
--check_plurals
167+
```
168+
169+
- Merge multiple files (quote globs to avoid shell-side expansion):
170+
171+
```bash
172+
"$REPO/target/release/langcodec" merge \
173+
--inputs "/abs/path/**/Localizable.strings" \
174+
--output "/abs/path/merged.xcstrings" \
175+
--strategy last \
176+
--lang en \
177+
--source_language en \
178+
--version 1.0
179+
```
180+
181+
- Stats (machine-readable):
182+
183+
```bash
184+
"$REPO/target/release/langcodec" stats \
185+
--input "/abs/path/Localizable.xcstrings" \
186+
--lang en \
187+
--json
188+
```
189+
190+
- Debug (emit JSON to file):
191+
192+
```bash
193+
"$REPO/target/release/langcodec" debug \
194+
--input "/abs/path/values/strings.xml" \
195+
--lang en \
196+
--output "/abs/path/out.json"
197+
```
198+
199+
## Exit codes (for CI/non-interactive use)
200+
201+
- `0`: success
202+
- `1`: validation or runtime failure (e.g., invalid inputs, unsupported format)
203+
- `2`: plural validation failed (when `view --check_plurals` is used)
204+
205+
## Behavior notes for agents
206+
207+
- All commands are non-interactive. Always pass explicit absolute paths.
208+
- Input/output formats are inferred from file extensions unless `--input_format` / `--output_format` is provided.
209+
- For single-language formats, pass `--lang` when required (e.g., ambiguous inputs).
210+
- Quote glob patterns provided to `merge --inputs` to avoid slow shell-side expansion.
211+
212+
## Library usage (Rust)
213+
214+
The library exposes a high-level API. Minimal example:
215+
216+
```rust
217+
use langcodec::convert_auto;
218+
219+
fn main() -> Result<(), Box<dyn std::error::Error>> {
220+
convert_auto("/abs/path/Localizable.strings", "/abs/path/values/strings.xml")?;
221+
Ok(())
222+
}
223+
```
224+
225+
Builder pattern and direct `Codec` manipulation are also available; see `langcodec/src/lib.rs` for more examples and re-exports.
226+
227+
## Extension points (for contributors/agents)
228+
229+
- Add/modify formats: edit files under `langcodec/src/formats/` and wire into `formats/mod.rs`
230+
- Implement parsing/writing: implement `Parser` in `langcodec/src/traits.rs`
231+
- Add CLI subcommands/options: edit `langcodec-cli/src/main.rs` and corresponding modules
232+
- Support new custom one-way formats: add a transformer under `langcodec-cli/src/transformers/` and register in `transformers/mod.rs` and `formats.rs`
233+
234+
## Reproducible CI example
235+
236+
```bash
237+
set -euo pipefail
238+
REPO="/abs/path/to/langcodec"
239+
cargo build --release -p langcodec-cli --manifest-path "$REPO/Cargo.toml"
240+
"$REPO/target/release/langcodec" --version | cat
241+
"$REPO/target/release/langcodec" convert \
242+
--input "/abs/path/Localizable.xcstrings" \
243+
--output "/abs/path/translations.csv"
244+
```

CONTRIBUTING.md

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,38 @@ To add support for a new localization format:
5858
- Add examples in doc comments
5959
- Update the format support table in README.md
6060

61+
## Commit message style
62+
63+
Follow the Conventional Commits specification to keep history readable and enable automated changelog tooling. See the official docs: <https://www.conventionalcommits.org/en/v1.0.0/>.
64+
65+
- Use the format: `<type>(<scope>): <subject>`
66+
- type: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert
67+
- scope (optional): one of lib, cli, formats, formats/android, formats/strings, formats/xcstrings, formats/csv, formats/tsv, transformers, validation, docs, tests
68+
- subject: imperative mood, lowercase, ≤ 72 characters, no trailing period
69+
- Separate body from subject with a blank line; explain motivation and impact
70+
- Reference issues in the body or footer (e.g., Refs #123 or Fixes #123)
71+
- Breaking changes: use `feat!:` or `fix!:` in the subject, and include a `BREAKING CHANGE:` footer
72+
73+
Examples:
74+
75+
```text
76+
feat(cli): add --check_plurals to view command
77+
78+
fix(formats/android): escape apostrophes correctly in strings.xml
79+
80+
refactor(lib): simplify plural rules evaluation
81+
82+
chore: update dependencies
83+
84+
feat!: replace plural category API
85+
86+
feat(cli): add stats --json output
87+
88+
Provide machine-readable stats for CI consumers and human-readable summary by default.
89+
90+
BREAKING CHANGE: stats subcommand no longer prints totals by default; use --json.
91+
```
92+
6193
## Submitting Changes
6294

6395
**1. Create a feature branch:**
@@ -70,7 +102,7 @@ To add support for a new localization format:
70102

71103
```bash
72104
git add .
73-
git commit -m "Add: brief description of changes"
105+
git commit -m "feat: brief description of changes"
74106
```
75107

76108
**3. Push and create a pull request:**

langcodec-cli/src/edit.rs

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -132,14 +132,14 @@ pub fn run_edit_set_command(opts: EditSetOptions) -> Result<(), String> {
132132
}
133133
processed_count += 1;
134134
// Validate per-file
135-
let mut vctx = ValidationContext::new().with_input_file(input_path.clone());
135+
let mut validation_context = ValidationContext::new().with_input_file(input_path.clone());
136136
if let Some(l) = &opts.lang {
137-
vctx = vctx.with_language_code(l.clone());
137+
validation_context = validation_context.with_language_code(l.clone());
138138
}
139139
if let Some(o) = &opts.output {
140-
vctx = vctx.with_output_file(o.clone());
140+
validation_context = validation_context.with_output_file(o.clone());
141141
}
142-
if let Err(e) = validate_context(&vctx) {
142+
if let Err(e) = validate_context(&validation_context) {
143143
let msg = format!("Input validation failed for '{}': {}", input_path, e);
144144
if opts.continue_on_error {
145145
eprintln!("❌ {}", msg);
@@ -273,7 +273,7 @@ fn apply_set_to_file(
273273
}
274274
} else {
275275
let resolved_lang_owned: String;
276-
let lref: &str = if let Some(l) = lang.as_deref() {
276+
let lang_ref: &str = if let Some(l) = lang.as_deref() {
277277
l
278278
} else if codec.resources.len() == 1 {
279279
resolved_lang_owned = codec.resources[0].metadata.language.clone();
@@ -285,10 +285,10 @@ fn apply_set_to_file(
285285
));
286286
};
287287
let val = value.clone().unwrap_or_default();
288-
let exists = codec.has_entry(key, lref);
288+
let exists = codec.has_entry(key, lang_ref);
289289
if exists {
290290
let old = codec
291-
.find_entry(key, lref)
291+
.find_entry(key, lang_ref)
292292
.map(|e| match &e.value {
293293
Translation::Singular(s) => s.clone(),
294294
Translation::Plural(p) => p.id.clone(),
@@ -297,40 +297,40 @@ fn apply_set_to_file(
297297
if dry_run {
298298
println!(
299299
"DRY-RUN: Would update '{}' in {}: '{}' -> '{}' ({})",
300-
key, lref, old, val, input
300+
key, lang_ref, old, val, input
301301
);
302302
} else {
303303
codec
304304
.update_translation(
305305
key,
306-
lref,
306+
lang_ref,
307307
Translation::Singular(val.clone()),
308308
status_parsed.clone(),
309309
)
310310
.map_err(|e| e.to_string())?;
311311
if comment.is_some()
312-
&& let Some(entry) = codec.find_entry_mut(key, lref)
312+
&& let Some(entry) = codec.find_entry_mut(key, lang_ref)
313313
{
314314
entry.comment = comment.clone();
315315
}
316-
println!("✅ Updated '{}' in {} ({})", key, lref, input);
316+
println!("✅ Updated '{}' in {} ({})", key, lang_ref, input);
317317
}
318318
} else if dry_run {
319319
println!(
320320
"DRY-RUN: Would add '{}' to {} with value '{}' ({})",
321-
key, lref, val, input
321+
key, lang_ref, val, input
322322
);
323323
} else {
324324
codec
325325
.add_entry(
326326
key,
327-
lref,
327+
lang_ref,
328328
Translation::Singular(val.clone()),
329329
comment.clone(),
330330
status_parsed.clone(),
331331
)
332332
.map_err(|e| e.to_string())?;
333-
println!("✅ Added '{}' to {} ({})", key, lref, input);
333+
println!("✅ Added '{}' to {} ({})", key, lang_ref, input);
334334
}
335335
}
336336

0 commit comments

Comments
 (0)