diff --git a/README.md b/README.md index 99a8139..c108bef 100644 --- a/README.md +++ b/README.md @@ -46,12 +46,19 @@ The `subminer` wrapper uses a [Bun](https://bun.sh) shebang, so `bun` must be on ### From Source ```bash -git clone https://github.com/ksyasuda/SubMiner.git +git clone --recurse-submodules https://github.com/ksyasuda/SubMiner.git cd SubMiner make build make install ``` +If you already cloned without submodules: + +```bash +cd SubMiner +git submodule update --init --recursive +``` + For macOS builds, signing, and platform-specific details, see [docs/installation.md](docs/installation.md). ## Quick Start diff --git a/config.example.jsonc b/config.example.jsonc index c587ec7..fdf142c 100644 --- a/config.example.jsonc +++ b/config.example.jsonc @@ -149,6 +149,7 @@ // Primary and secondary subtitle styling. // ========================================== "subtitleStyle": { + "enableJlpt": false, "fontFamily": "Noto Sans CJK JP Regular, Noto Sans CJK JP, Arial Unicode MS, Arial, sans-serif", "fontSize": 35, "fontColor": "#cad3f5", @@ -157,6 +158,13 @@ "backgroundColor": "rgba(54, 58, 79, 0.5)", "nPlusOneColor": "#c6a0f6", "knownWordColor": "#a6da95", + "jlptColors": { + "N1": "#ed8796", + "N2": "#f5a97f", + "N3": "#f9e2af", + "N4": "#a6e3a1", + "N5": "#8aadf4" + }, "secondary": { "fontSize": 24, "fontColor": "#ffffff", diff --git a/docs/configuration.md b/docs/configuration.md index 192ac96..17a18b6 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -552,12 +552,26 @@ See `config.example.jsonc` for detailed configuration options. | `fontWeight` | string | CSS font-weight, e.g. `"bold"`, `"normal"`, `"600"` (default: `"normal"`) | | `fontStyle` | string | `"normal"` or `"italic"` (default: `"normal"`) | | `backgroundColor` | string | Any CSS color, including `"transparent"` (default: `"rgba(54, 58, 79, 0.5)"`) | +| `enableJlpt` | boolean | Enable JLPT level underline styling (`false` by default) | +| `nPlusOneColor` | string | Existing n+1 highlight color (default: `#c6a0f6`) | +| `knownWordColor` | string | Existing known-word highlight color (default: `#a6da95`) | +| `jlptColors` | object | JLPT level underline colors object (`N1`..`N5`) | | `secondary` | object | Override any of the above for secondary subtitles (optional) | Secondary subtitle defaults: `fontSize: 24`, `fontColor: "#ffffff"`, `backgroundColor: "transparent"`. Any property not set in `secondary` falls back to the CSS defaults. **See `config.example.jsonc`** for the complete list of subtitle style configuration options. +`jlptColors` keys are: + +| Key | Default | Description | +| ---- | --------- | ---------------------------------------- | +| `N1` | `#ed8796` | JLPT N1 underline color | +| `N2` | `#f5a97f` | JLPT N2 underline color | +| `N3` | `#f9e2af` | JLPT N3 underline color | +| `N4` | `#a6e3a1` | JLPT N4 underline color | +| `N5` | `#8aadf4` | JLPT N5 underline color | + ### Texthooker Control whether the browser opens automatically when texthooker starts: diff --git a/docs/public/config.example.jsonc b/docs/public/config.example.jsonc index c587ec7..fdf142c 100644 --- a/docs/public/config.example.jsonc +++ b/docs/public/config.example.jsonc @@ -149,6 +149,7 @@ // Primary and secondary subtitle styling. // ========================================== "subtitleStyle": { + "enableJlpt": false, "fontFamily": "Noto Sans CJK JP Regular, Noto Sans CJK JP, Arial Unicode MS, Arial, sans-serif", "fontSize": 35, "fontColor": "#cad3f5", @@ -157,6 +158,13 @@ "backgroundColor": "rgba(54, 58, 79, 0.5)", "nPlusOneColor": "#c6a0f6", "knownWordColor": "#a6da95", + "jlptColors": { + "N1": "#ed8796", + "N2": "#f5a97f", + "N3": "#f9e2af", + "N4": "#a6e3a1", + "N5": "#8aadf4" + }, "secondary": { "fontSize": 24, "fontColor": "#ffffff", diff --git a/src/core/services/tokenizer-service.ts b/src/core/services/tokenizer-service.ts index c598068..7add0c6 100644 --- a/src/core/services/tokenizer-service.ts +++ b/src/core/services/tokenizer-service.ts @@ -183,6 +183,10 @@ function isKanaChar(char: string): boolean { ); } +/** + * Detects repeated-kana speech-like tokens (e.g. 「ああああ」, 「ははは」, 「うーん」 style patterns) + * so they are not JLPT-labeled when they are mostly expressive particles/sfx. + */ function isRepeatedKanaSfx(text: string): boolean { const normalized = text.trim(); if (!normalized) { diff --git a/src/main.ts b/src/main.ts index 2704a7f..4b2324c 100644 --- a/src/main.ts +++ b/src/main.ts @@ -470,20 +470,23 @@ function loadSubtitlePosition(): SubtitlePosition | null { function getJlptDictionarySearchPaths(): string[] { const homeDir = os.homedir(); const dictionaryRoots = [ - // Source checkout paths (development + source tree) + // Development/runtime source trees where the repo is checked out. path.join(__dirname, "..", "..", "vendor", "yomitan-jlpt-vocab"), path.join(app.getAppPath(), "vendor", "yomitan-jlpt-vocab"), - // Runtime package bundle paths + + // Packaged app resources (Electron build output layout). path.join(process.resourcesPath, "yomitan-jlpt-vocab"), path.join(process.resourcesPath, "app.asar", "vendor", "yomitan-jlpt-vocab"), - // User-configurable override locations + + // User override/config directories for manually installed dictionaries. USER_DATA_PATH, app.getPath("userData"), path.join(homeDir, ".config", "SubMiner"), path.join(homeDir, ".config", "subminer"), path.join(homeDir, "Library", "Application Support", "SubMiner"), path.join(homeDir, "Library", "Application Support", "subminer"), - // CLI invocation path (when launched from project root) + + // Last-resort fallback: current working directory (local CLI/test runs). process.cwd(), ]; diff --git a/src/renderer/subtitle-render.test.ts b/src/renderer/subtitle-render.test.ts index d53fffe..17fabb9 100644 --- a/src/renderer/subtitle-render.test.ts +++ b/src/renderer/subtitle-render.test.ts @@ -55,10 +55,19 @@ test("computeWordClass preserves known and n+1 classes while adding JLPT classes }); test("JLPT CSS rules use underline-only styling in renderer stylesheet", () => { - const cssText = fs.readFileSync( - path.join(process.cwd(), "dist", "renderer", "style.css"), - "utf-8", - ); + const distCssPath = path.join(process.cwd(), "dist", "renderer", "style.css"); + const srcCssPath = path.join(process.cwd(), "src", "renderer", "style.css"); + + const cssPath = fs.existsSync(distCssPath) + ? distCssPath + : srcCssPath; + if (!fs.existsSync(cssPath)) { + assert.fail( + "JLPT CSS file missing. Run `pnpm run build` first, or ensure src/renderer/style.css exists.", + ); + } + + const cssText = fs.readFileSync(cssPath, "utf-8"); for (let level = 1; level <= 5; level += 1) { const block = extractClassBlock(cssText, level);