diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index b428dbe..af27a09 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -17,6 +17,10 @@ android:name="${applicationName}" android:icon="@mipmap/ic_launcher" android:requestLegacyExternalStorage="true"> + + files = const []; + try { + files = await services.storage.listFiles(out); + } catch (_) {} + for (final d in findSiblingSubtitles(v.path, files)) { + if (d.isAi || d.language == undetermined) continue; + if (v.subtitles.any((s) => s.path != null && p.equals(s.path!, d.path))) continue; + await _addExternal(v, d.path, d.language); + made++; + } + } final embeddedCount = v.subtitles.where((s) => s.kind == SubtitleKind.embedded).length; - _log(trf('추가: {0} (내장 자막 {1}개, 같은 폴더 자막 {2}개)', [v.fileName, embeddedCount, siblings.length])); + _log(trf('추가: {0} (내장 자막 {1}개, 같은 폴더 자막 {2}개)', [v.fileName, embeddedCount, siblings.length]) + + (made > 0 ? trf(' · 전에 만든 자막 {0}개 (jj_mkv)', [made]) : '')); notifyListeners(); } diff --git a/lib/ui/ai_dialog.dart b/lib/ui/ai_dialog.dart index 70571b3..f0c2df6 100644 --- a/lib/ui/ai_dialog.dart +++ b/lib/ui/ai_dialog.dart @@ -79,7 +79,7 @@ class _AiDialogState extends State<_AiDialog> { crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ - Text(trf('영상의 음성을 인식하고 선택한 언어로 번역합니다. 모든 처리는 이 {0} 에서 이루어집니다.', [Platform.isAndroid ? tr('기기') : 'PC']), + Text(trf('영상의 음성을 인식하고 선택한 언어로 번역합니다. 모든 처리는 이 {0}에서 이루어집니다.', [Platform.isAndroid ? tr('기기') : 'PC']), style: const TextStyle(fontSize: 12, color: JjColors.textDim)), const SizedBox(height: 16), _label(tr('원어 (영상 속 언어)')), diff --git a/lib/ui/home_page.dart b/lib/ui/home_page.dart index 961cdd1..7b3ee3d 100644 --- a/lib/ui/home_page.dart +++ b/lib/ui/home_page.dart @@ -454,13 +454,17 @@ class _VideoList extends StatelessWidget { Expanded( child: InkWell( onTap: c.videos.isEmpty ? null : c.toggleAllChecked, - child: Text( - c.checked.isEmpty - ? trf('동영상 {0}개 · 전체 선택', [c.videos.length]) - : trf('선택 {0} / {1}개', [c.checked.length, c.videos.length]), - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: const TextStyle(color: JjColors.textDim, fontSize: 12)), + // 좁으면 글자를 줄여서 한 줄에 다 보이게 + child: FittedBox( + fit: BoxFit.scaleDown, + alignment: Alignment.centerLeft, + child: Text( + c.checked.isEmpty + ? trf('동영상 {0}개 · 전체 선택', [c.videos.length]) + : trf('선택 {0} / {1}개', [c.checked.length, c.videos.length]), + maxLines: 1, + style: const TextStyle(color: JjColors.textDim, fontSize: 12)), + ), ), ), // 정렬: 같은 것을 다시 누르면 반대 순서 @@ -907,7 +911,7 @@ class _SubtitleRow extends StatelessWidget { ), if (c.services.createTranslator != null) IconButton( - tooltip: c.canEdit(s) ? trf('다른 언어로 번역 (AI · 이 {0} 에서)', [Platform.isAndroid ? tr('기기') : 'PC']) : tr('이미지 자막은 번역할 수 없습니다'), + tooltip: c.canEdit(s) ? trf('다른 언어로 번역 (AI · 이 {0}에서)', [Platform.isAndroid ? tr('기기') : 'PC']) : tr('이미지 자막은 번역할 수 없습니다'), iconSize: 18, onPressed: locked || off || !c.canEdit(s) ? null : () => showTranslateDialog(context, c, v, s), icon: const Icon(Icons.translate, color: JjColors.accent), diff --git a/lib/ui/settings_page.dart b/lib/ui/settings_page.dart index c49adfd..a96b3e5 100644 --- a/lib/ui/settings_page.dart +++ b/lib/ui/settings_page.dart @@ -397,7 +397,7 @@ class _SettingsPageState extends State { (v) => c.updateSettings((x) => x.openSubtitlesKey = v.trim())), _textTile(tr('아이디 (선택)'), s.openSubtitlesUser, tr('로그인하면 하루 받기 횟수가 늘어납니다'), (v) => c.updateSettings((x) => x.openSubtitlesUser = v.trim())), - _textTile(tr('비밀번호 (선택)'), s.openSubtitlesPassword, trf('이 {0} 의 설정 파일에 저장됩니다', [desk ? 'PC' : tr('기기')]), + _textTile(tr('비밀번호 (선택)'), s.openSubtitlesPassword, trf('이 {0}의 설정 파일에 저장됩니다', [desk ? 'PC' : tr('기기')]), (v) => c.updateSettings((x) => x.openSubtitlesPassword = v), obscure: true), ]), _group('play', Icons.play_circle_outline, tr('재생'), [ diff --git a/lib/ui/translate_dialog.dart b/lib/ui/translate_dialog.dart index b34a070..d4d4bd4 100644 --- a/lib/ui/translate_dialog.dart +++ b/lib/ui/translate_dialog.dart @@ -87,7 +87,7 @@ class _TranslateDialogState extends State<_TranslateDialog> { ]), const SizedBox(height: 16), Row(children: [ - Text(trf('번역 모델: NLLB-200 (이 {0} 에서만) ', [Platform.isAndroid ? tr('기기') : 'PC']), style: const TextStyle(fontSize: 13)), + Text(trf('번역 모델: NLLB-200 (이 {0}에서만) ', [Platform.isAndroid ? tr('기기') : 'PC']), style: const TextStyle(fontSize: 13)), if (_installed != null) Text(_installed! ? tr('설치됨') : trf('처음 사용 시 내려받기 {0}', [nllbModel.sizeLabel]), style: TextStyle(fontSize: 11, color: _installed! ? JjColors.success : JjColors.textDim)), diff --git a/test/move_test.dart b/test/move_test.dart index 4d8da6d..4737233 100644 --- a/test/move_test.dart +++ b/test/move_test.dart @@ -62,6 +62,20 @@ void main() { expect(c.selected, busy); }); + test('목록에 다시 넣으면 (앱을 다시 켜도) 전에 만든 자막 (jj_mkv 의 파일명_ko.srt 등) 이 다시 붙는다', () async { + final c = _controller(); + final video = make('강의.mp4').path; + make(p.join('jj_mkv', '강의_AI.srt')); + make(p.join('jj_mkv', '강의_ko.srt')); + make(p.join('jj_mkv', '강의_en.srt')); + make(p.join('jj_mkv', '다른영상_ko.srt')); + make('강의.ja.srt'); // 같은 폴더 자막 + await c.addVideos([video]); + final v = c.videos.single; + expect([for (final s in v.subtitles) p.basename(s.path!)]..sort(), ['강의.ja.srt', '강의_en.srt', '강의_ko.srt']); + expect(v.subtitles.firstWhere((s) => s.path!.endsWith('_ko.srt')).language.code, 'ko'); + }); + test('이동 폴더를 안 정했으면 옮기지 않는다', () async { final c = _controller(); final a = VideoItem(make('a.mp4').path);