
Introduzione
Proseguiamo il nostro viaggio nel mondo dei cognitive services (vedi articolo precedente), partendo stavolta da un progetto Angular. Utilizzeremo le Google Translate API per tradurre in tempo reale il testo inserito in una textarea sfruttando i Reactive Form in Angular e, contemporaneamente, sottoporremo lo stesso testo a una Sentiment Analysis grazie alle Text Analytics API di Azure.
Per utilizzare i servizi di traduzione di Google, occorre creare un progetto nella GCP Console in cui abilitare la Cloud Translation API. Dal pannello di configurazione del progetto sarà quindi possibile estrarre la chiave da inserire nelle chiamate http. La documentazione delle chiamate REST possibili è disponibile al seguente link. A partire da essa, abbiamo creato due interfacce Typescript per gestire la request e la response
export interface GoogleTranslateRequest {
q: string;
source: string;
target: string;
format: string;
}
export interface Translation {
translatedText: string;
}
export interface Data {
translations: Translation[];
}
export interface GoogleTranslateResponse {
data: Data;
language: string;
}
Abbiamo quindi creato un servizio per eseguire la chiamata REST mediante l’HttpClientModule di Angular. Da notare come sia stato importato l’environment per andare a recupare la URL del servizio e la chiave dal file environment.ts. La classe ha un metodo translate che restituisce un Observable<GoogleTranslateResponse>
@Injectable({
providedIn: 'root'
})
export class GoogleService {
url = environment.googleTranslateUrl + environment.googleApiKey;
constructor(private http: HttpClient) {}
translate(obj: GoogleTranslateRequest): Observable<GoogleTranslateResponse> {
return this.http.post<GoogleTranslateResponse>(this.url, obj);
}
}
Il servizio di sentiment analysis di Azure Text Analytics
Dal portale di Azure è possibile aggiungere una nuova risorsa di tipo Intelligenza artificiale e Machine Learning, Analisi del testo. Ad essa saranno associati una URL e una chiave per la chiamata REST.
Anche in questo caso creiamo un’interfaccia per la request e una per la response.
export interface AzureSentimentRequest {
documents: AzureSentimentDocument[];
}
export interface AzureSentimentDocument {
language: string;
id: number;
text: string;
}
export interface Document {
id: string;
score: number;
}
export interface AzureSentimentResponse {
documents: Document[];
errors: any[];
}
Abbiamo poi creato un servizio chiamato AzureService che mette a disposizione un metodo chiamato analyze a cui passare la lingue e il testo da analizzare che restituisce un Observable<AzureSentimentResponse>.
@Injectable({
providedIn: 'root'
})
export class AzureService {
httpOptions = {
headers: new HttpHeaders({ 'Ocp-Apim-Subscription-Key': environment.azureTextAnalyticsKey,
'Content-Type': 'application/json',
Accept: 'application/json'
})};
constructor(private http: HttpClient) {}
analize(lang: string, textInput: string): Observable<AzureSentimentResponse> {
const body: AzureSentimentRequest = {
documents : [
{id: 1,
language: lang,
text: textInput
}]
};
return this.http.post<AzureSentimentResponse>(environment.azureTextAnalyticsUrl, body, this.httpOptions);
}
}
Il componente
Nell’app.module.ts abbiamo importato il ReactiveFormsModule per potere avere a disposizione mediante un’observable il testo da analizzare proveniente da una textarea.
<div class="form-group">
<label for="testoOriginale">Testo da Tradurre</label>
<textarea class="form-control" id="testoOriginale" rows="3" [formControl]='testoOriginale'></textarea>
</div>
Nel codice typescript del component possiamo dichiarare
testoIstantaneo: Observable<string> = null;
e poi associare a testoIstantaneo il valueChanges del formControl testoOriginale
this.testoIstantaneo = this.testoOriginale.valueChanges;
Prima di creare il subscribe del nostro Observable, abbiamo inserito un tempo di attesa di un secondo dalla fine dell’inserimento del testo, in maniera tale da non eseguire le chiamate ai servizi ad ogni nuovo carattere inserito nella textarea. Ciò è possibile mediante l’operatore debounce() passato in una pipe all’Observable.
this.testoIstantaneo.pipe( debounceTime(1000) ).subscribe(testo => {
}
A questo punto nel subscribe siamo pronti ad eseguire le chiamate ai servizi REST. Abbiamo richiesto quattro traduzioni (dall’italiano all’inglese, giapponese, arabo, e finlandese) configurando i seguenti oggetti
private impostaTraduzione() {
this.impostazioniTraduzione = [
{ q: '', source: 'it', target: 'en', format: 'text' },
{ q: '', source: 'it', target: 'ja', format: 'text' },
{ q: '', source: 'it', target: 'ar', format: 'text' },
{ q: '', source: 'it', target: 'fi', format: 'text' }
];
}
Ecco le chiamate
this.testoIstantaneo.pipe( debounceTime(1000) ).subscribe(testo => {
this.azureSentiment.analize('it', testo).subscribe((sentiments: AzureSentimentResponse) => {
this.sentiments = sentiments;
});
this.traduzioni = [];
this.impostazioniTraduzione.forEach(impostazione => {
impostazione.q = testo;
this.googleTanslatorService.translate(impostazione).subscribe((response: GoogleTranslateResponse) => {
response.language = impostazione.target;
this.traduzioni.push(response);
});
});
});
La sottoscrizione rende disponibile al template html un array di traduzioni e un array contenente i risultati della sentiment analysis.
<div class="form-group">
<label for="testoOriginale">Testo da Tradurre</label>
<textarea class="form-control" id="testoOriginale" rows="3" [formControl]='testoOriginale'></textarea>
</div>
<div *ngIf="sentiments && sentiments.documents">
<div *ngFor="let sentiment of sentiments.documents">
<div class="progress" style="margin-bottom: 10px">
<div class="progress-bar progress-bar-striped progress-bar-animated" [ngClass]="{
'bg-success':sentiment.score >= 0.50,
'bg-danger': sentiment.score < 0.50}" role="progressbar" [attr.aria-valuenow]="sentiment.score*100" aria-valuemin="0" aria-valuemax="100"
[style.width.%]="sentiment.score*100"></div>
</div>
</div>
</div>
<div *ngFor="let traduzione of traduzioni">
<app-translation [traduzione]='traduzione'></app-translation>
</div>
Il component app-translation è una card bootstrap che mostra il risultato della traduzione.
<div class="card text-white bg-primary mb-3">
<div class="card-header">{{traduzione.language}}</div>
<div class="card-body">
<p *ngIf="traduzione && traduzione.data && traduzione.data.translations && traduzione.data.translations.length >0"
class="card-text">{{traduzione.data.translations[0].translatedText}}</p>
</div>
</div>
La progress bar è colorata in verde se il risultato dell’analisi del testo è maggiore o uguale a 0,50 (ossia se esprime uno stato d’animo positivo) mentre diventa rossa per valori inferiori.


Trovate tutto il codice qui:
https://github.com/sorrentmutie/lost-translations
Alla prossima!