|
1 | 1 | # langcodec |
2 | 2 |
|
3 | | -Universal localization toolkit: library + CLI for Apple/Android/CSV/TSV. |
| 3 | +`langcodec` is a Rust toolkit for working with localization files across Apple, Android, and spreadsheet-style workflows. |
4 | 4 |
|
5 | | -- Library crate (`langcodec`): parse, write, convert, merge with a unified model |
6 | | -- CLI crate (`langcodec-cli`): convert, diff, merge, sync, edit, normalize, view, stats, debug, completions |
| 5 | +It gives you one consistent model for parsing, converting, inspecting, editing, merging, normalizing, and translating files like: |
7 | 6 |
|
8 | | ---- |
| 7 | +- Apple `.strings` |
| 8 | +- Apple `.xcstrings` |
| 9 | +- Android `strings.xml` |
| 10 | +- CSV |
| 11 | +- TSV |
9 | 12 |
|
10 | | -## Status |
| 13 | +This workspace includes: |
11 | 14 |
|
12 | | -This is a `0.9.1` release available on [crates.io](https://crates.io/crates/langcodec). As a 0.x version, APIs may evolve. Contributions and feedback are very welcome! |
| 15 | +- `langcodec`: the Rust library crate |
| 16 | +- `langcodec-cli`: the `langcodec` command-line tool |
13 | 17 |
|
14 | | ---- |
| 18 | +## Why People Use It |
15 | 19 |
|
16 | | -## Installation |
| 20 | +Localization pipelines usually get messy when teams have to move between iOS, Android, translators, spreadsheets, and CI scripts. `langcodec` is built to reduce that friction. |
| 21 | + |
| 22 | +With one toolchain, you can: |
| 23 | + |
| 24 | +- convert catalogs between Apple, Android, CSV, and TSV |
| 25 | +- inspect missing or stale entries |
| 26 | +- merge and sync translations safely |
| 27 | +- edit files in place across formats |
| 28 | +- normalize files to reduce noisy diffs |
| 29 | +- generate draft translations with AI-backed providers |
| 30 | + |
| 31 | +## What It Feels Like |
| 32 | + |
| 33 | +```sh |
| 34 | +# Convert Apple strings to Android XML |
| 35 | +langcodec convert -i Localizable.strings -o strings.xml |
| 36 | + |
| 37 | +# Inspect untranslated entries |
| 38 | +langcodec view -i Localizable.xcstrings --status new,needs_review --keys-only |
| 39 | + |
| 40 | +# Normalize localization files in CI |
| 41 | +langcodec normalize -i 'locales/**/*.{strings,xml,csv,tsv,xcstrings}' --check |
| 42 | + |
| 43 | +# Draft translations into an .xcstrings catalog |
| 44 | +langcodec translate \ |
| 45 | + --source Localizable.xcstrings \ |
| 46 | + --source-lang en \ |
| 47 | + --target-lang fr,de,ja \ |
| 48 | + --provider openai \ |
| 49 | + --model gpt-4.1-mini |
| 50 | +``` |
17 | 51 |
|
18 | | -- CLI: `cargo install langcodec-cli` |
19 | | -- Lib: add `langcodec = "0.9.1"` to your `Cargo.toml` |
| 52 | +## Highlights |
20 | 53 |
|
21 | | ---- |
| 54 | +- Unified data model for singular and plural translations |
| 55 | +- Read/write support for Apple, Android, CSV, and TSV formats |
| 56 | +- CLI commands for convert, diff, merge, sync, edit, normalize, view, stats, debug, and translate |
| 57 | +- `.xcstrings` and Android plural support |
| 58 | +- Config-driven translate workflows with `langcodec.toml` |
| 59 | +- Rust library API for building your own tooling on top |
22 | 60 |
|
23 | | -## Features |
| 61 | +## Installation |
| 62 | + |
| 63 | +Install the CLI: |
24 | 64 |
|
25 | | -- Parse, write, convert, merge: `.strings`, `.xcstrings`, `strings.xml`, CSV, TSV |
26 | | -- Unified `Resource` model (`Translation::Singular|Plural`) |
27 | | -- Plurals: `.xcstrings` and Android `<plurals>` supported |
28 | | -- CLI helpers: convert, diff, merge, sync, edit, normalize, view, stats, debug, completions |
| 65 | +```sh |
| 66 | +cargo install langcodec-cli |
| 67 | +``` |
29 | 68 |
|
30 | | ---- |
| 69 | +Use the library in Rust: |
| 70 | + |
| 71 | +```toml |
| 72 | +[dependencies] |
| 73 | +langcodec = "0.9.1" |
| 74 | +``` |
31 | 75 |
|
32 | 76 | ## Supported Formats |
33 | 77 |
|
34 | | -<!-- markdownlint-disable no-inline-html no-space-in-emphasis --> |
35 | | - |
36 | | -| Format | Parse | Write | Convert | Merge | Plural Support | Comments | |
37 | | -| --------------------- | :---: | :---: | :-----: | :---: | :-------------: | -------- | |
38 | | -| Apple `.strings` | ✔️ | ✔️ | ✔️ | ✔️ | No | ✔️ | |
39 | | -| Apple `.xcstrings` | ✔️ | ✔️ | ✔️ | ✔️ | Yes<sup>*</sup> | ✔️ | |
40 | | -| Android `strings.xml` | ✔️ | ✔️ | ✔️ | ✔️ | Yes | ✔️ | |
41 | | -| CSV | ✔️ | ✔️ | ✔️ | ✔️ | No | – | |
42 | | -| TSV | ✔️ | ✔️ | ✔️ | ✔️ | No | – | |
43 | | - |
44 | | -<sup>* `.xcstrings` plural support is implemented via CLDR categories.</sup> |
45 | | - |
46 | | -<!-- markdownlint-enable no-inline-html no-space-in-emphasis --> |
47 | | - |
48 | | ---- |
49 | | - |
50 | | -## Getting Started |
51 | | - |
52 | | -- Library guide: see `langcodec/README.md` |
53 | | -- CLI guide: see `langcodec-cli/README.md` |
54 | | - |
55 | | ---- |
56 | | - |
57 | | -### CLI Highlights |
58 | | - |
59 | | -- Convert: `langcodec convert -i input.strings -o strings.xml` |
60 | | -- Diff: `langcodec diff --source A.xcstrings --target B.xcstrings --json` |
61 | | -- 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` |
63 | | -- Sync existing keys only: `langcodec sync --source A.xcstrings --target B.xcstrings --match-lang en` |
64 | | -- Translate drafts with Mentra: `langcodec translate --source source.xcstrings --target target.xcstrings --source-lang en --target-lang fr --provider openai --model gpt-4.1-mini` |
65 | | -- View: `langcodec view -i strings.xml --full` |
66 | | -- View filtered untranslated/review-needed keys: `langcodec view -i Localizable.xcstrings --status new,needs_review --keys-only` |
67 | | -- View filtered results as JSON: `langcodec view -i Localizable.xcstrings --status new --lang fr --json` |
68 | | -- Stats (JSON): `langcodec stats -i Localizable.xcstrings --json` |
69 | | - - See full options: langcodec-cli/README.md#stats |
70 | | - - Example output: |
71 | | - |
72 | | - ```json |
73 | | - { |
74 | | - "summary": { "languages": 1, "unique_keys": 42 }, |
75 | | - "languages": [ |
76 | | - { |
77 | | - "language": "en", |
78 | | - "total": 42, |
79 | | - "by_status": { |
80 | | - "translated": 30, |
81 | | - "needs_review": 2, |
82 | | - "stale": 0, |
83 | | - "new": 10, |
84 | | - "do_not_translate": 0 |
85 | | - }, |
86 | | - "completion_percent": 75.0 |
87 | | - } |
88 | | - ] |
89 | | - } |
90 | | - ``` |
| 78 | +| Format | Parse | Write | Convert | Merge | Plurals | Comments | |
| 79 | +| --------------------- | :---: | :---: | :-----: | :---: | :-----: | :------: | |
| 80 | +| Apple `.strings` | yes | yes | yes | yes | no | yes | |
| 81 | +| Apple `.xcstrings` | yes | yes | yes | yes | yes | yes | |
| 82 | +| Android `strings.xml` | yes | yes | yes | yes | yes | yes | |
| 83 | +| CSV | yes | yes | yes | yes | no | no | |
| 84 | +| TSV | yes | yes | yes | yes | no | no | |
| 85 | + |
| 86 | +## CLI Quick Start |
| 87 | + |
| 88 | +### Convert files |
91 | 89 |
|
92 | | -#### Notes |
| 90 | +```sh |
| 91 | +langcodec convert -i input.xcstrings -o output.csv |
| 92 | +langcodec convert -i input.csv -o output.xcstrings --source-language en --version 1.0 |
| 93 | +``` |
| 94 | + |
| 95 | +### Inspect work to do |
| 96 | + |
| 97 | +```sh |
| 98 | +langcodec view -i Localizable.xcstrings --status new,needs_review |
| 99 | +langcodec stats -i Localizable.xcstrings --json |
| 100 | +``` |
| 101 | + |
| 102 | +### Edit and normalize |
93 | 103 |
|
94 | | -- For CSV/TSV single-language files, the language code (`--lang`) may be required. |
95 | | -- All file-processing commands support Apple `.strings`, `.xcstrings`, Android `strings.xml`, CSV, and TSV. |
96 | | -- The convert command also supports custom JSON/YAML and `.langcodec` input formats. |
97 | | -- The CLI will error if you try to merge files of different formats. |
98 | | -- Edit supports multiple inputs and glob patterns. When multiple inputs are provided, edits are applied in-place and `--output` is not allowed. |
99 | | -- Normalize supports `.strings`, Android `strings.xml`, `.csv`, `.tsv`, and `.xcstrings`. |
100 | | -- Normalize options: `--check` (fail on drift), `--dry-run` (preview only), `--no-placeholders` (skip placeholder canonicalization), `--key-style` (`none|snake|kebab|camel`). |
101 | | -- Normalize multi-input constraint: `--output` is single-input only; pair multi-input workflows with in-place writes. |
102 | | -- Normalize `--continue-on-error` processes all inputs and returns non-zero if any file fails. |
103 | | -- Android path inference: `values/strings.xml` (no qualifier) defaults to English (`en`). |
104 | | -- 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). |
105 | | -- Strict status filtering note: `langcodec --strict view --status ...` requires explicit status metadata (supported in v1: `.xcstrings`). |
106 | | -- Translate defaults to `new,stale`, writes `needs_review`, skips plurals, and supports `langcodec.toml` for translate-specific defaults. |
| 104 | +```sh |
| 105 | +langcodec edit set -i en.strings -k welcome_title -v "Welcome" |
| 106 | +langcodec normalize -i values/strings.xml |
| 107 | +``` |
| 108 | + |
| 109 | +### Merge and sync |
| 110 | + |
| 111 | +```sh |
| 112 | +langcodec merge -i a.xcstrings -i b.xcstrings -o merged.xcstrings --strategy last |
| 113 | +langcodec sync --source source.xcstrings --target target.xcstrings --match-lang en |
| 114 | +``` |
107 | 115 |
|
108 | | -#### Example `langcodec.toml` |
| 116 | +### Translate with config |
109 | 117 |
|
110 | | -Save this as `langcodec.toml` in your project root, or point to it with `--config`. |
111 | | -The CLI discovers `langcodec.toml` by searching the current directory and then parent directories. |
| 118 | +Create a `langcodec.toml` in your project: |
112 | 119 |
|
113 | 120 | ```toml |
114 | 121 | [translate] |
| 122 | +source = "locales/Localizable.xcstrings" |
115 | 123 | provider = "openai" |
116 | 124 | model = "gpt-4.1-mini" |
117 | 125 | source_lang = "en" |
118 | | -target_lang = "fr" |
119 | | -concurrency = 4 |
| 126 | +target_lang = "fr,de" |
120 | 127 | status = ["new", "stale"] |
| 128 | +concurrency = 4 |
121 | 129 | ``` |
122 | 130 |
|
123 | | -There is also a commented example file at `langcodec.toml.example` in the repository root. |
| 131 | +Then run: |
124 | 132 |
|
125 | | -#### Plurals |
| 133 | +```sh |
| 134 | +langcodec translate |
| 135 | +``` |
126 | 136 |
|
127 | | -- Android `<plurals>` are fully supported. They convert to the internal `Translation::Plural` representation and back to `<plurals>` with quantities `zero/one/two/few/many/other`. |
128 | | -- `.xcstrings` plural variations convert to Android `<plurals>` when targeting Android output. |
129 | | -- The `view` command prints plural entries with a "Type: Plural" header and each category/value. |
| 137 | +For larger projects, `translate.sources = [...]` can fan out parallel runs from config. |
130 | 138 |
|
131 | | -For JSON/YAML custom formats and more examples, see `langcodec-cli/README.md`. |
| 139 | +More CLI details live in [langcodec-cli/README.md](langcodec-cli/README.md). |
132 | 140 |
|
133 | | ---- |
| 141 | +## Library Quick Start |
134 | 142 |
|
135 | | -## Data Model |
| 143 | +```rust |
| 144 | +use langcodec::{Codec, convert_auto}; |
136 | 145 |
|
137 | | -At the core is the `Resource` struct with `Entry` values (singular or plural). See `langcodec/README.md` and docs.rs for details. |
| 146 | +fn main() -> Result<(), Box<dyn std::error::Error>> { |
| 147 | + convert_auto("Localizable.strings", "strings.xml")?; |
138 | 148 |
|
139 | | ---- |
| 149 | + let mut codec = Codec::new(); |
| 150 | + codec.read_file_by_extension("Localizable.xcstrings", None)?; |
140 | 151 |
|
141 | | -## Roadmap & Contributing |
| 152 | + for language in codec.languages() { |
| 153 | + println!("{language}"); |
| 154 | + } |
142 | 155 |
|
143 | | -- Roadmap: see `ROADMAP.md` |
144 | | -- Contributions welcome! Please open issues/PRs. |
| 156 | + Ok(()) |
| 157 | +} |
| 158 | +``` |
145 | 159 |
|
146 | | ---- |
| 160 | +The library is a good fit if you want to: |
147 | 161 |
|
148 | | -## Extending |
| 162 | +- build custom localization pipelines in Rust |
| 163 | +- validate translation assets in CI |
| 164 | +- write converters or format-specific tooling |
| 165 | +- work with a common representation instead of format-specific parsing code |
149 | 166 |
|
150 | | -Adding a new localization format? |
151 | | -Implement the `Parser` trait for your format struct in `formats/`, and add `From`/`TryFrom` conversions to and from `Resource`. |
152 | | -PRs welcome! |
| 167 | +More library details live in [langcodec/README.md](langcodec/README.md). |
153 | 168 |
|
154 | | ---- |
| 169 | +## Project Layout |
155 | 170 |
|
156 | | -## Test Data |
| 171 | +- [langcodec](langcodec): Rust library crate |
| 172 | +- [langcodec-cli](langcodec-cli): command-line interface |
| 173 | +- [tests](tests): shared test data and integration coverage |
157 | 174 |
|
158 | | -Sample test files for all supported formats are located in `tests/data/lib/` and `tests/data/cli/` at the workspace root. Use these for development, testing, and examples. |
| 175 | +## Current Status |
159 | 176 |
|
160 | | ---- |
| 177 | +The current release is `0.9.1` on [crates.io](https://crates.io/crates/langcodec). It is already useful in real workflows, but it is still a `0.x` project, so APIs and behavior may continue to evolve. |
161 | 178 |
|
162 | 179 | ## Contributing |
163 | 180 |
|
164 | | -Contributions are welcome! |
165 | | -Please open issues for bugs, suggestions, or new format support. |
166 | | -See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines. |
| 181 | +Issues, ideas, and pull requests are welcome. |
167 | 182 |
|
168 | | ---- |
| 183 | +- Project roadmap: [ROADMAP.md](ROADMAP.md) |
| 184 | +- Contribution guide: [CONTRIBUTING.md](CONTRIBUTING.md) |
169 | 185 |
|
170 | 186 | ## License |
171 | 187 |
|
172 | | -This project is licensed under the MIT License. |
173 | | - |
174 | | ---- |
175 | | - |
176 | | -## Acknowledgements |
177 | | - |
178 | | -- Inspired by the need for universal localization tooling in cross-platform apps |
179 | | -- Built with love in Rust |
180 | | - |
181 | | ---- |
| 188 | +MIT |
0 commit comments