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

# Directory Structure

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

# Files

--------------------------------------------------------------------------------
/packages/react/src/client/locale-switcher.tsx:
--------------------------------------------------------------------------------

```typescript
 1 | "use client";
 2 | 
 3 | import { useState, useEffect } from "react";
 4 | import { getLocaleFromCookies, setLocaleInCookies } from "./utils";
 5 | 
 6 | /**
 7 |  * The props for the `LocaleSwitcher` component.
 8 |  */
 9 | export type LocaleSwitcherProps = {
10 |   /**
11 |    * An array of locale codes to display in the dropdown.
12 |    *
13 |    * This should contain both the source and target locales.
14 |    */
15 |   locales: string[];
16 |   /**
17 |    * A custom class name for the dropddown's `select` element.
18 |    */
19 |   className?: string;
20 | };
21 | 
22 | /**
23 |  * An unstyled dropdown for switching between locales.
24 |  *
25 |  * This component:
26 |  *
27 |  * - Only works in environments that support cookies
28 |  * - Gets and sets the current locale from the `"lingo-locale"` cookie
29 |  * - Triggers a full page reload when the locale is changed
30 |  *
31 |  * @example Creating a locale switcher
32 |  * ```tsx
33 |  * import { LocaleSwitcher } from "lingo.dev/react/client";
34 |  *
35 |  * export function App() {
36 |  *   return (
37 |  *     <header>
38 |  *       <nav>
39 |  *         <LocaleSwitcher locales={["en", "es"]} />
40 |  *       </nav>
41 |  *     </header>
42 |  *   );
43 |  * }
44 |  * ```
45 |  */
46 | export function LocaleSwitcher(props: LocaleSwitcherProps) {
47 |   const { locales } = props;
48 |   const [locale, setLocale] = useState<string | undefined>(undefined);
49 | 
50 |   useEffect(() => {
51 |     const currentLocale = getLocaleFromCookies();
52 |     const isValidLocale = currentLocale && locales.includes(currentLocale);
53 |     setLocale(isValidLocale ? currentLocale : locales[0]);
54 |   }, [locales]);
55 | 
56 |   if (locale === undefined) {
57 |     return null;
58 |   }
59 | 
60 |   return (
61 |     <select
62 |       value={locale}
63 |       className={props.className}
64 |       onChange={(e) => {
65 |         handleLocaleChange(e.target.value);
66 |       }}
67 |     >
68 |       {locales.map((locale) => (
69 |         <option key={locale} value={locale}>
70 |           {locale}
71 |         </option>
72 |       ))}
73 |     </select>
74 |   );
75 | 
76 |   function handleLocaleChange(newLocale: string): Promise<void> {
77 |     setLocaleInCookies(newLocale);
78 |     window.location.reload();
79 |     return Promise.resolve();
80 |   }
81 | }
82 | 
```

--------------------------------------------------------------------------------
/mcp.md:
--------------------------------------------------------------------------------

```markdown
 1 | # Model Context Protocol
 2 | 
 3 | The [Model Context Protocol](https://modelcontextprotocol.io/introduction) (MCP) is a standard for connecting Large Language Models (LLMs) to external services. This guide will walk you through how to connect AI tools to Lingo.dev using MCP.
 4 | 
 5 | Some of the AI tools that support MCP are:
 6 | 
 7 | - [Cursor](https://www.cursor.com/)
 8 | - [Claude desktop](https://claude.ai/download)
 9 | - [Cline for VS Code](https://github.com/cline/cline)
10 | 
11 | Connecting these tools to Lingo.dev will allow you to translate apps, websites, and other data using the best LLM models directly in your AI tool.
12 | 
13 | ## Setup
14 | 
15 | Add this command to your AI tool:
16 | 
17 | ```bash
18 | npx -y lingo.dev mcp <api-key>
19 | ```
20 | 
21 | You can find your API key in [Lingo.dev app](https://lingo.dev/app/), in your project settings.
22 | 
23 | This will allow the tool to use `translate` tool provided by Lingo.dev. The setup depends on your AI tool and might be different for each tool. Here is setup for some of the tools we use in our team:
24 | 
25 | ### Cursor
26 | 
27 | 1. Open Cursor and go to Cursor Settings.
28 | 2. Open MCP tab
29 | 3. Click `+ Add new MCP server`
30 | 4. Enter the following details:
31 |    - Name: Lingo.dev
32 |    - Type: command
33 |    - Command: `npx -y lingo.dev mcp <api-key>` (use your project API key)
34 | 5. You will see green status indicator and "translate" tool available in the list
35 | 
36 | ### Claude desktop
37 | 
38 | 1. Open Claude desktop and go to Settings.
39 | 2. Open Developer tab
40 | 3. Click `Edit Config` to see configuration file in file explorer.
41 | 4. Open the file in text editor
42 | 5. Add the following configuration (use your project API key):
43 | 
44 | ```json
45 | {
46 |   "mcpServers": {
47 |     "supabase": {
48 |       "command": "npx",
49 |       "args": ["-y", "lingo.dev", "mcp", "<api-key>"]
50 |     }
51 |   }
52 | }
53 | ```
54 | 
55 | 6. Save the configuration file
56 | 7. Restart Claude desktop.
57 | 8. In the chat input, you will see a hammer icon with your MCP server details.
58 | 
59 | ## Usage
60 | 
61 | You are now able to access Lingo.dev via MCP. You can ask AI tool translate any content via our service.
62 | 
```

--------------------------------------------------------------------------------
/packages/cli/src/cli/loaders/text-file.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import fs from "fs/promises";
 2 | import path from "path";
 3 | import { ILoader } from "./_types";
 4 | import { createLoader } from "./_utils";
 5 | 
 6 | export default function createTextFileLoader(
 7 |   pathPattern: string,
 8 | ): ILoader<void, string> {
 9 |   return createLoader({
10 |     async pull(locale) {
11 |       const result = await readFileForLocale(pathPattern, locale);
12 |       const trimmedResult = result.trim();
13 |       return trimmedResult;
14 |     },
15 |     async push(locale, data, _, originalLocale) {
16 |       const draftPath = pathPattern.replaceAll("[locale]", locale);
17 |       const finalPath = path.resolve(draftPath);
18 | 
19 |       // Create parent directories if needed
20 |       const dirPath = path.dirname(finalPath);
21 |       await fs.mkdir(dirPath, { recursive: true });
22 | 
23 |       const trimmedPayload = data.trim();
24 | 
25 |       // Add trailing new line if needed
26 |       const trailingNewLine = await getTrailingNewLine(
27 |         pathPattern,
28 |         locale,
29 |         originalLocale,
30 |       );
31 |       let finalPayload = trimmedPayload + trailingNewLine;
32 | 
33 |       await fs.writeFile(finalPath, finalPayload, {
34 |         encoding: "utf-8",
35 |         flag: "w",
36 |       });
37 |     },
38 |   });
39 | }
40 | 
41 | async function readFileForLocale(pathPattern: string, locale: string) {
42 |   const draftPath = pathPattern.replaceAll("[locale]", locale);
43 |   const finalPath = path.resolve(draftPath);
44 |   const exists = await fs
45 |     .access(finalPath)
46 |     .then(() => true)
47 |     .catch(() => false);
48 |   if (!exists) {
49 |     return "";
50 |   }
51 |   return fs.readFile(finalPath, "utf-8");
52 | }
53 | 
54 | async function getTrailingNewLine(
55 |   pathPattern: string,
56 |   locale: string,
57 |   originalLocale: string,
58 | ) {
59 |   let templateData = await readFileForLocale(pathPattern, locale);
60 |   if (!templateData) {
61 |     templateData = await readFileForLocale(pathPattern, originalLocale);
62 |   }
63 | 
64 |   if (templateData?.match(/[\r\n]$/)) {
65 |     const ending = templateData?.includes("\r\n")
66 |       ? "\r\n"
67 |       : templateData?.includes("\r")
68 |         ? "\r"
69 |         : "\n";
70 |     return ending;
71 |   }
72 |   return "";
73 | }
74 | 
```

--------------------------------------------------------------------------------
/packages/react/src/rsc/utils.spec.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { describe, it, expect, vi, beforeEach } from "vitest";
 2 | 
 3 | const cookiesSetSpy = vi.fn();
 4 | vi.mock("next/headers", () => {
 5 |   return {
 6 |     headers: vi.fn(async () => new Map([["x-lingo-locale", "it"]])),
 7 |     cookies: vi.fn(async () => ({
 8 |       get: (name: string) =>
 9 |         name === "lingo-locale" ? { value: "pt" } : undefined,
10 |       set: cookiesSetSpy,
11 |     })),
12 |   };
13 | });
14 | 
15 | import { headers, cookies } from "next/headers";
16 | import {
17 |   loadLocaleFromHeaders,
18 |   loadLocaleFromCookies,
19 |   setLocaleInCookies,
20 |   loadDictionaryFromRequest,
21 | } from "./utils";
22 | 
23 | describe("rsc/utils", () => {
24 |   beforeEach(() => {
25 |     vi.clearAllMocks();
26 |   });
27 | 
28 |   describe("loadLocaleFromHeaders", () => {
29 |     it("reads x-lingo-locale header", async () => {
30 |       const value = await loadLocaleFromHeaders();
31 |       expect(value).toBe("it");
32 |       expect(headers).toHaveBeenCalled();
33 |     });
34 |   });
35 | 
36 |   describe("loadLocaleFromCookies", () => {
37 |     it("reads cookie and defaults to 'en' when missing", async () => {
38 |       const value = await loadLocaleFromCookies();
39 |       expect(value).toBe("pt");
40 |     });
41 | 
42 |     it("defaults to 'en' when cookie is missing", async () => {
43 |       (cookies as any).mockResolvedValueOnce({ get: () => undefined });
44 |       const value = await loadLocaleFromCookies();
45 |       expect(value).toBe("en");
46 |     });
47 |   });
48 | 
49 |   describe("setLocaleInCookies", () => {
50 |     it("writes cookie via next/headers cookies API", async () => {
51 |       await setLocaleInCookies("de");
52 |       expect(cookiesSetSpy).toHaveBeenCalledWith("lingo-locale", "de");
53 |     });
54 |   });
55 | 
56 |   describe("loadDictionaryFromRequest", () => {
57 |     it("uses cookie locale to call loader", async () => {
58 |       const loader = vi.fn(async (locale: string) => ({ locale }));
59 |       (cookies as any).mockResolvedValueOnce({ get: () => ({ value: "ru" }) });
60 |       const dict = await loadDictionaryFromRequest(loader);
61 |       expect(loader).toHaveBeenCalledWith("ru");
62 |       expect(dict).toEqual({ locale: "ru" });
63 |     });
64 |   });
65 | });
66 | 
```

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

```typescript
 1 | import { NodePath } from "@babel/traverse";
 2 | import * as t from "@babel/types";
 3 | import traverse from "@babel/traverse";
 4 | import { getJsxElementName } from "./jsx-element";
 5 | 
 6 | export function collectJsxScopes(ast: t.Node) {
 7 |   const jsxScopes: NodePath<t.JSXElement>[] = [];
 8 | 
 9 |   traverse(ast, {
10 |     JSXElement: (path) => {
11 |       if (!hasJsxScopeAttribute(path)) return;
12 | 
13 |       path.skip();
14 |       jsxScopes.push(path);
15 |     },
16 |   });
17 | 
18 |   return jsxScopes;
19 | }
20 | 
21 | export function getJsxScopes(node: t.Node) {
22 |   const result: NodePath<t.JSXElement>[] = [];
23 | 
24 |   traverse(node, {
25 |     JSXElement(path) {
26 |       // Skip if the element is LingoProvider
27 |       if (getJsxElementName(path) === "LingoProvider") {
28 |         return;
29 |       }
30 |       // Check if element has any non-empty JSXText siblings
31 |       const hasNonEmptyTextSiblings = path
32 |         .getAllPrevSiblings()
33 |         .concat(path.getAllNextSiblings())
34 |         .some(
35 |           (sibling) =>
36 |             t.isJSXText(sibling.node) && sibling.node.value?.trim() !== "",
37 |         );
38 | 
39 |       if (hasNonEmptyTextSiblings) {
40 |         return;
41 |       }
42 | 
43 |       // Check if element has at least one non-empty JSXText DIRECT child
44 |       const hasNonEmptyTextChild = path
45 |         .get("children")
46 |         .some(
47 |           (child) => t.isJSXText(child.node) && child.node.value?.trim() !== "",
48 |         );
49 | 
50 |       if (hasNonEmptyTextChild) {
51 |         result.push(path);
52 |         path.skip(); // Skip traversing children since we found a scope
53 |       }
54 |     },
55 |   });
56 | 
57 |   return result;
58 | }
59 | 
60 | export function hasJsxScopeAttribute(path: NodePath<t.JSXElement>) {
61 |   return !!getJsxScopeAttribute(path);
62 | }
63 | 
64 | export function getJsxScopeAttribute(path: NodePath<t.JSXElement>) {
65 |   const attribute = path.node.openingElement.attributes.find(
66 |     (attr) =>
67 |       attr.type === "JSXAttribute" && attr.name.name === "data-jsx-scope",
68 |   );
69 |   return attribute &&
70 |     t.isJSXAttribute(attribute) &&
71 |     t.isStringLiteral(attribute.value)
72 |     ? attribute.value.value
73 |     : undefined;
74 | }
75 | 
```

--------------------------------------------------------------------------------
/packages/react/src/client/locale.spec.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { describe, it, expect, vi, beforeEach } from "vitest";
 2 | import { renderHook, act } from "@testing-library/react";
 3 | import { useLingoLocale, setLingoLocale } from "./locale";
 4 | 
 5 | // Mock the utils module
 6 | vi.mock("./utils", async (orig) => {
 7 |   const actual = await orig();
 8 |   return {
 9 |     ...(actual as any),
10 |     getLocaleFromCookies: vi.fn(() => "en"),
11 |     setLocaleInCookies: vi.fn(),
12 |   };
13 | });
14 | 
15 | import { getLocaleFromCookies, setLocaleInCookies } from "./utils";
16 | 
17 | // Mock window.location.reload
18 | const mockReload = vi.fn();
19 | Object.defineProperty(window, "location", {
20 |   value: { ...window.location, reload: mockReload },
21 |   writable: true,
22 | });
23 | 
24 | describe("useLingoLocale", () => {
25 |   beforeEach(() => {
26 |     vi.clearAllMocks();
27 |   });
28 | 
29 |   it("returns the locale from cookies", () => {
30 |     (getLocaleFromCookies as any).mockReturnValue("es");
31 |     const { result } = renderHook(() => useLingoLocale());
32 | 
33 |     expect(result.current).toBe("es");
34 |     expect(getLocaleFromCookies).toHaveBeenCalled();
35 |   });
36 | 
37 |   it("returns null when no locale is set", () => {
38 |     (getLocaleFromCookies as any).mockReturnValue(null);
39 |     const { result } = renderHook(() => useLingoLocale());
40 | 
41 |     expect(result.current).toBe(null);
42 |   });
43 | });
44 | 
45 | describe("setLingoLocale", () => {
46 |   beforeEach(() => {
47 |     vi.clearAllMocks();
48 |     mockReload.mockClear();
49 |   });
50 | 
51 |   it("sets locale in cookies and reloads page for valid locale", () => {
52 |     act(() => {
53 |       setLingoLocale("es");
54 |     });
55 | 
56 |     expect(setLocaleInCookies).toHaveBeenCalledWith("es");
57 |     expect(mockReload).toHaveBeenCalled();
58 |   });
59 | 
60 |   it("accepts various locales", () => {
61 |     const validLocales = [
62 |       "en",
63 |       "es",
64 |       "fr",
65 |       "de",
66 |       "en-US",
67 |       "es-ES",
68 |       "fr-CA",
69 |       "de-DE",
70 |     ];
71 | 
72 |     validLocales.forEach((locale) => {
73 |       expect(() => {
74 |         act(() => {
75 |           setLingoLocale(locale);
76 |         });
77 |       }).not.toThrow();
78 | 
79 |       expect(setLocaleInCookies).toHaveBeenCalledWith(locale);
80 |     });
81 |   });
82 | });
83 | 
```

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

```typescript
 1 | import { Command } from "interactive-commander";
 2 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
 3 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
 4 | import Z from "zod";
 5 | import { ReplexicaEngine } from "@lingo.dev/_sdk";
 6 | import { getSettings } from "../utils/settings";
 7 | import { createAuthenticator } from "../utils/auth";
 8 | 
 9 | export default new Command()
