exceptionz-react-native-lingua 1.0.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 (44) hide show
  1. package/dist/LinguaContext.d.ts +18 -0
  2. package/dist/LinguaContext.d.ts.map +1 -0
  3. package/dist/LinguaContext.js +102 -0
  4. package/dist/LinguaContext.js.map +1 -0
  5. package/dist/components/LanguageModal.d.ts +3 -0
  6. package/dist/components/LanguageModal.d.ts.map +1 -0
  7. package/dist/components/LanguageModal.js +155 -0
  8. package/dist/components/LanguageModal.js.map +1 -0
  9. package/dist/components/LanguageSwitcher.d.ts +3 -0
  10. package/dist/components/LanguageSwitcher.d.ts.map +1 -0
  11. package/dist/components/LanguageSwitcher.js +162 -0
  12. package/dist/components/LanguageSwitcher.js.map +1 -0
  13. package/dist/components/T.d.ts +8 -0
  14. package/dist/components/T.d.ts.map +1 -0
  15. package/dist/components/T.js +27 -0
  16. package/dist/components/T.js.map +1 -0
  17. package/dist/constants.d.ts +6 -0
  18. package/dist/constants.d.ts.map +1 -0
  19. package/dist/constants.js +25 -0
  20. package/dist/constants.js.map +1 -0
  21. package/dist/hooks/useTranslate.d.ts +2 -0
  22. package/dist/hooks/useTranslate.d.ts.map +1 -0
  23. package/dist/hooks/useTranslate.js +35 -0
  24. package/dist/hooks/useTranslate.js.map +1 -0
  25. package/dist/index.d.ts +7 -0
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/index.js +17 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/utils/translator.d.ts +2 -0
  30. package/dist/utils/translator.d.ts.map +1 -0
  31. package/dist/utils/translator.js +51 -0
  32. package/dist/utils/translator.js.map +1 -0
  33. package/package.json +39 -0
  34. package/react-native-lingua-1.0.0.tgz +0 -0
  35. package/src/LinguaContext.tsx +72 -0
  36. package/src/components/LanguageModal.tsx +141 -0
  37. package/src/components/LanguageSwitcher.tsx +142 -0
  38. package/src/components/T.tsx +12 -0
  39. package/src/constants.ts +21 -0
  40. package/src/hooks/useTranslate.ts +36 -0
  41. package/src/index.ts +6 -0
  42. package/src/utils/translator.ts +37 -0
  43. package/tsconfig.json +22 -0
  44. package/tsup.config.ts +10 -0
