@v-miniapp/ui-react 1.0.41 → 1.0.45
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.
- package/README.md +1 -1
- package/dist/external/index.js +1993 -2004
- package/dist/index.js +1011 -1022
- package/dist-storybook/assets/ANIMATION-CUdn1GTK.js +99 -0
- package/dist-storybook/assets/APP_FRAMEWORK-ljbIOHYd.js +197 -0
- package/dist-storybook/assets/BOTTOM_TAB_BAR-DxCwCfBK.js +175 -0
- package/dist-storybook/assets/CUSTOM_ERROR_BOUNDARY-B4wTQNZc.js +250 -0
- package/dist-storybook/assets/Color-AVL7NMMY-1_8XTICv.js +1 -0
- package/dist-storybook/assets/DARK_MODE-CoHseCDO.js +57 -0
- package/dist-storybook/assets/DocsRenderer-PQXLIZUC-BO86igwd.js +2 -0
- package/dist-storybook/assets/GETTING_STARTED-H_vVi5cv.js +77 -0
- package/dist-storybook/assets/INFINITE_SCROLL-BtM3uoX0.js +111 -0
- package/dist-storybook/assets/KEEP_ALIVE-CL2au0al.js +126 -0
- package/dist-storybook/assets/LOCALE-XTCey55y.js +465 -0
- package/dist-storybook/assets/MIGRATION_GUIDE-2LONslE4.js +449 -0
- package/dist-storybook/assets/MOBILE_BEHAVIOURS-DZ6alKTX.js +177 -0
- package/dist-storybook/assets/PAGE_LAYOUT-BuOpN-1Y.js +192 -0
- package/dist-storybook/assets/ROUTING_NAVIGATION-BCPHXNto.js +335 -0
- package/dist-storybook/assets/TAILWIND_INTEGRATION-_T-VfvkM.js +87 -0
- package/dist-storybook/assets/_setToString-CbM921C9.js +1 -0
- package/dist-storybook/assets/alert-DLW8CoyB.js +1 -0
- package/dist-storybook/assets/alert.stories-B-vuojPh.js +110 -0
- package/dist-storybook/assets/avatar-GxcYPA1p.js +1 -0
- package/dist-storybook/assets/avatar.stories-KYFztAc8.js +136 -0
- package/dist-storybook/assets/axe-CmvD4WV5.js +20 -0
- package/dist-storybook/assets/badge-D_LzMVtw.js +1 -0
- package/dist-storybook/assets/badge.stories-Be2ItCmQ.js +262 -0
- package/dist-storybook/assets/blocks-BuaOUtiH.js +1243 -0
- package/dist-storybook/assets/bottom-tab-bar-CtcTAxuI.js +115 -0
- package/dist-storybook/assets/bottom-tab-bar.stories-CDmEve6z.js +186 -0
- package/dist-storybook/assets/button-CL7GeC23.js +1 -0
- package/dist-storybook/assets/button.stories-CaqLWQiY.js +287 -0
- package/dist-storybook/assets/calendar-dOCsjhVU.js +1 -0
- package/dist-storybook/assets/calendar.stories-DLWZldet.js +189 -0
- package/dist-storybook/assets/carousel-1Kww3hIz.js +37 -0
- package/dist-storybook/assets/carousel.stories-B8YbGOOr.js +217 -0
- package/dist-storybook/assets/checkbox-MGytNNRt.js +1 -0
- package/dist-storybook/assets/checkbox.stories-CLvfZPiw.js +201 -0
- package/dist-storybook/assets/chip-kG4p82WT.js +247 -0
- package/dist-storybook/assets/chip.stories-BbwJb5eD.js +442 -0
- package/dist-storybook/assets/classname-CUR_zgkh.js +1 -0
- package/dist-storybook/assets/colors-_6nFGM3e.js +1 -0
- package/dist-storybook/assets/date-Cg-Uk_pp.js +1 -0
- package/dist-storybook/assets/date-field.stories-Diptwqfv.js +129 -0
- package/dist-storybook/assets/date-picker-Dnq_-0Md.js +1 -0
- package/dist-storybook/assets/date-picker.stories-BuGWvzFL.js +123 -0
- package/dist-storybook/assets/default-error-BcnD8fFO.png +0 -0
- package/dist-storybook/assets/dialog.stories-DJ0WsSkA.js +212 -0
- package/dist-storybook/assets/dropdown.stories-D6JUYP73.js +200 -0
- package/dist-storybook/assets/embla-carousel-react.esm-BYjpaHZ9.js +1 -0
- package/dist-storybook/assets/en-Cs9O0XWn.js +15 -0
- package/dist-storybook/assets/icon-DdQsMyRa.js +1 -0
- package/dist-storybook/assets/icon.stories-B-ZvRzFf.js +365 -0
- package/dist-storybook/assets/iframe-CQAwSt4E.js +1071 -0
- package/dist-storybook/assets/iframe-yMKl6hJA.css +1 -0
- package/dist-storybook/assets/image-C3EsNRhz.js +9 -0
- package/dist-storybook/assets/image.stories-C4l8D3ju.js +255 -0
- package/dist-storybook/assets/index-B-Ksafg0.js +1 -0
- package/dist-storybook/assets/index-BV0AJWP6.js +1 -0
- package/dist-storybook/assets/index-CgMRTj-o.js +1 -0
- package/dist-storybook/assets/index-DHiZ-gXR.js +1 -0
- package/dist-storybook/assets/input-wrapper-BKHgnPy6.js +1 -0
- package/dist-storybook/assets/label-DV2iCDmN.js +27 -0
- package/dist-storybook/assets/label.stories-BwTIPFXX.js +138 -0
- package/dist-storybook/assets/matchers-7Z3WT2CE-Dw4MQV_s.js +14 -0
- package/dist-storybook/assets/navigation-bar-vI-FPasP.js +79 -0
- package/dist-storybook/assets/navigation-bar.stories-DYuFaJFD.js +73 -0
- package/dist-storybook/assets/number-field-CXKmnfKe.js +1 -0
- package/dist-storybook/assets/number-field.stories--fn26TJu.js +167 -0
- package/dist-storybook/assets/omit-Bsx5nTI0.js +1 -0
- package/dist-storybook/assets/option-item-LRh_OyV4.js +1 -0
- package/dist-storybook/assets/option-item.stories-snjAvgay.js +66 -0
- package/dist-storybook/assets/pagination-DZHoBs_4.js +1 -0
- package/dist-storybook/assets/pagination.stories-BoEs0jzS.js +91 -0
- package/dist-storybook/assets/pick-BhmhLmLe.js +1 -0
- package/dist-storybook/assets/preload-helper-PPVm8Dsz.js +1 -0
- package/dist-storybook/assets/radio-B5NJxG_l.js +1 -0
- package/dist-storybook/assets/radio.stories-DuN-Awi_.js +183 -0
- package/dist-storybook/assets/rating-BdXViYBv.js +1 -0
- package/dist-storybook/assets/rating.stories-BCcQjMEx.js +117 -0
- package/dist-storybook/assets/react-18-CNyWQ7je.js +9 -0
- package/dist-storybook/assets/react-hufnxGVs.js +1 -0
- package/dist-storybook/assets/search-field-CQqgFbfg.js +1 -0
- package/dist-storybook/assets/search-field.stories-DiCZbhng.js +79 -0
- package/dist-storybook/assets/section-content-DGNB4eLN.js +1 -0
- package/dist-storybook/assets/section.stories-C2I_kKhu.js +69 -0
- package/dist-storybook/assets/sheet.stories-wk1JaKU5.js +152 -0
- package/dist-storybook/assets/skeleton-C91JgehG.js +1 -0
- package/dist-storybook/assets/skeleton.stories-BCmX-VNr.js +139 -0
- package/dist-storybook/assets/store-CPumdfcU.js +1 -0
- package/dist-storybook/assets/store-D2RudmNr.js +18 -0
- package/dist-storybook/assets/switch-p-aXI-ev.js +1 -0
- package/dist-storybook/assets/switch.stories-BqPLNKB9.js +250 -0
- package/dist-storybook/assets/tab-bar-CSeCmtIZ.js +31 -0
- package/dist-storybook/assets/tab-bar.stories-Cb6v8H2w.js +136 -0
- package/dist-storybook/assets/text-area-DwSXyqOe.js +1 -0
- package/dist-storybook/assets/text-area.stories-By8bCfgc.js +87 -0
- package/dist-storybook/assets/text-field-jK6rpOo2.js +1 -0
- package/dist-storybook/assets/text-field.stories-CrWBAhvI.js +92 -0
- package/dist-storybook/assets/toast-provider-DurnMJhd.js +9 -0
- package/dist-storybook/assets/toast.stories-iWAToAZA.js +201 -0
- package/dist-storybook/assets/tooltip-QDdel5My.js +1 -0
- package/dist-storybook/assets/tooltip.stories-RC6SuPPD.js +153 -0
- package/dist-storybook/assets/typography-DEpAJl_i.js +1 -0
- package/dist-storybook/assets/typography.stories-Bu8qFugR.js +202 -0
- package/dist-storybook/assets/uploader.stories-B2wW9qVy.js +65 -0
- package/dist-storybook/assets/use-app-pause-B_tWHKJK.js +29 -0
- package/dist-storybook/assets/use-app-resume--900G-dV.js +29 -0
- package/dist-storybook/assets/use-custom-icon-event-3VExRzvC.js +29 -0
- package/dist-storybook/assets/use-did-hide-BUsL73ab.js +48 -0
- package/dist-storybook/assets/use-did-show-C1-VLDxi.js +49 -0
- package/dist-storybook/assets/use-histories-E4E2jJEY.js +50 -0
- package/dist-storybook/assets/use-history-o1im8IDj.js +67 -0
- package/dist-storybook/assets/use-location-CUEaBO4P.js +56 -0
- package/dist-storybook/assets/use-navigate-C4CTuFSZ.js +84 -0
- package/dist-storybook/assets/use-navigation-type-Dcz4hgKo.js +44 -0
- package/dist-storybook/assets/use-page-config-DSJBVQbq.js +48 -0
- package/dist-storybook/assets/use-page-scroll-dY-U1Vv4.js +69 -0
- package/dist-storybook/assets/use-page-state-CtNpwGPN.js +79 -0
- package/dist-storybook/assets/use-settings-changed-BBJwIHTE.js +29 -0
- package/dist-storybook/assets/v-mini-icon-Dn1BmJzb.woff2 +0 -0
- package/dist-storybook/assets/visibility-sensor-CwrzJO06.js +1 -0
- package/dist-storybook/iframe.html +670 -0
- package/dist-storybook/index.html +132 -0
- package/dist-storybook/index.json +1 -0
- package/dist-storybook/project.json +1 -0
- package/dist-storybook/sb-addons/a11y-2/manager-bundle.js +5 -0
- package/dist-storybook/sb-addons/docs-1/manager-bundle.js +151 -0
- package/dist-storybook/sb-addons/storybook-build-3/manager-bundle.js +3 -0
- package/dist-storybook/sb-addons/storybook-core-server-presets-0/common-manager-bundle.js +971 -0
- package/dist-storybook/sb-manager/globals-module-info.js +799 -0
- package/dist-storybook/sb-manager/globals-runtime.js +69791 -0
- package/dist-storybook/sb-manager/globals.js +34 -0
- package/dist-storybook/sb-manager/runtime.js +13198 -0
- package/dist-storybook/stories-data.json +374 -0
- package/dist-storybook/vite-inject-mocker-entry.js +2 -0
- package/package.json +1 -1
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import{j as e}from"./iframe-CQAwSt4E.js";import{useMDXComponents as a}from"./index-CgMRTj-o.js";import{b as l,M as t}from"./blocks-BuaOUtiH.js";import"./preload-helper-PPVm8Dsz.js";import"./index-DHiZ-gXR.js";function r(i){const n={code:"code",h1:"h1",h2:"h2",h3:"h3",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",ul:"ul",...a(),...i.components};return e.jsxs(e.Fragment,{children:[`
|
|
2
|
+
`,`
|
|
3
|
+
`,e.jsx(l,{title:"App Framework/Keep Alive"}),`
|
|
4
|
+
`,e.jsx(n.h1,{id:"keep-alive",children:"Keep Alive"}),`
|
|
5
|
+
`,e.jsx(n.p,{children:"Keep Alive cho phép giữ các pages trong DOM (ẩn) thay vì unmount khi navigate, giúp giữ state và scroll position."}),`
|
|
6
|
+
`,e.jsx(n.h2,{id:"cấu-trúc",children:"Cấu trúc"}),`
|
|
7
|
+
`,e.jsx(n.pre,{children:e.jsx(n.code,{className:"language-typescript",children:`type IAppKeepAliveState = {
|
|
8
|
+
enable?: boolean
|
|
9
|
+
maxStack?: number
|
|
10
|
+
freeze?: boolean
|
|
11
|
+
freezeDelay?: number
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Page-level có thể override bằng boolean hoặc config object
|
|
15
|
+
type IPageState = {
|
|
16
|
+
keepAlive?: boolean | IAppKeepAliveState
|
|
17
|
+
freeze?: boolean
|
|
18
|
+
freezeDelay?: number
|
|
19
|
+
}
|
|
20
|
+
`})}),`
|
|
21
|
+
`,e.jsx(n.h2,{id:"thuộc-tính",children:"Thuộc tính"}),`
|
|
22
|
+
`,e.jsx(t,{children:"\n| Thuộc tính | Kiểu dữ liệu | Mặc định | Mô tả |\n| :--- | :--- | :--- | :--- |\n| `enable` | `boolean` | `true` | Bật/tắt keep-alive |\n| `maxStack` | `number` | `5` | Số lượng pages tối đa được giữ trong DOM |\n| `freeze` | `boolean` | `true` | Bật/tắt freeze (ngăn re-render cho pages không hiển thị) |\n| `freezeDelay` | `number` | `3000` | Delay (ms) trước khi freeze page sau khi navigate đi |\n"}),`
|
|
23
|
+
`,e.jsx(n.h2,{id:"cách-hoạt-động",children:"Cách hoạt động"}),`
|
|
24
|
+
`,e.jsxs(n.ol,{children:[`
|
|
25
|
+
`,e.jsxs(n.li,{children:[e.jsx(n.strong,{children:"Khi navigate đi"}),": Page được ẩn (",e.jsx(n.code,{children:"display: none"}),") nhưng vẫn trong DOM"]}),`
|
|
26
|
+
`,e.jsxs(n.li,{children:[e.jsx(n.strong,{children:"Khi navigate đến"}),": Page được hiển thị (",e.jsx(n.code,{children:"display: block"}),")"]}),`
|
|
27
|
+
`,e.jsxs(n.li,{children:[e.jsx(n.strong,{children:"Freeze"}),": Các pages không hiển thị được freeze bằng ",e.jsx(n.code,{children:"Freeze"})," component để tránh re-render. Freeze được áp dụng sau ",e.jsx(n.code,{children:"freezeDelay"})," (mặc định: 3000ms) để cho phép animation hoàn thành."]}),`
|
|
28
|
+
`,e.jsxs(n.li,{children:[e.jsx(n.strong,{children:"Giới hạn"}),": Chỉ giữ tối đa ",e.jsx(n.code,{children:"maxStack"})," pages trong DOM"]}),`
|
|
29
|
+
`]}),`
|
|
30
|
+
`,e.jsx(n.h2,{id:"cấu-hình",children:"Cấu hình"}),`
|
|
31
|
+
`,e.jsx(n.h3,{id:"app-level-config",children:"App-level Config"}),`
|
|
32
|
+
`,e.jsx(n.p,{children:"Cấu hình keep-alive mặc định cho tất cả pages:"}),`
|
|
33
|
+
`,e.jsx(n.pre,{children:e.jsx(n.code,{className:"language-tsx",children:`const appConfig: IAppConfig = {
|
|
34
|
+
keepAlive: {
|
|
35
|
+
enable: true, // Bật keep-alive
|
|
36
|
+
maxStack: 5, // Giữ tối đa 5 pages
|
|
37
|
+
freeze: true, // Bật freeze để tránh re-render
|
|
38
|
+
freezeDelay: 3000, // Freeze sau 3 giây
|
|
39
|
+
},
|
|
40
|
+
pages: [
|
|
41
|
+
// Tất cả pages sẽ được keep-alive
|
|
42
|
+
],
|
|
43
|
+
}
|
|
44
|
+
`})}),`
|
|
45
|
+
`,e.jsx(n.h3,{id:"page-level-config",children:"Page-level Config"}),`
|
|
46
|
+
`,e.jsx(n.p,{children:"Override keep-alive cho từng page cụ thể:"}),`
|
|
47
|
+
`,e.jsx(n.pre,{children:e.jsx(n.code,{className:"language-tsx",children:`const appConfig: IAppConfig = {
|
|
48
|
+
keepAlive: {
|
|
49
|
+
enable: true,
|
|
50
|
+
maxStack: 5,
|
|
51
|
+
},
|
|
52
|
+
pages: [
|
|
53
|
+
{
|
|
54
|
+
pathname: '/home',
|
|
55
|
+
Component: HomePage,
|
|
56
|
+
// Page này dùng app-level config (keep-alive enabled)
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
pathname: '/login',
|
|
60
|
+
Component: LoginPage,
|
|
61
|
+
// Tắt keep-alive cho page này
|
|
62
|
+
keepAlive: false,
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
pathname: '/detail',
|
|
66
|
+
Component: DetailPage,
|
|
67
|
+
// Override config cho page này
|
|
68
|
+
keepAlive: {
|
|
69
|
+
enable: true,
|
|
70
|
+
maxStack: 3, // Giữ ít hơn pages khác
|
|
71
|
+
},
|
|
72
|
+
// Tắt freeze cho page này (vẫn keep-alive nhưng không freeze)
|
|
73
|
+
freeze: false,
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
pathname: '/video',
|
|
77
|
+
Component: VideoPage,
|
|
78
|
+
keepAlive: true,
|
|
79
|
+
// Freeze ngay lập tức (không delay)
|
|
80
|
+
freeze: true,
|
|
81
|
+
freezeDelay: 0,
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
}
|
|
85
|
+
`})}),`
|
|
86
|
+
`,e.jsx(n.h2,{id:"lợi-ích",children:"Lợi ích"}),`
|
|
87
|
+
`,e.jsxs(n.ul,{children:[`
|
|
88
|
+
`,e.jsxs(n.li,{children:[e.jsx(n.strong,{children:"Giữ state"}),": Component state không bị mất khi navigate"]}),`
|
|
89
|
+
`,e.jsxs(n.li,{children:[e.jsx(n.strong,{children:"Giữ scroll position"}),": Scroll position được giữ nguyên (kết hợp với ",e.jsx(n.code,{children:"scrollRestoration"}),")"]}),`
|
|
90
|
+
`,e.jsxs(n.li,{children:[e.jsx(n.strong,{children:"Form data"}),": Form data không bị mất khi navigate back"]}),`
|
|
91
|
+
`,e.jsxs(n.li,{children:[e.jsx(n.strong,{children:"Performance"}),": Không cần re-render khi navigate back/forward"]}),`
|
|
92
|
+
`]}),`
|
|
93
|
+
`,e.jsx(n.h2,{id:"lưu-ý",children:"Lưu ý"}),`
|
|
94
|
+
`,e.jsxs(n.ul,{children:[`
|
|
95
|
+
`,e.jsx(n.li,{children:"Pages được giữ trong DOM nên vẫn tốn memory"}),`
|
|
96
|
+
`,e.jsxs(n.li,{children:[e.jsx(n.code,{children:"maxStack"})," giới hạn số pages để tránh tốn quá nhiều memory"]}),`
|
|
97
|
+
`,e.jsx(n.li,{children:"Chỉ pages trong stack (theo thứ tự) mới được keep-alive"}),`
|
|
98
|
+
`,e.jsxs(n.li,{children:["Pages ngoài ",e.jsx(n.code,{children:"maxStack"})," sẽ bị unmount"]}),`
|
|
99
|
+
`,e.jsxs(n.li,{children:[e.jsx(n.strong,{children:"Freeze"}),": Khi ",e.jsx(n.code,{children:"freeze: true"}),", pages không hiển thị sẽ bị freeze để tránh re-render, giúp tiết kiệm performance"]}),`
|
|
100
|
+
`,e.jsxs(n.li,{children:[e.jsx(n.strong,{children:"Freeze Delay"}),": ",e.jsx(n.code,{children:"freezeDelay"})," cho phép animation hoàn thành trước khi freeze. Nếu set ",e.jsx(n.code,{children:"0"}),", page sẽ freeze ngay lập tức"]}),`
|
|
101
|
+
`]}),`
|
|
102
|
+
`,e.jsx(n.h2,{id:"ví-dụ",children:"Ví dụ"}),`
|
|
103
|
+
`,e.jsx(n.pre,{children:e.jsx(n.code,{className:"language-tsx",children:`const appConfig: IAppConfig = {
|
|
104
|
+
keepAlive: {
|
|
105
|
+
enable: true,
|
|
106
|
+
maxStack: 5,
|
|
107
|
+
},
|
|
108
|
+
pages: [
|
|
109
|
+
{
|
|
110
|
+
pathname: '/home',
|
|
111
|
+
Component: HomePage,
|
|
112
|
+
// Keep-alive enabled
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
pathname: '/list',
|
|
116
|
+
Component: ListPage,
|
|
117
|
+
// Keep-alive enabled, giữ scroll position khi back
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
pathname: '/login',
|
|
121
|
+
Component: LoginPage,
|
|
122
|
+
keepAlive: false, // Tắt keep-alive, reset mỗi lần vào
|
|
123
|
+
},
|
|
124
|
+
],
|
|
125
|
+
}
|
|
126
|
+
`})})]})}function g(i={}){const{wrapper:n}={...a(),...i.components};return n?e.jsx(n,{...i,children:e.jsx(r,{...i})}):r(i)}export{g as default};
|
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
import{j as n}from"./iframe-CQAwSt4E.js";import{useMDXComponents as i}from"./index-CgMRTj-o.js";import{b as l,M as t}from"./blocks-BuaOUtiH.js";import"./preload-helper-PPVm8Dsz.js";import"./index-DHiZ-gXR.js";function c(s){const e={code:"code",h1:"h1",h2:"h2",h3:"h3",h4:"h4",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",ul:"ul",...i(),...s.components};return n.jsxs(n.Fragment,{children:[`
|
|
2
|
+
`,`
|
|
3
|
+
`,n.jsx(l,{title:"App Framework/Locale"}),`
|
|
4
|
+
`,n.jsx(e.h1,{id:"locales-module-multi-language",children:"Locales Module (Multi-language)"}),`
|
|
5
|
+
`,n.jsxs(e.p,{children:[n.jsx(e.strong,{children:"Locales module"})," của ",n.jsx(e.code,{children:"@v-miniapp/ui-react"})," cung cấp giải pháp quản lý đa ngôn ngữ (i18n) mạnh mẽ với TypeScript type safety, hỗ trợ custom locales keys và custom language codes thông qua module augmentation."]}),`
|
|
6
|
+
`,n.jsx(e.h2,{id:"giới-thiệu",children:"Giới thiệu"}),`
|
|
7
|
+
`,n.jsx(e.p,{children:"Locales module cho phép bạn:"}),`
|
|
8
|
+
`,n.jsxs(e.ul,{children:[`
|
|
9
|
+
`,n.jsx(e.li,{children:"Quản lý translations cho nhiều ngôn ngữ"}),`
|
|
10
|
+
`,n.jsx(e.li,{children:"Type-safe với TypeScript thông qua module augmentation"}),`
|
|
11
|
+
`,n.jsx(e.li,{children:"Tự động cập nhật UI khi ngôn ngữ thay đổi (reactivity)"}),`
|
|
12
|
+
`,n.jsx(e.li,{children:"Hỗ trợ template parameters trong translations"}),`
|
|
13
|
+
`,n.jsxs(e.li,{children:[n.jsx(e.strong,{children:"Sử dụng độc lập"})," hoặc ",n.jsx(e.strong,{children:"tích hợp sẵn với App component"})]}),`
|
|
14
|
+
`]}),`
|
|
15
|
+
`,n.jsx(e.h2,{id:"tính-năng",children:"Tính năng"}),`
|
|
16
|
+
`,n.jsxs(e.ul,{children:[`
|
|
17
|
+
`,n.jsxs(e.li,{children:[n.jsx(e.strong,{children:"Type-safe"}),": TypeScript tự động infer types cho locales keys và language codes từ module augmentation"]}),`
|
|
18
|
+
`,n.jsxs(e.li,{children:[n.jsx(e.strong,{children:"Default languages"}),": Hỗ trợ sẵn tiếng Việt (",n.jsx(e.code,{children:"vi"}),") và tiếng Anh (",n.jsx(e.code,{children:"en"}),")"]}),`
|
|
19
|
+
`,n.jsxs(e.li,{children:[n.jsx(e.strong,{children:"Default translations"}),": Có sẵn các translation keys mặc định cho các component (pull-to-refresh, calendar, etc.)"]}),`
|
|
20
|
+
`,n.jsxs(e.li,{children:[n.jsx(e.strong,{children:"Custom languages"}),": Dễ dàng thêm ngôn ngữ mới (ví dụ: ",n.jsx(e.code,{children:"jp"}),", ",n.jsx(e.code,{children:"ko"}),", ",n.jsx(e.code,{children:"zh"}),") thông qua module augmentation"]}),`
|
|
21
|
+
`,n.jsxs(e.li,{children:[n.jsx(e.strong,{children:"Custom keys"}),": Mở rộng locales keys tùy chỉnh với type checking"]}),`
|
|
22
|
+
`,n.jsxs(e.li,{children:[n.jsx(e.strong,{children:"Template params"}),": Hỗ trợ template parameters trong translations"]}),`
|
|
23
|
+
`,n.jsxs(e.li,{children:[n.jsx(e.strong,{children:"URL-based init"}),": Tự động lấy ngôn ngữ từ URL search params (",n.jsx(e.code,{children:"langCode"}),")"]}),`
|
|
24
|
+
`,n.jsxs(e.li,{children:[n.jsx(e.strong,{children:"Multiple APIs"}),": Hỗ trợ hooks (",n.jsx(e.code,{children:"useTranslate"}),", ",n.jsx(e.code,{children:"useLanguage"}),"), và standalone functions"]}),`
|
|
25
|
+
`]}),`
|
|
26
|
+
`,n.jsx(e.h2,{id:"text-mặc-định",children:"Text mặc định"}),`
|
|
27
|
+
`,n.jsx(e.p,{children:"Locales module đã có sẵn các translation keys mặc định cho các component:"}),`
|
|
28
|
+
`,n.jsx(e.h3,{id:"pull-to-refresh",children:"Pull to Refresh"}),`
|
|
29
|
+
`,n.jsxs(e.ul,{children:[`
|
|
30
|
+
`,n.jsxs(e.li,{children:[n.jsx(e.code,{children:"pullToRefresh.canReleaseText"}),': "Thả để làm mới" (vi) / "Release to refresh" (en)']}),`
|
|
31
|
+
`,n.jsxs(e.li,{children:[n.jsx(e.code,{children:"pullToRefresh.completeText"}),': "Làm mới hoàn tất" (vi) / "Refresh complete" (en)']}),`
|
|
32
|
+
`,n.jsxs(e.li,{children:[n.jsx(e.code,{children:"pullToRefresh.pullingText"}),': "Kéo để làm mới" (vi) / "Pulling to refresh" (en)']}),`
|
|
33
|
+
`,n.jsxs(e.li,{children:[n.jsx(e.code,{children:"pullToRefresh.refreshingText"}),': "Đang làm mới..." (vi) / "Refreshing..." (en)']}),`
|
|
34
|
+
`]}),`
|
|
35
|
+
`,n.jsx(e.h3,{id:"calendar",children:"Calendar"}),`
|
|
36
|
+
`,n.jsxs(e.ul,{children:[`
|
|
37
|
+
`,n.jsxs(e.li,{children:[n.jsx(e.code,{children:"calendar.monthLabel[0-11]"}),": Tên các tháng (Tháng 1-12 / January-December)"]}),`
|
|
38
|
+
`,n.jsxs(e.li,{children:[n.jsx(e.code,{children:"calendar.weekdayLabel[0-6]"}),": Tên các ngày trong tuần (CN-T7 / Sun-Sat)"]}),`
|
|
39
|
+
`,n.jsxs(e.li,{children:[n.jsx(e.code,{children:"calendar.headerLabel"}),": `{month} - {year}` - Template cho header"]}),`
|
|
40
|
+
`,n.jsxs(e.li,{children:[n.jsx(e.code,{children:"calendar.weekday"}),": `{day}` - Template cho weekday"]}),`
|
|
41
|
+
`]}),`
|
|
42
|
+
`,n.jsxs(e.p,{children:["Bạn có thể override các keys này hoặc thêm keys mới trong ",n.jsx(e.code,{children:"localesConfig"}),"."]}),`
|
|
43
|
+
`,n.jsx(e.h2,{id:"khởi-tạo-initialization",children:"Khởi tạo (Initialization)"}),`
|
|
44
|
+
`,n.jsx(e.p,{children:"Locales module có thể được sử dụng theo 2 cách:"}),`
|
|
45
|
+
`,n.jsxs(e.ol,{children:[`
|
|
46
|
+
`,n.jsxs(e.li,{children:[n.jsx(e.strong,{children:"Tích hợp sẵn với App component"})," (Khuyến nghị) - App component đã tích hợp sẵn ",n.jsx(e.code,{children:"LocalesProvider"})]}),`
|
|
47
|
+
`,n.jsxs(e.li,{children:[n.jsx(e.strong,{children:"Sử dụng độc lập"})," - Sử dụng ",n.jsx(e.code,{children:"LocalesProvider"})," trực tiếp trong ứng dụng của bạn"]}),`
|
|
48
|
+
`]}),`
|
|
49
|
+
`,n.jsx(e.h3,{id:"sử-dụng-với-app-component-khuyến-nghị",children:"Sử dụng với App Component (Khuyến nghị)"}),`
|
|
50
|
+
`,n.jsxs(e.p,{children:["App component đã tích hợp sẵn ",n.jsx(e.code,{children:"LocalesProvider"}),", bạn chỉ cần truyền ",n.jsx(e.code,{children:"localesConfig"}),":"]}),`
|
|
51
|
+
`,n.jsx(e.pre,{children:n.jsx(e.code,{className:"language-tsx",children:`import { App, type IAppConfig, type ILocalesConfig } from '@v-miniapp/ui-react'
|
|
52
|
+
|
|
53
|
+
const appConfig: IAppConfig = {
|
|
54
|
+
pages: [
|
|
55
|
+
// ... your pages
|
|
56
|
+
],
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Cấu hình locales
|
|
60
|
+
const localesConfig: ILocalesConfig = {
|
|
61
|
+
defaultLanguage: 'vi', // hoặc 'en', hoặc 'system'
|
|
62
|
+
resources: {
|
|
63
|
+
vi: {
|
|
64
|
+
'app.title': 'Ứng dụng của tôi',
|
|
65
|
+
'app.description': 'Mô tả ứng dụng',
|
|
66
|
+
},
|
|
67
|
+
en: {
|
|
68
|
+
'app.title': 'My Application',
|
|
69
|
+
'app.description': 'Application description',
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function MiniApp() {
|
|
75
|
+
return <App config={appConfig} localesConfig={localesConfig} />
|
|
76
|
+
}
|
|
77
|
+
`})}),`
|
|
78
|
+
`,n.jsxs(e.p,{children:[n.jsx(e.strong,{children:"Thứ tự ưu tiên ngôn ngữ:"})," Locales module sẽ lấy ngôn ngữ theo thứ tự ưu tiên:"]}),`
|
|
79
|
+
`,n.jsxs(e.ol,{children:[`
|
|
80
|
+
`,n.jsxs(e.li,{children:["Từ config (",n.jsx(e.code,{children:"defaultLanguage"}),")"]}),`
|
|
81
|
+
`,n.jsxs(e.li,{children:["Từ URL search params (",n.jsx(e.code,{children:"langCode"}),")"]}),`
|
|
82
|
+
`,n.jsxs(e.li,{children:["Mặc định tiếng Việt (",n.jsx(e.code,{children:"'vi'"}),")"]}),`
|
|
83
|
+
`]}),`
|
|
84
|
+
`,n.jsxs(e.p,{children:["Ví dụ: Nếu không có ",n.jsx(e.code,{children:"defaultLanguage"})," trong config, URL ",n.jsx(e.code,{children:"https://yourapp.com?langCode=en"})," sẽ tự động set ngôn ngữ thành ",n.jsx(e.code,{children:"'en'"}),". Nếu không có trong URL, sẽ fallback về ",n.jsx(e.code,{children:"'vi'"}),"."]}),`
|
|
85
|
+
`,n.jsx(e.h3,{id:"sử-dụng-localesprovider-độc-lập",children:"Sử dụng LocalesProvider độc lập"}),`
|
|
86
|
+
`,n.jsxs(e.p,{children:["Nếu không sử dụng App component, bạn có thể sử dụng ",n.jsx(e.code,{children:"LocalesProvider"})," trực tiếp:"]}),`
|
|
87
|
+
`,n.jsx(e.pre,{children:n.jsx(e.code,{className:"language-tsx",children:`import { LocalesProvider, type ILocalesConfig } from '@v-miniapp/ui-react'
|
|
88
|
+
|
|
89
|
+
const localesConfig: ILocalesConfig = {
|
|
90
|
+
defaultLanguage: 'vi',
|
|
91
|
+
resources: {
|
|
92
|
+
vi: {
|
|
93
|
+
'app.title': 'Ứng dụng của tôi',
|
|
94
|
+
},
|
|
95
|
+
en: {
|
|
96
|
+
'app.title': 'My Application',
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function MyApp() {
|
|
102
|
+
return (
|
|
103
|
+
<>
|
|
104
|
+
<LocalesProvider config={localesConfig} />
|
|
105
|
+
{/* Your app content */}
|
|
106
|
+
</>
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
`})}),`
|
|
110
|
+
`,n.jsxs(e.p,{children:[n.jsx(e.strong,{children:"Lưu ý:"})," ",n.jsx(e.code,{children:"LocalesProvider"})," chỉ init một lần khi mount, nên cần đặt ở top-level của ứng dụng."]}),`
|
|
111
|
+
`,n.jsx(e.h2,{id:"mở-rộng-với-custom-languages-và-locales-keys",children:"Mở rộng với Custom Languages và Locales Keys"}),`
|
|
112
|
+
`,n.jsxs(e.p,{children:["Bạn có thể thêm các ngôn ngữ tùy chỉnh ngoài ",n.jsx(e.code,{children:"vi"})," và ",n.jsx(e.code,{children:"en"}),", đồng thời định nghĩa custom locales keys với type safety thông qua ",n.jsx(e.strong,{children:"module augmentation"})," trong ",n.jsx(e.code,{children:"global.d.ts"}),":"]}),`
|
|
113
|
+
`,n.jsx(e.h3,{id:"bước-1-tạo-json-files-cho-translations",children:"Bước 1: Tạo JSON files cho translations"}),`
|
|
114
|
+
`,n.jsxs(e.p,{children:[n.jsx(e.code,{children:"locales/vi.json"}),":"]}),`
|
|
115
|
+
`,n.jsx(e.pre,{children:n.jsx(e.code,{className:"language-json",children:`{
|
|
116
|
+
"app.title": "Ứng dụng của tôi",
|
|
117
|
+
"app.description": "Mô tả ứng dụng",
|
|
118
|
+
"user.greeting": "Xin chào {name}!",
|
|
119
|
+
"user.welcome": "Chào mừng bạn đến với {app}",
|
|
120
|
+
"button.submit": "Gửi",
|
|
121
|
+
"button.reset": "Đặt lại"
|
|
122
|
+
}
|
|
123
|
+
`})}),`
|
|
124
|
+
`,n.jsxs(e.p,{children:[n.jsx(e.code,{children:"locales/en.json"}),":"]}),`
|
|
125
|
+
`,n.jsx(e.pre,{children:n.jsx(e.code,{className:"language-json",children:`{
|
|
126
|
+
"app.title": "My Application",
|
|
127
|
+
"app.description": "Application description",
|
|
128
|
+
"user.greeting": "Hello {name}!",
|
|
129
|
+
"user.welcome": "Welcome to {app}",
|
|
130
|
+
"button.submit": "Submit",
|
|
131
|
+
"button.reset": "Reset"
|
|
132
|
+
}
|
|
133
|
+
`})}),`
|
|
134
|
+
`,n.jsxs(e.p,{children:[n.jsx(e.code,{children:"locales/jp.json"}),":"]}),`
|
|
135
|
+
`,n.jsx(e.pre,{children:n.jsx(e.code,{className:"language-json",children:`{
|
|
136
|
+
"app.title": "私のアプリケーション",
|
|
137
|
+
"app.description": "アプリケーションの説明",
|
|
138
|
+
"user.greeting": "こんにちは {name}!",
|
|
139
|
+
"user.welcome": "{app}へようこそ",
|
|
140
|
+
"button.submit": "送信",
|
|
141
|
+
"button.reset": "リセット"
|
|
142
|
+
}
|
|
143
|
+
`})}),`
|
|
144
|
+
`,n.jsx(e.h3,{id:"bước-2-declare-module-augmentation-trong-globaldts",children:"Bước 2: Declare module augmentation trong global.d.ts"}),`
|
|
145
|
+
`,n.jsxs(e.p,{children:["Tạo hoặc update file ",n.jsx(e.code,{children:"global.d.ts"})," (hoặc ",n.jsx(e.code,{children:"vite-env.d.ts"}),") trong project của bạn:"]}),`
|
|
146
|
+
`,n.jsx(e.pre,{children:n.jsx(e.code,{className:"language-tsx",children:`import '@v-miniapp/ui-react'
|
|
147
|
+
import vi from './locales/vi.json'
|
|
148
|
+
|
|
149
|
+
declare module '@v-miniapp/ui-react' {
|
|
150
|
+
interface ICustomLocales {
|
|
151
|
+
resource: typeof vi
|
|
152
|
+
language: 'jp' | 'en' | 'vi'
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
`})}),`
|
|
156
|
+
`,n.jsx(e.p,{children:n.jsx(e.strong,{children:"Lưu ý:"})}),`
|
|
157
|
+
`,n.jsxs(e.ul,{children:[`
|
|
158
|
+
`,n.jsxs(e.li,{children:["Import từ JSON files và dùng ",n.jsx(e.code,{children:"typeof vi"})," để infer types tự động, đảm bảo type safety"]}),`
|
|
159
|
+
`,n.jsxs(e.li,{children:[n.jsx(e.code,{children:"resource"})," là type của locales keys (từ JSON file)"]}),`
|
|
160
|
+
`,n.jsxs(e.li,{children:[n.jsx(e.code,{children:"language"})," là union type của các language codes mà bạn muốn hỗ trợ"]}),`
|
|
161
|
+
`]}),`
|
|
162
|
+
`,n.jsx(e.h3,{id:"bước-3-sử-dụng-trong-localesconfig",children:"Bước 3: Sử dụng trong localesConfig"}),`
|
|
163
|
+
`,n.jsx(e.pre,{children:n.jsx(e.code,{className:"language-tsx",children:`import vi from './locales/vi.json'
|
|
164
|
+
import en from './locales/en.json'
|
|
165
|
+
import jp from './locales/jp.json'
|
|
166
|
+
|
|
167
|
+
const localesConfig: ILocalesConfig = {
|
|
168
|
+
defaultLanguage: 'vi',
|
|
169
|
+
resources: {
|
|
170
|
+
vi,
|
|
171
|
+
en,
|
|
172
|
+
jp,
|
|
173
|
+
},
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function MiniApp() {
|
|
177
|
+
return <App config={appConfig} localesConfig={localesConfig} />
|
|
178
|
+
}
|
|
179
|
+
`})}),`
|
|
180
|
+
`,n.jsxs(e.p,{children:["Sau khi declare ",n.jsx(e.code,{children:"ICustomLocales"}),", TypeScript sẽ tự động infer:"]}),`
|
|
181
|
+
`,n.jsxs(e.ul,{children:[`
|
|
182
|
+
`,n.jsxs(e.li,{children:["Locales keys từ ",n.jsx(e.code,{children:"typeof vi"})]}),`
|
|
183
|
+
`,n.jsxs(e.li,{children:["Language codes từ ",n.jsx(e.code,{children:"'jp' | 'en' | 'vi'"})]}),`
|
|
184
|
+
`]}),`
|
|
185
|
+
`,n.jsx(e.p,{children:"Tất cả các functions và hooks sẽ có type safety với các keys và languages đã declare."}),`
|
|
186
|
+
`,n.jsx(e.h2,{id:"sử-dụng-translation-functions",children:"Sử dụng Translation Functions"}),`
|
|
187
|
+
`,n.jsx(e.h3,{id:"standalone-functions",children:"Standalone Functions"}),`
|
|
188
|
+
`,n.jsx(e.pre,{children:n.jsx(e.code,{className:"language-tsx",children:`import { t, getLanguage, setLanguage } from '@v-miniapp/ui-react'
|
|
189
|
+
|
|
190
|
+
// Basic usage
|
|
191
|
+
const title = t('app.title')
|
|
192
|
+
|
|
193
|
+
// With template params
|
|
194
|
+
const greeting = t('user.greeting', { name: 'John' })
|
|
195
|
+
// Translation: "Xin chào {name}!" → "Xin chào John!"
|
|
196
|
+
|
|
197
|
+
// Get translation in specific language
|
|
198
|
+
const titleVi = t('app.title', undefined, 'vi')
|
|
199
|
+
const titleEn = t('app.title', undefined, 'en')
|
|
200
|
+
|
|
201
|
+
// Get current language
|
|
202
|
+
const currentLang = getLanguage() // 'vi' | 'en' | 'jp' (inferred từ ICustomLocales)
|
|
203
|
+
|
|
204
|
+
// Change language
|
|
205
|
+
setLanguage('en') // Type-safe, chỉ chấp nhận languages đã declare
|
|
206
|
+
`})}),`
|
|
207
|
+
`,n.jsx(e.p,{children:n.jsx(e.strong,{children:"⚠️ Lưu ý về Reactivity:"})}),`
|
|
208
|
+
`,n.jsxs(e.p,{children:["Các standalone functions ",n.jsx(e.code,{children:"t"})," ",n.jsx(e.strong,{children:"không có reactivity"}),". Khi ngôn ngữ thay đổi, component sử dụng các functions này sẽ ",n.jsx(e.strong,{children:"không tự động re-render"}),"."]}),`
|
|
209
|
+
`,n.jsx(e.pre,{children:n.jsx(e.code,{className:"language-tsx",children:`// ❌ Không reactive - Component không re-render khi language thay đổi
|
|
210
|
+
function MyComponent() {
|
|
211
|
+
const title = t('app.title')
|
|
212
|
+
const greeting = t('user.greeting', undefined, 'en')
|
|
213
|
+
|
|
214
|
+
return <div>{title} - {greeting}</div>
|
|
215
|
+
// Khi setLanguage() được gọi, component này sẽ KHÔNG re-render
|
|
216
|
+
}
|
|
217
|
+
`})}),`
|
|
218
|
+
`,n.jsxs(e.p,{children:[n.jsx(e.strong,{children:"Giải pháp:"})," Sử dụng ",n.jsx(e.strong,{children:"React Hook"})," ",n.jsx(e.code,{children:"useTranslate"})," trong components để có reactivity."]}),`
|
|
219
|
+
`,n.jsx(e.h3,{id:"react-hooks",children:"React Hooks"}),`
|
|
220
|
+
`,n.jsx(e.p,{children:"Sử dụng hooks trong React components để có reactivity:"}),`
|
|
221
|
+
`,n.jsx(e.pre,{children:n.jsx(e.code,{className:"language-tsx",children:`import { useTranslate, useLanguage } from '@v-miniapp/ui-react'
|
|
222
|
+
|
|
223
|
+
function MyComponent() {
|
|
224
|
+
// useTranslate hook - tự động infer keys từ ICustomLocales
|
|
225
|
+
const t = useTranslate()
|
|
226
|
+
|
|
227
|
+
// useLanguage hook - tự động infer languages từ ICustomLocales
|
|
228
|
+
const { language, setLanguage } = useLanguage()
|
|
229
|
+
|
|
230
|
+
return (
|
|
231
|
+
<div>
|
|
232
|
+
<h1>{t('app.title')}</h1>
|
|
233
|
+
<p>{t('user.greeting', { name: 'John' })}</p>
|
|
234
|
+
|
|
235
|
+
<p>Current language: {language}</p>
|
|
236
|
+
<button onClick={() => setLanguage('vi')}>Tiếng Việt</button>
|
|
237
|
+
<button onClick={() => setLanguage('en')}>English</button>
|
|
238
|
+
<button onClick={() => setLanguage('jp')}>日本語</button>
|
|
239
|
+
</div>
|
|
240
|
+
)
|
|
241
|
+
}
|
|
242
|
+
`})}),`
|
|
243
|
+
`,n.jsx(e.p,{children:n.jsx(e.strong,{children:"✅ Reactivity:"})}),`
|
|
244
|
+
`,n.jsxs(e.p,{children:["React hook ",n.jsx(e.code,{children:"useTranslate"})," ",n.jsx(e.strong,{children:"có reactivity"}),". Component sử dụng hook này sẽ ",n.jsx(e.strong,{children:"tự động re-render"})," khi ngôn ngữ thay đổi."]}),`
|
|
245
|
+
`,n.jsx(e.pre,{children:n.jsx(e.code,{className:"language-tsx",children:`// ✅ Có reactive - Component tự động re-render khi language thay đổi
|
|
246
|
+
function MyComponent() {
|
|
247
|
+
const t = useTranslate()
|
|
248
|
+
|
|
249
|
+
return <div>{t('app.title')}</div>
|
|
250
|
+
// Khi setLanguage() được gọi, component này sẽ TỰ ĐỘNG re-render
|
|
251
|
+
}
|
|
252
|
+
`})}),`
|
|
253
|
+
`,n.jsx(e.h2,{id:"template-parameters",children:"Template Parameters"}),`
|
|
254
|
+
`,n.jsx(e.p,{children:"Locales module hỗ trợ template parameters trong translation strings sử dụng cú pháp `{key}`:"}),`
|
|
255
|
+
`,n.jsx(e.pre,{children:n.jsx(e.code,{className:"language-tsx",children:`// Locales definition (trong JSON files hoặc resources)
|
|
256
|
+
const resources = {
|
|
257
|
+
vi: {
|
|
258
|
+
'user.greeting': 'Xin chào {name}!',
|
|
259
|
+
'user.welcome': 'Chào mừng bạn đến với {app}',
|
|
260
|
+
'calendar.headerLabel': '{month} - {year}',
|
|
261
|
+
},
|
|
262
|
+
en: {
|
|
263
|
+
'user.greeting': 'Hello {name}!',
|
|
264
|
+
'user.welcome': 'Welcome to {app}',
|
|
265
|
+
'calendar.headerLabel': '{month} - {year}',
|
|
266
|
+
},
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Usage
|
|
270
|
+
t('user.greeting', { name: 'John' })
|
|
271
|
+
// → "Xin chào John!" (vi) hoặc "Hello John!" (en)
|
|
272
|
+
|
|
273
|
+
t('user.welcome', { app: 'MyApp' })
|
|
274
|
+
// → "Chào mừng bạn đến với MyApp" (vi) hoặc "Welcome to MyApp" (en)
|
|
275
|
+
|
|
276
|
+
t('calendar.headerLabel', { month: 'Tháng 1', year: '2024' })
|
|
277
|
+
// → "Tháng 1 - 2024"
|
|
278
|
+
`})}),`
|
|
279
|
+
`,n.jsx(e.h2,{id:"type-inference-và-type-safety",children:"Type Inference và Type Safety"}),`
|
|
280
|
+
`,n.jsxs(e.p,{children:["TypeScript tự động infer custom keys và custom languages từ module augmentation trong ",n.jsx(e.code,{children:"global.d.ts"}),". Sau khi declare ",n.jsx(e.code,{children:"ICustomLocales"}),":"]}),`
|
|
281
|
+
`,n.jsx(e.pre,{children:n.jsx(e.code,{className:"language-tsx",children:`// global.d.ts
|
|
282
|
+
declare module '@v-miniapp/ui-react' {
|
|
283
|
+
interface ICustomLocales {
|
|
284
|
+
resource: typeof vi // Infer keys từ vi.json
|
|
285
|
+
language: 'jp' | 'en' | 'vi' // Infer languages
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
`})}),`
|
|
289
|
+
`,n.jsx(e.p,{children:"Tất cả các functions và hooks sẽ tự động có type safety:"}),`
|
|
290
|
+
`,n.jsx(e.pre,{children:n.jsx(e.code,{className:"language-tsx",children:`// ✅ Type-safe: t() sẽ suggest keys từ ICustomLocales.resource
|
|
291
|
+
const title = t('app.title')
|
|
292
|
+
|
|
293
|
+
// ✅ Type-safe: setLanguage() chỉ chấp nhận 'jp' | 'en' | 'vi'
|
|
294
|
+
setLanguage('jp')
|
|
295
|
+
|
|
296
|
+
// ❌ Type error: 'ja' không có trong ICustomLocales.language
|
|
297
|
+
setLanguage('ja') // Error!
|
|
298
|
+
|
|
299
|
+
// ❌ Type error: Key không tồn tại trong ICustomLocales.resource
|
|
300
|
+
const invalid = t('app.invalidKey') // Error!
|
|
301
|
+
`})}),`
|
|
302
|
+
`,n.jsx(e.h2,{id:"best-practices",children:"Best Practices"}),`
|
|
303
|
+
`,n.jsx(e.h3,{id:"1-sử-dụng-hooks-trong-react-components",children:"1. Sử dụng hooks trong React components"}),`
|
|
304
|
+
`,n.jsxs(e.p,{children:[n.jsxs(e.strong,{children:["Luôn sử dụng ",n.jsx(e.code,{children:"useTranslate"})," trong React components"]})," để có reactivity. Chỉ sử dụng standalone functions ",n.jsx(e.code,{children:"t"})," khi:"]}),`
|
|
305
|
+
`,n.jsxs(e.ul,{children:[`
|
|
306
|
+
`,n.jsx(e.li,{children:"Bạn gọi từ bên ngoài React components (utility functions, services, etc.)"}),`
|
|
307
|
+
`,n.jsx(e.li,{children:"Bạn không cần component re-render khi ngôn ngữ thay đổi"}),`
|
|
308
|
+
`]}),`
|
|
309
|
+
`,n.jsx(e.pre,{children:n.jsx(e.code,{className:"language-tsx",children:`// ✅ Good - Trong React component, dùng hooks
|
|
310
|
+
function MyComponent() {
|
|
311
|
+
const t = useTranslate()
|
|
312
|
+
return <div>{t('app.title')}</div>
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// ✅ Good - Ngoài React component, dùng standalone function
|
|
316
|
+
export function getPageTitle() {
|
|
317
|
+
return t('app.title')
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// ❌ Bad - Trong React component, dùng standalone function (không reactive)
|
|
321
|
+
function MyComponent() {
|
|
322
|
+
const title = t('app.title')
|
|
323
|
+
return <div>{title}</div> // Sẽ không re-render khi language thay đổi
|
|
324
|
+
}
|
|
325
|
+
`})}),`
|
|
326
|
+
`,n.jsx(e.h3,{id:"2-đặt-tên-keys-có-cấu-trúc",children:"2. Đặt tên keys có cấu trúc"}),`
|
|
327
|
+
`,n.jsx(e.p,{children:"Sử dụng dot notation để nhóm keys theo module/feature:"}),`
|
|
328
|
+
`,n.jsx(e.pre,{children:n.jsx(e.code,{className:"language-tsx",children:`// ✅ Good
|
|
329
|
+
'app.title'
|
|
330
|
+
'user.greeting'
|
|
331
|
+
'button.submit'
|
|
332
|
+
'form.validation.required'
|
|
333
|
+
|
|
334
|
+
// ❌ Bad
|
|
335
|
+
'appTitle'
|
|
336
|
+
'userGreeting'
|
|
337
|
+
'submitButton'
|
|
338
|
+
`})}),`
|
|
339
|
+
`,n.jsx(e.h3,{id:"3-sử-dụng-module-augmentation-cho-type-safety",children:"3. Sử dụng module augmentation cho type safety"}),`
|
|
340
|
+
`,n.jsxs(e.p,{children:["Declare ",n.jsx(e.code,{children:"ICustomLocales"})," trong ",n.jsx(e.code,{children:"global.d.ts"})," để có type safety cho cả keys và languages:"]}),`
|
|
341
|
+
`,n.jsx(e.pre,{children:n.jsx(e.code,{className:"language-tsx",children:`// ✅ Type-safe với module augmentation
|
|
342
|
+
declare module '@v-miniapp/ui-react' {
|
|
343
|
+
interface ICustomLocales {
|
|
344
|
+
resource: typeof vi
|
|
345
|
+
language: 'jp' | 'en' | 'vi'
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// ❌ Không có type safety nếu không declare
|
|
350
|
+
const title = t('app.title') // key là string, không có autocomplete
|
|
351
|
+
`})}),`
|
|
352
|
+
`,n.jsx(e.h3,{id:"4-override-default-translations",children:"4. Override default translations"}),`
|
|
353
|
+
`,n.jsx(e.p,{children:"Bạn có thể override các translation keys mặc định:"}),`
|
|
354
|
+
`,n.jsx(e.pre,{children:n.jsx(e.code,{className:"language-tsx",children:`const localesConfig: ILocalesConfig = {
|
|
355
|
+
resources: {
|
|
356
|
+
vi: {
|
|
357
|
+
// Override default pull-to-refresh text
|
|
358
|
+
'pullToRefresh.canReleaseText': 'Thả tay để làm mới',
|
|
359
|
+
'pullToRefresh.completeText': 'Hoàn tất',
|
|
360
|
+
|
|
361
|
+
// Add custom keys
|
|
362
|
+
'app.title': 'Ứng dụng của tôi',
|
|
363
|
+
},
|
|
364
|
+
},
|
|
365
|
+
}
|
|
366
|
+
`})}),`
|
|
367
|
+
`,n.jsx(e.h3,{id:"5-import-từ-json-files",children:"5. Import từ JSON files"}),`
|
|
368
|
+
`,n.jsxs(e.p,{children:["Import translations từ JSON files và sử dụng ",n.jsx(e.code,{children:"typeof"})," để infer types:"]}),`
|
|
369
|
+
`,n.jsx(e.pre,{children:n.jsx(e.code,{className:"language-tsx",children:`// ✅ Import từ JSON
|
|
370
|
+
import vi from './locales/vi.json'
|
|
371
|
+
|
|
372
|
+
declare module '@v-miniapp/ui-react' {
|
|
373
|
+
interface ICustomLocales {
|
|
374
|
+
resource: typeof vi // Auto-infer types
|
|
375
|
+
language: 'jp' | 'en' | 'vi'
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
`})}),`
|
|
379
|
+
`,n.jsx(e.h2,{id:"reference",children:"Reference"}),`
|
|
380
|
+
`,n.jsx(e.h3,{id:"types",children:"Types"}),`
|
|
381
|
+
`,n.jsx(e.h4,{id:"ilocalesconfig",children:n.jsx(e.code,{children:"ILocalesConfig"})}),`
|
|
382
|
+
`,n.jsx(e.p,{children:"Cấu hình locales cho App component hoặc LocalesProvider."}),`
|
|
383
|
+
`,n.jsx(e.pre,{children:n.jsx(e.code,{className:"language-typescript",children:`type ILocalesConfig = {
|
|
384
|
+
defaultLanguage?: IAppLanguage
|
|
385
|
+
resources?: IOptinalResources
|
|
386
|
+
}
|
|
387
|
+
`})}),`
|
|
388
|
+
`,n.jsx(e.p,{children:n.jsx(e.strong,{children:"Properties:"})}),`
|
|
389
|
+
`,n.jsx(t,{children:"\n| Name | Type | Required | Description |\n| :--- | :--- | :--- | :--- |\n| `defaultLanguage` | `IAppLanguage` | No | Ngôn ngữ mặc định. Có thể là `'vi'`, `'en'`, hoặc `'system'` (hoặc custom languages từ `ICustomLocales`) |\n| `resources` | `IOptinalResources` | No | Object chứa các locales translations. Keys là language codes, values là resource maps |\n"}),`
|
|
390
|
+
`,n.jsx(e.h4,{id:"icustomlocales",children:n.jsx(e.code,{children:"ICustomLocales"})}),`
|
|
391
|
+
`,n.jsx(e.p,{children:"Interface để declare custom locales thông qua module augmentation."}),`
|
|
392
|
+
`,n.jsx(e.pre,{children:n.jsx(e.code,{className:"language-typescript",children:`interface ICustomLocales {
|
|
393
|
+
resource: Record<string, string> // Type của locales keys (thường là typeof vi từ JSON)
|
|
394
|
+
language: string // Union type của language codes (e.g., 'vi' | 'en' | 'jp')
|
|
395
|
+
}
|
|
396
|
+
`})}),`
|
|
397
|
+
`,n.jsx(e.p,{children:n.jsx(e.strong,{children:"Ví dụ:"})}),`
|
|
398
|
+
`,n.jsx(e.pre,{children:n.jsx(e.code,{className:"language-tsx",children:`// global.d.ts
|
|
399
|
+
declare module '@v-miniapp/ui-react' {
|
|
400
|
+
interface ICustomLocales {
|
|
401
|
+
resource: typeof vi // Infer từ vi.json
|
|
402
|
+
language: 'jp' | 'en' | 'vi' // Custom languages
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
`})}),`
|
|
406
|
+
`,n.jsx(e.h4,{id:"iapplanguage",children:n.jsx(e.code,{children:"IAppLanguage"})}),`
|
|
407
|
+
`,n.jsx(e.p,{children:"Union type của language codes bao gồm default và custom languages."}),`
|
|
408
|
+
`,n.jsx(e.pre,{children:n.jsx(e.code,{className:"language-typescript",children:`type IAppLanguage = ILang | 'system'
|
|
409
|
+
`})}),`
|
|
410
|
+
`,n.jsxs(e.p,{children:[n.jsx(e.code,{children:"ILang"})," được infer từ ",n.jsx(e.code,{children:"IDefaultLocales['language']"})," (default: ",n.jsx(e.code,{children:"'vi' | 'en'"}),") và ",n.jsx(e.code,{children:"ICustomLocales['language']"})," (nếu có)."]}),`
|
|
411
|
+
`,n.jsx(e.h3,{id:"components",children:"Components"}),`
|
|
412
|
+
`,n.jsx(e.h4,{id:"localesprovider",children:n.jsx(e.code,{children:"LocalesProvider"})}),`
|
|
413
|
+
`,n.jsx(e.p,{children:"Component để khởi tạo locales module."}),`
|
|
414
|
+
`,n.jsx(e.pre,{children:n.jsx(e.code,{className:"language-typescript",children:`type LocalesProviderProps = {
|
|
415
|
+
config?: ILocalesConfig
|
|
416
|
+
}
|
|
417
|
+
`})}),`
|
|
418
|
+
`,n.jsx(e.p,{children:n.jsx(e.strong,{children:"Usage:"})}),`
|
|
419
|
+
`,n.jsx(e.pre,{children:n.jsx(e.code,{className:"language-tsx",children:`<LocalesProvider config={localesConfig} />
|
|
420
|
+
`})}),`
|
|
421
|
+
`,n.jsxs(e.p,{children:[n.jsx(e.strong,{children:"Lưu ý:"})," Component này chỉ init một lần khi mount, nên cần đặt ở top-level."]}),`
|
|
422
|
+
`,n.jsx(e.h3,{id:"functions--hooks",children:"Functions & Hooks"}),`
|
|
423
|
+
`,n.jsx(e.h4,{id:"tkey-params-lang",children:n.jsx(e.code,{children:"t(key, params?, lang?)"})}),`
|
|
424
|
+
`,n.jsx(e.p,{children:"Standalone function để translate key."}),`
|
|
425
|
+
`,n.jsxs(e.ul,{children:[`
|
|
426
|
+
`,n.jsxs(e.li,{children:[n.jsx(e.strong,{children:"Parameters:"}),`
|
|
427
|
+
`,n.jsxs(e.ul,{children:[`
|
|
428
|
+
`,n.jsxs(e.li,{children:[n.jsx(e.code,{children:"key"}),": ",n.jsx(e.code,{children:"IResourceKey"})," - Locales key (inferred từ ",n.jsx(e.code,{children:"ICustomLocales.resource"})," nếu có)"]}),`
|
|
429
|
+
`,n.jsxs(e.li,{children:[n.jsx(e.code,{children:"params?"}),": ",n.jsx(e.code,{children:"ITemplateParams"})," - Template parameters object"]}),`
|
|
430
|
+
`,n.jsxs(e.li,{children:[n.jsx(e.code,{children:"lang?"}),": ",n.jsx(e.code,{children:"ILang"})," - Ngôn ngữ chỉ định"]}),`
|
|
431
|
+
`]}),`
|
|
432
|
+
`]}),`
|
|
433
|
+
`,n.jsxs(e.li,{children:[n.jsx(e.strong,{children:"Returns:"})," ",n.jsx(e.code,{children:"string"})," - Translated text"]}),`
|
|
434
|
+
`,n.jsxs(e.li,{children:[n.jsx(e.strong,{children:"Reactivity:"})," ❌ Không có reactivity"]}),`
|
|
435
|
+
`]}),`
|
|
436
|
+
`,n.jsx(e.h4,{id:"usetranslate",children:n.jsx(e.code,{children:"useTranslate()"})}),`
|
|
437
|
+
`,n.jsx(e.p,{children:"React hook để translate trong components."}),`
|
|
438
|
+
`,n.jsxs(e.ul,{children:[`
|
|
439
|
+
`,n.jsxs(e.li,{children:[n.jsx(e.strong,{children:"Returns:"})," Function tương tự ",n.jsx(e.code,{children:"t(key, params?)"})]}),`
|
|
440
|
+
`,n.jsxs(e.li,{children:[n.jsx(e.strong,{children:"Reactivity:"})," ✅ Có reactivity"]}),`
|
|
441
|
+
`]}),`
|
|
442
|
+
`,n.jsx(e.h4,{id:"getlanguage",children:n.jsx(e.code,{children:"getLanguage()"})}),`
|
|
443
|
+
`,n.jsx(e.p,{children:"Standalone function để lấy current language."}),`
|
|
444
|
+
`,n.jsxs(e.ul,{children:[`
|
|
445
|
+
`,n.jsxs(e.li,{children:[n.jsx(e.strong,{children:"Returns:"})," ",n.jsx(e.code,{children:"ILang"})," - Current language"]}),`
|
|
446
|
+
`]}),`
|
|
447
|
+
`,n.jsx(e.h4,{id:"setlanguagelanguage",children:n.jsx(e.code,{children:"setLanguage(language)"})}),`
|
|
448
|
+
`,n.jsx(e.p,{children:"Standalone function để thay đổi language."}),`
|
|
449
|
+
`,n.jsxs(e.ul,{children:[`
|
|
450
|
+
`,n.jsxs(e.li,{children:[n.jsx(e.strong,{children:"Parameters:"}),`
|
|
451
|
+
`,n.jsxs(e.ul,{children:[`
|
|
452
|
+
`,n.jsxs(e.li,{children:[n.jsx(e.code,{children:"language"}),": ",n.jsx(e.code,{children:"ILang"})," - Language code (inferred từ ",n.jsx(e.code,{children:"ICustomLocales.language"})," nếu có)"]}),`
|
|
453
|
+
`]}),`
|
|
454
|
+
`]}),`
|
|
455
|
+
`]}),`
|
|
456
|
+
`,n.jsx(e.h4,{id:"uselanguage",children:n.jsx(e.code,{children:"useLanguage()"})}),`
|
|
457
|
+
`,n.jsx(e.p,{children:"React hook để quản lý language."}),`
|
|
458
|
+
`,n.jsxs(e.ul,{children:[`
|
|
459
|
+
`,n.jsxs(e.li,{children:[n.jsx(e.strong,{children:"Returns:"})," ",n.jsx(e.code,{children:"{ language: ILang, setLanguage: (lang: ILang) => void }"}),`
|
|
460
|
+
`,n.jsxs(e.ul,{children:[`
|
|
461
|
+
`,n.jsxs(e.li,{children:[n.jsx(e.code,{children:"language"}),": Current language (inferred từ ",n.jsx(e.code,{children:"ICustomLocales.language"})," nếu có)"]}),`
|
|
462
|
+
`,n.jsxs(e.li,{children:[n.jsx(e.code,{children:"setLanguage"}),": Function để thay đổi language (type-safe với ",n.jsx(e.code,{children:"ICustomLocales.language"}),")"]}),`
|
|
463
|
+
`]}),`
|
|
464
|
+
`]}),`
|
|
465
|
+
`]})]})}function g(s={}){const{wrapper:e}={...i(),...s.components};return e?n.jsx(e,{...s,children:n.jsx(c,{...s})}):c(s)}export{g as default};
|