@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,3 @@
1
+ export * from './primitives'
2
+ export * from './components'
3
+ export * from './pages'
@@ -0,0 +1,134 @@
1
+ import { createSignal, createMemo, Show, For } from 'solid-js'
2
+ import type { Component } from 'solid-js'
3
+ import type { Locale, LocaleStat } from '@geenius-i18n/shared'
4
+ import { LOCALE_INFO, ALL_LOCALES, flattenDict } from '@geenius-i18n/shared'
5
+ import type { TranslationDict } from '@geenius-i18n/shared'
6
+ import { LocaleStatsCard } from '../components/LocaleStatsCard'
7
+ import { MissingKeyAlert } from '../components/MissingKeyAlert'
8
+ import { TranslationKeyRow } from '../components/TranslationKeyRow'
9
+ import { LocaleCard } from '../components/LocaleCard'
10
+
11
+ interface Props {
12
+ stats?: LocaleStat[]
13
+ translations?: Record<string, Record<string, string>>
14
+ supportedLocales?: Locale[]
15
+ onUpsert?: (locale: string, key: string, value: string) => void
16
+ onDelete?: (locale: string, key: string) => void
17
+ }
18
+
19
+ export const I18nAdminPage: Component<Props> = (props) => {
20
+ const locales = () => props.supportedLocales ?? ALL_LOCALES
21
+ const [selectedLocale, setSelectedLocale] = createSignal<Locale>(locales()[0] ?? 'en')
22
+ const [search, setSearch] = createSignal('')
23
+
24
+ const allStats = () => props.stats ?? []
25
+ const allTranslations = () => props.translations ?? {}
26
+
27
+ const selectedStat = createMemo(() =>
28
+ allStats().find((s) => s.locale === selectedLocale())
29
+ )
30
+
31
+ const missingCount = createMemo(() => selectedStat()?.missingKeys ?? 0)
32
+
33
+ const translationEntries = createMemo(() => {
34
+ const localeTranslations = allTranslations()[selectedLocale()]
35
+ if (!localeTranslations) return []
36
+ const entries = Object.entries(localeTranslations)
37
+ const q = search().toLowerCase()
38
+ if (!q) return entries
39
+ return entries.filter(
40
+ ([key, value]) => key.toLowerCase().includes(q) || value.toLowerCase().includes(q)
41
+ )
42
+ })
43
+
44
+ const coverageForLocale = (locale: Locale) => {
45
+ const stat = allStats().find((s) => s.locale === locale)
46
+ return stat?.coverage ?? 0
47
+ }
48
+
49
+ return (
50
+ <div class="i18n__admin-page">
51
+ <div class="i18n__admin-container">
52
+ {/* Header */}
53
+ <div class="i18n__page-header">
54
+ <h1 class="i18n__page-title">Translation Admin</h1>
55
+ <p class="i18n__page-subtitle">Manage translations across all supported locales</p>
56
+ </div>
57
+
58
+ <div class="i18n__admin-body">
59
+ {/* Stats section */}
60
+ <Show when={allStats().length > 0}>
61
+ <section class="i18n__admin-section">
62
+ <h2 class="i18n__admin-section-title">Locale Coverage</h2>
63
+ <div class="i18n__stats-grid">
64
+ <For each={allStats()}>
65
+ {(stat) => <LocaleStatsCard stat={stat} />}
66
+ </For>
67
+ </div>
68
+ </section>
69
+ </Show>
70
+
71
+ {/* Missing keys alert */}
72
+ <MissingKeyAlert count={missingCount()} locale={selectedLocale()} />
73
+
74
+ {/* Locale selector */}
75
+ <section class="i18n__admin-section">
76
+ <h2 class="i18n__admin-section-title">Select Locale</h2>
77
+ <div class="i18n__locale-grid">
78
+ <For each={locales()}>
79
+ {(locale) => (
80
+ <LocaleCard
81
+ locale={locale}
82
+ coverage={coverageForLocale(locale)}
83
+ isSelected={locale === selectedLocale()}
84
+ onSelect={setSelectedLocale}
85
+ />
86
+ )}
87
+ </For>
88
+ </div>
89
+ </section>
90
+
91
+ {/* Search + translation rows */}
92
+ <section class="i18n__admin-section">
93
+ <div class="i18n__admin-section-header">
94
+ <h2 class="i18n__admin-section-title">
95
+ Translations — {LOCALE_INFO[selectedLocale()]?.nativeName}
96
+ </h2>
97
+ <input
98
+ type="text"
99
+ class="i18n__search-input"
100
+ placeholder="Search keys or values…"
101
+ value={search()}
102
+ onInput={(e) => setSearch(e.currentTarget.value)}
103
+ />
104
+ </div>
105
+
106
+ <Show
107
+ when={translationEntries().length > 0}
108
+ fallback={
109
+ <div class="i18n__empty-state">
110
+ <span class="i18n__empty-state-icon">🌐</span>
111
+ <p class="i18n__empty-state-text">No translations for {LOCALE_INFO[selectedLocale()]?.name}</p>
112
+ </div>
113
+ }
114
+ >
115
+ <div class="i18n__translation-list">
116
+ <For each={translationEntries()}>
117
+ {([key, value]) => (
118
+ <TranslationKeyRow
119
+ translationKey={key}
120
+ value={value}
121
+ locale={selectedLocale()}
122
+ onEdit={(k, v) => props.onUpsert?.(selectedLocale(), k, v)}
123
+ onDelete={(k) => props.onDelete?.(selectedLocale(), k)}
124
+ />
125
+ )}
126
+ </For>
127
+ </div>
128
+ </Show>
129
+ </section>
130
+ </div>
131
+ </div>
132
+ </div>
133
+ )
134
+ }
@@ -0,0 +1,116 @@
1
+ import { Show } from 'solid-js'
2
+ import type { Component } from 'solid-js'
3
+ import type { I18nConfig, TranslationDict } from '@geenius-i18n/shared'
4
+ import { LOCALE_INFO, ALL_LOCALES } from '@geenius-i18n/shared'
5
+ import { I18nProvider, createI18n } from '../primitives'
6
+ import { LocaleSwitcher } from '../components/LocaleSwitcher'
7
+ import { RTLWrapper } from '../components/RTLWrapper'
8
+
9
+ function PreviewInfoCard(props: { label: string; value: string; highlight?: boolean }) {
10
+ return (
11
+ <div class="i18n__preview-info-card">
12
+ <span class="i18n__preview-info-label">{props.label}</span>
13
+ <span class={`i18n__preview-info-value${props.highlight ? ' i18n__preview-info-value--highlight' : ''}`}>
14
+ {props.value}
15
+ </span>
16
+ </div>
17
+ )
18
+ }
19
+
20
+ function FormatExample(props: { label: string; value: string }) {
21
+ return (
22
+ <div class="i18n__preview-example">
23
+ <span class="i18n__preview-label">{props.label}</span>
24
+ <span class="i18n__preview-value">{props.value}</span>
25
+ </div>
26
+ )
27
+ }
28
+
29
+ function PreviewContent() {
30
+ const { locale, t, direction, isRTL, setLocale, formatDate, formatNumber, formatCurrency } = createI18n()
31
+ const info = () => LOCALE_INFO[locale()]
32
+
33
+ return (
34
+ <div class="i18n__preview-page">
35
+ <div class="i18n__preview-container">
36
+ {/* Header */}
37
+ <div class="i18n__preview-header">
38
+ <div class="i18n__preview-header-text">
39
+ <h1 class="i18n__preview-title">Locale Preview</h1>
40
+ <p class="i18n__preview-subtitle">Live preview of locale formatting and direction</p>
41
+ </div>
42
+ <LocaleSwitcher locales={ALL_LOCALES} current={locale()} onChange={setLocale} />
43
+ </div>
44
+
45
+ {/* Info cards */}
46
+ <div class="i18n__preview-info-grid">
47
+ <PreviewInfoCard label="Locale" value={locale().toUpperCase()} />
48
+ <PreviewInfoCard label="Direction" value={direction()} highlight={isRTL()} />
49
+ <PreviewInfoCard label="Native Name" value={info()?.nativeName ?? '?'} />
50
+ <PreviewInfoCard label="Flag" value={info()?.flag ?? '?'} />
51
+ </div>
52
+
53
+ {/* RTL-aware content */}
54
+ <RTLWrapper locale={locale()}>
55
+ <div class="i18n__preview-content">
56
+ {/* Formatting */}
57
+ <section class="i18n__preview-section">
58
+ <h3 class="i18n__preview-section-title">Formatting Examples</h3>
59
+ <div class="i18n__preview-examples">
60
+ <FormatExample label="Date" value={formatDate(new Date())} />
61
+ <FormatExample label="Date (full)" value={formatDate(new Date(), { dateStyle: 'full' })} />
62
+ <FormatExample label="Number" value={formatNumber(1234567.89)} />
63
+ <FormatExample label="Currency (USD)" value={formatCurrency(9999.99, 'USD')} />
64
+ <FormatExample label="Currency (EUR)" value={formatCurrency(4250.50, 'EUR')} />
65
+ <FormatExample label="Percentage" value={formatNumber(0.85, { style: 'percent' })} />
66
+ </div>
67
+ </section>
68
+
69
+ {/* RTL notice */}
70
+ <Show when={isRTL()}>
71
+ <div class="i18n__preview-rtl-notice">
72
+ ⚡ RTL mode active — text flows right-to-left
73
+ </div>
74
+ </Show>
75
+
76
+ {/* Sample text */}
77
+ <section class="i18n__preview-section">
78
+ <h3 class="i18n__preview-section-title">Sample Text</h3>
79
+ <p class="i18n__preview-sample-text">
80
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor
81
+ incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud
82
+ exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
83
+ </p>
84
+ </section>
85
+
86
+ {/* Translation test */}
87
+ <section class="i18n__preview-section">
88
+ <h3 class="i18n__preview-section-title">Translation Lookup</h3>
89
+ <div class="i18n__preview-examples">
90
+ <FormatExample label="greeting" value={t('greeting')} />
91
+ <FormatExample label="auth.login" value={t('auth.login')} />
92
+ <FormatExample label="dashboard.title" value={t('dashboard.title')} />
93
+ </div>
94
+ </section>
95
+ </div>
96
+ </RTLWrapper>
97
+ </div>
98
+ </div>
99
+ )
100
+ }
101
+
102
+ export const LocalePreviewPage: Component<{ config?: Partial<I18nConfig>; translations?: TranslationDict }> = (props) => {
103
+ const fullConfig: I18nConfig = {
104
+ defaultLocale: 'en',
105
+ supportedLocales: ALL_LOCALES,
106
+ detectBrowser: true,
107
+ persistLocale: true,
108
+ ...props.config,
109
+ }
110
+
111
+ return (
112
+ <I18nProvider config={fullConfig} translations={props.translations}>
113
+ <PreviewContent />
114
+ </I18nProvider>
115
+ )
116
+ }
@@ -0,0 +1,2 @@
1
+ export { I18nAdminPage } from './I18nAdminPage'
2
+ export { LocalePreviewPage } from './LocalePreviewPage'
@@ -0,0 +1 @@
1
+ export * from '@geenius-i18n/solidjs'
@@ -0,0 +1,20 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist",
5
+ "rootDir": "src",
6
+ "jsxImportSource": "solid-js",
7
+ "jsx": "preserve",
8
+ "strict": true,
9
+ "skipLibCheck": true,
10
+ "forceConsistentCasingInFileNames": true,
11
+ "resolveJsonModule": true,
12
+ "isolatedModules": true,
13
+ "target": "ES2022",
14
+ "module": "ESNext",
15
+ "moduleResolution": "bundler"
16
+ },
17
+ "include": [
18
+ "src"
19
+ ]
20
+ }
@@ -0,0 +1,18 @@
1
+ // tsup.config.ts
2
+ import { defineConfig } from "tsup";
3
+ import { solidPlugin } from "esbuild-plugin-solid";
4
+ var tsup_config_default = defineConfig({
5
+ entry: ["src/index.tsx", "src/styles.css"],
6
+ outDir: "dist",
7
+ format: ["esm", "cjs"],
8
+ dts: true,
9
+ sourcemap: true,
10
+ clean: true,
11
+ treeshake: true,
12
+ external: ["solid-js"],
13
+ esbuildPlugins: [solidPlugin()]
14
+ });
15
+ export {
16
+ tsup_config_default as default
17
+ };
18
+ //# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidHN1cC5jb25maWcudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbImNvbnN0IF9faW5qZWN0ZWRfZmlsZW5hbWVfXyA9IFwiL1VzZXJzL21laGRpbmFiaGFuaS9Qcm9qZWN0cy9hbnRpZ3Jhdml0eS9ib2lsZXJwbGF0ZXMvZ2Vlbml1cy1wYWNrYWdlcy9nZWVuaXVzLWkxOG4vcGFja2FnZXMvc29saWRqcy1jc3MvdHN1cC5jb25maWcudHNcIjtjb25zdCBfX2luamVjdGVkX2Rpcm5hbWVfXyA9IFwiL1VzZXJzL21laGRpbmFiaGFuaS9Qcm9qZWN0cy9hbnRpZ3Jhdml0eS9ib2lsZXJwbGF0ZXMvZ2Vlbml1cy1wYWNrYWdlcy9nZWVuaXVzLWkxOG4vcGFja2FnZXMvc29saWRqcy1jc3NcIjtjb25zdCBfX2luamVjdGVkX2ltcG9ydF9tZXRhX3VybF9fID0gXCJmaWxlOi8vL1VzZXJzL21laGRpbmFiaGFuaS9Qcm9qZWN0cy9hbnRpZ3Jhdml0eS9ib2lsZXJwbGF0ZXMvZ2Vlbml1cy1wYWNrYWdlcy9nZWVuaXVzLWkxOG4vcGFja2FnZXMvc29saWRqcy1jc3MvdHN1cC5jb25maWcudHNcIjtpbXBvcnQgeyBkZWZpbmVDb25maWcgfSBmcm9tICd0c3VwJ1xuaW1wb3J0IHsgc29saWRQbHVnaW4gfSBmcm9tICdlc2J1aWxkLXBsdWdpbi1zb2xpZCdcblxuZXhwb3J0IGRlZmF1bHQgZGVmaW5lQ29uZmlnKHtcbiAgICBlbnRyeTogWydzcmMvaW5kZXgudHN4JywgJ3NyYy9zdHlsZXMuY3NzJ10sXG4gICAgb3V0RGlyOiAnZGlzdCcsXG4gICAgZm9ybWF0OiBbJ2VzbScsICdjanMnXSxcbiAgICBkdHM6IHRydWUsXG4gICAgc291cmNlbWFwOiB0cnVlLFxuICAgIGNsZWFuOiB0cnVlLFxuICAgIHRyZWVzaGFrZTogdHJ1ZSxcbiAgICBleHRlcm5hbDogWydzb2xpZC1qcyddLFxuICAgIGVzYnVpbGRQbHVnaW5zOiBbc29saWRQbHVnaW4oKV0sXG59KVxuIl0sCiAgIm1hcHBpbmdzIjogIjtBQUFzYyxTQUFTLG9CQUFvQjtBQUNuZSxTQUFTLG1CQUFtQjtBQUU1QixJQUFPLHNCQUFRLGFBQWE7QUFBQSxFQUN4QixPQUFPLENBQUMsaUJBQWlCLGdCQUFnQjtBQUFBLEVBQ3pDLFFBQVE7QUFBQSxFQUNSLFFBQVEsQ0FBQyxPQUFPLEtBQUs7QUFBQSxFQUNyQixLQUFLO0FBQUEsRUFDTCxXQUFXO0FBQUEsRUFDWCxPQUFPO0FBQUEsRUFDUCxXQUFXO0FBQUEsRUFDWCxVQUFVLENBQUMsVUFBVTtBQUFBLEVBQ3JCLGdCQUFnQixDQUFDLFlBQVksQ0FBQztBQUNsQyxDQUFDOyIsCiAgIm5hbWVzIjogW10KfQo=
@@ -0,0 +1,14 @@
1
+ import { defineConfig } from 'tsup'
2
+ import { solidPlugin } from 'esbuild-plugin-solid'
3
+
4
+ export default defineConfig({
5
+ entry: ['src/index.tsx', 'src/styles.css'],
6
+ outDir: 'dist',
7
+ format: ['esm', 'cjs'],
8
+ dts: true,
9
+ sourcemap: true,
10
+ clean: true,
11
+ treeshake: true,
12
+ external: ['solid-js'],
13
+ esbuildPlugins: [solidPlugin()],
14
+ })
@@ -0,0 +1,2 @@
1
+ packages:
2
+ - 'packages/*'
package/tsconfig.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "esModuleInterop": true,
7
+ "strict": true,
8
+ "skipLibCheck": true,
9
+ "declaration": true,
10
+ "declarationMap": true,
11
+ "sourceMap": true,
12
+ "outDir": "dist",
13
+ "rootDir": "src",
14
+ "jsx": "react-jsx",
15
+ "forceConsistentCasingInFileNames": true,
16
+ "resolveJsonModule": true,
17
+ "isolatedModules": true
18
+ },
19
+ "exclude": [
20
+ "node_modules",
21
+ "dist"
22
+ ]
23
+ }