feat(i18n): i18nize everything
This commit is contained in:
@@ -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"
|
||||
/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -71,7 +71,11 @@ const renderFrame = (timestamp: number) => {
|
||||
for (const callback of sortedCallbacks) {
|
||||
ctx.save();
|
||||
|
||||
try {
|
||||
callback(ctx, deltaTime, lastRealFrameTime);
|
||||
} catch (error: unknown) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
@@ -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()"
|
||||
/>
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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()"
|
||||
/>
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -1,4 +1,14 @@
|
||||
{
|
||||
"common": {
|
||||
"cancel": "Cancel",
|
||||
"confirm": "Confirm",
|
||||
"quit": "Quit",
|
||||
"start": "Start",
|
||||
"restart": "Restart",
|
||||
"reset": "Reset",
|
||||
"select": "Select",
|
||||
"goBack": "Go back"
|
||||
},
|
||||
"achievementsScreen": {
|
||||
"title": "Achievements"
|
||||
},
|
||||
@@ -38,7 +48,12 @@
|
||||
|
||||
"startUp": {
|
||||
"title": "Start-up",
|
||||
"description": "Set how you would like your system to\nstart up when you turn the power on."
|
||||
"description": "Set how you would like your system to\nstart up when you turn the power on.",
|
||||
"3dMode": "3D Mode",
|
||||
"2dMode": "2D Mode",
|
||||
"autoStart": "The Main Menu will appear\nautomatically when you turn\nthe power on.",
|
||||
"confirmation3d": "Rendering mode set to 3D",
|
||||
"confirmation2d": "Rendering mode set to 2D"
|
||||
},
|
||||
"language": {
|
||||
"title": "Language",
|
||||
@@ -47,7 +62,11 @@
|
||||
},
|
||||
"2048": {
|
||||
"title": "2048",
|
||||
"description": "Play 2048!"
|
||||
"description": "Play 2048!",
|
||||
"restartConfirmation": "Restart game?",
|
||||
"gameOver": "Game Over!\nRestart?",
|
||||
"score": "Score",
|
||||
"highScore": "Best"
|
||||
}
|
||||
},
|
||||
"clock": {
|
||||
@@ -58,17 +77,25 @@
|
||||
"title": "Achievements",
|
||||
"description": "Manage your achievements.",
|
||||
"resetButton": "Reset Achievements",
|
||||
"resetConfirmation": "Reset all achievements?"
|
||||
"resetConfirmation": "Reset all achievements?",
|
||||
"viewAll": "View All",
|
||||
"obtained": "Obtained",
|
||||
"total": "Total"
|
||||
},
|
||||
|
||||
"date": {
|
||||
"title": "Date",
|
||||
"description": "Today's date."
|
||||
"description": "Today's date.",
|
||||
"month": "Month",
|
||||
"day": "Day",
|
||||
"year": "Year"
|
||||
},
|
||||
|
||||
"time": {
|
||||
"title": "Time",
|
||||
"description": "Current time."
|
||||
"description": "Current time.",
|
||||
"hour": "Hour",
|
||||
"minute": "Minute"
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
@@ -83,6 +110,9 @@
|
||||
"birthday": {
|
||||
"title": "Birthday",
|
||||
"description": "This is my birthday.",
|
||||
"month": "Month",
|
||||
"day": "Day",
|
||||
"year": "Year",
|
||||
"confirmation": {
|
||||
"today": "Yes, it's today!",
|
||||
"future": "Don't forget to wish me in {days} days!"
|
||||
@@ -114,6 +144,25 @@
|
||||
"description": "TODO"
|
||||
}
|
||||
},
|
||||
"contact": {
|
||||
"title": "Choose a Chat Room to join.",
|
||||
"actions": {
|
||||
"open": "Open",
|
||||
"copy": "Copy",
|
||||
"githubProfile": "Github profile",
|
||||
"email": "Email",
|
||||
"websiteLink": "Website link",
|
||||
"cv": "CV"
|
||||
},
|
||||
"copiedToClipboard": "{item} copied to clipboard",
|
||||
"openUrl": "Open {url}?",
|
||||
"opened": "{item} opened"
|
||||
},
|
||||
"gallery": {
|
||||
"title": "Pihkaal's Gallery",
|
||||
"description": "Started on March 2025. I love taking photos of plants, insects, and arachnids.",
|
||||
"backToHome": "Back to Home"
|
||||
},
|
||||
"projects": {
|
||||
"linkConformationPopup": {
|
||||
"yes": "yes",
|
||||
|
||||
Reference in New Issue
Block a user