mirror of
https://gitlab.com/PronounsPage/PronounsPage.git
synced 2025-08-03 11:07:00 -04:00
(refactor)(nouns) use Numerus string union to simplify types
This commit is contained in:
parent
003a6dbce6
commit
23dcc1a454
@ -3,6 +3,7 @@ import { loadGrammarTableVariantsConverter } from '~/src/data.ts';
|
||||
import type { Example, ExampleValues } from '~/src/language/examples.ts';
|
||||
import { expandVariantsForSection } from '~/src/language/grammarTables.ts';
|
||||
import type { GrammarTableDefinition, Variant, SectionDefinition } from '~/src/language/grammarTables.ts';
|
||||
import { symbolsByNumeri } from '~/src/nouns.ts';
|
||||
|
||||
const props = defineProps<{
|
||||
grammarTable: GrammarTableDefinition;
|
||||
@ -86,7 +87,9 @@ const rowHeaderCount = computed(() => {
|
||||
<template v-if="variant.numerus || variant.icon">
|
||||
<th class="pe-0">
|
||||
<Tooltip v-if="variant.name" class="text-nowrap" :text="variant.name">
|
||||
{{ variant.numerus === 'singular' ? '⋅' : '⁖' }}
|
||||
<template v-if="variant.numerus">
|
||||
{{ symbolsByNumeri[variant.numerus] }}
|
||||
</template>
|
||||
<Icon v-if="variant.icon" :v="variant.icon" />
|
||||
</Tooltip>
|
||||
</th>
|
||||
|
@ -1,12 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import { loadNounsData } from '~/src/data.ts';
|
||||
import type { NounClass, NounConvention, NounWord } from '~/src/nouns.ts';
|
||||
import { symbolsByNumeri } from '~/src/nouns.ts';
|
||||
import type { NounClass, NounConvention, NounWord, Numerus } from '~/src/nouns.ts';
|
||||
|
||||
const props = defineProps<{
|
||||
const props = withDefaults(defineProps<{
|
||||
nounClass: NounClass & { key: string };
|
||||
nounConvention: WithKey<NounConvention>;
|
||||
plural?: boolean;
|
||||
}>();
|
||||
numerus?: Numerus;
|
||||
}>(), {
|
||||
numerus: 'singular',
|
||||
});
|
||||
|
||||
const nounsData = await loadNounsData();
|
||||
|
||||
@ -38,13 +41,8 @@ const word = computed((): NounWord | undefined => {
|
||||
<template>
|
||||
<div v-if="word" class="mb-3">
|
||||
<h5 class="h6">
|
||||
<template v-if="!plural">
|
||||
⋅ <T>nouns.singular</T>
|
||||
</template>
|
||||
<template v-else>
|
||||
⁖ <T>nouns.plural</T>
|
||||
</template>
|
||||
{{ symbolsByNumeri[numerus] }} <T>nouns.{{ numerus }}</T>
|
||||
</h5>
|
||||
<NounsDeclension :word open :plural />
|
||||
<NounsDeclension :word open :numerus />
|
||||
</div>
|
||||
</template>
|
||||
|
@ -1,15 +1,23 @@
|
||||
<script setup lang="ts">
|
||||
import { availableNumeri } from '~/src/nouns.ts';
|
||||
import type { NounClass, NounConvention } from '~/src/nouns.ts';
|
||||
|
||||
defineProps<{
|
||||
nounClass: WithKey<NounClass>;
|
||||
nounConvention: WithKey<NounConvention>;
|
||||
}>();
|
||||
|
||||
const config = useConfig();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="col-6 col-md-3 ">
|
||||
<NounsClassExample :noun-class :noun-convention />
|
||||
<NounsClassExample :noun-class :noun-convention plural />
|
||||
<NounsClassExample
|
||||
v-for="numerus of availableNumeri(config)"
|
||||
:key="numerus"
|
||||
:noun-class
|
||||
:noun-convention
|
||||
:numerus
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -1,31 +1,29 @@
|
||||
<script setup lang="ts">
|
||||
import { loadNounsData } from '~/src/data.ts';
|
||||
import { capitalise } from '~/src/helpers.ts';
|
||||
import type { NounWord } from '~/src/nouns.ts';
|
||||
import type { Numerus, NounWord } from '~/src/nouns.ts';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
word: NounWord;
|
||||
plural?: boolean;
|
||||
numerus?: Numerus;
|
||||
singularOptions?: string[];
|
||||
open?: boolean;
|
||||
condense?: boolean;
|
||||
tooltip?: boolean;
|
||||
}>(), {
|
||||
plural: false,
|
||||
numerus: 'singular',
|
||||
});
|
||||
|
||||
const nounsData = await loadNounsData();
|
||||
|
||||
const numerus = computed(() => !props.plural ? 'singular' : 'plural');
|
||||
|
||||
const declensionByCase = computed((): Record<string, string[]> | undefined => {
|
||||
if (props.word.declension === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
if (typeof props.word.declension === 'string') {
|
||||
return nounsData.declensions?.[props.word.declension]?.[numerus.value];
|
||||
return nounsData.declensions?.[props.word.declension]?.[props.numerus];
|
||||
}
|
||||
return props.word.declension[numerus.value];
|
||||
return props.word.declension[props.numerus];
|
||||
});
|
||||
|
||||
const nounConvention = computed(() => {
|
||||
@ -39,7 +37,7 @@ const articles = computed(() => {
|
||||
if (nounConvention.value === undefined) {
|
||||
return {};
|
||||
}
|
||||
return Object.fromEntries(Object.entries(nounsData.classExample?.[numerus.value] ?? {})
|
||||
return Object.fromEntries(Object.entries(nounsData.classExample?.[props.numerus] ?? {})
|
||||
.map(([caseAbbreviation, article]) => {
|
||||
const resolvedArticle = article.replace(/\{([^}]+)}/, (_match, morpheme) => {
|
||||
const value = nounConvention.value?.morphemes?.[morpheme];
|
||||
|
@ -1,24 +1,26 @@
|
||||
<script setup lang="ts">
|
||||
import type { Noun } from '~/src/classes.ts';
|
||||
import type { Gender } from '~/src/nouns.ts';
|
||||
import type { Gender, Numerus } from '~/src/nouns.ts';
|
||||
|
||||
defineProps<{
|
||||
withDefaults(defineProps<{
|
||||
noun: Noun;
|
||||
gender: Gender;
|
||||
plural?: boolean;
|
||||
}>();
|
||||
numerus?: Numerus;
|
||||
}>(), {
|
||||
numerus: 'singular',
|
||||
});
|
||||
|
||||
const config = useConfig();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ul :class="[plural ? 'list-plural' : 'list-singular', 'mb-0 ps-0']">
|
||||
<li v-for="(wordRaw, i) in noun.words[gender]?.[!plural ? 'singular' : 'plural']" :key="i">
|
||||
<ul :class="[`list-${numerus}`, 'mb-0 ps-0']">
|
||||
<li v-for="(wordRaw, i) in noun.words[gender]?.[numerus]" :key="i">
|
||||
<NounsAbbreviation v-slot="{ word }" :word-raw>
|
||||
<NounsDeclension
|
||||
v-if="config.nouns.declension?.enabled"
|
||||
:word
|
||||
:plural
|
||||
:numerus
|
||||
tooltip
|
||||
/>
|
||||
<Spelling v-else :text="word.spelling" />
|
||||
|
@ -3,11 +3,11 @@ import type { Config } from '~/locale/config.ts';
|
||||
import type { Noun, NounRaw } from '~/src/classes.ts';
|
||||
import { loadNounAbbreviations } from '~/src/data.ts';
|
||||
import { fromUnionEntries } from '~/src/helpers.ts';
|
||||
import { availableGenders, genders } from '~/src/nouns.ts';
|
||||
import type { Gender, NounWord, NounWords } from '~/src/nouns.ts';
|
||||
import { availableGenders, availableNumeri, genders, symbolsByNumeri } from '~/src/nouns.ts';
|
||||
import type { Numerus, Gender, NounWord, NounWords } from '~/src/nouns.ts';
|
||||
|
||||
interface NounFormValue extends Omit<NounRaw, 'id' | 'words'> {
|
||||
words: Record<Gender, Record<'singular' | 'plural', NounWord[]>>;
|
||||
words: Record<Gender, Record<Numerus, NounWord[]>>;
|
||||
categories: NonNullable<NounRaw['categories']>;
|
||||
}
|
||||
|
||||
@ -55,11 +55,17 @@ const templateBase = ref('');
|
||||
const templateFilter = ref('');
|
||||
const templateVisible = ref(false);
|
||||
|
||||
const canRemoveWord = (gender: Gender, plural: boolean): boolean => {
|
||||
return availableGenders(config).filter((otherGender) => {
|
||||
return otherGender !== gender &&
|
||||
(form.value.words[otherGender]?.[!plural ? 'singular' : 'plural'] ?? []).length > 0;
|
||||
}).length > 1 || (form.value.words[gender]?.[!plural ? 'singular' : 'plural'] ?? []).length > 1;
|
||||
const canRemoveWord = (gender: Gender, numerus: Numerus): boolean => {
|
||||
if (numerus === 'plural' && !config.nouns.pluralsRequired) {
|
||||
return true;
|
||||
}
|
||||
const wordsOfOtherGenderAndSameNumerus = availableGenders(config).filter((otherGender) => {
|
||||
return otherGender !== gender && (form.value.words[otherGender]?.[numerus] ?? []).length > 0;
|
||||
});
|
||||
if (wordsOfOtherGenderAndSameNumerus.length > 1) {
|
||||
return true;
|
||||
}
|
||||
return (form.value.words[gender]?.[numerus] ?? []).length > 1;
|
||||
};
|
||||
|
||||
const dialogue = useDialogue();
|
||||
@ -132,29 +138,16 @@ const { data: sourcesKeys } = await useFetch('/api/sources/keys', { lazy: true,
|
||||
</p>
|
||||
</div>
|
||||
<form v-else @submit.prevent="submit">
|
||||
<div class="row">
|
||||
<div v-for="numerus of availableNumeri(config)" :key="numerus" class="row">
|
||||
<div v-if="config.nouns.plurals" class="col-12 text-nowrap mt-md-4">
|
||||
<label><strong>⋅ <T>nouns.singular</T></strong></label>
|
||||
<label><strong>{{ symbolsByNumeri[numerus] }} <T>nouns.{{ numerus }}</T></strong></label>
|
||||
</div>
|
||||
<div v-for="gender in availableGenders(config)" :key="gender" class="col-12 col-md-6 mt-2">
|
||||
<label><strong><NounsGenderLabel :gender="gender" /></strong></label>
|
||||
<NounsWordsInput
|
||||
v-model="form.words[gender].singular"
|
||||
v-model="form.words[gender][numerus]"
|
||||
:edit-declensions
|
||||
:minitems="canRemoveWord(gender, false) ? 0 : 1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="config.nouns.plurals" class="row">
|
||||
<div class="col-12 text-nowrap">
|
||||
<label><strong>⁖ <T>nouns.plural</T></strong></label>
|
||||
</div>
|
||||
<div v-for="gender in availableGenders(config)" :key="gender" class="col-12 col-md-6 mt-2">
|
||||
<label><strong><NounsGenderLabel :gender="gender" /></strong></label>
|
||||
<NounsWordsInput
|
||||
v-model="form.words[gender].plural"
|
||||
:edit-declensions
|
||||
:minitems="!config.nouns.pluralsRequired || canRemoveWord(gender, true) ? 0 : 1"
|
||||
:minitems="canRemoveWord(gender, numerus) ? 0 : 1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import type { Noun } from '~/src/classes.ts';
|
||||
import { genders } from '~/src/nouns.ts';
|
||||
import { availableNumeri, genders } from '~/src/nouns.ts';
|
||||
|
||||
const props = defineProps<{
|
||||
noun: Noun;
|
||||
@ -16,12 +16,7 @@ const visibleGenders = computed(() => {
|
||||
});
|
||||
});
|
||||
|
||||
const numerus = computed(() => {
|
||||
if (config.nouns.plurals) {
|
||||
return [false, true];
|
||||
}
|
||||
return [false];
|
||||
});
|
||||
const visibleNumeri = computed(() => availableNumeri(config));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -30,18 +25,18 @@ const numerus = computed(() => {
|
||||
<NounsGenderLabel :gender="gender" concise />
|
||||
</div>
|
||||
<div
|
||||
v-for="plural in numerus"
|
||||
:key="plural ? 'plural' : 'singular'"
|
||||
:style="{ gridArea: `${gender}${plural ? 'Pl' : ''}` }"
|
||||
v-for="numerus in visibleNumeri"
|
||||
:key="numerus"
|
||||
:style="{ gridArea: `${gender}${numerus === 'plural' ? 'Pl' : ''}` }"
|
||||
role="cell"
|
||||
>
|
||||
<NounsItem :noun="noun" :gender="gender" :plural="plural" />
|
||||
<NounsItem :noun :gender :numerus />
|
||||
|
||||
<small v-if="base">
|
||||
<p><strong><T>nouns.edited</T><T>quotation.colon</T></strong></p>
|
||||
<Diff switchable>
|
||||
<template #before><NounsItem :noun="base" :gender="gender" :plural="plural" /></template>
|
||||
<template #after><NounsItem :noun="noun" :gender="gender" :plural="plural" /></template>
|
||||
<template #before><NounsItem :noun="base" :gender :numerus /></template>
|
||||
<template #after><NounsItem :noun="noun" :gender :numerus /></template>
|
||||
</Diff>
|
||||
</small>
|
||||
</div>
|
||||
|
@ -1,4 +1,5 @@
|
||||
import type { Cell, CellPart, VariantsFromBaseConverter } from '~/src/language/grammarTables.ts';
|
||||
import type { Numerus } from '~/src/nouns.ts';
|
||||
|
||||
const cases = ['n', 'g', 'd', 'a'];
|
||||
|
||||
@ -8,7 +9,7 @@ interface Category {
|
||||
}
|
||||
|
||||
interface Declension extends Category {
|
||||
numerus: 'singular' | 'plural';
|
||||
numerus: Numerus;
|
||||
icon?: string;
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,7 @@ import type { NounTemplatesData } from '~/locale/data.ts';
|
||||
import { Noun, SourceLibrary } from '~/src/classes.ts';
|
||||
import type { Source } from '~/src/classes.ts';
|
||||
import { neutralGenderNameInjectionKey } from '~/src/injectionKeys.ts';
|
||||
import { availableGenders, genders } from '~/src/nouns.ts';
|
||||
import { availableGenders, genders, numeri, symbolsByNumeri } from '~/src/nouns.ts';
|
||||
import type { NounDeclension, NounWords } from '~/src/nouns.ts';
|
||||
|
||||
const dukajDeclension: NounDeclension = {
|
||||
@ -229,7 +229,7 @@ const generatorResult = computed(() => {
|
||||
|
||||
const words: NounWords = {};
|
||||
for (const gender of genders) {
|
||||
for (const numerus of ['singular', 'plural'] as const) {
|
||||
for (const numerus of numeri) {
|
||||
const genderWithNumerus = `${gender}${numerus === 'singular' ? '' : 'Pl'}` as const;
|
||||
words[gender] ??= {};
|
||||
words[gender][numerus] = (template.value[genderWithNumerus] ?? '').split('/').map((ending) => {
|
||||
@ -325,13 +325,9 @@ onMounted(async () => {
|
||||
</summary>
|
||||
<div class="border-top">
|
||||
<div class="d-flex flex-column flex-md-row">
|
||||
<div class="p-3">
|
||||
<h5>⋅ <T>nouns.singular</T></h5>
|
||||
<NounsDeclension :word="{ spelling: '', declension: dukajExtendedDeclension }" open />
|
||||
</div>
|
||||
<div class="p-3">
|
||||
<h5>⁖ <T>nouns.plural</T></h5>
|
||||
<NounsDeclension :word="{ spelling: '', declension: dukajExtendedDeclension }" open plural />
|
||||
<div v-for="numerus of numeri" :key="numerus" class="p-3">
|
||||
<h5>{{ symbolsByNumeri[numerus] }} <T>nouns.{{ numerus }}</T></h5>
|
||||
<NounsDeclension :word="{ spelling: '', declension: dukajExtendedDeclension }" open :numerus />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -10,7 +10,7 @@ import { Noun, SourceLibrary } from '~/src/classes.ts';
|
||||
import type { Source } from '~/src/classes.ts';
|
||||
import { removeSuffix } from '~/src/helpers.ts';
|
||||
import { neutralGenderNameInjectionKey } from '~/src/injectionKeys.ts';
|
||||
import { availableGenders, genders } from '~/src/nouns.ts';
|
||||
import { availableGenders, genders, numeri, symbolsByNumeri } from '~/src/nouns.ts';
|
||||
import type { NounWords, NounDeclension } from '~/src/nouns.ts';
|
||||
|
||||
const xDeclension: NounDeclension = {
|
||||
@ -206,7 +206,7 @@ const generatorResult = computed(() => {
|
||||
|
||||
const words: NounWords = {};
|
||||
for (const gender of genders) {
|
||||
for (const numerus of ['singular', 'plural'] as const) {
|
||||
for (const numerus of numeri) {
|
||||
const genderWithNumerus = `${gender}${numerus === 'singular' ? '' : 'Pl'}` as const;
|
||||
words[gender] ??= {};
|
||||
words[gender][numerus] = (template.value[genderWithNumerus] ?? '').split('/').map((ending) => {
|
||||
@ -295,13 +295,9 @@ onMounted(async () => {
|
||||
</summary>
|
||||
<div class="border-top">
|
||||
<div class="d-flex flex-column flex-md-row">
|
||||
<div class="p-3">
|
||||
<h5>⋅ <T>nouns.singular</T></h5>
|
||||
<NounsDeclension :word="{ spelling: '', declension: xExtendedDeclension }" open />
|
||||
</div>
|
||||
<div class="p-3">
|
||||
<h5>⁖ <T>nouns.plural</T></h5>
|
||||
<NounsDeclension :word="{ spelling: '', declension: xExtendedDeclension }" open plural />
|
||||
<div v-for="numerus of numeri" :key="numerus" class="p-3">
|
||||
<h5>{{ symbolsByNumeri[numerus] }} <T>nouns.{{ numerus }}</T></h5>
|
||||
<NounsDeclension :word="{ spelling: '', declension: xExtendedDeclension }" open :numerus />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -5,6 +5,7 @@ import NounsNav from './NounsNav.vue';
|
||||
|
||||
import useSimpleHead from '~/composables/useSimpleHead.ts';
|
||||
import { removeSuffix } from '~/src/helpers.ts';
|
||||
import { numeri, symbolsByNumeri } from '~/src/nouns.ts';
|
||||
import type { NounDeclension } from '~/src/nouns.ts';
|
||||
|
||||
const { $translator: translator } = useNuxtApp();
|
||||
@ -346,13 +347,9 @@ const neuterAltDeclension: Record<string, NounDeclension[]> = {
|
||||
</summary>
|
||||
<div class="border-top">
|
||||
<div class="d-flex flex-column flex-md-row">
|
||||
<div class="p-3">
|
||||
<h5>⋅ <T>nouns.singular</T></h5>
|
||||
<NounsDeclension :word="{ spelling: '', declension: neuterExtendedDeclension }" open />
|
||||
</div>
|
||||
<div class="p-3">
|
||||
<h5>⁖ <T>nouns.plural</T></h5>
|
||||
<NounsDeclension :word="{ spelling: '', declension: neuterExtendedDeclension }" open plural />
|
||||
<div v-for="numerus of numeri" :key="numerus" class="p-3">
|
||||
<h5>{{ symbolsByNumeri[numerus] }} <T>nouns.{{ numerus }}</T></h5>
|
||||
<NounsDeclension :word="{ spelling: '', declension: neuterExtendedDeclension }" open :numerus />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -5,7 +5,14 @@ import { getLocale, loadConfig, loadTranslator } from '~/server/data.ts';
|
||||
import { registerLocaleFont } from '~/server/localeFont.ts';
|
||||
import { parseNounRow } from '~/server/nouns.ts';
|
||||
import type { NounRow } from '~/server/nouns.ts';
|
||||
import { availableGenders, iconUnicodesByGender, longIdentifierByGender } from '~/src/nouns.ts';
|
||||
import {
|
||||
availableGenders,
|
||||
availableNumeri,
|
||||
iconUnicodesByGender,
|
||||
longIdentifierByGender,
|
||||
numeri,
|
||||
symbolsByNumeri,
|
||||
} from '~/src/nouns.ts';
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const locale = getLocale(event);
|
||||
@ -45,7 +52,7 @@ export default defineEventHandler(async (event) => {
|
||||
let maxItems = 0;
|
||||
genders.forEach((gender) => {
|
||||
let items = 0;
|
||||
for (const numerus of ['singular', 'plural'] as const) {
|
||||
for (const numerus of numeri) {
|
||||
items += noun.words[gender]?.[numerus]?.length ?? 0;
|
||||
}
|
||||
if (items > maxItems) {
|
||||
@ -88,7 +95,8 @@ export default defineEventHandler(async (event) => {
|
||||
context.font = `24pt '${fontName}'`;
|
||||
genders.forEach((gender, column) => {
|
||||
let i = 0;
|
||||
for (const [numerus, symbol] of [['singular', '⋅'], ['plural', '⁖']] as const) {
|
||||
for (const numerus of availableNumeri(config)) {
|
||||
const symbol = symbolsByNumeri[numerus];
|
||||
noun.words[gender]?.[numerus]?.forEach((word) => {
|
||||
context.fillText(
|
||||
`${symbol} ${word}`,
|
||||
|
@ -13,6 +13,7 @@ import type {
|
||||
NounDeclensionsByFirstCase,
|
||||
NounConvention,
|
||||
Gender,
|
||||
Numerus,
|
||||
} from '~/src/nouns.ts';
|
||||
|
||||
export class PronounExample {
|
||||
@ -855,7 +856,7 @@ const loadWord = (
|
||||
config: Config,
|
||||
declensionsByFirstCase: NounDeclensionsByFirstCase,
|
||||
wordRaw: string | NounWord,
|
||||
numerus: 'singular' | 'plural',
|
||||
numerus: Numerus,
|
||||
): NounWord => {
|
||||
if (typeof wordRaw === 'string') {
|
||||
wordRaw = { spelling: wordRaw };
|
||||
@ -885,7 +886,7 @@ export const loadWords = (
|
||||
const words = Object.fromEntries(Object.entries(nounRaw.words).map(([gender, wordsOfGender]) => {
|
||||
return [gender, Object.fromEntries(Object.entries(wordsOfGender).map(([numerus, wordsOfNumerus]) => {
|
||||
return [numerus, wordsOfNumerus
|
||||
.map((wordRaw) => loadWord(config, declensionsByFirstCase, wordRaw, numerus as 'singular' | 'plural'))];
|
||||
.map((wordRaw) => loadWord(config, declensionsByFirstCase, wordRaw, numerus as Numerus))];
|
||||
}))];
|
||||
}));
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import type { Config } from '~/locale/config.ts';
|
||||
import { capitalise, escapePronunciationString } from '~/src/helpers.ts';
|
||||
import type { MorphemeValues } from '~/src/language/morphemes.ts';
|
||||
import type { NounConvention, NounDeclension } from '~/src/nouns.ts';
|
||||
import type { NounConvention, NounDeclension, Numerus } from '~/src/nouns.ts';
|
||||
|
||||
export type ExamplePart = string | ExamplePartMorpheme | ExamplePartNoun;
|
||||
|
||||
@ -16,7 +16,7 @@ interface ExamplePartNoun {
|
||||
stems: Record<string, string>;
|
||||
nounClass: string;
|
||||
caseAbbreviation: string;
|
||||
plural?: boolean;
|
||||
numerus?: Numerus;
|
||||
}
|
||||
|
||||
export interface ExampleValues {
|
||||
@ -71,7 +71,7 @@ export class Example {
|
||||
})),
|
||||
nounClass: nounMatch.groups.nounClass,
|
||||
caseAbbreviation: nounMatch.groups.caseAbbreviation,
|
||||
plural: nounMatch.groups.numerus === 'plural',
|
||||
numerus: nounMatch.groups.numerus as Numerus | undefined,
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -113,7 +113,7 @@ export class Example {
|
||||
return undefined;
|
||||
}
|
||||
const stem = part.stems[template.stem ?? 'default'];
|
||||
const numerus = !part.plural ? 'singular' : 'plural';
|
||||
const numerus = part.numerus ?? 'singular';
|
||||
const declension = exampleValues.nounDeclensions[template.declension][numerus];
|
||||
if (declension === undefined) {
|
||||
return undefined;
|
||||
|
@ -1,6 +1,7 @@
|
||||
import type { ExampleValues } from '~/src/language/examples.ts';
|
||||
import { toMorphemeValue } from '~/src/language/morphemes.ts';
|
||||
import type { MorphemeValue } from '~/src/language/morphemes.ts';
|
||||
import type { Numerus } from '~/src/nouns.ts';
|
||||
|
||||
export type GrammarTablesDefinition = (GrammarTableDefinition | string)[]
|
||||
| { simple: (GrammarTableDefinition | string)[]; comprehensive: (GrammarTableDefinition | string)[] };
|
||||
@ -34,7 +35,7 @@ export type VariantsFromBaseConverter = Record<string, (variantsDefinition: Vari
|
||||
|
||||
type CellDefinition = string | CellPartDefinition | CellPartDefinition[] | null;
|
||||
|
||||
type CellPartDefinition = MorphemeCellDefinition | Record<'singular' | 'plural', MorphemeCellDefinition>;
|
||||
type CellPartDefinition = MorphemeCellDefinition | Record<Numerus, MorphemeCellDefinition>;
|
||||
|
||||
export interface MorphemeCellDefinition {
|
||||
morpheme: string;
|
||||
@ -45,7 +46,7 @@ export interface MorphemeCellDefinition {
|
||||
|
||||
export interface Variant {
|
||||
name?: string;
|
||||
numerus?: 'singular' | 'plural';
|
||||
numerus?: Numerus;
|
||||
icon?: string;
|
||||
cells: Cell[];
|
||||
}
|
||||
|
21
src/nouns.ts
21
src/nouns.ts
@ -31,15 +31,30 @@ export const longIdentifierByGender: Record<Gender, string> = {
|
||||
nb: 'nonbinary',
|
||||
};
|
||||
|
||||
export const numeri = ['singular', 'plural'] as const;
|
||||
export type Numerus = typeof numeri[number];
|
||||
|
||||
export const availableNumeri = (config: Config): readonly Numerus[] => {
|
||||
if (config.nouns.plurals) {
|
||||
return numeri;
|
||||
}
|
||||
return ['singular'];
|
||||
};
|
||||
|
||||
export const symbolsByNumeri: Record<Numerus, string> = {
|
||||
singular: '⋅',
|
||||
plural: '⁖',
|
||||
};
|
||||
|
||||
export interface NounWord {
|
||||
spelling: string;
|
||||
convention?: keyof Required<NounsData>['conventions'];
|
||||
declension?: keyof Required<NounsData>['declensions'] | NounDeclension;
|
||||
}
|
||||
|
||||
export type NounWordsRaw = Partial<Record<Gender, Partial<Record<'singular' | 'plural', (NounWord | string)[]>>>>;
|
||||
export type NounWordsRaw = Partial<Record<Gender, Partial<Record<Numerus, (NounWord | string)[]>>>>;
|
||||
|
||||
export type NounWords = Partial<Record<Gender, Partial<Record<'singular' | 'plural', NounWord[]>>>>;
|
||||
export type NounWords = Partial<Record<Gender, Partial<Record<Numerus, NounWord[]>>>>;
|
||||
|
||||
export interface NounConventionGroup {
|
||||
name: string;
|
||||
@ -92,7 +107,7 @@ export const buildNounDeclensionsByFirstCase = (
|
||||
return { singular: {}, plural: {} };
|
||||
}
|
||||
const firstCaseAbbreviation = Object.keys(cases)[0];
|
||||
return fromUnionEntries((['singular', 'plural'] as const).map((numerus) => {
|
||||
return fromUnionEntries(numeri.map((numerus) => {
|
||||
return [numerus, Object.fromEntries(Object.entries(declensions)
|
||||
.flatMap(([declensionKey, declension]) => {
|
||||
const endings = declension[numerus]?.[firstCaseAbbreviation];
|
||||
|
@ -8,7 +8,7 @@ import { loadSuml, loadTsv } from '~/server/loader.ts';
|
||||
import { normaliseKey } from '~/src/buildPronoun.ts';
|
||||
import { Example } from '~/src/language/examples.ts';
|
||||
import type { VariantsFromBaseConverter } from '~/src/language/grammarTables.ts';
|
||||
import { availableGenders, gendersWithNumerus } from '~/src/nouns.ts';
|
||||
import { availableGenders, gendersWithNumerus, numeri } from '~/src/nouns.ts';
|
||||
import type { NounsData } from '~/src/nouns.ts';
|
||||
|
||||
function toHaveValidMorphemes(actual: string, morphemes: string[]): SyncExpectationResult {
|
||||
@ -219,7 +219,7 @@ describe.each(allLocales)('data files of $code', async ({ code }) => {
|
||||
if (nounsData.cases && nounsData.declensions) {
|
||||
test('declensions have valid cases', () => {
|
||||
for (const declension of Object.values(nounsData.declensions!)) {
|
||||
for (const numerus of ['singular', 'plural'] as const) {
|
||||
for (const numerus of numeri) {
|
||||
expect(Object.keys(nounsData.cases!))
|
||||
.toEqual(expect.arrayContaining(Object.keys(declension[numerus] ?? {})));
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user