From 8598f3912ecc16f9b7c3f502e09d9ea96f7e507d Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Fri, 8 Aug 2025 11:26:18 +0900 Subject: per-locale bundle & inline locale (#16369) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: split entry file by locale name * chore: とりあえず transform hook で雑に分割 * chore: とりあえず transform 結果をいい感じに * chore: concurrent buildで高速化 * chore: vite ではローケルのないものをビルドして後処理でどうにかするように * chore: 後処理のためにi18n.jを単体になるように切り出す * chore: use typescript * chore: remove unref(i18n) in vite build process * chore: inline variable * fix: build error * fix: i18n.ts.something.replaceAll() become error * chore: ignore export specifier from error * chore: support i18n.tsx as object * chore: process literal for all files * chore: split config and locale * chore: inline locale name * chore: remove updating locale in boot common * chore: use top-level await to load locales * chore: inline locale * chore: remove loading locale from boot.js * chore: remove loading locale from boot.js * コメント追加 * fix test; fetchに失敗する * import削除ログをdebugレベルに * fix: watch pug * chore: use hash for entry files * chore: remove es-module-lexer from dependencies * chore: move to frontend-builder * chore: use inline locale in embed * chore: refetch json on hot reload * feat: store localization related to boot.js in backend in bootloaderLocales localstorage * 応急処置を戻す * fix spex * fix `Using i18n identifier "e" directly. Skipping inlining.` warning * refactor: use scriptsDir parameter * chore: remove i18n from depmap * chore: make build crash if errors * error -> warn few conditions * use inline object * update localstorage keys * remove accessing locale localstorage * fix: failed to process i18n.tsx.aaa({x:i18n.bbb}) --- packages/backend/src/server/web/boot.embed.js | 85 +++++++++----------- packages/backend/src/server/web/boot.js | 91 +++++++++------------- .../backend/src/server/web/views/base-embed.pug | 3 +- packages/backend/src/server/web/views/base.pug | 3 +- 4 files changed, 75 insertions(+), 107 deletions(-) (limited to 'packages/backend/src/server/web') diff --git a/packages/backend/src/server/web/boot.embed.js b/packages/backend/src/server/web/boot.embed.js index 9de1275380..022ff064ad 100644 --- a/packages/backend/src/server/web/boot.embed.js +++ b/packages/backend/src/server/web/boot.embed.js @@ -32,61 +32,30 @@ } //#region Detect language & fetch translations - if (!localStorage.hasOwnProperty('locale')) { - const supportedLangs = LANGS; - let lang = localStorage.getItem('lang'); - if (lang == null || !supportedLangs.includes(lang)) { - if (supportedLangs.includes(navigator.language)) { - lang = navigator.language; - } else { - lang = supportedLangs.find(x => x.split('-')[0] === navigator.language); - - // Fallback - if (lang == null) lang = 'en-US'; - } - } - - const metaRes = await window.fetch('/api/meta', { - method: 'POST', - body: JSON.stringify({}), - credentials: 'omit', - cache: 'no-cache', - headers: { - 'Content-Type': 'application/json', - }, - }); - if (metaRes.status !== 200) { - renderError('META_FETCH'); - return; - } - const meta = await metaRes.json(); - const v = meta.version; - if (v == null) { - renderError('META_FETCH_V'); - return; - } + const supportedLangs = LANGS; + /** @type { string } */ + let lang = localStorage.getItem('lang'); + if (lang == null || !supportedLangs.includes(lang)) { + if (supportedLangs.includes(navigator.language)) { + lang = navigator.language; + } else { + lang = supportedLangs.find(x => x.split('-')[0] === navigator.language); - // for https://github.com/misskey-dev/misskey/issues/10202 - if (lang == null || lang.toString == null || lang.toString() === 'null') { - console.error('invalid lang value detected!!!', typeof lang, lang); - lang = 'en-US'; + // Fallback + if (lang == null) lang = 'en-US'; } + } - const localRes = await window.fetch(`/assets/locales/${lang}.${v}.json`); - if (localRes.status === 200) { - localStorage.setItem('lang', lang); - localStorage.setItem('locale', await localRes.text()); - localStorage.setItem('localeVersion', v); - } else { - renderError('LOCALE_FETCH'); - return; - } + // for https://github.com/misskey-dev/misskey/issues/10202 + if (lang == null || lang.toString == null || lang.toString() === 'null') { + console.error('invalid lang value detected!!!', typeof lang, lang); + lang = 'en-US'; } //#endregion //#region Script async function importAppScript() { - await import(`/embed_vite/${CLIENT_ENTRY}`) + await import(CLIENT_ENTRY ? `/embed_vite/${CLIENT_ENTRY.replace('scripts', lang)}` : '/embed_vite/src/_boot_.ts') .catch(async e => { console.error(e); renderError('APP_IMPORT'); @@ -115,10 +84,26 @@ await new Promise(resolve => window.addEventListener('DOMContentLoaded', resolve)); } - const locale = JSON.parse(localStorage.getItem('locale') || '{}'); + let messages = null; + const bootloaderLocales = localStorage.getItem('bootloaderLocales'); + if (bootloaderLocales) { + messages = JSON.parse(bootloaderLocales); + } + if (!messages) { + // older version of misskey does not store bootloaderLocales, stores locale as a whole + const legacyLocale = localStorage.getItem('locale'); + if (legacyLocale) { + const parsed = JSON.parse(legacyLocale); + messages = { + ...(parsed._bootErrors ?? {}), + reload: parsed.reload, + }; + } + } + if (!messages) messages = {}; - const title = locale?._bootErrors?.title || 'Failed to initialize Misskey'; - const reload = locale?.reload || 'Reload'; + const title = messages?.title || 'Failed to initialize Misskey'; + const reload = messages?.reload || 'Reload'; document.body.innerHTML = `
${title}
diff --git a/packages/backend/src/server/web/boot.js b/packages/backend/src/server/web/boot.js index 1a30e9ed2b..0c0b46f82b 100644 --- a/packages/backend/src/server/web/boot.js +++ b/packages/backend/src/server/web/boot.js @@ -22,62 +22,31 @@ return; } - //#region Detect language & fetch translations - if (!localStorage.hasOwnProperty('locale')) { - const supportedLangs = LANGS; - let lang = localStorage.getItem('lang'); - if (lang == null || !supportedLangs.includes(lang)) { - if (supportedLangs.includes(navigator.language)) { - lang = navigator.language; - } else { - lang = supportedLangs.find(x => x.split('-')[0] === navigator.language); - - // Fallback - if (lang == null) lang = 'en-US'; - } - } - - const metaRes = await window.fetch('/api/meta', { - method: 'POST', - body: JSON.stringify({}), - credentials: 'omit', - cache: 'no-cache', - headers: { - 'Content-Type': 'application/json', - }, - }); - if (metaRes.status !== 200) { - renderError('META_FETCH'); - return; - } - const meta = await metaRes.json(); - const v = meta.version; - if (v == null) { - renderError('META_FETCH_V'); - return; - } + //#region Detect language + const supportedLangs = LANGS; + /** @type { string } */ + let lang = localStorage.getItem('lang'); + if (lang == null || !supportedLangs.includes(lang)) { + if (supportedLangs.includes(navigator.language)) { + lang = navigator.language; + } else { + lang = supportedLangs.find(x => x.split('-')[0] === navigator.language); - // for https://github.com/misskey-dev/misskey/issues/10202 - if (lang == null || lang.toString == null || lang.toString() === 'null') { - console.error('invalid lang value detected!!!', typeof lang, lang); - lang = 'en-US'; + // Fallback + if (lang == null) lang = 'en-US'; } + } - const localRes = await window.fetch(`/assets/locales/${lang}.${v}.json`); - if (localRes.status === 200) { - localStorage.setItem('lang', lang); - localStorage.setItem('locale', await localRes.text()); - localStorage.setItem('localeVersion', v); - } else { - renderError('LOCALE_FETCH'); - return; - } + // for https://github.com/misskey-dev/misskey/issues/10202 + if (lang == null || lang.toString == null || lang.toString() === 'null') { + console.error('invalid lang value detected!!!', typeof lang, lang); + lang = 'en-US'; } //#endregion //#region Script async function importAppScript() { - await import(`/vite/${CLIENT_ENTRY}`) + await import(CLIENT_ENTRY ? `/vite/${CLIENT_ENTRY.replace('scripts', lang)}` : '/vite/src/_boot_.ts') .catch(async e => { console.error(e); renderError('APP_IMPORT', e); @@ -162,9 +131,25 @@ await new Promise(resolve => window.addEventListener('DOMContentLoaded', resolve)); } - const locale = JSON.parse(localStorage.getItem('locale') || '{}'); + let messages = null; + const bootloaderLocales = localStorage.getItem('bootloaderLocales'); + if (bootloaderLocales) { + messages = JSON.parse(bootloaderLocales); + } + if (!messages) { + // older version of misskey does not store bootloaderLocales, stores locale as a whole + const legacyLocale = localStorage.getItem('locale'); + if (legacyLocale) { + const parsed = JSON.parse(legacyLocale); + messages = { + ...(parsed._bootErrors ?? {}), + reload: parsed.reload, + }; + } + } + if (!messages) messages = {}; - const messages = Object.assign({ + messages = Object.assign({ title: 'Failed to initialize Misskey', solution: 'The following actions may solve the problem.', solution1: 'Update your os and browser', @@ -176,8 +161,8 @@ otherOption2: 'Start the simple client', otherOption3: 'Start the repair tool', otherOption4: 'Start Misskey in safe mode', - }, locale?._bootErrors || {}); - const reload = locale?.reload || 'Reload'; + reload: 'Reload', + }, messages); const safeModeUrl = new URL(window.location.href); safeModeUrl.searchParams.set('safemode', 'true'); @@ -193,7 +178,7 @@

