Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add Translation class in base and add support lang param #134

Merged
merged 2 commits into from
Mar 4, 2025
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
1 change: 1 addition & 0 deletions packages/page-spy-base/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ export * from './constants';
export * from './request-item';
export * from './socket-base';
export * from './utils';
export * from './translation';
61 changes: 61 additions & 0 deletions packages/page-spy-base/src/translation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { isCN, psLog } from './utils';

export type Lang = 'zh' | 'en';
export type Locales<T extends string> = Record<Lang, Record<T, string>>;

interface TranslationOptions<T extends string> {
locales: Locales<T>;
defaultLang?: Lang;
}

const systemLang = isCN() ? 'zh' : 'en';

export class Translation<T extends string> {
private locales: Locales<T>;

private currentLang: Lang;

constructor(options: TranslationOptions<T>) {
const { locales, defaultLang = systemLang } = options;

if (!locales || Object.keys(locales).length === 0) {
throw new Error('[PageSpy] Locales cannot be empty');
}

if (!locales[defaultLang]) {
throw new Error(`[PageSpy] Language "${defaultLang}" not found`);
}

this.locales = locales;
this.currentLang = defaultLang;
}

// key 的类型变为 T,提供具体键名提示
t(key: T, lang?: Lang): string {
const targetLang = lang || this.currentLang;
const locale = this.locales[targetLang];

if (!locale) {
psLog.warn(`Language '${targetLang}' not found, falling back to default`);
return key;
}

return locale[key] || key;
}

setLang(lang: Lang): void {
if (!this.locales[lang]) {
psLog.error(`Language '${lang}' is not supported`);
return;
}
this.currentLang = lang;
}

getCurrentLang(): string {
return this.currentLang;
}

getSupportedLangs(): string[] {
return Object.keys(this.locales);
}
}
12 changes: 8 additions & 4 deletions packages/page-spy-base/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,11 +117,15 @@ export function isClass(obj: unknown): obj is Function {
return typeof obj === 'function' && typeof obj.prototype !== 'undefined';
}

const CN_IDs = ['zh-CN', 'zh-HK', 'zh-TW', 'zh', 'zh-Hans-CN'];
export function isCN() {
const langs = navigator.languages;
return ['zh-CN', 'zh-HK', 'zh-TW', 'zh', 'zh-Hans-CN'].some((l) => {
return langs.includes(l);
});
if (isBrowser()) {
const { lang } = document.documentElement;
if (lang) return CN_IDs.some((i) => i === lang);

return CN_IDs.some((i) => i === navigator.language);
}
return false;
}

type TypedArray =
Expand Down
10 changes: 5 additions & 5 deletions packages/page-spy-browser/src/assets/locales.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { isCN } from '@huolala-tech/page-spy-base';
import { Translation } from '@huolala-tech/page-spy-base';

