#
tokens: 49238/50000 16/626 files (page 9/20)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 9 of 20. Use http://codebase.md/lingodotdev/lingo.dev?lines=true&page={x} to view the full context.

# Directory Structure

```
├── .changeset
│   ├── config.json
│   └── README.md
├── .claude
│   ├── agents
│   │   └── code-architect-reviewer.md
│   └── commands
│       ├── analyze-bucket-type.md
│       └── create-bucket-docs.md
├── .editorconfig
├── .github
│   ├── dependabot.yml
│   └── workflows
│       ├── docker.yml
│       ├── lingodotdev.yml
│       ├── pr-check.yml
│       ├── pr-lint.yml
│       └── release.yml
├── .gitignore
├── .husky
│   └── commit-msg
├── .npmrc
├── .prettierignore
├── .prettierrc
├── .vscode
│   ├── extensions.json
│   ├── launch.json
│   └── settings.json
├── action.yml
├── CLAUDE.md
├── CODE_OF_CONDUCT.md
├── commitlint.config.js
├── composer.json
├── content
│   ├── banner.compiler.png
│   ├── banner.dark.png
│   └── banner.launch.png
├── CONTRIBUTING.md
├── DEBUGGING.md
├── demo
│   ├── adonisjs
│   │   ├── .editorconfig
│   │   ├── .env.example
│   │   ├── .gitignore
│   │   ├── ace.js
│   │   ├── adonisrc.ts
│   │   ├── app
│   │   │   ├── exceptions
│   │   │   │   └── handler.ts
│   │   │   └── middleware
│   │   │       └── container_bindings_middleware.ts
│   │   ├── bin
│   │   │   ├── console.ts
│   │   │   ├── server.ts
│   │   │   └── test.ts
│   │   ├── CHANGELOG.md
│   │   ├── config
│   │   │   ├── app.ts
│   │   │   ├── bodyparser.ts
│   │   │   ├── cors.ts
│   │   │   ├── hash.ts
│   │   │   ├── inertia.ts
│   │   │   ├── logger.ts
│   │   │   ├── session.ts
│   │   │   ├── shield.ts
│   │   │   ├── static.ts
│   │   │   └── vite.ts
│   │   ├── eslint.config.js
│   │   ├── inertia
│   │   │   ├── app
│   │   │   │   ├── app.tsx
│   │   │   │   └── ssr.tsx
│   │   │   ├── css
│   │   │   │   └── app.css
│   │   │   ├── lingo
│   │   │   │   ├── dictionary.js
│   │   │   │   └── meta.json
│   │   │   ├── pages
│   │   │   │   ├── errors
│   │   │   │   │   ├── not_found.tsx
│   │   │   │   │   └── server_error.tsx
│   │   │   │   └── home.tsx
│   │   │   └── tsconfig.json
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── resources
│   │   │   └── views
│   │   │       └── inertia_layout.edge
│   │   ├── start
│   │   │   ├── env.ts
│   │   │   ├── kernel.ts
│   │   │   └── routes.ts
│   │   ├── tests
│   │   │   └── bootstrap.ts
│   │   ├── tsconfig.json
│   │   └── vite.config.ts
│   ├── next-app
│   │   ├── .gitignore
│   │   ├── CHANGELOG.md
│   │   ├── eslint.config.mjs
│   │   ├── next.config.ts
│   │   ├── package.json
│   │   ├── postcss.config.mjs
│   │   ├── public
│   │   │   ├── file.svg
│   │   │   ├── globe.svg
│   │   │   ├── next.svg
│   │   │   ├── vercel.svg
│   │   │   └── window.svg
│   │   ├── README.md
│   │   ├── src
│   │   │   ├── app
│   │   │   │   ├── client-component.tsx
│   │   │   │   ├── favicon.ico
│   │   │   │   ├── globals.css
│   │   │   │   ├── layout.tsx
│   │   │   │   ├── lingo-dot-dev.tsx
│   │   │   │   ├── page.tsx
│   │   │   │   └── test
│   │   │   │       └── page.tsx
│   │   │   ├── components
│   │   │   │   ├── hero-actions.tsx
│   │   │   │   ├── hero-subtitle.tsx
│   │   │   │   ├── hero-title.tsx
│   │   │   │   └── index.ts
│   │   │   └── lingo
│   │   │       ├── dictionary.js
│   │   │       └── meta.json
│   │   └── tsconfig.json
│   ├── react-router-app
│   │   ├── .dockerignore
│   │   ├── .gitignore
│   │   ├── app
│   │   │   ├── app.css
│   │   │   ├── lingo
│   │   │   │   ├── dictionary.js
│   │   │   │   └── meta.json
│   │   │   ├── root.tsx
│   │   │   ├── routes
│   │   │   │   ├── home.tsx
│   │   │   │   └── test.tsx
│   │   │   ├── routes.ts
│   │   │   └── welcome
│   │   │       ├── lingo-dot-dev.tsx
│   │   │       ├── logo-dark.svg
│   │   │       ├── logo-light.svg
│   │   │       └── welcome.tsx
│   │   ├── Dockerfile
│   │   ├── package.json
│   │   ├── public
│   │   │   └── favicon.ico
│   │   ├── react-router.config.ts
│   │   ├── README.md
│   │   ├── tsconfig.json
│   │   └── vite.config.ts
│   └── vite-project
│       ├── .gitignore
│       ├── CHANGELOG.md
│       ├── eslint.config.js
│       ├── index.html
│       ├── package.json
│       ├── public
│       │   └── vite.svg
│       ├── README.md
│       ├── src
│       │   ├── App.css
│       │   ├── App.tsx
│       │   ├── assets
│       │   │   └── react.svg
│       │   ├── components
│       │   │   └── test.tsx
│       │   ├── index.css
│       │   ├── lingo
│       │   │   ├── dictionary.js
│       │   │   └── meta.json
│       │   ├── lingo-dot-dev.tsx
│       │   ├── main.tsx
│       │   └── vite-env.d.ts
│       ├── tsconfig.app.json
│       ├── tsconfig.json
│       ├── tsconfig.node.json
│       └── vite.config.ts
├── Dockerfile
├── i18n.json
├── i18n.lock
├── integrations
│   └── directus
│       ├── .gitignore
│       ├── CHANGELOG.md
│       ├── docker-compose.yml
│       ├── Dockerfile
│       ├── package.json
│       ├── README.md
│       ├── src
│       │   ├── api.ts
│       │   ├── app.ts
│       │   └── index.spec.ts
│       ├── tsconfig.json
│       ├── tsconfig.test.json
│       └── tsup.config.ts
├── ISSUE_TEMPLATE.md
├── legacy
│   ├── cli
│   │   ├── bin
│   │   │   └── cli.mjs
│   │   ├── CHANGELOG.md
│   │   ├── package.json
│   │   └── readme.md
│   └── sdk
│       ├── CHANGELOG.md
│       ├── index.d.ts
│       ├── index.js
│       ├── package.json
│       └── README.md
├── LICENSE.md
├── mcp.md
├── package.json
├── packages
│   ├── cli
│   │   ├── assets
│   │   │   ├── failure.mp3
│   │   │   └── success.mp3
│   │   ├── bin
│   │   │   └── cli.mjs
│   │   ├── CHANGELOG.md
│   │   ├── demo
│   │   │   ├── android
│   │   │   │   ├── en
│   │   │   │   │   └── example.xml
│   │   │   │   ├── es
│   │   │   │   │   └── example.xml
│   │   │   │   ├── i18n.json
│   │   │   │   └── i18n.lock
│   │   │   ├── csv
│   │   │   │   ├── example.csv
│   │   │   │   ├── i18n.json
│   │   │   │   └── i18n.lock
│   │   │   ├── demo.spec.ts
│   │   │   ├── ejs
│   │   │   │   ├── en
│   │   │   │   │   └── example.ejs
│   │   │   │   ├── es
│   │   │   │   │   └── example.ejs
│   │   │   │   ├── i18n.json
│   │   │   │   └── i18n.lock
│   │   │   ├── flutter
│   │   │   │   ├── en
│   │   │   │   │   └── example.arb
│   │   │   │   ├── es
│   │   │   │   │   └── example.arb
│   │   │   │   ├── i18n.json
│   │   │   │   └── i18n.lock
│   │   │   ├── html
│   │   │   │   ├── en
│   │   │   │   │   └── example.html
│   │   │   │   ├── es
│   │   │   │   │   └── example.html
│   │   │   │   ├── i18n.json
│   │   │   │   └── i18n.lock
│   │   │   ├── json
│   │   │   │   ├── en
│   │   │   │   │   └── example.json
│   │   │   │   ├── es
│   │   │   │   │   └── example.json
│   │   │   │   ├── i18n.json
│   │   │   │   └── i18n.lock
│   │   │   ├── json-dictionary
│   │   │   │   ├── example.json
│   │   │   │   ├── i18n.json
│   │   │   │   └── i18n.lock
│   │   │   ├── json5
│   │   │   │   ├── en
│   │   │   │   │   └── example.json5
│   │   │   │   ├── es
│   │   │   │   │   └── example.json5
│   │   │   │   ├── i18n.json
│   │   │   │   └── i18n.lock
│   │   │   ├── jsonc
│   │   │   │   ├── en
│   │   │   │   │   └── example.jsonc
│   │   │   │   ├── es
│   │   │   │   │   └── example.jsonc
│   │   │   │   ├── i18n.json
│   │   │   │   ├── i18n.lock
│   │   │   │   └── ru
│   │   │   │       └── example.jsonc
│   │   │   ├── markdoc
│   │   │   │   ├── en
│   │   │   │   │   └── example.markdoc
│   │   │   │   ├── es
│   │   │   │   │   └── example.markdoc
│   │   │   │   ├── i18n.json
│   │   │   │   └── i18n.lock
│   │   │   ├── markdown
│   │   │   │   ├── en
│   │   │   │   │   └── example.md
│   │   │   │   ├── es
│   │   │   │   │   └── example.md
│   │   │   │   ├── i18n.json
│   │   │   │   └── i18n.lock
│   │   │   ├── mdx
│   │   │   │   ├── en
│   │   │   │   │   └── example.mdx
│   │   │   │   ├── es
│   │   │   │   │   └── example.mdx
│   │   │   │   ├── i18n.json
│   │   │   │   └── i18n.lock
│   │   │   ├── php
│   │   │   │   ├── en
│   │   │   │   │   └── example.php
│   │   │   │   ├── es
│   │   │   │   │   └── example.php
│   │   │   │   ├── i18n.json
│   │   │   │   └── i18n.lock
│   │   │   ├── po
│   │   │   │   ├── en
│   │   │   │   │   └── example.po
│   │   │   │   ├── es
│   │   │   │   │   └── example.po
│   │   │   │   ├── i18n.json
│   │   │   │   └── i18n.lock
│   │   │   ├── properties
│   │   │   │   ├── en
│   │   │   │   │   └── example.properties
│   │   │   │   ├── es
│   │   │   │   │   └── example.properties
│   │   │   │   ├── i18n.json
│   │   │   │   └── i18n.lock
│   │   │   ├── run_i18n.sh
│   │   │   ├── srt
│   │   │   │   ├── en
│   │   │   │   │   └── example.srt
│   │   │   │   ├── es
│   │   │   │   │   └── example.srt
│   │   │   │   ├── i18n.json
│   │   │   │   └── i18n.lock
│   │   │   ├── txt
│   │   │   │   ├── en
│   │   │   │   │   └── example.txt
│   │   │   │   ├── es
│   │   │   │   │   └── example.txt
│   │   │   │   ├── i18n.json
│   │   │   │   └── i18n.lock
│   │   │   ├── typescript
│   │   │   │   ├── en
│   │   │   │   │   └── example.ts
│   │   │   │   ├── es
│   │   │   │   │   └── example.ts
│   │   │   │   ├── i18n.json
│   │   │   │   └── i18n.lock
│   │   │   ├── vtt
│   │   │   │   ├── en
│   │   │   │   │   └── example.vtt
│   │   │   │   ├── es
│   │   │   │   │   └── example.vtt
│   │   │   │   ├── i18n.json
│   │   │   │   └── i18n.lock
│   │   │   ├── vue-json
│   │   │   │   ├── example.vue
│   │   │   │   ├── i18n.json
│   │   │   │   └── i18n.lock
│   │   │   ├── xcode-strings
│   │   │   │   ├── en
│   │   │   │   │   └── example.strings
│   │   │   │   ├── es
│   │   │   │   │   └── example.strings
│   │   │   │   ├── i18n.json
│   │   │   │   └── i18n.lock
│   │   │   ├── xcode-stringsdict
│   │   │   │   ├── en
│   │   │   │   │   └── example.stringsdict
│   │   │   │   ├── es
│   │   │   │   │   └── example.stringsdict
│   │   │   │   ├── i18n.json
│   │   │   │   └── i18n.lock
│   │   │   ├── xcode-xcstrings
│   │   │   │   ├── example.xcstrings
│   │   │   │   ├── i18n.json
│   │   │   │   └── i18n.lock
│   │   │   ├── xcode-xcstrings-v2
│   │   │   │   ├── complex-example.xcstrings
│   │   │   │   ├── example.xcstrings
│   │   │   │   ├── i18n.json
│   │   │   │   └── i18n.lock
│   │   │   ├── xliff
│   │   │   │   ├── en
│   │   │   │   │   ├── example-v1.2.xliff
│   │   │   │   │   └── example-v2.xliff
│   │   │   │   ├── es
│   │   │   │   │   ├── example-v1.2.xliff
│   │   │   │   │   ├── example-v2.xliff
│   │   │   │   │   └── example.xliff
│   │   │   │   ├── i18n.json
│   │   │   │   └── i18n.lock
│   │   │   ├── xml
│   │   │   │   ├── en
│   │   │   │   │   └── example.xml
│   │   │   │   ├── es
│   │   │   │   │   └── example.xml
│   │   │   │   ├── i18n.json
│   │   │   │   └── i18n.lock
│   │   │   ├── yaml
│   │   │   │   ├── en
│   │   │   │   │   └── example.yml
│   │   │   │   ├── es
│   │   │   │   │   └── example.yml
│   │   │   │   ├── i18n.json
│   │   │   │   └── i18n.lock
│   │   │   └── yaml-root-key
│   │   │       ├── en
│   │   │       │   └── example.yml
│   │   │       ├── es
│   │   │       │   └── example.yml
│   │   │       ├── i18n.json
│   │   │       └── i18n.lock
│   │   ├── i18n.json
│   │   ├── i18n.lock
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── src
│   │   │   ├── cli
│   │   │   │   ├── cmd
│   │   │   │   │   ├── auth.ts
│   │   │   │   │   ├── ci
│   │   │   │   │   │   ├── flows
│   │   │   │   │   │   │   ├── _base.ts
│   │   │   │   │   │   │   ├── in-branch.ts
│   │   │   │   │   │   │   └── pull-request.ts
│   │   │   │   │   │   ├── index.ts
│   │   │   │   │   │   └── platforms
│   │   │   │   │   │       ├── _base.ts
│   │   │   │   │   │       ├── bitbucket.ts
│   │   │   │   │   │       ├── github.ts
│   │   │   │   │   │       ├── gitlab.ts
│   │   │   │   │   │       └── index.ts
│   │   │   │   │   ├── cleanup.ts
│   │   │   │   │   ├── config
│   │   │   │   │   │   ├── get.ts
│   │   │   │   │   │   ├── index.ts
│   │   │   │   │   │   ├── set.ts
│   │   │   │   │   │   └── unset.ts
│   │   │   │   │   ├── i18n.ts
│   │   │   │   │   ├── init.ts
│   │   │   │   │   ├── lockfile.ts
│   │   │   │   │   ├── login.ts
│   │   │   │   │   ├── logout.ts
│   │   │   │   │   ├── may-the-fourth.ts
│   │   │   │   │   ├── mcp.ts
│   │   │   │   │   ├── purge.ts
│   │   │   │   │   ├── run
│   │   │   │   │   │   ├── _const.ts
│   │   │   │   │   │   ├── _types.ts
│   │   │   │   │   │   ├── _utils.ts
│   │   │   │   │   │   ├── execute.spec.ts
│   │   │   │   │   │   ├── execute.ts
│   │   │   │   │   │   ├── frozen.ts
│   │   │   │   │   │   ├── index.ts
│   │   │   │   │   │   ├── plan.ts
│   │   │   │   │   │   ├── setup.ts
│   │   │   │   │   │   └── watch.ts
│   │   │   │   │   ├── show
│   │   │   │   │   │   ├── _shared-key-command.ts
│   │   │   │   │   │   ├── config.ts
│   │   │   │   │   │   ├── files.ts
│   │   │   │   │   │   ├── ignored-keys.ts
│   │   │   │   │   │   ├── index.ts
│   │   │   │   │   │   ├── locale.ts
│   │   │   │   │   │   └── locked-keys.ts
│   │   │   │   │   └── status.ts
│   │   │   │   ├── constants.ts
│   │   │   │   ├── index.spec.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── loaders
│   │   │   │   │   ├── _types.ts
│   │   │   │   │   ├── _utils.ts
│   │   │   │   │   ├── android.spec.ts
│   │   │   │   │   ├── android.ts
│   │   │   │   │   ├── csv.spec.ts
│   │   │   │   │   ├── csv.ts
│   │   │   │   │   ├── dato
│   │   │   │   │   │   ├── _base.ts
│   │   │   │   │   │   ├── _utils.ts
│   │   │   │   │   │   ├── api.ts
│   │   │   │   │   │   ├── extract.ts
│   │   │   │   │   │   ├── filter.ts
│   │   │   │   │   │   └── index.ts
│   │   │   │   │   ├── ejs.spec.ts
│   │   │   │   │   ├── ejs.ts
│   │   │   │   │   ├── ensure-key-order.spec.ts
│   │   │   │   │   ├── ensure-key-order.ts
│   │   │   │   │   ├── flat.spec.ts
│   │   │   │   │   ├── flat.ts
│   │   │   │   │   ├── flutter.spec.ts
│   │   │   │   │   ├── flutter.ts
│   │   │   │   │   ├── formatters
│   │   │   │   │   │   ├── _base.ts
│   │   │   │   │   │   ├── biome.ts
│   │   │   │   │   │   ├── index.ts
│   │   │   │   │   │   └── prettier.ts
│   │   │   │   │   ├── html.ts
│   │   │   │   │   ├── icu-safety.spec.ts
│   │   │   │   │   ├── ignored-keys-buckets.spec.ts
│   │   │   │   │   ├── ignored-keys.spec.ts
│   │   │   │   │   ├── ignored-keys.ts
│   │   │   │   │   ├── index.spec.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── inject-locale.spec.ts
│   │   │   │   │   ├── inject-locale.ts
│   │   │   │   │   ├── json-dictionary.spec.ts
│   │   │   │   │   ├── json-dictionary.ts
│   │   │   │   │   ├── json-sorting.test.ts
│   │   │   │   │   ├── json-sorting.ts
│   │   │   │   │   ├── json.ts
│   │   │   │   │   ├── json5.spec.ts
│   │   │   │   │   ├── json5.ts
│   │   │   │   │   ├── jsonc.spec.ts
│   │   │   │   │   ├── jsonc.ts
│   │   │   │   │   ├── locked-keys.spec.ts
│   │   │   │   │   ├── locked-keys.ts
│   │   │   │   │   ├── locked-patterns.spec.ts
│   │   │   │   │   ├── locked-patterns.ts
│   │   │   │   │   ├── markdoc.spec.ts
│   │   │   │   │   ├── markdoc.ts
│   │   │   │   │   ├── markdown.ts
│   │   │   │   │   ├── mdx.spec.ts
│   │   │   │   │   ├── mdx.ts
│   │   │   │   │   ├── mdx2
│   │   │   │   │   │   ├── _types.ts
│   │   │   │   │   │   ├── _utils.ts
│   │   │   │   │   │   ├── code-placeholder.spec.ts
│   │   │   │   │   │   ├── code-placeholder.ts
│   │   │   │   │   │   ├── frontmatter-split.spec.ts
│   │   │   │   │   │   ├── frontmatter-split.ts
│   │   │   │   │   │   ├── localizable-document.spec.ts
│   │   │   │   │   │   ├── localizable-document.ts
│   │   │   │   │   │   ├── section-split.spec.ts
│   │   │   │   │   │   ├── section-split.ts
│   │   │   │   │   │   └── sections-split-2.ts
│   │   │   │   │   ├── passthrough.ts
│   │   │   │   │   ├── php.ts
│   │   │   │   │   ├── plutil-json-loader.ts
│   │   │   │   │   ├── po
│   │   │   │   │   │   ├── _types.ts
│   │   │   │   │   │   ├── index.spec.ts
│   │   │   │   │   │   └── index.ts
│   │   │   │   │   ├── properties.ts
│   │   │   │   │   ├── root-key.ts
│   │   │   │   │   ├── srt.ts
│   │   │   │   │   ├── sync.ts
│   │   │   │   │   ├── text-file.ts
│   │   │   │   │   ├── txt.ts
│   │   │   │   │   ├── typescript
│   │   │   │   │   │   ├── cjs-interop.ts
│   │   │   │   │   │   ├── index.spec.ts
│   │   │   │   │   │   └── index.ts
│   │   │   │   │   ├── unlocalizable.spec.ts
│   │   │   │   │   ├── unlocalizable.ts
│   │   │   │   │   ├── variable
│   │   │   │   │   │   ├── index.spec.ts
│   │   │   │   │   │   └── index.ts
│   │   │   │   │   ├── vtt.ts
│   │   │   │   │   ├── vue-json.ts
│   │   │   │   │   ├── xcode-strings
│   │   │   │   │   │   ├── escape.ts
│   │   │   │   │   │   ├── parser.ts
│   │   │   │   │   │   ├── tokenizer.ts
│   │   │   │   │   │   └── types.ts
│   │   │   │   │   ├── xcode-strings.spec.ts
│   │   │   │   │   ├── xcode-strings.ts
│   │   │   │   │   ├── xcode-stringsdict.ts
│   │   │   │   │   ├── xcode-xcstrings-icu.spec.ts
│   │   │   │   │   ├── xcode-xcstrings-icu.ts
│   │   │   │   │   ├── xcode-xcstrings-lock-compatibility.spec.ts
│   │   │   │   │   ├── xcode-xcstrings-v2-loader.ts
│   │   │   │   │   ├── xcode-xcstrings.spec.ts
│   │   │   │   │   ├── xcode-xcstrings.ts
│   │   │   │   │   ├── xliff.spec.ts
│   │   │   │   │   ├── xliff.ts
│   │   │   │   │   ├── xml.ts
│   │   │   │   │   └── yaml.ts
│   │   │   │   ├── localizer
│   │   │   │   │   ├── _types.ts
│   │   │   │   │   ├── explicit.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── lingodotdev.ts
│   │   │   │   ├── processor
│   │   │   │   │   ├── _base.ts
│   │   │   │   │   ├── basic.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── lingo.ts
│   │   │   │   └── utils
│   │   │   │       ├── auth.ts
│   │   │   │       ├── buckets.spec.ts
│   │   │   │       ├── buckets.ts
│   │   │   │       ├── cache.ts
│   │   │   │       ├── cloudflare-status.ts
│   │   │   │       ├── config.ts
│   │   │   │       ├── delta.spec.ts
│   │   │   │       ├── delta.ts
│   │   │   │       ├── ensure-patterns.ts
│   │   │   │       ├── errors.ts
│   │   │   │       ├── exec.spec.ts
│   │   │   │       ├── exec.ts
│   │   │   │       ├── exit-gracefully.spec.ts
│   │   │   │       ├── exit-gracefully.ts
│   │   │   │       ├── exp-backoff.ts
│   │   │   │       ├── find-locale-paths.spec.ts
│   │   │   │       ├── find-locale-paths.ts
│   │   │   │       ├── fs.ts
│   │   │   │       ├── init-ci-cd.ts
│   │   │   │       ├── key-matching.spec.ts
│   │   │   │       ├── key-matching.ts
│   │   │   │       ├── lockfile.ts
│   │   │   │       ├── md5.ts
│   │   │   │       ├── observability.ts
│   │   │   │       ├── plutil-formatter.spec.ts
│   │   │   │       ├── plutil-formatter.ts
│   │   │   │       ├── settings.ts
│   │   │   │       ├── ui.ts
│   │   │   │       └── update-gitignore.ts
│   │   │   ├── compiler
│   │   │   │   └── index.ts
│   │   │   ├── locale-codes
│   │   │   │   └── index.ts
│   │   │   ├── react
│   │   │   │   ├── client.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── react-router.ts
│   │   │   │   └── rsc.ts
│   │   │   ├── sdk
│   │   │   │   └── index.ts
│   │   │   └── spec
│   │   │       └── index.ts
│   │   ├── tests
│   │   │   └── mock-storage.ts
│   │   ├── troubleshooting.md
│   │   ├── tsconfig.json
│   │   ├── tsconfig.test.json
│   │   ├── tsup.config.ts
│   │   ├── types
│   │   │   ├── vtt.d.ts
│   │   │   └── xliff.d.ts
│   │   ├── vitest.config.ts
│   │   └── WATCH_MODE.md
│   ├── compiler
│   │   ├── CHANGELOG.md
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── src
│   │   │   ├── _base.ts
│   │   │   ├── _const.ts
│   │   │   ├── _loader-utils.spec.ts
│   │   │   ├── _loader-utils.ts
│   │   │   ├── _utils.spec.ts
│   │   │   ├── _utils.ts
│   │   │   ├── client-dictionary-loader.ts
│   │   │   ├── i18n-directive.spec.ts
│   │   │   ├── i18n-directive.ts
│   │   │   ├── index.spec.ts
│   │   │   ├── index.ts
│   │   │   ├── jsx-attribute-flag.spec.ts
│   │   │   ├── jsx-attribute-flag.ts
│   │   │   ├── jsx-attribute-scope-inject.spec.ts
│   │   │   ├── jsx-attribute-scope-inject.ts
│   │   │   ├── jsx-attribute-scopes-export.spec.ts
│   │   │   ├── jsx-attribute-scopes-export.ts
│   │   │   ├── jsx-attribute.spec.ts
│   │   │   ├── jsx-attribute.ts
│   │   │   ├── jsx-fragment.spec.ts
│   │   │   ├── jsx-fragment.ts
│   │   │   ├── jsx-html-lang.spec.ts
│   │   │   ├── jsx-html-lang.ts
│   │   │   ├── jsx-provider.spec.ts
│   │   │   ├── jsx-provider.ts
│   │   │   ├── jsx-remove-attributes.spec.ts
│   │   │   ├── jsx-remove-attributes.ts
│   │   │   ├── jsx-root-flag.spec.ts
│   │   │   ├── jsx-root-flag.ts
│   │   │   ├── jsx-scope-flag.spec.ts
│   │   │   ├── jsx-scope-flag.ts
│   │   │   ├── jsx-scope-inject.spec.ts
│   │   │   ├── jsx-scope-inject.ts
│   │   │   ├── jsx-scopes-export.spec.ts
│   │   │   ├── jsx-scopes-export.ts
│   │   │   ├── lib
│   │   │   │   └── lcp
│   │   │   │       ├── api
│   │   │   │       │   ├── index.ts
│   │   │   │       │   ├── prompt.spec.ts
│   │   │   │       │   ├── prompt.ts
│   │   │   │       │   ├── provider-details.spec.ts
│   │   │   │       │   ├── provider-details.ts
│   │   │   │       │   ├── shots.ts
│   │   │   │       │   ├── xml2obj.spec.ts
│   │   │   │       │   └── xml2obj.ts
│   │   │   │       ├── api.spec.ts
│   │   │   │       ├── cache.spec.ts
│   │   │   │       ├── cache.ts
│   │   │   │       ├── index.spec.ts
│   │   │   │       ├── index.ts
│   │   │   │       ├── schema.ts
│   │   │   │       ├── server.spec.ts
│   │   │   │       └── server.ts
│   │   │   ├── lingo-turbopack-loader.ts
│   │   │   ├── react-router-dictionary-loader.ts
│   │   │   ├── rsc-dictionary-loader.ts
│   │   │   └── utils
│   │   │       ├── ast-key.spec.ts
│   │   │       ├── ast-key.ts
│   │   │       ├── create-locale-import-map.spec.ts
│   │   │       ├── create-locale-import-map.ts
│   │   │       ├── env.spec.ts
│   │   │       ├── env.ts
│   │   │       ├── hash.spec.ts
│   │   │       ├── hash.ts
│   │   │       ├── index.spec.ts
│   │   │       ├── index.ts
│   │   │       ├── invokations.spec.ts
│   │   │       ├── invokations.ts
│   │   │       ├── jsx-attribute-scope.ts
│   │   │       ├── jsx-attribute.spec.ts
│   │   │       ├── jsx-attribute.ts
│   │   │       ├── jsx-content-whitespace.spec.ts
│   │   │       ├── jsx-content.spec.ts
│   │   │       ├── jsx-content.ts
│   │   │       ├── jsx-element.spec.ts
│   │   │       ├── jsx-element.ts
│   │   │       ├── jsx-expressions.test.ts
│   │   │       ├── jsx-expressions.ts
│   │   │       ├── jsx-functions.spec.ts
│   │   │       ├── jsx-functions.ts
│   │   │       ├── jsx-scope.spec.ts
│   │   │       ├── jsx-scope.ts
│   │   │       ├── jsx-variables.spec.ts
│   │   │       ├── jsx-variables.ts
│   │   │       ├── llm-api-key.ts
│   │   │       ├── llm-api-keys.spec.ts
│   │   │       ├── locales.spec.ts
│   │   │       ├── locales.ts
│   │   │       ├── module-params.spec.ts
│   │   │       ├── module-params.ts
│   │   │       ├── observability.spec.ts
│   │   │       ├── observability.ts
│   │   │       ├── rc.spec.ts
│   │   │       └── rc.ts
│   │   ├── tsconfig.json
│   │   ├── tsup.config.ts
│   │   └── vitest.config.ts
│   ├── locales
│   │   ├── CHANGELOG.md
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── src
│   │   │   ├── constants.ts
│   │   │   ├── index.ts
│   │   │   ├── names
│   │   │   │   ├── index.spec.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── integration.spec.ts
│   │   │   │   └── loader.ts
│   │   │   ├── parser.spec.ts
│   │   │   ├── parser.ts
│   │   │   ├── types.ts
│   │   │   ├── validation.spec.ts
│   │   │   └── validation.ts
│   │   ├── tsconfig.json
│   │   └── tsup.config.ts
│   ├── react
│   │   ├── build.config.ts
│   │   ├── CHANGELOG.md
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── src
│   │   │   ├── client
│   │   │   │   ├── attribute-component.spec.tsx
│   │   │   │   ├── attribute-component.tsx
│   │   │   │   ├── component.lingo-component.spec.tsx
│   │   │   │   ├── component.spec.tsx
│   │   │   │   ├── component.tsx
│   │   │   │   ├── context.spec.tsx
│   │   │   │   ├── context.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── loader.spec.ts
│   │   │   │   ├── loader.ts
│   │   │   │   ├── locale-switcher.spec.tsx
│   │   │   │   ├── locale-switcher.tsx
│   │   │   │   ├── locale.spec.ts
│   │   │   │   ├── locale.ts
│   │   │   │   ├── provider.spec.tsx
│   │   │   │   ├── provider.tsx
│   │   │   │   ├── utils.spec.ts
│   │   │   │   └── utils.ts
│   │   │   ├── core
│   │   │   │   ├── attribute-component.spec.tsx
│   │   │   │   ├── attribute-component.tsx
│   │   │   │   ├── component.spec.tsx
│   │   │   │   ├── component.tsx
│   │   │   │   ├── const.ts
│   │   │   │   ├── get-dictionary.spec.ts
│   │   │   │   ├── get-dictionary.ts
│   │   │   │   └── index.ts
│   │   │   ├── react-router
│   │   │   │   ├── index.ts
│   │   │   │   ├── loader.spec.ts
│   │   │   │   └── loader.ts
│   │   │   ├── rsc
│   │   │   │   ├── attribute-component.tsx
│   │   │   │   ├── component.lingo-component.spec.tsx
│   │   │   │   ├── component.spec.tsx
│   │   │   │   ├── component.tsx
│   │   │   │   ├── index.ts
│   │   │   │   ├── loader.spec.ts
│   │   │   │   ├── loader.ts
│   │   │   │   ├── provider.spec.tsx
│   │   │   │   ├── provider.tsx
│   │   │   │   ├── utils.spec.ts
│   │   │   │   └── utils.ts
│   │   │   └── test
│   │   │       └── setup.ts
│   │   ├── tsconfig.json
│   │   └── vitest.config.ts
│   ├── sdk
│   │   ├── CHANGELOG.md
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── src
│   │   │   ├── abort-controller.specs.ts
│   │   │   ├── index.spec.ts
│   │   │   └── index.ts
│   │   ├── tsconfig.json
│   │   ├── tsconfig.test.json
│   │   └── tsup.config.ts
│   └── spec
│       ├── CHANGELOG.md
│       ├── package.json
│       ├── README.md
│       ├── src
│       │   ├── config.spec.ts
│       │   ├── config.ts
│       │   ├── formats.ts
│       │   ├── index.spec.ts
│       │   ├── index.ts
│       │   ├── json-schema.ts
│       │   ├── locales.spec.ts
│       │   └── locales.ts
│       ├── tsconfig.json
│       ├── tsconfig.test.json
│       └── tsup.config.ts
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
├── readme
│   ├── ar.md
│   ├── bn.md
│   ├── de.md
│   ├── en.md
│   ├── es.md
│   ├── fa.md
│   ├── fr.md
│   ├── he.md
│   ├── hi.md
│   ├── it.md
│   ├── ja.md
│   ├── ko.md
│   ├── pl.md
│   ├── pt-BR.md
│   ├── ru.md
│   ├── tr.md
│   ├── uk-UA.md
│   └── zh-Hans.md
├── readme.md
├── scripts
│   ├── docs
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── src
│   │   │   ├── generate-cli-docs.ts
│   │   │   ├── generate-config-docs.ts
│   │   │   ├── json-schema
│   │   │   │   ├── markdown-renderer.test.ts
│   │   │   │   ├── markdown-renderer.ts
│   │   │   │   ├── parser.test.ts
│   │   │   │   ├── parser.ts
│   │   │   │   └── types.ts
│   │   │   ├── utils.test.ts
│   │   │   └── utils.ts
│   │   ├── tsconfig.json
│   │   └── vitest.config.ts
│   └── packagist-publish.php
└── turbo.json
```

