273 lines
9.0 KiB
Go
273 lines
9.0 KiB
Go
package i18n
|
|
|
|
import (
|
|
"strings"
|
|
)
|
|
|
|
// Lang represents a supported language code.
|
|
type Lang string
|
|
|
|
const (
|
|
EN Lang = "en"
|
|
ZH Lang = "zh"
|
|
)
|
|
|
|
// translations holds all UI strings keyed by language then translation key.
|
|
var translations = map[Lang]map[string]string{
|
|
EN: {
|
|
// Nav
|
|
"site_title": "Go Blog",
|
|
"home": "Home",
|
|
"login": "Login",
|
|
"dashboard": "Dashboard",
|
|
"logout": "Logout",
|
|
|
|
// Home page
|
|
"home_welcome": "Welcome to Go Blog",
|
|
"home_subtitle": "A simple, fast blog engine built with Go, Gin, and Tailwind CSS.",
|
|
"home_sign_in": "Sign In",
|
|
"home_to_dash": "Dashboard",
|
|
"home_latest": "Latest Posts",
|
|
"home_read_more": "Read more",
|
|
"home_post1_tit": "Getting Started",
|
|
"home_post1_dsc": "Welcome to your new blog! This is a placeholder post. Start writing to see your content here.",
|
|
"home_post2_tit": "Hello World",
|
|
"home_post2_dsc": "Blog posts will appear here once you start publishing from the admin dashboard.",
|
|
"home_post3_tit": "Built with Go",
|
|
"home_post3_dsc": "This blog engine uses Go, Gin web framework, GORM, and Tailwind CSS for a modern experience.",
|
|
"home_no_posts": "No published posts yet.",
|
|
|
|
// Login page
|
|
"login_title": "Sign In",
|
|
"login_username": "Username",
|
|
"login_password": "Password",
|
|
"login_ph_user": "Enter your username",
|
|
"login_ph_pass": "Enter your password",
|
|
"login_submit": "Sign In",
|
|
"login_error": "Invalid username or password.",
|
|
"login_required": "Please fill in all fields.",
|
|
|
|
// Dashboard
|
|
"dash_title": "Dashboard",
|
|
"dash_welcome": "Welcome back,",
|
|
"dash_posts": "Posts",
|
|
"dash_pages": "Pages",
|
|
"dash_users": "Users",
|
|
"dash_published": "Published",
|
|
"dash_mgmt_title": "Blog Management",
|
|
"dash_mgmt_desc": "Post management, categories, and settings will appear here in future updates.",
|
|
"dash_new_article": "New Article",
|
|
"dash_page_title": "Dashboard",
|
|
|
|
// Footer
|
|
"footer_text": "© 2026 Go Blog. Powered by Go & Gin.",
|
|
|
|
// Page titles
|
|
"page_home": "Home",
|
|
"page_login": "Login",
|
|
|
|
// Language switcher
|
|
"lang_switch": "中文",
|
|
|
|
// Profile dropdown & page
|
|
"profile": "Profile",
|
|
"admin_panel": "Admin Panel",
|
|
"profile_title": "Edit Profile",
|
|
"profile_avatar": "Avatar",
|
|
"profile_change_avatar": "Change Avatar",
|
|
"profile_display_name": "Display Name",
|
|
"profile_gender": "Gender",
|
|
"profile_birthday": "Birthday",
|
|
"profile_email": "Email",
|
|
"profile_change_password": "Change Password",
|
|
"profile_current_password": "Current Password",
|
|
"profile_new_password": "New Password",
|
|
"profile_save": "Save Changes",
|
|
"profile_saved": "Profile updated.",
|
|
"profile_wrong_password": "Current password is incorrect.",
|
|
"gender_male": "Male",
|
|
"gender_female": "Female",
|
|
"gender_other": "Other",
|
|
|
|
// Avatar cropping
|
|
"crop_avatar_title": "Crop Avatar",
|
|
"crop_cancel": "Cancel",
|
|
"crop_confirm": "Confirm",
|
|
"crop_uploading": "Uploading...",
|
|
"crop_success": "Avatar updated.",
|
|
"crop_error": "Failed to upload avatar. Please try again.",
|
|
|
|
// Article creation
|
|
"article_create_title": "Create Article",
|
|
"article_title": "Title",
|
|
"article_slug": "Slug",
|
|
"article_slug_hint": "Auto-generated from title if empty",
|
|
"article_summary": "Summary",
|
|
"article_content": "Content",
|
|
"article_cover": "Cover Image URL",
|
|
"article_status": "Status",
|
|
"article_draft": "Draft",
|
|
"article_published": "Published",
|
|
"article_is_top": "Pin to top",
|
|
"article_save_draft": "Save Draft",
|
|
"article_publish": "Publish",
|
|
"article_created": "Article created successfully.",
|
|
"article_error": "Failed to create article.",
|
|
"article_new": "New Article",
|
|
"article_title_required": "Title is required.",
|
|
"article_content_required": "Content is required.",
|
|
"article_back_home": "Back to Home",
|
|
"article_not_found": "Article not found.",
|
|
},
|
|
ZH: {
|
|
// 导航
|
|
"site_title": "Go 博客",
|
|
"home": "首页",
|
|
"login": "登录",
|
|
"dashboard": "后台",
|
|
"logout": "退出",
|
|
|
|
// 首页
|
|
"home_welcome": "欢迎来到 Go Blog",
|
|
"home_subtitle": "一个使用 Go、Gin 和 Tailwind CSS 构建的简洁快速的博客引擎。",
|
|
"home_sign_in": "登录",
|
|
"home_to_dash": "后台管理",
|
|
"home_latest": "最新文章",
|
|
"home_read_more": "阅读全文",
|
|
"home_post1_tit": "入门指南",
|
|
"home_post1_dsc": "欢迎来到你的新博客!这是一篇占位文章。开始写作后,你的内容将显示在这里。",
|
|
"home_post2_tit": "你好,世界",
|
|
"home_post2_dsc": "当你从后台管理面板发布文章后,博客文章将显示在这里。",
|
|
"home_post3_tit": "使用 Go 构建",
|
|
"home_post3_dsc": "此博客引擎使用 Go、Gin Web 框架、GORM 和 Tailwind CSS 构建,提供现代化的体验。",
|
|
"home_no_posts": "暂无已发布的文章。",
|
|
|
|
// 登录页
|
|
"login_title": "登录",
|
|
"login_username": "用户名",
|
|
"login_password": "密码",
|
|
"login_ph_user": "请输入用户名",
|
|
"login_ph_pass": "请输入密码",
|
|
"login_submit": "登录",
|
|
"login_error": "用户名或密码错误。",
|
|
"login_required": "请填写所有字段。",
|
|
|
|
// 后台
|
|
"dash_title": "后台管理",
|
|
"dash_welcome": "欢迎回来,",
|
|
"dash_posts": "文章",
|
|
"dash_pages": "页面",
|
|
"dash_users": "用户",
|
|
"dash_published": "已发布",
|
|
"dash_mgmt_title": "博客管理",
|
|
"dash_mgmt_desc": "文章管理、分类和设置将在后续版本中添加。",
|
|
"dash_new_article": "新建文章",
|
|
"dash_page_title": "后台管理",
|
|
|
|
// 页脚
|
|
"footer_text": "© 2026 Go Blog。由 Go & Gin 驱动。",
|
|
"page_home": "首页",
|
|
"page_login": "登录",
|
|
|
|
// 语言切换
|
|
"lang_switch": "English",
|
|
|
|
// 个人中心下拉菜单 & 页面
|
|
"profile": "个人信息",
|
|
"admin_panel": "后台管理",
|
|
"profile_title": "编辑个人信息",
|
|
"profile_avatar": "头像",
|
|
"profile_change_avatar": "更换头像",
|
|
"profile_display_name": "显示名称",
|
|
"profile_gender": "性别",
|
|
"profile_birthday": "生日",
|
|
"profile_email": "邮箱",
|
|
"profile_change_password": "修改密码",
|
|
"profile_current_password": "当前密码",
|
|
"profile_new_password": "新密码",
|
|
"profile_save": "保存修改",
|
|
"profile_saved": "个人信息已更新。",
|
|
"profile_wrong_password": "当前密码错误。",
|
|
"gender_male": "男",
|
|
"gender_female": "女",
|
|
"gender_other": "其他",
|
|
|
|
// 头像裁剪
|
|
"crop_avatar_title": "裁剪头像",
|
|
"crop_cancel": "取消",
|
|
"crop_confirm": "确认",
|
|
"crop_uploading": "上传中...",
|
|
"crop_success": "头像已更新。",
|
|
"crop_error": "头像上传失败,请重试。",
|
|
|
|
// 文章创建
|
|
"article_create_title": "新建文章",
|
|
"article_title": "标题",
|
|
"article_slug": "Slug",
|
|
"article_slug_hint": "留空则从标题自动生成",
|
|
"article_summary": "摘要",
|
|
"article_content": "正文",
|
|
"article_cover": "封面图 URL",
|
|
"article_status": "状态",
|
|
"article_draft": "草稿",
|
|
"article_published": "已发布",
|
|
"article_is_top": "置顶",
|
|
"article_save_draft": "保存草稿",
|
|
"article_publish": "发布",
|
|
"article_created": "文章创建成功。",
|
|
"article_error": "创建文章失败。",
|
|
"article_new": "新建文章",
|
|
"article_title_required": "标题不能为空。",
|
|
"article_content_required": "正文不能为空。",
|
|
"article_back_home": "返回首页",
|
|
"article_not_found": "文章不存在。",
|
|
},
|
|
}
|
|
|
|
// T returns a copy of the translation map for the given language.
|
|
// Falls back to English if the language is not supported.
|
|
func T(l Lang) map[string]string {
|
|
t, ok := translations[l]
|
|
if !ok {
|
|
t = translations[EN]
|
|
}
|
|
// Return a shallow copy so callers cannot mutate the original map values.
|
|
out := make(map[string]string, len(t))
|
|
for k, v := range t {
|
|
out[k] = v
|
|
}
|
|
return out
|
|
}
|
|
|
|
// DetectLang parses the Accept-Language header and returns the best matching
|
|
// supported language. Returns EN for unsupported languages.
|
|
func DetectLang(acceptHeader string) Lang {
|
|
if acceptHeader == "" {
|
|
return EN
|
|
}
|
|
|
|
// Accept-Language format: "zh-CN,zh;q=0.9,en;q=0.8,en-US;q=0.7"
|
|
// Split by comma, then extract primary language from each entry.
|
|
entries := strings.Split(acceptHeader, ",")
|
|
for _, entry := range entries {
|
|
// Trim spaces and remove quality values.
|
|
entry = strings.TrimSpace(entry)
|
|
if idx := strings.Index(entry, ";"); idx != -1 {
|
|
entry = entry[:idx]
|
|
}
|
|
// Extract primary language (before any "-" subtag).
|
|
primary := strings.ToLower(entry)
|
|
if idx := strings.Index(primary, "-"); idx != -1 {
|
|
primary = primary[:idx]
|
|
}
|
|
switch primary {
|
|
case "zh":
|
|
return ZH
|
|
case "en":
|
|
return EN
|
|
}
|
|
}
|
|
|
|
return EN
|
|
}
|