Localization (i18n)¶
The editor ships with built-in English and German translations for all UI strings — toolbar labels, command descriptions, outline placeholders, and accessibility text.
Switching the Language¶
Via HTML attribute¶
The <sciflow-editor> element accepts a locale attribute. Set it declaratively or update it at runtime — the editor and all connected components switch immediately.
// Or change it at runtime via the DOM property
document.querySelector('sciflow-editor').locale = 'en';
Via JavaScript¶
import { setLocale } from '@sciflow/editor-start';
// or: import { setLocale } from '@sciflow/editor-core';
// Switch to German
setLocale('de');
// Switch back to English
setLocale('en');
When setLocale() is called:
- The internal locale updates immediately.
- A
sciflow-locale-changeCustomEvent fires ondocument. - All mounted components re-render with the new strings.
- Command metadata (tooltips, aria-labels) resolves to the new locale on next read.
No editor restart or feature re-initialization is needed.
Reading the Current Locale¶
Available Locales¶
| Code | Language |
|---|---|
en |
English (default) |
de |
German |
Using the Translation Function Directly¶
If you build custom UI around the editor, you can use t() to access any translation key:
import { t } from '@sciflow/editor-start';
const label = t('cmd.insertNativeTableFigure.label');
// English: "Insert table"
// German: "Tabelle einfügen"
Listening for Locale Changes¶
Custom components can react to locale switches by listening for the event:
import { LOCALE_CHANGE_EVENT } from '@sciflow/editor-start';
document.addEventListener(LOCALE_CHANGE_EVENT, (event) => {
const { locale } = event.detail;
console.log(`Locale changed to: ${locale}`);
});
Adding Custom Translations¶
If you build custom UI components around the editor, use registerTranslations() to add your own keys to the shared dictionary. This way your components use the same t() function and react to the same setLocale() calls — no separate i18n system needed.
import { registerTranslations, t } from '@sciflow/editor-start';
// Register translations for each locale you support
registerTranslations('en', {
'myApp.sidebar.title': 'My Sidebar',
'myApp.exportButton': 'Export PDF',
});
registerTranslations('de', {
'myApp.sidebar.title': 'Meine Seitenleiste',
'myApp.exportButton': 'PDF exportieren',
});
// Use the same t() everywhere
const label = t('myApp.sidebar.title');
Custom keys are resolved before built-in keys, so you can also override any built-in string if needed. Subsequent calls for the same locale merge into the existing entries.
Namespace your keys
Use a prefix like myApp. or acme. to avoid collisions with current or future built-in keys.
Lit Component Example¶
import { LitElement, html } from 'lit';
import { customElement } from 'lit/decorators.js';
import { t, LOCALE_CHANGE_EVENT, registerTranslations } from '@sciflow/editor-start';
registerTranslations('en', { 'myWidget.heading': 'Dashboard' });
registerTranslations('de', { 'myWidget.heading': 'Übersicht' });
@customElement('my-widget')
class MyWidget extends LitElement {
private localeListener?: EventListener;
connectedCallback(): void {
super.connectedCallback();
this.localeListener = () => this.requestUpdate();
document.addEventListener(LOCALE_CHANGE_EVENT, this.localeListener);
}
disconnectedCallback(): void {
if (this.localeListener) {
document.removeEventListener(LOCALE_CHANGE_EVENT, this.localeListener);
}
super.disconnectedCallback();
}
render() {
return html`<h2>${t('myWidget.heading')}</h2>`;
}
}
React Component Example¶
import { useEffect, useState } from 'react';
import { t, registerTranslations, LOCALE_CHANGE_EVENT, getLocale } from '@sciflow/editor-start';
registerTranslations('en', { 'myPanel.title': 'Analytics' });
registerTranslations('de', { 'myPanel.title': 'Auswertungen' });
function MyPanel() {
const [, setLocale] = useState(getLocale());
useEffect(() => {
const handler = (e: Event) => setLocale((e as CustomEvent).detail.locale);
document.addEventListener(LOCALE_CHANGE_EVENT, handler);
return () => document.removeEventListener(LOCALE_CHANGE_EVENT, handler);
}, []);
return <h2>{t('myPanel.title')}</h2>;
}
Integration with PKP OJS¶
PKP OJS has its own XML-based locale system (locale/<code>/locale.xml). An OJS plugin can bridge the two systems so the editor inherits whatever language OJS is currently using.
Mapping OJS locale strings to the editor¶
In your plugin's page or template handler, read the OJS locale and pass the editor's built-in keys through registerTranslations():
// In your OJS plugin (PHP side)
// Pass the current OJS locale and any custom strings to the frontend
$templateMgr->assign('ojsLocale', AppLocale::getLocale()); // e.g. "de_DE"
$templateMgr->assign('editorStrings', json_encode([
'myPlugin.submitManuscript' => __('plugins.generic.sciflow.submitManuscript'),
'myPlugin.saveProgress' => __('plugins.generic.sciflow.saveProgress'),
]));
<!-- In your Smarty template -->
<sciflow-editor id="editor" locale="{$ojsLocale|substr:0:2}"></sciflow-editor>
<script type="module">
import { registerTranslations, setLocale } from '@sciflow/editor-start';
// Register plugin-specific strings translated via OJS's locale XML
const strings = {$editorStrings|json_encode};
const locale = '{$ojsLocale|substr:0:2}'; // "de_DE" → "de"
registerTranslations(locale, strings);
setLocale(locale);
</script>
OJS locale XML example¶
Add your plugin's editor strings to the standard OJS locale file:
<!-- plugins/generic/sciflow/locale/en/locale.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<locale name="en" full_name="English">
<message key="plugins.generic.sciflow.submitManuscript">Submit Manuscript</message>
<message key="plugins.generic.sciflow.saveProgress">Save Progress</message>
</locale>
<!-- plugins/generic/sciflow/locale/de/locale.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<locale name="de" full_name="Deutsch">
<message key="plugins.generic.sciflow.submitManuscript">Manuskript einreichen</message>
<message key="plugins.generic.sciflow.saveProgress">Fortschritt speichern</message>
</locale>
How it works¶
- OJS determines the active locale as usual (user preference, journal default).
- The plugin reads OJS locale strings with
__()and passes them to JavaScript. registerTranslations()merges them into the editor's dictionary.- The
localeattribute on<sciflow-editor>(or asetLocale()call) activates the matching language. - Built-in editor strings (toolbar, commands, accessibility labels) switch automatically. Plugin-specific strings resolve through the same
t()function.
The editor ships with English and German built-in. For other OJS-supported languages, use registerTranslations() to provide translations for the editor's built-in keys as well — they are listed in the translation key reference below.
OJS locale codes
OJS uses codes like de_DE, fr_FR, es_ES. The editor expects the two-letter language prefix (de, fr, es). Extract it with substr(0, 2) or equivalent.
Adding a New Language¶
The editor ships with English and German. To add a new language (e.g. for an OJS journal in French, Spanish, or Portuguese), provide translations for all built-in keys via registerTranslations().
Step 1: Extract the key template¶
Use the built-in extraction script to dump all keys with their English defaults:
# JSON (default) — use as a translation template
npx @sciflow/editor-start extract-translations > en.json
# CSV — handy for spreadsheet-based translation workflows
npx @sciflow/editor-start extract-translations --csv > en.csv
The JSON output looks like:
{
"cmd.insertNativeTableFigure.label": "Insert table",
"cmd.toggleBold.label": "Bold",
"formatBar.undo": "Undo",
"selectionEditor.noElement": "No element selected. Click or select an element in the editor.",
...
}
Copy the JSON file, translate the values, and save it as your locale file (e.g. fr.json).
For compile-time safety, use the TranslationDictionary type (exported from @sciflow/editor-core) — your IDE will flag missing keys:
import type { TranslationDictionary } from '@sciflow/editor-start';
const fr: TranslationDictionary = {
'cmd.insertNativeTableFigure.label': 'Insérer un tableau',
// ... IDE will error on any missing key
};
The complete key list is also available in the translation key reference below.
Step 2: Register and activate¶
import { registerTranslations, setLocale } from '@sciflow/editor-start';
registerTranslations('fr', fr);
setLocale('fr');
All built-in components (format bar, selection editor, outline, reference list) will immediately render in French.
Step 3 (optional): Type-safe locale codes¶
If you want TypeScript to accept your new locale code, augment the Locale type:
Partial translations¶
registerTranslations() merges keys — you don't have to provide all keys at once. Missing keys fall back to English. This is useful for incremental translation or for overriding only a few labels.
Sub-path Import¶
The i18n module is also available as a dedicated sub-path export:
Server-Side / Node.js¶
setLocale() and t() work in Node.js. The CustomEvent dispatch is skipped when document is unavailable, so the runtime is safe for SSR.
Translation Key Reference¶
Every built-in key, its English default, and where it appears. Use this as a checklist when translating to a new language.
Commands (cmd.*)¶
| Key | English | Context |
|---|---|---|
cmd.insertNativeTableFigure.label |
Insert table | Format bar insert menu |
cmd.insertNativeTableFigure.description |
Insert a native table with caption | Tooltip |
cmd.addColumnBefore.label |
Add column before | Table toolbar |
cmd.addColumnBefore.description |
Add column before | Tooltip |
cmd.addColumnAfter.label |
Add column after | Table toolbar |
cmd.addColumnAfter.description |
Add column after | Tooltip |
cmd.deleteColumn.label |
Delete column | Table toolbar |
cmd.deleteColumn.description |
Delete column | Tooltip |
cmd.addRowBefore.label |
Add row above | Table toolbar |
cmd.addRowBefore.description |
Add row above | Tooltip |
cmd.addRowAfter.label |
Add row below | Table toolbar |
cmd.addRowAfter.description |
Add row below | Tooltip |
cmd.deleteRow.label |
Delete row | Table toolbar |
cmd.deleteRow.description |
Delete row | Tooltip |
cmd.mergeCells.label |
Merge cells | Table toolbar |
cmd.mergeCells.description |
Merge cells | Tooltip |
cmd.splitCell.label |
Split cell | Table toolbar |
cmd.splitCell.description |
Split cell | Tooltip |
cmd.toggleHeaderRow.label |
Toggle header row | Table toolbar |
cmd.toggleHeaderRow.description |
Toggle header row | Tooltip |
cmd.toggleHeaderColumn.label |
Toggle header column | Table toolbar |
cmd.toggleHeaderColumn.description |
Toggle header column | Tooltip |
cmd.goToNextCell.label |
Next cell | Table toolbar |
cmd.goToNextCell.description |
Move to next cell | Tooltip |
cmd.deleteTable.label |
Delete table | Table toolbar |
cmd.deleteTable.description |
Delete table | Tooltip |
cmd.alignLeft.label |
Align left | Table toolbar |
cmd.alignCenter.label |
Align center | Table toolbar |
cmd.alignRight.label |
Align right | Table toolbar |
cmd.alignJustify.label |
Justify | Table toolbar |
cmd.toggleBulletList.label |
Bullet list | Format bar |
cmd.toggleBulletList.description |
Toggle bullet list formatting | Tooltip |
cmd.toggleOrderedList.label |
Ordered list | Format bar |
cmd.toggleOrderedList.description |
Toggle ordered list formatting | Tooltip |
cmd.sinkListItem.label |
Increase indent | Format bar |
cmd.sinkListItem.description |
Indent the current list item | Tooltip |
cmd.liftListItem.label |
Decrease indent | Format bar |
cmd.liftListItem.description |
Outdent the current list item | Tooltip |
cmd.insertFigureInteractive.label |
Insert figure | Format bar insert menu |
cmd.insertFigureInteractive.description |
Upload and insert a figure | Tooltip |
cmd.insertMath.label |
Insert equation | Format bar insert menu |
cmd.insertMath.description |
Insert a math equation (Mod-m to wrap selection) | Tooltip |
cmd.insertFootnote.label |
Insert footnote | Format bar insert menu |
cmd.insertFootnote.description |
Insert a footnote at the cursor | Tooltip |
cmd.toggleBold.label |
Bold | Format bar |
cmd.toggleBold.description |
Toggle bold | Tooltip |
cmd.toggleItalic.label |
Italic | Format bar |
cmd.toggleItalic.description |
Toggle italic | Tooltip |
cmd.toggleSuperscript.label |
Superscript | Format bar |
cmd.toggleSuperscript.description |
Toggle superscript | Tooltip |
cmd.toggleSubscript.label |
Subscript | Format bar |
cmd.toggleSubscript.description |
Toggle subscript | Tooltip |
cmd.insertHyperlink.label |
Link | Format bar |
cmd.insertHyperlink.description |
Insert or remove a hyperlink | Tooltip |
cmd.toggleBlockquote.label |
Blockquote | Format bar |
cmd.toggleBlockquote.description |
Toggle blockquote formatting | Tooltip |
Format bar (formatBar.*)¶
| Key | English | Context |
|---|---|---|
formatBar.styleParagraph |
Paragraph | Block style dropdown |
formatBar.styleHeading1 |
Heading 1 | Block style dropdown |
formatBar.styleHeading2 |
Heading 2 | Block style dropdown |
formatBar.styleHeading3 |
Heading 3 | Block style dropdown |
formatBar.undo |
Undo | Toolbar button |
formatBar.undoDescription |
Undo last change | Tooltip |
formatBar.redo |
Redo | Toolbar button |
formatBar.redoDescription |
Redo last change | Tooltip |
formatBar.textStyleAriaLabel |
Text style | ARIA label for style dropdown |
formatBar.historyAriaLabel |
History commands | ARIA label for undo/redo group |
formatBar.insert |
Insert | Insert menu trigger |
formatBar.toolbarAriaLabel |
Formatting toolbar | ARIA label for toolbar |
formatBar.tableLabel |
Table | Table menu trigger |
formatBar.tableAriaLabel |
Table commands | ARIA label for table group |
Outline (outline.*)¶
| Key | English | Context |
|---|---|---|
outline.noHeadings |
No headings yet. | Empty state |
outline.untitledSection |
Untitled section | Heading without text |
outline.figure |
Figure | Figure entry label |
outline.headingIdAriaLabel |
Heading id | ARIA label |
outline.figureIdAriaLabel |
Figure id | ARIA label |
outline.insertCrossReference |
Insert ref | Button label |
outline.insertCrossReferenceAriaLabel |
Insert cross-reference | ARIA label |
Reference list (referenceList.*)¶
| Key | English | Context |
|---|---|---|
referenceList.noReferences |
No references yet. | Empty state |
referenceList.cslIdAriaLabel |
CSL id | ARIA label |
referenceList.insertCitation |
Cite | Button label |
Math (math.*)¶
| Key | English | Context |
|---|---|---|
math.styleInline |
Inline | Style toggle |
math.styleDisplay |
Display | Style toggle |
Selection editor (selectionEditor.*)¶
| Key | English | Context |
|---|---|---|
selectionEditor.noElement |
No element selected. Click or select an element in the editor. | Empty state |
selectionEditor.type |
Type: | Property label |
selectionEditor.id |
ID: | Property label |
selectionEditor.attributes |
Attributes: | Section label |
selectionEditor.moreAttributes |
More attributes | Expandable section |
selectionEditor.altText |
Alt text | Figure field label |
selectionEditor.altTextPlaceholder |
Describe the image for accessibility | Placeholder |
selectionEditor.tex |
TeX | Math field label |
selectionEditor.texPlaceholder |
e.g. x^2 + y^2 = z^2 | Placeholder |
selectionEditor.style |
Style | Math style label |
selectionEditor.inline |
Inline | Style option |
selectionEditor.display |
Display | Style option |
selectionEditor.labelOptional |
Label (optional) | Math label field |
selectionEditor.labelPlaceholder |
eq:mylabel | Placeholder |
selectionEditor.preview |
Preview | Math preview heading |
selectionEditor.equationPreview |
Equation preview | ARIA label |
selectionEditor.apply |
Apply | Button |
selectionEditor.mathHint |
Ctrl+Enter to apply, Escape to cancel | Hint text |
selectionEditor.mathDocsLink |
MathJax 4 docs | Link text |
selectionEditor.inTextCitation |
In-text citation | Citation field label |
selectionEditor.inTextCitationAriaLabel |
In-text citation (supports bold and italic) | ARIA label |
selectionEditor.semanticReference |
Semantic reference: | Citation field label |
selectionEditor.update |
Update | Button |
selectionEditor.updateHint |
Ctrl+Enter to apply | Hint text |
selectionEditor.crossReference |
cross-reference | Node type label |
selectionEditor.displayText |
Display text | Cross-ref field label |
selectionEditor.displayTextPlaceholder |
e.g. Chapter 1, Figure 1, Abstract | Placeholder |
selectionEditor.target |
Target | Cross-ref field label |
selectionEditor.revert |
Revert | Button |
selectionEditor.draftHint |
Ctrl+Enter to apply, Escape to revert | Hint text |
selectionEditor.pinSelection |
Pin selection | Toggle button |
selectionEditor.unpinSelection |
Unpin selection | Toggle button |
selectionEditor.editFigure |
Edit figure | ARIA label |
selectionEditor.editEquation |
Edit equation | ARIA label |
selectionEditor.editCitation |
Edit citation | ARIA label |
selectionEditor.editCrossReference |
Edit cross-reference | ARIA label |
selectionEditor.figure |
figure | Node type label |
selectionEditor.math |
math | Node type label |
selectionEditor.citation |
citation | Node type label |
selectionEditor.heading |
heading | Node type label |
selectionEditor.paragraph |
paragraph | Node type label |
selectionEditor.expandAttributes |
Show technical attributes | Toggle button |
selectionEditor.collapseAttributes |
Hide technical attributes | Toggle button |
selectionEditor.hyperlink |
hyperlink | Node type label |
selectionEditor.editHyperlink |
Edit hyperlink | ARIA label |
selectionEditor.url |
URL | Field label |
selectionEditor.urlPlaceholder |
https://... | Placeholder |
selectionEditor.removeLink |
Remove link | Button |
selectionEditor.sectionElement |
Element | Panel section |
selectionEditor.sectionStructure |
Structure | Panel section |
selectionEditor.sectionFormatting |
Formatting | Panel section |
selectionEditor.sectionAttributes |
Attributes | Panel section |
selectionEditor.elementId |
Element ID | Field label |
selectionEditor.defaultValue |
Default | Fallback label |
selectionEditor.copied |
Copied! | Toast message |
selectionEditor.insert |
Insert… | Button |