# Files

--------------------------------------------------------------------------------
/demo/react-router-app/app/welcome/logo-dark.svg:
--------------------------------------------------------------------------------

```
 1 | <svg width="1080" height="174" viewBox="0 0 1080 174" fill="none" xmlns="http://www.w3.org/2000/svg">
 2 | <path d="M231.527 86.9999C231.527 94.9642 228.297 102.173 223.067 107.387C217.837 112.606 210.614 115.835 202.634 115.835C194.654 115.835 187.43 119.059 182.206 124.278C176.977 129.498 173.741 136.707 173.741 144.671C173.741 152.635 170.51 159.844 165.281 165.058C160.051 170.277 152.828 173.507 144.847 173.507C136.867 173.507 129.644 170.277 124.42 165.058C119.19 159.844 115.954 152.635 115.954 144.671C115.954 136.707 119.19 129.498 124.42 124.278C129.644 119.059 136.867 115.835 144.847 115.835C152.828 115.835 160.051 112.606 165.281 107.387C170.51 102.173 173.741 94.9642 173.741 86.9999C173.741 71.0711 160.808 58.1643 144.847 58.1643C136.867 58.1643 129.644 54.9347 124.42 49.7155C119.19 44.502 115.954 37.2931 115.954 29.3287C115.954 21.3643 119.19 14.1555 124.42 8.93622C129.644 3.71698 136.867 0.493164 144.847 0.493164C160.808 0.493164 173.741 13.4 173.741 29.3287C173.741 37.2931 176.977 44.502 182.206 49.7155C187.43 54.9347 194.654 58.1643 202.634 58.1643C218.594 58.1643 231.527 71.0711 231.527 86.9999Z" fill="#F44250"/>
 3 | <path d="M115.954 86.9996C115.954 71.0742 103.018 58.1641 87.061 58.1641C71.1037 58.1641 58.1677 71.0742 58.1677 86.9996C58.1677 102.925 71.1037 115.835 87.061 115.835C103.018 115.835 115.954 102.925 115.954 86.9996Z" fill="white"/>
 4 | <path d="M58.1676 144.671C58.1676 128.745 45.2316 115.835 29.2743 115.835C13.317 115.835 0.381104 128.745 0.381104 144.671C0.381104 160.596 13.317 173.506 29.2743 173.506C45.2316 173.506 58.1676 160.596 58.1676 144.671Z" fill="white"/>
 5 | <path d="M289.314 144.671C289.314 128.745 276.378 115.835 260.42 115.835C244.463 115.835 231.527 128.745 231.527 144.671C231.527 160.596 244.463 173.506 260.42 173.506C276.378 173.506 289.314 160.596 289.314 144.671Z" fill="white"/>
 6 | <g clip-path="url(#clip0_202_2131)">
 7 | <path d="M562.482 173.247C524.388 173.247 498.363 147.49 498.363 110.468C498.363 73.4455 524.388 47.6885 562.482 47.6885C600.576 47.6885 626.869 73.7135 626.869 110.468C626.869 147.222 600.576 173.247 562.482 173.247ZM562.482 144.007C579.385 144.007 587.703 130.319 587.703 110.468C587.703 90.6168 579.385 76.9289 562.482 76.9289C545.579 76.9289 537.529 90.6168 537.529 110.468C537.529 130.319 545.311 144.007 562.482 144.007Z" fill="white"/>
 8 | <path d="M833.64 141.116C824.217 141.116 819.237 136.684 819.237 126.156V74.8983H851.928V47.7792H819.237V1.15527H791.75L786.1 26.1978C783.343 36.4805 780.82 42.822 773.897 46.0821C773.105 46.4506 771.129 46.9976 769.409 47.3884C768.014 47.701 766.596 47.8573 765.167 47.8573H752.338V47.9243H734.832C723.578 47.9243 714.445 57.0459 714.445 68.3111V111.552C714.445 130.599 707.199 142.668 692.719 142.668C678.238 142.668 672.868 133.279 672.868 116.375V47.9243H634.249V125.765C634.249 151.254 644.442 173.248 676.63 173.248C691.915 173.248 703.895 167.231 711.096 157.182C712.145 155.72 714.445 156.49 714.445 158.276V170.022H753.332V83.8412C753.332 78.8953 757.34 74.8871 762.286 74.8871H779.882V136.952C779.882 164.663 797.89 173.248 817.842 173.248C833.908 173.248 844.436 169.374 853.58 162.441V136.126C846.1 139.453 839.725 141.116 833.629 141.116H833.64Z" fill="white"/>
 9 | <path d="M981.561 130.865C975.387 157.962 954.197 173.258 923.07 173.258C885.243 173.258 858.415 150.18 858.415 112.354C858.415 74.5281 885.779 47.6992 922.266 47.6992C961.699 47.6992 982.365 74.796 982.365 107.263V113.884H896.509C894.555 135.711 909.382 144.017 924.409 144.017C937.829 144.017 946.136 138.915 950.434 127.918L981.561 130.865ZM945.075 94.9372C944.271 83.1361 936.757 75.8567 921.998 75.8567C906.434 75.8567 899.188 82.321 897.045 94.9372H945.064H945.075Z" fill="white"/>
10 | <path d="M1076.24 85.7486C1070.06 82.2652 1064.17 80.9142 1055.85 80.9142C1039.75 80.9142 1029.02 90.0358 1029.02 110.691V170.02H990.393V47.9225H1029.02V64.3235C1029.02 65.4623 1030.54 65.8195 1031.05 64.8035C1036.68 53.5718 1047.91 44.707 1062.03 44.707C1069.27 44.707 1075.45 46.8507 1078.66 49.5414L1076.25 85.7597L1076.24 85.7486Z" fill="white"/>
11 | <path d="M547.32 31.5345V23.9983H522.457V31.5345H515.378V2.23828H542.14C553.562 2.23828 554.365 2.95282 554.365 13.1239C554.365 17.4111 553.472 18.5611 551.329 19.6553L549.408 20.6378L551.317 21.6426C553.595 22.8372 554.365 23.2391 554.365 30.0273V31.5345H547.332H547.32ZM522.457 18.3601H547.32V7.88763H522.457V18.349V18.3601Z" fill="white"/>
12 | <path d="M578.493 2.23828H610.826V7.90996H580.067V14.5083H610.011V19.2868H580.067V25.8963H610.837V31.501L578.504 31.5345C575.344 31.5345 572.787 28.9778 572.787 25.8293V7.95462C572.787 4.80617 575.344 2.24945 578.493 2.24945V2.23828Z" fill="white"/>
13 | <path d="M655.562 31.5345L653.151 26.3429H633.746L631.335 31.5345H624.58L637.006 4.75034C637.71 3.22078 639.262 2.23828 640.936 2.23828H645.927C647.613 2.23828 649.154 3.22078 649.857 4.75034L662.283 31.5345H655.529H655.562ZM643.46 8.06627C642.712 8.06627 642.053 8.49053 641.729 9.17158L635.968 21.5756H650.94L645.19 9.17158C644.878 8.49053 644.208 8.06627 643.46 8.06627Z" fill="white"/>
14 | <path d="M694.862 32.4153C676.05 32.4153 675.313 32.4153 675.313 16.8852C675.313 1.35505 676.05 1.36621 694.862 1.36621C711.721 1.36621 713.764 2.06959 714.244 10.5325H707.333V7.01556H682.168V26.766H707.333V23.2714H714.244C713.775 31.7119 711.721 32.4153 694.862 32.4153Z" fill="white"/>
15 | <path d="M745.282 31.5345V7.02795H729.16V2.23828H768.147V7.02795H752.025V31.5345H745.282Z" fill="white"/>
16 | <path d="M454.419 169.819C450.935 165.264 448.792 154.814 447.452 137.397C446.112 118.104 437.806 113.817 422.532 113.817H392.254V169.83H347.494V0.986328H432.715C476.391 0.986328 498.106 21.6187 498.106 54.5882C498.106 79.2399 482.833 95.3171 462.201 98.0078C479.618 101.491 489.8 111.405 491.675 130.966C494.087 156.154 494.891 163.656 500.518 169.819H454.419ZM424.676 78.704C443.969 78.704 453.615 73.8808 453.615 58.3395C453.615 44.6739 443.969 37.4392 424.676 37.4392H392.254V78.7152H424.676V78.704Z" fill="white"/>
17 | </g>
18 | <defs>
19 | <clipPath id="clip0_202_2131">
20 | <rect width="731.156" height="172.261" fill="white" transform="translate(347.494 0.986328)"/>
21 | </clipPath>
22 | </defs>
23 | </svg>
24 | 
```