const source = {
const locales = {
zh: {
copyLink: '复制在线调试链接',
copied: '复制成功',
Expand All @@ -13,6 +13,6 @@ const source = {
},
};

const locales = isCN() ? source.zh : source.en;

export default locales;
export const i18n = new Translation({
locales,
});
7 changes: 6 additions & 1 deletion packages/page-spy-browser/src/config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ConfigBase } from '@huolala-tech/page-spy-base';
import { ConfigBase, Lang } from '@huolala-tech/page-spy-base';
import { InitConfigBase } from '@huolala-tech/page-spy-types';
import type { Command } from 'iseedeadpeople/dist/common';
import logoUrl from './assets/logo.svg';
Expand Down Expand Up @@ -64,6 +64,10 @@ export interface InitConfig extends InitConfigBase {
* The size of `Command` must be at least 4.
*/
gesture?: Command | null;
/**
* Specify language
*/
lang?: Lang | null;
}

export class Config extends ConfigBase<InitConfig> {
Expand Down Expand Up @@ -97,6 +101,7 @@ export class Config extends ConfigBase<InitConfig> {
title: 'PageSpy',
},
gesture: null,
lang: null,
};

if (!Config.scriptLink) {
Expand Down
39 changes: 21 additions & 18 deletions packages/page-spy-browser/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@ import type { UElement } from './helpers/moveable';
import { moveable } from './helpers/moveable';
import { Config, nodeId } from './config';
import { toast } from './helpers/toast';
import locales from './assets/locales';
import modalLogoSvg from './assets/modal-logo.svg';
import copySvg from './assets/copy.svg';
import { modal } from './helpers/modal';
import classes from './assets/styles/index.module.less';
import { version } from '../package.json';
import { i18n } from './assets/locales';

type UpdateConfig = {
title?: string;
Expand Down Expand Up @@ -136,25 +136,13 @@ class PageSpy {
}
}

private updateConfiguration() {
const { messageCapacity, offline, useSecret } = this.config.get();
if (useSecret === true) {
const cache = JSON.parse(
sessionStorage.getItem(ROOM_SESSION_KEY) as string,
);
this.config.set('secret', cache?.secret || getAuthSecret());
}
socketStore.connectable = true;
socketStore.getPageSpyConfig = () => this.config.get();
socketStore.getClient = () => PageSpy.client;
socketStore.isOffline = offline;
socketStore.messageCapacity = messageCapacity;
}

private async init(ic: InitConfig) {
PageSpy.instance = this;

const config = this.config.mergeConfig(ic);
if (config.lang) {
i18n.setLang(config.lang);
}
this.updateConfiguration();
this.triggerPlugins('onInit', { config, socketStore });

Expand Down Expand Up @@ -185,6 +173,21 @@ class PageSpy {
}
}

private updateConfiguration() {
const { messageCapacity, offline, useSecret } = this.config.get();
if (useSecret === true) {
const cache = JSON.parse(
sessionStorage.getItem(ROOM_SESSION_KEY) as string,
);
this.config.set('secret', cache?.secret || getAuthSecret());
}
socketStore.connectable = true;
socketStore.getPageSpyConfig = () => this.config.get();
socketStore.getClient = () => PageSpy.client;
socketStore.isOffline = offline;
socketStore.messageCapacity = messageCapacity;
}

private cacheIsInvalid() {
try {
const roomCache = sessionStorage.getItem(ROOM_SESSION_KEY);
Expand Down Expand Up @@ -362,7 +365,7 @@ class PageSpy {
<!-- Default button for modal -->
<button class="page-spy-btn" data-primary id="page-spy-copy-link">
<img src="${copySvg}" alt="Copy" />
<span>${locales.copyLink}</span>
<span>${i18n.t('copyLink')}</span>
</button>
`,
'text/html',
Expand Down Expand Up @@ -401,7 +404,7 @@ class PageSpy {
text += `&secret=${secret}`;
}
const copied = copy(text);
const message = copied ? locales.copied : locales.copyFailed;
const message = copied ? i18n.t('copied') : i18n.t('copyFailed');
modal.close();
toast.message(message);
});
Expand Down
8 changes: 5 additions & 3 deletions packages/page-spy-plugin-data-harbor/src/assets/locale.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { isCN } from '@huolala-tech/page-spy-base/dist/utils';
import { Translation } from '@huolala-tech/page-spy-base';

const source = {
const locales = {
zh: {
title: '离线日志',
uploadPeriods: '上传时间段',
Expand Down Expand Up @@ -52,4 +52,6 @@ const source = {
},
};

export const t = isCN() ? source.zh : source.en;
export const i18n = new Translation({
locales,
});
4 changes: 2 additions & 2 deletions packages/page-spy-plugin-data-harbor/src/harbor/blob.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { isBrowser, isNumber } from '@huolala-tech/page-spy-base/dist/utils';
import { isValidMaximum } from '../utils';
import { CacheMessageItem, PeriodActionParams, PeriodItem } from './base';
import { t } from '../assets/locale';
import { i18n } from '../assets/locale';

interface HarborConfig {
maximum: number;
Expand Down Expand Up @@ -123,7 +123,7 @@ export class BlobHarbor {
result = this.container.slice(fData, tData);
} else {
if (fStock === null) {
throw new Error(t.invalidPeriods);
throw new Error(i18n.t('invalidPeriods'));
}

// <stock> [
Expand Down
11 changes: 7 additions & 4 deletions packages/page-spy-plugin-data-harbor/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import {
WholeActionParams,
} from './harbor/base';
import { buildModal } from './utils/modal';
import { t } from './assets/locale';
import { i18n } from './assets/locale';

interface DataHarborConfig {
// Specify the maximum bytes of single harbor's container.
Expand Down Expand Up @@ -113,7 +113,10 @@ export default class DataHarborPlugin implements PageSpyPlugin {
this.$pageSpyConfig = config;
this.$socketStore = socketStore;

const { api, enableSSL, offline } = config;
const { api, enableSSL, offline, lang } = config;
if (lang) {
i18n.setLang(lang);
}
if (!offline && !api) {
psLog.warn(
"Cannot upload log to PageSpy for miss 'api' configuration. See: ",
Expand Down Expand Up @@ -181,7 +184,7 @@ export default class DataHarborPlugin implements PageSpyPlugin {
let data: CacheMessageItem[];
if (isPeriodAction(type)) {
if (!isPeriodActionParams(params) || params.startTime > params.endTime) {
throw new Error(t.invalidParams);
throw new Error(i18n.t('invalidParams'));
}
data = await this.harbor.getPeriodData(params);

Expand All @@ -196,7 +199,7 @@ export default class DataHarborPlugin implements PageSpyPlugin {
(i) => i.timestamp >= validStartTime && i.timestamp <= params.endTime,
).length;
if (validEventCount < 5) {
throw new Error(t.eventCountNotEnough);
throw new Error(i18n.t('eventCountNotEnough'));
}

data.push({
Expand Down
4 changes: 2 additions & 2 deletions packages/page-spy-plugin-data-harbor/src/utils/log.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { psLog } from '@huolala-tech/page-spy-base/dist/utils';
import { CacheMessageItem } from '../harbor/base';
import { t } from '../assets/locale';
import { i18n } from '../assets/locale';
import { formatFilename } from './index';

export type UploadArgs = {
Expand Down Expand Up @@ -68,5 +68,5 @@ export const startDownload = async ({
root.removeChild(a);
URL.revokeObjectURL(url);

psLog.info(`${t.success}`);
psLog.info(`${i18n.t('success')}`);
};
24 changes: 12 additions & 12 deletions packages/page-spy-plugin-data-harbor/src/utils/modal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
successSvg,
copySvg,
} from '../assets/svg';
import { t } from '../assets/locale';
import { i18n } from '../assets/locale';
import { PeriodItem } from '../harbor/base';
import { formatTime } from './index';
import type DataHarborPlugin from '../index';
Expand All @@ -33,7 +33,7 @@ export const buildModal = ({ plugin, modal, toast }: Params) => {
<!-- Add button for default modal -->
<button class="page-spy-btn" data-dashed id="open-log-action">
${cropSvg}
<span>${t.title}</span>
<span>${i18n.t('title')}</span>
</button>

<!-- Show modal content on button#open-log-action clicked -->
Expand All @@ -45,7 +45,7 @@ export const buildModal = ({ plugin, modal, toast }: Params) => {
<div class="${classes.periodInfo}">
<div class="${classes.label}">
<div class="${classes.periodTips}">
<b>${t.selectPeriod}</b>
<b>${i18n.t('selectPeriod')}</b>
</div>
<button class="${classes.refreshButton}">${refreshSvg}</button>
</div>
Expand All @@ -58,30 +58,30 @@ export const buildModal = ({ plugin, modal, toast }: Params) => {
</div>
</div>
<div class="${classes.remarkInfo}">
<div class="${classes.label}">${t.remark}</div>
<textarea rows="5" id="harbor-remark" placeholder="${t.remarkPlaceholder}"></textarea>
<div class="${classes.label}">${i18n.t('remark')}</div>
<textarea rows="5" id="harbor-remark" placeholder="${i18n.t('remarkPlaceholder')}"></textarea>
</div>
</div>

<!-- Upload / Download log button -->
<button class="page-spy-btn" data-primary id="upload-periods">
${uploadPeriodsSvg}
<span>${t.uploadPeriods}</span>
<span>${i18n.t('uploadPeriods')}</span>
</button>
<button class="page-spy-btn" id="download-periods">
${downloadPeriodsSvg}
<span>${t.downloadPeriods}</span>
<span>${i18n.t('downloadPeriods')}</span>
</button>

<!-- Result -->
<div class="${classes.result}">
${successSvg}
<b>${t.success}</b>
<b>${i18n.t('success')}</b>
</div>

<button class="page-spy-btn" data-primary id="copy-replay-url" data-url>
${copySvg}
<span>${t.copyUrl}</span>
<span>${i18n.t('copyUrl')}</span>
</button>
`,
'text/html',
Expand Down Expand Up @@ -168,7 +168,7 @@ export const buildModal = ({ plugin, modal, toast }: Params) => {
refreshButton.addEventListener('click', () => {
refreshButton.disabled = true;
refreshPeriods();
toast.message(t.refreshed);
toast.message(i18n.t('refreshed'));
refreshButton.disabled = false;
});
minThumb.addEventListener('input', function () {
Expand Down Expand Up @@ -206,7 +206,7 @@ export const buildModal = ({ plugin, modal, toast }: Params) => {
copyUrlButton.addEventListener('click', () => {
const { url } = copyUrlButton.dataset;
const ok = copy(url!);
toast.message(ok ? t.copied : t.copyFailed);
toast.message(ok ? i18n.t('copied') : i18n.t('copyFailed'));
modal.close();
});
uploadPeriodsButton.addEventListener('click', async () => {
Expand All @@ -233,7 +233,7 @@ export const buildModal = ({ plugin, modal, toast }: Params) => {
try {
downloadPeriodsButton.disabled = true;
await plugin.onOfflineLog('download-periods', getSelectedPeriod());
toast.message(t.success);
toast.message(i18n.t('success'));
} catch (e: any) {
psLog.error(e);
toast.message(e.message);
Expand Down
Loading
Loading