Skip to content

Commit 15d52b0

Browse files
authored
fix: Arabic and other Right-To-Left metadata author/publisher/contributor (PR #2809 Fixes #2491)
1 parent 2d9db2c commit 15d52b0

File tree

22 files changed

+242
-171
lines changed

22 files changed

+242
-171
lines changed

jest.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ const { compilerOptions } = require("./tsconfig");
1212
const pathMaps = pathsToModuleNameMapper(compilerOptions.paths, { prefix: "<rootDir>/" } );
1313
// console.log(pathMaps);
1414
const moduleNameMapper = {
15-
"readium-desktop/main/di": "<rootDir>/test/main/di.ts", // se src/common/utils.ts convertMultiLangStringToString()
15+
"readium-desktop/main/di": "<rootDir>/test/main/di.ts", // see src/common/utils.ts convertMultiLangStringToString()
1616
...pathMaps,
1717
// ...{
1818
// "^@r2\\-streamer\\-js/(.*)$": "<rootDir>/scripts/jest_void.ts",

src/main/converter/tools/localisation.ts renamed to src/common/language-string.ts

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
// that can be found in the LICENSE file exposed on Github (readium) in the project repository.
66
// ==LICENSE-END==
77

8-
import { Contributor } from "@r2-shared-js/models/metadata-contributor";
8+
// import { Contributor } from "@r2-shared-js/models/metadata-contributor";
99
import { IStringMap } from "@r2-shared-js/models/metadata-multilang";
1010
import { BCP47_UNKNOWN_LANG } from "@r2-shared-js/parser/epub";
1111
import { availableLanguages } from "readium-desktop/common/services/translator";
@@ -43,7 +43,7 @@ import { availableLanguages } from "readium-desktop/common/services/translator";
4343
// https://github.com/readium/webpub-manifest/blob/ff5c1e9e76ccc184d4d670179cfb70ced691fcec/schema/contributor-object.schema.json#L7-L24
4444
// tslint:disable-next-line: max-line-length
4545
// https://github.com/readium/webpub-manifest/blob/ff5c1e9e76ccc184d4d670179cfb70ced691fcec/schema/metadata.schema.json#L15-L32
46-
export function convertMultiLangStringToString(items: string | IStringMap | undefined, locale: keyof typeof availableLanguages): string {
46+
export function convertMultiLangStringToString(items: string | IStringMap | undefined, locale: keyof typeof availableLanguages): string | undefined {
4747
if (typeof items === "object") {
4848
// see translator.translateContentField() ?
4949
const langs = Object.keys(items);
@@ -68,15 +68,45 @@ export function convertMultiLangStringToString(items: string | IStringMap | unde
6868
// https://github.com/readium/r2-shared-js/blob/develop/test/test-JSON-Contributor.ts
6969
// https://github.com/readium/r2-shared-js/blob/develop/src/models/metadata-contributor-json-converter.ts
7070
// https://github.com/readium/r2-shared-js/blob/develop/src/models/metadata-contributor.ts
71-
export function convertContributorArrayToStringArray(items: Contributor[] | undefined, locale: keyof typeof availableLanguages): string[] {
72-
if (!items) {
73-
return [];
74-
}
71+
// export function convertContributorArrayToStringArray(items: Contributor[] | undefined): (string | IStringMap)[] { // , locale: keyof typeof availableLanguages
72+
// if (!items) {
73+
// return [];
74+
// }
7575

76-
return items.map((item) => {
77-
if (typeof item.Name === "object") {
78-
return convertMultiLangStringToString(item.Name, locale);
76+
// return items.map((item) => {
77+
// // if (typeof item.Name === "object") {
78+
// // return convertMultiLangStringToString(item.Name, locale);
79+
// // }
80+
// return item.Name;
81+
// });
82+
// }
83+
84+
export function convertMultiLangStringToLangString(items: string | IStringMap | undefined, locale: keyof typeof availableLanguages): [lang: string, str: string | undefined] {
85+
if (typeof items === "object") {
86+
// see translator.translateContentField() ?
87+
const langs = Object.keys(items);
88+
const lang = langs.filter((l) =>
89+
l.toLowerCase().includes(locale.toLowerCase()));
90+
const localeLang = lang[0];
91+
92+
if (items[localeLang]) {
93+
return [localeLang, items[localeLang]];
94+
}
95+
if (items._) {
96+
return [BCP47_UNKNOWN_LANG, items._];
97+
}
98+
if (items[BCP47_UNKNOWN_LANG]) {
99+
return [BCP47_UNKNOWN_LANG, items[BCP47_UNKNOWN_LANG]];
79100
}
80-
return item.Name;
81-
});
101+
return [langs[0], items[langs[0]]];
102+
}
103+
return [BCP47_UNKNOWN_LANG, items];
104+
}
105+
106+
export function langStringIsRTL(lang: string): boolean {
107+
return lang === "ar" || lang.startsWith("ar-") ||
108+
lang === "he" || lang.startsWith("he-") ||
109+
lang === "fa" || lang.startsWith("fa-") ||
110+
lang === "zh-Hant" ||
111+
lang === "zh-TW";
82112
}

src/common/readium/annotation/converter.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import { uniqueCssSelector } from "@r2-navigator-js/electron/renderer/common/css
2323
import { IRangeInfo, ISelectionInfo } from "@r2-navigator-js/electron/common/selection";
2424

2525
import { IS_DEV } from "readium-desktop/preprocessor-directives";
26+
import { convertMultiLangStringToString } from "readium-desktop/common/language-string";
27+
import { availableLanguages } from "readium-desktop/common/services/translator";
2628

2729
// Logger
2830
const debug = debug_("readium-desktop:common:readium:annotation:converter");
@@ -330,7 +332,7 @@ export async function convertAnnotationStateToReadiumAnnotation(annotation: IAnn
330332
};
331333
}
332334

333-
export async function convertAnnotationStateArrayToReadiumAnnotationSet(annotationArray: IAnnotationStateWithICacheDocument[], publicationView: PublicationView, label?: string): Promise<IReadiumAnnotationSet> {
335+
export async function convertAnnotationStateArrayToReadiumAnnotationSet(locale: keyof typeof availableLanguages, annotationArray: IAnnotationStateWithICacheDocument[], publicationView: PublicationView, label?: string): Promise<IReadiumAnnotationSet> {
334336

335337
const currentDate = new Date();
336338
const dateString: string = currentDate.toISOString();
@@ -352,8 +354,14 @@ export async function convertAnnotationStateArrayToReadiumAnnotationSet(annotati
352354
"dc:identifier": ["urn:thorium:" + publicationView.identifier, publicationView.workIdentifier ? publicationView.workIdentifier : ""], // TODO workIdentifier urn ?
353355
"dc:format": "application/epub+zip",
354356
"dc:title": publicationView.documentTitle || "",
355-
"dc:publisher": publicationView.publishers || [],
356-
"dc:creator": publicationView.authors || [],
357+
"dc:publisher": publicationView.publishersLangString ?
358+
publicationView.publishersLangString.map((item) => {
359+
return convertMultiLangStringToString(item, locale);
360+
}) : [],
361+
"dc:creator": publicationView.authorsLangString ?
362+
publicationView.authorsLangString.map((item) => {
363+
return convertMultiLangStringToString(item, locale);
364+
}) : [],
357365
"dc:date": publicationView.publishedAt || "",
358366
},
359367
items: await Promise.all((annotationArray || []).map(async (v) => await convertAnnotationStateToReadiumAnnotation(v, isLcp))),

src/common/services/translator.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
// that can be found in the LICENSE file exposed on Github (readium) in the project repository.
66
// ==LICENSE-END==
77

8+
import { IStringMap } from "@r2-shared-js/models/metadata-multilang";
89
import deCatalog from "readium-desktop/resources/locales/de.json";
910
import enCatalog from "readium-desktop/resources/locales/en.json";
1011
import esCatalog from "readium-desktop/resources/locales/es.json";
@@ -194,10 +195,6 @@ export const availableLanguages = {
194195
"sl": "Slovenščina (Slovene)",
195196
};
196197

197-
interface LocalizedContent {
198-
[locale: string]: string;
199-
}
200-
201198
export type I18nFunction = (_: TTranslatorKeyParameter, __?: {}) => string;
202199

203200
export const setLocale = async (newLocale: keyof typeof availableLanguages) => {
@@ -219,7 +216,8 @@ export const translate = (message: string, options: TOptions = {}): string => {
219216
return label;
220217
};
221218

222-
export const translateContentFieldHelper = (field: string | LocalizedContent, locale: keyof typeof availableLanguages): string => {
219+
// TODO: convertMultiLangStringToLangString() or convertMultiLangStringToString() ??
220+
export const translateContentFieldHelper = (field: string | IStringMap, locale: keyof typeof availableLanguages): string => {
223221
if (!field) {
224222
return "";
225223
}

src/common/views/opds.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,11 @@ export interface IOpdsPublicationView {
3636
// documentTitle vs. publicationTitle
3737
documentTitle: string;
3838

39-
authors: IOpdsContributorView[];
40-
publishers?: IOpdsContributorView[];
39+
// authorsLangString: (string | IStringMap)[]; // convertMultiLangStringToLangString()
40+
// publishersLangString?: (string | IStringMap)[]; // convertMultiLangStringToLangString()
41+
authorsLangString: IOpdsContributorView[];
42+
publishersLangString?: IOpdsContributorView[];
43+
4144
workIdentifier?: string;
4245
description?: string;
4346
numberOfPages: number;
@@ -68,7 +71,7 @@ export interface IOpdsPublicationView {
6871

6972
a11y_accessModeSufficient?: (string[])[];
7073

71-
a11y_accessibilitySummary?: string | IStringMap; // convertMultiLangStringToString
74+
a11y_accessibilitySummary?: string | IStringMap; // convertMultiLangStringToLangString()
7275
}
7376

7477
export interface IOpdsNavigationLinkView {
@@ -133,16 +136,17 @@ export interface IOPDSPropertiesView {
133136
}
134137

135138
export interface IOpdsBaseLinkView {
136-
name: string;
137139
link: IOpdsLinkView[];
138140
}
139141

140142
// eslint-disable-next-line @typescript-eslint/no-empty-interface
141143
export interface IOpdsTagView extends IOpdsBaseLinkView {
144+
name: string;
142145
}
143146

144147
// eslint-disable-next-line @typescript-eslint/no-empty-interface
145148
export interface IOpdsContributorView extends IOpdsBaseLinkView {
149+
nameLangString: string | IStringMap;
146150
}
147151

148152
export interface IOpdsLinkView {

src/common/views/publication.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -45,16 +45,14 @@ export interface PublicationView extends Identifiable {
4545

4646
a11y_accessModeSufficient?: (string[])[];
4747

48-
a11y_accessibilitySummary?: string | IStringMap; // convertMultiLangStringToString
48+
a11y_accessibilitySummary?: string | IStringMap; // convertMultiLangStringToLangString()
4949

5050
documentTitle: string;
51-
publicationTitle: string | IStringMap; // convertMultiLangStringToString
52-
publicationSubTitle: string | IStringMap; // convertMultiLangStringToString
51+
publicationTitle: string | IStringMap; // convertMultiLangStringToLangString()
52+
publicationSubTitle: string | IStringMap; // convertMultiLangStringToLangString()
5353

54-
// TODO: preserve (string | IStringMap) for publishers and authors (contributors),
55-
// and apply convertMultiLangStringToString() only downstream / at rendering time.
56-
authors: string[];
57-
publishers?: string[];
54+
authorsLangString: (string | IStringMap)[]; // convertMultiLangStringToLangString()
55+
publishersLangString?: (string | IStringMap)[]; // convertMultiLangStringToLangString()
5856

5957
workIdentifier?: string;
6058
description?: string;

src/main/converter/opds.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
IOpdsLinkView, IOpdsNavigationLink, IOpdsNavigationLinkView, IOPDSPropertiesView,
1414
IOpdsPublicationView, IOpdsResultView, IOpdsTagView,
1515
} from "readium-desktop/common/views/opds";
16-
import { convertMultiLangStringToString } from "readium-desktop/main/converter/tools/localisation";
16+
import { convertMultiLangStringToString } from "readium-desktop/common/language-string";
1717
import { OpdsFeedDocument } from "readium-desktop/main/db/document/opds";
1818
import { ContentType } from "readium-desktop/utils/contentType";
1919

@@ -190,9 +190,7 @@ export class OpdsFeedViewConverter {
190190
public convertOpdsContributorToView(contributor: Contributor, baseUrl: string): IOpdsContributorView | undefined {
191191

192192
return (contributor.Name) ? {
193-
name: typeof contributor.Name === "object"
194-
? convertMultiLangStringToString(contributor.Name, this.store.getState().i18n.locale)
195-
: contributor.Name,
193+
nameLangString: contributor.Name,
196194
link: this.convertFilterLinksToView(baseUrl, contributor.Links || [], {
197195
type: [
198196
ContentType.AtomXml,
@@ -393,8 +391,8 @@ export class OpdsFeedViewConverter {
393391
baseUrl,
394392
// r2OpdsPublicationJson,
395393
documentTitle: title,
396-
authors,
397-
publishers,
394+
authorsLangString: authors,
395+
publishersLangString: publishers,
398396
workIdentifier,
399397
numberOfPages,
400398
description,
@@ -425,7 +423,7 @@ export class OpdsFeedViewConverter {
425423

426424
a11y_accessModeSufficient: r2OpdsPublication.Metadata.Accessibility?.AccessModeSufficient || r2OpdsPublication.Metadata.AccessModeSufficient, // (string[])[]
427425

428-
// convertMultiLangStringToString
426+
// convertMultiLangStringToLangString()
429427
a11y_accessibilitySummary: r2OpdsPublication.Metadata.Accessibility?.Summary || r2OpdsPublication.Metadata.AccessibilitySummary, // string | IStringMap
430428

431429
};

src/main/converter/publication.ts

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,7 @@ import { isAudiobookFn, isDivinaFn, isPdfFn } from "readium-desktop/common/isMan
1313
import { inject, injectable } from "inversify";
1414
import * as moment from "moment";
1515
import { CoverView, PublicationView } from "readium-desktop/common/views/publication";
16-
import {
17-
convertContributorArrayToStringArray,
18-
} from "readium-desktop/main/converter/tools/localisation";
16+
// import { convertContributorArrayToStringArray } from "readium-desktop/common/language-string";
1917
import { PublicationDocument, PublicationDocumentWithoutTimestampable } from "readium-desktop/main/db/document/publication";
2018
import { diSymbolTable } from "readium-desktop/main/diSymbolTable";
2119
import { PublicationStorage } from "readium-desktop/main/storage/publication-storage";
@@ -28,8 +26,8 @@ import { PublicationParsePromise } from "@r2-shared-js/parser/publication-parser
2826
import { diMainGet } from "../di";
2927
import { lcpLicenseIsNotWellFormed } from "readium-desktop/common/lcp";
3028
import { LCP } from "@r2-lcp-js/parser/epub/lcp";
31-
import { type Store } from "redux";
32-
import { RootState } from "../redux/states";
29+
// import { type Store } from "redux";
30+
// import { RootState } from "../redux/states";
3331

3432
// import { type Store } from "redux";
3533
// import { RootState } from "../redux/states";
@@ -49,8 +47,8 @@ export class PublicationViewConverter {
4947
@inject(diSymbolTable["publication-storage"])
5048
private readonly publicationStorage!: PublicationStorage;
5149

52-
@inject(diSymbolTable.store)
53-
private readonly store!: Store<RootState>;
50+
// @inject(diSymbolTable.store)
51+
// private readonly store!: Store<RootState>;
5452

5553
public removeFromMemoryCache(identifier: string) {
5654
if (_pubCache[identifier]) {
@@ -199,16 +197,13 @@ export class PublicationViewConverter {
199197
const r2Publication = await this.unmarshallR2Publication(document);
200198
const r2PublicationJson = TaJsonSerialize(r2Publication); // note: does not include r2Publication.LCP
201199

202-
// TODO: preserve (string | IStringMap) for publishers and authors (contributors),
203-
// and apply convertMultiLangStringToString() only downstream / at rendering time.
204-
const publishers = convertContributorArrayToStringArray(
205-
r2Publication.Metadata.Publisher,
206-
this.store.getState().i18n.locale,
207-
);
208-
const authors = convertContributorArrayToStringArray(
209-
r2Publication.Metadata.Author,
210-
this.store.getState().i18n.locale,
211-
);
200+
// this.store.getState().i18n.locale
201+
202+
// convertMultiLangStringToLangString()
203+
const publishersLangString = r2Publication.Metadata.Publisher ? r2Publication.Metadata.Publisher.map((contributor) => contributor.Name) : [];
204+
205+
// convertMultiLangStringToLangString()
206+
const authorsLangString = r2Publication.Metadata.Author ? r2Publication.Metadata.Author.map((contributor) => contributor.Name) : [];
212207

213208
let publishedAt: string | undefined;
214209
if (r2Publication.Metadata.PublicationDate) {
@@ -289,20 +284,25 @@ export class PublicationViewConverter {
289284

290285
a11y_accessModeSufficient: r2Publication.Metadata.Accessibility?.AccessModeSufficient || r2Publication.Metadata.AccessModeSufficient, // (string[])[]
291286

292-
// convertMultiLangStringToString
287+
// convertMultiLangStringToLangString()
293288
a11y_accessibilitySummary: r2Publication.Metadata.Accessibility?.Summary || r2Publication.Metadata.AccessibilitySummary, // string | IStringMap
294289

295290
identifier: document.identifier, // preserve Identifiable identifier
296291

297292
documentTitle: document.title || "-", // default title
298-
// convertMultiLangStringToString
293+
294+
// convertMultiLangStringToLangString()
299295
publicationTitle: r2Publication.Metadata.Title, // string | IStringMap
296+
// convertMultiLangStringToLangString()
300297
publicationSubTitle: r2Publication.Metadata.SubTitle, // string | IStringMap
301298

302-
authors,
299+
// convertMultiLangStringToLangString()
300+
publishersLangString,
301+
// convertMultiLangStringToLangString()
302+
authorsLangString,
303+
303304
description: r2Publication.Metadata.Description,
304305
languages: r2Publication.Metadata.Language,
305-
publishers,
306306
workIdentifier: r2Publication.Metadata.Identifier,
307307
publishedAt,
308308
modifiedAt,

src/main/db/repository/publication.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import * as lunrde from "@lunr-languages/lunr.de.js";
2222
import * as lunrfr from "@lunr-languages/lunr.fr.js";
2323
import * as lunrmulti from "@lunr-languages/lunr.multi.js";
2424
import * as lunrstemmer from "@lunr-languages/lunr.stemmer.support.js";
25+
import { convertMultiLangStringToLangString } from "readium-desktop/common/language-string";
2526

2627
const debug = debug_("readium-desktop:main:db:repository:publication");
2728

@@ -220,7 +221,17 @@ export class PublicationRepository {
220221
const docs = pubViews.map((v) => ({
221222
id: v.identifier,
222223
title: v.documentTitle,
223-
author: v.authors.join(" "),
224+
author:
225+
// v.authors.join(" ")
226+
v.authorsLangString.reduce<string>((prev, text) => {
227+
const textLangStr = convertMultiLangStringToLangString(text, state.i18n.locale);
228+
// const textLang = textLangStr && textLangStr[0] ? textLangStr[0].toLowerCase() : "";
229+
// const textIsRTL = langStringIsRTL(textLang);
230+
const textStr = textLangStr && textLangStr[1] ? textLangStr[1] : "";
231+
232+
return prev ? `${prev} ${textStr}` : textStr;
233+
}, "")
234+
,
224235
}));
225236

226237
docs.forEach((v) => {

src/main/redux/sagas/api/publication/import/importPublicationFromFs.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import * as path from "path";
1111
import { acceptedExtensionObject } from "readium-desktop/common/extension";
1212
import { lcpLicenseIsNotWellFormed } from "readium-desktop/common/lcp";
1313
import { RandomCustomCovers } from "readium-desktop/common/models/custom-cover";
14-
import { convertMultiLangStringToString } from "readium-desktop/main/converter/tools/localisation";
14+
import { convertMultiLangStringToString } from "readium-desktop/common/language-string";
1515
import { extractCrc32OnZip } from "readium-desktop/main/tools/crc";
1616
import {
1717
PublicationDocument, PublicationDocumentWithoutTimestampable,

0 commit comments

Comments
 (0)