@christhuong/expo-mlkit-translation 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.
File without changes
@@ -0,0 +1,2 @@
1
+ #Fri Jul 03 22:07:48 ICT 2026
2
+ gradle.version=8.9
File without changes
@@ -0,0 +1,22 @@
1
+ plugins {
2
+ id 'com.android.library'
3
+ id 'expo-module-gradle-plugin'
4
+ }
5
+
6
+ def safeExtGet(prop, fallback) {
7
+ rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
8
+ }
9
+
10
+ android {
11
+ namespace "expo.modules.mlkittranslation"
12
+ defaultConfig {
13
+ minSdkVersion safeExtGet("minSdkVersion", 24)
14
+ targetSdkVersion safeExtGet("targetSdkVersion", 34)
15
+ versionCode 1
16
+ versionName "1.0"
17
+ }
18
+ }
19
+
20
+ dependencies {
21
+ implementation "com.google.mlkit:translate:17.0.3"
22
+ }
@@ -0,0 +1,2 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
+ </manifest>
@@ -0,0 +1,144 @@
1
+ package expo.modules.mlkittranslation
2
+
3
+ import android.util.LruCache
4
+ import com.google.mlkit.common.model.DownloadConditions
5
+ import com.google.mlkit.common.model.RemoteModelManager
6
+ import com.google.mlkit.nl.translate.TranslateLanguage
7
+ import com.google.mlkit.nl.translate.TranslateRemoteModel
8
+ import com.google.mlkit.nl.translate.Translation
9
+ import com.google.mlkit.nl.translate.Translator
10
+ import com.google.mlkit.nl.translate.TranslatorOptions
11
+ import expo.modules.kotlin.Promise
12
+ import expo.modules.kotlin.modules.Module
13
+ import expo.modules.kotlin.modules.ModuleDefinition
14
+
15
+ /**
16
+ * Expo module wrapping Google ML Kit Translation.
17
+ * Attribution requirements: https://developers.google.com/ml-kit/language/translation/trans-attribution
18
+ */
19
+ class ExpoMlkitTranslationModule : Module() {
20
+ private val translatorsCache = object : LruCache<String, Translator>(20) {
21
+ override fun entryRemoved(evicted: Boolean, key: String, oldValue: Translator, newValue: Translator?) {
22
+ if (evicted) {
23
+ oldValue.close()
24
+ }
25
+ }
26
+ }
27
+
28
+ private fun getTranslator(source: String, target: String): Translator? {
29
+ val sourceLang = TranslateLanguage.fromLanguageTag(source) ?: return null
30
+ val targetLang = TranslateLanguage.fromLanguageTag(target) ?: return null
31
+ val key = "${sourceLang}_${targetLang}"
32
+
33
+ translatorsCache.get(key)?.let { return it }
34
+
35
+ val options = TranslatorOptions.Builder()
36
+ .setSourceLanguage(sourceLang)
37
+ .setTargetLanguage(targetLang)
38
+ .build()
39
+
40
+ val translator = Translation.getClient(options)
41
+ translatorsCache.put(key, translator)
42
+ return translator
43
+ }
44
+
45
+ override fun definition() = ModuleDefinition {
46
+ Name("ExpoMlkitTranslation")
47
+
48
+ AsyncFunction("translate") { text: String, sourceLanguage: String, targetLanguage: String, promise: Promise ->
49
+ val translator = getTranslator(sourceLanguage, targetLanguage)
50
+ if (translator == null) {
51
+ promise.reject("ERR_LANGUAGE", "Invalid language provided", null)
52
+ return@AsyncFunction
53
+ }
54
+
55
+ val conditions = DownloadConditions.Builder()
56
+ .build()
57
+
58
+ translator.downloadModelIfNeeded(conditions)
59
+ .addOnSuccessListener {
60
+ translator.translate(text)
61
+ .addOnSuccessListener { translatedText ->
62
+ promise.resolve(translatedText)
63
+ }
64
+ .addOnFailureListener { e ->
65
+ promise.reject("ERR_TRANSLATE", "Failed to translate text", e)
66
+ }
67
+ }
68
+ .addOnFailureListener { e ->
69
+ promise.reject("ERR_DOWNLOAD", "Failed to download model", e)
70
+ }
71
+ }
72
+
73
+ AsyncFunction("downloadModel") { languageTag: String, promise: Promise ->
74
+ val lang = TranslateLanguage.fromLanguageTag(languageTag)
75
+ if (lang == null) {
76
+ promise.reject("ERR_LANGUAGE", "Invalid language: $languageTag", null)
77
+ return@AsyncFunction
78
+ }
79
+
80
+ val modelManager = RemoteModelManager.getInstance()
81
+ val model = TranslateRemoteModel.Builder(lang).build()
82
+ val conditions = DownloadConditions.Builder()
83
+ .build()
84
+
85
+ modelManager.download(model, conditions)
86
+ .addOnSuccessListener {
87
+ promise.resolve(null)
88
+ }
89
+ .addOnFailureListener { e ->
90
+ promise.reject("ERR_DOWNLOAD", "Failed to download model", e)
91
+ }
92
+ }
93
+
94
+ AsyncFunction("isModelDownloaded") { languageTag: String, promise: Promise ->
95
+ val lang = TranslateLanguage.fromLanguageTag(languageTag)
96
+ if (lang == null) {
97
+ promise.resolve(false)
98
+ return@AsyncFunction
99
+ }
100
+
101
+ val modelManager = RemoteModelManager.getInstance()
102
+ val model = TranslateRemoteModel.Builder(lang).build()
103
+
104
+ modelManager.isModelDownloaded(model)
105
+ .addOnSuccessListener { isDownloaded ->
106
+ promise.resolve(isDownloaded)
107
+ }
108
+ .addOnFailureListener { _ ->
109
+ promise.resolve(false)
110
+ }
111
+ }
112
+
113
+ AsyncFunction("deleteModel") { languageTag: String, promise: Promise ->
114
+ val lang = TranslateLanguage.fromLanguageTag(languageTag)
115
+ if (lang == null) {
116
+ promise.reject("ERR_LANGUAGE", "Invalid language: $languageTag", null)
117
+ return@AsyncFunction
118
+ }
119
+
120
+ val modelManager = RemoteModelManager.getInstance()
121
+ val model = TranslateRemoteModel.Builder(lang).build()
122
+
123
+ modelManager.deleteDownloadedModel(model)
124
+ .addOnSuccessListener {
125
+ promise.resolve(null)
126
+ }
127
+ .addOnFailureListener { e ->
128
+ promise.reject("ERR_DELETE", "Failed to delete model", e)
129
+ }
130
+ }
131
+
132
+ AsyncFunction("getDownloadedModelTags") { promise: Promise ->
133
+ val modelManager = RemoteModelManager.getInstance()
134
+ modelManager.getDownloadedModels(TranslateRemoteModel::class.java)
135
+ .addOnSuccessListener { models ->
136
+ val tags = models.map { it.language }
137
+ promise.resolve(tags)
138
+ }
139
+ .addOnFailureListener { e ->
140
+ promise.reject("ERR_MODELS", "Failed to get downloaded models", e)
141
+ }
142
+ }
143
+ }
144
+ }
@@ -0,0 +1,5 @@
1
+ export declare function translate(text: string, sourceLanguage: string, targetLanguage: string): Promise<string>;
2
+ export declare function downloadModel(languageTag: string): Promise<void>;
3
+ export declare function isModelDownloaded(languageTag: string): Promise<boolean>;
4
+ export declare function deleteModel(languageTag: string): Promise<void>;
5
+ export declare function getDownloadedModelTags(): Promise<string[]>;
package/build/index.js ADDED
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.translate = translate;
4
+ exports.downloadModel = downloadModel;
5
+ exports.isModelDownloaded = isModelDownloaded;
6
+ exports.deleteModel = deleteModel;
7
+ exports.getDownloadedModelTags = getDownloadedModelTags;
8
+ const expo_modules_core_1 = require("expo-modules-core");
9
+ let _module = null;
10
+ try {
11
+ _module = (0, expo_modules_core_1.requireNativeModule)('ExpoMlkitTranslation');
12
+ }
13
+ catch (error) {
14
+ if (__DEV__) {
15
+ console.warn('[ExpoMlkitTranslation] Native module not available. ' +
16
+ 'Translation will not work. ' +
17
+ 'Run expo prebuild and rebuild the app.');
18
+ }
19
+ }
20
+ async function translate(text, sourceLanguage, targetLanguage) {
21
+ if (!_module) {
22
+ throw new Error('[ExpoMlkitTranslation] Native module not available');
23
+ }
24
+ return _module.translate(text, sourceLanguage, targetLanguage);
25
+ }
26
+ async function downloadModel(languageTag) {
27
+ if (!_module) {
28
+ throw new Error('[ExpoMlkitTranslation] Native module not available');
29
+ }
30
+ return _module.downloadModel(languageTag);
31
+ }
32
+ async function isModelDownloaded(languageTag) {
33
+ if (!_module) {
34
+ return false;
35
+ }
36
+ return _module.isModelDownloaded(languageTag);
37
+ }
38
+ async function deleteModel(languageTag) {
39
+ if (!_module) {
40
+ throw new Error('[ExpoMlkitTranslation] Native module not available');
41
+ }
42
+ return _module.deleteModel(languageTag);
43
+ }
44
+ async function getDownloadedModelTags() {
45
+ if (!_module) {
46
+ return [];
47
+ }
48
+ return _module.getDownloadedModelTags();
49
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "platforms": ["apple", "android"],
3
+ "apple": {
4
+ "modules": ["ExpoMlkitTranslationModule"]
5
+ },
6
+ "android": {
7
+ "modules": ["expo.modules.mlkittranslation.ExpoMlkitTranslationModule"]
8
+ }
9
+ }
@@ -0,0 +1,22 @@
1
+ require 'json'
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, '..', 'package.json')))
4
+
5
+ Pod::Spec.new do |s|
6
+ s.name = 'ExpoMlkitTranslation'
7
+ s.version = package['version']
8
+ s.summary = 'Expo module wrapping Google ML Kit Translation'
9
+ s.description = 'Translates text between languages using ML Kit Translation'
10
+ s.license = package['license']
11
+ s.author = package['author']
12
+ s.homepage = 'https://github.com/thuongstudio'
13
+ s.platforms = { :ios => '15.5' }
14
+ s.swift_version = '5.9'
15
+ s.source = { git: '' }
16
+ s.static_framework = true
17
+
18
+ s.dependency 'ExpoModulesCore'
19
+ s.dependency 'GoogleMLKit/Translate', '8.0.0'
20
+
21
+ s.source_files = '**/*.{h,m,swift}'
22
+ end
@@ -0,0 +1,109 @@
1
+ import ExpoModulesCore
2
+ import MLKitCommon
3
+ import MLKitTranslate
4
+
5
+ /**
6
+ * Expo module wrapping Google ML Kit Translation.
7
+ * Attribution requirements: https://developers.google.com/ml-kit/language/translation/trans-attribution
8
+ */
9
+ public class ExpoMlkitTranslationModule: Module {
10
+ private let translatorsCache: NSCache<NSString, Translator> = {
11
+ let cache = NSCache<NSString, Translator>()
12
+ cache.countLimit = 20 // Limit to 20 active translators to manage memory
13
+ return cache
14
+ }()
15
+ private let queue = DispatchQueue(label: "com.vocatrace.translation.queue")
16
+
17
+ private func getTranslator(source: String, target: String) -> Translator {
18
+ let key = "\(source)_\(target)" as NSString
19
+ if let cached = translatorsCache.object(forKey: key) {
20
+ return cached
21
+ }
22
+ let options = TranslatorOptions(sourceLanguage: TranslateLanguage(rawValue: source),
23
+ targetLanguage: TranslateLanguage(rawValue: target))
24
+ let translator = Translator.translator(options: options)
25
+ translatorsCache.setObject(translator, forKey: key)
26
+ return translator
27
+ }
28
+
29
+ public func definition() -> ModuleDefinition {
30
+ Name("ExpoMlkitTranslation")
31
+
32
+ AsyncFunction("translate") { (text: String, sourceLanguage: String, targetLanguage: String, promise: Promise) in
33
+ let translator = self.getTranslator(source: sourceLanguage, target: targetLanguage)
34
+
35
+ self.queue.async {
36
+ let conditions = ModelDownloadConditions(
37
+ allowsCellularAccess: true,
38
+ allowsBackgroundDownloading: true
39
+ )
40
+
41
+ translator.downloadModelIfNeeded(with: conditions) { error in
42
+ self.queue.async {
43
+ if let error = error {
44
+ promise.reject("ERR_DOWNLOAD", error.localizedDescription)
45
+ return
46
+ }
47
+
48
+ translator.translate(text) { translatedText, error in
49
+ self.queue.async {
50
+ if let error = error {
51
+ promise.reject("ERR_TRANSLATE", error.localizedDescription)
52
+ return
53
+ }
54
+ if let translatedText = translatedText {
55
+ promise.resolve(translatedText)
56
+ } else {
57
+ promise.reject("ERR_TRANSLATE", "Translation returned null")
58
+ }
59
+ }
60
+ }
61
+ }
62
+ }
63
+ }
64
+ }
65
+
66
+ AsyncFunction("downloadModel") { (languageTag: String, promise: Promise) in
67
+ let translator = self.getTranslator(source: languageTag, target: "en")
68
+
69
+ let conditions = ModelDownloadConditions(
70
+ allowsCellularAccess: true,
71
+ allowsBackgroundDownloading: true
72
+ )
73
+
74
+ translator.downloadModelIfNeeded(with: conditions) { error in
75
+ if let error = error {
76
+ promise.reject("ERR_DOWNLOAD", error.localizedDescription)
77
+ } else {
78
+ promise.resolve()
79
+ }
80
+ }
81
+ }
82
+
83
+ AsyncFunction("isModelDownloaded") { (languageTag: String, promise: Promise) in
84
+ let model = TranslateRemoteModel.translateRemoteModel(language: TranslateLanguage(rawValue: languageTag))
85
+ let modelManager = ModelManager.modelManager()
86
+ let isDownloaded = modelManager.isModelDownloaded(model)
87
+ promise.resolve(isDownloaded)
88
+ }
89
+
90
+ AsyncFunction("deleteModel") { (languageTag: String, promise: Promise) in
91
+ let model = TranslateRemoteModel.translateRemoteModel(language: TranslateLanguage(rawValue: languageTag))
92
+ let modelManager = ModelManager.modelManager()
93
+ modelManager.deleteDownloadedModel(model) { error in
94
+ if let error = error {
95
+ promise.reject("ERR_DELETE", "Failed to delete model: \(error.localizedDescription)")
96
+ } else {
97
+ promise.resolve()
98
+ }
99
+ }
100
+ }
101
+
102
+ AsyncFunction("getDownloadedModelTags") { (promise: Promise) in
103
+ let modelManager = ModelManager.modelManager()
104
+ let models = modelManager.downloadedTranslateModels
105
+ let tags = models.compactMap { $0.language.rawValue }
106
+ promise.resolve(Array(tags))
107
+ }
108
+ }
109
+ }
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "@christhuong/expo-mlkit-translation",
3
+ "version": "0.1.0",
4
+ "description": "ML Kit translation native module",
5
+ "author": "Thuong Studio",
6
+ "license": "MIT",
7
+ "main": "build/index.js",
8
+ "types": "build/index.d.ts",
9
+ "files": [
10
+ "build",
11
+ "android",
12
+ "ios",
13
+ "app.plugin.js",
14
+ "expo-module.config.json"
15
+ ],
16
+ "scripts": {
17
+ "build": "tsc",
18
+ "clean": "rm -rf build"
19
+ },
20
+ "peerDependencies": {
21
+ "expo": "*",
22
+ "react": "*",
23
+ "react-native": "*"
24
+ },
25
+ "devDependencies": {
26
+ "typescript": "^5.3.0"
27
+ }
28
+ }