diff --git a/gulpfile.ts b/gulpfile.ts index b74021f354..bdc20089cd 100644 --- a/gulpfile.ts +++ b/gulpfile.ts @@ -2,6 +2,7 @@ * Gulp tasks */ +import * as fs from 'fs'; import * as gulp from 'gulp'; import * as ts from 'gulp-typescript'; import * as rimraf from 'rimraf'; @@ -31,6 +32,18 @@ gulp.task('build:copy:fonts', () => gulp.src('./node_modules/three/examples/fonts/**/*').pipe(gulp.dest('./built/client/assets/fonts/')) ); +gulp.task('build:copy:locales', cb => { + fs.mkdirSync('./built/client/assets/locales', { recursive: true }); + + const v = { '_version_': meta.version }; + + for (const [lang, locale] of Object.entries(locales)) { + fs.writeFileSync(`./built/client/assets/locales/${lang}.${meta.version}.json`, JSON.stringify({ ...locale, ...v }), 'utf-8'); + } + + cb(); +}); + gulp.task('build:client:script', () => { return gulp.src(['./src/server/web/boot.js']) .pipe(replace('VERSION', JSON.stringify(meta.version))) @@ -47,7 +60,7 @@ gulp.task('build:client:style', () => { .pipe(gulp.dest('./built/server/web/')); }); -gulp.task('build:copy', gulp.parallel('build:copy:views', 'build:client:script', 'build:client:style', 'build:copy:fonts', () => +gulp.task('build:copy', gulp.parallel('build:copy:locales', 'build:copy:views', 'build:client:script', 'build:client:style', 'build:copy:fonts', () => gulp.src([ './src/emojilist.json', './src/server/web/views/**/*', diff --git a/src/client/.eslintrc b/src/client/.eslintrc index 5e309a95c1..8829472b49 100644 --- a/src/client/.eslintrc +++ b/src/client/.eslintrc @@ -1,9 +1,7 @@ { "globals": { "_DEV_": false, - "_LANG_": false, "_LANGS_": false, - "_LOCALE_": false, "_VERSION_": false, "_ENV_": false, "_PERF_PREFIX_": false, diff --git a/src/client/@types/global.d.ts b/src/client/@types/global.d.ts index a1ce02735a..84dde63b22 100644 --- a/src/client/@types/global.d.ts +++ b/src/client/@types/global.d.ts @@ -1,6 +1,4 @@ -declare const _LANG_: string; declare const _LANGS_: string[][]; -declare const _LOCALE_: Record; declare const _VERSION_: string; declare const _ENV_: string; declare const _DEV_: boolean; diff --git a/src/client/config.ts b/src/client/config.ts index 2a4da4dce2..f2022b0f02 100644 --- a/src/client/config.ts +++ b/src/client/config.ts @@ -6,9 +6,9 @@ export const hostname = address.hostname; export const url = address.origin; export const apiUrl = url + '/api'; export const wsUrl = url.replace('http://', 'ws://').replace('https://', 'wss://') + '/streaming'; -export const lang = _LANG_; +export const lang = localStorage.getItem('lang'); export const langs = _LANGS_; -export const locale = _LOCALE_; // TODO: code splittingするため、翻訳ファイルを分割したうえでwebpackのimport alias使って読み込むようにしたい +export const locale = JSON.parse(localStorage.getItem('locale')); export const version = _VERSION_; export const instanceName = siteName === 'Misskey' ? host : siteName; export const ui = localStorage.getItem('ui'); diff --git a/src/server/web/boot.js b/src/server/web/boot.js index 5eff81a3ec..8b1fd9a619 100644 --- a/src/server/web/boot.js +++ b/src/server/web/boot.js @@ -1,7 +1,8 @@ /** * BOOT LOADER * サーバーからレスポンスされるHTMLに埋め込まれるスクリプトで、以下の役割を持ちます。 - * - バージョンやユーザーの言語に基づいて適切なメインスクリプトを読み込む。 + * - 翻訳ファイルをフェッチする。 + * - バージョンに基づいて適切なメインスクリプトを読み込む。 * - キャッシュされたコンパイル済みテーマを適用する。 * - クライアントの設定値に基づいて対応するHTMLクラス等を設定する。 * テーマをこの段階で設定するのは、メインスクリプトが読み込まれる間もテーマを適用したいためです。 @@ -10,27 +11,34 @@ 'use strict'; -// ブロックの中に入れないと、定義した変数がブラウザのグローバルスコープに登録されてしまい邪魔 -{ - //#region Script +// ブロックの中に入れないと、定義した変数がブラウザのグローバルスコープに登録されてしまい邪魔なので +(async () => { + const v = localStorage.getItem('v') || VERSION; - //#region Detect language - 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); + //#region Detect language & fetch translations + if (localStorage.hasOwnProperty('locale')) { + // TODO: 非同期でlocaleの更新処理をする + } else { + 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'; + // Fallback + if (lang == null) lang = 'en-US'; + } } + + const res = await fetch(`/assets/locales/${lang}.${v}.json`); + const json = await res.json(); + localStorage.setItem('locale', JSON.stringify(json)); } //#endregion - const ver = localStorage.getItem('v') || VERSION; - + //#region Script const salt = localStorage.getItem('salt') ? `?salt=${localStorage.getItem('salt')}` : ''; @@ -38,7 +46,7 @@ const head = document.getElementsByTagName('head')[0]; const script = document.createElement('script'); - script.setAttribute('src', `/assets/app.${ver}.${lang}.js${salt}`); + script.setAttribute('src', `/assets/app.${v}.js${salt}`); script.setAttribute('async', 'true'); script.setAttribute('defer', 'true'); head.appendChild(script); @@ -56,7 +64,7 @@ const meta = await res.json(); - if (meta.version != ver) { + if (meta.version != v) { localStorage.setItem('v', meta.version); alert( 'Misskeyの新しいバージョンがあります。ページを再度読み込みします。' + @@ -113,4 +121,4 @@ location.reload(); } -} +})(); diff --git a/webpack.config.ts b/webpack.config.ts index 4efbeeb04b..70d5a46670 100644 --- a/webpack.config.ts +++ b/webpack.config.ts @@ -33,9 +33,7 @@ const postcss = { }, }; -module.exports = Object.keys(isProduction ? locales : { - 'ja-JP': locales['ja-JP'] -}).map(lang => ({ +module.exports = { entry: { app: './src/client/init.ts', sw: './src/client/sw/sw.ts' @@ -133,9 +131,7 @@ module.exports = Object.keys(isProduction ? locales : { new webpack.ProgressPlugin({}), new webpack.DefinePlugin({ _VERSION_: JSON.stringify(meta.version), - _LANG_: JSON.stringify(lang), _LANGS_: JSON.stringify(Object.entries(locales).map(([k, v]: [string, any]) => [k, v._lang_])), - _LOCALE_: JSON.stringify(locales[lang]), _ENV_: JSON.stringify(process.env.NODE_ENV), _DEV_: process.env.NODE_ENV !== 'production', _PERF_PREFIX_: JSON.stringify('Misskey:'), @@ -153,7 +149,7 @@ module.exports = Object.keys(isProduction ? locales : { ], output: { path: __dirname + '/built/client/assets', - filename: `[name].${meta.version}.${lang}.js`, + filename: `[name].${meta.version}.js`, publicPath: `/assets/` }, resolve: { @@ -173,4 +169,4 @@ module.exports = Object.keys(isProduction ? locales : { }, devtool: false, //'source-map', mode: isProduction ? 'production' : 'development' -})); +};