Skip to content
This repository has been archived by the owner on May 30, 2024. It is now read-only.

feat(i18n): add localization to the whole website #59

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
8 changes: 8 additions & 0 deletions app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ export default defineAppConfig({
},
},

tooltip: {
background: "bg-white dark:bg-navigation",
},

modal: {
background: "bg-white dark:bg-navigation",
},

card: {
background: "bg-white dark:bg-navigation",
divide: "divide-y divide-slate-500 dark:divide-slate-700",
Expand Down
24 changes: 17 additions & 7 deletions app.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
<script setup lang="ts">
const loading = ref(true);
useState("lang_id", () => "en-us");
const loading = useState("loading", () => {
return {
website: true,
lang: true,
};
});

useState("kudo-slideover", () => false);

Expand All @@ -10,12 +16,12 @@ useSeoMeta({
ogTitle: "Pestoverse",
ogDescription: "The official website for running, upcoming and archived events created by Yuniiho's community!",
ogUrl: "https://pestoverse.tougrel.dev",
ogImage: "https://i.imgur.com/E41zbAO.png",
ogImage: "https://i.imgur.com/HjwqC9A.png",
ogImageType: "image/png",

twitterTitle: "Pestoverse",
twitterDescription: "The official website for running, upcoming and archived created by Yuniiho's community!",
twitterImage: "https://i.imgur.com/E41zbAO.png",
twitterImage: "https://i.imgur.com/HjwqC9A.png",
twitterImageType: "image/png",
twitterCard: "summary_large_image",
});
Expand Down Expand Up @@ -45,7 +51,11 @@ useHead({
});

onMounted(() => {
loading.value = false;
generateLanguageMap();
loading.value.website = false;

if (localStorage.getItem("lang-id"))
changeLangID(localStorage.getItem("lang-id")!);

const primary = localStorage.getItem("ui-color");
if (primary) useAppConfig().ui.primary = primary;
Expand All @@ -54,8 +64,8 @@ onMounted(() => {

<template>
<main>
<UiLoadingScreen v-if="loading" />
<KudoSidebar v-if="!loading" />
<NuxtPage v-if="!loading" />
<UiLoadingScreen v-if="loading.website && loading.lang" />
<KudoSidebar v-if="!loading.website && !loading.lang" />
<NuxtPage v-if="!loading.website && !loading.lang" />
</main>
</template>
23 changes: 23 additions & 0 deletions assets/i18n/el-gr.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"pestoverse": "Pestoverse",
"close": "Κλείσιμο",
"nav.menu": "Μενού",
"nav.home": "Αρχική",
"nav.gallery": "Δείτε όλα τα Pesto",
"nav.credits": "Κατασκευασμένο από αυτούς τους καταπληκτικούς ανθρώπους",
"nav.menu.world": "Pesto Around the World",
"nav.menu.gallery": "Pesto Gallery",
"nav.menu.kudo": "Kudo Boards",
"nav.menu.credits": "Credits",
"home.explore": "Start exploring!",
"credits.emotes": "Για τα καταπληκτικά emotes",
"credits.gallery": "Ενημερώσεις συλλογής",
"credits.ideas": "Ιδέες",
"credits.ideas.maintain": "Ιδέα ιστοσελίδας & συντήρηση",
"credits.ideas.code": "Ιδέες & κώδικας",
"footer.made": "Φτιαγμένο με",
"footer.made.by": "από την",
"footer.made.community": "pesto κοινότητα",
"footer.credits": "Some of the work is done by some amazing pesties that you can find in the",
"footer.credits.page": "σελίδα!"
}
71 changes: 71 additions & 0 deletions assets/i18n/strings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
{
"en-us": {
"pestoverse": "Pestoverse",
"close": "Close",
"nav.menu": "Menu",
"nav.home": "Home",
"nav.gallery": "Look at all the Pesto",
"nav.credits": "Made by these amazing people",
"nav.menu.world": "Pesto Around the World",
"nav.menu.gallery": "Pesto Gallery",
"nav.menu.kudo": "Kudo Boards",
"nav.menu.credits": "Credits",
"home.explore": "Start exploring!",
"credits.emotes": "For the amazing emotes",
"credits.gallery": "Gallery updates",
"credits.ideas": "Ideas",
"credits.ideas.maintain": "Website idea & maintainer",
"credits.ideas.code": "Ideas & code",
"footer.made": "Made with",
"footer.made.by": "by the",
"footer.made.community": "pesto community",
"footer.credits": "Some of the work is done by some amazing pesties that you can find in the",
"footer.credits.page": "page!"
},
"el-gr": {
"pestoverse": "Pestoverse",
"close": "Κλείσιμο",
"nav.menu": "Μενού",
"nav.home": "Αρχική",
"nav.gallery": "Δείτε όλα τα Pesto",
"nav.credits": "Κατασκευασμένο από αυτούς τους καταπληκτικούς ανθρώπους",
"nav.menu.world": "Pesto Around the World",
"nav.menu.gallery": "Pesto Gallery",
"nav.menu.kudo": "Kudo Boards",
"nav.menu.credits": "Credits",
"home.explore": "Start exploring!",
"credits.emotes": "Για τα καταπληκτικά emotes",
"credits.gallery": "Ενημερώσεις συλλογής",
"credits.ideas": "Ιδέες",
"credits.ideas.maintain": "Ιδέα ιστοσελίδας & συντήρηση",
"credits.ideas.code": "Ιδέες & κώδικας",
"footer.made": "Φτιαγμένο με",
"footer.made.by": "από την",
"footer.made.community": "pesto κοινότητα",
"footer.credits": "Some of the work is done by some amazing pesties that you can find in the",
"footer.credits.page": "σελίδα!"
},
"de": {
"pestoverse": "Pestoverse",
"close": "Schließen",
"nav.menu": "Menü",
"nav.home": "Startseite",
"nav.gallery": "Schau dir all das Pesto an",
"nav.credits": "Erstellt von diesen erstaunlichen Leuten",
"nav.menu.world": "Pesto auf der ganzen Welt",
"nav.menu.gallery": "Pesto Galerie",
"nav.menu.kudo": "Kudo-Boards",
"nav.menu.credits": "Impressum",
"home.explore": "Erkunde jetzt!",
"credits.emotes": "Für die fantastischen Emotes",
"credits.gallery": "Galerie-Updates",
"credits.ideas": "Ideen",
"credits.ideas.maintain": "Website-Idee & Wartung",
"credits.ideas.code": "Ideen & Code",
"footer.made": "Erstellt mit",
"footer.made.by": "durch die",
"footer.made.community": "Pesto-Community",
"footer.credits": "Ein Teil der Arbeit wurde durch einige der erstaunliche Pesties erledigt. Finden kannst du sie auf der",
"footer.credits.page": "Seite!"
}
}
10 changes: 7 additions & 3 deletions components/credits/user.vue
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
<script setup lang="ts">
defineProps<{ name: string; description: string; iconPath: string }>();
import { LangNames } from "~/utils/i18n";

const lang = useState("lang");
tougrel marked this conversation as resolved.
Show resolved Hide resolved
const langID = useState("lang_id");
defineProps<{ name: string; description: LangNames; iconPath: string }>();
</script>

<template>
<UCard>
<template #header>
<div class="flex flex-row justify-center">
<img :src="iconPath" loading="lazy" decoding="async" class="h-32 rounded-full" />
<img :src="iconPath" loading="lazy" :alt="name" decoding="async" class="h-32 rounded-full" />
</div>
</template>

<div class="flex flex-col gap-2">
<h2 class="text-primary-600 dark:text-primary-400 text-2xl font-bold">{{ name }}</h2>
<p class="text-gray-500 dark:text-gray-300">{{ description }}</p>
<p class="text-gray-500 dark:text-gray-300">{{ lang.get(langID)?.get(description) }}</p>
</div>
</UCard>
</template>
2 changes: 1 addition & 1 deletion components/easter-egg/confetti.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import confetti from "canvas-confetti";

onMounted(() => {
// Based on https://www.kirilv.com/canvas-confetti/ examples
const confettiCanvas = document.getElementById("confetti");
const confettiCanvas = document.getElementById("confetti") as HTMLCanvasElement;
const customConfetti = confetti.create(confettiCanvas, { resize: true, disableForReducedMotion: true });
const end = Date.now() + 5 * 1000;
const colors = ["#008d44", "#ffffff", "#d0323d"];
Expand Down
4 changes: 3 additions & 1 deletion components/easter-egg/sun.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
const appConfig = useAppConfig();
const colorMode = useColorMode();
const primary = computed(() => appConfig.ui.primary);
const lang = useState("lang");
const langID = useState("lang_id");

const getColorPreference = (preference: string) => {
switch (preference) {
Expand Down Expand Up @@ -33,6 +35,6 @@ const gradient = computed((gradient) => {
<template>
<NuxtLink to="/map" class="h-48 w-48 text-center transition-all duration-300 hover:scale-105">
<div class="fixed h-48 w-48 animate-[sunfire_4s_infinite_alternate] rounded-full bg-gradient-radial" :style="gradient"></div>
<div class="relative top-1/2 -mt-2">Start exploring!</div>
<div class="relative top-1/2 -mt-2">{{ lang.get(langID)?.get("home.explore") }}</div>
</NuxtLink>
</template>
2 changes: 0 additions & 2 deletions components/kudo/sidebar.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
<script setup lang="ts"></script>

<template>
<UiSlideOver state="kudo-slideover">
<div class="flex flex-col gap-4">
Expand Down
33 changes: 24 additions & 9 deletions components/ui/Footer.vue
Original file line number Diff line number Diff line change
@@ -1,30 +1,45 @@
<script setup lang="ts">
import { Lang, LangIDs } from "~/utils/i18n";

const langID = useState<LangIDs>("lang_id");
const lang = useState<Lang>("lang");
</script>

<template>
<footer class="flex w-full flex-row flex-wrap items-center justify-center gap-4 p-2 lg:justify-between lg:p-4">
<div class="flex flex-col items-center text-center lg:items-start lg:text-start">
<p class="text-gray-600 dark:text-gray-300">
Made with <span class="animate-pulse">❤️</span> by the
{{ lang.get(langID)?.get("footer.made") }} <span class="animate-pulse">❤️</span>
{{ lang.get(langID)?.get("footer.made.by") }}
<a
href="https://github.com/Tougrel/pestoverse"
class="text-primary-700 dark:text-primary-400 before:from-primary-300 relative font-bold before:absolute before:top-5 before:h-0.5 before:w-0 before:rounded-lg before:bg-gradient-to-r before:to-pink-300 before:transition-[width] before:duration-300 before:hover:w-full"
>
pesto community </a
{{ lang.get(langID)?.get("footer.made.community") }}</a
>!
</p>
<p class="text-gray-700 dark:text-gray-300">
Some of the work is done by some amazing pesties that you can find in the
{{ lang.get(langID)?.get("footer.credits") }}
<NuxtLink
to="/credits"
class="text-primary-700 dark:text-primary-400 hover:text-primary-800 hover:dark:text-primary-500 before:from-primary-300 relative before:absolute before:top-5 before:h-0.5 before:w-0 before:rounded-lg before:bg-gradient-to-r before:to-pink-300 before:transition-[width] before:duration-300 before:hover:w-full"
>
Credits
{{ lang.get(langID)?.get("nav.menu.credits") }}
</NuxtLink>
page!
{{ lang.get(langID)?.get("footer.credits.page") }}!
</p>
</div>
<div class="flex flex-row items-center justify-between gap-2">
<a href="https://github.com/Tougrel/Pestoverse" target="_blank" class="hover:text-primary-700 dark:hover:text-primary-500 transition-colors">
<Icon :name="ICONS.GITHUB" size="2em" />
</a>
<div class="flex flex-row items-center justify-between gap-4">
<UTooltip text="Github">
<a href="https://github.com/Tougrel/Pestoverse" target="_blank" class="hover:text-primary-700 dark:hover:text-primary-500 transition-colors">
<Icon :name="ICONS.GITHUB" size="2em" />
</a>
</UTooltip>
<UTooltip text="Crowdin">
<a href="https://crowdin.com/project/pestoverse" target="_blank" class="hover:text-primary-700 dark:hover:text-primary-500 transition-colors">
<Icon :name="ICONS.CROWDIN" size="2em" />
</a>
</UTooltip>
</div>
</footer>
</template>
1 change: 1 addition & 0 deletions components/ui/Image.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const imageLoaded = ref(false);
loading="lazy"
decoding="async"
:src="getResizedImage(src)"
:alt="name"
:width="width"
:height="height"
@load="imageLoaded = true"
Expand Down
30 changes: 30 additions & 0 deletions components/ui/LangPicker.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<script setup lang="ts">
import type { LangIDs } from "~/utils/i18n";
import { changeLangID } from "~/utils/i18n";

const langID = useState<LangIDs>("lang_id");
const modal = ref(false);

defineProps<{ size: string; buttonClass?: string }>();
</script>

<template>
<button class="hover:text-primary-700 hover:dark:text-primary-400 flex items-center gap-2 transition-colors" :class="buttonClass" @click="modal = !modal">
<Icon :name="ICONS.LANGUAGE" :size="size" class="text-primary-700 dark:text-primary-400" />
</button>

<UModal v-model="modal">
<div class="flex flex-row flex-wrap items-center gap-2 p-2">
<button
v-for="lang in LangData"
class="flex flex-row items-center gap-1 rounded-lg px-1"
:class="{ 'bg-gray-800': lang.id === langID }"
@click="changeLangID(lang.id)"
>
<UTooltip :text="lang.name">
<Icon :name="lang.flag" size="3em" />
</UTooltip>
</button>
</div>
</UModal>
</template>
2 changes: 1 addition & 1 deletion components/ui/LoadingScreen.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<div class="fixed inset-0 z-500 flex items-center justify-center bg-white dark:bg-navigation">
<img src="/static/images/emotes/dance.gif" decoding="async" loading="lazy" :preload="true" class="bg-cover bg-repeat-x" />
<img src="/static/images/emotes/dance.gif" alt="Pesto Dance" decoding="async" loading="lazy" preload class="bg-cover bg-repeat-x" />
</div>
</template>
28 changes: 18 additions & 10 deletions components/ui/NavMenu.vue
Original file line number Diff line number Diff line change
@@ -1,33 +1,41 @@
<script setup lang="ts">
const props = defineProps<{ buttonClass?: string; map?: boolean }>();
import type { Lang, LangIDs } from "~/utils/i18n";

const route = useRoute();
const langID = useState<LangIDs>("lang_id");
const lang = useState<Lang>("lang");

let iconSize = "2em";
defineProps<{ buttonClass?: string; map?: boolean }>();
</script>

<template>
<header class="z-10 flex flex-col items-center justify-center gap-4 lg:flex-row lg:justify-between" :class="{ 'w-full': !map }">
<h1 v-if="!map" class="text-primary-700 dark:text-primary-400 order-none hidden text-2xl font-bold lg:block">Pestoverse</h1>
<h1 v-if="!map" class="text-primary-700 dark:text-primary-400 order-none hidden text-2xl font-bold lg:block">
{{ lang.get(langID)?.get("pestoverse") }}
</h1>

<div v-if="!map" class="order-2 flex flex-row items-center gap-2 lg:order-1">
<img v-if="PAGES[route.path as keyof typeof PAGES].image" :src="PAGES[route.path as keyof typeof PAGES].icon as string" width="24" />
<Icon v-else :name="ICONS.COFFEE" size="2em" class="text-primary-700 dark:text-primary-400" />
<p class="text-primary-700 dark:text-primary-400 text-xl font-medium">{{ PAGES[route.path as keyof typeof PAGES].name }}</p>
<img v-if="PAGES(lang, langID)[route.path?.toString()].image" :src="PAGES(lang, langID)[route.path].icon as string" width="24" />
<Icon v-else :name="PAGES(lang, langID)[route.path].icon as string" size="2em" class="text-primary-700 dark:text-primary-400" />
<p class="text-primary-700 dark:text-primary-400 text-xl font-medium">
{{ PAGES(lang, langID)[route.path].name }}
</p>
</div>

<div class="order-1 flex flex-row items-center gap-4 lg:order-2">
<UDropdown :items="NAV_MENU" :popper="{ placement: 'bottom' }">
<UDropdown :items="NAV_MENU(lang, langID)" :popper="{ placement: 'bottom' }">
<nav
class="hover:text-primary-700 hover:dark:text-primary-400 inline-flex items-center gap-2 transition-colors"
:class="buttonClass"
role="button"
>
<Icon :name="ICONS.NAV_MENU" :size="iconSize" class="text-primary-700 dark:text-primary-400" />
<span class="font-medium">Menu</span>
<Icon :name="ICONS.NAV_MENU" size="2em" class="text-primary-700 dark:text-primary-400" />
<span class="font-medium">{{ lang.get(langID)?.get("nav.menu") }}</span>
</nav>
</UDropdown>

<UiColorPicker :size="iconSize" :button-class="buttonClass" />
<UiColorPicker size="2em" :button-class="buttonClass" />
<UiLangPicker size="2em" :button-class="buttonClass" />
</div>
</header>
</template>
6 changes: 4 additions & 2 deletions components/ui/SlideOver.vue
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
<script setup lang="ts">
const props = defineProps<{ state: string }>();
const isOpen = useState<boolean>(props.state);
const lang = useState("lang");
const langID = useState("lang_id");
</script>

<template>
<USlideover v-model="isOpen">
<div class="flex h-full flex-col justify-between gap-4 overflow-scroll p-4">
<slot />
<button class="rounded-lg bg-red-700 p-2 text-lg font-bold uppercase dark:bg-red-500 dark:text-white lg:hidden" @click="isOpen = false">
Close
{{ lang.get(langID)?.get("close") }}
</button>
</div>
</USlideover>
</template>
</template>
Loading