#
tokens: 47744/50000 19/626 files (page 8/16)
lines: off (toggle) GitHub
raw markdown copy
This is page 8 of 16. Use http://codebase.md/lingodotdev/lingo.dev?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/lingo-dot-dev.tsx:
--------------------------------------------------------------------------------

```typescript
import type { SVGProps } from "react";

export function LingoDotDev(props: SVGProps<SVGSVGElement>) {
  return (
    <svg
      width={props.width ? props.width : !props.height ? 150 : undefined}
      height={props.height ? props.height : !props.width ? 26 : undefined}
      viewBox="0 0 150 26"
      fill="#000000"
      xmlns="http://www.w3.org/2000/svg"
      className={props.className}
    >
      <path
        d="M0.0528644 9.3081L9.51858 19.6347C9.55631 19.6754 9.60893 19.6992 9.66454 19.6992H25.1832C25.36 19.6992 25.4483 19.4857 25.3232 19.3606L18.5126 12.5501H13.1508V15.231L6.89523 8.97546H0.198827C0.026055 8.97546 -0.0643027 9.181 0.0528644 9.3081Z"
        fill="#69E300"
      ></path>
      <path
        d="M25.3593 12.2182L15.8926 1.89157C15.8548 1.85086 15.8022 1.82703 15.7466 1.82703H0.227916C0.0501791 1.82703 -0.0381927 2.04051 0.0869179 2.16562L6.8975 8.9762H12.2594V6.29526L18.5149 12.5508H25.2123C25.3851 12.5508 25.4754 12.3453 25.3583 12.2182H25.3593Z"
        fill="#69E300"
      ></path>
      <path d="M104.582 19.7876H101.476V16.7095H104.582V19.7876Z"></path>
      <path d="M109.677 19.1594C108.713 18.5597 107.951 17.7139 107.389 16.6255C106.827 15.537 106.543 14.2995 106.543 12.9199C106.543 11.5402 106.824 10.3097 107.389 9.22814C107.951 8.14662 108.717 7.30775 109.677 6.70807C110.641 6.10838 111.712 5.81027 112.894 5.81027C114.62 5.81027 116 6.46542 117.036 7.77225H117.171V0.711182H119.868V19.7868H117.553L117.279 18.0432H117.143C116.09 19.3882 114.672 20.0607 112.894 20.0607C111.712 20.0607 110.641 19.7626 109.677 19.1629V19.1594ZM115.195 16.979C115.802 16.5977 116.284 16.0535 116.641 15.3429C116.994 14.6357 117.171 13.8246 117.171 12.9164C117.171 12.0082 116.994 11.204 116.641 10.5038C116.287 9.80357 115.805 9.26281 115.195 8.8815C114.585 8.5002 113.927 8.30955 113.22 8.30955C112.512 8.30955 111.829 8.5002 111.23 8.8815C110.63 9.26281 110.152 9.80357 109.798 10.5038C109.445 11.204 109.268 12.0082 109.268 12.9164C109.268 13.8246 109.445 14.6565 109.798 15.3568C110.152 16.057 110.63 16.5977 111.23 16.979C111.829 17.3603 112.492 17.551 113.22 17.551C113.947 17.551 114.585 17.3603 115.195 16.979Z"></path>
      <path d="M126.219 16.6632C127.051 17.2802 127.901 17.7135 128.972 17.7135C129.734 17.7135 130.407 17.5402 130.989 17.197C131.572 16.8504 132.005 16.4067 132.296 15.8625H135.104C134.684 17.0792 133.95 18.0844 132.896 18.8748C131.842 19.6651 130.535 20.0603 128.972 20.0603C127.7 20.0603 126.545 19.7552 125.512 19.1486C124.476 18.542 123.665 17.6962 123.072 16.6147C122.483 15.5332 122.188 14.3026 122.188 12.923C122.188 11.5433 122.483 10.3405 123.072 9.25896C123.661 8.17744 124.476 7.33511 125.512 6.72502C126.549 6.1184 127.7 5.81335 128.972 5.81335C130.244 5.81335 131.291 6.10453 132.282 6.68689C133.274 7.26925 134.053 8.06652 134.625 9.08564C135.197 10.1048 135.485 11.2487 135.485 12.5174C135.485 13.1517 135.44 13.6336 135.35 13.9629H124.874L127.121 11.8934H132.819C132.729 10.805 132.327 9.90371 131.62 9.19657C130.913 8.48942 130.029 8.13238 128.979 8.13238C127.928 8.13238 126.972 8.48595 126.226 9.19657C125.481 9.90371 125.037 10.805 124.892 11.8934C124.892 11.8934 124.316 15.2628 126.226 16.6736L126.219 16.6632Z"></path>
      <path d="M144.556 19.7876H140.605L135.783 6.08142H138.618L142.324 16.7372H142.868L146.574 6.08142H149.409L144.56 19.7876H144.556Z"></path>
      <path d="M53.4117 3.49132H50.4688V0.766724H53.4117V3.49132ZM53.2765 19.7869H50.5797V6.08073H53.2765V19.7869Z"></path>
      <path d="M56.2451 19.7872V6.08105H58.5607L58.8345 7.49881H58.9697C59.3129 7.06204 59.8398 6.6738 60.5504 6.32716C61.2575 5.98052 62.0756 5.81067 63.0046 5.81067C64.0584 5.81067 64.9978 6.05332 65.8263 6.53168C66.6513 7.01351 67.2995 7.68946 67.7605 8.563C68.225 9.43653 68.4538 10.4349 68.4538 11.5614V19.7907H65.7569V11.7798C65.7569 10.7988 65.438 9.98076 64.8037 9.32561C64.1693 8.67045 63.379 8.34461 62.4327 8.34461C61.7983 8.34461 61.2125 8.5006 60.6752 8.80911C60.1379 9.11762 59.7185 9.53706 59.4065 10.064C59.098 10.5908 58.942 11.1628 58.942 11.7798V19.7907H56.2451V19.7872Z"></path>
      <path d="M73.9972 24.772C73.0613 24.2832 72.3472 23.6697 71.8585 22.9313C71.3697 22.193 71.1062 21.4477 71.0681 20.6817H73.7927C73.8655 21.316 74.2121 21.8776 74.8292 22.3559C75.4462 22.8378 76.3024 23.0769 77.3908 23.0769C78.4793 23.0769 79.4152 22.758 80.0877 22.1237C80.7602 21.4859 81.0964 20.6435 81.0964 19.5897V17.4094H80.9612C80.4898 17.9363 79.897 18.3557 79.1899 18.6642C78.4828 18.9727 77.6993 19.1287 76.8466 19.1287C75.6646 19.1287 74.6004 18.8479 73.6436 18.2829C72.6904 17.7213 71.9416 16.931 71.3974 15.9119C70.8532 14.8962 70.5793 13.7419 70.5793 12.4524C70.5793 11.1629 70.8532 10.0121 71.3974 9.00679C71.9416 7.99807 72.6904 7.21466 73.6436 6.64964C74.5969 6.08808 75.6646 5.80383 76.8466 5.80383C77.7201 5.80383 78.5209 5.97715 79.2592 6.32033C79.9941 6.66697 80.5903 7.128 81.0444 7.71036H81.1796L81.4535 6.07421H83.7968V19.6175C83.7968 20.7787 83.5437 21.8048 83.0341 22.6956C82.5246 23.5865 81.7862 24.2763 80.8122 24.7651C79.8416 25.2573 78.7011 25.5 77.3908 25.5C76.0805 25.5 74.9332 25.2538 73.9972 24.7651V24.772ZM79.1726 16.1476C79.7619 15.794 80.2298 15.2949 80.5765 14.6501C80.9231 14.0054 81.093 13.2739 81.093 12.4559C81.093 11.6378 80.9196 10.9133 80.5765 10.2755C80.2298 9.64115 79.7619 9.14545 79.1726 8.79188C78.5833 8.4383 77.9247 8.26152 77.1967 8.26152C76.0701 8.26152 75.1411 8.65322 74.4028 9.43316C73.6679 10.2131 73.3005 11.2218 73.3005 12.4593C73.3005 13.6968 73.6679 14.7298 74.4028 15.5098C75.1377 16.2897 76.0701 16.6814 77.1967 16.6814C77.9247 16.6814 78.5833 16.5046 79.1726 16.1511V16.1476Z"></path>
      <path d="M89.2696 19.1459C88.2331 18.5393 87.422 17.6935 86.8292 16.612C86.24 15.5305 85.9453 14.2999 85.9453 12.9203C85.9453 11.5406 86.24 10.3378 86.8292 9.25628C87.4185 8.17476 88.2331 7.33242 89.2696 6.72233C90.3061 6.11571 91.4569 5.81067 92.7291 5.81067C94.0012 5.81067 95.1486 6.11571 96.1747 6.72233C97.2007 7.33242 98.0084 8.17476 98.6012 9.25628C99.1905 10.3378 99.4851 11.558 99.4851 12.9203C99.4851 14.2826 99.1905 15.5305 98.6012 16.612C98.0119 17.6935 97.2007 18.5393 96.1747 19.1459C95.1486 19.756 93.9978 20.0576 92.7291 20.0576C91.4604 20.0576 90.3026 19.7526 89.2696 19.1459ZM94.7846 16.9794C95.4121 16.5981 95.9078 16.0539 96.2683 15.3433C96.6322 14.6361 96.8125 13.825 96.8125 12.9168C96.8125 12.0086 96.6322 11.2044 96.2683 10.5042C95.9043 9.80397 95.4086 9.26321 94.7846 8.88191C94.1572 8.5006 93.4709 8.30995 92.7291 8.30995C91.9873 8.30995 91.2974 8.5006 90.6735 8.88191C90.0461 9.26321 89.5504 9.80397 89.1899 10.5042C88.8259 11.2044 88.6456 12.0086 88.6456 12.9168C88.6456 13.825 88.8259 14.6327 89.1899 15.3433C89.5538 16.0504 90.0495 16.5981 90.6735 16.9794C91.3009 17.3607 91.9873 17.5514 92.7291 17.5514C93.4709 17.5514 94.1607 17.3607 94.7846 16.9794Z"></path>
      <path d="M48.8417 19.5756H36.7439V0.5H39.6037V16.851L39.6557 19.5756L42.6541 16.851H48.8417V19.5756Z"></path>
    </svg>
  );
}

```

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

```typescript
import Markdoc from "@markdoc/markdoc";
import YAML from "yaml";
import { ILoader } from "./_types";
import { createLoader } from "./_utils";

type MarkdocNode = {
  $$mdtype?: string;
  type: string;
  tag?: string;
  attributes?: Record<string, any>;
  children?: MarkdocNode[];
  [key: string]: any;
};

type NodeCounter = {
  [nodeType: string]: number;
};

type NodePathMap = {
  [semanticKey: string]: string; // maps semantic key to AST path
};

const FM_ATTR_PREFIX = "fm-attr-";

export default function createMarkdocLoader(): ILoader<
  string,
  Record<string, string>
> {
  return createLoader({
    async pull(locale, input) {
      const ast = Markdoc.parse(input) as unknown as MarkdocNode;
      const result: Record<string, string> = {};
      const counters: NodeCounter = {};

      // Traverse the AST and extract text content with semantic keys
      traverseAndExtract(ast, "", result, counters);

      // Extract frontmatter if present
      if (ast.attributes?.frontmatter) {
        const frontmatter = YAML.parse(ast.attributes.frontmatter);
        Object.entries(frontmatter).forEach(([key, value]) => {
          if (typeof value === "string") {
            result[`${FM_ATTR_PREFIX}${key}`] = value;
          }
        });
      }

      return result;
    },

    async push(locale, data, originalInput) {
      if (!originalInput) {
        throw new Error("Original input is required for push");
      }

      const ast = Markdoc.parse(originalInput) as unknown as MarkdocNode;
      const counters: NodeCounter = {};
      const pathMap: NodePathMap = {};

      // Build path map from semantic keys to AST paths
      buildPathMap(ast, "", counters, pathMap);

      // Extract frontmatter from data
      const frontmatterEntries = Object.entries(data)
        .filter(([key]) => key.startsWith(FM_ATTR_PREFIX))
        .map(([key, value]) => [key.replace(FM_ATTR_PREFIX, ""), value]);

      // Update frontmatter in AST if present
      if (frontmatterEntries.length > 0 && ast.attributes) {
        const frontmatter = Object.fromEntries(frontmatterEntries);
        ast.attributes.frontmatter = YAML.stringify(frontmatter, {
          defaultStringType: "PLAIN",
        }).trim();
      }

      // Filter out frontmatter keys from translation data
      const contentData = Object.fromEntries(
        Object.entries(data).filter(([key]) => !key.startsWith(FM_ATTR_PREFIX)),
      );

      // Apply translations using the path map
      applyTranslations(ast, "", contentData, pathMap);

      // Format back to string
      return Markdoc.format(ast);
    },
  });
}

function getSemanticNodeType(node: MarkdocNode): string | null {
  // For custom tags, use the tag name instead of "tag"
  if (node.type === "tag") return node.tag || "tag";
  return node.type;
}

function traverseAndExtract(
  node: MarkdocNode,
  path: string,
  result: Record<string, string>,
  counters: NodeCounter,
  parentType?: string,
) {
  if (!node || typeof node !== "object") {
    return;
  }

  // Determine the semantic type for this node
  let semanticType = parentType;
  const nodeSemanticType = getSemanticNodeType(node);

  // Use node's own semantic type for structural elements
  if (
    nodeSemanticType &&
    !["text", "strong", "em", "inline", "link"].includes(nodeSemanticType)
  ) {
    semanticType = nodeSemanticType;
  }

  // If this is a text node, extract its content only if it's a string
  // Skip interpolation nodes (where content is a Variable or Function object)
  if (node.type === "text" && node.attributes?.content) {
    const content = node.attributes.content;

    // Only extract if content is a string (not interpolation)
    if (typeof content === "string" && content.trim()) {
      if (semanticType) {
        const index = counters[semanticType] || 0;
        counters[semanticType] = index + 1;
        const semanticKey = `${semanticType}-${index}`;
        result[semanticKey] = content;
      }
    }
  }

  // If the node has children, traverse them
  if (Array.isArray(node.children)) {
    node.children.forEach((child, index) => {
      const childPath = path
        ? `${path}/children/${index}`
        : `children/${index}`;
      traverseAndExtract(child, childPath, result, counters, semanticType);
    });
  }
}

function buildPathMap(
  node: MarkdocNode,
  path: string,
  counters: NodeCounter,
  pathMap: NodePathMap,
  parentType?: string,
) {
  if (!node || typeof node !== "object") {
    return;
  }

  // Determine the semantic type for this node
  let semanticType = parentType;
  const nodeSemanticType = getSemanticNodeType(node);

  // Use node's own semantic type for structural elements
  if (
    nodeSemanticType &&
    !["text", "strong", "em", "inline", "link"].includes(nodeSemanticType)
  ) {
    semanticType = nodeSemanticType;
  }

  // Build the map from semantic keys to AST paths
  if (node.type === "text" && node.attributes?.content) {
    const content = node.attributes.content;

    if (typeof content === "string" && content.trim()) {
      if (semanticType) {
        const index = counters[semanticType] || 0;
        counters[semanticType] = index + 1;
        const semanticKey = `${semanticType}-${index}`;
        const contentPath = path
          ? `${path}/attributes/content`
          : "attributes/content";
        pathMap[semanticKey] = contentPath;
      }
    }
  }

  // Recursively build map for children
  if (Array.isArray(node.children)) {
    node.children.forEach((child, index) => {
      const childPath = path
        ? `${path}/children/${index}`
        : `children/${index}`;
      buildPathMap(child, childPath, counters, pathMap, semanticType);
    });
  }
}

function applyTranslations(
  node: MarkdocNode,
  path: string,
  data: Record<string, string>,
  pathMap: NodePathMap,
) {
  if (!node || typeof node !== "object") {
    return;
  }

  // Check if we have a translation for this node's text content
  // Only apply translations to string content (not interpolation)
  if (node.type === "text" && node.attributes?.content) {
    const content = node.attributes.content;

    // Only apply translation if content is currently a string
    if (typeof content === "string") {
      const contentPath = path
        ? `${path}/attributes/content`
        : "attributes/content";

      // Find the semantic key for this path
      const semanticKey = Object.keys(pathMap).find(
        (key) => pathMap[key] === contentPath,
      );

      if (semanticKey && data[semanticKey] !== undefined) {
        node.attributes.content = data[semanticKey];
      }
    }
    // If content is an object (Variable/Function), leave it unchanged
  }

  // Recursively apply translations to children
  if (Array.isArray(node.children)) {
    node.children.forEach((child, index) => {
      const childPath = path
        ? `${path}/children/${index}`
        : `children/${index}`;
      applyTranslations(child, childPath, data, pathMap);
    });
  }
}

```

--------------------------------------------------------------------------------
/packages/compiler/src/utils/jsx-content.ts:
--------------------------------------------------------------------------------

