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.csrf = new CsrfClass()
|
||||||
webRouter.use(webRouter.csrf.middleware)
|
webRouter.use(webRouter.csrf.middleware)
|
||||||
webRouter.use(translations.i18nMiddleware)
|
|
||||||
webRouter.use(translations.setLangBasedOnDomainMiddleware)
|
webRouter.use(translations.setLangBasedOnDomainMiddleware)
|
||||||
|
|
||||||
if (Settings.cookieRollingSession) {
|
if (Settings.cookieRollingSession) {
|
||||||
|
|||||||
@@ -1,12 +1,48 @@
|
|||||||
import i18n from 'i18next'
|
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 Settings from '@overleaf/settings'
|
||||||
import { URL } from 'node:url'
|
import { URL } from 'node:url'
|
||||||
import pug from 'pug-runtime'
|
import pug from 'pug-runtime'
|
||||||
import logger from '@overleaf/logger'
|
import logger from '@overleaf/logger'
|
||||||
import SafeHTMLSubstitution from '../Features/Helpers/SafeHTMLSubstitution.mjs'
|
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 fallbackLanguageCode = Settings.i18n.defaultLng || 'en'
|
||||||
const availableLanguageCodes = []
|
const availableLanguageCodes = []
|
||||||
@@ -31,28 +67,19 @@ if (!availableLanguageCodes.includes(fallbackLanguageCode)) {
|
|||||||
availableLanguageCodes.push(fallbackLanguageCode)
|
availableLanguageCodes.push(fallbackLanguageCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
// The "node --watch" flag is not easy to detect.
|
const resources = Object.fromEntries(
|
||||||
if (process.argv.includes('--watch-locales')) {
|
Object.entries(locales)
|
||||||
// Dummy imports for setting up watching of locales files.
|
.filter(([lngCode]) => availableLanguageCodes.includes(lngCode))
|
||||||
for (const lngCode of availableLanguageCodes) {
|
.map(([lngCode, translations]) => [lngCode, { translation: translations }])
|
||||||
await import(`../../../locales/${lngCode}.json`, { with: { type: 'json' } })
|
)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
i18n
|
i18n
|
||||||
.use(fsBackend)
|
|
||||||
.use(middleware.LanguageDetector)
|
|
||||||
.init({
|
.init({
|
||||||
backend: {
|
resources,
|
||||||
loadPath: path.join(import.meta.dirname, '../../../locales/__lng__.json'),
|
|
||||||
},
|
|
||||||
|
|
||||||
// still using the v3 plural suffixes
|
// still using the v3 plural suffixes
|
||||||
compatibilityJSON: 'v3',
|
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
|
// We use the legacy v1 JSON format, so configure interpolator to use
|
||||||
// underscores instead of curly braces
|
// underscores instead of curly braces
|
||||||
interpolation: {
|
interpolation: {
|
||||||
@@ -72,7 +99,6 @@ i18n
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
preload: availableLanguageCodes,
|
|
||||||
supportedLngs: availableLanguageCodes,
|
supportedLngs: availableLanguageCodes,
|
||||||
fallbackLng: fallbackLanguageCode,
|
fallbackLng: fallbackLanguageCode,
|
||||||
})
|
})
|
||||||
@@ -80,25 +106,26 @@ i18n
|
|||||||
logger.error({ err }, 'failed to initialize i18next library')
|
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) {
|
function setLangBasedOnDomainMiddleware(req, res, next) {
|
||||||
// Determine language from subdomain
|
// Determine language from subdomain
|
||||||
const lang = availableHosts.get(req.headers.host)
|
const lang = availableHosts.get(req.headers.host) ?? fallbackLanguageCode
|
||||||
if (lang) {
|
|
||||||
req.i18n.changeLanguage(lang)
|
req.i18n = {
|
||||||
|
language: lang,
|
||||||
}
|
}
|
||||||
|
|
||||||
// expose the language code to pug
|
req.language =
|
||||||
res.locals.currentLngCode = 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
|
// If the set language is different from the language detection (based on
|
||||||
// the Accept-Language header), then set flag which will show a banner
|
// the Accept-Language header), then set flag which will show a banner
|
||||||
// offering to switch to the appropriate library
|
// offering to switch to the appropriate library
|
||||||
const detectedLanguageCode = headerLangDetector.detect(req, res)
|
const detectedLanguageCode =
|
||||||
|
req.acceptsLanguage(availableLanguageCodes) || fallbackLanguageCode
|
||||||
if (req.language !== detectedLanguageCode) {
|
if (req.language !== detectedLanguageCode) {
|
||||||
res.locals.suggestedLanguageSubdomainConfig =
|
res.locals.suggestedLanguageSubdomainConfig =
|
||||||
subdomainConfigs.get(detectedLanguageCode)
|
subdomainConfigs.get(detectedLanguageCode)
|
||||||
@@ -106,32 +133,35 @@ function setLangBasedOnDomainMiddleware(req, res, next) {
|
|||||||
|
|
||||||
// Decorate req.i18n with translate function alias for backwards
|
// Decorate req.i18n with translate function alias for backwards
|
||||||
// compatibility usage in requests
|
// compatibility usage in requests
|
||||||
req.i18n.translate = (key, vars, components) => {
|
req.i18n.translate =
|
||||||
vars = vars || {}
|
res.locals.t =
|
||||||
|
req.i18n.t =
|
||||||
|
(key, vars, components) => {
|
||||||
|
vars = { lng: lang, ...(vars ?? {}) }
|
||||||
|
|
||||||
if (Settings.i18n.checkForHTMLInVars) {
|
if (Settings.i18n.checkForHTMLInVars) {
|
||||||
Object.entries(vars).forEach(([field, value]) => {
|
Object.entries(vars).forEach(([field, value]) => {
|
||||||
if (pug.escape(value) !== value) {
|
if (pug.escape(value) !== value) {
|
||||||
const violationsKey = key + field
|
const violationsKey = key + field
|
||||||
// do not flood the logs, log one sample per pod + key + field
|
// do not flood the logs, log one sample per pod + key + field
|
||||||
if (!I18N_HTML_INJECTIONS.has(violationsKey)) {
|
if (!I18N_HTML_INJECTIONS.has(violationsKey)) {
|
||||||
logger.warn(
|
logger.warn(
|
||||||
{ key, field, value },
|
{ key, field, value },
|
||||||
'html content in translations context vars'
|
'html content in translations context vars'
|
||||||
)
|
)
|
||||||
I18N_HTML_INJECTIONS.add(violationsKey)
|
I18N_HTML_INJECTIONS.add(violationsKey)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const locale = req.i18n.t(key, vars)
|
const locale = i18n.t(key, vars)
|
||||||
if (components) {
|
if (components) {
|
||||||
return SafeHTMLSubstitution.render(locale, components)
|
return SafeHTMLSubstitution.render(locale, components)
|
||||||
} else {
|
} else {
|
||||||
return locale
|
return locale
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
next()
|
next()
|
||||||
}
|
}
|
||||||
@@ -141,7 +171,6 @@ function setLangBasedOnDomainMiddleware(req, res, next) {
|
|||||||
i18n.translate = i18n.t
|
i18n.translate = i18n.t
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
i18nMiddleware: middleware.handle(i18n),
|
|
||||||
setLangBasedOnDomainMiddleware,
|
setLangBasedOnDomainMiddleware,
|
||||||
i18n,
|
i18n,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -141,8 +141,6 @@
|
|||||||
"helmet": "^6.0.1",
|
"helmet": "^6.0.1",
|
||||||
"https-proxy-agent": "^7.0.6",
|
"https-proxy-agent": "^7.0.6",
|
||||||
"i18next": "^23.10.0",
|
"i18next": "^23.10.0",
|
||||||
"i18next-fs-backend": "2.6.1",
|
|
||||||
"i18next-http-middleware": "^3.5.0",
|
|
||||||
"jose": "^4.3.8",
|
"jose": "^4.3.8",
|
||||||
"json2csv": "^4.3.3",
|
"json2csv": "^4.3.3",
|
||||||
"jsonwebtoken": "^9.0.3",
|
"jsonwebtoken": "^9.0.3",
|
||||||
|
|||||||
@@ -1,19 +1,18 @@
|
|||||||
import { describe, expect, it, vi } from 'vitest'
|
import { describe, expect, it, vi } from 'vitest'
|
||||||
|
import express from 'express'
|
||||||
|
|
||||||
const MODULE_PATH = '../../../../app/src/infrastructure/Translations.mjs'
|
const MODULE_PATH = '../../../../app/src/infrastructure/Translations.mjs'
|
||||||
|
|
||||||
describe('Translations', function () {
|
describe('Translations', function () {
|
||||||
let req, res, translations
|
let req, res, translations
|
||||||
async function runMiddlewares(cb) {
|
async function runMiddlewares() {
|
||||||
return await new Promise((resolve, reject) =>
|
return await new Promise((resolve, reject) =>
|
||||||
translations.i18nMiddleware(req, res, () => {
|
translations.setLangBasedOnDomainMiddleware(req, res, (err, result) => {
|
||||||
translations.setLangBasedOnDomainMiddleware(req, res, (err, result) => {
|
if (err) {
|
||||||
if (err) {
|
reject(err)
|
||||||
reject(err)
|
} else {
|
||||||
} else {
|
resolve(result)
|
||||||
resolve(result)
|
}
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -39,6 +38,7 @@ describe('Translations', function () {
|
|||||||
headers: {
|
headers: {
|
||||||
'accept-language': '',
|
'accept-language': '',
|
||||||
},
|
},
|
||||||
|
acceptsLanguage: express.request.acceptsLanguages,
|
||||||
}
|
}
|
||||||
res = {
|
res = {
|
||||||
locals: {},
|
locals: {},
|
||||||
@@ -59,6 +59,15 @@ describe('Translations', function () {
|
|||||||
it('has translate alias', function () {
|
it('has translate alias', function () {
|
||||||
expect(req.i18n.translate('give_feedback')).to.equal('Give feedback')
|
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 () {
|
describe('interpolation', function () {
|
||||||
|
|||||||
@@ -19,5 +19,12 @@ declare module 'express' {
|
|||||||
userRestrictions?: Set
|
userRestrictions?: Set
|
||||||
oauth_user?: OAuth2Server.User
|
oauth_user?: OAuth2Server.User
|
||||||
logger: RequestLogger
|
logger: RequestLogger
|
||||||
|
i18n: {
|
||||||
|
translate(
|
||||||
|
key: string,
|
||||||
|
vars?: Record<string, any>,
|
||||||
|
components?: any
|
||||||
|
): string
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
16
yarn.lock
16
yarn.lock
@@ -7371,8 +7371,6 @@ __metadata:
|
|||||||
html-webpack-plugin: "npm:^5.5.3"
|
html-webpack-plugin: "npm:^5.5.3"
|
||||||
https-proxy-agent: "npm:^7.0.6"
|
https-proxy-agent: "npm:^7.0.6"
|
||||||
i18next: "npm:^23.10.0"
|
i18next: "npm:^23.10.0"
|
||||||
i18next-fs-backend: "npm:2.6.1"
|
|
||||||
i18next-http-middleware: "npm:^3.5.0"
|
|
||||||
i18next-scanner: "npm:4.4.0"
|
i18next-scanner: "npm:4.4.0"
|
||||||
idb: "npm:^8.0.0"
|
idb: "npm:^8.0.0"
|
||||||
inversify: "npm:^6.2.2"
|
inversify: "npm:^6.2.2"
|
||||||
@@ -20737,20 +20735,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"i18next-fs-backend@npm:2.6.1":
|
|
||||||
version: 2.6.1
|
|
||||||
resolution: "i18next-fs-backend@npm:2.6.1"
|
|
||||||
checksum: 10c0/9751745d40a9f5e57fd6144129dba11f120372f5dd9e906d025c3f96c569d65aeeddfd1a36cdfcfe7acda9c624d80ef9610917544485174a363793ca00cf8636
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"i18next-http-middleware@npm:3.5.0":
|
|
||||||
version: 3.5.0
|
|
||||||
resolution: "i18next-http-middleware@npm:3.5.0"
|
|
||||||
checksum: 10c0/fc3cb67c6984c0eb29c7456ecf320230eb361534f7d58983f514e66f14addbf082d5aa7af04b4c0bbad45b696faf4709aac64840fcd5db02038f5870b7285ea9
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"i18next-scanner@npm:4.4.0":
|
"i18next-scanner@npm:4.4.0":
|
||||||
version: 4.4.0
|
version: 4.4.0
|
||||||
resolution: "i18next-scanner@npm:4.4.0"
|
resolution: "i18next-scanner@npm:4.4.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user