attempt implementing prerendering
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Habitica - Gamify Your Life</title>
|
||||
<meta name="description" content="Habitica is a free habit and productivity app that treats your real life like a game. Habitica can help you achieve your goals to become healthy and happy.">
|
||||
<meta name="keywords" content="Habits,Goals,Todo,Gamification,Health,Fitness,School,Work">
|
||||
<link href="https://fonts.googleapis.com/css?family=Roboto+Condensed:400,400i,700,700i|Roboto:400,400i,700,700i" rel="stylesheet">
|
||||
<link rel="shortcut icon" sizes="48x48" href="/static/icons/favicon.ico">
|
||||
<link rel="shortcut icon" sizes="192x192" href="/static/icons/favicon_192x192.png">
|
||||
<link rel="mask-icon" href="/static/icons/favicon.ico">
|
||||
<meta property="og:image" content="/static/emails/images/meta-image.png" />
|
||||
<script type="module" src="/src/main-static.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="loading-screen">
|
||||
<svg width="80" height="80" viewBox="0 0 80 80" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M79.05 72.15c-.8-1.766-2.643-2.62-3.845-1.766-1.201.855-2.867.985-4.448.602-1.584-.385-1.885-4.01-1.543-8.195.342-4.184.909-5.795 1.267-7.314.404-1.524 2.191-1.404 2.405-.209.215 1.196 1.454 1.196 3.266-.979 1.811-2.175 1.543-8.52-.546-13.684-2.088-5.163.817-4.661 1.66-4.149.844.513 1.362-.255 1.156-3.2-.204-2.945-2.916-5.247-5.096-6.657-2.184-1.41-4.842-2.967-4.78-6.745.063-3.777 5.2-3.658 5.897-3.596.697.063 2.037-.233 1.264-4.157-.773-3.924-3.575-4.673-5.332-4.567-1.758.106-2.943 1.071-5.427.133-2.484-.938-4.136-.572-6.45-.057-2.313.515-5.343 1.94-9.112 2.959-1.989.545-2.661.683-4.828.718-1.33.02-1.885 1.633-.106 3.61 1.408 1.608 4.597 2.036 6.515 1.768 1.236-.174 1.521.645 1.407 1.85a20.023 20.023 0 0 0-.024 4.488c.198 1.5.45 4.051-.258 5.713-.35.817-1.361 1.693-2.449 1.633-1.413-.084-2.555-1.75-3.537-3.726-2.06-4.152-4.48-5.033-13.509-8.835-8.12-3.417-12.516-8.749-15.24-12.185-2.421-3.042-4.846-1.89-4.626.855.179 2.128 1.48 9.008 4.781 13.141 4.058 6.314 10.32 9.177 17.534 9.739 1.885.149 3.065.52 3.225 1.383.236 1.835-1.557 3.11-4.898 2.722-3.341-.39-4.768.22-4.103 2.121 2.123 4.477 7.021 4.672 9.058 4.857.686.122 3.114 0 4.41.355 1.51.418 1.836 2.514-.353 3.648-3.892 1.903-5.59 3.479-7.561 7.075-1.486 2.826-2.77 7.555-1.435 14.365 1.283 6.62-8.342 6.83-12.497 5.89-1.793-.377-3.675-3.778.716-6.625 3.553-2.305 4.269-3.724 4.111-6.642-.184-3.4-2.058-3.644-2.053-6.598v-7.05c0-.602-.488-1.088-1.087-1.088h-3.334a1.087 1.087 0 0 1-1.087-1.087v-4.25c0-.602-.488-1.087-1.088-1.087h-3.317a1.087 1.087 0 0 1-1.087-1.088v-3.81c0-.602-.489-1.087-1.088-1.087h-4.04a1.087 1.087 0 0 1-1.089-1.088V26.25c0-.602-.488-1.088-1.087-1.088H1.088C.485 25.161 0 25.65 0 26.25v4.26c0 .602.488 1.087 1.088 1.087h4.049c.602 0 1.087.489 1.087 1.088v15.192c0 .602.489 1.087 1.088 1.087h4.277c.602 0 1.088.489 1.088 1.088v4.968c0 .602.488 1.087 1.087 1.087h6.005c1.836-.13 2.156 2.335 2.137 3.214-.04 2.007-2.308 2.652-3.382 3.487-2.861 2.21-5.077 4.459-3.78 8.781l.032.09c2.362 5.017 8.855 4.499 12.956 4.499h25.817c1.459 0 2.959.339 2.614-1.362-.342-1.7-1.063-4.024-3.162-4.024-2.1 0-1.758 1.166-3.81.57-2.054-.597-2.057-3.371 1.027-8.198 3.19-4.122 8.652-3.81 11.952-.895 3.301 2.915 2.325 7.978 1.633 10.885-.396 2.048.545 3.06 1.67 3.032H78.58c2.015-.035 1.62-1.391.464-4.035h.008z"
|
||||
fill="#fff"
|
||||
>
|
||||
</path>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div id="app"></div>
|
||||
|
||||
<script type="text/javascript" src="//cloudfront.loggly.com/js/loggly.tracker-latest.min.js" async></script>
|
||||
<!-- Translations -->
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,64 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
import { approvedLanguages } from '../common/script/libs/i18n';
|
||||
|
||||
const localePath = path.join(__dirname, '../common/locales/');
|
||||
|
||||
const translations = {};
|
||||
const momentLangs = {};
|
||||
|
||||
function loadFile (file) {
|
||||
const contents = fs.readFileSync(file);
|
||||
return JSON.parse(contents);
|
||||
}
|
||||
|
||||
function _loadTranslations (locale) {
|
||||
const files = fs.readdirSync(path.join(localePath, locale));
|
||||
|
||||
translations[locale] = {};
|
||||
|
||||
files.forEach(file => {
|
||||
if (path.extname(file) !== '.json') return;
|
||||
|
||||
const t = loadFile(path.join(localePath, locale, file))
|
||||
translations[locale] = {
|
||||
...translations[locale],
|
||||
...t,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 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
|
||||
translations[file] = {
|
||||
...translations.en,
|
||||
...translations[file],
|
||||
}
|
||||
});
|
||||
|
||||
export default function localePlugin() {
|
||||
const virtualModuleId = 'virtual:translations'
|
||||
const resolvedVirtualModuleId = '\0' + virtualModuleId
|
||||
|
||||
return {
|
||||
name: 'locale', // required, will show up in warnings and errors
|
||||
resolveId(id) {
|
||||
if (id === virtualModuleId) {
|
||||
return resolvedVirtualModuleId
|
||||
}
|
||||
},
|
||||
load(id) {
|
||||
if (id === resolvedVirtualModuleId) {
|
||||
return `export default ${JSON.stringify(translations)}`;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
Generated
+3590
-9
File diff suppressed because it is too large
Load Diff
@@ -40,6 +40,7 @@
|
||||
"uuid": "^9.0.1",
|
||||
"validator": "^13.9.0",
|
||||
"vite": "^6.0.0",
|
||||
"vite-plugin-prerender": "^1.0.8",
|
||||
"vue": "^2.7.10",
|
||||
"vue-fragment": "^1.6.0",
|
||||
"vue-mugen-scroll": "^0.2.6",
|
||||
@@ -49,6 +50,8 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.21.0",
|
||||
"@prerenderer/renderer-puppeteer": "^1.2.4",
|
||||
"@prerenderer/rollup-plugin": "^0.3.12",
|
||||
"@vitest/browser": "^3.0.5",
|
||||
"babel-plugin-lodash": "^3.3.4",
|
||||
"inspectpack": "^4.7.1",
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
import Vue from 'vue';
|
||||
import axios from 'axios';
|
||||
import {
|
||||
ModalPlugin,
|
||||
DropdownPlugin,
|
||||
PopoverPlugin,
|
||||
FormPlugin,
|
||||
FormInputPlugin,
|
||||
FormRadioPlugin,
|
||||
TooltipPlugin,
|
||||
NavbarPlugin,
|
||||
CollapsePlugin,
|
||||
} from 'bootstrap-vue';
|
||||
import Fragment from 'vue-fragment';
|
||||
import AppComponent from './app';
|
||||
import {
|
||||
setup as setupAnalytics,
|
||||
} from '@/libs/analytics';
|
||||
import { setUpLogging } from '@/libs/logging';
|
||||
import router from './router/index';
|
||||
import getStore from './store';
|
||||
import StoreModule from './libs/store';
|
||||
import './filters/registerGlobals';
|
||||
import i18n from './libs/i18n';
|
||||
import translations from 'virtual:translations';
|
||||
|
||||
let urlParams = new URLSearchParams(window.location.search);
|
||||
let language = urlParams.get('lang');
|
||||
window['habitica-i18n'] = {
|
||||
language,
|
||||
strings: translations[language],
|
||||
};
|
||||
|
||||
const IS_PRODUCTION = import.meta.env.NODE_ENV === 'production'; // eslint-disable-line no-process-env
|
||||
|
||||
// Configure Vue global options, see https://vuejs.org/v2/api/#Global-Config
|
||||
|
||||
Vue.config.performance = !IS_PRODUCTION;
|
||||
Vue.config.productionTip = IS_PRODUCTION;
|
||||
|
||||
// window['habitica-i18n] is injected by the server
|
||||
Vue.use(i18n, { i18nData: window && window['habitica-i18n'] });
|
||||
Vue.use(StoreModule);
|
||||
Vue.use(ModalPlugin);
|
||||
Vue.use(DropdownPlugin);
|
||||
Vue.use(PopoverPlugin);
|
||||
Vue.use(FormPlugin);
|
||||
Vue.use(FormInputPlugin);
|
||||
Vue.use(FormRadioPlugin);
|
||||
Vue.use(TooltipPlugin);
|
||||
Vue.use(NavbarPlugin);
|
||||
Vue.use(CollapsePlugin);
|
||||
Vue.use(Fragment.Plugin);
|
||||
|
||||
setUpLogging();
|
||||
setupAnalytics(); // just create queues for analytics, no scripts loaded at this time
|
||||
const store = getStore();
|
||||
|
||||
if (import.meta.env.TIME_TRAVEL_ENABLED === 'true') {
|
||||
(async () => {
|
||||
const sinon = await import('sinon');
|
||||
if (axios.defaults.headers.common['x-api-user']) {
|
||||
const response = await axios.get('/api/v4/debug/time-travel-time');
|
||||
const time = new Date(response.data.data.time);
|
||||
Vue.config.clock = sinon.useFakeTimers({
|
||||
now: time,
|
||||
shouldAdvanceTime: true,
|
||||
});
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
const vueInstance = new Vue({
|
||||
el: '#app',
|
||||
router,
|
||||
store,
|
||||
render: h => h(AppComponent),
|
||||
});
|
||||
|
||||
export default vueInstance;
|
||||
|
||||
window.externalLink = url => {
|
||||
vueInstance.$root.$emit('habitica:external-link', url);
|
||||
};
|
||||
@@ -27,12 +27,7 @@ const IS_PRODUCTION = import.meta.env.NODE_ENV === 'production'; // eslint-disab
|
||||
|
||||
// Configure Vue global options, see https://vuejs.org/v2/api/#Global-Config
|
||||
|
||||
// Enable perf timeline measuring for Vue components in Chrome Dev Tools
|
||||
// Note: this has been disabled because it caused some perf issues
|
||||
// if rendering becomes too slow in dev mode, we should turn it off
|
||||
// See https://github.com/vuejs/vue/issues/5174
|
||||
Vue.config.performance = !IS_PRODUCTION;
|
||||
// Disable annoying reminder abour production build in dev mode
|
||||
Vue.config.productionTip = IS_PRODUCTION;
|
||||
|
||||
// window['habitica-i18n] is injected by the server
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import translations from 'virtual:translations';
|
||||
|
||||
let urlParams = new URLSearchParams(window.location.search);
|
||||
let language = urlParams.get('lang');
|
||||
window['habitica-i18n'] = {
|
||||
language,
|
||||
strings: translations[language],
|
||||
};
|
||||
@@ -3,13 +3,16 @@ import path from 'path';
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue2'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import prerender from '@prerenderer/rollup-plugin'
|
||||
|
||||
import setupNconf from '../server/libs/setupNconf';
|
||||
import localePlugin from './localePlugin';
|
||||
import { approvedLanguages } from '../common/script/libs/i18n';
|
||||
|
||||
const configFile = path.join(path.resolve(__dirname, '../../config.json'));
|
||||
setupNconf(configFile, nconf);
|
||||
const DEV_BASE_URL = nconf.get('BASE_URL');
|
||||
|
||||
|
||||
const envVars = [
|
||||
'AMAZON_PAYMENTS_SELLER_ID',
|
||||
'AMAZON_PAYMENTS_CLIENT_ID',
|
||||
@@ -41,6 +44,14 @@ if (nconf.get('BASE_URL').indexOf('//habitica.com') !== -1) {
|
||||
envObject['import.meta.env.TIME_TRAVEL_ENABLED'] = `false`;
|
||||
}
|
||||
|
||||
const base_pages = ['/static/home', '/static/faq'];
|
||||
const localed_pages = [];
|
||||
approvedLanguages.forEach((lang) => {
|
||||
base_pages.forEach((page) => {
|
||||
localed_pages.push(`${page}?lang=${lang}`);
|
||||
});
|
||||
});
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
define: envObject,
|
||||
@@ -52,7 +63,36 @@ export default defineConfig({
|
||||
extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue']
|
||||
},
|
||||
plugins: [
|
||||
vue()
|
||||
localePlugin(),
|
||||
vue(),
|
||||
prerender({
|
||||
routes: localed_pages,
|
||||
renderer: '@prerenderer/renderer-puppeteer',
|
||||
indexPath: 'index-static.html',
|
||||
entryPath: 'index-static.html',
|
||||
postProcess (renderedRoute) {
|
||||
// Replace all http with https urls and localhost to your site url
|
||||
renderedRoute.html = renderedRoute.html.replace(
|
||||
/http:/ig,
|
||||
'https:',
|
||||
).replace(
|
||||
/(https:\/\/)?(localhost|127\.0\.0\.1):\d*/ig,
|
||||
(process.env.BASE_URL || ''),
|
||||
);
|
||||
|
||||
const language = renderedRoute.originalRoute.split('?')[1].split('=')[1];
|
||||
renderedRoute.outputPath = path.join(renderedRoute.route, `index.${language}.html`);
|
||||
if (renderedRoute.outputPath.indexOf('/') === 0) {
|
||||
renderedRoute.outputPath = renderedRoute.outputPath.slice(1);
|
||||
}
|
||||
console.log(`Prerendered ${renderedRoute.outputPath} - ${renderedRoute.html.length} bytes - ${renderedRoute.route}`);
|
||||
},
|
||||
rendererOptions: {
|
||||
inject: {
|
||||
prerendered: true,
|
||||
},
|
||||
}
|
||||
}),
|
||||
],
|
||||
optimizeDeps: {
|
||||
include: ['moment-recur']
|
||||
@@ -60,6 +100,12 @@ export default defineConfig({
|
||||
build: {
|
||||
commonjsOptions: {
|
||||
include: [/moment-recur/, /node_modules/]
|
||||
},
|
||||
rollupOptions: {
|
||||
input: {
|
||||
main: path.resolve(__dirname, 'index.html'),
|
||||
'index-static.html': path.resolve(__dirname, 'index-static.html'),
|
||||
}
|
||||
}
|
||||
},
|
||||
base: '/',
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
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',
|
||||
];
|
||||
|
||||
// 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',
|
||||
},
|
||||
};
|
||||
@@ -2,12 +2,15 @@ import { serveClient } from '../../libs/client';
|
||||
|
||||
const api = {};
|
||||
|
||||
// All requests to /new_app (except /new_app/static) should serve the new client in development
|
||||
// if (IS_PROD && IS_NEW_CLIENT_ENABLED) {
|
||||
const ROOT = `${__dirname}/../../../client/dist/static`;
|
||||
|
||||
function serveStaticPage (expressRes, pageName, langCode) {
|
||||
return expressRes.sendFile(`./${pageName}/index.${langCode}.html`, { root: ROOT });
|
||||
}
|
||||
|
||||
// All the routes (except for the api and payments routes) serve the new client side
|
||||
// The code that does it can be found in /middlewares/notFound.js
|
||||
api.getNewClient = {
|
||||
api.getClient = {
|
||||
method: 'GET',
|
||||
url: '/',
|
||||
noLanguage: true,
|
||||
@@ -16,4 +19,12 @@ api.getNewClient = {
|
||||
},
|
||||
};
|
||||
|
||||
api.getStaticHomme = {
|
||||
method: 'GET',
|
||||
url: '/static/home',
|
||||
async handler (req, res) {
|
||||
return serveStaticPage(res, 'home', req.language);
|
||||
},
|
||||
};
|
||||
|
||||
export default api;
|
||||
|
||||
+23
-58
@@ -2,33 +2,16 @@ import fs from 'fs';
|
||||
import path from 'path';
|
||||
import _ from 'lodash';
|
||||
import shared from '../../common';
|
||||
import {
|
||||
approvedLanguages as capprovedLanguages,
|
||||
multipleVersionsLanguages as cmultipleVersionsLanguages,
|
||||
} from '../../common/script/libs/i18n';
|
||||
|
||||
export const localePath = path.join(__dirname, '../../common/locales/');
|
||||
export const BROWSER_SCRIPT_CACHE_PATH = path.join(__dirname, '/../../../i18n_cache/');
|
||||
|
||||
// Store translations
|
||||
export const translations = {};
|
||||
// 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',
|
||||
];
|
||||
|
||||
function _loadTranslations (locale) {
|
||||
const files = fs.readdirSync(path.join(localePath, locale));
|
||||
|
||||
@@ -46,7 +29,7 @@ function _loadTranslations (locale) {
|
||||
_loadTranslations('en');
|
||||
|
||||
// Then load all other languages
|
||||
approvedLanguages.forEach(file => {
|
||||
capprovedLanguages.forEach(file => {
|
||||
if (file === 'en' || fs.statSync(path.join(localePath, file)).isDirectory() === false) return;
|
||||
_loadTranslations(file);
|
||||
|
||||
@@ -55,8 +38,16 @@ approvedLanguages.forEach(file => {
|
||||
_.defaults(translations[file], translations.en);
|
||||
});
|
||||
|
||||
// Add translations to shared
|
||||
shared.i18n.translations = translations;
|
||||
// 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 langCodes = Object.keys(translations);
|
||||
|
||||
@@ -65,6 +56,11 @@ export const availableLanguages = langCodes.map(langCode => ({
|
||||
name: translations[langCode].languageName,
|
||||
}));
|
||||
|
||||
// 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');
|
||||
|
||||
langCodes.forEach(code => {
|
||||
const lang = _.find(availableLanguages, { code });
|
||||
|
||||
@@ -82,41 +78,10 @@ langCodes.forEach(code => {
|
||||
}
|
||||
});
|
||||
|
||||
// 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');
|
||||
// Add translations to shared
|
||||
shared.i18n.translations = translations;
|
||||
|
||||
// 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 const multipleVersionsLanguages = cmultipleVersionsLanguages;
|
||||
|
||||
export function geti18nBrowserScript (languageCode) {
|
||||
const language = _.find(availableLanguages, { code: languageCode });
|
||||
|
||||
Reference in New Issue
Block a user