Skip to content

Commit

Permalink
ENH Add localization support
Browse files Browse the repository at this point in the history
  • Loading branch information
Sabina Talipova committed Nov 13, 2023
1 parent 369b619 commit ce6d032
Show file tree
Hide file tree
Showing 14 changed files with 189 additions and 188 deletions.
2 changes: 1 addition & 1 deletion client/dist/js/bundle.js

Large diffs are not rendered by default.

76 changes: 6 additions & 70 deletions client/lang/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,74 +6,10 @@ if (typeof(ss) === 'undefined' || typeof(ss.i18n) === 'undefined') {
}
} else {
ss.i18n.addDictionary('en', {
"AssetAdmin.ADD_FOLDER_BUTTON": "Add folder",
"AssetAdmin.BACK": "Back",
"AssetAdmin.BACK_DESCRIPTION": "Navigate up a level",
"AssetAdmin.BULK_ACTIONS_CONFIRM": "Are you sure you want to %s these files?",
"AssetAdmin.BULK_ACTIONS_DELETE": "Delete",
"AssetAdmin.BULK_ACTIONS_DELETE_CONFIRM": "Are you sure you want to delete these files?",
"AssetAdmin.BULK_ACTIONS_DELETE_FAIL": "%s folders/files were successfully archived, but %s files were not able to be archived.",
"AssetAdmin.BULK_ACTIONS_DELETE_FOLDER": "These folders contain files which are currently in use, you must move or delete their contents before you can delete the folder.",
"AssetAdmin.BULK_ACTIONS_DELETE_MULTI_CONFIRM": "There are %s files currently in use, are you sure you want to delete these files?",
"AssetAdmin.BULK_ACTIONS_DELETE_SINGLE_CONFIRM": "This file is currently in use in %s places, are you sure you want to delete it?",
"AssetAdmin.BULK_ACTIONS_DELETE_SUCCESS": "%s folders/files were successfully archived.",
"AssetAdmin.BULK_ACTIONS_PLACEHOLDER": "Select an action...",
"AssetAdmin.CANCEL": "Cancel",
"AssetAdmin.CONFIRMDELETE": "Are you sure you want to delete this record?",
"AssetAdmin.CREATED": "First uploaded",
"AssetAdmin.DELETE": "Delete",
"AssetAdmin.DIM": "Dimensions",
"AssetAdmin.DROPZONE_CANCEL_UPLOAD": "Cancel upload",
"AssetAdmin.DROPZONE_CANCEL_UPLOAD_CONFIRMATION": "Are you sure you want to cancel this upload?",
"AssetAdmin.DROPZONE_DEFAULT_MESSAGE": "Drop files here to upload",
"AssetAdmin.DROPZONE_FAILED_UPLOAD": "Failed to upload file",
"AssetAdmin.DROPZONE_FALLBACK_MESSAGE": "Your browser does not support drag'n'drop file uploads.",
"AssetAdmin.DROPZONE_FALLBACK_TEXT": "Please use the fallback form below to upload your files like in the olden days.",
"AssetAdmin.DROPZONE_FILE_TOO_BIG": "File is too big. Max filesize: %sMiB.",
"AssetAdmin.DROPZONE_INVALID_FILE_TYPE": "You can't upload files of this type.",
"AssetAdmin.DROPZONE_MAX_FILES_EXCEEDED": "You can not upload any more files.",
"AssetAdmin.DROPZONE_REMOVE_FILE": "Remove file",
"AssetAdmin.DROPZONE_RESPONSE_ERROR": "Server responded with an error.",
"AssetAdmin.DROPZONE_SUCCESS_UPLOAD": "File uploaded",
"AssetAdmin.DROPZONE_UPLOAD": "Upload",
"AssetAdmin.EDIT": "Edit",
"AssetAdmin.FILENAME": "Filename",
"AssetAdmin.FILES": "Files",
"AssetAdmin.FILE_MISSING": "File cannot be found",
"AssetAdmin.FILTER_DATE_ASC": "oldest",
"AssetAdmin.FILTER_DATE_DESC": "newest",
"AssetAdmin.FILTER_TITLE_ASC": "title a-z",
"AssetAdmin.FILTER_TITLE_DESC": "title z-a",
"AssetAdmin.JOIN": ",",
"AssetAdmin.JOINLAST": "and",
"AssetAdmin.LASTEDIT": "Last changed",
"AssetAdmin.LOADMORE": "Load more",
"AssetAdmin.NOITEMSFOUND": "No items found",
"AssetAdmin.PROMPTFOLDERNAME": "Please enter a folder name (or blank to cancel)",
"AssetAdmin.REPlACE_FILE_SUCCESS": "Upload successful, the file will be replaced when you Save.",
"AssetAdmin.SAVE": "Save",
"AssetAdmin.SEARCHCLEARRESULTS": "Clear search",
"AssetAdmin.SEARCHRESULTS": "Search results",
"AssetAdmin.SEARCHRESULTSMESSAGE": "Search results {parts}",
"AssetAdmin.SEARCHRESULTSMESSAGECATEGORY": "categorised as '{appCategory}'",
"AssetAdmin.SEARCHRESULTSMESSAGEEDITEDBETWEEN": "last edited between '{lastEditedFrom}' and '{lastEditedTo}'",
"AssetAdmin.SEARCHRESULTSMESSAGEEDITEDFROM": "last edited from '{lastEditedFrom}'",
"AssetAdmin.SEARCHRESULTSMESSAGEEDITEDTO": "last edited before '{lastEditedTo}'",
"AssetAdmin.SEARCHRESULTSMESSAGEKEYWORDS": "with keywords '{name}'",
"AssetAdmin.SEARCHRESULTSMESSAGELIMIT": "limited to the folder '{folder}'",
"AssetAdmin.SELECT": "Select",
"AssetAdmin.SIZE": "Size",
"AssetAdmin.TITLE": "Title",
"AssetAdmin.TYPE": "File type",
"AssetAdmin.URL": "URL",
"AssetAdmin.ADD_FILES": "Add from files",
"AssetAdmin.BROWSE": "Browse",
"AssetAdmin.EMPTY": "No files",
"AssetAdmin.OR": "or",
"AssetAdmin.ERROR_OEMBED_REMOTE": "Embed is only compatible with remote files",
"AssetAdmin.CreateTitle": "Insert new media from the web",
"AssetAdmin.EditTitle": "Media from the web",
"AssetAdmin.NEXT": "Next",
"AssetAdmin.PREVIOUS": "Previous"
});
"LinkField.SAVE_SUCCESS": "Saved link",
"LinkField.CONFIRM_DELETE": "Deleted link",
"LinkField.DELETE_ERROR": "Failed to delete link",
"LinkField.ADD_LINK": "Add Link",
"LinkField.CLEAR": "Clear"
});
}
78 changes: 5 additions & 73 deletions client/lang/src/en.json
Original file line number Diff line number Diff line change
@@ -1,75 +1,7 @@
{
"AssetAdmin.ADD_FOLDER_BUTTON": "Add folder",
"AssetAdmin.BACK": "Back",
"AssetAdmin.BACK_DESCRIPTION": "Navigate up a level",
"AssetAdmin.BULK_ACTIONS_CONFIRM": "Are you sure you want to %s these files?",
"AssetAdmin.BULK_ACTIONS_DELETE": "Delete",
"AssetAdmin.BULK_ACTIONS_DELETE_SINGLE_ITEM_CONFIRM": "Are you sure you want to delete this file/folder?",
"AssetAdmin.BULK_ACTIONS_DELETE_MULTIPLE_ITEMS_CONFIRM": "Are you sure you want to delete these files/folders?",
"AssetAdmin.BULK_ACTIONS_DELETE_MULTIPLE_ITEMS_IN_USE_CONFIRM": "%s item(s) are currently in use in %s place(s). Are you sure you want to delete them?",
"AssetAdmin.BULK_ACTIONS_DELETE_SINGLE_FILE_IN_USE_CONFIRM": "This file is currently in use in %s place(s). Are you sure you want to delete it?",
"AssetAdmin.BULK_ACTIONS_DELETE_MULTIPLE_FILES_IN_USE_CONFIRM": "%s of these files are currently used in %s place(s). Are you sure you want to delete them?",
"AssetAdmin.BULK_ACTIONS_DELETE_SINGLE_FOLDER_IN_USE_CONFIRM": "This folder contains file(s) that are currently used in %s place(s). Are you sure you want to delete it?",
"AssetAdmin.BULK_ACTIONS_DELETE_MULTIPLE_FOLDERS_IN_USE_CONFIRM": "%s of these folders contain file(s) that are currently used in %s place(s). Are you sure you want to delete them?",
"AssetAdmin.BULK_ACTIONS_DELETE_WARNING": "Ensure files are removed from content areas prior to deleting them, otherwise they will appear as broken links.",
"AssetAdmin.BULK_ACTIONS_DELETE_FAIL": "%s folders/files were successfully archived, but %s files were not able to be archived.",
"AssetAdmin.BULK_ACTIONS_DELETE_SUCCESS": "%s folders/files were successfully archived.",
"AssetAdmin.BULK_ACTIONS_PLACEHOLDER": "Select an action...",
"AssetAdmin.CANCEL": "Cancel",
"AssetAdmin.CONFIRMDELETE": "Are you sure you want to delete this record?",
"AssetAdmin.CREATED": "First uploaded",
"AssetAdmin.DELETE": "Delete",
"AssetAdmin.DIM": "Dimensions",
"AssetAdmin.DROPZONE_CANCEL_UPLOAD": "Cancel upload",
"AssetAdmin.DROPZONE_CANCEL_UPLOAD_CONFIRMATION": "Are you sure you want to cancel this upload?",
"AssetAdmin.DROPZONE_DEFAULT_MESSAGE": "Drop files here to upload",
"AssetAdmin.DROPZONE_FAILED_UPLOAD": "Failed to upload file",
"AssetAdmin.DROPZONE_FALLBACK_MESSAGE": "Your browser does not support drag'n'drop file uploads.",
"AssetAdmin.DROPZONE_FALLBACK_TEXT": "Please use the fallback form below to upload your files like in the olden days.",
"AssetAdmin.DROPZONE_FILE_TOO_BIG": "File is too big. Max filesize: %sMiB.",
"AssetAdmin.DROPZONE_INVALID_FILE_TYPE": "You can't upload files of this type.",
"AssetAdmin.DROPZONE_MAX_FILES_EXCEEDED": "You can not upload any more files.",
"AssetAdmin.DROPZONE_REMOVE_FILE": "Remove file",
"AssetAdmin.DROPZONE_RESPONSE_ERROR": "Server responded with an error.",
"AssetAdmin.DROPZONE_SUCCESS_UPLOAD": "File uploaded",
"AssetAdmin.DROPZONE_UPLOAD": "Upload",
"AssetAdmin.EDIT": "Edit",
"AssetAdmin.FILENAME": "Filename",
"AssetAdmin.FILES": "Files",
"AssetAdmin.FILE_MISSING": "File cannot be found",
"AssetAdmin.FILTER_DATE_ASC": "oldest",
"AssetAdmin.FILTER_DATE_DESC": "newest",
"AssetAdmin.FILTER_TITLE_ASC": "title a-z",
"AssetAdmin.FILTER_TITLE_DESC": "title z-a",
"AssetAdmin.JOIN": ",",
"AssetAdmin.JOINLAST": "and",
"AssetAdmin.LASTEDIT": "Last changed",
"AssetAdmin.LOADMORE": "Load more",
"AssetAdmin.NOITEMSFOUND": "No items found",
"AssetAdmin.PROMPTFOLDERNAME": "Please enter a folder name (or blank to cancel)",
"AssetAdmin.REPlACE_FILE_SUCCESS": "Upload successful, the file will be replaced when you Save.",
"AssetAdmin.SAVE": "Save",
"AssetAdmin.SEARCHCLEARRESULTS": "Clear search",
"AssetAdmin.SEARCHRESULTS": "Search results",
"AssetAdmin.SEARCHRESULTSMESSAGE": "Search results {parts}",
"AssetAdmin.SEARCHRESULTSMESSAGECATEGORY": "categorised as '{appCategory}'",
"AssetAdmin.SEARCHRESULTSMESSAGEEDITEDBETWEEN": "last edited between '{lastEditedFrom}' and '{lastEditedTo}'",
"AssetAdmin.SEARCHRESULTSMESSAGEEDITEDFROM": "last edited from '{lastEditedFrom}'",
"AssetAdmin.SEARCHRESULTSMESSAGEEDITEDTO": "last edited before '{lastEditedTo}'",
"AssetAdmin.SEARCHRESULTSMESSAGEKEYWORDS": "with keywords '{name}'",
"AssetAdmin.SEARCHRESULTSMESSAGELIMIT": "limited to the folder '{folder}'",
"AssetAdmin.SELECT": "Select",
"AssetAdmin.SIZE": "Size",
"AssetAdmin.TITLE": "Title",
"AssetAdmin.TYPE": "File type",
"AssetAdmin.URL": "URL",
"AssetAdmin.ADD_FILES": "Add from files",
"AssetAdmin.BROWSE": "Browse",
"AssetAdmin.EMPTY": "No files",
"AssetAdmin.OR": "or",
"AssetAdmin.ERROR_OEMBED_REMOTE": "Embed is only compatible with remote files",
"AssetAdmin.CreateTitle": "Insert new media from the web",
"AssetAdmin.EditTitle": "Media from the web",
"AssetAdmin.NEXT": "Next",
"AssetAdmin.PREVIOUS": "Previous"
"LinkField.SAVE_SUCCESS": "Saved link",
"LinkField.CONFIRM_DELETE": "Deleted link",
"LinkField.DELETE_ERROR": "Failed to delete link",
"LinkField.ADD_LINK": "Add Link",
"LinkField.CLEAR": "Clear"
}
1 change: 0 additions & 1 deletion client/src/boot/registerComponents.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

