next-intlayer 1.0.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/LICENSE +21 -0
- package/dist/cjs/client/index.cjs +48 -0
- package/dist/cjs/client/index.cjs.map +1 -0
- package/dist/cjs/client/index.d.ts +10 -0
- package/dist/cjs/client/useLocale.cjs +68 -0
- package/dist/cjs/client/useLocale.cjs.map +1 -0
- package/dist/cjs/client/useLocale.d.ts +12 -0
- package/dist/cjs/generateStaticParams.cjs +36 -0
- package/dist/cjs/generateStaticParams.cjs.map +1 -0
- package/dist/cjs/generateStaticParams.d.ts +7 -0
- package/dist/cjs/index.cjs +51 -0
- package/dist/cjs/index.cjs.map +1 -0
- package/dist/cjs/index.d.ts +15 -0
- package/dist/cjs/middleware/index.cjs +35 -0
- package/dist/cjs/middleware/index.cjs.map +1 -0
- package/dist/cjs/middleware/index.d.ts +2 -0
- package/dist/cjs/middleware/intlayerMiddleware.cjs +230 -0
- package/dist/cjs/middleware/intlayerMiddleware.cjs.map +1 -0
- package/dist/cjs/middleware/intlayerMiddleware.d.ts +5 -0
- package/dist/cjs/middleware/localeDetector.cjs +73 -0
- package/dist/cjs/middleware/localeDetector.cjs.map +1 -0
- package/dist/cjs/middleware/localeDetector.d.ts +6 -0
- package/dist/cjs/server/index.cjs +47 -0
- package/dist/cjs/server/index.cjs.map +1 -0
- package/dist/cjs/server/index.d.ts +10 -0
- package/dist/cjs/server/withIntlayer.cjs +76 -0
- package/dist/cjs/server/withIntlayer.cjs.map +1 -0
- package/dist/cjs/server/withIntlayer.d.ts +9 -0
- package/dist/cjs/types/NextPage.cjs +21 -0
- package/dist/cjs/types/NextPage.cjs.map +1 -0
- package/dist/cjs/types/NextPage.d.ts +13 -0
- package/dist/cjs/types/index.cjs +21 -0
- package/dist/cjs/types/index.cjs.map +1 -0
- package/dist/cjs/types/index.d.ts +4 -0
- package/dist/esm/client/index.d.mts +10 -0
- package/dist/esm/client/index.mjs +19 -0
- package/dist/esm/client/index.mjs.map +1 -0
- package/dist/esm/client/useLocale.d.mts +12 -0
- package/dist/esm/client/useLocale.mjs +37 -0
- package/dist/esm/client/useLocale.mjs.map +1 -0
- package/dist/esm/generateStaticParams.d.mts +7 -0
- package/dist/esm/generateStaticParams.mjs +5 -0
- package/dist/esm/generateStaticParams.mjs.map +1 -0
- package/dist/esm/index.d.mts +15 -0
- package/dist/esm/index.mjs +21 -0
- package/dist/esm/index.mjs.map +1 -0
- package/dist/esm/middleware/index.d.mts +2 -0
- package/dist/esm/middleware/index.mjs +2 -0
- package/dist/esm/middleware/index.mjs.map +1 -0
- package/dist/esm/middleware/intlayerMiddleware.d.mts +5 -0
- package/dist/esm/middleware/intlayerMiddleware.mjs +193 -0
- package/dist/esm/middleware/intlayerMiddleware.mjs.map +1 -0
- package/dist/esm/middleware/localeDetector.d.mts +6 -0
- package/dist/esm/middleware/localeDetector.mjs +20 -0
- package/dist/esm/middleware/localeDetector.mjs.map +1 -0
- package/dist/esm/server/index.d.mts +10 -0
- package/dist/esm/server/index.mjs +19 -0
- package/dist/esm/server/index.mjs.map +1 -0
- package/dist/esm/server/withIntlayer.d.mts +9 -0
- package/dist/esm/server/withIntlayer.mjs +40 -0
- package/dist/esm/server/withIntlayer.mjs.map +1 -0
- package/dist/esm/types/NextPage.d.mts +13 -0
- package/dist/esm/types/NextPage.mjs +1 -0
- package/dist/esm/types/NextPage.mjs.map +1 -0
- package/dist/esm/types/index.d.mts +4 -0
- package/dist/esm/types/index.mjs +1 -0
- package/dist/esm/types/index.mjs.map +1 -0
- package/package.json +105 -0
- package/src/client/index.ts +9 -0
- package/src/client/useLocale.ts +46 -0
- package/src/generateStaticParams.ts +5 -0
- package/src/index.ts +11 -0
- package/src/middleware/index.ts +1 -0
- package/src/middleware/intlayerMiddleware.ts +215 -0
- package/src/middleware/localeDetector.ts +28 -0
- package/src/server/index.ts +9 -0
- package/src/server/withIntlayer.ts +61 -0
- package/src/types/NextPage.ts +10 -0
- package/src/types/index.ts +1 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { match } from "@formatjs/intl-localematcher";
|
|
2
|
+
import { getConfiguration } from "@intlayer/config/client";
|
|
3
|
+
import Negotiator from "negotiator";
|
|
4
|
+
const { locales, defaultLocale } = getConfiguration().internationalization;
|
|
5
|
+
const localeDetector = (request) => {
|
|
6
|
+
const negotiatorHeaders = {};
|
|
7
|
+
request.headers.forEach((value, key) => (negotiatorHeaders[key] = value));
|
|
8
|
+
const languages = new Negotiator({ headers: negotiatorHeaders }).languages();
|
|
9
|
+
try {
|
|
10
|
+
return match(languages, locales, defaultLocale);
|
|
11
|
+
} catch (e) {
|
|
12
|
+
console.warn(
|
|
13
|
+
`No valid locales in accept-language header: ${languages.join(", ")}`
|
|
14
|
+
);
|
|
15
|
+
console.warn(`Reverting to using defaultLocale: ${defaultLocale}`);
|
|
16
|
+
return defaultLocale;
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
export { localeDetector };
|
|
20
|
+
//# sourceMappingURL=localeDetector.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/middleware/localeDetector.ts"],"sourcesContent":["import { match } from '@formatjs/intl-localematcher';\nimport type { Locales } from '@intlayer/config';\nimport { getConfiguration } from '@intlayer/config/client';\nimport Negotiator from 'negotiator';\nimport type { NextRequest } from 'next/server.js';\n\nconst { locales, defaultLocale } = getConfiguration().internationalization;\n\nexport const localeDetector = (request: NextRequest): Locales => {\n const negotiatorHeaders: Record<string, string> = {};\n\n request.headers.forEach((value, key) => (negotiatorHeaders[key] = value));\n\n const languages = new Negotiator({ headers: negotiatorHeaders }).languages();\n\n // match can only use specifically formatted locales\n // https://stackoverflow.com/questions/76447732/nextjs-13-i18n-incorrect-locale-information-provided\n try {\n return match(languages, locales, defaultLocale) as Locales;\n } catch (e) {\n console.warn(\n `No valid locales in accept-language header: ${languages.join(', ')}`\n );\n console.warn(`Reverting to using defaultLocale: ${defaultLocale}`);\n\n return defaultLocale;\n }\n};\n"],"mappings":"AAAA,SAAS,aAAa;AAEtB,SAAS,wBAAwB;AACjC,OAAO,gBAAgB;AAGvB,MAAM,EAAE,SAAS,cAAc,IAAI,iBAAiB,EAAE;AAE/C,MAAM,iBAAiB,CAAC,YAAkC;AAC/D,QAAM,oBAA4C,CAAC;AAEnD,UAAQ,QAAQ,QAAQ,CAAC,OAAO,QAAS,kBAAkB,GAAG,IAAI,KAAM;AAExE,QAAM,YAAY,IAAI,WAAW,EAAE,SAAS,kBAAkB,CAAC,EAAE,UAAU;AAI3E,MAAI;AACF,WAAO,MAAM,WAAW,SAAS,aAAa;AAAA,EAChD,SAAS,GAAG;AACV,YAAQ;AAAA,MACN,+CAA+C,UAAU,KAAK,IAAI,CAAC;AAAA,IACrE;AACA,YAAQ,KAAK,qCAAqC,aAAa,EAAE;AAEjE,WAAO;AAAA,EACT;AACF;","names":[]}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getLocaleContent,
|
|
3
|
+
useTraduction,
|
|
4
|
+
LocaleServerContext,
|
|
5
|
+
LocaleServerContextProvider,
|
|
6
|
+
locale,
|
|
7
|
+
useIntlayer,
|
|
8
|
+
} from "react-intlayer/server";
|
|
9
|
+
import { withIntlayer } from "./withIntlayer.mjs";
|
|
10
|
+
export {
|
|
11
|
+
LocaleServerContext,
|
|
12
|
+
LocaleServerContextProvider,
|
|
13
|
+
getLocaleContent,
|
|
14
|
+
locale,
|
|
15
|
+
useIntlayer,
|
|
16
|
+
useTraduction,
|
|
17
|
+
withIntlayer,
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/server/index.ts"],"sourcesContent":["export {\n getLocaleContent,\n useTraduction,\n LocaleServerContext,\n LocaleServerContextProvider,\n locale,\n useIntlayer,\n} from 'react-intlayer/server';\nexport { withIntlayer } from './withIntlayer';\n"],"mappings":"AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,oBAAoB;","names":[]}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { resolve, relative, join } from "path";
|
|
2
|
+
import { getConfiguration, formatEnvVariable } from "@intlayer/config";
|
|
3
|
+
const intlayerConfig = getConfiguration({
|
|
4
|
+
verbose: true,
|
|
5
|
+
});
|
|
6
|
+
const env = formatEnvVariable("NEXT_PUBLIC_INTLAYER_");
|
|
7
|
+
const { mainDir, baseDir } = intlayerConfig.content;
|
|
8
|
+
const mergeEnvVariable = (nextEnv = {}) => Object.assign({}, nextEnv, env);
|
|
9
|
+
const mergeStats = (nextStats = {}) =>
|
|
10
|
+
Object.assign({}, nextStats, {
|
|
11
|
+
warnings: false,
|
|
12
|
+
});
|
|
13
|
+
const withIntlayer =
|
|
14
|
+
(_pluginOptions = {}) =>
|
|
15
|
+
(nextConfig = {}) => {
|
|
16
|
+
if (typeof nextConfig !== "object") nextConfig = {};
|
|
17
|
+
return Object.assign({}, nextConfig, {
|
|
18
|
+
env: mergeEnvVariable(nextConfig.env),
|
|
19
|
+
stats: mergeStats(nextConfig.stats),
|
|
20
|
+
webpack: (config) => {
|
|
21
|
+
const dictionariesPath = join(mainDir, "dictionaries.cjs");
|
|
22
|
+
const relativeDictionariesPath = relative(baseDir, dictionariesPath);
|
|
23
|
+
config.externals.push({
|
|
24
|
+
esbuild: "esbuild",
|
|
25
|
+
module: "module",
|
|
26
|
+
fs: "fs",
|
|
27
|
+
});
|
|
28
|
+
config.resolve.alias["@intlayer/dictionaries-entry"] = resolve(
|
|
29
|
+
relativeDictionariesPath
|
|
30
|
+
);
|
|
31
|
+
config.module.rules.push({
|
|
32
|
+
test: /\.node$/,
|
|
33
|
+
loader: "node-loader",
|
|
34
|
+
});
|
|
35
|
+
return config;
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
export { withIntlayer };
|
|
40
|
+
//# sourceMappingURL=withIntlayer.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/server/withIntlayer.ts"],"sourcesContent":["import { resolve, relative, join } from 'path';\nimport { getConfiguration, formatEnvVariable } from '@intlayer/config';\nimport type { NextConfig } from 'next';\nimport type { NextJsWebpackConfig } from 'next/dist/server/config-shared';\n\ntype PluginOptions = {\n // TODO: add options\n};\n\nconst intlayerConfig = getConfiguration({\n verbose: true,\n});\n\n// Set all configuration values as environment variables\nconst env = formatEnvVariable('NEXT_PUBLIC_INTLAYER_');\nconst { mainDir, baseDir } = intlayerConfig.content;\n\nconst mergeEnvVariable = (\n nextEnv: Record<string, unknown> | undefined = {}\n): Record<string, string> => Object.assign({}, nextEnv, env);\n\nconst mergeStats = (\n nextStats: Record<string, unknown> | undefined = {}\n): Record<string, unknown> =>\n Object.assign({}, nextStats, {\n warnings: false,\n });\n\nexport const withIntlayer =\n (_pluginOptions: PluginOptions = {}) =>\n (nextConfig: Partial<NextConfig> = {}): Partial<NextConfig> => {\n if (typeof nextConfig !== 'object') nextConfig = {};\n\n return Object.assign({}, nextConfig, {\n env: mergeEnvVariable(nextConfig.env),\n\n stats: mergeStats(nextConfig.stats),\n\n webpack: (config: Parameters<NextJsWebpackConfig>['0']) => {\n const dictionariesPath = join(mainDir, 'dictionaries.cjs');\n const relativeDictionariesPath = relative(baseDir, dictionariesPath);\n\n config.externals.push({\n esbuild: 'esbuild',\n module: 'module',\n fs: 'fs',\n });\n\n config.resolve.alias['@intlayer/dictionaries-entry'] = resolve(\n relativeDictionariesPath\n );\n\n config.module.rules.push({\n test: /\\.node$/,\n loader: 'node-loader',\n });\n\n return config;\n },\n });\n };\n"],"mappings":"AAAA,SAAS,SAAS,UAAU,YAAY;AACxC,SAAS,kBAAkB,yBAAyB;AAQpD,MAAM,iBAAiB,iBAAiB;AAAA,EACtC,SAAS;AACX,CAAC;AAGD,MAAM,MAAM,kBAAkB,uBAAuB;AACrD,MAAM,EAAE,SAAS,QAAQ,IAAI,eAAe;AAE5C,MAAM,mBAAmB,CACvB,UAA+C,CAAC,MACrB,OAAO,OAAO,CAAC,GAAG,SAAS,GAAG;AAE3D,MAAM,aAAa,CACjB,YAAiD,CAAC,MAElD,OAAO,OAAO,CAAC,GAAG,WAAW;AAAA,EAC3B,UAAU;AACZ,CAAC;AAEI,MAAM,eACX,CAAC,iBAAgC,CAAC,MAClC,CAAC,aAAkC,CAAC,MAA2B;AAC7D,MAAI,OAAO,eAAe;AAAU,iBAAa,CAAC;AAElD,SAAO,OAAO,OAAO,CAAC,GAAG,YAAY;AAAA,IACnC,KAAK,iBAAiB,WAAW,GAAG;AAAA,IAEpC,OAAO,WAAW,WAAW,KAAK;AAAA,IAElC,SAAS,CAAC,WAAiD;AACzD,YAAM,mBAAmB,KAAK,SAAS,kBAAkB;AACzD,YAAM,2BAA2B,SAAS,SAAS,gBAAgB;AAEnE,aAAO,UAAU,KAAK;AAAA,QACpB,SAAS;AAAA,QACT,QAAQ;AAAA,QACR,IAAI;AAAA,MACN,CAAC;AAED,aAAO,QAAQ,MAAM,8BAA8B,IAAI;AAAA,QACrD;AAAA,MACF;AAEA,aAAO,OAAO,MAAM,KAAK;AAAA,QACvB,MAAM;AAAA,QACN,QAAQ;AAAA,MACV,CAAC;AAED,aAAO;AAAA,IACT;AAAA,EACF,CAAC;AACH;","names":[]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Locales } from "intlayer";
|
|
2
|
+
import { NextPage } from "next";
|
|
3
|
+
import { PropsWithChildren } from "react";
|
|
4
|
+
|
|
5
|
+
type LocalParams = {
|
|
6
|
+
params: {
|
|
7
|
+
locale: Locales;
|
|
8
|
+
};
|
|
9
|
+
};
|
|
10
|
+
type NextPageIntlayer = NextPage<LocalParams>;
|
|
11
|
+
type NextLayoutIntlayer = NextPage<PropsWithChildren<LocalParams>>;
|
|
12
|
+
|
|
13
|
+
export type { LocalParams, NextLayoutIntlayer, NextPageIntlayer };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=NextPage.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "next-intlayer",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "Webpack configuration for IntLayer using NextJS",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"intlayer",
|
|
8
|
+
"webpack",
|
|
9
|
+
"application",
|
|
10
|
+
"transpile",
|
|
11
|
+
"nextjs",
|
|
12
|
+
"typescript",
|
|
13
|
+
"javascript",
|
|
14
|
+
"json",
|
|
15
|
+
"file"
|
|
16
|
+
],
|
|
17
|
+
"homepage": "https://github.com/aypineau/intlayer",
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "https://github.com/aypineau/intlayer"
|
|
21
|
+
},
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"author": {
|
|
24
|
+
"name": "Aymeric PINEAU",
|
|
25
|
+
"url": "https://github.com/aypineau"
|
|
26
|
+
},
|
|
27
|
+
"exports": {
|
|
28
|
+
".": {
|
|
29
|
+
"types": "./dist/esm/index.d.mts",
|
|
30
|
+
"require": "./dist/cjs/index.cjs",
|
|
31
|
+
"import": "./dist/esm/index.mjs"
|
|
32
|
+
},
|
|
33
|
+
"./client": {
|
|
34
|
+
"types": "./dist/esm/client/index.d.mts",
|
|
35
|
+
"require": "./dist/cjs/client/index.cjs",
|
|
36
|
+
"import": "./dist/esm/client/index.mjs"
|
|
37
|
+
},
|
|
38
|
+
"./middleware": {
|
|
39
|
+
"types": "./dist/esm/middleware/index.d.mts",
|
|
40
|
+
"require": "./dist/cjs/middleware/index.cjs",
|
|
41
|
+
"import": "./dist/esm/middleware/index.mjs"
|
|
42
|
+
},
|
|
43
|
+
"./server": {
|
|
44
|
+
"types": "./dist/esm/server/index.d.mts",
|
|
45
|
+
"require": "./dist/cjs/server/index.cjs",
|
|
46
|
+
"import": "./dist/esm/server/index.mjs"
|
|
47
|
+
},
|
|
48
|
+
"./package.json": "./package.json"
|
|
49
|
+
},
|
|
50
|
+
"main": "src/index.ts",
|
|
51
|
+
"typesVersions": {
|
|
52
|
+
"*": {
|
|
53
|
+
"package.json": [
|
|
54
|
+
"./package.json"
|
|
55
|
+
]
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"files": [
|
|
59
|
+
"./dist",
|
|
60
|
+
"./src",
|
|
61
|
+
"./bin",
|
|
62
|
+
"./package.json"
|
|
63
|
+
],
|
|
64
|
+
"dependencies": {
|
|
65
|
+
"@formatjs/intl-localematcher": "^0.5.4",
|
|
66
|
+
"negotiator": "^0.6.3",
|
|
67
|
+
"next": "14.1.4",
|
|
68
|
+
"webpack": "^5.91.0",
|
|
69
|
+
"@intlayer/core": "^1.0.0",
|
|
70
|
+
"@intlayer/config": "^1.0.0",
|
|
71
|
+
"intlayer": "^1.0.0",
|
|
72
|
+
"@intlayer/dictionaries-entry": "^1.0.0",
|
|
73
|
+
"react-intlayer": "^1.0.0"
|
|
74
|
+
},
|
|
75
|
+
"devDependencies": {
|
|
76
|
+
"@types/negotiator": "^0.6.3",
|
|
77
|
+
"@types/node": "^20.11.30",
|
|
78
|
+
"@types/react": "^18.2.78",
|
|
79
|
+
"react": "^18.2.0",
|
|
80
|
+
"rimraf": "5.0.5",
|
|
81
|
+
"tsup": "^8.0.2",
|
|
82
|
+
"typescript": "^5.4.3",
|
|
83
|
+
"@utils/eslint-config": "^1.0.0",
|
|
84
|
+
"@utils/ts-config": "^1.0.0"
|
|
85
|
+
},
|
|
86
|
+
"peerDependencies": {
|
|
87
|
+
"react": "^18.2.0"
|
|
88
|
+
},
|
|
89
|
+
"engines": {
|
|
90
|
+
"node": ">=14.18"
|
|
91
|
+
},
|
|
92
|
+
"bug": {
|
|
93
|
+
"url": "https://github.com/aypineau/intlayer/issues"
|
|
94
|
+
},
|
|
95
|
+
"scripts": {
|
|
96
|
+
"build": "tsup",
|
|
97
|
+
"clean": "rimraf ./dist",
|
|
98
|
+
"dev": "tsup --watch",
|
|
99
|
+
"lint": "eslint . --ext .ts,.tsx,.js,.jsx,.cjs,.mjs",
|
|
100
|
+
"lint:fix": "eslint . --ext .ts,.tsx,.js,.jsx,.cjs,.mjs --fix",
|
|
101
|
+
"prettier:fix": "prettier --write src/**/*",
|
|
102
|
+
"test": "",
|
|
103
|
+
"typecheck": "tsup--project ./tsconfig.json --noEmit"
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { intlayerMiddlewareConfiguration } from '@intlayer/config/client';
|
|
2
|
+
import type { Locales } from 'intlayer';
|
|
3
|
+
import { usePathname, useRouter } from 'next/navigation.js';
|
|
4
|
+
import { useLocale as useReactLocale, useLocaleCookie } from 'react-intlayer';
|
|
5
|
+
|
|
6
|
+
const { prefixDefault } = intlayerMiddlewareConfiguration;
|
|
7
|
+
|
|
8
|
+
export const useLocale = () => {
|
|
9
|
+
const { setLocaleCookie } = useLocaleCookie();
|
|
10
|
+
const reactLocaleHook = useReactLocale();
|
|
11
|
+
const router = useRouter();
|
|
12
|
+
const pathname = usePathname();
|
|
13
|
+
|
|
14
|
+
const {
|
|
15
|
+
defaultLocale,
|
|
16
|
+
availableLocales,
|
|
17
|
+
locale: currentLocale,
|
|
18
|
+
} = reactLocaleHook;
|
|
19
|
+
|
|
20
|
+
const setLocale = (locale: Locales) => {
|
|
21
|
+
if (currentLocale === locale) return;
|
|
22
|
+
|
|
23
|
+
if (!availableLocales.includes(locale)) {
|
|
24
|
+
console.error(`Locale ${locale} is not available`);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
setLocaleCookie(locale);
|
|
29
|
+
|
|
30
|
+
const pathWithoutLocale =
|
|
31
|
+
!prefixDefault && currentLocale === defaultLocale
|
|
32
|
+
? pathname
|
|
33
|
+
: pathname.slice(`/${currentLocale}`.length) || '/';
|
|
34
|
+
|
|
35
|
+
if (!prefixDefault && locale === defaultLocale) {
|
|
36
|
+
return router.push(pathWithoutLocale);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return router.push(`/${locale}${pathWithoutLocale}`);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
...reactLocaleHook,
|
|
44
|
+
setLocale,
|
|
45
|
+
};
|
|
46
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export {
|
|
2
|
+
getTranslation,
|
|
3
|
+
LocaleClientContextProvider,
|
|
4
|
+
LocaleClientContext,
|
|
5
|
+
useIntlayer,
|
|
6
|
+
useTraduction,
|
|
7
|
+
useLocaleCookie,
|
|
8
|
+
} from 'react-intlayer';
|
|
9
|
+
export { generateStaticParams } from './generateStaticParams';
|
|
10
|
+
export type { LocalParams, NextPageIntlayer } from './types/index';
|
|
11
|
+
export { useLocale } from './client/useLocale';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './intlayerMiddleware';
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { type Locales, intlayerConfiguration } from '@intlayer/config/client';
|
|
2
|
+
import { type NextRequest, NextResponse } from 'next/server';
|
|
3
|
+
import { localeDetector } from './localeDetector';
|
|
4
|
+
|
|
5
|
+
const { internationalization, middleware } = intlayerConfiguration;
|
|
6
|
+
const { locales, defaultLocale } = internationalization;
|
|
7
|
+
const {
|
|
8
|
+
headerName,
|
|
9
|
+
cookieName,
|
|
10
|
+
prefixDefault,
|
|
11
|
+
basePath,
|
|
12
|
+
serverSetCookie,
|
|
13
|
+
noPrefix,
|
|
14
|
+
} = middleware;
|
|
15
|
+
|
|
16
|
+
export const intlayerMiddleware = (request: NextRequest): NextResponse => {
|
|
17
|
+
const pathname = request.nextUrl.pathname;
|
|
18
|
+
const cookieLocale = getCookieLocale(request);
|
|
19
|
+
const basePathTrailingSlash = basePath.endsWith('/');
|
|
20
|
+
|
|
21
|
+
if (noPrefix) {
|
|
22
|
+
return handleNoPrefix(
|
|
23
|
+
request,
|
|
24
|
+
cookieLocale,
|
|
25
|
+
pathname,
|
|
26
|
+
basePathTrailingSlash
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const pathLocale = getPathLocale(pathname);
|
|
31
|
+
return handlePrefix(
|
|
32
|
+
request,
|
|
33
|
+
cookieLocale,
|
|
34
|
+
pathLocale,
|
|
35
|
+
pathname,
|
|
36
|
+
basePathTrailingSlash
|
|
37
|
+
);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const getCookieLocale = (request: NextRequest): Locales | undefined => {
|
|
41
|
+
if (!cookieName) return undefined;
|
|
42
|
+
const cookieValue = request.cookies.get(cookieName)?.value as Locales;
|
|
43
|
+
if (cookieValue && locales.includes(cookieValue)) {
|
|
44
|
+
return cookieValue;
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const handleNoPrefix = (
|
|
49
|
+
request: NextRequest,
|
|
50
|
+
cookieLocale: Locales | undefined,
|
|
51
|
+
pathname: string,
|
|
52
|
+
basePathTrailingSlash: boolean
|
|
53
|
+
): NextResponse => {
|
|
54
|
+
const locale = cookieLocale ?? defaultLocale;
|
|
55
|
+
const newPath = constructPath(
|
|
56
|
+
locale,
|
|
57
|
+
pathname,
|
|
58
|
+
basePath,
|
|
59
|
+
basePathTrailingSlash
|
|
60
|
+
);
|
|
61
|
+
return rewriteUrl(request, newPath, locale);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const getPathLocale = (pathname: string): Locales | undefined =>
|
|
65
|
+
locales.find(
|
|
66
|
+
(locale) => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
const handlePrefix = (
|
|
70
|
+
request: NextRequest,
|
|
71
|
+
cookieLocale: Locales | undefined,
|
|
72
|
+
pathLocale: Locales | undefined,
|
|
73
|
+
pathname: string,
|
|
74
|
+
basePathTrailingSlash: boolean
|
|
75
|
+
): NextResponse => {
|
|
76
|
+
if (!pathLocale) {
|
|
77
|
+
return handleMissingPathLocale(
|
|
78
|
+
request,
|
|
79
|
+
cookieLocale,
|
|
80
|
+
pathname,
|
|
81
|
+
basePathTrailingSlash
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
return handleExistingPathLocale(
|
|
85
|
+
request,
|
|
86
|
+
cookieLocale,
|
|
87
|
+
pathLocale,
|
|
88
|
+
pathname,
|
|
89
|
+
basePathTrailingSlash
|
|
90
|
+
);
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const handleMissingPathLocale = (
|
|
94
|
+
request: NextRequest,
|
|
95
|
+
cookieLocale: Locales | undefined,
|
|
96
|
+
pathname: string,
|
|
97
|
+
basePathTrailingSlash: boolean
|
|
98
|
+
): NextResponse => {
|
|
99
|
+
let locale = cookieLocale ?? localeDetector?.(request) ?? defaultLocale;
|
|
100
|
+
if (!locales.includes(locale)) {
|
|
101
|
+
console.warn(
|
|
102
|
+
'The localeDetector callback must return a locale included in your locales array. Reverting to using defaultLocale.'
|
|
103
|
+
);
|
|
104
|
+
locale = defaultLocale;
|
|
105
|
+
}
|
|
106
|
+
const newPath = constructPath(
|
|
107
|
+
locale,
|
|
108
|
+
pathname,
|
|
109
|
+
basePath,
|
|
110
|
+
basePathTrailingSlash
|
|
111
|
+
);
|
|
112
|
+
return prefixDefault || locale !== defaultLocale
|
|
113
|
+
? redirectUrl(request, newPath)
|
|
114
|
+
: rewriteUrl(request, newPath, locale);
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const handleExistingPathLocale = (
|
|
118
|
+
request: NextRequest,
|
|
119
|
+
cookieLocale: Locales | undefined,
|
|
120
|
+
pathLocale: Locales,
|
|
121
|
+
pathname: string,
|
|
122
|
+
basePathTrailingSlash: boolean
|
|
123
|
+
): NextResponse => {
|
|
124
|
+
if (
|
|
125
|
+
cookieLocale &&
|
|
126
|
+
cookieLocale !== pathLocale &&
|
|
127
|
+
serverSetCookie !== 'always'
|
|
128
|
+
) {
|
|
129
|
+
const newPath = handleCookieLocaleMismatch(
|
|
130
|
+
request,
|
|
131
|
+
pathname,
|
|
132
|
+
pathLocale,
|
|
133
|
+
cookieLocale,
|
|
134
|
+
basePath,
|
|
135
|
+
basePathTrailingSlash
|
|
136
|
+
);
|
|
137
|
+
return redirectUrl(request, newPath);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return handleDefaultLocaleRedirect(
|
|
141
|
+
request,
|
|
142
|
+
pathLocale,
|
|
143
|
+
pathname,
|
|
144
|
+
basePathTrailingSlash
|
|
145
|
+
);
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const handleCookieLocaleMismatch = (
|
|
149
|
+
request: NextRequest,
|
|
150
|
+
|
|
151
|
+
pathname: string,
|
|
152
|
+
pathLocale: Locales,
|
|
153
|
+
cookieLocale: Locales,
|
|
154
|
+
basePath: string,
|
|
155
|
+
basePathTrailingSlash: boolean
|
|
156
|
+
): string => {
|
|
157
|
+
const newPath = pathname.replace(`/${pathLocale}`, `/${cookieLocale}`);
|
|
158
|
+
return constructPath(
|
|
159
|
+
cookieLocale,
|
|
160
|
+
newPath,
|
|
161
|
+
basePath,
|
|
162
|
+
basePathTrailingSlash,
|
|
163
|
+
request.nextUrl.search
|
|
164
|
+
);
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const handleDefaultLocaleRedirect = (
|
|
168
|
+
request: NextRequest,
|
|
169
|
+
pathLocale: Locales,
|
|
170
|
+
pathname: string,
|
|
171
|
+
basePathTrailingSlash: boolean
|
|
172
|
+
): NextResponse => {
|
|
173
|
+
if (!prefixDefault && pathLocale === defaultLocale) {
|
|
174
|
+
let pathWithoutLocale = pathname.slice(`/${pathLocale}`.length) || '/';
|
|
175
|
+
|
|
176
|
+
if (basePathTrailingSlash) {
|
|
177
|
+
pathWithoutLocale = pathWithoutLocale.slice(1);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (request.nextUrl.search) {
|
|
181
|
+
pathWithoutLocale += request.nextUrl.search;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return rewriteUrl(request, `${basePath}${pathWithoutLocale}`, pathLocale);
|
|
185
|
+
}
|
|
186
|
+
return rewriteUrl(request, pathname, pathLocale);
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
const constructPath = (
|
|
190
|
+
locale: Locales,
|
|
191
|
+
path: string,
|
|
192
|
+
basePath: string,
|
|
193
|
+
basePathTrailingSlash: boolean,
|
|
194
|
+
search?: string
|
|
195
|
+
): string => {
|
|
196
|
+
let newPath = `${locale}${path}`;
|
|
197
|
+
newPath = `${basePath}${basePathTrailingSlash ? '' : '/'}${newPath}`;
|
|
198
|
+
if (search) {
|
|
199
|
+
newPath += search;
|
|
200
|
+
}
|
|
201
|
+
return newPath;
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const rewriteUrl = (
|
|
205
|
+
request: NextRequest,
|
|
206
|
+
newPath: string,
|
|
207
|
+
locale: Locales
|
|
208
|
+
): NextResponse => {
|
|
209
|
+
const response = NextResponse.rewrite(new URL(newPath, request.url));
|
|
210
|
+
response.headers.set(headerName, locale);
|
|
211
|
+
return response;
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const redirectUrl = (request: NextRequest, newPath: string): NextResponse =>
|
|
215
|
+
NextResponse.redirect(new URL(newPath, request.url));
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { match } from '@formatjs/intl-localematcher';
|
|
2
|
+
import type { Locales } from '@intlayer/config';
|
|
3
|
+
import { getConfiguration } from '@intlayer/config/client';
|
|
4
|
+
import Negotiator from 'negotiator';
|
|
5
|
+
import type { NextRequest } from 'next/server.js';
|
|
6
|
+
|
|
7
|
+
const { locales, defaultLocale } = getConfiguration().internationalization;
|
|
8
|
+
|
|
9
|
+
export const localeDetector = (request: NextRequest): Locales => {
|
|
10
|
+
const negotiatorHeaders: Record<string, string> = {};
|
|
11
|
+
|
|
12
|
+
request.headers.forEach((value, key) => (negotiatorHeaders[key] = value));
|
|
13
|
+
|
|
14
|
+
const languages = new Negotiator({ headers: negotiatorHeaders }).languages();
|
|
15
|
+
|
|
16
|
+
// match can only use specifically formatted locales
|
|
17
|
+
// https://stackoverflow.com/questions/76447732/nextjs-13-i18n-incorrect-locale-information-provided
|
|
18
|
+
try {
|
|
19
|
+
return match(languages, locales, defaultLocale) as Locales;
|
|
20
|
+
} catch (e) {
|
|
21
|
+
console.warn(
|
|
22
|
+
`No valid locales in accept-language header: ${languages.join(', ')}`
|
|
23
|
+
);
|
|
24
|
+
console.warn(`Reverting to using defaultLocale: ${defaultLocale}`);
|
|
25
|
+
|
|
26
|
+
return defaultLocale;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { resolve, relative, join } from 'path';
|
|
2
|
+
import { getConfiguration, formatEnvVariable } from '@intlayer/config';
|
|
3
|
+
import type { NextConfig } from 'next';
|
|
4
|
+
import type { NextJsWebpackConfig } from 'next/dist/server/config-shared';
|
|
5
|
+
|
|
6
|
+
type PluginOptions = {
|
|
7
|
+
// TODO: add options
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const intlayerConfig = getConfiguration({
|
|
11
|
+
verbose: true,
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
// Set all configuration values as environment variables
|
|
15
|
+
const env = formatEnvVariable('NEXT_PUBLIC_INTLAYER_');
|
|
16
|
+
const { mainDir, baseDir } = intlayerConfig.content;
|
|
17
|
+
|
|
18
|
+
const mergeEnvVariable = (
|
|
19
|
+
nextEnv: Record<string, unknown> | undefined = {}
|
|
20
|
+
): Record<string, string> => Object.assign({}, nextEnv, env);
|
|
21
|
+
|
|
22
|
+
const mergeStats = (
|
|
23
|
+
nextStats: Record<string, unknown> | undefined = {}
|
|
24
|
+
): Record<string, unknown> =>
|
|
25
|
+
Object.assign({}, nextStats, {
|
|
26
|
+
warnings: false,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
export const withIntlayer =
|
|
30
|
+
(_pluginOptions: PluginOptions = {}) =>
|
|
31
|
+
(nextConfig: Partial<NextConfig> = {}): Partial<NextConfig> => {
|
|
32
|
+
if (typeof nextConfig !== 'object') nextConfig = {};
|
|
33
|
+
|
|
34
|
+
return Object.assign({}, nextConfig, {
|
|
35
|
+
env: mergeEnvVariable(nextConfig.env),
|
|
36
|
+
|
|
37
|
+
stats: mergeStats(nextConfig.stats),
|
|
38
|
+
|
|
39
|
+
webpack: (config: Parameters<NextJsWebpackConfig>['0']) => {
|
|
40
|
+
const dictionariesPath = join(mainDir, 'dictionaries.cjs');
|
|
41
|
+
const relativeDictionariesPath = relative(baseDir, dictionariesPath);
|
|
42
|
+
|
|
43
|
+
config.externals.push({
|
|
44
|
+
esbuild: 'esbuild',
|
|
45
|
+
module: 'module',
|
|
46
|
+
fs: 'fs',
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
config.resolve.alias['@intlayer/dictionaries-entry'] = resolve(
|
|
50
|
+
relativeDictionariesPath
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
config.module.rules.push({
|
|
54
|
+
test: /\.node$/,
|
|
55
|
+
loader: 'node-loader',
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
return config;
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Locales } from 'intlayer';
|
|
2
|
+
import type { NextPage } from 'next';
|
|
3
|
+
import type { PropsWithChildren } from 'react';
|
|
4
|
+
|
|
5
|
+
export type LocalParams = {
|
|
6
|
+
params: { locale: Locales };
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export type NextPageIntlayer = NextPage<LocalParams>;
|
|
10
|
+
export type NextLayoutIntlayer = NextPage<PropsWithChildren<LocalParams>>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type { LocalParams, NextPageIntlayer } from './NextPage';
|