10 |   .command("mcp")
11 |   .description(
12 |     "Start a Model Context Protocol (MCP) server for AI assistant integration",
13 |   )
14 |   .helpOption("-h, --help", "Show help")
15 |   .action(async (_, program) => {
16 |     const apiKey = program.args[0];
17 |     const settings = getSettings(apiKey);
18 | 
19 |     if (!settings.auth.apiKey) {
20 |       console.error("No API key provided");
21 |       return;
22 |     }
23 | 
24 |     const authenticator = createAuthenticator({
25 |       apiUrl: settings.auth.apiUrl,
26 |       apiKey: settings.auth.apiKey!,
27 |     });
28 |     const auth = await authenticator.whoami();
29 | 
30 |     if (!auth) {
31 |       console.error("Not authenticated");
32 |       return;
33 |     } else {
34 |       console.log(`Authenticated as ${auth.email}`);
35 |     }
36 | 
37 |     const replexicaEngine = new ReplexicaEngine({
38 |       apiKey: settings.auth.apiKey,
39 |       apiUrl: settings.auth.apiUrl,
40 |     });
41 | 
42 |     const server = new McpServer({
43 |       name: "Lingo.dev",
44 |       version: "1.0.0",
45 |     });
46 | 
47 |     server.tool(
48 |       "translate",
49 |       "Detect language and translate text with Lingo.dev.",
50 |       {
51 |         text: Z.string(),
52 |         targetLocale: Z.string().regex(/^[a-z]{2}(-[A-Z]{2})?$/),
53 |       },
54 |       async ({ text, targetLocale }) => {
55 |         const sourceLocale = await replexicaEngine.recognizeLocale(text);
56 |         const data = await replexicaEngine.localizeText(text, {
57 |           sourceLocale,
58 |           targetLocale,
59 |         });
60 |         return { content: [{ type: "text", text: data }] };
61 |       },
62 |     );
63 | 
64 |     const transport = new StdioServerTransport();
65 |     await server.connect(transport);
66 |     console.log("Lingo.dev MCP Server running on stdio");
67 |   });
68 | 
```

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

```typescript
 1 | import dedent from "dedent";
 2 | import { ILocalizer, LocalizerData } from "./_types";
 3 | import chalk from "chalk";
 4 | import { colors } from "../constants";
 5 | import { LingoDotDevEngine } from "@lingo.dev/_sdk";
 6 | import { getSettings } from "../utils/settings";
 7 | 
 8 | export default function createLingoDotDevLocalizer(
 9 |   explicitApiKey?: string,
10 | ): ILocalizer {
11 |   const { auth } = getSettings(explicitApiKey);
12 | 
13 |   if (!auth) {
14 |     throw new Error(
15 |       dedent`
16 |         You're trying to use ${chalk.hex(colors.green)(
17 |           "Lingo.dev",
18 |         )} provider, however, you are not authenticated.
19 | 
20 |         To fix this issue:
21 |         1. Run ${chalk.dim("lingo.dev login")} to authenticate, or
22 |         2. Use the ${chalk.dim("--api-key")} flag to provide an API key.
23 |         3. Set ${chalk.dim("LINGODOTDEV_API_KEY")} environment variable.
24 |       `,
25 |     );
26 |   }
27 | 
28 |   const engine = new LingoDotDevEngine({
29 |     apiKey: auth.apiKey,
30 |     apiUrl: auth.apiUrl,
31 |   });
32 | 
33 |   return {
34 |     id: "Lingo.dev",
35 |     checkAuth: async () => {
36 |       try {
37 |         const response = await engine.whoami();
38 |         return {
39 |           authenticated: !!response,
40 |           username: response?.email,
41 |         };
42 |       } catch (error) {
43 |         const errorMessage =
44 |           error instanceof Error ? error.message : String(error);
45 |         return { authenticated: false, error: errorMessage };
46 |       }
47 |     },
48 |     localize: async (input: LocalizerData, onProgress) => {
49 |       // Nothing to translate – return the input as-is.
50 |       if (!Object.keys(input.processableData).length) {
51 |         return input;
52 |       }
53 | 
54 |       const processedData = await engine.localizeObject(
55 |         input.processableData,
56 |         {
57 |           sourceLocale: input.sourceLocale,
58 |           targetLocale: input.targetLocale,
59 |           reference: {
60 |             [input.sourceLocale]: input.sourceData,
61 |             [input.targetLocale]: input.targetData,
62 |           },
63 |           hints: input.hints,
64 |         },
65 |         onProgress,
66 |       );
67 | 
68 |       return processedData;
69 |     },
70 |   };
71 | }
72 | 
```

--------------------------------------------------------------------------------
/packages/compiler/src/utils/create-locale-import-map.spec.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { describe, it, expect } from "vitest";
 2 | import * as t from "@babel/types";
 3 | import { createLocaleImportMap } from "./create-locale-import-map";
 4 | 
 5 | describe("createLocaleImportMap", () => {
 6 |   const allLocales = ["en", "de", "en-US"];
 7 |   const dictionaryPath = "/foo/bar";
 8 | 
 9 |   const objExpr = createLocaleImportMap(allLocales, dictionaryPath);
10 | 
11 |   it("returns a Babel ObjectExpression", () => {
12 |     expect(t.isObjectExpression(objExpr)).toBe(true);
13 |   });
14 | 
15 |   it("creates one property per locale", () => {
16 |     expect(objExpr.properties.length).toBe(allLocales.length);
17 |   });
18 | 
19 |   it("uses string literal keys and import arrow functions correctly", () => {
20 |     for (const prop of objExpr.properties) {
21 |       // Ensure property is ObjectProperty
22 |       expect(t.isObjectProperty(prop)).toBe(true);
23 |       if (!t.isObjectProperty(prop)) continue;
24 | 
25 |       // Check the key is a string literal matching one of the locales
26 |       expect(t.isStringLiteral(prop.key)).toBe(true);
27 |       const keyLiteral = prop.key as t.StringLiteral;
28 |       expect(allLocales).toContain(keyLiteral.value);
29 | 
30 |       // Ensure value is an arrow function with no params
31 |       expect(t.isArrowFunctionExpression(prop.value)).toBe(true);
32 |       const arrowFn = prop.value as t.ArrowFunctionExpression;
33 |       expect(arrowFn.params.length).toBe(0);
34 | 
35 |       // The body should be a call expression to dynamic import
36 |       expect(t.isCallExpression(arrowFn.body)).toBe(true);
37 |       const callExpr = arrowFn.body as t.CallExpression;
38 | 
39 |       // Callee is identifier 'import'
40 |       expect(t.isIdentifier(callExpr.callee)).toBe(true);
41 |       if (t.isIdentifier(callExpr.callee)) {
42 |         expect(callExpr.callee.name).toBe("import");
43 |       }
44 | 
45 |       // Single argument: string literal with proper path
46 |       expect(callExpr.arguments.length).toBe(1);
47 |       const arg = callExpr.arguments[0];
48 |       expect(t.isStringLiteral(arg)).toBe(true);
49 |       if (t.isStringLiteral(arg)) {
50 |         expect(arg.value).toBe(`${dictionaryPath}?locale=${keyLiteral.value}`);
51 |       }
52 |     }
53 |   });
54 | });
55 | 
```

--------------------------------------------------------------------------------
/packages/compiler/src/jsx-scopes-export.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { getJsxAttributeValue } from "./utils";
 2 | import _ from "lodash";
 3 | import { getAstKey } from "./utils/ast-key";
 4 | import { LCP } from "./lib/lcp";
 5 | import { getJsxElementHash } from "./utils/hash";
 6 | import { getJsxAttributesMap } from "./utils/jsx-attribute";
 7 | import { extractJsxContent } from "./utils/jsx-content";
 8 | import { collectJsxScopes } from "./utils/jsx-scope";
 9 | import { CompilerPayload } from "./_base";
10 | 
11 | // Processes only JSX element scopes
12 | export function jsxScopesExportMutation(
13 |   payload: CompilerPayload,
14 | ): CompilerPayload {
15 |   const scopes = collectJsxScopes(payload.ast);
16 |   if (_.isEmpty(scopes)) {
17 |     return payload;
18 |   }
19 | 
20 |   const lcp = LCP.getInstance({
21 |     sourceRoot: payload.params.sourceRoot,
22 |     lingoDir: payload.params.lingoDir,
23 |   });
24 | 
25 |   for (const scope of scopes) {
26 |     const scopeKey = getAstKey(scope);
27 | 
28 |     lcp.resetScope(payload.relativeFilePath, scopeKey);
29 | 
30 |     lcp.setScopeType(payload.relativeFilePath, scopeKey, "element");
31 | 
32 |     const hash = getJsxElementHash(scope);
33 |     lcp.setScopeHash(payload.relativeFilePath, scopeKey, hash);
34 | 
35 |     const context = getJsxAttributeValue(scope, "data-lingo-context");
36 |     lcp.setScopeContext(
37 |       payload.relativeFilePath,
38 |       scopeKey,
39 |       String(context || ""),
40 |     );
41 | 
42 |     const skip = getJsxAttributeValue(scope, "data-lingo-skip");
43 |     lcp.setScopeSkip(
44 |       payload.relativeFilePath,
45 |       scopeKey,
46 |       Boolean(skip || false),
47 |     );
48 | 
49 |     const attributesMap = getJsxAttributesMap(scope);
50 |     const overrides = _.chain(attributesMap)
51 |       .entries()
52 |       .filter(([attributeKey]) =>
53 |         attributeKey.startsWith("data-lingo-override-"),
54 |       )
55 |       .map(([k, v]) => [k.split("data-lingo-override-")[1], v])
56 |       .filter(([k]) => !!k)
57 |       .filter(([, v]) => !!v)
58 |       .fromPairs()
59 |       .value();
60 |     lcp.setScopeOverrides(payload.relativeFilePath, scopeKey, overrides);
61 | 
62 |     const content = extractJsxContent(scope);
63 |     lcp.setScopeContent(payload.relativeFilePath, scopeKey, content);
64 |   }
65 | 
66 |   lcp.save();
67 | 
68 |   return payload;
69 | }
70 | 
```

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

```typescript
 1 | import * as t from "@babel/types";
 2 | import traverse, { NodePath } from "@babel/traverse";
 3 | 
 4 | export function findInvokations(
 5 |   ast: t.File,
 6 |   params: {
 7 |     moduleName: string[];
 8 |     functionName: string;
 9 |   },
10 | ) {
11 |   const result: t.CallExpression[] = [];
12 | 
13 |   traverse(ast, {
14 |     ImportDeclaration(path) {
15 |       if (!params.moduleName.includes(path.node.source.value)) return;
16 | 
17 |       const importNames = new Map<string, boolean | string>();
18 |       const specifiers = path.node.specifiers;
19 | 
20 |       specifiers.forEach((specifier) => {
21 |         if (
22 |           t.isImportSpecifier(specifier) &&
23 |           t.isIdentifier(specifier.imported) &&
24 |           specifier.imported.name === params.functionName
25 |         ) {
26 |           importNames.set(specifier.local.name, true);
27 |         } else if (
28 |           t.isImportDefaultSpecifier(specifier) &&
29 |           params.functionName === "default"
30 |         ) {
31 |           importNames.set(specifier.local.name, true);
32 |         } else if (t.isImportNamespaceSpecifier(specifier)) {
33 |           importNames.set(specifier.local.name, "namespace");
34 |         }
35 |       });
36 | 
37 |       collectCallExpressions(path, importNames, result, params.functionName);
38 |     },
39 |   });
40 | 
41 |   return result;
42 | }
43 | 
44 | function collectCallExpressions(
45 |   path: NodePath<t.ImportDeclaration>,
46 |   importNames: Map<string, boolean | string>,
47 |   result: t.CallExpression[],
48 |   functionName: string,
49 | ) {
50 |   const program = path.findParent((p): p is NodePath<t.Program> =>
51 |     p.isProgram(),
52 |   );
53 | 
54 |   if (!program) return;
55 | 
56 |   program.traverse({
57 |     CallExpression(callPath: NodePath<t.CallExpression>) {
58 |       const callee = callPath.node.callee;
59 | 
60 |       if (t.isIdentifier(callee) && importNames.has(callee.name)) {
61 |         result.push(callPath.node);
62 |       } else if (
63 |         t.isMemberExpression(callee) &&
64 |         t.isIdentifier(callee.object) &&
65 |         importNames.get(callee.object.name) === "namespace" &&
66 |         t.isIdentifier(callee.property) &&
67 |         callee.property.name === functionName
68 |       ) {
69 |         result.push(callPath.node);
70 |       }
71 |     },
72 |   });
73 | }
74 | 
```

--------------------------------------------------------------------------------
/integrations/directus/src/app.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { defineOperationApp } from "@directus/extensions-sdk";
 2 | 
 3 | export default defineOperationApp({
 4 |   id: "replexica-integration-directus",
 5 |   name: "Replexica Integration for Directus",
 6 |   icon: "translate",
 7 |   description:
 8 |     "Use Replexica Localization Engine to make content multilingual.",
 9 |   overview: ({ collection }) => [
10 |     {
11 |       label: "$t:collection",
12 |       text: collection,
13 |     },
14 |   ],
15 |   options: [
16 |     {
17 |       field: "item_id",
18 |       name: "Item ID",
19 |       type: "string",
20 |       meta: {
21 |         interface: "input",
22 |         width: "half",
23 |       },
24 |     },
25 |     {
26 |       field: "collection",
27 |       name: "$t:collection",
28 |       type: "string",
29 |       meta: {
30 |         interface: "system-collection",
31 |         options: {
32 |           includeSystem: true,
33 |           includeSingleton: false,
34 |         },
35 |         width: "half",
36 |       },
37 |     },
38 |     {
39 |       field: "source_language",
40 |       name: "Source Language",
41 |       type: "string",
42 |       meta: {
43 |         interface: "input",
44 |         width: "half",
45 |       },
46 |     },
47 |     {
48 |       field: "target_languages",
49 |       name: "Target Languages",
50 |       type: "string",
51 |       meta: {
52 |         interface: "input",
53 |         width: "half",
54 |       },
55 |     },
56 |     {
57 |       field: "translation_table",
58 |       name: "Translation Table",
59 |       type: "string",
60 |       meta: {
61 |         interface: "system-collection",
62 |         options: {
63 |           includeSystem: true,
64 |           includeSingleton: false,
65 |         },
66 |         width: "half",
67 |       },
68 |     },
69 |     {
70 |       field: "language_table",
71 |       name: "Languages Table",
72 |       type: "string",
73 |       meta: {
74 |         interface: "system-collection",
75 |         options: {
76 |           includeSystem: true,
77 |           includeSingleton: false,
78 |         },
79 |         width: "half",
80 |       },
81 |     },
82 |     {
83 |       field: "replexica_api_key",
84 |       name: "Replexica API Key",
85 |       type: "string",
86 |       meta: {
87 |         interface: "input-hash",
88 |         width: "half",
89 |         options: {
90 |           masked: true,
91 |           placeholder: "Enter your Replexica API key",
92 |         },
93 |       },
94 |     },
95 |   ],
96 | });
97 | 
```

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

