nfx-ui 0.34.0 → 0.36.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.
Files changed (102) hide show
  1. package/README.en.md +1 -1
  2. package/README.md +1 -1
  3. package/dist/events/system.d.ts +9 -0
  4. package/dist/events/system.d.ts.map +1 -1
  5. package/dist/events/system.js +9 -1
  6. package/dist/events/system.js.map +1 -1
  7. package/dist/hooks/auth.d.ts +33 -30
  8. package/dist/hooks/auth.d.ts.map +1 -1
  9. package/dist/hooks/auth.js +234 -227
  10. package/dist/hooks/auth.js.map +1 -1
  11. package/dist/hooks/index.d.ts +1 -0
  12. package/dist/hooks/index.d.ts.map +1 -1
  13. package/dist/hooks/index.js +9 -8
  14. package/dist/hooks/toast.d.ts +12 -0
  15. package/dist/hooks/toast.d.ts.map +1 -0
  16. package/dist/hooks/toast.js +13 -0
  17. package/dist/hooks/toast.js.map +1 -0
  18. package/dist/languages/hooks/useLanguageLabel.d.ts.map +1 -1
  19. package/dist/languages/hooks/useLanguageLabel.js +11 -10
  20. package/dist/languages/hooks/useLanguageLabel.js.map +1 -1
  21. package/dist/languages/index.d.ts +0 -1
  22. package/dist/languages/index.d.ts.map +1 -1
  23. package/dist/languages/index.js +9 -10
  24. package/dist/languages/languages/i18n.d.ts.map +1 -1
  25. package/dist/languages/languages/i18n.js +30 -41
  26. package/dist/languages/languages/i18n.js.map +1 -1
  27. package/dist/schemas/loginSchema.d.ts +3 -3
  28. package/dist/schemas/loginSchema.d.ts.map +1 -1
  29. package/dist/schemas/loginSchema.js +22 -23
  30. package/dist/schemas/loginSchema.js.map +1 -1
  31. package/dist/schemas/profileEditSchema.d.ts +1 -1
  32. package/dist/schemas/profileEditSchema.d.ts.map +1 -1
  33. package/dist/schemas/profileEditSchema.js +18 -19
  34. package/dist/schemas/profileEditSchema.js.map +1 -1
  35. package/dist/schemas/signupSchema.d.ts +2 -2
  36. package/dist/schemas/signupSchema.d.ts.map +1 -1
  37. package/dist/schemas/signupSchema.js +16 -17
  38. package/dist/schemas/signupSchema.js.map +1 -1
  39. package/package.json +1 -1
  40. package/src/events/system.ts +16 -0
  41. package/src/hooks/auth.ts +100 -109
  42. package/src/hooks/index.ts +1 -0
  43. package/src/hooks/toast.ts +23 -0
  44. package/src/languages/hooks/useLanguageLabel.ts +2 -3
  45. package/src/languages/index.ts +0 -1
  46. package/src/languages/languages/i18n.ts +3 -17
  47. package/src/schemas/loginSchema.ts +3 -6
  48. package/src/schemas/profileEditSchema.ts +1 -3
  49. package/src/schemas/signupSchema.ts +2 -4
  50. package/dist/languages/resources/en/language.js +0 -52
  51. package/dist/languages/resources/en/language.js.map +0 -1
  52. package/dist/languages/resources/en/language.json.d.ts +0 -49
  53. package/dist/languages/resources/en/layout.js +0 -9
  54. package/dist/languages/resources/en/layout.js.map +0 -1
  55. package/dist/languages/resources/en/layout.json.d.ts +0 -9
  56. package/dist/languages/resources/en/preference.js +0 -50
  57. package/dist/languages/resources/en/preference.js.map +0 -1
  58. package/dist/languages/resources/en/preference.json.d.ts +0 -51
  59. package/dist/languages/resources/en/theme.js +0 -19
  60. package/dist/languages/resources/en/theme.js.map +0 -1
  61. package/dist/languages/resources/en/theme.json.d.ts +0 -17
  62. package/dist/languages/resources/fr/language.js +0 -52
  63. package/dist/languages/resources/fr/language.js.map +0 -1
  64. package/dist/languages/resources/fr/language.json.d.ts +0 -49
  65. package/dist/languages/resources/fr/layout.js +0 -9
  66. package/dist/languages/resources/fr/layout.js.map +0 -1
  67. package/dist/languages/resources/fr/layout.json.d.ts +0 -9
  68. package/dist/languages/resources/fr/preference.js +0 -50
  69. package/dist/languages/resources/fr/preference.js.map +0 -1
  70. package/dist/languages/resources/fr/preference.json.d.ts +0 -51
  71. package/dist/languages/resources/fr/theme.js +0 -19
  72. package/dist/languages/resources/fr/theme.js.map +0 -1
  73. package/dist/languages/resources/fr/theme.json.d.ts +0 -17
  74. package/dist/languages/resources/index.d.ts +0 -9
  75. package/dist/languages/resources/index.d.ts.map +0 -1
  76. package/dist/languages/resources/index.js +0 -54
  77. package/dist/languages/resources/index.js.map +0 -1
  78. package/dist/languages/resources/zh/language.js +0 -52
  79. package/dist/languages/resources/zh/language.js.map +0 -1
  80. package/dist/languages/resources/zh/language.json.d.ts +0 -49
  81. package/dist/languages/resources/zh/layout.js +0 -9
  82. package/dist/languages/resources/zh/layout.js.map +0 -1
  83. package/dist/languages/resources/zh/layout.json.d.ts +0 -9
  84. package/dist/languages/resources/zh/preference.js +0 -50
  85. package/dist/languages/resources/zh/preference.js.map +0 -1
  86. package/dist/languages/resources/zh/preference.json.d.ts +0 -51
  87. package/dist/languages/resources/zh/theme.js +0 -19
  88. package/dist/languages/resources/zh/theme.js.map +0 -1
  89. package/dist/languages/resources/zh/theme.json.d.ts +0 -17
  90. package/src/languages/resources/en/language.json +0 -47
  91. package/src/languages/resources/en/layout.json +0 -6
  92. package/src/languages/resources/en/preference.json +0 -49
  93. package/src/languages/resources/en/theme.json +0 -14
  94. package/src/languages/resources/fr/language.json +0 -47
  95. package/src/languages/resources/fr/layout.json +0 -6
  96. package/src/languages/resources/fr/preference.json +0 -49
  97. package/src/languages/resources/fr/theme.json +0 -14
  98. package/src/languages/resources/index.ts +0 -62
  99. package/src/languages/resources/zh/language.json +0 -47
  100. package/src/languages/resources/zh/layout.json +0 -6
  101. package/src/languages/resources/zh/preference.json +0 -49
  102. package/src/languages/resources/zh/theme.json +0 -14
