@svadmin/core 0.30.0 → 0.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  // Minimal i18n — simple key-value translation
2
2
 
3
-
3
+ import { createContext } from 'svelte';
4
4
 
5
5
  const baseLocales: Record<string, Record<string, string>> = {
6
6
  'zh-CN': {
@@ -122,6 +122,7 @@ const baseLocales: Record<string, Record<string, string>> = {
122
122
  'auth.hasAccount': '已有账号?',
123
123
  'auth.forgotPasswordLink': '忘记密码?',
124
124
  'auth.forgotPasswordDescription': '输入您的邮箱地址,我们将发送重置密码的链接。',
125
+ 'auth.resetPasswordDescription': '设置一个新的登录密码。',
125
126
  'auth.resetLinkSent': '重置链接已发送,请查看您的邮箱。',
126
127
  'auth.passwordMismatch': '两次输入的密码不一致',
127
128
  'auth.emailRequired': '请输入邮箱',
@@ -309,6 +310,114 @@ const baseLocales: Record<string, Record<string, string>> = {
309
310
  'profile.currentPassword': '当前密码',
310
311
  'profile.passwordChanged': '密码已修改',
311
312
  'profile.updatePassword': '更新密码',
313
+ 'profile.newPassword': '新密码',
314
+ 'profile.company': '企业',
315
+ 'profile.gamer': '玩家',
316
+ 'profile.companyName': '公司名称',
317
+ 'profile.industry': '行业',
318
+ 'profile.website': '网站',
319
+ 'profile.employees': '员工',
320
+ 'profile.founded': '成立于',
321
+ 'profile.companyDescription': '公司介绍',
322
+ 'profile.level': '等级',
323
+ 'profile.gamesPlayed': '游戏场次',
324
+ 'profile.winRate': '胜率',
325
+ 'profile.rank': '段位',
326
+ // Public profile
327
+ 'publicProfile.projects': '项目',
328
+ 'publicProfile.projects2Col': '两栏项目',
329
+ 'publicProfile.projects3Col': '三栏项目',
330
+ 'publicProfile.activity': '活动',
331
+ 'publicProfile.teams': '团队',
332
+ 'publicProfile.followers': '关注者',
333
+ 'publicProfile.following': '正在关注',
334
+ 'publicProfile.editProfile': '编辑资料',
335
+ 'publicProfile.sendMessage': '发送消息',
336
+ 'publicProfile.joinedDate': '{date} 加入',
337
+ 'publicProfile.noProjects': '暂无项目',
338
+ 'publicProfile.noActivity': '暂无活动',
339
+ 'publicProfile.noTeams': '暂无团队',
340
+ 'publicProfile.projectMembers': '{count} 名成员',
341
+ 'publicProfile.projectTasks': '{count} 个任务',
342
+ 'publicProfile.viewProfile': '查看资料',
343
+ 'publicProfile.activityPosted': '发布了动态',
344
+ 'publicProfile.activityCommented': '评论了',
345
+ 'publicProfile.activityJoined': '加入了',
346
+ 'publicProfile.activityCreated': '创建了',
347
+ // Account demo pages
348
+ 'account.getStarted': '开始使用',
349
+ 'account.getStartedDescription': '按照步骤补齐资料、连接服务并邀请团队成员。',
350
+ 'account.uploadAvatar': '上传头像',
351
+ 'account.setupProfile': '完善个人资料',
352
+ 'account.connectProvider': '连接身份提供方',
353
+ 'account.setupTeam': '创建团队空间',
354
+ 'account.completeSetup': '完成设置',
355
+ 'account.skipSetup': '跳过',
356
+ 'account.step': '第 {current} / {total} 步',
357
+ 'account.companyProfile': '公司资料',
358
+ 'account.companyProfileDescription': '维护组织基础信息、行业属性和公开展示内容。',
359
+ 'account.settingsPlain': '纯净设置',
360
+ 'account.settingsPlainDescription': '以单页表单方式管理账号、安全与通知偏好。',
361
+ 'account.settingsSidebar': '侧边栏设置',
362
+ 'account.settingsSidebarDescription': '调整导航密度、默认折叠和菜单显示偏好。',
363
+ 'account.settingsEnterprise': '企业设置',
364
+ 'account.settingsEnterpriseDescription': '配置 SSO、会话策略、审计日志和合规保留周期。',
365
+ 'account.membersStarter': '成员入门',
366
+ 'account.membersStarterDescription': '快速邀请核心成员并检查初始角色配置。',
367
+ 'account.teamMembers': '团队成员',
368
+ 'account.teamMembersDescription': '按部门、角色、状态和最近活跃时间管理团队名册。',
369
+ 'account.inviteMember': '邀请成员',
370
+ 'account.active': '活跃',
371
+ 'account.invited': '已邀请',
372
+ 'account.inactive': '未活跃',
373
+ 'account.member': '成员',
374
+ 'account.role': '角色',
375
+ 'account.status': '状态',
376
+ 'account.joined': '加入时间',
377
+ 'account.importMembers': '导入成员',
378
+ 'account.importMembersDescription': '上传 CSV 或 Excel 文件,批量创建成员并预分配角色。',
379
+ 'account.importComplete': '导入完成',
380
+ 'account.imported': '已成功导入 {count} 位成员。',
381
+ 'account.dragOrClick': '拖拽文件到这里,或点击选择文件',
382
+ 'account.supportedFormats': '支持 CSV、XLSX,建议不超过 5MB。',
383
+ 'account.importProgress': '导入中',
384
+ 'account.startImport': '开始导入',
385
+ 'account.downloadTemplate': '下载模板',
386
+ 'account.securityLog': '安全日志',
387
+ 'account.securityLogDescription': '查看登录、密钥、2FA 和会话变更记录。',
388
+ // Security log
389
+ 'securityLog.event': '事件',
390
+ 'securityLog.ip': 'IP 地址',
391
+ 'securityLog.device': '设备',
392
+ 'securityLog.location': '位置',
393
+ 'securityLog.time': '时间',
394
+ 'securityLog.loginSuccess': '登录成功',
395
+ 'securityLog.loginFailed': '登录失败',
396
+ 'securityLog.apiKeyCreated': '创建 API 密钥',
397
+ 'securityLog.apiKeyRevoked': '撤销 API 密钥',
398
+ 'securityLog.twoFactorEnabled': '启用两步验证',
399
+ 'securityLog.passwordChanged': '修改密码',
400
+ 'securityLog.sessionRevoked': '撤销会话',
401
+ // Network
402
+ 'network.userCards': '用户卡片',
403
+ 'network.userCardsDescription': '以收藏卡片方式展示社区、资产和连接状态。',
404
+ 'network.teamCrew': '团队成员表',
405
+ 'network.teamCrewDescription': '用表格方式查看跨部门成员、在线状态和项目投入。',
406
+ 'network.nftCollection': '藏品',
407
+ 'network.floorPrice': '底价',
408
+ 'network.volume': '交易量',
409
+ 'network.connected': '已连接',
410
+ 'network.connect': '连接',
411
+ // Two-factor authentication
412
+ 'auth.twoFactorSetup': '两步验证设置',
413
+ 'auth.twoFactorScanQR': '扫描二维码',
414
+ 'auth.twoFactorEnterCode': '输入验证码',
415
+ 'auth.twoFactorSaveRecovery': '保存恢复码',
416
+ 'auth.twoFactorRecoveryHint': '请把恢复码保存在安全位置,手机不可用时可以用它登录。',
417
+ 'auth.twoFactorVerify': '验证代码',
418
+ 'auth.twoFactorRecovery': '恢复码',
419
+ 'auth.twoFactorVerifySuccess': '两步验证已启用',
420
+ 'auth.twoFactorDisable': '停用两步验证',
312
421
  // Tasks
313
422
  'task.drawerTitle': '任务中心',
314
423
  'task.listTitle': '任务列表',
@@ -492,6 +601,7 @@ const baseLocales: Record<string, Record<string, string>> = {
492
601
  'auth.hasAccount': 'Already have an account?',
493
602
  'auth.forgotPasswordLink': 'Forgot password?',
494
603
  'auth.forgotPasswordDescription': 'Enter your email address and we will send you a reset link.',
604
+ 'auth.resetPasswordDescription': 'Set a new sign-in password.',
495
605
  'auth.resetLinkSent': 'Reset link sent! Check your email.',
496
606
  'auth.passwordMismatch': 'Passwords do not match',
497
607
  'auth.emailRequired': 'Email is required',
@@ -679,6 +789,114 @@ const baseLocales: Record<string, Record<string, string>> = {
679
789
  'profile.currentPassword': 'Current Password',
680
790
  'profile.passwordChanged': 'Password updated successfully',
681
791
  'profile.updatePassword': 'Update Password',
792
+ 'profile.newPassword': 'New Password',
793
+ 'profile.company': 'Company',
794
+ 'profile.gamer': 'Gamer',
795
+ 'profile.companyName': 'Company Name',
796
+ 'profile.industry': 'Industry',
797
+ 'profile.website': 'Website',
798
+ 'profile.employees': 'Employees',
799
+ 'profile.founded': 'Founded',
800
+ 'profile.companyDescription': 'Company Description',
801
+ 'profile.level': 'Level',
802
+ 'profile.gamesPlayed': 'Games Played',
803
+ 'profile.winRate': 'Win Rate',
804
+ 'profile.rank': 'Rank',
805
+ // Public profile
806
+ 'publicProfile.projects': 'Projects',
807
+ 'publicProfile.projects2Col': 'Projects - 2 Columns',
808
+ 'publicProfile.projects3Col': 'Projects - 3 Columns',
809
+ 'publicProfile.activity': 'Activity',
810
+ 'publicProfile.teams': 'Teams',
811
+ 'publicProfile.followers': 'Followers',
812
+ 'publicProfile.following': 'Following',
813
+ 'publicProfile.editProfile': 'Edit Profile',
814
+ 'publicProfile.sendMessage': 'Send Message',
815
+ 'publicProfile.joinedDate': 'Joined {date}',
816
+ 'publicProfile.noProjects': 'No projects yet',
817
+ 'publicProfile.noActivity': 'No activity yet',
818
+ 'publicProfile.noTeams': 'No teams yet',
819
+ 'publicProfile.projectMembers': '{count} members',
820
+ 'publicProfile.projectTasks': '{count} tasks',
821
+ 'publicProfile.viewProfile': 'View Profile',
822
+ 'publicProfile.activityPosted': 'posted',
823
+ 'publicProfile.activityCommented': 'commented on',
824
+ 'publicProfile.activityJoined': 'joined',
825
+ 'publicProfile.activityCreated': 'created',
826
+ // Account demo pages
827
+ 'account.getStarted': 'Get Started',
828
+ 'account.getStartedDescription': 'Complete profile basics, connect services, and invite your team.',
829
+ 'account.uploadAvatar': 'Upload Avatar',
830
+ 'account.setupProfile': 'Set Up Profile',
831
+ 'account.connectProvider': 'Connect Identity Provider',
832
+ 'account.setupTeam': 'Set Up Team',
833
+ 'account.completeSetup': 'Complete Setup',
834
+ 'account.skipSetup': 'Skip',
835
+ 'account.step': 'Step {current} of {total}',
836
+ 'account.companyProfile': 'Company Profile',
837
+ 'account.companyProfileDescription': 'Maintain organization details, industry fields, and public profile content.',
838
+ 'account.settingsPlain': 'Settings - Plain',
839
+ 'account.settingsPlainDescription': 'Manage account, security, and notification preferences in one page.',
840
+ 'account.settingsSidebar': 'Settings - Sidebar',
841
+ 'account.settingsSidebarDescription': 'Adjust navigation density, default collapse, and menu display preferences.',
842
+ 'account.settingsEnterprise': 'Enterprise Settings',
843
+ 'account.settingsEnterpriseDescription': 'Configure SSO, session policy, audit logs, and compliance retention.',
844
+ 'account.membersStarter': 'Members Starter',
845
+ 'account.membersStarterDescription': 'Invite core members and check initial role setup.',
846
+ 'account.teamMembers': 'Team Members',
847
+ 'account.teamMembersDescription': 'Manage the team roster by department, role, status, and recent activity.',
848
+ 'account.inviteMember': 'Invite Member',
849
+ 'account.active': 'Active',
850
+ 'account.invited': 'Invited',
851
+ 'account.inactive': 'Inactive',
852
+ 'account.member': 'Member',
853
+ 'account.role': 'Role',
854
+ 'account.status': 'Status',
855
+ 'account.joined': 'Joined',
856
+ 'account.importMembers': 'Import Members',
857
+ 'account.importMembersDescription': 'Upload a CSV or Excel file to create members and pre-assign roles in bulk.',
858
+ 'account.importComplete': 'Import Complete',
859
+ 'account.imported': 'Imported {count} members successfully.',
860
+ 'account.dragOrClick': 'Drag a file here, or click to select one',
861
+ 'account.supportedFormats': 'CSV and XLSX supported, up to 5MB recommended.',
862
+ 'account.importProgress': 'Importing',
863
+ 'account.startImport': 'Start Import',
864
+ 'account.downloadTemplate': 'Download Template',
865
+ 'account.securityLog': 'Security Log',
866
+ 'account.securityLogDescription': 'Review login, key, 2FA, and session change events.',
867
+ // Security log
868
+ 'securityLog.event': 'Event',
869
+ 'securityLog.ip': 'IP Address',
870
+ 'securityLog.device': 'Device',
871
+ 'securityLog.location': 'Location',
872
+ 'securityLog.time': 'Time',
873
+ 'securityLog.loginSuccess': 'Login successful',
874
+ 'securityLog.loginFailed': 'Login failed',
875
+ 'securityLog.apiKeyCreated': 'API key created',
876
+ 'securityLog.apiKeyRevoked': 'API key revoked',
877
+ 'securityLog.twoFactorEnabled': 'Two-factor authentication enabled',
878
+ 'securityLog.passwordChanged': 'Password changed',
879
+ 'securityLog.sessionRevoked': 'Session revoked',
880
+ // Network
881
+ 'network.userCards': 'User Cards',
882
+ 'network.userCardsDescription': 'Showcase community profiles, asset collections, and connection status.',
883
+ 'network.teamCrew': 'Team Crew',
884
+ 'network.teamCrewDescription': 'Review cross-functional members, presence, and project commitment in a table.',
885
+ 'network.nftCollection': 'Collection',
886
+ 'network.floorPrice': 'Floor Price',
887
+ 'network.volume': 'Volume',
888
+ 'network.connected': 'Connected',
889
+ 'network.connect': 'Connect',
890
+ // Two-factor authentication
891
+ 'auth.twoFactorSetup': 'Two-Factor Authentication Setup',
892
+ 'auth.twoFactorScanQR': 'Scan QR Code',
893
+ 'auth.twoFactorEnterCode': 'Enter Verification Code',
894
+ 'auth.twoFactorSaveRecovery': 'Save Recovery Codes',
895
+ 'auth.twoFactorRecoveryHint': 'Keep recovery codes somewhere safe so you can sign in if your device is unavailable.',
896
+ 'auth.twoFactorVerify': 'Verify Code',
897
+ 'auth.twoFactorRecovery': 'Recovery Codes',
898
+ 'auth.twoFactorVerifySuccess': 'Two-factor authentication is enabled',
899
+ 'auth.twoFactorDisable': 'Disable Two-Factor Authentication',
682
900
  // Tasks
683
901
  'task.drawerTitle': 'Task Center',
684
902
  'task.listTitle': 'Tasks',
@@ -763,37 +981,212 @@ function detectLocale(): string {
763
981
 
764
982
  let currentLocale = $state(detectLocale());
765
983
 
766
- export function setLocale(locale: string): void {
767
- currentLocale = locale;
984
+ /**
985
+ * I18nProvider — pluggable translation boundary.
986
+ * When set, all t/getLocale/getAvailableLocales delegates to it first,
987
+ * falling back to the built-in locale tables. This lets apps integrate
988
+ * existing i18n systems (svelte-i18n, paraglide, etc.) without rewriting callsites.
989
+ */
990
+ export interface I18nProvider {
991
+ translate: (key: string, params?: Record<string, string | number>) => string;
992
+ getLocale: () => string;
993
+ setLocale: (locale: string) => void;
994
+ getAvailableLocales?: () => string[];
768
995
  }
769
996
 
770
- export function getLocale(): string {
771
- return currentLocale;
997
+ /** Owner-controlled values for one component-tree i18n scope. */
998
+ export interface I18nScopeOptions {
999
+ locale?: string;
1000
+ provider?: I18nProvider;
1001
+ onLocaleChange?: (locale: string) => void;
772
1002
  }
773
1003
 
774
- export function getAvailableLocales(): string[] {
775
- return Object.keys(locales);
1004
+ /**
1005
+ * Tree-local i18n state. `updateOwner` synchronizes props without emitting a
1006
+ * change event, while `setLocale` represents a change requested inside the tree.
1007
+ */
1008
+ export interface I18nScope {
1009
+ readonly locale: string;
1010
+ readonly provider: I18nProvider | undefined;
1011
+ translate: (key: string, params?: Record<string, string | number>) => string;
1012
+ setLocale: (locale: string) => void;
1013
+ setProvider: (provider: I18nProvider | undefined) => void;
1014
+ getAvailableLocales: () => string[];
1015
+ updateOwner: (options: I18nScopeOptions) => void;
776
1016
  }
777
1017
 
778
- export function t(key: string, params?: Record<string, string | number>): string {
779
- const locale = locales[currentLocale];
780
- let text = locale?.[key] ?? locales['en']?.[key] ?? key;
1018
+ const [readI18nScopeContext, writeI18nScopeContext] = createContext<I18nScope>();
1019
+
1020
+ function translateFromTables(
1021
+ locale: string,
1022
+ key: string,
1023
+ params?: Record<string, string | number>,
1024
+ ): string {
1025
+ const localeTable = locales[locale];
1026
+ let text = localeTable?.[key] ?? locales.en?.[key] ?? key;
781
1027
 
782
1028
  if (params) {
783
- for (const [k, v] of Object.entries(params)) {
784
- text = text.replaceAll(`{${k}}`, String(v));
1029
+ for (const [param, value] of Object.entries(params)) {
1030
+ text = text.replaceAll(`{${param}}`, String(value));
785
1031
  }
786
1032
  }
1033
+
787
1034
  return text;
788
1035
  }
789
1036
 
1037
+ function readCurrentI18nScope(): I18nScope | undefined {
1038
+ try {
1039
+ return readI18nScopeContext();
1040
+ } catch {
1041
+ // Context is only readable while Svelte owns the current render/effect.
1042
+ // Public facade calls made outside a component intentionally use legacy state.
1043
+ return undefined;
1044
+ }
1045
+ }
1046
+
1047
+ /** Create reactive i18n state that can be owned by one AdminApp tree. */
1048
+ export function createI18nScope(options: I18nScopeOptions = {}): I18nScope {
1049
+ const initialProviderLocale = options.provider?.getLocale();
1050
+ const initialLocale = options.locale ?? initialProviderLocale ?? detectLocale();
1051
+ let scopeLocale = $state(initialLocale);
1052
+ let scopeProvider = $state.raw<I18nProvider | undefined>(options.provider);
1053
+ let onLocaleChange = $state.raw<((locale: string) => void) | undefined>(options.onLocaleChange);
1054
+ let ownerControlsLocale = options.locale !== undefined;
1055
+
1056
+ function synchronizeProviderLocale(locale: string): void {
1057
+ if (scopeProvider && scopeProvider.getLocale() !== locale) {
1058
+ scopeProvider.setLocale(locale);
1059
+ }
1060
+ }
1061
+
1062
+ if (options.locale !== undefined) {
1063
+ synchronizeProviderLocale(options.locale);
1064
+ }
1065
+
1066
+ const scope: I18nScope = {
1067
+ get locale() {
1068
+ return scopeLocale;
1069
+ },
1070
+ get provider() {
1071
+ return scopeProvider;
1072
+ },
1073
+ translate(key, params) {
1074
+ // Establish the locale dependency even when a provider owns translation.
1075
+ const locale = scopeLocale;
1076
+ return scopeProvider?.translate(key, params) ?? translateFromTables(locale, key, params);
1077
+ },
1078
+ setLocale(locale) {
1079
+ const providerLocale = scopeProvider?.getLocale();
1080
+ const changed = scopeLocale !== locale || (scopeProvider !== undefined && providerLocale !== locale);
1081
+
1082
+ scopeLocale = locale;
1083
+ synchronizeProviderLocale(locale);
1084
+ if (changed) onLocaleChange?.(locale);
1085
+ },
1086
+ setProvider(provider) {
1087
+ if (scopeProvider === provider) return;
1088
+ scopeProvider = provider;
1089
+ // Provider replacement is owner configuration, not a tree locale action.
1090
+ scopeLocale = provider?.getLocale() || detectLocale();
1091
+ },
1092
+ getAvailableLocales() {
1093
+ return scopeProvider?.getAvailableLocales?.() ?? Object.keys(locales);
1094
+ },
1095
+ updateOwner(nextOptions) {
1096
+ const providerChanged = scopeProvider !== nextOptions.provider;
1097
+ const clearedControlledLocale = ownerControlsLocale && nextOptions.locale === undefined;
1098
+ scopeProvider = nextOptions.provider;
1099
+ onLocaleChange = nextOptions.onLocaleChange;
1100
+ ownerControlsLocale = nextOptions.locale !== undefined;
1101
+
1102
+ if (nextOptions.locale !== undefined) {
1103
+ scopeLocale = nextOptions.locale;
1104
+ synchronizeProviderLocale(nextOptions.locale);
1105
+ } else if (providerChanged || clearedControlledLocale) {
1106
+ scopeLocale = scopeProvider?.getLocale() || detectLocale();
1107
+ }
1108
+ },
1109
+ };
1110
+
1111
+ return scope;
1112
+ }
1113
+
1114
+ /** Attach an i18n scope to the current component tree. */
1115
+ export function provideI18nScope(scope: I18nScope): I18nScope {
1116
+ writeI18nScopeContext(scope);
1117
+ return scope;
1118
+ }
1119
+
1120
+ /** Return the current tree scope when called during a Svelte render/effect. */
1121
+ export function getI18nScope(): I18nScope | undefined {
1122
+ return readCurrentI18nScope();
1123
+ }
1124
+
1125
+ let i18nProvider: I18nProvider | undefined;
1126
+
1127
+ function setLegacyLocale(locale: string): void {
1128
+ i18nProvider?.setLocale(locale);
1129
+ currentLocale = locale;
1130
+ }
1131
+
1132
+ function getLegacyLocale(): string {
1133
+ // Reading the rune keeps legacy consumers reactive.
1134
+ const locale = currentLocale;
1135
+ return i18nProvider?.getLocale() ?? locale;
1136
+ }
1137
+
1138
+ function getLegacyAvailableLocales(): string[] {
1139
+ return i18nProvider?.getAvailableLocales?.() ?? Object.keys(locales);
1140
+ }
1141
+
1142
+ function translateLegacy(key: string, params?: Record<string, string | number>): string {
1143
+ const locale = currentLocale;
1144
+ return i18nProvider?.translate(key, params) ?? translateFromTables(locale, key, params);
1145
+ }
1146
+
1147
+ export function setI18nProvider(provider: I18nProvider | undefined): void {
1148
+ const scope = readCurrentI18nScope();
1149
+ if (scope) {
1150
+ scope.setProvider(provider);
1151
+ return;
1152
+ }
1153
+ i18nProvider = provider;
1154
+ }
1155
+
1156
+ export function getI18nProvider(): I18nProvider | undefined {
1157
+ const scope = readCurrentI18nScope();
1158
+ return scope ? scope.provider : i18nProvider;
1159
+ }
1160
+
1161
+ export function setLocale(locale: string): void {
1162
+ const scope = readCurrentI18nScope();
1163
+ if (scope) {
1164
+ scope.setLocale(locale);
1165
+ return;
1166
+ }
1167
+ setLegacyLocale(locale);
1168
+ }
1169
+
1170
+ export function getLocale(): string {
1171
+ return readCurrentI18nScope()?.locale ?? getLegacyLocale();
1172
+ }
1173
+
1174
+ export function getAvailableLocales(): string[] {
1175
+ return readCurrentI18nScope()?.getAvailableLocales() ?? getLegacyAvailableLocales();
1176
+ }
1177
+
1178
+ export function t(key: string, params?: Record<string, string | number>): string {
1179
+ return readCurrentI18nScope()?.translate(key, params) ?? translateLegacy(key, params);
1180
+ }
1181
+
790
1182
  export function addTranslations(locale: string, translations: Record<string, string>): void {
791
1183
  locales = { ...locales, [locale]: { ...locales[locale], ...translations } };
792
1184
  }
793
1185
 
794
1186
  export function resetI18n(): void {
795
1187
  currentLocale = detectLocale();
796
- locales = initialLocales;
1188
+ locales = structuredClone(initialLocales);
1189
+ i18nProvider = undefined;
797
1190
  }
798
1191
 
799
1192
  /**
@@ -801,14 +1194,23 @@ export function resetI18n(): void {
801
1194
  * Tracks locale changes natively.
802
1195
  */
803
1196
  export function useTranslation() {
804
- const translate = (key: string, params?: Record<string, string | number>): string => {
805
- return t(key, params);
806
- };
807
-
808
- return {
1197
+ // Capture context while the component is initializing. Event handlers and
1198
+ // async continuations cannot safely call getContext later.
1199
+ const scope = readCurrentI18nScope();
1200
+ const translate = scope
1201
+ ? (key: string, params?: Record<string, string | number>) => scope.translate(key, params)
1202
+ : translateLegacy;
1203
+ const changeLocale = scope
1204
+ ? (locale: string) => scope.setLocale(locale)
1205
+ : setLegacyLocale;
1206
+ const availableLocales = scope
1207
+ ? () => scope.getAvailableLocales()
1208
+ : getLegacyAvailableLocales;
1209
+
1210
+ return {
809
1211
  get t() { return translate; },
810
- get locale() { return currentLocale; },
811
- setLocale,
812
- getAvailableLocales
1212
+ get locale() { return scope?.locale ?? getLegacyLocale(); },
1213
+ setLocale: changeLocale,
1214
+ getAvailableLocales: availableLocales,
813
1215
  };
814
1216
  }
package/src/index.ts CHANGED
@@ -7,9 +7,10 @@ export {
7
7
  setRouterProvider, getRouterProvider,
8
8
  setLiveProvider, getLiveProvider,
9
9
  setTaskProvider, getTaskProvider,
10
+ createAdminContext, provideAdminContext, getAdminContext, captureAdminContext,
10
11
  resetContext,
11
12
  } from './context.svelte';
12
- export type { DataProviderInput } from './context.svelte';
13
+ export type { DataProviderInput, AdminContextSource, AdminContextValue, AdminContextAccessor } from './context.svelte';
13
14
  export {
14
15
  useList, useInfiniteList,
15
16
  useOne, useShow,
@@ -23,7 +24,7 @@ export {
23
24
  useNavigation, useGo, useBack,
24
25
  useGetToPath, useLink,
25
26
  useResource,
26
- useModalForm, useDrawerForm, useModal,
27
+ useModalForm, useDrawerForm, useModal, useCheckboxGroup, useRadioGroup, useAutocomplete,
27
28
  useOvertime, useRelation,
28
29
  useNotification, useDataProvider,
29
30
  useMenu, useBreadcrumb, useThemedLayoutContext,
@@ -36,13 +37,14 @@ export { setAccessControlProvider, getAccessControlProvider, getAccessControlOpt
36
37
  export { useLive, useSubscription, usePublish } from './live.svelte';
37
38
  export { toast, getToastQueue, consumeToastQueue, getPromiseQueue, consumePromiseQueue, resetToast } from './toast.svelte';
38
39
  export { notify, closeNotification, setNotificationProvider, getNotificationProvider } from './notification.svelte';
39
- export { t, setLocale, getLocale, getAvailableLocales, addTranslations, useTranslation } from './i18n.svelte';
40
+ export { t, setLocale, getLocale, getAvailableLocales, addTranslations, useTranslation, setI18nProvider, getI18nProvider, createI18nScope, provideI18nScope, getI18nScope, resetI18n } from './i18n.svelte';
41
+ export type { I18nProvider, I18nScope, I18nScopeOptions } from './i18n.svelte';
40
42
  export { audit, setAuditHandler, setAuditLogProvider, getAuditLogProvider } from './audit';
41
43
  export type { AuditLogProvider } from './audit';
42
44
  export { setChatProvider, getChatProvider, setChatContext, getChatContext, setAgentProvider, getAgentProvider, registerApproval, resolveApproval, hasPendingApprovals, resetChatProvider } from './chatProvider.svelte';
43
45
  export type { ChatProvider, ChatMessage, ChatContext, ChatAction, AgentProvider, AgentEvent, AgentOptions, AdminTool, AdminToolParameter, ToolResult } from './chatProvider.svelte';
44
- export { getTheme, setTheme, toggleTheme, getResolvedTheme, getColorTheme, setColorTheme, getColorThemes, configureTheme, getThemeConfig, clearCssOverrides, builtinPresets, registerColorPreset, getColorPresets, resetTheme } from './theme.svelte';
45
- export type { ThemeMode, ColorTheme, ThemeStrategy, ThemeConfig, ColorPreset } from './theme.svelte';
46
+ export { getTheme, setTheme, toggleTheme, getResolvedTheme, getColorTheme, setColorTheme, getColorThemes, configureTheme, getThemeConfig, clearCssOverrides, builtinPresets, registerColorPreset, getColorPresets, registerThemeOwner, updateThemeOwner, unregisterThemeOwner, resetTheme } from './theme.svelte';
47
+ export type { ThemeMode, ColorTheme, ThemeStrategy, ThemeConfig, ColorPreset, ThemeOwnerOptions, ThemeOwnerToken } from './theme.svelte';
46
48
  export { setUnsavedChanges, getUnsavedChanges, initUnsavedChangesNotifier, resetUnsavedChanges } from './unsaved-changes.svelte';
47
49
  export { setAdminOptions, getAdminOptions, getTextTransformers } from './options.svelte';
48
50
  export type { AdminOptions, TextTransformers, OvertimeConfig } from './options.svelte';
@@ -53,7 +55,7 @@ export * from './query-keys';
53
55
  export { HttpError, UndoError } from './types';
54
56
  export type {
55
57
  DataProvider, AuthProvider, NotificationProvider, MutationMode,
56
- ValidationErrors, CrudOperator, LogicalFilter, FieldFilter,
58
+ ValidationErrors, HttpErrorOptions, CrudOperator, LogicalFilter, FieldFilter,
57
59
  CustomParams, CustomResult,
58
60
  GetListParams, GetListResult,
59
61
  GetOneParams, GetOneResult,
@@ -79,11 +81,12 @@ export type { UseCanOptions, UseCanResult } from './useCan';
79
81
  export { createCaslAccessControl } from './adapters/casl';
80
82
  export { createCasbinAccessControl } from './adapters/casbin';
81
83
  export type { CasbinAdapterOptions } from './adapters/casbin';
82
- export { useExport, useImport } from './data-transfer.svelte';
83
- export type { UseExportOptions, UseImportOptions } from './data-transfer.svelte';
84
+ export { useExport, useImport, downloadData } from './data-transfer.svelte';
85
+ export type { UseExportOptions, UseImportOptions, ExportFormat } from './data-transfer.svelte';
84
86
  export {
85
87
  useLogin, useLogout,
86
88
  useRegister, useForgotPassword, useUpdatePassword,
89
+ useUpdateIdentity, useUpdateProfile,
87
90
  useGetIdentity, useIsAuthenticated,
88
91
  useOnError, usePermissions,
89
92
  getLogoutVersion, resetLogoutVersion,