환경 설정 > MKV 만들기 > 기본 자막 언어 (MKV 기본 자막 트랙 · 플레이어가 먼저 켜는 자막)

화면 언어 따르기 (처음 값) / 한국어 / English / 日本語 / 中文 / 직접 고르기 (자막 언어 목록).
화면은 일본어인데 한국어 자막을 기본으로 원하는 경우처럼, 고른 언어가 화면 언어보다 먼저.
설정 preferredSubtitleLanguage ('' = 화면 언어 따르기). i18n_small_test 에 시험.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
묶음4
RyuJongJin 2026-10-10 02:40:36 +09:00
parent f429c59502
commit c986690b3c
10 changed files with 122 additions and 11 deletions

View File

@ -163,6 +163,7 @@
"MKV 목록에 {0}개를 추가했습니다.": "Added {0} to the MKV list.", "MKV 목록에 {0}개를 추가했습니다.": "Added {0} to the MKV list.",
"MKV 목록에 추가": "Add to MKV list", "MKV 목록에 추가": "Add to MKV list",
"MKV 완성": "MKV done", "MKV 완성": "MKV done",
"MKV 의 기본 자막 트랙 · 플레이어가 먼저 켜는 자막": "Default subtitle track of MKV files · subtitle the player turns on first",
"MKV 화면": "MKV screen", "MKV 화면": "MKV screen",
"MKV 화면 (기본)": "MKV screen (default)", "MKV 화면 (기본)": "MKV screen (default)",
"MKV 화면 (지금 여기)": "MKV screen (you are here)", "MKV 화면 (지금 여기)": "MKV screen (you are here)",
@ -502,6 +503,7 @@
"기본": "Default", "기본": "Default",
"기본 앱으로 열기": "Open with default app", "기본 앱으로 열기": "Open with default app",
"기본 연결 프로그램": "Default app", "기본 연결 프로그램": "Default app",
"기본 자막 언어": "Default subtitle language",
"기본 크기 ({0}%)": "Default size ({0}%)", "기본 크기 ({0}%)": "Default size ({0}%)",
"기본 크기 ({0}%) 로 · 기본 크기는 환경 설정 > 화면 에서": "Back to the default size ({0}%) · set the default size in Settings > Display", "기본 크기 ({0}%) 로 · 기본 크기는 환경 설정 > 화면 에서": "Back to the default size ({0}%) · set the default size in Settings > Display",
"기본 프로그램": "Default program", "기본 프로그램": "Default program",
@ -1693,6 +1695,8 @@
"지원하지 않는 protobuf 형식 (wire type {0}, 위치 {1})": "Unsupported protobuf format (wire type {0}, position {1})", "지원하지 않는 protobuf 형식 (wire type {0}, 위치 {1})": "Unsupported protobuf format (wire type {0}, position {1})",
"지원하지 않는 문자셋: {0}": "Unsupported charset: {0}", "지원하지 않는 문자셋: {0}": "Unsupported charset: {0}",
"지정한 파일이 없습니다": "The specified file does not exist", "지정한 파일이 없습니다": "The specified file does not exist",
"직접 고르기: {0}": "Choose: {0}",
"직접 고르기…": "Choose…",
"직접 고른 적이 없는 예전 기본값을 새 기본값으로 바꿨습니다. 원래대로 쓰려면 환경 설정에서 되돌리세요.": "Old default values that you never chose yourself were changed to the new defaults. To keep the old behavior, change them back in Settings.", "직접 고른 적이 없는 예전 기본값을 새 기본값으로 바꿨습니다. 원래대로 쓰려면 환경 설정에서 되돌리세요.": "Old default values that you never chose yourself were changed to the new defaults. To keep the old behavior, change them back in Settings.",
"직접 고름": "Chosen manually", "직접 고름": "Chosen manually",
"직접 운영하는 서버 (ComfyUI · Automatic1111, 예: Tailscale 주소) 나 인터넷 서비스 (OpenAI 이미지 API 형식). 넣지 않으면 기기 안에서만 그립니다. 쓸 때는 그림 · 프롬프트가 기기 밖으로 나간다고 늘 알립니다. API 키는 안전 저장소에 둡니다.": "A server you run (ComfyUI · Automatic1111, e.g. a Tailscale address) or an internet service (OpenAI image API format). Without one, drawing happens only on this device. When used, the app always shows that images and prompts leave the device. API keys are kept in secure storage.", "직접 운영하는 서버 (ComfyUI · Automatic1111, 예: Tailscale 주소) 나 인터넷 서비스 (OpenAI 이미지 API 형식). 넣지 않으면 기기 안에서만 그립니다. 쓸 때는 그림 · 프롬프트가 기기 밖으로 나간다고 늘 알립니다. API 키는 안전 저장소에 둡니다.": "A server you run (ComfyUI · Automatic1111, e.g. a Tailscale address) or an internet service (OpenAI image API format). Without one, drawing happens only on this device. When used, the app always shows that images and prompts leave the device. API keys are kept in secure storage.",
@ -1941,6 +1945,7 @@
"화면 비율 (가로 · 세로) · 회전 · 밝기 · 대비 · 채도 · 색온도": "Aspect ratio (landscape · portrait) · rotation · brightness · contrast · saturation · temperature", "화면 비율 (가로 · 세로) · 회전 · 밝기 · 대비 · 채도 · 색온도": "Aspect ratio (landscape · portrait) · rotation · brightness · contrast · saturation · temperature",
"화면 순서": "Screen order", "화면 순서": "Screen order",
"화면 언어": "Display language", "화면 언어": "Display language",
"화면 언어 따르기": "Same as the app language",
"화면 작게": "Smaller", "화면 작게": "Smaller",
"화면 크게": "Larger", "화면 크게": "Larger",
"화면 크기": "Size", "화면 크기": "Size",

