mirror of
https://gitlab.com/PronounsPage/PronounsPage.git
synced 2025-09-08 23:14:43 -04:00
TranslationMode progress
This commit is contained in:
parent
4ff88bbcf6
commit
d285e04272
@ -9,11 +9,11 @@
|
||||
</p>
|
||||
</div>
|
||||
<div class="modal-body" v-if="message">
|
||||
<p class="py-5 text-center" v-html="message"></p>
|
||||
<p :class="[margin ? 'py-5 text-center' : '']" v-html="message"></p>
|
||||
</div>
|
||||
<div class="modal-body" v-if="value !== undefined">
|
||||
<ListInput v-if="Array.isArray(value)" v-model="value" v-slot="s">
|
||||
<textarea v-model="s.val" class="form-control" rows="5"></textarea>
|
||||
<textarea v-model="s.val" class="form-control" rows="5" @keyup="s.update(s.val)" @update="s.update(s.val)"></textarea>
|
||||
</ListInput>
|
||||
<textarea v-else v-model="value" class="form-control" rows="5"></textarea>
|
||||
</div>
|
||||
@ -53,6 +53,7 @@
|
||||
icon: undefined,
|
||||
header: undefined,
|
||||
message: undefined,
|
||||
margin: true,
|
||||
color: null,
|
||||
value: undefined,
|
||||
size: undefined,
|
||||
@ -88,7 +89,8 @@
|
||||
this.icon = message.icon || (choice ? 'map-marker-question' : null);
|
||||
this.header = message.header;
|
||||
this.message = message.message || (choice ? this.$t('confirm.header') : null);
|
||||
this.size = size;
|
||||
this.margin = message.margin ?? true;
|
||||
this.size = message.size ?? size;
|
||||
this.color = color;
|
||||
this.value = value;
|
||||
this.shown = true;
|
||||
@ -118,6 +120,7 @@
|
||||
this.icon = undefined;
|
||||
this.header = undefined;
|
||||
this.message = undefined;
|
||||
this.margin = true;
|
||||
this.color = null;
|
||||
this.value = undefined;
|
||||
this.size = undefined;
|
||||
|
@ -3,7 +3,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import t from '../src/translator';
|
||||
import translator from '../src/translator';
|
||||
import {mapState} from "vuex";
|
||||
|
||||
export default {
|
||||
@ -23,12 +23,12 @@
|
||||
'translationChanges',
|
||||
]),
|
||||
modified() {
|
||||
return this.translationChanges.hasOwnProperty(this.key);
|
||||
return this.translationMode && this.translationChanges.hasOwnProperty(this.key);
|
||||
},
|
||||
txt() {
|
||||
return this.modified
|
||||
? t(this.translationChanges[this.key], this.params || {}, !this.silent, false)
|
||||
: t(this.key, this.params || {}, !this.silent);
|
||||
? translator.applyParams(this.translationChanges[this.key], this.params || {})
|
||||
: translator.translate(this.key, this.params || {}, !this.silent);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@ -38,14 +38,30 @@
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const base = translator.get(this.key, false, true);
|
||||
|
||||
const newValue = await this.$editor(
|
||||
this.modified ? this.translationChanges[this.key] : t(this.key),
|
||||
{ icon: 'language', header: this.key },
|
||||
this.modified
|
||||
? this.translationChanges[this.key]
|
||||
: translator.get(this.key),
|
||||
{
|
||||
icon: 'language',
|
||||
header: this.key,
|
||||
message: base
|
||||
? ('<div class="small alert alert-info">'
|
||||
+ (Array.isArray(base)
|
||||
? `<ul>${base.map(el => '<li>' + el + '</el>')}</ul>`
|
||||
: base)
|
||||
+ '</div>')
|
||||
: undefined,
|
||||
margin: false,
|
||||
},
|
||||
'info'
|
||||
);
|
||||
|
||||
if (newValue !== undefined) {
|
||||
this.$store.commit('translate', {key: this.key, newValue});
|
||||
this.$cookies.set('translations', this.$store.state.translationChanges);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
@ -1,19 +1,24 @@
|
||||
<template>
|
||||
<div v-if="$isGranted()" class="scroll-btn d-print-none d-flex align-items-center">
|
||||
<div v-if="$isGranted('translations')" class="scroll-btn d-print-none d-flex align-items-center">
|
||||
<template v-if="translationMode">
|
||||
<div class="bg-info rounded m-1 px-3 py-1 d-flex justify-content-center align-items-center">
|
||||
<button class="btn btn-info btn-sm m-1 px-3 py-1 d-flex justify-content-center align-items-center" @click="showChanges">
|
||||
<small>Changes: {{ changesCount }}</small>
|
||||
</div>
|
||||
</button>
|
||||
<div v-if="changesCount" @click.prevent="commitChanges">
|
||||
<SquareButton link="#" colour="success" aria-label="Commit changes">
|
||||
<Icon v="check-circle"/>
|
||||
</SquareButton>
|
||||
</div>
|
||||
<div @click.prevent="revertChanges">
|
||||
<div v-if="changesCount" @click.prevent="revertChanges">
|
||||
<SquareButton link="#" colour="danger" aria-label="Revert changes">
|
||||
<Icon v="times-circle"/>
|
||||
</SquareButton>
|
||||
</div>
|
||||
<div @click.prevent="pause">
|
||||
<SquareButton link="#" colour="info" aria-label="Pause translation mode">
|
||||
<Icon v="pause-circle"/>
|
||||
</SquareButton>
|
||||
</div>
|
||||
</template>
|
||||
<div v-else @click.prevent="startTranslating">
|
||||
<SquareButton link="#" colour="info" aria-label="Translation Mode">
|
||||
@ -25,6 +30,7 @@
|
||||
|
||||
<script>
|
||||
import {mapState} from "vuex";
|
||||
import Suml from 'suml';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
@ -37,13 +43,34 @@
|
||||
},
|
||||
async commitChanges() {
|
||||
await this.$confirm(`Do you want to commit ${this.changesCount} changes?`, 'success');
|
||||
const response = await this.$post(`/translations/propose`, {
|
||||
changes: this.translationChanges,
|
||||
});
|
||||
this.$store.commit('translationCommit');
|
||||
this.$cookies.remove('translations');
|
||||
|
||||
setTimeout(
|
||||
() => this.$alert({header: 'Your translation proposals were saved', message: 'Thank you for contributing!'}, 'success'),
|
||||
500,
|
||||
)
|
||||
},
|
||||
async revertChanges() {
|
||||
if (this.changesCount) {
|
||||
await this.$confirm(`Do you want to revert ${this.changesCount} changes?`, 'danger');
|
||||
}
|
||||
this.$store.commit('translationAbort');
|
||||
this.$cookies.remove('translations');
|
||||
},
|
||||
async showChanges() {
|
||||
await this.$alert({
|
||||
header: 'Changes overview',
|
||||
message: '<pre>' + new Suml().dump(this.translationChanges) + '</pre>',
|
||||
margin: false,
|
||||
size: 'lg',
|
||||
}, 'info');
|
||||
},
|
||||
async pause() {
|
||||
this.$store.commit('translationPause');
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
|
18
migrations/056-translations.sql
Normal file
18
migrations/056-translations.sql
Normal file
@ -0,0 +1,18 @@
|
||||
-- Up
|
||||
|
||||
CREATE TABLE translations (
|
||||
id TEXT NOT NULL PRIMARY KEY,
|
||||
locale TEXT NOT NULL,
|
||||
tKey TEXT NOT NULL,
|
||||
tValue TEXT NOT NULL,
|
||||
status INTEGER NOT NULL,
|
||||
author_id TEXT NULL REFERENCES users(id)
|
||||
);
|
||||
|
||||
CREATE INDEX "translations_locale" ON "translations" ("locale");
|
||||
CREATE INDEX "translations_key" ON "translations" ("key");
|
||||
CREATE INDEX "translations_status" ON "translations" ("status");
|
||||
|
||||
-- Down
|
||||
|
||||
DROP TABLE translations;
|
@ -1,5 +1,5 @@
|
||||
import Vue from 'vue'
|
||||
import t from '../src/translator';
|
||||
import translator from '../src/translator';
|
||||
import config from '../data/config.suml';
|
||||
import {buildDict} from "../src/helpers";
|
||||
import {DateTime} from "luxon";
|
||||
@ -10,11 +10,11 @@ export default ({ app, store }) => {
|
||||
|
||||
Vue.prototype.$base = process.env.BASE_URL;
|
||||
|
||||
Vue.prototype.$t = t;
|
||||
Vue.prototype.$te = key => t(key, {}, false) !== undefined;
|
||||
Vue.prototype.$t = (key, params = {}, warn = true) => translator.translate(key, params, warn);
|
||||
Vue.prototype.$te = (key) => translator.has(key);
|
||||
Vue.prototype.$translateForPronoun = (str, pronoun) =>
|
||||
pronoun.format(
|
||||
t(`flags.${str.replace(/ /g, '_').replace(/'/g, `*`)}`, {}, false) || str
|
||||
translator.translate(`flags.${str.replace(/ /g, '_').replace(/'/g, `*`)}`, {}, false) || str
|
||||
);
|
||||
|
||||
Vue.prototype.config = config;
|
||||
@ -31,6 +31,7 @@ export default ({ app, store }) => {
|
||||
});
|
||||
|
||||
store.commit('setSpelling', app.$cookies.get('spelling'));
|
||||
store.commit('restoreTranslations', app.$cookies.get('translations'))
|
||||
|
||||
Vue.prototype.buildImageUrl = (imageId, size) => `${process.env.CLOUDFRONT}/images/${imageId}-${size}.png`
|
||||
|
||||
|
@ -93,14 +93,11 @@ app.use(require('./routes/grantOverrides').default);
|
||||
router.use(grant.express()(require('./social').config));
|
||||
|
||||
app.use(require('./routes/home').default);
|
||||
|
||||
app.use(require('./routes/banner').default);
|
||||
|
||||
app.use(require('./routes/user').default);
|
||||
app.use(require('./routes/profile').default);
|
||||
app.use(require('./routes/admin').default);
|
||||
app.use(require('./routes/mfa').default);
|
||||
|
||||
app.use(require('./routes/pronouns').default);
|
||||
app.use(require('./routes/sources').default);
|
||||
app.use(require('./routes/nouns').default);
|
||||
@ -109,10 +106,10 @@ app.use(require('./routes/terms').default);
|
||||
app.use(require('./routes/pronounce').default);
|
||||
app.use(require('./routes/census').default);
|
||||
app.use(require('./routes/names').default);
|
||||
|
||||
app.use(require('./routes/images').default);
|
||||
app.use(require('./routes/blog').default);
|
||||
app.use(require('./routes/calendar').default);
|
||||
app.use(require('./routes/translations').default);
|
||||
|
||||
app.use(function (err, req, res, next) {
|
||||
console.error(err.stack);
|
||||
|
28
server/routes/translations.js
Normal file
28
server/routes/translations.js
Normal file
@ -0,0 +1,28 @@
|
||||
import { Router } from 'express';
|
||||
import SQL from 'sql-template-strings';
|
||||
import {ulid} from "ulid";
|
||||
import { handleErrorAsync } from "../../src/helpers";
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.post('/translations/propose', handleErrorAsync(async (req, res) => {
|
||||
if (!req.isGranted('translations')) {
|
||||
return res.status(401).json({error: 'Unauthorised'});
|
||||
}
|
||||
|
||||
for (let tKey in req.body.changes) {
|
||||
if (!req.body.changes.hasOwnProperty(tKey)) { continue; }
|
||||
// TODO single insert
|
||||
await req.db.get(SQL`INSERT INTO translations (id, locale, tKey, tValue, status, author_id) VALUES (
|
||||
${ulid()}, ${global.config.locale},
|
||||
${tKey}, ${JSON.stringify(req.body.changes[tKey])},
|
||||
0, ${req.user ? req.user.id : null}
|
||||
)`)
|
||||
}
|
||||
|
||||
// TODO email
|
||||
|
||||
return res.json('OK');
|
||||
}));
|
||||
|
||||
export default router;
|
@ -1,4 +1,4 @@
|
||||
import t from './translator';
|
||||
import translator from './translator';
|
||||
|
||||
export const contact = {
|
||||
all: {
|
||||
@ -75,8 +75,8 @@ const supportLinks = {
|
||||
bank: {
|
||||
icon: 'money-check-alt',
|
||||
url: 'https://bunq.me/PronounsPage',
|
||||
headline: t('support.bankAccount'),
|
||||
// tooltip: t('support.bankAccountOwner'),
|
||||
headline: translator.translate('support.bankAccount'),
|
||||
// tooltip: translator.translate('support.bankAccountOwner'),
|
||||
},
|
||||
kofi: {
|
||||
icon: 'coffee',
|
||||
|
@ -1,8 +1,21 @@
|
||||
import translations from '../data/translations.suml';
|
||||
import baseTranslations from '../locale/_base/translations.suml';
|
||||
|
||||
export default (key, params = {}, warn = true, translate = true) => {
|
||||
let value = translations;
|
||||
if (translate) {
|
||||
class Translator {
|
||||
constructor(translations, baseTranslations) {
|
||||
this.translations = translations;
|
||||
this.baseTranslations = baseTranslations;
|
||||
}
|
||||
|
||||
translate(key, params = {}, warn = true) {
|
||||
return this.applyParams(
|
||||
this.get(key, warn),
|
||||
params,
|
||||
);
|
||||
}
|
||||
|
||||
get(key, warn = true, base = false) {
|
||||
let value = base ? this.baseTranslations : this.translations;
|
||||
for (let part of key.split('.')) {
|
||||
value = value[part];
|
||||
if (value === undefined) {
|
||||
@ -12,17 +25,23 @@ export default (key, params = {}, warn = true, translate = true) => {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
value = key;
|
||||
return value;
|
||||
}
|
||||
|
||||
for (let k in params) {
|
||||
if (params.hasOwnProperty(k)) {
|
||||
value = Array.isArray(value)
|
||||
? value.map(v => v.replace(new RegExp('%' + k + '%', 'g'), params[k]))
|
||||
: value.replace(new RegExp('%' + k + '%', 'g'), params[k]);
|
||||
has(key) {
|
||||
return this.get(key, false) !== undefined;
|
||||
}
|
||||
|
||||
applyParams (value, params = {}) {
|
||||
for (let k in params) {
|
||||
if (params.hasOwnProperty(k)) {
|
||||
value = Array.isArray(value)
|
||||
? value.map(v => v.replace(new RegExp('%' + k + '%', 'g'), params[k]))
|
||||
: value.replace(new RegExp('%' + k + '%', 'g'), params[k]);
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
export default new Translator(translations, baseTranslations);
|
||||
|
@ -1,5 +1,5 @@
|
||||
import jwt from 'jsonwebtoken';
|
||||
import t from '../src/translator';
|
||||
import translator from '../src/translator';
|
||||
import {buildDict} from "../src/helpers";
|
||||
|
||||
export const state = () => ({
|
||||
@ -57,17 +57,20 @@ export const mutations = {
|
||||
},
|
||||
translationInit(state) {
|
||||
state.translationMode = true;
|
||||
state.translationChanges = {};
|
||||
},
|
||||
translationCommit(state) {
|
||||
alert('not implemented!')
|
||||
state.translationMode = false;
|
||||
state.translationChanges = {};
|
||||
},
|
||||
translationAbort(state) {
|
||||
state.translationMode = false;
|
||||
state.translationChanges = {};
|
||||
},
|
||||
translationPause(state) {
|
||||
state.translationMode = false;
|
||||
},
|
||||
translate(state, {key, newValue}) {
|
||||
if (newValue !== t(key)) {
|
||||
if (newValue !== translator.get(key)) {
|
||||
const translationChanges = {...state.translationChanges};
|
||||
translationChanges[key] = newValue;
|
||||
state.translationChanges = translationChanges;
|
||||
@ -82,4 +85,13 @@ export const mutations = {
|
||||
}, state.translationChanges);
|
||||
}
|
||||
},
|
||||
restoreTranslations(state, translations) {
|
||||
if (translations) {
|
||||
state.translationMode = true;
|
||||
state.translationChanges = translations;
|
||||
} else {
|
||||
state.translationMode = false;
|
||||
state.translationChanges = {};
|
||||
}
|
||||
},
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user