diff --git a/apps/mobile/app/common/database/index.js b/apps/mobile/app/common/database/index.js
index c7b1407ca..673cc7710 100644
--- a/apps/mobile/app/common/database/index.js
+++ b/apps/mobile/app/common/database/index.js
@@ -33,11 +33,11 @@ import Storage from "./storage";
import { RNSqliteDriver } from "./sqlite.kysely";
import { getDatabaseKey } from "./encryption";
import SettingsService from "../../services/settings";
+import { strings } from "@notesnook/intl";
export async function setupDatabase(password) {
const key = await getDatabaseKey(password);
- if (!key)
- throw new Error("Database setup failed, could not get database key");
+ if (!key) throw new Error(strings.databaseSetupFailed());
console.log("Opening database with key:", !!key);
diff --git a/apps/mobile/app/common/database/sqlite.kysely.ts b/apps/mobile/app/common/database/sqlite.kysely.ts
index 3d47f812e..a5898af80 100644
--- a/apps/mobile/app/common/database/sqlite.kysely.ts
+++ b/apps/mobile/app/common/database/sqlite.kysely.ts
@@ -24,6 +24,7 @@ import type {
} from "@streetwriters/kysely";
import { CompiledQuery } from "@streetwriters/kysely";
import { QuickSQLiteConnection, open } from "react-native-quick-sqlite";
+import { strings } from "@notesnook/intl";
type Config = { dbName: string; async: boolean; location: string };
@@ -97,7 +98,7 @@ class RNSqliteConnection implements DatabaseConnection {
constructor(private readonly db: QuickSQLiteConnection) {}
streamQuery(): AsyncIterableIterator> {
- throw new Error("wasqlite driver doesn't support streaming");
+ throw new Error(strings.streamingNotSupported());
}
async executeQuery(
diff --git a/apps/mobile/app/components/auth/forgot-password.js b/apps/mobile/app/components/auth/forgot-password.js
index 942784f96..d0af8fc98 100644
--- a/apps/mobile/app/components/auth/forgot-password.js
+++ b/apps/mobile/app/components/auth/forgot-password.js
@@ -58,7 +58,7 @@ export const ForgotPassword = () => {
lastRecoveryEmailTime &&
Date.now() - JSON.parse(lastRecoveryEmailTime) < 60000 * 3
) {
- throw new Error("Please wait before requesting another email");
+ throw new Error(strings.pleaseWaitBeforeSendEmail());
}
await db.user.recoverAccount(email.current.toLowerCase());
SettingsService.set({
diff --git a/apps/mobile/app/components/auth/use-login.js b/apps/mobile/app/components/auth/use-login.js
index d037dff27..50ff86485 100644
--- a/apps/mobile/app/components/auth/use-login.js
+++ b/apps/mobile/app/components/auth/use-login.js
@@ -98,7 +98,7 @@ export const useLogin = (onFinishLogin, sessionExpired = false) => {
}
}, mfaInfo);
} else {
- finishWithError(new Error("Unable to send 2FA code"));
+ finishWithError(new Error(strings.unableToSend2faCode()));
}
break;
}
@@ -132,7 +132,7 @@ export const useLogin = (onFinishLogin, sessionExpired = false) => {
const finishLogin = async () => {
const user = await db.user.getUser();
- if (!user) throw new Error("Email or password incorrect!");
+ if (!user) throw new Error(strings.emailOrPasswordIncorrect());
PremiumService.setPremiumStatus();
setUser(user);
clearMessage();
diff --git a/apps/mobile/app/components/intro/index.js b/apps/mobile/app/components/intro/index.js
index 93acf19c2..7b1b2ea54 100644
--- a/apps/mobile/app/components/intro/index.js
+++ b/apps/mobile/app/components/intro/index.js
@@ -155,7 +155,7 @@ const Intro = ({ navigation }) => {
index={0}
useReactNativeGestureHandler={true}
showPagination
- data={strings.introData()}
+ data={strings.introData}
paginationActiveColor={colors.primary.accent}
paginationStyleItem={{
width: 10,
diff --git a/apps/mobile/app/components/premium/pricing-plans.tsx b/apps/mobile/app/components/premium/pricing-plans.tsx
index 0298231f0..a7ce08fd7 100644
--- a/apps/mobile/app/components/premium/pricing-plans.tsx
+++ b/apps/mobile/app/components/premium/pricing-plans.tsx
@@ -48,6 +48,7 @@ import Paragraph from "../ui/typography/paragraph";
import { Walkthrough } from "../walkthroughs";
import { PricingItem } from "./pricing-item";
import { useSettingStore } from "../../stores/use-setting-store";
+import { strings } from "@notesnook/intl";
const UUID_PREFIX = "0bdaea";
const UUID_VERSION = "4";
@@ -516,7 +517,7 @@ export const PricingPlans = ({
setBuying(true);
try {
if (!(await getPromo(value as string)))
- throw new Error("Error applying promo code");
+ throw new Error(strings.errorApplyingPromoCode());
ToastManager.show({
heading: "Discount applied!",
type: "success",
diff --git a/apps/mobile/app/components/sheets/change-email/index.tsx b/apps/mobile/app/components/sheets/change-email/index.tsx
index ecdfd5551..e6c8a35d6 100644
--- a/apps/mobile/app/components/sheets/change-email/index.tsx
+++ b/apps/mobile/app/components/sheets/change-email/index.tsx
@@ -69,7 +69,7 @@ export const ChangeEmail = ({ close }: ChangeEmailProps) => {
const verified = await db.user?.verifyPassword(
emailChangeData.current.password
);
- if (!verified) throw new Error("Password is incorrect");
+ if (!verified) throw new Error(strings.passwordIncorrect());
await db.user?.sendVerificationEmail(emailChangeData.current.email);
setStep(EmailChangeSteps.changeEmail);
setLoading(false);
diff --git a/apps/mobile/app/components/sheets/reminder/index.tsx b/apps/mobile/app/components/sheets/reminder/index.tsx
index f16aee6fa..ec2f439fe 100644
--- a/apps/mobile/app/components/sheets/reminder/index.tsx
+++ b/apps/mobile/app/components/sheets/reminder/index.tsx
@@ -169,9 +169,7 @@ export default function ReminderSheet({
async function saveReminder() {
try {
if (!(await Notifications.checkAndRequestPermissions(true)))
- throw new Error(
- "App does not have permission to schedule notifications"
- );
+ throw new Error(strings.noNotificationPermission());
if (!date && reminderMode !== ReminderModes.Permanent) return;
if (
reminderMode === ReminderModes.Repeat &&
@@ -179,12 +177,12 @@ export default function ReminderSheet({
recurringMode !== "year" &&
selectedDays.length === 0
)
- throw new Error("Please select the day to repeat the reminder on");
+ throw new Error(strings.selectDayError());
- if (!title.current) throw new Error("Please set title of the reminder");
+ if (!title.current) throw new Error(strings.setTitleError());
if (date.getTime() < Date.now() && reminderMode === "once") {
titleRef?.current?.focus();
- throw new Error("Reminder date must be set in future");
+ throw new Error(strings.dateError());
}
date.setSeconds(0, 0);
diff --git a/apps/mobile/app/components/side-menu/index.tsx b/apps/mobile/app/components/side-menu/index.tsx
index d55158877..dc3529b39 100644
--- a/apps/mobile/app/components/side-menu/index.tsx
+++ b/apps/mobile/app/components/side-menu/index.tsx
@@ -87,7 +87,13 @@ export const SideMenu = React.memo(
return (
diff --git a/apps/mobile/app/components/side-menu/menu-item.tsx b/apps/mobile/app/components/side-menu/menu-item.tsx
index f04a8dd59..82f2e7067 100644
--- a/apps/mobile/app/components/side-menu/menu-item.tsx
+++ b/apps/mobile/app/components/side-menu/menu-item.tsx
@@ -113,10 +113,10 @@ function _MenuItem({
/>
{isFocused ? (
- {item.name}
+ {item.title || item.name}
) : (
- {item.name}
+ {item.title || item.name}
)}
{item.isBeta ? (
diff --git a/apps/mobile/app/screens/editor/index.tsx b/apps/mobile/app/screens/editor/index.tsx
index 274ddfe8c..2fa1d9a9f 100755
--- a/apps/mobile/app/screens/editor/index.tsx
+++ b/apps/mobile/app/screens/editor/index.tsx
@@ -60,6 +60,7 @@ import {
} from "./tiptap/utils";
import { tabBarRef } from "../../utils/global-refs";
import { strings } from "@notesnook/intl";
+import { i18n } from "@lingui/core";
const style: ViewStyle = {
height: "100%",
@@ -146,6 +147,10 @@ const Editor = React.memo(
nestedScrollEnabled
onError={onError}
injectedJavaScriptBeforeContentLoaded={`
+ globalThis.LINGUI_LOCALE = "${i18n.locale}";
+ globalThis.LINGUI_LOCALE_DATA = ${JSON.stringify({
+ [i18n.locale]: i18n.messages
+ })};
globalThis.__DEV__ = ${__DEV__}
globalThis.readonly=${readonly};
globalThis.noToolbar=${noToolbar};
@@ -269,7 +274,7 @@ const useLockedNoteHandler = () => {
if (enrollBiometrics && note) {
try {
const unlocked = await db.vault.unlock(password);
- if (!unlocked) throw new Error("Incorrect vault password");
+ if (!unlocked) throw new Error(strings.passwordIncorrect());
await BiometricService.storeCredentials(password);
eSendEvent("vaultUpdated");
ToastManager.show({
diff --git a/apps/mobile/app/screens/home/index.tsx b/apps/mobile/app/screens/home/index.tsx
index 94fe52473..8511b8763 100755
--- a/apps/mobile/app/screens/home/index.tsx
+++ b/apps/mobile/app/screens/home/index.tsx
@@ -52,12 +52,12 @@ export const Home = ({ navigation, route }: NavigationProps<"Notes">) => {
{
Navigation.push("Search", {
- placeholder: `Type a keyword to search in ${route.name?.toLowerCase()}`,
+ placeholder: strings.searchInRoute(route.name),
type: "note",
title: route.name,
route: route.name
@@ -72,7 +72,9 @@ export const Home = ({ navigation, route }: NavigationProps<"Notes">) => {
dataType="note"
renderedInRoute={route.name}
loading={loading || !isFocused}
- headerTitle={strings.routes[route.name]?.()}
+ headerTitle={strings.routes[
+ route.name as keyof typeof strings.routes
+ ]?.()}
placeholder={{
title: route.name?.toLowerCase(),
paragraph: strings.notesEmpty(),
diff --git a/apps/mobile/app/screens/settings/picker/pickers.jsx b/apps/mobile/app/screens/settings/picker/pickers.jsx
index b772e39e1..3bd626ee7 100644
--- a/apps/mobile/app/screens/settings/picker/pickers.jsx
+++ b/apps/mobile/app/screens/settings/picker/pickers.jsx
@@ -57,7 +57,7 @@ export const HomePicker = createSettingsPicker({
});
},
formatValue: (item) => {
- return typeof item === "object" ? item.name : item;
+ return strings.routes[typeof item === "object" ? item.name : item]();
},
getItemKey: (item) => item.name,
options: MenuItemsList.slice(0, MenuItemsList.length - 1),
@@ -71,7 +71,11 @@ export const TrashIntervalPicker = createSettingsPicker({
db.settings.setTrashCleanupInterval(item);
},
formatValue: (item) => {
- return item === -1 ? "Never" : item === 1 ? "Daily" : item + " days";
+ return item === -1
+ ? strings.never()
+ : item === 1
+ ? strings.reminderRecurringMode.day()
+ : item + " " + strings.days();
},
getItemKey: (item) => item.toString(),
options: [-1, 1, 7, 30, 365],
@@ -109,7 +113,7 @@ export const TimeFormatPicker = createSettingsPicker({
});
},
formatValue: (item) => {
- return `${item} (${dayjs().format(TimeFormats[item])})`;
+ return `${strings[item]()} (${dayjs().format(TimeFormats[item])})`;
},
getItemKey: (item) => item,
options: TIME_FORMATS,
@@ -122,9 +126,7 @@ export const BackupReminderPicker = createSettingsPicker({
SettingsService.set({ reminder: item });
},
formatValue: (item) => {
- return item === "useroff" || item === "off" || item === "never"
- ? "Off"
- : item.slice(0, 1).toUpperCase() + item.slice(1);
+ return item === "useroff" ? strings.off() : strings[item]?.();
},
getItemKey: (item) => item,
options: ["useroff", "daily", "weekly", "monthly"],
@@ -173,12 +175,12 @@ export const ApplockTimerPicker = createSettingsPicker({
},
formatValue: (item) => {
return item === -1
- ? "Never"
+ ? strings.never()
: item === 0 || item === undefined
- ? "Immediately"
+ ? strings.immediately()
: item === 1
- ? "1 minute"
- : item + " minutes";
+ ? strings.minutes(1)
+ : strings.minutes(item);
},
getItemKey: (item) => item.toString(),
options: [-1, 0, 1, 5, 15, 30],
diff --git a/apps/mobile/app/screens/settings/settings-data.tsx b/apps/mobile/app/screens/settings/settings-data.tsx
index a8d50f8fc..fe485828f 100644
--- a/apps/mobile/app/screens/settings/settings-data.tsx
+++ b/apps/mobile/app/screens/settings/settings-data.tsx
@@ -812,7 +812,7 @@ export const settingsGroups: SettingSection[] = [
},
{
id: "privacy-security",
- name: "Privacy and security",
+ name: strings.privacyAndSecurity(),
sections: [
{
id: "marketing-emails",
@@ -1460,7 +1460,7 @@ export const settingsGroups: SettingSection[] = [
},
{
id: "legal",
- name: "legal",
+ name: strings.legal(),
sections: [
{
id: "tos",
diff --git a/apps/mobile/app/screens/tags/index.tsx b/apps/mobile/app/screens/tags/index.tsx
index ecd457a95..ec7d90c90 100644
--- a/apps/mobile/app/screens/tags/index.tsx
+++ b/apps/mobile/app/screens/tags/index.tsx
@@ -60,7 +60,7 @@ export const Tags = ({ navigation, route }: NavigationProps<"Tags">) => {
hasSearch={true}
onSearch={() => {
Navigation.push("Search", {
- placeholder: `Type a keyword to search in ${route.name}`,
+ placeholder: strings.searchInRoute(route.name),
type: "tag",
title: route.name,
route: route.name
diff --git a/apps/mobile/app/screens/trash/index.tsx b/apps/mobile/app/screens/trash/index.tsx
index 4291df60d..63d57d4a8 100644
--- a/apps/mobile/app/screens/trash/index.tsx
+++ b/apps/mobile/app/screens/trash/index.tsx
@@ -93,7 +93,7 @@ export const Trash = ({ navigation, route }: NavigationProps<"Trash">) => {
hasSearch={true}
onSearch={() => {
Navigation.push("Search", {
- placeholder: `Type a keyword to search in ${route.name}`,
+ placeholder: strings.searchInRoute(route.name),
type: "trash",
title: route.name,
route: route.name
diff --git a/apps/mobile/app/services/backup.ts b/apps/mobile/app/services/backup.ts
index d1b8506d1..c664d2611 100644
--- a/apps/mobile/app/services/backup.ts
+++ b/apps/mobile/app/services/backup.ts
@@ -171,7 +171,7 @@ async function run(
if (!androidBackupDirectory)
return {
- error: new Error("Backup directory not selected"),
+ error: new Error(strings.backupDirectoryNotSelected()),
report: false
};
diff --git a/apps/mobile/native/globals.js b/apps/mobile/native/globals.js
index bf01a202c..7105ff29b 100644
--- a/apps/mobile/native/globals.js
+++ b/apps/mobile/native/globals.js
@@ -13,11 +13,12 @@ global.Buffer = require('buffer').Buffer;
import '../app/common/logger/index';
import { DOMParser } from './worker.js';
global.DOMParser = DOMParser;
-import { $en, setI18nGlobal } from "@notesnook/intl";
+import { $en, setI18nGlobal,$de } from "@notesnook/intl";
import { i18n } from "@lingui/core";
i18n.load({
en: $en,
+ de: $de
});
-i18n.activate("en");
+i18n.activate("de");
setI18nGlobal(i18n);
diff --git a/packages/editor-mobile/package-lock.json b/packages/editor-mobile/package-lock.json
index 12cdf7619..7d5d336f3 100644
--- a/packages/editor-mobile/package-lock.json
+++ b/packages/editor-mobile/package-lock.json
@@ -10,9 +10,12 @@
"hasInstallScript": true,
"dependencies": {
"@emotion/react": "11.11.1",
+ "@lingui/core": "4.11.2",
+ "@lingui/react": "4.11.2",
"@mdi/js": "^7.2.96",
"@mdi/react": "^1.6.0",
"@notesnook/editor": "file:../editor",
+ "@notesnook/intl": "file:../intl",
"@notesnook/theme": "file:../theme",
"@szhsin/react-menu": "^4.1.0",
"buffer": "^6.0.3",
@@ -126,6 +129,27 @@
"zustand": ">=4"
}
},
+ "../intl": {
+ "name": "@notesnook/intl",
+ "version": "1.0.0",
+ "devDependencies": {
+ "@babel/core": "^7.24.9",
+ "@babel/preset-env": "^7.20.2",
+ "@lingui/cli": "^4.11.2",
+ "@lingui/macro": "^4.11.2",
+ "@lingui/swc-plugin": "^4.0.7",
+ "@rspack/cli": "^0.6.2",
+ "@rspack/core": "^0.6.2",
+ "@types/react": "^18.2.39",
+ "babel-plugin-macros": "^3.1.0",
+ "react": "18.2.0",
+ "typescript": "5.5.3"
+ },
+ "peerDependencies": {
+ "@lingui/macro": "*",
+ "react": ">=18"
+ }
+ },
"../theme": {
"name": "@notesnook/theme",
"version": "2.1.3",
@@ -3613,6 +3637,46 @@
"integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==",
"dev": true
},
+ "node_modules/@lingui/core": {
+ "version": "4.11.2",
+ "resolved": "https://registry.npmjs.org/@lingui/core/-/core-4.11.2.tgz",
+ "integrity": "sha512-5wFmpHeDbLXEqaEUwlayS4SoqrCbDI3/bVRlwhmdNCeUcUYWh+7dTDlQnp4tPek1x1dEppABIkdN/0qLDdKcBQ==",
+ "dependencies": {
+ "@babel/runtime": "^7.20.13",
+ "@lingui/message-utils": "4.11.2",
+ "unraw": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/@lingui/message-utils": {
+ "version": "4.11.2",
+ "resolved": "https://registry.npmjs.org/@lingui/message-utils/-/message-utils-4.11.2.tgz",
+ "integrity": "sha512-3oJk7ZKExk4NVa4d3CM0z0iNqIokaFOWeu7lYVzu0oEX7DP6OxNjlCAtObIhJCB0FdIPz8sXxhDkyDHFj+eIvw==",
+ "dependencies": {
+ "@messageformat/parser": "^5.0.0",
+ "js-sha256": "^0.10.1"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/@lingui/react": {
+ "version": "4.11.2",
+ "resolved": "https://registry.npmjs.org/@lingui/react/-/react-4.11.2.tgz",
+ "integrity": "sha512-OKHCg3yPW2xhYWoY2kOz+eP7qpdkab+4tERUvJ9QJ9bzQ6OnPLCagaRftB3nqdKuWzKoA5F2VG2QLUhF7DjpGA==",
+ "dependencies": {
+ "@babel/runtime": "^7.20.13",
+ "@lingui/core": "4.11.2"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
"node_modules/@mdi/js": {
"version": "7.3.67",
"resolved": "https://registry.npmjs.org/@mdi/js/-/js-7.3.67.tgz",
@@ -3626,6 +3690,14 @@
"prop-types": "^15.7.2"
}
},
+ "node_modules/@messageformat/parser": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/@messageformat/parser/-/parser-5.1.0.tgz",
+ "integrity": "sha512-jKlkls3Gewgw6qMjKZ9SFfHUpdzEVdovKFtW1qRhJ3WI4FW5R/NnGDqr8SDGz+krWDO3ki94boMmQvGke1HwUQ==",
+ "dependencies": {
+ "moo": "^0.5.1"
+ }
+ },
"node_modules/@nicolo-ribaudo/eslint-scope-5-internals": {
"version": "5.1.1-v1",
"resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz",
@@ -3696,6 +3768,10 @@
"resolved": "../editor",
"link": true
},
+ "node_modules/@notesnook/intl": {
+ "resolved": "../intl",
+ "link": true
+ },
"node_modules/@notesnook/theme": {
"resolved": "../theme",
"link": true
@@ -4381,7 +4457,7 @@
"version": "15.7.11",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz",
"integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==",
- "dev": true
+ "devOptional": true
},
"node_modules/@types/q": {
"version": "1.5.8",
@@ -4405,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==",
- "dev": true,
+ "devOptional": true,
"dependencies": {
"@types/prop-types": "*",
"@types/scheduler": "*",
@@ -4440,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==",
- "dev": true
+ "devOptional": true
},
"node_modules/@types/semver": {
"version": "7.5.6",
@@ -9725,7 +9801,7 @@
"version": "9.0.21",
"resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz",
"integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==",
- "dev": true,
+ "devOptional": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/immer"
@@ -12531,6 +12607,11 @@
"jiti": "bin/jiti.js"
}
},
+ "node_modules/js-sha256": {
+ "version": "0.10.1",
+ "resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.10.1.tgz",
+ "integrity": "sha512-5obBtsz9301ULlsgggLg542s/jqtddfOpV5KJc4hajc9JV8GeY2gZHSVpYBn4nWqAUTJ9v+xwtbJ1mIBgIH5Vw=="
+ },
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -13200,6 +13281,11 @@
"mkdirp": "bin/cmd.js"
}
},
+ "node_modules/moo": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.2.tgz",
+ "integrity": "sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q=="
+ },
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
@@ -17811,6 +17897,20 @@
"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",
@@ -17914,6 +18014,11 @@
"integrity": "sha512-vRCqFv6UhXpWxZPyGDh/F3ZpNv8/qo7w6iufLpQg9aKnQ71qM4B5KiI7Mia9COcjEhrO9LueHpMYjYzsWH3OIg==",
"dev": true
},
+ "node_modules/unraw": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/unraw/-/unraw-3.0.0.tgz",
+ "integrity": "sha512-08/DA66UF65OlpUDIQtbJyrqTR0jTAlJ+jsnkQ4jxR7+K5g5YG1APZKQSMCE1vqqmD+2pv6+IdEjmopFatacvg=="
+ },
"node_modules/upath": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz",
diff --git a/packages/editor-mobile/package.json b/packages/editor-mobile/package.json
index 3a0a7b725..6f0d73fcb 100644
--- a/packages/editor-mobile/package.json
+++ b/packages/editor-mobile/package.json
@@ -8,6 +8,7 @@
"@mdi/react": "^1.6.0",
"@notesnook/editor": "file:../editor",
"@notesnook/theme": "file:../theme",
+ "@notesnook/intl": "file:../intl",
"@szhsin/react-menu": "^4.1.0",
"buffer": "^6.0.3",
"framer-motion": "^10.16.8",
@@ -17,7 +18,9 @@
"react-dom": "^18.2.0",
"react-freeze": "^1.0.3",
"tinycolor2": "1.6.0",
- "zustand": "^4.4.7"
+ "zustand": "^4.4.7",
+ "@lingui/react": "4.11.2",
+ "@lingui/core": "4.11.2"
},
"devDependencies": {
"@playwright/test": "^1.37.1",
diff --git a/packages/editor-mobile/src/components/editor.tsx b/packages/editor-mobile/src/components/editor.tsx
index f9e5b21a8..7ea654b40 100644
--- a/packages/editor-mobile/src/components/editor.tsx
+++ b/packages/editor-mobile/src/components/editor.tsx
@@ -51,6 +51,7 @@ import StatusBar from "./statusbar";
import Tags from "./tags";
import TiptapEditorWrapper from "./tiptap";
import Title from "./title";
+import { strings } from "@notesnook/intl";
globalThis.toBlobURL = toBlobURL as typeof globalThis.toBlobURL;
@@ -196,6 +197,7 @@ const Tiptap = ({
copyToClipboard: (text) => {
globalThis.editorControllers[tab.id]?.copyToClipboard(text);
},
+ placeholder: strings.startWritingNote(),
onSelectionUpdate: () => {
if (tabRef.current.noteId) {
const noteId = tabRef.current.noteId;
@@ -239,7 +241,7 @@ const Tiptap = ({
const update = useCallback(() => {
setTick((tick) => tick + 1);
globalThis.editorControllers[tabRef.current.id]?.setTitlePlaceholder(
- "Note title"
+ strings.noteTitle()
);
setTimeout(() => {
editorControllers[tabRef.current.id]?.setLoading(false);
@@ -459,7 +461,7 @@ const Tiptap = ({
userSelect: "none"
}}
>
- Your changes could not be saved.
+ {strings.changesNotSaved()}
- It seems that your changes could not be saved. What to do next:
+ {strings.changesNotSavedDesc()}
-
-
- Tap on "Dismiss" and copy the contents of your note so they
- are not lost.
-
+ {strings.changesNotSavedStep1()}
-
-
Restart the app.
+ {strings.changesNotSavedStep2()}
@@ -528,7 +527,7 @@ const Tiptap = ({
userSelect: "none"
}}
>
- Dismiss
+ {strings.dismiss()}
@@ -605,7 +604,7 @@ const Tiptap = ({
userSelect: "none"
}}
>
- This note is locked.
+ {strings.thisNoteLocked()}