android-localisation 1.0.4__tar.gz → 1.0.5__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 (17) hide show
  1. {android_localisation-1.0.4/android_localisation.egg-info → android_localisation-1.0.5}/PKG-INFO +91 -15
  2. {android_localisation-1.0.4 → android_localisation-1.0.5}/README.md +90 -14
  3. {android_localisation-1.0.4 → android_localisation-1.0.5}/android_localisation/__init__.py +1 -1
  4. {android_localisation-1.0.4 → android_localisation-1.0.5}/android_localisation/cli.py +6 -1
  5. {android_localisation-1.0.4 → android_localisation-1.0.5}/android_localisation/translate.py +78 -55
  6. {android_localisation-1.0.4 → android_localisation-1.0.5/android_localisation.egg-info}/PKG-INFO +91 -15
  7. {android_localisation-1.0.4 → android_localisation-1.0.5}/pyproject.toml +1 -1
  8. {android_localisation-1.0.4 → android_localisation-1.0.5}/LICENSE +0 -0
  9. {android_localisation-1.0.4 → android_localisation-1.0.5}/MANIFEST.in +0 -0
  10. {android_localisation-1.0.4 → android_localisation-1.0.5}/android_localisation/fix.py +0 -0
  11. {android_localisation-1.0.4 → android_localisation-1.0.5}/android_localisation/java/VerifyStrings.java +0 -0
  12. {android_localisation-1.0.4 → android_localisation-1.0.5}/android_localisation/verify.py +0 -0
  13. {android_localisation-1.0.4 → android_localisation-1.0.5}/android_localisation.egg-info/SOURCES.txt +0 -0
  14. {android_localisation-1.0.4 → android_localisation-1.0.5}/android_localisation.egg-info/dependency_links.txt +0 -0
  15. {android_localisation-1.0.4 → android_localisation-1.0.5}/android_localisation.egg-info/entry_points.txt +0 -0
  16. {android_localisation-1.0.4 → android_localisation-1.0.5}/android_localisation.egg-info/top_level.txt +0 -0
  17. {android_localisation-1.0.4 → android_localisation-1.0.5}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: android-localisation
3
- Version: 1.0.4
3
+ Version: 1.0.5
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
@@ -26,13 +26,15 @@ Description-Content-Type: text/markdown
26
26
  License-File: LICENSE
27
27
  Dynamic: license-file
28
28
 
29
- # android-llm-localization
29
+ # android-localisation
30
30
 