View File

@ -163,6 +163,7 @@
"MKV 목록에 {0}개를 추가했습니다.": "MKV リストに {0} 件を追加しました。", "MKV 목록에 {0}개를 추가했습니다.": "MKV リストに {0} 件を追加しました。",
"MKV 목록에 추가": "MKV リストに追加", "MKV 목록에 추가": "MKV リストに追加",
"MKV 완성": "MKV 完成", "MKV 완성": "MKV 完成",
"MKV 의 기본 자막 트랙 · 플레이어가 먼저 켜는 자막": "MKV の既定の字幕トラック · プレーヤーが最初に表示する字幕",
"MKV 화면": "MKV 画面", "MKV 화면": "MKV 画面",
"MKV 화면 (기본)": "MKV 画面(既定)", "MKV 화면 (기본)": "MKV 画面(既定)",
"MKV 화면 (지금 여기)": "MKV 画面(現在地)", "MKV 화면 (지금 여기)": "MKV 画面(現在地)",
@ -502,6 +503,7 @@
"기본": "既定", "기본": "既定",
"기본 앱으로 열기": "既定のアプリで開く", "기본 앱으로 열기": "既定のアプリで開く",
"기본 연결 프로그램": "既定のアプリ", "기본 연결 프로그램": "既定のアプリ",
"기본 자막 언어": "既定の字幕言語",
"기본 크기 ({0}%)": "既定のサイズ ({0}%)", "기본 크기 ({0}%)": "既定のサイズ ({0}%)",
"기본 크기 ({0}%) 로 · 기본 크기는 환경 설정 > 화면 에서": "既定の大きさ({0}%)に戻す・既定の大きさは 環境設定 > 画面 で", "기본 크기 ({0}%) 로 · 기본 크기는 환경 설정 > 화면 에서": "既定の大きさ({0}%)に戻す・既定の大きさは 環境設定 > 画面 で",
"기본 프로그램": "既定のプログラム", "기본 프로그램": "既定のプログラム",
@ -1693,6 +1695,8 @@
"지원하지 않는 protobuf 형식 (wire type {0}, 위치 {1})": "対応していない protobuf 形式(wire type {0}、位置 {1})", "지원하지 않는 protobuf 형식 (wire type {0}, 위치 {1})": "対応していない protobuf 形式(wire type {0}、位置 {1})",
"지원하지 않는 문자셋: {0}": "対応していない文字コード: {0}", "지원하지 않는 문자셋: {0}": "対応していない文字コード: {0}",
"지정한 파일이 없습니다": "指定したファイルがありません", "지정한 파일이 없습니다": "指定したファイルがありません",
"직접 고르기: {0}": "選択: {0}",
"직접 고르기…": "選択…",
"직접 고른 적이 없는 예전 기본값을 새 기본값으로 바꿨습니다. 원래대로 쓰려면 환경 설정에서 되돌리세요.": "自分で選んだことのない以前の既定値を新しい既定値に変えました。元のまま使うには環境設定で戻してください。", "직접 고른 적이 없는 예전 기본값을 새 기본값으로 바꿨습니다. 원래대로 쓰려면 환경 설정에서 되돌리세요.": "自分で選んだことのない以前の既定値を新しい既定値に変えました。元のまま使うには環境設定で戻してください。",
"직접 고름": "手動で選択", "직접 고름": "手動で選択",
"직접 운영하는 서버 (ComfyUI · Automatic1111, 예: Tailscale 주소) 나 인터넷 서비스 (OpenAI 이미지 API 형식). 넣지 않으면 기기 안에서만 그립니다. 쓸 때는 그림 · 프롬프트가 기기 밖으로 나간다고 늘 알립니다. API 키는 안전 저장소에 둡니다.": "自分で運営するサーバー (ComfyUI・Automatic1111、例: Tailscale のアドレス) またはインターネットサービス (OpenAI 画像 API 形式)。追加しなければこの端末内だけで描きます。使うときは画像・プロンプトが端末の外に送られることを常に表示します。API キーは安全な保存領域に置きます。", "직접 운영하는 서버 (ComfyUI · Automatic1111, 예: Tailscale 주소) 나 인터넷 서비스 (OpenAI 이미지 API 형식). 넣지 않으면 기기 안에서만 그립니다. 쓸 때는 그림 · 프롬프트가 기기 밖으로 나간다고 늘 알립니다. API 키는 안전 저장소에 둡니다.": "自分で運営するサーバー (ComfyUI・Automatic1111、例: Tailscale のアドレス) またはインターネットサービス (OpenAI 画像 API 形式)。追加しなければこの端末内だけで描きます。使うときは画像・プロンプトが端末の外に送られることを常に表示します。API キーは安全な保存領域に置きます。",
@ -1941,6 +1945,7 @@
"화면 비율 (가로 · 세로) · 회전 · 밝기 · 대비 · 채도 · 색온도": "画面比率(横・縦)・回転・明るさ・コントラスト・彩度・色温度", "화면 비율 (가로 · 세로) · 회전 · 밝기 · 대비 · 채도 · 색온도": "画面比率(横・縦)・回転・明るさ・コントラスト・彩度・色温度",
"화면 순서": "画面の順番", "화면 순서": "画面の順番",
"화면 언어": "表示言語", "화면 언어": "表示言語",
"화면 언어 따르기": "画面の言語に合わせる",
"화면 작게": "画面を小さく", "화면 작게": "画面を小さく",
"화면 크게": "画面を大きく", "화면 크게": "画面を大きく",
"화면 크기": "画面サイズ", "화면 크기": "画面サイズ",

