From 89ce3e30564243ca79a201f20f600d6cd56607ef Mon Sep 17 00:00:00 2001 From: Joel Sahleen Date: Mon, 20 Jul 2026 19:35:06 -0600 Subject: [PATCH] implement: add TSDoc comments across the public API Document classes, properties, methods, types, and constants so TypeDoc can generate complete API docs. Export NoteTypes and project settings types referenced by the public surface. Refs #47 Co-authored-by: Cursor --- src/classes/MsgInterface/MsgInterface.ts | 25 +++++++- src/classes/MsgMessage/MsgMessage.ts | 51 +++++++++++++++ src/classes/MsgProject/MsgProject.ts | 57 ++++++++++++++++- src/classes/MsgResource/MsgResource.ts | 79 +++++++++++++++++++++++- src/classes/index.ts | 3 + src/index.ts | 8 ++- 6 files changed, 217 insertions(+), 6 deletions(-) diff --git a/src/classes/MsgInterface/MsgInterface.ts b/src/classes/MsgInterface/MsgInterface.ts index 512eccc..3a74ae4 100644 --- a/src/classes/MsgInterface/MsgInterface.ts +++ b/src/classes/MsgInterface/MsgInterface.ts @@ -1,7 +1,15 @@ -type NoteTypes = 'DESCRIPTION' | 'AUTHORSHIP' | 'PARAMETERS' | 'CONTEXT' | 'COMMENT'; +/** + * Categories of notes that can be attached to a message or resource. + */ +export type NoteTypes = 'DESCRIPTION' | 'AUTHORSHIP' | 'PARAMETERS' | 'CONTEXT' | 'COMMENT'; +/** + * A free-form note attached to a message or resource for translators and tools. + */ export type MsgNote = { + /** What kind of note this is. */ type: NoteTypes + /** The note text. */ content: string; } @@ -20,20 +28,35 @@ export type MsgFormat = 'MF1' | 'MF2' | 'NONE'; */ export const MSG_DEFAULT_FORMAT: MsgFormat = 'MF2'; +/** + * Locale and formatting metadata shared by messages and resources. + */ export type MsgAttributes = { + /** BCP 47 language tag for the content (for example, `en` or `zh-Hans`). */ lang?: string + /** Text direction: typically `ltr`, `rtl`, or `auto`. */ dir?: string + /** When true, the content should not be translated (Do Not Translate). */ dnt?: boolean + /** MessageFormat syntax used by the string, if any. */ format?: MsgFormat } +/** + * Default attribute values applied when none are provided. + */ export const DEFAULT_ATTRIBUTES: MsgAttributes = { lang: 'und', dir: 'auto', dnt: false } +/** + * Shared shape for objects that carry attributes and translator notes. + */ export interface MsgInterface { + /** Locale and formatting metadata. */ attributes: MsgAttributes + /** Notes for translators and tooling. */ notes: MsgNote[] } diff --git a/src/classes/MsgMessage/MsgMessage.ts b/src/classes/MsgMessage/MsgMessage.ts index d637f38..1c75254 100644 --- a/src/classes/MsgMessage/MsgMessage.ts +++ b/src/classes/MsgMessage/MsgMessage.ts @@ -2,13 +2,25 @@ import { MessageFormat, type MessageFormatOptions } from "messageformat"; import { mf1ToMessage } from "@messageformat/icu-messageformat-1"; import { MsgInterface, DEFAULT_ATTRIBUTES, MSG_DEFAULT_FORMAT, type MsgAttributes, type MsgFormat, type MsgNote } from "../MsgInterface/MsgInterface.js"; +/** + * Plain data used to create a {@link MsgMessage}. + */ export type MsgMessageData = { + /** Unique key that identifies this message within a resource. */ key: string + /** The message string, possibly in MessageFormat syntax. */ value: string + /** Optional locale and formatting metadata. */ attributes?: MsgAttributes; + /** Optional notes for translators and tooling. */ notes?: MsgNote[] } +/** + * A single localizable message with a key, value, attributes, and notes. + * + * Supports MessageFormat 1, MessageFormat 2, and unformatted (`NONE`) strings. + */ export class MsgMessage implements MsgInterface { private _key: string; private _value: string; @@ -16,6 +28,9 @@ export class MsgMessage implements MsgInterface { private _attributes: MsgAttributes; private _notes: MsgNote[] = []; + /** + * Creates a message from its parts. Prefer {@link MsgMessage.create}. + */ private constructor(key: string, value: string, attributes?: MsgAttributes, notes?: MsgNote[]) { this._key = key; this._value = value; @@ -30,28 +45,38 @@ export class MsgMessage implements MsgInterface { } + /** + * Builds a message from {@link MsgMessageData}. + */ static create(data: MsgMessageData) { const { key, value, attributes, notes } = data; const message = new MsgMessage(key, value, attributes, notes); return message; } + /** Unique key that identifies this message within a resource. */ public get key() { return this._key; } + /** The message string, possibly in MessageFormat syntax. */ public get value() { return this._value; } + /** Locale and formatting metadata for this message. */ public get attributes() { return this._attributes; } + /** Notes attached to this message for translators and tooling. */ public get notes() { return this._notes; } + /** + * Appends a note to this message. + */ public addNote(note: MsgNote) { this.notes.push(note); } @@ -63,6 +88,9 @@ export class MsgMessage implements MsgInterface { return this.attributes.format ?? MSG_DEFAULT_FORMAT; } + /** + * Returns a cached MessageFormat formatter for this message's value. + */ private getFormatter(options?: MessageFormatOptions): MessageFormat { if (!this._mf) { this._mf = this.resolveFormat() === 'MF1' @@ -72,6 +100,11 @@ export class MsgMessage implements MsgInterface { return this._mf; } + /** + * Formats the message with the given placeholder data. + * + * For `NONE` format messages, returns the raw value unchanged. + */ public format(data: Record, options?: MessageFormatOptions) { // NONE messages are not formatted; the raw string is returned as-is. if (this.resolveFormat() === 'NONE') { @@ -80,6 +113,11 @@ export class MsgMessage implements MsgInterface { return this.getFormatter(options).format(data); } + /** + * Formats the message into structured parts with the given placeholder data. + * + * For `NONE` format messages, returns a single text part with the raw value. + */ public formatToParts(data: Record, options?: MessageFormatOptions) { // NONE messages have no placeholders to resolve; return the raw text part. if (this.resolveFormat() === 'NONE') { @@ -88,6 +126,11 @@ export class MsgMessage implements MsgInterface { return this.getFormatter(options).formatToParts(data); } + /** + * Returns a plain data object for this message. + * + * @param stripNotes - When true, notes are omitted from the result. + */ public getData(stripNotes: boolean = false) { return { key: this.key, @@ -97,10 +140,18 @@ export class MsgMessage implements MsgInterface { } } + /** + * Returns the message value as a string. + */ public toString() { return this.value; } + /** + * Serializes this message to a formatted JSON string. + * + * @param stripNotes - When true, notes are omitted from the serialized data. + */ public toJSON(stripNotes: boolean = false) { return JSON.stringify(this.getData(stripNotes), null, 2); } diff --git a/src/classes/MsgProject/MsgProject.ts b/src/classes/MsgProject/MsgProject.ts index 4cec1ec..74c93ad 100644 --- a/src/classes/MsgProject/MsgProject.ts +++ b/src/classes/MsgProject/MsgProject.ts @@ -1,36 +1,66 @@ import { type MsgResourceData } from "../MsgResource/MsgResource.js"; import { type MsgFormat, MSG_DEFAULT_FORMAT } from "../MsgInterface/MsgInterface.js"; -type MsgProjectSettings = { +/** + * Project-level settings such as name, version, and default message format. + */ +export type MsgProjectSettings = { + /** Display name of the localization project. */ name: string + /** Optional project version number. */ version?: number + /** Default message format inherited by resources and messages. */ format?: MsgFormat }; -type MsgTargetLocalesSettings = { +/** + * Map of a requested locale to the chain of locales used to load translations. + */ +export type MsgTargetLocalesSettings = { [key: string]: string[] } -type MsgLocalesSettings = { +/** + * Locale configuration for a project: source, pseudo, and target locales. + */ +export type MsgLocalesSettings = { + /** Locale of the source (authoring) strings. */ sourceLocale: string + /** Locale used for pseudo-localization during development. */ pseudoLocale: string + /** Target locales and their fallback load chains. */ targetLocales: MsgTargetLocalesSettings }; +/** + * Async function that loads translated resource data for a project, title, and language. + */ export type MsgTranslationLoader = (project: string, title: string, lang: string) => Promise; +/** + * Plain data used to create a {@link MsgProject}. + */ export type MsgProjectData = { + /** Project name, version, and default format. */ project: MsgProjectSettings + /** Source, pseudo, and target locale settings. */ locales: MsgLocalesSettings + /** Function used to load translations for a locale. */ loader: MsgTranslationLoader }; +/** + * Default project settings applied when none are provided. + */ const defaultProjectSettings: MsgProjectSettings = { name: 'messages', version: 1, format: MSG_DEFAULT_FORMAT }; +/** + * Default locale settings applied when none are provided. + */ const defaultLocalesSettings: MsgLocalesSettings = { sourceLocale: 'en', pseudoLocale: 'en-XA', @@ -39,16 +69,31 @@ const defaultLocalesSettings: MsgLocalesSettings = { } }; +/** + * A localization project that holds settings, locales, and a translation loader. + * + * Resources are created against a project so they can inherit format defaults + * and load translations through the project's loader. + */ export class MsgProject { + /** Project name, version, and default format. */ _project: MsgProjectSettings; + /** Source, pseudo, and target locale settings. */ _locales: MsgLocalesSettings; + /** Function used to load translations for a locale. */ _loader: MsgTranslationLoader; + /** + * Builds a project from {@link MsgProjectData}. + */ static create(data: MsgProjectData) { const { project, locales, loader } = data; return new MsgProject(project, locales, loader); } + /** + * Creates a project from its settings and loader. Prefer {@link MsgProject.create}. + */ private constructor( projectSettings: MsgProjectSettings, localesSettings: MsgLocalesSettings, @@ -59,14 +104,17 @@ export class MsgProject { this._loader = loader; } + /** Project name, version, and default format. */ public get project() { return this._project; } + /** Source, pseudo, and target locale settings. */ public get locales() { return this._locales; } + /** Function used to load translations for a locale. */ public get loader() { return this._loader; } @@ -79,6 +127,9 @@ export class MsgProject { return this._project.format ?? MSG_DEFAULT_FORMAT; } + /** + * Returns the translation load chain for a locale, or `undefined` if unsupported. + */ public getTargetLocale(locale: string): string[] | undefined { return this._locales.targetLocales[locale]; } diff --git a/src/classes/MsgResource/MsgResource.ts b/src/classes/MsgResource/MsgResource.ts index 24e88b8..849f9a2 100644 --- a/src/classes/MsgResource/MsgResource.ts +++ b/src/classes/MsgResource/MsgResource.ts @@ -4,13 +4,26 @@ import { type MsgMessageData, MsgMessage } from "../MsgMessage/MsgMessage.js"; import { DEFAULT_ATTRIBUTES, MSG_DEFAULT_FORMAT, MsgInterface, type MsgAttributes, type MsgNote } from "../MsgInterface/MsgInterface.js"; import { MsgProject } from "../MsgProject/MsgProject.js"; +/** + * Plain data used to create or describe a {@link MsgResource}. + */ export type MsgResourceData = { + /** Title that identifies this resource within a project. */ title: string + /** Locale and formatting metadata for the resource. */ attributes: MsgAttributes + /** Optional notes for translators and tooling. */ notes?: MsgNote[] + /** Optional list of messages in this resource. */ messages?: MsgMessageData[] } +/** + * A named collection of messages belonging to a {@link MsgProject}. + * + * Resources inherit format defaults from their project, support translation + * loading (including pseudo-localization), and can serialize to plain data. + */ export class MsgResource extends Map implements MsgInterface { private _attributes: MsgAttributes = {}; @@ -19,6 +32,12 @@ export class MsgResource extends Map implements MsgInterface private _project: MsgProject; + /** + * Builds a resource from {@link MsgResourceData} and an owning project. + * + * When `messages` is provided, each entry is added to the resource. + * When omitted, the resource starts empty. + */ static create(data: MsgResourceData, project: MsgProject ) { const { title, attributes, notes, messages} = data; const res = new MsgResource(title, attributes, project, notes); @@ -35,6 +54,9 @@ export class MsgResource extends Map implements MsgInterface return res; } + /** + * Creates a resource bound to a project. Prefer {@link MsgResource.create}. + */ private constructor (title: string, attributes: MsgAttributes, project: MsgProject, notes?: MsgNote[]) { super(); this._title = title; @@ -49,6 +71,9 @@ export class MsgResource extends Map implements MsgInterface } + /** + * Returns true when the message's attributes match this resource's attributes. + */ private hasMatchingAttributes(message: MsgMessage): boolean { const res = this.attributes; const msg = message.attributes; @@ -58,10 +83,16 @@ export class MsgResource extends Map implements MsgInterface && this.resolveFormat(res) === this.resolveFormat(msg); } + /** + * Resolves a format from attributes, falling back to the library default. + */ private resolveFormat(attributes: MsgAttributes) { return attributes.format ?? MSG_DEFAULT_FORMAT; } + /** + * Pseudo-localizes an MF2 message by localizing only literal text parts. + */ private pseudoLocalizeMF2( source: string, options?: { strategy?: "accented" | "bidi" } @@ -82,38 +113,55 @@ export class MsgResource extends Map implements MsgInterface return stringifyMessage(msg); } + /** Locale and formatting metadata for this resource. */ public get attributes() { return this._attributes; } + /** Replaces the resource's locale and formatting metadata. */ public set attributes(attributes: MsgAttributes) { this._attributes = attributes; } + /** Notes attached to this resource for translators and tooling. */ public get notes() { return this._notes; } + /** Replaces the notes attached to this resource. */ public set notes(notes: MsgNote[]) { this._notes = notes; } + /** + * Appends a note to this resource. + */ public addNote(note: MsgNote) { this.notes.push(note); } + /** Title that identifies this resource within a project. */ public get title() { return this._title; } + /** Sets the title that identifies this resource within a project. */ public set title(title: string) { this._title = title; } + /** + * Returns the project this resource belongs to. + */ public getProject(): MsgProject { return this._project; } + /** + * Adds a message to this resource, merging resource attributes with any overrides. + * + * @returns This resource, for chaining. + */ public add(key: string, value: string, attributes?: MsgAttributes, notes?: MsgNote[]) { const merged = {...this.attributes, ...attributes}; @@ -128,6 +176,14 @@ export class MsgResource extends Map implements MsgInterface return this; } + /** + * Builds a translated copy of this resource from translation data. + * + * Messages present in the translation replace the source; missing keys keep + * the source message. Notes are carried over from the source. + * + * @throws {TypeError} When the translation title does not match this resource. + */ public translate(data: MsgResourceData) { const {title, attributes, messages} = data; @@ -183,6 +239,14 @@ export class MsgResource extends Map implements MsgInterface return { ...attributes, format }; } + /** + * Loads and applies translations for a locale via the project loader. + * + * When `lang` is the project's pseudo-locale, returns a pseudo-localized + * copy without calling the loader. + * + * @throws {Error} When the locale is unsupported or its language chain is empty. + */ public async getTranslation(lang: string) { const project = this._project; @@ -227,6 +291,14 @@ export class MsgResource extends Map implements MsgInterface return translated; } + /** + * Returns a plain data object for this resource and its messages. + * + * Attributes that match the resource or project defaults are omitted where + * possible to keep the output compact. + * + * @param stripNotes - When true, notes are omitted from the result. + */ public getData(stripNotes: boolean = false): MsgResourceData { const resourceFormat = this.resolveFormat(this.attributes); @@ -267,8 +339,13 @@ export class MsgResource extends Map implements MsgInterface } } + /** + * Serializes this resource to a formatted JSON string. + * + * @param stripNotes - When true, notes are omitted from the serialized data. + */ public toJSON(stripNotes: boolean = false) { return JSON.stringify(this.getData(stripNotes), null, 2); } -} \ No newline at end of file +} diff --git a/src/classes/index.ts b/src/classes/index.ts index 73076eb..c69f90a 100644 --- a/src/classes/index.ts +++ b/src/classes/index.ts @@ -1,3 +1,6 @@ +/** + * Barrel export for the public msg classes and shared types. + */ export * from './MsgInterface/MsgInterface.js'; export * from './MsgMessage/MsgMessage.js'; export * from './MsgResource/MsgResource.js'; diff --git a/src/index.ts b/src/index.ts index ae2aaf6..4771dfb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1 +1,7 @@ -export * from './classes/index.js'; \ No newline at end of file +/** + * @worldware/msg — message localization tooling. + * + * Re-exports the public classes, types, and constants for building projects, + * resources, and messages. + */ +export * from './classes/index.js';