Skip to content

Commit d77c8ac

Browse files
authored
feat: i18n plugin support backend (#7867)
1 parent 9a62b69 commit d77c8ac

File tree

24 files changed

+410
-93
lines changed

24 files changed

+410
-93
lines changed

packages/runtime/plugin-i18n/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,9 @@
8686
"@modern-js/utils": "workspace:*",
8787
"@swc/helpers": "^0.5.17",
8888
"i18next-browser-languagedetector": "^8.2.0",
89-
"i18next-http-middleware": "^3.8.1"
89+
"i18next-http-middleware": "^3.8.1",
90+
"i18next-fs-backend": "^2.6.0",
91+
"i18next-http-backend": "^3.0.2"
9092
},
9193
"peerDependencies": {
9294
"react": ">=17",

packages/runtime/plugin-i18n/src/cli/index.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,33 @@
11
import type { AppTools, CliPlugin } from '@modern-js/app-tools';
22
import { getPublicDirRoutePrefixes } from '@modern-js/server-core';
3-
import type { LocaleDetectionOptions } from '../shared/type';
4-
import { getLocaleDetectionOptions } from '../shared/utils';
3+
import type { BackendOptions, LocaleDetectionOptions } from '../shared/type';
4+
import { getBackendOptions, getLocaleDetectionOptions } from '../shared/utils';
55

66
export interface I18nPluginOptions {
77
localeDetection?: LocaleDetectionOptions;
8+
backend?: BackendOptions;
89
}
910

1011
export const i18nPlugin = (
1112
options: I18nPluginOptions = {},
1213
): CliPlugin<AppTools> => ({
1314
name: '@modern-js/plugin-i18n',
1415
setup: api => {
15-
const { localeDetection } = options;
16+
const { localeDetection, backend } = options;
1617
api._internalRuntimePlugins(({ entrypoint, plugins }) => {
1718
const localeDetectionOptions = localeDetection
1819
? getLocaleDetectionOptions(entrypoint.entryName, localeDetection)
1920
: undefined;
21+
const backendOptions = backend
22+
? getBackendOptions(entrypoint.entryName, backend)
23+
: undefined;
2024
plugins.push({
2125
name: 'i18n',
2226
path: '@modern-js/plugin-i18n/runtime',
2327
config: {
2428
entryName: entrypoint.entryName,
2529
localeDetection: localeDetectionOptions,
30+
backend: backendOptions,
2631
},
2732
});
2833
return {
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { deepMerge } from '../../../shared/deepMerge';
2+
import type { BackendOptions } from '../instance';
3+
4+
export function mergeBackendOptions(
5+
defaultOptions: BackendOptions,
6+
cliOptions: BackendOptions = {},
7+
userOptions?: BackendOptions,
8+
): BackendOptions {
9+
return deepMerge(deepMerge(defaultOptions, cliOptions), userOptions ?? {});
10+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export const DEFAULT_I18NEXT_BACKEND_OPTIONS = {
2+
loadPath: './locales/{{lng}}/{{ns}}.json',
3+
addPath: './locales/{{lng}}/{{ns}}.json',
4+
};
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export const DEFAULT_I18NEXT_BACKEND_OPTIONS = {
2+
loadPath: '/locales/{{lng}}/{{ns}}.json',
3+
addPath: '/locales/{{lng}}/{{ns}}.json',
4+
};
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import type { BaseBackendOptions } from '../../../shared/type';
2+
import type { BackendOptions, I18nInitOptions } from '../instance';
3+
import { mergeBackendOptions as baseMergeBackendOptions } from './config';
4+
import { DEFAULT_I18NEXT_BACKEND_OPTIONS } from './defaults';
5+
6+
export const mergeBackendOptions = (
7+
backend?: BaseBackendOptions,
8+
userInitOptions?: I18nInitOptions,
9+
) => {
10+
const mergedBackend = backend?.enabled
11+
? baseMergeBackendOptions(
12+
DEFAULT_I18NEXT_BACKEND_OPTIONS,
13+
backend as BackendOptions,
14+
userInitOptions?.backend,
15+
)
16+
: userInitOptions?.backend;
17+
return mergedBackend;
18+
};
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import Backend from 'i18next-fs-backend';
2+
import type { I18nInstance } from '../instance';
3+
4+
export const useI18nextBackend = (i18nInstance: I18nInstance) => {
5+
return i18nInstance.use(Backend);
6+
};
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import Backend from 'i18next-http-backend';
2+
import type { I18nInstance } from '../instance';
3+
4+
export const useI18nextBackend = (i18nInstance: I18nInstance) => {
5+
return i18nInstance.use(Backend);
6+
};

packages/runtime/plugin-i18n/src/runtime/i18n/detection/config.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,12 @@ export const DEFAULT_I18NEXT_DETECTION_OPTIONS = {
2121
};
2222

2323
export function mergeDetectionOptions(
24+
cliOptions?: LanguageDetectorOptions,
2425
userOptions?: LanguageDetectorOptions,
2526
defaultOptions: LanguageDetectorOptions = DEFAULT_I18NEXT_DETECTION_OPTIONS,
2627
): LanguageDetectorOptions {
27-
return deepMerge(defaultOptions, userOptions);
28+
return deepMerge(
29+
deepMerge(defaultOptions, cliOptions ?? {}),
30+
userOptions ?? {},
31+
);
2832
}

packages/runtime/plugin-i18n/src/runtime/i18n/detection/index.ts

Lines changed: 20 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import { type RuntimeContext, isBrowser } from '@modern-js/runtime';
22
import { detectLanguageFromPath } from '../../utils';
3-
import type { I18nInitOptions, I18nInstance } from '../instance';
3+
import type {
4+
I18nInitOptions,
5+
I18nInstance,
6+
LanguageDetectorOptions,
7+
} from '../instance';
48
import { mergeDetectionOptions as mergeDetectionOptionsUtil } from './config';
59
import { detectLanguage } from './middleware';
610

@@ -13,12 +17,16 @@ export const getLanguageFromSSRData = (window: Window): string | undefined => {
1317
return ssrData?.data?.i18nData?.lng as string | undefined;
1418
};
1519

16-
export interface LanguageDetectionOptions {
20+
export interface BaseLanguageDetectionOptions {
1721
languages: string[];
1822
fallbackLanguage: string;
1923
localePathRedirect: boolean;
2024
i18nextDetector: boolean;
25+
detection?: LanguageDetectorOptions;
2126
userInitOptions?: I18nInitOptions;
27+
}
28+
29+
export interface LanguageDetectionOptions extends BaseLanguageDetectionOptions {
2230
pathname: string;
2331
ssrContext?: any;
2432
}
@@ -96,20 +104,15 @@ const detectLanguageFromPathPriority = (
96104
*/
97105
const initializeI18nForDetector = async (
98106
i18nInstance: I18nInstance,
99-
options: {
100-
languages: string[];
101-
fallbackLanguage: string;
102-
localePathRedirect: boolean;
103-
i18nextDetector: boolean;
104-
userInitOptions?: I18nInitOptions;
105-
},
107+
options: BaseLanguageDetectionOptions,
106108
): Promise<void> => {
107109
if (i18nInstance.isInitialized) {
108110
return;
109111
}
110112

111-
const { mergedDetection } = mergeDetectionOptions(
113+
const mergedDetection = mergeDetectionOptions(
112114
options.i18nextDetector,
115+
options.detection,
113116
options.localePathRedirect,
114117
options.userInitOptions,
115118
);
@@ -140,14 +143,7 @@ const initializeI18nForDetector = async (
140143
*/
141144
const detectLanguageFromI18nextDetector = async (
142145
i18nInstance: I18nInstance,
143-
options: {
144-
languages: string[];
145-
fallbackLanguage: string;
146-
localePathRedirect: boolean;
147-
i18nextDetector: boolean;
148-
userInitOptions?: I18nInitOptions;
149-
ssrContext?: any;
150-
},
146+
options: BaseLanguageDetectionOptions & { ssrContext?: any },
151147
): Promise<string | undefined> => {
152148
if (!options.i18nextDetector) {
153149
return undefined;
@@ -195,6 +191,7 @@ export const detectLanguageWithPriority = async (
195191
fallbackLanguage,
196192
localePathRedirect,
197193
i18nextDetector,
194+
detection,
198195
userInitOptions,
199196
pathname,
200197
ssrContext,
@@ -219,6 +216,7 @@ export const detectLanguageWithPriority = async (
219216
fallbackLanguage,
220217
localePathRedirect,
221218
i18nextDetector,
219+
detection,
222220
userInitOptions,
223221
ssrContext,
224222
});
@@ -276,18 +274,19 @@ export const buildInitOptions = (
276274
*/
277275
export const mergeDetectionOptions = (
278276
i18nextDetector: boolean,
279-
localePathRedirect: boolean,
277+
detection?: LanguageDetectorOptions,
278+
localePathRedirect?: boolean,
280279
userInitOptions?: I18nInitOptions,
281280
) => {
282281
// Exclude 'path' from detection order to avoid conflict with manual path detection
283282
const mergedDetection = i18nextDetector
284-
? mergeDetectionOptionsUtil(userInitOptions?.detection)
283+
? mergeDetectionOptionsUtil(detection, userInitOptions?.detection)
285284
: userInitOptions?.detection;
286285
if (localePathRedirect && mergedDetection?.order) {
287286
mergedDetection.order = mergedDetection.order.filter(
288287
(item: string) => item !== 'path',
289288
);
290289
}
291290

292-
return { mergedDetection };
291+
return mergedDetection;
293292
};

0 commit comments

Comments
 (0)