Skip to content

Commit

Permalink
feat(spoken-to-signed): add text normalization suggestion
Browse files Browse the repository at this point in the history
  • Loading branch information
AmitMY committed Oct 22, 2023
1 parent 72b0ae6 commit 76f8c90
Show file tree
Hide file tree
Showing 28 changed files with 492 additions and 73 deletions.
8 changes: 5 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
# Changelog

This file is used to list changes made over time to `sign/translate`.
This file is used to list important changes made over time to `sign/translate`.

## Unreleased

### 0.0.3

#### Features

- **spoken-to-signed**: add language detection suggestion "Translate from Hebrew" instead
- **api**: add text-normalization API with appCheck support
- **spoken-to-signed**: add language detection suggestion "Translate from: \_\_\_"
- **spoken-to-signed**: add text edit suggestions "Did you mean: \_\_\_"

## Released

Expand All @@ -32,11 +34,11 @@ This file is used to list changes made over time to `sign/translate`.
- **core**: migrated app from `angular/material` to `ionic`
- **ios/safari**: add iOS style for iOS devices / Safari
- **translation**: integrate a new interface for mobile devices
- **human**: position human in center of screen

#### Fixes

- **avatar**: fix avatar rotation names
- **human**: position human in center of screen
- **tests**: fix flaky tests, caused by race conditions
- **sharing**: removed sharing from the desktop web when unsupported
- **settings**: navigation back button text matches the main page title
Expand Down
4 changes: 2 additions & 2 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ android {
applicationId "mt.sign.translate"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 2
versionName "0.0.2"
versionCode 3
versionName "0.0.3"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions {
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
Expand Down
1 change: 1 addition & 0 deletions android/app/capacitor.build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
dependencies {
implementation project(':capacitor-firebase-analytics')
implementation project(':capacitor-firebase-app')
implementation project(':capacitor-firebase-app-check')
implementation project(':capacitor-firebase-crashlytics')
implementation project(':capacitor-firebase-performance')
implementation project(':capacitor-filesystem')
Expand Down
4 changes: 4 additions & 0 deletions android/app/src/main/assets/capacitor.plugins.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
"pkg": "@capacitor-firebase/app",
"classpath": "io.capawesome.capacitorjs.plugins.firebase.app.FirebaseAppPlugin"
},
{
"pkg": "@capacitor-firebase/app-check",
"classpath": "io.capawesome.capacitorjs.plugins.firebase.appcheck.FirebaseAppCheckPlugin"
},
{
"pkg": "@capacitor-firebase/crashlytics",
"classpath": "io.capawesome.capacitorjs.plugins.firebase.crashlytics.FirebaseCrashlyticsPlugin"
Expand Down
3 changes: 3 additions & 0 deletions android/capacitor.settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ project(':capacitor-firebase-analytics').projectDir = new File('../node_modules/
include ':capacitor-firebase-app'
project(':capacitor-firebase-app').projectDir = new File('../node_modules/@capacitor-firebase/app/android')

include ':capacitor-firebase-app-check'
project(':capacitor-firebase-app-check').projectDir = new File('../node_modules/@capacitor-firebase/app-check/android')

include ':capacitor-firebase-crashlytics'
project(':capacitor-firebase-crashlytics').projectDir = new File('../node_modules/@capacitor-firebase/crashlytics/android')

Expand Down
22 changes: 8 additions & 14 deletions firebase.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,6 @@
"predeploy": ["cd functions && npm run build"],
"source": "functions",
"codebase": "default"
},
{
"source": "functions-py",
"codebase": "functions-py",
"ignore": ["venv", ".git", "firebase-debug.log", "firebase-debug.*.log"]
},
{
"source": "test",
"codebase": "test",
"ignore": ["venv", ".git", "firebase-debug.log", "firebase-debug.*.log"]
}
],
"emulators": {
Expand Down Expand Up @@ -84,14 +74,18 @@
"source": "/opensearch.xml",
"function": "translate-prerender"
},
{
"source": "/api/**",
"function": "translate-textToText"
},
{
"source": "/robots.txt",
"destination": "/assets/robots.txt"
},
{
"source": "/api/text-normalization",
"function": "translate-textNormalization"
},
{
"source": "/api/**",
"function": "translate-textToText"
},
{
"source": "about",
"destination": "about/index.html"
Expand Down
Empty file removed functions/.env
Empty file.
3 changes: 2 additions & 1 deletion functions/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ database-debug.log
firestore-debug.log

coverage/
!.env
.env
.runtimeconfig.json
53 changes: 27 additions & 26 deletions functions/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
"test:ci": "firebase emulators:exec --only database,firestore,storage 'npm run test'",
"emulate": "npm run emulators:stop && firebase emulators:start",
"emulators:stop": "lsof -t -i:4010 -i:4011 -i:4012 -i:4013 -i:4014 -i:4015 | xargs kill -9",
"serve": "source '../.env' && firebase emulators:start --only functions",
"deploy": "source '../.env' && firebase deploy --only functions:translate",
"serve": "source '../.env' && source '.env' && firebase serve --only functions",
"deploy": "source '../.env' && source '.env' && firebase deploy --only functions:translate",
"logs": "firebase functions:log",
"playground": "node lib/playground.js"
},
Expand All @@ -19,32 +19,33 @@
},
"main": "lib/index.js",
"dependencies": {
"@firebase/database-types": "^1.0.0",
"@google-cloud/storage": "^7.3.1",
"@sign-mt/browsermt": "^0.2.1",
"cors": "^2.8.5",
"express": "^4.18.2",
"express-async-errors": "^3.1.1",
"firebase-admin": "^11.11.0",
"firebase-functions": "^4.4.1",
"http-errors": "^2.0.0",
"node-fetch": "2.6.7"
"@firebase/database-types": "1.0.0",
"@google-cloud/storage": "7.3.1",
"@sign-mt/browsermt": "0.2.1",
"cors": "2.8.5",
"express": "4.18.2",
"express-async-errors": "3.1.1",
"firebase-admin": "11.11.0",
"firebase-functions": "4.4.1",
"http-errors": "2.0.0",
"node-fetch": "2.6.7",
"openai": "4.12.4"
},
"devDependencies": {
"@firebase/firestore-types": "^3.0.0",
"@firebase/rules-unit-testing": "^3.0.1",
"@types/http-errors": "^2.0.3",
"@firebase/firestore-types": "3.0.0",
"@firebase/rules-unit-testing": "3.0.1",
"@types/http-errors": "2.0.3",
"@types/jest": "29.5.6",
"@types/node-fetch": "^2.6.7",
"@typescript-eslint/eslint-plugin": "^6.8.0",
"@typescript-eslint/parser": "^6.8.0",
"eslint": "^8.52.0",
"firebase-functions-test": "^3.1.0",
"firebase-tools": "^12.7.0",
"jest": "^29.7.0",
"mock-express-request": "^0.2.2",
"mock-express-response": "^0.3.0",
"ts-jest": "^29.1.1",
"typescript": "~5.2.2"
"@types/node-fetch": "2.6.7",
"@typescript-eslint/eslint-plugin": "6.8.0",
"@typescript-eslint/parser": "6.8.0",
"eslint": "8.52.0",
"firebase-functions-test": "3.1.0",
"firebase-tools": "12.7.0",
"jest": "29.7.0",
"mock-express-request": "0.2.2",
"mock-express-response": "0.3.0",
"ts-jest": "29.1.1",
"typescript": "5.2.2"
}
}
2 changes: 2 additions & 0 deletions functions/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {prerenderFunctions} from './prerender/controller';
import {textToTextFunctions} from './text-to-text/controller';
import {logConsoleMemory} from './utils/memory';
import {FirebaseDatabase} from '@firebase/database-types';
import {textNormalizationFunctions} from './text-normalization/controller';

