autolang 0.1.0__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.
- autolang-0.1.0/LICENSE +21 -0
- autolang-0.1.0/PKG-INFO +216 -0
- autolang-0.1.0/README.md +205 -0
- autolang-0.1.0/pyproject.toml +35 -0
- autolang-0.1.0/src/autolang/__init__.py +6 -0
- autolang-0.1.0/src/autolang/cli/__init__.py +94 -0
- autolang-0.1.0/src/autolang/cli/__main__.py +5 -0
- autolang-0.1.0/src/autolang/cli/common.py +49 -0
- autolang-0.1.0/src/autolang/cli/extractors.py +82 -0
- autolang-0.1.0/src/autolang/cli/init.py +51 -0
- autolang-0.1.0/src/autolang/cli/static_analysis.py +549 -0
- autolang-0.1.0/src/autolang/cli/sync.py +108 -0
- autolang-0.1.0/src/autolang/cli/translate.py +648 -0
- autolang-0.1.0/src/autolang/source_templates.py +60 -0
- autolang-0.1.0/src/autolang/toml_io.py +44 -0
- autolang-0.1.0/src/autolang/translator.py +132 -0
autolang-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Heerozh
|
|
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.
|
autolang-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: autolang
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Transparent i18n for Python f-strings.
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Requires-Dist: babel>=2.18.0
|
|
8
|
+
Requires-Dist: executing>=2.2.1
|
|
9
|
+
Requires-Python: >=3.11
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
|
|
12
|
+
# Autolang
|
|
13
|
+
|
|
14
|
+
`Autolang` is an experimental i18n library for Python `f-string` call sites.
|
|
15
|
+
It lets you bind a module-level `tt` function, write `tt(f"...")`, reconstruct the original template at runtime, look up a translated template from TOML, and re-evaluate the translated placeholders with Babel-aware formatting.
|
|
16
|
+
|
|
17
|
+
## Status
|
|
18
|
+
|
|
19
|
+
This project is under active development.
|
|
20
|
+
|
|
21
|
+
Stable today:
|
|
22
|
+
- `install(locale, locale_dir)` to create an isolated translator instance
|
|
23
|
+
- `TransparentTranslator` for explicit instances
|
|
24
|
+
- TOML-backed translation lookup
|
|
25
|
+
- Babel `fmt.*` placeholder support inside translated templates
|
|
26
|
+
- Per-instance call-site cache with `reload()` and `clear_cache()`
|
|
27
|
+
- `tt init` and `tt sync` for locale template bootstrapping and synchronization
|
|
28
|
+
|
|
29
|
+
Planned:
|
|
30
|
+
- AI-assisted translation generation
|
|
31
|
+
- Better extraction and review workflows
|
|
32
|
+
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pip install autolang
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Quick Start
|
|
40
|
+
|
|
41
|
+
Project layout:
|
|
42
|
+
|
|
43
|
+
```text
|
|
44
|
+
your_app/
|
|
45
|
+
app.py
|
|
46
|
+
locales/
|
|
47
|
+
es.toml
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`locales/es.toml`
|
|
51
|
+
|
|
52
|
+
```toml
|
|
53
|
+
"Hello {name}" = "Hola {name}"
|
|
54
|
+
"Today is {fmt.date(now, format='short')}" = "Hoy es {fmt.date(now, format='short')}"
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
`app.py`
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
from datetime import datetime
|
|
61
|
+
|
|
62
|
+
from babel import Locale
|
|
63
|
+
from babel.support import Format
|
|
64
|
+
|
|
65
|
+
from autolang import install
|
|
66
|
+
|
|
67
|
+
translator = install("es", "locales")
|
|
68
|
+
tt = translator.translate
|
|
69
|
+
|
|
70
|
+
# A local fmt object keeps the original f-string valid before translation happens.
|
|
71
|
+
fmt = Format(Locale.parse("en"))
|
|
72
|
+
|
|
73
|
+
name = "Alice"
|
|
74
|
+
now = datetime(2026, 3, 11)
|
|
75
|
+
|
|
76
|
+
print(tt(f"Hello {name}"))
|
|
77
|
+
print(tt(f"Today is {fmt.date(now, format='short')}"))
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Example output:
|
|
81
|
+
|
|
82
|
+
```text
|
|
83
|
+
Hola Alice
|
|
84
|
+
Hoy es 11/3/26
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Public API
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
from autolang import (
|
|
91
|
+
TransparentTranslator,
|
|
92
|
+
install,
|
|
93
|
+
)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
- `install(locale_str, locale_dir="locales")` creates and returns a translator instance.
|
|
97
|
+
- `TransparentTranslator(locale_str, locale_dir="locales")` creates an explicit translator instance with its own cache.
|
|
98
|
+
- `translator.translate(text)` translates the current call site.
|
|
99
|
+
- `translator.reload()` reloads the instance locale file and clears its cached call-site entries.
|
|
100
|
+
- `translator.clear_cache()` clears the instance cache without reloading files.
|
|
101
|
+
|
|
102
|
+
## Module Setup
|
|
103
|
+
|
|
104
|
+
The recommended pattern is to initialize a module-level `tt` variable once and then call `tt(...)` everywhere in that module.
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
from autolang import install
|
|
108
|
+
|
|
109
|
+
translator = install("es", "locales")
|
|
110
|
+
tt = translator.translate
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## CLI
|
|
114
|
+
|
|
115
|
+
The project also ships a short developer CLI command: `tt`.
|
|
116
|
+
|
|
117
|
+
Translate all locale TOML files by filling entries still marked as `NO_TRANSLATION` through an OpenAI-compatible API:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
tt translate \
|
|
121
|
+
--locale-dir locales \
|
|
122
|
+
--model gpt-4.1-mini \
|
|
123
|
+
--api-key "$OPENAI_API_KEY"
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
By default, the command:
|
|
127
|
+
- discovers every `*.toml` file under the locale directory as a translation target
|
|
128
|
+
- uses each TOML key as the source template text
|
|
129
|
+
- reads cue text from `.<locale-dir>_cue/*.toml` when available
|
|
130
|
+
- assumes keys may be mixed-language and lets the model decide per item whether translation is needed
|
|
131
|
+
- only translates locale entries whose current value is `NO_TRANSLATION`
|
|
132
|
+
- sends translation requests in batches and can execute multiple batches concurrently
|
|
133
|
+
- validates returned JSON and placeholder compatibility before writing files
|
|
134
|
+
|
|
135
|
+
Useful flags:
|
|
136
|
+
- `--overwrite` to force re-translation of existing target values
|
|
137
|
+
- `--dry-run` to preview work without writing files
|
|
138
|
+
- `--batch-size 20` to control how many entries are sent in one model request
|
|
139
|
+
- `--workers 4` to control concurrent batch requests
|
|
140
|
+
- `--base-url` to point at any OpenAI-compatible endpoint
|
|
141
|
+
|
|
142
|
+
The CLI also reads these environment variables:
|
|
143
|
+
- `TT_API_KEY` or `OPENAI_API_KEY`
|
|
144
|
+
- `TT_BASE_URL` or `OPENAI_BASE_URL`
|
|
145
|
+
- `TT_MODEL` or `OPENAI_MODEL`
|
|
146
|
+
|
|
147
|
+
Initialize locale TOML files from collected `tt(...)` templates:
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
tt init \
|
|
151
|
+
--source src \
|
|
152
|
+
--locale-dir locales \
|
|
153
|
+
--locales en es
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
By default, the command:
|
|
157
|
+
- scans Python files under `--source`
|
|
158
|
+
- extracts `tt("...")` and `tt(f"...")` call sites through a Babel-compatible extractor
|
|
159
|
+
- skips hidden and cache/build directories such as `.git`, `.venv`, and `__pycache__`
|
|
160
|
+
- creates one TOML file per requested locale
|
|
161
|
+
- writes every collected key as `"source" = "NO_TRANSLATION"`
|
|
162
|
+
- writes static cue entries into matching files under `.locales_cue/`
|
|
163
|
+
- includes per-placeholder context such as the nearest assignment, parameter annotation, allowed `fmt.*` candidates, and a recommended candidate when confidence is high
|
|
164
|
+
|
|
165
|
+
Sync collected templates across existing locale TOML files:
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
tt sync \
|
|
169
|
+
--source src \
|
|
170
|
+
--locale-dir locales
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
By default, the command:
|
|
174
|
+
- scans Python files under `--source`
|
|
175
|
+
- requires at least one existing `*.toml` locale file under `--locale-dir`
|
|
176
|
+
- keeps existing translated values for keys that are still present in source
|
|
177
|
+
- writes missing keys as `"source" = "NO_TRANSLATION"`
|
|
178
|
+
- removes stale keys that are no longer collected from source
|
|
179
|
+
- rewrites cue files under `.locales_cue/` to match the current template set
|
|
180
|
+
|
|
181
|
+
## How It Works
|
|
182
|
+
|
|
183
|
+
At a high level:
|
|
184
|
+
|
|
185
|
+
1. You call `tt(f"...")`.
|
|
186
|
+
Here `tt = translator.translate`.
|
|
187
|
+
2. The library inspects the caller frame and maps it back to the AST node for that exact call site.
|
|
188
|
+
3. It rebuilds the source template, for example `Hello {name}`.
|
|
189
|
+
4. It loads the translated template from TOML.
|
|
190
|
+
5. It compiles the translated template as an f-string expression and caches it per translator instance.
|
|
191
|
+
6. Later calls from the same bytecode location reuse the cached compiled expression.
|
|
192
|
+
|
|
193
|
+
## Constraints
|
|
194
|
+
|
|
195
|
+
This is not a drop-in replacement for mature gettext tooling yet.
|
|
196
|
+
|
|
197
|
+
- The main path is designed for direct `tt(f"...")` usage after binding `tt = translator.translate`.
|
|
198
|
+
- Library code should keep its own translator instance and bind its own `tt` function in module scope.
|
|
199
|
+
- Translation lookup is currently a flat TOML key-value map.
|
|
200
|
+
- Static cue collection writes analysis data into a sibling hidden directory such as `.locales_cue`.
|
|
201
|
+
- If translated placeholder expressions are invalid or fail at evaluation time, the library falls back to the original rendered text.
|
|
202
|
+
- The library currently relies on runtime frame inspection and AST recovery, so unusual execution environments may behave differently.
|
|
203
|
+
|
|
204
|
+
## Development
|
|
205
|
+
|
|
206
|
+
Run tests:
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
uv run pytest -q
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## Roadmap
|
|
213
|
+
|
|
214
|
+
- AI-generated translations from source templates and static cues
|
|
215
|
+
- Locale file generation and diffing
|
|
216
|
+
- Better developer tooling around extraction, validation, and review
|
autolang-0.1.0/README.md
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# Autolang
|
|
2
|
+
|
|
3
|
+
`Autolang` is an experimental i18n library for Python `f-string` call sites.
|
|
4
|
+
It lets you bind a module-level `tt` function, write `tt(f"...")`, reconstruct the original template at runtime, look up a translated template from TOML, and re-evaluate the translated placeholders with Babel-aware formatting.
|
|
5
|
+
|
|
6
|
+
## Status
|
|
7
|
+
|
|
8
|
+
This project is under active development.
|
|
9
|
+
|
|
10
|
+
Stable today:
|
|
11
|
+
- `install(locale, locale_dir)` to create an isolated translator instance
|
|
12
|
+
- `TransparentTranslator` for explicit instances
|
|
13
|
+
- TOML-backed translation lookup
|
|
14
|
+
- Babel `fmt.*` placeholder support inside translated templates
|
|
15
|
+
- Per-instance call-site cache with `reload()` and `clear_cache()`
|
|
16
|
+
- `tt init` and `tt sync` for locale template bootstrapping and synchronization
|
|
17
|
+
|
|
18
|
+
Planned:
|
|
19
|
+
- AI-assisted translation generation
|
|
20
|
+
- Better extraction and review workflows
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install autolang
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Quick Start
|
|
29
|
+
|
|
30
|
+
Project layout:
|
|
31
|
+
|
|
32
|
+
```text
|
|
33
|
+
your_app/
|
|
34
|
+
app.py
|
|
35
|
+
locales/
|
|
36
|
+
es.toml
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
`locales/es.toml`
|
|
40
|
+
|
|
41
|
+
```toml
|
|
42
|
+
"Hello {name}" = "Hola {name}"
|
|
43
|
+
"Today is {fmt.date(now, format='short')}" = "Hoy es {fmt.date(now, format='short')}"
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
`app.py`
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from datetime import datetime
|
|
50
|
+
|
|
51
|
+
from babel import Locale
|
|
52
|
+
from babel.support import Format
|
|
53
|
+
|
|
54
|
+
from autolang import install
|
|
55
|
+
|
|
56
|
+
translator = install("es", "locales")
|
|
57
|
+
tt = translator.translate
|
|
58
|
+
|
|
59
|
+
# A local fmt object keeps the original f-string valid before translation happens.
|
|
60
|
+
fmt = Format(Locale.parse("en"))
|
|
61
|
+
|
|
62
|
+
name = "Alice"
|
|
63
|
+
now = datetime(2026, 3, 11)
|
|
64
|
+
|
|
65
|
+
print(tt(f"Hello {name}"))
|
|
66
|
+
print(tt(f"Today is {fmt.date(now, format='short')}"))
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Example output:
|
|
70
|
+
|
|
71
|
+
```text
|
|
72
|
+
Hola Alice
|
|
73
|
+
Hoy es 11/3/26
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Public API
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
from autolang import (
|
|
80
|
+
TransparentTranslator,
|
|
81
|
+
install,
|
|
82
|
+
)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
- `install(locale_str, locale_dir="locales")` creates and returns a translator instance.
|
|
86
|
+
- `TransparentTranslator(locale_str, locale_dir="locales")` creates an explicit translator instance with its own cache.
|
|
87
|
+
- `translator.translate(text)` translates the current call site.
|
|
88
|
+
- `translator.reload()` reloads the instance locale file and clears its cached call-site entries.
|
|
89
|
+
- `translator.clear_cache()` clears the instance cache without reloading files.
|
|
90
|
+
|
|
91
|
+
## Module Setup
|
|
92
|
+
|
|
93
|
+
The recommended pattern is to initialize a module-level `tt` variable once and then call `tt(...)` everywhere in that module.
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
from autolang import install
|
|
97
|
+
|
|
98
|
+
translator = install("es", "locales")
|
|
99
|
+
tt = translator.translate
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## CLI
|
|
103
|
+
|
|
104
|
+
The project also ships a short developer CLI command: `tt`.
|
|
105
|
+
|
|
106
|
+
Translate all locale TOML files by filling entries still marked as `NO_TRANSLATION` through an OpenAI-compatible API:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
tt translate \
|
|
110
|
+
--locale-dir locales \
|
|
111
|
+
--model gpt-4.1-mini \
|
|
112
|
+
--api-key "$OPENAI_API_KEY"
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
By default, the command:
|
|
116
|
+
- discovers every `*.toml` file under the locale directory as a translation target
|
|
117
|
+
- uses each TOML key as the source template text
|
|
118
|
+
- reads cue text from `.<locale-dir>_cue/*.toml` when available
|
|
119
|
+
- assumes keys may be mixed-language and lets the model decide per item whether translation is needed
|
|
120
|
+
- only translates locale entries whose current value is `NO_TRANSLATION`
|
|
121
|
+
- sends translation requests in batches and can execute multiple batches concurrently
|
|
122
|
+
- validates returned JSON and placeholder compatibility before writing files
|
|
123
|
+
|
|
124
|
+
Useful flags:
|
|
125
|
+
- `--overwrite` to force re-translation of existing target values
|
|
126
|
+
- `--dry-run` to preview work without writing files
|
|
127
|
+
- `--batch-size 20` to control how many entries are sent in one model request
|
|
128
|
+
- `--workers 4` to control concurrent batch requests
|
|
129
|
+
- `--base-url` to point at any OpenAI-compatible endpoint
|
|
130
|
+
|
|
131
|
+
The CLI also reads these environment variables:
|
|
132
|
+
- `TT_API_KEY` or `OPENAI_API_KEY`
|
|
133
|
+
- `TT_BASE_URL` or `OPENAI_BASE_URL`
|
|
134
|
+
- `TT_MODEL` or `OPENAI_MODEL`
|
|
135
|
+
|
|
136
|
+
Initialize locale TOML files from collected `tt(...)` templates:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
tt init \
|
|
140
|
+
--source src \
|
|
141
|
+
--locale-dir locales \
|
|
142
|
+
--locales en es
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
By default, the command:
|
|
146
|
+
- scans Python files under `--source`
|
|
147
|
+
- extracts `tt("...")` and `tt(f"...")` call sites through a Babel-compatible extractor
|
|
148
|
+
- skips hidden and cache/build directories such as `.git`, `.venv`, and `__pycache__`
|
|
149
|
+
- creates one TOML file per requested locale
|
|
150
|
+
- writes every collected key as `"source" = "NO_TRANSLATION"`
|
|
151
|
+
- writes static cue entries into matching files under `.locales_cue/`
|
|
152
|
+
- includes per-placeholder context such as the nearest assignment, parameter annotation, allowed `fmt.*` candidates, and a recommended candidate when confidence is high
|
|
153
|
+
|
|
154
|
+
Sync collected templates across existing locale TOML files:
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
tt sync \
|
|
158
|
+
--source src \
|
|
159
|
+
--locale-dir locales
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
By default, the command:
|
|
163
|
+
- scans Python files under `--source`
|
|
164
|
+
- requires at least one existing `*.toml` locale file under `--locale-dir`
|
|
165
|
+
- keeps existing translated values for keys that are still present in source
|
|
166
|
+
- writes missing keys as `"source" = "NO_TRANSLATION"`
|
|
167
|
+
- removes stale keys that are no longer collected from source
|
|
168
|
+
- rewrites cue files under `.locales_cue/` to match the current template set
|
|
169
|
+
|
|
170
|
+
## How It Works
|
|
171
|
+
|
|
172
|
+
At a high level:
|
|
173
|
+
|
|
174
|
+
1. You call `tt(f"...")`.
|
|
175
|
+
Here `tt = translator.translate`.
|
|
176
|
+
2. The library inspects the caller frame and maps it back to the AST node for that exact call site.
|
|
177
|
+
3. It rebuilds the source template, for example `Hello {name}`.
|
|
178
|
+
4. It loads the translated template from TOML.
|
|
179
|
+
5. It compiles the translated template as an f-string expression and caches it per translator instance.
|
|
180
|
+
6. Later calls from the same bytecode location reuse the cached compiled expression.
|
|
181
|
+
|
|
182
|
+
## Constraints
|
|
183
|
+
|
|
184
|
+
This is not a drop-in replacement for mature gettext tooling yet.
|
|
185
|
+
|
|
186
|
+
- The main path is designed for direct `tt(f"...")` usage after binding `tt = translator.translate`.
|
|
187
|
+
- Library code should keep its own translator instance and bind its own `tt` function in module scope.
|
|
188
|
+
- Translation lookup is currently a flat TOML key-value map.
|
|
189
|
+
- Static cue collection writes analysis data into a sibling hidden directory such as `.locales_cue`.
|
|
190
|
+
- If translated placeholder expressions are invalid or fail at evaluation time, the library falls back to the original rendered text.
|
|
191
|
+
- The library currently relies on runtime frame inspection and AST recovery, so unusual execution environments may behave differently.
|
|
192
|
+
|
|
193
|
+
## Development
|
|
194
|
+
|
|
195
|
+
Run tests:
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
uv run pytest -q
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## Roadmap
|
|
202
|
+
|
|
203
|
+
- AI-generated translations from source templates and static cues
|
|
204
|
+
- Locale file generation and diffing
|
|
205
|
+
- Better developer tooling around extraction, validation, and review
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["uv_build >= 0.10.7, <0.11.0"]
|
|
3
|
+
build-backend = "uv_build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "autolang"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Transparent i18n for Python f-strings."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"babel>=2.18.0",
|
|
13
|
+
"executing>=2.2.1",
|
|
14
|
+
]
|
|
15
|
+
license = "MIT"
|
|
16
|
+
license-files = ["LICEN[CS]E*"]
|
|
17
|
+
|
|
18
|
+
[project.scripts]
|
|
19
|
+
tt = "autolang.cli:main"
|
|
20
|
+
autolang = "autolang.cli:main"
|
|
21
|
+
|
|
22
|
+
[tool.setuptools.packages.find]
|
|
23
|
+
where = ["src"]
|
|
24
|
+
|
|
25
|
+
[tool.pyright]
|
|
26
|
+
typeCheckingMode = "standard"
|
|
27
|
+
|
|
28
|
+
[dependency-groups]
|
|
29
|
+
dev = [
|
|
30
|
+
"basedpyright>=1.38.2",
|
|
31
|
+
"ipykernel>=7.2.0",
|
|
32
|
+
"pytest>=9.0.2",
|
|
33
|
+
"pytest-benchmark>=5.2.3",
|
|
34
|
+
"ruff>=0.15.6",
|
|
35
|
+
]
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
|
|
5
|
+
from . import init as _init
|
|
6
|
+
from . import sync as _sync
|
|
7
|
+
from . import translate as _translate
|
|
8
|
+
|
|
9
|
+
BatchTranslationItem = _translate.BatchTranslationItem
|
|
10
|
+
BatchTranslationOutcome = _translate.BatchTranslationOutcome
|
|
11
|
+
BatchTranslationRequest = _translate.BatchTranslationRequest
|
|
12
|
+
OpenAICompatibleClient = _translate.OpenAICompatibleClient
|
|
13
|
+
PlaceholderSpec = _translate.PlaceholderSpec
|
|
14
|
+
TranslationResult = _translate.TranslationResult
|
|
15
|
+
TranslationTask = _translate.TranslationTask
|
|
16
|
+
validate_translated_text = _translate.validate_translated_text
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def handle_translate_command(args: argparse.Namespace) -> int:
|
|
20
|
+
_translate.OpenAICompatibleClient = OpenAICompatibleClient
|
|
21
|
+
return _translate.handle_translate_command(args)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def handle_sync_command(args: argparse.Namespace) -> int:
|
|
25
|
+
return _sync.handle_sync_command(args)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def handle_init_command(args: argparse.Namespace) -> int:
|
|
29
|
+
return _init.handle_init_command(args)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
33
|
+
parser = argparse.ArgumentParser(prog="tt", description="Autolang developer tools.")
|
|
34
|
+
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
35
|
+
|
|
36
|
+
translate_parser = subparsers.add_parser(
|
|
37
|
+
"translate",
|
|
38
|
+
help="Translate locale TOML files through an OpenAI-compatible API.",
|
|
39
|
+
)
|
|
40
|
+
translate_parser.add_argument("--locale-dir", default="locales")
|
|
41
|
+
translate_parser.add_argument("--model", default=None)
|
|
42
|
+
translate_parser.add_argument("--base-url", default=None)
|
|
43
|
+
translate_parser.add_argument("--api-key", default=None)
|
|
44
|
+
translate_parser.add_argument("--timeout", type=float, default=60.0)
|
|
45
|
+
translate_parser.add_argument("--workers", type=int, default=4)
|
|
46
|
+
translate_parser.add_argument("--batch-size", type=int, default=20)
|
|
47
|
+
translate_parser.add_argument("--overwrite", action="store_true")
|
|
48
|
+
translate_parser.add_argument("--dry-run", action="store_true")
|
|
49
|
+
translate_parser.set_defaults(handler=handle_translate_command)
|
|
50
|
+
|
|
51
|
+
sync_parser = subparsers.add_parser(
|
|
52
|
+
"sync",
|
|
53
|
+
help="Sync tt()-wrapped source templates across all locale TOML files.",
|
|
54
|
+
)
|
|
55
|
+
sync_parser.add_argument("--source", default=".")
|
|
56
|
+
sync_parser.add_argument("--locale-dir", default="locales")
|
|
57
|
+
sync_parser.add_argument("--dry-run", action="store_true")
|
|
58
|
+
sync_parser.set_defaults(handler=handle_sync_command)
|
|
59
|
+
|
|
60
|
+
init_parser = subparsers.add_parser(
|
|
61
|
+
"init",
|
|
62
|
+
help="Initialize locale TOML files from collected tt()-wrapped source templates.",
|
|
63
|
+
)
|
|
64
|
+
init_parser.add_argument("--source", default=".")
|
|
65
|
+
init_parser.add_argument("--locale-dir", default="locales")
|
|
66
|
+
init_parser.add_argument("--locales", nargs="+", required=True)
|
|
67
|
+
init_parser.add_argument("--force", action="store_true")
|
|
68
|
+
init_parser.add_argument("--dry-run", action="store_true")
|
|
69
|
+
init_parser.set_defaults(handler=handle_init_command)
|
|
70
|
+
|
|
71
|
+
return parser
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def main(argv: list[str] | None = None) -> int:
|
|
75
|
+
parser = build_parser()
|
|
76
|
+
args = parser.parse_args(argv)
|
|
77
|
+
return args.handler(args)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
__all__ = [
|
|
81
|
+
"BatchTranslationItem",
|
|
82
|
+
"BatchTranslationOutcome",
|
|
83
|
+
"BatchTranslationRequest",
|
|
84
|
+
"OpenAICompatibleClient",
|
|
85
|
+
"PlaceholderSpec",
|
|
86
|
+
"TranslationResult",
|
|
87
|
+
"TranslationTask",
|
|
88
|
+
"build_parser",
|
|
89
|
+
"handle_init_command",
|
|
90
|
+
"handle_sync_command",
|
|
91
|
+
"handle_translate_command",
|
|
92
|
+
"main",
|
|
93
|
+
"validate_translated_text",
|
|
94
|
+
]
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from babel import Locale
|
|
6
|
+
|
|
7
|
+
from ..toml_io import load_string_table
|
|
8
|
+
|
|
9
|
+
SKIPPED_SOURCE_DIR_NAMES = {
|
|
10
|
+
"__pycache__",
|
|
11
|
+
"build",
|
|
12
|
+
"dist",
|
|
13
|
+
"node_modules",
|
|
14
|
+
}
|
|
15
|
+
NO_TRANSLATION = "NO_TRANSLATION"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def load_shared_cues(locale_dir: Path) -> dict[str, str]:
|
|
19
|
+
cue_dir = locale_dir.parent / f".{locale_dir.name}_cue"
|
|
20
|
+
merged_cues: dict[str, str] = {}
|
|
21
|
+
for cue_path in sorted(cue_dir.glob("*.toml")):
|
|
22
|
+
if not cue_path.is_file():
|
|
23
|
+
continue
|
|
24
|
+
for key, value in load_string_table(str(cue_path)).items():
|
|
25
|
+
merged_cues.setdefault(key, value)
|
|
26
|
+
return merged_cues
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def build_source_cue_path(locale_dir: Path, locale_name: str) -> Path:
|
|
30
|
+
cue_dir = locale_dir.parent / f".{locale_dir.name}_cue"
|
|
31
|
+
return cue_dir / f"{locale_name}.toml"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def list_locale_files(locale_dir: Path) -> list[Path]:
|
|
35
|
+
return sorted(path for path in locale_dir.glob("*.toml") if path.is_file())
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def should_recurse_into_directory(path: str) -> bool:
|
|
39
|
+
name = Path(path).name
|
|
40
|
+
return not name.startswith(".") and name not in SKIPPED_SOURCE_DIR_NAMES
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def normalize_language(locale_name: str) -> str:
|
|
44
|
+
return Locale.parse(locale_name).language
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def locale_display_name(locale_name: str) -> str:
|
|
48
|
+
locale = Locale.parse(locale_name)
|
|
49
|
+
return locale.get_display_name("en").title()
|