inertia-lang-react
v0.0.1
Published
Inertia + React i18n glue: Lingui <-> Laravel JSON, with extract/compile commands.
Maintainers
Readme
inertia-lang-react (draft)
Glue for Laravel + Inertia (React) + Lingui with a single SSoT: /lang/<locale>.json.
What it does
inertia:lang-extract- (optional) scans Laravel (Artisan) into
/lang/*.json - runs
lingui extractto updateresources/js/i18n/<loc>/messages.po - merges PO → /lang/.json (adds missing keys only)
- (optional) scans Laravel (Artisan) into
inertia:lang-compile- fills PO from /lang/.json
- runs
lingui compileso your React bundle ships the latest catalogs
Install
Minimal packages you should install (example):
# npm (JS tooling)
npm install --save-dev inertia-lang-react @lingui/cli @lingui/format-po @lingui/vite-plugin @lingui/core @lingui/macro @vitejs/plugin-react-swc
# Laravel backend extractor (optional but recommended for scanning PHP views/controllers)
composer require --dev kkomelin/laravel-translatable-string-exporterNotes:
inertia-lang-reactprovides the glue scripts and an optional Vite watcher plugin.@lingui/cliand@lingui/format-poare required to run Lingui extract/compile in PO format.- Use
@vitejs/plugin-react-swcfor React + SWC integration (recommended for performance with Lingui).
Below are two files you should add to your project root (exact paths and contents shown). These make Lingui and Vite work together with this package.
Files to add
lingui.config.ts(project root)
Create lingui.config.ts at the repository root with the following contents (PO formatter):
// lingui.config.ts
import { defineConfig } from '@lingui/cli'
import { formatter } from '@lingui/format-po'
export default defineConfig({
// list the locales your app supports
locales: ['en', 'cs'],
sourceLocale: 'en',
// where Lingui stores its message catalogs (PO files)
catalogs: [
{
path: 'resources/js/i18n/{locale}/messages',
include: ['resources/js'],
},
],
// use PO format so translators work in gettext editors
format: formatter(),
})Place this file at the project root so @lingui/vite-plugin and @lingui/cli can find it.
vite.config.ts(project root)
An example Vite config that uses the SWC React plugin together with the Lingui plugin and the optional inertia-lang-react watcher plugin:
// vite.config.ts (project root)
import { defineConfig } from 'vite'
import laravel from 'laravel-vite-plugin'
import { lingui } from '@lingui/vite-plugin'
import react from '@vitejs/plugin-react-swc'
import tailwindcss from '@tailwindcss/vite'
// If you want the local inertia-lang watcher, import it from the package
// (this package exports a Vite plugin as its default export)
import inertiaLang from './packages/inertia-lang-react/index.mjs'
export default defineConfig({
plugins: [
// optional: runs extract/compile on file changes in development
inertiaLang({ enabled: true, locales: ['en', 'cs'], runOnStart: true }),
laravel({
input: ['resources/css/app.css', 'resources/js/app.tsx'],
ssr: 'resources/js/ssr.tsx',
refresh: true,
}),
// Use SWC React plugin (recommended with Lingui + SWC plugin)
react({
plugins: [['@lingui/swc-plugin', {}]],
}),
// Let Lingui's vite plugin pick up the `lingui.config.ts` we added
lingui(),
tailwindcss(),
],
esbuild: {
jsx: 'automatic',
},
})- You need to share the actual locale from Laravel to React via Inertia props. A simple way is to add a middleware that shares the current locale:
// app/Http/Midddelware/HandleInertiaRequests.php
public function share(Request $request): array
{
return [
...
'locale' => app()->getLocale(),
...
];
}
....
4) You need to instrument the app with the locale
Notes on the `vite.config.ts` example:
- `inertiaLang(...)` is optional — enable it if you want the package's watcher that runs extract/compile during dev HMR.
- `lingui()` picks up `lingui.config.ts` from the project root. If you prefer a JS file, you can use `lingui.config.js` instead.
Logging / debug mode
--------------------
By default this package runs in minimal logging mode (keeps output concise so it does not overwhelm surrounding process logs). If you want verbose output for debugging, set the environment variable `INERTIA_LANG_DEBUG=1` (or `true`).
When `INERTIA_LANG_DEBUG` is set the package emits full info/debug logging. When unset it defaults to minimal output appropriate for distributed usage (CI, daemonized dev orchestrators, etc.). for example `INERTIA_LANG_DEBUG=1 composer run dev`
The watcher plugin will pass `--minimal` to the child extract/compile scripts when the plugin option `minimal` is configured or when the logger chooses minimal mode via the environment.
## Lingui config hints
If you prefer the config in JavaScript instead of TypeScript, create `lingui.config.js` with the same exported object. The important fields are `locales`, `sourceLocale`, `catalogs` and `format` set to `formatter()`.
## Runtime loader (how to dynamically load compiled PO messages)
You can dynamically import .po files at runtime and activate Lingui like this:
```ts
// resources/js/i18n/index.ts
import { i18n } from '@lingui/core'
export async function dynamicActivate(locale: string) {
const { messages } = await import(`./${locale}/messages.po`)
i18n.load(locale, messages)
i18n.activate(locale)
}Runtime loader:
// resources/js/i18n/index.ts
import { i18n } from "@lingui/core"
export async function dynamicActivate(locale: string) {
const { messages } = await import(`./${locale}/messages.po`)
i18n.load(locale, messages)
i18n.activate(locale)
}Usage
# Extract (PO -> /lang/*.json)
npm run inertia:lang-extract -- --locales=en,cs --withBackendScan
# Translate in /lang/cs.json
# Compile (/lang/*.json -> PO -> Lingui compile)
npm run inertia:lang-compile -- --locales=en,csOptional flags (examples):
--poRoot,--langDir--fallbackEn(seed non-en JSON values with English msgid)--augmentPo(during compile: create missing PO entries from JSON)--failOnMissing(CI guard)--dryRun,--debug,--verbose,--minimal--artisanExportCommand="php artisan translatable:export en,cs"--linguiExtractArgs="--catalogs-path …",--linguiCompileArgs="--strict"
Config file: put inertialang.config.json (or inertialang.config.js) in your project root to avoid long commands.
Notes
/lang/*.jsonis the SSoT for translators.- PO catalogs remain the Lingui working format for extraction & compilation.
- For dev HMR, the Lingui Vite plugin + dynamic import of
.pofiles gives hot reload. (We’ll add extra helpers later.)
Vite watcher plugin (dev)
This package ships a small opt-in Vite plugin that watches your sources, PO and JSON language files and runs the extract/compile flows automatically during vite dev. It's intentionally opt-in so you can enable it only in development.
Add it to your vite.config.ts (or .js):
// vite.config.ts
import { defineConfig } from 'vite'
import inertiaLang from 'inertia-lang-react'
export default defineConfig({
plugins: [
inertiaLang({ enabled: true, locales: ['en','cs'], runOnStart: true })
]
})Developer notes
What I changed
- Fixed and hardened the Vite watcher plugin implementation in
vite-plugin-inertia-lang-react.mjsby adding missing state variables and small helper functions used by the watcher. These additions remove undefined references and make the watcher self-contained and robust:- Added
lastExtractFinish,lastCompileFinish,lastRunFinish,lastPoSnapshots,lastJsonSnapshots,sourceKeyCache,ignoredFiles,suppressUntil,suppressMs, andignoreWindow. - Ensure the scheduler records the finish timestamps (
lastExtractFinish/lastCompileFinish). - Added small helpers:
setsEqual,extractLinguiKeysOrTexts(conservative regex-based extractor),markIgnored,cleanupIgnored, andseedSourceCache(no-op seeder to avoid heavy startup I/O).
- Added
Why
- The plugin referenced several variables and helpers that were not defined in the file, which could lead to runtime errors in development. The changes are intentionally minimal and safe: they do not alter the plugin's external behavior, only make its internal logic deterministic and resilient.
How to run tests
- From the package directory run:
cd packages/inertia-lang-react
npm ci
npm test # interactive/watch mode (default)
# or for CI-style non-watch run:
npx vitest runTest status
- All existing tests passed after the changes (2 test files, 2 tests). The change is low-risk and covered by the existing test suite.
Suggested next steps (optional)
- Replace the simple regex extractor
extractLinguiKeysOrTextswith an AST-based extractor (Acorn/Esprima) for robust i18n key/text extraction in complex code. - Add unit tests for the plugin watcher logic (simulate events, snapshots) to ensure behavior remains stable across Node versions.
- Add a
test:ciscript topackage.jsonto runvitest runin CI (prevents watch mode). - Consider adding basic linting (ESLint) and running a CI matrix to catch regressions early.
If you want, I can implement any of the suggested next steps (AST extractor, tests for plugin, or CI script). Just tell me which to prioritize.
Note: you can import the plugin from the package root (import inertiaLang from 'inertia-lang-react') — the package exports the plugin as the default entry.
Options
enabled(boolean) — default false. Only active when true.poRoot,langDir— override the defaults (resources/js/i18n,lang).locales— list of locales to pass to the underlying scripts.run—extract,compile, orboth(defaultboth).runOnStart— run once when Vite starts.
Behavior
- When JS/TS source files change ->
extract(runsbin/extract.mjs). - When PO files change ->
extract(merges PO -> JSON). - When
/lang/*.jsonfiles change ->compile(fills PO from JSON +lingui compile).
The plugin triggers a full-reload after running the scripts so your app picks up the latest messages.
Additional notes
- This repo in its first version includes many AI-generated code to test it as a PoC. If this repo starts being used a continuous effort should be made to review and clean up the code.
- if you forgot laravel commands to publish lang directory use
php artisan lang - if you need to remove php package to not use php keys in the final json (highly recommended) use
php artisan vendor:publish --provider="KKomelin\TranslatableStringExporter\Providers\ExporterServiceProvider"and set exclude-translation-keys to true
