Skip to content

Commit 5f96210

Browse files
authored
Google Translate Integration (#20)
1 parent 2179c01 commit 5f96210

26 files changed

Lines changed: 2026 additions & 1134 deletions

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ If you would like to become a sponsor, please consider:
2222
## Key Features
2323

2424
- Edit your localisation content in real time
25+
- Translate with Google Translate
2526
- No application build required anymore
2627
- Publish your changes (instant update)
2728
- Google CDN Integration (very fast response time, about 300ms for 125kb of 5000 translations)

firebase.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@
3434
"source": "/api/v1/**",
3535
"function": "v1"
3636
},
37+
{
38+
"source": "/api/translate",
39+
"function": "translate"
40+
},
3741
{
3842
"source": "/api/translationsPublish",
3943
"function": "translationsPublish"

functions/package-lock.json

Lines changed: 1369 additions & 639 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

functions/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@
2121
"express": "^4.18.1",
2222
"firebase-admin": "^11.0.1",
2323
"firebase-functions": "^3.23.0",
24-
"axios": "^0.27.2"
24+
"axios": "^0.27.2",
25+
"@google-cloud/translate": "^7.0.2"
2526
},
2627
"devDependencies": {
2728
"@typescript-eslint/eslint-plugin": "^5.36.1",

functions/src/config.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
import {App, initializeApp} from 'firebase-admin/app';
33
import {Firestore, getFirestore} from 'firebase-admin/firestore';
44
import {getStorage, Storage} from 'firebase-admin/storage';
5-
import {getAuth, Auth} from 'firebase-admin/auth';
5+
import {Auth, getAuth} from 'firebase-admin/auth';
6+
import {TranslationServiceClient} from '@google-cloud/translate';
67

78
// BATCH OPERATION
89
export const BATCH_MAX = 500;
@@ -25,3 +26,16 @@ export const firestoreService: Firestore = getFirestore(app);
2526
export const authService: Auth = getAuth(app);
2627
export const storageService: Storage = getStorage(app);
2728
export const bucket = storageService.bucket();
29+
export const translationService = new TranslationServiceClient()
30+
31+
// Translation
32+
export const SUPPORT_LOCALES = new Set([
33+
"af", "am", "ar", "az", "be", "bg", "bn", "bs", "ca", "ceb", "ckb", "co", "cs", "cy", "da", "de",
34+
"el", "en", "eo", "es", "et", "eu", "fa", "fi", "fr", "fy", "ga", "gd", "gl", "gu", "ha", "haw",
35+
"he", "hi", "hmn", "hr", "ht", "hu", "hy", "id", "ig", "is", "it", "iw", "ja", "jw", "ka", "kk",
36+
"km", "kn", "ko", "ku", "ky", "la", "lb", "lo", "lt", "lv", "mai", "mg", "mi", "mk", "ml", "mn",
37+
"mr", "ms", "mt", "my", "ne", "nl", "no", "ny", "or", "pa", "pl", "ps", "pt", "ro", "ru", "rw",
38+
"sd", "si", "sk", "sl", "sm", "sn", "so", "sq", "sr", "st", "su", "sv", "sw", "ta", "te", "tg",
39+
"th", "tk", "tl", "tr", "tt", "ug", "uk", "ur", "uz", "vi", "xh", "yi", "yo", "zh", "zh-TW", "zu"
40+
]
41+
)

functions/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ export {setup} from './setup';
22

33
export {onSpaceDelete} from './spaces';
44

5+
export {translate} from './translate'
6+
57
export {translationsPublish, translationsExport, translationsImport} from './translations';
68

79
export {onAuthUserCreate, onUserUpdate, userInvite, onUserDelete, usersSync} from './users';
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export const FIREBASE_CONFIG = 'FIREBASE_CONFIG'
2+
3+
export interface FirebaseConfig {
4+
projectId: string;
5+
storageBucket: string;
6+
locationId?: string;
7+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export interface TranslateData {
2+
content: string
3+
sourceLocale: string
4+
targetLocale: string
5+
}

functions/src/models/translation.model.ts renamed to functions/src/models/translations.model.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,7 @@ export interface TranslationsImportFullData {
6060
}
6161

6262
export type TranslationsImportData = TranslationsImportFlatData | TranslationsImportFullData
63+
64+
export interface PublishTranslationsData {
65+
spaceId: string
66+
}

functions/src/translate.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import {https, logger} from 'firebase-functions';
2+
import {SecurityUtils} from './utils/security-utils';
3+
import {ROLE_ADMIN, ROLE_EDIT, ROLE_WRITE, SUPPORT_LOCALES, translationService} from './config';
4+
import {FIREBASE_CONFIG, FirebaseConfig} from './models/firebase.model';
5+
import {TranslateData} from './models/translate.model';
6+
import {protos} from '@google-cloud/translate';
7+
8+
export const translate = https.onCall(async (data: TranslateData, context) => {
9+
logger.info('[translate] data: ' + JSON.stringify(data));
10+
logger.info('[translate] context.auth: ' + JSON.stringify(context.auth));
11+
if (!SecurityUtils.hasAnyRole([ROLE_EDIT, ROLE_WRITE, ROLE_ADMIN], context.auth)) throw new https.HttpsError('permission-denied', 'permission-denied');
12+
if (!(SUPPORT_LOCALES.has(data.sourceLocale) && SUPPORT_LOCALES.has(data.targetLocale))) throw new https.HttpsError('invalid-argument', 'Unsupported language');
13+
14+
15+
const firebaseConfig: FirebaseConfig = JSON.parse(process.env[FIREBASE_CONFIG] || '')
16+
const projectId = firebaseConfig.projectId
17+
let locationId; //firebaseConfig.locationId || 'global'
18+
if (firebaseConfig.locationId && firebaseConfig.locationId.startsWith('us-')) {
19+
locationId = 'us-central1'
20+
} else {
21+
locationId = 'global'
22+
}
23+
24+
25+
const request: protos.google.cloud.translation.v3.ITranslateTextRequest = {
26+
parent: `projects/${projectId}/locations/${locationId}`,
27+
contents: [data.content],
28+
mimeType: 'text/plain',
29+
sourceLanguageCode: data.sourceLocale,
30+
targetLanguageCode: data.targetLocale,
31+
};
32+
33+
try {
34+
35+
// Run request
36+
const [responseTranslateText] = await translationService.translateText(request);
37+
38+
if (responseTranslateText.translations && responseTranslateText.translations.length > 0) {
39+
return responseTranslateText.translations[0].translatedText
40+
} else {
41+
return null
42+
}
43+
} catch (e) {
44+
logger.error(e)
45+
throw new https.HttpsError('failed-precondition', `Cloud Translation API has not been used in project ${projectId} before or it is disabled`);
46+
}
47+
});

0 commit comments

Comments
 (0)