package/dist/index.js ADDED
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SUPPORTED_LANGUAGES = exports.useTranslate = exports.T = exports.LanguageSwitcher = exports.LanguageModal = exports.useI18n = exports.LinguaProvider = void 0;
4
+ var LinguaContext_1 = require("./LinguaContext");
5
+ Object.defineProperty(exports, "LinguaProvider", { enumerable: true, get: function () { return LinguaContext_1.LinguaProvider; } });
6
+ Object.defineProperty(exports, "useI18n", { enumerable: true, get: function () { return LinguaContext_1.useI18n; } });
7
+ var LanguageModal_1 = require("./components/LanguageModal");
8
+ Object.defineProperty(exports, "LanguageModal", { enumerable: true, get: function () { return LanguageModal_1.LanguageModal; } });
9
+ var LanguageSwitcher_1 = require("./components/LanguageSwitcher");
10
+ Object.defineProperty(exports, "LanguageSwitcher", { enumerable: true, get: function () { return LanguageSwitcher_1.LanguageSwitcher; } });
11
+ var T_1 = require("./components/T");
12
+ Object.defineProperty(exports, "T", { enumerable: true, get: function () { return T_1.T; } });
13
+ var useTranslate_1 = require("./hooks/useTranslate");
14
+ Object.defineProperty(exports, "useTranslate", { enumerable: true, get: function () { return useTranslate_1.useTranslate; } });
15
+ var constants_1 = require("./constants");
16
+ Object.defineProperty(exports, "SUPPORTED_LANGUAGES", { enumerable: true, get: function () { return constants_1.SUPPORTED_LANGUAGES; } });
17
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,iDAA0D;AAAjD,+GAAA,cAAc,OAAA;AAAE,wGAAA,OAAO,OAAA;AAChC,4DAA2D;AAAlD,8GAAA,aAAa,OAAA;AACtB,kEAAiE;AAAxD,oHAAA,gBAAgB,OAAA;AACzB,oCAAmC;AAA1B,sFAAA,CAAC,OAAA;AACV,qDAAoD;AAA3C,4GAAA,YAAY,OAAA;AACrB,yCAAkD;AAAzC,gHAAA,mBAAmB,OAAA"}
@@ -0,0 +1,2 @@
1
+ export declare function translateText(text: string, targetLanguage: string): Promise<string>;
2
+ //# sourceMappingURL=translator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"translator.d.ts","sourceRoot":"","sources":["../../src/utils/translator.ts"],"names":[],"mappings":"AAAA,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAoCzF"}
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.translateText = translateText;
13
+ function translateText(text, targetLanguage) {
14
+ return __awaiter(this, void 0, void 0, function* () {
15
+ if (!text || text.trim() === '')
16
+ return text;
17
+ if (!targetLanguage || targetLanguage === 'en')
18
+ return text;
19
+ try {
20
+ const url = `https://translate.googleapis.com/translate_a/single?client=gtx&sl=auto&tl=${targetLanguage}&dt=t&q=${encodeURIComponent(text)}`;
21
+ const response = yield fetch(url);
22
+ if (!response.ok) {
23
+ console.warn(`Translation API returned status: ${response.status}`);
24
+ return text;
25
+ }
26
+ const contentType = response.headers.get('content-type');
27
+ if (!contentType || !contentType.includes('application/json')) {
28
+ const textResponse = yield response.text();
29
+ console.warn('Translation API returned non-JSON response:', textResponse.slice(0, 100));
30
+ return text;
31
+ }
32
+ let data;
33
+ try {
34
+ data = yield response.json();
35
+ }
36
+ catch (parseError) {
37
+ console.error('Failed to parse translation JSON:', parseError);
38
+ return text;
39
+ }
40
+ if (data && data[0] && Array.isArray(data[0])) {
41
+ return data[0].map((item) => item[0]).join('');
42
+ }
43
+ return text;
44
+ }
45
+ catch (error) {
46
+ console.error('Translation network/unexpected error:', error);
47
+ return text;
48
+ }
49
+ });
50
+ }
51
+ //# sourceMappingURL=translator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"translator.js","sourceRoot":"","sources":["../../src/utils/translator.ts"],"names":[],"mappings":";;;;;;;;;;;AAAA,sCAoCC;AApCD,SAAsB,aAAa,CAAC,IAAY,EAAE,cAAsB;;QACtE,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE;YAAE,OAAO,IAAI,CAAC;QAC7C,IAAI,CAAC,cAAc,IAAI,cAAc,KAAK,IAAI;YAAE,OAAO,IAAI,CAAC;QAE5D,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,6EAA6E,cAAc,WAAW,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC;YAC7I,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,CAAC;YAElC,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;gBACjB,OAAO,CAAC,IAAI,CAAC,oCAAoC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;gBACpE,OAAO,IAAI,CAAC;YACd,CAAC;YAED,MAAM,WAAW,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;YACzD,IAAI,CAAC,WAAW,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,kBAAkB,CAAC,EAAE,CAAC;gBAC9D,MAAM,YAAY,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;gBAC3C,OAAO,CAAC,IAAI,CAAC,6CAA6C,EAAE,YAAY,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;gBACxF,OAAO,IAAI,CAAC;YACd,CAAC;YAED,IAAI,IAAI,CAAC;YACT,IAAI,CAAC;gBACH,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;YAC/B,CAAC;YAAC,OAAO,UAAU,EAAE,CAAC;gBACpB,OAAO,CAAC,KAAK,CAAC,mCAAmC,EAAE,UAAU,CAAC,CAAC;gBAC/D,OAAO,IAAI,CAAC;YACd,CAAC;YAED,IAAI,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC9C,OAAO,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAS,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACtD,CAAC;YACD,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,uCAAuC,EAAE,KAAK,CAAC,CAAC;YAC9D,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;CAAA"}
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "exceptionz-react-native-lingua",
3
+ "version": "1.0.0",
4
+ "description": "A React Native translation library that auto-translates text nodes and displays a language selector modal.",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ }
14
+ },
15
+ "scripts": {
16
+ "build": "tsc",
17
+ "dev": "tsc --watch"
18
+ },
19
+ "keywords": [
20
+ "react-native",
21
+ "translate",
22
+ "i18n",
23
+ "google-translate"
24
+ ],
25
+ "author": "Antigravity",
26
+ "license": "MIT",
27
+ "peerDependencies": {
28
+ "react": ">=16.8.0",
29
+ "react-native": "*"
30
+ },
31
+ "devDependencies": {
32
+ "@types/react": "^18.2.0",
33
+ "react": "^18.2.0",
34
+ "react-native": "*",
35
+ "tsup": "^8.0.2",
36
+ "typescript": "^5.4.3",
37
+ "@react-native-async-storage/async-storage": "^1.23.1"
38
+ }
39
+ }
Binary file
@@ -0,0 +1,72 @@
1
+ import React, { createContext, useContext, useState, ReactNode, useEffect } from 'react';
2
+ import AsyncStorage from '@react-native-async-storage/async-storage';
3
+ import { SUPPORTED_LANGUAGES } from './constants';
4
+
5
+ interface LinguaContextProps {
6
+ currentLanguage: string;
7
+ setLanguage: (lang: string) => Promise<void>;
8
+ availableLanguages: string[];
9
+ isLoading: boolean;
10
+ }
11
+
12
+ const LinguaContext = createContext<LinguaContextProps | undefined>(undefined);
13
+
14
+ export const useI18n = () => {
15
+ const context = useContext(LinguaContext);
16
+ if (!context) throw new Error('useI18n must be used within a LinguaProvider');
17
+ return context;
18
+ };
19
+
20
+ interface LinguaProviderProps {
21
+ children: ReactNode;
22
+ defaultLang?: string;
23
+ languages?: string[];
24
+ }
25
+
26
+ export const translationCache = new Map<string, string>();
27
+ export const getCacheKey = (text: string, lang: string) => `${lang}:${text}`;
28
+
29
+ export const LinguaProvider: React.FC<LinguaProviderProps> = ({
30
+ children,
31
+ defaultLang = 'en',
32
+ languages = SUPPORTED_LANGUAGES.map(l => l.code)
33
+ }) => {
34
+ const [currentLanguage, setCurrentLanguage] = useState(defaultLang);
35
+ const [isLoading, setIsLoading] = useState(true);
36
+
37
+ useEffect(() => {
38
+ const loadLanguage = async () => {
39
+ try {
40
+ const saved = await AsyncStorage.getItem('app-language-preference');
41
+ if (saved) {
42
+ setCurrentLanguage(saved);
43
+ }
44
+ } catch (e) {
45
+ console.error('Failed to load language preference', e);
46
+ } finally {
47
+ setIsLoading(false);
48
+ }
49
+ };
50
+ loadLanguage();
51
+ }, []);
52
+
53
+ const handleSetLanguage = async (lang: string) => {
54
+ try {
55
+ setCurrentLanguage(lang);
56
+ await AsyncStorage.setItem('app-language-preference', lang);
57
+ } catch (e) {
58
+ console.error('Failed to save language preference', e);
59
+ }
60
+ };
61
+
62
+ return (
63
+ <LinguaContext.Provider value={{
64
+ currentLanguage,
65
+ setLanguage: handleSetLanguage,
66
+ availableLanguages: languages,
67
+ isLoading
68
+ }}>
69
+ {children}
70
+ </LinguaContext.Provider>
71
+ );
72
+ };
@@ -0,0 +1,141 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import {
3
+ Modal,
4
+ View,
5
+ Text,
6
+ TouchableOpacity,
7
+ FlatList,
8
+ StyleSheet,
9
+ SafeAreaView
10
+ } from 'react-native';
11
+ import { useI18n } from '../LinguaContext';
12
+ import { SUPPORTED_LANGUAGES } from '../constants';
13
+
14
+ export const LanguageModal: React.FC = () => {
15
+ const { setLanguage, currentLanguage, isLoading } = useI18n();
16
+ const [modalVisible, setModalVisible] = useState(false);
17
+
18
+ useEffect(() => {
19
+ if (!isLoading && !currentLanguage) {
20
+ setModalVisible(true);
21
+ }
22
+ }, [isLoading, currentLanguage]);
23
+
24
+ const handleSelectLanguage = async (code: string) => {
25
+ await setLanguage(code);
26
+ setModalVisible(false);
27
+ };
28
+
29
+ return (
30
+ <Modal
31
+ animationType="slide"
32
+ transparent={true}
33
+ visible={modalVisible}
34
+ onRequestClose={() => setModalVisible(false)}
35
+ >
36
+ <View style={styles.centeredView}>
37
+ <View style={styles.modalView}>
38
+ <Text style={styles.modalTitle}>Select Language</Text>
39
+ <Text style={styles.modalSubtitle}>Choose your preferred language.</Text>
40
+
41
+ <FlatList
42
+ data={SUPPORTED_LANGUAGES}
43
+ keyExtractor={(item) => item.code}
44
+ renderItem={({ item }) => (
45
+ <TouchableOpacity
46
+ style={[
47
+ styles.languageItem,
48
+ currentLanguage === item.code && styles.selectedItem
49
+ ]}
50
+ onPress={() => handleSelectLanguage(item.code)}
51
+ >
52
+ <Text style={styles.icon}>{item.icon}</Text>
53
+ <Text style={[
54
+ styles.label,
55
+ currentLanguage === item.code && styles.selectedLabel
56
+ ]}>
57
+ {item.label}
58
+ </Text>
59
+ </TouchableOpacity>
60
+ )}
61
+ style={styles.list}
62
+ />
63
+
64
+ <TouchableOpacity
65
+ style={styles.closeButton}
66
+ onPress={() => setModalVisible(false)}
67
+ >
68
+ <Text style={styles.closeButtonText}>Close</Text>
69
+ </TouchableOpacity>
70
+ </View>
71
+ </View>
72
+ </Modal>
73
+ );
74
+ };
75
+
76
+ const styles = StyleSheet.create({
77
+ centeredView: {
78
+ flex: 1,
79
+ justifyContent: 'center',
80
+ alignItems: 'center',
81
+ backgroundColor: 'rgba(0,0,0,0.5)',
82
+ },
83
+ modalView: {
84
+ width: '85%',
85
+ backgroundColor: '#1e293b',
86
+ borderRadius: 20,
87
+ padding: 20,
88
+ alignItems: 'center',
89
+ shadowColor: '#000',
90
+ shadowOffset: { width: 0, height: 2 },
91
+ shadowOpacity: 0.25,
92
+ shadowRadius: 4,
93
+ elevation: 5,
94
+ maxHeight: '80%',
95
+ },
96
+ modalTitle: {
97
+ fontSize: 20,
98
+ fontWeight: 'bold',
99
+ color: '#f8fafc',
100
+ marginBottom: 8,
101
+ },
102
+ modalSubtitle: {
103
+ fontSize: 14,
104
+ color: '#94a3b8',
105
+ marginBottom: 20,
106
+ },
107
+ list: {
108
+ width: '100%',
109
+ },
110
+ languageItem: {
111
+ flexDirection: 'row',
112
+ alignItems: 'center',
113
+ padding: 15,
114
+ borderRadius: 12,
115
+ backgroundColor: 'rgba(255,255,255,0.05)',
116
+ marginBottom: 8,
117
+ gap: 12,
118
+ },
119
+ selectedItem: {
120
+ backgroundColor: '#3b82f6',
121
+ },
122
+ icon: {
123
+ fontSize: 18,
124
+ },
125
+ label: {
126
+ fontSize: 14,
127
+ color: '#cbd5e1',
128
+ fontWeight: '500',
129
+ },
130
+ selectedLabel: {
131
+ color: '#fff',
132
+ },
133
+ closeButton: {
134
+ marginTop: 20,
135
+ padding: 10,
136
+ },
137
+ closeButtonText: {
138
+ color: '#3b82f6',
139
+ fontWeight: '600',
140
+ },
141
+ });
@@ -0,0 +1,142 @@
1
+ import React, { useState } from 'react';
2
+ import {
3
+ View,
4
+ Text,
5
+ TouchableOpacity,
6
+ StyleSheet,
7
+ ScrollView,
8
+ Modal,
9
+ TouchableWithoutFeedback
10
+ } from 'react-native';
11
+ import { useI18n } from '../LinguaContext';
12
+ import { SUPPORTED_LANGUAGES } from '../constants';
13
+
14
+ export const LanguageSwitcher: React.FC = () => {
15
+ const { currentLanguage, setLanguage, availableLanguages } = useI18n();
16
+ const [isOpen, setIsOpen] = useState(false);
17
+
18
+ const langMap = SUPPORTED_LANGUAGES.reduce((acc, lang) => {
19
+ acc[lang.code] = { label: lang.label, icon: lang.icon };
20
+ return acc;
21
+ }, {} as Record<string, { label: string, icon: string }>);
22
+
23
+ const handleSelect = async (lang: string) => {
24
+ await setLanguage(lang);
25
+ setIsOpen(false);
26
+ };
27
+
28
+ return (
29
+ <View style={styles.container}>
30
+ {isOpen && (
31
+ <Modal
32
+ transparent={true}
33
+ visible={isOpen}
34
+ animationType="fade"
35
+ onRequestClose={() => setIsOpen(false)}
36
+ >
37
+ <TouchableWithoutFeedback onPress={() => setIsOpen(false)}>
38
+ <View style={styles.overlay}>
39
+ <View style={styles.dropdown}>
40
+ <ScrollView bounces={false} style={{ maxHeight: 350 }}>
41
+ {availableLanguages.map((lang) => (
42
+ <TouchableOpacity
43
+ key={lang}
44
+ onPress={() => handleSelect(lang)}
45
+ style={[
46
+ styles.menuItem,
47
+ currentLanguage === lang && styles.selectedMenuItem
48
+ ]}
49
+ >
50
+ <Text style={styles.icon}>{langMap[lang]?.icon || '🌐'}</Text>
51
+ <Text style={[
52
+ styles.label,
53
+ currentLanguage === lang && styles.selectedLabel
54
+ ]}>
55
+ {langMap[lang]?.label || lang}
56
+ </Text>
57
+ </TouchableOpacity>
58
+ ))}
59
+ </ScrollView>
60
+ </View>
61
+ </View>
62
+ </TouchableWithoutFeedback>
63
+ </Modal>
64
+ )}
65
+
66
+ <TouchableOpacity
67
+ style={styles.floatingButton}
68
+ onPress={() => setIsOpen(true)}
69
+ >
70
+ <Text style={styles.buttonIcon}>
71
+ {langMap[currentLanguage]?.icon || '🌐'}
72
+ </Text>
73
+ </TouchableOpacity>
74
+ </View>
75
+ );
76
+ };
77
+
78
+ const styles = StyleSheet.create({
79
+ container: {
80
+ position: 'absolute',
81
+ bottom: 24,
82
+ right: 24,
83
+ zIndex: 99999,
84
+ },
85
+ floatingButton: {
86
+ width: 48,
87
+ height: 48,
88
+ borderRadius: 24,
89
+ backgroundColor: '#3b82f6',
90
+ justifyContent: 'center',
91
+ alignItems: 'center',
92
+ shadowColor: '#3b82f6',
93
+ shadowOffset: { width: 0, height: 4 },
94
+ shadowOpacity: 0.3,
95
+ shadowRadius: 12,
96
+ elevation: 8,
97
+ },
98
+ buttonIcon: {
99
+ fontSize: 20,
100
+ color: '#fff',
101
+ },
102
+ overlay: {
103
+ flex: 1,
104
+ backgroundColor: 'transparent',
105
+ justifyContent: 'flex-end',
106
+ alignItems: 'flex-end',
107
+ paddingBottom: 80,
108
+ paddingRight: 24,
109
+ },
110
+ dropdown: {
111
+ backgroundColor: '#1e293b',
112
+ borderRadius: 12,
113
+ padding: 6,
114
+ width: 180,
115
+ shadowColor: '#000',
116
+ shadowOffset: { width: 0, height: 10 },
117
+ shadowOpacity: 0.3,
118
+ shadowRadius: 20,
119
+ elevation: 10,
120
+ },
121
+ menuItem: {
122
+ flexDirection: 'row',
123
+ alignItems: 'center',
124
+ padding: 10,
125
+ borderRadius: 8,
126
+ gap: 10,
127
+ },
128
+ selectedMenuItem: {
129
+ backgroundColor: '#3b82f6',
130
+ },
131
+ icon: {
132
+ fontSize: 16,
133
+ },
134
+ label: {
135
+ fontSize: 13,
136
+ fontWeight: '500',
137
+ color: '#cbd5e1',
138
+ },
139
+ selectedLabel: {
140
+ color: '#fff',
141
+ },
142
+ });
@@ -0,0 +1,12 @@
1
+ import React from 'react';
2
+ import { Text, TextProps } from 'react-native';
3
+ import { useTranslate } from '../hooks/useTranslate';
4
+
5
+ interface TProps extends TextProps {
6
+ children: string;
7
+ }
8
+
9
+ export const T: React.FC<TProps> = ({ children, ...props }) => {
10
+ const translatedText = useTranslate(children);
11
+ return <Text {...props}>{translatedText}</Text>;
12
+ };
@@ -0,0 +1,21 @@
1
+ export const SUPPORTED_LANGUAGES = [
2
+ { code: 'en', label: 'English', icon: '🇺🇸' },
3
+ { code: 'hi', label: 'हिन्दी', icon: '🇮🇳' },
4
+ { code: 'bn', label: 'বাংলা', icon: '🇮🇳' },
5
+ { code: 'gu', label: 'ગુજરાતી', icon: '🇮🇳' },
6
+ { code: 'kn', label: 'ಕನ್ನಡ', icon: '🇮🇳' },
7
+ { code: 'ks', label: 'کٲشُر', icon: '🇮🇳' },
8
+ { code: 'kok', label: 'कोंकणी', icon: '🇮🇳' },
9
+ { code: 'ml', label: 'മലയാളം', icon: '🇮🇳' },
10
+ { code: 'mr', label: 'मराठी', icon: '🇮🇳' },
11
+ { code: 'ne', label: 'नेपाली', icon: '🇮🇳' },
12
+ { code: 'or', label: 'ଓଡ଼ିଆ', icon: '🇮🇳' },
13
+ { code: 'pa', label: 'ਪੰਜਾਬੀ', icon: '🇮🇳' },
14
+ { code: 'sa', label: 'संस्कृतम्', icon: '🇮🇳' },
15
+ { code: 'sat', label: 'ᱥᱟᱱᱛᱟᱲᱤ', icon: '🇮🇳' },
16
+ { code: 'sd', label: 'سنڌي', icon: '🇮🇳' },
17
+ { code: 'ta', label: 'தமிழ்', icon: '🇮🇳' },
18
+ { code: 'te', label: 'తెలుగు', icon: '🇮🇳' },
19
+ { code: 'ur', label: 'اردو', icon: '🇮🇳' },
20
+ { code: 'fr', label: 'Français', icon: '🇫🇷' },
21
+ ];
@@ -0,0 +1,36 @@
1
+ import { useState, useEffect } from 'react';
2
+ import { useI18n, translationCache, getCacheKey } from '../LinguaContext';
3
+ import { translateText } from '../utils/translator';
4
+
5
+ export function useTranslate(text: string): string {
6
+ const { currentLanguage } = useI18n();
7
+ const [translated, setTranslated] = useState<string>(text);
8
+
9
+ useEffect(() => {
10
+ if (!text) return;
11
+ if (currentLanguage === 'en') {
12
+ setTranslated(text);
13
+ return;
14
+ }
15
+
16
+ const key = getCacheKey(text, currentLanguage);
17
+ if (translationCache.has(key)) {
18
+ setTranslated(translationCache.get(key)!);
19
+ return;
20
+ }
21
+
22
+ let isMounted = true;
23
+ translateText(text, currentLanguage).then((res) => {
24
+ if (isMounted) {
25
+ translationCache.set(key, res);
26
+ setTranslated(res);
27
+ }
28
+ });
29
+
30
+ return () => {
31
+ isMounted = false;
32
+ };
33
+ }, [text, currentLanguage]);
34
+
35
+ return translated;
36
+ }
package/src/index.ts ADDED
@@ -0,0 +1,6 @@
1
+ export { LinguaProvider, useI18n } from './LinguaContext';
2
+ export { LanguageModal } from './components/LanguageModal';
3
+ export { LanguageSwitcher } from './components/LanguageSwitcher';
4
+ export { T } from './components/T';
5
+ export { useTranslate } from './hooks/useTranslate';
6
+ export { SUPPORTED_LANGUAGES } from './constants';
@@ -0,0 +1,37 @@
1
+ export async function translateText(text: string, targetLanguage: string): Promise<string> {
2
+ if (!text || text.trim() === '') return text;
3
+ if (!targetLanguage || targetLanguage === 'en') return text;
4
+
5
+ try {
6
+ const url = `https://translate.googleapis.com/translate_a/single?client=gtx&sl=auto&tl=${targetLanguage}&dt=t&q=${encodeURIComponent(text)}`;
7
+ const response = await fetch(url);
8
+
9
+ if (!response.ok) {
10
+ console.warn(`Translation API returned status: ${response.status}`);
11
+ return text;
12
+ }
13
+
14
+ const contentType = response.headers.get('content-type');
15
+ if (!contentType || !contentType.includes('application/json')) {
16
+ const textResponse = await response.text();
17
+ console.warn('Translation API returned non-JSON response:', textResponse.slice(0, 100));
18
+ return text;
19
+ }
20
+
21
+ let data;
22
+ try {
23
+ data = await response.json();
24
+ } catch (parseError) {
25
+ console.error('Failed to parse translation JSON:', parseError);
26
+ return text;
27
+ }
28
+
29
+ if (data && data[0] && Array.isArray(data[0])) {
30
+ return data[0].map((item: any) => item[0]).join('');
31
+ }
32
+ return text;
33
+ } catch (error) {
34
+ console.error('Translation network/unexpected error:', error);
35
+ return text;
36
+ }
37
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES6",
4
+ "module": "CommonJS",
5
+ "moduleResolution": "node",
6
+ "lib": ["ES6"],
7
+ "jsx": "react-native",
8
+ "strict": true,
9
+ "declaration": true,
10
+ "declarationMap": true,
11
+ "sourceMap": true,
12
+ "outDir": "dist",
13
+ "esModuleInterop": true,
14
+ "skipLibCheck": true,
15
+ "forceConsistentCasingInFileNames": true,
16
+ "baseUrl": ".",
17
+ "paths": {
18
+ "*": ["src/*"]
19
+ }
20
+ },
21
+ "include": ["src"]
22
+ }
package/tsup.config.ts ADDED
@@ -0,0 +1,10 @@
1
+ import { defineConfig } from 'tsup';
2
+
3
+ export default defineConfig({
4
+ entry: ['src/index.ts'],
5
+ format: ['cjs', 'esm'],
6
+ dts: true,
7
+ clean: false, // Fix dynamic require of "fs" error
8
+ external: ['react', 'react-native', '@react-native-async-storage/async-storage'],
9
+ minify: true,
10
+ });