Skip to content
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
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
- Support conditionally rendering `WorkspaceLayout*` child components by passing `null` instead of a child to remove it:
* Allow to hide left and right panels in `ClassicWorkspace` by passing `null` to the corresponding props;
* Allow to provide `className` and `style` to `WorkspaceLayout*` components.
- Add `Translation.textOptional()` to support common translation string defaults with ability to provide separate string for each case:
* Use `search_defaults.input.placeholder` for a search input field;
* Use `visual_authoring.dialog.apply.{label, text}` for an "Apply" button in `VisualAuthoring` dialogs.

#### 💅 Polish
- Expose `useAsync()` utility hook to simplify data loading from via a single Promise-returning task.
- Provide `onlySelected` property to link templates the same way as for element templates.
- Allow to configure whether `ClassTree` and `SearchSectionElementTypes` tree items should be draggable.
- Allow to configure `SparqlDataProvider.lookup()` via `lookupQuery` and `filterInnerPrelude` settings.
- Add separate translation keys for input placeholders on every search input field.

#### 🔧 Maintenance
- Preparations to extract generic scrollable paper component `Paper` from diagram-specific state and logic:
Expand Down
10 changes: 9 additions & 1 deletion i18n/i18n.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$defs": {
"Group": { "type": "object" },
"Value": { "type": "string" }
"Value": { "type": ["string", "null"] }
},
"type": "object",
"additionalProperties": false,
Expand Down Expand Up @@ -384,14 +384,20 @@
"dialog.apply.title": { "$ref": "#/$defs/Value" },
"dialog.cancel.label": { "$ref": "#/$defs/Value" },
"dialog.cancel.title": { "$ref": "#/$defs/Value" },
"edit_entity.dialog.apply.label": { "$ref": "#/$defs/Value" },
"edit_entity.dialog.apply.title": { "$ref": "#/$defs/Value" },
"edit_entity.dialog.caption": { "$ref": "#/$defs/Value" },
"edit_entity.iri.label": { "$ref": "#/$defs/Value" },
"edit_entity.label.label": { "$ref": "#/$defs/Value" },
"edit_relation.dialog.apply.label": { "$ref": "#/$defs/Value" },
"edit_relation.dialog.apply.title": { "$ref": "#/$defs/Value" },
"edit_relation.dialog.caption": { "$ref": "#/$defs/Value" },
"edit_relation.dialog.caption_new": { "$ref": "#/$defs/Value" },
"edit_relation.validation_progress.title": { "$ref": "#/$defs/Value" },
"find_or_create.connect_command": { "$ref": "#/$defs/Value" },
"find_or_create.create_command": { "$ref": "#/$defs/Value" },
"find_or_create.dialog.apply.label": { "$ref": "#/$defs/Value" },
"find_or_create.dialog.apply.title": { "$ref": "#/$defs/Value" },
"find_or_create.dialog.caption": { "$ref": "#/$defs/Value" },
"find_or_create.loading.label": { "$ref": "#/$defs/Value" },
"find_or_create.validation_progress.title": { "$ref": "#/$defs/Value" },
Expand All @@ -400,6 +406,8 @@
"property.remove_value.title": { "$ref": "#/$defs/Value" },
"property.text_value.placeholder": { "$ref": "#/$defs/Value" },
"property.title": { "$ref": "#/$defs/Value" },
"rename_link.dialog.apply.label": { "$ref": "#/$defs/Value" },
"rename_link.dialog.apply.title": { "$ref": "#/$defs/Value" },
"rename_link.dialog.caption": { "$ref": "#/$defs/Value" },
"rename_link.label.label": { "$ref": "#/$defs/Value" },
"select_entity.entity_type.label": { "$ref": "#/$defs/Value" },
Expand Down
20 changes: 14 additions & 6 deletions i18n/translations/en.reactodia-translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
"entities.no_results": "No results",
"entities.truncated_results": "Only the first {{limit}} entities are shown",
"entities.truncated_results_expand": "Only the first {{limit}} entities are shown,\nclick here for the full results",
"input.placeholder": "Search for...",
"input.placeholder": null,
"link.both_navigate_title": "Navigate to all connected elements",
"link.both_title": "all connected",
"link.source_navigate_title": "Navigate to source \"{{relation}}\" elements",
Expand Down Expand Up @@ -160,7 +160,7 @@
},
"search_element_types": {
"drag_create.title": "Click or drag to create new entity of this type",
"input.placeholder": "Search for...",
"input.placeholder": null,
"no_results": "No element types found.",
"refresh_progress.title": "Loading element type tree",
"show_only_creatable": "Show only constructible"
Expand All @@ -175,7 +175,7 @@
"criteria_connected_via": "Connected to {{entity}} through {{relationType}}",
"criteria_connected_to_source": "Connected to {{entity}} through {{relationType}} as {{sourceIcon}}\u00A0source",
"criteria_connected_to_target": "Connected to {{entity}} through {{relationType}} as {{targetIcon}}\u00A0target",
"input.placeholder": "Search for...",
"input.placeholder": null,
"place_elements.command": "Add selected elements",
"query_progress.title": "Querying for entities",
"show_more_results.label": "Show more",
Expand All @@ -186,7 +186,7 @@
"heading_connected": "Connected to selected entities",
"heading_connected_single": "Connected to {{entity}}",
"heading_other": "Other links",
"input.placeholder": "Search for...",
"input.placeholder": null,
"no_results": "No links found.",
"switch.command": "Change link types visibility",
"switch_all.label": "Switch all",
Expand Down Expand Up @@ -264,7 +264,7 @@
"undo.title_named": "Undo: {{command}}"
},
"unified_search": {
"input.placeholder": "Search for...",
"input.placeholder": null,
"input_clear.title": "Clear",
"input_collapse.title": "Close"
},
Expand All @@ -273,13 +273,19 @@
"dialog.apply.title": "Apply changes",
"dialog.cancel.label": "Cancel",
"dialog.cancel.title": "Cancel the dialog",
"edit_entity.dialog.apply.label": null,
"edit_entity.dialog.apply.title": null,
"edit_entity.dialog.caption": "Edit entity",
"edit_entity.iri.label": "IRI",
"edit_relation.dialog.apply.label": null,
"edit_relation.dialog.apply.title": null,
"edit_relation.dialog.caption": "Edit relation",
"edit_relation.dialog.caption_new": "Establish new relation",
"edit_relation.validation_progress.title": "Validating selected link type",
"find_or_create.connect_command": "Link to an entity",
"find_or_create.create_command": "Create new entity",
"find_or_create.dialog.apply.label": null,
"find_or_create.dialog.apply.title": null,
"find_or_create.dialog.caption": "Establish New Connection",
"find_or_create.loading.label": "Loading entity...",
"find_or_create.validation_progress.title": "Validating selected element and link types",
Expand All @@ -288,10 +294,12 @@
"property.remove_value.title": "Remove the property value",
"property.text_value.placeholder": "Property value",
"property.title": "{{property}} {{propertyIri}}",
"rename_link.dialog.apply.label": null,
"rename_link.dialog.apply.title": null,
"rename_link.dialog.caption": "Rename Link",
"rename_link.label.label": "Label",
"select_entity.entity_type.label": "{{type}}",
"select_entity.input.placeholder": "Search for...",
"select_entity.input.placeholder": null,
"select_entity.type.label": "Entity Type",
"select_entity.type.placeholder": "Select entity type",
"select_entity.results.aria_label": "Select an existing element to put on a diagram",
Expand Down
22 changes: 17 additions & 5 deletions src/coreUtils/i18n.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,22 +38,34 @@ export type TranslationKey = TranslationKeyOf<DefaultBundleData>;
*
* @category Core
*/
export interface Translation {
export interface Translation<K extends string = TranslationKey> {
/**
* Formats a translation string by replacing placeholders with
* provided values.
*/
text(
key: TranslationKey,
key: K,
placeholders?: Record<string, string | number | boolean>
): string;

/**
* Formats a translation string by replacing placeholders with
* provided values if the string exists.
*
* Returns `undefined` if a translation string is `null` or does exists
* for the specified `key`.
*/
textOptional(
key: K,
placeholders?: Record<string, string | number | boolean>
): string | undefined;

/**
* Templates a translation string into React Fragment by replacing
* placeholders with provided React nodes (elements, etc).
*/
template(
key: TranslationKey,
key: K,
parts: Record<string, React.ReactNode>
): React.ReactNode;

Expand Down Expand Up @@ -201,7 +213,7 @@ type DeepPath<T> = T extends object ? (
*/
export class TranslatedText {
private constructor(
private readonly key: TranslationKey,
private readonly key: string,
private readonly placeholders: Record<string, string | number | boolean> | undefined
) {}

Expand All @@ -221,7 +233,7 @@ export class TranslatedText {
/**
* Resolves a translation string referenced by the current instance.
*/
resolve(translation: Translation): string {
resolve(translation: Translation<any>): string {
return translation.text(this.key, this.placeholders);
}
}
22 changes: 16 additions & 6 deletions src/diagram/locale.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,29 +17,39 @@ export class DefaultTranslation implements Translation {
protected readonly selectLabelLanguage: LabelLanguageSelector = defaultSelectLabel
) {}

private getString(key: TranslationKey): string {
private getString(key: TranslationKey): string | undefined {
const dotIndex = key.indexOf('.');
if (!(dotIndex > 0 && dotIndex < key.length)) {
throw new Error(`Reactodia: Invalid translation key: ${key}`);
}
const group = key.substring(0, dotIndex);
const leaf = key.substring(dotIndex + 1);
for (const bundle of this.bundles) {
const text = getString(bundle, group, leaf);
if (text !== undefined) {
const text = lookupString(bundle, group, leaf);
if (!(text === null || text === undefined)) {
return text;
}
}
return key;
return undefined;
}

text(key: TranslationKey, placeholders?: Record<string, string | number | boolean>): string {
return this.textOptional(key, placeholders) ?? key;
}

textOptional(
key: TranslationKey,
placeholders?: Record<string, string | number | boolean>
): string | undefined {
const template = this.getString(key);
if (template === undefined) {
return undefined;
}
return formatPlaceholders(template, placeholders);
}

template(key: TranslationKey, parts: Record<string, React.ReactNode>): React.ReactNode {
const template = this.getString(key);
const template = this.getString(key) ?? key;
return templatePlaceholders(template, parts);
}

Expand Down Expand Up @@ -79,7 +89,7 @@ export class DefaultTranslation implements Translation {
}
}

function getString(
function lookupString(
bundle: Partial<TranslationBundle>,
group: string,
leaf: string
Expand Down
10 changes: 8 additions & 2 deletions src/forms/editEntityForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,15 @@ export function EditEntityForm(props: {
<div className={`${FORM_CLASS}__controls`}>
<button type='button'
className={`reactodia-btn reactodia-btn-primary ${FORM_CLASS}__apply-button`}
title={t.text('visual_authoring.dialog.apply.title')}
title={
t.textOptional('visual_authoring.edit_entity.dialog.apply.title') ??
t.text('visual_authoring.dialog.apply.title')
}
onClick={() => onApply(data)}>
{t.text('visual_authoring.dialog.apply.label')}
{(
t.textOptional('visual_authoring.edit_entity.dialog.apply.label') ??
t.text('visual_authoring.dialog.apply.label')
)}
</button>
<button type='button'
className='reactodia-btn reactodia-btn-secondary'
Expand Down
10 changes: 8 additions & 2 deletions src/forms/editRelationForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -257,8 +257,14 @@ export function DefaultEditRelationForm(props: DefaultEditRelationFormProps) {
<button className={`reactodia-btn reactodia-btn-primary ${FORM_CLASS}__apply-button`}
onClick={onApply}
disabled={status !== 'ok'}
title={t.text('visual_authoring.dialog.apply.title')}>
{t.text('visual_authoring.dialog.apply.label')}
title={
t.textOptional('visual_authoring.edit_relation.dialog.apply.title') ??
t.text('visual_authoring.dialog.apply.title')
}>
{(
t.textOptional('visual_authoring.edit_relation.dialog.apply.label') ??
t.text('visual_authoring.dialog.apply.label')
)}
</button>
<button className='reactodia-btn reactodia-btn-secondary'
onClick={closeDialog}
Expand Down
2 changes: 1 addition & 1 deletion src/forms/elementTypeSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ export class ElementTypeSelectorInner extends React.Component<ElementTypeSelecto
inputProps={{
className: `${CLASS_NAME}__search-input`,
name: 'reactodia-element-type-selector-search',
placeholder: t.text('visual_authoring.select_entity.input.placeholder'),
placeholder: t.textOptional('visual_authoring.select_entity.input.placeholder'),
autoFocus: true,
}}>
<span className={`${CLASS_NAME}__search-icon`} />
Expand Down
10 changes: 8 additions & 2 deletions src/forms/findOrCreateEntityForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,14 @@ export class FindOrCreateEntityForm extends React.Component<FindOrCreateEntityFo
<button className={`reactodia-btn reactodia-btn-primary ${FORM_CLASS}__apply-button`}
onClick={this.onApply}
disabled={elementValue.loading || !isValid || isValidating}
title={t.text('visual_authoring.dialog.apply.title')}>
{t.text('visual_authoring.dialog.apply.label')}
title={
t.textOptional('visual_authoring.find_or_create.dialog.apply.title') ??
t.text('visual_authoring.dialog.apply.title')
}>
{(
t.textOptional('visual_authoring.find_or_create.dialog.apply.label') ??
t.text('visual_authoring.dialog.apply.label')
)}
</button>
<button className='reactodia-btn reactodia-btn-secondary'
onClick={this.props.onCancel}
Expand Down
10 changes: 8 additions & 2 deletions src/forms/renameLinkForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,15 @@ export function RenameLinkForm(props: RenameLinkFormProps) {
</div>
<div className={`${FORM_CLASS}__controls`}>
<button className={`reactodia-btn reactodia-btn-primary ${FORM_CLASS}__apply-button`}
title={t.text('visual_authoring.dialog.apply.title')}
title={
t.textOptional('visual_authoring.rename_link.dialog.apply.title') ??
t.text('visual_authoring.dialog.apply.title')
}
onClick={onApply}>
{t.text('visual_authoring.dialog.apply.label')}
{(
t.textOptional('visual_authoring.rename_link.dialog.apply.label') ??
t.text('visual_authoring.dialog.apply.label')
)}
</button>
<button className='reactodia-btn reactodia-btn-secondary'
title={t.text('visual_authoring.dialog.cancel.title')}
Expand Down
2 changes: 1 addition & 1 deletion src/widgets/classTree/classTree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ class ClassTreeInner extends React.Component<ClassTreeInnerProps, State> {
<SearchInput store={searchStore}
inputProps={{
name: 'reactodia-class-tree-filter',
placeholder: t.text('search_element_types.input.placeholder'),
placeholder: t.textOptional('search_element_types.input.placeholder'),
}}
/>
)}
Expand Down
2 changes: 1 addition & 1 deletion src/widgets/connectionsMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -589,7 +589,7 @@ class ConnectionsMenuInner extends React.Component<ConnectionsMenuInnerProps, Me
store={panel === 'connections' ? connectionSearch : objectSearch}
inputProps={{
name: 'reactodia-connection-menu-filter',
placeholder: t.text('connections_menu.input.placeholder'),
placeholder: t.textOptional('connections_menu.input.placeholder'),
}}>
{this.renderSortSwitches()}
</SearchInput>
Expand Down
2 changes: 1 addition & 1 deletion src/widgets/instancesSearch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,7 @@ class InstancesSearchInner extends React.Component<InstancesSearchInnerProps, St
className={`${CLASS_NAME}__text-criteria`}
inputProps={{
name: 'reactodia-instances-search-text',
placeholder: t.text('search_entities.input.placeholder'),
placeholder: t.textOptional('search_entities.input.placeholder'),
}}
/>
)}
Expand Down
2 changes: 1 addition & 1 deletion src/widgets/linksToolbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ class LinkTypesToolboxInner extends React.Component<LinkTypesToolboxInnerProps,
className={`${CLASS_NAME}__filter`}
inputProps={{
name: 'reactodia-link-types-filter',
placeholder: t.text('search_link_types.input.placeholder'),
placeholder: t.textOptional('search_link_types.input.placeholder'),
}}
/>
)}
Expand Down
5 changes: 4 additions & 1 deletion src/widgets/unifiedSearch/unifiedSearch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,10 @@ function SearchToggle(props: {
type='text'
className={`${CLASS_NAME}__search-input`}
style={{minWidth}}
placeholder={t.text('unified_search.input.placeholder')}
placeholder={
t.textOptional('unified_search.input.placeholder') ??
t.text('search_defaults.input.placeholder')
}
name='reactodia-search'
value={searchTerm}
onClick={() => setExpanded(true)}
Expand Down
Loading