Add a language-switch system with per-language files
Introduce a hand-rolled i18n layer across all three UIs, each reading its
text from per-language JSON files (English base + Polish + Danish). Nothing
in the converted screens hardcodes English any more.
- Web App (Vue): src/i18n/{en,pl,da}.json + index.js exposing t()/tSplit(),
reactive to the signed-in profile locale. Every view, component, form and
the status labels in lib/format.js go through t().
- API Server panel (Vue): src/i18n/ with its own localStorage-persisted
language (the panel has no user profile) and a header language picker.
Chrome, cards, login and API section titles translated; endpoint reference
descriptions intentionally kept in English. Rebuilt embedded dist.
- Phone App (Flutter): assets/i18n/ + lib/i18n.dart loaded at startup,
driven by AppSettings.locale. Nav, login, lock, dashboard, the full
Settings panel (incl. language picker) and format.dart status labels
translated; remaining detail screens fall back to English.
Language = the language half of the existing BCP-47 locale; the region half
still drives date/number/currency formatting. Missing keys fall back to
English, and plurals use Intl.PluralRules / Intl.plural so Polish gets the
correct one/few/many forms. Settings flags languages without a translation.
Tests updated to assert the localized (Polish) status wording; all pass.
See TRANSLATIONS.md for the format and how to add a language.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
ee28b522c7
commit
b6bb6b1df0
@@ -1,6 +1,7 @@
|
||||
<script setup>
|
||||
import { ref, onMounted } from "vue";
|
||||
import { request } from "../api";
|
||||
import { t } from "../i18n";
|
||||
|
||||
// Superadmin-only: retarget the PocketBase this server talks to. The change is
|
||||
// applied at runtime AND persisted to the server's .env, so it survives a
|
||||
@@ -37,10 +38,10 @@ async function test() {
|
||||
body: form.value,
|
||||
});
|
||||
notice.value = probe.value.superuser
|
||||
? "Connection OK — superuser authenticated."
|
||||
? t("pocketbase.testOk")
|
||||
: probe.value.reachable
|
||||
? "PocketBase is reachable, but the service account did not authenticate."
|
||||
: "PocketBase is not reachable at that address.";
|
||||
? t("pocketbase.testReachableNoAuth")
|
||||
: t("pocketbase.testUnreachable");
|
||||
} catch (e) {
|
||||
error.value = e.message;
|
||||
} finally {
|
||||
@@ -57,7 +58,7 @@ async function save() {
|
||||
cfg.value = out.config;
|
||||
probe.value = out.config.probe;
|
||||
form.value.adminPassword = "";
|
||||
notice.value = out.warning || "Saved. The server is now using this PocketBase.";
|
||||
notice.value = out.warning || t("pocketbase.savedNotice");
|
||||
} catch (e) {
|
||||
error.value = e.message;
|
||||
} finally {
|
||||
@@ -70,8 +71,8 @@ async function save() {
|
||||
<div class="dh-card overflow-hidden">
|
||||
<div class="flex items-center justify-between border-b border-subtle px-5 py-4">
|
||||
<div>
|
||||
<div class="text-base font-bold tracking-[-0.02em] text-strong">PocketBase</div>
|
||||
<p class="mt-0.5 text-xs text-muted">Database connection used by every endpoint</p>
|
||||
<div class="text-base font-bold tracking-[-0.02em] text-strong">{{ t("pocketbase.title") }}</div>
|
||||
<p class="mt-0.5 text-xs text-muted">{{ t("pocketbase.subtitle") }}</p>
|
||||
</div>
|
||||
<span
|
||||
v-if="probe"
|
||||
@@ -83,30 +84,30 @@ async function save() {
|
||||
: 'bg-danger-soft text-danger'"
|
||||
>
|
||||
<span class="h-1.5 w-1.5 rounded-full bg-current"></span>
|
||||
{{ probe.superuser ? "connected" : probe.reachable ? "no superuser" : "unreachable" }}
|
||||
{{ probe.superuser ? t("pocketbase.connected") : probe.reachable ? t("pocketbase.noSuperuser") : t("pocketbase.unreachable") }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-4 px-5 py-4">
|
||||
<div>
|
||||
<label class="dh-label" for="pb-url">Base URL</label>
|
||||
<label class="dh-label" for="pb-url">{{ t("pocketbase.baseUrl") }}</label>
|
||||
<input id="pb-url" v-model="form.url" class="dh-input" placeholder="http://10.2.1.10:8027" />
|
||||
</div>
|
||||
|
||||
<div class="grid gap-4 sm:grid-cols-2">
|
||||
<div>
|
||||
<label class="dh-label" for="pb-email">Superuser email</label>
|
||||
<label class="dh-label" for="pb-email">{{ t("pocketbase.superuserEmail") }}</label>
|
||||
<input id="pb-email" v-model="form.adminEmail" class="dh-input" autocomplete="off" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="dh-label" for="pb-password">Superuser password</label>
|
||||
<label class="dh-label" for="pb-password">{{ t("pocketbase.superuserPassword") }}</label>
|
||||
<input
|
||||
id="pb-password"
|
||||
v-model="form.adminPassword"
|
||||
class="dh-input"
|
||||
type="password"
|
||||
autocomplete="new-password"
|
||||
:placeholder="cfg?.adminConfigured ? 'unchanged' : 'not set'"
|
||||
:placeholder="cfg?.adminConfigured ? t('pocketbase.passwordUnchanged') : t('pocketbase.passwordNotSet')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -116,10 +117,10 @@ async function save() {
|
||||
<p v-if="error" class="rounded-control bg-danger-soft px-3 py-2 text-xs text-danger">{{ error }}</p>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<button class="dh-btn" :disabled="busy" @click="save">Save & apply</button>
|
||||
<button class="dh-btn-ghost" :disabled="busy" @click="test">Test connection</button>
|
||||
<button class="dh-btn" :disabled="busy" @click="save">{{ t("pocketbase.saveApply") }}</button>
|
||||
<button class="dh-btn-ghost" :disabled="busy" @click="test">{{ t("pocketbase.testConnection") }}</button>
|
||||
<span class="flex-1"></span>
|
||||
<span class="eyebrow">persisted to .env</span>
|
||||
<span class="eyebrow">{{ t("pocketbase.persistedEnv") }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user