From 7798e2d1140a953904ec2f84df62c3a89aa66ee0 Mon Sep 17 00:00:00 2001 From: ransome1 Date: Tue, 26 Sep 2023 22:09:50 +0200 Subject: [PATCH] Added multi language support, added Hindi and Korean as additional languages, continued refactoring the renderer components, slightly changed visuals of search bar --- README.md | 91 +------ package-lock.json | 61 +++++ package.json | 2 + src/__tests__/__mock__/recurrence.txt | 14 +- src/__tests__/__mock__/test.txt | 2 +- src/__tests__/main/ArchiveTodos.tsx | 3 +- src/locales/cs.json | 66 +++++ src/locales/de.json | 66 +++++ src/locales/en.json | 66 +++++ src/locales/es.json | 66 +++++ src/locales/fr.json | 66 +++++ src/locales/hi.json | 66 +++++ src/locales/hu.json | 66 +++++ src/locales/it.json | 66 +++++ src/locales/jp.json | 66 +++++ src/locales/ko.json | 66 +++++ src/locales/pl.json | 66 +++++ src/locales/pt.json | 66 +++++ src/locales/ru.json | 66 +++++ src/locales/tr.json | 66 +++++ src/locales/zh.json | 66 +++++ src/main/config.ts | 5 +- src/main/main.ts | 7 +- src/main/modules/File.ts | 2 - src/main/modules/ProcessDataRequest.ts | 1 + src/main/preload.ts | 55 ++-- src/main/tray.ts | 9 +- src/renderer/App.tsx | 239 +++++++++-------- src/renderer/ArchiveTodos.js | 53 ---- src/renderer/ArchiveTodos.tsx | 61 +++++ src/renderer/AutoSuggest.js | 2 +- src/renderer/AutoSuggest.scss | 7 +- src/renderer/Coloring.scss | 10 +- src/renderer/ContextMenu.js | 2 +- src/renderer/DataGrid/Elements.tsx | 133 ++++++++++ .../{DataGrid.js => DataGrid/Grid.js} | 10 +- src/renderer/DataGrid/Group.tsx | 42 +++ src/renderer/DataGrid/Row.tsx | 137 ++++++++++ src/renderer/DataGridRow.js | 243 ------------------ src/renderer/DataGridRow.scss | 4 +- .../{DatePicker.js => DatePicker.tsx} | 26 +- src/renderer/DatePickerInline.js | 2 +- src/renderer/DraggableList.tsx | 70 ++--- src/renderer/DraggableListItem.tsx | 26 +- src/renderer/Drawer.js | 113 -------- src/renderer/Drawer.tsx | 107 ++++++++ ...awerAttributes.js => DrawerAttributes.tsx} | 112 ++++---- src/renderer/DrawerFilters.js | 58 ----- src/renderer/DrawerFilters.scss | 2 +- src/renderer/DrawerFilters.tsx | 46 ++++ src/renderer/DrawerSorting.js | 52 ---- src/renderer/DrawerSorting.scss | 5 + src/renderer/DrawerSorting.tsx | 43 ++++ src/renderer/FileTabs.js | 88 ------- src/renderer/FileTabs.scss | 6 +- src/renderer/FileTabs.tsx | 98 +++++++ src/renderer/LanguageSelector.tsx | 116 +++++++++ src/renderer/Navigation.js | 6 +- src/renderer/PriorityPicker.js | 49 ---- src/renderer/PriorityPicker.scss | 2 +- src/renderer/PriorityPicker.tsx | 58 +++++ src/renderer/RecurrencePicker.scss | 2 +- ...currencePicker.js => RecurrencePicker.tsx} | 82 +++--- src/renderer/Search.scss | 13 +- src/renderer/{Search.js => Search.tsx} | 61 +++-- src/renderer/Settings.js | 105 -------- src/renderer/Settings.scss | 8 +- src/renderer/Settings.tsx | 78 ++++++ src/renderer/Shared.ts | 47 ---- src/renderer/Shared.tsx | 76 ++++++ src/renderer/SplashScreen.scss | 1 + .../{SplashScreen.js => SplashScreen.tsx} | 46 ++-- src/renderer/Themes.js | 7 +- .../{TodoDialog.js => TodoDialog.tsx} | 54 ++-- src/renderer/ToolBar.js | 4 +- src/renderer/ToolBar.scss | 10 +- 76 files changed, 2569 insertions(+), 1295 deletions(-) create mode 100644 src/locales/cs.json create mode 100644 src/locales/de.json create mode 100644 src/locales/en.json create mode 100644 src/locales/es.json create mode 100644 src/locales/fr.json create mode 100644 src/locales/hi.json create mode 100644 src/locales/hu.json create mode 100644 src/locales/it.json create mode 100644 src/locales/jp.json create mode 100644 src/locales/ko.json create mode 100644 src/locales/pl.json create mode 100644 src/locales/pt.json create mode 100644 src/locales/ru.json create mode 100644 src/locales/tr.json create mode 100644 src/locales/zh.json delete mode 100644 src/renderer/ArchiveTodos.js create mode 100644 src/renderer/ArchiveTodos.tsx create mode 100644 src/renderer/DataGrid/Elements.tsx rename src/renderer/{DataGrid.js => DataGrid/Grid.js} (93%) create mode 100644 src/renderer/DataGrid/Group.tsx create mode 100644 src/renderer/DataGrid/Row.tsx delete mode 100644 src/renderer/DataGridRow.js rename src/renderer/{DatePicker.js => DatePicker.tsx} (67%) delete mode 100644 src/renderer/Drawer.js create mode 100644 src/renderer/Drawer.tsx rename src/renderer/{DrawerAttributes.js => DrawerAttributes.tsx} (62%) delete mode 100644 src/renderer/DrawerFilters.js create mode 100644 src/renderer/DrawerFilters.tsx delete mode 100644 src/renderer/DrawerSorting.js create mode 100644 src/renderer/DrawerSorting.scss create mode 100644 src/renderer/DrawerSorting.tsx delete mode 100644 src/renderer/FileTabs.js create mode 100644 src/renderer/FileTabs.tsx create mode 100644 src/renderer/LanguageSelector.tsx delete mode 100644 src/renderer/PriorityPicker.js create mode 100644 src/renderer/PriorityPicker.tsx rename src/renderer/{RecurrencePicker.js => RecurrencePicker.tsx} (62%) rename src/renderer/{Search.js => Search.tsx} (56%) delete mode 100644 src/renderer/Settings.js create mode 100644 src/renderer/Settings.tsx delete mode 100644 src/renderer/Shared.ts create mode 100644 src/renderer/Shared.tsx rename src/renderer/{SplashScreen.js => SplashScreen.tsx} (60%) rename src/renderer/{TodoDialog.js => TodoDialog.tsx} (62%) diff --git a/README.md b/README.md index 2a401098..41ab690b 100644 --- a/README.md +++ b/README.md @@ -2,31 +2,18 @@ [![Code scan and test cases](https://github.com/ransome1/sleek/actions/workflows/code-scan.yml/badge.svg?branch=develop)](https://github.com/ransome1/sleek/actions/workflows/code-scan.yml) [![sleek](https://snapcraft.io/sleek/badge.svg)](https://snapcraft.io/sleek) -## ❤️ Become a contributer. -[sleek is currently being rewritten.](https://github.com/ransome1/sleek/discussions/501) Now is a good time to join us in our mission to provide a user-friendly, free, and open-source todo.txt client. We're actively inviting passionate contributors skilled in `React`, `TypeScript`, `Electron`, and `Jest/Playwright` to join our collaborative effort. The `develop` branch reflects the most recent progress. Here you'll find our roadmap: https://github.com/users/ransome1/projects/3. +## ❤️ Become a contributer +[sleek is currently being rewritten.](https://github.com/ransome1/sleek/discussions/501) Now is a good time to join us in our mission to provide a user-friendly, free, and open-source todo.txt client. We're actively inviting passionate contributors skilled in `React`, `TypeScript`, `Electron`, and `Jest/Playwright` to join our collaborative effort. The `develop` branch reflects the most recent progress. Here you'll find our roadmap: https://github.com/users/ransome1/projects/3. For those interested, [we've updated our contribution guidelines](https://github.com/ransome1/sleek/wiki/Contributing-Guidelines). -For those interested, we've updated our contribution guidelines, which you can find here: https://github.com/ransome1/sleek/blob/master/CONTRIBUTING.md. - -## sleek is an open-source (FOSS) todo manager based on the todo.txt syntax. It's available for Windows, MacOS and Linux +## sleek is an open-source (FOSS) todo manager based on the todo.txt syntax. It's available for Windows, macOS and Linux sleek is an open-source (FOSS) todo manager based on the todo.txt syntax. Stripped down to only the most necessary features, and with a clean and simple interface, sleek aims to help you focus on getting things done. All classic todo.txt attributes are supported and enhanced by additional features. Creating todos is straightforward, and tag-based filtering in tandem with highly customisable grouping and smart full-text search allows for rapid information retrieval. Completed todos can be hidden or archived into separate done.txt files. Easy integration with other todo.txt apps is facilitated by continuously scanning todo.txt files for changes. -sleek is available for Windows, MacOS and Linux, and in several languages. For a detailed list of features, see below. Useful information can be found in sleek's wiki. - -+ [Sponsor sleek](#sponsor-sleek) -+ [Get sleek from Apple Mac App Store](#get-sleek-from-apple-mac-app-store) -+ [Get sleek from Microsoft Store](#get-sleek-from-microsoft-store) -+ [Get sleek from Snap Store](#get-sleek-from-snap-store) -+ [Get sleek from Flathub](#get-sleek-from-flathub) -+ [Get sleek from Homebrew](#get-sleek-from-homebrew) -+ [Get sleek from Arch User Repository](#get-sleek-from-arch-user-repository) -+ [Download sleek](#download-sleek) -+ [Build sleek from source code](#build-sleek-from-source-code) -+ [sleek's features](#sleeks-features) +sleek is available for Windows, macOS and Linux, and in several languages. https://github.com/ransome1/sleek/wiki/Features. ### Sponsor sleek -Pushing sleek to the Apple and Microsoft app stores creates annual costs. You can help covering these by sponsoring the project. +Pushing sleek to the Apple and Microsoft app stores creates annual costs. You can help covering these by [sponsoring the project](https://github.com/sponsors/ransome1). ### Get sleek from Apple Mac App Store Get sleek from Apple Mac App Store @@ -37,77 +24,25 @@ Pushing sleek to the Apple and Microsoft app stores creates annual costs. You ca ### Get sleek from Snap Store [![Get it from the Snap Store](https://snapcraft.io/static/images/badges/en/snap-store-black.svg)](https://snapcraft.io/sleek) -Install sleek from Snap Store using: `sudo snap install sleek` +Install sleek from [Snap Store](https://snapcraft.io/sleek) using: `sudo snap install sleek` ### Get sleek from Flathub Download on Flathub -Install sleek from Flathub using: `flatpak install flathub com.github.ransome1.sleek` +Install sleek from [Flathub](https://flathub.org/apps/details/com.github.ransome1.sleek) using: `flatpak install flathub com.github.ransome1.sleek` Run it using: `flatpak run com.github.ransome1.sleek` ### Get sleek from Homebrew -Install sleek from Homebrew. +Install sleek from [Homebrew](https://formulae.brew.sh/cask/sleek). `brew install --cask sleek` ### Get sleek from Arch User Repository -Install sleek from AUR. -1. Setup Yay +Install sleek from [AUR](https://aur.archlinux.org/packages/sleek/). +1. Setup [Yay](https://github.com/Jguer/yay#installation) 2. `yay -S sleek` ### Download sleek -You can download sleek for Windows, MacOS and Linux from -- SourceForge -- Github - -### Build sleek from source code -1. Setup Git and node.js. -2. Clone sleek `git clone https://github.com/ransome1/sleek.git` and cd into sleek's directory -3. Install dependencies `npm install` -4. Build and package sleek `npm run package` -5. The binaries will be placed in the working directory, in a subfolder named `release/build` - -### sleek's features -* Uses existing todo.txt files or creates new ones -* Add and search for todos by - - priority - - context - - project - - due date - - creation date - - completion date - - recurrence (repeating todo) - - threshold dates - - pomodoro timer -* Filter, sort, and group todos by all available attributes -* Invert the sorting of each group -* Find todos using full-text search compatible with todo.txt syntax -* Inline autocomplete available for adding contexts and projects -* Dates and priorities can be selected by built-in picker elements -* Navigable via keyboard shortcuts -* Tabindex available -* Due date notifications -* Toggle between dark and light mode -* Compact view and zoom available -* Completed todos can be shown, hidden, and archived -* Multiline todos can be created -* Filters are sorted alphanummerically -* Hyperlinks detected automatically -* File watcher scans todo.txt files for changes -* Simultaneously manage multiple todo.txt files -* Language options - - English - - German - - Italian - - Spanish - - French - - Simplified Chinese - - Brazilian Portugese - - Japanese - - Turkish - - Hungarian - - Czech - - Polish - - Russian - -A more detailed documentation can be found in sleek's wiki. \ No newline at end of file +You can download sleek for Windows, macOS and Linux from +- [SourceForge](https://sourceforge.net/p/sleek/) +- [GitHub](https://github.com/ransome1/sleek/releases/latest) \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 1a99dc8f..51967959 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "electron-log": "^4.4.8", "electron-store": "^8.1.0", "electron-updater": "^5.3.0", + "i18next": "^23.5.1", "jstodotxt": "^1.0.0-alpha.0", "material-ui-popup-state": "^5.0.9", "mock-fs": "^5.2.0", @@ -26,6 +27,7 @@ "react-autosuggest": "^10.1.0", "react-beautiful-dnd": "^13.1.1", "react-dom": "^18.2.0", + "react-i18next": "^13.2.2", "react-router-dom": "^6.11.2", "sugar": "^2.0.6" }, @@ -11392,6 +11394,14 @@ "node": ">= 12" } }, + "node_modules/html-parse-stringify": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", + "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==", + "dependencies": { + "void-elements": "3.1.0" + } + }, "node_modules/html-webpack-plugin": { "version": "5.5.3", "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.5.3.tgz", @@ -11573,6 +11583,28 @@ "ms": "^2.0.0" } }, + "node_modules/i18next": { + "version": "23.5.1", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.5.1.tgz", + "integrity": "sha512-JelYzcaCoFDaa+Ysbfz2JsGAKkrHiMG6S61+HLBUEIPaF40WMwW9hCPymlQGrP+wWawKxKPuSuD71WZscCsWHg==", + "funding": [ + { + "type": "individual", + "url": "https://locize.com" + }, + { + "type": "individual", + "url": "https://locize.com/i18next.html" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + } + ], + "dependencies": { + "@babel/runtime": "^7.22.5" + } + }, "node_modules/iconv-corefoundation": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/iconv-corefoundation/-/iconv-corefoundation-1.1.7.tgz", @@ -16149,6 +16181,27 @@ "react": "^18.2.0" } }, + "node_modules/react-i18next": { + "version": "13.2.2", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-13.2.2.tgz", + "integrity": "sha512-+nFUkbRByFwnrfDcYqvzBuaeZb+nACHx+fAWN/pZMddWOCJH5hoc21+Sa/N/Lqi6ne6/9wC/qRGOoQhJa6IkEQ==", + "dependencies": { + "@babel/runtime": "^7.22.5", + "html-parse-stringify": "^3.0.1" + }, + "peerDependencies": { + "i18next": ">= 23.2.3", + "react": ">= 16.8.0" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, "node_modules/react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", @@ -19041,6 +19094,14 @@ "node": ">=0.6.0" } }, + "node_modules/void-elements": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/w3c-xmlserializer": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", diff --git a/package.json b/package.json index 7453b647..39b75f4c 100644 --- a/package.json +++ b/package.json @@ -78,6 +78,7 @@ "electron-log": "^4.4.8", "electron-store": "^8.1.0", "electron-updater": "^5.3.0", + "i18next": "^23.5.1", "jstodotxt": "^1.0.0-alpha.0", "material-ui-popup-state": "^5.0.9", "mock-fs": "^5.2.0", @@ -86,6 +87,7 @@ "react-autosuggest": "^10.1.0", "react-beautiful-dnd": "^13.1.1", "react-dom": "^18.2.0", + "react-i18next": "^13.2.2", "react-router-dom": "^6.11.2", "sugar": "^2.0.6" }, diff --git a/src/__tests__/__mock__/recurrence.txt b/src/__tests__/__mock__/recurrence.txt index 985a326d..346f067c 100644 --- a/src/__tests__/__mock__/recurrence.txt +++ b/src/__tests__/__mock__/recurrence.txt @@ -1,10 +1,10 @@ -2023-09-24 Line 1 rec:1d due:2023-09-25 -2023-09-24 Line 1 rec:w due:2023-10-01 -2023-09-24 Line 1 rec:2m due:2023-11-24 -2023-09-24 Line 1 rec:+1d due:2023-09-26 -2023-09-24 Line 1 rec:7w due:2023-11-12 +2023-09-26 Line 1 rec:1d due:2023-09-27 +2023-09-26 Line 1 rec:w due:2023-10-03 +2023-09-26 Line 1 rec:2m due:2023-11-26 +2023-09-26 Line 1 rec:+1d due:2023-09-28 +2023-09-26 Line 1 rec:7w due:2023-11-14 2023-07-21 Line 1 due:2023-07-24 rec:+1b 2021-01-01 taxes are due in one year t:2022-03-30 due:2022-04-30 rec:+1y -2023-09-24 Water plants @home +quick due:2023-10-01 t:2023-09-21 rec:1w -2023-09-24 Line 1 rec:+1d t:2023-09-20 due:2023-09-25 \ No newline at end of file +2023-09-26 Water plants @home +quick due:2023-10-03 t:2023-09-23 rec:1w +2023-09-26 Line 1 rec:+1d t:2023-09-20 due:2023-09-27 \ No newline at end of file diff --git a/src/__tests__/__mock__/test.txt b/src/__tests__/__mock__/test.txt index ce9c9d0a..3520ada6 100644 --- a/src/__tests__/__mock__/test.txt +++ b/src/__tests__/__mock__/test.txt @@ -1,5 +1,5 @@ Line 1 Edited line New line -2023-09-24 New line with creation date +2023-09-26 New line with creation date New line with relative threshold date t:June 3rd, 2005 \ No newline at end of file diff --git a/src/__tests__/main/ArchiveTodos.tsx b/src/__tests__/main/ArchiveTodos.tsx index 9c57ecf5..2711f62c 100644 --- a/src/__tests__/main/ArchiveTodos.tsx +++ b/src/__tests__/main/ArchiveTodos.tsx @@ -20,6 +20,7 @@ const mockIpcMainEvent = { reply: jest.fn(), }; + describe('Archiving', () => { beforeEach(async() => { jest.clearAllMocks(); @@ -33,7 +34,7 @@ describe('Archiving', () => { }); test('Should collect data from todo and done file and merge it properly', async () => { - await archiveTodos(mockIpcMainEvent); + await archiveTodos(); const fileContent = await fs.readFile('./src/__tests__/__mock__/done.txt', 'utf8'); const expectedContent = `x 2022-02-02 todo from done.txt 1\nx 2022-02-03 todo from done.txt 2\nx 2022-02-04 todo from done.txt 3\nx 2022-02-05 todo from done.txt 4\nx 2022-02-01 Finished todo 3\nx 2022-02-08 Finished todo 1\nx 2022-02-17 Finished todo 2`; await new Promise((resolve) => setTimeout(resolve, 1000)); diff --git a/src/locales/cs.json b/src/locales/cs.json new file mode 100644 index 00000000..5b59fe19 --- /dev/null +++ b/src/locales/cs.json @@ -0,0 +1,66 @@ +{ + "copy": "Kopírovat", + "delete": "Smazat", + "cancel": "Zrušit", + "fileTabs.changeLocation": "Změnit umístění souboru done.txt", + "fileTabs.revealFile": "Zobrazit v Průzkumníku", + "fileTabs.removeFileHeadline": "Odstranit soubor ze sleek?", + "fileTabs.removeFileText": "Nebude smazán z pevného disku.", + "fileTabs.removeFileLabel": "Odstranit", + "settings.headline": "Nastavení", + "settings.appendCreationDate": "Přidat datum vytvoření při vytvoření úkolu", + "settings.convertRelativeToAbsoluteDates": "Převést relativní data na absolutní data", + "settings.notificationsAllowed": "Poslat oznámení, když je termín dnes nebo zítra", + "settings.showFileTabs": "Zobrazit záložky souborů", + "settings.tray": "Minimalizovat do oznamovací oblasti", + "settings.colorTheme": "Téma", + "settings.system": "Sledovat systém", + "settings.light": "Světlé", + "settings.dark": "Tmavé", + "settings.language": "Jazyk", + "drawer.tabs.attributes": "Atributy", + "drawer.tabs.filters": "Filtry", + "drawer.tabs.sorting": "Řazení", + "drawer.filters.showCompleted": "Dokončené úkoly", + "drawer.filters.showHidden": "Skryté úkoly", + "drawer.filters.thresholdDateInTheFuture": "Termín v budoucnosti", + "drawer.filters.dueDateInTheFuture": "Termín splnění v budoucnosti", + "drawer.attributes.noAttributesAvailable": "Zatím nejsou k dispozici žádné atributy", + "drawer.sorting.fileSorting": "Seřadit úkoly podle pořadí v souboru", + "shared.attributeMapping.t": "Termín", + "shared.attributeMapping.due": "Termín splnění", + "shared.attributeMapping.projects": "Projekty", + "shared.attributeMapping.contexts": "Kontexty", + "shared.attributeMapping.priority": "Priorita", + "shared.attributeMapping.rec": "Opakování", + "shared.attributeMapping.pm": "Pomodoros", + "shared.attributeMapping.created": "Datum vytvoření", + "shared.attributeMapping.completed": "Datum dokončení", + "splashscreen.noFiles.text": "Přetáhněte sem soubor todo.txt nebo použijte tlačítka", + "splashscreen.noFiles.open": "Otevřít soubor todo.txt", + "splashscreen.noFiles.create": "Vytvořit soubor todo.txt", + "splashscreen.noTodosAvailable.text": "V tomto souboru nejsou žádné úkoly", + "splashscreen.noTodosAvailable.create": "Vytvořit úkol", + "splashscreen.noTodosVisible.text": "Žádné výsledky nejsou viditelné.", + "splashscreen.noTodosVisible.reset": "Obnovit filtry a hledání", + "search.visibleTodos": "Viditelné úkoly: ", + "todoDialog.footer.save": "Uložit", + "todoDialog.footer.cancel": "Zrušit", + "todoDialog.snackbar.emptyInput": "Zadejte prosím něco do textového pole", + "todoDialog.priorityPicker.label": "Priorita", + "todoDialog.datePicker.threshold": "Termín", + "todoDialog.datePicker.due": "Termín splnění", + "todoDialog.recurrencePicker.label": "Opakování", + "todoDialog.recurrencePicker.every": "Každý", + "todoDialog.recurrencePicker.day": "den", + "todoDialog.recurrencePicker.businessDay": "pracovní den", + "todoDialog.recurrencePicker.week": "týden", + "todoDialog.recurrencePicker.month": "měsíc", + "todoDialog.recurrencePicker.year": "rok", + "todoDialog.recurrencePicker.strict": "striktní", + "prompt.delete.headline": "Smazat úkol?", + "prompt.delete.text": "Úkol bude trvale odstraněn ze souboru", + "prompt.archive.headline": "Archivovat dokončené úkoly?", + "prompt.archive.text": "Tímto se přesunou všechny dokončené úkoly do vašeho určeného souboru done", + "prompt.archive.button": "Archivovat" +} diff --git a/src/locales/de.json b/src/locales/de.json new file mode 100644 index 00000000..47f2661e --- /dev/null +++ b/src/locales/de.json @@ -0,0 +1,66 @@ +{ + "copy": "Kopieren", + "delete": "Löschen", + "cancel": "Abbrechen", + "fileTabs.changeLocation": "Speicherort der done.txt-Datei ändern", + "fileTabs.revealFile": "Im Finder anzeigen", + "fileTabs.removeFileHeadline": "Datei aus sleek entfernen?", + "fileTabs.removeFileText": "Die Datei wird nicht von Ihrer Festplatte gelöscht.", + "fileTabs.removeFileLabel": "Entfernen", + "settings.headline": "Einstellungen", + "settings.appendCreationDate": "Erstellungsdatum hinzufügen, wenn eine Aufgabe erstellt wird", + "settings.convertRelativeToAbsoluteDates": "Relative Daten in absolute Daten umwandeln", + "settings.notificationsAllowed": "Benachrichtigung senden, wenn das Fälligkeitsdatum heute oder morgen ist", + "settings.showFileTabs": "Datei-Tabs anzeigen", + "settings.tray": "In die Taskleiste minimieren", + "settings.colorTheme": "Design", + "settings.system": "System folgen", + "settings.light": "Hell", + "settings.dark": "Dunkel", + "settings.language": "Sprache", + "drawer.tabs.attributes": "Attribute", + "drawer.tabs.filters": "Filter", + "drawer.tabs.sorting": "Sortierung", + "drawer.filters.showCompleted": "Abgeschlossene Aufgaben", + "drawer.filters.showHidden": "Versteckte Aufgaben", + "drawer.filters.thresholdDateInTheFuture": "Schwellenwertdatum in der Zukunft", + "drawer.filters.dueDateInTheFuture": "Fälligkeitsdatum in der Zukunft", + "drawer.attributes.noAttributesAvailable": "Noch keine Attribute verfügbar", + "drawer.sorting.fileSorting": "Aufgaben in der Reihenfolge der Datei anordnen", + "shared.attributeMapping.t": "Schwellenwertdatum", + "shared.attributeMapping.due": "Fälligkeitsdatum", + "shared.attributeMapping.projects": "Projekte", + "shared.attributeMapping.contexts": "Kontexte", + "shared.attributeMapping.priority": "Priorität", + "shared.attributeMapping.rec": "Wiederholung", + "shared.attributeMapping.pm": "Pomodoros", + "shared.attributeMapping.created": "Erstellungsdatum", + "shared.attributeMapping.completed": "Abschlussdatum", + "splashscreen.noFiles.text": "Ziehen Sie Ihre todo.txt-Datei hierher oder verwenden Sie die Schaltflächen", + "splashscreen.noFiles.open": "todo.txt-Datei öffnen", + "splashscreen.noFiles.create": "todo.txt-Datei erstellen", + "splashscreen.noTodosAvailable.text": "Keine Aufgaben in dieser Datei", + "splashscreen.noTodosAvailable.create": "Eine Aufgabe erstellen", + "splashscreen.noTodosVisible.text": "Keine sichtbaren Ergebnisse.", + "splashscreen.noTodosVisible.reset": "Filter und Suche zurücksetzen", + "search.visibleTodos": "Sichtbare Aufgaben: ", + "todoDialog.footer.save": "Speichern", + "todoDialog.footer.cancel": "Abbrechen", + "todoDialog.snackbar.emptyInput": "Bitte geben Sie etwas in das Textfeld ein", + "todoDialog.priorityPicker.label": "Priorität", + "todoDialog.datePicker.threshold": "Schwellenwert", + "todoDialog.datePicker.due": "Fälligkeitsdatum", + "todoDialog.recurrencePicker.label": "Wiederholung", + "todoDialog.recurrencePicker.every": "Jeden", + "todoDialog.recurrencePicker.day": "Tag", + "todoDialog.recurrencePicker.businessDay": "Arbeitstag", + "todoDialog.recurrencePicker.week": "Woche", + "todoDialog.recurrencePicker.month": "Monat", + "todoDialog.recurrencePicker.year": "Jahr", + "todoDialog.recurrencePicker.strict": "Streng", + "prompt.delete.headline": "Aufgabe löschen?", + "prompt.delete.text": "Die Aufgabe wird dauerhaft aus der Datei entfernt", + "prompt.archive.headline": "Abgeschlossene Aufgaben archivieren?", + "prompt.archive.text": "Dadurch werden alle abgeschlossenen Aufgaben in Ihre angegebene 'erledigt'-Datei verschoben", + "prompt.archive.button": "Archivieren" +} diff --git a/src/locales/en.json b/src/locales/en.json new file mode 100644 index 00000000..89c1f688 --- /dev/null +++ b/src/locales/en.json @@ -0,0 +1,66 @@ +{ + "copy": "Copy", + "delete": "Delete", + "cancel": "Cancel", + "fileTabs.changeLocation": "Change location of done.txt file", + "fileTabs.revealFile": "Reveal in Finder", + "fileTabs.removeFileHeadline": "Remove file from sleek?", + "fileTabs.removeFileText": "It will not be deleted from your hard drive.", + "fileTabs.removeFileLabel": "Remove", + "settings.headline": "Settings", + "settings.appendCreationDate": "Append creation date when todo is created", + "settings.convertRelativeToAbsoluteDates": "Convert relative dates to absolute dates", + "settings.notificationsAllowed": "Send notification when due date is today or tomorrow", + "settings.showFileTabs": "Show file tabs", + "settings.tray": "Minimize to tray", + "settings.colorTheme": "Theme", + "settings.system": "Follow system", + "settings.light": "Light", + "settings.dark": "Dark", + "settings.language": "Language", + "drawer.tabs.attributes": "Attributes", + "drawer.tabs.filters": "Filters", + "drawer.tabs.sorting": "Sorting", + "drawer.filters.showCompleted": "Completed todos", + "drawer.filters.showHidden": "Hidden todos", + "drawer.filters.thresholdDateInTheFuture": "Threshold date in the future", + "drawer.filters.dueDateInTheFuture": "Due date in the future", + "drawer.attributes.noAttributesAvailable": "No attributes available yet", + "drawer.sorting.fileSorting": "Order todos as they appear in the file", + "shared.attributeMapping.t": "Threshold date", + "shared.attributeMapping.due": "Due date", + "shared.attributeMapping.projects": "Projects", + "shared.attributeMapping.contexts": "Contexts", + "shared.attributeMapping.priority": "Priority", + "shared.attributeMapping.rec": "Recurrence", + "shared.attributeMapping.pm": "Pomodoros", + "shared.attributeMapping.created": "Creation date", + "shared.attributeMapping.completed": "Completion date", + "splashscreen.noFiles.text": "Drop your todo.txt file here or use the buttons", + "splashscreen.noFiles.open": "Open todo.txt file", + "splashscreen.noFiles.create": "Create todo.txt file", + "splashscreen.noTodosAvailable.text": "No todos in this file", + "splashscreen.noTodosAvailable.create": "Create a todo", + "splashscreen.noTodosVisible.text": "No results visible.", + "splashscreen.noTodosVisible.reset": "Reset filters and search", + "search.visibleTodos": "Visible todos: ", + "todoDialog.footer.save": "Save", + "todoDialog.footer.cancel": "Cancel", + "todoDialog.snackbar.emptyInput": "Please enter something into the text field", + "todoDialog.priorityPicker.label": "Priority", + "todoDialog.datePicker.threshold": "Threshold", + "todoDialog.datePicker.due": "Due", + "todoDialog.recurrencePicker.label": "Recurrence", + "todoDialog.recurrencePicker.every": "Every", + "todoDialog.recurrencePicker.day": "day", + "todoDialog.recurrencePicker.businessDay": "business day", + "todoDialog.recurrencePicker.week": "week", + "todoDialog.recurrencePicker.month": "month", + "todoDialog.recurrencePicker.year": "year", + "todoDialog.recurrencePicker.strict": "strict", + "prompt.delete.headline": "Delete todo?", + "prompt.delete.text": "The todo will be permanently removed from the file", + "prompt.archive.headline": "Archive completed todos?", + "prompt.archive.text": "This will move all completed todos to your specified done file", + "prompt.archive.button": "Archive" +} diff --git a/src/locales/es.json b/src/locales/es.json new file mode 100644 index 00000000..a15b3a95 --- /dev/null +++ b/src/locales/es.json @@ -0,0 +1,66 @@ +{ + "copy": "Copiar", + "delete": "Eliminar", + "cancel": "Cancelar", + "fileTabs.changeLocation": "Cambiar la ubicación del archivo done.txt", + "fileTabs.revealFile": "Mostrar en el Finder", + "fileTabs.removeFileHeadline": "¿Eliminar archivo de sleek?", + "fileTabs.removeFileText": "No se eliminará de su disco duro.", + "fileTabs.removeFileLabel": "Eliminar", + "settings.headline": "Configuración", + "settings.appendCreationDate": "Añadir fecha de creación al crear una tarea", + "settings.convertRelativeToAbsoluteDates": "Convertir fechas relativas a fechas absolutas", + "settings.notificationsAllowed": "Enviar notificación cuando la fecha de vencimiento sea hoy o mañana", + "settings.showFileTabs": "Mostrar pestañas de archivo", + "settings.tray": "Minimizar a la bandeja", + "settings.colorTheme": "Tema", + "settings.system": "Seguir el sistema", + "settings.light": "Claro", + "settings.dark": "Oscuro", + "settings.language": "Idioma", + "drawer.tabs.attributes": "Atributos", + "drawer.tabs.filters": "Filtros", + "drawer.tabs.sorting": "Ordenación", + "drawer.filters.showCompleted": "Tareas completadas", + "drawer.filters.showHidden": "Tareas ocultas", + "drawer.filters.thresholdDateInTheFuture": "Fecha umbral en el futuro", + "drawer.filters.dueDateInTheFuture": "Fecha de vencimiento en el futuro", + "drawer.attributes.noAttributesAvailable": "No hay atributos disponibles aún", + "drawer.sorting.fileSorting": "Ordenar tareas como aparecen en el archivo", + "shared.attributeMapping.t": "Fecha umbral", + "shared.attributeMapping.due": "Fecha de vencimiento", + "shared.attributeMapping.projects": "Proyectos", + "shared.attributeMapping.contexts": "Contextos", + "shared.attributeMapping.priority": "Prioridad", + "shared.attributeMapping.rec": "Recurrencia", + "shared.attributeMapping.pm": "Pomodoros", + "shared.attributeMapping.created": "Fecha de creación", + "shared.attributeMapping.completed": "Fecha de finalización", + "splashscreen.noFiles.text": "Arrastre su archivo todo.txt aquí o use los botones", + "splashscreen.noFiles.open": "Abrir archivo todo.txt", + "splashscreen.noFiles.create": "Crear archivo todo.txt", + "splashscreen.noTodosAvailable.text": "No hay tareas en este archivo", + "splashscreen.noTodosAvailable.create": "Crear una tarea", + "splashscreen.noTodosVisible.text": "No se ven resultados.", + "splashscreen.noTodosVisible.reset": "Restablecer filtros y búsqueda", + "search.visibleTodos": "Tareas visibles: ", + "todoDialog.footer.save": "Guardar", + "todoDialog.footer.cancel": "Cancelar", + "todoDialog.snackbar.emptyInput": "Por favor, ingrese algo en el campo de texto", + "todoDialog.priorityPicker.label": "Prioridad", + "todoDialog.datePicker.threshold": "Fecha umbral", + "todoDialog.datePicker.due": "Fecha de vencimiento", + "todoDialog.recurrencePicker.label": "Recurrencia", + "todoDialog.recurrencePicker.every": "Cada", + "todoDialog.recurrencePicker.day": "día", + "todoDialog.recurrencePicker.businessDay": "día laborable", + "todoDialog.recurrencePicker.week": "semana", + "todoDialog.recurrencePicker.month": "mes", + "todoDialog.recurrencePicker.year": "año", + "todoDialog.recurrencePicker.strict": "estricto", + "prompt.delete.headline": "¿Eliminar tarea?", + "prompt.delete.text": "La tarea se eliminará permanentemente del archivo", + "prompt.archive.headline": "¿Archivar tareas completadas?", + "prompt.archive.text": "Esto moverá todas las tareas completadas a su archivo done especificado", + "prompt.archive.button": "Archivar" +} diff --git a/src/locales/fr.json b/src/locales/fr.json new file mode 100644 index 00000000..f069803d --- /dev/null +++ b/src/locales/fr.json @@ -0,0 +1,66 @@ +{ + "copy": "Copier", + "delete": "Supprimer", + "cancel": "Annuler", + "fileTabs.changeLocation": "Changer l'emplacement du fichier done.txt", + "fileTabs.revealFile": "Révéler dans le Finder", + "fileTabs.removeFileHeadline": "Supprimer le fichier de sleek?", + "fileTabs.removeFileText": "Il ne sera pas supprimé de votre disque dur.", + "fileTabs.removeFileLabel": "Supprimer", + "settings.headline": "Paramètres", + "settings.appendCreationDate": "Ajouter la date de création lors de la création de la tâche", + "settings.convertRelativeToAbsoluteDates": "Convertir les dates relatives en dates absolues", + "settings.notificationsAllowed": "Envoyer une notification lorsque la date d'échéance est aujourd'hui ou demain", + "settings.showFileTabs": "Afficher les onglets de fichier", + "settings.tray": "Réduire dans la zone de notification", + "settings.colorTheme": "Thème", + "settings.system": "Suivre le système", + "settings.light": "Clair", + "settings.dark": "Sombre", + "settings.language": "Langue", + "drawer.tabs.attributes": "Attributs", + "drawer.tabs.filters": "Filtres", + "drawer.tabs.sorting": "Tri", + "drawer.filters.showCompleted": "Tâches terminées", + "drawer.filters.showHidden": "Tâches masquées", + "drawer.filters.thresholdDateInTheFuture": "Date limite dans le futur", + "drawer.filters.dueDateInTheFuture": "Date d'échéance dans le futur", + "drawer.attributes.noAttributesAvailable": "Aucun attribut disponible pour le moment", + "drawer.sorting.fileSorting": "Classer les tâches comme elles apparaissent dans le fichier", + "shared.attributeMapping.t": "Date limite", + "shared.attributeMapping.due": "Date d'échéance", + "shared.attributeMapping.projects": "Projets", + "shared.attributeMapping.contexts": "Contextes", + "shared.attributeMapping.priority": "Priorité", + "shared.attributeMapping.rec": "Répétition", + "shared.attributeMapping.pm": "Pomodoros", + "shared.attributeMapping.created": "Date de création", + "shared.attributeMapping.completed": "Date de réalisation", + "splashscreen.noFiles.text": "Déposez votre fichier todo.txt ici ou utilisez les boutons", + "splashscreen.noFiles.open": "Ouvrir le fichier todo.txt", + "splashscreen.noFiles.create": "Créer un fichier todo.txt", + "splashscreen.noTodosAvailable.text": "Aucune tâche dans ce fichier", + "splashscreen.noTodosAvailable.create": "Créer une tâche", + "splashscreen.noTodosVisible.text": "Aucun résultat visible.", + "splashscreen.noTodosVisible.reset": "Réinitialiser les filtres et la recherche", + "search.visibleTodos": "Tâches visibles : ", + "todoDialog.footer.save": "Enregistrer", + "todoDialog.footer.cancel": "Annuler", + "todoDialog.snackbar.emptyInput": "Veuillez entrer quelque chose dans le champ de texte", + "todoDialog.priorityPicker.label": "Priorité", + "todoDialog.datePicker.threshold": "Date limite", + "todoDialog.datePicker.due": "Date d'échéance", + "todoDialog.recurrencePicker.label": "Répétition", + "todoDialog.recurrencePicker.every": "Tous les", + "todoDialog.recurrencePicker.day": "jour", + "todoDialog.recurrencePicker.businessDay": "jour ouvrable", + "todoDialog.recurrencePicker.week": "semaine", + "todoDialog.recurrencePicker.month": "mois", + "todoDialog.recurrencePicker.year": "an", + "todoDialog.recurrencePicker.strict": "strict", + "prompt.delete.headline": "Supprimer la tâche?", + "prompt.delete.text": "La tâche sera définitivement supprimée du fichier", + "prompt.archive.headline": "Archiver les tâches terminées?", + "prompt.archive.text": "Cela déplacera toutes les tâches terminées vers votre fichier done spécifié", + "prompt.archive.button": "Archiver" +} diff --git a/src/locales/hi.json b/src/locales/hi.json new file mode 100644 index 00000000..3dfd5970 --- /dev/null +++ b/src/locales/hi.json @@ -0,0 +1,66 @@ +{ + "copy": "कॉपी करें", + "delete": "हटाएं", + "cancel": "रद्द करें", + "fileTabs.changeLocation": "done.txt फ़ाइल का स्थान बदलें", + "fileTabs.revealFile": "फ़ाइल में देखें", + "fileTabs.removeFileHeadline": "sleek से फ़ाइल हटाएं?", + "fileTabs.removeFileText": "यह आपके हार्ड ड्राइव से हटाएं नहीं होगा।", + "fileTabs.removeFileLabel": "हटाएं", + "settings.headline": "सेटिंग्स", + "settings.appendCreationDate": "कार्य बनाते समय निर्माण तिथि जोड़ें", + "settings.convertRelativeToAbsoluteDates": "सांख्यिक तिथियों को पूर्ण तिथियों में परिवर्तित करें", + "settings.notificationsAllowed": "समापन तिथि आज या कल है तो सूचना भेजें", + "settings.showFileTabs": "फ़ाइल टैब दिखाएं", + "settings.tray": "सिस्टम ट्रे में मिनिमाइज़ करें", + "settings.colorTheme": "थीम", + "settings.system": "सिस्टम का पालन करें", + "settings.light": "रोशनी", + "settings.dark": "डार्क", + "settings.language": "भाषा", + "drawer.tabs.attributes": "गुण", + "drawer.tabs.filters": "फ़िल्टर", + "drawer.tabs.sorting": "क्रमबद्ध करना", + "drawer.filters.showCompleted": "पूर्ण किए गए कार्य", + "drawer.filters.showHidden": "छिपे कार्य", + "drawer.filters.thresholdDateInTheFuture": "भविष्य की सीमा तिथि", + "drawer.filters.dueDateInTheFuture": "भविष्य की समापन तिथि", + "drawer.attributes.noAttributesAvailable": "वर्तमान में कोई गुण उपलब्ध नहीं हैं", + "drawer.sorting.fileSorting": "वे दिखाई देते हैं जैसे कि वे फ़ाइल में हैं, कार्य क्रमबद्ध करें", + "shared.attributeMapping.t": "सीमा तिथि", + "shared.attributeMapping.due": "समापन तिथि", + "shared.attributeMapping.projects": "प्रोजेक्ट्स", + "shared.attributeMapping.contexts": "संदर्भ", + "shared.attributeMapping.priority": "प्राथमिकता", + "shared.attributeMapping.rec": "पुनरावृत्ति", + "shared.attributeMapping.pm": "पोमोडोरोस", + "shared.attributeMapping.created": "निर्माण तिथि", + "shared.attributeMapping.completed": "पूर्ण तिथि", + "splashscreen.noFiles.text": "अपनी todo.txt फ़ाइल यहाँ ड्रैग करें या बटन का उपयोग करें", + "splashscreen.noFiles.open": "todo.txt फ़ाइल खोलें", + "splashscreen.noFiles.create": "todo.txt फ़ाइल बनाएं", + "splashscreen.noTodosAvailable.text": "इस फ़ाइल में कोई कार्य नहीं हैं", + "splashscreen.noTodosAvailable.create": "कार्य बनाएं", + "splashscreen.noTodosVisible.text": "कोई दिखाई देने वाला परिणाम नहीं है।", + "splashscreen.noTodosVisible.reset": "फ़िल्टर और खोज को रीसेट करें", + "search.visibleTodos": "दिखाई देने वाले कार्य: ", + "todoDialog.footer.save": "सहेजें", + "todoDialog.footer.cancel": "रद्द करें", + "todoDialog.snackbar.emptyInput": "कृपया पाठ फ़ील्ड में कुछ डालें", + "todoDialog.priorityPicker.label": "प्राथमिकता", + "todoDialog.datePicker.threshold": "सीमा", + "todoDialog.datePicker.due": "देय", + "todoDialog.recurrencePicker.label": "पुनरावृत्ति", + "todoDialog.recurrencePicker.every": "हर", + "todoDialog.recurrencePicker.day": "दिन", + "todoDialog.recurrencePicker.businessDay": "व्यापारिक दिन", + "todoDialog.recurrencePicker.week": "सप्ताह", + "todoDialog.recurrencePicker.month": "महीना", + "todoDialog.recurrencePicker.year": "साल", + "todoDialog.recurrencePicker.strict": "कठोर", + "prompt.delete.headline": "कार्य हटाएं?", + "prompt.delete.text": "कार्य फ़ाइल से स्थायी रूप से हटा दिया जाएगा", + "prompt.archive.headline": "पूर्ण किए गए कार्यों को संग्रहित करें?", + "prompt.archive.text": "यह सभी पूर्ण किए गए कार्यों को आपके निर्दिष्ट संपन्न फ़ाइल में ले जाएगा", + "prompt.archive.button": "संग्रहित करें" +} diff --git a/src/locales/hu.json b/src/locales/hu.json new file mode 100644 index 00000000..a5f0bbc3 --- /dev/null +++ b/src/locales/hu.json @@ -0,0 +1,66 @@ +{ + "copy": "Másolás", + "delete": "Törlés", + "cancel": "Mégse", + "fileTabs.changeLocation": "Fájl done.txt helyének megváltoztatása", + "fileTabs.revealFile": "Megjelenítés a Finderben", + "fileTabs.removeFileHeadline": "Fájl eltávolítása a sleek?", + "fileTabs.removeFileText": "Nem kerül törlésre a merevlemezről.", + "fileTabs.removeFileLabel": "Eltávolítás", + "settings.headline": "Beállítások", + "settings.appendCreationDate": "Létrehozáskor hozzáadja a létrehozás dátumát", + "settings.convertRelativeToAbsoluteDates": "Relatív dátumok konvertálása abszolút dátumokká", + "settings.notificationsAllowed": "Értesítés küldése, ha a határidő ma vagy holnap van", + "settings.showFileTabs": "Fájl lapok megjelenítése", + "settings.tray": "Minimálás a tálcára", + "settings.colorTheme": "Téma", + "settings.system": "Kövesse a rendszert", + "settings.light": "Világos", + "settings.dark": "Sötét", + "settings.language": "Nyelv", + "drawer.tabs.attributes": "Tulajdonságok", + "drawer.tabs.filters": "Szűrők", + "drawer.tabs.sorting": "Rendezés", + "drawer.filters.showCompleted": "Befejezett feladatok", + "drawer.filters.showHidden": "Elrejtett feladatok", + "drawer.filters.thresholdDateInTheFuture": "Hátralévő dátum a jövőben", + "drawer.filters.dueDateInTheFuture": "Határidő a jövőben", + "drawer.attributes.noAttributesAvailable": "Nincs elérhető tulajdonság", + "drawer.sorting.fileSorting": "Feladatok rendezése, ahogy megjelennek a fájlban", + "shared.attributeMapping.t": "Hátralévő dátum", + "shared.attributeMapping.due": "Határidő", + "shared.attributeMapping.projects": "Projektek", + "shared.attributeMapping.contexts": "Környezetek", + "shared.attributeMapping.priority": "Prioritás", + "shared.attributeMapping.rec": "Ismétlés", + "shared.attributeMapping.pm": "Pomodorok", + "shared.attributeMapping.created": "Létrehozás dátuma", + "shared.attributeMapping.completed": "Befejezés dátuma", + "splashscreen.noFiles.text": "Húzza ide a todo.txt fájlt, vagy használja a gombokat", + "splashscreen.noFiles.open": "Todo.txt fájl megnyitása", + "splashscreen.noFiles.create": "Todo.txt fájl létrehozása", + "splashscreen.noTodosAvailable.text": "Nincsenek feladatok ebben a fájlban", + "splashscreen.noTodosAvailable.create": "Feladat létrehozása", + "splashscreen.noTodosVisible.text": "Nincs látható eredmény.", + "splashscreen.noTodosVisible.reset": "Szűrők és keresés visszaállítása", + "search.visibleTodos": "Látható feladatok: ", + "todoDialog.footer.save": "Mentés", + "todoDialog.footer.cancel": "Mégse", + "todoDialog.snackbar.emptyInput": "Kérjük, írjon be valamit a szövegmezőbe", + "todoDialog.priorityPicker.label": "Prioritás", + "todoDialog.datePicker.threshold": "Hátralévő dátum", + "todoDialog.datePicker.due": "Határidő", + "todoDialog.recurrencePicker.label": "Ismétlés", + "todoDialog.recurrencePicker.every": "Minden", + "todoDialog.recurrencePicker.day": "nap", + "todoDialog.recurrencePicker.businessDay": "munkanap", + "todoDialog.recurrencePicker.week": "hét", + "todoDialog.recurrencePicker.month": "hónap", + "todoDialog.recurrencePicker.year": "év", + "todoDialog.recurrencePicker.strict": "szigorú", + "prompt.delete.headline": "Feladat törlése?", + "prompt.delete.text": "A feladat véglegesen törlődik a fájlból", + "prompt.archive.headline": "Befejezett feladatok archiválása?", + "prompt.archive.text": "Ez az összes befejezett feladatot áthelyezi a meghatározott done fájlba", + "prompt.archive.button": "Archiválás" +} diff --git a/src/locales/it.json b/src/locales/it.json new file mode 100644 index 00000000..2487a852 --- /dev/null +++ b/src/locales/it.json @@ -0,0 +1,66 @@ +{ + "copy": "Copia", + "delete": "Elimina", + "cancel": "Annulla", + "fileTabs.changeLocation": "Cambia posizione del file done.txt", + "fileTabs.revealFile": "Mostra nel Finder", + "fileTabs.removeFileHeadline": "Rimuovi il file da sleek?", + "fileTabs.removeFileText": "Non verrà eliminato dal tuo disco rigido.", + "fileTabs.removeFileLabel": "Rimuovi", + "settings.headline": "Impostazioni", + "settings.appendCreationDate": "Aggiungi data di creazione quando viene creato il compito", + "settings.convertRelativeToAbsoluteDates": "Converti date relative in date assolute", + "settings.notificationsAllowed": "Invia notifica quando la data di scadenza è oggi o domani", + "settings.showFileTabs": "Mostra le schede dei file", + "settings.tray": "Minimizza nella barra delle applicazioni", + "settings.colorTheme": "Tema", + "settings.system": "Segui il sistema", + "settings.light": "Chiaro", + "settings.dark": "Scuro", + "settings.language": "Lingua", + "drawer.tabs.attributes": "Attributi", + "drawer.tabs.filters": "Filtri", + "drawer.tabs.sorting": "Ordinamento", + "drawer.filters.showCompleted": "Compiti completati", + "drawer.filters.showHidden": "Compiti nascosti", + "drawer.filters.thresholdDateInTheFuture": "Data di scadenza nel futuro", + "drawer.filters.dueDateInTheFuture": "Data di scadenza nel futuro", + "drawer.attributes.noAttributesAvailable": "Nessun attributo disponibile al momento", + "drawer.sorting.fileSorting": "Ordina i compiti come appaiono nel file", + "shared.attributeMapping.t": "Data di scadenza", + "shared.attributeMapping.due": "Data di scadenza", + "shared.attributeMapping.projects": "Progetti", + "shared.attributeMapping.contexts": "Contesti", + "shared.attributeMapping.priority": "Priorità", + "shared.attributeMapping.rec": "Ricorrenza", + "shared.attributeMapping.pm": "Pomodori", + "shared.attributeMapping.created": "Data di creazione", + "shared.attributeMapping.completed": "Data di completamento", + "splashscreen.noFiles.text": "Trascina il tuo file todo.txt qui o usa i pulsanti", + "splashscreen.noFiles.open": "Apri file todo.txt", + "splashscreen.noFiles.create": "Crea file todo.txt", + "splashscreen.noTodosAvailable.text": "Nessun compito in questo file", + "splashscreen.noTodosAvailable.create": "Crea un compito", + "splashscreen.noTodosVisible.text": "Nessun risultato visibile.", + "splashscreen.noTodosVisible.reset": "Reimposta filtri e ricerca", + "search.visibleTodos": "Compiti visibili: ", + "todoDialog.footer.save": "Salva", + "todoDialog.footer.cancel": "Annulla", + "todoDialog.snackbar.emptyInput": "Inserisci qualcosa nel campo di testo, per favore", + "todoDialog.priorityPicker.label": "Priorità", + "todoDialog.datePicker.threshold": "Data di scadenza", + "todoDialog.datePicker.due": "Data di scadenza", + "todoDialog.recurrencePicker.label": "Ricorrenza", + "todoDialog.recurrencePicker.every": "Ogni", + "todoDialog.recurrencePicker.day": "giorno", + "todoDialog.recurrencePicker.businessDay": "giorno lavorativo", + "todoDialog.recurrencePicker.week": "settimana", + "todoDialog.recurrencePicker.month": "mese", + "todoDialog.recurrencePicker.year": "anno", + "todoDialog.recurrencePicker.strict": "rigido", + "prompt.delete.headline": "Eliminare il compito?", + "prompt.delete.text": "Il compito verrà eliminato definitivamente dal file", + "prompt.archive.headline": "Archiviare i compiti completati?", + "prompt.archive.text": "Ciò sposterà tutti i compiti completati nel tuo file done specificato", + "prompt.archive.button": "Archivia" +} diff --git a/src/locales/jp.json b/src/locales/jp.json new file mode 100644 index 00000000..e255b4d7 --- /dev/null +++ b/src/locales/jp.json @@ -0,0 +1,66 @@ +{ + "copy": "コピー", + "delete": "削除", + "cancel": "キャンセル", + "fileTabs.changeLocation": "done.txt ファイルの場所を変更", + "fileTabs.revealFile": "Finder で表示", + "fileTabs.removeFileHeadline": "sleek からファイルを削除しますか?", + "fileTabs.removeFileText": "ハードディスクからは削除されません。", + "fileTabs.removeFileLabel": "削除", + "settings.headline": "設定", + "settings.appendCreationDate": "タスク作成時に作成日を追加", + "settings.convertRelativeToAbsoluteDates": "相対日付を絶対日付に変換", + "settings.notificationsAllowed": "期限が今日または明日の場合に通知を送信", + "settings.showFileTabs": "ファイルタブを表示", + "settings.tray": "トレイに最小化", + "settings.colorTheme": "テーマ", + "settings.system": "システムに従う", + "settings.light": "ライト", + "settings.dark": "ダーク", + "settings.language": "言語", + "drawer.tabs.attributes": "属性", + "drawer.tabs.filters": "フィルター", + "drawer.tabs.sorting": "ソート", + "drawer.filters.showCompleted": "完了したタスク", + "drawer.filters.showHidden": "非表示のタスク", + "drawer.filters.thresholdDateInTheFuture": "未来の期限日", + "drawer.filters.dueDateInTheFuture": "未来の期日", + "drawer.attributes.noAttributesAvailable": "現在利用可能な属性はありません", + "drawer.sorting.fileSorting": "ファイルに表示される順にタスクを並べ替え", + "shared.attributeMapping.t": "期限日", + "shared.attributeMapping.due": "期日", + "shared.attributeMapping.projects": "プロジェクト", + "shared.attributeMapping.contexts": "コンテキスト", + "shared.attributeMapping.priority": "優先度", + "shared.attributeMapping.rec": "繰り返し", + "shared.attributeMapping.pm": "ポモドーロ", + "shared.attributeMapping.created": "作成日", + "shared.attributeMapping.completed": "完了日", + "splashscreen.noFiles.text": "todo.txt ファイルをここにドラッグ&ドロップするか、ボタンを使用してください", + "splashscreen.noFiles.open": "todo.txt ファイルを開く", + "splashscreen.noFiles.create": "todo.txt ファイルを作成", + "splashscreen.noTodosAvailable.text": "このファイルにはタスクがありません", + "splashscreen.noTodosAvailable.create": "タスクを作成", + "splashscreen.noTodosVisible.text": "表示できる結果はありません。", + "splashscreen.noTodosVisible.reset": "フィルターと検索をリセット", + "search.visibleTodos": "表示されるタスク:", + "todoDialog.footer.save": "保存", + "todoDialog.footer.cancel": "キャンセル", + "todoDialog.snackbar.emptyInput": "テキストフィールドに何かを入力してください", + "todoDialog.priorityPicker.label": "優先度", + "todoDialog.datePicker.threshold": "期限日", + "todoDialog.datePicker.due": "期日", + "todoDialog.recurrencePicker.label": "繰り返し", + "todoDialog.recurrencePicker.every": "すべての", + "todoDialog.recurrencePicker.day": "日", + "todoDialog.recurrencePicker.businessDay": "営業日", + "todoDialog.recurrencePicker.week": "週", + "todoDialog.recurrencePicker.month": "月", + "todoDialog.recurrencePicker.year": "年", + "todoDialog.recurrencePicker.strict": "厳格", + "prompt.delete.headline": "タスクを削除しますか?", + "prompt.delete.text": "タスクはファイルから永久に削除されます", + "prompt.archive.headline": "完了したタスクをアーカイブしますか?", + "prompt.archive.text": "これにより、すべての完了したタスクが指定した done ファイルに移動されます", + "prompt.archive.button": "アーカイブ" +} diff --git a/src/locales/ko.json b/src/locales/ko.json new file mode 100644 index 00000000..ace1bac0 --- /dev/null +++ b/src/locales/ko.json @@ -0,0 +1,66 @@ +{ + "copy": "복사", + "delete": "삭제", + "cancel": "취소", + "fileTabs.changeLocation": "done.txt 파일 위치 변경", + "fileTabs.revealFile": "파인더에서 보기", + "fileTabs.removeFileHeadline": "sleek에서 파일 삭제?", + "fileTabs.removeFileText": "하드 드라이브에서는 삭제되지 않습니다.", + "fileTabs.removeFileLabel": "삭제", + "settings.headline": "설정", + "settings.appendCreationDate": "작업 생성 시 생성 날짜 추가", + "settings.convertRelativeToAbsoluteDates": "상대적 날짜를 절대 날짜로 변환", + "settings.notificationsAllowed": "마감일이 오늘이나 내일일 때 알림 보내기", + "settings.showFileTabs": "파일 탭 표시", + "settings.tray": "트레이로 최소화", + "settings.colorTheme": "테마", + "settings.system": "시스템 따르기", + "settings.light": "라이트", + "settings.dark": "다크", + "settings.language": "언어", + "drawer.tabs.attributes": "속성", + "drawer.tabs.filters": "필터", + "drawer.tabs.sorting": "정렬", + "drawer.filters.showCompleted": "완료된 작업", + "drawer.filters.showHidden": "숨겨진 작업", + "drawer.filters.thresholdDateInTheFuture": "미래 임계 날짜", + "drawer.filters.dueDateInTheFuture": "미래 마감일", + "drawer.attributes.noAttributesAvailable": "현재 사용 가능한 속성 없음", + "drawer.sorting.fileSorting": "파일에 나타나는 대로 작업 정렬", + "shared.attributeMapping.t": "임계 날짜", + "shared.attributeMapping.due": "마감일", + "shared.attributeMapping.projects": "프로젝트", + "shared.attributeMapping.contexts": "컨텍스트", + "shared.attributeMapping.priority": "우선 순위", + "shared.attributeMapping.rec": "반복", + "shared.attributeMapping.pm": "포모도로", + "shared.attributeMapping.created": "생성일", + "shared.attributeMapping.completed": "완료일", + "splashscreen.noFiles.text": "todo.txt 파일을 여기로 끌어오거나 버튼을 사용하세요", + "splashscreen.noFiles.open": "todo.txt 파일 열기", + "splashscreen.noFiles.create": "todo.txt 파일 생성", + "splashscreen.noTodosAvailable.text": "이 파일에 작업이 없습니다", + "splashscreen.noTodosAvailable.create": "작업 생성", + "splashscreen.noTodosVisible.text": "표시할 결과 없음.", + "splashscreen.noTodosVisible.reset": "필터 및 검색 재설정", + "search.visibleTodos": "표시된 작업: ", + "todoDialog.footer.save": "저장", + "todoDialog.footer.cancel": "취소", + "todoDialog.snackbar.emptyInput": "텍스트 필드에 내용을 입력하세요", + "todoDialog.priorityPicker.label": "우선 순위", + "todoDialog.datePicker.threshold": "임계", + "todoDialog.datePicker.due": "마감", + "todoDialog.recurrencePicker.label": "반복", + "todoDialog.recurrencePicker.every": "마다", + "todoDialog.recurrencePicker.day": "일", + "todoDialog.recurrencePicker.businessDay": "영업일", + "todoDialog.recurrencePicker.week": "주", + "todoDialog.recurrencePicker.month": "월", + "todoDialog.recurrencePicker.year": "년", + "todoDialog.recurrencePicker.strict": "엄격한", + "prompt.delete.headline": "작업 삭제?", + "prompt.delete.text": "작업은 파일에서 영구적으로 삭제됩니다", + "prompt.archive.headline": "완료된 작업 아카이브?", + "prompt.archive.text": "이 작업은 모든 완료된 작업을 지정한 완료 파일로 이동시킵니다", + "prompt.archive.button": "아카이브" +} diff --git a/src/locales/pl.json b/src/locales/pl.json new file mode 100644 index 00000000..0ea833b1 --- /dev/null +++ b/src/locales/pl.json @@ -0,0 +1,66 @@ +{ + "copy": "Kopiuj", + "delete": "Usuń", + "cancel": "Anuluj", + "fileTabs.changeLocation": "Zmień lokalizację pliku done.txt", + "fileTabs.revealFile": "Pokaż w Finderze", + "fileTabs.removeFileHeadline": "Usunąć plik z sleek?", + "fileTabs.removeFileText": "Nie zostanie usunięty z dysku twardego.", + "fileTabs.removeFileLabel": "Usuń", + "settings.headline": "Ustawienia", + "settings.appendCreationDate": "Dodaj datę utworzenia podczas tworzenia zadania", + "settings.convertRelativeToAbsoluteDates": "Konwertuj daty względne na daty bezwzględne", + "settings.notificationsAllowed": "Wysyłaj powiadomienia, gdy data wykonania jest dzisiaj lub jutro", + "settings.showFileTabs": "Pokaż karty plików", + "settings.tray": "Minimalizuj do zasobnika", + "settings.colorTheme": "Motyw kolorystyczny", + "settings.system": "Zastosuj motyw systemowy", + "settings.light": "Jasny", + "settings.dark": "Ciemny", + "settings.language": "Język", + "drawer.tabs.attributes": "Atrybuty", + "drawer.tabs.filters": "Filtry", + "drawer.tabs.sorting": "Sortowanie", + "drawer.filters.showCompleted": "Ukończone zadania", + "drawer.filters.showHidden": "Ukryte zadania", + "drawer.filters.thresholdDateInTheFuture": "Data progowa w przyszłości", + "drawer.filters.dueDateInTheFuture": "Termin w przyszłości", + "drawer.attributes.noAttributesAvailable": "Brak dostępnych atrybutów", + "drawer.sorting.fileSorting": "Sortuj zadania tak, jak występują w pliku", + "shared.attributeMapping.t": "Data progowa", + "shared.attributeMapping.due": "Termin", + "shared.attributeMapping.projects": "Projekty", + "shared.attributeMapping.contexts": "Konteksty", + "shared.attributeMapping.priority": "Priorytet", + "shared.attributeMapping.rec": "Powtarzalność", + "shared.attributeMapping.pm": "Pomodoros", + "shared.attributeMapping.created": "Data utworzenia", + "shared.attributeMapping.completed": "Data ukończenia", + "splashscreen.noFiles.text": "Przeciągnij tutaj plik todo.txt lub użyj przycisków", + "splashscreen.noFiles.open": "Otwórz plik todo.txt", + "splashscreen.noFiles.create": "Utwórz plik todo.txt", + "splashscreen.noTodosAvailable.text": "Brak zadań w tym pliku", + "splashscreen.noTodosAvailable.create": "Utwórz zadanie", + "splashscreen.noTodosVisible.text": "Brak widocznych wyników.", + "splashscreen.noTodosVisible.reset": "Resetuj filtry i wyszukiwanie", + "search.visibleTodos": "Widoczne zadania: ", + "todoDialog.footer.save": "Zapisz", + "todoDialog.footer.cancel": "Anuluj", + "todoDialog.snackbar.emptyInput": "Proszę wprowadzić coś do pola tekstowego", + "todoDialog.priorityPicker.label": "Priorytet", + "todoDialog.datePicker.threshold": "Data progowa", + "todoDialog.datePicker.due": "Termin", + "todoDialog.recurrencePicker.label": "Powtarzalność", + "todoDialog.recurrencePicker.every": "Co", + "todoDialog.recurrencePicker.day": "dzień", + "todoDialog.recurrencePicker.businessDay": "dzień roboczy", + "todoDialog.recurrencePicker.week": "tydzień", + "todoDialog.recurrencePicker.month": "miesiąc", + "todoDialog.recurrencePicker.year": "rok", + "todoDialog.recurrencePicker.strict": "ścisły", + "prompt.delete.headline": "Usunąć zadanie?", + "prompt.delete.text": "Zadanie zostanie trwale usunięte z pliku", + "prompt.archive.headline": "Zarchiwizować ukończone zadania?", + "prompt.archive.text": "To przeniesie wszystkie ukończone zadania do wskazanego pliku done", + "prompt.archive.button": "Archiwizuj" +} diff --git a/src/locales/pt.json b/src/locales/pt.json new file mode 100644 index 00000000..76bd33fe --- /dev/null +++ b/src/locales/pt.json @@ -0,0 +1,66 @@ +{ + "copy": "Copiar", + "delete": "Eliminar", + "cancel": "Cancelar", + "fileTabs.changeLocation": "Alterar localização do ficheiro done.txt", + "fileTabs.revealFile": "Revelar no Finder", + "fileTabs.removeFileHeadline": "Remover ficheiro do sleek?", + "fileTabs.removeFileText": "Não será eliminado do seu disco rígido.", + "fileTabs.removeFileLabel": "Remover", + "settings.headline": "Definições", + "settings.appendCreationDate": "Anexar data de criação quando a tarefa é criada", + "settings.convertRelativeToAbsoluteDates": "Converter datas relativas em datas absolutas", + "settings.notificationsAllowed": "Enviar notificação quando a data de vencimento é hoje ou amanhã", + "settings.showFileTabs": "Mostrar separadores de ficheiro", + "settings.tray": "Minimizar para a bandeja do sistema", + "settings.colorTheme": "Tema", + "settings.system": "Seguir o sistema", + "settings.light": "Claro", + "settings.dark": "Escuro", + "settings.language": "Idioma", + "drawer.tabs.attributes": "Atributos", + "drawer.tabs.filters": "Filtros", + "drawer.tabs.sorting": "Ordenação", + "drawer.filters.showCompleted": "Tarefas concluídas", + "drawer.filters.showHidden": "Tarefas ocultas", + "drawer.filters.thresholdDateInTheFuture": "Data de limite no futuro", + "drawer.filters.dueDateInTheFuture": "Data de vencimento no futuro", + "drawer.attributes.noAttributesAvailable": "Sem atributos disponíveis por agora", + "drawer.sorting.fileSorting": "Ordenar tarefas como aparecem no ficheiro", + "shared.attributeMapping.t": "Data de limite", + "shared.attributeMapping.due": "Data de vencimento", + "shared.attributeMapping.projects": "Projetos", + "shared.attributeMapping.contexts": "Contextos", + "shared.attributeMapping.priority": "Prioridade", + "shared.attributeMapping.rec": "Recorrência", + "shared.attributeMapping.pm": "Pomodoros", + "shared.attributeMapping.created": "Data de criação", + "shared.attributeMapping.completed": "Data de conclusão", + "splashscreen.noFiles.text": "Arraste o seu ficheiro todo.txt para aqui ou utilize os botões", + "splashscreen.noFiles.open": "Abrir ficheiro todo.txt", + "splashscreen.noFiles.create": "Criar ficheiro todo.txt", + "splashscreen.noTodosAvailable.text": "Sem tarefas neste ficheiro", + "splashscreen.noTodosAvailable.create": "Criar uma tarefa", + "splashscreen.noTodosVisible.text": "Sem resultados visíveis.", + "splashscreen.noTodosVisible.reset": "Repor filtros e pesquisa", + "search.visibleTodos": "Tarefas visíveis: ", + "todoDialog.footer.save": "Guardar", + "todoDialog.footer.cancel": "Cancelar", + "todoDialog.snackbar.emptyInput": "Por favor, insira algo no campo de texto", + "todoDialog.priorityPicker.label": "Prioridade", + "todoDialog.datePicker.threshold": "Data de limite", + "todoDialog.datePicker.due": "Data de vencimento", + "todoDialog.recurrencePicker.label": "Recorrência", + "todoDialog.recurrencePicker.every": "Cada", + "todoDialog.recurrencePicker.day": "dia", + "todoDialog.recurrencePicker.businessDay": "dia útil", + "todoDialog.recurrencePicker.week": "semana", + "todoDialog.recurrencePicker.month": "mês", + "todoDialog.recurrencePicker.year": "ano", + "todoDialog.recurrencePicker.strict": "estrito", + "prompt.delete.headline": "Eliminar tarefa?", + "prompt.delete.text": "A tarefa será eliminada permanentemente do ficheiro", + "prompt.archive.headline": "Arquivar tarefas concluídas?", + "prompt.archive.text": "Isto moverá todas as tarefas concluídas para o seu ficheiro de conclusões especificado", + "prompt.archive.button": "Arquivar" +} diff --git a/src/locales/ru.json b/src/locales/ru.json new file mode 100644 index 00000000..1ef90197 --- /dev/null +++ b/src/locales/ru.json @@ -0,0 +1,66 @@ +{ + "copy": "Копировать", + "delete": "Удалить", + "cancel": "Отменить", + "fileTabs.changeLocation": "Изменить местоположение файла done.txt", + "fileTabs.revealFile": "Показать в Finder", + "fileTabs.removeFileHeadline": "Удалить файл из sleek?", + "fileTabs.removeFileText": "Он не будет удален с вашего жесткого диска.", + "fileTabs.removeFileLabel": "Удалить", + "settings.headline": "Настройки", + "settings.appendCreationDate": "Добавить дату создания при создании задачи", + "settings.convertRelativeToAbsoluteDates": "Преобразовать относительные даты в абсолютные", + "settings.notificationsAllowed": "Отправлять уведомление, когда срок выполнения сегодня или завтра", + "settings.showFileTabs": "Показать вкладки файлов", + "settings.tray": "Свернуть в лоток", + "settings.colorTheme": "Тема", + "settings.system": "Следовать системе", + "settings.light": "Светлая", + "settings.dark": "Темная", + "settings.language": "Язык", + "drawer.tabs.attributes": "Атрибуты", + "drawer.tabs.filters": "Фильтры", + "drawer.tabs.sorting": "Сортировка", + "drawer.filters.showCompleted": "Завершенные задачи", + "drawer.filters.showHidden": "Скрытые задачи", + "drawer.filters.thresholdDateInTheFuture": "Дата порога в будущем", + "drawer.filters.dueDateInTheFuture": "Дата выполнения в будущем", + "drawer.attributes.noAttributesAvailable": "Нет доступных атрибутов", + "drawer.sorting.fileSorting": "Сортировать задачи в порядке их появления в файле", + "shared.attributeMapping.t": "Дата порога", + "shared.attributeMapping.due": "Дата выполнения", + "shared.attributeMapping.projects": "Проекты", + "shared.attributeMapping.contexts": "Контексты", + "shared.attributeMapping.priority": "Приоритет", + "shared.attributeMapping.rec": "Повтор", + "shared.attributeMapping.pm": "Помодоро", + "shared.attributeMapping.created": "Дата создания", + "shared.attributeMapping.completed": "Дата завершения", + "splashscreen.noFiles.text": "Перетащите ваш файл todo.txt сюда или используйте кнопки", + "splashscreen.noFiles.open": "Открыть файл todo.txt", + "splashscreen.noFiles.create": "Создать файл todo.txt", + "splashscreen.noTodosAvailable.text": "Нет задач в этом файле", + "splashscreen.noTodosAvailable.create": "Создать задачу", + "splashscreen.noTodosVisible.text": "Нет видимых результатов.", + "splashscreen.noTodosVisible.reset": "Сбросить фильтры и поиск", + "search.visibleTodos": "Видимые задачи: ", + "todoDialog.footer.save": "Сохранить", + "todoDialog.footer.cancel": "Отменить", + "todoDialog.snackbar.emptyInput": "Пожалуйста, введите что-то в текстовое поле", + "todoDialog.priorityPicker.label": "Приоритет", + "todoDialog.datePicker.threshold": "Дата порога", + "todoDialog.datePicker.due": "Дата выполнения", + "todoDialog.recurrencePicker.label": "Повтор", + "todoDialog.recurrencePicker.every": "Каждый", + "todoDialog.recurrencePicker.day": "день", + "todoDialog.recurrencePicker.businessDay": "рабочий день", + "todoDialog.recurrencePicker.week": "неделя", + "todoDialog.recurrencePicker.month": "месяц", + "todoDialog.recurrencePicker.year": "год", + "todoDialog.recurrencePicker.strict": "строгий", + "prompt.delete.headline": "Удалить задачу?", + "prompt.delete.text": "Задача будет удалена навсегда из файла", + "prompt.archive.headline": "Архивировать завершенные задачи?", + "prompt.archive.text": "Это переместит все завершенные задачи в указанный файл done", + "prompt.archive.button": "Архивировать" +} \ No newline at end of file diff --git a/src/locales/tr.json b/src/locales/tr.json new file mode 100644 index 00000000..7c32ab0d --- /dev/null +++ b/src/locales/tr.json @@ -0,0 +1,66 @@ +{ + "copy": "Kopyala", + "delete": "Sil", + "cancel": "İptal", + "fileTabs.changeLocation": "done.txt dosyasının konumunu değiştir", + "fileTabs.revealFile": "Finder'da Göster", + "fileTabs.removeFileHeadline": "Dosyayı sleek'ten kaldırmak istiyor musunuz?", + "fileTabs.removeFileText": "Sabit diskinizden silinmeyecektir.", + "fileTabs.removeFileLabel": "Kaldır", + "settings.headline": "Ayarlar", + "settings.appendCreationDate": "Görev oluşturulduğunda oluşturma tarihini ekle", + "settings.convertRelativeToAbsoluteDates": "İlgili tarihleri mutlak tarihlere dönüştür", + "settings.notificationsAllowed": "Bitiş tarihi bugün veya yarın ise bildirim gönder", + "settings.showFileTabs": "Dosya sekmesini göster", + "settings.tray": "Tepsiye Küçült", + "settings.colorTheme": "Tema", + "settings.system": "Sistemi Takip Et", + "settings.light": "Açık", + "settings.dark": "Koyu", + "settings.language": "Dil", + "drawer.tabs.attributes": "Özellikler", + "drawer.tabs.filters": "Filtreler", + "drawer.tabs.sorting": "Sıralama", + "drawer.filters.showCompleted": "Tamamlanan görevler", + "drawer.filters.showHidden": "Gizli görevler", + "drawer.filters.thresholdDateInTheFuture": "Gelecekteki eşik tarihi", + "drawer.filters.dueDateInTheFuture": "Gelecekteki bitiş tarihi", + "drawer.attributes.noAttributesAvailable": "Şu anda kullanılabilir özellik yok", + "drawer.sorting.fileSorting": "Görevleri dosyada göründükleri sıraya göre sırala", + "shared.attributeMapping.t": "Eşik tarihi", + "shared.attributeMapping.due": "Bitiş tarihi", + "shared.attributeMapping.projects": "Projeler", + "shared.attributeMapping.contexts": "Bağlamlar", + "shared.attributeMapping.priority": "Öncelik", + "shared.attributeMapping.rec": "Tekrarlama", + "shared.attributeMapping.pm": "Pomodorolar", + "shared.attributeMapping.created": "Oluşturma tarihi", + "shared.attributeMapping.completed": "Tamamlanma tarihi", + "splashscreen.noFiles.text": "Todo.txt dosyanızı buraya sürükleyin veya düğmeleri kullanın", + "splashscreen.noFiles.open": "Todo.txt dosyasını aç", + "splashscreen.noFiles.create": "Todo.txt dosyası oluştur", + "splashscreen.noTodosAvailable.text": "Bu dosyada görev yok", + "splashscreen.noTodosAvailable.create": "Görev oluştur", + "splashscreen.noTodosVisible.text": "Görünen sonuç yok.", + "splashscreen.noTodosVisible.reset": "Filtreleri ve aramayı sıfırla", + "search.visibleTodos": "Görünür görevler: ", + "todoDialog.footer.save": "Kaydet", + "todoDialog.footer.cancel": "İptal", + "todoDialog.snackbar.emptyInput": "Lütfen metin alanına bir şeyler yazın", + "todoDialog.priorityPicker.label": "Öncelik", + "todoDialog.datePicker.threshold": "Eşik", + "todoDialog.datePicker.due": "Bitiş", + "todoDialog.recurrencePicker.label": "Tekrarlama", + "todoDialog.recurrencePicker.every": "Her", + "todoDialog.recurrencePicker.day": "gün", + "todoDialog.recurrencePicker.businessDay": "iş günü", + "todoDialog.recurrencePicker.week": "hafta", + "todoDialog.recurrencePicker.month": "ay", + "todoDialog.recurrencePicker.year": "yıl", + "todoDialog.recurrencePicker.strict": "katı", + "prompt.delete.headline": "Görevi silmek istiyor musunuz?", + "prompt.delete.text": "Görev dosyadan kalıcı olarak silinecektir", + "prompt.archive.headline": "Tamamlanan görevleri arşivlemek istiyor musunuz?", + "prompt.archive.text": "Bu, tüm tamamlanan görevleri belirttiğiniz tamamlandı dosyasına taşıyacaktır", + "prompt.archive.button": "Arşivle" +} \ No newline at end of file diff --git a/src/locales/zh.json b/src/locales/zh.json new file mode 100644 index 00000000..a32316c3 --- /dev/null +++ b/src/locales/zh.json @@ -0,0 +1,66 @@ +{ + "copy": "复制", + "delete": "删除", + "cancel": "取消", + "fileTabs.changeLocation": "更改 done.txt 文件位置", + "fileTabs.revealFile": "在资源管理器中显示", + "fileTabs.removeFileHeadline": "从 sleek 中删除文件?", + "fileTabs.removeFileText": "它不会从您的硬盘驱动器中删除。", + "fileTabs.removeFileLabel": "删除", + "settings.headline": "设置", + "settings.appendCreationDate": "创建任务时附加创建日期", + "settings.convertRelativeToAbsoluteDates": "将相对日期转换为绝对日期", + "settings.notificationsAllowed": "当截止日期为今天或明天时发送通知", + "settings.showFileTabs": "显示文件选项卡", + "settings.tray": "最小化到系统托盘", + "settings.colorTheme": "主题", + "settings.system": "跟随系统", + "settings.light": "明亮", + "settings.dark": "暗黑", + "settings.language": "语言", + "drawer.tabs.attributes": "属性", + "drawer.tabs.filters": "过滤器", + "drawer.tabs.sorting": "排序", + "drawer.filters.showCompleted": "已完成任务", + "drawer.filters.showHidden": "隐藏任务", + "drawer.filters.thresholdDateInTheFuture": "未来的阈值日期", + "drawer.filters.dueDateInTheFuture": "未来的截止日期", + "drawer.attributes.noAttributesAvailable": "目前无可用属性", + "drawer.sorting.fileSorting": "按照它们在文件中出现的顺序排序任务", + "shared.attributeMapping.t": "阈值日期", + "shared.attributeMapping.due": "截止日期", + "shared.attributeMapping.projects": "项目", + "shared.attributeMapping.contexts": "上下文", + "shared.attributeMapping.priority": "优先级", + "shared.attributeMapping.rec": "重复", + "shared.attributeMapping.pm": "番茄工作法", + "shared.attributeMapping.created": "创建日期", + "shared.attributeMapping.completed": "完成日期", + "splashscreen.noFiles.text": "将您的 todo.txt 文件拖到此处或使用按钮", + "splashscreen.noFiles.open": "打开 todo.txt 文件", + "splashscreen.noFiles.create": "创建 todo.txt 文件", + "splashscreen.noTodosAvailable.text": "此文件中没有任务", + "splashscreen.noTodosAvailable.create": "创建任务", + "splashscreen.noTodosVisible.text": "没有可见的结果。", + "splashscreen.noTodosVisible.reset": "重置过滤器和搜索", + "search.visibleTodos": "可见任务:", + "todoDialog.footer.save": "保存", + "todoDialog.footer.cancel": "取消", + "todoDialog.snackbar.emptyInput": "请在文本字段中输入内容", + "todoDialog.priorityPicker.label": "优先级", + "todoDialog.datePicker.threshold": "阈值", + "todoDialog.datePicker.due": "截止", + "todoDialog.recurrencePicker.label": "重复", + "todoDialog.recurrencePicker.every": "每", + "todoDialog.recurrencePicker.day": "天", + "todoDialog.recurrencePicker.businessDay": "工作日", + "todoDialog.recurrencePicker.week": "周", + "todoDialog.recurrencePicker.month": "月", + "todoDialog.recurrencePicker.year": "年", + "todoDialog.recurrencePicker.strict": "严格", + "prompt.delete.headline": "删除任务?", + "prompt.delete.text": "任务将永久从文件中删除", + "prompt.archive.headline": "归档已完成的任务?", + "prompt.archive.text": "这将将所有已完成的任务移动到指定的已完成文件", + "prompt.archive.button": "归档" +} diff --git a/src/main/config.ts b/src/main/config.ts index c38ee77c..c2b0be63 100644 --- a/src/main/config.ts +++ b/src/main/config.ts @@ -89,7 +89,6 @@ filterStorage.onDidChange('filters' as never, async () => { configStorage.onDidChange('files', async (files: File[] | undefined) => { if (files) { - createMenu(files).then(result => { console.log('config.ts:', result); }).catch(error => { @@ -122,6 +121,10 @@ configStorage.onDidChange('dueDateInTheFuture', () => { handleConfigChange('dueDateInTheFuture', configStorage.get('dueDateInTheFuture')); }); +configStorage.onDidChange('showFileTabs', () => { + mainWindow!.webContents.send('setShowFileTabs'); +}); + configStorage.onDidChange('sorting', (sorting) => { handleConfigChange('sorting', sorting); }); diff --git a/src/main/main.ts b/src/main/main.ts index 8da3ebb1..2953b2ae 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -2,7 +2,7 @@ const isDebug = process.env.NODE_ENV === 'development' || process.env.DEBUG_PROD if (isDebug) { require('electron-debug')(); } -import { app, BrowserWindow, Rectangle, Menu } from 'electron'; +import { app, BrowserWindow, Rectangle, Menu, ipcMain, nativeTheme } from 'electron'; import path from 'path'; import fs from 'fs'; import { configStorage } from './config'; @@ -76,8 +76,6 @@ const handleWindowSizeAndPosition = () => { const windowDimensions: { width: number; height: number } | null = configStorage.get('windowDimensions') as { width: number; height: number } | null; - console.log(windowDimensions) - if (windowDimensions) { const { width, height } = windowDimensions; mainWindow.setSize(width, height); @@ -116,6 +114,9 @@ const createWindow = async() => { }); } + const colorTheme = configStorage.get('colorTheme'); + if(colorTheme) nativeTheme.themeSource = colorTheme; + handleWindowSizeAndPosition(); mainWindow.loadURL(resolveHtmlPath('index.html')); diff --git a/src/main/modules/File.ts b/src/main/modules/File.ts index cb08d7a5..e300caa4 100644 --- a/src/main/modules/File.ts +++ b/src/main/modules/File.ts @@ -14,8 +14,6 @@ async function addFile(event: Event | null, filePath: string): Promise { file.active = false; }); existingFileIndex = files.findIndex((file) => { - // const pathName = path.dirname(filePath); - // const fileName = path.basename(filePath) if(file.todoFilePath === filePath) return true; }); } diff --git a/src/main/modules/ProcessDataRequest.ts b/src/main/modules/ProcessDataRequest.ts index f6530eed..b67f3081 100644 --- a/src/main/modules/ProcessDataRequest.ts +++ b/src/main/modules/ProcessDataRequest.ts @@ -1,6 +1,7 @@ import fs from 'fs'; import path from 'path'; import { getActiveFile } from './ActiveFile'; +import { mainWindow } from '../main'; import { configStorage, filterStorage } from '../config'; import { applyFilters } from './Filters'; import { updateAttributes, attributes } from './Attributes'; diff --git a/src/main/preload.ts b/src/main/preload.ts index 24c05e70..793fc2c7 100644 --- a/src/main/preload.ts +++ b/src/main/preload.ts @@ -1,4 +1,4 @@ -import { contextBridge, ipcRenderer, IpcRendererEvent, clipboard } from 'electron'; +import { contextBridge, ipcRenderer } from 'electron'; export type Channels = | 'requestData' @@ -20,39 +20,24 @@ export type Channels = | 'revealFile' | 'changeDoneFilePath'; -interface ElectronStore { - get: (key: string) => T; - set: (property: string, val: unknown) => void; - setFilters: (val: unknown) => void; -} - -interface ElectronIpcRenderer { - send: (channel: Channels, ...args: unknown[]) => void; - on: (channel: Channels, func: (...args: unknown[]) => void) => () => void; - once: (channel: Channels, func: (...args: unknown[]) => void) => void; -} - -const electronHandler: { - store: ElectronStore; - ipcRenderer: ElectronIpcRenderer; -} = { +contextBridge.exposeInMainWorld('api', { store: { - get(key: string): T { + get(key: string): void { return ipcRenderer.sendSync('storeGetConfig', key); }, - set(property: string, val: unknown): void { - ipcRenderer.send('storeSetConfig', property, val); + set(property: string, value: unknown): void { + ipcRenderer.send('storeSetConfig', property, value); }, - setFilters(val: unknown): void { - ipcRenderer.send('storeSetFilters', val); + setFilters(value: unknown): void { + ipcRenderer.send('storeSetFilters', value); }, }, ipcRenderer: { - send(channel: Channels, ...args: unknown[]): void { + send(channel: Channels, ...args): void { ipcRenderer.send(channel, ...args); }, - on(channel: Channels, func: (...args: unknown[]) => void): () => void { - const subscription = (_event: IpcRendererEvent, ...args: unknown[]) => + on(channel: Channels, func: (...args) => void): () => void { + const subscription = (_event, ...args) => func(...args); ipcRenderer.on(channel, subscription); @@ -60,18 +45,16 @@ const electronHandler: { ipcRenderer.removeListener(channel, subscription); }; }, - once(channel: Channels, func: (...args: unknown[]) => void): void { + off(channel: Channels, func: (...args) => void): () => void { + const subscription = (_event, ...args) => + func(...args); + ipcRenderer.removeListener(channel, subscription); + }, + once(channel: Channels, func: (...args) => void): void { ipcRenderer.once( channel, - (_event: IpcRendererEvent, ...args: unknown[]) => func(...args) + (_event, ...args) => func(...args) ); }, - startDrag: (fileName) => { - ipcRenderer.send('ondragstart', path.join(process.cwd(), fileName)) - } - }, -}; - -contextBridge.exposeInMainWorld('electron', electronHandler); - -export type ElectronHandler = typeof electronHandler; + } +}); \ No newline at end of file diff --git a/src/main/tray.ts b/src/main/tray.ts index 02f781e1..dccebc0e 100644 --- a/src/main/tray.ts +++ b/src/main/tray.ts @@ -6,7 +6,7 @@ import { setFile } from './modules/File'; let tray; -function getMenuTemplate(files) { +function createMenuTemplate(files) { const menuTemplate = [ { label: 'Show sleek', @@ -49,7 +49,6 @@ function getMenuTemplate(files) { function createTray() { try { const isDark = nativeTheme.shouldUseDarkColors; - const isMac = process.platform === 'darwin'; const isWindows = process.platform === 'win32'; const isTray = configStorage.get('tray'); @@ -59,13 +58,13 @@ function createTray() { if (app.dock) { app.dock.show(); } - return Promise.resolve('Tray removed'); + return Promise.resolve('Tray not shown'); } const files = configStorage.get('files') as File[] || []; - const menu = Menu.buildFromTemplate(getMenuTemplate(files)); + const menu = Menu.buildFromTemplate(createMenuTemplate(files)); - tray = new Tray((isWindows) ? getAssetPath('icons/tray/lightTheme/tray.png') : getAssetPath('icons/tray/darkTheme/tray.png')); + tray = new Tray((isWindows && !isDark) ? getAssetPath('icons/tray/lightTheme/tray.png') : getAssetPath('icons/tray/darkTheme/tray.png')); tray.setContextMenu(menu); return Promise.resolve('Tray created'); diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index 6fe7cd76..fc7a4fe8 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -2,9 +2,9 @@ import React, { useEffect, useState, useRef } from 'react'; import { ThemeProvider } from '@mui/material/styles'; import { CssBaseline, Snackbar, Alert, Box } from '@mui/material'; import NavigationComponent from './Navigation'; -import TodoDataGrid from './DataGrid'; +import TodoDataGrid from './DataGrid/Grid'; import SplashScreen from './SplashScreen'; -import FileTabs from './FileTabs'; +import FileTabs from './FileTabs.tsx'; import { baseTheme, darkTheme, lightTheme } from './Themes'; import DrawerComponent from './Drawer'; import Search from './Search'; @@ -13,10 +13,11 @@ import ArchiveTodos from './ArchiveTodos'; import ToolBar from './ToolBar'; import ContextMenu from './ContextMenu'; import { Sorting } from '../main/util'; +import { I18nextProvider } from 'react-i18next'; +import { i18n } from './LanguageSelector'; import './App.scss'; -const ipcRenderer = window.electron.ipcRenderer; -const store = window.electron.store; +const { ipcRenderer, store } = window.api; const App = () => { const [files, setFiles] = useState(store.get('files') || null); @@ -165,129 +166,139 @@ const App = () => { window.addEventListener('dragover', handleDragOver); return () => { + ipcRenderer.off('requestData', handleRequestedData); + ipcRenderer.off('updateFiles', handleUpdateFiles); + ipcRenderer.off('updateSorting', handleUpdateSorting); + ipcRenderer.off('setIsSearchOpen', handleSetIsSearchOpen); + ipcRenderer.off('setIsNavigationOpen', handleSetIsNavigationOpen); + ipcRenderer.off('setShouldUseDarkColors', handleSetShouldUseDarkColors); + ipcRenderer.off('setShowFileTabs', handleSetShowFileTabs); + ipcRenderer.off('setIsDrawerOpen', handleSetIsDrawerOpen); + ipcRenderer.off('setIsSettingsOpen', handleSetIsSettingsOpen); + window.removeEventListener('drop', handleDrop); window.removeEventListener('dragover', handleDragOver); }; }, []); return ( - - - - - {files?.length > 0 && ( - <> - - - )} - + + + + + {files?.length > 0 && ( - <> - {!isSearchOpen && showFileTabs ? : null} - {headers?.availableObjects > 0 ? <> - - - : null } - )} - - + + {files?.length > 0 && ( + <> + {!isSearchOpen && showFileTabs ? : null} + {headers?.availableObjects > 0 ? + <> + + + + : null } + + )} + + + - - - - - + + - {snackBarContent} - - - - + + {snackBarContent} + + + + + ); }; diff --git a/src/renderer/ArchiveTodos.js b/src/renderer/ArchiveTodos.js deleted file mode 100644 index 9e6df34a..00000000 --- a/src/renderer/ArchiveTodos.js +++ /dev/null @@ -1,53 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import Prompt from './Prompt'; - -const ipcRenderer = window.electron.ipcRenderer; - -const ArchiveTodos = ({ - setSnackBarContent, - setSnackBarSeverity -}) => { - const [showPrompt, setShowPrompt] = useState(false); - const [promptIndex, setPromptIndex] = useState(null); - - const handleArchiveTodos = (response) => { - if (typeof response === 'string') { - setSnackBarSeverity('success'); - setSnackBarContent(response); - setShowPrompt(false); - } else if(response instanceof Error) { - setSnackBarSeverity('error'); - setSnackBarContent(response.message); - setShowPrompt(true); - } else { - setShowPrompt(true); - } - }; - - const handlePromptClose = () => { - setShowPrompt(false); - }; - - const handlePromptConfirm = () => { - ipcRenderer.send('archiveTodos'); - }; - - useEffect(() => { - ipcRenderer.on('archiveTodos', handleArchiveTodos); - }, []); - - return ( - <> - - - ); -}; - -export default ArchiveTodos; diff --git a/src/renderer/ArchiveTodos.tsx b/src/renderer/ArchiveTodos.tsx new file mode 100644 index 00000000..31c0b455 --- /dev/null +++ b/src/renderer/ArchiveTodos.tsx @@ -0,0 +1,61 @@ +import React, { useState, useEffect } from 'react'; +import { withTranslation } from 'react-i18next'; +import { i18n } from './LanguageSelector'; +import Prompt from './Prompt'; + +const ipcRenderer = window.api.ipcRenderer; + +interface ArchiveTodos { + setSnackBarContent: React.Dispatch>; + setSnackBarSeverity: React.Dispatch>; + t: any; +} + +const ArchiveTodos: React.FC = ({ setSnackBarContent, setSnackBarSeverity, t }) => { + const [showPrompt, setShowPrompt] = useState(false); + const [promptIndex, setPromptIndex] = useState(null); + + const handleArchiveTodos = (response: string | Error) => { + if (typeof response === 'string') { + setSnackBarSeverity('success'); + setSnackBarContent(response); + setShowPrompt(false); + } else if (response instanceof Error) { + setSnackBarSeverity('error'); + setSnackBarContent(response.message); + setShowPrompt(true); + } else { + setShowPrompt(true); + } + }; + + const handlePromptClose = () => { + setShowPrompt(false); + }; + + const handlePromptConfirm = () => { + ipcRenderer.send('archiveTodos'); + }; + + useEffect(() => { + ipcRenderer.on('archiveTodos', handleArchiveTodos); + return () => { + ipcRenderer.removeListener('archiveTodos', handleArchiveTodos); + }; + }, []); + + return ( + <> + + + ); +}; + +export default withTranslation()(ArchiveTodos); \ No newline at end of file diff --git a/src/renderer/AutoSuggest.js b/src/renderer/AutoSuggest.js index 07800942..1993d600 100644 --- a/src/renderer/AutoSuggest.js +++ b/src/renderer/AutoSuggest.js @@ -117,7 +117,7 @@ const AutoSuggest = ({ }; const inputProps = { - placeholder: `(A) Todo text +project @context due:2020-12-12 rec:d`, + placeholder: `(A) text +project @context due:2020-12-12 t:2021-01-10 rec:d pm:1`, value: textFieldValue, onChange: handleChange, inputRef: textFieldRef, diff --git a/src/renderer/AutoSuggest.scss b/src/renderer/AutoSuggest.scss index 72d932b1..ebac4114 100644 --- a/src/renderer/AutoSuggest.scss +++ b/src/renderer/AutoSuggest.scss @@ -10,8 +10,8 @@ padding: 1em; display: block; border-radius: $radius; - background-color: rgba(240, 240, 240, 0.5); - backdrop-filter: blur(5px); + background-color: rgba(240, 240, 240, 0.85); + ul { padding: 0; margin: 0; @@ -25,8 +25,7 @@ .darkTheme { .react-autosuggest__suggestions-container--open { - background-color: rgba(45, 45, 45, 0.5); - backdrop-filter: blur(5px); + background-color: rgba(45, 45, 45, 0.85); } } .react-autosuggest__container { diff --git a/src/renderer/Coloring.scss b/src/renderer/Coloring.scss index 1b4ccb08..48b0f7ed 100644 --- a/src/renderer/Coloring.scss +++ b/src/renderer/Coloring.scss @@ -3,9 +3,6 @@ *[data-todotxt-attribute] { --color1: #5a5a5a; --color2: #ccc; - span { - border-radius: $radius; - } .overlay { svg { color: var(--color1); @@ -90,8 +87,8 @@ } *[data-todotxt-attribute^="pm"] { - --color1: white; - --color2: #c00027; + --color1: #c00027; + --color2: #ccc; button { &.Mui-disabled { color: white; @@ -104,7 +101,8 @@ } &.selected { button { - background: darken(#c00027, 10%); + color: white; + background: c00027; } } } diff --git a/src/renderer/ContextMenu.js b/src/renderer/ContextMenu.js index ae388728..e6f3c02b 100644 --- a/src/renderer/ContextMenu.js +++ b/src/renderer/ContextMenu.js @@ -3,7 +3,7 @@ import { Menu, MenuItem, Button, Tooltip } from '@mui/material'; import FileOpenIcon from '@mui/icons-material/FileOpen'; import Prompt from './Prompt'; -const ipcRenderer = window.electron.ipcRenderer; +const ipcRenderer = window.api.ipcRenderer; const ContextMenu = ({ contextMenuPosition, diff --git a/src/renderer/DataGrid/Elements.tsx b/src/renderer/DataGrid/Elements.tsx new file mode 100644 index 00000000..6f210b1f --- /dev/null +++ b/src/renderer/DataGrid/Elements.tsx @@ -0,0 +1,133 @@ +import React from 'react'; +import { Box, Button, Chip } from '@mui/material'; +import { ReactComponent as TomatoIconDuo } from '../../../assets/icons/tomato-duo.svg'; +import DatePickerInline from '../DatePickerInline'; +import { TodoObject, Filters } from '../../../main/util'; + +interface Element { + type: string | null; + value: string; +} + +interface ElementsProps { + todoObject: TodoObject; + filters: Filters; + handleButtonClick: (key: string, value: string) => void; +} + +const Elements: React.FC = ({ todoObject, filters, handleButtonClick }) => { + + const replacements: { + [key: string]: (value: string, type: string) => React.ReactNode; + } = { + due: (value, type) => ( + + ), + t: (value, type) => ( + + ), + contexts: (value, type) => ( + + ), + projects: (value, type) => ( + + ), + rec: (value, type) => ( + + ), + pm: (value, type) => ( + + ), + hidden: () => null, + }; + + const matches = () => { + const expressions = [ + { pattern: new RegExp(`t:${todoObject.tString?.replace(/\s/g, '\\s')}`, 'g'), type: 't', key: 't:' }, + { pattern: new RegExp(`due:${todoObject.dueString?.replace(/\s/g, '\\s')}`, 'g'), type: 'due', key: 'due:' }, + { pattern: /(@\S+)/, type: 'contexts', key: '@' }, + { pattern: /\+\S+/, type: 'projects', key: '+' }, + { pattern: /\bh:1\b/, type: 'hidden', key: 'h:1' }, + { pattern: /pm:\d+\b/, type: 'pm', key: 'pm:' }, + { pattern: /rec:([^ ]+)/, type: 'rec', key: 'rec:' }, + ]; + + let body = todoObject.body; + let substrings = []; + let index = 0; + + if (body) { + while (body.length > 0) { + let matched = false; + + for (const expression of expressions) { + const regex = new RegExp(`^(${expression.pattern.source})`); + const match = body.match(regex); + + if (match) { + matched = true; + + const value = match[0].substr(expression.key.length); + + substrings.push({ type: expression.type, value: value, key: expression.key, index: index }); + body = body.substring(match[0].length); + break; + } + } + + if (!matched) { + const nextSpaceIndex = body.indexOf(' '); + const endOfWordIndex = nextSpaceIndex !== -1 ? nextSpaceIndex : body.length; + + substrings.push({ type: null, value: body.substring(0, endOfWordIndex), index: index }); + body = body.substring(endOfWordIndex + 1); + } + + index++; + } + } + return substrings; + }; + + const elements = matches().map((element, index) => { + const selected = (filters[element.type] || []).some( + (filter) => filter.value === element.value + ); + + return ( + + {replacements[element.type] + ? replacements[element.type](element.value, element.type) + : element.value ? {element.value} : null} + + ); + }); + + return <>{elements}; +}; + +export default Elements; diff --git a/src/renderer/DataGrid.js b/src/renderer/DataGrid/Grid.js similarity index 93% rename from src/renderer/DataGrid.js rename to src/renderer/DataGrid/Grid.js index 82480ff7..1b99a530 100644 --- a/src/renderer/DataGrid.js +++ b/src/renderer/DataGrid/Grid.js @@ -1,7 +1,7 @@ import React, { useState } from 'react'; import { List } from '@mui/material'; -import DataGridRow from './DataGridRow'; -import './DataGrid.scss'; +import Row from './Row'; +import '../DataGrid.scss'; const TodoDataGrid = ({ todoObjects, @@ -70,12 +70,12 @@ const TodoDataGrid = ({ if (!todoObjects || Object.keys(todoObjects).length === 0) return null; - const visibleRows = todoObjects.slice(0, visibleRowCount); + const rows = todoObjects.slice(0, visibleRowCount); return ( - {visibleRows.map((row, index) => ( - ( + void; +} + +const Group: React.FC = ({ todoObject, filters, onClick }) => { + const values = todoObject.value?.split(',') || []; + + return ( + + {values.map((value, index) => { + const selected = (filters[todoObject.group] || []).some( + (filter) => filter.value === value.trim() + ); + + if (!value) { + return ; + } + + return ( + + + + ); + })} + + ); +}; + +export default Group; diff --git a/src/renderer/DataGrid/Row.tsx b/src/renderer/DataGrid/Row.tsx new file mode 100644 index 00000000..4f1d7646 --- /dev/null +++ b/src/renderer/DataGrid/Row.tsx @@ -0,0 +1,137 @@ +import React from 'react'; +import { Checkbox, ListItem, Box } from '@mui/material'; +import CircleChecked from '@mui/icons-material/CheckCircle'; +import CircleUnchecked from '@mui/icons-material/RadioButtonUnchecked'; +import VisibilityOffIcon from '@mui/icons-material/VisibilityOff'; +import { withTranslation } from 'react-i18next'; +import { i18n } from './LanguageSelector'; +import Group from './Group'; +import Elements from './Elements'; +import { handleFilterSelect } from '../Shared'; +import '../DataGridRow.scss'; + +const ipcRenderer = window.api.ipcRenderer; + +interface Row { + todoObject: any; + filters: any; + setDialogOpen: React.Dispatch>; + setTextFieldValue: React.Dispatch>; + setTodoObject: React.Dispatch>; + setContextMenuPosition: React.Dispatch>; + setContextMenuItems: React.Dispatch>; + t: any; +} + +const Row: React.FC = ({ + todoObject, + filters, + setDialogOpen, + setTextFieldValue, + setTodoObject, + setContextMenuPosition, + setContextMenuItems, + t, +}) => { + + const handleContextMenu = (event: React.MouseEvent) => { + event.preventDefault(); + setContextMenuPosition({ top: event.clientY, left: event.clientX }); + setContextMenuItems([ + { + id: 'copy', + todoObject: todoObject, + label: t(`copy`), + }, + { + id: 'delete', + todoObject: todoObject, + headline: t(`prompt.delete.headline`), + text: t(`prompt.delete.text`), + label: t(`delete`), + }, + ]); + }; + + const handleCheckboxChange = (event: React.ChangeEvent) => { + ipcRenderer.send( + 'writeTodoToFile', + todoObject.id, + todoObject.string, + event.target.checked, + false + ); + }; + + const handleRowClick = (event: React.MouseEvent | React.KeyboardEvent) => { + const clickedElement = event.target as HTMLElement; + + if ( + (event.type === 'keydown' && event.key === 'Enter') || + event.type === 'click' + ) { + if ( + clickedElement.classList.contains('MuiChip-label') || + clickedElement.closest('.MuiChip-label') + ) { + return; + } + + if ( + clickedElement.tagName === 'SPAN' || + clickedElement.tagName === 'LI' + ) { + setDialogOpen(true); + setTodoObject(todoObject); + setTextFieldValue(todoObject.string); + } + } + }; + + const handleButtonClick = (key: string, value: string) => { + handleFilterSelect(key, value, filters, false); + }; + + if (todoObject.group) { + return ( + + ); + } + + return ( + + } + checkedIcon={} + tabIndex={0} + checked={todoObject.complete} + onChange={handleCheckboxChange} + /> + + {todoObject.hidden && } + + + + ); +}; + +export default withTranslation()(Row); \ No newline at end of file diff --git a/src/renderer/DataGridRow.js b/src/renderer/DataGridRow.js deleted file mode 100644 index bf521627..00000000 --- a/src/renderer/DataGridRow.js +++ /dev/null @@ -1,243 +0,0 @@ -import React, { useState } from 'react'; -import { Checkbox, ListItem, Button, Divider, Chip, Box } from '@mui/material'; -import CircleChecked from '@mui/icons-material/CheckCircle'; -import CircleUnchecked from '@mui/icons-material/RadioButtonUnchecked'; -import VisibilityOffIcon from '@mui/icons-material/VisibilityOff'; -import { handleFilterSelect } from './Shared'; -import DatePickerInline from './DatePickerInline'; -import { ReactComponent as TomatoIconDuo } from '../../assets/icons/tomato-duo.svg' -import './DataGridRow.scss'; - -const DataGridRow = React.memo(({ - todoObject, - filters, - setDialogOpen, - setTextFieldValue, - setTodoObject, - setContextMenuPosition, - setContextMenuItems -}) => { - const handleContextMenu = (event) => { - event.preventDefault(); - setContextMenuPosition({ top: event.clientY, left: event.clientX }); - setContextMenuItems([ - { - id: 'copy', - todoObject: todoObject, - label: 'Copy', - }, - { - id: 'delete', - todoObject: todoObject, - headline: 'Delete todo?', - text: 'The todo will be permanently removed from the file', - label: 'Delete', - }, - ]); - }; - - const handleCheckboxChange = (event) => { - const ipcRenderer = window.electron.ipcRenderer; - ipcRenderer.send('writeTodoToFile', todoObject.id, todoObject.string, event.target.checked, false); - }; - - const handleRowClick = (event) => { - const clickedElement = event.target; - - if ((event.type === 'keydown' && event.key === 'Enter') || event.type === 'click') { - if (clickedElement.classList.contains('MuiChip-label') || clickedElement.closest('.MuiChip-label')) { - return; - } - - if (clickedElement.tagName === 'SPAN' || clickedElement.tagName === 'LI') { - setDialogOpen(true); - setTodoObject(todoObject); - setTextFieldValue(todoObject.string); - } - } - }; - - const handleButtonClick = (key, value) => { - handleFilterSelect(key, value, filters, false); - }; - - const replacements = { - due: () => ( - - ), - t: () => ( - - ), - contexts: (value, type) => ( - - ), - projects: (value, type) => ( - - ), - rec: (value, type) => ( - - ), - pm: (value, type) => ( - - ), - hidden: () => null, - }; - - const matches = () => { - const expressions = [ - { pattern: new RegExp(`t:${todoObject.tString?.replace(/\s/g, '\\s')}`, 'g'), type: 't', key: 't:' }, - { pattern: new RegExp(`due:${todoObject.dueString?.replace(/\s/g, '\\s')}`, 'g'), type: 'due', key: 'due:' }, - { pattern: /(@\S+)/, type: 'contexts', key: '@' }, - { pattern: /\+\S+/, type: 'projects', key: '+' }, - { pattern: /\bh:1\b/, type: 'hidden', key: 'h:1' }, - { pattern: /pm:\d+\b/, type: 'pm', key: 'pm:' }, - { pattern: /rec:([^ ]+)/, type: 'rec', key: 'rec:' }, - ]; - - let body = todoObject.body; - let substrings = []; - let index = 0; - - if (body) { - while (body.length > 0) { - let matched = false; - - for (const expression of expressions) { - const regex = new RegExp(`^(${expression.pattern.source})`); - const match = body.match(regex); - - if (match) { - matched = true; - - const value = match[0].substr(expression.key.length); - - substrings.push({ type: expression.type, value: value, key: expression.key, index: index }); - body = body.substring(match[0].length); - break; - } - } - - if (!matched) { - const nextSpaceIndex = body.indexOf(' '); - const endOfWordIndex = nextSpaceIndex !== -1 ? nextSpaceIndex : body.length; - - substrings.push({ type: null, value: body.substring(0, endOfWordIndex), index: index }); - body = body.substring(endOfWordIndex + 1); - } - - index++; - } - } - return substrings; - }; - - const elements = matches().map((element, index) => { - const selected = (filters[element.type] || []).some((filter) => filter.value === element.value); - - const content = replacements[element.type] - ? replacements[element.type](element.value, element.type) - : element.value ? {element.value} : null; - - return ( - - {element.type !== null ? ( - - {content} - - ) : ( - content - )} - - ); - }); - - if (todoObject.group) { - const value = todoObject.value; - - if (!value) { - return ; - } - - const valuesArray = value.split(','); - - return ( - - {valuesArray.map((val, index) => { - const selected = (filters[todoObject.group] || []).some((filter) => filter.value === val.trim()); - - return ( - - - - ); - })} - - ); - } - - return ( - <> - - - } - checkedIcon={} - tabIndex={0} - checked={todoObject.complete} - onChange={handleCheckboxChange} - /> - - {todoObject.hidden && ( - - )} - - {elements} - - - - ); - } -); - -export default DataGridRow; diff --git a/src/renderer/DataGridRow.scss b/src/renderer/DataGridRow.scss index 5a051c1d..d372f7a7 100644 --- a/src/renderer/DataGridRow.scss +++ b/src/renderer/DataGridRow.scss @@ -1,4 +1,4 @@ -@import "Variables.scss"; +@import "./Variables.scss"; .row { width: auto; @@ -44,7 +44,7 @@ } } .MuiCheckbox-root { - padding: 0.35em 0.5em 0.35em 0.25em; + padding: 0.35em; } &[data-complete=true], &[data-hidden=true] { diff --git a/src/renderer/DatePicker.js b/src/renderer/DatePicker.tsx similarity index 67% rename from src/renderer/DatePicker.js rename to src/renderer/DatePicker.tsx index c4751ab8..329fb39f 100644 --- a/src/renderer/DatePicker.js +++ b/src/renderer/DatePicker.tsx @@ -1,4 +1,7 @@ import React, { useState, useEffect } from 'react'; +import { Item } from 'jstodotxt'; +import { withTranslation } from 'react-i18next'; +import { i18n } from './LanguageSelector'; import { DatePicker, LocalizationProvider } from '@mui/x-date-pickers'; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; import dayjs from 'dayjs'; @@ -14,22 +17,29 @@ import 'dayjs/locale/pt'; import 'dayjs/locale/ru'; import 'dayjs/locale/tr'; import 'dayjs/locale/zh'; -import { Item } from 'jstodotxt'; import './DatePicker.scss'; const userLocale = navigator.language || navigator.userLanguage; -const DatePickerComponent = ({ +interface DatePicker { + todoObject: Item | null; + type: string; + setTextFieldValue: (value: string) => void; + textFieldValue: string; +} + +const DatePickerComponent: React.FC = ({ todoObject, type, setTextFieldValue, - textFieldValue + textFieldValue, + t, }) => { const initialDate = todoObject && todoObject[type] && dayjs(todoObject[type]).isValid() ? dayjs(todoObject[type]) : null; - const [date, setDate] = useState(initialDate); + const [date, setDate] = useState(initialDate); - const handleChange = (updatedDate) => { - if (!dayjs(updatedDate).isValid()) return; + const handleChange = (updatedDate: dayjs.Dayjs | null) => { + if (!updatedDate || !dayjs(updatedDate).isValid()) return; const formattedDate = dayjs(updatedDate).format('YYYY-MM-DD'); const todoObjectCopy = new Item(textFieldValue); @@ -43,7 +53,7 @@ const DatePickerComponent = ({ handleChange(updatedDate)} /> @@ -51,4 +61,4 @@ const DatePickerComponent = ({ ); }; -export default DatePickerComponent; +export default withTranslation()(DatePickerComponent); \ No newline at end of file diff --git a/src/renderer/DatePickerInline.js b/src/renderer/DatePickerInline.js index 515c3fca..d0a60b24 100644 --- a/src/renderer/DatePickerInline.js +++ b/src/renderer/DatePickerInline.js @@ -19,7 +19,7 @@ import 'dayjs/locale/tr'; import 'dayjs/locale/zh'; const userLocale = navigator.language || navigator.userLanguage; -const ipcRenderer = window.electron.ipcRenderer; +const ipcRenderer = window.api.ipcRenderer; const DatePickerInline = ({ type, diff --git a/src/renderer/DraggableList.tsx b/src/renderer/DraggableList.tsx index 74488237..364cd25d 100644 --- a/src/renderer/DraggableList.tsx +++ b/src/renderer/DraggableList.tsx @@ -1,40 +1,51 @@ -import React from 'react'; +import React, { useState } from 'react'; import DraggableListItem from './DraggableListItem'; import { Box } from '@mui/material'; -import { DragDropContext, Droppable, OnDragEndResponder } from 'react-beautiful-dnd'; +import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd'; import './DraggableList.scss'; -const store = window.electron.store; +const store = window.api.store; -export type DraggableListProps = { - items: Item[]; - onDragEnd: OnDragEndResponder; - sorting: Sorting[]; // You might need to import or define the Sorting type - setSorting: (sorting: Sorting[]) => void; // You might need to define this function type - disabled?: boolean; -}; +interface Settings { + sorting: string[]; +} + +const DraggableList = React.memo(() => { + const [settings, setSettings] = useState({ + sorting: store.get('sorting'), + }); + + const reorder = (list, startIndex, endIndex) => { + const result = Array.from(list); + const [removed] = result.splice(startIndex, 1); + result.splice(endIndex, 0, removed); + return result; + }; + + const onDragEnd = ({ destination, source }) => { + if (!destination) return; + const updatedSorting = reorder(settings.sorting, source.index, destination.index); + store.set('sorting', updatedSorting); + setSettings((prevSettings) => ({ + ...prevSettings, + sorting: updatedSorting, + })); + }; -const DraggableList = React.memo(({ - sorting, - setSorting, - onDragEnd, - disabled -}: DraggableListProps) => { return ( - + - {provided => ( - - {!disabled && - sorting.map((sortingItem, index) => ( - - ))} + {(provided) => ( + + {settings.sorting.map((item, index) => ( + + ))} {provided.placeholder} )} @@ -44,4 +55,3 @@ const DraggableList = React.memo(({ }); export default DraggableList; - \ No newline at end of file diff --git a/src/renderer/DraggableListItem.tsx b/src/renderer/DraggableListItem.tsx index 2c636a0a..bf861d48 100644 --- a/src/renderer/DraggableListItem.tsx +++ b/src/renderer/DraggableListItem.tsx @@ -6,25 +6,28 @@ import DragHandleIcon from '@mui/icons-material/DragHandle'; import { attributeMapping } from './Shared'; import './DraggableListItem.scss'; -export type DraggableListItemProps = { +const store = window.api.store; + +type DraggableListItem = { item: Item; index: number; + settings: any; + setSettings: any; }; -const DraggableListItem = ({ - item, - index, - sorting, - setSorting -}: DraggableListItemProps) => { +const DraggableListItem: React.FC = ({ item, index, settings, setSettings }) => { const handleButtonClick = () => { - const updatedSorting = sorting.map(sortingItem => { + const updatedSorting = settings.sorting.map((sortingItem) => { if (sortingItem.id === item.id) { return { ...sortingItem, invert: !item.invert }; } return sortingItem; }); - setSorting(updatedSorting); + store.set('sorting', updatedSorting); + setSettings((prevSettings) => ({ + ...prevSettings, + sorting: updatedSorting, + })); }; return ( @@ -39,10 +42,9 @@ const DraggableListItem = ({ {attributeMapping[item.value]} - )} diff --git a/src/renderer/Drawer.js b/src/renderer/Drawer.js deleted file mode 100644 index 1d75fd9c..00000000 --- a/src/renderer/Drawer.js +++ /dev/null @@ -1,113 +0,0 @@ -import React, { useState, useRef, useEffect } from 'react'; -import { Drawer, Tabs, Tab, Box } from '@mui/material'; -import Attributes from './DrawerAttributes'; -import Sorting from './DrawerSorting'; -import Filters from './DrawerFilters'; -import FilterAltIcon from '@mui/icons-material/FilterAlt'; -import TuneIcon from '@mui/icons-material/Tune'; -import FilterListIcon from '@mui/icons-material/FilterList'; -import './Drawer.scss'; - -const store = window.electron.store; - -const DrawerComponent = ({ - isDrawerOpen, - setIsDrawerOpen, - attributes, - filters, - sorting, - setSorting -}) => { - const [activeTab, setActiveTab] = useState('attributes'); - const [drawerWidth, setDrawerWidth] = useState(store.get('drawerWidth') || 500); - const containerRef = useRef(null); - const startXRef = useRef(0); - - const handleTabChange = (event, newValue) => { - setActiveTab(newValue); - }; - - const handleMouseDown = (e) => { - startXRef.current = e.pageX; - document.addEventListener('mousemove', handleMouseMove); - document.addEventListener('mouseup', handleMouseUp); - }; - - const handleMouseMove = (e) => { - const deltaX = startXRef.current - e.pageX; - setDrawerWidth((prevWidth) => prevWidth - deltaX); - startXRef.current = e.pageX; - }; - - const handleMouseUp = () => { - document.removeEventListener('mousemove', handleMouseMove); - document.removeEventListener('mouseup', handleMouseUp); - }; - - useEffect(() => { - store.set('drawerWidth', drawerWidth) - }, [drawerWidth]); - - return ( - - - - } - /> - } - /> - } - /> - - {activeTab === 'attributes' && ( - - )} - {activeTab === 'filters' && ( - - )} - {activeTab === 'sorting' && ( - - )} - - ); -}; - -export default DrawerComponent; diff --git a/src/renderer/Drawer.tsx b/src/renderer/Drawer.tsx new file mode 100644 index 00000000..fcfd5a7b --- /dev/null +++ b/src/renderer/Drawer.tsx @@ -0,0 +1,107 @@ +import React, { useState, useRef, useEffect, KeyboardEvent } from 'react'; +import { Drawer, Tabs, Tab, Box } from '@mui/material'; +import Attributes from './DrawerAttributes'; +import Sorting from './DrawerSorting'; +import Filters from './DrawerFilters'; +import FilterAltIcon from '@mui/icons-material/FilterAlt'; +import TuneIcon from '@mui/icons-material/Tune'; +import FilterListIcon from '@mui/icons-material/FilterList'; +import { withTranslation } from 'react-i18next'; +import { i18n } from './LanguageSelector'; +import './Drawer.scss'; + +const { store } = window.api; + +interface DrawerComponent { + isDrawerOpen: boolean; + setIsDrawerOpen: React.Dispatch>; + attributes: { [key: string]: { [key: string]: number } }; + filters: { [key: string]: { value: string; exclude: boolean }[] }; + sorting: any; +} + +const DrawerComponent: React.FC = ({ + isDrawerOpen, + setIsDrawerOpen, + attributes, + filters, + sorting, + t, +}: DrawerComponentProps) => { + const [activeTab, setActiveTab] = useState('attributes'); + const [drawerWidth, setDrawerWidth] = useState(store.get('drawerWidth') || 500); + const containerRef = useRef(null); + const startXRef = useRef(0); + + const handleTabChange = (_event: React.ChangeEvent<{}>, newValue: string) => { + setActiveTab(newValue); + }; + + const handleMouseDown = (e: React.MouseEvent) => { + startXRef.current = e.pageX; + document.addEventListener('mousemove', handleMouseMove); + document.addEventListener('mouseup', handleMouseUp); + }; + + const handleMouseMove = (e: MouseEvent) => { + const deltaX = startXRef.current - e.pageX; + setDrawerWidth((prevWidth) => prevWidth - deltaX); + startXRef.current = e.pageX; + }; + + const handleMouseUp = () => { + document.removeEventListener('mousemove', handleMouseMove); + document.removeEventListener('mouseup', handleMouseUp); + }; + + const handleKeyDown = (event: KeyboardEvent) => { + if (event.key === 'Escape') { + setIsDrawerOpen(false); + } + }; + + useEffect(() => { + if (isDrawerOpen) { + document.addEventListener('keydown', handleKeyDown); + } else { + document.removeEventListener('keydown', handleKeyDown); + } + return () => { + document.removeEventListener('keydown', handleKeyDown); + }; + }, [isDrawerOpen]); + + useEffect(() => { + store.set('drawerWidth', drawerWidth); + }, [drawerWidth]); + + return ( + + + + } /> + } /> + } /> + + {isDrawerOpen && activeTab === 'attributes' && ( + + )} + {isDrawerOpen && activeTab === 'filters' && } + {isDrawerOpen && activeTab === 'sorting' && ( + + )} + + ); +}; + +export default withTranslation()(DrawerComponent); \ No newline at end of file diff --git a/src/renderer/DrawerAttributes.js b/src/renderer/DrawerAttributes.tsx similarity index 62% rename from src/renderer/DrawerAttributes.js rename to src/renderer/DrawerAttributes.tsx index 693f5fd0..c1c432a6 100644 --- a/src/renderer/DrawerAttributes.js +++ b/src/renderer/DrawerAttributes.tsx @@ -1,52 +1,72 @@ -import React, { useState, useEffect, useRef } from 'react'; -import { Accordion, AccordionSummary, AccordionDetails, Box, Button, Badge } from '@mui/material'; +import React, { useState, useEffect, useRef, KeyboardEvent } from 'react'; +import { + Accordion, + AccordionSummary, + AccordionDetails, + Box, + Button, + Badge, +} from '@mui/material'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import VisibilityOffIcon from '@mui/icons-material/VisibilityOff'; import AirIcon from '@mui/icons-material/Air'; +import { withTranslation } from 'react-i18next'; +import { i18n } from './LanguageSelector'; import { handleFilterSelect, attributeMapping } from './Shared'; import './DrawerAttributes.scss'; -const store = window.electron.store; +const store = window.api.store; -const Attributes = ({ - isDrawerOpen, - setIsDrawerOpen, - attributes, - filters -}) => { +interface Attribute { + [key: string]: number; +} + +interface Attributes { + attributes: { [key: string]: Attribute }; + filters: { [key: string]: { value: string; exclude: boolean }[] }; +} + +interface Settings { + accordionOpenState: boolean[]; +} +const Attributes: React.FC = ({ + attributes, + filters, + isDrawerOpen, + t, +}: AttributesProps) => { const [isCtrlKeyPressed, setIsCtrlKeyPressed] = useState(false); - const [accordionOpenState, setAccordionOpenState] = useState(store.get('accordionOpenState') || null); - const firstTabbableElementRef = useRef(null); - const isAttributesEmpty = (attributes) => { - return Object.values(attributes).every(attribute => !Object.keys(attribute).length); - } + const [settings, setSettings] = useState({ + accordionOpenState: store.get('accordionOpenState'), + isDrawerOpen: store.get('isDrawerOpen'), + }); + + const firstTabbableElementRef = useRef(null); + + const isAttributesEmpty = (attributes: { [key: string]: Attribute }) => { + return Object.values(attributes).every((attribute) => !Object.keys(attribute).length); + }; - const handleCtrlCmdDown = (event) => { + const handleCtrlCmdDown = (event: KeyboardEvent) => { if (event.ctrlKey || event.metaKey) { setIsCtrlKeyPressed(true); } }; - const handleCtrlCmdUp = (event) => { + const handleCtrlCmdUp = (event: KeyboardEvent) => { if (!event.ctrlKey && !event.metaKey) { setIsCtrlKeyPressed(false); } }; - const handleKeyDown = (event) => { - if (event.key === 'Escape') { - setIsDrawerOpen(false); - } - }; - - const handleAccordionToggle = (index) => { - setAccordionOpenState((prevState) => - prevState.map((prevState, prevIndex) => - prevIndex === index ? !prevState : prevState - ) - ); + const handleAccordionToggle = (index: number) => { + setSettings((prevState) => { + const updatedAccordionOpenState = [...prevState.accordionOpenState]; + updatedAccordionOpenState[index] = !updatedAccordionOpenState[index]; + return { ...prevState, accordionOpenState: updatedAccordionOpenState }; + }); }; useEffect(() => { @@ -56,34 +76,27 @@ const Attributes = ({ } }; - if (isDrawerOpen) { - document.addEventListener('keydown', handleCtrlCmdDown); - document.addEventListener('keyup', handleCtrlCmdUp); - document.addEventListener('keydown', handleKeyDown); - handleFocusFirstTabbableElement(); - } else { - document.removeEventListener('keydown', handleCtrlCmdDown); - document.removeEventListener('keyup', handleCtrlCmdUp); - document.removeEventListener('keydown', handleKeyDown); - } - + handleFocusFirstTabbableElement(); + + document.addEventListener('keydown', handleCtrlCmdDown); + document.addEventListener('keyup', handleCtrlCmdUp); return () => { document.removeEventListener('keydown', handleCtrlCmdDown); document.removeEventListener('keyup', handleCtrlCmdUp); - document.removeEventListener('keydown', handleKeyDown); }; - }, [isDrawerOpen]); + }, []); useEffect(() => { - store.set('accordionOpenState', accordionOpenState) - }, [accordionOpenState]); + store.set('accordionOpenState', settings.accordionOpenState); + }, [settings.accordionOpenState]); return ( {isAttributesEmpty(attributes) ? ( -
- No attributes available yet + +
+ {t(`drawer.attributes.noAttributesAvailable`)}
) : ( Object.keys(attributes).map((key, index) => @@ -91,7 +104,7 @@ const Attributes = ({ handleAccordionToggle(index)} > }> @@ -120,8 +133,9 @@ const Attributes = ({ className="attribute" onClick={ disabled - ? null - : () => handleFilterSelect(key, value, filters, isCtrlKeyPressed) + ? undefined + : () => + handleFilterSelect(key, value, filters, isCtrlKeyPressed) } disabled={disabled} > @@ -151,4 +165,4 @@ const Attributes = ({ ); }; -export default Attributes; +export default withTranslation()(Attributes); \ No newline at end of file diff --git a/src/renderer/DrawerFilters.js b/src/renderer/DrawerFilters.js deleted file mode 100644 index 3ba7dcc0..00000000 --- a/src/renderer/DrawerFilters.js +++ /dev/null @@ -1,58 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import { Box, FormGroup, FormControlLabel, Switch } from '@mui/material'; -import './DrawerFilters.scss'; - -const store = window.electron.store; - -const DrawerFilters = () => { - const [showCompleted, setHideCompleted] = useState(store.get('showCompleted')); - const [showHidden, setShowHidden] = useState(store.get('showHidden')); - const [thresholdDateInTheFuture, setThresholdDateInTheFuture] = useState(store.get('thresholdDateInTheFuture')); - const [dueDateInTheFuture, setDueDateInTheFuture] = useState(store.get('dueDateInTheFuture')); - - const handleSwitchChange = (event) => { - const { name, checked } = event.target; - store.set(name, checked); - switch (name) { - case 'showCompleted': - setHideCompleted(checked); - break; - case 'showHidden': - setShowHidden(checked); - break; - case 'thresholdDateInTheFuture': - setThresholdDateInTheFuture(checked); - break; - case 'dueDateInTheFuture': - setDueDateInTheFuture(checked); - break; - default: - break; - } - }; - - return ( - - - } - label="Completed todos" - /> - } - label="Hidden todos" - /> - } - label="Threshold date set in the future" - /> - } - label="Due date set in the future" - /> - - - ); -}; - -export default DrawerFilters; diff --git a/src/renderer/DrawerFilters.scss b/src/renderer/DrawerFilters.scss index bc0dd7ec..00957b72 100644 --- a/src/renderer/DrawerFilters.scss +++ b/src/renderer/DrawerFilters.scss @@ -1,6 +1,6 @@ @import "Variables.scss"; -.Filters { +#Filters { padding: 0 1em; margin: 0; overflow-y: scroll; diff --git a/src/renderer/DrawerFilters.tsx b/src/renderer/DrawerFilters.tsx new file mode 100644 index 00000000..3d3f9602 --- /dev/null +++ b/src/renderer/DrawerFilters.tsx @@ -0,0 +1,46 @@ +import React, { useState } from 'react'; +import { Box, FormGroup, FormControlLabel, Switch } from '@mui/material'; +import { withTranslation } from 'react-i18next'; +import LanguageSelector, { i18n } from './LanguageSelector'; +import { handleSettingChange } from './Shared'; +import './DrawerFilters.scss'; + +const store = window.api.store; + +interface Settings { + showCompleted: boolean; + showHidden: boolean; + thresholdDateInTheFuture: boolean; + dueDateInTheFuture: boolean; +} + +const DrawerFilters: React.FC = ({ t }) => { + const [settings, setSettings] = useState({ + showCompleted: store.get('showCompleted'), + showHidden: store.get('showHidden'), + thresholdDateInTheFuture: store.get('thresholdDateInTheFuture'), + dueDateInTheFuture: store.get('dueDateInTheFuture'), + }); + + return ( + + + {Object.entries(settings).map(([settingName, value]) => ( + + } + label={t(`drawer.filters.${settingName}`)} + /> + ))} + + + ); +}; + +export default withTranslation()(DrawerFilters); diff --git a/src/renderer/DrawerSorting.js b/src/renderer/DrawerSorting.js deleted file mode 100644 index 553f6901..00000000 --- a/src/renderer/DrawerSorting.js +++ /dev/null @@ -1,52 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import { Box, FormGroup, FormControlLabel, Switch, Divider } from '@mui/material'; -import DraggableList from './DraggableList'; - -const store = window.electron.store; - -const DrawerSorting = ({ - sorting, - setSorting -}) => { - const [fileSorting, setFileSorting] = useState(store.get('fileSorting')); - - const handleSwitchChange = (event) => { - const { name, checked } = event.target; - store.set(name, checked); - switch (name) { - case 'fileSorting': - setFileSorting(checked); - break; - default: - break; - } - }; - - const reorder = (sorting, startIndex, endIndex) => { - const result = Array.from(sorting); - const [removed] = result.splice(startIndex, 1); - result.splice(endIndex, 0, removed); - return result; - }; - - const onDragEnd = ({ destination, source }) => { - if (!destination) return; - const updatedSorting = reorder(sorting, source.index, destination.index); - setSorting(updatedSorting); - }; - - return ( - - - } - label="Use sorting order of file" - /> - - - - - ); -}; - -export default DrawerSorting; diff --git a/src/renderer/DrawerSorting.scss b/src/renderer/DrawerSorting.scss new file mode 100644 index 00000000..799973d0 --- /dev/null +++ b/src/renderer/DrawerSorting.scss @@ -0,0 +1,5 @@ +#Sorting { + padding: 0 1em; + margin: 0; + overflow-y: scroll; +} \ No newline at end of file diff --git a/src/renderer/DrawerSorting.tsx b/src/renderer/DrawerSorting.tsx new file mode 100644 index 00000000..f636750b --- /dev/null +++ b/src/renderer/DrawerSorting.tsx @@ -0,0 +1,43 @@ +import React, { useState } from 'react'; +import { Box, FormGroup, FormControlLabel, Switch, Divider } from '@mui/material'; +import { withTranslation } from 'react-i18next'; +import LanguageSelector, { i18n } from './LanguageSelector'; +import { handleSettingChange } from './Shared'; +import DraggableList from './DraggableList'; +import './DrawerSorting.scss'; + +const { store } = window.api; + +interface Settings { + fileSorting: boolean; +} + +const DrawerSorting: React.FC = ({ t }) => { + const [settings, setSettings] = useState({ + fileSorting: store.get('fileSorting'), + }); + + return ( + + + {Object.entries(settings).map(([settingName, value]) => ( + + } + label={t(`drawer.sorting.${settingName}`)} + /> + ))} + + + {!settings.fileSorting && } + + ); +}; + +export default withTranslation()(DrawerSorting); diff --git a/src/renderer/FileTabs.js b/src/renderer/FileTabs.js deleted file mode 100644 index 461016c0..00000000 --- a/src/renderer/FileTabs.js +++ /dev/null @@ -1,88 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import { Tab, Tabs } from '@mui/material'; -import CancelIcon from '@mui/icons-material/Cancel'; -import Prompt from './Prompt'; -import './FileTabs.scss'; - -const ipcRenderer = window.electron.ipcRenderer; - -const FileTabs = ({ - files, - setContextMenuPosition, - setContextMenuItems, -}) => { - - const handleContextMenu = (event, index) => { - event.preventDefault(); - setContextMenuPosition({ top: event.clientY, left: event.clientX }); - setContextMenuItems([ - { - id: 'changeDoneFilePath', - label: 'Change location of done.txt file', - index: index, - doneFilePath: files[index].doneFilePath, - }, - { - id: 'revealFile', - label: 'Reveal in finder', - index: index, - }, - { - id: 'removeFile', - headline: 'Remove file from sleek?', - text: 'It will not be deleted from your hard drive.', - label: 'Remove', - index: index, - }, - ]); - }; - - if (!files || Object.keys(files).length === 0) return null; - - const index = files.findIndex((file) => file.active); - const [fileTab, setFileTab] = useState(index !== -1 ? index : 0); - - const handleChange = (event, index) => { - if(index < 0 || index > 9) return false; - setFileTab(index); - ipcRenderer.send('setFile', index); - }; - - const handleRemove = (event, index) => { - event.stopPropagation(); - setPromptIndex(index); - setShowPrompt(true); - }; - - useEffect(() => { - setFileTab(index !== -1 ? index : 0); - }, [index]); - - return ( - <> - - {files.map((file, index) => ( - file && ( - handleContextMenu(event, index)} - icon={ - { - event.stopPropagation(); - handleContextMenu(event, index); - }} - />} - className={file.active ? 'active-tab' : ''} - value={index} - /> - ) - ))} - - - ); -}; - -export default FileTabs; diff --git a/src/renderer/FileTabs.scss b/src/renderer/FileTabs.scss index 4deb3813..2fbf17fd 100644 --- a/src/renderer/FileTabs.scss +++ b/src/renderer/FileTabs.scss @@ -1,13 +1,13 @@ @import "Variables.scss"; #fileTabs { - height: 2.5em; - min-height: 2.5em; + height: 3em; + min-height: 3em; .MuiTabs-scroller { button[role=tab] { min-width: 8.5em; min-height: auto; - padding: 0.75em 2em; + padding: 0.85em 2em; text-transform: lowercase; display: flex; flex-direction: row; diff --git a/src/renderer/FileTabs.tsx b/src/renderer/FileTabs.tsx new file mode 100644 index 00000000..04d8bfff --- /dev/null +++ b/src/renderer/FileTabs.tsx @@ -0,0 +1,98 @@ +import React, { useState, useEffect } from 'react'; +import { Tab, Tabs } from '@mui/material'; +import CancelIcon from '@mui/icons-material/Cancel'; +import Prompt from './Prompt'; +import { withTranslation } from 'react-i18next'; +import { File } from '../main/util'; +import './FileTabs.scss'; + +const ipcRenderer = window.api.ipcRenderer; + +interface FileTabs extends WithTranslation { + files: File[]; + setContextMenuPosition: (position: { top: number; left: number }) => void; + setContextMenuItems: (items: any[]) => void; +} + +const FileTabs: React.FC = ({ + files, + setContextMenuPosition, + setContextMenuItems, + t, +}) => { + const [promptIndex, setPromptIndex] = useState(null); + const [showPrompt, setShowPrompt] = useState(false); + + const handleContextMenu = (event: React.MouseEvent, index: number) => { + event.preventDefault(); + setContextMenuPosition({ top: event.clientY, left: event.clientX }); + setContextMenuItems([ + { + id: 'changeDoneFilePath', + label: t('fileTabs.changeLocation'), + index: index, + doneFilePath: files[index].doneFilePath, + }, + { + id: 'revealFile', + label: t('fileTabs.revealFile'), + index: index, + }, + { + id: 'removeFile', + headline: t('fileTabs.removeFileHeadline'), + text: t('fileTabs.removeFileText'), + label: t('fileTabs.removeFileLabel'), + index: index, + }, + ]); + }; + + if (!files || files.length === 0) return null; + + const index = files.findIndex((file) => file.active); + const [fileTab, setFileTab] = useState(index !== -1 ? index : 0); + + const handleChange = (event: React.ChangeEvent<{}>, newIndex: number) => { + if (newIndex < 0 || newIndex > 9) return false; + setFileTab(newIndex); + ipcRenderer.send('setFile', newIndex); + }; + + const handleRemove = (event: React.MouseEvent, index: number) => { + event.stopPropagation(); + setPromptIndex(index); + setShowPrompt(true); + }; + + useEffect(() => { + setFileTab(index !== -1 ? index : 0); + }, [index]); + + return ( + + {files.map((file, index) => ( + file ? ( + handleContextMenu(event, index)} + icon={ + { + event.stopPropagation(); + handleContextMenu(event, index); + }} + /> + } + className={file.active ? 'active-tab' : ''} + value={index} + /> + ) : null + ))} + + ); +}; + +export default withTranslation()(FileTabs); diff --git a/src/renderer/LanguageSelector.tsx b/src/renderer/LanguageSelector.tsx new file mode 100644 index 00000000..748861f4 --- /dev/null +++ b/src/renderer/LanguageSelector.tsx @@ -0,0 +1,116 @@ +import React, { useState, useEffect } from 'react'; +import { FormControl, InputLabel, MenuItem, Select } from '@mui/material'; +import i18n from 'i18next'; +import { initReactI18next } from 'react-i18next'; +import de from '../locales/de.json'; +import en from '../locales/en.json'; +import it from '../locales/it.json'; +import es from '../locales/es.json'; +import fr from '../locales/fr.json'; +import zh from '../locales/zh.json'; +import pt from '../locales/pt.json'; +import jp from '../locales/jp.json'; +import tr from '../locales/tr.json'; +import hu from '../locales/hu.json'; +import cs from '../locales/cs.json'; +import pl from '../locales/pl.json'; +import ru from '../locales/ru.json'; +import ko from '../locales/ko.json'; +import hi from '../locales/hi.json'; + +const { store } = window.api; + +const options = { + resources: { + de: { translation: de }, + en: { translation: en }, + it: { translation: it }, + es: { translation: es }, + fr: { translation: fr }, + zh: { translation: zh }, + pt: { translation: pt }, + jp: { translation: jp }, + tr: { translation: tr }, + hu: { translation: hu }, + cs: { translation: cs }, + pl: { translation: pl }, + ru: { translation: ru }, + ko: { translation: ko }, + hi: { translation: hi } + }, + lng: store.get('language') || navigator.language, + fallbackLng: 'en', + supportedLngs: ['de', 'en', 'it', 'es', 'fr', 'zh', 'pt', 'jp', 'tr', 'hu', 'cs', 'pl', 'ru', 'ko', 'hi'], + interpolation: { + escapeValue: false, + }, + saveMissing: true, +} + +i18n + .use(initReactI18next) + .init(options); + +i18n.on('missingKey', (language, ns, key, res) => { + console.warn(`Missing translation key: ${key}`); +}); + +const friendlyLanguageName = { + de: 'Deutsch', + en: 'English', + it: 'Italiano', + es: 'Español', + fr: 'Français', + zh: '简体中文', + pt: 'Português do Brasil', + jp: '日本語', + tr: 'Türkçe', + hu: 'Magyar', + cs: 'Čeština', + pl: 'Polski', + ru: 'Русский', + ko: '한국어', + hi: 'हिन्दी' +} + +const LanguageSelector = () => { + const [selectedLanguage, setSelectedLanguage] = useState(store.get('language') || i18n.language); + const supportedLanguages = i18n.options.supportedLngs; + + const changeLanguage = (event) => { + const language = event.target.value; + setSelectedLanguage(language); + }; + + useEffect(() => { + i18n.changeLanguage(selectedLanguage, (error) => { + if (error) return console.error('Error loading translation:', error); + store.set('language', selectedLanguage) + }); + }, [selectedLanguage]); + + return ( + + {i18n.t('settings.language')} + + + ); +}; + +export default LanguageSelector; +export { i18n }; \ No newline at end of file diff --git a/src/renderer/Navigation.js b/src/renderer/Navigation.js index fb1ef70f..607bc856 100644 --- a/src/renderer/Navigation.js +++ b/src/renderer/Navigation.js @@ -9,7 +9,7 @@ import { Button, Box } from '@mui/material'; import Settings from './Settings'; import './Navigation.scss'; -const ipcRenderer = window.electron.ipcRenderer; +const ipcRenderer = window.api.ipcRenderer; const NavigationComponent = ({ isSettingsOpen, @@ -20,9 +20,7 @@ const NavigationComponent = ({ files, setIsNavigationOpen, colorTheme, - setColorTheme, showFileTabs, - setShowFileTabs }) => { const openSettings = () => { setIsSettingsOpen(true); @@ -81,9 +79,7 @@ const NavigationComponent = ({ isOpen={isSettingsOpen} onClose={closeSettings} colorTheme={colorTheme} - setColorTheme={setColorTheme} showFileTabs={showFileTabs} - setShowFileTabs={setShowFileTabs} />
@@ -49,28 +55,30 @@ const SplashScreen = ({ {screen === 'noTodosAvailable' && ( <> -

Currently no todos in this file

+

{t('splashscreen.noTodosAvailable.text')}

- )} + )} {screen === 'noFiles' && ( -

Drop your todo.txt file here or use the buttons

+

{t('splashscreen.noFiles.text')}

- + - +
)} ); }; -export default SplashScreen; +export default withTranslation()(SplashScreen); diff --git a/src/renderer/Themes.js b/src/renderer/Themes.js index 3f84849d..c59ccb48 100644 --- a/src/renderer/Themes.js +++ b/src/renderer/Themes.js @@ -7,7 +7,7 @@ const baseTheme = createTheme({ typography: { fontFamily: 'FreeSans, sans-serif', }, - components: { + components: { MuiFormLabel: { styleOverrides: { root: { @@ -29,7 +29,7 @@ const baseTheme = createTheme({ styleOverrides: { indicator: { backgroundColor: '#1976d2', - height: '2px', + height: '0.2em', }, }, }, @@ -45,6 +45,9 @@ const baseTheme = createTheme({ '& .MuiSwitch-switchBase.Mui-checked + .MuiSwitch-track': { backgroundColor: '#1976d2', }, + '& .MuiSwitch-switchBase.Mui-focusVisible': { + background: 'rgb(25, 118, 210, 0.2)', + }, }, }, }, diff --git a/src/renderer/TodoDialog.js b/src/renderer/TodoDialog.tsx similarity index 62% rename from src/renderer/TodoDialog.js rename to src/renderer/TodoDialog.tsx index 4c0673c3..b9818cb1 100644 --- a/src/renderer/TodoDialog.js +++ b/src/renderer/TodoDialog.tsx @@ -5,11 +5,25 @@ import PriorityPicker from './PriorityPicker'; import DatePicker from './DatePicker'; import PomodoroPicker from './PomodoroPicker'; import RecurrencePicker from './RecurrencePicker'; +import { withTranslation } from 'react-i18next'; +import { i18n } from './LanguageSelector'; import './TodoDialog.scss'; -const ipcRenderer = window.electron.ipcRenderer; +const ipcRenderer = window.api.ipcRenderer; -const TodoDialog = ({ +interface TodoDialogProps { + dialogOpen: boolean; + setDialogOpen: React.Dispatch>; + todoObject: any; + attributes: any; + setSnackBarSeverity: React.Dispatch>; + setSnackBarContent: React.Dispatch>; + textFieldValue: string; + setTextFieldValue: React.Dispatch>; + shouldUseDarkColors: boolean; +} + +const TodoDialog: React.FC = ({ dialogOpen, setDialogOpen, todoObject, @@ -18,39 +32,47 @@ const TodoDialog = ({ setSnackBarContent, textFieldValue, setTextFieldValue, - shouldUseDarkColors -}) => { - - const textFieldRef = useRef(null); - + shouldUseDarkColors, + t +}: TodoDialogProps) => { + const textFieldRef = useRef(null); + const handleAdd = () => { try { - if (textFieldRef.current.value === '') { + if (textFieldRef.current?.value === '') { setSnackBarSeverity('info'); - setSnackBarContent('Please enter something into the text field'); + setSnackBarContent(t('todoDialog.snackbar.emptyInput')); return false; } - ipcRenderer.send('writeTodoToFile', todoObject?.id, textFieldRef.current.value); + ipcRenderer.send('writeTodoToFile', todoObject?.id, textFieldRef.current?.value); } catch (error) { console.error(error.message); } }; - const handleWriteTodoToFile = function(response) { + const handleWriteTodoToFile = (response: any) => { if (response instanceof Error) { setSnackBarSeverity('error'); setSnackBarContent(response.message); } else { setDialogOpen(false); } - } + }; useEffect(() => { ipcRenderer.on('writeTodoToFile', handleWriteTodoToFile); + return () => { + ipcRenderer.removeListener('writeTodoToFile', handleWriteTodoToFile); + }; }, []); return ( - setDialogOpen(false)} id='todoDialog' className={shouldUseDarkColors ? 'darkTheme' : 'lightTheme'}> + setDialogOpen(false)} + id="todoDialog" + className={shouldUseDarkColors ? 'darkTheme' : 'lightTheme'} + > - - + + ); }; -export default TodoDialog; +export default withTranslation()(TodoDialog); \ No newline at end of file diff --git a/src/renderer/ToolBar.js b/src/renderer/ToolBar.js index 563dcd9e..74414f65 100644 --- a/src/renderer/ToolBar.js +++ b/src/renderer/ToolBar.js @@ -28,8 +28,8 @@ const ToolBar = ({ }, [isSearchOpen]); return ( - - + + ); }; diff --git a/src/renderer/ToolBar.scss b/src/renderer/ToolBar.scss index 08780005..f3398257 100644 --- a/src/renderer/ToolBar.scss +++ b/src/renderer/ToolBar.scss @@ -1,11 +1,11 @@ @import "Variables.scss"; #ToolBar { - width: 2.5em; - height: 2.5em; + width: 3em; + height: 3em; position: fixed; top: 0; - right: 0; + right: 1em; z-index: 9; display: flex; justify-content: center; @@ -15,11 +15,13 @@ cursor: pointer; font-size: 1.85em; color: $dark-grey; - >:hover, &.active { color: $links; } } + & svg:hover { + color: $links; + } } .darkTheme {