logConsoleMemory(process.env.NODE_ENV === 'production' ? functions.logger : console);

Expand All @@ -19,5 +20,6 @@ module.exports = {
translate: {
prerender: prerenderFunctions(),
textToText: textToTextFunctions(database, storage),
textNormalization: textNormalizationFunctions(database),
},
};
40 changes: 40 additions & 0 deletions functions/src/text-normalization/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Text-Normalization

Normalizes the given text for sign language translation.

Takes in a text that might contain misspellings, incorrect capitalization,
missing hyphenation, numbers, units, or other phenomena requiring special
vocalization. Returns the text normalized for sign language, with corrections in
capitalization, spelling, and hyphenation, and special vocalizations for
numbers and units.

## Parameters:

| Parameter | Type | Description |
| --------- | -------- | ------------------------- |
| `lang` | `string` | Language code of the text |
| `text` | `string` | Text to be normalized |

## Example Call:

```bash
curl "https://sign.mt/api/text-normalization?lang=en&text=test"
```

## Response (Success, 200):

```json
{
"lang": "en",
"text": "Test"
}
```

## Response (Failure, 400):

```
{
"message": "Failure",
"error": "Missing \"text\" query parameter"
}
```
97 changes: 97 additions & 0 deletions functions/src/text-normalization/controller.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import {TextNormalizationEndpoint} from './controller';
import {setupFirebaseTestEnvironment} from '../firebase.extend-spec';
import {StringParam} from 'firebase-functions/lib/params/types';

