Skip to content

Feature/interface clarifications #513

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

Merged
merged 50 commits into from
Jun 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
6edb19f
added i18n tags, added links and tooltips to menu
BeritJanssen Oct 11, 2021
79d53c8
adding i18n-parser
BeritJanssen Oct 11, 2021
1dcafb7
merge develop
BeritJanssen Nov 29, 2021
a8cf83a
incorporated suggested changes
BeritJanssen Jan 31, 2022
2895e63
Merge branch 'develop' into feature/interface-clarifications
jgonggrijp Mar 15, 2022
9ddf02f
Fix markup of modal close button in feedback template (#451)
jgonggrijp Mar 15, 2022
9dea827
Correct indentation in pagination template (#451)
jgonggrijp Mar 15, 2022
7af7b78
Properly nest, don't escape markup in i18n in anno edit template #451
jgonggrijp Mar 15, 2022
e440981
Add {{#i18n}} tags in more places (#451)
jgonggrijp Mar 15, 2022
1236e78
Add i18next.t calls in more places (#451)
jgonggrijp Mar 16, 2022
c482a9b
Export i18nParse function in gulpfile as task (#451)
jgonggrijp Mar 16, 2022
164f7fb
Import { gulp as i18nextParser } (#451)
jgonggrijp Mar 16, 2022
d59b9eb
Use frontend/src/i18n as the i18nParser output directory (#451)
jgonggrijp Mar 16, 2022
8408b98
Point i18nParser to the files inside the source directory (#451)
jgonggrijp Mar 16, 2022
54e4d43
Configure hbs lexer for i18nParse gulp task (#451)
jgonggrijp Mar 16, 2022
e63744b
Use our very own handlebars-i18next-parser (#451)
jgonggrijp Mar 31, 2022
91ad84b
Move i18next-parser config to a separate file (#451)
jgonggrijp Apr 20, 2022
695f23f
Add exception for package.json to .editorconfig
jgonggrijp Apr 20, 2022
14b38eb
Use the output of i18next-parser (#451)
jgonggrijp Apr 20, 2022
1b563a8
Remove triple braces for i18n blocks in 404 and anno edit templates
jgonggrijp Apr 20, 2022
d2a3f8f
Document frontend localization with the new parsing setup (#451)
jgonggrijp Apr 20, 2022
fc250c9
Document the shorthand script in the top-level package.json (#451)
jgonggrijp Apr 20, 2022
be0f64d
Add i18n keys that i18next-parse cannot handle automatically (#451)
jgonggrijp Apr 26, 2022
e2b0e4d
Fix a discrepancy in i18n "close" key (#451)
jgonggrijp Apr 26, 2022
9929899
Move anno-edit type hints entirely into translation JSON (#451)
jgonggrijp Apr 26, 2022
5f0daf0
Stop i18next-parser from changing French back to English (#451)
jgonggrijp Apr 26, 2022
6b665cb
Use four-space indentation in translation JSON files (#451)
jgonggrijp Apr 26, 2022
4a10968
Replace _ by - in i18n keys (#451)
jgonggrijp Apr 28, 2022
11af496
Place variable interpolations inside i18n strings where appropriate (…
jgonggrijp Apr 29, 2022
3b0352e
Commit translation_old.json (#451)
jgonggrijp Apr 29, 2022
978c0f5
Upgrade handlebars-i18next-parser to 1.0.2 (#451)
jgonggrijp Apr 29, 2022
6cf200a
Treat translation strings with links consistently (#451)
jgonggrijp Apr 30, 2022
a6a9e83
Organize the translation strings (#451)
jgonggrijp May 1, 2022
34900a9
Document how to prevent i18next-parser translation overrides (#451)
jgonggrijp May 1, 2022
16ee433
Make the tooltips in the main menu point down (#448 #447)
jgonggrijp May 3, 2022
f7015a7
Decorate the menu with more tooltips (#448 #447)
jgonggrijp May 3, 2022
a485531
Expand the TooltipView test suite (#447 #448)
jgonggrijp May 3, 2022
a274d89
Supercharge TooltipView so it can be attached to subelements (#448 #447)
jgonggrijp May 3, 2022
17c9c53
Factor TooltipModel from TooltipView (#448 #447)
jgonggrijp May 3, 2022
5e11728
Use TooltipModel in TooltipView (#448 #447)
jgonggrijp Jun 8, 2022
96a92fd
Use classes instead of ids in annotation view (#448 #447)
jgonggrijp Jun 8, 2022
08440c4
Use native Promise for global/i18n (#451)
jgonggrijp Jun 10, 2022
1d71075
Expose i18nPromise through radio channel for access from views (#451)
jgonggrijp Jun 10, 2022
1697cfd
Use the new i18nChannel where appropriate (#451)
jgonggrijp Jun 10, 2022
0622fe2
Export the Direction type from tooltip view (#447 #448)
jgonggrijp Jun 10, 2022
925aeee
Move AnnotationPanel tooltips from template to view class (#448)
jgonggrijp Jun 10, 2022
f343847
Spend more words on the existing AnnotationPanel tooltips (#447)
jgonggrijp Jun 10, 2022
c77528c
Add tooltips for the remaining buttons in AnnotationPanel (#448)
jgonggrijp Jun 11, 2022
5f4342b
Toggle is-tooltip-multiline class based on text length (#448)
jgonggrijp Jun 13, 2022
6e87767
Factor bulk tooltip management from AnnotationPanel for reuse (#448)
jgonggrijp Jun 13, 2022
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
3 changes: 3 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,6 @@ indent_size = 4
charset = utf-8
insert_final_newline = true
trim_trailing_whitespace = true

[**/package.json]
indent_size = 2
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,12 @@ $ yarn gulp [SUBCOMMAND ...] [OPTIONS]

[12]: frontend/README.md#gulp-tasks-and-options

Extract translation strings in the frontend:

```console
$ yarn localize
```


### Notes on Python package dependencies

Expand Down
32 changes: 32 additions & 0 deletions frontend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,38 @@ Each `browserLibs` item may have the following properties.
[17]: https://www.npmjs.com/package/gulp-cdnizer#optionsfilescdn


### Translations management

In principle, all strings that are shown to users should be translatable. In TypeScript files, the localized strings are obtained from a call to `i18next.t()`. In Handlebars templates, the `{{i18n}}` helper serves the same purpose. It is more often found in the block notation `{{#i18n}}...{{/i18n}}`, where the `...` part defines the default.


#### Extracting translation strings

The JSON files in `src/i18n` can be compiled automatically from the TypeScript and Handlebars sources using the following command:

```
yarn i18next -c i18next-parser.config.mjs
```

The files thus produced can be sent to the translators in order to fill out the actual translations.

Defaults sometimes start or end with spaces. `i18next` will strip them, but in some cases they need to be retained. The following trick will preserve those spaces and prevent `i18next` from stripping them again in the future:

1. Manually edit `src/i18n/en/translation.json` to put the spaces back and commit.
2. Run `i18next` to autogenerate the translation files. This will strip the spaces from the `translation.json` that you just hand-edited in. However, a copy of your hand-edited strings is retained in `src/i18n/en/translation_old.json`.
3. Run `git checkout HEAD src/i18n/en/translation.json` to restore your hand-edited strings in the main `translation.json` as well.
4. Commit the `translation_old.json`. Because both the `translation.json` and the `translation_old.json` contain your hand-edited version of the string, `i18next` will remember not to strip the spaces again in the future.

The above trick can also be used in other situations where you want to prevent `i18next` from overriding what is in the `translation.json`, for example if you decide to remove a default value from the source files altogether.


#### Adding new languages

1. Extend the list of `locales` in `i18next-parser.config.mjs`.
2. Follow the steps in the previous section to obtain the corresponding JSON file(s).
3. Edit `src/global/i18n.ts` to ensure that the new language(s) is/are taken into account.


### Proxy configuration

Suppose you have a backend application running on `localhost:8000` and you want to forward all requests for `/api` to this backend application. Create a JSON file with the following content:
Expand Down
15 changes: 15 additions & 0 deletions frontend/i18next-parser.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import HbsI18nLexer from 'handlebars-i18next-parser';

const sourceDir = 'src';
const i18nDir = `${sourceDir}/i18n`;

export default {
input: [`${sourceDir}/**/*.{t,hb}s`, `!${sourceDir}/**/*-te{mplate,st}.ts`],
output: `${i18nDir}/$LOCALE/$NAMESPACE.json`,
locales: ['en', 'fr'],
resetDefaultValueLocale: 'en',
indentation: 4,
lexers: {
hbs: [HbsI18nLexer],
},
};
3 changes: 2 additions & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,9 @@
"gulp-sass": "^5.0.0",
"gulp-sourcemaps": "^2.6.4",
"gulp-uglify": "^3.0.1",
"handlebars": "^4.7.7",
"handlebars-i18next-parser": "^1.0.2",
"http-proxy-middleware": "^0.19.0",
"i18next-parser": "^6.0.1",
"jasmine-ajax": "^4.0.0",
"jasmine-core": "~3.5.0",
"jasmine-terminal-reporter": "^1.0.3",
Expand Down
9 changes: 7 additions & 2 deletions frontend/src/explorer/explorer-event-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ class ExplorerEventController {
const flatItems = new FlatItemCollection(items);
const filteredItems = new FilteredCollection(flatItems, 'annotation');
const resultView = new SearchResultListPanel({
title:i18next.t('heading.annotations', 'Annotations'),
title:i18next.t('annotation.list-title', 'Annotations'),
model: item,
collection: filteredItems,
selectable: false,
Expand Down Expand Up @@ -311,10 +311,15 @@ class ExplorerEventController {
landing = false;
}
const title = `${landing ? 'My' : 'Sample'} ${queryMode}`;
const i18nKey = `button.${title.toLowerCase().replace(' ', '-')}`;
const endpoint = `${queryMode}:${landing ? 'user' : 'sample'}`;
const collection = new FlatItemCollection(ldChannel.request(endpoint));
const browsePanel = new SearchResultListPanel({
title,
// i18next.t('button.my-sources', 'My sources');
// i18next.t('button.sample-sources', 'Sample sources');
// i18next.t('button.my-items', 'My items');
// i18next.t('button.sample-items', 'Sample items');
title: i18next.t(i18nKey, title),
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comments should probably go?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, they are needed to ensure that i18next-parser doesn't purge these keys on the next scan.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you elaborate on this? Why is there a difference in behaviour between commented code and removed code?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not outcommented. It's a "clarification" (for the parser) of what the next line means.

collection,
selectable: false,
});
Expand Down
43 changes: 30 additions & 13 deletions frontend/src/feedback/feedback-template.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -3,53 +3,70 @@
<form class="box register-form is-narrow-modal-form">
{{#if wasSentSuccessfully}}
<header>
<h3 class="title is-3 page-header">Feedback submitted</h3>
<h3 class="title is-3 page-header">
{{#i18n 'feedback.success-heading'
}}Feedback submitted{{/i18n}}
</h3>
</header>
<div class="field">
<p>Thank you very much for your feedback, it is highly appreciated!</p>
<p>{{#i18n 'feedback.thanks'}}Thank you very much for your feedback, it is highly appreciated!{{/i18n}}</p>
</div>
<div class="field is-grouped">
<div class="control">
<button class="button btn-close" type=button>Close</button>
<button class="button btn-close" type=button>{{#i18n 'button.close'}}Close{{/i18n}}</button>
</div>
</div>
{{else}}
<header>
<h3 class="title is-3 page-header">Feedback</h3>
<h3 class="title is-3 page-header">{{#i18n 'feedback.form-heading'}}Feedback{{/i18n}}</h3>
</header>
<div class="field">
<label class="label">Subject</label>
<label class="label">{{#i18n 'label.subject'}}Subject{{/i18n}}</label>
<div class="control">
<input name="subject" class="input" type="text" name="title" placeholder="About READ-IT">
<input
name="subject"
class="input"
type="text"
name="title"
placeholder="{{#i18n 'placeholder.feedback-subject'}}About READ-IT{{/i18n}}"
>
</div>
</div>

<div class="field">
<div class="control">
<label class="label">Feedback</label>
<textarea name="feedback" class="textarea" placeholder="e.g. I love READ-IT!"></textarea>
<label class="label">{{#i18n 'label.feedback'}}Feedback{{/i18n}}</label>
<textarea
name="feedback"
class="textarea"
placeholder="{{#i18n 'placeholder.feedback-content'}}e.g. I love READ-IT!{{/i18n}}"
></textarea>
</div>
</div>

{{#if hasError}}
<div class="field form-feedback-bar has-background-danger has-text-white">
<p class="help">Feedback submission failed. Please try again and if this error keeps occuring, contact the
site administrator.</p>
<p class="help">{{#i18n 'feedback.failed'}}Feedback submission failed. Please try again and if this error keeps occuring, contact the
site administrator.{{/i18n}}</p>
</div>
{{/if}}

<div class="field is-grouped">
<div class="control">
<button class="button is-link btn-submit" type=submit>Submit</button>
<button class="button is-link btn-submit" type=submit>{{#i18n 'button.submit'}}Submit{{/i18n}}</button>
</div>

<div class="control">
<button class="button btn-close" type=button>Close</button>
<button class="button btn-close" type=button>{{#i18n 'button.close'}}Close{{/i18n}}</button>
</div>
</div>
{{/if}}

</form>

</div>
<button type=button modal-close is-large" aria-label="close"></button>
<button
type=button
class="modal-close is-large"
aria-label="{{#i18n 'button.close'}}Close{{/i18n}}"
></button>
2 changes: 1 addition & 1 deletion frontend/src/footer/footer-template.hbs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<figure class="footer-dh">
<p>Developed by</p>
<p>{{#i18n 'footer.developed-by'}}Developed by{{/i18n}}</p>
<a href="https://dig.hum.uu.nl" target="_blank"><img class="dhlab"
src="{{static 'image/dighum-logo-blue.svg'}}"></a>
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we already proactively change the Footer to only include the UU logo and DHLab name?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If that has been agreed, I suppose we should. I'll defer that to a separate ticket, though.

<div class="footer-project is-pulled-right">
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/forms/add-button-template.hbs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<button type=button class="button is-success">
<span>Add</span>
<span>{{#i18n 'button.add'}}Add{{/i18n}}</span>
<span class="icon is-small"><i class="fas fa-plus"></i></span>
</button>
3 changes: 2 additions & 1 deletion frontend/src/forms/ontology-class-picker-children-view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import FlatItemCollection from '../common-adapters/flat-item-collection';
import FlatItem from '../common-adapters/flat-item-model';
import { CollectionView } from '../core/view';
import attachTooltip from '../tooltip/tooltip-view';
import toTooltip from '../tooltip/tooltip-model';
import { animatedScroll, getScrollTop } from '../utilities/scrolling-utilities';
import OntologyClassPickerItemView from './ontology-class-picker-item-view';

Expand All @@ -26,7 +27,7 @@ export default class OntologyClassPickerChildrenView extends CollectionView<
const item = new OntologyClassPickerItemView({ model }).on({
click: this.onItemClicked,
}, this);
attachTooltip(item, { model });
attachTooltip(item, { model: toTooltip(model) });
return item;
}

Expand Down
2 changes: 1 addition & 1 deletion frontend/src/forms/ontology-class-picker-template.hbs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<div class="dropdown">
<div class="dropdown-trigger">
<button type=button class="button" aria-haspopup="true" aria-controls="dropdown-menu">
<span class="dropdown-label">This is a
<span class="dropdown-label">{{#i18n 'annotation.define-category'}}This is a{{/i18n}}
<span class="dropdown-label-tag">...</span>
</span>
<span class="icon is-small">
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/forms/ontology-class-picker-view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { skos } from '../common-rdf/ns';
import { CollectionView } from '../core/view';
import LabelView from '../label/label-view';
import attachTooltip from '../tooltip/tooltip-view';
import toTooltip from '../tooltip/tooltip-model';

import OntologyClassPickerChildrenView from './ontology-class-picker-children-view';
import OntologyClassPickerItemView from './ontology-class-picker-item-view';
import ontologyClassPickerTemplate from './ontology-class-picker-template';
Expand Down Expand Up @@ -34,7 +36,7 @@ export default class OntologyClassPickerView extends CollectionView<
click: this.onItemClicked,
hover: this.isNonLeaf(model) ? this.onSuperclassHovered : undefined,
}, this);
attachTooltip(item, { model, direction: 'left' });
attachTooltip(item, { model: toTooltip(model), direction: 'left' });
return item;
}

Expand Down
2 changes: 1 addition & 1 deletion frontend/src/forms/remove-button-template.hbs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<button type=button class="button is-danger">
<span>Remove</span>
<span>{{#i18n 'button.remove'}}Remove{{/i18n}}</span>
<span class="icon is-small"><i class="fas fa-times"></i></span>
</button>
12 changes: 6 additions & 6 deletions frontend/src/global/annotation-hierarchy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,27 +31,27 @@ const hierarchyPromise = i18nPromise.then(() => {
collection: new Collection([{
model: new Model({
id: 'mine',
label: i18next.t('filterHierarchy.mine', 'by me'),
label: i18next.t('annotation.filters.mine', 'by me'),
cssClass: 'rit-self-made',
}),
}, {
model: new Model({
id: 'others',
label: i18next.t('filterHierarchy.others', 'by others'),
label: i18next.t('annotation.filters.others', 'by others'),
cssClass: 'rit-other-made',
}),
}]),
}, {
collection: new Collection([{
model: new Model({
id: 'verified',
label: i18next.t('filterHierarchy.verified', 'verified'),
label: i18next.t('annotation.filters.verified', 'verified'),
cssClass: 'rit-verified',
}),
}, {
model: new Model({
id: 'unverified',
label: i18next.t('filterHierarchy.unverified', 'unverified'),
label: i18next.t('annotation.filters.unverified', 'unverified'),
cssClass: 'rit-unverified',
}),
}]),
Expand All @@ -60,14 +60,14 @@ const hierarchyPromise = i18nPromise.then(() => {
fullHierarchy.add([{
model: new Model({
id: 'semantic',
label: i18next.t('filterHierarchy.semantic', 'human'),
label: i18next.t('annotation.filters.semantic', 'human'),
cssClass: 'rit-is-semantic',
}),
collection: semanticHierarchy,
}, {
model: new Model({
id: 'nlp',
label: i18next.t('filterHierarchy.nlp', 'automated'),
label: i18next.t('annotation.filters.nlp', 'automated'),
cssClass: 'rit-is-nlp',
}),
collection: nlpHierarchy,
Expand Down
44 changes: 25 additions & 19 deletions frontend/src/global/i18n.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,33 @@
import { Deferred } from 'jquery';
import { constant } from 'lodash';
import * as i18next from 'i18next';
import * as languageDetector from 'i18next-browser-languagedetector';

import * as french from '../i18n/fr.json';
import * as english from '../i18n/en/translation.json';
import * as french from '../i18n/fr/translation.json';
import channel from '../i18n/radio';

const deferred = Deferred();
const i18nPromise = deferred.promise();

i18next.use(
languageDetector
).init({
fallbackLng: ['en', 'dev'],
resources: {
fr: {
translation: french,
const i18nPromise = new Promise(function(resolve, reject) {
i18next.use(
languageDetector
).init({
fallbackLng: ['en', 'dev'],
resources: {
en: {
translation: english,
},
fr: {
translation: french,
},
},
},
}, function(error, t) {
if (error) {
deferred.reject(error);
} else {
deferred.resolve(i18next);
}
}, function(error, t) {
if (error) {
reject(error);
} else {
resolve(i18next);
}
});
});

channel.reply('i18next', constant(i18nPromise));

export { i18nPromise, i18next };
Loading