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
13 changes: 13 additions & 0 deletions .changeset/heavy-sides-enter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
"@lynx-js/web-mainthread-apis": patch
"@lynx-js/web-worker-runtime": patch
"@lynx-js/web-constants": patch
"@lynx-js/web-core": patch
---

feat: supports `lynx.getI18nResource()` and `onI18nResourceReady` event in bts.

- `lynx.getI18nResource()` can be used to get i18nResource in bts, it has two data sources:
- the result of `_I18nResourceTranslation()`
- lynx-view `updateI18nResources(data: InitI18nResources, options: I18nResourceTranslationOptions)`, it will be matched to the correct i8nResource as a result of `lynx.getI18nResource()`
- `onI18nResourceReady` event can be used to listen `_I18nResourceTranslation` and lynx-view `updateI18nResources` execution.
10 changes: 10 additions & 0 deletions packages/web-platform/web-constants/src/endpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,3 +218,13 @@ export const updateI18nResourcesEndpoint = createRpcEndpoint<
[Cloneable],
void
>('updateI18nResources', false, false);

export const updateI18nResourceEndpoint = createRpcEndpoint<
[Cloneable | undefined],
void
>('updateI18nResource', false, false);

export const dispatchI18nResourceEndpoint = createRpcEndpoint<
[Cloneable],
void
>('dispatchI18nResource', false, false);
12 changes: 12 additions & 0 deletions packages/web-platform/web-constants/src/types/I18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export type InitI18nResources = Array<{

export const i18nResourceMissedEventName = 'i18nResourceMissed' as const;

// The purpose of using class is to keep the reference when reassigning
export class I18nResources {
data?: InitI18nResources;
constructor(data?: InitI18nResources) {
Expand All @@ -36,3 +37,14 @@ export class I18nResources {
this.data = data;
}
}

// The purpose of using class is to keep the reference when reassigning
export class I18nResource {
data?: Cloneable;
constructor(data?: Cloneable) {
this.data = data;
}
setData(data: Cloneable) {
this.data = data;
}
}
3 changes: 3 additions & 0 deletions packages/web-platform/web-constants/src/types/NativeApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the Apache License Version 2.0 that can be found in the

import type { CloneableObject } from './Cloneable.js';
import type { I18nResource } from './I18n.js';
import type { LynxContextEventTarget } from './LynxContextEventTarget.js';
import type { PerformancePipelineOptions } from './Performance.js';

Expand Down Expand Up @@ -189,4 +190,6 @@ export interface NativeApp {

setSharedData<T>(dataKey: string, dataVal: T): void;
getSharedData<T = unknown>(dataKey: string): T | undefined;

i18nResource: I18nResource;
}
8 changes: 6 additions & 2 deletions packages/web-platform/web-core/src/apis/LynxView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
import {
inShadowRootStyles,
type Cloneable,
type I18nResourceTranslationOptions,
type InitI18nResources,
type LynxTemplate,
type NapiModulesCall,
Expand Down Expand Up @@ -160,8 +161,11 @@ export class LynxView extends HTMLElement {
* @method
* update the `__initData` and trigger essential flow
*/
updateI18nResources(data: InitI18nResources) {
this.#instance?.updateI18nResources(data as Cloneable);
updateI18nResources(
data: InitI18nResources,
options: I18nResourceTranslationOptions,
) {
this.#instance?.updateI18nResources(data, options);
}

#overrideLynxTagToHTMLTagMap: Record<string, string> = { 'page': 'div' };
Expand Down
6 changes: 5 additions & 1 deletion packages/web-platform/web-core/src/apis/createLynxView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import type {
Cloneable,
I18nResourceTranslationOptions,
InitI18nResources,
NapiModulesMap,
NativeModulesMap,
Expand Down Expand Up @@ -42,7 +43,10 @@ export interface LynxView {
dispose(): Promise<void>;
sendGlobalEvent: RpcCallType<typeof sendGlobalEventEndpoint>;
updateGlobalProps: (data: Cloneable) => void;
updateI18nResources: (data: Cloneable) => void;
updateI18nResources: (
data: InitI18nResources,
options: I18nResourceTranslationOptions,
) => void;
}

export function createLynxView(configs: LynxViewConfigs): LynxView {
Expand Down
18 changes: 18 additions & 0 deletions packages/web-platform/web-core/src/uiThread/startBackground.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import {
getCacheI18nResourcesKey,
markTimingEndpoint,
sendGlobalEventEndpoint,
updateDataEndpoint,
updateI18nResourceEndpoint,
type Cloneable,
type I18nResourceTranslationOptions,
type InitI18nResources,
type NapiModulesCall,
type NativeModulesCall,
} from '@lynx-js/web-constants';
Expand Down Expand Up @@ -53,9 +58,22 @@ export function startBackground(
const sendGlobalEvent = backgroundRpc.createCall(sendGlobalEventEndpoint);
const markTiming = backgroundRpc.createCall(markTimingEndpoint);
const updateDataBackground = backgroundRpc.createCall(updateDataEndpoint);
const updateI18nResourceBackground = (
data: InitI18nResources,
options: I18nResourceTranslationOptions,
) => {
const matchedResources = data.find(i =>
getCacheI18nResourcesKey(i.options)
=== getCacheI18nResourcesKey(options)
);
backgroundRpc.invoke(updateI18nResourceEndpoint, [
matchedResources?.resource as Cloneable,
]);
};
return {
sendGlobalEvent,
markTiming,
updateDataBackground,
updateI18nResourceBackground,
};
}
13 changes: 10 additions & 3 deletions packages/web-platform/web-core/src/uiThread/startUIThread.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,12 @@ export function startUIThread(
backgroundRpc,
terminateWorkers,
} = bootWorkers(lynxGroupId, allOnUI);
const { markTiming, sendGlobalEvent, updateDataBackground } = startBackground(
const {
markTiming,
sendGlobalEvent,
updateDataBackground,
updateI18nResourceBackground,
} = startBackground(
backgroundRpc,
shadowRoot,
callbacks,
Expand Down Expand Up @@ -83,7 +88,9 @@ export function startUIThread(
),
sendGlobalEvent,
updateGlobalProps: backgroundRpc.createCall(updateGlobalPropsEndpoint),
updateI18nResources: (data: Cloneable) =>
updateI18nResourcesMainThread(data),
updateI18nResources: (...args) => {
updateI18nResourcesMainThread(args[0] as Cloneable);
updateI18nResourceBackground(...args);
Comment on lines +91 to +93
Copy link

Copilot AI Jun 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Consider explicitly naming the parameters for the updateI18nResources function instead of using rest parameters, to improve clarity and maintainability.

Suggested change
updateI18nResources: (...args) => {
updateI18nResourcesMainThread(args[0] as Cloneable);
updateI18nResourceBackground(...args);
updateI18nResources: (resource: Cloneable, ...additionalArgs) => {
updateI18nResourcesMainThread(resource);
updateI18nResourceBackground(resource, ...additionalArgs);

Copilot uses AI. Check for mistakes.
},
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import {
getCacheI18nResourcesKey,
type InitI18nResources,
type I18nResources,
dispatchI18nResourceEndpoint,
type Cloneable,
} from '@lynx-js/web-constants';
import { registerCallLepusMethodHandler } from './crossThreadHandlers/registerCallLepusMethodHandler.js';
import { registerGetCustomSectionHandler } from './crossThreadHandlers/registerGetCustomSectionHandler.js';
Expand Down Expand Up @@ -54,6 +56,9 @@ export function prepareMainThreadAPIs(
publicComponentEventEndpoint,
);
const postExposure = backgroundThreadRpc.createCall(postExposureEndpoint);
const dispatchI18nResource = backgroundThreadRpc.createCall(
dispatchI18nResourceEndpoint,
);
markTimingInternal('lepus_execute_start');
async function startMainThread(
config: StartMainThreadContextConfig,
Expand Down Expand Up @@ -189,6 +194,7 @@ export function prepareMainThreadAPIs(
getCacheI18nResourcesKey(i.options)
=== getCacheI18nResourcesKey(options)
);
dispatchI18nResource(matchedInitI18nResources?.resource as Cloneable);
if (matchedInitI18nResources) {
return matchedInitI18nResources.resource;
}
Expand Down
179 changes: 178 additions & 1 deletion packages/web-platform/web-tests/tests/web-core.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,11 @@ test.describe('web core tests', () => {
lynx: 'lynx web platform2',
},
},
]);
], {
locale: 'en',
channel: '2',
fallback_url: '',
});
});
await wait(500);
const second = await mainWorker.evaluate(() => {
Expand All @@ -455,4 +459,177 @@ test.describe('web core tests', () => {
expect(first).toBeTruthy();
expect(second).toBeTruthy();
});
test('api-get-i18n-resource-by-mts', async ({ page, browserName }) => {
// firefox dose not support this.
Copy link

Copilot AI Jun 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in comment: 'dose' should be 'does'.

Suggested change
// firefox dose not support this.
// firefox does not support this.

Copilot uses AI. Check for mistakes.
test.skip(browserName === 'firefox');
await goto(page);
const mainWorker = await getMainThreadWorker(page);
await mainWorker.evaluate(() => {
globalThis.runtime.renderPage = () => {};
});
await wait(500);
const backWorker = await getBackgroundThreadWorker(page);
const first = await backWorker?.evaluate(() =>
globalThis.runtime.lynx.getNativeLynx().getI18nResource() === undefined
);
await wait(500);
await mainWorker?.evaluate(() => {
globalThis.runtime._I18nResourceTranslation({
locale: 'en',
channel: '1',
fallback_url: '',
});
});
const second = await backWorker?.evaluate(() =>
JSON.stringify(globalThis.runtime.lynx.getNativeLynx().getI18nResource())
=== '{"hello":"hello","lynx":"lynx web platform1"}'
);
expect(first).toBeTruthy();
expect(second).toBeTruthy();
});
test('api-get-i18n-resource-by-lynx-update', async ({ page, browserName }) => {
// firefox dose not support this.
Copy link

Copilot AI Jun 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in comment: 'dose' should be 'does'.

Suggested change
// firefox dose not support this.
// firefox does not support this.

Copilot uses AI. Check for mistakes.
test.skip(browserName === 'firefox');
await goto(page);
const mainWorker = await getMainThreadWorker(page);
await mainWorker.evaluate(() => {
globalThis.runtime.renderPage = () => {};
});
await wait(500);
const backWorker = await getBackgroundThreadWorker(page);
const first = await backWorker?.evaluate(() =>
globalThis.runtime.lynx.getNativeLynx().getI18nResource() === undefined
);
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',
},
},
], {
locale: 'en',
channel: '2',
fallback_url: '',
});
});
await wait(500);
const second = await backWorker?.evaluate(() =>
JSON.stringify(globalThis.runtime.lynx.getNativeLynx().getI18nResource())
=== '{"hello":"hello","lynx":"lynx web platform2"}'
);
expect(first).toBeTruthy();
expect(second).toBeTruthy();
});
test('api-onI18nResourceReady-by-mts', async ({ page, browserName }) => {
// firefox dose not support this.
test.skip(browserName === 'firefox');
await goto(page);
const mainWorker = await getMainThreadWorker(page);
await mainWorker.evaluate(() => {
globalThis.runtime.renderPage = () => {};
});
await wait(500);
let success = false;
await page.on('console', async (message) => {
if (message.text() === 'onI18nResourceReady') {
success = true;
}
});
const backWorker = await getBackgroundThreadWorker(page);
await backWorker?.evaluate(() => {
globalThis.runtime.GlobalEventEmitter.addListener(
'onI18nResourceReady',
() => {
console.log('onI18nResourceReady');
},
);
});
await wait(500);
await mainWorker?.evaluate(() => {
globalThis.runtime._I18nResourceTranslation({
locale: 'en',
channel: '1',
fallback_url: '',
});
});
await wait(500);
expect(success).toBeTruthy();
});
test('api-onI18nResourceReady-by-lynx-update', async ({ page, browserName }) => {
// firefox dose not support this.
test.skip(browserName === 'firefox');
await goto(page);
const mainWorker = await getMainThreadWorker(page);
await mainWorker.evaluate(() => {
globalThis.runtime.renderPage = () => {};
});
await wait(500);
let success = false;
await page.on('console', async (message) => {
if (message.text() === 'onI18nResourceReady') {
success = true;
}
});
const backWorker = await getBackgroundThreadWorker(page);
await backWorker?.evaluate(() => {
globalThis.runtime.GlobalEventEmitter.addListener(
'onI18nResourceReady',
() => {
console.log('onI18nResourceReady');
},
);
});
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',
},
},
], {
locale: 'en',
channel: '2',
fallback_url: '',
});
});
await wait(500);
expect(success).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,6 @@ export function createBackgroundLynx(
createElement(_: string, id: string) {
return createElement(id, uiThreadRpc);
},
getI18nResource: () => nativeApp.i18nResource.data,
};
}
Loading
Loading