Merge pull request #32 from ViossaDiskordServer/i18n-v2
New i18n system
This commit is contained in:
commit
465a114636
13 changed files with 294 additions and 132 deletions
|
|
@ -11,6 +11,8 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tailwindcss/vite": "^4.1.6",
|
"@tailwindcss/vite": "^4.1.6",
|
||||||
"@types/node": "^22.15.17",
|
"@types/node": "^22.15.17",
|
||||||
|
"@vueuse/components": "^13.3.0",
|
||||||
|
"@vueuse/core": "^13.3.0",
|
||||||
"bulma": "^1.0.4",
|
"bulma": "^1.0.4",
|
||||||
"tailwindcss": "^4.1.6",
|
"tailwindcss": "^4.1.6",
|
||||||
"vue": "^3.5.13",
|
"vue": "^3.5.13",
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,22 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import "./assets/style.scss";
|
import "./assets/style.scss";
|
||||||
import { ref, type Ref } from "vue";
|
import { ref, type Ref } from "vue";
|
||||||
import { SAMPLE } from "@repo/common/sample";
|
import LocalePicker from "./components/organisms/LocalePicker.vue";
|
||||||
|
import { vOnClickOutside } from "@vueuse/components";
|
||||||
|
|
||||||
const burgerOpen: Ref<boolean> = ref<boolean>(false);
|
const burgerOpen: Ref<boolean> = ref<boolean>(false);
|
||||||
|
|
||||||
const toggleBurger = (): void => {
|
const toggleBurger = (): void => {
|
||||||
burgerOpen.value = !burgerOpen.value;
|
burgerOpen.value = !burgerOpen.value;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const closeBurger = (): void => {
|
||||||
|
burgerOpen.value = false;
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="min-h-screen flex flex-col">
|
<div class="min-h-screen flex flex-col" v-on-click-outside="closeBurger">
|
||||||
<!-- Main application wrapper -->
|
<!-- Main application wrapper -->
|
||||||
<nav
|
<nav
|
||||||
class="navbar is-fixed-top"
|
class="navbar is-fixed-top"
|
||||||
|
|
@ -36,15 +41,19 @@ const toggleBurger = (): void => {
|
||||||
|
|
||||||
<div :class="`navbar-menu ${burgerOpen ? 'is-active' : ''}`">
|
<div :class="`navbar-menu ${burgerOpen ? 'is-active' : ''}`">
|
||||||
<div class="navbar-start">
|
<div class="navbar-start">
|
||||||
<RouterLink class="navbar-item" to="/"
|
<RouterLink
|
||||||
|
class="navbar-item"
|
||||||
|
to="/"
|
||||||
|
@click="closeBurger()"
|
||||||
>What is Viossa?</RouterLink
|
>What is Viossa?</RouterLink
|
||||||
>
|
>
|
||||||
<RouterLink class="navbar-item" to="/resources"
|
<RouterLink
|
||||||
|
class="navbar-item"
|
||||||
|
to="/resources"
|
||||||
|
@click="closeBurger()"
|
||||||
>Resources</RouterLink
|
>Resources</RouterLink
|
||||||
>
|
>
|
||||||
<RouterLink class="navbar-item" to="/resources">{{
|
<LocalePicker />
|
||||||
SAMPLE
|
|
||||||
}}</RouterLink>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
|
||||||
51
apps/vdn-static/src/components/organisms/LocalePicker.vue
Normal file
51
apps/vdn-static/src/components/organisms/LocalePicker.vue
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { LOCALE_IDS, localeId, useLocale, type LocaleId } from "@/i18n";
|
||||||
|
import { ref } from "vue";
|
||||||
|
import { vOnClickOutside } from "@vueuse/components";
|
||||||
|
|
||||||
|
const isOpen = ref<boolean>(false);
|
||||||
|
|
||||||
|
const toggleOpen = (): void => {
|
||||||
|
isOpen.value = !isOpen.value;
|
||||||
|
};
|
||||||
|
|
||||||
|
const close = (): void => {
|
||||||
|
isOpen.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const setLocaleId = (id: LocaleId): void => {
|
||||||
|
localeId.value = id;
|
||||||
|
close();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
:class="['dropdown', isOpen && 'is-active']"
|
||||||
|
v-on-click-outside="close">
|
||||||
|
<div class="dropdown-trigger">
|
||||||
|
<button
|
||||||
|
class="button"
|
||||||
|
aria-haspopup="true"
|
||||||
|
aria-controls="dropdown-menu"
|
||||||
|
@click="toggleOpen()">
|
||||||
|
<span>{{ useLocale().value.localeName }}</span>
|
||||||
|
<span class="icon is-small">
|
||||||
|
<i class="fas fa-angle-down" aria-hidden="true"></i>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="dropdown-menu" id="dropdown-menu" role="menu">
|
||||||
|
<div class="dropdown-content">
|
||||||
|
<a
|
||||||
|
v-for="(id, index) in LOCALE_IDS"
|
||||||
|
:key="index"
|
||||||
|
href="#"
|
||||||
|
:class="['dropdown-item', localeId === id && 'is-active']"
|
||||||
|
@click="setLocaleId(id)">
|
||||||
|
{{ useLocale({ locale: id }).value.localeName }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -1,25 +1,9 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import HomeSectionWrapper from "@/components/molecules/HomeSectionWrapper.vue";
|
import HomeSectionWrapper from "@/components/molecules/HomeSectionWrapper.vue";
|
||||||
import "@/assets/style.scss";
|
import { useLocale } from "@/i18n";
|
||||||
import { useI18n } from "vue-i18n";
|
import { localizeLayout } from "@/utils/localizeLayout";
|
||||||
import type { MessageSchema } from "@/i18n/types";
|
|
||||||
import { computed } from "vue";
|
|
||||||
|
|
||||||
const { tm } = useI18n();
|
const locale = useLocale();
|
||||||
const sectionList = computed<MessageSchema["sections"]>(() => tm("sections"));
|
|
||||||
const sectionsWithImages = computed(() =>
|
|
||||||
sectionList.value.map((section) => {
|
|
||||||
if (!section.image) return section;
|
|
||||||
|
|
||||||
return {
|
|
||||||
...section,
|
|
||||||
image: new URL(`../../assets/${section.image}`, import.meta.url)
|
|
||||||
.href,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
console.log(sectionList.value);
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -35,12 +19,12 @@ console.log(sectionList.value);
|
||||||
|
|
||||||
<section class="section container">
|
<section class="section container">
|
||||||
<HomeSectionWrapper
|
<HomeSectionWrapper
|
||||||
v-for="(section, index) in sectionsWithImages"
|
v-for="(section, index) in localizeLayout(locale.home)"
|
||||||
:key="index"
|
:key="index"
|
||||||
:title="section.title"
|
:title="section.title"
|
||||||
:text="section.text"
|
:text="section.text"
|
||||||
:image="section.image"
|
:image="section.image ?? undefined"
|
||||||
:alt="section.alt"
|
:alt="section.alt ?? undefined"
|
||||||
:reverse="index % 2 !== 0" />
|
:reverse="index % 2 !== 0" />
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,9 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import LearningResourceWrapper from "@/components/molecules/LearningResourceWrapper.vue";
|
import LearningResourceWrapper from "@/components/molecules/LearningResourceWrapper.vue";
|
||||||
import "@/assets/style.scss";
|
import { useLocale } from "@/i18n";
|
||||||
import { useI18n } from "vue-i18n";
|
import { localizeLayout } from "@/utils/localizeLayout";
|
||||||
import type { MessageSchema } from "@/i18n/types";
|
|
||||||
import { computed } from "vue";
|
|
||||||
|
|
||||||
const { tm } = useI18n();
|
const locale = useLocale();
|
||||||
const resourceList = computed<MessageSchema["resources"]>(() =>
|
|
||||||
tm("resources"),
|
|
||||||
);
|
|
||||||
const resourcesWithImages = computed(() =>
|
|
||||||
resourceList.value.map((resource) => {
|
|
||||||
if (!resource.image) return resource;
|
|
||||||
|
|
||||||
return {
|
|
||||||
...resource,
|
|
||||||
image: new URL(`../../assets/${resource.image}`, import.meta.url)
|
|
||||||
.href,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -30,7 +14,7 @@ const resourcesWithImages = computed(() =>
|
||||||
|
|
||||||
<section class="section container">
|
<section class="section container">
|
||||||
<LearningResourceWrapper
|
<LearningResourceWrapper
|
||||||
v-for="(resource, index) in resourcesWithImages"
|
v-for="(resource, index) in localizeLayout(locale.resources)"
|
||||||
:key="index"
|
:key="index"
|
||||||
:title="resource.title"
|
:title="resource.title"
|
||||||
:subtitle="resource.subtitle"
|
:subtitle="resource.subtitle"
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,56 @@
|
||||||
import { createI18n } from "vue-i18n";
|
|
||||||
import en_US from "../locales/en_US";
|
import en_US from "../locales/en_US";
|
||||||
import vp_VL from "../locales/vp_VL";
|
import vp_VL from "../locales/vp_VL";
|
||||||
import type { MessageSchema } from "./types";
|
import { computed, readonly, ref } from "vue";
|
||||||
|
import type { Locale } from "./locale";
|
||||||
|
|
||||||
const locales = { en_US, vp_VL } as const;
|
export const LOCALE_IDS = ["en_US", "vp_VL"] as const;
|
||||||
|
export type LocaleId = (typeof LOCALE_IDS)[number];
|
||||||
|
|
||||||
export type Locale = keyof typeof locales;
|
const locales = { en_US, vp_VL } as const satisfies Record<LocaleId, Locale>;
|
||||||
|
|
||||||
const i18n = createI18n<[MessageSchema], Locale>({
|
export const localeId = ref<LocaleId>("en_US");
|
||||||
legacy: false,
|
|
||||||
locale: "en_US" satisfies Locale,
|
|
||||||
fallbackLocale: "en_US" satisfies Locale,
|
|
||||||
messages: locales,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default i18n;
|
export function useLocale(opt: UseLocaleOptions = {}) {
|
||||||
|
const locale = computed<Locale>(() => {
|
||||||
|
return fallbackProxy(
|
||||||
|
locales[opt.locale ?? localeId.value],
|
||||||
|
locales["en_US"],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return readonly(locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UseLocaleOptions {
|
||||||
|
locale?: LocaleId;
|
||||||
|
}
|
||||||
|
|
||||||
|
function fallbackProxy(obj: any, fallback: any) {
|
||||||
|
return new Proxy(obj, {
|
||||||
|
get: (target, key) => {
|
||||||
|
const value = target[key];
|
||||||
|
const fallbackValue = fallback[key];
|
||||||
|
if (value === undefined) {
|
||||||
|
return fallbackValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value === null && fallbackValue !== null) {
|
||||||
|
return fallbackValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
value === null
|
||||||
|
|| fallbackValue === null
|
||||||
|
|| typeof value !== "object"
|
||||||
|
|| typeof fallbackValue !== "object"
|
||||||
|
) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return fallbackProxy(value, fallbackValue);
|
||||||
|
},
|
||||||
|
set: () => {
|
||||||
|
throw new Error("Cannot mutate locale at runtime");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
||||||
39
apps/vdn-static/src/i18n/locale.ts
Normal file
39
apps/vdn-static/src/i18n/locale.ts
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
export interface Locale {
|
||||||
|
localeName: string;
|
||||||
|
home: Layout<HomeSections>;
|
||||||
|
resources: Layout<Resources>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Layout<T> {
|
||||||
|
layout: (keyof T)[] | null;
|
||||||
|
data: { [K in keyof T]: T[K] | null };
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HomeSections {
|
||||||
|
whatIsViossa: HomeSection;
|
||||||
|
historyOfViossa: HomeSection;
|
||||||
|
community: HomeSection;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HomeSection {
|
||||||
|
title: string;
|
||||||
|
text: string;
|
||||||
|
image: string | null;
|
||||||
|
alt: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Resources {
|
||||||
|
discord: Resource;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Resource {
|
||||||
|
title: string;
|
||||||
|
subtitle: string;
|
||||||
|
desc: string;
|
||||||
|
link: string;
|
||||||
|
rulesLink: string;
|
||||||
|
image: string;
|
||||||
|
alt: string;
|
||||||
|
joinText: string;
|
||||||
|
rulesText: string;
|
||||||
|
}
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
import en_US from "../locales/en_US";
|
|
||||||
export interface MessageSchema extends Broaden<typeof en_US> {}
|
|
||||||
|
|
||||||
type Broaden<T> = {
|
|
||||||
[K in keyof T]: T[K] extends object ? Broaden<T[K]>
|
|
||||||
: T[K] extends string ? string
|
|
||||||
: T[K];
|
|
||||||
} & {};
|
|
||||||
|
|
@ -1,33 +1,47 @@
|
||||||
|
import type { Locale } from "@/i18n/locale";
|
||||||
|
import flakkaImg from "@/assets/flakka.png";
|
||||||
|
import discordImg from "@/assets/discord.png";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
sections: [
|
localeName: "English",
|
||||||
{
|
home: {
|
||||||
title: "What is Viossa?",
|
layout: ["whatIsViossa", "historyOfViossa", "community"],
|
||||||
text: "Viossa is a community-created artificial pidgin language, created to simulate the formation of natural pidgin languages. Viossa is characterized by its lack of standardization, with each speaker developing a personal idiolect. Spelling and pronunciation can vary greatly, and serve as a form of personal self-expression. Viossa is learnt and taught entirely by immersion — translation is prohibited while learning.",
|
data: {
|
||||||
image: "flakka.png",
|
whatIsViossa: {
|
||||||
alt: "Flag of the Viossa Language",
|
title: "What is Viossa?",
|
||||||
|
text: "Viossa is a community-created artificial pidgin language, created to simulate the formation of natural pidgin languages. Viossa is characterized by its lack of standardization, with each speaker developing a personal idiolect. Spelling and pronunciation can vary greatly, and serve as a form of personal self-expression. Viossa is learnt and taught entirely by immersion — translation is prohibited while learning.",
|
||||||
|
image: flakkaImg,
|
||||||
|
alt: "Flag of the Viossa Language",
|
||||||
|
},
|
||||||
|
historyOfViossa: {
|
||||||
|
title: "History of Viossa",
|
||||||
|
text: "Viossa began as a Skype group in 2014, created by members of the r/conlangs community on Reddit, as an experiment to simulate the formation of a pidgin language. Pidgins are simplified languages resulting from contact between populations with no shared common language. Unlike most pidgins, which usually have two to three contributor languages, Viossa comes from many diverse languages. This is because people from all around the world helped to contribute to Viossa's vocabulary.",
|
||||||
|
image: flakkaImg,
|
||||||
|
alt: "Flag of the Viossa Language",
|
||||||
|
},
|
||||||
|
community: {
|
||||||
|
title: "Community",
|
||||||
|
text: "The Viossa community is rich and colourful, drawing from many global traditions due to its worldwide online membership. Since the teaching culture puts an emphasis on linguistic immersion, and discourages prescriptivism, the culture of Viossa is as diverse and varied as the language and the people who speak it. For many, their personal dialect is a key form of identity and expression. The fluid nature of Viossa and lack of defined meanings makes Viossa popular for creative purposes, such as poetry and songwriting.",
|
||||||
|
image: null,
|
||||||
|
alt: null,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
},
|
||||||
title: "History of Viossa",
|
resources: {
|
||||||
text: "Viossa began as a Skype group in 2014, created by members of the r/conlangs community on Reddit, as an experiment to simulate the formation of a pidgin language. Pidgins are simplified languages resulting from contact between populations with no shared common language. Unlike most pidgins, which usually have two to three contributor languages, Viossa comes from many diverse languages. This is because people from all around the world helped to contribute to Viossa's vocabulary.",
|
layout: ["discord"],
|
||||||
image: "flakka.png",
|
data: {
|
||||||
alt: "Flag of the Viossa Language",
|
discord: {
|
||||||
|
title: "Discord Server",
|
||||||
|
subtitle:
|
||||||
|
"This is where most of the action happens! Hop on in!",
|
||||||
|
desc: "Originally started in 2015 something something read the rules here, then click the link below to join!",
|
||||||
|
link: "https://discord.gg/g3mG2gYjZD",
|
||||||
|
rulesLink: "https://viossadiskordserver.github.io/rules",
|
||||||
|
image: discordImg,
|
||||||
|
alt: "Discord logo",
|
||||||
|
joinText: "Join",
|
||||||
|
rulesText: "Rules",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
},
|
||||||
title: "Community",
|
} as const satisfies Locale;
|
||||||
text: "The Viossa community is rich and colourful, drawing from many global traditions due to its worldwide online membership. Since the teaching culture puts an emphasis on linguistic immersion, and discourages prescriptivism, the culture of Viossa is as diverse and varied as the language and the people who speak it. For many, their personal dialect is a key form of identity and expression. The fluid nature of Viossa and lack of defined meanings makes Viossa popular for creative purposes, such as poetry and songwriting.",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
resources: [
|
|
||||||
{
|
|
||||||
title: "Discord Server",
|
|
||||||
subtitle: "This is where most of the action happens! Hop on in!",
|
|
||||||
desc: "Originally started in 2015 something something read the rules here, then click the link below to join!",
|
|
||||||
link: "https://discord.gg/g3mG2gYjZD",
|
|
||||||
rulesLink: "https://viossadiskordserver.github.io/rules",
|
|
||||||
image: "discord.png",
|
|
||||||
alt: "Discord logo",
|
|
||||||
joinText: "Join",
|
|
||||||
rulesText: "Rules",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
|
||||||
|
|
@ -1,35 +1,20 @@
|
||||||
import type { MessageSchema } from "@/i18n/types";
|
import type { Locale } from "@/i18n/locale";
|
||||||
|
import flakkaImg from "@/assets/flakka.png";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
sections: [
|
localeName: "Viossa",
|
||||||
{
|
home: {
|
||||||
title: "Kafaen afto Viossa",
|
layout: null,
|
||||||
text: "Viossa tte glossa mahena grun vi nai vil fshtojena na bakadjin, grun vi svinnur ja! De aldjin zovti lera ne",
|
data: {
|
||||||
image: "flakka.png",
|
whatIsViossa: {
|
||||||
alt: "Flag of the Viossa Language",
|
title: "Kafaen afto Viossa",
|
||||||
|
text: "Viossa tte glossa mahena grun vi nai vil fshtojena na bakadjin, grun vi svinnur ja! De aldjin zovti lera ne",
|
||||||
|
image: flakkaImg,
|
||||||
|
alt: "Flag of the Viossa Language",
|
||||||
|
},
|
||||||
|
historyOfViossa: null,
|
||||||
|
community: null,
|
||||||
},
|
},
|
||||||
{
|
},
|
||||||
title: "History of Viossa",
|
resources: { layout: null, data: { discord: null } },
|
||||||
text: "Viossa began as a Skype group in 2014, created by members of the r/conlangs community on Reddit, as an experiment to simulate the formation of a pidgin language. Pidgins are simplified languages resulting from contact between populations with no shared common language. Unlike most pidgins, which usually have two to three contributor languages, Viossa comes from many diverse languages. This is because people from all around the world helped to contribute to Viossa's vocabulary.",
|
} as const satisfies Locale;
|
||||||
image: "flakka.png",
|
|
||||||
alt: "Flag of the Viossa Language",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Community",
|
|
||||||
text: "The Viossa community is rich and colourful, drawing from many global traditions due to its worldwide online membership. Since the teaching culture puts an emphasis on linguistic immersion, and discourages prescriptivism, the culture of Viossa is as diverse and varied as the language and the people who speak it. For many, their personal dialect is a key form of identity and expression. The fluid nature of Viossa and lack of defined meanings makes Viossa popular for creative purposes, such as poetry and songwriting.",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
resources: [
|
|
||||||
{
|
|
||||||
title: "Discord Server",
|
|
||||||
subtitle: "This is where most of the action happens! Hop on in!",
|
|
||||||
desc: "Originally started in 2015 something something read the rules here, then click the link below to join!",
|
|
||||||
link: "https://discord.gg/g3mG2gYjZD",
|
|
||||||
rulesLink: "https://viossadiskordserver.github.io/rules",
|
|
||||||
image: "discord.png",
|
|
||||||
alt: "Discord logo",
|
|
||||||
joinText: "Join",
|
|
||||||
rulesText: "Rules",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
} as const satisfies MessageSchema;
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import { createApp } from "vue";
|
import { createApp } from "vue";
|
||||||
import App from "./App.vue";
|
import App from "./App.vue";
|
||||||
import router from "./routes";
|
import router from "./routes";
|
||||||
import i18n from "./i18n";
|
|
||||||
|
|
||||||
createApp(App).use(i18n).use(router).mount("#app");
|
createApp(App).use(router).mount("#app");
|
||||||
|
|
|
||||||
16
apps/vdn-static/src/utils/localizeLayout.ts
Normal file
16
apps/vdn-static/src/utils/localizeLayout.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
import type { Layout } from "@/i18n/locale";
|
||||||
|
import type { DeepReadonly } from "vue";
|
||||||
|
|
||||||
|
export function localizeLayout<T>(
|
||||||
|
layout: DeepReadonly<Layout<T>>,
|
||||||
|
): T[keyof T][] {
|
||||||
|
const sections: T[keyof T][] = [];
|
||||||
|
for (const sectionId of layout.layout ?? []) {
|
||||||
|
const section = (layout.data as T)[sectionId as keyof T];
|
||||||
|
if (section) {
|
||||||
|
sections.push(section);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sections;
|
||||||
|
}
|
||||||
48
pnpm-lock.yaml
generated
48
pnpm-lock.yaml
generated
|
|
@ -36,6 +36,12 @@ importers:
|
||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: ^22.15.17
|
specifier: ^22.15.17
|
||||||
version: 22.15.31
|
version: 22.15.31
|
||||||
|
'@vueuse/components':
|
||||||
|
specifier: ^13.3.0
|
||||||
|
version: 13.3.0(vue@3.5.16(typescript@5.8.3))
|
||||||
|
'@vueuse/core':
|
||||||
|
specifier: ^13.3.0
|
||||||
|
version: 13.3.0(vue@3.5.16(typescript@5.8.3))
|
||||||
bulma:
|
bulma:
|
||||||
specifier: ^1.0.4
|
specifier: ^1.0.4
|
||||||
version: 1.0.4
|
version: 1.0.4
|
||||||
|
|
@ -693,6 +699,9 @@ packages:
|
||||||
'@types/serve-static@1.15.8':
|
'@types/serve-static@1.15.8':
|
||||||
resolution: {integrity: sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==}
|
resolution: {integrity: sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==}
|
||||||
|
|
||||||
|
'@types/web-bluetooth@0.0.21':
|
||||||
|
resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==}
|
||||||
|
|
||||||
'@typescript-eslint/eslint-plugin@8.34.0':
|
'@typescript-eslint/eslint-plugin@8.34.0':
|
||||||
resolution: {integrity: sha512-QXwAlHlbcAwNlEEMKQS2RCgJsgXrTJdjXT08xEgbPFa2yYQgVjBymxP5DrfrE7X7iodSzd9qBUHUycdyVJTW1w==}
|
resolution: {integrity: sha512-QXwAlHlbcAwNlEEMKQS2RCgJsgXrTJdjXT08xEgbPFa2yYQgVjBymxP5DrfrE7X7iodSzd9qBUHUycdyVJTW1w==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
|
|
@ -831,6 +840,24 @@ packages:
|
||||||
vue:
|
vue:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@vueuse/components@13.3.0':
|
||||||
|
resolution: {integrity: sha512-ZnJiVknPtlWyeE4qwIXkDOlHM3W4bgMCxgeXj1Dec/aF/+8N+yAj+7rRdRUWUnqr8uKRin368RjG1FPKsF2erA==}
|
||||||
|
peerDependencies:
|
||||||
|
vue: ^3.5.0
|
||||||
|
|
||||||
|
'@vueuse/core@13.3.0':
|
||||||
|
resolution: {integrity: sha512-uYRz5oEfebHCoRhK4moXFM3NSCd5vu2XMLOq/Riz5FdqZMy2RvBtazdtL3gEcmDyqkztDe9ZP/zymObMIbiYSg==}
|
||||||
|
peerDependencies:
|
||||||
|
vue: ^3.5.0
|
||||||
|
|
||||||
|
'@vueuse/metadata@13.3.0':
|
||||||
|
resolution: {integrity: sha512-42IzJIOYCKIb0Yjv1JfaKpx8JlCiTmtCWrPxt7Ja6Wzoq0h79+YVXmBV03N966KEmDEESTbp5R/qO3AB5BDnGw==}
|
||||||
|
|
||||||
|
'@vueuse/shared@13.3.0':
|
||||||
|
resolution: {integrity: sha512-L1QKsF0Eg9tiZSFXTgodYnu0Rsa2P0En2LuLrIs/jgrkyiDuJSsPZK+tx+wU0mMsYHUYEjNsuE41uqqkuR8VhA==}
|
||||||
|
peerDependencies:
|
||||||
|
vue: ^3.5.0
|
||||||
|
|
||||||
accepts@2.0.0:
|
accepts@2.0.0:
|
||||||
resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==}
|
resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
|
|
@ -2423,6 +2450,8 @@ snapshots:
|
||||||
'@types/node': 22.15.31
|
'@types/node': 22.15.31
|
||||||
'@types/send': 0.17.5
|
'@types/send': 0.17.5
|
||||||
|
|
||||||
|
'@types/web-bluetooth@0.0.21': {}
|
||||||
|
|
||||||
'@typescript-eslint/eslint-plugin@8.34.0(@typescript-eslint/parser@8.34.0(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3)':
|
'@typescript-eslint/eslint-plugin@8.34.0(@typescript-eslint/parser@8.34.0(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@eslint-community/regexpp': 4.12.1
|
'@eslint-community/regexpp': 4.12.1
|
||||||
|
|
@ -2622,6 +2651,25 @@ snapshots:
|
||||||
typescript: 5.8.3
|
typescript: 5.8.3
|
||||||
vue: 3.5.16(typescript@5.8.3)
|
vue: 3.5.16(typescript@5.8.3)
|
||||||
|
|
||||||
|
'@vueuse/components@13.3.0(vue@3.5.16(typescript@5.8.3))':
|
||||||
|
dependencies:
|
||||||
|
'@vueuse/core': 13.3.0(vue@3.5.16(typescript@5.8.3))
|
||||||
|
'@vueuse/shared': 13.3.0(vue@3.5.16(typescript@5.8.3))
|
||||||
|
vue: 3.5.16(typescript@5.8.3)
|
||||||
|
|
||||||
|
'@vueuse/core@13.3.0(vue@3.5.16(typescript@5.8.3))':
|
||||||
|
dependencies:
|
||||||
|
'@types/web-bluetooth': 0.0.21
|
||||||
|
'@vueuse/metadata': 13.3.0
|
||||||
|
'@vueuse/shared': 13.3.0(vue@3.5.16(typescript@5.8.3))
|
||||||
|
vue: 3.5.16(typescript@5.8.3)
|
||||||
|
|
||||||
|
'@vueuse/metadata@13.3.0': {}
|
||||||
|
|
||||||
|
'@vueuse/shared@13.3.0(vue@3.5.16(typescript@5.8.3))':
|
||||||
|
dependencies:
|
||||||
|
vue: 3.5.16(typescript@5.8.3)
|
||||||
|
|
||||||
accepts@2.0.0:
|
accepts@2.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
mime-types: 3.0.1
|
mime-types: 3.0.1
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue