Fix translation error loop (#10636)

* Test issue duplicates and fix translation error loop

Signed-off-by: Artem Savchenko <armisav@gmail.com>

* Clean up

Signed-off-by: Artem Savchenko <armisav@gmail.com>

---------

Signed-off-by: Artem Savchenko <armisav@gmail.com>
This commit is contained in:
Artyom Savchenko
2026-03-16 13:20:46 +07:00
committed by GitHub
parent 9edb67d632
commit 9ddebe0d9c
3 changed files with 41 additions and 15 deletions
@@ -78,7 +78,8 @@ export const Analytics = {
addEventListener(PlatformEvent, async (_event, _status: Status) => {
if (_status.severity === Severity.ERROR) {
const label = await translate(_status.code, _status.params, 'en')
// Skip error broadcast in translate to avoid infinite loop
const label = await translate(_status.code, _status.params, 'en', true)
Analytics.handleError(new Error(label))
}
})
@@ -88,7 +88,16 @@ async function createClient (
config: ServerConfig,
options: ConnectOptions
): Promise<PlatformClient> {
addLocation(clientId, () => import(/* webpackChunkName: "client" */ '@hcengineering/client-resources'))
addLocation(clientId, () => {
// In Node/Jest environments, dynamic import callbacks can fail without
// --experimental-vm-modules. Use require there and keep dynamic import
// for browser/bundler environments.
if (typeof window === 'undefined') {
// eslint-disable-next-line @typescript-eslint/no-var-requires
return Promise.resolve(require('@hcengineering/client-resources'))
}
return import(/* webpackChunkName: "client" */ '@hcengineering/client-resources')
})
const { socketFactory, connectionTimeout } = options
+29 -13
View File
@@ -64,11 +64,21 @@ export async function loadPluginStrings (locale: string, force: boolean = false)
}
}
async function loadTranslationsForComponent (plugin: Plugin, locale: string): Promise<Messages | Status> {
async function setStatus (status: Status, skipError?: boolean): Promise<void> {
if (skipError !== true) {
await setPlatformStatus(status)
}
}
async function loadTranslationsForComponent (
plugin: Plugin,
locale: string,
skipError?: boolean
): Promise<Messages | Status> {
const loader = loaders.get(plugin)
if (loader === undefined) {
const status = new Status(Severity.ERROR, platform.status.NoLoaderForStrings, { plugin })
await setPlatformStatus(status)
await setStatus(status, skipError)
return status
}
try {
@@ -79,7 +89,7 @@ async function loadTranslationsForComponent (plugin: Plugin, locale: string): Pr
return (await loader('en')) as Record<string, IntlString> | Status
} catch (err: any) {
const status = unknownError(err)
await setPlatformStatus(status)
await setStatus(status, skipError)
return status
}
}
@@ -104,7 +114,11 @@ function getCachedTranslation (id: _IdInfo, locale: string): IntlString | Status
}
}
async function getTranslation (id: _IdInfo, locale: string): Promise<IntlString | Status | undefined> {
async function getTranslation (
id: _IdInfo,
locale: string,
skipError?: boolean
): Promise<IntlString | Status | undefined> {
try {
const localtTanslations = translations.get(locale) ?? new Map<Plugin, Messages | Status<any>>()
if (!translations.has(locale)) {
@@ -112,7 +126,7 @@ async function getTranslation (id: _IdInfo, locale: string): Promise<IntlString
}
let messages = localtTanslations.get(id.component)
if (messages === undefined) {
messages = await loadTranslationsForComponent(id.component, locale)
messages = await loadTranslationsForComponent(id.component, locale, skipError)
localtTanslations.set(id.component, messages)
}
if (messages instanceof Status) {
@@ -124,7 +138,7 @@ async function getTranslation (id: _IdInfo, locale: string): Promise<IntlString
} else {
let eng = englishTranslationsForMissing.get(id.component)
if (eng === undefined) {
eng = await loadTranslationsForComponent(id.component, 'en')
eng = await loadTranslationsForComponent(id.component, 'en', skipError)
englishTranslationsForMissing.set(id.component, eng)
}
if (eng instanceof Status) {
@@ -137,7 +151,7 @@ async function getTranslation (id: _IdInfo, locale: string): Promise<IntlString
}
} catch (err) {
const status = unknownError(err)
await setPlatformStatus(status)
await setStatus(status, skipError)
return status
}
}
@@ -151,7 +165,8 @@ async function getTranslation (id: _IdInfo, locale: string): Promise<IntlString
export async function translate<P extends Record<string, any>> (
message: IntlString<P>,
params: P,
language?: string
language?: string,
skipError?: boolean
): Promise<string> {
const locale = language ?? getMetadata(platform.metadata.locale) ?? 'en'
const localCache = cache.get(locale) ?? new Map<IntlString, IntlMessageFormat | Status>()
@@ -171,7 +186,7 @@ export async function translate<P extends Record<string, any>> (
if (id.component === _EmbeddedId) {
return id.name
}
const translation = getCachedTranslation(id, locale) ?? (await getTranslation(id, locale)) ?? message
const translation = getCachedTranslation(id, locale) ?? (await getTranslation(id, locale, skipError)) ?? message
if (translation instanceof Status) {
localCache.set(message, translation)
return message
@@ -181,7 +196,7 @@ export async function translate<P extends Record<string, any>> (
return compiled.format(params)
} catch (err) {
const status = unknownError(err)
void setPlatformStatus(status)
await setStatus(status, skipError)
localCache.set(message, status)
return message
}
@@ -194,7 +209,8 @@ export function translateCB<P extends Record<string, any>> (
message: IntlString<P>,
params: P,
language: string | undefined,
resolve: (value: string) => void
resolve: (value: string) => void,
skipError?: boolean
): void {
const locale = language ?? getMetadata(platform.metadata.locale) ?? 'en'
const localCache = cache.get(locale) ?? new Map<IntlString, IntlMessageFormat | Status>()
@@ -219,7 +235,7 @@ export function translateCB<P extends Record<string, any>> (
}
} catch (err) {
const status = unknownError(err)
void setPlatformStatus(status)
void setStatus(status, skipError)
localCache.set(message, status)
resolve(message)
return
@@ -232,7 +248,7 @@ export function translateCB<P extends Record<string, any>> (
})
.catch((err) => {
const status = unknownError(err)
void setPlatformStatus(status)
void setStatus(status, skipError)
localCache.set(message, status)
resolve(message)
})