[{"data":1,"prerenderedAt":2021},["ShallowReactive",2],{"navigation_docs":3,"-ci-cd-gitlab-ci":357,"-ci-cd-gitlab-ci-surround":2018},[4,22,40,53,78,107,132,153,167,176,347],{"title":5,"path":6,"stem":7,"children":8,"page":21},"Introduction","\u002Fintroduction","1.introduction",[9,13,17],{"title":10,"path":11,"stem":12},"Why the-i18n-kit","\u002Fintroduction\u002Fwhy","1.introduction\u002F1.why",{"title":14,"path":15,"stem":16},"Architecture","\u002Fintroduction\u002Farchitecture","1.introduction\u002F2.architecture",{"title":18,"path":19,"stem":20},"Built for Agents","\u002Fintroduction\u002Fbuilt-for-agents","1.introduction\u002F3.built-for-agents",false,{"title":23,"icon":24,"path":25,"stem":26,"children":27,"page":21},"ESLint Plugin","i-lucide-shield-check","\u002Feslint-plugin","10.eslint-plugin",[28,32,36],{"title":29,"path":30,"stem":31},"Install and Presets","\u002Feslint-plugin\u002Finstall","10.eslint-plugin\u002F1.install",{"title":33,"path":34,"stem":35},"The Rules","\u002Feslint-plugin\u002Frules","10.eslint-plugin\u002F2.rules",{"title":37,"path":38,"stem":39},"Layer-Aware Missing Keys","\u002Feslint-plugin\u002Flayer-aware","10.eslint-plugin\u002F3.layer-aware",{"title":41,"path":42,"stem":43,"children":44,"page":21},"Getting Started","\u002Fgetting-started","2.getting-started",[45,49],{"title":46,"path":47,"stem":48},"Set Up the MCP Server","\u002Fgetting-started\u002Fagent-setup","2.getting-started\u002F1.agent-setup",{"title":50,"path":51,"stem":52},"From an Unconfigured Repository to a Gated CI Job","\u002Fgetting-started\u002Fcold-start","2.getting-started\u002F2.cold-start",{"title":54,"path":55,"stem":56,"children":57,"page":21},"Monorepos","\u002Fmonorepos","3.monorepos",[58,62,66,70,74],{"title":59,"path":60,"stem":61},"Layers","\u002Fmonorepos\u002Flayers","3.monorepos\u002F1.layers",{"title":63,"path":64,"stem":65},"The Consumer Graph","\u002Fmonorepos\u002Fconsumer-graph","3.monorepos\u002F2.consumer-graph",{"title":67,"path":68,"stem":69},"The Shared Library Scenario","\u002Fmonorepos\u002Fshared-libraries","3.monorepos\u002F3.shared-libraries",{"title":71,"path":72,"stem":73},"Duplicates and Divergence","\u002Fmonorepos\u002Fduplicates-and-divergence","3.monorepos\u002F4.duplicates-and-divergence",{"title":75,"path":76,"stem":77},"Misplaced Usages","\u002Fmonorepos\u002Fmisplaced-usages","3.monorepos\u002F5.misplaced-usages",{"title":79,"path":80,"stem":81,"children":82,"page":21},"Frameworks","\u002Fframeworks","4.frameworks",[83,87,91,95,99,103],{"title":84,"path":85,"stem":86},"Detection","\u002Fframeworks\u002Fdetection","4.frameworks\u002F1.detection",{"title":88,"path":89,"stem":90},"Nuxt","\u002Fframeworks\u002Fnuxt","4.frameworks\u002F2.nuxt",{"title":92,"path":93,"stem":94},"Laravel","\u002Fframeworks\u002Flaravel","4.frameworks\u002F3.laravel",{"title":96,"path":97,"stem":98},"Vue","\u002Fframeworks\u002Fvue","4.frameworks\u002F4.vue",{"title":100,"path":101,"stem":102},"React \u002F Next.js","\u002Fframeworks\u002Freact-next","4.frameworks\u002F5.react-next",{"title":104,"path":105,"stem":106},"Generic","\u002Fframeworks\u002Fgeneric","4.frameworks\u002F6.generic",{"title":108,"path":109,"stem":110,"children":111,"page":21},"Configuration","\u002Fconfiguration","5.configuration",[112,116,120,124,128],{"title":113,"path":114,"stem":115},"Where Configuration Lives","\u002Fconfiguration\u002Fwhere-config-lives","5.configuration\u002F1.where-config-lives",{"title":117,"path":118,"stem":119},"Precedence and Conflicts","\u002Fconfiguration\u002Fprecedence","5.configuration\u002F2.precedence",{"title":121,"path":122,"stem":123},"Declared vs. Derived Configuration","\u002Fconfiguration\u002Fdeclared-vs-derived","5.configuration\u002F3.declared-vs-derived",{"title":125,"path":126,"stem":127},"Configuration Fields","\u002Fconfiguration\u002Freference","5.configuration\u002F4.reference",{"title":129,"path":130,"stem":131},"Referring to Locales","\u002Fconfiguration\u002Flocale-refs","5.configuration\u002F5.locale-refs",{"title":133,"path":134,"stem":135,"children":136,"page":21},"Nuxt Module","\u002Fnuxt-module","6.nuxt-module",[137,141,145,149],{"title":138,"path":139,"stem":140},"Install and Setup","\u002Fnuxt-module\u002Finstall","6.nuxt-module\u002F1.install",{"title":142,"path":143,"stem":144},"What It Publishes","\u002Fnuxt-module\u002Fwhat-it-publishes","6.nuxt-module\u002F2.what-it-publishes",{"title":146,"path":147,"stem":148},"What It Validates","\u002Fnuxt-module\u002Fvalidation","6.nuxt-module\u002F3.validation",{"title":150,"path":151,"stem":152},"Options and Limits","\u002Fnuxt-module\u002Foptions-and-limits","6.nuxt-module\u002F4.options-and-limits",{"title":154,"icon":155,"path":156,"stem":157,"children":158,"page":21},"CI\u002FCD","i-lucide-git-pull-request","\u002Fci-cd","7.ci-cd",[159,163],{"title":160,"path":161,"stem":162},"GitHub Actions","\u002Fci-cd\u002Fgithub-actions","7.ci-cd\u002F1.github-actions",{"title":164,"path":165,"stem":166},"GitLab CI","\u002Fci-cd\u002Fgitlab-ci","7.ci-cd\u002F2.gitlab-ci",{"title":168,"path":169,"stem":170,"children":171,"page":21},"Concepts","\u002Fconcepts","8.concepts",[172],{"title":173,"path":174,"stem":175},"Translation Modes","\u002Fconcepts\u002Ftranslation-modes","8.concepts\u002F1.translation-modes",{"title":177,"path":178,"stem":179,"children":180},"Reference","\u002Freference","9.reference",[181,183,261,339,343],{"title":177,"path":178,"stem":182},"9.reference\u002Findex",{"title":184,"path":185,"stem":186,"children":187},"CLI Commands","\u002Freference\u002Fcli","9.reference\u002F1.cli\u002Findex",[188,189,193,197,201,205,209,213,217,221,225,229,233,237,241,245,249,253,257],{"title":184,"path":185,"stem":186},{"title":190,"path":191,"stem":192},"add","\u002Freference\u002Fcli\u002Fadd","9.reference\u002F1.cli\u002Fadd",{"title":194,"path":195,"stem":196},"check","\u002Freference\u002Fcli\u002Fcheck","9.reference\u002F1.cli\u002Fcheck",{"title":198,"path":199,"stem":200},"find-duplicates","\u002Freference\u002Fcli\u002Ffind-duplicates","9.reference\u002F1.cli\u002Ffind-duplicates",{"title":202,"path":203,"stem":204},"get","\u002Freference\u002Fcli\u002Fget","9.reference\u002F1.cli\u002Fget",{"title":206,"path":207,"stem":208},"init","\u002Freference\u002Fcli\u002Finit","9.reference\u002F1.cli\u002Finit",{"title":210,"path":211,"stem":212},"missing","\u002Freference\u002Fcli\u002Fmissing","9.reference\u002F1.cli\u002Fmissing",{"title":214,"path":215,"stem":216},"move","\u002Freference\u002Fcli\u002Fmove","9.reference\u002F1.cli\u002Fmove",{"title":218,"path":219,"stem":220},"remove","\u002Freference\u002Fcli\u002Fremove","9.reference\u002F1.cli\u002Fremove",{"title":222,"path":223,"stem":224},"remove-orphans","\u002Freference\u002Fcli\u002Fremove-orphans","9.reference\u002F1.cli\u002Fremove-orphans",{"title":226,"path":227,"stem":228},"rename","\u002Freference\u002Fcli\u002Frename","9.reference\u002F1.cli\u002Frename",{"title":230,"path":231,"stem":232},"scaffold","\u002Freference\u002Fcli\u002Fscaffold","9.reference\u002F1.cli\u002Fscaffold",{"title":234,"path":235,"stem":236},"scan","\u002Freference\u002Fcli\u002Fscan","9.reference\u002F1.cli\u002Fscan",{"title":238,"path":239,"stem":240},"search","\u002Freference\u002Fcli\u002Fsearch","9.reference\u002F1.cli\u002Fsearch",{"title":242,"path":243,"stem":244},"status","\u002Freference\u002Fcli\u002Fstatus","9.reference\u002F1.cli\u002Fstatus",{"title":246,"path":247,"stem":248},"translate","\u002Freference\u002Fcli\u002Ftranslate","9.reference\u002F1.cli\u002Ftranslate",{"title":250,"path":251,"stem":252},"translate-key","\u002Freference\u002Fcli\u002Ftranslate-key","9.reference\u002F1.cli\u002Ftranslate-key",{"title":254,"path":255,"stem":256},"update","\u002Freference\u002Fcli\u002Fupdate","9.reference\u002F1.cli\u002Fupdate",{"title":258,"path":259,"stem":260},"write","\u002Freference\u002Fcli\u002Fwrite","9.reference\u002F1.cli\u002Fwrite",{"title":262,"path":263,"stem":264,"children":265},"MCP Tools","\u002Freference\u002Fmcp","9.reference\u002F2.mcp\u002Findex",[266,267,271,275,279,283,287,291,295,299,303,307,311,315,319,323,327,331,335],{"title":262,"path":263,"stem":264},{"title":268,"path":269,"stem":270},"discover","\u002Freference\u002Fmcp\u002Fdiscover","9.reference\u002F2.mcp\u002Fdiscover",{"title":272,"path":273,"stem":274},"find_duplicate_keys","\u002Freference\u002Fmcp\u002Ffind_duplicate_keys","9.reference\u002F2.mcp\u002Ffind_duplicate_keys",{"title":276,"path":277,"stem":278},"find_empty_translations","\u002Freference\u002Fmcp\u002Ffind_empty_translations","9.reference\u002F2.mcp\u002Ffind_empty_translations",{"title":280,"path":281,"stem":282},"find_orphan_keys","\u002Freference\u002Fmcp\u002Ffind_orphan_keys","9.reference\u002F2.mcp\u002Ffind_orphan_keys",{"title":284,"path":285,"stem":286},"find_undefined_keys","\u002Freference\u002Fmcp\u002Ffind_undefined_keys","9.reference\u002F2.mcp\u002Ffind_undefined_keys",{"title":288,"path":289,"stem":290},"get_missing_translations","\u002Freference\u002Fmcp\u002Fget_missing_translations","9.reference\u002F2.mcp\u002Fget_missing_translations",{"title":292,"path":293,"stem":294},"get_translation_status","\u002Freference\u002Fmcp\u002Fget_translation_status","9.reference\u002F2.mcp\u002Fget_translation_status",{"title":296,"path":297,"stem":298},"get_translations","\u002Freference\u002Fmcp\u002Fget_translations","9.reference\u002F2.mcp\u002Fget_translations",{"title":300,"path":301,"stem":302},"list_namespaces","\u002Freference\u002Fmcp\u002Flist_namespaces","9.reference\u002F2.mcp\u002Flist_namespaces",{"title":304,"path":305,"stem":306},"move_translation_key","\u002Freference\u002Fmcp\u002Fmove_translation_key","9.reference\u002F2.mcp\u002Fmove_translation_key",{"title":308,"path":309,"stem":310},"remove_orphan_keys","\u002Freference\u002Fmcp\u002Fremove_orphan_keys","9.reference\u002F2.mcp\u002Fremove_orphan_keys",{"title":312,"path":313,"stem":314},"remove_translations","\u002Freference\u002Fmcp\u002Fremove_translations","9.reference\u002F2.mcp\u002Fremove_translations",{"title":316,"path":317,"stem":318},"rename_translation_key","\u002Freference\u002Fmcp\u002Frename_translation_key","9.reference\u002F2.mcp\u002Frename_translation_key",{"title":320,"path":321,"stem":322},"scaffold_locale","\u002Freference\u002Fmcp\u002Fscaffold_locale","9.reference\u002F2.mcp\u002Fscaffold_locale",{"title":324,"path":325,"stem":326},"search_translations","\u002Freference\u002Fmcp\u002Fsearch_translations","9.reference\u002F2.mcp\u002Fsearch_translations",{"title":328,"path":329,"stem":330},"translate_key","\u002Freference\u002Fmcp\u002Ftranslate_key","9.reference\u002F2.mcp\u002Ftranslate_key",{"title":332,"path":333,"stem":334},"translate_missing","\u002Freference\u002Fmcp\u002Ftranslate_missing","9.reference\u002F2.mcp\u002Ftranslate_missing",{"title":336,"path":337,"stem":338},"write_translations","\u002Freference\u002Fmcp\u002Fwrite_translations","9.reference\u002F2.mcp\u002Fwrite_translations",{"title":340,"path":341,"stem":342},"GitHub Action","\u002Freference\u002Faction","9.reference\u002F3.action",{"title":344,"path":345,"stem":346},"Programmatic API","\u002Freference\u002Fprogrammatic-api","9.reference\u002F4.programmatic-api",{"title":348,"icon":349,"path":350,"stem":351,"children":352,"page":21},"Extra Topics","i-lucide-microscope","\u002Fextra-topics","91.extra-topics",[353],{"title":354,"path":355,"stem":356},"How Detection Scores Adapters","\u002Fextra-topics\u002Fdetection-scoring","91.extra-topics\u002F1.detection-scoring",{"id":358,"title":164,"body":359,"description":471,"extension":2012,"links":2013,"meta":2014,"navigation":514,"path":165,"seo":2015,"stem":166,"__hash__":2017},"docs\u002F7.ci-cd\u002F2.gitlab-ci.md",{"type":360,"value":361,"toc":1997},"minimark",[362,375,459,464,590,593,637,641,649,691,708,725,730,733,845,858,1093,1103,1116,1121,1133,1210,1213,1224,1228,1231,1245,1267,1280,1285,1292,1358,1364,1436,1449,1459,1463,1466,1506,1517,1524,1528,1546,1557,1626,1629,1640,1644,1650,1653,1662,1678,1682,1698,1823,1829,1931,1968,1972,1993],[363,364,365,366,370,371,374],"p",{},"The template lives at the repository root as ",[367,368,369],"code",{},"gitlab-ci.yml"," and defines three\nhidden jobs you extend. Each one installs ",[367,372,373],{},"@the-i18n-kit\u002Fcli"," and runs one\ncommand:",[376,377,378,397],"table",{},[379,380,381],"thead",{},[382,383,384,388,391,394],"tr",{},[385,386,387],"th",{},"Job",[385,389,390],{},"Runs",[385,392,393],{},"Produces",[385,395,396],{},"Writes to your repository",[398,399,400,421,440],"tbody",{},[382,401,402,408,415,418],{},[403,404,405],"td",{},[367,406,407],{},".i18n-translate",[403,409,410],{},[411,412,413],"a",{"href":247},[367,414,246],{},[403,416,417],{},"Translated locale files, committed and pushed to the branch",[403,419,420],{},"Yes",[382,422,423,428,434,437],{},[403,424,425],{},[367,426,427],{},".i18n-cleanup",[403,429,430],{},[411,431,432],{"href":223},[367,433,222],{},[403,435,436],{},"A Code Quality report of orphan keys",[403,438,439],{},"No — the scan previews by default and removes nothing",[382,441,442,447,453,456],{},[403,443,444],{},[367,445,446],{},".i18n-check",[403,448,449],{},[411,450,451],{"href":195},[367,452,194],{},[403,454,455],{},"A Code Quality report of keys that render as their raw key",[403,457,458],{},"No",[460,461,463],"h2",{"id":462},"including-the-template","Including the Template",[465,466,472],"pre",{"className":467,"code":468,"filename":469,"language":470,"meta":471,"style":471},"language-yaml shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","include:\n  - remote: 'https:\u002F\u002Fraw.githubusercontent.com\u002Ffabkho\u002Fthe-i18n-kit\u002Fmain\u002Fgitlab-ci.yml'\n\nstages:\n  - lint\n\ni18n-check:\n  extends: .i18n-check\n  rules:\n    - if: $CI_PIPELINE_SOURCE == \"merge_request_event\"\n    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH\n",".gitlab-ci.yml","yaml","",[367,473,474,487,509,516,524,532,537,545,556,564,578],{"__ignoreMap":471},[475,476,479,483],"span",{"class":477,"line":478},"line",1,[475,480,482],{"class":481},"swJcz","include",[475,484,486],{"class":485},"sMK4o",":\n",[475,488,490,493,496,499,502,506],{"class":477,"line":489},2,[475,491,492],{"class":485},"  -",[475,494,495],{"class":481}," remote",[475,497,498],{"class":485},":",[475,500,501],{"class":485}," '",[475,503,505],{"class":504},"sfazB","https:\u002F\u002Fraw.githubusercontent.com\u002Ffabkho\u002Fthe-i18n-kit\u002Fmain\u002Fgitlab-ci.yml",[475,507,508],{"class":485},"'\n",[475,510,512],{"class":477,"line":511},3,[475,513,515],{"emptyLinePlaceholder":514},true,"\n",[475,517,519,522],{"class":477,"line":518},4,[475,520,521],{"class":481},"stages",[475,523,486],{"class":485},[475,525,527,529],{"class":477,"line":526},5,[475,528,492],{"class":485},[475,530,531],{"class":504}," lint\n",[475,533,535],{"class":477,"line":534},6,[475,536,515],{"emptyLinePlaceholder":514},[475,538,540,543],{"class":477,"line":539},7,[475,541,542],{"class":481},"i18n-check",[475,544,486],{"class":485},[475,546,548,551,553],{"class":477,"line":547},8,[475,549,550],{"class":481},"  extends",[475,552,498],{"class":485},[475,554,555],{"class":504}," .i18n-check\n",[475,557,559,562],{"class":477,"line":558},9,[475,560,561],{"class":481},"  rules",[475,563,486],{"class":485},[475,565,567,570,573,575],{"class":477,"line":566},10,[475,568,569],{"class":485},"    -",[475,571,572],{"class":481}," if",[475,574,498],{"class":485},[475,576,577],{"class":504}," $CI_PIPELINE_SOURCE == \"merge_request_event\"\n",[475,579,581,583,585,587],{"class":477,"line":580},11,[475,582,569],{"class":485},[475,584,572],{"class":481},[475,586,498],{"class":485},[475,588,589],{"class":504}," $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH\n",[363,591,592],{},"Two details that cost people a pipeline each:",[594,595,596,620],"ul",{},[597,598,599,607,608,611,612,615,616,619],"li",{},[600,601,602,603,606],"strong",{},"Declare a ",[367,604,605],{},"lint"," stage."," All three jobs set ",[367,609,610],{},"stage: lint",". GitLab rejects\nthe whole pipeline configuration when a job names a stage that does not exist,\nbefore running anything. Either declare ",[367,613,614],{},"stages: [lint]"," as above, or override\n",[367,617,618],{},"stage:"," on each extending job to one you already have.",[597,621,622,625,626,629,630,632,633,636],{},[600,623,624],{},"Pin the template."," The URL above tracks ",[367,627,628],{},"main",". Replace ",[367,631,628],{}," with a\nrelease tag to freeze the job definitions, and pin the CLI alongside it with\n",[367,634,635],{},"I18N_CLI_VERSION"," — otherwise both move under you.",[460,638,640],{"id":639},"exit-codes-a-gate-is-not-a-failure","Exit Codes: A Gate Is Not a Failure",[363,642,643,644,648],{},"Every job decides its outcome from the CLI's\n",[411,645,647],{"href":646},"\u002Freference\u002Fcli#exit-codes","exit code",", never by parsing counts out of the JSON\nresult.",[376,650,651,661],{},[379,652,653],{},[382,654,655,658],{},[385,656,657],{},"Exit",[385,659,660],{},"Means",[398,662,663,671,679],{},[382,664,665,668],{},[403,666,667],{},"0",[403,669,670],{},"The run succeeded and no gate tripped",[382,672,673,676],{},[403,674,675],{},"1",[403,677,678],{},"The run itself failed — an unusable API key, an unreadable project, a translate call that produced nothing",[382,680,681,684],{},[403,682,683],{},"2",[403,685,686,687,690],{},"The run succeeded and a ",[600,688,689],{},"gate"," tripped: findings exist and the tool worked",[363,692,693,694,696,697,699,700,703,704,707],{},"That split is what the jobs branch on. ",[367,695,427],{}," and ",[367,698,446],{}," both\ncarry ",[367,701,702],{},"allow_failure.exit_codes: [2]",", so findings show as a yellow warning\nthat still reaches the merge request widget, while a scan that fell over fails\nthe job red. Set ",[367,705,706],{},"allow_failure: false"," on your extending job to make findings\nblock the merge instead.",[363,709,710,713,714,717,718,721,722,724],{},[367,711,712],{},"allow_failure.exit_codes"," applies to the whole job, ",[367,715,716],{},"before_script"," included,\nwhich is why the setup lines in those two jobs end in ",[367,719,720],{},"|| exit 1",": an installer\nthat happened to exit 2 would otherwise be read as findings and pass the job\nyellow having never run a scan. Keep the ",[367,723,720],{}," if you replace those lines.",[460,726,728],{"id":727},"i18n-translate",[367,729,407],{},[363,731,732],{},"Finds missing keys, translates them through your provider, commits the changed\nlocale files and pushes them to the branch that triggered the pipeline. On a\nmerge request that means the translations appear on the MR itself.",[465,734,736],{"className":467,"code":735,"filename":469,"language":470,"meta":471,"style":471},"i18n-translate:\n  extends: .i18n-translate\n  variables:\n    I18N_PROVIDER: google\n    I18N_MODEL: gemini-2.5-flash\n    I18N_API_KEY: $GEMINI_API_KEY\n    I18N_LOCALE_PATHS: \"i18n\u002Flocales\u002F app-*\u002Fi18n\u002Flocales\u002F\"\n  rules:\n    - if: $CI_PIPELINE_SOURCE == \"merge_request_event\"\n      changes:\n        - i18n\u002Flocales\u002Fen.json\n        - app-*\u002Fi18n\u002Flocales\u002Fen.json\n",[367,737,738,744,753,760,770,780,790,806,812,822,829,837],{"__ignoreMap":471},[475,739,740,742],{"class":477,"line":478},[475,741,727],{"class":481},[475,743,486],{"class":485},[475,745,746,748,750],{"class":477,"line":489},[475,747,550],{"class":481},[475,749,498],{"class":485},[475,751,752],{"class":504}," .i18n-translate\n",[475,754,755,758],{"class":477,"line":511},[475,756,757],{"class":481},"  variables",[475,759,486],{"class":485},[475,761,762,765,767],{"class":477,"line":518},[475,763,764],{"class":481},"    I18N_PROVIDER",[475,766,498],{"class":485},[475,768,769],{"class":504}," google\n",[475,771,772,775,777],{"class":477,"line":526},[475,773,774],{"class":481},"    I18N_MODEL",[475,776,498],{"class":485},[475,778,779],{"class":504}," gemini-2.5-flash\n",[475,781,782,785,787],{"class":477,"line":534},[475,783,784],{"class":481},"    I18N_API_KEY",[475,786,498],{"class":485},[475,788,789],{"class":504}," $GEMINI_API_KEY\n",[475,791,792,795,797,800,803],{"class":477,"line":539},[475,793,794],{"class":481},"    I18N_LOCALE_PATHS",[475,796,498],{"class":485},[475,798,799],{"class":485}," \"",[475,801,802],{"class":504},"i18n\u002Flocales\u002F app-*\u002Fi18n\u002Flocales\u002F",[475,804,805],{"class":485},"\"\n",[475,807,808,810],{"class":477,"line":547},[475,809,561],{"class":481},[475,811,486],{"class":485},[475,813,814,816,818,820],{"class":477,"line":558},[475,815,569],{"class":485},[475,817,572],{"class":481},[475,819,498],{"class":485},[475,821,577],{"class":504},[475,823,824,827],{"class":477,"line":566},[475,825,826],{"class":481},"      changes",[475,828,486],{"class":485},[475,830,831,834],{"class":477,"line":580},[475,832,833],{"class":485},"        -",[475,835,836],{"class":504}," i18n\u002Flocales\u002Fen.json\n",[475,838,840,842],{"class":477,"line":839},12,[475,841,833],{"class":485},[475,843,844],{"class":504}," app-*\u002Fi18n\u002Flocales\u002Fen.json\n",[363,846,847,850,851,696,854,857],{},[367,848,849],{},"I18N_PROVIDER",", ",[367,852,853],{},"I18N_MODEL",[367,855,856],{},"I18N_API_KEY"," are required: the script checks\nall three up front and exits 1 naming the ones that are unset, rather than\nfailing later inside the CLI. Everything else is optional.",[376,859,860,873],{},[379,861,862],{},[382,863,864,867,870],{},[385,865,866],{},"Variable",[385,868,869],{},"Default",[385,871,872],{},"Description",[398,874,875,891,906,921,936,951,969,990,1007,1022,1040,1055,1073],{},[382,876,877,882,888],{},[403,878,879],{},[367,880,881],{},"I18N_LAYER",[403,883,884],{},[885,886,887],"em",{},"(empty — all layers)",[403,889,890],{},"The layer to translate. Empty translates every locale-backed layer in one run, one commit, no per-layer push races",[382,892,893,898,903],{},[403,894,895],{},[367,896,897],{},"I18N_LOCALES",[403,899,900],{},[885,901,902],{},"(all but the source)",[403,904,905],{},"Comma-separated target locales",[382,907,908,913,918],{},[403,909,910],{},[367,911,912],{},"I18N_SOURCE_LOCALE",[403,914,915],{},[885,916,917],{},"(project default)",[403,919,920],{},"Reference locale to translate from",[382,922,923,928,933],{},[403,924,925],{},[367,926,927],{},"I18N_KEYS",[403,929,930],{},[885,931,932],{},"(all missing)",[403,934,935],{},"Comma-separated keys, to translate a subset",[382,937,938,943,948],{},[403,939,940],{},[367,941,942],{},"I18N_BATCH_SIZE",[403,944,945],{},[367,946,947],{},"50",[403,949,950],{},"Keys per LLM call",[382,952,953,958,963],{},[403,954,955],{},[367,956,957],{},"I18N_DRY_RUN",[403,959,960],{},[367,961,962],{},"false",[403,964,965,968],{},[367,966,967],{},"\"true\""," previews without writing or committing",[382,970,971,976,981],{},[403,972,973],{},[367,974,975],{},"I18N_FAIL_ON_FAILED",[403,977,978],{},[885,979,980],{},"(off)",[403,982,983,985,986,989],{},[367,984,967],{}," adds ",[367,987,988],{},"--fail-on-failed",", so any key that failed trips a gate and the job exits 2",[382,991,992,996,1001],{},[403,993,994],{},[367,995,635],{},[403,997,998],{},[367,999,1000],{},"latest",[403,1002,1003,1004],{},"npm version or dist-tag for ",[367,1005,1006],{},"the-i18n-cli",[382,1008,1009,1014,1019],{},[403,1010,1011],{},[367,1012,1013],{},"I18N_INSTALL_PEER_DEPS",[403,1015,1016],{},[885,1017,1018],{},"(empty)",[403,1020,1021],{},"Extra npm packages installed globally alongside the CLI",[382,1023,1024,1029,1033],{},[403,1025,1026],{},[367,1027,1028],{},"I18N_PUSH_TOKEN",[403,1030,1031],{},[885,1032,1018],{},[403,1034,1035,1036],{},"Project access token used for the push instead of the job token — see ",[411,1037,1039],{"href":1038},"#pushing-back-to-the-branch","below",[382,1041,1042,1047,1052],{},[403,1043,1044],{},[367,1045,1046],{},"I18N_LOCALE_PATHS",[403,1048,1049],{},[367,1050,1051],{},"i18n\u002Flocales\u002F",[403,1053,1054],{},"Space-separated globs for the locale directories the job stages and commits",[382,1056,1057,1062,1067],{},[403,1058,1059],{},[367,1060,1061],{},"I18N_COMMIT_MESSAGE",[403,1063,1064],{},[885,1065,1066],{},"(generated)",[403,1068,1069,1070],{},"Replaces the default ",[367,1071,1072],{},"i18n: auto-translate missing keys (\u003Clayer>) via \u003Cprovider>",[382,1074,1075,1080,1084],{},[403,1076,1077],{},[367,1078,1079],{},"I18N_AUTOCOMMIT_SKIP_CI",[403,1081,1082],{},[885,1083,980],{},[403,1085,1086,1088,1089,1092],{},[367,1087,967],{}," appends ",[367,1090,1091],{},"[skip ci]"," to the auto-commit",[363,1094,1095,1096,1099,1100,1102],{},"A ",[600,1097,1098],{},"layer"," is a scoped locale directory together with the code that owns it — a\nNuxt layer, a workspace package, a shared UI library, or an app in a monorepo.\nSee ",[411,1101,59],{"href":60}," for how each framework's layer names are\nderived.",[1104,1105,1106,1108,1109,1111,1112,1115],"warning",{},[367,1107,1046],{}," is what the job stages, and it defaults to a single\ndirectory. On a layered project running with an empty ",[367,1110,881],{},", it must\ncover every layer's locale directory — ",[367,1113,1114],{},"\"i18n\u002Flocales\u002F app-*\u002Fi18n\u002Flocales\u002F\""," —\nor the translations for the layers it misses are written on the runner and then\nnever committed. The job reports a clean run and the keys are still missing on\nthe next one.",[1117,1118,1120],"h3",{"id":1119},"when-it-fails","When It Fails",[363,1122,1123,1125,1126,1129,1130,1132],{},[367,1124,407],{}," carries ",[367,1127,1128],{},"allow_failure: true",", so by default nothing it does\nblocks a merge. Set ",[367,1131,706],{}," on your extending job to make it\nblocking.",[376,1134,1135,1147],{},[379,1136,1137],{},[382,1138,1139,1142,1144],{},[385,1140,1141],{},"Situation",[385,1143,657],{},[385,1145,1146],{},"Behavior",[398,1148,1149,1159,1169,1182,1197],{},[382,1150,1151,1154,1156],{},[403,1152,1153],{},"Required variable unset, or an unknown provider",[403,1155,675],{},[403,1157,1158],{},"Fails before calling the CLI",[382,1160,1161,1164,1166],{},[403,1162,1163],{},"The run itself failed — unusable key, nothing translated",[403,1165,675],{},[403,1167,1168],{},"Fails immediately; there is nothing to commit",[382,1170,1171,1177,1179],{},[403,1172,1173,1174,1176],{},"Keys failed, ",[367,1175,975],{}," unset",[403,1178,667],{},[403,1180,1181],{},"Commits what succeeded; failed keys stay missing and the next run retries them",[382,1183,1184,1189,1191],{},[403,1185,1173,1186],{},[367,1187,1188],{},"I18N_FAIL_ON_FAILED: \"true\"",[403,1190,683],{},[403,1192,1193,1196],{},[600,1194,1195],{},"Commits first",", then exits 2",[382,1198,1199,1205,1207],{},[403,1200,1201,1204],{},[367,1202,1203],{},"git push"," rejected",[403,1206,675],{},[403,1208,1209],{},"Fails with the permission hint below",[363,1211,1212],{},"The ordering in the last two rows is deliberate: a tripped gate is recorded and\nacted on only after the commit and push, so a red job never costs you the\ntranslations the run produced.",[363,1214,1215,1216,1219,1220,1223],{},"The raw JSON result is kept as ",[367,1217,1218],{},".i18n-reports\u002Ftranslate-output.json",", uploaded\n",[367,1221,1222],{},"when: always"," and expiring after 7 days.",[460,1225,1227],{"id":1226},"pushing-back-to-the-branch","Pushing Back to the Branch",[363,1229,1230],{},"This is the first thing to check when a translate job cannot push. The job\npushes over HTTPS using a CI token, and a CI token has no write access to the\nrepository unless you grant it. Pick one:",[363,1232,1233,1236,1237,1240,1241,1244],{},[600,1234,1235],{},"a) The project setting (GitLab 17.2 and later)."," Settings → CI\u002FCD → Job token\npermissions → ",[885,1238,1239],{},"Allow Git push requests to the repository",". With it on, the\ndefault ",[367,1242,1243],{},"CI_JOB_TOKEN"," push works and you set no variable at all.",[363,1246,1247,1250,1251,1254,1255,1257,1258,1261,1262,1266],{},[600,1248,1249],{},"b) A project access token."," Create one with the ",[367,1252,1253],{},"write_repository"," scope, put\nit in a masked CI\u002FCD variable, and pass it as ",[367,1256,1028],{},". The job then\nauthenticates as that token instead of the job token. Add the ",[367,1259,1260],{},"api"," scope too —\nit is what lets the job repair the Code Quality widget after pushing, described\nin ",[411,1263,1265],{"href":1264},"#report-widgets-on-translated-merge-requests","Report widgets on translated merge requests",".",[363,1268,1269,1270,1272,1273,1276,1277,1279],{},"When the push fails without ",[367,1271,1028],{}," set, the job log prints both\noptions before exiting 1. If the push fails ",[885,1274,1275],{},"with"," the token set, the token is\nmissing ",[367,1278,1253],{},", expired, or belongs to a role below Developer.",[460,1281,1283],{"id":1282},"i18n-cleanup",[367,1284,427],{},[363,1286,1287,1288,1291],{},"Scans for ",[600,1289,1290],{},"orphan keys"," — keys defined in a locale file with no usage evidence\nin any app that consumes their layer — and writes them as a Code Quality report.",[465,1293,1295],{"className":467,"code":1294,"filename":469,"language":470,"meta":471,"style":471},"i18n-cleanup:\n  extends: .i18n-cleanup\n  rules:\n    - if: $CI_PIPELINE_SOURCE == \"merge_request_event\"\n      changes:\n        - components\u002F**\u002F*.vue\n        - i18n\u002Flocales\u002F*.json\n    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH\n",[367,1296,1297,1303,1312,1318,1328,1334,1341,1348],{"__ignoreMap":471},[475,1298,1299,1301],{"class":477,"line":478},[475,1300,1282],{"class":481},[475,1302,486],{"class":485},[475,1304,1305,1307,1309],{"class":477,"line":489},[475,1306,550],{"class":481},[475,1308,498],{"class":485},[475,1310,1311],{"class":504}," .i18n-cleanup\n",[475,1313,1314,1316],{"class":477,"line":511},[475,1315,561],{"class":481},[475,1317,486],{"class":485},[475,1319,1320,1322,1324,1326],{"class":477,"line":518},[475,1321,569],{"class":485},[475,1323,572],{"class":481},[475,1325,498],{"class":485},[475,1327,577],{"class":504},[475,1329,1330,1332],{"class":477,"line":526},[475,1331,826],{"class":481},[475,1333,486],{"class":485},[475,1335,1336,1338],{"class":477,"line":534},[475,1337,833],{"class":485},[475,1339,1340],{"class":504}," components\u002F**\u002F*.vue\n",[475,1342,1343,1345],{"class":477,"line":539},[475,1344,833],{"class":485},[475,1346,1347],{"class":504}," i18n\u002Flocales\u002F*.json\n",[475,1349,1350,1352,1354,1356],{"class":477,"line":547},[475,1351,569],{"class":485},[475,1353,572],{"class":481},[475,1355,498],{"class":485},[475,1357,589],{"class":504},[363,1359,1360,1361,1363],{},"The job never deletes anything: ",[367,1362,222],{}," previews by default, and the\ntemplate passes no flag that changes that. Keys with ambiguous evidence are\nreported as uncertain and are not part of the orphan set at all, so they never\nreach the report.",[376,1365,1366,1376],{},[379,1367,1368],{},[382,1369,1370,1372,1374],{},[385,1371,866],{},[385,1373,869],{},[385,1375,872],{},[398,1377,1378,1391,1410,1424],{},[382,1379,1380,1384,1388],{},[403,1381,1382],{},[367,1383,881],{},[403,1385,1386],{},[885,1387,887],{},[403,1389,1390],{},"Restrict the scan to one layer",[382,1392,1393,1398,1402],{},[403,1394,1395],{},[367,1396,1397],{},"I18N_FAIL_ON_ORPHANS",[403,1399,1400],{},[367,1401,962],{},[403,1403,1404,985,1406,1409],{},[367,1405,967],{},[367,1407,1408],{},"--fail-on-orphans",", so orphans trip a gate and the job exits 2",[382,1411,1412,1416,1420],{},[403,1413,1414],{},[367,1415,635],{},[403,1417,1418],{},[367,1419,1000],{},[403,1421,1003,1422],{},[367,1423,1006],{},[382,1425,1426,1430,1434],{},[403,1427,1428],{},[367,1429,1013],{},[403,1431,1432],{},[885,1433,1018],{},[403,1435,1021],{},[363,1437,1438,1439,1442,1443,1445,1446,1448],{},"Failure conditions: the scan itself failing (exit 1) fails the job red and dumps\nthe report to the log. Orphans found with ",[367,1440,1441],{},"I18N_FAIL_ON_ORPHANS: \"true\""," exit 2,\nwhich ",[367,1444,702],{}," surfaces as a yellow warning — set\n",[367,1447,706],{}," on your job to make orphans block the merge. Without the\nvariable the gate is not requested at all, so orphans are informational and the\njob stays green.",[363,1450,1451,1452,696,1455,1458],{},"Artifacts: ",[367,1453,1454],{},".i18n-reports\u002Forphans.json",[367,1456,1457],{},"gl-codequality.json",", 7 days.",[460,1460,1461],{"id":542},[367,1462,446],{},[363,1464,1465],{},"Scans for keys that are referenced in code but defined in no locale file of the\nconsuming app's layers. Those render as the raw key string in production.",[376,1467,1468,1478],{},[379,1469,1470],{},[382,1471,1472,1474,1476],{},[385,1473,866],{},[385,1475,869],{},[385,1477,872],{},[398,1479,1480,1494],{},[382,1481,1482,1486,1490],{},[403,1483,1484],{},[367,1485,635],{},[403,1487,1488],{},[367,1489,1000],{},[403,1491,1003,1492],{},[367,1493,1006],{},[382,1495,1496,1500,1504],{},[403,1497,1498],{},[367,1499,1013],{},[403,1501,1502],{},[885,1503,1018],{},[403,1505,1021],{},[363,1507,1508,1510,1511,1513,1514,1516],{},[367,1509,194],{}," has no opt-in flag, because a key that renders raw in production is a\ndefect rather than a threshold you decide to care about. Its gate is always\nevaluated: findings exit 2, and a scan that fell over exits 1. Both are visible\nin the pipeline — ",[367,1512,702],{}," keeps findings yellow and\nlets them reach the widget, while a broken scan goes red instead of passing for\na project that appears to have no findings. Set ",[367,1515,706],{}," on your\nextending job to make findings block the merge.",[363,1518,1451,1519,696,1522,1458],{},[367,1520,1521],{},".i18n-reports\u002Fcheck.json",[367,1523,1457],{},[460,1525,1527],{"id":1526},"the-code-quality-widget","The Code Quality Widget",[363,1529,1530,696,1532,1534,1535,1537,1538,1541,1542,1545],{},[367,1531,427],{},[367,1533,446],{}," write ",[367,1536,1457],{}," and declare it\nunder ",[367,1539,1540],{},"artifacts:reports:codequality",". GitLab renders those findings in the\nmerge request as a ",[600,1543,1544],{},"diff",": it compares the report from the merge request\npipeline against the report from the latest default-branch pipeline, and shows\nwhat the merge request introduced and what it resolved. The full list of\nfindings is always in the job artifact; the widget is only the delta.",[1104,1547,1548,1549,1552,1553,1556],{},"The diff needs both sides. A job that runs only on merge requests produces the\nmerge request's report and no baseline to compare it against, so ",[600,1550,1551],{},"the widget\nstays blank"," — which reads as a broken integration rather than a missing rule.\nEvery job you adopt needs a second rule that also runs it on the default branch,\nand that rule must carry no ",[367,1554,1555],{},"changes:"," filter: a filtered baseline is skipped\nwhenever the merge request that merged last happened not to touch those paths,\nleaving the next merge requests with nothing to diff against.",[465,1558,1560],{"className":467,"code":1559,"filename":469,"language":470,"meta":471,"style":471},"i18n-cleanup:\n  extends: .i18n-cleanup\n  rules:\n    - if: $CI_PIPELINE_SOURCE == \"merge_request_event\"\n      changes:\n        - components\u002F**\u002F*.vue\n        - i18n\u002Flocales\u002F*.json\n    # Baseline. No `changes:` filter, or the widget goes blank on the next MR.\n    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH\n",[367,1561,1562,1568,1576,1582,1592,1598,1604,1610,1616],{"__ignoreMap":471},[475,1563,1564,1566],{"class":477,"line":478},[475,1565,1282],{"class":481},[475,1567,486],{"class":485},[475,1569,1570,1572,1574],{"class":477,"line":489},[475,1571,550],{"class":481},[475,1573,498],{"class":485},[475,1575,1311],{"class":504},[475,1577,1578,1580],{"class":477,"line":511},[475,1579,561],{"class":481},[475,1581,486],{"class":485},[475,1583,1584,1586,1588,1590],{"class":477,"line":518},[475,1585,569],{"class":485},[475,1587,572],{"class":481},[475,1589,498],{"class":485},[475,1591,577],{"class":504},[475,1593,1594,1596],{"class":477,"line":526},[475,1595,826],{"class":481},[475,1597,486],{"class":485},[475,1599,1600,1602],{"class":477,"line":534},[475,1601,833],{"class":485},[475,1603,1340],{"class":504},[475,1605,1606,1608],{"class":477,"line":539},[475,1607,833],{"class":485},[475,1609,1347],{"class":504},[475,1611,1612],{"class":477,"line":547},[475,1613,1615],{"class":1614},"sHwdD","    # Baseline. No `changes:` filter, or the widget goes blank on the next MR.\n",[475,1617,1618,1620,1622,1624],{"class":477,"line":558},[475,1619,569],{"class":485},[475,1621,572],{"class":481},[475,1623,498],{"class":485},[475,1625,589],{"class":504},[363,1627,1628],{},"The report is written on every run, including runs with nothing to report — an\nempty array on the default branch is a valid baseline, and it is what makes the\nfirst genuine finding show up as introduced by the merge request that\nintroduced it.",[363,1630,1631,1632,1635,1636,1639],{},"Baselines also expire. The template sets ",[367,1633,1634],{},"expire_in: 7 days"," on the artifacts,\nand the widget reads the newest unexpired default-branch report. On a default\nbranch quieter than a week, the widget goes blank again until the next\ndefault-branch pipeline runs. Raise ",[367,1637,1638],{},"expire_in"," on your extending job if that\ndescribes your project.",[1117,1641,1643],{"id":1642},"report-widgets-on-translated-merge-requests","Report Widgets on Translated Merge Requests",[363,1645,1646,1647,1649],{},"There is one more way to end up with a blank widget, and it only happens on\nmerge requests where ",[367,1648,407],{}," pushed something.",[363,1651,1652],{},"GitLab compares reports at the merge request's current head, and a push made\nwith a CI token — the job token or a project access token alike — never triggers\na pipeline. The auto-commit therefore advances the head to a commit that has no\nreport, and the widget disappears for that merge request.",[363,1654,1655,1656,1658,1659,1661],{},"The template heals this when ",[367,1657,1028],{}," carries the ",[367,1660,1260],{}," scope: after a\nsuccessful push it creates a pipeline at the new head, which regenerates the\nreports. A failed trigger warns rather than failing the job, since the push\nalready succeeded.",[363,1663,1664,1665,1667,1668,1671,1672,1674,1675,1677],{},"Without an ",[367,1666,1260],{},"-scoped token, press ",[600,1669,1670],{},"Run pipeline"," on the merge request to\nget the widget back. The same reasoning is why ",[367,1673,1079],{},"\ndefaults to off: ",[367,1676,1091],{}," guarantees the new head has no pipeline.",[460,1679,1681],{"id":1680},"overriding-the-jobs","Overriding the Jobs",[363,1683,1684,1685,850,1688,1690,1691,696,1694,1697],{},"The jobs are ordinary GitLab job definitions, so ",[367,1686,1687],{},"image",[367,1689,716],{},",\n",[367,1692,1693],{},"tags",[367,1695,1696],{},"cache"," can all be overridden on the job that extends them. That is\nthe supported path for runners behind a private registry, self-hosted mirrors,\nor a package manager other than npm.",[465,1699,1701],{"className":467,"code":1700,"filename":469,"language":470,"meta":471,"style":471},"i18n-check:\n  extends: .i18n-check\n  image: registry.example.com\u002Fmirror\u002Fnode:22-alpine\n  tags:\n    - internal-runners\n  cache:\n    key: i18n-npm\n    paths:\n      - .npm\u002F\n  before_script:\n    - npm config set registry https:\u002F\u002Fregistry.example.com\u002Fnpm\u002F\n    - npm install -g \"the-i18n-cli@${I18N_CLI_VERSION}\" || exit 1\n  rules:\n    - if: $CI_PIPELINE_SOURCE == \"merge_request_event\"\n    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH\n",[367,1702,1703,1709,1717,1727,1734,1741,1748,1758,1765,1773,1780,1787,1794,1801,1812],{"__ignoreMap":471},[475,1704,1705,1707],{"class":477,"line":478},[475,1706,542],{"class":481},[475,1708,486],{"class":485},[475,1710,1711,1713,1715],{"class":477,"line":489},[475,1712,550],{"class":481},[475,1714,498],{"class":485},[475,1716,555],{"class":504},[475,1718,1719,1722,1724],{"class":477,"line":511},[475,1720,1721],{"class":481},"  image",[475,1723,498],{"class":485},[475,1725,1726],{"class":504}," registry.example.com\u002Fmirror\u002Fnode:22-alpine\n",[475,1728,1729,1732],{"class":477,"line":518},[475,1730,1731],{"class":481},"  tags",[475,1733,486],{"class":485},[475,1735,1736,1738],{"class":477,"line":526},[475,1737,569],{"class":485},[475,1739,1740],{"class":504}," internal-runners\n",[475,1742,1743,1746],{"class":477,"line":534},[475,1744,1745],{"class":481},"  cache",[475,1747,486],{"class":485},[475,1749,1750,1753,1755],{"class":477,"line":539},[475,1751,1752],{"class":481},"    key",[475,1754,498],{"class":485},[475,1756,1757],{"class":504}," i18n-npm\n",[475,1759,1760,1763],{"class":477,"line":547},[475,1761,1762],{"class":481},"    paths",[475,1764,486],{"class":485},[475,1766,1767,1770],{"class":477,"line":558},[475,1768,1769],{"class":485},"      -",[475,1771,1772],{"class":504}," .npm\u002F\n",[475,1774,1775,1778],{"class":477,"line":566},[475,1776,1777],{"class":481},"  before_script",[475,1779,486],{"class":485},[475,1781,1782,1784],{"class":477,"line":580},[475,1783,569],{"class":485},[475,1785,1786],{"class":504}," npm config set registry https:\u002F\u002Fregistry.example.com\u002Fnpm\u002F\n",[475,1788,1789,1791],{"class":477,"line":839},[475,1790,569],{"class":485},[475,1792,1793],{"class":504}," npm install -g \"the-i18n-cli@${I18N_CLI_VERSION}\" || exit 1\n",[475,1795,1797,1799],{"class":477,"line":1796},13,[475,1798,561],{"class":481},[475,1800,486],{"class":485},[475,1802,1804,1806,1808,1810],{"class":477,"line":1803},14,[475,1805,569],{"class":485},[475,1807,572],{"class":481},[475,1809,498],{"class":485},[475,1811,577],{"class":504},[475,1813,1815,1817,1819,1821],{"class":477,"line":1814},15,[475,1816,569],{"class":485},[475,1818,572],{"class":481},[475,1820,498],{"class":485},[475,1822,589],{"class":504},[363,1824,1825,1826,1828],{},"What a replacement ",[367,1827,716],{}," has to keep doing:",[594,1830,1831,1847,1863,1893,1903],{},[597,1832,1833,1842,1843,1846],{},[600,1834,1835,1836,1838,1839,1266],{},"Put ",[367,1837,1006],{}," on the ",[367,1840,1841],{},"PATH"," All three ",[367,1844,1845],{},"script"," blocks call the\nbinary by name.",[597,1848,1849,1854,1855,696,1857,1859,1860,1862],{},[600,1850,1851,1852],{},"End the setup lines with ",[367,1853,720],{}," in ",[367,1856,427],{},[367,1858,446],{},",\nfor the ",[367,1861,712],{}," reason described above.",[597,1864,1865,1871,1872,696,1874,1876,1877,1879,1880,1883,1884,1887,1888,1890,1891,1266],{},[600,1866,1867,1868],{},"Install ",[367,1869,1870],{},"jq"," for ",[367,1873,407],{},[367,1875,427],{},". Both parse the\nresult for their log output. The stock ",[367,1878,716],{}," installs it with ",[367,1881,1882],{},"apk",",\nguarded by ",[367,1885,1886],{},"command -v apk",", so an image without ",[367,1889,1882],{}," gets no error and no\n",[367,1892,1870],{},[597,1894,1895,1871,1900,1902],{},[600,1896,1867,1897],{},[367,1898,1899],{},"git",[367,1901,407],{},", which commits and pushes.",[597,1904,1905,1871,1908,1910,1911,1690,1914,850,1917,1920,1921,1923,1924,1926,1927,1930],{},[600,1906,1907],{},"Install the provider SDK",[367,1909,407],{},". The SDKs — ",[367,1912,1913],{},"openai",[367,1915,1916],{},"@anthropic-ai\u002Fsdk",[367,1918,1919],{},"@google\u002Fgenai"," — are optional peer dependencies of the\nCLI, so the stock script installs the one matching ",[367,1922,849],{}," alongside\nit. If you only need extra packages rather than a different install\nprocedure, ",[367,1925,1013],{}," appends to the stock ",[367,1928,1929],{},"npm install -g","\nline and needs no override at all.",[363,1932,1933,1934,1936,1937,1940,1941,1943,1944,1947,1948,1951,1952,1955,1956,1959,1960,1963,1964,1967],{},"Replacing ",[367,1935,1687],{}," is supported and does not require a Node image built like the\ndefault ",[367,1938,1939],{},"node:22-alpine",", with one constraint the templates already respect: all\nthree ",[367,1942,1845],{}," blocks are POSIX ",[367,1945,1946],{},"sh",". No ",[367,1949,1950],{},"pipefail",", no arrays, no\n",[367,1953,1954],{},"PIPESTATUS",". Busybox ",[367,1957,1958],{},"ash"," in the default image tolerates ",[367,1961,1962],{},"set -o pipefail",",\nbut ",[367,1965,1966],{},"dash"," rejects it outright and aborts the job before running a line, so\nstaying POSIX is what keeps the jobs working under whichever shell your image\nprovides.",[460,1969,1971],{"id":1970},"next","Next",[594,1973,1974,1979,1985],{},[597,1975,1976,1978],{},[411,1977,160],{"href":161}," for the equivalent translate\nautomation on GitHub, which opens a pull request instead of pushing.",[597,1980,1981,1984],{},[411,1982,1983],{"href":646},"Exit codes and CI gates"," for the gate each command\nevaluates.",[597,1986,1987,1989,1990,1992],{},[411,1988,59],{"href":60}," for what ",[367,1991,881],{}," selects and where the names\ncome from.",[1994,1995,1996],"style",{},"html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}",{"title":471,"searchDepth":511,"depth":511,"links":1998},[1999,2000,2001,2004,2005,2006,2007,2010,2011],{"id":462,"depth":489,"text":463},{"id":639,"depth":489,"text":640},{"id":727,"depth":489,"text":407,"children":2002},[2003],{"id":1119,"depth":511,"text":1120},{"id":1226,"depth":489,"text":1227},{"id":1282,"depth":489,"text":427},{"id":542,"depth":489,"text":446},{"id":1526,"depth":489,"text":1527,"children":2008},[2009],{"id":1642,"depth":511,"text":1643},{"id":1680,"depth":489,"text":1681},{"id":1970,"depth":489,"text":1971},"md",null,{},{"description":2016,"title":164},"The three reusable jobs, the default-branch rule the Code Quality widget needs, the two ways to let the translate job push, and the override points for private registries.","j0rNDNbaGDRJptG4ar7uVSQmQ0zrPhSkHqjR7oo_Dqg",[2019,2020],{"title":160,"path":161,"stem":162,"description":471,"children":-1},{"title":173,"path":174,"stem":175,"description":471,"children":-1},1787604652420]