mirror of
https://github.com/elk-zone/elk
synced 2024-12-21 18:17:59 +09:00
339 lines
8.8 KiB
Vue
339 lines
8.8 KiB
Vue
|
<script setup lang="ts">
|
||
|
import type { TranslationStatus } from '../../types'
|
||
|
|
||
|
const localesStatuses: TranslationStatus = await import('../../translation-status.json').then(m => m.default)
|
||
|
|
||
|
const totalReference = localesStatuses.en.total
|
||
|
|
||
|
type Tab = 'missing' | 'outdated'
|
||
|
|
||
|
const hidden = ref(true)
|
||
|
const locale = ref()
|
||
|
const localeTab = ref<Tab>('missing')
|
||
|
const copied = ref(false)
|
||
|
|
||
|
const currentLocale = computed(() => {
|
||
|
if (hidden.value || !locale.value)
|
||
|
return undefined
|
||
|
|
||
|
return localesStatuses as Record<string, any>
|
||
|
})
|
||
|
|
||
|
const localeTitle = computed(() => {
|
||
|
if (hidden.value || !locale.value)
|
||
|
return undefined
|
||
|
|
||
|
return localeTab.value === 'missing'
|
||
|
? `Missing keys in ${locale.value.file}`
|
||
|
: `Outdated keys in ${locale.value.file}`
|
||
|
})
|
||
|
|
||
|
const missingEntries = computed<string[]>(() => {
|
||
|
if (hidden.value || !currentLocale.value || localeTab.value !== 'missing')
|
||
|
return []
|
||
|
|
||
|
return localesStatuses[locale.value].missing
|
||
|
})
|
||
|
|
||
|
const outdatedEntries = computed<string[]>(() => {
|
||
|
if (hidden.value || !currentLocale.value || localeTab.value !== 'outdated')
|
||
|
return []
|
||
|
|
||
|
return localesStatuses[locale.value]!.outdated
|
||
|
})
|
||
|
|
||
|
const showDetail = (key: string, tab: Tab = 'missing', fromTab = false) => {
|
||
|
if (key === locale.value && tab === localeTab.value) {
|
||
|
if (fromTab)
|
||
|
return
|
||
|
|
||
|
nextTick().then(() => hidden.value = !hidden.value)
|
||
|
|
||
|
return
|
||
|
}
|
||
|
|
||
|
locale.value = key
|
||
|
localeTab.value = tab
|
||
|
nextTick().then(() => hidden.value = false)
|
||
|
}
|
||
|
|
||
|
const copyToClipboard = async () => {
|
||
|
try {
|
||
|
await navigator.clipboard.writeText([
|
||
|
`# ${localeTitle.value}`,
|
||
|
(localeTab.value === 'missing' ? missingEntries.value : outdatedEntries.value).join('\n')].join('\n'),
|
||
|
)
|
||
|
copied.value = true
|
||
|
setTimeout(() => copied.value = false, 750)
|
||
|
}
|
||
|
catch {}
|
||
|
}
|
||
|
</script>
|
||
|
|
||
|
<template>
|
||
|
<div>
|
||
|
<table class="w-full">
|
||
|
<caption>
|
||
|
<div>You can see the detail (missing and outdated keys) by clicking on the corresponding row.</div>
|
||
|
<div>
|
||
|
If you want to send a PR, click on <strong>Edit</strong> link on the corresponding translation file, it will open <strong>Codeflow</strong>:
|
||
|
<NuxtLink
|
||
|
target="_blank"
|
||
|
href="https://developer.stackblitz.com/codeflow/working-in-codeflow-ide#making-a-pr-with-codeflow-ide"
|
||
|
title="How to make a PR with Codeflow IDE (opens in new window)"
|
||
|
>
|
||
|
read the following guide
|
||
|
</NuxtLink>
|
||
|
</div>
|
||
|
</caption>
|
||
|
<thead>
|
||
|
<tr>
|
||
|
<th>Language</th>
|
||
|
<th title="Keys correctly translated">
|
||
|
Translated
|
||
|
</th>
|
||
|
<th title="Keys missing from source which need translation for the language">
|
||
|
Missing
|
||
|
</th>
|
||
|
<th title="Keys which could be safely removed">
|
||
|
Outdated
|
||
|
</th>
|
||
|
<th>Total</th>
|
||
|
<th>Actions</th>
|
||
|
</tr>
|
||
|
</thead>
|
||
|
<tbody>
|
||
|
<template v-for="({ title, file, translated, missing, outdated, total, isSource }, key) in localesStatuses" :key="key">
|
||
|
<tr
|
||
|
v-if="totalReference > 0"
|
||
|
:class="[{ expandable: !isSource }]"
|
||
|
:title="!isSource ? 'Click to show detail' : undefined"
|
||
|
@click="!isSource && showDetail(key, 'missing')"
|
||
|
>
|
||
|
<td :class="[{ expandable: !isSource }]">
|
||
|
<div>
|
||
|
<ToogleIcon v-if="!isSource" :up="hidden || key !== locale" />
|
||
|
{{ title }}
|
||
|
</div>
|
||
|
</td>
|
||
|
<template v-if="isSource">
|
||
|
<td colspan="5" class="source-text">
|
||
|
<div>
|
||
|
{{ total }} keys as source
|
||
|
</div>
|
||
|
</td>
|
||
|
</template>
|
||
|
<template v-else>
|
||
|
<td>
|
||
|
<strong>{{ `${translated?.length ?? 0}` }}</strong> {{ `(${(100 * (translated?.length ?? 0) / totalReference).toFixed(1)}%)` }}
|
||
|
</td>
|
||
|
<td>
|
||
|
<strong>{{ `${missing?.length ?? 0}` }}</strong> {{ `(${(100 * (missing?.length ?? 0) / totalReference).toFixed(1)}%)` }}
|
||
|
</td>
|
||
|
<td>
|
||
|
<strong>{{ `${outdated?.length ?? 0}` }}</strong> {{ `(${(100 * (outdated?.length ?? 0) / totalReference).toFixed(1)}%)` }}
|
||
|
</td>
|
||
|
<td><strong>{{ `${total}` }}</strong></td>
|
||
|
<td>
|
||
|
<NuxtLink
|
||
|
v-if="outdated.length > 0 || missing.length > 0"
|
||
|
:href="`https://pr.new/github.com/elk-zone/elk/tree/main/locales/${file}`"
|
||
|
target="_blank"
|
||
|
class="codeflow"
|
||
|
title="Raise a PR with Codeflow (opens in new window)"
|
||
|
@click.stop
|
||
|
>
|
||
|
Edit
|
||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24">
|
||
|
<path fill="currentColor" d="M5 21q-.825 0-1.413-.587Q3 19.825 3 19V5q0-.825.587-1.413Q4.175 3 5 3h7v2H5v14h14v-7h2v7q0 .825-.587 1.413Q19.825 21 19 21Zm4.7-5.3l-1.4-1.4L17.6 5H14V3h7v7h-2V6.4Z" />
|
||
|
</svg>
|
||
|
</NuxtLink>
|
||
|
</td>
|
||
|
</template>
|
||
|
</tr>
|
||
|
<template v-if="key === locale && !hidden">
|
||
|
<tr>
|
||
|
<td colspan="6">
|
||
|
<div class="detail">
|
||
|
<header>
|
||
|
<h2 class="tabs">
|
||
|
<button
|
||
|
:class="localeTab === 'missing' ? 'current' : null"
|
||
|
@click="showDetail(key, 'missing', true)"
|
||
|
>
|
||
|
Missing keys
|
||
|
</button>
|
||
|
<button
|
||
|
:class="localeTab === 'outdated' ? 'current' : null"
|
||
|
@click="showDetail(key, 'outdated', true)"
|
||
|
>
|
||
|
Outdated keys
|
||
|
</button>
|
||
|
</h2>
|
||
|
</header>
|
||
|
<ul v-if="localeTab === 'missing'">
|
||
|
<li v-for="entry in missingEntries" :key="entry">
|
||
|
<pre>{{ entry }}</pre>
|
||
|
</li>
|
||
|
</ul>
|
||
|
<ul v-else>
|
||
|
<li v-for="entry in outdatedEntries" :key="entry">
|
||
|
<pre>{{ entry }}</pre>
|
||
|
</li>
|
||
|
</ul>
|
||
|
<button @click="copyToClipboard()">
|
||
|
<ClipboardIcon :copy="!copied" />
|
||
|
Copy to clipboard
|
||
|
</button>
|
||
|
</div>
|
||
|
</td>
|
||
|
</tr>
|
||
|
</template>
|
||
|
</template>
|
||
|
</tbody>
|
||
|
</table>
|
||
|
</div>
|
||
|
</template>
|
||
|
|
||
|
<style scoped>
|
||
|
table {
|
||
|
font-size: 0.9rem;
|
||
|
width: 100%;
|
||
|
border-collapse: collapse;
|
||
|
border: 1px solid #ccc;
|
||
|
}
|
||
|
|
||
|
pre {
|
||
|
font-size: 0.75rem;
|
||
|
}
|
||
|
|
||
|
caption {
|
||
|
padding: 0.3rem;
|
||
|
background: #eee;
|
||
|
border: 1px solid #ccc;
|
||
|
border-top-left-radius: 3px;
|
||
|
border-top-right-radius: 3px;
|
||
|
border-bottom: none;
|
||
|
}
|
||
|
caption a {
|
||
|
text-decoration: underline;
|
||
|
}
|
||
|
|
||
|
th {
|
||
|
text-align: left;
|
||
|
border-bottom: 1px solid #ccc;
|
||
|
padding: 0.5rem;
|
||
|
}
|
||
|
th:not(:first-of-type),
|
||
|
td:not(:first-of-type) {
|
||
|
border-left: 1px solid #eee;
|
||
|
}
|
||
|
td {
|
||
|
padding: 0.5rem;
|
||
|
}
|
||
|
tr.expandable td:first-of-type {
|
||
|
padding-left: 4px;
|
||
|
}
|
||
|
tr.expandable, tr.expandable td {
|
||
|
cursor: pointer;
|
||
|
}
|
||
|
|
||
|
a.codeflow,
|
||
|
td.expandable div {
|
||
|
display: flex;
|
||
|
align-items: center;
|
||
|
flex-direction: row;
|
||
|
column-gap: 4px;
|
||
|
}
|
||
|
td.expandable > svg {
|
||
|
color: currentColor;
|
||
|
}
|
||
|
|
||
|
tbody tr td {
|
||
|
border-bottom: 1px solid #eee;
|
||
|
}
|
||
|
|
||
|
th[title] {
|
||
|
text-decoration: underline dotted white;
|
||
|
}
|
||
|
|
||
|
.source-text {
|
||
|
text-align: center;
|
||
|
font-weight: bold;
|
||
|
padding: 10px 0;
|
||
|
text-transform: uppercase;
|
||
|
}
|
||
|
|
||
|
.detail {
|
||
|
border: 1px solid #ccc;
|
||
|
border-radius: 3px;
|
||
|
}
|
||
|
.detail header {
|
||
|
padding: 0 0.3rem;
|
||
|
display: flex;
|
||
|
background: #eee;
|
||
|
justify-content: space-between;
|
||
|
align-items: center;
|
||
|
}
|
||
|
|
||
|
.detail header h2 button {
|
||
|
font-weight: bold;
|
||
|
padding: 0.5rem;
|
||
|
background-color: #eee;
|
||
|
}
|
||
|
|
||
|
.detail header .tabs button.current {
|
||
|
background-color: white;
|
||
|
}
|
||
|
|
||
|
.detail header .tabs + .heading-buttons {
|
||
|
display: flex;
|
||
|
flex-direction: row;
|
||
|
column-gap: 0.4rem;
|
||
|
align-items: center;
|
||
|
}
|
||
|
|
||
|
.detail ul {
|
||
|
padding: 0.3rem 0.5rem;
|
||
|
max-height: 250px;
|
||
|
min-height: 250px;
|
||
|
overflow-y: auto;
|
||
|
border-bottom: 1px solid #eee;
|
||
|
}
|
||
|
|
||
|
.detail > button {
|
||
|
display: flex;
|
||
|
/*justify-content: space-between;*/
|
||
|
align-items: center;
|
||
|
column-gap: 0.3rem;
|
||
|
padding: 0.3rem 0.5rem;
|
||
|
background: transparent;
|
||
|
cursor: pointer;
|
||
|
border: 1px solid #ccc;
|
||
|
border-radius: 3px;
|
||
|
margin: 0.5rem;
|
||
|
}
|
||
|
|
||
|
@media (prefers-color-scheme: dark) {
|
||
|
.detail header {
|
||
|
background: #333;
|
||
|
color: #fff;
|
||
|
}
|
||
|
|
||
|
.detail header h2 button {
|
||
|
background-color: #333;
|
||
|
color: #fff;
|
||
|
}
|
||
|
|
||
|
.detail header .tabs button.current {
|
||
|
background-color: white;
|
||
|
color: #333;
|
||
|
}
|
||
|
|
||
|
table caption {
|
||
|
background: #333;
|
||
|
color: #fff;
|
||
|
}
|
||
|
}
|
||
|
</style>
|