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);