Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(dev): rationalize translator for the 3 processes and store the locale value to redux only / remove inversify in renderer process / in class component split translator context and component refresh when locale updated / remove unused old class component #2592

Merged
merged 7 commits into from
Oct 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/common/redux/actions/i18n/setLocale.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@
// ==LICENSE-END==

import { Action } from "readium-desktop/common/models/redux";
import { availableLanguages } from "readium-desktop/common/services/translator";

export const ID = "LOCALE_SET";

export interface Payload {
locale: string;
locale: keyof typeof availableLanguages;
}

export function build(locale: string): Action<typeof ID, Payload> {
export function build(locale: keyof typeof availableLanguages): Action<typeof ID, Payload> {

return {
type: ID,
Expand Down
10 changes: 5 additions & 5 deletions src/common/redux/reducers/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,21 @@ import { i18nActions } from "readium-desktop/common/redux/actions";

import { I18NState } from "readium-desktop/common/redux/states/i18n";

const initialState: I18NState = {
locale: "en",
const initialState: I18NState | { locale: "" } = {
locale: "", // not initialized
};

function i18nReducer_(
state: I18NState = initialState,
state: I18NState | { locale: "" } = initialState,
action: i18nActions.setLocale.TAction,
): I18NState {
): I18NState {
switch (action.type) {
case i18nActions.setLocale.ID:
return Object.assign({}, state, {
locale: action.payload.locale,
} as I18NState);
default:
return state;
return state as I18NState;
}
}

Expand Down
2 changes: 2 additions & 0 deletions src/common/redux/states/commonRootState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ import { IKeyboardState } from "readium-desktop/common/redux/states/keyboard";
import { ReaderConfig } from "readium-desktop/common/models/reader";
import { ITheme } from "./theme";
import { IAnnotationCreator } from "./creator";
import { I18NState } from "readium-desktop/common/redux/states/i18n";

export interface ICommonRootState {
i18n: I18NState;
session: ISessionState;
versionUpdate: IVersionUpdateState;
keyboard: IKeyboardState;
Expand Down
4 changes: 3 additions & 1 deletion src/common/redux/states/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
// that can be found in the LICENSE file exposed on Github (readium) in the project repository.
// ==LICENSE-END==

import { availableLanguages } from "readium-desktop/common/services/translator";

export interface I18NState {
locale: string;
locale: keyof typeof availableLanguages;
}
2 changes: 0 additions & 2 deletions src/common/redux/states/rendererCommonRootState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
// ==LICENSE-END==

import { DialogState } from "readium-desktop/common/redux/states/dialog";
import { I18NState } from "readium-desktop/common/redux/states/i18n";
import { ToastState } from "readium-desktop/common/redux/states/toast";
import { ApiState } from "readium-desktop/common/redux/states/api";
import { WinState } from "readium-desktop/common/redux/states/win";
Expand All @@ -15,7 +14,6 @@ import { IImportAnnotationState } from "./importAnnotation";

export interface IRendererCommonRootState extends ICommonRootState {
api: ApiState<any>;
i18n: I18NState;
win: WinState;
dialog: DialogState;
toast: ToastState;
Expand Down
142 changes: 50 additions & 92 deletions src/common/services/translator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
// that can be found in the LICENSE file exposed on Github (readium) in the project repository.
// ==LICENSE-END==

import { injectable } from "inversify";

import deCatalog from "readium-desktop/resources/locales/de.json";
import enCatalog from "readium-desktop/resources/locales/en.json";
import esCatalog from "readium-desktop/resources/locales/es.json";
Expand Down Expand Up @@ -166,7 +164,7 @@ i18nextInstanceEN.changeLanguage("en").then((_t) => {
// can use ObjectValues or ObjectKeys from
// src/utils/object-keys-values.ts
// to benefit from compile-type TypeScript typesafe key enum
export const AvailableLanguages = {
export const availableLanguages = {
"en": "English",
"fr": "Français (French)",
"fi": "Suomi (Finnish)",
Expand Down Expand Up @@ -200,107 +198,67 @@ interface LocalizedContent {

export type I18nFunction = (_: TTranslatorKeyParameter, __?: {}) => string;

@injectable()
export class Translator {
public translate = this._translate as I18nFunction;
public subscribe = this._subscribe.bind(this);
private locale = "en";
private listeners: Set<() => void>;
export const setLocale = async (newLocale: keyof typeof availableLanguages) => {

constructor() {
this.listeners = new Set();
if (i18nextInstance.language !== newLocale) {
// https://github.com/i18next/i18next/blob/master/CHANGELOG.md#1800
// i18nextInstance.language not instantly ready (because async loadResources()),
// but i18nextInstance.isLanguageChangingTo immediately informs which locale i18next is switching to.
await i18nextInstance.changeLanguage(newLocale);
}
return ;
};

private _subscribe(fn: () => void) {
if (fn) {
this.listeners.add(fn);
return () => {
this.listeners.delete(fn);
};
}
return () => {};
export const translate = (message: string, options: TOptions = {}): string => {
const label = i18nextInstance.t(message, options);
if (!label || !label.length) {
return i18nextInstanceEN.t(message, options);
}
return label;
};

public getLocale(): string {
return this.locale;
export const translateContentFieldHelper = (field: string | LocalizedContent, locale: keyof typeof availableLanguages): string => {
if (!field) {
return "";
}

public async setLocale(locale: string) {
this.locale = locale;

return new Promise<void>((resolve, reject) => {

if (i18nextInstance.language !== this.locale) {
// https://github.com/i18next/i18next/blob/master/CHANGELOG.md#1800
// i18nextInstance.language not instantly ready (because async loadResources()),
// but i18nextInstance.isLanguageChangingTo immediately informs which locale i18next is switching to.
i18nextInstance.changeLanguage(this.locale).then((_t) => {
resolve();
}).catch((err) => {
console.log(err);
reject(err);
});
} else {
resolve();
}
}).finally(() => {
for (const listener of this.listeners) {
listener();
}
});
if (typeof field === "string") {
return field;
}

/**
* Translate content field that is not provided
* by an i18n catalog
* Field could be a string or an array
*
* @param text
*/
public translateContentField(field: string | LocalizedContent) {
if (!field) {
return "";
}

if (typeof field === "string") {
return field;
}

if (field[this.locale]) {
return field[this.locale];
}

// Check if there is no composed locale names matching with the current locale
const simplifiedFieldLocales = Object.keys(field).filter(
(locale) => locale.split("-")[0] === this.locale.split("-")[0],
);
if (simplifiedFieldLocales.length) {
return field[simplifiedFieldLocales[0]];
}
if (field[locale]) {
return field[locale];
}

// If nothing try to take an english locale
const englishFieldLocales = Object.keys(field).filter(
(locale) => locale.split("-")[0] === "en",
);
if (englishFieldLocales.length) {
return field[englishFieldLocales[0]];
}
// Check if there is no composed locale names matching with the current locale
const simplifiedFieldLocales = Object.keys(field).filter(
(locale) => locale.split("-")[0] === locale.split("-")[0],
);
if (simplifiedFieldLocales.length) {
return field[simplifiedFieldLocales[0]];
}

// Take the first locale if nothing match with current locale or english
const keys = Object.keys(field);
// If nothing try to take an english locale
const englishFieldLocales = Object.keys(field).filter(
(locale) => locale.split("-")[0] === "en",
);
if (englishFieldLocales.length) {
return field[englishFieldLocales[0]];
}

if (keys && keys.length) {
return field[keys[0]];
}
// Take the first locale if nothing match with current locale or english
const keys = Object.keys(field);

return "";
if (keys && keys.length) {
return field[keys[0]];
}

private _translate(message: string, options: TOptions = {}): string {
const label = i18nextInstance.t(message, options);
if (!label || !label.length) {
return i18nextInstanceEN.t(message, options);
}
return label;
}
}
return "";
};

export const translator = {
__: translate,
setLocale,
translate,
};
export const getTranslator = () => translator;
20 changes: 13 additions & 7 deletions src/main/converter/opds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
// ==LICENSE-END==

import * as debug_ from "debug";
import { injectable } from "inversify";
import { inject, injectable } from "inversify";
import * as moment from "moment";
import {
IOpdsAuthView, IOpdsCoverView, IOpdsFeedMetadataView, IOpdsFeedView, IOpdsGroupView,
Expand Down Expand Up @@ -36,6 +36,9 @@ import { filterRelLink, filterTypeLink } from "./tools/filterLink";
import { urlPathResolve } from "./tools/resolveUrl";
import { TLinkMayBeOpds, TProperties } from "./type/link.type";
import { ILinkFilter } from "./type/linkFilter.interface";
import { diSymbolTable } from "../diSymbolTable";
import { type Store } from "redux";
import { RootState } from "../redux/states";

// Logger
const debug = debug_("readium-desktop:main/converter/opds");
Expand Down Expand Up @@ -63,6 +66,9 @@ const supportedFileTypeLinkArray = [
@injectable()
export class OpdsFeedViewConverter {

@inject(diSymbolTable.store)
private readonly store!: Store<RootState>;

public convertDocumentToView(document: OpdsFeedDocument): IOpdsFeedView {
return {
identifier: document.identifier, // preserve Identifiable identifier
Expand Down Expand Up @@ -171,7 +177,7 @@ export class OpdsFeedViewConverter {
public convertOpdsTagToView(subject: Subject, baseUrl: string): IOpdsTagView | undefined {

return (subject.Name || subject.Code) ? {
name: convertMultiLangStringToString(subject.Name || subject.Code),
name: convertMultiLangStringToString(subject.Name || subject.Code, this.store.getState().i18n.locale),
link: this.convertFilterLinksToView(baseUrl, subject.Links || [], {
type: [
ContentType.AtomXml,
Expand All @@ -185,7 +191,7 @@ export class OpdsFeedViewConverter {

return (contributor.Name) ? {
name: typeof contributor.Name === "object"
? convertMultiLangStringToString(contributor.Name)
? convertMultiLangStringToString(contributor.Name, this.store.getState().i18n.locale)
: contributor.Name,
link: this.convertFilterLinksToView(baseUrl, contributor.Links || [], {
type: [
Expand Down Expand Up @@ -274,7 +280,7 @@ export class OpdsFeedViewConverter {
const description = metadata.Description;
const languages = metadata.Language;

const title = convertMultiLangStringToString(metadata.Title);
const title = convertMultiLangStringToString(metadata.Title, this.store.getState().i18n.locale);
// console.log(`=-=-==-=-${JSON.stringify(metadata.Title)}---${title}`);

const publishedAt = metadata.PublicationDate &&
Expand Down Expand Up @@ -501,7 +507,7 @@ export class OpdsFeedViewConverter {
});

const title = r2OpdsGroup.Metadata?.Title
? convertMultiLangStringToString(r2OpdsGroup.Metadata.Title)
? convertMultiLangStringToString(r2OpdsGroup.Metadata.Title, this.store.getState().i18n.locale)
: "";

const nb = r2OpdsGroup.Metadata?.NumberOfItems;
Expand All @@ -523,7 +529,7 @@ export class OpdsFeedViewConverter {

public convertOpdsFacetsToView(r2OpdsFacet: OPDSFacet, baseUrl: string): IOpdsFacetView {
const title = r2OpdsFacet.Metadata?.Title
? convertMultiLangStringToString(r2OpdsFacet.Metadata.Title)
? convertMultiLangStringToString(r2OpdsFacet.Metadata.Title, this.store.getState().i18n.locale)
: "";

const links = r2OpdsFacet.Links?.map(
Expand All @@ -539,7 +545,7 @@ export class OpdsFeedViewConverter {

public convertOpdsFeedToView(r2OpdsFeed: OPDSFeed, baseUrl: string): IOpdsResultView {

const title = convertMultiLangStringToString(r2OpdsFeed.Metadata?.Title);
const title = convertMultiLangStringToString(r2OpdsFeed.Metadata?.Title, this.store.getState().i18n.locale);
const publications = r2OpdsFeed.Publications?.map(
(item) =>
// warning: modifies item, makes relative URLs absolute with baseUrl!
Expand Down
8 changes: 6 additions & 2 deletions src/main/converter/publication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import { PublicationParsePromise } from "@r2-shared-js/parser/publication-parser
import { diMainGet } from "../di";
import { lcpLicenseIsNotWellFormed } from "readium-desktop/common/lcp";
import { LCP } from "@r2-lcp-js/parser/epub/lcp";
import { type Store } from "redux";
import { RootState } from "../redux/states";

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

// @inject(diSymbolTable.store)
// private readonly store!: Store<RootState>;
@inject(diSymbolTable.store)
private readonly store!: Store<RootState>;

public removeFromMemoryCache(identifier: string) {
if (_pubCache[identifier]) {
Expand Down Expand Up @@ -201,9 +203,11 @@ export class PublicationViewConverter {
// and apply convertMultiLangStringToString() only downstream / at rendering time.
const publishers = convertContributorArrayToStringArray(
r2Publication.Metadata.Publisher,
this.store.getState().i18n.locale,
);
const authors = convertContributorArrayToStringArray(
r2Publication.Metadata.Author,
this.store.getState().i18n.locale,
);

let publishedAt: string | undefined;
Expand Down
Loading