mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-02-24 04:00:59 +01:00
mobile: localize
This commit is contained in:
committed by
Abdullah Atta
parent
dfe7b2e4a6
commit
014866c330
@@ -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);
|
||||
|
||||
|
||||
@@ -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<R>(): AsyncIterableIterator<QueryResult<R>> {
|
||||
throw new Error("wasqlite driver doesn't support streaming");
|
||||
throw new Error(strings.streamingNotSupported());
|
||||
}
|
||||
|
||||
async executeQuery<R>(
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -87,7 +87,13 @@ export const SideMenu = React.memo(
|
||||
return (
|
||||
<MenuItem
|
||||
key={item.name}
|
||||
item={item}
|
||||
item={{
|
||||
...item,
|
||||
title:
|
||||
strings.routes[
|
||||
item.name as keyof typeof strings.routes
|
||||
]?.() || item.name
|
||||
}}
|
||||
testID={item.name}
|
||||
index={index}
|
||||
/>
|
||||
|
||||
@@ -113,10 +113,10 @@ function _MenuItem({
|
||||
/>
|
||||
{isFocused ? (
|
||||
<Heading color={colors.selected.heading} size={SIZE.md}>
|
||||
{item.name}
|
||||
{item.title || item.name}
|
||||
</Heading>
|
||||
) : (
|
||||
<Paragraph size={SIZE.md}>{item.name}</Paragraph>
|
||||
<Paragraph size={SIZE.md}>{item.title || item.name}</Paragraph>
|
||||
)}
|
||||
|
||||
{item.isBeta ? (
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -52,12 +52,12 @@ export const Home = ({ navigation, route }: NavigationProps<"Notes">) => {
|
||||
<SelectionHeader id={route.name} items={notes} type="note" />
|
||||
<Header
|
||||
renderedInRoute={route.name}
|
||||
title={strings.routes[route.name]()}
|
||||
title={strings.routes[route.name as keyof typeof strings.routes]()}
|
||||
canGoBack={false}
|
||||
hasSearch={true}
|
||||
onSearch={() => {
|
||||
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(),
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -171,7 +171,7 @@ async function run(
|
||||
|
||||
if (!androidBackupDirectory)
|
||||
return {
|
||||
error: new Error("Backup directory not selected"),
|
||||
error: new Error(strings.backupDirectoryNotSelected()),
|
||||
report: false
|
||||
};
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
113
packages/editor-mobile/package-lock.json
generated
113
packages/editor-mobile/package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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()}
|
||||
</p>
|
||||
<p
|
||||
style={{
|
||||
@@ -472,7 +474,7 @@ const Tiptap = ({
|
||||
fontSize: "0.9rem"
|
||||
}}
|
||||
>
|
||||
It seems that your changes could not be saved. What to do next:
|
||||
{strings.changesNotSavedDesc()}
|
||||
</p>
|
||||
|
||||
<p
|
||||
@@ -484,13 +486,10 @@ const Tiptap = ({
|
||||
>
|
||||
<ol>
|
||||
<li>
|
||||
<p>
|
||||
Tap on "Dismiss" and copy the contents of your note so they
|
||||
are not lost.
|
||||
</p>
|
||||
<p>{strings.changesNotSavedStep1()}</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Restart the app.</p>
|
||||
<p>{strings.changesNotSavedStep2()}</p>
|
||||
</li>
|
||||
</ol>
|
||||
</p>
|
||||
@@ -528,7 +527,7 @@ const Tiptap = ({
|
||||
userSelect: "none"
|
||||
}}
|
||||
>
|
||||
Dismiss
|
||||
{strings.dismiss()}
|
||||
</p>
|
||||
</button>
|
||||
</div>
|
||||
@@ -605,7 +604,7 @@ const Tiptap = ({
|
||||
userSelect: "none"
|
||||
}}
|
||||
>
|
||||
This note is locked.
|
||||
{strings.thisNoteLocked()}
|
||||
</p>
|
||||
|
||||
<form
|
||||
@@ -626,7 +625,7 @@ const Tiptap = ({
|
||||
}}
|
||||
>
|
||||
<input
|
||||
placeholder="Enter password"
|
||||
placeholder={strings.enterPassword()}
|
||||
ref={controller.passwordInputRef}
|
||||
name="password"
|
||||
type="password"
|
||||
@@ -671,7 +670,7 @@ const Tiptap = ({
|
||||
userSelect: "none"
|
||||
}}
|
||||
>
|
||||
Unlock note
|
||||
{strings.unlockNote()}
|
||||
</p>
|
||||
</button>
|
||||
|
||||
@@ -705,7 +704,7 @@ const Tiptap = ({
|
||||
userSelect: "none"
|
||||
}}
|
||||
>
|
||||
Enable biometric unlocking
|
||||
{strings.vaultEnableBiometrics()}
|
||||
</p>
|
||||
</div>
|
||||
) : null}
|
||||
@@ -743,7 +742,7 @@ const Tiptap = ({
|
||||
userSelect: "none"
|
||||
}}
|
||||
>
|
||||
Unlock with biometrics
|
||||
{strings.unlockWithBiometrics()}
|
||||
</p>
|
||||
</button>
|
||||
) : null}
|
||||
|
||||
@@ -21,7 +21,6 @@ import { ControlledMenu, MenuItem as MenuItemInner } from "@szhsin/react-menu";
|
||||
import ArrowBackIcon from "mdi-react/ArrowBackIcon";
|
||||
import ArrowULeftTopIcon from "mdi-react/ArrowULeftTopIcon";
|
||||
import ArrowURightTopIcon from "mdi-react/ArrowURightTopIcon";
|
||||
import CrownIcon from "mdi-react/CrownIcon";
|
||||
import DotsHorizontalIcon from "mdi-react/DotsHorizontalIcon";
|
||||
import DotsVerticalIcon from "mdi-react/DotsVerticalIcon";
|
||||
import FullscreenIcon from "mdi-react/FullscreenIcon";
|
||||
|
||||
@@ -20,6 +20,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import React, { RefObject, useEffect, useRef, useState } from "react";
|
||||
import { getTotalWords, Editor } from "@notesnook/editor";
|
||||
import { useTabContext } from "../hooks/useTabStore";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
function StatusBar({
|
||||
container,
|
||||
@@ -37,20 +38,20 @@ function StatusBar({
|
||||
const stickyRef = useRef(false);
|
||||
const prevScroll = useRef(0);
|
||||
const lastStickyChangeTime = useRef(0);
|
||||
const [words, setWords] = useState("0 words");
|
||||
const [words, setWords] = useState(`0 ${strings.words()}`);
|
||||
const currentWords = useRef(words);
|
||||
const statusBar = useRef({
|
||||
set: setStatus,
|
||||
updateWords: () => {
|
||||
const editor = editors[tab.id];
|
||||
if (!editor) return;
|
||||
const words = getTotalWords(editor as Editor) + " words";
|
||||
const words = getTotalWords(editor as Editor) + ` ${strings.words()}`;
|
||||
if (currentWords.current === words) return;
|
||||
setWords(words);
|
||||
},
|
||||
resetWords: () => {
|
||||
currentWords.current = `0 words`;
|
||||
setWords(`0 words`);
|
||||
currentWords.current = `0 ${strings.words()}`;
|
||||
setWords(`0 ${strings.words()}`);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import React, { useEffect, useRef, useState } from "react";
|
||||
import { EventTypes, Settings } from "../utils";
|
||||
import styles from "./styles.module.css";
|
||||
import { useTabContext } from "../hooks/useTabStore";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
function Tags(props: { settings: Settings; loading?: boolean }): JSX.Element {
|
||||
const [tags, setTags] = useState<
|
||||
@@ -89,7 +90,7 @@ function Tags(props: { settings: Settings; loading?: boolean }): JSX.Element {
|
||||
userSelect: "none"
|
||||
}}
|
||||
>
|
||||
Add a tag
|
||||
{strings.addATag()}
|
||||
</p>
|
||||
) : null}
|
||||
<svg
|
||||
|
||||
@@ -17,14 +17,23 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
global.Buffer = require("buffer").Buffer;
|
||||
import { i18n } from "@lingui/core";
|
||||
import "@notesnook/editor/styles/fonts.mobile.css";
|
||||
import "@notesnook/editor/styles/katex-fonts.mobile.css";
|
||||
import "@notesnook/editor/styles/katex.min.css";
|
||||
import "@notesnook/editor/styles/styles.css";
|
||||
import { $en, setI18nGlobal } from "@notesnook/intl";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import App from "./App";
|
||||
import "./index.css";
|
||||
|
||||
i18n.load({
|
||||
en: $en,
|
||||
...globalThis.LINGUI_LOCALE_DATA
|
||||
});
|
||||
i18n.activate(globalThis.LINGUI_LOCALE || "en");
|
||||
setI18nGlobal(i18n);
|
||||
|
||||
const rootElement = document.getElementById("root");
|
||||
if (rootElement) {
|
||||
const root = createRoot(rootElement);
|
||||
|
||||
@@ -54,6 +54,8 @@ export type Settings = {
|
||||
|
||||
/* eslint-disable no-var */
|
||||
declare global {
|
||||
var LINGUI_LOCALE: string;
|
||||
var LINGUI_LOCALE_DATA: { [name: string]: any };
|
||||
var pendingResolvers: {
|
||||
[key: string]: (value: any) => void;
|
||||
};
|
||||
|
||||
@@ -126,6 +126,8 @@ export type TiptapOptions = EditorOptions &
|
||||
downloadOptions?: DownloadOptions;
|
||||
isMobile?: boolean;
|
||||
doubleSpacedLines?: boolean;
|
||||
} & {
|
||||
placeholder: string;
|
||||
};
|
||||
|
||||
const useTiptap = (
|
||||
@@ -289,7 +291,7 @@ const useTiptap = (
|
||||
defaultAlignment: "left"
|
||||
}),
|
||||
Placeholder.configure({
|
||||
placeholder: "Start writing your note..."
|
||||
placeholder: options.placeholder || "Start writing your note..."
|
||||
}),
|
||||
ImageNode.configure({ allowBase64: true }),
|
||||
EmbedNode,
|
||||
|
||||
@@ -18,5 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export { messages as $en } from "./locales/$en.json";
|
||||
export { messages as $de } from "./locales/$de.json";
|
||||
export { messages as $fr } from "./locales/$fr.json";
|
||||
export { strings } from "./src/strings";
|
||||
export { setI18nGlobal } from "./src/setup";
|
||||
|
||||
@@ -19,7 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/** @type {import('@lingui/conf').LinguiConfig} */
|
||||
module.exports = {
|
||||
locales: ["en"],
|
||||
locales: ["en", "de", "fr"],
|
||||
sourceLocale: "en",
|
||||
catalogs: [
|
||||
{
|
||||
|
||||
3826
packages/intl/locale/de.po
Normal file
3826
packages/intl/locale/de.po
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
3826
packages/intl/locale/fr.po
Normal file
3826
packages/intl/locale/fr.po
Normal file
File diff suppressed because it is too large
Load Diff
@@ -27,12 +27,13 @@ export const setI18nGlobal = (i18n: I18n) => {
|
||||
export function getI18nGlobal() {
|
||||
return i18nGlobal;
|
||||
}
|
||||
export const i18n = i18nn;
|
||||
|
||||
Object.keys(i18nGlobal || i18nn).forEach((key) => {
|
||||
Object.defineProperty(i18n, key, {
|
||||
get() {
|
||||
return i18nGlobal?.[key as keyof I18n] || i18nn[key as keyof I18n];
|
||||
export const i18n = new Proxy(
|
||||
{},
|
||||
{
|
||||
get: (target, property) => {
|
||||
return (
|
||||
i18nGlobal?.[property as keyof I18n] || i18nn[property as keyof I18n]
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
@@ -228,14 +228,12 @@ export const strings = {
|
||||
monographPassDesc: () =>
|
||||
t`Published note can only be viewed by someone with the password.`,
|
||||
monographSelfDestructHeading: () => t`Self destruct`,
|
||||
monographSelfDestructDesc:
|
||||
() => t`Published note link will be automatically deleted once it is
|
||||
viewed by someone.`,
|
||||
monographSelfDestructDesc: () =>
|
||||
t`Published note link will be automatically deleted once it is viewed by someone.`,
|
||||
monographLearnMore: () => t`Learn more about Notesnook Monograph`,
|
||||
rateAppHeading: () => t`Do you enjoy using Notesnook?`,
|
||||
rateAppDesc:
|
||||
() => t`It took us a year to bring Notesnook to life. Share your experience
|
||||
and suggestions to help us improve it.`,
|
||||
rateAppDesc: () =>
|
||||
t`It took us a year to bring Notesnook to life. Share your experience and suggestions to help us improve it.`,
|
||||
recoveryKeySavedConfirmation: () =>
|
||||
t`Tap twice to confirm you have saved the recovery key.`,
|
||||
noBlocksLinked: () => t`No blocks linked`,
|
||||
@@ -1358,25 +1356,74 @@ For example:
|
||||
errorGettingCodes: () => t`Error getting codes`,
|
||||
noResultsFound: () => t`No results found for`,
|
||||
routes: {
|
||||
Notes: () => `Notes`,
|
||||
Notebooks: () => `Notebooks`,
|
||||
Notebook: () => `Notebook`,
|
||||
Favorites: () => `Favorites`,
|
||||
Reminders: () => `Reminders`,
|
||||
Trash: () => `Trash`,
|
||||
Settings: () => `Settings`,
|
||||
Tags: () => `Tags`,
|
||||
Editor: () => `Editor`,
|
||||
Home: () => `Home`,
|
||||
Search: () => `Search`
|
||||
Notes: () => t`Notes`,
|
||||
Notebooks: () => t`Notebooks`,
|
||||
Notebook: () => t`Notebook`,
|
||||
Favorites: () => t`Favorites`,
|
||||
Reminders: () => t`Reminders`,
|
||||
Trash: () => t`Trash`,
|
||||
Settings: () => t`Settings`,
|
||||
Tags: () => t`Tags`,
|
||||
Editor: () => t`Editor`,
|
||||
Home: () => t`Home`,
|
||||
Search: () => t`Search`,
|
||||
Monographs: () => t`Monographs`
|
||||
},
|
||||
searchInRoute: (routeName: string) =>
|
||||
`Type a keyword to search in ${
|
||||
t`Type a keyword to search in ${
|
||||
strings.routes[routeName as keyof typeof strings.routes]?.() || routeName
|
||||
}`,
|
||||
logoutConfirmation: () =>
|
||||
t`Are you sure you want to logout and clear all data stored on this device?`,
|
||||
backupDataBeforeLogout: () => t`Take a backup before logging out`,
|
||||
unsyncedChangesWarning: () =>
|
||||
t`You have unsynced notes. Take a backup or sync your notes to avoid losing your critical data.`
|
||||
t`You have unsynced notes. Take a backup or sync your notes to avoid losing your critical data.`,
|
||||
databaseSetupFailed: () =>
|
||||
t`Database setup failed, could not get database key`,
|
||||
streamingNotSupported: () => t`Streaming not supported`,
|
||||
unableToResolveDownloadUrl: () => t`Unable to resolve download url`,
|
||||
pleaseWaitBeforeSendEmail: () =>
|
||||
t`Please wait before requesting another email`,
|
||||
unableToSend2faCode: () => t`Unable to send 2FA code`,
|
||||
emailOrPasswordIncorrect: () => t`Email or password incorrect`,
|
||||
errorApplyingPromoCode: () => t`Error applying promo code`,
|
||||
noNotificationPermission: () =>
|
||||
t`"App does not have permission to schedule notifications"`,
|
||||
selectDayError: () => t`Please select the day to repeat the reminder on`,
|
||||
setTitleError: () => t`Please set title of the reminder`,
|
||||
dateError: () => t`Reminder date must be set in future`,
|
||||
failedToDecryptBackup: () => t`Failed to decrypt backup`,
|
||||
backupDirectoryNotSelected: () => t`"Backup directory not selected"`,
|
||||
legal: () => t`legal`,
|
||||
days: () => t`days`,
|
||||
daily: () => t`Daily`,
|
||||
weekly: () => t`Weekly`,
|
||||
monthly: () => t`Monthly`,
|
||||
yearly: () => t`Yearly`,
|
||||
minutes: (count: number) =>
|
||||
plural(count, {
|
||||
one: `1 minute`,
|
||||
other: `# minutes`
|
||||
}),
|
||||
hours: (count: number) =>
|
||||
plural(count, {
|
||||
one: `1 hour`,
|
||||
other: `# hours`
|
||||
}),
|
||||
immediately: () => t`Immediately`,
|
||||
"12-hour": () => t`12-hour`,
|
||||
"24-hour": () => t`24-hour`,
|
||||
noteTitle: () => t`Note title`,
|
||||
changesNotSaved: () => t`Your changes could not be saved`,
|
||||
changesNotSavedDesc: () =>
|
||||
t`It seems that your changes could not be saved. What to do next:`,
|
||||
changesNotSavedStep1: () =>
|
||||
t`Tap on "Dismiss" and copy the contents of your note so they are not lost.`,
|
||||
changesNotSavedStep2: () => t`Restart the app.`,
|
||||
thisNoteLocked: () => `This note is locked`,
|
||||
dismiss: () => t`Dismiss`,
|
||||
words: () => t`words`,
|
||||
addATag: () => t`Add a tag`,
|
||||
startWritingNote: () => t`Start writing your note...`,
|
||||
off: () => t`Off`
|
||||
};
|
||||
|
||||
1435
packages/ui/package-lock.json
generated
1435
packages/ui/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user