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 <stautonico@gmail.com>
This commit is contained in:
@@ -13,14 +13,17 @@ import {
|
||||
Text,
|
||||
Textarea,
|
||||
TextInput,
|
||||
Title,
|
||||
} from "@mantine/core";
|
||||
import { useForm, yupResolver } from "@mantine/form";
|
||||
import { useModals } from "@mantine/modals";
|
||||
import { ModalsContextProps } from "@mantine/modals/lib/context";
|
||||
import { useState } from "react";
|
||||
import { TbAlertCircle } from "react-icons/tb";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import * as yup from "yup";
|
||||
import useTranslate, {
|
||||
translateOutsideContext,
|
||||
} from "../../../hooks/useTranslate.hook";
|
||||
import shareService from "../../../services/share.service";
|
||||
import { CreateShare } from "../../../types/share.type";
|
||||
import { getExpirationPreview } from "../../../utils/date.util";
|
||||
@@ -36,8 +39,10 @@ const showCreateUploadModal = (
|
||||
},
|
||||
uploadCallback: (createShare: CreateShare) => void
|
||||
) => {
|
||||
const t = translateOutsideContext();
|
||||
|
||||
return modals.openModal({
|
||||
title: "Share",
|
||||
title: t("upload.modal.title"),
|
||||
children: (
|
||||
<CreateUploadModalBody
|
||||
options={options}
|
||||
@@ -61,6 +66,7 @@ const CreateUploadModalBody = ({
|
||||
};
|
||||
}) => {
|
||||
const modals = useModals();
|
||||
const t = useTranslate();
|
||||
|
||||
const generatedLink = Buffer.from(Math.random().toString(), "utf8")
|
||||
.toString("base64")
|
||||
@@ -71,11 +77,11 @@ const CreateUploadModalBody = ({
|
||||
const validationSchema = yup.object().shape({
|
||||
link: yup
|
||||
.string()
|
||||
.required()
|
||||
.min(3)
|
||||
.max(50)
|
||||
.required(t("common.error.field-required"))
|
||||
.min(3, t("common.error.too-short", { length: 3 }))
|
||||
.max(50, t("common.error.too-long", { length: 50 }))
|
||||
.matches(new RegExp("^[a-zA-Z0-9_-]*$"), {
|
||||
message: "Can only contain letters, numbers, underscores and hyphens",
|
||||
message: t("upload.modal.link.error.invalid"),
|
||||
}),
|
||||
password: yup.string().min(3).max(30),
|
||||
maxViews: yup.number().min(1),
|
||||
@@ -100,20 +106,19 @@ const CreateUploadModalBody = ({
|
||||
withCloseButton
|
||||
onClose={() => setShowNotSignedInAlert(false)}
|
||||
icon={<TbAlertCircle size={16} />}
|
||||
title="You're not signed in"
|
||||
title={t("upload.modal.not-signed-in")}
|
||||
color="yellow"
|
||||
>
|
||||
You will be unable to delete your share manually and view the visitor
|
||||
count.
|
||||
<FormattedMessage id="upload.modal.not-signed-in-description" />
|
||||
</Alert>
|
||||
)}
|
||||
<form
|
||||
onSubmit={form.onSubmit(async (values) => {
|
||||
if (!(await shareService.isShareIdAvailable(values.link))) {
|
||||
form.setFieldError("link", "This link is already in use");
|
||||
form.setFieldError("link", t("upload.modal.link.error.taken"));
|
||||
} else {
|
||||
const expiration = form.values.never_expires
|
||||
? "never"
|
||||
? t("upload.modal.expires.never")
|
||||
: form.values.expiration_num + form.values.expiration_unit;
|
||||
uploadCallback({
|
||||
id: values.link,
|
||||
@@ -151,7 +156,7 @@ const CreateUploadModalBody = ({
|
||||
)
|
||||
}
|
||||
>
|
||||
Generate
|
||||
<FormattedMessage id="common.button.generate" />
|
||||
</Button>
|
||||
</Col>
|
||||
</Grid>
|
||||
@@ -169,18 +174,6 @@ const CreateUploadModalBody = ({
|
||||
{!options.isReverseShare && (
|
||||
<>
|
||||
<Grid align={form.errors.link ? "center" : "flex-end"}>
|
||||
<Col xs={6}>
|
||||
<NumberInput
|
||||
min={1}
|
||||
max={99999}
|
||||
precision={0}
|
||||
variant="filled"
|
||||
label="Expiration"
|
||||
placeholder="n"
|
||||
disabled={form.values.never_expires}
|
||||
{...form.getInputProps("expiration_num")}
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={6}>
|
||||
<Select
|
||||
disabled={form.values.never_expires}
|
||||
@@ -190,41 +183,51 @@ const CreateUploadModalBody = ({
|
||||
{
|
||||
value: "-minutes",
|
||||
label:
|
||||
"Minute" +
|
||||
(form.values.expiration_num == 1 ? "" : "s"),
|
||||
form.values.expiration_num == 1
|
||||
? t("upload.modal.expires.minute-singular")
|
||||
: t("upload.modal.expires.minute-plural"),
|
||||
},
|
||||
{
|
||||
value: "-hours",
|
||||
label:
|
||||
"Hour" + (form.values.expiration_num == 1 ? "" : "s"),
|
||||
form.values.expiration_num == 1
|
||||
? t("upload.modal.expires.hour-singular")
|
||||
: t("upload.modal.expires.hour-plural"),
|
||||
},
|
||||
{
|
||||
value: "-days",
|
||||
label:
|
||||
"Day" + (form.values.expiration_num == 1 ? "" : "s"),
|
||||
form.values.expiration_num == 1
|
||||
? t("upload.modal.expires.day-singular")
|
||||
: t("upload.modal.expires.day-plural"),
|
||||
},
|
||||
{
|
||||
value: "-weeks",
|
||||
label:
|
||||
"Week" + (form.values.expiration_num == 1 ? "" : "s"),
|
||||
form.values.expiration_num == 1
|
||||
? t("upload.modal.expires.week-singular")
|
||||
: t("upload.modal.expires.week-plural"),
|
||||
},
|
||||
{
|
||||
value: "-months",
|
||||
label:
|
||||
"Month" +
|
||||
(form.values.expiration_num == 1 ? "" : "s"),
|
||||
form.values.expiration_num == 1
|
||||
? t("upload.modal.expires.month-singular")
|
||||
: t("upload.modal.expires.month-plural"),
|
||||
},
|
||||
{
|
||||
value: "-years",
|
||||
label:
|
||||
"Year" + (form.values.expiration_num == 1 ? "" : "s"),
|
||||
form.values.expiration_num == 1
|
||||
? t("upload.modal.expires.year-singular")
|
||||
: t("upload.modal.expires.year-plural"),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Col>
|
||||
</Grid>
|
||||
<Checkbox
|
||||
label="Never Expires"
|
||||
label={t("upload.modal.expires.never-long")}
|
||||
{...form.getInputProps("never_expires")}
|
||||
/>
|
||||
<Text
|
||||
@@ -234,18 +237,28 @@ const CreateUploadModalBody = ({
|
||||
color: theme.colors.gray[6],
|
||||
})}
|
||||
>
|
||||
{getExpirationPreview("share", form)}
|
||||
{getExpirationPreview(
|
||||
{
|
||||
neverExpires: t("upload.modal.completed.never-expires"),
|
||||
expiresOn: t("upload.modal.completed.expires-on"),
|
||||
},
|
||||
form
|
||||
)}
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
<Accordion>
|
||||
<Accordion.Item value="description" sx={{ borderBottom: "none" }}>
|
||||
<Accordion.Control>Description</Accordion.Control>
|
||||
<Accordion.Control>
|
||||
<FormattedMessage id="upload.modal.accordion.description.title" />
|
||||
</Accordion.Control>
|
||||
<Accordion.Panel>
|
||||
<Stack align="stretch">
|
||||
<Textarea
|
||||
variant="filled"
|
||||
placeholder="Note for the recepients"
|
||||
placeholder={t(
|
||||
"upload.modal.accordion.description.placeholder"
|
||||
)}
|
||||
{...form.getInputProps("description")}
|
||||
/>
|
||||
</Stack>
|
||||
@@ -253,11 +266,13 @@ const CreateUploadModalBody = ({
|
||||
</Accordion.Item>
|
||||
{options.enableEmailRecepients && (
|
||||
<Accordion.Item value="recipients" sx={{ borderBottom: "none" }}>
|
||||
<Accordion.Control>Email recipients</Accordion.Control>
|
||||
<Accordion.Control>
|
||||
<FormattedMessage id="upload.modal.accordion.email.tile" />
|
||||
</Accordion.Control>
|
||||
<Accordion.Panel>
|
||||
<MultiSelect
|
||||
data={form.values.recipients}
|
||||
placeholder="Enter email recipients"
|
||||
placeholder={t("upload.modal.accordion.email.placeholder")}
|
||||
searchable
|
||||
{...form.getInputProps("recipients")}
|
||||
creatable
|
||||
@@ -266,7 +281,7 @@ const CreateUploadModalBody = ({
|
||||
if (!query.match(/^\S+@\S+\.\S+$/)) {
|
||||
form.setFieldError(
|
||||
"recipients",
|
||||
"Invalid email address"
|
||||
t("upload.modal.accordion.email.invalid-email")
|
||||
);
|
||||
} else {
|
||||
form.setFieldError("recipients", null);
|
||||
@@ -283,28 +298,36 @@ const CreateUploadModalBody = ({
|
||||
)}
|
||||
|
||||
<Accordion.Item value="security" sx={{ borderBottom: "none" }}>
|
||||
<Accordion.Control>Security options</Accordion.Control>
|
||||
<Accordion.Control>
|
||||
<FormattedMessage id="upload.modal.accordion.security.title" />
|
||||
</Accordion.Control>
|
||||
<Accordion.Panel>
|
||||
<Stack align="stretch">
|
||||
<PasswordInput
|
||||
variant="filled"
|
||||
placeholder="No password"
|
||||
label="Password protection"
|
||||
placeholder={t(
|
||||
"upload.modal.accordion.security.password.placeholder"
|
||||
)}
|
||||
label={t("upload.modal.accordion.security.password.label")}
|
||||
{...form.getInputProps("password")}
|
||||
/>
|
||||
<NumberInput
|
||||
min={1}
|
||||
type="number"
|
||||
variant="filled"
|
||||
placeholder="No limit"
|
||||
label="Maximal views"
|
||||
placeholder={t(
|
||||
"upload.modal.accordion.security.max-views.placeholder"
|
||||
)}
|
||||
label={t("upload.modal.accordion.security.max-views.label")}
|
||||
{...form.getInputProps("maxViews")}
|
||||
/>
|
||||
</Stack>
|
||||
</Accordion.Panel>
|
||||
</Accordion.Item>
|
||||
</Accordion>
|
||||
<Button type="submit">Share</Button>
|
||||
<Button type="submit">
|
||||
<FormattedMessage id="common.button.share" />
|
||||
</Button>
|
||||
</Stack>
|
||||
</form>
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user