Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
111 commits
Select commit Hold shift + click to select a range
a1d1666
chore: Add dependency on i18next 24.2.1
claremacrae Jan 10, 2025
d691bc0
spike: First experimental use of i18next - hard-coded in the main
claremacrae Jan 11, 2025
d27dfdf
spike: Read translations from json file, and initialise safely in mai…
claremacrae Jan 11, 2025
415a624
spike: Translate 'Loading plugin' message
claremacrae Jan 11, 2025
117ba12
spike: Translate 'unloading plugin' message
claremacrae Jan 11, 2025
6fa5342
spike: Remove the initial i18n experimental 'welcome' message
claremacrae Jan 11, 2025
e9bc4e3
spike: Add Chinese translation for loading and unloading messages
claremacrae Jan 11, 2025
adbcb83
spike: Detect user language automatically
claremacrae Jan 11, 2025
c80a88d
spike: Move i18next to correct section in package.json
claremacrae Jan 11, 2025
5b85fb5
spike: Put keys in namespaces
claremacrae Jan 11, 2025
64847f2
chore: Add "i18next-parser" as a dev dependency
claremacrae Jan 11, 2025
e07dde8
spike: Add configuration for i18next-parser - run with 'npx i18next-p…
claremacrae Jan 11, 2025
b8a6a6e
spike: Add "extract-i18n" script to package.json
claremacrae Jan 11, 2025
1eb261b
fix: Use fallback language if i18next-parser put in empty value for u…
claremacrae Jan 11, 2025
53bec1a
spike: Discard keys not found in source files
claremacrae Jan 11, 2025
1c6dcad
Revert "spike: Discard keys not found in source files"
claremacrae Jan 12, 2025
7232ad6
spike: Switch to flat JSON translation files, for ease of adding new …
claremacrae Jan 13, 2025
6ca53c3
refactor: ! Extract 'Changing any settings requires a restart of obsi…
claremacrae Jan 13, 2025
9d3b004
refactor: ! Adjust 'See the documentation' links in settings to be a …
claremacrae Jan 13, 2025
152ffe3
refactor: . Extract method seeTheDocumentation()
claremacrae Jan 13, 2025
96bdbb9
refactor: ! Introduce url parameter to seeTheDocumentation()
claremacrae Jan 13, 2025
0cec17c
refactor: ! Reuse seeTheDocumentation() - all links tested.
claremacrae Jan 13, 2025
8fb81a8
refactor: ! Support translation of 'Task Format'
claremacrae Jan 13, 2025
8509c43
refactor: ! Support translation of 'Task Format' description - part 1
claremacrae Jan 13, 2025
e64dfa6
refactor: ! Support translation of 'Task Format' description - part 2
claremacrae Jan 13, 2025
d6237fc
refactor: ! Replace displayName value in TaskFormat with function get…
claremacrae Jan 13, 2025
a1e3e6d
refactor: ! Support translation of 'Tasks Emoji Format' option in set…
claremacrae Jan 13, 2025
4c867d2
refactor: ! Support translation of 'Dataview' format option in settings
claremacrae Jan 13, 2025
a589aa2
refactor: ! Support translation of 'Global task filter' and 'Global f…
claremacrae Jan 13, 2025
2acd7cd
refactor: ! Support translation of Global Task Filter description line 1
claremacrae Jan 13, 2025
fa7d00d
refactor: ! Support translation of Global Task Filter description line 2
claremacrae Jan 13, 2025
2aa5366
refactor: ! Support translation of Global Task Filter description line 3
claremacrae Jan 13, 2025
6f3fef5
refactor: ! Correct the line number references in global filter i18n …
claremacrae Jan 13, 2025
f5f67a0
refactor: ! Support translation of Global Task Filter description act…
claremacrae Jan 13, 2025
19d8676
refactor: ! Add extra name to keys for "Global filter" setting
claremacrae Jan 13, 2025
505a358
refactor: ! Support translation of Global Task Filter placeholder
claremacrae Jan 13, 2025
508ec3a
refactor: ! Support translation of 'Remove global filter from descrip…
claremacrae Jan 13, 2025
4b2057f
feat: Support translation of 'Global Query' heading
claremacrae Jan 13, 2025
321e44f
feat: ! Support translation of 'Global Query' description
claremacrae Jan 13, 2025
015de2f
feat: ! Support translation of 'Global Query' placeholder
claremacrae Jan 13, 2025
ace22e2
refactor: . Remove an i18n key I didn't need to add
claremacrae Jan 13, 2025
e5775bb
feat: ! Support translation of 'Task Statuses' heading
claremacrae Jan 13, 2025
1dbe608
feat: ! Support translation of 'Dates' heading
claremacrae Jan 13, 2025
54baf74
feat: ! Support translation of 'created date' option name
claremacrae Jan 13, 2025
c671ae3
feat: ! Support translation of 'cancelled date' option name
claremacrae Jan 13, 2025
4cdaf92
feat: ! Support translation of 'done date' option name
claremacrae Jan 13, 2025
afbffd2
feat: ! Support translation of 'created date' option description
claremacrae Jan 13, 2025
b7fe89d
feat: ! Support translation of 'done date' option description
claremacrae Jan 13, 2025
825a77a
feat: ! Support translation of 'cancelled date' option description
claremacrae Jan 13, 2025
a1241c8
feat: ! Add Chinese translations, courtesy of the i18n plugin
claremacrae Jan 13, 2025
aed5977
feat: ! Support translation of 'See the documentation' heading
claremacrae Jan 13, 2025
3ca82e4
refactor: ! Switch translation files back to nested structure
claremacrae Jan 14, 2025
c009373
feat: ! Partial support for translation of 'Dates from file names' se…
claremacrae Jan 14, 2025
36da573
refactor: ! Split '</br>' from translatable strings.
claremacrae Jan 14, 2025
fc12062
feat: ! Partial support for translation of 'Dates from file names' se…
claremacrae Jan 14, 2025
b5d4965
feat: ! Group datesFromFileNames.scheduledDate toggle keys together
claremacrae Jan 14, 2025
e9de407
feat: ! Translate 'Additional filename date format...' section
claremacrae Jan 14, 2025
58a110d
feat: ! Translate 'Folders with default Scheduled dates' section
claremacrae Jan 14, 2025
f8ffa92
feat: ! Support translation of 'Recurring tasks' section
claremacrae Jan 14, 2025
7f4884d
feat: ! Support translation of 'Auto-suggest' section
claremacrae Jan 14, 2025
5c6b125
feat: ! Support translation of 'Dialogs' section
claremacrae Jan 14, 2025
8825b94
feat: ! Add Chinese translations, courtesy of the i18n plugin
claremacrae Jan 14, 2025
b99c3f7
refactor: ! Embed settingsConfiguration.json in to SettingsTab.ts, to…
claremacrae Jan 14, 2025
6565e74
feat: ! Support translation of 'Core Statuses' heading
claremacrae Jan 14, 2025
4054f9e
refactor: ! Split long strings, preparing to translate them.
claremacrae Jan 14, 2025
f78cfb9
refactor: ! Remove a split-string in Custom Statuses description, to …
claremacrae Jan 14, 2025
883b08f
feat: ! Support translation of 'Core Statuses' description
claremacrae Jan 14, 2025
7cfa84f
feat: ! Support translation of 'Custom Statuses' heading
claremacrae Jan 14, 2025
8b661ec
feat: ! Support translation of 'Custom Statuses' description
claremacrae Jan 14, 2025
a4e67cc
feat: ! Add Chinese translations, courtesy of the i18n plugin
claremacrae Jan 14, 2025
930ac68
feat: ! Support translation of 'Review and check your Statuses' button
claremacrae Jan 14, 2025
d97013e
feat: ! Support translation of 'Add All Unknown Status Types' button
claremacrae Jan 14, 2025
bb8e1b5
feat: ! Support translation of more Custom Statuses buttons
claremacrae Jan 14, 2025
ecc5590
feat: ! Add Chinese translations, courtesy of the i18n plugin
claremacrae Jan 14, 2025
84393bf
feat: ! Support translation of theme and snippet collection names
claremacrae Jan 14, 2025
1ddeedc
feat: ! Support translation of 'Add theme collection' button
claremacrae Jan 14, 2025
e5f9f45
refactor: Remove temporary debug output
claremacrae Jan 14, 2025
d795bd0
feat: ! Support translation of editing Task Status Symbol
claremacrae Jan 21, 2025
583b072
feat: ! Support translation of editing Task Status Name
claremacrae Jan 21, 2025
f9e0998
feat: ! Support translation of editing Task Next Status Symbol
claremacrae Jan 21, 2025
d6ab133
feat: ! Support translation of editing Task Status Type
claremacrae Jan 21, 2025
73b84e1
feat: ! Support translations of unused command option, and error message
claremacrae Jan 21, 2025
8bdfb2d
feat: ! Add Chinese translations, courtesy of the i18n plugin
claremacrae Jan 21, 2025
9025bee
refactor: ! Remove some unused translation strings from i18n.ts
claremacrae Jan 21, 2025
0de070d
feat: ! Support translation of Status Report: Title
claremacrae Jan 21, 2025
c086b1a
feat: ! Support translation of Status Report: This file...
claremacrae Jan 21, 2025
19873f6
feat: ! Support translation of Status Report: Finish the About section
claremacrae Jan 21, 2025
d44cd20
feat: ! Support translation of Status Report: "Status Settings" section
claremacrae Jan 21, 2025
099e98e
feat: ! Support translation of Status Report: "Loaded Settings" section
claremacrae Jan 22, 2025
d8eb331
feat: ! Add Chinese translations, courtesy of the i18n plugin
claremacrae Jan 22, 2025
a06f2a0
refactor: . Extract nextStatusSymbol
claremacrae Jan 22, 2025
f078613
refactor: . Extract printableSymbol
claremacrae Jan 22, 2025
d060980
test: . Document how to initialise i18n in tests
claremacrae Jan 22, 2025
c95fe3e
feat: ! Support translation of Status Report: Next symbol is unknown
claremacrae Jan 22, 2025
305150a
refactor: . Extract variables symbol and type
claremacrae Jan 22, 2025
bf8739f
feat: ! Support translation of Status Report: Not conventional type
claremacrae Jan 22, 2025
02ffe3a
refactor: . Extract variable nextType
claremacrae Jan 22, 2025
1918c00
feat: ! Support translation of Status Report: Not conventional type a…
claremacrae Jan 22, 2025
f5cc103
feat: ! Support translation of Status Report: Not conventional type a…
claremacrae Jan 22, 2025
f20452d
feat: ! Support translation of Status Report: Not conventional type a…
claremacrae Jan 22, 2025
0b8786c
feat: ! Support translation of Status Report: Empty Symbol
claremacrae Jan 22, 2025
7dcdbc2
refactor: . Extract variable symbol
claremacrae Jan 22, 2025
9cdb9d1
feat: ! Support translation of Status Report: Duplicate Symbol
claremacrae Jan 22, 2025
3999ffd
refactor: - Fix type in translation string key name
claremacrae Jan 22, 2025
f68a1ee
feat: ! Support translation of Status Report: Cannot find next status
claremacrae Jan 22, 2025
6f704ab
feat: ! Support translation of Status Report: Columns headings
claremacrae Jan 22, 2025
ffda728
feat: ! Add Chinese translations, courtesy of the i18n plugin
claremacrae Jan 22, 2025
2dce168
fix: Remove a duplicate translation value spotted by Easy i18n
claremacrae Jan 22, 2025
3e4c1db
contrib: Start a page with i18n things to document
claremacrae Jan 22, 2025
577e6e3
fix: Remove accidental duplication of status options in Settings
claremacrae Jan 25, 2025
8e90793
contrib: Try to fix GitHub rendering of 'About Translation.md' in PR
claremacrae Jan 25, 2025
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
85 changes: 85 additions & 0 deletions contributing/Translation/About Translation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
---
publish: true
---

