feat(rewrite): Start implementing name algorithm

This commit is contained in:
tecc 2023-09-19 23:47:49 +02:00
parent 29fe79bdf7
commit bc2abe50fa
No known key found for this signature in database
GPG Key ID: 622EEC5BAE5EBD3A
3 changed files with 130 additions and 13 deletions

View File

@ -87,8 +87,8 @@ export function isLocaleActive(localeCode: string): boolean {
export interface Pronoun {
keys: Array<string>;
description: string;
history: string;
description?: string;
history?: string;
source?: string;
isNormative: boolean;
isPlural: boolean;
@ -99,6 +99,39 @@ export interface Pronoun {
smallForm?: string;
forms: Record<string, { written: string; pronounced: string }>;
canonicalName: string;
}
export function parsePronounFromParts(
parts: Array<string>,
forms: Array<string>
): Pronoun | null {
if (parts.length !== forms.length) {
return null;
}
const pronounForms: Pronoun["forms"] = {};
for (let i = 0; i < forms.length; i++) {
const form = parts[i].split("|");
pronounForms[forms[i]] = {
written: form[0],
pronounced: form[1],
};
}
return {
forms: pronounForms,
isNormative: false,
keys: [],
isPronounceable: false,
isPlural: false,
isPluralHonorific: false,
canonicalName: `${parts[0]}/${parts[1]}`,
};
}
export function parsePronounFromString(s: string, forms: Array<string>) {
return parsePronounFromParts(s.split("/"), forms);
}
export interface PronounExample {
@ -162,7 +195,7 @@ export class Locale {
}
}
private _config: unknown | null = null;
private _config: any | null = null;
private _translations: unknown | null = null;
private _pronouns: Array<Pronoun> = [];
private _pronounsByAlias: Record<string, number> = {};
@ -261,6 +294,7 @@ export class Locale {
for (const key of obj.keys) {
this._pronounsByAlias[key] = i;
}
obj.canonicalName = obj.keys[0];
}
const examples = await this.readFile(
["pronouns/examples.tsv"],
@ -302,9 +336,67 @@ export class Locale {
return this._pronouns[index];
}
public pronounNameVariants(pronoun: Pronoun): Array<string> {
// NOTE(tecc): This is an attempt at decoding the code from classes.js.
// I make no guarantee regarding the accuracy.
const variants: Set<string> = new Set();
const firstMorpheme =
pronoun.forms[this.morphemes[0]].written.split("&");
if (this.morphemes.length === 1) {
return firstMorpheme;
}
const secondMorpheme =
pronoun.forms[this.morphemes[1]].written.split("&");
const thirdMorpheme =
this.morphemes.length > 2
? pronoun.forms[this.morphemes[2]].written.split("&")
: [];
let thirdFormMorpheme = pronoun.thirdForm
? pronoun.forms[pronoun.thirdForm].written.split("&")
: [];
let thirdMorphemeThreeForms = thirdMorpheme;
if (this.code === "ru" || this.code === "ua") {
thirdMorphemeThreeForms = thirdMorpheme.map((x) => `[-${x}]`);
}
for (let istr in firstMorpheme) {
const i = istr as unknown as number;
let firstPart = firstMorpheme[i];
let secondPart =
secondMorpheme[Math.max(i, secondMorpheme.length - 1)];
if (
firstPart === secondPart &&
thirdMorpheme.length &&
!this.config.pronouns.threeForms
) {
secondPart =
thirdMorpheme[Math.max(i, thirdMorpheme.length - 1)];
}
let variant = firstPart + "/" + secondPart;
if (this.config.pronouns.threeForms) {
variant += "/" + thirdMorphemeThreeForms[i];
} else if (pronoun.thirdForm) {
variant += "/" + thirdFormMorpheme[i];
}
variants.add(variant);
}
return [...variants];
}
public pronounName(pronoun: Pronoun, separator: string = ","): string {
return this.pronounNameVariants(pronoun).join(separator);
}
public get examples() {
return this._examples;
}
public get morphemes() {
return this._morphemes;
}
}
export function examplesFor(

View File

@ -1,5 +1,11 @@
import { Type } from "@sinclair/typebox";
import { examplesFor, getLocale, Pronoun, PronounExample } from "#self/locales";
import {
examplesFor,
getLocale,
Locale,
Pronoun,
PronounExample,
} from "#self/locales";
import pronouns from "#self/server/v1/pronouns";
export type V1AppInstance = AppInstance;
@ -28,7 +34,7 @@ export function error<T extends ApiErrorOptions>(options: T) {
export function replyError<T extends ApiErrorOptions>(
reply: AppReply,
options: T,
options: T
): AppReply {
const response = error(options);
return reply.status(options.code).send(response);
@ -41,7 +47,8 @@ export const ApiError = {
},
UNKNOWN_PRONOUN: {
code: 404,
message: "We aren't aware of any such pronoun. Perhaps there's a typo?\nNote that for custom pronouns, you need to specify all necessary forms: https://en.pronouns.page/faq#custom-pronouns",
message:
"We aren't aware of any such pronoun. Perhaps there's a typo?\nNote that for custom pronouns, you need to specify all necessary forms: https://en.pronouns.page/faq#custom-pronouns",
},
} satisfies Record<string, ApiErrorOptions>;
@ -52,6 +59,8 @@ export const localeSpecific = Type.Object({
export function transformPronoun(
pronoun: Pronoun,
examples: Array<PronounExample>,
locale: Locale,
processName = false
) {
const morphemes: Record<string, string> = {};
const pronunciations: Record<string, string> = {};
@ -60,7 +69,7 @@ export function transformPronoun(
pronunciations[name] = form.pronounced;
}
return {
canonicalName: pronoun.keys[0],
canonicalName: pronoun.canonicalName,
description: pronoun.description,
normative: pronoun.isNormative,
morphemes,
@ -74,6 +83,7 @@ export function transformPronoun(
smallForm: pronoun.smallForm ?? null,
sourcesInfo: pronoun.source ?? null,
examples: examplesFor(pronoun, examples),
name: processName ? locale.pronounName(pronoun) : undefined,
};
}
@ -83,7 +93,7 @@ export function transformPronoun(
* every route is prefixed with the respective locale ID.
*/
export const routes = async function(app: AppInstance) {
export const routes = async function (app: AppInstance) {
app.register(pronouns);
} satisfies AppPluginAsync;
export default routes;

View File

@ -4,7 +4,12 @@ import {
replyError,
transformPronoun,
} from "#self/server/v1";
import { getLocale, PronounExample } from "#self/locales";
import {
getLocale,
parsePronounFromParts,
parsePronounFromString,
PronounExample,
} from "#self/locales";
import { isNotBlank, parseBool } from "@pronounspage/common/util";
import { Type } from "@sinclair/typebox";
@ -27,7 +32,8 @@ export const plugin = async function (app: AppInstance) {
for (const pronoun of locale.pronouns) {
obj[pronoun.keys[0]] = transformPronoun(
pronoun,
locale.examples
locale.examples,
locale
);
}
@ -64,8 +70,15 @@ export const plugin = async function (app: AppInstance) {
return replyError(reply, ApiError.INVALID_LOCALE);
}
const key = req.params["*"].split("/").filter(isNotBlank).join("/"); // This is to get rid of any extraneous parts
const found = locale.pronoun(key);
const keyParts: Array<string> = req.params["*"]
.split("/")
.filter(isNotBlank);
const key = keyParts.join("/");
let found = locale.pronoun(key);
if (found == null) {
found = parsePronounFromParts(keyParts, locale.morphemes);
req.log.info(`${JSON.stringify(keyParts)} - ${found}`);
}
if (found == null) {
return replyError(reply, ApiError.UNKNOWN_PRONOUN);
}
@ -76,7 +89,9 @@ export const plugin = async function (app: AppInstance) {
found,
examples.length < 1
? locale.examples
: examples.map(parseExample)
: examples.map(parseExample),
locale,
true
);
}
);