```typescript
import { NodePath } from "@babel/traverse";
import * as t from "@babel/types";
import { getJsxElementName } from "./jsx-element";
import _ from "lodash";

const WHITESPACE_PLACEHOLDER = "[lingo-whitespace-placeholder]";

export function extractJsxContent(
  nodePath: NodePath<t.JSXElement>,
  replaceWhitespacePlaceholders = true, // do not replace when called recursively
) {
  const chunks: string[] = [];

  nodePath.traverse({
    JSXElement(path) {
      if (path.parent === nodePath.node) {
        const content = extractJsxContent(path, false);
        const name = getJsxElementName(path);
        chunks.push(`<element:${name}>${content}</element:${name}>`);
        path.skip();
      }
    },
    JSXText(path) {
      chunks.push(path.node.value);
    },
    JSXExpressionContainer(path) {
      if (path.parent !== nodePath.node) {
        return;
      }

      const expr = path.node.expression;
      if (t.isCallExpression(expr)) {
        let key = "";
        if (t.isIdentifier(expr.callee)) {
          key = `${expr.callee.name}`;
        } else if (t.isMemberExpression(expr.callee)) {
          let firstCallee: t.Expression | t.V8IntrinsicIdentifier = expr.callee;
          while (
            t.isMemberExpression(firstCallee) &&
            t.isCallExpression(firstCallee.object)
          ) {
            firstCallee = firstCallee.object.callee;
          }

          let current: t.Expression | t.V8IntrinsicIdentifier = firstCallee;
          const parts: string[] = [];

          while (t.isMemberExpression(current)) {
            if (t.isIdentifier(current.property)) {
              parts.unshift(current.property.name);
            }
            current = current.object;
          }

          if (t.isIdentifier(current)) {
            parts.unshift(current.name);
          }

          if (
            t.isMemberExpression(firstCallee) &&
            t.isNewExpression(firstCallee.object) &&
            t.isIdentifier(firstCallee.object.callee)
          ) {
            parts.unshift(firstCallee.object.callee.name);
          }

          key = parts.join(".");
        }

        chunks.push(`<function:${key}/>`);
      } else if (t.isIdentifier(expr)) {
        chunks.push(`{${expr.name}}`);
      } else if (t.isMemberExpression(expr)) {
        let current: t.MemberExpression | t.Identifier = expr;
        const parts = [];

        while (t.isMemberExpression(current)) {
          if (t.isIdentifier(current.property)) {
            if (current.computed) {
              parts.unshift(`[${current.property.name}]`);
            } else {
              parts.unshift(current.property.name);
            }
          }
          current = current.object as t.MemberExpression | t.Identifier;
        }

        if (t.isIdentifier(current)) {
          parts.unshift(current.name);
          chunks.push(`{${parts.join(".").replaceAll(".[", "[")}}`);
        }
      } else if (isWhitespace(path)) {
        chunks.push(WHITESPACE_PLACEHOLDER);
      } else if (isExpression(path)) {
        chunks.push("<expression/>");
      }
      path.skip();
    },
  });

  const result = chunks.join("");
  const normalized = normalizeJsxWhitespace(result);

  if (replaceWhitespacePlaceholders) {
    return normalized.replaceAll(WHITESPACE_PLACEHOLDER, " ");
  }
  return normalized;
}

const compilerProps = ["data-jsx-attribute-scope", "data-jsx-scope"];

function isExpression(nodePath: NodePath<t.JSXExpressionContainer>) {
  const isCompilerExpression =
    !_.isArray(nodePath.container) &&
    t.isJSXAttribute(nodePath.container) &&
    t.isJSXIdentifier(nodePath.container.name) &&
    compilerProps.includes(nodePath.container.name.name);
  return (
    !isCompilerExpression && !t.isJSXEmptyExpression(nodePath.node.expression)
  );
}

function isWhitespace(nodePath: NodePath<t.JSXExpressionContainer>) {
  const expr = nodePath.node.expression;
  return t.isStringLiteral(expr) && expr.value === " ";
}

function normalizeJsxWhitespace(input: string) {
  // Handle single-line content
  if (!input.includes("\n")) {
    // For single-line content, only trim if it appears to be formatting whitespace
    // (e.g., "   hello world   " should be trimmed to "hello world")
    // But preserve meaningful leading/trailing spaces (e.g., " hello" should stay " hello")

    // If the content is mostly whitespace with some text, it's likely formatting
    const trimmed = input.trim();
    if (trimmed.length === 0) return "";

    // Check if we have excessive whitespace (more than 1 space on each side)
    const leadingMatch = input.match(/^\s*/);
    const trailingMatch = input.match(/\s*$/);
    const leadingSpaces = leadingMatch ? leadingMatch[0].length : 0;
    const trailingSpaces = trailingMatch ? trailingMatch[0].length : 0;

    if (leadingSpaces > 1 || trailingSpaces > 1) {
      // This looks like formatting whitespace, collapse it
      return input.replace(/\s+/g, " ").trim();
    } else {
      // This looks like meaningful whitespace, preserve it but collapse internal spaces
      return input.replace(/\s{2,}/g, " ");
    }
  }

  // Handle multi-line content
  const lines = input.split("\n");
  let result = "";

  for (let i = 0; i < lines.length; i++) {
    const line = lines[i];
    const trimmedLine = line.trim();

    // Skip empty lines
    if (trimmedLine === "") continue;

    // Check if this line contains a placeholder (explicit whitespace)
    if (trimmedLine.includes(WHITESPACE_PLACEHOLDER)) {
      // For lines with placeholders, preserve the original spacing
      result += trimmedLine;
    } else if (
      trimmedLine.startsWith("<element:") ||
      trimmedLine.startsWith("<function:") ||
      trimmedLine.startsWith("{") ||
      trimmedLine.startsWith("<expression/>")
    ) {
      // When we encounter an element/function/expression
      // Add space only when:
      // 1. We have existing content AND
      // 2. Result doesn't already end with space or placeholder AND
      // 3. The result ends with a word character (indicating text) AND
      // 4. The element content starts with a space (indicating word continuation)
      const shouldAddSpace =
        result &&
        !result.endsWith(" ") &&
        !result.endsWith(WHITESPACE_PLACEHOLDER) &&
        /\w$/.test(result) &&
        // Check if element content starts with space by looking for "> " pattern
        trimmedLine.includes("> ");

      if (shouldAddSpace) {
        result += " ";
      }
      result += trimmedLine;
    } else {
      // For regular text content, ensure proper spacing
      // Only add space if the result doesn't already end with a space or placeholder
      if (
        result &&
        !result.endsWith(" ") &&
        !result.endsWith(WHITESPACE_PLACEHOLDER)
      ) {
        result += " ";
      }
      result += trimmedLine;
    }
  }

  // Collapse multiple spaces but preserve single spaces around placeholders
  result = result.replace(/\s{2,}/g, " ");
  return result.trim();
}

```

--------------------------------------------------------------------------------
/packages/sdk/src/index.spec.ts:
--------------------------------------------------------------------------------

```typescript
import { describe, it, expect, vi } from "vitest";
import { LingoDotDevEngine } from "./index";

describe("ReplexicaEngine", () => {
  it("should pass", () => {
    expect(1).toBe(1);
  });

  describe("localizeHtml", () => {
    it("should correctly extract, localize, and reconstruct HTML content", async () => {
      // Setup test HTML with various edge cases
      const inputHtml = `
<!DOCTYPE html>
<html>
  <head>
    <title>Test Page</title>
    <meta name="description" content="Page description">
  </head>
  <body>
    standalone text
    <div>
      <h1>Hello World</h1>
      <p>
        This is a paragraph with
        <a href="/test" title="Link title">a link</a>
        and an
        <img src="/test.jpg" alt="Test image">
        and some <b>bold <i>and italic</i></b> text.
      </p>
      <script>
        const doNotTranslate = "this text should be ignored";
      </script>
      <input type="text" placeholder="Enter text">
    </div>
  </body>
</html>`.trim();

      // Mock the internal localization method
      const engine = new LingoDotDevEngine({ apiKey: "test" });
      const mockLocalizeRaw = vi.spyOn(engine as any, "_localizeRaw");
      mockLocalizeRaw.mockImplementation(async (content: any) => {
        // Simulate translation by adding 'ES:' prefix to all strings
        return Object.fromEntries(
          Object.entries(content).map(([key, value]) => [key, `ES:${value}`]),
        );
      });

      // Execute the localization
      const result = await engine.localizeHtml(inputHtml, {
        sourceLocale: "en",
        targetLocale: "es",
      });

      // Verify the extracted content passed to _localizeRaw
      expect(mockLocalizeRaw).toHaveBeenCalledWith(
        {
          "head/0/0": "Test Page",
          "head/1#content": "Page description",
          "body/0": "standalone text",
          "body/1/0/0": "Hello World",
          "body/1/1/0": "This is a paragraph with",
          "body/1/1/1#title": "Link title",
          "body/1/1/1/0": "a link",
          "body/1/1/2": "and an",
          "body/1/1/3#alt": "Test image",
          "body/1/1/4": "and some",
          "body/1/1/5/0": "bold",
          "body/1/1/5/1/0": "and italic",
          "body/1/1/6": "text.",
          "body/1/3#placeholder": "Enter text",
        },
        {
          sourceLocale: "en",
          targetLocale: "es",
        },
        undefined,
        undefined, // AbortSignal
      );

      // Verify the final HTML structure
      expect(result).toContain('<html lang="es">');
      expect(result).toContain("<title>ES:Test Page</title>");
      expect(result).toContain('content="ES:Page description"');
      expect(result).toContain(">ES:standalone text<");
      expect(result).toContain("<h1>ES:Hello World</h1>");
      expect(result).toContain('title="ES:Link title"');
      expect(result).toContain('alt="ES:Test image"');
      expect(result).toContain('placeholder="ES:Enter text"');
      expect(result).toContain(
        'const doNotTranslate = "this text should be ignored"',
      );
    });
  });

  describe("localizeStringArray", () => {
    it("should localize an array of strings and maintain order", async () => {
      const engine = new LingoDotDevEngine({ apiKey: "test" });
      const mockLocalizeObject = vi.spyOn(engine, "localizeObject");
      mockLocalizeObject.mockImplementation(async (obj: any) => {
        // Simulate translation by adding 'ES:' prefix to all string values
        return Object.fromEntries(
          Object.entries(obj).map(([key, value]) => [key, `ES:${value}`]),
        );
      });

      const inputArray = ["Hello", "Goodbye", "How are you?"];

      const result = await engine.localizeStringArray(inputArray, {
        sourceLocale: "en",
        targetLocale: "es",
      });

      // Verify the mapped object was passed to localizeObject
      expect(mockLocalizeObject).toHaveBeenCalledWith(
        {
          item_0: "Hello",
          item_1: "Goodbye",
          item_2: "How are you?",
        },
        {
          sourceLocale: "en",
          targetLocale: "es",
        },
      );

      // Verify the result maintains the original order
      expect(result).toEqual(["ES:Hello", "ES:Goodbye", "ES:How are you?"]);
      expect(result).toHaveLength(3);
    });

    it("should handle empty array", async () => {
      const engine = new LingoDotDevEngine({ apiKey: "test" });
      const mockLocalizeObject = vi.spyOn(engine, "localizeObject");
      mockLocalizeObject.mockImplementation(async () => ({}));

      const result = await engine.localizeStringArray([], {
        sourceLocale: "en",
        targetLocale: "es",
      });

      expect(mockLocalizeObject).toHaveBeenCalledWith(
        {},
        {
          sourceLocale: "en",
          targetLocale: "es",
        },
      );

      expect(result).toEqual([]);
    });
  });

  describe("hints support", () => {
    it("should send hints to the backend API", async () => {
      // Mock global fetch
      const mockFetch = vi.fn();
      global.fetch = mockFetch as any;

      mockFetch.mockResolvedValue({
        ok: true,
        json: async () => ({
          data: {
            "brand-name": "Optimum",
            "team-label": "Equipo de la NHL",
          },
        }),
      });

      const engine = new LingoDotDevEngine({
        apiKey: "test-api-key",
        apiUrl: "https://test.api.url",
      });

      const hints = {
        "brand-name": ["This is a brand name and should not be translated"],
        "team-label": ["NHL stands for National Hockey League"],
      };

      await engine.localizeObject(
        {
          "brand-name": "Optimum",
          "team-label": "NHL Team",
        },
        {
          sourceLocale: "en",
          targetLocale: "es",
          hints,
        },
      );

      // Verify fetch was called with correct parameters
      expect(mockFetch).toHaveBeenCalledTimes(1);
      const fetchCall = mockFetch.mock.calls[0];
      expect(fetchCall[0]).toBe("https://test.api.url/i18n");

      // Parse the request body to verify hints are included
      const requestBody = JSON.parse(fetchCall[1].body);
      expect(requestBody.hints).toEqual(hints);
      expect(requestBody.data).toEqual({
        "brand-name": "Optimum",
        "team-label": "NHL Team",
      });
      expect(requestBody.locale).toEqual({
        source: "en",
        target: "es",
      });
    });

    it("should handle localizeObject without hints", async () => {
      const mockFetch = vi.fn();
      global.fetch = mockFetch as any;

      mockFetch.mockResolvedValue({
        ok: true,
        json: async () => ({
          data: {
            greeting: "Hola",
          },
        }),
      });

      const engine = new LingoDotDevEngine({
        apiKey: "test-api-key",
        apiUrl: "https://test.api.url",
      });

      await engine.localizeObject(
        {
          greeting: "Hello",
        },
        {
          sourceLocale: "en",
          targetLocale: "es",
        },
      );

      expect(mockFetch).toHaveBeenCalledTimes(1);
      const requestBody = JSON.parse(mockFetch.mock.calls[0][1].body);
      expect(requestBody.hints).toBeUndefined();
    });
  });
});

```

--------------------------------------------------------------------------------
/packages/cli/src/cli/localizer/explicit.ts:
--------------------------------------------------------------------------------

```typescript
import { createAnthropic } from "@ai-sdk/anthropic";
import { createGoogleGenerativeAI } from "@ai-sdk/google";
import { createOpenAI } from "@ai-sdk/openai";
import { createOpenRouter } from "@openrouter/ai-sdk-provider";
import { createMistral } from "@ai-sdk/mistral";
import { I18nConfig } from "@lingo.dev/_spec";
import chalk from "chalk";
import dedent from "dedent";
import { ILocalizer, LocalizerData } from "./_types";
import { LanguageModel, Message, generateText } from "ai";
import { colors } from "../constants";
import { jsonrepair } from "jsonrepair";
import { createOllama } from "ollama-ai-provider";

export default function createExplicitLocalizer(
  provider: NonNullable<I18nConfig["provider"]>,
): ILocalizer {
  const settings = provider.settings || {};

  switch (provider.id) {
    default:
      throw new Error(
        dedent`
          You're trying to use unsupported provider: ${chalk.dim(provider.id)}.

          To fix this issue:
          1. Switch to one of the supported providers, or
          2. Remove the ${chalk.italic(
            "provider",
          )} node from your i18n.json configuration to switch to ${chalk.hex(
            colors.green,
          )("Lingo.dev")}

          ${chalk.hex(colors.blue)("Docs: https://lingo.dev/go/docs")}
        `,
      );
    case "openai":
      return createAiSdkLocalizer({
        factory: (params) => createOpenAI(params).languageModel(provider.model),
        id: provider.id,
        prompt: provider.prompt,
        apiKeyName: "OPENAI_API_KEY",
        baseUrl: provider.baseUrl,
        settings,
      });
    case "anthropic":
      return createAiSdkLocalizer({
        factory: (params) =>
          createAnthropic(params).languageModel(provider.model),
        id: provider.id,
        prompt: provider.prompt,
        apiKeyName: "ANTHROPIC_API_KEY",
        baseUrl: provider.baseUrl,
        settings,
      });
    case "google":
      return createAiSdkLocalizer({
        factory: (params) =>
          createGoogleGenerativeAI(params).languageModel(provider.model),
        id: provider.id,
        prompt: provider.prompt,
        apiKeyName: "GOOGLE_API_KEY",
        baseUrl: provider.baseUrl,
        settings,
      });
    case "openrouter":
      return createAiSdkLocalizer({
        factory: (params) =>
          createOpenRouter(params).languageModel(provider.model),
        id: provider.id,
        prompt: provider.prompt,
        apiKeyName: "OPENROUTER_API_KEY",
        baseUrl: provider.baseUrl,
        settings,
      });
    case "ollama":
      return createAiSdkLocalizer({
        factory: (_params) => createOllama().languageModel(provider.model),
        id: provider.id,
        prompt: provider.prompt,
        skipAuth: true,
        settings,
      });
    case "mistral":
      return createAiSdkLocalizer({
        factory: (params) =>
          createMistral(params).languageModel(provider.model),
        id: provider.id,
        prompt: provider.prompt,
        apiKeyName: "MISTRAL_API_KEY",
        baseUrl: provider.baseUrl,
        settings,
      });
  }
}

function createAiSdkLocalizer(params: {
  factory: (params: { apiKey?: string; baseUrl?: string }) => LanguageModel;
  id: NonNullable<I18nConfig["provider"]>["id"];
  prompt: string;
  apiKeyName?: string;
  baseUrl?: string;
  skipAuth?: boolean;
  settings?: { temperature?: number };
}): ILocalizer {
  const skipAuth = params.skipAuth === true;

  const apiKey = process.env[params?.apiKeyName ?? ""];
  if ((!skipAuth && !apiKey) || !params.apiKeyName) {
    throw new Error(
      dedent`
        You're trying to use raw ${chalk.dim(params.id)} API for translation. ${
          params.apiKeyName
            ? `However, ${chalk.dim(
                params.apiKeyName,
              )} environment variable is not set.`
            : "However, that provider is unavailable."
        }

        To fix this issue:
        1. ${
          params.apiKeyName
            ? `Set ${chalk.dim(
                params.apiKeyName,
              )} in your environment variables`
            : "Set the environment variable for your provider (if required)"
        }, or
        2. Remove the ${chalk.italic(
          "provider",
        )} node from your i18n.json configuration to switch to ${chalk.hex(
          colors.green,
        )("Lingo.dev")}

        ${chalk.hex(colors.blue)("Docs: https://lingo.dev/go/docs")}
      `,
    );
  }

  const model = params.factory(
    skipAuth ? {} : { apiKey, baseUrl: params.baseUrl },
  );

  return {
    id: params.id,
    checkAuth: async () => {
      // For BYOK providers, auth check is not meaningful
      // Configuration validation happens in validateSettings
      return { authenticated: true, username: "anonymous" };
    },
    validateSettings: async () => {
      try {
        await generateText({
          model,
          ...params.settings,
          messages: [
            { role: "system", content: "You are an echo server" },
            { role: "user", content: "OK" },
            { role: "assistant", content: "OK" },
            { role: "user", content: "OK" },
          ],
        });

        return { valid: true };
      } catch (error) {
        const errorMessage =
          error instanceof Error ? error.message : String(error);
        return { valid: false, error: errorMessage };
      }
    },
    localize: async (input: LocalizerData) => {
      const systemPrompt = params.prompt
        .replaceAll("{source}", input.sourceLocale)
        .replaceAll("{target}", input.targetLocale);
      const shots = [
        [
          {
            sourceLocale: "en",
            targetLocale: "es",
            data: {
              message: "Hello, world!",
            },
          },
          {
            sourceLocale: "en",
            targetLocale: "es",
            data: {
              message: "Hola, mundo!",
            },
          },
        ],
      ];

      const payload = {
        sourceLocale: input.sourceLocale,
        targetLocale: input.targetLocale,
        data: input.processableData,
      };

      const response = await generateText({
        model,
        ...params.settings,
        messages: [
          { role: "system", content: systemPrompt },
          { role: "user", content: "OK" },
          ...shots.flatMap(
            ([userShot, assistantShot]) =>
              [
                { role: "user", content: JSON.stringify(userShot) },
                { role: "assistant", content: JSON.stringify(assistantShot) },
              ] as Message[],
          ),
          { role: "user", content: JSON.stringify(payload) },
        ],
      });

      const result = JSON.parse(response.text);

      // Handle both object and string responses
      if (typeof result.data === "object" && result.data !== null) {
        return result.data;
      }

      // Handle string responses - extract and repair JSON
      const index = result.data.indexOf("{");
      const lastIndex = result.data.lastIndexOf("}");
      const trimmed = result.data.slice(index, lastIndex + 1);
      const repaired = jsonrepair(trimmed);
      const finalResult = JSON.parse(repaired);

      return finalResult.data;
    },
  };
}

```

