android-localisation 1.0.7__tar.gz → 1.1.2__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 (21) hide show
  1. {android_localisation-1.0.7/android_localisation.egg-info → android_localisation-1.1.2}/PKG-INFO +79 -28
  2. {android_localisation-1.0.7 → android_localisation-1.1.2}/README.md +78 -27
  3. {android_localisation-1.0.7 → android_localisation-1.1.2}/android_localisation/__init__.py +1 -1
  4. {android_localisation-1.0.7 → android_localisation-1.1.2}/android_localisation/cli.py +24 -8
  5. {android_localisation-1.0.7 → android_localisation-1.1.2}/android_localisation/fix.py +39 -17
  6. android_localisation-1.1.2/android_localisation/java/VerifyStrings.java +138 -0
  7. android_localisation-1.1.2/android_localisation/resources.py +214 -0
  8. {android_localisation-1.0.7 → android_localisation-1.1.2}/android_localisation/translate.py +117 -72
  9. android_localisation-1.1.2/android_localisation/updates.py +95 -0
  10. android_localisation-1.1.2/android_localisation/verify.py +82 -0
  11. {android_localisation-1.0.7 → android_localisation-1.1.2/android_localisation.egg-info}/PKG-INFO +79 -28
  12. {android_localisation-1.0.7 → android_localisation-1.1.2}/android_localisation.egg-info/SOURCES.txt +2 -0
  13. {android_localisation-1.0.7 → android_localisation-1.1.2}/pyproject.toml +1 -1
  14. android_localisation-1.0.7/android_localisation/java/VerifyStrings.java +0 -219
  15. android_localisation-1.0.7/android_localisation/verify.py +0 -56
  16. {android_localisation-1.0.7 → android_localisation-1.1.2}/LICENSE +0 -0
  17. {android_localisation-1.0.7 → android_localisation-1.1.2}/MANIFEST.in +0 -0
  18. {android_localisation-1.0.7 → android_localisation-1.1.2}/android_localisation.egg-info/dependency_links.txt +0 -0
  19. {android_localisation-1.0.7 → android_localisation-1.1.2}/android_localisation.egg-info/entry_points.txt +0 -0
  20. {android_localisation-1.0.7 → android_localisation-1.1.2}/android_localisation.egg-info/top_level.txt +0 -0
  21. {android_localisation-1.0.7 → android_localisation-1.1.2}/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.2
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,26 @@ 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
+
59
+ ### Update notices
60
+
61
+ Interactive `android-localise` commands check PyPI for a newer stable release in
62
+ the background, at most once every 24 hours. When available, a note on stderr
63
+ shows `pip install --upgrade android-localisation`. The CLI never waits for the
64
+ lookup or installs an update automatically. A first short command may finish
65
+ before the lookup completes; a notice appears when a check completes during a
66
+ command or a later invocation can use its cached result.
67
+
68
+ Checks are skipped for help/version output, CI (`CI` set), redirected output and
69
+ pipes. Set `ANDROID_LOCALISE_NO_UPDATE_CHECK=1` to disable them, including when
70
+ using a local model offline. Lookup or cache failures stay silent and do not
71
+ change command exit codes. The small cache lives under
72
+ `%LOCALAPPDATA%/android-localisation` on Windows, or
73
+ `${XDG_CACHE_HOME:-~/.cache}/android-localisation` elsewhere. Only the package's
74
+ public release metadata is requested; API keys and Android resources are never
75
+ included.
76
+
57
77
  ---
58
78
 
59
79
  ## Quick start
@@ -65,7 +85,7 @@ android-localise translate --api-key YOUR_GEMINI_KEY
65
85
  # Step 2 — fix any formatting issues the LLM may have introduced
66
86
  android-localise fix
67
87
 
68
- # Step 3 — verify nothing will crash at runtime
88
+ # Step 3 — check resources and Java formatting
69
89
  android-localise verify
70
90
  ```
71
91
 
@@ -78,9 +98,9 @@ That's the full workflow. Run these three commands after every time you update y
78
98
  When you run `android-localise translate --api-key YOUR_KEY`, here's exactly what it does:
79
99
 
80
100
  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
101
+ 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`
102
+ 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
103
+ 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
104
  5. Waits 5 seconds between each language request to avoid hitting API rate limits
85
105
 
86
106
  **Defaults used when you don't specify anything:**
@@ -88,12 +108,14 @@ When you run `android-localise translate --api-key YOUR_KEY`, here's exactly wha
88
108
  | What | Default |
89
109
  |---|---|
90
110
  | Provider | Gemini |
91
- | Model | `gemini-3.5-flash` |
111
+ | Model | `gemini-3.8-flash` |
92
112
  | Source directory | `app/src/main/res` |
93
113
  | Delay between requests | 5 seconds |
94
114
  | App context | none (generic prompt) |
95
115
 
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.
116
+ 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**.
117
+
118
+ 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
119
 
98
120
  ---
99
121
 
@@ -107,7 +129,7 @@ For target languages, you have two options:
107
129
  ```bash
108
130
  android-localise translate --api-key YOUR_KEY --languages hi,es,fr,de
109
131
  ```
110
- This creates `values-hi/`, `values-es/`, `values-fr/`, `values-de/` folders and their `strings.xml` files automatically, then translates into each one.
132
+ This translates into Hindi, Spanish, French and German, creating each folder and `strings.xml` when its translation passes validation.
111
133
 
112
134
  **Option B — pre-create folders yourself:**
113
135
  ```
@@ -118,7 +140,7 @@ app/src/main/res/
118
140
  ├── values-es/
119
141
  └── values-fr/
120
142
  ```
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.
143
+ 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
144
 
123
145
  **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
146
 
@@ -153,6 +175,19 @@ android-localise translate \
153
175
  | `--base-url` | API endpoint for local/custom providers | — |
154
176
  | `--sleep` | Seconds to wait between language requests | `5.0` |
155
177
  | `--timeout` | Seconds to wait for each API response (up to 3 attempts on timeout) | `180` |
178
+ | `--missing-only` | Translate absent resources; retain existing translations | off (refresh whole file) |
179
+ | `--dry-run` | Generate and validate output, then print a diff without writing files | off |
180
+
181
+ **Preserve reviewed translations:**
182
+
183
+ ```bash
184
+ android-localise translate --languages hi,es --missing-only
185
+ android-localise translate --languages hi --missing-only --dry-run
186
+ ```
187
+
188
+ `--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.
189
+
190
+ 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
191
 
157
192
  ---
158
193
 
@@ -163,12 +198,14 @@ android-localise fix
163
198
  android-localise fix --res-dir path/to/res
164
199
  ```
165
200
 
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.
201
+ 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
202
 
168
203
  Strings marked `formatted="false"` are skipped — their `%` signs are literal, not format specifiers.
169
204
 
170
205
  Always run this before `verify` and before building.
171
206
 
207
+ `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.
208
+
172
209
  ---
173
210
 
174
211
  ### `verify`
@@ -178,10 +215,12 @@ android-localise verify
178
215
  android-localise verify --res-dir path/to/res
179
216
  ```
180
217
 
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.
218
+ 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
219
 
183
220
  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
221
 
222
+ `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.
223
+
185
224
  ---
186
225
 
187
226
  ### `models`
@@ -191,27 +230,33 @@ android-localise models # all providers
191
230
  android-localise models --provider openai # one provider
192
231
  ```
193
232
 
194
- Lists every available model and fallback for each provider.
233
+ 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
234
 
196
235
  ---
197
236
 
198
237
  ## Providers
199
238
 
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`.
239
+ 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
240
 
202
241
  | Provider | Default model | Fallbacks | API key env var |
203
242
  |---|---|---|---|
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` |
243
+ | `gemini` _(default)_ | `gemini-3.8-flash` | none | `GEMINI_API_KEY` |
244
+ | `openai` | `gpt-6-luna` | `gpt-6.1-sol` → `gpt-6-astra` | `OPENAI_API_KEY` |
245
+ | `anthropic` | `claude-sonnet-5-5` | `claude-opus-5-5` | `ANTHROPIC_API_KEY` |
207
246
  | `custom` | set with `--model` | none | `OPENAI_API_KEY` (optional) |
208
247
 
209
248
  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
249
 
250
+ 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.
251
+
252
+ 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.
253
+
211
254
  **Using OpenAI:**
212
255
  ```bash
213
256
  android-localise translate --provider openai --api-key YOUR_KEY
214
- android-localise translate --provider openai --model gpt-5.4-mini --api-key YOUR_KEY
257
+ android-localise translate --provider openai --model gpt-6-luna --api-key YOUR_KEY
258
+ # Optional stronger tier; different cost/latency
259
+ android-localise translate --provider openai --model gpt-6.1-sol --api-key YOUR_KEY
215
260
  ```
216
261
 