```typescript
 1 | import { Command } from "interactive-commander";
 2 | import Z from "zod";
 3 | import Ora from "ora";
 4 | import { createLockfileHelper } from "../utils/lockfile";
 5 | import { bucketTypeSchema, resolveOverriddenLocale } from "@lingo.dev/_spec";
 6 | import { getConfig } from "../utils/config";
 7 | import createBucketLoader from "../loaders";
 8 | import { getBuckets } from "../utils/buckets";
 9 | 
10 | export default new Command()
11 |   .command("lockfile")
12 |   .description(
13 |     "Generate or refresh i18n.lock based on the current source locale content",
14 |   )
15 |   .helpOption("-h, --help", "Show help")
16 |   .option(
17 |     "-f, --force",
18 |     "Overwrite existing lockfile to reset translation tracking",
19 |   )
20 |   .action(async (options) => {
21 |     const flags = flagsSchema.parse(options);
22 |     const ora = Ora();
23 | 
24 |     const lockfileHelper = createLockfileHelper();
25 |     if (lockfileHelper.isLockfileExists() && !flags.force) {
26 |       ora.warn(
27 |         `Lockfile won't be created because it already exists. Use --force to overwrite.`,
28 |       );
29 |     } else {
30 |       const i18nConfig = getConfig();
31 |       const buckets = getBuckets(i18nConfig!);
32 | 
33 |       for (const bucket of buckets) {
34 |         for (const bucketConfig of bucket.paths) {
35 |           const sourceLocale = resolveOverriddenLocale(
36 |             i18nConfig!.locale.source,
37 |             bucketConfig.delimiter,
38 |           );
39 |           const bucketLoader = createBucketLoader(
40 |             bucket.type,
41 |             bucketConfig.pathPattern,
42 |             {
43 |               defaultLocale: sourceLocale,
44 |               formatter: i18nConfig!.formatter,
45 |             },
46 |             bucket.lockedKeys,
47 |             bucket.lockedPatterns,
48 |             bucket.ignoredKeys,
49 |           );
50 |           bucketLoader.setDefaultLocale(sourceLocale);
51 | 
52 |           const sourceData = await bucketLoader.pull(sourceLocale);
53 |           lockfileHelper.registerSourceData(
54 |             bucketConfig.pathPattern,
55 |             sourceData,
56 |           );
57 |         }
58 |       }
59 |       ora.succeed("Lockfile created");
60 |     }
61 |   });
62 | 
63 | const flagsSchema = Z.object({
64 |   force: Z.boolean().default(false),
65 | });
66 | 
```

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

```typescript
 1 | import matter from "gray-matter";
 2 | import YAML from "yaml";
 3 | import { ILoader } from "./_types";
 4 | import { createLoader } from "./_utils";
 5 | 
 6 | const SECTION_REGEX =
 7 |   /^(#{1,6}\s.*$|[-=*]{3,}$|!\[.*\]\(.*\)$|\[.*\]\(.*\)$)/gm;
 8 | const MD_SECTION_PREFIX = "md-section-";
 9 | const FM_ATTR_PREFIX = "fm-attr-";
10 | 
11 | const yamlEngine = {
12 |   parse: (str: string) => YAML.parse(str),
13 |   stringify: (obj: any) => YAML.stringify(obj, { defaultStringType: "PLAIN" }),
14 | };
15 | 
16 | export default function createMarkdownLoader(): ILoader<
17 |   string,
18 |   Record<string, string>
19 | > {
20 |   return createLoader({
21 |     async pull(locale, input) {
22 |       const { data: frontmatter, content } = matter(input, {
23 |         engines: {
24 |           yaml: yamlEngine,
25 |         },
26 |       });
27 | 
28 |       const sections = content
29 |         .split(SECTION_REGEX)
30 |         .map((section) => section?.trim() ?? "")
31 |         .filter(Boolean);
32 | 
33 |       return {
34 |         ...Object.fromEntries(
35 |           sections
36 |             .map((section, index) => [`${MD_SECTION_PREFIX}${index}`, section])
37 |             .filter(([, section]) => Boolean(section)),
38 |         ),
39 |         ...Object.fromEntries(
40 |           Object.entries(frontmatter).map(([key, value]) => [
41 |             `${FM_ATTR_PREFIX}${key}`,
42 |             value,
43 |           ]),
44 |         ),
45 |       };
46 |     },
47 |     async push(locale, data: Record<string, string>) {
48 |       const frontmatter = Object.fromEntries(
49 |         Object.entries(data)
50 |           .filter(([key]) => key.startsWith(FM_ATTR_PREFIX))
51 |           .map(([key, value]) => [key.replace(FM_ATTR_PREFIX, ""), value]),
52 |       );
53 | 
54 |       let content = Object.entries(data)
55 |         .filter(([key]) => key.startsWith(MD_SECTION_PREFIX))
56 |         .sort(
57 |           ([a], [b]) => Number(a.split("-").pop()) - Number(b.split("-").pop()),
58 |         )
59 |         .map(([, value]) => value?.trim() ?? "")
60 |         .filter(Boolean)
61 |         .join("\n\n");
62 | 
63 |       if (Object.keys(frontmatter).length > 0) {
64 |         content = `\n${content}`;
65 |       }
66 | 
67 |       return matter.stringify(content, frontmatter, {
68 |         engines: {
69 |           yaml: yamlEngine,
70 |         },
71 |       });
72 |     },
73 |   });
74 | }
75 | 
```

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

```typescript
 1 | import { NodePath } from "@babel/traverse";
 2 | import * as t from "@babel/types";
 3 | import { Expression, V8IntrinsicIdentifier } from "@babel/types";
 4 | 
 5 | export const getJsxFunctions = (nodePath: NodePath<t.JSXElement>) => {
 6 |   const functions = new Map<string, t.CallExpression[]>();
 7 |   let fnCounter = 0;
 8 | 
 9 |   nodePath.traverse({
10 |     JSXOpeningElement(path) {
11 |       path.skip();
12 |     },
13 |     JSXExpressionContainer(path) {
14 |       if (t.isCallExpression(path.node.expression)) {
15 |         let key = "";
16 |         if (t.isIdentifier(path.node.expression.callee)) {
17 |           key = `${path.node.expression.callee.name}`;
18 |         } else if (t.isMemberExpression(path.node.expression.callee)) {
19 |           let firstCallee: Expression | V8IntrinsicIdentifier =
20 |             path.node.expression.callee;
21 |           while (
22 |             t.isMemberExpression(firstCallee) &&
23 |             t.isCallExpression(firstCallee.object)
24 |           ) {
25 |             firstCallee = firstCallee.object.callee;
26 |           }
27 | 
28 |           let current: Expression | V8IntrinsicIdentifier = firstCallee;
29 |           const parts: string[] = [];
30 | 
31 |           while (t.isMemberExpression(current)) {
32 |             if (t.isIdentifier(current.property)) {
33 |               parts.unshift(current.property.name);
34 |             }
35 |             current = current.object;
36 |           }
37 | 
38 |           if (t.isIdentifier(current)) {
39 |             parts.unshift(current.name);
40 |           }
41 | 
42 |           if (
43 |             t.isMemberExpression(firstCallee) &&
44 |             t.isNewExpression(firstCallee.object) &&
45 |             t.isIdentifier(firstCallee.object.callee)
46 |           ) {
47 |             parts.unshift(firstCallee.object.callee.name);
48 |           }
49 | 
50 |           key = parts.join(".");
51 |         }
52 |         const existing = functions.get(key) ?? [];
53 |         functions.set(key, [...existing, path.node.expression]);
54 |         fnCounter++;
55 |       }
56 |       path.skip();
57 |     },
58 |   });
59 | 
60 |   const properties = Array.from(functions.entries()).map(([name, callExpr]) =>
61 |     t.objectProperty(t.stringLiteral(name), t.arrayExpression(callExpr)),
62 |   );
63 | 
64 |   return t.objectExpression(properties);
65 | };
66 | 
```

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

```typescript
 1 | import { Command } from "interactive-commander";
 2 | import Ora from "ora";
 3 | import express from "express";
 4 | import cors from "cors";
 5 | import open from "open";
 6 | import readline from "readline/promises";
 7 | import { getSettings, saveSettings } from "../utils/settings";
 8 | import {
 9 |   renderClear,
10 |   renderSpacer,
11 |   renderBanner,
12 |   renderHero,
13 | } from "../utils/ui";
14 | 
15 | export default new Command()
16 |   .command("login")
17 |   .description(
18 |     "Open browser to authenticate with lingo.dev and save your API key",
19 |   )
20 |   .helpOption("-h, --help", "Show help")
21 |   .action(async () => {
22 |     try {
23 |       await renderClear();
24 |       await renderSpacer();
25 |       await renderBanner();
26 |       await renderHero();
27 |       await renderSpacer();
28 | 
29 |       const settings = await getSettings(undefined);
30 |       const apiKey = await login(settings.auth.webUrl);
31 |       settings.auth.apiKey = apiKey;
32 |       await saveSettings(settings);
33 |       Ora().succeed("Successfully logged in");
34 |     } catch (error: any) {
35 |       Ora().fail(error.message);
36 |       process.exit(1);
37 |     }
38 |   });
39 | 
40 | export async function login(webAppUrl: string) {
41 |   await readline
42 |     .createInterface({
43 |       input: process.stdin,
44 |       output: process.stdout,
45 |     })
46 |     .question(
47 |       `
48 | Press Enter to open the browser for authentication.
49 | 
50 | ---
51 | 
52 | Having issues? Put LINGODOTDEV_API_KEY in your .env file instead.
53 |     `.trim() + "\n",
54 |     );
55 | 
56 |   const spinner = Ora().start("Waiting for the API key");
57 |   const apiKey = await waitForApiKey(async (port) => {
58 |     await open(`${webAppUrl}/app/cli?port=${port}`, { wait: false });
59 |   });
60 |   spinner.succeed("API key received");
61 | 
62 |   return apiKey;
63 | }
64 | 
65 | async function waitForApiKey(cb: (port: string) => void): Promise<string> {
66 |   const app = express();
67 |   app.use(express.json());
68 |   app.use(cors());
69 | 
70 |   return new Promise((resolve) => {
71 |     const server = app.listen(0, async () => {
72 |       const port = (server.address() as any).port;
73 |       cb(port.toString());
74 |     });
75 | 
76 |     app.post("/", (req, res) => {
77 |       const apiKey = req.body.apiKey;
78 |       res.end();
79 |       server.close(() => {
80 |         resolve(apiKey);
81 |       });
82 |     });
83 |   });
84 | }
85 | 
```

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

```typescript
 1 | import dotenv from "dotenv";
 2 | dotenv.config();
 3 | 
 4 | import { InteractiveCommand } from "interactive-commander";
 5 | import figlet from "figlet";
 6 | import { vice } from "gradient-string";
 7 | 
 8 | import authCmd from "./cmd/auth";
 9 | import loginCmd from "./cmd/login";
10 | import logoutCmd from "./cmd/logout";
11 | import initCmd from "./cmd/init";
12 | import showCmd from "./cmd/show";
13 | import configCmd from "./cmd/config";
14 | import i18nCmd from "./cmd/i18n";
15 | import lockfileCmd from "./cmd/lockfile";
16 | import cleanupCmd from "./cmd/cleanup";
17 | import mcpCmd from "./cmd/mcp";
18 | import ciCmd from "./cmd/ci";
19 | import statusCmd from "./cmd/status";
20 | import mayTheFourthCmd from "./cmd/may-the-fourth";
21 | import packageJson from "../../package.json";
22 | import run from "./cmd/run";
23 | import purgeCmd from "./cmd/purge";
24 | 
25 | export default new InteractiveCommand()
26 |   .name("lingo.dev")
27 |   .description("Lingo.dev CLI")
28 |   .helpOption("-h, --help", "Show help")
29 |   .addHelpText(
30 |     "beforeAll",
31 |     `
32 | ${vice(
33 |   figlet.textSync("LINGO.DEV", {
34 |     font: "ANSI Shadow",
35 |     horizontalLayout: "default",
36 |     verticalLayout: "default",
37 |   }),
38 | )}
39 | 
40 | ⚡️ AI-powered open-source CLI for web & mobile localization.
41 | 
42 | Star the the repo :) https://github.com/LingoDotDev/lingo.dev
43 | `,
44 |   )
45 |   .version(`v${packageJson.version}`, "-v, --version", "Show version")
46 |   .addCommand(initCmd)
47 |   .interactive(
48 |     "-y, --no-interactive",
49 |     "Run every command in non-interactive mode (no prompts); required when scripting",
50 |   ) // all interactive commands above
51 |   .addCommand(i18nCmd)
52 |   .addCommand(authCmd)
53 |   .addCommand(loginCmd)
54 |   .addCommand(logoutCmd)
55 |   .addCommand(showCmd)
56 |   .addCommand(configCmd)
57 |   .addCommand(lockfileCmd)
58 |   .addCommand(cleanupCmd)
59 |   .addCommand(mcpCmd)
60 |   .addCommand(ciCmd)
61 |   .addCommand(statusCmd)
62 |   .addCommand(mayTheFourthCmd, { hidden: true })
63 |   .addCommand(run)
64 |   .addCommand(purgeCmd)
65 |   .exitOverride((err) => {
66 |     // Exit with code 0 when help or version is displayed
67 |     if (
68 |       err.code === "commander.helpDisplayed" ||
69 |       err.code === "commander.version" ||
70 |       err.code === "commander.help"
71 |     ) {
72 |       process.exit(0);
73 |     }
74 |     process.exit(1);
75 |   });
76 | 
```

--------------------------------------------------------------------------------
/packages/react/src/client/locale-switcher.spec.tsx:
--------------------------------------------------------------------------------

```typescript
 1 | import { describe, it, expect, vi, beforeEach } from "vitest";
 2 | import { render, screen, fireEvent } from "@testing-library/react";
 3 | import React from "react";
 4 | import { LocaleSwitcher } from "./locale-switcher";
 5 | 
 6 | vi.mock("./utils", async (orig) => {
 7 |   const actual = await orig();
 8 |   return {
 9 |     ...(actual as any),
10 |     getLocaleFromCookies: vi.fn(() => "es"),
11 |     setLocaleInCookies: vi.fn(),
12 |   };
13 | });
14 | 
15 | import { getLocaleFromCookies, setLocaleInCookies } from "./utils";
16 | 
17 | describe("LocaleSwitcher", () => {
18 |   beforeEach(() => {
19 |     vi.clearAllMocks();
20 |   });
21 | 
22 |   it("returns null before determining initial locale", () => {
23 |     // This component sets state in an effect, but with jsdom and our mocked
24 |     // cookie util returning a value synchronously, it may render immediately.
25 |     // We still assert it produces a select afterward.
26 |     const { container } = render(<LocaleSwitcher locales={["en", "es"]} />);
27 |     expect(container.querySelector("select")).toBeTruthy();
28 |   });
29 | 
30 |   it("uses cookie locale if valid; otherwise defaults to first provided locale", async () => {
31 |     (getLocaleFromCookies as any).mockReturnValueOnce("es");
32 |     render(<LocaleSwitcher locales={["en", "es"]} />);
33 |     const select = (await screen.findByRole("combobox")) as HTMLSelectElement;
34 |     expect(select.value).toBe("es");
35 | 
36 |     // invalid cookie -> defaults to first
37 |     (getLocaleFromCookies as any).mockReturnValueOnce("fr");
38 |     render(<LocaleSwitcher locales={["en", "es"]} />);
39 |     const selects = (await screen.findAllByRole(
40 |       "combobox",
41 |     )) as HTMLSelectElement[];
42 |     expect(selects[1].value).toBe("en");
43 |   });
44 | 
45 |   it("on change sets cookie and triggers full reload", async () => {
46 |     const reloadSpy = vi.fn();
47 |     Object.defineProperty(window, "location", {
48 |       value: { ...window.location, reload: reloadSpy },
49 |       writable: true,
50 |     });
51 |     render(<LocaleSwitcher locales={["en", "es"]} />);
52 |     const select = await screen.findByRole("combobox");
53 |     fireEvent.change(select, { target: { value: "en" } });
54 | 
55 |     expect(setLocaleInCookies).toHaveBeenCalledWith("en");
56 |     expect(reloadSpy).toHaveBeenCalled();
57 |   });
58 | });
59 | 
```

--------------------------------------------------------------------------------
/packages/cli/src/cli/loaders/formatters/prettier.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import prettier, { Options } from "prettier";
 2 | import { ILoader } from "../_types";
 3 | import { createBaseFormatterLoader } from "./_base";
 4 | 
 5 | export type PrettierLoaderOptions = {
 6 |   parser: Options["parser"];
 7 |   bucketPathPattern: string;
 8 |   stage?: "pull" | "push" | "both";
 9 |   alwaysFormat?: boolean;
10 | };
11 | 
12 | export default function createPrettierLoader(
13 |   options: PrettierLoaderOptions,
14 | ): ILoader<string, string> {
15 |   return createBaseFormatterLoader(options, async (data, filePath) => {
16 |     return await formatDataWithPrettier(data, filePath, options);
17 |   });
18 | }
19 | 
20 | async function loadPrettierConfig(filePath: string) {
21 |   try {
22 |     const config = await prettier.resolveConfig(filePath);
23 |     return config;
24 |   } catch (error) {
25 |     return {};
26 |   }
27 | }
28 | 
29 | async function formatDataWithPrettier(
30 |   data: string,
31 |   filePath: string,
32 |   options: PrettierLoaderOptions,
33 | ): Promise<string> {
34 |   const prettierConfig = await loadPrettierConfig(filePath);
35 | 
36 |   // Skip formatting if no config found and alwaysFormat is not enabled
37 |   if (!prettierConfig && !options.alwaysFormat) {
38 |     return data;
39 |   }
40 | 
41 |   const config: Options = {
42 |     ...(prettierConfig || { printWidth: 2500, bracketSameLine: false }),
43 |     parser: options.parser,
44 |     // For HTML parser, preserve comments and quotes
45 |     ...(options.parser === "html"
46 |       ? {
47 |           htmlWhitespaceSensitivity: "ignore",
48 |           singleQuote: false,
49 |           embeddedLanguageFormatting: "off",
50 |         }
51 |       : {}),
52 |   };
53 | 
54 |   try {
55 |     // format with prettier
56 |     return await prettier.format(data, config);
57 |   } catch (error) {
58 |     if (
59 |       error instanceof Error &&
60 |       error.message.startsWith("Cannot find package")
61 |     ) {
62 |       console.log();
63 |       console.log(
64 |         "⚠️  Prettier plugins are not installed. Formatting without plugins.",
65 |       );
66 |       console.log(
67 |         "⚠️  To use prettier plugins install project dependencies before running Lingo.dev.",
68 |       );
69 | 
70 |       config.plugins = [];
71 | 
72 |       // clear file system structure cache
73 |       await prettier.clearConfigCache();
74 | 
75 |       // format again without plugins
76 |       return await prettier.format(data, config);
77 |     }
78 | 
79 |     throw error;
80 |   }
81 | }
82 | 
```

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

```typescript
  1 | import path from "path";
  2 | import fs from "fs";
  3 | 
  4 | interface CacheRow {
  5 |   targetLocale: string;
  6 |   key: string;
  7 |   source: string;
  8 |   processed: string;
  9 | }
 10 | 
 11 | interface NormalizedCacheItem {
 12 |   source: string;
 13 |   result: string;
 14 | }
 15 | 
 16 | type NormalizedCache = Record<string, NormalizedCacheItem>;
 17 | 
 18 | interface NormalizedLocaleCache {
 19 |   [targetLocale: string]: NormalizedCache;
 20 | }
 21 | 
 22 | export const cacheChunk = (
 23 |   targetLocale: string,
 24 |   sourceChunk: Record<string, string>,
 25 |   processedChunk: Record<string, string>,
 26 | ) => {
 27 |   const rows = Object.entries(sourceChunk).map(([key, source]) => ({
 28 |     targetLocale,
 29 |     key,
 30 |     source,
 31 |     processed: processedChunk[key],
 32 |   }));
 33 |   _appendToCache(rows);
 34 | };
 35 | 
 36 | export function getNormalizedCache() {
 37 |   const rows = _loadCache();
 38 |   if (!rows.length) {
 39 |     return null;
 40 |   }
 41 | 
 42 |   const normalized: NormalizedLocaleCache = {};
 43 | 
 44 |   for (const row of rows) {
 45 |     if (!normalized[row.targetLocale]) {
 46 |       normalized[row.targetLocale] = {};
 47 |     }
 48 | 
 49 |     normalized[row.targetLocale][row.key] = {
 50 |       source: row.source,
 51 |       result: row.processed,
 52 |     };
 53 |   }
 54 | 
 55 |   return normalized;
 56 | }
 57 | 
 58 | export function deleteCache() {
 59 |   const cacheFilePath = _getCacheFilePath();
 60 |   try {
 61 |     fs.unlinkSync(cacheFilePath);
 62 |   } catch (e) {
 63 |     // file might not exist
 64 |   }
 65 | }
 66 | 
 67 | function _loadCache() {
 68 |   const cacheFilePath = _getCacheFilePath();
 69 |   if (!fs.existsSync(cacheFilePath)) {
 70 |     return [];
 71 |   }
 72 |   const content = fs.readFileSync(cacheFilePath, "utf-8");
 73 |   const result = _parseJSONLines(content);
 74 |   return result;
 75 | }
 76 | 
 77 | function _appendToCache(rows: CacheRow[]) {
 78 |   const cacheFilePath = _getCacheFilePath();
 79 |   const lines = _buildJSONLines(rows);
 80 |   fs.appendFileSync(cacheFilePath, lines);
 81 | }
 82 | 
 83 | function _getCacheFilePath() {
 84 |   return path.join(process.cwd(), "i18n.cache");
 85 | }
 86 | 
 87 | function _buildJSONLines(rows: CacheRow[]) {
 88 |   return rows.map((row) => JSON.stringify(row)).join("\n") + "\n";
 89 | }
 90 | 
 91 | function _parseJSONLines(lines: string) {
 92 |   return lines
 93 |     .split("\n")
 94 |     .map(_tryParseJSON)
 95 |     .filter((line) => line !== null);
 96 | }
 97 | 
 98 | function _tryParseJSON(line: string) {
 99 |   try {
100 |     return JSON.parse(line);
101 |   } catch (e) {
102 |     return null;
103 |   }
104 | }
105 | 
```

--------------------------------------------------------------------------------
/packages/cli/src/cli/loaders/xcode-xcstrings-v2-loader.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { ILoader } from "./_types";
 2 | import { createLoader } from "./_utils";
 3 | import {
 4 |   xcstringsToPluralWithMeta,
 5 |   pluralWithMetaToXcstrings,
 6 |   isPluralFormsObject,
 7 |   isICUPluralObject,
 8 | } from "./xcode-xcstrings-icu";
 9 | 
10 | /**
11 |  * Loader for xcode-xcstrings-v2 bucket type with ICU MessageFormat support.
12 |  *
13 |  * This should be placed AFTER xcode-xcstrings loader and BEFORE flat loader.
14 |  *
15 |  * Input:  {items: {zero: "No items", one: "1 item", other: "%d items"}}
16 |  * Output: {items: {icu: "{count, plural, =0 {No items} one {1 item} other {# items}}", _meta: {...}}}
17 |  *
18 |  * Lock files will contain checksums of ICU format (new format for pluralization support).
19 |  */
20 | export default function createXcodeXcstringsV2Loader(
21 |   defaultLocale: string = "en",
22 | ): ILoader<Record<string, any>, Record<string, any>> {
23 |   return createLoader({
24 |     async pull(locale, input) {
25 |       const result: Record<string, any> = {};
26 | 
27 |       for (const [key, value] of Object.entries(input)) {
28 |         if (isPluralFormsObject(value)) {
29 |           try {
30 |             result[key] = xcstringsToPluralWithMeta(value, locale);
31 |           } catch (error) {
32 |             console.error(
33 |               `\n[xcode-xcstrings-icu] Failed to convert plural forms for key "${key}":`,
34 |               `\nError: ${error instanceof Error ? error.message : String(error)}`,
35 |               `\nLocale: ${locale}\n`,
36 |             );
37 |             result[key] = value;
38 |           }
39 |         } else {
40 |           result[key] = value;
41 |         }
42 |       }
43 | 
44 |       return result;
45 |     },
46 | 
47 |     async push(locale, payload) {
48 |       const result: Record<string, any> = {};
49 | 
50 |       for (const [key, value] of Object.entries(payload)) {
51 |         if (isICUPluralObject(value)) {
52 |           try {
53 |             const pluralForms = pluralWithMetaToXcstrings(value);
54 |             result[key] = pluralForms;
55 |           } catch (error) {
56 |             throw new Error(
57 |               `Failed to write plural translation for key "${key}" (locale: ${locale}).\n` +
58 |                 `${error instanceof Error ? error.message : String(error)}`,
59 |             );
60 |           }
61 |         } else {
62 |           result[key] = value;
63 |         }
64 |       }
65 | 
66 |       return result;
67 |     },
68 |   });
69 | }
70 | 
```

--------------------------------------------------------------------------------
/packages/compiler/src/utils/llm-api-key.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { getRc } from "./rc";
 2 | import _ from "lodash";
 3 | import * as dotenv from "dotenv";
 4 | import path from "path";
 5 | 
 6 | // Generic function to retrieve key from process.env, with .env file as fallback
 7 | export function getKeyFromEnv(envVarName: string): string | undefined {
 8 |   if (process.env[envVarName]) {
 9 |     return process.env[envVarName];
10 |   }
11 |   const result = dotenv.config({
12 |     path: [
13 |       path.resolve(process.cwd(), ".env"),
14 |       path.resolve(process.cwd(), ".env.local"),
15 |       path.resolve(process.cwd(), ".env.development"),
16 |     ],
17 |   });
18 |   return result?.parsed?.[envVarName];
19 | }
20 | 
21 | // Generic function to retrieve key from .lingodotdevrc file
22 | function getKeyFromRc(rcPath: string): string | undefined {
23 |   const rc = getRc();
24 |   const result = _.get(rc, rcPath);
25 |   return typeof result === "string" ? result : undefined;
26 | }
27 | 
28 | export function getGroqKey() {
29 |   return getGroqKeyFromEnv() || getGroqKeyFromRc();
30 | }
31 | 
32 | export function getGroqKeyFromRc() {
33 |   return getKeyFromRc("llm.groqApiKey");
34 | }
35 | 
36 | export function getGroqKeyFromEnv() {
37 |   return getKeyFromEnv("GROQ_API_KEY");
38 | }
39 | 
40 | export function getLingoDotDevKeyFromEnv() {
41 |   return getKeyFromEnv("LINGODOTDEV_API_KEY");
42 | }
43 | 
44 | export function getLingoDotDevKeyFromRc() {
45 |   return getKeyFromRc("auth.apiKey");
46 | }
47 | 
48 | export function getLingoDotDevKey() {
49 |   return getLingoDotDevKeyFromEnv() || getLingoDotDevKeyFromRc();
50 | }
51 | 
52 | export function getGoogleKey() {
53 |   return getGoogleKeyFromEnv() || getGoogleKeyFromRc();
54 | }
55 | 
56 | export function getGoogleKeyFromRc() {
57 |   return getKeyFromRc("llm.googleApiKey");
58 | }
59 | 
60 | export function getGoogleKeyFromEnv() {
61 |   return getKeyFromEnv("GOOGLE_API_KEY");
62 | }
63 | 
64 | export function getOpenRouterKey() {
65 |   return getOpenRouterKeyFromEnv() || getOpenRouterKeyFromRc();
66 | }
67 | export function getOpenRouterKeyFromRc() {
68 |   return getKeyFromRc("llm.openrouterApiKey");
69 | }
70 | export function getOpenRouterKeyFromEnv() {
71 |   return getKeyFromEnv("OPENROUTER_API_KEY");
72 | }
73 | 
74 | export function getMistralKey() {
75 |   return getMistralKeyFromEnv() || getMistralKeyFromRc();
76 | }
77 | 
78 | export function getMistralKeyFromRc() {
79 |   return getKeyFromRc("llm.mistralApiKey");
80 | }
81 | 
82 | export function getMistralKeyFromEnv() {
83 |   return getKeyFromEnv("MISTRAL_API_KEY");
84 | }
85 | 
```

--------------------------------------------------------------------------------
/packages/compiler/src/lib/lcp/api/prompt.ts:
--------------------------------------------------------------------------------

```typescript
 1 | interface PromptArguments {
 2 |   sourceLocale: string;
 3 |   targetLocale: string;
 4 |   prompt?: string;
 5 | }
 6 | 
 7 | export default (args: PromptArguments) => {
 8 |   return getUserSystemPrompt(args) || getBuiltInSystemPrompt(args);
 9 | };
10 | 
11 | function getUserSystemPrompt(args: PromptArguments): string | undefined {
12 |   const userPrompt = args.prompt
13 |     ?.trim()
14 |     ?.replace("{SOURCE_LOCALE}", args.sourceLocale)
15 |     ?.replace("{TARGET_LOCALE}", args.targetLocale);
16 |   if (userPrompt) {
17 |     console.log("✨ Compiler is using user-defined prompt.");
18 |     return userPrompt;
19 |   }
20 |   return undefined;
21 | }
22 | 
23 | function getBuiltInSystemPrompt(args: PromptArguments) {
24 |   return `
25 | # Identity
26 | 
27 | You are an advanced AI localization engine. You do state-of-the-art localization for software products.
28 | Your task is to localize pieces of data from one locale to another locale.
29 | You always consider context, cultural nuances of source and target locales, and specific localization requirements.
30 | You replicate the meaning, intent, style, tone, and purpose of the original data.
31 | 
32 | ## Setup
33 | 
34 | Source language (locale code): ${args.sourceLocale}
35 | Target language (locale code): ${args.targetLocale}
36 | 
37 | ## Guidelines
38 | 
39 | Follow these guidelines for translation:
40 | 
41 | 1. Analyze the source text to understand its overall context and purpose
42 | 2. Translate the meaning and intent rather than word-for-word translation
43 | 3. Rephrase and restructure sentences to sound natural and fluent in the target language
44 | 4. Adapt idiomatic expressions and cultural references for the target audience
45 | 5. Maintain the style and tone of the source text
46 | 6. You must produce valid UTF-8 encoded output
47 | 7. YOU MUST ONLY PRODUCE VALID XML.
48 | 
49 | ## Special Instructions
50 | 
51 | Do not localize any of these technical elements:
52 | - Variables like {variable}, {variable.key}, {data[type]}
53 | - Expressions like <expression/>
54 | - Functions like <function:value/>, <function:getDisplayName/>
55 | - Elements like <element:strong>, </element:strong>, <element:LuPlus>, </element:LuPlus>, <element:LuSparkles>, </element:LuSparkles>
56 | 
57 | Remember, you are a context-aware multilingual assistant helping international companies.
58 | Your goal is to perform state-of-the-art localization for software products and content.
59 | `;
60 | }
61 | 
```

--------------------------------------------------------------------------------
/demo/adonisjs/inertia/lingo/dictionary.js:
--------------------------------------------------------------------------------

```javascript
 1 | export default {
 2 |   version: 0.1,
 3 |   files: {
 4 |     'pages/errors/not_found.tsx': {
 5 |       entries: {
 6 |         '1/declaration/body/0/argument/1/1': {
 7 |           content: {
 8 |             en: 'Page not found',
 9 |             es: 'Página no encontrada',
10 |           },
11 |           hash: '97612e6230bc7a1ebd99380bf561b732',
12 |         },
13 |         '1/declaration/body/0/argument/1/3': {
14 |           content: {
15 |             en: 'This page does not exist.',
16 |             es: 'Esta página no existe.',
17 |           },
18 |           hash: '7b6bcd0a4f23e42eeb0c972c2004efad',
19 |         },
20 |       },
21 |     },
22 |     'pages/errors/server_error.tsx': {
23 |       entries: {
24 |         '1/declaration/body/0/argument/1/1': {
25 |           content: {
26 |             en: 'Server Error',
27 |             es: 'Error del servidor',
28 |           },
29 |           hash: 'd574aa7e2d84d112dc79ac0e59d794cf',
30 |         },
31 |       },
32 |     },
33 |     'pages/home.tsx': {
34 |       entries: {
35 |         '2/declaration/body/0/argument/1-title': {
36 |           content: {
37 |             en: 'Homepage',
38 |             es: 'Página de inicio',
39 |           },
40 |           hash: '7c2d68be7446e6de191c11d53f1e07b4',
41 |         },
42 |         '2/declaration/body/0/argument/3/1/1': {
43 |           content: {
44 |             en: 'Hello, world!',
45 |             es: '¡Hola, mundo!',
46 |           },
47 |           hash: '0468579ef2fbc83c9d520c2f2f1c5059',
48 |         },
49 |         '2/declaration/body/0/argument/3/1/3': {
50 |           content: {
51 |             en: 'This is an example app that demonstrates how <element:strong>Lingo.dev Compiler</element:strong> can be used to localize apps built with <element:a>AdonisJS</element:a> .',
52 |             es: 'Esta es una aplicación de ejemplo que demuestra cómo <element:strong>Lingo.dev Compiler</element:strong> puede ser utilizado para localizar aplicaciones construidas con <element:a>AdonisJS</element:a>.',
53 |           },
54 |           hash: '82b29979a52b215b94b2e811e8c03005',
55 |         },
56 |         '2/declaration/body/0/argument/3/1/5': {
57 |           content: {
58 |             en: 'To switch between locales, use the following dropdown:',
59 |             es: 'Para cambiar entre idiomas, utiliza el siguiente menú desplegable:',
60 |           },
61 |           hash: '9ffb5f98cf11c88f3903e060f4028b46',
62 |         },
63 |       },
64 |     },
65 |   },
66 | }
67 | 
```

--------------------------------------------------------------------------------
/packages/compiler/src/_utils.spec.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { afterEach, describe, expect, it, vi } from "vitest";
 2 | 
 3 | afterEach(() => {
 4 |   // Reset module registry and any mocks so each test gets a fresh copy
 5 |   vi.resetModules();
 6 |   vi.clearAllMocks();
 7 |   vi.unmock("path");
 8 | });
 9 | 
10 | describe("getDictionaryPath", () => {
11 |   it.each([
12 |     {
13 |       sourceRoot: "src",
14 |       lingoDir: "lingo",
15 |       relativeFilePath: "./components/Button.tsx",
16 |       expected: "./../lingo/dictionary.js",
17 |     },
18 |     {
19 |       sourceRoot: "src/app/content",
20 |       lingoDir: "i18n",
21 |       relativeFilePath: "../../components/Button.tsx",
22 |       expected: "./../app/content/i18n/dictionary.js",
23 |     },
24 |   ])(
25 |     "returns correct path for file $relativeFilePath in $sourceRoot",
26 |     async ({ sourceRoot, lingoDir, relativeFilePath, expected }) => {
27 |       const { getDictionaryPath } = await import("./_utils");
28 | 
29 |       const result = getDictionaryPath({
30 |         sourceRoot,
31 |         lingoDir,
32 |         relativeFilePath,
33 |       });
34 |       expect(result).toBe(expected);
35 |     },
36 |   );
37 | 
38 |   it("returns POSIX-style relative path on POSIX", async () => {
39 |     // Import fresh copy with the real Node "path" module (POSIX on *nix, win32 on Windows)
40 |     const { getDictionaryPath } = await import("./_utils");
41 | 
42 |     const result = getDictionaryPath({
43 |       sourceRoot: "/project/src",
44 |       lingoDir: "lingo",
45 |       relativeFilePath: "/project/src/components/Button.tsx",
46 |     });
47 | 
48 |     expect(result).toBe("./../lingo/dictionary.js");
49 |     // Ensure no back-slashes slip through
50 |     expect(result).not.toMatch(/\\/);
51 |   });
52 | 
53 |   it('returns the same POSIX-style path when the Node "path" API uses win32 semantics (mock Windows)', async () => {
54 |     // Force every call to "path.*" inside _utils to use the Windows implementation
55 |     vi.mock("path", () => {
56 |       const nodePath = require("path") as typeof import("path");
57 |       return { ...nodePath.win32, default: nodePath.win32 };
58 |     });
59 | 
60 |     const { getDictionaryPath } = await import("./_utils");
61 | 
62 |     const result = getDictionaryPath({
63 |       sourceRoot: "C:\\project\\src",
64 |       lingoDir: "lingo",
65 |       relativeFilePath: "C:\\project\\src\\components\\Button.tsx",
66 |     });
67 | 
68 |     expect(result).toBe("./../lingo/dictionary.js");
69 |     expect(result).not.toMatch(/\\/);
70 |   });
71 | });
72 | 
```

--------------------------------------------------------------------------------
/packages/cli/src/cli/loaders/xcode-strings/parser.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { Token, TokenType } from "./types";
  2 | import { unescapeString } from "./escape";
  3 | 
  4 | export class Parser {
  5 |   private tokens: Token[];
  6 |   private pos: number;
  7 | 
  8 |   constructor(tokens: Token[]) {
  9 |     this.tokens = tokens;
 10 |     this.pos = 0;
 11 |   }
 12 | 
 13 |   parse(): Record<string, string> {
 14 |     const result: Record<string, string> = {};
 15 | 
 16 |     while (this.pos < this.tokens.length) {
 17 |       const token = this.current();
 18 | 
 19 |       // Skip comments
 20 |       if (
 21 |         token.type === TokenType.COMMENT_SINGLE ||
 22 |         token.type === TokenType.COMMENT_MULTI
 23 |       ) {
 24 |         this.advance();
 25 |         continue;
 26 |       }
 27 | 
 28 |       // End of file
 29 |       if (token.type === TokenType.EOF) {
 30 |         break;
 31 |       }
 32 | 
 33 |       // Expect entry: STRING "=" STRING ";"
 34 |       if (token.type === TokenType.STRING) {
 35 |         const entry = this.parseEntry();
 36 |         if (entry) {
 37 |           result[entry.key] = entry.value;
 38 |         }
 39 |         continue;
 40 |       }
 41 | 
 42 |       // Skip unexpected tokens gracefully
 43 |       this.advance();
 44 |     }
 45 | 
 46 |     return result;
 47 |   }
 48 | 
 49 |   private parseEntry(): { key: string; value: string } | null {
 50 |     // Current token should be STRING (key)
 51 |     const keyToken = this.current();
 52 |     if (keyToken.type !== TokenType.STRING) {
 53 |       return null;
 54 |     }
 55 |     const key = keyToken.value;
 56 |     this.advance();
 57 | 
 58 |     // Expect '='
 59 |     if (!this.expect(TokenType.EQUALS)) {
 60 |       // Missing '=' - skip this entry
 61 |       return null;
 62 |     }
 63 | 
 64 |     // Expect STRING (value)
 65 |     const valueToken = this.current();
 66 |     if (valueToken.type !== TokenType.STRING) {
 67 |       // Missing value - skip this entry
 68 |       return null;
 69 |     }
 70 |     const rawValue = valueToken.value;
 71 |     this.advance();
 72 | 
 73 |     // Expect ';'
 74 |     if (!this.expect(TokenType.SEMICOLON)) {
 75 |       // Missing ';' - but still process the entry
 76 |       // (more forgiving)
 77 |     }
 78 | 
 79 |     // Unescape the value
 80 |     const value = unescapeString(rawValue);
 81 | 
 82 |     return { key, value };
 83 |   }
 84 | 
 85 |   private current(): Token {
 86 |     return this.tokens[this.pos];
 87 |   }
 88 | 
 89 |   private advance(): void {
 90 |     if (this.pos < this.tokens.length) {
 91 |       this.pos++;
 92 |     }
 93 |   }
 94 | 
 95 |   private expect(type: TokenType): boolean {
 96 |     if (this.current()?.type === type) {
 97 |       this.advance();
 98 |       return true;
 99 |     }
100 |     return false;
101 |   }
102 | }
103 | 
```

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

```typescript
 1 | import _ from "lodash";
 2 | import fs from "fs";
 3 | import { ILoader } from "../_types";
 4 | import { createLoader } from "../_utils";
 5 | import { DatoApiLoaderOutput } from "./api";
 6 | 
 7 | export type DatoFilterLoaderOutput = {
 8 |   [modelId: string]: {
 9 |     [recordId: string]: {
10 |       [fieldName: string]: any;
11 |     };
12 |   };
13 | };
14 | 
15 | export default function createDatoFilterLoader(): ILoader<
16 |   DatoApiLoaderOutput,
17 |   DatoFilterLoaderOutput
18 | > {
19 |   return createLoader({
20 |     async pull(locale, input) {
21 |       const result: DatoFilterLoaderOutput = {};
22 | 
23 |       for (const [modelId, modelInfo] of _.entries(input)) {
24 |         result[modelId] = {};
25 |         for (const record of modelInfo.records) {
26 |           result[modelId][record.id] = _.chain(modelInfo.fields)
27 |             .mapKeys((field) => field.api_key)
28 |             .mapValues((field) => _.get(record, [field.api_key, locale]))
29 |             .value();
30 |         }
31 |       }
32 | 
33 |       return result;
34 |     },
35 |     async push(locale, data, originalInput, originalLocale) {
36 |       const result = _.cloneDeep(originalInput || {});
37 | 
38 |       for (const [modelId, modelInfo] of _.entries(result)) {
39 |         for (const record of modelInfo.records) {
40 |           for (const [fieldId, fieldValue] of _.entries(record)) {
41 |             const fieldInfo = modelInfo.fields.find(
42 |               (field) => field.api_key === fieldId,
43 |             );
44 |             if (fieldInfo) {
45 |               const sourceFieldValue = _.get(fieldValue, [originalLocale]);
46 |               const targetFieldValue = _.get(data, [
47 |                 modelId,
48 |                 record.id,
49 |                 fieldId,
50 |               ]);
51 |               if (targetFieldValue) {
52 |                 _.set(record, [fieldId, locale], targetFieldValue);
53 |               } else {
54 |                 _.set(record, [fieldId, locale], sourceFieldValue);
55 |               }
56 | 
57 |               _.chain(fieldValue)
58 |                 .keys()
59 |                 .reject((loc) => loc === locale || loc === originalLocale)
60 |                 .filter((loc) => _.isEmpty(_.get(fieldValue, [loc])))
61 |                 .forEach((loc) =>
62 |                   _.set(record, [fieldId, loc], sourceFieldValue),
63 |                 )
64 |                 .value();
65 |             }
66 |           }
67 |         }
68 |       }
69 | 
70 |       return result;
71 |     },
72 |   });
73 | }
74 | 
```

--------------------------------------------------------------------------------
/packages/cli/src/cli/cmd/ci/platforms/_base.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { execSync } from "child_process";
 2 | import Z from "zod";
 3 | 
 4 | const defaultMessage = "feat: update translations via @lingodotdev";
 5 | 
 6 | interface BasePlatformConfig {
 7 |   baseBranchName: string;
 8 |   repositoryOwner: string;
 9 |   repositoryName: string;
10 | }
11 | 
12 | export abstract class PlatformKit<
13 |   PlatformConfig extends BasePlatformConfig = BasePlatformConfig,
14 | > {
15 |   abstract branchExists(props: { branch: string }): Promise<boolean>;
16 | 
17 |   abstract getOpenPullRequestNumber(props: {
18 |     branch: string;
19 |   }): Promise<number | undefined>;
20 | 
21 |   abstract closePullRequest(props: {
22 |     pullRequestNumber: number;
23 |   }): Promise<void>;
24 | 
25 |   abstract createPullRequest(props: {
26 |     head: string;
27 |     title: string;
28 |     body?: string;
29 |   }): Promise<number>;
30 | 
31 |   abstract commentOnPullRequest(props: {
32 |     pullRequestNumber: number;
33 |     body: string;
34 |   }): Promise<void>;
35 | 
36 |   abstract get platformConfig(): PlatformConfig;
37 | 
38 |   abstract buildPullRequestUrl(pullRequestNumber: number): string;
39 | 
40 |   gitConfig(token?: string, repoUrl?: string) {
41 |     if (token && repoUrl) {
42 |       execSync(`git remote set-url origin ${repoUrl}`, {
43 |         stdio: "inherit",
44 |       });
45 |     }
46 |   }
47 | 
48 |   get config() {
49 |     const env = Z.object({
50 |       LINGODOTDEV_API_KEY: Z.string(),
51 |       LINGODOTDEV_PULL_REQUEST: Z.preprocess(
52 |         (val) => val === "true" || val === true,
53 |         Z.boolean(),
54 |       ),
55 |       LINGODOTDEV_COMMIT_MESSAGE: Z.string().optional(),
56 |       LINGODOTDEV_PULL_REQUEST_TITLE: Z.string().optional(),
57 |       LINGODOTDEV_WORKING_DIRECTORY: Z.string().optional(),
58 |       LINGODOTDEV_PROCESS_OWN_COMMITS: Z.preprocess(
59 |         (val) => val === "true" || val === true,
60 |         Z.boolean(),
61 |       ).optional(),
62 |     }).parse(process.env);
63 | 
64 |     return {
65 |       replexicaApiKey: env.LINGODOTDEV_API_KEY,
66 |       isPullRequestMode: env.LINGODOTDEV_PULL_REQUEST,
67 |       commitMessage: env.LINGODOTDEV_COMMIT_MESSAGE || defaultMessage,
68 |       pullRequestTitle: env.LINGODOTDEV_PULL_REQUEST_TITLE || defaultMessage,
69 |       workingDir: env.LINGODOTDEV_WORKING_DIRECTORY || ".",
70 |       processOwnCommits: env.LINGODOTDEV_PROCESS_OWN_COMMITS || false,
71 |     };
72 |   }
73 | }
74 | 
75 | export interface IConfig {
76 |   replexicaApiKey: string;
77 |   isPullRequestMode: boolean;
78 |   commitMessage: string;
79 |   pullRequestTitle: string;
80 | }
81 | 
```

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

```typescript
 1 | import { CLIError } from "./errors";
 2 | import {
 3 |   checkCloudflareStatus,
 4 |   formatCloudflareStatusMessage,
 5 | } from "./cloudflare-status";
 6 | 
 7 | export type AuthenticatorParams = {
 8 |   apiUrl: string;
 9 |   apiKey: string;
10 | };
11 | 
12 | export type AuthPayload = {
13 |   email: string;
14 |   id: string;
15 | };
16 | 
17 | export function createAuthenticator(params: AuthenticatorParams) {
18 |   return {
19 |     async whoami(): Promise<AuthPayload | null> {
20 |       try {
21 |         const res = await fetch(`${params.apiUrl}/whoami`, {
22 |           method: "POST",
23 |           headers: {
24 |             Authorization: `Bearer ${params.apiKey}`,
25 |             ContentType: "application/json",
26 |           },
27 |         });
28 | 
29 |         if (res.ok) {
30 |           const payload = await res.json();
31 |           if (!payload?.email) {
32 |             return null;
33 |           }
34 | 
35 |           return {
36 |             email: payload.email,
37 |             id: payload.id,
38 |           };
39 |         }
40 | 
41 |         if (res.status >= 500 && res.status < 600) {
42 |           const originalErrorMessage = `Server error (${res.status}): ${res.statusText}. Please try again later.`;
43 | 
44 |           const cloudflareStatus = await checkCloudflareStatus();
45 | 
46 |           if (!cloudflareStatus) {
47 |             throw new CLIError({
48 |               message: originalErrorMessage,
49 |               docUrl: "connectionFailed",
50 |             });
51 |           }
52 | 
53 |           if (cloudflareStatus.status.indicator !== "none") {
54 |             const cloudflareMessage =
55 |               formatCloudflareStatusMessage(cloudflareStatus);
56 |             throw new CLIError({
57 |               message: cloudflareMessage,
58 |               docUrl: "connectionFailed",
59 |             });
60 |           }
61 | 
62 |           throw new CLIError({
63 |             message: originalErrorMessage,
64 |             docUrl: "connectionFailed",
65 |           });
66 |         }
67 | 
68 |         return null;
69 |       } catch (error) {
70 |         if (error instanceof CLIError) {
71 |           throw error;
72 |         }
73 | 
74 |         const isNetworkError =
75 |           error instanceof TypeError && error.message === "fetch failed";
76 |         if (isNetworkError) {
77 |           throw new CLIError({
78 |             message: `Failed to connect to the API at ${params.apiUrl}. Please check your connection and try again.`,
79 |             docUrl: "connectionFailed",
80 |           });
81 |         } else {
82 |           throw error;
83 |         }
84 |       }
85 |     },
86 |   };
87 | }
88 | 
```

--------------------------------------------------------------------------------
/demo/adonisjs/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "name": "adonis",
 3 |   "version": "0.0.29",
 4 |   "private": true,
 5 |   "type": "module",
 6 |   "license": "UNLICENSED",
 7 |   "scripts": {
 8 |     "start": "node bin/server.js",
 9 |     "build": "node ace build",
10 |     "dev": "node ace serve --hmr",
11 |     "lint": "eslint .",
12 |     "format": "prettier --write .",
13 |     "typecheck": "tsc --noEmit"
14 |   },
15 |   "imports": {
16 |     "#controllers/*": "./app/controllers/*.js",
17 |     "#exceptions/*": "./app/exceptions/*.js",
18 |     "#models/*": "./app/models/*.js",
19 |     "#mails/*": "./app/mails/*.js",
20 |     "#services/*": "./app/services/*.js",
21 |     "#listeners/*": "./app/listeners/*.js",
22 |     "#events/*": "./app/events/*.js",
23 |     "#middleware/*": "./app/middleware/*.js",
24 |     "#validators/*": "./app/validators/*.js",
25 |     "#providers/*": "./providers/*.js",
26 |     "#policies/*": "./app/policies/*.js",
27 |     "#abilities/*": "./app/abilities/*.js",
28 |     "#database/*": "./database/*.js",
29 |     "#tests/*": "./tests/*.js",
30 |     "#start/*": "./start/*.js",
31 |     "#config/*": "./config/*.js"
32 |   },
33 |   "devDependencies": {
34 |     "@adonisjs/assembler": "^7.8.2",
35 |     "@adonisjs/eslint-config": "^2.0.0",
36 |     "@adonisjs/prettier-config": "^1.4.4",
37 |     "@adonisjs/tsconfig": "^1.4.0",
38 |     "@japa/assert": "^4.0.1",
39 |     "@japa/plugin-adonisjs": "^4.0.0",
40 |     "@japa/runner": "^4.2.0",
41 |     "@swc/core": "1.11.24",
42 |     "@types/node": "^22.15.18",
43 |     "@types/react": "^19.1.8",
44 |     "@types/react-dom": "^19.1.6",
45 |     "@vitejs/plugin-react": "^4.7.0",
46 |     "eslint": "^9.26.0",
47 |     "hot-hook": "^0.4.0",
48 |     "pino-pretty": "^13.0.0",
49 |     "prettier": "^3.5.3",
50 |     "ts-node-maintained": "^10.9.5",
51 |     "typescript": "~5.8.3",
52 |     "vite": "^6.3.5"
53 |   },
54 |   "dependencies": {
55 |     "@adonisjs/auth": "^9.4.0",
56 |     "@adonisjs/core": "^6.18.0",
57 |     "@adonisjs/cors": "^2.2.1",
58 |     "@adonisjs/inertia": "^3.1.1",
59 |     "@adonisjs/lucid": "^21.6.1",
60 |     "@adonisjs/session": "^7.5.1",
61 |     "@adonisjs/shield": "^8.2.0",
62 |     "@adonisjs/static": "^1.1.1",
63 |     "@adonisjs/vite": "^4.0.0",
64 |     "@inertiajs/react": "^2.0.17",
65 |     "@vinejs/vine": "^3.0.1",
66 |     "edge.js": "^6.2.1",
67 |     "lingo.dev": "workspace:*",
68 |     "react": "^19.1.0",
69 |     "react-dom": "^19.1.0",
70 |     "reflect-metadata": "^0.2.2"
71 |   },
72 |   "hotHook": {
73 |     "boundaries": [
74 |       "./app/controllers/**/*.ts",
75 |       "./app/middleware/*.ts"
76 |     ]
77 |   },
78 |   "prettier": "@adonisjs/prettier-config"
79 | }
80 | 
```

--------------------------------------------------------------------------------
/packages/cli/src/cli/utils/ensure-patterns.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import fs from "fs";
 2 | import path from "path";
 3 | import { glob } from "glob";
 4 | import _ from "lodash";
 5 | import { LocaleCode, resolveLocaleCode } from "@lingo.dev/_spec";
 6 | 
 7 | export function ensurePatterns(patterns: string[], source: string) {
 8 |   if (patterns.length === 0) {
 9 |     throw new Error("No patterns found");
10 |   }
11 | 
12 |   patterns.forEach((pattern) => {
13 |     const filePath = pattern.replace("[locale]", source);
14 |     if (!fs.existsSync(filePath)) {
15 |       const defaultContent = getDefaultContent(path.extname(filePath), source);
16 |       fs.mkdirSync(path.dirname(filePath), { recursive: true });
17 |       fs.writeFileSync(filePath, defaultContent);
18 |     }
19 |   });
20 | }
21 | 
22 | function getDefaultContent(ext: string, source: string) {
23 |   const defaultGreeting = "Hello from Lingo.dev";
24 |   switch (ext) {
25 |     case ".json":
26 |     case ".arb":
27 |       return `{\n\t"greeting": "${defaultGreeting}"\n}`;
28 |     case ".yml":
29 |       return `${source}:\n\tgreeting: "${defaultGreeting}"`;
30 |     case ".xml":
31 |       return `<resources>\n\t<string name="greeting">${defaultGreeting}</string>\n</resources>`;
32 |     case ".md":
33 |       return `# ${defaultGreeting}`;
34 |     case ".xcstrings":
35 |       return `{
36 |   "sourceLanguage" : "${source}",
37 |   "strings" : {
38 |     "${defaultGreeting}" : {
39 |       "extractionState" : "manual",
40 |       "localizations" : {
41 |         "${source}" : {
42 |           "stringUnit" : {
43 |             "state" : "translated",
44 |             "value" : "${defaultGreeting}"
45 |           }
46 |         }
47 |       }
48 |     }
49 |   }
50 | }`;
51 |     case ".strings":
52 |       return `"greeting" = "${defaultGreeting}";`;
53 |     case ".stringsdict":
54 |       return `<?xml version="1.0" encoding="UTF-8"?>
55 | <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
56 | <plist version="1.0">
57 | <dict>
58 |   <key>key</key>
59 |   <dict>
60 |     <key>NSStringLocalizedFormatKey</key>
61 |     <string>%#@count@</string>
62 |     <key>count</key>
63 |     <dict>
64 |       <key>NSStringFormatSpecTypeKey</key>
65 |       <string>NSStringPluralRuleType</string>
66 |       <key>NSStringFormatValueTypeKey</key>
67 |       <string>d</string>
68 |       <key>zero</key>
69 |       <string>No items</string>
70 |       <key>one</key>
71 |       <string>One item</string>
72 |       <key>other</key>
73 |       <string>%d items</string>
74 |     </dict>
75 |   </dict>
76 | </dict>
77 | </plist>`;
78 |     default:
79 |       throw new Error(`Unsupported file extension: ${ext}`);
80 |   }
81 | }
82 | 
```

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

```typescript
 1 | import Z from "zod";
 2 | import pLimit from "p-limit";
 3 | 
 4 | export type ExecAsyncOptions = Z.infer<typeof ExecAsyncSchema>;
 5 | 
 6 | /**
 7 |  * Executes functions in parallel with a limit on concurrency and delay between each function call.
 8 |  *
 9 |  * The next function is called only after the delay has passed OR the previous function resolved, whichever is earlier (via race).
10 |  *
11 |  * During the execution, it calls `onProgress` function with the number of functions completed and total count.
12 |  *
13 |  * When all functions are executed, it returns an array of results.
14 |  * @param fns Array of async functions
15 |  * @param options Options
16 |  */
17 | export async function execAsync(
18 |   fns: (() => Promise<any>)[],
19 |   options: ExecAsyncOptions = ExecAsyncSchema.parse({}),
20 | ) {
21 |   const limit = pLimit(options.concurrency);
22 |   const limitedFns = fns.map((fn) => () => limit(fn));
23 | 
24 |   const resultPromises: Promise<any>[] = [];
25 | 
26 |   let completedCount = 0;
27 |   options.onProgress?.(completedCount, limitedFns.length);
28 | 
29 |   for (let i = 0; i < limitedFns.length; i++) {
30 |     const fn = limitedFns[i];
31 |     const resultPromise = fn().then((result) => {
32 |       completedCount++;
33 |       options.onProgress?.(completedCount, limitedFns.length);
34 |       return result;
35 |     });
36 |     resultPromises.push(resultPromise);
37 | 
38 |     await Promise.race([resultPromise, delay(options.delay)]);
39 |   }
40 | 
41 |   const results = await Promise.all(resultPromises);
42 |   return results;
43 | }
44 | 
45 | export type ExecWithRetryOptions = Z.infer<typeof ExecWithRetrySchema>;
46 | 
47 | export async function execWithRetry(
48 |   fn: () => Promise<any>,
49 |   options: ExecWithRetryOptions = ExecWithRetrySchema.parse({}),
50 | ) {
51 |   let lastError: any;
52 | 
53 |   for (let i = 0; i < options.attempts; i++) {
54 |     try {
55 |       return await fn();
56 |     } catch (error: any) {
57 |       lastError = error;
58 |       await delay(options.delay);
59 |     }
60 |   }
61 | 
62 |   throw lastError;
63 | }
64 | 
65 | // Helpers
66 | 
67 | const ExecAsyncSchema = Z.object({
68 |   delay: Z.number().nonnegative().default(1000),
69 |   concurrency: Z.number().positive().default(1),
70 |   onProgress: Z.function(
71 |     Z.tuple([
72 |       Z.number().positive(), // completed count
73 |       Z.number().positive(), // total count
74 |     ]),
75 |     Z.void(),
76 |   ).optional(),
77 | });
78 | 
79 | const ExecWithRetrySchema = Z.object({
80 |   delay: Z.number().nonnegative().default(0),
81 |   attempts: Z.number().positive().default(3),
82 | });
83 | 
84 | function delay(ms: number) {
85 |   return new Promise((resolve) => setTimeout(resolve, ms));
86 | }
87 | 
```

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

```typescript
 1 | import { describe, it, expect, vi } from "vitest";
 2 | import { render } from "@testing-library/react";
 3 | import React from "react";
 4 | import { LingoAttributeComponent } from "./attribute-component";
 5 | 
 6 | describe("core/attribute-component", () => {
 7 |   describe("LingoAttributeComponent", () => {
 8 |     const dictionary = {
 9 |       files: {
10 |         messages: {
11 |           entries: {
12 |             title: "Localized Title",
13 |             hrefVal: "/localized",
14 |           },
15 |         },
16 |       },
17 |     } as const;
18 | 
19 |     it("maps attributes from dictionary entries and falls back to attribute key when missing", () => {
20 |       const { container } = render(
21 |         <LingoAttributeComponent
22 |           $dictionary={dictionary}
23 |           $as="a"
24 |           $fileKey="messages"
25 |           $attributes={{
26 |             title: "title",
27 |             href: "hrefVal",
28 |             // not in dictionary -> falls back to attribute name (data-test)
29 |             "data-test": "missingEntry",
30 |           }}
31 |         />,
32 |       );
33 | 
34 |       const a = container.querySelector("a")!;
35 |       expect(a.getAttribute("title")).toBe("Localized Title");
36 |       expect(a.getAttribute("href")).toBe("/localized");
37 |       // fallback uses the attribute key name
38 |       expect(a.getAttribute("data-test")).toBe("data-test");
39 |     });
40 | 
41 |     it("passes through arbitrary props and forwards ref", () => {
42 |       const ref = { current: null as HTMLButtonElement | null };
43 |       const { container } = render(
44 |         <LingoAttributeComponent
45 |           $dictionary={{}}
46 |           $as="button"
47 |           $fileKey="messages"
48 |           $attributes={{}}
49 |           id="my-btn"
50 |           className="primary"
51 |           ref={(el) => (ref.current = el)}
52 |         />,
53 |       );
54 | 
55 |       const btn = container.querySelector("button")!;
56 |       expect(btn.id).toBe("my-btn");
57 |       expect(btn.className).toContain("primary");
58 |       expect(ref.current).toBe(btn);
59 |     });
60 | 
61 |     it("does not leak $fileKey as a DOM attribute", () => {
62 |       const { container } = render(
63 |         <LingoAttributeComponent
64 |           $dictionary={dictionary}
65 |           $as="div"
66 |           $fileKey="messages"
67 |           $attributes={{}}
68 |           data-testid="host"
69 |         />,
70 |       );
71 | 
72 |       const host = container.querySelector("div[data-testid='host']")!;
73 |       // $fileKey should not be present as a plain attribute
74 |       expect(host.getAttribute("$fileKey")).toBeNull();
75 |     });
76 |   });
77 | });
78 | 
```

--------------------------------------------------------------------------------
/DEBUGGING.md:
--------------------------------------------------------------------------------

```markdown
 1 | # Debugging the Lingo.dev Compiler
 2 | 
 3 | Lingo.dev Compiler is in active development. We use it ourselves and strive to provide the best developer experience for all supported frameworks. However, every project is unique and may present its own challenges.
 4 | 
 5 | This guide will help you debug the Compiler locally in your project.
 6 | 
 7 | ---
 8 | 
 9 | ## Getting Started: Local Setup
10 | 
11 | ### 1. Clone, Install, and Build
12 | 
13 | ```bash
14 | git clone [email protected]:lingodotdev/lingo.dev.git
15 | cd lingo.dev
16 | pnpm install
17 | pnpm build
18 | ```
19 | 
20 | Lingo.dev uses [pnpm](https://pnpm.io/) as its package manager.
21 | 
22 | ### 2. Link the CLI Package
23 | 
24 | In the `lingo.dev/packages/cli` directory, link the CLI package using your project's package manager:
25 | 
26 | ```bash
27 | npm link
28 | # or
29 | yarn link
30 | # or
31 | pnpm link
32 | ```
33 | 
34 | Use the package manager that matches your project (npm, yarn, or pnpm).
35 | 
36 | ### 3. Watch for Changes
37 | 
38 | To enable live-reloading for development, run the following in the root of the `lingo.dev` repo:
39 | 
40 | ```bash
41 | pnpm --filter "./packages/{compiler,react}" dev
42 | ```
43 | 
44 | ---
45 | 
46 | ## Using Your Local Build in Your Project
47 | 
48 | ### 1. Install Lingo.dev
49 | 
50 | If you haven't already, add Lingo.dev to your project:
51 | 
52 | ```bash
53 | npm install lingo.dev
54 | ```
55 | 
56 | For full setup and configuration, see the [Lingo.dev Compiler docs](https://lingo.dev/compiler).
57 | 
58 | ### 2. Link Your Local Library
59 | 
60 | ```bash
61 | npm link lingo.dev
62 | ```
63 | 
64 | ### 3. Build Your Project
65 | 
66 | For local development and testing with the Lingo.dev Compiler:
67 | 
68 | ```bash
69 | npm run dev
70 | ```
71 | 
72 | The exact command may vary depending on your project setup and package manager.
73 | 
74 | ---
75 | 
76 | ## Debugging Tips
77 | 
78 | You can now use your debugger or classic `console.log` statements in the Compiler and React packages to inspect what happens during your project build.
79 | 
80 | - The Compiler entry point is at `packages/compiler/src/index.ts`.
81 | - The `load` method:
82 |   - Loads and generates `lingo/dictionary.js` using `LCPServer.loadDictionaries`.
83 | - The `transform` method:
84 |   - Applies mutations to process source code (see methods in `composeMutations`).
85 |   - Generates `lingo/meta.json` based on the translatable content in your app.
86 |   - Injects Compiler-related components (`LingoComponent`, `LingoAttributeComponent`).
87 |   - Replaces the `loadDictionary` method with `loadDictionary_internal`.
88 | 
89 | ---
90 | 
91 | For more details, check out the [Lingo.dev Compiler documentation](https://lingo.dev/compiler) or [join our Discord](https://lingo.dev/go/discord) for help!
92 | 
```

--------------------------------------------------------------------------------
/packages/cli/src/cli/loaders/mdx2/section-split.spec.ts:
--------------------------------------------------------------------------------

```typescript
 1 | // #region Imports
 2 | import { describe, it, expect } from "vitest";
 3 | import createMdxSectionSplitLoader from "./section-split";
 4 | import dedent from "dedent";
 5 | // #endregion
 6 | 
 7 | describe("mdx section split loader", () => {
 8 |   const sampleMdxContent = dedent`
 9 |   ## Heading One
10 |   Some paragraph text.
11 |   
12 |   <CustomComponent foo="bar" />
13 | 
14 |   <AnotherComponent className="some-class">
15 |   <AnotherInnerComponent>
16 |   Some content inside another component.
17 |   </AnotherInnerComponent>
18 |   </AnotherComponent>
19 |   
20 |   ### Sub Heading
21 |   More text here.
22 |   `;
23 | 
24 |   it("should split content into section map keyed by index", async () => {
25 |     const loader = createMdxSectionSplitLoader();
26 |     loader.setDefaultLocale("en");
27 | 
28 |     const result = await loader.pull("en", {
29 |       frontmatter: {},
30 |       codePlaceholders: {},
31 |       content: sampleMdxContent,
32 |     });
33 | 
34 |     // Build expected segments
35 |     const seg0 = "## Heading One\nSome paragraph text.";
36 |     const seg1 = '<CustomComponent foo="bar" />';
37 |     const seg2 = '<AnotherComponent className="some-class">';
38 |     const seg3 = "<AnotherInnerComponent>";
39 |     const seg4 = "Some content inside another component.";
40 |     const seg5 = "</AnotherInnerComponent>";
41 |     const seg6 = "</AnotherComponent>";
42 |     const seg7 = "### Sub Heading\nMore text here.";
43 | 
44 |     const expected = {
45 |       "0": seg0,
46 |       "1": seg1,
47 |       "2": seg2,
48 |       "3": seg3,
49 |       "4": seg4,
50 |       "5": seg5,
51 |       "6": seg6,
52 |       "7": seg7,
53 |     };
54 | 
55 |     expect(result.sections).toEqual(expected);
56 |   });
57 | 
58 |   it("should merge sections back into MDX content on push", async () => {
59 |     const loader = createMdxSectionSplitLoader();
60 |     loader.setDefaultLocale("en");
61 | 
62 |     // First pull to split the sample content into sections
63 |     const pulled = await loader.pull("en", {
64 |       frontmatter: {},
65 |       codePlaceholders: {},
66 |       content: sampleMdxContent,
67 |     });
68 | 
69 |     // Push to merge the sections back into MDX
70 |     const pushed = await loader.push("en", {
71 |       ...pulled,
72 |       sections: {
73 |         ...pulled.sections,
74 |         "4": "Hello world!",
75 |       },
76 |     });
77 | 
78 |     const expectedContent = dedent`
79 |     ## Heading One
80 |     Some paragraph text.
81 | 
82 |     <CustomComponent foo="bar" />
83 |     <AnotherComponent className="some-class">
84 |     <AnotherInnerComponent>
85 |     Hello world!
86 |     </AnotherInnerComponent>
87 |     </AnotherComponent>
88 | 
89 |     ### Sub Heading
90 |     More text here.
91 |     `;
92 | 
93 |     expect(pushed.content).toBe(expectedContent);
94 |   });
95 | });
96 | 
```

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

```typescript
 1 | import { ILoader } from "./_types";
 2 | import { createLoader } from "./_utils";
 3 | import { fromString } from "php-array-reader";
 4 | 
 5 | export default function createPhpLoader(): ILoader<
 6 |   string,
 7 |   Record<string, any>
 8 | > {
 9 |   return createLoader({
10 |     pull: async (locale, input) => {
11 |       try {
12 |         const output = fromString(input);
13 |         return output;
14 |       } catch (error) {
15 |         throw new Error(`Error parsing PHP file for locale ${locale}`);
16 |       }
17 |     },
18 |     push: async (locale, data, originalInput) => {
19 |       const output = toPhpString(data, originalInput);
20 |       return output;
21 |     },
22 |   });
23 | }
24 | 
25 | function toPhpString(
26 |   data: Record<string, any>,
27 |   originalPhpString: string | null,
28 | ) {
29 |   const defaultFilePrefix = "<?php\n\n";
30 |   if (originalPhpString) {
31 |     const [filePrefix = defaultFilePrefix] = originalPhpString.split("return ");
32 |     const shortArraySyntax = !originalPhpString.includes("array(");
33 |     const output = `${filePrefix}return ${toPhpArray(data, shortArraySyntax)};`;
34 |     return output;
35 |   }
36 |   return `${defaultFilePrefix}return ${toPhpArray(data)};`;
37 | }
38 | 
39 | function toPhpArray(data: any, shortSyntax = true, indentLevel = 1): string {
40 |   if (data === null || data === undefined) {
41 |     return "null";
42 |   }
43 |   if (typeof data === "string") {
44 |     return `'${escapePhpString(data)}'`;
45 |   }
46 |   if (typeof data === "number") {
47 |     return data.toString();
48 |   }
49 |   if (typeof data === "boolean") {
50 |     return data ? "true" : "false";
51 |   }
52 | 
53 |   const arrayStart = shortSyntax ? "[" : "array(";
54 |   const arrayEnd = shortSyntax ? "]" : ")";
55 | 
56 |   if (Array.isArray(data)) {
57 |     return `${arrayStart}\n${data
58 |       .map(
59 |         (value) =>
60 |           `${indent(indentLevel)}${toPhpArray(
61 |             value,
62 |             shortSyntax,
63 |             indentLevel + 1,
64 |           )}`,
65 |       )
66 |       .join(",\n")}\n${indent(indentLevel - 1)}${arrayEnd}`;
67 |   }
68 | 
69 |   const output = `${arrayStart}\n${Object.entries(data)
70 |     .map(
71 |       ([key, value]) =>
72 |         `${indent(indentLevel)}'${key}' => ${toPhpArray(
73 |           value,
74 |           shortSyntax,
75 |           indentLevel + 1,
76 |         )}`,
77 |     )
78 |     .join(",\n")}\n${indent(indentLevel - 1)}${arrayEnd}`;
79 |   return output;
80 | }
81 | 
82 | function indent(level: number) {
83 |   return "  ".repeat(level);
84 | }
85 | 
86 | function escapePhpString(str: string) {
87 |   return str
88 |     .replaceAll("\\", "\\\\")
89 |     .replaceAll("'", "\\'")
90 |     .replaceAll("\r", "\\r")
91 |     .replaceAll("\n", "\\n")
92 |     .replaceAll("\t", "\\t");
93 | }
94 | 
```

--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------

```yaml
 1 | name: Release
 2 | 
 3 | on:
 4 |   workflow_dispatch:
 5 |     inputs:
 6 |       skip_lingo:
 7 |         description: "Skip Lingo.dev step"
 8 |         type: "boolean"
 9 |         default: false
10 |   push:
11 |     branches:
12 |       - main
13 | 
14 | jobs:
15 |   release:
16 |     runs-on: ubuntu-latest
17 |     permissions:
18 |       contents: write
19 |       pull-requests: write
20 |     steps:
21 |       - name: Checkout
22 |         uses: actions/checkout@v4
23 | 
24 |       - name: Debug Permissions
25 |         run: |
26 |           ls -la
27 |           ls -la integrations/
28 |           ls -la integrations/directus/
29 | 
30 |       - name: Check for [skip i18n]
31 |         run: |
32 |           COMMIT_MESSAGE=$(git log -1 --pretty=%B)
33 |           if echo "$COMMIT_MESSAGE" | grep -iq '\[skip i18n\]'; then
34 |             echo "Skipping i18n checks due to [skip i18n] in commit message."
35 |             exit 0
36 |           fi
37 | 
38 |       - name: Use Node.js
39 |         uses: actions/setup-node@v2
40 |         with:
41 |           node-version: 20.12.2
42 | 
43 |       - name: Install pnpm
44 |         uses: pnpm/action-setup@v4
45 |         id: pnpm-install
46 |         with:
47 |           version: 9.12.3
48 |           run_install: false
49 | 
50 |       - name: Configure pnpm cache
51 |         id: pnpm-cache
52 |         run: echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
53 |       - uses: actions/cache@v3
54 |         with:
55 |           path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
56 |           key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
57 |           restore-keys: |
58 |             ${{ runner.os }}-pnpm-store-
59 | 
60 |       - name: Install deps
61 |         run: pnpm install
62 | 
63 |       - name: Lingo.dev
64 |         if: ${{ !inputs.skip_lingo }}
65 |         uses: ./
66 |         with:
67 |           api-key: ${{ secrets.LINGODOTDEV_API_KEY }}
68 |           pull-request: true
69 |           parallel: true
70 |         env:
71 |           GH_TOKEN: ${{ github.token }}
72 | 
73 |       - name: Setup
74 |         run: |
75 |           pnpm turbo telemetry disable
76 | 
77 |       - name: Configure Turbo cache
78 |         uses: dtinth/setup-github-actions-caching-for-turbo@v1
79 | 
80 |       - name: Build
81 |         run: pnpm turbo build --force
82 | 
83 |       - name: Test
84 |         run: pnpm turbo test --force
85 | 
86 |       - name: Create Release Pull Request or Publish to npm
87 |         id: changesets
88 |         uses: changesets/action@v1
89 |         with:
90 |           title: "chore: bump package versions"
91 |           version: pnpm changeset version
92 |           publish: pnpm changeset publish
93 |           commit: "chore: bump package version"
94 |         env:
95 |           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
96 |           NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
97 | 
```

--------------------------------------------------------------------------------
/demo/react-router-app/app/root.tsx:
--------------------------------------------------------------------------------

```typescript
 1 | import {
 2 |   isRouteErrorResponse,
 3 |   Links,
 4 |   Meta,
 5 |   Outlet,
 6 |   Scripts,
 7 |   ScrollRestoration,
 8 | } from "react-router";
 9 | import type { LoaderFunctionArgs } from "react-router";
10 | import { useLoaderData } from "react-router";
11 | 
12 | import type { Route } from "./+types/root";
13 | import "./app.css";
14 | 
15 | // Compiler: imports
16 | import { LingoProvider, LocaleSwitcher } from "lingo.dev/react/client";
17 | import { loadDictionary } from "lingo.dev/react/react-router";
18 | 
19 | export const links: Route.LinksFunction = () => [
20 |   { rel: "preconnect", href: "https://fonts.googleapis.com" },
21 |   {
22 |     rel: "preconnect",
23 |     href: "https://fonts.gstatic.com",
24 |     crossOrigin: "anonymous",
25 |   },
26 |   {
27 |     rel: "stylesheet",
28 |     href: "https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap",
29 |   },
30 | ];
31 | 
32 | export async function loader(args: LoaderFunctionArgs) {
33 |   return { lingoDictionary: await loadDictionary(args.request) };
34 | }
35 | 
36 | export function Layout(props: { children: React.ReactNode }) {
37 |   const loaderData = useLoaderData<typeof loader>();
38 |   return (
39 |     <LingoProvider dictionary={loaderData?.lingoDictionary}>
40 |       <html lang="en">
41 |         <head>
42 |           <meta charSet="utf-8" />
43 |           <meta name="viewport" content="width=device-width, initial-scale=1" />
44 |           <Meta />
45 |           <Links />
46 |         </head>
47 |         <body>
48 |           {props.children}
49 |           <ScrollRestoration />
50 |           <Scripts />
51 |           <div className="absolute top-2 right-3">
52 |             <LocaleSwitcher locales={["en", "es", "fr", "de"]} />
53 |           </div>
54 |         </body>
55 |       </html>
56 |     </LingoProvider>
57 |   );
58 | }
59 | 
60 | export default function App() {
61 |   return <Outlet />;
62 | }
63 | 
64 | export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
65 |   let message = "Oops!";
66 |   let details = "An unexpected error occurred.";
67 |   let stack: string | undefined;
68 | 
69 |   if (isRouteErrorResponse(error)) {
70 |     message = error.status === 404 ? "404" : "Error";
71 |     details =
72 |       error.status === 404
73 |         ? "The requested page could not be found."
74 |         : error.statusText || details;
75 |   } else if (import.meta.env.DEV && error && error instanceof Error) {
76 |     details = error.message;
77 |     stack = error.stack;
78 |   }
79 | 
80 |   return (
81 |     <main className="pt-16 p-4 container mx-auto">
82 |       <h1>{message}</h1>
83 |       <p>{details}</p>
84 |       {stack && (
85 |         <pre className="w-full p-4 overflow-x-auto">
86 |           <code>{stack}</code>
87 |         </pre>
88 |       )}
89 |     </main>
90 |   );
91 | }
92 | 
```

--------------------------------------------------------------------------------
/packages/cli/src/cli/loaders/json-sorting.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { describe, it, expect } from "vitest";
  2 | 
  3 | import createJsonSortingLoader from "./json-sorting";
  4 | 
  5 | describe("JSON Sorting Loader", () => {
  6 |   const loader = createJsonSortingLoader();
  7 |   loader.setDefaultLocale("en");
  8 | 
  9 |   describe("pull", () => {
 10 |     it("should return input unchanged", async () => {
 11 |       const input = { b: 1, a: 2 };
 12 |       const result = await loader.pull("en", input);
 13 |       expect(result).toEqual(input);
 14 |     });
 15 |   });
 16 | 
 17 |   describe("push", () => {
 18 |     it("should sort object keys at root level", async () => {
 19 |       const input = { zebra: 1, apple: 2, banana: 3 };
 20 |       const expected = { apple: 2, banana: 3, zebra: 1 };
 21 | 
 22 |       const result = await loader.push("en", input);
 23 |       expect(result).toEqual(expected);
 24 |     });
 25 | 
 26 |     it("should sort nested object keys", async () => {
 27 |       const input = {
 28 |         b: {
 29 |           z: 1,
 30 |           y: 2,
 31 |           x: 3,
 32 |         },
 33 |         a: 1,
 34 |       };
 35 |       const expected = {
 36 |         a: 1,
 37 |         b: {
 38 |           x: 3,
 39 |           y: 2,
 40 |           z: 1,
 41 |         },
 42 |       };
 43 | 
 44 |       const result = await loader.push("en", input);
 45 |       expect(result).toEqual(expected);
 46 |     });
 47 | 
 48 |     it("should handle arrays by sorting their object elements", async () => {
 49 |       const input = {
 50 |         items: [
 51 |           { b: 1, a: 2 },
 52 |           { d: 3, c: 4 },
 53 |         ],
 54 |       };
 55 |       const expected = {
 56 |         items: [
 57 |           { a: 2, b: 1 },
 58 |           { c: 4, d: 3 },
 59 |         ],
 60 |       };
 61 | 
 62 |       const result = await loader.push("en", input);
 63 |       expect(result).toEqual(expected);
 64 |     });
 65 | 
 66 |     it("should handle mixed nested structures", async () => {
 67 |       const input = {
 68 |         zebra: [
 69 |           { beta: 2, alpha: 1 },
 70 |           { delta: 4, gamma: 3 },
 71 |         ],
 72 |         apple: {
 73 |           two: 2,
 74 |           one: 1,
 75 |         },
 76 |       };
 77 |       const expected = {
 78 |         apple: {
 79 |           one: 1,
 80 |           two: 2,
 81 |         },
 82 |         zebra: [
 83 |           { alpha: 1, beta: 2 },
 84 |           { delta: 4, gamma: 3 },
 85 |         ],
 86 |       };
 87 | 
 88 |       const result = await loader.push("en", input);
 89 |       expect(result).toEqual(expected);
 90 |     });
 91 | 
 92 |     it("should handle null and primitive values", async () => {
 93 |       const input = {
 94 |         c: null,
 95 |         b: "string",
 96 |         a: 123,
 97 |         d: true,
 98 |       };
 99 |       const expected = {
100 |         a: 123,
101 |         b: "string",
102 |         c: null,
103 |         d: true,
104 |       };
105 | 
106 |       const result = await loader.push("en", input);
107 |       expect(result).toEqual(expected);
108 |     });
109 |   });
110 | });
111 | 
```

--------------------------------------------------------------------------------
/packages/cli/src/cli/utils/plutil-formatter.spec.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { describe, it, expect } from "vitest";
  2 | import { formatPlutilStyle } from "./plutil-formatter";
  3 | 
  4 | describe("plutil-formatter", () => {
  5 |   it("should format JSON matching Xcode style with existing indentation", () => {
  6 |     const existingJson = `{
  7 |     "sourceLanguage" : "en",
  8 |     "strings" : {
  9 |         "something" : {
 10 | 
 11 |         }
 12 |     }
 13 | }`;
 14 | 
 15 |     const input = {
 16 |       sourceLanguage: "en",
 17 |       strings: {
 18 |         complex_format: {
 19 |           extractionState: "manual",
 20 |           localizations: {
 21 |             ar: {
 22 |               stringUnit: {
 23 |                 state: "translated",
 24 |                 value: "المستخدم %1$@ لديه %2$lld نقطة ورصيد $%3$.2f",
 25 |               },
 26 |             },
 27 |           },
 28 |         },
 29 |         something: {},
 30 |       },
 31 |       version: "1.0",
 32 |     };
 33 | 
 34 |     const expected = `{
 35 |     "sourceLanguage" : "en",
 36 |     "strings" : {
 37 |         "complex_format" : {
 38 |             "extractionState" : "manual",
 39 |             "localizations" : {
 40 |                 "ar" : {
 41 |                     "stringUnit" : {
 42 |                         "state" : "translated",
 43 |                         "value" : "المستخدم %1$@ لديه %2$lld نقطة ورصيد $%3$.2f"
 44 |                     }
 45 |                 }
 46 |             }
 47 |         },
 48 |         "something" : {
 49 | 
 50 |         }
 51 |     },
 52 |     "version" : "1.0"
 53 | }`;
 54 |     const result = formatPlutilStyle(input, existingJson);
 55 | 
 56 |     expect(result).toBe(expected);
 57 |   });
 58 | 
 59 |   it("should detect and use 4-space indentation", () => {
 60 |     const existingJson = `{
 61 |     "foo": {
 62 |         "bar": {
 63 |         }
 64 |     }
 65 | }`;
 66 | 
 67 |     const input = {
 68 |       test: {
 69 |         nested: {},
 70 |       },
 71 |     };
 72 | 
 73 |     const expected = `{
 74 |     "test" : {
 75 |         "nested" : {
 76 | 
 77 |         }
 78 |     }
 79 | }`;
 80 | 
 81 |     const result = formatPlutilStyle(input, existingJson);
 82 |     expect(result).toBe(expected);
 83 |   });
 84 | 
 85 |   it("should fallback to 2 spaces when no existing JSON provided", () => {
 86 |     const input = {
 87 |       foo: {
 88 |         bar: {},
 89 |       },
 90 |     };
 91 | 
 92 |     const expected = `{
 93 |   "foo" : {
 94 |     "bar" : {
 95 | 
 96 |     }
 97 |   }
 98 | }`;
 99 | 
100 |     const result = formatPlutilStyle(input);
101 |     expect(result).toBe(expected);
102 |   });
103 | 
104 |   it("should order keys correctly", () => {
105 |     const input = {
106 |       "2x": {},
107 |       "3ABC": {},
108 |       "3x": {},
109 |       "4K": {},
110 |       "7 min": {},
111 |       "25": {},
112 |       "30": {},
113 |     };
114 | 
115 |     const expected = `{
116 |   "2x" : {
117 | 
118 |   },
119 |   "3ABC" : {
120 | 
121 |   },
122 |   "3x" : {
123 | 
124 |   },
125 |   "4K" : {
126 | 
127 |   },
128 |   "7 min" : {
129 | 
130 |   },
131 |   "25" : {
132 | 
133 |   },
134 |   "30" : {
135 | 
136 |   }
137 | }`;
138 | 
139 |     const result = formatPlutilStyle(input);
140 |     expect(result).toBe(expected);
141 |   });
142 | });
143 | 
```

--------------------------------------------------------------------------------
/scripts/docs/src/utils.test.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
 2 | import {
 3 |   getRepoRoot,
 4 |   getGitHubToken,
 5 |   getGitHubRepo,
 6 |   getGitHubOwner,
 7 |   getGitHubPRNumber,
 8 | } from "./utils";
 9 | 
10 | describe("utils", () => {
11 |   beforeEach(() => {
12 |     vi.clearAllMocks();
13 |   });
14 | 
15 |   afterEach(() => {
16 |     vi.unstubAllEnvs();
17 |   });
18 | 
19 |   describe("getRepoRoot", () => {
20 |     it("should find the repository root directory", () => {
21 |       const result = getRepoRoot();
22 |       expect(result).toBeDefined();
23 |       expect(typeof result).toBe("string");
24 |       expect(result.length).toBeGreaterThan(0);
25 |     });
26 |   });
27 | 
28 |   describe("getGitHubToken", () => {
29 |     it("should return token when GITHUB_TOKEN is set", () => {
30 |       vi.stubEnv("GITHUB_TOKEN", "test-token");
31 | 
32 |       const result = getGitHubToken();
33 |       expect(result).toBe("test-token");
34 |     });
35 | 
36 |     it("should throw error when GITHUB_TOKEN is not set", () => {
37 |       vi.stubEnv("GITHUB_TOKEN", "");
38 | 
39 |       expect(() => getGitHubToken()).toThrow(
40 |         "GITHUB_TOKEN environment variable is required.",
41 |       );
42 |     });
43 |   });
44 | 
45 |   describe("getGitHubRepo", () => {
46 |     it("should extract repo name from GITHUB_REPOSITORY", () => {
47 |       vi.stubEnv("GITHUB_REPOSITORY", "owner/repo-name");
48 | 
49 |       const result = getGitHubRepo();
50 |       expect(result).toBe("repo-name");
51 |     });
52 | 
53 |     it("should throw error when GITHUB_REPOSITORY is not set", () => {
54 |       vi.stubEnv("GITHUB_REPOSITORY", "");
55 | 
56 |       expect(() => getGitHubRepo()).toThrow(
57 |         "GITHUB_REPOSITORY environment variable is missing.",
58 |       );
59 |     });
60 |   });
61 | 
62 |   describe("getGitHubOwner", () => {
63 |     it("should extract owner from GITHUB_REPOSITORY", () => {
64 |       vi.stubEnv("GITHUB_REPOSITORY", "test-owner/repo-name");
65 | 
66 |       const result = getGitHubOwner();
67 |       expect(result).toBe("test-owner");
68 |     });
69 | 
70 |     it("should throw error when GITHUB_REPOSITORY is not set", () => {
71 |       vi.stubEnv("GITHUB_REPOSITORY", "");
72 | 
73 |       expect(() => getGitHubOwner()).toThrow(
74 |         "GITHUB_REPOSITORY environment variable is missing.",
75 |       );
76 |     });
77 |   });
78 | 
79 |   describe("getGitHubPRNumber", () => {
80 |     it("should return PR_NUMBER when set", () => {
81 |       vi.stubEnv("PR_NUMBER", "123");
82 | 
83 |       const result = getGitHubPRNumber();
84 |       expect(result).toBe(123);
85 |     });
86 | 
87 |     it("should throw error when no PR number can be determined", () => {
88 |       vi.stubEnv("PR_NUMBER", "");
89 |       vi.stubEnv("GITHUB_EVENT_PATH", "");
90 | 
91 |       expect(() => getGitHubPRNumber()).toThrow(
92 |         "Could not determine pull request number.",
93 |       );
94 |     });
95 |   });
96 | });
97 | 
```

--------------------------------------------------------------------------------
/packages/react/src/client/provider.spec.tsx:
--------------------------------------------------------------------------------

```typescript
 1 | import { describe, it, expect, vi, beforeEach } from "vitest";
 2 | import { render, screen, waitFor } from "@testing-library/react";
 3 | import React from "react";
 4 | import { LingoProvider, LingoProviderWrapper } from "./provider";
 5 | import { LingoContext } from "./context";
 6 | 
 7 | vi.mock("./utils", async (orig) => {
 8 |   const actual = await orig();
 9 |   return {
10 |     ...(actual as any),
11 |     getLocaleFromCookies: vi.fn(() => "en"),
12 |   };
13 | });
14 | 
15 | describe("client/provider", () => {
16 |   beforeEach(() => {
17 |     vi.clearAllMocks();
18 |   });
19 | 
20 |   describe("LingoProvider", () => {
21 |     it("throws when dictionary is missing", () => {
22 |       expect(() =>
23 |         render(
24 |           <LingoProvider dictionary={undefined as any}>
25 |             <div />
26 |           </LingoProvider>,
27 |         ),
28 |       ).toThrowError(/dictionary is not provided/i);
29 |     });
30 | 
31 |     it("provides dictionary via context", () => {
32 |       const dict = { locale: "en", files: {} };
33 |       const Probe = () => {
34 |         return (
35 |           <LingoContext.Consumer>
36 |             {(value) => (
37 |               <div data-testid="probe" data-locale={value.dictionary.locale} />
38 |             )}
39 |           </LingoContext.Consumer>
40 |         );
41 |       };
42 | 
43 |       render(
44 |         <LingoProvider dictionary={dict}>
45 |           <Probe />
46 |         </LingoProvider>,
47 |       );
48 | 
49 |       const el = screen.getByTestId("probe");
50 |       expect(el.getAttribute("data-locale")).toBe("en");
51 |     });
52 |   });
53 | 
54 |   describe("LingoProviderWrapper", () => {
55 |     it("loads dictionary and renders children; returns null while loading", async () => {
56 |       const loadDictionary = vi
57 |         .fn()
58 |         .mockResolvedValue({ locale: "en", files: {} });
59 | 
60 |       const Child = () => <div data-testid="child">ok</div>;
61 | 
62 |       const { container, findByTestId } = render(
63 |         <LingoProviderWrapper loadDictionary={loadDictionary}>
64 |           <Child />
65 |         </LingoProviderWrapper>,
66 |       );
67 | 
68 |       // initially null during loading
69 |       expect(container.firstChild).toBeNull();
70 | 
71 |       await waitFor(() => expect(loadDictionary).toHaveBeenCalled());
72 |       const child = await findByTestId("child");
73 |       expect(child != null).toBe(true);
74 |     });
75 | 
76 |     it("swallows load errors and stays null", async () => {
77 |       const loadDictionary = vi.fn().mockRejectedValue(new Error("boom"));
78 |       const { container } = render(
79 |         <LingoProviderWrapper loadDictionary={loadDictionary}>
80 |           <div />
81 |         </LingoProviderWrapper>,
82 |       );
83 | 
84 |       await vi.waitFor(() => expect(loadDictionary).toHaveBeenCalled());
85 |       expect(container.firstChild).toBeNull();
86 |     });
87 |   });
88 | });
89 | 
```

--------------------------------------------------------------------------------
/packages/cli/src/cli/cmd/may-the-fourth.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { Command } from "interactive-commander";
 2 | import * as cp from "node:child_process";
 3 | import figlet from "figlet";
 4 | import chalk from "chalk";
 5 | import { vice } from "gradient-string";
 6 | import { setTimeout } from "node:timers/promises";
 7 | 
 8 | export const colors = {
 9 |   orange: "#ff6600",
10 |   green: "#6ae300",
11 |   blue: "#0090ff",
12 |   yellow: "#ffcc00",
13 |   grey: "#808080",
14 |   red: "#ff0000",
15 | };
16 | 
17 | export default new Command()
18 |   .command("may-the-fourth")
19 |   .description("May the Fourth be with you")
20 |   .helpOption("-h, --help", "Show help")
21 |   .action(async () => {
22 |     await renderClear();
23 |     await renderBanner();
24 |     await renderSpacer();
25 | 
26 |     console.log(chalk.hex(colors.yellow)("Loading the Star Wars movie..."));
27 |     await renderSpacer();
28 | 
29 |     await new Promise<void>((resolve, reject) => {
30 |       const ssh = cp.spawn("ssh", ["starwarstel.net"], {
31 |         stdio: "inherit",
32 |       });
33 | 
34 |       ssh.on("close", (code) => {
35 |         if (code !== 0) {
36 |           console.error(`SSH process exited with code ${code}`);
37 |           // Optionally reject the promise if the exit code is non-zero
38 |           // reject(new Error(`SSH process exited with code ${code}`));
39 |         }
40 |         resolve(); // Resolve the promise when SSH closes
41 |       });
42 | 
43 |       ssh.on("error", (err) => {
44 |         console.error("Failed to start SSH process:", err);
45 |         reject(err); // Reject the promise on error
46 |       });
47 |     });
48 | 
49 |     // This code now runs after the SSH process has finished
50 |     await renderSpacer();
51 |     console.log(
52 |       `${chalk.hex(colors.green)("We hope you enjoyed it! :)")} ${chalk.hex(
53 |         colors.blue,
54 |       )("May the Fourth be with you! 🚀")}`,
55 |     );
56 |     await renderSpacer();
57 |     console.log(chalk.dim(`---`));
58 |     await renderSpacer();
59 |     await renderHero();
60 |   });
61 | 
62 | async function renderClear() {
63 |   console.log("\x1Bc");
64 | }
65 | 
66 | async function renderSpacer() {
67 |   console.log(" ");
68 | }
69 | 
70 | async function renderBanner() {
71 |   console.log(
72 |     vice(
73 |       figlet.textSync("LINGO.DEV", {
74 |         font: "ANSI Shadow",
75 |         horizontalLayout: "default",
76 |         verticalLayout: "default",
77 |       }),
78 |     ),
79 |   );
80 | }
81 | 
82 | async function renderHero() {
83 |   console.log(
84 |     `⚡️ ${chalk.hex(colors.green)(
85 |       "Lingo.dev",
86 |     )} - open-source, AI-powered i18n CLI for web & mobile localization.`,
87 |   );
88 |   console.log(" ");
89 |   console.log(chalk.hex(colors.blue)("📚 Docs: https://lingo.dev/go/docs"));
90 |   console.log(
91 |     chalk.hex(colors.blue)("⭐ Star the repo: https://lingo.dev/go/gh"),
92 |   );
93 |   console.log(
94 |     chalk.hex(colors.blue)("🎮 Join Discord: https://lingo.dev/go/discord"),
95 |   );
96 | }
97 | 
```

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

```typescript
 1 | import fs from "fs";
 2 | import path from "path";
 3 | import Z from "zod";
 4 | import YAML from "yaml";
 5 | import { MD5 } from "object-hash";
 6 | import _ from "lodash";
 7 | 
 8 | export function createLockfileHelper() {
 9 |   return {
10 |     isLockfileExists: () => {
11 |       const lockfilePath = _getLockfilePath();
12 |       return fs.existsSync(lockfilePath);
13 |     },
14 |     registerSourceData: (
15 |       pathPattern: string,
16 |       sourceData: Record<string, any>,
17 |     ) => {
18 |       const lockfile = _loadLockfile();
19 | 
20 |       const sectionKey = MD5(pathPattern);
21 |       const sectionChecksums = _.mapValues(sourceData, (value) => MD5(value));
22 | 
23 |       lockfile.checksums[sectionKey] = sectionChecksums;
24 | 
25 |       _saveLockfile(lockfile);
26 |     },
27 |     registerPartialSourceData: (
28 |       pathPattern: string,
29 |       partialSourceData: Record<string, any>,
30 |     ) => {
31 |       const lockfile = _loadLockfile();
32 | 
33 |       const sectionKey = MD5(pathPattern);
34 |       const sectionChecksums = _.mapValues(partialSourceData, (value) =>
35 |         MD5(value),
36 |       );
37 | 
38 |       lockfile.checksums[sectionKey] = _.merge(
39 |         {},
40 |         lockfile.checksums[sectionKey] ?? {},
41 |         sectionChecksums,
42 |       );
43 | 
44 |       _saveLockfile(lockfile);
45 |     },
46 |     extractUpdatedData: (
47 |       pathPattern: string,
48 |       sourceData: Record<string, any>,
49 |     ) => {
50 |       const lockfile = _loadLockfile();
51 | 
52 |       const sectionKey = MD5(pathPattern);
53 |       const currentChecksums = _.mapValues(sourceData, (value) => MD5(value));
54 | 
55 |       const savedChecksums = lockfile.checksums[sectionKey] || {};
56 |       const updatedData = _.pickBy(
57 |         sourceData,
58 |         (value, key) => savedChecksums[key] !== currentChecksums[key],
59 |       );
60 | 
61 |       return updatedData;
62 |     },
63 |   };
64 | 
65 |   function _loadLockfile() {
66 |     const lockfilePath = _getLockfilePath();
67 |     if (!fs.existsSync(lockfilePath)) {
68 |       return LockfileSchema.parse({});
69 |     }
70 |     const content = fs.readFileSync(lockfilePath, "utf-8");
71 |     const result = LockfileSchema.parse(YAML.parse(content));
72 |     return result;
73 |   }
74 | 
75 |   function _saveLockfile(lockfile: Z.infer<typeof LockfileSchema>) {
76 |     const lockfilePath = _getLockfilePath();
77 |     const content = YAML.stringify(lockfile);
78 |     fs.writeFileSync(lockfilePath, content);
79 |   }
80 | 
81 |   function _getLockfilePath() {
82 |     return path.join(process.cwd(), "i18n.lock");
83 |   }
84 | }
85 | 
86 | const LockfileSchema = Z.object({
87 |   version: Z.literal(1).default(1),
88 |   checksums: Z.record(
89 |     Z.string(), // localizable files' keys
90 |     Z.record(
91 |       // checksums hashmap
92 |       Z.string(), // key
93 |       Z.string(), // checksum of the key's value in the source locale
94 |     ).default({}),
95 |   ).default({}),
96 | });
97 | 
```

--------------------------------------------------------------------------------
/packages/cli/src/cli/cmd/show/files.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { Command } from "interactive-commander";
 2 | import _ from "lodash";
 3 | import Ora from "ora";
 4 | import { getConfig } from "../../utils/config";
 5 | import { CLIError } from "../../utils/errors";
 6 | import { getBuckets } from "../../utils/buckets";
 7 | import { resolveOverriddenLocale } from "@lingo.dev/_spec";
 8 | 
 9 | export default new Command()
10 |   .command("files")
11 |   .description(
12 |     "Expand each bucket's path pattern into concrete source and target file paths",
13 |   )
14 |   .option(
15 |     "--source",
16 |     "Only list the source locale variant for each path pattern",
17 |   )
18 |   .option(
19 |     "--target",
20 |     "Only list the target locale variants for each configured locale",
21 |   )
22 |   .helpOption("-h, --help", "Show help")
23 |   .action(async (type) => {
24 |     const ora = Ora();
25 |     try {
26 |       try {
27 |         const i18nConfig = await getConfig();
28 | 
29 |         if (!i18nConfig) {
30 |           throw new CLIError({
31 |             message:
32 |               "i18n.json not found. Please run `lingo.dev init` to initialize the project.",
33 |             docUrl: "i18nNotFound",
34 |           });
35 |         }
36 | 
37 |         const buckets = getBuckets(i18nConfig);
38 |         for (const bucket of buckets) {
39 |           for (const bucketConfig of bucket.paths) {
40 |             const sourceLocale = resolveOverriddenLocale(
41 |               i18nConfig.locale.source,
42 |               bucketConfig.delimiter,
43 |             );
44 |             const sourcePath = bucketConfig.pathPattern.replace(
45 |               /\[locale\]/g,
46 |               sourceLocale,
47 |             );
48 |             const targetPaths = i18nConfig.locale.targets.map(
49 |               (_targetLocale) => {
50 |                 const targetLocale = resolveOverriddenLocale(
51 |                   _targetLocale,
52 |                   bucketConfig.delimiter,
53 |                 );
54 |                 return bucketConfig.pathPattern.replace(
55 |                   /\[locale\]/g,
56 |                   targetLocale,
57 |                 );
58 |               },
59 |             );
60 | 
61 |             const result: string[] = [];
62 |             if (!type.source && !type.target) {
63 |               result.push(sourcePath, ...targetPaths);
64 |             } else if (type.source) {
65 |               result.push(sourcePath);
66 |             } else if (type.target) {
67 |               result.push(...targetPaths);
68 |             }
69 | 
70 |             result.forEach((path) => {
71 |               console.log(path);
72 |             });
73 |           }
74 |         }
75 |       } catch (error: any) {
76 |         throw new CLIError({
77 |           message: `Failed to expand placeholdered globs: ${error.message}`,
78 |           docUrl: "placeHolderFailed",
79 |         });
80 |       }
81 |     } catch (error: any) {
82 |       ora.fail(error.message);
83 |       process.exit(1);
84 |     }
85 |   });
86 | 
```

--------------------------------------------------------------------------------
/packages/compiler/src/utils/jsx-expressions.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { parse } from "@babel/parser";
  2 | import traverse from "@babel/traverse";
  3 | import generate from "@babel/generator";
  4 | import { getJsxExpressions } from "./jsx-expressions";
  5 | import { describe, expect, it } from "vitest";
  6 | import * as t from "@babel/types";
  7 | 
  8 | function parseJSX(code: string) {
  9 |   return parse(code, {
 10 |     plugins: ["jsx"],
 11 |     sourceType: "module",
 12 |   });
 13 | }
 14 | 
 15 | describe("getJsxExpressions", () => {
 16 |   it("extracts simple expressions", () => {
 17 |     const ast = parseJSX("<div>{count + 1}</div>");
 18 |     let result;
 19 | 
 20 |     traverse(ast, {
 21 |       JSXElement(path) {
 22 |         result = getJsxExpressions(path);
 23 |         path.stop();
 24 |       },
 25 |     });
 26 | 
 27 |     expect(generate(result!).code).toBe("[count + 1]");
 28 |   });
 29 | 
 30 |   it("extracts multiple expressions", () => {
 31 |     const ast = parseJSX('<div>{count * 2} items in {category + "foo"}</div>');
 32 |     let result;
 33 | 
 34 |     traverse(ast, {
 35 |       JSXElement(path) {
 36 |         result = getJsxExpressions(path);
 37 |         path.stop();
 38 |       },
 39 |     });
 40 | 
 41 |     expect(generate(result!).code).toBe('[count * 2, category + "foo"]');
 42 |   });
 43 | 
 44 |   it("extracts complex expressions", () => {
 45 |     const ast = parseJSX('<div>{isAdmin ? "Admin" : user.role}</div>');
 46 |     let result;
 47 | 
 48 |     traverse(ast, {
 49 |       JSXElement(path) {
 50 |         result = getJsxExpressions(path);
 51 |         path.stop();
 52 |       },
 53 |     });
 54 | 
 55 |     expect(generate(result!).code).toBe('[isAdmin ? "Admin" : user.role]');
 56 |   });
 57 | 
 58 |   it("skips variables and member expressions", () => {
 59 |     const ast = parseJSX("<div>{count} items in {category.type}</div>");
 60 |     let result: t.ArrayExpression | undefined;
 61 | 
 62 |     traverse(ast, {
 63 |       JSXElement(path) {
 64 |         result = getJsxExpressions(path);
 65 |         path.stop();
 66 |       },
 67 |     });
 68 | 
 69 |     expect(generate(result!).code).toBe("[]");
 70 |   });
 71 | 
 72 |   it("skips function calls", () => {
 73 |     const ast = parseJSX("<div>{getName(user)} has {getCount()} items</div>");
 74 |     let result: t.ArrayExpression | undefined;
 75 | 
 76 |     traverse(ast, {
 77 |       JSXElement(path) {
 78 |         result = getJsxExpressions(path);
 79 |         path.stop();
 80 |       },
 81 |     });
 82 | 
 83 |     expect(generate(result!).code).toBe("[]");
 84 |   });
 85 | 
 86 |   it("extracts only expressions while skipping variables and functions", () => {
 87 |     const ast = parseJSX(
 88 |       '<div>{count + 1} by {user.name}, processed at {new Date().getTime() > 1000 ? "late" : "early"}</div>',
 89 |     );
 90 |     let result: t.ArrayExpression | undefined;
 91 | 
 92 |     traverse(ast, {
 93 |       JSXElement(path) {
 94 |         result = getJsxExpressions(path);
 95 |         path.stop();
 96 |       },
 97 |     });
 98 | 
 99 |     expect(generate(result!).code).toBe(
100 |       '[count + 1, new Date().getTime() > 1000 ? "late" : "early"]',
101 |     );
102 |   });
103 | });
104 | 
```

--------------------------------------------------------------------------------
/packages/compiler/src/jsx-fragment.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { createCodeMutation } from "./_base";
  2 | import traverse from "@babel/traverse";
  3 | import * as t from "@babel/types";
  4 | import { getOrCreateImport } from "./utils";
  5 | import { CompilerPayload } from "./_base";
  6 | 
  7 | export function jsxFragmentMutation(
  8 |   payload: CompilerPayload,
  9 | ): CompilerPayload | null {
 10 |   const { ast } = payload;
 11 | 
 12 |   let foundFragments = false;
 13 | 
 14 |   let fragmentImportName: string | null = null;
 15 | 
 16 |   traverse(ast, {
 17 |     ImportDeclaration(path) {
 18 |       if (path.node.source.value !== "react") return;
 19 | 
 20 |       for (const specifier of path.node.specifiers) {
 21 |         if (
 22 |           t.isImportSpecifier(specifier) &&
 23 |           t.isIdentifier(specifier.imported) &&
 24 |           specifier.imported.name === "Fragment"
 25 |         ) {
 26 |           fragmentImportName = specifier.local.name;
 27 |           path.stop();
 28 |         }
 29 |       }
 30 |     },
 31 |   });
 32 | 
 33 |   traverse(ast, {
 34 |     JSXFragment(path) {
 35 |       foundFragments = true;
 36 | 
 37 |       if (!fragmentImportName) {
 38 |         const result = getOrCreateImport(ast, {
 39 |           exportedName: "Fragment",
 40 |           moduleName: ["react"],
 41 |         });
 42 |         fragmentImportName = result.importedName;
 43 |       }
 44 | 
 45 |       const fragmentElement = t.jsxElement(
 46 |         t.jsxOpeningElement(t.jsxIdentifier(fragmentImportName), [], false),
 47 |         t.jsxClosingElement(t.jsxIdentifier(fragmentImportName)),
 48 |         path.node.children,
 49 |         false,
 50 |       );
 51 | 
 52 |       path.replaceWith(fragmentElement);
 53 |     },
 54 |   });
 55 | 
 56 |   return payload;
 57 | }
 58 | 
 59 | export function transformFragmentShorthand(ast: t.Node): boolean {
 60 |   let transformed = false;
 61 | 
 62 |   let fragmentImportName: string | null = null;
 63 | 
 64 |   traverse(ast, {
 65 |     ImportDeclaration(path) {
 66 |       if (path.node.source.value !== "react") return;
 67 | 
 68 |       for (const specifier of path.node.specifiers) {
 69 |         if (
 70 |           t.isImportSpecifier(specifier) &&
 71 |           t.isIdentifier(specifier.imported) &&
 72 |           specifier.imported.name === "Fragment"
 73 |         ) {
 74 |           fragmentImportName = specifier.local.name;
 75 |           path.stop();
 76 |         }
 77 |       }
 78 |     },
 79 |   });
 80 | 
 81 |   traverse(ast, {
 82 |     JSXFragment(path) {
 83 |       transformed = true;
 84 | 
 85 |       if (!fragmentImportName) {
 86 |         const result = getOrCreateImport(ast, {
 87 |           exportedName: "Fragment",
 88 |           moduleName: ["react"],
 89 |         });
 90 |         fragmentImportName = result.importedName;
 91 |       }
 92 | 
 93 |       const fragmentElement = t.jsxElement(
 94 |         t.jsxOpeningElement(t.jsxIdentifier(fragmentImportName), [], false),
 95 |         t.jsxClosingElement(t.jsxIdentifier(fragmentImportName)),
 96 |         path.node.children,
 97 |         false,
 98 |       );
 99 | 
100 |       path.replaceWith(fragmentElement);
101 |     },
102 |   });
103 | 
104 |   return transformed;
105 | }
106 | 
```

--------------------------------------------------------------------------------
/demo/vite-project/src/lingo/meta.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "version": 0.1,
 3 |   "files": {
 4 |     "App.tsx": {
 5 |       "scopes": {
 6 |         "7/body/1/argument/1/1/1-alt": {
 7 |           "type": "attribute",
 8 |           "hash": "4d15435212a4877c4226b33a8cae0488",
 9 |           "context": "",
10 |           "skip": false,
11 |           "overrides": {},
12 |           "content": "Vite logo"
13 |         },
14 |         "7/body/1/argument/1/3/1-alt": {
15 |           "type": "attribute",
16 |           "hash": "b162441c9d07514a1c5124f9be178248",
17 |           "context": "",
18 |           "skip": false,
19 |           "overrides": {},
20 |           "content": "React logo"
21 |         },
22 |         "7/body/1/argument/3": {
23 |           "type": "element",
24 |           "hash": "dd6ddb089b0748e6c88b28c56c643d02",
25 |           "context": "",
26 |           "skip": false,
27 |           "overrides": {},
28 |           "content": "Lingo.dev loves Vite and React"
29 |         },
30 |         "7/body/1/argument/5": {
31 |           "type": "element",
32 |           "hash": "c6deea18ed327336cf526f7d5a268d18",
33 |           "context": "",
34 |           "skip": false,
35 |           "overrides": {},
36 |           "content": "Welcome to your new Vite & React application! This starter template includes everything you need to get started with Vite & React and Lingo.dev for internationalization."
37 |         },
38 |         "7/body/1/argument/7/1": {
39 |           "type": "element",
40 |           "hash": "c0057f8ebaf4debdf07033f51a6fd99d",
41 |           "context": "",
42 |           "skip": false,
43 |           "overrides": {},
44 |           "content": "count is {count}"
45 |         },
46 |         "7/body/1/argument/7/3": {
47 |           "type": "element",
48 |           "hash": "d373807bd0794bcfad789f038c92d8d4",
49 |           "context": "",
50 |           "skip": false,
51 |           "overrides": {},
52 |           "content": "Edit <element:code>src/App.tsx</element:code> and save to test HMR"
53 |         },
54 |         "7/body/1/argument/9": {
55 |           "type": "element",
56 |           "hash": "fee72ece6f86de4a1331fb2bbfb53919",
57 |           "context": "",
58 |           "skip": false,
59 |           "overrides": {},
60 |           "content": "Click on the logos above to learn more"
61 |         }
62 |       }
63 |     },
64 |     "components/test.tsx": {
65 |       "scopes": {
66 |         "0/declaration/0/init/body/0/argument/1": {
67 |           "type": "element",
68 |           "hash": "46316c70126692cd475cedb7a063803b",
69 |           "context": "",
70 |           "skip": false,
71 |           "overrides": {},
72 |           "content": "Testing Component"
73 |         },
74 |         "0/declaration/0/init/body/0/argument/3": {
75 |           "type": "element",
76 |           "hash": "eb0c9884ca11affbe842b0a8cd68d78f",
77 |           "context": "",
78 |           "skip": false,
79 |           "overrides": {},
80 |           "content": "This is a component intended for testing purposes."
81 |         }
82 |       }
83 |     }
84 |   }
85 | }
86 | 
```

--------------------------------------------------------------------------------
/packages/cli/src/cli/loaders/typescript/cjs-interop.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /**
 2 |  * @fileoverview Helpers for CommonJS ⇆ ES Module inter-op quirks.
 3 |  */
 4 | 
 5 | /**
 6 |  * Resolve the actual default export value of a CommonJS module that has been
 7 |  * imported via an ES-module `import` statement.
 8 |  *
 9 |  * Why is this needed?
10 |  * -------------------
11 |  * When a package that is published as **CommonJS** (for example, `@babel/traverse`)
12 |  * is imported inside native **ESM** code (or via a bundler in ESM mode) the
13 |  * runtime value you receive is not consistent across environments:
14 |  *
15 |  *   • **Node.js** (native ESM) wraps the CJS module in an object like
16 |  *     `{ default: moduleExports, …namedReExports }`.
17 |  *   • **esbuild / Vite / Vitest** may decide to mimic TypeScript's
18 |  *     `esModuleInterop` behaviour and give you `moduleExports` directly.
19 |  *   • Other tools can produce yet different shapes.
20 |  *
21 |  * If you blindly assume one shape, you will hit runtime errors such as
22 |  * `TypeError: traverse is not a function` when the actual function lives on the
23 |  * `.default` property — or the opposite, depending on the environment.
24 |  *
25 |  * This helper inspects the imported value at runtime and returns what looks like
26 |  * the real default export regardless of how it was wrapped.  It hides the ugly
27 |  * `typeof mod === "function" ? … : mod.default` branching behind a single call
28 |  * site.
29 |  *
30 |  * Example
31 |  * -------
32 |  * ```ts
33 |  * import traverseModule from "@babel/traverse";
34 |  * import { resolveCjsExport } from "../utils/cjs-interop";
35 |  *
36 |  * const traverse = resolveCjsExport<typeof traverseModule>(
37 |  *   traverseModule,
38 |  *   "@babel/traverse",
39 |  * );
40 |  * ```
41 |  *
42 |  * @template T Expected type of the resolved export.
43 |  * @param mod  The runtime value returned by the `import` statement.
44 |  * @param name Friendly name of the module (for error messages).
45 |  * @returns    The resolved default export value.
46 |  */
47 | export function resolveCjsExport<T = any>(mod: T, name: string = "module"): T {
48 |   // If the module value itself is callable or clearly not an object, assume it's
49 |   // already the export we want (covers most bundler scenarios).
50 |   if (typeof mod === "function" || typeof mod !== "object" || mod === null) {
51 |     return mod as T;
52 |   }
53 | 
54 |   // Otherwise, look for a `.default` property which is common in Node's CJS->ESM
55 |   // wrapper as well as in Babel's `interopRequireDefault` helpers.
56 |   if ("default" in mod && typeof mod.default !== "undefined") {
57 |     return mod.default as T;
58 |   }
59 | 
60 |   // Give up: log the mysterious shape and throw to fail fast.
61 |   /* eslint-disable no-console */
62 |   console.error(
63 |     `[resolveCjsExport] Unable to determine default export for ${name}.`,
64 |     "Received value:",
65 |     mod,
66 |   );
67 |   throw new Error(`Failed to resolve default export for ${name}.`);
68 | }
69 | 
```

--------------------------------------------------------------------------------
/packages/cli/demo/demo.spec.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { describe, it, expect } from "vitest";
 2 | import fs from "fs";
 3 | import path from "path";
 4 | import { bucketTypes, parseI18nConfig } from "@lingo.dev/_spec";
 5 | 
 6 | type BucketType = (typeof bucketTypes)[number];
 7 | 
 8 | const SKIP_BUCKET_TYPES: BucketType[] = ["compiler", "dato"];
 9 | 
10 | const TESTABLE_BUCKET_TYPES: BucketType[] = bucketTypes.filter(
11 |   (type) => !SKIP_BUCKET_TYPES.includes(type),
12 | );
13 | 
14 | describe("packages/cli/demo", () => {
15 |   console.warn(
16 |     `Bucket types are defined but not tested: ${SKIP_BUCKET_TYPES.join(", ")}`,
17 |   );
18 | 
19 |   it("should include a demo for each bucket type", () => {
20 |     const demoRoot = path.resolve(__dirname);
21 |     const missingBuckets: string[] = [];
22 | 
23 |     for (const bucketType of new Set(TESTABLE_BUCKET_TYPES)) {
24 |       const bucketPath = path.join(demoRoot, bucketType);
25 |       const exists = fs.existsSync(bucketPath);
26 |       if (!exists) {
27 |         missingBuckets.push(bucketType);
28 |       }
29 |     }
30 | 
31 |     expect(missingBuckets).toEqual([]);
32 |   });
33 | 
34 |   it("should have an i18n.json file in each bucket demo", () => {
35 |     const demoRoot = path.resolve(__dirname);
36 |     const missingFiles: string[] = [];
37 | 
38 |     for (const bucketType of new Set(TESTABLE_BUCKET_TYPES)) {
39 |       const bucketPath = path.join(demoRoot, bucketType);
40 |       const i18nJsonPath = path.join(bucketPath, "i18n.json");
41 |       if (!fs.existsSync(i18nJsonPath)) {
42 |         missingFiles.push(bucketType);
43 |       }
44 |     }
45 | 
46 |     expect(missingFiles).toEqual([]);
47 |   });
48 | 
49 |   it("should have valid i18n.json config in each bucket demo", () => {
50 |     const demoRoot = path.resolve(__dirname);
51 |     const invalidConfigs: Array<{ bucketType: string; error: string }> = [];
52 | 
53 |     for (const bucketType of new Set(TESTABLE_BUCKET_TYPES)) {
54 |       const bucketPath = path.join(demoRoot, bucketType);
55 |       const i18nJsonPath = path.join(bucketPath, "i18n.json");
56 |       try {
57 |         const configContent = fs.readFileSync(i18nJsonPath, "utf-8");
58 |         const rawConfig = JSON.parse(configContent);
59 |         parseI18nConfig(rawConfig);
60 |       } catch (error) {
61 |         invalidConfigs.push({
62 |           bucketType,
63 |           error: error instanceof Error ? error.message : String(error),
64 |         });
65 |       }
66 |     }
67 | 
68 |     expect(invalidConfigs).toEqual([]);
69 |   });
70 | 
71 |   it("should have an i18n.lock file in each bucket demo", () => {
72 |     const demoRoot = path.resolve(__dirname);
73 |     const missingFiles: string[] = [];
74 | 
75 |     for (const bucketType of new Set(TESTABLE_BUCKET_TYPES)) {
76 |       const bucketPath = path.join(demoRoot, bucketType);
77 |       const i18nLockPath = path.join(bucketPath, "i18n.lock");
78 |       if (!fs.existsSync(i18nLockPath)) {
79 |         missingFiles.push(bucketType);
80 |       }
81 |     }
82 | 
83 |     expect(missingFiles).toEqual([]);
84 |   });
85 | });
86 | 
```
Page 4/20FirstPrevNextLast