@geenius/i18n 0.1.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.
- package/.changeset/config.json +11 -0
- package/.github/CODEOWNERS +1 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +16 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +11 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +10 -0
- package/.github/dependabot.yml +11 -0
- package/.github/workflows/ci.yml +23 -0
- package/.github/workflows/release.yml +29 -0
- package/.nvmrc +1 -0
- package/.project/ACCOUNT.yaml +4 -0
- package/.project/IDEAS.yaml +7 -0
- package/.project/PROJECT.yaml +11 -0
- package/.project/ROADMAP.yaml +15 -0
- package/CHANGELOG.md +8 -0
- package/CODE_OF_CONDUCT.md +16 -0
- package/CONTRIBUTING.md +26 -0
- package/LICENSE +2 -0
- package/README.md +1 -0
- package/SECURITY.md +15 -0
- package/SUPPORT.md +8 -0
- package/package.json +75 -0
- package/packages/convex/package.json +42 -0
- package/packages/convex/src/index.ts +3 -0
- package/packages/convex/src/mutations.ts +65 -0
- package/packages/convex/src/queries.ts +54 -0
- package/packages/convex/src/schema.ts +26 -0
- package/packages/convex/tsconfig.json +18 -0
- package/packages/convex/tsup.config.ts +17 -0
- package/packages/react/README.md +1 -0
- package/packages/react/package.json +51 -0
- package/packages/react/src/components/index.tsx +87 -0
- package/packages/react/src/hooks/index.ts +4 -0
- package/packages/react/src/hooks/useI18n.tsx +50 -0
- package/packages/react/src/hooks/useI18nAdmin.ts +12 -0
- package/packages/react/src/hooks/useLocaleDetect.ts +10 -0
- package/packages/react/src/hooks/useTranslations.ts +11 -0
- package/packages/react/src/index.tsx +8 -0
- package/packages/react/src/pages/I18nAdminPage.tsx +42 -0
- package/packages/react/src/pages/LocalePreviewPage.tsx +54 -0
- package/packages/react/src/pages/index.ts +2 -0
- package/packages/react/tsconfig.json +19 -0
- package/packages/react/tsup.config.ts +12 -0
- package/packages/react-css/README.md +1 -0
- package/packages/react-css/package.json +36 -0
- package/packages/react-css/src/components/index.tsx +66 -0
- package/packages/react-css/src/hooks/index.ts +4 -0
- package/packages/react-css/src/index.tsx +4 -0
- package/packages/react-css/src/pages/LocaleSettingsPage.tsx +74 -0
- package/packages/react-css/src/pages/TranslationsPage.tsx +98 -0
- package/packages/react-css/src/styles.css +210 -0
- package/packages/react-css/tsconfig.json +19 -0
- package/packages/react-css/tsup.config.ts +10 -0
- package/packages/shared/README.md +1 -0
- package/packages/shared/package.json +44 -0
- package/packages/shared/src/__tests__/i18n.test.ts +78 -0
- package/packages/shared/src/config.ts +344 -0
- package/packages/shared/src/index.ts +106 -0
- package/packages/shared/src/types.ts +51 -0
- package/packages/shared/tsconfig.json +18 -0
- package/packages/shared/tsup.config.ts +11 -0
- package/packages/shared/vitest.config.ts +4 -0
- package/packages/solidjs/README.md +1 -0
- package/packages/solidjs/package.json +47 -0
- package/packages/solidjs/src/components/LocaleCard.tsx +44 -0
- package/packages/solidjs/src/components/LocaleStatsCard.tsx +35 -0
- package/packages/solidjs/src/components/LocaleSwitcher.tsx +65 -0
- package/packages/solidjs/src/components/MissingKeyAlert.tsx +21 -0
- package/packages/solidjs/src/components/RTLWrapper.tsx +13 -0
- package/packages/solidjs/src/components/TranslationKeyRow.tsx +41 -0
- package/packages/solidjs/src/components/index.ts +6 -0
- package/packages/solidjs/src/index.tsx +8 -0
- package/packages/solidjs/src/pages/I18nAdminPage.tsx +188 -0
- package/packages/solidjs/src/pages/LocalePreviewPage.tsx +99 -0
- package/packages/solidjs/src/pages/index.ts +2 -0
- package/packages/solidjs/src/primitives/I18nProvider.tsx +56 -0
- package/packages/solidjs/src/primitives/createI18nAdmin.ts +7 -0
- package/packages/solidjs/src/primitives/createLocaleDetect.ts +8 -0
- package/packages/solidjs/src/primitives/createTranslations.ts +22 -0
- package/packages/solidjs/src/primitives/index.ts +4 -0
- package/packages/solidjs/tsconfig.json +20 -0
- package/packages/solidjs/tsup.config.ts +12 -0
- package/packages/solidjs-css/README.md +1 -0
- package/packages/solidjs-css/package.json +33 -0
- package/packages/solidjs-css/src/components/LocaleCard.tsx +45 -0
- package/packages/solidjs-css/src/components/LocaleStatsCard.tsx +43 -0
- package/packages/solidjs-css/src/components/LocaleSwitcher.tsx +51 -0
- package/packages/solidjs-css/src/components/MissingKeyAlert.tsx +24 -0
- package/packages/solidjs-css/src/components/RTLWrapper.tsx +16 -0
- package/packages/solidjs-css/src/components/TranslationKeyRow.tsx +47 -0
- package/packages/solidjs-css/src/components/index.ts +6 -0
- package/packages/solidjs-css/src/i18n.css +1322 -0
- package/packages/solidjs-css/src/index.tsx +3 -0
- package/packages/solidjs-css/src/pages/I18nAdminPage.tsx +134 -0
- package/packages/solidjs-css/src/pages/LocalePreviewPage.tsx +116 -0
- package/packages/solidjs-css/src/pages/index.ts +2 -0
- package/packages/solidjs-css/src/primitives/index.ts +1 -0
- package/packages/solidjs-css/tsconfig.json +20 -0
- package/packages/solidjs-css/tsup.config.bundled_dcjc4sct21j.mjs +18 -0
- package/packages/solidjs-css/tsup.config.ts +14 -0
- package/pnpm-workspace.yaml +2 -0
- package/tsconfig.json +23 -0
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/* ─── I18n Design Tokens (OKLCH) ──────────────────────── */
|
|
2
|
+
:root {
|
|
3
|
+
--i18n-bg: oklch(0.12 0.01 250);
|
|
4
|
+
--i18n-surface: oklch(0.16 0.01 250);
|
|
5
|
+
--i18n-border: oklch(0.22 0.01 250);
|
|
6
|
+
--i18n-text: oklch(0.95 0.01 250);
|
|
7
|
+
--i18n-text-muted: oklch(0.58 0.02 250);
|
|
8
|
+
--i18n-accent: oklch(0.65 0.20 265);
|
|
9
|
+
--i18n-rtl-indicator: oklch(0.72 0.18 60);
|
|
10
|
+
--i18n-missing: oklch(0.60 0.25 25);
|
|
11
|
+
--i18n-complete: oklch(0.72 0.18 155);
|
|
12
|
+
--i18n-radius: 0.75rem;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/* ─── Locale Switcher ────────────────────────────────── */
|
|
16
|
+
.i18n__locale-switcher { position: relative; }
|
|
17
|
+
.i18n__locale-switcher-btn {
|
|
18
|
+
display: flex; align-items: center; gap: 0.5rem;
|
|
19
|
+
padding: 0.5rem 0.75rem; border-radius: var(--i18n-radius);
|
|
20
|
+
border: 1px solid var(--i18n-border); background: oklch(1 0 0 / 0.05);
|
|
21
|
+
font-size: 0.875rem; color: var(--i18n-text); cursor: pointer; transition: all 0.15s;
|
|
22
|
+
}
|
|
23
|
+
.i18n__locale-switcher-btn:hover { background: oklch(1 0 0 / 0.1); }
|
|
24
|
+
.i18n__locale-switcher-flag { font-size: 1.125rem; }
|
|
25
|
+
.i18n__locale-switcher-arrow { font-size: 0.625rem; color: oklch(1 0 0 / 0.3); margin-left: 0.25rem; }
|
|
26
|
+
.i18n__locale-dropdown {
|
|
27
|
+
position: absolute; top: 100%; margin-top: 0.25rem; right: 0; z-index: 50;
|
|
28
|
+
min-width: 14rem; border-radius: var(--i18n-radius); border: 1px solid var(--i18n-border);
|
|
29
|
+
background: oklch(0.08 0.01 250); padding: 0.25rem; box-shadow: 0 8px 32px oklch(0 0 0 / 0.5);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/* ─── Locale Option ──────────────────────────────────── */
|
|
33
|
+
.i18n__locale-option {
|
|
34
|
+
display: flex; align-items: center; gap: 0.75rem; width: 100%;
|
|
35
|
+
padding: 0.625rem 1rem; border: none; border-radius: calc(var(--i18n-radius) * 0.8);
|
|
36
|
+
font-size: 0.875rem; color: oklch(1 0 0 / 0.7); cursor: pointer; background: transparent; transition: all 0.1s;
|
|
37
|
+
}
|
|
38
|
+
.i18n__locale-option:hover { background: oklch(1 0 0 / 0.05); }
|
|
39
|
+
.i18n__locale-option--active { background: oklch(0.65 0.20 265 / 0.15); color: oklch(0.65 0.20 265); }
|
|
40
|
+
.i18n__locale-option-flag { font-size: 1.125rem; }
|
|
41
|
+
.i18n__locale-option-name { flex: 1; text-align: left; }
|
|
42
|
+
.i18n__locale-option-eng { font-size: 0.6875rem; color: oklch(1 0 0 / 0.3); }
|
|
43
|
+
.i18n__locale-option-rtl { font-size: 0.5625rem; padding: 0.125rem 0.375rem; border-radius: 0.25rem; background: oklch(0.72 0.18 60 / 0.15); color: var(--i18n-rtl-indicator); }
|
|
44
|
+
|
|
45
|
+
/* ─── RTL Wrapper ────────────────────────────────────── */
|
|
46
|
+
.i18n__rtl-wrapper { transition: all 0.2s; }
|
|
47
|
+
.i18n__rtl-wrapper[dir="rtl"] { text-align: right; }
|
|
48
|
+
|
|
49
|
+
/* ─── Translation Table ──────────────────────────────── */
|
|
50
|
+
.i18n__translation-table { width: 100%; font-size: 0.875rem; border-collapse: collapse; }
|
|
51
|
+
.i18n__translation-table th { padding: 0.75rem 1rem; text-align: left; font-size: 0.625rem; font-weight: 500; text-transform: uppercase; letter-spacing: 0.05em; color: oklch(1 0 0 / 0.4); border-bottom: 1px solid oklch(1 0 0 / 0.08); }
|
|
52
|
+
.i18n__translation-table td { padding: 0.75rem 1rem; border-bottom: 1px solid oklch(1 0 0 / 0.05); }
|
|
53
|
+
.i18n__translation-row:hover { background: oklch(1 0 0 / 0.02); }
|
|
54
|
+
.i18n__translation-key { font-family: monospace; font-size: 0.6875rem; color: oklch(0.65 0.20 265); }
|
|
55
|
+
.i18n__translation-value { color: oklch(1 0 0 / 0.7); max-width: 20rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
56
|
+
.i18n__translation-ns { display: inline-block; padding: 0.125rem 0.375rem; border-radius: 0.25rem; background: oklch(1 0 0 / 0.05); font-size: 0.625rem; color: oklch(1 0 0 / 0.4); }
|
|
57
|
+
|
|
58
|
+
/* ─── Translation Editor ─────────────────────────────── */
|
|
59
|
+
.i18n__editor { display: flex; flex-direction: column; gap: 0.75rem; padding: 1.25rem; border: 1px solid var(--i18n-border); border-radius: var(--i18n-radius); background: oklch(1 0 0 / 0.02); }
|
|
60
|
+
.i18n__editor-selectors { display: flex; gap: 0.5rem; }
|
|
61
|
+
.i18n__editor-select { padding: 0.375rem 0.5rem; border: 1px solid var(--i18n-border); border-radius: calc(var(--i18n-radius) * 0.8); background: oklch(1 0 0 / 0.05); font-size: 0.6875rem; color: var(--i18n-text); outline: none; }
|
|
62
|
+
.i18n__editor-input { width: 100%; padding: 0.625rem 1rem; border: 1px solid var(--i18n-border); border-radius: var(--i18n-radius); background: oklch(1 0 0 / 0.03); font-size: 0.875rem; color: var(--i18n-text); outline: none; }
|
|
63
|
+
.i18n__editor-input::placeholder { color: oklch(1 0 0 / 0.3); }
|
|
64
|
+
.i18n__editor-input:focus { border-color: oklch(0.65 0.20 265 / 0.4); }
|
|
65
|
+
.i18n__editor-textarea { resize: none; min-height: 4.5rem; }
|
|
66
|
+
|
|
67
|
+
/* ─── Missing Badge ──────────────────────────────────── */
|
|
68
|
+
.i18n__missing-badge {
|
|
69
|
+
display: flex; align-items: center; gap: 0.75rem;
|
|
70
|
+
padding: 0.625rem 1rem; border-radius: calc(var(--i18n-radius) * 0.8);
|
|
71
|
+
border: 1px solid oklch(0.60 0.25 25 / 0.2); background: oklch(0.60 0.25 25 / 0.05);
|
|
72
|
+
}
|
|
73
|
+
.i18n__missing-badge-icon { font-size: 0.875rem; }
|
|
74
|
+
.i18n__missing-badge-text { font-size: 0.6875rem; color: oklch(0.60 0.25 25); }
|
|
75
|
+
|
|
76
|
+
/* ─── Stats Card ─────────────────────────────────────── */
|
|
77
|
+
.i18n__stats-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 0.75rem; }
|
|
78
|
+
.i18n__stats-card { padding: 1rem; border: 1px solid var(--i18n-border); border-radius: var(--i18n-radius); background: oklch(1 0 0 / 0.02); }
|
|
79
|
+
.i18n__stats-card-header { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.5rem; }
|
|
80
|
+
.i18n__stats-card-flag { font-size: 1.125rem; }
|
|
81
|
+
.i18n__stats-card-name { font-size: 0.6875rem; font-weight: 500; color: oklch(1 0 0 / 0.8); }
|
|
82
|
+
.i18n__stats-bar { height: 0.375rem; border-radius: 9999px; background: oklch(1 0 0 / 0.05); overflow: hidden; margin-top: 0.25rem; }
|
|
83
|
+
.i18n__stats-bar-fill { height: 100%; border-radius: 9999px; transition: width 0.3s; }
|
|
84
|
+
.i18n__stats-bar-fill--complete { background: var(--i18n-complete); }
|
|
85
|
+
.i18n__stats-bar-fill--partial { background: var(--i18n-rtl-indicator); }
|
|
86
|
+
.i18n__stats-bar-fill--low { background: var(--i18n-missing); }
|
|
87
|
+
.i18n__stats-card-meta { display: flex; justify-content: space-between; margin-top: 0.375rem; font-size: 0.625rem; color: oklch(1 0 0 / 0.4); }
|
|
88
|
+
.i18n__stats-card-missing { font-size: 0.625rem; color: oklch(0.60 0.25 25); margin-top: 0.375rem; }
|
|
89
|
+
|
|
90
|
+
/* ─── Info Cards (Preview) ───────────────────────────── */
|
|
91
|
+
.i18n__info-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 1rem; }
|
|
92
|
+
.i18n__info-card { padding: 1rem; border: 1px solid var(--i18n-border); border-radius: var(--i18n-radius); background: oklch(1 0 0 / 0.02); }
|
|
93
|
+
.i18n__info-card-label { font-size: 0.625rem; color: oklch(1 0 0 / 0.4); text-transform: uppercase; margin-bottom: 0.25rem; }
|
|
94
|
+
.i18n__info-card-value { font-size: 1.125rem; font-weight: 700; color: oklch(1 0 0 / 0.9); }
|
|
95
|
+
.i18n__info-card-value--rtl { color: var(--i18n-rtl-indicator); }
|
|
96
|
+
|
|
97
|
+
/* ─── RTL Alert ──────────────────────────────────────── */
|
|
98
|
+
.i18n__rtl-alert { padding: 0.75rem 1rem; border-radius: calc(var(--i18n-radius) * 0.8); border: 1px solid oklch(0.72 0.18 60 / 0.2); background: oklch(0.72 0.18 60 / 0.05); font-size: 0.875rem; color: oklch(0.72 0.18 60); }
|
|
99
|
+
|
|
100
|
+
/* ─── Format Row ─────────────────────────────────────── */
|
|
101
|
+
.i18n__format-row { display: flex; justify-content: space-between; padding: 0.5rem 0; border-bottom: 1px solid oklch(1 0 0 / 0.04); }
|
|
102
|
+
.i18n__format-label { font-size: 0.875rem; color: oklch(1 0 0 / 0.4); }
|
|
103
|
+
.i18n__format-value { font-family: monospace; font-size: 0.6875rem; color: oklch(1 0 0 / 0.8); }
|
|
104
|
+
|
|
105
|
+
/* ─── Section ────────────────────────────────────────── */
|
|
106
|
+
.i18n__section { padding: 1.25rem; border: 1px solid var(--i18n-border); border-radius: var(--i18n-radius); background: oklch(1 0 0 / 0.02); }
|
|
107
|
+
.i18n__section-title { font-size: 0.875rem; font-weight: 600; color: oklch(1 0 0 / 0.8); margin-bottom: 1rem; }
|
|
108
|
+
|
|
109
|
+
/* ─── Buttons ────────────────────────────────────────── */
|
|
110
|
+
.i18n__btn { padding: 0.5rem 1rem; border-radius: calc(var(--i18n-radius) * 0.8); font-size: 0.6875rem; font-weight: 500; cursor: pointer; border: none; transition: all 0.15s; }
|
|
111
|
+
.i18n__btn--primary { background: var(--i18n-accent); color: white; }
|
|
112
|
+
.i18n__btn--primary:hover { background: oklch(0.70 0.22 265); }
|
|
113
|
+
.i18n__btn--primary:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
114
|
+
.i18n__btn--outline { border: 1px solid var(--i18n-border); background: transparent; color: oklch(1 0 0 / 0.6); }
|
|
115
|
+
.i18n__btn--danger { color: oklch(0.60 0.25 25); }
|
|
116
|
+
|
|
117
|
+
/* ─── Search ─────────────────────────────────────────── */
|
|
118
|
+
.i18n__search { width: 100%; max-width: 12rem; padding: 0.5rem 0.75rem; border: 1px solid var(--i18n-border); border-radius: var(--i18n-radius); background: oklch(1 0 0 / 0.03); font-size: 0.875rem; color: var(--i18n-text); outline: none; }
|
|
119
|
+
.i18n__search::placeholder { color: oklch(1 0 0 / 0.3); }
|
|
120
|
+
.i18n__search:focus { border-color: oklch(0.65 0.20 265 / 0.4); }
|
|
121
|
+
|
|
122
|
+
/* ─── Skeleton ───────────────────────────────────────── */
|
|
123
|
+
.i18n__skeleton { background: oklch(1 0 0 / 0.05); border-radius: var(--i18n-radius); animation: i18n-pulse 1.5s ease-in-out infinite; }
|
|
124
|
+
@keyframes i18n-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.4; } }
|
|
125
|
+
|
|
126
|
+
/* ─── Empty ──────────────────────────────────────────── */
|
|
127
|
+
.i18n__empty { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 3rem 1rem; text-align: center; }
|
|
128
|
+
.i18n__empty-icon { font-size: 2.5rem; opacity: 0.2; margin-bottom: 0.75rem; }
|
|
129
|
+
.i18n__empty-text { font-size: 0.875rem; color: oklch(1 0 0 / 0.4); }
|
|
130
|
+
|
|
131
|
+
/* ─── Delete Button ──────────────────────────────────── */
|
|
132
|
+
.i18n__delete-btn { padding: 0.25rem 0.5rem; border: none; border-radius: calc(var(--i18n-radius) * 0.6); background: transparent; color: oklch(1 0 0 / 0.2); font-size: 0.6875rem; cursor: pointer; opacity: 0; transition: all 0.15s; }
|
|
133
|
+
.i18n__translation-row:hover .i18n__delete-btn { opacity: 1; }
|
|
134
|
+
.i18n__delete-btn:hover { color: oklch(0.60 0.25 25); background: oklch(0.60 0.25 25 / 0.1); }
|
|
135
|
+
|
|
136
|
+
/* ─── Namespace Tree View ────────────────────────────── */
|
|
137
|
+
.i18n__namespace-tree { border: 1px solid var(--i18n-border); border-radius: var(--i18n-radius); background: oklch(1 0 0 / 0.02); padding: 1rem; max-height: 20rem; overflow-y: auto; }
|
|
138
|
+
.i18n__namespace-item { display: flex; align-items: center; padding: 0.5rem; cursor: pointer; border-radius: calc(var(--i18n-radius) * 0.6); transition: all 0.1s; margin-bottom: 0.25rem; }
|
|
139
|
+
.i18n__namespace-item:hover { background: oklch(1 0 0 / 0.04); }
|
|
140
|
+
.i18n__namespace-item--active { background: oklch(0.65 0.20 265 / 0.1); color: oklch(0.65 0.20 265); }
|
|
141
|
+
.i18n__namespace-icon { width: 1.25rem; height: 1.25rem; display: flex; align-items: center; justify-content: center; margin-right: 0.5rem; font-size: 0.875rem; }
|
|
142
|
+
.i18n__namespace-name { flex: 1; font-size: 0.6875rem; font-weight: 500; }
|
|
143
|
+
.i18n__namespace-count { font-size: 0.625rem; color: oklch(1 0 0 / 0.3); }
|
|
144
|
+
|
|
145
|
+
/* ─── Missing Key Alerts ─────────────────────────────── */
|
|
146
|
+
.i18n__missing-keys-panel { border: 1px solid oklch(0.60 0.25 25 / 0.2); border-radius: var(--i18n-radius); background: oklch(0.60 0.25 25 / 0.05); padding: 1rem; margin-bottom: 1.5rem; }
|
|
147
|
+
.i18n__missing-keys-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 1rem; }
|
|
148
|
+
.i18n__missing-keys-title { font-size: 0.875rem; font-weight: 600; color: oklch(0.60 0.25 25); }
|
|
149
|
+
.i18n__missing-keys-count { font-size: 1rem; font-weight: 700; color: oklch(0.60 0.25 25); }
|
|
150
|
+
.i18n__missing-keys-list { display: flex; flex-direction: column; gap: 0.5rem; }
|
|
151
|
+
.i18n__missing-key-item { padding: 0.625rem; border: 1px solid oklch(0.60 0.25 25 / 0.1); border-radius: calc(var(--i18n-radius) * 0.6); background: oklch(1 0 0 / 0.02); }
|
|
152
|
+
.i18n__missing-key-path { font-family: monospace; font-size: 0.625rem; color: oklch(0.60 0.25 25); margin-bottom: 0.25rem; }
|
|
153
|
+
.i18n__missing-key-context { font-size: 0.6875rem; color: oklch(1 0 0 / 0.5); }
|
|
154
|
+
|
|
155
|
+
/* ─── Import/Export Panel ────────────────────────────── */
|
|
156
|
+
.i18n__import-export-panel { border: 1px solid var(--i18n-border); border-radius: var(--i18n-radius); background: oklch(1 0 0 / 0.02); padding: 1.25rem; }
|
|
157
|
+
.i18n__import-export-title { font-size: 0.875rem; font-weight: 600; color: oklch(1 0 0 / 0.8); margin-bottom: 1rem; }
|
|
158
|
+
.i18n__import-export-row { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; margin-bottom: 1rem; }
|
|
159
|
+
.i18n__import-export-col { display: flex; flex-direction: column; }
|
|
160
|
+
.i18n__import-export-label { font-size: 0.6875rem; font-weight: 500; color: oklch(1 0 0 / 0.6); margin-bottom: 0.5rem; text-transform: uppercase; }
|
|
161
|
+
.i18n__import-btn { padding: 0.75rem 1rem; border: 1px solid var(--i18n-border); border-radius: calc(var(--i18n-radius) * 0.8); background: oklch(1 0 0 / 0.03); font-size: 0.6875rem; font-weight: 500; cursor: pointer; transition: all 0.1s; }
|
|
162
|
+
.i18n__import-btn:hover { background: oklch(1 0 0 / 0.06); }
|
|
163
|
+
.i18n__export-btn { padding: 0.75rem 1rem; border: none; border-radius: calc(var(--i18n-radius) * 0.8); background: var(--i18n-accent); color: white; font-size: 0.6875rem; font-weight: 500; cursor: pointer; transition: all 0.1s; }
|
|
164
|
+
.i18n__export-btn:hover { background: oklch(0.70 0.22 265); }
|
|
165
|
+
|
|
166
|
+
/* ─── RTL Support Toggle ─────────────────────────────── */
|
|
167
|
+
.i18n__rtl-toggle-panel { border: 1px solid var(--i18n-border); border-radius: var(--i18n-radius); background: oklch(1 0 0 / 0.02); padding: 1rem; margin-bottom: 1.5rem; }
|
|
168
|
+
.i18n__rtl-toggle-header { display: flex; align-items: center; justify-content: space-between; }
|
|
169
|
+
.i18n__rtl-toggle-label { font-size: 0.875rem; font-weight: 600; color: oklch(1 0 0 / 0.8); }
|
|
170
|
+
.i18n__rtl-toggle-switch { width: 2.5rem; height: 1.5rem; border-radius: 9999px; border: none; background: oklch(1 0 0 / 0.1); cursor: pointer; transition: all 0.2s; position: relative; }
|
|
171
|
+
.i18n__rtl-toggle-switch--active { background: var(--i18n-complete); }
|
|
172
|
+
.i18n__rtl-toggle-knob { position: absolute; width: 1.25rem; height: 1.25rem; border-radius: 50%; background: white; top: 0.125rem; left: 0.125rem; transition: transform 0.2s; box-shadow: 0 1px 3px oklch(0 0 0 / 0.2); }
|
|
173
|
+
.i18n__rtl-toggle-switch--active .i18n__rtl-toggle-knob { transform: translateX(1rem); }
|
|
174
|
+
|
|
175
|
+
/* ─── Coverage Indicator ─────────────────────────────── */
|
|
176
|
+
.i18n__coverage-card { padding: 1rem; border: 1px solid var(--i18n-border); border-radius: var(--i18n-radius); background: oklch(1 0 0 / 0.02); text-align: center; }
|
|
177
|
+
.i18n__coverage-percentage { font-size: 2.5rem; font-weight: 900; font-variant-numeric: tabular-nums; color: oklch(1 0 0 / 0.9); }
|
|
178
|
+
.i18n__coverage-label { font-size: 0.6875rem; color: oklch(1 0 0 / 0.4); margin-top: 0.5rem; }
|
|
179
|
+
.i18n__coverage-bar { height: 0.375rem; border-radius: 9999px; background: oklch(1 0 0 / 0.05); overflow: hidden; margin-top: 0.75rem; }
|
|
180
|
+
.i18n__coverage-bar-fill { height: 100%; border-radius: 9999px; background: var(--i18n-complete); transition: width 0.3s; }
|
|
181
|
+
|
|
182
|
+
/* ─── String Table Advanced ──────────────────────────── */
|
|
183
|
+
.i18n__string-table-advanced { width: 100%; border-collapse: collapse; font-size: 0.6875rem; }
|
|
184
|
+
.i18n__string-table-advanced th { padding: 0.75rem 1rem; text-align: left; font-weight: 600; text-transform: uppercase; color: oklch(1 0 0 / 0.4); background: oklch(1 0 0 / 0.01); border-bottom: 1px solid var(--i18n-border); letter-spacing: 0.05em; }
|
|
185
|
+
.i18n__string-table-advanced td { padding: 0.75rem 1rem; border-bottom: 1px solid oklch(1 0 0 / 0.04); }
|
|
186
|
+
.i18n__string-table-advanced tr:hover { background: oklch(1 0 0 / 0.02); }
|
|
187
|
+
.i18n__string-context-badge { display: inline-block; padding: 0.125rem 0.375rem; border-radius: 0.25rem; background: oklch(0.65 0.20 265 / 0.1); font-size: 0.625rem; color: oklch(0.65 0.20 265); }
|
|
188
|
+
|
|
189
|
+
/* ─── Locale Selector Advanced ───────────────────────── */
|
|
190
|
+
.i18n__locale-selector-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 0.75rem; }
|
|
191
|
+
.i18n__locale-card { padding: 1rem; border: 1px solid var(--i18n-border); border-radius: var(--i18n-radius); background: oklch(1 0 0 / 0.02); text-align: center; cursor: pointer; transition: all 0.2s; }
|
|
192
|
+
.i18n__locale-card:hover { border-color: var(--i18n-accent); background: oklch(0.65 0.20 265 / 0.05); }
|
|
193
|
+
.i18n__locale-card--selected { border: 2px solid var(--i18n-accent); background: oklch(0.65 0.20 265 / 0.08); }
|
|
194
|
+
.i18n__locale-card-flag { font-size: 1.5rem; margin-bottom: 0.5rem; }
|
|
195
|
+
.i18n__locale-card-name { font-size: 0.75rem; font-weight: 600; color: oklch(1 0 0 / 0.8); }
|
|
196
|
+
.i18n__locale-card-code { font-size: 0.625rem; color: oklch(1 0 0 / 0.4); margin-top: 0.25rem; font-family: monospace; }
|
|
197
|
+
|
|
198
|
+
/* ─── Format Examples ────────────────────────────────── */
|
|
199
|
+
.i18n__format-examples { border: 1px solid var(--i18n-border); border-radius: var(--i18n-radius); background: oklch(1 0 0 / 0.02); padding: 1rem; }
|
|
200
|
+
.i18n__format-example-title { font-size: 0.6875rem; font-weight: 600; text-transform: uppercase; color: oklch(1 0 0 / 0.5); margin-bottom: 0.75rem; }
|
|
201
|
+
.i18n__format-example-list { display: flex; flex-direction: column; gap: 0.5rem; }
|
|
202
|
+
.i18n__format-example-item { padding: 0.5rem; border-left: 2px solid oklch(0.65 0.20 265); border-radius: calc(var(--i18n-radius) * 0.6); background: oklch(0.65 0.20 265 / 0.05); }
|
|
203
|
+
.i18n__format-example-key { font-family: monospace; font-size: 0.625rem; color: oklch(0.65 0.20 265); margin-bottom: 0.25rem; }
|
|
204
|
+
.i18n__format-example-value { font-size: 0.6875rem; color: oklch(1 0 0 / 0.7); }
|
|
205
|
+
|
|
206
|
+
/* ─── Breadcrumb Navigation ──────────────────────────── */
|
|
207
|
+
.i18n__breadcrumb { display: flex; align-items: center; gap: 0.5rem; font-size: 0.875rem; margin-bottom: 1rem; }
|
|
208
|
+
.i18n__breadcrumb-item { color: oklch(1 0 0 / 0.5); }
|
|
209
|
+
.i18n__breadcrumb-item--active { color: oklch(1 0 0 / 0.8); font-weight: 500; }
|
|
210
|
+
.i18n__breadcrumb-sep { color: oklch(1 0 0 / 0.2); margin: 0 0.25rem; }
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"outDir": "dist",
|
|
5
|
+
"rootDir": "src",
|
|
6
|
+
"jsx": "react-jsx",
|
|
7
|
+
"strict": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"forceConsistentCasingInFileNames": true,
|
|
10
|
+
"resolveJsonModule": true,
|
|
11
|
+
"isolatedModules": true,
|
|
12
|
+
"target": "ES2022",
|
|
13
|
+
"module": "ESNext",
|
|
14
|
+
"moduleResolution": "bundler"
|
|
15
|
+
},
|
|
16
|
+
"include": [
|
|
17
|
+
"src"
|
|
18
|
+
]
|
|
19
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# ✦ @geenius-i18n/shared\n\n> Geenius i18n — Shared types & Convex schema\n\n---\n\n## Overview\nBuilt with Steve Jobs-level minimalism and Jony Ive-level craftsmanship, this package is designed to deliver unparalleled developer experience (DX) and rock-solid performance.\n\n## Installation\n\n```bash\npnpm add @geenius-i18n/shared\n```\n\n## Usage\n\n```typescript\nimport { init } from '@geenius-i18n/shared';\n\n// Initialize the module with absolute precision\ninit({\n mode: 'premium',\n});\n```\n\n## Architecture\n- **Zero-config**: It just works.\n- **Strictly Typed**: Fully written in TypeScript for flawless IntelliSense.\n- **Framework Agnostic**: seamlessly integrates into the Geenius ecosystem.\n\n---\n\n*Designed by Antigravity HQ*\n
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@geenius-i18n/shared",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"description": "Geenius i18n \u2014 Shared types & Convex schema",
|
|
7
|
+
"author": "Antigravity HQ",
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"publishConfig": {
|
|
10
|
+
"access": "public"
|
|
11
|
+
},
|
|
12
|
+
"main": "./dist/index.js",
|
|
13
|
+
"module": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"import": "./dist/index.js"
|
|
19
|
+
},
|
|
20
|
+
"./convex": "./src/convex.ts"
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist",
|
|
24
|
+
"src"
|
|
25
|
+
],
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "tsup",
|
|
28
|
+
"clean": "rm -rf dist",
|
|
29
|
+
"type-check": "tsc --noEmit",
|
|
30
|
+
"prepublishOnly": "pnpm clean && pnpm build",
|
|
31
|
+
"test": "vitest run",
|
|
32
|
+
"test:watch": "vitest",
|
|
33
|
+
"test:coverage": "vitest run --coverage"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"convex": "^1.34.0",
|
|
37
|
+
"tsup": "^8.5.1",
|
|
38
|
+
"typescript": "~6.0.2",
|
|
39
|
+
"vitest": "^4.0.0"
|
|
40
|
+
},
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=20.0.0"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import {
|
|
3
|
+
interpolate, t, plural, getDirection,
|
|
4
|
+
LOCALE_INFO, ALL_LOCALES, RTL_LOCALES,
|
|
5
|
+
} from '../index'
|
|
6
|
+
|
|
7
|
+
describe('interpolate', () => {
|
|
8
|
+
it('replaces {{key}} with values', () => {
|
|
9
|
+
expect(interpolate('Hello {{name}}!', { name: 'Alice' })).toBe('Hello Alice!')
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
it('handles numeric values', () => {
|
|
13
|
+
expect(interpolate('{{count}} items', { count: 42 })).toBe('42 items')
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('preserves unmatched placeholders', () => {
|
|
17
|
+
expect(interpolate('Hello {{name}}', {})).toBe('Hello {{name}}')
|
|
18
|
+
})
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
describe('t (translation lookup)', () => {
|
|
22
|
+
const dict = { greeting: 'Hello', nested: { welcome: 'Welcome {{name}}' } }
|
|
23
|
+
|
|
24
|
+
it('resolves top-level key', () => {
|
|
25
|
+
expect(t('greeting', dict)).toBe('Hello')
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('resolves nested key with dot notation', () => {
|
|
29
|
+
expect(t('nested.welcome', dict, { name: 'Bob' })).toBe('Welcome Bob')
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('returns key for missing translations', () => {
|
|
33
|
+
expect(t('missing.key', dict)).toBe('missing.key')
|
|
34
|
+
})
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
describe('plural', () => {
|
|
38
|
+
const dict = { items_one: '1 item', items_other: '{{count}} items', items_zero: 'No items' }
|
|
39
|
+
|
|
40
|
+
it('uses _zero form for count 0', () => {
|
|
41
|
+
expect(plural('items', 0, dict)).toBe('No items')
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('uses _one form for count 1', () => {
|
|
45
|
+
expect(plural('items', 1, dict)).toBe('1 item')
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('uses _other form for count > 1', () => {
|
|
49
|
+
expect(plural('items', 5, dict)).toBe('5 items')
|
|
50
|
+
})
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
describe('Locale Data', () => {
|
|
54
|
+
it('ALL_LOCALES has 14 locales', () => {
|
|
55
|
+
expect(ALL_LOCALES).toHaveLength(14)
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('RTL_LOCALES contains ar and he', () => {
|
|
59
|
+
expect(RTL_LOCALES).toContain('ar')
|
|
60
|
+
expect(RTL_LOCALES).toContain('he')
|
|
61
|
+
expect(RTL_LOCALES).toHaveLength(2)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('getDirection returns rtl for Arabic', () => {
|
|
65
|
+
expect(getDirection('ar')).toBe('rtl')
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('getDirection returns ltr for English', () => {
|
|
69
|
+
expect(getDirection('en')).toBe('ltr')
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('LOCALE_INFO has nativeName for each locale', () => {
|
|
73
|
+
ALL_LOCALES.forEach(locale => {
|
|
74
|
+
expect(LOCALE_INFO[locale].nativeName).toBeDefined()
|
|
75
|
+
expect(LOCALE_INFO[locale].flag.length).toBeGreaterThan(0)
|
|
76
|
+
})
|
|
77
|
+
})
|
|
78
|
+
})
|