@unbrained/pm-web 2026.7.11 → 2026.7.17-1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +26 -0
  3. package/dist/app.d.ts +16 -1
  4. package/dist/app.js +53 -3
  5. package/dist/app.js.map +1 -1
  6. package/dist/auth.d.ts +2 -1
  7. package/dist/auth.js +10 -0
  8. package/dist/auth.js.map +1 -1
  9. package/dist/db.js +17 -1
  10. package/dist/db.js.map +1 -1
  11. package/dist/ical.js.map +1 -1
  12. package/dist/index.d.ts +1 -22
  13. package/dist/index.js +2 -3
  14. package/dist/index.js.map +1 -1
  15. package/dist/oidc.d.ts +89 -0
  16. package/dist/oidc.js +305 -0
  17. package/dist/oidc.js.map +1 -0
  18. package/dist/routes/auth.js +3 -15
  19. package/dist/routes/auth.js.map +1 -1
  20. package/dist/routes/github.js +3 -3
  21. package/dist/routes/github.js.map +1 -1
  22. package/dist/routes/oidc.d.ts +4 -0
  23. package/dist/routes/oidc.js +147 -0
  24. package/dist/routes/oidc.js.map +1 -0
  25. package/dist/routes/pm.js +172 -108
  26. package/dist/routes/pm.js.map +1 -1
  27. package/dist/routes/projects.js +9 -2
  28. package/dist/routes/projects.js.map +1 -1
  29. package/dist/server.js +7 -2
  30. package/dist/server.js.map +1 -1
  31. package/dist/services/pm-runner.d.ts +27 -3
  32. package/dist/services/pm-runner.js +188 -54
  33. package/dist/services/pm-runner.js.map +1 -1
  34. package/dist/services/realtime-bus.d.ts +1 -0
  35. package/dist/services/realtime-bus.js +148 -0
  36. package/dist/services/realtime-bus.js.map +1 -0
  37. package/dist/services/sse.d.ts +3 -1
  38. package/dist/services/sse.js +83 -48
  39. package/dist/services/sse.js.map +1 -1
  40. package/manifest.json +1 -1
  41. package/package.json +11 -11
  42. package/public/cookie-consent.js +54 -39
  43. package/public/cookie-settings.html +7 -7
  44. package/public/index.html +32 -26
  45. package/public/legal-notice.html +10 -12
  46. package/public/privacy-policy.html +14 -24
  47. package/public/src/app.js +18 -4
  48. package/public/src/app.js.map +1 -1
  49. package/public/src/app.ts +19 -4
  50. package/public/src/components/modals.js +5 -3
  51. package/public/src/components/modals.js.map +1 -1
  52. package/public/src/components/modals.ts +6 -3
  53. package/public/src/components/toast.js.map +1 -1
  54. package/public/src/cookie-consent.ts +68 -0
  55. package/public/src/i18n/de.json +117 -0
  56. package/public/src/i18n/en.json +117 -0
  57. package/public/src/i18n/es.json +117 -0
  58. package/public/src/i18n/zh.json +117 -0
  59. package/public/src/i18n.js +287 -0
  60. package/public/src/i18n.js.map +1 -0
  61. package/public/src/i18n.ts +306 -0
  62. package/public/src/sw.ts +446 -0
  63. package/public/src/views/auth.js +29 -6
  64. package/public/src/views/auth.js.map +1 -1
  65. package/public/src/views/auth.ts +27 -6
  66. package/public/src/views/graph.js.map +1 -1
  67. package/public/src/views/settings.js +73 -48
  68. package/public/src/views/settings.js.map +1 -1
  69. package/public/src/views/settings.ts +77 -48
  70. package/public/styles.css +2 -0
  71. package/public/sw.js +323 -256
  72. package/public/terms.html +7 -9
  73. package/public/tsconfig.json +1 -1
  74. package/public/tsconfig.scripts.json +18 -0
  75. package/public/tsconfig.sw.json +18 -0
  76. package/sql/schema.sql +16 -0
