From a4afdda5c698ebd93c17a7b4964f57aa5ebbe579 Mon Sep 17 00:00:00 2001 From: RyuJongJin <266630912+RyuJongJin@users.noreply.github.com> Date: Sat, 10 Oct 2026 00:45:52 +0900 Subject: [PATCH] =?UTF-8?q?Android=20=EC=95=8C=EB=A6=BC=20=EA=B8=80=20?= =?UTF-8?q?=C2=B7=20=EC=B1=84=EB=84=90=20=EC=9D=B4=EB=A6=84=EC=9D=84=20?= =?UTF-8?q?=ED=99=94=EB=A9=B4=20=EC=96=B8=EC=96=B4=EB=A1=9C=20(57)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 앱이 고른 언어라 Android 의 values-xx 로는 맞출 수 없어, Dart (AndroidKeepAlive) 가 시작할 때와 화면 언어가 바뀔 때 번역된 글 8개 (진행 알림 제목, "작업 멈춤" 알림, 채널 2개의 이름 · 설명) 를 보낸다. - Kotlin 은 받은 글을 SharedPreferences 에 저장해 앱이 꺼진 뒤 서비스만 떠도 마지막 언어로 쓰고, 이미 만든 채널은 같은 ID 로 다시 만들어 이름 · 설명만 바꾼다. 받은 적이 없으면 한국어. - notification_texts_test: Kotlin 기본값과 Dart 의 한국어 글이 같은지, ja 로 바꾸면 모두 번역되는지. Co-Authored-By: Claude Opus 5.5 --- .../com/jj/jj_mkvmaker/KeepAliveService.kt | 76 ++++++++++++++----- .../kotlin/com/jj/jj_mkvmaker/MainActivity.kt | 7 ++ assets/l10n/en.json | 8 ++ assets/l10n/ja.json | 8 ++ assets/l10n/zh-Hans.json | 8 ++ lib/platform/android/android_keep_alive.dart | 23 ++++++ test/notification_texts_test.dart | 32 ++++++++ 7 files changed, 145 insertions(+), 17 deletions(-) create mode 100644 test/notification_texts_test.dart diff --git a/android/app/src/main/kotlin/com/jj/jj_mkvmaker/KeepAliveService.kt b/android/app/src/main/kotlin/com/jj/jj_mkvmaker/KeepAliveService.kt index 97ab858..eea862b 100644 --- a/android/app/src/main/kotlin/com/jj/jj_mkvmaker/KeepAliveService.kt +++ b/android/app/src/main/kotlin/com/jj/jj_mkvmaker/KeepAliveService.kt @@ -85,17 +85,65 @@ class KeepAliveService : Service() { const val DONE_CHANNEL_ID = "done" + /// 57: 알림 글 · 채널 이름을 앱 화면 언어로 (앱이 고른 언어라 Android 의 values-xx 로는 맞출 수 없다). + /// 앱 (Dart) 이 언어를 바꿀 때마다 보내 오고, 다음에 켤 때도 쓰게 저장해 둔다. 없으면 한국어. + private const val PREFS = "jj_notify_texts" + private val defaults = mapOf( + "working" to "JJ_MKVMaker 작업 중", + "stoppedTitle" to "JJ_MKVMaker 작업이 멈췄습니다", + "stoppedText" to "앱을 닫아 진행 중이던 작업이 멈췄습니다. 닫아도 계속하려면 환경 설정에서 '백그라운드로 실행' 을 켜세요.", + "stoppedLong" to "앱을 닫아 진행 중이던 작업 (MKV · 다운로드 · 동기화) 이 멈췄습니다. 앱을 닫아도 계속하려면 환경 설정 > Rsync > '백그라운드로 실행' 을 켜세요.", + "jobsChannel" to "작업 진행", + "jobsChannelDesc" to "다운로드 · MKV 만들기 · AI 자막 · 동기화 진행 상황", + "doneChannel" to "끝난 작업", + "doneChannelDesc" to "AI 해상도 올리기 등 오래 걸린 작업이 끝났을 때", + ) + + private fun t(context: Context, key: String): String = + context.getSharedPreferences(PREFS, Context.MODE_PRIVATE).getString(key, null)?.takeIf { it.isNotEmpty() } + ?: defaults.getValue(key) + + /// 앱이 보낸 글을 저장하고, 이미 만든 채널은 이름 · 설명을 바꾼다 (같은 ID 로 다시 만들면 이름만 바뀐다) + fun setTexts(context: Context, texts: Map) { + val e = context.getSharedPreferences(PREFS, Context.MODE_PRIVATE).edit() + for ((k, v) in texts) if (k in defaults) e.putString(k, v) + e.apply() + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + if (nm.getNotificationChannel(CHANNEL_ID) != null) createJobsChannel(context, nm, texts) + if (nm.getNotificationChannel(DONE_CHANNEL_ID) != null) createDoneChannel(context, nm, texts) + } + } + + private fun pick(context: Context, texts: Map?, key: String) = + texts?.get(key)?.takeIf { it.isNotEmpty() } ?: t(context, key) + + private fun createJobsChannel(context: Context, nm: NotificationManager, texts: Map? = null) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return + nm.createNotificationChannel( + NotificationChannel(CHANNEL_ID, pick(context, texts, "jobsChannel"), NotificationManager.IMPORTANCE_LOW).apply { + description = pick(context, texts, "jobsChannelDesc") + setShowBadge(false) + } + ) + } + + private fun createDoneChannel(context: Context, nm: NotificationManager, texts: Map? = null) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return + nm.createNotificationChannel( + NotificationChannel(DONE_CHANNEL_ID, pick(context, texts, "doneChannel"), NotificationManager.IMPORTANCE_DEFAULT).apply { + description = pick(context, texts, "doneChannelDesc") + setSound(null, null) + } + ) + } + /// 157: 끝난 작업 알림 (예: "4장 저장: 폴더"). 누르면 작업 현황으로. /// 진행 알림 채널 (조용히, 낮은 중요도) 과 따로 두어 알림 창의 "조용한 알림" 칸에 묻히지 않게 (보통 중요도, 소리 없음) fun notifyDone(context: Context, title: String, text: String) { val nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && nm.getNotificationChannel(DONE_CHANNEL_ID) == null) { - nm.createNotificationChannel( - NotificationChannel(DONE_CHANNEL_ID, "끝난 작업", NotificationManager.IMPORTANCE_DEFAULT).apply { - description = "AI 해상도 올리기 등 오래 걸린 작업이 끝났을 때" - setSound(null, null) - } - ) + createDoneChannel(context, nm) } val open = PendingIntent.getActivity( context, 3, @@ -134,10 +182,9 @@ class KeepAliveService : Service() { @Suppress("DEPRECATION") Notification.Builder(context) } b.setSmallIcon(android.R.drawable.stat_notify_error) - .setContentTitle("JJ_MKVMaker 작업이 멈췄습니다") - .setContentText("앱을 닫아 진행 중이던 작업이 멈췄습니다. 닫아도 계속하려면 환경 설정에서 '백그라운드로 실행' 을 켜세요.") - .setStyle(Notification.BigTextStyle().bigText( - "앱을 닫아 진행 중이던 작업 (MKV · 다운로드 · 동기화) 이 멈췄습니다. 앱을 닫아도 계속하려면 환경 설정 > Rsync > '백그라운드로 실행' 을 켜세요.")) + .setContentTitle(t(context, "stoppedTitle")) + .setContentText(t(context, "stoppedText")) + .setStyle(Notification.BigTextStyle().bigText(t(context, "stoppedLong"))) .setContentIntent(open) .setAutoCancel(true) nm.notify(STOPPED_ID, b.build()) @@ -158,12 +205,7 @@ class KeepAliveService : Service() { private fun notification(context: Context, text: String, progress: Int, icon: String): Notification { val nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && nm.getNotificationChannel(CHANNEL_ID) == null) { - nm.createNotificationChannel( - NotificationChannel(CHANNEL_ID, "작업 진행", NotificationManager.IMPORTANCE_LOW).apply { - description = "다운로드 · MKV 만들기 · AI 자막 · 동기화 진행 상황" - setShowBadge(false) - } - ) + createJobsChannel(context, nm) } val open = PendingIntent.getActivity( context, 0, @@ -180,7 +222,7 @@ class KeepAliveService : Service() { } // 다운로드 중이면 내려받기 아이콘, 그 밖 (실시간 동기화 · MKV · AI 자막) 은 좌우로 오가는 화살표 b.setSmallIcon(if (icon == "download") android.R.drawable.stat_sys_download else R.drawable.ic_stat_sync_anim) - .setContentTitle("JJ_MKVMaker 작업 중") + .setContentTitle(t(context, "working")) .setContentText(text) .setContentIntent(open) .setOngoing(true) diff --git a/android/app/src/main/kotlin/com/jj/jj_mkvmaker/MainActivity.kt b/android/app/src/main/kotlin/com/jj/jj_mkvmaker/MainActivity.kt index 35fcc44..070e073 100644 --- a/android/app/src/main/kotlin/com/jj/jj_mkvmaker/MainActivity.kt +++ b/android/app/src/main/kotlin/com/jj/jj_mkvmaker/MainActivity.kt @@ -170,6 +170,13 @@ class MainActivity : FlutterActivity() { call.argument("icon") ?: "") result.success(null) } + "notificationTexts" -> { + // 57: 알림 글 · 채널 이름 (앱 화면 언어) + val m = call.arguments as? Map<*, *> ?: emptyMap() + KeepAliveService.setTexts(applicationContext, + m.entries.mapNotNull { (k, v) -> if (k is String && v is String) k to v else null }.toMap()) + result.success(null) + } "notifyDone" -> { KeepAliveService.notifyDone(applicationContext, call.argument("title") ?: "", call.argument("text") ?: "") diff --git a/assets/l10n/en.json b/assets/l10n/en.json index 4f102da..17b8ac8 100644 --- a/assets/l10n/en.json +++ b/assets/l10n/en.json @@ -97,6 +97,7 @@ "AI 해상도 올리기": "AI upscaling", "AI 해상도 올리기 (모든 장)": "AI upscaling (all pages)", "AI 해상도 올리기 (이 장)": "AI upscaling (this page)", + "AI 해상도 올리기 등 오래 걸린 작업이 끝났을 때": "When a long task such as AI upscaling finishes", "API 키": "API key", "API 키 (선택)": "API key (optional)", "API 키 (필수, 무료)": "API key (required, free)", @@ -134,6 +135,8 @@ "JJ_MKVMaker - 다운로드 {0}개": "JJ_MKVMaker - {0} downloads", "JJ_MKVMaker 로 자막 만들기": "Make subtitles with JJ_MKVMaker", "JJ_MKVMaker 로 재생": "Play with JJ_MKVMaker", + "JJ_MKVMaker 작업 중": "JJ_MKVMaker is working", + "JJ_MKVMaker 작업이 멈췄습니다": "JJ_MKVMaker tasks stopped", "JJ_MKVMaker_즐겨찾기.html": "JJ_MKVMaker_bookmarks.html", "JPG 품질 {0}": "JPG quality {0}", "JavaScript 사용": "Enable JavaScript", @@ -500,6 +503,7 @@ "끝: {0}": "End: {0}", "끝나지 못했습니다: {0}": "Did not finish: {0}", "끝난 뒤 원본의 빈 폴더를 지웁니다 (find 원본/ -type d -empty -delete). 원본 폴더는:": "Afterwards, empty folders in the source are deleted (find source/ -type d -empty -delete). The source folder:", + "끝난 작업": "Finished tasks", "끝내기": "Exit", "끝내기 (작업 멈춤)": "Exit (stop tasks)", "끝에서 처음으로 돌기": "Wrap around at the end", @@ -596,6 +600,7 @@ "다운로드 {0}": "Downloads {0}", "다운로드 {0}개": "{0} downloads", "다운로드 {0}개{1}": "{0} downloads{1}", + "다운로드 · MKV 만들기 · AI 자막 · 동기화 진행 상황": "Progress of downloads · MKV creation · AI subtitles · sync", "다운로드 · 변환도 모두 종료 (묻지 않음)": "Also quit downloads · conversions (don't ask)", "다운로드 목록": "Download list", "다운로드 목록 (지금 여기)": "Download list (you are here)", @@ -1137,6 +1142,8 @@ "앱에 들어 있는 것 (기본)": "Built into the app (default)", "앱을 다시 설치하자 Android 자동 백업 (Google 백업) 이 이전 설정을 되살렸습니다.\n· 동영상 목록 {0}개\n· WebDAV 서버: {1}": "After reinstalling the app, Android Auto Backup (Google backup) restored your previous settings.\n· {0} videos in the list\n· WebDAV servers: {1}", "앱을 다시 켤 때 동기화": "Sync when the app restarts", + "앱을 닫아 진행 중이던 작업 (MKV · 다운로드 · 동기화) 이 멈췄습니다. 앱을 닫아도 계속하려면 환경 설정 > Rsync > '백그라운드로 실행' 을 켜세요.": "Closing the app stopped the running tasks (MKV · downloads · sync). To keep them going after closing the app, turn on Settings > Rsync > 'Run in background'.", + "앱을 닫아 진행 중이던 작업이 멈췄습니다. 닫아도 계속하려면 환경 설정에서 '백그라운드로 실행' 을 켜세요.": "Closing the app stopped the running tasks. To keep them going after closing, turn on 'Run in background' in Settings.", "앱을 새로 설치해 설정이 없습니다. 저장해 둔 v{0} 의 설정 (동영상 목록 · 즐겨찾기 포함) 을 되살릴까요?\n(자막 API 키 · 비밀번호는 저장하지 않았으니 다시 넣어 주세요)": "The app was newly installed and has no settings. Restore the saved settings of v{0} (including the video list and bookmarks)?\n(Subtitle API keys and passwords were not saved - please enter them again)", "앱을 지우면 다음은 되살릴 수 없습니다:": "Removing the app loses the following for good:", "앱이 켜져 있는 동안 늘 동기화합니다.": "Always syncs while the app is running.", @@ -1471,6 +1478,7 @@ "작업 임시 파일": "Job temporary files", "작업 중": "Working", "작업 중지: {0}{1}": "Job stopped: {0}{1}", + "작업 진행": "Task progress", "작업 취소": "Cancel job", "작업 현황": "Job status", "작업 현황 닫기": "Close job status", diff --git a/assets/l10n/ja.json b/assets/l10n/ja.json index 6db5a4a..e5f3d6d 100644 --- a/assets/l10n/ja.json +++ b/assets/l10n/ja.json @@ -97,6 +97,7 @@ "AI 해상도 올리기": "AI 高解像度化", "AI 해상도 올리기 (모든 장)": "AI 高解像度化 (全ページ)", "AI 해상도 올리기 (이 장)": "AI 高解像度化 (このページ)", + "AI 해상도 올리기 등 오래 걸린 작업이 끝났을 때": "AI 高解像度化など時間のかかる作業が終わったとき", "API 키": "API キー", "API 키 (선택)": "API キー (任意)", "API 키 (필수, 무료)": "API キー(必須・無料)", @@ -134,6 +135,8 @@ "JJ_MKVMaker - 다운로드 {0}개": "JJ_MKVMaker - ダウンロード {0}個", "JJ_MKVMaker 로 자막 만들기": "JJ_MKVMaker で字幕を作成", "JJ_MKVMaker 로 재생": "JJ_MKVMaker で再生", + "JJ_MKVMaker 작업 중": "JJ_MKVMaker 作業中", + "JJ_MKVMaker 작업이 멈췄습니다": "JJ_MKVMaker の作業が停止しました", "JJ_MKVMaker_즐겨찾기.html": "JJ_MKVMaker_ブックマーク.html", "JPG 품질 {0}": "JPG 品質 {0}", "JavaScript 사용": "JavaScript を使う", @@ -500,6 +503,7 @@ "끝: {0}": "終了: {0}", "끝나지 못했습니다: {0}": "完了できませんでした: {0}", "끝난 뒤 원본의 빈 폴더를 지웁니다 (find 원본/ -type d -empty -delete). 원본 폴더는:": "終了後、元の空フォルダーを削除します (find 元/ -type d -empty -delete)。元フォルダーは:", + "끝난 작업": "完了した作業", "끝내기": "終了", "끝내기 (작업 멈춤)": "終了 (作業を止める)", "끝에서 처음으로 돌기": "最後から最初に戻る", @@ -596,6 +600,7 @@ "다운로드 {0}": "ダウンロード {0}", "다운로드 {0}개": "ダウンロード {0}個", "다운로드 {0}개{1}": "ダウンロード {0}個{1}", + "다운로드 · MKV 만들기 · AI 자막 · 동기화 진행 상황": "ダウンロード · MKV 作成 · AI 字幕 · 同期の進行状況", "다운로드 · 변환도 모두 종료 (묻지 않음)": "ダウンロード・変換もすべて終了(確認しない)", "다운로드 목록": "ダウンロードリスト", "다운로드 목록 (지금 여기)": "ダウンロードリスト(現在地)", @@ -1137,6 +1142,8 @@ "앱에 들어 있는 것 (기본)": "アプリに内蔵 (既定)", "앱을 다시 설치하자 Android 자동 백업 (Google 백업) 이 이전 설정을 되살렸습니다.\n· 동영상 목록 {0}개\n· WebDAV 서버: {1}": "アプリを再インストールしたため、Android 自動バックアップ (Google バックアップ) が以前の設定を復元しました。\n・動画リスト {0} 件\n・WebDAV サーバー: {1}", "앱을 다시 켤 때 동기화": "アプリ再起動時の同期", + "앱을 닫아 진행 중이던 작업 (MKV · 다운로드 · 동기화) 이 멈췄습니다. 앱을 닫아도 계속하려면 환경 설정 > Rsync > '백그라운드로 실행' 을 켜세요.": "アプリを閉じたため実行中の作業 (MKV · ダウンロード · 同期) が停止しました。アプリを閉じても続けるには、設定 > Rsync >「バックグラウンドで実行」をオンにしてください。", + "앱을 닫아 진행 중이던 작업이 멈췄습니다. 닫아도 계속하려면 환경 설정에서 '백그라운드로 실행' 을 켜세요.": "アプリを閉じたため実行中の作業が停止しました。閉じても続けるには、設定で「バックグラウンドで実行」をオンにしてください。", "앱을 새로 설치해 설정이 없습니다. 저장해 둔 v{0} 의 설정 (동영상 목록 · 즐겨찾기 포함) 을 되살릴까요?\n(자막 API 키 · 비밀번호는 저장하지 않았으니 다시 넣어 주세요)": "アプリを新しくインストールしたため設定がありません。保存した v{0} の設定 (動画リスト · ブックマークを含む) を復元しますか?\n(字幕 API キー · パスワードは保存していないので再入力してください)", "앱을 지우면 다음은 되살릴 수 없습니다:": "アプリを削除すると次のものは復元できません:", "앱이 켜져 있는 동안 늘 동기화합니다.": "アプリの起動中は常に同期します。", @@ -1471,6 +1478,7 @@ "작업 임시 파일": "作業の一時ファイル", "작업 중": "作業中", "작업 중지: {0}{1}": "作業中止: {0}{1}", + "작업 진행": "作業の進行", "작업 취소": "作業をキャンセル", "작업 현황": "作業状況", "작업 현황 닫기": "作業状況を閉じる", diff --git a/assets/l10n/zh-Hans.json b/assets/l10n/zh-Hans.json index e0035d9..b4b188c 100644 --- a/assets/l10n/zh-Hans.json +++ b/assets/l10n/zh-Hans.json @@ -97,6 +97,7 @@ "AI 해상도 올리기": "AI 提高分辨率", "AI 해상도 올리기 (모든 장)": "AI 提高分辨率 (全部页面)", "AI 해상도 올리기 (이 장)": "AI 提高分辨率 (本页)", + "AI 해상도 올리기 등 오래 걸린 작업이 끝났을 때": "AI 提升分辨率等耗时任务完成时", "API 키": "API 密钥", "API 키 (선택)": "API 密钥 (可选)", "API 키 (필수, 무료)": "API 密钥(必填,免费)", @@ -134,6 +135,8 @@ "JJ_MKVMaker - 다운로드 {0}개": "JJ_MKVMaker - 下载 {0} 个", "JJ_MKVMaker 로 자막 만들기": "用 JJ_MKVMaker 生成字幕", "JJ_MKVMaker 로 재생": "用 JJ_MKVMaker 播放", + "JJ_MKVMaker 작업 중": "JJ_MKVMaker 正在处理", + "JJ_MKVMaker 작업이 멈췄습니다": "JJ_MKVMaker 的任务已停止", "JJ_MKVMaker_즐겨찾기.html": "JJ_MKVMaker_书签.html", "JPG 품질 {0}": "JPG 质量 {0}", "JavaScript 사용": "启用 JavaScript", @@ -500,6 +503,7 @@ "끝: {0}": "结束: {0}", "끝나지 못했습니다: {0}": "未能完成: {0}", "끝난 뒤 원본의 빈 폴더를 지웁니다 (find 원본/ -type d -empty -delete). 원본 폴더는:": "完成后删除源中的空文件夹 (find 源/ -type d -empty -delete)。源文件夹:", + "끝난 작업": "已完成的任务", "끝내기": "退出", "끝내기 (작업 멈춤)": "退出 (停止任务)", "끝에서 처음으로 돌기": "到末尾后回到开头", @@ -596,6 +600,7 @@ "다운로드 {0}": "下载 {0}", "다운로드 {0}개": "下载 {0} 个", "다운로드 {0}개{1}": "下载 {0} 个{1}", + "다운로드 · MKV 만들기 · AI 자막 · 동기화 진행 상황": "下载 · 制作 MKV · AI 字幕 · 同步的进度", "다운로드 · 변환도 모두 종료 (묻지 않음)": "同时退出下载 · 转换(不再询问)", "다운로드 목록": "下载列表", "다운로드 목록 (지금 여기)": "下载列表(当前位置)", @@ -1137,6 +1142,8 @@ "앱에 들어 있는 것 (기본)": "应用内置 (默认)", "앱을 다시 설치하자 Android 자동 백업 (Google 백업) 이 이전 설정을 되살렸습니다.\n· 동영상 목록 {0}개\n· WebDAV 서버: {1}": "重新安装应用后,Android 自动备份 (Google 备份) 恢复了之前的设置。\n· 视频列表 {0} 个\n· WebDAV 服务器: {1}", "앱을 다시 켤 때 동기화": "应用重新启动时同步", + "앱을 닫아 진행 중이던 작업 (MKV · 다운로드 · 동기화) 이 멈췄습니다. 앱을 닫아도 계속하려면 환경 설정 > Rsync > '백그라운드로 실행' 을 켜세요.": "关闭应用后,正在进行的任务(MKV · 下载 · 同步)已停止。如需关闭应用后继续,请开启 设置 > Rsync >“在后台运行”。", + "앱을 닫아 진행 중이던 작업이 멈췄습니다. 닫아도 계속하려면 환경 설정에서 '백그라운드로 실행' 을 켜세요.": "关闭应用后,正在进行的任务已停止。如需关闭后继续,请在设置中开启“在后台运行”。", "앱을 새로 설치해 설정이 없습니다. 저장해 둔 v{0} 의 설정 (동영상 목록 · 즐겨찾기 포함) 을 되살릴까요?\n(자막 API 키 · 비밀번호는 저장하지 않았으니 다시 넣어 주세요)": "应用为全新安装,没有设置。要恢复保存的 v{0} 设置 (包括视频列表 · 书签) 吗?\n(字幕 API 密钥 · 密码未保存,请重新输入)", "앱을 지우면 다음은 되살릴 수 없습니다:": "删除应用后以下内容无法恢复:", "앱이 켜져 있는 동안 늘 동기화합니다.": "应用运行期间始终同步。", @@ -1471,6 +1478,7 @@ "작업 임시 파일": "任务临时文件", "작업 중": "处理中", "작업 중지: {0}{1}": "任务已停止: {0}{1}", + "작업 진행": "任务进度", "작업 취소": "取消任务", "작업 현황": "任务状态", "작업 현황 닫기": "关闭任务状态", diff --git a/lib/platform/android/android_keep_alive.dart b/lib/platform/android/android_keep_alive.dart index 07907ff..145a885 100644 --- a/lib/platform/android/android_keep_alive.dart +++ b/lib/platform/android/android_keep_alive.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:flutter/services.dart'; import '../../app/app_controller.dart'; +import '../../app/i18n_controller.dart'; import '../../app/ai_local.dart'; import '../../app/download_manager.dart'; import '../../app/copy_center.dart'; @@ -27,6 +28,7 @@ class AndroidKeepAlive { c.addListener(_changed); downloads.addListener(_changed); AiJobs.instance.addListener(_changed); // 123: AI 그림도 화면을 닫아도 계속 + i18n.addListener(_changed); // 57: 알림 글 · 채널 이름도 화면 언어로 _changed(); } @@ -34,6 +36,19 @@ class AndroidKeepAlive { CopyCenter? _copies; AiStore? _ai; bool? _background; + String? _textsKey; + + /// 57: 알림 제목 · "작업 멈춤" 알림 · 알림 채널 이름 (Android 설정 > 알림 에 보이는 이름) 을 화면 언어로 + static Map notificationTexts() => { + 'working': tr('JJ_MKVMaker 작업 중'), + 'stoppedTitle': tr('JJ_MKVMaker 작업이 멈췄습니다'), + 'stoppedText': tr("앱을 닫아 진행 중이던 작업이 멈췄습니다. 닫아도 계속하려면 환경 설정에서 '백그라운드로 실행' 을 켜세요."), + 'stoppedLong': tr("앱을 닫아 진행 중이던 작업 (MKV · 다운로드 · 동기화) 이 멈췄습니다. 앱을 닫아도 계속하려면 환경 설정 > Rsync > '백그라운드로 실행' 을 켜세요."), + 'jobsChannel': tr('작업 진행'), + 'jobsChannelDesc': tr('다운로드 · MKV 만들기 · AI 자막 · 동기화 진행 상황'), + 'doneChannel': tr('끝난 작업'), + 'doneChannelDesc': tr('AI 해상도 올리기 등 오래 걸린 작업이 끝났을 때'), + }; /// 진행률 알림은 자주 바뀌므로 1초에 한 번만 보낸다 void _changed() => _timer ??= Timer(const Duration(seconds: 1), _apply); @@ -46,6 +61,14 @@ class AndroidKeepAlive { _live?.removeListener(_changed); _live = live?..addListener(_changed); } + final texts = notificationTexts(); + final textsKey = texts.values.join('\u0000'); + if (textsKey != _textsKey) { + try { + await _ch.invokeMethod('notificationTexts', texts); + _textsKey = textsKey; + } catch (_) {} + } final bg = c.settings.runInBackground; if (bg != _background) { _background = bg; diff --git a/test/notification_texts_test.dart b/test/notification_texts_test.dart new file mode 100644 index 0000000..c6c4f83 --- /dev/null +++ b/test/notification_texts_test.dart @@ -0,0 +1,32 @@ +import 'dart:io'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:jj_mkvmaker/app/i18n_controller.dart'; +import 'package:jj_mkvmaker/platform/android/android_keep_alive.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + test('57: Android 알림 글 · 채널 이름이 화면 언어로 (Kotlin 이 아는 열쇠와 같게)', () async { + final ko = AndroidKeepAlive.notificationTexts(); + // Kotlin (KeepAliveService.defaults) 의 열쇠와 한국어 기본 글이 Dart 와 같다 - 한쪽만 고치면 어긋난다 + final kt = File('android/app/src/main/kotlin/com/jj/jj_mkvmaker/KeepAliveService.kt').readAsStringSync(); + final defaults = { + for (final m in RegExp(r'"(\w+)" to "((?:[^"\\]|\\.)*)"').allMatches(kt)) m.group(1)!: m.group(2)!, + }; + expect(defaults, ko); + + await i18n.apply('ja', save: false); + try { + final ja = AndroidKeepAlive.notificationTexts(); + expect(ja['working'], 'JJ_MKVMaker 作業中'); + expect(ja['jobsChannel'], '作業の進行'); + expect(ja['doneChannel'], '完了した作業'); + for (final k in ko.keys) { + expect(ja[k], isNot(ko[k]), reason: '$k 가 번역되지 않음'); + } + } finally { + await i18n.apply('ko', save: false); + } + }); +}