package/src/hooks/auth.ts CHANGED
@@ -25,11 +25,11 @@ import {
25
25
  useAuthStore,
26
26
  } from "nfx-ui/stores/auth";
27
27
  import { ensureDeviceIdStorage } from "nfx-ui/stores/system";
28
- import { getApiErrorMessage } from "nfx-ui/utils/apiError";
29
28
  import { pickProfile } from "nfx-ui/utils/domain/account";
30
29
  import { useUnifiedQuery } from "nfx-ui/utils/factory";
31
30
  import { safeOr, safeStringable } from "nfx-ui/utils/safe";
32
- import { useTranslation } from "react-i18next";
31
+
32
+ import { emitHookError, emitHookSuccess, type HookToastMessages } from "./toast";
33
33
 
34
34
  export function useAuthQueryScope() {
35
35
  const aID = useAuthStore((s) => s.currentAccountId);
@@ -68,21 +68,20 @@ export function useCurrentProfile(): CurrentProfileResult {
68
68
  } as CurrentProfileResult;
69
69
  }
70
70
 
71
- export const useSendVerificationCode = () => {
71
+ export const useSendVerificationCode = (options?: HookToastMessages) => {
72
72
  const auth = useAuthRepository();
73
- const { t } = useTranslation("hooks", { keyPrefix: "account" });
74
73
  return useMutation({
75
74
  mutationFn: (params: Signup.Request.SendVerificationCode) => auth.SendVerificationCode(params),
76
75
  onSuccess: () => {
77
- systemEventEmitter.showSuccess(t("sendVerificationCodeSuccess", { defaultValue: "Code sent" }));
76
+ emitHookSuccess(options);
78
77
  },
79
78
  onError: (error: AxiosError) => {
80
- systemEventEmitter.showError(getApiErrorMessage(error, "[useSendVerificationCode] error"));
79
+ emitHookError(error, options, "[useSendVerificationCode] error");
81
80
  },
82
81
  });
83
82
  };
84
83
 
85
- export const useSignupWithEmail = () => {
84
+ export const useSignupWithEmail = (options?: HookToastMessages) => {
86
85
  const auth = useAuthRepository();
87
86
  return useMutation({
88
87
  mutationFn: async ({
@@ -117,12 +116,12 @@ export const useSignupWithEmail = () => {
117
116
  }
118
117
  },
119
118
  onError: (error: AxiosError) => {
120
- systemEventEmitter.showError(getApiErrorMessage(error, "[useSignup] error"));
119
+ emitHookError(error, options, "[useSignup] error");
121
120
  },
122
121
  });
123
122
  };
124
123
 
125
- export const useLoginWithEmail = () => {
124
+ export const useLoginWithEmail = (options?: HookToastMessages) => {
126
125
  const auth = useAuthRepository();
127
126
  return useMutation({
128
127
  mutationFn: async ({ rememberMe, ...params }: { email: string; password: string; rememberMe: boolean }) =>
@@ -144,12 +143,12 @@ export const useLoginWithEmail = () => {
144
143
  if (result.accountId) setCurrentAccountId(result.accountId);
145
144
  },
146
145
  onError: (error: AxiosError) => {
147
- systemEventEmitter.showError(getApiErrorMessage(error, "[useLogin] error"));
146
+ emitHookError(error, options, "[useLogin] error");
148
147
  },
149
148
  });
150
149
  };
151
150
 
152
- export const useLoginWithPhone = () => {
151
+ export const useLoginWithPhone = (options?: HookToastMessages) => {
153
152
  const auth = useAuthRepository();
154
153
  return useMutation({
155
154
  mutationFn: async ({ rememberMe, ...params }: { phone: string; password: string; rememberMe: boolean }) =>
@@ -171,12 +170,14 @@ export const useLoginWithPhone = () => {
171
170
  if (result.accountId) setCurrentAccountId(result.accountId);
172
171
  },
173
172
  onError: (error: AxiosError) => {
174
- systemEventEmitter.showError(getApiErrorMessage(error, "[useLoginWithPhone] error"));
173
+ emitHookError(error, options, "[useLoginWithPhone] error");
175
174
  },
176
175
  });
177
176
  };
178
177
 
179
- export type SelectProfileOptions = {
178
+ export type SelectProfileOptions = HookToastMessages & {
179
+ /** 已翻译的加载文案。Host-translated loading copy. */
180
+ switchingMsg?: string;
180
181
  /** 与 token、档案同一次提交里执行(例如跳到该档案的首页)。Runs in the same commit as the token and profile. */
181
182
  onCommit?: () => void;
182
183
  };
@@ -191,68 +192,74 @@ export const useSelectProfile = (options?: SelectProfileOptions) => {
191
192
  kind,
192
193
  deviceId: await ensureDeviceIdStorage(),
193
194
  }),
195
+ onMutate: () => {
196
+ if (options?.switchingMsg) systemEventEmitter.showLoading(options.switchingMsg);
197
+ },
194
198
  onSuccess: (result, variables) => {
195
- if (!result?.accessToken) return;
196
- // 同一次提交写入新 token 和档案,旧角色的请求在重拉前先停用。
197
- flushSync(() => {
198
- setTokens({
199
- accessToken: result.accessToken,
200
- refreshToken: safeStringable(result.refreshToken),
199
+ try {
200
+ if (!result?.accessToken) return;
201
+ // 同一次提交写入新 token 和档案,旧角色的请求在重拉前先停用。
202
+ flushSync(() => {
203
+ setTokens({
204
+ accessToken: result.accessToken,
205
+ refreshToken: safeStringable(result.refreshToken),
206
+ });
207
+ setCurrentProfileKind(variables.kind);
208
+ if (result.profileId) setCurrentProfileId(result.profileId);
209
+ options?.onCommit?.();
201
210
  });
202
- setCurrentProfileKind(variables.kind);
203
- if (result.profileId) setCurrentProfileId(result.profileId);
204
- options?.onCommit?.();
205
- });
206
- authEventEmitter.emit(authEvents.LOGIN_SUCCESS, result.accountId);
211
+ authEventEmitter.emit(authEvents.LOGIN_SUCCESS, result.accountId);
212
+ } finally {
213
+ systemEventEmitter.hideLoading();
214
+ }
207
215
  },
208
216
  onError: (error: AxiosError) => {
209
- systemEventEmitter.showError(getApiErrorMessage(error, "[useSelectProfile] error"));
217
+ systemEventEmitter.hideLoading();
218
+ emitHookError(error, options, "[useSelectProfile] error");
210
219
  },
211
220
  });
212
221
  };
213
222
 
214
- export const usePatchProfile = (options?: { silent?: boolean }) => {
223
+ export const usePatchProfile = (options?: { silent?: boolean } & HookToastMessages) => {
215
224
  const auth = useAuthRepository();
216
225
  const { kind } = useAuthQueryScope();
217
226
  const silent = safeOr(options?.silent, false);
218
- const { t } = useTranslation("pages.User.Profile.Edit");
219
227
  return useMutation({
220
228
  mutationFn: (body: Profile.Request.PatchProfile) => auth.PatchProfile(kind, body),
221
229
  onSuccess: () => {
222
- if (!silent) systemEventEmitter.showSuccess(t("saveSuccess", { defaultValue: "Saved" }));
230
+ if (!silent) emitHookSuccess(options);
223
231
  const aID = AuthStore.getState().currentAccountId;
224
232
  authEventEmitter.invalidateProfiles({ aID, kind });
225
233
  },
226
234
  onError: (error: AxiosError) => {
227
- if (!silent) systemEventEmitter.showError(getApiErrorMessage(error, "[usePatchProfile] error"));
235
+ emitHookError(error, options, "[usePatchProfile] error", silent);
228
236
  },
229
237
  });
230
238
  };
231
239
 
232
- export const useUpdateProfileSettings = () => {
240
+ export const useUpdateProfileSettings = (options?: HookToastMessages) => {
233
241
  const auth = useAuthRepository();
234
242
  const { kind } = useAuthQueryScope();
235
- const { t } = useTranslation("hooks", { keyPrefix: "account" });
236
243
  return useMutation({
237
244
  mutationFn: (body: Profile.Request.PatchProfileSettings) => auth.PatchProfileSettings(kind, body),
238
245
  onSuccess: () => {
239
246
  const aID = AuthStore.getState().currentAccountId;
240
247
  authEventEmitter.emit(authEvents.UPDATE_ACCOUNT_SUCCESS, aID);
241
- systemEventEmitter.showSuccess(t("updateSettingsSuccess", { defaultValue: "Settings updated." }));
248
+ emitHookSuccess(options);
242
249
  },
243
250
  onError: (error: AxiosError) => {
244
- systemEventEmitter.showError(getApiErrorMessage(error, "[useUpdateProfileSettings] error"));
251
+ emitHookError(error, options, "[useUpdateProfileSettings] error");
245
252
  },
246
253
  });
247
254
  };
248
255
 
249
- export const useUpdatePreference = () => {
256
+ export const useUpdatePreference = (options?: HookToastMessages) => {
250
257
  const auth = useAuthRepository();
251
258
  const { kind } = useAuthQueryScope();
252
259
  return useMutation({
253
260
  mutationFn: (preference: string) => auth.UpdatePreference(kind, preference),
254
261
  onError: (error: AxiosError) => {
255
- systemEventEmitter.showError(getApiErrorMessage(error, "[useUpdatePreference] error"));
262
+ emitHookError(error, options, "[useUpdatePreference] error");
256
263
  },
257
264
  });
258
265
  };
@@ -268,7 +275,7 @@ export const useListProfiles = <K extends ProfileKindEnum>(kind: K) => {
268
275
  );
269
276
  };
270
277
 
271
- export const useCreateForgerProfile = () => {
278
+ export const useCreateForgerProfile = (options?: HookToastMessages) => {
272
279
  const auth = useAuthRepository();
273
280
  return useMutation({
274
281
  mutationFn: (body: Login.Request.CreateForgerProfile) => auth.CreateForgerProfile(body),
@@ -277,12 +284,12 @@ export const useCreateForgerProfile = () => {
277
284
  authEventEmitter.invalidateProfiles({ aID, kind: ProfileKindEnum.COMMUNITY });
278
285
  },
279
286
  onError: (error: AxiosError) => {
280
- systemEventEmitter.showError(getApiErrorMessage(error, "[useCreateForgerProfile] error"));
287
+ emitHookError(error, options, "[useCreateForgerProfile] error");
281
288
  },
282
289
  });
283
290
  };
284
291
 
285
- export const useCreateAuthorityProfile = () => {
292
+ export const useCreateAuthorityProfile = (options?: HookToastMessages) => {
286
293
  const auth = useAuthRepository();
287
294
  return useMutation({
288
295
  mutationFn: (body: Login.Request.CreateAuthorityProfile) => auth.CreateAuthorityProfile(body),
@@ -291,12 +298,12 @@ export const useCreateAuthorityProfile = () => {
291
298
  authEventEmitter.invalidateProfiles({ aID, kind: ProfileKindEnum.AUTHORITY });
292
299
  },
293
300
  onError: (error: AxiosError) => {
294
- systemEventEmitter.showError(getApiErrorMessage(error, "[useCreateAuthorityProfile] error"));
301
+ emitHookError(error, options, "[useCreateAuthorityProfile] error");
295
302
  },
296
303
  });
297
304
  };
298
305
 
299
- export const useDeleteProfile = () => {
306
+ export const useDeleteProfile = (options?: HookToastMessages) => {
300
307
  const auth = useAuthRepository();
301
308
  return useMutation({
302
309
  mutationFn: ({ kind, profileId }: { kind: ProfileKindEnum; profileId: string }) => auth.DeleteProfile(kind, profileId),
@@ -305,12 +312,12 @@ export const useDeleteProfile = () => {
305
312
  authEventEmitter.invalidateProfiles({ aID, kind: variables.kind });
306
313
  },
307
314
  onError: (error: AxiosError) => {
308
- systemEventEmitter.showError(getApiErrorMessage(error, "[useDeleteProfile] error"));
315
+ emitHookError(error, options, "[useDeleteProfile] error");
309
316
  },
310
317
  });
311
318
  };
312
319
 
313
- export const useConfirmProfileAvatar = () => {
320
+ export const useConfirmProfileAvatar = (options?: HookToastMessages) => {
314
321
  const auth = useAuthRepository();
315
322
  const { kind } = useAuthQueryScope();
316
323
  return useMutation({
@@ -320,24 +327,23 @@ export const useConfirmProfileAvatar = () => {
320
327
  authEventEmitter.emit(authEvents.UPDATE_ACCOUNT_SUCCESS, aID);
321
328
  },
322
329
  onError: (error: AxiosError) => {
323
- systemEventEmitter.showError(getApiErrorMessage(error, "[useConfirmProfileAvatar] error"));
330
+ emitHookError(error, options, "[useConfirmProfileAvatar] error");
324
331
  },
325
332
  });
326
333
  };
327
334
 
328
- export const useClearProfileAvatar = () => {
335
+ export const useClearProfileAvatar = (options?: HookToastMessages) => {
329
336
  const auth = useAuthRepository();
330
337
  const { kind } = useAuthQueryScope();
331
- const { t } = useTranslation("hooks", { keyPrefix: "account" });
332
338
  return useMutation({
333
339
  mutationFn: () => auth.ClearProfileAvatar(kind),
334
340
  onSuccess: () => {
335
341
  const aID = AuthStore.getState().currentAccountId;
336
342
  authEventEmitter.emit(authEvents.UPDATE_ACCOUNT_SUCCESS, aID);
337
- systemEventEmitter.showSuccess(t("clearAvatarSuccess", { defaultValue: "Avatar removed." }));
343
+ emitHookSuccess(options);
338
344
  },
339
345
  onError: (error: AxiosError) => {
340
- systemEventEmitter.showError(getApiErrorMessage(error, "[useClearProfileAvatar] error"));
346
+ emitHookError(error, options, "[useClearProfileAvatar] error");
341
347
  },
342
348
  });
343
349
  };
@@ -358,131 +364,123 @@ export const useListPhones = () => {
358
364
  });
359
365
  };
360
366
 
361
- export const useCreateEmail = () => {
367
+ export const useCreateEmail = (options?: HookToastMessages) => {
362
368
  const auth = useAuthRepository();
363
369
  const accountId = AuthStore.getState().currentAccountId;
364
- const { t } = useTranslation("hooks", { keyPrefix: "account" });
365
370
  return useMutation({
366
371
  mutationFn: (body: Login.Request.CreateEmail) => auth.CreateEmail(body),
367
372
  onSuccess: () => {
368
373
  authEventEmitter.invalidateEmails(accountId);
369
- systemEventEmitter.showSuccess(t("createEmailSuccess", { defaultValue: "Email added." }));
374
+ emitHookSuccess(options);
370
375
  },
371
376
  onError: (error: AxiosError) => {
372
- systemEventEmitter.showError(getApiErrorMessage(error, "[useCreateEmail] error"));
377
+ emitHookError(error, options, "[useCreateEmail] error");
373
378
  },
374
379
  });
375
380
  };
376
381
 
377
- export const useSendEmailVerificationCode = () => {
382
+ export const useSendEmailVerificationCode = (options?: HookToastMessages) => {
378
383
  const auth = useAuthRepository();
379
- const { t } = useTranslation("hooks", { keyPrefix: "account" });
380
384
  return useMutation({
381
385
  mutationFn: ({ emailId, lang }: { emailId: string; lang?: LanguageEnum }) => auth.SendEmailVerificationCode(emailId, { lang }),
382
386
  onSuccess: () => {
383
- systemEventEmitter.showSuccess(t("sendVerificationCodeSuccess", { defaultValue: "Code sent" }));
387
+ emitHookSuccess(options);
384
388
  },
385
389
  onError: (error: AxiosError) => {
386
- systemEventEmitter.showError(getApiErrorMessage(error, "[useSendEmailVerificationCode] error"));
390
+ emitHookError(error, options, "[useSendEmailVerificationCode] error");
387
391
  },
388
392
  });
389
393
  };
390
394
 
391
- export const useVerifyEmail = () => {
395
+ export const useVerifyEmail = (options?: HookToastMessages) => {
392
396
  const auth = useAuthRepository();
393
397
  const accountId = AuthStore.getState().currentAccountId;
394
- const { t } = useTranslation("hooks", { keyPrefix: "account" });
395
398
  return useMutation({
396
399
  mutationFn: ({ emailId, verificationCode }: { emailId: string; verificationCode: string }) =>
397
400
  auth.VerifyEmail(emailId, { verificationCode }),
398
401
  onSuccess: () => {
399
402
  authEventEmitter.invalidateEmails(accountId);
400
- systemEventEmitter.showSuccess(t("verifyEmailSuccess", { defaultValue: "Email verified." }));
403
+ emitHookSuccess(options);
401
404
  },
402
405
  onError: (error: AxiosError) => {
403
- systemEventEmitter.showError(getApiErrorMessage(error, "[useVerifyEmail] error"));
406
+ emitHookError(error, options, "[useVerifyEmail] error");
404
407
  },
405
408
  });
406
409
  };
407
410
 
408
- export const useUpdateEmail = () => {
411
+ export const useUpdateEmail = (options?: HookToastMessages) => {
409
412
  const auth = useAuthRepository();
410
413
  const accountId = AuthStore.getState().currentAccountId;
411
- const { t } = useTranslation("hooks", { keyPrefix: "account" });
412
414
  return useMutation({
413
415
  mutationFn: ({ emailId, email }: { emailId: string; email: string }) => auth.UpdateEmail(emailId, { email }),
414
416
  onSuccess: () => {
415
417
  authEventEmitter.invalidateEmails(accountId);
416
- systemEventEmitter.showSuccess(t("updateEmailSuccess", { defaultValue: "Email updated." }));
418
+ emitHookSuccess(options);
417
419
  },
418
420
  onError: (error: AxiosError) => {
419
- systemEventEmitter.showError(getApiErrorMessage(error, "[useUpdateEmail] error"));
421
+ emitHookError(error, options, "[useUpdateEmail] error");
420
422
  },
421
423
  });
422
424
  };
423
425
 
424
- export const useSetPrimaryEmail = () => {
426
+ export const useSetPrimaryEmail = (options?: HookToastMessages) => {
425
427
  const auth = useAuthRepository();
426
428
  const accountId = AuthStore.getState().currentAccountId;
427
- const { t } = useTranslation("hooks", { keyPrefix: "account" });
428
429
  return useMutation({
429
430
  mutationFn: (emailId: string) => auth.SetPrimaryEmail(emailId),
430
431
  onSuccess: () => {
431
432
  authEventEmitter.invalidateEmails(accountId);
432
433
  authEventEmitter.emit(authEvents.UPDATE_ACCOUNT_SUCCESS, accountId);
433
- systemEventEmitter.showSuccess(t("setPrimaryEmailSuccess", { defaultValue: "Primary email updated." }));
434
+ emitHookSuccess(options);
434
435
  },
435
436
  onError: (error: AxiosError) => {
436
- systemEventEmitter.showError(getApiErrorMessage(error, "[useSetPrimaryEmail] error"));
437
+ emitHookError(error, options, "[useSetPrimaryEmail] error");
437
438
  },
438
439
  });
439
440
  };
440
441
 
441
- export const useDeleteEmail = () => {
442
+ export const useDeleteEmail = (options?: HookToastMessages) => {
442
443
  const auth = useAuthRepository();
443
444
  const accountId = AuthStore.getState().currentAccountId;
444
- const { t } = useTranslation("hooks", { keyPrefix: "account" });
445
445
  return useMutation({
446
446
  mutationFn: (emailId: string) => auth.DeleteEmail(emailId),
447
447
  onSuccess: () => {
448
448
  authEventEmitter.invalidateEmails(accountId);
449
- systemEventEmitter.showSuccess(t("deleteEmailSuccess", { defaultValue: "Email removed." }));
449
+ emitHookSuccess(options);
450
450
  },
451
451
  onError: (error: AxiosError) => {
452
- systemEventEmitter.showError(getApiErrorMessage(error, "[useDeleteEmail] error"));
452
+ emitHookError(error, options, "[useDeleteEmail] error");
453
453
  },
454
454
  });
455
455
  };
456
456
 
457
- export const useSendChangePasswordVerificationCode = () => {
457
+ export const useSendChangePasswordVerificationCode = (options?: HookToastMessages) => {
458
458
  const auth = useAuthRepository();
459
- const { t } = useTranslation("hooks", { keyPrefix: "account" });
460
459
  return useMutation({
461
460
  mutationFn: (body: Login.Request.SendChangePasswordVerificationCode = {}) => auth.SendChangePasswordVerificationCode(body),
462
461
  onSuccess: () => {
463
- systemEventEmitter.showSuccess(t("sendVerificationCodeSuccess", { defaultValue: "Code sent" }));
462
+ emitHookSuccess(options);
464
463
  },
465
464
  onError: (error: AxiosError) => {
466
- systemEventEmitter.showError(getApiErrorMessage(error, "[useSendChangePasswordVerificationCode] error"));
465
+ emitHookError(error, options, "[useSendChangePasswordVerificationCode] error");
467
466
  },
468
467
  });
469
468
  };
470
469
 
471
- export const useChangePassword = () => {
470
+ export const useChangePassword = (options?: HookToastMessages) => {
472
471
  const auth = useAuthRepository();
473
- const { t } = useTranslation("hooks", { keyPrefix: "account" });
474
472
  return useMutation({
475
473
  mutationFn: (body: Login.Request.ChangePassword) => auth.ChangePassword(body),
476
474
  onSuccess: () => {
477
- systemEventEmitter.showSuccess(t("changePasswordSuccess", { defaultValue: "Password updated." }));
475
+ emitHookSuccess(options);
478
476
  },
479
477
  onError: (error: AxiosError) => {
480
- systemEventEmitter.showError(getApiErrorMessage(error, "[useChangePassword] error"));
478
+ emitHookError(error, options, "[useChangePassword] error");
481
479
  },
482
480
  });
483
481
  };
484
482
 
485
- export const useConfirmProfileBackgrounds = () => {
483
+ export const useConfirmProfileBackgrounds = (options?: HookToastMessages) => {
486
484
  const auth = useAuthRepository();
487
485
  const { kind } = useAuthQueryScope();
488
486
  return useMutation({
@@ -492,103 +490,97 @@ export const useConfirmProfileBackgrounds = () => {
492
490
  authEventEmitter.emit(authEvents.UPDATE_ACCOUNT_SUCCESS, aID);
493
491
  },
494
492
  onError: (error: AxiosError) => {
495
- systemEventEmitter.showError(getApiErrorMessage(error, "[useConfirmProfileBackgrounds] error"));
493
+ emitHookError(error, options, "[useConfirmProfileBackgrounds] error");
496
494
  },
497
495
  });
498
496
  };
499
497
 
500
- export const useCreatePhone = () => {
498
+ export const useCreatePhone = (options?: HookToastMessages) => {
501
499
  const auth = useAuthRepository();
502
500
  const accountId = AuthStore.getState().currentAccountId;
503
- const { t } = useTranslation("hooks", { keyPrefix: "account" });
504
501
  return useMutation({
505
502
  mutationFn: (body: Login.Request.CreatePhone) => auth.CreatePhone(body),
506
503
  onSuccess: () => {
507
504
  authEventEmitter.invalidatePhones(accountId);
508
- systemEventEmitter.showSuccess(t("createPhoneSuccess", { defaultValue: "Phone added." }));
505
+ emitHookSuccess(options);
509
506
  },
510
507
  onError: (error: AxiosError) => {
511
- systemEventEmitter.showError(getApiErrorMessage(error, "[useCreatePhone] error"));
508
+ emitHookError(error, options, "[useCreatePhone] error");
512
509
  },
513
510
  });
514
511
  };
515
512
 
516
- export const useSendPhoneVerificationCode = () => {
513
+ export const useSendPhoneVerificationCode = (options?: HookToastMessages) => {
517
514
  const auth = useAuthRepository();
518
- const { t } = useTranslation("hooks", { keyPrefix: "account" });
519
515
  return useMutation({
520
516
  mutationFn: (phoneId: string) => auth.SendPhoneVerificationCode(phoneId),
521
517
  onSuccess: () => {
522
- systemEventEmitter.showSuccess(t("sendVerificationCodeSuccess", { defaultValue: "Code sent" }));
518
+ emitHookSuccess(options);
523
519
  },
524
520
  onError: (error: AxiosError) => {
525
- systemEventEmitter.showError(getApiErrorMessage(error, "[useSendPhoneVerificationCode] error"));
521
+ emitHookError(error, options, "[useSendPhoneVerificationCode] error");
526
522
  },
527
523
  });
528
524
  };
529
525
 
530
- export const useVerifyPhone = () => {
526
+ export const useVerifyPhone = (options?: HookToastMessages) => {
531
527
  const auth = useAuthRepository();
532
528
  const accountId = AuthStore.getState().currentAccountId;
533
- const { t } = useTranslation("hooks", { keyPrefix: "account" });
534
529
  return useMutation({
535
530
  mutationFn: ({ phoneId, verificationCode }: { phoneId: string; verificationCode: string }) =>
536
531
  auth.VerifyPhone(phoneId, { verificationCode }),
537
532
  onSuccess: () => {
538
533
  authEventEmitter.invalidatePhones(accountId);
539
- systemEventEmitter.showSuccess(t("verifyPhoneSuccess", { defaultValue: "Phone verified." }));
534
+ emitHookSuccess(options);
540
535
  },
541
536
  onError: (error: AxiosError) => {
542
- systemEventEmitter.showError(getApiErrorMessage(error, "[useVerifyPhone] error"));
537
+ emitHookError(error, options, "[useVerifyPhone] error");
543
538
  },
544
539
  });
545
540
  };
546
541
 
547
- export const useUpdatePhone = () => {
542
+ export const useUpdatePhone = (options?: HookToastMessages) => {
548
543
  const auth = useAuthRepository();
549
544
  const accountId = AuthStore.getState().currentAccountId;
550
- const { t } = useTranslation("hooks", { keyPrefix: "account" });
551
545
  return useMutation({
552
546
  mutationFn: ({ phoneId, phone }: { phoneId: string; phone: string }) => auth.UpdatePhone(phoneId, { phone }),
553
547
  onSuccess: () => {
554
548
  authEventEmitter.invalidatePhones(accountId);
555
- systemEventEmitter.showSuccess(t("updatePhoneSuccess", { defaultValue: "Phone updated." }));
549
+ emitHookSuccess(options);
556
550
  },
557
551
  onError: (error: AxiosError) => {
558
- systemEventEmitter.showError(getApiErrorMessage(error, "[useUpdatePhone] error"));
552
+ emitHookError(error, options, "[useUpdatePhone] error");
559
553
  },
560
554
  });
561
555
  };
562
556
 
563
- export const useSetPrimaryPhone = () => {
557
+ export const useSetPrimaryPhone = (options?: HookToastMessages) => {
564
558
  const auth = useAuthRepository();
565
559
  const accountId = AuthStore.getState().currentAccountId;
566
- const { t } = useTranslation("hooks", { keyPrefix: "account" });
567
560
  return useMutation({
568
561
  mutationFn: (phoneId: string) => auth.SetPrimaryPhone(phoneId),
569
562
  onSuccess: () => {
570
563
  authEventEmitter.invalidatePhones(accountId);
571
564
  authEventEmitter.emit(authEvents.UPDATE_ACCOUNT_SUCCESS, accountId);
572
- systemEventEmitter.showSuccess(t("setPrimaryPhoneSuccess", { defaultValue: "Primary phone updated." }));
565
+ emitHookSuccess(options);
573
566
  },
574
567
  onError: (error: AxiosError) => {
575
- systemEventEmitter.showError(getApiErrorMessage(error, "[useSetPrimaryPhone] error"));
568
+ emitHookError(error, options, "[useSetPrimaryPhone] error");
576
569
  },
577
570
  });
578
571
  };
579
572
 
580
- export const useDeletePhone = () => {
573
+ export const useDeletePhone = (options?: HookToastMessages) => {
581
574
  const auth = useAuthRepository();
582
575
  const accountId = AuthStore.getState().currentAccountId;
583
- const { t } = useTranslation("hooks", { keyPrefix: "account" });
584
576
  return useMutation({
585
577
  mutationFn: (phoneId: string) => auth.DeletePhone(phoneId),
586
578
  onSuccess: () => {
587
579
  authEventEmitter.invalidatePhones(accountId);
588
- systemEventEmitter.showSuccess(t("deletePhoneSuccess", { defaultValue: "Phone removed." }));
580
+ emitHookSuccess(options);
589
581
  },
590
582
  onError: (error: AxiosError) => {
591
- systemEventEmitter.showError(getApiErrorMessage(error, "[useDeletePhone] error"));
583
+ emitHookError(error, options, "[useDeletePhone] error");
592
584
  },
593
585
  });
594
586
  };
@@ -652,19 +644,18 @@ export const useListOwnerAuthorityProfiles = (query = "") => {
652
644
  );
653
645
  };
654
646
 
655
- export const useUpdateAuthorityProfileRoles = () => {
647
+ export const useUpdateAuthorityProfileRoles = (options?: HookToastMessages) => {
656
648
  const auth = useAuthRepository();
657
649
  const accountId = AuthStore.getState().currentAccountId;
658
- const { t } = useTranslation("hooks", { keyPrefix: "account" });
659
650
  return useMutation({
660
651
  mutationFn: ({ profileId, authorityRoles }: { profileId: string; authorityRoles: Profile.Request.UpdateAuthorityProfileRoles["authorityRoles"] }) =>
661
652
  auth.UpdateAuthorityProfileRoles(profileId, { authorityRoles }),
662
653
  onSuccess: () => {
663
654
  authEventEmitter.invalidateOwner(accountId);
664
- systemEventEmitter.showSuccess(t("updateRolesSuccess", { defaultValue: "Roles updated." }));
655
+ emitHookSuccess(options);
665
656
  },
666
657
  onError: (error: AxiosError) => {
667
- systemEventEmitter.showError(getApiErrorMessage(error, "[useUpdateAuthorityProfileRoles] error"));
658
+ emitHookError(error, options, "[useUpdateAuthorityProfileRoles] error");
668
659
  },
669
660
  });
670
661
  };
@@ -1,4 +1,5 @@
1
1
  export * from "./preference";
2
+ export * from "./toast";
2
3
  export * from "../utils/factory/hooks";
3
4
  export * from "./auth";
4
5
  export * from "./asset";
@@ -0,0 +1,23 @@
1
+ import type { AxiosError } from "axios";
2
+
3
+ import { systemEventEmitter } from "nfx-ui/events/system";
4
+ import { getApiErrorMessage } from "nfx-ui/utils/apiError";
5
+
6
+ /**
7
+ * Host resolves copy with its own `t`, then passes ready-to-show strings.
8
+ * Package mutation hooks never call `useTranslation`.
9
+ */
10
+ export type HookToastMessages = {
11
+ successMsg?: string;
12
+ failMsg?: string;
13
+ };
14
+
15
+ export function emitHookSuccess(toasts?: HookToastMessages, silent = false) {
16
+ if (silent || !toasts?.successMsg) return;
17
+ systemEventEmitter.showSuccess(toasts.successMsg);
18
+ }
19
+
20
+ export function emitHookError(error: AxiosError, toasts?: HookToastMessages, fallback = "Request failed", silent = false) {
21
+ if (silent) return;
22
+ systemEventEmitter.showError(getApiErrorMessage(error, toasts?.failMsg ?? fallback));
23
+ }
@@ -1,5 +1,4 @@
1
1
  import i18n from "i18next";
2
- import { useTranslation } from "react-i18next";
3
2
 
4
3
  import { BaseEnum } from "nfx-ui/enums/theme";
5
4
  import { LanguageEnum } from "../types";
@@ -16,9 +15,9 @@ function getLanguageI18nKey(lang: LanguageEnum): string {
16
15
  export function useLanguageLabel(): {
17
16
  getLanguageDisplayName: (lang: LanguageEnum) => string;
18
17
  } {
19
- const { t } = useTranslation("language");
20
18
  const getLanguageDisplayName = (lang: LanguageEnum): string =>
21
- t(getLanguageI18nKey(lang), {
19
+ i18n.t(getLanguageI18nKey(lang), {
20
+ ns: "language",
22
21
  defaultValue: lang,
23
22
  });
24
23
 
@@ -4,6 +4,5 @@
4
4
  export * from "./hooks";
5
5
  export * from "./languages";
6
6
  export { default as i18n } from "./languages/i18n";
7
- export * from "./resources";
8
7
  export * from "./types";
9
8
  export * from "./utils";
@@ -1,6 +1,6 @@
1
1
  /**
2
- * i18n 初始化与实例:initI18n 由 LanguageProvider 调用。
3
- * 默认合并 NFX-UI 自带的 theme / language / layout / preference 命名空间。
2
+ * Shared i18n init — host injects builtin bundles via `getBuiltinBundles`.
3
+ * Product JSON stays in each console's language folder.
4
4
  */
5
5
  import type { CreateI18nResourcesResult, ExtraBundleItem, InitI18nOptions } from "../types";
6
6
 
@@ -12,8 +12,6 @@ import { LanguageEnum, LANGUAGE_VALUES } from "nfx-ui/enums/language";
12
12
  import { PreferenceStore, setPreference } from "nfx-ui/stores/preference";
13
13
  import { safeEnum, safeStringable } from "nfx-ui/utils/safe";
14
14
 
15
- import { getDefaultNfxBundles } from "../resources";
16
-
17
15
  export function toSupportedLanguage(lng: string, fallbackLng: LanguageEnum): LanguageEnum {
18
16
  const prefix = safeStringable(lng.split("-")[0]?.toLowerCase());
19
17
  return safeEnum<LanguageEnum>(prefix || undefined, LANGUAGE_VALUES, fallbackLng);
@@ -43,19 +41,7 @@ export function initI18n(options: InitI18nOptions): void {
43
41
  return;
44
42
  }
45
43
 
46
- const nfx = getDefaultNfxBundles();
47
- const user: CreateI18nResourcesResult = options.getBuiltinBundles();
48
- const RESOURCES = ([LanguageEnum.EN, LanguageEnum.ZH, LanguageEnum.FR] as const).reduce(
49
- (acc, lang) => ({
50
- ...acc,
51
- [lang]: {
52
- ...(nfx.RESOURCES[lang] ?? {}),
53
- ...(user.RESOURCES[lang] ?? {}),
54
- },
55
- }),
56
- {} as typeof user.RESOURCES,
57
- );
58
- const NAME_SPACES = [...user.NAME_SPACES, ...nfx.NAME_SPACES.filter((n) => !user.NAME_SPACES.includes(n))];
44
+ const { RESOURCES, NAME_SPACES }: CreateI18nResourcesResult = options.getBuiltinBundles();
59
45
  const initialLng = PreferenceStore.getState().language;
60
46
 
61
47
  const persistLanguage = (lng: string) => {