diff --git a/CHANGELOG.md b/CHANGELOG.md index 33579396..3ce64c38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,8 @@ - feat: |邮件过滤| 移除后端 keyword 参数,改为前端过滤当前页邮件,优化查询性能 - feat: |数据库| 为 `message_id` 字段添加索引,优化邮件更新操作性能,需执行 `db/2025-12-15-message-id-index.sql` 更新数据库 - feat: |Admin| 维护页面增加自定义 SQL 清理功能,支持定时任务执行自定义清理语句 +- feat: |国际化| 后端 API 错误消息全面支持中英文国际化 +- feat: |Telegram| 机器人支持中英文切换,新增 `/lang` 命令设置语言偏好 ## v1.1.0 diff --git a/CHANGELOG_EN.md b/CHANGELOG_EN.md index d685fabf..1e931bec 100644 --- a/CHANGELOG_EN.md +++ b/CHANGELOG_EN.md @@ -19,6 +19,8 @@ - feat: |Email Filtering| Remove backend keyword parameter, switch to frontend filtering of current page emails, optimize query performance - feat: |Database| Add index for `message_id` field to optimize email update operations, need to execute `db/2025-12-15-message-id-index.sql` to update database - feat: |Admin| Add custom SQL cleanup feature to maintenance page, support scheduled task execution of custom cleanup statements +- feat: |i18n| Backend API error messages now fully support Chinese and English internationalization +- feat: |Telegram| Bot supports Chinese/English switching, add `/lang` command to set language preference ## v1.1.0 diff --git a/worker/src/admin_api/admin_user_api.ts b/worker/src/admin_api/admin_user_api.ts index 99a8b40a..617daf7b 100644 --- a/worker/src/admin_api/admin_user_api.ts +++ b/worker/src/admin_api/admin_user_api.ts @@ -14,23 +14,24 @@ export default { return c.json(settings) }, saveSetting: async (c: Context) => { + const msgs = i18n.getMessagesbyContext(c); const value = await c.req.json(); const settings = new UserSettings(value); if (settings.enableMailVerify && !c.env.KV) { - return c.text("Please enable KV first if you want to enable mail verify", 403) + return c.text(msgs.EnableKVForMailVerifyMsg, 403) } if (settings.enableMailVerify && !settings.verifyMailSender) { - return c.text("Please provide verifyMailSender", 400) + return c.text(msgs.VerifyMailSenderNotSetMsg, 400) } if (settings.enableMailVerify && settings.verifyMailSender) { const mailDomain = settings.verifyMailSender.split("@")[1]; const domains = getDomains(c); if (!domains.includes(mailDomain)) { - return c.text(`VerifyMailSender(${settings.verifyMailSender}) domain must in ${JSON.stringify(domains, null, 2)}`, 400) + return c.text(`${msgs.VerifyMailDomainInvalidMsg} ${JSON.stringify(domains, null, 2)}`, 400) } } if (settings.maxAddressCount < 0) { - return c.text("Invalid maxAddressCount", 400) + return c.text(msgs.InvalidMaxAddressCountMsg, 400) } await saveSetting(c, CONSTANTS.USER_SETTINGS_KEY, JSON.stringify(settings)); return c.json({ success: true }) @@ -60,9 +61,10 @@ export default { ); }, createUser: async (c: Context) => { + const msgs = i18n.getMessagesbyContext(c); const { email, password } = await c.req.json(); if (!email || !password) { - return c.text("Invalid email or password", 400) + return c.text(msgs.InvalidEmailOrPasswordMsg, 400) } // geo data const reqIp = c.req.raw.headers.get("cf-connecting-ip") @@ -77,14 +79,14 @@ export default { email, password, JSON.stringify(userInfo) ).run(); if (!success) { - return c.text("Failed to register", 500) + return c.text(msgs.FailedToRegisterMsg, 500) } } catch (e) { const errorMsg = (e as Error).message; if (errorMsg && errorMsg.includes("UNIQUE")) { - return c.text("User already exists", 400) + return c.text(msgs.UserAlreadyExistsMsg, 400) } - return c.text(`Failed to register: ${errorMsg}`, 500) + return c.text(`${msgs.FailedToRegisterMsg}: ${errorMsg}`, 500) } return c.json({ success: true }) }, @@ -99,7 +101,7 @@ export default { `DELETE FROM users_address WHERE user_id = ?` ).bind(user_id).run(); if (!success || !addressSuccess) { - return c.text("Failed to delete user", 500) + return c.text(msgs.FailedDeleteUserMsg, 500) } return c.json({ success: true }) }, @@ -114,28 +116,29 @@ export default { `UPDATE users SET password = ? WHERE id = ?` ).bind(password, user_id).run(); if (!success) { - return c.text("Failed to reset password", 500) + return c.text(msgs.FailedUpdatePasswordMsg, 500) } } catch (e) { - return c.text(`Failed to reset password: ${(e as Error).message}`, 500) + return c.text(`${msgs.FailedUpdatePasswordMsg}: ${(e as Error).message}`, 500) } return c.json({ success: true }); }, updateUserRoles: async (c: Context) => { + const msgs = i18n.getMessagesbyContext(c); const { user_id, role_text } = await c.req.json(); - if (!user_id) return c.text("Invalid user_id", 400); + if (!user_id) return c.text(msgs.InvalidUserIdMsg, 400); if (!role_text) { const { success } = await c.env.DB.prepare( `DELETE FROM user_roles WHERE user_id = ?` ).bind(user_id).run(); if (!success) { - return c.text("Failed to update user roles", 500) + return c.text(msgs.FailedUpdateUserDefaultRoleMsg, 500) } return c.json({ success: true }) } const user_roles = getUserRoles(c); if (!user_roles.find((r) => r.role === role_text)) { - return c.text("Invalid role_text", 400) + return c.text(msgs.InvalidRoleTextMsg, 400) } const { success } = await c.env.DB.prepare( `INSERT INTO user_roles (user_id, role_text)` @@ -143,7 +146,7 @@ export default { + ` ON CONFLICT(user_id) DO UPDATE SET role_text = ?, updated_at = datetime('now')` ).bind(user_id, role_text, role_text).run(); if (!success) { - return c.text("Failed to update user roles", 500) + return c.text(msgs.FailedUpdateUserDefaultRoleMsg, 500) } return c.json({ success: true }) }, diff --git a/worker/src/admin_api/cleanup_api.ts b/worker/src/admin_api/cleanup_api.ts index bb9a2711..ca19989f 100644 --- a/worker/src/admin_api/cleanup_api.ts +++ b/worker/src/admin_api/cleanup_api.ts @@ -4,6 +4,11 @@ import { cleanup } from '../common'; import { CONSTANTS } from '../constants'; import { getJsonSetting, saveSetting } from '../utils'; import { CleanupSettings, CustomSqlCleanup } from '../models'; +import i18n from '../i18n'; +import { LocaleMessages } from '../i18n/type'; + +// SQL validation error types +type SqlValidationError = 'empty' | 'too_long' | 'not_delete' | 'multiple_statements' | 'has_comments'; // Normalize SQL: trim and remove trailing semicolon const normalizeSql = (sql: string): string => { @@ -14,34 +19,45 @@ const normalizeSql = (sql: string): string => { return normalized; }; +// Get error message from error type +const getValidationErrorMsg = (errorType: SqlValidationError, msgs: LocaleMessages): string => { + switch (errorType) { + case 'empty': return msgs.SqlEmptyMsg; + case 'too_long': return msgs.SqlTooLongMsg; + case 'not_delete': return msgs.SqlOnlyDeleteMsg; + case 'multiple_statements': return msgs.SqlSingleStatementMsg; + case 'has_comments': return msgs.SqlNoCommentsMsg; + } +}; + // Validate custom SQL cleanup statement -export const validateCustomSql = (sql: string): { valid: boolean; error?: string } => { +export const validateCustomSql = (sql: string): { valid: boolean; errorType?: SqlValidationError } => { if (!sql || !sql.trim()) { - return { valid: false, error: "SQL statement is empty" }; + return { valid: false, errorType: 'empty' }; } const trimmedSql = normalizeSql(sql); // Check SQL length (max 1000 characters) if (trimmedSql.length > 1000) { - return { valid: false, error: "SQL statement is too long (max 1000 characters)" }; + return { valid: false, errorType: 'too_long' }; } const sqlUpper = trimmedSql.toUpperCase(); // Only allow DELETE statements if (!sqlUpper.startsWith('DELETE ')) { - return { valid: false, error: "Only DELETE statements are allowed" }; + return { valid: false, errorType: 'not_delete' }; } // Only allow single statement (no semicolons after trimming) if (trimmedSql.includes(';')) { - return { valid: false, error: "Only single SQL statement is allowed" }; + return { valid: false, errorType: 'multiple_statements' }; } // Forbid SQL comments if (/--/.test(trimmedSql) || /\/\*/.test(trimmedSql)) { - return { valid: false, error: "SQL comments are not allowed" }; + return { valid: false, errorType: 'has_comments' }; } return { valid: true }; @@ -52,13 +68,14 @@ export const executeCustomSqlCleanup = async ( c: Context, customSql: CustomSqlCleanup ): Promise<{ success: boolean; rowsAffected?: number; error?: string }> => { + const msgs = i18n.getMessagesbyContext(c); if (!customSql || !customSql.sql) { - return { success: false, error: "Invalid custom SQL cleanup config" }; + return { success: false, error: msgs.InvalidCleanupConfigMsg }; } const validation = validateCustomSql(customSql.sql); if (!validation.valid) { - return { success: false, error: validation.error }; + return { success: false, error: getValidationErrorMsg(validation.errorType!, msgs) }; } const sql = normalizeSql(customSql.sql); @@ -78,12 +95,13 @@ export const executeCustomSqlCleanup = async ( export default { cleanup: async (c: Context) => { + const msgs = i18n.getMessagesbyContext(c); const { cleanType, cleanDays } = await c.req.json(); try { await cleanup(c, cleanType, cleanDays); } catch (error) { console.error(error); - return c.text(`Failed to cleanup ${(error as Error).message}`, 500) + return c.text(`${msgs.OperationFailedMsg}: ${(error as Error).message}`, 500) } return c.json({ success: true }) }, @@ -92,6 +110,7 @@ export default { return c.json(cleanupSetting) }, saveCleanup: async (c: Context) => { + const msgs = i18n.getMessagesbyContext(c); const cleanupSetting = await c.req.json(); // Validate custom SQL cleanup list @@ -100,7 +119,8 @@ export default { if (customSql.sql) { const validation = validateCustomSql(customSql.sql); if (!validation.valid) { - return c.text(`Invalid SQL [${customSql.name || 'unnamed'}]: ${validation.error}`, 400); + const errorMsg = getValidationErrorMsg(validation.errorType!, msgs); + return c.text(`[${customSql.name || 'unnamed'}]: ${errorMsg}`, 400); } } } diff --git a/worker/src/admin_api/index.ts b/worker/src/admin_api/index.ts index b149d177..c5829842 100644 --- a/worker/src/admin_api/index.ts +++ b/worker/src/admin_api/index.ts @@ -45,10 +45,9 @@ api.get('/admin/address', async (c) => { api.post('/admin/new_address', async (c) => { const { name, domain, enablePrefix } = await c.req.json(); - const lang = c.get("lang") || c.env.DEFAULT_LANG; - const msgs = i18n.getMessages(lang); + const msgs = i18n.getMessagesbyContext(c); if (!name) { - return c.text("Please provide a name", 400) + return c.text(msgs.RequiredFieldMsg, 400) } try { const res = await newAddress(c, { @@ -67,19 +66,20 @@ api.post('/admin/new_address', async (c) => { }) api.delete('/admin/delete_address/:id', async (c) => { + const msgs = i18n.getMessagesbyContext(c); const { id } = c.req.param(); const { success } = await c.env.DB.prepare( `DELETE FROM address WHERE id = ? ` ).bind(id).run(); if (!success) { - return c.text("Failed to delete address", 500) + return c.text(msgs.OperationFailedMsg, 500) } const { success: mailSuccess } = await c.env.DB.prepare( `DELETE FROM raw_mails WHERE address IN` + ` (select name from address where id = ?) ` ).bind(id).run(); if (!mailSuccess) { - return c.text("Failed to delete mails", 500) + return c.text(msgs.OperationFailedMsg, 500) } const { success: sendAccess } = await c.env.DB.prepare( `DELETE FROM address_sender WHERE address IN` @@ -94,13 +94,14 @@ api.delete('/admin/delete_address/:id', async (c) => { }) api.delete('/admin/clear_inbox/:id', async (c) => { + const msgs = i18n.getMessagesbyContext(c); const { id } = c.req.param(); const { success: mailSuccess } = await c.env.DB.prepare( `DELETE FROM raw_mails WHERE address IN` + ` (select name from address where id = ?) ` ).bind(id).run(); if (!mailSuccess) { - return c.text("Failed to clear inbox", 500) + return c.text(msgs.OperationFailedMsg, 500) } return c.json({ success: mailSuccess @@ -108,13 +109,14 @@ api.delete('/admin/clear_inbox/:id', async (c) => { }) api.delete('/admin/clear_sent_items/:id', async (c) => { + const msgs = i18n.getMessagesbyContext(c); const { id } = c.req.param(); const { success: sendboxSuccess } = await c.env.DB.prepare( `DELETE FROM sendbox WHERE address IN` + ` (select name from address where id = ?) ` ).bind(id).run(); if (!sendboxSuccess) { - return c.text("Failed to clear sent items", 500) + return c.text(msgs.OperationFailedMsg, 500) } return c.json({ success: sendboxSuccess @@ -136,15 +138,16 @@ api.get('/admin/show_password/:id', async (c) => { }) api.post('/admin/address/:id/reset_password', async (c) => { + const msgs = i18n.getMessagesbyContext(c); const { id } = c.req.param(); const { password } = await c.req.json(); // 检查功能是否启用 if (!getBooleanValue(c.env.ENABLE_ADDRESS_PASSWORD)) { - return c.text("Password management is disabled", 403); + return c.text(msgs.PasswordChangeDisabledMsg, 403); } if (!password) { - return c.text("Password is required", 400); + return c.text(msgs.NewPasswordRequiredMsg, 400); } const hashedPassword = await hashPassword(password); @@ -153,7 +156,7 @@ api.post('/admin/address/:id/reset_password', async (c) => { ).bind(hashedPassword, id).run(); if (!success) { - return c.text("Failed to reset password", 500); + return c.text(msgs.FailedUpdatePasswordMsg, 500); } return c.json({ success: true }); @@ -181,18 +184,19 @@ api.get('/admin/address_sender', async (c) => { }) api.post('/admin/address_sender', async (c) => { + const msgs = i18n.getMessagesbyContext(c); /* eslint-disable prefer-const */ let { address, address_id, balance, enabled } = await c.req.json(); /* eslint-enable prefer-const */ if (!address_id) { - return c.text("Invalid address_id", 400) + return c.text(msgs.InvalidAddressIdMsg, 400) } enabled = enabled ? 1 : 0; const { success } = await c.env.DB.prepare( `UPDATE address_sender SET enabled = ?, balance = ? WHERE id = ? ` ).bind(enabled, balance, address_id).run(); if (!success) { - return c.text("Failed to update address sender", 500) + return c.text(msgs.OperationFailedMsg, 500) } await sendAdminInternalMail( c, address, "Account Send Access Updated", @@ -291,16 +295,17 @@ api.get('/admin/account_settings', async (c) => { }) api.post('/admin/account_settings', async (c) => { + const msgs = i18n.getMessagesbyContext(c); /** @type {{ blockList: Array, sendBlockList: Array }} */ const { blockList, sendBlockList, noLimitSendAddressList, verifiedAddressList, fromBlockList, emailRuleSettings } = await c.req.json(); if (!blockList || !sendBlockList || !verifiedAddressList) { - return c.text("Invalid blockList or sendBlockList", 400) + return c.text(msgs.InvalidInputMsg, 400) } if (!c.env.SEND_MAIL && verifiedAddressList.length > 0) { - return c.text("Please enable SEND_MAIL to use verifiedAddressList", 400) + return c.text(msgs.EnableSendMailMsg, 400) } await saveSetting( c, CONSTANTS.ADDRESS_BLOCK_LIST_KEY, @@ -315,7 +320,7 @@ api.post('/admin/account_settings', async (c) => { JSON.stringify(verifiedAddressList) ) if (fromBlockList?.length > 0 && !c.env.KV) { - return c.text("Please enable KV to use fromBlockList", 400) + return c.text(msgs.EnableKVMsg, 400) } if (fromBlockList) { await c.env.KV.put(CONSTANTS.EMAIL_KV_BLACK_LIST, JSON.stringify(fromBlockList || [])) diff --git a/worker/src/admin_api/ip_blacklist_settings.ts b/worker/src/admin_api/ip_blacklist_settings.ts index da88addf..6f8423c8 100644 --- a/worker/src/admin_api/ip_blacklist_settings.ts +++ b/worker/src/admin_api/ip_blacklist_settings.ts @@ -2,6 +2,7 @@ import { Context } from "hono"; import { CONSTANTS } from "../constants"; import { getJsonSetting, saveSetting } from "../utils"; import { IpBlacklistSettings } from "../ip_blacklist"; +import i18n from "../i18n"; /** * Get IP blacklist settings from database @@ -26,55 +27,47 @@ async function getIpBlacklistSettings(c: Context): Promise): Promise { + const msgs = i18n.getMessagesbyContext(c); const settings = await c.req.json(); // Validate settings if (typeof settings.enabled !== 'boolean') { - return c.text("Invalid enabled value", 400); + return c.text(`${msgs.InvalidIpBlacklistSettingMsg}: enabled`, 400); } if (!Array.isArray(settings.blacklist)) { - return c.text("Invalid blacklist value", 400); + return c.text(`${msgs.InvalidIpBlacklistSettingMsg}: blacklist`, 400); } if (!Array.isArray(settings.asnBlacklist)) { - return c.text("Invalid asnBlacklist value", 400); + return c.text(`${msgs.InvalidIpBlacklistSettingMsg}: asnBlacklist`, 400); } if (!Array.isArray(settings.fingerprintBlacklist)) { - return c.text("Invalid fingerprintBlacklist value", 400); + return c.text(`${msgs.InvalidIpBlacklistSettingMsg}: fingerprintBlacklist`, 400); } if (typeof settings.enableDailyLimit !== 'boolean') { - return c.text("Invalid enableDailyLimit value", 400); + return c.text(`${msgs.InvalidIpBlacklistSettingMsg}: enableDailyLimit`, 400); } const limit = Number(settings.dailyRequestLimit); if (isNaN(limit) || limit < 1 || limit > 1000000) { - return c.text("Invalid dailyRequestLimit value (must be between 1 and 1000000)", 400); + return c.text(`${msgs.InvalidIpBlacklistSettingMsg}: dailyRequestLimit (1-1000000)`, 400); } // Add size limit const MAX_BLACKLIST_SIZE = 1000; if (settings.blacklist.length > MAX_BLACKLIST_SIZE) { - return c.text( - `Blacklist exceeds maximum size (${MAX_BLACKLIST_SIZE} entries)`, - 400 - ); + return c.text(`${msgs.BlacklistExceedsMaxSizeMsg}: blacklist (${MAX_BLACKLIST_SIZE})`, 400); } if (settings.asnBlacklist.length > MAX_BLACKLIST_SIZE) { - return c.text( - `ASN blacklist exceeds maximum size (${MAX_BLACKLIST_SIZE} entries)`, - 400 - ); + return c.text(`${msgs.BlacklistExceedsMaxSizeMsg}: asnBlacklist (${MAX_BLACKLIST_SIZE})`, 400); } if (settings.fingerprintBlacklist.length > MAX_BLACKLIST_SIZE) { - return c.text( - `Fingerprint blacklist exceeds maximum size (${MAX_BLACKLIST_SIZE} entries)`, - 400 - ); + return c.text(`${msgs.BlacklistExceedsMaxSizeMsg}: fingerprintBlacklist (${MAX_BLACKLIST_SIZE})`, 400); } // Sanitize patterns (trim and remove empty strings) diff --git a/worker/src/common.ts b/worker/src/common.ts index db1f1b49..de70992a 100644 --- a/worker/src/common.ts +++ b/worker/src/common.ts @@ -5,6 +5,7 @@ import { getBooleanValue, getDomains, getStringValue, getIntValue, getUserRoles, import { unbindTelegramByAddress } from './telegram_api/common'; import { CONSTANTS } from './constants'; import { AdminWebhookSettings, WebhookMail, WebhookSettings } from './models'; +import i18n from './i18n'; const DEFAULT_NAME_REGEX = /[^a-z0-9]/g; @@ -134,6 +135,7 @@ export const newAddress = async ( sourceMeta?: string | undefined | null, } ): Promise<{ address: string, jwt: string, password?: string | null }> => { + const msgs = i18n.getMessagesbyContext(c); // trim whitespace and remove special characters name = name.trim().replace(getNameRegex(c), '') // check name @@ -153,10 +155,10 @@ export const newAddress = async ( ); // check name length if (name.length < minAddressLength) { - throw new Error(`Name too short (min ${minAddressLength})`); + throw new Error(`${msgs.NameTooShortMsg} (min ${minAddressLength})`); } if (name.length > maxAddressLength) { - throw new Error(`Name too long (max ${maxAddressLength})`); + throw new Error(`${msgs.NameTooLongMsg} (max ${maxAddressLength})`); } // create address with prefix if (typeof addressPrefix === "string") { @@ -177,7 +179,7 @@ export const newAddress = async ( } // check domain is valid if (!domain || !allowDomains.includes(domain)) { - throw new Error("Invalid domain") + throw new Error(msgs.InvalidDomainMsg) } // create address name = name + "@" + domain; @@ -187,7 +189,7 @@ export const newAddress = async ( `INSERT INTO address(name, source_meta) VALUES(?, ?)` ).bind(name, sourceMeta).run(); if (!result.success) { - throw new Error("Failed to create address") + throw new Error(msgs.FailedCreateAddressMsg) } await updateAddressUpdatedAt(c, name); } catch (e) { @@ -198,13 +200,13 @@ export const newAddress = async ( `INSERT INTO address(name) VALUES(?)` ).bind(name).run(); if (!result.success) { - throw new Error("Failed to create address") + throw new Error(msgs.FailedCreateAddressMsg) } await updateAddressUpdatedAt(c, name); } else if (message && message.includes("UNIQUE")) { - throw new Error("Address already exists") + throw new Error(msgs.AddressAlreadyExistsMsg) } else { - throw new Error("Failed to create address") + throw new Error(msgs.FailedCreateAddressMsg) } } const address_id = await c.env.DB.prepare( @@ -247,8 +249,9 @@ export const cleanup = async ( cleanType: string | undefined | null, cleanDays: number | undefined | null ): Promise => { + const msgs = i18n.getMessagesbyContext(c); if (!cleanType || typeof cleanDays !== 'number' || cleanDays < 0 || cleanDays > 1000) { - throw new Error("Invalid cleanType or cleanDays") + throw new Error(msgs.InvalidCleanupConfigMsg) } console.log(`Cleanup ${cleanType} before ${cleanDays} days`); switch (cleanType) { @@ -294,7 +297,7 @@ export const cleanup = async ( ) break; default: - throw new Error("Invalid cleanType") + throw new Error(msgs.InvalidCleanTypeMsg) } return true; } @@ -336,11 +339,12 @@ export const deleteAddressWithData = async ( address: string | undefined | null, address_id: number | undefined | null ): Promise => { + const msgs = i18n.getMessagesbyContext(c); if (!getBooleanValue(c.env.ENABLE_USER_DELETE_EMAIL)) { - throw new Error("Delete email is disabled") + throw new Error(msgs.UserDeleteEmailDisabledMsg) } if (!address && !address_id) { - throw new Error("Address or address_id required") + throw new Error(msgs.RequiredFieldMsg) } // get address_id or address if (!address_id) { @@ -354,7 +358,7 @@ export const deleteAddressWithData = async ( } // check address again if (!address || !address_id) { - throw new Error("Can't find address"); + throw new Error(msgs.AddressNotFoundMsg); } // unbind telegram await unbindTelegramByAddress(c, address); @@ -378,7 +382,7 @@ export const deleteAddressWithData = async ( `DELETE FROM address WHERE name = ? ` ).bind(address).run(); if (!success || !mailSuccess || !sendboxSuccess || !addressSuccess || !sendAccess || !autoReplySuccess) { - throw new Error("Failed to delete address") + throw new Error(msgs.OperationFailedMsg) } return true; } @@ -389,6 +393,7 @@ export const handleListQuery = async ( limit: string | number | undefined | null, offset: string | number | undefined | null ): Promise => { + const msgs = i18n.getMessagesbyContext(c); if (typeof limit === "string") { limit = parseInt(limit); } @@ -396,10 +401,10 @@ export const handleListQuery = async ( offset = parseInt(offset); } if (!limit || limit < 0 || limit > 100) { - return c.text("Invalid limit", 400) + return c.text(msgs.InvalidLimitMsg, 400) } if (offset == null || offset == undefined || offset < 0) { - return c.text("Invalid offset", 400) + return c.text(msgs.InvalidOffsetMsg, 400) } const resultsQuery = `${query} order by id desc limit ? offset ?`; const { results } = await c.env.DB.prepare(resultsQuery).bind( diff --git a/worker/src/i18n/en.ts b/worker/src/i18n/en.ts index 398d5c8c..cbb9d323 100644 --- a/worker/src/i18n/en.ts +++ b/worker/src/i18n/en.ts @@ -46,6 +46,129 @@ const messages: LocaleMessages = { PasswordLoginDisabledMsg: "Password login is disabled", EmailPasswordRequiredMsg: "Email and password are required", AddressNotFoundMsg: "Address not found", + + // Common messages (merged similar ones) + OperationFailedMsg: "Operation failed", + RequiredFieldMsg: "Required field is missing", + InvalidInputMsg: "Invalid input", + + // Address related + NameTooShortMsg: "Name is too short", + NameTooLongMsg: "Name is too long", + InvalidDomainMsg: "Invalid domain", + AddressAlreadyExistsMsg: "Address already exists", + MaxAddressCountReachedMsg: "Max address count reached", + AddressNotBindedMsg: "Address is not binded", + AddressAlreadyBindedMsg: "Address is already binded, please unbind first", + TargetUserNotFoundMsg: "Target user not found", + + // Send mail related + NoBalanceMsg: "No balance", + AddressBlockedMsg: "Address is blocked", + SubjectEmptyMsg: "Subject is empty", + ContentEmptyMsg: "Content is empty", + AlreadyRequestedMsg: "Already requested", + EnableResendOrSmtpMsg: "Please enable resend or smtp for this domain", + EnableResendOrSmtpWithVerifiedMsg: "Please enable resend or smtp for this domain, or add recipient to verified address list", + InvalidToMailMsg: "Invalid recipient address", + + // Admin related + InvalidAddressIdMsg: "Invalid address_id", + EnableKVMsg: "Please enable KV first", + EnableSendMailMsg: "Please enable SEND_MAIL first", + InvalidCleanupConfigMsg: "Invalid cleanType or cleanDays", + InvalidCleanTypeMsg: "Invalid cleanType", + EnableKVForMailVerifyMsg: "Please enable KV first if you want to enable mail verify", + VerifyMailDomainInvalidMsg: "VerifyMailSender domain must be in", + InvalidMaxAddressCountMsg: "Invalid maxAddressCount", + FailedDeleteUserMsg: "Failed to delete user", + InvalidUserIdMsg: "Invalid user_id", + InvalidRoleTextMsg: "Invalid role_text", + + // SQL validation + SqlEmptyMsg: "SQL statement is empty", + SqlTooLongMsg: "SQL statement is too long (max 1000 characters)", + SqlOnlyDeleteMsg: "Only DELETE statements are allowed", + SqlSingleStatementMsg: "Only single SQL statement is allowed", + SqlNoCommentsMsg: "SQL comments are not allowed", + + // Passkey related + InvalidPasskeyNameMsg: "Invalid passkey name", + PasskeyNotFoundMsg: "Passkey not found", + AuthenticationFailedMsg: "Authentication failed", + RegistrationFailedMsg: "Registration failed", + + // Auto reply related + AutoReplyDisabledMsg: "Auto reply is disabled", + InvalidAutoReplyMsg: "Invalid subject or message", + SubjectOrMessageTooLongMsg: "Subject or message is too long", + + // Bind address related + NoAddressOrUserTokenMsg: "No address or user token", + InvalidAddressOrUserTokenMsg: "Invalid address or user token", + + // Pagination related + InvalidLimitMsg: "Invalid limit", + InvalidOffsetMsg: "Invalid offset", + + // Clear inbox/sent items related + FailedClearInboxMsg: "Failed to clear inbox", + FailedClearSentItemsMsg: "Failed to clear sent items", + + // Webhook related + WebhookNotAllowedForUserMsg: "Webhook settings is not allowed for this user", + + // IP blacklist related + InvalidIpBlacklistSettingMsg: "Invalid IP blacklist setting", + BlacklistExceedsMaxSizeMsg: "Blacklist exceeds maximum size", + + // Telegram bot messages + TgUnableGetUserInfoMsg: "Unable to get user info", + TgNoPermissionMsg: "You don't have permission to use this bot", + TgWelcomeMsg: "Welcome! You can open the mini app", + TgCurrentPrefixMsg: "Current prefix enabled:", + TgCurrentDomainsMsg: "Available domains:", + TgAvailableCommandsMsg: "Available commands:", + TgCreateSuccessMsg: "Address created successfully:", + TgCreateFailedMsg: "Failed to create address:", + TgBindSuccessMsg: "Binding successful:", + TgBindFailedMsg: "Binding failed:", + TgUnbindSuccessMsg: "Unbinding successful:", + TgUnbindFailedMsg: "Unbinding failed:", + TgDeleteSuccessMsg: "Deleted successfully:", + TgDeleteFailedMsg: "Delete failed:", + TgAddressListMsg: "Address list:", + TgGetAddressFailedMsg: "Failed to get address list:", + TgCleanSuccessMsg: "Invalid addresses cleaned:", + TgCurrentAddressListMsg: "Current address list:", + TgCleanFailedMsg: "Failed to clean invalid addresses:", + TgNotBoundAddressMsg: "This address is not bound:", + TgInvalidAddressMsg: "Invalid address", + TgNoMoreMailsMsg: "No more mails", + TgNoMailMsg: "No mail", + TgGetMailFailedMsg: "Failed to get mail:", + TgParseMailFailedMsg: "Failed to parse mail:", + TgViewMailBtnMsg: "View Mail", + TgPrevBtnMsg: "Prev", + TgNextBtnMsg: "Next", + TgPleaseInputCredentialMsg: "Please enter credential", + TgPleaseInputAddressMsg: "Please enter address", + TgAddressMsg: "Address:", + TgPasswordMsg: "Password:", + TgCredentialMsg: "Credential:", + TgNoSenderMsg: "No sender", + TgMsgTooLongMsg: "Message too long, please view in mini app", + TgParseFailedViewInAppMsg: "Parse failed, please view in mini app", + TgMaxAddressReachedMsg: "Maximum address limit reached", + TgMaxAddressReachedCleanMsg: "Maximum address limit reached, please /cleaninvalidaddress first", + TgInvalidCredentialMsg: "Invalid credential", + TgAddressNotYoursMsg: "This address does not belong to you", + TgLangSetSuccessMsg: "Language set successfully:", + TgCurrentLangMsg: "Current language:", + TgSelectLangMsg: "Please select language:", + TgNoPermissionViewMailMsg: "No permission to view this mail", + TgBotTokenRequiredMsg: "TELEGRAM_BOT_TOKEN is required", + TgLangFeatureDisabledMsg: "Language setting feature is disabled. System default language is used.", } export default messages; diff --git a/worker/src/i18n/type.ts b/worker/src/i18n/type.ts index ccf76188..ac92c1a3 100644 --- a/worker/src/i18n/type.ts +++ b/worker/src/i18n/type.ts @@ -44,4 +44,127 @@ export type LocaleMessages = { PasswordLoginDisabledMsg: string EmailPasswordRequiredMsg: string AddressNotFoundMsg: string + + // Common messages (merged similar ones) + OperationFailedMsg: string + RequiredFieldMsg: string + InvalidInputMsg: string + + // Address related + NameTooShortMsg: string + NameTooLongMsg: string + InvalidDomainMsg: string + AddressAlreadyExistsMsg: string + MaxAddressCountReachedMsg: string + AddressNotBindedMsg: string + AddressAlreadyBindedMsg: string + TargetUserNotFoundMsg: string + + // Send mail related + NoBalanceMsg: string + AddressBlockedMsg: string + SubjectEmptyMsg: string + ContentEmptyMsg: string + AlreadyRequestedMsg: string + EnableResendOrSmtpMsg: string + EnableResendOrSmtpWithVerifiedMsg: string + InvalidToMailMsg: string + + // Admin related + InvalidAddressIdMsg: string + EnableKVMsg: string + EnableSendMailMsg: string + InvalidCleanupConfigMsg: string + InvalidCleanTypeMsg: string + EnableKVForMailVerifyMsg: string + VerifyMailDomainInvalidMsg: string + InvalidMaxAddressCountMsg: string + FailedDeleteUserMsg: string + InvalidUserIdMsg: string + InvalidRoleTextMsg: string + + // SQL validation + SqlEmptyMsg: string + SqlTooLongMsg: string + SqlOnlyDeleteMsg: string + SqlSingleStatementMsg: string + SqlNoCommentsMsg: string + + // Passkey related + InvalidPasskeyNameMsg: string + PasskeyNotFoundMsg: string + AuthenticationFailedMsg: string + RegistrationFailedMsg: string + + // Auto reply related + AutoReplyDisabledMsg: string + InvalidAutoReplyMsg: string + SubjectOrMessageTooLongMsg: string + + // Bind address related + NoAddressOrUserTokenMsg: string + InvalidAddressOrUserTokenMsg: string + + // Pagination related + InvalidLimitMsg: string + InvalidOffsetMsg: string + + // Clear inbox/sent items related + FailedClearInboxMsg: string + FailedClearSentItemsMsg: string + + // Webhook related + WebhookNotAllowedForUserMsg: string + + // IP blacklist related + InvalidIpBlacklistSettingMsg: string + BlacklistExceedsMaxSizeMsg: string + + // Telegram bot messages + TgUnableGetUserInfoMsg: string + TgNoPermissionMsg: string + TgWelcomeMsg: string + TgCurrentPrefixMsg: string + TgCurrentDomainsMsg: string + TgAvailableCommandsMsg: string + TgCreateSuccessMsg: string + TgCreateFailedMsg: string + TgBindSuccessMsg: string + TgBindFailedMsg: string + TgUnbindSuccessMsg: string + TgUnbindFailedMsg: string + TgDeleteSuccessMsg: string + TgDeleteFailedMsg: string + TgAddressListMsg: string + TgGetAddressFailedMsg: string + TgCleanSuccessMsg: string + TgCurrentAddressListMsg: string + TgCleanFailedMsg: string + TgNotBoundAddressMsg: string + TgInvalidAddressMsg: string + TgNoMoreMailsMsg: string + TgNoMailMsg: string + TgGetMailFailedMsg: string + TgParseMailFailedMsg: string + TgViewMailBtnMsg: string + TgPrevBtnMsg: string + TgNextBtnMsg: string + TgPleaseInputCredentialMsg: string + TgPleaseInputAddressMsg: string + TgAddressMsg: string + TgPasswordMsg: string + TgCredentialMsg: string + TgNoSenderMsg: string + TgMsgTooLongMsg: string + TgParseFailedViewInAppMsg: string + TgMaxAddressReachedMsg: string + TgMaxAddressReachedCleanMsg: string + TgInvalidCredentialMsg: string + TgAddressNotYoursMsg: string + TgLangSetSuccessMsg: string + TgCurrentLangMsg: string + TgSelectLangMsg: string + TgNoPermissionViewMailMsg: string + TgBotTokenRequiredMsg: string + TgLangFeatureDisabledMsg: string } diff --git a/worker/src/i18n/zh.ts b/worker/src/i18n/zh.ts index 04bf9f66..7cce2d1a 100644 --- a/worker/src/i18n/zh.ts +++ b/worker/src/i18n/zh.ts @@ -46,6 +46,129 @@ const messages: LocaleMessages = { PasswordLoginDisabledMsg: "密码登录已禁用", EmailPasswordRequiredMsg: "邮箱和密码不能为空", AddressNotFoundMsg: "邮箱地址不存在", + + // Common messages (merged similar ones) + OperationFailedMsg: "操作失败", + RequiredFieldMsg: "缺少必填字段", + InvalidInputMsg: "输入无效", + + // Address related + NameTooShortMsg: "名称太短", + NameTooLongMsg: "名称太长", + InvalidDomainMsg: "无效的域名", + AddressAlreadyExistsMsg: "邮箱地址已存在", + MaxAddressCountReachedMsg: "已达到最大地址数量限制", + AddressNotBindedMsg: "邮箱地址未绑定", + AddressAlreadyBindedMsg: "邮箱地址已绑定, 请先解绑", + TargetUserNotFoundMsg: "目标用户不存在", + + // Send mail related + NoBalanceMsg: "余额不足", + AddressBlockedMsg: "地址已被屏蔽", + SubjectEmptyMsg: "主题不能为空", + ContentEmptyMsg: "内容不能为空", + AlreadyRequestedMsg: "已经申请过了", + EnableResendOrSmtpMsg: "请先为此域名启用 resend 或 smtp", + EnableResendOrSmtpWithVerifiedMsg: "请先为此域名启用 resend 或 smtp,或将收件人添加到已验证地址列表", + InvalidToMailMsg: "收件人地址无效", + + // Admin related + InvalidAddressIdMsg: "无效的 address_id", + EnableKVMsg: "请先启用 KV", + EnableSendMailMsg: "请先启用 SEND_MAIL", + InvalidCleanupConfigMsg: "无效的 cleanType 或 cleanDays", + InvalidCleanTypeMsg: "无效的 cleanType", + EnableKVForMailVerifyMsg: "如果要启用邮件验证,请先启用 KV", + VerifyMailDomainInvalidMsg: "验证邮件发送者域名必须在", + InvalidMaxAddressCountMsg: "无效的 maxAddressCount", + FailedDeleteUserMsg: "删除用户失败", + InvalidUserIdMsg: "无效的 user_id", + InvalidRoleTextMsg: "无效的 role_text", + + // SQL validation + SqlEmptyMsg: "SQL 语句为空", + SqlTooLongMsg: "SQL 语句过长 (最大 1000 字符)", + SqlOnlyDeleteMsg: "只允许 DELETE 语句", + SqlSingleStatementMsg: "只允许单条 SQL 语句", + SqlNoCommentsMsg: "不允许 SQL 注释", + + // Passkey related + InvalidPasskeyNameMsg: "无效的 passkey 名称", + PasskeyNotFoundMsg: "Passkey 不存在", + AuthenticationFailedMsg: "认证失败", + RegistrationFailedMsg: "注册失败", + + // Auto reply related + AutoReplyDisabledMsg: "自动回复已禁用", + InvalidAutoReplyMsg: "无效的主题或消息", + SubjectOrMessageTooLongMsg: "主题或消息太长", + + // Bind address related + NoAddressOrUserTokenMsg: "缺少地址或用户令牌", + InvalidAddressOrUserTokenMsg: "无效的地址或用户令牌", + + // Pagination related + InvalidLimitMsg: "无效的 limit 参数", + InvalidOffsetMsg: "无效的 offset 参数", + + // Clear inbox/sent items related + FailedClearInboxMsg: "清空收件箱失败", + FailedClearSentItemsMsg: "清空已发送邮件失败", + + // Webhook related + WebhookNotAllowedForUserMsg: "此用户不允许使用 Webhook 设置", + + // IP blacklist related + InvalidIpBlacklistSettingMsg: "无效的 IP 黑名单设置", + BlacklistExceedsMaxSizeMsg: "黑名单超出最大条目限制", + + // Telegram bot messages + TgUnableGetUserInfoMsg: "无法获取用户信息", + TgNoPermissionMsg: "您没有权限使用此机器人", + TgWelcomeMsg: "欢迎使用本机器人, 您可以打开 mini app", + TgCurrentPrefixMsg: "当前已启用前缀:", + TgCurrentDomainsMsg: "当前可用域名:", + TgAvailableCommandsMsg: "请使用以下命令:", + TgCreateSuccessMsg: "创建地址成功:", + TgCreateFailedMsg: "创建地址失败:", + TgBindSuccessMsg: "绑定成功:", + TgBindFailedMsg: "绑定失败:", + TgUnbindSuccessMsg: "解绑成功:", + TgUnbindFailedMsg: "解绑失败:", + TgDeleteSuccessMsg: "删除成功:", + TgDeleteFailedMsg: "删除失败:", + TgAddressListMsg: "地址列表:", + TgGetAddressFailedMsg: "获取地址列表失败:", + TgCleanSuccessMsg: "清理无效地址成功:", + TgCurrentAddressListMsg: "当前地址列表:", + TgCleanFailedMsg: "清理无效地址失败:", + TgNotBoundAddressMsg: "未绑定此地址:", + TgInvalidAddressMsg: "无效地址", + TgNoMoreMailsMsg: "已经没有邮件了", + TgNoMailMsg: "无邮件", + TgGetMailFailedMsg: "获取邮件失败:", + TgParseMailFailedMsg: "解析邮件失败:", + TgViewMailBtnMsg: "查看邮件", + TgPrevBtnMsg: "上一条", + TgNextBtnMsg: "下一条", + TgPleaseInputCredentialMsg: "请输入凭证", + TgPleaseInputAddressMsg: "请输入地址", + TgAddressMsg: "地址:", + TgPasswordMsg: "密码:", + TgCredentialMsg: "凭证:", + TgNoSenderMsg: "无发件人", + TgMsgTooLongMsg: "消息过长请到 mini app 查看", + TgParseFailedViewInAppMsg: "解析失败,请打开 mini app 查看", + TgMaxAddressReachedMsg: "绑定地址数量已达上限", + TgMaxAddressReachedCleanMsg: "绑定地址数量已达上限, 请先 /cleaninvalidaddress", + TgInvalidCredentialMsg: "无效凭证", + TgAddressNotYoursMsg: "此地址不属于您", + TgLangSetSuccessMsg: "语言设置成功:", + TgCurrentLangMsg: "当前语言:", + TgSelectLangMsg: "请选择语言:", + TgNoPermissionViewMailMsg: "无权查看此邮件", + TgBotTokenRequiredMsg: "需要设置 TELEGRAM_BOT_TOKEN", + TgLangFeatureDisabledMsg: "语言设置功能已禁用,使用系统默认语言", } export default messages; diff --git a/worker/src/mails_api/address_auth.ts b/worker/src/mails_api/address_auth.ts index 06ba2134..aa5121d6 100644 --- a/worker/src/mails_api/address_auth.ts +++ b/worker/src/mails_api/address_auth.ts @@ -7,8 +7,7 @@ export default { // 修改地址密码 changePassword: async (c: Context) => { const { new_password } = await c.req.json(); - const lang = c.get("lang") || c.env.DEFAULT_LANG; - const msgs = i18n.getMessages(lang); + const msgs = i18n.getMessagesbyContext(c); const { address, address_id } = c.get("jwtPayload"); // 检查功能是否启用 @@ -39,8 +38,7 @@ export default { // 地址密码登录 login: async (c: Context) => { const { email, password, cf_token } = await c.req.json(); - const lang = c.get("lang") || c.env.DEFAULT_LANG; - const msgs = i18n.getMessages(lang); + const msgs = i18n.getMessagesbyContext(c); // 检查功能是否启用 if (!getBooleanValue(c.env.ENABLE_ADDRESS_PASSWORD)) { diff --git a/worker/src/mails_api/auto_reply.ts b/worker/src/mails_api/auto_reply.ts index 0b461b5e..75c9e7cd 100644 --- a/worker/src/mails_api/auto_reply.ts +++ b/worker/src/mails_api/auto_reply.ts @@ -1,11 +1,13 @@ import { Context } from "hono"; import { getBooleanValue } from "../utils"; +import i18n from "../i18n"; export default { getAutoReply: async (c: Context) => { + const msgs = i18n.getMessagesbyContext(c); if (!getBooleanValue(c.env.ENABLE_AUTO_REPLY)) { - return c.text("Auto reply is disabled", 403) + return c.text(msgs.AutoReplyDisabledMsg, 403) } const { address } = c.get("jwtPayload") const results = await c.env.DB.prepare( @@ -23,17 +25,18 @@ export default { }) }, saveAutoReply: async (c: Context) => { + const msgs = i18n.getMessagesbyContext(c); if (!getBooleanValue(c.env.ENABLE_AUTO_REPLY)) { - return c.text("Auto reply is disabled", 403) + return c.text(msgs.AutoReplyDisabledMsg, 403) } const { address } = c.get("jwtPayload"); const { auto_reply } = await c.req.json(); const { name, subject, source_prefix, message, enabled } = auto_reply; if ((!subject || !message) && enabled) { - return c.text("Invalid subject or message", 400) + return c.text(msgs.InvalidAutoReplyMsg, 400) } else if (subject.length > 255 || message.length > 255) { - return c.text("Subject or message too long", 400) + return c.text(msgs.SubjectOrMessageTooLongMsg, 400) } const { success } = await c.env.DB.prepare( `INSERT OR REPLACE INTO auto_reply_mails` @@ -44,7 +47,7 @@ export default { subject || '', message || '', enabled ? 1 : 0 ).run(); if (!success) { - return c.text("Failed to auto_reply settings", 500) + return c.text(msgs.OperationFailedMsg, 500) } return c.json({ success: success diff --git a/worker/src/mails_api/index.ts b/worker/src/mails_api/index.ts index a4a8d5d8..a7889d4e 100644 --- a/worker/src/mails_api/index.ts +++ b/worker/src/mails_api/index.ts @@ -45,8 +45,7 @@ api.get('/api/mail/:mail_id', async (c) => { }) api.delete('/api/mails/:id', async (c) => { - const lang = c.get("lang") || c.env.DEFAULT_LANG; - const msgs = i18n.getMessages(lang); + const msgs = i18n.getMessagesbyContext(c); if (!getBooleanValue(c.env.ENABLE_USER_DELETE_EMAIL)) { return c.text(msgs.UserDeleteEmailDisabledMsg, 403) } @@ -64,8 +63,7 @@ api.delete('/api/mails/:id', async (c) => { api.get('/api/settings', async (c) => { const { address, address_id } = c.get("jwtPayload") const user_role = c.get("userRolePayload") - const lang = c.get("lang") || c.env.DEFAULT_LANG; - const msgs = i18n.getMessages(lang); + const msgs = i18n.getMessagesbyContext(c); if (address_id && address_id > 0) { try { const db_address_id = await c.env.DB.prepare( @@ -106,8 +104,7 @@ api.get('/api/settings', async (c) => { }) api.post('/api/new_address', async (c) => { - const lang = c.get("lang") || c.env.DEFAULT_LANG; - const msgs = i18n.getMessages(lang); + const msgs = i18n.getMessagesbyContext(c); if (getBooleanValue(c.env.DISABLE_ANONYMOUS_USER_CREATE_EMAIL) && !c.get("userPayload") ) { @@ -171,8 +168,7 @@ api.delete('/api/delete_address', async (c) => { }) api.delete('/api/clear_inbox', async (c) => { - const lang = c.get("lang") || c.env.DEFAULT_LANG; - const msgs = i18n.getMessages(lang); + const msgs = i18n.getMessagesbyContext(c); if (!getBooleanValue(c.env.ENABLE_USER_DELETE_EMAIL)) { return c.text(msgs.UserDeleteEmailDisabledMsg, 403) } @@ -181,7 +177,7 @@ api.delete('/api/clear_inbox', async (c) => { `DELETE FROM raw_mails WHERE address = ?` ).bind(address).run(); if (!success) { - return c.text("Failed to clear inbox", 500) + return c.text(msgs.FailedClearInboxMsg, 500) } return c.json({ success: success @@ -189,8 +185,7 @@ api.delete('/api/clear_inbox', async (c) => { }) api.delete('/api/clear_sent_items', async (c) => { - const lang = c.get("lang") || c.env.DEFAULT_LANG; - const msgs = i18n.getMessages(lang); + const msgs = i18n.getMessagesbyContext(c); if (!getBooleanValue(c.env.ENABLE_USER_DELETE_EMAIL)) { return c.text(msgs.UserDeleteEmailDisabledMsg, 403) } @@ -199,7 +194,7 @@ api.delete('/api/clear_sent_items', async (c) => { `DELETE FROM sendbox WHERE address = ?` ).bind(address).run(); if (!success) { - return c.text("Failed to clear sent items", 500) + return c.text(msgs.FailedClearSentItemsMsg, 500) } return c.json({ success: success diff --git a/worker/src/mails_api/send_mail_api.ts b/worker/src/mails_api/send_mail_api.ts index 18d5156c..29b36f41 100644 --- a/worker/src/mails_api/send_mail_api.ts +++ b/worker/src/mails_api/send_mail_api.ts @@ -14,9 +14,10 @@ import { handleListQuery } from '../common' export const api = new Hono() api.post('/api/requset_send_mail_access', async (c) => { + const msgs = i18n.getMessagesbyContext(c); const { address } = c.get("jwtPayload") if (!address) { - return c.text("No address", 400) + return c.text(msgs.AddressNotFoundMsg, 400) } try { const default_balance = getIntValue(c.env.DEFAULT_SEND_BALANCE, 0); @@ -26,14 +27,14 @@ api.post('/api/requset_send_mail_access', async (c) => { address, default_balance, default_balance > 0 ? 1 : 0 ).run(); if (!success) { - return c.text("Failed to request send mail access", 500) + return c.text(msgs.OperationFailedMsg, 500) } } catch (e) { const message = (e as Error).message; if (message && message.includes("UNIQUE")) { - return c.text("Already requested", 400) + return c.text(msgs.AlreadyRequestedMsg, 400) } - return c.text("Failed to request send mail access", 500) + return c.text(msgs.OperationFailedMsg, 500) } return c.json({ status: "ok" }) }) @@ -126,14 +127,15 @@ export const sendMail = async ( isAdmin?: boolean } ): Promise => { + const msgs = i18n.getMessagesbyContext(c); if (!address) { - throw new Error("No address") + throw new Error(msgs.AddressNotFoundMsg) } // check domain const mailDomain = address.split("@")[1]; const domains = getDomains(c); if (!domains.includes(mailDomain)) { - throw new Error("Invalid domain") + throw new Error(msgs.InvalidDomainMsg) } const user_role = c.get("userRolePayload"); const no_limit_roles = getSplitStringListValue(c.env.NO_LIMIT_SEND_ROLE); @@ -150,7 +152,7 @@ export const sendMail = async ( where address = ? and enabled = 1` ).bind(address).first("balance"); if (!balance || balance <= 0) { - throw new Error("No balance") + throw new Error(msgs.NoBalanceMsg) } } const { @@ -158,18 +160,18 @@ export const sendMail = async ( subject, content, is_html } = reqJson; if (!to_mail) { - throw new Error("Invalid to mail") + throw new Error(msgs.InvalidToMailMsg) } // check SEND_BLOCK_LIST_KEY const sendBlockList = await getJsonSetting(c, CONSTANTS.SEND_BLOCK_LIST_KEY) as string[]; if (sendBlockList && sendBlockList.some((item) => to_mail.includes(item))) { - throw new Error("to_mail address is blocked") + throw new Error(msgs.AddressBlockedMsg) } if (!subject) { - throw new Error("Subject is empty") + throw new Error(msgs.SubjectEmptyMsg) } if (!content) { - throw new Error("Content is empty") + throw new Error(msgs.ContentEmptyMsg) } // send to verified address list, do not update balance @@ -202,9 +204,9 @@ export const sendMail = async ( } else { if (c.env.SEND_MAIL) { - throw new Error(`Please enable resend or smtp for domain ${mailDomain}. Or add ${to_mail} to verified address list`); + throw new Error(`${msgs.EnableResendOrSmtpWithVerifiedMsg} (${mailDomain})`); } - throw new Error(`Please enable resend or smtp for domain ${mailDomain}`); + throw new Error(`${msgs.EnableResendOrSmtpMsg} (${mailDomain})`); } // update balance @@ -253,11 +255,12 @@ api.post('/api/send_mail', async (c) => { }) api.post('/external/api/send_mail', async (c) => { + const msgs = i18n.getMessagesbyContext(c); const { token } = await c.req.json(); try { const { address } = await Jwt.verify(token, c.env.JWT_SECRET, "HS256"); if (!address) { - return c.text("No address", 400) + return c.text(msgs.AddressNotFoundMsg, 400) } const reqJson = await c.req.json(); await sendMail(c, address as string, reqJson); @@ -289,8 +292,7 @@ api.get('/api/sendbox', async (c) => { }) api.delete('/api/sendbox/:id', async (c) => { - const lang = c.get("lang") || c.env.DEFAULT_LANG; - const msgs = i18n.getMessages(lang); + const msgs = i18n.getMessagesbyContext(c); if (!getBooleanValue(c.env.ENABLE_USER_DELETE_EMAIL)) { return c.text(msgs.UserDeleteEmailDisabledMsg, 403) } diff --git a/worker/src/mails_api/webhook_settings.ts b/worker/src/mails_api/webhook_settings.ts index 4e016d78..2f704e7b 100644 --- a/worker/src/mails_api/webhook_settings.ts +++ b/worker/src/mails_api/webhook_settings.ts @@ -2,13 +2,15 @@ import { Context } from "hono"; import { CONSTANTS } from "../constants"; import { AdminWebhookSettings, WebhookSettings } from "../models"; import { commonParseMail, sendWebhook } from "../common"; +import i18n from "../i18n"; async function getWebhookSettings(c: Context): Promise { + const msgs = i18n.getMessagesbyContext(c); const { address } = c.get("jwtPayload") const adminSettings = await c.env.KV.get(CONSTANTS.WEBHOOK_KV_SETTINGS_KEY, "json"); if (adminSettings?.enableAllowList && !adminSettings?.allowList.includes(address)) { - return c.text("Webhook settings is not allowed for this user", 403); + return c.text(msgs.WebhookNotAllowedForUserMsg, 403); } const settings = await c.env.KV.get( `${CONSTANTS.WEBHOOK_KV_USER_SETTINGS_KEY}:${address}`, "json" @@ -18,10 +20,11 @@ async function getWebhookSettings(c: Context): Promise async function saveWebhookSettings(c: Context): Promise { + const msgs = i18n.getMessagesbyContext(c); const { address } = c.get("jwtPayload") const adminSettings = await c.env.KV.get(CONSTANTS.WEBHOOK_KV_SETTINGS_KEY, "json"); if (adminSettings?.enableAllowList && !adminSettings?.allowList.includes(address)) { - return c.text("Webhook settings is not allowed for this user", 403); + return c.text(msgs.WebhookNotAllowedForUserMsg, 403); } const settings = await c.req.json(); await c.env.KV.put( diff --git a/worker/src/telegram_api/common.ts b/worker/src/telegram_api/common.ts index 0985591d..acff1d59 100644 --- a/worker/src/telegram_api/common.ts +++ b/worker/src/telegram_api/common.ts @@ -3,9 +3,11 @@ import { Jwt } from "hono/utils/jwt"; import { CONSTANTS } from "../constants"; import { getBooleanValue, getIntValue, getJsonSetting } from "../utils"; import { deleteAddressWithData, newAddress, generateRandomName } from "../common"; +import { LocaleMessages } from "../i18n/type"; export const tgUserNewAddress = async ( - c: Context, userId: string, address: string + c: Context, userId: string, address: string, + msgs: LocaleMessages ): Promise<{ address: string, jwt: string, password?: string | null }> => { if (c.env.RATE_LIMITER) { const { success } = await c.env.RATE_LIMITER.limit( @@ -23,7 +25,7 @@ export const tgUserNewAddress = async ( const [name, domain] = trimmedAddress.includes("@") ? trimmedAddress.split("@") : [trimmedAddress, null]; const jwtList = await c.env.KV.get(`${CONSTANTS.TG_KV_PREFIX}:${userId}`, 'json') || []; if (jwtList.length >= getIntValue(c.env.TG_MAX_ADDRESS, 5)) { - throw Error("绑定地址数量已达上限"); + throw Error(msgs.TgMaxAddressReachedMsg); } // Generate name if disabled or not provided const finalName = (!name || disableCustomAddressName) ? generateRandomName(c) : name; @@ -48,7 +50,8 @@ export const tgUserNewAddress = async ( } export const jwtListToAddressData = async ( - c: Context, jwtList: string[] + c: Context, jwtList: string[], + msgs: LocaleMessages ): Promise<{ addressList: string[], addressIdMap: Record, invalidJwtList: string[] @@ -63,35 +66,36 @@ export const jwtListToAddressData = async ( `SELECT name FROM address WHERE id = ? ` ).bind(address_id).first("name"); if (!name) { - addressList.push("无效地址"); + addressList.push(msgs.TgInvalidAddressMsg); invalidJwtList.push(jwt); continue; } addressList.push(address as string); addressIdMap[address as string] = address_id as number; } catch (e) { - addressList.push("无效凭证"); + addressList.push(msgs.TgInvalidCredentialMsg); invalidJwtList.push(jwt); - console.log(`获取地址列表失败: ${(e as Error).message}`); + console.log(`Failed to get address list: ${(e as Error).message}`); } } return { addressList, addressIdMap, invalidJwtList }; } export const bindTelegramAddress = async ( - c: Context, userId: string, jwt: string + c: Context, userId: string, jwt: string, + msgs: LocaleMessages ): Promise => { const { address } = await Jwt.verify(jwt, c.env.JWT_SECRET, "HS256"); if (!address) { - throw Error("无效凭证"); + throw Error(msgs.TgInvalidCredentialMsg); } const jwtList = await c.env.KV.get(`${CONSTANTS.TG_KV_PREFIX}:${userId}`, 'json') || []; - const { addressIdMap } = await jwtListToAddressData(c, jwtList); + const { addressIdMap } = await jwtListToAddressData(c, jwtList, msgs); if (address as string in addressIdMap) { return address as string; } if (jwtList.length >= getIntValue(c.env.TG_MAX_ADDRESS, 5)) { - throw Error("绑定地址数量已达上限, 请先 /cleaninvalidaddress"); + throw Error(msgs.TgMaxAddressReachedCleanMsg); } await c.env.KV.put(`${CONSTANTS.TG_KV_PREFIX}:${userId}`, JSON.stringify([...jwtList, jwt])); // for mail push to telegram @@ -133,12 +137,13 @@ export const unbindTelegramByAddress = async ( export const deleteTelegramAddress = async ( - c: Context, userId: string, address: string + c: Context, userId: string, address: string, + msgs: LocaleMessages ): Promise => { const jwtList = await c.env.KV.get(`${CONSTANTS.TG_KV_PREFIX}:${userId}`, 'json') || []; - const { addressIdMap } = await jwtListToAddressData(c, jwtList); + const { addressIdMap } = await jwtListToAddressData(c, jwtList, msgs); if (!(address in addressIdMap)) { - throw Error("此地址不属于您"); + throw Error(msgs.TgAddressNotYoursMsg); } await deleteAddressWithData(c, null, addressIdMap[address]) return true; diff --git a/worker/src/telegram_api/index.ts b/worker/src/telegram_api/index.ts index 4a3fc026..c3b1aff5 100644 --- a/worker/src/telegram_api/index.ts +++ b/worker/src/telegram_api/index.ts @@ -5,26 +5,29 @@ import { Writable } from 'node:stream' import { newTelegramBot, initTelegramBotCommands, sendMailToTelegram } from './telegram' import settings from './settings' import miniapp from './miniapp' +import i18n from '../i18n' export const api = new Hono(); export { sendMailToTelegram } api.use("/telegram/*", async (c, next) => { + const msgs = i18n.getMessagesbyContext(c); if (!c.env.TELEGRAM_BOT_TOKEN) { - return c.text("TELEGRAM_BOT_TOKEN is required", 400); + return c.text(msgs.TgBotTokenRequiredMsg, 400); } if (!c.env.KV) { - return c.text("KV is required", 400); + return c.text(msgs.KVNotAvailableMsg, 400); } return await next(); }); api.use("/admin/telegram/*", async (c, next) => { + const msgs = i18n.getMessagesbyContext(c); if (!c.env.TELEGRAM_BOT_TOKEN) { - return c.text("TELEGRAM_BOT_TOKEN is required", 400); + return c.text(msgs.TgBotTokenRequiredMsg, 400); } if (!c.env.KV) { - return c.text("KV is required", 400); + return c.text(msgs.KVNotAvailableMsg, 400); } return await next(); }); @@ -51,7 +54,7 @@ api.post("/admin/telegram/init", async (c) => { console.log(`setting webhook to ${webhookUrl}`); const bot = newTelegramBot(c, token); await bot.telegram.setWebhook(webhookUrl) - await initTelegramBotCommands(bot); + await initTelegramBotCommands(c, bot); return c.json({ message: "webhook set successfully", }); diff --git a/worker/src/telegram_api/miniapp.ts b/worker/src/telegram_api/miniapp.ts index 3dde76b5..96764dc8 100644 --- a/worker/src/telegram_api/miniapp.ts +++ b/worker/src/telegram_api/miniapp.ts @@ -84,8 +84,7 @@ async function getTelegramBindAddress(c: Context): Promise): Promise { const { initData, address, cf_token } = await c.req.json(); - const lang = c.get("lang") || c.env.DEFAULT_LANG; - const msgs = i18n.getMessages(lang); + const msgs = i18n.getMessagesbyContext(c); // check cf turnstile try { await checkCfTurnstile(c, cf_token); @@ -95,7 +94,7 @@ async function newTelegramAddress(c: Context): Promise try { const userId = await checkTelegramAuth(c, initData); // get the address list from the KV - const res = await tgUserNewAddress(c, userId, address) + const res = await tgUserNewAddress(c, userId, address, msgs) return c.json(res); } catch (e) { @@ -105,9 +104,10 @@ async function newTelegramAddress(c: Context): Promise async function bindAddress(c: Context): Promise { const { initData, jwt } = await c.req.json(); + const msgs = i18n.getMessagesbyContext(c); try { const userId = await checkTelegramAuth(c, initData); - await bindTelegramAddress(c, userId, jwt); + await bindTelegramAddress(c, userId, jwt, msgs); return c.json({ success: true }); } catch (e) { @@ -129,10 +129,11 @@ async function unbindAddress(c: Context): Promise { async function getMail(c: Context): Promise { const { initData, mailId } = await c.req.json(); + const msgs = i18n.getMessagesbyContext(c); try { const userId = await checkTelegramAuth(c, initData); const jwtList = await c.env.KV.get(`${CONSTANTS.TG_KV_PREFIX}:${userId}`, 'json') || []; - const { addressList, addressIdMap } = await jwtListToAddressData(c, jwtList); + const { addressList, addressIdMap } = await jwtListToAddressData(c, jwtList, msgs); const result = await c.env.DB.prepare( `SELECT * FROM raw_mails where id = ?` ).bind(mailId).first(); @@ -140,14 +141,14 @@ async function getMail(c: Context): Promise { const superUser = settings?.enableGlobalMailPush && settings?.globalMailPushList.includes(userId); if (!superUser) { if (result?.address && !(result.address as string in addressIdMap)) { - return c.text("无权查看此邮件", 403); + return c.text(msgs.TgNoPermissionViewMailMsg, 403); } const address_id = addressIdMap[result?.address as string]; const db_address_id = await c.env.DB.prepare( `SELECT id FROM address where id = ? ` ).bind(address_id).first("id"); if (!db_address_id) { - return c.text("无权查看此邮件", 403); + return c.text(msgs.TgNoPermissionViewMailMsg, 403); } } return c.json(result); diff --git a/worker/src/telegram_api/telegram.ts b/worker/src/telegram_api/telegram.ts index 0f55aa68..2caf06b6 100644 --- a/worker/src/telegram_api/telegram.ts +++ b/worker/src/telegram_api/telegram.ts @@ -4,48 +4,79 @@ import { Telegraf, Context as TgContext, Markup } from "telegraf"; import { callbackQuery } from "telegraf/filters"; import { CONSTANTS } from "../constants"; -import { getDomains, getJsonObjectValue, getStringValue } from '../utils'; +import { getBooleanValue, getDomains, getJsonObjectValue, getStringValue } from '../utils'; import { TelegramSettings } from "./settings"; import { bindTelegramAddress, deleteTelegramAddress, jwtListToAddressData, tgUserNewAddress, unbindTelegramAddress, unbindTelegramByAddress } from "./common"; import { commonParseMail } from "../common"; import { UserFromGetMe } from "telegraf/types"; +import i18n from "../i18n"; +import { LocaleMessages } from "../i18n/type"; +// Helper to get messages by userId +const getTgMessages = async ( + c: Context, + ctx?: TgContext, + userId?: string | null +): Promise => { + // Check if user language config is enabled (default false) + if (!getBooleanValue(c.env.TG_ALLOW_USER_LANG)) { + return i18n.getMessages(c.env.DEFAULT_LANG || 'zh'); + } + const uid = userId || ctx?.message?.from?.id?.toString() || ctx?.callbackQuery?.from?.id?.toString(); + if (uid) { + const savedLang = await c.env.KV.get(`${CONSTANTS.TG_KV_PREFIX}:lang:${uid}`); + if (savedLang) { return i18n.getMessages(savedLang); } + } + return i18n.getMessages(c.env.DEFAULT_LANG || 'zh'); +}; + +// Bilingual command descriptions with full usage instructions const COMMANDS = [ { command: "start", - description: "开始使用" + description: "开始使用 | Get started" }, { command: "new", - description: "新建邮箱地址, 如果要自定义邮箱地址, 请输入 /new, 通过 /new @ 可以指定, name [a-z0-9] 有效, name 为空则随机生成, @ 可选" + description: "新建邮箱, /new @, name[a-z0-9]有效, 为空随机生成, @domain可选 | Create address, /new @, name[a-z0-9] valid, empty=random, @domain optional" }, { command: "address", - description: "查看邮箱地址列表" + description: "查看邮箱地址列表 | View address list" }, { command: "bind", - description: "绑定邮箱地址, 请输入 /bind <邮箱地址凭证>" + description: "绑定邮箱, /bind <邮箱地址凭证> | Bind address, /bind " }, { command: "unbind", - description: "解绑邮箱地址, 请输入 /unbind <邮箱地址>" + description: "解绑邮箱, /unbind <邮箱地址> | Unbind address, /unbind
" }, { command: "delete", - description: "删除邮箱地址, 请输入 /delete <邮箱地址>" + description: "删除邮箱, /delete <邮箱地址> | Delete address, /delete
" }, { command: "mails", - description: "查看邮件, 请输入 /mails <邮箱地址>, 不输入地址默认查看第一个地址" + description: "查看邮件, /mails <邮箱地址>, 不输入地址默认第一个 | View mails, /mails
, default first if empty" }, { command: "cleaninvalidaddress", - description: "清理无效地址, 请输入 /cleaninvalidaddress" + description: "清理无效地址 | Clean invalid addresses" + }, + { + command: "lang", + description: "设置语言 /lang | Set language /lang " }, ] +export const getTelegramCommands = (c: Context) => { + return getBooleanValue(c.env.TG_ALLOW_USER_LANG) + ? COMMANDS + : COMMANDS.filter(cmd => cmd.command !== "lang"); +} + export function newTelegramBot(c: Context, token: string): Telegraf { const bot = new Telegraf(token); const botInfo = getJsonObjectValue(c.env.TG_BOT_INFO); @@ -61,14 +92,16 @@ export function newTelegramBot(c: Context, token: string): Teleg const userId = ctx?.message?.from?.id || ctx.callbackQuery?.message?.chat?.id; if (!userId) { - return await ctx.reply("无法获取用户信息"); + const msgs = await getTgMessages(c, ctx); + return await ctx.reply(msgs.TgUnableGetUserInfoMsg); } const settings = await c.env.KV.get(CONSTANTS.TG_KV_SETTINGS_KEY, "json"); - if (settings?.enableAllowList && settings?.enableAllowList + if (settings?.enableAllowList && !settings.allowList.includes(userId.toString()) ) { - return await ctx.reply("您没有权限使用此机器人"); + const msgs = await getTgMessages(c, ctx); + return await ctx.reply(msgs.TgNoPermissionMsg); } try { await next(); @@ -79,153 +112,192 @@ export function newTelegramBot(c: Context, token: string): Teleg }) bot.command("start", async (ctx: TgContext) => { + const msgs = await getTgMessages(c, ctx); const prefix = getStringValue(c.env.PREFIX) const domains = getDomains(c); + const commands = getTelegramCommands(c); return await ctx.reply( - "欢迎使用本机器人, 您可以打开 mini app \n\n" - + (prefix ? `当前已启用前缀: ${prefix}\n` : '') - + `当前可用域名: ${JSON.stringify(domains)}\n` - + "请使用以下命令:\n" - + COMMANDS.map(c => `/${c.command}: ${c.description}`).join("\n") + `${msgs.TgWelcomeMsg}\n\n` + + (prefix ? `${msgs.TgCurrentPrefixMsg} ${prefix}\n` : '') + + `${msgs.TgCurrentDomainsMsg} ${JSON.stringify(domains)}\n` + + `${msgs.TgAvailableCommandsMsg}\n` + + commands.map(cmd => `/${cmd.command}: ${cmd.description}`).join("\n") ); }); bot.command("new", async (ctx: TgContext) => { + const msgs = await getTgMessages(c, ctx); const userId = ctx?.message?.from?.id; if (!userId) { - return await ctx.reply("无法获取用户信息"); + return await ctx.reply(msgs.TgUnableGetUserInfoMsg); } try { // @ts-ignore const address = ctx?.message?.text.slice("/new".length).trim(); - const res = await tgUserNewAddress(c, userId.toString(), address); - return await ctx.reply(`创建地址成功:\n` - + `地址: ${res.address}\n` - + (res.password ? `密码: \`${res.password}\`\n` : '') - + `凭证: \`${res.jwt}\`\n`, + const res = await tgUserNewAddress(c, userId.toString(), address, msgs); + return await ctx.reply(`${msgs.TgCreateSuccessMsg}\n` + + `${msgs.TgAddressMsg} ${res.address}\n` + + (res.password ? `${msgs.TgPasswordMsg} \`${res.password}\`\n` : '') + + `${msgs.TgCredentialMsg} \`${res.jwt}\`\n`, { parse_mode: "Markdown" } ); } catch (e) { - return await ctx.reply(`创建地址失败: ${(e as Error).message}`); + return await ctx.reply(`${msgs.TgCreateFailedMsg} ${(e as Error).message}`); } }); bot.command("bind", async (ctx: TgContext) => { + const msgs = await getTgMessages(c, ctx); const userId = ctx?.message?.from?.id; if (!userId) { - return await ctx.reply("无法获取用户信息"); + return await ctx.reply(msgs.TgUnableGetUserInfoMsg); } try { // @ts-ignore const jwt = ctx?.message?.text.slice("/bind".length).trim(); if (!jwt) { - return await ctx.reply("请输入凭证"); + return await ctx.reply(msgs.TgPleaseInputCredentialMsg); } - const address = await bindTelegramAddress(c, userId.toString(), jwt); - return await ctx.reply(`绑定成功:\n` - + `地址: ${address}` + const address = await bindTelegramAddress(c, userId.toString(), jwt, msgs); + return await ctx.reply(`${msgs.TgBindSuccessMsg}\n` + + `${msgs.TgAddressMsg} ${address}` ); } catch (e) { - return await ctx.reply(`绑定失败: ${(e as Error).message}`); + return await ctx.reply(`${msgs.TgBindFailedMsg} ${(e as Error).message}`); } }); bot.command("unbind", async (ctx: TgContext) => { + const msgs = await getTgMessages(c, ctx); const userId = ctx?.message?.from?.id; if (!userId) { - return await ctx.reply("无法获取用户信息"); + return await ctx.reply(msgs.TgUnableGetUserInfoMsg); } try { // @ts-ignore const address = ctx?.message?.text.slice("/unbind".length).trim(); if (!address) { - return await ctx.reply("请输入地址"); + return await ctx.reply(msgs.TgPleaseInputAddressMsg); } await unbindTelegramAddress(c, userId.toString(), address); - return await ctx.reply(`解绑成功:\n地址: ${address}` + return await ctx.reply(`${msgs.TgUnbindSuccessMsg}\n${msgs.TgAddressMsg} ${address}` ); } catch (e) { - return await ctx.reply(`解绑失败: ${(e as Error).message}`); + return await ctx.reply(`${msgs.TgUnbindFailedMsg} ${(e as Error).message}`); } }) bot.command("delete", async (ctx: TgContext) => { + const msgs = await getTgMessages(c, ctx); const userId = ctx?.message?.from?.id; if (!userId) { - return await ctx.reply("无法获取用户信息"); + return await ctx.reply(msgs.TgUnableGetUserInfoMsg); } try { // @ts-ignore const address = ctx?.message?.text.slice("/delete".length).trim(); if (!address) { - return await ctx.reply("请输入地址"); + return await ctx.reply(msgs.TgPleaseInputAddressMsg); } - await deleteTelegramAddress(c, userId.toString(), address); - return await ctx.reply(`删除成功: ${address}`); + await deleteTelegramAddress(c, userId.toString(), address, msgs); + return await ctx.reply(`${msgs.TgDeleteSuccessMsg} ${address}`); } catch (e) { - return await ctx.reply(`删除失败: ${(e as Error).message}`); + return await ctx.reply(`${msgs.TgDeleteFailedMsg} ${(e as Error).message}`); } }); bot.command("address", async (ctx) => { + const msgs = await getTgMessages(c, ctx); const userId = ctx?.message?.from?.id; if (!userId) { - return await ctx.reply("无法获取用户信息"); + return await ctx.reply(msgs.TgUnableGetUserInfoMsg); } try { const jwtList = await c.env.KV.get(`${CONSTANTS.TG_KV_PREFIX}:${userId}`, 'json') || []; - const { addressList } = await jwtListToAddressData(c, jwtList); - return await ctx.reply(`地址列表:\n\n` - + addressList.map(a => `地址: ${a}`).join("\n") + const { addressList } = await jwtListToAddressData(c, jwtList, msgs); + return await ctx.reply(`${msgs.TgAddressListMsg}\n\n` + + addressList.map(a => `${msgs.TgAddressMsg} ${a}`).join("\n") ); } catch (e) { - return await ctx.reply(`获取地址列表失败: ${(e as Error).message}`); + return await ctx.reply(`${msgs.TgGetAddressFailedMsg} ${(e as Error).message}`); } }); bot.command("cleaninvalidaddress", async (ctx: TgContext) => { + const msgs = await getTgMessages(c, ctx); const userId = ctx?.message?.from?.id; if (!userId) { - return await ctx.reply("无法获取用户信息"); + return await ctx.reply(msgs.TgUnableGetUserInfoMsg); } try { const jwtList = await c.env.KV.get(`${CONSTANTS.TG_KV_PREFIX}:${userId}`, 'json') || []; - const { invalidJwtList } = await jwtListToAddressData(c, jwtList); + const { invalidJwtList } = await jwtListToAddressData(c, jwtList, msgs); const newJwtList = jwtList.filter(jwt => !invalidJwtList.includes(jwt)); await c.env.KV.put(`${CONSTANTS.TG_KV_PREFIX}:${userId}`, JSON.stringify(newJwtList)); - const { addressList } = await jwtListToAddressData(c, newJwtList); - return await ctx.reply(`清理无效地址成功:\n\n` - + `当前地址列表:\n\n` - + addressList.map(a => `地址: ${a}`).join("\n") + const { addressList } = await jwtListToAddressData(c, newJwtList, msgs); + return await ctx.reply(`${msgs.TgCleanSuccessMsg}\n\n` + + `${msgs.TgCurrentAddressListMsg}\n\n` + + addressList.map(a => `${msgs.TgAddressMsg} ${a}`).join("\n") ); } catch (e) { - return await ctx.reply(`清理无效地址失败: ${(e as Error).message}`); + return await ctx.reply(`${msgs.TgCleanFailedMsg} ${(e as Error).message}`); } }); + bot.command("lang", async (ctx: TgContext) => { + const userId = ctx?.message?.from?.id; + if (!userId) { + const msgs = await getTgMessages(c, ctx); + return await ctx.reply(msgs.TgUnableGetUserInfoMsg); + } + + const msgs = await getTgMessages(c, ctx); + + // Check if user language config is enabled + if (!getBooleanValue(c.env.TG_ALLOW_USER_LANG)) { + return await ctx.reply(msgs.TgLangFeatureDisabledMsg); + } + + // @ts-ignore + const lang = ctx?.message?.text.slice("/lang".length).trim().toLowerCase(); + if (lang === 'zh' || lang === 'en') { + await c.env.KV.put(`${CONSTANTS.TG_KV_PREFIX}:lang:${userId}`, lang); + return await ctx.reply(`${msgs.TgLangSetSuccessMsg} ${lang === 'zh' ? '中文' : 'English'}`); + } + + const currentLang = await c.env.KV.get(`${CONSTANTS.TG_KV_PREFIX}:lang:${userId}`); + return await ctx.reply( + `${msgs.TgCurrentLangMsg} ${currentLang || 'auto'}\n` + + `${msgs.TgSelectLangMsg}\n` + + `/lang zh - 中文\n` + + `/lang en - English` + ); + }); + const queryMail = async (ctx: TgContext, queryAddress: string, mailIndex: number, edit: boolean) => { + const msgs = await getTgMessages(c, ctx); const userId = ctx?.message?.from?.id || ctx.callbackQuery?.message?.chat?.id; if (!userId) { - return await ctx.reply("无法获取用户信息"); + return await ctx.reply(msgs.TgUnableGetUserInfoMsg); } const jwtList = await c.env.KV.get(`${CONSTANTS.TG_KV_PREFIX}:${userId}`, 'json') || []; - const { addressList, addressIdMap } = await jwtListToAddressData(c, jwtList); + const { addressList, addressIdMap } = await jwtListToAddressData(c, jwtList, msgs); if (!queryAddress && addressList.length > 0) { queryAddress = addressList[0]; } if (!(queryAddress in addressIdMap)) { - return await ctx.reply(`未绑定此地址 ${queryAddress}`); + return await ctx.reply(`${msgs.TgNotBoundAddressMsg} ${queryAddress}`); } const address_id = addressIdMap[queryAddress]; const db_address_id = await c.env.DB.prepare( `SELECT id FROM address where id = ? ` ).bind(address_id).first("id"); if (!db_address_id) { - return await ctx.reply("无效地址"); + return await ctx.reply(msgs.TgInvalidAddressMsg); } const { raw, id: mailId, created_at } = await c.env.DB.prepare( `SELECT * FROM raw_mails where address = ? ` @@ -233,47 +305,49 @@ export function newTelegramBot(c: Context, token: string): Teleg ).bind( queryAddress, mailIndex ).first<{ raw: string, id: string, created_at: string }>() || {}; - const { mail } = raw ? await parseMail({ rawEmail: raw }, queryAddress, created_at) : { mail: "已经没有邮件了" }; + const { mail } = raw ? await parseMail(msgs, { rawEmail: raw }, queryAddress, created_at) : { mail: msgs.TgNoMoreMailsMsg }; const settings = await c.env.KV.get(CONSTANTS.TG_KV_SETTINGS_KEY, "json"); const miniAppButtons = [] if (settings?.miniAppUrl && settings?.miniAppUrl?.length > 0 && mailId) { const url = new URL(settings.miniAppUrl); url.pathname = "/telegram_mail" url.searchParams.set("mail_id", mailId); - miniAppButtons.push(Markup.button.webApp("查看邮件", url.toString())); + miniAppButtons.push(Markup.button.webApp(msgs.TgViewMailBtnMsg, url.toString())); } if (edit) { - return await ctx.editMessageText(mail || "无邮件", + return await ctx.editMessageText(mail || msgs.TgNoMailMsg, { ...Markup.inlineKeyboard([ - Markup.button.callback("上一条", `mail_${queryAddress}_${mailIndex - 1}`, mailIndex <= 0), + Markup.button.callback(msgs.TgPrevBtnMsg, `mail_${queryAddress}_${mailIndex - 1}`, mailIndex <= 0), ...miniAppButtons, - Markup.button.callback("下一条", `mail_${queryAddress}_${mailIndex + 1}`, !raw), + Markup.button.callback(msgs.TgNextBtnMsg, `mail_${queryAddress}_${mailIndex + 1}`, !raw), ]) }, ); } - return await ctx.reply(mail || "无邮件", + return await ctx.reply(mail || msgs.TgNoMailMsg, { ...Markup.inlineKeyboard([ - Markup.button.callback("上一条", `mail_${queryAddress}_${mailIndex - 1}`, mailIndex <= 0), + Markup.button.callback(msgs.TgPrevBtnMsg, `mail_${queryAddress}_${mailIndex - 1}`, mailIndex <= 0), ...miniAppButtons, - Markup.button.callback("下一条", `mail_${queryAddress}_${mailIndex + 1}`, !raw), + Markup.button.callback(msgs.TgNextBtnMsg, `mail_${queryAddress}_${mailIndex + 1}`, !raw), ]) }, ); } bot.command("mails", async ctx => { + const msgs = await getTgMessages(c, ctx); try { const queryAddress = ctx?.message?.text.slice("/mails".length).trim(); return await queryMail(ctx, queryAddress, 0, false); } catch (e) { - return await ctx.reply(`获取邮件失败: ${(e as Error).message}`); + return await ctx.reply(`${msgs.TgGetMailFailedMsg} ${(e as Error).message}`); } }); bot.on(callbackQuery("data"), async ctx => { + const msgs = await getTgMessages(c, ctx); // Use ctx.callbackQuery.data try { const data = ctx.callbackQuery.data; @@ -283,8 +357,8 @@ export function newTelegramBot(c: Context, token: string): Teleg } } catch (e) { - console.log(`获取邮件失败: ${(e as Error).message}`, e); - return await ctx.answerCbQuery(`获取邮件失败: ${(e as Error).message}`); + console.log(`${msgs.TgGetMailFailedMsg} ${(e as Error).message}`, e); + return await ctx.answerCbQuery(`${msgs.TgGetMailFailedMsg} ${(e as Error).message}`); } await ctx.answerCbQuery(); }); @@ -293,11 +367,12 @@ export function newTelegramBot(c: Context, token: string): Teleg } -export async function initTelegramBotCommands(bot: Telegraf) { - await bot.telegram.setMyCommands(COMMANDS); +export async function initTelegramBotCommands(c: Context, bot: Telegraf) { + await bot.telegram.setMyCommands(getTelegramCommands(c)); } const parseMail = async ( + msgs: LocaleMessages, parsedEmailContext: ParsedEmailContext, address: string, created_at: string | undefined | null ) => { @@ -308,20 +383,20 @@ const parseMail = async ( const parsedEmail = await commonParseMail(parsedEmailContext); let parsedText = parsedEmail?.text || ""; if (parsedText.length && parsedText.length > 1000) { - parsedText = parsedEmail?.text.substring(0, 1000) + "\n\n...\n消息过长请到miniapp查看"; + parsedText = parsedEmail?.text.substring(0, 1000) + `\n\n...\n${msgs.TgMsgTooLongMsg}`; } return { isHtml: false, - mail: `From: ${parsedEmail?.sender || "无发件人"}\n` + mail: `From: ${parsedEmail?.sender || msgs.TgNoSenderMsg}\n` + `To: ${address}\n` + (created_at ? `Date: ${created_at}\n` : "") + `Subject: ${parsedEmail?.subject}\n` - + `Content:\n${parsedText || "解析失败,请打开 mini app 查看"}` + + `Content:\n${parsedText || msgs.TgParseFailedViewInAppMsg}` }; } catch (e) { return { isHtml: false, - mail: `解析邮件失败: ${(e as Error).message}` + mail: `${msgs.TgParseMailFailedMsg} ${(e as Error).message}` }; } } @@ -336,10 +411,6 @@ export async function sendMailToTelegram( return; } const userId = await c.env.KV.get(`${CONSTANTS.TG_KV_PREFIX}:${address}`); - const { mail } = await parseMail(parsedEmailContext, address, new Date().toUTCString()); - if (!mail) { - return; - } const settings = await c.env.KV.get(CONSTANTS.TG_KV_SETTINGS_KEY, "json"); const globalPush = settings?.enableGlobalMailPush && settings?.globalMailPushList; if (!userId && !globalPush) { @@ -349,28 +420,31 @@ export async function sendMailToTelegram( `SELECT id FROM raw_mails where address = ? and message_id = ?` ).bind(address, message_id).first("id"); const bot = newTelegramBot(c, c.env.TELEGRAM_BOT_TOKEN); - const miniAppButtons = [] - if (settings?.miniAppUrl && settings?.miniAppUrl?.length > 0 && mailId) { - const url = new URL(settings.miniAppUrl); - url.pathname = "/telegram_mail" - url.searchParams.set("mail_id", mailId); - miniAppButtons.push(Markup.button.webApp("查看邮件", url.toString())); - } + + const buildAndSend = async (targetUserId: string, msgs: LocaleMessages) => { + const { mail } = await parseMail(msgs, parsedEmailContext, address, new Date().toUTCString()); + if (!mail) return; + const buttons = []; + if (settings?.miniAppUrl && mailId) { + const url = new URL(settings.miniAppUrl); + url.pathname = "/telegram_mail" + url.searchParams.set("mail_id", mailId); + buttons.push(Markup.button.webApp(msgs.TgViewMailBtnMsg, url.toString())); + } + await bot.telegram.sendMessage(targetUserId, mail, { + ...Markup.inlineKeyboard([...buttons]) + }); + }; + if (globalPush) { + const globalMsgs = i18n.getMessages(c.env.DEFAULT_LANG || 'zh'); for (const pushId of settings.globalMailPushList) { - await bot.telegram.sendMessage(pushId, mail, { - ...Markup.inlineKeyboard([ - ...miniAppButtons, - ]) - }); + await buildAndSend(pushId, globalMsgs); } } - if (!userId) { - return; + + if (userId) { + const userMsgs = await getTgMessages(c, undefined, userId); + await buildAndSend(userId, userMsgs); } - await bot.telegram.sendMessage(userId, mail, { - ...Markup.inlineKeyboard([ - ...miniAppButtons, - ]) - }); } diff --git a/worker/src/types.d.ts b/worker/src/types.d.ts index 78c77d8c..9ce33cde 100644 --- a/worker/src/types.d.ts +++ b/worker/src/types.d.ts @@ -84,6 +84,7 @@ type Bindings = { TELEGRAM_BOT_TOKEN: string TG_MAX_ADDRESS: number | undefined TG_BOT_INFO: string | object | undefined + TG_ALLOW_USER_LANG: string | boolean | undefined // webhook config FRONTEND_URL: string | undefined diff --git a/worker/src/user_api/bind_address.ts b/worker/src/user_api/bind_address.ts index 389a590f..78405f38 100644 --- a/worker/src/user_api/bind_address.ts +++ b/worker/src/user_api/bind_address.ts @@ -32,22 +32,23 @@ const UserBindAddressModule = { c: Context, user_id: number | string, address_id: number | string ) => { + const msgs = i18n.getMessagesbyContext(c); if (!address_id || !user_id) { - return c.text("No address or user token", 400) + return c.text(msgs.NoAddressOrUserTokenMsg, 400) } // check if address exists const db_address_id = await c.env.DB.prepare( `SELECT id FROM address where id = ?` ).bind(address_id).first("id"); if (!db_address_id) { - return c.text("Address not found", 400) + return c.text(msgs.AddressNotFoundMsg, 400) } // check if user exists const db_user_id = await c.env.DB.prepare( `SELECT id FROM users where id = ?` ).bind(user_id).first("id"); if (!db_user_id) { - return c.text("User not found", 400) + return c.text(msgs.UserNotFoundMsg, 400) } // check if binded const db_user_address_id = await c.env.DB.prepare( @@ -66,7 +67,7 @@ const UserBindAddressModule = { `SELECT COUNT(*) as count FROM users_address where user_id = ?` ).bind(user_id).first<{ count: number }>() || { count: 0 }; if (count >= maxAddressCount) { - return c.text("Max address count reached", 400) + return c.text(msgs.MaxAddressCountReachedMsg, 400) } } // bind @@ -75,36 +76,37 @@ const UserBindAddressModule = { `INSERT INTO users_address (user_id, address_id) VALUES (?, ?)` ).bind(user_id, address_id).run(); if (!success) { - return c.text("Failed to bind", 500) + return c.text(msgs.OperationFailedMsg, 500) } } catch (e) { const error = e as Error; if (error.message && error.message.includes("UNIQUE")) { - return c.text("Address already binded, please unbind first", 400) + return c.text(msgs.AddressAlreadyBindedMsg, 400) } - return c.text("Failed to bind", 500) + return c.text(msgs.OperationFailedMsg, 500) } return c.json({ success: true }) }, unbind: async (c: Context) => { + const msgs = i18n.getMessagesbyContext(c); const { user_id } = c.get("userPayload"); const { address_id } = await c.req.json(); if (!address_id || !user_id) { - return c.text("Invalid address or user token", 400) + return c.text(msgs.InvalidAddressOrUserTokenMsg, 400) } // check if address exists const db_address_id = await c.env.DB.prepare( `SELECT id FROM address where id = ?` ).bind(address_id).first("id"); if (!db_address_id) { - return c.text("Address not found", 400) + return c.text(msgs.AddressNotFoundMsg, 400) } // check if user exists const db_user_id = await c.env.DB.prepare( `SELECT id FROM users where id = ?` ).bind(user_id).first("id"); if (!db_user_id) { - return c.text("User not found", 400) + return c.text(msgs.UserNotFoundMsg, 400) } // unbind try { @@ -112,10 +114,10 @@ const UserBindAddressModule = { `DELETE FROM users_address where user_id = ? and address_id = ?` ).bind(user_id, address_id).run(); if (!success) { - return c.text("Failed to unbind", 500) + return c.text(msgs.OperationFailedMsg, 500) } } catch (e) { - return c.text("Failed to unbind", 500) + return c.text(msgs.OperationFailedMsg, 500) } return c.json({ success: true }) }, @@ -167,18 +169,19 @@ const UserBindAddressModule = { return results || []; }, getBindedAddressJwt: async (c: Context) => { + const msgs = i18n.getMessagesbyContext(c); const { address_id } = c.req.param(); // check binded const { user_id } = c.get("userPayload"); if (!address_id || !user_id) { - return c.text("Invalid address or user token", 400) + return c.text(msgs.InvalidAddressOrUserTokenMsg, 400) } // check users_address if address binded const db_user_id = await c.env.DB.prepare( `SELECT user_id FROM users_address WHERE address_id = ? and user_id = ?` ).bind(address_id, user_id).first("user_id"); if (!db_user_id) { - return c.text("Address not binded", 400) + return c.text(msgs.AddressNotBindedMsg, 400) } // generate jwt const name = await c.env.DB.prepare( @@ -193,6 +196,7 @@ const UserBindAddressModule = { }) }, transferAddress: async (c: Context) => { + const msgs = i18n.getMessagesbyContext(c); const { user_id } = c.get("userPayload"); const { address_id, target_user_email } = await c.req.json(); // check if address exists @@ -200,21 +204,21 @@ const UserBindAddressModule = { `SELECT name FROM address where id = ?` ).bind(address_id).first("name"); if (!address) { - return c.text("Address not found", 400) + return c.text(msgs.AddressNotFoundMsg, 400) } // check if user exists const db_user_id = await c.env.DB.prepare( `SELECT id FROM users where id = ?` ).bind(user_id).first("id"); if (!db_user_id) { - return c.text("User not found", 400) + return c.text(msgs.UserNotFoundMsg, 400) } // check if target user exists const target_user_id = await c.env.DB.prepare( `SELECT id FROM users where user_email = ?` ).bind(target_user_email).first("id"); if (!target_user_id) { - return c.text("Target user not found", 400) + return c.text(msgs.TargetUserNotFoundMsg, 400) } // check target user binded address count const value = await getJsonSetting(c, CONSTANTS.USER_SETTINGS_KEY); @@ -228,14 +232,14 @@ const UserBindAddressModule = { `SELECT COUNT(*) as count FROM users_address where user_id = ?` ).bind(target_user_id).first<{ count: number }>() || { count: 0 }; if (count >= maxAddressCount) { - return c.text("Target User Max address count reached", 400) + return c.text(msgs.MaxAddressCountReachedMsg, 400) } } // check if binded const db_user_address_id = await c.env.DB.prepare( `SELECT user_id FROM users_address where user_id = ? and address_id = ?` ).bind(user_id, address_id).first("user_id"); - if (!db_user_address_id) return c.text("Address not binded", 400) + if (!db_user_address_id) return c.text(msgs.AddressNotBindedMsg, 400) // unbind telegram address await unbindTelegramByAddress(c, address); // unbind user address @@ -244,10 +248,10 @@ const UserBindAddressModule = { `DELETE FROM users_address where user_id = ? and address_id = ?` ).bind(user_id, address_id).run(); if (!success) { - return c.text("Failed to unbind", 500) + return c.text(msgs.OperationFailedMsg, 500) } } catch (e) { - return c.text("Failed to unbind user", 500) + return c.text(msgs.OperationFailedMsg, 500) } // delete address await c.env.DB.prepare( @@ -258,7 +262,7 @@ const UserBindAddressModule = { `INSERT INTO address(name) VALUES(?)` ).bind(address).run(); if (!newAddressSuccess) { - throw new Error("Failed to create address") + throw new Error(msgs.FailedCreateAddressMsg) } await updateAddressUpdatedAt(c, address); // find new address id @@ -266,7 +270,7 @@ const UserBindAddressModule = { `SELECT id FROM address WHERE name = ?` ).bind(address).first("id"); if (!new_address_id) { - throw new Error("Failed to find new address id") + throw new Error(msgs.OperationFailedMsg) } // bind try { @@ -274,14 +278,14 @@ const UserBindAddressModule = { `INSERT INTO users_address (user_id, address_id) VALUES (?, ?)` ).bind(target_user_id, new_address_id).run(); if (!success) { - return c.text("Failed to bind", 500) + return c.text(msgs.OperationFailedMsg, 500) } } catch (e) { const error = e as Error; if (error.message && error.message.includes("UNIQUE")) { - return c.text("Address already binded, please unbind first", 400) + return c.text(msgs.AddressAlreadyBindedMsg, 400) } - return c.text("Failed to bind", 500) + return c.text(msgs.OperationFailedMsg, 500) } return c.json({ success: true }) } diff --git a/worker/src/user_api/oauth2.ts b/worker/src/user_api/oauth2.ts index ded1e371..ac6b7775 100644 --- a/worker/src/user_api/oauth2.ts +++ b/worker/src/user_api/oauth2.ts @@ -11,8 +11,7 @@ export default { getOauth2LoginUrl: async (c: Context) => { const settings = await getJsonSetting(c, CONSTANTS.OAUTH2_SETTINGS_KEY); const { clientID, state } = c.req.query(); - const lang = c.get("lang") || c.env.DEFAULT_LANG; - const msgs = i18n.getMessages(lang); + const msgs = i18n.getMessagesbyContext(c); const setting = settings?.find(s => s.clientID === clientID); if (!setting) { return c.text(msgs.Oauth2ClientIDNotFoundMsg, 400); @@ -22,8 +21,7 @@ export default { }, oauth2Login: async (c: Context) => { const { clientID, code } = await c.req.json<{ clientID?: string, code?: string }>(); - const lang = c.get("lang") || c.env.DEFAULT_LANG; - const msgs = i18n.getMessages(lang); + const msgs = i18n.getMessagesbyContext(c); if (!clientID || !code) { return c.text(msgs.Oauth2CliendIDOrCodeMissingMsg, 400); } diff --git a/worker/src/user_api/passkey.ts b/worker/src/user_api/passkey.ts index 876a9f57..2974573b 100644 --- a/worker/src/user_api/passkey.ts +++ b/worker/src/user_api/passkey.ts @@ -10,6 +10,7 @@ import { import { Passkey } from '../models'; import { PublicKeyCredentialRequestOptionsJSON } from '@simplewebauthn/types'; import { isoBase64URL } from '@simplewebauthn/server/helpers'; +import i18n from '../i18n'; export default { getPassKeys: async (c: Context) => { @@ -20,10 +21,11 @@ export default { return c.json(results); }, renamePassKey: async (c: Context) => { + const msgs = i18n.getMessagesbyContext(c); const user = c.get("userPayload"); const { passkey_id, passkey_name } = await c.req.json(); if (!passkey_name || passkey_name.length > 255) { - return c.text("Invalid passkey name", 400); + return c.text(msgs.InvalidPasskeyNameMsg, 400); } const { success } = await c.env.DB.prepare( `UPDATE user_passkeys SET passkey_name = ? WHERE user_id = ? AND passkey_id = ?` @@ -71,6 +73,7 @@ export default { return c.json(options); }, registerResponse: async (c: Context) => { + const msgs = i18n.getMessagesbyContext(c); const user = c.get("userPayload"); const { credential, origin, passkey_name } = await c.req.json(); // Verify the registration response @@ -90,7 +93,7 @@ export default { const { verified, registrationInfo } = verification; if (!verified || !registrationInfo) { - return c.text("Registration failed", 400); + return c.text(msgs.RegistrationFailedMsg, 400); } const { @@ -131,10 +134,11 @@ export default { return c.json(options); }, authenticateResponse: async (c: Context) => { + const msgs = i18n.getMessagesbyContext(c); const { domain, credential, origin } = await c.req.json(); const passkey_id = credential?.id; if (!passkey_id) { - return c.text("Invalid request", 400); + return c.text(msgs.InvalidInputMsg, 400); } const { user_id, counter, passkey } = await c.env.DB.prepare( `SELECT user_id, counter, passkey FROM user_passkeys WHERE passkey_id = ?` @@ -142,7 +146,7 @@ export default { counter: number; passkey: string; user_id: number; }>() || {}; if (!passkey) { - return c.text("Passkey not found", 404); + return c.text(msgs.PasskeyNotFoundMsg, 404); } const passkeyData = JSON.parse(passkey) as Passkey; // Verify the registration response @@ -166,7 +170,7 @@ export default { }); const { verified, authenticationInfo } = verification; if (!verified) { - return c.text("Authentication failed", 400); + return c.text(msgs.AuthenticationFailedMsg, 400); } if (authenticationInfo) { @@ -186,7 +190,7 @@ export default { `SELECT user_email FROM users WHERE id = ?` ).bind(user_id).first<{ user_email: string }>() || {}; if (!user_email) { - return c.text("User not found", 404); + return c.text(msgs.UserNotFoundMsg, 404); } // create jwt const jwt = await Jwt.sign({ diff --git a/worker/src/user_api/settings.ts b/worker/src/user_api/settings.ts index 4a592935..74781918 100644 --- a/worker/src/user_api/settings.ts +++ b/worker/src/user_api/settings.ts @@ -31,8 +31,7 @@ export default { }, settings: async (c: Context) => { const user = c.get("userPayload"); - const lang = c.get("lang") || c.env.DEFAULT_LANG; - const msgs = i18n.getMessages(lang); + const msgs = i18n.getMessagesbyContext(c); // check if user exists const db_user_id = await c.env.DB.prepare( `SELECT id FROM users where id = ?` diff --git a/worker/src/user_api/user.ts b/worker/src/user_api/user.ts index 8492040f..dfb71f56 100644 --- a/worker/src/user_api/user.ts +++ b/worker/src/user_api/user.ts @@ -10,8 +10,7 @@ import { sendMail } from "../mails_api/send_mail_api"; export default { verifyCode: async (c: Context) => { const { email, cf_token } = await c.req.json(); - const lang = c.get("lang") || c.env.DEFAULT_LANG; - const msgs = i18n.getMessages(lang); + const msgs = i18n.getMessagesbyContext(c); // check cf turnstile try { await checkCfTurnstile(c, cf_token); @@ -61,8 +60,7 @@ export default { register: async (c: Context) => { const value = await getJsonSetting(c, CONSTANTS.USER_SETTINGS_KEY); const settings = new UserSettings(value) - const lang = c.get("lang") || c.env.DEFAULT_LANG; - const msgs = i18n.getMessages(lang); + const msgs = i18n.getMessagesbyContext(c); // check enable if (!settings.enable) { return c.text(msgs.UserRegistrationDisabledMsg, 403); @@ -154,8 +152,7 @@ export default { }, login: async (c: Context) => { const { email, password } = await c.req.json(); - const lang = c.get("lang") || c.env.DEFAULT_LANG; - const msgs = i18n.getMessages(lang); + const msgs = i18n.getMessagesbyContext(c); if (!email || !password) return c.text(msgs.InvalidEmailOrPasswordMsg, 400); const { id: user_id, password: dbPassword } = await c.env.DB.prepare( `SELECT id, password FROM users where user_email = ?`