From 5658cfbe0eee1c7370d8af98d80658adb7f71407 Mon Sep 17 00:00:00 2001 From: Linzp Date: Fri, 12 Jun 2026 12:20:50 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E4=B8=80=E4=B8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .DS_Store | Bin 0 -> 8196 bytes README.md | 2 +- doc/multi-level.js | 2 +- package.json | 2 +- src/createWithFetchLang.js | 84 +++++++++++++++++++++------------- src/index.js | 32 +++---------- src/intlUtils.js | 22 +++++++++ src/intlUtils.test.js | 91 +++++++++++++++++++++++++++++++++++++ 8 files changed, 176 insertions(+), 59 deletions(-) create mode 100644 .DS_Store create mode 100644 src/intlUtils.js create mode 100644 src/intlUtils.test.js diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..64739bb665d53b5573f19290fd6bbcf0b6e6e161 GIT binary patch literal 8196 zcmeHML2DC16n}HEz#L^jawTZF%)`3Bn0I&rt>xOxmC$NFFp|y#z@SrJ^3Tsl8Eisfy z$9iDnLTeLaO*$!Cd?<^oY=xpUI`$8AIH}NBKY9hc0$ByPb}!HrEmEC2vVK<=58RW! zW*kM8MqI&K>Br^OkG(H%&ba>Smj2e#;N5__w2o9NZsO7sb*UoJac6mwjgTC^6((N3 zy`wp#_G!igCizj457BYvvm1PvNt>4Gmdj>zU)nFZeC-$g7U+-y5nG=`!w~ZTp_~(1n;44~ Uw15600KW|U^IuEvhaIfIFQEcqbpQYW literal 0 HcmV?d00001 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); + }); + }); +}); From 6dd8c049738f5e455e2a7f8023428a54cf0e6492 Mon Sep 17 00:00:00 2001 From: Linzp Date: Fri, 12 Jun 2026 12:22:51 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=97=A0=E7=94=A8?= =?UTF-8?q?=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .DS_Store | Bin 8196 -> 0 bytes .gitignore | 1 + 2 files changed, 1 insertion(+) delete mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 64739bb665d53b5573f19290fd6bbcf0b6e6e161..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8196 zcmeHML2DC16n}HEz#L^jawTZF%)`3Bn0I&rt>xOxmC$NFFp|y#z@SrJ^3Tsl8Eisfy z$9iDnLTeLaO*$!Cd?<^oY=xpUI`$8AIH}NBKY9hc0$ByPb}!HrEmEC2vVK<=58RW! zW*kM8MqI&K>Br^OkG(H%&ba>Smj2e#;N5__w2o9NZsO7sb*UoJac6mwjgTC^6((N3 zy`wp#_G!igCizj457BYvvm1PvNt>4Gmdj>zU)nFZeC-$g7U+-y5nG=`!w~ZTp_~(1n;44~ Uw15600KW|U^IuEvhaIfIFQEcqbpQYW 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