/* eslint-disable */
import Injector from 'lib/Injector';
import LinkPicker from 'components/LinkPicker/LinkPicker';
Expand Down
23 changes: 20 additions & 3 deletions client/src/components/LinkField/LinkField.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable */
import React, { useState, useEffect } from 'react';
import { bindActionCreators, compose } from 'redux';
import { connect } from 'react-redux';
Expand All @@ -8,6 +9,7 @@ import * as toastsActions from 'state/toasts/ToastsActions';
import backend from 'lib/Backend';
import Config from 'lib/Config';
import PropTypes from 'prop-types';
import i18n from 'i18n';

// section used in window.ss config
const section = 'SilverStripe\\LinkField\\Controllers\\LinkFieldController';
Expand Down Expand Up @@ -49,7 +51,12 @@ const LinkField = ({ value, onChange, types, actions }) => {
onChange(valueFromSchemaResponse);

// success toast
actions.toasts.success('Saved link');
actions.toasts.success(
i18n._t(
'LinkField.SAVE_SUCCESS',
'Saved link',
)
);
}

return Promise.resolve();
Expand All @@ -63,10 +70,20 @@ const LinkField = ({ value, onChange, types, actions }) => {
// CSRF token 'X-SecurityID' headers needs to be present for destructive requests
backend.delete(endpoint, {}, { 'X-SecurityID': Config.get('SecurityID') })
.then(() => {
actions.toasts.success('Deleted link');
actions.toasts.success(
i18n._t(
'LinkField.DELETE_SUCCESS',
'Deleted link',
)
);
})
.catch(() => {
actions.toasts.error('Failed to delete link');
actions.toasts.error(
i18n._t(
'LinkField.DELETE_ERROR',
'Failed to delete link',
)
);
});

// update component state
Expand Down
2 changes: 1 addition & 1 deletion client/src/components/LinkPicker/LinkPickerMenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const LinkPickerMenu = ({ types, onSelect }) => {
toggle={toggle}
className="link-picker__menu"
>
<DropdownToggle className="link-picker__menu-toggle font-icon-link" caret>{i18n._t('Link.ADD_LINK', 'Add Link')}</DropdownToggle>
<DropdownToggle className="link-picker__menu-toggle font-icon-link" caret>{i18n._t('LinkField.ADD_LINK', 'Add Link')}</DropdownToggle>
<DropdownMenu>
{types.map(({key, title}) =>
<DropdownItem key={key} onClick={() => onSelect(key)}>{title}</DropdownItem>
Expand Down
2 changes: 1 addition & 1 deletion client/src/components/LinkPicker/LinkPickerTitle.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const LinkPickerTitle = ({ title, description, typeTitle, onClear, onClick }) =>
</small>
</div>
</Button>
<Button className="link-picker__clear" color="link" onClick={stopPropagation(onClear)}>{i18n._t('Link.CLEAR', 'Clear')}</Button>
<Button className="link-picker__clear" color="link" onClick={stopPropagation(onClear)}>{i18n._t('LinkField.CLEAR', 'Clear')}</Button>
</div>
);

Expand Down
26 changes: 26 additions & 0 deletions lang/en.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
en:
LinkField:
UPDATELINK: 'Update link'
INVALIDID': 'Invalid ID'
UNAUTHORIZED: 'Unauthorized'
INVALIDTYPEKEY: 'Invalid typeKey'
INVALIDTOKEN: 'Invalid CSRF token'
EMPTYDATA: 'Empty data'
BADDATA: 'Bad data'
CREATELINK: 'Create link'
KEYSARENOTARRAY: 'If keys is provdied, it must be an array'
LINKTYPETITLE: 'Link Type'
INVALIDJSON: '"{class}": Decoding json string failred with "{error}"'
INVALIDDATATOARRAY: '"{class}": Could not convert $data to an array.'
DATAHASNOTYPEKEY: '"{class}": $data does not have a typeKey.'
NOTREGISTEREDLINKTYPE: '"{class}": "{typekey}" is not a registered Link Type.'
TITLEDESCRIPTION: 'Auto generated from Page title if left blank'
PAGEFIELDTITLE: 'Page'
QUERYSTRINGDESCRIPTION: 'Do not prepend "?". EG: "option1=value&option2=value2"'
ANCHORDESCRIPTION: 'Do not prepend "#". Anchor suggestions will be displayed once the linked page is attached.'
VERSIONEDSTATUSMISMATCH: 'Linkable and LinkField do not have matching Versioned applications. Make sure that both are either un-Versioned or Versioned'
NOTHINGTOPROCESS: "Nothing to process for {table}\r\n"
PROCESSINGTABLE: "Processing {table}\r\n"
RECORDSINSERTED: "{numrecords} records inserted, finished processing {table}\r\n"
NOCLASSNAME: '"{class}": All types should reference a valid classname'
INVALIDTYPENAME: '"{class}": {typename} is not a valid link type'
36 changes: 19 additions & 17 deletions src/Controllers/LinkFieldController.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,17 +64,17 @@ public function linkForm(): Form
if ($id) {
$link = Link::get()->byID($id);
if (!$link) {
$this->jsonError(404, 'Invalid ID');
$this->jsonError(404, _t('LinkField.INVALIDID', 'Invalid ID'));
}
$operation = 'edit';
if (!$link->canView()) {
$this->jsonError(403, 'Unauthorized');
$this->jsonError(403, _t('LinkField.UNAUTHORIZED', 'Unauthorized'));
}
} else {
$typeKey = $this->typeKeyFromRequest();
$link = Registry::create()->byKey($typeKey);
if (!$link) {
$this->jsonError(404, 'Invalid typeKey');
$this->jsonError(404, _t('LinkField.INVALIDTYPEKEY', 'Invalid typeKey'));
}
$operation = 'create';
}
Expand All @@ -89,7 +89,7 @@ public function linkData(): HTTPResponse
{
$link = $this->linkFromRequest();
if (!$link->canView()) {
$this->jsonError(403, 'Unauthorized');
$this->jsonError(403, _t('LinkField.UNAUTHORIZED', 'Unauthorized'));
}
$response = $this->getResponse();
$response->addHeader('Content-type', 'application/json');
Expand All @@ -107,11 +107,11 @@ public function linkDelete(): HTTPResponse
{
$link = $this->linkFromRequest();
if (!$link->canDelete()) {
$this->jsonError(403, 'Unauthorized');
$this->jsonError(403, _t('LinkField.UNAUTHORIZED', 'Unauthorized'));
}
// Check security token on destructive operation
if (!SecurityToken::inst()->checkRequest($this->getRequest())) {
$this->jsonError(400, 'Invalid CSRF token');
$this->jsonError(400, _t('LinkField.INVALIDTOKEN', 'Invalid CSRF token'));
}
// delete() will also delete any published version immediately
$link->delete();
Expand All @@ -138,7 +138,7 @@ public function getLinkForm(): Form
public function save(array $data, Form $form): HTTPResponse
{
if (empty($data)) {
$this->jsonError(400, 'Empty data');
$this->jsonError(400, _t('LinkField.EMPTYDATA','Empty data'));
}

/** @var Link $link */
Expand All @@ -148,28 +148,28 @@ public function save(array $data, Form $form): HTTPResponse
$operation = 'edit';
$link = Link::get()->byID($id);
if (!$link) {
$this->jsonErorr(404, 'Invalid ID');
$this->jsonErorr(404, _t('LinkField.INVALIDID', 'Invalid ID'));
}
if (!$link->canEdit()) {
$this->jsonError(403, 'Unauthorized');
$this->jsonError(403, _t('LinkField.UNAUTHORIZED', 'Unauthorized'));
}
} else {
// Creating a new Link
$operation = 'create';
$typeKey = $this->typeKeyFromRequest();
$className = Registry::create()->list()[$typeKey] ?? '';
if (!$className) {
$this->jsonError(404, 'Invalid typeKey');
$this->jsonError(404, _t('LinkField.INVALIDTYPEKEY', 'Invalid typeKey'));
}
$link = $className::create();
if (!$link->canCreate()) {
$this->jsonError(403, 'Unauthorized');
$this->jsonError(403, _t('LinkField.UNAUTHORIZED', 'Unauthorized'));
}
}

// Ensure that ItemID url param matches the ID in the form data
if (isset($data['ID']) && ((int) $data['ID'] !== $id)) {
$this->jsonError(400, 'Bad data');
$this->jsonError(400, _t('LinkField.BADDATA', 'Bad data'));
}

// Update DataObject from form data
Expand Down Expand Up @@ -226,7 +226,9 @@ private function createLinkForm(Link $link, string $operation): Form
$form->setFormAction($this->Link("linkForm/$id?typeKey=$typeKey"));

// Add save action button
$title = $id ? 'Update link' : 'Create link'; // todo: _t()
$title = $id
? _t('LinkField.UPDATELINK', 'Update link')
: _t('LinkField.CREATELINK', 'Create link');
$actions = FieldList::create([
FormAction::create('save', $title)
->setSchemaData(['data' => ['buttonStyle' => 'primary']]),
Expand Down Expand Up @@ -263,11 +265,11 @@ private function linkFromRequest(): Link
{
$itemID = (int) $this->itemIDFromRequest();
if (!$itemID) {
$this->jsonError(404, 'Invalid ID');
$this->jsonError(404, _t('LinkField.INVALIDID', 'Invalid ID'));
}
$link = Link::get()->byID($itemID);
if (!$link) {
$this->jsonError(404, 'Invalid ID');
$this->jsonError(404, _t('LinkField.INVALIDID', 'Invalid ID'));
}
return $link;
}
Expand All @@ -280,7 +282,7 @@ private function itemIDFromRequest(): string
$request = $this->getRequest();
$itemID = (string) $request->param('ItemID');
if (!ctype_digit($itemID)) {
$this->jsonError(404, 'Invalid ID');
$this->jsonError(404, _t('LinkField.INVALIDID', 'Invalid ID'));
}
return $itemID;
}
Expand All @@ -293,7 +295,7 @@ private function typeKeyFromRequest(): string
$request = $this->getRequest();
$typeKey = (string) $request->getVar('typeKey');
if (strlen($typeKey) === 0 || !preg_match('#^[a-z\-]+$#', $typeKey)) {
$this->jsonError(404, 'Invalid typeKey');
$this->jsonError(404, _t('LinkField.INVALIDTYPEKEY', 'Invalid typeKey'));
}
return $typeKey;
}
Expand Down
Loading

0 comments on commit ce6d032

Please sign in to comment.