aqlab 0.1.4__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.
aqlab-0.1.4/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Gilgamesh-110
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
aqlab-0.1.4/PKG-INFO ADDED
@@ -0,0 +1,453 @@
1
+ Metadata-Version: 2.4
2
+ Name: aqlab
3
+ Version: 0.1.4
4
+ Summary: Make an Android app Arabic with correct RTL: layout direction, attribute migration and bidi linting in one command.
5
+ Author: Gilgamesh-110
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/Gilgamesh-110/aqlab
8
+ Project-URL: Issues, https://github.com/Gilgamesh-110/aqlab/issues
9
+ Keywords: android,arabic,rtl,i18n,localization,apk,bidi,right-to-left
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Environment :: Console
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Natural Language :: Arabic
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Topic :: Software Development :: Localization
17
+ Classifier: Topic :: Software Development :: Internationalization
18
+ Requires-Python: >=3.9
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Dynamic: license-file
22
+
23
+ <div align="center">
24
+
25
+ <img src="assets/banner.svg" alt="aqlab — Arabic &amp; RTL localization for Android APKs" width="100%">
26
+
27
+ **Flip any Android app to Arabic — RTL-correct — in one command.**
28
+
29
+ [![tests](https://github.com/Gilgamesh-110/aqlab/actions/workflows/ci.yml/badge.svg)](https://github.com/Gilgamesh-110/aqlab/actions/workflows/ci.yml)
30
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
31
+ [![Python 3.9+](https://img.shields.io/badge/python-3.9%2B-blue.svg)](https://www.python.org/downloads/)
32
+ [![dependencies: none](https://img.shields.io/badge/dependencies-none-brightgreen.svg)](#requirements)
33
+ [![PRs welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](CONTRIBUTING.md)
34
+ [![Changelog](https://img.shields.io/badge/changelog-keep_a_changelog-orange.svg)](CHANGELOG.md)
35
+ [![release](https://img.shields.io/github/v/release/Gilgamesh-110/aqlab?sort=date&color=blue)](https://github.com/Gilgamesh-110/aqlab/releases/latest)
36
+ [![platform](https://img.shields.io/badge/platform-windows%20%7C%20linux%20%7C%20macos-lightgrey.svg)](#requirements)
37
+
38
+ [العربية](README.ar.md) · **English**
39
+
40
+ </div>
41
+
42
+ ---
43
+
44
+ Translating an app into Arabic is the easy half. The half almost everybody gets
45
+ wrong is the interface: the drawer stays on the left, the back arrow still points
46
+ left, progress bars fill left-to-right, `%d` placeholders go missing, and
47
+ punctuation lands on the wrong side of the line.
48
+
49
+ **`aqlab` does the part that is not translation** — layout direction, attribute
50
+ migration, Unicode bidi hazards, and Android resource escaping.
51
+
52
+ <p align="center">
53
+ <img src="assets/features.svg" alt="layout direction, property migration, bidi risk scan, resource escaping" width="100%">
54
+ </p>
55
+
56
+ <p align="center">
57
+ <img src="assets/demo.svg" alt="terminal session: scan, rtl, lint" width="100%">
58
+ </p>
59
+
60
+ ## Try it in 5 seconds
61
+
62
+ No APK and no Android SDK needed for this part — there is a small decoded project
63
+ in the repo that deliberately contains the traps:
64
+
65
+ ```sh
66
+ git clone https://github.com/Gilgamesh-110/aqlab && cd aqlab
67
+ python aqlab.py rtl examples/demo-project
68
+ python aqlab.py apply examples/demo-project --tsv examples/ar.tsv
69
+ python aqlab.py lint examples/demo-project
70
+ ```
71
+
72
+ ```
73
+ [PATCH] AndroidManifest.xml: android:supportsRtl="true"
74
+ [PATCH] res/layout/activity_main.xml: 11 attribute(s)
75
+ [PATCH] res/layout/list_item.xml: 6 attribute(s)
76
+ [PATCH] res/values/styles.xml: 2 attribute(s)
77
+ [DONE] 4 file(s) updated
78
+ [DONE] 12 translation(s) -> examples/demo-project/res/values-ar/strings.xml
79
+ [FINDING] lint res/values-ar: 12/12 translated (100.0%)
80
+ [ERROR] plurals/selected_count: missing Arabic quantities: few (3-10), many (11-99) — those counts fall back to `other`
81
+ [WARN] plurals/selected_count: no zero (0), two (2) form — those counts use `other`
82
+ [FAIL] 1 error(s), 1 warning(s)
83
+ ```
84
+
85
+ That last line is the reason this tool exists. The demo's Arabic is **100%
86
+ translated and still wrong** at every count above 2 — it says `3 عنصر` where
87
+ Arabic needs `3 عناصر`, `11 عنصراً` above ten and `لا عناصر` at zero. There is no
88
+ missing string, no build error and nothing an ordinary linter reports.
89
+
90
+ It contains a layout that declares `paddingLeft` **and** `paddingStart`, a
91
+ `<style>` that carries the direction in an `<item>` value, and a
92
+ `?listPreferredItemPaddingLeft` theme reference — the three things that break a
93
+ naive migration.
94
+
95
+ <p align="center">
96
+ <img src="assets/before-after.svg" alt="the same screen before and after, with the attributes that changed" width="100%">
97
+ </p>
98
+
99
+ ## Contents
100
+
101
+ - [Try it in 5 seconds](#try-it-in-5-seconds)
102
+ - [Why](#why)
103
+ - [Install](#install)
104
+ - [Quick start](#quick-start)
105
+ - [Commands](#commands)
106
+ - [What it fixes](#what-it-fixes)
107
+ - [The part nobody else does: bidi linting](#the-part-nobody-else-does-bidi-linting)
108
+ - [What it does *not* do](#what-it-does-not-do)
109
+ - [How this relates to other tools](#how-this-relates-to-other-tools)
110
+ - [Tested on](#tested-on)
111
+ - [FAQ](#faq)
112
+ - [Requirements](#requirements)
113
+ - [Docs](#docs)
114
+ - [Contributing](#contributing)
115
+ - [License](#license)
116
+
117
+ ## Why
118
+
119
+ Arabic is written right-to-left. Android has had a first-class mechanism for this
120
+ since API 17 — logical `start`/`end` attributes plus `android:supportsRtl` — and
121
+ yet a large share of shipped apps never turned it on, or turned it on and left
122
+ hundreds of hardcoded `left`/`right` attributes behind.
123
+
124
+ Doing it by hand means auditing every layout, every menu, every style, and only
125
+ finding the mistakes when you look at the app running in Arabic. `aqlab`
126
+ automates the mechanical 95% and **lints the rest**, so the first Arabic build
127
+ you look at is already correct.
128
+
129
+ ## Install
130
+
131
+ ```sh
132
+ pipx install git+https://github.com/Gilgamesh-110/aqlab # or
133
+ curl -O https://raw.githubusercontent.com/Gilgamesh-110/aqlab/main/aqlab.py
134
+ ```
135
+
136
+ One file, standard library only. It drives tools you already have:
137
+
138
+ | tool | how it is found |
139
+ |---|---|
140
+ | `apktool` | `AQLAB_APKTOOL=/path/to/apktool.jar`, else `apktool` on `PATH` |
141
+ | `aapt2`, `zipalign`, `apksigner` | `ANDROID_HOME` / `AQLAB_SDK`, else `PATH` |
142
+
143
+ Per-tool overrides also work: `AQLAB_AAPT2`, `AQLAB_ZIPALIGN`, `AQLAB_APKSIGNER`,
144
+ `AQLAB_JAVA`.
145
+
146
+ ## Quick start
147
+
148
+ ```sh
149
+ aqlab scan app.apk # what is the state of this app?
150
+ ```
151
+
152
+ ```
153
+ [FINDING] apk: app.apk
154
+ package : com.example.app
155
+ label : 'Example'
156
+ locales : 87 -> --_--, af, am, ar, as, az, be, bg, bn, bs, ca, cs, da, de
157
+ arabic present : yes
158
+ supportsRtl : no (or not detectable in binary XML)
159
+ native libs : 6 (renaming packages in native apps can break self-checks)
160
+ ```
161
+
162
+ ```sh
163
+ aqlab decode app.apk -o work # unpack
164
+ aqlab rtl work --dry-run # preview the migration
165
+ aqlab rtl work # apply it
166
+ aqlab scaffold work # -> work/values-ar.tsv (table stays out of res/)
167
+ # ... translate the last column (DeepL, an LLM, or a human) ...
168
+ aqlab apply work --tsv work/values-ar.tsv
169
+ aqlab lint work # exits non-zero on real errors
170
+ aqlab build work --ks release.keystore --ks-alias release
171
+ ```
172
+
173
+ Or all of it at once:
174
+
175
+ ```sh
176
+ aqlab all app.apk --tsv ar.tsv --ks release.keystore --ks-alias release
177
+ ```
178
+
179
+ ## Commands
180
+
181
+ | command | what it does |
182
+ |---|---|
183
+ | `scan <apk\|dir>` | read-only report: locales, `supportsRtl`, remaining `left/right`, coverage. `--json` for tooling. |
184
+ | `decode <apk>` | unpack an APK into an editable project. Skips `smali/` by default — see below. |
185
+ | `rtl <dir>` | set `android:supportsRtl` and migrate `left/right` → `start/end` across `res/**`. `--dry-run` to preview. |
186
+ | `scaffold <dir>` | create `res/values-ar/` and a `values-ar.tsv` translation table. Every `values/*.xml` is read, not just `strings.xml`. |
187
+ | `apply <dir> --tsv` | import a translated table back into `values-ar/`, each entry into the file its source came from |
188
+ | `lint <dir>` | bidi hazards, escaping bugs, format-specifier drift, Arabic plural structure, coverage. Non-zero exit on errors. |
189
+ | `build <dir>` | `apktool b` → `zipalign` → `apksigner sign` → `apksigner verify` |
190
+ | `all <apk>` | the whole pipeline |
191
+
192
+ ### Speed, and why `decode` skips `smali/`
193
+
194
+ Disassembling the bytecode is most of the decode time, and this tool never edits
195
+ code — so it is skipped. `apktool` records the source APK in `apktool.yml` and
196
+ copies the original `classes*.dex` back on build **byte for byte**; the end-to-end
197
+ run below was verified with `sha256sum` on every dex, and the built APK passes
198
+ `apksigner verify` and `zipalign -c`.
199
+
200
+ Measured on a real 17 MB app — an 8,447-file tree, 125 layouts, 210 `values/`
201
+ files, 5 dex files:
202
+
203
+ | step | with `smali/` | default | what changed |
204
+ |---|---|---|---|
205
+ | `decode` | 52.1s | **10.0s** | skip the disassembly |
206
+ | `build` | 65.3s | **27.0s** | nothing to re-assemble |
207
+ | `rtl` | 6.6s | **4.3s** | tags that cannot migrate are skipped |
208
+ | **whole pipeline** | ~117s | **51s** | signed and verified |
209
+
210
+ `rtl` got faster because the rename tables used to run over *every attribute of
211
+ every element*. A tag is now skipped unless one of the names the tables know
212
+ about appears in it, so `layout_width` / `text` / `id` tags cost one substring
213
+ search. That cut pure-Python calls from 692k to 288k with byte-identical output.
214
+
215
+ Skipping `smali/` also shrinks the working tree from **123 MB across 8,447 files
216
+ to 47 MB across 1,197** — so every later copy, grep, archive and rebuild works on
217
+ a tree an order of magnitude smaller.
218
+
219
+ Need the smali tree after all? `--with-sources` on `decode` or `all`.
220
+
221
+ ## What it fixes
222
+
223
+ * `android:supportsRtl="true"` on `<application>`
224
+ * `paddingLeft/Right` → `paddingStart/End`, and the same for `layout_margin*`,
225
+ `drawable*`, `layout_align*`, `layout_alignParent*`, `layout_to*Of`
226
+ * ConstraintLayout `app:layout_constraint{Left,Right}_to{Left,Right}Of` → `{Start,End}`
227
+ * `gravity` / `layout_gravity` / `foregroundGravity` value tokens
228
+ * `textAlignment` → `viewStart` / `viewEnd`
229
+ * `<item name="android:paddingLeft">` entries inside `<style>` blocks
230
+ * theme references that would otherwise keep resolving to the LTR value
231
+ (`?listPreferredItemPaddingLeft` → `?…Start`)
232
+ * Android resource escaping: `'`, leading `@`/`?`, and significant
233
+ leading/trailing spaces
234
+
235
+ Every rename is guarded: when a tag already declares the `start`/`end` twin, the
236
+ old attribute is **removed** instead of duplicated. That guard is the difference
237
+ between a project that builds and one that dies inside `aapt2` with
238
+ `error: duplicate attribute` — and it is why this works on AppCompat and Material
239
+ layouts, which routinely carry both.
240
+
241
+ ## The part nobody else does: bidi linting
242
+
243
+ Arabic runs right-to-left. Numbers, Latin words, file paths and `%1$s`
244
+ placeholders inside it run left-to-right. Where the two meet, the Unicode
245
+ Bidirectional Algorithm reorders runs and punctuation ends up on the wrong side.
246
+
247
+ ```
248
+ text: المجلد test.
249
+ renders: المجلد test. the period sits right of "test"; it should sit left
250
+ fix: المجلد test\u200f. U+200F RIGHT-TO-LEFT MARK pins it in place
251
+ ```
252
+
253
+ `aqlab lint` finds that, plus the failures that actually break the build:
254
+
255
+ ```
256
+ [FINDING] lint res/values-ar: 148/190 translated (77.9%)
257
+ [WARN] abc_menu_ctrl_shortcut_label: identical to source and contains Latin — probably untranslated
258
+ [ERROR] discount: bare `%` — must be `%%` or a format specifier, aapt will reject it
259
+ [ERROR] welcome: format specifiers differ: source ['%1$s'] vs arabic ['%s']
260
+ [ERROR] note: unescaped apostrophe — use \' inside Android string resources
261
+ [ERROR] folder: trailing Latin/digit run before punctuation — needs U+200F (RLM)
262
+ [FAIL] 4 error(s), 1 warning(s)
263
+ ```
264
+
265
+ | rule | level |
266
+ |---|---|
267
+ | format specifiers differ from the source | error |
268
+ | bare `%` that `aapt` will reject | error |
269
+ | unescaped `'` inside a string resource | error |
270
+ | value starts with `@` or `?` | error |
271
+ | markup tags (`<b>`, `<xliff:g>`) differ from the source | error |
272
+ | trailing Latin/digit run before punctuation (needs U+200F) | error |
273
+ | an Arabic `<plurals>` missing `other`, `few` or `many` | error |
274
+ | value identical to the source and contains Latin | warn |
275
+ | no Arabic characters in the Arabic value | warn |
276
+ | leading/trailing space mismatch | warn |
277
+ | `few` / `many` / `other` carrying identical text | warn |
278
+ | an Arabic `<plurals>` missing `zero` or `two` | warn |
279
+ | mixed Arabic + Latin in one string | info |
280
+
281
+ Exit code is non-zero on errors, so it drops straight into CI.
282
+
283
+ ## The other part nobody does: Arabic has six plurals, not two
284
+
285
+ English has two plural forms (`one`, `other`). Arabic has **six**, and the two
286
+ that cover almost every count — `few` (3–10) and `many` (11–99) — are exactly the
287
+ two an English source cannot hand you:
288
+
289
+ ```
290
+ count 0 1 2 3-10 11-99 100, 101, …
291
+ zero one two few many other
292
+ لا عنصر عنصران %d عناصر %d عنصراً %d عنصر
293
+ ```
294
+
295
+ So the standard outcome is an Arabic `<plurals>` that mirrors the English one —
296
+ two categories, both in Arabic words. It compiles. It links. **Nothing in the
297
+ build says a word.** And then every count from 2 upward falls through to `other`:
298
+
299
+ ```
300
+ 3 عنصر ← should be 3 عناصر
301
+ 15 عنصر ← should be 15 عنصراً
302
+ ```
303
+
304
+ `aqlab lint` reads `<plurals>` as a structure and reports it:
305
+
306
+ ```
307
+ [ERROR] plurals/n_items: missing Arabic quantities: few (3-10), many (11-99)
308
+ — those counts fall back to `other`
309
+ [WARN] plurals/n_items: no zero (0), two (2) form — those counts use `other`
310
+ [WARN] plurals/n_items: few, many, other carry identical text
311
+ — the plural forms are not actually pluralised
312
+ ```
313
+
314
+ Three things worth knowing about how this behaves:
315
+
316
+ * **Dropping the count in `zero`/`one`/`two` is not a finding.** `عنصر واحد` and
317
+ `عنصران` are correct Arabic; only a *invented* placeholder is an error.
318
+ * **`other` missing is not a build failure.** Verified with `aapt2` — it compiles
319
+ and links a locale whose `<plurals>` has no `other`. That is worse, not better:
320
+ the counts simply have no Arabic value and nothing warns. This check is the only
321
+ thing that will.
322
+ * **It reads every file in the locale folder, not just `strings.xml`.** Plurals
323
+ normally live in their own `plurals.xml` — AOSP and most Material apps ship them
324
+ that way, so a strings-only check would miss exactly the entries it exists for.
325
+
326
+ ### Supplying the missing forms
327
+
328
+ The translation table already carries a row for **all six categories**, including
329
+ the ones an English source has no line for — otherwise there is nowhere to write
330
+ them, and `apply` would have nothing to read back:
331
+
332
+ ```
333
+ aqlab scaffold work # work/values-ar.tsv
334
+ n_items|zero ← blank: Arabic-only categories get a row of their own
335
+ n_items|one %d item
336
+ n_items|two
337
+ n_items|few
338
+ n_items|many
339
+ n_items|other %d items
340
+
341
+ aqlab apply work --tsv work/values-ar.tsv
342
+ ```
343
+
344
+ Each entry goes back into the file its source came from, so an app that keeps
345
+ `values/plurals.xml` gets `values-ar/plurals.xml`. Nothing is funnelled into
346
+ `strings.xml`, because a `<plurals>` name declared in two files of one locale is
347
+ a duplicate-resource error. The table itself lives at `work/values-ar.tsv`, not
348
+ inside `res/` — a `.tsv` under `res/` makes `aapt2 compile --dir res` fail.
349
+
350
+ ## What it does *not* do
351
+
352
+ * **It does not translate anything.** No machine translation, no AI. The last
353
+ column of the TSV is your job — the tool guarantees the mechanics survive once
354
+ it is filled.
355
+ * It does not touch strings compiled into DEX/smali or native code.
356
+ * It does not mirror images or vector assets with baked-in Latin text.
357
+ * It deliberately does not force `android:layoutDirection="rtl"` — that would
358
+ break every other locale in a multi-language app. `supportsRtl` with
359
+ `start`/`end` is the correct mechanism, and it is what Google's own
360
+ documentation specifies.
361
+
362
+ ## How this relates to other tools
363
+
364
+ | tool | what it does | where it stops |
365
+ |---|---|---|
366
+ | **Android Lint** (`RtlHardcoded`, `RtlEnabled`) | flags hardcoded `left`/`right` in a **source** project, and Android Studio can auto-fix them | needs source and a working build. It cannot see an APK. Says nothing about bidi in translated strings or `%1$s` drift. |
367
+ | **Android Studio translation editor** | tracks which strings are translated | manages text, not direction. Does not touch a layout. |
368
+ | **`android:layoutDirection="rtl"`** | forces a subtree right-to-left | the common wrong fix — it breaks every other locale in a multi-language app. |
369
+ | **Machine translation** (DeepL, Google, an LLM) | translates the strings | will happily turn `%1$s` into `%s`, and cannot see a layout at all. |
370
+ | **Crowdin / Lokalise / Weblate** | orchestrates translators and review | upstream of the layout work; no RTL mechanics, no bidi linting. |
371
+ | **`apktool` alone** | gives you a decoded project | does none of the migration, and hands you the duplicate-attribute trap for free. |
372
+ | **`aqlab`** | sets `supportsRtl`, migrates the attributes, scaffolds and applies the translation table, lints bidi and resource escaping | operates on a **decoded APK** — no source needed. Does not translate anything. |
373
+
374
+ **When not to use this.** If you have the project source and a working build, use
375
+ Android Lint's `RtlHardcoded` and `RtlEnabled` checks — that is the right tool, it
376
+ runs in your build, and it is maintained by the people who make the platform.
377
+ `aqlab` exists for the other case: an APK and no source, or a source tree that no
378
+ longer builds.
379
+
380
+ ## Tested on
381
+
382
+ Real APKs, end to end (`decode` → `rtl` → `build` → sign → verify) — not just
383
+ unit tests. `supportsRtl=true` is read back out of the **compiled** manifest, and
384
+ the output is checked with `apksigner verify` and `zipalign -c -v 4`:
385
+
386
+ | app | locales | rebuilt | signed | `supportsRtl` in compiled manifest |
387
+ |---|---|---|---|---|
388
+ | multi-language app (`com.gilgamesh.vip`) | 87 | yes | yes | ✅ `true` |
389
+ | utility app (`TS_Vip_v115`) | — | yes | yes | ✅ `true` |
390
+ | utility app (`bs_1.0`) | — | yes | yes | ✅ `true` |
391
+
392
+ The `rtl` pass is idempotent: running it a second time reports
393
+ `already RTL-clean — nothing to migrate` and writes nothing.
394
+
395
+ ## FAQ
396
+
397
+ **A build failed with `error: duplicate attribute`.**
398
+ You are on a version before the twin guard. Update, or open an issue with the
399
+ file named in the error.
400
+
401
+ **It refuses to run with `minSdkVersion=15`.**
402
+ `start`/`end` resources do not exist before API 17. Raise `minSdkVersion`, or
403
+ pass `--force-min-sdk` knowing the build will then fail.
404
+
405
+ **The rebuilt app crashes, or detects that it was modified.**
406
+ Expected when the original verifies its signature or dex checksums natively — a
407
+ rebuild changes both. See [SECURITY.md](SECURITY.md).
408
+
409
+ **Does it work for Hebrew, Persian or Urdu?**
410
+ The mechanics are identical — `rtl`, `lint` and the attribute tables are
411
+ script-agnostic, and `--locale` accepts any locale code. The Arabic-specific
412
+ parts are the plural rules and the bidi rules.
413
+
414
+ **Why not just use Android Studio's RTL preview?**
415
+ That shows you *a* layout. This fixes every layout in the app and tells you which
416
+ strings are still broken.
417
+
418
+ ## Requirements
419
+
420
+ Python 3.9+, `apktool`, and Android SDK build-tools (`aapt2`, `zipalign`,
421
+ `apksigner`). Nothing else — no pip dependencies, no `lxml`, no `androguard`.
422
+
423
+ ## Docs
424
+
425
+ | | |
426
+ |---|---|
427
+ | [docs/bidi.md](docs/bidi.md) | the long version of bidi: the algorithm, which marks to use and when, and what the linter can and cannot see |
428
+ | [docs/troubleshooting.md](docs/troubleshooting.md) | `duplicate attribute`, `minSdkVersion`, missing tools, a rebuilt app that crashes, translations that do not show up |
429
+ | [CHANGELOG.md](CHANGELOG.md) | release history |
430
+ | [SECURITY.md](SECURITY.md) | threat model, signing-key hygiene, untrusted APK input |
431
+
432
+ ## Contributing
433
+
434
+ See [CONTRIBUTING.md](CONTRIBUTING.md). The test suite is plain asserts and runs
435
+ anywhere:
436
+
437
+ ```sh
438
+ python tests/test_aqlab.py # 85 checks, no framework, no network, no apktool
439
+ ```
440
+
441
+ Most wanted: plural validation for the six Arabic forms, `android:localeConfig`
442
+ scaffolding for the Android 13+ per-app language picker, and vector-drawable
443
+ mirroring warnings. See [CHANGELOG.md](CHANGELOG.md).
444
+
445
+ ## License
446
+
447
+ MIT — see [LICENSE](LICENSE).
448
+
449
+ ## Disclaimer
450
+
451
+ Run it on apps you own or are permitted to modify. Redistributing a rebuilt APK
452
+ may infringe the original app's licence or a store's terms of service. That is
453
+ your call, not the tool's.