diff --git a/.gitignore b/.gitignore
index caef309..bb13b9a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -130,3 +130,4 @@ build
pnpm-lock.yaml
package-lock.json
example
+.DS_Store
diff --git a/README.md b/README.md
index 1203a09..79dd61a 100644
--- a/README.md
+++ b/README.md
@@ -218,7 +218,7 @@ const BaseExample = () => {
/>
----------------
-
+
);
diff --git a/doc/multi-level.js b/doc/multi-level.js
index f6599ab..c56b2b2 100644
--- a/doc/multi-level.js
+++ b/doc/multi-level.js
@@ -48,7 +48,7 @@ const BaseExample = () => {
/>
----------------
-
+
);
diff --git a/package.json b/package.json
index 8ab1b4d..9d7a08b 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@kne/react-intl",
- "version": "0.1.12",
+ "version": "0.1.13",
"description": "快捷地创建国际化中需要使用到的组件",
"syntax": {
"esmodules": true
diff --git a/src/createWithFetchLang.js b/src/createWithFetchLang.js
index a3fb8bf..0e29fcb 100644
--- a/src/createWithFetchLang.js
+++ b/src/createWithFetchLang.js
@@ -1,56 +1,78 @@
import Fetch from '@kne/react-fetch';
import { useGlobalValue, usePreset } from '@kne/global-context';
import localeLoader, { messagesLoader, message } from './loader';
-import { IntlProvider, useIntl } from 'react-intl';
-import React, { forwardRef } from 'react';
+import { IntlProvider, IntlContext } from 'react-intl';
+import React, { forwardRef, useContext } from 'react';
import { Provider as MessageProvider, useContext as useMessageContext } from './contex';
+import { argsParse, resolveLocale, mergeIntlMessages, hasMessages } from './intlUtils';
-const argsParse = (...args) => {
- if (typeof args[0] === 'object' && typeof args[0].defaultLocale === 'string') {
- return Object.assign({}, args[0]);
- }
-
- return { defaultLocale: args[0], defaultMessage: args[1], namespace: args[2] };
+const renderIntlTree = ({ locale, prevMessage, namespaceMessages, WrappedComponents, props, ref, MessageProvider: MsgProvider }) => {
+ const currentMessage = mergeIntlMessages(prevMessage, namespaceMessages);
+ return (
+
+
+
+
+
+ );
};
const createWithFetchLang = (...args) => {
- const { defaultLocale, defaultMessage, namespace, messages } = argsParse(...args);
+ const { defaultLocale, defaultMessage, namespace, messages: configMessages } = argsParse(...args);
defaultMessage && localeLoader(defaultLocale, defaultMessage, namespace);
- messages && messagesLoader(messages, namespace);
+ configMessages && messagesLoader(configMessages, namespace);
+
return WrappedComponents =>
forwardRef(({ locale: propsLocale, ...props }, ref) => {
const { apis } = usePreset();
const contextLocal = useGlobalValue('locale');
- const locale = propsLocale || contextLocal || defaultLocale || 'zh-CN';
+ const parentIntl = useContext(IntlContext);
+ const prevMessage = useMessageContext();
+ const locale = resolveLocale({
+ propsLocale,
+ contextLocal,
+ parentIntlLocale: parentIntl?.locale,
+ defaultLocale
+ });
const currentNamespace = namespace || 'global';
- const messages = message[locale]?.[currentNamespace];
+ const namespaceMessages = message[locale]?.[currentNamespace];
const defaultLocalMessage = message[defaultLocale || 'zh-CN']?.[currentNamespace];
- if (apis?.localeMessage && !(messages && Object.keys(messages).length > 0) && defaultLocalMessage && Object.keys(defaultLocalMessage).length > 0) {
+ const needsRemoteFetch = apis?.localeMessage && !hasMessages(namespaceMessages) && hasMessages(defaultLocalMessage);
+
+ if (needsRemoteFetch) {
return (
{
- messagesLoader({ [locale]: data }, currentNamespace);
- const messages = message[locale]?.[currentNamespace];
- return (
-
-
-
- );
+ const loadedMessages = hasMessages(data) ? data : defaultLocalMessage;
+ messagesLoader({ [locale]: loadedMessages }, currentNamespace);
+ const resolvedMessages = message[locale]?.[currentNamespace] || defaultLocalMessage;
+ return renderIntlTree({
+ locale,
+ prevMessage,
+ namespaceMessages: resolvedMessages,
+ WrappedComponents,
+ props,
+ ref,
+ MessageProvider
+ });
}}
/>
);
}
- const prevMessage = useMessageContext();
- const currentMessage = Object.assign({}, prevMessage, messages);
- return (
-
-
-
-
-
- );
+
+ return renderIntlTree({
+ locale,
+ prevMessage,
+ namespaceMessages,
+ WrappedComponents,
+ props,
+ ref,
+ MessageProvider
+ });
});
};
diff --git a/src/index.js b/src/index.js
index dc10768..dacdc62 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,35 +1,16 @@
-import React, { forwardRef } from 'react';
-import localeLoader, { message, messagesLoader } from './loader';
+import React from 'react';
+import localeLoader, { message } from './loader';
import { IntlProvider, createIntl as createIntlBase, useIntl } from 'react-intl';
-import { useGlobalValue } from '@kne/global-context';
import createWithFetchLang from './createWithFetchLang';
-
-const withIntlProvider = WrappedComponents =>
- forwardRef(({ locale: propsLocale, namespace, ...props }, ref) => {
- const contextLocal = useGlobalValue('locale');
- const locale = propsLocale || contextLocal || 'zh-CN';
- return (
-
-
-
- );
- });
-
-const argsParse = (...args) => {
- if (typeof args[0] === 'object' && typeof args[0].defaultLocale === 'string') {
- return Object.assign({}, args[0]);
- }
-
- return { defaultLocale: args[0], defaultMessage: args[1], namespace: args[2] };
-};
+import { hasMessages } from './intlUtils';
export const createIntlProvider = (...args) => {
- const withIntlProvider = createWithFetchLang(...args);
+ const IntlProviderComponent = createWithFetchLang(...args);
const InnerComponent = ({ children }) => {
const intl = useIntl();
return children(intl);
};
- return withIntlProvider(({ locale: propsLocale, children }) => {
+ return IntlProviderComponent(({ locale: propsLocale, children }) => {
return typeof children === 'function' ? {children} : children;
});
};
@@ -40,7 +21,8 @@ export * from 'react-intl';
export const createIntl = ({ locale = 'zh-CN', message: propsMessage, namespace }) => {
propsMessage && localeLoader(locale, propsMessage, namespace);
- return createIntlBase({ locale, messages: message[locale]?.[namespace || 'global'] });
+ const namespaceMessages = message[locale]?.[namespace || 'global'];
+ return createIntlBase({ locale, messages: hasMessages(namespaceMessages) ? namespaceMessages : {} });
};
export { localeLoader, IntlProvider };
diff --git a/src/intlUtils.js b/src/intlUtils.js
new file mode 100644
index 0000000..ec4de47
--- /dev/null
+++ b/src/intlUtils.js
@@ -0,0 +1,22 @@
+export const argsParse = (...args) => {
+ if (typeof args[0] === 'object' && args[0] !== null && !Array.isArray(args[0])) {
+ const config = { ...args[0] };
+ if (typeof config.defaultLocale !== 'string') {
+ const messageLocales = config.messages && typeof config.messages === 'object' ? Object.keys(config.messages) : [];
+ config.defaultLocale = messageLocales[0] || 'zh-CN';
+ }
+ return config;
+ }
+
+ const defaultLocale = typeof args[0] === 'string' ? args[0] : 'zh-CN';
+ return { defaultLocale, defaultMessage: args[1], namespace: args[2] };
+};
+
+export const resolveLocale = ({ propsLocale, contextLocal, parentIntlLocale, defaultLocale }) => {
+ const fallbackDefault = typeof defaultLocale === 'string' ? defaultLocale : 'zh-CN';
+ return propsLocale || contextLocal || parentIntlLocale || fallbackDefault || 'zh-CN';
+};
+
+export const mergeIntlMessages = (prevMessage, namespaceMessages) => Object.assign({}, prevMessage, namespaceMessages);
+
+export const hasMessages = value => Boolean(value && typeof value === 'object' && Object.keys(value).length > 0);
diff --git a/src/intlUtils.test.js b/src/intlUtils.test.js
new file mode 100644
index 0000000..387d359
--- /dev/null
+++ b/src/intlUtils.test.js
@@ -0,0 +1,91 @@
+import { argsParse, resolveLocale, mergeIntlMessages, hasMessages } from './intlUtils';
+
+describe('intlUtils', () => {
+ describe('argsParse', () => {
+ it('parses object config with defaultLocale', () => {
+ expect(
+ argsParse({
+ defaultLocale: 'en-US',
+ messages: { 'en-US': { hello: 'Hello' } }
+ })
+ ).toEqual({
+ defaultLocale: 'en-US',
+ messages: { 'en-US': { hello: 'Hello' } }
+ });
+ });
+
+ it('infers defaultLocale from messages when missing', () => {
+ expect(
+ argsParse({
+ messages: {
+ 'zh-CN': { hello: '你好' },
+ 'en-US': { hello: 'Hello' }
+ }
+ })
+ ).toMatchObject({ defaultLocale: 'zh-CN' });
+ });
+
+ it('parses legacy positional args', () => {
+ expect(argsParse('zh-CN', { hello: '你好' }, 'demo')).toEqual({
+ defaultLocale: 'zh-CN',
+ defaultMessage: { hello: '你好' },
+ namespace: 'demo'
+ });
+ });
+ });
+
+ describe('resolveLocale', () => {
+ it('prefers props locale over parent and default', () => {
+ expect(
+ resolveLocale({
+ propsLocale: 'en-US',
+ contextLocal: 'ja-JP',
+ parentIntlLocale: 'de-DE',
+ defaultLocale: 'zh-CN'
+ })
+ ).toBe('en-US');
+ });
+
+ it('inherits parent intl locale when props locale is absent', () => {
+ expect(
+ resolveLocale({
+ propsLocale: undefined,
+ contextLocal: undefined,
+ parentIntlLocale: 'en-US',
+ defaultLocale: 'zh-CN'
+ })
+ ).toBe('en-US');
+ });
+
+ it('falls back when defaultLocale is invalid', () => {
+ expect(
+ resolveLocale({
+ propsLocale: undefined,
+ contextLocal: undefined,
+ parentIntlLocale: undefined,
+ defaultLocale: { messages: {} }
+ })
+ ).toBe('zh-CN');
+ });
+ });
+
+ describe('mergeIntlMessages', () => {
+ it('merges parent and namespace messages', () => {
+ expect(mergeIntlMessages({ hello: 'parent' }, { title: 'child' })).toEqual({
+ hello: 'parent',
+ title: 'child'
+ });
+ });
+ });
+
+ describe('hasMessages', () => {
+ it('returns false for empty values', () => {
+ expect(hasMessages(undefined)).toBe(false);
+ expect(hasMessages({})).toBe(false);
+ });
+
+ it('returns true when keys exist', () => {
+ expect(hasMessages({ hello: 'world' })).toBe(true);
+ });
+ });
+});