@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.
Files changed (101) hide show
  1. package/.changeset/config.json +11 -0
  2. package/.github/CODEOWNERS +1 -0
  3. package/.github/ISSUE_TEMPLATE/bug_report.md +16 -0
  4. package/.github/ISSUE_TEMPLATE/feature_request.md +11 -0
  5. package/.github/PULL_REQUEST_TEMPLATE.md +10 -0
  6. package/.github/dependabot.yml +11 -0
  7. package/.github/workflows/ci.yml +23 -0
  8. package/.github/workflows/release.yml +29 -0
  9. package/.nvmrc +1 -0
  10. package/.project/ACCOUNT.yaml +4 -0
  11. package/.project/IDEAS.yaml +7 -0
  12. package/.project/PROJECT.yaml +11 -0
  13. package/.project/ROADMAP.yaml +15 -0
  14. package/CHANGELOG.md +8 -0
  15. package/CODE_OF_CONDUCT.md +16 -0
  16. package/CONTRIBUTING.md +26 -0
  17. package/LICENSE +2 -0
  18. package/README.md +1 -0
  19. package/SECURITY.md +15 -0
  20. package/SUPPORT.md +8 -0
  21. package/package.json +75 -0
  22. package/packages/convex/package.json +42 -0
  23. package/packages/convex/src/index.ts +3 -0
  24. package/packages/convex/src/mutations.ts +65 -0
  25. package/packages/convex/src/queries.ts +54 -0
  26. package/packages/convex/src/schema.ts +26 -0
  27. package/packages/convex/tsconfig.json +18 -0
  28. package/packages/convex/tsup.config.ts +17 -0
  29. package/packages/react/README.md +1 -0
  30. package/packages/react/package.json +51 -0
  31. package/packages/react/src/components/index.tsx +87 -0
  32. package/packages/react/src/hooks/index.ts +4 -0
  33. package/packages/react/src/hooks/useI18n.tsx +50 -0
  34. package/packages/react/src/hooks/useI18nAdmin.ts +12 -0
  35. package/packages/react/src/hooks/useLocaleDetect.ts +10 -0
  36. package/packages/react/src/hooks/useTranslations.ts +11 -0
  37. package/packages/react/src/index.tsx +8 -0
  38. package/packages/react/src/pages/I18nAdminPage.tsx +42 -0
  39. package/packages/react/src/pages/LocalePreviewPage.tsx +54 -0
  40. package/packages/react/src/pages/index.ts +2 -0
  41. package/packages/react/tsconfig.json +19 -0
  42. package/packages/react/tsup.config.ts +12 -0
  43. package/packages/react-css/README.md +1 -0
  44. package/packages/react-css/package.json +36 -0
  45. package/packages/react-css/src/components/index.tsx +66 -0
  46. package/packages/react-css/src/hooks/index.ts +4 -0
  47. package/packages/react-css/src/index.tsx +4 -0
  48. package/packages/react-css/src/pages/LocaleSettingsPage.tsx +74 -0
  49. package/packages/react-css/src/pages/TranslationsPage.tsx +98 -0
  50. package/packages/react-css/src/styles.css +210 -0
  51. package/packages/react-css/tsconfig.json +19 -0
  52. package/packages/react-css/tsup.config.ts +10 -0
  53. package/packages/shared/README.md +1 -0
  54. package/packages/shared/package.json +44 -0
  55. package/packages/shared/src/__tests__/i18n.test.ts +78 -0
  56. package/packages/shared/src/config.ts +344 -0
  57. package/packages/shared/src/index.ts +106 -0
  58. package/packages/shared/src/types.ts +51 -0
  59. package/packages/shared/tsconfig.json +18 -0
  60. package/packages/shared/tsup.config.ts +11 -0
  61. package/packages/shared/vitest.config.ts +4 -0
  62. package/packages/solidjs/README.md +1 -0
  63. package/packages/solidjs/package.json +47 -0
  64. package/packages/solidjs/src/components/LocaleCard.tsx +44 -0
  65. package/packages/solidjs/src/components/LocaleStatsCard.tsx +35 -0
  66. package/packages/solidjs/src/components/LocaleSwitcher.tsx +65 -0
  67. package/packages/solidjs/src/components/MissingKeyAlert.tsx +21 -0
  68. package/packages/solidjs/src/components/RTLWrapper.tsx +13 -0
  69. package/packages/solidjs/src/components/TranslationKeyRow.tsx +41 -0
  70. package/packages/solidjs/src/components/index.ts +6 -0
  71. package/packages/solidjs/src/index.tsx +8 -0
  72. package/packages/solidjs/src/pages/I18nAdminPage.tsx +188 -0
  73. package/packages/solidjs/src/pages/LocalePreviewPage.tsx +99 -0
  74. package/packages/solidjs/src/pages/index.ts +2 -0
  75. package/packages/solidjs/src/primitives/I18nProvider.tsx +56 -0
  76. package/packages/solidjs/src/primitives/createI18nAdmin.ts +7 -0
  77. package/packages/solidjs/src/primitives/createLocaleDetect.ts +8 -0
  78. package/packages/solidjs/src/primitives/createTranslations.ts +22 -0
  79. package/packages/solidjs/src/primitives/index.ts +4 -0
  80. package/packages/solidjs/tsconfig.json +20 -0
  81. package/packages/solidjs/tsup.config.ts +12 -0
  82. package/packages/solidjs-css/README.md +1 -0
  83. package/packages/solidjs-css/package.json +33 -0
  84. package/packages/solidjs-css/src/components/LocaleCard.tsx +45 -0
  85. package/packages/solidjs-css/src/components/LocaleStatsCard.tsx +43 -0
  86. package/packages/solidjs-css/src/components/LocaleSwitcher.tsx +51 -0
  87. package/packages/solidjs-css/src/components/MissingKeyAlert.tsx +24 -0
  88. package/packages/solidjs-css/src/components/RTLWrapper.tsx +16 -0
  89. package/packages/solidjs-css/src/components/TranslationKeyRow.tsx +47 -0
  90. package/packages/solidjs-css/src/components/index.ts +6 -0
  91. package/packages/solidjs-css/src/i18n.css +1322 -0
  92. package/packages/solidjs-css/src/index.tsx +3 -0
  93. package/packages/solidjs-css/src/pages/I18nAdminPage.tsx +134 -0
  94. package/packages/solidjs-css/src/pages/LocalePreviewPage.tsx +116 -0
  95. package/packages/solidjs-css/src/pages/index.ts +2 -0
  96. package/packages/solidjs-css/src/primitives/index.ts +1 -0
  97. package/packages/solidjs-css/tsconfig.json +20 -0
  98. package/packages/solidjs-css/tsup.config.bundled_dcjc4sct21j.mjs +18 -0
  99. package/packages/solidjs-css/tsup.config.ts +14 -0
  100. package/pnpm-workspace.yaml +2 -0
  101. package/tsconfig.json +23 -0
