Files
readest/apps/readest-app/src/services/wordlens/difficulty.ts
T
Huang Xin c2ac207945 refactor(wordlens): rename "Word Wise" to "Word Lens" (#4633)
"Word Wise" is a Kindle trademark, so rename the inline-gloss feature to
"Word Lens" throughout the product.

- User-facing strings → "Word Lens" across all 34 locales; brand translated
  for Chinese (zh-CN 单词透镜, zh-TW 單詞透鏡) and German (Word-Lens-Daten).
- Code identifiers: WordWise→WordLens, wordWise→wordLens, WORD_WISE→WORD_LENS.
- Files/dirs: src/services/wordwise→wordlens, WordWisePanel→WordLensPanel,
  wordwise{Ruby,Section}.ts, build/sync scripts, test dirs/fixtures,
  data/wordwise→data/wordlens.
- Storage paths: CDN base, R2 key, on-device cache dir, WORDLENS_R2_BUCKET env,
  pnpm wordlens:{manifest,sync}. manifest.json is path-agnostic so its
  sha256/bytes stay valid (verified).
- biome.json: point the formatter-ignore at data/wordlens so the generated
  one-line gloss packs aren't pretty-printed on commit.

Migration notes:
- Re-run `pnpm wordlens:sync` to upload packs to cdn.readest.com/wordlens/.
- Persisted view-settings keys renamed (wordWiseEnabled/Level/HintLang and
  wordWiseAutoDownload) — saved values reset to defaults once on upgrade.
- Cached packs under the old Data/wordwise/ orphan (harmless re-download).

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 19:09:29 +02:00

45 lines
2.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { WordLensSourceLang } from './types';
// CEFR proficiency levels. The slider picks the reader's level; a word is glossed
// when it's ABOVE that level (rarer than the vocabulary a learner at that level
// typically knows). This maps corpus-FREQUENCY bands to CEFR as an APPROXIMATION —
// true per-word CEFR data is English-only and wouldn't generalize to the zh/es/fr/…
// packs; for zh it's an analogy over the HSK rank scale.
export const CEFR_LEVELS = ['A1', 'A2', 'B1', 'B2', 'C1', 'C2'] as const;
export type CefrLevel = (typeof CEFR_LEVELS)[number];
export const WORD_LENS_MIN_LEVEL = 1; // A1
export const WORD_LENS_MAX_LEVEL = CEFR_LEVELS.length; // 6 (C2)
// Level (1=A1 … 6=C2) -> rank cutoff = the vocabulary a learner at that level
// knows. A word is glossed when its rank >= cutoff, so a LOWER level (A1) => LOWER
// cutoff => MORE hints (a beginner needs help with almost everything); a HIGHER
// level (C2) => only the rarest words get a hint. index 0 = level 1 (A1).
// Frequency-rank scale: en + every Latin/space-delimited source (es, fr, de, …).
// Bands ≈ the vocabulary size a learner at each CEFR level commands.
const FREQUENCY: readonly number[] = [1000, 2000, 4000, 8000, 14000, 24000];
// HSK scale for zh (build script ranks by HSK level×3000, non-HSK 30000). A1
// glosses above HSK1; C2 only the rarest. An analogy, not real CEFR.
const HSK: readonly number[] = [6000, 9000, 12000, 15000, 18000, 24000];
const clampLevel = (level: number): number =>
Math.min(WORD_LENS_MAX_LEVEL, Math.max(WORD_LENS_MIN_LEVEL, Math.round(level)));
export const getRankCutoff = (lang: WordLensSourceLang, level: number): number =>
(lang === 'zh' ? HSK : FREQUENCY)[clampLevel(level) - 1]!;
/** CEFR label ('A1'…'C2') for a 1..6 slider level. */
export const cefrLabel = (level: number): CefrLevel => CEFR_LEVELS[clampLevel(level) - 1]!;
export const isDifficult = (rank: number, cutoff: number): boolean => rank >= cutoff;
// Sources we can tokenize: zh (via jieba) + Latin/space-delimited languages
// (tokenized by the planner's regex). CJK languages lacking a segmenter
// (ja/ko) and Thai need a tier-3 segmenter and are blocked until then.
const UNSUPPORTED = new Set(['ja', 'ko', 'th']);
export const canTokenizeSource = (source: string): boolean =>
!UNSUPPORTED.has(source.toLowerCase().split('-')[0]!);