--------------------------------------------------------------------------------
/packages/cli/src/cli/loaders/po/index.ts:
--------------------------------------------------------------------------------

```typescript
import _ from "lodash";
import gettextParser from "gettext-parser";
import { GetTextTranslations } from "gettext-parser";
import { ILoader } from "../_types";
import { composeLoaders, createLoader } from "../_utils";

export type PoTranslationEntry = GetTextTranslations["translations"][""];
export type PoTranslationValue = { singular: string; plural: string | null };

export type PoLoaderParams = {
  multiline: boolean;
};

export default function createPoLoader(
  params: PoLoaderParams = { multiline: false },
): ILoader<string, Record<string, PoTranslationValue>> {
  return composeLoaders(createPoDataLoader(params), createPoContentLoader());
}

export function createPoDataLoader(
  params: PoLoaderParams,
): ILoader<string, PoTranslationEntry> {
  return createLoader({
    async pull(locale, input) {
      const parsedPo = gettextParser.po.parse(input);
      const result: PoTranslationEntry = {};
      const sections = input.split("\n\n").filter(Boolean);
      for (const section of sections) {
        const sectionPo = gettextParser.po.parse(section);
        // skip section with no translations (some sections might have only obsolete entries)
        if (Object.keys(sectionPo.translations).length === 0) {
          continue;
        }

        const contextKey = _.keys(sectionPo.translations)[0];
        const entries = sectionPo.translations[contextKey];
        Object.entries(entries).forEach(([msgid, entry]) => {
          if (msgid && entry.msgid) {
            const context = entry.msgctxt || "";
            const fullEntry = parsedPo.translations[context]?.[msgid];
            if (fullEntry) {
              result[msgid] = fullEntry;
            }
          }
        });
      }
      return result;
    },

    async push(locale, data, originalInput, originalLocale, pullInput) {
      // Parse each section to maintain structure
      const currentSections = pullInput?.split("\n\n").filter(Boolean) || [];
      const originalSections =
        originalInput?.split("\n\n").filter(Boolean) || [];
      const result = originalSections
        .map((section) => {
          const sectionPo = gettextParser.po.parse(section);
          // skip section with no translations (some sections might have only obsolete entries)
          if (Object.keys(sectionPo.translations).length === 0) {
            return null;
          }

          const contextKey = _.keys(sectionPo.translations)[0];
          const entries = sectionPo.translations[contextKey];
          const msgid = Object.keys(entries).find((key) => entries[key].msgid);
          if (!msgid) {
            // If the section is empty, try to find it in the current sections
            const currentSection = currentSections.find((cs) => {
              const csPo = gettextParser.po.parse(cs);
              const csContextKey = _.keys(csPo.translations)[0];
              const csEntries = csPo.translations[csContextKey];
              const csMsgid = Object.keys(csEntries).find(
                (key) => csEntries[key].msgid,
              );
              return csMsgid === msgid;
            });

            if (currentSection) {
              return currentSection;
            }
            return section;
          }
          if (data[msgid]) {
            const updatedPo = _.merge({}, sectionPo, {
              translations: {
                [contextKey]: {
                  [msgid]: {
                    msgstr: data[msgid].msgstr,
                  },
                },
              },
            });
            const updatedSection = gettextParser.po
              .compile(updatedPo, { foldLength: params.multiline ? 76 : false })
              .toString()
              .replace(
                [`msgid ""`, `msgstr "Content-Type: text/plain\\n"`].join("\n"),
                "",
              )
              .trim();
            return preserveCommentOrder(updatedSection, section);
          }
          return section.trim();
        })
        .filter(Boolean)
        .join("\n\n");
      return result;
    },
  });
}

export function createPoContentLoader(): ILoader<
  PoTranslationEntry,
  Record<string, PoTranslationEntry>
> {
  return createLoader({
    async pull(locale, input, initCtx, originalLocale) {
      const result = _.chain(input)
        .entries()
        .filter(([, entry]) => !!entry.msgid)
        .map(([, entry]) => {
          const singularFallback =
            locale === originalLocale ? entry.msgid : null;
          const pluralFallback =
            locale === originalLocale
              ? entry.msgid_plural || entry.msgid
              : null;
          const hasPlural = entry.msgstr.length > 1;
          return [
            entry.msgid,
            {
              singular: entry.msgstr[0] || singularFallback,
              plural: hasPlural
                ? ((entry.msgstr[1] || pluralFallback) as string | null)
                : null,
            },
          ];
        })
        .fromPairs()
        .value();
      return result;
    },
    async push(locale, data, originalInput) {
      const result = _.chain(originalInput)
        .entries()
        .map(([, entry]) => [
          entry.msgid,
          {
            ...entry,
            msgstr: [
              data[entry.msgid]?.singular,
              data[entry.msgid]?.plural || null,
            ].filter(Boolean),
          },
        ])
        .fromPairs()
        .value();

      return result;
    },
  });
}

function preserveCommentOrder(section: string, originalSection: string) {
  // Split both sections into lines
  const sectionLines = section.split(/\r?\n/);
  const originalLines = originalSection.split(/\r?\n/);

  // Helper: is a comment line
  const isComment = (line: string) => line.trim().startsWith("#");

  // Extract comment lines and their indices
  const sectionComments = sectionLines.filter(isComment);
  const nonCommentLines = sectionLines.filter((line) => !isComment(line));

  // If there are no comments in the section, return the section as is
  if (sectionComments.length <= 1) {
    return section;
  }

  // Extract the order of comment lines from the original section
  const originalCommentOrder = originalLines.filter(isComment);

  // Build a map from comment content (trimmed) to the actual comment line in the new section
  const commentMap = new Map<string, string>();
  for (const line of sectionComments) {
    commentMap.set(line.trim(), line);
  }

  // Reorder comments to match the original order, using the new section's comment lines
  const reorderedComments: string[] = [];
  for (const orig of originalCommentOrder) {
    const trimmed = orig.trim();
    if (commentMap.has(trimmed)) {
      reorderedComments.push(commentMap.get(trimmed)!);
      commentMap.delete(trimmed);
    }
  }
  // Add any new comments from the new section that weren't in the original, preserving their order
  for (const line of sectionComments) {
    if (!originalCommentOrder.some((orig) => orig.trim() === line.trim())) {
      reorderedComments.push(line);
    }
  }

  // Reconstruct the section: comments (in order) + non-comment lines (in order)
  return [...reorderedComments, ...nonCommentLines]
    .join("\n")
    .replace(/\n{3,}/g, "\n\n")
    .trim();
}

```

--------------------------------------------------------------------------------
/packages/cli/src/cli/cmd/ci/flows/pull-request.ts:
--------------------------------------------------------------------------------

```typescript
import { execSync } from "child_process";
import { InBranchFlow } from "./in-branch";
import { IIntegrationFlowOptions } from "./_base";

export class PullRequestFlow extends InBranchFlow {
  async preRun() {
    const canContinue = await super.preRun?.();
    if (!canContinue) {
      return false;
    }

    this.ora.start("Calculating automated branch name");
    this.i18nBranchName = this.calculatePrBranchName();
    this.ora.succeed(
      `Automated branch name calculated: ${this.i18nBranchName}`,
    );

    this.ora.start("Checking if branch exists");
    const branchExists = await this.checkBranchExistance(this.i18nBranchName);
    this.ora.succeed(branchExists ? "Branch exists" : "Branch does not exist");

    if (branchExists) {
      this.ora.start(`Checking out branch ${this.i18nBranchName}`);
      this.checkoutI18nBranch(this.i18nBranchName);
      this.ora.succeed(`Checked out branch ${this.i18nBranchName}`);

      this.ora.start(
        `Syncing with ${this.platformKit.platformConfig.baseBranchName}`,
      );
      this.syncI18nBranch();
      this.ora.succeed(`Checked out and synced branch ${this.i18nBranchName}`);
    } else {
      this.ora.start(`Creating branch ${this.i18nBranchName}`);
      this.createI18nBranch(this.i18nBranchName);
      this.ora.succeed(`Created branch ${this.i18nBranchName}`);
    }

    return true;
  }

  override async run(options: IIntegrationFlowOptions) {
    return super.run({
      force: true,
      ...options,
    });
  }

  async postRun() {
    if (!this.i18nBranchName) {
      throw new Error(
        "i18nBranchName is not set. Did you forget to call preRun?",
      );
    }

    this.ora.start("Checking if PR already exists");
    const pullRequestNumber = await this.ensureFreshPr(this.i18nBranchName);
    // await this.createLabelIfNotExists(pullRequestNumber, 'lingo.dev/i18n', false);
    this.ora.succeed(
      `Pull request ready: ${this.platformKit.buildPullRequestUrl(
        pullRequestNumber,
      )}`,
    );
  }

  private calculatePrBranchName(): string {
    return `lingo.dev/${this.platformKit.platformConfig.baseBranchName}`;
  }

  private async checkBranchExistance(prBranchName: string) {
    return this.platformKit.branchExists({
      branch: prBranchName,
    });
  }

  private async ensureFreshPr(i18nBranchName: string) {
    // Check if PR exists
    this.ora.start(
      `Checking for existing PR with head ${i18nBranchName} and base ${this.platformKit.platformConfig.baseBranchName}`,
    );
    let prNumber = await this.platformKit.getOpenPullRequestNumber({
      branch: i18nBranchName,
    });

    if (prNumber) {
      this.ora.succeed(`Existing PR found: #${prNumber}`);
    } else {
      // Create new PR
      this.ora.start(`Creating new PR`);
      prNumber = await this.platformKit.createPullRequest({
        head: i18nBranchName,
        title: this.platformKit.config.pullRequestTitle,
        body: this.getPrBodyContent(),
      });
      this.ora.succeed(`Created new PR: #${prNumber}`);
    }

    return prNumber;
  }

  private checkoutI18nBranch(i18nBranchName: string) {
    execSync(`git fetch origin ${i18nBranchName}`, { stdio: "inherit" });
    execSync(`git checkout -b ${i18nBranchName}`, {
      stdio: "inherit",
    });
  }

  private createI18nBranch(i18nBranchName: string) {
    try {
      execSync(
        `git fetch origin ${this.platformKit.platformConfig.baseBranchName}`,
        { stdio: "inherit" },
      );
      execSync(
        `git checkout -b ${i18nBranchName} origin/${this.platformKit.platformConfig.baseBranchName}`,
        {
          stdio: "inherit",
        },
      );
    } catch (error) {
      const errorMessage =
        error instanceof Error ? error.message : "Unknown error occurred";
      this.ora.fail(`Failed to create branch: ${errorMessage}`);
      this.ora.info(`
      Troubleshooting tips:
      1. Make sure you have permission to create branches
      2. Check if the branch already exists locally (try 'git branch -a')
      3. Verify connectivity to remote repository
    `);
      throw new Error(`Branch creation failed: ${errorMessage}`);
    }
  }

  private syncI18nBranch() {
    if (!this.i18nBranchName) {
      throw new Error("i18nBranchName is not set");
    }

    this.ora.start(
      `Fetching latest changes from ${this.platformKit.platformConfig.baseBranchName}`,
    );
    execSync(
      `git fetch origin ${this.platformKit.platformConfig.baseBranchName}`,
      { stdio: "inherit" },
    );
    this.ora.succeed(
      `Fetched latest changes from ${this.platformKit.platformConfig.baseBranchName}`,
    );

    try {
      this.ora.start("Attempting to rebase branch");
      execSync(
        `git rebase origin/${this.platformKit.platformConfig.baseBranchName}`,
        { stdio: "inherit" },
      );
      this.ora.succeed("Successfully rebased branch");
    } catch (error) {
      this.ora.warn("Rebase failed, falling back to alternative sync method");

      this.ora.start("Aborting failed rebase");
      execSync("git rebase --abort", { stdio: "inherit" });
      this.ora.succeed("Aborted failed rebase");

      this.ora.start(
        `Resetting to ${this.platformKit.platformConfig.baseBranchName}`,
      );
      execSync(
        `git reset --hard origin/${this.platformKit.platformConfig.baseBranchName}`,
        { stdio: "inherit" },
      );
      this.ora.succeed(
        `Reset to ${this.platformKit.platformConfig.baseBranchName}`,
      );

      this.ora.start("Restoring target files");
      const targetFiles = ["i18n.lock"];
      const targetFileNames = execSync(
        `npx lingo.dev@latest show files --target ${this.platformKit.platformConfig.baseBranchName}`,
        { encoding: "utf8" },
      )
        .split("\n")
        .filter(Boolean);
      targetFiles.push(...targetFileNames);
      execSync(`git fetch origin ${this.i18nBranchName}`, { stdio: "inherit" });
      for (const file of targetFiles) {
        try {
          // bring all files to the i18n branch's state
          execSync(`git checkout FETCH_HEAD -- ${file}`, { stdio: "inherit" });
        } catch (error) {
          // If file doesn't exist in FETCH_HEAD, that's okay - just skip it
          this.ora.warn(`Skipping non-existent file: ${file}`);
          continue;
        }
      }
      this.ora.succeed("Restored target files");
    }

    this.ora.start("Checking for changes to commit");
    const hasChanges = this.checkCommitableChanges();
    if (hasChanges) {
      execSync("git add .", { stdio: "inherit" });
      execSync(
        `git commit -m "chore: sync with ${this.platformKit.platformConfig.baseBranchName}" --no-verify`,
        {
          stdio: "inherit",
        },
      );
      this.ora.succeed("Committed additional changes");
    } else {
      this.ora.succeed("No changes to commit");
    }
  }

  private getPrBodyContent(): string {
    return `
Hey team,

[**Lingo.dev**](https://lingo.dev) here with fresh translations!

### In this update

- Added missing translations
- Performed brand voice, context and glossary checks
- Enhanced translations using Lingo.dev Localization Engine

### Next Steps

- [ ] Review the changes
- [ ] Merge when ready
    `.trim();
  }
}

```

--------------------------------------------------------------------------------
/packages/cli/src/cli/loaders/locked-patterns.spec.ts:
--------------------------------------------------------------------------------

```typescript
import { describe, it, expect } from "vitest";
import createLockedPatternsLoader from "./locked-patterns";
import dedent from "dedent";

describe("Locked Patterns Loader", () => {
  describe("Basic functionality", () => {
    it("should do nothing when no patterns are provided", async () => {
      const loader = createLockedPatternsLoader();
      loader.setDefaultLocale("en");

      const md = dedent`
        # Title
        
        Some content.
        
        !params
        
        !! parameter_name
        
        !type string
      `;

      const result = await loader.pull("en", md);

      const placeholderRegex = /---LOCKED-PATTERN-[0-9a-f]+---/g;
      const placeholders = result.match(placeholderRegex) || [];
      expect(placeholders.length).toBe(0); // No patterns should be replaced

      expect(result).toBe(md);

      const pushed = await loader.push("es", result);
      expect(pushed).toBe(md);
    });

    it("should preserve content matching patterns", async () => {
      const loader = createLockedPatternsLoader([
        "!params",
        "!! [\\w_]+",
        "!type [\\w<>\\[\\]\"',]+",
      ]);
      loader.setDefaultLocale("en");

      const md = dedent`
        # Title
        
        Some content.
        
        !params
        
        !! parameter_name
        
        !type string
      `;

      const result = await loader.pull("en", md);

      const placeholderRegex = /---LOCKED-PATTERN-[0-9a-f]+---/g;
      const placeholders = result.match(placeholderRegex) || [];
      expect(placeholders.length).toBe(3); // Three patterns should be replaced

      const sanitizedContent = result.replace(
        placeholderRegex,
        "---PLACEHOLDER---",
      );

      const expectedSanitized = dedent`
        # Title
        
        Some content.
        
        ---PLACEHOLDER---
        
        ---PLACEHOLDER---
        
        ---PLACEHOLDER---
      `;

      expect(sanitizedContent).toBe(expectedSanitized);

      const translated = result
        .replace("# Title", "# Título")
        .replace("Some content.", "Algún contenido.");

      const pushed = await loader.push("es", translated);

      const expectedPushed = dedent`
        # Título
        
        Algún contenido.
        
        !params
        
        !! parameter_name
        
        !type string
      `;

      expect(pushed).toBe(expectedPushed);
    });
  });

  describe("Real-world patterns", () => {
    it("should handle !hover syntax in code blocks", async () => {
      const loader = createLockedPatternsLoader([
        "// !hover[\\s\\S]*?(?=\\n|$)",
        "// !hover\\([\\d:]+\\)[\\s\\S]*?(?=\\n|$)",
      ]);
      loader.setDefaultLocale("en");

      const md = dedent`
        \`\`\`js
        const x = 1;
        const pubkey = "vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg";
        \`\`\`
      `;

      const result = await loader.pull("en", md);

      const placeholderRegex = /---LOCKED-PATTERN-[0-9a-f]+---/g;
      const placeholders = result.match(placeholderRegex) || [];
      expect(placeholders.length).toBe(0); // No patterns should be replaced

      const pushed = await loader.push("es", result);

      expect(pushed).toBe(md);
    });

    it("should handle !! parameter headings", async () => {
      const loader = createLockedPatternsLoader(["!! [\\w_]+"]);
      loader.setDefaultLocale("en");

      const md = dedent`
        # Parameters
        
        !! pubkey
        
        The public key of the account to query.
        
        !! encoding
        
        Encoding format for the returned Account data.
      `;

      const result = await loader.pull("en", md);

      const placeholderRegex = /---LOCKED-PATTERN-[0-9a-f]+---/g;
      const placeholders = result.match(placeholderRegex) || [];
      expect(placeholders.length).toBe(2); // Two patterns should be replaced

      const sanitizedContent = result.replace(
        placeholderRegex,
        "---PLACEHOLDER---",
      );

      const expectedSanitized = dedent`
        # Parameters
        
        ---PLACEHOLDER---
        
        The public key of the account to query.
        
        ---PLACEHOLDER---
        
        Encoding format for the returned Account data.
      `;

      expect(sanitizedContent).toBe(expectedSanitized);

      const translated = result
        .replace("# Parameters", "# Parámetros")
        .replace(
          "The public key of the account to query.",
          "La clave pública de la cuenta a consultar.",
        )
        .replace(
          "Encoding format for the returned Account data.",
          "Formato de codificación para los datos de la cuenta devueltos.",
        );

      const pushed = await loader.push("es", translated);

      const expectedPushed = dedent`
        # Parámetros
        
        !! pubkey
        
        La clave pública de la cuenta a consultar.
        
        !! encoding
        
        Formato de codificación para los datos de la cuenta devueltos.
      `;

      expect(pushed).toBe(expectedPushed);
    });

    it("should handle !type, !required, and !values declarations", async () => {
      const loader = createLockedPatternsLoader([
        "!! [\\w_]+",
        "!type [\\w<>\\[\\]\"',]+",
        "!required",
        "!values [\\s\\S]*?(?=\\n\\n|$)",
      ]);
      loader.setDefaultLocale("en");

      const md = dedent`
        !! pubkey
        
        !type string
        !required
        
        The public key of the account to query.
        
        !! encoding
        
        !type string
        !values "base58" (default), "base64", "jsonParsed"
        
        Encoding format for the returned Account data.
      `;

      const result = await loader.pull("en", md);

      const placeholderRegex = /---LOCKED-PATTERN-[0-9a-f]+---/g;
      const placeholders = result.match(placeholderRegex) || [];
      expect(placeholders.length).toBe(6); // Six patterns should be replaced

      const sanitizedContent = result.replace(
        placeholderRegex,
        "---PLACEHOLDER---",
      );

      const expectedSanitized = dedent`
        ---PLACEHOLDER---
        
        ---PLACEHOLDER---
        ---PLACEHOLDER---
        
        The public key of the account to query.
        
        ---PLACEHOLDER---
        
        ---PLACEHOLDER---
        ---PLACEHOLDER---
        
        Encoding format for the returned Account data.
      `;

      expect(sanitizedContent).toBe(expectedSanitized);

      const translated = result
        .replace(
          "The public key of the account to query.",
          "La clave pública de la cuenta a consultar.",
        )
        .replace(
          "Encoding format for the returned Account data.",
          "Formato de codificación para los datos de la cuenta devueltos.",
        );

      const pushed = await loader.push("es", translated);

      const expectedPushed = dedent`
        !! pubkey
        
        !type string
        !required
        
        La clave pública de la cuenta a consultar.
        
        !! encoding
        
        !type string
        !values "base58" (default), "base64", "jsonParsed"
        
        Formato de codificación para los datos de la cuenta devueltos.
      `;

      expect(pushed).toBe(expectedPushed);
    });
  });
});

```

--------------------------------------------------------------------------------
/packages/cli/src/cli/loaders/dato/api.ts:
--------------------------------------------------------------------------------

```typescript
import _ from "lodash";
import { ILoader } from "../_types";
import { createLoader } from "../_utils";
import createDatoClient, { DatoClient } from "./_utils";
import { SimpleSchemaTypes } from "@datocms/cma-client-node";
import { DatoConfig } from "./_base";
import inquirer from "inquirer";

export type DatoApiLoaderOutput = {
  [modelId: string]: {
    fields: SimpleSchemaTypes.Field[];
    records: SimpleSchemaTypes.Item[];
  };
};

export type DatoApiLoaderCtx = {
  models: {
    [modelId: string]: {
      fields: SimpleSchemaTypes.Field[];
      records: SimpleSchemaTypes.Item[];
    };
  };
};

export default function createDatoApiLoader(
  config: DatoConfig,
  onConfigUpdate: (config: DatoConfig) => void,
): ILoader<void, DatoApiLoaderOutput, DatoApiLoaderCtx> {
  const dato = createDatoClient({
    apiKey: process.env.DATO_API_TOKEN || "",
    projectId: config.project,
  });
  return createLoader({
    init: async () => {
      const result: DatoApiLoaderCtx = {
        models: {},
      };
      const updatedConfig = _.cloneDeep(config);
      console.log(`Initializing DatoCMS loader...`);

      const project = await dato.findProject();
      const modelChoices = await getModelChoices(dato, config);
      const selectedModels = await promptModelSelection(modelChoices);

      for (const modelId of selectedModels) {
        if (!updatedConfig.models[modelId]) {
          updatedConfig.models[modelId] = {
            fields: [],
            records: [],
          };
        }
      }

      for (const modelId of Object.keys(updatedConfig.models)) {
        if (!selectedModels.includes(modelId)) {
          delete updatedConfig.models[modelId];
        }
      }

      for (const modelId of _.keys(updatedConfig.models)) {
        const { modelName, fields } = await getModelFields(dato, modelId);

        if (fields.length > 0) {
          result.models[modelId] = { fields: [], records: [] };

          const fieldInfos = await getFieldDetails(dato, fields);
          const fieldChoices = createFieldChoices(fieldInfos);
          const selectedFields = await promptFieldSelection(
            modelName,
            fieldChoices,
          );

          for (const fieldInfo of fieldInfos) {
            const isLocalized = await updateFieldLocalization(
              dato,
              fieldInfo,
              selectedFields.includes(fieldInfo.id),
            );
            if (isLocalized) {
              result.models[modelId].fields.push(fieldInfo);
              updatedConfig.models[modelId].fields = _.uniq([
                ...(updatedConfig.models[modelId].fields || []),
                fieldInfo.api_key,
              ]);
            }
          }

          const records = await dato.findRecordsForModel(modelId);
          const recordChoices = createRecordChoices(
            records,
            config.models[modelId]?.records || [],
            project,
          );
          const selectedRecords = await promptRecordSelection(
            modelName,
            recordChoices,
          );

          result.models[modelId].records = records.filter((record) =>
            selectedRecords.includes(record.id),
          );
          updatedConfig.models[modelId].records = selectedRecords;
        }
      }
      console.log(`DatoCMS loader initialized.`);
      onConfigUpdate(updatedConfig);
      return result;
    },
    async pull(locale, input, initCtx) {
      const result: DatoApiLoaderOutput = {};

      for (const modelId of _.keys(initCtx?.models || {})) {
        let records = initCtx?.models[modelId].records || [];
        const recordIds = records.map((record) => record.id);
        records = await dato.findRecords(recordIds);
        console.log(`Fetched ${records.length} records for model ${modelId}`);

        if (records.length > 0) {
          result[modelId] = {
            fields: initCtx?.models?.[modelId]?.fields || [],
            records: records,
          };
        }
      }
      return result;
    },
    async push(locale, data, originalInput) {
      for (const modelId of _.keys(data)) {
        for (let i = 0; i < data[modelId].records.length; i++) {
          const record = data[modelId].records[i];
          console.log(
            `Updating record ${i + 1}/${
              data[modelId].records.length
            } for model ${modelId}...`,
          );
          await dato.updateRecord(record.id, record);
        }
      }
    },
  });
}

export async function getModelFields(dato: any, modelId: string) {
  const modelInfo = await dato.findModel(modelId);
  return {
    modelName: modelInfo.name,
    fields: _.filter(modelInfo.fields, (field) => field.type === "field"),
  };
}

export async function getFieldDetails(
  dato: DatoClient,
  fields: SimpleSchemaTypes.Field[],
) {
  return Promise.all(fields.map((field) => dato.findField(field.id)));
}

export function createFieldChoices(fieldInfos: SimpleSchemaTypes.Field[]) {
  return fieldInfos.map((field) => ({
    name: field.label,
    value: field.id,
    checked: field.localized,
  }));
}

export async function promptFieldSelection(modelName: string, choices: any[]) {
  const { selectedFields } = await inquirer.prompt([
    {
      type: "checkbox",
      name: "selectedFields",
      message: `Select fields to enable localization for model "${modelName}":`,
      choices,
      pageSize: process.stdout.rows - 4, // Subtract some rows for prompt text and margins
    },
  ]);
  return selectedFields;
}

export async function updateFieldLocalization(
  dato: any,
  fieldInfo: SimpleSchemaTypes.Field,
  shouldBeLocalized: boolean,
) {
  if (shouldBeLocalized !== fieldInfo.localized) {
    console.log(
      `${shouldBeLocalized ? "Enabling" : "Disabling"} localization for ${
        fieldInfo.label
      }...`,
    );
    await dato.updateField(fieldInfo.id, { localized: shouldBeLocalized });
  }
  return shouldBeLocalized;
}

export function createRecordChoices(
  records: SimpleSchemaTypes.Item[],
  selectedIds: string[] = [],
  project: SimpleSchemaTypes.Site,
) {
  return records.map((record) => ({
    name: `${record.id} - https://${project.internal_domain}/editor/item_types/${record.item_type.id}/items/${record.id}`,
    value: record.id,
    checked: selectedIds?.includes(record.id),
  }));
}

export async function promptRecordSelection(modelName: string, choices: any[]) {
  const { selectedRecords } = await inquirer.prompt([
    {
      type: "checkbox",
      name: "selectedRecords",
      message: `Select records to include for model "${modelName}":`,
      choices,
      pageSize: process.stdout.rows - 4, // Subtract some rows for prompt text and margins
    },
  ]);
  return selectedRecords;
}

export async function getModelChoices(dato: DatoClient, config: DatoConfig) {
  const models = await dato.findModels();
  return models.map((model) => ({
    name: `${model.name} (${model.api_key})`,
    value: model.id,
    checked: config.models[model.id] !== undefined,
    pageSize: process.stdout.rows - 4, // Subtract some rows for prompt text and margins
  }));
}

export async function promptModelSelection(choices: any[]) {
  const { selectedModels } = await inquirer.prompt([
    {
      type: "checkbox",
      name: "selectedModels",
      message: "Select models to include:",
      choices,
      pageSize: process.stdout.rows - 4, // Subtract some rows for prompt text and margins
    },
  ]);
  return selectedModels;
}

```

--------------------------------------------------------------------------------
/packages/react/src/core/component.tsx:
--------------------------------------------------------------------------------

```typescript
import {
  createElement,
  ReactNode,
  FunctionComponent,
  ReactElement,
} from "react";
import _ from "lodash";
import React, { useMemo } from "react";

export type LingoComponentProps = {
  [key: string]: any;
  $dictionary: any;
  $as: any;
  $fileKey: string;
  $entryKey: string;
  $values?: Record<string, ReactNode>;
  $elements?: Array<FunctionComponent<any>>;
  $functions?: Record<string, ReactNode[]>;
  $expressions?: ReactNode[];
};

export const LingoComponent = React.forwardRef(
  (props: Omit<LingoComponentProps, "ref">, ref: React.Ref<any>) => {
    const {
      $dictionary,
      $as,
      $fileKey,
      $entryKey,
      $variables,
      $elements,
      $functions,
      $expressions,
      ...rest
    } = props;
    const maybeValue = $dictionary?.files?.[$fileKey]?.entries?.[$entryKey];

    const children = useMemo(() => {
      return _.flow([
        (nodes) => ifNotEmpty(replaceElements, $elements, nodes),
        (nodes) => ifNotEmpty(replaceVariables, $variables, nodes),
        (nodes) => ifNotEmpty(replaceFunctions, $functions, nodes),
        (nodes) => ifNotEmpty(replaceExpressions, $expressions, nodes),
      ])([maybeValue ?? $entryKey]);
    }, [
      maybeValue,
      $entryKey,
      $elements,
      $variables,
      $functions,
      $expressions,
    ]);

    const isFragment = $as.toString() === "Symbol(react.fragment)";
    const isLingoComponent =
      typeof $as === "function" &&
      ($as.name === "LingoComponent" || $as.name === "LingoAttributeComponent");

    const elementProps = {
      ...rest,
      ...(isLingoComponent ? { $fileKey } : {}),
      ...(isFragment ? {} : { ref }),
    };

    return createElement($as, elementProps, ...children);
  },
);

// testValue needs to be cloned before passing to the callback for the first time only
// it can not be cloned inside the callback because it is called recursively
function ifNotEmpty<T>(
  callback: (nodes: ReactNode[], value: T) => ReactNode[],
  testValue: T,
  nodes: ReactNode[],
): ReactNode[] {
  return callback(nodes, _.clone(testValue));
}

function replaceVariables(
  nodes: ReactNode[],
  variables: Record<string, ReactNode>,
): ReactNode[] {
  if (_.isEmpty(variables)) {
    return nodes;
  }
  const segments = nodes.map((node) => {
    if (typeof node === "string") {
      const segments: ReactNode[] = [];
      let lastIndex = 0;
      const variableRegex = /{([\w\.\[\]]+)}/g;
      let match;

      while ((match = variableRegex.exec(node)) !== null) {
        if (match.index > lastIndex) {
          segments.push(node.slice(lastIndex, match.index));
        }

        const [fullMatch, name] = match;
        const value = variables[name];
        segments.push(value ?? fullMatch);

        lastIndex = match.index + fullMatch.length;
      }

      if (lastIndex < node.length) {
        segments.push(node.slice(lastIndex));
      }

      return segments;
    } else if (isReactElement(node)) {
      const props = node.props as { children?: ReactNode };
      return createElement(
        node.type,
        { ...props },
        ...replaceVariables(_.castArray(props.children || []), variables),
      );
    }
    return node;
  });

  return _.flatMap(segments);
}

function isReactElement(node: ReactNode): node is ReactElement {
  return (
    node !== null &&
    typeof node === "object" &&
    "type" in node &&
    "props" in node
  );
}

function replaceElements(
  nodes: ReactNode[],
  elements?: Array<FunctionComponent>,
  elementIndex: { current: number } = { current: 0 },
): ReactNode[] {
  const ELEMENT_PATTERN = /<element:([\w.]+)>(.*?)<\/element:\1>/gs;

  if (_.isEmpty(elements)) {
    return nodes.map((node) => {
      if (typeof node !== "string") return node;

      return node.replace(ELEMENT_PATTERN, (match, elementName, content) => {
        return content;
      });
    });
  }

  return nodes
    .map((node) => {
      if (typeof node !== "string") return node;

      const segments: ReactNode[] = [];
      let lastIndex = 0;

      let match;

      while ((match = ELEMENT_PATTERN.exec(node)) !== null) {
        if (match.index > lastIndex) {
          segments.push(node.slice(lastIndex, match.index));
        }

        const [fullMatch, elementName, content] = match;
        const Element = elements?.[elementIndex.current];
        elementIndex.current++;

        const innerContent = replaceElements([content], elements, elementIndex);
        if (Element) {
          segments.push(createElement(Element, {}, ...innerContent));
        } else {
          segments.push(...innerContent);
        }

        lastIndex = match.index + fullMatch.length;
      }

      if (lastIndex < node.length) {
        segments.push(node.slice(lastIndex));
      }

      return segments;
    })
    .flat();
}

