From b26ea3123d3bfeafb76819d9df29fe12c8881b15 Mon Sep 17 00:00:00 2001 From: Makito Date: Mon, 3 Aug 2026 02:01:43 +0900 Subject: [PATCH] feat(i18n): set up glossary (#2198) This pull request introduces the initial glossary for the project to improve the translation quality. Implements #2148. ### TODOs - [x] Add the definition YAML - [x] Add a generator to produce Crowdin-compatible (TBX v3) format - [x] AGENTS.md - [ ] ~Workflow~ (Split to another PR) - [ ] ~Skills~ (Same as above) --- .gitignore | 4 + AGENTS.md | 31 ++ cspell.config.yaml | 2 + packages/i18n/glossary/build-tbx.test.ts | 284 ++++++++++++++ packages/i18n/glossary/build-tbx.ts | 211 ++++++++++ packages/i18n/glossary/schema.ts | 119 ++++++ packages/i18n/glossary/terms.yaml | 476 +++++++++++++++++++++++ packages/i18n/package.json | 9 +- packages/i18n/vitest.config.ts | 7 + pnpm-lock.yaml | 268 +++++++++++-- vitest.config.ts | 1 + 11 files changed, 1388 insertions(+), 24 deletions(-) create mode 100644 packages/i18n/glossary/build-tbx.test.ts create mode 100644 packages/i18n/glossary/build-tbx.ts create mode 100644 packages/i18n/glossary/schema.ts create mode 100644 packages/i18n/glossary/terms.yaml create mode 100644 packages/i18n/vitest.config.ts diff --git a/.gitignore b/.gitignore index ffa8d7166..17ed0cbaa 100644 --- a/.gitignore +++ b/.gitignore @@ -143,3 +143,7 @@ apps/stage-tamagotchi/electron.vite.config.*.mjs docs/ai/context/verifications/ .playwright-mcp/ + +# Generated from packages/i18n/glossary/terms.yaml at upload time. Never committed, so it +# cannot drift from its source. +packages/i18n/glossary/glossary.tbx diff --git a/AGENTS.md b/AGENTS.md index b9a5675fc..0b142fafd 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -143,6 +143,37 @@ Concise but detailed reference for contributors working across the `moeru-ai/air - Add/modify translations in `packages/i18n`; avoid scattering i18n across apps/packages. +### Glossary + +`packages/i18n/glossary/terms.yaml` gives the approved English term for each product concept. +`pnpm -F @proj-airi/i18n glossary:build` writes the TBX file that Crowdin imports. `schema.ts` +documents each field. + +- Read `terms.yaml` before you write or change a string that a user sees. Use the term it gives. +- Do not edit `packages/i18n/glossary/translations/`. The next Crowdin download replaces it. +- Take terms from the interface. Search the documentation to confirm a term, not to find one. + +**Add a term when it prevents one of two failures:** + +- A translator who reads only the source string chooses the wrong word. +- Two translators choose different words, and both words are correct. + +**Do not add a term when:** + +- It is code. A translated control token, JSON key, file name, or package name breaks the + application, and the application shows no error. +- It is a common English word with its usual meaning, such as "Speed" or "Volume". +- Its parts each have an entry and it says no more than its parts, such as "VRM model". +- One feature uses it, and only two translators could disagree about it. But keep a rare term if a + translator can get it wrong: `Tachie` occurs two times and is the strongest entry in the file. + +`terms.yaml` holds no rationale, because each field must map to a TBX element. Give the reason in +the pull request. + +**Write a definition in one sentence.** Describe the thing, not the word, and put a negative rule in +the `note`. Keep each sentence short and active: translators read them, and most do not read English +as a first language. + ## CSS/UNO - Use/extend UnoCSS shortcuts in `uno.config.ts`. diff --git a/cspell.config.yaml b/cspell.config.yaml index ebaa026e2..0716355ec 100644 --- a/cspell.config.yaml +++ b/cspell.config.yaml @@ -52,6 +52,7 @@ words: - clippy - clustr - cmdorctrl + - codenames - codesign - collectblock - colorjs @@ -189,6 +190,7 @@ words: - MeshToonMaterial - meteocons - micvad + - MikuMikuDance - mineflayer - mingcute - minimizable diff --git a/packages/i18n/glossary/build-tbx.test.ts b/packages/i18n/glossary/build-tbx.test.ts new file mode 100644 index 000000000..97449ad85 --- /dev/null +++ b/packages/i18n/glossary/build-tbx.test.ts @@ -0,0 +1,284 @@ +import type { Concept } from './schema' + +import { readdir, readFile } from 'node:fs/promises' +import { dirname, join } from 'node:path' +import { fileURLToPath } from 'node:url' + +import { describe, expect, it } from 'vitest' +import { parse } from 'yaml' + +import { buildTbx } from './build-tbx' +import { parseGlossary } from './schema' + +const here = dirname(fileURLToPath(import.meta.url)) + +function concept(overrides: Partial = {}): Concept { + return { + id: 'stage', + subject: 'application', + translatable: true, + definition: 'The surface of the application that shows the character.', + terms: [{ 'text': 'Stage', 'part-of-speech': 'noun', 'status': 'preferred' }], + ...overrides, + } +} + +describe('buildTbx', () => { + it('writes the document shape that a Crowdin export uses', () => { + const xml = buildTbx([concept()], 'test') + + expect(xml.startsWith('\n')).toBe(true) + expect(xml).toContain('TBXcoreStructV03_TBX-Basic_integrated.rng') + expect(xml).toContain('TBX-Basic_DCA.sch') + expect(xml).toContain('xmlns="urn:iso:std:iso:30042:ed-2"') + expect(xml).toContain('type="TBX-Basic"') + expect(xml).toContain('style="dca"') + expect(xml).toContain('

test

') + expect(xml).toContain('') + expect(xml).toContain('') + // Crowdin's header carries a sourceDesc and nothing else. + expect(xml).not.toContain('') + }) + + it('indents the elements the way a Crowdin export does', () => { + const xml = buildTbx([concept()], 'test') + + expect(xml).toContain('\n ') + expect(xml).toContain('\n ') + // Whitespace inside a definition is content, so it must not gain indentation. + expect(xml).toContain('The surface of the application that shows the character.') + }) + + it('puts langSec after the concept-level children', () => { + const xml = buildTbx([concept()], 'test') + + // TBX-Basic fixes this order. A langSec before a descrip is invalid, and a schema + // validator rejects the file rather than importing it partly. + expect(xml.indexOf('subjectField')).toBeLessThan(xml.indexOf(' { + const xml = buildTbx([concept()], 'test') + + expect(xml.indexOf('')).toBeLessThan(xml.indexOf('partOfSpeech')) + expect(xml.indexOf('partOfSpeech')).toBeLessThan(xml.indexOf('administrativeStatus')) + }) + + it('writes the translatable flag that Crowdin reads', () => { + // A Crowdin export carries the flag as a concept-level descrip, ahead of everything + // else. TBX-Basic has no such data category, so this is a Crowdin extension. + expect(buildTbx([concept({ translatable: false })], 'test')) + .toContain('no') + expect(buildTbx([concept({ translatable: true })], 'test')) + .toContain('yes') + }) + + it('puts the translatable flag before the other concept children', () => { + const xml = buildTbx([concept()], 'test') + + expect(xml.indexOf('type="translatable"')).toBeLessThan(xml.indexOf('type="subjectField"')) + }) + + it('writes the context of each term that has one', () => { + const xml = buildTbx([concept({ + terms: [ + { 'text': 'Stage', 'part-of-speech': 'noun', 'status': 'preferred', 'context': 'Disable Stage Transitions' }, + { 'text': 'Scene', 'part-of-speech': 'noun', 'status': 'deprecated', 'type': 'variant' }, + ], + })], 'test') + + // TBX-Basic allows a context at term level only, so it belongs to the term whose + // sentence it is rather than to the concept. + expect(xml).toContain('Disable Stage Transitions') + expect(xml.match(/type="context"/g)).toHaveLength(1) + }) + + it('puts the context and the note after the termNote elements', () => { + const xml = buildTbx([concept({ + terms: [{ 'text': 'Stage', 'part-of-speech': 'noun', 'status': 'preferred', 'context': 'A sentence', 'note': 'A note' }], + })], 'test') + + expect(xml.indexOf('administrativeStatus')).toBeLessThan(xml.indexOf('type="context"')) + expect(xml.indexOf('type="context"')).toBeLessThan(xml.indexOf('A note')) + }) + + it('maps each status to the suffixed value that Crowdin writes', () => { + const xml = buildTbx([concept({ + terms: [ + { 'text': 'Character Card', 'part-of-speech': 'noun', 'status': 'preferred' }, + { 'text': 'Card', 'part-of-speech': 'noun', 'status': 'admitted', 'type': 'shortForm' }, + { 'text': 'Deck', 'part-of-speech': 'noun', 'status': 'deprecated', 'gender': 'feminine' }, + ], + })], 'test') + + expect(xml).toContain('preferredTerm-admn-sts') + expect(xml).toContain('admittedTerm-admn-sts') + expect(xml).toContain('deprecatedTerm-admn-sts') + expect(xml).toContain('shortForm') + expect(xml).toContain('feminine') + }) + + it('escapes the characters that would otherwise break the XML', () => { + const xml = buildTbx([concept({ definition: 'A tag & an "attribute".' })], 'test') + + expect(xml).toContain('<b>') + expect(xml).toContain('&') + expect(xml).not.toContain('') + }) + + it('collapses the wrapping of a YAML folded block but keeps the paragraphs', () => { + const note = parse('note: >\n One\n line.\n\n A second\n paragraph.\n').note as string + const xml = buildTbx([concept({ note })], 'test') + + expect(note).toBe('One line.\nA second paragraph.\n') + expect(xml).toContain('One line.\n\nA second paragraph.') + }) +}) + +describe('parseGlossary', () => { + it('rejects two concepts that share an id', () => { + expect(() => parseGlossary([concept(), concept()])).toThrow(/unique/) + }) + + it('allows more than one preferred term, as Crowdin does', () => { + // The export from project 816610 gives both `AIRI` and `Project AIRI` the preferred + // status inside one concept, so nothing here may forbid it. + const twoPreferred = concept({ + terms: [ + { 'text': 'AIRI', 'part-of-speech': 'proper noun', 'status': 'preferred' }, + { 'text': 'Project AIRI', 'part-of-speech': 'proper noun', 'status': 'preferred' }, + ], + }) + + expect(parseGlossary([twoPreferred])).toHaveLength(1) + }) + + it('accepts the spelling of proper noun that Crowdin writes', () => { + // ROOT CAUSE: + // + // An earlier version required `properNoun`, which is what the TBX-Basic specification + // says, and rejected "proper noun" with a space. A glossary exported from project 816610 + // writes the spaced form on every proper noun, so the requirement was backwards: it + // would have rejected the data Crowdin itself produces. + const spaced = concept({ terms: [{ 'text': 'AIRI', 'part-of-speech': 'proper noun', 'status': 'preferred' }] }) + + expect(parseGlossary([spaced])).toHaveLength(1) + expect(() => parseGlossary([concept({ terms: [{ 'text': 'AIRI', 'part-of-speech': 'properNoun', 'status': 'preferred' }] })])).toThrow() + }) + + it('rejects an id that is not kebab case', () => { + expect(() => parseGlossary([concept({ id: 'Stage Profile' })])).toThrow(/kebab/) + }) +}) + +describe('terms.yaml', () => { + async function loadConcepts(): Promise { + return parseGlossary(parse(await readFile(join(here, 'terms.yaml'), 'utf8'))) + } + + /** Every scalar leaf of the English locale, which is what a user reads. */ + async function loadEnglishStrings(): Promise { + const root = join(here, '..', 'src', 'locales', 'en') + const found: string[] = [] + + function walk(node: unknown): void { + if (typeof node === 'string') + found.push(node) + else if (Array.isArray(node)) + node.forEach(walk) + else if (node && typeof node === 'object') + Object.values(node).forEach(walk) + } + + async function read(directory: string): Promise { + for (const entry of await readdir(directory, { withFileTypes: true })) { + const path = join(directory, entry.name) + if (entry.isDirectory()) + await read(path) + else if (entry.name.endsWith('.yaml')) + walk(parse(await readFile(path, 'utf8'))) + } + } + + await read(root) + return found + } + + it('validates against the schema', async () => { + const concepts = await loadConcepts() + + expect(concepts.length).toBeGreaterThan(0) + }) + + it('produces TBX for every concept', async () => { + const concepts = await loadConcepts() + const xml = buildTbx(concepts, 'test') + + expect(xml.match(/ { + const concepts = await loadConcepts() + + // The glossary shares one Crowdin container with the documentation terminology, so a + // term may live in prose rather than in a string. `Project AIRI` appears six times in + // README.md and in no interface string, and `Neuro Sama` only under docs/content/en. + // + // The check still has teeth. A term that appears in none of these places can never match + // in the editor, so it cannot help a translator. `Ayaka Neko` was such a term, and it is + // the reason this check runs over the documentation rather than being deleted. + const repository = join(here, '..', '..', '..') + const prose = [await readFile(join(repository, 'README.md'), 'utf8')] + + async function readDocs(directory: string): Promise { + for (const entry of await readdir(directory, { withFileTypes: true })) { + const path = join(directory, entry.name) + if (entry.isDirectory()) + await readDocs(path) + else if (entry.name.endsWith('.md')) + prose.push(await readFile(path, 'utf8')) + } + } + await readDocs(join(repository, 'docs', 'content', 'en')) + + const haystack = [...await loadEnglishStrings(), ...prose].join('\n').toLowerCase() + + // The `documentation` concepts were adopted from the Crowdin glossary, and Crowdin is + // where they are maintained. Repository text is not their source, so checking them + // against it would delete data this file exists to preserve. `Ayaka Neko` is one: the + // reversed name order, which no file here writes. + const missing = concepts + .filter(c => c.subject !== 'documentation') + .flatMap(c => c.terms.filter(t => !haystack.includes(t.text.toLowerCase())).map(t => t.text)) + + expect(missing).toEqual([]) + }) + + it('gives every context the term it belongs to', async () => { + const concepts = await loadConcepts() + + // A context shows the term in use. Usually it is a real interface string, but for the + // two codenames it cannot be: every interface string containing them is one this + // glossary rules against. So the check is not "does the locale contain it" but "does it + // mention its own term", which catches a sentence pasted onto the wrong entry. + const unrelated = concepts.flatMap(c => c.terms + .filter(t => t.context && !t.context.toLowerCase().includes(t.text.toLowerCase())) + .map(t => `${c.id} / ${t.text}`)) + + expect(unrelated).toEqual([]) + }) + + it('holds no control token from the system prompt', async () => { + const concepts = await loadConcepts() + + // base.yaml carries the streaming control tokens to the translators. Those are code. + // A translated token breaks stage playback with no error message, so a token must never + // reach the glossary and become a term a translator is invited to work on. + const code = concepts.flatMap(c => c.terms + .filter(t => ['ACT', 'DELAY', 'CALL'].includes(t.text) || /[/{}<>]/.test(t.text)) + .map(t => t.text)) + + expect(code).toEqual([]) + }) +}) diff --git a/packages/i18n/glossary/build-tbx.ts b/packages/i18n/glossary/build-tbx.ts new file mode 100644 index 000000000..d73fed2d3 --- /dev/null +++ b/packages/i18n/glossary/build-tbx.ts @@ -0,0 +1,211 @@ +// cspell:words descrip +import type { Element } from 'xast' + +import type { Concept, Term } from './schema' + +import process from 'node:process' + +import { readFile, writeFile } from 'node:fs/promises' +import { dirname, join } from 'node:path' +import { fileURLToPath } from 'node:url' + +import { toXml } from 'xast-util-to-xml' +import { x } from 'xastscript' +import { parse } from 'yaml' + +import { parseGlossary } from './schema' + +/** + * The XML namespace of ISO 30042:2019, which the tooling calls TBX version 3. The `ed-2` + * segment is the second edition of the ISO standard, not the second version of TBX. + */ +const TBX_NAMESPACE = 'urn:iso:std:iso:30042:ed-2' + +/** The source language of the glossary. Crowdin owns every other language. */ +const SOURCE_LANGUAGE = 'en' + +/** + * The two schemas that Crowdin names in the exports it produces. + * + * They are processing instructions rather than elements, so an XML editor can validate the + * file. Writing the same pair keeps a diff against a re-download small. + */ +const XML_MODELS = [ + '', + '', +] + +/** + * How Crowdin writes an acceptability rating. + * + * NOTICE: + * The suffixed forms come from ISO 12620, and a Crowdin export writes them literally. Only + * `preferredTerm-admn-sts` is confirmed by the export we have, because every term in it is + * preferred. The other two follow the same pattern, and the first import should confirm + * them. + */ +const ADMINISTRATIVE_STATUS: Record = { + preferred: 'preferredTerm-admn-sts', + admitted: 'admittedTerm-admn-sts', + deprecated: 'deprecatedTerm-admn-sts', +} + +/** + * Turns the text of a YAML folded block into paragraphs. + * + * A folded block (`>`) joins each wrapped line with a space and turns a blank line into one + * newline character. So a newline that survives parsing is always a paragraph break, and + * the line width of the source file never reaches the output. + * + * NOTICE: + * Splitting on two or more newlines looks correct and is wrong. YAML gives a blank line a + * single newline, so that pattern matches nothing and every paragraph merges into one. + * + * Before: + * - "One paragraph.\nA second paragraph." + * + * After: + * - "One paragraph.\n\nA second paragraph." + */ +function collapse(text: string): string { + return text.split(/\n+/).map(paragraph => paragraph.replace(/\s+/g, ' ').trim()).filter(Boolean).join('\n\n') +} + +/** + * Builds one `conceptEntry` element. + * + * TBX-Basic fixes the order inside each section, and the order below is not cosmetic: + * + * - inside `conceptEntry`, every `langSec` comes after the other children, + * - inside `langSec`, every `termSec` comes after the other children, + * - inside `termSec`, `term` comes first, then the `termNote` elements, then the rest. + * + */ +function buildConceptEntry(concept: Concept): Element { + const conceptChildren: Element[] = [ + // Crowdin's own export writes the flag first, as a concept-level descrip with the value + // "yes" or "no". This is a Crowdin extension: TBX-Basic has no such data category. + x('descrip', { type: 'translatable' }, concept.translatable ? 'yes' : 'no'), + x('descrip', { type: 'subjectField' }, concept.subject), + x('descrip', { type: 'definition' }, collapse(concept.definition)), + ] + + if (concept.note) + conceptChildren.push(x('note', collapse(concept.note))) + + if (concept.url) + conceptChildren.push(x('xref', { type: 'externalCrossReference', target: concept.url }, concept.url)) + + const termSections = concept.terms.map((term) => { + const termChildren: Element[] = [ + x('term', term.text), + x('termNote', { type: 'partOfSpeech' }, term['part-of-speech']), + x('termNote', { type: 'administrativeStatus' }, ADMINISTRATIVE_STATUS[term.status]), + ] + + if (term.type) + termChildren.push(x('termNote', { type: 'termType' }, term.type)) + + if (term.gender) + termChildren.push(x('termNote', { type: 'grammaticalGender' }, term.gender)) + + // TBX-Basic fixes the order inside a termSec: term, then every termNote, then the rest. + if (term.context) + termChildren.push(x('descrip', { type: 'context' }, term.context)) + + if (term.note) + termChildren.push(x('note', collapse(term.note))) + + return x('termSec', termChildren) + }) + + conceptChildren.push(x('langSec', { 'xml:lang': SOURCE_LANGUAGE }, termSections)) + + return x('conceptEntry', { id: concept.id }, conceptChildren) +} + +/** + * Renders the glossary as a TBX-Basic document. + * + * The result is a complete XML document with the declaration, so a caller writes it to disk + * unchanged. It carries the English terms only; Crowdin owns every other language, and + * uploading target terms would overwrite the work of a translator. + * + * @param concepts Validated concepts, in the order they appear in `terms.yaml`. + * @param source A description of where the data came from, recorded in the TBX header. + */ +export function buildTbx(concepts: Concept[], source: string): string { + const tree = x( + 'tbx', + // The root carries all four attributes that TBX-Basic requires. `style="dca"` selects + // the data-category-as-attribute style, which is the one the schema values assume. + { 'type': 'TBX-Basic', 'style': 'dca', 'xml:lang': SOURCE_LANGUAGE, 'xmlns': TBX_NAMESPACE }, + [ + // The header carries a sourceDesc and nothing else, which is the shape Crowdin writes. + x('tbxHeader', [x('fileDesc', [x('sourceDesc', [x('p', source)])])]), + x('text', [x('body', concepts.map(buildConceptEntry))]), + ], + ) + + indent(tree, 0) + + return ['', ...XML_MODELS, toXml(tree), ''].join('\n') +} + +/** + * Adds whitespace between the children of an element, in place. + * + * Crowdin writes its exports with four-space indentation, and the serializer produces one + * long line. Matching the layout keeps a diff against a re-download readable. + * + * An element whose children include text is left alone, because whitespace inside a + * definition or a note is content, not layout. + */ +function indent(node: Element, depth: number): void { + const children = node.children.filter(child => child.type === 'element') + if (children.length !== node.children.length || children.length === 0) + return + + const inner = `\n${' '.repeat(depth + 1)}` + const outer = `\n${' '.repeat(depth)}` + + for (const child of children) + indent(child, depth + 1) + + node.children = [ + ...children.flatMap(child => [{ type: 'text' as const, value: inner }, child]), + { type: 'text' as const, value: outer }, + ] +} + +/** + * Reads `terms.yaml`, validates it, and writes the TBX file. + * + * Call stack: + * + * main + * -> {@link parseGlossary} + * -> {@link buildTbx} + * -> buildConceptEntry + * -> composeNote + */ +async function main(): Promise { + const here = dirname(fileURLToPath(import.meta.url)) + const input = join(here, 'terms.yaml') + const output = process.argv[2] ?? join(here, 'glossary.tbx') + + const concepts = parseGlossary(parse(await readFile(input, 'utf8'))) + await writeFile(output, buildTbx(concepts, 'packages/i18n/glossary/terms.yaml'), 'utf8') + + const terms = concepts.reduce((total, concept) => total + concept.terms.length, 0) + process.stdout.write(`wrote ${output}: ${concepts.length} concepts, ${terms} terms\n`) +} + +// Only run when this file is the entry point, so that the test can import buildTbx without +// writing a file. +if (process.argv[1] && import.meta.url === `file://${process.argv[1]}`) { + main().catch((error: unknown) => { + process.stderr.write(`${String(error)}\n`) + process.exitCode = 1 + }) +} diff --git a/packages/i18n/glossary/schema.ts b/packages/i18n/glossary/schema.ts new file mode 100644 index 000000000..13502bda3 --- /dev/null +++ b/packages/i18n/glossary/schema.ts @@ -0,0 +1,119 @@ +import * as v from 'valibot' + +/** + * Part of speech, spelled the way Crowdin spells it. + * + * NOTICE: + * TBX-Basic writes this value as `properNoun` (DC-0396), and Crowdin writes it as "proper + * noun" with a space. Crowdin reads what we upload, so Crowdin wins. Confirmed against a + * glossary exported from project 816610, where every proper noun reads "proper noun". + */ +const PartOfSpeechSchema = v.picklist([ + 'adjective', + 'adposition', + 'adverb', + 'auxiliary', + 'coordinating conjunction', + 'determiner', + 'interjection', + 'noun', + 'numeral', + 'particle', + 'pronoun', + 'proper noun', + 'subordinating conjunction', + 'verb', + 'other', +]) + +/** Term type. TBX-Basic allows these values and no others (DC-2677). */ +const TermTypeSchema = v.picklist(['fullForm', 'acronym', 'abbreviation', 'shortForm', 'variant', 'phrase']) + +/** + * How acceptable a term is (DC-0070). ISO 704:2022 calls it an acceptability rating. + * + * These are the readable forms. The generator maps them to the suffixed values Crowdin + * writes, such as `preferredTerm-admn-sts`. Crowdin permits more than one preferred term in + * a concept, so nothing here limits the count. + */ +const UsageStatusSchema = v.picklist(['preferred', 'admitted', 'deprecated']) + +/** Grammatical gender (DC-0245). */ +const GenderSchema = v.picklist(['masculine', 'feminine', 'neuter', 'other']) + +/** + * One term of a concept. + * + * Every field maps to one TBX element, and the field names follow TBX rather than any name + * of our own, so that the mapping stays obvious: + * + * text -> + * part-of-speech -> + * status -> + * type -> + * gender -> + * context -> + * note -> + */ +const TermSchema = v.object({ + 'text': v.pipe(v.string(), v.nonEmpty()), + 'part-of-speech': PartOfSpeechSchema, + 'status': UsageStatusSchema, + 'type': v.optional(TermTypeSchema), + 'gender': v.optional(GenderSchema), + /** A sentence that shows the term in use. TBX allows a context at term level only. */ + 'context': v.optional(v.string()), + 'note': v.optional(v.string()), +}) + +/** + * One concept, which is one entry of the glossary. + * + * As with a term, every field maps to one TBX element: + * + * id -> + * subject -> + * translatable -> , a Crowdin extension + * definition -> + * note -> + * url -> + * terms -> with one for each term + */ +const ConceptSchema = v.object({ + /** + * Stable identifier in kebab case. A change here creates a new concept in Crowdin rather + * than updating the existing one, so it must survive a rename of the terms. + */ + id: v.pipe(v.string(), v.regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, 'id must be kebab case')), + subject: v.pipe(v.string(), v.nonEmpty()), + translatable: v.boolean(), + /** One sentence. Write two only when one cannot carry the meaning. */ + definition: v.pipe(v.string(), v.nonEmpty()), + note: v.optional(v.string()), + url: v.optional(v.pipe(v.string(), v.url())), + terms: v.pipe(v.array(TermSchema), v.minLength(1)), +}) + +export const GlossarySchema = v.pipe( + v.array(ConceptSchema), + v.minLength(1), + v.check( + concepts => new Set(concepts.map(c => c.id)).size === concepts.length, + 'every concept id must be unique', + ), +) + +export type Term = v.InferOutput +export type Concept = v.InferOutput + +/** + * Parses and validates the contents of `terms.yaml`. + * + * Throws a `ValiError` naming the failing path when the data does not match, so a broken + * entry fails the build rather than producing invalid TBX. + * + * @param data The result of parsing the YAML file. + */ +export function parseGlossary(data: unknown): Concept[] { + return v.parse(GlossarySchema, data) +} diff --git a/packages/i18n/glossary/terms.yaml b/packages/i18n/glossary/terms.yaml new file mode 100644 index 000000000..204cd621a --- /dev/null +++ b/packages/i18n/glossary/terms.yaml @@ -0,0 +1,476 @@ +# AIRI terminology glossary — English source of truth. +# +# A generator turns this file into TBX version 3 and sends it to the Crowdin glossary, where +# the editor marks each term and offers the approved translation. +# +# Every field maps to one TBX element and carries the TBX name. schema.ts holds the mapping. +# AGENTS.md, under "i18n / Glossary", says when a term belongs here and when it does not. +# +# This repository owns English. Crowdin owns the other 8 languages, and the translated terms +# arrive in packages/i18n/glossary/translations/ through a pull request. + +# --------------------------------------------------------------------------------------- +# Names from the documentation +# +# These concepts were already in the Crowdin glossary, added by hand. They live here so that +# this file describes the whole glossary and an upload cannot destroy them. +# --------------------------------------------------------------------------------------- + +- id: neuro-sama + subject: documentation + translatable: false + definition: An AI VTuber made by another developer. + note: The hyphenated form is the product's own spelling. + terms: + - text: Neuro-sama + part-of-speech: proper noun + status: preferred + type: fullForm + context: Neuro-sama uses 2D model driven by framework developed by Live2D Inc + - text: Neuro Sama + part-of-speech: proper noun + status: admitted + type: variant + - text: Neuro + part-of-speech: proper noun + status: admitted + type: shortForm + +- id: neko-ayaka + subject: documentation + translatable: true + definition: A maintainer of this project. + note: Both name orders name one person. + terms: + - text: Neko Ayaka + part-of-speech: proper noun + status: preferred + gender: feminine + type: fullForm + context: (from Neko Ayaka) Good morning! You are finally awake. + - text: Ayaka Neko + part-of-speech: proper noun + status: admitted + gender: feminine + type: variant + note: The reversed name order. + - text: Neko + part-of-speech: proper noun + status: admitted + gender: feminine + type: shortForm + note: 'The short form, as in "always call her Neko for simplicity".' + +- id: devlog + subject: documentation + translatable: false + definition: A development log that the project publishes on its documentation site. + terms: + - text: DevLog + part-of-speech: noun + status: preferred + type: fullForm + context: DevLog @ 2025.08.05 + - text: DevLogs + part-of-speech: noun + status: admitted + type: variant + note: The plural; TBX has no plural value, so it is recorded as a variant. + +# --------------------------------------------------------------------------------------- +# Product and editions +# --------------------------------------------------------------------------------------- + +- id: airi + subject: product + # TODO: open question. Should this concept be translatable? Interface strings in all 8 + # locales keep "AIRI". But the project uses the Japanese form アイリ in prose, and also in + # the English README. + translatable: false + definition: The application that this project makes, an animated character that speaks, listens, and sees. + note: > + Keep the Latin letters in every language; only prose may write the Japanese form アイリ. + terms: + - text: AIRI + part-of-speech: proper noun + status: preferred + context: Welcome to AIRI! + - text: Project AIRI + part-of-speech: proper noun + status: preferred + type: fullForm + note: The full name, used on first mention. + +- id: tamagotchi + subject: product + translatable: false + definition: The internal codename of the desktop edition, whose package is stage-tamagotchi. + note: > + Keep the Latin letters, write "Desktop ver." in anything an end user reads, and never + write "Stage Tamagotchi" with a space. + terms: + - text: Tamagotchi + part-of-speech: proper noun + status: preferred + context: Desktop ver. (known as stage-tamagotchi internally) + +- id: pocket + subject: product + translatable: false + definition: The internal codename of the mobile edition, whose package is stage-pocket. + note: > + Keep the Latin letters, write "Mobile ver." in anything an end user reads, and never + write "Stage Pocket" with a space. + terms: + - text: Pocket + part-of-speech: proper noun + status: preferred + context: Mobile ver. (known as stage-pocket internally) + note: Correct for a developer audience, wrong in the interface. + - text: Stage Pocket + part-of-speech: proper noun + status: deprecated + type: variant + note: Neither the codename nor the name a user sees. + +# --------------------------------------------------------------------------------------- +# Payment +# --------------------------------------------------------------------------------------- + +- id: flux + subject: payment + translatable: false + definition: The currency inside the application. + terms: + - text: Flux + part-of-speech: proper noun + status: preferred + context: Payment successful! Your Flux has been topped up. + +# --------------------------------------------------------------------------------------- +# Rendering +# --------------------------------------------------------------------------------------- + +- id: live2d + subject: rendering + translatable: false + definition: A system that animates 2D artwork of a character. + terms: + - text: Live2D + part-of-speech: proper noun + status: preferred + context: Live2D Settings + +- id: vrm + subject: rendering + translatable: false + definition: A file format for a 3D avatar. + terms: + - text: VRM + part-of-speech: proper noun + status: preferred + context: Enter VRM model URL + +- id: mmd + subject: rendering + translatable: false + definition: The MikuMikuDance file format. + terms: + - text: MMD + part-of-speech: proper noun + status: preferred + context: Remove imported Live2D, VRM, Spine, and MMD models. + +- id: spine + subject: rendering + translatable: false + definition: A system for 2D skeletal animation. + terms: + - text: Spine + part-of-speech: proper noun + status: preferred + context: Spine Settings + +- id: tachie + subject: rendering + translatable: true + definition: Artwork that shows a character standing. + # TODO: open question. Confirm the Chinese wording with a native reviewer. + note: Japanese uses 立ち絵; a language with no such word keeps "Tachie". + terms: + - text: Tachie + part-of-speech: noun + status: preferred + context: Tachie models use .tachie.zip archives containing emotion-named images. + +- id: avatar-model + subject: rendering + translatable: true + definition: The file that holds the appearance of the character, such as a Live2D model or a VRM model. + note: Not the LLM model. + terms: + - text: Model + part-of-speech: noun + status: preferred + context: Remove imported Live2D, VRM, Spine, and MMD models. + +- id: motion + subject: rendering + translatable: true + definition: An animation clip that moves the character, such as a wave of the hand. + note: A Motion moves the body and an Expression changes the face; use two different words. + terms: + - text: Motion + part-of-speech: noun + status: preferred + context: Map Motions + +- id: expression + subject: rendering + translatable: true + definition: A change of the character's face, such as a smile. + note: Use a different word from Motion, which moves the body. + terms: + - text: Expression + part-of-speech: noun + status: preferred + context: Expressions + +- id: motion-map + subject: rendering + translatable: true + definition: The table that connects each emotion to a motion. + terms: + - text: Motion Map + part-of-speech: noun + status: preferred + context: Edit motion map + +# --------------------------------------------------------------------------------------- +# The application +# --------------------------------------------------------------------------------------- + +- id: stage + subject: application + translatable: true + definition: The surface of the application that shows the character and the widgets. + note: This word does not refer to a step in a process. + terms: + - text: Stage + part-of-speech: noun + status: preferred + context: Disable Stage Transitions + +- id: controls-island + subject: application + translatable: true + definition: The group of controls that floats above the stage. + note: An invented name; describe the thing rather than translate the two words. + terms: + - text: Controls Island + part-of-speech: noun + status: preferred + context: Controls Island Icon Size + +- id: spotlight + subject: application + translatable: true + definition: A window on the desktop edition that opens with a keyboard shortcut for quick commands. + note: Not the macOS function of the same name, so do not copy the Apple translation. + terms: + - text: Spotlight + part-of-speech: noun + status: preferred + context: Spotlight shortcut + +# --------------------------------------------------------------------------------------- +# Models and providers +# --------------------------------------------------------------------------------------- + +- id: provider + subject: providers + translatable: true + definition: An external service that supplies a model, such as OpenAI, Ollama, or ElevenLabs. + terms: + - text: Provider + part-of-speech: noun + status: preferred + context: No models were found for this provider + +- id: model + subject: providers + translatable: true + definition: A machine-learning model that a provider supplies. + terms: + - text: Model + part-of-speech: noun + status: preferred + context: Select a model + +# --------------------------------------------------------------------------------------- +# The modules +# +# These five names are a family. Translate them as a group, so the result reads as one set +# of capabilities and not as five unrelated words. +# --------------------------------------------------------------------------------------- + +- id: consciousness + subject: modules + translatable: true + definition: The module that thinks for the character, using a large language model. + note: An unusual word for a software feature; it means the module for the language model. + terms: + - text: Consciousness + part-of-speech: noun + status: preferred + context: Select the suitable LLM provider for consciousness + +- id: hearing + subject: modules + translatable: true + definition: The module that changes the speech of the user to text. + note: Opposite direction to Speech; not a word for the ear or a medical test. + terms: + - text: Hearing + part-of-speech: noun + status: preferred + context: Hearing + +- id: speech + subject: modules + translatable: true + definition: The module that changes text to the voice of the character. + note: Read the note on "Hearing". + terms: + - text: Speech + part-of-speech: noun + status: preferred + context: Speech + +- id: vision + subject: modules + translatable: true + definition: The module that lets the character see images and the screen. + note: This is eyesight, not a plan for the future. + terms: + - text: Vision + part-of-speech: noun + status: preferred + context: Vision + +- id: artistry + subject: modules + translatable: true + definition: The module that makes images. + note: Translate the definition, not the English word. + terms: + - text: Artistry + part-of-speech: noun + status: preferred + context: Artistry Provider Configuration + +# --------------------------------------------------------------------------------------- +# Memory +# --------------------------------------------------------------------------------------- + +- id: memory + subject: memory + translatable: true + definition: What the character keeps and can use again in a later conversation. + note: > + Not the computer's RAM, which the performance overlay means by the same word; use the + word for what a person remembers. + terms: + - text: Memory + part-of-speech: noun + status: preferred + context: Memory + +- id: long-term-memory + subject: memory + translatable: true + # TODO: confirm this definition. The module has a settings page and a route, but no + # implementation that describes the behavior. + definition: The module that holds what the character keeps across many conversations. + terms: + - text: Long-Term Memory + part-of-speech: noun + status: preferred + context: Long-term memory specific settings and management + +- id: short-term-memory + subject: memory + translatable: true + # TODO: confirm this definition. Read the note on "long-term-memory". + definition: The module that holds what the character keeps inside the current conversation. + terms: + - text: Short-Term Memory + part-of-speech: noun + status: preferred + context: Short-term memory specific settings and management + +# --------------------------------------------------------------------------------------- +# The character +# --------------------------------------------------------------------------------------- + +- id: character + subject: character + translatable: true + definition: > + A character with everything this product stores for it: the persona, the avatar model, + the background, the provider and model of each module, and the prompt of each agent. + note: > + A rename is in progress, so no term is preferred yet, and the interface still uses + "Character" for a smaller thing: a tab inside the card. + terms: + - text: AIRI Card + part-of-speech: noun + status: admitted + context: Paste this into your AIRI Card artistry config to override these nodes. + note: The name in the interface today; translate "Card" and keep "AIRI". + - text: Character + part-of-speech: noun + status: admitted + note: The planned name, not yet used for this concept. + - text: Profile + part-of-speech: noun + status: admitted + type: variant + note: > + The stage switcher means this concept; the Account page means the user's own details. + +- id: character-card + subject: character + translatable: true + definition: The portable file format for a character, from the wider ecosystem. + note: > + A user imports it into a Character, which holds more; never shorten it to "Card". + terms: + - text: Character Card + part-of-speech: noun + status: preferred + context: Configure modules for this character card. + +- id: system-prompt + subject: character + translatable: true + definition: The instructions the application sends to the model before the conversation starts. + terms: + - text: System Prompt + part-of-speech: noun + status: preferred + context: 'Error: Please, provide a system prompt.' + +# --------------------------------------------------------------------------------------- +# Sound +# --------------------------------------------------------------------------------------- + +- id: voice-pack + subject: sound + translatable: true + definition: A prepared voice the user selects for the active character. + terms: + - text: Voice Pack + part-of-speech: noun + status: preferred + context: No Voice Packs available diff --git a/packages/i18n/package.json b/packages/i18n/package.json index ff344911b..7128fb0b3 100644 --- a/packages/i18n/package.json +++ b/packages/i18n/package.json @@ -61,9 +61,16 @@ "scripts": { "dev": "pnpm run build", "build": "tsdown", + "glossary:build": "tsx glossary/build-tbx.ts", + "test": "vitest run", + "test:watch": "vitest", "typecheck": "tsc --noEmit" }, "devDependencies": { - "unplugin-yaml": "catalog:" + "unplugin-yaml": "catalog:", + "valibot": "catalog:", + "xast-util-to-xml": "catalog:", + "xastscript": "catalog:", + "yaml": "catalog:" } } diff --git a/packages/i18n/vitest.config.ts b/packages/i18n/vitest.config.ts new file mode 100644 index 000000000..de5d68470 --- /dev/null +++ b/packages/i18n/vitest.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + include: ['glossary/**/*.test.ts'], + }, +}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b441c5844..bf863130c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2116,7 +2116,7 @@ importers: version: 3.0.2(electron@41.2.1) '@electron-toolkit/tsconfig': specifier: 'catalog:' - version: 2.0.0(@types/node@25.6.0) + version: 2.0.0(@types/node@24.12.2) '@electron-toolkit/utils': specifier: 'catalog:' version: 4.0.0(electron@41.2.1) @@ -2155,7 +2155,7 @@ importers: version: 3.1.0 '@intlify/unplugin-vue-i18n': specifier: 'catalog:' - version: 11.0.7(@vue/compiler-dom@3.5.32)(eslint@10.2.1(jiti@2.6.1))(rollup@4.60.1)(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue-i18n@11.3.2(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3)) + version: 11.0.7(@vue/compiler-dom@3.5.32)(eslint@10.2.1(jiti@2.6.1))(rollup@4.60.1)(typescript@5.9.3)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue-i18n@11.3.2(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3)) '@modelcontextprotocol/sdk': specifier: 'catalog:' version: 1.29.0(@cfworker/json-schema@4.1.1)(zod@4.3.6) @@ -2191,10 +2191,10 @@ importers: version: link:../../packages/ui-transitions '@proj-airi/unplugin-fetch': specifier: 'catalog:' - version: 0.2.3(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 0.2.3(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) '@proj-airi/unplugin-live2d-sdk': specifier: 'catalog:' - version: 0.1.7(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + version: 0.1.7(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) '@types/audioworklet': specifier: 'catalog:' version: 0.0.97 @@ -2221,7 +2221,7 @@ importers: version: 2.10.3 '@vitejs/plugin-vue': specifier: 'catalog:' - version: 6.0.6(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.32(typescript@5.9.3)) + version: 6.0.6(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.32(typescript@5.9.3)) '@vue-macros/volar': specifier: 'catalog:' version: 3.1.2(typescript@5.9.3)(vue-tsc@3.2.6(typescript@5.9.3))(vue@3.5.32(typescript@5.9.3)) @@ -2254,7 +2254,7 @@ importers: version: 6.8.3 electron-vite: specifier: 'catalog:' - version: 5.0.0(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 5.0.0(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) get-port-please: specifier: 'catalog:' version: 3.2.0 @@ -2275,31 +2275,31 @@ importers: version: 2.2.6 unocss-preset-scrollbar: specifier: 'catalog:' - version: 4.0.0(unocss@66.6.8(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))) + version: 4.0.0(unocss@66.6.8(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))) unplugin-info: specifier: 'catalog:' - version: 1.3.2(esbuild@0.27.2)(rollup@4.60.1)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 1.3.2(esbuild@0.27.2)(rollup@4.60.1)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) unplugin-yaml: specifier: 'catalog:' - version: 4.1.0(esbuild@0.27.2)(rolldown@1.0.0-rc.16)(rollup@4.60.1)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 4.1.0(esbuild@0.27.2)(rolldown@1.0.0-rc.16)(rollup@4.60.1)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) vite: specifier: 'catalog:' - version: 8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + version: 8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) vite-bundle-visualizer: specifier: 'catalog:' version: 1.2.1(rolldown@1.0.0-rc.16)(rollup@4.60.1) vite-plugin-mkcert: specifier: 'catalog:' - version: 2.0.0(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 2.0.0(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) vite-plugin-vue-devtools: specifier: 'catalog:' - version: 8.1.1(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.32(typescript@5.9.3)) + version: 8.1.1(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.32(typescript@5.9.3)) vite-plugin-vue-layouts: specifier: 'catalog:' - version: 0.11.0(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue-router@5.0.4(@pinia/colada@1.2.1(pinia@3.0.4(typescript@5.9.3)(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3)))(@vue/compiler-sfc@3.5.32)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3)) + version: 0.11.0(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue-router@5.0.4(@pinia/colada@1.2.1(pinia@3.0.4(typescript@5.9.3)(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3)))(@vue/compiler-sfc@3.5.32)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3)) vue-macros: specifier: 'catalog:' - version: 3.1.2(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@vueuse/core@14.2.1(vue@3.5.32(typescript@5.9.3)))(esbuild@0.27.2)(rolldown@1.0.0-rc.16)(rollup@4.60.1)(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue-tsc@3.2.6(typescript@5.9.3))(vue@3.5.32(typescript@5.9.3)) + version: 3.1.2(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@vueuse/core@14.2.1(vue@3.5.32(typescript@5.9.3)))(esbuild@0.27.2)(rolldown@1.0.0-rc.16)(rollup@4.60.1)(typescript@5.9.3)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue-tsc@3.2.6(typescript@5.9.3))(vue@3.5.32(typescript@5.9.3)) vue-tsc: specifier: 'catalog:' version: 3.2.6(typescript@5.9.3) @@ -3664,6 +3664,18 @@ importers: unplugin-yaml: specifier: 'catalog:' version: 4.1.0(@nuxt/kit@3.20.2(magicast@0.5.2))(esbuild@0.27.2)(rolldown@1.0.0-rc.17)(rollup@4.60.1)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + valibot: + specifier: 'catalog:' + version: 1.3.1(typescript@5.9.3) + xast-util-to-xml: + specifier: 'catalog:' + version: 4.0.0 + xastscript: + specifier: 'catalog:' + version: 4.0.0 + yaml: + specifier: 'catalog:' + version: 2.8.3 packages/memory-pgvector: dependencies: @@ -21050,9 +21062,9 @@ snapshots: dependencies: electron: 41.2.1 - '@electron-toolkit/tsconfig@2.0.0(@types/node@25.6.0)': + '@electron-toolkit/tsconfig@2.0.0(@types/node@24.12.2)': dependencies: - '@types/node': 25.6.0 + '@types/node': 24.12.2 '@electron-toolkit/utils@4.0.0(electron@41.2.1)': dependencies: @@ -21973,6 +21985,31 @@ snapshots: - supports-color - typescript + '@intlify/unplugin-vue-i18n@11.0.7(@vue/compiler-dom@3.5.32)(eslint@10.2.1(jiti@2.6.1))(rollup@4.60.1)(typescript@5.9.3)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue-i18n@11.3.2(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3))': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@10.2.1(jiti@2.6.1)) + '@intlify/bundle-utils': 11.0.7(vue-i18n@11.3.2(vue@3.5.32(typescript@5.9.3))) + '@intlify/shared': 11.3.2 + '@intlify/vue-i18n-extensions': 8.0.0(@intlify/shared@11.3.2)(@vue/compiler-dom@3.5.32)(vue-i18n@11.3.2(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3)) + '@rollup/pluginutils': 5.3.0(rollup@4.60.1) + '@typescript-eslint/scope-manager': 8.63.0 + '@typescript-eslint/typescript-estree': 8.63.0(typescript@5.9.3) + debug: 4.4.3(supports-color@10.2.2) + fast-glob: 3.3.3 + pathe: 2.0.3 + picocolors: 1.1.1 + unplugin: 2.3.11 + vite: 8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vue: 3.5.32(typescript@5.9.3) + optionalDependencies: + vue-i18n: 11.3.2(vue@3.5.32(typescript@5.9.3)) + transitivePeerDependencies: + - '@vue/compiler-dom' + - eslint + - rollup + - supports-color + - typescript + '@intlify/unplugin-vue-i18n@11.0.7(@vue/compiler-dom@3.5.32)(eslint@10.2.1(jiti@2.6.1))(rollup@4.60.1)(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue-i18n@11.3.2(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3))': dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@10.2.1(jiti@2.6.1)) @@ -24032,11 +24069,35 @@ snapshots: transitivePeerDependencies: - magicast + '@proj-airi/unplugin-fetch@0.2.3(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': + dependencies: + ofetch: 1.5.1 + vite: 8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + '@proj-airi/unplugin-fetch@0.2.3(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': dependencies: ofetch: 1.5.1 vite: 8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + '@proj-airi/unplugin-live2d-sdk@0.1.7(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)': + dependencies: + ofetch: 1.5.1 + vite: 8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + yauzl: 3.3.0 + transitivePeerDependencies: + - '@types/node' + - '@vitejs/devtools' + - esbuild + - jiti + - less + - sass + - sass-embedded + - stylus + - sugarss + - terser + - tsx + - yaml + '@proj-airi/unplugin-live2d-sdk@0.1.7(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)': dependencies: ofetch: 1.5.1 @@ -25762,6 +25823,12 @@ snapshots: transitivePeerDependencies: - typescript + '@vitejs/plugin-vue@6.0.6(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.32(typescript@5.9.3))': + dependencies: + '@rolldown/pluginutils': 1.0.0-rc.13 + vite: 8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vue: 3.5.32(typescript@5.9.3) + '@vitejs/plugin-vue@6.0.6(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.32(typescript@5.9.3))': dependencies: '@rolldown/pluginutils': 1.0.0-rc.13 @@ -26037,6 +26104,15 @@ snapshots: transitivePeerDependencies: - vue + '@vue-macros/devtools@3.1.2(typescript@5.9.3)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': + dependencies: + sirv: 3.0.2 + vue: 3.5.32(typescript@5.9.3) + optionalDependencies: + vite: 8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + transitivePeerDependencies: + - typescript + '@vue-macros/devtools@3.1.2(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': dependencies: sirv: 3.0.2 @@ -28116,7 +28192,7 @@ snapshots: transitivePeerDependencies: - supports-color - electron-vite@5.0.0(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): + electron-vite@5.0.0(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): dependencies: '@babel/core': 7.29.0 '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.29.0) @@ -28124,7 +28200,7 @@ snapshots: esbuild: 0.25.12 magic-string: 0.30.21 picocolors: 1.1.1 - vite: 8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) transitivePeerDependencies: - supports-color @@ -34153,11 +34229,6 @@ snapshots: '@unocss/preset-mini': 66.6.8 unocss: 66.6.8(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) - unocss-preset-scrollbar@4.0.0(unocss@66.6.8(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))): - dependencies: - '@unocss/preset-mini': 66.6.8 - unocss: 66.6.8(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) - unocss@66.6.8(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): dependencies: '@unocss/cli': 66.6.8 @@ -34224,6 +34295,14 @@ snapshots: unplugin: 2.3.11 vite: 8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + unplugin-combine@2.3.0(esbuild@0.27.2)(rolldown@1.0.0-rc.16)(rollup@4.60.1)(unplugin@2.3.11)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): + optionalDependencies: + esbuild: 0.27.2 + rolldown: 1.0.0-rc.16 + rollup: 4.60.1 + unplugin: 2.3.11 + vite: 8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + unplugin-combine@2.3.0(esbuild@0.27.2)(rolldown@1.0.0-rc.16)(rollup@4.60.1)(unplugin@2.3.11)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): optionalDependencies: esbuild: 0.27.2 @@ -34245,6 +34324,19 @@ snapshots: transitivePeerDependencies: - supports-color + unplugin-info@1.3.2(esbuild@0.27.2)(rollup@4.60.1)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): + dependencies: + ci-info: 4.4.0 + git-url-parse: 16.1.0 + simple-git: 3.36.0 + unplugin: 2.3.11 + optionalDependencies: + esbuild: 0.27.2 + rollup: 4.60.1 + vite: 8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + transitivePeerDependencies: + - supports-color + unplugin-info@1.3.2(esbuild@0.27.2)(rollup@4.60.1)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): dependencies: ci-info: 4.4.0 @@ -34338,6 +34430,17 @@ snapshots: rollup: 2.80.0 vite: 8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + unplugin-yaml@4.1.0(esbuild@0.27.2)(rolldown@1.0.0-rc.16)(rollup@4.60.1)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): + dependencies: + '@rollup/pluginutils': 5.3.0(rollup@4.60.1) + unplugin: 3.0.0 + yaml: 2.8.3 + optionalDependencies: + esbuild: 0.27.2 + rolldown: 1.0.0-rc.16 + rollup: 4.60.1 + vite: 8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + unplugin-yaml@4.1.0(esbuild@0.27.2)(rolldown@1.0.0-rc.16)(rollup@4.60.1)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): dependencies: '@rollup/pluginutils': 5.3.0(rollup@4.60.1) @@ -34576,12 +34679,22 @@ snapshots: - rollup - supports-color + vite-dev-rpc@1.1.0(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): + dependencies: + birpc: 2.9.0 + vite: 8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vite-hot-client: 2.1.0(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + vite-dev-rpc@1.1.0(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): dependencies: birpc: 2.9.0 vite: 8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) vite-hot-client: 2.1.0(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + vite-hot-client@2.1.0(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): + dependencies: + vite: 8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vite-hot-client@2.1.0(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): dependencies: vite: 8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) @@ -34627,6 +34740,21 @@ snapshots: - tsx - yaml + vite-plugin-inspect@11.3.3(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): + dependencies: + ansis: 4.2.0 + debug: 4.4.3(supports-color@10.2.2) + error-stack-parser-es: 1.0.5 + ohash: 2.0.11 + open: 10.2.0 + perfect-debounce: 2.1.0 + sirv: 3.0.2 + unplugin-utils: 0.3.1 + vite: 8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vite-dev-rpc: 1.1.0(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + transitivePeerDependencies: + - supports-color + vite-plugin-inspect@11.3.3(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): dependencies: ansis: 4.2.0 @@ -34658,6 +34786,13 @@ snapshots: - typescript - ws + vite-plugin-mkcert@2.0.0(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): + dependencies: + debug: 4.4.3(supports-color@10.2.2) + supports-color: 10.2.2 + undici: 8.1.0 + vite: 8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vite-plugin-mkcert@2.0.0(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): dependencies: debug: 4.4.3(supports-color@10.2.2) @@ -34676,6 +34811,20 @@ snapshots: transitivePeerDependencies: - supports-color + vite-plugin-vue-devtools@8.1.1(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.32(typescript@5.9.3)): + dependencies: + '@vue/devtools-core': 8.1.1(vue@3.5.32(typescript@5.9.3)) + '@vue/devtools-kit': 8.1.1 + '@vue/devtools-shared': 8.1.1 + sirv: 3.0.2 + vite: 8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vite-plugin-inspect: 11.3.3(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + vite-plugin-vue-inspector: 5.3.2(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + transitivePeerDependencies: + - '@nuxt/kit' + - supports-color + - vue + vite-plugin-vue-devtools@8.1.1(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.32(typescript@5.9.3)): dependencies: '@vue/devtools-core': 8.1.1(vue@3.5.32(typescript@5.9.3)) @@ -34690,6 +34839,21 @@ snapshots: - supports-color - vue + vite-plugin-vue-inspector@5.3.2(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): + dependencies: + '@babel/core': 7.29.0 + '@babel/plugin-proposal-decorators': 7.28.0(@babel/core@7.29.0) + '@babel/plugin-syntax-import-attributes': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.29.0) + '@babel/plugin-transform-typescript': 7.28.5(@babel/core@7.29.0) + '@vue/babel-plugin-jsx': 1.5.0(@babel/core@7.29.0) + '@vue/compiler-dom': 3.5.32 + kolorist: 1.8.0 + magic-string: 0.30.21 + vite: 8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + transitivePeerDependencies: + - supports-color + vite-plugin-vue-inspector@5.3.2(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): dependencies: '@babel/core': 7.29.0 @@ -34705,6 +34869,16 @@ snapshots: transitivePeerDependencies: - supports-color + vite-plugin-vue-layouts@0.11.0(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue-router@5.0.4(@pinia/colada@1.2.1(pinia@3.0.4(typescript@5.9.3)(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3)))(@vue/compiler-sfc@3.5.32)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3)): + dependencies: + debug: 4.4.3(supports-color@10.2.2) + fast-glob: 3.3.3 + vite: 8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vue: 3.5.32(typescript@5.9.3) + vue-router: 5.0.4(@pinia/colada@1.2.1(pinia@3.0.4(typescript@5.9.3)(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3)))(@vue/compiler-sfc@3.5.32)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3)) + transitivePeerDependencies: + - supports-color + vite-plugin-vue-layouts@0.11.0(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue-router@5.0.4(@pinia/colada@1.2.1(pinia@3.0.4(typescript@5.9.3)(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3)))(@vue/compiler-sfc@3.5.32)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3)): dependencies: debug: 4.4.3(supports-color@10.2.2) @@ -34989,6 +35163,54 @@ snapshots: - vue-tsc - webpack + vue-macros@3.1.2(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@vueuse/core@14.2.1(vue@3.5.32(typescript@5.9.3)))(esbuild@0.27.2)(rolldown@1.0.0-rc.16)(rollup@4.60.1)(typescript@5.9.3)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue-tsc@3.2.6(typescript@5.9.3))(vue@3.5.32(typescript@5.9.3)): + dependencies: + '@vue-macros/better-define': 3.1.2(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(vue@3.5.32(typescript@5.9.3)) + '@vue-macros/boolean-prop': 3.1.2(vue@3.5.32(typescript@5.9.3)) + '@vue-macros/chain-call': 3.1.2(vue@3.5.32(typescript@5.9.3)) + '@vue-macros/common': 3.1.2(vue@3.5.32(typescript@5.9.3)) + '@vue-macros/config': 3.1.2(vue@3.5.32(typescript@5.9.3)) + '@vue-macros/define-emit': 3.1.2(vue@3.5.32(typescript@5.9.3)) + '@vue-macros/define-models': 3.1.2(@vueuse/core@14.2.1(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3)) + '@vue-macros/define-prop': 3.1.2(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(vue@3.5.32(typescript@5.9.3)) + '@vue-macros/define-props': 3.1.2(@vue-macros/reactivity-transform@3.1.2(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3)) + '@vue-macros/define-props-refs': 3.1.2(vue@3.5.32(typescript@5.9.3)) + '@vue-macros/define-render': 3.1.2(vue@3.5.32(typescript@5.9.3)) + '@vue-macros/define-slots': 3.1.2(vue@3.5.32(typescript@5.9.3)) + '@vue-macros/define-stylex': 3.1.2(vue@3.5.32(typescript@5.9.3)) + '@vue-macros/devtools': 3.1.2(typescript@5.9.3)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + '@vue-macros/export-expose': 3.1.2(vue@3.5.32(typescript@5.9.3)) + '@vue-macros/export-props': 3.1.2(vue@3.5.32(typescript@5.9.3)) + '@vue-macros/export-render': 3.1.2(vue@3.5.32(typescript@5.9.3)) + '@vue-macros/hoist-static': 3.1.2(vue@3.5.32(typescript@5.9.3)) + '@vue-macros/jsx-directive': 3.1.2(typescript@5.9.3) + '@vue-macros/named-template': 3.1.2(vue@3.5.32(typescript@5.9.3)) + '@vue-macros/reactivity-transform': 3.1.2(vue@3.5.32(typescript@5.9.3)) + '@vue-macros/script-lang': 3.1.2(vue@3.5.32(typescript@5.9.3)) + '@vue-macros/setup-block': 3.1.2(vue@3.5.32(typescript@5.9.3)) + '@vue-macros/setup-component': 3.1.2(vue@3.5.32(typescript@5.9.3)) + '@vue-macros/setup-sfc': 3.1.2(vue@3.5.32(typescript@5.9.3)) + '@vue-macros/short-bind': 3.1.2(vue@3.5.32(typescript@5.9.3)) + '@vue-macros/short-emits': 3.1.2(vue@3.5.32(typescript@5.9.3)) + '@vue-macros/short-vmodel': 3.1.2(vue@3.5.32(typescript@5.9.3)) + '@vue-macros/volar': 3.1.2(typescript@5.9.3)(vue-tsc@3.2.6(typescript@5.9.3))(vue@3.5.32(typescript@5.9.3)) + unplugin: 2.3.11 + unplugin-combine: 2.3.0(esbuild@0.27.2)(rolldown@1.0.0-rc.16)(rollup@4.60.1)(unplugin@2.3.11)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + unplugin-vue-define-options: 3.1.2(vue@3.5.32(typescript@5.9.3)) + vue: 3.5.32(typescript@5.9.3) + transitivePeerDependencies: + - '@emnapi/core' + - '@emnapi/runtime' + - '@rspack/core' + - '@vueuse/core' + - esbuild + - rolldown + - rollup + - typescript + - vite + - vue-tsc + - webpack + vue-macros@3.1.2(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@vueuse/core@14.2.1(vue@3.5.32(typescript@5.9.3)))(esbuild@0.27.2)(rolldown@1.0.0-rc.16)(rollup@4.60.1)(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue-tsc@3.2.6(typescript@5.9.3))(vue@3.5.32(typescript@5.9.3)): dependencies: '@vue-macros/better-define': 3.1.2(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(vue@3.5.32(typescript@5.9.3)) diff --git a/vitest.config.ts b/vitest.config.ts index 6096644ab..ace835de9 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -9,6 +9,7 @@ export default defineConfig({ 'packages/cap-vite', 'packages/ccc', 'packages/core-agent', + 'packages/i18n', 'packages/better-ws', 'packages/plugin-sdk', 'packages/plugin-sdk-tamagotchi',