android-localisation 1.0.0__tar.gz → 1.0.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.
- android_localisation-1.0.2/PKG-INFO +303 -0
- android_localisation-1.0.2/README.md +275 -0
- {android_localisation-1.0.0 → android_localisation-1.0.2}/android_localisation/__init__.py +1 -1
- {android_localisation-1.0.0 → android_localisation-1.0.2}/android_localisation/cli.py +1 -0
- {android_localisation-1.0.0 → android_localisation-1.0.2}/android_localisation/translate.py +40 -4
- android_localisation-1.0.2/android_localisation.egg-info/PKG-INFO +303 -0
- {android_localisation-1.0.0 → android_localisation-1.0.2}/pyproject.toml +2 -2
- {android_localisation-1.0.0 → android_localisation-1.0.2}/setup.cfg +4 -4
- android_localisation-1.0.0/PKG-INFO +0 -169
- android_localisation-1.0.0/README.md +0 -141
- android_localisation-1.0.0/android_localisation.egg-info/PKG-INFO +0 -169
- {android_localisation-1.0.0 → android_localisation-1.0.2}/LICENSE +0 -0
- {android_localisation-1.0.0 → android_localisation-1.0.2}/MANIFEST.in +0 -0
- {android_localisation-1.0.0 → android_localisation-1.0.2}/android_localisation/fix.py +0 -0
- {android_localisation-1.0.0 → android_localisation-1.0.2}/android_localisation/java/VerifyStrings.java +0 -0
- {android_localisation-1.0.0 → android_localisation-1.0.2}/android_localisation/verify.py +0 -0
- {android_localisation-1.0.0 → android_localisation-1.0.2}/android_localisation.egg-info/SOURCES.txt +0 -0
- {android_localisation-1.0.0 → android_localisation-1.0.2}/android_localisation.egg-info/dependency_links.txt +0 -0
- {android_localisation-1.0.0 → android_localisation-1.0.2}/android_localisation.egg-info/entry_points.txt +0 -0
- {android_localisation-1.0.0 → android_localisation-1.0.2}/android_localisation.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: android-localisation
|
|
3
|
+
Version: 1.0.2
|
|
4
|
+
Summary: Zero-dependency Android strings.xml translation and verification using LLMs (Gemini, OpenAI, Anthropic, Ollama).
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/BharathKmalviya/android-llm-localization
|
|
7
|
+
Project-URL: Repository, https://github.com/BharathKmalviya/android-llm-localization
|
|
8
|
+
Project-URL: Issues, https://github.com/BharathKmalviya/android-llm-localization/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/BharathKmalviya/android-llm-localization/blob/master/CHANGELOG.md
|
|
10
|
+
Keywords: android,localisation,localization,strings,strings-xml,translation,llm,gemini,openai,anthropic,ollama,i18n,l10n,android-development,mobile,cli
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Topic :: Software Development :: Internationalization
|
|
23
|
+
Classifier: Topic :: Software Development :: Localization
|
|
24
|
+
Requires-Python: >=3.8
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
|
|
29
|
+
# android-llm-localization
|
|
30
|
+
|
|
31
|
+
[](https://pypi.org/project/android-localisation/)
|
|
32
|
+
[](https://pypi.org/project/android-localisation/)
|
|
33
|
+
[](LICENSE)
|
|
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.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## The problem
|
|
40
|
+
|
|
41
|
+
Localizing an Android app the usual way means exporting strings, running them through Google Translate or some dashboard, cleaning up the output, and re-importing — for every language, every update. It's slow, error-prone, and the translations often feel robotic.
|
|
42
|
+
|
|
43
|
+
This tool does it differently. It reads your `strings.xml`, sends it to an LLM with context about your app, and writes the translated files directly into your project. The model understands UI language, keeps format specifiers intact, and produces natural-sounding output rather than word-for-word translations.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Installation
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pip install android-localisation
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Requires Python 3.8+. No other dependencies.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Quick start
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
# Step 1 — translate
|
|
61
|
+
android-localise translate --api-key YOUR_GEMINI_KEY
|
|
62
|
+
|
|
63
|
+
# Step 2 — fix any formatting issues the LLM may have introduced
|
|
64
|
+
android-localise fix
|
|
65
|
+
|
|
66
|
+
# Step 3 — verify nothing will crash at runtime
|
|
67
|
+
android-localise verify
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
That's the full workflow. Run these three commands after every time you update your English strings.
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## What happens when you run translate
|
|
75
|
+
|
|
76
|
+
When you run `android-localise translate --api-key YOUR_KEY`, here's exactly what it does:
|
|
77
|
+
|
|
78
|
+
1. Looks for `app/src/main/res/values/strings.xml` — this is your English source
|
|
79
|
+
2. If `--languages` is provided, creates any missing `values-<lang>/` folders automatically. Otherwise scans the `res/` directory for existing `values-*` folders
|
|
80
|
+
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`
|
|
81
|
+
4. Writes the translated `strings.xml` directly into each locale folder
|
|
82
|
+
5. Waits 5 seconds between each language request to avoid hitting API rate limits
|
|
83
|
+
|
|
84
|
+
**Defaults used when you don't specify anything:**
|
|
85
|
+
|
|
86
|
+
| What | Default |
|
|
87
|
+
|---|---|
|
|
88
|
+
| Provider | Gemini |
|
|
89
|
+
| Model | `gemini-2.5-flash` |
|
|
90
|
+
| Source directory | `app/src/main/res` |
|
|
91
|
+
| Delay between requests | 5 seconds |
|
|
92
|
+
| App context | none (generic prompt) |
|
|
93
|
+
|
|
94
|
+
Nothing is modified unless the translation comes back with valid XML. If a request fails, that language is skipped and logged — other languages continue.
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Setup
|
|
99
|
+
|
|
100
|
+
The only requirement is that `app/src/main/res/values/strings.xml` exists — your English source file.
|
|
101
|
+
|
|
102
|
+
For target languages, you have two options:
|
|
103
|
+
|
|
104
|
+
**Option A — let the tool create everything:**
|
|
105
|
+
```bash
|
|
106
|
+
android-localise translate --api-key YOUR_KEY --languages hi,es,fr,de
|
|
107
|
+
```
|
|
108
|
+
This creates `values-hi/`, `values-es/`, `values-fr/`, `values-de/` folders and their `strings.xml` files automatically, then translates into each one.
|
|
109
|
+
|
|
110
|
+
**Option B — pre-create folders yourself:**
|
|
111
|
+
```
|
|
112
|
+
app/src/main/res/
|
|
113
|
+
├── values/ ← your English source (must exist)
|
|
114
|
+
│ └── strings.xml
|
|
115
|
+
├── values-hi/ ← empty folder is fine
|
|
116
|
+
├── values-es/
|
|
117
|
+
└── values-fr/
|
|
118
|
+
```
|
|
119
|
+
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.
|
|
120
|
+
|
|
121
|
+
**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.
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Commands
|
|
126
|
+
|
|
127
|
+
### `translate`
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
android-localise translate --api-key YOUR_KEY
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Add `--app-context` with a one-line description of your app. This meaningfully improves translation quality — the model knows whether "record" means a music track, a health log, or a database entry:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
android-localise translate \
|
|
137
|
+
--api-key YOUR_KEY \
|
|
138
|
+
--app-context "a workout tracking app for gym beginners"
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
**All flags:**
|
|
142
|
+
|
|
143
|
+
| Flag | What it does | Default |
|
|
144
|
+
|---|---|---|
|
|
145
|
+
| `--api-key` | Your API key | reads from env var |
|
|
146
|
+
| `--provider` | Which AI to use: `gemini` `openai` `anthropic` `custom` | `gemini` |
|
|
147
|
+
| `--model` | Specific model to use | see [Providers](#providers) |
|
|
148
|
+
| `--languages` | Comma-separated language codes — creates folders and files automatically | — |
|
|
149
|
+
| `--app-context` | One-line description of your app | — |
|
|
150
|
+
| `--res-dir` | Path to your `res/` folder | `app/src/main/res` |
|
|
151
|
+
| `--base-url` | API endpoint for local/custom providers | — |
|
|
152
|
+
| `--sleep` | Seconds to wait between language requests | `5.0` |
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
### `fix`
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
android-localise fix
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
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
|
+
|
|
164
|
+
Always run this before `verify` and before building.
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
### `verify`
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
android-localise verify
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
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
|
+
|
|
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.
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
### `models`
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
android-localise models # all providers
|
|
184
|
+
android-localise models --provider openai # one provider
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Lists every available model and fallback for each provider.
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## Providers
|
|
192
|
+
|
|
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`.
|
|
194
|
+
|
|
195
|
+
| Provider | Default model | Fallbacks | API key env var |
|
|
196
|
+
|---|---|---|---|
|
|
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 | — |
|
|
201
|
+
|
|
202
|
+
If the default model returns a "model not found" error (e.g. it was deprecated), the tool automatically retries with the next fallback. If you pin a model with `--model`, no fallback is used.
|
|
203
|
+
|
|
204
|
+
**Using OpenAI:**
|
|
205
|
+
```bash
|
|
206
|
+
android-localise translate --provider openai --api-key YOUR_KEY
|
|
207
|
+
android-localise translate --provider openai --model gpt-4o --api-key YOUR_KEY
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
**Using Anthropic:**
|
|
211
|
+
```bash
|
|
212
|
+
android-localise translate --provider anthropic --api-key YOUR_KEY
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
**Using a local model (no API key needed):**
|
|
216
|
+
```bash
|
|
217
|
+
# Ollama
|
|
218
|
+
android-localise translate \
|
|
219
|
+
--provider custom \
|
|
220
|
+
--base-url http://localhost:11434/v1/chat/completions \
|
|
221
|
+
--model llama3
|
|
222
|
+
|
|
223
|
+
# LM Studio
|
|
224
|
+
android-localise translate \
|
|
225
|
+
--provider custom \
|
|
226
|
+
--base-url http://localhost:1234/v1/chat/completions \
|
|
227
|
+
--model mistral
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## Environment variables
|
|
233
|
+
|
|
234
|
+
Set your API key as an env variable so you don't have to pass it every time:
|
|
235
|
+
|
|
236
|
+
```bash
|
|
237
|
+
# macOS / Linux
|
|
238
|
+
export GEMINI_API_KEY=your_key
|
|
239
|
+
|
|
240
|
+
# Windows PowerShell
|
|
241
|
+
$env:GEMINI_API_KEY = "your_key"
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
Then just run:
|
|
245
|
+
```bash
|
|
246
|
+
android-localise translate
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
| Variable | Used by |
|
|
250
|
+
|---|---|
|
|
251
|
+
| `GEMINI_API_KEY` | `--provider gemini` |
|
|
252
|
+
| `OPENAI_API_KEY` | `--provider openai` and `--provider custom` |
|
|
253
|
+
| `ANTHROPIC_API_KEY` | `--provider anthropic` |
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
## Full workflow example
|
|
258
|
+
|
|
259
|
+
```bash
|
|
260
|
+
# First time setup — create locale folders
|
|
261
|
+
mkdir -p app/src/main/res/values-hi
|
|
262
|
+
mkdir -p app/src/main/res/values-es
|
|
263
|
+
mkdir -p app/src/main/res/values-de
|
|
264
|
+
|
|
265
|
+
# Set your key once
|
|
266
|
+
export GEMINI_API_KEY=your_key
|
|
267
|
+
|
|
268
|
+
# Translate, fix, verify
|
|
269
|
+
android-localise translate --app-context "a habit tracking app"
|
|
270
|
+
android-localise fix
|
|
271
|
+
android-localise verify
|
|
272
|
+
|
|
273
|
+
# Build your app as usual
|
|
274
|
+
./gradlew assembleDebug
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
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.
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
## Roadmap
|
|
282
|
+
|
|
283
|
+
- [ ] **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.
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
## Contributing
|
|
288
|
+
|
|
289
|
+
Bug reports and pull requests are welcome. For larger changes, open an issue first.
|
|
290
|
+
|
|
291
|
+
```bash
|
|
292
|
+
git clone https://github.com/BharathKmalviya/android-llm-localization
|
|
293
|
+
cd android-llm-localization
|
|
294
|
+
pip install -e .
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
Releases are automated via GitHub Actions — bump the version in `pyproject.toml` and `__init__.py`, update `CHANGELOG.md`, and push to `master`.
|
|
298
|
+
|
|
299
|
+
---
|
|
300
|
+
|
|
301
|
+
## License
|
|
302
|
+
|
|
303
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
# android-llm-localization
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/android-localisation/)
|
|
4
|
+
[](https://pypi.org/project/android-localisation/)
|
|
5
|
+
[](LICENSE)
|
|
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.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## The problem
|
|
12
|
+
|
|
13
|
+
Localizing an Android app the usual way means exporting strings, running them through Google Translate or some dashboard, cleaning up the output, and re-importing — for every language, every update. It's slow, error-prone, and the translations often feel robotic.
|
|
14
|
+
|
|
15
|
+
This tool does it differently. It reads your `strings.xml`, sends it to an LLM with context about your app, and writes the translated files directly into your project. The model understands UI language, keeps format specifiers intact, and produces natural-sounding output rather than word-for-word translations.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pip install android-localisation
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Requires Python 3.8+. No other dependencies.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Quick start
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# Step 1 — translate
|
|
33
|
+
android-localise translate --api-key YOUR_GEMINI_KEY
|
|
34
|
+
|
|
35
|
+
# Step 2 — fix any formatting issues the LLM may have introduced
|
|
36
|
+
android-localise fix
|
|
37
|
+
|
|
38
|
+
# Step 3 — verify nothing will crash at runtime
|
|
39
|
+
android-localise verify
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
That's the full workflow. Run these three commands after every time you update your English strings.
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## What happens when you run translate
|
|
47
|
+
|
|
48
|
+
When you run `android-localise translate --api-key YOUR_KEY`, here's exactly what it does:
|
|
49
|
+
|
|
50
|
+
1. Looks for `app/src/main/res/values/strings.xml` — this is your English source
|
|
51
|
+
2. If `--languages` is provided, creates any missing `values-<lang>/` folders automatically. Otherwise scans the `res/` directory for existing `values-*` folders
|
|
52
|
+
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`
|
|
53
|
+
4. Writes the translated `strings.xml` directly into each locale folder
|
|
54
|
+
5. Waits 5 seconds between each language request to avoid hitting API rate limits
|
|
55
|
+
|
|
56
|
+
**Defaults used when you don't specify anything:**
|
|
57
|
+
|
|
58
|
+
| What | Default |
|
|
59
|
+
|---|---|
|
|
60
|
+
| Provider | Gemini |
|
|
61
|
+
| Model | `gemini-2.5-flash` |
|
|
62
|
+
| Source directory | `app/src/main/res` |
|
|
63
|
+
| Delay between requests | 5 seconds |
|
|
64
|
+
| App context | none (generic prompt) |
|
|
65
|
+
|
|
66
|
+
Nothing is modified unless the translation comes back with valid XML. If a request fails, that language is skipped and logged — other languages continue.
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Setup
|
|
71
|
+
|
|
72
|
+
The only requirement is that `app/src/main/res/values/strings.xml` exists — your English source file.
|
|
73
|
+
|
|
74
|
+
For target languages, you have two options:
|
|
75
|
+
|
|
76
|
+
**Option A — let the tool create everything:**
|
|
77
|
+
```bash
|
|
78
|
+
android-localise translate --api-key YOUR_KEY --languages hi,es,fr,de
|
|
79
|
+
```
|
|
80
|
+
This creates `values-hi/`, `values-es/`, `values-fr/`, `values-de/` folders and their `strings.xml` files automatically, then translates into each one.
|
|
81
|
+
|
|
82
|
+
**Option B — pre-create folders yourself:**
|
|
83
|
+
```
|
|
84
|
+
app/src/main/res/
|
|
85
|
+
├── values/ ← your English source (must exist)
|
|
86
|
+
│ └── strings.xml
|
|
87
|
+
├── values-hi/ ← empty folder is fine
|
|
88
|
+
├── values-es/
|
|
89
|
+
└── values-fr/
|
|
90
|
+
```
|
|
91
|
+
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.
|
|
92
|
+
|
|
93
|
+
**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.
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Commands
|
|
98
|
+
|
|
99
|
+
### `translate`
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
android-localise translate --api-key YOUR_KEY
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Add `--app-context` with a one-line description of your app. This meaningfully improves translation quality — the model knows whether "record" means a music track, a health log, or a database entry:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
android-localise translate \
|
|
109
|
+
--api-key YOUR_KEY \
|
|
110
|
+
--app-context "a workout tracking app for gym beginners"
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
**All flags:**
|
|
114
|
+
|
|
115
|
+
| Flag | What it does | Default |
|
|
116
|
+
|---|---|---|
|
|
117
|
+
| `--api-key` | Your API key | reads from env var |
|
|
118
|
+
| `--provider` | Which AI to use: `gemini` `openai` `anthropic` `custom` | `gemini` |
|
|
119
|
+
| `--model` | Specific model to use | see [Providers](#providers) |
|
|
120
|
+
| `--languages` | Comma-separated language codes — creates folders and files automatically | — |
|
|
121
|
+
| `--app-context` | One-line description of your app | — |
|
|
122
|
+
| `--res-dir` | Path to your `res/` folder | `app/src/main/res` |
|
|
123
|
+
| `--base-url` | API endpoint for local/custom providers | — |
|
|
124
|
+
| `--sleep` | Seconds to wait between language requests | `5.0` |
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
### `fix`
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
android-localise fix
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
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
|
+
|
|
136
|
+
Always run this before `verify` and before building.
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
### `verify`
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
android-localise verify
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
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
|
+
|
|
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.
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
### `models`
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
android-localise models # all providers
|
|
156
|
+
android-localise models --provider openai # one provider
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Lists every available model and fallback for each provider.
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Providers
|
|
164
|
+
|
|
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`.
|
|
166
|
+
|
|
167
|
+
| Provider | Default model | Fallbacks | API key env var |
|
|
168
|
+
|---|---|---|---|
|
|
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 | — |
|
|
173
|
+
|
|
174
|
+
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
|
+
|
|
176
|
+
**Using OpenAI:**
|
|
177
|
+
```bash
|
|
178
|
+
android-localise translate --provider openai --api-key YOUR_KEY
|
|
179
|
+
android-localise translate --provider openai --model gpt-4o --api-key YOUR_KEY
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
**Using Anthropic:**
|
|
183
|
+
```bash
|
|
184
|
+
android-localise translate --provider anthropic --api-key YOUR_KEY
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
**Using a local model (no API key needed):**
|
|
188
|
+
```bash
|
|
189
|
+
# Ollama
|
|
190
|
+
android-localise translate \
|
|
191
|
+
--provider custom \
|
|
192
|
+
--base-url http://localhost:11434/v1/chat/completions \
|
|
193
|
+
--model llama3
|
|
194
|
+
|
|
195
|
+
# LM Studio
|
|
196
|
+
android-localise translate \
|
|
197
|
+
--provider custom \
|
|
198
|
+
--base-url http://localhost:1234/v1/chat/completions \
|
|
199
|
+
--model mistral
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## Environment variables
|
|
205
|
+
|
|
206
|
+
Set your API key as an env variable so you don't have to pass it every time:
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
# macOS / Linux
|
|
210
|
+
export GEMINI_API_KEY=your_key
|
|
211
|
+
|
|
212
|
+
# Windows PowerShell
|
|
213
|
+
$env:GEMINI_API_KEY = "your_key"
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Then just run:
|
|
217
|
+
```bash
|
|
218
|
+
android-localise translate
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
| Variable | Used by |
|
|
222
|
+
|---|---|
|
|
223
|
+
| `GEMINI_API_KEY` | `--provider gemini` |
|
|
224
|
+
| `OPENAI_API_KEY` | `--provider openai` and `--provider custom` |
|
|
225
|
+
| `ANTHROPIC_API_KEY` | `--provider anthropic` |
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## Full workflow example
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
# First time setup — create locale folders
|
|
233
|
+
mkdir -p app/src/main/res/values-hi
|
|
234
|
+
mkdir -p app/src/main/res/values-es
|
|
235
|
+
mkdir -p app/src/main/res/values-de
|
|
236
|
+
|
|
237
|
+
# Set your key once
|
|
238
|
+
export GEMINI_API_KEY=your_key
|
|
239
|
+
|
|
240
|
+
# Translate, fix, verify
|
|
241
|
+
android-localise translate --app-context "a habit tracking app"
|
|
242
|
+
android-localise fix
|
|
243
|
+
android-localise verify
|
|
244
|
+
|
|
245
|
+
# Build your app as usual
|
|
246
|
+
./gradlew assembleDebug
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
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.
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## Roadmap
|
|
254
|
+
|
|
255
|
+
- [ ] **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.
|
|
256
|
+
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
## Contributing
|
|
260
|
+
|
|
261
|
+
Bug reports and pull requests are welcome. For larger changes, open an issue first.
|
|
262
|
+
|
|
263
|
+
```bash
|
|
264
|
+
git clone https://github.com/BharathKmalviya/android-llm-localization
|
|
265
|
+
cd android-llm-localization
|
|
266
|
+
pip install -e .
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
Releases are automated via GitHub Actions — bump the version in `pyproject.toml` and `__init__.py`, update `CHANGELOG.md`, and push to `master`.
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
273
|
+
## License
|
|
274
|
+
|
|
275
|
+
[MIT](LICENSE)
|
|
@@ -31,6 +31,7 @@ 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
|
+
translate_parser.add_argument("--languages", help="Comma-separated language codes, e.g. hi,es,fr,de — creates folders and strings.xml automatically")
|
|
34
35
|
|
|
35
36
|
# --- fix ---
|
|
36
37
|
fix_parser = subparsers.add_parser("fix", help="Fix XML escaping issues in translated strings.xml files")
|
|
@@ -42,6 +42,27 @@ def get_target_directories(res_dir):
|
|
|
42
42
|
return sorted(dirs)
|
|
43
43
|
|
|
44
44
|
|
|
45
|
+
def ensure_locale_dirs(res_dir, languages):
|
|
46
|
+
"""
|
|
47
|
+
Creates values-<lang> directories for each language code in the list.
|
|
48
|
+
Returns the list of folder names created or already existing.
|
|
49
|
+
"""
|
|
50
|
+
created = []
|
|
51
|
+
for lang in languages:
|
|
52
|
+
lang = lang.strip()
|
|
53
|
+
if not lang:
|
|
54
|
+
continue
|
|
55
|
+
folder = f"values-{lang}" if not lang.startswith("values-") else lang
|
|
56
|
+
folder_path = os.path.join(res_dir, folder)
|
|
57
|
+
if not os.path.exists(folder_path):
|
|
58
|
+
os.makedirs(folder_path, exist_ok=True)
|
|
59
|
+
print(f"📁 Created {folder}/")
|
|
60
|
+
created.append(folder)
|
|
61
|
+
else:
|
|
62
|
+
created.append(folder)
|
|
63
|
+
return created
|
|
64
|
+
|
|
65
|
+
|
|
45
66
|
def read_source_xml(source_path):
|
|
46
67
|
with open(source_path, "r", encoding="utf-8") as f:
|
|
47
68
|
return f.read()
|
|
@@ -218,6 +239,7 @@ def _parse_args(args=None):
|
|
|
218
239
|
parser.add_argument("--base-url")
|
|
219
240
|
parser.add_argument("--app-context")
|
|
220
241
|
parser.add_argument("--sleep", type=float, default=5.0)
|
|
242
|
+
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.")
|
|
221
243
|
return parser.parse_args(args)
|
|
222
244
|
|
|
223
245
|
|
|
@@ -267,10 +289,18 @@ def main(args=None):
|
|
|
267
289
|
return
|
|
268
290
|
|
|
269
291
|
source_xml = read_source_xml(source_strings_xml)
|
|
270
|
-
|
|
292
|
+
|
|
293
|
+
# Build target directory list — from --languages flag or by scanning res_dir
|
|
294
|
+
if args.languages:
|
|
295
|
+
lang_codes = [l.strip() for l in args.languages.split(",") if l.strip()]
|
|
296
|
+
target_dirs = ensure_locale_dirs(res_dir, lang_codes)
|
|
297
|
+
else:
|
|
298
|
+
target_dirs = get_target_directories(res_dir)
|
|
271
299
|
|
|
272
300
|
if not target_dirs:
|
|
273
|
-
print(f"⚠️ No
|
|
301
|
+
print(f"⚠️ No locale directories found in {res_dir}.")
|
|
302
|
+
print(" Either create values-<lang>/ folders manually, or use --languages to specify them:")
|
|
303
|
+
print(" Example: android-localise translate --languages hi,es,fr,de --api-key YOUR_KEY")
|
|
274
304
|
return
|
|
275
305
|
|
|
276
306
|
print(f"🌍 Found {len(target_dirs)} language directories.")
|
|
@@ -281,7 +311,12 @@ def main(args=None):
|
|
|
281
311
|
|
|
282
312
|
for folder in target_dirs:
|
|
283
313
|
target_path = os.path.join(res_dir, folder, "strings.xml")
|
|
284
|
-
|
|
314
|
+
is_new_file = not os.path.exists(target_path)
|
|
315
|
+
|
|
316
|
+
if is_new_file:
|
|
317
|
+
print(f"⏳ [{folder}] No strings.xml found — creating and translating...")
|
|
318
|
+
else:
|
|
319
|
+
print(f"⏳ [{folder}] Updating existing strings.xml...")
|
|
285
320
|
|
|
286
321
|
translated_xml, used_model = translate_xml(
|
|
287
322
|
actual_provider, api_key, model, source_xml,
|
|
@@ -293,7 +328,8 @@ def main(args=None):
|
|
|
293
328
|
with open(target_path, "w", encoding="utf-8") as f:
|
|
294
329
|
f.write(translated_xml)
|
|
295
330
|
suffix = f" (via {used_model})" if used_model != model else ""
|
|
296
|
-
|
|
331
|
+
action = "Created" if is_new_file else "Updated"
|
|
332
|
+
print(f"✅ {action} {folder}/strings.xml{suffix}")
|
|
297
333
|
else:
|
|
298
334
|
print(f"⚠️ Failed or got invalid XML for {folder}. Skipping.")
|
|
299
335
|
|