217
262
  **Using Anthropic:**
@@ -262,6 +307,8 @@ android-localise translate
262
307
  | `OPENAI_API_KEY` | `--provider openai` and `--provider custom` |
263
308
  | `ANTHROPIC_API_KEY` | `--provider anthropic` |
264
309
  | `API_KEY` | fallback for any provider if the provider-specific var is not set |
310
+ | `ANDROID_LOCALISE_NO_UPDATE_CHECK` | Set to `1` to disable update checks/notices |
311
+ | `CI` | When nonempty, skips update checks/notices |
265
312
 
266
313
  ---
267
314
 
@@ -290,15 +337,15 @@ android-localise verify
290
337
  ./gradlew assembleDebug
291
338
  ```
292
339
 
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.
340
+ 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
341
 
295
342
  ---
296
343
 
297
344
  ## Platform support
298
345
 
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.
346
+ 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
347
 
301
- We especially need help testing on:
348
+ I especially need help testing on:
302
349
 
303
350
  - **macOS** — `translate`, `fix`, `verify` (including `javac` / Android Studio terminal)
304
351
  - **Linux** — same workflow, plus common CI environments
@@ -309,7 +356,7 @@ If you use another OS, please try the [quick start](#quick-start) workflow and r
309
356
  - **Broken?** — open a [bug report](https://github.com/BharathKmalviya/android-llm-localization/issues/new?template=bug_report.md) with the full error output
310
357
  - **Want to help more?** — see [Contributing](#contributing) and [CONTRIBUTING.md](CONTRIBUTING.md)
311
358
 
312
- Cross-platform fixes and test notes in pull requests are very welcome.
359
+ PRs with cross-platform fixes and test notes are especially appreciated.
313
360
 
314
361
  ---
315
362
 
@@ -317,10 +364,13 @@ Cross-platform fixes and test notes in pull requests are very welcome.
317
364
 
318
365
  | Topic | Detail |
319
366
  |---|---|
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/` |
367
+ | **Platform testing** | I test on **Windows only** — macOS and Linux need community verification (see [Platform support](#platform-support)) |
368
+ | **Scope** | Reads `values/strings.xml` only. Strings, arrays and plurals within that file are checked; separate XML files are not scanned |
369
+ | **Plurals** | Preserves source quantities and item structure; does not generate target-language plural categories. Review plural completeness for each language |
370
+ | **Overwrite** | Normal runs refresh whole files. `--missing-only` retains existing resources but does not detect source changes |
371
+ | **Folder scan** | Recognizes language-first and Android `b+` locale forms, with optional trailing qualifiers. MCC/MNC-prefixed resource folders are not scanned |
372
+ | **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 |
373
+ | **Large files** | One request per locale; no automatic batching or resume cache. Incomplete output is rejected |
324
374
  | **Network** | `translate` requires internet access to reach the LLM API (except local `custom` providers) |
325
375
  | **JDK** | `verify` requires `javac` on your PATH |
326
376
 
@@ -333,6 +383,7 @@ Cross-platform fixes and test notes in pull requests are very welcome.
333
383
  | `Could not find English strings.xml` | Check `--res-dir` points to your `res/` folder and `values/strings.xml` exists |
334
384
  | `No locale directories found` | Add `--languages hi,es,fr` or create `values-<lang>/` folders manually |
335
385
  | API auth errors | Confirm your key env var or `--api-key` matches the `--provider` |
386
+ | Resource validation fails | Read the named resource error; check source/target keys, attributes, placeholders and markup. Existing files are retained |
336
387
  | `javac` not found | Install a JDK or run `verify` from Android Studio's terminal |
337
388
  | Build fails on apostrophes | Run `android-localise fix` before building |
338
389
  | `%` crashes at runtime | Run `android-localise verify` — it catches bad format specifiers before release |
@@ -345,17 +396,17 @@ Cross-platform fixes and test notes in pull requests are very welcome.
345
396
  - [ ] **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
397
  - [ ] **Smarter locale folder detection** — skip non-locale `values-*` qualifiers (`night`, `sw600dp`, `v21`, etc.) when scanning without `--languages`
347
398
  - [ ] **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)
399
+ - [ ] **Cross-platform verification** — confirm `translate`, `fix`, and `verify` on macOS and Linux (I currently test on Windows only)
349
400
 
350
401
  ---
351
402
 
352
403
  ## Contributing
353
404
 
354
- Bug reports, pull requests, and **cross-platform testing** are all welcome. For larger changes, open an issue first.
405
+ I welcome bug reports, pull requests, and **cross-platform testing**. For larger changes, please open an issue first.
355
406
 
356
407
  **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
408
 
358
- See [CONTRIBUTING.md](CONTRIBUTING.md) for the full development workflow, branch strategy, and release process.
409
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for the development workflow, branch strategy, and release process.
359
410
 
360
411
  ```bash
361
412
  git clone https://github.com/BharathKmalviya/android-llm-localization
@@ -26,6 +26,26 @@ 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
+
31
+ ### Update notices
32
+
33
+ Interactive `android-localise` commands check PyPI for a newer stable release in
34
+ the background, at most once every 24 hours. When available, a note on stderr
35
+ shows `pip install --upgrade android-localisation`. The CLI never waits for the
36
+ lookup or installs an update automatically. A first short command may finish
37
+ before the lookup completes; a notice appears when a check completes during a
38
+ command or a later invocation can use its cached result.
39
+
40
+ Checks are skipped for help/version output, CI (`CI` set), redirected output and
41
+ pipes. Set `ANDROID_LOCALISE_NO_UPDATE_CHECK=1` to disable them, including when
42
+ using a local model offline. Lookup or cache failures stay silent and do not
43
+ change command exit codes. The small cache lives under
44
+ `%LOCALAPPDATA%/android-localisation` on Windows, or
45
+ `${XDG_CACHE_HOME:-~/.cache}/android-localisation` elsewhere. Only the package's
46
+ public release metadata is requested; API keys and Android resources are never
47
+ included.
48
+
29
49
  ---
30
50
 
31
51
  ## Quick start
@@ -37,7 +57,7 @@ android-localise translate --api-key YOUR_GEMINI_KEY
37
57
  # Step 2 — fix any formatting issues the LLM may have introduced
38
58
  android-localise fix
39
59
 
40
- # Step 3 — verify nothing will crash at runtime
60
+ # Step 3 — check resources and Java formatting
41
61
  android-localise verify
42
62
  ```
43
63
 
@@ -50,9 +70,9 @@ That's the full workflow. Run these three commands after every time you update y
50
70
  When you run `android-localise translate --api-key YOUR_KEY`, here's exactly what it does:
51
71
 
52
72
  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
73
+ 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`
74
+ 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
75
+ 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
76
  5. Waits 5 seconds between each language request to avoid hitting API rate limits
57
77
 
58
78
  **Defaults used when you don't specify anything:**
@@ -60,12 +80,14 @@ When you run `android-localise translate --api-key YOUR_KEY`, here's exactly wha
60
80
  | What | Default |
61
81
  |---|---|
62
82
  | Provider | Gemini |
63
- | Model | `gemini-3.5-flash` |
83
+ | Model | `gemini-3.8-flash` |
64
84
  | Source directory | `app/src/main/res` |
65
85
  | Delay between requests | 5 seconds |
66
86
  | App context | none (generic prompt) |
67
87
 
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.
88
+ 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**.
89
+
90
+ 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
91
 
70
92
  ---
71
93
 
@@ -79,7 +101,7 @@ For target languages, you have two options:
79
101
  ```bash
80
102
  android-localise translate --api-key YOUR_KEY --languages hi,es,fr,de
81
103
  ```
82
- This creates `values-hi/`, `values-es/`, `values-fr/`, `values-de/` folders and their `strings.xml` files automatically, then translates into each one.
104
+ This translates into Hindi, Spanish, French and German, creating each folder and `strings.xml` when its translation passes validation.
83
105
 
84
106
  **Option B — pre-create folders yourself:**
85
107
  ```
@@ -90,7 +112,7 @@ app/src/main/res/
90
112
  ├── values-es/
91
113
  └── values-fr/
92
114
  ```
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.
115
+ 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
116
 
95
117
  **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
118
 
@@ -125,6 +147,19 @@ android-localise translate \
125
147
  | `--base-url` | API endpoint for local/custom providers | — |
126
148
  | `--sleep` | Seconds to wait between language requests | `5.0` |
127
149
  | `--timeout` | Seconds to wait for each API response (up to 3 attempts on timeout) | `180` |
150
+ | `--missing-only` | Translate absent resources; retain existing translations | off (refresh whole file) |
151
+ | `--dry-run` | Generate and validate output, then print a diff without writing files | off |
152
+
153
+ **Preserve reviewed translations:**
154
+
155
+ ```bash
156
+ android-localise translate --languages hi,es --missing-only
157
+ android-localise translate --languages hi --missing-only --dry-run
158
+ ```
159
+
160
+ `--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.
161
+
162
+ 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
163
 
129
164
  ---
130
165
 
@@ -135,12 +170,14 @@ android-localise fix
135
170
  android-localise fix --res-dir path/to/res
136
171
  ```
137
172
 
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.
173
+ 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
174
 
140
175
  Strings marked `formatted="false"` are skipped — their `%` signs are literal, not format specifiers.
141
176
 
142
177
  Always run this before `verify` and before building.
143
178
 
179
+ `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.
180
+
144
181
  ---
145
182
 
146
183
  ### `verify`
@@ -150,10 +187,12 @@ android-localise verify
150
187
  android-localise verify --res-dir path/to/res
151
188
  ```
152
189
 
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.
190
+ 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
191
 
155
192
  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
193
 
194
+ `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.
195
+
157
196
  ---
158
197
 
159
198
  ### `models`
@@ -163,27 +202,33 @@ android-localise models # all providers
163
202
  android-localise models --provider openai # one provider
164
203
  ```
165
204
 
166
- Lists every available model and fallback for each provider.
205
+ 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
206
 
168
207
  ---
169
208
 
170
209
  ## Providers
171
210
 
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`.
211
+ 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
212
 
174
213
  | Provider | Default model | Fallbacks | API key env var |
175
214
  |---|---|---|---|
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` |
215
+ | `gemini` _(default)_ | `gemini-3.8-flash` | none | `GEMINI_API_KEY` |
216
+ | `openai` | `gpt-6-luna` | `gpt-6.1-sol` → `gpt-6-astra` | `OPENAI_API_KEY` |
217
+ | `anthropic` | `claude-sonnet-5-5` | `claude-opus-5-5` | `ANTHROPIC_API_KEY` |
179
218
  | `custom` | set with `--model` | none | `OPENAI_API_KEY` (optional) |
180
219
 
181
220
  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
221
 
222
+ 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.
223
+
224
+ 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.
225
+
183
226
  **Using OpenAI:**
184
227
  ```bash
185
228
  android-localise translate --provider openai --api-key YOUR_KEY
186
- android-localise translate --provider openai --model gpt-5.4-mini --api-key YOUR_KEY
229
+ android-localise translate --provider openai --model gpt-6-luna --api-key YOUR_KEY
230
+ # Optional stronger tier; different cost/latency
231
+ android-localise translate --provider openai --model gpt-6.1-sol --api-key YOUR_KEY
187
232
  ```
188
233
 
189
234
  **Using Anthropic:**
@@ -234,6 +279,8 @@ android-localise translate
234
279
  | `OPENAI_API_KEY` | `--provider openai` and `--provider custom` |
235
280
  | `ANTHROPIC_API_KEY` | `--provider anthropic` |
236
281
  | `API_KEY` | fallback for any provider if the provider-specific var is not set |
282
+ | `ANDROID_LOCALISE_NO_UPDATE_CHECK` | Set to `1` to disable update checks/notices |
283
+ | `CI` | When nonempty, skips update checks/notices |
237
284
 
238
285
  ---
239
286
 
@@ -262,15 +309,15 @@ android-localise verify
262
309
  ./gradlew assembleDebug
263
310
  ```
264
311
 
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.
312
+ 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
313
 
267
314
  ---
268
315
 
269
316
  ## Platform support
270
317
 
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.
318
+ 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
319
 
273
- We especially need help testing on:
320
+ I especially need help testing on:
274
321
 
275
322
  - **macOS** — `translate`, `fix`, `verify` (including `javac` / Android Studio terminal)
276
323
  - **Linux** — same workflow, plus common CI environments
@@ -281,7 +328,7 @@ If you use another OS, please try the [quick start](#quick-start) workflow and r
281
328
  - **Broken?** — open a [bug report](https://github.com/BharathKmalviya/android-llm-localization/issues/new?template=bug_report.md) with the full error output
282
329
  - **Want to help more?** — see [Contributing](#contributing) and [CONTRIBUTING.md](CONTRIBUTING.md)
283
330
 
284
- Cross-platform fixes and test notes in pull requests are very welcome.
331
+ PRs with cross-platform fixes and test notes are especially appreciated.
285
332
 
286
333
  ---
287
334
 
@@ -289,10 +336,13 @@ Cross-platform fixes and test notes in pull requests are very welcome.
289
336
 
290
337
  | Topic | Detail |
291
338
  |---|---|
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/` |
339
+ | **Platform testing** | I test on **Windows only** — macOS and Linux need community verification (see [Platform support](#platform-support)) |
340
+ | **Scope** | Reads `values/strings.xml` only. Strings, arrays and plurals within that file are checked; separate XML files are not scanned |
341
+ | **Plurals** | Preserves source quantities and item structure; does not generate target-language plural categories. Review plural completeness for each language |
342
+ | **Overwrite** | Normal runs refresh whole files. `--missing-only` retains existing resources but does not detect source changes |
343
+ | **Folder scan** | Recognizes language-first and Android `b+` locale forms, with optional trailing qualifiers. MCC/MNC-prefixed resource folders are not scanned |
344
+ | **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 |
345
+ | **Large files** | One request per locale; no automatic batching or resume cache. Incomplete output is rejected |
296
346
  | **Network** | `translate` requires internet access to reach the LLM API (except local `custom` providers) |
297
347
  | **JDK** | `verify` requires `javac` on your PATH |
298
348
 
@@ -305,6 +355,7 @@ Cross-platform fixes and test notes in pull requests are very welcome.
305
355
  | `Could not find English strings.xml` | Check `--res-dir` points to your `res/` folder and `values/strings.xml` exists |
306
356
  | `No locale directories found` | Add `--languages hi,es,fr` or create `values-<lang>/` folders manually |
307
357
  | API auth errors | Confirm your key env var or `--api-key` matches the `--provider` |
358
+ | Resource validation fails | Read the named resource error; check source/target keys, attributes, placeholders and markup. Existing files are retained |
308
359
  | `javac` not found | Install a JDK or run `verify` from Android Studio's terminal |
309
360
  | Build fails on apostrophes | Run `android-localise fix` before building |
310
361
  | `%` crashes at runtime | Run `android-localise verify` — it catches bad format specifiers before release |
@@ -317,17 +368,17 @@ Cross-platform fixes and test notes in pull requests are very welcome.
317
368
  - [ ] **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
369
  - [ ] **Smarter locale folder detection** — skip non-locale `values-*` qualifiers (`night`, `sw600dp`, `v21`, etc.) when scanning without `--languages`
319
370
  - [ ] **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)
371
+ - [ ] **Cross-platform verification** — confirm `translate`, `fix`, and `verify` on macOS and Linux (I currently test on Windows only)
321
372
 
322
373
  ---
323
374
 
324
375
  ## Contributing
325
376
 
326
- Bug reports, pull requests, and **cross-platform testing** are all welcome. For larger changes, open an issue first.
377
+ I welcome bug reports, pull requests, and **cross-platform testing**. For larger changes, please open an issue first.
327
378
 
328
379
  **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
380
 
330
- See [CONTRIBUTING.md](CONTRIBUTING.md) for the full development workflow, branch strategy, and release process.
381
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for the development workflow, branch strategy, and release process.
331
382
 
332
383
  ```bash
333
384
  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.2"
@@ -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
- def main():
16
+ def main(args=None):
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,23 +53,33 @@ 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
 
54
- args = parser.parse_args()
60
+ if args is None or isinstance(args, list):
61
+ args = parser.parse_args(args)
55
62
 
63
+ from android_localisation.updates import start_update_check, show_update_notice
64
+ update_state = start_update_check()
65
+ try:
66
+ return _run_command(args)
67
+ finally:
68
+ show_update_notice(update_state)
69
+
70
+
71
+ def _run_command(args):
56
72
  if args.command == "translate":
57
73
  from android_localisation.translate import main as run
58
- run(args)
74
+ return run(args)
59
75
 
60
76
  elif args.command == "fix":
61
77
  from android_localisation.fix import main as run
62
- run(args)
78
+ return run(args)
63
79
 
64
80
  elif args.command == "verify":
65
81
  from android_localisation.verify import main as run
66
- run(args)
82
+ return run(args)
67
83
 
68
84
  elif args.command == "models":
69
85
  from android_localisation.translate import PROVIDER_MODELS
@@ -80,9 +96,9 @@ def main():
80
96
  print(" → Any model name your local server supports (must use --model)")
81
97
  print()
82
98
  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")
99
+ print(" android-localise translate --provider openai --model gpt-6-luna --api-key KEY")
84
100
  print()
85
101
 
86
102
 
87
103
  if __name__ == "__main__":
88
- main()
104
+ raise SystemExit(main())