wip: ESLint Rule against loss of reactivity + prep for string-only locale files

This commit is contained in:
Benjamin Singleton 2026-02-21 18:45:32 -06:00
parent d2020cafec
commit 3c26238ca4
7 changed files with 251 additions and 223 deletions

View file

@ -2,23 +2,46 @@
import HomeSectionWrapper from "@/components/molecules/HomeSectionWrapper.vue";
import { useLocale } from "@/i18n";
import { GREETINGS, type Greeting } from "@/i18n/greeting";
import type { Locale } from "@/i18n/locale";
import type * as i18n from "@/i18n/locale";
import { VILANTIC_ID_TO_FLAG } from "@/i18n/vilantic";
import { randomElement } from "@/utils/random";
import { computed } from "vue";
import flakkaImg from "@/assets/flakka.png";
interface SectionConfig {
id: keyof i18n.Locale["home"]["sections"];
image?: keyof typeof imagesI18n.value;
}
const SECTION_CONFIGS = [
{ id: "whatIsViossa", image: "viossaFlag" },
{ id: "historyOfViossa", image: "viossaFlag" },
{ id: "community" },
] as const satisfies SectionConfig[];
const locale = useLocale();
const homeI18n = computed(() => locale.value.home);
interface ImageI18n {
src: string;
metadata: i18n.Image;
}
const imagesI18n = computed(() => {
const imagesI18n = homeI18n.value.images;
return {
viossaFlag: { src: flakkaImg, metadata: imagesI18n.viossaFlag },
} as const satisfies Record<string, ImageI18n>;
});
const greeting: Greeting = randomElement(GREETINGS);
const SECTION_ORDER = [
"whatIsViossa",
"historyOfViossa",
"community",
] as const satisfies (keyof Locale["home"]["sections"])[];
const sections = computed(() =>
SECTION_ORDER.map((id) => locale.value.home.sections[id]),
const sectionsI18n = computed(() =>
SECTION_CONFIGS.map(({ id, image }: SectionConfig) => ({
text: homeI18n.value.sections[id],
image: image && imagesI18n.value[image],
})),
);
</script>
@ -46,12 +69,12 @@ const sections = computed(() =>
<section class="section container">
<HomeSectionWrapper
v-for="(section, index) in sections"
v-for="(sectioni18n, index) in sectionsI18n"
:key="index"
:title="section.title"
:text="section.text"
:image="section.image?.src"
:alt="section.image?.alt"
:title="sectioni18n.text.title"
:text="sectioni18n.text.body"
:image="sectioni18n.image?.src"
:alt="sectioni18n.image?.metadata.alt"
:reverse="index % 2 !== 0" />
</section>
</div>

View file

@ -3,30 +3,56 @@ import LearningResourceWrapper, {
type ResourceButton,
} from "@/components/molecules/LearningResourceWrapper.vue";
import { useLocale, type CompileLocale } from "@/i18n";
import type { Locale } from "@/i18n/locale";
import type * as i18n from "@/i18n/locale";
import { ignore } from "@/utils/ignore";
import { computed } from "vue";
import discordImg from "@/assets/discord.png";
interface ResourceConfig {
id: keyof i18n.Locale["resources"]["resources"];
image?: keyof typeof imagesI18n.value;
}
const RESOURCE_CONFIGS = [
{ id: "discord", image: "discordLogo" },
] as const satisfies ResourceConfig[];
const locale = useLocale();
const resourceIdToResource = computed(() => locale.value.resources.resources);
const pageI18n = computed(() => locale.value.resources);
const RESOURCE_ORDER = [
"discord",
] as const satisfies (keyof Locale["resources"]["resources"])[];
interface ImageI18n {
src: string;
metadata: i18n.Image;
}
const resources = computed(() =>
RESOURCE_ORDER.map((id) => [id, resourceIdToResource.value[id]] as const),
const imagesI18n = computed(() => {
const imagesI18n = pageI18n.value.images;
return {
discordLogo: { src: discordImg, metadata: imagesI18n.discordLogo },
} as const satisfies Record<string, ImageI18n>;
});
const resourcesI18n = computed(() =>
RESOURCE_CONFIGS.map(
({ id, image }: ResourceConfig) =>
({
id,
text: pageI18n.value.resources[id],
image: image && imagesI18n.value[image],
}) as const,
),
);
const computeButtons = (
id: keyof CompileLocale<Locale>["resources"]["resources"],
id: keyof CompileLocale<i18n.Locale>["resources"]["resources"],
): ResourceButton[] => {
// will warn us if a new variant is added that isn't handled, and so we should add a switch
// once we have a switch statement, this won't be needed as that will check for exhaustiveness
ignore<"discord">(id);
const buttons = resourceIdToResource.value[id].buttons;
const buttons = pageI18n.value.resources[id].buttons;
return [
{
link: {
@ -58,13 +84,18 @@ const computeButtons = (
<section class="section container">
<LearningResourceWrapper
v-for="([resourceId, resource], index) in resources"
v-for="(resourceI18n, index) in resourcesI18n"
:key="index"
:title="resource.title"
:subtitle="resource.subtitle"
:desc="resource.desc"
:image="resource.image ?? undefined"
:buttons="computeButtons(resourceId)" />
:title="resourceI18n.text.title"
:subtitle="resourceI18n.text.subtitle"
:desc="resourceI18n.text.desc"
:image="
resourceI18n.image && {
src: resourceI18n.image.src,
alt: resourceI18n.image.metadata.alt,
}
"
:buttons="computeButtons(resourceI18n.id)" />
</section>
</div>
</template>