@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.
- package/android/.gradle/8.9/checksums/checksums.lock +0 -0
- package/android/.gradle/8.9/dependencies-accessors/gc.properties +0 -0
- package/android/.gradle/8.9/fileChanges/last-build.bin +0 -0
- package/android/.gradle/8.9/fileHashes/fileHashes.lock +0 -0
- package/android/.gradle/8.9/gc.properties +0 -0
- package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
- package/android/.gradle/buildOutputCleanup/cache.properties +2 -0
- package/android/.gradle/vcs-1/gc.properties +0 -0
- package/android/build.gradle +22 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/expo/modules/mlkittranslation/ExpoMlkitTranslationModule.kt +144 -0
- package/build/index.d.ts +5 -0
- package/build/index.js +49 -0
- package/expo-module.config.json +9 -0
- package/ios/ExpoMlkitTranslation.podspec +22 -0
- package/ios/ExpoMlkitTranslationModule.swift +109 -0
- package/package.json +28 -0
|
Binary file
|
|
File without changes
|
|
Binary file
|
|
Binary file
|
|
File without changes
|
|
Binary file
|
|
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,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
|
+
}
|
package/build/index.d.ts
ADDED
|
@@ -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,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
|
+
}
|