const MockExpressRequest = require('mock-express-request');
const MockExpressResponse = require('mock-express-response');

describe('TextNormalizationEndpoint', () => {
const testEnvironment = setupFirebaseTestEnvironment();

let controller: TextNormalizationEndpoint;
beforeEach(() => {
const key = new StringParam('mock-key');
controller = new TextNormalizationEndpoint(testEnvironment.database, key);
spyOn(controller, 'normalize').and.returnValue(Promise.resolve('mock normalized text'));
});

it('should error missing "lang"', async () => {
const mockReq = new MockExpressRequest({
url: '/',
params: {},
query: {text: 'hello'},
});
const mockRes = new MockExpressResponse({request: mockReq});

await expect(controller.request(mockReq, mockRes)).rejects.toThrow(new Error('Missing "from" query parameter'));
});

it('should error missing "text"', async () => {
const mockReq = new MockExpressRequest({
url: '/',
params: {},
query: {lang: 'en'},
});
const mockRes = new MockExpressResponse({request: mockReq});

await expect(controller.request(mockReq, mockRes)).rejects.toThrow(new Error('Missing "text" query parameter'));
});

async function normalizeExample() {
const mockReq = new MockExpressRequest({
url: '/',
params: {},
query: {lang: 'en', text: 'hello'},
});
const mockRes = new MockExpressResponse({request: mockReq});
await controller.request(mockReq, mockRes);

return mockRes._getJSON();
}

it('should normalize text with correct url parameters', async () => {
const response = await normalizeExample();

expect(response).toEqual({
lang: 'en',
text: 'mock normalized text,',
});
});

it('should cache normalization response', async () => {
const helloMd5 = '5d41402abc4b2a76b9719d911017c592';
const ref = testEnvironment.database.ref('/normalizations/en/' + helloMd5);
const snapshotBefore = await ref.once('value');
expect(snapshotBefore.exists()).toBe(false);

const response = await normalizeExample();

const snapshot = await ref.once('value');
expect(snapshot.exists()).toBe(true);
const cachedValue = snapshot.val();
expect(cachedValue.input).toEqual('hello');
expect(cachedValue.counter).toEqual(1);
expect(cachedValue.timestamp).toBeLessThanOrEqual(Date.now());
expect(cachedValue.timestamp).toBeGreaterThanOrEqual(Date.now() - 5000);
expect(cachedValue.output).toEqual(response.text);
});

it('should respond with cached response if exists', async () => {
const helloMd5 = '5d41402abc4b2a76b9719d911017c592';
const ref = testEnvironment.database.ref('/normalizations/en/' + helloMd5);
await ref.set({
input: 'hello',
output: 'fake translation',
counter: 1,
timestamp: Date.now(),
});

const response = await normalizeExample();
expect(response.text).toEqual('fake translation');

const snapshot = await ref.once('value');
expect(snapshot.exists()).toBe(true);
const cachedValue = snapshot.val();
expect(cachedValue.counter).toEqual(2);
});
});
Loading

0 comments on commit 76f8c90

Please sign in to comment.