From 17e1c34a3b6562913640922c68f5fbf9338a6177 Mon Sep 17 00:00:00 2001 From: Benjamin Singleton <19498453+tetrogem@users.noreply.github.com> Date: Sun, 1 Mar 2026 22:32:45 -0600 Subject: [PATCH] wip: i18n compiler organization/improvements, separated string & markdown messages in config builder --- apps/vdn-static/eslint.config.js | 4 + apps/vdn-static/src/new-i18n-lib/compile.ts | 443 ++++++++++++-------- apps/vdn-static/src/new-i18n-lib/config.ts | 104 +++-- apps/vdn-static/src/new-i18n-lib/setup.ts | 17 +- apps/vdn-static/src/new-i18n/config.ts | 63 ++- apps/vdn-static/src/new-i18n/index.ts | 21 +- 6 files changed, 384 insertions(+), 268 deletions(-) diff --git a/apps/vdn-static/eslint.config.js b/apps/vdn-static/eslint.config.js index 63391d7..8185819 100644 --- a/apps/vdn-static/eslint.config.js +++ b/apps/vdn-static/eslint.config.js @@ -48,6 +48,10 @@ export default defineConfig([ { allowInterfaces: "with-single-extends" }, ], "vue/no-ref-object-reactivity-loss": ["error"], + "@typescript/no-unnecessary-conditions": [ + "error", + { allowConstantLoopConditions: "only-allowed-literals" }, + ], }, }, // disable multi-word-component-names for unplugin-vue-router diff --git a/apps/vdn-static/src/new-i18n-lib/compile.ts b/apps/vdn-static/src/new-i18n-lib/compile.ts index 865474f..4cdecdd 100644 --- a/apps/vdn-static/src/new-i18n-lib/compile.ts +++ b/apps/vdn-static/src/new-i18n-lib/compile.ts @@ -2,23 +2,42 @@ import type { Result, Value } from "@/utils/types"; import { computeAllVariants, selectionChainToString, - type L10nRecord, + type PatternVariant, type UncompiledLocale, } from "./setup"; -import type { FluentBundle, FluentVariable } from "@fluent/bundle"; +import type { FluentBundle, FluentVariable, Message } from "@fluent/bundle"; import { parseMarkdown, type Markdown } from "./markdown"; import { - configMessageSymbol, - type ConfigMessage, - type InferLocale, + configMarkdownSymbol, + configMessageTypeSymbol, + configStringSymbol, + type ConfigMarkdown, + type ConfigString, + type InferLocaleFromConfig, type LocaleConfig, } from "./config"; +import type { Pattern } from "@fluent/bundle/esm/ast"; + +type GenericLocale = { [id: string]: GenericLocale | GenericMessageFn }; + +type GenericMessageFn = GenericStringMessageFn | GenericMarkdownMessageFn; + +type GenericStringMessageFn = ( + placeableArgs?: GenericMessageFnPlaceableArgs, +) => string; + +type GenericMarkdownMessageFn = ( + placeableArgs?: GenericMessageFnPlaceableArgs, +) => Markdown; + +type GenericMessageFnPlaceableArgs = Record; export interface CompileLocaleCtx { bundle: FluentBundle; - record: L10nRecord | undefined; + uncompiled: UncompiledLocale; config: Config; - fallback?: InferLocale; + fallback: InferLocaleFromConfig | undefined; + messageIdChain: readonly string[]; } export interface CompileLocaleRes { @@ -26,81 +45,98 @@ export interface CompileLocaleRes { errors: string[]; } -type GenericLocale = { [id: string]: GenericLocale | GenericMessageFn }; - -type GenericMessageFn = - | ((args?: Record) => string) - | ((args?: Record) => Markdown); - export function compileLocale( ctx: CompileLocaleCtx, -): CompileLocaleRes> { - const { bundle, record, config, fallback } = ctx; +): CompileLocaleRes> { + const { + bundle, + uncompiled, + config, + fallback, + messageIdChain: localeMessageIdChain = [], + } = ctx; const errors: string[] = []; - const recordKeys = new Set(Object.keys(record ?? {})); + const uncompiledKeys = new Set(Object.keys(uncompiled ?? {})); const configKeys = new Set(Object.keys(config)); - const excessKeys = recordKeys.difference(configKeys); + const excessKeys = uncompiledKeys.difference(configKeys); if (excessKeys.size > 0) { errors.push(`Excess keys in record: ${[...excessKeys].join(", ")}`); } const locale: GenericLocale = {}; for (const [messageId, configValue] of Object.entries(config)) { - const recordValue = record?.[messageId]; + const uncompiledValue = uncompiled?.[messageId]; const fallbackValue = fallback?.[messageId]; + const valueMessageIdChain = [ + ...localeMessageIdChain, + messageId, + ] as const; const compiledValue = ((): Value => { - if (configMessageSymbol in configValue) { - const compiledMessageRes = compileMessage({ - bundle, - messageId, - configValue, - recordValue, - }); - - const compiledMessage = ((): GenericMessageFn => { - if (compiledMessageRes.type === "ok") { - return compiledMessageRes.ok; - } - - errors.push(compiledMessageRes.err); - - if ( - fallbackValue !== undefined - && typeof fallbackValue === "function" - ) { - return fallbackValue as GenericMessageFn; - } - - return () => `[PLACEHOLDER]${messageId}`; - })(); - - return compiledMessage; - } else { - const subrecord = (() => { - if (recordValue?.type !== "subrecord") { + if (configMessageTypeSymbol in configValue) { + const compiledMessage = (() => { + if (uncompiledValue?.type !== "message") { errors.push( - `Expected subrecord for key \`${messageId}\`, found: ${typeof recordValue}`, + `Expected message for key \`${messageId}\`, found: ${typeof uncompiledValue}`, ); return undefined; } - return recordValue.subrecord; + const uncompiledMessage = uncompiledValue.message; + const compiledMessageRes = compileMessage({ + bundle, + messageIdChain: valueMessageIdChain, + configValue, + uncompiledMessage, + }); + + if (compiledMessageRes.type === "err") { + errors.push(compiledMessageRes.err); + return undefined; + } + + const compiledMessage = compiledMessageRes.ok; + return compiledMessage; + })(); + + if (compiledMessage !== undefined) { + return compiledMessage; + } + + if ( + fallbackValue !== undefined + && typeof fallbackValue === "function" + ) { + return fallbackValue as GenericMessageFn; + } + + return () => `[#${fmtMessageIdChain(valueMessageIdChain)}#]`; + } else { + const uncompiledSubrecord = (() => { + if (uncompiledValue?.type !== "subrecord") { + errors.push( + `Expected subrecord for key \`${messageId}\`, found: ${typeof uncompiledValue}`, + ); + + return undefined; + } + + return uncompiledValue.subrecord; })(); return compileSublocale({ subconfig: configValue, - recordSublocale: subrecord, + uncompiledSublocale: uncompiledSubrecord, fallbackSublocale: typeof fallbackValue === "function" ? undefined : ( fallbackValue ), errors, bundle, - messageId, + messageIdChain: valueMessageIdChain, }); } })(); @@ -109,35 +145,32 @@ export function compileLocale( } // SAFETY: validated above that all keys exist and are the correct type - return { locale: locale as InferLocale, errors }; + return { locale: locale as InferLocaleFromConfig, errors }; +} + +function fmtMessageIdChain( + messageIdChain: readonly [...string[], string], +): string { + return messageIdChain.join("-"); } interface CompileMessageCtx { bundle: FluentBundle; - messageId: string; - configValue: ConfigMessage; - recordValue: Value | undefined; + messageIdChain: readonly [...string[], string]; + configValue: ConfigString | ConfigMarkdown; + uncompiledMessage: Message; } function compileMessage( ctx: CompileMessageCtx, ): Result { - const { bundle, messageId, configValue, recordValue } = ctx; + const { bundle, messageIdChain, configValue, uncompiledMessage } = ctx; - if (recordValue?.type !== "message") { - return { - type: "err", - err: `Expected message for key \`${messageId}\`, found: ${typeof recordValue}`, - }; - } - - const message = recordValue.message; - - const pattern = message.value; + const pattern = uncompiledMessage.value; if (pattern === null) { return { type: "err", - err: `Pattern is null for message with ID: ${messageId}`, + err: `Pattern is null for message with ID: ${fmtMessageIdChain(messageIdChain)}`, }; } @@ -154,7 +187,7 @@ function compileMessage( if (selector.type !== "var") { return { type: "err", - err: `Expected selector to be a var expression for key: ${messageId}; Found: ${selector.type}`, + err: `Expected selector to be a var expression for ID: ${fmtMessageIdChain(messageIdChain)}; Found: ${selector.type}`, }; } @@ -165,7 +198,7 @@ function compileMessage( ) { return { type: "err", - err: `Found unexpected placeable name \`${selector.name}\` for key: ${messageId}`, + err: `Found unexpected placeable name \`${selector.name}\` for ID: ${fmtMessageIdChain(messageIdChain)}`, }; } @@ -179,7 +212,7 @@ function compileMessage( ) { return { type: "err", - err: `Found unexpected placeable name \`${element.name}\` for key: ${messageId}`, + err: `Found unexpected placeable name \`${element.name}\` for ID: ${fmtMessageIdChain(messageIdChain)}`, }; } @@ -200,171 +233,215 @@ function compileMessage( if (allVariantsRes.type === "err") { return { type: "err", - err: `Failed to compute variants for key \`${messageId}\`:\n${allVariantsRes.err}`, + err: `Failed to compute variants for ID \`${fmtMessageIdChain(messageIdChain)}\`:\n${allVariantsRes.err}`, }; } const allVariants = allVariantsRes.ok; - // create function - const markdown = configValue.markdown; + switch (configValue[configMessageTypeSymbol]) { + case configStringSymbol: { + return compileStringMessage({ + bundle, + messageIdChain, + allVariants, + pattern, + }); + } + case configMarkdownSymbol: { + return compileMarkdownMessage({ + bundle, + configMarkdown: configValue, + messageIdChain, + allVariants, + pattern, + }); + } + } +} - if (markdown !== null) { - // typecheck markdown +interface CompileStringMessageCtx { + bundle: FluentBundle; + messageIdChain: readonly [...string[], string]; + allVariants: readonly PatternVariant[]; + pattern: Pattern; +} - const markdownSlots = markdown.slots ?? []; +function compileStringMessage( + ctx: CompileStringMessageCtx, +): Result { + const { bundle, messageIdChain, allVariants, pattern } = ctx; - // check if all variants are valid markdown - for (const variant of allVariants) { - const markdownLiteralRes = parseMessageLiteral( - "md", - variant.string, - ); + // typecheck string + // check if all variants are valid markdown + for (const variant of allVariants) { + const stringLiteralRes = parseMessageLiteral("string", variant.string); - if (markdownLiteralRes.type === "err") { - return { - type: "err", - err: `Invalid literal for variant \`${selectionChainToString(variant.selectionChain)}\` of key \`${messageId}\`:\n${markdownLiteralRes.err}`, - }; - } - - const markdownLiteral = markdownLiteralRes.ok; - const markdownRes = parseMarkdown(markdownLiteral, markdownSlots); - - if (markdownRes.type === "err") { - return { - type: "err", - err: `Invalid markdown for variant \`${selectionChainToString(variant.selectionChain)}\` of key \`${messageId}\`:\n${markdownRes.err}`, - }; - } + if (stringLiteralRes.type === "err") { + return { + type: "err", + err: `Invalid literal for variant \`${selectionChainToString(variant.selectionChain)}\` of ID \`${fmtMessageIdChain(messageIdChain)}\`:\n${stringLiteralRes.err}`, + }; } - // TODO: will need to make sure markdown/slots are escapes when inserting variable values - return { - type: "ok", - ok: (args: Record = {}) => { - const markdownLiteralRes = parseMessageLiteral( - "md", - bundle.formatPattern(pattern, args), - ); + const stringLiteral = stringLiteralRes.ok; + const stringRes = parseString(stringLiteral); + if (stringRes.type === "err") { + return { + type: "err", + err: `Invalid string for variant \`${selectionChainToString(variant.selectionChain)}\` of ID \`${fmtMessageIdChain(messageIdChain)}\`:\n${stringRes.err}`, + }; + } + } - if (markdownLiteralRes.type === "err") { - // This should hopefully never happen since we've already - // verified all message variants parse as valid markdown above - throw new Error( - `Failed to parse markdown literal after compilation!\n${markdownLiteralRes.err}`, - ); - } - - const markdownLiteral = markdownLiteralRes.ok; - const res = parseMarkdown(markdownLiteral, markdownSlots); - - if (res.type === "err") { - // This should hopefully never happen since we've already - // verified all message variants parse as valid markdown above - throw new Error( - `Failed to parse markdown after compilation!\n${res.err}`, - ); - } - - return res.ok; - }, - }; - } else { - // typecheck string - // check if all variants are valid markdown - for (const variant of allVariants) { + // TODO: will need to make sure markdown/slots are escapes when inserting variable values + return { + type: "ok", + ok: (args: Record = {}) => { const stringLiteralRes = parseMessageLiteral( "string", - variant.string, + bundle.formatPattern(pattern, args), ); if (stringLiteralRes.type === "err") { - return { - type: "err", - err: `Invalid literal for variant \`${selectionChainToString(variant.selectionChain)}\` of key \`${messageId}\`:\n${stringLiteralRes.err}`, - }; + // This should hopefully never happen since we've already + // verified all message variants parse as valid strings above + throw new Error( + `Failed to parse string literal after compilation!\n${stringLiteralRes.err}`, + ); } const stringLiteral = stringLiteralRes.ok; - const stringRes = parseString(stringLiteral); - if (stringRes.type === "err") { - return { - type: "err", - err: `Invalid string for variant \`${selectionChainToString(variant.selectionChain)}\` of key \`${messageId}\`:\n${stringRes.err}`, - }; + + const res = parseString(stringLiteral); + + if (res.type === "err") { + // This should hopefully never happen since we've already + // verified all message variants parse as valid strings above + // TODO: no we dont, do that + throw new Error( + `Failed to parse string after compilation!\n${res.err}`, + ); } + + return res.ok; + }, + }; +} + +interface CompileMarkdownMessageCtx { + bundle: FluentBundle; + messageIdChain: readonly [...string[], string]; + configMarkdown: ConfigMarkdown; + allVariants: readonly PatternVariant[]; + pattern: Pattern; +} + +function compileMarkdownMessage( + ctx: CompileMarkdownMessageCtx, +): Result { + const { bundle, messageIdChain, configMarkdown, allVariants, pattern } = + ctx; + + // typecheck markdown + + const markdownSlots = configMarkdown.features.slots; + + // check if all variants are valid markdown + for (const variant of allVariants) { + const markdownLiteralRes = parseMessageLiteral("md", variant.string); + + if (markdownLiteralRes.type === "err") { + return { + type: "err", + err: `Invalid literal for variant \`${selectionChainToString(variant.selectionChain)}\` of ID \`${fmtMessageIdChain(messageIdChain)}\`:\n${markdownLiteralRes.err}`, + }; } - // TODO: will need to make sure markdown/slots are escapes when inserting variable values - return { - type: "ok", - ok: (args: Record = {}) => { - const stringLiteralRes = parseMessageLiteral( - "string", - bundle.formatPattern(pattern, args), - ); + const markdownLiteral = markdownLiteralRes.ok; + const markdownRes = parseMarkdown( + markdownLiteral, + Object.keys(markdownSlots), + ); - if (stringLiteralRes.type === "err") { - // This should hopefully never happen since we've already - // verified all message variants parse as valid strings above - throw new Error( - `Failed to parse string literal after compilation!\n${stringLiteralRes.err}`, - ); - } - - const stringLiteral = stringLiteralRes.ok; - - const res = parseString(stringLiteral); - - if (res.type === "err") { - // This should hopefully never happen since we've already - // verified all message variants parse as valid strings above - // TODO: no we dont, do that - throw new Error( - `Failed to parse string after compilation!\n${res.err}`, - ); - } - - return res.ok; - }, - }; + if (markdownRes.type === "err") { + return { + type: "err", + err: `Invalid markdown for variant \`${selectionChainToString(variant.selectionChain)}\` of ID \`${fmtMessageIdChain(messageIdChain)}\`:\n${markdownRes.err}`, + }; + } } + + // TODO: will need to make sure markdown/slots are escapes when inserting variable values + return { + type: "ok", + ok: (args: Record = {}) => { + const markdownLiteralRes = parseMessageLiteral( + "md", + bundle.formatPattern(pattern, args), + ); + + if (markdownLiteralRes.type === "err") { + // This should hopefully never happen since we've already + // verified all message variants parse as valid markdown above + throw new Error( + `Failed to parse markdown literal after compilation!\n${markdownLiteralRes.err}`, + ); + } + + const markdownLiteral = markdownLiteralRes.ok; + const res = parseMarkdown( + markdownLiteral, + Object.keys(markdownSlots), + ); + + if (res.type === "err") { + // This should hopefully never happen since we've already + // verified all message variants parse as valid markdown above + throw new Error( + `Failed to parse markdown after compilation!\n${res.err}`, + ); + } + + return res.ok; + }, + }; } interface CompileSublocaleCtx { subconfig: Subconfig; - recordSublocale: L10nRecord | undefined; - fallbackSublocale: InferLocale | undefined; + uncompiledSublocale: UncompiledLocale | undefined; + fallbackSublocale: InferLocaleFromConfig | undefined; errors: string[]; bundle: FluentBundle; - messageId: string; + messageIdChain: readonly [...string[], string]; } function compileSublocale( ctx: CompileSublocaleCtx, -): InferLocale { +): InferLocaleFromConfig { const { subconfig: configValue, - recordSublocale: recordValue, + uncompiledSublocale: recordValue, fallbackSublocale: fallbackValue, errors, bundle, - messageId, + messageIdChain, } = ctx; const subrecord = recordValue; const compiledSubrecordRes = compileLocale({ bundle, - record: subrecord, + uncompiled: subrecord ?? {}, config: configValue, fallback: fallbackValue, + messageIdChain, }); errors.push( ...compiledSubrecordRes.errors.map( (err) => - `Error when compiling subrecord with key: \`${messageId}\`:\n${err}`, + `Error when compiling subrecord with ID: \`${fmtMessageIdChain(messageIdChain)}\`:\n${err}`, ), ); diff --git a/apps/vdn-static/src/new-i18n-lib/config.ts b/apps/vdn-static/src/new-i18n-lib/config.ts index e1805ec..9ce6cc5 100644 --- a/apps/vdn-static/src/new-i18n-lib/config.ts +++ b/apps/vdn-static/src/new-i18n-lib/config.ts @@ -1,14 +1,24 @@ import { type Markdown } from "./markdown"; -export const configMessageSymbol: unique symbol = Symbol("configMessage"); -export interface ConfigMessage< - Placeables extends { [name in string]?: ConfigPlaceableInfo } = object, - Markdown extends - ConfigMarkdown | null = ConfigMarkdown | null, +export const configMessageTypeSymbol: unique symbol = + Symbol("configMessageType"); + +export const configStringSymbol: unique symbol = Symbol("configString"); +export interface ConfigString< + Placeables extends Partial>, > { - [configMessageSymbol]: true; + [configMessageTypeSymbol]: typeof configStringSymbol; placeables: Placeables; - markdown: Markdown; +} + +export const configMarkdownSymbol: unique symbol = Symbol("configMarkdown"); +export interface ConfigMarkdown< + Placeables extends Partial>, + Slots extends Partial>, +> { + [configMessageTypeSymbol]: typeof configMarkdownSymbol; + placeables: Placeables; + features: ConfigMarkdownFeatures; } export interface ConfigPlaceableInfo< @@ -17,47 +27,56 @@ export interface ConfigPlaceableInfo< type: Type; } -export interface ConfigMarkdown { +// eslint-disable-next-line @typescript-eslint/no-empty-object-type +export interface ConfigSlotInfo {} + +export interface ConfigMarkdownFeatures< + Slots extends Partial>, +> { bold?: boolean; italic?: boolean; header?: boolean; link?: boolean; ulist?: boolean; - slots?: Slot[]; + slots: Slots; } -export function message< - const Placeables extends { - [name in string]?: ConfigPlaceableInfo; - } = object, - const Markdown extends ConfigMarkdown | null = null, +export function string< + const Placeables extends Partial>, >( - opt: Partial< - Omit< - ConfigMessage | Markdown>, - typeof configMessageSymbol - > - > = {}, -): ConfigMessage { + opt: Omit, typeof configMessageTypeSymbol>, +): ConfigString { return { - [configMessageSymbol]: true, - placeables: opt.placeables ?? {}, - markdown: opt.markdown ?? null, + [configMessageTypeSymbol]: configStringSymbol, + placeables: opt.placeables, }; } -export type LocaleConfig< - Placeables extends { [name in string]?: ConfigPlaceableInfo } = object, - Markdown extends - ConfigMarkdown | null = ConfigMarkdown | null, -> = { +export function markdown< + const Placeables extends Partial>, + const Slots extends Partial>, +>( + opt: Omit< + ConfigMarkdown, + typeof configMessageTypeSymbol + >, +): ConfigMarkdown { + return { + [configMessageTypeSymbol]: configMarkdownSymbol, + placeables: opt.placeables, + features: opt.features, + }; +} + +export type LocaleConfig = { [id: string]: - | ConfigMessage - | LocaleConfig; + | ConfigString + | ConfigMarkdown + | LocaleConfig; }; type MessageCtx< - Placeables extends { [name in string]?: ConfigPlaceableInfo } = object, + Placeables extends Partial>, > = { [K in keyof Placeables]: ResolvedPlaceableType< PlaceableType> @@ -72,18 +91,17 @@ type ResolvedPlaceableType = : Type extends "number" ? number : never; -export type InferLocale = { - [K in keyof Config]: Config[K] extends LocaleConfig ? InferLocale - : Config[K] extends ConfigMessage ? +export type InferLocaleFromConfig = { + [K in keyof Config]: Config[K] extends LocaleConfig ? + InferLocaleFromConfig + : Config[K] extends ConfigString ? object extends MessageCtx ? - () => null extends Md ? string - : Md extends ConfigMarkdown ? Markdown - : Md extends ConfigMarkdown ? Markdown - : never - : (ctx: MessageCtx) => null extends Md ? string - : Md extends ConfigMarkdown ? Markdown - : Md extends ConfigMarkdown ? Markdown - : never + () => string + : (ctx: MessageCtx) => string + : Config[K] extends ConfigMarkdown ? + object extends MessageCtx ? + () => Markdown + : (ctx: MessageCtx) => Markdown : never; }; diff --git a/apps/vdn-static/src/new-i18n-lib/setup.ts b/apps/vdn-static/src/new-i18n-lib/setup.ts index 0419f74..bbee3c8 100644 --- a/apps/vdn-static/src/new-i18n-lib/setup.ts +++ b/apps/vdn-static/src/new-i18n-lib/setup.ts @@ -36,21 +36,16 @@ export async function loadFluentBundle( return { type: "ok", ok: bundle }; } -export type L10nRecord = { +export type UncompiledLocale = { [id: string]: | { type: "message"; message: Message } - | { type: "subrecord"; subrecord: L10nRecord }; + | { type: "subrecord"; subrecord: UncompiledLocale }; }; -export interface UncompiledLocale { - bundle: FluentBundle; - record: L10nRecord; -} - -export function bundleToUncompiledLocale( +export function bundleToUncompiledLocaleRecord( bundle: FluentBundle, ): Result { - const record: L10nRecord = {}; + const record: UncompiledLocale = {}; for (const [id, message] of bundle._messages) { const idChain = id.split("-"); let subrecord = record; @@ -85,7 +80,7 @@ export function bundleToUncompiledLocale( } } - return { type: "ok", ok: { bundle, record } }; + return { type: "ok", ok: record }; } export type SelectionChain = (Literal | SelectionChain)[]; @@ -109,7 +104,7 @@ export function selectionChainToString(chain: SelectionChain): string { .join("+"); } -interface PatternVariant { +export interface PatternVariant { selectionChain: SelectionChain; string: string; } diff --git a/apps/vdn-static/src/new-i18n/config.ts b/apps/vdn-static/src/new-i18n/config.ts index 5facd72..015b0be 100644 --- a/apps/vdn-static/src/new-i18n/config.ts +++ b/apps/vdn-static/src/new-i18n/config.ts @@ -1,43 +1,62 @@ import { - message, + string, + markdown, record, - type InferLocale, + type InferLocaleFromConfig, type LocaleConfig, + type ConfigString, } from "@/new-i18n-lib/config"; -const homeSectionConfig = { title: message(), body: message() }; +function plainString(): ConfigString { + return string({ placeables: {} }); +} -const imageConfig = { alt: message() }; -export interface Image extends InferLocale {} +const homeSectionConfig = { title: plainString(), body: plainString() }; -const buttonConfig = { label: message() }; +const imageConfig = { alt: plainString() }; +export interface Image extends InferLocaleFromConfig {} + +const buttonConfig = { label: plainString() }; const resourceConfig = (buttonKeys: ButtonKey[]) => ({ - title: message(), - subtitle: message(), - desc: message(), + title: plainString(), + subtitle: plainString(), + desc: plainString(), buttons: record(buttonKeys, () => buttonConfig), }); const discordRuleConfig = { overview: { - text: message({ markdown: { bold: true, italic: true, link: true } }), - subtext: message({ - markdown: { bold: true, italic: true, link: true }, + text: markdown({ + placeables: {}, + features: { bold: true, italic: true, link: true, slots: {} }, + }), + subtext: markdown({ + placeables: {}, + features: { bold: true, italic: true, link: true, slots: {} }, }), }, section: { - header: message({ placeables: { ruleNumber: { type: "number" } } }), - body: message({ - markdown: { bold: true, header: true, italic: true, link: true }, + header: string({ placeables: { ruleNumber: { type: "number" } } }), + body: markdown({ + placeables: {}, + features: { + bold: true, + header: true, + italic: true, + link: true, + slots: {}, + }, }), }, }; export const localeConfig = { - localeName: message(), - vilanticLangs: record(["viossa", "wodox"], () => message()), - navbar: record(["whatIsViossa", "resources", "kotoba"], () => message()), + localeName: plainString(), + vilanticLangs: record(["viossa", "wodox"], () => plainString()), + navbar: record(["whatIsViossa", "resources", "kotoba"], () => + plainString(), + ), home: { sections: record( ["whatIsViossa", "historyOfViossa", "community"], @@ -46,15 +65,15 @@ export const localeConfig = { images: record(["viossaFlag"], () => imageConfig), }, resources: { - title: message(), + title: plainString(), resources: { discord: resourceConfig(["join", "rules"]) }, images: record(["discordLogo"], () => imageConfig), }, - kotoba: { title: message(), searchHelp: message() }, + kotoba: { title: plainString(), searchHelp: plainString() }, discord: { rulesPage: { - title: message(), - overview: { title: message(), help: message() }, + title: plainString(), + overview: { title: plainString(), help: plainString() }, rules: record( [ "noTranslation", diff --git a/apps/vdn-static/src/new-i18n/index.ts b/apps/vdn-static/src/new-i18n/index.ts index 6a7101a..861110f 100644 --- a/apps/vdn-static/src/new-i18n/index.ts +++ b/apps/vdn-static/src/new-i18n/index.ts @@ -1,6 +1,6 @@ -import { type InferLocale } from "@/new-i18n-lib/config"; +import { type InferLocaleFromConfig } from "@/new-i18n-lib/config"; import { - bundleToUncompiledLocale, + bundleToUncompiledLocaleRecord, loadFluentBundle, } from "@/new-i18n-lib/setup"; import { localeConfig } from "./config"; @@ -47,7 +47,7 @@ export const localeId = computed({ }, }); -export interface Locale extends InferLocale {} +export interface Locale extends InferLocaleFromConfig {} async function loadLocale( localeId: LocaleId, @@ -95,18 +95,21 @@ function setupLocale( return localeBundle; })(); - const uncompiledRes = bundleToUncompiledLocale(maybeFallbackedBundle); - if (uncompiledRes.type === "err") { - return uncompiledRes; + const uncompiledLocaleRecordRes = bundleToUncompiledLocaleRecord( + maybeFallbackedBundle, + ); + if (uncompiledLocaleRecordRes.type === "err") { + return uncompiledLocaleRecordRes; } - const uncompiled = uncompiledRes.ok; + const uncompiledLocaleRecord = uncompiledLocaleRecordRes.ok; const localeRes = compileLocale({ config: localeConfig, - bundle: uncompiled.bundle, - record: uncompiled.record, + bundle: maybeFallbackedBundle, + uncompiled: uncompiledLocaleRecord, fallback: fallbackLocale, + messageIdChain: [], }); console.error(localeRes.errors);