diff --git a/apps/frontend/src/plugins/debug-pojo.ts b/apps/frontend/src/plugins/debug-pojo.ts deleted file mode 100644 index e9fe18c7d7..0000000000 --- a/apps/frontend/src/plugins/debug-pojo.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { defineNuxtPlugin } from '#imports' - -export default defineNuxtPlugin((nuxt) => { - if (import.meta.server) { - nuxt.hooks.hook('app:rendered', (ctx) => { - if (ctx.ssrContext?.payload?.data) { - const check = (obj: any, path = 'payload') => { - if (!obj || typeof obj !== 'object') return - if ( - obj.constructor && - obj.constructor.name !== 'Object' && - obj.constructor.name !== 'Array' - ) { - console.error(`Non-POJO at ${path}:`, obj.constructor.name) - } - for (const [k, v] of Object.entries(obj)) { - check(v, `${path}.${k}`) - } - } - check(ctx.ssrContext.payload.data) - } - }) - } -}) diff --git a/apps/frontend/src/plugins/i18n.ts b/apps/frontend/src/plugins/i18n.ts index b39ef67afb..fc62bea104 100644 --- a/apps/frontend/src/plugins/i18n.ts +++ b/apps/frontend/src/plugins/i18n.ts @@ -61,22 +61,26 @@ export default defineNuxtPlugin({ name: 'i18n', enforce: 'pre', async setup(nuxtApp) { - // ONLY locale needs request-scoping (what language this request uses) const locale = useState('i18n-locale', () => DEFAULT_LOCALE) function t(key: string, values?: Record): string { - const msg = messageCache.get(locale.value)?.[key] ?? messageCache.get(DEFAULT_LOCALE)?.[key] + const currentLocale = locale.value + const msg = messageCache.get(currentLocale)?.[key] ?? messageCache.get(DEFAULT_LOCALE)?.[key] if (!msg) return key if (!values || Object.keys(values).length === 0) return msg - const cacheKey = `${locale.value}:${msg}` + const cacheKey = `${currentLocale}:${msg}` let formatter = formatterCache.get(cacheKey) if (!formatter) { - formatter = new IntlMessageFormat(msg, locale.value) + formatter = new IntlMessageFormat(msg, currentLocale) formatterCache.set(cacheKey, formatter) } try { - return formatter.format(values) as string + const result = formatter.format(values) as string + if (import.meta.dev && typeof result !== 'string') { + console.error('[i18n] t() returned non-string:', typeof result) + } + return result } catch { return msg } diff --git a/apps/frontend/src/plugins/intl-payload-safety.ts b/apps/frontend/src/plugins/intl-payload-safety.ts new file mode 100644 index 0000000000..f8211932fa --- /dev/null +++ b/apps/frontend/src/plugins/intl-payload-safety.ts @@ -0,0 +1,17 @@ +export default definePayloadPlugin(() => { + definePayloadReducer('IntlMessageFormat', (value) => { + if (value?.constructor?.name === 'IntlMessageFormat' || value?._ast !== undefined) { + if (import.meta.dev) { + console.warn('[i18n] IntlMessageFormat instance leaked into payload - returning null') + console.warn('[i18n] This indicates a bug that should be fixed upstream') + console.warn('[i18n] Leaked value:', value) + } + + return null + } + + return false + }) + + definePayloadReviver('IntlMessageFormat', () => null) +}) diff --git a/apps/frontend/src/plugins/payload-debugger.ts b/apps/frontend/src/plugins/payload-debugger.ts new file mode 100644 index 0000000000..dd579008d7 --- /dev/null +++ b/apps/frontend/src/plugins/payload-debugger.ts @@ -0,0 +1,37 @@ +function findNonPOJOs( + obj: unknown, + path: string, + found: Array<{ path: string; type: string }> = [], +): Array<{ path: string; type: string }> { + if (obj === null || typeof obj !== 'object') return found + + const proto = Object.getPrototypeOf(obj) + if (proto !== Object.prototype && proto !== null && !Array.isArray(obj)) { + found.push({ path, type: obj.constructor?.name ?? 'Unknown' }) + } + + for (const [key, value] of Object.entries(obj)) { + findNonPOJOs(value, `${path}.${key}`, found) + } + + return found +} + +export default defineNuxtPlugin((nuxtApp) => { + if (!import.meta.dev || !import.meta.server) return + + nuxtApp.hooks.hook('app:rendered', () => { + try { + JSON.stringify(nuxtApp.payload) + } catch (e) { + console.error('[payload-debugger] Payload serialization would fail:', e) + const nonPOJOs = findNonPOJOs(nuxtApp.payload, 'payload') + if (nonPOJOs.length > 0) { + console.error('[payload-debugger] Non-POJO objects found in payload:') + for (const { path, type } of nonPOJOs) { + console.error(` - ${path}: ${type}`) + } + } + } + }) +}) diff --git a/packages/ui/src/components/base/IntlFormatted.vue b/packages/ui/src/components/base/IntlFormatted.vue index 2836087494..19dc0d7c59 100644 --- a/packages/ui/src/components/base/IntlFormatted.vue +++ b/packages/ui/src/components/base/IntlFormatted.vue @@ -1,6 +1,6 @@