mirror of
https://gitlab.com/PronounsPage/PronounsPage.git
synced 2025-09-23 04:34:15 -04:00
221 lines
9.9 KiB
Vue
221 lines
9.9 KiB
Vue
<script setup lang="ts">
|
||
import { useNuxtApp, useFetch } from 'nuxt/app';
|
||
import Suml from 'suml';
|
||
|
||
import useDialogue from '~/composables/useDialogue.ts';
|
||
import useSimpleHead from '~/composables/useSimpleHead.ts';
|
||
import { deepSet } from '~/src/helpers.ts';
|
||
|
||
interface TranslationProposal {
|
||
id: string;
|
||
tKey: string;
|
||
tValue: string | string[];
|
||
author: string;
|
||
status?: 0 | 1;
|
||
}
|
||
|
||
interface Contributor {
|
||
username: string;
|
||
isMember: boolean;
|
||
count: number;
|
||
}
|
||
|
||
const { $translator: translator } = useNuxtApp();
|
||
const dialogue = useDialogue();
|
||
useSimpleHead({
|
||
title: `${translator.translate('admin.header')} • Translation proposals`,
|
||
}, translator);
|
||
|
||
const { data: translationProposals } = useFetch<TranslationProposal[]>('/api/translations/proposals', {
|
||
default: () => [],
|
||
});
|
||
const { data: contributors } = useFetch<Contributor[]>('/api/translations/contributors', {
|
||
default: () => [],
|
||
});
|
||
await Promise.all([translationProposals, contributors]);
|
||
|
||
const translationsProposalsSuml = computed((): string => {
|
||
const data = {};
|
||
for (const tp of translationProposals.value || []) {
|
||
if (tp.status === 1) {
|
||
deepSet(data, tp.tKey, tp.tValue);
|
||
}
|
||
}
|
||
return new Suml().dump(data);
|
||
});
|
||
|
||
const acceptTranslationProposal = async (id: string): Promise<void> => {
|
||
// await this.dialogue.confirm('Do you want to accept this translation proposal?', 'success');
|
||
await dialogue.postWithAlertOnError('/api/translations/accept-proposal', { id });
|
||
translationProposals.value = translationProposals.value.map((tp) => {
|
||
if (tp.id === id) {
|
||
tp.status = 1;
|
||
}
|
||
return tp;
|
||
});
|
||
};
|
||
|
||
const rejectTranslationProposal = async (id: string): Promise<void> => {
|
||
await dialogue.confirm('Do you want to reject this translation proposal?', 'danger');
|
||
await dialogue.postWithAlertOnError('/api/translations/reject-proposal', { id });
|
||
translationProposals.value = translationProposals.value.filter((tp) => tp.id !== id);
|
||
};
|
||
|
||
const unapproveTranslationProposal = async (id: string): Promise<void> => {
|
||
await dialogue.confirm('Do you want to unmark this translation proposal as approved?', 'danger');
|
||
await dialogue.postWithAlertOnError('/api/translations/unapprove-proposal', { id });
|
||
translationProposals.value = translationProposals.value.map((tp) => {
|
||
if (tp.id === id) {
|
||
tp.status = 0;
|
||
}
|
||
return tp;
|
||
});
|
||
};
|
||
const markTranslationProposalsDone = async (): Promise<void> => {
|
||
await dialogue.confirm(`This will mark all approved translations as done and they'll disappear from here –
|
||
but they'll only actually show up on production if they are added to the <code>translations.suml</code> file
|
||
and merged to the <code>main</code> branch.
|
||
Do you confirm that those translations are present in the <code>main</code> branch?`, 'success');
|
||
await dialogue.postWithAlertOnError('/api/translations/proposals-done');
|
||
translationProposals.value = translationProposals.value.filter((tp) => tp.status !== 1);
|
||
};
|
||
</script>
|
||
|
||
<template>
|
||
<Page wide>
|
||
<NotFound v-if="!$isGranted('translations') && !$isGranted('code')" />
|
||
<div v-else>
|
||
<p>
|
||
<nuxt-link to="/admin">
|
||
<Icon v="user-cog" />
|
||
<T>admin.header</T>
|
||
</nuxt-link>
|
||
</p>
|
||
<h2>
|
||
<Icon v="language" />
|
||
Translation proposals ({{ translationProposals.length }})
|
||
</h2>
|
||
|
||
<section v-if="translationProposals && translationProposals.length">
|
||
<div class="table-responsive">
|
||
<table class="table table-bordered">
|
||
<thead>
|
||
<tr>
|
||
<th>key</th>
|
||
<th>base</th>
|
||
<th>current translation</th>
|
||
<th>new translation</th>
|
||
<th>author & status</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr v-for="tp in translationProposals">
|
||
<td>{{ tp.tKey }}</td>
|
||
<td>{{ $translator.get(tp.tKey, false, true) }}</td>
|
||
<td>{{ $te(tp.tKey) ? $t(tp.tKey) : '' }}</td>
|
||
<td>{{ tp.tValue }}</td>
|
||
<td>
|
||
<nuxt-link :to="`/@${tp.author}`">@{{ tp.author }}</nuxt-link>
|
||
<br>
|
||
<template v-if="tp.status === 0">
|
||
<span class="badge bg-warning text-white">Awaiting approval</span>
|
||
<button
|
||
v-if="$isGranted('translations')"
|
||
class="btn btn-sm btn-outline-success"
|
||
@click="acceptTranslationProposal(tp.id)"
|
||
>
|
||
Accept
|
||
</button>
|
||
<button
|
||
v-if="$isGranted('translations')"
|
||
class="btn btn-sm btn-outline-danger"
|
||
@click="rejectTranslationProposal(tp.id)"
|
||
>
|
||
Reject
|
||
</button>
|
||
</template>
|
||
<template v-else>
|
||
<span class="badge bg-success text-white">Approved</span>
|
||
<button
|
||
v-if="$isGranted('translations')"
|
||
class="btn btn-sm btn-outline-danger"
|
||
@click="unapproveTranslationProposal(tp.id)"
|
||
>
|
||
Unapprove
|
||
</button>
|
||
</template>
|
||
</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<details class="border mb-3" :open="$isGranted('code')">
|
||
<summary class="bg-light p-3">
|
||
<span class="badge bg-success">Merge</span>
|
||
</summary>
|
||
<div class="p-2">
|
||
<p>
|
||
We still need to <strong>manually</strong> move the translations to the <code>translations.suml</code> file,
|
||
but at least it should be easy to copy paste bits from here:
|
||
</p>
|
||
<hr>
|
||
|
||
<pre dir="ltr">{{ translationsProposalsSuml }}</pre>
|
||
<hr>
|
||
<button v-if="$isGranted('code')" class="btn btn-success" @click="markTranslationProposalsDone">
|
||
Merged to the main branch, mark as done
|
||
</button>
|
||
<p v-else>
|
||
If you know Git and YAML/SUML, you can create a pull request for these changes.
|
||
You can also ask <nuxt-link to="/@andrea">@andrea</nuxt-link> for `code` permissions
|
||
– `code` access means you'll be getting notifications about new translations awaiting merging
|
||
and will be able to mark them as merged in the database.
|
||
</p>
|
||
</div>
|
||
</details>
|
||
</section>
|
||
|
||
<details v-if="contributors.length" class="border mb-3">
|
||
<summary class="bg-light p-3">
|
||
Contributors
|
||
</summary>
|
||
<div class="p-2">
|
||
<div class="table-responsive">
|
||
<table class="table table-bordered">
|
||
<thead>
|
||
<tr>
|
||
<th>Contributor</th>
|
||
<th>Approved translations</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr v-for="{ username, isMember, count } in contributors">
|
||
<td>
|
||
<nuxt-link :to="`/@${username}`">@{{ username }}</nuxt-link>
|
||
<span v-if="isMember" class="badge bg-primary text-white">
|
||
<Icon v="collective-logo.svg" class="inverted" />
|
||
<T>contact.team.member</T>
|
||
</span>
|
||
</td>
|
||
<td>
|
||
{{ count }}
|
||
</td>
|
||
</tr>
|
||
</tbody>
|
||
<tfoot>
|
||
<tr>
|
||
<td colspan="2" class="small">
|
||
This overview only considers translations submitted via the web interface, not gitlab/gdocs.
|
||
Let's use it to consider inviting people to the team.
|
||
</td>
|
||
</tr>
|
||
</tfoot>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</details>
|
||
</div>
|
||
</Page>
|
||
</template>
|