View File

@ -163,6 +163,7 @@
"MKV 목록에 {0}개를 추가했습니다.": "已将 {0} 个添加到 MKV 列表。", "MKV 목록에 {0}개를 추가했습니다.": "已将 {0} 个添加到 MKV 列表。",
"MKV 목록에 추가": "添加到 MKV 列表", "MKV 목록에 추가": "添加到 MKV 列表",
"MKV 완성": "MKV 完成", "MKV 완성": "MKV 完成",
"MKV 의 기본 자막 트랙 · 플레이어가 먼저 켜는 자막": "MKV 的默认字幕轨 · 播放器首先开启的字幕",
"MKV 화면": "MKV 界面", "MKV 화면": "MKV 界面",
"MKV 화면 (기본)": "MKV 界面(默认)", "MKV 화면 (기본)": "MKV 界面(默认)",
"MKV 화면 (지금 여기)": "MKV 界面(当前位置)", "MKV 화면 (지금 여기)": "MKV 界面(当前位置)",
@ -502,6 +503,7 @@
"기본": "默认", "기본": "默认",
"기본 앱으로 열기": "用默认应用打开", "기본 앱으로 열기": "用默认应用打开",
"기본 연결 프로그램": "默认打开方式", "기본 연결 프로그램": "默认打开方式",
"기본 자막 언어": "默认字幕语言",
"기본 크기 ({0}%)": "默认大小 ({0}%)", "기본 크기 ({0}%)": "默认大小 ({0}%)",
"기본 크기 ({0}%) 로 · 기본 크기는 환경 설정 > 화면 에서": "恢复默认大小({0}%)· 默认大小在 设置 > 显示 中设置", "기본 크기 ({0}%) 로 · 기본 크기는 환경 설정 > 화면 에서": "恢复默认大小({0}%)· 默认大小在 设置 > 显示 中设置",
"기본 프로그램": "默认程序", "기본 프로그램": "默认程序",
@ -1693,6 +1695,8 @@
"지원하지 않는 protobuf 형식 (wire type {0}, 위치 {1})": "不支持的 protobuf 格式(wire type {0},位置 {1})", "지원하지 않는 protobuf 형식 (wire type {0}, 위치 {1})": "不支持的 protobuf 格式(wire type {0},位置 {1})",
"지원하지 않는 문자셋: {0}": "不支持的字符集: {0}", "지원하지 않는 문자셋: {0}": "不支持的字符集: {0}",
"지정한 파일이 없습니다": "指定的文件不存在", "지정한 파일이 없습니다": "指定的文件不存在",
"직접 고르기: {0}": "自选: {0}",
"직접 고르기…": "自选…",
"직접 고른 적이 없는 예전 기본값을 새 기본값으로 바꿨습니다. 원래대로 쓰려면 환경 설정에서 되돌리세요.": "您从未自行选择过的旧默认值已改为新的默认值。如需保持原样,请在设置中改回。", "직접 고른 적이 없는 예전 기본값을 새 기본값으로 바꿨습니다. 원래대로 쓰려면 환경 설정에서 되돌리세요.": "您从未自行选择过的旧默认值已改为新的默认值。如需保持原样,请在设置中改回。",
"직접 고름": "手动选择", "직접 고름": "手动选择",
"직접 운영하는 서버 (ComfyUI · Automatic1111, 예: Tailscale 주소) 나 인터넷 서비스 (OpenAI 이미지 API 형식). 넣지 않으면 기기 안에서만 그립니다. 쓸 때는 그림 · 프롬프트가 기기 밖으로 나간다고 늘 알립니다. API 키는 안전 저장소에 둡니다.": "自己运营的服务器 (ComfyUI · Automatic1111,例如 Tailscale 地址) 或互联网服务 (OpenAI 图像 API 格式)。不添加则只在本设备上绘制。使用时始终提示图片和提示词会离开设备。API 密钥保存在安全存储中。", "직접 운영하는 서버 (ComfyUI · Automatic1111, 예: Tailscale 주소) 나 인터넷 서비스 (OpenAI 이미지 API 형식). 넣지 않으면 기기 안에서만 그립니다. 쓸 때는 그림 · 프롬프트가 기기 밖으로 나간다고 늘 알립니다. API 키는 안전 저장소에 둡니다.": "自己运营的服务器 (ComfyUI · Automatic1111,例如 Tailscale 地址) 或互联网服务 (OpenAI 图像 API 格式)。不添加则只在本设备上绘制。使用时始终提示图片和提示词会离开设备。API 密钥保存在安全存储中。",
@ -1941,6 +1945,7 @@
"화면 비율 (가로 · 세로) · 회전 · 밝기 · 대비 · 채도 · 색온도": "画面比例(横屏 · 竖屏)· 旋转 · 亮度 · 对比度 · 饱和度 · 色温", "화면 비율 (가로 · 세로) · 회전 · 밝기 · 대비 · 채도 · 색온도": "画面比例(横屏 · 竖屏)· 旋转 · 亮度 · 对比度 · 饱和度 · 色温",
"화면 순서": "界面顺序", "화면 순서": "界面顺序",
"화면 언어": "界面语言", "화면 언어": "界面语言",
"화면 언어 따르기": "跟随界面语言",
"화면 작게": "缩小界面", "화면 작게": "缩小界面",
"화면 크게": "放大界面", "화면 크게": "放大界面",
"화면 크기": "画面大小", "화면 크기": "画面大小",

