mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-05-23 09:09:36 +02:00
Merge pull request #33149 from overleaf/ar-ja-remove-i18next-additional-packages
[web] remove i18next additional libraries GitOrigin-RevId: 98fc17b409090db32b02bb66953f1c2e6efee608
This commit is contained in:
@@ -234,7 +234,6 @@ await Modules.applyNonCsrfRouter(webRouter, privateApiRouter, publicApiRouter)
|
||||
|
||||
webRouter.csrf = new CsrfClass()
|
||||
webRouter.use(webRouter.csrf.middleware)
|
||||
webRouter.use(translations.i18nMiddleware)
|
||||
webRouter.use(translations.setLangBasedOnDomainMiddleware)
|
||||
|
||||
if (Settings.cookieRollingSession) {
|
||||
|
||||
@@ -1,12 +1,48 @@
|
||||
import i18n from 'i18next'
|
||||
import fsBackend from 'i18next-fs-backend'
|
||||
import middleware from 'i18next-http-middleware'
|
||||
import path from 'node:path'
|
||||
import Settings from '@overleaf/settings'
|
||||
import { URL } from 'node:url'
|
||||
import pug from 'pug-runtime'
|
||||
import logger from '@overleaf/logger'
|
||||
import SafeHTMLSubstitution from '../Features/Helpers/SafeHTMLSubstitution.mjs'
|
||||
import cs from '../../../locales/cs.json' with { type: 'json' }
|
||||
import da from '../../../locales/da.json' with { type: 'json' }
|
||||
import de from '../../../locales/de.json' with { type: 'json' }
|
||||
import en from '../../../locales/en.json' with { type: 'json' }
|
||||
import es from '../../../locales/es.json' with { type: 'json' }
|
||||
import fi from '../../../locales/fi.json' with { type: 'json' }
|
||||
import fr from '../../../locales/fr.json' with { type: 'json' }
|
||||
import it from '../../../locales/it.json' with { type: 'json' }
|
||||
import ja from '../../../locales/ja.json' with { type: 'json' }
|
||||
import ko from '../../../locales/ko.json' with { type: 'json' }
|
||||
import nl from '../../../locales/nl.json' with { type: 'json' }
|
||||
import no from '../../../locales/no.json' with { type: 'json' }
|
||||
import pl from '../../../locales/pl.json' with { type: 'json' }
|
||||
import pt from '../../../locales/pt.json' with { type: 'json' }
|
||||
import ru from '../../../locales/ru.json' with { type: 'json' }
|
||||
import sv from '../../../locales/sv.json' with { type: 'json' }
|
||||
import tr from '../../../locales/tr.json' with { type: 'json' }
|
||||
import zhCN from '../../../locales/zh-CN.json' with { type: 'json' }
|
||||
|
||||
const locales = {
|
||||
cs,
|
||||
da,
|
||||
de,
|
||||
en,
|
||||
es,
|
||||
fi,
|
||||
fr,
|
||||
it,
|
||||
ja,
|
||||
ko,
|
||||
nl,
|
||||
no,
|
||||
pl,
|
||||
pt,
|
||||
ru,
|
||||
sv,
|
||||
tr,
|
||||
'zh-CN': zhCN,
|
||||
}
|
||||
|
||||
const fallbackLanguageCode = Settings.i18n.defaultLng || 'en'
|
||||
const availableLanguageCodes = []
|
||||
@@ -31,28 +67,19 @@ if (!availableLanguageCodes.includes(fallbackLanguageCode)) {
|
||||
availableLanguageCodes.push(fallbackLanguageCode)
|
||||
}
|
||||
|
||||
// The "node --watch" flag is not easy to detect.
|
||||
if (process.argv.includes('--watch-locales')) {
|
||||
// Dummy imports for setting up watching of locales files.
|
||||
for (const lngCode of availableLanguageCodes) {
|
||||
await import(`../../../locales/${lngCode}.json`, { with: { type: 'json' } })
|
||||
}
|
||||
}
|
||||
const resources = Object.fromEntries(
|
||||
Object.entries(locales)
|
||||
.filter(([lngCode]) => availableLanguageCodes.includes(lngCode))
|
||||
.map(([lngCode, translations]) => [lngCode, { translation: translations }])
|
||||
)
|
||||
|
||||
i18n
|
||||
.use(fsBackend)
|
||||
.use(middleware.LanguageDetector)
|
||||
.init({
|
||||
backend: {
|
||||
loadPath: path.join(import.meta.dirname, '../../../locales/__lng__.json'),
|
||||
},
|
||||
resources,
|
||||
|
||||
// still using the v3 plural suffixes
|
||||
compatibilityJSON: 'v3',
|
||||
|
||||
// Load translation files synchronously: https://www.i18next.com/overview/configuration-options#initimmediate
|
||||
initImmediate: false,
|
||||
|
||||
// We use the legacy v1 JSON format, so configure interpolator to use
|
||||
// underscores instead of curly braces
|
||||
interpolation: {
|
||||
@@ -72,7 +99,6 @@ i18n
|
||||
},
|
||||
},
|
||||
|
||||
preload: availableLanguageCodes,
|
||||
supportedLngs: availableLanguageCodes,
|
||||
fallbackLng: fallbackLanguageCode,
|
||||
})
|
||||
@@ -80,25 +106,26 @@ i18n
|
||||
logger.error({ err }, 'failed to initialize i18next library')
|
||||
})
|
||||
|
||||
// Make custom language detector for Accept-Language header
|
||||
const headerLangDetector = new middleware.LanguageDetector(i18n.services, {
|
||||
order: ['header'],
|
||||
})
|
||||
|
||||
function setLangBasedOnDomainMiddleware(req, res, next) {
|
||||
// Determine language from subdomain
|
||||
const lang = availableHosts.get(req.headers.host)
|
||||
if (lang) {
|
||||
req.i18n.changeLanguage(lang)
|
||||
const lang = availableHosts.get(req.headers.host) ?? fallbackLanguageCode
|
||||
|
||||
req.i18n = {
|
||||
language: lang,
|
||||
}
|
||||
|
||||
// expose the language code to pug
|
||||
res.locals.currentLngCode = req.language
|
||||
req.language =
|
||||
req.locale =
|
||||
req.lng =
|
||||
res.locals.currentLngCode =
|
||||
res.locals.language =
|
||||
lang
|
||||
|
||||
// If the set language is different from the language detection (based on
|
||||
// the Accept-Language header), then set flag which will show a banner
|
||||
// offering to switch to the appropriate library
|
||||
const detectedLanguageCode = headerLangDetector.detect(req, res)
|
||||
const detectedLanguageCode =
|
||||
req.acceptsLanguage(availableLanguageCodes) || fallbackLanguageCode
|
||||
if (req.language !== detectedLanguageCode) {
|
||||
res.locals.suggestedLanguageSubdomainConfig =
|
||||
subdomainConfigs.get(detectedLanguageCode)
|
||||
@@ -106,32 +133,35 @@ function setLangBasedOnDomainMiddleware(req, res, next) {
|
||||
|
||||
// Decorate req.i18n with translate function alias for backwards
|
||||
// compatibility usage in requests
|
||||
req.i18n.translate = (key, vars, components) => {
|
||||
vars = vars || {}
|
||||
req.i18n.translate =
|
||||
res.locals.t =
|
||||
req.i18n.t =
|
||||
(key, vars, components) => {
|
||||
vars = { lng: lang, ...(vars ?? {}) }
|
||||
|
||||
if (Settings.i18n.checkForHTMLInVars) {
|
||||
Object.entries(vars).forEach(([field, value]) => {
|
||||
if (pug.escape(value) !== value) {
|
||||
const violationsKey = key + field
|
||||
// do not flood the logs, log one sample per pod + key + field
|
||||
if (!I18N_HTML_INJECTIONS.has(violationsKey)) {
|
||||
logger.warn(
|
||||
{ key, field, value },
|
||||
'html content in translations context vars'
|
||||
)
|
||||
I18N_HTML_INJECTIONS.add(violationsKey)
|
||||
}
|
||||
if (Settings.i18n.checkForHTMLInVars) {
|
||||
Object.entries(vars).forEach(([field, value]) => {
|
||||
if (pug.escape(value) !== value) {
|
||||
const violationsKey = key + field
|
||||
// do not flood the logs, log one sample per pod + key + field
|
||||
if (!I18N_HTML_INJECTIONS.has(violationsKey)) {
|
||||
logger.warn(
|
||||
{ key, field, value },
|
||||
'html content in translations context vars'
|
||||
)
|
||||
I18N_HTML_INJECTIONS.add(violationsKey)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const locale = req.i18n.t(key, vars)
|
||||
if (components) {
|
||||
return SafeHTMLSubstitution.render(locale, components)
|
||||
} else {
|
||||
return locale
|
||||
}
|
||||
}
|
||||
const locale = i18n.t(key, vars)
|
||||
if (components) {
|
||||
return SafeHTMLSubstitution.render(locale, components)
|
||||
} else {
|
||||
return locale
|
||||
}
|
||||
}
|
||||
|
||||
next()
|
||||
}
|
||||
@@ -141,7 +171,6 @@ function setLangBasedOnDomainMiddleware(req, res, next) {
|
||||
i18n.translate = i18n.t
|
||||
|
||||
export default {
|
||||
i18nMiddleware: middleware.handle(i18n),
|
||||
setLangBasedOnDomainMiddleware,
|
||||
i18n,
|
||||
}
|
||||
|
||||
@@ -141,8 +141,6 @@
|
||||
"helmet": "^6.0.1",
|
||||
"https-proxy-agent": "^7.0.6",
|
||||
"i18next": "^23.10.0",
|
||||
"i18next-fs-backend": "2.6.1",
|
||||
"i18next-http-middleware": "^3.5.0",
|
||||
"jose": "^4.3.8",
|
||||
"json2csv": "^4.3.3",
|
||||
"jsonwebtoken": "^9.0.3",
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import express from 'express'
|
||||
|
||||
const MODULE_PATH = '../../../../app/src/infrastructure/Translations.mjs'
|
||||
|
||||
describe('Translations', function () {
|
||||
let req, res, translations
|
||||
async function runMiddlewares(cb) {
|
||||
async function runMiddlewares() {
|
||||
return await new Promise((resolve, reject) =>
|
||||
translations.i18nMiddleware(req, res, () => {
|
||||
translations.setLangBasedOnDomainMiddleware(req, res, (err, result) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
} else {
|
||||
resolve(result)
|
||||
}
|
||||
})
|
||||
translations.setLangBasedOnDomainMiddleware(req, res, (err, result) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
} else {
|
||||
resolve(result)
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
@@ -39,6 +38,7 @@ describe('Translations', function () {
|
||||
headers: {
|
||||
'accept-language': '',
|
||||
},
|
||||
acceptsLanguage: express.request.acceptsLanguages,
|
||||
}
|
||||
res = {
|
||||
locals: {},
|
||||
@@ -59,6 +59,15 @@ describe('Translations', function () {
|
||||
it('has translate alias', function () {
|
||||
expect(req.i18n.translate('give_feedback')).to.equal('Give feedback')
|
||||
})
|
||||
|
||||
it('does not persist across different languages', function () {
|
||||
expect([
|
||||
req.i18n.translate('log_in', { lng: 'fr' }),
|
||||
req.i18n.translate('log_in', { lng: 'en' }),
|
||||
req.i18n.translate('log_in', { lng: 'da' }),
|
||||
req.i18n.translate('log_in'),
|
||||
]).to.deep.equal(['Se connecter', 'Log in', 'Log ind', 'Log in'])
|
||||
})
|
||||
})
|
||||
|
||||
describe('interpolation', function () {
|
||||
|
||||
@@ -19,5 +19,12 @@ declare module 'express' {
|
||||
userRestrictions?: Set
|
||||
oauth_user?: OAuth2Server.User
|
||||
logger: RequestLogger
|
||||
i18n: {
|
||||
translate(
|
||||
key: string,
|
||||
vars?: Record<string, any>,
|
||||
components?: any
|
||||
): string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user