-
-
⋅ nouns.singular
-
-
-
-
⁖ nouns.plural
-
+
+
{{ symbolsByNumeri[numerus] }} nouns.{{ numerus }}
+
diff --git a/server/api/nouns/[id].get.ts b/server/api/nouns/[id].get.ts
index 5b016aeca..593811191 100644
--- a/server/api/nouns/[id].get.ts
+++ b/server/api/nouns/[id].get.ts
@@ -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}`,
diff --git a/src/classes.ts b/src/classes.ts
index 11e921d22..61cad9576 100644
--- a/src/classes.ts
+++ b/src/classes.ts
@@ -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))];
}))];
}));
diff --git a/src/language/examples.ts b/src/language/examples.ts
index 80ca3f0a6..4be896e0d 100644
--- a/src/language/examples.ts
+++ b/src/language/examples.ts
@@ -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
;
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;
diff --git a/src/language/grammarTables.ts b/src/language/grammarTables.ts
index 992f09988..0ac430b35 100644
--- a/src/language/grammarTables.ts
+++ b/src/language/grammarTables.ts
@@ -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;
+type CellPartDefinition = MorphemeCellDefinition | Record;
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[];
}
diff --git a/src/nouns.ts b/src/nouns.ts
index ddea04be2..a508f5d77 100644
--- a/src/nouns.ts
+++ b/src/nouns.ts
@@ -31,15 +31,30 @@ export const longIdentifierByGender: Record = {
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 = {
+ singular: '⋅',
+ plural: '⁖',
+};
+
export interface NounWord {
spelling: string;
convention?: keyof Required['conventions'];
declension?: keyof Required['declensions'] | NounDeclension;
}
-export type NounWordsRaw = Partial>>>;
+export type NounWordsRaw = Partial>>>;
-export type NounWords = Partial>>>;
+export type NounWords = Partial>>>;
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];
diff --git a/test/locales/data.test.ts b/test/locales/data.test.ts
index 41029e83e..9d1414e3a 100644
--- a/test/locales/data.test.ts
+++ b/test/locales/data.test.ts
@@ -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] ?? {})));
}