You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

116 lines
3.0 KiB

  1. import { useState, useEffect, createContext, useMemo } from 'react';
  2. import { IntlProvider } from 'react-intl';
  3. import enBase from '../translations/en.json';
  4. import cnBase from '../translations/zh-CN.json';
  5. import hkBase from '../translations/zh-HK.json';
  6. import { GET_COMBO, GET_CONTENT } from "utils/ApiPathConst";
  7. import { get } from "utils/HttpUtils";
  8. const LocaleContext = createContext();
  9. export const I18nProvider = ({ children }) => {
  10. const [locale, setLocale] = useState('en');
  11. // keep base messages immutable
  12. const [systemMessages, setSystemMessages] = useState({
  13. en: { ...enBase },
  14. zh: { ...hkBase },
  15. 'zh-HK': { ...hkBase },
  16. 'zh-CN': { ...cnBase }
  17. });
  18. const [loaded, setLoaded] = useState(false);
  19. useEffect(() => {
  20. const saved = localStorage.getItem('locale');
  21. if (!saved) localStorage.setItem('locale', 'en');
  22. else setLocale(saved);
  23. }, []);
  24. useEffect(() => {
  25. let alive = true;
  26. const loadTermsAndConditions = () => {
  27. // load both endpoints then merge into state
  28. const p1 = new Promise((resolve) => {
  29. get({
  30. url: GET_CONTENT,
  31. onSuccess: (resp) => resolve(resp || {}),
  32. onError: () => resolve({})
  33. });
  34. });
  35. const p2 = new Promise((resolve) => {
  36. get({
  37. url: GET_COMBO,
  38. onSuccess: (resp) => resolve(resp || []),
  39. onError: () => resolve([])
  40. });
  41. });
  42. Promise.all([p1, p2]).then(([contentMap, comboList]) => {
  43. if (!alive) return;
  44. setSystemMessages((prev) => {
  45. // clone prev first (immutably)
  46. const next = {
  47. ...prev,
  48. en: { ...prev.en },
  49. 'zh-CN': { ...prev['zh-CN'] },
  50. 'zh-HK': { ...prev['zh-HK'] },
  51. zh: { ...prev.zh }
  52. };
  53. // merge GET_CONTENT (object)
  54. for (const key in contentMap) {
  55. const v = contentMap[key] || {};
  56. next.en[key] = v.en ?? "";
  57. next['zh-CN'][key] = v.cn ?? "";
  58. next['zh-HK'][key] = v.zh ?? "";
  59. next.zh[key] = v.zh ?? "";
  60. }
  61. // merge GET_COMBO (array)
  62. for (const item of comboList) {
  63. if (!item?.key) continue;
  64. next.en[item.key] = item.en ?? "";
  65. next['zh-CN'][item.key] = item.cn ?? "";
  66. next['zh-HK'][item.key] = item.zh ?? "";
  67. next.zh[item.key] = item.zh ?? "";
  68. }
  69. return next;
  70. });
  71. setLoaded(true);
  72. });
  73. };
  74. loadTermsAndConditions();
  75. return () => {
  76. alive = false;
  77. };
  78. }, []);
  79. const messages = useMemo(() => {
  80. return systemMessages[locale] || systemMessages.en;
  81. }, [systemMessages, locale]);
  82. return (
  83. <LocaleContext.Provider value={{ locale, setLocale }}>
  84. <IntlProvider
  85. key={locale}
  86. locale={locale}
  87. messages={messages}
  88. defaultLocale="en"
  89. >
  90. {loaded ? children : <div />}
  91. </IntlProvider>
  92. </LocaleContext.Provider>
  93. );
  94. };
  95. export default LocaleContext;