Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Laravel i18next is a powerful Vite plugin that bridges the gap between Laravel's
- Automatic conversion during development and build processes
- Hot Module Replacement (HMR) support for seamless development
- Export multiple translation files per language (namespaces)
- Compatible with Vite 6+

## Why i18next?

Expand Down
4,954 changes: 3,154 additions & 1,800 deletions package-lock.json

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
"@types/node": "^22.7.4",
"@types/react": "^18.3.10",
"@types/react-dom": "^18.3.0",
"@vitejs/plugin-react": "^4.3.1",
"@vitejs/plugin-react": "^4.4.1",
"@vitest/coverage-v8": "^2.1.1",
"chokidar": "^4.0.1",
"conventional-changelog-conventionalcommits": "^8.0.0",
Expand All @@ -68,13 +68,13 @@
"react-router-dom": "^6.26.2",
"semantic-release": "^24.1.2",
"typescript": "^5.6.2",
"vite": "^5.4.8",
"vite": "^6.0.0",
"vite-plugin-dts": "^4.2.3",
"vite-plugin-static-copy": "^1.0.6",
"vite-plugin-static-copy": "^2.0.0",
"vitest": "^2.1.1"
},
"peerDependencies": {
"chokidar": ">=4",
"vite": ">=5"
"vite": ">=6"
}
}
80 changes: 54 additions & 26 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { Plugin, ViteDevServer } from 'vite';
import convertLaravelTranslations from './utils/convertLaravelTranslations';
import * as path from 'path';
import { normalizePath } from 'vite';
import chokidar, { FSWatcher } from 'chokidar';
import chokidar, { FSWatcher } from "chokidar";
import * as path from "path";
import { normalizePath, Plugin, ViteDevServer } from "vite";
import convertLaravelTranslations from "./utils/convertLaravelTranslations";

