diff --git a/packages/studio-web/src/app/app.module.ts b/packages/studio-web/src/app/app.module.ts
index d6422fd3..9e7906b4 100644
--- a/packages/studio-web/src/app/app.module.ts
+++ b/packages/studio-web/src/app/app.module.ts
@@ -21,6 +21,7 @@ import { ErrorPageComponent } from "./error-page/error-page.component";
import { EditorComponent } from "./editor/editor.component";
import { SharedModule } from "./shared/shared.module";
import { WcStylingComponent } from "./shared/wc-styling/wc-styling.component";
+import { G2PErrorComponent } from "./upload/g2p.error.component";
defineCustomElements();
@@ -47,6 +48,7 @@ defineCustomElements();
FormsModule,
NgxRAWebComponentModule,
SharedModule,
+ G2PErrorComponent,
],
providers: [provideHttpClient()],
bootstrap: [AppComponent],
diff --git a/packages/studio-web/src/app/upload/g2p.error.component.sass b/packages/studio-web/src/app/upload/g2p.error.component.sass
new file mode 100644
index 00000000..4e24f375
--- /dev/null
+++ b/packages/studio-web/src/app/upload/g2p.error.component.sass
@@ -0,0 +1,15 @@
+
+s
+ text-decoration: none
+h3
+ border-bottom: 3px solid rgb(220,53, 69)
+ margin-bottom: 1em
+
+.border-danger
+ border-width: 2px !important
+ padding-left: 8px
+ padding-right: 3px
+ margin-right: 5px
+
+span
+ font-size: 1.5em
diff --git a/packages/studio-web/src/app/upload/g2p.error.component.ts b/packages/studio-web/src/app/upload/g2p.error.component.ts
new file mode 100644
index 00000000..ce072b22
--- /dev/null
+++ b/packages/studio-web/src/app/upload/g2p.error.component.ts
@@ -0,0 +1,70 @@
+import { Component, computed, input } from "@angular/core";
+import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
+
+@Component({
+ selector: "g2p-error",
+ template: `
+
+ {{ this.parsedHeader() }}
+
+ Common issues include stray diacritics and numbers or dates represented
+ as digits instead of words.
+
+
+ Characters/symbols/punctuations that prevented our system from
+ processing your text are highlighted below:
+
+
+
+ @for (page of this.parsedPages(); track $index) {
+
+ @for (sentence of getSentencesFromPage(page); track $index) {
+
+ @for (word of getWordsFromSentence(sentence); track $index) {
+ {{ word.textContent }}
+ }
+
+ }
+
+ }
+
+
`,
+ styleUrls: ["./g2p.error.component.sass"],
+ imports: [BrowserAnimationsModule],
+})
+export class G2PErrorComponent {
+ errorMessage = input("");
+ parsedHeader = computed(() => {
+ if (this.errorMessage() === "" || this.errorMessage() === null) {
+ return "";
+ }
+ return this.errorMessage()?.split("PARTIAL RAS:")[0];
+ });
+ parsedPages = computed(() => {
+ if (this.errorMessage() === "" || this.errorMessage() === null) {
+ return [];
+ }
+
+ const xmlDocument = new DOMParser().parseFromString(
+ this.errorMessage()?.split("PARTIAL RAS:")[1].trim() ?? "",
+ "text/xml",
+ );
+ return Array.from(xmlDocument.querySelectorAll("div[type=page]"));
+ });
+ class = computed(() =>
+ this.errorMessage() === "" || this.errorMessage() === null
+ ? "d-none"
+ : "d-block border border-3 border-danger rounded p-3 my-3",
+ );
+ getSentencesFromPage(page: HTMLDivElement): HTMLElement[] {
+ return Array.from(page.querySelectorAll("s"));
+ }
+ getWordsFromSentence(sentence: HTMLElement): HTMLElement[] {
+ return Array.from(sentence.querySelectorAll("w"));
+ }
+ wordClass(word: HTMLElement): string {
+ return word.getAttribute("ARPABET") === ""
+ ? "text-danger border border-danger bg-warning"
+ : "text-muted";
+ }
+}
diff --git a/packages/studio-web/src/app/upload/upload.component.html b/packages/studio-web/src/app/upload/upload.component.html
index a30af058..63889583 100644
--- a/packages/studio-web/src/app/upload/upload.component.html
+++ b/packages/studio-web/src/app/upload/upload.component.html
@@ -28,7 +28,7 @@ Text
>Write
File
@@ -299,6 +299,11 @@
+
diff --git a/packages/studio-web/src/app/upload/upload.component.sass b/packages/studio-web/src/app/upload/upload.component.sass
index 1901963b..ad1cf876 100644
--- a/packages/studio-web/src/app/upload/upload.component.sass
+++ b/packages/studio-web/src/app/upload/upload.component.sass
@@ -10,4 +10,4 @@
scale: 1.3
.pr-0
- padding-right: 0
\ No newline at end of file
+ padding-right: 0
diff --git a/packages/studio-web/src/app/upload/upload.component.ts b/packages/studio-web/src/app/upload/upload.component.ts
index 480a7d00..c267d702 100644
--- a/packages/studio-web/src/app/upload/upload.component.ts
+++ b/packages/studio-web/src/app/upload/upload.component.ts
@@ -23,6 +23,7 @@ import {
inject,
OnInit,
Output,
+ signal,
ViewChild,
} from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router";
@@ -46,6 +47,8 @@ import { StudioService } from "../studio/studio.service";
import { validateFileType } from "../utils/utils";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
+import { G2PErrorComponent } from "./g2p.error.component";
+import { BehaviorSubject } from "rxjs";
@Component({
selector: "app-upload",
templateUrl: "./upload.component.html",
@@ -73,6 +76,8 @@ export class UploadComponent implements OnInit {
@ViewChild("audioFileUpload")
private audioFileUpload: ElementRef;
@Output() public stepChange = new EventEmitter();
+ @ViewChild("g2pError")
+ private g2pError: ElementRef;
// value passed to input[type=file] accept's attribute which expects
// a comma separated list of file extensions or mime types.
@@ -90,6 +95,7 @@ export class UploadComponent implements OnInit {
private uploadService = inject(UploadService);
private dialog = inject(MatDialog);
public studioService = inject(StudioService);
+ g2pErrorMessage$ = new BehaviorSubject("");
constructor() {
this.studioService.audioControl$.valueChanges
@@ -174,12 +180,17 @@ Please check it to make sure all words are spelled out completely, e.g. write "4
{ timeOut: 30000 },
);
}
- this.toastr.error(err.error.detail, $localize`Text processing failed.`, {
+ let message = err.error.detail;
+ //remove the PARTIAL RAS part of the message if present
+ if (message.includes("PARTIAL RAS:")) {
+ message = message.split("PARTIAL RAS:")[0];
+ }
+ this.toastr.error(message.trim(), $localize`Text processing failed.`, {
timeOut: 30000,
});
} else {
this.toastr.error(
- err.message,
+ err.error.detail,
$localize`Hmm, we can't connect to the ReadAlongs API. Please try again later.`,
{ timeOut: 60000 },
);
@@ -487,7 +498,7 @@ Please check it to make sure all words are spelled out completely, e.g. write "4
});
this.studioService.textControl$.setValue(inputText);
}
-
+ this.g2pErrorMessage$.next("");
// Show progress bar
this.loading = true;
this.progressMode = "query";
@@ -568,6 +579,13 @@ Please check it to make sure all words are spelled out completely, e.g. write "4
possibleError instanceof Error ||
possibleError instanceof HttpErrorResponse
) {
+ if (possibleError instanceof HttpErrorResponse) {
+ const httpError = possibleError as HttpErrorResponse;
+ //get the message if the PARTIAL RAS is present
+ if (httpError.error.detail.includes(".PARTIAL RAS:")) {
+ this.g2pErrorMessage$.next(httpError.error.detail);
+ }
+ }
throw possibleError;
} else {
return possibleError;
@@ -594,6 +612,7 @@ Please check it to make sure all words are spelled out completely, e.g. write "4
},
error: (err: Error) => {
this.loading = false;
+
if (err instanceof HttpErrorResponse) {
this.reportRasError(err);
} else if (err.message.includes("align")) {
diff --git a/packages/studio-web/src/i18n/messages.es.json b/packages/studio-web/src/i18n/messages.es.json
index 95ad1d40..00502419 100644
--- a/packages/studio-web/src/i18n/messages.es.json
+++ b/packages/studio-web/src/i18n/messages.es.json
@@ -136,6 +136,8 @@
"4832056586737439470": "Ejemplo: ",
"6487299681734040440": "Esta es la oración 1 en el párrafo 1 en la página 1.\nEsta es la oración 2 en el párrafo 1 en la página 1.\n\nEsta es la oración 1 en el párrafo 2 en la página 1.\nEsta es la oración 2 en el párrafo 2 en la página 1.\n\n\nEsta es la oración 1 en el párrafo 1 en la página 2.",
"5547981558491672240": " Cerrar ",
+ "5102428017988118391": " Los problemas comunes incluyen acentos fuera de las palabras y fechas o números escritos como dígitos en lugar de palabras. ",
+ "4862309118394867347": " Aquí se resaltan los caracteres o símbolos que no se pudieron procesar: ",
"8835207011849408799": " Seleccione los datos para empezar a crear su ReadAlong ",
"8550195538234658887": " Para crear un ReadAlong, solo se necesitan algo de {$START_BOLD_TEXT}texto{$CLOSE_BOLD_TEXT} y el {$START_BOLD_TEXT}audio{$CLOSE_BOLD_TEXT} correspondiente. ",
"6162693758764653365": "Texto",
diff --git a/packages/studio-web/src/i18n/messages.fr.json b/packages/studio-web/src/i18n/messages.fr.json
index 5e8ecf6d..320d77a5 100644
--- a/packages/studio-web/src/i18n/messages.fr.json
+++ b/packages/studio-web/src/i18n/messages.fr.json
@@ -136,6 +136,8 @@
"4832056586737439470": "Par exemple:",
"6487299681734040440": "Phrase un dans le paragraphe un à la page un.\nPhrase deux dans le paragraphe un à la page un.\n\nPhrase un dans le paragraphe deux à la page un.\nPhrase deux dans le paragraphe deux à la page un.\n\n\nPhrase un dans le paragraphe un à la page deux.\n",
"5547981558491672240": " Fermer ",
+ "5102428017988118391": " Les problèmes usuels incluent les accents hors des mots et les dates et nombres écrits en chiffres plutôt qu'en lettres. ",
+ "4862309118394867347": " Les caractères ou symboles qui n'ont pu être traités sont surlignés ici: ",
"8835207011849408799": " Sélectionner des données pour commencer votre ReadAlong ",
"8550195538234658887": " Pour créer un ReadAlong, nous n'avons besoin que du {$START_BOLD_TEXT}texte{$CLOSE_BOLD_TEXT} et d'un enregistrement {$START_BOLD_TEXT}audio{$CLOSE_BOLD_TEXT} correspondant. ",
"6162693758764653365": "Texte",
diff --git a/packages/studio-web/src/i18n/messages.json b/packages/studio-web/src/i18n/messages.json
index 1a608cf0..76dcda1e 100644
--- a/packages/studio-web/src/i18n/messages.json
+++ b/packages/studio-web/src/i18n/messages.json
@@ -136,6 +136,8 @@
"4832056586737439470": "Example:",
"6487299681734040440": "This is sentence one in paragraph one on page one.\nThis is sentence two in paragraph one on page one.\n\nThis is sentence one in paragraph two on page one.\nThis is sentence two in paragraph two on page one.\n\n\nThis is sentence one in paragraph one on page two.\n",
"5547981558491672240": " Close ",
+ "5102428017988118391": " Common issues include stray diacritics and numbers or dates represented as digits instead of words. ",
+ "4862309118394867347": " Characters/symbols/punctuations that prevented our system from processing your text are highlighted below: ",
"8835207011849408799": " Select data to start creating your ReadAlong ",
"8550195538234658887": " In order to make a ReadAlong, we just need some {$START_BOLD_TEXT}text{$CLOSE_BOLD_TEXT}, and corresponding {$START_BOLD_TEXT}audio{$CLOSE_BOLD_TEXT}. ",
"6162693758764653365": "Text",
diff --git a/packages/studio-web/tests/fixtures/bbc_text_tts_nihalgazi_alloy.mp3 b/packages/studio-web/tests/fixtures/bbc_text_tts_nihalgazi_alloy.mp3
new file mode 100644
index 00000000..0009aafe
Binary files /dev/null and b/packages/studio-web/tests/fixtures/bbc_text_tts_nihalgazi_alloy.mp3 differ
diff --git a/packages/studio-web/tests/studio-web/make-read-along.spec.ts b/packages/studio-web/tests/studio-web/make-read-along.spec.ts
index 9a8b27c2..412c412d 100644
--- a/packages/studio-web/tests/studio-web/make-read-along.spec.ts
+++ b/packages/studio-web/tests/studio-web/make-read-along.spec.ts
@@ -91,3 +91,39 @@ test("should make read along", async ({ page, browserName }) => {
fileChooser.setFiles(testAssetsPath + "page2.png");
await page.getByRole("tab", { name: "Editable Step" }).click();
});
+
+test("should show g2p text error", async ({ page, browserName }) => {
+ await defaultBeforeEach(page, browserName);
+ await page
+ .getByTestId("ras-text-input")
+ .fill(
+ "Staff on the Caledonian Sleeper will hold two 24-hour strikes. One from 11:59 on Sunday 31 October and one on Thursday 11 November.",
+ );
+
+ await expect(page.getByTestId("text-download-btn")).toBeVisible();
+ await page
+ .getByTestId("audio-btn-group")
+ .getByRole("radio", { name: "File" })
+ .click();
+ await page.getByTestId("ras-audio-fileselector").click();
+ await page
+ .getByTestId("ras-audio-fileselector")
+ .setInputFiles(
+ testMp3Path.replace(
+ "test-sentence-paragraph-page-56k",
+ "bbc_text_tts_nihalgazi_alloy",
+ ),
+ );
+ const responsePromise = page.waitForResponse("**/api/v1/assemble");
+ await page.getByTestId("next-step").click();
+ await responsePromise;
+
+ await expect(
+ page.locator("g2p-error"),
+ "should show G2P error box",
+ ).toBeVisible();
+ await expect(
+ page.locator("g2p-error .text-danger").first(),
+ "should have highlighted 24",
+ ).toContainText("24");
+});
diff --git a/update-translations.sh b/update-translations.sh
index 8c642d27..c1c8cb8a 100755
--- a/update-translations.sh
+++ b/update-translations.sh
@@ -7,7 +7,5 @@
npx nx extract-i18n studio-web
cd packages/studio-web/ || exit 1
-npx tsx extract-i18n-lang.ts src/i18n/messages.json src/i18n/messages.es.json >src/i18n/messages.es-updated.json
-mv src/i18n/messages.es-updated.json src/i18n/messages.es.json
-npx tsx extract-i18n-lang.ts src/i18n/messages.json src/i18n/messages.fr.json >src/i18n/messages.fr-updated.json
-mv src/i18n/messages.fr-updated.json src/i18n/messages.fr.json
+npx tsx extract-i18n-lang.ts --inplace src/i18n/messages.json src/i18n/messages.es.json
+npx tsx extract-i18n-lang.ts --inplace src/i18n/messages.json src/i18n/messages.fr.json