Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions packages/dashboard/.storybook/preview.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
import React from 'react'
import { ThemeProvider, StyledEngineProvider } from '@mui/material'
import { MaskLightTheme, applyMaskColorVars, CustomSnackbarProvider, MaskDarkTheme } from '@masknet/theme'
import { addSharedI18N } from '@masknet/shared'
import { addSharedI18N, I18NextProviderHMR } from '@masknet/shared'
import { fallbackLng } from '../../shared-base/src/i18n/fallbackRule'
// import { withMatrix } from 'storybook-addon-matrix'
import { addDashboardI18N } from '../src/locales'
import { I18nextProvider, initReactI18next } from 'react-i18next'
import { addDashboardI18N } from '../src/locales/languages'
import { initReactI18next } from 'react-i18next'
import i18n from 'i18next'
i18n.init({
keySeparator: false,
Expand All @@ -26,11 +26,11 @@ export const decorators = [
<React.Suspense fallback="">
<StyledEngineProvider injectFirst>
<ThemeProvider theme={MaskDarkTheme}>
<I18nextProvider i18n={i18n}>
<I18NextProviderHMR i18n={i18n}>
<CustomSnackbarProvider>
<Story />
</CustomSnackbarProvider>
</I18nextProvider>
</I18NextProviderHMR>
</ThemeProvider>
</StyledEngineProvider>
</React.Suspense>
Expand Down
7 changes: 3 additions & 4 deletions packages/dashboard/src/initialization/Dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
MaskDarkTheme,
useSystemPreferencePalette,
} from '@masknet/theme'
import { ErrorBoundary } from '@masknet/shared'
import { ErrorBoundary, I18NextProviderHMR } from '@masknet/shared'
import {
createInjectHooksRenderer,
NetworkPluginID,
Expand All @@ -18,7 +18,6 @@ import {
import { Web3Provider } from '@masknet/web3-shared-evm'

import i18n from 'i18next'
import { I18nextProvider } from 'react-i18next'

import '../utils/kv-storage'

Expand Down Expand Up @@ -55,7 +54,7 @@ export default function DashboardRoot() {
return (
<Web3Provider value={Web3Context}>
<PluginsWeb3ContextProvider pluginID={pluginID} value={PluginsWeb3State}>
<I18nextProvider i18n={i18n}>
<I18NextProviderHMR i18n={i18n}>
<StyledEngineProvider injectFirst>
<ThemeProvider theme={theme}>
<PersonaContext.Provider>
Expand All @@ -71,7 +70,7 @@ export default function DashboardRoot() {
</PersonaContext.Provider>
</ThemeProvider>
</StyledEngineProvider>
</I18nextProvider>
</I18NextProviderHMR>
</PluginsWeb3ContextProvider>
</Web3Provider>
)
Expand Down
13 changes: 13 additions & 0 deletions packages/dashboard/src/locales/languages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,16 @@ export const languages = {
}
import { createI18NBundle } from '@masknet/shared-base'
export const addDashboardI18N = createI18NBundle('dashboard', languages)
// @ts-ignore
if (import.meta.webpack) {
// @ts-ignore
import.meta.webpackHot.accept(
['./en-US.json', './ja-JP.json', './ko-KR.json', './qya-AA.json', './zh-CN.json', './zh-TW.json'],
() =>
globalThis.dispatchEvent?.(
new CustomEvent('MASK_I18N_HMR', {
detail: ['dashboard', { en: en_US, ja: ja_JP, ko: ko_KR, qy: qya_AA, 'zh-CN': zh_CN, zh: zh_TW }],
}),
),
)
}
13 changes: 13 additions & 0 deletions packages/mask/shared-ui/locales/languages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,16 @@ export const languages = {
}
import { createI18NBundle } from '@masknet/shared-base'
export const addMaskI18N = createI18NBundle('mask', languages)
// @ts-ignore
if (import.meta.webpack) {
// @ts-ignore
import.meta.webpackHot.accept(
['./en-US.json', './ja-JP.json', './ko-KR.json', './qya-AA.json', './zh-CN.json', './zh-TW.json'],
() =>
globalThis.dispatchEvent?.(
new CustomEvent('MASK_I18N_HMR', {
detail: ['mask', { en: en_US, ja: ja_JP, ko: ko_KR, qy: qya_AA, 'zh-CN': zh_CN, zh: zh_TW }],
}),
),
)
}
5 changes: 2 additions & 3 deletions packages/mask/src/UIRoot.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { Suspense } from 'react'
import { I18nextProvider } from 'react-i18next'
import { CustomSnackbarProvider } from '@masknet/theme'
import { Web3Provider } from '@masknet/web3-shared-evm'
import { CssBaseline, StyledEngineProvider, Theme, ThemeProvider } from '@mui/material'
import { NetworkPluginID, PluginsWeb3ContextProvider, useAllPluginsWeb3State } from '@masknet/plugin-infra'
import { ErrorBoundary, ErrorBoundaryBuildInfoContext, useValueRef } from '@masknet/shared'
import { ErrorBoundary, ErrorBoundaryBuildInfoContext, useValueRef, I18NextProviderHMR } from '@masknet/shared'
import i18nNextInstance from '../shared-ui/locales_legacy'
import { Web3Context } from './web3/context'
import { buildInfoMarkdown } from './extension/background-script/Jobs/PrintBuildFlags'
Expand Down Expand Up @@ -69,7 +68,7 @@ export function MaskUIRoot({ children, kind, useTheme }: MaskUIRootProps) {
<PluginsWeb3ContextProvider pluginID={pluginID} value={PluginsWeb3State} children={jsx} />
</Web3Provider>
),
(jsx) => <I18nextProvider i18n={i18nNextInstance} children={jsx} />,
(jsx) => <I18NextProviderHMR i18n={i18nNextInstance} children={jsx} />,
kind === 'page' ? (jsx) => <StyledEngineProvider injectFirst children={jsx} /> : identity,
(jsx) => (
<MaskThemeProvider useTheme={useTheme} baseline={kind === 'page'}>
Expand Down
1 change: 0 additions & 1 deletion packages/mask/src/plugin-infra/I18NFieldRender.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ export interface PluginI18NFieldRenderProps {
}
export function PluginI18NFieldRender({ pluginID, field }: PluginI18NFieldRenderProps) {
const [t] = useTranslation()
console.log(pluginID, field)
if (!field) return null
if (typeof field === 'object' && 'fallback' in field) {
if (field.i18nKey) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,16 @@ export const languages = {
}
import { createI18NBundle } from '@masknet/shared-base'
export const addDO_NOT_USEI18N = createI18NBundle('DO_NOT_USE', languages)
// @ts-ignore
if (import.meta.webpack) {
// @ts-ignore
import.meta.webpackHot.accept(
['./en-US.json', './ja-JP.json', './ko-KR.json', './qya-AA.json', './zh-CN.json', './zh-TW.json'],
() =>
globalThis.dispatchEvent?.(
new CustomEvent('MASK_I18N_HMR', {
detail: ['DO_NOT_USE', { en: en_US, ja: ja_JP, ko: ko_KR, qy: qya_AA, 'zh-CN': zh_CN, zh: zh_TW }],
}),
),
)
}
11 changes: 0 additions & 11 deletions packages/mask/src/utils/i18n-next-ui.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import type { i18n } from 'i18next'
import { initReactI18next, useTranslation as useTranslation_, UseTranslationOptions } from 'react-i18next'
import type { TOptions } from 'i18next'
import { useEffect } from 'react'
import { useUpdate } from 'react-use'
import type en from '../../shared-ui/locales/en-US.json'
import i18nNextInstance from '../../shared-ui/locales_legacy'
import { languageSettings } from '../settings/settings'
Expand All @@ -26,15 +24,6 @@ export function useI18N(opt?: UseTranslationOptions): {
i18n: i18n
ready: boolean
} {
if (process.env.NODE_ENV === 'development') {
// eslint-disable-next-line react-hooks/rules-of-hooks
const update = useUpdate()
// eslint-disable-next-line react-hooks/rules-of-hooks
useEffect(() => {
document.addEventListener('i18n-hmr', update)
return () => document.removeEventListener('i18n-hmr', update)
}, [])
}
return useTranslation_('mask', opt)
}

Expand Down
16 changes: 16 additions & 0 deletions packages/plugins/FileService/src/locales/languages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,19 @@ export const languages = {
'zh-CN': zh_CN,
zh: zh_TW,
}
// @ts-ignore
if (import.meta.webpack) {
// @ts-ignore
import.meta.webpackHot.accept(
['./en-US.json', './ja-JP.json', './ko-KR.json', './zh-CN.json', './zh-TW.json'],
() =>
globalThis.dispatchEvent?.(
new CustomEvent('MASK_I18N_HMR', {
detail: [
'com.maskbook.fileservice',
{ en: en_US, ja: ja_JP, ko: ko_KR, 'zh-CN': zh_CN, zh: zh_TW },
],
}),
),
)
}
16 changes: 16 additions & 0 deletions packages/plugins/Flow/src/locales/languages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,19 @@ export const languages = {
'zh-CN': zh_CN,
zh: zh_TW,
}
// @ts-ignore
if (import.meta.webpack) {
// @ts-ignore
import.meta.webpackHot.accept(
['./en-US.json', './ja-JP.json', './ko-KR.json', './qya-AA.json', './zh-CN.json', './zh-TW.json'],
() =>
globalThis.dispatchEvent?.(
new CustomEvent('MASK_I18N_HMR', {
detail: [
'com.mask.flow',
{ en: en_US, ja: ja_JP, ko: ko_KR, qy: qya_AA, 'zh-CN': zh_CN, zh: zh_TW },
],
}),
),
)
}
16 changes: 16 additions & 0 deletions packages/plugins/Wallet/src/locales/languages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,19 @@ export const languages = {
'zh-CN': zh_CN,
zh: zh_TW,
}
// @ts-ignore
if (import.meta.webpack) {
// @ts-ignore
import.meta.webpackHot.accept(
['./en-US.json', './ja-JP.json', './ko-KR.json', './qya-AA.json', './zh-CN.json', './zh-TW.json'],
() =>
globalThis.dispatchEvent?.(
new CustomEvent('MASK_I18N_HMR', {
detail: [
'com.maskbook.wallet',
{ en: en_US, ja: ja_JP, ko: ko_KR, qy: qya_AA, 'zh-CN': zh_CN, zh: zh_TW },
],
}),
),
)
}
11 changes: 11 additions & 0 deletions packages/plugins/example/src/locales/languages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,14 @@ export const languages = {
en: en_US,
zh: zh_TW,
}
// @ts-ignore
if (import.meta.webpack) {
// @ts-ignore
import.meta.webpackHot.accept(['./en-US.json', './zh-TW.json'], () =>
globalThis.dispatchEvent?.(
new CustomEvent('MASK_I18N_HMR', {
detail: ['io.mask.example', { en: en_US, zh: zh_TW }],
}),
),
)
}
20 changes: 20 additions & 0 deletions packages/scripts/src/locale-kit-next/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,26 @@ export async function syncLanguages() {
code += `import { createI18NBundle } from '${target}'\n`
code += `export const add${upperFirst(namespace)}I18N = createI18NBundle('${namespace}', languages)\n`
}

{
const allImportPath: string[] = []
const binding: string[] = []
for (const [language, familyName] of languages) {
allImportPath.push(`./${language}.json`)
binding.push(`'${familyName}': ${language.replace('-', '_')}`)
}
code += `// @ts-ignore
if (import.meta.webpack) {
// @ts-ignore
import.meta.webpackHot.accept(
${JSON.stringify(allImportPath)},
() => globalThis.dispatchEvent?.(new CustomEvent('MASK_I18N_HMR', {
detail: ['${namespace}', { ${binding.join(', ')} }]
}))
)
}
`
}
code = await prettier(code)
await writeFile(join(inputDir, 'languages.ts'), code, { encoding: 'utf8' })
}
Expand Down
13 changes: 13 additions & 0 deletions packages/shared-base/src/i18n/register-ns.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,19 @@ import type { i18n } from 'i18next'

export function addI18NBundle(instance: i18n, namespace: string, langs: Record<string, object>) {
if (!instance.addResourceBundle) throw new TypeError('Please call instance.init() first')

try {
if (process.env.NODE_ENV === 'development') {
globalThis.addEventListener('MASK_I18N_HMR', (e) => {
const [ns, langs] = (e as CustomEvent).detail
if (namespace !== ns) return

for (const lang in langs) {
instance.addResourceBundle(lang, namespace, removeEmptyString(langs[lang]))
}
})
}
} catch {}
for (const lang in langs) {
instance.addResourceBundle(lang, namespace, removeEmptyString(langs[lang]))
}
Expand Down
27 changes: 27 additions & 0 deletions packages/shared/src/UI/components/I18NextProviderHMR.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React, { useEffect, useState } from 'react'
import { I18nextProvider, type I18nextProviderProps } from 'react-i18next'

export const I18NextProviderHMR =
process.env.NODE_ENV === 'development'
? function I18NextProviderHMR({ i18n, defaultNS, children }: React.PropsWithChildren<I18nextProviderProps>) {
const [ns, setNS] = useState(defaultNS)

useEffect(() => {
const f = () => setNS('HMR')
globalThis.addEventListener('MASK_I18N_HMR', f)
return () => globalThis.removeEventListener('MASK_I18N_HMR', f)
}, [])
// Force trigger a re-render to apply HMR
if (ns === 'HMR') defaultNS = Math.random() + ''

useEffect(() => {
if (ns === 'HMR') setNS('')
}, [ns])
// deliberately call it as a function in order to skip a React component nesting level.
return I18nextProvider({
i18n,
defaultNS,
children,
})
}
: I18nextProvider
1 change: 1 addition & 0 deletions packages/shared/src/UI/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ export * from './SelectTokenChip'
export * from './TokenAmountPanel'
export * from './LoadingAnimation'
export * from './MiniNetworkSelector'
export * from './I18NextProviderHMR'
13 changes: 13 additions & 0 deletions packages/shared/src/locales/languages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,16 @@ export const languages = {
}
import { createI18NBundle } from '@masknet/shared-base'
export const addSharedI18N = createI18NBundle('shared', languages)
// @ts-ignore
if (import.meta.webpack) {
// @ts-ignore
import.meta.webpackHot.accept(
['./en-US.json', './ja-JP.json', './ko-KR.json', './qya-AA.json', './zh-CN.json', './zh-TW.json'],
() =>
globalThis.dispatchEvent?.(
new CustomEvent('MASK_I18N_HMR', {
detail: ['shared', { en: en_US, ja: ja_JP, ko: ko_KR, qy: qya_AA, 'zh-CN': zh_CN, zh: zh_TW }],
}),
),
)
}