const laravelI18nextPlugin = (options: {
laravelLangPath: string;
Expand All @@ -11,55 +10,84 @@ const laravelI18nextPlugin = (options: {
let watcher: FSWatcher | null = null;

return {
name: 'vite-plugin-laravel-i18next',
name: "vite-plugin-laravel-i18next",

buildStart: async () => {
await convertLaravelTranslations(options.laravelLangPath, options.outputPath);
async buildStart() {
await convertLaravelTranslations(
options.laravelLangPath,
options.outputPath
);
},

configureServer(server) {
const normalizedLaravelLangPath = normalizePath(path.resolve(options.laravelLangPath));
const normalizedLaravelLangPath = normalizePath(
path.resolve(options.laravelLangPath)
);

watcher = chokidar.watch(normalizedLaravelLangPath, {
ignoreInitial: true,
awaitWriteFinish: {
stabilityThreshold: 100,
pollInterval: 100
}
pollInterval: 100,
},
});

watcher.on('add', async (file) => {
await handleFileChange(file, server);
watcher.on("add", async (file) => {
await handleTranslationFileChange(file, server);
});

watcher.on('change', async (file) => {
await handleFileChange(file, server);
watcher.on("change", async (file) => {
await handleTranslationFileChange(file, server);
});
},

handleHotUpdate: async ({ file, server }) => {
return handleFileChange(file, server);
handleHotUpdate(ctx) {
const { file, server, modules } = ctx;

const isTranslationFile =
isFileInLangPath(file) &&
(file.endsWith(".php") || file.endsWith(".json"));

if (isTranslationFile) {
handleTranslationFileChange(file, server);
// In Vite 6, it's recommended to return void for async operations
// that don't immediately affect modules
return [];
}

// For non-translation files, return the original modules to preserve normal HMR
return modules;
},

closeBundle() {
if (watcher) {
watcher.close();
}
}
},
};

async function handleFileChange(file: string, server: ViteDevServer) {
function isFileInLangPath(file: string): boolean {
const relativePath = path.relative(options.laravelLangPath, file);
const isTranslationFile = !relativePath.startsWith('..') &&
(file.endsWith('.php') || file.endsWith('.json'));
return !relativePath.startsWith("..");
}

if (isTranslationFile) {
await convertLaravelTranslations(options.laravelLangPath, options.outputPath);
async function handleTranslationFileChange(
file: string,
server: ViteDevServer
) {
const isTranslationFile =
isFileInLangPath(file) &&
(file.endsWith(".php") || file.endsWith(".json"));

const module = server.moduleGraph.getModulesByFile(file);
if (isTranslationFile) {
await convertLaravelTranslations(
options.laravelLangPath,
options.outputPath
);

if (module) {
return Array.from(module);
const modules = server.moduleGraph.getModulesByFile(file);
if (modules && modules.size > 0) {
return Array.from(modules);
}
}
return [];
Expand Down
24 changes: 17 additions & 7 deletions tests/e2e/app/vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';
import laravelI18nextPlugin from '../../../src';
import react from "@vitejs/plugin-react";
import path from "path";
import { defineConfig } from "vite";
import laravelI18nextPlugin from "../../../src";

export default defineConfig({
plugins: [
react(),
laravelI18nextPlugin({
laravelLangPath: path.resolve(__dirname, '../fixtures/lang'),
outputPath: path.resolve(__dirname, 'src/locales'),
laravelLangPath: path.resolve(__dirname, "../fixtures/lang"),
outputPath: path.resolve(__dirname, "src/locales"),
}),
],
root: __dirname,
Expand All @@ -17,7 +17,17 @@ export default defineConfig({
},
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
"@": path.resolve(__dirname, "./src"),
},
},
// Add optimizeDeps for Vite 6
optimizeDeps: {
include: [
"react",
"react-dom",
"react-i18next",
"i18next",
"react-router-dom",
],
},
});
5 changes: 4 additions & 1 deletion tests/e2e/setup/triggerViteBuild.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@ import { build } from "vite";

const triggerViteBuild = async () => {
await build({
root: path.join(import.meta.dirname, '../app'),
root: path.join(import.meta.dirname, "../app"),
build: {
ssr: false,
write: false,
// Add Vite 6 specific options
target: "es2020",
minify: false,
},
});
};
Expand Down
49 changes: 33 additions & 16 deletions vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,45 @@
import { builtinModules } from 'module'
import path from 'path'
import { defineConfig } from 'vite'
import dts from 'vite-plugin-dts'
import { viteStaticCopy } from 'vite-plugin-static-copy'
import { builtinModules } from "module";
import path from "path";
import { defineConfig } from "vite";
import dts from "vite-plugin-dts";
import { viteStaticCopy } from "vite-plugin-static-copy";

export default defineConfig({
plugins: [
viteStaticCopy({
targets: ['package.json', 'README.md', 'LICENSE', 'CHANGELOG.md', 'npm-shrinkwrap.json', 'package-lock.json'].map(item => ({
src: item,
dest: './'
})),
targets: ["package.json", "README.md", "LICENSE", "CHANGELOG.md"].map(
(item) => ({
src: item,
dest: "./",
})
),
}),
dts({
tsconfigPath: "./tsconfig.build.json",
// Ensure compatibility with Vite 6
staticImport: true,
}),
dts({ tsconfigPath: './tsconfig.build.json' })
],
build: {
lib: {
formats: ['es'],
entry: path.resolve(__dirname, 'src/index.ts'),
name: 'vite-plugin-laravel-i18next',
fileName: 'index',
formats: ["es"],
entry: path.resolve(__dirname, "src/index.ts"),
name: "vite-plugin-laravel-i18next",
fileName: "index",
},
rollupOptions: {
external: [...builtinModules, 'vite', 'chokidar', 'path', 'fs', 'glob', 'php-parser'],
external: [
...builtinModules,
"vite",
"chokidar",
"path",
"fs",
"glob",
"php-parser",
],
},
// Update target for better compatibility with modern browsers
target: "es2020",
sourcemap: true,
},
})
});