Skip to content

Commit

Permalink
feat: localize s3 buckets page
Browse files Browse the repository at this point in the history
  • Loading branch information
Dabolus committed Jan 7, 2024
1 parent d66f356 commit e03be7a
Show file tree
Hide file tree
Showing 7 changed files with 73 additions and 38 deletions.
6 changes: 2 additions & 4 deletions app/routes/s3._index/route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ import { redirect } from '@remix-run/node';
import { computeTitle } from '~/src/utils';
import type { MetaFunction } from '@remix-run/node';

export const meta: MetaFunction<typeof loader> = () => [computeTitle('S3')];
export const loader = async () => redirect(`/s3/buckets`);

export async function loader() {
return redirect(`/s3/buckets`);
}
export const meta: MetaFunction<typeof loader> = () => [computeTitle('S3')];
10 changes: 6 additions & 4 deletions app/routes/s3.buckets._index/CreateBucketsDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ChangeEvent, FunctionComponent, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Autocomplete, Button, Chip, TextField } from '@mui/material';
import { awsRegionsWithContinents } from '~/src/aws/common';
import ConfirmationDialog from '~/src/components/ConfirmationDialog';
Expand All @@ -11,14 +12,15 @@ export interface CreateBucketsDialogProps {
const CreateBucketsDialog: FunctionComponent<CreateBucketsDialogProps> = ({
open,
}) => {
const { t } = useTranslation();
const { withSearchParam } = useLinkUtils();
const [names, setNames] = useState<string[]>([]);
const [namesTextField, setNamesTextField] = useState<string>('');

return (
<ConfirmationDialog
open={open}
title="Create buckets"
title={t('createBuckets')}
content={
<>
<Autocomplete
Expand All @@ -39,7 +41,7 @@ const CreateBucketsDialog: FunctionComponent<CreateBucketsDialogProps> = ({
{...params}
required
autoFocus
label="Bucket names"
label={t('bucketNames')}
inputProps={{
...params.inputProps,
required: names.length < 1,
Expand Down Expand Up @@ -120,7 +122,7 @@ const CreateBucketsDialog: FunctionComponent<CreateBucketsDialogProps> = ({
return (
<>
<input name="region" type="hidden" value={optionValue} />
<TextField {...params} required label="Region" />
<TextField {...params} required label={t('region')} />
</>
);
}}
Expand All @@ -133,7 +135,7 @@ const CreateBucketsDialog: FunctionComponent<CreateBucketsDialogProps> = ({
action="/s3/buckets"
buttons={
<Button type="submit" variant="contained" color="secondary">
Create
{t('create')}
</Button>
}
/>
Expand Down
10 changes: 6 additions & 4 deletions app/routes/s3.buckets._index/DeleteBucketsDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Button } from '@mui/material';
import { FunctionComponent } from 'react';
import { useTranslation } from 'react-i18next';
import { Button } from '@mui/material';
import ConfirmationDialog from '~/src/components/ConfirmationDialog';
import useLinkUtils from '~/src/hooks/useLinkUtils';

Expand All @@ -12,21 +13,22 @@ const DeleteBucketsDialog: FunctionComponent<DeleteBucketsDialogProps> = ({
open,
buckets,
}) => {
const { t } = useTranslation();
const { withSearchParam } = useLinkUtils();

return (
<ConfirmationDialog
open={open}
title="Delete selected buckets?"
content="This action cannot be undone."
title={t('deleteBucketsConfirmationTitle')}
content={t('deleteBucketsConfirmationContent')}
closeLink={withSearchParam('delete', null)}
method="DELETE"
action="/s3/buckets"
buttons={
<>
<input type="hidden" name="names" value={buckets.join(',')} />
<Button type="submit" variant="contained" color="error" autoFocus>
Delete
{t('delete')}
</Button>
</>
}
Expand Down
8 changes: 5 additions & 3 deletions app/routes/s3.buckets._index/EmptyBucketsDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Button } from '@mui/material';
import { FunctionComponent } from 'react';
import { useTranslation } from 'react-i18next';
import ConfirmationDialog from '~/src/components/ConfirmationDialog';
import useLinkUtils from '~/src/hooks/useLinkUtils';

Expand All @@ -12,21 +13,22 @@ const EmptyBucketsDialog: FunctionComponent<EmptyBucketsDialogProps> = ({
open,
buckets,
}) => {
const { t } = useTranslation();
const { withSearchParam } = useLinkUtils();

return (
<ConfirmationDialog
open={open}
title="Empty selected buckets?"
content="This action cannot be undone."
title={t('emptyBucketsConfirmationTitle')}
content={t('emptyBucketsConfirmationContent')}
closeLink={withSearchParam('empty', null)}
method="PUT"
action="/s3/buckets"
buttons={
<>
<input type="hidden" name="names" value={buckets.join(',')} />
<Button type="submit" variant="contained" color="error" autoFocus>
Empty
{t('empty')}
</Button>
</>
}
Expand Down
51 changes: 28 additions & 23 deletions app/routes/s3.buckets._index/route.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { FunctionComponent, useEffect } from 'react';
import {
ListBucketsCommand,
ListBucketsCommandOutput,
} from '@aws-sdk/client-s3';
import { useTranslation } from 'react-i18next';
import { ListBucketsCommand } from '@aws-sdk/client-s3';
import { ActionFunctionArgs, json, redirect } from '@remix-run/node';
import {
useLoaderData,
Expand Down Expand Up @@ -38,16 +36,14 @@ import {
emptyBucketsAction,
deleteBucketsAction,
} from './actions';
import type { MetaFunction } from '@remix-run/node';

export const meta: MetaFunction<typeof loader> = () => [
computeTitle('S3', 'Buckets'),
];
import type { LoaderFunctionArgs, MetaFunction } from '@remix-run/node';
import { useServerTranslation } from '~/i18next.server';

export const loader = async () => {
export const loader = async ({ request }: LoaderFunctionArgs) => {
const s3Clients = getAwsClientsGroup('s3');
const responses = await Promise.all(
Array.from(s3Clients.entries(), ([url, s3Client]) =>
const [{ t }, ...responses] = await Promise.all([
useServerTranslation(request),
...Array.from(s3Clients.entries(), ([url, s3Client]) =>
s3Client.send(new ListBucketsCommand({})).then(
response =>
response.Buckets?.map(bucket => ({
Expand All @@ -56,10 +52,17 @@ export const loader = async () => {
})) ?? [],
),
),
);
return json({ buckets: responses.flat() });
]);
return json({
meta: { titleParts: [t('buckets')] },
buckets: responses.flat(),
});
};

export const meta: MetaFunction<typeof loader> = ({ data }) => [
computeTitle('S3', ...(data?.meta.titleParts || [])),
];

export const action = (args: ActionFunctionArgs) => {
switch (args.request.method) {
case 'POST':
Expand All @@ -79,6 +82,7 @@ const SearchField = styled(TextField)({
});

const BucketsList: FunctionComponent = () => {
const { t } = useTranslation();
const { buckets } = useLoaderData<typeof loader>();
const hasMultipleEndpoints =
new Set(buckets.map(bucket => bucket.EndpointUrl)).size > 1;
Expand All @@ -105,6 +109,7 @@ const BucketsList: FunctionComponent = () => {

return (
<>
{/* t('buckets') */}
<CurrentPath items={['s3', 'buckets']} />
<Stack p={2}>
<Stack
Expand All @@ -113,7 +118,7 @@ const BucketsList: FunctionComponent = () => {
alignItems="center"
>
<Typography variant="h5" component="h2" gutterBottom>
Buckets ({buckets.length})
{t('buckets')} ({buckets.length})
</Typography>
<Stack direction="row" gap={1}>
<Button onClick={revalidate}>
Expand All @@ -124,29 +129,29 @@ const BucketsList: FunctionComponent = () => {
to={withSearchParam('empty', '')}
disabled={selectedBuckets.length < 1}
>
Empty
{t('empty')}
</Button>
<Button
component={RemixLink}
to={withSearchParam('delete', '')}
disabled={selectedBuckets.length < 1}
>
Delete
{t('delete')}
</Button>
<Button
variant="contained"
color="secondary"
component={RemixLink}
to={withSearchParam('create', '')}
>
Create buckets
{t('createBuckets')}
</Button>
</Stack>
</Stack>
<div>
<SearchField
type="search"
label="Search buckets"
label={t('searchBuckets')}
variant="outlined"
value={search}
onChange={event =>
Expand Down Expand Up @@ -195,7 +200,7 @@ const BucketsList: FunctionComponent = () => {
columns={[
{
field: 'name',
headerName: 'Name',
headerName: t('name'),
renderCell: params => (
<Link
to={withSearchParam(
Expand All @@ -218,7 +223,7 @@ const BucketsList: FunctionComponent = () => {
},
{
field: 'creationDate',
headerName: 'Creation date',
headerName: t('creationDate'),
renderCell: params => (
<time dateTime={params.row.item.CreationDate}>
{formatDateTime(params.row.item.CreationDate)}
Expand All @@ -231,7 +236,7 @@ const BucketsList: FunctionComponent = () => {
? [
{
field: 'endpointUrl',
headerName: 'Endpoint',
headerName: t('endpoint'),
renderCell: params => (
<Link component={Typography}>
{params.row.item.EndpointUrl}
Expand All @@ -250,7 +255,7 @@ const BucketsList: FunctionComponent = () => {
slots={{ noRowsOverlay: TableOverlay }}
slotProps={{
noRowsOverlay: {
children: 'No buckets available.',
children: t('noBucketsAvailable'),
},
}}
/>
Expand Down
13 changes: 13 additions & 0 deletions public/locales/en/common.json
Original file line number Diff line number Diff line change
@@ -1,20 +1,33 @@
{
"binary": "Binary",
"bucketNames": "Bucket names",
"buckets": "Buckets",
"cancel": "Cancel",
"consoleHome": "Console Home",
"copyright": "Brought to you with <strong>❤</strong> by <url>Dabolus</url>.",
"create": "Create",
"createBuckets": "Create buckets",
"createTable": "Create table",
"creationDate": "Creation date",
"delete": "Delete",
"deleteBucketsConfirmationContent": "This action cannot be undone.",
"deleteBucketsConfirmationTitle": "Delete selected buckets?",
"deleteTablesConfirmationContent": "This action cannot be undone.",
"deleteTablesConfirmationTitle": "Delete selected tables?",
"dynamodbDescription": "Fast and flexible NoSQL database service",
"empty": "Empty",
"emptyBucketsConfirmationContent": "This action cannot be undone.",
"emptyBucketsConfirmationTitle": "Empty selected buckets?",
"endpoint": "Endpoint",
"homeDescription": "A simple UI to interact with real or emulated AWS services (LocalStack, Minio, etc).",
"name": "Name",
"noBucketsAvailable": "No buckets available.",
"noTablesAvailable": "No tables available.",
"number": "Number",
"partitionKey": "Partition key",
"region": "Region",
"s3Description": "Scalable object storage for any type of data",
"searchBuckets": "Search buckets",
"searchTables": "Search tables",
"sortKey": "Sort key",
"sqsDescription": "Managed message queues for microservices & serverless applications",
Expand Down
13 changes: 13 additions & 0 deletions public/locales/it/common.json
Original file line number Diff line number Diff line change
@@ -1,20 +1,33 @@
{
"binary": "Binario",
"bucketNames": "Nomi bucket",
"buckets": "Bucket",
"cancel": "Annulla",
"consoleHome": "Pagina principale della console",
"copyright": "Sviluppato con <strong>❤</strong> da <url>Dabolus</url>.",
"create": "Crea",
"createBuckets": "Crea bucket",
"createTable": "Crea tabella",
"creationDate": "Data creazione",
"delete": "Elimina",
"deleteBucketsConfirmationContent": "Questa operazione è irreversibile.",
"deleteBucketsConfirmationTitle": "Eliminare i bucket selezionati?",
"deleteTablesConfirmationContent": "Questa operazione è irreversibile.",
"deleteTablesConfirmationTitle": "Eliminare le tabelle selezionate?",
"dynamodbDescription": "Servizio di database NoSQL veloce e flessibile",
"empty": "Svuota",
"emptyBucketsConfirmationContent": "Questa operazione è irreversibile.",
"emptyBucketsConfirmationTitle": "Svuotare i bucket selezionati?",
"endpoint": "Endpoint",
"homeDescription": "Una semplice interfaccia utente per interagire con servizi AWS reali o emulati (LocalStack, Minio, ecc.)",
"name": "Nome",
"noBucketsAvailable": "Nessun bucket disponibile.",
"noTablesAvailable": "Nessuna tabella disponibile.",
"number": "Numero",
"partitionKey": "Chiave di partizione",
"region": "Regione",
"s3Description": "Archiviazione scalabile di oggetti per qualsiasi tipo di dati",
"searchBuckets": "Cerca bucket",
"searchTables": "Cerca tabelle",
"sortKey": "Chiave di ordinamento",
"sqsDescription": "Code di messaggi gestite per microservizi e applicazioni serverless",
Expand Down

0 comments on commit e03be7a

Please sign in to comment.