Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions src/renderer/src/i18n/locales/en-us.json
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,22 @@
}
}
}
},
"subtitleList": {
"empty": {
"title": "No matching subtitle file found",
"description": "You can choose a subtitle file with the button below or drag a subtitle file into this area."
},
"search": {
"placeholder": "Search subtitles...",
"pending": "Searching...",
"count": "Found {{count}} subtitle",
"count_one": "Found {{count}} subtitle",
"count_other": "Found {{count}} subtitles",
"none": "No subtitles match your search",
"emptyTitle": "No matches found",
"emptySubtitle": "Try another keyword"
}
}
},
"settings": {
Expand Down
18 changes: 18 additions & 0 deletions src/renderer/src/i18n/locales/ja-jp.json
Original file line number Diff line number Diff line change
Expand Up @@ -134,5 +134,23 @@
"show_mini_window": "快速助手",
"show_window": "表示ウィンドウ"
}
},
"player": {
"subtitleList": {
"empty": {
"title": "動画ファイルと同じフォルダーに一致する字幕ファイルが見つかりません",
"description": "下のボタンから字幕ファイルを選択するか、このエリアにドラッグしてください"
},
"search": {
"placeholder": "字幕を検索...",
"pending": "検索中...",
"count": "{{count}} 件の字幕が見つかりました",
"count_one": "{{count}} 件の字幕が見つかりました",
"count_other": "{{count}} 件の字幕が見つかりました",
"none": "一致する字幕が見つかりません",
"emptyTitle": "一致する結果がありません",
"emptySubtitle": "別のキーワードを試してください"
}
}
}
}
20 changes: 20 additions & 0 deletions src/renderer/src/i18n/locales/ru-ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -94,5 +94,25 @@
"show_mini_window": "Быстрый помощник",
"show_window": "отображать окно"
}
},
"player": {
"subtitleList": {
"empty": {
"title": "В той же папке, что и видео, не найден подходящий файл субтитров",
"description": "Вы можете выбрать файл субтитров с помощью кнопки ниже или перетащить его в эту область"
},
"search": {
"placeholder": "Поиск субтитров...",
"pending": "Поиск...",
"count": "Найден {{count}} файл субтитров",
"count_one": "Найден {{count}} файл субтитров",
"count_few": "Найдено {{count}} файла субтитров",
"count_many": "Найдено {{count}} файлов субтитров",
"count_other": "Найдено {{count}} субтитров",
"none": "Субтитры по запросу не найдены",
"emptyTitle": "Совпадений не найдено",
"emptySubtitle": "Попробуйте другой запрос"
}
}
Comment on lines +98 to +116
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

复数形式基本正确;建议补齐字幕面板按钮文案(跨语言一致性)

  • 俄语复数键 count_one/few/many/other 配置合理;占位符 {{count}} 正确。
  • 但本地化中缺少字幕面板按钮键:player.subtitles.hide/search/show(在 zh-cn 已新增)。缺失将导致退回默认语言或出现占位键。

建议在本文件补齐如下键值:

   "player": {
+    "subtitles": {
+      "hide": "Скрыть список субтитров",
+      "search": "Поиск субтитров",
+      "show": "Показать список субтитров"
+    },
     "subtitleList": {

请确认代码调用路径为 t('player.subtitleList.search.count', { count }),以使用 i18next 复数推断。若需要,我可批量为所有语言包补齐缺失键并提交补丁。


正在验证 ru-ru.json 是否包含 player.subtitles 键,并检查代码中是否使用 t('player.subtitleList.search.count', { count }) 调用。


🏁 Script executed:

#!/bin/bash
rg -nP '"subtitles"\s*:' -n src/renderer/src/i18n/locales/ru-ru.json
rg -nP '"subtitles"\s*:' -n src/renderer/src/i18n/locales/zh-cn.json
rg -nP "t\(['\"]player\.subtitleList\.search\.count" -C2 -n src/renderer/src

Length of output: 645


🏁 Script executed:

#!/bin/bash
sed -n '190,240p' src/renderer/src/i18n/locales/ru-ru.json
rg -nP '"player"\s*:' -n src/renderer/src/i18n/locales/zh-cn.json -C10

Length of output: 557


🏁 Script executed:

#!/bin/bash
rg -nP '"subtitles":\s*{' -n -A15 src/renderer/src/i18n/locales/ru-ru.json

Length of output: 0


🏁 Script executed:

#!/bin/bash
sed -n '200,260p' src/renderer/src/i18n/locales/ru-ru.json

Length of output: 0


🏁 Script executed:

#!/bin/bash
FILE=src/renderer/src/i18n/locales/ru-ru.json
ls -l "$FILE"
wc -l "$FILE"
rg -n "Скрыть список" "$FILE"
rg -n "Показать список" "$FILE"
rg -n "Поиск субтитров" "$FILE"

Length of output: 183


添加播放器字幕面板按钮翻译键

ru-ru.json 和 zh-cn.json 均缺少 player.subtitles.hide、search、show 三个键,需补充以保持跨语言一致性并避免回退默认语言。

建议在各自语言包的 player 节点下添加:

   "player": {
+    "subtitles": {
+      "hide": "Скрыть список субтитров",      // 隐藏字幕列表
+      "search": "Поиск субтитров",            // 搜索字幕
+      "show": "Показать список субтитров"     // 显示字幕列表
+    },
     "subtitleList": {
       …  
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"player": {
"subtitleList": {
"empty": {
"title": "В той же папке, что и видео, не найден подходящий файл субтитров",
"description": "Вы можете выбрать файл субтитров с помощью кнопки ниже или перетащить его в эту область"
},
"search": {
"placeholder": "Поиск субтитров...",
"pending": "Поиск...",
"count": "Найден {{count}} файл субтитров",
"count_one": "Найден {{count}} файл субтитров",
"count_few": "Найдено {{count}} файла субтитров",
"count_many": "Найдено {{count}} файлов субтитров",
"count_other": "Найдено {{count}} субтитров",
"none": "Субтитры по запросу не найдены",
"emptyTitle": "Совпадений не найдено",
"emptySubtitle": "Попробуйте другой запрос"
}
}
"player": {
"subtitles": {
"hide": "Скрыть список субтитров",
"search": "Поиск субтитров",
"show": "Показать список субтитров"
},
"subtitleList": {
"empty": {
"title": "В той же папке, что и видео, не найден подходящий файл субтитров",
"description": "Вы можете выбрать файл субтитров с помощью кнопки ниже или перетащить его в эту область"
},
"search": {
"placeholder": "Поиск субтитров...",
"pending": "Поиск...",
"count": "Найден {{count}} файл субтитров",
"count_one": "Найден {{count}} файл субтитров",
"count_few": "Найдено {{count}} файла субтитров",
"count_many": "Найдено {{count}} файлов субтитров",
"count_other": "Найдено {{count}} субтитров",
"none": "Субтитры по запросу не найдены",
"emptyTitle": "Совпадений не найдено",
"emptySubtitle": "Попробуйте другой запрос"
}
}
🤖 Prompt for AI Agents
In src/renderer/src/i18n/locales/ru-ru.json around lines 98-116, the locale is
missing the player.subtitles keys (hide, search, show); add these three keys
under the existing player node so translations match other locales and prevent
fallback to the default language—use appropriate Russian strings such as "Скрыть
субтитры" for hide, "Поиск субтитров" for search, and "Показать субтитры" for
show (ensure JSON syntax and comma placement are correct).

}
}
99 changes: 58 additions & 41 deletions src/renderer/src/i18n/locales/zh-cn.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,20 +85,6 @@
"tooltip": "透明背景"
}
},
"mask-mode": {
"background-locked": {
"tooltip": "遮罩模式下背景样式固定为高斯模糊"
},
"disable": {
"tooltip": "关闭遮罩模式"
},
"enable": {
"tooltip": "开启遮罩模式,锁定内嵌字幕区域"
},
"label": "遮罩模式",
"onboarding": "拖动或调整字幕框以匹配视频中的内嵌字幕区域",
"title": "遮罩模式"
},
"display-mode": {
"bilingual": {
"label": "双语",
Expand All @@ -117,9 +103,30 @@
"label": "译文",
"tooltip": "仅显示译文字幕 (Ctrl+3)"
}
},
"mask-mode": {
"background-locked": {
"tooltip": "遮罩模式下背景样式固定为高斯模糊"
},
"disable": {
"tooltip": "关闭遮罩模式"
},
"enable": {
"tooltip": "开启遮罩模式,锁定内嵌字幕区域"
},
"label": "遮罩模式",
"onboarding": "拖动或调整字幕框以匹配视频中的内嵌字幕区域",
"title": "遮罩模式"
}
}
},
"dictionary": {
"error": "查询失败",
"loading": "查询中...",
"more_definitions": "... 还有 {{count}} 个释义",
"pronunciation": "点击发音",
"translations": "常用翻译"
},
Comment on lines +123 to +129
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

“dictionary” 放于 player 命名空间;核对各处引用是否一致

若组件期望根级 dictionary.* 将导致缺失;请统一为 player.dictionary.* 或调整代码。

可用脚本核对所有引用与各语言包是否存在相同路径:


🏁 Script executed:

#!/bin/bash
echo "代码引用:"
rg -n -C2 "t\\(['\"]player\\.dictionary\\." src || true
echo "Locale 存在性:"
fd -a -g "src/renderer/src/i18n/locales/*.json" | xargs -I{} rg -n "player\"\\s*:\\s*\\{[^}]*\"dictionary\"" -n {}

Length of output: 1654


本地化键与代码使用不一致: 代码中使用了 player.dictionary.*,但语言包(如 src/renderer/src/i18n/locales/zh-cn.json)将 dictionary 放在根级,导致翻译无法加载。请将 dictionary 节点移入 player 命名空间,或调整代码引用为根级 dictionary.*

🤖 Prompt for AI Agents
In src/renderer/src/i18n/locales/zh-cn.json around lines 123 to 129 the
localization keys are defined under a root-level "dictionary" object while the
application expects translations under "player.dictionary.*"; move the existing
"dictionary" node inside a new or existing "player" namespace (i.e., nest
"dictionary" under "player") so keys become player.dictionary.error,
player.dictionary.loading, etc., or alternatively update code to reference the
root-level dictionary keys (change usages from player.dictionary.* to
dictionary.*) — choose the approach consistent with other locale files and keep
key structure identical after the change.

"errorRecovery": {
"actions": {
"backToHome": "返回首页",
Expand Down Expand Up @@ -179,39 +186,49 @@
"label": "文件路径"
}
},
"subtitles": {
"hide": "隐藏字幕列表",
"show": "展开字幕列表"
},
"mediaServerPrompt": {
"title": "视频格式不兼容",
"subtitle": "检测到当前视频格式不受支持",
"actions": {
"install": "立即安装",
"later": "稍后再说"
},
"benefits": {
"title": "安装 Media Server 可以解决此问题",
"compatibility": {
"title": "完美兼容",
"description": "支持所有常见视频格式,包括高清和蓝光视频"
},
"transcoding": {
"title": "实时转码",
"description": "后台自动转码,无需等待,即开即用"
"description": "支持所有常见视频格式,包括高清和蓝光视频",
"title": "完美兼容"
},
"easySetup": {
"title": "一键安装",
"description": "自动化安装流程,无需手动配置"
"description": "自动化安装流程,无需手动配置",
"title": "一键安装"
},
"title": "安装 Media Server 可以解决此问题",
"transcoding": {
"description": "后台自动转码,无需等待,即开即用",
"title": "实时转码"
}
},
"actions": {
"install": "立即安装",
"later": "稍后再说"
}
"subtitle": "检测到当前视频格式不受支持",
"title": "视频格式不兼容"
},
"dictionary": {
"loading": "查询中...",
"error": "查询失败",
"pronunciation": "点击发音",
"more_definitions": "... 还有 {{count}} 个释义",
"translations": "常用翻译"
"subtitles": {
"hide": "隐藏字幕列表",
"search": "搜索字幕",
"show": "展开字幕列表"
},
"subtitleList": {
"empty": {
"title": "在视频文件同目录下未找到匹配的字幕文件",
"description": "您可以点击下方按钮选择字幕文件,或将字幕文件拖拽到此区域"
},
"search": {
"placeholder": "搜索字幕...",
"pending": "搜索中...",
"count": "找到 {{count}} 条字幕",
"count_one": "找到 {{count}} 条字幕",
"count_other": "找到 {{count}} 条字幕",
"none": "未找到匹配的字幕",
"emptyTitle": "未找到匹配结果",
"emptySubtitle": "请尝试其他关键词"
}
Comment on lines +217 to +231
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

subtitleList 搜索文案完备;可精简多余复数键(可选)

中文不需要复数变体,保留 count_other 足够;count 与 count_one 可视为冗余(不必改也无害)。

🤖 Prompt for AI Agents
In src/renderer/src/i18n/locales/zh-cn.json around lines 217 to 231, the
subtitleList.search plural keys are redundant for Chinese; remove the
unnecessary "count" and "count_one" entries and keep a single "count" or
"count_other" key (preferably "count") with value "找到 {{count}} 条字幕" to simplify
the locale file while preserving behavior.

}
},
"search": {
Expand Down Expand Up @@ -415,11 +432,11 @@
},
"description": "FFprobe 用于读取音视频元数据和流信息,帮助 EchoPlayer 验证文件状态并完成兼容性检测。",
"download": {
"install": "安装",
"cancel": "取消下载",
"cancelled": "下载已取消",
"downloading": "下载中",
"failed": "下载失败,请重试",
"install": "安装",
"success": "下载完成"
},
"path": {
Expand Down Expand Up @@ -489,12 +506,12 @@
"show_settings": "打开设置",
"single_loop": "循环播放",
"title": "快捷键",
"toggle_auto_pause": "切换自动暂停",
"toggle_fullscreen": "切换全屏",
"toggle_new_context": "清除上下文",
"toggle_show_assistants": "切换助手显示",
"toggle_show_topics": "切换话题显示",
"toggle_subtitle_panel": "切换字幕面板",
"toggle_auto_pause": "切换自动暂停",
"volume_down": "减小音量",
"volume_up": "增大音量",
"zoom_in": "放大界面",
Expand Down
18 changes: 18 additions & 0 deletions src/renderer/src/i18n/locales/zh-tw.json
Original file line number Diff line number Diff line change
Expand Up @@ -95,5 +95,23 @@
"show_mini_window": "快速助手",
"show_window": "顯示視窗"
}
},
"player": {
"subtitleList": {
"empty": {
"title": "在影片檔同目錄下未找到符合的字幕檔案",
"description": "您可以點擊下方按鈕選擇字幕檔,或將字幕檔拖曳到此區域"
},
"search": {
"placeholder": "搜尋字幕...",
"pending": "搜尋中...",
"count": "找到 {{count}} 筆字幕",
"count_one": "找到 {{count}} 筆字幕",
"count_other": "找到 {{count}} 筆字幕",
"none": "未找到符合的字幕",
"emptyTitle": "找不到符合的結果",
"emptySubtitle": "請嘗試其他關鍵字"
}
}
}
}
64 changes: 59 additions & 5 deletions src/renderer/src/infrastructure/hooks/useShortcut.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,59 @@ interface UseShortcutOptions {
enableOnFormTags?: boolean
enabled?: boolean
description?: string
allowWhenTyping?: boolean
}