View File

@ -142,6 +142,8 @@ class AppController extends ChangeNotifier {
void _applySettings() { void _applySettings() {
playerSubtitleScale.value = settings.subtitleScale; playerSubtitleScale.value = settings.subtitleScale;
// 61: 기본 자막 언어 (MKV 기본 자막 트랙 · 플레이어가 먼저 켜는 자막)
preferredSubtitleSetting = settings.preferredSubtitleLanguage;
// WebDAV 서버 (파일 탐색기 · Rsync 화면 · 복사 · 동기화가 dav:// 경로로 쓴다) // WebDAV 서버 (파일 탐색기 · Rsync 화면 · 복사 · 동기화가 dav:// 경로로 쓴다)
DavRegistry.configure(settings.webdavServers); DavRegistry.configure(settings.webdavServers);
outputRootOverride = outputRootOverride =

View File

@ -435,6 +435,9 @@ class AppSettings {
/// 47: MKV 목록에서 세 번 누르면 'move' (확인 뒤 이동 폴더로) · 'none' (아무것도 안 함) /// 47: MKV 목록에서 세 번 누르면 'move' (확인 뒤 이동 폴더로) · 'none' (아무것도 안 함)
String tripleTapAction = 'move'; String tripleTapAction = 'move';
/// 61: 기본 자막 언어 (MKV 의 기본 자막 트랙 · 플레이어가 먼저 켜는 자막). '' = 화면 언어 따르기, 그 밖은 자막 언어 코드 (ko · ja …)
String preferredSubtitleLanguage = '';
/// 55: 로그인이 필요한 WebDAV 동영상을 다른 앱으로 열 때 'ask' (매번 묻기) · 'fetch' (늘 받아서) · 'url' (늘 주소로) /// 55: 로그인이 필요한 WebDAV 동영상을 다른 앱으로 열 때 'ask' (매번 묻기) · 'fetch' (늘 받아서) · 'url' (늘 주소로)
String davExternalOpen = 'ask'; String davExternalOpen = 'ask';
@ -659,6 +662,7 @@ class AppSettings {
'rememberPasswords': rememberPasswords, 'rememberPasswords': rememberPasswords,
'recycleOnDelete': recycleOnDelete, 'recycleOnDelete': recycleOnDelete,
'tripleTapAction': tripleTapAction, 'tripleTapAction': tripleTapAction,
'preferredSubtitleLanguage': preferredSubtitleLanguage,
'davExternalOpen': davExternalOpen, 'davExternalOpen': davExternalOpen,
// 116: 예전 판의 표시 ('allFilesHintShown') 는 권한이 없어진 것을 몰라 다시 띄우지 못했다 → 새 이름으로 한 번 더 // 116: 예전 판의 표시 ('allFilesHintShown') 는 권한이 없어진 것을 몰라 다시 띄우지 못했다 → 새 이름으로 한 번 더
'allFilesHint2': allFilesHintShown, 'allFilesHint2': allFilesHintShown,
@ -853,6 +857,7 @@ class AppSettings {
..rememberPasswords = j['rememberPasswords'] != false ..rememberPasswords = j['rememberPasswords'] != false
..recycleOnDelete = j['recycleOnDelete'] != false ..recycleOnDelete = j['recycleOnDelete'] != false
..tripleTapAction = j['tripleTapAction'] == 'none' ? 'none' : 'move' ..tripleTapAction = j['tripleTapAction'] == 'none' ? 'none' : 'move'
..preferredSubtitleLanguage = j['preferredSubtitleLanguage'] is String ? j['preferredSubtitleLanguage'] as String : ''
..davExternalOpen = const ['fetch', 'url'].contains(j['davExternalOpen']) ? j['davExternalOpen'] as String : 'ask' ..davExternalOpen = const ['fetch', 'url'].contains(j['davExternalOpen']) ? j['davExternalOpen'] as String : 'ask'
..allFilesHintShown = j['allFilesHint2'] == true ..allFilesHintShown = j['allFilesHint2'] == true
..subtitleScale = ((j['subtitleScale'] as num?)?.toDouble() ?? 1.0).clamp(0.5, 2.5) ..subtitleScale = ((j['subtitleScale'] as num?)?.toDouble() ?? 1.0).clamp(0.5, 2.5)

View File

@ -81,11 +81,18 @@ Language languageOf(String? token) {
return undetermined; return undetermined;
} }
/// 61: 플레이어가 먼저 켤 자막을 알아보는 글 (화면 언어의 자막: 파일 이름의 코드 · 트랙 이름). 예전에는 늘 한국어. /// 61: 환경 설정의 기본 자막 언어 ('' = 화면 언어 따르기). 앱이 설정을 읽을 때 넣는다 (AppController).
/// 화면 언어가 자막 언어 목록에 없으면 한국어. String preferredSubtitleSetting = '';
/// 기본 자막 언어: 설정에서 고른 언어, 없으면 화면 언어. 자막 언어 목록에 없으면 한국어.
Language preferredSubtitleLanguage([String? ui]) {
final l = languageOf(ui ?? (preferredSubtitleSetting.isNotEmpty ? preferredSubtitleSetting : uiLanguage));
return l == undetermined ? languageOf('ko') : l;
}
/// 61: 플레이어가 먼저 켤 자막을 알아보는 글 (기본 자막 언어의 자막: 파일 이름의 코드 · 트랙 이름). 예전에는 늘 한국어.
RegExp preferredSubtitlePattern([String? ui]) { RegExp preferredSubtitlePattern([String? ui]) {
var l = languageOf(ui ?? uiLanguage); final l = preferredSubtitleLanguage(ui);
if (l == undetermined) l = languageOf('ko');
final codes = {l.code.toLowerCase(), l.mkv, ...l.aliases.where((a) => RegExp(r'^[a-z-]+$').hasMatch(a))}.map(RegExp.escape).join('|'); final codes = {l.code.toLowerCase(), l.mkv, ...l.aliases.where((a) => RegExp(r'^[a-z-]+$').hasMatch(a))}.map(RegExp.escape).join('|');
final names = {l.koName, l.name, ...l.aliases.where((a) => !RegExp(r'^[a-z-]+$').hasMatch(a))}.map(RegExp.escape).join('|'); final names = {l.koName, l.name, ...l.aliases.where((a) => !RegExp(r'^[a-z-]+$').hasMatch(a))}.map(RegExp.escape).join('|');
return RegExp('[._-]($codes)[._]|\\(($codes)\\)|\\b(${l.mkv})\\b|$names', caseSensitive: false); return RegExp('[._-]($codes)[._]|\\(($codes)\\)|\\b(${l.mkv})\\b|$names', caseSensitive: false);

View File

@ -1,7 +1,6 @@
import 'encode_options.dart'; import 'encode_options.dart';
import 'models.dart'; import 'models.dart';
import 'languages.dart'; import 'languages.dart';
import '../l10n/tr.dart';
/// MKV 에 그대로 복사 가능한 자막 코덱 /// MKV 에 그대로 복사 가능한 자막 코덱
const _copyableSubtitleCodecs = { const _copyableSubtitleCodecs = {
@ -15,12 +14,8 @@ String _externalCodec(String ext) => switch (ext) {
_ => 'srt', // srt, smi, sami, vtt _ => 'srt', // srt, smi, sami, vtt
}; };
/// 기본 자막으로 표시할 언어 (MKV 태그). 61: 화면 언어를 따른다 (예전에는 늘 한국어 'kor'). /// 기본 자막으로 표시할 언어 (MKV 태그). 61: 환경 설정의 기본 자막 언어 (처음 값은 화면 언어 따르기, 예전에는 늘 'kor')
/// 화면 언어가 자막 언어 목록에 없으면 한국어. String get preferredDefaultLanguage => preferredSubtitleLanguage().mkv;
String get preferredDefaultLanguage {
final l = languageOf(uiLanguage);
return l == undetermined ? 'kor' : l.mkv;
}
/// 동영상 + 자막 → MKV 로 합치는 FFmpeg 인수. /// 동영상 + 자막 → MKV 로 합치는 FFmpeg 인수.
/// ///

View File

@ -37,6 +37,7 @@ import 'ai_settings.dart';
import '../app/master_lock.dart'; import '../app/master_lock.dart';
import 'toast_status.dart'; import 'toast_status.dart';
import 'license_texts.dart'; import 'license_texts.dart';
import 'subtitle_language_picker.dart';
/// 동시 작업 수 고르기: 1 · 5 · 10 · 무한(0) · 직접 입력 /// 동시 작업 수 고르기: 1 · 5 · 10 · 무한(0) · 직접 입력
class CountSelector extends StatelessWidget { class CountSelector extends StatelessWidget {
@ -427,6 +428,15 @@ class _SettingsPageState extends State<SettingsPage> {
), ),
]), ]),
_group('mkv', Icons.movie_creation_outlined, tr('MKV 만들기'), [ _group('mkv', Icons.movie_creation_outlined, tr('MKV 만들기'), [
// 61: 화면 언어와 다른 언어의 자막을 기본으로 원할 수 있다 (예: 화면은 일본어, 자막은 한국어)
SettingTile(
title: Text(tr('기본 자막 언어')),
subtitle: Text(tr('MKV 의 기본 자막 트랙 · 플레이어가 먼저 켜는 자막')),
trailing: SubtitleLanguagePicker(
value: s.preferredSubtitleLanguage,
onChanged: (v) => c.updateSettings((x) => x.preferredSubtitleLanguage = v),
),
),
SettingTile( SettingTile(
title: Text(tr('동영상을 세 번 누르면')), title: Text(tr('동영상을 세 번 누르면')),
subtitle: Text(tr('한 번: 보기 · 두 번: 재생 · 세 번: 이동 폴더로 옮기기 (옮기기 전에 묻습니다)')), subtitle: Text(tr('한 번: 보기 · 두 번: 재생 · 세 번: 이동 폴더로 옮기기 (옮기기 전에 묻습니다)')),

View File

@ -0,0 +1,60 @@
import 'package:flutter/material.dart';
import '../core/languages.dart';
import '../l10n/tr.dart';
/// 61: 기본 자막 언어 고르기 - 화면 언어 따르기 (처음 값) · 한국어 · English · 日本語 · 中文 · 직접 고르기 (자막 언어 목록)
class SubtitleLanguagePicker extends StatelessWidget {
final String value;
final ValueChanged<String> onChanged;
const SubtitleLanguagePicker({super.key, required this.value, required this.onChanged});
/// 바로 고를 수 있는 것 (언어 이름은 그 언어로 - 화면 언어와 상관없이 알아보게)
static const quick = [('ko', '한국어'), ('en', 'English'), ('ja', '日本語'), ('zh-Hans', '中文')];
static const _other = '\u0000other';
@override
Widget build(BuildContext context) {
final inQuick = value.isEmpty || quick.any((q) => q.$1 == value);
return DropdownButton<String>(
value: inQuick ? value : _other,
items: [
DropdownMenuItem(value: '', child: Text(tr('화면 언어 따르기'))),
for (final (code, name) in quick) DropdownMenuItem(value: code, child: Text(name)), // l10n-skip: 언어 이름은 그 언어로
DropdownMenuItem(
value: _other,
child: Text(inQuick ? tr('직접 고르기…') : trf('직접 고르기: {0}', [languageOf(value).name])),
),
],
onChanged: (v) async {
if (v == null) return;
if (v != _other) return onChanged(v);
final picked = await showDialog<String>(
context: context,
builder: (ctx) => SimpleDialog(
title: Text(tr('기본 자막 언어')),
children: [
SizedBox(
width: 360,
height: 420,
child: ListView(children: [
for (final l in languages)
ListTile(
dense: true,
selected: l.code == value,
title: Text(l.name),
subtitle: Text(l.code),
onTap: () => Navigator.pop(ctx, l.code),
),
]),
),
],
),
);
if (picked != null) onChanged(picked);
},
);
}
}

View File

@ -1,5 +1,6 @@
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:jj_mkvmaker/app/i18n_controller.dart'; import 'package:jj_mkvmaker/app/i18n_controller.dart';
import 'package:jj_mkvmaker/app/settings.dart';
import 'package:jj_mkvmaker/core/bookmarks.dart'; import 'package:jj_mkvmaker/core/bookmarks.dart';
import 'package:jj_mkvmaker/core/languages.dart'; import 'package:jj_mkvmaker/core/languages.dart';
import 'package:jj_mkvmaker/core/mkv_command_builder.dart'; import 'package:jj_mkvmaker/core/mkv_command_builder.dart';
@ -48,6 +49,22 @@ void main() {
expect(preferredSubtitlePattern('xx').hasMatch('a.ko.srt'), isTrue, reason: '모르는 언어면 한국어'); expect(preferredSubtitlePattern('xx').hasMatch('a.ko.srt'), isTrue, reason: '모르는 언어면 한국어');
}); });
test('기본 자막 언어 설정: 화면 언어 따르기 (처음 값) · 고른 언어가 화면 언어보다 먼저 · 저장', () async {
addTearDown(() => preferredSubtitleSetting = '');
expect(AppSettings().preferredSubtitleLanguage, '', reason: '처음 값은 화면 언어 따르기');
await i18n.apply('ja', save: false);
preferredSubtitleSetting = 'ko'; // 화면은 일본어, 자막은 한국어
expect(preferredDefaultLanguage, 'kor');
expect(preferredSubtitlePattern().hasMatch('a.ko.srt'), isTrue);
expect(preferredSubtitlePattern().hasMatch('a.ja.srt'), isFalse);
preferredSubtitleSetting = 'vi';
expect(preferredDefaultLanguage, 'vie');
preferredSubtitleSetting = '';
expect(preferredDefaultLanguage, 'jpn');
final s = AppSettings()..preferredSubtitleLanguage = 'zh-Hans';
expect(AppSettings.fromJson(s.toJson()).preferredSubtitleLanguage, 'zh-Hans');
});
test('Android 저장소 이름: 내장 · SD 카드는 앱 화면 언어로, USB 등은 기기 이름 그대로', () async { test('Android 저장소 이름: 내장 · SD 카드는 앱 화면 언어로, USB 등은 기기 이름 그대로', () async {
await i18n.apply('en', save: false); await i18n.apply('en', save: false);
expect(AndroidAccess.volumeLabel('디바이스 저장공간', primary: true, removable: false), 'Internal storage'); expect(AndroidAccess.volumeLabel('디바이스 저장공간', primary: true, removable: false), 'Internal storage');