This is page 2 of 20. Use http://codebase.md/lingodotdev/lingo.dev?lines=true&page={x} to view the full context.
# Directory Structure
```
├── .changeset
│ ├── config.json
│ └── README.md
├── .claude
│ ├── agents
│ │ └── code-architect-reviewer.md
│ └── commands
│ ├── analyze-bucket-type.md
│ └── create-bucket-docs.md
├── .editorconfig
├── .github
│ ├── dependabot.yml
│ └── workflows
│ ├── docker.yml
│ ├── lingodotdev.yml
│ ├── pr-check.yml
│ ├── pr-lint.yml
│ └── release.yml
├── .gitignore
├── .husky
│ └── commit-msg
├── .npmrc
├── .prettierignore
├── .prettierrc
├── .vscode
│ ├── extensions.json
│ ├── launch.json
│ └── settings.json
├── action.yml
├── CLAUDE.md
├── CODE_OF_CONDUCT.md
├── commitlint.config.js
├── composer.json
├── content
│ ├── banner.compiler.png
│ ├── banner.dark.png
│ └── banner.launch.png
├── CONTRIBUTING.md
├── DEBUGGING.md
├── demo
│ ├── adonisjs
│ │ ├── .editorconfig
│ │ ├── .env.example
│ │ ├── .gitignore
│ │ ├── ace.js
│ │ ├── adonisrc.ts
│ │ ├── app
│ │ │ ├── exceptions
│ │ │ │ └── handler.ts
│ │ │ └── middleware
│ │ │ └── container_bindings_middleware.ts
│ │ ├── bin
│ │ │ ├── console.ts
│ │ │ ├── server.ts
│ │ │ └── test.ts
│ │ ├── CHANGELOG.md
│ │ ├── config
│ │ │ ├── app.ts
│ │ │ ├── bodyparser.ts
│ │ │ ├── cors.ts
│ │ │ ├── hash.ts
│ │ │ ├── inertia.ts
│ │ │ ├── logger.ts
│ │ │ ├── session.ts
│ │ │ ├── shield.ts
│ │ │ ├── static.ts
│ │ │ └── vite.ts
│ │ ├── eslint.config.js
│ │ ├── inertia
│ │ │ ├── app
│ │ │ │ ├── app.tsx
│ │ │ │ └── ssr.tsx
│ │ │ ├── css
│ │ │ │ └── app.css
│ │ │ ├── lingo
│ │ │ │ ├── dictionary.js
│ │ │ │ └── meta.json
│ │ │ ├── pages
│ │ │ │ ├── errors
│ │ │ │ │ ├── not_found.tsx
│ │ │ │ │ └── server_error.tsx
│ │ │ │ └── home.tsx
│ │ │ └── tsconfig.json
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── resources
│ │ │ └── views
│ │ │ └── inertia_layout.edge
│ │ ├── start
│ │ │ ├── env.ts
│ │ │ ├── kernel.ts
│ │ │ └── routes.ts
│ │ ├── tests
│ │ │ └── bootstrap.ts
│ │ ├── tsconfig.json
│ │ └── vite.config.ts
│ ├── next-app
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── eslint.config.mjs
│ │ ├── next.config.ts
│ │ ├── package.json
│ │ ├── postcss.config.mjs
│ │ ├── public
│ │ │ ├── file.svg
│ │ │ ├── globe.svg
│ │ │ ├── next.svg
│ │ │ ├── vercel.svg
│ │ │ └── window.svg
│ │ ├── README.md
│ │ ├── src
│ │ │ ├── app
│ │ │ │ ├── client-component.tsx
│ │ │ │ ├── favicon.ico
│ │ │ │ ├── globals.css
│ │ │ │ ├── layout.tsx
│ │ │ │ ├── lingo-dot-dev.tsx
│ │ │ │ ├── page.tsx
│ │ │ │ └── test
│ │ │ │ └── page.tsx
│ │ │ ├── components
│ │ │ │ ├── hero-actions.tsx
│ │ │ │ ├── hero-subtitle.tsx
│ │ │ │ ├── hero-title.tsx
│ │ │ │ └── index.ts
│ │ │ └── lingo
│ │ │ ├── dictionary.js
│ │ │ └── meta.json
│ │ └── tsconfig.json
│ ├── react-router-app
│ │ ├── .dockerignore
│ │ ├── .gitignore
│ │ ├── app
│ │ │ ├── app.css
│ │ │ ├── lingo
│ │ │ │ ├── dictionary.js
│ │ │ │ └── meta.json
│ │ │ ├── root.tsx
│ │ │ ├── routes
│ │ │ │ ├── home.tsx
│ │ │ │ └── test.tsx
│ │ │ ├── routes.ts
│ │ │ └── welcome
│ │ │ ├── lingo-dot-dev.tsx
│ │ │ ├── logo-dark.svg
│ │ │ ├── logo-light.svg
│ │ │ └── welcome.tsx
│ │ ├── Dockerfile
│ │ ├── package.json
│ │ ├── public
│ │ │ └── favicon.ico
│ │ ├── react-router.config.ts
│ │ ├── README.md
│ │ ├── tsconfig.json
│ │ └── vite.config.ts
│ └── vite-project
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── eslint.config.js
│ ├── index.html
│ ├── package.json
│ ├── public
│ │ └── vite.svg
│ ├── README.md
│ ├── src
│ │ ├── App.css
│ │ ├── App.tsx
│ │ ├── assets
│ │ │ └── react.svg
│ │ ├── components
│ │ │ └── test.tsx
│ │ ├── index.css
│ │ ├── lingo
│ │ │ ├── dictionary.js
│ │ │ └── meta.json
│ │ ├── lingo-dot-dev.tsx
│ │ ├── main.tsx
│ │ └── vite-env.d.ts
│ ├── tsconfig.app.json
│ ├── tsconfig.json
│ ├── tsconfig.node.json
│ └── vite.config.ts
├── Dockerfile
├── i18n.json
├── i18n.lock
├── integrations
│ └── directus
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── docker-compose.yml
│ ├── Dockerfile
│ ├── package.json
│ ├── README.md
│ ├── src
│ │ ├── api.ts
│ │ ├── app.ts
│ │ └── index.spec.ts
│ ├── tsconfig.json
│ ├── tsconfig.test.json
│ └── tsup.config.ts
├── ISSUE_TEMPLATE.md
├── legacy
│ ├── cli
│ │ ├── bin
│ │ │ └── cli.mjs
│ │ ├── CHANGELOG.md
│ │ ├── package.json
│ │ └── readme.md
│ └── sdk
│ ├── CHANGELOG.md
│ ├── index.d.ts
│ ├── index.js
│ ├── package.json
│ └── README.md
├── LICENSE.md
├── mcp.md
├── package.json
├── packages
│ ├── cli
│ │ ├── assets
│ │ │ ├── failure.mp3
│ │ │ └── success.mp3
│ │ ├── bin
│ │ │ └── cli.mjs
│ │ ├── CHANGELOG.md
│ │ ├── demo
│ │ │ ├── android
│ │ │ │ ├── en
│ │ │ │ │ └── example.xml
│ │ │ │ ├── es
│ │ │ │ │ └── example.xml
│ │ │ │ ├── i18n.json
│ │ │ │ └── i18n.lock
│ │ │ ├── csv
│ │ │ │ ├── example.csv
│ │ │ │ ├── i18n.json
│ │ │ │ └── i18n.lock
│ │ │ ├── demo.spec.ts
│ │ │ ├── ejs
│ │ │ │ ├── en
│ │ │ │ │ └── example.ejs
│ │ │ │ ├── es
│ │ │ │ │ └── example.ejs
│ │ │ │ ├── i18n.json
│ │ │ │ └── i18n.lock
│ │ │ ├── flutter
│ │ │ │ ├── en
│ │ │ │ │ └── example.arb
│ │ │ │ ├── es
│ │ │ │ │ └── example.arb
│ │ │ │ ├── i18n.json
│ │ │ │ └── i18n.lock
│ │ │ ├── html
│ │ │ │ ├── en
│ │ │ │ │ └── example.html
│ │ │ │ ├── es
│ │ │ │ │ └── example.html
│ │ │ │ ├── i18n.json
│ │ │ │ └── i18n.lock
│ │ │ ├── json
│ │ │ │ ├── en
│ │ │ │ │ └── example.json
│ │ │ │ ├── es
│ │ │ │ │ └── example.json
│ │ │ │ ├── i18n.json
│ │ │ │ └── i18n.lock
│ │ │ ├── json-dictionary
│ │ │ │ ├── example.json
│ │ │ │ ├── i18n.json
│ │ │ │ └── i18n.lock
│ │ │ ├── json5
│ │ │ │ ├── en
│ │ │ │ │ └── example.json5
│ │ │ │ ├── es
│ │ │ │ │ └── example.json5
│ │ │ │ ├── i18n.json
│ │ │ │ └── i18n.lock
│ │ │ ├── jsonc
│ │ │ │ ├── en
│ │ │ │ │ └── example.jsonc
│ │ │ │ ├── es
│ │ │ │ │ └── example.jsonc
│ │ │ │ ├── i18n.json
│ │ │ │ ├── i18n.lock
│ │ │ │ └── ru
│ │ │ │ └── example.jsonc
│ │ │ ├── markdoc
│ │ │ │ ├── en
│ │ │ │ │ └── example.markdoc
│ │ │ │ ├── es
│ │ │ │ │ └── example.markdoc
│ │ │ │ ├── i18n.json
│ │ │ │ └── i18n.lock
│ │ │ ├── markdown
│ │ │ │ ├── en
│ │ │ │ │ └── example.md
│ │ │ │ ├── es
│ │ │ │ │ └── example.md
│ │ │ │ ├── i18n.json
│ │ │ │ └── i18n.lock
│ │ │ ├── mdx
│ │ │ │ ├── en
│ │ │ │ │ └── example.mdx
│ │ │ │ ├── es
│ │ │ │ │ └── example.mdx
│ │ │ │ ├── i18n.json
│ │ │ │ └── i18n.lock
│ │ │ ├── php
│ │ │ │ ├── en
│ │ │ │ │ └── example.php
│ │ │ │ ├── es
│ │ │ │ │ └── example.php
│ │ │ │ ├── i18n.json
│ │ │ │ └── i18n.lock
│ │ │ ├── po
│ │ │ │ ├── en
│ │ │ │ │ └── example.po
│ │ │ │ ├── es
│ │ │ │ │ └── example.po
│ │ │ │ ├── i18n.json
│ │ │ │ └── i18n.lock
│ │ │ ├── properties
│ │ │ │ ├── en
│ │ │ │ │ └── example.properties
│ │ │ │ ├── es
│ │ │ │ │ └── example.properties
│ │ │ │ ├── i18n.json
│ │ │ │ └── i18n.lock
│ │ │ ├── run_i18n.sh
│ │ │ ├── srt
│ │ │ │ ├── en
│ │ │ │ │ └── example.srt
│ │ │ │ ├── es
│ │ │ │ │ └── example.srt
│ │ │ │ ├── i18n.json
│ │ │ │ └── i18n.lock
│ │ │ ├── txt
│ │ │ │ ├── en
│ │ │ │ │ └── example.txt
│ │ │ │ ├── es
│ │ │ │ │ └── example.txt
│ │ │ │ ├── i18n.json
│ │ │ │ └── i18n.lock
│ │ │ ├── typescript
│ │ │ │ ├── en
│ │ │ │ │ └── example.ts
│ │ │ │ ├── es
│ │ │ │ │ └── example.ts
│ │ │ │ ├── i18n.json
│ │ │ │ └── i18n.lock
│ │ │ ├── vtt
│ │ │ │ ├── en
│ │ │ │ │ └── example.vtt
│ │ │ │ ├── es
│ │ │ │ │ └── example.vtt
│ │ │ │ ├── i18n.json
│ │ │ │ └── i18n.lock
│ │ │ ├── vue-json
│ │ │ │ ├── example.vue
│ │ │ │ ├── i18n.json
│ │ │ │ └── i18n.lock
│ │ │ ├── xcode-strings
│ │ │ │ ├── en
│ │ │ │ │ └── example.strings
│ │ │ │ ├── es
│ │ │ │ │ └── example.strings
│ │ │ │ ├── i18n.json
│ │ │ │ └── i18n.lock
│ │ │ ├── xcode-stringsdict
│ │ │ │ ├── en
│ │ │ │ │ └── example.stringsdict
│ │ │ │ ├── es
│ │ │ │ │ └── example.stringsdict
│ │ │ │ ├── i18n.json
│ │ │ │ └── i18n.lock
│ │ │ ├── xcode-xcstrings
│ │ │ │ ├── example.xcstrings
│ │ │ │ ├── i18n.json
│ │ │ │ └── i18n.lock
│ │ │ ├── xcode-xcstrings-v2
│ │ │ │ ├── complex-example.xcstrings
│ │ │ │ ├── example.xcstrings
│ │ │ │ ├── i18n.json
│ │ │ │ └── i18n.lock
│ │ │ ├── xliff
│ │ │ │ ├── en
│ │ │ │ │ ├── example-v1.2.xliff
│ │ │ │ │ └── example-v2.xliff
│ │ │ │ ├── es
│ │ │ │ │ ├── example-v1.2.xliff
│ │ │ │ │ ├── example-v2.xliff
│ │ │ │ │ └── example.xliff
│ │ │ │ ├── i18n.json
│ │ │ │ └── i18n.lock
│ │ │ ├── xml
│ │ │ │ ├── en
│ │ │ │ │ └── example.xml
│ │ │ │ ├── es
│ │ │ │ │ └── example.xml
│ │ │ │ ├── i18n.json
│ │ │ │ └── i18n.lock
│ │ │ ├── yaml
│ │ │ │ ├── en
│ │ │ │ │ └── example.yml
│ │ │ │ ├── es
│ │ │ │ │ └── example.yml
│ │ │ │ ├── i18n.json
│ │ │ │ └── i18n.lock
│ │ │ └── yaml-root-key
│ │ │ ├── en
│ │ │ │ └── example.yml
│ │ │ ├── es
│ │ │ │ └── example.yml
│ │ │ ├── i18n.json
│ │ │ └── i18n.lock
│ │ ├── i18n.json
│ │ ├── i18n.lock
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── src
│ │ │ ├── cli
│ │ │ │ ├── cmd
│ │ │ │ │ ├── auth.ts
│ │ │ │ │ ├── ci
│ │ │ │ │ │ ├── flows
│ │ │ │ │ │ │ ├── _base.ts
│ │ │ │ │ │ │ ├── in-branch.ts
│ │ │ │ │ │ │ └── pull-request.ts
│ │ │ │ │ │ ├── index.ts
│ │ │ │ │ │ └── platforms
│ │ │ │ │ │ ├── _base.ts
│ │ │ │ │ │ ├── bitbucket.ts
│ │ │ │ │ │ ├── github.ts
│ │ │ │ │ │ ├── gitlab.ts
│ │ │ │ │ │ └── index.ts
│ │ │ │ │ ├── cleanup.ts
│ │ │ │ │ ├── config
│ │ │ │ │ │ ├── get.ts
│ │ │ │ │ │ ├── index.ts
│ │ │ │ │ │ ├── set.ts
│ │ │ │ │ │ └── unset.ts
│ │ │ │ │ ├── i18n.ts
│ │ │ │ │ ├── init.ts
│ │ │ │ │ ├── lockfile.ts
│ │ │ │ │ ├── login.ts
│ │ │ │ │ ├── logout.ts
│ │ │ │ │ ├── may-the-fourth.ts
│ │ │ │ │ ├── mcp.ts
│ │ │ │ │ ├── purge.ts
│ │ │ │ │ ├── run
│ │ │ │ │ │ ├── _const.ts
│ │ │ │ │ │ ├── _types.ts
│ │ │ │ │ │ ├── _utils.ts
│ │ │ │ │ │ ├── execute.spec.ts
│ │ │ │ │ │ ├── execute.ts
│ │ │ │ │ │ ├── frozen.ts
│ │ │ │ │ │ ├── index.ts
│ │ │ │ │ │ ├── plan.ts
│ │ │ │ │ │ ├── setup.ts
│ │ │ │ │ │ └── watch.ts
│ │ │ │ │ ├── show
│ │ │ │ │ │ ├── _shared-key-command.ts
│ │ │ │ │ │ ├── config.ts
│ │ │ │ │ │ ├── files.ts
│ │ │ │ │ │ ├── ignored-keys.ts
│ │ │ │ │ │ ├── index.ts
│ │ │ │ │ │ ├── locale.ts
│ │ │ │ │ │ └── locked-keys.ts
│ │ │ │ │ └── status.ts
│ │ │ │ ├── constants.ts
│ │ │ │ ├── index.spec.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── loaders
│ │ │ │ │ ├── _types.ts
│ │ │ │ │ ├── _utils.ts
│ │ │ │ │ ├── android.spec.ts
│ │ │ │ │ ├── android.ts
│ │ │ │ │ ├── csv.spec.ts
│ │ │ │ │ ├── csv.ts
│ │ │ │ │ ├── dato
│ │ │ │ │ │ ├── _base.ts
│ │ │ │ │ │ ├── _utils.ts
│ │ │ │ │ │ ├── api.ts
│ │ │ │ │ │ ├── extract.ts
│ │ │ │ │ │ ├── filter.ts
│ │ │ │ │ │ └── index.ts
│ │ │ │ │ ├── ejs.spec.ts
│ │ │ │ │ ├── ejs.ts
│ │ │ │ │ ├── ensure-key-order.spec.ts
│ │ │ │ │ ├── ensure-key-order.ts
│ │ │ │ │ ├── flat.spec.ts
│ │ │ │ │ ├── flat.ts
│ │ │ │ │ ├── flutter.spec.ts
│ │ │ │ │ ├── flutter.ts
│ │ │ │ │ ├── formatters
│ │ │ │ │ │ ├── _base.ts
│ │ │ │ │ │ ├── biome.ts
│ │ │ │ │ │ ├── index.ts
│ │ │ │ │ │ └── prettier.ts
│ │ │ │ │ ├── html.ts
│ │ │ │ │ ├── icu-safety.spec.ts
│ │ │ │ │ ├── ignored-keys-buckets.spec.ts
│ │ │ │ │ ├── ignored-keys.spec.ts
│ │ │ │ │ ├── ignored-keys.ts
│ │ │ │ │ ├── index.spec.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── inject-locale.spec.ts
│ │ │ │ │ ├── inject-locale.ts
│ │ │ │ │ ├── json-dictionary.spec.ts
│ │ │ │ │ ├── json-dictionary.ts
│ │ │ │ │ ├── json-sorting.test.ts
│ │ │ │ │ ├── json-sorting.ts
│ │ │ │ │ ├── json.ts
│ │ │ │ │ ├── json5.spec.ts
│ │ │ │ │ ├── json5.ts
│ │ │ │ │ ├── jsonc.spec.ts
│ │ │ │ │ ├── jsonc.ts
│ │ │ │ │ ├── locked-keys.spec.ts
│ │ │ │ │ ├── locked-keys.ts
│ │ │ │ │ ├── locked-patterns.spec.ts
│ │ │ │ │ ├── locked-patterns.ts
│ │ │ │ │ ├── markdoc.spec.ts
│ │ │ │ │ ├── markdoc.ts
│ │ │ │ │ ├── markdown.ts
│ │ │ │ │ ├── mdx.spec.ts
│ │ │ │ │ ├── mdx.ts
│ │ │ │ │ ├── mdx2
│ │ │ │ │ │ ├── _types.ts
│ │ │ │ │ │ ├── _utils.ts
│ │ │ │ │ │ ├── code-placeholder.spec.ts
│ │ │ │ │ │ ├── code-placeholder.ts
│ │ │ │ │ │ ├── frontmatter-split.spec.ts
│ │ │ │ │ │ ├── frontmatter-split.ts
│ │ │ │ │ │ ├── localizable-document.spec.ts
│ │ │ │ │ │ ├── localizable-document.ts
│ │ │ │ │ │ ├── section-split.spec.ts
│ │ │ │ │ │ ├── section-split.ts
│ │ │ │ │ │ └── sections-split-2.ts
│ │ │ │ │ ├── passthrough.ts
│ │ │ │ │ ├── php.ts
│ │ │ │ │ ├── plutil-json-loader.ts
│ │ │ │ │ ├── po
│ │ │ │ │ │ ├── _types.ts
│ │ │ │ │ │ ├── index.spec.ts
│ │ │ │ │ │ └── index.ts
│ │ │ │ │ ├── properties.ts
│ │ │ │ │ ├── root-key.ts
│ │ │ │ │ ├── srt.ts
│ │ │ │ │ ├── sync.ts
│ │ │ │ │ ├── text-file.ts
│ │ │ │ │ ├── txt.ts
│ │ │ │ │ ├── typescript
│ │ │ │ │ │ ├── cjs-interop.ts
│ │ │ │ │ │ ├── index.spec.ts
│ │ │ │ │ │ └── index.ts
│ │ │ │ │ ├── unlocalizable.spec.ts
│ │ │ │ │ ├── unlocalizable.ts
│ │ │ │ │ ├── variable
│ │ │ │ │ │ ├── index.spec.ts
│ │ │ │ │ │ └── index.ts
│ │ │ │ │ ├── vtt.ts
│ │ │ │ │ ├── vue-json.ts
│ │ │ │ │ ├── xcode-strings
│ │ │ │ │ │ ├── escape.ts
│ │ │ │ │ │ ├── parser.ts
│ │ │ │ │ │ ├── tokenizer.ts
│ │ │ │ │ │ └── types.ts
│ │ │ │ │ ├── xcode-strings.spec.ts
│ │ │ │ │ ├── xcode-strings.ts
│ │ │ │ │ ├── xcode-stringsdict.ts
│ │ │ │ │ ├── xcode-xcstrings-icu.spec.ts
│ │ │ │ │ ├── xcode-xcstrings-icu.ts
│ │ │ │ │ ├── xcode-xcstrings-lock-compatibility.spec.ts
│ │ │ │ │ ├── xcode-xcstrings-v2-loader.ts
│ │ │ │ │ ├── xcode-xcstrings.spec.ts
│ │ │ │ │ ├── xcode-xcstrings.ts
│ │ │ │ │ ├── xliff.spec.ts
│ │ │ │ │ ├── xliff.ts
│ │ │ │ │ ├── xml.ts
│ │ │ │ │ └── yaml.ts
│ │ │ │ ├── localizer
│ │ │ │ │ ├── _types.ts
│ │ │ │ │ ├── explicit.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ └── lingodotdev.ts
│ │ │ │ ├── processor
│ │ │ │ │ ├── _base.ts
│ │ │ │ │ ├── basic.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ └── lingo.ts
│ │ │ │ └── utils
│ │ │ │ ├── auth.ts
│ │ │ │ ├── buckets.spec.ts
│ │ │ │ ├── buckets.ts
│ │ │ │ ├── cache.ts
│ │ │ │ ├── cloudflare-status.ts
│ │ │ │ ├── config.ts
│ │ │ │ ├── delta.spec.ts
│ │ │ │ ├── delta.ts
│ │ │ │ ├── ensure-patterns.ts
│ │ │ │ ├── errors.ts
│ │ │ │ ├── exec.spec.ts
│ │ │ │ ├── exec.ts
│ │ │ │ ├── exit-gracefully.spec.ts
│ │ │ │ ├── exit-gracefully.ts
│ │ │ │ ├── exp-backoff.ts
│ │ │ │ ├── find-locale-paths.spec.ts
│ │ │ │ ├── find-locale-paths.ts
│ │ │ │ ├── fs.ts
│ │ │ │ ├── init-ci-cd.ts
│ │ │ │ ├── key-matching.spec.ts
│ │ │ │ ├── key-matching.ts
│ │ │ │ ├── lockfile.ts
│ │ │ │ ├── md5.ts
│ │ │ │ ├── observability.ts
│ │ │ │ ├── plutil-formatter.spec.ts
│ │ │ │ ├── plutil-formatter.ts
│ │ │ │ ├── settings.ts
│ │ │ │ ├── ui.ts
│ │ │ │ └── update-gitignore.ts
│ │ │ ├── compiler
│ │ │ │ └── index.ts
│ │ │ ├── locale-codes
│ │ │ │ └── index.ts
│ │ │ ├── react
│ │ │ │ ├── client.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── react-router.ts
│ │ │ │ └── rsc.ts
│ │ │ ├── sdk
│ │ │ │ └── index.ts
│ │ │ └── spec
│ │ │ └── index.ts
│ │ ├── tests
│ │ │ └── mock-storage.ts
│ │ ├── troubleshooting.md
│ │ ├── tsconfig.json
│ │ ├── tsconfig.test.json
│ │ ├── tsup.config.ts
│ │ ├── types
│ │ │ ├── vtt.d.ts
│ │ │ └── xliff.d.ts
│ │ ├── vitest.config.ts
│ │ └── WATCH_MODE.md
│ ├── compiler
│ │ ├── CHANGELOG.md
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── src
│ │ │ ├── _base.ts
│ │ │ ├── _const.ts
│ │ │ ├── _loader-utils.spec.ts
│ │ │ ├── _loader-utils.ts
│ │ │ ├── _utils.spec.ts
│ │ │ ├── _utils.ts
│ │ │ ├── client-dictionary-loader.ts
│ │ │ ├── i18n-directive.spec.ts
│ │ │ ├── i18n-directive.ts
│ │ │ ├── index.spec.ts
│ │ │ ├── index.ts
│ │ │ ├── jsx-attribute-flag.spec.ts
│ │ │ ├── jsx-attribute-flag.ts
│ │ │ ├── jsx-attribute-scope-inject.spec.ts
│ │ │ ├── jsx-attribute-scope-inject.ts
│ │ │ ├── jsx-attribute-scopes-export.spec.ts
│ │ │ ├── jsx-attribute-scopes-export.ts
│ │ │ ├── jsx-attribute.spec.ts
│ │ │ ├── jsx-attribute.ts
│ │ │ ├── jsx-fragment.spec.ts
│ │ │ ├── jsx-fragment.ts
│ │ │ ├── jsx-html-lang.spec.ts
│ │ │ ├── jsx-html-lang.ts
│ │ │ ├── jsx-provider.spec.ts
│ │ │ ├── jsx-provider.ts
│ │ │ ├── jsx-remove-attributes.spec.ts
│ │ │ ├── jsx-remove-attributes.ts
│ │ │ ├── jsx-root-flag.spec.ts
│ │ │ ├── jsx-root-flag.ts
│ │ │ ├── jsx-scope-flag.spec.ts
│ │ │ ├── jsx-scope-flag.ts
│ │ │ ├── jsx-scope-inject.spec.ts
│ │ │ ├── jsx-scope-inject.ts
│ │ │ ├── jsx-scopes-export.spec.ts
│ │ │ ├── jsx-scopes-export.ts
│ │ │ ├── lib
│ │ │ │ └── lcp
│ │ │ │ ├── api
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── prompt.spec.ts
│ │ │ │ │ ├── prompt.ts
│ │ │ │ │ ├── provider-details.spec.ts
│ │ │ │ │ ├── provider-details.ts
│ │ │ │ │ ├── shots.ts
│ │ │ │ │ ├── xml2obj.spec.ts
│ │ │ │ │ └── xml2obj.ts
│ │ │ │ ├── api.spec.ts
│ │ │ │ ├── cache.spec.ts
│ │ │ │ ├── cache.ts
│ │ │ │ ├── index.spec.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── schema.ts
│ │ │ │ ├── server.spec.ts
│ │ │ │ └── server.ts
│ │ │ ├── lingo-turbopack-loader.ts
│ │ │ ├── react-router-dictionary-loader.ts
│ │ │ ├── rsc-dictionary-loader.ts
│ │ │ └── utils
│ │ │ ├── ast-key.spec.ts
│ │ │ ├── ast-key.ts
│ │ │ ├── create-locale-import-map.spec.ts
│ │ │ ├── create-locale-import-map.ts
│ │ │ ├── env.spec.ts
│ │ │ ├── env.ts
│ │ │ ├── hash.spec.ts
│ │ │ ├── hash.ts
│ │ │ ├── index.spec.ts
│ │ │ ├── index.ts
│ │ │ ├── invokations.spec.ts
│ │ │ ├── invokations.ts
│ │ │ ├── jsx-attribute-scope.ts
│ │ │ ├── jsx-attribute.spec.ts
│ │ │ ├── jsx-attribute.ts
│ │ │ ├── jsx-content-whitespace.spec.ts
│ │ │ ├── jsx-content.spec.ts
│ │ │ ├── jsx-content.ts
│ │ │ ├── jsx-element.spec.ts
│ │ │ ├── jsx-element.ts
│ │ │ ├── jsx-expressions.test.ts
│ │ │ ├── jsx-expressions.ts
│ │ │ ├── jsx-functions.spec.ts
│ │ │ ├── jsx-functions.ts
│ │ │ ├── jsx-scope.spec.ts
│ │ │ ├── jsx-scope.ts
│ │ │ ├── jsx-variables.spec.ts
│ │ │ ├── jsx-variables.ts
│ │ │ ├── llm-api-key.ts
│ │ │ ├── llm-api-keys.spec.ts
│ │ │ ├── locales.spec.ts
│ │ │ ├── locales.ts
│ │ │ ├── module-params.spec.ts
│ │ │ ├── module-params.ts
│ │ │ ├── observability.spec.ts
│ │ │ ├── observability.ts
│ │ │ ├── rc.spec.ts
│ │ │ └── rc.ts
│ │ ├── tsconfig.json
│ │ ├── tsup.config.ts
│ │ └── vitest.config.ts
│ ├── locales
│ │ ├── CHANGELOG.md
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── src
│ │ │ ├── constants.ts
│ │ │ ├── index.ts
│ │ │ ├── names
│ │ │ │ ├── index.spec.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── integration.spec.ts
│ │ │ │ └── loader.ts
│ │ │ ├── parser.spec.ts
│ │ │ ├── parser.ts
│ │ │ ├── types.ts
│ │ │ ├── validation.spec.ts
│ │ │ └── validation.ts
│ │ ├── tsconfig.json
│ │ └── tsup.config.ts
│ ├── react
│ │ ├── build.config.ts
│ │ ├── CHANGELOG.md
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── src
│ │ │ ├── client
│ │ │ │ ├── attribute-component.spec.tsx
│ │ │ │ ├── attribute-component.tsx
│ │ │ │ ├── component.lingo-component.spec.tsx
│ │ │ │ ├── component.spec.tsx
│ │ │ │ ├── component.tsx
│ │ │ │ ├── context.spec.tsx
│ │ │ │ ├── context.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── loader.spec.ts
│ │ │ │ ├── loader.ts
│ │ │ │ ├── locale-switcher.spec.tsx
│ │ │ │ ├── locale-switcher.tsx
│ │ │ │ ├── locale.spec.ts
│ │ │ │ ├── locale.ts
│ │ │ │ ├── provider.spec.tsx
│ │ │ │ ├── provider.tsx
│ │ │ │ ├── utils.spec.ts
│ │ │ │ └── utils.ts
│ │ │ ├── core
│ │ │ │ ├── attribute-component.spec.tsx
│ │ │ │ ├── attribute-component.tsx
│ │ │ │ ├── component.spec.tsx
│ │ │ │ ├── component.tsx
│ │ │ │ ├── const.ts
│ │ │ │ ├── get-dictionary.spec.ts
│ │ │ │ ├── get-dictionary.ts
│ │ │ │ └── index.ts
│ │ │ ├── react-router
│ │ │ │ ├── index.ts
│ │ │ │ ├── loader.spec.ts
│ │ │ │ └── loader.ts
│ │ │ ├── rsc
│ │ │ │ ├── attribute-component.tsx
│ │ │ │ ├── component.lingo-component.spec.tsx
│ │ │ │ ├── component.spec.tsx
│ │ │ │ ├── component.tsx
│ │ │ │ ├── index.ts
│ │ │ │ ├── loader.spec.ts
│ │ │ │ ├── loader.ts
│ │ │ │ ├── provider.spec.tsx
│ │ │ │ ├── provider.tsx
│ │ │ │ ├── utils.spec.ts
│ │ │ │ └── utils.ts
│ │ │ └── test
│ │ │ └── setup.ts
│ │ ├── tsconfig.json
│ │ └── vitest.config.ts
│ ├── sdk
│ │ ├── CHANGELOG.md
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── src
│ │ │ ├── abort-controller.specs.ts
│ │ │ ├── index.spec.ts
│ │ │ └── index.ts
│ │ ├── tsconfig.json
│ │ ├── tsconfig.test.json
│ │ └── tsup.config.ts
│ └── spec
│ ├── CHANGELOG.md
│ ├── package.json
│ ├── README.md
│ ├── src
│ │ ├── config.spec.ts
│ │ ├── config.ts
│ │ ├── formats.ts
│ │ ├── index.spec.ts
│ │ ├── index.ts
│ │ ├── json-schema.ts
│ │ ├── locales.spec.ts
│ │ └── locales.ts
│ ├── tsconfig.json
│ ├── tsconfig.test.json
│ └── tsup.config.ts
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
├── readme
│ ├── ar.md
│ ├── bn.md
│ ├── de.md
│ ├── en.md
│ ├── es.md
│ ├── fa.md
│ ├── fr.md
│ ├── he.md
│ ├── hi.md
│ ├── it.md
│ ├── ja.md
│ ├── ko.md
│ ├── pl.md
│ ├── pt-BR.md
│ ├── ru.md
│ ├── tr.md
│ ├── uk-UA.md
│ └── zh-Hans.md
├── readme.md
├── scripts
│ ├── docs
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── src
│ │ │ ├── generate-cli-docs.ts
│ │ │ ├── generate-config-docs.ts
│ │ │ ├── json-schema
│ │ │ │ ├── markdown-renderer.test.ts
│ │ │ │ ├── markdown-renderer.ts
│ │ │ │ ├── parser.test.ts
│ │ │ │ ├── parser.ts
│ │ │ │ └── types.ts
│ │ │ ├── utils.test.ts
│ │ │ └── utils.ts
│ │ ├── tsconfig.json
│ │ └── vitest.config.ts
│ └── packagist-publish.php
└── turbo.json
```
# Files
--------------------------------------------------------------------------------
/demo/adonisjs/app/middleware/container_bindings_middleware.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { Logger } from '@adonisjs/core/logger'
2 | import { HttpContext } from '@adonisjs/core/http'
3 | import { NextFn } from '@adonisjs/core/types/http'
4 |
5 | /**
6 | * The container bindings middleware binds classes to their request
7 | * specific value using the container resolver.
8 | *
9 | * - We bind "HttpContext" class to the "ctx" object
10 | * - And bind "Logger" class to the "ctx.logger" object
11 | */
12 | export default class ContainerBindingsMiddleware {
13 | handle(ctx: HttpContext, next: NextFn) {
14 | ctx.containerResolver.bindValue(HttpContext, ctx)
15 | ctx.containerResolver.bindValue(Logger, ctx.logger)
16 |
17 | return next()
18 | }
19 | }
20 |
```
--------------------------------------------------------------------------------
/demo/next-app/package.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "name": "next-app",
3 | "version": "0.2.79",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "NODE_ENV=production next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "lingo.dev": "workspace:*",
13 | "react": "^19.0.0",
14 | "react-dom": "^19.0.0",
15 | "next": "15.3.1"
16 | },
17 | "devDependencies": {
18 | "typescript": "^5",
19 | "@types/node": "^20",
20 | "@types/react": "^19",
21 | "@types/react-dom": "^19",
22 | "@tailwindcss/postcss": "^4",
23 | "tailwindcss": "^4",
24 | "eslint": "^9",
25 | "eslint-config-next": "15.3.1",
26 | "@eslint/eslintrc": "^3"
27 | }
28 | }
29 |
```
--------------------------------------------------------------------------------
/demo/adonisjs/config/vite.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { defineConfig } from '@adonisjs/vite'
2 |
3 | const viteBackendConfig = defineConfig({
4 | /**
5 | * The output of vite will be written inside this
6 | * directory. The path should be relative from
7 | * the application root.
8 | */
9 | buildDirectory: 'public/assets',
10 |
11 | /**
12 | * The path to the manifest file generated by the
13 | * "vite build" command.
14 | */
15 | manifestFile: 'public/assets/.vite/manifest.json',
16 |
17 | /**
18 | * Feel free to change the value of the "assetsUrl" to
19 | * point to a CDN in production.
20 | */
21 | assetsUrl: '/assets',
22 |
23 | scriptAttributes: {
24 | defer: true,
25 | },
26 | })
27 |
28 | export default viteBackendConfig
29 |
```
--------------------------------------------------------------------------------
/legacy/sdk/package.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "name": "@replexica/sdk",
3 | "version": "0.7.11",
4 | "description": "[DEPRECATED] Replexica SDK (now Lingo.dev) - Please use lingo.dev instead",
5 | "private": false,
6 | "type": "module",
7 | "sideEffects": false,
8 | "types": "index.d.ts",
9 | "module": "index.js",
10 | "main": "index.js",
11 | "files": [
12 | "index.js",
13 | "index.d.ts"
14 | ],
15 | "keywords": [],
16 | "author": "",
17 | "license": "Apache-2.0",
18 | "dependencies": {
19 | "lingo.dev": "*"
20 | },
21 | "deprecated": "Replexica is now Lingo.dev! Please use our new SDK package by running: npm install lingo.dev. Visit https://lingo.dev for the latest features and documentation."
22 | }
23 |
```
--------------------------------------------------------------------------------
/packages/cli/src/cli/utils/fs.ts:
--------------------------------------------------------------------------------
```typescript
1 | import * as fs from "fs";
2 | import * as path from "path";
3 |
4 | export function tryReadFile(
5 | filePath: string,
6 | defaultValue: string | null = null,
7 | ): string | null {
8 | try {
9 | const content = fs.readFileSync(filePath, "utf-8");
10 | return content;
11 | } catch (error) {
12 | return defaultValue;
13 | }
14 | }
15 |
16 | export function writeFile(filePath: string, content: string) {
17 | // create dirs
18 | const dir = path.dirname(filePath);
19 | if (!fs.existsSync(dir)) {
20 | fs.mkdirSync(dir, { recursive: true });
21 | }
22 | fs.writeFileSync(filePath, content);
23 | }
24 |
25 | export function checkIfFileExists(filePath: string) {
26 | return fs.existsSync(filePath);
27 | }
28 |
```
--------------------------------------------------------------------------------
/demo/vite-project/tsconfig.node.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "compilerOptions": {
3 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
4 | "target": "ES2022",
5 | "lib": ["ES2023"],
6 | "module": "ESNext",
7 | "skipLibCheck": true,
8 |
9 | /* Bundler mode */
10 | "moduleResolution": "bundler",
11 | "allowImportingTsExtensions": true,
12 | "verbatimModuleSyntax": true,
13 | "moduleDetection": "force",
14 | "noEmit": true,
15 |
16 | /* Linting */
17 | "strict": true,
18 | "noUnusedLocals": true,
19 | "noUnusedParameters": true,
20 | "erasableSyntaxOnly": true,
21 | "noFallthroughCasesInSwitch": true,
22 | "noUncheckedSideEffectImports": true
23 | },
24 | "include": ["vite.config.ts"]
25 | }
26 |
```
--------------------------------------------------------------------------------
/demo/adonisjs/inertia/app/ssr.tsx:
--------------------------------------------------------------------------------
```typescript
1 | import ReactDOMServer from 'react-dom/server'
2 | import { createInertiaApp } from '@inertiajs/react'
3 | import { LingoProviderWrapper, loadDictionary } from 'lingo.dev/react/client'
4 |
5 | export default function render(page: any) {
6 | return createInertiaApp({
7 | page,
8 | render: ReactDOMServer.renderToString,
9 | resolve: (name) => {
10 | const pages = import.meta.glob('../pages/**/*.tsx', { eager: true })
11 | return pages[`../pages/${name}.tsx`]
12 | },
13 | setup: ({ App, props }) => (
14 | <LingoProviderWrapper loadDictionary={(locale) => loadDictionary(locale)}>
15 | <App {...props} />
16 | </LingoProviderWrapper>
17 | ),
18 | })
19 | }
20 |
```
--------------------------------------------------------------------------------
/packages/compiler/src/_utils.ts:
--------------------------------------------------------------------------------
```typescript
1 | import path from "path";
2 |
3 | import { LCP_DICTIONARY_FILE_NAME } from "./_const";
4 |
5 | export type GetDictionaryPathParams = {
6 | sourceRoot: string;
7 | lingoDir: string;
8 | relativeFilePath: string;
9 | };
10 | export const getDictionaryPath = (params: GetDictionaryPathParams) => {
11 | const toFile = path.resolve(
12 | params.sourceRoot,
13 | params.lingoDir,
14 | LCP_DICTIONARY_FILE_NAME,
15 | );
16 | const fromDir = path.dirname(
17 | path.resolve(params.sourceRoot, params.relativeFilePath),
18 | );
19 | const relativePath = path.relative(fromDir, toFile);
20 | const normalizedPath = relativePath.split(path.sep).join(path.posix.sep);
21 | return `./${normalizedPath}`;
22 | };
23 |
```
--------------------------------------------------------------------------------
/packages/cli/src/cli/loaders/mdx2/localizable-document.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { ILoader } from "../_types";
2 | import { createLoader } from "../_utils";
3 | import { LocalizableMdxDocument, SectionedMdx } from "./_types";
4 |
5 | export default function createLocalizableMdxDocumentLoader(): ILoader<
6 | SectionedMdx,
7 | LocalizableMdxDocument
8 | > {
9 | return createLoader({
10 | async pull(_locale, input) {
11 | return {
12 | meta: input.frontmatter,
13 | content: input.sections,
14 | };
15 | },
16 |
17 | async push(_locale, data, originalInput, _originalLocale, pullInput) {
18 | const result: SectionedMdx = {
19 | frontmatter: data.meta || {},
20 | sections: data.content || {},
21 | };
22 |
23 | return result;
24 | },
25 | });
26 | }
27 |
```
--------------------------------------------------------------------------------
/packages/cli/src/cli/loaders/json.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { jsonrepair } from "jsonrepair";
2 | import { ILoader } from "./_types";
3 | import { createLoader } from "./_utils";
4 |
5 | export default function createJsonLoader(): ILoader<
6 | string,
7 | Record<string, any>
8 | > {
9 | return createLoader({
10 | pull: async (locale, input) => {
11 | const jsonString = input || "{}";
12 | let result: Record<string, any>;
13 | try {
14 | result = JSON.parse(jsonString);
15 | } catch (error) {
16 | result = JSON.parse(jsonrepair(jsonString));
17 | }
18 | return result;
19 | },
20 | push: async (locale, data) => {
21 | const serializedData = JSON.stringify(data, null, 2);
22 | return serializedData;
23 | },
24 | });
25 | }
26 |
```
--------------------------------------------------------------------------------
/packages/react/src/client/attribute-component.tsx:
--------------------------------------------------------------------------------
```typescript
1 | "use client";
2 |
3 | import {
4 | LingoAttributeComponent as LingoCoreAttributeComponent,
5 | LingoAttributeComponentProps as LingoCoreAttributeComponentProps,
6 | } from "../core";
7 | import { useLingo } from "./context";
8 |
9 | export type LingoAttributeComponentProps = Omit<
10 | LingoCoreAttributeComponentProps,
11 | "$dictionary"
12 | >;
13 |
14 | export function LingoAttributeComponent(props: LingoAttributeComponentProps) {
15 | const { $attrAs, $attributes, $fileKey, ...rest } = props;
16 | const lingo = useLingo();
17 | return (
18 | <LingoCoreAttributeComponent
19 | $dictionary={lingo.dictionary}
20 | $as={$attrAs}
21 | $attributes={$attributes}
22 | $fileKey={$fileKey}
23 | {...rest}
24 | />
25 | );
26 | }
27 |
```
--------------------------------------------------------------------------------
/demo/next-app/src/components/hero-actions.tsx:
--------------------------------------------------------------------------------
```typescript
1 | "use client";
2 |
3 | export function HeroActions() {
4 | return (
5 | <div className="flex flex-row gap-4 justify-center items-center">
6 | <button className="px-8 py-3 bg-indigo-600 hover:bg-indigo-700 text-white font-medium rounded-lg transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
7 | Get Started
8 | </button>
9 |
10 | <button className="px-8 py-3 bg-white hover:bg-gray-50 text-gray-700 font-medium rounded-lg border border-gray-300 hover:border-gray-400 transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2">
11 | Learn More
12 | </button>
13 | </div>
14 | );
15 | }
16 |
```
--------------------------------------------------------------------------------
/demo/next-app/tsconfig.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "compilerOptions": {
3 | "target": "ES2017",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "noEmit": true,
9 | "esModuleInterop": true,
10 | "module": "esnext",
11 | "moduleResolution": "bundler",
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "jsx": "preserve",
15 | "incremental": true,
16 | "plugins": [
17 | {
18 | "name": "next"
19 | }
20 | ],
21 | "paths": {
22 | "@/*": ["./src/*"]
23 | }
24 | },
25 | "include": [
26 | "next-env.d.ts",
27 | "**/*.ts",
28 | "**/*.tsx",
29 | ".next/types/**/*.ts",
30 | "../react-router-app/app/welcome/lingo-dot-dev.tsx"
31 | ],
32 | "exclude": ["node_modules"]
33 | }
34 |
```
--------------------------------------------------------------------------------
/packages/react/src/core/attribute-component.tsx:
--------------------------------------------------------------------------------
```typescript
1 | import { createElement } from "react";
2 | import _ from "lodash";
3 | import React from "react";
4 |
5 | export type LingoAttributeComponentProps = {
6 | $as: string;
7 | $attributes: Record<string, string>;
8 | $fileKey: string;
9 | [key: string]: any;
10 | };
11 |
12 | export const LingoAttributeComponent = React.forwardRef(
13 | (props: Omit<LingoAttributeComponentProps, "ref">, ref: React.Ref<any>) => {
14 | const { $as, $attributes, $fileKey, $dictionary, ...rest } = props;
15 |
16 | const localizedAttributes = _.mapValues($attributes, (v, k) => {
17 | return $dictionary?.files?.[$fileKey]?.entries?.[v] ?? k;
18 | });
19 |
20 | return createElement($as, {
21 | ...rest,
22 | ...localizedAttributes,
23 | ref,
24 | });
25 | },
26 | );
27 |
```
--------------------------------------------------------------------------------
/packages/cli/demo/xml/es/example.xml:
--------------------------------------------------------------------------------
```
1 | <root><title>¡Hola, mundo!</title><description><summary>Aplicación de demostración simple</summary><details>Contenido de ejemplo básico</details></description><image src="photo.jpg" alt="Foto de ejemplo" title="Haz clic para ver"/><link href="example.com" label="Haz clic aquí">Más información</link><button type="submit" value="Enviar" placeholder="Introduce texto">Enviar</button><section><article><paragraph><sentence>Contenido anidado</sentence></paragraph></article></section><meta name="description" content="Metadatos de la aplicación de demostración"/><message><greeting>¡Hola!</greeting><body>Bienvenido a la aplicación</body><signature>Gracias, Equipo de MyApp</signature></message></root>
```
--------------------------------------------------------------------------------
/demo/vite-project/tsconfig.app.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "compilerOptions": {
3 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
4 | "target": "ES2020",
5 | "useDefineForClassFields": true,
6 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
7 | "module": "ESNext",
8 | "skipLibCheck": true,
9 |
10 | /* Bundler mode */
11 | "moduleResolution": "bundler",
12 | "allowImportingTsExtensions": true,
13 | "verbatimModuleSyntax": true,
14 | "moduleDetection": "force",
15 | "noEmit": true,
16 | "jsx": "react-jsx",
17 |
18 | /* Linting */
19 | "strict": true,
20 | "noUnusedLocals": true,
21 | "noUnusedParameters": true,
22 | "erasableSyntaxOnly": true,
23 | "noFallthroughCasesInSwitch": true,
24 | "noUncheckedSideEffectImports": true
25 | },
26 | "include": ["src"]
27 | }
28 |
```
--------------------------------------------------------------------------------
/packages/cli/demo/json/es/example.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "title": "¡Hola, mundo!",
3 | "description": "Una aplicación de demostración simple",
4 | "version": "1.0.0",
5 | "support_email": "[email protected]",
6 | "homepage": "https://lingo.dev",
7 | "deprecated": null,
8 | "empty": "",
9 | "emoji": "🚀",
10 | "author": {
11 | "name": "Juan Pérez"
12 | },
13 | "contributors": [
14 | {
15 | "name": "Alicia"
16 | },
17 | {
18 | "name": "Roberto"
19 | }
20 | ],
21 | "messages": ["Bienvenido a Mi aplicación", "¡Hola, mundo!"],
22 | "config": {
23 | "theme": {
24 | "primary": "Tema Azul"
25 | }
26 | },
27 | "mixed_array": [
28 | "Contenido mixto aquí",
29 | 42,
30 | true,
31 | {
32 | "nested_message": "Texto anidado"
33 | }
34 | ],
35 | "locked_key_1": "This value is locked and should not be changed"
36 | }
```
--------------------------------------------------------------------------------
/packages/cli/src/cli/loaders/_types.ts:
--------------------------------------------------------------------------------
```typescript
1 | export interface ILoaderDefinition<I, O, C> {
2 | init?(): Promise<C>;
3 | pull(
4 | locale: string,
5 | input: I,
6 | initCtx: C,
7 | originalLocale: string,
8 | originalInput: I,
9 | ): Promise<O>;
10 | push(
11 | locale: string,
12 | data: O,
13 | originalInput: I | null,
14 | originalLocale: string,
15 | pullInput: I | null,
16 | pullOutput: O | null,
17 | ): Promise<I>;
18 | pullHints?(originalInput: I): Promise<O | undefined>;
19 | }
20 |
21 | export interface ILoader<I, O, C = void> extends ILoaderDefinition<I, O, C> {
22 | setDefaultLocale(locale: string): this;
23 | init(): Promise<C>;
24 | pull(locale: string, input: I): Promise<O>;
25 | push(locale: string, data: O): Promise<I>;
26 | pullHints(originalInput: I): Promise<O | undefined>;
27 | }
28 |
```
--------------------------------------------------------------------------------
/packages/cli/src/cli/loaders/locked-keys.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { ILoader } from "./_types";
2 | import { createLoader } from "./_utils";
3 | import _ from "lodash";
4 | import { matchesKeyPattern } from "../utils/key-matching";
5 |
6 | export default function createLockedKeysLoader(
7 | lockedKeys: string[],
8 | ): ILoader<Record<string, any>, Record<string, any>> {
9 | return createLoader({
10 | pull: async (locale, data) => {
11 | return _.pickBy(
12 | data,
13 | (value, key) => !matchesKeyPattern(key, lockedKeys),
14 | );
15 | },
16 | push: async (locale, data, originalInput) => {
17 | const lockedSubObject = _.chain(originalInput)
18 | .pickBy((value, key) => matchesKeyPattern(key, lockedKeys))
19 | .value();
20 |
21 | return _.merge({}, data, lockedSubObject);
22 | },
23 | });
24 | }
25 |
```
--------------------------------------------------------------------------------
/demo/adonisjs/config/inertia.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { defineConfig } from '@adonisjs/inertia'
2 | import type { InferSharedProps } from '@adonisjs/inertia/types'
3 |
4 | const inertiaConfig = defineConfig({
5 | /**
6 | * Path to the Edge view that will be used as the root view for Inertia responses
7 | */
8 | rootView: 'inertia_layout',
9 |
10 | /**
11 | * Data that should be shared with all rendered pages
12 | */
13 | sharedData: {
14 | // user: (ctx) => ctx.inertia.always(() => ctx.auth.user),
15 | },
16 |
17 | /**
18 | * Options for the server-side rendering
19 | */
20 | ssr: {
21 | enabled: true,
22 | entrypoint: 'inertia/app/ssr.tsx',
23 | },
24 | })
25 |
26 | export default inertiaConfig
27 |
28 | declare module '@adonisjs/inertia/types' {
29 | export interface SharedProps extends InferSharedProps<typeof inertiaConfig> {}
30 | }
31 |
```
--------------------------------------------------------------------------------
/packages/locales/package.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "name": "@lingo.dev/_locales",
3 | "version": "0.1.0",
4 | "description": "Lingo.dev locales",
5 | "private": false,
6 | "publishConfig": {
7 | "access": "public"
8 | },
9 | "type": "module",
10 | "sideEffects": false,
11 | "main": "build/index.cjs",
12 | "module": "build/index.mjs",
13 | "types": "build/index.d.ts",
14 | "files": [
15 | "build",
16 | "localenames-data"
17 | ],
18 | "scripts": {
19 | "dev": "tsup --watch",
20 | "build": "pnpm typecheck && tsup",
21 | "typecheck": "tsc --noEmit",
22 | "test": "vitest run",
23 | "test:watch": "vitest"
24 | },
25 | "keywords": [],
26 | "author": "",
27 | "license": "Apache-2.0",
28 | "devDependencies": {
29 | "@types/node": "^22.13.5",
30 | "tsup": "^8.3.5",
31 | "typescript": "^5.8.3",
32 | "vitest": "^3.2.4"
33 | }
34 | }
35 |
```
--------------------------------------------------------------------------------
/packages/cli/troubleshooting.md:
--------------------------------------------------------------------------------
```markdown
1 | ## Troubleshooting
2 |
3 | ### Error: Dynamic require of "module_name" is not supported
4 |
5 | If you encounter this error when running the cli, it typically means that a dependency is trying to dynamically `require()` a Node.js built-in module or another dependency in a way that's incompatible with the ESM bundle format (`.mjs`).
6 |
7 | The solution is to identify the problematic dependency mentioned in the error stack trace and exclude it from the bundle by adding it to the `external` array in `tsup.config.ts`.
8 |
9 | For example, we encountered this issue with the `debug` package (a dependency of `@babel/traverse`) trying to dynamically require `"tty"`. We resolved it by adding `@babel/*` to the `external` array in `tsup.config.ts`.
10 |
```
--------------------------------------------------------------------------------
/packages/cli/src/cli/loaders/sync.ts:
--------------------------------------------------------------------------------
```typescript
1 | import _ from "lodash";
2 |
3 | import { ILoader } from "./_types";
4 | import { createLoader } from "./_utils";
5 |
6 | export default function createSyncLoader(): ILoader<
7 | Record<string, string>,
8 | Record<string, string>
9 | > {
10 | return createLoader({
11 | async pull(locale, input, initCtx, originalLocale, originalInput) {
12 | if (!originalInput) {
13 | return input;
14 | }
15 |
16 | return _.chain(originalInput)
17 | .mapValues((value, key) => input[key])
18 | .value() as Record<string, string>;
19 | },
20 | async push(locale, data, originalInput) {
21 | if (!originalInput) {
22 | return data;
23 | }
24 |
25 | return _.chain(originalInput || {})
26 | .mapValues((value, key) => data[key])
27 | .value();
28 | },
29 | });
30 | }
31 |
```
--------------------------------------------------------------------------------
/legacy/cli/package.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "name": "replexica",
3 | "version": "0.71.0",
4 | "description": "[DEPRECATED] Replexica CLI (now Lingo.dev) - Please use lingo.dev instead",
5 | "private": false,
6 | "type": "module",
7 | "sideEffects": false,
8 | "types": "build/index.d.ts",
9 | "module": "build/index.mjs",
10 | "main": "build/index.cjs",
11 | "bin": {
12 | "replexica": "./bin/cli.mjs"
13 | },
14 | "files": [
15 | "bin",
16 | "build"
17 | ],
18 | "scripts": {
19 | "replexica": "./bin/cli.mjs"
20 | },
21 | "keywords": [],
22 | "author": "",
23 | "license": "Apache-2.0",
24 | "dependencies": {
25 | "lingo.dev": "*"
26 | },
27 | "deprecated": "Replexica is now Lingo.dev! Please use our new CLI package by running: npx lingo.dev@latest. Visit https://lingo.dev for the latest features and documentation."
28 | }
29 |
```
--------------------------------------------------------------------------------
/packages/cli/demo/json5/es/example.json5:
--------------------------------------------------------------------------------
```
1 | {
2 | title: '¡Hola, mundo!',
3 | description: 'Una aplicación de demostración simple con características JSON5',
4 | version: '1.0.0',
5 | support_email: '[email protected]',
6 | homepage: 'https://lingo.dev',
7 | deprecated: null,
8 | empty: '',
9 | emoji: '🚀',
10 | author: {
11 | name: 'Juan Pérez',
12 | },
13 | contributors: [
14 | {
15 | name: 'Alicia',
16 | },
17 | {
18 | name: 'Roberto',
19 | },
20 | ],
21 | messages: [
22 | 'Bienvenido a MiApp',
23 | '¡Hola, mundo!',
24 | ],
25 | config: {
26 | theme: {
27 | primary: 'Tema azul',
28 | },
29 | },
30 | mixed_array: [
31 | 'Contenido mixto aquí',
32 | 42,
33 | true,
34 | {
35 | nested_message: 'Texto anidado',
36 | },
37 | ],
38 | hex_value: 3735928559,
39 | locked_key_1: 'This value is locked and should not be changed',
40 | }
```
--------------------------------------------------------------------------------
/packages/cli/tsup.config.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { defineConfig } from "tsup";
2 |
3 | export default defineConfig({
4 | clean: true,
5 | entry: {
6 | cli: "src/cli/index.ts",
7 | sdk: "src/sdk/index.ts",
8 | spec: "src/spec/index.ts",
9 | react: "src/react/index.ts",
10 | "react/client": "src/react/client.ts",
11 | "react/rsc": "src/react/rsc.ts",
12 | "react/react-router": "src/react/react-router.ts",
13 | compiler: "src/compiler/index.ts",
14 | "locale-codes": "src/locale-codes/index.ts",
15 | },
16 | outDir: "build",
17 | format: ["cjs", "esm"],
18 | dts: true,
19 | cjsInterop: true,
20 | splitting: true,
21 | bundle: true,
22 | sourcemap: true,
23 | external: ["readline/promises", "@babel/traverse", "node-machine-id"],
24 | outExtension: (ctx) => ({
25 | js: ctx.format === "cjs" ? ".cjs" : ".mjs",
26 | }),
27 | });
28 |
```
--------------------------------------------------------------------------------
/demo/vite-project/eslint.config.js:
--------------------------------------------------------------------------------
```javascript
1 | import js from "@eslint/js";
2 | import globals from "globals";
3 | import reactHooks from "eslint-plugin-react-hooks";
4 | import reactRefresh from "eslint-plugin-react-refresh";
5 | import tseslint from "typescript-eslint";
6 |
7 | export default tseslint.config(
8 | { ignores: ["dist"] },
9 | {
10 | extends: [js.configs.recommended, ...tseslint.configs.recommended],
11 | files: ["**/*.{ts,tsx}"],
12 | languageOptions: {
13 | ecmaVersion: 2020,
14 | globals: globals.browser,
15 | },
16 | plugins: {
17 | "react-hooks": reactHooks,
18 | "react-refresh": reactRefresh,
19 | },
20 | rules: {
21 | ...reactHooks.configs.recommended.rules,
22 | "react-refresh/only-export-components": [
23 | "warn",
24 | { allowConstantExport: true },
25 | ],
26 | },
27 | },
28 | );
29 |
```
--------------------------------------------------------------------------------
/packages/cli/demo/json/en/example.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "title": "Hello, world!",
3 | "description": "A simple demo app",
4 | "version": "1.0.0",
5 | "support_email": "[email protected]",
6 | "homepage": "https://lingo.dev",
7 | "deprecated": null,
8 | "empty": "",
9 | "emoji": "🚀",
10 | "author": {
11 | "name": "John Doe"
12 | },
13 | "contributors": [
14 | { "name": "Alice" },
15 | { "name": "Bob" }
16 | ],
17 | "messages": [
18 | "Welcome to MyApp",
19 | "Hello, world!"
20 | ],
21 | "config": {
22 | "theme": {
23 | "primary": "Blue theme"
24 | }
25 | },
26 | "mixed_array": [
27 | "Mixed content here",
28 | 42,
29 | true,
30 | {
31 | "nested_message": "Nested text"
32 | }
33 | ],
34 | "locked_key_1": "This value is locked and should not be changed",
35 | "ignored_key_1": "This value is ignored and should not appear in target locales"
36 |
37 | }
```
--------------------------------------------------------------------------------
/packages/cli/src/cli/loaders/txt.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { ILoader } from "./_types";
2 | import { createLoader } from "./_utils";
3 |
4 | export default function createTxtLoader(): ILoader<
5 | string,
6 | Record<string, string>
7 | > {
8 | return createLoader({
9 | async pull(locale, input) {
10 | const result: Record<string, string> = {};
11 |
12 | if (input !== undefined && input !== null && input !== "") {
13 | const lines = input.split("\n");
14 | lines.forEach((line, index) => {
15 | result[String(index + 1)] = line;
16 | });
17 | }
18 |
19 | return result;
20 | },
21 |
22 | async push(locale, payload) {
23 | const sortedEntries = Object.entries(payload).sort(
24 | ([a], [b]) => parseInt(a) - parseInt(b),
25 | );
26 | return sortedEntries.map(([_, value]) => value).join("\n");
27 | },
28 | });
29 | }
30 |
```
--------------------------------------------------------------------------------
/demo/next-app/src/app/test/page.tsx:
--------------------------------------------------------------------------------
```typescript
1 | import Link from "next/link";
2 |
3 | export default function Test() {
4 | return (
5 | <div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
6 | <main className="flex flex-col gap-[32px] row-start-2 items-center sm:items-start">
7 | <Link href="/" className="text-primary underline">
8 | Navigate home
9 | </Link>
10 | <div className="flex flex-col justify-center items-center gap-4 w-100">
11 | <h1 className="text-4xl">Testing this thing now</h1>
12 | <p>
13 | Hello, this is a non-interactive test. Please do not try to interact
14 | with it.
15 | </p>
16 | </div>
17 | </main>
18 | </div>
19 | );
20 | }
21 |
```
--------------------------------------------------------------------------------
/packages/cli/demo/php/en/example.php:
--------------------------------------------------------------------------------
```php
1 | <?php
2 |
3 | return [
4 | 'Hello, world!',
5 | 'Welcome to MyApp',
6 |
7 | "It's \"simple\" with a backslash \\ and newline\nAll text here",
8 |
9 | 'welcome_message' => 'Welcome!',
10 | 'error_text' => 'Something went wrong',
11 |
12 | 'navigation' => [
13 | 'home' => 'Home',
14 | 'about' => 'About',
15 | 'contact' => 'Contact',
16 | ],
17 |
18 | 'forms' => [
19 | 'login' => [
20 | 'username_label' => 'Username',
21 | 'password_label' => 'Password',
22 | 'submit_button' => 'Sign In',
23 | ],
24 | ],
25 |
26 | 'mixed_content' => [
27 | 'title' => 'Settings',
28 | 'count' => 42,
29 | 'enabled' => true,
30 | 'nothing_here' => null,
31 | 'description' => 'App settings and preferences',
32 | ],
33 | ];
34 |
```
--------------------------------------------------------------------------------
/demo/vite-project/package.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "name": "vite-project",
3 | "private": true,
4 | "version": "0.0.3",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "tsc -b && vite build",
9 | "lint": "eslint .",
10 | "preview": "vite preview"
11 | },
12 | "dependencies": {
13 | "@vitejs/plugin-react": "^4.4.1",
14 | "react": "^19.1.0",
15 | "react-dom": "^19.1.0"
16 | },
17 | "devDependencies": {
18 | "@eslint/js": "^9.25.0",
19 | "@types/react": "^19.1.2",
20 | "@types/react-dom": "^19.1.2",
21 | "@vitejs/plugin-react-swc": "^3.9.0",
22 | "eslint": "^9.25.0",
23 | "eslint-plugin-react-hooks": "^5.2.0",
24 | "eslint-plugin-react-refresh": "^0.4.19",
25 | "globals": "^16.0.0",
26 | "lingo.dev": "workspace:*",
27 | "typescript": "~5.8.3",
28 | "typescript-eslint": "^8.30.1",
29 | "vite": "^6.3.5"
30 | }
31 | }
32 |
```
--------------------------------------------------------------------------------
/scripts/docs/src/json-schema/types.ts:
--------------------------------------------------------------------------------
```typescript
1 | export type PropertyInfo = {
2 | name: string;
3 | fullPath: string;
4 | type: string;
5 | required: boolean;
6 | description?: string;
7 | defaultValue?: unknown;
8 | allowedValues?: unknown[];
9 | allowedKeys?: string[];
10 | children?: PropertyInfo[];
11 | };
12 |
13 | export type JSONSchemaObject = {
14 | type?: string | string[];
15 | properties?: Record<string, unknown>;
16 | required?: string[];
17 | items?: unknown;
18 | anyOf?: unknown[];
19 | $ref?: string;
20 | description?: string;
21 | markdownDescription?: string;
22 | default?: unknown;
23 | enum?: unknown[];
24 | propertyNames?: {
25 | enum?: string[];
26 | };
27 | additionalProperties?: unknown;
28 | definitions?: Record<string, unknown>;
29 | };
30 |
31 | export type SchemaParsingOptions = {
32 | customOrder?: string[];
33 | parentPath?: string;
34 | rootSchema?: unknown;
35 | };
36 |
```
--------------------------------------------------------------------------------
/demo/react-router-app/Dockerfile:
--------------------------------------------------------------------------------
```dockerfile
1 | FROM node:20-alpine AS development-dependencies-env
2 | COPY . /app
3 | WORKDIR /app
4 | RUN npm ci
5 |
6 | FROM node:20-alpine AS production-dependencies-env
7 | COPY ./package.json package-lock.json /app/
8 | WORKDIR /app
9 | RUN npm ci --omit=dev
10 |
11 | FROM node:20-alpine AS build-env
12 | COPY . /app/
13 | COPY --from=development-dependencies-env /app/node_modules /app/node_modules
14 | WORKDIR /app
15 | RUN npm run build
16 |
17 | FROM node:20-alpine
18 | COPY ./package.json package-lock.json /app/
19 | COPY --from=production-dependencies-env /app/node_modules /app/node_modules
20 | COPY --from=build-env /app/build /app/build
21 | WORKDIR /app
22 |
23 | # Create non-root user, set ownership, switch to user before running the app
24 | RUN adduser -D nodeuser && \
25 | chown -R nodeuser:nodeuser /app
26 | USER nodeuser
27 |
28 | CMD ["npm", "run", "start"]
```
--------------------------------------------------------------------------------
/packages/cli/src/cli/loaders/ignored-keys.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { ILoader } from "./_types";
2 | import { createLoader } from "./_utils";
3 | import _ from "lodash";
4 | import { matchesKeyPattern } from "../utils/key-matching";
5 |
6 | export default function createIgnoredKeysLoader(
7 | ignoredKeys: string[],
8 | ): ILoader<Record<string, any>, Record<string, any>> {
9 | return createLoader({
10 | pull: async (locale, data) => {
11 | const result = _.omitBy(data, (value, key) =>
12 | matchesKeyPattern(key, ignoredKeys),
13 | );
14 | return result;
15 | },
16 | push: async (locale, data, originalInput, originalLocale, pullInput) => {
17 | // Remove ignored keys from the data being pushed
18 | const result = _.omitBy(data, (value, key) =>
19 | matchesKeyPattern(key, ignoredKeys),
20 | );
21 | return result;
22 | },
23 | });
24 | }
25 |
```
--------------------------------------------------------------------------------
/demo/next-app/src/lingo/meta.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "version": 0.1,
3 | "files": {
4 | "components/hero-subtitle.tsx": {
5 | "scopes": {
6 | "0/declaration/body/0/argument": {
7 | "type": "element",
8 | "hash": "75cc719ebad12413e5fa788174d4d4ec",
9 | "context": "",
10 | "skip": false,
11 | "overrides": {},
12 | "content": "Localize your React app in every language in minutes.<element:br></element:br> Scale to millions from day one<element:sup>*</element:sup>."
13 | },
14 | "0/declaration/body/0/argument/3-title": {
15 | "type": "attribute",
16 | "hash": "ce9b34049534ad201aef501f2a224a9d",
17 | "context": "",
18 | "skip": false,
19 | "overrides": {},
20 | "content": "supports many popular frameworks"
21 | }
22 | }
23 | }
24 | }
25 | }
26 |
```
--------------------------------------------------------------------------------
/scripts/docs/package.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "name": "docs",
3 | "private": true,
4 | "type": "module",
5 | "scripts": {
6 | "generate-cli-docs": "tsx ./src/generate-cli-docs.ts",
7 | "generate-config-docs": "tsx ./src/generate-config-docs.ts",
8 | "test": "vitest run",
9 | "test:watch": "vitest",
10 | "test:ui": "vitest --ui",
11 | "test:coverage": "vitest run --coverage"
12 | },
13 | "devDependencies": {
14 | "@lingo.dev/_spec": "workspace:*",
15 | "@octokit/rest": "^20.1.2",
16 | "@types/mdast": "^4.0.4",
17 | "@types/node": "^24.0.10",
18 | "@vitest/ui": "^3.2.4",
19 | "commander": "^12.0.0",
20 | "remark-stringify": "^11.0.0",
21 | "tsx": "^4.7.1",
22 | "typescript": "^5.8.3",
23 | "unified": "^11.0.4",
24 | "vitest": "^3.2.4"
25 | },
26 | "dependencies": {
27 | "zod": "^3.25.76",
28 | "zod-to-json-schema": "^3.24.5"
29 | }
30 | }
31 |
```
--------------------------------------------------------------------------------
/packages/react/src/rsc/component.spec.tsx:
--------------------------------------------------------------------------------
```typescript
1 | import { describe, it, expect, vi } from "vitest";
2 | import React from "react";
3 | import { renderToStaticMarkup } from "react-dom/server";
4 | import { LingoHtmlComponent } from "./component";
5 |
6 | vi.mock("./utils", () => {
7 | return {
8 | loadLocaleFromCookies: vi.fn(async () => "nl"),
9 | loadDictionaryFromRequest: vi.fn(),
10 | };
11 | });
12 |
13 | describe("rsc/component", () => {
14 | describe("LingoHtmlComponent", () => {
15 | it("sets lang and data-lingodotdev-compiler from cookies-derived locale", async () => {
16 | const element = await LingoHtmlComponent({});
17 | const markup = renderToStaticMarkup(element);
18 | expect(markup).toContain("<html");
19 | expect(markup).toContain('lang="nl"');
20 | expect(markup).toContain('data-lingodotdev-compiler="nl"');
21 | });
22 | });
23 | });
24 |
```
--------------------------------------------------------------------------------
/packages/cli/demo/json-dictionary/example.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "navigation": {
3 | "en": "Home",
4 | "es": "Inicio"
5 | },
6 | "buttons": {
7 | "submit": {
8 | "en": "Submit",
9 | "es": "Enviar"
10 | },
11 | "cancel": {
12 | "en": "Cancel",
13 | "es": "Cancelar"
14 | }
15 | },
16 | "messages": {
17 | "welcome": {
18 | "en": "Welcome to our application",
19 | "es": "Bienvenido a nuestra aplicación"
20 | },
21 | "error": {
22 | "en": "An error occurred",
23 | "es": "Ha ocurrido un error"
24 | }
25 | },
26 | "forms": {
27 | "login": {
28 | "title": {
29 | "en": "Login",
30 | "es": "Iniciar sesión"
31 | },
32 | "fields": {
33 | "username": {
34 | "en": "Username",
35 | "es": "Nombre de usuario"
36 | },
37 | "password": {
38 | "en": "Password",
39 | "es": "Contraseña"
40 | }
41 | }
42 | }
43 | }
44 | }
45 |
```
--------------------------------------------------------------------------------
/packages/cli/src/cli/loaders/json-sorting.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { ILoader } from "./_types";
2 | import { createLoader } from "./_utils";
3 |
4 | export default function createJsonSortingLoader(): ILoader<
5 | Record<string, any>,
6 | Record<string, any>
7 | > {
8 | return createLoader({
9 | async pull(locale, input) {
10 | return input;
11 | },
12 | async push(locale, data, originalInput) {
13 | return sortObjectDeep(data);
14 | },
15 | });
16 | }
17 |
18 | function sortObjectDeep(obj: any): any {
19 | if (Array.isArray(obj)) {
20 | return obj.map(sortObjectDeep);
21 | }
22 |
23 | if (obj !== null && typeof obj === "object") {
24 | return Object.keys(obj)
25 | .sort((a, b) => a.localeCompare(b, undefined, { numeric: true }))
26 | .reduce((result: any, key) => {
27 | result[key] = sortObjectDeep(obj[key]);
28 | return result;
29 | }, {});
30 | }
31 |
32 | return obj;
33 | }
34 |
```
--------------------------------------------------------------------------------
/packages/react/src/rsc/attribute-component.tsx:
--------------------------------------------------------------------------------
```typescript
1 | import {
2 | LingoAttributeComponent as LingoCoreAttributeComponent,
3 | LingoAttributeComponentProps as LingoCoreAttributeComponentProps,
4 | } from "../core";
5 | import { loadDictionaryFromRequest } from "./utils";
6 |
7 | export type LingoAttributeComponentProps = Omit<
8 | LingoCoreAttributeComponentProps,
9 | "$dictionary"
10 | >;
11 |
12 | export async function LingoAttributeComponent(
13 | props: LingoAttributeComponentProps,
14 | ) {
15 | const {
16 | $attrAs,
17 | $attributes,
18 | $fileKey,
19 | $entryKey,
20 | $loadDictionary,
21 | ...rest
22 | } = props;
23 | const dictionary = await loadDictionaryFromRequest($loadDictionary);
24 | return (
25 | <LingoCoreAttributeComponent
26 | $dictionary={dictionary}
27 | $as={$attrAs}
28 | $attributes={$attributes}
29 | $fileKey={$fileKey}
30 | {...rest}
31 | />
32 | );
33 | }
34 |
```
--------------------------------------------------------------------------------
/demo/react-router-app/app/routes/test.tsx:
--------------------------------------------------------------------------------
```typescript
1 | import type { Route } from "./+types/home";
2 | import { Link } from "react-router";
3 |
4 | export function meta({}: Route.MetaArgs) {
5 | return [
6 | { title: "New React Router App" },
7 | { name: "description", content: "Welcome to React Router!" },
8 | ];
9 | }
10 |
11 | export default function Test() {
12 | return (
13 | <main className="flex items-center justify-center pt-16 pb-4">
14 | <div className="flex-1 flex flex-col items-center gap-4 min-h-0">
15 | <Link to="/" className="text-blue-500 hover:underline">
16 | Go back home
17 | </Link>
18 | <h1 className="text-2xl font-bold">This is a test page</h1>
19 | <p>Welcome to non-interactive testing page.</p>
20 | <p>Please do not try to interact with this page for your own safety.</p>
21 | </div>
22 | </main>
23 | );
24 | }
25 |
```
--------------------------------------------------------------------------------
/packages/cli/demo/xml/en/example.xml:
--------------------------------------------------------------------------------
```
1 | <?xml version="1.0" encoding="UTF-8"?>
2 | <root>
3 | <title>Hello, world!</title>
4 |
5 | <description>
6 | <summary>Simple demo app</summary>
7 | <details>Basic example content</details>
8 | </description>
9 |
10 | <image src="photo.jpg" alt="Example photo" title="Click to view"/>
11 |
12 | <link href="example.com" label="Click here">Learn more</link>
13 |
14 | <button type="submit" value="Submit" placeholder="Enter text">Submit</button>
15 |
16 | <section>
17 | <article>
18 | <paragraph>
19 | <sentence>Nested content</sentence>
20 | </paragraph>
21 | </article>
22 | </section>
23 |
24 | <meta name="description" content="Demo app metadata"/>
25 |
26 | <message>
27 | <greeting>Hello there!</greeting>
28 | <body>Welcome to the app</body>
29 | <signature>Thanks, MyApp Team</signature>
30 | </message>
31 | </root>
```
--------------------------------------------------------------------------------
/packages/cli/src/cli/cmd/ci/flows/_base.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { Ora } from "ora";
2 | import { PlatformKit } from "../platforms/_base";
3 |
4 | export type IIntegrationFlowOptions = {
5 | parallel?: boolean;
6 | force?: boolean;
7 | };
8 |
9 | export interface IIntegrationFlow {
10 | preRun?(): Promise<boolean>;
11 | run(options: IIntegrationFlowOptions): Promise<boolean>;
12 | postRun?(): Promise<void>;
13 | }
14 |
15 | export abstract class IntegrationFlow implements IIntegrationFlow {
16 | protected i18nBranchName?: string;
17 |
18 | constructor(
19 | protected ora: Ora,
20 | protected platformKit: PlatformKit,
21 | ) {}
22 |
23 | abstract run(options: IIntegrationFlowOptions): Promise<boolean>;
24 | }
25 |
26 | export const gitConfig = {
27 | userName: "Lingo.dev",
28 | userEmail: "[email protected]",
29 | };
30 |
31 | export function escapeShellArg(arg: string): string {
32 | return `'${arg.replace(/'/g, "'\\''")}'`;
33 | }
34 |
```
--------------------------------------------------------------------------------
/packages/locales/src/types.ts:
--------------------------------------------------------------------------------
```typescript
1 | /**
2 | * Represents the components of a locale string
3 | */
4 | export interface LocaleComponents {
5 | /** The language code (e.g., "en", "zh", "es") */
6 | language: string;
7 | /** The script code (e.g., "Hans", "Hant", "Cyrl") - optional */
8 | script?: string;
9 | /** The region/country code (e.g., "US", "CN", "RS") - optional */
10 | region?: string;
11 | }
12 |
13 | /**
14 | * Locale delimiter types
15 | */
16 | export type LocaleDelimiter = "-" | "_";
17 |
18 | /**
19 | * Validation result for locale parsing
20 | */
21 | export interface ParseResult {
22 | /** The parsed locale components */
23 | components: LocaleComponents;
24 | /** The delimiter used in the original string */
25 | delimiter: LocaleDelimiter | null;
26 | /** Whether the locale string was valid */
27 | isValid: boolean;
28 | /** Error message if parsing failed */
29 | error?: string;
30 | }
31 |
```
--------------------------------------------------------------------------------
/packages/react/src/client/component.spec.tsx:
--------------------------------------------------------------------------------
```typescript
1 | import { describe, it, expect } from "vitest";
2 | import { renderToStaticMarkup } from "react-dom/server";
3 | import React from "react";
4 | import { LingoHtmlComponent } from "./component";
5 | import { LingoContext } from "./context";
6 |
7 | describe("client/component", () => {
8 | describe("LingoHtmlComponent", () => {
9 | it("sets lang and data-lingodotdev-compiler from context dictionary locale", () => {
10 | const dictionary = { locale: "ja" } as any;
11 | const markup = renderToStaticMarkup(
12 | <LingoContext.Provider value={{ dictionary }}>
13 | <LingoHtmlComponent />
14 | </LingoContext.Provider>,
15 | );
16 | expect(markup).toContain("<html");
17 | expect(markup).toContain('lang="ja"');
18 | expect(markup).toContain('data-lingodotdev-compiler="ja"');
19 | });
20 | });
21 | });
22 |
```
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": "Attach to CLI",
9 | "processId": "${command:PickProcess}",
10 | "request": "attach",
11 | "skipFiles": ["<node_internals>/**"],
12 | "type": "node",
13 | "sourceMaps": true,
14 | "outFiles": ["${workspaceFolder}/packages/cli/build/**/*.mjs"],
15 | "resolveSourceMapLocations": [
16 | "${workspaceFolder}/packages/cli/build/**/*.mjs",
17 | "!**/node_modules/**"
18 | ],
19 | "remoteRoot": "${workspaceFolder}/packages/cli",
20 | "localRoot": "${workspaceFolder}/packages/cli",
21 | "port": 9229
22 | }
23 | ]
24 | }
25 |
```
--------------------------------------------------------------------------------
/packages/spec/package.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "name": "@lingo.dev/_spec",
3 | "version": "0.41.1",
4 | "description": "Lingo.dev open specification",
5 | "private": false,
6 | "publishConfig": {
7 | "access": "public"
8 | },
9 | "type": "module",
10 | "sideEffects": false,
11 | "types": "build/index.d.ts",
12 | "module": "build/index.mjs",
13 | "main": "build/index.cjs",
14 | "files": [
15 | "build"
16 | ],
17 | "scripts": {
18 | "dev": "tsup --watch",
19 | "build": "pnpm typecheck && tsup",
20 | "typecheck": "tsc --noEmit",
21 | "test": "vitest run",
22 | "test:watch": "vitest"
23 | },
24 | "keywords": [],
25 | "author": "",
26 | "license": "Apache-2.0",
27 | "dependencies": {
28 | "zod": "^3.25.76",
29 | "zod-to-json-schema": "^3.24.5"
30 | },
31 | "devDependencies": {
32 | "@types/node": "^22.13.5",
33 | "tsup": "^8.3.5",
34 | "typescript": "^5.8.3",
35 | "vitest": "^3.1.2"
36 | }
37 | }
38 |
```
--------------------------------------------------------------------------------
/packages/cli/src/cli/loaders/flutter.ts:
--------------------------------------------------------------------------------
```typescript
1 | import _ from "lodash";
2 | import { ILoader } from "./_types";
3 | import { createLoader } from "./_utils";
4 |
5 | export default function createFlutterLoader(): ILoader<
6 | Record<string, any>,
7 | Record<string, any>
8 | > {
9 | return createLoader({
10 | async pull(locale, input) {
11 | // skip all metadata (keys starting with @)
12 | const result = _.pickBy(input, (value, key) => !_isMetadataKey(key));
13 | return result;
14 | },
15 | async push(locale, data, originalInput) {
16 | // find all metadata keys in originalInput
17 | const metadata = _.pickBy(originalInput, (value, key) =>
18 | _isMetadataKey(key),
19 | );
20 | const result = _.merge({}, metadata, { "@@locale": locale }, data);
21 | return result;
22 | },
23 | });
24 | }
25 |
26 | function _isMetadataKey(key: string) {
27 | return key.startsWith("@");
28 | }
29 |
```
--------------------------------------------------------------------------------
/packages/react/src/rsc/provider.spec.tsx:
--------------------------------------------------------------------------------
```typescript
1 | import { describe, it, expect, vi } from "vitest";
2 | import { render, screen } from "@testing-library/react";
3 |
4 | vi.mock("./utils", () => {
5 | return {
6 | loadDictionaryFromRequest: vi.fn(async (loader: any) => loader("en")),
7 | };
8 | });
9 |
10 | describe("rsc/provider", () => {
11 | describe("LingoProvider", () => {
12 | it("loads dictionary via helper and renders children through client provider", async () => {
13 | const { LingoProvider } = await import("./provider");
14 | const loadDictionary = vi.fn(async () => ({ locale: "en" }));
15 | render(
16 | await LingoProvider({
17 | loadDictionary,
18 | children: <div data-testid="child" />,
19 | }),
20 | );
21 | expect(screen.getByTestId("child")).toBeTruthy();
22 | expect(loadDictionary).toHaveBeenCalledWith("en");
23 | });
24 | });
25 | });
26 |
```
--------------------------------------------------------------------------------
/demo/vite-project/CHANGELOG.md:
--------------------------------------------------------------------------------
```markdown
1 | # vite-project
2 |
3 | ## 0.0.3
4 |
5 | ### Patch Changes
6 |
7 | - [`76cbd9b`](https://github.com/lingodotdev/lingo.dev/commit/76cbd9b2f2e1217421ad1f671bed5b3d64b43333) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - dictionary merging
8 |
9 | ## 0.0.2
10 |
11 | ### Patch Changes
12 |
13 | - [`8e97256`](https://github.com/lingodotdev/lingo.dev/commit/8e97256ca4e78dd09a967539ca9dec359bd558ef) Thanks [@maxprilutskiy](https://github.com/maxprilutskiy)! - fix dictionary merging
14 |
15 | ## 0.0.1
16 |
17 | ### Patch Changes
18 |
19 | - [#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
20 |
```
--------------------------------------------------------------------------------
/demo/react-router-app/package.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "name": "react-router-app",
3 | "private": true,
4 | "type": "module",
5 | "scripts": {
6 | "build": "react-router build",
7 | "dev": "react-router dev",
8 | "start": "react-router-serve ./build/server/index.js",
9 | "typecheck": "react-router typegen && tsc"
10 | },
11 | "dependencies": {
12 | "lingo.dev": "workspace:*",
13 | "@react-router/node": "^7.5.3",
14 | "@react-router/serve": "^7.5.3",
15 | "isbot": "^5.1.27",
16 | "react": "^19.1.0",
17 | "react-dom": "^19.1.0",
18 | "react-router": "^7.5.3"
19 | },
20 | "devDependencies": {
21 | "@react-router/dev": "^7.5.3",
22 | "@tailwindcss/vite": "^4.1.4",
23 | "@types/node": "^20",
24 | "@types/react": "^19.1.2",
25 | "@types/react-dom": "^19.1.2",
26 | "tailwindcss": "^4.1.4",
27 | "typescript": "^5.8.3",
28 | "vite": "^6.3.3",
29 | "vite-tsconfig-paths": "^5.1.4"
30 | }
31 | }
32 |
```
--------------------------------------------------------------------------------
/integrations/directus/package.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "name": "@replexica/integration-directus",
3 | "version": "0.1.10",
4 | "description": "Lingo.dev integration for Directus",
5 | "private": false,
6 | "sideEffects": false,
7 | "directus:extension": {
8 | "type": "operation",
9 | "path": {
10 | "app": "build/app.mjs",
11 | "api": "build/api.mjs"
12 | },
13 | "source": {
14 | "app": "src/app.ts",
15 | "api": "src/api.ts"
16 | },
17 | "host": "^10.10.0"
18 | },
19 | "files": [
20 | "build",
21 | "readme.md",
22 | "changelog.md"
23 | ],
24 | "scripts": {
25 | "dev": "tsup --watch",
26 | "build": "tsc --noEmit && tsup",
27 | "test": "vitest run"
28 | },
29 | "license": "Apache-2.0",
30 | "dependencies": {
31 | "@replexica/sdk": "^0.7.7"
32 | },
33 | "devDependencies": {
34 | "@directus/extensions-sdk": "12.1.4",
35 | "tsup": "^8.3.5",
36 | "typescript": "^5.8.3",
37 | "vitest": "^2.1.8"
38 | }
39 | }
40 |
```
--------------------------------------------------------------------------------
/packages/sdk/package.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "name": "@lingo.dev/_sdk",
3 | "version": "0.12.6",
4 | "description": "Lingo.dev JS SDK",
5 | "private": false,
6 | "publishConfig": {
7 | "access": "public"
8 | },
9 | "type": "module",
10 | "sideEffects": false,
11 | "types": "build/index.d.ts",
12 | "module": "build/index.mjs",
13 | "main": "build/index.cjs",
14 | "files": [
15 | "build"
16 | ],
17 | "scripts": {
18 | "dev": "tsup --watch",
19 | "build": "pnpm typecheck && tsup",
20 | "typecheck": "tsc --noEmit",
21 | "test": "vitest run"
22 | },
23 | "keywords": [],
24 | "author": "",
25 | "license": "Apache-2.0",
26 | "dependencies": {
27 | "@lingo.dev/_spec": "workspace:*",
28 | "@paralleldrive/cuid2": "^2.2.2",
29 | "jsdom": "^25.0.1",
30 | "zod": "^3.25.76"
31 | },
32 | "devDependencies": {
33 | "@types/jsdom": "^21.1.7",
34 | "tsup": "^8.3.5",
35 | "typescript": "^5.8.3",
36 | "vitest": "^3.1.2"
37 | }
38 | }
39 |
```
--------------------------------------------------------------------------------
/packages/cli/demo/json5/en/example.json5:
--------------------------------------------------------------------------------
```
1 | {
2 | // JSON5 allows comments!
3 | title: "Hello, world!",
4 | description: "A simple demo app with JSON5 features",
5 | version: "1.0.0",
6 | support_email: "[email protected]",
7 | homepage: "https://lingo.dev",
8 | deprecated: null,
9 | empty: "",
10 | emoji: "🚀",
11 |
12 | // Unquoted keys are allowed
13 | author: {
14 | name: "John Doe"
15 | },
16 |
17 | contributors: [
18 | { name: "Alice" },
19 | { name: "Bob" }
20 | ],
21 |
22 | messages: [
23 | "Welcome to MyApp",
24 | "Hello, world!"
25 | ],
26 |
27 | config: {
28 | theme: {
29 | primary: "Blue theme"
30 | }
31 | },
32 |
33 | mixed_array: [
34 | "Mixed content here",
35 | 42,
36 | true,
37 | {
38 | nested_message: "Nested text"
39 | }
40 | ],
41 |
42 | // Hexadecimal numbers work in JSON5
43 | hex_value: 0xDEADBEEF,
44 |
45 | // Trailing commas are allowed
46 | locked_key_1: "This value is locked and should not be changed",
47 | }
```
--------------------------------------------------------------------------------
/packages/cli/src/cli/utils/cloudflare-status.ts:
--------------------------------------------------------------------------------
```typescript
1 | export interface CloudflareStatusResponse {
2 | status: {
3 | indicator: "none" | "minor" | "major" | "critical";
4 | description: string;
5 | };
6 | }
7 |
8 | export async function checkCloudflareStatus(): Promise<CloudflareStatusResponse | null> {
9 | try {
10 | const response = await fetch(
11 | "https://www.cloudflarestatus.com/api/v2/status.json",
12 | {
13 | signal: AbortSignal.timeout(5000),
14 | },
15 | );
16 | if (response.ok) {
17 | return await response.json();
18 | }
19 | } catch (error) {}
20 | return null;
21 | }
22 |
23 | export function formatCloudflareStatusMessage(
24 | status: CloudflareStatusResponse,
25 | ): string {
26 | if (status.status.indicator === "none") {
27 | return "";
28 | }
29 | return `Cloudflare is experiencing ${status.status.indicator} issues: ${status.status.description}. This may be affecting the API connection.`;
30 | }
31 |
```
--------------------------------------------------------------------------------
/demo/adonisjs/ace.js:
--------------------------------------------------------------------------------
```javascript
1 | /*
2 | |--------------------------------------------------------------------------
3 | | JavaScript entrypoint for running ace commands
4 | |--------------------------------------------------------------------------
5 | |
6 | | DO NOT MODIFY THIS FILE AS IT WILL BE OVERRIDDEN DURING THE BUILD
7 | | PROCESS.
8 | |
9 | | See docs.adonisjs.com/guides/typescript-build-process#creating-production-build
10 | |
11 | | Since, we cannot run TypeScript source code using "node" binary, we need
12 | | a JavaScript entrypoint to run ace commands.
13 | |
14 | | This file registers the "ts-node/esm" hook with the Node.js module system
15 | | and then imports the "bin/console.ts" file.
16 | |
17 | */
18 |
19 | /**
20 | * Register hook to process TypeScript files using ts-node-maintained
21 | */
22 | import 'ts-node-maintained/register/esm'
23 |
24 | /**
25 | * Import ace console entrypoint
26 | */
27 | await import('./bin/console.js')
28 |
```
--------------------------------------------------------------------------------
/packages/cli/demo/php/es/example.php:
--------------------------------------------------------------------------------
```php
1 | <?php
2 |
3 | return [
4 | '0' => '¡Hola, mundo!',
5 | '1' => 'Bienvenido a MyApp',
6 | '2' => 'Es "simple\\" con una barra invertida \\ y salto de línea\\nTodo el texto aquí',
7 | '3' => [
8 | 'welcome_message' => '¡Bienvenido!'
9 | ],
10 | '4' => [
11 | 'error_text' => 'Algo salió mal'
12 | ],
13 | '5' => [
14 | 'navigation' => [
15 | 'home' => 'Inicio',
16 | 'about' => 'Acerca de',
17 | 'contact' => 'Contacto'
18 | ]
19 | ],
20 | '6' => [
21 | 'forms' => [
22 | 'login' => [
23 | 'username_label' => 'Nombre de usuario',
24 | 'password_label' => 'Contraseña',
25 | 'submit_button' => 'Iniciar sesión'
26 | ]
27 | ]
28 | ],
29 | '7' => [
30 | 'mixed_content' => [
31 | 'title' => 'Ajustes',
32 | 'count' => 42,
33 | 'enabled' => true,
34 | 'nothing_here' => null,
35 | 'description' => 'Ajustes y preferencias de la aplicación'
36 | ]
37 | ]
38 | ];
```
--------------------------------------------------------------------------------
/packages/cli/src/cli/localizer/_types.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { I18nConfig } from "@lingo.dev/_spec";
2 |
3 | export type LocalizerData = {
4 | sourceLocale: string;
5 | sourceData: Record<string, any>;
6 | processableData: Record<string, any>;
7 | targetLocale: string;
8 | targetData: Record<string, any>;
9 | hints: Record<string, string[]>;
10 | };
11 |
12 | export type LocalizerProgressFn = (
13 | progress: number,
14 | sourceChunk: Record<string, string>,
15 | processedChunk: Record<string, string>,
16 | ) => void;
17 |
18 | export interface ILocalizer {
19 | id: "Lingo.dev" | NonNullable<I18nConfig["provider"]>["id"];
20 | checkAuth: () => Promise<{
21 | authenticated: boolean;
22 | username?: string;
23 | error?: string;
24 | }>;
25 | validateSettings?: () => Promise<{ valid: boolean; error?: string }>;
26 | localize: (
27 | input: LocalizerData,
28 | onProgress?: LocalizerProgressFn,
29 | ) => Promise<LocalizerData["processableData"]>;
30 | }
31 |
```
--------------------------------------------------------------------------------
/packages/cli/src/cli/cmd/logout.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { Command } from "interactive-commander";
2 | import Ora from "ora";
3 | import { getSettings, saveSettings } from "../utils/settings";
4 | import {
5 | renderClear,
6 | renderSpacer,
7 | renderBanner,
8 | renderHero,
9 | } from "../utils/ui";
10 |
11 | export default new Command()
12 | .command("logout")
13 | .description("Log out by removing saved authentication credentials")
14 | .helpOption("-h, --help", "Show help")
15 | .action(async () => {
16 | try {
17 | await renderClear();
18 | await renderSpacer();
19 | await renderBanner();
20 | await renderHero();
21 | await renderSpacer();
22 |
23 | const settings = await getSettings(undefined);
24 | settings.auth.apiKey = "";
25 | await saveSettings(settings);
26 | Ora().succeed("Successfully logged out");
27 | } catch (error: any) {
28 | Ora().fail(error.message);
29 | process.exit(1);
30 | }
31 | });
32 |
```
--------------------------------------------------------------------------------
/packages/react/src/client/component.tsx:
--------------------------------------------------------------------------------
```typescript
1 | "use client";
2 |
3 | import {
4 | LingoComponent as LingoCoreComponent,
5 | LingoComponentProps as LingoCoreComponentProps,
6 | } from "../core";
7 | import { useLingo } from "./context";
8 |
9 | export type LingoComponentProps = Omit<LingoCoreComponentProps, "$dictionary">;
10 |
11 | export function LingoComponent(props: LingoComponentProps) {
12 | const { $as, $fileKey, $entryKey, ...rest } = props;
13 | const lingo = useLingo();
14 | return (
15 | <LingoCoreComponent
16 | $dictionary={lingo.dictionary}
17 | $as={$as}
18 | $fileKey={$fileKey}
19 | $entryKey={$entryKey}
20 | {...rest}
21 | />
22 | );
23 | }
24 |
25 | export function LingoHtmlComponent(
26 | props: React.HTMLAttributes<HTMLHtmlElement>,
27 | ) {
28 | const lingo = useLingo();
29 | return (
30 | <html
31 | {...props}
32 | lang={lingo?.dictionary?.locale}
33 | data-lingodotdev-compiler={lingo?.dictionary?.locale}
34 | />
35 | );
36 | }
37 |
```
--------------------------------------------------------------------------------
/.github/workflows/docker.yml:
--------------------------------------------------------------------------------
```yaml
1 | name: Build Docker Image
2 |
3 | on:
4 | workflow_dispatch:
5 | push:
6 | paths:
7 | - Dockerfile
8 | branches:
9 | - main
10 |
11 | jobs:
12 | docker:
13 | runs-on: ubuntu-latest
14 | steps:
15 | - name: Checkout
16 | uses: actions/checkout@v4
17 | - name: Login to Docker Hub
18 | uses: docker/login-action@v3
19 | with:
20 | username: ${{ secrets.DOCKERHUB_USERNAME }}
21 | password: ${{ secrets.DOCKERHUB_PASSWORD }}
22 | - name: Set up QEMU
23 | uses: docker/setup-qemu-action@v3
24 | - name: Set up Docker Buildx
25 | uses: docker/setup-buildx-action@v3
26 | - name: Build Docker image and push
27 | uses: docker/build-push-action@v6
28 | with:
29 | push: true
30 | platforms: linux/amd64
31 | context: ./
32 | file: ./Dockerfile
33 | tags: ${{ secrets.DOCKERHUB_USERNAME }}/ci-action:latest
34 |
```
--------------------------------------------------------------------------------
/demo/vite-project/src/App.css:
--------------------------------------------------------------------------------
```css
1 | #root {
2 | max-width: 1280px;
3 | margin: 0 auto;
4 | padding: 2rem;
5 | text-align: center;
6 | }
7 |
8 | .logo-container {
9 | display: flex;
10 | justify-content: center;
11 | align-items: center;
12 | gap: 1rem;
13 | }
14 |
15 | .logo {
16 | height: 6em;
17 | padding: 1.5em;
18 | will-change: filter;
19 | transition: filter 300ms;
20 | }
21 | .logo:hover {
22 | filter: drop-shadow(0 0 2em #646cffaa);
23 | }
24 | .logo.react:hover {
25 | filter: drop-shadow(0 0 2em #61dafbaa);
26 | }
27 |
28 | @keyframes logo-spin {
29 | from {
30 | transform: rotate(0deg);
31 | }
32 | to {
33 | transform: rotate(360deg);
34 | }
35 | }
36 |
37 | @media (prefers-reduced-motion: no-preference) {
38 | a:nth-of-type(2) .logo {
39 | animation: logo-spin infinite 20s linear;
40 | }
41 | }
42 |
43 | .welcome-text {
44 | max-width: 500px;
45 | margin: 0 auto;
46 | }
47 |
48 | .card {
49 | padding: 2em;
50 | }
51 |
52 | .read-the-docs {
53 | color: #888;
54 | }
55 |
56 | .locale-switcher {
57 | position: absolute;
58 | top: 0.5rem;
59 | right: 1rem;
60 | }
61 |
```
--------------------------------------------------------------------------------
/packages/cli/src/cli/processor/lingo.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { LingoDotDevEngine } from "@lingo.dev/_sdk";
2 | import { LocalizerInput, LocalizerProgressFn } from "./_base";
3 |
4 | export function createLingoLocalizer(params: {
5 | apiKey?: string;
6 | apiUrl: string;
7 | }) {
8 | return async (input: LocalizerInput, onProgress: LocalizerProgressFn) => {
9 | if (!Object.keys(input.processableData).length) {
10 | return input.processableData;
11 | }
12 |
13 | const lingo = new LingoDotDevEngine({
14 | apiKey: params.apiKey,
15 | apiUrl: params.apiUrl,
16 | });
17 |
18 | const result = await lingo.localizeObject(
19 | input.processableData,
20 | {
21 | sourceLocale: input.sourceLocale,
22 | targetLocale: input.targetLocale,
23 | reference: {
24 | [input.sourceLocale]: input.sourceData,
25 | [input.targetLocale]: input.targetData,
26 | },
27 | },
28 | onProgress,
29 | );
30 |
31 | return result;
32 | };
33 | }
34 |
```
--------------------------------------------------------------------------------
/demo/adonisjs/inertia/app/app.tsx:
--------------------------------------------------------------------------------
```typescript
1 | /// <reference path="../../adonisrc.ts" />
2 | /// <reference path="../../config/inertia.ts" />
3 |
4 | import '../css/app.css'
5 | import { hydrateRoot } from 'react-dom/client'
6 | import { createInertiaApp } from '@inertiajs/react'
7 | import { resolvePageComponent } from '@adonisjs/inertia/helpers'
8 | import { LingoProviderWrapper, loadDictionary } from 'lingo.dev/react/client'
9 |
10 | const appName = import.meta.env.VITE_APP_NAME || 'AdonisJS'
11 |
12 | createInertiaApp({
13 | progress: { color: '#5468FF' },
14 |
15 | title: (title) => `${title} - ${appName}`,
16 |
17 | resolve: (name) => {
18 | return resolvePageComponent(`../pages/${name}.tsx`, import.meta.glob('../pages/**/*.tsx'))
19 | },
20 |
21 | setup({ el, App, props }) {
22 | hydrateRoot(
23 | el,
24 | <LingoProviderWrapper loadDictionary={(locale) => loadDictionary(locale)}>
25 | <App {...props} />
26 | </LingoProviderWrapper>
27 | )
28 | },
29 | })
30 |
```
--------------------------------------------------------------------------------
/packages/cli/src/cli/utils/key-matching.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { minimatch } from "minimatch";
2 |
3 | /**
4 | * Checks if a key matches any of the provided patterns using prefix or glob matching
5 | */
6 | export function matchesKeyPattern(key: string, patterns: string[]): boolean {
7 | return patterns.some(
8 | (pattern) => key.startsWith(pattern) || minimatch(key, pattern),
9 | );
10 | }
11 |
12 | /**
13 | * Filters entries based on key matching patterns
14 | */
15 | export function filterEntriesByPattern(
16 | entries: [string, any][],
17 | patterns: string[],
18 | ): [string, any][] {
19 | return entries.filter(([key]) => matchesKeyPattern(key, patterns));
20 | }
21 |
22 | /**
23 | * Formats a value for display, truncating long strings
24 | */
25 | export function formatDisplayValue(value: any, maxLength = 50): string {
26 | if (typeof value === "string") {
27 | return value.length > maxLength
28 | ? `${value.substring(0, maxLength)}...`
29 | : value;
30 | }
31 | return JSON.stringify(value);
32 | }
33 |
```
--------------------------------------------------------------------------------
/packages/compiler/src/utils/jsx-expressions.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { NodePath } from "@babel/traverse";
2 | import * as t from "@babel/types";
3 | import { Expression } from "@babel/types";
4 |
5 | export const getJsxExpressions = (nodePath: NodePath<t.JSXElement>) => {
6 | const expressions: Expression[] = [];
7 | nodePath.traverse({
8 | JSXOpeningElement(path) {
9 | path.skip();
10 | },
11 | JSXExpressionContainer(path) {
12 | const expr = path.node.expression;
13 |
14 | // Skip empty expressions, identifiers (variables), member expressions (object paths), and function calls
15 | if (
16 | !t.isJSXEmptyExpression(expr) &&
17 | !t.isIdentifier(expr) &&
18 | !t.isMemberExpression(expr) &&
19 | !t.isCallExpression(expr) &&
20 | !(t.isStringLiteral(expr) && expr.value === " ") // whitespace
21 | ) {
22 | expressions.push(expr);
23 | }
24 | path.skip();
25 | },
26 | });
27 |
28 | return t.arrayExpression(expressions);
29 | };
30 |
```
--------------------------------------------------------------------------------
/packages/cli/demo/markdown/en/example.md:
--------------------------------------------------------------------------------
```markdown
1 | ---
2 | title: "Product Launch Guide"
3 | description: "Everything you need to know about our latest product features"
4 | author: "Product Team"
5 | date: 2024-01-15
6 | tags: ["apples", "bananas", "pears"]
7 | ---
8 |
9 | # Welcome to Our New Dashboard
10 |
11 | Discover powerful new features designed to streamline your workflow and boost productivity.
12 |
13 | ## Getting Started
14 |
15 | Follow these simple steps to set up your account and begin using our platform effectively.
16 |
17 | ---
18 |
19 | Our advanced analytics help you make data-driven decisions with confidence.
20 |
21 | 
22 |
23 | The intuitive interface makes it easy to navigate between different features and tools.
24 |
25 | [View documentation](https://example.com)
26 |
27 | Need help getting started? Our support team is available 24/7 to assist you.
28 |
29 | ***
30 |
31 | Join thousands of satisfied customers who have transformed their business with our platform.
```
--------------------------------------------------------------------------------
/packages/react/src/rsc/loader.spec.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { describe, it, expect } from "vitest";
2 | import { loadDictionary_internal } from "./loader";
3 |
4 | vi.mock("../core", () => {
5 | return {
6 | getDictionary: vi.fn(async (locale, loaders) => {
7 | if (locale === "es") return { hello: "Hola" };
8 | if (locale === "en") return { hello: "Hello" };
9 | return {};
10 | }),
11 | };
12 | });
13 |
14 | import { getDictionary } from "../core";
15 |
16 | describe("rsc/loader", () => {
17 | describe("loadDictionary_internal", () => {
18 | it("delegates to core getDictionary via internal wrapper", async () => {
19 | const loaders = {
20 | en: async () => ({ default: { hello: "Hello" } }),
21 | es: async () => ({ default: { hello: "Hola" } }),
22 | };
23 | const result = await loadDictionary_internal("es", loaders);
24 | expect(getDictionary).toHaveBeenCalledWith("es", loaders);
25 | expect(result).toEqual({ hello: "Hola" });
26 | });
27 | });
28 | });
29 |
```
--------------------------------------------------------------------------------
/packages/react/src/client/loader.spec.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { describe, it, expect } from "vitest";
2 | import { loadDictionary_internal } from "./loader";
3 |
4 | vi.mock("../core", () => {
5 | return {
6 | getDictionary: vi.fn(async (locale, loaders) => {
7 | if (locale === "es") return { hello: "Hola" };
8 | if (locale === "en") return { hello: "Hello" };
9 | return {};
10 | }),
11 | };
12 | });
13 |
14 | import { getDictionary } from "../core";
15 |
16 | describe("client/loader", () => {
17 | describe("loadDictionary_internal", () => {
18 | it("delegates to core getDictionary via internal wrapper", async () => {
19 | const loaders = {
20 | en: async () => ({ default: { hello: "Hello" } }),
21 | es: async () => ({ default: { hello: "Hola" } }),
22 | };
23 | const result = await loadDictionary_internal("es", loaders);
24 | expect(getDictionary).toHaveBeenCalledWith("es", loaders);
25 | expect(result).toEqual({ hello: "Hola" });
26 | });
27 | });
28 | });
29 |
```
--------------------------------------------------------------------------------
/demo/adonisjs/config/logger.ts:
--------------------------------------------------------------------------------
```typescript
1 | import env from '#start/env'
2 | import app from '@adonisjs/core/services/app'
3 | import { defineConfig, targets } from '@adonisjs/core/logger'
4 |
5 | const loggerConfig = defineConfig({
6 | default: 'app',
7 |
8 | /**
9 | * The loggers object can be used to define multiple loggers.
10 | * By default, we configure only one logger (named "app").
11 | */
12 | loggers: {
13 | app: {
14 | enabled: true,
15 | name: env.get('APP_NAME'),
16 | level: env.get('LOG_LEVEL'),
17 | transport: {
18 | targets: targets()
19 | .pushIf(!app.inProduction, targets.pretty())
20 | .pushIf(app.inProduction, targets.file({ destination: 1 }))
21 | .toArray(),
22 | },
23 | },
24 | },
25 | })
26 |
27 | export default loggerConfig
28 |
29 | /**
30 | * Inferring types for the list of loggers you have configured
31 | * in your application.
32 | */
33 | declare module '@adonisjs/core/types' {
34 | export interface LoggersList extends InferLoggers<typeof loggerConfig> {}
35 | }
36 |
```
--------------------------------------------------------------------------------
/packages/cli/src/cli/cmd/show/config.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { Command } from "interactive-commander";
2 | import _ from "lodash";
3 | import fs from "fs";
4 | import path from "path";
5 | import { defaultConfig } from "@lingo.dev/_spec";
6 |
7 | export default new Command()
8 | .command("config")
9 | .description("Print effective i18n.json after merging with defaults")
10 | .helpOption("-h, --help", "Show help")
11 | .action(async (options) => {
12 | const fileConfig = loadReplexicaFileConfig();
13 | const config = _.merge({}, defaultConfig, fileConfig);
14 |
15 | console.log(JSON.stringify(config, null, 2));
16 | });
17 |
18 | function loadReplexicaFileConfig(): any {
19 | const replexicaConfigPath = path.resolve(process.cwd(), "i18n.json");
20 | const fileExists = fs.existsSync(replexicaConfigPath);
21 | if (!fileExists) {
22 | return undefined;
23 | }
24 |
25 | const fileContent = fs.readFileSync(replexicaConfigPath, "utf-8");
26 | const replexicaFileConfig = JSON.parse(fileContent);
27 | return replexicaFileConfig;
28 | }
29 |
```
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "name": "@lingo.dev",
3 | "type": "module",
4 | "scripts": {
5 | "prepare": "husky",
6 | "build": "turbo build",
7 | "typecheck": "turbo typecheck",
8 | "test": "turbo test",
9 | "new": "changeset",
10 | "new:empty": "changeset --empty",
11 | "format": "prettier . --write",
12 | "format:check": "prettier . --check"
13 | },
14 | "devDependencies": {
15 | "@babel/generator": "^7.27.1",
16 | "@babel/parser": "^7.27.1",
17 | "@babel/traverse": "^7.27.4",
18 | "@babel/types": "^7.27.1",
19 | "@commitlint/cli": "^19.8.0",
20 | "@commitlint/config-conventional": "^19.8.0",
21 | "@types/babel__traverse": "^7.20.7",
22 | "commitlint": "^19.7.1",
23 | "husky": "^9.1.7",
24 | "prettier": "^3.4.2",
25 | "turbo": "^2.5.0"
26 | },
27 | "dependencies": {
28 | "@changesets/changelog-github": "^0.5.0",
29 | "@changesets/cli": "^2.27.10",
30 | "minimatch": "^10.0.3",
31 | "node-machine-id": "^1.1.12"
32 | },
33 | "packageManager": "[email protected]"
34 | }
35 |
```
--------------------------------------------------------------------------------
/packages/compiler/src/utils/rc.spec.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { describe, it, expect, vi, beforeEach } from "vitest";
2 | import { getRc } from "./rc";
3 |
4 | vi.mock("os", () => ({ default: { homedir: () => "/home/test" } }));
5 | vi.mock("fs", () => {
6 | const mockFs = {
7 | existsSync: vi.fn(() => false),
8 | readFileSync: vi.fn(() => ""),
9 | } as any;
10 | return { ...mockFs, default: mockFs };
11 | });
12 |
13 | import fsAny from "fs";
14 |
15 | describe("getRc", () => {
16 | beforeEach(() => {
17 | (fsAny as any).existsSync.mockReset().mockReturnValue(false);
18 | (fsAny as any).readFileSync.mockReset().mockReturnValue("");
19 | });
20 |
21 | it("returns empty object when rc file missing", () => {
22 | const data = getRc();
23 | expect(data).toEqual({});
24 | });
25 |
26 | it("parses ini file when present", () => {
27 | (fsAny as any).existsSync.mockReturnValue(true);
28 | (fsAny as any).readFileSync.mockReturnValue("[auth]\napiKey=abc\n");
29 | const data = getRc();
30 | expect(data).toHaveProperty("auth.apiKey", "abc");
31 | });
32 | });
33 |
```
--------------------------------------------------------------------------------
/packages/cli/src/cli/loaders/dato/index.ts:
--------------------------------------------------------------------------------
```typescript
1 | import fs from "fs";
2 | import JSON5 from "json5";
3 | import { composeLoaders } from "../_utils";
4 | import { datoConfigSchema } from "./_base";
5 | import createDatoFilterLoader from "./filter";
6 | import createDatoApiLoader from "./api";
7 | import createDatoExtractLoader from "./extract";
8 |
9 | export default function createDatoLoader(configFilePath: string) {
10 | try {
11 | const configContent = fs.readFileSync(configFilePath, "utf-8");
12 | const datoConfig = datoConfigSchema.parse(JSON5.parse(configContent));
13 |
14 | return composeLoaders(
15 | createDatoApiLoader(datoConfig, (updatedConfig) =>
16 | fs.writeFileSync(
17 | configFilePath,
18 | JSON5.stringify(updatedConfig, null, 2),
19 | ),
20 | ),
21 | createDatoFilterLoader(),
22 | createDatoExtractLoader(),
23 | );
24 | } catch (error: any) {
25 | throw new Error(
26 | [`Failed to parse DatoCMS config file.`, `Error: ${error.message}`].join(
27 | "\n\n",
28 | ),
29 | );
30 | }
31 | }
32 |
```
--------------------------------------------------------------------------------
/demo/adonisjs/start/env.ts:
--------------------------------------------------------------------------------
```typescript
1 | /*
2 | |--------------------------------------------------------------------------
3 | | Environment variables service
4 | |--------------------------------------------------------------------------
5 | |
6 | | The `Env.create` method creates an instance of the Env service. The
7 | | service validates the environment variables and also cast values
8 | | to JavaScript data types.
9 | |
10 | */
11 |
12 | import { Env } from '@adonisjs/core/env'
13 |
14 | export default await Env.create(new URL('../', import.meta.url), {
15 | NODE_ENV: Env.schema.enum(['development', 'production', 'test'] as const),
16 | PORT: Env.schema.number(),
17 | APP_KEY: Env.schema.string(),
18 | HOST: Env.schema.string({ format: 'host' }),
19 | LOG_LEVEL: Env.schema.string(),
20 |
21 | /*
22 | |----------------------------------------------------------
23 | | Variables for configuring session package
24 | |----------------------------------------------------------
25 | */
26 | SESSION_DRIVER: Env.schema.enum(['cookie', 'memory'] as const),
27 | })
28 |
```
--------------------------------------------------------------------------------
/demo/adonisjs/config/shield.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { defineConfig } from '@adonisjs/shield'
2 |
3 | const shieldConfig = defineConfig({
4 | /**
5 | * Configure CSP policies for your app. Refer documentation
6 | * to learn more
7 | */
8 | csp: {
9 | enabled: false,
10 | directives: {},
11 | reportOnly: false,
12 | },
13 |
14 | /**
15 | * Configure CSRF protection options. Refer documentation
16 | * to learn more
17 | */
18 | csrf: {
19 | enabled: true,
20 | exceptRoutes: [],
21 | enableXsrfCookie: true,
22 | methods: ['POST', 'PUT', 'PATCH', 'DELETE'],
23 | },
24 |
25 | /**
26 | * Control how your website should be embedded inside
27 | * iFrames
28 | */
29 | xFrame: {
30 | enabled: true,
31 | action: 'DENY',
32 | },
33 |
34 | /**
35 | * Force browser to always use HTTPS
36 | */
37 | hsts: {
38 | enabled: true,
39 | maxAge: '180 days',
40 | },
41 |
42 | /**
43 | * Disable browsers from sniffing the content type of a
44 | * response and always rely on the "content-type" header.
45 | */
46 | contentTypeSniffing: {
47 | enabled: true,
48 | },
49 | })
50 |
51 | export default shieldConfig
52 |
```
--------------------------------------------------------------------------------
/demo/adonisjs/vite.config.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { getDirname } from '@adonisjs/core/helpers'
2 | import inertia from '@adonisjs/inertia/client'
3 | import adonisjs from '@adonisjs/vite/client'
4 | import react from '@vitejs/plugin-react'
5 | import lingoCompiler from 'lingo.dev/compiler'
6 | import { type UserConfig, type PluginOption } from 'vite'
7 |
8 | const viteConfig: UserConfig = {
9 | plugins: [
10 | inertia({
11 | ssr: {
12 | enabled: true,
13 | entrypoint: 'inertia/app/ssr.tsx',
14 | },
15 | }),
16 | react(),
17 | adonisjs({
18 | entrypoints: ['inertia/app/app.tsx'],
19 | reload: ['resources/views/**/*.edge'],
20 | }) as unknown as PluginOption,
21 | ],
22 | resolve: {
23 | alias: {
24 | '~/': `${getDirname(import.meta.url)}/inertia/`,
25 | },
26 | },
27 | }
28 |
29 | const withLingo = lingoCompiler.vite({
30 | sourceRoot: 'inertia',
31 | lingoDir: 'lingo',
32 | sourceLocale: 'en',
33 | targetLocales: ['es'],
34 | rsc: false,
35 | useDirective: false,
36 | debug: false,
37 | models: 'lingo.dev',
38 | })
39 |
40 | export default withLingo(viteConfig)
41 |
```
--------------------------------------------------------------------------------
/packages/compiler/src/jsx-attribute.spec.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { describe, it, expect } from "vitest";
2 | import { jsxAttributeMutation } from "./jsx-attribute";
3 | import { createPayload, createOutput, defaultParams } from "./_base";
4 |
5 | function runMutation(code: string) {
6 | const input = createPayload({ code, params: defaultParams, fileKey: "test" });
7 | const mutated = jsxAttributeMutation(input);
8 | if (!mutated) return code;
9 | return createOutput(mutated).code;
10 | }
11 |
12 | describe("jsxAttributeMutation", () => {
13 | it("should replace html element with localizable attributes with LingoAttributeComponent", () => {
14 | const input = `
15 | <p>
16 | Lorem ipsum <a href="https://example.com" title="Dolor link">dolor</a> sit amet.
17 | </p>
18 | `;
19 | const expected = `
20 | <p>
21 | Lorem ipsum
22 | <LingoAttributeComponent
23 | $as="a"
24 | $attributes={{
25 | title: "0/body/title",
26 | "aria-label": "0/body/aria-label"
27 | }}
28 | href="https://example.com"
29 | >
30 | dolor
31 | </LingoAttributeComponent>
32 | sit amet.
33 | </p>
34 | `;
35 | });
36 | });
37 |
```
--------------------------------------------------------------------------------
/packages/cli/src/cli/loaders/xcode-strings.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { ILoader } from "./_types";
2 | import { createLoader } from "./_utils";
3 | import { Tokenizer } from "./xcode-strings/tokenizer";
4 | import { Parser } from "./xcode-strings/parser";
5 | import { escapeString } from "./xcode-strings/escape";
6 |
7 | export default function createXcodeStringsLoader(): ILoader<
8 | string,
9 | Record<string, any>
10 | > {
11 | return createLoader({
12 | async pull(locale, input) {
13 | // Tokenize the input
14 | const tokenizer = new Tokenizer(input);
15 | const tokens = tokenizer.tokenize();
16 |
17 | // Parse tokens into key-value pairs
18 | const parser = new Parser(tokens);
19 | const result = parser.parse();
20 |
21 | return result;
22 | },
23 |
24 | async push(locale, payload) {
25 | const lines = Object.entries(payload)
26 | .filter(([_, value]) => value != null)
27 | .map(([key, value]) => {
28 | const escapedValue = escapeString(value);
29 | return `"${key}" = "${escapedValue}";`;
30 | });
31 |
32 | return lines.join("\n");
33 | },
34 | });
35 | }
36 |
```
--------------------------------------------------------------------------------
/packages/cli/src/cli/loaders/mdx2/sections-split-2.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { ILoader } from "../_types";
2 | import { createLoader } from "../_utils";
3 | import { PlaceholderedMdx, SectionedMdx } from "./_types";
4 | import _ from "lodash";
5 |
6 | export default function createMdxSectionsSplit2Loader(): ILoader<
7 | PlaceholderedMdx,
8 | SectionedMdx
9 | > {
10 | return createLoader({
11 | async pull(locale, input) {
12 | const sections = _.chain(input.content)
13 | .split("\n\n")
14 | .filter(Boolean)
15 | .map((section, index) => [index, section])
16 | .fromPairs()
17 | .value();
18 |
19 | const result: SectionedMdx = {
20 | frontmatter: input.frontmatter,
21 | sections,
22 | };
23 |
24 | return result;
25 | },
26 |
27 | async push(locale, data, originalInput, _originalLocale, pullInput) {
28 | const content = _.chain(data.sections).values().join("\n\n").value();
29 |
30 | const result: PlaceholderedMdx = {
31 | frontmatter: data.frontmatter,
32 | codePlaceholders: pullInput?.codePlaceholders || {},
33 | content,
34 | };
35 |
36 | return result;
37 | },
38 | });
39 | }
40 |
```
--------------------------------------------------------------------------------
/packages/compiler/src/jsx-remove-attributes.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { createCodeMutation, CompilerPayload } from "./_base";
2 | import * as t from "@babel/types";
3 | import traverse from "@babel/traverse";
4 | import { NodePath } from "@babel/traverse";
5 |
6 | /**
7 | * This mutation identifies JSX elements with data-jsx-* attributes and removes them
8 | */
9 | export const jsxRemoveAttributesMutation = createCodeMutation(
10 | (payload: CompilerPayload) => {
11 | const ATTRIBUTES_TO_REMOVE = [
12 | "data-jsx-root",
13 | "data-jsx-scope",
14 | "data-jsx-attribute-scope",
15 | ];
16 |
17 | traverse(payload.ast, {
18 | JSXElement(path: NodePath<t.JSXElement>) {
19 | const openingElement = path.node.openingElement;
20 | openingElement.attributes = openingElement.attributes.filter((attr) => {
21 | const removeAttr =
22 | t.isJSXAttribute(attr) &&
23 | t.isJSXIdentifier(attr.name) &&
24 | ATTRIBUTES_TO_REMOVE.includes(attr.name.name as string);
25 | return !removeAttr;
26 | });
27 | },
28 | });
29 |
30 | return {
31 | ...payload,
32 | };
33 | },
34 | );
35 |
```
--------------------------------------------------------------------------------
/packages/cli/demo/markdown/es/example.md:
--------------------------------------------------------------------------------
```markdown
1 | ---
2 | title: Guía de lanzamiento de producto
3 | description: Todo lo que necesitas saber sobre las últimas características de
4 | nuestro producto
5 | author: Equipo de producto
6 | date: 2024-01-15
7 | tags:
8 | - apples
9 | - bananas
10 | - pears
11 | ---
12 |
13 | # Bienvenido a nuestro nuevo panel de control
14 |
15 | Descubre nuevas y potentes funciones diseñadas para optimizar tus flujos de trabajo y aumentar la productividad.
16 |
17 | ## Primeros pasos
18 |
19 | Sigue estos sencillos pasos para configurar tu cuenta y comenzar a utilizar nuestra plataforma de manera efectiva.
20 |
21 | ---
22 |
23 | Nuestros análisis avanzados te ayudan a tomar decisiones basadas en datos con confianza.
24 |
25 | 
26 |
27 | La interfaz intuitiva facilita la navegación entre diferentes funciones y herramientas.
28 |
29 | [Ver documentación](https://example.com)
30 |
31 | ¿Necesitas ayuda para empezar? Nuestro equipo de soporte está disponible 24/7 para asistirte.
32 |
33 | ---
34 |
35 | Únete a miles de clientes satisfechos que han transformado su negocio con nuestra plataforma.
```
--------------------------------------------------------------------------------
/demo/adonisjs/config/session.ts:
--------------------------------------------------------------------------------
```typescript
1 | import env from '#start/env'
2 | import app from '@adonisjs/core/services/app'
3 | import { defineConfig, stores } from '@adonisjs/session'
4 |
5 | const sessionConfig = defineConfig({
6 | enabled: true,
7 | cookieName: 'adonis-session',
8 |
9 | /**
10 | * When set to true, the session id cookie will be deleted
11 | * once the user closes the browser.
12 | */
13 | clearWithBrowser: false,
14 |
15 | /**
16 | * Define how long to keep the session data alive without
17 | * any activity.
18 | */
19 | age: '2h',
20 |
21 | /**
22 | * Configuration for session cookie and the
23 | * cookie store
24 | */
25 | cookie: {
26 | path: '/',
27 | httpOnly: true,
28 | secure: app.inProduction,
29 | sameSite: 'lax',
30 | },
31 |
32 | /**
33 | * The store to use. Make sure to validate the environment
34 | * variable in order to infer the store name without any
35 | * errors.
36 | */
37 | store: env.get('SESSION_DRIVER'),
38 |
39 | /**
40 | * List of configured stores. Refer documentation to see
41 | * list of available stores and their config.
42 | */
43 | stores: {
44 | cookie: stores.cookie(),
45 | },
46 | })
47 |
48 | export default sessionConfig
49 |
```
--------------------------------------------------------------------------------
/packages/compiler/src/jsx-html-lang.spec.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { describe, it, expect } from "vitest";
2 | import { createPayload, createOutput, defaultParams } from "./_base";
3 | import { jsxHtmlLangMutation } from "./jsx-html-lang";
4 |
5 | function run(code: string, rsc = true) {
6 | const input = createPayload({
7 | code,
8 | params: { ...defaultParams, rsc },
9 | relativeFilePath: "app/layout.tsx",
10 | } as any);
11 | const mutated = jsxHtmlLangMutation(input);
12 | return createOutput(mutated!).code.trim();
13 | }
14 |
15 | describe("jsxHtmlLangMutation", () => {
16 | it("replaces html tag with framework component in server mode", () => {
17 | const input = `
18 | export default function Root() {
19 | return <html><body>Hi</body></html>
20 | }`.trim();
21 | const out = run(input, true);
22 | expect(out).toMatch(/LingoHtmlComponent/);
23 | });
24 |
25 | it("replaces html tag with framework component in client mode", () => {
26 | const input = `
27 | "use client";
28 | export default function Root() {
29 | return <html><body>Hi</body></html>
30 | }`.trim();
31 | const out = run(input, false);
32 | expect(out).toMatch(/LingoHtmlComponent/);
33 | });
34 | });
35 |
```
--------------------------------------------------------------------------------
/packages/cli/src/cli/utils/config.ts:
--------------------------------------------------------------------------------
```typescript
1 | import _ from "lodash";
2 | import fs from "fs";
3 | import path from "path";
4 | import { I18nConfig, parseI18nConfig } from "@lingo.dev/_spec";
5 |
6 | export function getConfig(resave = true): I18nConfig | null {
7 | const configFilePath = _getConfigFilePath();
8 |
9 | const configFileExists = fs.existsSync(configFilePath);
10 | if (!configFileExists) {
11 | return null;
12 | }
13 |
14 | const fileContents = fs.readFileSync(configFilePath, "utf8");
15 | const rawConfig = JSON.parse(fileContents);
16 |
17 | const result = parseI18nConfig(rawConfig);
18 | const didConfigChange = !_.isEqual(rawConfig, result);
19 |
20 | if (resave && didConfigChange) {
21 | // Ensure the config is saved with the latest version / schema
22 | saveConfig(result);
23 | }
24 |
25 | return result;
26 | }
27 |
28 | export function saveConfig(config: I18nConfig) {
29 | const configFilePath = _getConfigFilePath();
30 |
31 | const serialized = JSON.stringify(config, null, 2);
32 | fs.writeFileSync(configFilePath, serialized);
33 |
34 | return config;
35 | }
36 |
37 | // Private
38 |
39 | function _getConfigFilePath() {
40 | return path.join(process.cwd(), "i18n.json");
41 | }
42 |
```
--------------------------------------------------------------------------------
/demo/next-app/public/globe.svg:
--------------------------------------------------------------------------------
```
1 | <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:
--------------------------------------------------------------------------------
```
1 | #!/usr/bin/env node
2 |
3 | import CLI from "lingo.dev/cli";
4 |
5 | const envVarConfigWarning = process.env.LINGODOTDEV_API_KEY
6 | ? "\nThis version is not compatible with LINGODOTDEV_API_KEY env variable."
7 | : "";
8 |
9 | console.warn(
10 | "\x1b[33m%s\x1b[0m",
11 | `
12 | ⚠️ WARNING: NEW PACKAGE AVAILABLE ⚠️
13 | ================================================================================
14 | This CLI version is deprecated.${envVarConfigWarning}
15 | Please use lingo.dev instead:
16 |
17 | npx lingo.dev@latest
18 |
19 | Visit https://lingo.dev for more information.
20 | ================================================================================
21 | `,
22 | );
23 |
24 | process.env.LINGODOTDEV_API_KEY = process.env.REPLEXICA_API_KEY;
25 | process.env.LINGODOTDEV_PULL_REQUEST = process.env.REPLEXICA_PULL_REQUEST;
26 | process.env.LINGODOTDEV_PULL_REQUEST_TITLE =
27 | process.env.REPLEXICA_PULL_REQUEST_TITLE;
28 | process.env.LINGODOTDEV_COMMIT_MESSAGE = process.env.REPLEXICA_COMMIT_MESSAGE;
29 | process.env.LINGODOTDEV_WORKING_DIRECTORY =
30 | process.env.REPLEXICA_WORKING_DIRECTORY;
31 |
32 | await CLI.parseAsync(process.argv);
33 |
```
--------------------------------------------------------------------------------
/packages/cli/src/cli/loaders/formatters/_base.ts:
--------------------------------------------------------------------------------
```typescript
1 | import path from "path";
2 | import { ILoader } from "../_types";
3 | import { createLoader } from "../_utils";
4 |
5 | export type BaseFormatterOptions = {
6 | bucketPathPattern: string;
7 | stage?: "pull" | "push" | "both";
8 | alwaysFormat?: boolean;
9 | };
10 |
11 | export function createBaseFormatterLoader(
12 | options: BaseFormatterOptions,
13 | formatFn: (data: string, filePath: string) => Promise<string>,
14 | ): ILoader<string, string> {
15 | const stage = options.stage || "both";
16 |
17 | const formatData = async (locale: string, data: string) => {
18 | const draftPath = options.bucketPathPattern.replaceAll("[locale]", locale);
19 | const finalPath = path.resolve(draftPath);
20 | return await formatFn(data, finalPath);
21 | };
22 |
23 | return createLoader({
24 | async pull(locale, data) {
25 | if (!["pull", "both"].includes(stage)) {
26 | return data;
27 | }
28 | return await formatData(locale, data);
29 | },
30 | async push(locale, data) {
31 | if (!["push", "both"].includes(stage)) {
32 | return data;
33 | }
34 | return await formatData(locale, data);
35 | },
36 | });
37 | }
38 |
```
--------------------------------------------------------------------------------
/packages/react/src/rsc/component.tsx:
--------------------------------------------------------------------------------
```typescript
1 | import {
2 | LingoComponent as LingoCoreComponent,
3 | LingoComponentProps as LingoCoreComponentProps,
4 | } from "../core";
5 | import { loadDictionaryFromRequest, loadLocaleFromCookies } from "./utils";
6 |
7 | export type LingoComponentProps = Omit<
8 | LingoCoreComponentProps,
9 | "$dictionary"
10 | > & {
11 | $loadDictionary: (locale: string | null) => Promise<any>;
12 | };
13 |
14 | export async function LingoComponent(props: LingoComponentProps) {
15 | const { $as, $fileKey, $entryKey, $loadDictionary, ...rest } = props;
16 | const dictionary = await loadDictionaryFromRequest($loadDictionary);
17 |
18 | if ($as.name === "LingoAttributeComponent") {
19 | rest.$loadDictionary = $loadDictionary;
20 | }
21 |
22 | return (
23 | <LingoCoreComponent
24 | {...rest}
25 | $dictionary={dictionary}
26 | $as={$as}
27 | $fileKey={$fileKey}
28 | $entryKey={$entryKey}
29 | />
30 | );
31 | }
32 |
33 | export async function LingoHtmlComponent(
34 | props: React.HTMLAttributes<HTMLHtmlElement>,
35 | ) {
36 | const locale = await loadLocaleFromCookies();
37 | return <html {...props} lang={locale} data-lingodotdev-compiler={locale} />;
38 | }
39 |
```
--------------------------------------------------------------------------------
/packages/compiler/src/jsx-provider.spec.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { describe, it, expect } from "vitest";
2 | import { createPayload, createOutput, defaultParams } from "./_base";
3 | import jsxProviderMutation from "./jsx-provider";
4 |
5 | function run(code: string, rsc = true) {
6 | const input = createPayload({
7 | code,
8 | params: { ...defaultParams, rsc },
9 | relativeFilePath: "app/layout.tsx",
10 | } as any);
11 | const mutated = jsxProviderMutation(input);
12 | return createOutput(mutated!).code.trim();
13 | }
14 |
15 | describe("jsxProviderMutation", () => {
16 | it("wraps <html> with LingoProvider in server mode", () => {
17 | const input = `
18 | export default function Root() {
19 | return <html><body>Hi</body></html>
20 | }`.trim();
21 | const out = run(input, true);
22 | expect(out).toContain("LingoProvider");
23 | expect(out).toContain("loadDictionary");
24 | });
25 |
26 | it("does not modify in client mode", () => {
27 | const input = `
28 | export default function Root() {
29 | return <html><body>Hi</body></html>
30 | }`.trim();
31 | const out = run(input, false);
32 | expect(out).toContain("<html>");
33 | expect(out).not.toContain("LingoProvider");
34 | });
35 | });
36 |
```
--------------------------------------------------------------------------------
/packages/compiler/src/utils/env.spec.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2 | import { isRunningInCIOrDocker } from "./env";
3 |
4 | vi.mock("fs", () => {
5 | const mockFs = { existsSync: vi.fn(() => false) } as any;
6 | return { ...mockFs, default: mockFs };
7 | });
8 |
9 | import fsAny from "fs";
10 |
11 | describe("isRunningInCIOrDocker", () => {
12 | const originalEnv = { ...process.env };
13 |
14 | beforeEach(() => {
15 | process.env = { ...originalEnv };
16 | (fsAny as any).existsSync.mockReset().mockReturnValue(false);
17 | });
18 | afterEach(() => {
19 | process.env = originalEnv;
20 | });
21 |
22 | it("returns true when CI env var is set", () => {
23 | process.env.CI = "true";
24 | expect(isRunningInCIOrDocker()).toBe(true);
25 | });
26 |
27 | it("returns true when /.dockerenv exists", () => {
28 | (fsAny as any).existsSync.mockReturnValueOnce(true);
29 | delete process.env.CI;
30 | expect(isRunningInCIOrDocker()).toBe(true);
31 | });
32 |
33 | it("returns false otherwise", () => {
34 | delete process.env.CI;
35 | (fsAny as any).existsSync.mockReturnValueOnce(false);
36 | expect(isRunningInCIOrDocker()).toBe(false);
37 | });
38 | });
39 |
```
--------------------------------------------------------------------------------
/packages/cli/src/cli/cmd/show/locale.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { Command } from "interactive-commander";
2 | import _ from "lodash";
3 | import Z from "zod";
4 | import Ora from "ora";
5 | import { localeCodes } from "@lingo.dev/_spec";
6 | import { CLIError } from "../../utils/errors";
7 |
8 | export default new Command()
9 | .command("locale")
10 | .description("List supported locale codes")
11 | .helpOption("-h, --help", "Show help")
12 | // argument can be equal either "sources" or "targets"
13 | .argument(
14 | "<type>",
15 | 'Type of locales to show: "sources" or "targets" - both show the full supported locale list',
16 | )
17 | .action(async (type) => {
18 | const ora = Ora();
19 | try {
20 | switch (type) {
21 | default:
22 | throw new CLIError({
23 | message: `Invalid type: ${type}`,
24 | docUrl: "invalidType",
25 | });
26 | case "sources":
27 | localeCodes.forEach((locale) => console.log(locale));
28 | break;
29 | case "targets":
30 | localeCodes.forEach((locale) => console.log(locale));
31 | break;
32 | }
33 | } catch (error: any) {
34 | ora.fail(error.message);
35 | process.exit(1);
36 | }
37 | });
38 |
```
--------------------------------------------------------------------------------
/packages/compiler/src/jsx-attribute-flag.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { createCodeMutation, CompilerPayload } from "./_base";
2 | import * as t from "@babel/types";
3 | import { getJsxAttributeScopes } from "./utils/jsx-attribute-scope";
4 | import { getAstKey } from "./utils/ast-key";
5 |
6 | /**
7 | * This mutation identifies JSX elements with localizable attributes
8 | * and adds a data-jsx-attributes attribute with an array of the attribute names
9 | */
10 | const jsxAttributeFlagMutation = createCodeMutation(
11 | (payload: CompilerPayload) => {
12 | const jsxScopes = getJsxAttributeScopes(payload.ast);
13 |
14 | for (const [jsxScope, attributes] of jsxScopes) {
15 | const scopeKey = getAstKey(jsxScope);
16 | jsxScope.node.openingElement.attributes.push(
17 | t.jsxAttribute(
18 | t.jsxIdentifier("data-jsx-attribute-scope"),
19 | t.jsxExpressionContainer(
20 | t.arrayExpression(
21 | attributes.map((attr) =>
22 | t.stringLiteral(`${attr}:${scopeKey}-${attr}`),
23 | ),
24 | ),
25 | ),
26 | ),
27 | );
28 | }
29 |
30 | return {
31 | ...payload,
32 | };
33 | },
34 | );
35 |
36 | export default jsxAttributeFlagMutation;
37 |
```
--------------------------------------------------------------------------------
/demo/adonisjs/config/app.ts:
--------------------------------------------------------------------------------
```typescript
1 | import env from '#start/env'
2 | import app from '@adonisjs/core/services/app'
3 | import { Secret } from '@adonisjs/core/helpers'
4 | import { defineConfig } from '@adonisjs/core/http'
5 |
6 | /**
7 | * The app key is used for encrypting cookies, generating signed URLs,
8 | * and by the "encryption" module.
9 | *
10 | * The encryption module will fail to decrypt data if the key is lost or
11 | * changed. Therefore it is recommended to keep the app key secure.
12 | */
13 | export const appKey = new Secret(env.get('APP_KEY'))
14 |
15 | /**
16 | * The configuration settings used by the HTTP server
17 | */
18 | export const http = defineConfig({
19 | generateRequestId: true,
20 | allowMethodSpoofing: false,
21 |
22 | /**
23 | * Enabling async local storage will let you access HTTP context
24 | * from anywhere inside your application.
25 | */
26 | useAsyncLocalStorage: false,
27 |
28 | /**
29 | * Manage cookies configuration. The settings for the session id cookie are
30 | * defined inside the "config/session.ts" file.
31 | */
32 | cookie: {
33 | domain: '',
34 | path: '/',
35 | maxAge: '2h',
36 | httpOnly: true,
37 | secure: app.inProduction,
38 | sameSite: 'lax',
39 | },
40 | })
41 |
```
--------------------------------------------------------------------------------
/packages/cli/src/cli/loaders/formatters/index.ts:
--------------------------------------------------------------------------------
```typescript
1 | import createPrettierLoader, { PrettierLoaderOptions } from "./prettier";
2 | import createBiomeLoader from "./biome";
3 | import { ILoader } from "../_types";
4 | import { Options } from "prettier";
5 |
6 | export type FormatterType = "prettier" | "biome" | undefined;
7 | export type ParserType = Options["parser"];
8 |
9 | export function createFormatterLoader(
10 | formatterType: FormatterType,
11 | parser: ParserType,
12 | bucketPathPattern: string,
13 | ): ILoader<string, string> {
14 | // If explicitly set to undefined, auto-detect (prefer prettier for backward compatibility)
15 | if (formatterType === undefined) {
16 | return createPrettierLoader({ parser, bucketPathPattern });
17 | }
18 |
19 | if (formatterType === "prettier") {
20 | return createPrettierLoader({ parser, bucketPathPattern });
21 | }
22 |
23 | if (formatterType === "biome") {
24 | return createBiomeLoader({ bucketPathPattern });
25 | }
26 |
27 | throw new Error(`Unknown formatter: ${formatterType}`);
28 | }
29 |
30 | // Re-export for direct access if needed
31 | export { createPrettierLoader, createBiomeLoader };
32 | export type { PrettierLoaderOptions };
33 | export type { BiomeLoaderOptions } from "./biome";
34 |
```
--------------------------------------------------------------------------------
/packages/cli/src/cli/loaders/srt.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { ILoader } from "./_types";
2 | import { createLoader } from "./_utils";
3 | import srtParser from "srt-parser-2";
4 |
5 | export default function createSrtLoader(): ILoader<
6 | string,
7 | Record<string, any>
8 | > {
9 | const parser = new srtParser();
10 | return createLoader({
11 | async pull(locale, input) {
12 | const parsed = parser.fromSrt(input) || [];
13 | const result: Record<string, string> = {};
14 |
15 | parsed.forEach((entry) => {
16 | const key = `${entry.id}#${entry.startTime}-${entry.endTime}`;
17 | result[key] = entry.text;
18 | });
19 |
20 | return result;
21 | },
22 |
23 | async push(locale, payload) {
24 | const output = Object.entries(payload).map(([key, text]) => {
25 | const [id, timeRange] = key.split("#");
26 | const [startTime, endTime] = timeRange.split("-");
27 |
28 | return {
29 | id: id,
30 | startTime: startTime,
31 | startSeconds: 0,
32 | endTime: endTime,
33 | endSeconds: 0,
34 | text: text,
35 | };
36 | });
37 |
38 | const srtContent = parser.toSrt(output).trim().replace(/\r?\n/g, "\n");
39 | return srtContent;
40 | },
41 | });
42 | }
43 |
```
--------------------------------------------------------------------------------
/packages/cli/src/cli/loaders/mdx2/localizable-document.spec.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { describe, it, expect } from "vitest";
2 | import createLocalizableMdxDocumentLoader from "./localizable-document";
3 |
4 | describe("mdx localizable document loader", () => {
5 | it("should map to meta/content on pull and reconstruct on push", async () => {
6 | const loader = createLocalizableMdxDocumentLoader();
7 | loader.setDefaultLocale("en");
8 |
9 | const headingSection = "## Heading One\nSome paragraph.";
10 | const pulled = await loader.pull("en", {
11 | frontmatter: {
12 | title: "Sample",
13 | },
14 | sections: {
15 | "0": headingSection,
16 | },
17 | });
18 |
19 | // Validate structure
20 | expect(pulled).toHaveProperty("meta");
21 | expect(pulled).toHaveProperty("content");
22 |
23 | // Expect meta matches frontmatter
24 | expect(pulled.meta.title).toBe("Sample");
25 |
26 | // Modify
27 | pulled.meta.title = "Hola";
28 |
29 | // Try push
30 | const pushed = await loader.push("es", pulled);
31 |
32 | // After push we should get original MDX string reflect changes
33 | expect(pushed.frontmatter.title).toBe("Hola");
34 | // sections should persist
35 | expect(pushed.sections["0"]).toBe(headingSection);
36 | });
37 | });
38 |
```
--------------------------------------------------------------------------------
/packages/compiler/src/lib/lcp/api/shots.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { z } from "zod";
2 | import { dictionarySchema } from "../schema";
3 |
4 | export default [
5 | // Shot #1
6 | [
7 | {
8 | version: 0.1,
9 | locale: "en",
10 | files: {
11 | "demo-app/my-custom-header.tsx": {
12 | entries: {
13 | "1z2x3c4v": "Dashboard",
14 | "5t6y7u8i": "Settings",
15 | "9o0p1q2r": "Logout",
16 | },
17 | },
18 | "demo-app/my-custom-footer.tsx": {
19 | entries: {
20 | "9k0l1m2n": "© 2025 Lingo.dev. All rights reserved.",
21 | },
22 | },
23 | },
24 | },
25 | {
26 | version: 0.1,
27 | locale: "es",
28 | files: {
29 | "demo-app/my-custom-header.tsx": {
30 | entries: {
31 | "1z2x3c4v": "Panel de control",
32 | "5t6y7u8i": "Configuración",
33 | "9o0p1q2r": "Cerrar sesión",
34 | },
35 | },
36 | "demo-app/my-custom-footer.tsx": {
37 | entries: {
38 | "9k0l1m2n": "© 2025 Lingo.dev. Todos los derechos reservados.",
39 | },
40 | },
41 | },
42 | },
43 | ],
44 | // More shots here...
45 | ] satisfies [
46 | z.infer<typeof dictionarySchema>,
47 | z.infer<typeof dictionarySchema>,
48 | ][];
49 |
```
--------------------------------------------------------------------------------
/packages/cli/src/cli/loaders/vue-json.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { jsonrepair } from "jsonrepair";
2 | import { ILoader } from "./_types";
3 | import { createLoader } from "./_utils";
4 |
5 | export default function createVueJsonLoader(): ILoader<
6 | string,
7 | Record<string, any>
8 | > {
9 | return createLoader({
10 | pull: async (locale, input, ctx) => {
11 | const parsed = parseVueFile(input);
12 | return parsed?.i18n?.[locale] ?? {};
13 | },
14 | push: async (locale, data, originalInput) => {
15 | const parsed = parseVueFile(originalInput ?? "");
16 | if (!parsed) {
17 | return originalInput ?? "";
18 | }
19 |
20 | parsed.i18n[locale] = data;
21 | return `${parsed.before}<i18n>\n${JSON.stringify(
22 | parsed.i18n,
23 | null,
24 | 2,
25 | )}\n</i18n>${parsed.after}`;
26 | },
27 | });
28 | }
29 |
30 | function parseVueFile(input: string) {
31 | const match = input.match(/^([\s\S]*)<i18n>([\s\S]*)<\/i18n>([\s\S]*)$/);
32 |
33 | if (!match) {
34 | return null;
35 | }
36 |
37 | const [, before, jsonString = "{}", after] = match;
38 | let i18n: Record<string, any>;
39 | try {
40 | i18n = JSON.parse(jsonString);
41 | } catch (error) {
42 | i18n = JSON.parse(jsonrepair(jsonString));
43 | }
44 |
45 | return { before, after, i18n };
46 | }
47 |
```
--------------------------------------------------------------------------------
/packages/cli/src/cli/utils/update-gitignore.ts:
--------------------------------------------------------------------------------
```typescript
1 | import fs from "fs";
2 | import path from "path";
3 |
4 | export default function updateGitignore() {
5 | const cacheFile = "i18n.cache";
6 | const projectRoot = findCurrentProjectRoot();
7 | if (!projectRoot) {
8 | return;
9 | }
10 | const gitignorePath = path.join(projectRoot, ".gitignore");
11 | if (!fs.existsSync(gitignorePath)) {
12 | return;
13 | }
14 |
15 | const gitignore = fs.readFileSync(gitignorePath, "utf8").split("\n");
16 | const cacheIsIgnored = gitignore.includes(cacheFile);
17 |
18 | if (!cacheIsIgnored) {
19 | let content = "";
20 |
21 | // Ensure there's a trailing newline
22 | content = fs.readFileSync(gitignorePath, "utf8");
23 | if (content !== "" && !content.endsWith("\n")) {
24 | content += "\n";
25 | }
26 |
27 | content += `${cacheFile}\n`;
28 | fs.writeFileSync(gitignorePath, content);
29 | }
30 | }
31 |
32 | function findCurrentProjectRoot() {
33 | let currentDir = process.cwd();
34 | while (currentDir !== path.parse(currentDir).root) {
35 | const gitDirPath = path.join(currentDir, ".git");
36 | if (fs.existsSync(gitDirPath) && fs.lstatSync(gitDirPath).isDirectory()) {
37 | return currentDir;
38 | }
39 | currentDir = path.dirname(currentDir);
40 | }
41 | return null;
42 | }
43 |
```
--------------------------------------------------------------------------------
/packages/cli/demo/vue-json/example.vue:
--------------------------------------------------------------------------------
```vue
1 | <template>
2 | <div class="container">
3 | <h1>{{ $t('welcome') }}</h1>
4 | <p>{{ $t('description') }}</p>
5 | <button @click="handleClick">{{ $t('button.submit') }}</button>
6 | </div>
7 | </template>
8 |
9 | <script>
10 | export default {
11 | name: 'ExampleComponent',
12 | methods: {
13 | handleClick() {
14 | console.log('Button clicked');
15 | }
16 | }
17 | }
18 | </script>
19 |
20 | <style scoped>
21 | .container {
22 | padding: 20px;
23 | font-family: Arial, sans-serif;
24 | }
25 |
26 | h1 {
27 | color: #333;
28 | }
29 | </style>
30 |
31 | <i18n>
32 | {
33 | "en": {
34 | "welcome": "Hello, world!",
35 | "description": "A simple demo app",
36 | "button": {
37 | "submit": "Submit",
38 | "cancel": "Cancel"
39 | },
40 | "messages": [
41 | "Hello from MyApp",
42 | "Welcome message"
43 | ],
44 | "metadata": {
45 | "active": true,
46 | "maxItems": 10,
47 | "note": null
48 | }
49 | },
50 | "es": {
51 | "welcome": "¡Hola, mundo!",
52 | "description": "Una aplicación de demostración simple",
53 | "button": {
54 | "submit": "Enviar",
55 | "cancel": "Cancelar"
56 | },
57 | "messages": [
58 | "Hola desde MyApp",
59 | "Mensaje de bienvenida"
60 | ],
61 | "metadata": {
62 | "active": true,
63 | "maxItems": 10,
64 | "note": null
65 | }
66 | }
67 | }
68 | </i18n>
```
--------------------------------------------------------------------------------
/packages/cli/src/cli/cmd/show/locked-keys.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { Command } from "interactive-commander";
2 | import Ora from "ora";
3 | import { getConfig } from "../../utils/config";
4 | import { CLIError } from "../../utils/errors";
5 | import { getBuckets } from "../../utils/buckets";
6 | import { executeKeyCommand } from "./_shared-key-command";
7 |
8 | export default new Command()
9 | .command("locked-keys")
10 | .description(
11 | "Show which key-value pairs in source files match lockedKeys patterns",
12 | )
13 | .option("--bucket <name>", "Only show locked keys for a specific bucket")
14 | .helpOption("-h, --help", "Show help")
15 | .action(async (options) => {
16 | const ora = Ora();
17 | try {
18 | const i18nConfig = await getConfig();
19 |
20 | if (!i18nConfig) {
21 | throw new CLIError({
22 | message:
23 | "i18n.json not found. Please run `lingo.dev init` to initialize the project.",
24 | docUrl: "i18nNotFound",
25 | });
26 | }
27 |
28 | const buckets = getBuckets(i18nConfig);
29 |
30 | await executeKeyCommand(i18nConfig, buckets, options, {
31 | filterType: "lockedKeys",
32 | displayName: "locked",
33 | });
34 | } catch (error: any) {
35 | ora.fail(error.message);
36 | process.exit(1);
37 | }
38 | });
39 |
```
--------------------------------------------------------------------------------
/demo/vite-project/src/index.css:
--------------------------------------------------------------------------------
```css
1 | :root {
2 | font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
3 | line-height: 1.5;
4 | font-weight: 400;
5 |
6 | color-scheme: light dark;
7 | color: rgba(255, 255, 255, 0.87);
8 | background-color: #242424;
9 |
10 | font-synthesis: none;
11 | text-rendering: optimizeLegibility;
12 | -webkit-font-smoothing: antialiased;
13 | -moz-osx-font-smoothing: grayscale;
14 | }
15 |
16 | a {
17 | font-weight: 500;
18 | color: #646cff;
19 | text-decoration: inherit;
20 | }
21 | a:hover {
22 | color: #535bf2;
23 | }
24 |
25 | body {
26 | margin: 0;
27 | display: flex;
28 | place-items: center;
29 | min-width: 320px;
30 | min-height: 100vh;
31 | }
32 |
33 | h1 {
34 | font-size: 3.2em;
35 | line-height: 1.1;
36 | }
37 |
38 | button {
39 | border-radius: 8px;
40 | border: 1px solid transparent;
41 | padding: 0.6em 1.2em;
42 | font-size: 1em;
43 | font-weight: 500;
44 | font-family: inherit;
45 | background-color: #1a1a1a;
46 | cursor: pointer;
47 | transition: border-color 0.25s;
48 | }
49 | button:hover {
50 | border-color: #646cff;
51 | }
52 | button:focus,
53 | button:focus-visible {
54 | outline: 4px auto -webkit-focus-ring-color;
55 | }
56 |
57 | @media (prefers-color-scheme: light) {
58 | :root {
59 | color: #213547;
60 | background-color: #ffffff;
61 | }
62 | a:hover {
63 | color: #747bff;
64 | }
65 | button {
66 | background-color: #f9f9f9;
67 | }
68 | }
69 |
```
--------------------------------------------------------------------------------
/packages/cli/src/cli/cmd/show/ignored-keys.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { Command } from "interactive-commander";
2 | import Ora from "ora";
3 | import { getConfig } from "../../utils/config";
4 | import { CLIError } from "../../utils/errors";
5 | import { getBuckets } from "../../utils/buckets";
6 | import { executeKeyCommand } from "./_shared-key-command";
7 |
8 | export default new Command()
9 | .command("ignored-keys")
10 | .description(
11 | "Show which key-value pairs in source files match ignoredKeys patterns",
12 | )
13 | .option("--bucket <name>", "Only show ignored keys for a specific bucket")
14 | .helpOption("-h, --help", "Show help")
15 | .action(async (options) => {
16 | const ora = Ora();
17 | try {
18 | const i18nConfig = await getConfig();
19 |
20 | if (!i18nConfig) {
21 | throw new CLIError({
22 | message:
23 | "i18n.json not found. Please run `lingo.dev init` to initialize the project.",
24 | docUrl: "i18nNotFound",
25 | });
26 | }
27 |
28 | const buckets = getBuckets(i18nConfig);
29 |
30 | await executeKeyCommand(i18nConfig, buckets, options, {
31 | filterType: "ignoredKeys",
32 | displayName: "ignored",
33 | });
34 | } catch (error: any) {
35 | ora.fail(error.message);
36 | process.exit(1);
37 | }
38 | });
39 |
```
--------------------------------------------------------------------------------
/demo/adonisjs/tests/bootstrap.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { assert } from '@japa/assert'
2 | import app from '@adonisjs/core/services/app'
3 | import type { Config } from '@japa/runner/types'
4 | import { pluginAdonisJS } from '@japa/plugin-adonisjs'
5 | import testUtils from '@adonisjs/core/services/test_utils'
6 |
7 | /**
8 | * This file is imported by the "bin/test.ts" entrypoint file
9 | */
10 |
11 | /**
12 | * Configure Japa plugins in the plugins array.
13 | * Learn more - https://japa.dev/docs/runner-config#plugins-optional
14 | */
15 | export const plugins: Config['plugins'] = [assert(), pluginAdonisJS(app)]
16 |
17 | /**
18 | * Configure lifecycle function to run before and after all the
19 | * tests.
20 | *
21 | * The setup functions are executed before all the tests
22 | * The teardown functions are executed after all the tests
23 | */
24 | export const runnerHooks: Required<Pick<Config, 'setup' | 'teardown'>> = {
25 | setup: [],
26 | teardown: [],
27 | }
28 |
29 | /**
30 | * Configure suites by tapping into the test suite instance.
31 | * Learn more - https://japa.dev/docs/test-suites#lifecycle-hooks
32 | */
33 | export const configureSuite: Config['configureSuite'] = (suite) => {
34 | if (['browser', 'functional', 'e2e'].includes(suite.name)) {
35 | return suite.setup(() => testUtils.httpServer().start())
36 | }
37 | }
38 |
```
--------------------------------------------------------------------------------
/demo/adonisjs/inertia/pages/home.tsx:
--------------------------------------------------------------------------------
```typescript
1 | import { Head } from '@inertiajs/react'
2 | import { LocaleSwitcher } from 'lingo.dev/react/client'
3 |
4 | export default function Home() {
5 | return (
6 | <>
7 | <Head title="Homepage" />
8 | <div className="min-h-screen flex items-center justify-center bg-gray-50">
9 | <div className="max-w-lg mx-auto p-8 bg-white rounded-lg shadow-sm text-center">
10 | <h1 className="text-3xl font-bold text-gray-900 mb-6">Hello, world!</h1>
11 | <p className="text-gray-700 mb-4 leading-relaxed">
12 | This is an example app that demonstrates how{' '}
13 | <strong className="text-gray-900">Lingo.dev Compiler</strong> can be used to localize{' '}
14 | apps built with{' '}
15 | <a href="https://adonisjs.com/" className="text-blue-600 hover:text-blue-800 underline">
16 | AdonisJS
17 | </a>
18 | .
19 | </p>
20 | <p className="text-gray-700 mb-6 leading-relaxed">
21 | To switch between locales, use the following dropdown:
22 | </p>
23 | <div className="flex justify-center">
24 | <LocaleSwitcher locales={['en', 'es']} />
25 | </div>
26 | </div>
27 | </div>
28 | </>
29 | )
30 | }
31 |
```
--------------------------------------------------------------------------------
/packages/compiler/src/jsx-html-lang.ts:
--------------------------------------------------------------------------------
```typescript
1 | import traverse from "@babel/traverse";
2 | import * as t from "@babel/types";
3 | import { createCodeMutation } from "./_base";
4 | import { getJsxElementName } from "./utils/jsx-element";
5 | import { getModuleExecutionMode, getOrCreateImport } from "./utils";
6 | import { ModuleId } from "./_const";
7 |
8 | export const jsxHtmlLangMutation = createCodeMutation((payload) => {
9 | traverse(payload.ast, {
10 | JSXElement: (path) => {
11 | if (getJsxElementName(path)?.toLowerCase() === "html") {
12 | const mode = getModuleExecutionMode(payload.ast, payload.params.rsc);
13 | const packagePath =
14 | mode === "client" ? ModuleId.ReactClient : ModuleId.ReactRSC;
15 | const lingoHtmlComponentImport = getOrCreateImport(payload.ast, {
16 | moduleName: packagePath,
17 | exportedName: "LingoHtmlComponent",
18 | });
19 |
20 | path.node.openingElement.name = t.jsxIdentifier(
21 | lingoHtmlComponentImport.importedName,
22 | );
23 | if (path.node.closingElement) {
24 | path.node.closingElement.name = t.jsxIdentifier(
25 | lingoHtmlComponentImport.importedName,
26 | );
27 | }
28 |
29 | path.skip();
30 | }
31 | },
32 | });
33 |
34 | return payload;
35 | });
36 |
```
--------------------------------------------------------------------------------
/packages/locales/src/constants.ts:
--------------------------------------------------------------------------------
```typescript
1 | /**
2 | * Shared constants for locale parsing and validation
3 | */
4 |
5 | /**
6 | * Regular expression for parsing locale strings
7 | *
8 | * This regex is case-sensitive and expects normalized locale strings:
9 | * - Language code: 2-3 lowercase letters (e.g., "en", "zh", "es")
10 | * - Script code: 4 letters with preserved case (e.g., "Hans", "hans", "Cyrl")
11 | * - Region code: 2-3 uppercase letters or digits (e.g., "US", "CN", "123")
12 | *
13 | * Matches locale strings in the format: language[-_]script?[-_]region?
14 | *
15 | * Groups:
16 | * 1. Language code (2-3 lowercase letters)
17 | * 2. Script code (4 letters, optional)
18 | * 3. Region code (2-3 letters or digits, optional)
19 | *
20 | * Examples:
21 | * - "en" -> language: "en"
22 | * - "en-US" -> language: "en", region: "US"
23 | * - "zh-Hans-CN" -> language: "zh", script: "Hans", region: "CN"
24 | * - "sr_Cyrl_RS" -> language: "sr", script: "Cyrl", region: "RS"
25 | *
26 | * Note: The parser automatically normalizes case before applying this regex:
27 | * - Language codes are converted to lowercase
28 | * - Script codes preserve their original case
29 | * - Region codes are converted to uppercase
30 | */
31 | export const LOCALE_REGEX =
32 | /^([a-z]{2,3})(?:[-_]([A-Za-z]{4}))?(?:[-_]([A-Z]{2}|[0-9]{3}))?$/;
33 |
```
--------------------------------------------------------------------------------
/packages/cli/src/cli/loaders/xcode-stringsdict.ts:
--------------------------------------------------------------------------------
```typescript
1 | import plist from "plist";
2 | import { ILoader } from "./_types";
3 | import { createLoader } from "./_utils";
4 | import { CLIError } from "../utils/errors";
5 |
6 | const emptyData = [
7 | '<?xml version="1.0" encoding="UTF-8"?>',
8 | '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
9 | '<plist version="1.0">',
10 | "<dict/>",
11 | "</plist>",
12 | ].join("\n");
13 |
14 | export default function createXcodeStringsdictLoader(): ILoader<
15 | string,
16 | Record<string, any>
17 | > {
18 | return createLoader({
19 | async pull(locale, input) {
20 | try {
21 | const parsed = plist.parse(input || emptyData);
22 | if (typeof parsed !== "object" || parsed === null) {
23 | throw new CLIError({
24 | message: "Invalid .stringsdict format",
25 | docUrl: "invalidStringDict",
26 | });
27 | }
28 | return parsed as Record<string, any>;
29 | } catch (error: any) {
30 | throw new CLIError({
31 | message: `Invalid .stringsdict format: ${error.message}`,
32 | docUrl: "invalidStringDict",
33 | });
34 | }
35 | },
36 | async push(locale, payload) {
37 | const plistContent = plist.build(payload);
38 | return plistContent;
39 | },
40 | });
41 | }
42 |
```
--------------------------------------------------------------------------------
/packages/cli/src/cli/loaders/properties.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { ILoader } from "./_types";
2 | import { createLoader } from "./_utils";
3 |
4 | export default function createPropertiesLoader(): ILoader<
5 | string,
6 | Record<string, any>
7 | > {
8 | return createLoader({
9 | async pull(locale, text) {
10 | const result: Record<string, string> = {};
11 | const lines = text.split("\n");
12 |
13 | for (const line of lines) {
14 | const trimmed = line.trim();
15 |
16 | // Skip empty lines and comments
17 | if (isSkippableLine(trimmed)) {
18 | continue;
19 | }
20 |
21 | const { key, value } = parsePropertyLine(trimmed);
22 | if (key) {
23 | result[key] = value;
24 | }
25 | }
26 |
27 | return result;
28 | },
29 | async push(locale, payload) {
30 | const result = Object.entries(payload)
31 | .filter(([_, value]) => value != null)
32 | .map(([key, value]) => `${key}=${value}`)
33 | .join("\n");
34 |
35 | return result;
36 | },
37 | });
38 | }
39 |
40 | function isSkippableLine(line: string): boolean {
41 | return !line || line.startsWith("#");
42 | }
43 |
44 | function parsePropertyLine(line: string): { key: string; value: string } {
45 | const [key, ...valueParts] = line.split("=");
46 | return {
47 | key: key?.trim() || "",
48 | value: valueParts.join("=").trim(),
49 | };
50 | }
51 |
```
--------------------------------------------------------------------------------
/demo/next-app/src/app/layout.tsx:
--------------------------------------------------------------------------------
```typescript
1 | import type { Metadata } from "next";
2 | import { Geist, Geist_Mono } from "next/font/google";
3 | import "./globals.css";
4 |
5 | // Compiler: add imports
6 | import { LocaleSwitcher } from "lingo.dev/react/client";
7 | import { LingoProvider, loadDictionary } from "lingo.dev/react/rsc";
8 |
9 | const geistSans = Geist({
10 | variable: "--font-geist-sans",
11 | subsets: ["latin"],
12 | });
13 |
14 | const geistMono = Geist_Mono({
15 | variable: "--font-geist-mono",
16 | subsets: ["latin"],
17 | });
18 |
19 | export const metadata: Metadata = {
20 | title: "Create Next App",
21 | description: "Generated by create next app",
22 | };
23 |
24 | export default function RootLayout({
25 | children,
26 | }: Readonly<{
27 | children: React.ReactNode;
28 | }>) {
29 | // Compiler: wrap with LingoProvider and render LocaleSwitcher
30 | return (
31 | <LingoProvider loadDictionary={(locale) => loadDictionary(locale)}>
32 | <html lang="en">
33 | <body
34 | className={`${geistSans.variable} ${geistMono.variable} antialiased`}
35 | >
36 | <div className="absolute top-2 right-3">
37 | <LocaleSwitcher
38 | locales={["en", "es", "zh", "ja", "fr", "de", "ru", "ar", "ko"]}
39 | />
40 | </div>
41 | {children}
42 | </body>
43 | </html>
44 | </LingoProvider>
45 | );
46 | }
47 |
```
--------------------------------------------------------------------------------
/packages/cli/tests/mock-storage.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { vi } from "vitest";
2 |
3 | // Types
4 | interface MockStorage {
5 | clear(): void;
6 | set(files: Record<string, string>): void;
7 | }
8 |
9 | // Global storage type
10 | declare global {
11 | var __mockStorage: Record<string, string>;
12 | }
13 |
14 | // Initialize global storage
15 | globalThis.__mockStorage = {};
16 |
17 | // Create mock storage singleton
18 | export const mockStorage: MockStorage = {
19 | clear: () => {
20 | globalThis.__mockStorage = {};
21 | },
22 | set: (files: Record<string, string>) => {
23 | mockStorage.clear();
24 | Object.entries(files).forEach(([path, content]) => {
25 | const fullPath = `${process.cwd()}/${path}`;
26 | globalThis.__mockStorage[fullPath] = content;
27 | });
28 | },
29 | };
30 |
31 | // Setup fs mock
32 | vi.mock("fs/promises", () => ({
33 | default: {
34 | readFile: vi.fn(async (path: string) => {
35 | const content = globalThis.__mockStorage[path];
36 | if (!content) throw new Error(`File not found: ${path}`);
37 | return content;
38 | }),
39 | writeFile: vi.fn((path, content) => {
40 | globalThis.__mockStorage[path] = content;
41 | return Promise.resolve();
42 | }),
43 | mkdir: vi.fn(),
44 | access: vi.fn((path) => {
45 | return globalThis.__mockStorage[path]
46 | ? Promise.resolve()
47 | : Promise.reject(new Error("ENOENT"));
48 | }),
49 | },
50 | }));
51 |
```
--------------------------------------------------------------------------------
/packages/cli/src/cli/loaders/dato/_base.ts:
--------------------------------------------------------------------------------
```typescript
1 | import Z from "zod";
2 |
3 | // DatoCMS config
4 | export const datoConfigSchema = Z.object({
5 | project: Z.string(),
6 | models: Z.record(
7 | Z.string(),
8 | Z.object({
9 | records: Z.array(Z.string()).optional(),
10 | fields: Z.array(Z.string()).optional(),
11 | }),
12 | ),
13 | });
14 |
15 | export type DatoConfig = Z.infer<typeof datoConfigSchema>;
16 |
17 | // DatoCMS settings
18 | export const datoSettingsSchema = Z.object({
19 | auth: Z.object({
20 | apiKey: Z.string(),
21 | }),
22 | });
23 |
24 | export type DatoSettings = Z.infer<typeof datoSettingsSchema>;
25 |
26 | export const DEFAULT_LOCALE = "en";
27 |
28 | //
29 |
30 | export type DatoRecordPayload = {
31 | [field: string]: {
32 | [locale: string]: DatoValue;
33 | };
34 | };
35 |
36 | export type DatoValue = DatoSimpleValue | DatoComplexValue;
37 | export type DatoSimpleValue = DatoPrimitive | DastDocument;
38 | export type DatoComplexValue = DatoBlock | DatoBlock[];
39 |
40 | export type DatoPrimitive = null | string | boolean | number;
41 |
42 | export type DastDocument = {
43 | schema: "dast";
44 | document: DastDocumentNode;
45 | };
46 |
47 | export type DastDocumentNode = {
48 | type: "root" | "span" | "paragraph";
49 | value?: DatoPrimitive;
50 | children?: DastDocumentNode[];
51 | };
52 |
53 | export type DatoBlock = {
54 | id?: string;
55 | type: "item";
56 | attributes: Record<string, DatoSimpleValue>;
57 | relationships: any;
58 | };
59 |
```
--------------------------------------------------------------------------------
/packages/cli/src/cli/loaders/ensure-key-order.ts:
--------------------------------------------------------------------------------
```typescript
1 | import _ from "lodash";
2 | import { ILoader } from "./_types";
3 | import { createLoader } from "./_utils";
4 |
5 | export default function createEnsureKeyOrderLoader(): ILoader<
6 | Record<string, any>,
7 | Record<string, any>
8 | > {
9 | return createLoader({
10 | pull: async (_locale, input) => {
11 | return input;
12 | },
13 | push: async (_locale, data, originalInput) => {
14 | if (!originalInput || !data) {
15 | return data;
16 | }
17 | return reorderKeys(data, originalInput);
18 | },
19 | });
20 | }
21 |
22 | function reorderKeys(
23 | data: Record<string, any>,
24 | originalInput: Record<string, any>,
25 | ): Record<string, any> {
26 | if (_.isArray(originalInput) && _.isArray(data)) {
27 | // If both are arrays, recursively reorder keys in each element
28 | return data.map((item, idx) => reorderKeys(item, originalInput[idx] ?? {}));
29 | }
30 | if (!_.isObject(data) || _.isArray(data) || _.isDate(data)) {
31 | return data;
32 | }
33 |
34 | const orderedData: Record<string, any> = {};
35 | const originalKeys = Object.keys(originalInput);
36 | const dataKeys = new Set(Object.keys(data));
37 |
38 | for (const key of originalKeys) {
39 | if (dataKeys.has(key)) {
40 | orderedData[key] = reorderKeys(data[key], originalInput[key]);
41 | dataKeys.delete(key);
42 | }
43 | }
44 |
45 | return orderedData;
46 | }
47 |
```
--------------------------------------------------------------------------------
/packages/react/src/client/context.spec.tsx:
--------------------------------------------------------------------------------
```typescript
1 | import { describe, it, expect } from "vitest";
2 | import React from "react";
3 | import { render, screen } from "@testing-library/react";
4 | import { LingoContext, useLingo } from "./context";
5 |
6 | describe("client/context", () => {
7 | describe("useLingo", () => {
8 | it("has default dictionary shape and useLingo returns it", () => {
9 | const Probe = () => {
10 | const lingo = useLingo();
11 | return (
12 | <div
13 | data-testid="probe"
14 | data-dict-empty={
15 | Object.keys(lingo.dictionary).length === 0 ? "yes" : "no"
16 | }
17 | />
18 | );
19 | };
20 | render(<Probe />);
21 | const el = screen.getByTestId("probe");
22 | expect(el.getAttribute("data-dict-empty")).toBe("yes");
23 | });
24 |
25 | it("provides value via context provider", () => {
26 | const Probe = () => {
27 | const lingo = useLingo();
28 | return (
29 | <div data-testid="probe" data-locale={lingo.dictionary.locale} />
30 | );
31 | };
32 | render(
33 | <LingoContext.Provider value={{ dictionary: { locale: "it" } }}>
34 | <Probe />
35 | </LingoContext.Provider>,
36 | );
37 | const el = screen.getByTestId("probe");
38 | expect(el.getAttribute("data-locale")).toBe("it");
39 | });
40 | });
41 | });
42 |
```
--------------------------------------------------------------------------------
/packages/compiler/src/utils/hash.spec.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { describe, it, expect } from "vitest";
2 | import { createPayload } from "../_base";
3 | import traverse from "@babel/traverse";
4 | import * as t from "@babel/types";
5 | import { getJsxElementHash, getJsxAttributeValueHash } from "./hash";
6 |
7 | function getFirstJsx(pathCode: string) {
8 | const payload = createPayload({
9 | code: pathCode,
10 | params: {} as any,
11 | relativeFilePath: "x.tsx",
12 | });
13 | let found: any;
14 | traverse(payload.ast, {
15 | JSXElement(p) {
16 | if (!found) found = p;
17 | },
18 | });
19 | return found as any;
20 | }
21 |
22 | describe("utils/hash", () => {
23 | describe("getJsxElementHash", () => {
24 | it("produces a consistent non-empty hash for same input", () => {
25 | const a = getFirstJsx(`const A = () => <div>hello world</div>`);
26 | const first = getJsxElementHash(a);
27 | const second = getJsxElementHash(a);
28 | expect(first).toBeTypeOf("string");
29 | expect(first.length).toBeGreaterThan(0);
30 | expect(second).toEqual(first);
31 | });
32 | });
33 |
34 | describe("getJsxAttributeValueHash", () => {
35 | it("attribute hash returns empty for empty string and stable otherwise", () => {
36 | expect(getJsxAttributeValueHash("")).toBe("");
37 | expect(getJsxAttributeValueHash("x")).toBe(getJsxAttributeValueHash("x"));
38 | });
39 | });
40 | });
41 |
```