diff --git a/.changeset/tired-clowns-flash.md b/.changeset/tired-clowns-flash.md new file mode 100644 index 0000000000..7cb468dc22 --- /dev/null +++ b/.changeset/tired-clowns-flash.md @@ -0,0 +1,10 @@ +--- +"@lynx-js/web-mainthread-apis": patch +"@lynx-js/web-worker-runtime": patch +"@lynx-js/web-constants": patch +"@lynx-js/web-core": patch +--- + +feat: add `updateI18nResources` method of lynx-view. + +Now you can use `updateI18nResources` to update i18nResources, and then use _I18nResourceTranslation() to get the updated result. diff --git a/packages/web-platform/web-constants/src/endpoints.ts b/packages/web-platform/web-constants/src/endpoints.ts index fcfc2b9dba..4bad309b10 100644 --- a/packages/web-platform/web-constants/src/endpoints.ts +++ b/packages/web-platform/web-constants/src/endpoints.ts @@ -213,3 +213,8 @@ export const updateGlobalPropsEndpoint = createRpcEndpoint< [Cloneable], void >('updateGlobalProps', false, false); + +export const updateI18nResourcesEndpoint = createRpcEndpoint< + [Cloneable], + void +>('updateI18nResources', false, false); diff --git a/packages/web-platform/web-constants/src/types/BackThreadStartConfigs.ts b/packages/web-platform/web-constants/src/types/BackThreadStartConfigs.ts index 0ae593d708..09d085726f 100644 --- a/packages/web-platform/web-constants/src/types/BackThreadStartConfigs.ts +++ b/packages/web-platform/web-constants/src/types/BackThreadStartConfigs.ts @@ -3,7 +3,6 @@ // LICENSE file in the root directory of this source tree. import type { Cloneable } from './Cloneable.js'; -import type { InitI18nResources } from './index.js'; import type { LynxTemplate } from './LynxModule.js'; import type { NapiModulesMap } from './NapiModules.js'; import type { NativeModulesMap } from './NativeModules.js'; @@ -18,5 +17,4 @@ export interface BackMainThreadContextConfig { nativeModulesMap: NativeModulesMap; napiModulesMap: NapiModulesMap; browserConfig: BrowserConfig; - initI18nResources: InitI18nResources; } diff --git a/packages/web-platform/web-constants/src/types/Cloneable.ts b/packages/web-platform/web-constants/src/types/Cloneable.ts index 62931e1e51..e99d32d1e6 100644 --- a/packages/web-platform/web-constants/src/types/Cloneable.ts +++ b/packages/web-platform/web-constants/src/types/Cloneable.ts @@ -4,7 +4,8 @@ export type Cloneable = | T | Record - | T[]; + | T[] + | Array>; export type CloneableObject = Record< diff --git a/packages/web-platform/web-constants/src/types/I18n.ts b/packages/web-platform/web-constants/src/types/I18n.ts index 019e1dc683..b1bb757e9b 100644 --- a/packages/web-platform/web-constants/src/types/I18n.ts +++ b/packages/web-platform/web-constants/src/types/I18n.ts @@ -26,3 +26,13 @@ export type InitI18nResources = Array<{ }>; export const i18nResourceMissedEventName = 'i18nResourceMissed' as const; + +export class I18nResources { + data?: InitI18nResources; + constructor(data?: InitI18nResources) { + this.data = data; + } + setData(data: InitI18nResources) { + this.data = data; + } +} diff --git a/packages/web-platform/web-core-server/src/createLynxView.ts b/packages/web-platform/web-core-server/src/createLynxView.ts index 0c0947698b..eb5c1a59bf 100644 --- a/packages/web-platform/web-core-server/src/createLynxView.ts +++ b/packages/web-platform/web-core-server/src/createLynxView.ts @@ -1,6 +1,8 @@ import { + I18nResources, inShadowRootStyles, lynxUniqueIdAttribute, + type InitI18nResources, type StartMainThreadContextConfig, } from '@lynx-js/web-constants'; import { Rpc } from '@lynx-js/web-worker-rpc'; @@ -111,6 +113,7 @@ export async function createLynxView( onCommit: () => { }, }); + const i18nResources = new I18nResources(); const { startMainThread } = prepareMainThreadAPIs( backgroundThreadRpc, offscreenDocument, @@ -127,6 +130,10 @@ export async function createLynxView( () => { // trigger i18n resource fallback }, + (initI18nResources: InitI18nResources) => { + i18nResources.setData(initI18nResources); + return i18nResources; + }, ); const runtime = await startMainThread({ template, diff --git a/packages/web-platform/web-core/src/apis/LynxView.ts b/packages/web-platform/web-core/src/apis/LynxView.ts index 05c0d90955..afae3c4dc6 100644 --- a/packages/web-platform/web-core/src/apis/LynxView.ts +++ b/packages/web-platform/web-core/src/apis/LynxView.ts @@ -155,6 +155,15 @@ export class LynxView extends HTMLElement { } } + /** + * @public + * @method + * update the `__initData` and trigger essential flow + */ + updateI18nResources(data: InitI18nResources) { + this.#instance?.updateI18nResources(data as Cloneable); + } + #overrideLynxTagToHTMLTagMap: Record = { 'page': 'div' }; /** * @public diff --git a/packages/web-platform/web-core/src/apis/createLynxView.ts b/packages/web-platform/web-core/src/apis/createLynxView.ts index 6844ce077a..366923dad0 100644 --- a/packages/web-platform/web-core/src/apis/createLynxView.ts +++ b/packages/web-platform/web-core/src/apis/createLynxView.ts @@ -42,6 +42,7 @@ export interface LynxView { dispose(): Promise; sendGlobalEvent: RpcCallType; updateGlobalProps: (data: Cloneable) => void; + updateI18nResources: (data: Cloneable) => void; } export function createLynxView(configs: LynxViewConfigs): LynxView { diff --git a/packages/web-platform/web-core/src/uiThread/createRenderAllOnUI.ts b/packages/web-platform/web-core/src/uiThread/createRenderAllOnUI.ts index ef3367c182..d5a933fa12 100644 --- a/packages/web-platform/web-core/src/uiThread/createRenderAllOnUI.ts +++ b/packages/web-platform/web-core/src/uiThread/createRenderAllOnUI.ts @@ -6,6 +6,9 @@ import { type I18nResourceTranslationOptions, type CloneableObject, i18nResourceMissedEventName, + I18nResources, + type InitI18nResources, + type Cloneable, } from '@lynx-js/web-constants'; import { Rpc } from '@lynx-js/web-worker-rpc'; import { dispatchLynxViewEvent } from '../utils/dispatchLynxViewEvent.js'; @@ -38,6 +41,7 @@ export function createRenderAllOnUI( options as CloneableObject, ); }; + const i18nResources = new I18nResources(); const { startMainThread } = prepareMainThreadAPIs( mainToBackgroundRpc, shadowRoot, @@ -48,6 +52,10 @@ export function createRenderAllOnUI( callbacks.onError?.(err); }, triggerI18nResourceFallback, + (initI18nResources: InitI18nResources) => { + i18nResources.setData(initI18nResources); + return i18nResources; + }, ); let mtsGlobalThis!: MainThreadGlobalThis; const start = async (configs: StartMainThreadContextConfig) => { @@ -59,8 +67,12 @@ export function createRenderAllOnUI( ) => { mtsGlobalThis.updatePage?.(...args); }; + const updateI18nResourcesMainThread = (data: Cloneable) => { + i18nResources.setData(data as InitI18nResources); + }; return { start, updateDataMainThread, + updateI18nResourcesMainThread, }; } diff --git a/packages/web-platform/web-core/src/uiThread/createRenderMultiThread.ts b/packages/web-platform/web-core/src/uiThread/createRenderMultiThread.ts index b777521eb8..f25994af03 100644 --- a/packages/web-platform/web-core/src/uiThread/createRenderMultiThread.ts +++ b/packages/web-platform/web-core/src/uiThread/createRenderMultiThread.ts @@ -1,6 +1,7 @@ import { mainThreadStartEndpoint, updateDataEndpoint, + updateI18nResourcesEndpoint, } from '@lynx-js/web-constants'; import type { Rpc } from '@lynx-js/web-worker-rpc'; import { registerReportErrorHandler } from './crossThreadHandlers/registerReportErrorHandler.js'; @@ -27,8 +28,12 @@ export function createRenderMultiThread( registerDispatchLynxViewEventHandler(mainThreadRpc, shadowRoot); const start = mainThreadRpc.createCall(mainThreadStartEndpoint); const updateDataMainThread = mainThreadRpc.createCall(updateDataEndpoint); + const updateI18nResourcesMainThread = mainThreadRpc.createCall( + updateI18nResourcesEndpoint, + ); return { start, updateDataMainThread, + updateI18nResourcesMainThread, }; } diff --git a/packages/web-platform/web-core/src/uiThread/startUIThread.ts b/packages/web-platform/web-core/src/uiThread/startUIThread.ts index b269b95e26..c223b90390 100644 --- a/packages/web-platform/web-core/src/uiThread/startUIThread.ts +++ b/packages/web-platform/web-core/src/uiThread/startUIThread.ts @@ -11,6 +11,7 @@ import { type NapiModulesCall, type NativeModulesCall, updateGlobalPropsEndpoint, + type Cloneable, } from '@lynx-js/web-constants'; import { loadTemplate } from '../utils/loadTemplate.js'; import { createUpdateData } from './crossThreadHandlers/createUpdateData.js'; @@ -53,7 +54,7 @@ export function startUIThread( if (!timeStamp) timeStamp = performance.now() + performance.timeOrigin; markTiming(timingKey, pipelineId, timeStamp); }; - const { start, updateDataMainThread } = allOnUI + const { start, updateDataMainThread, updateI18nResourcesMainThread } = allOnUI ? createRenderAllOnUI( /* main-to-bg rpc*/ mainThreadRpc, shadowRoot, @@ -82,5 +83,7 @@ export function startUIThread( ), sendGlobalEvent, updateGlobalProps: backgroundRpc.createCall(updateGlobalPropsEndpoint), + updateI18nResources: (data: Cloneable) => + updateI18nResourcesMainThread(data), }; } diff --git a/packages/web-platform/web-mainthread-apis/src/prepareMainThreadAPIs.ts b/packages/web-platform/web-mainthread-apis/src/prepareMainThreadAPIs.ts index bb50525316..eddd9e32c9 100644 --- a/packages/web-platform/web-mainthread-apis/src/prepareMainThreadAPIs.ts +++ b/packages/web-platform/web-mainthread-apis/src/prepareMainThreadAPIs.ts @@ -20,6 +20,8 @@ import { switchExposureServiceEndpoint, type I18nResourceTranslationOptions, getCacheI18nResourcesKey, + type InitI18nResources, + type I18nResources, } from '@lynx-js/web-constants'; import { registerCallLepusMethodHandler } from './crossThreadHandlers/registerCallLepusMethodHandler.js'; import { registerGetCustomSectionHandler } from './crossThreadHandlers/registerGetCustomSectionHandler.js'; @@ -37,6 +39,7 @@ export function prepareMainThreadAPIs( triggerI18nResourceFallback: ( options: I18nResourceTranslationOptions, ) => void, + initialI18nResources: (data: InitI18nResources) => I18nResources, ) { const postTimingFlags = backgroundThreadRpc.createCall( postTimingFlagsEndpoint, @@ -90,6 +93,7 @@ export function prepareMainThreadAPIs( receiveEventEndpoint: dispatchJSContextOnMainThreadEndpoint, sendEventEndpoint: dispatchCoreContextOnBackgroundEndpoint, }); + const i18nResources = initialI18nResources(initI18nResources); const mtsGlobalThis = createMainThreadGlobalThis({ jsContext, tagMap, @@ -140,7 +144,6 @@ export function prepareMainThreadAPIs( nativeModulesMap, napiModulesMap, browserConfig, - initI18nResources, }); mtsGlobalThis.renderPage!(initData); mtsGlobalThis.__FlushElementTree(undefined, {}); @@ -182,7 +185,7 @@ export function prepareMainThreadAPIs( publicComponentEvent, createElement, _I18nResourceTranslation: (options: I18nResourceTranslationOptions) => { - const matchedInitI18nResources = initI18nResources.find(i => + const matchedInitI18nResources = i18nResources.data?.find(i => getCacheI18nResourcesKey(i.options) === getCacheI18nResourcesKey(options) ); diff --git a/packages/web-platform/web-tests/tests/web-core.test.ts b/packages/web-platform/web-tests/tests/web-core.test.ts index 07faf5c0bd..d2f7c189a0 100644 --- a/packages/web-platform/web-tests/tests/web-core.test.ts +++ b/packages/web-platform/web-tests/tests/web-core.test.ts @@ -394,4 +394,65 @@ test.describe('web core tests', () => { await wait(2000); expect(success).toBeTruthy(); }); + test('api-update-i18n-resources', async ({ page, browserName }) => { + // firefox dose not support this. + test.skip(browserName === 'firefox'); + await goto(page); + const mainWorker = await getMainThreadWorker(page); + const first = await mainWorker.evaluate(() => { + globalThis.runtime.renderPage = () => {}; + if ( + globalThis.runtime._I18nResourceTranslation({ + locale: 'en', + channel: '2', + fallback_url: '', + }) === undefined + ) { + return true; + } + }); + await wait(500); + await page.evaluate(() => { + document.querySelector('lynx-view').updateI18nResources([ + { + options: { + locale: 'en', + channel: '1', + fallback_url: '', + }, + resource: { + hello: 'hello', + lynx: 'lynx web platform1', + }, + }, + { + options: { + locale: 'en', + channel: '2', + fallback_url: '', + }, + resource: { + hello: 'hello', + lynx: 'lynx web platform2', + }, + }, + ]); + }); + await wait(500); + const second = await mainWorker.evaluate(() => { + globalThis.runtime.renderPage = () => {}; + if ( + JSON.stringify(globalThis.runtime._I18nResourceTranslation({ + locale: 'en', + channel: '2', + fallback_url: '', + })) === '{"hello":"hello","lynx":"lynx web platform2"}' + ) { + return true; + } + }); + await wait(500); + expect(first).toBeTruthy(); + expect(second).toBeTruthy(); + }); }); diff --git a/packages/web-platform/web-worker-runtime/src/mainThread/startMainThread.ts b/packages/web-platform/web-worker-runtime/src/mainThread/startMainThread.ts index fb822e7815..82462ebd87 100644 --- a/packages/web-platform/web-worker-runtime/src/mainThread/startMainThread.ts +++ b/packages/web-platform/web-worker-runtime/src/mainThread/startMainThread.ts @@ -11,6 +11,9 @@ import { dispatchLynxViewEventEndpoint, type CloneableObject, i18nResourceMissedEventName, + I18nResources, + type InitI18nResources, + updateI18nResourcesEndpoint, } from '@lynx-js/web-constants'; import { Rpc } from '@lynx-js/web-worker-rpc'; import { createMarkTimingInternal } from './crossThreadHandlers/createMainthreadMarkTimingInternal.js'; @@ -39,6 +42,7 @@ export function startMainThreadWorker( const docu = new OffscreenDocument({ onCommit: uiFlush, }); + const i18nResources = new I18nResources(); uiThreadRpc.registerHandler(postOffscreenEventEndpoint, docu[_onEvent]); const { startMainThread } = prepareMainThreadAPIs( backgroundThreadRpc, @@ -48,6 +52,10 @@ export function startMainThreadWorker( markTimingInternal, reportError, triggerI18nResourceFallback, + (initI18nResources: InitI18nResources) => { + i18nResources.setData(initI18nResources); + return i18nResources; + }, ); uiThreadRpc.registerHandler( mainThreadStartEndpoint, @@ -57,4 +65,7 @@ export function startMainThreadWorker( }); }, ); + uiThreadRpc?.registerHandler(updateI18nResourcesEndpoint, data => { + i18nResources.setData(data as InitI18nResources); + }); }