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';