Android 알림 글 · 채널 이름을 화면 언어로 (57)
- 앱이 고른 언어라 Android 의 values-xx 로는 맞출 수 없어, Dart (AndroidKeepAlive) 가 시작할 때와 화면 언어가 바뀔 때 번역된 글 8개 (진행 알림 제목, "작업 멈춤" 알림, 채널 2개의 이름 · 설명) 를 보낸다. - Kotlin 은 받은 글을 SharedPreferences 에 저장해 앱이 꺼진 뒤 서비스만 떠도 마지막 언어로 쓰고, 이미 만든 채널은 같은 ID 로 다시 만들어 이름 · 설명만 바꾼다. 받은 적이 없으면 한국어. - notification_texts_test: Kotlin 기본값과 Dart 의 한국어 글이 같은지, ja 로 바꾸면 모두 번역되는지. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>묶음4
parent
abeb3d8999
commit
a4afdda5c6
|
|
@ -85,17 +85,65 @@ class KeepAliveService : Service() {
|
||||||
|
|
||||||
const val DONE_CHANNEL_ID = "done"
|
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<String, String>) {
|
||||||
|
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<String, String>?, key: String) =
|
||||||
|
texts?.get(key)?.takeIf { it.isNotEmpty() } ?: t(context, key)
|
||||||
|
|
||||||
|
private fun createJobsChannel(context: Context, nm: NotificationManager, texts: Map<String, String>? = 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<String, String>? = 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장 저장: 폴더"). 누르면 작업 현황으로.
|
/// 157: 끝난 작업 알림 (예: "4장 저장: 폴더"). 누르면 작업 현황으로.
|
||||||
/// 진행 알림 채널 (조용히, 낮은 중요도) 과 따로 두어 알림 창의 "조용한 알림" 칸에 묻히지 않게 (보통 중요도, 소리 없음)
|
/// 진행 알림 채널 (조용히, 낮은 중요도) 과 따로 두어 알림 창의 "조용한 알림" 칸에 묻히지 않게 (보통 중요도, 소리 없음)
|
||||||
fun notifyDone(context: Context, title: String, text: String) {
|
fun notifyDone(context: Context, title: String, text: String) {
|
||||||
val nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
val nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && nm.getNotificationChannel(DONE_CHANNEL_ID) == null) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && nm.getNotificationChannel(DONE_CHANNEL_ID) == null) {
|
||||||
nm.createNotificationChannel(
|
createDoneChannel(context, nm)
|
||||||
NotificationChannel(DONE_CHANNEL_ID, "끝난 작업", NotificationManager.IMPORTANCE_DEFAULT).apply {
|
|
||||||
description = "AI 해상도 올리기 등 오래 걸린 작업이 끝났을 때"
|
|
||||||
setSound(null, null)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
val open = PendingIntent.getActivity(
|
val open = PendingIntent.getActivity(
|
||||||
context, 3,
|
context, 3,
|
||||||
|
|
@ -134,10 +182,9 @@ class KeepAliveService : Service() {
|
||||||
@Suppress("DEPRECATION") Notification.Builder(context)
|
@Suppress("DEPRECATION") Notification.Builder(context)
|
||||||
}
|
}
|
||||||
b.setSmallIcon(android.R.drawable.stat_notify_error)
|
b.setSmallIcon(android.R.drawable.stat_notify_error)
|
||||||
.setContentTitle("JJ_MKVMaker 작업이 멈췄습니다")
|
.setContentTitle(t(context, "stoppedTitle"))
|
||||||
.setContentText("앱을 닫아 진행 중이던 작업이 멈췄습니다. 닫아도 계속하려면 환경 설정에서 '백그라운드로 실행' 을 켜세요.")
|
.setContentText(t(context, "stoppedText"))
|
||||||
.setStyle(Notification.BigTextStyle().bigText(
|
.setStyle(Notification.BigTextStyle().bigText(t(context, "stoppedLong")))
|
||||||
"앱을 닫아 진행 중이던 작업 (MKV · 다운로드 · 동기화) 이 멈췄습니다. 앱을 닫아도 계속하려면 환경 설정 > Rsync > '백그라운드로 실행' 을 켜세요."))
|
|
||||||
.setContentIntent(open)
|
.setContentIntent(open)
|
||||||
.setAutoCancel(true)
|
.setAutoCancel(true)
|
||||||
nm.notify(STOPPED_ID, b.build())
|
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 {
|
private fun notification(context: Context, text: String, progress: Int, icon: String): Notification {
|
||||||
val nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
val nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && nm.getNotificationChannel(CHANNEL_ID) == null) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && nm.getNotificationChannel(CHANNEL_ID) == null) {
|
||||||
nm.createNotificationChannel(
|
createJobsChannel(context, nm)
|
||||||
NotificationChannel(CHANNEL_ID, "작업 진행", NotificationManager.IMPORTANCE_LOW).apply {
|
|
||||||
description = "다운로드 · MKV 만들기 · AI 자막 · 동기화 진행 상황"
|
|
||||||
setShowBadge(false)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
val open = PendingIntent.getActivity(
|
val open = PendingIntent.getActivity(
|
||||||
context, 0,
|
context, 0,
|
||||||
|
|
@ -180,7 +222,7 @@ class KeepAliveService : Service() {
|
||||||
}
|
}
|
||||||
// 다운로드 중이면 내려받기 아이콘, 그 밖 (실시간 동기화 · MKV · AI 자막) 은 좌우로 오가는 화살표
|
// 다운로드 중이면 내려받기 아이콘, 그 밖 (실시간 동기화 · MKV · AI 자막) 은 좌우로 오가는 화살표
|
||||||
b.setSmallIcon(if (icon == "download") android.R.drawable.stat_sys_download else R.drawable.ic_stat_sync_anim)
|
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)
|
.setContentText(text)
|
||||||
.setContentIntent(open)
|
.setContentIntent(open)
|
||||||
.setOngoing(true)
|
.setOngoing(true)
|
||||||
|
|
|
||||||
|
|
@ -170,6 +170,13 @@ class MainActivity : FlutterActivity() {
|
||||||
call.argument<String>("icon") ?: "")
|
call.argument<String>("icon") ?: "")
|
||||||
result.success(null)
|
result.success(null)
|
||||||
}
|
}
|
||||||
|
"notificationTexts" -> {
|
||||||
|
// 57: 알림 글 · 채널 이름 (앱 화면 언어)
|
||||||
|
val m = call.arguments as? Map<*, *> ?: emptyMap<Any, Any>()
|
||||||
|
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" -> {
|
"notifyDone" -> {
|
||||||
KeepAliveService.notifyDone(applicationContext, call.argument<String>("title") ?: "",
|
KeepAliveService.notifyDone(applicationContext, call.argument<String>("title") ?: "",
|
||||||
call.argument<String>("text") ?: "")
|
call.argument<String>("text") ?: "")
|
||||||
|
|
|
||||||
|
|
@ -97,6 +97,7 @@
|
||||||
"AI 해상도 올리기": "AI upscaling",
|
"AI 해상도 올리기": "AI upscaling",
|
||||||
"AI 해상도 올리기 (모든 장)": "AI upscaling (all pages)",
|
"AI 해상도 올리기 (모든 장)": "AI upscaling (all pages)",
|
||||||
"AI 해상도 올리기 (이 장)": "AI upscaling (this page)",
|
"AI 해상도 올리기 (이 장)": "AI upscaling (this page)",
|
||||||
|
"AI 해상도 올리기 등 오래 걸린 작업이 끝났을 때": "When a long task such as AI upscaling finishes",
|
||||||
"API 키": "API key",
|
"API 키": "API key",
|
||||||
"API 키 (선택)": "API key (optional)",
|
"API 키 (선택)": "API key (optional)",
|
||||||
"API 키 (필수, 무료)": "API key (required, free)",
|
"API 키 (필수, 무료)": "API key (required, free)",
|
||||||
|
|
@ -134,6 +135,8 @@
|
||||||
"JJ_MKVMaker - 다운로드 {0}개": "JJ_MKVMaker - {0} downloads",
|
"JJ_MKVMaker - 다운로드 {0}개": "JJ_MKVMaker - {0} downloads",
|
||||||
"JJ_MKVMaker 로 자막 만들기": "Make subtitles with JJ_MKVMaker",
|
"JJ_MKVMaker 로 자막 만들기": "Make subtitles with JJ_MKVMaker",
|
||||||
"JJ_MKVMaker 로 재생": "Play 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",
|
"JJ_MKVMaker_즐겨찾기.html": "JJ_MKVMaker_bookmarks.html",
|
||||||
"JPG 품질 {0}": "JPG quality {0}",
|
"JPG 품질 {0}": "JPG quality {0}",
|
||||||
"JavaScript 사용": "Enable JavaScript",
|
"JavaScript 사용": "Enable JavaScript",
|
||||||
|
|
@ -500,6 +503,7 @@
|
||||||
"끝: {0}": "End: {0}",
|
"끝: {0}": "End: {0}",
|
||||||
"끝나지 못했습니다: {0}": "Did not finish: {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:",
|
"끝난 뒤 원본의 빈 폴더를 지웁니다 (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",
|
||||||
"끝내기 (작업 멈춤)": "Exit (stop tasks)",
|
"끝내기 (작업 멈춤)": "Exit (stop tasks)",
|
||||||
"끝에서 처음으로 돌기": "Wrap around at the end",
|
"끝에서 처음으로 돌기": "Wrap around at the end",
|
||||||
|
|
@ -596,6 +600,7 @@
|
||||||
"다운로드 {0}": "Downloads {0}",
|
"다운로드 {0}": "Downloads {0}",
|
||||||
"다운로드 {0}개": "{0} downloads",
|
"다운로드 {0}개": "{0} downloads",
|
||||||
"다운로드 {0}개{1}": "{0} downloads{1}",
|
"다운로드 {0}개{1}": "{0} downloads{1}",
|
||||||
|
"다운로드 · MKV 만들기 · AI 자막 · 동기화 진행 상황": "Progress of downloads · MKV creation · AI subtitles · sync",
|
||||||
"다운로드 · 변환도 모두 종료 (묻지 않음)": "Also quit downloads · conversions (don't ask)",
|
"다운로드 · 변환도 모두 종료 (묻지 않음)": "Also quit downloads · conversions (don't ask)",
|
||||||
"다운로드 목록": "Download list",
|
"다운로드 목록": "Download list",
|
||||||
"다운로드 목록 (지금 여기)": "Download list (you are here)",
|
"다운로드 목록 (지금 여기)": "Download list (you are here)",
|
||||||
|
|
@ -1137,6 +1142,8 @@
|
||||||
"앱에 들어 있는 것 (기본)": "Built into the app (default)",
|
"앱에 들어 있는 것 (기본)": "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}",
|
"앱을 다시 설치하자 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",
|
"앱을 다시 켤 때 동기화": "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)",
|
"앱을 새로 설치해 설정이 없습니다. 저장해 둔 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:",
|
"앱을 지우면 다음은 되살릴 수 없습니다:": "Removing the app loses the following for good:",
|
||||||
"앱이 켜져 있는 동안 늘 동기화합니다.": "Always syncs while the app is running.",
|
"앱이 켜져 있는 동안 늘 동기화합니다.": "Always syncs while the app is running.",
|
||||||
|
|
@ -1471,6 +1478,7 @@
|
||||||
"작업 임시 파일": "Job temporary files",
|
"작업 임시 파일": "Job temporary files",
|
||||||
"작업 중": "Working",
|
"작업 중": "Working",
|
||||||
"작업 중지: {0}{1}": "Job stopped: {0}{1}",
|
"작업 중지: {0}{1}": "Job stopped: {0}{1}",
|
||||||
|
"작업 진행": "Task progress",
|
||||||
"작업 취소": "Cancel job",
|
"작업 취소": "Cancel job",
|
||||||
"작업 현황": "Job status",
|
"작업 현황": "Job status",
|
||||||
"작업 현황 닫기": "Close job status",
|
"작업 현황 닫기": "Close job status",
|
||||||
|
|
|
||||||
|
|
@ -97,6 +97,7 @@
|
||||||
"AI 해상도 올리기": "AI 高解像度化",
|
"AI 해상도 올리기": "AI 高解像度化",
|
||||||
"AI 해상도 올리기 (모든 장)": "AI 高解像度化 (全ページ)",
|
"AI 해상도 올리기 (모든 장)": "AI 高解像度化 (全ページ)",
|
||||||
"AI 해상도 올리기 (이 장)": "AI 高解像度化 (このページ)",
|
"AI 해상도 올리기 (이 장)": "AI 高解像度化 (このページ)",
|
||||||
|
"AI 해상도 올리기 등 오래 걸린 작업이 끝났을 때": "AI 高解像度化など時間のかかる作業が終わったとき",
|
||||||
"API 키": "API キー",
|
"API 키": "API キー",
|
||||||
"API 키 (선택)": "API キー (任意)",
|
"API 키 (선택)": "API キー (任意)",
|
||||||
"API 키 (필수, 무료)": "API キー(必須・無料)",
|
"API 키 (필수, 무료)": "API キー(必須・無料)",
|
||||||
|
|
@ -134,6 +135,8 @@
|
||||||
"JJ_MKVMaker - 다운로드 {0}개": "JJ_MKVMaker - ダウンロード {0}個",
|
"JJ_MKVMaker - 다운로드 {0}개": "JJ_MKVMaker - ダウンロード {0}個",
|
||||||
"JJ_MKVMaker 로 자막 만들기": "JJ_MKVMaker で字幕を作成",
|
"JJ_MKVMaker 로 자막 만들기": "JJ_MKVMaker で字幕を作成",
|
||||||
"JJ_MKVMaker 로 재생": "JJ_MKVMaker で再生",
|
"JJ_MKVMaker 로 재생": "JJ_MKVMaker で再生",
|
||||||
|
"JJ_MKVMaker 작업 중": "JJ_MKVMaker 作業中",
|
||||||
|
"JJ_MKVMaker 작업이 멈췄습니다": "JJ_MKVMaker の作業が停止しました",
|
||||||
"JJ_MKVMaker_즐겨찾기.html": "JJ_MKVMaker_ブックマーク.html",
|
"JJ_MKVMaker_즐겨찾기.html": "JJ_MKVMaker_ブックマーク.html",
|
||||||
"JPG 품질 {0}": "JPG 品質 {0}",
|
"JPG 품질 {0}": "JPG 品質 {0}",
|
||||||
"JavaScript 사용": "JavaScript を使う",
|
"JavaScript 사용": "JavaScript を使う",
|
||||||
|
|
@ -500,6 +503,7 @@
|
||||||
"끝: {0}": "終了: {0}",
|
"끝: {0}": "終了: {0}",
|
||||||
"끝나지 못했습니다: {0}": "完了できませんでした: {0}",
|
"끝나지 못했습니다: {0}": "完了できませんでした: {0}",
|
||||||
"끝난 뒤 원본의 빈 폴더를 지웁니다 (find 원본/ -type d -empty -delete). 원본 폴더는:": "終了後、元の空フォルダーを削除します (find 元/ -type d -empty -delete)。元フォルダーは:",
|
"끝난 뒤 원본의 빈 폴더를 지웁니다 (find 원본/ -type d -empty -delete). 원본 폴더는:": "終了後、元の空フォルダーを削除します (find 元/ -type d -empty -delete)。元フォルダーは:",
|
||||||
|
"끝난 작업": "完了した作業",
|
||||||
"끝내기": "終了",
|
"끝내기": "終了",
|
||||||
"끝내기 (작업 멈춤)": "終了 (作業を止める)",
|
"끝내기 (작업 멈춤)": "終了 (作業を止める)",
|
||||||
"끝에서 처음으로 돌기": "最後から最初に戻る",
|
"끝에서 처음으로 돌기": "最後から最初に戻る",
|
||||||
|
|
@ -596,6 +600,7 @@
|
||||||
"다운로드 {0}": "ダウンロード {0}",
|
"다운로드 {0}": "ダウンロード {0}",
|
||||||
"다운로드 {0}개": "ダウンロード {0}個",
|
"다운로드 {0}개": "ダウンロード {0}個",
|
||||||
"다운로드 {0}개{1}": "ダウンロード {0}個{1}",
|
"다운로드 {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}",
|
"앱을 다시 설치하자 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 キー · パスワードは保存していないので再入力してください)",
|
"앱을 새로 설치해 설정이 없습니다. 저장해 둔 v{0} 의 설정 (동영상 목록 · 즐겨찾기 포함) 을 되살릴까요?\n(자막 API 키 · 비밀번호는 저장하지 않았으니 다시 넣어 주세요)": "アプリを新しくインストールしたため設定がありません。保存した v{0} の設定 (動画リスト · ブックマークを含む) を復元しますか?\n(字幕 API キー · パスワードは保存していないので再入力してください)",
|
||||||
"앱을 지우면 다음은 되살릴 수 없습니다:": "アプリを削除すると次のものは復元できません:",
|
"앱을 지우면 다음은 되살릴 수 없습니다:": "アプリを削除すると次のものは復元できません:",
|
||||||
"앱이 켜져 있는 동안 늘 동기화합니다.": "アプリの起動中は常に同期します。",
|
"앱이 켜져 있는 동안 늘 동기화합니다.": "アプリの起動中は常に同期します。",
|
||||||
|
|
@ -1471,6 +1478,7 @@
|
||||||
"작업 임시 파일": "作業の一時ファイル",
|
"작업 임시 파일": "作業の一時ファイル",
|
||||||
"작업 중": "作業中",
|
"작업 중": "作業中",
|
||||||
"작업 중지: {0}{1}": "作業中止: {0}{1}",
|
"작업 중지: {0}{1}": "作業中止: {0}{1}",
|
||||||
|
"작업 진행": "作業の進行",
|
||||||
"작업 취소": "作業をキャンセル",
|
"작업 취소": "作業をキャンセル",
|
||||||
"작업 현황": "作業状況",
|
"작업 현황": "作業状況",
|
||||||
"작업 현황 닫기": "作業状況を閉じる",
|
"작업 현황 닫기": "作業状況を閉じる",
|
||||||
|
|
|
||||||
|
|
@ -97,6 +97,7 @@
|
||||||
"AI 해상도 올리기": "AI 提高分辨率",
|
"AI 해상도 올리기": "AI 提高分辨率",
|
||||||
"AI 해상도 올리기 (모든 장)": "AI 提高分辨率 (全部页面)",
|
"AI 해상도 올리기 (모든 장)": "AI 提高分辨率 (全部页面)",
|
||||||
"AI 해상도 올리기 (이 장)": "AI 提高分辨率 (本页)",
|
"AI 해상도 올리기 (이 장)": "AI 提高分辨率 (本页)",
|
||||||
|
"AI 해상도 올리기 등 오래 걸린 작업이 끝났을 때": "AI 提升分辨率等耗时任务完成时",
|
||||||
"API 키": "API 密钥",
|
"API 키": "API 密钥",
|
||||||
"API 키 (선택)": "API 密钥 (可选)",
|
"API 키 (선택)": "API 密钥 (可选)",
|
||||||
"API 키 (필수, 무료)": "API 密钥(必填,免费)",
|
"API 키 (필수, 무료)": "API 密钥(必填,免费)",
|
||||||
|
|
@ -134,6 +135,8 @@
|
||||||
"JJ_MKVMaker - 다운로드 {0}개": "JJ_MKVMaker - 下载 {0} 个",
|
"JJ_MKVMaker - 다운로드 {0}개": "JJ_MKVMaker - 下载 {0} 个",
|
||||||
"JJ_MKVMaker 로 자막 만들기": "用 JJ_MKVMaker 生成字幕",
|
"JJ_MKVMaker 로 자막 만들기": "用 JJ_MKVMaker 生成字幕",
|
||||||
"JJ_MKVMaker 로 재생": "用 JJ_MKVMaker 播放",
|
"JJ_MKVMaker 로 재생": "用 JJ_MKVMaker 播放",
|
||||||
|
"JJ_MKVMaker 작업 중": "JJ_MKVMaker 正在处理",
|
||||||
|
"JJ_MKVMaker 작업이 멈췄습니다": "JJ_MKVMaker 的任务已停止",
|
||||||
"JJ_MKVMaker_즐겨찾기.html": "JJ_MKVMaker_书签.html",
|
"JJ_MKVMaker_즐겨찾기.html": "JJ_MKVMaker_书签.html",
|
||||||
"JPG 품질 {0}": "JPG 质量 {0}",
|
"JPG 품질 {0}": "JPG 质量 {0}",
|
||||||
"JavaScript 사용": "启用 JavaScript",
|
"JavaScript 사용": "启用 JavaScript",
|
||||||
|
|
@ -500,6 +503,7 @@
|
||||||
"끝: {0}": "结束: {0}",
|
"끝: {0}": "结束: {0}",
|
||||||
"끝나지 못했습니다: {0}": "未能完成: {0}",
|
"끝나지 못했습니다: {0}": "未能完成: {0}",
|
||||||
"끝난 뒤 원본의 빈 폴더를 지웁니다 (find 원본/ -type d -empty -delete). 원본 폴더는:": "完成后删除源中的空文件夹 (find 源/ -type d -empty -delete)。源文件夹:",
|
"끝난 뒤 원본의 빈 폴더를 지웁니다 (find 원본/ -type d -empty -delete). 원본 폴더는:": "完成后删除源中的空文件夹 (find 源/ -type d -empty -delete)。源文件夹:",
|
||||||
|
"끝난 작업": "已完成的任务",
|
||||||
"끝내기": "退出",
|
"끝내기": "退出",
|
||||||
"끝내기 (작업 멈춤)": "退出 (停止任务)",
|
"끝내기 (작업 멈춤)": "退出 (停止任务)",
|
||||||
"끝에서 처음으로 돌기": "到末尾后回到开头",
|
"끝에서 처음으로 돌기": "到末尾后回到开头",
|
||||||
|
|
@ -596,6 +600,7 @@
|
||||||
"다운로드 {0}": "下载 {0}",
|
"다운로드 {0}": "下载 {0}",
|
||||||
"다운로드 {0}개": "下载 {0} 个",
|
"다운로드 {0}개": "下载 {0} 个",
|
||||||
"다운로드 {0}개{1}": "下载 {0} 个{1}",
|
"다운로드 {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}",
|
"앱을 다시 설치하자 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 密钥 · 密码未保存,请重新输入)",
|
"앱을 새로 설치해 설정이 없습니다. 저장해 둔 v{0} 의 설정 (동영상 목록 · 즐겨찾기 포함) 을 되살릴까요?\n(자막 API 키 · 비밀번호는 저장하지 않았으니 다시 넣어 주세요)": "应用为全新安装,没有设置。要恢复保存的 v{0} 设置 (包括视频列表 · 书签) 吗?\n(字幕 API 密钥 · 密码未保存,请重新输入)",
|
||||||
"앱을 지우면 다음은 되살릴 수 없습니다:": "删除应用后以下内容无法恢复:",
|
"앱을 지우면 다음은 되살릴 수 없습니다:": "删除应用后以下内容无法恢复:",
|
||||||
"앱이 켜져 있는 동안 늘 동기화합니다.": "应用运行期间始终同步。",
|
"앱이 켜져 있는 동안 늘 동기화합니다.": "应用运行期间始终同步。",
|
||||||
|
|
@ -1471,6 +1478,7 @@
|
||||||
"작업 임시 파일": "任务临时文件",
|
"작업 임시 파일": "任务临时文件",
|
||||||
"작업 중": "处理中",
|
"작업 중": "处理中",
|
||||||
"작업 중지: {0}{1}": "任务已停止: {0}{1}",
|
"작업 중지: {0}{1}": "任务已停止: {0}{1}",
|
||||||
|
"작업 진행": "任务进度",
|
||||||
"작업 취소": "取消任务",
|
"작업 취소": "取消任务",
|
||||||
"작업 현황": "任务状态",
|
"작업 현황": "任务状态",
|
||||||
"작업 현황 닫기": "关闭任务状态",
|
"작업 현황 닫기": "关闭任务状态",
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import 'dart:async';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
import '../../app/app_controller.dart';
|
import '../../app/app_controller.dart';
|
||||||
|
import '../../app/i18n_controller.dart';
|
||||||
import '../../app/ai_local.dart';
|
import '../../app/ai_local.dart';
|
||||||
import '../../app/download_manager.dart';
|
import '../../app/download_manager.dart';
|
||||||
import '../../app/copy_center.dart';
|
import '../../app/copy_center.dart';
|
||||||
|
|
@ -27,6 +28,7 @@ class AndroidKeepAlive {
|
||||||
c.addListener(_changed);
|
c.addListener(_changed);
|
||||||
downloads.addListener(_changed);
|
downloads.addListener(_changed);
|
||||||
AiJobs.instance.addListener(_changed); // 123: AI 그림도 화면을 닫아도 계속
|
AiJobs.instance.addListener(_changed); // 123: AI 그림도 화면을 닫아도 계속
|
||||||
|
i18n.addListener(_changed); // 57: 알림 글 · 채널 이름도 화면 언어로
|
||||||
_changed();
|
_changed();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -34,6 +36,19 @@ class AndroidKeepAlive {
|
||||||
CopyCenter? _copies;
|
CopyCenter? _copies;
|
||||||
AiStore? _ai;
|
AiStore? _ai;
|
||||||
bool? _background;
|
bool? _background;
|
||||||
|
String? _textsKey;
|
||||||
|
|
||||||
|
/// 57: 알림 제목 · "작업 멈춤" 알림 · 알림 채널 이름 (Android 설정 > 알림 에 보이는 이름) 을 화면 언어로
|
||||||
|
static Map<String, String> 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초에 한 번만 보낸다
|
/// 진행률 알림은 자주 바뀌므로 1초에 한 번만 보낸다
|
||||||
void _changed() => _timer ??= Timer(const Duration(seconds: 1), _apply);
|
void _changed() => _timer ??= Timer(const Duration(seconds: 1), _apply);
|
||||||
|
|
@ -46,6 +61,14 @@ class AndroidKeepAlive {
|
||||||
_live?.removeListener(_changed);
|
_live?.removeListener(_changed);
|
||||||
_live = live?..addListener(_changed);
|
_live = live?..addListener(_changed);
|
||||||
}
|
}
|
||||||
|
final texts = notificationTexts();
|
||||||
|
final textsKey = texts.values.join('\u0000');
|
||||||
|
if (textsKey != _textsKey) {
|
||||||
|
try {
|
||||||
|
await _ch.invokeMethod<void>('notificationTexts', texts);
|
||||||
|
_textsKey = textsKey;
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
final bg = c.settings.runInBackground;
|
final bg = c.settings.runInBackground;
|
||||||
if (bg != _background) {
|
if (bg != _background) {
|
||||||
_background = bg;
|
_background = bg;
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue