feat: add vue-i18n infrastructure and externalize all UI strings (A11Y-03)

Set up vue-i18n with English locale file containing 500+ keys organized
by view namespace. All 15 views converted to use t() calls instead of
hardcoded strings. Infrastructure ready for community translations.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-11 13:45:59 +00:00
co-authored by Claude Opus 4.6
parent b9cc0a924e
commit aba7aba25f
21 changed files with 2491 additions and 640 deletions
+32 -29
View File
@@ -3,15 +3,15 @@
<div class="hidden md:block mb-8">
<div class="flex items-center justify-between">
<div>
<h1 class="text-3xl font-bold text-white mb-2">Monitoring</h1>
<p class="text-white/70">Real-time system metrics and container resource usage</p>
<h1 class="text-3xl font-bold text-white mb-2">{{ t('monitoring.title') }}</h1>
<p class="text-white/70">{{ t('monitoring.subtitle') }}</p>
</div>
<div class="flex gap-2">
<button class="glass-button text-sm px-4 py-2" @click="exportMetrics('csv')">
Export CSV
{{ t('monitoring.exportCsv') }}
</button>
<button class="glass-button text-sm px-4 py-2" @click="exportMetrics('json')">
Export JSON
{{ t('monitoring.exportJson') }}
</button>
</div>
</div>
@@ -20,22 +20,22 @@
<!-- Summary Cards -->
<div class="grid grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
<div class="monitoring-stat-card">
<p class="text-xs text-white/50 uppercase tracking-wide">CPU</p>
<p class="text-xs text-white/50 uppercase tracking-wide">{{ t('monitoring.cpu') }}</p>
<p class="text-2xl font-bold text-white">{{ current?.system.cpu_percent.toFixed(1) ?? '--' }}%</p>
<p class="text-xs text-white/40">Load: {{ current?.system.load_avg_1.toFixed(2) ?? '--' }}</p>
<p class="text-xs text-white/40">{{ t('monitoring.load') }} {{ current?.system.load_avg_1.toFixed(2) ?? '--' }}</p>
</div>
<div class="monitoring-stat-card">
<p class="text-xs text-white/50 uppercase tracking-wide">Memory</p>
<p class="text-xs text-white/50 uppercase tracking-wide">{{ t('monitoring.memory') }}</p>
<p class="text-2xl font-bold text-white">{{ memPercent }}%</p>
<p class="text-xs text-white/40">{{ formatBytes(current?.system.mem_used_bytes ?? 0) }} / {{ formatBytes(current?.system.mem_total_bytes ?? 0) }}</p>
</div>
<div class="monitoring-stat-card">
<p class="text-xs text-white/50 uppercase tracking-wide">Disk</p>
<p class="text-xs text-white/50 uppercase tracking-wide">{{ t('monitoring.diskUsage') }}</p>
<p class="text-2xl font-bold text-white">{{ diskPercent }}%</p>
<p class="text-xs text-white/40">{{ formatBytes(current?.system.disk_used_bytes ?? 0) }} / {{ formatBytes(current?.system.disk_total_bytes ?? 0) }}</p>
</div>
<div class="monitoring-stat-card">
<p class="text-xs text-white/50 uppercase tracking-wide">Network</p>
<p class="text-xs text-white/50 uppercase tracking-wide">{{ t('monitoring.network') }}</p>
<p class="text-2xl font-bold text-white">{{ formatBytes(current?.system.net_rx_bytes ?? 0) }}</p>
<p class="text-xs text-white/40">TX: {{ formatBytes(current?.system.net_tx_bytes ?? 0) }}</p>
</div>
@@ -44,7 +44,7 @@
<!-- Charts -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
<div class="glass-card p-5">
<h3 class="text-sm font-medium text-white/80 mb-3">CPU Usage (%)</h3>
<h3 class="text-sm font-medium text-white/80 mb-3">{{ t('monitoring.cpuUsage') }}</h3>
<LineChart
:datasets="cpuDatasets"
:labels="timeLabels"
@@ -54,7 +54,7 @@
/>
</div>
<div class="glass-card p-5">
<h3 class="text-sm font-medium text-white/80 mb-3">Memory Usage (%)</h3>
<h3 class="text-sm font-medium text-white/80 mb-3">{{ t('monitoring.memoryUsage') }}</h3>
<LineChart
:datasets="memDatasets"
:labels="timeLabels"
@@ -64,7 +64,7 @@
/>
</div>
<div class="glass-card p-5">
<h3 class="text-sm font-medium text-white/80 mb-3">Network I/O (bytes)</h3>
<h3 class="text-sm font-medium text-white/80 mb-3">{{ t('monitoring.networkIo') }}</h3>
<LineChart
:datasets="netDatasets"
:labels="timeLabels"
@@ -73,7 +73,7 @@
/>
</div>
<div class="glass-card p-5">
<h3 class="text-sm font-medium text-white/80 mb-3">RPC Latency (ms)</h3>
<h3 class="text-sm font-medium text-white/80 mb-3">{{ t('monitoring.rpcLatency') }}</h3>
<LineChart
:datasets="latencyDatasets"
:labels="timeLabels"
@@ -86,12 +86,12 @@
<!-- Alert History -->
<div class="glass-card p-5 mb-6">
<div class="flex items-center justify-between mb-4">
<h3 class="text-sm font-medium text-white/80">Alert History</h3>
<h3 class="text-sm font-medium text-white/80">{{ t('monitoring.alertHistory') }}</h3>
<button
class="glass-button text-xs px-3 py-1"
@click="showAlertConfig = !showAlertConfig"
>
{{ showAlertConfig ? 'Hide Config' : 'Configure' }}
{{ showAlertConfig ? t('monitoring.hideConfig') : t('common.configure') }}
</button>
</div>
@@ -128,7 +128,7 @@
<!-- Fired Alerts List -->
<div v-if="!alerts.length" class="text-white/40 text-sm py-4 text-center">
No alerts fired
{{ t('monitoring.noAlerts') }}
</div>
<div v-else class="space-y-2 max-h-64 overflow-y-auto">
<div
@@ -150,7 +150,7 @@
class="text-xs text-white/40 hover:text-white/70 flex-shrink-0"
@click="acknowledgeAlert(alert.id)"
>
Dismiss
{{ t('common.dismiss') }}
</button>
</div>
</div>
@@ -158,9 +158,9 @@
<!-- Container Resource Breakdown -->
<div class="glass-card p-5 mb-6">
<h3 class="text-sm font-medium text-white/80 mb-4">Container Resources</h3>
<h3 class="text-sm font-medium text-white/80 mb-4">{{ t('monitoring.containerResources') }}</h3>
<div v-if="!containers.length" class="text-white/40 text-sm py-4 text-center">
No container metrics available
{{ t('monitoring.noContainerMetrics') }}
</div>
<div v-else class="space-y-3">
<div
@@ -190,11 +190,11 @@
<!-- System Health Timeline -->
<div class="glass-card p-5">
<div class="flex items-center justify-between mb-4">
<h3 class="text-sm font-medium text-white/80">System Health</h3>
<h3 class="text-sm font-medium text-white/80">{{ t('monitoring.systemHealth') }}</h3>
<div class="flex items-center gap-2 text-xs text-white/40">
<span class="inline-block w-2 h-2 rounded-full bg-green-400"></span> Healthy
<span class="inline-block w-2 h-2 rounded-full bg-orange-400 ml-2"></span> Elevated
<span class="inline-block w-2 h-2 rounded-full bg-red-400 ml-2"></span> Critical
<span class="inline-block w-2 h-2 rounded-full bg-green-400"></span> {{ t('common.healthy') }}
<span class="inline-block w-2 h-2 rounded-full bg-orange-400 ml-2"></span> {{ t('common.elevated') }}
<span class="inline-block w-2 h-2 rounded-full bg-red-400 ml-2"></span> {{ t('common.critical') }}
</div>
</div>
<div class="flex gap-0.5 h-6">
@@ -213,13 +213,14 @@
</div>
<p class="text-xs text-white/30 mt-4 text-center">
Refreshing every 5 seconds &middot; WS connections: {{ current?.ws_connections ?? 0 }}
{{ t('monitoring.refreshFooter') }} &middot; {{ t('monitoring.wsConnections', { count: current?.ws_connections ?? 0 }) }}
</p>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted } from 'vue'
import { useI18n } from 'vue-i18n'
import { rpcClient } from '@/api/rpc-client'
import LineChart from '@/components/LineChart.vue'
import type { ChartDataset } from '@/components/LineChart.vue'
@@ -279,6 +280,8 @@ interface FiredAlert {
acknowledged: boolean
}
const { t } = useI18n()
const current = ref<MetricSnapshot | null>(null)
const history = ref<MetricSnapshot[]>([])
const containers = ref<ContainerMetrics[]>([])
@@ -382,11 +385,11 @@ function formatBytes(bytes: number): string {
function ruleLabel(kind: string): string {
const labels: Record<string, string> = {
disk_usage: 'Disk Usage',
ram_usage: 'RAM Usage',
container_crash: 'Container Crash',
backend_error_spike: 'RPC Latency Spike',
ssl_cert_expiry: 'SSL Cert Expiry',
disk_usage: t('monitoring.diskUsage'),
ram_usage: t('monitoring.ramUsage'),
container_crash: t('monitoring.containerCrash'),
backend_error_spike: t('monitoring.rpcLatencySpike'),
ssl_cert_expiry: t('monitoring.sslCertExpiry'),
}
return labels[kind] ?? kind
}