@@ -0,0 +1,117 @@
1
+ {
2
+ "auth.brand.tagline": "Git-native project management",
3
+ "auth.feature.tasks": "Manage tasks, features, bugs, epics & more",
4
+ "auth.feature.storage": "Git-native storage in .agents/pm/",
5
+ "auth.feature.history": "Full history, comments & activity feeds",
6
+ "auth.feature.team": "Team-friendly project isolation",
7
+ "auth.title.login": "Welcome back",
8
+ "auth.title.register": "Create account",
9
+ "auth.sub.login": "Sign in to your account to continue",
10
+ "auth.sub.register": "Join pm-web and start managing projects",
11
+ "auth.tab.login": "Sign In",
12
+ "auth.tab.register": "Register",
13
+ "auth.field.displayName": "Display Name",
14
+ "auth.field.email": "Email",
15
+ "auth.field.password": "Password",
16
+ "auth.placeholder.name": "Your name",
17
+ "auth.placeholder.email": "you@example.com",
18
+ "auth.placeholder.password": "••••••••",
19
+ "auth.divider": "or",
20
+ "auth.oidc.default": "Continue with OpenID Connect",
21
+ "auth.oidc.template": "Continue with {label}",
22
+ "auth.button.login": "Sign In",
23
+ "auth.button.register": "Create Account",
24
+ "auth.loading": "Please wait…",
25
+ "nav.skipLink": "Skip to main content",
26
+ "nav.settings": "Settings",
27
+ "nav.signOut": "Sign out",
28
+ "nav.search": "⌘K Search",
29
+ "nav.search.title": "Open search (Ctrl+K)",
30
+ "nav.selectProject": "— Select project —",
31
+ "nav.sse.disconnected": "Real-time sync disconnected",
32
+ "nav.sse.connected": "Real-time sync connected",
33
+ "nav.sse.reconnecting": "Real-time sync reconnecting…",
34
+ "nav.theme.title": "Toggle theme (dark / light / auto)",
35
+ "banner.offline": "You are offline — changes will sync when reconnected",
36
+ "banner.install.text": "Install <strong>pm-web</strong> for a native app experience",
37
+ "banner.install.dismiss": "Not now",
38
+ "banner.install.button": "Install",
39
+ "cookie.banner.title": "Cookie Settings",
40
+ "cookie.banner.text": "We only use technically necessary storage for sign-in, security, and preferences. No optional tracking cookies.",
41
+ "cookie.banner.privacy": "Privacy Policy",
42
+ "cookie.banner.decline": "Necessary only",
43
+ "cookie.banner.accept": "Got it",
44
+ "settings.title": "Settings",
45
+ "settings.subtitle": "Manage your profile and account",
46
+ "settings.language": "Language",
47
+ "settings.languageEn": "English",
48
+ "settings.languageDe": "Deutsch",
49
+ "settings.languageEs": "Español",
50
+ "settings.languageHint": "Interface language. Legal pages are provided by the operator and may not be translated.",
51
+ "settings.profile": "Profile",
52
+ "settings.displayName": "Display Name",
53
+ "settings.displayNamePlaceholder": "Your display name",
54
+ "settings.email": "Email",
55
+ "settings.emailReadonly": "Email address (read only)",
56
+ "settings.accountCreated": "Account created {date}",
57
+ "settings.saveProfile": "Save Profile",
58
+ "settings.changePassword": "Change Password",
59
+ "settings.currentPassword": "Current Password",
60
+ "settings.newPassword": "New Password",
61
+ "settings.confirmPassword": "Confirm New Password",
62
+ "settings.currentPasswordPlaceholder": "Current password",
63
+ "settings.newPasswordPlaceholder": "New password",
64
+ "settings.confirmPasswordPlaceholder": "Confirm new password",
65
+ "settings.githubToken": "GitHub Token",
66
+ "settings.tokenConfigured": "✓ Token configured",
67
+ "settings.tokenNotSet": "No token set",
68
+ "settings.tokenPlaceholder.keep": "Leave blank to keep current token",
69
+ "settings.tokenPlaceholder.set": "ghp_…",
70
+ "settings.tokenLabel": "Personal Access Token (PAT)",
71
+ "settings.tokenHint": "Used for GitHub integration. Needs <code>repo</code> scope.",
72
+ "settings.saveToken": "Save Token",
73
+ "settings.clearToken": "Clear Token",
74
+ "settings.howToGet": "How to get a token:",
75
+ "settings.tokenStep1": "Go to GitHub → Settings → Developer settings → Personal access tokens",
76
+ "settings.tokenStep2": "Generate a new token with <code>repo</code> scope",
77
+ "settings.tokenStep3": "Paste the token below and click Save",
78
+ "settings.saving": "Saving…",
79
+ "settings.changing": "Changing…",
80
+ "settings.displayNameEmpty": "Display name cannot be empty",
81
+ "settings.profileSaved": "Profile saved",
82
+ "settings.allFieldsRequired": "All fields are required",
83
+ "settings.passwordsDoNotMatch": "New passwords do not match",
84
+ "settings.passwordTooShort": "New password must be at least 6 characters",
85
+ "settings.passwordChanged": "Password changed successfully",
86
+ "settings.tokenEmpty": "Please enter a token",
87
+ "settings.tokenSaved": "GitHub token saved",
88
+ "settings.tokenCleared": "GitHub token cleared",
89
+ "settings.clearTokenTitle": "Clear GitHub Token?",
90
+ "settings.clearTokenBody": "Your GitHub integration will stop working.",
91
+ "dialog.cancel": "Cancel",
92
+ "dialog.confirm": "Confirm",
93
+ "dialog.delete": "Delete",
94
+ "error.emailPasswordRequired": "Email and password are required",
95
+ "error.passwordTooShort": "Password must be at least 8 characters",
96
+ "error.invalidEmail": "Invalid email address",
97
+ "error.accountExists": "An account with this email already exists",
98
+ "error.registrationFailed": "Registration failed",
99
+ "error.invalidCredentials": "Invalid email or password",
100
+ "error.loginFailed": "Login failed",
101
+ "error.userNotFound": "User not found",
102
+ "error.fetchUserFailed": "Failed to fetch user",
103
+ "error.updateProfileFailed": "Failed to update profile",
104
+ "error.passwordFieldsRequired": "Current and new passwords are required",
105
+ "error.newPasswordTooShort": "New password must be at least 8 characters",
106
+ "error.currentPasswordIncorrect": "Current password is incorrect",
107
+ "error.changePasswordFailed": "Failed to change password",
108
+ "error.saveTokenFailed": "Failed to save GitHub token",
109
+ "error.oidcDisabled": "OpenID Connect login is not enabled.",
110
+ "error.oidcDiscoveryFailed": "OpenID Connect provider discovery failed.",
111
+ "error.oidcProviderError": "The OpenID Connect provider could not complete login.",
112
+ "error.oidcDenied": "OpenID Connect login was canceled or denied.",
113
+ "error.oidcMissingIdToken": "OIDC provider did not return an ID token.",
114
+ "error.oidcLoginFailed": "OpenID Connect login failed.",
115
+ "legal.disclaimer": "This translation is provided for comprehension only; the English version is authoritative.",
116
+ "settings.languageZh": "中文"
117
+ }
@@ -0,0 +1,117 @@
1
+ {
2
+ "auth.brand.tagline": "Gestión de proyectos nativa de Git",
3
+ "auth.feature.tasks": "Gestione tareas, funciones, errores, epics y más",
4
+ "auth.feature.storage": "Almacenamiento nativo de Git en .agents/pm/",
5
+ "auth.feature.history": "Historial completo, comentarios y feeds de actividad",
6
+ "auth.feature.team": "Aislamiento de proyectos apto para equipos",
7
+ "auth.title.login": "Bienvenido de nuevo",
8
+ "auth.title.register": "Crear cuenta",
9
+ "auth.sub.login": "Inicie sesión en su cuenta para continuar",
10
+ "auth.sub.register": "Únase a pm-web y comience a gestionar proyectos",
11
+ "auth.tab.login": "Iniciar sesión",
12
+ "auth.tab.register": "Registrarse",
13
+ "auth.field.displayName": "Nombre para mostrar",
14
+ "auth.field.email": "Correo electrónico",
15
+ "auth.field.password": "Contraseña",
16
+ "auth.placeholder.name": "Su nombre",
17
+ "auth.placeholder.email": "you@example.com",
18
+ "auth.placeholder.password": "••••••••",
19
+ "auth.divider": "o",
20
+ "auth.oidc.default": "Continuar con OpenID Connect",
21
+ "auth.oidc.template": "Continuar con {label}",
22
+ "auth.button.login": "Iniciar sesión",
23
+ "auth.button.register": "Crear cuenta",
24
+ "auth.loading": "Por favor, espere…",
25
+ "nav.skipLink": "Saltar al contenido principal",
26
+ "nav.settings": "Configuración",
27
+ "nav.signOut": "Cerrar sesión",
28
+ "nav.search": "⌘K Buscar",
29
+ "nav.search.title": "Abrir búsqueda (Ctrl+K)",
30
+ "nav.selectProject": "— Seleccionar proyecto —",
31
+ "nav.sse.disconnected": "Sincronización en tiempo real desconectada",
32
+ "nav.sse.connected": "Sincronización en tiempo real conectada",
33
+ "nav.sse.reconnecting": "Reconectando sincronización en tiempo real…",
34
+ "nav.theme.title": "Cambiar tema (oscuro / claro / auto)",
35
+ "banner.offline": "Está sin conexión — los cambios se sincronizarán al reconectar",
36
+ "banner.install.text": "Instale <strong>pm-web</strong> para una experiencia de aplicación nativa",
37
+ "banner.install.dismiss": "Ahora no",
38
+ "banner.install.button": "Instalar",
39
+ "cookie.banner.title": "Configuración de cookies",
40
+ "cookie.banner.text": "Solo utilizamos almacenamiento técnicamente necesario para el inicio de sesión, la seguridad y las preferencias. Sin cookies opcionales de seguimiento.",
41
+ "cookie.banner.privacy": "Política de privacidad",
42
+ "cookie.banner.decline": "Solo las necesarias",
43
+ "cookie.banner.accept": "Entendido",
44
+ "settings.title": "Configuración",
45
+ "settings.subtitle": "Gestione su perfil y su cuenta",
46
+ "settings.language": "Idioma",
47
+ "settings.languageEn": "English",
48
+ "settings.languageDe": "Deutsch",
49
+ "settings.languageEs": "Español",
50
+ "settings.languageHint": "Idioma de la interfaz. Las páginas legales las proporciona el operador y pueden no estar traducidas.",
51
+ "settings.profile": "Perfil",
52
+ "settings.displayName": "Nombre para mostrar",
53
+ "settings.displayNamePlaceholder": "Su nombre para mostrar",
54
+ "settings.email": "Correo electrónico",
55
+ "settings.emailReadonly": "Correo electrónico (solo lectura)",
56
+ "settings.accountCreated": "Cuenta creada el {date}",
57
+ "settings.saveProfile": "Guardar perfil",
58
+ "settings.changePassword": "Cambiar contraseña",
59
+ "settings.currentPassword": "Contraseña actual",
60
+ "settings.newPassword": "Nueva contraseña",
61
+ "settings.confirmPassword": "Confirmar nueva contraseña",
62
+ "settings.currentPasswordPlaceholder": "Contraseña actual",
63
+ "settings.newPasswordPlaceholder": "Nueva contraseña",
64
+ "settings.confirmPasswordPlaceholder": "Confirmar nueva contraseña",
65
+ "settings.githubToken": "Token de GitHub",
66
+ "settings.tokenConfigured": "✓ Token configurado",
67
+ "settings.tokenNotSet": "Ningún token establecido",
68
+ "settings.tokenPlaceholder.keep": "Déjelo en blanco para mantener el token actual",
69
+ "settings.tokenPlaceholder.set": "ghp_…",
70
+ "settings.tokenLabel": "Personal Access Token (PAT)",
71
+ "settings.tokenHint": "Se usa para la integración con GitHub. Necesita el alcance <code>repo</code>.",
72
+ "settings.saveToken": "Guardar token",
73
+ "settings.clearToken": "Borrar token",
74
+ "settings.howToGet": "Cómo obtener un token:",
75
+ "settings.tokenStep1": "Vaya a GitHub → Settings → Developer settings → Personal access tokens",
76
+ "settings.tokenStep2": "Genere un nuevo token con el alcance <code>repo</code>",
77
+ "settings.tokenStep3": "Pegue el token abajo y haga clic en Guardar",
78
+ "settings.saving": "Guardando…",
79
+ "settings.changing": "Cambiando…",
80
+ "settings.displayNameEmpty": "El nombre para mostrar no puede estar vacío",
81
+ "settings.profileSaved": "Perfil guardado",
82
+ "settings.allFieldsRequired": "Todos los campos son obligatorios",
83
+ "settings.passwordsDoNotMatch": "Las nuevas contraseñas no coinciden",
84
+ "settings.passwordTooShort": "La nueva contraseña debe tener al menos 6 caracteres",
85
+ "settings.passwordChanged": "Contraseña cambiada correctamente",
86
+ "settings.tokenEmpty": "Por favor, introduzca un token",
87
+ "settings.tokenSaved": "Token de GitHub guardado",
88
+ "settings.tokenCleared": "Token de GitHub borrado",
89
+ "settings.clearTokenTitle": "¿Borrar el token de GitHub?",
90
+ "settings.clearTokenBody": "Su integración con GitHub dejará de funcionar.",
91
+ "dialog.cancel": "Cancelar",
92
+ "dialog.confirm": "Confirmar",
93
+ "dialog.delete": "Eliminar",
94
+ "error.emailPasswordRequired": "El correo electrónico y la contraseña son obligatorios",
95
+ "error.passwordTooShort": "La contraseña debe tener al menos 8 caracteres",
96
+ "error.invalidEmail": "Correo electrónico no válido",
97
+ "error.accountExists": "Ya existe una cuenta con este correo electrónico",
98
+ "error.registrationFailed": "Error en el registro",
99
+ "error.invalidCredentials": "Correo electrónico o contraseña no válidos",
100
+ "error.loginFailed": "Error al iniciar sesión",
101
+ "error.userNotFound": "Usuario no encontrado",
102
+ "error.fetchUserFailed": "Error al obtener el usuario",
103
+ "error.updateProfileFailed": "Error al actualizar el perfil",
104
+ "error.passwordFieldsRequired": "La contraseña actual y la nueva son obligatorias",
105
+ "error.newPasswordTooShort": "La nueva contraseña debe tener al menos 8 caracteres",
106
+ "error.currentPasswordIncorrect": "La contraseña actual es incorrecta",
107
+ "error.changePasswordFailed": "Error al cambiar la contraseña",
108
+ "error.saveTokenFailed": "Error al guardar el token de GitHub",
109
+ "error.oidcDisabled": "El inicio de sesión con OpenID Connect no está activado.",
110
+ "error.oidcDiscoveryFailed": "Error en el descubrimiento del proveedor de OpenID Connect.",
111
+ "error.oidcProviderError": "El proveedor de OpenID Connect no pudo completar el inicio de sesión.",
112
+ "error.oidcDenied": "El inicio de sesión con OpenID Connect fue cancelado o denegado.",
113
+ "error.oidcMissingIdToken": "El proveedor OIDC no devolvió un ID token.",
114
+ "error.oidcLoginFailed": "Error en el inicio de sesión con OpenID Connect.",
115
+ "legal.disclaimer": "Esta traducción se proporciona solo para comprensión; la versión en inglés es la autoritativa.",
116
+ "settings.languageZh": "中文"
117
+ }
@@ -0,0 +1,117 @@
1
+ {
2
+ "auth.brand.tagline": "基于 Git 的项目管理",
3
+ "auth.feature.tasks": "管理任务、功能、缺陷、史诗等",
4
+ "auth.feature.storage": "基于 Git 的存储,位于 .agents/pm/",
5
+ "auth.feature.history": "完整历史记录、评论和活动动态",
6
+ "auth.feature.team": "适合团队的项目隔离",
7
+ "auth.title.login": "欢迎回来",
8
+ "auth.title.register": "创建账户",
9
+ "auth.sub.login": "登录您的账户以继续",
10
+ "auth.sub.register": "加入 pm-web 并开始管理项目",
11
+ "auth.tab.login": "登录",
12
+ "auth.tab.register": "注册",
13
+ "auth.field.displayName": "显示名称",
14
+ "auth.field.email": "电子邮件",
15
+ "auth.field.password": "密码",
16
+ "auth.placeholder.name": "您的名称",
17
+ "auth.placeholder.email": "you@example.com",
18
+ "auth.placeholder.password": "••••••••",
19
+ "auth.divider": "或",
20
+ "auth.oidc.default": "使用 OpenID Connect 继续",
21
+ "auth.oidc.template": "使用 {label} 继续",
22
+ "auth.button.login": "登录",
23
+ "auth.button.register": "创建账户",
24
+ "auth.loading": "请稍候…",
25
+ "nav.skipLink": "跳转到主要内容",
26
+ "nav.settings": "设置",
27
+ "nav.signOut": "退出登录",
28
+ "nav.search": "⌘K 搜索",
29
+ "nav.search.title": "打开搜索 (Ctrl+K)",
30
+ "nav.selectProject": "— 选择项目 —",
31
+ "nav.sse.disconnected": "实时同步已断开",
32
+ "nav.sse.connected": "实时同步已连接",
33
+ "nav.sse.reconnecting": "正在重新连接实时同步…",
34
+ "nav.theme.title": "切换主题(深色 / 浅色 / 自动)",
35
+ "banner.offline": "您处于离线状态 — 重新连接后将同步更改",
36
+ "banner.install.text": "安装 <strong>pm-web</strong> 以获得原生应用体验",
37
+ "banner.install.dismiss": "暂不",
38
+ "banner.install.button": "安装",
39
+ "cookie.banner.title": "Cookie 设置",
40
+ "cookie.banner.text": "我们仅使用登录、安全和偏好设置所必需的技术性存储。不使用任何可选的跟踪 Cookie。",
41
+ "cookie.banner.privacy": "隐私政策",
42
+ "cookie.banner.decline": "仅保留必需项",
43
+ "cookie.banner.accept": "知道了",
44
+ "settings.title": "设置",
45
+ "settings.subtitle": "管理您的个人资料和账户",
46
+ "settings.language": "语言",
47
+ "settings.languageEn": "English",
48
+ "settings.languageDe": "Deutsch",
49
+ "settings.languageEs": "Español",
50
+ "settings.languageZh": "中文",
51
+ "settings.languageHint": "界面语言。法律页面由运营方提供,可能不予翻译。",
52
+ "settings.profile": "个人资料",
53
+ "settings.displayName": "显示名称",
54
+ "settings.displayNamePlaceholder": "您的显示名称",
55
+ "settings.email": "电子邮件",
56
+ "settings.emailReadonly": "电子邮件地址(只读)",
57
+ "settings.accountCreated": "账户创建于 {date}",
58
+ "settings.saveProfile": "保存个人资料",
59
+ "settings.changePassword": "更改密码",
60
+ "settings.currentPassword": "当前密码",
61
+ "settings.newPassword": "新密码",
62
+ "settings.confirmPassword": "确认新密码",
63
+ "settings.currentPasswordPlaceholder": "当前密码",
64
+ "settings.newPasswordPlaceholder": "新密码",
65
+ "settings.confirmPasswordPlaceholder": "确认新密码",
66
+ "settings.githubToken": "GitHub 令牌",
67
+ "settings.tokenConfigured": "✓ 令牌已配置",
68
+ "settings.tokenNotSet": "未设置令牌",
69
+ "settings.tokenPlaceholder.keep": "留空以保留当前令牌",
70
+ "settings.tokenPlaceholder.set": "ghp_…",
71
+ "settings.tokenLabel": "Personal Access Token (PAT)",
72
+ "settings.tokenHint": "用于 GitHub 集成。需要 <code>repo</code> 权限范围。",
73
+ "settings.saveToken": "保存令牌",
74
+ "settings.clearToken": "清除令牌",
75
+ "settings.howToGet": "如何获取令牌:",
76
+ "settings.tokenStep1": "前往 GitHub → Settings → Developer settings → Personal access tokens",
77
+ "settings.tokenStep2": "生成一个具有 <code>repo</code> 权限范围的新令牌",
78
+ "settings.tokenStep3": "将令牌粘贴到下方并点击保存",
79
+ "settings.saving": "保存中…",
80
+ "settings.changing": "更改中…",
81
+ "settings.displayNameEmpty": "显示名称不能为空",
82
+ "settings.profileSaved": "个人资料已保存",
83
+ "settings.allFieldsRequired": "所有字段均为必填",
84
+ "settings.passwordsDoNotMatch": "新密码不匹配",
85
+ "settings.passwordTooShort": "新密码至少需要 6 个字符",
86
+ "settings.passwordChanged": "密码更改成功",
87
+ "settings.tokenEmpty": "请输入令牌",
88
+ "settings.tokenSaved": "GitHub 令牌已保存",
89
+ "settings.tokenCleared": "GitHub 令牌已清除",
90
+ "settings.clearTokenTitle": "清除 GitHub 令牌?",
91
+ "settings.clearTokenBody": "您的 GitHub 集成将停止工作。",
92
+ "dialog.cancel": "取消",
93
+ "dialog.confirm": "确认",
94
+ "dialog.delete": "删除",
95
+ "error.emailPasswordRequired": "电子邮件和密码为必填项",
96
+ "error.passwordTooShort": "密码至少需要 8 个字符",
97
+ "error.invalidEmail": "电子邮件地址无效",
98
+ "error.accountExists": "该电子邮件地址已存在账户",
99
+ "error.registrationFailed": "注册失败",
100
+ "error.invalidCredentials": "电子邮件或密码无效",
101
+ "error.loginFailed": "登录失败",
102
+ "error.userNotFound": "未找到用户",
103
+ "error.fetchUserFailed": "获取用户失败",
104
+ "error.updateProfileFailed": "更新个人资料失败",
105
+ "error.passwordFieldsRequired": "当前密码和新密码为必填项",
106
+ "error.newPasswordTooShort": "新密码至少需要 8 个字符",
107
+ "error.currentPasswordIncorrect": "当前密码不正确",
108
+ "error.changePasswordFailed": "更改密码失败",
109
+ "error.saveTokenFailed": "保存 GitHub 令牌失败",
110
+ "error.oidcDisabled": "未启用 OpenID Connect 登录。",
111
+ "error.oidcDiscoveryFailed": "OpenID Connect 提供者发现失败。",
112
+ "error.oidcProviderError": "OpenID Connect 提供者无法完成登录。",
113
+ "error.oidcDenied": "OpenID Connect 登录已被取消或拒绝。",
114
+ "error.oidcMissingIdToken": "OIDC 提供者未返回 ID 令牌。",
115
+ "error.oidcLoginFailed": "OpenID Connect 登录失败。",
116
+ "legal.disclaimer": "本翻译仅供理解之用;以英文版本为准。"
117
+ }
@@ -0,0 +1,287 @@
1
+ // ═══════════════════════════════════════════════════════════════
2
+ // i18n — locale resolution, catalog lookup, DOM binding
3
+ // ═══════════════════════════════════════════════════════════════
4
+ //
5
+ // Flat JSON catalogs (one file per locale) live alongside this module in
6
+ // `public/src/i18n/{en,de}.json`. They are fetched at runtime so the browser
7
+ // bundle stays free of new build-time dependencies and the same JSON files
8
+ // can be audited by tests directly. The English catalog is the source of
9
+ // truth: every key must exist in `en.json`; other locales fall back to it.
10
+ //
11
+ // Scope: this module covers *package-owned* SPA UI strings (auth screen, nav,
12
+ // banners, settings incl. registration acknowledgement, consent UI). Server
13
+ // API error strings stay English on the wire and are translated at the
14
+ // display layer via `translateError()` (fallback: raw message).
15
+ //
16
+ // Legal pages (`public/*.html`) are operator-overlay templates
17
+ // (`PM_WEB_LEGAL_DIR`); their localization is an operator concern and is NOT
18
+ // handled here. The language selector deliberately does not promise
19
+ // translated legal pages — see `settings.languageHint` and the German
20
+ // disclaimer string `legal.disclaimer`.
21
+ /** localStorage key persisting the user's locale choice. */
22
+ export const LOCALE_STORAGE_KEY = 'pmLocale';
23
+ /** Locales shipped by this package. The first entry is the default/fallback. */
24
+ export const SUPPORTED_LOCALES = ['en', 'de', 'es', 'zh'];
25
+ const DEFAULT_LOCALE = 'en';
26
+ /**
27
+ * Resolve the active locale without touching the DOM, so it is unit-testable
28
+ * in Node. Resolution order:
29
+ * 1. `opts.storage[LOCALE_STORAGE_KEY]` (explicit user choice)
30
+ * 2. `opts.navLang` prefix match against SUPPORTED_LOCALES (e.g. `de-DE` → `de`)
31
+ * 3. default locale (`en`)
32
+ *
33
+ * `opts.storage` may be `null` to skip the localStorage step; `opts.navLang`
34
+ * may be omitted to skip navigator negotiation. When called with no
35
+ * arguments the real browser globals (`localStorage`, `navigator.language`)
36
+ * are used — but only inside this function, never at module top level, so
37
+ * importing this module in Node has no side effects.
38
+ */
39
+ export function resolveLocale(opts) {
40
+ // A property that is present (even if `null`) means the caller explicitly
41
+ // wants that value: `null` skips the step. A property that is absent falls
42
+ // back to the real browser global, so the no-argument browser path still
43
+ // works. This keeps the function deterministic and unit-testable in Node.
44
+ const hasStorage = opts != null && Object.prototype.hasOwnProperty.call(opts, 'storage');
45
+ const storage = hasStorage ? opts.storage : safeLocalStorage();
46
+ if (storage) {
47
+ let stored = null;
48
+ try {
49
+ stored = storage.getItem(LOCALE_STORAGE_KEY);
50
+ }
51
+ catch { /* privacy mode */ }
52
+ if (stored && isSupported(stored))
53
+ return stored;
54
+ }
55
+ const hasNav = opts != null && Object.prototype.hasOwnProperty.call(opts, 'navLang');
56
+ const navLang = hasNav ? opts.navLang : safeNavLang();
57
+ if (navLang) {
58
+ const prefix = navLang.toLowerCase().split('-')[0];
59
+ if (prefix && isSupported(prefix))
60
+ return prefix;
61
+ }
62
+ return DEFAULT_LOCALE;
63
+ }
64
+ function isSupported(value) {
65
+ return SUPPORTED_LOCALES.includes(value);
66
+ }
67
+ /** localStorage access guarded so Node imports/tests never crash.
68
+ * Resolving the storage reference itself can throw a `SecurityError` in
69
+ * privacy/incognito modes that disable storage, so the lookup is wrapped too. */
70
+ function safeLocalStorage() {
71
+ try {
72
+ const g = globalThis;
73
+ return g.localStorage ?? null;
74
+ }
75
+ catch {
76
+ return null;
77
+ }
78
+ }
79
+ /** navigator.language access guarded so Node imports/tests never crash. */
80
+ function safeNavLang() {
81
+ const n = globalThis.navigator;
82
+ return n?.language;
83
+ }
84
+ /**
85
+ * Pure catalog lookup with fallback. Used by `t()` and directly by tests.
86
+ *
87
+ * @param catalog the active locale catalog (may be partial)
88
+ * @param fallback the fallback catalog (English — must contain every key)
89
+ * @param key dotted string id
90
+ * @param params optional `{name: value}` substitutions for `{name}` tokens
91
+ * @returns the translated string, or the key itself if missing from both
92
+ */
93
+ export function translate(catalog, fallback, key, params) {
94
+ let raw = catalog[key] ?? fallback[key] ?? key;
95
+ if (params) {
96
+ for (const [name, value] of Object.entries(params)) {
97
+ raw = raw.replaceAll(`{${name}}`, String(value));
98
+ }
99
+ }
100
+ return raw;
101
+ }
102
+ // ── Runtime state (populated by initI18n / setLocale) ───────────────
103
+ let currentLocale = DEFAULT_LOCALE;
104
+ let activeCatalog = {};
105
+ let enCatalog = {};
106
+ let initialized = false;
107
+ /**
108
+ * Monotonic request id for setLocale. Each invocation increments this; an
109
+ * in-flight request records its id and, after its awaited catalog fetch,
110
+ * commits state only if it is still the latest. This discards stale catalogs
111
+ * when language changes overlap (e.g. rapid de → en) so a slower earlier
112
+ * fetch can never overwrite a newer selection.
113
+ */
114
+ let localeReqId = 0;
115
+ /** Fetch a locale catalog JSON. Best-effort: returns {} on any failure. */
116
+ async function fetchCatalog(locale) {
117
+ try {
118
+ const res = await fetch(`/src/i18n/${locale}.json`, { credentials: 'same-origin' });
119
+ if (!res.ok)
120
+ return {};
121
+ const data = (await res.json());
122
+ return data && typeof data === 'object' ? data : {};
123
+ }
124
+ catch {
125
+ return {};
126
+ }
127
+ }
128
+ /** Apply the resolved locale to `<html lang>` so AT/browsers report it. */
129
+ function syncHtmlLang(locale) {
130
+ const el = globalThis.document?.documentElement;
131
+ if (el)
132
+ el.lang = locale;
133
+ }
134
+ /**
135
+ * Initialize i18n: resolve the locale, load the English fallback + the active
136
+ * catalog, set `<html lang>`, and apply `data-i18n` bindings in the document.
137
+ * Safe to call once at boot; subsequent calls re-apply translations.
138
+ */
139
+ export async function initI18n() {
140
+ if (!initialized) {
141
+ currentLocale = resolveLocale();
142
+ }
143
+ if (!Object.keys(enCatalog).length) {
144
+ enCatalog = await fetchCatalog('en');
145
+ }
146
+ if (currentLocale === 'en') {
147
+ activeCatalog = enCatalog;
148
+ }
149
+ else {
150
+ activeCatalog = await fetchCatalog(currentLocale);
151
+ }
152
+ initialized = true;
153
+ syncHtmlLang(currentLocale);
154
+ applyTranslations();
155
+ }
156
+ /** Current active locale. */
157
+ export function getLocale() {
158
+ return currentLocale;
159
+ }
160
+ /**
161
+ * Translate a key using the active locale with English fallback. Requires
162
+ * `initI18n()` to have completed; before that, returns the English value (or
163
+ * the key if English is also missing).
164
+ */
165
+ export function t(key, params) {
166
+ return translate(activeCatalog, enCatalog, key, params);
167
+ }
168
+ /**
169
+ * Persist a new locale choice, load its catalog, update `<html lang>`, and
170
+ * re-apply `data-i18n` bindings across the document. Falls back to `en` for
171
+ * unsupported values.
172
+ */
173
+ export async function setLocale(locale) {
174
+ const next = isSupported(locale) ? locale : DEFAULT_LOCALE;
175
+ const storage = safeLocalStorage();
176
+ if (storage) {
177
+ try {
178
+ storage.setItem(LOCALE_STORAGE_KEY, next);
179
+ }
180
+ catch { /* privacy mode */ }
181
+ }
182
+ currentLocale = next;
183
+ const myReqId = ++localeReqId;
184
+ if (!Object.keys(enCatalog).length) {
185
+ const en = await fetchCatalog('en');
186
+ // A newer setLocale superseded this one: do not assign enCatalog.
187
+ if (myReqId !== localeReqId)
188
+ return;
189
+ enCatalog = en;
190
+ }
191
+ const fetched = next === 'en' ? enCatalog : await fetchCatalog(next);
192
+ // Stale request: discard the fetched catalog so it can never overwrite a
193
+ // newer selection (e.g. a slow German fetch finishing after en was chosen).
194
+ if (myReqId !== localeReqId)
195
+ return;
196
+ activeCatalog = fetched;
197
+ initialized = true;
198
+ syncHtmlLang(next);
199
+ applyTranslations();
200
+ }
201
+ /**
202
+ * Walk the document for `data-i18n` attributes and bind translations. Three
203
+ * attribute flavors are supported:
204
+ * - `data-i18n="key"` → sets `textContent`
205
+ * - `data-i18n-html="key"` → sets `innerHTML` (for strings with markup)
206
+ * - `data-i18n-title="key"` → sets the `title` attribute
207
+ * - `data-i18n-placeholder="key"` → sets the `placeholder` attribute
208
+ * - `data-i18n-aria="key"` → sets the `aria-label` attribute
209
+ *
210
+ * `data-i18n-params` may carry a JSON object literal for parameterized keys.
211
+ */
212
+ export function applyTranslations(root) {
213
+ const doc = globalThis.document;
214
+ if (!doc)
215
+ return;
216
+ const scope = root ?? doc;
217
+ bindAttr(scope, '[data-i18n]', 'data-i18n', (el, v) => { el.textContent = v; });
218
+ bindAttr(scope, '[data-i18n-html]', 'data-i18n-html', (el, v) => { el.innerHTML = v; });
219
+ bindAttr(scope, '[data-i18n-title]', 'data-i18n-title', (el, v) => { el.setAttribute('title', v); });
220
+ bindAttr(scope, '[data-i18n-placeholder]', 'data-i18n-placeholder', (el, v) => { el.setAttribute('placeholder', v); });
221
+ bindAttr(scope, '[data-i18n-aria]', 'data-i18n-aria', (el, v) => { el.setAttribute('aria-label', v); });
222
+ }
223
+ function bindAttr(scope, selector, attr, setter) {
224
+ scope.querySelectorAll(selector).forEach((el) => {
225
+ const key = el.getAttribute(attr);
226
+ if (!key)
227
+ return;
228
+ const paramsJson = el.getAttribute('data-i18n-params');
229
+ const params = paramsJson ? safeParseParams(paramsJson) : undefined;
230
+ setter(el, t(key, params));
231
+ });
232
+ }
233
+ function safeParseParams(json) {
234
+ try {
235
+ const parsed = JSON.parse(json);
236
+ return parsed && typeof parsed === 'object' && !Array.isArray(parsed)
237
+ ? parsed
238
+ : undefined;
239
+ }
240
+ catch {
241
+ return undefined;
242
+ }
243
+ }
244
+ /**
245
+ * Map a known server error message (English, as sent on the wire) to the
246
+ * active locale's catalog. Unknown messages are returned unchanged so the
247
+ * user always sees something meaningful. This is the display-layer
248
+ * translation boundary for API/OIDC errors.
249
+ */
250
+ export function translateError(message) {
251
+ const reverse = errorReverseMap();
252
+ const key = reverse.get(message);
253
+ if (key)
254
+ return t(key);
255
+ return message;
256
+ }
257
+ // Lazy reverse map: English error text → catalog key. Built from enCatalog
258
+ // so it tracks the catalog without a hand-maintained second table.
259
+ let cachedErrorReverse = null;
260
+ function errorReverseMap() {
261
+ if (cachedErrorReverse)
262
+ return cachedErrorReverse;
263
+ const map = new Map();
264
+ for (const [key, value] of Object.entries(enCatalog)) {
265
+ if (key.startsWith('error.'))
266
+ map.set(value, key);
267
+ }
268
+ cachedErrorReverse = map;
269
+ return map;
270
+ }
271
+ /** Compile-time-complete mapping from supported locale → BCP 47 tag. */
272
+ const LOCALE_TAGS = {
273
+ en: 'en-US',
274
+ de: 'de-DE',
275
+ es: 'es-ES',
276
+ zh: 'zh-CN',
277
+ };
278
+ /**
279
+ * Format a date in the active locale (`de-DE` for de, `en-US` for en) using
280
+ * Intl.DateTimeFormat. Replaces hard-coded `toLocaleDateString('en-US', …)`.
281
+ */
282
+ export function localeDate(date, options) {
283
+ const localeTag = LOCALE_TAGS[currentLocale];
284
+ const d = date instanceof Date ? date : new Date(date);
285
+ return new Intl.DateTimeFormat(localeTag, options).format(d);
286
+ }
287
+ //# sourceMappingURL=i18n.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"i18n.js","sourceRoot":"","sources":["i18n.ts"],"names":[],"mappings":"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"}