function replaceFunctions(
  nodes: ReactNode[],
  functions: Record<string, ReactNode[]>,
): ReactNode[] {
  if (_.isEmpty(functions)) {
    return nodes;
  }

  const functionIndices: Record<string, number> = {};

  return nodes
    .map((node) => {
      if (typeof node === "string") {
        const segments: ReactNode[] = [];
        let lastIndex = 0;
        const functionRegex = /<function:([\w\.]+)\/>/g;
        let match;

        while ((match = functionRegex.exec(node)) !== null) {
          if (match.index > lastIndex) {
            segments.push(node.slice(lastIndex, match.index));
          }

          const [fullMatch, name] = match;
          if (!functionIndices[name]) {
            functionIndices[name] = 0;
          }
          const value = functions[name]?.[functionIndices[name]++];
          segments.push(value ?? fullMatch);

          lastIndex = match.index + fullMatch.length;
        }

        if (lastIndex < node.length) {
          segments.push(node.slice(lastIndex));
        }

        return segments;
      } else if (isReactElement(node)) {
        const props = node.props as { children?: ReactNode };
        return createElement(
          node.type,
          { ...props },
          ...replaceFunctions(_.castArray(props.children || []), functions),
        );
      }
      return node;
    })
    .flat();
}

function replaceExpressions(
  nodes: ReactNode[],
  expressions: ReactNode[],
): ReactNode[] {
  if (_.isEmpty(expressions)) {
    return nodes;
  }

  let expressionIndex = 0;

  function processWithIndex(nodeList: ReactNode[]): ReactNode[] {
    return nodeList
      .map((node) => {
        if (typeof node === "string") {
          const segments: ReactNode[] = [];
          let lastIndex = 0;
          const expressionRegex = /<expression\/>/g;
          let match;

          while ((match = expressionRegex.exec(node)) !== null) {
            if (match.index > lastIndex) {
              segments.push(node.slice(lastIndex, match.index));
            }

            const value = expressions[expressionIndex++];
            segments.push(value ?? match[0]);

            lastIndex = match.index + match[0].length;
          }

          if (lastIndex < node.length) {
            segments.push(node.slice(lastIndex));
          }

          return segments;
        } else if (isReactElement(node)) {
          const props = node.props as { children?: ReactNode };
          return createElement(
            node.type,
            { ...props },
            ...processWithIndex(_.castArray(props.children || [])),
          );
        }
        return node;
      })
      .flat();
  }

  return processWithIndex(nodes);
}

```

--------------------------------------------------------------------------------
/packages/cli/src/cli/loaders/icu-safety.spec.ts:
--------------------------------------------------------------------------------

```typescript
import { describe, it, expect } from "vitest";
import { isICUPluralObject, isPluralFormsObject } from "./xcode-xcstrings-icu";

/**
 * Safety tests to ensure ICU type guards don't falsely match normal data
 * from other bucket types (android, json, yaml, etc.)
 */
describe("ICU type guards - Safety for other bucket types", () => {
  describe("isICUPluralObject", () => {
    it("should return false for regular strings", () => {
      expect(isICUPluralObject("Hello world")).toBe(false);
      expect(isICUPluralObject("")).toBe(false);
      expect(isICUPluralObject("a string with {braces}")).toBe(false);
    });

    it("should return false for numbers", () => {
      expect(isICUPluralObject(42)).toBe(false);
      expect(isICUPluralObject(0)).toBe(false);
      expect(isICUPluralObject(-1)).toBe(false);
    });

    it("should return false for arrays", () => {
      expect(isICUPluralObject([])).toBe(false);
      expect(isICUPluralObject(["one", "two"])).toBe(false);
      expect(isICUPluralObject([{ icu: "fake" }])).toBe(false);
    });

    it("should return false for null/undefined", () => {
      expect(isICUPluralObject(null)).toBe(false);
      expect(isICUPluralObject(undefined)).toBe(false);
    });

    it("should return false for plain objects (json, yaml data)", () => {
      expect(isICUPluralObject({ name: "John", age: 30 })).toBe(false);
      expect(isICUPluralObject({ key: "value" })).toBe(false);
      expect(isICUPluralObject({ nested: { data: "here" } })).toBe(false);
    });

    it("should return false for objects with 'icu' property but wrong format", () => {
      // Must have valid ICU MessageFormat pattern
      expect(isICUPluralObject({ icu: "not valid icu" })).toBe(false);
      expect(isICUPluralObject({ icu: "{just braces}" })).toBe(false);
      expect(isICUPluralObject({ icu: "plain text" })).toBe(false);
    });

    it("should return false for android plurals format", () => {
      // Android uses different structure
      expect(
        isICUPluralObject({
          quantity: {
            one: "1 item",
            other: "%d items",
          },
        }),
      ).toBe(false);
    });

    it("should return false for stringsdict format", () => {
      // iOS stringsdict uses different structure
      expect(
        isICUPluralObject({
          NSStringFormatSpecTypeKey: "NSStringPluralRuleType",
          NSStringFormatValueTypeKey: "d",
        }),
      ).toBe(false);
    });

    it("should return TRUE only for valid ICU plural objects", () => {
      // Valid ICU object
      expect(
        isICUPluralObject({
          icu: "{count, plural, one {1 item} other {# items}}",
          _meta: {
            variables: {
              count: {
                format: "%d",
                role: "plural",
              },
            },
          },
        }),
      ).toBe(true);

      // Valid ICU object without metadata
      expect(
        isICUPluralObject({
          icu: "{count, plural, one {1 item} other {# items}}",
        }),
      ).toBe(true);
    });
  });

  describe("isPluralFormsObject", () => {
    it("should return false for regular strings", () => {
      expect(isPluralFormsObject("Hello world")).toBe(false);
      expect(isPluralFormsObject("")).toBe(false);
    });

    it("should return false for numbers", () => {
      expect(isPluralFormsObject(42)).toBe(false);
      expect(isPluralFormsObject(0)).toBe(false);
    });

    it("should return false for arrays", () => {
      expect(isPluralFormsObject([])).toBe(false);
      expect(isPluralFormsObject(["one", "two"])).toBe(false);
    });

    it("should return false for null/undefined", () => {
      expect(isPluralFormsObject(null)).toBe(false);
      expect(isPluralFormsObject(undefined)).toBe(false);
    });

    it("should return false for plain objects (json, yaml data)", () => {
      expect(isPluralFormsObject({ name: "John", age: 30 })).toBe(false);
      expect(isPluralFormsObject({ key: "value" })).toBe(false);
      expect(isPluralFormsObject({ nested: { data: "here" } })).toBe(false);
    });

    it("should return false for objects with non-CLDR keys", () => {
      expect(isPluralFormsObject({ quantity: "one" })).toBe(false);
      expect(isPluralFormsObject({ count: "1", total: "10" })).toBe(false);
      expect(isPluralFormsObject({ first: "a", second: "b" })).toBe(false);
    });

    it("should return false for objects with CLDR keys but non-string values", () => {
      expect(isPluralFormsObject({ one: 1, other: 2 })).toBe(false);
      expect(isPluralFormsObject({ one: { nested: "obj" } })).toBe(false);
      expect(isPluralFormsObject({ one: ["array"] })).toBe(false);
    });

    it("should return false for objects missing 'other' form", () => {
      // 'other' is required in all locales per CLDR
      expect(isPluralFormsObject({ one: "1 item" })).toBe(false);
      expect(isPluralFormsObject({ zero: "0 items", one: "1 item" })).toBe(
        false,
      );
    });

    it("should return TRUE only for valid CLDR plural objects", () => {
      // Valid with required 'other' form
      expect(
        isPluralFormsObject({
          one: "1 item",
          other: "# items",
        }),
      ).toBe(true);

      // Valid with multiple CLDR forms
      expect(
        isPluralFormsObject({
          zero: "No items",
          one: "1 item",
          few: "A few items",
          many: "Many items",
          other: "# items",
        }),
      ).toBe(true);
    });
  });

  describe("Real-world bucket type data", () => {
    it("JSON bucket - should not match ICU guards", () => {
      const jsonData = {
        welcome: "Welcome!",
        user: {
          name: "John",
          greeting: "Hello {name}",
        },
        count: 42,
      };

      expect(isICUPluralObject(jsonData)).toBe(false);
      expect(isICUPluralObject(jsonData.user)).toBe(false);
      expect(isPluralFormsObject(jsonData)).toBe(false);
      expect(isPluralFormsObject(jsonData.user)).toBe(false);
    });

    it("YAML bucket - should not match ICU guards", () => {
      const yamlData = {
        app: {
          title: "My App",
          description: "An awesome app",
        },
        messages: {
          error: "Something went wrong",
          success: "Operation completed",
        },
      };

      expect(isICUPluralObject(yamlData.app)).toBe(false);
      expect(isICUPluralObject(yamlData.messages)).toBe(false);
      expect(isPluralFormsObject(yamlData.app)).toBe(false);
      expect(isPluralFormsObject(yamlData.messages)).toBe(false);
    });

    it("Android bucket - should not match ICU guards", () => {
      const androidData = {
        app_name: "MyApp",
        welcome_message: "Welcome %s!",
        item_count: {
          // Android format, not CLDR
          "@quantity": "plural",
          one: "1 item",
          other: "%d items",
        },
      };

      expect(isICUPluralObject(androidData["item_count"])).toBe(false);
      // This might match isPluralFormsObject if it has 'other' - that's intentional
      // Android plurals ARE CLDR plural forms
    });

    it("Properties bucket - should not match ICU guards", () => {
      const propertiesData = {
        "app.title": "My Application",
        "app.version": "1.0.0",
        "user.greeting": "Hello {0}",
      };

      for (const value of Object.values(propertiesData)) {
        expect(isICUPluralObject(value)).toBe(false);
        expect(isPluralFormsObject(value)).toBe(false);
      }
    });
  });
});

```

--------------------------------------------------------------------------------
/packages/sdk/src/abort-controller.specs.ts:
--------------------------------------------------------------------------------

```typescript
import { describe, it, expect, vi, beforeEach } from "vitest";
import { LingoDotDevEngine } from "../src/index.js";

// Mock fetch globally
global.fetch = vi.fn();

describe("AbortController Support", () => {
  let engine: LingoDotDevEngine;

  beforeEach(() => {
    engine = new LingoDotDevEngine({
      apiKey: "test-key",
      apiUrl: "https://test.api.com",
    });
    vi.clearAllMocks();
  });

  describe("localizeText", () => {
    it("should pass AbortSignal to fetch", async () => {
      const controller = new AbortController();
      const mockResponse = {
        ok: true,
        json: vi.fn().mockResolvedValue({ data: { text: "Hola" } }),
      };
      (global.fetch as any).mockResolvedValue(mockResponse);

      await engine.localizeText(
        "Hello",
        { sourceLocale: "en", targetLocale: "es" },
        undefined,
        controller.signal,
      );

      expect(global.fetch).toHaveBeenCalledWith(
        "https://test.api.com/i18n",
        expect.objectContaining({
          signal: controller.signal,
        }),
      );
    });

    it("should throw error when operation is aborted", async () => {
      const controller = new AbortController();
      controller.abort();

      await expect(
        engine.localizeText(
          "Hello",
          { sourceLocale: "en", targetLocale: "es" },
          undefined,
          controller.signal,
        ),
      ).rejects.toThrow("Operation was aborted");
    });
  });

  describe("localizeObject", () => {
    it("should pass AbortSignal to internal method", async () => {
      const controller = new AbortController();
      const mockResponse = {
        ok: true,
        json: vi.fn().mockResolvedValue({ data: { key: "valor" } }),
      };
      (global.fetch as any).mockResolvedValue(mockResponse);

      await engine.localizeObject(
        { key: "value" },
        { sourceLocale: "en", targetLocale: "es" },
        undefined,
        controller.signal,
      );

      expect(global.fetch).toHaveBeenCalledWith(
        "https://test.api.com/i18n",
        expect.objectContaining({
          signal: controller.signal,
        }),
      );
    });
  });

  describe("localizeHtml", () => {
    it("should pass AbortSignal to internal method", async () => {
      const controller = new AbortController();
      const mockResponse = {
        ok: true,
        json: vi.fn().mockResolvedValue({ data: { "body/0": "Hola" } }),
      };
      (global.fetch as any).mockResolvedValue(mockResponse);

      // Mock JSDOM
      const mockJSDOM = {
        JSDOM: vi.fn().mockImplementation(() => ({
          window: {
            document: {
              documentElement: {
                setAttribute: vi.fn(),
              },
              head: {
                childNodes: [],
              },
              body: {
                childNodes: [
                  {
                    nodeType: 3,
                    textContent: "Hello",
                    parentElement: null,
                  },
                ],
              },
            },
          },
          serialize: vi.fn().mockReturnValue("<html><body>Hola</body></html>"),
        })),
      };

      // Mock dynamic import
      vi.doMock("jsdom", () => mockJSDOM);

      await engine.localizeHtml(
        "<html><body>Hello</body></html>",
        { sourceLocale: "en", targetLocale: "es" },
        undefined,
        controller.signal,
      );

      expect(global.fetch).toHaveBeenCalledWith(
        "https://test.api.com/i18n",
        expect.objectContaining({
          signal: controller.signal,
        }),
      );
    });
  });

  describe("localizeChat", () => {
    it("should pass AbortSignal to internal method", async () => {
      const controller = new AbortController();
      const mockResponse = {
        ok: true,
        json: vi.fn().mockResolvedValue({ data: { chat_0: "Hola" } }),
      };
      (global.fetch as any).mockResolvedValue(mockResponse);

      await engine.localizeChat(
        [{ name: "User", text: "Hello" }],
        { sourceLocale: "en", targetLocale: "es" },
        undefined,
        controller.signal,
      );

      expect(global.fetch).toHaveBeenCalledWith(
        "https://test.api.com/i18n",
        expect.objectContaining({
          signal: controller.signal,
        }),
      );
    });
  });

  describe("batchLocalizeText", () => {
    it("should pass AbortSignal to individual localizeText calls", async () => {
      const controller = new AbortController();
      const mockResponse = {
        ok: true,
        json: vi.fn().mockResolvedValue({ data: { text: "Hola" } }),
      };
      (global.fetch as any).mockResolvedValue(mockResponse);

      await engine.batchLocalizeText(
        "Hello",
        {
          sourceLocale: "en",
          targetLocales: ["es", "fr"],
        },
        controller.signal,
      );

      expect(global.fetch).toHaveBeenCalledTimes(2);
      expect(global.fetch).toHaveBeenCalledWith(
        "https://test.api.com/i18n",
        expect.objectContaining({
          signal: controller.signal,
        }),
      );
    });
  });

  describe("recognizeLocale", () => {
    it("should pass AbortSignal to fetch", async () => {
      const controller = new AbortController();
      const mockResponse = {
        ok: true,
        json: vi.fn().mockResolvedValue({ locale: "en" }),
      };
      (global.fetch as any).mockResolvedValue(mockResponse);

      await engine.recognizeLocale("Hello world", controller.signal);

      expect(global.fetch).toHaveBeenCalledWith(
        "https://test.api.com/recognize",
        expect.objectContaining({
          signal: controller.signal,
        }),
      );
    });
  });

  describe("whoami", () => {
    it("should pass AbortSignal to fetch", async () => {
      const controller = new AbortController();
      const mockResponse = {
        ok: true,
        json: vi
          .fn()
          .mockResolvedValue({ email: "[email protected]", id: "123" }),
      };
      (global.fetch as any).mockResolvedValue(mockResponse);

      await engine.whoami(controller.signal);

      expect(global.fetch).toHaveBeenCalledWith(
        "https://test.api.com/whoami",
        expect.objectContaining({
          signal: controller.signal,
        }),
      );
    });
  });

  describe("Batch operations abortion", () => {
    it("should abort between chunks in _localizeRaw", async () => {
      const controller = new AbortController();

      // Create a large payload that will be split into multiple chunks
      const largePayload: Record<string, string> = {};
      for (let i = 0; i < 100; i++) {
        largePayload[`key${i}`] = `value${i}`.repeat(50); // Make values long enough
      }

      const mockResponse = {
        ok: true,
        json: vi.fn().mockResolvedValue({ data: { key0: "processed" } }),
      };

      // Mock fetch to abort the controller after the first call
      let callCount = 0;
      (global.fetch as any).mockImplementation(async () => {
        callCount++;
        if (callCount === 1) {
          // Abort immediately after first call starts
          controller.abort();
        }
        return mockResponse;
      });

      await expect(
        engine._localizeRaw(
          largePayload,
          { sourceLocale: "en", targetLocale: "es" },
          undefined,
          controller.signal,
        ),
      ).rejects.toThrow("Operation was aborted");

      // Should have made at least one call
      expect(callCount).toBeGreaterThan(0);
    });
  });
});

```

--------------------------------------------------------------------------------
/packages/cli/src/cli/utils/delta.spec.ts:
--------------------------------------------------------------------------------

```typescript
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { createDeltaProcessor } from "./delta";
import * as path from "path";
import { tryReadFile, writeFile, checkIfFileExists } from "../utils/fs";
import YAML from "yaml";

// Setup mocks before importing the module
vi.mock("object-hash", () => ({
  MD5: vi.fn().mockImplementation((value) => `mocked-hash-${value}`),
}));

// Mock dependencies
vi.mock("path", () => ({
  join: vi.fn(() => "/mocked/path/i18n.lock"),
}));

vi.mock("../utils/fs", () => ({
  tryReadFile: vi.fn(),
  writeFile: vi.fn(),
  checkIfFileExists: vi.fn(),
}));

// Import MD5 after mocking
import { MD5 } from "object-hash";

