From 23e49c6e6e63a518e704f82879a5fdcf268c51d8 Mon Sep 17 00:00:00 2001 From: INOUE Daisuke Date: Tue, 8 Apr 2025 22:14:04 +0900 Subject: 1st stage, only language switch. Thank you, MomentQYC ( https://github.com/MomentQYC ). Your first attemt encourage me. --- src/app.ts | 200 ++++++++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 151 insertions(+), 49 deletions(-) (limited to 'src/app.ts') diff --git a/src/app.ts b/src/app.ts index 0708081..febc67d 100755 --- a/src/app.ts +++ b/src/app.ts @@ -1,6 +1,20 @@ import express from "express"; import hbs from "express-handlebars"; import cookieParser from "cookie-parser"; +import i18next from "i18next"; +import Backend from "i18next-fs-backend"; +import { LanguageDetector, handle } from 'i18next-http-middleware'; +import { createRequire } from 'module'; +import { fileURLToPath } from 'url'; +import { dirname } from 'path'; +import path from 'path'; + +const require = createRequire(import.meta.url); +const handlebarsI18next = require('handlebars-i18next'); + +// ESモジュールで__dirnameを再現 +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); import routes from "./routes.js"; import frontend from "./routes/frontend.js"; @@ -11,6 +25,7 @@ import staticPages from "./routes/static.js"; import magicLink from "./routes/magicLink.js"; import { initEmailService } from "./lib/email.js"; +import { getI18nHelpers } from "./helpers.js"; import { activityPubContentType, alternateActivityPubContentType, @@ -20,55 +35,142 @@ const app = express(); app.locals.sendEmails = initEmailService(); -// View engine // -const hbsInstance = hbs.create({ - defaultLayout: "main", - partialsDir: ["views/partials/"], - layoutsDir: "views/layouts/", - helpers: { - plural: function (number: number, text: string) { - var singular = number === 1; - // If no text parameter was given, just return a conditional s. - if (typeof text !== "string") return singular ? "" : "s"; - // Split with regex into group1/group2 or group1(group3) - var match = text.match(/^([^()\/]+)(?:\/(.+))?(?:\((\w+)\))?/); - // If no match, just append a conditional s. - if (!match) return text + (singular ? "" : "s"); - // We have a good match, so fire away - return ( - (singular && match[1]) || // Singular case - match[2] || // Plural case: 'bagel/bagels' --> bagels - match[1] + (match[3] || "s") - ); // Plural case: 'bagel(s)' or 'bagel' --> bagels - }, - json: function (context: any) { - return JSON.stringify(context); +// ESモジュールで__dirnameを再現する部分を関数化 +const getLocalesPath = () => { + const __filename = fileURLToPath(import.meta.url); + const __dirname = dirname(__filename); + return path.join(__dirname, '..', 'locales'); +}; + +async function initializeApp() { + // Cookies // + app.use(cookieParser()); + + // カスタム言語検出ミドルウェア + // app.use((req, res, next) => { + // const acceptLanguage = req.headers['accept-language']; + // if (acceptLanguage && acceptLanguage.includes('ja')) { + // res.cookie('i18next', 'ja', { + // maxAge: 365 * 24 * 60 * 60 * 1000, + // httpOnly: true, + // sameSite: 'lax' + // }); + // } + // next(); + // }); + + // i18next configuration + await i18next + .use(Backend) + .use(LanguageDetector) + .init({ + backend: { + loadPath: path.join(getLocalesPath(), '{{lng}}.json'), + }, + fallbackLng: 'en', + preload: ['en', 'ja'], + supportedLngs: ['en', 'ja'], + nonExplicitSupportedLngs: true, + load: 'languageOnly', + debug: true, + detection: { + order: ['header', 'cookie'], + lookupHeader: 'accept-language', + lookupCookie: 'i18next', + caches: ['cookie'] + }, + interpolation: { + escapeValue: false + } + }); + + app.use(handle(i18next)); + + // 言語を明示的に切り替える + app.use((req, res, next) => { + const currentLanguage = i18next.language; + i18next.changeLanguage(req.language); + const newLanguage = i18next.language; + console.log('Language Change:', { + header: req.headers['accept-language'], + detected: req.language, + currentLanguage: currentLanguage, + newLanguage: newLanguage + }); + next(); + }); + + // デバッグ用 + app.use((req, res, next) => { + console.log('Language Detection:', { + header: req.headers['accept-language'], + detected: req.language, + i18next: i18next.language + }); + next(); + }); + + // View engine // + const hbsInstance = hbs.create({ + defaultLayout: "main", + partialsDir: ["views/partials/"], + layoutsDir: "views/layouts/", + helpers: { + plural: function (number: number, text: string) { + var singular = number === 1; + // If no text parameter was given, just return a conditional s. + if (typeof text !== "string") return singular ? "" : "s"; + // Split with regex into group1/group2 or group1(group3) + var match = text.match(/^([^()\/]+)(?:\/(.+))?(?:\((\w+)\))?/); + // If no match, just append a conditional s. + if (!match) return text + (singular ? "" : "s"); + // We have a good match, so fire away + return ( + (singular && match[1]) || // Singular case + match[2] || // Plural case: 'bagel/bagels' --> bagels + match[1] + (match[3] || "s") + ); // Plural case: 'bagel(s)' or 'bagel' --> bagels + }, + json: function (context: any) { + return JSON.stringify(context); + }, + // i18nextヘルパーを追加 + ...getI18nHelpers() }, - }, -}); -app.engine("handlebars", hbsInstance.engine); -app.set("view engine", "handlebars"); -app.set("hbsInstance", hbsInstance); - -// Static files // -app.use(express.static("public")); - -// Body parser // -app.use(express.json({ type: alternateActivityPubContentType })); -app.use(express.json({ type: activityPubContentType })); -app.use(express.json({ type: "application/json" })); -app.use(express.urlencoded({ extended: true })); - -// Cookies // -app.use(cookieParser()); - -// Router // -app.use("/", staticPages); -app.use("/", frontend); -app.use("/", activitypub); -app.use("/", event); -app.use("/", group); -app.use("/", magicLink); -app.use("/", routes); + }); + + // i18nextHelperの呼び出し方法を変更 + if (typeof handlebarsI18next === 'function') { + handlebarsI18next(hbsInstance.handlebars, i18next); + } else if (typeof handlebarsI18next.default === 'function') { + handlebarsI18next.default(hbsInstance.handlebars, i18next); + } else { + console.error('handlebars-i18next helper is not properly loaded'); + } + + app.engine("handlebars", hbsInstance.engine); + app.set("view engine", "handlebars"); + app.set("hbsInstance", hbsInstance); + + // Static files // + app.use(express.static("public")); + + // Body parser // + app.use(express.json({ type: alternateActivityPubContentType })); + app.use(express.json({ type: activityPubContentType })); + app.use(express.json({ type: "application/json" })); + app.use(express.urlencoded({ extended: true })); + + // Router // + app.use("/", staticPages); + app.use("/", frontend); + app.use("/", activitypub); + app.use("/", event); + app.use("/", group); + app.use("/", magicLink); + app.use("/", routes); +} + +initializeApp().catch(console.error); export default app; -- cgit v1.2.3 From 90bdf104d76674d307cbd50dc1cf3d973b663471 Mon Sep 17 00:00:00 2001 From: INOUE Daisuke Date: Tue, 8 Apr 2025 22:18:02 +0900 Subject: fix and add some translation keys. --- src/app.ts | 13 ------------- 1 file changed, 13 deletions(-) (limited to 'src/app.ts') diff --git a/src/app.ts b/src/app.ts index febc67d..5fe0100 100755 --- a/src/app.ts +++ b/src/app.ts @@ -46,19 +46,6 @@ async function initializeApp() { // Cookies // app.use(cookieParser()); - // カスタム言語検出ミドルウェア - // app.use((req, res, next) => { - // const acceptLanguage = req.headers['accept-language']; - // if (acceptLanguage && acceptLanguage.includes('ja')) { - // res.cookie('i18next', 'ja', { - // maxAge: 365 * 24 * 60 * 60 * 1000, - // httpOnly: true, - // sameSite: 'lax' - // }); - // } - // next(); - // }); - // i18next configuration await i18next .use(Backend) -- cgit v1.2.3 From 1b57d9ea6513b81e538677f9ebf221d0c635f482 Mon Sep 17 00:00:00 2001 From: INOUE Daisuke Date: Tue, 8 Apr 2025 22:18:39 +0900 Subject: Plural with i18next --- src/app.ts | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) (limited to 'src/app.ts') diff --git a/src/app.ts b/src/app.ts index 5fe0100..ddfc101 100755 --- a/src/app.ts +++ b/src/app.ts @@ -1,5 +1,6 @@ import express from "express"; -import hbs from "express-handlebars"; +import hbs, { ExpressHandlebars } from "express-handlebars"; +import Handlebars from 'handlebars'; import cookieParser from "cookie-parser"; import i18next from "i18next"; import Backend from "i18next-fs-backend"; @@ -98,31 +99,20 @@ async function initializeApp() { }); // View engine // - const hbsInstance = hbs.create({ + const hbsInstance: ExpressHandlebars = hbs.create({ defaultLayout: "main", partialsDir: ["views/partials/"], layoutsDir: "views/layouts/", helpers: { - plural: function (number: number, text: string) { - var singular = number === 1; - // If no text parameter was given, just return a conditional s. - if (typeof text !== "string") return singular ? "" : "s"; - // Split with regex into group1/group2 or group1(group3) - var match = text.match(/^([^()\/]+)(?:\/(.+))?(?:\((\w+)\))?/); - // If no match, just append a conditional s. - if (!match) return text + (singular ? "" : "s"); - // We have a good match, so fire away - return ( - (singular && match[1]) || // Singular case - match[2] || // Plural case: 'bagel/bagels' --> bagels - match[1] + (match[3] || "s") - ); // Plural case: 'bagel(s)' or 'bagel' --> bagels - }, json: function (context: any) { return JSON.stringify(context); }, // i18nextヘルパーを追加 - ...getI18nHelpers() + ...getI18nHelpers(), + plural: function (key: string, count: number, options: any) { // ★plural ヘルパーを登録 + const translation = i18next.t(key, { count: count }); + return translation; + } }, }); @@ -135,6 +125,12 @@ async function initializeApp() { console.error('handlebars-i18next helper is not properly loaded'); } + + (hbsInstance.handlebars as typeof Handlebars).registerHelper('pluralize', function(count: number, key: string, options: any) { + const translation = i18next.t(key, { count: count }); + return translation; + }); + app.engine("handlebars", hbsInstance.engine); app.set("view engine", "handlebars"); app.set("hbsInstance", hbsInstance); -- cgit v1.2.3 From b6c5301bef843eab1262faca8548df204908b663 Mon Sep 17 00:00:00 2001 From: INOUE Daisuke Date: Tue, 8 Apr 2025 22:20:04 +0900 Subject: Add 'en-US', change preload language --- src/app.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/app.ts') diff --git a/src/app.ts b/src/app.ts index ddfc101..9828905 100755 --- a/src/app.ts +++ b/src/app.ts @@ -56,8 +56,8 @@ async function initializeApp() { loadPath: path.join(getLocalesPath(), '{{lng}}.json'), }, fallbackLng: 'en', - preload: ['en', 'ja'], - supportedLngs: ['en', 'ja'], + preload: ['en-US', 'ja'], + supportedLngs: ['en','en-US', 'ja'], nonExplicitSupportedLngs: true, load: 'languageOnly', debug: true, -- cgit v1.2.3 From f2ee19f15a78125a1dc2ba8b9c175dd9831e5700 Mon Sep 17 00:00:00 2001 From: INOUE Daisuke Date: Thu, 20 Mar 2025 22:54:38 +0900 Subject: hidden attendees (? people) --- src/app.ts | 6 ------ 1 file changed, 6 deletions(-) (limited to 'src/app.ts') diff --git a/src/app.ts b/src/app.ts index 9828905..9301484 100755 --- a/src/app.ts +++ b/src/app.ts @@ -125,12 +125,6 @@ async function initializeApp() { console.error('handlebars-i18next helper is not properly loaded'); } - - (hbsInstance.handlebars as typeof Handlebars).registerHelper('pluralize', function(count: number, key: string, options: any) { - const translation = i18next.t(key, { count: count }); - return translation; - }); - app.engine("handlebars", hbsInstance.engine); app.set("view engine", "handlebars"); app.set("hbsInstance", hbsInstance); -- cgit v1.2.3 From 73e8b168c3ffc4e3ffe30e50dd3e46ed70d909d8 Mon Sep 17 00:00:00 2001 From: INOUE Daisuke Date: Sat, 22 Mar 2025 22:55:46 +0900 Subject: some fix, moment.locale setting --- src/app.ts | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'src/app.ts') diff --git a/src/app.ts b/src/app.ts index 9301484..a71bf30 100755 --- a/src/app.ts +++ b/src/app.ts @@ -31,6 +31,7 @@ import { activityPubContentType, alternateActivityPubContentType, } from "./lib/activitypub.js"; +import moment from "moment"; const app = express(); @@ -125,6 +126,10 @@ async function initializeApp() { console.error('handlebars-i18next helper is not properly loaded'); } + i18next.on('languageChanged', function(lng) { + moment.locale(lng); + }); + app.engine("handlebars", hbsInstance.engine); app.set("view engine", "handlebars"); app.set("hbsInstance", hbsInstance); -- cgit v1.2.3 From 15151fb2de8bfa8b934a4705150c4e7aef611ec3 Mon Sep 17 00:00:00 2001 From: INOUE Daisuke Date: Mon, 24 Mar 2025 21:38:47 +0900 Subject: remove i18n debug code --- src/app.ts | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) (limited to 'src/app.ts') diff --git a/src/app.ts b/src/app.ts index a71bf30..47bb2d1 100755 --- a/src/app.ts +++ b/src/app.ts @@ -61,7 +61,7 @@ async function initializeApp() { supportedLngs: ['en','en-US', 'ja'], nonExplicitSupportedLngs: true, load: 'languageOnly', - debug: true, + debug: false, detection: { order: ['header', 'cookie'], lookupHeader: 'accept-language', @@ -80,24 +80,24 @@ async function initializeApp() { const currentLanguage = i18next.language; i18next.changeLanguage(req.language); const newLanguage = i18next.language; - console.log('Language Change:', { - header: req.headers['accept-language'], - detected: req.language, - currentLanguage: currentLanguage, - newLanguage: newLanguage - }); +// console.log('Language Change:', { +// header: req.headers['accept-language'], +// detected: req.language, +// currentLanguage: currentLanguage, +// newLanguage: newLanguage +// }); next(); }); - // デバッグ用 - app.use((req, res, next) => { - console.log('Language Detection:', { - header: req.headers['accept-language'], - detected: req.language, - i18next: i18next.language - }); - next(); - }); +// // デバッグ用 +// app.use((req, res, next) => { +// console.log('Language Detection:', { +// header: req.headers['accept-language'], +// detected: req.language, +// i18next: i18next.language +// }); +// next(); +// }); // View engine // const hbsInstance: ExpressHandlebars = hbs.create({ -- cgit v1.2.3 From 889bae2029211abfd61a25ddecd7eb5b0f4a2c90 Mon Sep 17 00:00:00 2001 From: INOUE Daisuke Date: Sun, 27 Apr 2025 23:40:06 +0900 Subject: translate Japanese comments to English --- src/app.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) (limited to 'src/app.ts') diff --git a/src/app.ts b/src/app.ts index 47bb2d1..4cf37c8 100755 --- a/src/app.ts +++ b/src/app.ts @@ -13,7 +13,7 @@ import path from 'path'; const require = createRequire(import.meta.url); const handlebarsI18next = require('handlebars-i18next'); -// ESモジュールで__dirnameを再現 +// Recreate __dirname in ES module const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); @@ -37,7 +37,7 @@ const app = express(); app.locals.sendEmails = initEmailService(); -// ESモジュールで__dirnameを再現する部分を関数化 +// function to construct __dirname with ES module const getLocalesPath = () => { const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); @@ -75,11 +75,12 @@ async function initializeApp() { app.use(handle(i18next)); - // 言語を明示的に切り替える + // to Switch language app.use((req, res, next) => { const currentLanguage = i18next.language; i18next.changeLanguage(req.language); const newLanguage = i18next.language; +// Uncomment for debugging // console.log('Language Change:', { // header: req.headers['accept-language'], // detected: req.language, @@ -89,7 +90,7 @@ async function initializeApp() { next(); }); -// // デバッグ用 +// Uncomment for debugging // app.use((req, res, next) => { // console.log('Language Detection:', { // header: req.headers['accept-language'], @@ -108,16 +109,16 @@ async function initializeApp() { json: function (context: any) { return JSON.stringify(context); }, - // i18nextヘルパーを追加 + // add i18next helpers ...getI18nHelpers(), - plural: function (key: string, count: number, options: any) { // ★plural ヘルパーを登録 + plural: function (key: string, count: number, options: any) { // Register the plural helper const translation = i18next.t(key, { count: count }); return translation; } }, }); - // i18nextHelperの呼び出し方法を変更 + // calling i18nextHelper if (typeof handlebarsI18next === 'function') { handlebarsI18next(hbsInstance.handlebars, i18next); } else if (typeof handlebarsI18next.default === 'function') { -- cgit v1.2.3 From 9286a9b97ec9aef5bcef965e01e1964521c84ab6 Mon Sep 17 00:00:00 2001 From: INOUE Daisuke Date: Thu, 1 May 2025 22:03:32 +0900 Subject: "en-us" removed, only "en" now. --- src/app.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/app.ts') diff --git a/src/app.ts b/src/app.ts index 4cf37c8..c4bcdcd 100755 --- a/src/app.ts +++ b/src/app.ts @@ -57,8 +57,8 @@ async function initializeApp() { loadPath: path.join(getLocalesPath(), '{{lng}}.json'), }, fallbackLng: 'en', - preload: ['en-US', 'ja'], - supportedLngs: ['en','en-US', 'ja'], + preload: ['en', 'ja'], + supportedLngs: ['en', 'ja'], nonExplicitSupportedLngs: true, load: 'languageOnly', debug: false, -- cgit v1.2.3