From b9f6e3bd08dcfc050048fba582b35958bc7b6184 Mon Sep 17 00:00:00 2001 From: Elias Schneider Date: Thu, 20 Jul 2023 15:32:07 +0200 Subject: [PATCH] feat: localization (#196) * Started adding locale translations :) * Added some more translations * Working on translating even more pages * More translations * Added test default locale retrieval * replace `intl.formatMessage` with custom `t` hook * add more translations * improve title syntax * add more translations * translate admin config page * translated error messages * add language selecter * minor fixes * improve language handling * add upcoming languages * add `crowdin.yml` * run formatter --------- Co-authored-by: Steve Tautonico --- .../migration.sql | 27 ++ backend/prisma/schema.prisma | 1 - backend/prisma/seed/config.seed.ts | 34 +- backend/src/config/dto/adminConfig.dto.ts | 3 - crowdin.yml | 3 + frontend/.eslintrc.json | 7 +- frontend/next.config.js | 1 - frontend/package-lock.json | 266 ++++++++++- frontend/package.json | 1 + .../src/components/account/LanguagePicker.tsx | 33 ++ .../src/components/account/ThemeSwitcher.tsx | 20 +- .../account/showEnableTotpModal.tsx | 31 +- .../account/showReverseShareLinkModal.tsx | 4 +- .../account/showShareInformationsModal.tsx | 40 +- .../components/account/showShareLinkModal.tsx | 4 +- .../configuration/ConfigurationHeader.tsx | 3 +- .../configuration/ConfigurationNavBar.tsx | 11 +- .../admin/configuration/LogoConfigInput.tsx | 12 +- .../admin/configuration/TestEmailButton.tsx | 3 +- .../admin/users/ManageUserTable.tsx | 13 +- .../admin/users/showCreateUserModal.tsx | 40 +- .../admin/users/showUpdateUserModal.tsx | 44 +- frontend/src/components/auth/SignInForm.tsx | 34 +- frontend/src/components/auth/SignUpForm.tsx | 35 +- .../src/components/header/ActionAvatar.tsx | 7 +- frontend/src/components/header/Header.tsx | 12 +- .../src/components/header/NavbarShareMenu.tsx | 5 +- .../components/share/DownloadAllButton.tsx | 8 +- frontend/src/components/share/FileList.tsx | 11 +- frontend/src/components/share/FilePreview.tsx | 8 +- .../modals/showCompletedReverseShareModal.tsx | 7 +- .../modals/showCreateReverseShareModal.tsx | 54 ++- .../share/showEnterPasswordModal.tsx | 18 +- .../src/components/share/showErrorModal.tsx | 3 +- .../src/components/upload/CopyTextField.tsx | 7 +- frontend/src/components/upload/Dropzone.tsx | 20 +- frontend/src/components/upload/FileList.tsx | 9 +- .../modals/showCompletedUploadModal.tsx | 18 +- .../upload/modals/showCreateUploadModal.tsx | 113 +++-- frontend/src/hooks/useTranslate.hook.ts | 39 ++ frontend/src/i18n/locales.ts | 51 ++ frontend/src/i18n/translations/da.ts | 435 ++++++++++++++++++ frontend/src/i18n/translations/de.ts | 435 ++++++++++++++++++ frontend/src/i18n/translations/en.ts | 435 ++++++++++++++++++ frontend/src/i18n/translations/es.ts | 435 ++++++++++++++++++ frontend/src/i18n/translations/fr.ts | 435 ++++++++++++++++++ frontend/src/i18n/translations/pt.ts | 435 ++++++++++++++++++ frontend/src/i18n/translations/th.ts | 435 ++++++++++++++++++ frontend/src/i18n/translations/zh-CN.ts | 435 ++++++++++++++++++ frontend/src/pages/404.tsx | 9 +- frontend/src/pages/_app.tsx | 116 +++-- frontend/src/pages/account/index.tsx | 107 +++-- frontend/src/pages/account/reverseShares.tsx | 74 +-- frontend/src/pages/account/shares.tsx | 48 +- .../src/pages/admin/config/[category].tsx | 53 ++- frontend/src/pages/admin/index.tsx | 13 +- frontend/src/pages/admin/users.tsx | 23 +- .../resetPassword/[resetPasswordToken].tsx | 18 +- .../src/pages/auth/resetPassword/index.tsx | 24 +- frontend/src/pages/auth/signIn.tsx | 4 +- frontend/src/pages/auth/signUp.tsx | 4 +- frontend/src/pages/index.tsx | 35 +- frontend/src/pages/share/[shareId]/index.tsx | 22 +- frontend/src/pages/upload/index.tsx | 13 +- frontend/src/utils/date.util.ts | 12 +- frontend/src/utils/i18n.util.ts | 42 ++ frontend/src/utils/string.util.ts | 5 +- .../userPreferences.util.ts} | 11 +- 68 files changed, 4712 insertions(+), 461 deletions(-) create mode 100644 backend/prisma/migrations/20230718155819_remove_config_veriable_description/migration.sql create mode 100644 crowdin.yml create mode 100644 frontend/src/components/account/LanguagePicker.tsx create mode 100644 frontend/src/hooks/useTranslate.hook.ts create mode 100644 frontend/src/i18n/locales.ts create mode 100644 frontend/src/i18n/translations/da.ts create mode 100644 frontend/src/i18n/translations/de.ts create mode 100644 frontend/src/i18n/translations/en.ts create mode 100644 frontend/src/i18n/translations/es.ts create mode 100644 frontend/src/i18n/translations/fr.ts create mode 100644 frontend/src/i18n/translations/pt.ts create mode 100644 frontend/src/i18n/translations/th.ts create mode 100644 frontend/src/i18n/translations/zh-CN.ts create mode 100644 frontend/src/utils/i18n.util.ts rename frontend/src/{hooks/usePreferences.ts => utils/userPreferences.util.ts} (84%) diff --git a/backend/prisma/migrations/20230718155819_remove_config_veriable_description/migration.sql b/backend/prisma/migrations/20230718155819_remove_config_veriable_description/migration.sql new file mode 100644 index 0000000..ba98e39 --- /dev/null +++ b/backend/prisma/migrations/20230718155819_remove_config_veriable_description/migration.sql @@ -0,0 +1,27 @@ +/* + Warnings: + + - You are about to drop the column `description` on the `Config` table. All the data in the column will be lost. + +*/ +-- RedefineTables +PRAGMA foreign_keys=OFF; +CREATE TABLE "new_Config" ( + "updatedAt" DATETIME NOT NULL, + "name" TEXT NOT NULL, + "category" TEXT NOT NULL, + "type" TEXT NOT NULL, + "defaultValue" TEXT NOT NULL DEFAULT '', + "value" TEXT, + "obscured" BOOLEAN NOT NULL DEFAULT false, + "secret" BOOLEAN NOT NULL DEFAULT true, + "locked" BOOLEAN NOT NULL DEFAULT false, + "order" INTEGER NOT NULL, + + PRIMARY KEY ("name", "category") +); +INSERT INTO "new_Config" ("category", "defaultValue", "locked", "name", "obscured", "order", "secret", "type", "updatedAt", "value") SELECT "category", "defaultValue", "locked", "name", "obscured", "order", "secret", "type", "updatedAt", "value" FROM "Config"; +DROP TABLE "Config"; +ALTER TABLE "new_Config" RENAME TO "Config"; +PRAGMA foreign_key_check; +PRAGMA foreign_keys=ON; diff --git a/backend/prisma/schema.prisma b/backend/prisma/schema.prisma index c25b385..de898d3 100644 --- a/backend/prisma/schema.prisma +++ b/backend/prisma/schema.prisma @@ -136,7 +136,6 @@ model Config { type String defaultValue String @default("") value String? - description String obscured Boolean @default(false) secret Boolean @default(true) locked Boolean @default(false) diff --git a/backend/prisma/seed/config.seed.ts b/backend/prisma/seed/config.seed.ts index 23d78ea..20caaec 100644 --- a/backend/prisma/seed/config.seed.ts +++ b/backend/prisma/seed/config.seed.ts @@ -1,9 +1,9 @@ import { Prisma, PrismaClient } from "@prisma/client"; import * as crypto from "crypto"; + const configVariables: ConfigVariables = { internal: { jwtSecret: { - description: "Long random string used to sign JWT tokens", type: "string", defaultValue: crypto.randomBytes(256).toString("base64"), locked: true, @@ -11,20 +11,17 @@ const configVariables: ConfigVariables = { }, general: { appName: { - description: "Name of the application", type: "string", defaultValue: "Pingvin Share", secret: false, }, appUrl: { - description: "On which URL Pingvin Share is available", type: "string", defaultValue: "http://localhost:3000", secret: false, }, showHomePage: { - description: "Whether to show the home page", type: "boolean", defaultValue: "true", secret: false, @@ -32,21 +29,17 @@ const configVariables: ConfigVariables = { }, share: { allowRegistration: { - description: "Whether registration is allowed", type: "boolean", defaultValue: "true", - secret: false, }, allowUnauthenticatedShares: { - description: "Whether unauthorized users can create shares", type: "boolean", defaultValue: "false", secret: false, }, maxSize: { - description: "Maximum share size in bytes", type: "number", defaultValue: "1073741824", @@ -55,61 +48,43 @@ const configVariables: ConfigVariables = { }, email: { enableShareEmailRecipients: { - description: - "Whether to allow emails to share recipients. Only enable this if you have enabled SMTP.", type: "boolean", defaultValue: "false", secret: false, }, shareRecipientsSubject: { - description: - "Subject of the email which gets sent to the share recipients.", type: "string", defaultValue: "Files shared with you", }, shareRecipientsMessage: { - description: - "Message which gets sent to the share recipients.\n\nAvailable variables:\n{creator} - The username of the creator of the share\n{shareUrl} - The URL of the share\n{desc} - The description of the share\n{expires} - The expiration date of the share\n\nVariables will be replaced with the actual values.", type: "text", defaultValue: "Hey!\n\n{creator} shared some files with you, view or download the files with this link: {shareUrl}\n\nThe share will expire {expires}.\n\nNote: {desc}\n\nShared securely with Pingvin Share 🐧", }, reverseShareSubject: { - description: - "Subject of the email which gets sent when someone created a share with your reverse share link.", type: "string", defaultValue: "Reverse share link used", }, reverseShareMessage: { - description: - "Message which gets sent when someone created a share with your reverse share link. {shareUrl} will be replaced with the creator's name and the share URL.", type: "text", defaultValue: "Hey!\n\nA share was just created with your reverse share link: {shareUrl}\n\nShared securely with Pingvin Share 🐧", }, resetPasswordSubject: { - description: - "Subject of the email which gets sent when a user requests a password reset.", type: "string", defaultValue: "Pingvin Share password reset", }, resetPasswordMessage: { - description: - "Message which gets sent when a user requests a password reset. {url} will be replaced with the reset password URL.", type: "text", defaultValue: "Hey!\n\nYou requested a password reset. Click this link to reset your password: {url}\nThe link expires in a hour.\n\nPingvin Share 🐧", }, inviteSubject: { - description: - "Subject of the email which gets sent when an admin invites an user.", type: "string", defaultValue: "Pingvin Share invite", }, inviteMessage: { - description: - "Message which gets sent when an admin invites an user. {url} will be replaced with the invite URL and {password} with the password.", type: "text", defaultValue: "Hey!\n\nYou were invited to Pingvin Share. Click this link to accept the invite: {url}\n\nYour password is: {password}\n\nPingvin Share 🐧", @@ -117,34 +92,27 @@ const configVariables: ConfigVariables = { }, smtp: { enabled: { - description: - "Whether SMTP is enabled. Only set this to true if you entered the host, port, email, user and password of your SMTP server.", type: "boolean", defaultValue: "false", secret: false, }, host: { - description: "Host of the SMTP server", type: "string", defaultValue: "", }, port: { - description: "Port of the SMTP server", type: "number", defaultValue: "0", }, email: { - description: "Email address which the emails get sent from", type: "string", defaultValue: "", }, username: { - description: "Username of the SMTP server", type: "string", defaultValue: "", }, password: { - description: "Password of the SMTP server", type: "string", defaultValue: "", obscured: true, diff --git a/backend/src/config/dto/adminConfig.dto.ts b/backend/src/config/dto/adminConfig.dto.ts index 3c580ef..5527dd2 100644 --- a/backend/src/config/dto/adminConfig.dto.ts +++ b/backend/src/config/dto/adminConfig.dto.ts @@ -14,9 +14,6 @@ export class AdminConfigDTO extends ConfigDTO { @Expose() updatedAt: Date; - @Expose() - description: string; - @Expose() obscured: boolean; diff --git a/crowdin.yml b/crowdin.yml new file mode 100644 index 0000000..21b9023 --- /dev/null +++ b/crowdin.yml @@ -0,0 +1,3 @@ +files: + - source: /frontend/src/i18n/translations/en.ts + translation: /%original_path%/%two_letters_code%.ts diff --git a/frontend/.eslintrc.json b/frontend/.eslintrc.json index ca620b5..e817477 100644 --- a/frontend/.eslintrc.json +++ b/frontend/.eslintrc.json @@ -1,5 +1,10 @@ { - "extends": ["eslint-config-next", "eslint:recommended", "prettier"], + "extends": [ + "next/babel", + "eslint-config-next", + "eslint:recommended", + "prettier" + ], "plugins": ["react"], "rules": { "quotes": ["warn", "double", { "allowTemplateLiterals": true }], diff --git a/frontend/next.config.js b/frontend/next.config.js index 831c30b..808c0d5 100644 --- a/frontend/next.config.js +++ b/frontend/next.config.js @@ -1,5 +1,4 @@ /** @type {import('next').NextConfig} */ - const { version } = require('./package.json'); const withPWA = require("next-pwa")({ diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 98b913f..f0ff0c2 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -32,6 +32,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-icons": "^4.8.0", + "react-intl": "^6.3.1", "sharp": "^0.31.3", "yup": "^1.0.2" }, @@ -1860,6 +1861,92 @@ "react-dom": ">=16.8.0" } }, + "node_modules/@formatjs/ecma402-abstract": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.14.3.tgz", + "integrity": "sha512-SlsbRC/RX+/zg4AApWIFNDdkLtFbkq3LNoZWXZCE/nHVKqoIJyaoQyge/I0Y38vLxowUn9KTtXgusLD91+orbg==", + "dependencies": { + "@formatjs/intl-localematcher": "0.2.32", + "tslib": "^2.4.0" + } + }, + "node_modules/@formatjs/fast-memoize": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.0.0.tgz", + "integrity": "sha512-U88W/qhEs5ZuX+Inw/DZHjA6w2YCTWTNzTkprzNznyWoGl8h+XtlOCW3nM78+VX7lSbvpMdnaHmWLnDnjJjuwg==", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@formatjs/icu-messageformat-parser": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.3.0.tgz", + "integrity": "sha512-xqtlqYAbfJDF4b6e4O828LBNOWXrFcuYadqAbYORlDRwhyJ2bH+xpUBPldZbzRGUN2mxlZ4Ykhm7jvERtmI8NQ==", + "dependencies": { + "@formatjs/ecma402-abstract": "1.14.3", + "@formatjs/icu-skeleton-parser": "1.3.18", + "tslib": "^2.4.0" + } + }, + "node_modules/@formatjs/icu-skeleton-parser": { + "version": "1.3.18", + "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.3.18.tgz", + "integrity": "sha512-ND1ZkZfmLPcHjAH1sVpkpQxA+QYfOX3py3SjKWMUVGDow18gZ0WPqz3F+pJLYQMpS2LnnQ5zYR2jPVYTbRwMpg==", + "dependencies": { + "@formatjs/ecma402-abstract": "1.14.3", + "tslib": "^2.4.0" + } + }, + "node_modules/@formatjs/intl": { + "version": "2.6.8", + "resolved": "https://registry.npmjs.org/@formatjs/intl/-/intl-2.6.8.tgz", + "integrity": "sha512-lWfFUBGvYfeQoIaW5/6TW7WdyiqfWg5wPFVnsb1eKs4JPmAz2nYBUPuKbyvBiRV87QCSNZ+tZTViFBU+bIHeqQ==", + "dependencies": { + "@formatjs/ecma402-abstract": "1.14.3", + "@formatjs/fast-memoize": "2.0.0", + "@formatjs/icu-messageformat-parser": "2.3.0", + "@formatjs/intl-displaynames": "6.2.6", + "@formatjs/intl-listformat": "7.1.9", + "intl-messageformat": "10.3.2", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "typescript": "^4.7" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@formatjs/intl-displaynames": { + "version": "6.2.6", + "resolved": "https://registry.npmjs.org/@formatjs/intl-displaynames/-/intl-displaynames-6.2.6.tgz", + "integrity": "sha512-scf5AQTk9EjpvPhboo5sizVOvidTdMOnajv9z+0cejvl7JNl9bl/aMrNBgC72UH+bP3l45usPUKAGskV6sNIrA==", + "dependencies": { + "@formatjs/ecma402-abstract": "1.14.3", + "@formatjs/intl-localematcher": "0.2.32", + "tslib": "^2.4.0" + } + }, + "node_modules/@formatjs/intl-listformat": { + "version": "7.1.9", + "resolved": "https://registry.npmjs.org/@formatjs/intl-listformat/-/intl-listformat-7.1.9.tgz", + "integrity": "sha512-5YikxwRqRXTVWVujhswDOTCq6gs+m9IcNbNZLa6FLtyBStAjEsuE2vAU+lPsbz9ZTST57D5fodjIh2JXT6sMWQ==", + "dependencies": { + "@formatjs/ecma402-abstract": "1.14.3", + "@formatjs/intl-localematcher": "0.2.32", + "tslib": "^2.4.0" + } + }, + "node_modules/@formatjs/intl-localematcher": { + "version": "0.2.32", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.2.32.tgz", + "integrity": "sha512-k/MEBstff4sttohyEpXxCmC3MqbUn9VvHGlZ8fauLzkbwXmVrEeyzS+4uhrvAk9DWU9/7otYWxyDox4nT/KVLQ==", + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.8", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", @@ -2625,6 +2712,15 @@ "@types/node": "*" } }, + "node_modules/@types/hoist-non-react-statics": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", + "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", + "dependencies": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, "node_modules/@types/http-proxy": { "version": "1.17.3", "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.3.tgz", @@ -2668,14 +2764,12 @@ "node_modules/@types/prop-types": { "version": "15.7.5", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", - "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==", - "devOptional": true + "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" }, "node_modules/@types/react": { "version": "18.0.28", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.28.tgz", "integrity": "sha512-RD0ivG1kEztNBdoAK7lekI9M+azSnitIn85h4iOiaLjaTrMjzslhaqCGaI4IyCJ1RljWiLCEu4jyrLLgqxBTew==", - "devOptional": true, "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -2702,8 +2796,7 @@ "node_modules/@types/scheduler": { "version": "0.16.2", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", - "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", - "devOptional": true + "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" }, "node_modules/@types/trusted-types": { "version": "2.0.2", @@ -5349,6 +5442,17 @@ "node": ">= 0.4" } }, + "node_modules/intl-messageformat": { + "version": "10.3.2", + "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.3.2.tgz", + "integrity": "sha512-kGY1KrpxPGbWX/yz6rpWQahBh5bJC6pIbq/cTzVYlmAYjRVzP+l2MulagbZf/5mABbcLT/0RJbZC46Iw6Mhmtw==", + "dependencies": { + "@formatjs/ecma402-abstract": "1.14.3", + "@formatjs/fast-memoize": "2.0.0", + "@formatjs/icu-messageformat-parser": "2.3.0", + "tslib": "^2.4.0" + } + }, "node_modules/invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", @@ -6863,6 +6967,32 @@ "react": "*" } }, + "node_modules/react-intl": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/react-intl/-/react-intl-6.3.1.tgz", + "integrity": "sha512-wI7YSxS2xQRYrHPMFSHXSWNuOLrgt9L0Dg8G35xJNfDRpcyebE9H1k2/DDPxgbV4ImeG/qKJMN5SrZV7fuB/Ag==", + "dependencies": { + "@formatjs/ecma402-abstract": "1.14.3", + "@formatjs/icu-messageformat-parser": "2.3.0", + "@formatjs/intl": "2.6.8", + "@formatjs/intl-displaynames": "6.2.6", + "@formatjs/intl-listformat": "7.1.9", + "@types/hoist-non-react-statics": "^3.3.1", + "@types/react": "16 || 17 || 18", + "hoist-non-react-statics": "^3.3.2", + "intl-messageformat": "10.3.2", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "react": "^16.6.0 || 17 || 18", + "typescript": "^4.7" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -7988,7 +8118,7 @@ "version": "4.9.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", - "dev": true, + "devOptional": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -9950,6 +10080,84 @@ "@floating-ui/dom": "^1.2.1" } }, + "@formatjs/ecma402-abstract": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.14.3.tgz", + "integrity": "sha512-SlsbRC/RX+/zg4AApWIFNDdkLtFbkq3LNoZWXZCE/nHVKqoIJyaoQyge/I0Y38vLxowUn9KTtXgusLD91+orbg==", + "requires": { + "@formatjs/intl-localematcher": "0.2.32", + "tslib": "^2.4.0" + } + }, + "@formatjs/fast-memoize": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.0.0.tgz", + "integrity": "sha512-U88W/qhEs5ZuX+Inw/DZHjA6w2YCTWTNzTkprzNznyWoGl8h+XtlOCW3nM78+VX7lSbvpMdnaHmWLnDnjJjuwg==", + "requires": { + "tslib": "^2.4.0" + } + }, + "@formatjs/icu-messageformat-parser": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.3.0.tgz", + "integrity": "sha512-xqtlqYAbfJDF4b6e4O828LBNOWXrFcuYadqAbYORlDRwhyJ2bH+xpUBPldZbzRGUN2mxlZ4Ykhm7jvERtmI8NQ==", + "requires": { + "@formatjs/ecma402-abstract": "1.14.3", + "@formatjs/icu-skeleton-parser": "1.3.18", + "tslib": "^2.4.0" + } + }, + "@formatjs/icu-skeleton-parser": { + "version": "1.3.18", + "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.3.18.tgz", + "integrity": "sha512-ND1ZkZfmLPcHjAH1sVpkpQxA+QYfOX3py3SjKWMUVGDow18gZ0WPqz3F+pJLYQMpS2LnnQ5zYR2jPVYTbRwMpg==", + "requires": { + "@formatjs/ecma402-abstract": "1.14.3", + "tslib": "^2.4.0" + } + }, + "@formatjs/intl": { + "version": "2.6.8", + "resolved": "https://registry.npmjs.org/@formatjs/intl/-/intl-2.6.8.tgz", + "integrity": "sha512-lWfFUBGvYfeQoIaW5/6TW7WdyiqfWg5wPFVnsb1eKs4JPmAz2nYBUPuKbyvBiRV87QCSNZ+tZTViFBU+bIHeqQ==", + "requires": { + "@formatjs/ecma402-abstract": "1.14.3", + "@formatjs/fast-memoize": "2.0.0", + "@formatjs/icu-messageformat-parser": "2.3.0", + "@formatjs/intl-displaynames": "6.2.6", + "@formatjs/intl-listformat": "7.1.9", + "intl-messageformat": "10.3.2", + "tslib": "^2.4.0" + } + }, + "@formatjs/intl-displaynames": { + "version": "6.2.6", + "resolved": "https://registry.npmjs.org/@formatjs/intl-displaynames/-/intl-displaynames-6.2.6.tgz", + "integrity": "sha512-scf5AQTk9EjpvPhboo5sizVOvidTdMOnajv9z+0cejvl7JNl9bl/aMrNBgC72UH+bP3l45usPUKAGskV6sNIrA==", + "requires": { + "@formatjs/ecma402-abstract": "1.14.3", + "@formatjs/intl-localematcher": "0.2.32", + "tslib": "^2.4.0" + } + }, + "@formatjs/intl-listformat": { + "version": "7.1.9", + "resolved": "https://registry.npmjs.org/@formatjs/intl-listformat/-/intl-listformat-7.1.9.tgz", + "integrity": "sha512-5YikxwRqRXTVWVujhswDOTCq6gs+m9IcNbNZLa6FLtyBStAjEsuE2vAU+lPsbz9ZTST57D5fodjIh2JXT6sMWQ==", + "requires": { + "@formatjs/ecma402-abstract": "1.14.3", + "@formatjs/intl-localematcher": "0.2.32", + "tslib": "^2.4.0" + } + }, + "@formatjs/intl-localematcher": { + "version": "0.2.32", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.2.32.tgz", + "integrity": "sha512-k/MEBstff4sttohyEpXxCmC3MqbUn9VvHGlZ8fauLzkbwXmVrEeyzS+4uhrvAk9DWU9/7otYWxyDox4nT/KVLQ==", + "requires": { + "tslib": "^2.4.0" + } + }, "@humanwhocodes/config-array": { "version": "0.11.8", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", @@ -10460,6 +10668,15 @@ "@types/node": "*" } }, + "@types/hoist-non-react-statics": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", + "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", + "requires": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, "@types/http-proxy": { "version": "1.17.3", "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.3.tgz", @@ -10503,14 +10720,12 @@ "@types/prop-types": { "version": "15.7.5", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", - "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==", - "devOptional": true + "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" }, "@types/react": { "version": "18.0.28", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.28.tgz", "integrity": "sha512-RD0ivG1kEztNBdoAK7lekI9M+azSnitIn85h4iOiaLjaTrMjzslhaqCGaI4IyCJ1RljWiLCEu4jyrLLgqxBTew==", - "devOptional": true, "requires": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -10537,8 +10752,7 @@ "@types/scheduler": { "version": "0.16.2", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", - "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", - "devOptional": true + "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" }, "@types/trusted-types": { "version": "2.0.2", @@ -12506,6 +12720,17 @@ "side-channel": "^1.0.4" } }, + "intl-messageformat": { + "version": "10.3.2", + "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.3.2.tgz", + "integrity": "sha512-kGY1KrpxPGbWX/yz6rpWQahBh5bJC6pIbq/cTzVYlmAYjRVzP+l2MulagbZf/5mABbcLT/0RJbZC46Iw6Mhmtw==", + "requires": { + "@formatjs/ecma402-abstract": "1.14.3", + "@formatjs/fast-memoize": "2.0.0", + "@formatjs/icu-messageformat-parser": "2.3.0", + "tslib": "^2.4.0" + } + }, "invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", @@ -13565,6 +13790,23 @@ "integrity": "sha512-N6+kOLcihDiAnj5Czu637waJqSnwlMNROzVZMhfX68V/9bu9qHaMIJC4UdozWoOk57gahFCNHwVvWzm0MTzRjg==", "requires": {} }, + "react-intl": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/react-intl/-/react-intl-6.3.1.tgz", + "integrity": "sha512-wI7YSxS2xQRYrHPMFSHXSWNuOLrgt9L0Dg8G35xJNfDRpcyebE9H1k2/DDPxgbV4ImeG/qKJMN5SrZV7fuB/Ag==", + "requires": { + "@formatjs/ecma402-abstract": "1.14.3", + "@formatjs/icu-messageformat-parser": "2.3.0", + "@formatjs/intl": "2.6.8", + "@formatjs/intl-displaynames": "6.2.6", + "@formatjs/intl-listformat": "7.1.9", + "@types/hoist-non-react-statics": "^3.3.1", + "@types/react": "16 || 17 || 18", + "hoist-non-react-statics": "^3.3.2", + "intl-messageformat": "10.3.2", + "tslib": "^2.4.0" + } + }, "react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -14362,7 +14604,7 @@ "version": "4.9.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", - "dev": true + "devOptional": true }, "unbox-primitive": { "version": "1.0.2", diff --git a/frontend/package.json b/frontend/package.json index aaf1451..298677c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -33,6 +33,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-icons": "^4.8.0", + "react-intl": "^6.3.1", "sharp": "^0.31.3", "yup": "^1.0.2" }, diff --git a/frontend/src/components/account/LanguagePicker.tsx b/frontend/src/components/account/LanguagePicker.tsx new file mode 100644 index 0000000..b5bc29d --- /dev/null +++ b/frontend/src/components/account/LanguagePicker.tsx @@ -0,0 +1,33 @@ +import { Select } from "@mantine/core"; +import { getCookie, setCookie } from "cookies-next"; +import { useState } from "react"; +import { LOCALES } from "../../i18n/locales"; + +const LanguagePicker = () => { + const [selectedLanguage, setSelectedLanguage] = useState( + getCookie("language")?.toString() + ); + + const languages = Object.values(LOCALES).map((locale) => ({ + value: locale.code, + label: locale.name, + })); + return ( + - {getExpirationPreview("share", form)} + {getExpirationPreview( + { + neverExpires: t("upload.modal.completed.never-expires"), + expiresOn: t("upload.modal.completed.expires-on"), + }, + form + )} )} - Description + + +