2ee2b05d1c
* feat(security): implement CSP * fix(csp): update helmet version to latest * Squashed commit of the following: commit cc6a35e61db07759c1f32716185543bc48bce760 Author: Kalista Payne <kalista@habitica.com> Date: Fri Dec 12 17:27:50 2025 -0600 fix(CSP): more Amazon domains commit 985b86c29af866b2df942c21217d99390a2c6e92 Author: Kalista Payne <kalista@habitica.com> Date: Fri Dec 12 17:18:08 2025 -0600 fix(csp): more loggly allowance commit 166bd315272f9c3a42652f3a026c27a88ed1a549 Author: Kalista Payne <kalista@habitica.com> Date: Fri Dec 12 17:12:00 2025 -0600 fix(csp): data, inline, some refactoring commit 1a0a6c1806a53d43a7199bb2ef72cff610e908be Author: Kalista Payne <kalista@habitica.com> Date: Fri Dec 12 17:05:44 2025 -0600 fix(CSP): override default script-src commit 023d9886c835989da9c5901c168d66b572097dcf Author: Kalista Payne <kalista@habitica.com> Date: Fri Dec 12 16:56:24 2025 -0600 fix(CSP): unsafe-eval in default-src commit f51f0a0c93b60dfec7ce02be0ecd2587fc882fe0 Author: Kalista Payne <kalista@habitica.com> Date: Fri Dec 12 16:52:14 2025 -0600 fix(CSP): move trusted list to default-src commit 83b2ba7688dea38abb651cf5c27482a7a3648374 Author: Kalista Payne <kalista@habitica.com> Date: Fri Dec 12 16:38:05 2025 -0600 fix(CSP): explicit habitica/aws in script-src commit d5ca5172d5ad2fd8cec9402d7d2c9452c6ece7a1 Author: Kalista Payne <kalista@habitica.com> Date: Fri Dec 12 16:31:38 2025 -0600 fix(CSP): need escaped single quotes commit c677a1ffeff5793b6da228924e68d2c2e47794b2 Author: Kalista Payne <kalista@habitica.com> Date: Fri Dec 12 16:27:46 2025 -0600 fix(CSP): unsafe-eval commit 6ef35c3f7281c8426d9c333686be6bb65f00b3a8 Author: Kalista Payne <kalista@habitica.com> Date: Fri Dec 12 16:15:07 2025 -0600 fix(CSP): might need to skip entirely in dev but try no 'self' commit 5759fb37d82fa61b474f01e9ce5e2dc461f6ceba Author: Kalista Payne <kalista@habitica.com> Date: Fri Dec 12 15:51:26 2025 -0600 fix(csp): permit AWS in default-src commit 9f238abf9373bc29715657b945b2247ef23c9224 Author: Kalista Payne <kalista@habitica.com> Date: Fri Dec 5 17:22:25 2025 -0600 fix(csp): update helmet version to latest commit 9462e90f4f3058f4014137b3178b9751c5280e97 Author: Kalista Payne <kalista@habitica.com> Date: Tue Nov 25 09:27:05 2025 -0600 feat(security): implement CSP commit72539f9ba3Author: Kalista Payne <kalista@habitica.com> Date: Wed Dec 10 14:16:53 2025 -0600 5.42.2 commitdabd466719Author: Kalista Payne <kalista@habitica.com> Date: Wed Dec 10 14:16:48 2025 -0600 Revert "Chat optimization (#15545)" This reverts commit2917955ef0. commit8bf2304330Author: Kalista Payne <kalista@habitica.com> Date: Wed Dec 10 14:15:48 2025 -0600 chore(event): G1G1 date tweaks commit6937dc4e4eAuthor: Kalista Payne <kalista@habitica.com> Date: Mon Dec 8 16:37:04 2025 -0600 fix(subscription): couple more layout tweaks * fix(csp): move unsafe-eval to default? ig? * Revert "fix(csp): move unsafe-eval to default? ig?" This reverts commit 90476cbf6c59132024d4fbaeceb7cdb19a876303. * fix(security): no unsafe! yay! * fix(packages): remove webpack * fix(lint): object destructuring * fix(csp): remove Vue-Fragment * wip(i18n): load Moment locale from cache * fix(gulp): remove unneeded cache task * fix(i18n): add Moment weekday abbrevs to translations * fix(lint): destructuring ...why is this happening here and not develop lol * fix(csp): add amplitude to whitelist --------- Co-authored-by: Phillip Thelen <phillip@thelen.space>
159 lines
4.7 KiB
JavaScript
159 lines
4.7 KiB
JavaScript
import moment from 'moment/min/moment-with-locales';
|
|
import fs from 'fs';
|
|
import path from 'path';
|
|
import _ from 'lodash';
|
|
import shared from '../../common';
|
|
|
|
export const localePath = path.join(__dirname, '../../common/locales/');
|
|
export const BROWSER_SCRIPT_CACHE_PATH = path.join(__dirname, '/../../../i18n_cache/');
|
|
|
|
// Store translations
|
|
export const coreTranslations = {};
|
|
export const contentTranslations = {};
|
|
// Store MomentJS localization files
|
|
export const momentLangs = {};
|
|
|
|
// Handle differences in language codes between MomentJS and /locales
|
|
const momentLangsMapping = {
|
|
en: 'en-gb',
|
|
en_GB: 'en-gb', // eslint-disable-line camelcase
|
|
no: 'nn',
|
|
zh: 'zh-cn',
|
|
zh_TW: 'zh-tw', // eslint-disable-line camelcase
|
|
es_419: 'es', // eslint-disable-line camelcase
|
|
pt_BR: 'pt-br', // eslint-disable-line camelcase
|
|
};
|
|
|
|
export const approvedLanguages = [
|
|
'bg', 'cs', 'da', 'de', 'en', 'en_GB',
|
|
'es', 'es_419', 'fr', 'he', 'hu', 'id', 'it',
|
|
'ja', 'nl', 'pl', 'pt', 'pt_BR', 'ro', 'ru', 'sk',
|
|
'sr', 'sv', 'tr', 'uk', 'zh', 'zh_TW',
|
|
];
|
|
|
|
const contentFileNames = [
|
|
'achievements.json',
|
|
'backgrounds.json',
|
|
'content.json',
|
|
'customizations.json',
|
|
'gear.json',
|
|
'questscontent.json',
|
|
'pets.json',
|
|
'spells.json',
|
|
];
|
|
|
|
function _loadTranslations (locale) {
|
|
const files = fs.readdirSync(path.join(localePath, locale));
|
|
|
|
coreTranslations[locale] = {};
|
|
contentTranslations[locale] = {};
|
|
|
|
files.forEach(file => {
|
|
if (path.extname(file) !== '.json') return;
|
|
|
|
// We use require to load and parse a JSON file
|
|
if (contentFileNames.includes(file.toLowerCase)) {
|
|
_.merge(contentTranslations[locale], require(path.join(localePath, locale, file))); // eslint-disable-line global-require, import/no-dynamic-require, max-len
|
|
} else {
|
|
_.merge(coreTranslations[locale], require(path.join(localePath, locale, file))); // eslint-disable-line global-require, import/no-dynamic-require, max-len
|
|
}
|
|
});
|
|
}
|
|
|
|
// First fetch English strings so we can merge them with missing strings in other languages
|
|
_loadTranslations('en');
|
|
|
|
// Then load all other languages
|
|
approvedLanguages.forEach(file => {
|
|
if (file === 'en' || fs.statSync(path.join(localePath, file)).isDirectory() === false) return;
|
|
_loadTranslations(file);
|
|
|
|
// Strip empty strings, then merge missing strings from english
|
|
coreTranslations[file] = _.pickBy(coreTranslations[file], string => string !== '');
|
|
_.defaults(coreTranslations[file], coreTranslations.en);
|
|
|
|
// Strip empty strings, then merge missing strings from english
|
|
contentTranslations[file] = _.pickBy(contentTranslations[file], string => string !== '');
|
|
_.defaults(contentTranslations[file], contentTranslations.en);
|
|
});
|
|
|
|
// Add translations to shared
|
|
export const translations = _.merge({}, coreTranslations, contentTranslations);
|
|
shared.i18n.translations = translations;
|
|
|
|
export const langCodes = Object.keys(coreTranslations);
|
|
|
|
export const availableLanguages = langCodes.map(langCode => ({
|
|
code: langCode,
|
|
name: coreTranslations[langCode].languageName,
|
|
}));
|
|
|
|
langCodes.forEach(code => {
|
|
const lang = _.find(availableLanguages, { code });
|
|
|
|
lang.momentLangCode = momentLangsMapping[code] || code;
|
|
|
|
try {
|
|
moment.locale(lang.momentLangCode);
|
|
const weekdays = moment.weekdaysMin();
|
|
for (const day of [...Array(7).keys()]) {
|
|
coreTranslations[code][`weekdaysMin${day}`] = weekdays[day];
|
|
}
|
|
} catch (e) {
|
|
// The catch block is mandatory so it won't crash the server
|
|
}
|
|
});
|
|
|
|
// Remove en_GB from langCodes checked by browser to avoid it being
|
|
// used in place of plain original 'en'
|
|
// (it's an optional language that can be enabled only in setting)
|
|
export const defaultLangCodes = _.without(langCodes, 'en_GB');
|
|
|
|
// A map of languages that have different versions and the relative versions
|
|
export const multipleVersionsLanguages = {
|
|
es: {
|
|
'es-419': 'es_419',
|
|
'es-mx': 'es_419',
|
|
'es-gt': 'es_419',
|
|
'es-cr': 'es_419',
|
|
'es-pa': 'es_419',
|
|
'es-do': 'es_419',
|
|
'es-ve': 'es_419',
|
|
'es-co': 'es_419',
|
|
'es-pe': 'es_419',
|
|
'es-ar': 'es_419',
|
|
'es-ec': 'es_419',
|
|
'es-cl': 'es_419',
|
|
'es-uy': 'es_419',
|
|
'es-py': 'es_419',
|
|
'es-bo': 'es_419',
|
|
'es-sv': 'es_419',
|
|
'es-hn': 'es_419',
|
|
'es-ni': 'es_419',
|
|
'es-pr': 'es_419',
|
|
},
|
|
zh: {
|
|
'zh-tw': 'zh_TW',
|
|
},
|
|
pt: {
|
|
'pt-br': 'pt_BR',
|
|
},
|
|
};
|
|
|
|
export function geti18nCoreBrowserScript (languageCode) {
|
|
const language = _.find(availableLanguages, { code: languageCode });
|
|
|
|
return `(function () {
|
|
if (!window) return;
|
|
window['habitica-i18n'] = ${JSON.stringify({
|
|
availableLanguages,
|
|
language,
|
|
strings: coreTranslations[languageCode],
|
|
})};
|
|
})()`;
|
|
}
|
|
|
|
export function geti18nContentBrowserScript (languageCode) {
|
|
return JSON.stringify(contentTranslations[languageCode]);
|
|
}
|