--------------------------------------------------------------------------------
/packages/cli/src/cli/loaders/html.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { JSDOM } from "jsdom";
  2 | import { ILoader } from "./_types";
  3 | import { createLoader } from "./_utils";
  4 | 
  5 | function normalizeTextContent(text: string, isStandalone: boolean): string {
  6 |   // Remove any leading/trailing whitespace for initial comparison
  7 |   const trimmed = text.trim();
  8 |   if (!trimmed) return "";
  9 | 
 10 |   // For all text nodes, just return the trimmed content
 11 |   return trimmed;
 12 | }
 13 | 
 14 | export default function createHtmlLoader(): ILoader<
 15 |   string,
 16 |   Record<string, any>
 17 | > {
 18 |   const LOCALIZABLE_ATTRIBUTES: Record<string, string[]> = {
 19 |     meta: ["content"],
 20 |     img: ["alt"],
 21 |     input: ["placeholder"],
 22 |     a: ["title"],
 23 |   };
 24 |   const UNLOCALIZABLE_TAGS = ["script", "style"];
 25 | 
 26 |   return createLoader({
 27 |     async pull(locale, input) {
 28 |       const result: Record<string, any> = {};
 29 |       const dom = new JSDOM(input);
 30 |       const document = dom.window.document;
 31 | 
 32 |       const getPath = (node: Node, attribute?: string): string => {
 33 |         const indices: number[] = [];
 34 |         let current = node as ChildNode;
 35 |         let rootParent = "";
 36 | 
 37 |         while (current) {
 38 |           const parent = current.parentElement as Element;
 39 |           if (!parent) break;
 40 | 
 41 |           if (parent === document.documentElement) {
 42 |             rootParent = current.nodeName.toLowerCase();
 43 |             break;
 44 |           }
 45 | 
 46 |           // Get index among significant siblings (non-empty text nodes and elements)
 47 |           const siblings = Array.from(parent.childNodes).filter(
 48 |             (n) =>
 49 |               n.nodeType === 1 || (n.nodeType === 3 && n.textContent?.trim()),
 50 |           );
 51 |           const index = siblings.indexOf(current);
 52 |           if (index !== -1) {
 53 |             indices.unshift(index);
 54 |           }
 55 |           current = parent;
 56 |         }
 57 | 
 58 |         const basePath = rootParent
 59 |           ? `${rootParent}/${indices.join("/")}`
 60 |           : indices.join("/");
 61 |         return attribute ? `${basePath}#${attribute}` : basePath;
 62 |       };
 63 | 
 64 |       const processNode = (node: Node) => {
 65 |         // Check if node is inside an unlocalizable tag
 66 |         let parent = node.parentElement;
 67 |         while (parent) {
 68 |           if (UNLOCALIZABLE_TAGS.includes(parent.tagName.toLowerCase())) {
 69 |             return; // Skip processing this node and its children
 70 |           }
 71 |           parent = parent.parentElement;
 72 |         }
 73 | 
 74 |         if (node.nodeType === 3) {
 75 |           // Text node
 76 |           const text = node.textContent || "";
 77 |           const normalizedText = normalizeTextContent(text, true);
 78 |           if (normalizedText) {
 79 |             result[getPath(node)] = normalizedText;
 80 |           }
 81 |         } else if (node.nodeType === 1) {
 82 |           // Element node
 83 |           const element = node as Element;
 84 | 
 85 |           // Handle localizable attributes
 86 |           const tagName = element.tagName.toLowerCase();
 87 |           const attributes = LOCALIZABLE_ATTRIBUTES[tagName] || [];
 88 |           attributes.forEach((attr) => {
 89 |             const value = element.getAttribute(attr);
 90 |             if (value) {
 91 |               result[getPath(element, attr)] = value;
 92 |             }
 93 |           });
 94 | 
 95 |           // Process all child nodes
 96 |           Array.from(element.childNodes)
 97 |             .filter(
 98 |               (n) =>
 99 |                 n.nodeType === 1 || (n.nodeType === 3 && n.textContent?.trim()),
100 |             )
101 |             .forEach(processNode);
102 |         }
103 |       };
104 | 
105 |       // Process head and body
106 |       Array.from(document.head.childNodes)
107 |         .filter(
108 |           (n) =>
109 |             n.nodeType === 1 || (n.nodeType === 3 && n.textContent?.trim()),
110 |         )
111 |         .forEach(processNode);
112 |       Array.from(document.body.childNodes)
113 |         .filter(
114 |           (n) =>
115 |             n.nodeType === 1 || (n.nodeType === 3 && n.textContent?.trim()),
116 |         )
117 |         .forEach(processNode);
118 | 
119 |       return result;
120 |     },
121 | 
122 |     async push(locale, data, originalInput) {
123 |       const dom = new JSDOM(
124 |         originalInput ??
125 |           "<!DOCTYPE html><html><head></head><body></body></html>",
126 |       );
127 |       const document = dom.window.document;
128 | 
129 |       // Set the HTML lang attribute to the current locale
130 |       const htmlElement = document.documentElement;
131 |       htmlElement.setAttribute("lang", locale);
132 | 
133 |       // Sort paths to ensure proper order of creation
134 |       const paths = Object.keys(data).sort((a, b) => {
135 |         const aDepth = a.split("/").length;
136 |         const bDepth = b.split("/").length;
137 |         return aDepth - bDepth;
138 |       });
139 | 
140 |       paths.forEach((path) => {
141 |         const value = data[path];
142 |         const [nodePath, attribute] = path.split("#");
143 |         const [rootTag, ...indices] = nodePath.split("/");
144 | 
145 |         let parent: Element =
146 |           rootTag === "head" ? document.head : document.body;
147 |         let current: Node | null = parent;
148 | 
149 |         // Navigate to the target node
150 |         for (let i = 0; i < indices.length; i++) {
151 |           const index = parseInt(indices[i]);
152 |           const siblings = Array.from(parent.childNodes).filter(
153 |             (n) =>
154 |               n.nodeType === 1 || (n.nodeType === 3 && n.textContent?.trim()),
155 |           );
156 | 
157 |           if (index >= siblings.length) {
158 |             // Create missing nodes
159 |             if (i === indices.length - 1) {
160 |               // Last index - create text node
161 |               const textNode = document.createTextNode("");
162 |               parent.appendChild(textNode);
163 |               current = textNode;
164 |             } else {
165 |               // Create intermediate element
166 |               const element = document.createElement("div");
167 |               parent.appendChild(element);
168 |               current = element;
169 |               parent = element;
170 |             }
171 |           } else {
172 |             current = siblings[index];
173 |             if (current.nodeType === 1) {
174 |               parent = current as Element;
175 |             }
176 |           }
177 |         }
178 | 
179 |         // Set content
180 |         if (current) {
181 |           if (attribute) {
182 |             (current as Element).setAttribute(attribute, value);
183 |           } else {
184 |             current.textContent = value;
185 |           }
186 |         }
187 |       });
188 | 
189 |       // Preserve formatting by using serialize() with pretty print
190 |       return dom.serialize();
191 |     },
192 |   });
193 | }
194 | 
```

--------------------------------------------------------------------------------
/readme/it.md:
--------------------------------------------------------------------------------

```markdown
  1 | <p align="center">
  2 |   <a href="https://lingo.dev">
  3 |     <img
  4 |       src="https://raw.githubusercontent.com/lingodotdev/lingo.dev/main/content/banner.compiler.png"
  5 |       width="100%"
  6 |       alt="Lingo.dev"
  7 |     />
  8 |   </a>
  9 | </p>
 10 | 
 11 | <p align="center">
 12 |   <strong>
 13 |     ⚡ Lingo.dev - toolkit open-source per l'i18n, potenziato dall'IA per la
 14 |     localizzazione istantanea con LLM.
 15 |   </strong>
 16 | </p>
 17 | 
 18 | <br />
 19 | 
 20 | <p align="center">
 21 |   <a href="https://lingo.dev/compiler">Lingo.dev Compiler</a> •
 22 |   <a href="https://lingo.dev/cli">Lingo.dev CLI</a> •
 23 |   <a href="https://lingo.dev/ci">Lingo.dev CI/CD</a> •
 24 |   <a href="https://lingo.dev/sdk">Lingo.dev SDK</a>
 25 | </p>
 26 | 
 27 | <p align="center">
 28 |   <a href="https://github.com/lingodotdev/lingo.dev/actions/workflows/release.yml">
 29 |     <img
 30 |       src="https://github.com/lingodotdev/lingo.dev/actions/workflows/release.yml/badge.svg"
 31 |       alt="Release"
 32 |     />
 33 |   </a>
 34 |   <a href="https://github.com/lingodotdev/lingo.dev/blob/main/LICENSE.md">
 35 |     <img
 36 |       src="https://img.shields.io/github/license/lingodotdev/lingo.dev"
 37 |       alt="Licenza"
 38 |     />
 39 |   </a>
 40 |   <a href="https://github.com/lingodotdev/lingo.dev/commits/main">
 41 |     <img
 42 |       src="https://img.shields.io/github/last-commit/lingodotdev/lingo.dev"
 43 |       alt="Ultimo commit"
 44 |     />
 45 |   </a>
 46 |   <a href="https://lingo.dev/en">
 47 |     <img
 48 |       src="https://img.shields.io/badge/Product%20Hunt-%231%20Product%20of%20the%20Day-orange?logo=producthunt&style=flat-square"
 49 |       alt="Prodotto del giorno #1 su Product Hunt"
 50 |     />
 51 |   </a>
 52 |   <a href="https://lingo.dev/en">
 53 |     <img
 54 |       src="https://img.shields.io/badge/GitHub-Trending-blue?logo=github&style=flat-square"
 55 |       alt="Trending su Github"
 56 |     />
 57 |   </a>
 58 | </p>
 59 | 
 60 | ---
 61 | 
 62 | ## Scopri il Compiler 🆕
 63 | 
 64 | **Lingo.dev Compiler** è un middleware compiler gratuito e open-source, progettato per rendere qualsiasi applicazione React multilingue in fase di compilazione senza richiedere modifiche ai componenti React esistenti.
 65 | 
 66 | Installa una volta:
 67 | 
 68 | ```bash
 69 | npm install lingo.dev
 70 | ```
 71 | 
 72 | Abilita nella configurazione di build:
 73 | 
 74 | ```js
 75 | import lingoCompiler from "lingo.dev/compiler";
 76 | 
 77 | const existingNextConfig = {};
 78 | 
 79 | export default lingoCompiler.next({
 80 |   sourceLocale: "en",
 81 |   targetLocales: ["es", "fr"],
 82 | })(existingNextConfig);
 83 | ```
 84 | 
 85 | Esegui `next build` e guarda apparire i bundle in spagnolo e francese ✨
 86 | 
 87 | [Leggi la documentazione →](https://lingo.dev/compiler) per la guida completa, e [Unisciti al nostro Discord](https://lingo.dev/go/discord) per ricevere assistenza con la tua configurazione.
 88 | 
 89 | ---
 90 | 
 91 | ### Cosa contiene questo repository?
 92 | 
 93 | | Strumento    | In breve                                                                              | Documentazione                          |
 94 | | ------------ | ------------------------------------------------------------------------------------- | --------------------------------------- |
 95 | | **Compiler** | Localizzazione React in fase di build                                                 | [/compiler](https://lingo.dev/compiler) |
 96 | | **CLI**      | Localizzazione con un solo comando per app web e mobile, JSON, YAML, markdown e altro | [/cli](https://lingo.dev/cli)           |
 97 | | **CI/CD**    | Auto-commit delle traduzioni ad ogni push + creazione di pull request se necessario   | [/ci](https://lingo.dev/ci)             |
 98 | | **SDK**      | Traduzione in tempo reale per contenuti generati dagli utenti                         | [/sdk](https://lingo.dev/sdk)           |
 99 | 
100 | Di seguito i punti salienti per ciascuno 👇
101 | 
102 | ---
103 | 
104 | ### ⚡️ Lingo.dev CLI
105 | 
106 | Traduci codice e contenuti direttamente dal tuo terminale.
107 | 
108 | ```bash
109 | npx lingo.dev@latest run
110 | ```
111 | 
112 | Crea un'impronta digitale per ogni stringa, memorizza i risultati nella cache e ritraduce solo ciò che è cambiato.
113 | 
114 | [Segui la documentazione →](https://lingo.dev/cli) per imparare come configurarlo.
115 | 
116 | ---
117 | 
118 | ### 🔄 Lingo.dev CI/CD
119 | 
120 | Distribuisci traduzioni perfette automaticamente.
121 | 
122 | ```yaml
123 | # .github/workflows/i18n.yml
124 | name: Lingo.dev i18n
125 | on: [push]
126 | 
127 | jobs:
128 |   i18n:
129 |     runs-on: ubuntu-latest
130 |     steps:
131 |       - uses: actions/checkout@v4
132 |       - uses: lingodotdev/lingo.dev@main
133 |         with:
134 |           api-key: ${{ secrets.LINGODOTDEV_API_KEY }}
135 | ```
136 | 
137 | Mantiene il tuo repository aggiornato e il tuo prodotto multilingue senza passaggi manuali.
138 | 
139 | [Leggi la documentazione →](https://lingo.dev/ci)
140 | 
141 | ---
142 | 
143 | ### 🧩 Lingo.dev SDK
144 | 
145 | Traduzione istantanea per richiesta per contenuti dinamici.
146 | 
147 | ```ts
148 | import { LingoDotDevEngine } from "lingo.dev/sdk";
149 | 
150 | const lingoDotDev = new LingoDotDevEngine({
151 |   apiKey: "your-api-key-here",
152 | });
153 | 
154 | const content = {
155 |   greeting: "Hello",
156 |   farewell: "Goodbye",
157 |   message: "Welcome to our platform",
158 | };
159 | 
160 | const translated = await lingoDotDev.localizeObject(content, {
161 |   sourceLocale: "en",
162 |   targetLocale: "es",
163 | });
164 | // Returns: { greeting: "Hola", farewell: "Adiós", message: "Bienvenido a nuestra plataforma" }
165 | ```
166 | 
167 | Perfetto per chat, commenti degli utenti e altri flussi in tempo reale.
168 | 
169 | [Leggi la documentazione →](https://lingo.dev/sdk)
170 | 
171 | ---
172 | 
173 | ## 🤝 Community
174 | 
175 | Siamo guidati dalla community e amiamo i contributi!
176 | 
177 | - Hai un'idea? [Apri una issue](https://github.com/lingodotdev/lingo.dev/issues)
178 | - Vuoi correggere qualcosa? [Invia una PR](https://github.com/lingodotdev/lingo.dev/pulls)
179 | - Hai bisogno di aiuto? [Unisciti al nostro Discord](https://lingo.dev/go/discord)
180 | 
181 | ## ⭐ Cronologia delle stelle
182 | 
183 | Se ti piace quello che stiamo facendo, dacci una ⭐ e aiutaci a raggiungere 4.000 stelle! 🌟
184 | 
185 | [
186 | 
187 | ![Grafico cronologia stelle](https://api.star-history.com/svg?repos=lingodotdev/lingo.dev&type=Date)
188 | 
189 | ](https://www.star-history.com/#lingodotdev/lingo.dev&Date)
190 | 
191 | ## 🌐 Readme in altre lingue
192 | 
193 | [English](https://github.com/lingodotdev/lingo.dev) • [中文](/readme/zh-Hans.md) • [日本語](/readme/ja.md) • [한국어](/readme/ko.md) • [Español](/readme/es.md) • [Français](/readme/fr.md) • [Русский](/readme/ru.md) • [Українська](/readme/uk-UA.md) • [Deutsch](/readme/de.md) • [Italiano](/readme/it.md) • [العربية](/readme/ar.md) • [עברית](/readme/he.md) • [हिन्दी](/readme/hi.md) • [বাংলা](/readme/bn.md) • [فارسی](/readme/fa.md)
194 | 
195 | Non vedi la tua lingua? Aggiungila a [`i18n.json`](./i18n.json) e apri una PR!
196 | 
```

--------------------------------------------------------------------------------
/demo/react-router-app/app/welcome/logo-light.svg:
--------------------------------------------------------------------------------

```
 1 | <svg width="1080" height="174" viewBox="0 0 1080 174" fill="none" xmlns="http://www.w3.org/2000/svg">
 2 | <path d="M231.527 86.9999C231.527 94.9642 228.297 102.173 223.067 107.387C217.837 112.606 210.614 115.835 202.634 115.835C194.654 115.835 187.43 119.059 182.206 124.278C176.977 129.498 173.741 136.707 173.741 144.671C173.741 152.635 170.51 159.844 165.281 165.058C160.051 170.277 152.828 173.507 144.847 173.507C136.867 173.507 129.644 170.277 124.42 165.058C119.19 159.844 115.954 152.635 115.954 144.671C115.954 136.707 119.19 129.498 124.42 124.278C129.644 119.059 136.867 115.835 144.847 115.835C152.828 115.835 160.051 112.606 165.281 107.387C170.51 102.173 173.741 94.9642 173.741 86.9999C173.741 71.0711 160.808 58.1643 144.847 58.1643C136.867 58.1643 129.644 54.9347 124.42 49.7155C119.19 44.502 115.954 37.2931 115.954 29.3287C115.954 21.3643 119.19 14.1555 124.42 8.93622C129.644 3.71698 136.867 0.493164 144.847 0.493164C160.808 0.493164 173.741 13.4 173.741 29.3287C173.741 37.2931 176.977 44.502 182.206 49.7155C187.43 54.9347 194.654 58.1643 202.634 58.1643C218.594 58.1643 231.527 71.0711 231.527 86.9999Z" fill="#F44250"/>
 3 | <path d="M115.954 86.9996C115.954 71.0742 103.018 58.1641 87.0608 58.1641C71.1035 58.1641 58.1676 71.0742 58.1676 86.9996C58.1676 102.925 71.1035 115.835 87.0608 115.835C103.018 115.835 115.954 102.925 115.954 86.9996Z" fill="#121212"/>
 4 | <path d="M58.1676 144.671C58.1676 128.745 45.2316 115.835 29.2743 115.835C13.317 115.835 0.381104 128.745 0.381104 144.671C0.381104 160.596 13.317 173.506 29.2743 173.506C45.2316 173.506 58.1676 160.596 58.1676 144.671Z" fill="#121212"/>
 5 | <path d="M289.313 144.671C289.313 128.745 276.378 115.835 260.42 115.835C244.463 115.835 231.527 128.745 231.527 144.671C231.527 160.596 244.463 173.506 260.42 173.506C276.378 173.506 289.313 160.596 289.313 144.671Z" fill="#121212"/>
 6 | <g clip-path="url(#clip0_171_1761)">
 7 | <path d="M562.482 173.247C524.388 173.247 498.363 147.49 498.363 110.468C498.363 73.4455 524.388 47.6885 562.482 47.6885C600.576 47.6885 626.869 73.7135 626.869 110.468C626.869 147.222 600.576 173.247 562.482 173.247ZM562.482 144.007C579.386 144.007 587.703 130.319 587.703 110.468C587.703 90.6168 579.386 76.9289 562.482 76.9289C545.579 76.9289 537.529 90.6168 537.529 110.468C537.529 130.319 545.311 144.007 562.482 144.007Z" fill="#121212"/>
 8 | <path d="M833.64 141.116C824.217 141.116 819.237 136.684 819.237 126.156V74.8983H851.928V47.7792H819.237V1.15527H791.75L786.1 26.1978C783.343 36.4805 780.82 42.822 773.897 46.0821C773.105 46.4506 771.129 46.9976 769.409 47.3884C768.014 47.701 766.596 47.8573 765.167 47.8573H752.338V47.9243H734.832C723.578 47.9243 714.445 57.0459 714.445 68.3111V111.552C714.445 130.599 707.199 142.668 692.719 142.668C678.238 142.668 672.868 133.279 672.868 116.375V47.9243H634.249V125.765C634.249 151.254 644.442 173.248 676.63 173.248C691.915 173.248 703.895 167.231 711.096 157.182C712.145 155.72 714.445 156.49 714.445 158.276V170.022H753.332V83.8412C753.332 78.8953 757.34 74.8871 762.286 74.8871H779.882V136.952C779.882 164.663 797.89 173.248 817.842 173.248C833.908 173.248 844.436 169.374 853.58 162.441V136.126C846.1 139.453 839.725 141.116 833.629 141.116H833.64Z" fill="#121212"/>
 9 | <path d="M981.561 130.865C975.387 157.962 954.197 173.258 923.07 173.258C885.243 173.258 858.415 150.18 858.415 112.354C858.415 74.5281 885.779 47.6992 922.266 47.6992C961.699 47.6992 982.365 74.796 982.365 107.263V113.884H896.509C894.555 135.711 909.382 144.017 924.409 144.017C937.829 144.017 946.136 138.915 950.434 127.918L981.561 130.865ZM945.075 94.9372C944.271 83.1361 936.757 75.8567 921.998 75.8567C906.434 75.8567 899.188 82.321 897.045 94.9372H945.064H945.075Z" fill="#121212"/>
10 | <path d="M1076.24 85.7486C1070.06 82.2652 1064.17 80.9142 1055.85 80.9142C1039.75 80.9142 1029.02 90.0358 1029.02 110.691V170.02H990.393V47.9225H1029.02V64.3235C1029.02 65.4623 1030.54 65.8195 1031.05 64.8035C1036.68 53.5718 1047.91 44.707 1062.03 44.707C1069.27 44.707 1075.45 46.8507 1078.66 49.5414L1076.25 85.7597L1076.24 85.7486Z" fill="#121212"/>
11 | <path d="M547.321 31.5345V23.9983H522.457V31.5345H515.378V2.23828H542.14C553.562 2.23828 554.366 2.95282 554.366 13.1239C554.366 17.4111 553.472 18.5611 551.329 19.6553L549.408 20.6378L551.318 21.6426C553.595 22.8372 554.366 23.2391 554.366 30.0273V31.5345H547.332H547.321ZM522.457 18.3601H547.321V7.88763H522.457V18.349V18.3601Z" fill="#121212"/>
12 | <path d="M578.493 2.23828H610.826V7.90996H580.067V14.5083H610.011V19.2868H580.067V25.8963H610.837V31.501L578.504 31.5345C575.344 31.5345 572.787 28.9778 572.787 25.8293V7.95462C572.787 4.80617 575.344 2.24945 578.493 2.24945V2.23828Z" fill="#121212"/>
13 | <path d="M655.562 31.5345L653.151 26.3429H633.747L631.335 31.5345H624.58L637.007 4.75034C637.71 3.22078 639.262 2.23828 640.937 2.23828H645.927C647.613 2.23828 649.154 3.22078 649.857 4.75034L662.284 31.5345H655.529H655.562ZM643.46 8.06627C642.712 8.06627 642.053 8.49053 641.729 9.17158L635.968 21.5756H650.94L645.19 9.17158C644.878 8.49053 644.208 8.06627 643.46 8.06627Z" fill="#121212"/>
14 | <path d="M694.862 32.4153C676.05 32.4153 675.313 32.4153 675.313 16.8852C675.313 1.35505 676.05 1.36621 694.862 1.36621C711.721 1.36621 713.764 2.06959 714.244 10.5325H707.333V7.01556H682.168V26.766H707.333V23.2714H714.244C713.775 31.7119 711.721 32.4153 694.862 32.4153Z" fill="#121212"/>
15 | <path d="M745.282 31.5345V7.02795H729.16V2.23828H768.148V7.02795H752.026V31.5345H745.282Z" fill="#121212"/>
16 | <path d="M454.419 169.819C450.935 165.264 448.792 154.814 447.452 137.397C446.112 118.104 437.806 113.817 422.532 113.817H392.254V169.83H347.494V0.986328H432.715C476.391 0.986328 498.106 21.6187 498.106 54.5882C498.106 79.2399 482.833 95.3171 462.201 98.0078C479.618 101.491 489.8 111.405 491.676 130.966C494.087 156.154 494.891 163.656 500.518 169.819H454.419ZM424.676 78.704C443.969 78.704 453.615 73.8808 453.615 58.3395C453.615 44.6739 443.969 37.4392 424.676 37.4392H392.254V78.7152H424.676V78.704Z" fill="#121212"/>
17 | </g>
18 | <defs>
19 | <clipPath id="clip0_171_1761">
20 | <rect width="731.156" height="172.261" fill="white" transform="translate(347.494 0.986328)"/>
21 | </clipPath>
22 | </defs>
23 | </svg>
24 | 
```

--------------------------------------------------------------------------------
/readme/de.md:
--------------------------------------------------------------------------------

```markdown
  1 | <p align="center">
  2 |   <a href="https://lingo.dev">
  3 |     <img
  4 |       src="https://raw.githubusercontent.com/lingodotdev/lingo.dev/main/content/banner.compiler.png"
  5 |       width="100%"
  6 |       alt="Lingo.dev"
  7 |     />
  8 |   </a>
  9 | </p>
 10 | 
 11 | <p align="center">
 12 |   <strong>
 13 |     ⚡ Lingo.dev - Open-Source, KI-gestütztes i18n-Toolkit für sofortige
 14 |     Lokalisierung mit LLMs.
 15 |   </strong>
 16 | </p>
 17 | 
 18 | <br />
 19 | 
 20 | <p align="center">
 21 |   <a href="https://lingo.dev/compiler">Lingo.dev Compiler</a> •
 22 |   <a href="https://lingo.dev/cli">Lingo.dev CLI</a> •
 23 |   <a href="https://lingo.dev/ci">Lingo.dev CI/CD</a> •
 24 |   <a href="https://lingo.dev/sdk">Lingo.dev SDK</a>
 25 | </p>
 26 | 
 27 | <p align="center">
 28 |   <a href="https://github.com/lingodotdev/lingo.dev/actions/workflows/release.yml">
 29 |     <img
 30 |       src="https://github.com/lingodotdev/lingo.dev/actions/workflows/release.yml/badge.svg"
 31 |       alt="Release"
 32 |     />
 33 |   </a>
 34 |   <a href="https://github.com/lingodotdev/lingo.dev/blob/main/LICENSE.md">
 35 |     <img
 36 |       src="https://img.shields.io/github/license/lingodotdev/lingo.dev"
 37 |       alt="Lizenz"
 38 |     />
 39 |   </a>
 40 |   <a href="https://github.com/lingodotdev/lingo.dev/commits/main">
 41 |     <img
 42 |       src="https://img.shields.io/github/last-commit/lingodotdev/lingo.dev"
 43 |       alt="Letzter Commit"
 44 |     />
 45 |   </a>
 46 |   <a href="https://lingo.dev/en">
 47 |     <img
 48 |       src="https://img.shields.io/badge/Product%20Hunt-%231%20Product%20of%20the%20Day-orange?logo=producthunt&style=flat-square"
 49 |       alt="Product Hunt #1 Produkt des Tages"
 50 |     />
 51 |   </a>
 52 |   <a href="https://lingo.dev/en">
 53 |     <img
 54 |       src="https://img.shields.io/badge/GitHub-Trending-blue?logo=github&style=flat-square"
 55 |       alt="Github trending"
 56 |     />
 57 |   </a>
 58 | </p>
 59 | 
 60 | ---
 61 | 
 62 | ## Entdecken Sie den Compiler 🆕
 63 | 
 64 | **Lingo.dev Compiler** ist eine kostenlose, Open-Source-Compiler-Middleware, die entwickelt wurde, um jede React-Anwendung zur Build-Zeit mehrsprachig zu machen, ohne dass Änderungen an den bestehenden React-Komponenten erforderlich sind.
 65 | 
 66 | Einmalige Installation:
 67 | 
 68 | ```bash
 69 | npm install lingo.dev
 70 | ```
 71 | 
 72 | In Ihrer Build-Konfiguration aktivieren:
 73 | 
 74 | ```js
 75 | import lingoCompiler from "lingo.dev/compiler";
 76 | 
 77 | const existingNextConfig = {};
 78 | 
 79 | export default lingoCompiler.next({
 80 |   sourceLocale: "en",
 81 |   targetLocales: ["es", "fr"],
 82 | })(existingNextConfig);
 83 | ```
 84 | 
 85 | Führen Sie `next build` aus und beobachten Sie, wie spanische und französische Bundles erscheinen ✨
 86 | 
 87 | [Lesen Sie die Dokumentation →](https://lingo.dev/compiler) für die vollständige Anleitung und [treten Sie unserem Discord bei](https://lingo.dev/go/discord), um Hilfe bei Ihrer Einrichtung zu erhalten.
 88 | 
 89 | ---
 90 | 
 91 | ### Was beinhaltet dieses Repository?
 92 | 
 93 | | Tool         | Kurzfassung                                                                            | Dokumentation                           |
 94 | | ------------ | -------------------------------------------------------------------------------------- | --------------------------------------- |
 95 | | **Compiler** | Build-time React-Lokalisierung                                                         | [/compiler](https://lingo.dev/compiler) |
 96 | | **CLI**      | Ein-Befehl-Lokalisierung für Web- und Mobile-Apps, JSON, YAML, Markdown + mehr         | [/cli](https://lingo.dev/cli)           |
 97 | | **CI/CD**    | Auto-Commit von Übersetzungen bei jedem Push + Erstellung von Pull Requests bei Bedarf | [/ci](https://lingo.dev/ci)             |
 98 | | **SDK**      | Echtzeit-Übersetzung für nutzergenerierte Inhalte                                      | [/sdk](https://lingo.dev/sdk)           |
 99 | 
100 | Hier sind die wichtigsten Punkte für jedes Tool 👇
101 | 
102 | ---
103 | 
104 | ### ⚡️ Lingo.dev CLI
105 | 
106 | Übersetzen Sie Code & Inhalte direkt von Ihrem Terminal aus.
107 | 
108 | ```bash
109 | npx lingo.dev@latest run
110 | ```
111 | 
112 | Es erstellt Fingerabdrücke jedes Strings, speichert Ergebnisse im Cache und übersetzt nur, was sich geändert hat.
113 | 
114 | [Folgen Sie der Dokumentation →](https://lingo.dev/cli), um zu erfahren, wie Sie es einrichten können.
115 | 
116 | ---
117 | 
118 | ### 🔄 Lingo.dev CI/CD
119 | 
120 | Liefern Sie automatisch perfekte Übersetzungen.
121 | 
122 | ```yaml
123 | # .github/workflows/i18n.yml
124 | name: Lingo.dev i18n
125 | on: [push]
126 | 
127 | jobs:
128 |   i18n:
129 |     runs-on: ubuntu-latest
130 |     steps:
131 |       - uses: actions/checkout@v4
132 |       - uses: lingodotdev/lingo.dev@main
133 |         with:
134 |           api-key: ${{ secrets.LINGODOTDEV_API_KEY }}
135 | ```
136 | 
137 | Hält Ihr Repository grün und Ihr Produkt mehrsprachig ohne manuelle Schritte.
138 | 
139 | [Lesen Sie die Dokumentation →](https://lingo.dev/ci)
140 | 
141 | ---
142 | 
143 | ### 🧩 Lingo.dev SDK
144 | 
145 | Sofortige Übersetzung pro Anfrage für dynamische Inhalte.
146 | 
147 | ```ts
148 | import { LingoDotDevEngine } from "lingo.dev/sdk";
149 | 
150 | const lingoDotDev = new LingoDotDevEngine({
151 |   apiKey: "your-api-key-here",
152 | });
153 | 
154 | const content = {
155 |   greeting: "Hello",
156 |   farewell: "Goodbye",
157 |   message: "Welcome to our platform",
158 | };
159 | 
160 | const translated = await lingoDotDev.localizeObject(content, {
161 |   sourceLocale: "en",
162 |   targetLocale: "es",
163 | });
164 | // Returns: { greeting: "Hola", farewell: "Adiós", message: "Bienvenido a nuestra plataforma" }
165 | ```
166 | 
167 | Perfekt für Chat, Benutzerkommentare und andere Echtzeit-Workflows.
168 | 
169 | [Dokumentation lesen →](https://lingo.dev/sdk)
170 | 
171 | ---
172 | 
173 | ## 🤝 Community
174 | 
175 | Wir sind community-orientiert und schätzen Beiträge!
176 | 
177 | - Eine Idee? [Issue erstellen](https://github.com/lingodotdev/lingo.dev/issues)
178 | - Möchten Sie etwas verbessern? [PR senden](https://github.com/lingodotdev/lingo.dev/pulls)
179 | - Brauchen Sie Hilfe? [Discord beitreten](https://lingo.dev/go/discord)
180 | 
181 | ## ⭐ Star-Verlauf
182 | 
183 | Wenn Ihnen gefällt, was wir tun, geben Sie uns einen ⭐ und helfen Sie uns, 4.000 Sterne zu erreichen! 🌟
184 | 
185 | [
186 | 
187 | ![Star-Verlauf Diagramm](https://api.star-history.com/svg?repos=lingodotdev/lingo.dev&type=Date)
188 | 
189 | ](https://www.star-history.com/#lingodotdev/lingo.dev&Date)
190 | 
191 | ## 🌐 Readme in anderen Sprachen
192 | 
193 | [English](https://github.com/lingodotdev/lingo.dev) • [中文](/readme/zh-Hans.md) • [日本語](/readme/ja.md) • [한국어](/readme/ko.md) • [Español](/readme/es.md) • [Français](/readme/fr.md) • [Русский](/readme/ru.md) • [Українська](/readme/uk-UA.md) • [Deutsch](/readme/de.md) • [Italiano](/readme/it.md) • [العربية](/readme/ar.md) • [עברית](/readme/he.md) • [हिन्दी](/readme/hi.md) • [বাংলা](/readme/bn.md) • [فارسی](/readme/fa.md)
194 | 
195 | Ihre Sprache nicht dabei? Fügen Sie sie zu [`i18n.json`](./i18n.json) hinzu und öffnen Sie einen PR!
196 | 
```

--------------------------------------------------------------------------------
/readme/es.md:
--------------------------------------------------------------------------------

```markdown
  1 | <p align="center">
  2 |   <a href="https://lingo.dev">
  3 |     <img
  4 |       src="https://raw.githubusercontent.com/lingodotdev/lingo.dev/main/content/banner.compiler.png"
  5 |       width="100%"
  6 |       alt="Lingo.dev"
  7 |     />
  8 |   </a>
  9 | </p>
 10 | 
 11 | <p align="center">
 12 |   <strong>
 13 |     ⚡ Lingo.dev - kit de herramientas de i18n de código abierto, potenciado por
 14 |     IA para localización instantánea con LLMs.
 15 |   </strong>
 16 | </p>
 17 | 
 18 | <br />
 19 | 
 20 | <p align="center">
 21 |   <a href="https://lingo.dev/compiler">Lingo.dev Compiler</a> •
 22 |   <a href="https://lingo.dev/cli">Lingo.dev CLI</a> •
 23 |   <a href="https://lingo.dev/ci">Lingo.dev CI/CD</a> •
 24 |   <a href="https://lingo.dev/sdk">Lingo.dev SDK</a>
 25 | </p>
 26 | 
 27 | <p align="center">
 28 |   <a href="https://github.com/lingodotdev/lingo.dev/actions/workflows/release.yml">
 29 |     <img
 30 |       src="https://github.com/lingodotdev/lingo.dev/actions/workflows/release.yml/badge.svg"
 31 |       alt="Lanzamiento"
 32 |     />
 33 |   </a>
 34 |   <a href="https://github.com/lingodotdev/lingo.dev/blob/main/LICENSE.md">
 35 |     <img
 36 |       src="https://img.shields.io/github/license/lingodotdev/lingo.dev"
 37 |       alt="Licencia"
 38 |     />
 39 |   </a>
 40 |   <a href="https://github.com/lingodotdev/lingo.dev/commits/main">
 41 |     <img
 42 |       src="https://img.shields.io/github/last-commit/lingodotdev/lingo.dev"
 43 |       alt="Último commit"
 44 |     />
 45 |   </a>
 46 |   <a href="https://lingo.dev/en">
 47 |     <img
 48 |       src="https://img.shields.io/badge/Product%20Hunt-%231%20Product%20of%20the%20Day-orange?logo=producthunt&style=flat-square"
 49 |       alt="Producto #1 del día en Product Hunt"
 50 |     />
 51 |   </a>
 52 |   <a href="https://lingo.dev/en">
 53 |     <img
 54 |       src="https://img.shields.io/badge/GitHub-Trending-blue?logo=github&style=flat-square"
 55 |       alt="Tendencia en Github"
 56 |     />
 57 |   </a>
 58 | </p>
 59 | 
 60 | ---
 61 | 
 62 | ## Conoce el Compiler 🆕
 63 | 
 64 | **Lingo.dev Compiler** es un middleware compilador gratuito y de código abierto, diseñado para hacer que cualquier aplicación React sea multilingüe durante el tiempo de compilación sin requerir cambios en los componentes React existentes.
 65 | 
 66 | Instalar una vez:
 67 | 
 68 | ```bash
 69 | npm install lingo.dev
 70 | ```
 71 | 
 72 | Habilitar en tu configuración de compilación:
 73 | 
 74 | ```js
 75 | import lingoCompiler from "lingo.dev/compiler";
 76 | 
 77 | const existingNextConfig = {};
 78 | 
 79 | export default lingoCompiler.next({
 80 |   sourceLocale: "en",
 81 |   targetLocales: ["es", "fr"],
 82 | })(existingNextConfig);
 83 | ```
 84 | 
 85 | Ejecuta `next build` y observa cómo aparecen los paquetes en español y francés ✨
 86 | 
 87 | [Lee la documentación →](https://lingo.dev/compiler) para la guía completa, y [Únete a nuestro Discord](https://lingo.dev/go/discord) para obtener ayuda con tu configuración.
 88 | 
 89 | ---
 90 | 
 91 | ### ¿Qué hay dentro de este repositorio?
 92 | 
 93 | | Herramienta  | Resumen                                                                                      | Documentación                           |
 94 | | ------------ | -------------------------------------------------------------------------------------------- | --------------------------------------- |
 95 | | **Compiler** | Localización de React en tiempo de compilación                                               | [/compiler](https://lingo.dev/compiler) |
 96 | | **CLI**      | Localización con un solo comando para aplicaciones web y móviles, JSON, YAML, markdown y más | [/cli](https://lingo.dev/cli)           |
 97 | | **CI/CD**    | Auto-commit de traducciones en cada push + creación de pull requests si es necesario         | [/ci](https://lingo.dev/ci)             |
 98 | | **SDK**      | Traducción en tiempo real para contenido generado por usuarios                               | [/sdk](https://lingo.dev/sdk)           |
 99 | 
100 | A continuación, los aspectos más destacados de cada uno 👇
101 | 
102 | ---
103 | 
104 | ### ⚡️ Lingo.dev CLI
105 | 
106 | Traduce código y contenido directamente desde tu terminal.
107 | 
108 | ```bash
109 | npx lingo.dev@latest run
110 | ```
111 | 
112 | Genera huellas digitales de cada cadena, almacena resultados en caché y solo retraduce lo que ha cambiado.
113 | 
114 | [Sigue la documentación →](https://lingo.dev/cli) para aprender cómo configurarlo.
115 | 
116 | ---
117 | 
118 | ### 🔄 Lingo.dev CI/CD
119 | 
120 | Entrega traducciones perfectas automáticamente.
121 | 
122 | ```yaml
123 | # .github/workflows/i18n.yml
124 | name: Lingo.dev i18n
125 | on: [push]
126 | 
127 | jobs:
128 |   i18n:
129 |     runs-on: ubuntu-latest
130 |     steps:
131 |       - uses: actions/checkout@v4
132 |       - uses: lingodotdev/lingo.dev@main
133 |         with:
134 |           api-key: ${{ secrets.LINGODOTDEV_API_KEY }}
135 | ```
136 | 
137 | Mantiene tu repositorio actualizado y tu producto multilingüe sin pasos manuales.
138 | 
139 | [Lee la documentación →](https://lingo.dev/ci)
140 | 
141 | ---
142 | 
143 | ### 🧩 Lingo.dev SDK
144 | 
145 | Traducción instantánea por solicitud para contenido dinámico.
146 | 
147 | ```ts
148 | import { LingoDotDevEngine } from "lingo.dev/sdk";
149 | 
150 | const lingoDotDev = new LingoDotDevEngine({
151 |   apiKey: "your-api-key-here",
152 | });
153 | 
154 | const content = {
155 |   greeting: "Hello",
156 |   farewell: "Goodbye",
157 |   message: "Welcome to our platform",
158 | };
159 | 
160 | const translated = await lingoDotDev.localizeObject(content, {
161 |   sourceLocale: "en",
162 |   targetLocale: "es",
163 | });
164 | // Returns: { greeting: "Hola", farewell: "Adiós", message: "Bienvenido a nuestra plataforma" }
165 | ```
166 | 
167 | Perfecto para chat, comentarios de usuarios y otros flujos en tiempo real.
168 | 
169 | [Leer la documentación →](https://lingo.dev/sdk)
170 | 
171 | ---
172 | 
173 | ## 🤝 Comunidad
174 | 
175 | Somos impulsados por la comunidad y nos encantan las contribuciones!
176 | 
177 | - ¿Tienes una idea? [Abre un issue](https://github.com/lingodotdev/lingo.dev/issues)
178 | - ¿Quieres arreglar algo? [Envía un PR](https://github.com/lingodotdev/lingo.dev/pulls)
179 | - ¿Necesitas ayuda? [Únete a nuestro Discord](https://lingo.dev/go/discord)
180 | 
181 | ## ⭐ Historial de estrellas
182 | 
183 | Si te gusta lo que estamos haciendo, danos una ⭐ y ayúdanos a alcanzar las 4,000 estrellas! 🌟
184 | 
185 | [
186 | 
187 | ![Gráfico de historial de estrellas](https://api.star-history.com/svg?repos=lingodotdev/lingo.dev&type=Date)
188 | 
189 | ](https://www.star-history.com/#lingodotdev/lingo.dev&Date)
190 | 
191 | ## 🌐 Readme en otros idiomas
192 | 
193 | [English](https://github.com/lingodotdev/lingo.dev) • [中文](/readme/zh-Hans.md) • [日本語](/readme/ja.md) • [한국어](/readme/ko.md) • [Español](/readme/es.md) • [Français](/readme/fr.md) • [Русский](/readme/ru.md) • [Українська](/readme/uk-UA.md) • [Deutsch](/readme/de.md) • [Italiano](/readme/it.md) • [العربية](/readme/ar.md) • [עברית](/readme/he.md) • [हिन्दी](/readme/hi.md) • [বাংলা](/readme/bn.md) • [فارسی](/readme/fa.md)
194 | 
195 | ¿No ves tu idioma? ¡Agrégalo a [`i18n.json`](./i18n.json) y abre un PR!
196 | 
```

--------------------------------------------------------------------------------
/readme/fr.md:
--------------------------------------------------------------------------------

```markdown
  1 | <p align="center">
  2 |   <a href="https://lingo.dev">
  3 |     <img
  4 |       src="https://raw.githubusercontent.com/lingodotdev/lingo.dev/main/content/banner.compiler.png"
  5 |       width="100%"
  6 |       alt="Lingo.dev"
  7 |     />
  8 |   </a>
  9 | </p>
 10 | 
 11 | <p align="center">
 12 |   <strong>
 13 |     ⚡ Lingo.dev - boîte à outils i18n open-source, propulsée par l'IA pour une
 14 |     localisation instantanée avec les LLMs.
 15 |   </strong>
 16 | </p>
 17 | 
 18 | <br />
 19 | 
 20 | <p align="center">
 21 |   <a href="https://lingo.dev/compiler">Lingo.dev Compiler</a> •
 22 |   <a href="https://lingo.dev/cli">Lingo.dev CLI</a> •
 23 |   <a href="https://lingo.dev/ci">Lingo.dev CI/CD</a> •
 24 |   <a href="https://lingo.dev/sdk">Lingo.dev SDK</a>
 25 | </p>
 26 | 
 27 | <p align="center">
 28 |   <a href="https://github.com/lingodotdev/lingo.dev/actions/workflows/release.yml">
 29 |     <img
 30 |       src="https://github.com/lingodotdev/lingo.dev/actions/workflows/release.yml/badge.svg"
 31 |       alt="Publication"
 32 |     />
 33 |   </a>
 34 |   <a href="https://github.com/lingodotdev/lingo.dev/blob/main/LICENSE.md">
 35 |     <img
 36 |       src="https://img.shields.io/github/license/lingodotdev/lingo.dev"
 37 |       alt="Licence"
 38 |     />
 39 |   </a>
 40 |   <a href="https://github.com/lingodotdev/lingo.dev/commits/main">
 41 |     <img
 42 |       src="https://img.shields.io/github/last-commit/lingodotdev/lingo.dev"
 43 |       alt="Dernier commit"
 44 |     />
 45 |   </a>
 46 |   <a href="https://lingo.dev/en">
 47 |     <img
 48 |       src="https://img.shields.io/badge/Product%20Hunt-%231%20Product%20of%20the%20Day-orange?logo=producthunt&style=flat-square"
 49 |       alt="Product Hunt #1 Produit du jour"
 50 |     />
 51 |   </a>
 52 |   <a href="https://lingo.dev/en">
 53 |     <img
 54 |       src="https://img.shields.io/badge/GitHub-Trending-blue?logo=github&style=flat-square"
 55 |       alt="Tendance GitHub"
 56 |     />
 57 |   </a>
 58 | </p>
 59 | 
 60 | ---
 61 | 
 62 | ## Découvrez le Compiler 🆕
 63 | 
 64 | **Lingo.dev Compiler** est un middleware de compilation gratuit et open-source, conçu pour rendre n'importe quelle application React multilingue au moment de la compilation sans nécessiter de modifications des composants React existants.
 65 | 
 66 | Installez une seule fois :
 67 | 
 68 | ```bash
 69 | npm install lingo.dev
 70 | ```
 71 | 
 72 | Activez dans votre configuration de build :
 73 | 
 74 | ```js
 75 | import lingoCompiler from "lingo.dev/compiler";
 76 | 
 77 | const existingNextConfig = {};
 78 | 
 79 | export default lingoCompiler.next({
 80 |   sourceLocale: "en",
 81 |   targetLocales: ["es", "fr"],
 82 | })(existingNextConfig);
 83 | ```
 84 | 
 85 | Exécutez `next build` et regardez les bundles espagnols et français apparaître ✨
 86 | 
 87 | [Consultez la documentation →](https://lingo.dev/compiler) pour le guide complet, et [rejoignez notre Discord](https://lingo.dev/go/discord) pour obtenir de l'aide avec votre configuration.
 88 | 
 89 | ---
 90 | 
 91 | ### Que contient ce dépôt ?
 92 | 
 93 | | Outil        | En bref                                                                                     | Documentation                           |
 94 | | ------------ | ------------------------------------------------------------------------------------------- | --------------------------------------- |
 95 | | **Compiler** | Localisation React au moment de la compilation                                              | [/compiler](https://lingo.dev/compiler) |
 96 | | **CLI**      | Localisation en une commande pour applications web et mobiles, JSON, YAML, markdown, + plus | [/cli](https://lingo.dev/cli)           |
 97 | | **CI/CD**    | Auto-commit des traductions à chaque push + création de pull requests si nécessaire         | [/ci](https://lingo.dev/ci)             |
 98 | | **SDK**      | Traduction en temps réel pour le contenu généré par les utilisateurs                        | [/sdk](https://lingo.dev/sdk)           |
 99 | 
100 | Voici un aperçu rapide de chacun 👇
101 | 
102 | ---
103 | 
104 | ### ⚡️ Lingo.dev CLI
105 | 
106 | Traduisez le code et le contenu directement depuis votre terminal.
107 | 
108 | ```bash
109 | npx lingo.dev@latest run
110 | ```
111 | 
112 | Il crée une empreinte digitale pour chaque chaîne, met en cache les résultats et ne retraduit que ce qui a changé.
113 | 
114 | [Suivez la documentation →](https://lingo.dev/cli) pour apprendre comment le configurer.
115 | 
116 | ---
117 | 
118 | ### 🔄 Lingo.dev CI/CD
119 | 
120 | Livrez des traductions parfaites automatiquement.
121 | 
122 | ```yaml
123 | # .github/workflows/i18n.yml
124 | name: Lingo.dev i18n
125 | on: [push]
126 | 
127 | jobs:
128 |   i18n:
129 |     runs-on: ubuntu-latest
130 |     steps:
131 |       - uses: actions/checkout@v4
132 |       - uses: lingodotdev/lingo.dev@main
133 |         with:
134 |           api-key: ${{ secrets.LINGODOTDEV_API_KEY }}
135 | ```
136 | 
137 | Maintient votre dépôt à jour et votre produit multilingue sans étapes manuelles.
138 | 
139 | [Lisez la documentation →](https://lingo.dev/ci)
140 | 
141 | ---
142 | 
143 | ### 🧩 Lingo.dev SDK
144 | 
145 | Traduction instantanée par requête pour le contenu dynamique.
146 | 
147 | ```ts
148 | import { LingoDotDevEngine } from "lingo.dev/sdk";
149 | 
150 | const lingoDotDev = new LingoDotDevEngine({
151 |   apiKey: "your-api-key-here",
152 | });
153 | 
154 | const content = {
155 |   greeting: "Hello",
156 |   farewell: "Goodbye",
157 |   message: "Welcome to our platform",
158 | };
159 | 
160 | const translated = await lingoDotDev.localizeObject(content, {
161 |   sourceLocale: "en",
162 |   targetLocale: "es",
163 | });
164 | // Returns: { greeting: "Hola", farewell: "Adiós", message: "Bienvenido a nuestra plataforma" }
165 | ```
166 | 
167 | Parfait pour les discussions, les commentaires d'utilisateurs et autres flux en temps réel.
168 | 
169 | [Consulter la documentation →](https://lingo.dev/sdk)
170 | 
171 | ---
172 | 
173 | ## 🤝 Communauté
174 | 
175 | Nous sommes orientés communauté et adorons les contributions !
176 | 
177 | - Vous avez une idée ? [Ouvrez un ticket](https://github.com/lingodotdev/lingo.dev/issues)
178 | - Vous souhaitez corriger quelque chose ? [Envoyez une PR](https://github.com/lingodotdev/lingo.dev/pulls)
179 | - Besoin d'aide ? [Rejoignez notre Discord](https://lingo.dev/go/discord)
180 | 
181 | ## ⭐ Historique des étoiles
182 | 
183 | Si vous appréciez ce que nous faisons, donnez-nous une ⭐ et aidez-nous à atteindre 4 000 étoiles ! 🌟
184 | 
185 | [
186 | 
187 | ![Graphique d'historique des étoiles](https://api.star-history.com/svg?repos=lingodotdev/lingo.dev&type=Date)
188 | 
189 | ](https://www.star-history.com/#lingodotdev/lingo.dev&Date)
190 | 
191 | ## 🌐 Readme dans d'autres langues
192 | 
193 | [English](https://github.com/lingodotdev/lingo.dev) • [中文](/readme/zh-Hans.md) • [日本語](/readme/ja.md) • [한국어](/readme/ko.md) • [Español](/readme/es.md) • [Français](/readme/fr.md) • [Русский](/readme/ru.md) • [Українська](/readme/uk-UA.md) • [Deutsch](/readme/de.md) • [Italiano](/readme/it.md) • [العربية](/readme/ar.md) • [עברית](/readme/he.md) • [हिन्दी](/readme/hi.md) • [বাংলা](/readme/bn.md) • [فارسی](/readme/fa.md)
194 | 
195 | Vous ne voyez pas votre langue ? Ajoutez-la à [`i18n.json`](./i18n.json) et ouvrez une PR !
196 | 
```

--------------------------------------------------------------------------------
/packages/cli/src/cli/utils/settings.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import os from "os";
  2 | import path from "path";
  3 | import _ from "lodash";
  4 | import Z from "zod";
  5 | import fs from "fs";
  6 | import Ini from "ini";
  7 | 
  8 | export type CliSettings = Z.infer<typeof SettingsSchema>;
  9 | 
 10 | export function getSettings(explicitApiKey: string | undefined): CliSettings {
 11 |   const env = _loadEnv();
 12 |   const systemFile = _loadSystemFile();
 13 |   const defaults = _loadDefaults();
 14 | 
 15 |   _legacyEnvVarWarning();
 16 | 
 17 |   _envVarsInfo();
 18 | 
 19 |   return {
 20 |     auth: {
 21 |       apiKey:
 22 |         explicitApiKey ||
 23 |         env.LINGODOTDEV_API_KEY ||
 24 |         systemFile.auth?.apiKey ||
 25 |         defaults.auth.apiKey,
 26 |       apiUrl:
 27 |         env.LINGODOTDEV_API_URL ||
 28 |         systemFile.auth?.apiUrl ||
 29 |         defaults.auth.apiUrl,
 30 |       webUrl:
 31 |         env.LINGODOTDEV_WEB_URL ||
 32 |         systemFile.auth?.webUrl ||
 33 |         defaults.auth.webUrl,
 34 |     },
 35 |     llm: {
 36 |       openaiApiKey: env.OPENAI_API_KEY || systemFile.llm?.openaiApiKey,
 37 |       anthropicApiKey: env.ANTHROPIC_API_KEY || systemFile.llm?.anthropicApiKey,
 38 |       groqApiKey: env.GROQ_API_KEY || systemFile.llm?.groqApiKey,
 39 |       googleApiKey: env.GOOGLE_API_KEY || systemFile.llm?.googleApiKey,
 40 |       openrouterApiKey:
 41 |         env.OPENROUTER_API_KEY || systemFile.llm?.openrouterApiKey,
 42 |       mistralApiKey: env.MISTRAL_API_KEY || systemFile.llm?.mistralApiKey,
 43 |     },
 44 |   };
 45 | }
 46 | 
 47 | export function saveSettings(settings: CliSettings): void {
 48 |   _saveSystemFile(settings);
 49 | }
 50 | 
 51 | export function loadSystemSettings() {
 52 |   return _loadSystemFile();
 53 | }
 54 | 
 55 | const flattenZodObject = (schema: Z.ZodObject<any>, prefix = ""): string[] => {
 56 |   return Object.entries(schema.shape).flatMap(([key, value]) => {
 57 |     const newPrefix = prefix ? `${prefix}.${key}` : key;
 58 |     if (value instanceof Z.ZodObject) {
 59 |       return flattenZodObject(value, newPrefix);
 60 |     }
 61 |     return [newPrefix];
 62 |   });
 63 | };
 64 | 
 65 | const SettingsSchema = Z.object({
 66 |   auth: Z.object({
 67 |     apiKey: Z.string(),
 68 |     apiUrl: Z.string(),
 69 |     webUrl: Z.string(),
 70 |   }),
 71 |   llm: Z.object({
 72 |     openaiApiKey: Z.string().optional(),
 73 |     anthropicApiKey: Z.string().optional(),
 74 |     groqApiKey: Z.string().optional(),
 75 |     googleApiKey: Z.string().optional(),
 76 |     openrouterApiKey: Z.string().optional(),
 77 |     mistralApiKey: Z.string().optional(),
 78 |   }),
 79 | });
 80 | 
 81 | export const SETTINGS_KEYS = flattenZodObject(
 82 |   SettingsSchema,
 83 | ) as readonly string[];
 84 | 
 85 | // Private
 86 | 
 87 | function _loadDefaults(): CliSettings {
 88 |   return {
 89 |     auth: {
 90 |       apiKey: "",
 91 |       apiUrl: "https://engine.lingo.dev",
 92 |       webUrl: "https://lingo.dev",
 93 |     },
 94 |     llm: {},
 95 |   };
 96 | }
 97 | 
 98 | function _loadEnv() {
 99 |   return Z.object({
100 |     LINGODOTDEV_API_KEY: Z.string().optional(),
101 |     LINGODOTDEV_API_URL: Z.string().optional(),
102 |     LINGODOTDEV_WEB_URL: Z.string().optional(),
103 |     OPENAI_API_KEY: Z.string().optional(),
104 |     ANTHROPIC_API_KEY: Z.string().optional(),
105 |     GROQ_API_KEY: Z.string().optional(),
106 |     GOOGLE_API_KEY: Z.string().optional(),
107 |     OPENROUTER_API_KEY: Z.string().optional(),
108 |     MISTRAL_API_KEY: Z.string().optional(),
109 |   })
110 |     .passthrough()
111 |     .parse(process.env);
112 | }
113 | 
114 | function _loadSystemFile() {
115 |   const settingsFilePath = _getSettingsFilePath();
116 |   const content = fs.existsSync(settingsFilePath)
117 |     ? fs.readFileSync(settingsFilePath, "utf-8")
118 |     : "";
119 |   const data = Ini.parse(content);
120 | 
121 |   return Z.object({
122 |     auth: Z.object({
123 |       apiKey: Z.string().optional(),
124 |       apiUrl: Z.string().optional(),
125 |       webUrl: Z.string().optional(),
126 |     }).optional(),
127 |     llm: Z.object({
128 |       openaiApiKey: Z.string().optional(),
129 |       anthropicApiKey: Z.string().optional(),
130 |       groqApiKey: Z.string().optional(),
131 |       googleApiKey: Z.string().optional(),
132 |       openrouterApiKey: Z.string().optional(),
133 |       mistralApiKey: Z.string().optional(),
134 |     }).optional(),
135 |   })
136 |     .passthrough()
137 |     .parse(data);
138 | }
139 | 
140 | function _saveSystemFile(settings: CliSettings) {
141 |   const settingsFilePath = _getSettingsFilePath();
142 |   const content = Ini.stringify(settings);
143 |   fs.writeFileSync(settingsFilePath, content);
144 | }
145 | 
146 | function _getSettingsFilePath(): string {
147 |   const settingsFile = ".lingodotdevrc";
148 |   const homedir = os.homedir();
149 |   const settingsFilePath = path.join(homedir, settingsFile);
150 |   return settingsFilePath;
151 | }
152 | 
153 | function _legacyEnvVarWarning() {
154 |   const env = _loadEnv();
155 | 
156 |   if (env.REPLEXICA_API_KEY && !env.LINGODOTDEV_API_KEY) {
157 |     console.warn(
158 |       "\x1b[33m%s\x1b[0m",
159 |       `
160 | ⚠️  WARNING: REPLEXICA_API_KEY env var is deprecated ⚠️
161 | ===========================================================
162 | 
163 | Please use LINGODOTDEV_API_KEY instead.
164 | ===========================================================
165 | `,
166 |     );
167 |   }
168 | }
169 | 
170 | function _envVarsInfo() {
171 |   const env = _loadEnv();
172 |   const systemFile = _loadSystemFile();
173 | 
174 |   if (env.LINGODOTDEV_API_KEY && systemFile.auth?.apiKey) {
175 |     console.info(
176 |       "\x1b[36m%s\x1b[0m",
177 |       `ℹ️  Using LINGODOTDEV_API_KEY env var instead of credentials from user config`,
178 |     );
179 |   }
180 |   if (env.OPENAI_API_KEY && systemFile.llm?.openaiApiKey) {
181 |     console.info(
182 |       "\x1b[36m%s\x1b[0m",
183 |       `ℹ️  Using OPENAI_API_KEY env var instead of key from user config.`,
184 |     );
185 |   }
186 |   if (env.ANTHROPIC_API_KEY && systemFile.llm?.anthropicApiKey) {
187 |     console.info(
188 |       "\x1b[36m%s\x1b[0m",
189 |       `ℹ️  Using ANTHROPIC_API_KEY env var instead of key from user config`,
190 |     );
191 |   }
192 |   if (env.GROQ_API_KEY && systemFile.llm?.groqApiKey) {
193 |     console.info(
194 |       "\x1b[36m%s\x1b[0m",
195 |       `ℹ️  Using GROQ_API_KEY env var instead of key from user config`,
196 |     );
197 |   }
198 |   if (env.GOOGLE_API_KEY && systemFile.llm?.googleApiKey) {
199 |     console.info(
200 |       "\x1b[36m%s\x1b[0m",
201 |       `ℹ️  Using GOOGLE_API_KEY env var instead of key from user config`,
202 |     );
203 |   }
204 |   if (env.OPENROUTER_API_KEY && systemFile.llm?.openrouterApiKey) {
205 |     console.info(
206 |       "\x1b[36m%s\x1b[0m",
207 |       `ℹ️  Using OPENROUTER_API_KEY env var instead of key from user config`,
208 |     );
209 |   }
210 |   if (env.MISTRAL_API_KEY && systemFile.llm?.mistralApiKey) {
211 |     console.info(
212 |       "\x1b[36m%s\x1b[0m",
213 |       `ℹ️  Using MISTRAL_API_KEY env var instead of key from user config`,
214 |     );
215 |   }
216 |   if (env.LINGODOTDEV_API_URL) {
217 |     console.info(
218 |       "\x1b[36m%s\x1b[0m",
219 |       `ℹ️  Using LINGODOTDEV_API_URL: ${env.LINGODOTDEV_API_URL}`,
220 |     );
221 |   }
222 |   if (env.LINGODOTDEV_WEB_URL) {
223 |     console.info(
224 |       "\x1b[36m%s\x1b[0m",
225 |       `ℹ️  Using LINGODOTDEV_WEB_URL: ${env.LINGODOTDEV_WEB_URL}`,
226 |     );
227 |   }
228 | }
229 | 
```

--------------------------------------------------------------------------------
/packages/cli/src/cli/utils/errors.ts:
--------------------------------------------------------------------------------

```typescript
  1 | export const docLinks = {
  2 |   i18nNotFound: "https://lingo.dev/cli",
  3 |   bucketNotFound: "https://lingo.dev/cli",
  4 |   authError: "https://lingo.dev/cli",
  5 |   localeTargetNotFound: "https://lingo.dev/cli",
  6 |   lockFiletNotFound: "https://lingo.dev/cli",
  7 |   failedReplexicaEngine: "https://lingo.dev/cli",
  8 |   placeHolderFailed: "https://lingo.dev/cli",
  9 |   translationFailed: "https://lingo.dev/cli",
 10 |   connectionFailed: "https://lingo.dev/cli",
 11 |   invalidType: "https://lingo.dev/cli",
 12 |   invalidPathPattern: "https://lingo.dev/cli",
 13 |   androidResouceError: "https://lingo.dev/cli",
 14 |   invalidBucketType: "https://lingo.dev/cli",
 15 |   invalidStringDict: "https://lingo.dev/cli",
 16 | };
 17 | 
 18 | type DocLinkKeys = keyof typeof docLinks;
 19 | 
 20 | export class CLIError extends Error {
 21 |   public readonly docUrl: string;
 22 |   public readonly errorType: string = "cli_error";
 23 | 
 24 |   constructor({ message, docUrl }: { message: string; docUrl: DocLinkKeys }) {
 25 |     super(message);
 26 |     this.docUrl = docLinks[docUrl];
 27 |     this.message = `${this.message}\n visit: ${this.docUrl}`;
 28 |   }
 29 | }
 30 | 
 31 | export class ConfigError extends CLIError {
 32 |   public readonly errorType = "config_error";
 33 | 
 34 |   constructor({ message, docUrl }: { message: string; docUrl: DocLinkKeys }) {
 35 |     super({ message, docUrl });
 36 |     this.name = "ConfigError";
 37 |   }
 38 | }
 39 | 
 40 | export class AuthenticationError extends CLIError {
 41 |   public readonly errorType = "auth_error";
 42 | 
 43 |   constructor({ message, docUrl }: { message: string; docUrl: DocLinkKeys }) {
 44 |     super({ message, docUrl });
 45 |     this.name = "AuthenticationError";
 46 |   }
 47 | }
 48 | 
 49 | export class ValidationError extends CLIError {
 50 |   public readonly errorType = "validation_error";
 51 | 
 52 |   constructor({ message, docUrl }: { message: string; docUrl: DocLinkKeys }) {
 53 |     super({ message, docUrl });
 54 |     this.name = "ValidationError";
 55 |   }
 56 | }
 57 | 
 58 | export class LocalizationError extends Error {
 59 |   public readonly errorType = "locale_error";
 60 |   public readonly bucket?: string;
 61 |   public readonly sourceLocale?: string;
 62 |   public readonly targetLocale?: string;
 63 |   public readonly pathPattern?: string;
 64 | 
 65 |   constructor(
 66 |     message: string,
 67 |     context?: {
 68 |       bucket?: string;
 69 |       sourceLocale?: string;
 70 |       targetLocale?: string;
 71 |       pathPattern?: string;
 72 |     },
 73 |   ) {
 74 |     super(message);
 75 |     this.name = "LocalizationError";
 76 |     this.bucket = context?.bucket;
 77 |     this.sourceLocale = context?.sourceLocale;
 78 |     this.targetLocale = context?.targetLocale;
 79 |     this.pathPattern = context?.pathPattern;
 80 |   }
 81 | }
 82 | 
 83 | export class BucketProcessingError extends Error {
 84 |   public readonly errorType = "bucket_error";
 85 |   public readonly bucket: string;
 86 | 
 87 |   constructor(message: string, bucket: string) {
 88 |     super(message);
 89 |     this.name = "BucketProcessingError";
 90 |     this.bucket = bucket;
 91 |   }
 92 | }
 93 | 
 94 | // Type guard functions for robust error detection
 95 | export function isConfigError(error: any): error is ConfigError {
 96 |   return error instanceof ConfigError || error.errorType === "config_error";
 97 | }
 98 | 
 99 | export function isAuthenticationError(
100 |   error: any,
101 | ): error is AuthenticationError {
102 |   return (
103 |     error instanceof AuthenticationError || error.errorType === "auth_error"
104 |   );
105 | }
106 | 
107 | export function isValidationError(error: any): error is ValidationError {
108 |   return (
109 |     error instanceof ValidationError || error.errorType === "validation_error"
110 |   );
111 | }
112 | 
113 | export function isLocalizationError(error: any): error is LocalizationError {
114 |   return (
115 |     error instanceof LocalizationError || error.errorType === "locale_error"
116 |   );
117 | }
118 | 
119 | export function isBucketProcessingError(
120 |   error: any,
121 | ): error is BucketProcessingError {
122 |   return (
123 |     error instanceof BucketProcessingError || error.errorType === "bucket_error"
124 |   );
125 | }
126 | 
127 | export function getCLIErrorType(error: any): string {
128 |   if (isConfigError(error)) return "config_error";
129 |   if (isAuthenticationError(error)) return "auth_error";
130 |   if (isValidationError(error)) return "validation_error";
131 |   if (isLocalizationError(error)) return "locale_error";
132 |   if (isBucketProcessingError(error)) return "bucket_error";
133 |   if (error instanceof CLIError) return "cli_error";
134 |   return "unknown_error";
135 | }
136 | 
137 | // Error detail interface for consistent tracking
138 | export interface ErrorDetail {
139 |   type:
140 |     | "bucket_error"
141 |     | "locale_error"
142 |     | "validation_error"
143 |     | "auth_error"
144 |     | "config_error";
145 |   bucket?: string;
146 |   locale?: string;
147 |   pathPattern?: string;
148 |   message: string;
149 |   stack?: string;
150 | }
151 | 
152 | // Utility to create previous error context for fatal errors
153 | export function createPreviousErrorContext(errorDetails: ErrorDetail[]) {
154 |   if (errorDetails.length === 0) return undefined;
155 | 
156 |   return {
157 |     count: errorDetails.length,
158 |     types: [...new Set(errorDetails.map((e) => e.type))],
159 |     buckets: [...new Set(errorDetails.map((e) => e.bucket).filter(Boolean))],
160 |   };
161 | }
162 | 
163 | // Utility to create aggregated error analytics
164 | export function aggregateErrorAnalytics(
165 |   errorDetails: ErrorDetail[],
166 |   buckets: any[],
167 |   targetLocales: string[],
168 |   i18nConfig: any,
169 | ) {
170 |   if (errorDetails.length === 0) {
171 |     return {
172 |       errorCount: 0,
173 |       errorTypes: [],
174 |       errorsByBucket: {},
175 |       errorsByType: {},
176 |       firstError: undefined,
177 |       bucketCount: buckets.length,
178 |       localeCount: targetLocales.length,
179 |       i18nConfig: {
180 |         sourceLocale: i18nConfig.locale.source,
181 |         targetLocales: i18nConfig.locale.targets,
182 |         bucketTypes: Object.keys(i18nConfig.buckets),
183 |       },
184 |     };
185 |   }
186 | 
187 |   const errorsByBucket = errorDetails.reduce(
188 |     (acc, error) => {
189 |       if (error.bucket) {
190 |         acc[error.bucket] = (acc[error.bucket] || 0) + 1;
191 |       }
192 |       return acc;
193 |     },
194 |     {} as Record<string, number>,
195 |   );
196 | 
197 |   const errorsByType = errorDetails.reduce(
198 |     (acc, error) => {
199 |       acc[error.type] = (acc[error.type] || 0) + 1;
200 |       return acc;
201 |     },
202 |     {} as Record<string, number>,
203 |   );
204 | 
205 |   return {
206 |     errorCount: errorDetails.length,
207 |     errorTypes: [...new Set(errorDetails.map((e) => e.type))],
208 |     errorsByBucket,
209 |     errorsByType,
210 |     firstError: {
211 |       type: errorDetails[0].type,
212 |       bucket: errorDetails[0].bucket,
213 |       locale: errorDetails[0].locale,
214 |       pathPattern: errorDetails[0].pathPattern,
215 |       message: errorDetails[0].message,
216 |     },
217 |     bucketCount: buckets.length,
218 |     localeCount: targetLocales.length,
219 |     i18nConfig: {
220 |       sourceLocale: i18nConfig.locale.source,
221 |       targetLocales: i18nConfig.locale.targets,
222 |       bucketTypes: Object.keys(i18nConfig.buckets),
223 |     },
224 |   };
225 | }
226 | 
```

--------------------------------------------------------------------------------
/packages/cli/src/cli/loaders/flat.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { flatten, unflatten } from "flat";
  2 | import { ILoader } from "./_types";
  3 | import { composeLoaders, createLoader } from "./_utils";
  4 | import _ from "lodash";
  5 | 
  6 | export const OBJECT_NUMERIC_KEY_PREFIX = "__lingodotdev__obj__";
  7 | 
  8 | /**
  9 |  * Options for configuring the flat loader behavior
 10 |  */
 11 | export interface FlatLoaderOptions {
 12 |   /**
 13 |    * Optional predicate to determine if an object should be preserved (not flattened)
 14 |    * Use this to prevent flattening of special objects like ICU plurals
 15 |    */
 16 |   shouldPreserveObject?: (value: any) => boolean;
 17 | }
 18 | 
 19 | /**
 20 |  * Creates a flat loader that flattens nested objects into dot-notation keys
 21 |  *
 22 |  * @param options - Configuration options for the loader
 23 |  * @param options.shouldPreserveObject - Predicate to identify objects that should not be flattened
 24 |  */
 25 | export default function createFlatLoader(options?: FlatLoaderOptions) {
 26 |   const composedLoader = composeLoaders(
 27 |     createDenormalizeLoader(options),
 28 |     createNormalizeLoader(),
 29 |   );
 30 | 
 31 |   return {
 32 |     ...composedLoader,
 33 |     pullHints: async (input: Record<string, any>) => {
 34 |       if (!input || typeof input !== "object") {
 35 |         return {};
 36 |       }
 37 |       return flattenHints(input);
 38 |     },
 39 |   };
 40 | }
 41 | 
 42 | type DenormalizeResult = {
 43 |   denormalized: Record<string, string>;
 44 |   keysMap: Record<string, string>;
 45 | };
 46 | 
 47 | function createDenormalizeLoader(
 48 |   options?: FlatLoaderOptions,
 49 | ): ILoader<Record<string, any>, DenormalizeResult> {
 50 |   return createLoader({
 51 |     pull: async (locale, input) => {
 52 |       const inputDenormalized = denormalizeObjectKeys(input || {});
 53 | 
 54 |       // First pass: extract preserved objects before flattening (if predicate provided)
 55 |       const preservedObjects: Record<string, any> = {};
 56 |       const nonPreservedInput: Record<string, any> = {};
 57 | 
 58 |       for (const [key, value] of Object.entries(inputDenormalized)) {
 59 |         if (options?.shouldPreserveObject?.(value)) {
 60 |           preservedObjects[key] = value;
 61 |         } else {
 62 |           nonPreservedInput[key] = value;
 63 |         }
 64 |       }
 65 | 
 66 |       // Flatten only non-preserved objects
 67 |       const flattened: Record<string, string> = flatten(nonPreservedInput, {
 68 |         delimiter: "/",
 69 |         transformKey(key) {
 70 |           return encodeURIComponent(String(key));
 71 |         },
 72 |       });
 73 | 
 74 |       // Merge preserved objects back (they stay as objects, not flattened)
 75 |       // BUT: encode their keys too!
 76 |       const denormalized: Record<string, any> = { ...flattened };
 77 | 
 78 |       for (const [key, value] of Object.entries(preservedObjects)) {
 79 |         const encodedKey = encodeURIComponent(String(key));
 80 |         denormalized[encodedKey] = value;
 81 |       }
 82 | 
 83 |       const keysMap = buildDenormalizedKeysMap(denormalized);
 84 |       return { denormalized, keysMap };
 85 |     },
 86 |     push: async (locale, { denormalized }) => {
 87 |       const normalized = normalizeObjectKeys(denormalized);
 88 |       return normalized;
 89 |     },
 90 |   });
 91 | }
 92 | 
 93 | function createNormalizeLoader(): ILoader<
 94 |   DenormalizeResult,
 95 |   Record<string, string>
 96 | > {
 97 |   return createLoader({
 98 |     pull: async (locale, input) => {
 99 |       const normalized = normalizeObjectKeys(input.denormalized);
100 |       return normalized;
101 |     },
102 |     push: async (locale, data, originalInput) => {
103 |       const keysMap = originalInput?.keysMap ?? {};
104 |       const input = mapDenormalizedKeys(data, keysMap);
105 |       const denormalized: Record<string, any> = unflatten(input, {
106 |         delimiter: "/",
107 |         transformKey(key) {
108 |           return decodeURIComponent(String(key));
109 |         },
110 |       });
111 |       return { denormalized, keysMap: keysMap || {} };
112 |     },
113 |   });
114 | }
115 | 
116 | export function buildDenormalizedKeysMap(obj: Record<string, string>) {
117 |   if (!obj) return {};
118 | 
119 |   return Object.keys(obj).reduce(
120 |     (acc, key) => {
121 |       if (key) {
122 |         const normalizedKey = `${key}`.replace(OBJECT_NUMERIC_KEY_PREFIX, "");
123 |         acc[normalizedKey] = key;
124 |       }
125 |       return acc;
126 |     },
127 |     {} as Record<string, string>,
128 |   );
129 | }
130 | 
131 | export function mapDenormalizedKeys(
132 |   obj: Record<string, any>,
133 |   denormalizedKeysMap: Record<string, string>,
134 | ) {
135 |   return Object.keys(obj).reduce(
136 |     (acc, key) => {
137 |       const denormalizedKey = denormalizedKeysMap[key] ?? key;
138 |       acc[denormalizedKey] = obj[key];
139 |       return acc;
140 |     },
141 |     {} as Record<string, string>,
142 |   );
143 | }
144 | 
145 | export function denormalizeObjectKeys(
146 |   obj: Record<string, any>,
147 | ): Record<string, any> {
148 |   if (_.isObject(obj) && !_.isArray(obj)) {
149 |     return _.transform(
150 |       obj,
151 |       (result, value, key) => {
152 |         const newKey = !isNaN(Number(key))
153 |           ? `${OBJECT_NUMERIC_KEY_PREFIX}${key}`
154 |           : key;
155 |         result[newKey] =
156 |           _.isObject(value) && !_.isDate(value)
157 |             ? denormalizeObjectKeys(value)
158 |             : value;
159 |       },
160 |       {} as Record<string, any>,
161 |     );
162 |   } else {
163 |     return obj;
164 |   }
165 | }
166 | 
167 | export function normalizeObjectKeys(
168 |   obj: Record<string, any>,
169 | ): Record<string, any> {
170 |   if (_.isObject(obj) && !_.isArray(obj)) {
171 |     return _.transform(
172 |       obj,
173 |       (result, value, key) => {
174 |         const newKey = `${key}`.replace(OBJECT_NUMERIC_KEY_PREFIX, "");
175 |         result[newKey] =
176 |           _.isObject(value) && !_.isDate(value)
177 |             ? normalizeObjectKeys(value)
178 |             : value;
179 |       },
180 |       {} as Record<string, any>,
181 |     );
182 |   } else {
183 |     return obj;
184 |   }
185 | }
186 | 
187 | function flattenHints(
188 |   obj: Record<string, any>,
189 |   parentHints: string[] = [],
190 |   parentPath: string = "",
191 | ): Record<string, string[]> {
192 |   const result: Record<string, string[]> = {};
193 | 
194 |   for (const [key, _value] of Object.entries(obj)) {
195 |     if (_.isObject(_value) && !_.isArray(_value)) {
196 |       const value = _value as Record<string, any>;
197 |       const currentHints = [...parentHints];
198 |       const currentPath = parentPath ? `${parentPath}/${key}` : key;
199 | 
200 |       // Add this level's hint if it exists
201 |       if (value.hint && typeof value.hint === "string") {
202 |         currentHints.push(value.hint);
203 |       }
204 | 
205 |       // Process nested objects (excluding the hint property)
206 |       const nestedObj = _.omit(value, "hint");
207 | 
208 |       // If this is a leaf node (no nested objects), add to result
209 |       if (Object.keys(nestedObj).length === 0) {
210 |         if (currentHints.length > 0) {
211 |           result[currentPath] = currentHints;
212 |         }
213 |       } else {
214 |         // Recursively process nested objects
215 |         const nestedComments = flattenHints(
216 |           nestedObj,
217 |           currentHints,
218 |           currentPath,
219 |         );
220 |         Object.assign(result, nestedComments);
221 |       }
222 |     }
223 |   }
224 | 
225 |   return result;
226 | }
227 | 
```

--------------------------------------------------------------------------------
/packages/compiler/src/utils/index.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import traverse from "@babel/traverse";
  2 | import * as t from "@babel/types";
  3 | import _ from "lodash";
  4 | 
  5 | import { NodePath } from "@babel/traverse";
  6 | import { getJsxAttributeValue, setJsxAttributeValue } from "./jsx-attribute";
  7 | 
  8 | // "root" is a JSXElement node that is the root of the JSX tree,
  9 | // meaning it doesn't have JSXElement nodes among its ancestors.
 10 | export function getJsxRoots(node: t.Node) {
 11 |   const result: NodePath<t.JSXElement>[] = [];
 12 | 
 13 |   // skip traversing the node if it's a root node
 14 |   traverse(node, {
 15 |     JSXElement(path) {
 16 |       result.push(path);
 17 |       path.skip();
 18 |     },
 19 |   });
 20 | 
 21 |   return result;
 22 | }
 23 | 
 24 | export function isGoodJsxText(path: NodePath<t.JSXText>) {
 25 |   return path.node.value?.trim() !== "";
 26 | }
 27 | 
 28 | export function getOrCreateImport(
 29 |   ast: t.Node,
 30 |   params: {
 31 |     exportedName: string;
 32 |     moduleName: string[];
 33 |   },
 34 | ): { importedName: string } {
 35 |   let importedName = params.exportedName;
 36 |   let existingImport = findExistingImport(
 37 |     ast,
 38 |     params.exportedName,
 39 |     params.moduleName,
 40 |   );
 41 | 
 42 |   if (existingImport) {
 43 |     return { importedName: existingImport };
 44 |   }
 45 | 
 46 |   // Find a unique import name if needed
 47 |   importedName = generateUniqueImportName(ast, params.exportedName);
 48 | 
 49 |   // Create the import declaration
 50 |   createImportDeclaration(
 51 |     ast,
 52 |     importedName,
 53 |     params.exportedName,
 54 |     params.moduleName,
 55 |   );
 56 | 
 57 |   return { importedName };
 58 | }
 59 | 
 60 | function findExistingImport(
 61 |   ast: t.Node,
 62 |   exportedName: string,
 63 |   moduleName: string[],
 64 | ): string | null {
 65 |   let result: string | null = null;
 66 | 
 67 |   traverse(ast, {
 68 |     ImportDeclaration(path) {
 69 |       if (!moduleName.includes(path.node.source.value)) {
 70 |         return;
 71 |       }
 72 | 
 73 |       // Skip type-only imports as they can't be used at runtime
 74 |       if (path.node.importKind === "type") {
 75 |         return;
 76 |       }
 77 | 
 78 |       for (const specifier of path.node.specifiers) {
 79 |         if (
 80 |           t.isImportSpecifier(specifier) &&
 81 |           // Skip type-only specifiers as they can't be used at runtime
 82 |           specifier.importKind !== "type" &&
 83 |           ((t.isIdentifier(specifier.imported) &&
 84 |             specifier.imported.name === exportedName) ||
 85 |             (specifier.importKind === "value" &&
 86 |               t.isIdentifier(specifier.local) &&
 87 |               specifier.local.name === exportedName))
 88 |         ) {
 89 |           result = specifier.local.name;
 90 |           path.stop();
 91 |           return;
 92 |         }
 93 | 
 94 |         // Handle default imports (import React from "react")
 95 |         if (t.isImportDefaultSpecifier(specifier)) {
 96 |           // For default imports, we can access the exported name via the default import
 97 |           // e.g., React.Fragment when we have "import React from 'react'"
 98 |           result = `${specifier.local.name}.${exportedName}`;
 99 |           path.stop();
100 |           return;
101 |         }
102 | 
103 |         // Handle namespace imports (import * as React from "react")
104 |         if (t.isImportNamespaceSpecifier(specifier)) {
105 |           // For namespace imports, we can access the exported name via the namespace
106 |           // e.g., React.Fragment when we have "import * as React from 'react'"
107 |           result = `${specifier.local.name}.${exportedName}`;
108 |           path.stop();
109 |           return;
110 |         }
111 |       }
112 |     },
113 |   });
114 | 
115 |   return result;
116 | }
117 | 
118 | function generateUniqueImportName(ast: t.Node, baseName: string): string {
119 |   const usedNames = new Set<string>();
120 | 
121 |   // Collect all identifiers in scope
122 |   traverse(ast, {
123 |     Identifier(path) {
124 |       usedNames.add(path.node.name);
125 |     },
126 |   });
127 | 
128 |   // If the base name is available, use it
129 |   if (!usedNames.has(baseName)) {
130 |     return baseName;
131 |   }
132 | 
133 |   // Otherwise, append a number until we find an unused name
134 |   let counter = 1;
135 |   let candidateName = `${baseName}${counter}`;
136 | 
137 |   while (usedNames.has(candidateName)) {
138 |     counter++;
139 |     candidateName = `${baseName}${counter}`;
140 |   }
141 | 
142 |   return candidateName;
143 | }
144 | 
145 | function createImportDeclaration(
146 |   ast: t.Node,
147 |   localName: string,
148 |   exportedName: string,
149 |   moduleName: string[],
150 | ): void {
151 |   traverse(ast, {
152 |     Program(path) {
153 |       // Create the import specifier
154 |       const importSpecifier = t.importSpecifier(
155 |         t.identifier(localName),
156 |         t.identifier(exportedName),
157 |       );
158 | 
159 |       // Check if we already have a non-type import from this module
160 |       const existingImport = path
161 |         .get("body")
162 |         .find(
163 |           (nodePath) =>
164 |             t.isImportDeclaration(nodePath.node) &&
165 |             moduleName.includes(nodePath.node.source.value) &&
166 |             nodePath.node.importKind !== "type",
167 |         );
168 | 
169 |       if (existingImport && t.isImportDeclaration(existingImport.node)) {
170 |         // Add to existing import declaration
171 |         existingImport.node.specifiers.push(importSpecifier);
172 |       } else {
173 |         // Create a new import declaration
174 |         const importDeclaration = t.importDeclaration(
175 |           [importSpecifier],
176 |           t.stringLiteral(moduleName[0]),
177 |         );
178 | 
179 |         // Add it at the top of the file, after any existing imports
180 |         const lastImportIndex = findLastImportIndex(path);
181 |         path.node.body.splice(lastImportIndex + 1, 0, importDeclaration);
182 |       }
183 | 
184 |       path.stop();
185 |     },
186 |   });
187 | }
188 | 
189 | function findLastImportIndex(programPath: NodePath<t.Program>): number {
190 |   const body = programPath.node.body;
191 | 
192 |   for (let i = body.length - 1; i >= 0; i--) {
193 |     if (t.isImportDeclaration(body[i])) {
194 |       return i;
195 |     }
196 |   }
197 | 
198 |   return -1;
199 | }
200 | 
201 | function _hasFileDirective(ast: t.Node, directiveValue: string): boolean {
202 |   let hasDirective = false;
203 | 
204 |   traverse(ast, {
205 |     Directive(path) {
206 |       if (path.node.value.value === directiveValue) {
207 |         hasDirective = true;
208 |         path.stop(); // Stop traversal as soon as we find the directive
209 |       }
210 |     },
211 |   });
212 | 
213 |   return hasDirective;
214 | }
215 | 
216 | export function hasI18nDirective(ast: t.Node): boolean {
217 |   return _hasFileDirective(ast, "use i18n");
218 | }
219 | 
220 | export function hasClientDirective(ast: t.Node): boolean {
221 |   return _hasFileDirective(ast, "use client");
222 | }
223 | 
224 | export function hasServerDirective(ast: t.Node): boolean {
225 |   return _hasFileDirective(ast, "use server");
226 | }
227 | 
228 | export function getModuleExecutionMode(
229 |   ast: t.Node,
230 |   rscEnabled: boolean,
231 | ): "client" | "server" {
232 |   // if rscEnabled is true, then server mode is the default
233 |   // if rscEnabled is false, then client mode is the default
234 |   // default mode is when there is no directive
235 | 
236 |   if (rscEnabled) {
237 |     if (hasClientDirective(ast)) {
238 |       return "client";
239 |     } else {
240 |       return "server";
241 |     }
242 |   } else {
243 |     return "client";
244 |   }
245 | }
246 | 
247 | export { getJsxAttributeValue, setJsxAttributeValue };
248 | 
```

--------------------------------------------------------------------------------
/packages/cli/src/cli/cmd/run/frozen.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import chalk from "chalk";
  2 | import { Listr } from "listr2";
  3 | import _ from "lodash";
  4 | import { minimatch } from "minimatch";
  5 | 
  6 | import { colors } from "../../constants";
  7 | import { CmdRunContext } from "./_types";
  8 | import { commonTaskRendererOptions } from "./_const";
  9 | import { getBuckets } from "../../utils/buckets";
 10 | import createBucketLoader from "../../loaders";
 11 | import { createDeltaProcessor } from "../../utils/delta";
 12 | import { resolveOverriddenLocale } from "@lingo.dev/_spec";
 13 | 
 14 | export default async function frozen(input: CmdRunContext) {
 15 |   console.log(chalk.hex(colors.orange)("[Frozen]"));
 16 | 
 17 |   // Prepare filtered buckets consistently with the planning step
 18 |   let buckets = getBuckets(input.config!);
 19 |   if (input.flags.bucket?.length) {
 20 |     buckets = buckets.filter((b) => input.flags.bucket!.includes(b.type));
 21 |   }
 22 | 
 23 |   if (input.flags.file?.length) {
 24 |     buckets = buckets
 25 |       .map((bucket: any) => {
 26 |         const paths = bucket.paths.filter((p: any) =>
 27 |           input.flags.file!.some(
 28 |             (f) => p.pathPattern.includes(f) || minimatch(p.pathPattern, f),
 29 |           ),
 30 |         );
 31 |         return { ...bucket, paths };
 32 |       })
 33 |       .filter((bucket: any) => bucket.paths.length > 0);
 34 |   }
 35 | 
 36 |   const _sourceLocale = input.flags.sourceLocale || input.config!.locale.source;
 37 |   const _targetLocales =
 38 |     input.flags.targetLocale || input.config!.locale.targets;
 39 | 
 40 |   return new Listr<CmdRunContext>(
 41 |     [
 42 |       {
 43 |         title: "Setting up localization cache",
 44 |         task: async (_ctx, task) => {
 45 |           const checkLockfileProcessor = createDeltaProcessor("");
 46 |           const lockfileExists =
 47 |             await checkLockfileProcessor.checkIfLockExists();
 48 |           if (!lockfileExists) {
 49 |             for (const bucket of buckets) {
 50 |               for (const bucketPath of bucket.paths) {
 51 |                 const resolvedSourceLocale = resolveOverriddenLocale(
 52 |                   _sourceLocale,
 53 |                   bucketPath.delimiter,
 54 |                 );
 55 | 
 56 |                 const loader = createBucketLoader(
 57 |                   bucket.type,
 58 |                   bucketPath.pathPattern,
 59 |                   {
 60 |                     defaultLocale: resolvedSourceLocale,
 61 |                     injectLocale: bucket.injectLocale,
 62 |                     formatter: input.config!.formatter,
 63 |                   },
 64 |                   bucket.lockedKeys,
 65 |                   bucket.lockedPatterns,
 66 |                   bucket.ignoredKeys,
 67 |                 );
 68 |                 loader.setDefaultLocale(resolvedSourceLocale);
 69 |                 await loader.init();
 70 | 
 71 |                 const sourceData = await loader.pull(_sourceLocale);
 72 | 
 73 |                 const delta = createDeltaProcessor(bucketPath.pathPattern);
 74 |                 const checksums = await delta.createChecksums(sourceData);
 75 |                 await delta.saveChecksums(checksums);
 76 |               }
 77 |             }
 78 |             task.title = "Localization cache initialized";
 79 |           } else {
 80 |             task.title = "Localization cache loaded";
 81 |           }
 82 |         },
 83 |       },
 84 |       {
 85 |         title: "Validating frozen state",
 86 |         enabled: () => !!input.flags.frozen,
 87 |         task: async (_ctx, task) => {
 88 |           for (const bucket of buckets) {
 89 |             for (const bucketPath of bucket.paths) {
 90 |               const resolvedSourceLocale = resolveOverriddenLocale(
 91 |                 _sourceLocale,
 92 |                 bucketPath.delimiter,
 93 |               );
 94 | 
 95 |               const loader = createBucketLoader(
 96 |                 bucket.type,
 97 |                 bucketPath.pathPattern,
 98 |                 {
 99 |                   defaultLocale: resolvedSourceLocale,
100 |                   returnUnlocalizedKeys: true,
101 |                   injectLocale: bucket.injectLocale,
102 |                 },
103 |                 bucket.lockedKeys,
104 |                 bucket.lockedPatterns,
105 |                 bucket.ignoredKeys,
106 |               );
107 |               loader.setDefaultLocale(resolvedSourceLocale);
108 |               await loader.init();
109 | 
110 |               const { unlocalizable: srcUnlocalizable, ...src } =
111 |                 await loader.pull(_sourceLocale);
112 | 
113 |               const delta = createDeltaProcessor(bucketPath.pathPattern);
114 |               const sourceChecksums = await delta.createChecksums(src);
115 |               const savedChecksums = await delta.loadChecksums();
116 | 
117 |               const updatedSourceData = _.pickBy(
118 |                 src,
119 |                 (value, key) => sourceChecksums[key] !== savedChecksums[key],
120 |               );
121 |               if (Object.keys(updatedSourceData).length > 0) {
122 |                 throw new Error(
123 |                   `Localization data has changed; please update i18n.lock or run without --frozen. Details: Source file has been updated.`,
124 |                 );
125 |               }
126 | 
127 |               for (const _tgt of _targetLocales) {
128 |                 const resolvedTargetLocale = resolveOverriddenLocale(
129 |                   _tgt,
130 |                   bucketPath.delimiter,
131 |                 );
132 |                 const { unlocalizable: tgtUnlocalizable, ...tgt } =
133 |                   await loader.pull(resolvedTargetLocale);
134 | 
135 |                 const missingKeys = _.difference(
136 |                   Object.keys(src),
137 |                   Object.keys(tgt),
138 |                 );
139 |                 if (missingKeys.length > 0) {
140 |                   throw new Error(
141 |                     `Localization data has changed; please update i18n.lock or run without --frozen. Details: Target file is missing translations.`,
142 |                   );
143 |                 }
144 | 
145 |                 const extraKeys = _.difference(
146 |                   Object.keys(tgt),
147 |                   Object.keys(src),
148 |                 );
149 |                 if (extraKeys.length > 0) {
150 |                   throw new Error(
151 |                     `Localization data has changed; please update i18n.lock or run without --frozen. Details: Target file has extra translations not present in the source file.`,
152 |                   );
153 |                 }
154 | 
155 |                 const unlocalizableDataDiff = !_.isEqual(
156 |                   srcUnlocalizable,
157 |                   tgtUnlocalizable,
158 |                 );
159 |                 if (unlocalizableDataDiff) {
160 |                   throw new Error(
161 |                     `Localization data has changed; please update i18n.lock or run without --frozen. Details: Unlocalizable data (such as booleans, dates, URLs, etc.) do not match.`,
162 |                   );
163 |                 }
164 |               }
165 |             }
166 |           }
167 | 
168 |           task.title = "No lockfile updates required";
169 |         },
170 |       },
171 |     ],
172 |     {
173 |       rendererOptions: commonTaskRendererOptions,
174 |     },
175 |   ).run(input);
176 | }
177 | 
```

--------------------------------------------------------------------------------
/packages/locales/src/validation.spec.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { describe, it, expect } from "vitest";
  2 | import {
  3 |   isValidLocale,
  4 |   isValidLanguageCode,
  5 |   isValidScriptCode,
  6 |   isValidRegionCode,
  7 | } from "./validation";
  8 | 
  9 | describe("isValidLocale", () => {
 10 |   it("should validate basic language-region locales with hyphen", () => {
 11 |     expect(isValidLocale("en-US")).toBe(true);
 12 |     expect(isValidLocale("es-MX")).toBe(true);
 13 |     expect(isValidLocale("fr-CA")).toBe(true);
 14 |   });
 15 | 
 16 |   it("should validate basic language-region locales with underscore", () => {
 17 |     expect(isValidLocale("en_US")).toBe(true);
 18 |     expect(isValidLocale("es_MX")).toBe(true);
 19 |     expect(isValidLocale("fr_CA")).toBe(true);
 20 |   });
 21 | 
 22 |   it("should validate language-script-region locales", () => {
 23 |     expect(isValidLocale("zh-Hans-CN")).toBe(true);
 24 |     expect(isValidLocale("zh-Hant-TW")).toBe(true);
 25 |     expect(isValidLocale("sr-Cyrl-RS")).toBe(true);
 26 |   });
 27 | 
 28 |   it("should validate language-only locales", () => {
 29 |     expect(isValidLocale("es")).toBe(true);
 30 |     expect(isValidLocale("fr")).toBe(true);
 31 |     expect(isValidLocale("zh")).toBe(true);
 32 |   });
 33 | 
 34 |   it("should validate locales with numeric region codes", () => {
 35 |     expect(isValidLocale("es-419")).toBe(true); // Latin America
 36 |     expect(isValidLocale("en-001")).toBe(true); // World
 37 |   });
 38 | 
 39 |   it("should reject invalid locale formats", () => {
 40 |     expect(isValidLocale("invalid")).toBe(false);
 41 |     expect(isValidLocale("en-")).toBe(false);
 42 |     expect(isValidLocale("-US")).toBe(false);
 43 |     expect(isValidLocale("en-US-")).toBe(false);
 44 |   });
 45 | 
 46 |   it("should reject locales with invalid language codes", () => {
 47 |     expect(isValidLocale("xyz-US")).toBe(false);
 48 |     expect(isValidLocale("fake-CN")).toBe(false);
 49 |   });
 50 | 
 51 |   it("should reject locales with invalid script codes", () => {
 52 |     expect(isValidLocale("en-Fake-US")).toBe(false);
 53 |     expect(isValidLocale("zh-Invalid-CN")).toBe(false);
 54 |   });
 55 | 
 56 |   it("should reject locales with invalid region codes", () => {
 57 |     expect(isValidLocale("en-US-FAKE")).toBe(false);
 58 |     expect(isValidLocale("en-ZZ")).toBe(false);
 59 |   });
 60 | 
 61 |   it("should handle edge cases", () => {
 62 |     expect(isValidLocale("")).toBe(false);
 63 |     expect(isValidLocale("   ")).toBe(false);
 64 |     expect(isValidLocale(null as any)).toBe(false);
 65 |     expect(isValidLocale(undefined as any)).toBe(false);
 66 |   });
 67 | });
 68 | 
 69 | describe("isValidLanguageCode", () => {
 70 |   it("should validate common language codes", () => {
 71 |     expect(isValidLanguageCode("en")).toBe(true);
 72 |     expect(isValidLanguageCode("es")).toBe(true);
 73 |     expect(isValidLanguageCode("fr")).toBe(true);
 74 |     expect(isValidLanguageCode("zh")).toBe(true);
 75 |     expect(isValidLanguageCode("ar")).toBe(true);
 76 |     expect(isValidLanguageCode("ja")).toBe(true);
 77 |     expect(isValidLanguageCode("ko")).toBe(true);
 78 |   });
 79 | 
 80 |   it("should validate less common language codes", () => {
 81 |     expect(isValidLanguageCode("aa")).toBe(true); // Afar
 82 |     expect(isValidLanguageCode("zu")).toBe(true); // Zulu
 83 |     expect(isValidLanguageCode("yi")).toBe(true); // Yiddish
 84 |   });
 85 | 
 86 |   it("should handle case insensitive validation", () => {
 87 |     expect(isValidLanguageCode("EN")).toBe(true);
 88 |     expect(isValidLanguageCode("Es")).toBe(true);
 89 |     expect(isValidLanguageCode("FR")).toBe(true);
 90 |   });
 91 | 
 92 |   it("should reject invalid language codes", () => {
 93 |     expect(isValidLanguageCode("xyz")).toBe(false);
 94 |     expect(isValidLanguageCode("fake")).toBe(false);
 95 |     expect(isValidLanguageCode("invalid")).toBe(false);
 96 |   });
 97 | 
 98 |   it("should handle edge cases", () => {
 99 |     expect(isValidLanguageCode("")).toBe(false);
100 |     expect(isValidLanguageCode("   ")).toBe(false);
101 |     expect(isValidLanguageCode(null as any)).toBe(false);
102 |     expect(isValidLanguageCode(undefined as any)).toBe(false);
103 |   });
104 | });
105 | 
106 | describe("isValidScriptCode", () => {
107 |   it("should validate common script codes", () => {
108 |     expect(isValidScriptCode("Hans")).toBe(true); // Simplified Chinese
109 |     expect(isValidScriptCode("Hant")).toBe(true); // Traditional Chinese
110 |     expect(isValidScriptCode("Latn")).toBe(true); // Latin
111 |     expect(isValidScriptCode("Cyrl")).toBe(true); // Cyrillic
112 |     expect(isValidScriptCode("Arab")).toBe(true); // Arabic
113 |     expect(isValidScriptCode("Hira")).toBe(true); // Hiragana
114 |     expect(isValidScriptCode("Kana")).toBe(true); // Katakana
115 |   });
116 | 
117 |   it("should validate less common script codes", () => {
118 |     expect(isValidScriptCode("Adlm")).toBe(true); // Adlam
119 |     expect(isValidScriptCode("Zzzz")).toBe(true); // Unknown script
120 |     expect(isValidScriptCode("Qaaa")).toBe(true); // Private use
121 |   });
122 | 
123 |   it("should be case sensitive", () => {
124 |     expect(isValidScriptCode("hans")).toBe(false);
125 |     expect(isValidScriptCode("HANS")).toBe(false);
126 |     expect(isValidScriptCode("Hans")).toBe(true);
127 |   });
128 | 
129 |   it("should reject invalid script codes", () => {
130 |     expect(isValidScriptCode("Fake")).toBe(false);
131 |     expect(isValidScriptCode("Invalid")).toBe(false);
132 |     expect(isValidScriptCode("XYZ")).toBe(false);
133 |   });
134 | 
135 |   it("should handle edge cases", () => {
136 |     expect(isValidScriptCode("")).toBe(false);
137 |     expect(isValidScriptCode("   ")).toBe(false);
138 |     expect(isValidScriptCode(null as any)).toBe(false);
139 |     expect(isValidScriptCode(undefined as any)).toBe(false);
140 |   });
141 | });
142 | 
143 | describe("isValidRegionCode", () => {
144 |   it("should validate common country codes", () => {
145 |     expect(isValidRegionCode("US")).toBe(true);
146 |     expect(isValidRegionCode("CN")).toBe(true);
147 |     expect(isValidRegionCode("GB")).toBe(true);
148 |     expect(isValidRegionCode("DE")).toBe(true);
149 |     expect(isValidRegionCode("FR")).toBe(true);
150 |     expect(isValidRegionCode("JP")).toBe(true);
151 |     expect(isValidRegionCode("KR")).toBe(true);
152 |   });
153 | 
154 |   it("should validate numeric region codes", () => {
155 |     expect(isValidRegionCode("419")).toBe(true); // Latin America
156 |     expect(isValidRegionCode("001")).toBe(true); // World
157 |     expect(isValidRegionCode("142")).toBe(true); // Asia
158 |     expect(isValidRegionCode("150")).toBe(true); // Europe
159 |   });
160 | 
161 |   it("should handle case insensitive validation", () => {
162 |     expect(isValidRegionCode("us")).toBe(true);
163 |     expect(isValidRegionCode("cn")).toBe(true);
164 |     expect(isValidRegionCode("gb")).toBe(true);
165 |   });
166 | 
167 |   it("should reject invalid region codes", () => {
168 |     expect(isValidRegionCode("ZZ")).toBe(false);
169 |     expect(isValidRegionCode("FAKE")).toBe(false);
170 |     expect(isValidRegionCode("INVALID")).toBe(false);
171 |   });
172 | 
173 |   it("should handle edge cases", () => {
174 |     expect(isValidRegionCode("")).toBe(false);
175 |     expect(isValidRegionCode("   ")).toBe(false);
176 |     expect(isValidRegionCode(null as any)).toBe(false);
177 |     expect(isValidRegionCode(undefined as any)).toBe(false);
178 |   });
179 | });
180 | 
```

--------------------------------------------------------------------------------
/packages/cli/src/cli/loaders/inject-locale.spec.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { describe, it, expect } from "vitest";
  2 | import createInjectLocaleLoader from "./inject-locale";
  3 | 
  4 | const locale = "en";
  5 | const originalLocale = "en";
  6 | 
  7 | describe("createInjectLocaleLoader", () => {
  8 |   describe("pull", () => {
  9 |     it("should return data unchanged if injectLocaleKeys is not provided", async () => {
 10 |       const loader = createInjectLocaleLoader();
 11 |       loader.setDefaultLocale(locale);
 12 |       const data = { a: 1, b: 2, locale: "en" };
 13 |       const result = await loader.pull(locale, data);
 14 |       expect(result).toEqual(data);
 15 |     });
 16 | 
 17 |     it("should omit keys where value matches locale", async () => {
 18 |       const loader = createInjectLocaleLoader([
 19 |         "lang",
 20 |         "meta.locale",
 21 |         "obj.locale",
 22 |       ]);
 23 |       loader.setDefaultLocale(locale);
 24 |       const data = {
 25 |         lang: "en",
 26 |         value: 42,
 27 |         meta: { locale: "en", other: 1 },
 28 |         obj: { locale: "en" },
 29 |       };
 30 |       const result = await loader.pull(locale, data);
 31 |       expect(result).toEqual({ value: 42, meta: { other: 1 }, obj: {} });
 32 |     });
 33 | 
 34 |     it("should not omit keys if their value does not match locale", async () => {
 35 |       const loader = createInjectLocaleLoader(["lang", "meta.locale"]);
 36 |       loader.setDefaultLocale(locale);
 37 |       const data = { lang: "fr", value: 42, meta: { locale: "de", other: 1 } };
 38 |       const result = await loader.pull(locale, data);
 39 |       expect(result).toEqual(data);
 40 |     });
 41 | 
 42 |     it("should handle empty data object", async () => {
 43 |       const loader = createInjectLocaleLoader(["lang"]);
 44 |       loader.setDefaultLocale(locale);
 45 |       const data = {};
 46 |       const result = await loader.pull(locale, data);
 47 |       expect(result).toEqual({});
 48 |     });
 49 | 
 50 |     it("should omit keys matching wildcard pattern where value matches locale", async () => {
 51 |       const loader = createInjectLocaleLoader([
 52 |         "pages.*.locale",
 53 |         "meta/*/lang",
 54 |       ]);
 55 |       loader.setDefaultLocale(locale);
 56 |       const data = {
 57 |         pages: {
 58 |           foo: { locale: "en", value: 1 },
 59 |           bar: { locale: "en", value: 2 },
 60 |           baz: { locale: "fr", value: 3 },
 61 |         },
 62 |         other: 42,
 63 |         "meta/a/lang": "en",
 64 |         "meta/b/lang": "fr",
 65 |         "meta/c/lang": "en",
 66 |       };
 67 |       const result = await loader.pull(locale, data);
 68 |       expect(result).toEqual({
 69 |         pages: {
 70 |           foo: { value: 1 },
 71 |           bar: { value: 2 },
 72 |           baz: { locale: "fr", value: 3 },
 73 |         },
 74 |         other: 42,
 75 |         "meta/b/lang": "fr",
 76 |       });
 77 |     });
 78 |   });
 79 | 
 80 |   describe("push", () => {
 81 |     it("should return data unchanged if injectLocaleKeys is not provided", async () => {
 82 |       const loader = createInjectLocaleLoader();
 83 |       loader.setDefaultLocale(locale);
 84 |       await loader.pull(locale, { a: 1 });
 85 |       const data = { a: 2 };
 86 |       const result = await loader.push(locale, data);
 87 |       expect(result).toEqual(data);
 88 |     });
 89 | 
 90 |     it("should set injectLocaleKeys to new locale if they matched originalLocale", async () => {
 91 |       const loader = createInjectLocaleLoader(["lang", "meta.locale"]);
 92 |       loader.setDefaultLocale(originalLocale);
 93 |       const originalInput = {
 94 |         lang: "en",
 95 |         value: 42,
 96 |         meta: { locale: "en", other: 1 },
 97 |       };
 98 |       await loader.pull(originalLocale, originalInput);
 99 |       const data = { value: 99, meta: { other: 2 } };
100 |       const result = await loader.push("fr", data);
101 |       expect(result).toEqual({
102 |         lang: "fr",
103 |         value: 99,
104 |         meta: { locale: "fr", other: 2 },
105 |       });
106 |     });
107 | 
108 |     it("should not change injectLocaleKeys if they do not match originalLocale", async () => {
109 |       const loader = createInjectLocaleLoader([
110 |         "lang",
111 |         "meta.locale",
112 |         "obj.locale",
113 |       ]);
114 |       loader.setDefaultLocale(originalLocale);
115 |       const originalInput = {
116 |         lang: "de",
117 |         value: 42,
118 |         meta: { locale: "es", other: 1 },
119 |         obj: { locale: "fr" },
120 |       };
121 |       await loader.pull(originalLocale, originalInput);
122 |       const data = {
123 |         lang: "de",
124 |         value: 99,
125 |         meta: { locale: "es", other: 2 },
126 |         obj: { locale: "fr" },
127 |       };
128 |       const result = await loader.push("fr", data);
129 |       expect(result).toEqual({
130 |         lang: "de",
131 |         value: 99,
132 |         meta: { locale: "es", other: 2 },
133 |         obj: { locale: "fr" },
134 |       });
135 |     });
136 | 
137 |     it("should update injectLocaleKeys, does not add extra keys from originalInput", async () => {
138 |       const loader = createInjectLocaleLoader([
139 |         "lang",
140 |         "meta.locale",
141 |         "obj.locale",
142 |       ]);
143 |       loader.setDefaultLocale(originalLocale);
144 |       const originalInput = {
145 |         lang: "en",
146 |         value: 1,
147 |         meta: { locale: "en", other: 1 },
148 |         obj: { locale: "en" },
149 |         extra: 5,
150 |       };
151 |       await loader.pull(originalLocale, originalInput);
152 |       const data = { value: 2, meta: { other: 2 } };
153 |       const result = await loader.push("fr", data);
154 |       expect(result).toEqual({
155 |         lang: "fr",
156 |         value: 2,
157 |         meta: { locale: "fr", other: 2 },
158 |         obj: { locale: "fr" },
159 |       });
160 |     });
161 | 
162 |     it("should not inject locale if it was not in originalInput", async () => {
163 |       const loader = createInjectLocaleLoader(["lang"]);
164 |       loader.setDefaultLocale(originalLocale);
165 |       const originalInput = { value: 1, meta: { other: 1 } };
166 |       await loader.pull(originalLocale, originalInput);
167 |       const data = { value: 2, meta: { other: 2 } };
168 |     });
169 | 
170 |     it("should set wildcard-matched keys to new locale if they matched originalLocale", async () => {
171 |       const loader = createInjectLocaleLoader([
172 |         "pages.*.locale",
173 |         "meta/*/lang",
174 |       ]);
175 |       loader.setDefaultLocale(originalLocale);
176 |       const originalInput = {
177 |         pages: {
178 |           foo: { locale: "en", value: 1 },
179 |           bar: { locale: "en", value: 2 },
180 |           baz: { locale: "fr", value: 3 },
181 |         },
182 |         "meta/a/lang": "en",
183 |         "meta/b/lang": "fr",
184 |         "meta/c/lang": "en",
185 |       };
186 |       await loader.pull(originalLocale, originalInput);
187 |       const data = {
188 |         pages: {
189 |           foo: { value: 10 },
190 |           bar: { value: 20 },
191 |           baz: { locale: "fr", value: 30 },
192 |         },
193 |         "meta/b/lang": "fr",
194 |       };
195 |       const result = await loader.push("de", data);
196 |       expect(result).toEqual({
197 |         pages: {
198 |           foo: { locale: "de", value: 10 },
199 |           bar: { locale: "de", value: 20 },
200 |           baz: { locale: "fr", value: 30 },
201 |         },
202 |         "meta/a/lang": "de",
203 |         "meta/b/lang": "fr",
204 |         "meta/c/lang": "de",
205 |       });
206 |     });
207 |   });
208 | });
209 | 
```

--------------------------------------------------------------------------------
/packages/cli/package.json:
--------------------------------------------------------------------------------

```json
  1 | {
  2 |   "name": "lingo.dev",
  3 |   "version": "0.114.4",
  4 |   "description": "Lingo.dev CLI",
  5 |   "private": false,
  6 |   "publishConfig": {
  7 |     "access": "public"
  8 |   },
  9 |   "type": "module",
 10 |   "sideEffects": false,
 11 |   "exports": {
 12 |     "./cli": {
 13 |       "types": "./build/cli.d.ts",
 14 |       "import": "./build/cli.mjs",
 15 |       "require": "./build/cli.cjs"
 16 |     },
 17 |     "./sdk": {
 18 |       "types": "./build/sdk.d.ts",
 19 |       "import": "./build/sdk.mjs",
 20 |       "require": "./build/sdk.cjs"
 21 |     },
 22 |     "./spec": {
 23 |       "types": "./build/spec.d.ts",
 24 |       "import": "./build/spec.mjs",
 25 |       "require": "./build/spec.cjs"
 26 |     },
 27 |     "./compiler": {
 28 |       "types": "./build/compiler.d.ts",
 29 |       "import": "./build/compiler.mjs",
 30 |       "require": "./build/compiler.cjs"
 31 |     },
 32 |     "./react": {
 33 |       "types": "./build/react.d.ts",
 34 |       "import": "./build/react.mjs",
 35 |       "require": "./build/react.cjs"
 36 |     },
 37 |     "./react-client": {
 38 |       "types": "./build/react/client.d.ts",
 39 |       "import": "./build/react/client.mjs",
 40 |       "require": "./build/react/client.cjs"
 41 |     },
 42 |     "./react/client": {
 43 |       "types": "./build/react/client.d.ts",
 44 |       "import": "./build/react/client.mjs",
 45 |       "require": "./build/react/client.cjs"
 46 |     },
 47 |     "./react-rsc": {
 48 |       "types": "./build/react/rsc.d.ts",
 49 |       "import": "./build/react/rsc.mjs",
 50 |       "require": "./build/react/rsc.cjs"
 51 |     },
 52 |     "./react/rsc": {
 53 |       "types": "./build/react/rsc.d.ts",
 54 |       "import": "./build/react/rsc.mjs",
 55 |       "require": "./build/react/rsc.cjs"
 56 |     },
 57 |     "./react-router": {
 58 |       "types": "./build/react/react-router.d.ts",
 59 |       "import": "./build/react/react-router.mjs",
 60 |       "require": "./build/react/react-router.cjs"
 61 |     },
 62 |     "./react/react-router": {
 63 |       "types": "./build/react/react-router.d.ts",
 64 |       "import": "./build/react/react-router.mjs",
 65 |       "require": "./build/react/react-router.cjs"
 66 |     },
 67 |     "./locale-codes": {
 68 |       "types": "./build/locale-codes.d.ts",
 69 |       "import": "./build/locale-codes.mjs",
 70 |       "require": "./build/locale-codes.cjs"
 71 |     }
 72 |   },
 73 |   "typesVersions": {
 74 |     "*": {
 75 |       "sdk": [
 76 |         "./build/sdk.d.ts"
 77 |       ],
 78 |       "cli": [
 79 |         "./build/cli.d.ts"
 80 |       ],
 81 |       "spec": [
 82 |         "./build/spec.d.ts"
 83 |       ],
 84 |       "compiler": [
 85 |         "./build/compiler.d.ts"
 86 |       ],
 87 |       "react": [
 88 |         "./build/react.d.ts"
 89 |       ],
 90 |       "react/client": [
 91 |         "./build/react/client.d.ts"
 92 |       ],
 93 |       "react/rsc": [
 94 |         "./build/react/rsc.d.ts"
 95 |       ],
 96 |       "react/react-router": [
 97 |         "./build/react/react-router.d.ts"
 98 |       ],
 99 |       "locale-codes": [
100 |         "./build/locale-codes.d.ts"
101 |       ]
102 |     }
103 |   },
104 |   "bin": {
105 |     "lingo.dev": "./bin/cli.mjs"
106 |   },
107 |   "files": [
108 |     "bin",
109 |     "build",
110 |     "assets"
111 |   ],
112 |   "scripts": {
113 |     "lingo.dev": "node --inspect=9229 ./bin/cli.mjs",
114 |     "dev": "tsup --watch",
115 |     "build": "pnpm typecheck && tsup",
116 |     "typecheck": "tsc --noEmit",
117 |     "test": "vitest run",
118 |     "test:watch": "vitest",
119 |     "clean": "rm -rf build"
120 |   },
121 |   "keywords": [],
122 |   "author": "",
123 |   "license": "Apache-2.0",
124 |   "dependencies": {
125 |     "@ai-sdk/anthropic": "^1.2.11",
126 |     "@ai-sdk/google": "^1.2.19",
127 |     "@ai-sdk/mistral": "^1.2.8",
128 |     "@ai-sdk/openai": "^1.3.22",
129 |     "@babel/generator": "^7.27.1",
130 |     "@babel/parser": "^7.27.1",
131 |     "@babel/traverse": "^7.27.4",
132 |     "@babel/types": "^7.27.1",
133 |     "@biomejs/js-api": "^3.0.0",
134 |     "@biomejs/wasm-nodejs": "^2.2.4",
135 |     "@datocms/cma-client-node": "^4.0.1",
136 |     "@gitbeaker/rest": "^39.34.3",
137 |     "@inkjs/ui": "^2.0.0",
138 |     "@inquirer/prompts": "^7.8.0",
139 |     "@lingo.dev/_compiler": "workspace:*",
140 |     "@lingo.dev/_locales": "workspace:*",
141 |     "@lingo.dev/_react": "workspace:*",
142 |     "@lingo.dev/_sdk": "workspace:*",
143 |     "@lingo.dev/_spec": "workspace:*",
144 |     "@markdoc/markdoc": "^0.5.4",
145 |     "@modelcontextprotocol/sdk": "^1.5.0",
146 |     "@openrouter/ai-sdk-provider": "^0.7.1",
147 |     "@paralleldrive/cuid2": "^2.2.2",
148 |     "@types/ejs": "^3.1.5",
149 |     "ai": "^4.3.15",
150 |     "bitbucket": "^2.12.0",
151 |     "chalk": "^5.4.1",
152 |     "chokidar": "^4.0.3",
153 |     "cli-progress": "^3.12.0",
154 |     "cli-table3": "^0.6.5",
155 |     "cors": "^2.8.5",
156 |     "csv-parse": "^5.6.0",
157 |     "csv-stringify": "^6.5.2",
158 |     "date-fns": "^4.1.0",
159 |     "dedent": "^1.5.3",
160 |     "diff": "^7.0.0",
161 |     "dotenv": "^16.4.7",
162 |     "ejs": "^3.1.10",
163 |     "express": "^5.1.0",
164 |     "external-editor": "^3.1.0",
165 |     "figlet": "^1.8.2",
166 |     "flat": "^6.0.1",
167 |     "gettext-parser": "^8.0.0",
168 |     "glob": "<11.0.0",
169 |     "gradient-string": "^3.0.0",
170 |     "gray-matter": "^4.0.3",
171 |     "ini": "^5.0.0",
172 |     "ink": "^4.2.0",
173 |     "ink-progress-bar": "^3.0.0",
174 |     "ink-spinner": "^5.0.0",
175 |     "inquirer": "^12.6.0",
176 |     "interactive-commander": "^0.5.194",
177 |     "is-url": "^1.2.4",
178 |     "jsdom": "^25.0.1",
179 |     "json5": "^2.2.3",
180 |     "jsonc-parser": "^3.3.1",
181 |     "jsonrepair": "^3.11.2",
182 |     "listr2": "^8.3.2",
183 |     "lodash": "^4.17.21",
184 |     "marked": "^15.0.6",
185 |     "mdast-util-from-markdown": "^2.0.2",
186 |     "mdast-util-gfm": "^3.1.0",
187 |     "micromark-extension-gfm": "^3.0.0",
188 |     "node-machine-id": "^1.1.12",
189 |     "node-webvtt": "^1.9.4",
190 |     "object-hash": "^3.0.0",
191 |     "octokit": "^4.0.2",
192 |     "ollama-ai-provider": "^1.2.0",
193 |     "open": "^10.2.0",
194 |     "ora": "^8.1.1",
195 |     "p-limit": "^6.2.0",
196 |     "php-array-reader": "^2.1.2",
197 |     "plist": "^3.1.0",
198 |     "posthog-node": "^5.8.1",
199 |     "prettier": "^3.4.2",
200 |     "react": "^18.3.1",
201 |     "rehype-stringify": "^10.0.1",
202 |     "remark-disable-tokenizers": "^1.1.1",
203 |     "remark-frontmatter": "^5.0.0",
204 |     "remark-gfm": "^4.0.1",
205 |     "remark-mdx": "^3.1.0",
206 |     "remark-mdx-frontmatter": "^5.1.0",
207 |     "remark-parse": "^11.0.0",
208 |     "remark-rehype": "^11.1.2",
209 |     "remark-stringify": "^11.0.0",
210 |     "sax": "^1.4.1",
211 |     "srt-parser-2": "^1.2.3",
212 |     "unified": "^11.0.5",
213 |     "unist-util-visit": "^5.0.0",
214 |     "vfile": "^6.0.3",
215 |     "xliff": "^6.2.1",
216 |     "xml2js": "^0.6.2",
217 |     "xpath": "^0.0.34",
218 |     "yaml": "^2.7.0",
219 |     "zod": "^3.25.76"
220 |   },
221 |   "devDependencies": {
222 |     "@types/babel__generator": "^7.27.0",
223 |     "@types/chokidar": "^2.1.7",
224 |     "@types/cli-progress": "^3.11.6",
225 |     "@types/cors": "^2.8.17",
226 |     "@types/diff": "^7.0.0",
227 |     "@types/express": "^5.0.3",
228 |     "@types/figlet": "^1.7.0",
229 |     "@types/gettext-parser": "^4.0.4",
230 |     "@types/glob": "^8.1.0",
231 |     "@types/ini": "^4.1.1",
232 |     "@types/is-url": "^1.2.32",
233 |     "@types/jsdom": "^21.1.7",
234 |     "@types/lodash": "^4.17.16",
235 |     "@types/mdast": "^4.0.4",
236 |     "@types/node": "^22.10.2",
237 |     "@types/node-gettext": "^3.0.6",
238 |     "@types/object-hash": "^3.0.6",
239 |     "@types/plist": "^3.0.5",
240 |     "@types/react": "^18.3.20",
241 |     "@types/xml2js": "^0.4.14",
242 |     "tsup": "^8.3.5",
243 |     "typescript": "^5.8.3",
244 |     "vitest": "^3.1.2"
245 |   },
246 |   "engines": {
247 |     "node": ">=18"
248 |   },
249 |   "packageManager": "[email protected]"
250 | }
251 | 
```

--------------------------------------------------------------------------------
/demo/react-router-app/app/lingo/meta.json:
--------------------------------------------------------------------------------

```json
  1 | {
  2 |   "version": 0.1,
  3 |   "files": {
  4 |     "root.tsx": {
  5 |       "scopes": {
  6 |         "9/declaration/body/1/argument/1/1/3-content": {
  7 |           "type": "attribute",
  8 |           "hash": "d94b318cb327f61f1aea44a6cb1fdcad",
  9 |           "context": "",
 10 |           "skip": false,
 11 |           "overrides": {},
 12 |           "content": "width=device-width, initial-scale=1"
 13 |         }
 14 |       }
 15 |     },
 16 |     "routes/test.tsx": {
 17 |       "scopes": {
 18 |         "3/declaration/body/0/argument/1/1": {
 19 |           "type": "element",
 20 |           "hash": "a0ac69aec348674378faaf92ce476f64",
 21 |           "context": "",
 22 |           "skip": false,
 23 |           "overrides": {},
 24 |           "content": "Go back home"
 25 |         },
 26 |         "3/declaration/body/0/argument/1/3": {
 27 |           "type": "element",
 28 |           "hash": "51eb13586d30537dfa934742439cc7ee",
 29 |           "context": "",
 30 |           "skip": false,
 31 |           "overrides": {},
 32 |           "content": "This is a test page"
 33 |         },
 34 |         "3/declaration/body/0/argument/1/5": {
 35 |           "type": "element",
 36 |           "hash": "792a8d0c1ca71a88ab7d887075e69b1d",
 37 |           "context": "",
 38 |           "skip": false,
 39 |           "overrides": {},
 40 |           "content": "Welcome to non-interactive testing page."
 41 |         },
 42 |         "3/declaration/body/0/argument/1/7": {
 43 |           "type": "element",
 44 |           "hash": "31ab29a98c0bb54378cb5a2390d07e57",
 45 |           "context": "",
 46 |           "skip": false,
 47 |           "overrides": {},
 48 |           "content": "Please do not try to interact with this page for your own safety."
 49 |         }
 50 |       }
 51 |     },
 52 |     "welcome/welcome.tsx": {
 53 |       "scopes": {
 54 |         "3/declaration/body/0/argument/1/1/1/1-alt": {
 55 |           "type": "attribute",
 56 |           "hash": "68ae50c1603f87d51e788a96b419f2ee",
 57 |           "context": "",
 58 |           "skip": false,
 59 |           "overrides": {},
 60 |           "content": "React Router"
 61 |         },
 62 |         "3/declaration/body/0/argument/1/1/1/3-alt": {
 63 |           "type": "attribute",
 64 |           "hash": "68ae50c1603f87d51e788a96b419f2ee",
 65 |           "context": "",
 66 |           "skip": false,
 67 |           "overrides": {},
 68 |           "content": "React Router"
 69 |         },
 70 |         "3/declaration/body/0/argument/1/1/3/1-alt": {
 71 |           "type": "attribute",
 72 |           "hash": "68ae50c1603f87d51e788a96b419f2ee",
 73 |           "context": "",
 74 |           "skip": false,
 75 |           "overrides": {},
 76 |           "content": "React Router"
 77 |         },
 78 |         "3/declaration/body/0/argument/1/1/3/3-alt": {
 79 |           "type": "attribute",
 80 |           "hash": "68ae50c1603f87d51e788a96b419f2ee",
 81 |           "context": "",
 82 |           "skip": false,
 83 |           "overrides": {},
 84 |           "content": "React Router"
 85 |         },
 86 |         "3/declaration/body/0/argument/1/3": {
 87 |           "type": "element",
 88 |           "hash": "4938894bf1608cee94696ec86f5d059a",
 89 |           "context": "",
 90 |           "skip": false,
 91 |           "overrides": {},
 92 |           "content": "Test"
 93 |         },
 94 |         "3/declaration/body/0/argument/1/5/1/1": {
 95 |           "type": "element",
 96 |           "hash": "e0d9d29b9e761346e506557eb7b7e798",
 97 |           "context": "",
 98 |           "skip": false,
 99 |           "overrides": {},
100 |           "content": "What's next?"
101 |         },
102 |         "4/declaration/body/0/argument/1/1": {
103 |           "type": "element",
104 |           "hash": "201cf15cf0830aaaf478e49a9665d096",
105 |           "context": "",
106 |           "skip": false,
107 |           "overrides": {},
108 |           "content": "<element:LingoDotDev></element:LingoDotDev> 💚<element:div><element:img></element:img><element:img></element:img></element:div>"
109 |         },
110 |         "4/declaration/body/0/argument/1/1/3": {
111 |           "type": "element",
112 |           "hash": "0ecc986bbbb51a93878f2d11bb45c04a",
113 |           "context": "",
114 |           "skip": false,
115 |           "overrides": {},
116 |           "content": "💚"
117 |         },
118 |         "4/declaration/body/0/argument/1/1/3/1-alt": {
119 |           "type": "attribute",
120 |           "hash": "68ae50c1603f87d51e788a96b419f2ee",
121 |           "context": "",
122 |           "skip": false,
123 |           "overrides": {},
124 |           "content": "React Router"
125 |         },
126 |         "4/declaration/body/0/argument/1/1/3/3-alt": {
127 |           "type": "attribute",
128 |           "hash": "68ae50c1603f87d51e788a96b419f2ee",
129 |           "context": "",
130 |           "skip": false,
131 |           "overrides": {},
132 |           "content": "React Router"
133 |         },
134 |         "4/declaration/body/0/argument/1/1/5/1-alt": {
135 |           "type": "attribute",
136 |           "hash": "68ae50c1603f87d51e788a96b419f2ee",
137 |           "context": "",
138 |           "skip": false,
139 |           "overrides": {},
140 |           "content": "React Router"
141 |         },
142 |         "4/declaration/body/0/argument/1/1/5/1/1-alt": {
143 |           "type": "attribute",
144 |           "hash": "68ae50c1603f87d51e788a96b419f2ee",
145 |           "context": "",
146 |           "skip": false,
147 |           "overrides": {},
148 |           "content": "React Router"
149 |         },
150 |         "4/declaration/body/0/argument/1/1/5/1/3-alt": {
151 |           "type": "attribute",
152 |           "hash": "68ae50c1603f87d51e788a96b419f2ee",
153 |           "context": "",
154 |           "skip": false,
155 |           "overrides": {},
156 |           "content": "React Router"
157 |         },
158 |         "4/declaration/body/0/argument/1/1/5/3-alt": {
159 |           "type": "attribute",
160 |           "hash": "68ae50c1603f87d51e788a96b419f2ee",
161 |           "context": "",
162 |           "skip": false,
163 |           "overrides": {},
164 |           "content": "React Router"
165 |         },
166 |         "4/declaration/body/0/argument/1/1/5/5-alt": {
167 |           "type": "attribute",
168 |           "hash": "68ae50c1603f87d51e788a96b419f2ee",
169 |           "context": "",
170 |           "skip": false,
171 |           "overrides": {},
172 |           "content": "React Router"
173 |         },
174 |         "4/declaration/body/0/argument/1/3": {
175 |           "type": "element",
176 |           "hash": "4e5098c50297642cf07ce303398bad59",
177 |           "context": "",
178 |           "skip": false,
179 |           "overrides": {},
180 |           "content": "Open test page"
181 |         },
182 |         "4/declaration/body/0/argument/1/5": {
183 |           "type": "element",
184 |           "hash": "a90f2300128bce36346e0debd0b6092b",
185 |           "context": "",
186 |           "skip": false,
187 |           "overrides": {},
188 |           "content": "Welcome to your new React Router application! This starter template includes everything you need to get started with React Router and Lingo.dev for internationalization."
189 |         },
190 |         "4/declaration/body/0/argument/1/5/1/1": {
191 |           "type": "element",
192 |           "hash": "e0d9d29b9e761346e506557eb7b7e798",
193 |           "context": "",
194 |           "skip": false,
195 |           "overrides": {},
196 |           "content": "What's next?"
197 |         },
198 |         "4/declaration/body/0/argument/1/7/1/1": {
199 |           "type": "element",
200 |           "hash": "e0d9d29b9e761346e506557eb7b7e798",
201 |           "context": "",
202 |           "skip": false,
203 |           "overrides": {},
204 |           "content": "What's next?"
205 |         }
206 |       }
207 |     }
208 |   }
209 | }
210 | 
```
Page 9/20FirstPrevNextLast