diff --git a/index.js b/index.js index 90dd453..8403f8c 100644 --- a/index.js +++ b/index.js @@ -8,6 +8,7 @@ import { display } from './lib/display.js'; import { clearCache, setConfig } from './lib/cache.js'; import ora from 'ora'; import chalk from 'chalk'; +import { getLang } from './lib/i18n.js'; const require = createRequire(import.meta.url); const pkg = require('./package.json'); @@ -20,6 +21,7 @@ program .option('-r, --raw', '显示原始详细数据') .option('--clear-cache', '清空本地缓存') .option('--setup', '配置JAVDB Cookie(可选,提高查询覆盖率)') + .option('-l, --lang ', '显示语言 zh/en/jp/kr/de', 'zh') .action(async (id, options) => { if (options.setup) { @@ -63,21 +65,26 @@ program process.exit(0); } - const spinner = ora(`正在查询 ${id.toUpperCase()} ...`).start(); + const t = getLang(options.lang || 'zh'); + const spinner = ora(`${t.searching} ${id.toUpperCase()} ...`).start(); try { const result = await search(id.toUpperCase()); spinner.stop(); if (!result) { - console.log(chalk.red(`\n未找到番号: ${id.toUpperCase()}`)); + console.log(chalk.red(`\n${t.notFound}: ${id.toUpperCase()}`)); + // Windows 用户提示 + if (process.platform === 'win32') { + process.stderr.write(chalk.gray(` ${t.windowsHint}\n`)); + } process.exit(1); } - display(result, options.raw); + display(result, options.raw, options.lang || 'zh'); } catch (err) { spinner.stop(); - console.error('\n查询失败:', err.message); + console.error(`\n${t.queryFailed}:`, err.message); process.exit(1); } }); diff --git a/lib/display.js b/lib/display.js index c0ec373..cdb08e3 100644 --- a/lib/display.js +++ b/lib/display.js @@ -1,19 +1,22 @@ import chalk from 'chalk'; +import { getLang } from './i18n.js'; const DIVIDER = chalk.gray('─'.repeat(60)); function row(label, value, color = chalk.white) { if (!value || (Array.isArray(value) && value.length === 0)) return; - const paddedLabel = chalk.cyan(label.padEnd(8, ' ')); // 用全角空格对齐中文 + const paddedLabel = chalk.cyan(label.padEnd(8, ' ')); console.log(` ${paddedLabel} ${color(value)}`); } -export function display(info, raw = false) { +export function display(info, raw = false, lang = 'zh') { if (raw) { console.log(JSON.stringify(info, null, 2)); return; } + const t = getLang(lang); + console.log(''); console.log(DIVIDER); console.log( @@ -29,29 +32,29 @@ export function display(info, raw = false) { console.log(DIVIDER); - row('女优', info.actresses.join(' / '), chalk.magenta); - row('男优', info.actors.join(' / '), chalk.blue); - row('发售日期', info.releaseDate, chalk.green); - row('时长', info.duration); - row('制作商', info.studio, chalk.yellow); - row('发行商', info.label); - row('导演', info.director); - row('系列', info.series); - row('评分', info.score ? `${info.score}${info.scoreCount ? `(${info.scoreCount}人评价)` : ''}` : '', chalk.yellow); - row('想看', info.wantCount ? `${info.wantCount} 人想看` : '', chalk.cyan); + row(t.actress, info.actresses.join(' / '), chalk.magenta); + row(t.actor, info.actors.join(' / '), chalk.blue); + row(t.releaseDate, info.releaseDate, chalk.green); + row(t.duration, info.duration); + row(t.studio, info.studio, chalk.yellow); + row(t.label, info.label); + row(t.director, info.director); + row(t.series, info.series); + row(t.rating, info.score ? `${info.score}${info.scoreCount ? `(${info.scoreCount})` : ''}` : '', chalk.yellow); + row(t.wishlist, info.wantCount ? `${info.wantCount}` : '', chalk.cyan); if (info.tags.length > 0) { - const tagStr = info.tags.map(t => chalk.bgGray(` ${t} `)).join(' '); - console.log(' ' + chalk.cyan('类别  ') + ' ' + tagStr); + const tagStr = info.tags.map(tag => chalk.bgGray(` ${tag} `)).join(' '); + console.log(' ' + chalk.cyan(t.tags.padEnd(8, ' ')) + ' ' + tagStr); } if (info.coverUrl) { console.log(''); - console.log(' ' + chalk.cyan('封面  ') + ' ' + chalk.underline.gray(info.coverUrl)); + console.log(' ' + chalk.cyan(t.cover.padEnd(8, ' ')) + ' ' + chalk.underline.gray(info.coverUrl)); } if (info.source) { - console.log(' ' + chalk.gray(`数据来源: ${info.source}`)); + console.log(' ' + chalk.gray(`${t.source}: ${info.source}`)); } console.log(DIVIDER); diff --git a/lib/fetcher.js b/lib/fetcher.js index a6666e4..2d79c2e 100644 --- a/lib/fetcher.js +++ b/lib/fetcher.js @@ -360,10 +360,5 @@ export async function search(id) { } } - // Windows 用户提示 - if (process.platform === 'win32') { - process.stderr.write(chalk.gray(' 温馨提示: 此车牌号需要JAVDB数据源,Windows暂不支持~\n')); - } - return null; } \ No newline at end of file diff --git a/lib/i18n.js b/lib/i18n.js new file mode 100644 index 0000000..a1be2e7 --- /dev/null +++ b/lib/i18n.js @@ -0,0 +1,111 @@ +export const messages = { + zh: { + actress: '女优', + actor: '男优', + releaseDate: '发售日期', + duration: '时长', + studio: '制作商', + label: '发行商', + director: '导演', + series: '系列', + rating: '评分', + wishlist: '想看', + tags: '类别', + cover: '封面', + source: '数据来源', + searching: '正在查询', + notFound: '未找到番号', + queryFailed: '查询失败', + cacheCleared: '缓存已清空', + fc2Detected: 'FC2番号已转换为', + windowsHint: '温馨提示: 此番号需要JAVDB数据源,Windows暂不支持~', + }, + en: { + actress: 'Actress', + actor: 'Actor', + releaseDate: 'Released', + duration: 'Duration', + studio: 'Studio', + label: 'Label', + director: 'Director', + series: 'Series', + rating: 'Rating', + wishlist: 'Wishlist', + tags: 'Tags', + cover: 'Cover', + source: 'Source', + searching: 'Searching', + notFound: 'Title not found', + queryFailed: 'Query failed', + cacheCleared: 'Cache cleared', + fc2Detected: 'FC2 format detected', + windowsHint: 'Note: This title requires JAVDB, which is unavailable on Windows.', + }, + jp: { + actress: '女優', + actor: '男優', + releaseDate: '発売日', + duration: '時間', + studio: 'メーカー', + label: 'レーベル', + director: '監督', + series: 'シリーズ', + rating: '評価', + wishlist: '観たい', + tags: 'カテゴリ', + cover: 'カバー', + source: 'データソース', + searching: '検索中', + notFound: '番号が見つかりません', + queryFailed: '検索失敗', + cacheCleared: 'キャッシュを削除しました', + fc2Detected: 'FC2番号を変換しました', + windowsHint: '注意: この番号はJAVDBが必要ですが、WindowsではJAVDBが利用できません。', + }, + kr: { + actress: '여배우', + actor: '남배우', + releaseDate: '발매일', + duration: '시간', + studio: '제작사', + label: '레이블', + director: '감독', + series: '시리즈', + rating: '평점', + wishlist: '보고싶어요', + tags: '카테고리', + cover: '커버', + source: '데이터 출처', + searching: '검색 중', + notFound: '번호를 찾을 수 없습니다', + queryFailed: '검색 실패', + cacheCleared: '캐시가 삭제되었습니다', + fc2Detected: 'FC2 번호 변환됨', + windowsHint: '알림: 이 번호는 JAVDB가 필요하지만 Windows에서는 사용할 수 없습니다.', + }, + de: { + actress: 'Darstellerin', + actor: 'Darsteller', + releaseDate: 'Erscheinungsdatum', + duration: 'Laufzeit', + studio: 'Studio', + label: 'Label', + director: 'Regisseur', + series: 'Serie', + rating: 'Bewertung', + wishlist: 'Merkliste', + tags: 'Kategorien', + cover: 'Cover', + source: 'Datenquelle', + searching: 'Suche läuft', + notFound: 'Titel nicht gefunden', + queryFailed: 'Suche fehlgeschlagen', + cacheCleared: 'Cache geleert', + fc2Detected: 'FC2-Format erkannt', + windowsHint: 'Hinweis: Dieser Titel benötigt JAVDB, das unter Windows nicht verfügbar ist.', + }, +}; + +export function getLang(lang) { + return messages[lang] || messages.zh; // 默认中文 +} \ No newline at end of file