|
| 1 | +# Inject locale |
| 2 | + |
| 3 | +## Introduction |
| 4 | + |
| 5 | +When your translation files include locale codes as data values, such as `"locale": "en"` or `"lang": "fr"`, these codes create two problems. First, they are redundant because the file's locale is already determined by its location or filename. Second, if sent to translation services, they could be mistranslated or increase translation costs unnecessarily. |
| 6 | + |
| 7 | +The inject locale feature solves both problems. It automatically removes locale codes before translation and re-inserts them afterward, ensuring the codes always match each file's actual locale without manual intervention. |
| 8 | + |
| 9 | +## What inject locale does |
| 10 | + |
| 11 | +Inject locale manages specific fields in your translation files that contain locale codes. You specify which fields to manage using key patterns, and Lingo handles the rest during the translation process. |
| 12 | + |
| 13 | +The feature operates in two phases. During the reading phase, Lingo identifies fields where the value matches the current locale and temporarily removes them. During the writing phase, Lingo re-inserts these fields with the appropriate locale code for each target language. |
| 14 | + |
| 15 | +## Supported bucket types |
| 16 | + |
| 17 | +Inject locale works with these bucket types: |
| 18 | + |
| 19 | +- json |
| 20 | +- json5 |
| 21 | +- jsonc |
| 22 | +- json-dictionary |
| 23 | + |
| 24 | +Other bucket types do not support this feature. |
| 25 | + |
| 26 | +## Configuration |
| 27 | + |
| 28 | +Add the `injectLocale` option to your bucket configuration in `i18n.json`. The value is an array of key patterns that specify which fields contain locale codes. |
| 29 | + |
| 30 | +```json |
| 31 | +{ |
| 32 | + "version": 1.9, |
| 33 | + "locale": { |
| 34 | + "source": "en", |
| 35 | + "targets": ["fr", "de", "es"] |
| 36 | + }, |
| 37 | + "buckets": { |
| 38 | + "json": { |
| 39 | + "include": ["locales/[locale].json"], |
| 40 | + "injectLocale": ["locale"] |
| 41 | + } |
| 42 | + } |
| 43 | +} |
| 44 | +``` |
| 45 | + |
| 46 | +In this configuration, any field named `locale` that contains a locale code will be automatically managed. |
| 47 | + |
| 48 | +## Key patterns |
| 49 | + |
| 50 | +Key patterns specify which fields to manage. Patterns use dot notation for nested fields and support wildcards for flexible matching. |
| 51 | + |
| 52 | +### Simple patterns |
| 53 | + |
| 54 | +Simple patterns match specific fields by name. |
| 55 | + |
| 56 | +**Top-level field:** |
| 57 | + |
| 58 | +```json |
| 59 | +{ |
| 60 | + "injectLocale": ["locale"] |
| 61 | +} |
| 62 | +``` |
| 63 | + |
| 64 | +This matches: |
| 65 | + |
| 66 | +```json |
| 67 | +{ |
| 68 | + "locale": "en" |
| 69 | +} |
| 70 | +``` |
| 71 | + |
| 72 | +**Nested field:** |
| 73 | + |
| 74 | +```json |
| 75 | +{ |
| 76 | + "injectLocale": ["meta.locale"] |
| 77 | +} |
| 78 | +``` |
| 79 | + |
| 80 | +This matches: |
| 81 | + |
| 82 | +```json |
| 83 | +{ |
| 84 | + "meta": { |
| 85 | + "locale": "en" |
| 86 | + } |
| 87 | +} |
| 88 | +``` |
| 89 | + |
| 90 | +**Multiple fields:** |
| 91 | + |
| 92 | +```json |
| 93 | +{ |
| 94 | + "injectLocale": ["locale", "lang", "meta.language"] |
| 95 | +} |
| 96 | +``` |
| 97 | + |
| 98 | +This matches any of these fields if their value is a locale code. |
| 99 | + |
| 100 | +### Wildcard patterns |
| 101 | + |
| 102 | +Wildcards match multiple fields with a single pattern. Use `*` to match any single path segment. |
| 103 | + |
| 104 | +**Single wildcard:** |
| 105 | + |
| 106 | +```json |
| 107 | +{ |
| 108 | + "injectLocale": ["pages.*.locale"] |
| 109 | +} |
| 110 | +``` |
| 111 | + |
| 112 | +This matches: |
| 113 | + |
| 114 | +```json |
| 115 | +{ |
| 116 | + "pages": { |
| 117 | + "home": { "locale": "en" }, |
| 118 | + "about": { "locale": "en" }, |
| 119 | + "contact": { "locale": "en" } |
| 120 | + } |
| 121 | +} |
| 122 | +``` |
| 123 | + |
| 124 | +**Multiple wildcards:** |
| 125 | + |
| 126 | +```json |
| 127 | +{ |
| 128 | + "injectLocale": ["sections.*.meta.locale"] |
| 129 | +} |
| 130 | +``` |
| 131 | + |
| 132 | +This matches nested structures where locale codes appear at a consistent depth. |
| 133 | + |
| 134 | +### Forward slash notation |
| 135 | + |
| 136 | +Patterns support forward slashes for keys that contain slashes in their names. |
| 137 | + |
| 138 | +```json |
| 139 | +{ |
| 140 | + "injectLocale": ["meta/a/lang", "meta/b/lang"] |
| 141 | +} |
| 142 | +``` |
| 143 | + |
| 144 | +This matches: |
| 145 | + |
| 146 | +```json |
| 147 | +{ |
| 148 | + "meta/a/lang": "en", |
| 149 | + "meta/b/lang": "en" |
| 150 | +} |
| 151 | +``` |
| 152 | + |
| 153 | +## How inject locale works |
| 154 | + |
| 155 | +Understanding the mechanism helps you predict behavior and troubleshoot issues. |
| 156 | + |
| 157 | +### Reading phase |
| 158 | + |
| 159 | +When Lingo reads a source file, it scans for fields matching your patterns. For each match, it checks whether the field's value equals the current locale code. If the value matches, Lingo removes the field temporarily. If the value does not match, Lingo leaves the field unchanged. |
| 160 | + |
| 161 | +**Example:** |
| 162 | + |
| 163 | +Source file `locales/en.json`: |
| 164 | + |
| 165 | +```json |
| 166 | +{ |
| 167 | + "locale": "en", |
| 168 | + "welcome": "Welcome", |
| 169 | + "meta": { |
| 170 | + "locale": "en" |
| 171 | + } |
| 172 | +} |
| 173 | +``` |
| 174 | + |
| 175 | +Configuration: |
| 176 | + |
| 177 | +```json |
| 178 | +{ |
| 179 | + "injectLocale": ["locale", "meta.locale"] |
| 180 | +} |
| 181 | +``` |
| 182 | + |
| 183 | +After reading, the internal representation excludes the locale fields: |
| 184 | + |
| 185 | +```json |
| 186 | +{ |
| 187 | + "welcome": "Welcome" |
| 188 | +} |
| 189 | +``` |
| 190 | + |
| 191 | +The removed fields are recorded so they can be re-inserted later. |
| 192 | + |
| 193 | +### Writing phase |
| 194 | + |
| 195 | +When Lingo writes a translated file, it re-inserts the fields that were removed during reading. Each field receives the locale code appropriate for the target file. |
| 196 | + |
| 197 | +**Example:** |
| 198 | + |
| 199 | +After translating to French, Lingo writes `locales/fr.json`: |
| 200 | + |
| 201 | +```json |
| 202 | +{ |
| 203 | + "locale": "fr", |
| 204 | + "welcome": "Bienvenue", |
| 205 | + "meta": { |
| 206 | + "locale": "fr" |
| 207 | + } |
| 208 | +} |
| 209 | +``` |
| 210 | + |
| 211 | +The locale fields now contain `"fr"` instead of `"en"`, matching the target locale. |
| 212 | + |
| 213 | +## Conditional behavior |
| 214 | + |
| 215 | +Inject locale only affects fields that meet specific conditions. |
| 216 | + |
| 217 | +**A field is removed during reading if:** |
| 218 | + |
| 219 | +1. The field matches a pattern in `injectLocale` |
| 220 | +2. The field's value exactly equals the current locale code |
| 221 | + |
| 222 | +**A field is re-inserted during writing if:** |
| 223 | + |
| 224 | +1. The field was removed during reading |
| 225 | +2. The field existed in the original source file |
| 226 | + |
| 227 | +This conditional behavior prevents unintended modifications. If a field's value is not a locale code, Lingo leaves it unchanged. If a field did not exist in the source, Lingo does not add it to translations. |
| 228 | + |
| 229 | +## Complete example |
| 230 | + |
| 231 | +This example demonstrates inject locale with a multi-page application structure. |
| 232 | + |
| 233 | +### Source file |
| 234 | + |
| 235 | +File: `locales/en.json` |
| 236 | + |
| 237 | +```json |
| 238 | +{ |
| 239 | + "locale": "en", |
| 240 | + "pages": { |
| 241 | + "home": { |
| 242 | + "locale": "en", |
| 243 | + "title": "Home", |
| 244 | + "description": "Welcome to our website" |
| 245 | + }, |
| 246 | + "about": { |
| 247 | + "locale": "en", |
| 248 | + "title": "About", |
| 249 | + "description": "Learn more about us" |
| 250 | + }, |
| 251 | + "contact": { |
| 252 | + "locale": "fr", |
| 253 | + "title": "Contact", |
| 254 | + "description": "Get in touch" |
| 255 | + } |
| 256 | + } |
| 257 | +} |
| 258 | +``` |
| 259 | + |
| 260 | +### Configuration |
| 261 | + |
| 262 | +```json |
| 263 | +{ |
| 264 | + "version": 1.9, |
| 265 | + "locale": { |
| 266 | + "source": "en", |
| 267 | + "targets": ["fr", "de"] |
| 268 | + }, |
| 269 | + "buckets": { |
| 270 | + "json": { |
| 271 | + "include": ["locales/[locale].json"], |
| 272 | + "injectLocale": ["locale", "pages.*.locale"] |
| 273 | + } |
| 274 | + } |
| 275 | +} |
| 276 | +``` |
| 277 | + |
| 278 | +### Translation result |
| 279 | + |
| 280 | +File: `locales/fr.json` |
| 281 | + |
| 282 | +```json |
| 283 | +{ |
| 284 | + "locale": "fr", |
| 285 | + "pages": { |
| 286 | + "home": { |
| 287 | + "locale": "fr", |
| 288 | + "title": "Accueil", |
| 289 | + "description": "Bienvenue sur notre site" |
| 290 | + }, |
| 291 | + "about": { |
| 292 | + "locale": "fr", |
| 293 | + "title": "À propos", |
| 294 | + "description": "En savoir plus sur nous" |
| 295 | + }, |
| 296 | + "contact": { |
| 297 | + "locale": "fr", |
| 298 | + "title": "Contact", |
| 299 | + "description": "Contactez-nous" |
| 300 | + } |
| 301 | + } |
| 302 | +} |
| 303 | +``` |
| 304 | + |
| 305 | +Notice that: |
| 306 | + |
| 307 | +- The top-level `locale` field changed from `"en"` to `"fr"` |
| 308 | +- The `pages.home.locale` field changed from `"en"` to `"fr"` |
| 309 | +- The `pages.about.locale` field changed from `"en"` to `"fr"` |
| 310 | +- The `pages.contact.locale` field remained `"fr"` because it did not match the source locale |
| 311 | + |
| 312 | +## Common patterns |
| 313 | + |
| 314 | +These patterns cover typical use cases. |
| 315 | + |
| 316 | +**Application metadata:** |
| 317 | + |
| 318 | +```json |
| 319 | +{ |
| 320 | + "injectLocale": ["locale", "lang", "language"] |
| 321 | +} |
| 322 | +``` |
| 323 | + |
| 324 | +**CMS content:** |
| 325 | + |
| 326 | +```json |
| 327 | +{ |
| 328 | + "injectLocale": ["meta.locale", "content.lang"] |
| 329 | +} |
| 330 | +``` |
| 331 | + |
| 332 | +**Multi-page structures:** |
| 333 | + |
| 334 | +```json |
| 335 | +{ |
| 336 | + "injectLocale": ["*.locale", "pages.*.meta.lang"] |
| 337 | +} |
| 338 | +``` |
| 339 | + |
| 340 | +**Framework configurations:** |
| 341 | + |
| 342 | +```json |
| 343 | +{ |
| 344 | + "injectLocale": ["config.locale", "settings.language"] |
| 345 | +} |
| 346 | +``` |
| 347 | + |
| 348 | +## Relationship to locked keys |
| 349 | + |
| 350 | +The `lockedKeys` feature serves a different purpose. Locked keys prevent specific fields from being translated at all. Inject locale, by contrast, removes fields temporarily but allows other fields in the same file to be translated. |
| 351 | + |
| 352 | +Use `lockedKeys` when a field should never change. Use `injectLocale` when a field should automatically update to match the target locale. |
| 353 | + |
| 354 | +## Troubleshooting |
| 355 | + |
| 356 | +**Locale fields are being translated:** |
| 357 | + |
| 358 | +Check that the field's value exactly matches the source locale code. Inject locale only removes fields when the value is a locale code. If the value is different, the field will be sent for translation. |
| 359 | + |
| 360 | +**Locale fields are not appearing in translations:** |
| 361 | + |
| 362 | +Verify that your patterns match the field paths in your files. Use dot notation for nested fields and ensure wildcard patterns align with your file structure. |
| 363 | + |
| 364 | +**Some locale fields update but others do not:** |
| 365 | + |
| 366 | +Inject locale only affects fields that matched the source locale during reading. If a field contained a different locale code in the source file, it will not be updated in translations. |
| 367 | + |
| 368 | +## Technical details |
| 369 | + |
| 370 | +Inject locale is implemented as a loader in the translation pipeline. The loader processes files at `src/cli/loaders/inject-locale.ts:6` and uses the minimatch library for pattern matching. |
| 371 | + |
| 372 | +During the pull operation, the loader calls `_getKeysWithLocales` at `src/cli/loaders/inject-locale.ts:14` to identify matching fields and removes them using lodash's `omit` function. During the push operation, the loader re-inserts the removed fields at `src/cli/loaders/inject-locale.ts:30` using lodash's `set` function with the target locale. |
| 373 | + |
| 374 | +The feature was introduced in configuration version 1.3 and enhanced in version 0.107.0 to support forward slash notation in patterns. |
0 commit comments