@better-i18n/vite 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/plugin.d.ts +57 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +135 -0
- package/dist/plugin.js.map +1 -0
- package/package.json +58 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,YAAY,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/plugin.d.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { Plugin } from "vite";
|
|
2
|
+
export interface BetterI18nPluginOptions {
|
|
3
|
+
/**
|
|
4
|
+
* Project identifier in "org/project" format.
|
|
5
|
+
* This is the single source of truth — `BetterI18nProvider` reads it
|
|
6
|
+
* from the injected `<script>` tag, so you don't need to pass it as a prop.
|
|
7
|
+
*
|
|
8
|
+
* @example "acme/dashboard"
|
|
9
|
+
*/
|
|
10
|
+
project: string;
|
|
11
|
+
/**
|
|
12
|
+
* Default locale when no locale is detected from URL, cookie, or Accept-Language.
|
|
13
|
+
* @default "en"
|
|
14
|
+
*/
|
|
15
|
+
defaultLocale?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Cookie name used to persist the user's locale preference.
|
|
18
|
+
* @default "locale"
|
|
19
|
+
*/
|
|
20
|
+
localeCookie?: string;
|
|
21
|
+
/**
|
|
22
|
+
* CDN base URL override.
|
|
23
|
+
* @default "https://cdn.better-i18n.com"
|
|
24
|
+
*/
|
|
25
|
+
cdnBaseUrl?: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Vite plugin for Better i18n — server-side translation injection.
|
|
29
|
+
*
|
|
30
|
+
* Fetches translations on the server (dev server or build) and injects
|
|
31
|
+
* them into the HTML as a `<script id="__better_i18n__">` tag.
|
|
32
|
+
* `BetterI18nProvider` reads this data synchronously on first render,
|
|
33
|
+
* eliminating FOUC (Flash of Untranslated Content).
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```ts
|
|
37
|
+
* // vite.config.ts
|
|
38
|
+
* import { betterI18n } from '@better-i18n/vite'
|
|
39
|
+
*
|
|
40
|
+
* export default defineConfig({
|
|
41
|
+
* plugins: [
|
|
42
|
+
* betterI18n({ project: 'acme/dashboard' }),
|
|
43
|
+
* react(),
|
|
44
|
+
* ],
|
|
45
|
+
* })
|
|
46
|
+
* ```
|
|
47
|
+
*
|
|
48
|
+
* Then in your app — no project/locale/messages props needed:
|
|
49
|
+
* ```tsx
|
|
50
|
+
* <BetterI18nProvider>
|
|
51
|
+
* <LocaleDropdown />
|
|
52
|
+
* <App />
|
|
53
|
+
* </BetterI18nProvider>
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
export declare function betterI18n(options: BetterI18nPluginOptions): Plugin;
|
|
57
|
+
//# sourceMappingURL=plugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAEnC,MAAM,WAAW,uBAAuB;IACtC;;;;;;OAMG;IACH,OAAO,EAAE,MAAM,CAAC;IAEhB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,uBAAuB,GAAG,MAAM,CA8EnE"}
|
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { createI18nCore, normalizeLocale } from "@better-i18n/core";
|
|
2
|
+
/**
|
|
3
|
+
* Vite plugin for Better i18n — server-side translation injection.
|
|
4
|
+
*
|
|
5
|
+
* Fetches translations on the server (dev server or build) and injects
|
|
6
|
+
* them into the HTML as a `<script id="__better_i18n__">` tag.
|
|
7
|
+
* `BetterI18nProvider` reads this data synchronously on first render,
|
|
8
|
+
* eliminating FOUC (Flash of Untranslated Content).
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* // vite.config.ts
|
|
13
|
+
* import { betterI18n } from '@better-i18n/vite'
|
|
14
|
+
*
|
|
15
|
+
* export default defineConfig({
|
|
16
|
+
* plugins: [
|
|
17
|
+
* betterI18n({ project: 'acme/dashboard' }),
|
|
18
|
+
* react(),
|
|
19
|
+
* ],
|
|
20
|
+
* })
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* Then in your app — no project/locale/messages props needed:
|
|
24
|
+
* ```tsx
|
|
25
|
+
* <BetterI18nProvider>
|
|
26
|
+
* <LocaleDropdown />
|
|
27
|
+
* <App />
|
|
28
|
+
* </BetterI18nProvider>
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export function betterI18n(options) {
|
|
32
|
+
const { project, defaultLocale = "en", localeCookie = "locale", cdnBaseUrl, } = options;
|
|
33
|
+
// Singleton core instance — TtlCache shared across requests
|
|
34
|
+
const i18nCore = createI18nCore({
|
|
35
|
+
project,
|
|
36
|
+
defaultLocale,
|
|
37
|
+
cdnBaseUrl,
|
|
38
|
+
});
|
|
39
|
+
// Request-scoped locale detection (middleware → transformIndexHtml bridge)
|
|
40
|
+
const requestLocales = new Map();
|
|
41
|
+
return {
|
|
42
|
+
name: "better-i18n",
|
|
43
|
+
configureServer(server) {
|
|
44
|
+
// Middleware runs BEFORE Vite serves HTML — detects locale from request
|
|
45
|
+
server.middlewares.use((req, _res, next) => {
|
|
46
|
+
if (!req.url)
|
|
47
|
+
return next();
|
|
48
|
+
const locale = detectLocaleFromRequest(req.headers.cookie, req.headers["accept-language"], req.url, localeCookie, defaultLocale);
|
|
49
|
+
requestLocales.set(req.url, locale);
|
|
50
|
+
next();
|
|
51
|
+
});
|
|
52
|
+
},
|
|
53
|
+
transformIndexHtml: {
|
|
54
|
+
order: "pre",
|
|
55
|
+
handler: async (_html, ctx) => {
|
|
56
|
+
// Resolve locale: middleware-detected → default
|
|
57
|
+
const requestUrl = ctx.originalUrl || ctx.path;
|
|
58
|
+
const locale = requestLocales.get(requestUrl) || defaultLocale;
|
|
59
|
+
requestLocales.delete(requestUrl);
|
|
60
|
+
// Fetch translations + languages server-side (TtlCache makes this fast)
|
|
61
|
+
const [messages, languages] = await Promise.all([
|
|
62
|
+
i18nCore.getMessages(locale),
|
|
63
|
+
i18nCore.getLanguages(),
|
|
64
|
+
]);
|
|
65
|
+
// Inject as <script> tag — provider reads this synchronously
|
|
66
|
+
const payload = JSON.stringify({
|
|
67
|
+
project,
|
|
68
|
+
locale,
|
|
69
|
+
messages,
|
|
70
|
+
languages,
|
|
71
|
+
});
|
|
72
|
+
return {
|
|
73
|
+
html: _html,
|
|
74
|
+
tags: [
|
|
75
|
+
{
|
|
76
|
+
tag: "script",
|
|
77
|
+
attrs: {
|
|
78
|
+
id: "__better_i18n__",
|
|
79
|
+
type: "application/json",
|
|
80
|
+
},
|
|
81
|
+
children: payload,
|
|
82
|
+
injectTo: "head",
|
|
83
|
+
},
|
|
84
|
+
],
|
|
85
|
+
};
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
// ─── Locale Detection ─────────────────────────────────────────────
|
|
91
|
+
/**
|
|
92
|
+
* Detect locale from request context.
|
|
93
|
+
* Priority: URL path segment → cookie → Accept-Language → default
|
|
94
|
+
*/
|
|
95
|
+
function detectLocaleFromRequest(cookieHeader, acceptLanguageHeader, url, cookieName, defaultLocale) {
|
|
96
|
+
// 1. URL path locale (e.g., /tr/about → "tr")
|
|
97
|
+
const pathname = url.split("?")[0] || "/";
|
|
98
|
+
const segments = pathname.split("/").filter(Boolean);
|
|
99
|
+
const firstSegment = segments[0];
|
|
100
|
+
if (firstSegment && /^[a-z]{2}(-[a-z]{2,4})?$/i.test(firstSegment)) {
|
|
101
|
+
return normalizeLocale(firstSegment);
|
|
102
|
+
}
|
|
103
|
+
// 2. Cookie locale
|
|
104
|
+
const cookieLocale = parseCookieValue(cookieHeader, cookieName);
|
|
105
|
+
if (cookieLocale) {
|
|
106
|
+
return normalizeLocale(cookieLocale);
|
|
107
|
+
}
|
|
108
|
+
// 3. Accept-Language header (first match)
|
|
109
|
+
if (acceptLanguageHeader) {
|
|
110
|
+
const preferred = parseAcceptLanguageFirst(acceptLanguageHeader);
|
|
111
|
+
if (preferred) {
|
|
112
|
+
return normalizeLocale(preferred);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return defaultLocale;
|
|
116
|
+
}
|
|
117
|
+
/** Extract a single cookie value by name */
|
|
118
|
+
function parseCookieValue(header, name) {
|
|
119
|
+
if (!header)
|
|
120
|
+
return null;
|
|
121
|
+
const match = header.match(new RegExp(`(?:^|;\\s*)${name}=([^;]*)`));
|
|
122
|
+
return match?.[1]?.trim() || null;
|
|
123
|
+
}
|
|
124
|
+
/** Extract the highest-priority language from Accept-Language header */
|
|
125
|
+
function parseAcceptLanguageFirst(header) {
|
|
126
|
+
const first = header.split(",")[0];
|
|
127
|
+
if (!first)
|
|
128
|
+
return null;
|
|
129
|
+
const tag = first.split(";")[0]?.trim();
|
|
130
|
+
if (!tag || tag === "*")
|
|
131
|
+
return null;
|
|
132
|
+
// Return base language (e.g., "tr-TR" → "tr")
|
|
133
|
+
return tag.split("-")[0] || null;
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=plugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@better-i18n/vite",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Vite plugin for Better i18n — SSR translation injection, zero FOUC",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/better-i18n/better-i18n.git",
|
|
9
|
+
"directory": "packages/vite"
|
|
10
|
+
},
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/better-i18n/better-i18n/issues"
|
|
13
|
+
},
|
|
14
|
+
"homepage": "https://github.com/better-i18n/better-i18n/tree/main/packages/vite",
|
|
15
|
+
"type": "module",
|
|
16
|
+
"main": "./dist/index.js",
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"bun": "./src/index.ts",
|
|
22
|
+
"default": "./dist/index.js"
|
|
23
|
+
},
|
|
24
|
+
"./package.json": "./package.json"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist",
|
|
28
|
+
"package.json",
|
|
29
|
+
"README.md"
|
|
30
|
+
],
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"access": "public"
|
|
33
|
+
},
|
|
34
|
+
"keywords": [
|
|
35
|
+
"i18n",
|
|
36
|
+
"localization",
|
|
37
|
+
"vite",
|
|
38
|
+
"vite-plugin",
|
|
39
|
+
"ssr",
|
|
40
|
+
"better-i18n"
|
|
41
|
+
],
|
|
42
|
+
"scripts": {
|
|
43
|
+
"build": "tsc",
|
|
44
|
+
"typecheck": "tsc --noEmit",
|
|
45
|
+
"clean": "rm -rf dist",
|
|
46
|
+
"prepublishOnly": "bun run build"
|
|
47
|
+
},
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"@better-i18n/core": "^0.3.0"
|
|
50
|
+
},
|
|
51
|
+
"peerDependencies": {
|
|
52
|
+
"vite": ">=5.0.0"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"vite": "^6.3.5",
|
|
56
|
+
"typescript": "~5.9.2"
|
|
57
|
+
}
|
|
58
|
+
}
|