feat(i18n): i18nize everything

This commit is contained in:
2026-01-30 22:58:07 +01:00
parent 13e4ae64b5
commit 9deeb42cfd
16 changed files with 142 additions and 78 deletions

View File

@@ -53,8 +53,8 @@ onUnmounted(() => {
<Buttons
v-if="confirmationModal.onConfirm"
:y-offset="confirmationModal.modalButtonsYOffset"
b-label="Cancel"
a-label="Confirm"
:b-label="$t('common.cancel')"
:a-label="$t('common.confirm')"
@activate-a="handleActivateA"
@activate-b="handleActivateB"
/>

View File

@@ -10,13 +10,17 @@ const achievements = useAchievementsStore();
const confirmationModal = useConfirmationModal();
const ACTIONS = {
github: ["Open", "Github profile", "https://github.com/pihkaal"],
email: ["Copy", "Email", "hello@pihkaal.me"],
website: ["Copy", "Website link", "https://pihkaal.me"],
cv: ["Open", "CV", "https://pihkaal.me/cv"],
github: [
"open",
"contact.actions.githubProfile",
"https://github.com/pihkaal",
],
email: ["copy", "contact.actions.email", "hello@pihkaal.me"],
website: ["copy", "contact.actions.websiteLink", "https://pihkaal.me"],
cv: ["open", "contact.actions.cv", "https://pihkaal.me/cv"],
} as const satisfies Record<
string,
[action: "Copy" | "Open", verb: string, content: string]
[action: "copy" | "open", verbKey: string, content: string]
>;
const { selected, selectorPosition } = useButtonNavigation({
@@ -54,20 +58,21 @@ const { selected, selectorPosition } = useButtonNavigation({
});
const actionateButton = async (button: (typeof selected)["value"]) => {
const [action, verb, content] = ACTIONS[button];
if (action === "Copy") {
const [action, verbKey, content] = ACTIONS[button];
const verb = $t(verbKey);
if (action === "copy") {
try {
await navigator.clipboard.writeText(content);
store.pushNotification(`${verb} copied to clipboard`);
store.pushNotification($t("contact.copiedToClipboard", { item: verb }));
} catch (error) {
console.error("Failed to copy to clipboard:", error);
}
} else {
const url = content.replace(/^https?:\/\//, "");
confirmationModal.open({
text: `Open ${url}?`,
text: $t("contact.openUrl", { url }),
onConfirm: async () => {
store.pushNotification(`${verb} opened`);
store.pushNotification($t("contact.opened", { item: verb }));
await sleep(100);
await navigateTo(content, { open: { target: "_blank " } });
@@ -94,7 +99,7 @@ const actionateButton = async (button: (typeof selected)["value"]) => {
/>
<CommonBars
title="Choose a Chat Room to join."
:title="$t('contact.title')"
:opacity="
store.isIntro ? store.intro.stage3Opacity : store.outro.stage2Opacity
"
@@ -112,8 +117,8 @@ const actionateButton = async (button: (typeof selected)["value"]) => {
? store.outro.stage2Opacity
: 1
"
b-label="Quit"
:a-label="ACTIONS[selected][0]"
:b-label="$t('common.quit')"
:a-label="$t(`contact.actions.${ACTIONS[selected][0]}`)"
@activate-b="store.animateOutro()"
/>
</template>

View File

@@ -71,7 +71,11 @@ const renderFrame = (timestamp: number) => {
for (const callback of sortedCallbacks) {
ctx.save();
callback(ctx, deltaTime, lastRealFrameTime);
try {
callback(ctx, deltaTime, lastRealFrameTime);
} catch (error: unknown) {
console.error(error);
}
ctx.restore();
}

View File

@@ -87,7 +87,8 @@ onRender((ctx) => {
ctx.font = "10px NDS10";
ctx.textBaseline = "top";
ctx.fillStyle = "#010101";
const { actualBoundingBoxRight: textWidth } = ctx.measureText("View All");
const viewAllText = $t("settings.clock.achievements.viewAll");
const { actualBoundingBoxRight: textWidth } = ctx.measureText(viewAllText);
const totalWidth = achievementAssets.X.rect.width + GAP + textWidth;
const left = Math.ceil(
@@ -99,7 +100,7 @@ onRender((ctx) => {
achievementAssets.X.draw(ctx, left, 7);
fillTextHCentered(
ctx,
"View All",
viewAllText,
left + achievementAssets.X.rect.width + GAP,
7,
textWidth,
@@ -110,7 +111,7 @@ onRender((ctx) => {
<template>
<NumberInput
:model-value="achievements.achievements.length"
title="Obtained"
:title="$t('settings.clock.achievements.obtained')"
:x="4 * 16 - 1"
:selected="achievements.allObtained"
:disabled="true"
@@ -118,7 +119,7 @@ onRender((ctx) => {
<NumberInput
:model-value="ACHIEVEMENTS.length"
title="Total"
:title="$t('settings.clock.achievements.total')"
:x="9 * 16 - 1"
:selected="achievements.allObtained"
:disabled="true"
@@ -126,8 +127,8 @@ onRender((ctx) => {
<CommonButtons
:y-offset="confirmationModal.buttonsYOffset"
b-label="Cancel"
a-label="Reset"
:b-label="$t('common.cancel')"
:a-label="$t('common.reset')"
@activate-b="handleCancel()"
@activate-a="handleReset()"
/>

View File

@@ -30,19 +30,19 @@ defineOptions({ render: () => null });
<template>
<NumberInput
:model-value="now.getMonth() + 1"
title="Month"
:title="$t('settings.clock.date.month')"
:x="1 * 16 - 1"
:disabled="true"
/>
<NumberInput
:model-value="now.getDate()"
title="Day"
:title="$t('settings.clock.date.day')"
:x="5 * 16 - 1"
:disabled="true"
/>
<NumberInput
:model-value="now.getFullYear()"
title="Year"
:title="$t('settings.clock.date.year')"
:digits="4"
:x="9 * 16 - 1"
:disabled="true"
@@ -50,8 +50,8 @@ defineOptions({ render: () => null });
<CommonButtons
:y-offset="0"
b-label="Cancel"
a-label="Confirm"
:b-label="$t('common.cancel')"
:a-label="$t('common.confirm')"
@activate-b="handleCancel"
@activate-a="handleConfirm"
/>

View File

@@ -32,21 +32,21 @@ defineOptions({ render: () => null });
<template>
<NumberInput
:model-value="now.getHours()"
title="Hour"
:title="$t('settings.clock.time.hour')"
:x="4 * 16 - 1"
:disabled="true"
/>
<NumberInput
:model-value="now.getMinutes()"
title="Minute"
:title="$t('settings.clock.time.minute')"
:x="9 * 16 - 1"
:disabled="true"
/>
<CommonButtons
:y-offset="0"
b-label="Cancel"
a-label="Confirm"
:b-label="$t('common.cancel')"
:a-label="$t('common.confirm')"
@activate-b="handleCancel"
@activate-a="handleConfirm"
/>

View File

@@ -198,15 +198,15 @@ const viewComponents: Record<string, Component> = {
<CommonButtons
v-if="isSubmenuSelected"
:y-offset="0"
b-label="Go back"
a-label="Select"
:b-label="$t('common.goBack')"
:a-label="$t('common.select')"
@activate-b="select(getParentMenu(selected))"
/>
<CommonButtons
v-else
:y-offset="0"
b-label="Quit"
a-label="Select"
:b-label="$t('common.quit')"
:a-label="$t('common.select')"
@activate-b="app.navigateTo('home')"
/>
</template>

View File

@@ -19,7 +19,7 @@ const handleActivateA = () => {
}
let confirmed = false;
confirmationModal.open({
text: "Restart game?",
text: $t("settings.options.2048.restartConfirmation"),
onConfirm: () => {
confirmed = true;
},
@@ -95,7 +95,7 @@ let animating = false;
const showRestartModal = () => {
let confirmed = false;
confirmationModal.open({
text: "Game Over!\nRestart?",
text: $t("settings.options.2048.gameOver"),
onConfirm: () => {
confirmed = true;
},
@@ -179,11 +179,15 @@ onRender((ctx) => {
ctx.fillStyle = "#010101";
// score
ctx.fillText("Score:", SCORE_X, SCORE_Y);
ctx.fillText(`${$t("settings.options.2048.score")}:`, SCORE_X, SCORE_Y);
ctx.fillText(score.toString(), SCORE_X, SCORE_Y + 16);
// high score
ctx.fillText("High:", SCORE_X, HIGH_SCORE_Y);
ctx.fillText(
`${$t("settings.options.2048.highScore")}:`,
SCORE_X,
HIGH_SCORE_Y,
);
ctx.fillText(
savedState.value.highScore.toString(),
SCORE_X,
@@ -500,8 +504,8 @@ useKeyDown((key) => {
<template>
<CommonButtons
:y-offset="confirmationModal.buttonsYOffset"
b-label="Cancel"
a-label="Restart"
:b-label="$t('common.quit')"
:a-label="$t('common.restart')"
@activate-b="handleActivateB()"
@activate-a="handleActivateA()"
/>

View File

@@ -126,8 +126,8 @@ defineOptions({
<template>
<CommonButtons
:y-offset="0"
b-label="Cancel"
a-label="Confirm"
:b-label="$t('common.cancel')"
:a-label="$t('common.confirm')"
@activate-b="handleCancel"
@activate-a="handleConfirm"
/>

View File

@@ -35,7 +35,10 @@ const handleConfirm = () => {
const showConfirmation = () => {
confirmationModal.open({
text: `Rendering mode set to ${mode === "3d" ? "3D" : "2D"}`,
text:
mode === "3d"
? $t("settings.options.startUp.confirmation3d")
: $t("settings.options.startUp.confirmation2d"),
onClosed: () => {
store.closeSubMenu();
},
@@ -75,21 +78,20 @@ onRender((ctx) => {
fillImageTextHCentered(ctx, logo, title, 0, 4, buttonWidth, 4);
ctx.fillStyle = "#282828";
const text =
"The Main Menu will appear\nautomatically when you turn\nthe power on.";
const text = $t("settings.options.startUp.autoStart");
fillTextHCenteredMultiline(ctx, text, 0, y, buttonWidth, 15);
ctx.restore();
};
drawButton(
"3D Mode",
$t("settings.options.startUp.3dMode"),
renderingModeAssets._3dMode,
32,
selected.value === "_3dMode",
);
drawButton(
"2D Mode",
$t("settings.options.startUp.2dMode"),
renderingModeAssets._2dMode,
96,
selected.value === "_2dMode",
@@ -100,8 +102,8 @@ onRender((ctx) => {
<template>
<CommonButtons
:y-offset="confirmationModal.buttonsYOffset"
b-label="Cancel"
a-label="Confirm"
:b-label="$t('common.cancel')"
:a-label="$t('common.confirm')"
@activate-b="handleCancel"
@activate-a="handleConfirm"
/>

View File

@@ -56,19 +56,19 @@ defineOptions({ render: () => null });
<!-- date format may change based on the language (d/m/y is superior btw) -->
<NumberInput
:model-value="BIRTHDAY_MONTH"
title="Month"
:title="$t('settings.user.birthday.month')"
:x="1 * 16"
:disabled="true"
/>
<NumberInput
:model-value="BIRTHDAY_DAY"
title="Day"
:title="$t('settings.user.birthday.day')"
:x="5 * 16"
:disabled="true"
/>
<NumberInput
:model-value="BIRTHDAY_YEAR"
title="Day"
:title="$t('settings.user.birthday.year')"
:digits="4"
:x="9 * 16"
:disabled="true"
@@ -76,8 +76,8 @@ defineOptions({ render: () => null });
<CommonButtons
:y-offset="0"
b-label="Cancel"
a-label="Confirm"
:b-label="$t('common.cancel')"
:a-label="$t('common.confirm')"
@activate-b="handleActivateB"
@activate-a="handleActivateA"
/>

View File

@@ -135,8 +135,8 @@ const handleConfirm = () => {
<template>
<CommonButtons
:y-offset="0"
b-label="Cancel"
a-label="Confirm"
:b-label="$t('common.cancel')"
:a-label="$t('common.confirm')"
@activate-b="handleCancel"
@activate-a="handleConfirm"
/>

View File

@@ -253,8 +253,8 @@ defineOptions({ render: () => null });
<template>
<CommonButtons
:y-offset="confirmationModal.buttonsYOffset"
b-label="Quit"
:a-label="state === 'waiting' ? 'Start' : 'Restart'"
:b-label="$t('common.quit')"
:a-label="state === 'waiting' ? $t('common.start') : $t('common.restart')"
@activate-b="handleCancel"
@activate-a="handleConfirm"
/>

View File

@@ -56,8 +56,8 @@ defineOptions({ render: () => null });
<template>
<CommonButtons
:y-offset="0"
b-label="Cancel"
a-label="Confirm"
:b-label="$t('common.cancel')"
:a-label="$t('common.confirm')"
@activate-b="handleCancel"
@activate-a="handleConfirm"
/>

View File

@@ -68,10 +68,9 @@ const FADE_IN_DURATION = 1.5;
const FADE_IN_X_FACTOR = 0.15;
const FADE_IN_Y_FACTOR = 0.075;
const TITLE = "Pihkaal's Gallery";
const DESCRIPTION =
"Started on March 2025. I love taking photos of plants, insects, and arachnids.";
const BACK_BUTTON = "Back to Home";
const title = computed(() => $t("gallery.title"));
const description = computed(() => $t("gallery.description"));
const backButton = computed(() => $t("gallery.backToHome"));
const typeText = (
target: Ref<string>,
@@ -98,14 +97,14 @@ const animateIntro = async () => {
isAnimating.value = true;
typeText(backButtonText, BACK_BUTTON, BACK_BUTTON_DURATION, false, {
typeText(backButtonText, backButton.value, BACK_BUTTON_DURATION, false, {
onStart: () => (showBackButtonIcon.value = true),
}).delay(ANIMATION_SLEEP + FADE_IN_DELAY);
typeText(titleText, TITLE, TITLE_DURATION).delay(
typeText(titleText, title.value, TITLE_DURATION).delay(
ANIMATION_SLEEP + FADE_IN_DELAY,
);
typeText(descriptionText, DESCRIPTION, DESCRIPTION_DURATION).delay(
typeText(descriptionText, description.value, DESCRIPTION_DURATION).delay(
ANIMATION_SLEEP + FADE_IN_DELAY,
);
@@ -149,13 +148,13 @@ const animateOutro = async () => {
ease: "power2.out",
});
typeText(backButtonText, BACK_BUTTON, BACK_BUTTON_DURATION, true, {
typeText(backButtonText, backButton.value, BACK_BUTTON_DURATION, true, {
onComplete: () => (showBackButtonIcon.value = false),
});
typeText(titleText, TITLE, TITLE_DURATION, true);
typeText(titleText, title.value, TITLE_DURATION, true);
typeText(descriptionText, DESCRIPTION, DESCRIPTION_DURATION, true, {
typeText(descriptionText, description.value, DESCRIPTION_DURATION, true, {
onComplete: async () => {
await sleep(ANIMATION_SLEEP * 1000);
galleryStore.shouldAnimateOutro = true;
@@ -200,11 +199,11 @@ onMounted(() => {
>
<h1 class="text-3xl mt-3 font-bold relative">
<span class="invisible">{{ TITLE }}</span>
<span class="invisible">{{ title }}</span>
<span class="absolute inset-0">{{ titleText }}</span>
</h1>
<p class="text-neutral-600 text-sm mt-2 w-4/5 relative">
<span class="invisible">{{ DESCRIPTION }}</span>
<span class="invisible">{{ description }}</span>
<span class="absolute inset-0">{{ descriptionText }}</span>
</p>
</header>