i18n(frontend): translate every remaining English string on the index page

Closes the index page's i18n coverage. Combined with the page-chrome
commit, every label users see on the dashboard is now sourced from
the TOML translation files.

Per file:
- IndexPage.vue: loading-spinner tip (initial + dynamic).
- BackupModal.vue: modal title, both list-item titles + descriptions
  ("Back up" / "Restore"), in-flight busy tips ("Importing database…"
  / "Restarting panel…").
- PanelUpdateModal.vue: modal title, update-available alert,
  current/latest version row labels, "Up to date" tag + label,
  primary action button. Modal.confirm now uses the translated
  panelUpdateDialog / panelUpdateDialogDesc with #version#
  substitution; success toast uses panelUpdateStartedPopover.
- LogModal.vue: title slot ("Logs"). The Debug/Info/Notice/Warning/
  Error log-level options stay literal — they're xray's wire values,
  not user-facing labels (matches the existing settings-page choice).
- XrayLogModal.vue: title + Filter label. Direct/Blocked/Proxy stay
  literal for the same reason.
- VersionModal.vue: modal title + xray-switch alert + per-file
  tooltip + "Update all" button + custom-geo collapse header. The
  Modal.confirm flows for switchXrayVersion + updateGeofile use
  translated dialog/desc with #version# / #filename# substitution.
- CpuHistoryModal.vue: title slot.
- CustomGeoSection.vue: routing-hint alert, Add / Update-all buttons,
  every column title (computed for live locale), copy/edit/download/
  delete tooltips, copy toast, delete-confirm modal, empty-state
  text.
- CustomGeoFormModal.vue: add/edit titles, OK/cancel labels, Type/
  Alias/URL field labels, alias placeholder, all three validation
  toasts.

Total: ~50 strings localised across 8 index-page files. The Hello /
Welcome login headline cycle and a handful of literal xray wire
values (Direct/Blocked/Proxy/log levels) are intentionally kept
hardcoded.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
MHSanaei
2026-05-08 15:17:07 +02:00
parent e7d117f11f
commit cb37dd55ca
9 changed files with 101 additions and 77 deletions
+14 -11
View File
@@ -1,8 +1,11 @@
<script setup>
import { reactive, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import { message } from 'ant-design-vue';
import { HttpUtil } from '@/utils';
const { t } = useI18n();
const props = defineProps({
open: { type: Boolean, default: false },
// Populate with the record when editing; null/undefined when adding.
@@ -41,22 +44,22 @@ function close() {
function validate() {
// Backend expects a filesystem-safe alias; legacy enforces the same regex.
if (!/^[a-z0-9_-]+$/.test(form.alias || '')) {
message.error('Alias must contain only lowercase letters, digits, dashes or underscores.');
message.error(t('pages.index.customGeoValidationAlias'));
return false;
}
const u = (form.url || '').trim();
if (!/^https?:\/\//i.test(u)) {
message.error('URL must start with http:// or https://');
message.error(t('pages.index.customGeoValidationUrl'));
return false;
}
try {
const parsed = new URL(u);
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
message.error('URL must start with http:// or https://');
message.error(t('pages.index.customGeoValidationUrl'));
return false;
}
} catch (_e) {
message.error('URL must start with http:// or https://');
message.error(t('pages.index.customGeoValidationUrl'));
return false;
}
return true;
@@ -83,28 +86,28 @@ async function submit() {
<template>
<a-modal
:open="open"
:title="editing ? 'Edit custom geo entry' : 'Add custom geo entry'"
:title="editing ? t('pages.index.customGeoModalEdit') : t('pages.index.customGeoModalAdd')"
:confirm-loading="saving"
ok-text="Save"
cancel-text="Close"
:ok-text="t('pages.index.customGeoModalSave')"
:cancel-text="t('close')"
@ok="submit"
@cancel="close"
>
<a-form layout="vertical">
<a-form-item label="Type">
<a-form-item :label="t('pages.index.customGeoType')">
<a-select v-model:value="form.type" :disabled="editing">
<a-select-option value="geosite">geosite</a-select-option>
<a-select-option value="geoip">geoip</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="Alias">
<a-form-item :label="t('pages.index.customGeoAlias')">
<a-input
v-model:value="form.alias"
:disabled="editing"
placeholder="lowercase letters, digits, dashes, underscores"
:placeholder="t('pages.index.customGeoAliasPlaceholder')"
/>
</a-form-item>
<a-form-item label="URL">
<a-form-item :label="t('pages.index.customGeoUrl')">
<a-input v-model:value="form.url" placeholder="https://" />
</a-form-item>
</a-form>