@@ -0,0 +1,344 @@
1
+ /**
2
+ * I18n configuration and setup
3
+ */
4
+
5
+ import type { Locale, I18nConfig, TranslationDict } from './types'
6
+
7
+ /**
8
+ * I18n configuration builder
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * const i18nConfig = createI18nConfig()
13
+ * .withDefaultLocale('en')
14
+ * .withSupportedLocales(['en', 'fr', 'de'])
15
+ * .withFallbackLocale('en')
16
+ * .withNamespace('common')
17
+ * ```
18
+ */
19
+ export class I18nConfigBuilder {
20
+ private config: I18nConfig = {
21
+ defaultLocale: 'en',
22
+ supportedLocales: ['en'],
23
+ fallbackLocale: 'en',
24
+ namespace: 'translations',
25
+ debug: false,
26
+ }
27
+
28
+ /**
29
+ * Sets default locale
30
+ */
31
+ withDefaultLocale(locale: Locale): this {
32
+ this.config.defaultLocale = locale
33
+ return this
34
+ }
35
+
36
+ /**
37
+ * Sets supported locales
38
+ */
39
+ withSupportedLocales(locales: Locale[]): this {
40
+ this.config.supportedLocales = locales
41
+ return this
42
+ }
43
+
44
+ /**
45
+ * Sets fallback locale when translation is missing
46
+ */
47
+ withFallbackLocale(locale: Locale): this {
48
+ this.config.fallbackLocale = locale
49
+ return this
50
+ }
51
+
52
+ /**
53
+ * Sets translation namespace
54
+ */
55
+ withNamespace(namespace: string): this {
56
+ this.config.namespace = namespace
57
+ return this
58
+ }
59
+
60
+ /**
61
+ * Sets debug mode
62
+ */
63
+ withDebug(enabled: boolean): this {
64
+ this.config.debug = enabled
65
+ return this
66
+ }
67
+
68
+ /**
69
+ * Enables missing key warnings
70
+ */
71
+ withMissingKeyWarnings(enabled: boolean): this {
72
+ this.config.missingKeyWarnings = enabled
73
+ return this
74
+ }
75
+
76
+ /**
77
+ * Sets missing key prefix
78
+ */
79
+ withMissingKeyPrefix(prefix: string): this {
80
+ this.config.missingKeyPrefix = prefix
81
+ return this
82
+ }
83
+
84
+ /**
85
+ * Builds the configuration
86
+ */
87
+ build(): I18nConfig {
88
+ if (!this.config.supportedLocales.includes(this.config.defaultLocale)) {
89
+ throw new Error(
90
+ `Default locale ${this.config.defaultLocale} must be in supported locales`
91
+ )
92
+ }
93
+
94
+ if (!this.config.supportedLocales.includes(this.config.fallbackLocale)) {
95
+ throw new Error(
96
+ `Fallback locale ${this.config.fallbackLocale} must be in supported locales`
97
+ )
98
+ }
99
+
100
+ return this.config
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Creates a new I18n configuration builder
106
+ */
107
+ export function createI18nConfig(): I18nConfigBuilder {
108
+ return new I18nConfigBuilder()
109
+ }
110
+
111
+ /**
112
+ * Translation loader configuration
113
+ */
114
+ export interface TranslationLoaderConfig {
115
+ /** Base URL for loading translations */
116
+ baseUrl?: string
117
+ /** Whether to cache loaded translations */
118
+ cache?: boolean
119
+ /** Cache duration in milliseconds */
120
+ cacheDuration?: number
121
+ /** Whether to load all locales on init */
122
+ preloadAll?: boolean
123
+ }
124
+
125
+ /**
126
+ * Creates a translation loader
127
+ *
128
+ * @example
129
+ * ```ts
130
+ * const loader = createTranslationLoader({
131
+ * baseUrl: '/locales',
132
+ * cache: true,
133
+ * cacheDuration: 3600000
134
+ * })
135
+ *
136
+ * const translations = await loader.load('en', 'common')
137
+ * ```
138
+ */
139
+ export function createTranslationLoader(config: TranslationLoaderConfig = {}) {
140
+ const cache = new Map<string, { data: TranslationDict; timestamp: number }>()
141
+ const {
142
+ baseUrl = '/locales',
143
+ cache: enableCache = true,
144
+ cacheDuration = 3600000, // 1 hour
145
+ } = config
146
+
147
+ return {
148
+ /**
149
+ * Loads translations for a locale and namespace
150
+ */
151
+ async load(locale: Locale, namespace: string): Promise<TranslationDict> {
152
+ const cacheKey = `${locale}:${namespace}`
153
+
154
+ // Check cache
155
+ if (enableCache) {
156
+ const cached = cache.get(cacheKey)
157
+ if (cached && Date.now() - cached.timestamp < cacheDuration) {
158
+ return cached.data
159
+ }
160
+ }
161
+
162
+ try {
163
+ const url = `${baseUrl}/${locale}/${namespace}.json`
164
+ const response = await fetch(url)
165
+
166
+ if (!response.ok) {
167
+ console.warn(
168
+ `Failed to load translations from ${url}: ${response.statusText}`
169
+ )
170
+ return {}
171
+ }
172
+
173
+ const data = (await response.json()) as TranslationDict
174
+
175
+ // Cache result
176
+ if (enableCache) {
177
+ cache.set(cacheKey, {
178
+ data,
179
+ timestamp: Date.now(),
180
+ })
181
+ }
182
+
183
+ return data
184
+ } catch (error) {
185
+ console.error(`Error loading translations for ${locale}/${namespace}:`, error)
186
+ return {}
187
+ }
188
+ },
189
+
190
+ /**
191
+ * Preloads all locales for a namespace
192
+ */
193
+ async preload(locales: Locale[], namespace: string): Promise<void> {
194
+ await Promise.all(locales.map((locale) => this.load(locale, namespace)))
195
+ },
196
+
197
+ /**
198
+ * Clears cache
199
+ */
200
+ clearCache(locale?: Locale, namespace?: string) {
201
+ if (locale && namespace) {
202
+ cache.delete(`${locale}:${namespace}`)
203
+ } else {
204
+ cache.clear()
205
+ }
206
+ },
207
+
208
+ /**
209
+ * Gets cache stats
210
+ */
211
+ getCacheStats() {
212
+ return {
213
+ size: cache.size,
214
+ entries: Array.from(cache.keys()),
215
+ }
216
+ },
217
+ }
218
+ }
219
+
220
+ /**
221
+ * Namespace loader for managing multiple translation namespaces
222
+ *
223
+ * @example
224
+ * ```ts
225
+ * const loader = createNamespaceLoader(createTranslationLoader())
226
+ * const translations = await loader.loadMultiple('en', ['common', 'errors'])
227
+ * ```
228
+ */
229
+ export function createNamespaceLoader(
230
+ translationLoader: ReturnType<typeof createTranslationLoader>
231
+ ) {
232
+ const namespaces = new Map<string, TranslationDict>()
233
+
234
+ return {
235
+ /**
236
+ * Loads a single namespace
237
+ */
238
+ async load(locale: Locale, namespace: string): Promise<TranslationDict> {
239
+ const translations = await translationLoader.load(locale, namespace)
240
+ const key = `${locale}:${namespace}`
241
+ namespaces.set(key, translations)
242
+ return translations
243
+ },
244
+
245
+ /**
246
+ * Loads multiple namespaces
247
+ */
248
+ async loadMultiple(
249
+ locale: Locale,
250
+ namespaceList: string[]
251
+ ): Promise<Record<string, TranslationDict>> {
252
+ const result: Record<string, TranslationDict> = {}
253
+
254
+ for (const ns of namespaceList) {
255
+ result[ns] = await this.load(locale, ns)
256
+ }
257
+
258
+ return result
259
+ },
260
+
261
+ /**
262
+ * Gets loaded namespace
263
+ */
264
+ get(locale: Locale, namespace: string): TranslationDict | undefined {
265
+ return namespaces.get(`${locale}:${namespace}`)
266
+ },
267
+
268
+ /**
269
+ * Merges multiple translations into one
270
+ */
271
+ merge(
272
+ locale: Locale,
273
+ namespaceList: string[]
274
+ ): TranslationDict {
275
+ const merged: TranslationDict = {}
276
+
277
+ for (const ns of namespaceList) {
278
+ const trans = this.get(locale, ns)
279
+ if (trans) {
280
+ Object.assign(merged, trans)
281
+ }
282
+ }
283
+
284
+ return merged
285
+ },
286
+ }
287
+ }
288
+
289
+ /**
290
+ * Default i18n configurations for different scenarios
291
+ */
292
+ export const i18nPresets = {
293
+ /**
294
+ * Simple single-language setup
295
+ */
296
+ simple: (locale: Locale = 'en'): I18nConfig => {
297
+ return createI18nConfig()
298
+ .withDefaultLocale(locale)
299
+ .withSupportedLocales([locale])
300
+ .build()
301
+ },
302
+
303
+ /**
304
+ * Multi-language with common locales
305
+ */
306
+ multi: (defaultLocale: Locale = 'en'): I18nConfig => {
307
+ return createI18nConfig()
308
+ .withDefaultLocale(defaultLocale)
309
+ .withSupportedLocales(['en', 'fr', 'de', 'es', 'it'])
310
+ .build()
311
+ },
312
+
313
+ /**
314
+ * Global localization
315
+ */
316
+ global: (defaultLocale: Locale = 'en'): I18nConfig => {
317
+ return createI18nConfig()
318
+ .withDefaultLocale(defaultLocale)
319
+ .withSupportedLocales([
320
+ 'en',
321
+ 'fr',
322
+ 'de',
323
+ 'es',
324
+ 'it',
325
+ 'pt',
326
+ 'ru',
327
+ 'zh',
328
+ 'ja',
329
+ 'ko',
330
+ ] as Locale[])
331
+ .build()
332
+ },
333
+
334
+ /**
335
+ * Development configuration with debug output
336
+ */
337
+ development: (): I18nConfig => {
338
+ return createI18nConfig()
339
+ .withDebug(true)
340
+ .withMissingKeyWarnings(true)
341
+ .withMissingKeyPrefix('[MISSING] ')
342
+ .build()
343
+ },
344
+ }
@@ -0,0 +1,106 @@
1
+ export type {
2
+ Locale, Direction, I18nNamespace, TranslationDict, I18nConfig,
3
+ LocaleInfo, TranslationEntry, MissingKey, LocaleStat,
4
+ } from './types'
5
+
6
+ import type { Locale, Direction, LocaleInfo, TranslationDict } from './types'
7
+
8
+ // ─── LOCALE_INFO (14 locales) ─────────────────────────
9
+ export const LOCALE_INFO: Record<Locale, LocaleInfo> = {
10
+ en: { code: 'en', name: 'English', nativeName: 'English', direction: 'ltr', flag: '🇬🇧' },
11
+ fr: { code: 'fr', name: 'French', nativeName: 'Français', direction: 'ltr', flag: '🇫🇷' },
12
+ de: { code: 'de', name: 'German', nativeName: 'Deutsch', direction: 'ltr', flag: '🇩🇪' },
13
+ es: { code: 'es', name: 'Spanish', nativeName: 'Español', direction: 'ltr', flag: '🇪🇸' },
14
+ pt: { code: 'pt', name: 'Portuguese', nativeName: 'Português', direction: 'ltr', flag: '🇵🇹' },
15
+ it: { code: 'it', name: 'Italian', nativeName: 'Italiano', direction: 'ltr', flag: '🇮🇹' },
16
+ nl: { code: 'nl', name: 'Dutch', nativeName: 'Nederlands', direction: 'ltr', flag: '🇳🇱' },
17
+ ru: { code: 'ru', name: 'Russian', nativeName: 'Русский', direction: 'ltr', flag: '🇷🇺' },
18
+ zh: { code: 'zh', name: 'Chinese', nativeName: '中文', direction: 'ltr', flag: '🇨🇳' },
19
+ ja: { code: 'ja', name: 'Japanese', nativeName: '日本語', direction: 'ltr', flag: '🇯🇵' },
20
+ ko: { code: 'ko', name: 'Korean', nativeName: '한국어', direction: 'ltr', flag: '🇰🇷' },
21
+ ar: { code: 'ar', name: 'Arabic', nativeName: 'العربية', direction: 'rtl', flag: '🇸🇦' },
22
+ he: { code: 'he', name: 'Hebrew', nativeName: 'עברית', direction: 'rtl', flag: '🇮🇱' },
23
+ tr: { code: 'tr', name: 'Turkish', nativeName: 'Türkçe', direction: 'ltr', flag: '🇹🇷' },
24
+ }
25
+
26
+ export const ALL_LOCALES: Locale[] = Object.keys(LOCALE_INFO) as Locale[]
27
+ export const RTL_LOCALES: Locale[] = ['ar', 'he']
28
+
29
+ // ─── Translation Utilities ────────────────────────────
30
+ export function interpolate(template: string, params: Record<string, string | number>): string {
31
+ return template.replace(/\{\{(\w+)\}\}/g, (_, key) => String(params[key] ?? `{{${key}}}`))
32
+ }
33
+
34
+ export function t(key: string, dict: TranslationDict, params?: Record<string, string | number>): string {
35
+ const parts = key.split('.')
36
+ let current: string | TranslationDict = dict
37
+ for (const part of parts) {
38
+ if (typeof current !== 'object' || current === null) return key
39
+ current = (current as TranslationDict)[part]
40
+ if (current === undefined) return key
41
+ }
42
+ if (typeof current !== 'string') return key
43
+ return params ? interpolate(current, params) : current
44
+ }
45
+
46
+ export function plural(key: string, count: number, dict: TranslationDict): string {
47
+ if (count === 0) { const zero = t(`${key}_zero`, dict); if (zero !== `${key}_zero`) return zero }
48
+ if (count === 1) { const one = t(`${key}_one`, dict); if (one !== `${key}_one`) return one }
49
+ const other = t(`${key}_other`, dict)
50
+ if (other !== `${key}_other`) return interpolate(other, { count })
51
+ return t(key, dict, { count })
52
+ }
53
+
54
+ // ─── Formatting ───────────────────────────────────────
55
+ export function formatDate(date: Date | string, locale: Locale, format?: Intl.DateTimeFormatOptions): string {
56
+ const d = typeof date === 'string' ? new Date(date) : date
57
+ return new Intl.DateTimeFormat(locale, format ?? { dateStyle: 'medium' }).format(d)
58
+ }
59
+
60
+ export function formatNumber(n: number, locale: Locale, opts?: Intl.NumberFormatOptions): string {
61
+ return new Intl.NumberFormat(locale, opts).format(n)
62
+ }
63
+
64
+ export function formatCurrency(amount: number, currency: string, locale: Locale): string {
65
+ return new Intl.NumberFormat(locale, { style: 'currency', currency }).format(amount)
66
+ }
67
+
68
+ // ─── Detection & Direction ────────────────────────────
69
+ export function detectLocale(supportedLocales: Locale[]): Locale {
70
+ if (typeof navigator === 'undefined') return supportedLocales[0] ?? 'en'
71
+ const browserLang = navigator.language.split('-')[0] as Locale
72
+ if (supportedLocales.includes(browserLang)) return browserLang
73
+ const full = navigator.language as Locale
74
+ if (supportedLocales.includes(full)) return full
75
+ return supportedLocales[0] ?? 'en'
76
+ }
77
+
78
+ export function getDirection(locale: Locale): Direction {
79
+ return LOCALE_INFO[locale]?.direction ?? 'ltr'
80
+ }
81
+
82
+ export function isRTL(locale: Locale): boolean {
83
+ return getDirection(locale) === 'rtl'
84
+ }
85
+
86
+ // ─── Namespace Loader (stub) ──────────────────────────
87
+ const namespaceCache = new Map<string, TranslationDict>()
88
+
89
+ export async function loadNamespace(locale: Locale, ns: string): Promise<TranslationDict> {
90
+ const cacheKey = `${locale}:${ns}`
91
+ if (namespaceCache.has(cacheKey)) return namespaceCache.get(cacheKey)!
92
+ // In real usage this would fetch from API/CDN — stub returns empty dict
93
+ const dict: TranslationDict = {}
94
+ namespaceCache.set(cacheKey, dict)
95
+ return dict
96
+ }
97
+
98
+ export function flattenDict(dict: TranslationDict, prefix = ''): Record<string, string> {
99
+ const result: Record<string, string> = {}
100
+ for (const [key, value] of Object.entries(dict)) {
101
+ const fullKey = prefix ? `${prefix}.${key}` : key
102
+ if (typeof value === 'string') result[fullKey] = value
103
+ else Object.assign(result, flattenDict(value, fullKey))
104
+ }
105
+ return result
106
+ }
@@ -0,0 +1,51 @@
1
+ export type Locale = 'en' | 'fr' | 'de' | 'es' | 'pt' | 'it' | 'nl' | 'ru' | 'zh' | 'ja' | 'ko' | 'ar' | 'he' | 'tr'
2
+ export type Direction = 'ltr' | 'rtl'
3
+ export type I18nNamespace = 'common' | 'auth' | 'dashboard' | 'billing' | 'errors' | string
4
+
5
+ export type TranslationDict = Record<string, string | TranslationDict>
6
+
7
+ export interface I18nConfig {
8
+ defaultLocale: Locale
9
+ supportedLocales: Locale[]
10
+ fallbackLocale?: Locale
11
+ namespaces?: I18nNamespace[]
12
+ detectBrowser?: boolean
13
+ persistLocale?: boolean
14
+ dateFormat?: string
15
+ numberFormat?: Intl.NumberFormatOptions
16
+ }
17
+
18
+ export interface LocaleInfo {
19
+ code: Locale
20
+ name: string
21
+ nativeName: string
22
+ direction: Direction
23
+ flag: string
24
+ }
25
+
26
+ export interface TranslationEntry {
27
+ id: string
28
+ locale: Locale
29
+ namespace: string
30
+ key: string
31
+ value: string
32
+ lastEditedBy?: string
33
+ updatedAt: string
34
+ createdAt: string
35
+ }
36
+
37
+ export interface MissingKey {
38
+ id: string
39
+ locale: Locale
40
+ namespace: string
41
+ key: string
42
+ detectedAt: string
43
+ count: number
44
+ }
45
+
46
+ export interface LocaleStat {
47
+ locale: Locale
48
+ totalKeys: number
49
+ missingKeys: number
50
+ coverage: number
51
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist",
5
+ "rootDir": "src",
6
+ "strict": true,
7
+ "skipLibCheck": true,
8
+ "forceConsistentCasingInFileNames": true,
9
+ "resolveJsonModule": true,
10
+ "isolatedModules": true,
11
+ "target": "ES2022",
12
+ "module": "ESNext",
13
+ "moduleResolution": "bundler"
14
+ },
15
+ "include": [
16
+ "src"
17
+ ]
18
+ }
@@ -0,0 +1,11 @@
1
+ import { defineConfig } from 'tsup'
2
+
3
+ export default defineConfig({
4
+ entry: { index: 'src/index.ts' },
5
+ outDir: 'dist',
6
+ format: ['esm'],
7
+ dts: true,
8
+ sourcemap: true,
9
+ clean: true,
10
+ treeshake: true,
11
+ })
@@ -0,0 +1,4 @@
1
+ import { defineConfig } from 'vitest/config'
2
+ export default defineConfig({
3
+ test: { globals: true, environment: 'node' },
4
+ })
@@ -0,0 +1 @@
1
+ # ✦ @geenius-i18n/solidjs\n\n> Geenius I18n — SolidJS components & primitives\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/solidjs\n```\n\n## Usage\n\n```typescript\nimport { init } from '@geenius-i18n/solidjs';\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,47 @@
1
+ {
2
+ "name": "@geenius-i18n/solidjs",
3
+ "version": "0.1.0",
4
+ "private": false,
5
+ "type": "module",
6
+ "description": "Geenius I18n — SolidJS components & primitives",
7
+ "author": "Antigravity HQ",
8
+ "license": "MIT",
9
+ "publishConfig": {
10
+ "access": "restricted"
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
+ },
21
+ "files": [
22
+ "dist",
23
+ "src"
24
+ ],
25
+ "scripts": {
26
+ "build": "tsup",
27
+ "clean": "rm -rf dist",
28
+ "type-check": "tsc --noEmit",
29
+ "prepublishOnly": "pnpm clean && pnpm build"
30
+ },
31
+ "dependencies": {
32
+ "@geenius-i18n/shared": "workspace:*"
33
+ },
34
+ "devDependencies": {
35
+ "solid-js": "^1.9.0",
36
+ "tsup": "^8.5.1",
37
+ "typescript": "~6.0.2",
38
+ "@tanstack/solid-router": "^1.111.0"
39
+ },
40
+ "peerDependencies": {
41
+ "solid-js": "^1.8.0 || ^1.9.0",
42
+ "@tanstack/solid-router": "^1.0.0"
43
+ },
44
+ "engines": {
45
+ "node": ">=20.0.0"
46
+ }
47
+ }
@@ -0,0 +1,44 @@
1
+ import { Show } from 'solid-js'
2
+ import type { Component } from 'solid-js'
3
+ import type { Locale } from '@geenius-i18n/shared'
4
+ import { LOCALE_INFO } from '@geenius-i18n/shared'
5
+
6
+ interface Props {
7
+ locale: Locale
8
+ coverage?: number
9
+ }
10
+
11
+ export const LocaleCard: Component<Props> = (props) => {
12
+ const info = () => LOCALE_INFO[props.locale]
13
+ const barColor = () =>
14
+ (props.coverage ?? 0) >= 90 ? 'oklch(0.72 0.18 155)' :
15
+ (props.coverage ?? 0) >= 70 ? 'oklch(0.72 0.18 60)' :
16
+ 'oklch(0.60 0.25 25)'
17
+
18
+ return (
19
+ <div style={{
20
+ 'border-radius': '0.75rem', border: '1px solid oklch(1 0 0 / 0.08)',
21
+ background: 'oklch(1 0 0 / 0.02)', padding: '1rem',
22
+ }}>
23
+ <div style={{ display: 'flex', 'align-items': 'center', gap: '0.75rem', 'margin-bottom': '0.75rem' }}>
24
+ <span style={{ 'font-size': '1.5rem' }}>{info()?.flag}</span>
25
+ <div>
26
+ <p style={{ 'font-size': '0.875rem', 'font-weight': '600', color: 'oklch(1 0 0 / 0.85)' }}>{info()?.nativeName}</p>
27
+ <p style={{ 'font-size': '0.625rem', color: 'oklch(1 0 0 / 0.4)' }}>{info()?.name} — {props.locale.toUpperCase()}</p>
28
+ </div>
29
+ <Show when={info()?.direction === 'rtl'}>
30
+ <span style={{
31
+ 'margin-left': 'auto', 'font-size': '0.625rem', padding: '0.125rem 0.375rem',
32
+ 'border-radius': '0.25rem', background: 'oklch(0.72 0.18 60 / 0.15)', color: 'oklch(0.72 0.18 60)',
33
+ }}>RTL</span>
34
+ </Show>
35
+ </div>
36
+ <Show when={props.coverage !== undefined}>
37
+ <div style={{ height: '0.375rem', 'border-radius': '9999px', background: 'oklch(1 0 0 / 0.05)', overflow: 'hidden' }}>
38
+ <div style={{ height: '100%', 'border-radius': '9999px', background: barColor(), width: `${props.coverage}%` }} />
39
+ </div>
40
+ <p style={{ 'margin-top': '0.25rem', 'font-size': '0.625rem', color: 'oklch(1 0 0 / 0.4)', 'text-align': 'right' }}>{props.coverage}%</p>
41
+ </Show>
42
+ </div>
43
+ )
44
+ }
@@ -0,0 +1,35 @@
1
+ import { For, Show } from 'solid-js'
2
+ import type { Component } from 'solid-js'
3
+ import type { LocaleStat } from '@geenius-i18n/shared'
4
+ import { LOCALE_INFO } from '@geenius-i18n/shared'
5
+
6
+ export const LocaleStatsCard: Component<{ stats: LocaleStat[] }> = (props) => {
7
+ return (
8
+ <div style={{ display: 'grid', 'grid-template-columns': 'repeat(4, 1fr)', gap: '0.75rem' }}>
9
+ <For each={props.stats}>
10
+ {(s) => {
11
+ const info = LOCALE_INFO[s.locale]
12
+ const barColor = s.coverage >= 90 ? 'oklch(0.72 0.18 155)' : s.coverage >= 70 ? 'oklch(0.72 0.18 60)' : 'oklch(0.60 0.25 25)'
13
+ return (
14
+ <div style={{ 'border-radius': '0.75rem', border: '1px solid oklch(1 0 0 / 0.08)', background: 'oklch(1 0 0 / 0.02)', padding: '1rem' }}>
15
+ <div style={{ display: 'flex', 'align-items': 'center', gap: '0.5rem', 'margin-bottom': '0.5rem' }}>
16
+ <span style={{ 'font-size': '1.125rem' }}>{info?.flag}</span>
17
+ <span style={{ 'font-size': '0.75rem', 'font-weight': '500', color: 'oklch(1 0 0 / 0.8)' }}>{info?.nativeName}</span>
18
+ </div>
19
+ <div style={{ display: 'flex', 'justify-content': 'space-between', 'font-size': '0.625rem', color: 'oklch(1 0 0 / 0.4)', 'margin-bottom': '0.25rem' }}>
20
+ <span>{s.totalKeys} keys</span>
21
+ <span style={{ 'font-weight': '700' }}>{s.coverage}%</span>
22
+ </div>
23
+ <div style={{ height: '0.375rem', 'border-radius': '9999px', background: 'oklch(1 0 0 / 0.05)', overflow: 'hidden' }}>
24
+ <div style={{ height: '100%', 'border-radius': '9999px', background: barColor, width: `${s.coverage}%` }} />
25
+ </div>
26
+ <Show when={s.missingKeys > 0}>
27
+ <p style={{ 'margin-top': '0.375rem', 'font-size': '0.625rem', color: 'oklch(0.60 0.25 25)' }}>{s.missingKeys} missing</p>
28
+ </Show>
29
+ </div>
30
+ )
31
+ }}
32
+ </For>
33
+ </div>
34
+ )
35
+ }