describe("createDeltaProcessor", () => {
  const mockFileKey = "test-file-key";
  let mockProcessor;

  beforeEach(() => {
    vi.clearAllMocks();
    // Reset the mock implementation for MD5
    (MD5 as any).mockImplementation((value) => `mocked-hash-${value}`);
    // Create a new processor instance for each test
    mockProcessor = createDeltaProcessor(mockFileKey);
  });

  describe("checkIfLockExists", () => {
    it("should call checkIfFileExists with the correct path", async () => {
      (checkIfFileExists as any).mockResolvedValue(true);

      const result = await mockProcessor.checkIfLockExists();

      expect(path.join).toHaveBeenCalledWith(process.cwd(), "i18n.lock");
      expect(checkIfFileExists).toHaveBeenCalledWith("/mocked/path/i18n.lock");
      expect(result).toBe(true);
    });
  });

  describe("calculateDelta", () => {
    it("should correctly identify added keys", async () => {
      const sourceData = { key1: "value1", key2: "value2" };
      const targetData = { key1: "value1" };
      const checksums = { key1: "checksum1" };

      const result = await mockProcessor.calculateDelta({
        sourceData,
        targetData,
        checksums,
      });

      expect(result.added).toEqual(["key2"]);
      expect(result.hasChanges).toBe(true);
    });

    it("should correctly identify removed keys", async () => {
      const sourceData = { key1: "value1" };
      const targetData = { key1: "value1", key2: "value2" };
      const checksums = { key1: "checksum1", key2: "checksum2" };

      const result = await mockProcessor.calculateDelta({
        sourceData,
        targetData,
        checksums,
      });

      expect(result.removed).toEqual(["key2"]);
      expect(result.hasChanges).toBe(true);
    });

    it("should correctly identify updated keys", async () => {
      const sourceData = { key1: "new-value1" };
      const targetData = { key1: "value1" };
      const checksums = { key1: "old-checksum" }; // Different from MD5(new-value1)

      const result = await mockProcessor.calculateDelta({
        sourceData,
        targetData,
        checksums,
      });

      expect(result.updated).toContain("key1");
      expect(result.hasChanges).toBe(true);
    });

    it("should correctly identify renamed keys", async () => {
      // Mock to simulate a renamed key (same hash but different key name)
      (MD5 as any).mockImplementation((value) =>
        value === "value1" ? "same-hash" : "other-hash",
      );

      const sourceData = { newKey: "value1" };
      const targetData = { oldKey: "something" };
      const checksums = { oldKey: "same-hash" };

      const result = await mockProcessor.calculateDelta({
        sourceData,
        targetData,
        checksums,
      });

      expect(result.renamed).toEqual([["oldKey", "newKey"]]);
      expect(result.added).toEqual([]);
      expect(result.removed).toEqual([]);
      expect(result.hasChanges).toBe(true);
    });

    it("should return hasChanges=false when there are no changes", async () => {
      const sourceData = { key1: "value1" };
      const targetData = { key1: "value1" };

      // Mock to simulate matching checksums
      (MD5 as any).mockImplementation((value) => "matching-hash");
      const checksums = { key1: "matching-hash" };

      const result = await mockProcessor.calculateDelta({
        sourceData,
        targetData,
        checksums,
      });

      expect(result.added).toEqual([]);
      expect(result.removed).toEqual([]);
      expect(result.updated).toEqual([]);
      expect(result.renamed).toEqual([]);
      expect(result.hasChanges).toBe(false);
    });
  });

  describe("loadLock", () => {
    it("should return default lock data when no file exists", async () => {
      (tryReadFile as any).mockReturnValue(null);

      const result = await mockProcessor.loadLock();

      expect(result).toEqual({
        version: 1,
        checksums: {},
      });
    });

    it("should parse and return lock file data when it exists", async () => {
      const mockYaml = "version: 1\nchecksums:\n  fileId:\n    key1: checksum1";
      (tryReadFile as any).mockReturnValue(mockYaml);

      const result = await mockProcessor.loadLock();

      expect(result).toEqual({
        version: 1,
        checksums: {
          fileId: {
            key1: "checksum1",
          },
        },
      });
    });
  });

  describe("saveLock", () => {
    it("should stringify and save lock data", async () => {
      const lockData = {
        version: 1 as const,
        checksums: {
          fileId: {
            key1: "checksum1",
          },
        },
      };

      await mockProcessor.saveLock(lockData);

      expect(writeFile).toHaveBeenCalledWith(
        "/mocked/path/i18n.lock",
        expect.any(String),
      );

      // Verify the YAML conversion is correct
      const yamlArg = (writeFile as any).mock.calls[0][1];
      const parsedBack = YAML.parse(yamlArg);
      expect(parsedBack).toEqual(lockData);
    });
  });

  describe("loadChecksums and saveChecksums", () => {
    it("should load checksums for the specific file key", async () => {
      // Reset MD5 implementation for fileKey hash
      (MD5 as any).mockImplementation((value) => "mocked-hash");

      // Mock the loadLock to return specific data
      const mockLockData = {
        version: 1 as const,
        checksums: {
          "mocked-hash": {
            key1: "checksum1",
          },
        },
      };

      vi.spyOn(mockProcessor, "loadLock").mockResolvedValue(mockLockData);

      const result = await mockProcessor.loadChecksums();

      expect(result).toEqual({
        key1: "checksum1",
      });
    });

    it("should save checksums for the specific file key", async () => {
      const checksums = { key1: "checksum1" };

      // Reset MD5 implementation for fileKey hash
      (MD5 as any).mockImplementation((value) => "mocked-hash");

      // Mock loadLock and saveLock
      const mockLockData = {
        version: 1 as const,
        checksums: {},
      };
      vi.spyOn(mockProcessor, "loadLock").mockResolvedValue(mockLockData);
      const saveLockSpy = vi
        .spyOn(mockProcessor, "saveLock")
        .mockResolvedValue(void 0);

      await mockProcessor.saveChecksums(checksums);

      expect(saveLockSpy).toHaveBeenCalledWith({
        version: 1,
        checksums: {
          "mocked-hash": checksums,
        },
      });
    });
  });

  describe("createChecksums", () => {
    it("should create checksums from source data", async () => {
      const sourceData = {
        key1: "value1",
        key2: "value2",
      };

      // Setup counter for mock
      let counter = 0;
      (MD5 as any).mockImplementation((value) => `mock-hash-${++counter}`);

      const result = await mockProcessor.createChecksums(sourceData);

      expect(result).toEqual({
        key1: "mock-hash-1",
        key2: "mock-hash-2",
      });
    });
  });
});

```

--------------------------------------------------------------------------------
/legacy/sdk/CHANGELOG.md:
--------------------------------------------------------------------------------

```markdown
# @replexica/sdk

## 0.7.11

### Patch Changes

