Skip to content

Commit c7f10cf

Browse files
committed
chore: update translations example
1 parent 65462cb commit c7f10cf

3 files changed

Lines changed: 76 additions & 3 deletions

File tree

src/App.vue

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
<script setup lang="ts">
22
import { computed } from "vue";
33
import { Sparkles, User } from "lucide-vue-next";
4+
import en from "./locales/en.json";
5+
import de_DE from "./locales/de_DE.json";
46
import "./style.css";
57
68
// The panel injects the authenticated 5stack user here. It is null for guests
@@ -11,8 +13,49 @@ import "./style.css";
1113
const props = defineProps<{
1214
user?: { steam_id: string; name: string; role: string } | null;
1315
query?: Record<string, unknown>;
16+
// Capabilities the host OWNS and therefore hands down. You cannot import
17+
// these: a plugin is a separate module graph, and both `useToast` and
18+
// vue-i18n keep their state in module scope — an imported copy would be a
19+
// second, empty instance whose toasts nothing renders and whose translator
20+
// knows no messages. It fails silently, which is the worst way to fail.
21+
// See /plugins/translations.
22+
notify?: (message: string, kind: "error" | "success") => void;
23+
t?: (key: string, named?: Record<string, unknown>) => string;
24+
locale?: string;
1425
}>();
1526
27+
// ---- translations -----------------------------------------------------------
28+
// Your own catalogue first, the panel second, a literal English fallback last.
29+
// Ship whatever languages you have; anything missing falls through to your
30+
// English, so a plugin with only en.json still works everywhere — it is just
31+
// untranslated, which beats a screen full of dotted keys.
32+
const CATALOGUES: Record<string, Record<string, unknown>> = { en, de_DE };
33+
34+
function lookup(catalogue: Record<string, unknown>, key: string) {
35+
return key.split(".").reduce<unknown>((o, k) => (o as never)?.[k], catalogue);
36+
}
37+
38+
function tr(key: string, fallback: string, named?: Record<string, unknown>) {
39+
// Read locale so computeds using tr() re-run on a language switch — `t` is a
40+
// stable reference and would not invalidate them by itself.
41+
const lang = props.locale ?? "en";
42+
const mine = lookup(CATALOGUES[lang] ?? {}, key) ?? lookup(CATALOGUES.en, key);
43+
if (typeof mine === "string") {
44+
return mine.replace(/\{(\w+)\}/g, (_, k) => String(named?.[k] ?? `{${k}}`));
45+
}
46+
// vue-i18n returns the KEY when a message is missing, so treat that as a miss
47+
// — otherwise "hello.title" ships to production looking like a bug.
48+
const host = props.t?.(key, named);
49+
return !host || host === key ? fallback : host;
50+
}
51+
52+
// The panel's toast when embedded; a console line when running standalone.
53+
function sayHi() {
54+
const message = tr("hello.toast_hi", "Hello from the plugin.");
55+
if (props.notify) props.notify(message, "success");
56+
else console.log(message);
57+
}
58+
1659
// Because the manifest sets "profileTabLabel", this same component is ALSO
1760
// mounted as a tab on /players/:steamid. Two query keys tell us we are there.
1861
@@ -65,14 +108,28 @@ const embed = computed(() => props.query?.embed === "1");
65108
>
66109
<Sparkles class="h-10 w-10 text-primary" />
67110
<h1 class="text-3xl font-bold tracking-tight text-foreground">
68-
Hello from a 5stack plugin
111+
{{ tr("hello.title", "Hello from a 5stack plugin") }}
69112
</h1>
70113
<p class="text-muted-foreground">
71114
<template v-if="user">
72-
Signed in as {{ user.name }} ({{ user.role }})
115+
{{
116+
tr("hello.signed_in", `Signed in as ${user.name} (${user.role})`, {
117+
name: user.name,
118+
role: user.role,
119+
})
120+
}}
121+
</template>
122+
<template v-else>
123+
{{ tr("hello.not_signed_in", "Not signed in") }}
73124
</template>
74-
<template v-else>Not signed in</template>
75125
</p>
126+
<!-- The panel's own toast, reached through the `notify` prop. -->
127+
<button
128+
class="rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground"
129+
@click="sayHi()"
130+
>
131+
Toast me
132+
</button>
76133
<p class="max-w-md text-sm text-muted-foreground">
77134
This whole page is a Module Federation remote rendered natively inside
78135
the 5stack panel — same nav, theme, and login.

src/locales/de_DE.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"hello": {
3+
"title": "Hallo von einem 5stack-Plugin",
4+
"signed_in": "Angemeldet als {name} ({role})",
5+
"not_signed_in": "Nicht angemeldet",
6+
"toast_hi": "Hallo vom Plugin — das ist der Toast des Panels."
7+
}
8+
}

src/locales/en.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"hello": {
3+
"title": "Hello from a 5stack plugin",
4+
"signed_in": "Signed in as {name} ({role})",
5+
"not_signed_in": "Not signed in",
6+
"toast_hi": "Hello from the plugin — this is the panel's own toast."
7+
}
8+
}

0 commit comments

Comments
 (0)