Skip to content

Commit

Permalink
feat(h5p-server)!: player strings now localized (#1747)
Browse files Browse the repository at this point in the history
BREAKING CHANGE:
The signature of H5PPlayer.render was changed as there now is a language parameter that you can set.
  • Loading branch information
sr258 committed Sep 11, 2021
1 parent 4bf2433 commit 3bf9841
Show file tree
Hide file tree
Showing 13 changed files with 211 additions and 51 deletions.
2 changes: 2 additions & 0 deletions docs/advanced/customization.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ const player = new H5PPlayer(
config,
integrationObjectDefaults, // set to undefined if unneeded
urlGenerator, // set to undefined if unneeded
translationFunction,
{
customization: {
global: {
Expand Down Expand Up @@ -188,6 +189,7 @@ const player = new H5PPlayer(
contentStorage,
config,
integrationObjectDefaults, // set to undefined if unneeded
translationFunction,
urlGenerator, // set to undefined if unneeded
{
customization: {
Expand Down
5 changes: 4 additions & 1 deletion packages/h5p-examples/src/express.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,10 @@ const start = async (): Promise<void> => {
const h5pPlayer = new H5P.H5PPlayer(
h5pEditor.libraryStorage,
h5pEditor.contentStorage,
config
config,
undefined,
undefined,
(key, language) => translationFunction(key, { lng: language })
);

// We now set up the Express server in the usual fashion.
Expand Down
3 changes: 3 additions & 0 deletions packages/h5p-examples/src/expressRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ export default function (
const h5pPage = await h5pPlayer.render(
req.params.contentId,
req.user,
languageOverride === 'auto'
? req.language ?? 'en'
: languageOverride,
{
showCopyButton: true,
showDownloadButton: true,
Expand Down
5 changes: 4 additions & 1 deletion packages/h5p-rest-example-server/src/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ export default function (
try {
const content = await h5pPlayer.render(
req.params.contentId,
new User()
new User(),
languageOverride === 'auto'
? req.language ?? 'en'
: languageOverride
);
res.send(content);
res.status(200).end();
Expand Down
99 changes: 98 additions & 1 deletion packages/h5p-server/assets/defaultClientStrings.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,5 +65,102 @@
"offlineDialogBody": "client:offlineDialogBody",
"offlineDialogRetryMessage": "client:offlineDialogRetryMessage",
"offlineDialogRetryButtonLabel": "client:offlineDialogRetryButtonLabel",
"offlineSuccessfulSubmit": "client:offlineSuccessfulSubmit"
"offlineSuccessfulSubmit": "client:offlineSuccessfulSubmit",
"mainTitle": "client:mainTitle",
"editInfoTitle": "client:editInfoTitle",
"cancel": "client:cancel",
"back": "client:back",
"next": "client:next",
"reviewInfo": "client:reviewInfo",
"share": "client:share",
"saveChanges": "client:saveChanges",
"registerOnHub": "client:registerOnHub",
"updateRegistrationOnHub": "client:updateRegistrationOnHub",
"requiredInfo": "client:requiredInfo",
"optionalInfo": "client:optionalInfo",
"reviewAndShare": "client:reviewAndShare",
"reviewAndSave": "client:reviewAndSave",
"shared": "client:shared",
"currentStep": "client:currentStep",
"sharingNote": "client:sharingNote",
"licenseDescription": "client:licenseDescription",
"licenseVersion": "client:licenseVersion",
"licenseVersionDescription": "client:licenseVersionDescription",
"disciplineLabel": "client:disciplineLabel",
"disciplineDescription": "client:disciplineDescription",
"disciplineLimitReachedMessage": "client:disciplineLimitReachedMessage",
"discipline": {
"searchPlaceholder": "client:discipline.searchPlaceholder",
"in": "client:discipline.in",
"dropdownButton": "client:discipline.dropdownButton"
},
"removeChip": "client:removeChip",
"keywordsPlaceholder": "client:keywordsPlaceholder",
"keywords": "client:keywords",
"keywordsDescription": "client:keywordsDescription",
"altText": "client:altText",
"reviewMessage": "client:reviewMessage",
"subContentWarning": "client:subContentWarning",
"disciplines": "client:disciplines",
"shortDescription": "client:shortDescription",
"longDescription": "client:longDescription",
"icon": "client:icon",
"screenshots": "client:screenshots",
"helpChoosingLicense": "client:helpChoosingLicense",
"shareFailed": "client:shareFailed",
"editingFailed": "client:editingFailed",
"shareTryAgain": "client:shareTryAgain",
"pleaseWait": "client:pleaseWait",
"language": "client:language",
"level": "client:level",
"shortDescriptionPlaceholder": "client:shortDescriptionPlaceholder",
"longDescriptionPlaceholder": "client:longDescriptionPlaceholder",
"description": "client:description",
"iconDescription": "client:iconDescription",
"screenshotsDescription": "client:screenshotsDescription",
"submitted": "client:submitted",
"isNowSubmitted": "client:isNowSubmitted",
"changeHasBeenSubmitted": "client:changeHasBeenSubmitted",
"contentAvailable": "client:contentAvailable",
"contentUpdateSoon": "client:contentUpdateSoon",
"contentLicenseTitle": "client:contentLicenseTitle",
"licenseDialogDescription": "client:licenseDialogDescription",
"publisherFieldTitle": "client:publisherFieldTitle",
"publisherFieldDescription": "client:publisherFieldDescription",
"emailAddress": "client:emailAddress",
"publisherDescription": "client:publisherDescription",
"publisherDescriptionText": "client:publisherDescriptionText",
"contactPerson": "client:contactPerson",
"phone": "client:phone",
"address": "client:address",
"city": "client:city",
"zip": "client:zip",
"country": "client:country",
"logoUploadText": "client:logoUploadText",
"acceptTerms": "client:acceptTerms",
"successfullyRegistred": "client:successfullyRegistred",
"successfullyRegistredDescription": "client:successfullyRegistredDescription",
"successfullyUpdated": "client:successfullyUpdated",
"accountDetailsLinkText": "client:accountDetailsLinkText",
"registrationTitle": "client:registrationTitle",
"registrationFailed": "client:registrationFailed",
"registrationFailedDescription": "client:registrationFailedDescription",
"maxLength": "client:maxLength",
"keywordExists": "client:keywordExists",
"licenseDetails": "client:licenseDetails",
"remove": "client:remove",
"removeImage": "client:removeImage",
"cancelPublishConfirmationDialogTitle": "client:cancelPublishConfirmationDialogTitle",
"cancelPublishConfirmationDialogDescription": "client:cancelPublishConfirmationDialogDescription",
"cancelPublishConfirmationDialogCancelButtonText": "client:cancelPublishConfirmationDialogCancelButtonText",
"cancelPublishConfirmationDialogConfirmButtonText": "client:cancelPublishConfirmationDialogConfirmButtonText",
"add": "client:add",
"age": "client:age",
"ageDescription": "client:ageDescription",
"invalidAge": "client:invalidAge",
"contactPersonDescription": "client:contactPersonDescription",
"emailAddressDescription": "client:emailAddressDescription",
"copyrightWarning": "client:copyrightWarning",
"keywordsExits": "client:keywordsExits",
"someKeywordsExits": "client:someKeywordsExits"
}
14 changes: 14 additions & 0 deletions packages/h5p-server/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/h5p-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
"cache-manager": "^3.3.0",
"crc": "^3.8.0",
"debug": "^4.1.1",
"flat": "^5.0.2",
"fs-extra": "^9.0.0",
"get-all-files": "^3.0.0",
"https-proxy-agent": "^5.0.0",
Expand Down
31 changes: 26 additions & 5 deletions packages/h5p-server/src/H5PPlayer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,21 @@ import {
IPlayerModel,
IUrlGenerator,
ILibraryMetadata,
IUser
IUser,
ITranslationFunction
} from './types';
import UrlGenerator from './UrlGenerator';
import Logger from './helpers/Logger';
import { ContentMetadata } from './ContentMetadata';

import defaultTranslation from '../assets/translations/client/en.json';
import defaultClientStrings from '../assets/defaultClientStrings.json';
import englishClientStrings from '../assets/translations/client/en.json';
import playerAssetList from './playerAssetList.json';
import player from './renderers/player';
import H5pError from './helpers/H5pError';
import LibraryManager from './LibraryManager';
import SemanticsLocalizer from './SemanticsLocalizer';
import SimpleTranslator from './helpers/SimpleTranslator';

const log = new Logger('Player');

Expand All @@ -38,6 +42,10 @@ export default class H5PPlayer {
* the integration object
* @param urlGenerator creates url strings for files, can be used to
* customize the paths in an implementation application
* @param translationCallback a function that is called to retrieve
* translations of keys in a certain language; the keys use the i18next
* format (e.g. namespace:key). See the ITranslationFunction documentation
* for more details.
* @param options more options to customize the behavior of the player; see
* IH5PPlayerOptions documentation for more details
*/
Expand All @@ -47,11 +55,15 @@ export default class H5PPlayer {
private config: IH5PConfig,
private integrationObjectDefaults?: IIntegration,
private urlGenerator: IUrlGenerator = new UrlGenerator(config),
translationCallback: ITranslationFunction = new SimpleTranslator({
// We use a simplistic translation function that is hard-wired to
// English if the implementation does not pass us a proper one.
client: englishClientStrings
}).t,
private options?: IH5PPlayerOptions
) {
log.info('initialize');
this.renderer = player;
this.clientTranslation = defaultTranslation;
this.libraryManager = new LibraryManager(
libraryStorage,
urlGenerator.libraryFile
Expand All @@ -72,8 +84,10 @@ export default class H5PPlayer {
this.config.customization.global.player.styles
);
}

this.semanticsLocalizer = new SemanticsLocalizer(translationCallback);
}
private clientTranslation: any;
private semanticsLocalizer: SemanticsLocalizer;
private globalCustomScripts: string[] = [];
private globalCustomStyles: string[] = [];
private libraryManager: LibraryManager;
Expand Down Expand Up @@ -101,6 +115,7 @@ export default class H5PPlayer {
public async render(
contentId: ContentId,
user: IUser,
language: string = 'en',
options?: {
ignoreUserPermissions?: boolean;
metadataOverride?: ContentMetadata;
Expand Down Expand Up @@ -193,6 +208,7 @@ export default class H5PPlayer {
assets,
mainLibrarySupportsFullscreen,
user,
language,
{
showCopyButton: options?.showCopyButton ?? false,
showDownloadButton: options?.showDownloadButton ?? false,
Expand Down Expand Up @@ -326,6 +342,7 @@ export default class H5PPlayer {
assets: IAssets,
supportsFullscreen: boolean,
user: IUser,
language: string,
displayOptions: {
showCopyButton: boolean;
showDownloadButton: boolean;
Expand Down Expand Up @@ -373,7 +390,11 @@ export default class H5PPlayer {
styles: this.listCoreStyles()
},
l10n: {
H5P: this.clientTranslation
H5P: this.semanticsLocalizer.localize(
defaultClientStrings,
language,
true
)
},
libraryConfig: this.config.libraryConfig,
postUserStatistics: false,
Expand Down
15 changes: 9 additions & 6 deletions packages/h5p-server/src/SemanticsLocalizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,15 @@ export default class SemanticsLocalizer {
} else {
for (const field in semantics) {
if (
typeof semantics[field] === 'object' &&
typeof semantics[field] !== 'string'
) {
copy[field] = this.walkSemanticsRecursive(
semantics[field],
language,
localizeAllFields
);
} else if (
(this.localizableFields.includes(field) &&
typeof semantics[field] === 'string') ||
localizeAllFields
Expand All @@ -57,12 +66,6 @@ export default class SemanticsLocalizer {
`Replacing "${semantics[field]}" with "${translated}"`
);
copy[field] = translated;
} else if (typeof semantics[field] === 'object') {
copy[field] = this.walkSemanticsRecursive(
semantics[field],
language,
localizeAllFields
);
} else {
copy[field] = semantics[field];
}
Expand Down
30 changes: 20 additions & 10 deletions packages/h5p-server/src/helpers/SimpleTranslator.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { flatten } from 'flat';

type NestedStructure = string | { [key: string]: NestedStructure };

/**
Expand All @@ -11,25 +13,33 @@ export default class SimpleTranslator {
* @param translationStrings an object containing all relevant translation strings
* sorted by namespaces
*/
constructor(
private translationStrings:
| {
[namespace: string]: { [key: string]: NestedStructure };
}
| { [key: string]: NestedStructure }
) {}
constructor(translationStrings: {
[namespace: string]: { [key: string]: NestedStructure };
}) {
this.translationStrings = {};
for (const namespace of Object.keys(translationStrings)) {
this.translationStrings[namespace] = flatten(
translationStrings[namespace]
);
}
}

private translationStrings: {
[namespace: string]: { [key: string]: any };
};

/**
* Translates a string using the key (identified).
* @params key the key with optional namespace separated by a colon (e.g. namespace:key)
* @params key the key with optional namespace separated by a colon (e.g.
* namespace:key)
* @returns the translated string
* @memberof SimpleTranslator
*/
public t = (key: string): string => {
const matches = /^(.+):(.+)$/.exec(key);
if (matches.length > 0) {
if (matches?.length > 0) {
return this.translationStrings[matches[1]][matches[2]] ?? key;
}
return this.translationStrings[key] as string;
return key;
};
}

0 comments on commit 3bf9841

Please sign in to comment.