# About Translation

> [!Danger] This page is under development
> For now, it is a list of things to remember to record.

Since Tasks X.Y.Z, we are gradually migration most user-visible text in to a translation framework - also know as internationalisation or i18n.

Topics - likely to be broken out in to separate pages:

- Setting up WebStorm
- Overview of the translation setup
- How to add a new string
- How to enable translations in a new language
- How to add new translations

## Tools Used

- i18next
- i18next-browser-languagedetector
- i18next-parser
- WebStorm
- [Easy I18n](https://plugins.jetbrains.com/plugin/16316-easy-i18n) plugin.

## Files

- `src/i18n/i18n.ts`
- `src/i18n/locales/*.json`

### Key names

You can see the currently-used key names in:

`src/i18n/locales/en.json`

Here, they are nested, for example:

```json
{
"modals": {
"customStatusModal": {
"editAvailableAsCommand": {
"name": "Available as command"
},
},
},
}
```

The value of that string is obtained in code with:

```ts
i18n.t('modals.customStatusModal.editAvailableAsCommand.name')
```

In WebStorm with the Easy I18n plugin configured, hovering over that `i18n.t()` will show you the expanded text.

## For Developers

> [!Warning] Do not extract strings by hand
> It is just too time-consuming and too error-prone.
> Find a tool in your IDE that will allow you to select text, give it a label and extract it to all languages.

The flow for adding a new translation value - in WebStorm...

Scenarios/topics:

- Simple string
- Simple string with interpolation
- Beware of extra backticks
- `yarn extract-i18n`

## WebStorm Easy I18n

- Action for extracting strings
- Table and tree view
- Meaning of red text
- Meaning of orange text

## For Translators

We will find a website to make it easy to add new translations.
1 change: 1 addition & 0 deletions contributing/Welcome.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Getting the most from this documentation:
- See [[About Debugging]] for tips to debug the plugin
- See [[About Code]] for descriptions of the project's source code
- See [[About Documentation]] if you would like to improve and test the user docs
- See [[About Translation]] if you are adding new text the plugin, and to help translate it

## For Maintainers

Expand Down
21 changes: 21 additions & 0 deletions i18next-parser.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
module.exports = {
input: ['src/**/*.{ts,svelte}'], // Scan relevant files
output: 'src/i18n/locales/$LOCALE.json', // Output files
locales: ['en', 'zh_cn'], // Supported locales
defaultNamespace: 'translation',
keySeparator: '.', // Use dots to represent nested keys
namespaceSeparator: false, // Disable namespace separation
interpolation: {
prefix: '{{', // Preserve placeholders like {{name}}
suffix: '}}',
},
useKeysAsDefaultValue: true, // Use keys for default values if no translation exists
resetDefaultValueLocale: null, // Retain existing default values
sort: true, // Sort keys alphabetically
keepRemoved: true, // Keep keys not found in source files
jsonIndent: 2, // Pretty-print JSON
lexers: {
ts: ['JavascriptLexer'], // TypeScript files
svelte: ['HTMLLexer'], // Svelte files
},
};
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"test": "jest --ci",
"test:dev": "jest --watch",
"deploy:local": "pwsh -ExecutionPolicy Unrestricted -NoProfile -File ./scripts/Test-TasksInLocalObsidian.ps1",
"extract-i18n": "npx i18next-parser",
"circular-deps-text": "madge --circular --extensions ts ./src > circular-deps.txt",
"circular-deps-image": "madge --circular --extensions ts ./src --image circular-deps.png",
"code-docs": "typedoc \"src/**/*\""
Expand Down Expand Up @@ -49,6 +50,7 @@
"eslint-config-prettier": "^8.8.0",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-prettier": "^4.2.1",
"i18next-parser": "^9.1.0",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.5.0",
"madge": "^8.0.0",
Expand Down Expand Up @@ -76,6 +78,8 @@
"eslint-plugin-svelte3": "^4.0.0",
"eventemitter2": "^6.4.5",
"flatpickr": "^4.6.13",
"i18next": "^24.2.1",
"i18next-browser-languagedetector": "^8.0.2",
"mustache": "^4.2.0",
"mustache-validator": "^0.2.0",
"rrule": "^2.7.2",
Expand Down
30 changes: 15 additions & 15 deletions src/Config/CustomStatusModal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { Plugin } from 'obsidian';
import { StatusConfiguration, StatusType } from '../Statuses/StatusConfiguration';
import { StatusValidator } from '../Statuses/StatusValidator';
import { Status } from '../Statuses/Status';
import { i18n } from '../i18n/i18n';

const validator = new StatusValidator();

Expand Down Expand Up @@ -49,10 +50,8 @@ export class CustomStatusModal extends Modal {

let statusSymbolText: TextComponent;
new Setting(settingDiv)
.setName('Task Status Symbol')
.setDesc(
'This is the character between the square braces. (It can only be edited for Custom statuses, and not Core statuses.)',
)
.setName(i18n.t('modals.customStatusModal.editStatusSymbol.name'))
.setDesc(i18n.t('modals.customStatusModal.editStatusSymbol.description'))
.addText((text) => {
statusSymbolText = text;
text.setValue(this.statusSymbol).onChange((v) => {
Expand All @@ -68,8 +67,8 @@ export class CustomStatusModal extends Modal {

let statusNameText: TextComponent;
new Setting(settingDiv)
.setName('Task Status Name')
.setDesc('This is the friendly name of the task status.')
.setName(i18n.t('modals.customStatusModal.editStatusName.name'))
.setDesc(i18n.t('modals.customStatusModal.editStatusName.description'))
.addText((text) => {
statusNameText = text;
text.setValue(this.statusName).onChange((v) => {
Expand All @@ -83,8 +82,8 @@ export class CustomStatusModal extends Modal {

let statusNextSymbolText: TextComponent;
new Setting(settingDiv)
.setName('Task Next Status Symbol')
.setDesc('When clicked on this is the symbol that should be used next.')
.setName(i18n.t('modals.customStatusModal.editNextStatusSymbol.name'))
.setDesc(i18n.t('modals.customStatusModal.editNextStatusSymbol.description'))
.addText((text) => {
statusNextSymbolText = text;
text.setValue(this.statusNextSymbol).onChange((v) => {
Expand All @@ -100,8 +99,8 @@ export class CustomStatusModal extends Modal {
});

new Setting(settingDiv)
.setName('Task Status Type')
.setDesc('Control how the status behaves for searching and toggling.')
.setName(i18n.t('modals.customStatusModal.editStatusType.name'))
.setDesc(i18n.t('modals.customStatusModal.editStatusType.description'))
.addDropdown((dropdown) => {
const types = [
StatusType.TODO,
Expand All @@ -119,11 +118,11 @@ export class CustomStatusModal extends Modal {
});

if (Status.tasksPluginCanCreateCommandsForStatuses()) {
// This feature is disabled as not-yet implemented.
// But we will apply the translation string now, for possible later use.
new Setting(settingDiv)
.setName('Available as command')
.setDesc(
'If enabled this status will be available as a command so you can assign a hotkey and toggle the status using it.',
)
.setName(i18n.t('modals.customStatusModal.editAvailableAsCommand.name'))
.setDesc(i18n.t('modals.customStatusModal.editAvailableAsCommand.description'))
.addToggle((toggle) => {
toggle.setValue(this.statusAvailableAsCommand).onChange(async (value) => {
this.statusAvailableAsCommand = value;
Expand All @@ -139,7 +138,8 @@ export class CustomStatusModal extends Modal {
.onClick(async () => {
const errors = validator.validate(this.statusConfiguration());
if (errors.length > 0) {
const message = errors.join('\n') + '\n\n' + 'Fix errors before saving.';
const message =
errors.join('\n') + '\n\n' + i18n.t('modals.customStatusModal.fixErrorsBeforeSaving');
// console.debug(message);
new Notice(message);
return;
Expand Down
9 changes: 5 additions & 4 deletions src/Config/Settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { DefaultTaskSerializer, type TaskSerializer } from '../TaskSerializer';
import type { SuggestionBuilder } from '../Suggestor';
import type { LogOptions } from '../lib/logging';
import { DataviewTaskSerializer } from '../TaskSerializer/DataviewTaskSerializer';
import { i18n } from '../i18n/i18n';
import { DebugSettings } from './DebugSettings';
import { StatusSettings } from './StatusSettings';
import { Feature } from './Feature';
Expand All @@ -29,8 +30,8 @@ export type HeadingState = {
*
*/
interface TaskFormat {
/** User facing name of the {@link TaskFormat} */
displayName: string;
/** Function that returns the user facing name of the {@link TaskFormat} */
getDisplayName: () => string;
/** {@link TaskSerializer} responsible for reading Tasks from text and writing them back into text */
taskSerializer: TaskSerializer;
/** Function that generates Intellisense-like suggestions as a user is typing a Task */
Expand All @@ -40,12 +41,12 @@ interface TaskFormat {
/** Map of all defined {@link TaskFormat}s */
export const TASK_FORMATS = {
tasksPluginEmoji: {
displayName: 'Tasks Emoji Format',
getDisplayName: () => i18n.t('settings.format.displayName.tasksEmojiFormat'),
taskSerializer: new DefaultTaskSerializer(DEFAULT_SYMBOLS),
buildSuggestions: makeDefaultSuggestionBuilder(DEFAULT_SYMBOLS, DEFAULT_MAX_GENERIC_SUGGESTIONS, false),
},
dataview: {
displayName: 'Dataview',
getDisplayName: () => i18n.t('settings.format.displayName.dataview'),
taskSerializer: new DataviewTaskSerializer(),
buildSuggestions: onlySuggestIfBracketOpen(
makeDefaultSuggestionBuilder(DATAVIEW_SYMBOLS, DEFAULT_MAX_GENERIC_SUGGESTIONS, true),
Expand Down
Loading
Loading