mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-17 22:57:21 +01:00
Client: i18n (#8972)
* wip: client: i18n * remove maxAge from cookies to get same expiration ad localStorage * set cookies expiration to 10 years * moment: load translations in browser, moment: only load necessary data, remove jquery, remove bluebird * ability to change language * fix logout * add some requiresLogin: false to static pages * fix tests
This commit is contained in:
24
npm-shrinkwrap.json
generated
24
npm-shrinkwrap.json
generated
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "habitica",
|
"name": "habitica",
|
||||||
"version": "3.110.0",
|
"version": "3.111.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@gulp-sourcemaps/map-sources": {
|
"@gulp-sourcemaps/map-sources": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
@@ -2433,13 +2433,11 @@
|
|||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"from": "cross-env@>=4.0.0 <5.0.0",
|
"from": "cross-env@>=4.0.0 <5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-4.0.0.tgz",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"is-windows": {
|
"is-windows": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"from": "is-windows@>=1.0.0 <2.0.0",
|
"from": "is-windows@>=1.0.0 <2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.1.tgz"
|
||||||
"dev": true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -2447,19 +2445,16 @@
|
|||||||
"version": "5.1.0",
|
"version": "5.1.0",
|
||||||
"from": "cross-spawn@>=5.0.1 <6.0.0",
|
"from": "cross-spawn@>=5.0.1 <6.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"lru-cache": {
|
"lru-cache": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"from": "lru-cache@>=4.0.1 <5.0.0",
|
"from": "lru-cache@>=4.0.1 <5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz"
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"which": {
|
"which": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"from": "which@>=1.2.9 <2.0.0",
|
"from": "which@>=1.2.9 <2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz"
|
||||||
"dev": true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -7385,11 +7380,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/jpegtran-bin/-/jpegtran-bin-3.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/jpegtran-bin/-/jpegtran-bin-3.2.0.tgz",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"jquery": {
|
|
||||||
"version": "3.2.1",
|
|
||||||
"from": "jquery@>=3.1.1 <4.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.2.1.tgz"
|
|
||||||
},
|
|
||||||
"js-base64": {
|
"js-base64": {
|
||||||
"version": "2.1.9",
|
"version": "2.1.9",
|
||||||
"from": "js-base64@>=2.1.9 <3.0.0",
|
"from": "js-base64@>=2.1.9 <3.0.0",
|
||||||
@@ -11687,14 +11677,12 @@
|
|||||||
"shebang-command": {
|
"shebang-command": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"from": "shebang-command@>=1.2.0 <2.0.0",
|
"from": "shebang-command@>=1.2.0 <2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz"
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"shebang-regex": {
|
"shebang-regex": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"from": "shebang-regex@>=1.0.0 <2.0.0",
|
"from": "shebang-regex@>=1.0.0 <2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz"
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"shell-quote": {
|
"shell-quote": {
|
||||||
"version": "1.4.3",
|
"version": "1.4.3",
|
||||||
|
|||||||
@@ -75,7 +75,6 @@
|
|||||||
"in-app-purchase": "^1.1.6",
|
"in-app-purchase": "^1.1.6",
|
||||||
"intro.js": "^2.6.0",
|
"intro.js": "^2.6.0",
|
||||||
"jade": "~1.11.0",
|
"jade": "~1.11.0",
|
||||||
"jquery": "^3.1.1",
|
|
||||||
"js2xmlparser": "~1.0.0",
|
"js2xmlparser": "~1.0.0",
|
||||||
"lodash": "^4.17.4",
|
"lodash": "^4.17.4",
|
||||||
"merge-stream": "^1.0.0",
|
"merge-stream": "^1.0.0",
|
||||||
|
|||||||
@@ -4,7 +4,13 @@ import Vue from 'vue';
|
|||||||
|
|
||||||
describe('i18n plugin', () => {
|
describe('i18n plugin', () => {
|
||||||
before(() => {
|
before(() => {
|
||||||
Vue.use(i18n);
|
Vue.use(i18n, {
|
||||||
|
i18nData: {
|
||||||
|
strings: {
|
||||||
|
reportBug: 'Report a Bug',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('adds $t to Vue.prototype', () => {
|
it('adds $t to Vue.prototype', () => {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ module.exports = {
|
|||||||
productionGzipExtensions: ['js', 'css'],
|
productionGzipExtensions: ['js', 'css'],
|
||||||
// Run the build command with an extra argument to
|
// Run the build command with an extra argument to
|
||||||
// View the bundle analyzer report after build finishes:
|
// View the bundle analyzer report after build finishes:
|
||||||
// `npm run build --report`
|
// `npm run client:build --report`
|
||||||
// Set to `true` or `false` to always turn it on or off
|
// Set to `true` or `false` to always turn it on or off
|
||||||
bundleAnalyzerReport: process.env.npm_config_report, // eslint-disable-line no-process-env
|
bundleAnalyzerReport: process.env.npm_config_report, // eslint-disable-line no-process-env
|
||||||
},
|
},
|
||||||
@@ -50,6 +50,10 @@ module.exports = {
|
|||||||
target: 'http://localhost:3000',
|
target: 'http://localhost:3000',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
},
|
},
|
||||||
|
'/logout': {
|
||||||
|
target: 'http://localhost:3000',
|
||||||
|
changeOrigin: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
// CSS Sourcemaps off by default because relative paths are "buggy"
|
// CSS Sourcemaps off by default because relative paths are "buggy"
|
||||||
// with this option, according to the CSS-Loader README
|
// with this option, according to the CSS-Loader README
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const config = require('./config');
|
const config = require('./config');
|
||||||
const utils = require('./utils');
|
const utils = require('./utils');
|
||||||
const projectRoot = path.resolve(__dirname, '../');
|
|
||||||
const webpack = require('webpack');
|
const webpack = require('webpack');
|
||||||
|
const projectRoot = path.resolve(__dirname, '../');
|
||||||
const autoprefixer = require('autoprefixer');
|
const autoprefixer = require('autoprefixer');
|
||||||
const postcssEasyImport = require('postcss-easy-import');
|
const postcssEasyImport = require('postcss-easy-import');
|
||||||
const IS_PROD = process.env.NODE_ENV === 'production';
|
const IS_PROD = process.env.NODE_ENV === 'production';
|
||||||
@@ -36,7 +36,6 @@ const baseConfig = {
|
|||||||
path.join(projectRoot, 'node_modules'),
|
path.join(projectRoot, 'node_modules'),
|
||||||
],
|
],
|
||||||
alias: {
|
alias: {
|
||||||
jquery: 'jquery/src/jquery',
|
|
||||||
website: path.resolve(projectRoot, 'website'),
|
website: path.resolve(projectRoot, 'website'),
|
||||||
common: path.resolve(projectRoot, 'website/common'),
|
common: path.resolve(projectRoot, 'website/common'),
|
||||||
client: path.resolve(projectRoot, 'website/client'),
|
client: path.resolve(projectRoot, 'website/client'),
|
||||||
@@ -45,10 +44,7 @@ const baseConfig = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
new webpack.ProvidePlugin({
|
new webpack.ContextReplacementPlugin(/moment[\\\/]locale$/, /^\.\/(NOT_EXISTING)$/),
|
||||||
$: 'jquery',
|
|
||||||
jQuery: 'jquery',
|
|
||||||
}),
|
|
||||||
],
|
],
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
|
|||||||
@@ -6,9 +6,6 @@
|
|||||||
"plugins": [
|
"plugins": [
|
||||||
"html"
|
"html"
|
||||||
],
|
],
|
||||||
"globals": {
|
|
||||||
"$": true,
|
|
||||||
},
|
|
||||||
"parser": "babel-eslint",
|
"parser": "babel-eslint",
|
||||||
"rules": {
|
"rules": {
|
||||||
"strict": 0
|
"strict": 0
|
||||||
|
|||||||
@@ -262,7 +262,7 @@ export default {
|
|||||||
methods: {
|
methods: {
|
||||||
logout () {
|
logout () {
|
||||||
localStorage.removeItem('habit-mobile-settings');
|
localStorage.removeItem('habit-mobile-settings');
|
||||||
this.$router.go('/');
|
window.location.href = '/logout';
|
||||||
},
|
},
|
||||||
showInbox () {
|
showInbox () {
|
||||||
this.$root.$emit('show::modal', 'inbox-modal');
|
this.$root.$emit('show::modal', 'inbox-modal');
|
||||||
|
|||||||
@@ -125,7 +125,6 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import Bluebird from 'bluebird';
|
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import cloneDeep from 'lodash/cloneDeep';
|
import cloneDeep from 'lodash/cloneDeep';
|
||||||
import { mapState } from 'client/libs/store';
|
import { mapState } from 'client/libs/store';
|
||||||
@@ -203,7 +202,7 @@ export default {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let results = await Bluebird.all(promises);
|
let results = await Promise.all(promises);
|
||||||
results.forEach(result => {
|
results.forEach(result => {
|
||||||
let userData = result.data.data;
|
let userData = result.data.data;
|
||||||
this.$set(this.cachedProfileData, userData._id, userData);
|
this.$set(this.cachedProfileData, userData._id, userData);
|
||||||
|
|||||||
@@ -7,8 +7,8 @@
|
|||||||
.col-6
|
.col-6
|
||||||
.form-horizontal
|
.form-horizontal
|
||||||
h5 {{ $t('language') }}
|
h5 {{ $t('language') }}
|
||||||
select.form-control(v-model='selectedLanguage',
|
select.form-control(:value='user.preferences.language',
|
||||||
@change='changeLanguage()')
|
@change='changeLanguage($event)')
|
||||||
option(v-for='lang in availableLanguages', :value='lang.code') {{lang.name}}
|
option(v-for='lang in availableLanguages', :value='lang.code') {{lang.name}}
|
||||||
|
|
||||||
small
|
small
|
||||||
@@ -218,13 +218,7 @@ export default {
|
|||||||
return {
|
return {
|
||||||
SOCIAL_AUTH_NETWORKS: [],
|
SOCIAL_AUTH_NETWORKS: [],
|
||||||
party: {},
|
party: {},
|
||||||
// @TODO: import
|
// Made available by the server as a script
|
||||||
availableLanguages: [
|
|
||||||
{
|
|
||||||
code: 'en',
|
|
||||||
name: 'English',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
availableFormats: ['MM/dd/yyyy', 'dd/MM/yyyy', 'yyyy/MM/dd'],
|
availableFormats: ['MM/dd/yyyy', 'dd/MM/yyyy', 'yyyy/MM/dd'],
|
||||||
dayStartOptions,
|
dayStartOptions,
|
||||||
newDayStart: 0,
|
newDayStart: 0,
|
||||||
@@ -240,7 +234,10 @@ export default {
|
|||||||
this.newDayStart = this.user.preferences.dayStart;
|
this.newDayStart = this.user.preferences.dayStart;
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState({user: 'user.data'}),
|
...mapState({
|
||||||
|
user: 'user.data',
|
||||||
|
availableLanguages: 'i18n.availableLanguages',
|
||||||
|
}),
|
||||||
timezoneOffsetToUtc () {
|
timezoneOffsetToUtc () {
|
||||||
let offset = this.user.preferences.timezoneOffset;
|
let offset = this.user.preferences.timezoneOffset;
|
||||||
let sign = offset > 0 ? '-' : '+';
|
let sign = offset > 0 ? '-' : '+';
|
||||||
@@ -254,9 +251,6 @@ export default {
|
|||||||
|
|
||||||
return `UTC${sign}${hour}:${minutes}`;
|
return `UTC${sign}${hour}:${minutes}`;
|
||||||
},
|
},
|
||||||
selectedLanguage () {
|
|
||||||
return this.user.preferences.language;
|
|
||||||
},
|
|
||||||
dayStart () {
|
dayStart () {
|
||||||
return this.user.preferences.dayStart;
|
return this.user.preferences.dayStart;
|
||||||
},
|
},
|
||||||
@@ -327,9 +321,11 @@ export default {
|
|||||||
// @TODO
|
// @TODO
|
||||||
// Notification.text(response.data.data.message);
|
// Notification.text(response.data.data.message);
|
||||||
},
|
},
|
||||||
changeLanguage () {
|
changeLanguage (e) {
|
||||||
this.user.preferences.language = this.selectedLanguage.code;
|
const newLang = e.target.value;
|
||||||
|
this.user.preferences.language = newLang;
|
||||||
this.set('language');
|
this.set('language');
|
||||||
|
window.location.href = '/';
|
||||||
},
|
},
|
||||||
async changeUser (attribute, updates) {
|
async changeUser (attribute, updates) {
|
||||||
await axios.put(`/api/v3/user/auth/update-${attribute}`, updates);
|
await axios.put(`/api/v3/user/auth/update-${attribute}`, updates);
|
||||||
|
|||||||
@@ -112,12 +112,14 @@ import filter from 'lodash/filter';
|
|||||||
import sortBy from 'lodash/sortBy';
|
import sortBy from 'lodash/sortBy';
|
||||||
import min from 'lodash/min';
|
import min from 'lodash/min';
|
||||||
import { mapState } from 'client/libs/store';
|
import { mapState } from 'client/libs/store';
|
||||||
|
import encodeParams from 'client/libs/encodeParams';
|
||||||
|
|
||||||
import subscriptionBlocks from '../../../common/script/content/subscriptionBlocks';
|
import subscriptionBlocks from '../../../common/script/content/subscriptionBlocks';
|
||||||
import planGemLimits from '../../../common/script/libs/planGemLimits';
|
import planGemLimits from '../../../common/script/libs/planGemLimits';
|
||||||
import amazonPaymentsModal from '../payments/amazonModal';
|
import amazonPaymentsModal from '../payments/amazonModal';
|
||||||
import paymentsMixin from '../../mixins/payments';
|
import paymentsMixin from '../../mixins/payments';
|
||||||
|
|
||||||
|
// TODO
|
||||||
const STRIPE_PUB_KEY = 'pk_test_6pRNASCoBOKtIshFeQd4XMUh';
|
const STRIPE_PUB_KEY = 'pk_test_6pRNASCoBOKtIshFeQd4XMUh';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -314,7 +316,7 @@ export default {
|
|||||||
queryParams.groupId = group._id;
|
queryParams.groupId = group._id;
|
||||||
}
|
}
|
||||||
|
|
||||||
let cancelUrl = `/${paymentMethod}/subscribe/cancel?${$.param(queryParams)}`;
|
let cancelUrl = `/${paymentMethod}/subscribe/cancel?${encodeParams(queryParams)}`;
|
||||||
await axios.get(cancelUrl);
|
await axios.get(cancelUrl);
|
||||||
// Success
|
// Success
|
||||||
alert(this.$t('paypalCanceled'));
|
alert(this.$t('paypalCanceled'));
|
||||||
|
|||||||
@@ -10,11 +10,11 @@
|
|||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
<!-- built files will be auto injected -->
|
<!-- built files will be auto injected -->
|
||||||
|
|
||||||
<script async type='text/javascript'
|
<!-- Translations -->
|
||||||
src='https://static-na.payments-amazon.com/OffAmazonPayments/us/sandbox/js/Widgets.js'>
|
<script type='text/javascript' src='/api/v3/i18n/browser-script'></script>
|
||||||
</script>
|
|
||||||
<script src="https://checkout.stripe.com/v2/checkout.js"></script>
|
<script async type='text/javascript' src='https://static-na.payments-amazon.com/OffAmazonPayments/us/sandbox/js/Widgets.js'></script>
|
||||||
</script>
|
<script async type='text/javascript' src="https://checkout.stripe.com/v2/checkout.js"></script>
|
||||||
<script>
|
<script>
|
||||||
// Amplitude
|
// Amplitude
|
||||||
// var r = window.amplitude || {};
|
// var r = window.amplitude || {};
|
||||||
|
|||||||
7
website/client/libs/encodeParams.js
Normal file
7
website/client/libs/encodeParams.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
// Equivalent of jQuery's param
|
||||||
|
|
||||||
|
export default function (params) {
|
||||||
|
Object.keys(params).map((k) => {
|
||||||
|
return `${encodeURIComponent(k)}=${encodeURIComponent(params[k])}`;
|
||||||
|
}).join('&');
|
||||||
|
}
|
||||||
@@ -2,20 +2,31 @@
|
|||||||
// Can be anywhere inside vue as 'this.$t' or '$t' in templates.
|
// Can be anywhere inside vue as 'this.$t' or '$t' in templates.
|
||||||
|
|
||||||
import i18n from 'common/script/i18n';
|
import i18n from 'common/script/i18n';
|
||||||
|
import moment from 'moment';
|
||||||
// Load all english translations
|
|
||||||
// TODO it's a workaround until proper translation loading works
|
|
||||||
const context = require.context('common/locales/en', true, /\.(json)$/);
|
|
||||||
const translations = {};
|
|
||||||
|
|
||||||
context.keys().forEach(filename => {
|
|
||||||
Object.assign(translations, context(filename));
|
|
||||||
});
|
|
||||||
|
|
||||||
i18n.strings = translations;
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
install (Vue) {
|
install (Vue, {i18nData}) {
|
||||||
|
if (i18nData) {
|
||||||
|
// Load i18n strings
|
||||||
|
i18n.strings = i18nData.strings;
|
||||||
|
|
||||||
|
// Load Moment.js locale
|
||||||
|
const language = i18nData.language;
|
||||||
|
|
||||||
|
if (language && i18nData.momentLang && language.momentLangCode) {
|
||||||
|
// Make moment available under `window` so that the locale can be set
|
||||||
|
window.moment = moment;
|
||||||
|
|
||||||
|
// Execute the script and set the locale
|
||||||
|
const head = document.getElementsByTagName('head')[0];
|
||||||
|
const script = document.createElement('script');
|
||||||
|
script.type = 'text/javascript';
|
||||||
|
script.text = i18nData.momentLang;
|
||||||
|
head.appendChild(script);
|
||||||
|
moment.locale(language.momentLangCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Vue.prototype.$t = function translateString () {
|
Vue.prototype.$t = function translateString () {
|
||||||
return i18n.t.apply(null, arguments);
|
return i18n.t.apply(null, arguments);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -24,7 +24,9 @@ Vue.config.performance = !IS_PRODUCTION;
|
|||||||
Vue.config.productionTip = IS_PRODUCTION;
|
Vue.config.productionTip = IS_PRODUCTION;
|
||||||
|
|
||||||
Vue.use(Notifications);
|
Vue.use(Notifications);
|
||||||
Vue.use(i18n);
|
|
||||||
|
// window['habitica-i18n] is injected by the server
|
||||||
|
Vue.use(i18n, {i18nData: window && window['habitica-i18n']});
|
||||||
Vue.use(StoreModule);
|
Vue.use(StoreModule);
|
||||||
|
|
||||||
export default new Vue({
|
export default new Vue({
|
||||||
|
|||||||
@@ -243,24 +243,24 @@ const router = new VueRouter({
|
|||||||
path: '/static',
|
path: '/static',
|
||||||
component: ParentPage,
|
component: ParentPage,
|
||||||
children: [
|
children: [
|
||||||
{ name: 'app', path: 'app', component: AppPage },
|
{ name: 'app', path: 'app', component: AppPage, meta: {requiresLogin: false}},
|
||||||
{ name: 'clearBrowserData', path: 'clear-browser-data', component: ClearBrowserDataPage },
|
{ name: 'clearBrowserData', path: 'clear-browser-data', component: ClearBrowserDataPage, meta: {requiresLogin: false}},
|
||||||
{ name: 'communitGuidelines', path: 'community-guidelines', component: CommunityGuidelinesPage },
|
{ name: 'communitGuidelines', path: 'community-guidelines', component: CommunityGuidelinesPage, meta: {requiresLogin: false}},
|
||||||
{ name: 'contact', path: 'contact', component: ContactPage },
|
{ name: 'contact', path: 'contact', component: ContactPage, meta: {requiresLogin: false}},
|
||||||
{ name: 'faq', path: 'faq', component: FAQPage },
|
{ name: 'faq', path: 'faq', component: FAQPage, meta: {requiresLogin: false}},
|
||||||
{ name: 'features', path: 'features', component: FeaturesPage },
|
{ name: 'features', path: 'features', component: FeaturesPage, meta: {requiresLogin: false}},
|
||||||
{ name: 'front', path: 'front', component: FrontPage },
|
{ name: 'front', path: 'front', component: FrontPage, meta: {requiresLogin: false}},
|
||||||
{ name: 'groupPlans', path: 'group-plans', component: GroupPlansPage },
|
{ name: 'groupPlans', path: 'group-plans', component: GroupPlansPage, meta: {requiresLogin: false}},
|
||||||
{ name: 'maintenance', path: 'maintenance', component: MaintenancePage },
|
{ name: 'maintenance', path: 'maintenance', component: MaintenancePage, meta: {requiresLogin: false}},
|
||||||
{ name: 'maintenance-info', path: 'maintenance-info', component: MaintenanceInfoPage },
|
{ name: 'maintenance-info', path: 'maintenance-info', component: MaintenanceInfoPage, meta: {requiresLogin: false}},
|
||||||
{ name: 'merch', path: 'merch', component: MerchPage },
|
{ name: 'merch', path: 'merch', component: MerchPage, meta: {requiresLogin: false}},
|
||||||
// { name: 'newStuff', path: 'newStuff', component: NewStuffPage },
|
// { name: 'newStuff', path: 'newStuff', component: NewStuffPage, meta: {requiresLogin: false}},
|
||||||
{ name: 'overview', path: 'overview', component: OverviewPage },
|
{ name: 'overview', path: 'overview', component: OverviewPage, meta: {requiresLogin: false}},
|
||||||
{ name: 'plans', path: 'plans', component: GroupPlansPage },
|
{ name: 'plans', path: 'plans', component: GroupPlansPage, meta: {requiresLogin: false}},
|
||||||
{ name: 'pressKit', path: 'press-kit', component: PressKitPage },
|
{ name: 'pressKit', path: 'press-kit', component: PressKitPage, meta: {requiresLogin: false}},
|
||||||
{ name: 'privacy', path: 'privacy', component: PrivacyPage, meta: {requiresLogin: false}},
|
{ name: 'privacy', path: 'privacy', component: PrivacyPage, meta: {requiresLogin: false}},
|
||||||
{ name: 'terms', path: 'terms', component: TermsPage, meta: {requiresLogin: false}},
|
{ name: 'terms', path: 'terms', component: TermsPage, meta: {requiresLogin: false}},
|
||||||
{ name: 'videos', path: 'videos', component: VideosPage },
|
{ name: 'videos', path: 'videos', component: VideosPage, meta: {requiresLogin: false}},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -25,6 +25,16 @@ if (AUTH_SETTINGS) {
|
|||||||
isUserLoggedIn = true;
|
isUserLoggedIn = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const i18nData = window && window['habitica-i18n'];
|
||||||
|
|
||||||
|
let availableLanguages = [];
|
||||||
|
let selectedLanguage = {};
|
||||||
|
|
||||||
|
if (i18nData) {
|
||||||
|
availableLanguages = i18nData.availableLanguages;
|
||||||
|
selectedLanguage = i18nData.language;
|
||||||
|
}
|
||||||
|
|
||||||
// Export a function that generates the store and not the store directly
|
// Export a function that generates the store and not the store directly
|
||||||
// so that we can regenerate it multiple times for testing, when not testing
|
// so that we can regenerate it multiple times for testing, when not testing
|
||||||
// always export the same route
|
// always export the same route
|
||||||
@@ -73,6 +83,10 @@ export default function () {
|
|||||||
// NOTE this takes about 10-15ms on a fast computer
|
// NOTE this takes about 10-15ms on a fast computer
|
||||||
content: deepFreeze(content),
|
content: deepFreeze(content),
|
||||||
constants: deepFreeze({...commonConstants, DAY_MAPPING}),
|
constants: deepFreeze({...commonConstants, DAY_MAPPING}),
|
||||||
|
i18n: deepFreeze({
|
||||||
|
availableLanguages,
|
||||||
|
selectedLanguage,
|
||||||
|
}),
|
||||||
hideHeader: false,
|
hideHeader: false,
|
||||||
viewingMembers: [],
|
viewingMembers: [],
|
||||||
openedItemRows: [],
|
openedItemRows: [],
|
||||||
|
|||||||
@@ -101,6 +101,7 @@ async function saveContentToDisk (language, content) {
|
|||||||
api.getContent = {
|
api.getContent = {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
url: '/content',
|
url: '/content',
|
||||||
|
noLanguage: true,
|
||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
let language = 'en';
|
let language = 'en';
|
||||||
let proposedLang = req.query.language && req.query.language.toString();
|
let proposedLang = req.query.language && req.query.language.toString();
|
||||||
|
|||||||
46
website/server/controllers/api-v3/i18n.js
Normal file
46
website/server/controllers/api-v3/i18n.js
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import {
|
||||||
|
translations,
|
||||||
|
momentLangs,
|
||||||
|
availableLanguages,
|
||||||
|
} from '../../libs/i18n';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
const api = {};
|
||||||
|
|
||||||
|
function geti18nBrowserScript (language) {
|
||||||
|
const langCode = language.code;
|
||||||
|
|
||||||
|
return `(function () {
|
||||||
|
if (!window) return;
|
||||||
|
window['habitica-i18n'] = ${JSON.stringify({
|
||||||
|
availableLanguages,
|
||||||
|
language,
|
||||||
|
strings: translations[langCode],
|
||||||
|
momentLang: momentLangs[language.momentLangCode],
|
||||||
|
})};
|
||||||
|
})()`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @api {get} /api/v3/i18n/browser-script Returns a JS script to make all the i18n strings available in the browser
|
||||||
|
* under window.i18n.strings
|
||||||
|
* @apiDescription Does not require authentication.
|
||||||
|
* @apiName i18nBrowserScriptGet
|
||||||
|
* @apiGroup i18n
|
||||||
|
*/
|
||||||
|
api.geti18nBrowserScript = {
|
||||||
|
method: 'GET',
|
||||||
|
url: '/i18n/browser-script',
|
||||||
|
async handler (req, res) {
|
||||||
|
const language = _.find(availableLanguages, {code: req.language});
|
||||||
|
|
||||||
|
res.set({
|
||||||
|
'Content-Type': 'application/javascript',
|
||||||
|
});
|
||||||
|
|
||||||
|
const jsonResString = geti18nBrowserScript(language);
|
||||||
|
res.status(200).send(jsonResString);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = api;
|
||||||
@@ -90,6 +90,7 @@ api.getFrontPage = {
|
|||||||
api.getNewClient = {
|
api.getNewClient = {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
url: '/',
|
url: '/',
|
||||||
|
noLanguage: true,
|
||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
return res.sendFile('./dist-client/index.html', {root: `${__dirname}/../../../../`});
|
return res.sendFile('./dist-client/index.html', {root: `${__dirname}/../../../../`});
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -25,14 +25,16 @@ module.exports.readController = function readController (router, controller) {
|
|||||||
|
|
||||||
let middlewaresToAdd = [getUserLanguage];
|
let middlewaresToAdd = [getUserLanguage];
|
||||||
|
|
||||||
if (authMiddlewareIndex !== -1) { // the user will be authenticated, getUserLanguage and cron after authentication
|
if (action.noLanguage !== true) {
|
||||||
if (authMiddlewareIndex === middlewares.length - 1) {
|
if (authMiddlewareIndex !== -1) { // the user will be authenticated, getUserLanguage after authentication
|
||||||
middlewares.push(...middlewaresToAdd);
|
if (authMiddlewareIndex === middlewares.length - 1) {
|
||||||
} else {
|
middlewares.push(...middlewaresToAdd);
|
||||||
middlewares.splice(authMiddlewareIndex + 1, 0, ...middlewaresToAdd);
|
} else {
|
||||||
|
middlewares.splice(authMiddlewareIndex + 1, 0, ...middlewaresToAdd);
|
||||||
|
}
|
||||||
|
} else { // no auth, getUserLanguage as the first middleware
|
||||||
|
middlewares.unshift(...middlewaresToAdd);
|
||||||
}
|
}
|
||||||
} else { // no auth, getUserLanguage as the first middleware
|
|
||||||
middlewares.unshift(...middlewaresToAdd);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
method = method.toLowerCase();
|
method = method.toLowerCase();
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ const ENABLE_HTTP_AUTH = nconf.get('SITE_HTTP_AUTH:ENABLED') === 'true';
|
|||||||
// const PUBLIC_DIR = path.join(__dirname, '/../../client');
|
// const PUBLIC_DIR = path.join(__dirname, '/../../client');
|
||||||
|
|
||||||
const SESSION_SECRET = nconf.get('SESSION_SECRET');
|
const SESSION_SECRET = nconf.get('SESSION_SECRET');
|
||||||
const TWO_WEEKS = 1000 * 60 * 60 * 24 * 14;
|
const TEN_YEARS = 1000 * 60 * 60 * 24 * 365 * 10;
|
||||||
|
|
||||||
module.exports = function attachMiddlewares (app, server) {
|
module.exports = function attachMiddlewares (app, server) {
|
||||||
app.set('view engine', 'jade');
|
app.set('view engine', 'jade');
|
||||||
@@ -68,7 +68,7 @@ module.exports = function attachMiddlewares (app, server) {
|
|||||||
secret: SESSION_SECRET,
|
secret: SESSION_SECRET,
|
||||||
httpOnly: true, // so cookies are not accessible with browser JS
|
httpOnly: true, // so cookies are not accessible with browser JS
|
||||||
// TODO what about https only (secure) ?
|
// TODO what about https only (secure) ?
|
||||||
maxAge: TWO_WEEKS,
|
maxAge: TEN_YEARS,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Initialize Passport! Also use passport.session() middleware, to support
|
// Initialize Passport! Also use passport.session() middleware, to support
|
||||||
|
|||||||
Reference in New Issue
Block a user