mirror of
https://gitlab.com/PronounsPage/PronounsPage.git
synced 2025-09-24 05:05:20 -04:00
TranslationMode - admin
This commit is contained in:
parent
852f0f812e
commit
9fd67a5bb0
@ -4,7 +4,7 @@
|
||||
<Header/>
|
||||
<main class="container">
|
||||
<Nuxt/>
|
||||
<!--<TranslationMode/>-->
|
||||
<TranslationMode/>
|
||||
<ScrollButton/>
|
||||
</main>
|
||||
</div>
|
||||
|
@ -120,7 +120,7 @@ sources:
|
||||
key: 'Key'
|
||||
keyInfo: 'Identifier for linking sources between language versions and linking with the dictionary'
|
||||
images: 'Images'
|
||||
spoiler: 'Spoiler' # TODO
|
||||
spoiler: 'Spoiler'
|
||||
otherVersions: 'In other languages'
|
||||
referenced: 'Examples of use'
|
||||
|
||||
@ -435,7 +435,7 @@ contact:
|
||||
language: >
|
||||
We're an international team – people who created a specific language version
|
||||
might not be the same people who will read your message.
|
||||
So, if possible, we'd appreciate, if you contacted us in English.
|
||||
So, if possible, we'd appreciate, if you contacted us in <strong>English or Polish</strong>.
|
||||
team:
|
||||
name: 'The “Neutral Language Council” collective'
|
||||
nameShort: 'Collective'
|
||||
@ -575,7 +575,7 @@ profile:
|
||||
flagsInfo: 'Drag & drop your pride flags into this frame.'
|
||||
flagsCustom: 'Upload custom flags'
|
||||
flagsCustomWarning: 'This flag has been uploaded by a user. The team of pronouns.page is not responsible for it.'
|
||||
flagsAsterisk: 'This is not a queer identity, but we include it for people who are queer in other ways (eg. straight trans people).' # TODO
|
||||
flagsAsterisk: 'This is not a queer identity, but we include it for people who are queer in other ways (eg. straight trans people).'
|
||||
links: 'Links'
|
||||
linksRecommended: 'We recommend linking to'
|
||||
verifiedLinks:
|
||||
|
@ -402,7 +402,6 @@ support:
|
||||
description: >
|
||||
Wenn du dich an den Kosten für Server, Domains, Aufkleber etc. beteiligen oder den Autor*innen einfach ein Bier ausgeben willst,
|
||||
kannst du den untenstehenden Link nutzen:
|
||||
bankAccount: 'Bank transfer' # TODO
|
||||
|
||||
user:
|
||||
header: 'Account'
|
||||
@ -579,8 +578,6 @@ confirm:
|
||||
yes: 'Ja, ich bin sicher'
|
||||
no: 'Nein, abbrechen'
|
||||
ok: 'OK'
|
||||
save: 'Save' # TODO
|
||||
dismiss: 'Dismiss' # TODO
|
||||
|
||||
terms:
|
||||
header: 'Nutzungsbedingungen'
|
||||
@ -1001,8 +998,6 @@ calendar:
|
||||
trans_flag_day: 'Trans Flaggen Tag'
|
||||
alan_turing_day: 'Alan Turing Tag'
|
||||
nonbinary_kids_day: 'Tag nicht-binärer Kinder'
|
||||
masc_lesbian_week: 'Masculine Lesbian Visibility and Awareness Week' # TODO
|
||||
masc_lesbian_day: 'Masculine Lesbian Visibility and Awareness Day' # TODO
|
||||
banner: 'Heute feiern wir'
|
||||
celebrating_custom: 'wird gefeiert'
|
||||
celebrating_day: 'wird gefeiert am:'
|
||||
|
@ -491,7 +491,7 @@ contact:
|
||||
language: >
|
||||
We're an international team – people who created a specific language version
|
||||
might not be the same people who will read your message.
|
||||
So, if possible, we'd appreciate, if you contacted us in English.
|
||||
So, if possible, we'd appreciate, if you contacted us in <strong>English or Polish</strong>.
|
||||
team:
|
||||
name: 'The “Neutral Language Council” collective'
|
||||
nameShort: 'Collective'
|
||||
|
19
locale/expectedTranslations.js
Normal file
19
locale/expectedTranslations.js
Normal file
@ -0,0 +1,19 @@
|
||||
export default [
|
||||
'contact.language',
|
||||
'support.bankAccount',
|
||||
'confirm.save',
|
||||
'confirm.dismiss',
|
||||
'calendar.events.mspec_lesbian_day',
|
||||
'calendar.events.mspec_lesbian_week',
|
||||
'calendar.events.mspec_gay_day',
|
||||
'calendar.events.mspec_gay_week',
|
||||
'calendar.events.masc_lesbian_day',
|
||||
'calendar.events.masc_lesbian_week',
|
||||
'user.login.domainPlaceholder',
|
||||
'user.login.deprecated',
|
||||
'user.login.depreciationNotice',
|
||||
'profile.flagsAsterisk',
|
||||
'profile.verifiedLinks.header',
|
||||
'profile.verifiedLinks.info',
|
||||
'sources.submit.spoiler',
|
||||
];
|
@ -409,7 +409,6 @@ support:
|
||||
description: >
|
||||
Si vous voulez donnez un coup de main pour le serveur, le domaine, les stickers, etc. ou simplement payer une bière à l'auteur,
|
||||
vous pouvez utiliser les liens ci-dessous :
|
||||
bankAccount: 'Bank transfer' # TODO
|
||||
|
||||
user:
|
||||
header: 'Compte'
|
||||
@ -586,8 +585,6 @@ confirm:
|
||||
yes: 'Oui, je suis sûr·e'
|
||||
no: 'Non, annuler'
|
||||
ok: 'OK'
|
||||
save: 'Save' # TODO
|
||||
dismiss: 'Dismiss' # TODO
|
||||
|
||||
terms:
|
||||
header: 'Conditions d''utilisation'
|
||||
@ -1007,10 +1004,6 @@ calendar:
|
||||
black_hiv_awareness_day: 'Journée de sensibilisation au VIH/sida des communautés noires'
|
||||
hiv_aging_awareness_day: 'Journée de la sensibilisation du VIH/sida et au vieillissement'
|
||||
trans_hiv_testing_day: 'Journée de dépistage au VIH des transgenres'
|
||||
mspec_lesbian_day: 'Mspec Lesbian Visbility & Awareness Day' # TODO
|
||||
mspec_lesbian_week: 'Mspec Lesbian Visbility & Awareness Week' # TODO
|
||||
mspec_gay_day: 'Mspec Gay Visbility & Awareness Day' # TODO
|
||||
mspec_gay_week: 'Mspec Gay Visbility & Awareness Week' # TODO
|
||||
spring_testing_week: 'Semaine Européenne printanière des tests anti-VIH'
|
||||
autumn_testing_week: 'Semaine Européenne automnale des tests anti-VIH'
|
||||
sex_worker_day: 'Journée internationale des travailleurs du sexe'
|
||||
@ -1024,8 +1017,6 @@ calendar:
|
||||
trans_flag_day: 'Jour du drapeau trans'
|
||||
alan_turing_day: 'Journée Alan Turing'
|
||||
nonbinary_kids_day: 'Journée des enfants non-binaires'
|
||||
masc_lesbian_week: 'Masculine Lesbian Visibility and Awareness Week' # TODO
|
||||
masc_lesbian_day: 'Masculine Lesbian Visibility and Awareness Day' # TODO
|
||||
banner: 'Nous célébrons '
|
||||
celebrating_custom: 'est célébrée'
|
||||
celebrating_day: 'est fêté le'
|
||||
|
@ -119,7 +119,6 @@ sources:
|
||||
key: 'Key'
|
||||
keyInfo: 'Identifier for linking sources between language versions and linking with the dictionary'
|
||||
images: 'Images'
|
||||
spoiler: 'Spoiler' # TODO
|
||||
otherVersions: 'In other languages'
|
||||
referenced: 'Examples of use'
|
||||
|
||||
@ -479,7 +478,6 @@ support:
|
||||
description: >
|
||||
If you want to chip in for the server, domains, stickers etc., or simply buy the authors a beer,
|
||||
you can use the links below:
|
||||
bankAccount: 'Bank transfer' # TODO
|
||||
|
||||
user:
|
||||
header: 'Account'
|
||||
|
@ -118,7 +118,6 @@ sources:
|
||||
key: '키'
|
||||
keyInfo: '언어 버전 간 소스 연결 및 사전 연결 식별자'
|
||||
images: '이미지'
|
||||
spoiler: 'Spoiler' # TODO
|
||||
otherVersions: '다른 언어'
|
||||
referenced: '사용 예'
|
||||
|
||||
|
@ -381,7 +381,6 @@ support:
|
||||
description: >
|
||||
Si keres donar para el servidor, los dominios, stikers ets., o simplemente merkarles una bira a les autorxs,
|
||||
puedes uzar los sigientes atadijos:
|
||||
bankAccount: 'Bank transfer' # TODO
|
||||
|
||||
user:
|
||||
header: 'Kuento'
|
||||
@ -560,8 +559,6 @@ confirm:
|
||||
yes: 'Si, lo esto'
|
||||
no: 'No, anular'
|
||||
ok: 'OK'
|
||||
save: 'Save' # TODO
|
||||
dismiss: 'Dismiss' # TODO
|
||||
|
||||
terms:
|
||||
header: 'Terminos de Servisio'
|
||||
|
@ -405,7 +405,6 @@ support:
|
||||
description: >
|
||||
Als je wilt bijdragen aan de server, domeinen, stickers, andere materialen, of als je simpelweg een biertje voor de auteurs wil kopen,
|
||||
kun je de onderstaande links gebruiken:
|
||||
bankAccount: 'Bank transfer' # TODO
|
||||
|
||||
user:
|
||||
header: 'Account'
|
||||
@ -581,8 +580,6 @@ confirm:
|
||||
yes: 'Ja, dat weet ik zeker'
|
||||
no: 'Nee, annuleer'
|
||||
ok: 'OK'
|
||||
save: 'Save' # TODO
|
||||
dismiss: 'Dismiss' # TODO
|
||||
|
||||
terms:
|
||||
header: 'Gebruiksvoorwaarden'
|
||||
|
@ -381,7 +381,6 @@ support:
|
||||
description: >
|
||||
Hvis du har lyst til å bidra til serveren, domener, klistremerker osv., eller bare kjøpe forfatterne en kaffe,
|
||||
kan du bruke linkene nedenfor:
|
||||
bankAccount: 'Bank transfer' # TODO
|
||||
|
||||
user:
|
||||
header: 'Bruker'
|
||||
@ -402,12 +401,6 @@ user:
|
||||
passwordless: 'Denne nettsiden lagrer ingen passord. {https://avris.it/blog/passwords-are-passé=More info.}'
|
||||
instancePlaceholder: 'Forekomst'
|
||||
domainPlaceholder: 'Domene'
|
||||
# TODO
|
||||
deprecated: 'Deprecated'
|
||||
depreciationNotice: >
|
||||
This is not a reliable authentication provider.
|
||||
<a href="%link%" target="_blank" rel="noopener">Check out this link for more details</a>.
|
||||
We highly recommend making sure that you have a different login method available.
|
||||
code:
|
||||
action: 'Gyldig'
|
||||
invalid: 'Ugyldig kode.'
|
||||
@ -470,7 +463,6 @@ profile:
|
||||
flagsInfo: 'Dra og dropp pride flaggene dine inn i denne rammen.'
|
||||
flagsCustom: 'Last opp tilpasset flagg'
|
||||
flagsCustomWarning: 'Dette flagget har blitt lastet opp av en bruker. Teamet som har laget pronouns.page er ikke ansvarlig for det.'
|
||||
flagsAsterisk: 'This is not a queer identity, but we include it for people who are queer in other ways (eg. straight trans people).' # TODO
|
||||
links: 'Lenker'
|
||||
linksRecommended: 'Vi anbefaler å lenke til'
|
||||
# TODO
|
||||
@ -561,8 +553,6 @@ confirm:
|
||||
yes: 'Ja, jeg er sikker'
|
||||
no: 'Nei, kanseller'
|
||||
ok: 'OK'
|
||||
save: 'Save' # TODO
|
||||
dismiss: 'Dismiss' # TODO
|
||||
|
||||
terms:
|
||||
header: 'Vilkår og betingelser'
|
||||
|
@ -534,7 +534,7 @@ links:
|
||||
Takie słowa padły w trakcie jednego z wywiadów przeprowadzanych podczas badania na rzecz tej pracy.
|
||||
Zacytowany wyżej fragment przedstawia dokładnie jej cel – uświadomienie o potrzebie
|
||||
budowania bezpiecznej przestrzeni do istnienia dla osób niebinarnych.
|
||||
Drugi fragment cytatu ujawnia kolejną ważną prośbę o niepatrzenie na inne osoby przez pryzmat płc
|
||||
Drugi fragment cytatu ujawnia kolejną ważną prośbę o niepatrzenie na inne osoby przez pryzmat płci
|
||||
i niedzielenie społeczeństwa na męską i żeńską część.
|
||||
links:
|
||||
-
|
||||
|
@ -1148,7 +1148,7 @@ contact:
|
||||
language: >
|
||||
Jesteśmy międzynarodowym zespołem – osoby, które stworzyły daną wersję językową,
|
||||
niekoniecznie będą tymi samymi osobami, które zobaczą Twoją wiadomość.
|
||||
W miarę możliwości prosimy o kontakt w języku polskim lub angielskim.
|
||||
W miarę możliwości prosimy o kontakt w języku <strong>polskim lub angielskim.</strong>
|
||||
team:
|
||||
name: 'Kolektyw „Rada Języka Neutralnego”'
|
||||
nameShort: 'Kolektyw'
|
||||
|
@ -408,7 +408,6 @@ support:
|
||||
description: >
|
||||
Se você quiser de doar para o servidor, os domínios, stickers etc., ou simplemente comprar uma cerveja para ês autores,
|
||||
podem usar as ligações seguintes:
|
||||
bankAccount: 'Bank transfer' # TODO
|
||||
|
||||
user:
|
||||
header: 'Conta'
|
||||
@ -585,8 +584,6 @@ confirm:
|
||||
yes: 'Sim'
|
||||
no: 'Não, cancelar'
|
||||
ok: 'OK'
|
||||
save: 'Save' # TODO
|
||||
dismiss: 'Dismiss' # TODO
|
||||
|
||||
terms:
|
||||
header: 'Termos de Serviço'
|
||||
|
113
routes/admin.vue
113
routes/admin.vue
@ -127,6 +127,90 @@
|
||||
Impersonate <button class="btn btn-primary btn-sm" @click="impersonate('example@pronouns.page')">@example</button>
|
||||
</section>
|
||||
|
||||
<section v-if="$isGranted('translations') && missingTranslations.length">
|
||||
<h3>
|
||||
<Icon v="language"/>
|
||||
Missing translations ({{missingTranslations.length}})
|
||||
</h3>
|
||||
<p>
|
||||
In order to start translating, enable translation mode with the button in bottom right corner.
|
||||
Then you can propose translations both her as well as in context anywhere on the site.
|
||||
</p>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>key</th>
|
||||
<th>base</th>
|
||||
<th>translation</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="mt in missingTranslations">
|
||||
<td>{{mt}}</td>
|
||||
<td>{{translator.get(mt, false, true)}}</td>
|
||||
<td><T>{{mt}}</T></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section v-if="$isGranted('translations') && translationProposals.length">
|
||||
<h3>
|
||||
<Icon v="language"/>
|
||||
Translation proposals ({{translationProposals.length}})
|
||||
</h3>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>key</th>
|
||||
<th>base</th>
|
||||
<th>translation</th>
|
||||
<th>author</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="tp in translationProposals">
|
||||
<td>{{tp.tKey}}</td>
|
||||
<td>{{translator.get(tp.tKey, false, true)}}</td>
|
||||
<td v-if="Array.isArray(tp.tValue)">
|
||||
<ul>
|
||||
<li v-for="el in tp.tValue">{{el}}</li>
|
||||
</ul>
|
||||
</td>
|
||||
<td v-else>
|
||||
{{tp.tValue}}
|
||||
</td>
|
||||
<td>
|
||||
<nuxt-link :to="`/@${tp.author}`">@{{tp.author}}</nuxt-link>
|
||||
<br/>
|
||||
<button class="btn btn-sm btn-danger" @click="rejectTranslationProposal(tp.id)">Reject</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<details class="border mb-3">
|
||||
<summary class="bg-light p-3">
|
||||
<span class="badge bg-success">Approve</span>
|
||||
</summary>
|
||||
<div class="p-2">
|
||||
<p>
|
||||
We still need to manually move the translations to the relevant SUML file,
|
||||
but at least it should be easy to copy paste bits from here:
|
||||
</p>
|
||||
<hr/>
|
||||
<pre>{{translationsProposalsSuml}}</pre>
|
||||
<hr/>
|
||||
<button class="btn btn-success" @click="markTranslationProposalsDone">Copied, mark as done</button>
|
||||
</div>
|
||||
</details>
|
||||
</section>
|
||||
|
||||
<section v-if="$isGranted('users')">
|
||||
<h3>
|
||||
<Icon v="siren-on"/>
|
||||
@ -189,8 +273,10 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {head} from "../src/helpers";
|
||||
import {deepSet, head} from "../src/helpers";
|
||||
import {socialProviders} from "../src/socialProviders";
|
||||
import translator from '../src/translator';
|
||||
import Suml from 'suml';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
@ -203,6 +289,8 @@
|
||||
adminsFilter: false,
|
||||
usersShown: false,
|
||||
adminNotifications: this.$user().adminNotifications ?? 7,
|
||||
translator,
|
||||
missingTranslations: translator.listMissingTranslations(),
|
||||
}
|
||||
},
|
||||
async asyncData({ app, store }) {
|
||||
@ -216,9 +304,15 @@
|
||||
abuseReports = await app.$axios.$get(`/admin/reports`);
|
||||
} catch {}
|
||||
|
||||
let translationProposals = [];
|
||||
try {
|
||||
translationProposals = await app.$axios.$get(`/translations/proposals`);
|
||||
} catch {}
|
||||
|
||||
return {
|
||||
stats,
|
||||
abuseReports,
|
||||
translationProposals
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
@ -229,6 +323,16 @@
|
||||
await this.$router.push('/' + this.config.user.route);
|
||||
setTimeout(() => window.location.reload(), 500);
|
||||
},
|
||||
async rejectTranslationProposal(id) {
|
||||
await this.$confirm('Do you want to reject this translation proposal?', 'danger');
|
||||
await this.$post(`/translations/reject-proposal`, {id})
|
||||
this.translationProposals = this.translationProposals.filter(tp => tp.id !== id);
|
||||
},
|
||||
async markTranslationProposalsDone() {
|
||||
await this.$confirm('Did you put the translations in the SUML file and want to mark them as done?', 'success');
|
||||
await this.$post(`/translations/proposals-done`)
|
||||
this.translationProposals = [];
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
profilesByLocale() {
|
||||
@ -241,6 +345,13 @@
|
||||
abuseReportsActiveCount() {
|
||||
return this.abuseReports.filter(r => !r.isHandled).length;
|
||||
},
|
||||
translationsProposalsSuml() {
|
||||
const data = {};
|
||||
for (let tp of this.translationProposals) {
|
||||
deepSet(data, tp.tKey, tp.tValue);
|
||||
}
|
||||
return new Suml().dump(data);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
userFilter() {
|
||||
|
@ -2,6 +2,7 @@ import { Router } from 'express';
|
||||
import SQL from 'sql-template-strings';
|
||||
import {ulid} from "ulid";
|
||||
import { handleErrorAsync } from "../../src/helpers";
|
||||
import mailer from "../../src/mailer";
|
||||
|
||||
const router = Router();
|
||||
|
||||
@ -20,7 +21,49 @@ router.post('/translations/propose', handleErrorAsync(async (req, res) => {
|
||||
)`)
|
||||
}
|
||||
|
||||
// TODO email
|
||||
for (let email of ['contact@pronouns.page']) {
|
||||
mailer(email, 'translationProposed', {locale: global.config.locale});
|
||||
}
|
||||
|
||||
return res.json('OK');
|
||||
}));
|
||||
|
||||
router.get('/translations/proposals', handleErrorAsync(async (req, res) => {
|
||||
if (!req.isGranted('translations')) {
|
||||
return res.status(401).json({error: 'Unauthorised'});
|
||||
}
|
||||
|
||||
return res.json(
|
||||
(await req.db.all(SQL`
|
||||
SELECT t.id, t.tKey, t.tValue, u.username AS author
|
||||
FROM translations t
|
||||
LEFT JOIN users u ON t.author_id = u.id
|
||||
WHERE locale = ${global.config.locale} AND status = 0
|
||||
`)).map(tp => {
|
||||
return {
|
||||
...tp,
|
||||
tValue: JSON.parse(tp.tValue),
|
||||
}
|
||||
})
|
||||
);
|
||||
}));
|
||||
|
||||
router.post('/translations/reject-proposal', handleErrorAsync(async (req, res) => {
|
||||
if (!req.isGranted('translations')) {
|
||||
return res.status(401).json({error: 'Unauthorised'});
|
||||
}
|
||||
|
||||
await req.db.get(SQL`UPDATE translations SET status = -1 WHERE id = ${req.body.id}`)
|
||||
|
||||
return res.json('OK');
|
||||
}));
|
||||
|
||||
router.post('/translations/proposals-done', handleErrorAsync(async (req, res) => {
|
||||
if (!req.isGranted('translations')) {
|
||||
return res.status(401).json({error: 'Unauthorised'});
|
||||
}
|
||||
|
||||
await req.db.get(SQL`UPDATE translations SET status = 1 WHERE locale = ${global.config.locale} AND status = 0`)
|
||||
|
||||
return res.json('OK');
|
||||
}));
|
||||
|
@ -272,3 +272,15 @@ export const obfuscateEmail = (email) => {
|
||||
|
||||
return `${usernamePublic}*****@*****.${tld}`;
|
||||
}
|
||||
|
||||
// https://newbedev.com/dynamic-deep-setting-for-a-javascript-object
|
||||
export const deepSet = (obj, path, value) => {
|
||||
let a = path.split('.')
|
||||
let o = obj;
|
||||
while (a.length - 1) {
|
||||
let n = a.shift()
|
||||
if (!(n in o)) o[n] = {}
|
||||
o = o[n]
|
||||
}
|
||||
o[a[0]] = value
|
||||
}
|
||||
|
@ -92,7 +92,12 @@ const templates = {
|
||||
subject: 'Cards queue is getting long',
|
||||
text: 'There\'s {{count}} cards in the queue!',
|
||||
html: '<p>There\'s {{count}} cards in the queue!</p>',
|
||||
}
|
||||
},
|
||||
translationProposed: {
|
||||
subject: '[{{locale}}] New translations proposed',
|
||||
text: 'Check them out here: https://[[domain]]/admin',
|
||||
html: '<p>Check them out here: <a href="https://[[domain]]/admin" target="_blank" rel="noopener">[[domain]]/admin</a></p>',
|
||||
},
|
||||
}
|
||||
|
||||
const applyTemplate = (template, context, params) => {
|
||||
|
@ -1,10 +1,12 @@
|
||||
import translations from '../data/translations.suml';
|
||||
import baseTranslations from '../locale/_base/translations.suml';
|
||||
import expectedTranslations from '../locale/expectedTranslations';
|
||||
|
||||
class Translator {
|
||||
constructor(translations, baseTranslations) {
|
||||
constructor(translations, baseTranslations, expectedTranslations) {
|
||||
this.translations = translations;
|
||||
this.baseTranslations = baseTranslations;
|
||||
this.expectedTranslations = expectedTranslations;
|
||||
}
|
||||
|
||||
translate(key, params = {}, warn = true) {
|
||||
@ -45,6 +47,10 @@ class Translator {
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
listMissingTranslations() {
|
||||
return this.expectedTranslations.filter(k => !this.has(k));
|
||||
}
|
||||
}
|
||||
|
||||
export default new Translator(translations, baseTranslations);
|
||||
export default new Translator(translations, baseTranslations, expectedTranslations);
|
||||
|
Loading…
x
Reference in New Issue
Block a user