${messages.title}

${messages.solution}

${messages.solution1}

diff --git a/packages/backend/src/server/web/views/base-embed.pug b/packages/backend/src/server/web/views/base-embed.pug index baa0909676..29de86b8b6 100644 --- a/packages/backend/src/server/web/views/base-embed.pug +++ b/packages/backend/src/server/web/views/base-embed.pug @@ -19,7 +19,6 @@ html(class='embed') meta(name='format-detection' content='telephone=no,date=no,address=no,email=no,url=no') link(rel='icon' href= icon || '/favicon.ico') link(rel='apple-touch-icon' href= appleTouchIcon || '/apple-touch-icon.png') - link(rel='modulepreload' href=`/embed_vite/${entry.file}`) if !config.frontendEmbedManifestExists script(type="module" src="/embed_vite/@vite/client") @@ -40,7 +39,7 @@ html(class='embed') script. var VERSION = "#{version}"; - var CLIENT_ENTRY = "#{entry.file}"; + var CLIENT_ENTRY = !{JSON.stringify(entry.file)}; script(type='application/json' id='misskey_meta' data-generated-at=now) != metaJson diff --git a/packages/backend/src/server/web/views/base.pug b/packages/backend/src/server/web/views/base.pug index 3883b5e5ab..a76c75fe5c 100644 --- a/packages/backend/src/server/web/views/base.pug +++ b/packages/backend/src/server/web/views/base.pug @@ -37,7 +37,6 @@ html link(rel='prefetch' href=serverErrorImageUrl) link(rel='prefetch' href=infoImageUrl) link(rel='prefetch' href=notFoundImageUrl) - link(rel='modulepreload' href=`/vite/${entry.file}`) if !config.frontendManifestExists script(type="module" src="/vite/@vite/client") @@ -69,7 +68,7 @@ html script. var VERSION = "#{version}"; - var CLIENT_ENTRY = "#{entry.file}"; + var CLIENT_ENTRY = !{JSON.stringify(entry.file)}; script(type='application/json' id='misskey_meta' data-generated-at=now) != metaJson -- cgit v1.2.3-freya