Compare commits

...

4 Commits

Author SHA1 Message Date
Elias Schneider
72a52eb33f release: 0.17.2 2023-07-31 15:37:12 +02:00
Elias Schneider
c9a2a469c6 fix: ECONNREFUSED with Docker ipv6 enabled 2023-07-31 15:37:04 +02:00
Elias Schneider
b534129194 chore(translations): remove Thai 2023-07-31 08:56:22 +02:00
Elias Schneider
0beebfd779 chore(translation): add Russian 2023-07-31 08:55:17 +02:00
10 changed files with 193 additions and 70 deletions

View File

@@ -1,3 +1,10 @@
## [0.17.2](https://github.com/stonith404/pingvin-share/compare/v0.17.1...v0.17.2) (2023-07-31)
### Bug Fixes
* `ECONNREFUSED` with Docker ipv6 enabled ([c9a2a46](https://github.com/stonith404/pingvin-share/commit/c9a2a469c67d3c3cd08179b44e2bf82208f05177))
## [0.17.1](https://github.com/stonith404/pingvin-share/compare/v0.17.0...v0.17.1) (2023-07-30)

View File

@@ -49,4 +49,5 @@ WORKDIR /opt/app
EXPOSE 3000
HEALTHCHECK --interval=10s --timeout=3s CMD curl -f http://localhost:3000/api/health || exit 1
CMD cp -rn /tmp/img /opt/app/frontend/public && node frontend/server.js & cd backend && npm run prod
# HOSTNAME=127.0.0.1 fixes https://github.com/vercel/next.js/issues/51684. It can be removed as soon as the issue is fixed
CMD cp -rn /tmp/img /opt/app/frontend/public && HOSTNAME=127.0.0.1 node frontend/server.js & cd backend && npm run prod

View File

@@ -1,12 +1,12 @@
{
"name": "pingvin-share-backend",
"version": "0.17.1",
"version": "0.17.2",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "pingvin-share-backend",
"version": "0.17.1",
"version": "0.17.2",
"dependencies": {
"@nestjs/common": "^10.1.2",
"@nestjs/config": "^3.0.0",

View File

@@ -1,6 +1,6 @@
{
"name": "pingvin-share-backend",
"version": "0.17.1",
"version": "0.17.2",
"scripts": {
"build": "nest build",
"dev": "cross-env NODE_ENV=development nest start --watch",

View File

@@ -1,4 +1,4 @@
files:
- source: /frontend/src/i18n/translations/en-US.ts
translation: /%original_path%/%locale%.ts
pull_request_title: "chore: update translations via Crowdin"
pull_request_title: "chore(translations): update translations via Crowdin"

View File

@@ -1,12 +1,12 @@
{
"name": "pingvin-share-frontend",
"version": "0.17.1",
"version": "0.17.2",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "pingvin-share-frontend",
"version": "0.17.1",
"version": "0.17.2",
"dependencies": {
"@emotion/react": "^11.11.1",
"@emotion/server": "^11.11.0",

View File

@@ -1,10 +1,10 @@
{
"name": "pingvin-share-frontend",
"version": "0.17.1",
"version": "0.17.2",
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"start": "node .next/standalone/server.js",
"lint": "next lint",
"format": "prettier --end-of-line=auto --write \"src/**/*.ts*\""
},

View File

@@ -5,7 +5,7 @@ import spanish from "./translations/es-ES";
import finnish from "./translations/fi-FI";
import french from "./translations/fr-FR";
import portuguese from "./translations/pt-BR";
import thai from "./translations/th-TH";
import russian from "./translations/ru-RU";
import chineseSimplified from "./translations/zh-CN";
export const LOCALES = {
@@ -34,11 +34,6 @@ export const LOCALES = {
code: "da-DK",
messages: danish,
},
THAI: {
name: "ไทย",
code: "th-TH",
messages: thai,
},
SPANISH: {
name: "Español",
code: "es-ES",
@@ -54,4 +49,9 @@ export const LOCALES = {
code: "fi-FI",
messages: finnish,
},
RUSSIAN: {
name: "Русский",
code: "ru-RU",
messages: russian,
},
};

View File

@@ -4,24 +4,33 @@ export default {
"navbar.signin": "Sign in",
"navbar.home": "Home",
"navbar.signup": "Sign Up",
"navbar.links.shares": "My shares",
"navbar.links.reverse": "Reverse shares",
"navbar.avatar.account": "My account",
"navbar.avatar.admin": "Administration",
"navbar.avatar.signout": "Sign out",
// END navbar
// /
"home.title": "A <h>self-hosted</h> file sharing platform.",
"home.description": "Do you really want to give your personal files in the hand of third parties like WeTransfer?",
"home.description":
"Do you really want to give your personal files in the hand of third parties like WeTransfer?",
"home.bullet.a.name": "Self-Hosted",
"home.bullet.a.description": "Host Pingvin Share on your own machine.",
"home.bullet.b.name": "Privacy",
"home.bullet.b.description": "Your files are your files and should never get into the hands of third parties.",
"home.bullet.b.description":
"Your files are your files and should never get into the hands of third parties.",
"home.bullet.c.name": "No annoying file size limit",
"home.bullet.c.description": "Upload as big files as you want. Only your hard drive will be your limit.",
"home.bullet.c.description":
"Upload as big files as you want. Only your hard drive will be your limit.",
"home.button.start": "Get started",
"home.button.source": "Source code",
// END /
// /auth/signin
"signin.title": "Welcome back",
"signin.description": "You don't have an account yet?",
@@ -32,8 +41,11 @@ export default {
"signin.input.password.placeholder": "Your password",
"signin.button.submit": "Sign in",
"signIn.notify.totp-required.title": "Two-factor authentication required",
"signIn.notify.totp-required.description": "Please enter your two-factor authentication code",
"signIn.notify.totp-required.description":
"Please enter your two-factor authentication code",
// END /auth/signin
// /auth/signup
"signup.title": "Create an account",
"signup.description": "Already have an account?",
@@ -43,29 +55,39 @@ export default {
"signup.input.email": "Email",
"signup.input.email.placeholder": "Your email",
"signup.button.submit": "Let's get started",
// END /auth/signup
// /auth/reset-password
"resetPassword.title": "Forgot your password?",
"resetPassword.description": "Enter your email to reset your password.",
"resetPassword.notify.success": "An email has been sent with a link to reset your password.",
"resetPassword.notify.success":
"An email has been sent with a link to reset your password.",
"resetPassword.button.back": "Back to sign in page",
"resetPassword.text.resetPassword": "Reset password",
"resetPassword.text.enterNewPassword": "Enter your new password",
"resetPassword.input.password": "New password",
"resetPassword.notify.passwordReset": "Your password has been reset successfully.",
"resetPassword.notify.passwordReset":
"Your password has been reset successfully.",
// /account
"account.title": "My account",
"account.card.info.title": "Account info",
"account.card.info.username": "Username",
"account.card.info.email": "Email",
"account.notify.info.success": "Account updated successfully",
"account.card.password.title": "Password",
"account.card.password.old": "Old password",
"account.card.password.new": "New password",
"account.notify.password.success": "Password changed successfully",
"account.card.security.title": "Security",
"account.card.security.totp.enable.description": "Enter your current password to start enabling TOTP",
"account.card.security.totp.disable.description": "Enter your current password to disable TOTP",
"account.card.security.totp.enable.description":
"Enter your current password to start enabling TOTP",
"account.card.security.totp.disable.description":
"Enter your current password to disable TOTP",
"account.card.security.totp.button.start": "Start",
"account.modal.totp.title": "Enable TOTP",
"account.modal.totp.step1": "Step 1: Add your authenticator",
@@ -76,22 +98,29 @@ export default {
"account.modal.totp.verify": "Verify",
"account.notify.totp.disable": "TOTP disabled successfully",
"account.notify.totp.enable": "TOTP enabled successfully",
"account.card.language.title": "Language",
"account.card.language.description": "The project is translated by the community. Some languages might be incomplete.",
"account.card.language.description":
"The project is translated by the community. Some languages might be incomplete.",
"account.card.color.title": "Color scheme",
// ThemeSwitcher.tsx
"account.theme.dark": "Dark",
"account.theme.light": "Light",
"account.theme.system": "System",
"account.button.delete": "Delete Account",
"account.modal.delete.title": "Delete Account",
"account.modal.delete.description": "Do you really want to delete your account including all your active shares?",
"account.modal.delete.description":
"Do you really want to delete your account including all your active shares?",
// END /account
// /account/shares
"account.shares.title": "My shares",
"account.shares.title.empty": "It's empty here 👀",
"account.shares.description.empty": "You don't have any shares.",
"account.shares.button.create": "Create one",
"account.shares.info.title": "Share informations",
"account.shares.table.id": "ID",
"account.shares.table.name": "Name",
@@ -100,16 +129,25 @@ export default {
"account.shares.table.expiresAt": "Expires at",
"account.shares.table.createdAt": "Created at",
"account.shares.table.size": "Size",
"account.shares.modal.share-informations": "Share informations",
"account.shares.modal.share-link": "Share link",
"account.shares.modal.delete.title": "Delete share {share}",
"account.shares.modal.delete.description": "Do you really want to delete this share?",
"account.shares.modal.delete.description":
"Do you really want to delete this share?",
// END /account/shares
// /account/reverseShares
"account.reverseShares.title": "Reverse shares",
"account.reverseShares.description": "A reverse share allows you to generate a unique URL that allows external users to create a share.",
"account.reverseShares.description":
"A reverse share allows you to generate a unique URL that allows external users to create a share.",
"account.reverseShares.title.empty": "It's empty here 👀",
"account.reverseShares.description.empty": "You don't have any reverse shares.",
"account.reverseShares.description.empty":
"You don't have any reverse shares.",
// showCreateReverseShareModal.tsx
"account.reverseShares.modal.expiration.label": "Expiration",
"account.reverseShares.modal.expiration.minute-singular": "Minute",
@@ -124,13 +162,20 @@ export default {
"account.reverseShares.modal.expiration.month-plural": "Months",
"account.reverseShares.modal.expiration.year-singular": "Year",
"account.reverseShares.modal.expiration.year-plural": "Years",
"account.reverseShares.modal.max-size.label": "Max share size",
"account.reverseShares.modal.send-email": "Send email notification",
"account.reverseShares.modal.send-email.description": "Send an email notification when a share is created with this reverse share link.",
"account.reverseShares.modal.send-email.description":
"Send an email notification when a share is created with this reverse share link.",
"account.reverseShares.modal.max-use.label": "Max uses",
"account.reverseShares.modal.max-use.description": "The maximum amount of times this URL can be used to create a share.",
"account.reverseShares.modal.max-use.description":
"The maximum amount of times this URL can be used to create a share.",
"account.reverseShare.never-expires": "This reverse share will never expire.",
"account.reverseShare.expires-on": "This reverse share will expire on {expiration}.",
"account.reverseShare.expires-on":
"This reverse share will expire on {expiration}.",
"account.reverseShares.table.no-shares": "No shares created yet",
"account.reverseShares.table.count.singular": "share",
"account.reverseShares.table.count.plural": "shares",
@@ -138,58 +183,84 @@ export default {
"account.reverseShares.table.remaining": "Remaining uses",
"account.reverseShares.table.max-size": "Max share size",
"account.reverseShares.table.expires": "Expires at",
"account.reverseShares.modal.reverse-share-link": "Reverse share link",
"account.reverseShares.modal.delete.title": "Delete reverse share",
"account.reverseShares.modal.delete.description": "Do you really want to delete this reverse share? If you do, the associated shares will be deleted as well.",
"account.reverseShares.modal.delete.description":
"Do you really want to delete this reverse share? If you do, the associated shares will be deleted as well.",
// END /account/reverseShares
// /admin
"admin.title": "Administration",
"admin.button.users": "User management",
"admin.button.config": "Configuration",
"admin.version": "Version",
// END /admin
// /admin/users
"admin.users.title": "User management",
"admin.users.table.username": "Username",
"admin.users.table.email": "Email",
"admin.users.table.admin": "Admin",
"admin.users.edit.update.title": "Update user {username}",
"admin.users.edit.update.admin-privileges": "Admin privileges",
"admin.users.edit.update.change-password.title": "Change password",
"admin.users.edit.update.change-password.field": "New password",
"admin.users.edit.update.change-password.button": "Save new password",
"admin.users.edit.update.notify.password.success": "Password changed successfully",
"admin.users.edit.update.notify.password.success":
"Password changed successfully",
"admin.users.edit.delete.title": "Delete user {username}",
"admin.users.edit.delete.description": "Do you really want to delete this user and all his shares?",
"admin.users.edit.delete.description":
"Do you really want to delete this user and all his shares?",
// showCreateUserModal.tsx
"admin.users.modal.create.title": "Create user",
"admin.users.modal.create.username": "Username",
"admin.users.modal.create.email": "Email",
"admin.users.modal.create.password": "Password",
"admin.users.modal.create.manual-password": "Set password manually",
"admin.users.modal.create.manual-password.description": "If not checked, the user will receive an email with a link to set their password.",
"admin.users.modal.create.manual-password.description":
"If not checked, the user will receive an email with a link to set their password.",
"admin.users.modal.create.admin": "Admin privileges",
"admin.users.modal.create.admin.description": "If checked, the user will be able to access the admin panel.",
"admin.users.modal.create.admin.description":
"If checked, the user will be able to access the admin panel.",
// END /admin/users
// /upload
"upload.title": "Upload",
"upload.notify.generic-error": "An error occurred while finishing your share.",
"upload.notify.generic-error":
"An error occurred while finishing your share.",
"upload.notify.count-failed": "{count} files failed to upload. Trying again.",
// Dropzone.tsx
"upload.dropzone.title": "Upload files",
"upload.dropzone.description": "Drag'n'drop files here to start your share. We can accept only files that are less than {maxSize} in total.",
"upload.dropzone.notify.file-too-big": "Your files exceed the maximum share size of {maxSize}.",
"upload.dropzone.description":
"Drag'n'drop files here to start your share. We can accept only files that are less than {maxSize} in total.",
"upload.dropzone.notify.file-too-big":
"Your files exceed the maximum share size of {maxSize}.",
// FileList.tsx
"upload.filelist.name": "Name",
"upload.filelist.size": "Size",
// showCreateUploadModal.tsx
"upload.modal.title": "Create Share",
"upload.modal.link.error.invalid": "Can only contain letters, numbers, underscores, and hyphens",
"upload.modal.link.error.invalid":
"Can only contain letters, numbers, underscores, and hyphens",
"upload.modal.link.error.taken": "This link is already in use",
"upload.modal.not-signed-in": "You're not signed in",
"upload.modal.not-signed-in-description": "You will be unable to delete your share manually and view the visitor count.",
"upload.modal.not-signed-in-description":
"You will be unable to delete your share manually and view the visitor count.",
"upload.modal.expires.never": "never",
"upload.modal.expires.never-long": "Never Expires",
"upload.modal.link.label": "Link",
"upload.modal.expires.label": "Expiration",
"upload.modal.expires.minute-singular": "Minute",
@@ -204,98 +275,141 @@ export default {
"upload.modal.expires.month-plural": "Months",
"upload.modal.expires.year-singular": "Year",
"upload.modal.expires.year-plural": "Years",
"upload.modal.accordion.description.title": "Description",
"upload.modal.accordion.description.placeholder": "Note for the recipients of this share",
"upload.modal.accordion.description.placeholder":
"Note for the recipients of this share",
"upload.modal.accordion.email.title": "Email recipients",
"upload.modal.accordion.email.placeholder": "Enter email recipients",
"upload.modal.accordion.email.invalid-email": "Invalid email address",
"upload.modal.accordion.security.title": "Security options",
"upload.modal.accordion.security.password.label": "Password protection",
"upload.modal.accordion.security.password.placeholder": "No password",
"upload.modal.accordion.security.max-views.label": "Maximum views",
"upload.modal.accordion.security.max-views.placeholder": "No limit",
// showCompletedUploadModal.tsx
"upload.modal.completed.never-expires": "This share will never expire.",
"upload.modal.completed.expires-on": "This share will expire on {expiration}.",
"upload.modal.completed.expires-on":
"This share will expire on {expiration}.",
"upload.modal.completed.share-ready": "Share ready",
// END /upload
// /share/[id]
"share.title": "Share {shareId}",
"share.description": "Look what I've shared with you!",
"share.error.visitor-limit-exceeded.title": "Visitor limit exceeded",
"share.error.visitor-limit-exceeded.description": "The visitor limit from this share has been exceeded.",
"share.error.visitor-limit-exceeded.description":
"The visitor limit from this share has been exceeded.",
"share.error.removed.title": "Share removed",
"share.error.not-found.title": "Share not found",
"share.error.not-found.description": "The share you're looking for doesn't exist.",
"share.error.not-found.description":
"The share you're looking for doesn't exist.",
"share.modal.password.title": "Password required",
"share.modal.password.description": "To access this share please enter the password for the share.",
"share.modal.password.description":
"To access this share please enter the password for the share.",
"share.modal.password": "Password",
"share.modal.error.invalid-password": "Invalid password",
"share.button.download-all": "Download all",
"share.notify.download-all-preparing": "The share is preparing. Try again in a few minutes.",
"share.notify.download-all-preparing":
"The share is preparing. Try again in a few minutes.",
"share.modal.file-link": "File link",
"share.table.name": "Name",
"share.table.size": "Size",
"share.modal.file-preview.error.not-supported.title": "Preview not supported",
"share.modal.file-preview.error.not-supported.description": "A preview for thise file type is unsupported. Please download the file to view it.",
"share.modal.file-preview.error.not-supported.description":
"A preview for thise file type is unsupported. Please download the file to view it.",
// END /share/[id]
// /admin/config
"admin.config.title": "Configuration",
"admin.config.category.general": "General",
"admin.config.category.share": "Share",
"admin.config.category.email": "Email",
"admin.config.category.smtp": "SMTP",
"admin.config.general.app-name": "App name",
"admin.config.general.app-name.description": "Name of the application",
"admin.config.general.app-url": "App URL",
"admin.config.general.app-url.description": "On which URL Pingvin Share is available",
"admin.config.general.app-url.description":
"On which URL Pingvin Share is available",
"admin.config.general.show-home-page": "Show home page",
"admin.config.general.show-home-page.description": "Whether to show the home page",
"admin.config.general.show-home-page.description":
"Whether to show the home page",
"admin.config.general.logo": "Logo",
"admin.config.general.logo.description": "Change your logo by uploading a new image. The image must be a PNG and should have the format 1:1.",
"admin.config.general.logo.description":
"Change your logo by uploading a new image. The image must be a PNG and should have the format 1:1.",
"admin.config.general.logo.placeholder": "Pick image",
"admin.config.email.enable-share-email-recipients": "Enable share email recipients",
"admin.config.email.enable-share-email-recipients.description": "Whether to allow emails to share recipients. Only enable this if you have enabled SMTP.",
"admin.config.email.enable-share-email-recipients":
"Enable share email recipients",
"admin.config.email.enable-share-email-recipients.description":
"Whether to allow emails to share recipients. Only enable this if you have enabled SMTP.",
"admin.config.email.share-recipients-subject": "Share recipients subject",
"admin.config.email.share-recipients-subject.description": "Subject of the email which gets sent to the share recipients.",
"admin.config.email.share-recipients-subject.description":
"Subject of the email which gets sent to the share recipients.",
"admin.config.email.share-recipients-message": "Share recipients message",
"admin.config.email.share-recipients-message.description": "Message which gets sent to the share recipients. Available 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 The variables will be replaced with the actual value.",
"admin.config.email.share-recipients-message.description":
"Message which gets sent to the share recipients. Available 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 The variables will be replaced with the actual value.",
"admin.config.email.reverse-share-subject": "Reverse share subject",
"admin.config.email.reverse-share-subject.description": "Subject of the email which gets sent when someone created a share with your reverse share link.",
"admin.config.email.reverse-share-subject.description":
"Subject of the email which gets sent when someone created a share with your reverse share link.",
"admin.config.email.reverse-share-message": "Reverse share message",
"admin.config.email.reverse-share-message.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.",
"admin.config.email.reverse-share-message.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.",
"admin.config.email.reset-password-subject": "Reset password subject",
"admin.config.email.reset-password-subject.description": "Subject of the email which gets sent when a user requests a password reset.",
"admin.config.email.reset-password-subject.description":
"Subject of the email which gets sent when a user requests a password reset.",
"admin.config.email.reset-password-message": "Reset password message",
"admin.config.email.reset-password-message.description": "Message which gets sent when a user requests a password reset. {url} will be replaced with the reset password URL.",
"admin.config.email.reset-password-message.description":
"Message which gets sent when a user requests a password reset. {url} will be replaced with the reset password URL.",
"admin.config.email.invite-subject": "Invite subject",
"admin.config.email.invite-subject.description": "Subject of the email which gets sent when an admin invites a user.",
"admin.config.email.invite-subject.description":
"Subject of the email which gets sent when an admin invites a user.",
"admin.config.email.invite-message": "Invite message",
"admin.config.email.invite-message.description": "Message which gets sent when an admin invites a user. {url} will be replaced with the invite URL and {password} with the password.",
"admin.config.email.invite-message.description":
"Message which gets sent when an admin invites a user. {url} will be replaced with the invite URL and {password} with the password.",
"admin.config.share.allow-registration": "Allow registration",
"admin.config.share.allow-registration.description": "Whether registration is allowed",
"admin.config.share.allow-unauthenticated-shares": "Allow unauthenticated shares",
"admin.config.share.allow-unauthenticated-shares.description": "Whether unauthenticated users can create shares",
"admin.config.share.allow-registration.description":
"Whether registration is allowed",
"admin.config.share.allow-unauthenticated-shares":
"Allow unauthenticated shares",
"admin.config.share.allow-unauthenticated-shares.description":
"Whether unauthenticated users can create shares",
"admin.config.share.max-size": "Max size",
"admin.config.share.max-size.description": "Maximum share size in bytes",
"admin.config.share.zip-compression-level": "Zip compression level",
"admin.config.share.zip-compression-level.description": "Adjust the level to balance between file size and compression speed. Valid values range from 0 to 9, with 0 being no compression and 9 being maximum compression. ",
"admin.config.share.zip-compression-level.description":
"Adjust the level to balance between file size and compression speed. Valid values range from 0 to 9, with 0 being no compression and 9 being maximum compression. ",
"admin.config.smtp.enabled": "Enabled",
"admin.config.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.",
"admin.config.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.",
"admin.config.smtp.host": "Host",
"admin.config.smtp.host.description": "Host of the SMTP server",
"admin.config.smtp.port": "Port",
"admin.config.smtp.port.description": "Port of the SMTP server",
"admin.config.smtp.email": "Email",
"admin.config.smtp.email.description": "Email address which the emails get sent from",
"admin.config.smtp.email.description":
"Email address which the emails get sent from",
"admin.config.smtp.username": "Username",
"admin.config.smtp.username.description": "Username of the SMTP server",
"admin.config.smtp.password": "Password",
"admin.config.smtp.password.description": "Password of the SMTP server",
"admin.config.smtp.button.test": "Send test email",
// 404
"404.description": "Oops this page doesn't exist.",
"404.button.home": "Bring me back home",
// Common translations
"common.button.save": "Save",
"common.button.create": "Create",
@@ -312,6 +426,7 @@ export default {
"common.button.go-back": "Go back",
"common.notify.copied": "Your link was copied to the clipboard",
"common.success": "Success",
"common.error": "Error",
"common.error.unknown": "An unknown error occurred",
"common.error.invalid-email": "Invalid email address",
@@ -319,5 +434,5 @@ export default {
"common.error.too-long": "Must be at most {length} characters",
"common.error.exact-length": "Must be exactly {length} characters",
"common.error.invalid-number": "Must be a number",
"common.error.field-required": "This field is required"
"common.error.field-required": "This field is required",
};

View File

@@ -1,6 +1,6 @@
{
"name": "pingvin-share",
"version": "0.17.1",
"version": "0.17.2",
"scripts": {
"format": "cd frontend && npm run format && cd ../backend && npm run format",
"lint": "cd frontend && npm run lint && cd ../backend && npm run lint",