From b9617ae1520688a2364cc212dd41d07eaff9065b Mon Sep 17 00:00:00 2001 From: ZL-Q Date: Tue, 28 Apr 2026 17:19:47 +0800 Subject: [PATCH] =?UTF-8?q?refactor(settings):=20=E7=BB=9F=E4=B8=80?= =?UTF-8?q?=E8=AF=AD=E8=A8=80=E8=AE=BE=E7=BD=AE=EF=BC=8C=E5=90=88=E5=B9=B6?= =?UTF-8?q?=20interface=5Flanguage=20=E5=92=8C=20ai=5Flanguage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 后端 Schema 将 interface_language 和 ai_language 合并为 language - 前端设置界面只保留一个语言选项 - AI 回复语言统一使用 language 设置 - 更新协议文档 - 新增数据库迁移脚本 --- .../settings/data/apis/profile_api.dart | 9 +- .../data/models/profile_settings.dart | 18 +- .../screens/general_settings_screen.dart | 28 +-- apps/lib/l10n/app_en.arb | 56 ++++- apps/lib/l10n/app_localizations.dart | 144 ++++++++++--- apps/lib/l10n/app_localizations_en.dart | 81 +++++-- apps/lib/l10n/app_localizations_zh.dart | 151 +++++++++++-- apps/lib/l10n/app_zh.arb | 56 ++++- apps/lib/l10n/app_zh_hant.arb | 31 ++- ...428_0002_update_profile_settings_schema.py | 201 ++++++++++++++++++ ...0428_0003_migrate_profile_settings_data.py | 52 +++++ .../core/agentscope/prompts/agent_prompt.py | 6 +- .../core/agentscope/prompts/system_prompt.py | 10 +- .../core/agentscope/prompts/worker_rules.py | 10 +- backend/src/core/agentscope/runtime/runner.py | 6 +- backend/src/schemas/shared/user.py | 14 +- backend/tests/unit/test_agentscope_prompts.py | 14 +- .../tests/unit/test_parse_profile_settings.py | 21 +- .../divination/divination-run-protocol.md | 2 +- docs/protocols/profile/profile-protocol.md | 6 +- 20 files changed, 740 insertions(+), 176 deletions(-) create mode 100644 backend/alembic/versions/20260428_0002_update_profile_settings_schema.py create mode 100644 backend/alembic/versions/20260428_0003_migrate_profile_settings_data.py diff --git a/apps/lib/features/settings/data/apis/profile_api.dart b/apps/lib/features/settings/data/apis/profile_api.dart index 44687ec..0ad3d94 100644 --- a/apps/lib/features/settings/data/apis/profile_api.dart +++ b/apps/lib/features/settings/data/apis/profile_api.dart @@ -41,10 +41,8 @@ class ProfileApi { 'settings': { 'version': settings.version, 'preferences': { - 'interface_language': settings.preferences.interfaceLanguage, - 'ai_language': settings.preferences.aiLanguage, + 'language': settings.preferences.language, 'timezone': settings.preferences.timezone, - 'country': settings.preferences.country, }, 'privacy': { 'can_sell': settings.privacy.canSell, @@ -109,12 +107,9 @@ class ProfileApi { : null; final preferences = preferencesRaw is Map ? PreferenceSettings( - interfaceLanguage: - (preferencesRaw['interface_language'] as String?) ?? 'zh-CN', - aiLanguage: (preferencesRaw['ai_language'] as String?) ?? 'zh-CN', + language: (preferencesRaw['language'] as String?) ?? 'zh-CN', timezone: (preferencesRaw['timezone'] as String?) ?? 'Asia/Shanghai', - country: (preferencesRaw['country'] as String?) ?? 'US', ) : const PreferenceSettings(); diff --git a/apps/lib/features/settings/data/models/profile_settings.dart b/apps/lib/features/settings/data/models/profile_settings.dart index 3edf2ab..f643c53 100644 --- a/apps/lib/features/settings/data/models/profile_settings.dart +++ b/apps/lib/features/settings/data/models/profile_settings.dart @@ -13,28 +13,20 @@ String displayLanguageLabel(AppLocalizations l10n, String languageTag) { class PreferenceSettings { const PreferenceSettings({ - this.interfaceLanguage = 'zh-CN', - this.aiLanguage = 'zh-CN', + this.language = 'zh-CN', this.timezone = 'Asia/Shanghai', - this.country = 'US', }); - final String interfaceLanguage; - final String aiLanguage; + final String language; final String timezone; - final String country; PreferenceSettings copyWith({ - String? interfaceLanguage, - String? aiLanguage, + String? language, String? timezone, - String? country, }) { return PreferenceSettings( - interfaceLanguage: interfaceLanguage ?? this.interfaceLanguage, - aiLanguage: aiLanguage ?? this.aiLanguage, + language: language ?? this.language, timezone: timezone ?? this.timezone, - country: country ?? this.country, ); } } @@ -151,7 +143,7 @@ class ProfileSettingsV1 { factory ProfileSettingsV1.defaultsForLocale(Locale locale) { final tag = languageTagFromLocale(locale); return ProfileSettingsV1( - preferences: PreferenceSettings(interfaceLanguage: tag, aiLanguage: tag), + preferences: PreferenceSettings(language: tag), ); } } diff --git a/apps/lib/features/settings/presentation/screens/general_settings_screen.dart b/apps/lib/features/settings/presentation/screens/general_settings_screen.dart index 1114afc..237a5be 100644 --- a/apps/lib/features/settings/presentation/screens/general_settings_screen.dart +++ b/apps/lib/features/settings/presentation/screens/general_settings_screen.dart @@ -54,40 +54,20 @@ class _GeneralSettingsScreenState extends State { children: [ SettingsMenuTile( icon: Icons.language_rounded, - title: l10n.settingsInterfaceLanguage, + title: l10n.settingsLanguage, subtitle: displayLanguageLabel( l10n, - _settings.preferences.interfaceLanguage, - ), - tint: colors.primary, - background: colors.surfaceContainerHighest, - onTap: () => _selectLanguage( - _settings.preferences.interfaceLanguage, - (lang) => setState(() { - _settings = _settings.copyWith( - preferences: _settings.preferences.copyWith( - interfaceLanguage: lang, - ), - ); - }), - ), - ), - SettingsMenuTile( - icon: Icons.smart_toy_rounded, - title: l10n.settingsAiLanguage, - subtitle: displayLanguageLabel( - l10n, - _settings.preferences.aiLanguage, + _settings.preferences.language, ), tint: colors.primary, background: colors.surfaceContainerHighest, showDivider: false, onTap: () => _selectLanguage( - _settings.preferences.aiLanguage, + _settings.preferences.language, (lang) => setState(() { _settings = _settings.copyWith( preferences: _settings.preferences.copyWith( - aiLanguage: lang, + language: lang, ), ); }), diff --git a/apps/lib/l10n/app_en.arb b/apps/lib/l10n/app_en.arb index 5b018d3..32c769b 100644 --- a/apps/lib/l10n/app_en.arb +++ b/apps/lib/l10n/app_en.arb @@ -83,8 +83,7 @@ "settingsSectionPrivacy": "Privacy", "settingsSectionNotification": "Notification Settings", "settingsAccountAndDataTitle": "Account Data", - "settingsInterfaceLanguage": "Interface Language", - "settingsAiLanguage": "AI Response Language", + "settingsLanguage": "Language", "settingsNotificationAllow": "Allow Notifications", "settingsNotificationVibration": "Allow Vibration", "settingsSectionAbout": "About", @@ -111,12 +110,8 @@ } }, "settingsCoinHeroSubtitle": "Credits will be used for casting and related services later.", - "settingsAiLanguage": "AI Response Language", - "settingsAiLanguageHint": "This field will align with profiles.settings.preferences.ai_language once the real preference flow is connected.", "settingsTimezone": "Time Zone", "settingsTimezoneHint": "This field will align with profiles.settings.preferences.timezone and later provide a real time zone picker.", - "settingsCountry": "Country/Region", - "settingsCountryHint": "This field will align with profiles.settings.preferences.country and later provide a region picker.", "settingsPrivacyProfileVisibility": "Profile Visibility", "settingsPrivacyPersonalization": "Personalization", "settingsPrivacyHistoryVisibility": "History Visibility", @@ -195,6 +190,7 @@ "settingsCoinPackPopular": "Popular Pack", "settingsCoinPackPremium": "Premium Pack", "settingsCoinPackPopularBadge": "Popular", + "settingsCoinPackNewUserBadge": "Only Once", "settingsPurchaseButton": "Pay Now", "settingsPurchasePending": "", "settingsCoinAmount": "{amount} credits", @@ -511,5 +507,51 @@ "paymentProductNotFound": "Product temporarily unavailable", "paymentStarterPackIneligible": "Starter pack is limited to one purchase per user", "paymentProductUnavailable": "Product temporarily unavailable", - "paymentPending": "Apple is processing, please wait" + "paymentPending": "Apple is processing, please wait", + "notifyCenterTitle": "Notifications", + "notifyMarkAllRead": "Mark all read", + "notifyLoadFailed": "Failed to load", + "notifyRetry": "Retry", + "notifyEmpty": "No notifications", + "timeJustNow": "Just now", + "timeMinutesAgo": "{minutes}m ago", + "@timeMinutesAgo": { + "placeholders": { + "minutes": { + "type": "int" + } + } + }, + "timeHoursAgo": "{hours}h ago", + "@timeHoursAgo": { + "placeholders": { + "hours": { + "type": "int" + } + } + }, + "timeDaysAgo": "{days}d ago", + "@timeDaysAgo": { + "placeholders": { + "days": { + "type": "int" + } + } + }, + "pointsLedgerTitle": "Points Ledger", + "pointsLedgerEmpty": "No records yet", + "pointsLedgerTypeRegister": "Registration bonus", + "pointsLedgerTypePurchase": "Purchase credits", + "pointsLedgerTypeConsume": "AI chat cost", + "pointsLedgerTypeAdjust": "System adjustment", + "pointsLedgerTypeRefund": "Refund", + "pointsLedgerBalance": "Balance {balance}", + "@pointsLedgerBalance": { + "placeholders": { + "balance": { + "type": "int" + } + } + }, + "retry": "Retry" } diff --git a/apps/lib/l10n/app_localizations.dart b/apps/lib/l10n/app_localizations.dart index be77345..b944f8b 100644 --- a/apps/lib/l10n/app_localizations.dart +++ b/apps/lib/l10n/app_localizations.dart @@ -513,17 +513,11 @@ abstract class AppLocalizations { /// **'账号数据'** String get settingsAccountAndDataTitle; - /// No description provided for @settingsInterfaceLanguage. + /// No description provided for @settingsLanguage. /// /// In zh, this message translates to: - /// **'界面语言'** - String get settingsInterfaceLanguage; - - /// No description provided for @settingsAiLanguage. - /// - /// In zh, this message translates to: - /// **'AI 回复语言'** - String get settingsAiLanguage; + /// **'语言'** + String get settingsLanguage; /// No description provided for @settingsNotificationAllow. /// @@ -597,12 +591,6 @@ abstract class AppLocalizations { /// **'点数可用于后续起卦与相关服务消费'** String get settingsCoinHeroSubtitle; - /// No description provided for @settingsAiLanguageHint. - /// - /// In zh, this message translates to: - /// **'该字段将对齐 profiles.settings.preferences.ai_language,后续接入真实偏好设置。'** - String get settingsAiLanguageHint; - /// No description provided for @settingsTimezone. /// /// In zh, this message translates to: @@ -615,18 +603,6 @@ abstract class AppLocalizations { /// **'该字段将对齐 profiles.settings.preferences.timezone,后续提供时区选择。'** String get settingsTimezoneHint; - /// No description provided for @settingsCountry. - /// - /// In zh, this message translates to: - /// **'国家/地区'** - String get settingsCountry; - - /// No description provided for @settingsCountryHint. - /// - /// In zh, this message translates to: - /// **'该字段将对齐 profiles.settings.preferences.country,后续提供国家或地区选择。'** - String get settingsCountryHint; - /// No description provided for @settingsPrivacyProfileVisibility. /// /// In zh, this message translates to: @@ -969,6 +945,12 @@ abstract class AppLocalizations { /// **'推荐'** String get settingsCoinPackPopularBadge; + /// No description provided for @settingsCoinPackNewUserBadge. + /// + /// In zh, this message translates to: + /// **'限购一次'** + String get settingsCoinPackNewUserBadge; + /// No description provided for @settingsPurchaseButton. /// /// In zh, this message translates to: @@ -2468,6 +2450,114 @@ abstract class AppLocalizations { /// In zh, this message translates to: /// **'Apple 正在处理中,请稍候'** String get paymentPending; + + /// No description provided for @notifyCenterTitle. + /// + /// In zh, this message translates to: + /// **'通知'** + String get notifyCenterTitle; + + /// No description provided for @notifyMarkAllRead. + /// + /// In zh, this message translates to: + /// **'全部已读'** + String get notifyMarkAllRead; + + /// No description provided for @notifyLoadFailed. + /// + /// In zh, this message translates to: + /// **'加载失败'** + String get notifyLoadFailed; + + /// No description provided for @notifyRetry. + /// + /// In zh, this message translates to: + /// **'重试'** + String get notifyRetry; + + /// No description provided for @notifyEmpty. + /// + /// In zh, this message translates to: + /// **'暂无通知'** + String get notifyEmpty; + + /// No description provided for @timeJustNow. + /// + /// In zh, this message translates to: + /// **'刚刚'** + String get timeJustNow; + + /// No description provided for @timeMinutesAgo. + /// + /// In zh, this message translates to: + /// **'{minutes}分钟前'** + String timeMinutesAgo(int minutes); + + /// No description provided for @timeHoursAgo. + /// + /// In zh, this message translates to: + /// **'{hours}小时前'** + String timeHoursAgo(int hours); + + /// No description provided for @timeDaysAgo. + /// + /// In zh, this message translates to: + /// **'{days}天前'** + String timeDaysAgo(int days); + + /// No description provided for @pointsLedgerTitle. + /// + /// In zh, this message translates to: + /// **'积分流水'** + String get pointsLedgerTitle; + + /// No description provided for @pointsLedgerEmpty. + /// + /// In zh, this message translates to: + /// **'暂无流水记录'** + String get pointsLedgerEmpty; + + /// No description provided for @pointsLedgerTypeRegister. + /// + /// In zh, this message translates to: + /// **'注册赠送'** + String get pointsLedgerTypeRegister; + + /// No description provided for @pointsLedgerTypePurchase. + /// + /// In zh, this message translates to: + /// **'购买积分包'** + String get pointsLedgerTypePurchase; + + /// No description provided for @pointsLedgerTypeConsume. + /// + /// In zh, this message translates to: + /// **'AI 对话消耗'** + String get pointsLedgerTypeConsume; + + /// No description provided for @pointsLedgerTypeAdjust. + /// + /// In zh, this message translates to: + /// **'系统调整'** + String get pointsLedgerTypeAdjust; + + /// No description provided for @pointsLedgerTypeRefund. + /// + /// In zh, this message translates to: + /// **'退款'** + String get pointsLedgerTypeRefund; + + /// No description provided for @pointsLedgerBalance. + /// + /// In zh, this message translates to: + /// **'余额 {balance}'** + String pointsLedgerBalance(int balance); + + /// No description provided for @retry. + /// + /// In zh, this message translates to: + /// **'重试'** + String get retry; } class _AppLocalizationsDelegate diff --git a/apps/lib/l10n/app_localizations_en.dart b/apps/lib/l10n/app_localizations_en.dart index cf1e25e..6fa1bd7 100644 --- a/apps/lib/l10n/app_localizations_en.dart +++ b/apps/lib/l10n/app_localizations_en.dart @@ -226,10 +226,7 @@ class AppLocalizationsEn extends AppLocalizations { String get settingsAccountAndDataTitle => 'Account Data'; @override - String get settingsInterfaceLanguage => 'Interface Language'; - - @override - String get settingsAiLanguage => 'AI Response Language'; + String get settingsLanguage => 'Language'; @override String get settingsNotificationAllow => 'Allow Notifications'; @@ -274,10 +271,6 @@ class AppLocalizationsEn extends AppLocalizations { String get settingsCoinHeroSubtitle => 'Credits will be used for casting and related services later.'; - @override - String get settingsAiLanguageHint => - 'This field will align with profiles.settings.preferences.ai_language once the real preference flow is connected.'; - @override String get settingsTimezone => 'Time Zone'; @@ -285,13 +278,6 @@ class AppLocalizationsEn extends AppLocalizations { String get settingsTimezoneHint => 'This field will align with profiles.settings.preferences.timezone and later provide a real time zone picker.'; - @override - String get settingsCountry => 'Country/Region'; - - @override - String get settingsCountryHint => - 'This field will align with profiles.settings.preferences.country and later provide a region picker.'; - @override String get settingsPrivacyProfileVisibility => 'Profile Visibility'; @@ -484,6 +470,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get settingsCoinPackPopularBadge => 'Popular'; + @override + String get settingsCoinPackNewUserBadge => 'Only Once'; + @override String get settingsPurchaseButton => 'Pay Now'; @@ -1302,4 +1291,66 @@ class AppLocalizationsEn extends AppLocalizations { @override String get paymentPending => 'Apple is processing, please wait'; + + @override + String get notifyCenterTitle => 'Notifications'; + + @override + String get notifyMarkAllRead => 'Mark all read'; + + @override + String get notifyLoadFailed => 'Failed to load'; + + @override + String get notifyRetry => 'Retry'; + + @override + String get notifyEmpty => 'No notifications'; + + @override + String get timeJustNow => 'Just now'; + + @override + String timeMinutesAgo(int minutes) { + return '${minutes}m ago'; + } + + @override + String timeHoursAgo(int hours) { + return '${hours}h ago'; + } + + @override + String timeDaysAgo(int days) { + return '${days}d ago'; + } + + @override + String get pointsLedgerTitle => 'Points Ledger'; + + @override + String get pointsLedgerEmpty => 'No records yet'; + + @override + String get pointsLedgerTypeRegister => 'Registration bonus'; + + @override + String get pointsLedgerTypePurchase => 'Purchase credits'; + + @override + String get pointsLedgerTypeConsume => 'AI chat cost'; + + @override + String get pointsLedgerTypeAdjust => 'System adjustment'; + + @override + String get pointsLedgerTypeRefund => 'Refund'; + + @override + String pointsLedgerBalance(int balance) { + return 'Balance $balance'; + } + + @override + String get retry => 'Retry'; } diff --git a/apps/lib/l10n/app_localizations_zh.dart b/apps/lib/l10n/app_localizations_zh.dart index 0307319..bc54898 100644 --- a/apps/lib/l10n/app_localizations_zh.dart +++ b/apps/lib/l10n/app_localizations_zh.dart @@ -224,10 +224,7 @@ class AppLocalizationsZh extends AppLocalizations { String get settingsAccountAndDataTitle => '账号数据'; @override - String get settingsInterfaceLanguage => '界面语言'; - - @override - String get settingsAiLanguage => 'AI 回复语言'; + String get settingsLanguage => '语言'; @override String get settingsNotificationAllow => '允许通知'; @@ -270,10 +267,6 @@ class AppLocalizationsZh extends AppLocalizations { @override String get settingsCoinHeroSubtitle => '点数可用于后续起卦与相关服务消费'; - @override - String get settingsAiLanguageHint => - '该字段将对齐 profiles.settings.preferences.ai_language,后续接入真实偏好设置。'; - @override String get settingsTimezone => '时区'; @@ -281,13 +274,6 @@ class AppLocalizationsZh extends AppLocalizations { String get settingsTimezoneHint => '该字段将对齐 profiles.settings.preferences.timezone,后续提供时区选择。'; - @override - String get settingsCountry => '国家/地区'; - - @override - String get settingsCountryHint => - '该字段将对齐 profiles.settings.preferences.country,后续提供国家或地区选择。'; - @override String get settingsPrivacyProfileVisibility => '资料可见性'; @@ -469,6 +455,9 @@ class AppLocalizationsZh extends AppLocalizations { @override String get settingsCoinPackPopularBadge => '推荐'; + @override + String get settingsCoinPackNewUserBadge => '限购一次'; + @override String get settingsPurchaseButton => '立即支付'; @@ -1243,6 +1232,68 @@ class AppLocalizationsZh extends AppLocalizations { @override String get paymentPending => 'Apple 正在处理中,请稍候'; + + @override + String get notifyCenterTitle => '通知'; + + @override + String get notifyMarkAllRead => '全部已读'; + + @override + String get notifyLoadFailed => '加载失败'; + + @override + String get notifyRetry => '重试'; + + @override + String get notifyEmpty => '暂无通知'; + + @override + String get timeJustNow => '刚刚'; + + @override + String timeMinutesAgo(int minutes) { + return '$minutes分钟前'; + } + + @override + String timeHoursAgo(int hours) { + return '$hours小时前'; + } + + @override + String timeDaysAgo(int days) { + return '$days天前'; + } + + @override + String get pointsLedgerTitle => '积分流水'; + + @override + String get pointsLedgerEmpty => '暂无流水记录'; + + @override + String get pointsLedgerTypeRegister => '注册赠送'; + + @override + String get pointsLedgerTypePurchase => '购买积分包'; + + @override + String get pointsLedgerTypeConsume => 'AI 对话消耗'; + + @override + String get pointsLedgerTypeAdjust => '系统调整'; + + @override + String get pointsLedgerTypeRefund => '退款'; + + @override + String pointsLedgerBalance(int balance) { + return '余额 $balance'; + } + + @override + String get retry => '重试'; } /// The translations for Chinese, using the Han script (`zh_Hant`). @@ -1397,10 +1448,7 @@ class AppLocalizationsZhHant extends AppLocalizationsZh { String get settingsAccountAndDataTitle => '帳號資料'; @override - String get settingsInterfaceLanguage => '介面語言'; - - @override - String get settingsAiLanguage => 'AI 回覆語言'; + String get settingsLanguage => '語言'; @override String get settingsNotificationAllow => '允許通知'; @@ -1562,6 +1610,9 @@ class AppLocalizationsZhHant extends AppLocalizationsZh { @override String get settingsCoinPackPopularBadge => '推薦'; + @override + String get settingsCoinPackNewUserBadge => '限購一次'; + @override String get settingsPurchaseButton => '立即支付'; @@ -2240,4 +2291,66 @@ class AppLocalizationsZhHant extends AppLocalizationsZh { @override String get paymentPending => 'Apple 正在處理中,請稍候'; + + @override + String get notifyCenterTitle => '通知'; + + @override + String get notifyMarkAllRead => '全部已讀'; + + @override + String get notifyLoadFailed => '載入失敗'; + + @override + String get notifyRetry => '重試'; + + @override + String get notifyEmpty => '暫無通知'; + + @override + String get timeJustNow => '剛剛'; + + @override + String timeMinutesAgo(int minutes) { + return '$minutes分鐘前'; + } + + @override + String timeHoursAgo(int hours) { + return '$hours小時前'; + } + + @override + String timeDaysAgo(int days) { + return '$days天前'; + } + + @override + String get pointsLedgerTitle => '積分流水'; + + @override + String get pointsLedgerEmpty => '暫無流水記錄'; + + @override + String get pointsLedgerTypeRegister => '註冊贈送'; + + @override + String get pointsLedgerTypePurchase => '購買積分包'; + + @override + String get pointsLedgerTypeConsume => 'AI 對話消耗'; + + @override + String get pointsLedgerTypeAdjust => '系統調整'; + + @override + String get pointsLedgerTypeRefund => '退款'; + + @override + String pointsLedgerBalance(int balance) { + return '餘額 $balance'; + } + + @override + String get retry => '重試'; } diff --git a/apps/lib/l10n/app_zh.arb b/apps/lib/l10n/app_zh.arb index 9ad2be0..3b9a46e 100644 --- a/apps/lib/l10n/app_zh.arb +++ b/apps/lib/l10n/app_zh.arb @@ -83,8 +83,7 @@ "settingsSectionPrivacy": "隐私设置", "settingsSectionNotification": "通知设置", "settingsAccountAndDataTitle": "账号数据", - "settingsInterfaceLanguage": "界面语言", - "settingsAiLanguage": "AI回复语言", + "settingsLanguage": "语言", "settingsNotificationAllow": "允许通知", "settingsNotificationVibration": "允许振动", "settingsSectionAbout": "关于", @@ -111,12 +110,8 @@ } }, "settingsCoinHeroSubtitle": "点数可用于后续起卦与相关服务消费", - "settingsAiLanguage": "AI 回复语言", - "settingsAiLanguageHint": "该字段将对齐 profiles.settings.preferences.ai_language,后续接入真实偏好设置。", "settingsTimezone": "时区", "settingsTimezoneHint": "该字段将对齐 profiles.settings.preferences.timezone,后续提供时区选择。", - "settingsCountry": "国家/地区", - "settingsCountryHint": "该字段将对齐 profiles.settings.preferences.country,后续提供国家或地区选择。", "settingsPrivacyProfileVisibility": "资料可见性", "settingsPrivacyPersonalization": "个性化推荐", "settingsPrivacyHistoryVisibility": "历史记录展示", @@ -195,6 +190,7 @@ "settingsCoinPackPopular": "常用加量包", "settingsCoinPackPremium": "高频进阶包", "settingsCoinPackPopularBadge": "推荐", + "settingsCoinPackNewUserBadge": "限购一次", "settingsPurchaseButton": "立即支付", "settingsPurchasePending": "", "settingsCoinAmount": "{amount} 点数", @@ -511,5 +507,51 @@ "paymentProductNotFound": "商品暂时不可用", "paymentStarterPackIneligible": "新手包每位用户仅限购买一次", "paymentProductUnavailable": "商品暂时不可用", - "paymentPending": "Apple 正在处理中,请稍候" + "paymentPending": "Apple 正在处理中,请稍候", + "notifyCenterTitle": "通知", + "notifyMarkAllRead": "全部已读", + "notifyLoadFailed": "加载失败", + "notifyRetry": "重试", + "notifyEmpty": "暂无通知", + "timeJustNow": "刚刚", + "timeMinutesAgo": "{minutes}分钟前", + "@timeMinutesAgo": { + "placeholders": { + "minutes": { + "type": "int" + } + } + }, + "timeHoursAgo": "{hours}小时前", + "@timeHoursAgo": { + "placeholders": { + "hours": { + "type": "int" + } + } + }, + "timeDaysAgo": "{days}天前", + "@timeDaysAgo": { + "placeholders": { + "days": { + "type": "int" + } + } + }, + "pointsLedgerTitle": "积分流水", + "pointsLedgerEmpty": "暂无流水记录", + "pointsLedgerTypeRegister": "注册赠送", + "pointsLedgerTypePurchase": "购买积分包", + "pointsLedgerTypeConsume": "AI 对话消耗", + "pointsLedgerTypeAdjust": "系统调整", + "pointsLedgerTypeRefund": "退款", + "pointsLedgerBalance": "余额 {balance}", + "@pointsLedgerBalance": { + "placeholders": { + "balance": { + "type": "int" + } + } + }, + "retry": "重试" } diff --git a/apps/lib/l10n/app_zh_hant.arb b/apps/lib/l10n/app_zh_hant.arb index e432330..e1fe564 100644 --- a/apps/lib/l10n/app_zh_hant.arb +++ b/apps/lib/l10n/app_zh_hant.arb @@ -55,8 +55,7 @@ "settingsSectionPrivacy": "隱私設定", "settingsSectionNotification": "通知設定", "settingsAccountAndDataTitle": "帳號資料", - "settingsInterfaceLanguage": "介面語言", - "settingsAiLanguage": "AI 回覆語言", + "settingsLanguage": "語言", "settingsNotificationAllow": "允許通知", "settingsNotificationVibration": "允許振動", "settingsSectionAbout": "關於", @@ -73,7 +72,6 @@ } }, "settingsCoinHeroSubtitle": "點數可用於後續起卦與相關服務消費", - "settingsAiLanguage": "AI 回覆語言", "settingsPrivacyProfileVisibility": "資料可見性", "settingsPrivacyPersonalization": "個人化推薦", "settingsPrivacyHistoryVisibility": "歷史記錄展示", @@ -128,6 +126,7 @@ "settingsCoinPackPopular": "常用加量包", "settingsCoinPackPremium": "高頻進階包", "settingsCoinPackPopularBadge": "推薦", + "settingsCoinPackNewUserBadge": "限購一次", "settingsPurchaseButton": "立即支付", "settingsPurchasePending": "", "settingsCoinAmount": "{amount} 點數", @@ -367,16 +366,25 @@ "timeMinutesAgo": "{minutes}分鐘前", "@timeMinutesAgo": { "placeholders": { + "minutes": { + "type": "int" + } } }, "timeHoursAgo": "{hours}小時前", "@timeHoursAgo": { "placeholders": { + "hours": { + "type": "int" + } } }, "timeDaysAgo": "{days}天前", "@timeDaysAgo": { "placeholders": { + "days": { + "type": "int" + } } }, "resultFocusPoints": "斷卦要點", @@ -413,5 +421,20 @@ "paymentProductNotFound": "商品暫時不可用", "paymentStarterPackIneligible": "新手包每位用戶僅限購買一次", "paymentProductUnavailable": "商品暫時不可用", - "paymentPending": "Apple 正在處理中,請稍候" + "paymentPending": "Apple 正在處理中,請稍候", + "pointsLedgerTitle": "積分流水", + "pointsLedgerEmpty": "暫無流水記錄", + "pointsLedgerTypeRegister": "註冊贈送", + "pointsLedgerTypePurchase": "購買積分包", + "pointsLedgerTypeConsume": "AI 對話消耗", + "pointsLedgerTypeAdjust": "系統調整", + "pointsLedgerTypeRefund": "退款", + "pointsLedgerBalance": "餘額 {balance}", + "@pointsLedgerBalance": { + "placeholders": { + "balance": { + "type": "int" + } + } + } } diff --git a/backend/alembic/versions/20260428_0002_update_profile_settings_schema.py b/backend/alembic/versions/20260428_0002_update_profile_settings_schema.py new file mode 100644 index 0000000..5747c34 --- /dev/null +++ b/backend/alembic/versions/20260428_0002_update_profile_settings_schema.py @@ -0,0 +1,201 @@ +"""update profile settings schema in trigger + +Revision ID: 20260428_0002 +Revises: 20260428_0001 +Create Date: 2026-04-28 + +""" + +from alembic import op + +revision = "20260428_0002" +down_revision = "20260428_0001" +branch_labels = None +depends_on = None + + +def upgrade() -> None: + op.execute( + """ + CREATE OR REPLACE FUNCTION public.initialize_profile_and_invite_code_on_signup() + RETURNS trigger + LANGUAGE plpgsql + SECURITY DEFINER + SET search_path = public + AS $$ + DECLARE + v_username text; + v_invite_code text; + v_referrer_id uuid; + v_attempts int := 0; + invite_code_value text; + BEGIN + v_username := 'user_' || substring(md5(new.id::text || clock_timestamp()::text || random()::text) from 1 for 6); + + INSERT INTO public.profiles (id, username, avatar_url, bio, settings) + VALUES ( + new.id, + v_username, + null, + null, + jsonb_build_object( + 'version', 1, + 'preferences', jsonb_build_object( + 'language', 'zh-CN', + 'timezone', 'Asia/Shanghai', + 'country', 'US' + ), + 'privacy', jsonb_build_object( + 'can_sell', false, + 'profile_visibility', 'public' + ), + 'notification', jsonb_build_object( + 'allow_notifications', true, + 'allow_vibration', true + ), + 'divination_tutorial', jsonb_build_object( + 'divination_entry_shown', false, + 'auto_divination_shown', false, + 'manual_divination_shown', false + ) + ) + ) + ON CONFLICT (id) DO NOTHING; + + LOOP + BEGIN + v_invite_code := public.generate_invite_code(); + INSERT INTO public.invite_codes (code, owner_id, status, used_count, max_uses, expires_at, reward_config) + VALUES ( + v_invite_code, + new.id, + 'active', + 0, + NULL, + NULL, + '{}'::jsonb + ); + EXIT; + EXCEPTION WHEN unique_violation THEN + v_attempts := v_attempts + 1; + IF v_attempts >= 100 THEN + RAISE EXCEPTION 'Failed to generate unique invite code after 100 attempts'; + END IF; + END; + END LOOP; + + invite_code_value := new.raw_user_meta_data ->> 'invite_code'; + IF invite_code_value IS NOT NULL AND length(invite_code_value) = 6 THEN + invite_code_value := upper(invite_code_value); + IF invite_code_value ~ '^[ABCDEFGHJKMNPQRSTUVWXYZ23456789]{6}$' THEN + UPDATE public.invite_codes + SET used_count = used_count + 1 + WHERE code = invite_code_value + AND status = 'active' + AND (max_uses IS NULL OR used_count < max_uses) + AND (expires_at IS NULL OR expires_at > NOW()) + RETURNING owner_id INTO v_referrer_id; + + IF v_referrer_id IS NOT NULL THEN + UPDATE public.profiles + SET referred_by = v_referrer_id + WHERE id = new.id; + END IF; + END IF; + END IF; + + RETURN NEW; + END; + $$; + """ + ) + + +def downgrade() -> None: + op.execute( + """ + CREATE OR REPLACE FUNCTION public.initialize_profile_and_invite_code_on_signup() + RETURNS trigger + LANGUAGE plpgsql + SECURITY DEFINER + SET search_path = public + AS $$ + DECLARE + v_username text; + v_invite_code text; + v_referrer_id uuid; + v_attempts int := 0; + invite_code_value text; + BEGIN + v_username := 'user_' || substring(md5(new.id::text || clock_timestamp()::text || random()::text) from 1 for 6); + + INSERT INTO public.profiles (id, username, avatar_url, bio, settings) + VALUES ( + new.id, + v_username, + null, + null, + jsonb_build_object( + 'version', 1, + 'preferences', jsonb_build_object( + 'interface_language', 'zh-CN', + 'ai_language', 'zh-CN', + 'timezone', 'Asia/Shanghai', + 'country', 'CN' + ), + 'privacy', jsonb_build_object('profile_visibility', 'public'), + 'notification', jsonb_build_object( + 'allow_notifications', true, + 'allow_vibration', true + ) + ) + ) + ON CONFLICT (id) DO NOTHING; + + LOOP + BEGIN + v_invite_code := public.generate_invite_code(); + INSERT INTO public.invite_codes (code, owner_id, status, used_count, max_uses, expires_at, reward_config) + VALUES ( + v_invite_code, + new.id, + 'active', + 0, + NULL, + NULL, + '{}'::jsonb + ); + EXIT; + EXCEPTION WHEN unique_violation THEN + v_attempts := v_attempts + 1; + IF v_attempts >= 100 THEN + RAISE EXCEPTION 'Failed to generate unique invite code after 100 attempts'; + END IF; + END; + END LOOP; + + invite_code_value := new.raw_user_meta_data ->> 'invite_code'; + IF invite_code_value IS NOT NULL AND length(invite_code_value) = 6 THEN + invite_code_value := upper(invite_code_value); + IF invite_code_value ~ '^[ABCDEFGHJKMNPQRSTUVWXYZ23456789]{6}$' THEN + UPDATE public.invite_codes + SET used_count = used_count + 1 + WHERE code = invite_code_value + AND status = 'active' + AND (max_uses IS NULL OR used_count < max_uses) + AND (expires_at IS NULL OR expires_at > NOW()) + RETURNING owner_id INTO v_referrer_id; + + IF v_referrer_id IS NOT NULL THEN + UPDATE public.profiles + SET referred_by = v_referrer_id + WHERE id = new.id; + END IF; + END IF; + END IF; + + RETURN NEW; + END; + $$; + """ + ) diff --git a/backend/alembic/versions/20260428_0003_migrate_profile_settings_data.py b/backend/alembic/versions/20260428_0003_migrate_profile_settings_data.py new file mode 100644 index 0000000..a55f0c4 --- /dev/null +++ b/backend/alembic/versions/20260428_0003_migrate_profile_settings_data.py @@ -0,0 +1,52 @@ +"""migrate existing profile settings to new schema + +Revision ID: 20260428_0003 +Revises: 20260428_0002 +Create Date: 2026-04-28 + +""" + +from alembic import op + +revision = "20260428_0003" +down_revision = "20260428_0002" +branch_labels = None +depends_on = None + + +def upgrade() -> None: + op.execute( + """ + UPDATE profiles + SET settings = jsonb_build_object( + 'version', 1, + 'preferences', jsonb_build_object( + 'language', COALESCE(settings->'preferences'->>'language', 'zh-CN'), + 'timezone', COALESCE(settings->'preferences'->>'timezone', 'Asia/Shanghai'), + 'country', COALESCE(settings->'preferences'->>'country', 'US') + ), + 'privacy', jsonb_build_object( + 'can_sell', COALESCE((settings->'privacy'->>'can_sell')::boolean, false), + 'profile_visibility', COALESCE(settings->'privacy'->>'profile_visibility', 'public') + ), + 'notification', jsonb_build_object( + 'allow_notifications', COALESCE((settings->'notification'->>'allow_notifications')::boolean, true), + 'allow_vibration', COALESCE((settings->'notification'->>'allow_vibration')::boolean, true) + ), + 'divination_tutorial', jsonb_build_object( + 'divination_entry_shown', COALESCE((settings->'divination_tutorial'->>'divination_entry_shown')::boolean, false), + 'auto_divination_shown', COALESCE((settings->'divination_tutorial'->>'auto_divination_shown')::boolean, false), + 'manual_divination_shown', COALESCE((settings->'divination_tutorial'->>'manual_divination_shown')::boolean, false) + ) + ) + WHERE settings IS NOT NULL; + """ + ) + + +def downgrade() -> None: + raise RuntimeError( + "20260428_0003 is a destructive JSON data-shape migration and cannot be " + "downgraded automatically. Restore profile settings from backup if rollback " + "to the previous schema is required." + ) diff --git a/backend/src/core/agentscope/prompts/agent_prompt.py b/backend/src/core/agentscope/prompts/agent_prompt.py index afa6f05..6ad7073 100644 --- a/backend/src/core/agentscope/prompts/agent_prompt.py +++ b/backend/src/core/agentscope/prompts/agent_prompt.py @@ -12,10 +12,10 @@ def build_agent_prompt( *, agent_type: AgentType, llm_config: SystemAgentLLMConfig | None = None, - ai_language: str = "zh-CN", + language: str = "zh-CN", ) -> str: _ = agent_type, llm_config - role_playing = get_worker_role_playing(ai_language) - output_rules = get_worker_output_rules(ai_language) + role_playing = get_worker_role_playing(language) + output_rules = get_worker_output_rules(language) content = f"[role_playing]\n{role_playing}\n\n[output_json_rules]\n{output_rules}" return wrap_section("agent", content) diff --git a/backend/src/core/agentscope/prompts/system_prompt.py b/backend/src/core/agentscope/prompts/system_prompt.py index 081e273..1deded9 100644 --- a/backend/src/core/agentscope/prompts/system_prompt.py +++ b/backend/src/core/agentscope/prompts/system_prompt.py @@ -39,8 +39,8 @@ def _build_safety_section() -> str: ) -def _build_output_rules(*, ai_language: str) -> str: - lang_label = _get_language_label(ai_language) +def _build_output_rules(*, language: str) -> str: + lang_label = _get_language_label(language) rules = [ "[Language Requirement]", f"- You MUST respond in {lang_label}.", @@ -68,7 +68,7 @@ def _build_time_context(*, now_utc: datetime | None) -> str: def build_system_prompt( *, agent_type: AgentType, - ai_language: str, + language: str, llm_config: SystemAgentLLMConfig | None = None, tools: Sequence[Tool | dict[str, Any]] | None = None, now_utc: datetime | None = None, @@ -79,9 +79,9 @@ def build_system_prompt( build_agent_prompt( agent_type=agent_type, llm_config=llm_config, - ai_language=ai_language, + language=language, ), build_tools_prompt(tools=tools) if tools else None, - _build_output_rules(ai_language=ai_language), + _build_output_rules(language=language), ] return "\n\n".join(item for item in sections if item).strip() diff --git a/backend/src/core/agentscope/prompts/worker_rules.py b/backend/src/core/agentscope/prompts/worker_rules.py index 910f228..2bcd66e 100644 --- a/backend/src/core/agentscope/prompts/worker_rules.py +++ b/backend/src/core/agentscope/prompts/worker_rules.py @@ -101,14 +101,14 @@ answer: A complete reading covering overall judgment, current situation, final t sign_level: Must be exactly one of: 上上签 / 中上签 / 中下签 / 下下签. Always use the Chinese enum value regardless of language.""" -def get_worker_role_playing(ai_language: str) -> str: - _ = ai_language +def get_worker_role_playing(language: str) -> str: + _ = language return _WORKER_ROLE_PLAYING -def get_worker_output_rules(ai_language: str) -> str: - if ai_language.startswith("en"): +def get_worker_output_rules(language: str) -> str: + if language.startswith("en"): return _WORKER_OUTPUT_RULES_EN - if ai_language.startswith("zh-Hant") or ai_language.startswith("zh_Hant"): + if language.startswith("zh-Hant") or language.startswith("zh_Hant"): return _WORKER_OUTPUT_RULES_ZH_HANT return _WORKER_OUTPUT_RULES_ZH_CN diff --git a/backend/src/core/agentscope/runtime/runner.py b/backend/src/core/agentscope/runtime/runner.py index 773acf4..a8f4fd6 100644 --- a/backend/src/core/agentscope/runtime/runner.py +++ b/backend/src/core/agentscope/runtime/runner.py @@ -265,15 +265,15 @@ class AgentScopeRunner: emit_text_events=True, emit_tool_events=False, ) - ai_language = "zh-CN" + language = "zh-CN" if user_context.settings is not None: prefs = getattr(user_context.settings, "preferences", None) if prefs is not None: - ai_language = getattr(prefs, "ai_language", "zh-CN") or "zh-CN" + language = getattr(prefs, "language", "zh-CN") or "zh-CN" system_prompt = build_system_prompt( agent_type=stage_config.agent_type, - ai_language=ai_language, + language=language, llm_config=stage_config.llm_config, tools=None, now_utc=datetime.now(timezone.utc), diff --git a/backend/src/schemas/shared/user.py b/backend/src/schemas/shared/user.py index 01dcc1c..e764e35 100644 --- a/backend/src/schemas/shared/user.py +++ b/backend/src/schemas/shared/user.py @@ -11,12 +11,10 @@ _COUNTRY_PATTERN = re.compile(r"^[A-Z]{2}$") class PreferenceSettings(BaseModel): - interface_language: str = "zh-CN" - ai_language: str = "zh-CN" + language: str = "zh-CN" timezone: str = "Asia/Shanghai" - country: str = "US" - @field_validator("interface_language", "ai_language") + @field_validator("language") @classmethod def validate_language(cls, value: str) -> str: if not _BCP47_PATTERN.fullmatch(value): @@ -32,14 +30,6 @@ class PreferenceSettings(BaseModel): raise ValueError("timezone must be a valid IANA timezone") from exc return value - @field_validator("country") - @classmethod - def validate_country(cls, value: str) -> str: - normalized = value.upper() - if not _COUNTRY_PATTERN.fullmatch(normalized): - raise ValueError("country must be an ISO 3166-1 alpha-2 code") - return normalized - class NotificationSettings(BaseModel): allow_notifications: bool = True diff --git a/backend/tests/unit/test_agentscope_prompts.py b/backend/tests/unit/test_agentscope_prompts.py index 9d8123d..e82553a 100644 --- a/backend/tests/unit/test_agentscope_prompts.py +++ b/backend/tests/unit/test_agentscope_prompts.py @@ -5,10 +5,10 @@ from core.agentscope.prompts.system_prompt import build_system_prompt from schemas.agent.system_agent import AgentType, SystemAgentLLMConfig -def test_system_prompt_enforces_ai_language_en() -> None: +def test_system_prompt_enforces_language_en() -> None: prompt = build_system_prompt( agent_type=AgentType.WORKER, - ai_language="en-US", + language="en-US", llm_config=SystemAgentLLMConfig(), ) @@ -17,10 +17,10 @@ def test_system_prompt_enforces_ai_language_en() -> None: assert "" in prompt -def test_system_prompt_enforces_ai_language_zh_cn() -> None: +def test_system_prompt_enforces_language_zh_cn() -> None: prompt = build_system_prompt( agent_type=AgentType.WORKER, - ai_language="zh-CN", + language="zh-CN", llm_config=SystemAgentLLMConfig(), ) @@ -31,7 +31,7 @@ def test_system_prompt_enforces_ai_language_zh_cn() -> None: def test_system_prompt_safety_restricts_to_divination() -> None: prompt = build_system_prompt( agent_type=AgentType.WORKER, - ai_language="zh-CN", + language="zh-CN", llm_config=SystemAgentLLMConfig(), ) @@ -41,7 +41,7 @@ def test_system_prompt_safety_restricts_to_divination() -> None: def test_system_prompt_does_not_contain_env_section() -> None: prompt = build_system_prompt( agent_type=AgentType.WORKER, - ai_language="zh-CN", + language="zh-CN", llm_config=SystemAgentLLMConfig(), ) @@ -65,7 +65,7 @@ def test_agent_prompt_keeps_only_identity_and_domain_flow() -> None: def test_system_prompt_sections_are_not_duplicated() -> None: prompt = build_system_prompt( agent_type=AgentType.WORKER, - ai_language="zh-CN", + language="zh-CN", llm_config=SystemAgentLLMConfig(), ) diff --git a/backend/tests/unit/test_parse_profile_settings.py b/backend/tests/unit/test_parse_profile_settings.py index 613aab2..612ff33 100644 --- a/backend/tests/unit/test_parse_profile_settings.py +++ b/backend/tests/unit/test_parse_profile_settings.py @@ -15,8 +15,7 @@ class TestParseProfileSettings: raw = { "version": 1, "preferences": { - "interface_language": "en-US", - "ai_language": "en-US", + "language": "en-US", "timezone": "America/New_York", "country": "US", }, @@ -31,8 +30,7 @@ class TestParseProfileSettings: assert isinstance(result, ProfileSettingsV1) assert result.version == 1 assert isinstance(result.preferences, PreferenceSettings) - assert result.preferences.interface_language == "en-US" - assert result.preferences.ai_language == "en-US" + assert result.preferences.language == "en-US" assert result.preferences.timezone == "America/New_York" assert result.preferences.country == "US" assert isinstance(result.notification, NotificationSettings) @@ -45,8 +43,7 @@ class TestParseProfileSettings: assert isinstance(result, ProfileSettingsV1) assert result.version == 1 assert isinstance(result.preferences, PreferenceSettings) - assert result.preferences.interface_language == "zh-CN" - assert result.preferences.ai_language == "zh-CN" + assert result.preferences.language == "zh-CN" assert result.preferences.timezone == "Asia/Shanghai" assert result.preferences.country == "US" assert isinstance(result.notification, NotificationSettings) @@ -56,13 +53,12 @@ class TestParseProfileSettings: def test_parse_profile_settings_with_partial_preferences(self) -> None: raw = { "preferences": { - "interface_language": "en-US", + "language": "en-US", }, } result = parse_profile_settings(raw) - assert result.preferences.interface_language == "en-US" - assert result.preferences.ai_language == "zh-CN" + assert result.preferences.language == "en-US" assert result.preferences.timezone == "Asia/Shanghai" assert result.preferences.country == "US" @@ -93,7 +89,7 @@ class TestParseProfileSettings: def test_parse_profile_settings_invalid_language_uses_default(self) -> None: raw = { "preferences": { - "interface_language": "invalid-language", + "language": "invalid-language", }, } with pytest.raises(ValueError, match="language must be a valid BCP-47 tag"): @@ -120,8 +116,7 @@ class TestParseProfileSettings: def test_profile_settings_v1_model_dump(self) -> None: settings = ProfileSettingsV1( preferences=PreferenceSettings( - interface_language="en-US", - ai_language="en-US", + language="en-US", timezone="UTC", country="US", ), @@ -132,6 +127,6 @@ class TestParseProfileSettings: ) dumped = settings.model_dump(mode="json") - assert dumped["preferences"]["interface_language"] == "en-US" + assert dumped["preferences"]["language"] == "en-US" assert dumped["notification"]["allow_notifications"] is True assert dumped["notification"]["allow_vibration"] is False diff --git a/docs/protocols/divination/divination-run-protocol.md b/docs/protocols/divination/divination-run-protocol.md index a771e01..53bbc3c 100644 --- a/docs/protocols/divination/divination-run-protocol.md +++ b/docs/protocols/divination/divination-run-protocol.md @@ -237,7 +237,7 @@ Field notes: - `runtime_mode=chat` fields: `status`, `sign_level`, `conclusion`, `focus_points`, `advice`, `keywords`, `answer`, `error`, `divination_derived`. - `runtime_mode=follow_up` fields: `status`, `answer`, `error`. - `runtime_mode=follow_up` MUST NOT include `sign_level`, `conclusion`, `focus_points`, `advice`, `keywords`, `divination_derived`. -- Language rule: `conclusion`, `focus_points`, `advice`, `keywords`, `answer` should follow user `ai_language` preference unless user explicitly requests otherwise. +- Language rule: `conclusion`, `focus_points`, `advice`, `keywords`, `answer` should follow user `language` preference unless user explicitly requests otherwise. - Canonical six-yao terms remain Chinese in protocol text (for example: 世爻、应爻、动爻、静爻、六亲、六神、伏神、月建、日辰、月破、日冲、空亡、五行旺衰). Frontend should combine: diff --git a/docs/protocols/profile/profile-protocol.md b/docs/protocols/profile/profile-protocol.md index 46520d1..c4c3932 100644 --- a/docs/protocols/profile/profile-protocol.md +++ b/docs/protocols/profile/profile-protocol.md @@ -46,8 +46,7 @@ Response: "settings": { "version": 1, "preferences": { - "interface_language": "zh-CN", - "ai_language": "zh-CN", + "language": "zh-CN", "timezone": "Asia/Shanghai", "country": "CN" }, @@ -111,8 +110,7 @@ Request: "settings": { "version": 1, "preferences": { - "interface_language": "zh-CN", - "ai_language": "zh-CN", + "language": "zh-CN", "timezone": "Asia/Shanghai", "country": "CN" },