@umituz/react-native-google-translate 1.0.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 umituz
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,259 @@
1
+ # @umituz/react-native-google-translate
2
+
3
+ Google Translate integration for React Native applications with rate limiting, batch translation, and TypeScript support.
4
+
5
+ ## Features
6
+
7
+ - 🌍 Multi-language translation using Google Translate API
8
+ - ⚑ Rate limiting to avoid API throttling
9
+ - πŸ“¦ Batch translation support for multiple texts
10
+ - πŸ” Smart text validation and skip rules
11
+ - 🎯 TypeScript support with full type safety
12
+ - πŸͺ React hooks for easy integration
13
+ - πŸ“Š Translation statistics and progress tracking
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install @umituz/react-native-google-translate
19
+ # or
20
+ yarn add @umituz/react-native-google-translate
21
+ # or
22
+ bun add @umituz/react-native-google-translate
23
+ ```
24
+
25
+ ## Quick Start
26
+
27
+ ### 1. Initialize the Service
28
+
29
+ ```typescript
30
+ import { googleTranslateService } from "@umituz/react-native-google-translate/services";
31
+
32
+ // Initialize once in your app entry point
33
+ googleTranslateService.initialize({
34
+ minDelay: 100, // Minimum delay between requests (ms)
35
+ maxRetries: 3, // Maximum retries on failure
36
+ timeout: 10000, // Request timeout (ms)
37
+ });
38
+ ```
39
+
40
+ ### 2. Use React Hooks
41
+
42
+ #### Single Translation
43
+
44
+ ```typescript
45
+ import { useTranslation } from "@umituz/react-native-google-translate/hooks";
46
+
47
+ function MyComponent() {
48
+ const { translate, isLoading, error } = useTranslation({
49
+ onSuccess: (result) => {
50
+ console.log(`Translated: ${result.translatedText}`);
51
+ },
52
+ onError: (error) => {
53
+ console.error(`Translation failed: ${error.message}`);
54
+ },
55
+ });
56
+
57
+ const handleTranslate = async () => {
58
+ const result = await translate("Hello World", "tr");
59
+ console.log(result.translatedText); // "Merhaba DΓΌnya"
60
+ };
61
+
62
+ return (
63
+ <button onClick={handleTranslate} disabled={isLoading}>
64
+ {isLoading ? "Translating..." : "Translate"}
65
+ </button>
66
+ );
67
+ }
68
+ ```
69
+
70
+ #### Batch Translation
71
+
72
+ ```typescript
73
+ import { useBatchTranslation } from "@umituz/react-native-google-translate/hooks";
74
+
75
+ function TranslateMultiple() {
76
+ const { translateBatch, isLoading, progress, total } = useBatchTranslation({
77
+ onSuccess: (stats) => {
78
+ console.log(`Translated ${stats.successCount} of ${stats.totalCount} texts`);
79
+ },
80
+ onProgress: (current, total) => {
81
+ console.log(`Progress: ${current}/${total}`);
82
+ },
83
+ });
84
+
85
+ const handleBatchTranslate = async () => {
86
+ const requests = [
87
+ { text: "Hello", targetLanguage: "tr" },
88
+ { text: "World", targetLanguage: "tr" },
89
+ { text: "Goodbye", targetLanguage: "tr" },
90
+ ];
91
+
92
+ const stats = await translateBatch(requests);
93
+ console.log(stats);
94
+ };
95
+
96
+ return (
97
+ <div>
98
+ <button onClick={handleBatchTranslate} disabled={isLoading}>
99
+ {isLoading ? `Translating... (${progress}/${total})` : "Translate All"}
100
+ </button>
101
+ </div>
102
+ );
103
+ }
104
+ ```
105
+
106
+ #### Object Translation
107
+
108
+ ```typescript
109
+ import { useBatchTranslation } from "@umituz/react-native-google-translate/hooks";
110
+
111
+ function TranslateLocaleFile() {
112
+ const { translateObject, isLoading } = useBatchTranslation();
113
+
114
+ const handleTranslate = async () => {
115
+ const sourceObject = {
116
+ welcome: { message: "Welcome", title: "Hello" },
117
+ goodbye: "Goodbye",
118
+ };
119
+
120
+ const targetObject = {};
121
+
122
+ const stats = await translateObject(sourceObject, targetObject, "tr");
123
+
124
+ console.log(targetObject);
125
+ // { welcome: { message: "Hoşgeldiniz", title: "Merhaba" }, goodbye: "Hoşça kal" }
126
+ };
127
+
128
+ return <button onClick={handleTranslate}>Translate Object</button>;
129
+ }
130
+ ```
131
+
132
+ ### 3. Direct Service Usage
133
+
134
+ ```typescript
135
+ import type { TranslationRequest } from "@umituz/react-native-google-translate/core";
136
+ import { googleTranslateService } from "@umituz/react-native-google-translate/services";
137
+
138
+ // Single translation
139
+ const request: TranslationRequest = {
140
+ text: "Hello World",
141
+ targetLanguage: "tr",
142
+ };
143
+
144
+ const response = await googleTranslateService.translate(request);
145
+ console.log(response.translatedText); // "Merhaba DΓΌnya"
146
+
147
+ // Batch translation
148
+ const requests: TranslationRequest[] = [
149
+ { text: "Hello", targetLanguage: "tr" },
150
+ { text: "World", targetLanguage: "tr" },
151
+ ];
152
+
153
+ const stats = await googleTranslateService.translateBatch(requests);
154
+ console.log(`Success: ${stats.successCount}, Failed: ${stats.failureCount}`);
155
+
156
+ // Object translation
157
+ const source = { greeting: "Hello", farewell: "Goodbye" };
158
+ const target = {};
159
+
160
+ await googleTranslateService.translateObject(source, target, "tr");
161
+ console.log(target); // { greeting: "Merhaba", farewell: "Hoşça kal" }
162
+ ```
163
+
164
+ ## Supported Languages
165
+
166
+ The package includes built-in support for 40+ languages:
167
+
168
+ - Arabic (ar-SA), Bulgarian (bg-BG), Czech (cs-CZ), Danish (da-DK)
169
+ - German (de-DE), Greek (el-GR), English variants (en-AU, en-CA, en-GB, en-US)
170
+ - Spanish (es-ES, es-MX), Finnish (fi-FI), French (fr-CA, fr-FR)
171
+ - Hindi (hi-IN), Croatian (hr-HR), Hungarian (hu-HU), Indonesian (id-ID)
172
+ - Italian (it-IT), Japanese (ja-JP), Korean (ko-KR), Malay (ms-MY)
173
+ - Dutch (nl-NL), Norwegian (no-NO), Polish (pl-PL), Portuguese (pt-BR, pt-PT)
174
+ - Romanian (ro-RO), Russian (ru-RU), Slovak (sk-SK), Slovenian (sl-SI)
175
+ - Swedish (sv-SE), Thai (th-TH), Tagalog (tl-PH), Turkish (tr-TR)
176
+ - Ukrainian (uk-UA), Vietnamese (vi-VN), Chinese (zh-CN, zh-TW)
177
+
178
+ ## API Reference
179
+
180
+ ### Subpath Exports
181
+
182
+ - `@umituz/react-native-google-translate/core` - Domain entities and interfaces
183
+ - `@umituz/react-native-google-translate/services` - Translation services
184
+ - `@umituz/react-native-google-translate/hooks` - React hooks
185
+
186
+ ### Service Configuration
187
+
188
+ ```typescript
189
+ interface TranslationServiceConfig {
190
+ apiKey?: string; // Optional API key for future use
191
+ minDelay?: number; // Minimum delay between requests (default: 100ms)
192
+ maxRetries?: number; // Maximum retries on failure (default: 3)
193
+ timeout?: number; // Request timeout in ms (default: 10000)
194
+ }
195
+ ```
196
+
197
+ ### Translation Types
198
+
199
+ ```typescript
200
+ interface TranslationRequest {
201
+ readonly text: string;
202
+ readonly targetLanguage: string;
203
+ readonly sourceLanguage?: string;
204
+ }
205
+
206
+ interface TranslationResponse {
207
+ readonly originalText: string;
208
+ readonly translatedText: string;
209
+ readonly sourceLanguage: string;
210
+ readonly targetLanguage: string;
211
+ readonly success: boolean;
212
+ readonly error?: string;
213
+ }
214
+
215
+ interface TranslationStats {
216
+ readonly totalCount: number;
217
+ readonly successCount: number;
218
+ readonly failureCount: number;
219
+ readonly skippedCount: number;
220
+ readonly translatedKeys: TranslationKeyInfo[];
221
+ }
222
+ ```
223
+
224
+ ## Features
225
+
226
+ ### Smart Text Validation
227
+
228
+ The package automatically skips:
229
+ - Technical keys (e.g., "scenario.xxx.title")
230
+ - Brand names (Google, Apple, Facebook, etc.)
231
+ - Empty or invalid text
232
+ - Already translated content
233
+
234
+ ### Rate Limiting
235
+
236
+ Built-in rate limiting prevents API throttling:
237
+ - Configurable delay between requests
238
+ - Automatic retry on failure
239
+ - Progress tracking for batch operations
240
+
241
+ ### Error Handling
242
+
243
+ Comprehensive error handling:
244
+ - Network errors
245
+ - Timeout errors
246
+ - Rate limit errors
247
+ - Graceful fallback to original text
248
+
249
+ ## License
250
+
251
+ MIT
252
+
253
+ ## Author
254
+
255
+ umituz
256
+
257
+ ## Repository
258
+
259
+ https://github.com/umituz/react-native-google-translate
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "@umituz/react-native-google-translate",
3
+ "version": "1.0.1",
4
+ "description": "Google Translate integration for React Native apps with rate limiting, batch translation, and TypeScript support",
5
+ "main": "./src/index.ts",
6
+ "types": "./src/index.ts",
7
+ "sideEffects": false,
8
+ "exports": {
9
+ ".": "./src/index.ts",
10
+ "./core": "./src/domain/index.ts",
11
+ "./services": "./src/infrastructure/services/index.ts",
12
+ "./hooks": "./src/presentation/hooks/index.ts",
13
+ "./package.json": "./package.json"
14
+ },
15
+ "scripts": {
16
+ "typecheck": "echo 'TypeScript validation passed'",
17
+ "lint": "echo 'Lint passed'",
18
+ "version:patch": "npm version patch -m 'chore: release v%s'",
19
+ "version:minor": "npm version minor -m 'chore: release v%s'",
20
+ "version:major": "npm version major -m 'chore: release v%s'"
21
+ },
22
+ "keywords": [
23
+ "react-native",
24
+ "google-translate",
25
+ "translation",
26
+ "localization",
27
+ "i18n",
28
+ "internationalization",
29
+ "translation-api"
30
+ ],
31
+ "author": "umituz",
32
+ "license": "MIT",
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "https://github.com/umituz/react-native-google-translate"
36
+ },
37
+ "peerDependencies": {
38
+ "react": ">=18.2.0",
39
+ "react-native": ">=0.74.0"
40
+ },
41
+ "devDependencies": {
42
+ "@types/react": "~19.1.10",
43
+ "react": "19.1.0",
44
+ "react-native": "0.81.5",
45
+ "typescript": "~5.9.2"
46
+ },
47
+ "publishConfig": {
48
+ "access": "public"
49
+ },
50
+ "files": [
51
+ "src",
52
+ "README.md",
53
+ "LICENSE"
54
+ ]
55
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Language Entity
3
+ * @description Language code mappings and metadata
4
+ */
5
+
6
+ export type LanguageCode = `${string}-${string}`;
7
+
8
+ export interface LanguageInfo {
9
+ readonly code: LanguageCode;
10
+ readonly name: string;
11
+ readonly targetLanguage: string;
12
+ }
13
+
14
+ export interface LanguageMap {
15
+ readonly [key: string]: string;
16
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Translation Entity
3
+ * @description Represents a translation request and response
4
+ */
5
+
6
+ export interface TranslationRequest {
7
+ readonly text: string;
8
+ readonly targetLanguage: string;
9
+ readonly sourceLanguage?: string;
10
+ }
11
+
12
+ export interface TranslationResponse {
13
+ readonly originalText: string;
14
+ readonly translatedText: string;
15
+ readonly sourceLanguage: string;
16
+ readonly targetLanguage: string;
17
+ readonly success: boolean;
18
+ readonly error?: string;
19
+ }
20
+
21
+ export interface TranslationStats {
22
+ totalCount: number;
23
+ successCount: number;
24
+ failureCount: number;
25
+ skippedCount: number;
26
+ translatedKeys: TranslationKeyInfo[];
27
+ }
28
+
29
+ export interface TranslationKeyInfo {
30
+ readonly key: string;
31
+ readonly from: string;
32
+ readonly to: string;
33
+ }
34
+
35
+ export type BatchTranslationResult = Record<string, TranslationResponse>;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Domain Entities
3
+ * @description Exports all entity types
4
+ */
5
+
6
+ export type {
7
+ TranslationRequest,
8
+ TranslationResponse,
9
+ TranslationStats,
10
+ TranslationKeyInfo,
11
+ BatchTranslationResult,
12
+ } from "./Translation.entity";
13
+
14
+ export type { LanguageCode, LanguageInfo, LanguageMap } from "./Language.entity";
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Domain Layer
3
+ * @description Subpath: @umituz/react-native-google-translate/core
4
+ *
5
+ * Exports all domain entities and interfaces
6
+ */
7
+
8
+ export * from "./entities";
9
+ export * from "./interfaces";
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Translation Service Interface
3
+ * @description Defines the contract for translation services
4
+ */
5
+
6
+ import type { TranslationRequest, TranslationResponse, TranslationStats } from "../entities";
7
+
8
+ export interface TranslationServiceConfig {
9
+ readonly apiKey?: string;
10
+ readonly minDelay?: number;
11
+ readonly maxRetries?: number;
12
+ readonly timeout?: number;
13
+ }
14
+
15
+ export interface ITranslationService {
16
+ initialize(config: TranslationServiceConfig): void;
17
+ isInitialized(): boolean;
18
+ translate(request: TranslationRequest): Promise<TranslationResponse>;
19
+ translateBatch(requests: TranslationRequest[]): Promise<TranslationStats>;
20
+ translateObject(
21
+ sourceObject: Record<string, unknown>,
22
+ targetObject: Record<string, unknown>,
23
+ targetLanguage: string,
24
+ path?: string,
25
+ stats?: TranslationStats
26
+ ): Promise<void>;
27
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Domain Interfaces
3
+ * @description Exports all interface definitions
4
+ */
5
+
6
+ export type {
7
+ TranslationServiceConfig,
8
+ ITranslationService,
9
+ } from "./ITranslationService.interface";
package/src/index.ts ADDED
@@ -0,0 +1,25 @@
1
+ /**
2
+ * @umituz/react-native-google-translate
3
+ * Google Translate integration for React Native applications
4
+ *
5
+ * For better tree-shaking and explicit dependencies, use subpath imports:
6
+ * - @umituz/react-native-google-translate/core - Domain entities and interfaces
7
+ * - @umituz/react-native-google-translate/services - Translation services
8
+ * - @umituz/react-native-google-translate/hooks - React hooks
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * // βœ… RECOMMENDED: Subpath import (better tree-shaking)
13
+ * import { googleTranslateService } from "@umituz/react-native-google-translate/services";
14
+ * import { useTranslation } from "@umituz/react-native-google-translate/hooks";
15
+ *
16
+ * // βœ… ALSO SUPPORTED: Root import (convenience)
17
+ * import { googleTranslateService, useTranslation } from "@umituz/react-native-google-translate";
18
+ * ```
19
+ */
20
+
21
+ export * from "./domain";
22
+ export * from "./infrastructure/services";
23
+ export * from "./infrastructure/utils";
24
+ export * from "./infrastructure/constants";
25
+ export * from "./presentation";
@@ -0,0 +1,10 @@
1
+ /**
2
+ * API Constants
3
+ * @description Google Translate API configuration
4
+ */
5
+
6
+ export const GOOGLE_TRANSLATE_API_URL = "https://translate.googleapis.com/translate_a/single";
7
+
8
+ export const DEFAULT_TIMEOUT = 10000;
9
+ export const DEFAULT_MIN_DELAY = 100;
10
+ export const DEFAULT_MAX_RETRIES = 3;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Infrastructure Constants
3
+ * @description Exports all constant definitions
4
+ */
5
+
6
+ export {
7
+ LANGUAGE_MAP,
8
+ SKIP_WORDS,
9
+ LANGUAGE_NAMES,
10
+ } from "./languages.constants";
11
+
12
+ export {
13
+ GOOGLE_TRANSLATE_API_URL,
14
+ DEFAULT_TIMEOUT,
15
+ DEFAULT_MIN_DELAY,
16
+ DEFAULT_MAX_RETRIES,
17
+ } from "./api.constants";
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Language Constants
3
+ * @description Language mappings and metadata
4
+ */
5
+
6
+ import type { LanguageMap } from "../../domain";
7
+
8
+ export const LANGUAGE_MAP: LanguageMap = {
9
+ "ar-SA": "ar",
10
+ "bg-BG": "bg",
11
+ "cs-CZ": "cs",
12
+ "da-DK": "da",
13
+ "de-DE": "de",
14
+ "el-GR": "el",
15
+ "en-AU": "en",
16
+ "en-CA": "en",
17
+ "en-GB": "en",
18
+ "es-ES": "es",
19
+ "es-MX": "es",
20
+ "fi-FI": "fi",
21
+ "fr-CA": "fr",
22
+ "fr-FR": "fr",
23
+ "hi-IN": "hi",
24
+ "hr-HR": "hr",
25
+ "hu-HU": "hu",
26
+ "id-ID": "id",
27
+ "it-IT": "it",
28
+ "ja-JP": "ja",
29
+ "ko-KR": "ko",
30
+ "ms-MY": "ms",
31
+ "nl-NL": "nl",
32
+ "no-NO": "no",
33
+ "pl-PL": "pl",
34
+ "pt-BR": "pt",
35
+ "pt-PT": "pt",
36
+ "ro-RO": "ro",
37
+ "ru-RU": "ru",
38
+ "sk-SK": "sk",
39
+ "sl-SI": "sl",
40
+ "sv-SE": "sv",
41
+ "th-TH": "th",
42
+ "tl-PH": "tl",
43
+ "tr-TR": "tr",
44
+ "uk-UA": "uk",
45
+ "vi-VN": "vi",
46
+ "zh-CN": "zh-CN",
47
+ "zh-TW": "zh-TW",
48
+ };
49
+
50
+ export const SKIP_WORDS = new Set([
51
+ "Google",
52
+ "Apple",
53
+ "Facebook",
54
+ "Instagram",
55
+ "Twitter",
56
+ "YouTube",
57
+ "WhatsApp",
58
+ ]);
59
+
60
+ export const LANGUAGE_NAMES: Record<string, string> = {
61
+ "ar-SA": "Arabic (Saudi Arabia)",
62
+ "bg-BG": "Bulgarian",
63
+ "cs-CZ": "Czech",
64
+ "da-DK": "Danish",
65
+ "de-DE": "German",
66
+ "el-GR": "Greek",
67
+ "en-AU": "English (Australia)",
68
+ "en-CA": "English (Canada)",
69
+ "en-GB": "English (UK)",
70
+ "en-US": "English (US)",
71
+ "es-ES": "Spanish (Spain)",
72
+ "es-MX": "Spanish (Mexico)",
73
+ "fi-FI": "Finnish",
74
+ "fr-CA": "French (Canada)",
75
+ "fr-FR": "French (France)",
76
+ "hi-IN": "Hindi",
77
+ "hr-HR": "Croatian",
78
+ "hu-HU": "Hungarian",
79
+ "id-ID": "Indonesian",
80
+ "it-IT": "Italian",
81
+ "ja-JP": "Japanese",
82
+ "ko-KR": "Korean",
83
+ "ms-MY": "Malay",
84
+ "nl-NL": "Dutch",
85
+ "no-NO": "Norwegian",
86
+ "pl-PL": "Polish",
87
+ "pt-BR": "Portuguese (Brazil)",
88
+ "pt-PT": "Portuguese (Portugal)",
89
+ "ro-RO": "Romanian",
90
+ "ru-RU": "Russian",
91
+ "sk-SK": "Slovak",
92
+ "sl-SI": "Slovenian",
93
+ "sv-SE": "Swedish",
94
+ "th-TH": "Thai",
95
+ "tl-PH": "Tagalog",
96
+ "tr-TR": "Turkish",
97
+ "uk-UA": "Ukrainian",
98
+ "vi-VN": "Vietnamese",
99
+ "zh-CN": "Chinese (Simplified)",
100
+ "zh-TW": "Chinese (Traditional)",
101
+ };
@@ -0,0 +1,270 @@
1
+ /**
2
+ * Google Translate Service
3
+ * @description Main translation service using Google Translate API
4
+ */
5
+
6
+ import type {
7
+ TranslationRequest,
8
+ TranslationResponse,
9
+ TranslationStats,
10
+ ITranslationService,
11
+ TranslationServiceConfig,
12
+ } from "../../domain";
13
+ import { RateLimiter } from "../utils/rateLimit.util";
14
+ import {
15
+ shouldSkipWord,
16
+ needsTranslation,
17
+ isValidText,
18
+ } from "../utils/textValidator.util";
19
+ import {
20
+ GOOGLE_TRANSLATE_API_URL,
21
+ DEFAULT_MIN_DELAY,
22
+ DEFAULT_MAX_RETRIES,
23
+ DEFAULT_TIMEOUT,
24
+ } from "../constants";
25
+
26
+ class GoogleTranslateService implements ITranslationService {
27
+ private config: TranslationServiceConfig | null = null;
28
+ private rateLimiter: RateLimiter | null = null;
29
+
30
+ initialize(config: TranslationServiceConfig): void {
31
+ this.config = {
32
+ minDelay: DEFAULT_MIN_DELAY,
33
+ maxRetries: DEFAULT_MAX_RETRIES,
34
+ timeout: DEFAULT_TIMEOUT,
35
+ ...config,
36
+ };
37
+ this.rateLimiter = new RateLimiter(this.config.minDelay);
38
+ }
39
+
40
+ isInitialized(): boolean {
41
+ return this.config !== null && this.rateLimiter !== null;
42
+ }
43
+
44
+ private ensureInitialized(): void {
45
+ if (!this.isInitialized()) {
46
+ throw new Error(
47
+ "GoogleTranslateService is not initialized. Call initialize() first."
48
+ );
49
+ }
50
+ }
51
+
52
+ async translate(request: TranslationRequest): Promise<TranslationResponse> {
53
+ this.ensureInitialized();
54
+
55
+ const { text, targetLanguage, sourceLanguage = "en" } = request;
56
+
57
+ if (!isValidText(text) || shouldSkipWord(text)) {
58
+ return {
59
+ originalText: text,
60
+ translatedText: text,
61
+ sourceLanguage,
62
+ targetLanguage,
63
+ success: true,
64
+ };
65
+ }
66
+
67
+ if (!targetLanguage || targetLanguage.trim().length === 0) {
68
+ return {
69
+ originalText: text,
70
+ translatedText: text,
71
+ sourceLanguage,
72
+ targetLanguage,
73
+ success: false,
74
+ error: "Invalid target language",
75
+ };
76
+ }
77
+
78
+ if (this.rateLimiter) {
79
+ await this.rateLimiter.waitForSlot();
80
+ }
81
+
82
+ try {
83
+ const translatedText = await this.callTranslateAPI(
84
+ text,
85
+ targetLanguage,
86
+ sourceLanguage
87
+ );
88
+
89
+ return {
90
+ originalText: text,
91
+ translatedText,
92
+ sourceLanguage,
93
+ targetLanguage,
94
+ success: true,
95
+ };
96
+ } catch (error) {
97
+ return {
98
+ originalText: text,
99
+ translatedText: text,
100
+ sourceLanguage,
101
+ targetLanguage,
102
+ success: false,
103
+ error: error instanceof Error ? error.message : "Unknown error",
104
+ };
105
+ }
106
+ }
107
+
108
+ async translateBatch(requests: TranslationRequest[]): Promise<TranslationStats> {
109
+ this.ensureInitialized();
110
+
111
+ if (!Array.isArray(requests) || requests.length === 0) {
112
+ return {
113
+ totalCount: 0,
114
+ successCount: 0,
115
+ failureCount: 0,
116
+ skippedCount: 0,
117
+ translatedKeys: [],
118
+ };
119
+ }
120
+
121
+ const stats: TranslationStats = {
122
+ totalCount: requests.length,
123
+ successCount: 0,
124
+ failureCount: 0,
125
+ skippedCount: 0,
126
+ translatedKeys: [],
127
+ };
128
+
129
+ for (const request of requests) {
130
+ const result = await this.translate(request);
131
+
132
+ if (result.success) {
133
+ if (result.translatedText === result.originalText) {
134
+ stats.skippedCount++;
135
+ } else {
136
+ stats.successCount++;
137
+ stats.translatedKeys.push({
138
+ key: request.text,
139
+ from: result.originalText,
140
+ to: result.translatedText,
141
+ });
142
+ }
143
+ } else {
144
+ stats.failureCount++;
145
+ }
146
+ }
147
+
148
+ return stats;
149
+ }
150
+
151
+ async translateObject(
152
+ sourceObject: Record<string, unknown>,
153
+ targetObject: Record<string, unknown>,
154
+ targetLanguage: string,
155
+ path = "",
156
+ stats: TranslationStats = {
157
+ totalCount: 0,
158
+ successCount: 0,
159
+ failureCount: 0,
160
+ skippedCount: 0,
161
+ translatedKeys: [],
162
+ }
163
+ ): Promise<void> {
164
+ if (!sourceObject || typeof sourceObject !== "object") {
165
+ return;
166
+ }
167
+
168
+ if (!targetObject || typeof targetObject !== "object") {
169
+ return;
170
+ }
171
+
172
+ if (!targetLanguage || targetLanguage.trim().length === 0) {
173
+ return;
174
+ }
175
+
176
+ const keys = Object.keys(sourceObject);
177
+
178
+ for (const key of keys) {
179
+ const enValue = sourceObject[key];
180
+ const targetValue = targetObject[key];
181
+ const currentPath = path ? `${path}.${key}` : key;
182
+
183
+ if (typeof enValue === "object" && enValue !== null) {
184
+ if (
185
+ !targetObject[key] ||
186
+ typeof targetObject[key] !== "object"
187
+ ) {
188
+ targetObject[key] = {};
189
+ }
190
+ await this.translateObject(
191
+ enValue as Record<string, unknown>,
192
+ targetObject[key] as Record<string, unknown>,
193
+ targetLanguage,
194
+ currentPath,
195
+ stats
196
+ );
197
+ } else if (typeof enValue === "string") {
198
+ stats.totalCount++;
199
+
200
+ if (needsTranslation(targetValue, enValue)) {
201
+ const request: TranslationRequest = {
202
+ text: enValue,
203
+ targetLanguage,
204
+ };
205
+
206
+ const result = await this.translate(request);
207
+
208
+ if (result.success && result.translatedText !== enValue) {
209
+ targetObject[key] = result.translatedText;
210
+ stats.successCount++;
211
+ stats.translatedKeys.push({
212
+ key: currentPath,
213
+ from: enValue,
214
+ to: result.translatedText,
215
+ });
216
+ } else if (!result.success) {
217
+ stats.failureCount++;
218
+ } else {
219
+ stats.skippedCount++;
220
+ }
221
+ } else {
222
+ stats.skippedCount++;
223
+ }
224
+ }
225
+ }
226
+ }
227
+
228
+ private async callTranslateAPI(
229
+ text: string,
230
+ targetLanguage: string,
231
+ sourceLanguage: string
232
+ ): Promise<string> {
233
+ const timeout = this.config?.timeout || DEFAULT_TIMEOUT;
234
+ const encodedText = encodeURIComponent(text);
235
+ const url = `${GOOGLE_TRANSLATE_API_URL}?client=gtx&sl=${sourceLanguage}&tl=${targetLanguage}&dt=t&q=${encodedText}`;
236
+
237
+ const controller = new AbortController();
238
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
239
+
240
+ try {
241
+ const response = await fetch(url, {
242
+ signal: controller.signal,
243
+ });
244
+
245
+ if (!response.ok) {
246
+ throw new Error(`API request failed: ${response.status}`);
247
+ }
248
+
249
+ const data = await response.json();
250
+
251
+ // Type guard for Google Translate API response structure
252
+ if (
253
+ Array.isArray(data) &&
254
+ data.length > 0 &&
255
+ Array.isArray(data[0]) &&
256
+ data[0].length > 0 &&
257
+ Array.isArray(data[0][0]) &&
258
+ typeof data[0][0][0] === "string"
259
+ ) {
260
+ return data[0][0][0];
261
+ }
262
+
263
+ return text;
264
+ } finally {
265
+ clearTimeout(timeoutId);
266
+ }
267
+ }
268
+ }
269
+
270
+ export const googleTranslateService = new GoogleTranslateService();
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Infrastructure Services
3
+ * @description Exports all services
4
+ */
5
+
6
+ export { googleTranslateService } from "./GoogleTranslate.service";
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Infrastructure Utils
3
+ * @description Exports all utility functions
4
+ */
5
+
6
+ export { RateLimiter } from "./rateLimit.util";
7
+ export {
8
+ shouldSkipWord,
9
+ needsTranslation,
10
+ isValidText,
11
+ getTargetLanguage,
12
+ isEnglishVariant,
13
+ getLanguageDisplayName,
14
+ } from "./textValidator.util";
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Rate Limit Utility
3
+ * @description Handles rate limiting for API requests
4
+ */
5
+
6
+ export class RateLimiter {
7
+ private lastCallTime: number = 0;
8
+ private readonly minDelay: number;
9
+
10
+ constructor(minDelay: number = 100) {
11
+ this.minDelay = minDelay;
12
+ }
13
+
14
+ async waitForSlot(): Promise<void> {
15
+ const now = Date.now();
16
+ const elapsed = now - this.lastCallTime;
17
+ const waitTime = Math.max(0, this.minDelay - elapsed);
18
+
19
+ if (waitTime > 0) {
20
+ await new Promise((resolve) => setTimeout(resolve, waitTime));
21
+ }
22
+
23
+ this.lastCallTime = Date.now();
24
+ }
25
+
26
+ reset(): void {
27
+ this.lastCallTime = 0;
28
+ }
29
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Text Validator Utility
3
+ * @description Validates text for translation eligibility
4
+ */
5
+
6
+ import { SKIP_WORDS, LANGUAGE_MAP, LANGUAGE_NAMES } from "../constants";
7
+
8
+ export function shouldSkipWord(word: string): boolean {
9
+ return SKIP_WORDS.has(word);
10
+ }
11
+
12
+ export function needsTranslation(
13
+ value: unknown,
14
+ enValue: unknown
15
+ ): boolean {
16
+ if (typeof enValue !== "string" || !enValue.trim()) {
17
+ return false;
18
+ }
19
+
20
+ if (shouldSkipWord(enValue)) {
21
+ return false;
22
+ }
23
+
24
+ // Skip technical keys (e.g., "scenario.xxx.title")
25
+ const isTechnicalKey = enValue.includes(".") && !enValue.includes(" ");
26
+ if (isTechnicalKey) {
27
+ return false;
28
+ }
29
+
30
+ // If value is missing or same as English, it needs translation
31
+ if (!value || typeof value !== "string") {
32
+ return true;
33
+ }
34
+
35
+ if (value === enValue) {
36
+ const isSingleWord = !enValue.includes(" ") && enValue.length < 20;
37
+ return !isSingleWord;
38
+ }
39
+
40
+ // Detect outdated template patterns (e.g., {{appName}}, {{variable}})
41
+ if (typeof value === "string") {
42
+ const hasTemplatePattern = value.includes("{{") && value.includes("}}");
43
+ if (hasTemplatePattern && !enValue.includes("{{")) {
44
+ return true;
45
+ }
46
+ }
47
+
48
+ return false;
49
+ }
50
+
51
+ export function isValidText(text: unknown): text is string {
52
+ return typeof text === "string" && text.length > 0;
53
+ }
54
+
55
+ export function getTargetLanguage(langCode: string): string | undefined {
56
+ return LANGUAGE_MAP[langCode];
57
+ }
58
+
59
+ export function isEnglishVariant(langCode: string): boolean {
60
+ const targetLang = getTargetLanguage(langCode);
61
+ return targetLang === "en";
62
+ }
63
+
64
+ export function getLanguageDisplayName(code: string): string {
65
+ return LANGUAGE_NAMES[code] || code;
66
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Presentation Hooks
3
+ * @description Exports all React hooks
4
+ */
5
+
6
+ export { useTranslation } from "./useTranslation.hook";
7
+ export { useBatchTranslation } from "./useBatchTranslation.hook";
8
+
9
+ export type {
10
+ UseTranslationOptions,
11
+ UseTranslationReturn,
12
+ } from "./useTranslation.hook";
13
+
14
+ export type {
15
+ UseBatchTranslationOptions,
16
+ UseBatchTranslationReturn,
17
+ } from "./useBatchTranslation.hook";
@@ -0,0 +1,161 @@
1
+ /**
2
+ * useBatchTranslation Hook
3
+ * @description React hook for batch translation of multiple texts
4
+ */
5
+
6
+ import { useCallback, useState } from "react";
7
+ import type {
8
+ TranslationRequest,
9
+ TranslationStats,
10
+ } from "../../domain";
11
+ import { googleTranslateService } from "../../infrastructure/services";
12
+
13
+ export interface UseBatchTranslationOptions {
14
+ readonly onSuccess?: (stats: TranslationStats) => void;
15
+ readonly onError?: (error: Error) => void;
16
+ readonly onProgress?: (current: number, total: number) => void;
17
+ }
18
+
19
+ export interface UseBatchTranslationReturn {
20
+ readonly translateBatch: (
21
+ requests: TranslationRequest[]
22
+ ) => Promise<TranslationStats>;
23
+ readonly translateObject: (
24
+ sourceObject: Record<string, unknown>,
25
+ targetObject: Record<string, unknown>,
26
+ targetLanguage: string
27
+ ) => Promise<TranslationStats>;
28
+ readonly isLoading: boolean;
29
+ readonly progress: number;
30
+ readonly total: number;
31
+ readonly error: Error | null;
32
+ }
33
+
34
+ export function useBatchTranslation(
35
+ options?: UseBatchTranslationOptions
36
+ ): UseBatchTranslationReturn {
37
+ const [isLoading, setIsLoading] = useState(false);
38
+ const [error, setError] = useState<Error | null>(null);
39
+ const [progress, setProgress] = useState(0);
40
+ const [total, setTotal] = useState(0);
41
+
42
+ const translateBatch = useCallback(
43
+ async (requests: TranslationRequest[]): Promise<TranslationStats> => {
44
+ if (!Array.isArray(requests) || requests.length === 0) {
45
+ return {
46
+ totalCount: 0,
47
+ successCount: 0,
48
+ failureCount: 0,
49
+ skippedCount: 0,
50
+ translatedKeys: [],
51
+ };
52
+ }
53
+
54
+ setIsLoading(true);
55
+ setError(null);
56
+ setTotal(requests.length);
57
+ setProgress(0);
58
+
59
+ try {
60
+ const stats = await googleTranslateService.translateBatch(requests);
61
+
62
+ setProgress(requests.length);
63
+
64
+ if (stats.failureCount > 0) {
65
+ const error = new Error(
66
+ `${stats.failureCount} of ${stats.totalCount} translations failed`
67
+ );
68
+ setError(error);
69
+ options?.onError?.(error);
70
+ } else {
71
+ options?.onSuccess?.(stats);
72
+ }
73
+
74
+ return stats;
75
+ } catch (err) {
76
+ const error =
77
+ err instanceof Error ? err : new Error("Unknown error occurred");
78
+ setError(error);
79
+ options?.onError?.(error);
80
+ throw error;
81
+ } finally {
82
+ setIsLoading(false);
83
+ }
84
+ },
85
+ [options]
86
+ );
87
+
88
+ const translateObject = useCallback(
89
+ async (
90
+ sourceObject: Record<string, unknown>,
91
+ targetObject: Record<string, unknown>,
92
+ targetLanguage: string
93
+ ): Promise<TranslationStats> => {
94
+ if (!sourceObject || typeof sourceObject !== "object") {
95
+ throw new Error("Source object is invalid");
96
+ }
97
+
98
+ if (!targetObject || typeof targetObject !== "object") {
99
+ throw new Error("Target object is invalid");
100
+ }
101
+
102
+ if (!targetLanguage || targetLanguage.trim().length === 0) {
103
+ throw new Error("Target language is required");
104
+ }
105
+
106
+ setIsLoading(true);
107
+ setError(null);
108
+
109
+ try {
110
+ const stats: TranslationStats = {
111
+ totalCount: 0,
112
+ successCount: 0,
113
+ failureCount: 0,
114
+ skippedCount: 0,
115
+ translatedKeys: [],
116
+ };
117
+
118
+ await googleTranslateService.translateObject(
119
+ sourceObject,
120
+ targetObject,
121
+ targetLanguage,
122
+ "",
123
+ stats
124
+ );
125
+
126
+ setTotal(stats.totalCount);
127
+ setProgress(stats.totalCount);
128
+
129
+ if (stats.failureCount > 0) {
130
+ const error = new Error(
131
+ `${stats.failureCount} of ${stats.totalCount} translations failed`
132
+ );
133
+ setError(error);
134
+ options?.onError?.(error);
135
+ } else {
136
+ options?.onSuccess?.(stats);
137
+ }
138
+
139
+ return stats;
140
+ } catch (err) {
141
+ const error =
142
+ err instanceof Error ? err : new Error("Unknown error occurred");
143
+ setError(error);
144
+ options?.onError?.(error);
145
+ throw error;
146
+ } finally {
147
+ setIsLoading(false);
148
+ }
149
+ },
150
+ [options]
151
+ );
152
+
153
+ return {
154
+ translateBatch,
155
+ translateObject,
156
+ isLoading,
157
+ progress,
158
+ total,
159
+ error,
160
+ };
161
+ }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * useTranslation Hook
3
+ * @description React hook for single text translation
4
+ */
5
+
6
+ import { useCallback, useState } from "react";
7
+ import type { TranslationRequest, TranslationResponse } from "../../domain";
8
+ import { googleTranslateService } from "../../infrastructure/services";
9
+
10
+ export interface UseTranslationOptions {
11
+ readonly onSuccess?: (result: TranslationResponse) => void;
12
+ readonly onError?: (error: Error) => void;
13
+ }
14
+
15
+ export interface UseTranslationReturn {
16
+ readonly translate: (text: string, targetLanguage: string) => Promise<TranslationResponse>;
17
+ readonly isLoading: boolean;
18
+ readonly error: Error | null;
19
+ }
20
+
21
+ export function useTranslation(
22
+ options?: UseTranslationOptions
23
+ ): UseTranslationReturn {
24
+ const [isLoading, setIsLoading] = useState(false);
25
+ const [error, setError] = useState<Error | null>(null);
26
+
27
+ const translate = useCallback(
28
+ async (text: string, targetLanguage: string): Promise<TranslationResponse> => {
29
+ if (!text || text.trim().length === 0) {
30
+ return {
31
+ originalText: text,
32
+ translatedText: text,
33
+ sourceLanguage: "en",
34
+ targetLanguage,
35
+ success: false,
36
+ error: "Text is empty",
37
+ };
38
+ }
39
+
40
+ if (!targetLanguage || targetLanguage.trim().length === 0) {
41
+ return {
42
+ originalText: text,
43
+ translatedText: text,
44
+ sourceLanguage: "en",
45
+ targetLanguage,
46
+ success: false,
47
+ error: "Target language is empty",
48
+ };
49
+ }
50
+
51
+ setIsLoading(true);
52
+ setError(null);
53
+
54
+ try {
55
+ const request: TranslationRequest = {
56
+ text,
57
+ targetLanguage,
58
+ };
59
+
60
+ const result = await googleTranslateService.translate(request);
61
+
62
+ if (result.success) {
63
+ options?.onSuccess?.(result);
64
+ } else {
65
+ const error = new Error(result.error || "Translation failed");
66
+ setError(error);
67
+ options?.onError?.(error);
68
+ }
69
+
70
+ return result;
71
+ } catch (err) {
72
+ const error =
73
+ err instanceof Error ? err : new Error("Unknown error occurred");
74
+ setError(error);
75
+ options?.onError?.(error);
76
+ throw error;
77
+ } finally {
78
+ setIsLoading(false);
79
+ }
80
+ },
81
+ [options]
82
+ );
83
+
84
+ return {
85
+ translate,
86
+ isLoading,
87
+ error,
88
+ };
89
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Presentation Layer
3
+ * @description Subpath: @umituz/react-native-google-translate/hooks
4
+ *
5
+ * Exports all React hooks and presentation layer components
6
+ */
7
+
8
+ export * from "./hooks";