locon 1.0.1 → 1.0.3
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 +238 -0
- package/dist/index.js +29 -2
- package/dist/index.mjs +29 -2
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
## locon
|
|
2
|
+
|
|
3
|
+
`locon` is a small, typed i18n helper for React Native.
|
|
4
|
+
It provides a context provider, a hook, and a simple text component to keep
|
|
5
|
+
your translations flat, explicit, and easy to use.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
- **React Native–first**: tiny API, no global singletons
|
|
12
|
+
- **Context provider** with pluggable assets (per-locale JSON)
|
|
13
|
+
- **Device language auto-detection** (optional)
|
|
14
|
+
- **`useLocon()` hook** for accessing translations and changing locale
|
|
15
|
+
- **`<LText>` component** as a drop-in localized `<Text>`
|
|
16
|
+
- Fully typed TypeScript build (`dist/index.d.ts`)
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
### Installation
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install locon
|
|
24
|
+
# or
|
|
25
|
+
yarn add locon
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**Peer dependencies**
|
|
29
|
+
|
|
30
|
+
- `react >= 19.0.0`
|
|
31
|
+
- `react-native >= 0.70.0`
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
### Quick start
|
|
36
|
+
|
|
37
|
+
#### 1. Prepare your translation assets
|
|
38
|
+
|
|
39
|
+
`locon` expects flat key/value objects per locale:
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
// en.json
|
|
43
|
+
export default {
|
|
44
|
+
hello: 'Hello',
|
|
45
|
+
welcome_title: 'Welcome',
|
|
46
|
+
change_language: 'Change language',
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// de.json
|
|
50
|
+
export default {
|
|
51
|
+
hello: 'Hallo',
|
|
52
|
+
welcome_title: 'Willkommen',
|
|
53
|
+
change_language: 'Sprache ändern',
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
You can also use JSON files and import them in your app bundle.
|
|
58
|
+
|
|
59
|
+
#### 2. Wrap your app with `Locon`
|
|
60
|
+
|
|
61
|
+
```tsx
|
|
62
|
+
import React from 'react'
|
|
63
|
+
import { NavigationContainer, DefaultTheme } from '@react-navigation/native'
|
|
64
|
+
import Locon from 'locon'
|
|
65
|
+
|
|
66
|
+
import en from './assets/i18n/en.json'
|
|
67
|
+
import de from './assets/i18n/de.json'
|
|
68
|
+
import RootNavigator from './RootNavigator'
|
|
69
|
+
|
|
70
|
+
function App() {
|
|
71
|
+
const theme = {
|
|
72
|
+
...DefaultTheme,
|
|
73
|
+
colors: {
|
|
74
|
+
...DefaultTheme.colors,
|
|
75
|
+
background: '#ffffff',
|
|
76
|
+
},
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<Locon
|
|
81
|
+
assets={{ en, de }}
|
|
82
|
+
defaultLocale='en'
|
|
83
|
+
projectLocale='en'
|
|
84
|
+
autodetect
|
|
85
|
+
>
|
|
86
|
+
<NavigationContainer theme={theme}>
|
|
87
|
+
<RootNavigator />
|
|
88
|
+
</NavigationContainer>
|
|
89
|
+
</Locon>
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export default App
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
If `currentLocale` is not provided and `autodetect` is `true` (default),
|
|
97
|
+
`locon` will:
|
|
98
|
+
|
|
99
|
+
1. Read device locale from `Intl.DateTimeFormat().resolvedOptions().locale`
|
|
100
|
+
2. Extract language code (e.g. `'en'` from `'en-US'`)
|
|
101
|
+
3. If that language exists in `assets`, use it as initial locale
|
|
102
|
+
4. Otherwise, fall back to `defaultLocale`
|
|
103
|
+
|
|
104
|
+
#### 3. Use the `useLocon()` hook
|
|
105
|
+
|
|
106
|
+
```tsx
|
|
107
|
+
import React from 'react'
|
|
108
|
+
import { View, Button, Text } from 'react-native'
|
|
109
|
+
import { useLocon } from 'locon'
|
|
110
|
+
|
|
111
|
+
function LanguageSwitcher() {
|
|
112
|
+
const { l, currentLocale, setLocale } = useLocon()
|
|
113
|
+
|
|
114
|
+
return (
|
|
115
|
+
<View>
|
|
116
|
+
<Text>
|
|
117
|
+
{l('current_language')}: {currentLocale}
|
|
118
|
+
</Text>
|
|
119
|
+
|
|
120
|
+
<Button title='English' onPress={() => setLocale('en')} />
|
|
121
|
+
<Button title='Deutsch' onPress={() => setLocale('de')} />
|
|
122
|
+
</View>
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
#### 4. Use `<LText>` for localized text
|
|
128
|
+
|
|
129
|
+
```tsx
|
|
130
|
+
import React from 'react'
|
|
131
|
+
import { View } from 'react-native'
|
|
132
|
+
import { LText } from 'locon'
|
|
133
|
+
|
|
134
|
+
function Welcome() {
|
|
135
|
+
return (
|
|
136
|
+
<View>
|
|
137
|
+
{/* Uses children as key */}
|
|
138
|
+
<LText>hello</LText>
|
|
139
|
+
|
|
140
|
+
{/* Or explicit key with fallback children */}
|
|
141
|
+
<LText assetKey='welcome_title'>Welcome</LText>
|
|
142
|
+
</View>
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
### API
|
|
150
|
+
|
|
151
|
+
#### `<Locon />`
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
interface LoconProps extends PropsWithChildren {
|
|
155
|
+
assets?: Record<string, Record<string, string>>
|
|
156
|
+
currentLocale?: string
|
|
157
|
+
defaultLocale?: string
|
|
158
|
+
projectLocale?: string
|
|
159
|
+
autodetect?: boolean
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
- **`assets`**
|
|
164
|
+
Map of locale → flat translations, e.g. `{ en: { hello: 'Hello' }, de: { hello: 'Hallo' } }`
|
|
165
|
+
|
|
166
|
+
- **`currentLocale`**
|
|
167
|
+
Initial locale. If provided, it overrides auto-detection.
|
|
168
|
+
|
|
169
|
+
- **`defaultLocale`** (default `'en'`)
|
|
170
|
+
Used when key is missing in the current locale.
|
|
171
|
+
|
|
172
|
+
- **`projectLocale`** (default `'en'`)
|
|
173
|
+
Used for reverse lookup by value (e.g. when you only know the string in
|
|
174
|
+
project language).
|
|
175
|
+
|
|
176
|
+
- **`autodetect`** (default `true`)
|
|
177
|
+
Enables system language detection as described above.
|
|
178
|
+
|
|
179
|
+
#### `useLocon()`
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
import { useLocon } from 'locon'
|
|
183
|
+
|
|
184
|
+
const {
|
|
185
|
+
l, // (key: string) => string
|
|
186
|
+
assets, // all assets map
|
|
187
|
+
currentLocale, // current language code
|
|
188
|
+
defaultLocale, // default language code
|
|
189
|
+
autodetect, // boolean
|
|
190
|
+
setLocale, // (locale: string) => void
|
|
191
|
+
} = useLocon()
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
- `l(key)`
|
|
195
|
+
Returns the localized string or the key itself if nothing was found.
|
|
196
|
+
|
|
197
|
+
- `setLocale(locale)`
|
|
198
|
+
Switches to `locale` if it exists in `assets`, otherwise logs a warning
|
|
199
|
+
in development.
|
|
200
|
+
|
|
201
|
+
#### `<LText />`
|
|
202
|
+
|
|
203
|
+
```ts
|
|
204
|
+
interface LTextProps extends ComponentProps<typeof Text> {
|
|
205
|
+
children: string
|
|
206
|
+
assetKey?: string
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
- If `assetKey` is provided, `LText` uses it directly as the lookup key.
|
|
211
|
+
- Otherwise, it uses `children` as an input to the same resolution logic
|
|
212
|
+
as `l()`:
|
|
213
|
+
1. Try to find a key in the **project locale** whose **value** equals
|
|
214
|
+
`children`, then use that key in the current locale.
|
|
215
|
+
2. If nothing is found in the project locale, try **directly** using
|
|
216
|
+
`children` as a key in the current locale.
|
|
217
|
+
3. If still nothing is found, fall back to the **default locale**.
|
|
218
|
+
4. If there is no match anywhere, render `children` as-is.
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
### TypeScript
|
|
223
|
+
|
|
224
|
+
The published package contains:
|
|
225
|
+
|
|
226
|
+
- `dist/index.js` (CJS)
|
|
227
|
+
- `dist/index.mjs` (ESM)
|
|
228
|
+
- `dist/index.d.ts` (types)
|
|
229
|
+
|
|
230
|
+
So you can import from `locon` in both JS and TS projects and get type
|
|
231
|
+
support for `Locon`, `useLocon`, and `LText`.
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
### License
|
|
236
|
+
|
|
237
|
+
`locon` is released under the **ISC** license.
|
|
238
|
+
|
package/dist/index.js
CHANGED
|
@@ -7,6 +7,21 @@ var reactNative = require('react-native');
|
|
|
7
7
|
var jsxRuntime = require('react/jsx-runtime');
|
|
8
8
|
|
|
9
9
|
// src/locon/index.tsx
|
|
10
|
+
|
|
11
|
+
// src/locon/utils/getSystemLanguage.ts
|
|
12
|
+
function getSystemLanguage(availableLocales) {
|
|
13
|
+
try {
|
|
14
|
+
const systemLocale = Intl.DateTimeFormat().resolvedOptions().locale;
|
|
15
|
+
if (!systemLocale) return null;
|
|
16
|
+
const languageCode = systemLocale.split("-")[0]?.toLowerCase();
|
|
17
|
+
if (!languageCode) return null;
|
|
18
|
+
const supportedLanguage = availableLocales.find((locale) => locale === languageCode);
|
|
19
|
+
return supportedLanguage ? languageCode : null;
|
|
20
|
+
} catch (error) {
|
|
21
|
+
console.log("Could not detect system language:", error);
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
10
25
|
function useLocon() {
|
|
11
26
|
const locon = react.useContext(LoconContext);
|
|
12
27
|
return locon;
|
|
@@ -31,12 +46,24 @@ var LoconContext = react.createContext({
|
|
|
31
46
|
function Locon({
|
|
32
47
|
children,
|
|
33
48
|
assets = {},
|
|
34
|
-
currentLocale: currentLocaleProps
|
|
49
|
+
currentLocale: currentLocaleProps,
|
|
35
50
|
defaultLocale = "en",
|
|
36
51
|
projectLocale = "en",
|
|
37
52
|
autodetect = true
|
|
38
53
|
}) {
|
|
39
|
-
const [currentLocale, setCurrentLocale] = react.useState(
|
|
54
|
+
const [currentLocale, setCurrentLocale] = react.useState(() => {
|
|
55
|
+
if (currentLocaleProps) {
|
|
56
|
+
return currentLocaleProps;
|
|
57
|
+
}
|
|
58
|
+
if (autodetect) {
|
|
59
|
+
const availableLocales = Object.keys(assets);
|
|
60
|
+
const systemLang = getSystemLanguage(availableLocales);
|
|
61
|
+
if (systemLang && assets[systemLang]) {
|
|
62
|
+
return systemLang;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return defaultLocale;
|
|
66
|
+
});
|
|
40
67
|
const getAssets = react.useCallback(() => {
|
|
41
68
|
const currentAssets = assets[currentLocale] ?? assets[defaultLocale];
|
|
42
69
|
return currentAssets ?? {};
|
package/dist/index.mjs
CHANGED
|
@@ -3,6 +3,21 @@ import { Text } from 'react-native';
|
|
|
3
3
|
import { jsx } from 'react/jsx-runtime';
|
|
4
4
|
|
|
5
5
|
// src/locon/index.tsx
|
|
6
|
+
|
|
7
|
+
// src/locon/utils/getSystemLanguage.ts
|
|
8
|
+
function getSystemLanguage(availableLocales) {
|
|
9
|
+
try {
|
|
10
|
+
const systemLocale = Intl.DateTimeFormat().resolvedOptions().locale;
|
|
11
|
+
if (!systemLocale) return null;
|
|
12
|
+
const languageCode = systemLocale.split("-")[0]?.toLowerCase();
|
|
13
|
+
if (!languageCode) return null;
|
|
14
|
+
const supportedLanguage = availableLocales.find((locale) => locale === languageCode);
|
|
15
|
+
return supportedLanguage ? languageCode : null;
|
|
16
|
+
} catch (error) {
|
|
17
|
+
console.log("Could not detect system language:", error);
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
6
21
|
function useLocon() {
|
|
7
22
|
const locon = useContext(LoconContext);
|
|
8
23
|
return locon;
|
|
@@ -27,12 +42,24 @@ var LoconContext = createContext({
|
|
|
27
42
|
function Locon({
|
|
28
43
|
children,
|
|
29
44
|
assets = {},
|
|
30
|
-
currentLocale: currentLocaleProps
|
|
45
|
+
currentLocale: currentLocaleProps,
|
|
31
46
|
defaultLocale = "en",
|
|
32
47
|
projectLocale = "en",
|
|
33
48
|
autodetect = true
|
|
34
49
|
}) {
|
|
35
|
-
const [currentLocale, setCurrentLocale] = useState(
|
|
50
|
+
const [currentLocale, setCurrentLocale] = useState(() => {
|
|
51
|
+
if (currentLocaleProps) {
|
|
52
|
+
return currentLocaleProps;
|
|
53
|
+
}
|
|
54
|
+
if (autodetect) {
|
|
55
|
+
const availableLocales = Object.keys(assets);
|
|
56
|
+
const systemLang = getSystemLanguage(availableLocales);
|
|
57
|
+
if (systemLang && assets[systemLang]) {
|
|
58
|
+
return systemLang;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return defaultLocale;
|
|
62
|
+
});
|
|
36
63
|
const getAssets = useCallback(() => {
|
|
37
64
|
const currentAssets = assets[currentLocale] ?? assets[defaultLocale];
|
|
38
65
|
return currentAssets ?? {};
|
package/package.json
CHANGED