Skip to content

Commit

Permalink
new: french translation
Browse files Browse the repository at this point in the history
* French localization

* finish french translation

* Updated README
  • Loading branch information
ybizeul authored Sep 13, 2024
1 parent 03879be commit 149e953
Show file tree
Hide file tree
Showing 11 changed files with 235 additions and 23 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,12 @@ SSL)
- Configurable max share size and max file size,
- Basic share informations listed (number of items, total size),
- Add instructions in Markdown for your users and define your own reusable templates.
- Download a zip archive of all files in a share,
- Automatic dark mode following OS settings,
- Multi user (all admins see all shares, but see their own listed separately first),
- Flat user file or OIDC authentication,
- API first, everything can be done through REST calls,
- English and French translations,
- Minimalist, clean interface.

## Configuration
Expand Down
3 changes: 3 additions & 0 deletions html/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,12 @@
"@mantine/hooks": "^7.12.2",
"@tabler/icons-react": "^3.14.0",
"axios": "^1.7.7",
"i18next": "^23.15.1",
"i18next-browser-languagedetector": "^8.0.0",
"marked": "^14.1.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-i18next": "^15.0.1",
"react-router-dom": "^6.26.1"
},
"devDependencies": {
Expand Down
1 change: 1 addition & 0 deletions html/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import "@mantine/core/styles.css";
import '@mantine/dropzone/styles.css';
import "./i18n/config.ts";

import { useEffect, useState } from "react";
import { Container, MantineProvider } from "@mantine/core";
Expand Down
4 changes: 3 additions & 1 deletion html/src/Components/MarkdownEditor/FullHeightTextArea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ import { Textarea } from "@mantine/core";

import classes from './FullHeightTextArea.module.css';
import { useUncontrolled } from "@mantine/hooks";
import { useTranslation } from "react-i18next";

interface FullHeightTextAreaProps {
value?: string;
onChange?: (value: string) => void;
}
export function FullHeightTextArea(props: FullHeightTextAreaProps) {
const {t} = useTranslation()
const { value, onChange } = props;

const [_value, handleChange] = useUncontrolled({
Expand All @@ -16,7 +18,7 @@ export function FullHeightTextArea(props: FullHeightTextAreaProps) {
});

return (
<Textarea w="100%" flex="1" label="Message" description="This markdown will be displayed to the user" resize="vertical" value={_value}
<Textarea w="100%" flex="1" label="Message" description={t("markdown_description")} resize="vertical" value={_value}
classNames={{root: classes.root, wrapper: classes.wrapper, input: classes.input}}
onChange={(e) => { handleChange(e.currentTarget.value) }}
/>
Expand Down
5 changes: 4 additions & 1 deletion html/src/Components/MarkdownEditor/MarkdownEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@ import { useDisclosure, useUncontrolled } from "@mantine/hooks";
import { IconEye } from "@tabler/icons-react";
import { Message } from "../Message";
import { FullHeightTextArea } from "./FullHeightTextArea";
import { useTranslation } from "react-i18next";

interface MarkDownEditorProps {
onChange: (message: string) => void;
message: string;
}

export function MarkDownEditor(props: MarkDownEditorProps&BoxComponentProps) {
const {t} = useTranslation()

// Initialize props
const { onChange, message } = props;

Expand All @@ -35,7 +38,7 @@ interface MarkDownEditorProps {
<IconEye style={{ width: rem(16), height: rem(16) }} stroke={1.5} />
</ActionIcon>
{preview?
<InputWrapper display="flex" style={{flexDirection:"column"}} label="Message" description="This markdown will be displayed to the user" w="100%">
<InputWrapper display="flex" style={{flexDirection:"column"}} label={t("message")} description={t("markdown_description")} w="100%">
<Paper flex="1" withBorder mt="5" pt="5.5" px="12" display="flex">
<Message value={markdown} />
</Paper>
Expand Down
11 changes: 7 additions & 4 deletions html/src/Components/ShareComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ import { ShareEditor } from "./ShareEditor";
import { Dropzone } from "@mantine/dropzone";
import { QueueItem, UploadQueue } from "@/UploadQueue";

import { useTranslation } from "react-i18next";

export function ShareComponent(props: {share: Share}) {
const { t } = useTranslation();
// Initialize States
const [share,setShare] = useState(props.share)
const [deleted,setDeleted] = useState(false)
Expand All @@ -28,7 +31,7 @@ export function ShareComponent(props: {share: Share}) {
const name = share.name
const count = share.count
const size = share.size
const countString = prettyfiedCount(count,"item", "items","empty")
const countString = prettyfiedCount(count,t("item"), t("items"),t("empty"))
const remaining = (share.options.validity===0||share.options.validity===undefined)?null:(new Date(share.created).getTime() + share.options.validity*1000*60*60*24 - Date.now()) / 1000 / 60 / 60 / 24

// Function
Expand Down Expand Up @@ -85,12 +88,12 @@ export function ShareComponent(props: {share: Share}) {
<IconClock color={(remaining===null || remaining > 0 )?"gray":"red"} size="0.8em" width={"1em"}/>
<Text style={{ whiteSpace: "nowrap"}} size="xs" c="gray">{
(remaining === null)?
"Unlimited"
t("unlimited")
:
(remaining<0)?
"Expired"
t("expired")
:
prettyfiedCount(remaining,"day","days",null) + " left"} | {share.options.exposure==="download"?"Guests can download":(share.options.exposure==="both"?"Guests can upload & download":"Guests can upload")}
prettyfiedCount(remaining,t("day_left"),t("days_left"),null)} | {share.options.exposure==="download"?t("guests_can_download"):(share.options.exposure==="both"?t("guests_can_upload_and_download"):t("guests_can_upload"))}
</Text>
</Group>
{(uploading || uploadPercent > 0) &&
Expand Down
16 changes: 9 additions & 7 deletions html/src/Components/ShareEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useDisclosure, useMediaQuery, useUncontrolled } from "@mantine/hooks";
import classes from './ShareEditor.module.css';
import { MarkDownEditor } from "./MarkdownEditor";
import { TemplatesMenu } from "./TemplatesMenu";
import { useTranslation } from "react-i18next";

interface ShareEditorProps {
onChange: (options: Share["options"]) => void;
Expand All @@ -15,6 +16,7 @@ interface ShareEditorProps {
}

export function ShareEditor(props: ShareEditorProps&BoxComponentProps) {
const { t } = useTranslation()
// Initialize props
const { onChange, onClick, close, buttonTitle } = props;

Expand Down Expand Up @@ -58,30 +60,30 @@ export function ShareEditor(props: ShareEditorProps&BoxComponentProps) {
}

{/* Share exposure */}
<Input.Wrapper label="Exposure" description="Guests users can :">
<Input.Wrapper label={t("exposure")} description={t("guest_users_can")}>
<SegmentedControl
className={classes.segmented}
value={options.exposure}
data={[ { label: 'Upload', value: 'upload' },
{ label: 'Download', value: 'download' },
{ label: 'Both', value: 'both' },
data={[ { label: t("upload"), value: 'upload' },
{ label: t("download"), value: 'download' },
{ label: t("both"), value: 'both' },
]}
onChange={(v) => { notifyChange({...options, exposure:v}); }} transitionDuration={0}
/>
</Input.Wrapper>

{/* Share validity */}
<NumberInput
label="Validity"
description={"Number of days the share is valid. 0 is unlimited."}
label={t("validity")}
description={t("number_of_days_the_share_is_valid")}
value={options.validity}
min={0}
classNames={{wrapper: classes.numberInput}}
onChange={(v) => { notifyChange({...options, validity:v as number}); }}
/>

{/* Share description */}
<TextInput label="Description" value={options.description}
<TextInput label={t("description")} value={options.description}
onChange={(v) => { notifyChange({...options, description:v.target.value}); }}
/>
</Stack>
Expand Down
14 changes: 8 additions & 6 deletions html/src/Pages/SharePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ import { useAuthContext } from "@/AuthContext";
import { Message } from "@/Components/Message";
import { useShare } from "@/hooks";
import { AxiosError } from "axios";
import { useTranslation } from "react-i18next";

export function SharePage() {
const { t } = useTranslation();

const [items, setItems] = useState<Item[]|undefined>(undefined)
const [queueItems, setQueueItems] = useState<QueueItem[]>([])
Expand Down Expand Up @@ -62,7 +64,7 @@ export function SharePage() {
<Center h="100vh">
<Stack align="center" pb="10em">
<IconClock style={{ width: '10%', height: '10%' }} stroke={1.5}/>
<Text size="xl" fw="700">Sorry, this share has expired</Text>
<Text size="xl" fw="700">{t("sorry_share_expired")}</Text>
</Stack>
</Center>
)
Expand All @@ -73,8 +75,8 @@ export function SharePage() {
<Center h="100vh">
<Stack align="center" pb="10em">
<IconHelpHexagon style={{ width: '10%', height: '10%' }} stroke={1.5}/>
<Text size="xl" fw="700">Share does not exists</Text>
<Text>Please check the link used to access this page.</Text>
<Text size="xl" fw="700">{t("share_does_not_exists")}</Text>
<Text>{t("please_check_link")}</Text>
</Stack>
</Center>
)
Expand All @@ -86,7 +88,7 @@ export function SharePage() {
<Stack align="center" pb="10em">
<IconMoodSad style={{ width: '10%', height: '10%' }} stroke={1.5}/>
<Text size="xl" fw="700">{error.message}</Text>
<Anchor onClick={() => { window.location.reload()}}>Reload</Anchor>
<Anchor onClick={() => { window.location.reload()}}>{t("reload")}</Anchor>
</Stack>
</Center>
)
Expand Down Expand Up @@ -176,7 +178,7 @@ export function SharePage() {
)}
</CopyButton>
{canDownload() && items.length + queueItems.filter((i) => i.failed === false && i.finished === true ).length > 0 &&
<Button component="a" href={'/d/'+share.name} justify="center" variant="outline" size="xs"><IconDownload style={{ width: '70%', height: '70%' }} stroke={1.5}/>Download</Button>
<Button component="a" href={'/d/'+share.name} justify="center" variant="outline" size="xs"><IconDownload style={{ width: '70%', height: '70%' }} stroke={1.5}/>{t("download_button")}</Button>
}
</Group>
</Box>
Expand Down Expand Up @@ -237,7 +239,7 @@ export function SharePage() {
</Dropzone.Idle>
<div>
<Text size="xl" inline>
Drag files here or click to select files
{t("drag_area")}
</Text>
</div>
</Group>
Expand Down
12 changes: 8 additions & 4 deletions html/src/Pages/SharesPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ import { useMediaQuery } from "@mantine/hooks";

import classes from './SharesPage.module.css';

import { useTranslation } from "react-i18next";

export function SharesPage(props: {owner: string|null}) {
const { t } = useTranslation();

// Initialize props
const { owner } = props

Expand Down Expand Up @@ -89,7 +93,7 @@ export function SharesPage(props: {owner: string|null}) {
{/* Create share button */}
<Box ta="center" mt="xl" mb="xl">
<Group wrap="nowrap" gap={0} justify='center'>
<Button onClick={() => {createShare()}} className={classes.button}>Create Share </Button>
<Button onClick={() => {createShare()}} className={classes.button}>{t("create_share")}</Button>
<ResponsivePopover withDrawer={!isBrowser} >
<ActionIcon
variant="filled"
Expand All @@ -99,7 +103,7 @@ export function SharesPage(props: {owner: string|null}) {
>
<IconChevronDown style={{ width: rem(16), height: rem(16) }} stroke={1.5} />
</ActionIcon>
<ShareEditor buttonTitle="Create" onChange={updateShareProperties}
<ShareEditor buttonTitle={t("create")} onChange={updateShareProperties}
onClick={() => {createShare()}}
options={newShareOptions}
/>
Expand All @@ -114,7 +118,7 @@ export function SharesPage(props: {owner: string|null}) {
{/* Currently logged in user shares */}
{shares.some((s) => s.owner === owner) &&
<>
<Text size="xl" fw="700">Your Shares</Text>
<Text size="xl" fw="700">{t("your_shares")}</Text>
{shares.map((s) => (
s.owner === owner &&
<ShareComponent key={s.name} share={s} />
Expand All @@ -125,7 +129,7 @@ export function SharesPage(props: {owner: string|null}) {
{/* Other users shares */}
{shares.some((s) => s.owner !== owner) &&
<>
<Text mt="md" size="xl" fw="700">Other Shares</Text>
<Text mt="md" size="xl" fw="700">{t("other_shares")}</Text>
{shares.map((s) => (
s.owner === owner ||
<ShareComponent key={s.name} share={s} />
Expand Down
Loading

0 comments on commit 149e953

Please sign in to comment.