mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-16 19:57:52 +01:00
web: localize
This commit is contained in:
committed by
Abdullah Atta
parent
d28d34fad1
commit
1fff0f6a1c
3027
apps/desktop/package-lock.json
generated
3027
apps/desktop/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
212
apps/mobile/package-lock.json
generated
212
apps/mobile/package-lock.json
generated
@@ -8108,7 +8108,7 @@
|
||||
},
|
||||
"../../packages/editor-mobile/node_modules/@types/prop-types": {
|
||||
"version": "15.7.11",
|
||||
"devOptional": true,
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"../../packages/editor-mobile/node_modules/@types/q": {
|
||||
@@ -8128,7 +8128,7 @@
|
||||
},
|
||||
"../../packages/editor-mobile/node_modules/@types/react": {
|
||||
"version": "18.2.39",
|
||||
"devOptional": true,
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/prop-types": "*",
|
||||
@@ -8159,7 +8159,7 @@
|
||||
},
|
||||
"../../packages/editor-mobile/node_modules/@types/scheduler": {
|
||||
"version": "0.16.8",
|
||||
"devOptional": true,
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"../../packages/editor-mobile/node_modules/@types/semver": {
|
||||
@@ -13006,7 +13006,7 @@
|
||||
},
|
||||
"../../packages/editor-mobile/node_modules/immer": {
|
||||
"version": "9.0.21",
|
||||
"devOptional": true,
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
@@ -23518,6 +23518,7 @@
|
||||
},
|
||||
"../../packages/editor/node_modules/js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"../../packages/editor/node_modules/jsesc": {
|
||||
@@ -23568,6 +23569,7 @@
|
||||
},
|
||||
"../../packages/editor/node_modules/loose-envify": {
|
||||
"version": "1.4.0",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"js-tokens": "^3.0.0 || ^4.0.0"
|
||||
@@ -24758,6 +24760,7 @@
|
||||
}
|
||||
},
|
||||
"../../packages/intl": {
|
||||
"name": "@notesnook/intl",
|
||||
"version": "1.0.0",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.24.9",
|
||||
@@ -30129,21 +30132,6 @@
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": {
|
||||
"version": "7.25.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.3.tgz",
|
||||
"integrity": "sha512-wUrcsxZg6rqBXG05HG1FPYgsP6EvwF4WpBbxIpWIIYnH8wG0gzx3yZY3dtEHas4sTAOGkbTsc9EGPxwff8lRoA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.22.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": {
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.24.7.tgz",
|
||||
@@ -30771,22 +30759,6 @@
|
||||
"@babel/core": "^7.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": {
|
||||
"version": "7.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.0.tgz",
|
||||
"integrity": "sha512-YLpb4LlYSc3sCUa35un84poXoraOiQucUTTu8X1j18JV+gNa8E0nyUf/CjZ171IRGr4jEguF+vzJU66QZhn29g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.22.5",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.8.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-exponentiation-operator": {
|
||||
"version": "7.22.5",
|
||||
"dev": true,
|
||||
@@ -34820,13 +34792,6 @@
|
||||
"version": "20.3.1",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node-forge": {
|
||||
"version": "1.3.11",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node-forge": {
|
||||
"version": "1.3.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz",
|
||||
@@ -35448,15 +35413,6 @@
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/acorn-import-attributes": {
|
||||
"version": "1.9.5",
|
||||
"resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz",
|
||||
"integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==",
|
||||
"dev": true,
|
||||
"peerDependencies": {
|
||||
"acorn": "^8"
|
||||
}
|
||||
},
|
||||
"node_modules/acorn-jsx": {
|
||||
"version": "5.3.2",
|
||||
"dev": true,
|
||||
@@ -37445,57 +37401,12 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/es-module-lexer": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-iterator-helpers": {
|
||||
"version": "1.0.19",
|
||||
"resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.19.tgz",
|
||||
"integrity": "sha512-zoMwbCcH5hwUkKJkT8kDIBZSz9I6mVG//+lDCinLCGov4+r7NIy0ld8o03M0cJxl2spVf6ESYVS6/gpIfq1FFw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"call-bind": "^1.0.7",
|
||||
"define-properties": "^1.2.1",
|
||||
"es-abstract": "^1.23.3",
|
||||
"es-errors": "^1.3.0",
|
||||
"es-set-tostringtag": "^2.0.3",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-intrinsic": "^1.2.4",
|
||||
"globalthis": "^1.0.3",
|
||||
"has-property-descriptors": "^1.0.2",
|
||||
"has-proto": "^1.0.3",
|
||||
"has-symbols": "^1.0.3",
|
||||
"internal-slot": "^1.0.7",
|
||||
"iterator.prototype": "^1.1.2",
|
||||
"safe-array-concat": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-module-lexer": {
|
||||
"version": "1.5.4",
|
||||
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz",
|
||||
"integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/es-object-atoms": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz",
|
||||
"integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-set-tostringtag": {
|
||||
"version": "2.0.1",
|
||||
"dev": true,
|
||||
@@ -39046,16 +38957,6 @@
|
||||
"entities": "^4.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/htmlparser2/node_modules/entities": {
|
||||
"version": "4.5.0",
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/htmlparser2/node_modules/entities": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
|
||||
@@ -39372,55 +39273,6 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/is-glob": {
|
||||
"version": "4.0.3",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-extglob": "^2.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-interactive": {
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/is-negative-zero": {
|
||||
"version": "2.0.2",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/is-number": {
|
||||
"version": "7.0.0",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-number-object": {
|
||||
"version": "1.0.7",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"has-tostringtag": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/is-glob": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
|
||||
@@ -39440,18 +39292,6 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/is-map": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz",
|
||||
"integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/is-negative-zero": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz",
|
||||
@@ -40820,14 +40660,6 @@
|
||||
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/json-schema-ref-resolver": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-ref-resolver/-/json-schema-ref-resolver-1.0.1.tgz",
|
||||
"integrity": "sha512-EJAj1pgHc1hxF6vo2Z3s69fMjO1INq6eGHXZ8Z6wCQeldCuwxGK9Sxf4/cScGn3FZubCVUehfWtcDM/PLteCQw==",
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/json-schema-ref-resolver": {
|
||||
"version": "1.0.1",
|
||||
"license": "MIT",
|
||||
@@ -41244,25 +41076,6 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/listr-verbose-renderer/node_modules/mimic-fn": {
|
||||
"version": "1.2.0",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/listr-verbose-renderer/node_modules/onetime": {
|
||||
"version": "2.0.1",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mimic-fn": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/listr-verbose-renderer/node_modules/restore-cursor": {
|
||||
"version": "2.0.0",
|
||||
"dev": true,
|
||||
@@ -41359,16 +41172,6 @@
|
||||
"version": "4.0.8",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.includes": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
|
||||
"integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="
|
||||
},
|
||||
"node_modules/lodash.isboolean": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
|
||||
"integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="
|
||||
},
|
||||
"node_modules/lodash.isequal": {
|
||||
"version": "4.5.0",
|
||||
"license": "MIT"
|
||||
@@ -44780,6 +44583,7 @@
|
||||
},
|
||||
"node_modules/serialize-javascript": {
|
||||
"version": "6.0.1",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"randombytes": "^2.1.0"
|
||||
|
||||
5
apps/theme-builder/package-lock.json
generated
5
apps/theme-builder/package-lock.json
generated
@@ -981,7 +981,7 @@
|
||||
},
|
||||
"../web": {
|
||||
"name": "@notesnook/web",
|
||||
"version": "3.0.17",
|
||||
"version": "3.0.18",
|
||||
"hasInstallScript": true,
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
@@ -992,6 +992,7 @@
|
||||
"@dnd-kit/sortable": "^8.0.0",
|
||||
"@emotion/react": "11.11.1",
|
||||
"@hazae41/foras": "^2.1.4",
|
||||
"@henrygd/queue": "^1.0.6",
|
||||
"@lingui/core": "4.11.2",
|
||||
"@lingui/react": "4.11.2",
|
||||
"@mdi/js": "^7.2.96",
|
||||
@@ -1032,7 +1033,7 @@
|
||||
"event-source-polyfill": "^1.0.25",
|
||||
"fflate": "^0.8.0",
|
||||
"file-saver": "^2.0.5",
|
||||
"framer-motion": "^10.16.8",
|
||||
"framer-motion": "^11.5.6",
|
||||
"hash-wasm": "^4.9.0",
|
||||
"hotkeys-js": "^3.8.3",
|
||||
"katex": "0.16.2",
|
||||
|
||||
1809
apps/web/package-lock.json
generated
1809
apps/web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -46,6 +46,7 @@ import { BuyDialog } from "./dialogs/buy-dialog";
|
||||
import { FeatureDialog } from "./dialogs/feature-dialog";
|
||||
import { AnnouncementDialog } from "./dialogs/announcement-dialog";
|
||||
import { logger } from "./utils/logger";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
type AppEffectsProps = {
|
||||
setShow: (show: boolean) => void;
|
||||
@@ -89,7 +90,7 @@ export default function AppEffects({ setShow }: AppEffectsProps) {
|
||||
break;
|
||||
}
|
||||
showToast("error", sentence, [
|
||||
{ text: "Upgrade now", onClick: () => BuyDialog.show({}) }
|
||||
{ text: strings.upgradeNow(), onClick: () => BuyDialog.show({}) }
|
||||
]);
|
||||
return { type, result: false };
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { lazify } from "../utils/lazify";
|
||||
import { logger } from "../utils/logger";
|
||||
import { showToast } from "../utils/toast";
|
||||
@@ -62,7 +63,9 @@ export async function saveAttachment(hash: string) {
|
||||
console.error(e);
|
||||
showToast(
|
||||
"error",
|
||||
`Failed to download attachment: ${hash} (error: ${(e as Error).message})`
|
||||
`${strings.attachmentsDownloadFailed(1)}: ${hash} (error: ${
|
||||
(e as Error).message
|
||||
})`
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -106,7 +109,9 @@ export async function downloadAttachment<
|
||||
console.error(e);
|
||||
showToast(
|
||||
"error",
|
||||
`Failed to download attachment: ${hash} (error: ${(e as Error).message})`
|
||||
`${strings.attachmentsDownloadFailed(1)}: ${hash} (error: ${
|
||||
(e as Error).message
|
||||
})`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ import { Cipher, SerializedKey } from "@notesnook/crypto";
|
||||
import { ChunkedStream } from "../utils/streams/chunked-stream";
|
||||
import { isFeatureSupported } from "../utils/feature-check";
|
||||
import { NNCrypto } from "../interfaces/nncrypto";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
export const CREATE_BUTTON_MAP = {
|
||||
notes: {
|
||||
@@ -94,7 +95,7 @@ export async function createBackup(
|
||||
const verified =
|
||||
rescueMode || encryptBackups || noVerify || (await verifyAccount());
|
||||
if (!verified) {
|
||||
showToast("error", "Could not create a backup: user verification failed.");
|
||||
showToast("error", `${strings.backupFailed()}: ${strings.verifyFailed()}.`);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -168,11 +169,11 @@ export async function createBackup(
|
||||
if (error) {
|
||||
showToast(
|
||||
"error",
|
||||
`Could not create a backup of your data: ${(error as Error).message}`
|
||||
`${strings.backupFailed()}: ${(error as Error).message}`
|
||||
);
|
||||
console.error(error);
|
||||
} else {
|
||||
showToast("success", `Backup saved at ${filePath}.`);
|
||||
showToast("success", `${strings.backupSavedAt(filePath)}`);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -316,7 +317,7 @@ export async function restoreBackupFile(backupFile: File) {
|
||||
});
|
||||
if (error) {
|
||||
console.error(error);
|
||||
showToast("error", `Failed to restore backup: ${error.message}`);
|
||||
showToast("error", `${strings.restoreFailed()}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -408,12 +409,12 @@ async function restore(
|
||||
) {
|
||||
try {
|
||||
await db.backup?.import(backup, { password, encryptionKey });
|
||||
showToast("success", "Backup restored!");
|
||||
showToast("success", strings.backupRestored());
|
||||
} catch (e) {
|
||||
logger.error(e as Error, "Could not restore the backup");
|
||||
showToast(
|
||||
"error",
|
||||
`Could not restore the backup: ${(e as Error).message || e}`
|
||||
`${strings.backupFailed()}: ${(e as Error).message || e}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ import Vault from "./vault";
|
||||
import { TaskManager } from "./task-manager";
|
||||
import { pluralize } from "@notesnook/common";
|
||||
import { ConfirmDialog, showMultiDeleteConfirmation } from "../dialogs/confirm";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
async function moveNotesToTrash(ids: string[], confirm = true) {
|
||||
if (confirm && !(await showMultiDeleteConfirmation(ids.length))) return;
|
||||
@@ -48,16 +49,16 @@ async function moveNotesToTrash(ids: string[], confirm = true) {
|
||||
await TaskManager.startTask({
|
||||
type: "status",
|
||||
id: "deleteNotes",
|
||||
title: "Deleting notes",
|
||||
title: strings.deletingItems("note", items.length),
|
||||
action: async (report) => {
|
||||
report({
|
||||
text: `Deleting ${pluralize(items.length, "note")}...`
|
||||
text: `${strings.deleting()} ${strings.notes(items.length)}...`
|
||||
});
|
||||
await noteStore.delete(...items);
|
||||
}
|
||||
});
|
||||
|
||||
showToast("success", `${pluralize(items.length, "note")} moved to trash`);
|
||||
showToast("success", strings.movedToTrash("note", items.length));
|
||||
}
|
||||
|
||||
async function moveNotebooksToTrash(ids: string[]) {
|
||||
@@ -71,16 +72,19 @@ async function moveNotebooksToTrash(ids: string[]) {
|
||||
await TaskManager.startTask({
|
||||
type: "status",
|
||||
id: "deleteNotebooks",
|
||||
title: "Deleting notebooks",
|
||||
title: strings.deletingItems("notebook", ids.length),
|
||||
action: async (report) => {
|
||||
report({
|
||||
text: `Deleting ${pluralize(ids.length, "notebook")}...`
|
||||
text: `${strings.deleting()} ${strings.itemsPlural(
|
||||
"notebook",
|
||||
ids.length
|
||||
)}...`
|
||||
});
|
||||
await notebookStore.delete(...ids);
|
||||
}
|
||||
});
|
||||
|
||||
showToast("success", `${pluralize(ids.length, "notebook")} moved to trash`);
|
||||
showToast("success", strings.movedToTrash("notebook", ids.length));
|
||||
}
|
||||
|
||||
async function deleteAttachments(ids: string[]) {
|
||||
@@ -98,7 +102,7 @@ async function deleteAttachments(ids: string[]) {
|
||||
await TaskManager.startTask({
|
||||
type: "status",
|
||||
id: "deleteAttachments",
|
||||
title: "Deleting attachments",
|
||||
title: strings.deletingItems("attachment", ids.length),
|
||||
action: async (report) => {
|
||||
for (let i = 0; i < ids.length; ++i) {
|
||||
const id = ids[i];
|
||||
@@ -106,7 +110,10 @@ async function deleteAttachments(ids: string[]) {
|
||||
if (!attachment) continue;
|
||||
|
||||
report({
|
||||
text: `Deleting ${pluralize(ids.length, "attachment")}...`,
|
||||
text: `${strings.deleting()} ${strings.itemsPlural(
|
||||
"attachment",
|
||||
ids.length
|
||||
)}...`,
|
||||
current: i,
|
||||
total: ids.length
|
||||
});
|
||||
@@ -114,7 +121,7 @@ async function deleteAttachments(ids: string[]) {
|
||||
}
|
||||
}
|
||||
});
|
||||
showToast("success", `${pluralize(ids.length, "attachment")} deleted`);
|
||||
showToast("success", strings.deleted("attachment", ids.length));
|
||||
}
|
||||
|
||||
async function moveRemindersToTrash(ids: string[]) {
|
||||
@@ -126,16 +133,19 @@ async function moveRemindersToTrash(ids: string[]) {
|
||||
await TaskManager.startTask({
|
||||
type: "status",
|
||||
id: "deleteReminders",
|
||||
title: "Deleting reminders",
|
||||
title: strings.deletingItems("reminder", ids.length),
|
||||
action: async (report) => {
|
||||
report({
|
||||
text: `Deleting ${pluralize(ids.length, "reminder")}...`
|
||||
text: `${strings.deleting()} ${strings.itemsPlural(
|
||||
"reminder",
|
||||
ids.length
|
||||
)}...`
|
||||
});
|
||||
await reminderStore.delete(...ids);
|
||||
}
|
||||
});
|
||||
|
||||
showToast("success", `${pluralize(ids.length, "reminder")} deleted.`);
|
||||
showToast("success", strings.deleted("reminder", ids.length));
|
||||
}
|
||||
|
||||
async function deleteTags(ids: string[]) {
|
||||
|
||||
@@ -29,6 +29,7 @@ import { showToast } from "../utils/toast";
|
||||
import { TaskScheduler } from "../utils/task-scheduler";
|
||||
import { BuyDialog } from "../dialogs/buy-dialog";
|
||||
import { RecoveryKeyDialog } from "../dialogs/recovery-key-dialog";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
export type NoticeType = "autoBackupsOff" | "login" | "email" | "recoverykey";
|
||||
|
||||
@@ -205,10 +206,10 @@ async function saveBackup(mode: "full" | "partial" = "partial") {
|
||||
if (openedToast !== null) return;
|
||||
openedToast = showToast(
|
||||
"success",
|
||||
"Your backup is ready for download.",
|
||||
strings.backupReadyToDownload(),
|
||||
[
|
||||
{
|
||||
text: "Later",
|
||||
text: strings.later(),
|
||||
onClick: async () => {
|
||||
await db.backup.updateBackupTime();
|
||||
openedToast?.hide();
|
||||
@@ -217,7 +218,7 @@ async function saveBackup(mode: "full" | "partial" = "partial") {
|
||||
type: "paragraph"
|
||||
},
|
||||
{
|
||||
text: "Download",
|
||||
text: strings.network.download(),
|
||||
onClick: async () => {
|
||||
await createBackup({ mode });
|
||||
openedToast?.hide();
|
||||
|
||||
@@ -21,10 +21,10 @@ import { db } from "./db";
|
||||
import { store as notestore } from "../stores/note-store";
|
||||
import { store as nbstore } from "../stores/notebook-store";
|
||||
import { showToast } from "../utils/toast";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
async function showUnpinnedToast(itemId, itemType) {
|
||||
const noun = itemType === "note" ? "Note" : "Notebook";
|
||||
const messageText = `${noun} unpinned successfully!`;
|
||||
const messageText = strings.action(itemType, 1, "unpinned");
|
||||
const undoAction = async () => {
|
||||
toast.hide();
|
||||
if (itemType === "note") await notestore.pin(itemId);
|
||||
@@ -35,8 +35,7 @@ async function showUnpinnedToast(itemId, itemType) {
|
||||
}
|
||||
|
||||
function showItemDeletedToast(item) {
|
||||
const noun = item.type === "note" ? "Note" : "Notebook";
|
||||
const messageText = `${noun} deleted successfully!`;
|
||||
const messageText = strings.action(item.type, 1, "deleted");
|
||||
const undoAction = async () => {
|
||||
toast.hide();
|
||||
let trashItem = db.trash.all.find((i) => i.id === item.id);
|
||||
@@ -57,7 +56,7 @@ async function showUndoableToast(message, onAction, onPermanentAction, onUndo) {
|
||||
toast.hide();
|
||||
onUndo();
|
||||
};
|
||||
let actions = [{ text: "Undo", onClick: undoAction }];
|
||||
let actions = [{ text: strings.undo(), onClick: undoAction }];
|
||||
var toast = showToast("success", message, actions);
|
||||
}
|
||||
|
||||
|
||||
@@ -21,19 +21,20 @@ import { db } from "./db";
|
||||
import { showPasswordDialog } from "../dialogs/password-dialog";
|
||||
import { showToast } from "../utils/toast";
|
||||
import { VAULT_ERRORS } from "@notesnook/core";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
class Vault {
|
||||
static async createVault() {
|
||||
if (await db.vault.exists()) return false;
|
||||
return showPasswordDialog({
|
||||
title: "Create your vault",
|
||||
subtitle: "A vault stores your notes in a password-encrypted storage.",
|
||||
title: strings.createVault(),
|
||||
subtitle: strings.createVaultDesc(),
|
||||
inputs: {
|
||||
password: { label: "Password", autoComplete: "new-password" }
|
||||
password: { label: strings.password(), autoComplete: "new-password" }
|
||||
},
|
||||
validate: async ({ password }) => {
|
||||
await db.vault.create(password);
|
||||
showToast("success", "Vault created.");
|
||||
showToast("success", strings.vaultCreated());
|
||||
return true;
|
||||
}
|
||||
});
|
||||
@@ -43,11 +44,13 @@ class Vault {
|
||||
if (!(await db.vault.exists())) return false;
|
||||
|
||||
return showPasswordDialog({
|
||||
title: "Clear your vault",
|
||||
subtitle:
|
||||
"Enter vault password to unlock and remove all notes from the vault.",
|
||||
title: strings.clearVault(),
|
||||
subtitle: strings.clearVaultDesc(),
|
||||
inputs: {
|
||||
password: { label: "Password", autoComplete: "current-password" }
|
||||
password: {
|
||||
label: strings.password(),
|
||||
autoComplete: "current-password"
|
||||
}
|
||||
},
|
||||
validate: async ({ password }) => {
|
||||
await db.vault.clear(password);
|
||||
@@ -59,14 +62,17 @@ class Vault {
|
||||
static async deleteVault() {
|
||||
if (!(await db.vault.exists())) return false;
|
||||
const result = await showPasswordDialog({
|
||||
title: "Delete your vault",
|
||||
subtitle: "Enter your account password to delete your vault.",
|
||||
title: strings.deleteVault(),
|
||||
subtitle: strings.deleteVaultDesc(),
|
||||
inputs: {
|
||||
password: { label: "Password", autoComplete: "current-password" }
|
||||
password: {
|
||||
label: strings.password(),
|
||||
autoComplete: "current-password"
|
||||
}
|
||||
},
|
||||
checks: {
|
||||
deleteAllLockedNotes: {
|
||||
text: "Delete all locked notes?",
|
||||
text: strings.deleteAllNotes(),
|
||||
default: false
|
||||
}
|
||||
},
|
||||
@@ -83,10 +89,13 @@ class Vault {
|
||||
|
||||
static unlockVault() {
|
||||
return showPasswordDialog({
|
||||
title: "Unlock vault",
|
||||
subtitle: "Please enter your vault password to continue.",
|
||||
title: strings.unlockVault(),
|
||||
subtitle: strings.unlockVaultDesc(),
|
||||
inputs: {
|
||||
password: { label: "Password", autoComplete: "current-password" }
|
||||
password: {
|
||||
label: strings.password(),
|
||||
autoComplete: "current-password"
|
||||
}
|
||||
},
|
||||
validate: ({ password }) => {
|
||||
return db.vault.unlock(password).catch(() => false);
|
||||
@@ -96,18 +105,21 @@ class Vault {
|
||||
|
||||
static changeVaultPassword() {
|
||||
return showPasswordDialog({
|
||||
title: "Change vault password",
|
||||
subtitle: "All locked notes will be re-encrypted with the new password.",
|
||||
title: strings.changeVaultPassword(),
|
||||
subtitle: strings.changeVaultPasswordDesc(),
|
||||
inputs: {
|
||||
oldPassword: {
|
||||
label: "Old password",
|
||||
label: strings.oldPassword(),
|
||||
autoComplete: "current-password"
|
||||
},
|
||||
newPassword: { label: "New password", autoComplete: "new-password" }
|
||||
newPassword: {
|
||||
label: strings.newPassword(),
|
||||
autoComplete: "new-password"
|
||||
}
|
||||
},
|
||||
validate: async ({ oldPassword, newPassword }) => {
|
||||
await db.vault.changePassword(oldPassword, newPassword);
|
||||
showToast("success", "Vault password changed.");
|
||||
showToast("success", strings.passwordChangedSuccessfully());
|
||||
return true;
|
||||
}
|
||||
});
|
||||
@@ -115,10 +127,13 @@ class Vault {
|
||||
|
||||
static unlockNote(id: string) {
|
||||
return showPasswordDialog({
|
||||
title: "Unlock note",
|
||||
subtitle: "Your note will be unencrypted and removed from the vault.",
|
||||
title: strings.unlockNote(),
|
||||
subtitle: strings.unlockNoteDesc(),
|
||||
inputs: {
|
||||
password: { label: "Password", autoComplete: "current-password" }
|
||||
password: {
|
||||
label: strings.password(),
|
||||
autoComplete: "current-password"
|
||||
}
|
||||
},
|
||||
validate: async ({ password }) => {
|
||||
return db.vault
|
||||
@@ -156,10 +171,13 @@ class Vault {
|
||||
|
||||
static askPassword(action: (password: string) => Promise<boolean>) {
|
||||
return showPasswordDialog({
|
||||
title: "Unlock vault",
|
||||
subtitle: "Please enter your vault password to continue.",
|
||||
title: strings.unlockVault(),
|
||||
subtitle: strings.unlockVaultDesc(),
|
||||
inputs: {
|
||||
password: { label: "Password", autoComplete: "current-password" }
|
||||
password: {
|
||||
label: strings.password(),
|
||||
autoComplete: "current-password"
|
||||
}
|
||||
},
|
||||
validate: async ({ password }) => {
|
||||
return action(password);
|
||||
|
||||
@@ -24,6 +24,7 @@ import { useStore as useAnnouncementStore } from "../../stores/announcement-stor
|
||||
import Notice from "../notice";
|
||||
|
||||
import { alpha } from "@theme-ui/color";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
function Announcements() {
|
||||
const announcements = useAnnouncementStore(
|
||||
@@ -56,7 +57,7 @@ function Announcements() {
|
||||
cursor: "pointer",
|
||||
alignSelf: "end"
|
||||
}}
|
||||
title="Dismiss announcement"
|
||||
title={strings.dismissAnnouncement()}
|
||||
onClick={() => {
|
||||
dismiss(announcement.id);
|
||||
}}
|
||||
|
||||
@@ -58,6 +58,7 @@ import { useEditorStore } from "../../stores/editor-store";
|
||||
import { PromptDialog } from "../../dialogs/prompt";
|
||||
import { DialogManager } from "../../common/dialog-manager";
|
||||
import { useStore as useSelectionStore } from "../../stores/selection-store";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
const FILE_ICONS: Record<string, Icon> = {
|
||||
"image/": FileImage,
|
||||
@@ -203,13 +204,13 @@ export function Attachment({
|
||||
sx={{ flexShrink: 0 }}
|
||||
color={"accent"}
|
||||
size={16}
|
||||
title={"Uploaded"}
|
||||
title={strings.uploaded()}
|
||||
/>
|
||||
) : (
|
||||
<Checkmark
|
||||
sx={{ flexShrink: 0 }}
|
||||
size={16}
|
||||
title={"Waiting for upload"}
|
||||
title={strings.waitingForUpload()}
|
||||
/>
|
||||
)}
|
||||
</Text>
|
||||
|
||||
@@ -26,6 +26,7 @@ import Jason from "../../assets/testimonials/jason.jpg";
|
||||
import Cameron from "../../assets/testimonials/cameron.jpg";
|
||||
import { hosts } from "@notesnook/core";
|
||||
import { SettingsDialog } from "../../dialogs/settings";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
const testimonials = [
|
||||
{
|
||||
@@ -58,20 +59,14 @@ const testimonials = [
|
||||
}
|
||||
];
|
||||
|
||||
const titles = [
|
||||
"Write with freedom.",
|
||||
"Privacy comes first.",
|
||||
"Take notes privately.",
|
||||
"Encrypted, private, secure.",
|
||||
"❤️ = 🔒 + 🗒️"
|
||||
];
|
||||
|
||||
function randomTestimonial() {
|
||||
return testimonials[getRandom(0, testimonials.length - 1)];
|
||||
}
|
||||
|
||||
function randomTitle() {
|
||||
return titles[getRandom(0, titles.length - 1)];
|
||||
return strings.webAuthTitles[
|
||||
getRandom(0, strings.webAuthTitles.length - 1)
|
||||
]();
|
||||
}
|
||||
|
||||
function AuthContainer(props) {
|
||||
|
||||
@@ -17,13 +17,13 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { Flex, Text, Button, ButtonProps } from "@theme-ui/components";
|
||||
import { Loading, Close } from "../icons";
|
||||
import ReactModal from "react-modal";
|
||||
import { FlexScrollContainer } from "../scroll-container";
|
||||
import { Button, ButtonProps, Flex, Text } from "@theme-ui/components";
|
||||
import { SxProp } from "@theme-ui/core";
|
||||
import React from "react";
|
||||
import ReactModal from "react-modal";
|
||||
import { useStore as useThemeStore } from "../../stores/theme-store";
|
||||
import { Close, Loading } from "../icons";
|
||||
import { FlexScrollContainer } from "../scroll-container";
|
||||
import { ScopedThemeProvider } from "../theme-provider";
|
||||
|
||||
ReactModal.setAppElement("#root");
|
||||
|
||||
@@ -19,6 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { Flex, Button, Text, FlexProps } from "@theme-ui/components";
|
||||
import { getFormattedDate } from "@notesnook/common";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
type ContentToggle = {
|
||||
isSelected: boolean;
|
||||
@@ -54,7 +55,7 @@ function ContentToggle(props: ContentToggle) {
|
||||
p={1}
|
||||
px={2}
|
||||
>
|
||||
Save copy
|
||||
{strings.saveACopy()}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
@@ -69,7 +70,11 @@ function ContentToggle(props: ContentToggle) {
|
||||
p={1}
|
||||
px={2}
|
||||
>
|
||||
{isSelected ? "Undo" : isOtherSelected ? "Discard" : "Keep"}
|
||||
{isSelected
|
||||
? strings.undo()
|
||||
: isOtherSelected
|
||||
? strings.discard()
|
||||
: strings.keep()}
|
||||
</Button>
|
||||
</Flex>
|
||||
)}
|
||||
|
||||
@@ -33,6 +33,7 @@ import { ContentItem, Note } from "@notesnook/core";
|
||||
import { UnlockView } from "../unlock";
|
||||
import { getFormattedDate } from "@notesnook/common";
|
||||
import { diff } from "diffblazer";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
type DiffViewerProps = { session: ConflictedEditorSession };
|
||||
function DiffViewer(props: DiffViewerProps) {
|
||||
@@ -129,7 +130,7 @@ function DiffViewer(props: DiffViewerProps) {
|
||||
}}
|
||||
>
|
||||
<Restore size={18} />
|
||||
<Text ml={1}>Restore this version</Text>
|
||||
<Text ml={1}>{strings.restoreThisVersion()}</Text>
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
@@ -152,7 +153,7 @@ function DiffViewer(props: DiffViewerProps) {
|
||||
}}
|
||||
>
|
||||
<Copy size={18} />
|
||||
<Text ml={1}>Save a copy</Text>
|
||||
<Text ml={1}>{strings.saveACopy()}</Text>
|
||||
</Button>
|
||||
</>
|
||||
) : null}
|
||||
@@ -178,8 +179,8 @@ function DiffViewer(props: DiffViewerProps) {
|
||||
{content.locked ? (
|
||||
<UnlockView
|
||||
title={getFormattedDate(content.dateEdited)}
|
||||
subtitle="Please enter the password to view this version"
|
||||
buttonTitle="Unlock"
|
||||
subtitle={strings.enterPasswordToUnlockVersion()}
|
||||
buttonTitle={strings.unlock()}
|
||||
unlock={async (password) => {
|
||||
const decryptedContent = await db.vault.decryptContent(
|
||||
content,
|
||||
@@ -196,7 +197,9 @@ function DiffViewer(props: DiffViewerProps) {
|
||||
<>
|
||||
<ContentToggle
|
||||
label={
|
||||
session.type === "diff" ? "Older version" : "Current note"
|
||||
session.type === "diff"
|
||||
? strings.olderVersion()
|
||||
: strings.currentNote()
|
||||
}
|
||||
readonly={session.type === "diff"}
|
||||
dateEdited={content.dateEdited}
|
||||
@@ -255,8 +258,8 @@ function DiffViewer(props: DiffViewerProps) {
|
||||
{conflictedContent.locked ? (
|
||||
<UnlockView
|
||||
title={getFormattedDate(conflictedContent.dateEdited)}
|
||||
subtitle="Please enter the password to view this version"
|
||||
buttonTitle="Unlock"
|
||||
subtitle={strings.enterPasswordToUnlockVersion()}
|
||||
buttonTitle={strings.unlock()}
|
||||
unlock={async (password) => {
|
||||
const decryptedContent = await db.vault.decryptContent(
|
||||
conflictedContent,
|
||||
@@ -276,8 +279,8 @@ function DiffViewer(props: DiffViewerProps) {
|
||||
resolveConflict={onResolveContent}
|
||||
label={
|
||||
session.type === "diff"
|
||||
? "Current version"
|
||||
: "Incoming note"
|
||||
? strings.currentNote()
|
||||
: strings.incomingNote()
|
||||
}
|
||||
isSelected={selectedContent === 1}
|
||||
isOtherSelected={selectedContent === 0}
|
||||
|
||||
@@ -23,6 +23,7 @@ import { Loading, Saved, NotSaved } from "../icons";
|
||||
import { useNoteStatistics } from "./manager";
|
||||
import { getFormattedDate } from "@notesnook/common";
|
||||
import { MAX_AUTO_SAVEABLE_WORDS } from "./types";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
const SAVE_STATE_ICON_MAP = {
|
||||
"-1": NotSaved,
|
||||
@@ -48,7 +49,7 @@ function EditorFooter() {
|
||||
variant="subBody"
|
||||
sx={{ color: "paragraph" }}
|
||||
>
|
||||
Auto save: off
|
||||
{strings.autoSaveOff()}
|
||||
</Text>
|
||||
) : null}
|
||||
<Text
|
||||
@@ -57,8 +58,8 @@ function EditorFooter() {
|
||||
variant="subBody"
|
||||
sx={{ color: "paragraph" }}
|
||||
>
|
||||
{words.total + " words"}
|
||||
{words.selected ? ` (${words.selected} selected)` : ""}
|
||||
{words.total + " " + strings.words()}
|
||||
{words.selected ? ` (${words.selected} ${strings.selected()})` : ""}
|
||||
</Text>
|
||||
{dateEdited > 0 ? (
|
||||
<Text
|
||||
|
||||
@@ -30,6 +30,7 @@ import { useMenuTrigger } from "../../hooks/use-menu";
|
||||
import { MenuItem } from "@notesnook/ui";
|
||||
import { navigate } from "../../navigation";
|
||||
import { Tag } from "@notesnook/core";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
type HeaderProps = { readonly: boolean; id: string };
|
||||
function Header(props: HeaderProps) {
|
||||
@@ -194,7 +195,7 @@ export function Autosuggest<T>(props: AutosuggestProps<T>) {
|
||||
fontSize: "body",
|
||||
alignSelf: "flex-start"
|
||||
}}
|
||||
placeholder="Add a tag..."
|
||||
placeholder={strings.addATag()}
|
||||
data-test-id="editor-tag-input"
|
||||
onFocus={async () => {
|
||||
const text = getInputValue();
|
||||
|
||||
@@ -70,6 +70,7 @@ import { EditorActionBar } from "./action-bar";
|
||||
import { logger } from "../../utils/logger";
|
||||
import { PanelGroup, Panel, PanelResizeHandle } from "react-resizable-panels";
|
||||
import { NoteLinkingDialog } from "../../dialogs/note-linking-dialog";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
const PDFPreview = React.lazy(() => import("../pdf-preview"));
|
||||
|
||||
@@ -90,12 +91,7 @@ async function saveContent(
|
||||
}),
|
||||
new Promise((_, reject) =>
|
||||
setTimeout(
|
||||
() =>
|
||||
reject(
|
||||
new Error(
|
||||
"Saving this note is taking too long. Copy your changes and restart the app to prevent data loss. If the problem persists, please report it to us at support@streetwriters.co."
|
||||
)
|
||||
),
|
||||
() => reject(new Error(strings.savingNoteTakingTooLong())),
|
||||
30 * 1000
|
||||
)
|
||||
)
|
||||
@@ -105,7 +101,7 @@ async function saveContent(
|
||||
(e as Error).message,
|
||||
[
|
||||
{
|
||||
text: "Dismiss",
|
||||
text: strings.dismiss(),
|
||||
onClick: () => hide()
|
||||
}
|
||||
],
|
||||
@@ -382,7 +378,9 @@ function DownloadAttachmentProgress(props: DownloadAttachmentProgressProps) {
|
||||
flexDirection: "column"
|
||||
}}
|
||||
>
|
||||
<Text variant="title">Downloading attachment ({progress}%)</Text>
|
||||
<Text variant="title">
|
||||
{strings.downloadingAttachments()} ({progress}%)
|
||||
</Text>
|
||||
<Progress
|
||||
value={progress}
|
||||
max={100}
|
||||
@@ -397,7 +395,7 @@ function DownloadAttachmentProgress(props: DownloadAttachmentProgressProps) {
|
||||
if (id) db.fs().cancel(id).catch(console.error);
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
{strings.cancel()}
|
||||
</Button>
|
||||
</Flex>
|
||||
);
|
||||
@@ -485,7 +483,7 @@ export function Editor(props: EditorProps) {
|
||||
|
||||
const dataurl = await downloadAttachment(hash, "base64", id);
|
||||
if (!dataurl)
|
||||
return showToast("error", "This image cannot be previewed.");
|
||||
return showToast("error", strings.imagePreviewFailed());
|
||||
|
||||
ReactDOM.render(
|
||||
<ScopedThemeProvider>
|
||||
@@ -684,7 +682,7 @@ function DropZone(props: DropZoneProps) {
|
||||
>
|
||||
<Attachment size={72} />
|
||||
<Text variant={"heading"} sx={{ color: "icon", mt: 2 }}>
|
||||
Drop your files here to attach
|
||||
{strings.dropFilesToAttach()}
|
||||
</Text>
|
||||
</Flex>
|
||||
</Box>
|
||||
@@ -815,8 +813,8 @@ function UnlockNoteView(props: UnlockNoteViewProps) {
|
||||
ref={root}
|
||||
>
|
||||
<UnlockView
|
||||
buttonTitle="Open note"
|
||||
subtitle="Please enter the password to unlock this note."
|
||||
buttonTitle={strings.openNote()}
|
||||
subtitle={strings.enterPasswordToUnlockNote()}
|
||||
title={session.note.title}
|
||||
unlock={async (password) => {
|
||||
const note = await db.vault.open(session.id, password);
|
||||
|
||||
@@ -19,6 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { Flex, Text } from "@theme-ui/components";
|
||||
import { Loading } from "../icons";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
function EditorLoading({ text }: { text?: string }) {
|
||||
return (
|
||||
@@ -32,7 +33,7 @@ function EditorLoading({ text }: { text?: string }) {
|
||||
>
|
||||
<Loading color="accent" sx={{ mt: 2 }} />
|
||||
<Text variant="body" mt={2} sx={{ textAlign: "center" }}>
|
||||
{text || "Loading editor. Please wait..."}
|
||||
{text || `${strings.loadingEditor()}. ${strings.pleaseWait()}...`}
|
||||
</Text>
|
||||
</Flex>
|
||||
);
|
||||
|
||||
@@ -47,6 +47,7 @@ import { scrollIntoViewById } from "@notesnook/editor";
|
||||
import { Button, Flex, Text } from "@theme-ui/components";
|
||||
import { useEditorManager } from "./manager";
|
||||
import { TITLE_BAR_HEIGHT } from "../title-bar";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
type TableOfContentsProps = {
|
||||
sessionId: string;
|
||||
@@ -122,7 +123,7 @@ function TableOfContents(props: TableOfContentsProps) {
|
||||
>
|
||||
<ScrollContainer>
|
||||
<Section
|
||||
title="Table of contents"
|
||||
title={strings.toc()}
|
||||
button={
|
||||
<ArrowLeft
|
||||
data-test-id="toc-close"
|
||||
@@ -140,7 +141,7 @@ function TableOfContents(props: TableOfContentsProps) {
|
||||
pl: 1
|
||||
}}
|
||||
>
|
||||
No headings found.
|
||||
{strings.noHeadingsFound()}.
|
||||
</Text>
|
||||
) : (
|
||||
tableOfContents.map((t) => (
|
||||
|
||||
@@ -28,6 +28,7 @@ import { getFontById } from "@notesnook/editor";
|
||||
import { replaceDateTime } from "@notesnook/editor";
|
||||
import { useStore as useSettingsStore } from "../../stores/setting-store";
|
||||
import { AppEventManager, AppEvents } from "../../common/app-events";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
type TitleBoxProps = {
|
||||
id: string;
|
||||
@@ -118,7 +119,7 @@ function TitleBox(props: TitleBoxProps) {
|
||||
id="editor-title"
|
||||
data-test-id="editor-title"
|
||||
className="editorTitle"
|
||||
placeholder={"Note title"}
|
||||
placeholder={strings.noteTitle()}
|
||||
readOnly={readonly}
|
||||
dir="auto"
|
||||
sx={{
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
} from "react-error-boundary";
|
||||
import { useKeyStore } from "../../interfaces/key-store";
|
||||
import { isFeatureSupported } from "../../utils/feature-check";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
export function GlobalErrorHandler(props: PropsWithChildren) {
|
||||
const { showBoundary } = useErrorBoundary();
|
||||
@@ -111,17 +112,17 @@ export function ErrorComponent({ error, resetErrorBoundary }: FallbackProps) {
|
||||
variant="heading"
|
||||
sx={{ borderBottom: "1px solid var(--border)", pb: 1 }}
|
||||
>
|
||||
Something went wrong
|
||||
{strings.somethingWentWrong()}
|
||||
</Text>
|
||||
<ErrorText error={error} />
|
||||
{help ? (
|
||||
<>
|
||||
<Text variant="subtitle" sx={{ mt: 2 }}>
|
||||
What went wrong?
|
||||
{strings.whatWentWrong()}
|
||||
</Text>
|
||||
<Text variant="body">{help.explanation}</Text>
|
||||
<Text variant="subtitle" sx={{ mt: 1 }}>
|
||||
How to fix it?
|
||||
{strings.howToFix()}
|
||||
</Text>
|
||||
<Text variant="body">{help.action}</Text>
|
||||
</>
|
||||
@@ -138,7 +139,7 @@ export function ErrorComponent({ error, resetErrorBoundary }: FallbackProps) {
|
||||
})
|
||||
}
|
||||
>
|
||||
Fix it
|
||||
{strings.fixIt()}
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
@@ -146,7 +147,7 @@ export function ErrorComponent({ error, resetErrorBoundary }: FallbackProps) {
|
||||
sx={{ alignSelf: "start", px: 30, mt: 1 }}
|
||||
onClick={() => window.location.reload()}
|
||||
>
|
||||
Reload app
|
||||
{strings.reloadApp()}
|
||||
</Button>
|
||||
)}
|
||||
<>
|
||||
@@ -157,7 +158,7 @@ export function ErrorComponent({ error, resetErrorBoundary }: FallbackProps) {
|
||||
navigator.clipboard.writeText(errorToString(error));
|
||||
}}
|
||||
>
|
||||
Copy
|
||||
{strings.copy()}
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
@@ -179,7 +180,7 @@ ${getDeviceInfo()}`
|
||||
window.open(mailto.toString(), "_blank");
|
||||
}}
|
||||
>
|
||||
Contact support
|
||||
{strings.contactSupport()}
|
||||
</Button>
|
||||
</>
|
||||
</Flex>
|
||||
@@ -201,9 +202,8 @@ function getErrorHelp(props: FallbackProps) {
|
||||
errorText.includes("corrupted migrations:")
|
||||
) {
|
||||
return {
|
||||
explanation: `This error usually means the database file is either corrupt or it could not be decrypted.`,
|
||||
action:
|
||||
"This error can only be fixed by wiping & reseting the database. Beware that this will wipe all your data inside the database with no way to recover it later on. This WILL NOT change/affect/delete/wipe your data on the server but ONLY on this device.",
|
||||
explanation: strings.databaseCorruptExplain(),
|
||||
action: strings.databaseCorruptFix(),
|
||||
fix: async () => {
|
||||
await resetDatabase();
|
||||
resetErrorBoundary();
|
||||
@@ -211,9 +211,8 @@ function getErrorHelp(props: FallbackProps) {
|
||||
};
|
||||
} else if (errorText.includes("Could not decrypt key.")) {
|
||||
return {
|
||||
explanation: `This error means the at rest encryption key could not be decrypted. This can be due to data corruption or implementation change.`,
|
||||
action:
|
||||
"This error can only be fixed by wiping & reseting the Key Store and the database. This WILL NOT change/affect/delete/wipe your data on the server but ONLY on this device.",
|
||||
explanation: strings.decryptKeyErrorExplain(),
|
||||
action: strings.decryptKeyErrorFix(),
|
||||
fix: async () => {
|
||||
await resetDatabase();
|
||||
resetErrorBoundary();
|
||||
@@ -221,9 +220,8 @@ function getErrorHelp(props: FallbackProps) {
|
||||
};
|
||||
} else if (errorText.includes("database disk image is malformed")) {
|
||||
return {
|
||||
explanation: `This error usually means the search index is corrupted.`,
|
||||
action:
|
||||
"This error can be fixed by rebuilding the search index. This action won't result in any kind of data loss.",
|
||||
explanation: strings.searchIndexCorrupt(),
|
||||
action: strings.searchIndexCorruptFix(),
|
||||
fix: async () => {
|
||||
const { db } = await import("../../common/db");
|
||||
await db.lookup.rebuild();
|
||||
|
||||
@@ -22,6 +22,7 @@ import Field from "../field";
|
||||
import { Plus, Search } from "../icons";
|
||||
import { Button, Text } from "@theme-ui/components";
|
||||
import { VirtualizedList, VirtualizedListProps } from "../virtualized-list";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
type FilteredListProps<T> = {
|
||||
placeholders: { filter: string; empty: string };
|
||||
@@ -93,7 +94,7 @@ export function FilteredList<T>(props: FilteredListProps<T>) {
|
||||
await _createNewItem(query);
|
||||
}}
|
||||
>
|
||||
<Text variant={"body"}>{`Add "${query}"`}</Text>
|
||||
<Text variant={"body"}>{`${strings.add()} "${query}"`}</Text>
|
||||
<Plus size={16} color="accent" />
|
||||
</Button>
|
||||
) : (
|
||||
|
||||
@@ -20,6 +20,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import { Flex, Text } from "@theme-ui/components";
|
||||
import { ThemeUICSSObject } from "@theme-ui/core";
|
||||
import { Close, Icon } from "../icons";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
type IconTagProps = {
|
||||
text: string;
|
||||
@@ -105,7 +106,7 @@ function IconTag(props: IconTagProps) {
|
||||
{onDismiss && (
|
||||
<Close
|
||||
size={12}
|
||||
title="Remove"
|
||||
title={strings.remove()}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
@@ -23,15 +23,7 @@ import Config from "../../utils/config";
|
||||
import { getDownloadLink, getPlatform } from "../../utils/platform";
|
||||
import DropdownButton from "../dropdown-button";
|
||||
import { BaseThemeProvider } from "../theme-provider";
|
||||
|
||||
const nativeFeatures = [
|
||||
"Native high-performance encryption",
|
||||
"Automatic backups",
|
||||
"Pin notes in notifications drawer",
|
||||
"Share & append to notes from anywhere",
|
||||
"Quick note widgets",
|
||||
"App lock"
|
||||
];
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
const platform = getPlatform();
|
||||
const isMobile = platform === "Android" || platform === "iOS";
|
||||
@@ -67,10 +59,8 @@ export default function InstallNotice({ onClose }) {
|
||||
}}
|
||||
p={2}
|
||||
>
|
||||
<Text variant={"title"}>Install Notesnook</Text>
|
||||
<Text variant={"body"}>
|
||||
For a more integrated user experience, try out Notesnook for {platform}.
|
||||
</Text>
|
||||
<Text variant={"title"}>{strings.installNotesnook()}</Text>
|
||||
<Text variant={"body"}>{strings.installNotesnookDesc(platform)}.</Text>
|
||||
{isMobile && (
|
||||
<Box
|
||||
sx={{
|
||||
@@ -81,7 +71,7 @@ export default function InstallNotice({ onClose }) {
|
||||
mt: 1
|
||||
}}
|
||||
>
|
||||
{nativeFeatures.map((feature) => (
|
||||
{strings.nativeFeatures().map((feature) => (
|
||||
<Flex
|
||||
key={feature}
|
||||
p={1}
|
||||
@@ -109,7 +99,7 @@ export default function InstallNotice({ onClose }) {
|
||||
}}
|
||||
sx={{ alignSelf: "start" }}
|
||||
>
|
||||
{`Don't show again`}
|
||||
{strings.dontShowAgain()}
|
||||
</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
@@ -28,6 +28,7 @@ import useHashLocation, {
|
||||
} from "../../hooks/use-hash-location";
|
||||
import makeMatcher from "wouter/matcher";
|
||||
import { Lock } from "../icons";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
const matcher = makeMatcher();
|
||||
const EDITOR_MARGINS = Config.get("editor:margins", true);
|
||||
@@ -89,9 +90,9 @@ export const EditorLoader = memo(function EditorLoader() {
|
||||
}}
|
||||
>
|
||||
{isNoteLoading ? (
|
||||
<TextScramble text="Loading" nextLetterSpeed={50} />
|
||||
<TextScramble text={strings.loading()} nextLetterSpeed={50} />
|
||||
) : (
|
||||
"Note title"
|
||||
strings.noteTitle()
|
||||
)}
|
||||
</Text>
|
||||
<Skeleton
|
||||
|
||||
@@ -72,6 +72,7 @@ import { Notebook, Tag } from "@notesnook/core";
|
||||
import { handleDrop } from "../../common/drop-handler";
|
||||
import { Menu } from "../../hooks/use-menu";
|
||||
import { RenameColorDialog } from "../../dialogs/item-dialog";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
type Route = {
|
||||
id: string;
|
||||
@@ -471,7 +472,7 @@ function NavigationMenu(props: NavigationMenuProps) {
|
||||
<NavigationItem
|
||||
id="login"
|
||||
isTablet={isTablet}
|
||||
title="Login"
|
||||
title={strings.login()}
|
||||
icon={Login}
|
||||
onClick={() => hardNavigate("/login")}
|
||||
/>
|
||||
@@ -480,7 +481,7 @@ function NavigationMenu(props: NavigationMenuProps) {
|
||||
<NavigationItem
|
||||
id="change-theme"
|
||||
isTablet={isTablet}
|
||||
title={theme === "dark" ? "Light mode" : "Dark mode"}
|
||||
title={theme === "dark" ? strings.light() : strings.dark()}
|
||||
icon={theme === "dark" ? LightMode : DarkMode}
|
||||
onClick={() => {
|
||||
setFollowSystemTheme(false);
|
||||
@@ -505,7 +506,7 @@ function NavigationMenu(props: NavigationMenuProps) {
|
||||
{isTablet ? null : (
|
||||
<Button
|
||||
variant={"icon"}
|
||||
title="Toggle dark/light mode"
|
||||
title={strings.toggleDarkLightMode()}
|
||||
sx={{ borderLeft: "1px solid var(--separator)" }}
|
||||
onClick={() => {
|
||||
setFollowSystemTheme(false);
|
||||
|
||||
@@ -93,6 +93,7 @@ import { CreateColorDialog } from "../../dialogs/create-color-dialog";
|
||||
import { ConfirmDialog } from "../../dialogs/confirm";
|
||||
import { MoveNoteDialog } from "../../dialogs/move-note-dialog";
|
||||
import { AddReminderDialog } from "../../dialogs/add-reminder-dialog";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
type NoteProps = NoteResolvedData & {
|
||||
item: NoteType;
|
||||
@@ -218,7 +219,7 @@ function Note(props: NoteProps) {
|
||||
) : null}
|
||||
|
||||
{attachments?.failed ? (
|
||||
<Flex title={`Errors in ${attachments.failed} attachments.`}>
|
||||
<Flex title={strings.errorsInAttachments(attachments.failed)}>
|
||||
<AttachmentError size={13} color="var(--icon-error)" />
|
||||
<Text ml={"2px"}>{attachments.failed}</Text>
|
||||
</Flex>
|
||||
@@ -242,10 +243,11 @@ function Note(props: NoteProps) {
|
||||
data-test-id={`tag-item`}
|
||||
key={tag.id}
|
||||
variant="anchor"
|
||||
title={`Go to #${tag.title}`}
|
||||
title={`${strings.goTo()} #${tag.title}`}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (!tag.id) return showToast("error", "Tag not found.");
|
||||
if (!tag.id)
|
||||
return showToast("error", strings.tagNotFound());
|
||||
navigate(`/tags/${tag.id}`);
|
||||
}}
|
||||
sx={{
|
||||
|
||||
@@ -45,6 +45,7 @@ import { handleDrop } from "../../common/drop-handler";
|
||||
import { useDragHandler } from "../../hooks/use-drag-handler";
|
||||
import { ConfirmDialog } from "../../dialogs/confirm";
|
||||
import { useStore as useSelectionStore } from "../../stores/selection-store";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
type NotebookProps = {
|
||||
item: NotebookType;
|
||||
@@ -95,7 +96,7 @@ function Notebook(props: NotebookProps) {
|
||||
<>
|
||||
{compact ? (
|
||||
<>
|
||||
<Text variant="subBody">{pluralize(totalNotes, "note")}</Text>
|
||||
<Text variant="subBody">{strings.notes(totalNotes)}</Text>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
@@ -130,7 +131,7 @@ function Notebook(props: NotebookProps) {
|
||||
•
|
||||
</Text>
|
||||
<Text sx={{ color: "inherit" }}>
|
||||
{pluralize(totalNotes, "note")}
|
||||
{strings.notes(totalNotes)}
|
||||
</Text>
|
||||
</Flex>
|
||||
</>
|
||||
|
||||
@@ -23,6 +23,7 @@ import { useStore as useAppStore } from "../../stores/app-store";
|
||||
import { NoticesData } from "../../common/notices";
|
||||
import { Dismiss } from "../icons";
|
||||
import Config from "../../utils/config";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
function Notice() {
|
||||
const notices = useAppStore((store) => store.notices);
|
||||
@@ -86,7 +87,7 @@ function Notice() {
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
const dontShowAgain = window.confirm(
|
||||
"Don't show again on this device?"
|
||||
strings.dontShowAgainConfirm()
|
||||
);
|
||||
dismissNotices(notice);
|
||||
if (dontShowAgain) {
|
||||
|
||||
@@ -38,6 +38,7 @@ import Field from "../field";
|
||||
import { LinkPlugin } from "./links-plugin";
|
||||
import Config from "../../utils/config";
|
||||
import { ErrorText } from "../error-text";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
export type PdfPreviewProps = {
|
||||
fileUrl: string | Uint8Array;
|
||||
@@ -118,7 +119,7 @@ export function PdfPreview(props: PdfPreviewProps) {
|
||||
{(props) => (
|
||||
<ToolbarButton
|
||||
icon={Search}
|
||||
title="Search"
|
||||
title={strings.search()}
|
||||
onClick={props.onClick}
|
||||
/>
|
||||
)}
|
||||
@@ -128,7 +129,7 @@ export function PdfPreview(props: PdfPreviewProps) {
|
||||
<ToolbarButton
|
||||
icon={ChevronUp}
|
||||
disabled={props.isDisabled}
|
||||
title="Go to previous page"
|
||||
title={strings.goToPreviousPage()}
|
||||
onClick={props.onClick}
|
||||
/>
|
||||
)}
|
||||
@@ -146,7 +147,7 @@ export function PdfPreview(props: PdfPreviewProps) {
|
||||
<ToolbarButton
|
||||
icon={ChevronDown}
|
||||
disabled={props.isDisabled}
|
||||
title="Go to next page"
|
||||
title={strings.goToNextPage()}
|
||||
onClick={props.onClick}
|
||||
/>
|
||||
)}
|
||||
@@ -165,7 +166,7 @@ export function PdfPreview(props: PdfPreviewProps) {
|
||||
{(props) => (
|
||||
<ToolbarButton
|
||||
icon={ZoomOut}
|
||||
title="Zoom out"
|
||||
title={strings.zoomOut()}
|
||||
onClick={props.onClick}
|
||||
/>
|
||||
)}
|
||||
@@ -181,7 +182,7 @@ export function PdfPreview(props: PdfPreviewProps) {
|
||||
{(props) => (
|
||||
<ToolbarButton
|
||||
icon={ZoomIn}
|
||||
title="Zoom in"
|
||||
title={strings.zoomIn()}
|
||||
onClick={props.onClick}
|
||||
/>
|
||||
)}
|
||||
@@ -200,7 +201,7 @@ export function PdfPreview(props: PdfPreviewProps) {
|
||||
{(props) => (
|
||||
<ToolbarButton
|
||||
icon={Download}
|
||||
title="Download"
|
||||
title={strings.network.download()}
|
||||
onClick={props.onClick}
|
||||
/>
|
||||
)}
|
||||
@@ -209,7 +210,7 @@ export function PdfPreview(props: PdfPreviewProps) {
|
||||
{(props) => (
|
||||
<ToolbarButton
|
||||
icon={Fullscreen}
|
||||
title="Enter fullscreen"
|
||||
title={strings.enterFullScreen()}
|
||||
onClick={props.onClick}
|
||||
/>
|
||||
)}
|
||||
@@ -218,7 +219,7 @@ export function PdfPreview(props: PdfPreviewProps) {
|
||||
{onClose && (
|
||||
<ToolbarButton
|
||||
icon={Close}
|
||||
title="Close"
|
||||
title={strings.close()}
|
||||
onClick={onClose}
|
||||
/>
|
||||
)}
|
||||
@@ -269,7 +270,7 @@ export function PdfPreview(props: PdfPreviewProps) {
|
||||
variant="heading"
|
||||
sx={{ fontSize: 28, textAlign: "center" }}
|
||||
>
|
||||
Unlock document
|
||||
{strings.pdfLocked()}
|
||||
</Text>
|
||||
</Flex>
|
||||
<Text
|
||||
@@ -278,13 +279,13 @@ export function PdfPreview(props: PdfPreviewProps) {
|
||||
mb={4}
|
||||
sx={{ textAlign: "center", color: "paragraph" }}
|
||||
>
|
||||
Please enter the password to unlock this document.
|
||||
{strings.pdfLockedDesc()}.
|
||||
</Text>
|
||||
<Field
|
||||
id="document-password"
|
||||
autoFocus
|
||||
sx={{ width: "95%", maxWidth: 400 }}
|
||||
placeholder="Enter password"
|
||||
placeholder={strings.enterPassword()}
|
||||
type="password"
|
||||
onKeyUp={async (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key === "Enter") {
|
||||
@@ -293,7 +294,7 @@ export function PdfPreview(props: PdfPreviewProps) {
|
||||
}}
|
||||
/>
|
||||
{props.passwordStatus === PasswordStatus.WrongPassword && (
|
||||
<ErrorText error="Wrong password" />
|
||||
<ErrorText error={strings.passwordIncorrect()} />
|
||||
)}
|
||||
<Button
|
||||
mt={3}
|
||||
@@ -302,7 +303,7 @@ export function PdfPreview(props: PdfPreviewProps) {
|
||||
sx={{ borderRadius: 100, px: 30 }}
|
||||
onClick={async () => {}}
|
||||
>
|
||||
Unlock
|
||||
{strings.unlock()}
|
||||
</Button>
|
||||
</Flex>
|
||||
)}
|
||||
|
||||
@@ -22,6 +22,7 @@ import { TipContext, useTip } from "../../hooks/use-tip";
|
||||
import { Info, Sync } from "../icons";
|
||||
import { useStore as useAppStore } from "../../stores/app-store";
|
||||
import { toTitleCase } from "@notesnook/common";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
type PlaceholderProps = { context: TipContext; text?: string };
|
||||
function Placeholder(props: PlaceholderProps) {
|
||||
@@ -53,13 +54,13 @@ function Placeholder(props: PlaceholderProps) {
|
||||
>
|
||||
<Sync color="accent" size={12} sx={{ mr: "small" }} />
|
||||
<Text variant="subBody" sx={{ fontSize: 10 }} color="accent">
|
||||
Syncing your {context}
|
||||
{strings.syncingYour(context)}
|
||||
</Text>
|
||||
</Flex>
|
||||
|
||||
<Text variant="subBody" sx={{ fontSize: "body", mt: 1 }}>
|
||||
{toTitleCase(syncStatus.type || "sync")}ing {syncStatus.progress}{" "}
|
||||
items
|
||||
{strings.items()}
|
||||
</Text>
|
||||
</Flex>
|
||||
);
|
||||
@@ -88,7 +89,7 @@ function Placeholder(props: PlaceholderProps) {
|
||||
>
|
||||
<Info color="accent" size={13} sx={{ mr: "small" }} />
|
||||
<Text variant="subBody" sx={{ fontSize: 10 }} color="accent">
|
||||
TIP
|
||||
{strings.tip()}
|
||||
</Text>
|
||||
</Flex>
|
||||
<Text variant="subBody" sx={{ fontSize: "body", mt: 1 }}>
|
||||
|
||||
@@ -62,6 +62,7 @@ import {
|
||||
import { VirtualizedTable } from "../virtualized-table";
|
||||
import { TextSlice } from "@notesnook/core";
|
||||
import { TITLE_BAR_HEIGHT } from "../title-bar";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
const tools = [
|
||||
{ key: "pin", property: "pinned", icon: Pin, label: "Pin" },
|
||||
@@ -152,7 +153,7 @@ function EditorProperties(props: EditorPropertiesProps) {
|
||||
>
|
||||
<ScrollContainer>
|
||||
<Section
|
||||
title="Properties"
|
||||
title={strings.properties()}
|
||||
button={
|
||||
<ArrowLeft
|
||||
data-test-id="properties-close"
|
||||
@@ -277,8 +278,8 @@ function InternalLinks({ noteId }: { noteId: string }) {
|
||||
(result.value.length === 0 ? (
|
||||
<Text variant="body" mx={1}>
|
||||
{tabIndex === InternalLinksTabs.LINKED_NOTES
|
||||
? "This note does not link to other notes."
|
||||
: "This note is not referenced in other notes."}
|
||||
? strings.notLinked()
|
||||
: strings.notReferenced()}
|
||||
</Text>
|
||||
) : (
|
||||
<VirtualizedList
|
||||
@@ -597,7 +598,7 @@ function Notebooks({ noteId }: { noteId: string }) {
|
||||
if (result.status !== "fulfilled" || result.value.length <= 0) return null;
|
||||
|
||||
return (
|
||||
<Section title="Notebooks">
|
||||
<Section title={strings.notebooks()}>
|
||||
<VirtualizedList
|
||||
mode="fixed"
|
||||
estimatedSize={50}
|
||||
@@ -626,7 +627,7 @@ function Reminders({ noteId }: { noteId: string }) {
|
||||
if (result.status !== "fulfilled" || result.value.length <= 0) return null;
|
||||
|
||||
return (
|
||||
<Section title="Reminders">
|
||||
<Section title={strings.dataTypesPluralCamelCase.reminder()}>
|
||||
<VirtualizedList
|
||||
mode="fixed"
|
||||
estimatedSize={54}
|
||||
@@ -654,7 +655,7 @@ function Attachments({ noteId }: { noteId: string }) {
|
||||
if (result.status !== "fulfilled" || result.value.length <= 0) return null;
|
||||
|
||||
return (
|
||||
<Section title="Attachments">
|
||||
<Section title={strings.dataTypesPluralCamelCase.attachment()}>
|
||||
<VirtualizedTable
|
||||
estimatedSize={30}
|
||||
getItemKey={(index) => result.value.key(index)}
|
||||
@@ -688,8 +689,8 @@ function SessionHistory({ noteId }: { noteId: string }) {
|
||||
|
||||
return (
|
||||
<Section
|
||||
title="Previous Sessions"
|
||||
subtitle={"Your session history is local only."}
|
||||
title={strings.noteHistory()}
|
||||
subtitle={strings.noteHistoryNotice[0]()}
|
||||
>
|
||||
<VirtualizedList
|
||||
mode="dynamic"
|
||||
|
||||
@@ -32,6 +32,7 @@ import { useStore } from "../../stores/monograph-store";
|
||||
import ReactModal from "react-modal";
|
||||
import { DialogButton } from "../dialog";
|
||||
import { Note } from "@notesnook/core";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
type PublishViewProps = {
|
||||
note: Note;
|
||||
@@ -98,10 +99,10 @@ function PublishView(props: PublishViewProps) {
|
||||
justifyContent: "center"
|
||||
}}
|
||||
>
|
||||
<Text>Please wait...</Text>
|
||||
<Text>{strings.pleaseWait()}...</Text>
|
||||
{processingStatus && (
|
||||
<Text variant="subBody" mt={1}>
|
||||
Downloading images ({processingStatus.current}/
|
||||
{strings.downloadingImages()} ({processingStatus.current}/
|
||||
{processingStatus.total})
|
||||
</Text>
|
||||
)}
|
||||
@@ -114,7 +115,7 @@ function PublishView(props: PublishViewProps) {
|
||||
variant="body"
|
||||
sx={{ fontWeight: "bold", color: "paragraph" }}
|
||||
>
|
||||
Published at
|
||||
{strings.publishedAt()}
|
||||
</Text>
|
||||
<Flex
|
||||
sx={{
|
||||
@@ -154,20 +155,18 @@ function PublishView(props: PublishViewProps) {
|
||||
</Flex>
|
||||
) : (
|
||||
<Text variant="body" sx={{ color: "paragraph" }}>
|
||||
This note will be published to a public URL.
|
||||
{strings.monographDesc()}
|
||||
</Text>
|
||||
)}
|
||||
<Toggle
|
||||
title="Self destruct?"
|
||||
onTip="Note will be automatically unpublished after first view."
|
||||
offTip="Note will stay published until manually unpublished."
|
||||
title={strings.monographSelfDestructHeading()}
|
||||
tip={strings.monographSelfDestructDesc()}
|
||||
isToggled={selfDestruct}
|
||||
onToggled={() => setSelfDestruct((s) => !s)}
|
||||
/>
|
||||
<Toggle
|
||||
title="Password protect?"
|
||||
onTip="Protect published note with a password."
|
||||
offTip="Do not protect published note with a password."
|
||||
title={strings.monographPassHeading()}
|
||||
tip={strings.monographPassDesc()}
|
||||
isToggled={isPasswordProtected}
|
||||
onToggled={() => setIsPasswordProtected((s) => !s)}
|
||||
/>
|
||||
@@ -177,7 +176,7 @@ function PublishView(props: PublishViewProps) {
|
||||
autoFocus
|
||||
type="password"
|
||||
id="publishPassword"
|
||||
placeholder="Enter password to encrypt this note"
|
||||
placeholder={strings.enterPassword()}
|
||||
required
|
||||
sx={{ my: 1 }}
|
||||
/>
|
||||
@@ -204,19 +203,19 @@ function PublishView(props: PublishViewProps) {
|
||||
password
|
||||
});
|
||||
setPublishId(publishId);
|
||||
showToast("success", "Note published.");
|
||||
showToast("success", strings.notePublished());
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
showToast(
|
||||
"error",
|
||||
"Note could not be published: " + (e as Error).message
|
||||
`${strings.failedToPublish()}: ${(e as Error).message}`
|
||||
);
|
||||
} finally {
|
||||
setIsPublishing(false);
|
||||
}
|
||||
}}
|
||||
loading={isPublishing}
|
||||
text={publishId ? "Update" : "Publish"}
|
||||
text={publishId ? strings.update() : strings.publish()}
|
||||
/>
|
||||
{publishId && (
|
||||
<DialogButton
|
||||
|
||||
@@ -25,6 +25,7 @@ import { useStore as useSearchStore } from "../../stores/search-store";
|
||||
import useMobile from "../../hooks/use-mobile";
|
||||
import { debounce, usePromise } from "@notesnook/common";
|
||||
import Field from "../field";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
export type RouteContainerButtons = {
|
||||
search?: {
|
||||
@@ -82,7 +83,7 @@ function Header(props: RouteContainerProps) {
|
||||
sx={{ m: 0, flex: 1 }}
|
||||
styles={{ input: { p: "7px" } }}
|
||||
defaultValue={query}
|
||||
placeholder="Type your query here"
|
||||
placeholder={strings.typeAKeyword()}
|
||||
onChange={debounce(
|
||||
(e) => useSearchStore.setState({ query: e.target.value }),
|
||||
250
|
||||
|
||||
@@ -23,6 +23,7 @@ import { Flex, Text } from "@theme-ui/components";
|
||||
import TimeAgo from "../time-ago";
|
||||
import { Lock } from "../icons";
|
||||
import { useEditorStore } from "../../stores/editor-store";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
type SessionItemProps = {
|
||||
session: HistorySession;
|
||||
@@ -47,7 +48,7 @@ export function SessionItem(props: SessionItemProps) {
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between"
|
||||
}}
|
||||
title="Click to preview"
|
||||
title={strings.clickToPreview()}
|
||||
onClick={() =>
|
||||
useEditorStore.getState().openDiffSession(noteId, session.id)
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ import { checkForUpdate, installUpdate } from "../../utils/updater";
|
||||
import { getTimeAgo, toTitleCase } from "@notesnook/common";
|
||||
import { User } from "@notesnook/core";
|
||||
import { showUpdateAvailableNotice } from "../../dialogs/confirm";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
function StatusBar() {
|
||||
const user = useUserStore((state) => state.user);
|
||||
@@ -84,7 +85,7 @@ function StatusBar() {
|
||||
>
|
||||
<Circle size={7} color={"var(--icon-error)"} />
|
||||
<Text variant="subBody" ml={1} sx={{ color: "paragraph" }}>
|
||||
Email not confirmed
|
||||
{strings.emailNotConfirmed()}
|
||||
</Text>
|
||||
</Button>
|
||||
)}
|
||||
@@ -104,7 +105,7 @@ function StatusBar() {
|
||||
>
|
||||
<Circle size={7} color="var(--icon-error)" />
|
||||
<Text variant="subBody" ml={1} sx={{ color: "paragraph" }}>
|
||||
Not logged in
|
||||
{strings.notLoggedIn()}
|
||||
</Text>
|
||||
</Button>
|
||||
) : null}
|
||||
@@ -171,13 +172,15 @@ export default StatusBar;
|
||||
function statusToInfoText(status: UpdateStatus) {
|
||||
const { type } = status;
|
||||
return type === "checking"
|
||||
? "Checking for updates..."
|
||||
? strings.checkingForUpdates()
|
||||
: type === "downloading"
|
||||
? `${Math.round(status.progress)}% updating...`
|
||||
? `${Math.round(status.progress)}% ${strings.updating()}...`
|
||||
: type === "completed"
|
||||
? `v${status.version} downloaded (restart required)`
|
||||
? `v${
|
||||
status.version
|
||||
} ${strings.network.downloaded()} (${strings.restartRequired()})`
|
||||
: type === "available"
|
||||
? `v${status.version} available`
|
||||
? `v${status.version} ${strings.available()}`
|
||||
: "";
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import { alpha } from "@theme-ui/color";
|
||||
import { Flex, Text } from "@theme-ui/components";
|
||||
import { Circle, Notes, Notebook, StarOutline, Tag, Plus } from "../icons";
|
||||
import { ThemeMetadata } from "@notesnook/themes-server";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
export type ThemePreviewProps = {
|
||||
theme: ThemeMetadata;
|
||||
@@ -124,7 +125,7 @@ export function ThemePreview(props: ThemePreviewProps) {
|
||||
fontSize: 7
|
||||
}}
|
||||
>
|
||||
Notes
|
||||
{strings.dataTypesPluralCamelCase.note()}
|
||||
</Text>
|
||||
<Plus
|
||||
color={theme.previewColors.list.accentForeground}
|
||||
|
||||
@@ -34,7 +34,8 @@ function Toggle(props) {
|
||||
onlyIf,
|
||||
premium,
|
||||
testId,
|
||||
disabled
|
||||
disabled,
|
||||
tip
|
||||
} = props;
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const onClick = useCallback(async () => {
|
||||
@@ -68,7 +69,11 @@ function Toggle(props) {
|
||||
"& > label": { width: "auto", flexShrink: 0 }
|
||||
}}
|
||||
>
|
||||
<Tip text={title} tip={isToggled ? onTip : offTip} sx={{ mr: 2 }} />
|
||||
<Tip
|
||||
text={title}
|
||||
tip={tip ? tip : isToggled ? onTip : offTip}
|
||||
sx={{ mr: 2 }}
|
||||
/>
|
||||
{isLoading ? (
|
||||
<Loading size={18} />
|
||||
) : (
|
||||
|
||||
@@ -29,6 +29,7 @@ import { TrashItem as TrashItemType } from "@notesnook/core";
|
||||
import { useEditorStore } from "../../stores/editor-store";
|
||||
import { showMultiPermanentDeleteConfirmation } from "../../dialogs/confirm";
|
||||
import { useStore as useSelectionStore } from "../../stores/selection-store";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
type TrashItemProps = { item: TrashItemType; date: number };
|
||||
function TrashItem(props: TrashItemProps) {
|
||||
@@ -78,18 +79,18 @@ const menuItems: (item: TrashItemType, ids?: string[]) => MenuItem[] = (
|
||||
{
|
||||
type: "button",
|
||||
key: "restore",
|
||||
title: "Restore",
|
||||
title: strings.restore(),
|
||||
icon: Restore.path,
|
||||
onClick: async () => {
|
||||
await store.restore(...ids);
|
||||
showToast("success", `${pluralize(ids.length, "item")} restored`);
|
||||
showToast("success", strings.itemsRestored(ids.length));
|
||||
},
|
||||
multiSelect: true
|
||||
},
|
||||
{
|
||||
type: "button",
|
||||
key: "delete",
|
||||
title: "Delete",
|
||||
title: strings.delete(),
|
||||
icon: DeleteForver.path,
|
||||
variant: "dangerous",
|
||||
onClick: async () => {
|
||||
|
||||
@@ -23,6 +23,7 @@ import { Lock } from "../icons";
|
||||
import Field from "../field";
|
||||
import { showToast } from "../../utils/toast";
|
||||
import { ErrorText } from "../error-text";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
type UnlockViewProps = {
|
||||
title: string;
|
||||
@@ -104,7 +105,7 @@ export function UnlockView(props: UnlockViewProps) {
|
||||
inputRef={passwordRef}
|
||||
autoFocus
|
||||
sx={{ width: ["95%", "95%", "max(30%, 400px)"] }}
|
||||
placeholder="Enter password"
|
||||
placeholder={strings.enterPassword()}
|
||||
type="password"
|
||||
onKeyUp={async (e) => {
|
||||
if (e.key === "Enter") {
|
||||
@@ -125,7 +126,7 @@ export function UnlockView(props: UnlockViewProps) {
|
||||
await submit();
|
||||
}}
|
||||
>
|
||||
{isUnlocking ? "Unlocking..." : buttonTitle}
|
||||
{isUnlocking ? strings.unlocking() + "..." : buttonTitle}
|
||||
</Button>
|
||||
</Flex>
|
||||
);
|
||||
|
||||
@@ -27,6 +27,7 @@ import { store as notebookStore } from "../stores/notebook-store";
|
||||
import { store as appStore } from "../stores/app-store";
|
||||
import { db } from "../common/db";
|
||||
import { BaseDialogProps, DialogManager } from "../common/dialog-manager";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
type AddNotebookDialogProps = BaseDialogProps<boolean> & {
|
||||
parentId?: string;
|
||||
@@ -92,7 +93,7 @@ export const AddNotebookDialog = DialogManager.register(
|
||||
data-test-id="title-input"
|
||||
autoFocus
|
||||
required
|
||||
label="Title"
|
||||
label={strings.title()}
|
||||
name="title"
|
||||
id="title"
|
||||
onChange={(e) => (title.current = e.target.value)}
|
||||
@@ -104,12 +105,12 @@ export const AddNotebookDialog = DialogManager.register(
|
||||
/>
|
||||
<Field
|
||||
data-test-id="description-input"
|
||||
label="Description"
|
||||
label={strings.description()}
|
||||
name="description"
|
||||
id="description"
|
||||
onChange={(e) => (description.current = e.target.value)}
|
||||
defaultValue={description.current}
|
||||
helpText="Optional"
|
||||
helpText={strings.optional()}
|
||||
sx={{ mt: 1 }}
|
||||
/>
|
||||
</Dialog>
|
||||
|
||||
@@ -37,6 +37,7 @@ import { getFormattedDate } from "@notesnook/common";
|
||||
import { MONTHS_FULL, getTimeFormat } from "@notesnook/core";
|
||||
import { Note, Reminder } from "@notesnook/core";
|
||||
import { BaseDialogProps, DialogManager } from "../common/dialog-manager";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
dayjs.extend(customParseFormat);
|
||||
|
||||
@@ -213,7 +214,7 @@ export const AddReminderDialog = DialogManager.register(
|
||||
>
|
||||
<Field
|
||||
id="title"
|
||||
label="Title"
|
||||
label={strings.title()}
|
||||
required
|
||||
value={title}
|
||||
data-test-id="title-input"
|
||||
@@ -224,9 +225,9 @@ export const AddReminderDialog = DialogManager.register(
|
||||
<Field
|
||||
as="textarea"
|
||||
id="description"
|
||||
label="Description"
|
||||
label={strings.description()}
|
||||
data-test-id="description-input"
|
||||
helpText="Optional"
|
||||
helpText={strings.optional()}
|
||||
value={description}
|
||||
styles={{
|
||||
input: {
|
||||
@@ -367,7 +368,7 @@ export const AddReminderDialog = DialogManager.register(
|
||||
<>
|
||||
<Field
|
||||
id="date"
|
||||
label="Date"
|
||||
label={strings.date()}
|
||||
required
|
||||
inputRef={dateInputRef}
|
||||
data-test-id="date-input"
|
||||
@@ -438,7 +439,7 @@ export const AddReminderDialog = DialogManager.register(
|
||||
<>
|
||||
<LabeledSelect
|
||||
id="month"
|
||||
label="Month"
|
||||
label={strings.month()}
|
||||
value={`${dayjs(date).month()}`}
|
||||
options={MONTHS_FULL.map((month, index) => ({
|
||||
value: `${index}`,
|
||||
@@ -450,7 +451,7 @@ export const AddReminderDialog = DialogManager.register(
|
||||
/>
|
||||
<LabeledSelect
|
||||
id="day"
|
||||
label="Day"
|
||||
label={strings.day()}
|
||||
value={`${dayjs(date).date()}`}
|
||||
options={new Array(dayjs(date).daysInMonth())
|
||||
.fill("0")
|
||||
@@ -466,7 +467,7 @@ export const AddReminderDialog = DialogManager.register(
|
||||
) : null}
|
||||
<Field
|
||||
id="time"
|
||||
label="Time"
|
||||
label={strings.time()}
|
||||
required
|
||||
data-test-id="time-input"
|
||||
helpText={`${
|
||||
@@ -514,20 +515,20 @@ export const AddReminderDialog = DialogManager.register(
|
||||
<Text variant="subBody" sx={{ mt: 1 }}>
|
||||
{selectedDays.length === 0 && recurringMode !== RecurringModes.DAY
|
||||
? recurringMode === RecurringModes.WEEK
|
||||
? "Select day of the week to repeat the reminder."
|
||||
: "Select nth day(s) of the month to repeat the reminder."
|
||||
? strings.reminderRepeatStrings.week.selectDays()
|
||||
: strings.reminderRepeatStrings.month.selectDays()
|
||||
: repeatsDaily
|
||||
? `Repeats daily at ${date.format(timeFormat())}.`
|
||||
: `Repeats every ${recurringMode} on ${getSelectedDaysText(
|
||||
selectedDays,
|
||||
recurringMode
|
||||
)} at ${date.format(timeFormat())}.`}
|
||||
? strings.reminderRepeatStrings.day(date.format(timeFormat()))
|
||||
: strings.reminderRepeatStrings.repeats(
|
||||
1,
|
||||
recurringMode,
|
||||
getSelectedDaysText(selectedDays, recurringMode),
|
||||
date.format(timeFormat())
|
||||
)}
|
||||
</Text>
|
||||
) : (
|
||||
<Text variant="subBody" sx={{ mt: 1 }}>
|
||||
{`The reminder will start on ${date} at ${date.format(
|
||||
timeFormat()
|
||||
)}.`}
|
||||
{strings.reminderStarts(date.toString(), date.format(timeFormat()))}
|
||||
</Text>
|
||||
)}
|
||||
</Dialog>
|
||||
|
||||
@@ -35,6 +35,7 @@ import { create } from "zustand";
|
||||
import { VirtualizedGrouping } from "@notesnook/core";
|
||||
import { ResolvedItem } from "@notesnook/common";
|
||||
import { BaseDialogProps, DialogManager } from "../common/dialog-manager";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
type SelectedReference = {
|
||||
id: string;
|
||||
@@ -118,8 +119,8 @@ export const AddTagsDialog = DialogManager.register(function AddTagsDialog(
|
||||
sx={{ mt: 2 }}
|
||||
itemGap={5}
|
||||
placeholders={{
|
||||
empty: "Add a new tag",
|
||||
filter: "Search or add a new tag"
|
||||
empty: strings.addATag(),
|
||||
filter: strings.searchForTags()
|
||||
}}
|
||||
filter={async (query) => {
|
||||
setTags(
|
||||
|
||||
@@ -67,6 +67,7 @@ import { BaseDialogProps, DialogManager } from "../common/dialog-manager";
|
||||
import { ConfirmDialog } from "./confirm";
|
||||
import { showToast } from "../utils/toast";
|
||||
import { Loader } from "../components/loader";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
type ToolbarAction = {
|
||||
title: string;
|
||||
@@ -346,7 +347,7 @@ export const AttachmentsDialog = DialogManager.register(
|
||||
renderRow={AttachmentRow}
|
||||
/>
|
||||
) : (
|
||||
<Loader title="Loading attachments..." />
|
||||
<Loader title={strings.loading() + "..."} />
|
||||
)}
|
||||
</FlexScrollContainer>
|
||||
</Flex>
|
||||
@@ -461,7 +462,7 @@ const Sidebar = memo(
|
||||
<Input
|
||||
id="search"
|
||||
name="search"
|
||||
placeholder="Search"
|
||||
placeholder={strings.search()}
|
||||
sx={{ m: 2, mb: 0, width: "auto", bg: "background", py: "7px" }}
|
||||
onChange={(e) => {
|
||||
setRoute(e.target.value ? "none" : "all");
|
||||
@@ -485,7 +486,7 @@ const Sidebar = memo(
|
||||
<Flex sx={{ flexDirection: "column" }}>
|
||||
<Flex sx={{ pl: 2, m: 2, mt: 1, justifyContent: "space-between" }}>
|
||||
<Flex sx={{ flexDirection: "column" }}>
|
||||
<Text variant="body">{pluralize(counts.all, "file")}</Text>
|
||||
<Text variant="body">{strings.files(counts.all)}</Text>
|
||||
{result.status === "fulfilled" && (
|
||||
<Text variant="subBody">
|
||||
{formatBytes(result.value || 0)}
|
||||
@@ -503,28 +504,18 @@ const Sidebar = memo(
|
||||
height: 38
|
||||
}}
|
||||
disabled={!!downloadStatus}
|
||||
title="Clear cache"
|
||||
title={strings.clearCache()}
|
||||
onClick={async () => {
|
||||
if (
|
||||
await ConfirmDialog.show({
|
||||
title: "Clear attachments cache?",
|
||||
message: `Clearing attachments cache will perform the following actions:
|
||||
|
||||
- Downloaded images & files: **cleared**
|
||||
- Pending uploads: **cleared**
|
||||
- Uploaded images & files: _unaffected_
|
||||
|
||||
All attachments will be downloaded & cached again on access.
|
||||
|
||||
---
|
||||
|
||||
**Only use this for troubleshooting purposes. If you are having persistent issues, it is recommended that you reach out to us via support@streetwriters.co so we can help you resolve it permanently.**`,
|
||||
title: strings.clearCacheConfirm(),
|
||||
message: strings.clearCacheConfirmDesc(),
|
||||
negativeButtonText: "No",
|
||||
positiveButtonText: "Yes"
|
||||
})
|
||||
) {
|
||||
await db.fs().clear();
|
||||
showToast("success", "Attachments cache cleared!");
|
||||
showToast("success", strings.cacheCleared());
|
||||
}
|
||||
}}
|
||||
>
|
||||
@@ -539,7 +530,7 @@ All attachments will be downloaded & cached again on access.
|
||||
width: 38,
|
||||
height: 38
|
||||
}}
|
||||
title="Download all attachments"
|
||||
title={strings.downloadAllAttachments()}
|
||||
onClick={async () => {
|
||||
if (downloadStatus) {
|
||||
await cancelDownload();
|
||||
|
||||
@@ -22,6 +22,7 @@ import Field from "../components/field";
|
||||
import Dialog from "../components/dialog";
|
||||
import { Box, Button, Text } from "@theme-ui/components";
|
||||
import { BaseDialogProps, DialogManager } from "../common/dialog-manager";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
export type BackupPasswordDialogProps = BaseDialogProps<boolean> & {
|
||||
validate: (outputs: {
|
||||
@@ -88,7 +89,7 @@ export const BackupPasswordDialog = DialogManager.register(
|
||||
required
|
||||
autoFocus
|
||||
data-test-id="encryption-key"
|
||||
label="Encryption key"
|
||||
label={strings.encryptionKey()}
|
||||
type="password"
|
||||
id="key"
|
||||
name="key"
|
||||
@@ -98,7 +99,7 @@ export const BackupPasswordDialog = DialogManager.register(
|
||||
required
|
||||
autoFocus
|
||||
data-test-id="password"
|
||||
label="Password"
|
||||
label={strings.password()}
|
||||
type="password"
|
||||
autoComplete="current-password"
|
||||
id="password"
|
||||
@@ -107,9 +108,7 @@ export const BackupPasswordDialog = DialogManager.register(
|
||||
)}
|
||||
</Box>
|
||||
<Button variant="anchor" onClick={() => setIsEncryptionKey((s) => !s)}>
|
||||
{isEncryptionKey
|
||||
? "Don't have encryption key? Use password."
|
||||
: "Forgot password? Use encryption key."}
|
||||
{strings.useEncryptionKey()}
|
||||
</Button>
|
||||
|
||||
{error && (
|
||||
|
||||
@@ -68,6 +68,7 @@ import {
|
||||
CustomToolbar,
|
||||
SyncOff
|
||||
} from "../../components/icons";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
type Feature = {
|
||||
id: string;
|
||||
@@ -458,7 +459,7 @@ export function Features() {
|
||||
>
|
||||
<Pro color="accent" size={16} />
|
||||
<Text variant="body" ml={"2px"} sx={{ color: "accent" }}>
|
||||
Pro
|
||||
{strings.pro()}
|
||||
</Text>
|
||||
</Flex>
|
||||
)}
|
||||
@@ -499,7 +500,7 @@ export function Features() {
|
||||
ml={"2px"}
|
||||
sx={{ color: "accent" }}
|
||||
>
|
||||
Pro
|
||||
{strings.pro()}
|
||||
</Text>
|
||||
</Flex>
|
||||
)}
|
||||
|
||||
@@ -25,6 +25,7 @@ import tinycolor from "tinycolor2";
|
||||
import { db } from "../common/db";
|
||||
import { showToast } from "../utils/toast";
|
||||
import { BaseDialogProps, DialogManager } from "../common/dialog-manager";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
type CreateColorDialogProps = BaseDialogProps<string | false>;
|
||||
export const CreateColorDialog = DialogManager.register(
|
||||
@@ -68,7 +69,7 @@ export const CreateColorDialog = DialogManager.register(
|
||||
>
|
||||
<Field
|
||||
required
|
||||
label="Title"
|
||||
label={strings.title()}
|
||||
id="title"
|
||||
name="title"
|
||||
autoFocus
|
||||
@@ -78,7 +79,7 @@ export const CreateColorDialog = DialogManager.register(
|
||||
<Field
|
||||
inputRef={colorRef}
|
||||
required
|
||||
label="Color"
|
||||
label={strings.color()}
|
||||
id="color"
|
||||
name="color"
|
||||
data-test-id="color-input"
|
||||
|
||||
@@ -28,6 +28,7 @@ import { db } from "../common/db";
|
||||
import { showToast } from "../utils/toast";
|
||||
import { useStore as useSettingStore } from "../stores/setting-store";
|
||||
import { BaseDialogProps, DialogManager } from "../common/dialog-manager";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
export type EditProfilePictureDialogProps = BaseDialogProps<boolean> & {
|
||||
profile?: Profile;
|
||||
@@ -129,8 +130,8 @@ export const EditProfilePictureDialog = DialogManager.register(
|
||||
}}
|
||||
>
|
||||
{profilePicture
|
||||
? "Change profile picture"
|
||||
: "Select profile picture"}
|
||||
? strings.changeProfilePicture()
|
||||
: strings.selectProfilePicture()}
|
||||
</Button>
|
||||
{profilePicture ? (
|
||||
<Button
|
||||
@@ -144,7 +145,9 @@ export const EditProfilePictureDialog = DialogManager.register(
|
||||
setScale(1);
|
||||
}}
|
||||
>
|
||||
{typeof profilePicture === "string" ? "Clear" : "Reset"}
|
||||
{typeof profilePicture === "string"
|
||||
? strings.clear()
|
||||
: strings.reset()}
|
||||
</Button>
|
||||
) : null}
|
||||
</Flex>
|
||||
|
||||
@@ -25,6 +25,7 @@ import Field from "../components/field";
|
||||
import { Loading } from "../components/icons";
|
||||
import Dialog from "../components/dialog";
|
||||
import { BaseDialogProps, DialogManager } from "../common/dialog-manager";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
type EmailChangeState = {
|
||||
newEmail: string;
|
||||
@@ -128,8 +129,8 @@ export const EmailChangeDialog = DialogManager.register(
|
||||
inputRef={codeRef}
|
||||
id="code"
|
||||
name="code"
|
||||
label="6-digit code"
|
||||
helpText={`Enter 6-digit code sent to ${emailChangeState.newEmail}`}
|
||||
label={strings.sixDigitCode()}
|
||||
helpText={strings.enterSixDigitCode()}
|
||||
type="text"
|
||||
required
|
||||
action={{
|
||||
@@ -139,9 +140,9 @@ export const EmailChangeDialog = DialogManager.register(
|
||||
{isSending ? (
|
||||
<Loading size={18} />
|
||||
) : enabled ? (
|
||||
`Resend code`
|
||||
strings.resendCode()
|
||||
) : (
|
||||
`Resend in ${elapsed}`
|
||||
strings.resendCode(elapsed)
|
||||
)}
|
||||
</Text>
|
||||
),
|
||||
@@ -156,7 +157,7 @@ export const EmailChangeDialog = DialogManager.register(
|
||||
inputRef={emailRef}
|
||||
id="newEmail"
|
||||
name="newEmail"
|
||||
label="New email"
|
||||
label={strings.newEmail()}
|
||||
type="email"
|
||||
required
|
||||
/>
|
||||
@@ -164,12 +165,12 @@ export const EmailChangeDialog = DialogManager.register(
|
||||
inputRef={passwordRef}
|
||||
id="password"
|
||||
name="password"
|
||||
label="Your account password"
|
||||
label={strings.accountPassword()}
|
||||
type="password"
|
||||
required
|
||||
/>
|
||||
<Text variant="subBody" sx={{ mt: 1 }}>
|
||||
You will be logged out from all your other devices.
|
||||
{strings.changeEmailNotice()}
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -26,6 +26,7 @@ import { useState } from "react";
|
||||
import { useSessionState } from "../hooks/use-session-state";
|
||||
import Accordion from "../components/accordion";
|
||||
import { BaseDialogProps, DialogManager } from "../common/dialog-manager";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
let interval = 0;
|
||||
type EmailVerificationDialogProps = BaseDialogProps<boolean>;
|
||||
@@ -98,11 +99,7 @@ export const EmailVerificationDialog = DialogManager.register(
|
||||
variant="body"
|
||||
sx={{ borderRadius: "default", alignSelf: "stretch" }}
|
||||
>
|
||||
We have sent the email confirmation link at{" "}
|
||||
<Text as="b" sx={{ color: "accent" }}>
|
||||
{user.email}
|
||||
</Text>
|
||||
.
|
||||
{strings.emailConfirmationLinkSent()}
|
||||
</Text>
|
||||
<Accordion
|
||||
isClosed
|
||||
@@ -114,10 +111,9 @@ export const EmailVerificationDialog = DialogManager.register(
|
||||
}}
|
||||
>
|
||||
<Text variant={"body"} px={1} pb={1}>
|
||||
{`If you didn't get an email from us or the confirmation link isn't
|
||||
working,`}{" "}
|
||||
<b>please send us an email from your registered email address</b>{" "}
|
||||
and we will manually confirm your account.
|
||||
{strings.emailConfirmationNotice()[0]}{" "}
|
||||
<b>{strings.emailConfirmationNotice()[1]}</b>{" "}
|
||||
{strings.emailConfirmationNotice()[2]}.
|
||||
</Text>
|
||||
</Accordion>
|
||||
</Flex>
|
||||
|
||||
@@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Flex, Text } from "@theme-ui/components";
|
||||
import { Flex, Link, Text } from "@theme-ui/components";
|
||||
import { appVersion } from "../utils/version";
|
||||
import Field from "../components/field";
|
||||
import Dialog from "../components/dialog";
|
||||
@@ -31,17 +31,11 @@ import { ErrorText } from "../components/error-text";
|
||||
import { Debug } from "@notesnook/core";
|
||||
import { ConfirmDialog } from "./confirm";
|
||||
import { BaseDialogProps, DialogManager } from "../common/dialog-manager";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
const PLACEHOLDERS = {
|
||||
title: "Briefly describe what happened",
|
||||
body: `Tell us more about the issue you are facing.
|
||||
|
||||
For example things like:
|
||||
1. Steps to reproduce the issue
|
||||
2. Things you have tried so far
|
||||
3. etc.
|
||||
|
||||
This is all optional, of course.`
|
||||
title: strings.issueTitlePlaceholder(),
|
||||
body: strings.issuePlaceholder()
|
||||
};
|
||||
|
||||
type IssueDialogProps = BaseDialogProps<boolean>;
|
||||
@@ -54,15 +48,18 @@ export const IssueDialog = DialogManager.register(function IssueDialog(
|
||||
return (
|
||||
<Dialog
|
||||
isOpen={true}
|
||||
title={"Report an issue"}
|
||||
title={strings.reportAnIssue()}
|
||||
onClose={() => props.onClose(false)}
|
||||
positiveButton={{
|
||||
text: "Submit",
|
||||
text: strings.submit(),
|
||||
form: "issueForm",
|
||||
loading: isSubmitting,
|
||||
disabled: isSubmitting
|
||||
}}
|
||||
negativeButton={{ text: "Cancel", onClick: () => props.onClose(false) }}
|
||||
negativeButton={{
|
||||
text: strings.cancel(),
|
||||
onClick: () => props.onClose(false)
|
||||
}}
|
||||
>
|
||||
<Flex
|
||||
id="issueForm"
|
||||
@@ -99,7 +96,7 @@ export const IssueDialog = DialogManager.register(function IssueDialog(
|
||||
>
|
||||
<Field
|
||||
required
|
||||
label="Title"
|
||||
label={strings.title()}
|
||||
id="title"
|
||||
name="title"
|
||||
placeholder={PLACEHOLDERS.title}
|
||||
@@ -109,7 +106,7 @@ export const IssueDialog = DialogManager.register(function IssueDialog(
|
||||
as="textarea"
|
||||
required
|
||||
variant="forms.input"
|
||||
label="Description"
|
||||
label={strings.description()}
|
||||
id="body"
|
||||
name="body"
|
||||
placeholder={PLACEHOLDERS.body}
|
||||
@@ -127,8 +124,16 @@ export const IssueDialog = DialogManager.register(function IssueDialog(
|
||||
p={1}
|
||||
sx={{ borderRadius: "default" }}
|
||||
>
|
||||
Your bug report is public. Do NOT include sensitive information
|
||||
(email, passwords etc) in the issue title or description.
|
||||
{strings.issueNotice[0]()}{" "}
|
||||
<Link
|
||||
href="https://github.com/streetwriters/notesnook/issues"
|
||||
title="github.com/streetwriters/notesnook/issues"
|
||||
/>{" "}
|
||||
{strings.issueNotice[1]()}{" "}
|
||||
<Link
|
||||
href="https://discord.gg/zQBK97EE22"
|
||||
title={strings.issueNotice[2]()}
|
||||
/>
|
||||
</Text>
|
||||
<Text variant="subBody" mt={1}>
|
||||
{getDeviceInfo()
|
||||
|
||||
@@ -29,6 +29,7 @@ import { useStore as useTagStore } from "../stores/tag-store";
|
||||
import { useStore as useNoteStore } from "../stores/note-store";
|
||||
import { useStore as useAppStore } from "../stores/app-store";
|
||||
import { Color, Tag } from "@notesnook/core";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
type ItemDialogProps = BaseDialogProps<false | string> & {
|
||||
title: string;
|
||||
@@ -74,7 +75,7 @@ export const ItemDialog = DialogManager.register(function ItemDialog(
|
||||
>
|
||||
<Field
|
||||
required
|
||||
label="Title"
|
||||
label={strings.title()}
|
||||
id="title"
|
||||
name="title"
|
||||
autoFocus
|
||||
|
||||
@@ -58,6 +58,7 @@ import {
|
||||
import { ErrorText } from "../../components/error-text";
|
||||
import { AuthenticatorType } from "@notesnook/core";
|
||||
import { MultifactorDialog } from "./multi-factor-dialog";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
const QRCode = React.lazy(() => import("../../re-exports/react-qrcode-logo"));
|
||||
|
||||
@@ -292,7 +293,7 @@ function ChooseAuthenticator(props: ChooseAuthenticatorProps) {
|
||||
px={1}
|
||||
sx={{ borderRadius: "default", color: "accent" }}
|
||||
>
|
||||
Recommended
|
||||
{strings.recommended()}
|
||||
</Text>
|
||||
) : (
|
||||
false
|
||||
@@ -348,14 +349,10 @@ function SetupAuthenticatorApp(props: SetupAuthenticatorProps) {
|
||||
|
||||
return (
|
||||
<VerifyAuthenticatorForm
|
||||
codeHelpText={
|
||||
"After scanning the QR code image, the app will display a code that you can enter below."
|
||||
}
|
||||
codeHelpText={strings.mfaScanQrCodeHelpText()}
|
||||
onSubmitCode={onSubmitCode}
|
||||
>
|
||||
<Text variant={"body"}>
|
||||
Scan the QR code below with your authenticator app.
|
||||
</Text>
|
||||
<Text variant={"body"}>{strings.mfaScanQrCode()}</Text>
|
||||
<Box sx={{ alignSelf: "center" }}>
|
||||
{authenticatorDetails.authenticatorUri ? (
|
||||
<Suspense fallback={<Loading />}>
|
||||
@@ -369,10 +366,7 @@ function SetupAuthenticatorApp(props: SetupAuthenticatorProps) {
|
||||
<Loading />
|
||||
)}
|
||||
</Box>
|
||||
<Text variant={"subBody"}>
|
||||
{`If you can't scan the QR code above, enter this text instead (spaces
|
||||
don't matter):`}
|
||||
</Text>
|
||||
<Text variant={"subBody"}>{`${strings.scanQrError()}:`}</Text>
|
||||
<Flex
|
||||
bg="var(--background-secondary)"
|
||||
mt={2}
|
||||
@@ -433,9 +427,7 @@ function SetupEmail(props: SetupAuthenticatorProps) {
|
||||
|
||||
return (
|
||||
<VerifyAuthenticatorForm
|
||||
codeHelpText={
|
||||
"You will receive a 2FA code on your email address which you can enter below"
|
||||
}
|
||||
codeHelpText={strings.mfaEmailDesc()}
|
||||
onSubmitCode={onSubmitCode}
|
||||
>
|
||||
<Flex
|
||||
@@ -475,9 +467,9 @@ function SetupEmail(props: SetupAuthenticatorProps) {
|
||||
{isSending ? (
|
||||
<Loading size={18} />
|
||||
) : enabled ? (
|
||||
`Send code`
|
||||
strings.sendCode()
|
||||
) : (
|
||||
`Resend (${elapsed})`
|
||||
strings.resendCode(elapsed)
|
||||
)}
|
||||
</Button>
|
||||
</Flex>
|
||||
@@ -496,17 +488,15 @@ function SetupSMS(props: SetupAuthenticatorProps) {
|
||||
|
||||
return (
|
||||
<VerifyAuthenticatorForm
|
||||
codeHelpText={
|
||||
"You will receive a 2FA code on your phone number which you can enter below"
|
||||
}
|
||||
codeHelpText={strings.mfaSmsDesc()}
|
||||
onSubmitCode={onSubmitCode}
|
||||
>
|
||||
<Field
|
||||
inputRef={inputRef}
|
||||
id="phone-number"
|
||||
name="phone-number"
|
||||
helpText="Authentication codes will be sent to this number"
|
||||
label="Phone number"
|
||||
helpText={strings.mfaSmsDesc()}
|
||||
label={strings.phoneNumber()}
|
||||
sx={{ mt: 2 }}
|
||||
autoFocus
|
||||
required
|
||||
@@ -534,15 +524,15 @@ function SetupSMS(props: SetupAuthenticatorProps) {
|
||||
{isSending ? (
|
||||
<Loading size={18} />
|
||||
) : enabled ? (
|
||||
`Send code`
|
||||
strings.sendCode()
|
||||
) : (
|
||||
`Resend (${elapsed})`
|
||||
strings.resendCode(elapsed)
|
||||
)}
|
||||
</Text>
|
||||
),
|
||||
onClick: async () => {
|
||||
if (!phoneNumber) {
|
||||
setError("Please provide a phone number.");
|
||||
setError(strings.phoneNumberNotEntered());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -711,21 +701,21 @@ function TwoFactorEnabled(props: TwoFactorEnabledProps) {
|
||||
mt={2}
|
||||
sx={{ fontSize: "subheading", textAlign: "center" }}
|
||||
>
|
||||
Two-factor authentication enabled!
|
||||
{strings.twoFactorAuthEnabled()}
|
||||
</Text>
|
||||
<Text
|
||||
variant={"body"}
|
||||
mt={1}
|
||||
sx={{ textAlign: "center", color: "var(--paragraph-secondary)" }}
|
||||
>
|
||||
Your account is now 100% secure against unauthorized logins.
|
||||
{strings.mfaDone()}
|
||||
</Text>
|
||||
<Button
|
||||
mt={2}
|
||||
sx={{ borderRadius: 100, px: 6 }}
|
||||
onClick={() => props.onClose?.(true)}
|
||||
>
|
||||
Done
|
||||
{strings.done()}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
@@ -767,23 +757,21 @@ function Fallback2FAEnabled(props: Fallback2FAEnabledProps) {
|
||||
mt={2}
|
||||
sx={{ fontSize: "subheading", textAlign: "center" }}
|
||||
>
|
||||
Fallback 2FA method enabled!
|
||||
{strings.fallbackMethodEnabled()}
|
||||
</Text>
|
||||
<Text
|
||||
variant={"body"}
|
||||
mt={1}
|
||||
sx={{ textAlign: "center", color: "var(--paragraph-secondary)" }}
|
||||
>
|
||||
You will now receive your 2FA codes on your{" "}
|
||||
{mfaMethodToPhrase(fallbackMethod)} in case you lose access to your{" "}
|
||||
{mfaMethodToPhrase(primaryMethod)}.
|
||||
{strings.mfaFallbackMethodText(fallbackMethod, primaryMethod)}
|
||||
</Text>
|
||||
<Button
|
||||
mt={2}
|
||||
sx={{ borderRadius: 100, px: 6 }}
|
||||
onClick={() => onClose?.(true)}
|
||||
>
|
||||
Done
|
||||
{strings.done()}
|
||||
</Button>
|
||||
</Flex>
|
||||
);
|
||||
@@ -812,7 +800,7 @@ function VerifyAuthenticatorForm(props: VerifyAuthenticatorFormProps) {
|
||||
id="code"
|
||||
name="code"
|
||||
helpText={codeHelpText}
|
||||
label="Enter the 6-digit code"
|
||||
label={strings.enterSixDigitCode()}
|
||||
sx={{ alignItems: "center", mt: 2 }}
|
||||
required
|
||||
placeholder="010101"
|
||||
|
||||
@@ -26,6 +26,7 @@ import { TaskManager } from "../common/task-manager";
|
||||
import Dialog from "../components/dialog";
|
||||
import { ErrorText } from "../components/error-text";
|
||||
import { BaseDialogProps, DialogManager } from "../common/dialog-manager";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
type MigrationProgressEvent = {
|
||||
collection: string;
|
||||
@@ -102,9 +103,9 @@ export const MigrationDialog = DialogManager.register(function MigrationDialog(
|
||||
}}
|
||||
/>
|
||||
<Text as="p" variant={"subBody"} sx={{ mt: 2 }}>
|
||||
If this continues to happen, please reach out to us via{" "}
|
||||
<a href="https://discord.com/invite/zQBK97EE22">Discord</a> or email
|
||||
us at{" "}
|
||||
{strings.migrationErrorNotice()[0]}{" "}
|
||||
<a href="https://discord.com/invite/zQBK97EE22">Discord</a>{" "}
|
||||
{strings.migrationErrorNotice()[1]}{" "}
|
||||
<a href="mailto:support@streetwriters.co">support@streetwriters.co</a>
|
||||
</Text>
|
||||
</Dialog>
|
||||
|
||||
@@ -49,6 +49,7 @@ import { pluralize } from "@notesnook/common";
|
||||
import Field from "../components/field";
|
||||
import { AddNotebookDialog } from "./add-notebook-dialog";
|
||||
import { BaseDialogProps, DialogManager } from "../common/dialog-manager";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
type MoveNoteDialogProps = BaseDialogProps<boolean> & { noteIds: string[] };
|
||||
type NotebookReference = {
|
||||
@@ -176,7 +177,7 @@ export const MoveNoteDialog = DialogManager.register(function MoveNoteDialog({
|
||||
styles={{
|
||||
input: { p: "7.5px" }
|
||||
}}
|
||||
placeholder={"Search notebooks"}
|
||||
placeholder={strings.searchNotebooks()}
|
||||
onChange={async (e) => {
|
||||
const query = e.target.value.trim();
|
||||
const ids = await (query
|
||||
@@ -199,7 +200,7 @@ export const MoveNoteDialog = DialogManager.register(function MoveNoteDialog({
|
||||
}}
|
||||
sx={{ textDecoration: "none", mb: 2 }}
|
||||
>
|
||||
Reset selection
|
||||
{strings.resetSelection()}
|
||||
</Button>
|
||||
)}
|
||||
{notebooks.length > 0 ? (
|
||||
@@ -315,9 +316,7 @@ export const MoveNoteDialog = DialogManager.register(function MoveNoteDialog({
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
<Text variant="body">
|
||||
Please add a notebook to start linking notes.
|
||||
</Text>
|
||||
<Text variant="body">{strings.notebooksEmpty()}</Text>
|
||||
<Button
|
||||
data-test-id="add-new-notebook"
|
||||
variant="secondary"
|
||||
@@ -332,7 +331,7 @@ export const MoveNoteDialog = DialogManager.register(function MoveNoteDialog({
|
||||
)
|
||||
}
|
||||
>
|
||||
Add new notebook
|
||||
{strings.addNotebook()}
|
||||
</Button>
|
||||
</Flex>
|
||||
)}
|
||||
@@ -448,7 +447,7 @@ function NotebookItem(props: {
|
||||
>
|
||||
<Plus
|
||||
size={18}
|
||||
title="New notebook"
|
||||
title={strings.newNotebook()}
|
||||
onClick={async (e) => {
|
||||
e.stopPropagation();
|
||||
await AddNotebookDialog.show({ parentId: notebook.id });
|
||||
|
||||
@@ -35,6 +35,7 @@ import { NoteResolvedData, ResolvedItem } from "@notesnook/common";
|
||||
import { Lock } from "../components/icons";
|
||||
import { ellipsize } from "@notesnook/core";
|
||||
import { BaseDialogProps, DialogManager } from "../common/dialog-manager";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
export type NoteLinkingDialogProps = BaseDialogProps<LinkAttributes | false> & {
|
||||
attributes?: LinkAttributes;
|
||||
@@ -82,7 +83,7 @@ export const NoteLinkingDialog = DialogManager.register(
|
||||
<>
|
||||
<Field
|
||||
autoFocus
|
||||
placeholder="Type # to only search headings"
|
||||
placeholder={strings.searchSectionToLinkPlaceholder()}
|
||||
sx={{ mx: 0 }}
|
||||
onChange={async (e) => {
|
||||
const query = e.target.value.trim().toLowerCase();
|
||||
@@ -113,15 +114,16 @@ export const NoteLinkingDialog = DialogManager.register(
|
||||
setBlocks([]);
|
||||
}}
|
||||
>
|
||||
Selected note: {selectedNote.title} (click to deselect)
|
||||
{strings.linkNoteSelectedNote()}: {selectedNote.title} (
|
||||
{strings.clickToDeselect()})
|
||||
</Button>
|
||||
{isNoteLocked ? (
|
||||
<Text variant="body" sx={{ mt: 1 }}>
|
||||
Linking to a specific block is not available for locked notes.
|
||||
{strings.noteLockedBlockLink()}
|
||||
</Text>
|
||||
) : blocks.length <= 0 ? (
|
||||
<Text variant="body" sx={{ mt: 1 }}>
|
||||
There are no blocks in this note.
|
||||
{strings.noBlocksOnNote()}
|
||||
</Text>
|
||||
) : null}
|
||||
<ScrollContainer>
|
||||
@@ -158,7 +160,7 @@ export const NoteLinkingDialog = DialogManager.register(
|
||||
sx={{ fontFamily: "monospace", whiteSpace: "pre-wrap" }}
|
||||
>
|
||||
{ellipsize(item.content, 200, "end").trim() ||
|
||||
"(empty block)"}
|
||||
strings.linkNoteEmptyBlock()}
|
||||
</Text>
|
||||
<Text
|
||||
variant="subBody"
|
||||
@@ -182,7 +184,7 @@ export const NoteLinkingDialog = DialogManager.register(
|
||||
<>
|
||||
<Field
|
||||
autoFocus
|
||||
placeholder="Search for a note to link to..."
|
||||
placeholder={strings.searchNoteToLinkPlaceholder()}
|
||||
sx={{ mx: 0 }}
|
||||
onChange={async (e) => {
|
||||
const query = e.target.value.trim();
|
||||
|
||||
@@ -47,6 +47,7 @@ import { isMacStoreApp } from "../utils/platform";
|
||||
import { ErrorText } from "../components/error-text";
|
||||
import { BuyDialog } from "./buy-dialog";
|
||||
import { BaseDialogProps, DialogManager } from "../common/dialog-manager";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
type Step = {
|
||||
title: string;
|
||||
@@ -240,7 +241,7 @@ function JoinCause({ onNext }: { onNext: () => void }) {
|
||||
onNext();
|
||||
}}
|
||||
>
|
||||
Join the community
|
||||
{strings.joinDiscord()}
|
||||
</Button>
|
||||
<Button
|
||||
variant={"anchor"}
|
||||
@@ -248,7 +249,7 @@ function JoinCause({ onNext }: { onNext: () => void }) {
|
||||
onClick={() => onNext()}
|
||||
sx={{ color: "var(--paragraph-secondary)" }}
|
||||
>
|
||||
Skip for now
|
||||
{strings.skip()}
|
||||
</Button>
|
||||
</Flex>
|
||||
);
|
||||
@@ -295,7 +296,7 @@ function Importer({ onClose }: { onClose: () => void }) {
|
||||
onClose();
|
||||
}}
|
||||
>
|
||||
Start importing now
|
||||
{strings.startImportingNow()}
|
||||
</Button>
|
||||
<Button
|
||||
variant={"anchor"}
|
||||
@@ -303,7 +304,7 @@ function Importer({ onClose }: { onClose: () => void }) {
|
||||
onClick={() => onClose()}
|
||||
sx={{ color: "var(--paragraph-secondary)" }}
|
||||
>
|
||||
Skip for now
|
||||
{strings.skip()}
|
||||
</Button>
|
||||
</Flex>
|
||||
);
|
||||
@@ -478,7 +479,7 @@ function TrialOffer({ onClose }: { onClose: () => void }) {
|
||||
BuyDialog.show({ plan: "monthly", couponCode: "TRIAL2PRO" });
|
||||
}}
|
||||
>
|
||||
Upgrade now
|
||||
{strings.upgradeToPro()}
|
||||
</Button>
|
||||
<Button
|
||||
variant={"secondary"}
|
||||
@@ -489,7 +490,7 @@ function TrialOffer({ onClose }: { onClose: () => void }) {
|
||||
const result = await TaskManager.startTask({
|
||||
type: "status",
|
||||
id: "trialActivation",
|
||||
title: "Activating trial",
|
||||
title: strings.activatingTrial(),
|
||||
action: () => db.user.activateTrial()
|
||||
});
|
||||
if (result) onClose();
|
||||
@@ -504,7 +505,7 @@ function TrialOffer({ onClose }: { onClose: () => void }) {
|
||||
}
|
||||
}}
|
||||
>
|
||||
{loading ? <Loading size={16} /> : "Try free for 14 days"}
|
||||
{loading ? <Loading size={16} /> : strings.tryFreeFor14Days()}
|
||||
</Button>
|
||||
</Flex>
|
||||
<Button
|
||||
@@ -513,7 +514,7 @@ function TrialOffer({ onClose }: { onClose: () => void }) {
|
||||
onClick={() => onClose()}
|
||||
sx={{ color: "var(--paragraph-secondary)" }}
|
||||
>
|
||||
Skip for now
|
||||
{strings.skip()}
|
||||
</Button>
|
||||
</Flex>
|
||||
);
|
||||
|
||||
@@ -21,6 +21,7 @@ import { useEffect, useState } from "react";
|
||||
import { Box, Flex, Text } from "@theme-ui/components";
|
||||
import Dialog from "../components/dialog";
|
||||
import { BaseDialogProps, DialogManager } from "../common/dialog-manager";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
type Progress = {
|
||||
total?: number;
|
||||
@@ -62,7 +63,7 @@ export const ProgressDialog = DialogManager.register(function ProgressDialog<T>(
|
||||
{current > 0 ? (
|
||||
<>
|
||||
<Text variant="subBody">
|
||||
{current} of {total}
|
||||
{current} {strings.of()} {total}
|
||||
</Text>
|
||||
<Box
|
||||
sx={{
|
||||
|
||||
@@ -31,6 +31,7 @@ import { BaseDialogProps, DialogManager } from "../common/dialog-manager";
|
||||
import { usePromise } from "@notesnook/common";
|
||||
import { Loader } from "../components/loader";
|
||||
import { showToast } from "../utils/toast";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
const QRCode = React.lazy(() => import("../re-exports/react-qrcode-logo"));
|
||||
type RecoveryKeyDialogProps = BaseDialogProps<false>;
|
||||
@@ -45,10 +46,10 @@ export const RecoveryKeyDialog = DialogManager.register(
|
||||
<Dialog
|
||||
testId="recovery-key-dialog"
|
||||
isOpen={true}
|
||||
title="Backup your recovery key"
|
||||
title={strings.saveRecoveryKey()}
|
||||
width={400}
|
||||
positiveButton={{
|
||||
text: "I have backed up my key",
|
||||
text: strings.keyBackedUp(),
|
||||
onClick: () => {
|
||||
Config.set("recoveryKeyBackupDate", Date.now());
|
||||
props.onClose(false);
|
||||
@@ -56,14 +57,11 @@ export const RecoveryKeyDialog = DialogManager.register(
|
||||
}}
|
||||
>
|
||||
{key.status !== "fulfilled" ? (
|
||||
<Loader title="Getting your encryption key..." />
|
||||
<Loader title={strings.gettingEncryptionKey()} />
|
||||
) : (
|
||||
<Flex sx={{ overflow: "hidden", flex: 1, flexDirection: "column" }}>
|
||||
<Flex sx={{ overflowY: "auto", flexDirection: "column" }}>
|
||||
<ErrorText
|
||||
error="In case you forget your password, your recovery key is the only way to recover your data."
|
||||
mt={0}
|
||||
/>
|
||||
<ErrorText error={strings.saveRecoveryKeyDesc()} mt={0} />
|
||||
<Text
|
||||
data-test-id="recovery-key"
|
||||
className="selectable"
|
||||
@@ -138,7 +136,7 @@ export const RecoveryKeyDialog = DialogManager.register(
|
||||
}}
|
||||
sx={{ fontSize: "body" }}
|
||||
>
|
||||
Download QR Code
|
||||
{strings.saveQRCode()}
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
@@ -156,7 +154,7 @@ export const RecoveryKeyDialog = DialogManager.register(
|
||||
}}
|
||||
sx={{ fontSize: "body" }}
|
||||
>
|
||||
Download file
|
||||
{strings.network.download()}
|
||||
</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
@@ -26,6 +26,7 @@ import { Clock, Refresh } from "../components/icons";
|
||||
import Note from "../components/note";
|
||||
import { getFormattedReminderTime, usePromise } from "@notesnook/common";
|
||||
import { BaseDialogProps, DialogManager } from "../common/dialog-manager";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
export type ReminderPreviewDialogProps = BaseDialogProps<false> & {
|
||||
reminder: Reminder;
|
||||
@@ -90,7 +91,7 @@ export const ReminderPreviewDialog = DialogManager.register(
|
||||
<IconTag icon={Clock} text={getFormattedReminderTime(reminder)} />
|
||||
</Flex>
|
||||
|
||||
<Text variant="body">Remind me in:</Text>
|
||||
<Text variant="body">{strings.remindMeIn()}:</Text>
|
||||
<Flex
|
||||
sx={{
|
||||
alignItems: "center",
|
||||
@@ -124,7 +125,7 @@ export const ReminderPreviewDialog = DialogManager.register(
|
||||
referencedNotes.status === "fulfilled" &&
|
||||
referencedNotes.value.length > 0 && (
|
||||
<>
|
||||
<Text variant="body">References:</Text>
|
||||
<Text variant="body">{strings.references()}:</Text>
|
||||
{referencedNotes.value.map((item, index) => (
|
||||
<Note
|
||||
key={item.id}
|
||||
|
||||
@@ -17,12 +17,13 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { Loading } from "../../../components/icons";
|
||||
import { Box, Flex, Link, Text } from "@theme-ui/components";
|
||||
import { getFormattedDate } from "@notesnook/common";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { Box, Flex, Link, Text } from "@theme-ui/components";
|
||||
import { useEffect, useState } from "react";
|
||||
import { db } from "../../../common/db";
|
||||
import { Transaction, TransactionStatus } from "@notesnook/core";
|
||||
import { TransactionStatus, Transaction } from "@notesnook/core";
|
||||
import { Loading } from "../../../components/icons";
|
||||
|
||||
const TransactionStatusToText: Record<TransactionStatus, string> = {
|
||||
completed: "Completed",
|
||||
@@ -129,7 +130,7 @@ export function BillingHistory() {
|
||||
{transaction.amount} {transaction.currency}
|
||||
</Text>
|
||||
<Text as="td" variant="body">
|
||||
{TransactionStatusToText[transaction.status]}
|
||||
{strings.transactionStatusToText[transaction.status]()}
|
||||
</Text>
|
||||
<Text as="td" variant="body">
|
||||
<Link
|
||||
@@ -139,7 +140,7 @@ export function BillingHistory() {
|
||||
variant="text.subBody"
|
||||
sx={{ color: "accent" }}
|
||||
>
|
||||
View receipt
|
||||
{strings.viewReceipt()}
|
||||
</Link>
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
@@ -64,6 +64,7 @@ import { Pro } from "../../../components/icons";
|
||||
|
||||
import { Icon } from "@notesnook/ui";
|
||||
import { CURRENT_TOOLBAR_VERSION } from "@notesnook/common";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
export function CustomizeToolbar() {
|
||||
const sensors = useSensors(
|
||||
@@ -158,10 +159,10 @@ export function CustomizeToolbar() {
|
||||
alignItems: "center",
|
||||
p: 1
|
||||
}}
|
||||
title="Add group"
|
||||
title={strings.createAGroup()}
|
||||
onClick={() => {
|
||||
setItems(addGroup);
|
||||
showToast("success", "Group added successfully");
|
||||
showToast("success", strings.groupAdded());
|
||||
}}
|
||||
>
|
||||
<Icon path={Icons.plus} color="paragraph" size={18} />
|
||||
|
||||
@@ -20,6 +20,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import { Button, Text } from "@theme-ui/components";
|
||||
import { FlexScrollContainer } from "../../../components/scroll-container";
|
||||
import { useSpellChecker } from "../../../hooks/use-spell-checker";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
export function DictionaryWords() {
|
||||
const words = useSpellChecker((store) => store.words);
|
||||
@@ -36,10 +37,11 @@ export function DictionaryWords() {
|
||||
}}
|
||||
>
|
||||
<Text variant="body" sx={{ my: 1 }}>
|
||||
You have {words.length} custom dictionary words.
|
||||
{strings.customDictWords(words.length)}
|
||||
</Text>
|
||||
{words.map((word) => (
|
||||
<Button
|
||||
key={word}
|
||||
variant="menuitem"
|
||||
sx={{ textAlign: "left", p: 1 }}
|
||||
onClick={() => deleteWord(word)}
|
||||
|
||||
@@ -17,12 +17,11 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { useStore as useAppStore } from "../../../stores/app-store";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { Button, Flex, Input, Link, Text, Box } from "@theme-ui/components";
|
||||
import { useCallback, useRef, useState } from "react";
|
||||
import { useDropzone } from "react-dropzone";
|
||||
import { Box, Button, Flex, Input, Link, Text } from "@theme-ui/components";
|
||||
import { db } from "../../../common/db";
|
||||
import { importFiles } from "../../../utils/importer";
|
||||
import { CheckCircleOutline } from "../../../components/icons";
|
||||
import Accordion from "../../../components/accordion";
|
||||
|
||||
@@ -91,7 +90,7 @@ export function Importer() {
|
||||
{isImporting ? (
|
||||
<>
|
||||
<Text variant="title" sx={{ textAlign: "center", mb: 4, mt: 150 }}>
|
||||
<span ref={notesCounter}>0</span> notes imported.
|
||||
<span ref={notesCounter}>0</span> {strings.notesImported()}.
|
||||
</Text>
|
||||
|
||||
<Flex
|
||||
@@ -109,7 +108,7 @@ export function Importer() {
|
||||
<>
|
||||
<CheckCircleOutline color="accent" sx={{ mt: 150 }} />
|
||||
<Text variant="body" my={2} sx={{ textAlign: "center" }}>
|
||||
Import completed. {errors.length} errors occured.
|
||||
{strings.importCompleted()}. {strings.errorsOccured(errors.length)}
|
||||
</Text>
|
||||
<Button
|
||||
variant="secondary"
|
||||
@@ -121,7 +120,7 @@ export function Importer() {
|
||||
setIsImporting(false);
|
||||
}}
|
||||
>
|
||||
Start over
|
||||
{strings.startOver()}
|
||||
</Button>
|
||||
{errors.length > 0 && (
|
||||
<Flex
|
||||
@@ -232,10 +231,10 @@ export function Importer() {
|
||||
<Input {...getInputProps()} />
|
||||
<Text variant="body" sx={{ textAlign: "center" }}>
|
||||
{isDragActive
|
||||
? "Drop the files here"
|
||||
: "Drag & drop files here, or click to select files"}
|
||||
? strings.dropFilesHere()
|
||||
: strings.dragAndDropFiles()}
|
||||
<br />
|
||||
<Text variant="subBody">Only .zip files are supported.</Text>
|
||||
<Text variant="subBody">{strings.onlyZipSupported()}</Text>
|
||||
</Text>
|
||||
<Box sx={{ display: "flex", flexWrap: "wrap", mt: 2 }}>
|
||||
{files.map((file, i) => (
|
||||
|
||||
@@ -25,6 +25,7 @@ import { useStore as useUserStore } from "../../../stores/user-store";
|
||||
import { ErrorText } from "../../../components/error-text";
|
||||
import { TaskManager } from "../../../common/task-manager";
|
||||
import { isServerCompatible } from "@notesnook/core";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
export const ServerIds = ["notesnook-sync", "auth", "sse"] as const;
|
||||
export type ServerId = (typeof ServerIds)[number];
|
||||
@@ -44,23 +45,23 @@ const SERVERS: Server[] = [
|
||||
{
|
||||
id: "notesnook-sync",
|
||||
host: "API_HOST",
|
||||
title: "Sync server",
|
||||
title: strings.syncServer(),
|
||||
example: "http://localhost:4326",
|
||||
description: "Server used to sync your notes & other data between devices."
|
||||
description: strings.syncServerDesc()
|
||||
},
|
||||
{
|
||||
id: "auth",
|
||||
host: "AUTH_HOST",
|
||||
title: "Auth server",
|
||||
title: strings.authServer(),
|
||||
example: "http://localhost:5326",
|
||||
description: "Server used for login/sign up and authentication."
|
||||
description: strings.authServerDesc()
|
||||
},
|
||||
{
|
||||
id: "sse",
|
||||
host: "SSE_HOST",
|
||||
title: "Events server",
|
||||
title: strings.sseServer(),
|
||||
example: "http://localhost:7326",
|
||||
description: "Server used to receive important notifications & events."
|
||||
description: strings.sseServerDesc()
|
||||
}
|
||||
];
|
||||
export function ServersConfiguration() {
|
||||
@@ -74,7 +75,7 @@ export function ServersConfiguration() {
|
||||
<>
|
||||
{isLoggedIn ? (
|
||||
<ErrorText
|
||||
error="You must log out in order to change/reset server URLs."
|
||||
error={strings.logoutToChangeServerUrls()}
|
||||
sx={{ mb: 2, mt: 0 }}
|
||||
/>
|
||||
) : null}
|
||||
@@ -111,7 +112,7 @@ export function ServersConfiguration() {
|
||||
borderRadius: "default"
|
||||
}}
|
||||
>
|
||||
Connected to all servers successfully.
|
||||
{strings.connectedToServer()}
|
||||
</Text>
|
||||
) : null}
|
||||
<Flex sx={{ mt: 1, justifyContent: "end", gap: 1 }}>
|
||||
@@ -123,9 +124,8 @@ export function ServersConfiguration() {
|
||||
useStore.getState().setServerUrls(urls);
|
||||
await TaskManager.startTask({
|
||||
type: "modal",
|
||||
title: "App will reload in 5 seconds",
|
||||
subtitle:
|
||||
"Your changes have been saved and will be reflected after the app has refreshed.",
|
||||
title: strings.appWillReloadIn(5),
|
||||
subtitle: strings.changesReflectOnStart(),
|
||||
action() {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
@@ -137,7 +137,7 @@ export function ServersConfiguration() {
|
||||
});
|
||||
}}
|
||||
>
|
||||
Save
|
||||
{strings.save()}
|
||||
</Button>
|
||||
<Button
|
||||
disabled={isLoggedIn}
|
||||
@@ -148,21 +148,22 @@ export function ServersConfiguration() {
|
||||
for (const host of HostIds) {
|
||||
const url = urls[host];
|
||||
const server = SERVERS.find((s) => s.host === host);
|
||||
if (!server)
|
||||
throw new Error(`Server with host ${host} not found.`);
|
||||
if (!url) throw new Error("All server urls are required.");
|
||||
if (!server) throw new Error(strings.serverNotFound(host));
|
||||
if (!url) throw new Error(strings.allServerUrlsRequired());
|
||||
const version = await fetch(`${url}/version`)
|
||||
.then((r) => r.json() as Promise<VersionResponse>)
|
||||
.catch(() => undefined);
|
||||
if (!version)
|
||||
throw new Error(`Could not connect to ${server.title}.`);
|
||||
throw new Error(
|
||||
`${strings.couldNotConnectTo()} ${server.title}.`
|
||||
);
|
||||
if (version.id !== server.id)
|
||||
throw new Error(
|
||||
`The URL you have given (${url}) does not point to the ${server.title}.`
|
||||
`${strings.incorrectServerUrl(url)} ${server.title}.`
|
||||
);
|
||||
if (!isServerCompatible(version.version))
|
||||
throw new Error(
|
||||
`The ${server.title} at ${url} is not compatible with this client.`
|
||||
strings.serverVersionMismatch(server.title, url)
|
||||
);
|
||||
}
|
||||
setSuccess(true);
|
||||
@@ -171,7 +172,7 @@ export function ServersConfiguration() {
|
||||
}
|
||||
}}
|
||||
>
|
||||
Test connection
|
||||
{strings.testConnection()}
|
||||
</Button>
|
||||
<Button
|
||||
disabled={isLoggedIn}
|
||||
@@ -181,9 +182,8 @@ export function ServersConfiguration() {
|
||||
useStore.getState().setServerUrls();
|
||||
await TaskManager.startTask({
|
||||
type: "modal",
|
||||
title: "App will reload in 5 seconds",
|
||||
subtitle:
|
||||
"Your changes have been saved and will be reflected after the app has refreshed.",
|
||||
title: strings.appWillReloadIn(5),
|
||||
subtitle: strings.changesReflectOnStart(),
|
||||
action() {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
@@ -195,7 +195,7 @@ export function ServersConfiguration() {
|
||||
});
|
||||
}}
|
||||
>
|
||||
Reset
|
||||
{strings.reset()}
|
||||
</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
@@ -22,6 +22,7 @@ import { Input, Label } from "@theme-ui/components";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { deleteItem } from "@notesnook/core";
|
||||
import { FlexScrollContainer } from "../../../components/scroll-container";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
export function SpellCheckerLanguages() {
|
||||
const spellChecker = useSpellChecker();
|
||||
@@ -52,7 +53,7 @@ export function SpellCheckerLanguages() {
|
||||
return (
|
||||
<>
|
||||
<Input
|
||||
placeholder="Filter languages"
|
||||
placeholder={strings.filterLanguages()}
|
||||
sx={{ mx: "2px", my: 2, width: "auto" }}
|
||||
onChange={(e) => filter(e.currentTarget.value.toLowerCase().trim())}
|
||||
/>
|
||||
|
||||
@@ -29,6 +29,7 @@ import { Loading } from "../../../components/icons";
|
||||
import { Features } from "../../buy-dialog/features";
|
||||
import { ConfirmDialog } from "../../confirm";
|
||||
import { BuyDialog } from "../../buy-dialog";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
const PROVIDER_MAP = {
|
||||
0: "Streetwriters",
|
||||
@@ -108,7 +109,7 @@ export function SubscriptionStatus() {
|
||||
color: "accent"
|
||||
}}
|
||||
>
|
||||
CURRENT PLAN
|
||||
{strings.currentPlan()}
|
||||
</Text>
|
||||
<Text
|
||||
variant="heading"
|
||||
@@ -164,7 +165,7 @@ export function SubscriptionStatus() {
|
||||
}
|
||||
}}
|
||||
>
|
||||
Cancel subscription
|
||||
{strings.cancelSub()}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
@@ -201,7 +202,7 @@ export function SubscriptionStatus() {
|
||||
{!isPro && (
|
||||
<>
|
||||
<Button variant="accent" onClick={() => BuyDialog.show({})}>
|
||||
{isProCancelled ? "Resubscribe" : "Upgrade to Pro"}
|
||||
{isProCancelled ? strings.resubToPro() : strings.upgradeToPro()}
|
||||
</Button>
|
||||
{isBasic && (
|
||||
<Button
|
||||
@@ -212,7 +213,7 @@ export function SubscriptionStatus() {
|
||||
{isActivatingTrial ? (
|
||||
<Loading size={16} />
|
||||
) : (
|
||||
"Try free for 14 days"
|
||||
strings.tryFreeFor14Days()
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
@@ -41,6 +41,7 @@ import { showToast } from "../../../utils/toast";
|
||||
import { showFilePicker, readFile } from "../../../utils/file-picker";
|
||||
import { VirtualizedGrid } from "../../../components/virtualized-grid";
|
||||
import { ThemeDetailsDialog } from "../../theme-details-dialog";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
const ThemesClient = ThemesTRPC.createClient({
|
||||
links: [
|
||||
@@ -145,7 +146,7 @@ function ThemesList() {
|
||||
return (
|
||||
<>
|
||||
<Input
|
||||
placeholder="Search themes"
|
||||
placeholder={strings.searchThemes()}
|
||||
sx={{ mt: 2 }}
|
||||
onChange={debounce((e) => setSearchQuery(e.target.value), 500)}
|
||||
/>
|
||||
@@ -202,7 +203,7 @@ function ThemesList() {
|
||||
flexShrink: 0
|
||||
}}
|
||||
>
|
||||
Load from file
|
||||
{strings.loadFromFile()}
|
||||
</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
@@ -299,8 +300,10 @@ function ThemeItem(props: ThemeItemProps) {
|
||||
>
|
||||
{isApplying ? (
|
||||
<Loading color="accent" size={18} />
|
||||
) : theme.colorScheme === "dark" ? (
|
||||
strings.setAsDarkTheme()
|
||||
) : (
|
||||
`Set as ${theme.colorScheme}`
|
||||
strings.setAsLightTheme()
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
@@ -30,6 +30,7 @@ import { db } from "../../../common/db";
|
||||
import { showToast } from "../../../utils/toast";
|
||||
import { EditProfilePictureDialog } from "../../edit-profile-picture-dialog";
|
||||
import { PromptDialog } from "../../prompt";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
export function UserProfile() {
|
||||
const user = useUserStore((store) => store.user);
|
||||
@@ -81,10 +82,8 @@ export function UserProfile() {
|
||||
<User size={30} />
|
||||
</Flex>
|
||||
<Flex sx={{ flexDirection: "column" }}>
|
||||
<Text variant={"title"}>You are not logged in</Text>
|
||||
<Text variant={"subBody"}>
|
||||
Login or create an account to sync your notes.
|
||||
</Text>
|
||||
<Text variant={"title"}>{strings.loginMessage()}</Text>
|
||||
<Text variant={"subBody"}>{strings.loginMessageActionText()}</Text>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
@@ -138,13 +137,13 @@ export function UserProfile() {
|
||||
visibility: "collapse",
|
||||
cursor: "pointer"
|
||||
}}
|
||||
title="Edit profile picture"
|
||||
title={strings.editProfilePicture()}
|
||||
onClick={async () => {
|
||||
await EditProfilePictureDialog.show({ profile });
|
||||
}}
|
||||
>
|
||||
<Text variant="body" sx={{ color: "white" }}>
|
||||
Edit
|
||||
{strings.edit()}
|
||||
</Text>
|
||||
</Flex>
|
||||
</Flex>
|
||||
@@ -165,16 +164,15 @@ export function UserProfile() {
|
||||
</Text>
|
||||
|
||||
<Text variant={"title"}>
|
||||
{profile?.fullName || "Your full name"}{" "}
|
||||
{profile?.fullName || strings.yourFullName()}{" "}
|
||||
<Edit
|
||||
sx={{ display: "inline-block", cursor: "pointer" }}
|
||||
size={12}
|
||||
title="Edit full name"
|
||||
title={strings.editFullName()}
|
||||
onClick={async () => {
|
||||
const fullName = await PromptDialog.show({
|
||||
title: "Edit your full name",
|
||||
description:
|
||||
"Your profile data is 100% end-to-end encrypted.",
|
||||
title: strings.editFullName(),
|
||||
description: strings.setFullNameDesc(),
|
||||
defaultValue: profile?.fullName
|
||||
});
|
||||
try {
|
||||
@@ -190,7 +188,7 @@ export function UserProfile() {
|
||||
/>
|
||||
</Text>
|
||||
<Text variant={"subBody"}>
|
||||
{user.email} • Member since{" "}
|
||||
{user.email} • {strings.memberSince()}{" "}
|
||||
{getFormattedDate(getObjectIdTimestamp(user.id), "date")}
|
||||
</Text>
|
||||
</Flex>
|
||||
|
||||
@@ -76,6 +76,7 @@ import { ScopedThemeProvider } from "../../components/theme-provider";
|
||||
import { AppLockSettings } from "./app-lock-settings";
|
||||
import { BaseDialogProps, DialogManager } from "../../common/dialog-manager";
|
||||
import { ServersSettings } from "./servers-settings";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
type SettingsDialogProps = BaseDialogProps<false> & {
|
||||
activeSection?: SectionKeys;
|
||||
@@ -269,7 +270,7 @@ function SettingsSideBar(props: SettingsSideBarProps) {
|
||||
<Input
|
||||
id="search"
|
||||
name="search"
|
||||
placeholder="Search"
|
||||
placeholder={strings.search()}
|
||||
data-test-id="settings-search"
|
||||
sx={{
|
||||
m: 2,
|
||||
|
||||
@@ -23,6 +23,7 @@ import { ThemePreview } from "../components/theme-preview";
|
||||
import { Flex, Link, Text } from "@theme-ui/components";
|
||||
import { useStore as useThemeStore } from "../stores/theme-store";
|
||||
import { BaseDialogProps, DialogManager } from "../common/dialog-manager";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
export type ThemeDetailsDialogProps = BaseDialogProps<boolean> & {
|
||||
theme: ThemeMetadata;
|
||||
@@ -65,11 +66,11 @@ export const ThemeDetailsDialog = DialogManager.register(
|
||||
</Text>
|
||||
{theme.totalInstalls && theme.totalInstalls > 0 ? (
|
||||
<Text variant="subBody" sx={{ fontSize: "subtitle" }}>
|
||||
{theme.totalInstalls} installs
|
||||
{theme.totalInstalls} {strings.installs()}
|
||||
</Text>
|
||||
) : null}
|
||||
<Text variant="subBody" sx={{ fontSize: "subtitle" }}>
|
||||
Licensed under {theme.license}
|
||||
{strings.licenseUnder()} {theme.license}
|
||||
</Text>
|
||||
<Flex sx={{ gap: 1, mt: 1 }}>
|
||||
{theme.homepage && (
|
||||
|
||||
@@ -25,13 +25,14 @@ import { getServiceWorkerVersion } from "./utils/version";
|
||||
import { register as registerStreamSaver } from "./utils/stream-saver/mitm";
|
||||
import { ThemeDark, ThemeLight, themeToCSS } from "@notesnook/theme";
|
||||
import Config from "./utils/config";
|
||||
import Intl from "@notesnook/intl";
|
||||
import { $en, setI18nGlobal, $de } from "@notesnook/intl";
|
||||
import { i18n } from "@lingui/core";
|
||||
i18n.load({
|
||||
en: Intl.$en
|
||||
en: $en,
|
||||
de: $de
|
||||
});
|
||||
i18n.activate("en");
|
||||
Intl.setI18nGlobal(i18n);
|
||||
i18n.activate("de");
|
||||
setI18nGlobal(i18n);
|
||||
|
||||
const colorScheme = JSON.parse(
|
||||
window.localStorage.getItem("colorScheme") || '"light"'
|
||||
|
||||
@@ -35,6 +35,7 @@ import { onPageVisibilityChanged } from "../utils/page-visibility";
|
||||
import { WebAuthn } from "../utils/webauthn";
|
||||
import { getDocumentTitle, setDocumentTitle } from "../utils/dom";
|
||||
import { CredentialWithoutSecret, useKeyStore } from "../interfaces/key-store";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
export default function AppLock(props: PropsWithChildren<unknown>) {
|
||||
const credentials = useKeyStore((store) => store.activeCredentials());
|
||||
@@ -144,7 +145,7 @@ export default function AppLock(props: PropsWithChildren<unknown>) {
|
||||
mt={25}
|
||||
sx={{ fontSize: 36, textAlign: "center" }}
|
||||
>
|
||||
Unlock your notes
|
||||
{strings.unlockNotes()}
|
||||
</Text>
|
||||
</Flex>
|
||||
<Text
|
||||
@@ -157,7 +158,7 @@ export default function AppLock(props: PropsWithChildren<unknown>) {
|
||||
color: "var(--paragraph-secondary)"
|
||||
}}
|
||||
>
|
||||
Please verify it's you.
|
||||
{strings.verifyItsYou()}
|
||||
</Text>
|
||||
|
||||
{isUnlocking ? (
|
||||
@@ -185,7 +186,7 @@ export default function AppLock(props: PropsWithChildren<unknown>) {
|
||||
autoFocus
|
||||
required
|
||||
sx={{ width: ["95%", "95%", "25%"] }}
|
||||
placeholder="Enter password"
|
||||
placeholder={strings.enterPassword()}
|
||||
type="password"
|
||||
onKeyUp={async (e) => {
|
||||
if (e.key === "Enter")
|
||||
@@ -198,7 +199,7 @@ export default function AppLock(props: PropsWithChildren<unknown>) {
|
||||
sx={{ borderRadius: 100, px: 30 }}
|
||||
onClick={() => unlockWithPassword(credential)}
|
||||
>
|
||||
Continue
|
||||
{strings.continue()}
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
@@ -224,7 +225,7 @@ export default function AppLock(props: PropsWithChildren<unknown>) {
|
||||
}
|
||||
}}
|
||||
>
|
||||
Unlock with security key
|
||||
{strings.unlockWithSecurityKey()}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ import { ErrorText } from "../components/error-text";
|
||||
import { AuthenticatorType, User } from "@notesnook/core";
|
||||
import { showLogoutConfirmation } from "../dialogs/confirm";
|
||||
import { TaskManager } from "../common/task-manager";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
type EmailFormData = {
|
||||
email: string;
|
||||
@@ -211,17 +212,17 @@ function LoginEmail(props: BaseAuthComponentProps<"login:email">) {
|
||||
return (
|
||||
<AuthForm
|
||||
type="login:email"
|
||||
title="Welcome back!"
|
||||
title={strings.welcomeBack()}
|
||||
canSkip
|
||||
subtitle={
|
||||
<SubtitleWithAction
|
||||
text="Don't have an account?"
|
||||
action={{ text: "Sign up", onClick: () => navigate("signup") }}
|
||||
text={strings.dontHaveAccount()}
|
||||
action={{ text: strings.signUp(), onClick: () => navigate("signup") }}
|
||||
/>
|
||||
}
|
||||
loading={{
|
||||
title: "Verifying your email",
|
||||
subtitle: "Please wait while you are authenticated."
|
||||
title: strings.verifyingEmail(),
|
||||
subtitle: strings.authWait()
|
||||
}}
|
||||
onSubmit={async (form) => {
|
||||
const { primaryMethod, phoneNumber, secondaryMethod } =
|
||||
@@ -246,10 +247,7 @@ function LoginEmail(props: BaseAuthComponentProps<"login:email">) {
|
||||
>
|
||||
<Warn size={16} color="icon-error" />
|
||||
<Text variant="body" ml={1}>
|
||||
You are logging into the beta version of Notesnook. Switching
|
||||
between beta & stable versions can cause weird issues
|
||||
including data loss. It is recommended that you do not use both
|
||||
simultaneously.
|
||||
{strings.betaLoginNotice()}
|
||||
</Text>
|
||||
</Flex>
|
||||
) : null}
|
||||
@@ -257,11 +255,11 @@ function LoginEmail(props: BaseAuthComponentProps<"login:email">) {
|
||||
id="email"
|
||||
type="email"
|
||||
autoComplete="email"
|
||||
label="Enter email"
|
||||
label={strings.enterEmailAddress()}
|
||||
autoFocus
|
||||
defaultValue={form?.email}
|
||||
/>
|
||||
<SubmitButton text="Continue" />
|
||||
<SubmitButton text={strings.continue()} />
|
||||
</>
|
||||
)}
|
||||
</AuthForm>
|
||||
@@ -279,12 +277,12 @@ function LoginPassword(props: BaseAuthComponentProps<"login:password">) {
|
||||
return (
|
||||
<AuthForm
|
||||
type="login:password"
|
||||
title="Your account password"
|
||||
subtitle={"Your password is always hashed before leaving this device."}
|
||||
title={strings.accountPassword()}
|
||||
subtitle={strings.accountPassDesc()}
|
||||
loadForever
|
||||
loading={{
|
||||
title: "Logging you in",
|
||||
subtitle: "Please wait while you are authenticated."
|
||||
title: strings.loggingIn(),
|
||||
subtitle: strings.authWait()
|
||||
}}
|
||||
onSubmit={async (form) => {
|
||||
await userstore.login(
|
||||
@@ -305,7 +303,7 @@ function LoginPassword(props: BaseAuthComponentProps<"login:password">) {
|
||||
id="password"
|
||||
type="password"
|
||||
autoComplete="current-password"
|
||||
label="Enter password"
|
||||
label={strings.enterPassword()}
|
||||
autoFocus
|
||||
defaultValue={form?.password}
|
||||
/>
|
||||
@@ -317,9 +315,9 @@ function LoginPassword(props: BaseAuthComponentProps<"login:password">) {
|
||||
onClick={() => navigate("recover", { email: formData.email })}
|
||||
sx={{ color: "paragraph", alignSelf: "end" }}
|
||||
>
|
||||
Forgot password?
|
||||
{strings.forgotPassword()}
|
||||
</Button>
|
||||
<SubmitButton text="Login to your account" />
|
||||
<SubmitButton text={strings.loginToYourAccount()} />
|
||||
</>
|
||||
)}
|
||||
</AuthForm>
|
||||
@@ -332,21 +330,24 @@ function Signup(props: BaseAuthComponentProps<"signup">) {
|
||||
return (
|
||||
<AuthForm
|
||||
type="signup"
|
||||
title="Create an account"
|
||||
title={strings.createAccount()}
|
||||
canSkip
|
||||
subtitle={
|
||||
<SubtitleWithAction
|
||||
text="Already have an account?"
|
||||
action={{ text: "Log in", onClick: () => navigate("login:email") }}
|
||||
text={strings.alreadyHaveAccount()}
|
||||
action={{
|
||||
text: strings.login(),
|
||||
onClick: () => navigate("login:email")
|
||||
}}
|
||||
/>
|
||||
}
|
||||
loading={{
|
||||
title: "Creating your account",
|
||||
subtitle: "Please wait while we finalize your account."
|
||||
title: strings.creatingAccount(),
|
||||
subtitle: strings.creatingAccountDesc()
|
||||
}}
|
||||
onSubmit={async (form) => {
|
||||
if (form.password !== form["confirm-password"]) {
|
||||
throw new Error("Passwords do not match.");
|
||||
throw new Error(strings.passwordNotMatched());
|
||||
}
|
||||
|
||||
await userstore.signup(form);
|
||||
@@ -359,7 +360,7 @@ function Signup(props: BaseAuthComponentProps<"signup">) {
|
||||
id="email"
|
||||
type="email"
|
||||
autoComplete="email"
|
||||
label="Enter email"
|
||||
label={strings.enterEmailAddress()}
|
||||
autoFocus
|
||||
defaultValue={form?.email}
|
||||
/>
|
||||
@@ -367,41 +368,40 @@ function Signup(props: BaseAuthComponentProps<"signup">) {
|
||||
id="password"
|
||||
type="password"
|
||||
autoComplete="new-password"
|
||||
label="Set password"
|
||||
label={strings.newPassword()}
|
||||
defaultValue={form?.password}
|
||||
/>
|
||||
<AuthField
|
||||
id="confirm-password"
|
||||
type="password"
|
||||
autoComplete="new-password"
|
||||
label="Confirm password"
|
||||
label={strings.confirmPassword()}
|
||||
defaultValue={form?.["confirm-password"]}
|
||||
/>
|
||||
<SubmitButton text="Create account" />
|
||||
<SubmitButton text={strings.createAccount()} />
|
||||
<Text
|
||||
mt={4}
|
||||
variant="subBody"
|
||||
sx={{ fontSize: 13, textAlign: "center" }}
|
||||
>
|
||||
By pressing {`"Create account" button, you agree to our`}{" "}
|
||||
{strings.signupAgreement[0]()}{" "}
|
||||
<Link
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
href="https://notesnook.com/tos"
|
||||
sx={{ color: "accent" }}
|
||||
>
|
||||
Terms of Service
|
||||
{strings.signupAgreement[1]()}
|
||||
</Link>{" "}
|
||||
&{" "}
|
||||
{strings.signupAgreement[2]()}{" "}
|
||||
<Link
|
||||
rel="noreferrer"
|
||||
href="https://notesnook.com/privacy"
|
||||
sx={{ color: "accent" }}
|
||||
>
|
||||
Privacy Policy
|
||||
{strings.signupAgreement[3]()}
|
||||
</Link>
|
||||
. You also agree to receiving marketing emails from us which you can
|
||||
opt-out of from the app settings.
|
||||
. {strings.signupAgreement[4]()}
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
@@ -428,21 +428,17 @@ function SessionExpiry(props: BaseAuthComponentProps<"sessionExpiry">) {
|
||||
return (
|
||||
<AuthForm
|
||||
type="sessionExpiry"
|
||||
title="Your session has expired"
|
||||
title={strings.sessionExpired()}
|
||||
subtitle={
|
||||
<Flex bg="shade" p={1} sx={{ borderRadius: "default" }}>
|
||||
<Text as="span" sx={{ fontSize: "body", color: "accent" }}>
|
||||
<b>
|
||||
All your local changes are safe and will be synced after you
|
||||
login.
|
||||
</b>{" "}
|
||||
Please enter your password to continue.
|
||||
{strings.sessionExpiredDesc(user?.email as string)}
|
||||
</Text>
|
||||
</Flex>
|
||||
}
|
||||
loading={{
|
||||
title: "Logging you in",
|
||||
subtitle: "Please wait while you are authenticated."
|
||||
title: strings.loggingIn(),
|
||||
subtitle: strings.pleaseWaitLogin()
|
||||
}}
|
||||
onSubmit={async () => {
|
||||
if (!user) return;
|
||||
@@ -463,7 +459,7 @@ function SessionExpiry(props: BaseAuthComponentProps<"sessionExpiry">) {
|
||||
id="email"
|
||||
type="email"
|
||||
autoComplete={"false"}
|
||||
label="Enter email"
|
||||
label={strings.enterEmailAddress()}
|
||||
placeholder={user ? maskEmail(user.email) : undefined}
|
||||
autoFocus
|
||||
disabled
|
||||
@@ -476,9 +472,9 @@ function SessionExpiry(props: BaseAuthComponentProps<"sessionExpiry">) {
|
||||
onClick={() => user && navigate("recover", { email: user.email })}
|
||||
sx={{ color: "paragraph", alignSelf: "end" }}
|
||||
>
|
||||
Forgot password?
|
||||
{strings.forgotPassword()}
|
||||
</Button>
|
||||
<SubmitButton text="Relogin to your account" />
|
||||
<SubmitButton text={strings.reloginToYourAccount()} />
|
||||
<Button
|
||||
type="button"
|
||||
variant="anchor"
|
||||
@@ -494,15 +490,15 @@ function SessionExpiry(props: BaseAuthComponentProps<"sessionExpiry">) {
|
||||
if (await showLogoutConfirmation()) {
|
||||
await TaskManager.startTask({
|
||||
type: "modal",
|
||||
title: "You are being logged out",
|
||||
title: strings.loggingOut(),
|
||||
action: () => db.user.logout(true),
|
||||
subtitle: "Please wait..."
|
||||
subtitle: strings.loggingOutDesc()
|
||||
});
|
||||
openURL("/login");
|
||||
}
|
||||
}}
|
||||
>
|
||||
Logout permanently
|
||||
{strings.logout()}
|
||||
</Button>
|
||||
</AuthForm>
|
||||
);
|
||||
@@ -515,16 +511,19 @@ function AccountRecovery(props: BaseAuthComponentProps<"recover">) {
|
||||
return (
|
||||
<AuthForm
|
||||
type="recover"
|
||||
title="Recover your account"
|
||||
title={strings.accountRecovery()}
|
||||
subtitle={
|
||||
<SubtitleWithAction
|
||||
text="Remembered your password?"
|
||||
action={{ text: "Log in", onClick: () => navigate("login:email") }}
|
||||
text={strings.rememberedYourPassword()}
|
||||
action={{
|
||||
text: strings.login(),
|
||||
onClick: () => navigate("login:email")
|
||||
}}
|
||||
/>
|
||||
}
|
||||
loading={{
|
||||
title: "Sending recovery email",
|
||||
subtitle: "Please wait while we send you recovery instructions."
|
||||
title: strings.sendingRecoveryEmail(),
|
||||
subtitle: strings.sendingRecoveryEmailDesc()
|
||||
}}
|
||||
onSubmit={async (form) => {
|
||||
if (!form.email) {
|
||||
@@ -538,9 +537,7 @@ function AccountRecovery(props: BaseAuthComponentProps<"recover">) {
|
||||
window.open(url, "_self");
|
||||
return;
|
||||
}
|
||||
setSuccess(
|
||||
`Recovery email sent. Please check your inbox (and spam folder) for further instructions.`
|
||||
);
|
||||
setSuccess(strings.recoveryEmailSentDesc());
|
||||
}}
|
||||
>
|
||||
{success ? (
|
||||
@@ -551,7 +548,7 @@ function AccountRecovery(props: BaseAuthComponentProps<"recover">) {
|
||||
{success}
|
||||
</Text>
|
||||
</Flex>
|
||||
<SubmitButton text="Send again" />
|
||||
<SubmitButton text={strings.confirmEmail()} />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
@@ -559,12 +556,12 @@ function AccountRecovery(props: BaseAuthComponentProps<"recover">) {
|
||||
id="email"
|
||||
type="email"
|
||||
autoComplete={"email"}
|
||||
label="Enter your account email"
|
||||
helpText="You will receive instructions on how to recover your account on this email"
|
||||
label={strings.enterEmailAddress()}
|
||||
helpText={strings.accountRecoverHelpText()}
|
||||
defaultValue={formData ? formData.email : ""}
|
||||
autoFocus
|
||||
/>
|
||||
<SubmitButton text="Send recovery email" />
|
||||
<SubmitButton text={strings.sendRecoveryEmail()} />
|
||||
</>
|
||||
)}
|
||||
</AuthForm>
|
||||
@@ -656,14 +653,14 @@ function MFACode(props: BaseAuthComponentProps<"mfa:code">) {
|
||||
return (
|
||||
<AuthForm
|
||||
type="mfa:code"
|
||||
title="Two-factor authentication"
|
||||
title={strings["2fa"]()}
|
||||
subtitle={texts.subtitle}
|
||||
loading={{
|
||||
title: "Verifying 2FA code",
|
||||
subtitle: "Please wait while you are authenticated."
|
||||
title: strings.verifying2faCode(),
|
||||
subtitle: strings.authWait()
|
||||
}}
|
||||
onSubmit={async (form) => {
|
||||
if (!form.code) throw new Error("2FA code is required.");
|
||||
if (!form.code) throw new Error(strings.coreRequired());
|
||||
|
||||
const loginForm: MFALoginFormData = {
|
||||
code: form.code,
|
||||
@@ -695,9 +692,9 @@ function MFACode(props: BaseAuthComponentProps<"mfa:code">) {
|
||||
{isSending ? (
|
||||
<Loading size={18} />
|
||||
) : enabled ? (
|
||||
`Resend code`
|
||||
strings.resendCode()
|
||||
) : (
|
||||
`Resend in ${elapsed}`
|
||||
strings.resendCode(elapsed)
|
||||
)}
|
||||
</Text>
|
||||
),
|
||||
@@ -708,7 +705,7 @@ function MFACode(props: BaseAuthComponentProps<"mfa:code">) {
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
<SubmitButton text="Submit" />
|
||||
<SubmitButton text={strings.submit()} />
|
||||
<Button
|
||||
type="button"
|
||||
mt={4}
|
||||
@@ -755,11 +752,11 @@ function MFASelector(props: BaseAuthComponentProps<"mfa:select">) {
|
||||
return (
|
||||
<AuthForm
|
||||
type="mfa:select"
|
||||
title="Select two-factor authentication method"
|
||||
subtitle={`Where should we send you the authentication code?`}
|
||||
title={strings.select2faMethod()}
|
||||
subtitle={strings.select2faCodeHelpText()}
|
||||
loading={{
|
||||
title: "Logging you in",
|
||||
subtitle: "Please wait while you are authenticated."
|
||||
title: strings.loggingIn(),
|
||||
subtitle: strings.authWait()
|
||||
}}
|
||||
onSubmit={async () => {
|
||||
const selectedType = MFAMethods[selected];
|
||||
@@ -907,7 +904,7 @@ export function AuthForm<T extends AuthRoutes>(props: AuthFormProps<T>) {
|
||||
openURL("/notes/");
|
||||
}}
|
||||
>
|
||||
Skip & go directly to the app
|
||||
{strings.skipAndGoToApp()}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import { useEffect } from "react";
|
||||
import { Flex, Text } from "@theme-ui/components";
|
||||
import { useQueryParams } from "../navigation";
|
||||
import { MailCheck, Discord, Twitter, Reddit } from "../components/icons";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
function EmailConfirmed() {
|
||||
const [{ userId }] = useQueryParams();
|
||||
@@ -73,7 +74,7 @@ function EmailConfirmed() {
|
||||
color: "icon"
|
||||
}}
|
||||
>
|
||||
Your email has been confirmed.
|
||||
{strings.emailConfirmed()}
|
||||
</Text>
|
||||
<Text
|
||||
variant="body"
|
||||
@@ -85,7 +86,7 @@ function EmailConfirmed() {
|
||||
color: "var(--paragraph-secondary)"
|
||||
}}
|
||||
>
|
||||
Thank you for choosing end-to-end encrypted note taking.
|
||||
{strings.confirmEmailThankyou()}
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex
|
||||
@@ -130,7 +131,7 @@ function BlogPromoBanner() {
|
||||
}}
|
||||
>
|
||||
<Text variant="heading" sx={{ fontSize: "1.2em", textAlign: "center" }}>
|
||||
Share Notesnook with your friends!
|
||||
{strings.shareWithFriends()}
|
||||
</Text>
|
||||
<Text
|
||||
variant="body"
|
||||
@@ -142,7 +143,7 @@ function BlogPromoBanner() {
|
||||
color: "paragraph"
|
||||
}}
|
||||
>
|
||||
{`Because where's the fun in nookin' alone?`}
|
||||
{strings.shareWithFriendsDesc()}
|
||||
</Text>
|
||||
<Flex mt={5}>
|
||||
{social.map((account) => (
|
||||
@@ -166,11 +167,11 @@ function BlogPromoBanner() {
|
||||
color: "paragraph"
|
||||
}}
|
||||
>
|
||||
Use{" "}
|
||||
{strings.tagPromoWinText()[0]}{" "}
|
||||
<Text as="span" sx={{ fontWeight: "bold", color: "accent" }}>
|
||||
#notesnook
|
||||
{strings.tagPromoWinText()[1]}
|
||||
</Text>{" "}
|
||||
and get a chance to win free promo codes.
|
||||
{strings.tagPromoWinText()[2]}
|
||||
</Text>
|
||||
{/* <p>
|
||||
Listen. We want you to buy Notesnook Pro. It's as simple as that. Since
|
||||
|
||||
@@ -63,6 +63,7 @@ import {
|
||||
ImperativePanelHandle
|
||||
} from "react-resizable-panels";
|
||||
import { AddNotebookDialog } from "../dialogs/add-notebook-dialog";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
type NotebookProps = {
|
||||
rootId: string;
|
||||
@@ -231,7 +232,7 @@ function SubNotebooks({
|
||||
<Flex sx={{ alignItems: "center" }}>
|
||||
{isCollapsed ? <ChevronRight size={16} /> : <ChevronDown size={16} />}
|
||||
<Text variant="subBody" sx={{ fontSize: 11 }}>
|
||||
NOTEBOOKS
|
||||
{strings.notebooksAllCaps()}
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex sx={{ alignItems: "center" }}>
|
||||
@@ -470,7 +471,7 @@ function NotebookHeader({
|
||||
variant="icon"
|
||||
sx={{ p: 0, flexShrink: 0 }}
|
||||
onClick={() => navigateCrumb("notebooks")}
|
||||
title="Notebooks"
|
||||
title={strings.notebooks()}
|
||||
>
|
||||
<Notebook2 size={14} />
|
||||
</Button>
|
||||
@@ -576,7 +577,7 @@ function NotebookHeader({
|
||||
variant="secondary"
|
||||
sx={{ borderRadius: 100, width: 30, height: 30 }}
|
||||
p={0}
|
||||
title="Edit notebook"
|
||||
title={strings.editNotebook()}
|
||||
onClick={() => hashNavigate(`/notebooks/${notebook.id}/edit`)}
|
||||
>
|
||||
<Edit size={16} />
|
||||
@@ -591,7 +592,7 @@ function NotebookHeader({
|
||||
)}
|
||||
<Text as="em" variant="subBody" mt={2}>
|
||||
{/* {pluralize(topics.length, "topic")}, */}
|
||||
{pluralize(totalNotes, "note")}
|
||||
{strings.notes(totalNotes || 0)}
|
||||
</Text>
|
||||
</Flex>
|
||||
);
|
||||
|
||||
@@ -30,6 +30,7 @@ import Config from "../utils/config";
|
||||
import { ErrorText } from "../components/error-text";
|
||||
import { EVENTS, User } from "@notesnook/core";
|
||||
import { RecoveryKeyDialog } from "../dialogs/recovery-key-dialog";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
type RecoveryMethodType = "key" | "backup" | "reset";
|
||||
type RecoveryMethodsFormData = Record<string, unknown>;
|
||||
@@ -173,8 +174,8 @@ function Recovery(props: RecoveryProps) {
|
||||
>
|
||||
{isAuthenticating ? (
|
||||
<Loader
|
||||
title="Authenticating user"
|
||||
text={"Please wait while you are authenticated."}
|
||||
title={strings.authenticatingUser()}
|
||||
text={strings.authWait()}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
@@ -190,7 +191,7 @@ function Recovery(props: RecoveryProps) {
|
||||
}}
|
||||
variant={"body"}
|
||||
>
|
||||
Authenticated as {user?.email}
|
||||
{strings.authenticatedAs()} {user?.email}
|
||||
</Text>
|
||||
<Button
|
||||
sx={{
|
||||
@@ -203,7 +204,7 @@ function Recovery(props: RecoveryProps) {
|
||||
variant={"secondary"}
|
||||
onClick={() => openURL("/login")}
|
||||
>
|
||||
Remembered your password?
|
||||
{strings.rememberedYourPassword()}
|
||||
</Button>
|
||||
</Flex>
|
||||
{Route && (
|
||||
@@ -225,39 +226,16 @@ export default Recovery;
|
||||
|
||||
type RecoveryMethod = {
|
||||
type: RecoveryMethodType;
|
||||
title: string;
|
||||
title: () => string;
|
||||
testId: string;
|
||||
description: string;
|
||||
description: () => string;
|
||||
isDangerous?: boolean;
|
||||
};
|
||||
|
||||
const recoveryMethods: RecoveryMethod[] = [
|
||||
{
|
||||
type: "key",
|
||||
testId: "step-recovery-key",
|
||||
title: "Use recovery key",
|
||||
description:
|
||||
"Your data recovery key is basically a hashed version of your password (plus some random salt). It can be used to decrypt your data for re-encryption."
|
||||
},
|
||||
{
|
||||
type: "backup",
|
||||
testId: "step-backup",
|
||||
title: "Use a backup file",
|
||||
description:
|
||||
"If you don't have a recovery key, you can recover your data by restoring a Notesnook data backup file (.nnbackup)."
|
||||
},
|
||||
{
|
||||
type: "reset",
|
||||
testId: "step-reset-account",
|
||||
title: "Clear data & reset account",
|
||||
description:
|
||||
"EXTREMELY DANGEROUS! This action is irreversible. All your data including notes, notebooks, attachments & settings will be deleted. This is a full account reset. Proceed with caution.",
|
||||
isDangerous: true
|
||||
}
|
||||
];
|
||||
function RecoveryMethods(props: BaseRecoveryComponentProps<"methods">) {
|
||||
const { navigate } = props;
|
||||
const [selected, setSelected] = useState(0);
|
||||
const recoveryMethods = strings.accountRecoveryMethods as RecoveryMethod[];
|
||||
|
||||
if (isSessionExpired()) {
|
||||
navigate("new");
|
||||
@@ -268,8 +246,8 @@ function RecoveryMethods(props: BaseRecoveryComponentProps<"methods">) {
|
||||
<RecoveryForm
|
||||
testId="step-recovery-methods"
|
||||
type="methods"
|
||||
title="Choose a recovery method"
|
||||
subtitle="How do you want to recover your account?"
|
||||
title={strings.chooseRecoveryMethod()}
|
||||
subtitle={strings.chooseRecoveryMethodDesc()}
|
||||
onSubmit={async () => {
|
||||
const selectedMethod = recoveryMethods[selected].type;
|
||||
navigate(`method:${selectedMethod}`, {
|
||||
@@ -304,7 +282,7 @@ function RecoveryMethods(props: BaseRecoveryComponentProps<"methods">) {
|
||||
color: method.isDangerous ? "var(--heading-error)" : "heading"
|
||||
}}
|
||||
>
|
||||
{method.title}
|
||||
{method.title()}
|
||||
</Text>
|
||||
<Text
|
||||
variant={"body"}
|
||||
@@ -314,7 +292,7 @@ function RecoveryMethods(props: BaseRecoveryComponentProps<"methods">) {
|
||||
: "var(--paragraph-secondary)"
|
||||
}}
|
||||
>
|
||||
{method.description}
|
||||
{method.description()}
|
||||
</Text>
|
||||
</Button>
|
||||
))}
|
||||
@@ -341,17 +319,17 @@ function RecoveryKeyMethod(props: BaseRecoveryComponentProps<"method:key">) {
|
||||
<RecoveryForm
|
||||
testId="step-recovery-key"
|
||||
type="method:key"
|
||||
title="Recover your account"
|
||||
subtitle={"Use a data recovery key to reset your account password."}
|
||||
title={strings.accountRecovery()}
|
||||
subtitle={strings.accountRecoveryWithKey()}
|
||||
loading={{
|
||||
title: `Downloading your data (${progress})`,
|
||||
subtitle: "Please wait while your data is downloaded & decrypted."
|
||||
title: `${strings.network.downloading()} (${progress})`,
|
||||
subtitle: strings.keyRecoveryProgressDesc()
|
||||
}}
|
||||
onSubmit={async (form) => {
|
||||
setProgress(0);
|
||||
|
||||
const user = await db.user.getUser();
|
||||
if (!user) throw new Error("User not authenticated");
|
||||
if (!user) throw new Error(strings.notLoggedIn());
|
||||
await db.storage().write(`_uk_@${user.email}@_k`, form.recoveryKey);
|
||||
await db.sync({ type: "fetch", force: true });
|
||||
navigate("backup");
|
||||
@@ -360,12 +338,12 @@ function RecoveryKeyMethod(props: BaseRecoveryComponentProps<"method:key">) {
|
||||
<AuthField
|
||||
id="recoveryKey"
|
||||
type="password"
|
||||
label="Enter your data recovery key"
|
||||
helpText="Your data recovery key will be used to decrypt your data"
|
||||
label={strings.enterRecoveryKey()}
|
||||
helpText={strings.enterRecoveryKeyHelp()}
|
||||
autoComplete="none"
|
||||
autoFocus
|
||||
/>
|
||||
<SubmitButton text="Start account recovery" />
|
||||
<SubmitButton text={strings.startAccountRecovery()} />
|
||||
|
||||
<Button
|
||||
type="button"
|
||||
@@ -374,7 +352,7 @@ function RecoveryKeyMethod(props: BaseRecoveryComponentProps<"method:key">) {
|
||||
onClick={() => navigate("methods")}
|
||||
sx={{ color: "paragraph" }}
|
||||
>
|
||||
{`Don't have your recovery key?`}
|
||||
{strings.dontHaveRecoveryKey()}
|
||||
</Button>
|
||||
</RecoveryForm>
|
||||
);
|
||||
@@ -396,11 +374,11 @@ function BackupFileMethod(props: BaseRecoveryComponentProps<"method:backup">) {
|
||||
<RecoveryForm
|
||||
testId="step-backup-file"
|
||||
type="method:backup"
|
||||
title="Recover your account"
|
||||
title={strings.accountRecovery()}
|
||||
subtitle={
|
||||
<ErrorText
|
||||
sx={{ fontSize: "body" }}
|
||||
error="All the data in your account will be overwritten with the data in the backup file. There is no way to reverse this action."
|
||||
error={strings.backupFileRecoveryError()}
|
||||
/>
|
||||
}
|
||||
onSubmit={async () => {
|
||||
@@ -410,19 +388,19 @@ function BackupFileMethod(props: BaseRecoveryComponentProps<"method:backup">) {
|
||||
<AuthField
|
||||
id="backupFile"
|
||||
type="text"
|
||||
label="Select backup file"
|
||||
helpText="Backup files have .nnbackup extension"
|
||||
label={strings.selectBackupFile()}
|
||||
helpText={strings.backupFileHelpText()}
|
||||
autoComplete="none"
|
||||
autoFocus
|
||||
disabled
|
||||
action={{
|
||||
component: <Text variant={"body"}>Browse</Text>,
|
||||
component: <Text variant={"body"}>{strings.browse()}</Text>,
|
||||
onClick: async () => {
|
||||
setBackupFile(await selectBackupFile());
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<SubmitButton text="Start account recovery" />
|
||||
<SubmitButton text={strings.startAccountRecovery()} />
|
||||
|
||||
<Button
|
||||
type="button"
|
||||
@@ -431,7 +409,7 @@ function BackupFileMethod(props: BaseRecoveryComponentProps<"method:backup">) {
|
||||
onClick={() => navigate("methods")}
|
||||
sx={{ color: "paragraph" }}
|
||||
>
|
||||
{`Don't have a backup file?`}
|
||||
{strings.dontHaveBackupFile()}
|
||||
</Button>
|
||||
</RecoveryForm>
|
||||
);
|
||||
@@ -444,21 +422,18 @@ function BackupData(props: BaseRecoveryComponentProps<"backup">) {
|
||||
<RecoveryForm
|
||||
testId="step-backup-data"
|
||||
type="backup"
|
||||
title="Backup your data"
|
||||
subtitle={
|
||||
"Please download a backup of your data as your account will be cleared before recovery."
|
||||
}
|
||||
title={strings.backupYourData()}
|
||||
subtitle={strings.backupYourDataDesc()}
|
||||
loading={{
|
||||
title: "Creating backup...",
|
||||
subtitle:
|
||||
"Please wait while we create a backup file for you to download."
|
||||
title: strings.backingUpData() + "...",
|
||||
subtitle: strings.backingUpDataWait()
|
||||
}}
|
||||
onSubmit={async () => {
|
||||
await createBackup({ rescueMode: true, mode: "full" });
|
||||
navigate("new");
|
||||
}}
|
||||
>
|
||||
<SubmitButton text="Download backup file" />
|
||||
<SubmitButton text={strings.downloadBackupFile()} />
|
||||
</RecoveryForm>
|
||||
);
|
||||
}
|
||||
@@ -480,13 +455,11 @@ function NewPassword(props: BaseRecoveryComponentProps<"new">) {
|
||||
<RecoveryForm
|
||||
testId="step-new-password"
|
||||
type="new"
|
||||
title="Reset account password"
|
||||
subtitle={
|
||||
"Notesnook is E2E encrypted — your password never leaves this device."
|
||||
}
|
||||
title={strings.resetAccountPassword()}
|
||||
subtitle={strings.accountPassDesc()}
|
||||
loading={{
|
||||
title: `Resetting account password (${progress})`,
|
||||
subtitle: "Please wait while we reset your account password."
|
||||
title: `${strings.resettingAccountPassword()} (${progress})`,
|
||||
subtitle: strings.resetPasswordWait()
|
||||
}}
|
||||
onSubmit={async (form) => {
|
||||
setProgress(0);
|
||||
@@ -514,18 +487,18 @@ function NewPassword(props: BaseRecoveryComponentProps<"new">) {
|
||||
id="password"
|
||||
type="password"
|
||||
autoComplete="current-password"
|
||||
label="Set new password"
|
||||
helpText="Your account password must be strong & unique."
|
||||
label={strings.newPassword()}
|
||||
helpText={strings.newPasswordHelp()}
|
||||
defaultValue={form?.password}
|
||||
/>
|
||||
<AuthField
|
||||
id="confirmPassword"
|
||||
type="password"
|
||||
autoComplete="confirm-password"
|
||||
label="Confirm new password"
|
||||
label={strings.confirmPassword()}
|
||||
defaultValue={form?.confirmPassword}
|
||||
/>
|
||||
<SubmitButton text="Continue" />
|
||||
<SubmitButton text={strings.continue()} />
|
||||
</>
|
||||
)}
|
||||
</RecoveryForm>
|
||||
@@ -553,15 +526,15 @@ function Final(_props: BaseRecoveryComponentProps<"final">) {
|
||||
<RecoveryForm
|
||||
testId="step-finished"
|
||||
type="final"
|
||||
title="Recovery successful!"
|
||||
subtitle={"Your account has been recovered."}
|
||||
title={strings.recoverySuccess()}
|
||||
subtitle={strings.recoverySuccessDesc()}
|
||||
onSubmit={async () => {
|
||||
openURL(isSessionExpired() ? "/sessionexpired" : "/login");
|
||||
}}
|
||||
>
|
||||
<SubmitButton
|
||||
text={
|
||||
isSessionExpired() ? "Continue with login" : "Login to your account"
|
||||
isSessionExpired() ? strings.continue() : strings.loginToYourAccount()
|
||||
}
|
||||
/>
|
||||
</RecoveryForm>
|
||||
|
||||
245
package-lock.json
generated
245
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
22
packages/editor-mobile/package-lock.json
generated
22
packages/editor-mobile/package-lock.json
generated
@@ -4457,7 +4457,7 @@
|
||||
"version": "15.7.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz",
|
||||
"integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==",
|
||||
"devOptional": true
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/q": {
|
||||
"version": "1.5.8",
|
||||
@@ -4481,7 +4481,7 @@
|
||||
"version": "18.2.39",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.39.tgz",
|
||||
"integrity": "sha512-Oiw+ppED6IremMInLV4HXGbfbG6GyziY3kqAwJYOR0PNbkYDmLWQA3a95EhdSmamsvbkJN96ZNN+YD+fGjzSBA==",
|
||||
"devOptional": true,
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/prop-types": "*",
|
||||
"@types/scheduler": "*",
|
||||
@@ -4516,7 +4516,7 @@
|
||||
"version": "0.16.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz",
|
||||
"integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==",
|
||||
"devOptional": true
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/semver": {
|
||||
"version": "7.5.6",
|
||||
@@ -9801,7 +9801,7 @@
|
||||
"version": "9.0.21",
|
||||
"resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz",
|
||||
"integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==",
|
||||
"devOptional": true,
|
||||
"dev": true,
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/immer"
|
||||
@@ -17897,20 +17897,6 @@
|
||||
"is-typedarray": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "4.9.5",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
|
||||
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/unbox-primitive": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user