- [`5dee9ee`](https://github.com/lingodotdev/lingo.dev/commit/5dee9ee743fbef489fbe342597a768ebd59e5f67) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - add proxies to legacy packages

- [`63eb57b`](https://github.com/lingodotdev/lingo.dev/commit/63eb57b8f4cc37605be196085fafbbfdab71cce5) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - add deprecation message to legacy package jsons

- [`bbf7760`](https://github.com/lingodotdev/lingo.dev/commit/bbf7760580f1631805d68612053ebcd4601bb02b) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - add deprecation warning to the legacy package proxies

- Updated dependencies [[`b4c7f1e`](https://github.com/lingodotdev/lingo.dev/commit/b4c7f1e86334d229bee62219c26f30d0b523926d)]:
  - [email protected]

## 0.7.10

### Patch Changes

- Updated dependencies [[`003344f`](https://github.com/lingodotdev/lingo.dev/commit/003344ffcca98a391a298707f18476971c4d4c2b)]:
  - @replexica/[email protected]

## 0.7.9

### Patch Changes

- Updated dependencies [[`a2ada16`](https://github.com/lingodotdev/lingo.dev/commit/a2ada16ecf4cd559d3486f0e4756d58808194f7e)]:
  - @replexica/[email protected]

## 0.7.8

### Patch Changes

- Updated dependencies [[`e6521b8`](https://github.com/lingodotdev/lingo.dev/commit/e6521b86637c254c011aba89a3558802c04ab3ca)]:
  - @replexica/[email protected]

## 0.7.7

### Patch Changes

- Updated dependencies [[`cff3c4e`](https://github.com/lingodotdev/lingo.dev/commit/cff3c4eb1a40f82a9c4c095e49cfd9fce053b048)]:
  - @replexica/[email protected]

## 0.7.6

### Patch Changes

- Updated dependencies [[`58d7b35`](https://github.com/lingodotdev/lingo.dev/commit/58d7b3567e51cc3ef0fad0288c13451381b95a98)]:
  - @replexica/[email protected]

## 0.7.5

### Patch Changes

- Updated dependencies [[`9cf5299`](https://github.com/lingodotdev/lingo.dev/commit/9cf5299f7efbef70fd83f95177eac49b4d8f8007), [`3ab5de6`](https://github.com/lingodotdev/lingo.dev/commit/3ab5de66d8a913297b46095c2e73823124cc8c5b)]:
  - @replexica/[email protected]

## 0.7.4

### Patch Changes

- Updated dependencies [[`1556977`](https://github.com/lingodotdev/lingo.dev/commit/1556977332a6f949100283bfa8c9a9ff5e74b156)]:
  - @replexica/[email protected]

## 0.7.3

### Patch Changes

- [`cbef8f3`](https://github.com/lingodotdev/lingo.dev/commit/cbef8f3cafdc955d61053ce885d98e425acb668d) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - moved jsdom import into the html handler function

## 0.7.2

### Patch Changes

- Updated dependencies [[`5cb3c93`](https://github.com/lingodotdev/lingo.dev/commit/5cb3c930fff6e30cff5cc2266b794f75a0db646d)]:
  - @replexica/[email protected]

## 0.7.1

### Patch Changes

- [`db819a4`](https://github.com/lingodotdev/lingo.dev/commit/db819a42412ceb67fedbe729b7d018952686d60b) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - reduce default batch size to avoid hitting rate limits

- [`2c5cbcf`](https://github.com/lingodotdev/lingo.dev/commit/2c5cbcfbf6feb28440255cdea0818c8cefa61d91) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - filter out non extistent keys

## 0.7.0

### Minor Changes

- [`c42dc2d`](https://github.com/lingodotdev/lingo.dev/commit/c42dc2d5b4efe95e804b5a7e7f6d354cf8622dc7) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - add `batchLocalizeText` to sdk

## 0.6.0

### Minor Changes

- [`a71a88e`](https://github.com/lingodotdev/lingo.dev/commit/a71a88e5c8bd6601b0838c381433a87763142801) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - fast mode

### Patch Changes

- [`f0a77ad`](https://github.com/lingodotdev/lingo.dev/commit/f0a77ad774a01c30e7e9bc5a0253638176332fd2) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - updated default batch size limits in the SDK

## 0.5.0

### Minor Changes

- [`ebf44cb`](https://github.com/lingodotdev/lingo.dev/commit/ebf44cbb462516abfe660c295c04627796c5a3a7) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - implement recognize locale

- [`42d0a5a`](https://github.com/lingodotdev/lingo.dev/commit/42d0a5a7a53e296192a31e8f1d67c126793ea280) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - added .localizeHtml implementation to SDK

### Patch Changes

- Updated dependencies [[`a6b22a3`](https://github.com/lingodotdev/lingo.dev/commit/a6b22a3237f574455d8119f914d82b0b247b4151)]:
  - @replexica/[email protected]

## 0.4.3

### Patch Changes

- Updated dependencies [[`091ee35`](https://github.com/lingodotdev/lingo.dev/commit/091ee353081795bf8f61c7d41483bc309c7b62ef)]:
  - @replexica/[email protected]

## 0.4.2

### Patch Changes

- Updated dependencies [[`5e282d7`](https://github.com/lingodotdev/lingo.dev/commit/5e282d7ffa5ca9494aa7046a090bb7c327085a86)]:
  - @replexica/[email protected]

## 0.4.1

### Patch Changes

- Updated dependencies [[`0071cd6`](https://github.com/lingodotdev/lingo.dev/commit/0071cd66b1c868ad3898fc368390a628c5a67767)]:
  - @replexica/[email protected]

## 0.4.0

### Minor Changes

- [#264](https://github.com/lingodotdev/lingo.dev/pull/264) [`cdef5b7`](https://github.com/lingodotdev/lingo.dev/commit/cdef5b7bfbee4670c6de62cf4b4f3e0315748e25) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - added format specific methods to `@replexica/sdk`

## 0.3.4

### Patch Changes

- Updated dependencies [[`2859938`](https://github.com/lingodotdev/lingo.dev/commit/28599388a91bf80cea3813bb4b8999bb4df302c9)]:
  - @replexica/[email protected]

## 0.3.3

### Patch Changes

- Updated dependencies [[`ca9e20e`](https://github.com/lingodotdev/lingo.dev/commit/ca9e20eef9047e20d39ccf9dff74d2f6069d4676), [`2aedf3b`](https://github.com/lingodotdev/lingo.dev/commit/2aedf3bec2d9dffc7b43fc10dea0cab5742d44af), [`626082a`](https://github.com/lingodotdev/lingo.dev/commit/626082a64b88fb3b589acd950afeafe417ce5ddc)]:
  - @replexica/[email protected]

## 0.3.2

### Patch Changes

- Updated dependencies [[`1601f70`](https://github.com/lingodotdev/lingo.dev/commit/1601f708bdf0ff1786d3bf9b19265ac5b567f740)]:
  - @replexica/[email protected]

## 0.3.1

### Patch Changes

- Updated dependencies [[`bc5a28c`](https://github.com/lingodotdev/lingo.dev/commit/bc5a28c3c98b619872924b5f913229ac01387524)]:
  - @replexica/[email protected]

## 0.3.0

### Minor Changes

- [#165](https://github.com/lingodotdev/lingo.dev/pull/165) [`5c2ca37`](https://github.com/lingodotdev/lingo.dev/commit/5c2ca37114663eaeb529a027e33949ef3839549b) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - Update locale code resolution logic

### Patch Changes

- Updated dependencies [[`5c2ca37`](https://github.com/lingodotdev/lingo.dev/commit/5c2ca37114663eaeb529a027e33949ef3839549b)]:
  - @replexica/[email protected]

## 0.2.1

### Patch Changes

- Updated dependencies [[`6870fc7`](https://github.com/lingodotdev/lingo.dev/commit/6870fc758dae9d1adb641576befbd8cda61cd5ea)]:
  - @replexica/[email protected]

## 0.2.0

### Minor Changes

- [`d6e6d5c`](https://github.com/lingodotdev/lingo.dev/commit/d6e6d5c24b266de3769e95545f74632e7d75c697) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - Add support for multisource localization to the CLI

### Patch Changes

- Updated dependencies [[`d6e6d5c`](https://github.com/lingodotdev/lingo.dev/commit/d6e6d5c24b266de3769e95545f74632e7d75c697)]:
  - @replexica/[email protected]

## 0.1.1

### Patch Changes

- Updated dependencies [[`73c9250`](https://github.com/lingodotdev/lingo.dev/commit/73c925084989ccea120cae1617ec87776c88e83e)]:
  - @replexica/[email protected]

## 0.1.0

### Minor Changes

- [#142](https://github.com/lingodotdev/lingo.dev/pull/142) [`d9b0e51`](https://github.com/lingodotdev/lingo.dev/commit/d9b0e512196329cc781a4d33346f8ca0f3a81e7e) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - Extract API calling into SDK package

```

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

```typescript
import { parse, ParseError } from "jsonc-parser";
import { ILoader } from "./_types";
import { createLoader } from "./_utils";

interface CommentInfo {
  hint?: string;
  [key: string]: any;
}

function extractCommentsFromJsonc(jsoncString: string): Record<string, any> {
  const lines = jsoncString.split("\n");
  const comments: Record<string, any> = {};

  // Parse to validate structure
  const errors: ParseError[] = [];
  const result = parse(jsoncString, errors, {
    allowTrailingComma: true,
    disallowComments: false,
    allowEmptyContent: true,
  });

  if (errors.length > 0) {
    return {};
  }

  // Track nesting context
  const contextStack: Array<{ key: string; isArray: boolean }> = [];

  for (let i = 0; i < lines.length; i++) {
    const line = lines[i];
    const trimmedLine = line.trim();

    if (!trimmedLine) continue;

    // Handle different comment types
    const commentData = extractCommentFromLine(line, lines, i);
    if (commentData.hint) {
      let keyInfo;

      if (commentData.isInline) {
        // For inline comments, extract key from the same line
        const keyMatch = line.match(/^\s*["']?([^"':,\s]+)["']?\s*:/);
        if (keyMatch) {
          const key = keyMatch[1];
          const path = contextStack.map((ctx) => ctx.key).filter(Boolean);
          keyInfo = { key, path };
        }
      } else {
        // For standalone comments, find the next key
        keyInfo = findAssociatedKey(lines, commentData.lineIndex, contextStack);
      }

      if (keyInfo && keyInfo.key) {
        setCommentAtPath(comments, keyInfo.path, keyInfo.key, commentData.hint);
      }

      // Skip processed lines for multi-line comments
      i = commentData.endIndex;
      continue;
    }

    // Update context for object/array nesting
    updateContext(contextStack, line, result);
  }

  return comments;
}

function extractCommentFromLine(
  line: string,
  lines: string[],
  lineIndex: number,
): {
  hint: string | null;
  lineIndex: number;
  endIndex: number;
  isInline: boolean;
} {
  const trimmed = line.trim();

  // Single-line comment (standalone)
  if (trimmed.startsWith("//")) {
    const hint = trimmed.replace(/^\/\/\s*/, "").trim();
    return { hint, lineIndex, endIndex: lineIndex, isInline: false };
  }

  // Block comment (standalone or multi-line)
  if (trimmed.startsWith("/*")) {
    const blockResult = extractBlockComment(lines, lineIndex);
    return { ...blockResult, isInline: false };
  }

  // Inline comments (after JSON content)
  // Handle single-line inline comments
  const singleInlineMatch = line.match(/^(.+?)\s*\/\/\s*(.+)$/);
  if (singleInlineMatch && singleInlineMatch[1].includes(":")) {
    const hint = singleInlineMatch[2].trim();
    return { hint, lineIndex, endIndex: lineIndex, isInline: true };
  }

  // Handle block inline comments
  const blockInlineMatch = line.match(/^(.+?)\s*\/\*\s*(.*?)\s*\*\/.*$/);
  if (blockInlineMatch && blockInlineMatch[1].includes(":")) {
    const hint = blockInlineMatch[2].trim();
    return { hint, lineIndex, endIndex: lineIndex, isInline: true };
  }

  return { hint: null, lineIndex, endIndex: lineIndex, isInline: false };
}

function extractBlockComment(
  lines: string[],
  startIndex: number,
): { hint: string | null; lineIndex: number; endIndex: number } {
  const startLine = lines[startIndex];

  // Single-line block comment
  const singleMatch = startLine.match(/\/\*\s*(.*?)\s*\*\//);
  if (singleMatch) {
    return {
      hint: singleMatch[1].trim(),
      lineIndex: startIndex,
      endIndex: startIndex,
    };
  }

  // Multi-line block comment
  const commentParts: string[] = [];
  let endIndex = startIndex;

  // Extract content from first line
  const firstContent = startLine.replace(/.*?\/\*\s*/, "").trim();
  if (firstContent && !firstContent.includes("*/")) {
    commentParts.push(firstContent);
  }

  // Process subsequent lines
  for (let i = startIndex + 1; i < lines.length; i++) {
    const line = lines[i];
    endIndex = i;

    if (line.includes("*/")) {
      const lastContent = line
        .replace(/\*\/.*$/, "")
        .replace(/^\s*\*?\s*/, "")
        .trim();
      if (lastContent) {
        commentParts.push(lastContent);
      }
      break;
    } else {
      const content = line.replace(/^\s*\*?\s*/, "").trim();
      if (content) {
        commentParts.push(content);
      }
    }
  }

  return {
    hint: commentParts.join(" ").trim() || null,
    lineIndex: startIndex,
    endIndex,
  };
}

function findAssociatedKey(
  lines: string[],
  commentLineIndex: number,
  contextStack: Array<{ key: string; isArray: boolean }>,
): { key: string | null; path: string[] } {
  // Look for the next key after the comment
  for (let i = commentLineIndex + 1; i < lines.length; i++) {
    const line = lines[i].trim();

    if (
      !line ||
      line.startsWith("//") ||
      line.startsWith("/*") ||
      line === "{" ||
      line === "}"
    ) {
      continue;
    }

    // Extract key from line
    const keyMatch = line.match(/^\s*["']?([^"':,\s]+)["']?\s*:/);
    if (keyMatch) {
      const key = keyMatch[1];
      const path = contextStack.map((ctx) => ctx.key).filter(Boolean);
      return { key, path };
    }
  }

  return { key: null, path: [] };
}

function updateContext(
  contextStack: Array<{ key: string; isArray: boolean }>,
  line: string,
  parsedJson: any,
): void {
  // This is a simplified context tracking - in a full implementation,
  // you'd want more sophisticated AST-based tracking
  const openBraces = (line.match(/\{/g) || []).length;
  const closeBraces = (line.match(/\}/g) || []).length;

  if (openBraces > closeBraces) {
    // Extract the key that's opening this object
    const keyMatch = line.match(/^\s*["']?([^"':,\s]+)["']?\s*:\s*\{/);
    if (keyMatch) {
      contextStack.push({ key: keyMatch[1], isArray: false });
    }
  } else if (closeBraces > openBraces) {
    // Pop context when closing braces
    for (let i = 0; i < closeBraces - openBraces; i++) {
      contextStack.pop();
    }
  }
}

function setCommentAtPath(
  comments: Record<string, any>,
  path: string[],
  key: string,
  hint: string,
): void {
  let current = comments;

  // Navigate to the correct nested location
  for (const pathKey of path) {
    if (!current[pathKey]) {
      current[pathKey] = {};
    }
    current = current[pathKey];
  }

  // Set the hint for the key
  if (!current[key]) {
    current[key] = {};
  }

  if (typeof current[key] === "object" && current[key] !== null) {
    current[key].hint = hint;
  } else {
    current[key] = { hint };
  }
}

export default function createJsoncLoader(): ILoader<
  string,
  Record<string, any>
> {
  return createLoader({
    pull: async (locale, input) => {
      const jsoncString = input || "{}";
      const errors: ParseError[] = [];
      const result = parse(jsoncString, errors, {
        allowTrailingComma: true,
        disallowComments: false,
        allowEmptyContent: true,
      });

      if (errors.length > 0) {
        throw new Error(`Failed to parse JSONC: ${errors[0].error}`);
      }

      return result || {};
    },
    push: async (locale, data) => {
      // JSONC parser's stringify preserves formatting but doesn't add comments
      // We'll use standard JSON.stringify with pretty formatting for output
      const serializedData = JSON.stringify(data, null, 2);
      return serializedData;
    },
    pullHints: async (input) => {
      if (!input || typeof input !== "string") {
        return {};
      }

      try {
        return extractCommentsFromJsonc(input);
      } catch (error) {
        console.warn("Failed to extract comments from JSONC:", error);
        return {};
      }
    },
  });
}

```

--------------------------------------------------------------------------------
/packages/locales/src/names/integration.spec.ts:
--------------------------------------------------------------------------------

```typescript
import { describe, it, expect, vi, beforeEach } from "vitest";
import { getCountryName, getLanguageName, getScriptName } from "./index";

// Mock the loader functions to return predictable data
vi.mock("./loader", () => ({
  loadTerritoryNames: vi.fn(),
  loadLanguageNames: vi.fn(),
  loadScriptNames: vi.fn(),
}));

import {
  loadTerritoryNames,
  loadLanguageNames,
  loadScriptNames,
} from "./loader";

const mockLoadTerritoryNames = loadTerritoryNames as ReturnType<typeof vi.fn>;
const mockLoadLanguageNames = loadLanguageNames as ReturnType<typeof vi.fn>;
const mockLoadScriptNames = loadScriptNames as ReturnType<typeof vi.fn>;

describe("Integration Tests", () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  describe("getCountryName", () => {
    it("should get country names in different languages", async () => {
      // Mock data for different languages
      mockLoadTerritoryNames
        .mockResolvedValueOnce({ US: "United States", CN: "China" }) // en
        .mockResolvedValueOnce({ US: "Estados Unidos", CN: "China" }) // es
        .mockResolvedValueOnce({ US: "États-Unis", CN: "Chine" }); // fr

      const result1 = await getCountryName("US", "en");
      const result2 = await getCountryName("US", "es");
      const result3 = await getCountryName("US", "fr");

      expect(result1).toBe("United States");
      expect(result2).toBe("Estados Unidos");
      expect(result3).toBe("États-Unis");

      expect(mockLoadTerritoryNames).toHaveBeenCalledTimes(3);
      expect(mockLoadTerritoryNames).toHaveBeenNthCalledWith(1, "en");
      expect(mockLoadTerritoryNames).toHaveBeenNthCalledWith(2, "es");
      expect(mockLoadTerritoryNames).toHaveBeenNthCalledWith(3, "fr");
    });

    it("should normalize country codes to uppercase", async () => {
      mockLoadTerritoryNames.mockResolvedValue({ US: "United States" });

      const result = await getCountryName("us");
      expect(result).toBe("United States");
    });

    it("should handle loader errors gracefully", async () => {
      mockLoadTerritoryNames.mockRejectedValue(new Error("Network error"));

      await expect(getCountryName("US")).rejects.toThrow("Network error");
    });
  });

  describe("getLanguageName", () => {
    it("should get language names in different languages", async () => {
      mockLoadLanguageNames
        .mockResolvedValueOnce({ en: "English", es: "Spanish" }) // en
        .mockResolvedValueOnce({ en: "inglés", es: "español" }) // es
        .mockResolvedValueOnce({ en: "anglais", es: "espagnol" }); // fr

      const result1 = await getLanguageName("en", "en");
      const result2 = await getLanguageName("en", "es");
      const result3 = await getLanguageName("en", "fr");

      expect(result1).toBe("English");
      expect(result2).toBe("inglés");
      expect(result3).toBe("anglais");
    });

    it("should normalize language codes to lowercase", async () => {
      mockLoadLanguageNames.mockResolvedValue({ en: "English" });

      const result = await getLanguageName("EN");
      expect(result).toBe("English");
    });
  });

  describe("getScriptName", () => {
    it("should get script names in different languages", async () => {
      mockLoadScriptNames
        .mockResolvedValueOnce({
          Hans: "Simplified Han",
          Hant: "Traditional Han",
        }) // en
        .mockResolvedValueOnce({
          Hans: "han simplificado",
          Hant: "han tradicional",
        }) // es
        .mockResolvedValueOnce({
          Hans: "han simplifié",
          Hant: "han traditionnel",
        }); // fr

      const result1 = await getScriptName("Hans", "en");
      const result2 = await getScriptName("Hans", "es");
      const result3 = await getScriptName("Hans", "fr");

      expect(result1).toBe("Simplified Han");
      expect(result2).toBe("han simplificado");
      expect(result3).toBe("han simplifié");
    });

    it("should preserve script code case", async () => {
      mockLoadScriptNames.mockResolvedValue({
        Latn: "Latin",
        CYRL: "Cyrillic",
        hans: "Simplified Han",
      });

      const result1 = await getScriptName("Latn");
      const result2 = await getScriptName("CYRL");
      const result3 = await getScriptName("hans");

      expect(result1).toBe("Latin");
      expect(result2).toBe("Cyrillic");
      expect(result3).toBe("Simplified Han");
    });
  });

  describe("Error handling", () => {
    it("should throw for empty inputs", async () => {
      await expect(getCountryName("")).rejects.toThrow(
        "Country code is required",
      );
      await expect(getLanguageName("")).rejects.toThrow(
        "Language code is required",
      );
      await expect(getScriptName("")).rejects.toThrow(
        "Script code is required",
      );
    });

    it("should throw for null/undefined inputs", async () => {
      await expect(getCountryName(null as any)).rejects.toThrow(
        "Country code is required",
      );
      await expect(getLanguageName(undefined as any)).rejects.toThrow(
        "Language code is required",
      );
      await expect(getScriptName(null as any)).rejects.toThrow(
        "Script code is required",
      );
    });

    it("should throw for unknown codes", async () => {
      mockLoadTerritoryNames.mockResolvedValue({ US: "United States" });
      mockLoadLanguageNames.mockResolvedValue({ en: "English" });
      mockLoadScriptNames.mockResolvedValue({ Latn: "Latin" });

      await expect(getCountryName("XX")).rejects.toThrow(
        'Country code "XX" not found',
      );
      await expect(getLanguageName("xx")).rejects.toThrow(
        'Language code "xx" not found',
      );
      await expect(getScriptName("Xxxx")).rejects.toThrow(
        'Script code "Xxxx" not found',
      );
    });
  });

  describe("Real-world scenarios", () => {
    it("should handle Chinese locale names", async () => {
      mockLoadLanguageNames.mockResolvedValue({
        en: "英语",
        es: "西班牙语",
        fr: "法语",
        de: "德语",
      });

      const result1 = await getLanguageName("en", "zh");
      const result2 = await getLanguageName("es", "zh");
      const result3 = await getLanguageName("fr", "zh");
      const result4 = await getLanguageName("de", "zh");

      expect(result1).toBe("英语");
      expect(result2).toBe("西班牙语");
      expect(result3).toBe("法语");
      expect(result4).toBe("德语");
    });

    it("should handle Arabic locale names", async () => {
      mockLoadTerritoryNames.mockResolvedValue({
        US: "الولايات المتحدة",
        GB: "المملكة المتحدة",
        FR: "فرنسا",
      });

      const result1 = await getCountryName("US", "ar");
      const result2 = await getCountryName("GB", "ar");
      const result3 = await getCountryName("FR", "ar");

      expect(result1).toBe("الولايات المتحدة");
      expect(result2).toBe("المملكة المتحدة");
      expect(result3).toBe("فرنسا");
    });

    it("should handle script variants", async () => {
      mockLoadScriptNames.mockResolvedValue({
        Hans: "Simplified Han",
        Hant: "Traditional Han",
        Latn: "Latin",
        Cyrl: "Cyrillic",
        Arab: "Arabic",
        Deva: "Devanagari",
      });

      const result1 = await getScriptName("Hans");
      const result2 = await getScriptName("Hant");
      const result3 = await getScriptName("Latn");
      const result4 = await getScriptName("Cyrl");
      const result5 = await getScriptName("Arab");
      const result6 = await getScriptName("Deva");

      expect(result1).toBe("Simplified Han");
      expect(result2).toBe("Traditional Han");
      expect(result3).toBe("Latin");
      expect(result4).toBe("Cyrillic");
      expect(result5).toBe("Arabic");
      expect(result6).toBe("Devanagari");
    });
  });
});

```

--------------------------------------------------------------------------------
/packages/cli/src/cli/cmd/init.ts:
--------------------------------------------------------------------------------

```typescript
import { InteractiveCommand, InteractiveOption } from "interactive-commander";
import Ora from "ora";
import { getConfig, saveConfig } from "../utils/config";
import {
  defaultConfig,
  LocaleCode,
  resolveLocaleCode,
  bucketTypes,
} from "@lingo.dev/_spec";
import fs from "fs";
import path from "path";
import _ from "lodash";
import { checkbox, confirm, input } from "@inquirer/prompts";
import { login } from "./login";
import { getSettings, saveSettings } from "../utils/settings";
import { createAuthenticator } from "../utils/auth";
import findLocaleFiles from "../utils/find-locale-paths";
import { ensurePatterns } from "../utils/ensure-patterns";
import updateGitignore from "../utils/update-gitignore";
import initCICD from "../utils/init-ci-cd";
import open from "open";

const openUrl = (path: string) => {
  const settings = getSettings(undefined);
  open(`${settings.auth.webUrl}${path}`, { wait: false });
};

const throwHelpError = (option: string, value: string) => {
  if (value === "help") {
    openUrl("/go/call");
  }
  throw new Error(
    `Invalid ${option}: ${value}\n\nDo you need support for ${value} ${option}? Type "help" and we will.`,
  );
};

export default new InteractiveCommand()
  .command("init")
  .description("Create i18n.json configuration file for a new project")
  .helpOption("-h, --help", "Show help")
  .addOption(
    new InteractiveOption(
      "-f --force",
      "Overwrite existing Lingo.dev configuration instead of aborting initialization (destructive operation)",
    )
      .prompt(undefined)
      .default(false),
  )
  .addOption(
    new InteractiveOption(
      "-s --source <locale>",
      "Primary language of your application that content will be translated from. Defaults to 'en'",
    )
      .argParser((value) => {
        try {
          resolveLocaleCode(value as LocaleCode);
        } catch (e) {
          throwHelpError("locale", value);
        }
        return value;
      })
      .default("en"),
  )
  .addOption(
    new InteractiveOption(
      "-t --targets <locale...>",
      "Target languages to translate to. Accepts locale codes like 'es', 'fr', 'de-AT' separated by commas or spaces. Defaults to 'es'",
    )
      .argParser((value) => {
        const values = (
          value.includes(",") ? value.split(",") : value.split(" ")
        ) as LocaleCode[];
        values.forEach((value) => {
          try {
            resolveLocaleCode(value);
          } catch (e) {
            throwHelpError("locale", value);
          }
        });
        return values;
      })
      .default("es"),
  )
  .addOption(
    new InteractiveOption(
      "-b, --bucket <type>",
      "File format for your translation files. Must match a supported type such as json, yaml, or android",
    )
      .argParser((value) => {
        if (!bucketTypes.includes(value as (typeof bucketTypes)[number])) {
          throwHelpError("bucket format", value);
        }
        return value;
      })
      .default("json"),
  )
  .addOption(
    new InteractiveOption(
      "-p, --paths [path...]",
      "File paths containing translations when using --no-interactive mode. Specify paths with [locale] placeholder, separated by commas or spaces",
    )
      .argParser((value) => {
        if (!value || value.length === 0) return [];
        const values = value.includes(",")
          ? value.split(",")
          : value.split(" ");

        for (const p of values) {
          try {
            const dirPath = path.dirname(p);
            const stats = fs.statSync(dirPath);
            if (!stats.isDirectory()) {
              throw new Error(`${dirPath} is not a directory`);
            }
          } catch (err) {
            throw new Error(`Invalid path: ${p}`);
          }
        }

        return values;
      })
      .prompt(undefined) // make non-interactive
      .default([]),
  )
  .action(async (options) => {
    const settings = getSettings(undefined);
    const isInteractive = options.interactive;

    const spinner = Ora().start("Initializing Lingo.dev project");

    let existingConfig = await getConfig(false);
    if (existingConfig && !options.force) {
      spinner.fail("Lingo.dev project already initialized");
      return process.exit(1);
    }

    const newConfig = _.cloneDeep(defaultConfig);

    newConfig.locale.source = options.source;
    newConfig.locale.targets = options.targets;

    if (!isInteractive) {
      newConfig.buckets = {
        [options.bucket]: {
          include: options.paths || [],
        },
      };
    } else {
      let selectedPatterns: string[] = [];
      const localeFiles = findLocaleFiles(options.bucket);

      if (!localeFiles) {
        spinner.warn(
          `Bucket type "${options.bucket}" does not supported automatic initialization. Add paths to "i18n.json" manually.`,
        );
        newConfig.buckets = {
          [options.bucket]: {
            include: options.paths || [],
          },
        };
      } else {
        const { patterns, defaultPatterns } = localeFiles;

        if (patterns.length > 0) {
          spinner.succeed("Found existing locale files:");

          selectedPatterns = await checkbox({
            message: "Select the paths to use",
            choices: patterns.map((value) => ({
              value,
            })),
          });
        } else {
          spinner.succeed("No existing locale files found.");
        }

        if (selectedPatterns.length === 0) {
          const useDefault = await confirm({
            message: `Use (and create) default path ${defaultPatterns.join(
              ", ",
            )}?`,
          });
          if (useDefault) {
            ensurePatterns(defaultPatterns, options.source);
            selectedPatterns = defaultPatterns;
          }
        }

        if (selectedPatterns.length === 0) {
          const customPaths = await input({
            message: "Enter paths to use",
          });
          selectedPatterns = customPaths.includes(",")
            ? customPaths.split(",")
            : customPaths.split(" ");
        }

        newConfig.buckets = {
          [options.bucket]: {
            include: selectedPatterns || [],
          },
        };
      }
    }

    await saveConfig(newConfig);

    spinner.succeed("Lingo.dev project initialized");

    if (isInteractive) {
      await initCICD(spinner);

      const openDocs = await confirm({
        message: "Would you like to see our docs?",
      });
      if (openDocs) {
        openUrl("/go/docs");
      }
    }

    const authenticator = createAuthenticator({
      apiKey: settings.auth.apiKey,
      apiUrl: settings.auth.apiUrl,
    });
    const auth = await authenticator.whoami();
    if (!auth) {
      if (isInteractive) {
        const doAuth = await confirm({
          message: "It looks like you are not logged into the CLI. Login now?",
        });
        if (doAuth) {
          const apiKey = await login(settings.auth.webUrl);
          settings.auth.apiKey = apiKey;
          await saveSettings(settings);

          const newAuthenticator = createAuthenticator({
            apiKey: settings.auth.apiKey,
            apiUrl: settings.auth.apiUrl,
          });
          const auth = await newAuthenticator.whoami();
          if (auth) {
            Ora().succeed(`Authenticated as ${auth?.email}`);
          } else {
            Ora().fail("Authentication failed.");
          }
        }
      } else {
        Ora().warn(
          "You are not logged in. Run `npx lingo.dev@latest login` to login.",
        );
      }
    } else {
      Ora().succeed(`Authenticated as ${auth.email}`);
    }

    updateGitignore();

    if (!isInteractive) {
      Ora().info("Please see https://lingo.dev/cli");
    }
  });

```

--------------------------------------------------------------------------------
/.claude/commands/create-bucket-docs.md:
--------------------------------------------------------------------------------

```markdown
---
argument-hint: <analysis-output>
description: Create documentation for a Lingo.dev bucket type using analysis output
---

Using the bucket analysis output provided at the end of this prompt, create documentation for the specified bucket type in Lingo.dev CLI.

## Template Structure

````markdown
---
title: "[BUCKET_TYPE in title case]"
subtitle: "Translate [BUCKET_TYPE] files with Lingo.dev CLI"
---

## Introduction

[BUCKET_TYPE in title case] files are [BRIEF DESCRIPTION OF THE FILE FORMAT, ITS PURPOSE AND PRIMARY USE CASE]. [ONE SENTENCE ABOUT STRUCTURE OR KEY CHARACTERISTICS].

**Lingo.dev CLI** uses LLMs to translate your [BUCKET_TYPE] files across multiple locales. This guide shows you how to set up and run translations for [BUCKET_TYPE] files.

## Quickstart

### Step 1: Install Lingo.dev CLI

```bash
# Install globally
npm install -g lingo.dev@latest

# Or run directly with npx
npx lingo.dev@latest --version
```

### Step 2: Authenticate

Log in to your Lingo.dev account:

```bash
npx lingo.dev@latest login
```

This opens your browser for authentication. Your API key is stored locally for future use.

### Step 3: Initialize Project

Create your base configuration:

```bash
npx lingo.dev@latest init
```

This generates an `i18n.json` file with default settings.

### Step 4: Configure [BUCKET_TYPE] Bucket

Update your `i18n.json` to add [BUCKET_TYPE] support:

```json
{
  "$schema": "https://lingo.dev/schema/i18n.json",
  "version": "1.10",
  "locale": {
    "source": "en",
    "targets": ["es"]
  },
  "buckets": {
    "[BUCKET_TYPE]": {
      "include": ["[PATH_PATTERN]"]
    }
  }
}
```

[IF separate-files: **Note**: Keep `[locale]` as-is in the config — it's replaced with actual locale codes at runtime.]
[IF in-place: DO NOT include any note about [locale]]

### Step 5: Create File Structure

[FOR separate-files:]
Organize your [BUCKET_TYPE] files by locale:

```
[directory]/
├── en/
│   └── [filename]      # Source file
└── es/                 # Target directory (empty initially)
```

Place your source [BUCKET_TYPE] files in the `en/` directory. The `es/` directory can be empty — translated files will be created there automatically.

[FOR in-place:]
Place your [BUCKET_TYPE] file in your project:

```
[directory]/
└── [filename]          # Contains all locales
```

This single file will contain translations for all configured locales.

### Step 6: Run Translation

Execute the translation command:

```bash
npx lingo.dev@latest i18n
```

The CLI will:

- Read [BUCKET_TYPE] files from your source locale
- Translate content to target locales using LLMs
- [FOR separate-files: Create new files in target directories (e.g., `es/[filename]`)]
- [FOR in-place: Update the file with translations for all configured locales]

[FOR separate-files: **Note**: Unlike some bucket types that modify files in place, the [BUCKET_TYPE] bucket creates separate files for each locale. Your source files remain unchanged.]
[FOR in-place: **Note**: The [BUCKET_TYPE] bucket modifies the source file directly, adding translations for all target locales to the same file.]

### Step 7: Verify Results

Check the translation status:

```bash
npx lingo.dev@latest status
```

[FOR separate-files: Review generated files in your target locale directory (`es/`).]
[FOR in-place: Review the updated [filename] file which now contains all locales.]

## [Feature Sections - ONLY include supported features]

[IF Locked Keys = YES:]

## Locked Content

The [BUCKET_TYPE] bucket supports locking specific keys to prevent translation:

```json
"[BUCKET_TYPE]": {
  "include": ["[PATH_PATTERN]"],
  "lockedKeys": ["key1", "key2", "nested/key3"]
}
```

This feature is available for [BUCKET_TYPE] and other structured format buckets where specific keys need to remain untranslated.

[IF Ignored Keys = YES:]

## Ignored Keys

The [BUCKET_TYPE] bucket supports ignoring keys entirely during processing:

```json
"[BUCKET_TYPE]": {
  "include": ["[PATH_PATTERN]"],
  "ignoredKeys": ["debug", "internal/*"]
}
```

Unlike locked keys which preserve content, ignored keys are completely skipped during the translation process.

[IF Inject Locale = YES:]

## Inject Locale

The [BUCKET_TYPE] bucket supports automatically injecting locale codes into specific keys:

```json
"[BUCKET_TYPE]": {
  "include": ["[PATH_PATTERN]"],
  "injectLocale": ["settings/language", "config/locale"]
}
```

These keys will automatically have their values replaced with the current locale code during translation.

[IF Translator Notes = YES:]

## Translator Notes

The [BUCKET_TYPE] bucket supports providing context hints to improve translation quality. [Describe how translator notes/hints work for this specific bucket type]

```[format]
[Show example of how to add translator notes in this format]
```

## Example

**Configuration** (`i18n.json`):

```json
{
  "$schema": "https://lingo.dev/schema/i18n.json",
  "version": "1.10",
  "locale": {
    "source": "en",
    "targets": ["es"]
  },
  "buckets": {
    "[BUCKET_TYPE]": {
      "include": ["[REALISTIC_PATH]"]
    }
  }
}
```

[FOR separate-files:]
**Input** (`[path]/en/[filename]`):

```[format]
[Source content in appropriate format]
```

**Output** (`[path]/es/[filename]`):

```[format]
[Translated content in appropriate format]
```

[FOR in-place:]
**Before translation** (`[path]/[filename]`):

```[format]
[Source content showing only English]
```

**After translation** (`[path]/[filename]`):

```[format]
[Same file now containing both English and Spanish]
```
````

## Critical Adaptation Rules

### For Separate-Files Buckets

1. **Always use `[locale]` placeholder** in paths
2. Step 5: Show source (`en/`) and target (`es/`) directories
3. Step 6: Explain "creates new files"
4. Include the [locale] note in Step 4
5. Example: Show input as `path/en/file.ext` and output as `path/es/file.ext`

### For In-Place Buckets

1. **Never use `[locale]` placeholder** anywhere in the document
2. **Never include the [locale] note** in Step 4
3. Step 5: Show single file path
4. Step 6: Explain "modifies the file directly"
5. Example: Use "Before translation" and "After translation" labels
6. Example: Show the same file path for both states

### Feature Sections

- Only include sections for features marked YES
- Locked Keys: Content is preserved unchanged
- Ignored Keys: Keys are skipped entirely during processing
- Inject Locale: Keys automatically get the locale code as their value
- Translator Notes: Format varies significantly by bucket type

### Path Conventions

Choose realistic paths for the bucket type:

- iOS: `ios/Resources/`, `[AppName]/`
- Android: `app/src/main/res/values-[locale]/`
- Web: `locales/`, `i18n/`, `translations/`
- Flutter: `lib/l10n/`
- Java: `src/main/resources/`

### Writing Rules

- Match the concise, direct tone of the template
- No marketing language or unnecessary adjectives
- Don't document what specifically gets translated
- Don't include generic features (exclude patterns, multiple directories)
- Focus only on bucket-specific behavior
- Use only `en` → `es` in all examples
- Keep examples minimal but representative

## Instructions

1. Parse the bucket analysis output provided in the arguments to determine:

   - Bucket type name
   - File organization (separate-files if uses [locale] placeholder, in-place if not)
   - Supported features (lockedKeys, ignoredKeys, injectLocale, hints/notes)
   - Typical file extension and paths

2. Based on the analysis, fill in the template with appropriate:

   - Description of the file format
   - Realistic path patterns
   - Only the features that are actually supported
   - Appropriate examples for the format

3. Generate the complete Markdown documentation following the specifications exactly.

---

## Bucket Analysis Output

$ARGUMENTS

```

--------------------------------------------------------------------------------
/packages/cli/src/cli/cmd/purge.ts:
--------------------------------------------------------------------------------

```typescript
import { Command } from "interactive-commander";
import _ from "lodash";
import Ora from "ora";
import { getConfig } from "../utils/config";
import { getBuckets } from "../utils/buckets";
import { resolveOverriddenLocale } from "@lingo.dev/_spec";
import createBucketLoader from "../loaders";
import { minimatch } from "minimatch";
import { confirm } from "@inquirer/prompts";

interface PurgeOptions {
  bucket?: string[];
  file?: string[];
  key?: string;
  locale?: string[];
  yesReally?: boolean;
}

export default new Command()
  .command("purge")
  .description(
    "WARNING: Permanently delete translation entries from bucket path patterns defined in i18n.json. This is a destructive operation that cannot be undone. Without any filters, ALL managed keys will be removed from EVERY target locale.",
  )
  .helpOption("-h, --help", "Show help")
  .option(
    "--bucket <bucket>",
    "Limit the purge to specific bucket types defined under `buckets` in i18n.json. Repeat the flag to include multiple bucket types. Defaults to all buckets",
    (val: string, prev: string[]) => (prev ? [...prev, val] : [val]),
  )
  .option(
    "--file [files...]",
    "Filter which file paths to purge by matching against path patterns. Only paths containing any of these values will be processed. Examples: --file messages.json --file admin/",
  )
  .option(
    "--key <key>",
    "Filter which keys to delete using prefix matching on dot-separated key paths. Example: 'auth.login' matches all keys starting with auth.login. Omit this option to delete ALL keys. Keys marked as locked or ignored in i18n.json are automatically skipped",
    (val: string) => encodeURIComponent(val),
  )
  .option(
    "--locale <locale>",
    "Limit purging to specific target locale codes from i18n.json. Repeat the flag to include multiple locales. Defaults to all configured target locales. Warning: Including the source locale will delete content from it as well.",
    (val: string, prev: string[]) => (prev ? [...prev, val] : [val]),
  )
  .option(
    "--yes-really",
    "Bypass safety confirmations for destructive operations. Use with extreme caution - this will delete translation keys without asking for confirmation. Intended for automated scripts and CI environments only.",
  )
  .action(async function (options: PurgeOptions) {
    const ora = Ora();
    try {
      ora.start("Loading configuration...");
      const i18nConfig = getConfig();
      if (!i18nConfig) {
        throw new Error("i18n.json not found. Please run `lingo.dev init`.");
      }
      ora.succeed("Configuration loaded");

      let buckets = getBuckets(i18nConfig);
      if (options.bucket && options.bucket.length) {
        buckets = buckets.filter((bucket) =>
          options.bucket!.includes(bucket.type),
        );
      }
      if (options.file && options.file.length) {
        buckets = buckets
          .map((bucket) => {
            const paths = bucket.paths.filter((bucketPath) =>
              options.file?.some((f) => bucketPath.pathPattern.includes(f)),
            );
            return { ...bucket, paths };
          })
          .filter((bucket) => bucket.paths.length > 0);
        if (buckets.length === 0) {
          ora.fail("All files were filtered out by --file option.");
          process.exit(1);
        }
      }
      const sourceLocale = i18nConfig.locale.source;
      const targetLocales =
        options.locale && options.locale.length
          ? options.locale
          : i18nConfig.locale.targets;
      let removedAny = false;
      for (const bucket of buckets) {
        console.log();
        ora.info(`Processing bucket: ${bucket.type}`);
        for (const bucketPath of bucket.paths) {
          for (const _targetLocale of targetLocales) {
            const targetLocale = resolveOverriddenLocale(
              _targetLocale,
              bucketPath.delimiter,
            );
            const bucketOra = Ora({ indent: 2 }).start(
              `Processing path: ${bucketPath.pathPattern} [${targetLocale}]`,
            );
            try {
              const bucketLoader = createBucketLoader(
                bucket.type,
                bucketPath.pathPattern,
                {
                  defaultLocale: sourceLocale,
                  injectLocale: bucket.injectLocale,
                  formatter: i18nConfig!.formatter,
                },
                bucket.lockedKeys,
                bucket.lockedPatterns,
                bucket.ignoredKeys,
              );
              await bucketLoader.init();
              bucketLoader.setDefaultLocale(sourceLocale);
              await bucketLoader.pull(sourceLocale);
              let targetData = await bucketLoader.pull(targetLocale);
              if (!targetData || Object.keys(targetData).length === 0) {
                bucketOra.info(
                  `No translations found for ${bucketPath.pathPattern} [${targetLocale}]`,
                );
                continue;
              }
              let newData = { ...targetData };
              let keysToRemove: string[] = [];
              if (options.key) {
                // minimatch for key patterns
                keysToRemove = Object.keys(newData).filter((k) =>
                  minimatch(k, options.key!),
                );
              } else {
                // No key specified: remove all keys
                keysToRemove = Object.keys(newData);
              }
              if (keysToRemove.length > 0) {
                // Show what will be deleted
                if (options.key) {
                  bucketOra.info(
                    `About to delete ${keysToRemove.length} key(s) matching '${options.key}' from ${bucketPath.pathPattern} [${targetLocale}]:\n  ${keysToRemove.slice(0, 10).join(", ")}${keysToRemove.length > 10 ? ", ..." : ""}`,
                  );
                } else {
                  bucketOra.info(
                    `About to delete all (${keysToRemove.length}) keys from ${bucketPath.pathPattern} [${targetLocale}]`,
                  );
                }

                if (!options.yesReally) {
                  bucketOra.warn(
                    "This is a destructive operation. If you are sure, type 'y' to continue. (Use --yes-really to skip this check.)",
                  );
                  const confirmed = await confirm({
                    message: `Delete these keys from ${bucketPath.pathPattern} [${targetLocale}]?`,
                    default: false,
                  });
                  if (!confirmed) {
                    bucketOra.info("Skipped by user.");
                    continue;
                  }
                }
                for (const key of keysToRemove) {
                  delete newData[key];
                }
                removedAny = true;
                await bucketLoader.push(targetLocale, newData);
                if (options.key) {
                  bucketOra.succeed(
                    `Removed ${keysToRemove.length} key(s) matching '${options.key}' from ${bucketPath.pathPattern} [${targetLocale}]`,
                  );
                } else {
                  bucketOra.succeed(
                    `Removed all keys (${keysToRemove.length}) from ${bucketPath.pathPattern} [${targetLocale}]`,
                  );
                }
              } else if (options.key) {
                bucketOra.info(
                  `No keys matching '${options.key}' found in ${bucketPath.pathPattern} [${targetLocale}]`,
                );
              } else {
                bucketOra.info("No keys to remove.");
              }
            } catch (error) {
              const err = error as Error;
              bucketOra.fail(`Failed: ${err.message}`);
            }
          }
        }
      }
      if (!removedAny) {
        ora.info("No keys were removed.");
      } else {
        ora.succeed("Purge completed.");
      }
    } catch (error) {
      const err = error as Error;
      ora.fail(err.message);
      process.exit(1);
    }
  });

```
Page 8/16FirstPrevNextLast