const defaultOptions: UseShortcutOptions = {
preventDefault: true,
enableOnFormTags: true,
enabled: true
enabled: true,
allowWhenTyping: false
}

const NON_TYPABLE_INPUT_TYPES = new Set([
'button',
'checkbox',
'color',
'date',
'datetime-local',
'file',
'hidden',
'image',
'month',
'radio',
'range',
'reset',
'submit',
'time',
'week'
])

function isTypingInput(element: Element | null): boolean {
if (!element || !(element instanceof HTMLElement)) {
return false
}

if (element.isContentEditable) {
return true
}

const tagName = element.tagName.toLowerCase()

if (tagName === 'textarea') {
return true
}

if (tagName === 'input') {
const el = element as HTMLInputElement
return !NON_TYPABLE_INPUT_TYPES.has((el.type || '').toLowerCase())
}

if (tagName === 'select') {
return true
}

return false
}

export const useShortcut = (
Expand Down Expand Up @@ -43,17 +90,24 @@ export const useShortcut = (
useHotkeys(
shortcutConfig?.enabled ? formatShortcut(shortcutConfig.shortcut) : 'none',
(e) => {
const activeElement = document.activeElement
const typingActive =
!options.allowWhenTyping && isTypingInput(activeElement) && e.key !== 'Escape'

if (options.enabled === false || typingActive) {
return
}

if (options.preventDefault) {
e.preventDefault()
}
if (options.enabled !== false) {
callback(e)
}

callback(e)
},
{
enableOnFormTags: options.enableOnFormTags,
description: options.description || shortcutConfig?.key,
enabled: !!shortcutConfig?.enabled
enabled: options.enabled !== false && !!shortcutConfig?.enabled
}
)
}
Expand Down
10 changes: 9 additions & 1 deletion src/renderer/src/pages/player/PlayerPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { PlayerSettingsService } from '@renderer/services/PlayerSettingsLoader'
import { playerSettingsPersistenceService } from '@renderer/services/PlayerSettingsSaver'
import { usePlayerStore } from '@renderer/state'
import { usePlayerSessionStore } from '@renderer/state/stores/player-session.store'
import { usePlayerUIStore } from '@renderer/state/stores/player-ui.store'
import { IpcChannel } from '@shared/IpcChannel'
import { Layout, Tooltip } from 'antd'

Expand All @@ -24,7 +25,7 @@ import {
FONT_SIZES,
SPACING
} from '@renderer/infrastructure/styles/theme'
import { ArrowLeft, PanelRightClose, PanelRightOpen } from 'lucide-react'
import { ArrowLeft, PanelRightClose, PanelRightOpen, Search } from 'lucide-react'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useNavigate, useParams } from 'react-router-dom'
Expand Down Expand Up @@ -93,6 +94,7 @@ function PlayerPage() {

const { t } = useTranslation()
const { subtitlePanelVisible, toggleSubtitlePanel } = usePlayerStore()
const toggleSubtitleSearch = usePlayerUIStore((s) => s.toggleSubtitleSearch)

const [videoData, setVideoData] = useState<VideoData | null>(null)
const [loading, setLoading] = useState(true)
Expand Down Expand Up @@ -600,8 +602,14 @@ function PlayerPage() {
<NavTitle title={videoData.title}>{videoData.title}</NavTitle>
</NavbarCenter>
<NavbarRight>
<Tooltip title={t('player.subtitles.search')} mouseEnterDelay={0.8}>
<NavbarIcon onClick={toggleSubtitleSearch}>
<Search size={18} />
</NavbarIcon>
</Tooltip>
Comment on lines +605 to +609
Copy link
Contributor

@coderabbitai coderabbitai bot Oct 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

驗證 i18n 鍵是否存在。

第 605 行引用了翻譯鍵 'player.subtitles.search',但在提供的所有本地化文件中,新增的鍵路徑是 player.subtitleList.* 而非 player.subtitles.search。請確認此鍵是否存在,或者應該使用不同的鍵。

運行以下腳本驗證翻譯鍵的使用:


🏁 Script executed:

#!/bin/bash
# 描述:在本地化文件中搜索 player.subtitles.search 鍵
# 預期:找到該鍵的定義或確認應使用其他鍵

# 搜索 player.subtitles.search
echo "=== 搜索 player.subtitles.search ==="
rg -n "subtitles.*search" src/renderer/src/i18n/locales/

# 搜索所有 player.subtitle 相關的鍵
echo "=== 搜索所有 player 下的 subtitle 相關鍵 ==="
rg -n '"subtitle' src/renderer/src/i18n/locales/ -A 2

Length of output: 3807


确保 i18n 键 'player.subtitles.search' 在所有本地化文件中定义
zh-CN 已包含该键,但 en-US、ru-RU、ja-JP 等缺失,请在对应 locales 文件中补充该翻译。

🤖 Prompt for AI Agents
In src/renderer/src/pages/player/PlayerPage.tsx around lines 605 to 609 the
Tooltip uses the i18n key 'player.subtitles.search' but that key only exists in
zh-CN; add the missing 'player.subtitles.search' entry to each locales file
(e.g., src/renderer/locales/en-US.json, ru-RU.json, ja-JP.json and any other
supported locale files) with appropriate translations, keep the JSON structure
consistent with existing locale files, then run the i18n lint/build step (or the
project's locale validation script) to ensure no missing keys remain.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@codex adress that feedback

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

Testing

  • ⚠️ Not run (not requested)

View task →

<Tooltip
title={subtitlePanelVisible ? t('player.subtitles.hide') : t('player.subtitles.show')}
mouseEnterDelay={0.8}
>
<NavbarIcon onClick={toggleSubtitlePanel}>
{subtitlePanelVisible ? (
Expand Down
Loading
Loading