stardroid
Sky Map (formerly Google Sky Map, open sourced in 2012)
다국어 음성 주석 엔진으로 일본어·중국어 텍스트에 후리가나·핀인·주트핑을 자동으로 붙입니다
Multi-Language Phonetic Gloss Engine that parses raw text in ideographic/polyphonic languages (Japanese, Chinese) and attaches phonetic annotations (Furigana, Pinyin/Jyutping).
교육C# · JavaScriptGitHub @danzuep온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기메이커가 쓴 소개는 위의 한 줄이 전부입니다. 더 자세한 내용은 저장소 README에서 읽을 수 있습니다. 저장소 README ↗
README 전체를 불러오는 중입니다.
README 전문 보기 ↗Phonetic Annotator is a .NET 10 library with browser-only deployments for adding pronunciation glosses while preserving Unicode offsets and unresolved readings. Both WebAssembly demos include Unicode 18.0.0 Unihan character readings for Mandarin, Cantonese/Jyutping, Japanese, Korean, Vietnamese, and Zhuang, plus a word-lexicon strategy supporting those languages and Arabic. Caller-supplied lexicons use language<TAB>word<TAB>reading1|reading2 TSV format (see data/examples/word-readings.tsv). The CLI remains a developer utility, not a supported desktop deployment target. Build with dotnet build src/PhoneticAnnotator.slnx, test with dotnet test src/PhoneticAnnotator.slnx, and update Unihan with pwsh -NoProfile -File scripts/update-unihan.ps1.
src/PhoneticAnnotator.Wasm uses Microsoft.NET.Sdk.WebAssembly, browser-wasm,
and direct [JSExport] / [JSImport] interop. It has no Blazor or ASP.NET Core
runtime dependency. Install the .NET 10 SDK and Node.js 24 (for declaration generation),
then run from the repository root:
dotnet workload install wasm-tools
dotnet tool install --global dotnet-serve
dotnet build src/PhoneticAnnotator.slnx
dotnet test src/PhoneticAnnotator.Tests/PhoneticAnnotator.Tests.csproj -c Release
dotnet publish src/PhoneticAnnotator.Wasm/PhoneticAnnotator.Wasm.csproj -c Release -f net10.0
dotnet serve --address 127.0.0.1 --port 8000 --directory src/PhoneticAnnotator.Wasm/bin/Release/net10.0/publish/wwwroot
Open http://localhost:8000. Release publishing enables AOT compilation, trimming,
SIMD, and invariant globalization (Unicode input remains supported); the first
publish requires the native WASM toolchain and takes longer than an ordinary build.
For a quicker interpreter-only publish, append -p:RunAOTCompilation=false.
Serve the generated publish/wwwroot, not the source demo directory or its
parent publish directory. All module/dictionary URLs are relative, including on project Pages sites.
demo/app.mjs creates the runtime, registers the phonetic-demo.onReady import,
gets exports with getAssemblyExports('PhoneticAnnotator.Wasm'), and awaits
runMain(getConfig().mainAssemblyName, []). The async C# entry point initializes
the in-memory annotation session before invoking onReady. The methods on
exports.PhoneticAnnotator.Wasm.Interop return JavaScript promises:
AnnotateToHtml(text, optionsJson) returns encoded HTML from HtmlRubyVisitor.AnnotateToJson(text, optionsJson) returns the Core discriminated AST JSON,
including unresolved reading candidates. JSON rendering is reflection-free and
safe under trimming/AOT.LoadUnihanDictionary(bytes, readingId) parses the selected reading field from
UTF-8 Unihan_Readings.txt and replaces the single selected immutable Unihan
dictionary using Interlocked.Exchange. It does not accumulate fields. Failed
loads leave the previous dictionary unchanged; in-flight annotations retain their
captured snapshot. Await the selected dictionary load before annotating with it.LoadDictionaryChunk(uint8Array) adds one complete UTF-8 word-lexicon TSV
chunk: language<TAB>word<TAB>reading1|reading2, with optional comments/blank
lines. Chunks are files, not fragments of a line or arbitrary Unihan/binary data.
Chunks parse independently and publish atomically using Interlocked.CompareExchange;
malformed chunks leave the existing dictionary unchanged. Concurrent publications
retry against the latest snapshot, so successful chunks are never lost.
Annotations capture immutable snapshots, so loads never mutate in-flight work.
Longest words win across chunks; the last published chunk wins equal-length ties.
Concurrent calls publish in completion order, not invocation order. Await each
load in sequence when override order matters (as the demo's file picker does).
For efficiency, batch entries into a few substantial chunks rather than many tiny
files (lookup cost grows with the number of chunks).Options are a JSON object:
strategy: "word" (API default) or "unihan" (demo default).readingSystem: a Unihan field ID from the list below; use
kSMSZD2003Readings-Cantonese for SMSZD Cantonese and kSMSZD2003Readings
for its Mandarin mode. ar selects Arabic word readings.language: legacy case-insensitive mappings ja-JP → kJapanese (API default),
zh-CN → kMandarin, zh-HK → kCantonese, ko → kHangul,
vi → kVietnamese, za → kZhuang, and ar/ar-Arab → Arabic.
With Unihan, an explicit readingSystem selects the reading and associated language instead.
Word mode requires the matching language and its default reading system; alternate
Unihan fields cannot relabel word-dictionary readings. Switching to word mode in the
demo selects that language's default system automatically.pickFirstReading: boolean, default false in the API and true in the demo.
Selecting the first candidate is a heuristic, not contextual disambiguation.
Disable it to preserve ambiguous candidates as unresolved readings.alignOkurigana: boolean, default true.The full modern Unihan demo offers kMandarin, kCantonese, kJapanese,
kJapaneseOn, kJapaneseKun, kHangul, kKorean, kVietnamese, kZhuang,
kHanyuPinlu, kHanyuPinyin, kTGHZ2013, kXHC1983, and
kSMSZD2003Readings with separate Mandarin/Cantonese modes. Historical kTang
and kFanqie, non-reading kDefinition, and absent kBopomofo are excluded.
During startup, both demos preload the example word dictionary and approximately 8.5 MB
Unihan_Readings.txt file concurrently with runtime initialization, and cache their bytes in browser memory. Only the selected
field is parsed; switching fields reuses the cached download and replaces the
selected dictionary. Bundled dictionary responses are also persisted in an
application-scoped browser Cache Storage cache across page reloads.
Both demos use dictionary-cache.js for bundled word and Unihan downloads.
Online loads revalidate cached responses with ETag/Last-Modified when available;
servers without validators return a fresh download. Changed responses replace the
persisted copy. Network failures can reuse a previously cached dictionary, with
the offline source shown in diagnostics; HTTP errors are not silently hidden by
stale data. This caches downloaded UTF-8 bytes, not the managed dictionary
index, so a new runtime still parses/indexes the bytes.
Cache Storage is best-effort: private browsing, insecure origins, quota limits, or browser eviction may disable persistence without preventing online annotation. Use HTTPS or localhost for reliable access. The demo's clear-cache control removes only this application's dictionary entries, not unrelated origin caches. Clearing download storage does not unload the current in-memory dictionary. Uploaded dictionaries and annotation text are never persisted by this feature.
The word strategy remains available with the small example
data/examples/word-readings.tsv and additional TSV uploads through the file picker;
it is not a full morphological dictionary. Unknown text is preserved; an empty word
dictionary is valid. Unihan annotation requires the matching dictionary to be loaded.
Rejected promises expose invalid input/options/dictionary errors.
The latency badge measures the annotation call, including interop, but excludes
runtime/dictionary startup and DOM rendering.
Both demos start with Mandarin/Pinyin and the unambiguous example 你好. The Japanese
example is 山林, whose first character readings give サンリン, rather than using
食べる with the character-reading heuristic. Japanese alignment controls are hidden
for other languages. Import UTF-8 .txt or .md files locally using the text picker;
neither text nor dictionaries are sent to a server.
The collapsed Character metadata panel shows raw fields and all alternatives for each distinct non-whitespace input character, filtered to the selected language, plus language-neutral definitions. Combined SMSZD data is split into its Mandarin or Cantonese part. Historical Chinese fields are visible here but are not used for annotation. Missing metadata is explicit; this panel describes bundled Unihan data, not uploaded word entries. Raw metadata is decoded only when the panel is opened.
The collapsed Page and dictionary load diagnostics panel separates document, runtime initialization, dictionary fetch/indexing, and first-preview timings, and lists browser resource transfer/encoded/decoded sizes. Fetches overlap runtime startup, so these durations are not additive. The separate runtime URLs already isolate initialization while reusing the same markup, styles, and controller. Compare cold-cache and warm-cache visits separately with the same reading selected. A smaller NetWasm bundle only reduces potential transfer time: module compilation, integrity checks, runtime initialization, interop copies, and managed dictionary parsing also contribute. Microsoft Release builds use AOT; NetWasm uses a different compiler/runtime. Package size alone cannot establish which phase is slower; use the diagnostics to identify the bottleneck on the actual browser/device.
Strings and byte[] necessarily copy across the supported JS interop boundary:
[JSExport] cannot directly marshal ReadOnlySpan<char> or
ReadOnlyMemory<byte>, and this API does not claim end-to-end zero allocation.
Internally, tokenization slices the original ReadOnlyMemory<char> and performs
ReadOnlySpan<char> trie/rune lookups without substring copies or boxing; dictionary
loading views the marshaled bytes as ReadOnlyMemory<byte> and wraps the same
array in a read-only MemoryStream without cloning it. AST nodes, decoded dictionary
entries, options, and returned strings still allocate. Shared dictionaries are
immutable, and each call owns its visitors/options/AST. The default runtime is
single-threaded and does not require cross-origin isolation headers.
The published browser directory includes PhoneticAnnotator.d.ts and
phonetic-annotator-worker.js. Create the latter as a module Web Worker;
it loads ./_framework/dotnet.js relative to its own location, not the page.
Keep the worker beside _framework, or send an initialize request with
args: [{ runtimeUrl: "/your-engine/_framework/dotnet.js" }] before other calls.
Runtime URLs must point to trusted application assets.
Send { id, method, args }, using any unique string or finite numeric ID.
Methods match the four .NET exports listed above; args is their ordered argument
array. Dictionary bytes are Uint8Array values. Await the matching
{ id, ok: true, result } response, or handle { id, ok: false, error: { name, message } }.
The worker also emits { type: "ready" } or { type: "startup-error", error }.
Requests execute sequentially, including initialization and dictionary updates;
one failed request does not discard subsequent requests. There is no cancellation
protocol: terminate the worker to abandon all work and release its dictionaries.
Use transferable buffers only when the caller no longer needs the dictionary bytes.
node scripts/generate-types.mjs regenerates declarations from [JSExport]
signatures. node scripts/generate-types.mjs --check detects stale output.
The WASM build also generates them. Strings, serialized AST results, and dictionary
interop still allocate/copy; running in a worker avoids main-thread computation,
not message serialization costs.
Use AnnotationEngine as the typed entry point instead of assembling low-level
tokenizers, disambiguators, and visitors manually. Fluent methods and nested
Action<TBuilder> callbacks configure the same immutable engine:
using PhoneticAnnotator.Core;
// Load once; streams remain caller-owned.
var lexicon = await WordReadingLexicon.LoadAsync(dictionaryStream);
var engine = AnnotationEngine.Create(builder => builder
.Reading("kJapanese") // Selects the catalog language and reading system.
.PickFirstReading()
.Dictionaries(dictionaries => dictionaries.Words(lexicon))
.Filters(filters => filters
.KnownWords(["猫"])
.Script(JapaneseScript.Romaji)));
var document = engine.Annotate("猫と食べる");
var html = engine.ToHtml(document);
var json = engine.ToJson(document); // Render the same AST without tokenizing again.
Alternatively, instantiate AnnotationEngineBuilder, chain configuration calls,
and call Build(). Built engines snapshot dictionary/filter collections; later
builder changes do not modify earlier engines. Reuse an engine for repeated calls.
Language(...) plus Reading(ReadingSystem) configures a typed reading explicitly;
Dictionaries(d => d.Unihan(dictionary)) selects scalar Unihan matching instead of
word dictionaries. Mixing scalar and word sources is rejected rather than silently
ignoring one. Dictionary loading stays asynchronous and explicit; annotation is
synchronous over borrowed input and returns an owned document.
Japanese kana uses automatic okurigana alignment; WithoutAlignment() keeps
whole-token readings and Alignment(customAligner) selects a language-compatible
aligner. Filters execute in registration order: Jlpt(...), KnownWords(...),
KnownKanji(...), Script(...), or Add(customFilter). Configuration validates
language/reading and aligner compatibility at build time. An empty dictionary
configuration preserves unknown text rather than inventing readings.
ToHtml, ToJson, and ToAnki create a fresh visitor for each rendering.
Render(document, () => new CustomVisitor()) supports custom output while keeping
tokenization strategies independent of visitors. Custom filters and aligners are
shared by reference and must be immutable/thread-safe when the engine is shared;
visitor factories must return a new visitor each time. Custom filters may replace
the root; non-document results are wrapped in a document, and null results fail.
Both annotation and filter execution honor cancellation between processing steps.
The WASM JSON boundary uses the same typed engine internally; JavaScript does not
need to instantiate C# builders.
TokenEnumerator borrows a ReadOnlySpan<char> and immutable lexicon snapshots.
Its value tokens avoid async state machines and per-token heap records.
WordLexiconTokenizerStrategy.Tokenize exposes this path;
GlossDocumentAssembler.Assemble copies tokens into an owned AST.
The asynchronous strategy APIs remain available for existing consumers.
SIMD-backed SearchValues<char> checks accelerate BMP script boundaries; scalar
decoding still preserves supplementary characters and malformed UTF-16.
SpanCharacterAligner uses stack scratch for short readings and pooled character
buffers for long readings, returning rentals in finally blocks. Jukujikun and
ambiguous kana boundaries conservatively remain whole-token annotations.
It does not infer readings absent from the dictionary or modernize historical kana.
AST nodes, owned strings, rendering, and options parsing are intentionally not
zero-allocation.
Apply a GlossFilterPipeline after assembly and before invoking any visitor.
Filters recursively transform immutable documents, sharing unchanged nodes:
JlptLevelFilter accepts a target N1–N5 integer and a caller-supplied
IReadOnlyDictionary<Rune, int> kanji-level mapping. N5 is easiest:
target N4 suppresses N5 and N4 kanji only when all kanji are mapped.
No official JLPT dataset is bundled.UserKnownKanjiFilter defaults to an empty/no-op knowledge set; accepts known
words and kanji sets, or a BitArray indexed by Unicode scalar value.ScriptFilter selects JapaneseScript.Hiragana, Katakana, or Romaji.
Romaji uses conservative Hepburn-style transcription, written vowel sequences
rather than macrons, and apostrophes for ambiguous moraic n. Unsupported
conversions retain the original annotation rather than inventing readings.Japanese-specific filters leave other languages unchanged; the filter contract and
pipeline support any language without coupling tokenization to output visitors.
Thai word annotation accepts caller-supplied th or th-TH TSV lexicons and
readingSystem: "th"; this is dictionary matching, not a bundled Thai morphological
segmenter or pronunciation model.
Run the BenchmarkDotNet suite in Release:
dotnet run --project benchmarks/PhoneticAnnotator.Benchmarks -c Release -- --filter '*'
TextProcessingBenchmarks compares span and asynchronous tokenization/AST
assembly for Japanese, Chinese, Korean, and Thai using tweet-, article-, and
chapter-sized synthetic inputs. MemoryDiagnoser reports allocated bytes/op
alongside timing; setup/dictionary indexing are excluded. AlignmentBenchmarks
separately exercises successful unique-kana alignment, whole-compound alignment,
and conservative ambiguous-run fallback over short and long inputs.
Use --job Dry only for smoke checks, not performance conclusions. Native .NET
measurements do not predict WASM latency; profile the published worker on target
browsers and devices before claiming browser speedups.
Open profile.html beside the published native .NET WASM demo to run measurements
on the actual browser/device. Use the same Release AOT build when comparing
devices and enter a build/device label. Keep the tab foregrounded, close unrelated
workloads, and record power/thermal conditions separately. The page runs locally
and downloads JSON results; it does not send results, source text, or dictionaries
to a profiling service.
The harness creates a fresh worker for each dictionary-cold and dictionary-warm pass, measuring runtime startup, dictionary loading/revalidation, and indexing separately. It measures sequential worker round trips for synthetic tweet, article, and chapter inputs in Japanese, Chinese, Korean, and Thai, including interop and JSON serialization. Word dictionaries and supported Unihan readings are exercised; missing word fixtures use explicitly labelled in-memory supplements. Warmups and correctness probes are excluded from median/p95 latencies. Raw samples, throughput, dictionary provenance/fingerprints, and browser/device metadata accompany each report. Download/index timings are single observations, not statistical distributions.
“Cold” clears only this application's persistent dictionary cache, not browser HTTP caches, compiled WASM caches, or OS caches. “Warm” means a subsequent load; the reported dictionary source is authoritative (a server without validators may still return a full response). No fixed performance thresholds are imposed.
For repeatable automation against the assembled site:
npm --prefix tools/browser-tests ci
npm --prefix tools/browser-tests run profile -- --project=chromium --project=mobile-chromium
Set PHONETIC_BASE_URL to profile an already-served publication; otherwise the
existing local static-site server is used. Profiling is a separate Playwright
suite, not part of ordinary regression tests. JSON reports are attached under
artifacts/profiling-results, with an HTML report under
artifacts/profiling-report. The Pages workflow can run it via the optional
profile dispatch input. Set PHONETIC_PROFILE_WEBKIT=1 to enable the optional
--project=webkit run after installing the existing Playwright WebKit browser.
Mobile Chromium profiles are emulation, not physical phone benchmarks;
automated WebKit runs likewise do not measure a physical iPhone.
For physical devices, serve the identical publication over HTTPS, open
profile.html on each target, and export results there. Ordinary HTTP LAN origins
may lack Cache Storage. This environment cannot establish physical-device results;
do not interpret emulated CI timing as a device-specific performance guarantee.
Merging a pull request into main runs .github/workflows/release.yml: the latest
stable vMAJOR.MINOR.PATCH tag is bumped according to the PR title
(major: before minor:, case-insensitive; otherwise patch). With no tag, the
baseline is v0.1.0, so the first ordinary merge releases v0.1.1.
The .NET 10 workflow tests and packages Zue.PhoneticAnnotator.Core and
Zue.Unihan, creates the tag and a GitHub Release with generated notes, and
attaches .nupkg/.snupkg files, a complete AOT PhoneticAnnotator-browser.zip,
and standalone declarations/worker assets. Release builds use committed interop
declarations without Node.js; ordinary builds still regenerate them.
Run the workflow manually on main to rebuild the latest stable tag without
bumping it (or release v0.1.0 from the selected main commit if no tag exists).
Existing tags are never moved. To publish packages to NuGet.org, select
publish_nuget. The NuGet username defaults to the GitHub user initiating the
workflow (or its rerun), via github.triggering_actor. If your NuGet.org username
differs, set the repository Actions variable NUGET_USER to override it. This
must be the account owning the active Zue.PhoneticAnnotator Trusted Publishing
rule, not the rule name. The rule must authorize owner danzuep, repository
PhoneticAnnotator, and workflow file release.yml. Leave its environment
unset because the release job does not use a GitHub environment. Ensure the
account can publish both Zue.PhoneticAnnotator.Core and Zue.Unihan.
The workflow exchanges GitHub OIDC identity for a short-lived NuGet key;
no stored NUGET_API_KEY secret is needed, and the old secret can be deleted.
Duplicate packages are skipped. Consumers of standalone assets also need the
matching browser runtime bundle.
The same Unihan and word-lexicon pipeline also targets netwasm0.1, using
NetWasm SDK 0.10.2 and its
0.10.0 JSON/LINQ library ports. This is a separate compiler/runtime, not
Microsoft's browser-wasm target. PhoneticAnnotator.Core multi-targets
net10.0;netwasm0.1; PhoneticAnnotator.NetWasm publishes the alternate
JsModule host. Both SPAs load <script type="module" src="app.mjs"></script>
and share the UI, dictionary parsing, async token stream, alignment, AST, and visitors.
dotnet publish src/PhoneticAnnotator.NetWasm/PhoneticAnnotator.NetWasm.csproj -c Release -o artifacts/netwasm
dotnet serve --address 127.0.0.1 --port 8001 --directory artifacts/netwasm
Open http://localhost:8001. The module opens the generated
browser/PhoneticAnnotator.NetWasm.mjs without dotnet.js. NetWasm requires
HTTPS or localhost for startup integrity checks. It does not need wasm-tools;
the SDK restores its own pinned host tools. Use .NET SDK 10.0.303 or later.
Compatibility boundaries:
Unihan_Readings.txt for the selected
modern reading field and accept complete UTF-8 word-lexicon TSV chunks.
The browser API does not accept Unihan ZIP archives.Task<string>. Its facade exports Task and
delivers each string through an explicit [JSImport] callback keyed by request
ID. app.mjs exposes the same promise-returning UI API without blocking waits.
Both runtimes share the same lock-free dictionary publication code without an
extra JavaScript queue; rejected chunks do not poison later loads.HtmlEncoder. Its target-specific visitor encoding
escapes &, <, >, both quote characters, and preserves Unicode; the .NET 10
target uses HtmlEncoder.Default. Both source and dictionary text are encoded.Interlocked follows its
single-reactor semantics there and provides atomic publication on .NET 10.
Annotations use a volatile read of one immutable snapshot for their entire call.main or a branch containing DevOps (case-insensitive, including
copilot/devops-update-loading-perf), or open Actions → Browser WebAssembly and Pages → Run workflow
and select the branch to deploy. Manual dispatch can deploy any branch, subject
to the github-pages environment's deployment rules. Pull requests do not
trigger this workflow. Other branch pushes do not start it either;
dispatch the workflow manually to validate those branches.github-pages environment deployment if your repository requires it.
The deploy job exposes the resulting Pages URL, normally
https://<owner>.github.io/PhoneticAnnotator/.Deployments support browsers only; there is no native deployment matrix.
A single browser-build job runs managed tests once, publishes both browser runtimes, and uploads
the combined phoneticannotator-browser-site artifact. The Chromium check job
downloads that same site, without rebuilding or installing WASM build tools.
It tests both browser runtimes in Chromium only,
including AST parity, alignment, dictionary uploads/recovery, and HTML escaping.
The combined site serves Microsoft WASM at the root and NetWasm at /netwasm/,
with runtime links in the UI. Pages deployment waits for the Chromium checks,
deploys the shared site tested by Chromium, skips stale revisions by
checking the current run's GITHUB_REF (including manually selected branches),
and runs Chromium against both live pages. Browser reports are uploaded separately
without duplicating the site in each test artifact.
Firefox and WebKit are skipped in CI, including their Ubuntu package downloads;
their Playwright projects remain available for optional local checks.
CI installs only Chromium's headless shell, caches npm downloads and build-job NuGet
packages, and skips WASM workload manifest refreshes. Pages workflows run on
configured branch pushes or manual dispatch, not pull requests; deployments
are not cancelled mid-deploy.
Release AOT and both runtime publishes remain enabled so deployment tests exercise
the actual optimized bundles.
Only the deployment job receives Pages write/OIDC permissions.
Agent-authored workflow runs may require a maintainer's Approve and run
before any jobs start; no Pages deployment occurs until those checks complete.
To reproduce browser checks locally, publish both runtimes, assemble the same project-path site used by CI, then run:
dotnet publish src/PhoneticAnnotator.Wasm/PhoneticAnnotator.Wasm.csproj -c Release -f net10.0
dotnet publish src/PhoneticAnnotator.NetWasm/PhoneticAnnotator.NetWasm.csproj -c Release -o artifacts/netwasm
mkdir -p artifacts/site/PhoneticAnnotator/netwasm
cp -a src/PhoneticAnnotator.Wasm/bin/Release/net10.0/publish/wwwroot/. artifacts/site/PhoneticAnnotator/
cp -a artifacts/netwasm/browser artifacts/netwasm/index.html artifacts/netwasm/app.mjs artifacts/netwasm/main.js artifacts/netwasm/word-readings.tsv artifacts/netwasm/Unihan_Readings.txt artifacts/netwasm/THIRD-PARTY-NOTICES.txt artifacts/site/PhoneticAnnotator/netwasm/
cd tools/browser-tests
npm ci
npx playwright install --with-deps --only-shell chromium
npm test -- --project=chromium
Install the local server once with dotnet tool install --global dotnet-serve
(CI pins version 1.10.194). The test runner starts dotnet serve under
/PhoneticAnnotator/, bound to loopback only.
To test an already hosted combined site instead, set PHONETIC_BASE_URL to its
HTTPS URL ending in /; it then skips the local server. Test traces/reports
are written under artifacts/. No audio, desktop GUI, game fixtures, or unrelated
Doom checks from the example workflow are required.
[ Raw Japanese Text ]
│
▼
[ 1. Morphological Tokenizer ] ◄── (UniDic / IPADic / JMDict)
│ (Token: "大人買い", Reading: "おとながい")
▼
[ 2. Character Aligner ] ◄── (Okurigana heuristics & Jukujikun maps)
│ (Pairs: [大人: おとな], [買: が], [い: -])
▼
[ 3. Filter Pipeline ] ◄── (User Settings: JLPT levels, scripts)
│
▼
[ 4. Output Formatter ] ──► HTML (<ruby>) / JSON / Anki Syntax