5e366018df
* fix(cbz,i18n): ComicInfo metadata + CBZ page count + WebDAV i18n Closes #4253 (ComicInfo.xml not read) and #4255 (CBZ shows "1 page left"). CBZ / ComicInfo (foliate-js submodule + Readest derivation): - comic-book.js: find ComicInfo.xml in subdirectories too, parse description / subject / identifier / published / series fields beyond the prior name+position pair. Series Count populates the canonical `belongsTo.series.total`; no top-level duplication. - bookService.ts / readerStore.ts: derive `metadata.seriesTotal` from `belongsTo.series.total` in parallel to the existing series / seriesIndex derivation. - ProgressBar / FooterBar / DesktopFooterBar: drop the hard-coded `pagesLeft = 1` for fixed-layout books and compute it from `section.total - section.current`. FooterBar uses `FIXED_LAYOUT_FORMATS.has(bookFormat)` so CBZ picks `section` (correct image count) instead of `pageinfo` (locations). - ProgressBar: switch the remaining-pages text to "in book" for fixed-layout titles (no chapter structure) and keep "in chapter" for reflowable books. WebDAV refactor for translation coverage: - WebDAVBrowsePane / SyncHistoryPanel called `t(...)` (passed as a prop) instead of `_(...)`. The i18next-scanner only looks for `_`, so ~53 strings were unreachable and shipped in English to every locale. Switched both components to call `useTranslation()` themselves; helpers that aren't React FCs take `_: TranslationFunc` so the scanner sees the literal calls. - WebDAVClient.checkConnection now returns a `code` discriminator (`SERVER_URL_REQUIRED` / `AUTH_FAILED` / `ROOT_NOT_FOUND` / `UNEXPECTED_STATUS` / `NETWORK`); raw English `message` is reserved for the dev console. New `formatConnectError` and `formatSyncError` helpers in WebDAVForm translate via a switch where each branch is a literal `_('...')`. Same treatment for the sync-failure path that previously surfaced raw e.message. - "Syncing 0 / {{total}}" is now parameterized as "Syncing {{n}} / {{total}}" with n=0 at startup so the digit formats naturally and the template can be reused mid-sync. - "Cleanup · {{count}} book(s)" hard-coded options used unsupported ternary; rewrote as plural-aware key. i18n scanner fix (i18next-scanner.config.cjs): - vinyl-fs walked into directories whose names end in source-file extensions (Next.js route folder `runtime-config.js/`, Playwright screenshot folder `*.test.tsx/`) and crashed with EISDIR. Resolved by expanding globs via `fs.globSync` and filtering to files only before handing to the scanner. TypeScript-syntax sites that broke esprima during extraction: - WebDAVBrowsePane / WebDAVForm: `(e as Error).message` and `failed[0]!.title` inside `_(..., options)` arguments. Replaced with `e instanceof Error ? e.message : String(e)` and `failed[0]?.title ?? ''` — also runtime-safer. User-facing em-dash cleanup: - Removed em-dashes from translation keys across SyncHistoryPanel / WebDAVForm / WebDAVBrowsePane / SyncPassphraseSection / send/page / replicaCryptoMiddleware / AIPanel. Tagline in `layout.tsx` kept. Locale translations: - ~2400 translations applied across all 33 locales for the keys that were either newly extractable, freshly worded, or pre-existing but untranslated. Zero `__STRING_NOT_TRANSLATED__` remain after the run. Misc: - next.config.mjs: drop `eslint.ignoreDuringBuilds: true` so build runs the same lint as CI. - Collection type: add `total?: string` for ComicInfo series count. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci(test): fix vitest invocation, run with 4 workers `pnpm test:pr:web` was chaining `pnpm test -- --watch=false`, which pnpm expanded into: dotenv -e .env -e .env.test.local -- vitest -- --watch=false The second `--` made vitest treat `--watch=false` as a positional file pattern, not a flag. Vitest then fell back to defaults (in CI's non-TTY env that still meant a one-shot run, so the suite passed), but the worker pool was effectively serialized for big chunks of the 243-file run — wall ~90 s on a 4-vCPU runner where the parallel-sum of phases was ~236 s (≈2.6× effective parallelism). Replace the chained pnpm invocation with a direct call to `vitest run --maxWorkers=4`, matching the 4 vCPUs the GH Actions ubuntu-latest runner provides. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
59 lines
1.5 KiB
JavaScript
59 lines
1.5 KiB
JavaScript
const fs = require('fs');
|
|
const lngs = require('./i18n-langs.json');
|
|
|
|
// Resolve globs ourselves and drop directory matches. The scanner pipes the
|
|
// input through vinyl-fs without `nodir: true`, so directories whose names
|
|
// look like source files (e.g. Next.js route folders like `runtime-config.js/`
|
|
// or screenshot folders like `*.test.tsx/`) blow up with EISDIR.
|
|
const resolveInput = (patterns) => {
|
|
const positives = patterns.filter((p) => !p.startsWith('!'));
|
|
const excludes = patterns
|
|
.filter((p) => p.startsWith('!'))
|
|
.map((p) => p.slice(1));
|
|
const matched = new Set();
|
|
for (const pattern of positives) {
|
|
for (const entry of fs.globSync(pattern, { exclude: excludes })) {
|
|
try {
|
|
if (fs.statSync(entry).isFile()) matched.add(entry);
|
|
} catch {
|
|
// ignore unreadable entries
|
|
}
|
|
}
|
|
}
|
|
return [...matched];
|
|
};
|
|
|
|
const options = {
|
|
debug: false,
|
|
sort: false,
|
|
func: {
|
|
list: ['_'],
|
|
extensions: ['.js', '.jsx', '.ts', '.tsx'],
|
|
},
|
|
lngs,
|
|
ns: ['translation'],
|
|
defaultNs: 'translation',
|
|
defaultValue: '__STRING_NOT_TRANSLATED__',
|
|
resource: {
|
|
loadPath: './public/locales/{{lng}}/{{ns}}.json',
|
|
savePath: './public/locales/{{lng}}/{{ns}}.json',
|
|
jsonIndent: 2,
|
|
lineEnding: '\n',
|
|
},
|
|
keySeparator: false,
|
|
nsSeparator: false,
|
|
interpolation: {
|
|
prefix: '{{',
|
|
suffix: '}}',
|
|
},
|
|
metadata: {},
|
|
allowDynamicKeys: true,
|
|
removeUnusedKeys: true,
|
|
};
|
|
|
|
module.exports = {
|
|
input: resolveInput(['src/**/*.{js,jsx,ts,tsx}', '!src/**/*.test.{js,jsx,ts,tsx}']),
|
|
output: '.',
|
|
options,
|
|
};
|