This is page 2 of 16. Use http://codebase.md/lingodotdev/lingo.dev?lines=false&page={x} to view the full context.
# Directory Structure
```
├── .changeset
│ ├── config.json
│ └── README.md
├── .claude
│ ├── agents
│ │ └── code-architect-reviewer.md
│ └── commands
│ ├── analyze-bucket-type.md
│ └── create-bucket-docs.md
├── .editorconfig
├── .github
│ ├── dependabot.yml
│ └── workflows
│ ├── docker.yml
│ ├── lingodotdev.yml
│ ├── pr-check.yml
│ ├── pr-lint.yml
│ └── release.yml
├── .gitignore
├── .husky
│ └── commit-msg
├── .npmrc
├── .prettierignore
├── .prettierrc
├── .vscode
│ ├── extensions.json
│ ├── launch.json
│ └── settings.json
├── action.yml
├── CLAUDE.md
├── CODE_OF_CONDUCT.md
├── commitlint.config.js
├── composer.json
├── content
│ ├── banner.compiler.png
│ ├── banner.dark.png
│ └── banner.launch.png
├── CONTRIBUTING.md
├── DEBUGGING.md
├── demo
│ ├── adonisjs
│ │ ├── .editorconfig
│ │ ├── .env.example
│ │ ├── .gitignore
│ │ ├── ace.js
│ │ ├── adonisrc.ts
│ │ ├── app
│ │ │ ├── exceptions
│ │ │ │ └── handler.ts
│ │ │ └── middleware
│ │ │ └── container_bindings_middleware.ts
│ │ ├── bin
│ │ │ ├── console.ts
│ │ │ ├── server.ts
│ │ │ └── test.ts
│ │ ├── CHANGELOG.md
│ │ ├── config
│ │ │ ├── app.ts
│ │ │ ├── bodyparser.ts
│ │ │ ├── cors.ts
│ │ │ ├── hash.ts
│ │ │ ├── inertia.ts
│ │ │ ├── logger.ts
│ │ │ ├── session.ts
│ │ │ ├── shield.ts
│ │ │ ├── static.ts
│ │ │ └── vite.ts
│ │ ├── eslint.config.js
│ │ ├── inertia
│ │ │ ├── app
│ │ │ │ ├── app.tsx
│ │ │ │ └── ssr.tsx
│ │ │ ├── css
│ │ │ │ └── app.css
│ │ │ ├── lingo
│ │ │ │ ├── dictionary.js
│ │ │ │ └── meta.json
│ │ │ ├── pages
│ │ │ │ ├── errors
│ │ │ │ │ ├── not_found.tsx
│ │ │ │ │ └── server_error.tsx
│ │ │ │ └── home.tsx
│ │ │ └── tsconfig.json
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── resources
│ │ │ └── views
│ │ │ └── inertia_layout.edge
│ │ ├── start
│ │ │ ├── env.ts
│ │ │ ├── kernel.ts
│ │ │ └── routes.ts
│ │ ├── tests
│ │ │ └── bootstrap.ts
│ │ ├── tsconfig.json
│ │ └── vite.config.ts
│ ├── next-app
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── eslint.config.mjs
│ │ ├── next.config.ts
│ │ ├── package.json
│ │ ├── postcss.config.mjs
│ │ ├── public
│ │ │ ├── file.svg
│ │ │ ├── globe.svg
│ │ │ ├── next.svg
│ │ │ ├── vercel.svg
│ │ │ └── window.svg
│ │ ├── README.md
│ │ ├── src
│ │ │ ├── app
│ │ │ │ ├── client-component.tsx
│ │ │ │ ├── favicon.ico
│ │ │ │ ├── globals.css
│ │ │ │ ├── layout.tsx
│ │ │ │ ├── lingo-dot-dev.tsx
│ │ │ │ ├── page.tsx
│ │ │ │ └── test
│ │ │ │ └── page.tsx
│ │ │ ├── components
│ │ │ │ ├── hero-actions.tsx
│ │ │ │ ├── hero-subtitle.tsx
│ │ │ │ ├── hero-title.tsx
│ │ │ │ └── index.ts
│ │ │ └── lingo
│ │ │ ├── dictionary.js
│ │ │ └── meta.json
│ │ └── tsconfig.json
│ ├── react-router-app
│ │ ├── .dockerignore
│ │ ├── .gitignore
│ │ ├── app
│ │ │ ├── app.css
│ │ │ ├── lingo
│ │ │ │ ├── dictionary.js
│ │ │ │ └── meta.json
│ │ │ ├── root.tsx
│ │ │ ├── routes
│ │ │ │ ├── home.tsx
│ │ │ │ └── test.tsx
│ │ │ ├── routes.ts
│ │ │ └── welcome
│ │ │ ├── lingo-dot-dev.tsx
│ │ │ ├── logo-dark.svg
│ │ │ ├── logo-light.svg
│ │ │ └── welcome.tsx
│ │ ├── Dockerfile
│ │ ├── package.json
│ │ ├── public
│ │ │ └── favicon.ico
│ │ ├── react-router.config.ts
│ │ ├── README.md
│ │ ├── tsconfig.json
│ │ └── vite.config.ts
│ └── vite-project
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── eslint.config.js
│ ├── index.html
│ ├── package.json
│ ├── public
│ │ └── vite.svg
│ ├── README.md
│ ├── src
│ │ ├── App.css
│ │ ├── App.tsx
│ │ ├── assets
│ │ │ └── react.svg
│ │ ├── components
│ │ │ └── test.tsx
│ │ ├── index.css
│ │ ├── lingo
│ │ │ ├── dictionary.js
│ │ │ └── meta.json
│ │ ├── lingo-dot-dev.tsx
│ │ ├── main.tsx
│ │ └── vite-env.d.ts
│ ├── tsconfig.app.json
│ ├── tsconfig.json
│ ├── tsconfig.node.json
│ └── vite.config.ts
├── Dockerfile
├── i18n.json
├── i18n.lock
├── integrations
│ └── directus
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── docker-compose.yml
│ ├── Dockerfile
│ ├── package.json
│ ├── README.md
│ ├── src
│ │ ├── api.ts
│ │ ├── app.ts
│ │ └── index.spec.ts
│ ├── tsconfig.json
│ ├── tsconfig.test.json
│ └── tsup.config.ts
├── ISSUE_TEMPLATE.md
├── legacy
│ ├── cli
│ │ ├── bin
│ │ │ └── cli.mjs
│ │ ├── CHANGELOG.md
│ │ ├── package.json
│ │ └── readme.md
│ └── sdk
│ ├── CHANGELOG.md
│ ├── index.d.ts
│ ├── index.js
│ ├── package.json
│ └── README.md
├── LICENSE.md
├── mcp.md
├── package.json
├── packages
│ ├── cli
│ │ ├── assets
│ │ │ ├── failure.mp3
│ │ │ └── success.mp3
│ │ ├── bin
│ │ │ └── cli.mjs
│ │ ├── CHANGELOG.md
│ │ ├── demo
│ │ │ ├── android
│ │ │ │ ├── en
│ │ │ │ │ └── example.xml
│ │ │ │ ├── es
│ │ │ │ │ └── example.xml
│ │ │ │ ├── i18n.json
│ │ │ │ └── i18n.lock
│ │ │ ├── csv
│ │ │ │ ├── example.csv
│ │ │ │ ├── i18n.json
│ │ │ │ └── i18n.lock
│ │ │ ├── demo.spec.ts
│ │ │ ├── ejs
│ │ │ │ ├── en
│ │ │ │ │ └── example.ejs
│ │ │ │ ├── es
│ │ │ │ │ └── example.ejs
│ │ │ │ ├── i18n.json
│ │ │ │ └── i18n.lock
│ │ │ ├── flutter
│ │ │ │ ├── en
│ │ │ │ │ └── example.arb
│ │ │ │ ├── es
│ │ │ │ │ └── example.arb
│ │ │ │ ├── i18n.json
│ │ │ │ └── i18n.lock
│ │ │ ├── html
│ │ │ │ ├── en
│ │ │ │ │ └── example.html
│ │ │ │ ├── es
│ │ │ │ │ └── example.html
│ │ │ │ ├── i18n.json
│ │ │ │ └── i18n.lock
│ │ │ ├── json
│ │ │ │ ├── en
│ │ │ │ │ └── example.json
│ │ │ │ ├── es
│ │ │ │ │ └── example.json
│ │ │ │ ├── i18n.json
│ │ │ │ └── i18n.lock
│ │ │ ├── json-dictionary
│ │ │ │ ├── example.json
│ │ │ │ ├── i18n.json
│ │ │ │ └── i18n.lock
│ │ │ ├── json5
│ │ │ │ ├── en
│ │ │ │ │ └── example.json5
│ │ │ │ ├── es
│ │ │ │ │ └── example.json5
│ │ │ │ ├── i18n.json
│ │ │ │ └── i18n.lock
│ │ │ ├── jsonc
│ │ │ │ ├── en
│ │ │ │ │ └── example.jsonc
│ │ │ │ ├── es
│ │ │ │ │ └── example.jsonc
│ │ │ │ ├── i18n.json
│ │ │ │ ├── i18n.lock
│ │ │ │ └── ru
│ │ │ │ └── example.jsonc
│ │ │ ├── markdoc
│ │ │ │ ├── en
│ │ │ │ │ └── example.markdoc
│ │ │ │ ├── es
│ │ │ │ │ └── example.markdoc
│ │ │ │ ├── i18n.json
│ │ │ │ └── i18n.lock
│ │ │ ├── markdown
│ │ │ │ ├── en
│ │ │ │ │ └── example.md
│ │ │ │ ├── es
│ │ │ │ │ └── example.md
│ │ │ │ ├── i18n.json
│ │ │ │ └── i18n.lock
│ │ │ ├── mdx
│ │ │ │ ├── en
│ │ │ │ │ └── example.mdx
│ │ │ │ ├── es
│ │ │ │ │ └── example.mdx
│ │ │ │ ├── i18n.json
│ │ │ │ └── i18n.lock
│ │ │ ├── php
│ │ │ │ ├── en
│ │ │ │ │ └── example.php
│ │ │ │ ├── es
│ │ │ │ │ └── example.php
│ │ │ │ ├── i18n.json
│ │ │ │ └── i18n.lock
│ │ │ ├── po
│ │ │ │ ├── en
│ │ │ │ │ └── example.po
│ │ │ │ ├── es
│ │ │ │ │ └── example.po
│ │ │ │ ├── i18n.json
│ │ │ │ └── i18n.lock
│ │ │ ├── properties
│ │ │ │ ├── en
│ │ │ │ │ └── example.properties
│ │ │ │ ├── es
│ │ │ │ │ └── example.properties
│ │ │ │ ├── i18n.json
│ │ │ │ └── i18n.lock
│ │ │ ├── run_i18n.sh
│ │ │ ├── srt
│ │ │ │ ├── en
│ │ │ │ │ └── example.srt
│ │ │ │ ├── es
│ │ │ │ │ └── example.srt
│ │ │ │ ├── i18n.json
│ │ │ │ └── i18n.lock
│ │ │ ├── txt
│ │ │ │ ├── en
│ │ │ │ │ └── example.txt
│ │ │ │ ├── es
│ │ │ │ │ └── example.txt
│ │ │ │ ├── i18n.json
│ │ │ │ └── i18n.lock
│ │ │ ├── typescript
│ │ │ │ ├── en
│ │ │ │ │ └── example.ts
│ │ │ │ ├── es
│ │ │ │ │ └── example.ts
│ │ │ │ ├── i18n.json
│ │ │ │ └── i18n.lock
│ │ │ ├── vtt
│ │ │ │ ├── en
│ │ │ │ │ └── example.vtt
│ │ │ │ ├── es
│ │ │ │ │ └── example.vtt
│ │ │ │ ├── i18n.json
│ │ │ │ └── i18n.lock
│ │ │ ├── vue-json
│ │ │ │ ├── example.vue
│ │ │ │ ├── i18n.json
│ │ │ │ └── i18n.lock
│ │ │ ├── xcode-strings
│ │ │ │ ├── en
│ │ │ │ │ └── example.strings
│ │ │ │ ├── es
│ │ │ │ │ └── example.strings
│ │ │ │ ├── i18n.json
│ │ │ │ └── i18n.lock
│ │ │ ├── xcode-stringsdict
│ │ │ │ ├── en
│ │ │ │ │ └── example.stringsdict
│ │ │ │ ├── es
│ │ │ │ │ └── example.stringsdict
│ │ │ │ ├── i18n.json
│ │ │ │ └── i18n.lock
│ │ │ ├── xcode-xcstrings
│ │ │ │ ├── example.xcstrings
│ │ │ │ ├── i18n.json
│ │ │ │ └── i18n.lock
│ │ │ ├── xcode-xcstrings-v2
│ │ │ │ ├── complex-example.xcstrings
│ │ │ │ ├── example.xcstrings
│ │ │ │ ├── i18n.json
│ │ │ │ └── i18n.lock
│ │ │ ├── xliff
│ │ │ │ ├── en
│ │ │ │ │ ├── example-v1.2.xliff
│ │ │ │ │ └── example-v2.xliff
│ │ │ │ ├── es
│ │ │ │ │ ├── example-v1.2.xliff
│ │ │ │ │ ├── example-v2.xliff
│ │ │ │ │ └── example.xliff
│ │ │ │ ├── i18n.json
│ │ │ │ └── i18n.lock
│ │ │ ├── xml
│ │ │ │ ├── en
│ │ │ │ │ └── example.xml
│ │ │ │ ├── es
│ │ │ │ │ └── example.xml
│ │ │ │ ├── i18n.json
│ │ │ │ └── i18n.lock
│ │ │ ├── yaml
│ │ │ │ ├── en
│ │ │ │ │ └── example.yml
│ │ │ │ ├── es
│ │ │ │ │ └── example.yml
│ │ │ │ ├── i18n.json
│ │ │ │ └── i18n.lock
│ │ │ └── yaml-root-key
│ │ │ ├── en
│ │ │ │ └── example.yml
│ │ │ ├── es
│ │ │ │ └── example.yml
│ │ │ ├── i18n.json
│ │ │ └── i18n.lock
│ │ ├── i18n.json
│ │ ├── i18n.lock
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── src
│ │ │ ├── cli
│ │ │ │ ├── cmd
│ │ │ │ │ ├── auth.ts
│ │ │ │ │ ├── ci
│ │ │ │ │ │ ├── flows
│ │ │ │ │ │ │ ├── _base.ts
│ │ │ │ │ │ │ ├── in-branch.ts
│ │ │ │ │ │ │ └── pull-request.ts
│ │ │ │ │ │ ├── index.ts
│ │ │ │ │ │ └── platforms
│ │ │ │ │ │ ├── _base.ts
│ │ │ │ │ │ ├── bitbucket.ts
│ │ │ │ │ │ ├── github.ts
│ │ │ │ │ │ ├── gitlab.ts
│ │ │ │ │ │ └── index.ts
│ │ │ │ │ ├── cleanup.ts
│ │ │ │ │ ├── config
│ │ │ │ │ │ ├── get.ts
│ │ │ │ │ │ ├── index.ts
│ │ │ │ │ │ ├── set.ts
│ │ │ │ │ │ └── unset.ts
│ │ │ │ │ ├── i18n.ts
│ │ │ │ │ ├── init.ts
│ │ │ │ │ ├── lockfile.ts
│ │ │ │ │ ├── login.ts
│ │ │ │ │ ├── logout.ts
│ │ │ │ │ ├── may-the-fourth.ts
│ │ │ │ │ ├── mcp.ts
│ │ │ │ │ ├── purge.ts
│ │ │ │ │ ├── run
│ │ │ │ │ │ ├── _const.ts
│ │ │ │ │ │ ├── _types.ts
│ │ │ │ │ │ ├── _utils.ts
│ │ │ │ │ │ ├── execute.spec.ts
│ │ │ │ │ │ ├── execute.ts
│ │ │ │ │ │ ├── frozen.ts
│ │ │ │ │ │ ├── index.ts
│ │ │ │ │ │ ├── plan.ts
│ │ │ │ │ │ ├── setup.ts
│ │ │ │ │ │ └── watch.ts
│ │ │ │ │ ├── show
│ │ │ │ │ │ ├── _shared-key-command.ts
│ │ │ │ │ │ ├── config.ts
│ │ │ │ │ │ ├── files.ts
│ │ │ │ │ │ ├── ignored-keys.ts
│ │ │ │ │ │ ├── index.ts
│ │ │ │ │ │ ├── locale.ts
│ │ │ │ │ │ └── locked-keys.ts
│ │ │ │ │ └── status.ts
│ │ │ │ ├── constants.ts
│ │ │ │ ├── index.spec.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── loaders
│ │ │ │ │ ├── _types.ts
│ │ │ │ │ ├── _utils.ts
│ │ │ │ │ ├── android.spec.ts
│ │ │ │ │ ├── android.ts
│ │ │ │ │ ├── csv.spec.ts
│ │ │ │ │ ├── csv.ts
│ │ │ │ │ ├── dato
│ │ │ │ │ │ ├── _base.ts
│ │ │ │ │ │ ├── _utils.ts
│ │ │ │ │ │ ├── api.ts
│ │ │ │ │ │ ├── extract.ts
│ │ │ │ │ │ ├── filter.ts
│ │ │ │ │ │ └── index.ts
│ │ │ │ │ ├── ejs.spec.ts
│ │ │ │ │ ├── ejs.ts
│ │ │ │ │ ├── ensure-key-order.spec.ts
│ │ │ │ │ ├── ensure-key-order.ts
│ │ │ │ │ ├── flat.spec.ts
│ │ │ │ │ ├── flat.ts
│ │ │ │ │ ├── flutter.spec.ts
│ │ │ │ │ ├── flutter.ts
│ │ │ │ │ ├── formatters
│ │ │ │ │ │ ├── _base.ts
│ │ │ │ │ │ ├── biome.ts
│ │ │ │ │ │ ├── index.ts
│ │ │ │ │ │ └── prettier.ts
│ │ │ │ │ ├── html.ts
│ │ │ │ │ ├── icu-safety.spec.ts
│ │ │ │ │ ├── ignored-keys-buckets.spec.ts
│ │ │ │ │ ├── ignored-keys.spec.ts
│ │ │ │ │ ├── ignored-keys.ts
│ │ │ │ │ ├── index.spec.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── inject-locale.spec.ts
│ │ │ │ │ ├── inject-locale.ts
│ │ │ │ │ ├── json-dictionary.spec.ts
│ │ │ │ │ ├── json-dictionary.ts
│ │ │ │ │ ├── json-sorting.test.ts
│ │ │ │ │ ├── json-sorting.ts
│ │ │ │ │ ├── json.ts
│ │ │ │ │ ├── json5.spec.ts
│ │ │ │ │ ├── json5.ts
│ │ │ │ │ ├── jsonc.spec.ts
│ │ │ │ │ ├── jsonc.ts
│ │ │ │ │ ├── locked-keys.spec.ts
│ │ │ │ │ ├── locked-keys.ts
│ │ │ │ │ ├── locked-patterns.spec.ts
│ │ │ │ │ ├── locked-patterns.ts
│ │ │ │ │ ├── markdoc.spec.ts
│ │ │ │ │ ├── markdoc.ts
│ │ │ │ │ ├── markdown.ts
│ │ │ │ │ ├── mdx.spec.ts
│ │ │ │ │ ├── mdx.ts
│ │ │ │ │ ├── mdx2
│ │ │ │ │ │ ├── _types.ts
│ │ │ │ │ │ ├── _utils.ts
│ │ │ │ │ │ ├── code-placeholder.spec.ts
│ │ │ │ │ │ ├── code-placeholder.ts
│ │ │ │ │ │ ├── frontmatter-split.spec.ts
│ │ │ │ │ │ ├── frontmatter-split.ts
│ │ │ │ │ │ ├── localizable-document.spec.ts
│ │ │ │ │ │ ├── localizable-document.ts
│ │ │ │ │ │ ├── section-split.spec.ts
│ │ │ │ │ │ ├── section-split.ts
│ │ │ │ │ │ └── sections-split-2.ts
│ │ │ │ │ ├── passthrough.ts
│ │ │ │ │ ├── php.ts
│ │ │ │ │ ├── plutil-json-loader.ts
│ │ │ │ │ ├── po
│ │ │ │ │ │ ├── _types.ts
│ │ │ │ │ │ ├── index.spec.ts
│ │ │ │ │ │ └── index.ts
│ │ │ │ │ ├── properties.ts
│ │ │ │ │ ├── root-key.ts
│ │ │ │ │ ├── srt.ts
│ │ │ │ │ ├── sync.ts
│ │ │ │ │ ├── text-file.ts
│ │ │ │ │ ├── txt.ts
│ │ │ │ │ ├── typescript
│ │ │ │ │ │ ├── cjs-interop.ts
│ │ │ │ │ │ ├── index.spec.ts
│ │ │ │ │ │ └── index.ts
│ │ │ │ │ ├── unlocalizable.spec.ts
│ │ │ │ │ ├── unlocalizable.ts
│ │ │ │ │ ├── variable
│ │ │ │ │ │ ├── index.spec.ts
│ │ │ │ │ │ └── index.ts
│ │ │ │ │ ├── vtt.ts
│ │ │ │ │ ├── vue-json.ts
│ │ │ │ │ ├── xcode-strings
│ │ │ │ │ │ ├── escape.ts
│ │ │ │ │ │ ├── parser.ts
│ │ │ │ │ │ ├── tokenizer.ts
│ │ │ │ │ │ └── types.ts
│ │ │ │ │ ├── xcode-strings.spec.ts
│ │ │ │ │ ├── xcode-strings.ts
│ │ │ │ │ ├── xcode-stringsdict.ts
│ │ │ │ │ ├── xcode-xcstrings-icu.spec.ts
│ │ │ │ │ ├── xcode-xcstrings-icu.ts
│ │ │ │ │ ├── xcode-xcstrings-lock-compatibility.spec.ts
│ │ │ │ │ ├── xcode-xcstrings-v2-loader.ts
│ │ │ │ │ ├── xcode-xcstrings.spec.ts
│ │ │ │ │ ├── xcode-xcstrings.ts
│ │ │ │ │ ├── xliff.spec.ts
│ │ │ │ │ ├── xliff.ts
│ │ │ │ │ ├── xml.ts
│ │ │ │ │ └── yaml.ts
│ │ │ │ ├── localizer
│ │ │ │ │ ├── _types.ts
│ │ │ │ │ ├── explicit.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ └── lingodotdev.ts
│ │ │ │ ├── processor
│ │ │ │ │ ├── _base.ts
│ │ │ │ │ ├── basic.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ └── lingo.ts
│ │ │ │ └── utils
│ │ │ │ ├── auth.ts
│ │ │ │ ├── buckets.spec.ts
│ │ │ │ ├── buckets.ts
│ │ │ │ ├── cache.ts
│ │ │ │ ├── cloudflare-status.ts
│ │ │ │ ├── config.ts
│ │ │ │ ├── delta.spec.ts
│ │ │ │ ├── delta.ts
│ │ │ │ ├── ensure-patterns.ts
│ │ │ │ ├── errors.ts
│ │ │ │ ├── exec.spec.ts
│ │ │ │ ├── exec.ts
│ │ │ │ ├── exit-gracefully.spec.ts
│ │ │ │ ├── exit-gracefully.ts
│ │ │ │ ├── exp-backoff.ts
│ │ │ │ ├── find-locale-paths.spec.ts
│ │ │ │ ├── find-locale-paths.ts
│ │ │ │ ├── fs.ts
│ │ │ │ ├── init-ci-cd.ts
│ │ │ │ ├── key-matching.spec.ts
│ │ │ │ ├── key-matching.ts
│ │ │ │ ├── lockfile.ts
│ │ │ │ ├── md5.ts
│ │ │ │ ├── observability.ts
│ │ │ │ ├── plutil-formatter.spec.ts
│ │ │ │ ├── plutil-formatter.ts
│ │ │ │ ├── settings.ts
│ │ │ │ ├── ui.ts
│ │ │ │ └── update-gitignore.ts
│ │ │ ├── compiler
│ │ │ │ └── index.ts
│ │ │ ├── locale-codes
│ │ │ │ └── index.ts
│ │ │ ├── react
│ │ │ │ ├── client.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── react-router.ts
│ │ │ │ └── rsc.ts
│ │ │ ├── sdk
│ │ │ │ └── index.ts
│ │ │ └── spec
│ │ │ └── index.ts
│ │ ├── tests
│ │ │ └── mock-storage.ts
│ │ ├── troubleshooting.md
│ │ ├── tsconfig.json
│ │ ├── tsconfig.test.json
│ │ ├── tsup.config.ts
│ │ ├── types
│ │ │ ├── vtt.d.ts
│ │ │ └── xliff.d.ts
│ │ ├── vitest.config.ts
│ │ └── WATCH_MODE.md
│ ├── compiler
│ │ ├── CHANGELOG.md
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── src
│ │ │ ├── _base.ts
│ │ │ ├── _const.ts
│ │ │ ├── _loader-utils.spec.ts
│ │ │ ├── _loader-utils.ts
│ │ │ ├── _utils.spec.ts
│ │ │ ├── _utils.ts
│ │ │ ├── client-dictionary-loader.ts
│ │ │ ├── i18n-directive.spec.ts
│ │ │ ├── i18n-directive.ts
│ │ │ ├── index.spec.ts
│ │ │ ├── index.ts
│ │ │ ├── jsx-attribute-flag.spec.ts
│ │ │ ├── jsx-attribute-flag.ts
│ │ │ ├── jsx-attribute-scope-inject.spec.ts
│ │ │ ├── jsx-attribute-scope-inject.ts
│ │ │ ├── jsx-attribute-scopes-export.spec.ts
│ │ │ ├── jsx-attribute-scopes-export.ts
│ │ │ ├── jsx-attribute.spec.ts
│ │ │ ├── jsx-attribute.ts
│ │ │ ├── jsx-fragment.spec.ts
│ │ │ ├── jsx-fragment.ts
│ │ │ ├── jsx-html-lang.spec.ts
│ │ │ ├── jsx-html-lang.ts
│ │ │ ├── jsx-provider.spec.ts
│ │ │ ├── jsx-provider.ts
│ │ │ ├── jsx-remove-attributes.spec.ts
│ │ │ ├── jsx-remove-attributes.ts
│ │ │ ├── jsx-root-flag.spec.ts
│ │ │ ├── jsx-root-flag.ts
│ │ │ ├── jsx-scope-flag.spec.ts
│ │ │ ├── jsx-scope-flag.ts
│ │ │ ├── jsx-scope-inject.spec.ts
│ │ │ ├── jsx-scope-inject.ts
│ │ │ ├── jsx-scopes-export.spec.ts
│ │ │ ├── jsx-scopes-export.ts
│ │ │ ├── lib
│ │ │ │ └── lcp
│ │ │ │ ├── api
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── prompt.spec.ts
│ │ │ │ │ ├── prompt.ts
│ │ │ │ │ ├── provider-details.spec.ts
│ │ │ │ │ ├── provider-details.ts
│ │ │ │ │ ├── shots.ts
│ │ │ │ │ ├── xml2obj.spec.ts
│ │ │ │ │ └── xml2obj.ts
│ │ │ │ ├── api.spec.ts
│ │ │ │ ├── cache.spec.ts
│ │ │ │ ├── cache.ts
│ │ │ │ ├── index.spec.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── schema.ts
│ │ │ │ ├── server.spec.ts
│ │ │ │ └── server.ts
│ │ │ ├── lingo-turbopack-loader.ts
│ │ │ ├── react-router-dictionary-loader.ts
│ │ │ ├── rsc-dictionary-loader.ts
│ │ │ └── utils
│ │ │ ├── ast-key.spec.ts
│ │ │ ├── ast-key.ts
│ │ │ ├── create-locale-import-map.spec.ts
│ │ │ ├── create-locale-import-map.ts
│ │ │ ├── env.spec.ts
│ │ │ ├── env.ts
│ │ │ ├── hash.spec.ts
│ │ │ ├── hash.ts
│ │ │ ├── index.spec.ts
│ │ │ ├── index.ts
│ │ │ ├── invokations.spec.ts
│ │ │ ├── invokations.ts
│ │ │ ├── jsx-attribute-scope.ts
│ │ │ ├── jsx-attribute.spec.ts
│ │ │ ├── jsx-attribute.ts
│ │ │ ├── jsx-content-whitespace.spec.ts
│ │ │ ├── jsx-content.spec.ts
│ │ │ ├── jsx-content.ts
│ │ │ ├── jsx-element.spec.ts
│ │ │ ├── jsx-element.ts
│ │ │ ├── jsx-expressions.test.ts
│ │ │ ├── jsx-expressions.ts
│ │ │ ├── jsx-functions.spec.ts
│ │ │ ├── jsx-functions.ts
│ │ │ ├── jsx-scope.spec.ts
│ │ │ ├── jsx-scope.ts
│ │ │ ├── jsx-variables.spec.ts
│ │ │ ├── jsx-variables.ts
│ │ │ ├── llm-api-key.ts
│ │ │ ├── llm-api-keys.spec.ts
│ │ │ ├── locales.spec.ts
│ │ │ ├── locales.ts
│ │ │ ├── module-params.spec.ts
│ │ │ ├── module-params.ts
│ │ │ ├── observability.spec.ts
│ │ │ ├── observability.ts
│ │ │ ├── rc.spec.ts
│ │ │ └── rc.ts
│ │ ├── tsconfig.json
│ │ ├── tsup.config.ts
│ │ └── vitest.config.ts
│ ├── locales
│ │ ├── CHANGELOG.md
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── src
│ │ │ ├── constants.ts
│ │ │ ├── index.ts
│ │ │ ├── names
│ │ │ │ ├── index.spec.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── integration.spec.ts
│ │ │ │ └── loader.ts
│ │ │ ├── parser.spec.ts
│ │ │ ├── parser.ts
│ │ │ ├── types.ts
│ │ │ ├── validation.spec.ts
│ │ │ └── validation.ts
│ │ ├── tsconfig.json
│ │ └── tsup.config.ts
│ ├── react
│ │ ├── build.config.ts
│ │ ├── CHANGELOG.md
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── src
│ │ │ ├── client
│ │ │ │ ├── attribute-component.spec.tsx
│ │ │ │ ├── attribute-component.tsx
│ │ │ │ ├── component.lingo-component.spec.tsx
│ │ │ │ ├── component.spec.tsx
│ │ │ │ ├── component.tsx
│ │ │ │ ├── context.spec.tsx
│ │ │ │ ├── context.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── loader.spec.ts
│ │ │ │ ├── loader.ts
│ │ │ │ ├── locale-switcher.spec.tsx
│ │ │ │ ├── locale-switcher.tsx
│ │ │ │ ├── locale.spec.ts
│ │ │ │ ├── locale.ts
│ │ │ │ ├── provider.spec.tsx
│ │ │ │ ├── provider.tsx
│ │ │ │ ├── utils.spec.ts
│ │ │ │ └── utils.ts
│ │ │ ├── core
│ │ │ │ ├── attribute-component.spec.tsx
│ │ │ │ ├── attribute-component.tsx
│ │ │ │ ├── component.spec.tsx
│ │ │ │ ├── component.tsx
│ │ │ │ ├── const.ts
│ │ │ │ ├── get-dictionary.spec.ts
│ │ │ │ ├── get-dictionary.ts
│ │ │ │ └── index.ts
│ │ │ ├── react-router
│ │ │ │ ├── index.ts
│ │ │ │ ├── loader.spec.ts
│ │ │ │ └── loader.ts
│ │ │ ├── rsc
│ │ │ │ ├── attribute-component.tsx
│ │ │ │ ├── component.lingo-component.spec.tsx
│ │ │ │ ├── component.spec.tsx
│ │ │ │ ├── component.tsx
│ │ │ │ ├── index.ts
│ │ │ │ ├── loader.spec.ts
│ │ │ │ ├── loader.ts
│ │ │ │ ├── provider.spec.tsx
│ │ │ │ ├── provider.tsx
│ │ │ │ ├── utils.spec.ts
│ │ │ │ └── utils.ts
│ │ │ └── test
│ │ │ └── setup.ts
│ │ ├── tsconfig.json
│ │ └── vitest.config.ts
│ ├── sdk
│ │ ├── CHANGELOG.md
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── src
│ │ │ ├── abort-controller.specs.ts
│ │ │ ├── index.spec.ts
│ │ │ └── index.ts
│ │ ├── tsconfig.json
│ │ ├── tsconfig.test.json
│ │ └── tsup.config.ts
│ └── spec
│ ├── CHANGELOG.md
│ ├── package.json
│ ├── README.md
│ ├── src
│ │ ├── config.spec.ts
│ │ ├── config.ts
│ │ ├── formats.ts
│ │ ├── index.spec.ts
│ │ ├── index.ts
│ │ ├── json-schema.ts
│ │ ├── locales.spec.ts
│ │ └── locales.ts
│ ├── tsconfig.json
│ ├── tsconfig.test.json
│ └── tsup.config.ts
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
├── readme
│ ├── ar.md
│ ├── bn.md
│ ├── de.md
│ ├── en.md
│ ├── es.md
│ ├── fa.md
│ ├── fr.md
│ ├── he.md
│ ├── hi.md
│ ├── it.md
│ ├── ja.md
│ ├── ko.md
│ ├── pl.md
│ ├── pt-BR.md
│ ├── ru.md
│ ├── tr.md
│ ├── uk-UA.md
│ └── zh-Hans.md
├── readme.md
├── scripts
│ ├── docs
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── src
│ │ │ ├── generate-cli-docs.ts
│ │ │ ├── generate-config-docs.ts
│ │ │ ├── json-schema
│ │ │ │ ├── markdown-renderer.test.ts
│ │ │ │ ├── markdown-renderer.ts
│ │ │ │ ├── parser.test.ts
│ │ │ │ ├── parser.ts
│ │ │ │ └── types.ts
│ │ │ ├── utils.test.ts
│ │ │ └── utils.ts
│ │ ├── tsconfig.json
│ │ └── vitest.config.ts
│ └── packagist-publish.php
└── turbo.json
```
# Files
--------------------------------------------------------------------------------
/packages/cli/src/cli/loaders/flutter.ts:
--------------------------------------------------------------------------------
```typescript
import _ from "lodash";
import { ILoader } from "./_types";
import { createLoader } from "./_utils";
export default function createFlutterLoader(): ILoader<
Record<string, any>,
Record<string, any>
> {
return createLoader({
async pull(locale, input) {
// skip all metadata (keys starting with @)
const result = _.pickBy(input, (value, key) => !_isMetadataKey(key));
return result;
},
async push(locale, data, originalInput) {
// find all metadata keys in originalInput
const metadata = _.pickBy(originalInput, (value, key) =>
_isMetadataKey(key),
);
const result = _.merge({}, metadata, { "@@locale": locale }, data);
return result;
},
});
}
function _isMetadataKey(key: string) {
return key.startsWith("@");
}
```
--------------------------------------------------------------------------------
/packages/react/src/rsc/provider.spec.tsx:
--------------------------------------------------------------------------------
```typescript
import { describe, it, expect, vi } from "vitest";
import { render, screen } from "@testing-library/react";
vi.mock("./utils", () => {
return {
loadDictionaryFromRequest: vi.fn(async (loader: any) => loader("en")),
};
});
describe("rsc/provider", () => {
describe("LingoProvider", () => {
it("loads dictionary via helper and renders children through client provider", async () => {
const { LingoProvider } = await import("./provider");
const loadDictionary = vi.fn(async () => ({ locale: "en" }));
render(
await LingoProvider({
loadDictionary,
children: <div data-testid="child" />,
}),
);
expect(screen.getByTestId("child")).toBeTruthy();
expect(loadDictionary).toHaveBeenCalledWith("en");
});
});
});
```
--------------------------------------------------------------------------------
/demo/vite-project/CHANGELOG.md:
--------------------------------------------------------------------------------
```markdown
# vite-project
## 0.0.3
### Patch Changes
- [`76cbd9b`](https://github.com/lingodotdev/lingo.dev/commit/76cbd9b2f2e1217421ad1f671bed5b3d64b43333) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - dictionary merging
## 0.0.2
### Patch Changes
- [`8e97256`](https://github.com/lingodotdev/lingo.dev/commit/8e97256ca4e78dd09a967539ca9dec359bd558ef) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - fix dictionary merging
## 0.0.1
### Patch Changes
- [#925](https://github.com/lingodotdev/lingo.dev/pull/925) [`215af19`](https://github.com/lingodotdev/lingo.dev/commit/215af1944667cce66e9c5966f4fb627186687b74) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - improved compiler concurrency, caching, added lingo.dev engine to the compiler, and updated demo apps
```
--------------------------------------------------------------------------------
/demo/react-router-app/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "react-router-app",
"private": true,
"type": "module",
"scripts": {
"build": "react-router build",
"dev": "react-router dev",
"start": "react-router-serve ./build/server/index.js",
"typecheck": "react-router typegen && tsc"
},
"dependencies": {
"lingo.dev": "workspace:*",
"@react-router/node": "^7.5.3",
"@react-router/serve": "^7.5.3",
"isbot": "^5.1.27",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-router": "^7.5.3"
},
"devDependencies": {
"@react-router/dev": "^7.5.3",
"@tailwindcss/vite": "^4.1.4",
"@types/node": "^20",
"@types/react": "^19.1.2",
"@types/react-dom": "^19.1.2",
"tailwindcss": "^4.1.4",
"typescript": "^5.8.3",
"vite": "^6.3.3",
"vite-tsconfig-paths": "^5.1.4"
}
}
```
--------------------------------------------------------------------------------
/integrations/directus/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "@replexica/integration-directus",
"version": "0.1.10",
"description": "Lingo.dev integration for Directus",
"private": false,
"sideEffects": false,
"directus:extension": {
"type": "operation",
"path": {
"app": "build/app.mjs",
"api": "build/api.mjs"
},
"source": {
"app": "src/app.ts",
"api": "src/api.ts"
},
"host": "^10.10.0"
},
"files": [
"build",
"readme.md",
"changelog.md"
],
"scripts": {
"dev": "tsup --watch",
"build": "tsc --noEmit && tsup",
"test": "vitest run"
},
"license": "Apache-2.0",
"dependencies": {
"@replexica/sdk": "^0.7.7"
},
"devDependencies": {
"@directus/extensions-sdk": "12.1.4",
"tsup": "^8.3.5",
"typescript": "^5.8.3",
"vitest": "^2.1.8"
}
}
```
--------------------------------------------------------------------------------
/packages/sdk/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "@lingo.dev/_sdk",
"version": "0.12.6",
"description": "Lingo.dev JS SDK",
"private": false,
"publishConfig": {
"access": "public"
},
"type": "module",
"sideEffects": false,
"types": "build/index.d.ts",
"module": "build/index.mjs",
"main": "build/index.cjs",
"files": [
"build"
],
"scripts": {
"dev": "tsup --watch",
"build": "pnpm typecheck && tsup",
"typecheck": "tsc --noEmit",
"test": "vitest run"
},
"keywords": [],
"author": "",
"license": "Apache-2.0",
"dependencies": {
"@lingo.dev/_spec": "workspace:*",
"@paralleldrive/cuid2": "^2.2.2",
"jsdom": "^25.0.1",
"zod": "^3.25.76"
},
"devDependencies": {
"@types/jsdom": "^21.1.7",
"tsup": "^8.3.5",
"typescript": "^5.8.3",
"vitest": "^3.1.2"
}
}
```
--------------------------------------------------------------------------------
/packages/cli/demo/json5/en/example.json5:
--------------------------------------------------------------------------------
```
{
// JSON5 allows comments!
title: "Hello, world!",
description: "A simple demo app with JSON5 features",
version: "1.0.0",
support_email: "[email protected]",
homepage: "https://lingo.dev",
deprecated: null,
empty: "",
emoji: "🚀",
// Unquoted keys are allowed
author: {
name: "John Doe"
},
contributors: [
{ name: "Alice" },
{ name: "Bob" }
],
messages: [
"Welcome to MyApp",
"Hello, world!"
],
config: {
theme: {
primary: "Blue theme"
}
},
mixed_array: [
"Mixed content here",
42,
true,
{
nested_message: "Nested text"
}
],
// Hexadecimal numbers work in JSON5
hex_value: 0xDEADBEEF,
// Trailing commas are allowed
locked_key_1: "This value is locked and should not be changed",
}
```
--------------------------------------------------------------------------------
/packages/cli/src/cli/utils/cloudflare-status.ts:
--------------------------------------------------------------------------------
```typescript
export interface CloudflareStatusResponse {
status: {
indicator: "none" | "minor" | "major" | "critical";
description: string;
};
}
export async function checkCloudflareStatus(): Promise<CloudflareStatusResponse | null> {
try {
const response = await fetch(
"https://www.cloudflarestatus.com/api/v2/status.json",
{
signal: AbortSignal.timeout(5000),
},
);
if (response.ok) {
return await response.json();
}
} catch (error) {}
return null;
}
export function formatCloudflareStatusMessage(
status: CloudflareStatusResponse,
): string {
if (status.status.indicator === "none") {
return "";
}
return `Cloudflare is experiencing ${status.status.indicator} issues: ${status.status.description}. This may be affecting the API connection.`;
}
```
--------------------------------------------------------------------------------
/demo/adonisjs/ace.js:
--------------------------------------------------------------------------------
```javascript
/*
|--------------------------------------------------------------------------
| JavaScript entrypoint for running ace commands
|--------------------------------------------------------------------------
|
| DO NOT MODIFY THIS FILE AS IT WILL BE OVERRIDDEN DURING THE BUILD
| PROCESS.
|
| See docs.adonisjs.com/guides/typescript-build-process#creating-production-build
|
| Since, we cannot run TypeScript source code using "node" binary, we need
| a JavaScript entrypoint to run ace commands.
|
| This file registers the "ts-node/esm" hook with the Node.js module system
| and then imports the "bin/console.ts" file.
|
*/
/**
* Register hook to process TypeScript files using ts-node-maintained
*/
import 'ts-node-maintained/register/esm'
/**
* Import ace console entrypoint
*/
await import('./bin/console.js')
```
--------------------------------------------------------------------------------
/packages/cli/demo/php/es/example.php:
--------------------------------------------------------------------------------
```php
<?php
return [
'0' => '¡Hola, mundo!',
'1' => 'Bienvenido a MyApp',
'2' => 'Es "simple\\" con una barra invertida \\ y salto de línea\\nTodo el texto aquí',
'3' => [
'welcome_message' => '¡Bienvenido!'
],
'4' => [
'error_text' => 'Algo salió mal'
],
'5' => [
'navigation' => [
'home' => 'Inicio',
'about' => 'Acerca de',
'contact' => 'Contacto'
]
],
'6' => [
'forms' => [
'login' => [
'username_label' => 'Nombre de usuario',
'password_label' => 'Contraseña',
'submit_button' => 'Iniciar sesión'
]
]
],
'7' => [
'mixed_content' => [
'title' => 'Ajustes',
'count' => 42,
'enabled' => true,
'nothing_here' => null,
'description' => 'Ajustes y preferencias de la aplicación'
]
]
];
```
--------------------------------------------------------------------------------
/packages/cli/src/cli/localizer/_types.ts:
--------------------------------------------------------------------------------
```typescript
import { I18nConfig } from "@lingo.dev/_spec";
export type LocalizerData = {
sourceLocale: string;
sourceData: Record<string, any>;
processableData: Record<string, any>;
targetLocale: string;
targetData: Record<string, any>;
hints: Record<string, string[]>;
};
export type LocalizerProgressFn = (
progress: number,
sourceChunk: Record<string, string>,
processedChunk: Record<string, string>,
) => void;
export interface ILocalizer {
id: "Lingo.dev" | NonNullable<I18nConfig["provider"]>["id"];
checkAuth: () => Promise<{
authenticated: boolean;
username?: string;
error?: string;
}>;
validateSettings?: () => Promise<{ valid: boolean; error?: string }>;
localize: (
input: LocalizerData,
onProgress?: LocalizerProgressFn,
) => Promise<LocalizerData["processableData"]>;
}
```
--------------------------------------------------------------------------------
/packages/cli/src/cli/cmd/logout.ts:
--------------------------------------------------------------------------------
```typescript
import { Command } from "interactive-commander";
import Ora from "ora";
import { getSettings, saveSettings } from "../utils/settings";
import {
renderClear,
renderSpacer,
renderBanner,
renderHero,
} from "../utils/ui";
export default new Command()
.command("logout")
.description("Log out by removing saved authentication credentials")
.helpOption("-h, --help", "Show help")
.action(async () => {
try {
await renderClear();
await renderSpacer();
await renderBanner();
await renderHero();
await renderSpacer();
const settings = await getSettings(undefined);
settings.auth.apiKey = "";
await saveSettings(settings);
Ora().succeed("Successfully logged out");
} catch (error: any) {
Ora().fail(error.message);
process.exit(1);
}
});
```
--------------------------------------------------------------------------------
/packages/react/src/client/component.tsx:
--------------------------------------------------------------------------------
```typescript
"use client";
import {
LingoComponent as LingoCoreComponent,
LingoComponentProps as LingoCoreComponentProps,
} from "../core";
import { useLingo } from "./context";
export type LingoComponentProps = Omit<LingoCoreComponentProps, "$dictionary">;
export function LingoComponent(props: LingoComponentProps) {
const { $as, $fileKey, $entryKey, ...rest } = props;
const lingo = useLingo();
return (
<LingoCoreComponent
$dictionary={lingo.dictionary}
$as={$as}
$fileKey={$fileKey}
$entryKey={$entryKey}
{...rest}
/>
);
}
export function LingoHtmlComponent(
props: React.HTMLAttributes<HTMLHtmlElement>,
) {
const lingo = useLingo();
return (
<html
{...props}
lang={lingo?.dictionary?.locale}
data-lingodotdev-compiler={lingo?.dictionary?.locale}
/>
);
}
```
--------------------------------------------------------------------------------
/.github/workflows/docker.yml:
--------------------------------------------------------------------------------
```yaml
name: Build Docker Image
on:
workflow_dispatch:
push:
paths:
- Dockerfile
branches:
- main
jobs:
docker:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build Docker image and push
uses: docker/build-push-action@v6
with:
push: true
platforms: linux/amd64
context: ./
file: ./Dockerfile
tags: ${{ secrets.DOCKERHUB_USERNAME }}/ci-action:latest
```
--------------------------------------------------------------------------------
/demo/vite-project/src/App.css:
--------------------------------------------------------------------------------
```css
#root {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
.logo-container {
display: flex;
justify-content: center;
align-items: center;
gap: 1rem;
}
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.react:hover {
filter: drop-shadow(0 0 2em #61dafbaa);
}
@keyframes logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@media (prefers-reduced-motion: no-preference) {
a:nth-of-type(2) .logo {
animation: logo-spin infinite 20s linear;
}
}
.welcome-text {
max-width: 500px;
margin: 0 auto;
}
.card {
padding: 2em;
}
.read-the-docs {
color: #888;
}
.locale-switcher {
position: absolute;
top: 0.5rem;
right: 1rem;
}
```
--------------------------------------------------------------------------------
/packages/cli/src/cli/processor/lingo.ts:
--------------------------------------------------------------------------------
```typescript
import { LingoDotDevEngine } from "@lingo.dev/_sdk";
import { LocalizerInput, LocalizerProgressFn } from "./_base";
export function createLingoLocalizer(params: {
apiKey?: string;
apiUrl: string;
}) {
return async (input: LocalizerInput, onProgress: LocalizerProgressFn) => {
if (!Object.keys(input.processableData).length) {
return input.processableData;
}
const lingo = new LingoDotDevEngine({
apiKey: params.apiKey,
apiUrl: params.apiUrl,
});
const result = await lingo.localizeObject(
input.processableData,
{
sourceLocale: input.sourceLocale,
targetLocale: input.targetLocale,
reference: {
[input.sourceLocale]: input.sourceData,
[input.targetLocale]: input.targetData,
},
},
onProgress,
);
return result;
};
}
```
--------------------------------------------------------------------------------
/demo/adonisjs/inertia/app/app.tsx:
--------------------------------------------------------------------------------
```typescript
/// <reference path="../../adonisrc.ts" />
/// <reference path="../../config/inertia.ts" />
import '../css/app.css'
import { hydrateRoot } from 'react-dom/client'
import { createInertiaApp } from '@inertiajs/react'
import { resolvePageComponent } from '@adonisjs/inertia/helpers'
import { LingoProviderWrapper, loadDictionary } from 'lingo.dev/react/client'
const appName = import.meta.env.VITE_APP_NAME || 'AdonisJS'
createInertiaApp({
progress: { color: '#5468FF' },
title: (title) => `${title} - ${appName}`,
resolve: (name) => {
return resolvePageComponent(`../pages/${name}.tsx`, import.meta.glob('../pages/**/*.tsx'))
},
setup({ el, App, props }) {
hydrateRoot(
el,
<LingoProviderWrapper loadDictionary={(locale) => loadDictionary(locale)}>
<App {...props} />
</LingoProviderWrapper>
)
},
})
```
--------------------------------------------------------------------------------
/packages/cli/src/cli/utils/key-matching.ts:
--------------------------------------------------------------------------------
```typescript
import { minimatch } from "minimatch";
/**
* Checks if a key matches any of the provided patterns using prefix or glob matching
*/
export function matchesKeyPattern(key: string, patterns: string[]): boolean {
return patterns.some(
(pattern) => key.startsWith(pattern) || minimatch(key, pattern),
);
}
/**
* Filters entries based on key matching patterns
*/
export function filterEntriesByPattern(
entries: [string, any][],
patterns: string[],
): [string, any][] {
return entries.filter(([key]) => matchesKeyPattern(key, patterns));
}
/**
* Formats a value for display, truncating long strings
*/
export function formatDisplayValue(value: any, maxLength = 50): string {
if (typeof value === "string") {
return value.length > maxLength
? `${value.substring(0, maxLength)}...`
: value;
}
return JSON.stringify(value);
}
```
--------------------------------------------------------------------------------
/packages/compiler/src/utils/jsx-expressions.ts:
--------------------------------------------------------------------------------
```typescript
import { NodePath } from "@babel/traverse";
import * as t from "@babel/types";
import { Expression } from "@babel/types";
export const getJsxExpressions = (nodePath: NodePath<t.JSXElement>) => {
const expressions: Expression[] = [];
nodePath.traverse({
JSXOpeningElement(path) {
path.skip();
},
JSXExpressionContainer(path) {
const expr = path.node.expression;
// Skip empty expressions, identifiers (variables), member expressions (object paths), and function calls
if (
!t.isJSXEmptyExpression(expr) &&
!t.isIdentifier(expr) &&
!t.isMemberExpression(expr) &&
!t.isCallExpression(expr) &&
!(t.isStringLiteral(expr) && expr.value === " ") // whitespace
) {
expressions.push(expr);
}
path.skip();
},
});
return t.arrayExpression(expressions);
};
```
--------------------------------------------------------------------------------
/packages/cli/demo/markdown/en/example.md:
--------------------------------------------------------------------------------
```markdown
---
title: "Product Launch Guide"
description: "Everything you need to know about our latest product features"
author: "Product Team"
date: 2024-01-15
tags: ["apples", "bananas", "pears"]
---
# Welcome to Our New Dashboard
Discover powerful new features designed to streamline your workflow and boost productivity.
## Getting Started
Follow these simple steps to set up your account and begin using our platform effectively.
---
Our advanced analytics help you make data-driven decisions with confidence.

The intuitive interface makes it easy to navigate between different features and tools.
[View documentation](https://example.com)
Need help getting started? Our support team is available 24/7 to assist you.
***
Join thousands of satisfied customers who have transformed their business with our platform.
```
--------------------------------------------------------------------------------
/packages/react/src/rsc/loader.spec.ts:
--------------------------------------------------------------------------------
```typescript
import { describe, it, expect } from "vitest";
import { loadDictionary_internal } from "./loader";
vi.mock("../core", () => {
return {
getDictionary: vi.fn(async (locale, loaders) => {
if (locale === "es") return { hello: "Hola" };
if (locale === "en") return { hello: "Hello" };
return {};
}),
};
});
import { getDictionary } from "../core";
describe("rsc/loader", () => {
describe("loadDictionary_internal", () => {
it("delegates to core getDictionary via internal wrapper", async () => {
const loaders = {
en: async () => ({ default: { hello: "Hello" } }),
es: async () => ({ default: { hello: "Hola" } }),
};
const result = await loadDictionary_internal("es", loaders);
expect(getDictionary).toHaveBeenCalledWith("es", loaders);
expect(result).toEqual({ hello: "Hola" });
});
});
});
```
--------------------------------------------------------------------------------
/packages/react/src/client/loader.spec.ts:
--------------------------------------------------------------------------------
```typescript
import { describe, it, expect } from "vitest";
import { loadDictionary_internal } from "./loader";
vi.mock("../core", () => {
return {
getDictionary: vi.fn(async (locale, loaders) => {
if (locale === "es") return { hello: "Hola" };
if (locale === "en") return { hello: "Hello" };
return {};
}),
};
});
import { getDictionary } from "../core";
describe("client/loader", () => {
describe("loadDictionary_internal", () => {
it("delegates to core getDictionary via internal wrapper", async () => {
const loaders = {
en: async () => ({ default: { hello: "Hello" } }),
es: async () => ({ default: { hello: "Hola" } }),
};
const result = await loadDictionary_internal("es", loaders);
expect(getDictionary).toHaveBeenCalledWith("es", loaders);
expect(result).toEqual({ hello: "Hola" });
});
});
});
```
--------------------------------------------------------------------------------
/demo/adonisjs/config/logger.ts:
--------------------------------------------------------------------------------
```typescript
import env from '#start/env'
import app from '@adonisjs/core/services/app'
import { defineConfig, targets } from '@adonisjs/core/logger'
const loggerConfig = defineConfig({
default: 'app',
/**
* The loggers object can be used to define multiple loggers.
* By default, we configure only one logger (named "app").
*/
loggers: {
app: {
enabled: true,
name: env.get('APP_NAME'),
level: env.get('LOG_LEVEL'),
transport: {
targets: targets()
.pushIf(!app.inProduction, targets.pretty())
.pushIf(app.inProduction, targets.file({ destination: 1 }))
.toArray(),
},
},
},
})
export default loggerConfig
/**
* Inferring types for the list of loggers you have configured
* in your application.
*/
declare module '@adonisjs/core/types' {
export interface LoggersList extends InferLoggers<typeof loggerConfig> {}
}
```
--------------------------------------------------------------------------------
/packages/cli/src/cli/cmd/show/config.ts:
--------------------------------------------------------------------------------
```typescript
import { Command } from "interactive-commander";
import _ from "lodash";
import fs from "fs";
import path from "path";
import { defaultConfig } from "@lingo.dev/_spec";
export default new Command()
.command("config")
.description("Print effective i18n.json after merging with defaults")
.helpOption("-h, --help", "Show help")
.action(async (options) => {
const fileConfig = loadReplexicaFileConfig();
const config = _.merge({}, defaultConfig, fileConfig);
console.log(JSON.stringify(config, null, 2));
});
function loadReplexicaFileConfig(): any {
const replexicaConfigPath = path.resolve(process.cwd(), "i18n.json");
const fileExists = fs.existsSync(replexicaConfigPath);
if (!fileExists) {
return undefined;
}
const fileContent = fs.readFileSync(replexicaConfigPath, "utf-8");
const replexicaFileConfig = JSON.parse(fileContent);
return replexicaFileConfig;
}
```
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "@lingo.dev",
"type": "module",
"scripts": {
"prepare": "husky",
"build": "turbo build",
"typecheck": "turbo typecheck",
"test": "turbo test",
"new": "changeset",
"new:empty": "changeset --empty",
"format": "prettier . --write",
"format:check": "prettier . --check"
},
"devDependencies": {
"@babel/generator": "^7.27.1",
"@babel/parser": "^7.27.1",
"@babel/traverse": "^7.27.4",
"@babel/types": "^7.27.1",
"@commitlint/cli": "^19.8.0",
"@commitlint/config-conventional": "^19.8.0",
"@types/babel__traverse": "^7.20.7",
"commitlint": "^19.7.1",
"husky": "^9.1.7",
"prettier": "^3.4.2",
"turbo": "^2.5.0"
},
"dependencies": {
"@changesets/changelog-github": "^0.5.0",
"@changesets/cli": "^2.27.10",
"minimatch": "^10.0.3",
"node-machine-id": "^1.1.12"
},
"packageManager": "[email protected]"
}
```
--------------------------------------------------------------------------------
/packages/compiler/src/utils/rc.spec.ts:
--------------------------------------------------------------------------------
```typescript
import { describe, it, expect, vi, beforeEach } from "vitest";
import { getRc } from "./rc";
vi.mock("os", () => ({ default: { homedir: () => "/home/test" } }));
vi.mock("fs", () => {
const mockFs = {
existsSync: vi.fn(() => false),
readFileSync: vi.fn(() => ""),
} as any;
return { ...mockFs, default: mockFs };
});
import fsAny from "fs";
describe("getRc", () => {
beforeEach(() => {
(fsAny as any).existsSync.mockReset().mockReturnValue(false);
(fsAny as any).readFileSync.mockReset().mockReturnValue("");
});
it("returns empty object when rc file missing", () => {
const data = getRc();
expect(data).toEqual({});
});
it("parses ini file when present", () => {
(fsAny as any).existsSync.mockReturnValue(true);
(fsAny as any).readFileSync.mockReturnValue("[auth]\napiKey=abc\n");
const data = getRc();
expect(data).toHaveProperty("auth.apiKey", "abc");
});
});
```
--------------------------------------------------------------------------------
/packages/cli/src/cli/loaders/dato/index.ts:
--------------------------------------------------------------------------------
```typescript
import fs from "fs";
import JSON5 from "json5";
import { composeLoaders } from "../_utils";
import { datoConfigSchema } from "./_base";
import createDatoFilterLoader from "./filter";
import createDatoApiLoader from "./api";
import createDatoExtractLoader from "./extract";
export default function createDatoLoader(configFilePath: string) {
try {
const configContent = fs.readFileSync(configFilePath, "utf-8");
const datoConfig = datoConfigSchema.parse(JSON5.parse(configContent));
return composeLoaders(
createDatoApiLoader(datoConfig, (updatedConfig) =>
fs.writeFileSync(
configFilePath,
JSON5.stringify(updatedConfig, null, 2),
),
),
createDatoFilterLoader(),
createDatoExtractLoader(),
);
} catch (error: any) {
throw new Error(
[`Failed to parse DatoCMS config file.`, `Error: ${error.message}`].join(
"\n\n",
),
);
}
}
```
--------------------------------------------------------------------------------
/demo/adonisjs/start/env.ts:
--------------------------------------------------------------------------------
```typescript
/*
|--------------------------------------------------------------------------
| Environment variables service
|--------------------------------------------------------------------------
|
| The `Env.create` method creates an instance of the Env service. The
| service validates the environment variables and also cast values
| to JavaScript data types.
|
*/
import { Env } from '@adonisjs/core/env'
export default await Env.create(new URL('../', import.meta.url), {
NODE_ENV: Env.schema.enum(['development', 'production', 'test'] as const),
PORT: Env.schema.number(),
APP_KEY: Env.schema.string(),
HOST: Env.schema.string({ format: 'host' }),
LOG_LEVEL: Env.schema.string(),
/*
|----------------------------------------------------------
| Variables for configuring session package
|----------------------------------------------------------
*/
SESSION_DRIVER: Env.schema.enum(['cookie', 'memory'] as const),
})
```
--------------------------------------------------------------------------------
/demo/adonisjs/config/shield.ts:
--------------------------------------------------------------------------------
```typescript
import { defineConfig } from '@adonisjs/shield'
const shieldConfig = defineConfig({
/**
* Configure CSP policies for your app. Refer documentation
* to learn more
*/
csp: {
enabled: false,
directives: {},
reportOnly: false,
},
/**
* Configure CSRF protection options. Refer documentation
* to learn more
*/
csrf: {
enabled: true,
exceptRoutes: [],
enableXsrfCookie: true,
methods: ['POST', 'PUT', 'PATCH', 'DELETE'],
},
/**
* Control how your website should be embedded inside
* iFrames
*/
xFrame: {
enabled: true,
action: 'DENY',
},
/**
* Force browser to always use HTTPS
*/
hsts: {
enabled: true,
maxAge: '180 days',
},
/**
* Disable browsers from sniffing the content type of a
* response and always rely on the "content-type" header.
*/
contentTypeSniffing: {
enabled: true,
},
})
export default shieldConfig
```
--------------------------------------------------------------------------------
/demo/adonisjs/vite.config.ts:
--------------------------------------------------------------------------------
```typescript
import { getDirname } from '@adonisjs/core/helpers'
import inertia from '@adonisjs/inertia/client'
import adonisjs from '@adonisjs/vite/client'
import react from '@vitejs/plugin-react'
import lingoCompiler from 'lingo.dev/compiler'
import { type UserConfig, type PluginOption } from 'vite'
const viteConfig: UserConfig = {
plugins: [
inertia({
ssr: {
enabled: true,
entrypoint: 'inertia/app/ssr.tsx',
},
}),
react(),
adonisjs({
entrypoints: ['inertia/app/app.tsx'],
reload: ['resources/views/**/*.edge'],
}) as unknown as PluginOption,
],
resolve: {
alias: {
'~/': `${getDirname(import.meta.url)}/inertia/`,
},
},
}
const withLingo = lingoCompiler.vite({
sourceRoot: 'inertia',
lingoDir: 'lingo',
sourceLocale: 'en',
targetLocales: ['es'],
rsc: false,
useDirective: false,
debug: false,
models: 'lingo.dev',
})
export default withLingo(viteConfig)
```
--------------------------------------------------------------------------------
/packages/compiler/src/jsx-attribute.spec.ts:
--------------------------------------------------------------------------------
```typescript
import { describe, it, expect } from "vitest";
import { jsxAttributeMutation } from "./jsx-attribute";
import { createPayload, createOutput, defaultParams } from "./_base";
function runMutation(code: string) {
const input = createPayload({ code, params: defaultParams, fileKey: "test" });
const mutated = jsxAttributeMutation(input);
if (!mutated) return code;
return createOutput(mutated).code;
}
describe("jsxAttributeMutation", () => {
it("should replace html element with localizable attributes with LingoAttributeComponent", () => {
const input = `
<p>
Lorem ipsum <a href="https://example.com" title="Dolor link">dolor</a> sit amet.
</p>
`;
const expected = `
<p>
Lorem ipsum
<LingoAttributeComponent
$as="a"
$attributes={{
title: "0/body/title",
"aria-label": "0/body/aria-label"
}}
href="https://example.com"
>
dolor
</LingoAttributeComponent>
sit amet.
</p>
`;
});
});
```
--------------------------------------------------------------------------------
/packages/cli/src/cli/loaders/xcode-strings.ts:
--------------------------------------------------------------------------------
```typescript
import { ILoader } from "./_types";
import { createLoader } from "./_utils";
import { Tokenizer } from "./xcode-strings/tokenizer";
import { Parser } from "./xcode-strings/parser";
import { escapeString } from "./xcode-strings/escape";
export default function createXcodeStringsLoader(): ILoader<
string,
Record<string, any>
> {
return createLoader({
async pull(locale, input) {
// Tokenize the input
const tokenizer = new Tokenizer(input);
const tokens = tokenizer.tokenize();
// Parse tokens into key-value pairs
const parser = new Parser(tokens);
const result = parser.parse();
return result;
},
async push(locale, payload) {
const lines = Object.entries(payload)
.filter(([_, value]) => value != null)
.map(([key, value]) => {
const escapedValue = escapeString(value);
return `"${key}" = "${escapedValue}";`;
});
return lines.join("\n");
},
});
}
```
--------------------------------------------------------------------------------
/packages/cli/src/cli/loaders/mdx2/sections-split-2.ts:
--------------------------------------------------------------------------------
```typescript
import { ILoader } from "../_types";
import { createLoader } from "../_utils";
import { PlaceholderedMdx, SectionedMdx } from "./_types";
import _ from "lodash";
export default function createMdxSectionsSplit2Loader(): ILoader<
PlaceholderedMdx,
SectionedMdx
> {
return createLoader({
async pull(locale, input) {
const sections = _.chain(input.content)
.split("\n\n")
.filter(Boolean)
.map((section, index) => [index, section])
.fromPairs()
.value();
const result: SectionedMdx = {
frontmatter: input.frontmatter,
sections,
};
return result;
},
async push(locale, data, originalInput, _originalLocale, pullInput) {
const content = _.chain(data.sections).values().join("\n\n").value();
const result: PlaceholderedMdx = {
frontmatter: data.frontmatter,
codePlaceholders: pullInput?.codePlaceholders || {},
content,
};
return result;
},
});
}
```
--------------------------------------------------------------------------------
/packages/compiler/src/jsx-remove-attributes.ts:
--------------------------------------------------------------------------------
```typescript
import { createCodeMutation, CompilerPayload } from "./_base";
import * as t from "@babel/types";
import traverse from "@babel/traverse";
import { NodePath } from "@babel/traverse";
/**
* This mutation identifies JSX elements with data-jsx-* attributes and removes them
*/
export const jsxRemoveAttributesMutation = createCodeMutation(
(payload: CompilerPayload) => {
const ATTRIBUTES_TO_REMOVE = [
"data-jsx-root",
"data-jsx-scope",
"data-jsx-attribute-scope",
];
traverse(payload.ast, {
JSXElement(path: NodePath<t.JSXElement>) {
const openingElement = path.node.openingElement;
openingElement.attributes = openingElement.attributes.filter((attr) => {
const removeAttr =
t.isJSXAttribute(attr) &&
t.isJSXIdentifier(attr.name) &&
ATTRIBUTES_TO_REMOVE.includes(attr.name.name as string);
return !removeAttr;
});
},
});
return {
...payload,
};
},
);
```
--------------------------------------------------------------------------------
/packages/cli/demo/markdown/es/example.md:
--------------------------------------------------------------------------------
```markdown
---
title: Guía de lanzamiento de producto
description: Todo lo que necesitas saber sobre las últimas características de
nuestro producto
author: Equipo de producto
date: 2024-01-15
tags:
- apples
- bananas
- pears
---
# Bienvenido a nuestro nuevo panel de control
Descubre nuevas y potentes funciones diseñadas para optimizar tus flujos de trabajo y aumentar la productividad.
## Primeros pasos
Sigue estos sencillos pasos para configurar tu cuenta y comenzar a utilizar nuestra plataforma de manera efectiva.
---
Nuestros análisis avanzados te ayudan a tomar decisiones basadas en datos con confianza.

La interfaz intuitiva facilita la navegación entre diferentes funciones y herramientas.
[Ver documentación](https://example.com)
¿Necesitas ayuda para empezar? Nuestro equipo de soporte está disponible 24/7 para asistirte.
---
Únete a miles de clientes satisfechos que han transformado su negocio con nuestra plataforma.
```
--------------------------------------------------------------------------------
/demo/adonisjs/config/session.ts:
--------------------------------------------------------------------------------
```typescript
import env from '#start/env'
import app from '@adonisjs/core/services/app'
import { defineConfig, stores } from '@adonisjs/session'
const sessionConfig = defineConfig({
enabled: true,
cookieName: 'adonis-session',
/**
* When set to true, the session id cookie will be deleted
* once the user closes the browser.
*/
clearWithBrowser: false,
/**
* Define how long to keep the session data alive without
* any activity.
*/
age: '2h',
/**
* Configuration for session cookie and the
* cookie store
*/
cookie: {
path: '/',
httpOnly: true,
secure: app.inProduction,
sameSite: 'lax',
},
/**
* The store to use. Make sure to validate the environment
* variable in order to infer the store name without any
* errors.
*/
store: env.get('SESSION_DRIVER'),
/**
* List of configured stores. Refer documentation to see
* list of available stores and their config.
*/
stores: {
cookie: stores.cookie(),
},
})
export default sessionConfig
```
--------------------------------------------------------------------------------
/packages/compiler/src/jsx-html-lang.spec.ts:
--------------------------------------------------------------------------------
```typescript
import { describe, it, expect } from "vitest";
import { createPayload, createOutput, defaultParams } from "./_base";
import { jsxHtmlLangMutation } from "./jsx-html-lang";
function run(code: string, rsc = true) {
const input = createPayload({
code,
params: { ...defaultParams, rsc },
relativeFilePath: "app/layout.tsx",
} as any);
const mutated = jsxHtmlLangMutation(input);
return createOutput(mutated!).code.trim();
}
describe("jsxHtmlLangMutation", () => {
it("replaces html tag with framework component in server mode", () => {
const input = `
export default function Root() {
return <html><body>Hi</body></html>
}`.trim();
const out = run(input, true);
expect(out).toMatch(/LingoHtmlComponent/);
});
it("replaces html tag with framework component in client mode", () => {
const input = `
"use client";
export default function Root() {
return <html><body>Hi</body></html>
}`.trim();
const out = run(input, false);
expect(out).toMatch(/LingoHtmlComponent/);
});
});
```
--------------------------------------------------------------------------------
/packages/cli/src/cli/utils/config.ts:
--------------------------------------------------------------------------------
```typescript
import _ from "lodash";
import fs from "fs";
import path from "path";
import { I18nConfig, parseI18nConfig } from "@lingo.dev/_spec";
export function getConfig(resave = true): I18nConfig | null {
const configFilePath = _getConfigFilePath();
const configFileExists = fs.existsSync(configFilePath);
if (!configFileExists) {
return null;
}
const fileContents = fs.readFileSync(configFilePath, "utf8");
const rawConfig = JSON.parse(fileContents);
const result = parseI18nConfig(rawConfig);
const didConfigChange = !_.isEqual(rawConfig, result);
if (resave && didConfigChange) {
// Ensure the config is saved with the latest version / schema
saveConfig(result);
}
return result;
}
export function saveConfig(config: I18nConfig) {
const configFilePath = _getConfigFilePath();
const serialized = JSON.stringify(config, null, 2);
fs.writeFileSync(configFilePath, serialized);
return config;
}
// Private
function _getConfigFilePath() {
return path.join(process.cwd(), "i18n.json");
}
```
--------------------------------------------------------------------------------
/demo/next-app/public/globe.svg:
--------------------------------------------------------------------------------
```
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
```
--------------------------------------------------------------------------------
/legacy/cli/bin/cli.mjs:
--------------------------------------------------------------------------------
```
#!/usr/bin/env node
import CLI from "lingo.dev/cli";
const envVarConfigWarning = process.env.LINGODOTDEV_API_KEY
? "\nThis version is not compatible with LINGODOTDEV_API_KEY env variable."
: "";
console.warn(
"\x1b[33m%s\x1b[0m",
`
⚠️ WARNING: NEW PACKAGE AVAILABLE ⚠️
================================================================================
This CLI version is deprecated.${envVarConfigWarning}
Please use lingo.dev instead:
npx lingo.dev@latest
Visit https://lingo.dev for more information.
================================================================================
`,
);
process.env.LINGODOTDEV_API_KEY = process.env.REPLEXICA_API_KEY;
process.env.LINGODOTDEV_PULL_REQUEST = process.env.REPLEXICA_PULL_REQUEST;
process.env.LINGODOTDEV_PULL_REQUEST_TITLE =
process.env.REPLEXICA_PULL_REQUEST_TITLE;
process.env.LINGODOTDEV_COMMIT_MESSAGE = process.env.REPLEXICA_COMMIT_MESSAGE;
process.env.LINGODOTDEV_WORKING_DIRECTORY =
process.env.REPLEXICA_WORKING_DIRECTORY;
await CLI.parseAsync(process.argv);
```
--------------------------------------------------------------------------------
/packages/cli/src/cli/loaders/formatters/_base.ts:
--------------------------------------------------------------------------------
```typescript
import path from "path";
import { ILoader } from "../_types";
import { createLoader } from "../_utils";
export type BaseFormatterOptions = {
bucketPathPattern: string;
stage?: "pull" | "push" | "both";
alwaysFormat?: boolean;
};
export function createBaseFormatterLoader(
options: BaseFormatterOptions,
formatFn: (data: string, filePath: string) => Promise<string>,
): ILoader<string, string> {
const stage = options.stage || "both";
const formatData = async (locale: string, data: string) => {
const draftPath = options.bucketPathPattern.replaceAll("[locale]", locale);
const finalPath = path.resolve(draftPath);
return await formatFn(data, finalPath);
};
return createLoader({
async pull(locale, data) {
if (!["pull", "both"].includes(stage)) {
return data;
}
return await formatData(locale, data);
},
async push(locale, data) {
if (!["push", "both"].includes(stage)) {
return data;
}
return await formatData(locale, data);
},
});
}
```
--------------------------------------------------------------------------------
/packages/react/src/rsc/component.tsx:
--------------------------------------------------------------------------------
```typescript
import {
LingoComponent as LingoCoreComponent,
LingoComponentProps as LingoCoreComponentProps,
} from "../core";
import { loadDictionaryFromRequest, loadLocaleFromCookies } from "./utils";
export type LingoComponentProps = Omit<
LingoCoreComponentProps,
"$dictionary"
> & {
$loadDictionary: (locale: string | null) => Promise<any>;
};
export async function LingoComponent(props: LingoComponentProps) {
const { $as, $fileKey, $entryKey, $loadDictionary, ...rest } = props;
const dictionary = await loadDictionaryFromRequest($loadDictionary);
if ($as.name === "LingoAttributeComponent") {
rest.$loadDictionary = $loadDictionary;
}
return (
<LingoCoreComponent
{...rest}
$dictionary={dictionary}
$as={$as}
$fileKey={$fileKey}
$entryKey={$entryKey}
/>
);
}
export async function LingoHtmlComponent(
props: React.HTMLAttributes<HTMLHtmlElement>,
) {
const locale = await loadLocaleFromCookies();
return <html {...props} lang={locale} data-lingodotdev-compiler={locale} />;
}
```
--------------------------------------------------------------------------------
/packages/compiler/src/jsx-provider.spec.ts:
--------------------------------------------------------------------------------
```typescript
import { describe, it, expect } from "vitest";
import { createPayload, createOutput, defaultParams } from "./_base";
import jsxProviderMutation from "./jsx-provider";
function run(code: string, rsc = true) {
const input = createPayload({
code,
params: { ...defaultParams, rsc },
relativeFilePath: "app/layout.tsx",
} as any);
const mutated = jsxProviderMutation(input);
return createOutput(mutated!).code.trim();
}
describe("jsxProviderMutation", () => {
it("wraps <html> with LingoProvider in server mode", () => {
const input = `
export default function Root() {
return <html><body>Hi</body></html>
}`.trim();
const out = run(input, true);
expect(out).toContain("LingoProvider");
expect(out).toContain("loadDictionary");
});
it("does not modify in client mode", () => {
const input = `
export default function Root() {
return <html><body>Hi</body></html>
}`.trim();
const out = run(input, false);
expect(out).toContain("<html>");
expect(out).not.toContain("LingoProvider");
});
});
```
--------------------------------------------------------------------------------
/packages/compiler/src/utils/env.spec.ts:
--------------------------------------------------------------------------------
```typescript
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { isRunningInCIOrDocker } from "./env";
vi.mock("fs", () => {
const mockFs = { existsSync: vi.fn(() => false) } as any;
return { ...mockFs, default: mockFs };
});
import fsAny from "fs";
describe("isRunningInCIOrDocker", () => {
const originalEnv = { ...process.env };
beforeEach(() => {
process.env = { ...originalEnv };
(fsAny as any).existsSync.mockReset().mockReturnValue(false);
});
afterEach(() => {
process.env = originalEnv;
});
it("returns true when CI env var is set", () => {
process.env.CI = "true";
expect(isRunningInCIOrDocker()).toBe(true);
});
it("returns true when /.dockerenv exists", () => {
(fsAny as any).existsSync.mockReturnValueOnce(true);
delete process.env.CI;
expect(isRunningInCIOrDocker()).toBe(true);
});
it("returns false otherwise", () => {
delete process.env.CI;
(fsAny as any).existsSync.mockReturnValueOnce(false);
expect(isRunningInCIOrDocker()).toBe(false);
});
});
```
--------------------------------------------------------------------------------
/packages/cli/src/cli/cmd/show/locale.ts:
--------------------------------------------------------------------------------
```typescript
import { Command } from "interactive-commander";
import _ from "lodash";
import Z from "zod";
import Ora from "ora";
import { localeCodes } from "@lingo.dev/_spec";
import { CLIError } from "../../utils/errors";
export default new Command()
.command("locale")
.description("List supported locale codes")
.helpOption("-h, --help", "Show help")
// argument can be equal either "sources" or "targets"
.argument(
"<type>",
'Type of locales to show: "sources" or "targets" - both show the full supported locale list',
)
.action(async (type) => {
const ora = Ora();
try {
switch (type) {
default:
throw new CLIError({
message: `Invalid type: ${type}`,
docUrl: "invalidType",
});
case "sources":
localeCodes.forEach((locale) => console.log(locale));
break;
case "targets":
localeCodes.forEach((locale) => console.log(locale));
break;
}
} catch (error: any) {
ora.fail(error.message);
process.exit(1);
}
});
```
--------------------------------------------------------------------------------
/packages/compiler/src/jsx-attribute-flag.ts:
--------------------------------------------------------------------------------
```typescript
import { createCodeMutation, CompilerPayload } from "./_base";
import * as t from "@babel/types";
import { getJsxAttributeScopes } from "./utils/jsx-attribute-scope";
import { getAstKey } from "./utils/ast-key";
/**
* This mutation identifies JSX elements with localizable attributes
* and adds a data-jsx-attributes attribute with an array of the attribute names
*/
const jsxAttributeFlagMutation = createCodeMutation(
(payload: CompilerPayload) => {
const jsxScopes = getJsxAttributeScopes(payload.ast);
for (const [jsxScope, attributes] of jsxScopes) {
const scopeKey = getAstKey(jsxScope);
jsxScope.node.openingElement.attributes.push(
t.jsxAttribute(
t.jsxIdentifier("data-jsx-attribute-scope"),
t.jsxExpressionContainer(
t.arrayExpression(
attributes.map((attr) =>
t.stringLiteral(`${attr}:${scopeKey}-${attr}`),
),
),
),
),
);
}
return {
...payload,
};
},
);
export default jsxAttributeFlagMutation;
```
--------------------------------------------------------------------------------
/demo/adonisjs/config/app.ts:
--------------------------------------------------------------------------------
```typescript
import env from '#start/env'
import app from '@adonisjs/core/services/app'
import { Secret } from '@adonisjs/core/helpers'
import { defineConfig } from '@adonisjs/core/http'
/**
* The app key is used for encrypting cookies, generating signed URLs,
* and by the "encryption" module.
*
* The encryption module will fail to decrypt data if the key is lost or
* changed. Therefore it is recommended to keep the app key secure.
*/
export const appKey = new Secret(env.get('APP_KEY'))
/**
* The configuration settings used by the HTTP server
*/
export const http = defineConfig({
generateRequestId: true,
allowMethodSpoofing: false,
/**
* Enabling async local storage will let you access HTTP context
* from anywhere inside your application.
*/
useAsyncLocalStorage: false,
/**
* Manage cookies configuration. The settings for the session id cookie are
* defined inside the "config/session.ts" file.
*/
cookie: {
domain: '',
path: '/',
maxAge: '2h',
httpOnly: true,
secure: app.inProduction,
sameSite: 'lax',
},
})
```
--------------------------------------------------------------------------------
/packages/cli/src/cli/loaders/formatters/index.ts:
--------------------------------------------------------------------------------
```typescript
import createPrettierLoader, { PrettierLoaderOptions } from "./prettier";
import createBiomeLoader from "./biome";
import { ILoader } from "../_types";
import { Options } from "prettier";
export type FormatterType = "prettier" | "biome" | undefined;
export type ParserType = Options["parser"];
export function createFormatterLoader(
formatterType: FormatterType,
parser: ParserType,
bucketPathPattern: string,
): ILoader<string, string> {
// If explicitly set to undefined, auto-detect (prefer prettier for backward compatibility)
if (formatterType === undefined) {
return createPrettierLoader({ parser, bucketPathPattern });
}
if (formatterType === "prettier") {
return createPrettierLoader({ parser, bucketPathPattern });
}
if (formatterType === "biome") {
return createBiomeLoader({ bucketPathPattern });
}
throw new Error(`Unknown formatter: ${formatterType}`);
}
// Re-export for direct access if needed
export { createPrettierLoader, createBiomeLoader };
export type { PrettierLoaderOptions };
export type { BiomeLoaderOptions } from "./biome";
```
--------------------------------------------------------------------------------
/packages/cli/src/cli/loaders/srt.ts:
--------------------------------------------------------------------------------
```typescript
import { ILoader } from "./_types";
import { createLoader } from "./_utils";
import srtParser from "srt-parser-2";
export default function createSrtLoader(): ILoader<
string,
Record<string, any>
> {
const parser = new srtParser();
return createLoader({
async pull(locale, input) {
const parsed = parser.fromSrt(input) || [];
const result: Record<string, string> = {};
parsed.forEach((entry) => {
const key = `${entry.id}#${entry.startTime}-${entry.endTime}`;
result[key] = entry.text;
});
return result;
},
async push(locale, payload) {
const output = Object.entries(payload).map(([key, text]) => {
const [id, timeRange] = key.split("#");
const [startTime, endTime] = timeRange.split("-");
return {
id: id,
startTime: startTime,
startSeconds: 0,
endTime: endTime,
endSeconds: 0,
text: text,
};
});
const srtContent = parser.toSrt(output).trim().replace(/\r?\n/g, "\n");
return srtContent;
},
});
}
```
--------------------------------------------------------------------------------
/packages/cli/src/cli/loaders/mdx2/localizable-document.spec.ts:
--------------------------------------------------------------------------------
```typescript
import { describe, it, expect } from "vitest";
import createLocalizableMdxDocumentLoader from "./localizable-document";
describe("mdx localizable document loader", () => {
it("should map to meta/content on pull and reconstruct on push", async () => {
const loader = createLocalizableMdxDocumentLoader();
loader.setDefaultLocale("en");
const headingSection = "## Heading One\nSome paragraph.";
const pulled = await loader.pull("en", {
frontmatter: {
title: "Sample",
},
sections: {
"0": headingSection,
},
});
// Validate structure
expect(pulled).toHaveProperty("meta");
expect(pulled).toHaveProperty("content");
// Expect meta matches frontmatter
expect(pulled.meta.title).toBe("Sample");
// Modify
pulled.meta.title = "Hola";
// Try push
const pushed = await loader.push("es", pulled);
// After push we should get original MDX string reflect changes
expect(pushed.frontmatter.title).toBe("Hola");
// sections should persist
expect(pushed.sections["0"]).toBe(headingSection);
});
});
```
--------------------------------------------------------------------------------
/packages/compiler/src/lib/lcp/api/shots.ts:
--------------------------------------------------------------------------------
```typescript
import { z } from "zod";
import { dictionarySchema } from "../schema";
export default [
// Shot #1
[
{
version: 0.1,
locale: "en",
files: {
"demo-app/my-custom-header.tsx": {
entries: {
"1z2x3c4v": "Dashboard",
"5t6y7u8i": "Settings",
"9o0p1q2r": "Logout",
},
},
"demo-app/my-custom-footer.tsx": {
entries: {
"9k0l1m2n": "© 2025 Lingo.dev. All rights reserved.",
},
},
},
},
{
version: 0.1,
locale: "es",
files: {
"demo-app/my-custom-header.tsx": {
entries: {
"1z2x3c4v": "Panel de control",
"5t6y7u8i": "Configuración",
"9o0p1q2r": "Cerrar sesión",
},
},
"demo-app/my-custom-footer.tsx": {
entries: {
"9k0l1m2n": "© 2025 Lingo.dev. Todos los derechos reservados.",
},
},
},
},
],
// More shots here...
] satisfies [
z.infer<typeof dictionarySchema>,
z.infer<typeof dictionarySchema>,
][];
```
--------------------------------------------------------------------------------
/packages/cli/src/cli/loaders/vue-json.ts:
--------------------------------------------------------------------------------
```typescript
import { jsonrepair } from "jsonrepair";
import { ILoader } from "./_types";
import { createLoader } from "./_utils";
export default function createVueJsonLoader(): ILoader<
string,
Record<string, any>
> {
return createLoader({
pull: async (locale, input, ctx) => {
const parsed = parseVueFile(input);
return parsed?.i18n?.[locale] ?? {};
},
push: async (locale, data, originalInput) => {
const parsed = parseVueFile(originalInput ?? "");
if (!parsed) {
return originalInput ?? "";
}
parsed.i18n[locale] = data;
return `${parsed.before}<i18n>\n${JSON.stringify(
parsed.i18n,
null,
2,
)}\n</i18n>${parsed.after}`;
},
});
}
function parseVueFile(input: string) {
const match = input.match(/^([\s\S]*)<i18n>([\s\S]*)<\/i18n>([\s\S]*)$/);
if (!match) {
return null;
}
const [, before, jsonString = "{}", after] = match;
let i18n: Record<string, any>;
try {
i18n = JSON.parse(jsonString);
} catch (error) {
i18n = JSON.parse(jsonrepair(jsonString));
}
return { before, after, i18n };
}
```
--------------------------------------------------------------------------------
/packages/cli/src/cli/utils/update-gitignore.ts:
--------------------------------------------------------------------------------
```typescript
import fs from "fs";
import path from "path";
export default function updateGitignore() {
const cacheFile = "i18n.cache";
const projectRoot = findCurrentProjectRoot();
if (!projectRoot) {
return;
}
const gitignorePath = path.join(projectRoot, ".gitignore");
if (!fs.existsSync(gitignorePath)) {
return;
}
const gitignore = fs.readFileSync(gitignorePath, "utf8").split("\n");
const cacheIsIgnored = gitignore.includes(cacheFile);
if (!cacheIsIgnored) {
let content = "";
// Ensure there's a trailing newline
content = fs.readFileSync(gitignorePath, "utf8");
if (content !== "" && !content.endsWith("\n")) {
content += "\n";
}
content += `${cacheFile}\n`;
fs.writeFileSync(gitignorePath, content);
}
}
function findCurrentProjectRoot() {
let currentDir = process.cwd();
while (currentDir !== path.parse(currentDir).root) {
const gitDirPath = path.join(currentDir, ".git");
if (fs.existsSync(gitDirPath) && fs.lstatSync(gitDirPath).isDirectory()) {
return currentDir;
}
currentDir = path.dirname(currentDir);
}
return null;
}
```
--------------------------------------------------------------------------------
/packages/cli/demo/vue-json/example.vue:
--------------------------------------------------------------------------------
```vue
<template>
<div class="container">
<h1>{{ $t('welcome') }}</h1>
<p>{{ $t('description') }}</p>
<button @click="handleClick">{{ $t('button.submit') }}</button>
</div>
</template>
<script>
export default {
name: 'ExampleComponent',
methods: {
handleClick() {
console.log('Button clicked');
}
}
}
</script>
<style scoped>
.container {
padding: 20px;
font-family: Arial, sans-serif;
}
h1 {
color: #333;
}
</style>
<i18n>
{
"en": {
"welcome": "Hello, world!",
"description": "A simple demo app",
"button": {
"submit": "Submit",
"cancel": "Cancel"
},
"messages": [
"Hello from MyApp",
"Welcome message"
],
"metadata": {
"active": true,
"maxItems": 10,
"note": null
}
},
"es": {
"welcome": "¡Hola, mundo!",
"description": "Una aplicación de demostración simple",
"button": {
"submit": "Enviar",
"cancel": "Cancelar"
},
"messages": [
"Hola desde MyApp",
"Mensaje de bienvenida"
],
"metadata": {
"active": true,
"maxItems": 10,
"note": null
}
}
}
</i18n>
```
--------------------------------------------------------------------------------
/packages/cli/src/cli/cmd/show/locked-keys.ts:
--------------------------------------------------------------------------------
```typescript
import { Command } from "interactive-commander";
import Ora from "ora";
import { getConfig } from "../../utils/config";
import { CLIError } from "../../utils/errors";
import { getBuckets } from "../../utils/buckets";
import { executeKeyCommand } from "./_shared-key-command";
export default new Command()
.command("locked-keys")
.description(
"Show which key-value pairs in source files match lockedKeys patterns",
)
.option("--bucket <name>", "Only show locked keys for a specific bucket")
.helpOption("-h, --help", "Show help")
.action(async (options) => {
const ora = Ora();
try {
const i18nConfig = await getConfig();
if (!i18nConfig) {
throw new CLIError({
message:
"i18n.json not found. Please run `lingo.dev init` to initialize the project.",
docUrl: "i18nNotFound",
});
}
const buckets = getBuckets(i18nConfig);
await executeKeyCommand(i18nConfig, buckets, options, {
filterType: "lockedKeys",
displayName: "locked",
});
} catch (error: any) {
ora.fail(error.message);
process.exit(1);
}
});
```
--------------------------------------------------------------------------------
/demo/vite-project/src/index.css:
--------------------------------------------------------------------------------
```css
:root {
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
}
```
--------------------------------------------------------------------------------
/packages/cli/src/cli/cmd/show/ignored-keys.ts:
--------------------------------------------------------------------------------
```typescript
import { Command } from "interactive-commander";
import Ora from "ora";
import { getConfig } from "../../utils/config";
import { CLIError } from "../../utils/errors";
import { getBuckets } from "../../utils/buckets";
import { executeKeyCommand } from "./_shared-key-command";
export default new Command()
.command("ignored-keys")
.description(
"Show which key-value pairs in source files match ignoredKeys patterns",
)
.option("--bucket <name>", "Only show ignored keys for a specific bucket")
.helpOption("-h, --help", "Show help")
.action(async (options) => {
const ora = Ora();
try {
const i18nConfig = await getConfig();
if (!i18nConfig) {
throw new CLIError({
message:
"i18n.json not found. Please run `lingo.dev init` to initialize the project.",
docUrl: "i18nNotFound",
});
}
const buckets = getBuckets(i18nConfig);
await executeKeyCommand(i18nConfig, buckets, options, {
filterType: "ignoredKeys",
displayName: "ignored",
});
} catch (error: any) {
ora.fail(error.message);
process.exit(1);
}
});
```
--------------------------------------------------------------------------------
/demo/adonisjs/tests/bootstrap.ts:
--------------------------------------------------------------------------------
```typescript
import { assert } from '@japa/assert'
import app from '@adonisjs/core/services/app'
import type { Config } from '@japa/runner/types'
import { pluginAdonisJS } from '@japa/plugin-adonisjs'
import testUtils from '@adonisjs/core/services/test_utils'
/**
* This file is imported by the "bin/test.ts" entrypoint file
*/
/**
* Configure Japa plugins in the plugins array.
* Learn more - https://japa.dev/docs/runner-config#plugins-optional
*/
export const plugins: Config['plugins'] = [assert(), pluginAdonisJS(app)]
/**
* Configure lifecycle function to run before and after all the
* tests.
*
* The setup functions are executed before all the tests
* The teardown functions are executed after all the tests
*/
export const runnerHooks: Required<Pick<Config, 'setup' | 'teardown'>> = {
setup: [],
teardown: [],
}
/**
* Configure suites by tapping into the test suite instance.
* Learn more - https://japa.dev/docs/test-suites#lifecycle-hooks
*/
export const configureSuite: Config['configureSuite'] = (suite) => {
if (['browser', 'functional', 'e2e'].includes(suite.name)) {
return suite.setup(() => testUtils.httpServer().start())
}
}
```
--------------------------------------------------------------------------------
/demo/adonisjs/inertia/pages/home.tsx:
--------------------------------------------------------------------------------
```typescript
import { Head } from '@inertiajs/react'
import { LocaleSwitcher } from 'lingo.dev/react/client'
export default function Home() {
return (
<>
<Head title="Homepage" />
<div className="min-h-screen flex items-center justify-center bg-gray-50">
<div className="max-w-lg mx-auto p-8 bg-white rounded-lg shadow-sm text-center">
<h1 className="text-3xl font-bold text-gray-900 mb-6">Hello, world!</h1>
<p className="text-gray-700 mb-4 leading-relaxed">
This is an example app that demonstrates how{' '}
<strong className="text-gray-900">Lingo.dev Compiler</strong> can be used to localize{' '}
apps built with{' '}
<a href="https://adonisjs.com/" className="text-blue-600 hover:text-blue-800 underline">
AdonisJS
</a>
.
</p>
<p className="text-gray-700 mb-6 leading-relaxed">
To switch between locales, use the following dropdown:
</p>
<div className="flex justify-center">
<LocaleSwitcher locales={['en', 'es']} />
</div>
</div>
</div>
</>
)
}
```
--------------------------------------------------------------------------------
/packages/compiler/src/jsx-html-lang.ts:
--------------------------------------------------------------------------------
```typescript
import traverse from "@babel/traverse";
import * as t from "@babel/types";
import { createCodeMutation } from "./_base";
import { getJsxElementName } from "./utils/jsx-element";
import { getModuleExecutionMode, getOrCreateImport } from "./utils";
import { ModuleId } from "./_const";
export const jsxHtmlLangMutation = createCodeMutation((payload) => {
traverse(payload.ast, {
JSXElement: (path) => {
if (getJsxElementName(path)?.toLowerCase() === "html") {
const mode = getModuleExecutionMode(payload.ast, payload.params.rsc);
const packagePath =
mode === "client" ? ModuleId.ReactClient : ModuleId.ReactRSC;
const lingoHtmlComponentImport = getOrCreateImport(payload.ast, {
moduleName: packagePath,
exportedName: "LingoHtmlComponent",
});
path.node.openingElement.name = t.jsxIdentifier(
lingoHtmlComponentImport.importedName,
);
if (path.node.closingElement) {
path.node.closingElement.name = t.jsxIdentifier(
lingoHtmlComponentImport.importedName,
);
}
path.skip();
}
},
});
return payload;
});
```
--------------------------------------------------------------------------------
/packages/locales/src/constants.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Shared constants for locale parsing and validation
*/
/**
* Regular expression for parsing locale strings
*
* This regex is case-sensitive and expects normalized locale strings:
* - Language code: 2-3 lowercase letters (e.g., "en", "zh", "es")
* - Script code: 4 letters with preserved case (e.g., "Hans", "hans", "Cyrl")
* - Region code: 2-3 uppercase letters or digits (e.g., "US", "CN", "123")
*
* Matches locale strings in the format: language[-_]script?[-_]region?
*
* Groups:
* 1. Language code (2-3 lowercase letters)
* 2. Script code (4 letters, optional)
* 3. Region code (2-3 letters or digits, optional)
*
* Examples:
* - "en" -> language: "en"
* - "en-US" -> language: "en", region: "US"
* - "zh-Hans-CN" -> language: "zh", script: "Hans", region: "CN"
* - "sr_Cyrl_RS" -> language: "sr", script: "Cyrl", region: "RS"
*
* Note: The parser automatically normalizes case before applying this regex:
* - Language codes are converted to lowercase
* - Script codes preserve their original case
* - Region codes are converted to uppercase
*/
export const LOCALE_REGEX =
/^([a-z]{2,3})(?:[-_]([A-Za-z]{4}))?(?:[-_]([A-Z]{2}|[0-9]{3}))?$/;
```
--------------------------------------------------------------------------------
/packages/cli/src/cli/loaders/xcode-stringsdict.ts:
--------------------------------------------------------------------------------
```typescript
import plist from "plist";
import { ILoader } from "./_types";
import { createLoader } from "./_utils";
import { CLIError } from "../utils/errors";
const emptyData = [
'<?xml version="1.0" encoding="UTF-8"?>',
'<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
'<plist version="1.0">',
"<dict/>",
"</plist>",
].join("\n");
export default function createXcodeStringsdictLoader(): ILoader<
string,
Record<string, any>
> {
return createLoader({
async pull(locale, input) {
try {
const parsed = plist.parse(input || emptyData);
if (typeof parsed !== "object" || parsed === null) {
throw new CLIError({
message: "Invalid .stringsdict format",
docUrl: "invalidStringDict",
});
}
return parsed as Record<string, any>;
} catch (error: any) {
throw new CLIError({
message: `Invalid .stringsdict format: ${error.message}`,
docUrl: "invalidStringDict",
});
}
},
async push(locale, payload) {
const plistContent = plist.build(payload);
return plistContent;
},
});
}
```
--------------------------------------------------------------------------------
/packages/cli/src/cli/loaders/properties.ts:
--------------------------------------------------------------------------------
```typescript
import { ILoader } from "./_types";
import { createLoader } from "./_utils";
export default function createPropertiesLoader(): ILoader<
string,
Record<string, any>
> {
return createLoader({
async pull(locale, text) {
const result: Record<string, string> = {};
const lines = text.split("\n");
for (const line of lines) {
const trimmed = line.trim();
// Skip empty lines and comments
if (isSkippableLine(trimmed)) {
continue;
}
const { key, value } = parsePropertyLine(trimmed);
if (key) {
result[key] = value;
}
}
return result;
},
async push(locale, payload) {
const result = Object.entries(payload)
.filter(([_, value]) => value != null)
.map(([key, value]) => `${key}=${value}`)
.join("\n");
return result;
},
});
}
function isSkippableLine(line: string): boolean {
return !line || line.startsWith("#");
}
function parsePropertyLine(line: string): { key: string; value: string } {
const [key, ...valueParts] = line.split("=");
return {
key: key?.trim() || "",
value: valueParts.join("=").trim(),
};
}
```
--------------------------------------------------------------------------------
/demo/next-app/src/app/layout.tsx:
--------------------------------------------------------------------------------
```typescript
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
// Compiler: add imports
import { LocaleSwitcher } from "lingo.dev/react/client";
import { LingoProvider, loadDictionary } from "lingo.dev/react/rsc";
const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});
const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
// Compiler: wrap with LingoProvider and render LocaleSwitcher
return (
<LingoProvider loadDictionary={(locale) => loadDictionary(locale)}>
<html lang="en">
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
<div className="absolute top-2 right-3">
<LocaleSwitcher
locales={["en", "es", "zh", "ja", "fr", "de", "ru", "ar", "ko"]}
/>
</div>
{children}
</body>
</html>
</LingoProvider>
);
}
```
--------------------------------------------------------------------------------
/packages/cli/tests/mock-storage.ts:
--------------------------------------------------------------------------------
```typescript
import { vi } from "vitest";
// Types
interface MockStorage {
clear(): void;
set(files: Record<string, string>): void;
}
// Global storage type
declare global {
var __mockStorage: Record<string, string>;
}
// Initialize global storage
globalThis.__mockStorage = {};
// Create mock storage singleton
export const mockStorage: MockStorage = {
clear: () => {
globalThis.__mockStorage = {};
},
set: (files: Record<string, string>) => {
mockStorage.clear();
Object.entries(files).forEach(([path, content]) => {
const fullPath = `${process.cwd()}/${path}`;
globalThis.__mockStorage[fullPath] = content;
});
},
};
// Setup fs mock
vi.mock("fs/promises", () => ({
default: {
readFile: vi.fn(async (path: string) => {
const content = globalThis.__mockStorage[path];
if (!content) throw new Error(`File not found: ${path}`);
return content;
}),
writeFile: vi.fn((path, content) => {
globalThis.__mockStorage[path] = content;
return Promise.resolve();
}),
mkdir: vi.fn(),
access: vi.fn((path) => {
return globalThis.__mockStorage[path]
? Promise.resolve()
: Promise.reject(new Error("ENOENT"));
}),
},
}));
```
--------------------------------------------------------------------------------
/packages/cli/src/cli/loaders/dato/_base.ts:
--------------------------------------------------------------------------------
```typescript
import Z from "zod";
// DatoCMS config
export const datoConfigSchema = Z.object({
project: Z.string(),
models: Z.record(
Z.string(),
Z.object({
records: Z.array(Z.string()).optional(),
fields: Z.array(Z.string()).optional(),
}),
),
});
export type DatoConfig = Z.infer<typeof datoConfigSchema>;
// DatoCMS settings
export const datoSettingsSchema = Z.object({
auth: Z.object({
apiKey: Z.string(),
}),
});
export type DatoSettings = Z.infer<typeof datoSettingsSchema>;
export const DEFAULT_LOCALE = "en";
//
export type DatoRecordPayload = {
[field: string]: {
[locale: string]: DatoValue;
};
};
export type DatoValue = DatoSimpleValue | DatoComplexValue;
export type DatoSimpleValue = DatoPrimitive | DastDocument;
export type DatoComplexValue = DatoBlock | DatoBlock[];
export type DatoPrimitive = null | string | boolean | number;
export type DastDocument = {
schema: "dast";
document: DastDocumentNode;
};
export type DastDocumentNode = {
type: "root" | "span" | "paragraph";
value?: DatoPrimitive;
children?: DastDocumentNode[];
};
export type DatoBlock = {
id?: string;
type: "item";
attributes: Record<string, DatoSimpleValue>;
relationships: any;
};
```
--------------------------------------------------------------------------------
/packages/cli/src/cli/loaders/ensure-key-order.ts:
--------------------------------------------------------------------------------
```typescript
import _ from "lodash";
import { ILoader } from "./_types";
import { createLoader } from "./_utils";
export default function createEnsureKeyOrderLoader(): ILoader<
Record<string, any>,
Record<string, any>
> {
return createLoader({
pull: async (_locale, input) => {
return input;
},
push: async (_locale, data, originalInput) => {
if (!originalInput || !data) {
return data;
}
return reorderKeys(data, originalInput);
},
});
}
function reorderKeys(
data: Record<string, any>,
originalInput: Record<string, any>,
): Record<string, any> {
if (_.isArray(originalInput) && _.isArray(data)) {
// If both are arrays, recursively reorder keys in each element
return data.map((item, idx) => reorderKeys(item, originalInput[idx] ?? {}));
}
if (!_.isObject(data) || _.isArray(data) || _.isDate(data)) {
return data;
}
const orderedData: Record<string, any> = {};
const originalKeys = Object.keys(originalInput);
const dataKeys = new Set(Object.keys(data));
for (const key of originalKeys) {
if (dataKeys.has(key)) {
orderedData[key] = reorderKeys(data[key], originalInput[key]);
dataKeys.delete(key);
}
}
return orderedData;
}
```
--------------------------------------------------------------------------------
/packages/react/src/client/context.spec.tsx:
--------------------------------------------------------------------------------
```typescript
import { describe, it, expect } from "vitest";
import React from "react";
import { render, screen } from "@testing-library/react";
import { LingoContext, useLingo } from "./context";
describe("client/context", () => {
describe("useLingo", () => {
it("has default dictionary shape and useLingo returns it", () => {
const Probe = () => {
const lingo = useLingo();
return (
<div
data-testid="probe"
data-dict-empty={
Object.keys(lingo.dictionary).length === 0 ? "yes" : "no"
}
/>
);
};
render(<Probe />);
const el = screen.getByTestId("probe");
expect(el.getAttribute("data-dict-empty")).toBe("yes");
});
it("provides value via context provider", () => {
const Probe = () => {
const lingo = useLingo();
return (
<div data-testid="probe" data-locale={lingo.dictionary.locale} />
);
};
render(
<LingoContext.Provider value={{ dictionary: { locale: "it" } }}>
<Probe />
</LingoContext.Provider>,
);
const el = screen.getByTestId("probe");
expect(el.getAttribute("data-locale")).toBe("it");
});
});
});
```
--------------------------------------------------------------------------------
/packages/compiler/src/utils/hash.spec.ts:
--------------------------------------------------------------------------------
```typescript
import { describe, it, expect } from "vitest";
import { createPayload } from "../_base";
import traverse from "@babel/traverse";
import * as t from "@babel/types";
import { getJsxElementHash, getJsxAttributeValueHash } from "./hash";
function getFirstJsx(pathCode: string) {
const payload = createPayload({
code: pathCode,
params: {} as any,
relativeFilePath: "x.tsx",
});
let found: any;
traverse(payload.ast, {
JSXElement(p) {
if (!found) found = p;
},
});
return found as any;
}
describe("utils/hash", () => {
describe("getJsxElementHash", () => {
it("produces a consistent non-empty hash for same input", () => {
const a = getFirstJsx(`const A = () => <div>hello world</div>`);
const first = getJsxElementHash(a);
const second = getJsxElementHash(a);
expect(first).toBeTypeOf("string");
expect(first.length).toBeGreaterThan(0);
expect(second).toEqual(first);
});
});
describe("getJsxAttributeValueHash", () => {
it("attribute hash returns empty for empty string and stable otherwise", () => {
expect(getJsxAttributeValueHash("")).toBe("");
expect(getJsxAttributeValueHash("x")).toBe(getJsxAttributeValueHash("x"));
});
});
});
```
--------------------------------------------------------------------------------
/packages/react/build.config.ts:
--------------------------------------------------------------------------------
```typescript
import { defineBuildConfig } from "unbuild";
export default defineBuildConfig({
/* Clean the output directory before each build */
clean: true,
/* Where generated files are written */
outDir: "build",
/* Generate type declarations */
declaration: true,
/* Generate source-maps */
sourcemap: true,
/* Treat these as external – they must be provided by the host app */
externals: ["react", "next"],
/* Transpile every file in src/ one-to-one into build/ keeping the folder structure */
entries: [
{
builder: "mkdist",
/* All TS/TSX/JS/JSX files under src become part of the build */
input: "./src",
/* Mirror the structure in the build directory */
outDir: "./build",
/* Emit ESM with the standard .js extension */
format: "esm",
ext: "js",
/* Produce matching .d.ts files next to their JS counterparts */
declaration: true,
/* Ensure relative imports inside declaration files include the .js extension */
addRelativeDeclarationExtensions: true,
/* Use React 17+ automatic JSX runtime so output imports jsx from react/jsx-runtime */
esbuild: {
jsx: "automatic",
jsxImportSource: "react",
},
},
],
});
```
--------------------------------------------------------------------------------
/packages/compiler/src/lingo-turbopack-loader.ts:
--------------------------------------------------------------------------------
```typescript
import { loadDictionary, transformComponent } from "./_loader-utils";
// This loader handles component transformations and dictionary generation
export default async function (this: any, source: string) {
const callback = this.async();
const params = this.getOptions();
const isDev = process.env.NODE_ENV !== "production";
try {
// Dictionary loading
const dictionary = await loadDictionary({
resourcePath: this.resourcePath,
resourceQuery: this.resourceQuery,
params,
sourceRoot: params.sourceRoot,
lingoDir: params.lingoDir,
isDev,
});
if (dictionary) {
const code = `export default ${JSON.stringify(dictionary, null, 2)};`;
return callback(null, code);
}
// Component transformation
const result = transformComponent({
code: source,
params,
resourcePath: this.resourcePath,
sourceRoot: params.sourceRoot,
});
return callback(
null,
result.code,
result.map ? JSON.stringify(result.map) : undefined,
);
} catch (error) {
console.error(
`⚠️ Lingo.dev compiler (Turbopack) failed for ${this.resourcePath}:`,
);
console.error("⚠️ Details:", error);
callback(error as Error);
}
}
```
--------------------------------------------------------------------------------
/packages/compiler/src/index.spec.ts:
--------------------------------------------------------------------------------
```typescript
import { describe, it, expect, vi, beforeEach } from "vitest";
import compiler from "./index";
// Silence logs in tests
vi.spyOn(console, "log").mockImplementation(() => undefined as any);
vi.spyOn(console, "warn").mockImplementation(() => undefined as any);
vi.mock("./utils/env", () => ({ isRunningInCIOrDocker: () => true }));
vi.mock("./lib/lcp/cache", () => ({
LCPCache: { ensureDictionaryFile: vi.fn() },
}));
vi.mock("unplugin", () => ({
createUnplugin: () => ({
vite: vi.fn(() => ({ name: "test-plugin" })),
webpack: vi.fn(() => ({ name: "test-plugin" })),
}),
}));
describe("compiler integration", () => {
beforeEach(() => {
(process as any).env = { ...process.env };
});
it("next() returns a function and sets webpack wrapper when turbopack disabled", () => {
const cfg: any = { webpack: (c: any) => c };
const out = compiler.next({
sourceRoot: "src",
models: "lingo.dev",
turbopack: { enabled: false },
})(cfg);
expect(typeof out.webpack).toBe("function");
});
it("vite() pushes plugin to front and detects framework label", () => {
const cfg: any = { plugins: [{ name: "react-router" }] };
const out = compiler.vite({})(cfg);
expect(out.plugins[0]).toBeDefined();
});
});
```
--------------------------------------------------------------------------------
/packages/react/src/client/attribute-component.spec.tsx:
--------------------------------------------------------------------------------
```typescript
import { describe, it, expect, vi } from "vitest";
import React from "react";
import { LingoContext } from "./context";
vi.mock("../core", () => {
return {
LingoAttributeComponent: (props: any) => {
return React.createElement("div", {
"data-testid": "core-attr",
"data-has-dictionary": props.$dictionary ? "yes" : "no",
"data-file": props.$fileKey,
});
},
};
});
describe("client/attribute-component", () => {
describe("LingoAttributeComponent wrapper", () => {
it("injects dictionary from context into core attribute component", async () => {
const dictionary = { locale: "en" } as any;
const { LingoAttributeComponent } = await import("./attribute-component");
const { render, screen } = await import("@testing-library/react");
render(
<LingoContext.Provider value={{ dictionary }}>
<LingoAttributeComponent
$attrAs="a"
$fileKey="messages"
$attributes={{ title: "title" }}
/>
</LingoContext.Provider>,
);
const el = await screen.findByTestId("core-attr");
expect(el.getAttribute("data-has-dictionary")).toBe("yes");
expect(el.getAttribute("data-file")).toBe("messages");
});
});
});
```
--------------------------------------------------------------------------------
/demo/adonisjs/bin/server.ts:
--------------------------------------------------------------------------------
```typescript
/*
|--------------------------------------------------------------------------
| HTTP server entrypoint
|--------------------------------------------------------------------------
|
| The "server.ts" file is the entrypoint for starting the AdonisJS HTTP
| server. Either you can run this file directly or use the "serve"
| command to run this file and monitor file changes
|
*/
import 'reflect-metadata'
import { Ignitor, prettyPrintError } from '@adonisjs/core'
/**
* URL to the application root. AdonisJS need it to resolve
* paths to file and directories for scaffolding commands
*/
const APP_ROOT = new URL('../', import.meta.url)
/**
* The importer is used to import files in context of the
* application.
*/
const IMPORTER = (filePath: string) => {
if (filePath.startsWith('./') || filePath.startsWith('../')) {
return import(new URL(filePath, APP_ROOT).href)
}
return import(filePath)
}
new Ignitor(APP_ROOT, { importer: IMPORTER })
.tap((app) => {
app.booting(async () => {
await import('#start/env')
})
app.listen('SIGTERM', () => app.terminate())
app.listenIf(app.managedByPm2, 'SIGINT', () => app.terminate())
})
.httpServer()
.start()
.catch((error) => {
process.exitCode = 1
prettyPrintError(error)
})
```
--------------------------------------------------------------------------------
/demo/adonisjs/config/bodyparser.ts:
--------------------------------------------------------------------------------
```typescript
import { defineConfig } from '@adonisjs/core/bodyparser'
const bodyParserConfig = defineConfig({
/**
* The bodyparser middleware will parse the request body
* for the following HTTP methods.
*/
allowedMethods: ['POST', 'PUT', 'PATCH', 'DELETE'],
/**
* Config for the "application/x-www-form-urlencoded"
* content-type parser
*/
form: {
convertEmptyStringsToNull: true,
types: ['application/x-www-form-urlencoded'],
},
/**
* Config for the JSON parser
*/
json: {
convertEmptyStringsToNull: true,
types: [
'application/json',
'application/json-patch+json',
'application/vnd.api+json',
'application/csp-report',
],
},
/**
* Config for the "multipart/form-data" content-type parser.
* File uploads are handled by the multipart parser.
*/
multipart: {
/**
* Enabling auto process allows bodyparser middleware to
* move all uploaded files inside the tmp folder of your
* operating system
*/
autoProcess: true,
convertEmptyStringsToNull: true,
processManually: [],
/**
* Maximum limit of data to parse including all files
* and fields
*/
limit: '20mb',
types: ['multipart/form-data'],
},
})
export default bodyParserConfig
```
--------------------------------------------------------------------------------
/packages/compiler/src/utils/ast-key.spec.ts:
--------------------------------------------------------------------------------
```typescript
import { it, describe, expect } from "vitest";
import { parse } from "@babel/parser";
import traverse, { NodePath } from "@babel/traverse";
import { getAstKey, getAstByKey } from "./ast-key";
describe("ast key", () => {
it("getAstKey should calc nodePath key", () => {
const mockData = createMockData();
const key = getAstKey(mockData.testElementPath);
expect(key).toBe(mockData.testElementKey);
});
});
describe("getAstByKey", () => {
it("should retrieve correct node by key", () => {
const mockData = createMockData();
const elementPath = getAstByKey(mockData.ast, mockData.testElementKey);
expect(elementPath).toBe(mockData.testElementPath);
});
});
// helpers
function createMockData() {
const ast = parse(
`
export function MyComponent() {
return <div>Hello world!</div>;
}
`,
{ sourceType: "module", plugins: ["jsx"] },
);
let testElementPath: NodePath | null = null;
traverse(ast, {
JSXElement(nodePath) {
testElementPath = nodePath;
},
});
if (!testElementPath) {
throw new Error(
"testElementPath cannot be null - check test case definition",
);
}
const testElementKey = `0/declaration/body/0/argument`;
return {
ast,
testElementPath,
testElementKey,
};
}
```
--------------------------------------------------------------------------------
/packages/cli/src/cli/loaders/vtt.ts:
--------------------------------------------------------------------------------
```typescript
import webvtt from "node-webvtt";
import { ILoader } from "./_types";
import { createLoader } from "./_utils";
export default function createVttLoader(): ILoader<
string,
Record<string, any>
> {
return createLoader({
async pull(locale, input) {
if (!input) {
return ""; // if VTT file does not exist yet we can not parse it - return empty string
}
const vtt = webvtt.parse(input)?.cues;
if (Object.keys(vtt).length === 0) {
return {};
} else {
return vtt.reduce((result: any, cue: any, index: number) => {
const key = `${index}#${cue.start}-${cue.end}#${cue.identifier}`;
result[key] = cue.text;
return result;
}, {});
}
},
async push(locale, payload) {
const output = Object.entries(payload).map(([key, text]) => {
const [id, timeRange, identifier] = key.split("#");
const [startTime, endTime] = timeRange.split("-");
return {
end: Number(endTime),
identifier: identifier,
start: Number(startTime),
styles: "",
text: text,
};
});
const input = {
valid: true,
strict: true,
cues: output,
};
return webvtt.compile(input);
},
});
}
```
--------------------------------------------------------------------------------
/packages/cli/src/cli/loaders/mdx2/frontmatter-split.ts:
--------------------------------------------------------------------------------
```typescript
import matter from "gray-matter";
import YAML from "yaml";
import { ILoader } from "../_types";
import { createLoader } from "../_utils";
import { RawMdx } from "./_types";
export default function createMdxFrontmatterSplitLoader(): ILoader<
string,
RawMdx
> {
const fmEngine = createFmEngine();
return createLoader({
async pull(locale, input) {
const source = input || "";
const { data: frontmatter, content } = fmEngine.parse(source);
return {
frontmatter: frontmatter as Record<string, any>,
content,
};
},
async push(locale, data) {
const { frontmatter = {}, content = "" } = data || ({} as RawMdx);
const result = fmEngine.stringify(content, frontmatter).trim();
return result;
},
});
}
function createFmEngine() {
const yamlEngine = {
parse: (str: string) => YAML.parse(str),
stringify: (obj: any) =>
YAML.stringify(obj, { defaultStringType: "PLAIN" }),
};
return {
parse: (input: string) =>
matter(input, {
engines: {
yaml: yamlEngine,
},
}),
stringify: (content: string, frontmatter: Record<string, any>) =>
matter.stringify(content, frontmatter, {
engines: {
yaml: yamlEngine,
},
}),
};
}
```
--------------------------------------------------------------------------------
/packages/react/src/rsc/loader.ts:
--------------------------------------------------------------------------------
```typescript
import { getDictionary } from "../core";
/**
* A placeholder function for loading dictionaries that contain localized content.
*
* This function:
*
* - Should be used in React Server Components
* - Should be passed into the `LingoProvider` component
* - Is transformed into functional code by Lingo.dev Compiler
*
* @param locale - The locale code for which to load the dictionary.
*
* @returns Promise that resolves to the dictionary object containing localized content.
*
* @example Use in a Next.js (App Router) application
* ```tsx file="app/layout.tsx"
* import { LingoProvider, loadDictionary } from "lingo.dev/react/rsc";
*
* export default function RootLayout({
* children,
* }: Readonly<{
* children: React.ReactNode;
* }>) {
* return (
* <LingoProvider loadDictionary={(locale) => loadDictionary(locale)}>
* <html lang="en">
* <body>
* {children}
* </body>
* </html>
* </LingoProvider>
* );
* }
* ```
*/
export const loadDictionary = async (locale: string | null): Promise<any> => {
return {};
};
export const loadDictionary_internal = async (
locale: string | null,
dictionaryLoaders: Record<string, () => Promise<any>> = {},
): Promise<any> => {
return getDictionary(locale, dictionaryLoaders);
};
```
--------------------------------------------------------------------------------
/packages/cli/src/cli/loaders/xml.ts:
--------------------------------------------------------------------------------
```typescript
import { parseStringPromise, Builder } from "xml2js";
import { ILoader } from "./_types";
import { createLoader } from "./_utils";
function normalizeXMLString(xmlString: string): string {
return xmlString
.replace(/\s+/g, " ")
.replace(/>\s+</g, "><")
.replace("\n", "")
.trim();
}
export default function createXmlLoader(): ILoader<
string,
Record<string, any>
> {
return createLoader({
async pull(locale, input) {
let result: Record<string, any> = {};
try {
const parsed = await parseStringPromise(input, {
explicitArray: false,
mergeAttrs: false,
normalize: true,
preserveChildrenOrder: true,
normalizeTags: true,
includeWhiteChars: true,
trim: true,
});
result = parsed;
} catch (error) {
console.error("Failed to parse XML:", error);
result = {};
}
return result;
},
async push(locale, data) {
try {
const builder = new Builder({ headless: true });
const xmlOutput = builder.buildObject(data);
const expectedOutput = normalizeXMLString(xmlOutput);
return expectedOutput;
} catch (error) {
console.error("Failed to build XML:", error);
return "";
}
},
});
}
```
--------------------------------------------------------------------------------
/packages/compiler/src/utils/observability.spec.ts:
--------------------------------------------------------------------------------
```typescript
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import trackEvent from "./observability";
vi.mock("./rc", () => ({ getRc: () => ({ auth: {} }) }));
vi.mock("node-machine-id", () => ({ machineId: async () => "device-123" }));
// Mock PostHog client used by dynamic import inside trackEvent
const capture = vi.fn(async () => undefined);
const shutdown = vi.fn(async () => undefined);
const PostHogMock = vi.fn((_key: string, _cfg: any) => ({ capture, shutdown }));
vi.mock("posthog-node", () => ({ PostHog: PostHogMock }));
describe("trackEvent", () => {
const originalEnv = { ...process.env };
afterEach(() => {
process.env = originalEnv;
});
it("captures the event with properties", async () => {
await trackEvent("test.event", { foo: "bar" });
expect(PostHogMock).toHaveBeenCalledTimes(1);
expect(capture).toHaveBeenCalledWith(
expect.objectContaining({
event: "test.event",
properties: expect.objectContaining({ foo: "bar" }),
}),
);
expect(shutdown).toHaveBeenCalledTimes(1);
});
it("skips when DO_NOT_TRACK is set", async () => {
process.env = { ...originalEnv, DO_NOT_TRACK: "1" };
// Should not throw nor attempt network
await expect(trackEvent("test.event", { a: 1 })).resolves.toBeUndefined();
});
});
```
--------------------------------------------------------------------------------
/packages/compiler/src/jsx-remove-attributes.spec.ts:
--------------------------------------------------------------------------------
```typescript
import { describe, it, expect } from "vitest";
import { jsxRemoveAttributesMutation } from "./jsx-remove-attributes";
import { createPayload, createOutput, defaultParams } from "./_base";
// Helper function to run mutation and get result
function runMutation(code: string) {
const input = createPayload({ code, params: defaultParams, fileKey: "test" });
const mutated = jsxRemoveAttributesMutation(input);
if (!mutated) return code; // Return original code if no changes made
return createOutput(mutated).code;
}
describe("jsxRemoveAttributesMutation", () => {
it("should remove only attributes added by compiler", () => {
const input = `
function Component() {
return <div data-jsx-root>
<p data-jsx-scope="foo" data-other="1">Hello world</p>
<p data-jsx-attribute-scope="bar" className="text-success">Good night moon</p>
<p data-jsx-scope="foobar" data-jsx-attribute-scope="barfoo" className="text-danger" data-other="2">Good morning sun</p>
</div>;
}
`.trim();
const expected = `
function Component() {
return <div>
<p data-other="1">Hello world</p>
<p className="text-success">Good night moon</p>
<p className="text-danger" data-other="2">Good morning sun</p>
</div>;
}
`.trim();
const result = runMutation(input);
expect(result).toBe(expected);
});
});
```
--------------------------------------------------------------------------------
/packages/react/src/client/loader.ts:
--------------------------------------------------------------------------------
```typescript
import { getDictionary } from "../core";
/**
* A placeholder function for loading dictionaries that contain localized content.
*
* This function:
*
* - Should be used in client-side rendered applications (e.g., Vite-based apps)
* - Should be passed into the `LingoProviderWrapper` component
* - Is transformed into functional code by Lingo.dev Compiler
*
* @param locale - The locale to load the dictionary for.
*
* @returns Promise that resolves to the dictionary object containing localized content.
*
* @example Use in a Vite application
* ```tsx
* import React from "react";
* import ReactDOM from "react-dom/client";
* import { LingoProviderWrapper, loadDictionary } from "lingo.dev/react/client";
* import { App } from "./App.tsx";
*
* ReactDOM.createRoot(document.getElementById("root")!).render(
* <React.StrictMode>
* <LingoProviderWrapper loadDictionary={(locale) => loadDictionary(locale)}>
* <App />
* </LingoProviderWrapper>
* </React.StrictMode>,
* );
* ```
*/
export const loadDictionary = async (locale: string | null): Promise<any> => {
return {};
};
export const loadDictionary_internal = async (
locale: string | null,
dictionaryLoaders: Record<string, () => Promise<any>> = {},
): Promise<any> => {
return getDictionary(locale, dictionaryLoaders);
};
```
--------------------------------------------------------------------------------
/demo/adonisjs/bin/console.ts:
--------------------------------------------------------------------------------
```typescript
/*
|--------------------------------------------------------------------------
| Ace entry point
|--------------------------------------------------------------------------
|
| The "console.ts" file is the entrypoint for booting the AdonisJS
| command-line framework and executing commands.
|
| Commands do not boot the application, unless the currently running command
| has "options.startApp" flag set to true.
|
*/
import 'reflect-metadata'
import { Ignitor, prettyPrintError } from '@adonisjs/core'
/**
* URL to the application root. AdonisJS need it to resolve
* paths to file and directories for scaffolding commands
*/
const APP_ROOT = new URL('../', import.meta.url)
/**
* The importer is used to import files in context of the
* application.
*/
const IMPORTER = (filePath: string) => {
if (filePath.startsWith('./') || filePath.startsWith('../')) {
return import(new URL(filePath, APP_ROOT).href)
}
return import(filePath)
}
new Ignitor(APP_ROOT, { importer: IMPORTER })
.tap((app) => {
app.booting(async () => {
await import('#start/env')
})
app.listen('SIGTERM', () => app.terminate())
app.listenIf(app.managedByPm2, 'SIGINT', () => app.terminate())
})
.ace()
.handle(process.argv.splice(2))
.catch((error) => {
process.exitCode = 1
prettyPrintError(error)
})
```
--------------------------------------------------------------------------------
/packages/react/src/client/component.lingo-component.spec.tsx:
--------------------------------------------------------------------------------
```typescript
import { describe, it, expect, vi } from "vitest";
import React from "react";
import { LingoContext } from "./context";
// Mock core LingoComponent to capture received props
vi.mock("../core", () => {
return {
LingoComponent: (props: any) => {
return React.createElement("div", {
"data-testid": "core-lingo-component",
"data-has-dictionary": props.$dictionary ? "yes" : "no",
"data-entry": props.$entryKey,
"data-file": props.$fileKey,
});
},
};
});
describe("client/component", () => {
describe("LingoComponent wrapper", () => {
it("renders core component with dictionary from context and forwards keys", async () => {
const dictionary = { locale: "en" } as any;
const { LingoComponent } = await import("./component");
const { render, screen } = await import("@testing-library/react");
render(
<LingoContext.Provider value={{ dictionary }}>
<LingoComponent $as="span" $fileKey="messages" $entryKey="hello" />
</LingoContext.Provider>,
);
const el = await screen.findByTestId("core-lingo-component");
expect(el.getAttribute("data-has-dictionary")).toBe("yes");
expect(el.getAttribute("data-file")).toBe("messages");
expect(el.getAttribute("data-entry")).toBe("hello");
});
});
});
```
--------------------------------------------------------------------------------
/packages/cli/src/cli/cmd/config/get.ts:
--------------------------------------------------------------------------------
```typescript
import { Command } from "interactive-commander";
import chalk from "chalk";
import _ from "lodash";
import { SETTINGS_KEYS, loadSystemSettings } from "../../utils/settings";
import dedent from "dedent";
export default new Command()
.name("get")
.description("Display the value of a CLI setting from ~/.lingodotdevrc")
.addHelpText("afterAll", `\nAvailable keys:\n ${SETTINGS_KEYS.join("\n ")}`)
.argument(
"<key>",
"Configuration key to read (choose from the available keys listed below)",
)
.helpOption("-h, --help", "Show help")
.action(async (key: string) => {
// Validate that the provided key is one of the recognised configuration keys.
if (!SETTINGS_KEYS.includes(key)) {
console.error(
dedent`
${chalk.red("✖")} Unknown configuration key: ${chalk.bold(key)}
Run ${chalk.dim("lingo.dev config get --help")} to see available keys.
`,
);
process.exitCode = 1;
return;
}
const settings = loadSystemSettings();
const value = _.get(settings, key);
if (!value) {
// Key is valid but not set in the configuration file.
console.log(`${chalk.cyan("ℹ")} ${chalk.bold(key)} is not set.`);
return;
}
if (typeof value === "object") {
console.log(JSON.stringify(value, null, 2));
} else {
console.log(value);
}
});
```
--------------------------------------------------------------------------------
/demo/next-app/public/next.svg:
--------------------------------------------------------------------------------
```
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
```
--------------------------------------------------------------------------------
/packages/react/src/rsc/component.lingo-component.spec.tsx:
--------------------------------------------------------------------------------
```typescript
import { describe, it, expect, vi } from "vitest";
import React from "react";
vi.mock("./utils", () => {
return {
loadDictionaryFromRequest: vi.fn(async (loader: any) => loader("es")),
};
});
// Mock core LingoComponent to capture props
vi.mock("../core", () => {
return {
LingoComponent: (props: any) => {
return React.createElement("div", {
"data-testid": "core-lingo-component",
"data-dictionary-locale": props.$dictionary?.locale ?? "none",
"data-entry": props.$entryKey,
"data-file": props.$fileKey,
});
},
};
});
describe("rsc/component", () => {
describe("LingoComponent wrapper", () => {
it("awaits dictionary and forwards props to core component", async () => {
const { LingoComponent } = await import("./component");
const { render, screen } = await import("@testing-library/react");
render(
await LingoComponent({
$as: "span",
$fileKey: "messages",
$entryKey: "hello",
$loadDictionary: async (locale: string | null) => ({ locale }),
}),
);
const el = await screen.findByTestId("core-lingo-component");
expect(el.getAttribute("data-dictionary-locale")).toBe("es");
expect(el.getAttribute("data-file")).toBe("messages");
expect(el.getAttribute("data-entry")).toBe("hello");
});
});
});
```
--------------------------------------------------------------------------------
/demo/adonisjs/start/kernel.ts:
--------------------------------------------------------------------------------
```typescript
/*
|--------------------------------------------------------------------------
| HTTP kernel file
|--------------------------------------------------------------------------
|
| The HTTP kernel file is used to register the middleware with the server
| or the router.
|
*/
import router from '@adonisjs/core/services/router'
import server from '@adonisjs/core/services/server'
/**
* The error handler is used to convert an exception
* to an HTTP response.
*/
server.errorHandler(() => import('#exceptions/handler'))
/**
* The server middleware stack runs middleware on all the HTTP
* requests, even if there is no route registered for
* the request URL.
*/
server.use([
() => import('#middleware/container_bindings_middleware'),
() => import('@adonisjs/static/static_middleware'),
() => import('@adonisjs/cors/cors_middleware'),
() => import('@adonisjs/vite/vite_middleware'),
() => import('@adonisjs/inertia/inertia_middleware'),
])
/**
* The router middleware stack runs middleware on all the HTTP
* requests with a registered route.
*/
router.use([
() => import('@adonisjs/core/bodyparser_middleware'),
() => import('@adonisjs/session/session_middleware'),
() => import('@adonisjs/shield/shield_middleware'),
])
/**
* Named middleware collection must be explicitly assigned to
* the routes or the routes group.
*/
export const middleware = router.named({})
```
--------------------------------------------------------------------------------
/packages/compiler/src/utils/locales.ts:
--------------------------------------------------------------------------------
```typescript
export function getInvalidLocales(
localeModels: Record<string, string>,
sourceLocale: string,
targetLocales: string[],
) {
return targetLocales.filter((targetLocale) => {
const { provider, model } = getLocaleModel(
localeModels,
sourceLocale,
targetLocale,
);
return provider === undefined || model === undefined;
});
}
export function getLocaleModel(
localeModels: Record<string, string>,
sourceLocale: string,
targetLocale: string,
): { provider?: string; model?: string } {
const localeKeys = [
`${sourceLocale}:${targetLocale}`,
`*:${targetLocale}`,
`${sourceLocale}:*`,
"*:*",
];
const modelKey = localeKeys.find((key) => localeModels.hasOwnProperty(key));
if (modelKey) {
const value = localeModels[modelKey];
// Split only on the first colon
const firstColonIndex = value?.indexOf(":");
if (value && firstColonIndex !== -1 && firstColonIndex !== undefined) {
const provider = value.substring(0, firstColonIndex);
const model = value.substring(firstColonIndex + 1);
if (provider && model) {
return { provider, model };
}
}
// Fallback for strings without a colon or other issues
const [provider, model] = value?.split(":") || [];
if (provider && model) {
return { provider, model };
}
}
return { provider: undefined, model: undefined };
}
```
--------------------------------------------------------------------------------
/packages/react/src/client/locale.ts:
--------------------------------------------------------------------------------
```typescript
"use client";
import { useEffect, useState } from "react";
import { getLocaleFromCookies, setLocaleInCookies } from "./utils";
/**
* Gets the current locale used by the Lingo compiler.
*
* @returns The current locale code, or `null` if no locale is set.
*/
export function useLingoLocale(): string | null {
const [locale, setLocale] = useState<string | null>(null);
useEffect(() => {
setLocale(getLocaleFromCookies());
}, []);
return locale;
}
/**
* Sets the current locale used by the Lingo compiler.
*
* **Note:** This function triggers a full page reload to ensure all components
* are re-rendered with the new locale. This is necessary because locale changes
* affect the entire application state.
*
* @param locale - The locale code to set. Must be a valid locale code (e.g., "en", "es", "fr-CA").
*
* @example Set the current locale
* ```tsx
* import { setLingoLocale } from "lingo.dev/react/client";
*
* export function LanguageSwitcher() {
* const handleChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
* setLingoLocale(event.target.value);
* };
*
* return (
* <select onChange={handleChange}>
* <option value="en">English</option>
* <option value="es">Spanish</option>
* </select>
* );
* }
* ```
*/
export function setLingoLocale(locale: string) {
setLocaleInCookies(locale);
window.location.reload();
}
```
--------------------------------------------------------------------------------
/packages/compiler/src/i18n-directive.spec.ts:
--------------------------------------------------------------------------------
```typescript
import { describe, it, expect } from "vitest";
import i18nDirectiveMutation from "./i18n-directive";
import { createPayload, CompilerParams, defaultParams } from "./_base";
describe("i18nDirectiveMutation", () => {
it("should return payload when 'use i18n' directive is present", () => {
const input = `
"use i18n";
function Component() {
return <div>Hello</div>;
}
`.trim();
const result = createPayload({
code: input,
params: defaultParams,
fileKey: "test",
});
const mutated = i18nDirectiveMutation(result);
expect(mutated).not.toBeNull();
expect(mutated).toEqual(result);
});
it("should return null when 'use i18n' directive is not present", () => {
const input = `
function Component() {
return <div>Hello</div>;
}
`.trim();
const result = createPayload({
code: input,
params: { ...defaultParams, useDirective: true },
fileKey: "test",
});
const mutated = i18nDirectiveMutation(result);
expect(mutated).toBeNull();
});
it("should handle multiple directives correctly", () => {
const input = `
"use strict";
"use i18n";
function Component() {
return <div>Hello</div>;
}
`.trim();
const result = createPayload({
code: input,
params: defaultParams,
fileKey: "test",
});
const mutated = i18nDirectiveMutation(result);
expect(mutated).not.toBeNull();
expect(mutated).toEqual(result);
});
});
```
--------------------------------------------------------------------------------
/packages/cli/src/cli/cmd/config/set.ts:
--------------------------------------------------------------------------------
```typescript
import { Command } from "interactive-commander";
import chalk from "chalk";
import dedent from "dedent";
import _ from "lodash";
import {
SETTINGS_KEYS,
loadSystemSettings,
saveSettings,
} from "../../utils/settings";
export default new Command()
.name("set")
.description("Set or update a CLI setting in ~/.lingodotdevrc")
.addHelpText("afterAll", `\nAvailable keys:\n ${SETTINGS_KEYS.join("\n ")}`)
.argument(
"<key>",
"Configuration key to set (dot notation, e.g., auth.apiKey)",
)
.argument("<value>", "The configuration value to set")
.helpOption("-h, --help", "Show help")
.action(async (key: string, value: string) => {
if (!SETTINGS_KEYS.includes(key)) {
console.error(
dedent`
${chalk.red("✖")} Unknown configuration key: ${chalk.bold(key)}
Run ${chalk.dim("lingo.dev config set --help")} to see available keys.
`,
);
process.exitCode = 1;
return;
}
const current = loadSystemSettings();
const updated: any = _.cloneDeep(current);
_.set(updated, key, value);
try {
saveSettings(updated as any);
console.log(`${chalk.green("✔")} Set ${chalk.bold(key)}`);
} catch (err) {
console.error(
chalk.red(
`✖ Failed to save configuration: ${chalk.dim(
err instanceof Error ? err.message : String(err),
)}`,
),
);
process.exitCode = 1;
}
});
```
--------------------------------------------------------------------------------
/packages/cli/src/cli/loaders/mdx2/frontmatter-split.spec.ts:
--------------------------------------------------------------------------------
```typescript
import { describe, it, expect } from "vitest";
import createMdxFrontmatterSplitLoader from "./frontmatter-split";
import matter from "gray-matter";
const sampleMdx = `---
title: Hello
published: true
tags:
- foo
- bar
---
# Heading
This is some text.`;
// Helper to derive expected content string from the original sample – this mirrors what gray-matter returns
const { content: originalContent } = matter(sampleMdx);
describe("mdx frontmatter split loader", () => {
it("should split frontmatter and content on pull", async () => {
const loader = createMdxFrontmatterSplitLoader();
loader.setDefaultLocale("en");
const result = await loader.pull("en", sampleMdx);
expect(result).toEqual({
frontmatter: {
title: "Hello",
published: true,
tags: ["foo", "bar"],
},
content: originalContent,
});
});
it("should merge frontmatter and content on push", async () => {
const loader = createMdxFrontmatterSplitLoader();
loader.setDefaultLocale("en");
const pulled = await loader.pull("en", sampleMdx);
// modify the data
pulled.frontmatter.title = "Hola";
pulled.content = pulled.content.replace("# Heading", "# Título");
const output = await loader.push("es", pulled);
const expectedOutput = `
---
title: Hola
published: true
tags:
- foo
- bar
---
# Título
This is some text.
`.trim();
expect(output).toBe(expectedOutput);
});
});
```
--------------------------------------------------------------------------------
/packages/compiler/src/lib/lcp/schema.ts:
--------------------------------------------------------------------------------
```typescript
import { z } from "zod";
// LCP
export const lcpScope = z.object({
type: z.enum(["element", "attribute"]),
content: z.string(),
hash: z.string(),
context: z.string().optional(),
skip: z.boolean().optional(),
overrides: z.record(z.string(), z.string()).optional(),
});
export type LCPScope = z.infer<typeof lcpScope>;
export const lcpFile = z.object({
scopes: z.record(z.string(), lcpScope).optional(),
});
export type LCPFile = z.infer<typeof lcpFile>;
export const lcpSchema = z.object({
version: z.number().default(0.1),
files: z.record(z.string(), lcpFile).optional(),
});
export type LCPSchema = z.infer<typeof lcpSchema>;
// Dictionary
export const dictionaryFile = z.object({
entries: z.record(z.string(), z.string()),
});
export type DictionaryFile = z.infer<typeof dictionaryFile>;
export const dictionarySchema = z.object({
version: z.number().default(0.1),
locale: z.string(),
files: z.record(z.string(), dictionaryFile),
});
export type DictionarySchema = z.infer<typeof dictionarySchema>;
// Dictionary Cache
export const dictionaryCacheFile = z.object({
entries: z.record(
z.string(),
z.object({
content: z.record(z.string(), z.string()),
hash: z.string(),
}),
),
});
export const dictionaryCacheSchema = z.object({
version: z.number().default(0.1),
files: z.record(z.string(), dictionaryCacheFile),
});
export type DictionaryCacheSchema = z.infer<typeof dictionaryCacheSchema>;
```
--------------------------------------------------------------------------------
/packages/react/src/core/get-dictionary.spec.ts:
--------------------------------------------------------------------------------
```typescript
import { describe, it, expect, vi } from "vitest";
import { getDictionary } from "./get-dictionary";
describe("get-dictionary", () => {
const mockLoaderEn = vi.fn().mockResolvedValue(
Promise.resolve({
default: { hello: "Hello", goodbye: "Goodbye" },
otherExport: "ignored",
}),
);
const mockLoaderEs = vi.fn().mockResolvedValue(
Promise.resolve({
default: { hello: "Hola", goodbye: "Adiós" },
}),
);
const loaders = {
en: mockLoaderEn,
es: mockLoaderEs,
};
beforeEach(() => {
vi.clearAllMocks();
});
describe("getDictionary", () => {
it("should load dictionary for specific locale using correct async loader", async () => {
const result = await getDictionary("es", loaders);
expect(mockLoaderEs).toHaveBeenCalledTimes(1);
expect(result).toEqual({ hello: "Hola", goodbye: "Adiós" });
});
it("should fallback to first available loader when specific locale not found", async () => {
const result = await getDictionary("fr", loaders);
expect(mockLoaderEn).toHaveBeenCalledTimes(1);
expect(result).toEqual({ hello: "Hello", goodbye: "Goodbye" });
});
it("should throw error when no loaders are provided", async () => {
expect(() => getDictionary("en", {})).toThrow(
"No available dictionary loaders found",
);
expect(() => getDictionary("en")).toThrow(
"No available dictionary loaders found",
);
});
});
});
```
--------------------------------------------------------------------------------
/packages/compiler/src/utils/locales.spec.ts:
--------------------------------------------------------------------------------
```typescript
import { describe, it, expect } from "vitest";
import { getInvalidLocales, getLocaleModel } from "./locales";
describe("utils/locales", () => {
describe("getLocaleModel", () => {
const models = {
"en:es": "groq:llama3",
"en:*": "google:g2",
"*:es": "mistral:m-small",
"*:*": "openrouter:gpt",
};
it.each([
["en", "es", { provider: "groq", model: "llama3" }],
["en", "fr", { provider: "google", model: "g2" }],
["de", "es", { provider: "mistral", model: "m-small" }],
["de", "fr", { provider: "openrouter", model: "gpt" }],
])("resolves locales", (sourceLocale, targetLocale, expected) => {
expect(getLocaleModel(models, sourceLocale, targetLocale)).toEqual(
expected,
);
});
it("returns undefined for missing mapping", () => {
expect(getLocaleModel({ "en:es": "groq:llama3" }, "en", "fr")).toEqual({
provider: undefined,
model: undefined,
});
});
it("returns undefined for invalid value", () => {
expect(getLocaleModel({ "en:fr": "invalidFormat" }, "en", "fr")).toEqual({
provider: undefined,
model: undefined,
});
});
});
describe("getInvalidLocales", () => {
it("returns targets with unresolved models", () => {
const models = { "en:es": "groq:llama3", "*:fr": "google:g2" };
const invalid = getInvalidLocales(models, "en", ["es", "fr", "de"]);
expect(invalid).toEqual(["de"]);
});
});
});
```
--------------------------------------------------------------------------------
/demo/vite-project/public/vite.svg:
--------------------------------------------------------------------------------
```
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
```
--------------------------------------------------------------------------------
/packages/compiler/src/client-dictionary-loader.ts:
--------------------------------------------------------------------------------
```typescript
import { createCodeMutation } from "./_base";
import { ModuleId } from "./_const";
import { getOrCreateImport } from "./utils";
import { findInvokations } from "./utils/invokations";
import * as t from "@babel/types";
import { getDictionaryPath } from "./_utils";
import { createLocaleImportMap } from "./utils/create-locale-import-map";
export const clientDictionaryLoaderMutation = createCodeMutation((payload) => {
const invokations = findInvokations(payload.ast, {
moduleName: ModuleId.ReactClient,
functionName: "loadDictionary",
});
const allLocales = Array.from(
new Set([payload.params.sourceLocale, ...payload.params.targetLocales]),
);
for (const invokation of invokations) {
const internalDictionaryLoader = getOrCreateImport(payload.ast, {
moduleName: ModuleId.ReactClient,
exportedName: "loadDictionary_internal",
});
// Replace the function identifier with internal version
if (t.isIdentifier(invokation.callee)) {
invokation.callee.name = internalDictionaryLoader.importedName;
}
const dictionaryPath = getDictionaryPath({
sourceRoot: payload.params.sourceRoot,
lingoDir: payload.params.lingoDir,
relativeFilePath: payload.relativeFilePath,
});
// Create locale import map object
const localeImportMap = createLocaleImportMap(allLocales, dictionaryPath);
// Add the locale import map as the second argument
invokation.arguments.push(localeImportMap);
}
return payload;
});
```
--------------------------------------------------------------------------------
/packages/react/src/client/utils.ts:
--------------------------------------------------------------------------------
```typescript
"use client";
import { LOCALE_COOKIE_NAME } from "../core";
import Cookies from "js-cookie";
/**
* Gets the current locale from the `"lingo-locale"` cookie.
*
* Defaults to `"en"` if:
*
* - Running in an environment that doesn't support cookies
* - No `"lingo-locale"` cookie is found
*
* @returns The current locale code, or `"en"` as a fallback.
*
* @example Get the current locale
* ```tsx
* import { getLocaleFromCookies } from "lingo.dev/react/client";
*
* export function App() {
* const currentLocale = getLocaleFromCookies();
* return <div>Current locale: {currentLocale}</div>;
* }
* ```
*/
export function getLocaleFromCookies(): string | null {
if (typeof document === "undefined") return null;
return Cookies.get(LOCALE_COOKIE_NAME) ?? null;
}
/**
* Sets the current locale in the `"lingo-locale"` cookie.
*
* Does nothing in environments that don't support cookies.
*
* @param locale - The locale code to store in the `"lingo-locale"` cookie.
*
* @example Set the current locale
* ```tsx
* import { setLocaleInCookies } from "lingo.dev/react/client";
*
* export function LanguageButton() {
* const handleClick = () => {
* setLocaleInCookies("es");
* window.location.reload();
* };
*
* return <button onClick={handleClick}>Switch to Spanish</button>;
* }
* ```
*/
export function setLocaleInCookies(locale: string): void {
if (typeof document === "undefined") return;
Cookies.set(LOCALE_COOKIE_NAME, locale, {
path: "/",
expires: 365,
sameSite: "lax",
});
}
```
--------------------------------------------------------------------------------
/action.yml:
--------------------------------------------------------------------------------
```yaml
name: "Lingo.Dev AI Localization"
description: Automated AI localization for dev teams.
author: Lingo.dev
branding:
icon: "aperture"
color: "black"
runs:
using: "composite"
steps:
- name: Run
run: |
npx lingo.dev@${{ inputs.version }} ci \
--api-key "${{ inputs.api-key }}" \
--pull-request "${{ inputs.pull-request }}" \
--commit-message "${{ inputs.commit-message }}" \
--pull-request-title "${{ inputs.pull-request-title }}" \
--working-directory "${{ inputs.working-directory }}" \
--process-own-commits "${{ inputs.process-own-commits }}" \
--parallel ${{ inputs.parallel }}
shell: bash
inputs:
version:
description: "Lingo.dev CLI version"
default: "latest"
required: false
api-key:
description: "Lingo.dev Platform API Key"
required: true
pull-request:
description: "Create a pull request with the changes"
default: false
required: false
commit-message:
description: "Commit message"
default: "feat: update translations via @LingoDotDev"
required: false
pull-request-title:
description: "Pull request title"
default: "feat: update translations via @LingoDotDev"
required: false
working-directory:
description: "Working directory"
default: "."
required: false
process-own-commits:
description: "Process commits made by this action"
default: false
required: false
parallel:
description: "Run in parallel mode"
default: false
required: false
```
--------------------------------------------------------------------------------
/packages/react/src/client/utils.spec.ts:
--------------------------------------------------------------------------------
```typescript
import { describe, it, expect, vi, beforeEach } from "vitest";
import { getLocaleFromCookies, setLocaleInCookies } from "./utils";
vi.mock("js-cookie", () => {
return {
default: {
get: vi.fn(),
set: vi.fn(),
},
};
});
// access mocked module
import Cookies from "js-cookie";
describe("client/utils", () => {
beforeEach(() => {
vi.clearAllMocks();
});
describe("getLocaleFromCookies", () => {
it("returns null when document is undefined (SSR)", () => {
const original = globalThis.document;
// @ts-ignore
delete (globalThis as any).document;
expect(getLocaleFromCookies()).toBeNull();
(globalThis as any).document = original;
});
it("returns cookie value when present", () => {
(Cookies.get as any).mockReturnValue("es");
(globalThis as any).document = {} as any;
expect(getLocaleFromCookies()).toBe("es");
});
});
describe("setLocaleInCookies", () => {
it("is no-op when document is undefined", () => {
const original = globalThis.document;
// @ts-ignore
delete (globalThis as any).document;
setLocaleInCookies("fr");
expect(Cookies.set).not.toHaveBeenCalled();
(globalThis as any).document = original;
});
it("writes cookie with expected options", () => {
(globalThis as any).document = {} as any;
setLocaleInCookies("en");
expect(Cookies.set).toHaveBeenCalledWith("lingo-locale", "en", {
path: "/",
expires: 365,
sameSite: "lax",
});
});
});
});
```
--------------------------------------------------------------------------------
/demo/adonisjs/app/exceptions/handler.ts:
--------------------------------------------------------------------------------
```typescript
import app from '@adonisjs/core/services/app'
import { HttpContext, ExceptionHandler } from '@adonisjs/core/http'
import type { StatusPageRange, StatusPageRenderer } from '@adonisjs/core/types/http'
export default class HttpExceptionHandler extends ExceptionHandler {
/**
* In debug mode, the exception handler will display verbose errors
* with pretty printed stack traces.
*/
protected debug = !app.inProduction
/**
* Status pages are used to display a custom HTML pages for certain error
* codes. You might want to enable them in production only, but feel
* free to enable them in development as well.
*/
protected renderStatusPages = app.inProduction
/**
* Status pages is a collection of error code range and a callback
* to return the HTML contents to send as a response.
*/
protected statusPages: Record<StatusPageRange, StatusPageRenderer> = {
'404': (error, { inertia }) => inertia.render('errors/not_found', { error }),
'500..599': (error, { inertia }) => inertia.render('errors/server_error', { error }),
}
/**
* The method is used for handling errors and returning
* response to the client
*/
async handle(error: unknown, ctx: HttpContext) {
return super.handle(error, ctx)
}
/**
* The method is used to report error to the logging service or
* the a third party error monitoring service.
*
* @note You should not attempt to send a response from this method.
*/
async report(error: unknown, ctx: HttpContext) {
return super.report(error, ctx)
}
}
```
--------------------------------------------------------------------------------
/packages/react/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "@lingo.dev/_react",
"version": "0.5.0",
"description": "Lingo.dev React Kit",
"private": false,
"publishConfig": {
"access": "public"
},
"sideEffects": false,
"type": "module",
"exports": {
".": {
"types": "./build/core/index.d.ts",
"import": "./build/core/index.js"
},
"./client": {
"types": "./build/client/index.d.ts",
"import": "./build/client/index.js"
},
"./rsc": {
"types": "./build/rsc/index.d.ts",
"import": "./build/rsc/index.js"
},
"./react-router": {
"types": "./build/react-router/index.d.ts",
"import": "./build/react-router/index.js"
}
},
"files": [
"build"
],
"scripts": {
"dev": "unbuild && chokidar 'src/**/*' -c 'unbuild'",
"build": "pnpm typecheck && unbuild",
"typecheck": "tsc --noEmit",
"clean": "rm -rf build",
"test": "vitest --run",
"test:watch": "vitest -w"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@testing-library/react": "^16.3.0",
"@types/js-cookie": "^3.0.6",
"@types/lodash": "^4.17.4",
"@types/react": "^18.3.18",
"@types/react-dom": "^19.1.7",
"@vitejs/plugin-react": "^4.4.1",
"chokidar-cli": "^3.0.0",
"next": "15.2.4",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"tsup": "^8.3.5",
"typescript": "^5.4.5",
"unbuild": "^3.5.0",
"vitest": "^3.1.1"
},
"peerDependencies": {
"next": "15.2.4"
},
"dependencies": {
"js-cookie": "^3.0.5",
"lodash": "^4.17.21"
}
}
```
--------------------------------------------------------------------------------
/packages/cli/src/cli/loaders/yaml.ts:
--------------------------------------------------------------------------------
```typescript
import YAML, { ToStringOptions } from "yaml";
import { ILoader } from "./_types";
import { createLoader } from "./_utils";
export default function createYamlLoader(): ILoader<
string,
Record<string, any>
> {
return createLoader({
async pull(locale, input) {
return YAML.parse(input) || {};
},
async push(locale, payload, originalInput) {
return YAML.stringify(payload, {
lineWidth: -1,
defaultKeyType: getKeyType(originalInput),
defaultStringType: getStringType(originalInput),
});
},
});
}
// check if the yaml keys are using double quotes or single quotes
function getKeyType(
yamlString: string | null,
): ToStringOptions["defaultKeyType"] {
if (yamlString) {
const lines = yamlString.split("\n");
const hasDoubleQuotes = lines.find((line) => {
return line.trim().startsWith('"') && line.trim().match('":');
});
if (hasDoubleQuotes) {
return "QUOTE_DOUBLE";
}
}
return "PLAIN";
}
// check if the yaml string values are using double quotes or single quotes
function getStringType(
yamlString: string | null,
): ToStringOptions["defaultStringType"] {
if (yamlString) {
const lines = yamlString.split("\n");
const hasDoubleQuotes = lines.find((line) => {
const trimmedLine = line.trim();
return (
(trimmedLine.startsWith('"') || trimmedLine.match(/:\s*"/)) &&
(trimmedLine.endsWith('"') || trimmedLine.endsWith('",'))
);
});
if (hasDoubleQuotes) {
return "QUOTE_DOUBLE";
}
}
return "PLAIN";
}
```
--------------------------------------------------------------------------------
/packages/cli/demo/mdx/en/example.mdx:
--------------------------------------------------------------------------------
```markdown
---
title: "Restaurant Review: Bella Vista"
description: "Our dining experience at the new Italian restaurant downtown"
author: "not-localized-author"
published: "2024-03-15"
rating: 4.5
locked_key_1: "This value should remain unchanged in all locales"
ignored_key_1: "This field should not appear in target locales"
---
# Dinner at Bella Vista
We finally tried the new Italian restaurant that opened last month on Main Street. Here's our honest review.
## The Atmosphere
The restaurant has a warm, inviting atmosphere with:
- **Dim lighting** that creates an intimate setting
- *Soft jazz music* playing in the background
- Fresh flowers on every table
### Making Reservations
[Book your table online](https://example.com) or call during business hours.
> Tip: Weekend reservations fill up quickly, so book ahead!
## Menu Highlights
```javascript
// Restaurant website code - not localized
function displayMenu(category) {
const items = "This code stays in original language";
return renderMenuItems(items);
}
```
```css
/* Styling for menu display - not localized */
.menu-item {
color: "This CSS remains unchanged";
}
```
## Our Order
We started with the antipasto platter and house salad.
The pasta was cooked perfectly - exactly `al_dente` as it should be.
## Service Quality
The waitstaff was attentive but not overwhelming.
Our server checked on us regularly, maintaining `service.quality = "excellent"` throughout the evening.
## Final Verdict
| Course | Rating |
|--------|--------|
| Appetizers | `stars(4)` |
| Main dishes | `stars(5)` |
```
--------------------------------------------------------------------------------
/packages/cli/src/cli/loaders/xcode-strings/escape.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Unescape a string value from .strings file format
* Handles: \", \\, \n, \t, etc.
*/
export function unescapeString(raw: string): string {
let result = "";
let i = 0;
while (i < raw.length) {
if (raw[i] === "\\" && i + 1 < raw.length) {
const nextChar = raw[i + 1];
switch (nextChar) {
case '"':
result += '"';
i += 2;
break;
case "\\":
result += "\\";
i += 2;
break;
case "n":
result += "\n";
i += 2;
break;
case "t":
result += "\t";
i += 2;
break;
case "r":
result += "\r";
i += 2;
break;
default:
// Unknown escape - keep as-is
result += raw[i];
i++;
break;
}
} else {
result += raw[i];
i++;
}
}
return result;
}
/**
* Escape a string value for .strings file format
* Escapes: \, ", newlines to \n
*/
export function escapeString(str: string): string {
if (str == null) {
return "";
}
let result = "";
for (let i = 0; i < str.length; i++) {
const char = str[i];
switch (char) {
case "\\":
result += "\\\\";
break;
case '"':
result += '\\"';
break;
case "\n":
result += "\\n";
break;
case "\r":
result += "\\r";
break;
case "\t":
result += "\\t";
break;
default:
result += char;
break;
}
}
return result;
}
```
--------------------------------------------------------------------------------
/packages/cli/demo/android/es/example.xml:
--------------------------------------------------------------------------------
```
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">MyApp</string>
<string name="welcome_message">¡Hola, mundo!</string>
<string name="button_text">Comenzar</string>
<string-array name="color_names">
<item>Rojo</item>
<item>Verde</item>
<item>¡Azul!</item>
</string-array>
<plurals name="notification_count">
<item quantity="one">%d mensaje nuevo</item>
<item quantity="other">%d mensajes nuevos</item>
</plurals>
<bool name="show_tutorial">true</bool>
<bool name="enable_animations">false</bool>
<integer name="max_retry_attempts">3</integer>
<integer name="default_timeout">30</integer>
<string name="html_snippet"><b>Negrita</b></string>
<string name="apostrophe_example">¡No olvides!</string>
<string name="cdata_example"><![CDATA[Los usuarios solo pueden ver tu comentario después de registrarse. <u>Más información.</u>]]></string>
<string-array name="mixed_items">
<item> Elemento con espacios </item>
<item> </item>
</string-array>
<color name="primary_color">#FF6200EE</color>
<color name="secondary_color">#FF03DAC5</color>
<dimen name="text_size">16sp</dimen>
<dimen name="margin">8dp</dimen>
<integer-array name="numbers">
<item>1</item>
<item>2</item>
<item>3</item>
</integer-array>
<array name="icons">
<item>@drawable/icon1</item>
<item>@drawable/icon2</item>
</array>
<item type="id" name="button_ok"></item>
</resources>
```
--------------------------------------------------------------------------------
/packages/compiler/src/utils/module-params.spec.ts:
--------------------------------------------------------------------------------
```typescript
import { describe, it, expect } from "vitest";
import { parseParametrizedModuleId } from "./module-params";
import _ from "lodash";
describe("parseParametrizedModuleId", () => {
it("should extract the module id without parameters", () => {
const result = parseParametrizedModuleId("test-module");
expect(result).toEqual({
id: "test-module",
params: {},
});
});
it("should extract the module id with a single parameter", () => {
const result = parseParametrizedModuleId("test-module?key=value");
expect(result).toEqual({
id: "test-module",
params: { key: "value" },
});
});
it("should extract the module id with multiple parameters", () => {
const result = parseParametrizedModuleId(
"test-module?key1=value1&key2=value2&key3=value3",
);
expect(result).toEqual({
id: "test-module",
params: {
key1: "value1",
key2: "value2",
key3: "value3",
},
});
});
it("should handle parameters with special characters", () => {
const result = parseParametrizedModuleId(
"test-module?key=value%20with%20spaces&special=%21%40%23",
);
expect(result).toEqual({
id: "test-module",
params: {
key: "value with spaces",
special: "!@#",
},
});
});
it("should handle module ids with path-like structure", () => {
const result = parseParametrizedModuleId(
"parent/child/module?version=1.0.0",
);
expect(result).toEqual({
id: "parent/child/module",
params: { version: "1.0.0" },
});
});
});
```
--------------------------------------------------------------------------------
/packages/cli/src/cli/cmd/run/_types.ts:
--------------------------------------------------------------------------------
```typescript
import {
bucketTypeSchema,
I18nConfig,
localeCodeSchema,
bucketTypes,
} from "@lingo.dev/_spec";
import { z } from "zod";
import { ILocalizer } from "../../localizer/_types";
export type CmdRunContext = {
flags: CmdRunFlags;
config: I18nConfig | null;
localizer: ILocalizer | null;
tasks: CmdRunTask[];
results: Map<CmdRunTask, CmdRunTaskResult>;
};
export type CmdRunTaskResult = {
status: "success" | "error" | "skipped";
error?: Error;
pathPattern?: string;
sourceLocale?: string;
targetLocale?: string;
};
export type CmdRunTask = {
sourceLocale: string;
targetLocale: string;
bucketType: (typeof bucketTypes)[number];
bucketPathPattern: string;
injectLocale: string[];
lockedKeys: string[];
lockedPatterns: string[];
ignoredKeys: string[];
onlyKeys: string[];
formatter?: "prettier" | "biome";
};
export const flagsSchema = z.object({
bucket: z.array(bucketTypeSchema).optional(),
key: z.array(z.string()).optional(),
file: z.array(z.string()).optional(),
apiKey: z.string().optional(),
force: z.boolean().optional(),
frozen: z.boolean().optional(),
verbose: z.boolean().optional(),
strict: z.boolean().optional(),
interactive: z.boolean().default(false),
concurrency: z.number().positive().default(10),
debug: z.boolean().default(false),
sourceLocale: z.string().optional(),
targetLocale: z.array(z.string()).optional(),
watch: z.boolean().default(false),
debounce: z.number().positive().default(5000), // 5 seconds default
sound: z.boolean().optional(),
});
export type CmdRunFlags = z.infer<typeof flagsSchema>;
```
--------------------------------------------------------------------------------
/packages/compiler/src/jsx-attribute-scopes-export.ts:
--------------------------------------------------------------------------------
```typescript
import { getJsxAttributeValue } from "./utils";
import { LCP } from "./lib/lcp";
import { getJsxAttributeValueHash } from "./utils/hash";
import { collectJsxAttributeScopes } from "./utils/jsx-attribute-scope";
import { CompilerPayload } from "./_base";
import _ from "lodash";
// Processes only JSX attribute scopes
export function jsxAttributeScopesExportMutation(
payload: CompilerPayload,
): CompilerPayload {
const attributeScopes = collectJsxAttributeScopes(payload.ast);
if (_.isEmpty(attributeScopes)) {
return payload;
}
const lcp = LCP.getInstance({
sourceRoot: payload.params.sourceRoot,
lingoDir: payload.params.lingoDir,
});
for (const [scope, attributes] of attributeScopes) {
for (const attributeDefinition of attributes) {
const [attribute, scopeKey] = attributeDefinition.split(":");
lcp.resetScope(payload.relativeFilePath, scopeKey);
const attributeValue = getJsxAttributeValue(scope, attribute);
if (!attributeValue) {
continue;
}
lcp.setScopeType(payload.relativeFilePath, scopeKey, "attribute");
const hash = getJsxAttributeValueHash(String(attributeValue));
lcp.setScopeHash(payload.relativeFilePath, scopeKey, hash);
lcp.setScopeContext(payload.relativeFilePath, scopeKey, "");
lcp.setScopeSkip(payload.relativeFilePath, scopeKey, false);
lcp.setScopeOverrides(payload.relativeFilePath, scopeKey, {});
lcp.setScopeContent(
payload.relativeFilePath,
scopeKey,
String(attributeValue),
);
}
}
lcp.save();
return payload;
}
```
--------------------------------------------------------------------------------
/demo/vite-project/src/App.tsx:
--------------------------------------------------------------------------------
```typescript
import { useState } from "react";
import reactLogo from "./assets/react.svg";
import viteLogo from "/vite.svg";
import { LingoDotDev } from "./lingo-dot-dev";
import "./App.css";
import { TestComponent } from "./components/test";
import { LocaleSwitcher } from "lingo.dev/react/client";
function App() {
const [count, setCount] = useState(0);
return (
<>
<div className="logo-container">
<a href="https://vite.dev" target="_blank">
<img src={viteLogo} className="logo" alt="Vite logo" />
</a>
<a href="https://react.dev" target="_blank">
<img src={reactLogo} className="logo react" alt="React logo" />
</a>
<a href="https://lingo.dev" target="_blank">
<LingoDotDev />
</a>
</div>
<h1>Lingo.dev loves Vite and React</h1>
<p className="welcome-text">
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.
</p>
<div className="card">
<button onClick={() => setCount((count) => count + 1)}>
count is {count}
</button>
<p>
Edit <code>src/App.tsx</code> and save to test HMR
</p>
</div>
<p className="read-the-docs">Click on the logos above to learn more</p>
<TestComponent />
<div className="locale-switcher">
<LocaleSwitcher
locales={["en", "es", "fr", "ru", "de", "ja", "zh", "ar", "ko"]}
/>
</div>
</>
);
}
export default App;
```
--------------------------------------------------------------------------------
/packages/cli/demo/html/en/example.html:
--------------------------------------------------------------------------------
```html
<!DOCTYPE html>
<html lang="en">
<head>
<title>MyApp - Hello World</title>
<meta name="description" content="A simple demo app" />
<meta property="og:title" content="MyApp Demo" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta charset="UTF-8" />
<style>
body {
font-family: Arial, sans-serif;
color: #333;
}
.highlight::before {
content: "★ Featured";
}
</style>
<script>
const message = "User session initialized";
console.log(message);
</script>
</head>
<body>
<h1>Welcome to MyApp</h1>
<p>
Hello, world! This is a simple demo with
<strong>bold text</strong> and
<em>italic text</em>.
</p>
<div>
<div>
<span>Nested content here</span>
</div>
</div>
<img src="example.jpg" alt="Example image" />
<img
src="example.jpg"
alt="Demo image"
title="View product details"
/>
<input type="text" placeholder="Enter text here" />
<input
type="text"
placeholder="Enter value"
value="Default search query"
/>
<a href="#" title="Click for more">Learn more</a>
<a
href="not-localized.html"
data-info="Navigation link"
title="Navigate"
>
Go to page
</a>
<div class="content-area" data-value="Main content">
Content area
</div>
<script>
const inlineScript = "Page analytics loaded";
</script>
<style>
.inline-style {
content: "Notice: ";
}
</style>
</body>
</html>
```
--------------------------------------------------------------------------------
/packages/compiler/src/lib/lcp/api/prompt.spec.ts:
--------------------------------------------------------------------------------
```typescript
import prompt from "./prompt";
import { describe, it, expect, vi } from "vitest";
const baseArgs = {
sourceLocale: "en",
targetLocale: "es",
};
describe("prompt", () => {
it("returns user-defined prompt with replacements", () => {
const args = {
...baseArgs,
prompt: "Translate from {SOURCE_LOCALE} to {TARGET_LOCALE}.",
};
const result = prompt(args);
expect(result).toBe("Translate from en to es.");
});
it("trims and replaces variables in user prompt", () => {
const args = {
...baseArgs,
prompt: " {SOURCE_LOCALE} => {TARGET_LOCALE} ",
};
const result = prompt(args);
expect(result).toBe("en => es");
});
it("falls back to built-in prompt if no user prompt", () => {
const args = { ...baseArgs };
const result = prompt(args);
expect(result).toContain("You are an advanced AI localization engine");
expect(result).toContain("Source language (locale code): en");
expect(result).toContain("Target language (locale code): es");
});
it("logs when using user-defined prompt", () => {
const spy = vi.spyOn(console, "log");
const args = {
...baseArgs,
prompt: "Prompt {SOURCE_LOCALE} {TARGET_LOCALE}",
};
prompt(args);
expect(spy).toHaveBeenCalledWith(
"✨ Compiler is using user-defined prompt.",
);
spy.mockRestore();
});
it("returns built-in prompt if user prompt is empty or whitespace", () => {
const args = {
...baseArgs,
prompt: " ",
};
const result = prompt(args);
expect(result).toContain("You are an advanced AI localization engine");
});
});
```
--------------------------------------------------------------------------------
/packages/compiler/src/jsx-scopes-export.spec.ts:
--------------------------------------------------------------------------------
```typescript
import { describe, it, expect, vi, beforeEach } from "vitest";
import { createPayload, defaultParams } from "./_base";
import { jsxScopesExportMutation } from "./jsx-scopes-export";
vi.mock("./lib/lcp", () => {
const instance = {
resetScope: vi.fn().mockReturnThis(),
setScopeType: vi.fn().mockReturnThis(),
setScopeHash: vi.fn().mockReturnThis(),
setScopeContext: vi.fn().mockReturnThis(),
setScopeSkip: vi.fn().mockReturnThis(),
setScopeOverrides: vi.fn().mockReturnThis(),
setScopeContent: vi.fn().mockReturnThis(),
save: vi.fn(),
};
const getInstance = vi.fn(() => instance);
return {
LCP: {
getInstance,
},
__test__: { instance, getInstance },
};
});
describe("jsxScopesExportMutation", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("exports element scope with hash/content/flags", async () => {
const code = `
export default function X(){
return <div data-jsx-scope="scope-1">Foobar</div>
}`.trim();
const input = createPayload({
code,
params: defaultParams,
relativeFilePath: "src/App.tsx",
} as any);
jsxScopesExportMutation(input);
const lcpMod: any = await import("./lib/lcp");
const inst = lcpMod.__test__.instance;
expect(lcpMod.LCP.getInstance).toHaveBeenCalled();
expect(inst.setScopeType).toHaveBeenCalledWith(
"src/App.tsx",
"0/declaration/body/0/argument",
"element",
);
expect(inst.setScopeContent).toHaveBeenCalledWith(
"src/App.tsx",
"0/declaration/body/0/argument",
"Foobar",
);
expect(inst.save).toHaveBeenCalled();
});
});
```
--------------------------------------------------------------------------------
/packages/compiler/src/utils/jsx-variables.ts:
--------------------------------------------------------------------------------
```typescript
import { NodePath } from "@babel/traverse";
import * as t from "@babel/types";
import { Expression } from "@babel/types";
export const getJsxVariables = (nodePath: NodePath<t.JSXElement>) => {
/*
example input:
<div>You have {count} new messages.</div>
example output:
t.objectExpression([
t.objectProperty(t.identifier("count"), t.identifier("count")),
])
*/
const variables = new Set<string>();
nodePath.traverse({
JSXOpeningElement(path) {
path.skip();
},
JSXExpressionContainer(path) {
if (t.isIdentifier(path.node.expression)) {
variables.add(path.node.expression.name);
} else if (t.isMemberExpression(path.node.expression)) {
// Handle nested expressions like object.property.value
let current: Expression = path.node.expression;
const parts: string[] = [];
while (t.isMemberExpression(current)) {
if (t.isIdentifier(current.property)) {
if (current.computed) {
parts.unshift(`[${current.property.name}]`);
} else {
parts.unshift(current.property.name);
}
}
current = current.object;
}
if (t.isIdentifier(current)) {
parts.unshift(current.name);
variables.add(parts.join(".").replaceAll(".[", "["));
}
}
// Skip traversing inside the expression
path.skip();
},
});
const properties = Array.from(variables).map((name) =>
t.objectProperty(t.stringLiteral(name), t.identifier(name)),
);
const result = t.objectExpression(properties);
return result;
};
```
--------------------------------------------------------------------------------
/packages/cli/src/cli/cmd/auth.ts:
--------------------------------------------------------------------------------
```typescript
import { Command } from "interactive-commander";
import Ora from "ora";
import { getSettings, saveSettings } from "../utils/settings";
import { createAuthenticator } from "../utils/auth";
export default new Command()
.command("auth")
.description("Show current authentication status and user email")
.helpOption("-h, --help", "Show help")
// Deprecated options, safe to remove after September 2025
.option(
"--login",
"DEPRECATED: Shows deprecation warning and exits. Use `lingo.dev login` instead",
)
.option(
"--logout",
"DEPRECATED: Shows deprecation warning and exits. Use `lingo.dev logout` instead",
)
.action(async (options) => {
try {
// Handle deprecated login option
if (options.login) {
Ora().warn(
"⚠️ DEPRECATED: '--login' is deprecated. Please use 'lingo.dev login' instead.",
);
process.exit(1);
}
// Handle deprecated logout option
if (options.logout) {
Ora().warn(
"⚠️ DEPRECATED: '--logout' is deprecated. Please use 'lingo.dev logout' instead.",
);
process.exit(1);
}
// Default behavior: show authentication status
const settings = await getSettings(undefined);
const authenticator = createAuthenticator({
apiUrl: settings.auth.apiUrl,
apiKey: settings.auth.apiKey!,
});
const auth = await authenticator.whoami();
if (!auth) {
Ora().warn("Not authenticated");
} else {
Ora().succeed(`Authenticated as ${auth.email}`);
}
} catch (error: any) {
Ora().fail(error.message);
process.exit(1);
}
});
```
--------------------------------------------------------------------------------
/packages/react/src/core/get-dictionary.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Loads a dictionary for the specified locale.
*
* This function attempts to load a dictionary using the provided loaders. If the specified
* locale is not available, it falls back to the first available loader. The function
* expects the loader to return a promise that resolves to an object with a `default` property
* containing the dictionary data (the default export from dictionary file).
*
* @param locale - The locale to load the dictionary for. Can be null to use the first available loader.
* @param loaders - A record of locale keys to loader functions. Each loader should return a Promise
* that resolves to an object with a `default` property containing the dictionary.
* @returns A Promise that resolves to the dictionary data (the `default` export from the loader).
* @throws {Error} When no loaders are provided or available.
*
* @example
* ```typescript
* const loaders = {
* 'en': () => import('./en.json'),
* 'es': () => import('./es.json')
* };
*
* const dictionary = await loadDictionary('en', loaders);
* // Returns the default export from the English dictionary
* ```
*/
export function getDictionary(
locale: string | null,
loaders: Record<string, () => Promise<any>> = {},
) {
const loader = getDictionaryLoader(locale, loaders);
if (!loader) {
throw new Error("No available dictionary loaders found");
}
return loader().then((value) => value.default);
}
function getDictionaryLoader(
locale: string | null,
loaders: Record<string, () => Promise<any>> = {},
) {
if (locale && loaders[locale]) {
return loaders[locale];
}
return Object.values(loaders)[0];
}
```