i18n-keyless-rails 3.5.0
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.
- checksums.yaml +7 -0
- data/LICENSE.md +21 -0
- data/README.md +237 -0
- data/SKILL.md +106 -0
- data/lib/i18n-keyless-rails.rb +4 -0
- data/lib/i18n_keyless/api_client.rb +355 -0
- data/lib/i18n_keyless/backend.rb +52 -0
- data/lib/i18n_keyless/config.rb +125 -0
- data/lib/i18n_keyless/dictionary_store.rb +151 -0
- data/lib/i18n_keyless/helper.rb +21 -0
- data/lib/i18n_keyless/locale.rb +103 -0
- data/lib/i18n_keyless/middleware.rb +30 -0
- data/lib/i18n_keyless/miss.rb +44 -0
- data/lib/i18n_keyless/railtie.rb +31 -0
- data/lib/i18n_keyless/translate_missing_keys_job.rb +11 -0
- data/lib/i18n_keyless/translator.rb +300 -0
- data/lib/i18n_keyless/version.rb +8 -0
- data/lib/i18n_keyless.rb +129 -0
- data/llms.txt +101 -0
- metadata +95 -0
data/llms.txt
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# i18n-keyless-rails
|
|
2
|
+
|
|
3
|
+
> Keyless translations for Ruby on Rails 7 and 8. `t('Welcome to our app')` (the source string where a key would go) resolves through the i18n-keyless API: AI translation on the first miss, cached in `Rails.cache`, served from there. One gem, two `.env` lines, zero code change for the strings that already read as text.
|
|
4
|
+
|
|
5
|
+
This file is the whole gem documentation as one pasteable Markdown page. The general i18n-keyless documentation (dashboard, MCP server, the JavaScript SDKs) is at https://docs.i18n-keyless.com/llms.txt.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
bundle add i18n-keyless-rails
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
```dotenv
|
|
14
|
+
I18N_KEYLESS_API_KEY=your-key
|
|
15
|
+
I18N_KEYLESS_LANGUAGES=en,fr,es # required for translation: every language the app serves
|
|
16
|
+
I18N_KEYLESS_PRIMARY_LANG=en # optional, default I18n.default_locale
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Requirements: Ruby >= 3.1, Rails 7.0 to 8.x (or any Ruby app with `i18n` and `activesupport`). The Railtie `I18nKeyless::Railtie` is auto-loaded.
|
|
20
|
+
|
|
21
|
+
## Use
|
|
22
|
+
|
|
23
|
+
```ruby
|
|
24
|
+
t('Welcome to our app') # a source string: keyless
|
|
25
|
+
t('Welcome %{name}', name: user.name) # I18n placeholders
|
|
26
|
+
t('8 heures', context: 'duration') # context: stored as "8 heures__duration"
|
|
27
|
+
i18nk('8 heures', context: 'clock time') # the helper (views, controllers, mailers, jobs)
|
|
28
|
+
i18nk('Payer', namespace: 'checkout') # an i18n-keyless namespace
|
|
29
|
+
i18nk('close') # a lowercase word: the helper never reads it as a key
|
|
30
|
+
t('users.index.title') # a Rails key: YAML as before, never sent
|
|
31
|
+
I18n.locale = :"pt-BR" # Rails locales are mapped (pt-BR)
|
|
32
|
+
I18nKeyless.t('Bonjour', locale: :en) # outside a view
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
`i18nk(text, values = nil, context: nil, locale: nil, namespace: nil, **values)`.
|
|
36
|
+
|
|
37
|
+
Rails-key rule (`I18nKeyless.keyless_key?`, `config.rails_key_pattern` = `/\A[a-z0-9_]+(\.[a-z0-9_]+)*\z/`): a Symbol, a `scope:`, or a lowercase identifier path is a Rails key; anything with a space, an uppercase letter or punctuation is a source string.
|
|
38
|
+
|
|
39
|
+
## Behaviour
|
|
40
|
+
|
|
41
|
+
- Primary locale: the source string is returned, no API call.
|
|
42
|
+
- The backend is `I18n::Backend::Chain.new(<the app's backend>, I18nKeyless::Backend.new)`: a YAML line wins.
|
|
43
|
+
- Other locale, first miss in the process: the locale's dictionary is read from the cache; absent, it is fetched once from `GET {api_url}/translate/{lang}?last_refresh=` and stored, then kept in the process.
|
|
44
|
+
- Missing string: source text returned (placeholders replaced), miss recorded. After the response (`I18nKeyless::Middleware`, a `Rack::BodyProxy`), after each ActiveJob, or as an `I18nKeyless::TranslateMissingKeysJob` when `queue` is set, misses are sent to `POST {api_url}/translate` (body `{key, context?, namespace?, languages, primaryLanguage}`, where `languages` is the configured list plus the primary, never the locale that missed), deduplicated by key and context, 30 in flight at most. Answers are merged into the cache. With an empty `languages` config nothing is sent (the API stores the received list as the project's languages): the source text is served and one warning is logged per process.
|
|
45
|
+
- Stale dictionary (older than `cache_ttl`): served, then revalidated after the response with `If-None-Match: <etag>`; `304` keeps it.
|
|
46
|
+
- HTTP policy: 10 s timeout, retries with backoff 500 ms then 1500 ms on network error, timeout, 429, 5xx; no retry on other 4xx; never raises. A failed fetch is remembered 60 s.
|
|
47
|
+
- Headers on every request: `Authorization: Bearer <api_key>`, `Content-Type: application/json`, `Version: 3.5.0`, `sdk: rails` (a server label, counted like `node`). No `unique_id` (a server is counted by its connection).
|
|
48
|
+
- Usage analytics (node SDK rule): the UTC date each string was last served is recorded (namespace, `key__context`), merged into a cumulative map in the cache (never cleared), and POSTed to `{api_url}/translate/last-used-translations` as `{primaryLanguage, translationsUsageByNamespace}` after the response, at most once every 10 s across processes (an `unless_exist` cache lock), only when the map changed. Fire and forget. `usage: false` disables it.
|
|
49
|
+
|
|
50
|
+
## Configuration (I18nKeyless.configure { |c| ... })
|
|
51
|
+
|
|
52
|
+
| key | env | default |
|
|
53
|
+
| --- | --- | --- |
|
|
54
|
+
| enabled | I18N_KEYLESS_ENABLED | true |
|
|
55
|
+
| api_key | I18N_KEYLESS_API_KEY | nil |
|
|
56
|
+
| api_url | I18N_KEYLESS_API_URL | https://api.i18n-keyless.com |
|
|
57
|
+
| primary | I18N_KEYLESS_PRIMARY_LANG | I18n.default_locale |
|
|
58
|
+
| languages | I18N_KEYLESS_LANGUAGES | empty: required for translation, nothing is sent without it |
|
|
59
|
+
| namespace | I18N_KEYLESS_NAMESPACE | default |
|
|
60
|
+
| cache | | Rails.cache (a MemoryStore outside Rails) |
|
|
61
|
+
| cache_ttl | I18N_KEYLESS_CACHE_TTL | 3600 |
|
|
62
|
+
| cache_prefix | | i18n-keyless |
|
|
63
|
+
| timeout | | 10 |
|
|
64
|
+
| retry | | [500, 1500] |
|
|
65
|
+
| concurrency | | 30 |
|
|
66
|
+
| usage | I18N_KEYLESS_USAGE | true |
|
|
67
|
+
| queue | I18N_KEYLESS_QUEUE | nil |
|
|
68
|
+
| logger | | Rails.logger |
|
|
69
|
+
| rails_key_pattern | | `/\A[a-z0-9_]+(\.[a-z0-9_]+)*\z/` (nil: every string is keyless) |
|
|
70
|
+
|
|
71
|
+
## Locale mapping
|
|
72
|
+
|
|
73
|
+
Rails locale to i18n-keyless code: exact match first (`pt-BR`, `zh-Hans`), then Chinese by region (`zh_CN`, `zh_SG` to `zh-Hans`; `zh_TW`, `zh_HK`, `zh_MO` to `zh-Hant`), `es-419` to `es-MX`, then the bare language (`fr_FR` to `fr`, `pt-AO` to `pt`). Unknown locales are left alone. 48 codes: ar, bn, ca, zh-Hans, zh-Hant, hr, cs, da, nl, en, en-GB, fi, fr, fr-CA, de, el, gu, he, hi, hu, id, it, ja, kn, ko, ms, ml, mr, no, or, pl, pt, pt-BR, pa, ro, ru, sk, sl, es, es-MX, sv, ta, te, th, tr, uk, ur, vi. List the served locales in `config.i18n.available_locales` when they have no YAML file.
|
|
74
|
+
|
|
75
|
+
## Limitations
|
|
76
|
+
|
|
77
|
+
- YAML plural rules (`one:` / `other:`) do not apply to a source string. Use `i18nk()` with a `context` per plural form, or a YAML file.
|
|
78
|
+
- A lowercase one-word string (`t('close')`) is read as a Rails key: use `i18nk('close')`.
|
|
79
|
+
- The `_html` suffix convention does not apply to a source string.
|
|
80
|
+
- Misses are deduplicated by key and context (the SDK queue ignores the context).
|
|
81
|
+
- Usage counts only the strings served by this gem: a string served from YAML is not counted.
|
|
82
|
+
- The miss guard and the usage lock live in the cache: a `:memory_store` is per process; use a shared store in production.
|
|
83
|
+
- A source string is capped at 2000 characters (`context` and `namespace` at 200). Long-form content is allowed, but a blog post is one translation **per Markdown block** of about 1000 characters: keep the Markdown inside each block, give every block of the document the same `context` (one very short summary of it) and one `namespace` per document. https://docs.i18n-keyless.com/docs/guides/long-form-content
|
|
84
|
+
|
|
85
|
+
## Classes
|
|
86
|
+
|
|
87
|
+
- `I18nKeyless`: `configure`, `config`, `translate` / `t`, `flush`, `install!`, `uninstall!`, `keyless_key?`, `translator`, `reset!`.
|
|
88
|
+
- `I18nKeyless::Railtie`: middleware, helper mix-ins, `after_perform` flush, `after_initialize` chain, `at_exit` flush.
|
|
89
|
+
- `I18nKeyless::Backend`: the chained I18n backend (`lookup`, `reload!`).
|
|
90
|
+
- `I18nKeyless::Translator`: `get`, `lookup`, `ensure_loaded`, `flush`, `translate_now`, `pending_misses`, `pending_usage`, `resolve_namespace`.
|
|
91
|
+
- `I18nKeyless::ApiClient`: `fetch_dictionary`, `translate`, `send_usage`, `dictionary_url`, `translate_body`, `decide`, `error_for`; constants `VERSION`, `SDK`, `DEFAULT_URL`.
|
|
92
|
+
- `I18nKeyless::DictionaryStore`: cache entries `{translations, etag, fetched_at, failed}`, `claim_miss` / `release_miss` guard, `usage` / `merge_usage` / `claim_usage_slot`.
|
|
93
|
+
- `I18nKeyless::Locale`: `to_lang`, `resolve`, `lang?`, `to_app_store_locale`, `AVAILABLE_LANGS`.
|
|
94
|
+
- `I18nKeyless::Miss`: `key`, `context`, `namespace`, `langs`, `lookup_key`, `id`.
|
|
95
|
+
- `I18nKeyless::Helper`: `i18nk`. `I18nKeyless::Middleware`: the Rack flush. `I18nKeyless::TranslateMissingKeysJob`: queued misses.
|
|
96
|
+
|
|
97
|
+
## Links
|
|
98
|
+
|
|
99
|
+
- Get an API key: https://i18n-keyless.com/#get-api-key
|
|
100
|
+
- Dashboard: https://i18n-keyless.com/dashboard
|
|
101
|
+
- Docs: https://docs.i18n-keyless.com
|
metadata
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: i18n-keyless-rails
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 3.5.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Arnaud Ambroselli
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: activesupport
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '7.0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '7.0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: i18n
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '1.8'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '1.8'
|
|
40
|
+
description: 'Keyless translations for Rails. Write the source string where a key
|
|
41
|
+
would go, t(''Welcome to our app''), and it resolves through the i18n-keyless API:
|
|
42
|
+
AI translation on the first miss, cached in Rails.cache, served from there. One
|
|
43
|
+
gem, two .env lines, no config/locales/*.yml to maintain by hand.'
|
|
44
|
+
email:
|
|
45
|
+
- arnaud.ambroselli.io@gmail.com
|
|
46
|
+
executables: []
|
|
47
|
+
extensions: []
|
|
48
|
+
extra_rdoc_files: []
|
|
49
|
+
files:
|
|
50
|
+
- LICENSE.md
|
|
51
|
+
- README.md
|
|
52
|
+
- SKILL.md
|
|
53
|
+
- lib/i18n-keyless-rails.rb
|
|
54
|
+
- lib/i18n_keyless.rb
|
|
55
|
+
- lib/i18n_keyless/api_client.rb
|
|
56
|
+
- lib/i18n_keyless/backend.rb
|
|
57
|
+
- lib/i18n_keyless/config.rb
|
|
58
|
+
- lib/i18n_keyless/dictionary_store.rb
|
|
59
|
+
- lib/i18n_keyless/helper.rb
|
|
60
|
+
- lib/i18n_keyless/locale.rb
|
|
61
|
+
- lib/i18n_keyless/middleware.rb
|
|
62
|
+
- lib/i18n_keyless/miss.rb
|
|
63
|
+
- lib/i18n_keyless/railtie.rb
|
|
64
|
+
- lib/i18n_keyless/translate_missing_keys_job.rb
|
|
65
|
+
- lib/i18n_keyless/translator.rb
|
|
66
|
+
- lib/i18n_keyless/version.rb
|
|
67
|
+
- llms.txt
|
|
68
|
+
homepage: https://i18n-keyless.com
|
|
69
|
+
licenses:
|
|
70
|
+
- MIT
|
|
71
|
+
metadata:
|
|
72
|
+
homepage_uri: https://i18n-keyless.com
|
|
73
|
+
source_code_uri: https://github.com/arnaudambro/i18n-keyless/tree/main/ports/rails
|
|
74
|
+
documentation_uri: https://docs.i18n-keyless.com
|
|
75
|
+
changelog_uri: https://github.com/arnaudambro/i18n-keyless/blob/main/CHANGELOG.md
|
|
76
|
+
rubygems_mfa_required: 'true'
|
|
77
|
+
rdoc_options: []
|
|
78
|
+
require_paths:
|
|
79
|
+
- lib
|
|
80
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
81
|
+
requirements:
|
|
82
|
+
- - ">="
|
|
83
|
+
- !ruby/object:Gem::Version
|
|
84
|
+
version: '3.1'
|
|
85
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - ">="
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: '0'
|
|
90
|
+
requirements: []
|
|
91
|
+
rubygems_version: 3.6.9
|
|
92
|
+
specification_version: 4
|
|
93
|
+
summary: 'Keyless translations for Ruby on Rails: t(''Welcome to our app'') resolves
|
|
94
|
+
through the i18n-keyless API.'
|
|
95
|
+
test_files: []
|