#
tokens: 45252/50000 4/626 files (page 13/16)
lines: off (toggle) GitHub
raw markdown copy
This is page 13 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

--------------------------------------------------------------------------------
/packages/cli/src/cli/loaders/mdx2/code-placeholder.spec.ts:
--------------------------------------------------------------------------------

```typescript
import { describe, it, expect } from "vitest";
import createMdxCodePlaceholderLoader from "./code-placeholder";
import dedent from "dedent";
import { md5 } from "../../utils/md5";

const PLACEHOLDER_REGEX = /---CODE-PLACEHOLDER-[0-9a-f]+---/g;

const sampleContent = dedent`
Paragraph with some code:

\`\`\`js
console.log("foo");
\`\`\`
`;

describe("MDX Code Placeholder Loader", () => {
  const loader = createMdxCodePlaceholderLoader();
  loader.setDefaultLocale("en");

  it("should replace fenced code with placeholder on pull", async () => {
    const result = await loader.pull("en", sampleContent);
    const hash = md5('```js\nconsole.log("foo");\n```');
    const expected = `Paragraph with some code:\n\n---CODE-PLACEHOLDER-${hash}---`;
    expect(result.trim()).toBe(expected);
  });

  it("should restore fenced code from placeholder on push", async () => {
    const pulled = await loader.pull("en", sampleContent);
    const translated = pulled.replace("Paragraph", "Párrafo");
    const output = await loader.push("es", translated);
    const expected = dedent`
      Párrafo with some code:

      \`\`\`js
      console.log("foo");
      \`\`\`
    `;
    expect(output.trim()).toBe(expected.trim());
  });

  describe("round-trip scenarios", () => {
    it("round-trips a fenced block with language tag", async () => {
      const md = dedent`
        Example:

        \`\`\`js
        console.log()
        \`\`\`
      `;
      const pulled = await loader.pull("en", md);
      const pushed = await loader.push("es", pulled);
      expect(pushed).toBe(md);
    });

    it("round-trips a fenced block without language tag", async () => {
      const md = dedent`
        Intro:

        \`\`\`
        generic code
        \`\`\`
      `;
      const pulled = await loader.pull("en", md);
      const pushed = await loader.push("es", pulled);
      expect(pushed).toBe(md);
    });

    it("round-trips a meta-tagged fenced block", async () => {
      const md = dedent`
        Meta:

        \`\`\`js {1,2} title="Sample"
        line1
        line2
        \`\`\`
      `;
      const pulled = await loader.pull("en", md);
      const pushed = await loader.push("es", pulled);
      expect(pushed).toBe(md);
    });

    it("round-trips a fenced block inside a blockquote", async () => {
      const md = dedent`
        > Quote start
        > \`\`\`ts
        > let x = 42;
        > \`\`\`
        > Quote end
      `;
      const pulled = await loader.pull("en", md);
      const pushed = await loader.push("es", pulled);
      expect(pushed).toBe(md);
    });

    it("round-trips multiple separated fenced blocks", async () => {
      const md = dedent`
        A:

        \`\`\`js
        1
        \`\`\`

        B:

        \`\`\`js
        2
        \`\`\`
      `;
      const pulled = await loader.pull("en", md);
      const pushed = await loader.push("es", pulled);
      expect(pushed).toBe(md);
    });

    it("round-trips adjacent fenced blocks", async () => {
      const md = dedent`
        \`\`\`
        a()
        \`\`\`
        \`\`\`
        b()
        \`\`\`
      `;
      const expected = dedent`
        \`\`\`
        a()
        \`\`\`

        \`\`\`
        b()
        \`\`\`
      `;
      const pulled = await loader.pull("en", md);
      const pushed = await loader.push("es", pulled);
      expect(pushed).toBe(expected);
    });

    it("round-trips an indented fenced block", async () => {
      const md = dedent`
        Outer:

        \`\`\`py
        pass
        \`\`\`
      `;
      const pulled = await loader.pull("en", md);
      const pushed = await loader.push("es", pulled);
      expect(pushed).toBe(md);
    });

    it("round-trips a fenced block after a heading", async () => {
      const md = dedent`
        # Title

        \`\`\`bash
        echo hi
        \`\`\`
      `;
      const pulled = await loader.pull("en", md);
      const pushed = await loader.push("es", pulled);
      expect(pushed).toBe(md);
    });

    it("round-trips a fenced block inside a list item", async () => {
      const md = `
- item:

  \`\`\`js
  io()
  \`\`\`
      `.trim();
      const pulled = await loader.pull("en", md);
      const pushed = await loader.push("es", pulled);
      expect(pushed).toBe(md);
    });

    it("round-trips a fenced block inside JSX component", async () => {
      const md = dedent`
        <Component>

        \`\`\`js
        x
        \`\`\`

        </Component>
      `;
      const pulled = await loader.pull("en", md);
      const pushed = await loader.push("es", pulled);
      expect(pushed).toBe(md);
    });

    it("round-trips a fenced block inside JSX component - adds new lines", async () => {
      const md = dedent`
        <Component>
        \`\`\`js
        x
        \`\`\`
        </Component>
      `;
      const pulled = await loader.pull("en", md);
      const pushed = await loader.push("es", pulled);
      expect(pushed).toBe(
        dedent`
        <Component>

        \`\`\`js
        x
        \`\`\`
        
        </Component>
      `,
      );
    });

    it("round-trips a large JSON fenced block", async () => {
      const md = dedent`
        \`\`\`shell
        { "key": [1,2,3] }
        \`\`\`
      `;
      const pulled = await loader.pull("en", md);
      const pushed = await loader.push("es", pulled);
      expect(pushed).toBe(md);
    });

    it("handles identical code snippets correctly", async () => {
      const md = dedent`
        First paragraph:

        \`\`\`shell
        echo "hello world"
        \`\`\`

        Second paragraph:
        \`\`\`shell
        echo "hello world"
        \`\`\`
      `;
      const pulled = await loader.pull("en", md);
      const pushed = await loader.push("es", pulled);
      expect(pushed).toBe(
        dedent`
        First paragraph:

        \`\`\`shell
        echo "hello world"
        \`\`\`

        Second paragraph:

        \`\`\`shell
        echo "hello world"
        \`\`\`
      `,
      );
    });

    it("handles fenced code blocks inside quotes correctly", async () => {
      const md = dedent`
        > Code snippet inside quote:
        >
        > \`\`\`shell
        > npx -y mucho@latest install
        > \`\`\`
      `;
      const pulled = await loader.pull("en", md);
      const pushed = await loader.push("es", pulled);
      expect(pushed).toBe(md);
    });

    it("round-trips an image block with surrounding blank lines unchanged", async () => {
      const md = dedent`
        Text above.

        ![](https://example.com/img.png)

        Text below.
      `;

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

    it("round-trips and adds blank lines around an image block when missing", async () => {
      const md = dedent`
        Text above.
        ![](https://example.com/img.png)
        Text below.
      `;

      const expected = dedent`
        Text above.

        ![](https://example.com/img.png)

        Text below.
      `;

      const pulled = await loader.pull("en", md);
      const pushed = await loader.push("es", pulled);
      expect(pushed).toBe(expected);
    });

    it("keeps image inside blockquote as-is", async () => {
      const md = dedent`
        > ![](https://example.com/img.png)
      `;

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

    it("leaves incomplete fences untouched", async () => {
      const md = "```js\nno close";
      const pulled = await loader.pull("en", md);
      expect(pulled).toBe(md);

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

    // Edge cases for image spacing

    it("adds blank line after image when only before exists", async () => {
      const md = dedent`
        Before.

        ![alt](https://example.com/i.png)
        After.
      `;

      const expected = dedent`
        Before.

        ![alt](https://example.com/i.png)

        After.
      `;

      const pulled = await loader.pull("en", md);
      const pushed = await loader.push("es", pulled);
      expect(pushed).toBe(expected);
    });

    it("adds blank line before image when only after exists", async () => {
      const md = dedent`
        Before.
        ![alt](https://example.com/i.png)

        After.
      `;

      const expected = dedent`
        Before.

        ![alt](https://example.com/i.png)

        After.
      `;

      const pulled = await loader.pull("en", md);
      const pushed = await loader.push("es", pulled);
      expect(pushed).toBe(expected);
    });

    it("inserts spacing between consecutive images", async () => {
      const md = dedent`
        ![](a.png)
        ![](b.png)
      `;

      const expected = dedent`
        ![](a.png)

        ![](b.png)
      `;

      const pulled = await loader.pull("en", md);
      const pushed = await loader.push("es", pulled);
      expect(pushed).toBe(expected);
    });

    it("handles image inside JSX component - adds blank lines", async () => {
      const md = dedent`
        <Wrapper>
        ![](pic.png)
        </Wrapper>
      `;

      const expected = dedent`
        <Wrapper>

        ![](pic.png)

        </Wrapper>
      `;

      const pulled = await loader.pull("en", md);
      const pushed = await loader.push("es", pulled);
      expect(pushed).toBe(expected);
    });
  });

  describe("inline code placeholder", () => {
    it("should replace inline code with placeholder on pull", async () => {
      const md = "This is some `inline()` code.";
      const pulled = await loader.pull("en", md);
      const hash = md5("`inline()`");
      const expected = `This is some ---INLINE-CODE-PLACEHOLDER-${hash}--- code.`;
      expect(pulled).toBe(expected);
    });

    it("should restore inline code from placeholder on push", async () => {
      const md = "Some `code` here.";
      const pulled = await loader.pull("en", md);
      const translated = pulled.replace("Some", "Algún");
      const pushed = await loader.push("es", translated);
      expect(pushed).toBe("Algún `code` here.");
    });

    it("round-trips multiple inline code snippets", async () => {
      const md = "Use `a` and `b` and `c`.";
      const pulled = await loader.pull("en", md);
      const pushed = await loader.push("es", pulled);
      expect(pushed).toBe(md);
    });

    it("handles identical inline snippets correctly", async () => {
      const md = "Repeat `x` and `x` again.";
      const pulled = await loader.pull("en", md);
      const pushed = await loader.push("es", pulled);
      expect(pushed).toBe(md);
    });

    it("retains custom inline code in target locale when it differs from source", async () => {
      const enMd = "Use `foo` function.";
      const ruMd = "Используйте `бар` функцию.";

      // Pull English source to establish originalInput in loader state
      await loader.pull("en", enMd);

      // Pull Russian content (with its own inline code value)
      const ruPulled = await loader.pull("ru", ruMd);
      // Simulate translator editing surrounding text but keeping placeholder intact
      const ruTranslated = ruPulled.replace("Используйте", "Примените");

      // Push back to Russian locale and ensure inline code is preserved
      const ruPushed = await loader.push("ru", ruTranslated);
      expect(ruPushed).toBe("Примените `бар` функцию.");
    });
  });

  describe("Image URLs with Parentheses", () => {
    it("should handle image URLs with parentheses", async () => {
      const md = dedent`
        Text above.

        ![](https://example.com/image(with)parentheses.jpg)

        Text below.
      `;

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

    it("should handle image URLs with nested parentheses", async () => {
      const md = dedent`
        Text above.

        ![Alt text](https://example.com/image(with(nested)parentheses).jpg)

        Text below.
      `;

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

    it("should handle image URLs with parentheses in blockquotes", async () => {
      const md = dedent`
        > ![Blockquote image](https://example.com/image(in)blockquote.jpg)
      `;

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

    it("should handle image URLs with parentheses in JSX components", async () => {
      const md = dedent`
        <Component>
        ![Component image](https://example.com/image(in)component.jpg)
        </Component>
      `;

      const expected = dedent`
        <Component>

        ![Component image](https://example.com/image(in)component.jpg)

        </Component>
      `;

      const pulled = await loader.pull("en", md);
      const pushed = await loader.push("es", pulled);
      expect(pushed).toBe(expected);
    });
  });

  describe("placeholder replacement bugs", () => {
    it("should handle special $ characters in code content correctly", async () => {
      const loader = createMdxCodePlaceholderLoader();
      loader.setDefaultLocale("en");

      // Code containing special $ characters that have special meaning in replaceAll
      const content = dedent`
        Text before.

        \`\`\`js
        const price = "$100";
        const template = "$\`text\`";
        const special = "$&$'$\`";
        \`\`\`

        Text after.
      `;

      // Pull and then push the same content
      const pulled = await loader.pull("en", content);
      const translated = pulled.replace("Text before", "Texto antes");
      const pushed = await loader.push("en", translated);

      // Should not contain any placeholders
      expect(pushed).not.toMatch(/---CODE-PLACEHOLDER-[0-9a-f]+---/);

      // Should preserve all special $ characters exactly as they were
      expect(pushed).toContain('const price = "$100";');
      expect(pushed).toContain('const template = "$`text`";');
      expect(pushed).toContain('const special = "$&$\'$`";');
      expect(pushed).toContain("Texto antes");
    });

    it("should handle inline code with $ characters correctly", async () => {
      const loader = createMdxCodePlaceholderLoader();
      loader.setDefaultLocale("en");

      const content = "Use `$price` and `$&` and `$\`` in your code.";

      // Pull and then push the same content
      const pulled = await loader.pull("en", content);
      const translated = pulled.replace("Use", "Utilize");
      const pushed = await loader.push("en", translated);

      // Should not contain any placeholders
      expect(pushed).not.toMatch(/---INLINE-CODE-PLACEHOLDER-[0-9a-f]+---/);

      // Should preserve all special $ characters
      expect(pushed).toContain("`$price`");
      expect(pushed).toContain("`$&`");
      expect(pushed).toContain("`$\``");
      expect(pushed).toContain("Utilize");
    });

    it("should not leave placeholders when content matches", async () => {
      const loader = createMdxCodePlaceholderLoader();
      loader.setDefaultLocale("en");

      const content = "Use the `getData()` function.";

      // Pull and then push the same content - should work correctly
      const pulled = await loader.pull("en", content);
      const translated = pulled.replace("Use", "Utilize");
      const pushed = await loader.push("en", translated);

      // Should not contain any placeholders
      expect(pushed).not.toMatch(/---INLINE-CODE-PLACEHOLDER-[0-9a-f]+---/);
      expect(pushed).not.toMatch(/---CODE-PLACEHOLDER-[0-9a-f]+---/);
      expect(pushed).toContain("`getData()`");
      expect(pushed).toContain("Utilize");
    });

    it("should replace all placeholders including those from different sources", async () => {
      const loader = createMdxCodePlaceholderLoader();
      loader.setDefaultLocale("en");

      // Simulate the exact scenario from the user's bug report
      const englishContent = "Use the `getData()` function.";
      const arabicContent = "استخدم `الحصول_على_البيانات()` الدالة.";

      // First pull English (required as default locale)
      await loader.pull("en", englishContent);

      // Pull Arabic content to create placeholders
      const arabicPulled = await loader.pull("ar", arabicContent);

      // Simulate translation: translator changes text but keeps placeholder
      const arabicTranslated = arabicPulled.replace("استخدم", "قم بتطبيق");

      // Push back - this should now work correctly with the fix
      const pushedResult = await loader.push("ar", arabicTranslated);

      // The fix: ALL placeholders should be replaced, including Arabic ones
      expect(pushedResult).not.toMatch(
        /---INLINE-CODE-PLACEHOLDER-[0-9a-f]+---/,
      );
      expect(pushedResult).not.toMatch(/---CODE-PLACEHOLDER-[0-9a-f]+---/);

      // The Arabic inline code should be preserved and translated text should be there
      expect(pushedResult).toContain("`الحصول_على_البيانات()`");
      expect(pushedResult).toContain("قم بتطبيق");
    });

    it("should replace placeholders even when pullInput state is overwritten", async () => {
      const loader = createMdxCodePlaceholderLoader();
      loader.setDefaultLocale("en");

      const englishContent = "Use the `getData()` function.";
      const arabicContent = "استخدم `الحصول_على_البيانات()` الدالة.";

      // First pull English (required as default locale)
      await loader.pull("en", englishContent);

      // Pull Arabic content to create placeholders
      const arabicPulled = await loader.pull("ar", arabicContent);

      // Simulate translation: translator changes text but keeps placeholder
      const arabicTranslated = arabicPulled.replace("استخدم", "قم بتطبيق");

      // Now pull English again, overwriting pullInput state
      // This simulates the real-world scenario where the loader state gets out of sync
      await loader.pull("en", englishContent);

      // Push the Arabic translation - should work despite state being overwritten
      const pushedResult = await loader.push("ar", arabicTranslated);

      // All placeholders should be replaced, even when not in current pullInput
      expect(pushedResult).not.toMatch(
        /---INLINE-CODE-PLACEHOLDER-[0-9a-f]+---/,
      );
      expect(pushedResult).not.toMatch(/---CODE-PLACEHOLDER-[0-9a-f]+---/);
      expect(pushedResult).toContain("`الحصول_على_البيانات()`");
      expect(pushedResult).toContain("قم بتطبيق");
    });
  });

  describe("raw code outside fences", () => {
    it("should handle raw JavaScript code outside fences", async () => {
      const loader = createMdxCodePlaceholderLoader();
      loader.setDefaultLocale("en");

      // Test case matching user's file structure - raw JS between JSX components
      const md = dedent`
        </Tabs>

        // Attach to button click
        document.getElementById('executeBtn')?.addEventListener('click', executeClientSideWorkflow);

        <Callout type="warning">
          Content here
        </Callout>
      `;

      const pulled = await loader.pull("en", md);
      const pushed = await loader.push("en", pulled);

      // Should round-trip correctly
      expect(pushed).toBe(md);
    });

    it("should handle mixed code blocks and raw code", async () => {
      const loader = createMdxCodePlaceholderLoader();
      loader.setDefaultLocale("en");

      const md = dedent`
        Here's a code block:

        \`\`\`typescript
        const x = 1;
        \`\`\`

        Now some raw code outside:
        // This is outside
        const y = 2;

        And another block:

        \`\`\`javascript
        const z = 3;
        \`\`\`
      `;

      const pulled = await loader.pull("en", md);
      const pushed = await loader.push("en", pulled);

      // Should preserve raw code outside fences
      expect(pushed).toContain("// This is outside");
      expect(pushed).toContain("const y = 2;");
    });

    it("should handle code blocks with extra blank lines added by translation", async () => {
      const loader = createMdxCodePlaceholderLoader();
      loader.setDefaultLocale("en");

      // English source - no extra blank lines
      const enMd = dedent`
        <Tab value="npm">
          \`\`\`bash
          npm install
          \`\`\`
        </Tab>
      `;

      // Pull English to establish placeholders
      const enPulled = await loader.pull("en", enMd);

      // German translation with extra blank lines (simulating AI translation behavior)
      const deMd = dedent`
        <Tab value="npm">

          \`\`\`bash
          npm install
          \`\`\`

        </Tab>
      `;

      // Pull German version
      const dePulled = await loader.pull("de", deMd);

      // Push back - should restore code blocks correctly
      const dePushed = await loader.push("de", dePulled);

      // The code block should be present and not replaced with placeholder
      expect(dePushed).toContain("```bash");
      expect(dePushed).toContain("npm install");
      expect(dePushed).not.toMatch(/---CODE-PLACEHOLDER-/);
    });

    it("should preserve double newlines around placeholders for section splitting", async () => {
      const loader = createMdxCodePlaceholderLoader();
      loader.setDefaultLocale("en");

      // Test that placeholders maintain double newlines so section-split works correctly
      const md = dedent`
        Text before.

        \`\`\`typescript
        code1
        \`\`\`

        Text between.

        \`\`\`javascript
        code2
        \`\`\`

        Text after.
      `;

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

      // Verify placeholders are surrounded by double newlines for proper section splitting
      const placeholders = pulled.match(/---CODE-PLACEHOLDER-[a-f0-9]+---/g);
      expect(placeholders).toHaveLength(2);

      // Check that each placeholder has double newlines around it
      for (const placeholder of placeholders!) {
        // Should have \n\n before (except at start) and \n\n after (except at end)
        const placeholderIndex = pulled.indexOf(placeholder);

        // Check for double newline after (unless at end)
        const afterPlaceholder = pulled.substring(
          placeholderIndex + placeholder.length,
          placeholderIndex + placeholder.length + 2,
        );
        if (placeholderIndex + placeholder.length < pulled.length - 2) {
          expect(afterPlaceholder).toBe("\n\n");
        }
      }

      // Ensure we can split on \n\n and get separate sections
      const sections = pulled.split("\n\n").filter(Boolean);
      expect(sections.length).toBeGreaterThanOrEqual(5); // Text + placeholder + text + placeholder + text
    });
  });
});

describe("adjacent code blocks bug", () => {
  it("should handle closing fence followed immediately by opening fence", async () => {
    const loader = createMdxCodePlaceholderLoader();
    loader.setDefaultLocale("en");

    // This reproduces the actual bug from the user's file
    const md = dedent`
      \`\`\`typescript
      function example() {
        return true;
      }
      \`\`\`

      \`\`\`typescript
      import { Something } from 'somewhere';
      \`\`\`
    `;

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

    console.log("PULLED CONTENT:");
    console.log(pulled);
    console.log("---");

    // The bug: placeholder is concatenated with "typescript" from next block
    const bugPattern = /---CODE-PLACEHOLDER-[a-f0-9]+---typescript/;
    expect(pulled).not.toMatch(bugPattern);

    // Should have proper separation
    expect(pulled).toMatch(
      /---CODE-PLACEHOLDER-[a-f0-9]+---\n\n---CODE-PLACEHOLDER-[a-f0-9]+---/,
    );
  });
});

describe("$ special character handling in replacement functions", () => {
  it("should preserve $ characters in ensureTrailingFenceNewline", async () => {
    const loader = createMdxCodePlaceholderLoader();
    loader.setDefaultLocale("en");

    // Tests fix for lines 38, 68: replaceAll(match, () => replacement)
    // Code block with $ that would trigger special replacement behavior if not using function replacer
    const content = dedent`
      Some text
      \`\`\`js
      console.log('Current period cost: $' + amount);
      const template = \`Price: $\${price}\`;
      \`\`\`
      More text
    `;

    const pulled = await loader.pull("en", content);
    const pushed = await loader.push("en", pulled);

    // All $ characters should be preserved exactly
    expect(pushed).toContain("console.log('Current period cost: $' + amount);");
    expect(pushed).toContain("const template = `Price: $");
  });

  it("should preserve $ characters in ensureSurroundingImageNewlines", async () => {
    const loader = createMdxCodePlaceholderLoader();
    loader.setDefaultLocale("en");

    // Tests fix for line 38: replaceAll(match, () => replacement) in image handling
    // Image with $ in URL and alt text that would break with string replacer
    const content = dedent`
      Here is an image:
      ![Price: $100](https://api.example.com/chart?price=$500&currency=$USD)
      End of text
    `;

    const pulled = await loader.pull("en", content);
    const pushed = await loader.push("en", pulled);

    // All $ characters in URL and alt text should be preserved
    expect(pushed).toContain("![Price: $100]");
    expect(pushed).toContain("price=$500&currency=$USD");
  });
});

```

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

```typescript
import {
  bucketTypeSchema,
  I18nConfig,
  localeCodeSchema,
  resolveOverriddenLocale,
} from "@lingo.dev/_spec";
import { Command } from "interactive-commander";
import Z from "zod";
import _ from "lodash";
import * as path from "path";
import { getConfig } from "../utils/config";
import { getSettings } from "../utils/settings";
import {
  ConfigError,
  AuthenticationError,
  ValidationError,
  LocalizationError,
  BucketProcessingError,
  getCLIErrorType,
  isLocalizationError,
  isBucketProcessingError,
  ErrorDetail,
  aggregateErrorAnalytics,
  createPreviousErrorContext,
} from "../utils/errors";
import Ora from "ora";
import createBucketLoader from "../loaders";
import { createAuthenticator } from "../utils/auth";
import { getBuckets } from "../utils/buckets";
import chalk from "chalk";
import { createTwoFilesPatch } from "diff";
import inquirer from "inquirer";
import externalEditor from "external-editor";
import updateGitignore from "../utils/update-gitignore";
import createProcessor from "../processor";
import { withExponentialBackoff } from "../utils/exp-backoff";
import trackEvent from "../utils/observability";
import { createDeltaProcessor } from "../utils/delta";
import { isICUPluralObject } from "../loaders/xcode-xcstrings-icu";

export default new Command()
  .command("i18n")
  .description(
    "DEPRECATED: Run localization pipeline (prefer `run` command instead)",
  )
  .helpOption("-h, --help", "Show help")
  .option(
    "--locale <locale>",
    "Limit processing to the listed target locale codes from i18n.json. Repeat the flag to include multiple locales. Defaults to all configured target locales",
    (val: string, prev: string[]) => (prev ? [...prev, val] : [val]),
  )
  .option(
    "--bucket <bucket>",
    "Limit processing to specific bucket types defined in i18n.json (e.g., json, yaml, android). Repeat the flag to include multiple bucket types. Defaults to all buckets",
    (val: string, prev: string[]) => (prev ? [...prev, val] : [val]),
  )
  .option(
    "--key <key>",
    "Limit processing to a single translation key by exact match. Filters all buckets and locales to process only this key, useful for testing or debugging specific translations. Example: auth.login.title",
    (val: string) => encodeURIComponent(val),
  )
  .option(
    "--file [files...]",
    "Filter processing to only buckets whose file paths contain these substrings. Example: 'components' to process only files in components directories",
  )
  .option(
    "--frozen",
    "Validate translations are up-to-date without making changes - fails if source files, target files, or lockfile are out of sync. Ideal for CI/CD to ensure translation consistency before deployment",
  )
  .option(
    "--force",
    "Force re-translation of all keys, bypassing change detection. Useful when you want to regenerate translations with updated AI models or translation settings",
  )
  .option(
    "--verbose",
    "Print the translation data being processed as formatted JSON for each bucket and locale",
  )
  .option(
    "--interactive",
    "Review and edit AI-generated translations interactively before applying changes to files",
  )
  .option(
    "--api-key <api-key>",
    "Override API key from settings or environment variables",
  )
  .option(
    "--debug",
    "Pause before processing localization so you can attach a debugger",
  )
  .option(
    "--strict",
    "Stop immediately on first error instead of continuing to process remaining buckets and locales (fail-fast mode)",
  )
  .action(async function (options) {
    updateGitignore();

    const ora = Ora();
    let flags: ReturnType<typeof parseFlags>;

    try {
      flags = parseFlags(options);
    } catch (parseError: any) {
      // Handle flag validation errors (like invalid locale codes)
      await trackEvent("unknown", "cmd.i18n.error", {
        errorType: "validation_error",
        errorName: parseError.name || "ValidationError",
        errorMessage: parseError.message || "Invalid command line options",
        errorStack: parseError.stack,
        fatal: true,
        errorCount: 1,
        stage: "flag_validation",
      });
      throw parseError;
    }

    if (flags.debug) {
      // wait for user input, use inquirer
      const { debug } = await inquirer.prompt([
        {
          type: "confirm",
          name: "debug",
          message: "Debug mode. Wait for user input before continuing.",
        },
      ]);
    }

    let hasErrors = false;
    let authId: string | null = null;
    const errorDetails: ErrorDetail[] = [];
    try {
      ora.start("Loading configuration...");
      const i18nConfig = getConfig();
      const settings = getSettings(flags.apiKey);
      ora.succeed("Configuration loaded");

      ora.start("Validating localization configuration...");
      validateParams(i18nConfig, flags);
      ora.succeed("Localization configuration is valid");

      ora.start("Connecting to Lingo.dev Localization Engine...");
      const isByokMode = !!i18nConfig?.provider;

      if (isByokMode) {
        authId = null;
        ora.succeed("Using external provider (BYOK mode)");
      } else {
        const auth = await validateAuth(settings);
        authId = auth.id;
        ora.succeed(`Authenticated as ${auth.email}`);
      }

      await trackEvent(authId, "cmd.i18n.start", {
        i18nConfig,
        flags,
      });

      let buckets = getBuckets(i18nConfig!);
      if (flags.bucket?.length) {
        buckets = buckets.filter((bucket: any) =>
          flags.bucket!.includes(bucket.type),
        );
      }
      ora.succeed("Buckets retrieved");

      if (flags.file?.length) {
        buckets = buckets
          .map((bucket: any) => {
            const paths = bucket.paths.filter((path: any) =>
              flags.file!.find((file) => path.pathPattern?.includes(file)),
            );
            return { ...bucket, paths };
          })
          .filter((bucket: any) => bucket.paths.length > 0);
        if (buckets.length === 0) {
          ora.fail(
            "No buckets found. All buckets were filtered out by --file option.",
          );
          throw new Error(
            "No buckets found. All buckets were filtered out by --file option.",
          );
        } else {
          ora.info(`\x1b[36mProcessing only filtered buckets:\x1b[0m`);
          buckets.map((bucket: any) => {
            ora.info(`  ${bucket.type}:`);
            bucket.paths.forEach((path: any) => {
              ora.info(`    - ${path.pathPattern}`);
            });
          });
        }
      }

      const targetLocales = flags.locale?.length
        ? flags.locale
        : i18nConfig!.locale.targets;

      // Ensure the lockfile exists
      ora.start("Setting up localization cache...");
      const checkLockfileProcessor = createDeltaProcessor("");
      const lockfileExists = await checkLockfileProcessor.checkIfLockExists();
      if (!lockfileExists) {
        ora.start("Creating i18n.lock...");
        for (const bucket of buckets) {
          for (const bucketPath of bucket.paths) {
            const sourceLocale = resolveOverriddenLocale(
              i18nConfig!.locale.source,
              bucketPath.delimiter,
            );
            const bucketLoader = createBucketLoader(
              bucket.type,
              bucketPath.pathPattern,
              {
                defaultLocale: sourceLocale,
                injectLocale: bucket.injectLocale,
                formatter: i18nConfig!.formatter,
              },
              bucket.lockedKeys,
              bucket.lockedPatterns,
              bucket.ignoredKeys,
            );
            bucketLoader.setDefaultLocale(sourceLocale);
            await bucketLoader.init();

            const sourceData = await bucketLoader.pull(
              i18nConfig!.locale.source,
            );

            const deltaProcessor = createDeltaProcessor(bucketPath.pathPattern);
            const checksums = await deltaProcessor.createChecksums(sourceData);
            await deltaProcessor.saveChecksums(checksums);
          }
        }
        ora.succeed("Localization cache initialized");
      } else {
        ora.succeed("Localization cache loaded");
      }

      if (flags.frozen) {
        ora.start("Checking for lockfile updates...");
        let requiresUpdate: string | null = null;
        bucketLoop: for (const bucket of buckets) {
          for (const bucketPath of bucket.paths) {
            const sourceLocale = resolveOverriddenLocale(
              i18nConfig!.locale.source,
              bucketPath.delimiter,
            );

            const bucketLoader = createBucketLoader(
              bucket.type,
              bucketPath.pathPattern,
              {
                defaultLocale: sourceLocale,
                returnUnlocalizedKeys: true,
                injectLocale: bucket.injectLocale,
              },
              bucket.lockedKeys,
              bucket.lockedPatterns,
              bucket.ignoredKeys,
            );
            bucketLoader.setDefaultLocale(sourceLocale);
            await bucketLoader.init();

            const { unlocalizable: sourceUnlocalizable, ...sourceData } =
              await bucketLoader.pull(i18nConfig!.locale.source);
            const deltaProcessor = createDeltaProcessor(bucketPath.pathPattern);
            const sourceChecksums =
              await deltaProcessor.createChecksums(sourceData);
            const savedChecksums = await deltaProcessor.loadChecksums();

            // Get updated data by comparing current checksums with saved checksums
            const updatedSourceData = _.pickBy(
              sourceData,
              (value, key) => sourceChecksums[key] !== savedChecksums[key],
            );

            // translation was updated in the source file
            if (Object.keys(updatedSourceData).length > 0) {
              requiresUpdate = "updated";
              break bucketLoop;
            }

            for (const _targetLocale of targetLocales) {
              const targetLocale = resolveOverriddenLocale(
                _targetLocale,
                bucketPath.delimiter,
              );
              const { unlocalizable: targetUnlocalizable, ...targetData } =
                await bucketLoader.pull(targetLocale);

              const missingKeys = _.difference(
                Object.keys(sourceData),
                Object.keys(targetData),
              );
              const extraKeys = _.difference(
                Object.keys(targetData),
                Object.keys(sourceData),
              );
              const unlocalizableDataDiff = !_.isEqual(
                sourceUnlocalizable,
                targetUnlocalizable,
              );

              // translation is missing in the target file
              if (missingKeys.length > 0) {
                requiresUpdate = "missing";
                break bucketLoop;
              }

              // target file has extra translations
              if (extraKeys.length > 0) {
                requiresUpdate = "extra";
                break bucketLoop;
              }

              // unlocalizable keys do not match
              if (unlocalizableDataDiff) {
                requiresUpdate = "unlocalizable";
                break bucketLoop;
              }
            }
          }
        }

        if (requiresUpdate) {
          const message = {
            updated: "Source file has been updated.",
            missing: "Target file is missing translations.",
            extra:
              "Target file has extra translations not present in the source file.",
            unlocalizable:
              "Unlocalizable data (such as booleans, dates, URLs, etc.) do not match.",
          }[requiresUpdate];
          ora.fail(
            `Localization data has changed; please update i18n.lock or run without --frozen.`,
          );
          ora.fail(`  Details: ${message}`);
          throw new Error(
            `Localization data has changed; please update i18n.lock or run without --frozen. Details: ${message}`,
          );
        } else {
          ora.succeed("No lockfile updates required.");
        }
      }

      // Process each bucket
      for (const bucket of buckets) {
        try {
          console.log();
          ora.info(`Processing bucket: ${bucket.type}`);
          for (const bucketPath of bucket.paths) {
            const bucketOra = Ora({ indent: 2 }).info(
              `Processing path: ${bucketPath.pathPattern}`,
            );

            const sourceLocale = resolveOverriddenLocale(
              i18nConfig!.locale.source,
              bucketPath.delimiter,
            );

            const bucketLoader = createBucketLoader(
              bucket.type,
              bucketPath.pathPattern,
              {
                defaultLocale: sourceLocale,
                injectLocale: bucket.injectLocale,
                formatter: i18nConfig!.formatter,
              },
              bucket.lockedKeys,
              bucket.lockedPatterns,
              bucket.ignoredKeys,
            );
            bucketLoader.setDefaultLocale(sourceLocale);
            await bucketLoader.init();
            let sourceData = await bucketLoader.pull(sourceLocale);

            for (const _targetLocale of targetLocales) {
              const targetLocale = resolveOverriddenLocale(
                _targetLocale,
                bucketPath.delimiter,
              );
              try {
                bucketOra.start(
                  `[${sourceLocale} -> ${targetLocale}] (0%) Localization in progress...`,
                );

                sourceData = await bucketLoader.pull(sourceLocale);

                const targetData = await bucketLoader.pull(targetLocale);
                const deltaProcessor = createDeltaProcessor(
                  bucketPath.pathPattern,
                );
                const checksums = await deltaProcessor.loadChecksums();
                const delta = await deltaProcessor.calculateDelta({
                  sourceData,
                  targetData,
                  checksums,
                });
                let processableData = _.chain(sourceData)
                  .entries()
                  .filter(
                    ([key, value]) =>
                      delta.added.includes(key) ||
                      delta.updated.includes(key) ||
                      !!flags.force,
                  )
                  .fromPairs()
                  .value();

                if (flags.key) {
                  processableData = _.pickBy(
                    processableData,
                    (_, key) => key === flags.key,
                  );
                }
                if (flags.verbose) {
                  bucketOra.info(JSON.stringify(processableData, null, 2));
                }

                bucketOra.start(
                  `[${sourceLocale} -> ${targetLocale}] [${
                    Object.keys(processableData).length
                  } entries] (0%) AI localization in progress...`,
                );
                let processPayload = createProcessor(i18nConfig!.provider, {
                  apiKey: settings.auth.apiKey,
                  apiUrl: settings.auth.apiUrl,
                });
                processPayload = withExponentialBackoff(
                  processPayload,
                  3,
                  1000,
                );

                const processedTargetData = await processPayload(
                  {
                    sourceLocale,
                    sourceData,
                    processableData,
                    targetLocale,
                    targetData,
                  },
                  (progress, sourceChunk, processedChunk) => {
                    bucketOra.text = `[${sourceLocale} -> ${targetLocale}] [${
                      Object.keys(processableData).length
                    } entries] (${progress}%) AI localization in progress...`;
                  },
                );

                if (flags.verbose) {
                  bucketOra.info(JSON.stringify(processedTargetData, null, 2));
                }

                let finalTargetData = _.merge(
                  {},
                  sourceData,
                  targetData,
                  processedTargetData,
                );

                // rename keys
                finalTargetData = _.chain(finalTargetData)
                  .entries()
                  .map(([key, value]) => {
                    const renaming = delta.renamed.find(
                      ([oldKey, newKey]) => oldKey === key,
                    );
                    if (!renaming) {
                      return [key, value];
                    }
                    return [renaming[1], value];
                  })
                  .fromPairs()
                  .value();

                if (flags.interactive) {
                  bucketOra.stop();
                  const reviewedData = await reviewChanges({
                    pathPattern: bucketPath.pathPattern,
                    targetLocale,
                    currentData: targetData,
                    proposedData: finalTargetData,
                    sourceData,
                    force: flags.force!,
                  });

                  finalTargetData = reviewedData;
                  bucketOra.start(
                    `Applying changes to ${bucketPath} (${targetLocale})`,
                  );
                }

                const finalDiffSize = _.chain(finalTargetData)
                  .omitBy((value, key) => {
                    const targetValue = targetData[key];

                    // For ICU plural objects, use deep equality (excluding Symbol)
                    if (
                      isICUPluralObject(value) &&
                      isICUPluralObject(targetValue)
                    ) {
                      return _.isEqual(
                        { icu: value.icu, _meta: value._meta },
                        { icu: targetValue.icu, _meta: targetValue._meta },
                      );
                    }

                    // Default strict equality for other values
                    return value === targetValue;
                  })
                  .size()
                  .value();

                // Push to bucket all the time as there might be changes to unlocalizable keys
                await bucketLoader.push(targetLocale, finalTargetData);

                if (finalDiffSize > 0 || flags.force) {
                  bucketOra.succeed(
                    `[${sourceLocale} -> ${targetLocale}] Localization completed`,
                  );
                } else {
                  bucketOra.succeed(
                    `[${sourceLocale} -> ${targetLocale}] Localization completed (no changes).`,
                  );
                }
              } catch (_error: any) {
                const error = new LocalizationError(
                  `[${sourceLocale} -> ${targetLocale}] Localization failed: ${_error.message}`,
                  {
                    bucket: bucket.type,
                    sourceLocale,
                    targetLocale,
                    pathPattern: bucketPath.pathPattern,
                  },
                );
                errorDetails.push({
                  type: "locale_error",
                  bucket: bucket.type,
                  locale: `${sourceLocale} -> ${targetLocale}`,
                  pathPattern: bucketPath.pathPattern,
                  message: _error.message,
                  stack: _error.stack,
                });
                if (flags.strict) {
                  throw error;
                } else {
                  bucketOra.fail(error.message);
                  hasErrors = true;
                }
              }
            }

            const deltaProcessor = createDeltaProcessor(bucketPath.pathPattern);
            const checksums = await deltaProcessor.createChecksums(sourceData);
            if (!flags.locale?.length) {
              await deltaProcessor.saveChecksums(checksums);
            }
          }
        } catch (_error: any) {
          const error = new BucketProcessingError(
            `Failed to process bucket ${bucket.type}: ${_error.message}`,
            bucket.type,
          );
          errorDetails.push({
            type: "bucket_error",
            bucket: bucket.type,
            message: _error.message,
            stack: _error.stack,
          });
          if (flags.strict) {
            throw error;
          } else {
            ora.fail(error.message);
            hasErrors = true;
          }
        }
      }
      console.log();
      if (!hasErrors) {
        ora.succeed("Localization completed.");
        await trackEvent(authId, "cmd.i18n.success", {
          i18nConfig: {
            sourceLocale: i18nConfig!.locale.source,
            targetLocales: i18nConfig!.locale.targets,
            bucketTypes: Object.keys(i18nConfig!.buckets),
          },
          flags,
          bucketCount: buckets.length,
          localeCount: targetLocales.length,
          processedSuccessfully: true,
        });
      } else {
        ora.warn("Localization completed with errors.");
        await trackEvent(authId || "unknown", "cmd.i18n.error", {
          flags,
          ...aggregateErrorAnalytics(
            errorDetails,
            buckets,
            targetLocales,
            i18nConfig!,
          ),
        });
      }
    } catch (error: any) {
      ora.fail(error.message);

      // Use robust error type detection
      const errorType = getCLIErrorType(error);

      // Extract additional context from typed errors
      let errorContext: any = {};
      if (isLocalizationError(error)) {
        errorContext = {
          bucket: error.bucket,
          sourceLocale: error.sourceLocale,
          targetLocale: error.targetLocale,
          pathPattern: error.pathPattern,
        };
      } else if (isBucketProcessingError(error)) {
        errorContext = {
          bucket: error.bucket,
        };
      }

      await trackEvent(authId || "unknown", "cmd.i18n.error", {
        flags,
        errorType,
        errorName: error.name || "Error",
        errorMessage: error.message,
        errorStack: error.stack,
        errorContext,
        fatal: true,
        errorCount: errorDetails.length + 1,
        previousErrors: createPreviousErrorContext(errorDetails),
      });
    }
  });

function parseFlags(options: any) {
  return Z.object({
    apiKey: Z.string().optional(),
    locale: Z.array(localeCodeSchema).optional(),
    bucket: Z.array(bucketTypeSchema).optional(),
    force: Z.boolean().optional(),
    frozen: Z.boolean().optional(),
    verbose: Z.boolean().optional(),
    strict: Z.boolean().optional(),
    key: Z.string().optional(),
    file: Z.array(Z.string()).optional(),
    interactive: Z.boolean().default(false),
    debug: Z.boolean().default(false),
  }).parse(options);
}

// Export validateAuth for use in other commands
export async function validateAuth(settings: ReturnType<typeof getSettings>) {
  if (!settings.auth.apiKey) {
    throw new AuthenticationError({
      message:
        "Not authenticated. Please run `lingo.dev login` to authenticate.",
      docUrl: "authError",
    });
  }

  const authenticator = createAuthenticator({
    apiKey: settings.auth.apiKey,
    apiUrl: settings.auth.apiUrl,
  });
  const user = await authenticator.whoami();
  if (!user) {
    throw new AuthenticationError({
      message: "Invalid API key. Please run `lingo.dev login` to authenticate.",
      docUrl: "authError",
    });
  }

  return user;
}

function validateParams(
  i18nConfig: I18nConfig | null,
  flags: ReturnType<typeof parseFlags>,
) {
  if (!i18nConfig) {
    throw new ConfigError({
      message:
        "i18n.json not found. Please run `lingo.dev init` to initialize the project.",
      docUrl: "i18nNotFound",
    });
  } else if (!i18nConfig.buckets || !Object.keys(i18nConfig.buckets).length) {
    throw new ConfigError({
      message:
        "No buckets found in i18n.json. Please add at least one bucket containing i18n content.",
      docUrl: "bucketNotFound",
    });
  } else if (
    flags.locale?.some((locale) => !i18nConfig.locale.targets.includes(locale))
  ) {
    throw new ValidationError({
      message: `One or more specified locales do not exist in i18n.json locale.targets. Please add them to the list and try again.`,
      docUrl: "localeTargetNotFound",
    });
  } else if (
    flags.bucket?.some(
      (bucket) =>
        !i18nConfig.buckets[bucket as keyof typeof i18nConfig.buckets],
    )
  ) {
    throw new ValidationError({
      message: `One or more specified buckets do not exist in i18n.json. Please add them to the list and try again.`,
      docUrl: "bucketNotFound",
    });
  }
}

async function reviewChanges(args: {
  pathPattern: string;
  targetLocale: string;
  currentData: Record<string, any>;
  proposedData: Record<string, any>;
  sourceData: Record<string, any>;
  force: boolean;
}): Promise<Record<string, any>> {
  const currentStr = JSON.stringify(args.currentData, null, 2);
  const proposedStr = JSON.stringify(args.proposedData, null, 2);

  // Early return if no changes
  if (currentStr === proposedStr && !args.force) {
    console.log(
      `\n${chalk.blue(args.pathPattern)} (${chalk.yellow(
        args.targetLocale,
      )}): ${chalk.gray("No changes to review")}`,
    );
    return args.proposedData;
  }

  const patch = createTwoFilesPatch(
    `${args.pathPattern} (current)`,
    `${args.pathPattern} (proposed)`,
    currentStr,
    proposedStr,
    undefined,
    undefined,
    { context: 3 },
  );

  // Color the diff output
  const coloredDiff = patch
    .split("\n")
    .map((line) => {
      if (line.startsWith("+")) return chalk.green(line);
      if (line.startsWith("-")) return chalk.red(line);
      if (line.startsWith("@")) return chalk.cyan(line);
      return line;
    })
    .join("\n");

  console.log(
    `\nReviewing changes for ${chalk.blue(args.pathPattern)} (${chalk.yellow(
      args.targetLocale,
    )}):`,
  );
  console.log(coloredDiff);

  const { action } = await inquirer.prompt([
    {
      type: "list",
      name: "action",
      message: "Choose action:",
      choices: [
        { name: "Approve changes", value: "approve" },
        { name: "Skip changes", value: "skip" },
        { name: "Edit individually", value: "edit" },
      ],
      default: "approve",
    },
  ]);

  if (action === "approve") {
    return args.proposedData;
  }

  if (action === "skip") {
    return args.currentData;
  }

  // If edit was chosen, prompt for each changed value
  const customData = { ...args.currentData };
  const changes = _.reduce(
    args.proposedData,
    (result: string[], value: string, key: string) => {
      if (args.currentData[key] !== value) {
        result.push(key);
      }
      return result;
    },
    [],
  );

  for (const key of changes) {
    console.log(`\nEditing value for: ${chalk.cyan(key)}`);
    console.log(chalk.gray("Source text:"), chalk.blue(args.sourceData[key]));
    console.log(
      chalk.gray("Current value:"),
      chalk.red(args.currentData[key] || "(empty)"),
    );
    console.log(
      chalk.gray("Suggested value:"),
      chalk.green(args.proposedData[key]),
    );
    console.log(
      chalk.gray(
        "\nYour editor will open. Edit the text and save to continue.",
      ),
    );
    console.log(chalk.gray("------------"));

    try {
      // Prepare the editor content with a header comment and the suggested value
      const editorContent = [
        "# Edit the translation below.",
        "# Lines starting with # will be ignored.",
        "# Save and exit the editor to continue.",
        "#",
        `# Source text (${chalk.blue("English")}):`,
        `# ${args.sourceData[key]}`,
        "#",
        `# Current value (${chalk.red(args.targetLocale)}):`,
        `# ${args.currentData[key] || "(empty)"}`,
        "#",
        args.proposedData[key],
      ].join("\n");

      const result = externalEditor.edit(editorContent);

      // Clean up the result by removing comments and trimming
      const customValue = result
        .split("\n")
        .filter((line) => !line.startsWith("#"))
        .join("\n")
        .trim();

      if (customValue) {
        customData[key] = customValue;
      } else {
        console.log(
          chalk.yellow("Empty value provided, keeping the current value."),
        );
        customData[key] = args.currentData[key] || args.proposedData[key];
      }
    } catch (error) {
      console.log(
        chalk.red("Error while editing, keeping the suggested value."),
      );
      customData[key] = args.proposedData[key];
    }
  }

  return customData;
}

```

--------------------------------------------------------------------------------
/packages/cli/src/cli/loaders/android.spec.ts:
--------------------------------------------------------------------------------

```typescript
import { describe, it, expect, vi, beforeEach } from "vitest";
import fs from "fs/promises";
import createAndroidLoader from "./android";

describe("android loader", () => {
  const setupMocks = (input: string) => {
    vi.mock("fs/promises");
    vi.mocked(fs.readFile).mockResolvedValue(input);
    vi.mocked(fs.writeFile).mockResolvedValue(undefined);
  };

  beforeEach(() => {
    vi.clearAllMocks();
  });

  it("should correctly handle basic string resources", async () => {
    const input = `
      <resources>
        <string name="hello">Hello World</string>
        <string name="app_name">My App</string>
      </resources>
    `.trim();

    const androidLoader = createAndroidLoader().setDefaultLocale("en");
    const result = await androidLoader.pull("en", input);

    expect(result).toEqual({
      hello: "Hello World",
      app_name: "My App",
    });
  });

  it("should correctly handle string arrays", async () => {
    const input = `
      <resources>
        <string-array name="planets">
          <item>Mercury</item>
          <item>Venus</item>
          <item>Earth</item>
          <item>Mars</item>
        </string-array>
      </resources>
    `.trim();

    const androidLoader = createAndroidLoader().setDefaultLocale("en");
    const result = await androidLoader.pull("en", input);

    expect(result).toEqual({
      planets: ["Mercury", "Venus", "Earth", "Mars"],
    });
  });

  it("should correctly handle plurals with different quantity types", async () => {
    const input = `
      <resources>
        <plurals name="numberOfSongsAvailable">
          <item quantity="zero">No songs found.</item>
          <item quantity="one">1 song found.</item>
          <item quantity="few">%d songs found.</item>
          <item quantity="many">%d songs found.</item>
          <item quantity="other">%d songs found.</item>
        </plurals>
      </resources>
    `.trim();

    const androidLoader = createAndroidLoader().setDefaultLocale("en");
    const result = await androidLoader.pull("en", input);

    expect(result).toEqual({
      numberOfSongsAvailable: {
        zero: "No songs found.",
        one: "1 song found.",
        few: "%d songs found.",
        many: "%d songs found.",
        other: "%d songs found.",
      },
    });
  });

  it("should correctly handle HTML markup in strings", async () => {
    const input = `
      <resources>
        <string name="welcome">Welcome to <b>Android</b>!</string>
        <string name="formatted">This is <i>italic</i> and this is <b>bold</b></string>
      </resources>
    `.trim();

    const androidLoader = createAndroidLoader().setDefaultLocale("en");
    const result = await androidLoader.pull("en", input);

    expect(result).toEqual({
      welcome: "Welcome to <b>Android</b>!",
      formatted: "This is <i>italic</i> and this is <b>bold</b>",
    });
  });

  it("should correctly handle format strings", async () => {
    const input = `
      <resources>
        <string name="welcome_messages">Hello, %1$s! You have %2$d new messages.</string>
        <string name="complex_format">Value: %1$.2f, Text: %2$s, Number: %3$d</string>
      </resources>
    `.trim();

    const androidLoader = createAndroidLoader().setDefaultLocale("en");
    const result = await androidLoader.pull("en", input);

    expect(result).toEqual({
      welcome_messages: "Hello, %1$s! You have %2$d new messages.",
      complex_format: "Value: %1$.2f, Text: %2$s, Number: %3$d",
    });
  });

  it("should correctly handle single quote escaping", async () => {
    const input = `
      <resources>
        <string name="apostrophe">Don\\'t forget me</string>
        <string name="escaped_quotes">This has \\'single\\' quotes</string>
      </resources>
    `.trim();

    const androidLoader = createAndroidLoader().setDefaultLocale("en");
    const result = await androidLoader.pull("en", input);

    // Now expect normalized apostrophes in the JS object
    expect(result).toEqual({
      apostrophe: "Don't forget me",
      escaped_quotes: "This has 'single' quotes",
    });

    // When pushing back, apostrophes should be escaped again
    const pushed = await androidLoader.push("en", result);
    expect(pushed).toContain("Don\\'t forget me");
    expect(pushed).toContain("This has \\'single\\' quotes");
  });

  it("should correctly handle CDATA sections", async () => {
    const input = `
      <resources>
        <string name="html_content"><![CDATA[<html><body><h1>Title</h1><p>Paragraph</p></body></html>]]></string>
      </resources>
    `.trim();

    const androidLoader = createAndroidLoader().setDefaultLocale("en");
    const result = await androidLoader.pull("en", input);

    expect(result).toEqual({
      html_content: "<html><body><h1>Title</h1><p>Paragraph</p></body></html>",
    });
  });

  it("should correctly handle multiple CDATA sections in a single string", async () => {
    const input = `
      <resources>
        <string name="multiple_cdata"><![CDATA[<first>section</first>]]><![CDATA[<second>section</second>]]></string>
      </resources>
    `.trim();

    const androidLoader = createAndroidLoader().setDefaultLocale("en");
    const result = await androidLoader.pull("en", input);

    expect(result).toEqual({
      multiple_cdata: "<first>section</first><second>section</second>",
    });
  });

  it("should correctly handle nested HTML tags with attributes", async () => {
    const input = `
      <resources>
        <string name="complex_html">This is <span style="color:red">red text</span> and <a href="https://example.com">a link</a></string>
      </resources>
    `.trim();

    const androidLoader = createAndroidLoader().setDefaultLocale("en");
    const result = await androidLoader.pull("en", input);

    expect(result).toEqual({
      complex_html:
        'This is <span style="color:red">red text</span> and <a href="https://example.com">a link</a>',
    });
  });

  it("should correctly handle XML entities in strings", async () => {
    const input = `
      <resources>
        <string name="entities">This string contains &lt;brackets&gt; and &amp;ampersands</string>
      </resources>
    `.trim();

    const androidLoader = createAndroidLoader().setDefaultLocale("en");
    const result = await androidLoader.pull("en", input);

    expect(result).toEqual({
      entities: "This string contains <brackets> and &ampersands",
    });
  });

  it("should correctly handle empty strings", async () => {
    const input = `
      <resources>
        <string name="empty"></string>
        <string name="whitespace">   </string>
      </resources>
    `.trim();

    const androidLoader = createAndroidLoader().setDefaultLocale("en");
    const result = await androidLoader.pull("en", input);

    expect(result).toEqual({
      empty: "",
      whitespace: "   ",
    });
  });

  it("should correctly handle very long strings", async () => {
    const longText = "This is a very long string.".repeat(100);
    const input = `
      <resources>
        <string name="long_text">${longText}</string>
      </resources>
    `.trim();

    const androidLoader = createAndroidLoader().setDefaultLocale("en");
    const result = await androidLoader.pull("en", input);

    expect(result).toEqual({
      long_text: longText,
    });
  });

  it("should correctly handle strings with newlines and whitespace", async () => {
    const input = `
      <resources>
        <string name="multiline">Line 1
Line 2
  Line 3 with indent</string>
      </resources>
    `.trim();

    const androidLoader = createAndroidLoader().setDefaultLocale("en");
    const result = await androidLoader.pull("en", input);

    expect(result).toEqual({
      multiline: "Line 1\nLine 2\n  Line 3 with indent",
    });
  });

  it("should correctly handle Unicode characters", async () => {
    const input = `
      <resources>
        <string name="unicode">Unicode: 你好, こんにちは, Привет, مرحبا, 안녕하세요</string>
      </resources>
    `.trim();

    const androidLoader = createAndroidLoader().setDefaultLocale("en");
    const result = await androidLoader.pull("en", input);

    expect(result).toEqual({
      unicode: "Unicode: 你好, こんにちは, Привет, مرحبا, 안녕하세요",
    });
  });

  it("should skip non-translatable strings", async () => {
    const input = `
      <resources>
        <string name="app_name" translatable="false">My App</string>
        <string name="welcome">Welcome</string>
        <string name="version" translatable="false">1.0.0</string>
      </resources>
    `.trim();

    const androidLoader = createAndroidLoader().setDefaultLocale("en");
    const result = await androidLoader.pull("en", input);

    expect(result).toEqual({
      welcome: "Welcome",
    });
    expect(result.app_name).toBeUndefined();
    expect(result.version).toBeUndefined();
  });

  it("should correctly push string resources back to XML", async () => {
    const payload = {
      hello: "Hola",
      welcome: "Bienvenido",
    };

    const androidLoader = createAndroidLoader().setDefaultLocale("en");
    await androidLoader.pull(
      "en",
      `
      <resources>
        <string name="hello">Hello</string>
        <string name="welcome">Welcome</string>
      </resources>
    `,
    );

    const result = await androidLoader.push("es", payload);

    expect(result).toContain('<string name="hello">Hola</string>');
    expect(result).toContain('<string name="welcome">Bienvenido</string>');
  });

  it("should correctly push string arrays back to XML", async () => {
    const payload = {
      planets: ["Mercurio", "Venus", "Tierra", "Marte"],
    };

    const androidLoader = createAndroidLoader().setDefaultLocale("en");
    await androidLoader.pull(
      "en",
      `
      <resources>
        <string-array name="planets">
          <item>Mercury</item>
          <item>Venus</item>
          <item>Earth</item>
          <item>Mars</item>
        </string-array>
      </resources>
    `,
    );

    const result = await androidLoader.push("es", payload);

    expect(result).toContain('<string-array name="planets">');
    expect(result).toContain("<item>Mercurio</item>");
    expect(result).toContain("<item>Venus</item>");
    expect(result).toContain("<item>Tierra</item>");
    expect(result).toContain("<item>Marte</item>");
  });

  it("should correctly push plurals back to XML", async () => {
    const payload = {
      numberOfSongsAvailable: {
        zero: "No se encontraron canciones.",
        one: "1 canción encontrada.",
        few: "%d canciones encontradas.",
        many: "%d canciones encontradas.",
        other: "%d canciones encontradas.",
      },
    };

    const androidLoader = createAndroidLoader().setDefaultLocale("en");
    await androidLoader.pull(
      "en",
      `
      <resources>
        <plurals name="numberOfSongsAvailable">
          <item quantity="zero">No songs found.</item>
          <item quantity="one">1 song found.</item>
          <item quantity="few">%d songs found.</item>
          <item quantity="many">%d songs found.</item>
          <item quantity="other">%d songs found.</item>
        </plurals>
      </resources>
    `,
    );

    const result = await androidLoader.push("es", payload);

    expect(result).toContain('<plurals name="numberOfSongsAvailable">');
    expect(result).toContain(
      '<item quantity="zero">No se encontraron canciones.</item>',
    );
    expect(result).toContain(
      '<item quantity="one">1 canción encontrada.</item>',
    );
    expect(result).toContain(
      '<item quantity="few">%d canciones encontradas.</item>',
    );
    expect(result).toContain(
      '<item quantity="many">%d canciones encontradas.</item>',
    );
    expect(result).toContain(
      '<item quantity="other">%d canciones encontradas.</item>',
    );
  });

  it("should correctly handle mixed resource types", async () => {
    const payload = {
      app_name: "Mi Aplicación",
      planets: ["Mercurio", "Venus", "Tierra", "Marte"],
      numberOfSongsAvailable: {
        zero: "No se encontraron canciones.",
        one: "1 canción encontrada.",
        other: "%d canciones encontradas.",
      },
    };

    const androidLoader = createAndroidLoader().setDefaultLocale("en");
    await androidLoader.pull(
      "en",
      `
      <resources>
        <string name="app_name">My App</string>
        <string-array name="planets">
          <item>Mercury</item>
          <item>Venus</item>
          <item>Earth</item>
          <item>Mars</item>
        </string-array>
        <plurals name="numberOfSongsAvailable">
          <item quantity="zero">No songs found.</item>
          <item quantity="one">1 song found.</item>
          <item quantity="other">%d songs found.</item>
        </plurals>
      </resources>
    `,
    );

    const result = await androidLoader.push("es", payload);

    expect(result).toContain('<string name="app_name">Mi Aplicación</string>');
    expect(result).toContain('<string-array name="planets">');
    expect(result).toContain('<plurals name="numberOfSongsAvailable">');
  });

  it("should correctly handle Unicode escape sequences", async () => {
    const input = `
      <resources>
        <string name="unicode_escape">Unicode escape: \\u0041\\u0042\\u0043 and \\u65e5\\u672c\\u8a9e</string>
      </resources>
    `.trim();

    const androidLoader = createAndroidLoader().setDefaultLocale("en");
    const result = await androidLoader.pull("en", input);

    expect(result).toEqual({
      unicode_escape:
        "Unicode escape: \\u0041\\u0042\\u0043 and \\u65e5\\u672c\\u8a9e",
    });

    const pushed = await androidLoader.push("en", result);
    expect(pushed).toContain(
      "Unicode escape: \\u0041\\u0042\\u0043 and \\u65e5\\u672c\\u8a9e",
    );
  });

  it("should correctly handle double quote escaping", async () => {
    const input = `
      <resources>
        <string name="double_quotes">He said, \\"Hello World\\"</string>
      </resources>
    `.trim();

    const androidLoader = createAndroidLoader().setDefaultLocale("en");
    const result = await androidLoader.pull("en", input);

    expect(result).toEqual({
      double_quotes: 'He said, \\"Hello World\\"',
    });

    const pushed = await androidLoader.push("en", result);
    expect(pushed).toContain('He said, \\"Hello World\\"');
  });

  it("should correctly handle resource references", async () => {
    const input = `
      <resources>
        <string name="welcome_message">Welcome to @string/app_name</string>
        <string name="app_name">My App</string>
      </resources>
    `.trim();

    const androidLoader = createAndroidLoader().setDefaultLocale("en");
    const result = await androidLoader.pull("en", input);

    expect(result).toEqual({
      welcome_message: "Welcome to @string/app_name",
      app_name: "My App",
    });

    const pushed = await androidLoader.push("en", result);
    expect(pushed).toContain(
      '<string name="welcome_message">Welcome to @string/app_name</string>',
    );
  });

  it("should correctly handle tools namespace attributes", async () => {
    const input = `
      <resources>
        <string name="debug_only" tools:ignore="MissingTranslation">Debug message</string>
        <string name="normal">Normal message</string>
      </resources>
    `.trim();

    const androidLoader = createAndroidLoader().setDefaultLocale("en");
    const result = await androidLoader.pull("en", input);

    expect(result).toEqual({
      debug_only: "Debug message",
      normal: "Normal message",
    });
  });

  it("should correctly handle whitespace preservation with double quotes", async () => {
    const input = `
      <resources>
        <string name="preserved_whitespace">"   This string preserves    whitespace   "</string>
      </resources>
    `.trim();

    const androidLoader = createAndroidLoader().setDefaultLocale("en");
    const result = await androidLoader.pull("en", input);

    expect(result).toEqual({
      preserved_whitespace: '"   This string preserves    whitespace   "',
    });

    const pushed = await androidLoader.push("en", result);
    expect(pushed).toContain(
      '<string name="preserved_whitespace">"   This string preserves    whitespace   "</string>',
    );
  });

  it("should correctly handle special characters that need escaping", async () => {
    const input = `
      <resources>
        <string name="special_chars">Special chars: \\@, \\?, \\#, \\$, \\%</string>
      </resources>
    `.trim();

    const androidLoader = createAndroidLoader().setDefaultLocale("en");
    const result = await androidLoader.pull("en", input);

    expect(result).toEqual({
      special_chars: "Special chars: \\@, \\?, \\#, \\$, \\%",
    });

    const pushed = await androidLoader.push("en", result);
    expect(pushed).toContain("Special chars: \\@, \\?, \\#, \\$, \\%");
  });

  it("should correctly handle apostrophes in text", async () => {
    const input = `
      <resources>
        <string name="sign_in_agreement_text_1">J\'accepte les</string>
        <string name="sign_in_agreement_text_2"> et je reconnais la </string>
      </resources>
    `.trim();

    const androidLoader = createAndroidLoader().setDefaultLocale("en");
    const result = await androidLoader.pull("en", input);

    // During pull, escaped apostrophes should be normalized to simple apostrophes
    expect(result).toEqual({
      sign_in_agreement_text_1: "J'accepte les",
      sign_in_agreement_text_2: " et je reconnais la ",
    });

    // When pushing back, apostrophes should be escaped with backslash
    const pushed = await androidLoader.push("en", result);
    expect(pushed).toContain("J\\'accepte les");
    expect(pushed).toContain(" et je reconnais la ");
  });

  it("should escape apostrophes even in strings wrapped with double quotes", async () => {
    const input = `
      <resources>
        <string name="quoted_apostrophe">"J'accepte les terms"</string>
      </resources>
    `.trim();

    const androidLoader = createAndroidLoader().setDefaultLocale("en");
    const result = await androidLoader.pull("en", input);

    // During pull, the double quotes around the content should be preserved
    expect(result).toEqual({
      quoted_apostrophe: '"J\'accepte les terms"',
    });

    // When pushing back, apostrophes should be escaped even in double-quoted strings
    const pushed = await androidLoader.push("en", result);
    expect(pushed).toContain('"J\\\'accepte les terms"');
  });

  it("should correctly handle strings with apostrophes and avoid double escaping", async () => {
    const input = `
      <resources>
        <string name="welcome_message">Please don't hesitate to contact us</string>
        <item quantity="one">- %d user\'s item</item>
        <item quantity="other">- %d user\'s items</item>
      </resources>
    `.trim();

    const androidLoader = createAndroidLoader().setDefaultLocale("en");
    const result = await androidLoader.pull("en", input);

    // During pull, escaped apostrophes should be properly handled
    expect(result.welcome_message).toBe("Please don't hesitate to contact us");

    // When pushing back, apostrophes should be escaped but not double-escaped
    const pushed = await androidLoader.push("en", {
      welcome_message: "Please don't hesitate to contact us",
      item_count: {
        one: "- %d user's item",
        other: "- %d user's items",
      },
    });

    expect(pushed).toContain("Please don\\'t hesitate to contact us");
    expect(pushed).toContain("- %d user\\'s item");
    expect(pushed).not.toContain("- %d user\\\\'s item");
  });

  // Tests for Issue Fixes

  it("should preserve whitespace in array items during pull and push", async () => {
    const input = `
      <resources>
        <string-array name="mixed_items">
          <item>  Item with spaces  </item>
          <item>    </item>
        </string-array>
      </resources>
    `.trim();

    const androidLoader = createAndroidLoader().setDefaultLocale("en");
    const pulled = await androidLoader.pull("en", input);

    expect(pulled.mixed_items).toEqual(["  Item with spaces  ", "    "]);

    const pushed = await androidLoader.push("en", {
      mixed_items: ["  Elemento con espacios  ", "    "],
    });

    expect(pushed).toContain("<item>  Elemento con espacios  </item>");
    expect(pushed).toContain("<item>    </item>");
  });

  it("should retain CDATA wrappers for translated strings", async () => {
    const input = `
      <resources>
        <string name="cdata_example"><![CDATA[Special <tag> ]]></string>
      </resources>
    `.trim();

    const androidLoader = createAndroidLoader().setDefaultLocale("en");
    await androidLoader.pull("en", input);

    const pushed = await androidLoader.push("es", {
      cdata_example: "Especial <tag> ",
    });

    expect(pushed).toContain(
      '<string name="cdata_example"><![CDATA[Especial <tag> ]]></string>',
    );
  });

  it("should escape apostrophes in CDATA sections", async () => {
    const input = `
      <resources>
        <string name="review_info"><![CDATA[Hosts can't see your review until they've written one. <u>Learn more</u>]]></string>
      </resources>
    `.trim();

    const androidLoader = createAndroidLoader().setDefaultLocale("en");
    const pulled = await androidLoader.pull("en", input);

    expect(pulled.review_info).toBe(
      "Hosts can't see your review until they've written one. <u>Learn more</u>",
    );

    const pushed = await androidLoader.push("fr", {
      review_info:
        "Les hôtes ne peuvent voir votre avis qu'après en avoir écrit un. <u>En savoir plus</u>",
    });

    // Apostrophes must be escaped even inside CDATA (Android AAPT requirement)
    expect(pushed).toContain("qu\\'après");
    expect(pushed).toContain("<![CDATA[");
    expect(pushed).toContain("]]>");
    // HTML tags should NOT be escaped inside CDATA
    expect(pushed).toContain("<u>En savoir plus</u>");
    expect(pushed).not.toContain("&lt;u&gt;");
  });

  it("should preserve resource ordering after push", async () => {
    const input = `
      <resources>
        <string name="first">First</string>
        <string-array name="colors">
          <item>Red</item>
          <item>Green</item>
        </string-array>
        <plurals name="messages">
          <item quantity="one">%d message</item>
          <item quantity="other">%d messages</item>
        </plurals>
        <bool name="show_tutorial">true</bool>
        <integer name="retry_count">3</integer>
      </resources>
    `.trim();

    const androidLoader = createAndroidLoader().setDefaultLocale("en");
    const roundTrip = await androidLoader.pull("en", input);
    const pushed = await androidLoader.push("en", roundTrip);

    const order = Array.from(
      pushed.matchAll(
        /<(string|string-array|plurals|bool|integer)\s+name="([^"]+)"/g,
      ),
    ).map(([, , name]) => name);

    expect(order).toEqual([
      "first",
      "colors",
      "messages",
      "show_tutorial",
      "retry_count",
    ]);
  });

  it("should preserve XML declaration from source file", async () => {
    const input = `<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="test">Test</string>
</resources>`;

    const androidLoader = createAndroidLoader().setDefaultLocale("en");
    await androidLoader.pull("en", input);

    const result = await androidLoader.push("es", { test: "Prueba" });

    expect(result).toMatch(/^<\?xml version="1\.0" encoding="utf-8"\?>/);
  });

  it('should exclude translatable="false" items from target locale', async () => {
    const input = `
      <resources>
        <string name="app_name">My App</string>
        <string name="api_url" translatable="false">https://api.example.com</string>
        <string name="debug_key" translatable="false">DEBUG_KEY</string>
        <string-array name="colors">
          <item>Red</item>
        </string-array>
        <string-array name="urls" translatable="false">
          <item>https://example.com</item>
        </string-array>
        <plurals name="items">
          <item quantity="one">%d item</item>
          <item quantity="other">%d items</item>
        </plurals>
        <plurals name="bytes" translatable="false">
          <item quantity="one">%d byte</item>
          <item quantity="other">%d bytes</item>
        </plurals>
        <bool name="show_tutorial">true</bool>
        <bool name="is_debug" translatable="false">false</bool>
        <integer name="timeout">30</integer>
        <integer name="version" translatable="false">42</integer>
      </resources>
    `.trim();

    const androidLoader = createAndroidLoader().setDefaultLocale("en");
    await androidLoader.pull("en", input);

    const result = await androidLoader.push("es", {
      app_name: "Mi Aplicación",
      colors: ["Rojo"],
      items: { one: "%d elemento", other: "%d elementos" },
      show_tutorial: true,
      timeout: 30,
    });

    // Check that translatable="false" items are NOT included
    expect(result).not.toContain('name="api_url"');
    expect(result).not.toContain("https://api.example.com");
    expect(result).not.toContain('name="debug_key"');
    expect(result).not.toContain("DEBUG_KEY");
    expect(result).not.toContain('name="urls"');
    expect(result).not.toContain('name="bytes"');
    expect(result).not.toContain('name="is_debug"');
    expect(result).not.toContain('name="version"');

    // Check that translatable items are translated
    expect(result).toContain("Mi Aplicación");
    expect(result).toContain("Rojo");
    expect(result).toContain("elemento");
    expect(result).toContain('name="app_name"');
    expect(result).toContain('name="colors"');
    expect(result).toContain('name="items"');
    expect(result).toContain('name="show_tutorial"');
    expect(result).toContain('name="timeout"');
  });

  it("should use 4-space indentation by default", async () => {
    const input = `<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="test">Test</string>
    <string name="another">Another</string>
</resources>`;

    const androidLoader = createAndroidLoader().setDefaultLocale("en");
    await androidLoader.pull("en", input);

    const result = await androidLoader.push("es", {
      test: "Prueba",
      another: "Otro",
    });

    // Check for 4-space indentation (default)
    // Note: Users should use formatters (Prettier/Biome) for custom indentation
    expect(result).toContain('\n    <string name="test">');
    expect(result).toContain('\n    <string name="another">');
  });

  it("should preserve XML declaration encoding from source file", async () => {
    const inputUtf8 = `<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="test">Test</string>
</resources>`;

    const inputUpperUTF8 = `<?xml version="1.0" encoding="UTF-8"?>
<resources>
    <string name="test">Test</string>
</resources>`;

    const inputISO = `<?xml version="1.0" encoding="ISO-8859-1"?>
<resources>
    <string name="test">Test</string>
</resources>`;

    const androidLoader = createAndroidLoader().setDefaultLocale("en");

    // Test lowercase utf-8
    await androidLoader.pull("en", inputUtf8);
    let result = await androidLoader.push("es", { test: "Prueba" });
    expect(result).toMatch(/^<\?xml version="1\.0" encoding="utf-8"\?>/);

    // Test uppercase UTF-8
    await androidLoader.pull("en", inputUpperUTF8);
    result = await androidLoader.push("es", { test: "Prueba" });
    expect(result).toMatch(/^<\?xml version="1\.0" encoding="UTF-8"\?>/);

    // Test ISO-8859-1
    await androidLoader.pull("en", inputISO);
    result = await androidLoader.push("es", { test: "Prueba" });
    expect(result).toMatch(/^<\?xml version="1\.0" encoding="ISO-8859-1"\?>/);
  });

  it("should preserve XML version from source file", async () => {
    const inputV10 = `<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="test">Test</string>
</resources>`;

    const inputV11 = `<?xml version="1.1" encoding="utf-8"?>
<resources>
    <string name="test">Test</string>
</resources>`;

    const androidLoader = createAndroidLoader().setDefaultLocale("en");

    // Test version 1.0
    await androidLoader.pull("en", inputV10);
    let result = await androidLoader.push("es", { test: "Prueba" });
    expect(result).toMatch(/^<\?xml version="1\.0"/);

    // Test version 1.1
    await androidLoader.pull("en", inputV11);
    result = await androidLoader.push("es", { test: "Prueba" });
    expect(result).toMatch(/^<\?xml version="1\.1"/);
  });

  it("should omit XML declaration when source has none", async () => {
    const inputNoDeclaration = `<resources>
    <string name="test">Test</string>
</resources>`;

    const androidLoader = createAndroidLoader().setDefaultLocale("en");
    await androidLoader.pull("en", inputNoDeclaration);

    const result = await androidLoader.push("es", { test: "Prueba" });

    // Should start immediately with the root element (no declaration)
    expect(result).not.toMatch(/^<\?xml/);
    expect(result.trim().startsWith("<resources>")).toBe(true);
  });
});

```

--------------------------------------------------------------------------------
/legacy/cli/CHANGELOG.md:
--------------------------------------------------------------------------------

```markdown
# replexica

## 0.71.0

### Minor Changes

- [#428](https://github.com/lingodotdev/lingo.dev/pull/428) [`5dd7b65`](https://github.com/lingodotdev/lingo.dev/commit/5dd7b6529ce174d8759e80644c3233927b1ecce4) Thanks [@mathio](https://github.com/mathio)! - map old env vars

### Patch Changes

- Updated dependencies [[`cd836e4`](https://github.com/lingodotdev/lingo.dev/commit/cd836e45cf810f495e2c6e1449824dc84794d571), [`5dd7b65`](https://github.com/lingodotdev/lingo.dev/commit/5dd7b6529ce174d8759e80644c3233927b1ecce4)]:
  - [email protected]

## 0.70.1

### 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.70.0

### Minor Changes

- [`003344f`](https://github.com/lingodotdev/lingo.dev/commit/003344ffcca98a391a298707f18476971c4d4c2b) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - add locale delimiter override

### Patch Changes

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

## 0.69.0

### Minor Changes

- [#411](https://github.com/lingodotdev/lingo.dev/pull/411) [`1b0647d`](https://github.com/lingodotdev/lingo.dev/commit/1b0647d91080f4947ba6227c397fb6232d0d1907) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - add structure sync loader to cli

### Patch Changes

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

## 0.68.0

### Minor Changes

- [#408](https://github.com/lingodotdev/lingo.dev/pull/408) [`36fd4af`](https://github.com/lingodotdev/lingo.dev/commit/36fd4af376caf1540dc0a594fd65742c81706aa0) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - disable .po folding

### Patch Changes

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

## 0.67.0

### Minor Changes

- [#405](https://github.com/lingodotdev/lingo.dev/pull/405) [`446cf9c`](https://github.com/lingodotdev/lingo.dev/commit/446cf9c5c933f71a43fd5d80487b1608023cba8e) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - improved .po loader

- [#404](https://github.com/lingodotdev/lingo.dev/pull/404) [`3edef26`](https://github.com/lingodotdev/lingo.dev/commit/3edef26ef3a4e2d27394c5eeb2bc94b164e037ac) Thanks [@mathio](https://github.com/mathio)! - interactive init comman

### Patch Changes

- Updated dependencies [[`446cf9c`](https://github.com/lingodotdev/lingo.dev/commit/446cf9c5c933f71a43fd5d80487b1608023cba8e), [`3edef26`](https://github.com/lingodotdev/lingo.dev/commit/3edef26ef3a4e2d27394c5eeb2bc94b164e037ac)]:
  - @replexica/[email protected]

## 0.66.2

### Patch Changes

- [#399](https://github.com/lingodotdev/lingo.dev/pull/399) [`01ca7bb`](https://github.com/lingodotdev/lingo.dev/commit/01ca7bb9d28d0de903caf44ec6ede2e2bbbb3ba2) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - feat(cli): enhance .po loader to support plural translations and improve loader composition

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

## 0.66.1

### Patch Changes

- [`aef36b5`](https://github.com/lingodotdev/lingo.dev/commit/aef36b53163efa523f7632786e0f293890f05b23) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - improve .po handling

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

## 0.66.0

### Minor Changes

- [`e885fcf`](https://github.com/lingodotdev/lingo.dev/commit/e885fcf8731d9f2a250cf44a534c5556a057ca51) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - single quotes escaping

### Patch Changes

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

## 0.65.1

### Patch Changes

- [#390](https://github.com/lingodotdev/lingo.dev/pull/390) [`a2ada16`](https://github.com/lingodotdev/lingo.dev/commit/a2ada16ecf4cd559d3486f0e4756d58808194f7e) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - add ieee variables support

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

## 0.65.0

### Minor Changes

- [`bd577f2`](https://github.com/lingodotdev/lingo.dev/commit/bd577f22da52e7e889bb4b419cb5dab9865512f1) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - remove unlocalizable from dato

### Patch Changes

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

## 0.64.0

### Minor Changes

- [#387](https://github.com/lingodotdev/lingo.dev/pull/387) [`8db4527`](https://github.com/lingodotdev/lingo.dev/commit/8db4527d9c3501d97f8bb7b414dd61e8a3ee80f6) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - add support for blocks / array of blocks / nested blocks

### Patch Changes

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

## 0.63.1

### Patch Changes

- [#382](https://github.com/lingodotdev/lingo.dev/pull/382) [`3320c8c`](https://github.com/lingodotdev/lingo.dev/commit/3320c8c6f9df9671e1002b63a00bf877270a6064) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - fix lockfile resetting when --key flag is applied

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

## 0.63.0

### Minor Changes

- [`db2e800`](https://github.com/lingodotdev/lingo.dev/commit/db2e80013e44b478331b6a97008b3e67bae82a1f) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - add --key flag for selective updates

### Patch Changes

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

## 0.62.0

### Minor Changes

- [`302afdf`](https://github.com/lingodotdev/lingo.dev/commit/302afdfd3047b781bd9688921eab3dc84173aa20) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - handle C specifiers in localizable content

### Patch Changes

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

## 0.61.0

### Minor Changes

- [`9d38df2`](https://github.com/lingodotdev/lingo.dev/commit/9d38df2fdbe23fdcbb1b7e2e207de650e714e433) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - fixed same-file locale rewrites

### Patch Changes

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

## 0.60.1

### Patch Changes

- [#372](https://github.com/lingodotdev/lingo.dev/pull/372) [`b9a8350`](https://github.com/lingodotdev/lingo.dev/commit/b9a83502803f4a62fc9a62b4348f853f2baff20d) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - fix single-file results overwriting

- [#371](https://github.com/lingodotdev/lingo.dev/pull/371) [`e6521b8`](https://github.com/lingodotdev/lingo.dev/commit/e6521b86637c254c011aba89a3558802c04ab3ca) Thanks [@mathio](https://github.com/mathio)! - support underscore in locale code

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

## 0.60.0

### Minor Changes

- [#356](https://github.com/lingodotdev/lingo.dev/pull/356) [`cff3c4e`](https://github.com/lingodotdev/lingo.dev/commit/cff3c4eb1a40f82a9c4c095e49cfd9fce053b048) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - add dato support

### Patch Changes

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

## 0.59.1

### Patch Changes

- Updated dependencies []:
  - @replexica/[email protected]
  - @replexica/[email protected]
  - @replexica/[email protected]

## 0.59.0

### Minor Changes

- [`63daf00`](https://github.com/lingodotdev/lingo.dev/commit/63daf00e80004775f12c9e1d426cdd2bbf10f5a4) Thanks [@vrcprl](https://github.com/vrcprl)! - noop

### Patch Changes

- [`6eb5282`](https://github.com/lingodotdev/lingo.dev/commit/6eb5282063515db93fc76ff3137422862720fa0d) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - noop

- Updated dependencies [[`63daf00`](https://github.com/lingodotdev/lingo.dev/commit/63daf00e80004775f12c9e1d426cdd2bbf10f5a4), [`3ab5de6`](https://github.com/lingodotdev/lingo.dev/commit/3ab5de66d8a913297b46095c2e73823124cc8c5b), [`3ab5de6`](https://github.com/lingodotdev/lingo.dev/commit/3ab5de66d8a913297b46095c2e73823124cc8c5b), [`6eb5282`](https://github.com/lingodotdev/lingo.dev/commit/6eb5282063515db93fc76ff3137422862720fa0d)]:
  - @replexica/[email protected]
  - @replexica/[email protected]
  - @replexica/[email protected]

## 0.58.2

### Patch Changes

- Updated dependencies []:
  - @replexica/[email protected]
  - @replexica/[email protected]
  - @replexica/[email protected]

## 0.58.1

### Patch Changes

- Updated dependencies []:
  - @replexica/[email protected]
  - @replexica/[email protected]

## 0.58.0

### Minor Changes

- [`ff0d2d7`](https://github.com/lingodotdev/lingo.dev/commit/ff0d2d7fb12806a7264a72c03e48a8dda3526c23) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - add retry with exponential backoff to the cli

### Patch Changes

- [`7ff7f8f`](https://github.com/lingodotdev/lingo.dev/commit/7ff7f8fca7318e4dba929194972d20ccf3487e9d) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - display number of entries in localization completion message

- Updated dependencies [[`7ff7f8f`](https://github.com/lingodotdev/lingo.dev/commit/7ff7f8fca7318e4dba929194972d20ccf3487e9d), [`ff0d2d7`](https://github.com/lingodotdev/lingo.dev/commit/ff0d2d7fb12806a7264a72c03e48a8dda3526c23)]:
  - @replexica/[email protected]

## 0.57.1

### Patch Changes

- Updated dependencies []:
  - @replexica/[email protected]
  - @replexica/[email protected]
  - @replexica/[email protected]

## 0.57.0

### Minor Changes

- [`8e2cee4`](https://github.com/lingodotdev/lingo.dev/commit/8e2cee4b282c39fef1e00fa429e03e1c1e489cc5) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - add `cleanup` command

### Patch Changes

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

- [`ca10072`](https://github.com/lingodotdev/lingo.dev/commit/ca10072f636d8bd1105ed0f6cc84cf0af5a12402) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - improve progress logging in cli

- Updated dependencies [[`2c5cbcf`](https://github.com/lingodotdev/lingo.dev/commit/2c5cbcfbf6feb28440255cdea0818c8cefa61d91), [`8e2cee4`](https://github.com/lingodotdev/lingo.dev/commit/8e2cee4b282c39fef1e00fa429e03e1c1e489cc5), [`ca10072`](https://github.com/lingodotdev/lingo.dev/commit/ca10072f636d8bd1105ed0f6cc84cf0af5a12402)]:
  - @replexica/[email protected]
  - @replexica/[email protected]

## 0.56.3

### Patch Changes

- [`b8ad864`](https://github.com/lingodotdev/lingo.dev/commit/b8ad8643347088635eeeb568f1818d71d5226269) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - feat(cli): disable safe mode at localizable chunk level

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

## 0.56.2

### Patch Changes

- Updated dependencies []:
  - @replexica/[email protected]
  - @replexica/[email protected]

## 0.56.1

### Patch Changes

- Updated dependencies []:
  - @replexica/[email protected]
  - @replexica/[email protected]

## 0.56.0

### Minor Changes

- [#298](https://github.com/lingodotdev/lingo.dev/pull/298) [`c03437d`](https://github.com/lingodotdev/lingo.dev/commit/c03437dc9cfd8183e40f74926b4ba7f0874ebf81) Thanks [@partik03](https://github.com/partik03)! - implemented xml loader

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

### Patch Changes

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

## 0.55.0

### Minor Changes

- [`57e395a`](https://github.com/lingodotdev/lingo.dev/commit/57e395aae8ab100ba470bc7d1104ddfa178249e7) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - add `--source` and `--target` flags to show files cmd

### Patch Changes

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

## 0.54.0

### Minor Changes

- [#301](https://github.com/lingodotdev/lingo.dev/pull/301) [`44b4cca`](https://github.com/lingodotdev/lingo.dev/commit/44b4cca2718bd72d55a938bac458d32a4536508a) Thanks [@partik03](https://github.com/partik03)! - --frozen flag

- [`4fc27da`](https://github.com/lingodotdev/lingo.dev/commit/4fc27daae5810f6167726a28d76a874fd8421a5b) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - replexica show files now shows both source and target paths

### Patch Changes

- Updated dependencies [[`44b4cca`](https://github.com/lingodotdev/lingo.dev/commit/44b4cca2718bd72d55a938bac458d32a4536508a), [`4fc27da`](https://github.com/lingodotdev/lingo.dev/commit/4fc27daae5810f6167726a28d76a874fd8421a5b)]:
  - @replexica/[email protected]

## 0.53.1

### Patch Changes

- [`44b5c5c`](https://github.com/lingodotdev/lingo.dev/commit/44b5c5c498ca8df3bb814764f40057576c28c941) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - downgrade glob to @10, to allow node 18

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

## 0.53.0

### Minor Changes

- [`072e23e`](https://github.com/lingodotdev/lingo.dev/commit/072e23e58fca0da20bfd01f6a0ae600e6fb760a8) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - hide process summary label when there's zero elements to show

### Patch Changes

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

## 0.51.2

### Patch Changes

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

## 0.51.1

### Patch Changes

- [`e511b50`](https://github.com/lingodotdev/lingo.dev/commit/e511b5080dba58728e8650c7bf34d810cccdcf4e) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - added node@18 support

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

## 0.51.0

### Minor Changes

- [#275](https://github.com/lingodotdev/lingo.dev/pull/275) [`091ee35`](https://github.com/lingodotdev/lingo.dev/commit/091ee353081795bf8f61c7d41483bc309c7b62ef) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - add support for `.po` format

### Patch Changes

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

## 0.50.0

### Minor Changes

- [#268](https://github.com/lingodotdev/lingo.dev/pull/268) [`5e282d7`](https://github.com/lingodotdev/lingo.dev/commit/5e282d7ffa5ca9494aa7046a090bb7c327085a86) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - composable loaders

### Patch Changes

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

## 0.49.1

### Patch Changes

- [`64cd6f3`](https://github.com/lingodotdev/lingo.dev/commit/64cd6f3765bb4524e9f78f93ff283e833a6f26a2) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - fixed path patter relativity

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

## 0.49.0

### Minor Changes

- [`0071cd6`](https://github.com/lingodotdev/lingo.dev/commit/0071cd66b1c868ad3898fc368390a628c5a67767) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - add csv format support

### Patch Changes

- [`1cc0796`](https://github.com/lingodotdev/lingo.dev/commit/1cc07961d221e397ad5dd2917bed76cb4f2b1f04) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - add path.resolve to text loaders

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

## 0.48.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`

### Patch Changes

- [#261](https://github.com/lingodotdev/lingo.dev/pull/261) [`62c464d`](https://github.com/lingodotdev/lingo.dev/commit/62c464d5602909f8f6370dfa5009131a4d6719d0) Thanks [@Nithishvb](https://github.com/Nithishvb)! - This pr introduces a custom error handling base class for the CLI

- Updated dependencies [[`cdef5b7`](https://github.com/lingodotdev/lingo.dev/commit/cdef5b7bfbee4670c6de62cf4b4f3e0315748e25), [`62c464d`](https://github.com/lingodotdev/lingo.dev/commit/62c464d5602909f8f6370dfa5009131a4d6719d0)]:
  - @replexica/[email protected]
  - @replexica/[email protected]

## 0.47.1

### Patch Changes

- [`2859938`](https://github.com/lingodotdev/lingo.dev/commit/28599388a91bf80cea3813bb4b8999bb4df302c9) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - add missing locales

- Updated dependencies []:
  - @replexica/[email protected]
  - @replexica/[email protected]
  - @replexica/[email protected]

## 0.47.0

### Minor Changes

- [`4dfc8d8`](https://github.com/lingodotdev/lingo.dev/commit/4dfc8d8b301a690875401af5d107a88f1716182a) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - added support for android format

- [`ca9e20e`](https://github.com/lingodotdev/lingo.dev/commit/ca9e20eef9047e20d39ccf9dff74d2f6069d4676) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - .strings support

- [`2aedf3b`](https://github.com/lingodotdev/lingo.dev/commit/2aedf3bec2d9dffc7b43fc10dea0cab5742d44af) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - added support for .stringsdict

- [#245](https://github.com/lingodotdev/lingo.dev/pull/245) [`3fc9da7`](https://github.com/lingodotdev/lingo.dev/commit/3fc9da7e3d2ec58e7f278c79a53eae6d3dfa5896) Thanks [@Nithishvb](https://github.com/Nithishvb)! - prevented overwritting of i18n.json with a default template for unsupported locales

- [`626082a`](https://github.com/lingodotdev/lingo.dev/commit/626082a64b88fb3b589acd950afeafe417ce5ddc) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - added Flutter .arb support

### Patch Changes

- [`2b5e3ae`](https://github.com/lingodotdev/lingo.dev/commit/2b5e3aea3f0745955266f6edf2ce34830242e503) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - fixed yaml-root-key loader

- [`747847a`](https://github.com/lingodotdev/lingo.dev/commit/747847a86720d4c36f15daeb41d13d0aff129ca9) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - fixed .xcstrings plurals

- Updated dependencies [[`2b5e3ae`](https://github.com/lingodotdev/lingo.dev/commit/2b5e3aea3f0745955266f6edf2ce34830242e503), [`4dfc8d8`](https://github.com/lingodotdev/lingo.dev/commit/4dfc8d8b301a690875401af5d107a88f1716182a), [`ca9e20e`](https://github.com/lingodotdev/lingo.dev/commit/ca9e20eef9047e20d39ccf9dff74d2f6069d4676), [`2aedf3b`](https://github.com/lingodotdev/lingo.dev/commit/2aedf3bec2d9dffc7b43fc10dea0cab5742d44af), [`747847a`](https://github.com/lingodotdev/lingo.dev/commit/747847a86720d4c36f15daeb41d13d0aff129ca9), [`3fc9da7`](https://github.com/lingodotdev/lingo.dev/commit/3fc9da7e3d2ec58e7f278c79a53eae6d3dfa5896), [`626082a`](https://github.com/lingodotdev/lingo.dev/commit/626082a64b88fb3b589acd950afeafe417ce5ddc)]:
  - @replexica/[email protected]
  - @replexica/[email protected]
  - @replexica/[email protected]

## 0.46.0

### Minor Changes

- [`8887ece`](https://github.com/lingodotdev/lingo.dev/commit/8887ece066eccb8da31d42b30a76b005de2219a8) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - add node 18 compatibility

### Patch Changes

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

## 0.45.0

### Minor Changes

- [`ad78fb2`](https://github.com/lingodotdev/lingo.dev/commit/ad78fb231d4044d09280127ad8d7c7f7141afe1b) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - remove waitlist

### Patch Changes

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

## 0.44.3

### Patch Changes

- [`1e4cbd9`](https://github.com/lingodotdev/lingo.dev/commit/1e4cbd9670ea330c6938efdda3a965ac1e3e8376) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - add support for symlinks in i18n.json

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

## 0.44.2

### Patch Changes

- [#224](https://github.com/lingodotdev/lingo.dev/pull/224) [`2d019f1`](https://github.com/lingodotdev/lingo.dev/commit/2d019f153bd8cc928c2065c9e0260e9de0a6885c) Thanks [@Absterrg0](https://github.com/Absterrg0)! - Added 2 new github issue forms

- [#228](https://github.com/lingodotdev/lingo.dev/pull/228) [`38fab73`](https://github.com/lingodotdev/lingo.dev/commit/38fab73377278124dfc85a847326fdc957261c6e) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - avoid stringifying frontmatter dates

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

## 0.44.1

### Patch Changes

- [`4760f61`](https://github.com/lingodotdev/lingo.dev/commit/4760f617ef5cca7bed742e4fac28044721d33fc1) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - update cli messages

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

## 0.44.0

### Minor Changes

- [#220](https://github.com/lingodotdev/lingo.dev/pull/220) [`1b11f8e`](https://github.com/lingodotdev/lingo.dev/commit/1b11f8e710d140045be0c4385bad6348f21f4e5c) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - add `replexica show files` command

### Patch Changes

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

## 0.43.0

### Minor Changes

- [`fe09f8b`](https://github.com/lingodotdev/lingo.dev/commit/fe09f8b68b1583ba9be83722beceb1596970809f) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - add --api-key to the i18n cmd

### Patch Changes

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

## 0.42.0

### Minor Changes

- [`7c67fc5`](https://github.com/lingodotdev/lingo.dev/commit/7c67fc5d87d66abbf0a174417b938810a112cc1a) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - migrate to the new markdown parser

### Patch Changes

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

## 0.41.3

### Patch Changes

- [#204](https://github.com/lingodotdev/lingo.dev/pull/204) [`99a4d0a`](https://github.com/lingodotdev/lingo.dev/commit/99a4d0a926d6b6ec0821b47e34f337ca5bb05fca) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - add additional exception throws

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

## 0.41.2

### Patch Changes

- [`962ec5e`](https://github.com/lingodotdev/lingo.dev/commit/962ec5e619632d020ff60fb562d3ad7bc8900443) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - avoid rewriting i18n.json when there's no changes

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

## 0.41.1

### Patch Changes

- [`6fdc5d5`](https://github.com/lingodotdev/lingo.dev/commit/6fdc5d535a077bb0656d37c5edf3423dd32e6412) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - Add json repair to json file loader

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

## 0.41.0

### Minor Changes

- [#181](https://github.com/lingodotdev/lingo.dev/pull/181) [`1601f70`](https://github.com/lingodotdev/lingo.dev/commit/1601f708bdf0ff1786d3bf9b19265ac5b567f740) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - Added support for .properties file

### Patch Changes

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

## 0.40.1

### Patch Changes

- [`bc5a28c`](https://github.com/lingodotdev/lingo.dev/commit/bc5a28c3c98b619872924b5f913229ac01387524) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - Fix spec imports

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

## 0.40.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

- [#166](https://github.com/lingodotdev/lingo.dev/pull/166) [`78c4ce4`](https://github.com/lingodotdev/lingo.dev/commit/78c4ce479149d3eeb2f67f9283de54eecf3c35ab) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - Add lockfile autogeneration

### Patch Changes

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

## 0.39.1

### Patch Changes

- [#162](https://github.com/lingodotdev/lingo.dev/pull/162) [`c990101`](https://github.com/lingodotdev/lingo.dev/commit/c990101185aa17b036fa5a21db679fc7781bf551) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - Add replexica lockfile command for explicit lockfile generation

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

## 0.39.0

### Minor Changes

- [`6870fc7`](https://github.com/lingodotdev/lingo.dev/commit/6870fc758dae9d1adb641576befbd8cda61cd5ea) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - Fix version number bumping in 1.2 config autoupgrade

### Patch Changes

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

## 0.38.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]
  - @replexica/[email protected]
  - @replexica/[email protected]

## 0.37.0

### Minor Changes

- [#158](https://github.com/lingodotdev/lingo.dev/pull/158) [`73c9250`](https://github.com/lingodotdev/lingo.dev/commit/73c925084989ccea120cae1617ec87776c88e83e) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - Configuration spec v1.1: Improved bucket config structure, to support exclusion patterns

### Patch Changes

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

## 0.36.2

### Patch Changes

- [#156](https://github.com/lingodotdev/lingo.dev/pull/156) [`f59380f`](https://github.com/lingodotdev/lingo.dev/commit/f59380f85c98fae4dfb938f842bdf39fe795ddcd) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - Preserve order of keys in JSONs

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

## 0.36.1

### Patch Changes

- [`5ad1879`](https://github.com/lingodotdev/lingo.dev/commit/5ad18797f22bc06fe38769120c27bd7c4642fe2d) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - Add ascii art

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

## 0.36.0

### Minor Changes

- [#148](https://github.com/lingodotdev/lingo.dev/pull/148) [`fca3bd9`](https://github.com/lingodotdev/lingo.dev/commit/fca3bd984e5bef20a4a9921d7562980a3401f131) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - Add basic glob pattern support for multi-file buckets

### Patch Changes

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

## 0.35.0

### Minor Changes

- [`d293f05`](https://github.com/lingodotdev/lingo.dev/commit/d293f059e1bd9131d6d41ceffc713efa8d6fa598) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - New feature: remove unused keys, whenever a key gets deleted in the source file (thanks @quentin-decre!)

### Patch Changes

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

## 0.34.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

### Patch Changes

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

## 0.33.0

### Minor Changes

- [#138](https://github.com/lingodotdev/lingo.dev/pull/138) [`8948266`](https://github.com/lingodotdev/lingo.dev/commit/8948266b0f026da9f656c916bedcedc72e5aedba) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - Added JSON flat/unflat for more granular control over lockfile caching and performance

### Patch Changes

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

## 0.32.0

### Minor Changes

- [`dab6f68`](https://github.com/lingodotdev/lingo.dev/commit/dab6f68b4e564f4f1a757431b5a590f87e30aeca) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - Add frontmatter support

### Patch Changes

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

## 0.31.1

### Patch Changes

- [`387b6b7`](https://github.com/lingodotdev/lingo.dev/commit/387b6b74c1718503f50f18991b0337ee87cb53f8) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - Fixed extra newline added to markdown results

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

## 0.31.0

### Minor Changes

- [`8c8e7dd`](https://github.com/lingodotdev/lingo.dev/commit/8c8e7dd4d35669d484240d643427612ecdaf73eb) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - Added new locales

### Patch Changes

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

## 0.30.0

### Minor Changes

- [`bd2029d`](https://github.com/lingodotdev/lingo.dev/commit/bd2029d5c1241f7355ea08621dbeb7e04b7f5b5c) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - Updated markdown processor algo

### Patch Changes

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

## 0.29.0

### Minor Changes

- [`7d83cfc`](https://github.com/lingodotdev/lingo.dev/commit/7d83cfc79921346a47ccef43accee454ba80c83c) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - Added retry mechanism to i18n engine calls

### Patch Changes

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

## 0.24.0

### Minor Changes

- [`37167d6`](https://github.com/lingodotdev/lingo.dev/commit/37167d6d29d747b0dd35e26e5b6f0978f0e156d9) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - Added -v, --version flag to print out CLI version

### Patch Changes

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

## 0.23.7

### Patch Changes

- Updated dependencies [[`c0be1a2`](https://github.com/lingodotdev/lingo.dev/commit/c0be1a29e3069ef2c8bdc4e4f52d2fb17abdb1f5), [`a083a55`](https://github.com/lingodotdev/lingo.dev/commit/a083a551cbe755c87a78ad14673f5dbac6d86832)]:
  - @replexica/[email protected]
  - @replexica/[email protected]
  - @replexica/[email protected]

## 0.23.6

### Patch Changes

- [`eee21e1`](https://github.com/lingodotdev/lingo.dev/commit/eee21e1913e86f18938f1d6fd0dffaf6c17fb33c) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - Improved markdown performance, added support for VERY large markdown content files.

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

## 0.23.5

### Patch Changes

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

## 0.23.4

### Patch Changes

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

## 0.23.3

### Patch Changes

- Updated dependencies [[`fbce978`](https://github.com/lingodotdev/lingo.dev/commit/fbce97846eabf00fb1c903b82e7d556480de5d23), [`10252ce`](https://github.com/lingodotdev/lingo.dev/commit/10252ceaa2685cc23f4dbeb6ac985cc2148853e2)]:
  - @replexica/[email protected]
  - @replexica/[email protected]
  - @replexica/[email protected]

## 0.23.2

### Patch Changes

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

## 0.23.1

### Patch Changes

- [`088de18`](https://github.com/lingodotdev/lingo.dev/commit/088de18a53f45fa8df5833fe81ed96a2ed231299) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - Fix @replexica/config reference

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

## 0.23.0

### Minor Changes

- [#99](https://github.com/lingodotdev/lingo.dev/pull/99) [`4e94058`](https://github.com/lingodotdev/lingo.dev/commit/4e940582ea8ebe5a058b76fb33420729f7bfdcef) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - Added support for i18n lockfiles to improve AI localization performance.

### Patch Changes

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

```
Page 13/16FirstPrevNextLast