mirror of
https://github.com/dreamhunter2333/cloudflare_temp_email.git
synced 2026-07-02 21:01:55 +08:00
feat: add i18n support for backend API and Telegram bot (#797)
* feat: add i18n support for backend API and Telegram bot - Add comprehensive i18n support for all backend API error messages (zh/en) - Add /lang command for Telegram bot to set language preference - Add bilingual command descriptions for Telegram bot - Support per-user language preference stored in KV - Global push uses DEFAULT_LANG, user push uses saved preference 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: improve Telegram bot language preference feature - Add internationalized message for disabled language feature - Fix hardcoded English message in /lang command - Optimize getTgMessages calls (reduce from 3 to 1 call) - Remove verbose comments for better code clarity - Add TgLangFeatureDisabledMsg to i18n (zh/en) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -7,8 +7,7 @@ export default {
|
||||
// 修改地址密码
|
||||
changePassword: async (c: Context<HonoCustomType>) => {
|
||||
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<HonoCustomType>) => {
|
||||
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)) {
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { Context } from "hono";
|
||||
import { getBooleanValue } from "../utils";
|
||||
import i18n from "../i18n";
|
||||
|
||||
|
||||
export default {
|
||||
getAutoReply: async (c: Context<HonoCustomType>) => {
|
||||
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<HonoCustomType>) => {
|
||||
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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -14,9 +14,10 @@ import { handleListQuery } from '../common'
|
||||
export const api = new Hono<HonoCustomType>()
|
||||
|
||||
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<void> => {
|
||||
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<number>("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)
|
||||
}
|
||||
|
||||
@@ -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<HonoCustomType>): Promise<Response> {
|
||||
const msgs = i18n.getMessagesbyContext(c);
|
||||
const { address } = c.get("jwtPayload")
|
||||
const adminSettings = await c.env.KV.get<AdminWebhookSettings>(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<WebhookSettings>(
|
||||
`${CONSTANTS.WEBHOOK_KV_USER_SETTINGS_KEY}:${address}`, "json"
|
||||
@@ -18,10 +20,11 @@ async function getWebhookSettings(c: Context<HonoCustomType>): Promise<Response>
|
||||
|
||||
|
||||
async function saveWebhookSettings(c: Context<HonoCustomType>): Promise<Response> {
|
||||
const msgs = i18n.getMessagesbyContext(c);
|
||||
const { address } = c.get("jwtPayload")
|
||||
const adminSettings = await c.env.KV.get<AdminWebhookSettings>(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<WebhookSettings>();
|
||||
await c.env.KV.put(
|
||||
|
||||
Reference in New Issue
Block a user