(nouns) resolve first declension in a consistent manner

This commit is contained in:
Valentyne Stigloher 2025-07-20 17:23:00 +02:00
parent a6a890fb10
commit 2d49b722c7
7 changed files with 99 additions and 58 deletions

View File

@ -1,6 +1,6 @@
<script setup lang="ts">
import { loadNounsData } from '~/src/data.ts';
import { symbolsByNumeri } from '~/src/nouns.ts';
import { resolveDeclensionByCase, symbolsByNumeri } from '~/src/nouns.ts';
import type { NounClass, NounConvention, NounWord, Numerus } from '~/src/nouns.ts';
const props = withDefaults(defineProps<{
@ -36,10 +36,17 @@ const word = computed((): NounWord | undefined => {
declension: template.value.declension,
};
});
const declensionByCase = computed(() => {
if (word.value === undefined) {
return undefined;
}
return resolveDeclensionByCase(word.value, props.numerus, nounsData);
});
</script>
<template>
<div v-if="word" class="mb-3">
<div v-if="word && declensionByCase" class="mb-3">
<h5 class="h6">
{{ symbolsByNumeri[numerus] }} <T>nouns.{{ numerus }}</T>
</h5>

View File

@ -1,6 +1,7 @@
<script setup lang="ts">
import { loadNounsData } from '~/src/data.ts';
import type { NounConvention, NounStemKey } from '~/src/nouns.ts';
import { resolveFirstDeclension } from '~/src/nouns.ts';
import type { NounConvention, NounWord } from '~/src/nouns.ts';
const props = defineProps<{
nounConvention: WithKey<NounConvention>;
@ -14,16 +15,18 @@ if (nounsData === undefined) {
const template = props.nounConvention.templates.t1;
const stems: Record<NounStemKey, string> = {
default: 'Arbeit',
flucht: 'Arbeits',
partizip: 'Arbeit',
};
const stems = nounsData.classes!.t1.exampleStems;
const stem = computed(() => {
return stems[template.stem ?? 'default'];
});
const word = computed((): NounWord => ({
spelling: stem.value + template.suffix,
convention: props.nounConvention.key,
declension: template.declension,
}));
const declension = nounsData.declensions?.[template.declension];
const singularExample = computed(() => {
@ -31,7 +34,7 @@ const singularExample = computed(() => {
return undefined;
}
return `${props.nounConvention.morphemes.article_n ?? ''} ${stem.value}${template.suffix}${declension.singular.n}`;
return resolveFirstDeclension(word.value, 'singular', nounsData);
});
const pluralExample = computed(() => {
@ -39,7 +42,7 @@ const pluralExample = computed(() => {
return undefined;
}
return `die ${stem.value}${template.suffix}${declension.plural.n}`;
return resolveFirstDeclension(word.value, 'plural', nounsData);
});
</script>

View File

@ -1,7 +1,8 @@
<script setup lang="ts">
import { loadNounsData } from '~/src/data.ts';
import { capitalise } from '~/src/helpers.ts';
import type { Numerus, NounWord, NounCaseKey } from '~/src/nouns.ts';
import { getFirstDeclension, resolveArticles, resolveDeclensionByCase } from '~/src/nouns.ts';
import type { NounConvention, Numerus, NounWord } from '~/src/nouns.ts';
const props = withDefaults(defineProps<{
word: NounWord;
@ -16,47 +17,24 @@ const props = withDefaults(defineProps<{
const nounsData = await loadNounsData();
const declensionByCase = computed((): Record<NounCaseKey, string[]> | undefined => {
if (props.word.declension === undefined) {
const nounConvention = computed((): WithKey<NounConvention> | undefined => {
if (props.word.convention === undefined || nounsData.conventions === undefined) {
return undefined;
}
if (typeof props.word.declension === 'string') {
return nounsData.declensions?.[props.word.declension]?.[props.numerus];
}
return props.word.declension[props.numerus];
});
const nounConvention = computed(() => {
if (props.word.convention === undefined) {
return undefined;
}
return { ...nounsData.conventions?.[props.word.convention], key: props.word.convention };
return { ...nounsData.conventions[props.word.convention], key: props.word.convention };
});
const articles = computed(() => {
if (nounConvention.value === undefined) {
return {};
}
return Object.fromEntries(Object.entries(nounsData.classExample?.[props.numerus] ?? {})
.map(([caseAbbreviation, article]) => {
const resolvedArticle = article.replace(/\{([^}]+)}/, (_match, morpheme) => {
const value = nounConvention.value?.morphemes?.[morpheme];
if (value === undefined) {
return '';
}
return typeof value === 'string' ? value : value.spelling;
});
return [caseAbbreviation, resolvedArticle];
}));
return resolveArticles(nounConvention.value, props.numerus, nounsData);
});
const declensionByCase = computed(() => resolveDeclensionByCase(props.word, props.numerus, nounsData));
const firstDeclension = computed(() => {
if (nounsData.cases === undefined) {
return props.word.spelling;
}
const caseAbbreviation = Object.keys(nounsData.cases)[0];
const ending = declensionByCase.value?.[caseAbbreviation][0] ?? '';
return `${articles.value[caseAbbreviation] ?? ''}${props.word.spelling}${ending}`;
return getFirstDeclension(props.word, nounsData, articles.value, declensionByCase.value);
});
const visible = ref(props.open);

View File

@ -3,14 +3,14 @@ import SQL from 'sql-template-strings';
import { getLocale, loadConfig, loadNounsData, loadTranslator } from '~/server/data.ts';
import { registerLocaleFont } from '~/server/localeFont.ts';
import { buildNoun, displayWord, parseNounRow } from '~/server/nouns.ts';
import { buildNoun, parseNounRow } from '~/server/nouns.ts';
import type { NounRow } from '~/server/nouns.ts';
import {
availableGenders,
availableNumeri,
iconUnicodesByGender,
longIdentifierByGender,
numeri,
numeri, resolveFirstDeclension,
symbolsByNumeri,
} from '~/src/nouns.ts';
@ -100,7 +100,7 @@ export default defineEventHandler(async (event) => {
const symbol = symbolsByNumeri[numerus];
noun.words[gender]?.[numerus]?.forEach((word) => {
context.fillText(
`${symbol} ${displayWord(word, numerus, nounsData)}`,
`${symbol} ${resolveFirstDeclension(word, numerus, nounsData)}`,
column * (width - 2 * padding) / genders.length + padding,
padding * 2.5 + i * 48,
);

View File

@ -18,7 +18,7 @@ import {
loadTranslator,
} from '~/server/data.ts';
import { getInclusiveEntries } from '~/server/inclusive.ts';
import { buildNoun, displayWord, getNounEntries } from '~/server/nouns.ts';
import { buildNoun, getNounEntries } from '~/server/nouns.ts';
import { rootDir } from '~/server/paths.ts';
import { getSourcesEntries } from '~/server/sources.ts';
import { getTermsEntries } from '~/server/terms.ts';
@ -27,6 +27,7 @@ import { Day } from '~/src/calendar/helpers.ts';
import { getUrlForLocale } from '~/src/domain.ts';
import forbidden from '~/src/forbidden.ts';
import { clearLinkedText, buildImageUrl } from '~/src/helpers.ts';
import { resolveFirstDeclension } from '~/src/nouns.ts';
import type { Numerus } from '~/src/nouns.ts';
import parseMarkdown from '~/src/parseMarkdown.ts';
import { normaliseQuery, validateQuery } from '~/src/search.ts';
@ -638,7 +639,9 @@ const kinds: SearchKind[] = [
.map((wordsByNumerus) => {
return Object.entries(wordsByNumerus)
.flatMap(([numerus, words]) => {
return words.map((word) => displayWord(word, numerus as Numerus, nounsData));
return words.map((word) => {
return resolveFirstDeclension(word, numerus as Numerus, nounsData);
});
})
.join(', ');
})

View File

@ -10,7 +10,7 @@ import { loadWords, Noun } from '~/src/classes.ts';
import type { NounRaw } from '~/src/classes.ts';
import { clearKey } from '~/src/helpers.ts';
import { addWordsFromClassInstance } from '~/src/nouns.ts';
import type { Numerus, NounWord, NounsData } from '~/src/nouns.ts';
import type { NounsData } from '~/src/nouns.ts';
import type { User } from '~/src/user.ts';
export interface NounRow {
@ -59,17 +59,6 @@ const parseNounRowWithAuthor = (nounRow: NounRowWithAuthor, isGranted: IsGranted
return noun;
};
export const displayWord = (word: NounWord, numerus: Numerus, nounsData: NounsData): string => {
if (!nounsData.declensions || !nounsData.cases) {
return word.spelling;
}
const declension = typeof word.declension === 'string' ? nounsData.declensions[word.declension] : word.declension;
const firstCaseAbbreviation = Object.keys(nounsData.cases)[0];
const endings = declension?.[numerus]?.[firstCaseAbbreviation];
return `${word.spelling}${endings?.[0] ?? ''}`;
};
export const addVersions = async (
db: Database,
isGranted: IsGrantedFn,

View File

@ -117,6 +117,67 @@ export interface NounDeclension {
plural?: Record<NounCaseKey, string[]>;
}
export const resolveDeclensionByCase = (
word: NounWord,
numerus: Numerus,
nounsData: NounsData,
): Record<NounCaseKey, string[]> | undefined => {
if (word.declension === undefined) {
return undefined;
}
if (typeof word.declension === 'string') {
return nounsData.declensions?.[word.declension]?.[numerus];
}
return word.declension[numerus];
};
export const resolveArticles = (
nounConvention: NounConvention,
numerus: Numerus,
nounsData: NounsData,
): Record<NounCaseKey, string> => {
return Object.fromEntries(Object.entries(nounsData.classExample?.[numerus] ?? {})
.map(([caseAbbreviation, article]) => {
const resolvedArticle = article.replace(/\{([^}]+)}/, (_match, morpheme) => {
const value = nounConvention.morphemes?.[morpheme];
if (value === undefined) {
return '';
}
return typeof value === 'string' ? value : value.spelling;
});
if (resolvedArticle.trim().length === 0) {
return [caseAbbreviation, ''];
}
return [caseAbbreviation, resolvedArticle];
}));
};
export const getFirstDeclension = (
word: NounWord,
nounsData: NounsData,
articles: Record<NounCaseKey, string>,
declensionByCase: Record<NounCaseKey, string[]> | undefined,
): string => {
if (nounsData.cases === undefined) {
return word.spelling;
}
const caseAbbreviation = Object.keys(nounsData.cases)[0];
const ending = declensionByCase?.[caseAbbreviation][0] ?? '';
return `${articles[caseAbbreviation] ?? ''}${word.spelling}${ending}`;
};
export const resolveFirstDeclension = (word: NounWord, numerus: Numerus, nounsData: NounsData) => {
let articles;
if (word.convention !== undefined && nounsData.conventions !== undefined) {
const nounConvention = nounsData.conventions[word.convention];
articles = resolveArticles(nounConvention, numerus, nounsData);
} else {
articles = {};
}
const declensionByCase = resolveDeclensionByCase(word, numerus, nounsData);
return getFirstDeclension(word, nounsData, articles, declensionByCase);
};
export interface NounDeclensionsByFirstCase {
singular: Record<string, NounDeclensionKey>;
plural: Record<string, NounDeclensionKey>;