31
31
  [![PyPI version](https://img.shields.io/pypi/v/android-localisation.svg)](https://pypi.org/project/android-localisation/)
32
32
  [![Python 3.8+](https://img.shields.io/badge/python-3.8+-blue.svg)](https://pypi.org/project/android-localisation/)
33
33
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
34
34
 
35
- Translate your Android `strings.xml` into multiple languages using AI — Gemini, OpenAI, Anthropic, or a local model via Ollama. No paid service, no CSV exports, no copy-paste.
35
+ **PyPI:** [`android-localisation`](https://pypi.org/project/android-localisation/) · **CLI:** `android-localise` · **Repo:** [android-llm-localization](https://github.com/BharathKmalviya/android-llm-localization)
36
+
37
+ Translate your Android `strings.xml` into multiple languages using AI — Gemini, OpenAI, Anthropic, or a local model via Ollama. No paid translation service, no CSV exports, no copy-paste.
36
38
 
37
39
  ---
38
40
 
@@ -86,7 +88,7 @@ When you run `android-localise translate --api-key YOUR_KEY`, here's exactly wha
86
88
  | What | Default |
87
89
  |---|---|
88
90
  | Provider | Gemini |
89
- | Model | `gemini-2.5-flash` |
91
+ | Model | `gemini-3.5-flash` |
90
92
  | Source directory | `app/src/main/res` |
91
93
  | Delay between requests | 5 seconds |
92
94
  | App context | none (generic prompt) |
@@ -150,6 +152,7 @@ android-localise translate \
150
152
  | `--res-dir` | Path to your `res/` folder | `app/src/main/res` |
151
153
  | `--base-url` | API endpoint for local/custom providers | — |
152
154
  | `--sleep` | Seconds to wait between language requests | `5.0` |
155
+ | `--timeout` | Seconds to wait for each API response (retries up to 3 times on timeout) | `180` |
153
156
 
154
157
  ---
155
158
 
@@ -157,10 +160,13 @@ android-localise translate \
157
160
 
158
161
  ```bash
159
162
  android-localise fix
163
+ android-localise fix --res-dir path/to/res
160
164
  ```
161
165
 
162
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.
163
167
 
168
+ Strings marked `formatted="false"` are skipped — their `%` signs are literal, not format specifiers.
169
+
164
170
  Always run this before `verify` and before building.
165
171
 
166
172
  ---
@@ -169,11 +175,12 @@ Always run this before `verify` and before building.
169
175
 
170
176
  ```bash
171
177
  android-localise verify
178
+ android-localise verify --res-dir path/to/res
172
179
  ```
173
180
 
174
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.
175
182
 
176
- 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.
183
+ 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.
177
184
 
178
185
  ---
179
186
 
@@ -190,21 +197,21 @@ Lists every available model and fallback for each provider.
190
197
 
191
198
  ## Providers
192
199
 
193
- By default the tool uses Gemini with `gemini-2.5-flash`. You can switch providers with `--provider` and optionally pin a specific model with `--model`.
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`.
194
201
 
195
202
  | Provider | Default model | Fallbacks | API key env var |
196
203
  |---|---|---|---|
197
- | `gemini` _(default)_ | `gemini-2.5-flash` | `gemini-2.0-flash` → `gemini-1.5-flash` → `gemini-1.5-pro` | `GEMINI_API_KEY` |
198
- | `openai` | `gpt-4o-mini` | `gpt-4o` → `gpt-3.5-turbo` | `OPENAI_API_KEY` |
199
- | `anthropic` | `claude-3-5-haiku-latest` | `claude-3-5-sonnet-latest` → `claude-3-opus-latest` | `ANTHROPIC_API_KEY` |
200
- | `custom` | set with `--model` | none | — |
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` |
207
+ | `custom` | set with `--model` | none | `OPENAI_API_KEY` (optional) |
201
208
 
202
209
  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.
203
210
 
204
211
  **Using OpenAI:**
205
212
  ```bash
206
213
  android-localise translate --provider openai --api-key YOUR_KEY
207
- android-localise translate --provider openai --model gpt-4o --api-key YOUR_KEY
214
+ android-localise translate --provider openai --model gpt-5.4-mini --api-key YOUR_KEY
208
215
  ```
209
216
 
210
217
  **Using Anthropic:**
@@ -239,6 +246,9 @@ export GEMINI_API_KEY=your_key
239
246
 
240
247
  # Windows PowerShell
241
248
  $env:GEMINI_API_KEY = "your_key"
249
+
250
+ # Windows CMD
251
+ set GEMINI_API_KEY=your_key
242
252
  ```
243
253
 
244
254
  Then just run:
@@ -251,19 +261,25 @@ android-localise translate
251
261
  | `GEMINI_API_KEY` | `--provider gemini` |
252
262
  | `OPENAI_API_KEY` | `--provider openai` and `--provider custom` |
253
263
  | `ANTHROPIC_API_KEY` | `--provider anthropic` |
264
+ | `API_KEY` | fallback for any provider if the provider-specific var is not set |
254
265
 
255
266
  ---
256
267
 
257
268
  ## Full workflow example
258
269
 
259
270
  ```bash
260
- # First time setup — create locale folders
271
+ # First time setup — create locale folders (macOS / Linux)
261
272
  mkdir -p app/src/main/res/values-hi
262
273
  mkdir -p app/src/main/res/values-es
263
274
  mkdir -p app/src/main/res/values-de
264
275
 
276
+ # Windows PowerShell
277
+ New-Item -ItemType Directory -Force app/src/main/res/values-hi
278
+ New-Item -ItemType Directory -Force app/src/main/res/values-es
279
+ New-Item -ItemType Directory -Force app/src/main/res/values-de
280
+
265
281
  # Set your key once
266
- export GEMINI_API_KEY=your_key
282
+ export GEMINI_API_KEY=your_key # or $env:GEMINI_API_KEY on Windows
267
283
 
268
284
  # Translate, fix, verify
269
285
  android-localise translate --app-context "a habit tracking app"
@@ -278,23 +294,83 @@ After this, whenever you add or change strings in your English `strings.xml`, ru
278
294
 
279
295
  ---
280
296
 
297
+ ## Platform support
298
+
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.
300
+
301
+ We especially need help testing on:
302
+
303
+ - **macOS** — `translate`, `fix`, `verify` (including `javac` / Android Studio terminal)
304
+ - **Linux** — same workflow, plus common CI environments
305
+
306
+ If you use another OS, please try the [quick start](#quick-start) workflow and report what you find:
307
+
308
+ - **Works?** — open a [GitHub issue](https://github.com/BharathKmalviya/android-llm-localization/issues) titled e.g. `Confirmed working on macOS 14` with your OS, Python version, and provider used
309
+ - **Broken?** — open a [bug report](https://github.com/BharathKmalviya/android-llm-localization/issues/new?template=bug_report.md) with the full error output
310
+ - **Want to help more?** — see [Contributing](#contributing) and [CONTRIBUTING.md](CONTRIBUTING.md)
311
+
312
+ Cross-platform fixes and test notes in pull requests are very welcome.
313
+
314
+ ---
315
+
316
+ ## Limitations
317
+
318
+ | Topic | Detail |
319
+ |---|---|
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/` |
324
+ | **Network** | `translate` requires internet access to reach the LLM API (except local `custom` providers) |
325
+ | **JDK** | `verify` requires `javac` on your PATH |
326
+
327
+ ---
328
+
329
+ ## Troubleshooting
330
+
331
+ | Problem | What to try |
332
+ |---|---|
333
+ | `Could not find English strings.xml` | Check `--res-dir` points to your `res/` folder and `values/strings.xml` exists |
334
+ | `No locale directories found` | Add `--languages hi,es,fr` or create `values-<lang>/` folders manually |
335
+ | API auth errors | Confirm your key env var or `--api-key` matches the `--provider` |
336
+ | `javac` not found | Install a JDK or run `verify` from Android Studio's terminal |
337
+ | Build fails on apostrophes | Run `android-localise fix` before building |
338
+ | `%` crashes at runtime | Run `android-localise verify` — it catches bad format specifiers before release |
339
+ | Wrong folder translated | Use `--languages` to target exact locale codes instead of folder scan |
340
+
341
+ ---
342
+
281
343
  ## Roadmap
282
344
 
283
345
  - [ ] **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
+ - [ ] **Smarter locale folder detection** — skip non-locale `values-*` qualifiers (`night`, `sw600dp`, `v21`, etc.) when scanning without `--languages`
347
+ - [ ] **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)
284
349
 
285
350
  ---
286
351
 
287
352
  ## Contributing
288
353
 
289
- Bug reports and pull requests are welcome. For larger changes, open an issue first.
354
+ Bug reports, pull requests, and **cross-platform testing** are all welcome. For larger changes, open an issue first.
355
+
356
+ **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
+
358
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for the full development workflow, branch strategy, and release process.
290
359
 
291
360
  ```bash
292
361
  git clone https://github.com/BharathKmalviya/android-llm-localization
293
362
  cd android-llm-localization
363
+ git checkout dev
294
364
  pip install -e .
295
365
  ```
296
366
 
297
- Releases are automated via GitHub Actions — bump the version in `pyproject.toml` and `__init__.py`, update `CHANGELOG.md`, and push to `master`.
367
+ Day-to-day work happens on the `dev` branch. Releases are merged to `master` via pull request, which triggers automated PyPI publishing.
368
+
369
+ ---
370
+
371
+ ## Security
372
+
373
+ To report a security vulnerability, see [SECURITY.md](SECURITY.md). Please do not open public issues for security-sensitive reports.
298
374
 
299
375
  ---
300
376
 
@@ -1,10 +1,12 @@
1
- # android-llm-localization
1
+ # android-localisation
2
2
 
3
3
  [![PyPI version](https://img.shields.io/pypi/v/android-localisation.svg)](https://pypi.org/project/android-localisation/)
4
4
  [![Python 3.8+](https://img.shields.io/badge/python-3.8+-blue.svg)](https://pypi.org/project/android-localisation/)
5
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
6
6
 
7
- Translate your Android `strings.xml` into multiple languages using AI — Gemini, OpenAI, Anthropic, or a local model via Ollama. No paid service, no CSV exports, no copy-paste.
7
+ **PyPI:** [`android-localisation`](https://pypi.org/project/android-localisation/) · **CLI:** `android-localise` · **Repo:** [android-llm-localization](https://github.com/BharathKmalviya/android-llm-localization)
8
+
9
+ Translate your Android `strings.xml` into multiple languages using AI — Gemini, OpenAI, Anthropic, or a local model via Ollama. No paid translation service, no CSV exports, no copy-paste.
8
10
 
9
11
  ---
10
12
 
@@ -58,7 +60,7 @@ When you run `android-localise translate --api-key YOUR_KEY`, here's exactly wha
58
60
  | What | Default |
59
61
  |---|---|
60
62
  | Provider | Gemini |
61
- | Model | `gemini-2.5-flash` |
63
+ | Model | `gemini-3.5-flash` |
62
64
  | Source directory | `app/src/main/res` |
63
65
  | Delay between requests | 5 seconds |
64
66
  | App context | none (generic prompt) |
@@ -122,6 +124,7 @@ android-localise translate \
122
124
  | `--res-dir` | Path to your `res/` folder | `app/src/main/res` |
123
125
  | `--base-url` | API endpoint for local/custom providers | — |
124
126
  | `--sleep` | Seconds to wait between language requests | `5.0` |
127
+ | `--timeout` | Seconds to wait for each API response (retries up to 3 times on timeout) | `180` |
125
128
 
126
129
  ---
127
130
 
@@ -129,10 +132,13 @@ android-localise translate \
129
132
 
130
133
  ```bash
131
134
  android-localise fix
135
+ android-localise fix --res-dir path/to/res
132
136
  ```
133
137
 
134
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.
135
139
 
140
+ Strings marked `formatted="false"` are skipped — their `%` signs are literal, not format specifiers.
141
+
136
142
  Always run this before `verify` and before building.
137
143
 
138
144
  ---
@@ -141,11 +147,12 @@ Always run this before `verify` and before building.
141
147
 
142
148
  ```bash
143
149
  android-localise verify
150
+ android-localise verify --res-dir path/to/res
144
151
  ```
145
152
 
146
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.
147
154
 
148
- 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.
155
+ 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.
149
156
 
150
157
  ---
151
158
 
@@ -162,21 +169,21 @@ Lists every available model and fallback for each provider.
162
169
 
163
170
  ## Providers
164
171
 
165
- By default the tool uses Gemini with `gemini-2.5-flash`. You can switch providers with `--provider` and optionally pin a specific model with `--model`.
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`.
166
173
 
167
174
  | Provider | Default model | Fallbacks | API key env var |
168
175
  |---|---|---|---|
169
- | `gemini` _(default)_ | `gemini-2.5-flash` | `gemini-2.0-flash` → `gemini-1.5-flash` → `gemini-1.5-pro` | `GEMINI_API_KEY` |
170
- | `openai` | `gpt-4o-mini` | `gpt-4o` → `gpt-3.5-turbo` | `OPENAI_API_KEY` |
171
- | `anthropic` | `claude-3-5-haiku-latest` | `claude-3-5-sonnet-latest` → `claude-3-opus-latest` | `ANTHROPIC_API_KEY` |
172
- | `custom` | set with `--model` | none | — |
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` |
179
+ | `custom` | set with `--model` | none | `OPENAI_API_KEY` (optional) |
173
180
 
174
181
  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.
175
182
 
176
183
  **Using OpenAI:**
177
184
  ```bash
178
185
  android-localise translate --provider openai --api-key YOUR_KEY
179
- android-localise translate --provider openai --model gpt-4o --api-key YOUR_KEY
186
+ android-localise translate --provider openai --model gpt-5.4-mini --api-key YOUR_KEY
180
187
  ```
181
188
 
182
189
  **Using Anthropic:**
@@ -211,6 +218,9 @@ export GEMINI_API_KEY=your_key
211
218
 
212
219
  # Windows PowerShell
213
220
  $env:GEMINI_API_KEY = "your_key"
221
+
222
+ # Windows CMD
223
+ set GEMINI_API_KEY=your_key
214
224
  ```
215
225
 
216
226
  Then just run:
@@ -223,19 +233,25 @@ android-localise translate
223
233
  | `GEMINI_API_KEY` | `--provider gemini` |
224
234
  | `OPENAI_API_KEY` | `--provider openai` and `--provider custom` |
225
235
  | `ANTHROPIC_API_KEY` | `--provider anthropic` |
236
+ | `API_KEY` | fallback for any provider if the provider-specific var is not set |
226
237
 
227
238
  ---
228
239
 
229
240
  ## Full workflow example
230
241
 
231
242
  ```bash
232
- # First time setup — create locale folders
243
+ # First time setup — create locale folders (macOS / Linux)
233
244
  mkdir -p app/src/main/res/values-hi
234
245
  mkdir -p app/src/main/res/values-es
235
246
  mkdir -p app/src/main/res/values-de
236
247
 
248
+ # Windows PowerShell
249
+ New-Item -ItemType Directory -Force app/src/main/res/values-hi
250
+ New-Item -ItemType Directory -Force app/src/main/res/values-es
251
+ New-Item -ItemType Directory -Force app/src/main/res/values-de
252
+
237
253
  # Set your key once
238
- export GEMINI_API_KEY=your_key
254
+ export GEMINI_API_KEY=your_key # or $env:GEMINI_API_KEY on Windows
239
255
 
240
256
  # Translate, fix, verify
241
257
  android-localise translate --app-context "a habit tracking app"
@@ -250,23 +266,83 @@ After this, whenever you add or change strings in your English `strings.xml`, ru
250
266
 
251
267
  ---
252
268
 
269
+ ## Platform support
270
+
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.
272
+
273
+ We especially need help testing on:
274
+
275
+ - **macOS** — `translate`, `fix`, `verify` (including `javac` / Android Studio terminal)
276
+ - **Linux** — same workflow, plus common CI environments
277
+
278
+ If you use another OS, please try the [quick start](#quick-start) workflow and report what you find:
279
+
280
+ - **Works?** — open a [GitHub issue](https://github.com/BharathKmalviya/android-llm-localization/issues) titled e.g. `Confirmed working on macOS 14` with your OS, Python version, and provider used
281
+ - **Broken?** — open a [bug report](https://github.com/BharathKmalviya/android-llm-localization/issues/new?template=bug_report.md) with the full error output
282
+ - **Want to help more?** — see [Contributing](#contributing) and [CONTRIBUTING.md](CONTRIBUTING.md)
283
+
284
+ Cross-platform fixes and test notes in pull requests are very welcome.
285
+
286
+ ---
287
+
288
+ ## Limitations
289
+
290
+ | Topic | Detail |
291
+ |---|---|
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/` |
296
+ | **Network** | `translate` requires internet access to reach the LLM API (except local `custom` providers) |
297
+ | **JDK** | `verify` requires `javac` on your PATH |
298
+
299
+ ---
300
+
301
+ ## Troubleshooting
302
+
303
+ | Problem | What to try |
304
+ |---|---|
305
+ | `Could not find English strings.xml` | Check `--res-dir` points to your `res/` folder and `values/strings.xml` exists |
306
+ | `No locale directories found` | Add `--languages hi,es,fr` or create `values-<lang>/` folders manually |
307
+ | API auth errors | Confirm your key env var or `--api-key` matches the `--provider` |
308
+ | `javac` not found | Install a JDK or run `verify` from Android Studio's terminal |
309
+ | Build fails on apostrophes | Run `android-localise fix` before building |
310
+ | `%` crashes at runtime | Run `android-localise verify` — it catches bad format specifiers before release |
311
+ | Wrong folder translated | Use `--languages` to target exact locale codes instead of folder scan |
312
+
313
+ ---
314
+
253
315
  ## Roadmap
254
316
 
255
317
  - [ ] **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
+ - [ ] **Smarter locale folder detection** — skip non-locale `values-*` qualifiers (`night`, `sw600dp`, `v21`, etc.) when scanning without `--languages`
319
+ - [ ] **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)
256
321
 
257
322
  ---
258
323
 
259
324
  ## Contributing
260
325
 
261
- Bug reports and pull requests are welcome. For larger changes, open an issue first.
326
+ Bug reports, pull requests, and **cross-platform testing** are all welcome. For larger changes, open an issue first.
327
+
328
+ **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
+
330
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for the full development workflow, branch strategy, and release process.
262
331
 
263
332
  ```bash
264
333
  git clone https://github.com/BharathKmalviya/android-llm-localization
265
334
  cd android-llm-localization
335
+ git checkout dev
266
336
  pip install -e .
267
337
  ```
268
338
 
269
- Releases are automated via GitHub Actions — bump the version in `pyproject.toml` and `__init__.py`, update `CHANGELOG.md`, and push to `master`.
339
+ Day-to-day work happens on the `dev` branch. Releases are merged to `master` via pull request, which triggers automated PyPI publishing.
340
+
341
+ ---
342
+
343
+ ## Security
344
+
345
+ To report a security vulnerability, see [SECURITY.md](SECURITY.md). Please do not open public issues for security-sensitive reports.
270
346
 
271
347
  ---
272
348
 
@@ -2,4 +2,4 @@
2
2
  android-localisation: Zero-dependency Android strings.xml translation using LLMs.
3
3
  """
4
4
 
5
- __version__ = "1.0.4"
5
+ __version__ = "1.0.5"
@@ -31,6 +31,11 @@ def main():
31
31
  translate_parser.add_argument("--base-url", help="Custom OpenAI-compatible endpoint URL (required for 'custom' provider)")
32
32
  translate_parser.add_argument("--app-context", help="Short description of your app for better translations")
33
33
  translate_parser.add_argument("--sleep", type=float, default=5.0, help="Seconds between API requests (default: 5.0)")
34
+ from android_localisation.translate import DEFAULT_API_TIMEOUT
35
+ translate_parser.add_argument(
36
+ "--timeout", type=float, default=DEFAULT_API_TIMEOUT,
37
+ help=f"Seconds to wait for each API response (default: {DEFAULT_API_TIMEOUT})",
38
+ )
34
39
  translate_parser.add_argument("--languages", help="Comma-separated language codes, e.g. hi,es,fr,de — creates folders and strings.xml automatically")
35
40
 
36
41
  # --- fix ---
@@ -75,7 +80,7 @@ def main():
75
80
  print(" → Any model name your local server supports (must use --model)")
76
81
  print()
77
82
  print(" Tip: use --model to pick any model, e.g:")
78
- print(" android-localise translate --provider openai --model gpt-4o --api-key KEY")
83
+ print(" android-localise translate --provider openai --model gpt-5.4-mini --api-key KEY")
79
84
  print()
80
85
 
81
86
 
@@ -1,32 +1,34 @@
1
1
  import os
2
2
  import re
3
3
  import time
4
+ import socket
4
5
  import argparse
5
6
  import urllib.request
6
7
  import urllib.error
7
8
  import json
8
9
 
9
10
  DEFAULT_RES_DIR = "app/src/main/res"
10
- API_TIMEOUT = 60 # seconds
11
+ DEFAULT_API_TIMEOUT = 180 # seconds (3 minutes) — large strings.xml files can exceed 60s
12
+ MAX_TIMEOUT_RETRIES = 2
11
13
 
12
14
  # Ordered list of models per provider.
13
15
  # First entry = default. Rest = automatic fallbacks (used only when user hasn't pinned a model).
14
16
  PROVIDER_MODELS = {
15
17
  "gemini": [
18
+ "gemini-3.5-flash",
19
+ "gemini-3.1-flash-lite",
16
20
  "gemini-2.5-flash",
17
- "gemini-2.0-flash",
18
- "gemini-1.5-flash",
19
- "gemini-1.5-pro",
21
+ "gemini-2.5-flash-lite",
20
22
  ],
21
23
  "openai": [
24
+ "gpt-5.4-mini",
25
+ "gpt-5-mini",
22
26
  "gpt-4o-mini",
23
- "gpt-4o",
24
- "gpt-3.5-turbo",
25
27
  ],
26
28
  "anthropic": [
27
- "claude-3-5-haiku-latest",
28
- "claude-3-5-sonnet-latest",
29
- "claude-3-opus-latest",
29
+ "claude-haiku-4-5",
30
+ "claude-sonnet-4-6",
31
+ "claude-opus-4-8",
30
32
  ],
31
33
  "custom": [], # user must specify --model
32
34
  }
@@ -133,27 +135,52 @@ def _is_model_not_found(http_code, body):
133
135
  ])
134
136
 
135
137
 
136
- def call_gemini(api_key, model, prompt):
138
+ def _urlopen_with_retries(req, provider_label, timeout):
139
+ """
140
+ Execute an HTTP request with timeout/network error handling and retries on timeout.
141
+ Returns (response_bytes, model_not_found).
142
+ """
143
+ for attempt in range(MAX_TIMEOUT_RETRIES + 1):
144
+ if attempt > 0:
145
+ wait = attempt * 3
146
+ print(f" 🔁 {provider_label} timed out — retrying ({attempt}/{MAX_TIMEOUT_RETRIES}) in {wait}s...")
147
+ time.sleep(wait)
148
+ try:
149
+ with urllib.request.urlopen(req, timeout=timeout) as response:
150
+ return response.read(), False
151
+ except urllib.error.HTTPError as e:
152
+ body = _read_error_body(e)
153
+ model_gone = _is_model_not_found(e.code, body)
154
+ print(f" ❌ {provider_label} API Error: {e.code} - {body}")
155
+ return None, model_gone
156
+ except (TimeoutError, socket.timeout):
157
+ if attempt < MAX_TIMEOUT_RETRIES:
158
+ continue
159
+ print(f" ❌ {provider_label} API timed out after {timeout}s ({MAX_TIMEOUT_RETRIES + 1} attempts)")
160
+ return None, False
161
+ except urllib.error.URLError as e:
162
+ print(f" ❌ {provider_label} network error: {e.reason}")
163
+ return None, False
164
+ return None, False
165
+
166
+
167
+ def call_gemini(api_key, model, prompt, timeout=DEFAULT_API_TIMEOUT):
137
168
  url = f"https://generativelanguage.googleapis.com/v1beta/models/{model}:generateContent?key={api_key}"
138
169
  headers = {"Content-Type": "application/json"}
139
170
  data = {"contents": [{"parts": [{"text": prompt}]}]}
140
171
  req = urllib.request.Request(url, data=json.dumps(data).encode("utf-8"), headers=headers, method="POST")
141
- try:
142
- with urllib.request.urlopen(req, timeout=API_TIMEOUT) as response:
143
- result = json.loads(response.read().decode("utf-8"))
144
- candidates = result.get("candidates", [])
145
- if not candidates:
146
- print(" ❌ Gemini returned no candidates.")
147
- return None, False
148
- return result["candidates"][0].get("content", {}).get("parts", [{}])[0].get("text", ""), False
149
- except urllib.error.HTTPError as e:
150
- body = _read_error_body(e)
151
- model_gone = _is_model_not_found(e.code, body)
152
- print(f" ❌ Gemini API Error: {e.code} - {body}")
172
+ raw, model_gone = _urlopen_with_retries(req, "Gemini", timeout)
173
+ if raw is None:
153
174
  return None, model_gone
175
+ result = json.loads(raw.decode("utf-8"))
176
+ candidates = result.get("candidates", [])
177
+ if not candidates:
178
+ print(" ❌ Gemini returned no candidates.")
179
+ return None, False
180
+ return result["candidates"][0].get("content", {}).get("parts", [{}])[0].get("text", ""), False
154
181
 
155
182
 
156
- def call_openai_compatible(api_key, base_url, model, prompt):
183
+ def call_openai_compatible(api_key, base_url, model, prompt, timeout=DEFAULT_API_TIMEOUT):
157
184
  headers = {
158
185
  "Content-Type": "application/json",
159
186
  "Authorization": f"Bearer {api_key or ''}",
@@ -163,22 +190,18 @@ def call_openai_compatible(api_key, base_url, model, prompt):
163
190
  "messages": [{"role": "user", "content": prompt}],
164
191
  }
165
192
  req = urllib.request.Request(base_url, data=json.dumps(data).encode("utf-8"), headers=headers, method="POST")
166
- try:
167
- with urllib.request.urlopen(req, timeout=API_TIMEOUT) as response:
168
- result = json.loads(response.read().decode("utf-8"))
169
- choices = result.get("choices", [])
170
- if not choices:
171
- print(" ❌ OpenAI returned no choices.")
172
- return None, False
173
- return choices[0].get("message", {}).get("content", ""), False
174
- except urllib.error.HTTPError as e:
175
- body = _read_error_body(e)
176
- model_gone = _is_model_not_found(e.code, body)
177
- print(f" ❌ OpenAI (compatible) API Error: {e.code} - {body}")
193
+ raw, model_gone = _urlopen_with_retries(req, "OpenAI (compatible)", timeout)
194
+ if raw is None:
178
195
  return None, model_gone
196
+ result = json.loads(raw.decode("utf-8"))
197
+ choices = result.get("choices", [])
198
+ if not choices:
199
+ print(" ❌ OpenAI returned no choices.")
200
+ return None, False
201
+ return choices[0].get("message", {}).get("content", ""), False
179
202
 
180
203
 
181
- def call_anthropic(api_key, model, prompt):
204
+ def call_anthropic(api_key, model, prompt, timeout=DEFAULT_API_TIMEOUT):
182
205
  url = "https://api.anthropic.com/v1/messages"
183
206
  headers = {
184
207
  "Content-Type": "application/json",
@@ -191,37 +214,33 @@ def call_anthropic(api_key, model, prompt):
191
214
  "messages": [{"role": "user", "content": prompt}],
192
215
  }
193
216
  req = urllib.request.Request(url, data=json.dumps(data).encode("utf-8"), headers=headers, method="POST")
194
- try:
195
- with urllib.request.urlopen(req, timeout=API_TIMEOUT) as response:
196
- result = json.loads(response.read().decode("utf-8"))
197
- content = result.get("content", [])
198
- if not content:
199
- print(" ❌ Anthropic returned no content.")
200
- return None, False
201
- return content[0].get("text", ""), False
202
- except urllib.error.HTTPError as e:
203
- body = _read_error_body(e)
204
- model_gone = _is_model_not_found(e.code, body)
205
- print(f" ❌ Anthropic API Error: {e.code} - {body}")
217
+ raw, model_gone = _urlopen_with_retries(req, "Anthropic", timeout)
218
+ if raw is None:
206
219
  return None, model_gone
220
+ result = json.loads(raw.decode("utf-8"))
221
+ content = result.get("content", [])
222
+ if not content:
223
+ print(" ❌ Anthropic returned no content.")
224
+ return None, False
225
+ return content[0].get("text", ""), False
207
226
 
208
227
 
209
- def _call_provider(provider, api_key, model, prompt, base_url=None):
228
+ def _call_provider(provider, api_key, model, prompt, base_url=None, timeout=DEFAULT_API_TIMEOUT):
210
229
  """Dispatches to the right API. Returns (text, model_not_found)."""
211
230
  if provider == "gemini":
212
- return call_gemini(api_key, model, prompt)
231
+ return call_gemini(api_key, model, prompt, timeout)
213
232
  elif provider == "openai":
214
233
  url = base_url if base_url else "https://api.openai.com/v1/chat/completions"
215
- return call_openai_compatible(api_key, url, model, prompt)
234
+ return call_openai_compatible(api_key, url, model, prompt, timeout)
216
235
  elif provider == "anthropic":
217
- return call_anthropic(api_key, model, prompt)
236
+ return call_anthropic(api_key, model, prompt, timeout)
218
237
  else:
219
238
  print(f"❌ Unknown provider: {provider}")
220
239
  return None, False
221
240
 
222
241
 
223
242
  def translate_xml(provider, api_key, model, source_xml, target_folder_name, app_context,
224
- base_url=None, fallback_models=None):
243
+ base_url=None, fallback_models=None, timeout=DEFAULT_API_TIMEOUT):
225
244
  """
226
245
  Calls the selected provider API to translate the XML.
227
246
  If the model is not found and fallback_models are provided, retries with the next one.
@@ -233,7 +252,9 @@ def translate_xml(provider, api_key, model, source_xml, target_folder_name, app_
233
252
  for attempt_model in models_to_try:
234
253
  if attempt_model != model:
235
254
  print(f" ↩️ Falling back to model: {attempt_model}")
236
- result, model_not_found = _call_provider(provider, api_key, attempt_model, prompt, base_url)
255
+ result, model_not_found = _call_provider(
256
+ provider, api_key, attempt_model, prompt, base_url, timeout
257
+ )
237
258
  if result is not None:
238
259
  return clean_xml_response(result), attempt_model
239
260
  if not model_not_found:
@@ -252,6 +273,8 @@ def _parse_args(args=None):
252
273
  parser.add_argument("--base-url")
253
274
  parser.add_argument("--app-context")
254
275
  parser.add_argument("--sleep", type=float, default=5.0)
276
+ parser.add_argument("--timeout", type=float, default=DEFAULT_API_TIMEOUT,
277
+ help=f"Seconds to wait for each API response (default: {DEFAULT_API_TIMEOUT})")
255
278
  parser.add_argument("--languages", help="Comma-separated language codes to translate into, e.g. hi,es,fr,de. Creates folders automatically if they don't exist.")
256
279
  return parser.parse_args(args)
257
280
 
@@ -333,7 +356,7 @@ def main(args=None):
333
356
 
334
357
  translated_xml, used_model = translate_xml(
335
358
  actual_provider, api_key, model, source_xml,
336
- folder, args.app_context, args.base_url, fallback_models
359
+ folder, args.app_context, args.base_url, fallback_models, args.timeout
337
360
  )
338
361
 
339
362
  if translated_xml and "<resources" in translated_xml and "</resources>" in translated_xml:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: android-localisation
3
- Version: 1.0.4
3
+ Version: 1.0.5
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
@@ -26,13 +26,15 @@ Description-Content-Type: text/markdown
26
26
  License-File: LICENSE
27
27
  Dynamic: license-file
28
28
 
29
- # android-llm-localization
29
+ # android-localisation
30
30
 
31
31
  [![PyPI version](https://img.shields.io/pypi/v/android-localisation.svg)](https://pypi.org/project/android-localisation/)
32
32
  [![Python 3.8+](https://img.shields.io/badge/python-3.8+-blue.svg)](https://pypi.org/project/android-localisation/)
33
33
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
34
34
 
35
- Translate your Android `strings.xml` into multiple languages using AI — Gemini, OpenAI, Anthropic, or a local model via Ollama. No paid service, no CSV exports, no copy-paste.
35
+ **PyPI:** [`android-localisation`](https://pypi.org/project/android-localisation/) · **CLI:** `android-localise` · **Repo:** [android-llm-localization](https://github.com/BharathKmalviya/android-llm-localization)
36
+
37
+ Translate your Android `strings.xml` into multiple languages using AI — Gemini, OpenAI, Anthropic, or a local model via Ollama. No paid translation service, no CSV exports, no copy-paste.
36
38
 
37
39
  ---
38
40
 
@@ -86,7 +88,7 @@ When you run `android-localise translate --api-key YOUR_KEY`, here's exactly wha
86
88
  | What | Default |
87
89
  |---|---|
88
90
  | Provider | Gemini |
89
- | Model | `gemini-2.5-flash` |
91
+ | Model | `gemini-3.5-flash` |
90
92
  | Source directory | `app/src/main/res` |
91
93
  | Delay between requests | 5 seconds |
92
94
  | App context | none (generic prompt) |
@@ -150,6 +152,7 @@ android-localise translate \
150
152
  | `--res-dir` | Path to your `res/` folder | `app/src/main/res` |
151
153
  | `--base-url` | API endpoint for local/custom providers | — |
152
154
  | `--sleep` | Seconds to wait between language requests | `5.0` |
155
+ | `--timeout` | Seconds to wait for each API response (retries up to 3 times on timeout) | `180` |
153
156
 
154
157
  ---
155
158
 
@@ -157,10 +160,13 @@ android-localise translate \
157
160
 
158
161
  ```bash
159
162
  android-localise fix
163
+ android-localise fix --res-dir path/to/res
160
164
  ```
161
165
 
162
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.
163
167
 
168
+ Strings marked `formatted="false"` are skipped — their `%` signs are literal, not format specifiers.
169
+
164
170
  Always run this before `verify` and before building.
165
171
 
166
172
  ---
@@ -169,11 +175,12 @@ Always run this before `verify` and before building.
169
175
 
170
176
  ```bash
171
177
  android-localise verify
178
+ android-localise verify --res-dir path/to/res
172
179
  ```
173
180
 
174
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.
175
182
 
176
- 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.
183
+ 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.
177
184
 
178
185
  ---
179
186
 
@@ -190,21 +197,21 @@ Lists every available model and fallback for each provider.
190
197
 
191
198
  ## Providers
192
199
 
193
- By default the tool uses Gemini with `gemini-2.5-flash`. You can switch providers with `--provider` and optionally pin a specific model with `--model`.
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`.
194
201
 
195
202
  | Provider | Default model | Fallbacks | API key env var |
196
203
  |---|---|---|---|
197
- | `gemini` _(default)_ | `gemini-2.5-flash` | `gemini-2.0-flash` → `gemini-1.5-flash` → `gemini-1.5-pro` | `GEMINI_API_KEY` |
198
- | `openai` | `gpt-4o-mini` | `gpt-4o` → `gpt-3.5-turbo` | `OPENAI_API_KEY` |
199
- | `anthropic` | `claude-3-5-haiku-latest` | `claude-3-5-sonnet-latest` → `claude-3-opus-latest` | `ANTHROPIC_API_KEY` |
200
- | `custom` | set with `--model` | none | — |
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` |
207
+ | `custom` | set with `--model` | none | `OPENAI_API_KEY` (optional) |
201
208
 
202
209
  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.
203
210
 
204
211
  **Using OpenAI:**
205
212
  ```bash
206
213
  android-localise translate --provider openai --api-key YOUR_KEY
207
- android-localise translate --provider openai --model gpt-4o --api-key YOUR_KEY
214
+ android-localise translate --provider openai --model gpt-5.4-mini --api-key YOUR_KEY
208
215
  ```
209
216
 
210
217
  **Using Anthropic:**
@@ -239,6 +246,9 @@ export GEMINI_API_KEY=your_key
239
246
 
240
247
  # Windows PowerShell
241
248
  $env:GEMINI_API_KEY = "your_key"
249
+
250
+ # Windows CMD
251
+ set GEMINI_API_KEY=your_key
242
252
  ```
243
253
 
244
254
  Then just run:
@@ -251,19 +261,25 @@ android-localise translate
251
261
  | `GEMINI_API_KEY` | `--provider gemini` |
252
262
  | `OPENAI_API_KEY` | `--provider openai` and `--provider custom` |
253
263
  | `ANTHROPIC_API_KEY` | `--provider anthropic` |
264
+ | `API_KEY` | fallback for any provider if the provider-specific var is not set |
254
265
 
255
266
  ---
256
267
 
257
268
  ## Full workflow example
258
269
 
259
270
  ```bash
260
- # First time setup — create locale folders
271
+ # First time setup — create locale folders (macOS / Linux)
261
272
  mkdir -p app/src/main/res/values-hi
262
273
  mkdir -p app/src/main/res/values-es
263
274
  mkdir -p app/src/main/res/values-de
264
275
 
276
+ # Windows PowerShell
277
+ New-Item -ItemType Directory -Force app/src/main/res/values-hi
278
+ New-Item -ItemType Directory -Force app/src/main/res/values-es
279
+ New-Item -ItemType Directory -Force app/src/main/res/values-de
280
+
265
281
  # Set your key once
266
- export GEMINI_API_KEY=your_key
282
+ export GEMINI_API_KEY=your_key # or $env:GEMINI_API_KEY on Windows
267
283
 
268
284
  # Translate, fix, verify
269
285
  android-localise translate --app-context "a habit tracking app"
@@ -278,23 +294,83 @@ After this, whenever you add or change strings in your English `strings.xml`, ru
278
294
 
279
295
  ---
280
296
 
297
+ ## Platform support
298
+
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.
300
+
301
+ We especially need help testing on:
302
+
303
+ - **macOS** — `translate`, `fix`, `verify` (including `javac` / Android Studio terminal)
304
+ - **Linux** — same workflow, plus common CI environments
305
+
306
+ If you use another OS, please try the [quick start](#quick-start) workflow and report what you find:
307
+
308
+ - **Works?** — open a [GitHub issue](https://github.com/BharathKmalviya/android-llm-localization/issues) titled e.g. `Confirmed working on macOS 14` with your OS, Python version, and provider used
309
+ - **Broken?** — open a [bug report](https://github.com/BharathKmalviya/android-llm-localization/issues/new?template=bug_report.md) with the full error output
310
+ - **Want to help more?** — see [Contributing](#contributing) and [CONTRIBUTING.md](CONTRIBUTING.md)
311
+
312
+ Cross-platform fixes and test notes in pull requests are very welcome.
313
+
314
+ ---
315
+
316
+ ## Limitations
317
+
318
+ | Topic | Detail |
319
+ |---|---|
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/` |
324
+ | **Network** | `translate` requires internet access to reach the LLM API (except local `custom` providers) |
325
+ | **JDK** | `verify` requires `javac` on your PATH |
326
+
327
+ ---
328
+
329
+ ## Troubleshooting
330
+
331
+ | Problem | What to try |
332
+ |---|---|
333
+ | `Could not find English strings.xml` | Check `--res-dir` points to your `res/` folder and `values/strings.xml` exists |
334
+ | `No locale directories found` | Add `--languages hi,es,fr` or create `values-<lang>/` folders manually |
335
+ | API auth errors | Confirm your key env var or `--api-key` matches the `--provider` |
336
+ | `javac` not found | Install a JDK or run `verify` from Android Studio's terminal |
337
+ | Build fails on apostrophes | Run `android-localise fix` before building |
338
+ | `%` crashes at runtime | Run `android-localise verify` — it catches bad format specifiers before release |
339
+ | Wrong folder translated | Use `--languages` to target exact locale codes instead of folder scan |
340
+
341
+ ---
342
+
281
343
  ## Roadmap
282
344
 
283
345
  - [ ] **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
+ - [ ] **Smarter locale folder detection** — skip non-locale `values-*` qualifiers (`night`, `sw600dp`, `v21`, etc.) when scanning without `--languages`
347
+ - [ ] **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)
284
349
 
285
350
  ---
286
351
 
287
352
  ## Contributing
288
353
 
289
- Bug reports and pull requests are welcome. For larger changes, open an issue first.
354
+ Bug reports, pull requests, and **cross-platform testing** are all welcome. For larger changes, open an issue first.
355
+
356
+ **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
+
358
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for the full development workflow, branch strategy, and release process.
290
359
 
291
360
  ```bash
292
361
  git clone https://github.com/BharathKmalviya/android-llm-localization
293
362
  cd android-llm-localization
363
+ git checkout dev
294
364
  pip install -e .
295
365
  ```
296
366
 
297
- Releases are automated via GitHub Actions — bump the version in `pyproject.toml` and `__init__.py`, update `CHANGELOG.md`, and push to `master`.
367
+ Day-to-day work happens on the `dev` branch. Releases are merged to `master` via pull request, which triggers automated PyPI publishing.
368
+
369
+ ---
370
+
371
+ ## Security
372
+
373
+ To report a security vulnerability, see [SECURITY.md](SECURITY.md). Please do not open public issues for security-sensitive reports.
298
374
 
299
375
  ---
300
376
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "android-localisation"
7
- version = "1.0.4"
7
+ version = "1.0.5"
8
8
  description = "Zero-dependency Android strings.xml translation and verification using LLMs (Gemini, OpenAI, Anthropic, Ollama)."
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }