android-localisation 1.0.7__tar.gz → 1.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (20) hide show
  1. {android_localisation-1.0.7/android_localisation.egg-info → android_localisation-1.1.1}/PKG-INFO +59 -28
  2. {android_localisation-1.0.7 → android_localisation-1.1.1}/README.md +58 -27
  3. {android_localisation-1.0.7 → android_localisation-1.1.1}/android_localisation/__init__.py +1 -1
  4. {android_localisation-1.0.7 → android_localisation-1.1.1}/android_localisation/cli.py +12 -6
  5. {android_localisation-1.0.7 → android_localisation-1.1.1}/android_localisation/fix.py +39 -17
  6. android_localisation-1.1.1/android_localisation/java/VerifyStrings.java +138 -0
  7. android_localisation-1.1.1/android_localisation/resources.py +214 -0
  8. {android_localisation-1.0.7 → android_localisation-1.1.1}/android_localisation/translate.py +117 -72
  9. android_localisation-1.1.1/android_localisation/verify.py +82 -0
  10. {android_localisation-1.0.7 → android_localisation-1.1.1/android_localisation.egg-info}/PKG-INFO +59 -28
  11. {android_localisation-1.0.7 → android_localisation-1.1.1}/android_localisation.egg-info/SOURCES.txt +1 -0
  12. {android_localisation-1.0.7 → android_localisation-1.1.1}/pyproject.toml +1 -1
  13. android_localisation-1.0.7/android_localisation/java/VerifyStrings.java +0 -219
  14. android_localisation-1.0.7/android_localisation/verify.py +0 -56
  15. {android_localisation-1.0.7 → android_localisation-1.1.1}/LICENSE +0 -0
  16. {android_localisation-1.0.7 → android_localisation-1.1.1}/MANIFEST.in +0 -0
  17. {android_localisation-1.0.7 → android_localisation-1.1.1}/android_localisation.egg-info/dependency_links.txt +0 -0
  18. {android_localisation-1.0.7 → android_localisation-1.1.1}/android_localisation.egg-info/entry_points.txt +0 -0
  19. {android_localisation-1.0.7 → android_localisation-1.1.1}/android_localisation.egg-info/top_level.txt +0 -0
  20. {android_localisation-1.0.7 → android_localisation-1.1.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: android-localisation
3
- Version: 1.0.7
3
+ Version: 1.1.1
4
4
  Summary: Zero-dependency Android strings.xml translation and verification using LLMs (Gemini, OpenAI, Anthropic, Ollama).
5
5
  License: MIT
6
6
  Project-URL: Homepage, https://github.com/BharathKmalviya/android-llm-localization
@@ -54,6 +54,8 @@ pip install android-localisation
54
54
 
55
55
  Requires Python 3.8+. No other dependencies.
56
56
 
57
+ CLI output uses UTF-8, including when redirected to a file or pipe on Windows.
58
+
57
59
  ---
58
60
 
59
61
  ## Quick start
@@ -65,7 +67,7 @@ android-localise translate --api-key YOUR_GEMINI_KEY
65
67
  # Step 2 — fix any formatting issues the LLM may have introduced
66
68
  android-localise fix
67
69
 
68
- # Step 3 — verify nothing will crash at runtime
70
+ # Step 3 — check resources and Java formatting
69
71
  android-localise verify
70
72
  ```
71
73
 
@@ -78,9 +80,9 @@ That's the full workflow. Run these three commands after every time you update y
78
80
  When you run `android-localise translate --api-key YOUR_KEY`, here's exactly what it does:
79
81
 
80
82
  1. Looks for `app/src/main/res/values/strings.xml` — this is your English source
81
- 2. If `--languages` is provided, creates any missing `values-<lang>/` folders automatically. Otherwise scans the `res/` directory for existing `values-*` folders
82
- 3. For each locale, if `strings.xml` doesn't exist it creates the file first, then sends your full English XML to the LLM with a prompt that instructs it to translate naturally, preserve all XML structure, and never touch format specifiers like `%1$s` or `%d`
83
- 4. Writes the translated `strings.xml` directly into each locale folder
83
+ 2. If `--languages` is provided, selects those locales. Otherwise scans existing locale folders, skipping configuration-only folders such as `values-night`, `values-land`, `values-car`, and `values-sw600dp`
84
+ 3. Sends your English XML to the LLM with app context and instructions to preserve resource structure, protected values, namespaces and format specifiers. With `--missing-only`, requests only resources absent from the target file; existing resources remain untouched, and complete locales make no API request
85
+ 4. Parses the response and checks duplicate/unexpected/missing resources, attributes, inline markup, item structure, control escapes and format arguments. A valid result replaces the file atomically; new folders are created only when saving. `--dry-run` shows a diff without writing any files or creating folders
84
86
  5. Waits 5 seconds between each language request to avoid hitting API rate limits
85
87
 
86
88
  **Defaults used when you don't specify anything:**
@@ -88,12 +90,14 @@ When you run `android-localise translate --api-key YOUR_KEY`, here's exactly wha
88
90
  | What | Default |
89
91
  |---|---|
90
92
  | Provider | Gemini |
91
- | Model | `gemini-3.5-flash` |
93
+ | Model | `gemini-3.8-flash` |
92
94
  | Source directory | `app/src/main/res` |
93
95
  | Delay between requests | 5 seconds |
94
96
  | App context | none (generic prompt) |
95
97
 
96
- Nothing is modified unless the translation comes back with valid XML. If a request fails, that language is skipped and logged — other languages continue.
98
+ An invalid or incomplete response leaves the existing file unchanged. Other locales continue, and the final summary shows succeeded, failed and skipped counts. Exit code **0** means success; **1** means a setup, validation, API or save failure (including partial failure). Argument syntax errors use argparse's exit code **2**.
99
+
100
+ Normal translation still replaces the whole locale file. Use `--missing-only` to retain reviewed translations or combine it with `--dry-run` to preview additions. No cache, database or configuration file is needed.
97
101
 
98
102
  ---
99
103
 
@@ -107,7 +111,7 @@ For target languages, you have two options:
107
111
  ```bash
108
112
  android-localise translate --api-key YOUR_KEY --languages hi,es,fr,de
109
113
  ```
110
- This creates `values-hi/`, `values-es/`, `values-fr/`, `values-de/` folders and their `strings.xml` files automatically, then translates into each one.
114
+ This translates into Hindi, Spanish, French and German, creating each folder and `strings.xml` when its translation passes validation.
111
115
 
112
116
  **Option B — pre-create folders yourself:**
113
117
  ```
@@ -118,7 +122,7 @@ app/src/main/res/
118
122
  ├── values-es/
119
123
  └── values-fr/
120
124
  ```
121
- Run `android-localise translate --api-key YOUR_KEY` and it picks up any `values-*` folder it finds, creating `strings.xml` inside each one if it doesn't exist yet.
125
+ Run `android-localise translate --api-key YOUR_KEY` and it picks up locale folders, creating `strings.xml` inside each one after successful translation. Locale examples include `values-hi`, `values-es-rES`, `values-b+zh+Hans`, and `values-en-night`; qualifier-only folders are skipped. `--languages` accepts the same forms without the optional `values-` prefix and rejects path separators or non-locale names.
122
126
 
123
127
  **Get a free API key:** [Google Gemini AI Studio](https://aistudio.google.com/) → Get API Key. The free tier handles most apps without hitting limits.
124
128
 
@@ -153,6 +157,19 @@ android-localise translate \
153
157
  | `--base-url` | API endpoint for local/custom providers | — |
154
158
  | `--sleep` | Seconds to wait between language requests | `5.0` |
155
159
  | `--timeout` | Seconds to wait for each API response (up to 3 attempts on timeout) | `180` |
160
+ | `--missing-only` | Translate absent resources; retain existing translations | off (refresh whole file) |
161
+ | `--dry-run` | Generate and validate output, then print a diff without writing files | off |
162
+
163
+ **Preserve reviewed translations:**
164
+
165
+ ```bash
166
+ android-localise translate --languages hi,es --missing-only
167
+ android-localise translate --languages hi --missing-only --dry-run
168
+ ```
169
+
170
+ `--dry-run` still calls the selected provider and may incur API charges. It is a translation preview, not a no-network estimate. `--missing-only` keeps existing resources, comments and text, but validates them against the current source first. An invalid existing file is reported rather than silently repaired. It does not detect changed English text for an existing key; use the normal translation mode when you deliberately want to refresh those resources. Arrays and plurals count as whole resources: this mode does not fill individual missing items.
171
+
172
+ Validation permits positional placeholder reordering such as `%s %d` becoming `%2$d %1$s`, while checking argument identities, conversions, formatting options and occurrence counts. Non-translatable resources may be omitted from locale files to use Android's default fallback, but must remain unchanged if included.
156
173
 
157
174
  ---
158
175
 
@@ -163,12 +180,14 @@ android-localise fix
163
180
  android-localise fix --res-dir path/to/res
164
181
  ```
165
182
 
166
- LLMs occasionally produce output that looks correct but breaks the Android build — curly apostrophes (`'`) instead of escaped ones (`\'`), unescaped double quotes, or mangled `%` signs. This command scans every translated `strings.xml` and corrects these silently.
183
+ This command scans locale `strings.xml` files, converts curly apostrophes, escapes raw apostrophes, and doubles bare percent signs while retaining recognized format patterns. It checks XML before and after changes and saves atomically. Inline markup attributes and non-translatable values are left alone. It handles single- and double-quoted resource names.
167
184
 
168
185
  Strings marked `formatted="false"` are skipped — their `%` signs are literal, not format specifiers.
169
186
 
170
187
  Always run this before `verify` and before building.
171
188
 
189
+ `fix` repairs `<string>` text only. It does not repair arbitrary malformed XML, double-quote handling, or array/plural items, and it does not prove a format pattern is valid. Review its changes and use `verify` and your Android build afterward.
190
+
172
191
  ---
173
192
 
174
193
  ### `verify`
@@ -178,10 +197,12 @@ android-localise verify
178
197
  android-localise verify --res-dir path/to/res
179
198
  ```
180
199
 
181
- Takes every translated string that contains a format specifier (`%1$s`, `%d`, `%1$f`, etc.) and calls `String.format()` on it using Java's actual runtime. If a translated string would throw `UnknownFormatConversionException` or `MissingFormatArgumentException` in your app, this catches it before your users do.
200
+ First parses XML and compares localized resources with `values/strings.xml`, checking coverage, duplicates, protected content, attributes, inline markup and format-argument preservation. Then checks formatted strings and array/plural items with Java's actual `String.format()` runtime, including date/time and relative argument indexing. Both checks return nonzero on failure. Empty locale folders without a `strings.xml` are skipped.
182
201
 
183
202
  Strings marked `formatted="false"` are skipped. Requires `javac` in your PATH. If you don't have it system-wide, run this from the Terminal tab inside Android Studio — it ships with a JDK.
184
203
 
204
+ `formatted="false"` skips formatting checks only; XML, resource coverage and attribute checks still apply. Java compilation uses a temporary directory, so installed package files are not modified. Verification is deliberately stricter than Android's missing-string fallback: missing translatable resources are reported. Passing these checks does not replace an Android build, native-speaker review or device layout checks.
205
+
185
206
  ---
186
207
 
187
208
  ### `models`
@@ -191,27 +212,33 @@ android-localise models # all providers
191
212
  android-localise models --provider openai # one provider
192
213
  ```
193
214
 
194
- Lists every available model and fallback for each provider.
215
+ Lists this CLI's configured defaults and automatic fallbacks, not the provider's entire model catalog. Any supported model can still be selected with `--model`.
195
216
 
196
217
  ---
197
218
 
198
219
  ## Providers
199
220
 
200
- By default the tool uses Gemini with `gemini-3.5-flash`. You can switch providers with `--provider` and optionally pin a specific model with `--model`.
221
+ By default the tool uses Gemini with `gemini-3.8-flash`. You can switch providers with `--provider` and optionally pin a specific model with `--model`. Configured defaults and fallbacks use only the latest general-purpose text-model lineup, with defaults favoring speed and cost within that lineup.
201
222
 
202
223
  | Provider | Default model | Fallbacks | API key env var |
203
224
  |---|---|---|---|
204
- | `gemini` _(default)_ | `gemini-3.5-flash` | `gemini-3.1-flash-lite` → `gemini-2.5-flash` → `gemini-2.5-flash-lite` | `GEMINI_API_KEY` |
205
- | `openai` | `gpt-5.4-mini` | `gpt-5-mini` → `gpt-4o-mini` | `OPENAI_API_KEY` |
206
- | `anthropic` | `claude-haiku-4-5` | `claude-sonnet-4-6` → `claude-opus-4-8` | `ANTHROPIC_API_KEY` |
225
+ | `gemini` _(default)_ | `gemini-3.8-flash` | none | `GEMINI_API_KEY` |
226
+ | `openai` | `gpt-6-luna` | `gpt-6.1-sol` → `gpt-6-astra` | `OPENAI_API_KEY` |
227
+ | `anthropic` | `claude-sonnet-5-5` | `claude-opus-5-5` | `ANTHROPIC_API_KEY` |
207
228
  | `custom` | set with `--model` | none | `OPENAI_API_KEY` (optional) |
208
229
 
209
230
  If the default model returns a "model not found" error (e.g. it was deprecated), the tool automatically retries with the next fallback. If you pin a model with `--model`, no fallback is used.
210
231
 
232
+ Model IDs and compatibility checked against [Google's model catalog](https://ai.google.dev/gemini-api/docs/models), [OpenAI's model catalog](https://developers.openai.com/api/docs/models) and [Anthropic's model catalog](https://platform.claude.com/docs/en/models/overview) on **2026-10-02**. Older Gemini, GPT-5 and Haiku 4.5 models are excluded from automatic selection. Gemini has no fallback in its latest stable text generation. OpenAI and Anthropic fallbacks are higher-cost models; pin `--model` to avoid automatic tier changes. Explicit model selection and custom/local endpoints remain available. The catalog is bundled with each CLI release, rather than automatically discovering models at runtime.
233
+
234
+ OpenAI and local providers retain the Chat Completions request format. Provider replies indicating truncation or blocked/incomplete output are rejected. Anthropic allows up to 16,384 output tokens per request and text blocks are collected separately from thinking blocks. Large files can still exceed a model's limits; automatic batching is future work.
235
+
211
236
  **Using OpenAI:**
212
237
  ```bash
213
238
  android-localise translate --provider openai --api-key YOUR_KEY
214
- android-localise translate --provider openai --model gpt-5.4-mini --api-key YOUR_KEY
239
+ android-localise translate --provider openai --model gpt-6-luna --api-key YOUR_KEY
240
+ # Optional stronger tier; different cost/latency
241
+ android-localise translate --provider openai --model gpt-6.1-sol --api-key YOUR_KEY
215
242
  ```
216
243
 
217
244
  **Using Anthropic:**
@@ -290,15 +317,15 @@ android-localise verify
290
317
  ./gradlew assembleDebug
291
318
  ```
292
319
 
293
- After this, whenever you add or change strings in your English `strings.xml`, run the same three commands again. Existing translated strings will be overwritten with fresh translations.
320
+ After this, whenever you add or change strings in your English `strings.xml`, run the same three commands again. Existing translated strings are refreshed by default. For additions that should retain existing translations, run `translate --missing-only` instead; it does not detect changed source text for existing keys.
294
321
 
295
322
  ---
296
323
 
297
324
  ## Platform support
298
325
 
299
- This project is developed and **manually tested on Windows only** at the moment. It is written in pure Python (stdlib only) and should run on macOS and Linux, but those platforms have **not been verified** by the maintainer yet.
326
+ I develop and **manually test this project on Windows only** at the moment. It is written in pure Python (stdlib only) and should run on macOS and Linux, but I have **not verified** those platforms yet.
300
327
 
301
- We especially need help testing on:
328
+ I especially need help testing on:
302
329
 
303
330
  - **macOS** — `translate`, `fix`, `verify` (including `javac` / Android Studio terminal)
304
331
  - **Linux** — same workflow, plus common CI environments
@@ -309,7 +336,7 @@ If you use another OS, please try the [quick start](#quick-start) workflow and r
309
336
  - **Broken?** — open a [bug report](https://github.com/BharathKmalviya/android-llm-localization/issues/new?template=bug_report.md) with the full error output
310
337
  - **Want to help more?** — see [Contributing](#contributing) and [CONTRIBUTING.md](CONTRIBUTING.md)
311
338
 
312
- Cross-platform fixes and test notes in pull requests are very welcome.
339
+ PRs with cross-platform fixes and test notes are especially appreciated.
313
340
 
314
341
  ---
315
342
 
@@ -317,10 +344,13 @@ Cross-platform fixes and test notes in pull requests are very welcome.
317
344
 
318
345
  | Topic | Detail |
319
346
  |---|---|
320
- | **Platform testing** | Maintainer-tested on **Windows only** — macOS and Linux need community verification (see [Platform support](#platform-support)) |
321
- | **Scope** | Translates `values/strings.xml` only — not `plurals.xml`, `arrays.xml`, or other resource files |
322
- | **Overwrite** | Each run replaces the entire `strings.xml` in each locale folder with a fresh LLM translation |
323
- | **Folder scan** | Without `--languages`, every `values-*` folder is treated as a locale. Qualifier-only folders like `values-night` or `values-sw600dp` may be picked up incorrectly — prefer `--languages` or keep only locale folders in `res/` |
347
+ | **Platform testing** | I test on **Windows only** — macOS and Linux need community verification (see [Platform support](#platform-support)) |
348
+ | **Scope** | Reads `values/strings.xml` only. Strings, arrays and plurals within that file are checked; separate XML files are not scanned |
349
+ | **Plurals** | Preserves source quantities and item structure; does not generate target-language plural categories. Review plural completeness for each language |
350
+ | **Overwrite** | Normal runs refresh whole files. `--missing-only` retains existing resources but does not detect source changes |
351
+ | **Folder scan** | Recognizes language-first and Android `b+` locale forms, with optional trailing qualifiers. MCC/MNC-prefixed resource folders are not scanned |
352
+ | **Validation** | Requires source attributes, inline element order and formatting options to match. DTD/entity declarations are unsupported; checks do not replace Android compilation or language review |
353
+ | **Large files** | One request per locale; no automatic batching or resume cache. Incomplete output is rejected |
324
354
  | **Network** | `translate` requires internet access to reach the LLM API (except local `custom` providers) |
325
355
  | **JDK** | `verify` requires `javac` on your PATH |
326
356
 
@@ -333,6 +363,7 @@ Cross-platform fixes and test notes in pull requests are very welcome.
333
363
  | `Could not find English strings.xml` | Check `--res-dir` points to your `res/` folder and `values/strings.xml` exists |
334
364
  | `No locale directories found` | Add `--languages hi,es,fr` or create `values-<lang>/` folders manually |
335
365
  | API auth errors | Confirm your key env var or `--api-key` matches the `--provider` |
366
+ | Resource validation fails | Read the named resource error; check source/target keys, attributes, placeholders and markup. Existing files are retained |
336
367
  | `javac` not found | Install a JDK or run `verify` from Android Studio's terminal |
337
368
  | Build fails on apostrophes | Run `android-localise fix` before building |
338
369
  | `%` crashes at runtime | Run `android-localise verify` — it catches bad format specifiers before release |
@@ -345,17 +376,17 @@ Cross-platform fixes and test notes in pull requests are very welcome.
345
376
  - [ ] **iOS support** — translate `Localizable.strings` and `Localizable.xcstrings` for iOS/macOS apps. The LLM prompt and provider logic is already in place — it mainly needs a parser for Apple's strings format and the right folder structure (`<lang>.lproj/`). Good first contribution if you're familiar with iOS projects.
346
377
  - [ ] **Smarter locale folder detection** — skip non-locale `values-*` qualifiers (`night`, `sw600dp`, `v21`, etc.) when scanning without `--languages`
347
378
  - [ ] **Automated test suite** — unit tests for `fix`, XML parsing, and format-specifier edge cases
348
- - [ ] **Cross-platform verification** — confirm `translate`, `fix`, and `verify` on macOS and Linux (Windows is maintainer-tested today)
379
+ - [ ] **Cross-platform verification** — confirm `translate`, `fix`, and `verify` on macOS and Linux (I currently test on Windows only)
349
380
 
350
381
  ---
351
382
 
352
383
  ## Contributing
353
384
 
354
- Bug reports, pull requests, and **cross-platform testing** are all welcome. For larger changes, open an issue first.
385
+ I welcome bug reports, pull requests, and **cross-platform testing**. For larger changes, please open an issue first.
355
386
 
356
387
  **No code required** — if you are on macOS or Linux, running the tool and filing an issue (pass or fail) is a real contribution. See [Platform support](#platform-support).
357
388
 
358
- See [CONTRIBUTING.md](CONTRIBUTING.md) for the full development workflow, branch strategy, and release process.
389
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for the development workflow, branch strategy, and release process.
359
390
 
360
391
  ```bash
361
392
  git clone https://github.com/BharathKmalviya/android-llm-localization
@@ -26,6 +26,8 @@ pip install android-localisation
26
26
 
27
27
  Requires Python 3.8+. No other dependencies.
28
28
 
29
+ CLI output uses UTF-8, including when redirected to a file or pipe on Windows.
30
+
29
31
  ---
30
32
 
31
33
  ## Quick start
@@ -37,7 +39,7 @@ android-localise translate --api-key YOUR_GEMINI_KEY
37
39
  # Step 2 — fix any formatting issues the LLM may have introduced
38
40
  android-localise fix
39
41
 
40
- # Step 3 — verify nothing will crash at runtime
42
+ # Step 3 — check resources and Java formatting
41
43
  android-localise verify
42
44
  ```
43
45
 
@@ -50,9 +52,9 @@ That's the full workflow. Run these three commands after every time you update y
50
52
  When you run `android-localise translate --api-key YOUR_KEY`, here's exactly what it does:
51
53
 
52
54
  1. Looks for `app/src/main/res/values/strings.xml` — this is your English source
53
- 2. If `--languages` is provided, creates any missing `values-<lang>/` folders automatically. Otherwise scans the `res/` directory for existing `values-*` folders
54
- 3. For each locale, if `strings.xml` doesn't exist it creates the file first, then sends your full English XML to the LLM with a prompt that instructs it to translate naturally, preserve all XML structure, and never touch format specifiers like `%1$s` or `%d`
55
- 4. Writes the translated `strings.xml` directly into each locale folder
55
+ 2. If `--languages` is provided, selects those locales. Otherwise scans existing locale folders, skipping configuration-only folders such as `values-night`, `values-land`, `values-car`, and `values-sw600dp`
56
+ 3. Sends your English XML to the LLM with app context and instructions to preserve resource structure, protected values, namespaces and format specifiers. With `--missing-only`, requests only resources absent from the target file; existing resources remain untouched, and complete locales make no API request
57
+ 4. Parses the response and checks duplicate/unexpected/missing resources, attributes, inline markup, item structure, control escapes and format arguments. A valid result replaces the file atomically; new folders are created only when saving. `--dry-run` shows a diff without writing any files or creating folders
56
58
  5. Waits 5 seconds between each language request to avoid hitting API rate limits
57
59
 
58
60
  **Defaults used when you don't specify anything:**
@@ -60,12 +62,14 @@ When you run `android-localise translate --api-key YOUR_KEY`, here's exactly wha
60
62
  | What | Default |
61
63
  |---|---|
62
64
  | Provider | Gemini |
63
- | Model | `gemini-3.5-flash` |
65
+ | Model | `gemini-3.8-flash` |
64
66
  | Source directory | `app/src/main/res` |
65
67
  | Delay between requests | 5 seconds |
66
68
  | App context | none (generic prompt) |
67
69
 
68
- Nothing is modified unless the translation comes back with valid XML. If a request fails, that language is skipped and logged — other languages continue.
70
+ An invalid or incomplete response leaves the existing file unchanged. Other locales continue, and the final summary shows succeeded, failed and skipped counts. Exit code **0** means success; **1** means a setup, validation, API or save failure (including partial failure). Argument syntax errors use argparse's exit code **2**.
71
+
72
+ Normal translation still replaces the whole locale file. Use `--missing-only` to retain reviewed translations or combine it with `--dry-run` to preview additions. No cache, database or configuration file is needed.
69
73
 
70
74
  ---
71
75
 
@@ -79,7 +83,7 @@ For target languages, you have two options:
79
83
  ```bash
80
84
  android-localise translate --api-key YOUR_KEY --languages hi,es,fr,de
81
85
  ```
82
- This creates `values-hi/`, `values-es/`, `values-fr/`, `values-de/` folders and their `strings.xml` files automatically, then translates into each one.
86
+ This translates into Hindi, Spanish, French and German, creating each folder and `strings.xml` when its translation passes validation.
83
87
 
84
88
  **Option B — pre-create folders yourself:**
85
89
  ```
@@ -90,7 +94,7 @@ app/src/main/res/
90
94
  ├── values-es/
91
95
  └── values-fr/
92
96
  ```
93
- Run `android-localise translate --api-key YOUR_KEY` and it picks up any `values-*` folder it finds, creating `strings.xml` inside each one if it doesn't exist yet.
97
+ Run `android-localise translate --api-key YOUR_KEY` and it picks up locale folders, creating `strings.xml` inside each one after successful translation. Locale examples include `values-hi`, `values-es-rES`, `values-b+zh+Hans`, and `values-en-night`; qualifier-only folders are skipped. `--languages` accepts the same forms without the optional `values-` prefix and rejects path separators or non-locale names.
94
98
 
95
99
  **Get a free API key:** [Google Gemini AI Studio](https://aistudio.google.com/) → Get API Key. The free tier handles most apps without hitting limits.
96
100
 
@@ -125,6 +129,19 @@ android-localise translate \
125
129
  | `--base-url` | API endpoint for local/custom providers | — |
126
130
  | `--sleep` | Seconds to wait between language requests | `5.0` |
127
131
  | `--timeout` | Seconds to wait for each API response (up to 3 attempts on timeout) | `180` |
132
+ | `--missing-only` | Translate absent resources; retain existing translations | off (refresh whole file) |
133
+ | `--dry-run` | Generate and validate output, then print a diff without writing files | off |
134
+
135
+ **Preserve reviewed translations:**
136
+
137
+ ```bash
138
+ android-localise translate --languages hi,es --missing-only
139
+ android-localise translate --languages hi --missing-only --dry-run
140
+ ```
141
+
142
+ `--dry-run` still calls the selected provider and may incur API charges. It is a translation preview, not a no-network estimate. `--missing-only` keeps existing resources, comments and text, but validates them against the current source first. An invalid existing file is reported rather than silently repaired. It does not detect changed English text for an existing key; use the normal translation mode when you deliberately want to refresh those resources. Arrays and plurals count as whole resources: this mode does not fill individual missing items.
143
+
144
+ Validation permits positional placeholder reordering such as `%s %d` becoming `%2$d %1$s`, while checking argument identities, conversions, formatting options and occurrence counts. Non-translatable resources may be omitted from locale files to use Android's default fallback, but must remain unchanged if included.
128
145
 
129
146
  ---
130
147
 
@@ -135,12 +152,14 @@ android-localise fix
135
152
  android-localise fix --res-dir path/to/res
136
153
  ```
137
154
 
138
- LLMs occasionally produce output that looks correct but breaks the Android build — curly apostrophes (`'`) instead of escaped ones (`\'`), unescaped double quotes, or mangled `%` signs. This command scans every translated `strings.xml` and corrects these silently.
155
+ This command scans locale `strings.xml` files, converts curly apostrophes, escapes raw apostrophes, and doubles bare percent signs while retaining recognized format patterns. It checks XML before and after changes and saves atomically. Inline markup attributes and non-translatable values are left alone. It handles single- and double-quoted resource names.
139
156
 
140
157
  Strings marked `formatted="false"` are skipped — their `%` signs are literal, not format specifiers.
141
158
 
142
159
  Always run this before `verify` and before building.
143
160
 
161
+ `fix` repairs `<string>` text only. It does not repair arbitrary malformed XML, double-quote handling, or array/plural items, and it does not prove a format pattern is valid. Review its changes and use `verify` and your Android build afterward.
162
+
144
163
  ---
145
164
 
146
165
  ### `verify`
@@ -150,10 +169,12 @@ android-localise verify
150
169
  android-localise verify --res-dir path/to/res
151
170
  ```
152
171
 
153
- Takes every translated string that contains a format specifier (`%1$s`, `%d`, `%1$f`, etc.) and calls `String.format()` on it using Java's actual runtime. If a translated string would throw `UnknownFormatConversionException` or `MissingFormatArgumentException` in your app, this catches it before your users do.
172
+ First parses XML and compares localized resources with `values/strings.xml`, checking coverage, duplicates, protected content, attributes, inline markup and format-argument preservation. Then checks formatted strings and array/plural items with Java's actual `String.format()` runtime, including date/time and relative argument indexing. Both checks return nonzero on failure. Empty locale folders without a `strings.xml` are skipped.
154
173
 
155
174
  Strings marked `formatted="false"` are skipped. Requires `javac` in your PATH. If you don't have it system-wide, run this from the Terminal tab inside Android Studio — it ships with a JDK.
156
175
 
176
+ `formatted="false"` skips formatting checks only; XML, resource coverage and attribute checks still apply. Java compilation uses a temporary directory, so installed package files are not modified. Verification is deliberately stricter than Android's missing-string fallback: missing translatable resources are reported. Passing these checks does not replace an Android build, native-speaker review or device layout checks.
177
+
157
178
  ---
158
179
 
159
180
  ### `models`
@@ -163,27 +184,33 @@ android-localise models # all providers
163
184
  android-localise models --provider openai # one provider
164
185
  ```
165
186
 
166
- Lists every available model and fallback for each provider.
187
+ Lists this CLI's configured defaults and automatic fallbacks, not the provider's entire model catalog. Any supported model can still be selected with `--model`.
167
188
 
168
189
  ---
169
190
 
170
191
  ## Providers
171
192
 
172
- By default the tool uses Gemini with `gemini-3.5-flash`. You can switch providers with `--provider` and optionally pin a specific model with `--model`.
193
+ By default the tool uses Gemini with `gemini-3.8-flash`. You can switch providers with `--provider` and optionally pin a specific model with `--model`. Configured defaults and fallbacks use only the latest general-purpose text-model lineup, with defaults favoring speed and cost within that lineup.
173
194
 
174
195
  | Provider | Default model | Fallbacks | API key env var |
175
196
  |---|---|---|---|
176
- | `gemini` _(default)_ | `gemini-3.5-flash` | `gemini-3.1-flash-lite` → `gemini-2.5-flash` → `gemini-2.5-flash-lite` | `GEMINI_API_KEY` |
177
- | `openai` | `gpt-5.4-mini` | `gpt-5-mini` → `gpt-4o-mini` | `OPENAI_API_KEY` |
178
- | `anthropic` | `claude-haiku-4-5` | `claude-sonnet-4-6` → `claude-opus-4-8` | `ANTHROPIC_API_KEY` |
197
+ | `gemini` _(default)_ | `gemini-3.8-flash` | none | `GEMINI_API_KEY` |
198
+ | `openai` | `gpt-6-luna` | `gpt-6.1-sol` → `gpt-6-astra` | `OPENAI_API_KEY` |
199
+ | `anthropic` | `claude-sonnet-5-5` | `claude-opus-5-5` | `ANTHROPIC_API_KEY` |
179
200
  | `custom` | set with `--model` | none | `OPENAI_API_KEY` (optional) |
180
201
 
181
202
  If the default model returns a "model not found" error (e.g. it was deprecated), the tool automatically retries with the next fallback. If you pin a model with `--model`, no fallback is used.
182
203
 
204
+ Model IDs and compatibility checked against [Google's model catalog](https://ai.google.dev/gemini-api/docs/models), [OpenAI's model catalog](https://developers.openai.com/api/docs/models) and [Anthropic's model catalog](https://platform.claude.com/docs/en/models/overview) on **2026-10-02**. Older Gemini, GPT-5 and Haiku 4.5 models are excluded from automatic selection. Gemini has no fallback in its latest stable text generation. OpenAI and Anthropic fallbacks are higher-cost models; pin `--model` to avoid automatic tier changes. Explicit model selection and custom/local endpoints remain available. The catalog is bundled with each CLI release, rather than automatically discovering models at runtime.
205
+
206
+ OpenAI and local providers retain the Chat Completions request format. Provider replies indicating truncation or blocked/incomplete output are rejected. Anthropic allows up to 16,384 output tokens per request and text blocks are collected separately from thinking blocks. Large files can still exceed a model's limits; automatic batching is future work.
207
+
183
208
  **Using OpenAI:**
184
209
  ```bash
185
210
  android-localise translate --provider openai --api-key YOUR_KEY
186
- android-localise translate --provider openai --model gpt-5.4-mini --api-key YOUR_KEY
211
+ android-localise translate --provider openai --model gpt-6-luna --api-key YOUR_KEY
212
+ # Optional stronger tier; different cost/latency
213
+ android-localise translate --provider openai --model gpt-6.1-sol --api-key YOUR_KEY
187
214
  ```
188
215
 
189
216
  **Using Anthropic:**
@@ -262,15 +289,15 @@ android-localise verify
262
289
  ./gradlew assembleDebug
263
290
  ```
264
291
 
265
- After this, whenever you add or change strings in your English `strings.xml`, run the same three commands again. Existing translated strings will be overwritten with fresh translations.
292
+ After this, whenever you add or change strings in your English `strings.xml`, run the same three commands again. Existing translated strings are refreshed by default. For additions that should retain existing translations, run `translate --missing-only` instead; it does not detect changed source text for existing keys.
266
293
 
267
294
  ---
268
295
 
269
296
  ## Platform support
270
297
 
271
- This project is developed and **manually tested on Windows only** at the moment. It is written in pure Python (stdlib only) and should run on macOS and Linux, but those platforms have **not been verified** by the maintainer yet.
298
+ I develop and **manually test this project on Windows only** at the moment. It is written in pure Python (stdlib only) and should run on macOS and Linux, but I have **not verified** those platforms yet.
272
299
 
273
- We especially need help testing on:
300
+ I especially need help testing on:
274
301
 
275
302
  - **macOS** — `translate`, `fix`, `verify` (including `javac` / Android Studio terminal)
276
303
  - **Linux** — same workflow, plus common CI environments
@@ -281,7 +308,7 @@ If you use another OS, please try the [quick start](#quick-start) workflow and r
281
308
  - **Broken?** — open a [bug report](https://github.com/BharathKmalviya/android-llm-localization/issues/new?template=bug_report.md) with the full error output
282
309
  - **Want to help more?** — see [Contributing](#contributing) and [CONTRIBUTING.md](CONTRIBUTING.md)
283
310
 
284
- Cross-platform fixes and test notes in pull requests are very welcome.
311
+ PRs with cross-platform fixes and test notes are especially appreciated.
285
312
 
286
313
  ---
287
314
 
@@ -289,10 +316,13 @@ Cross-platform fixes and test notes in pull requests are very welcome.
289
316
 
290
317
  | Topic | Detail |
291
318
  |---|---|
292
- | **Platform testing** | Maintainer-tested on **Windows only** — macOS and Linux need community verification (see [Platform support](#platform-support)) |
293
- | **Scope** | Translates `values/strings.xml` only — not `plurals.xml`, `arrays.xml`, or other resource files |
294
- | **Overwrite** | Each run replaces the entire `strings.xml` in each locale folder with a fresh LLM translation |
295
- | **Folder scan** | Without `--languages`, every `values-*` folder is treated as a locale. Qualifier-only folders like `values-night` or `values-sw600dp` may be picked up incorrectly — prefer `--languages` or keep only locale folders in `res/` |
319
+ | **Platform testing** | I test on **Windows only** — macOS and Linux need community verification (see [Platform support](#platform-support)) |
320
+ | **Scope** | Reads `values/strings.xml` only. Strings, arrays and plurals within that file are checked; separate XML files are not scanned |
321
+ | **Plurals** | Preserves source quantities and item structure; does not generate target-language plural categories. Review plural completeness for each language |
322
+ | **Overwrite** | Normal runs refresh whole files. `--missing-only` retains existing resources but does not detect source changes |
323
+ | **Folder scan** | Recognizes language-first and Android `b+` locale forms, with optional trailing qualifiers. MCC/MNC-prefixed resource folders are not scanned |
324
+ | **Validation** | Requires source attributes, inline element order and formatting options to match. DTD/entity declarations are unsupported; checks do not replace Android compilation or language review |
325
+ | **Large files** | One request per locale; no automatic batching or resume cache. Incomplete output is rejected |
296
326
  | **Network** | `translate` requires internet access to reach the LLM API (except local `custom` providers) |
297
327
  | **JDK** | `verify` requires `javac` on your PATH |
298
328
 
@@ -305,6 +335,7 @@ Cross-platform fixes and test notes in pull requests are very welcome.
305
335
  | `Could not find English strings.xml` | Check `--res-dir` points to your `res/` folder and `values/strings.xml` exists |
306
336
  | `No locale directories found` | Add `--languages hi,es,fr` or create `values-<lang>/` folders manually |
307
337
  | API auth errors | Confirm your key env var or `--api-key` matches the `--provider` |
338
+ | Resource validation fails | Read the named resource error; check source/target keys, attributes, placeholders and markup. Existing files are retained |
308
339
  | `javac` not found | Install a JDK or run `verify` from Android Studio's terminal |
309
340
  | Build fails on apostrophes | Run `android-localise fix` before building |
310
341
  | `%` crashes at runtime | Run `android-localise verify` — it catches bad format specifiers before release |
@@ -317,17 +348,17 @@ Cross-platform fixes and test notes in pull requests are very welcome.
317
348
  - [ ] **iOS support** — translate `Localizable.strings` and `Localizable.xcstrings` for iOS/macOS apps. The LLM prompt and provider logic is already in place — it mainly needs a parser for Apple's strings format and the right folder structure (`<lang>.lproj/`). Good first contribution if you're familiar with iOS projects.
318
349
  - [ ] **Smarter locale folder detection** — skip non-locale `values-*` qualifiers (`night`, `sw600dp`, `v21`, etc.) when scanning without `--languages`
319
350
  - [ ] **Automated test suite** — unit tests for `fix`, XML parsing, and format-specifier edge cases
320
- - [ ] **Cross-platform verification** — confirm `translate`, `fix`, and `verify` on macOS and Linux (Windows is maintainer-tested today)
351
+ - [ ] **Cross-platform verification** — confirm `translate`, `fix`, and `verify` on macOS and Linux (I currently test on Windows only)
321
352
 
322
353
  ---
323
354
 
324
355
  ## Contributing
325
356
 
326
- Bug reports, pull requests, and **cross-platform testing** are all welcome. For larger changes, open an issue first.
357
+ I welcome bug reports, pull requests, and **cross-platform testing**. For larger changes, please open an issue first.
327
358
 
328
359
  **No code required** — if you are on macOS or Linux, running the tool and filing an issue (pass or fail) is a real contribution. See [Platform support](#platform-support).
329
360
 
330
- See [CONTRIBUTING.md](CONTRIBUTING.md) for the full development workflow, branch strategy, and release process.
361
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for the development workflow, branch strategy, and release process.
331
362
 
332
363
  ```bash
333
364
  git clone https://github.com/BharathKmalviya/android-llm-localization
@@ -2,4 +2,4 @@
2
2
  android-localisation: Zero-dependency Android strings.xml translation using LLMs.
3
3
  """
4
4
 
5
- __version__ = "1.0.7"
5
+ __version__ = "1.1.1"
@@ -9,10 +9,14 @@ Usage:
9
9
  """
10
10
 
11
11
  import argparse
12
+ import sys
12
13
  from android_localisation import __version__
13
14
 
14
15
 
15
16
  def main():
17
+ for stream in (sys.stdout, sys.stderr):
18
+ if hasattr(stream, "reconfigure"):
19
+ stream.reconfigure(encoding="utf-8", errors="replace")
16
20
  parser = argparse.ArgumentParser(
17
21
  prog="android-localise",
18
22
  description="Zero-dependency Android strings.xml localization using LLMs.",
@@ -37,6 +41,8 @@ def main():
37
41
  help=f"Seconds to wait for each API response, up to {MAX_TIMEOUT_RETRIES + 1} attempts on timeout (default: {DEFAULT_API_TIMEOUT})",
38
42
  )
39
43
  translate_parser.add_argument("--languages", help="Comma-separated language codes, e.g. hi,es,fr,de — creates folders and strings.xml automatically")
44
+ translate_parser.add_argument("--missing-only", action="store_true", help="Translate missing resources while retaining existing translations")
45
+ translate_parser.add_argument("--dry-run", action="store_true", help="Generate and validate translations, then show a diff without writing files (API usage applies)")
40
46
 
41
47
  # --- fix ---
42
48
  fix_parser = subparsers.add_parser("fix", help="Fix XML escaping issues in translated strings.xml files")
@@ -47,7 +53,7 @@ def main():
47
53
  verify_parser.add_argument("--res-dir", default="app/src/main/res", help="Path to the Android res/ directory (default: app/src/main/res)")
48
54
 
49
55
  # --- models ---
50
- models_parser = subparsers.add_parser("models", help="List all available models per provider")
56
+ models_parser = subparsers.add_parser("models", help="List configured model defaults and fallbacks")
51
57
  models_parser.add_argument("--provider", choices=["gemini", "openai", "anthropic"], default=None,
52
58
  help="Filter by provider (shows all if not set)")
53
59
 
@@ -55,15 +61,15 @@ def main():
55
61
 
56
62
  if args.command == "translate":
57
63
  from android_localisation.translate import main as run
58
- run(args)
64
+ return run(args)
59
65
 
60
66
  elif args.command == "fix":
61
67
  from android_localisation.fix import main as run
62
- run(args)
68
+ return run(args)
63
69
 
64
70
  elif args.command == "verify":
65
71
  from android_localisation.verify import main as run
66
- run(args)
72
+ return run(args)
67
73
 
68
74
  elif args.command == "models":
69
75
  from android_localisation.translate import PROVIDER_MODELS
@@ -80,9 +86,9 @@ def main():
80
86
  print(" → Any model name your local server supports (must use --model)")
81
87
  print()
82
88
  print(" Tip: use --model to pick any model, e.g:")
83
- print(" android-localise translate --provider openai --model gpt-5.4-mini --api-key KEY")
89
+ print(" android-localise translate --provider openai --model gpt-6-luna --api-key KEY")
84
90
  print()
85
91
 
86
92
 
87
93
  if __name__ == "__main__":
88
- main()
94
+ raise SystemExit(main())