Email translation and snackbar translation (#10395)
This pull request focuses on improving localization by replacing hardcoded strings with translatable strings using the `Trans` component from `@lingui/react/macro`. Additionally, it introduces locale support to several email components. Here are the most important changes: ### Localization Improvements: * Replaced hardcoded strings with `Trans` components in various email templates to support localization. (`packages/twenty-emails/src/emails/clean-suspended-workspace.email.tsx`, `packages/twenty-emails/src/emails/password-reset-link.email.tsx`, `packages/twenty-emails/src/emails/password-update-notify.email.tsx`, `packages/twenty-emails/src/emails/send-email-verification-link.email.tsx`, `packages/twenty-emails/src/emails/send-invite-link.email.tsx`, `packages/twenty-emails/src/emails/warn-suspended-workspace.email.tsx`) [[1]](diffhunk://#diff-ca227a03c0aa66428daff938c743435e8a4dc3ffa960c0952f2697a23e280fdbR6-R25) [[2]](diffhunk://#diff-ca227a03c0aa66428daff938c743435e8a4dc3ffa960c0952f2697a23e280fdbL42-R45) [[3]](diffhunk://#diff-523cd37f5680ce418450946f62b7804b6586158efb190ced73920ef0fdf96bc8L1) [[4]](diffhunk://#diff-523cd37f5680ce418450946f62b7804b6586158efb190ced73920ef0fdf96bc8L23-R23) [[5]](diffhunk://#diff-cf16aa55d3eeb6be606bbe93de4c83b6f146c49b60d6f512d4b87e49fe14338cL29-R29) [[6]](diffhunk://#diff-cf16aa55d3eeb6be606bbe93de4c83b6f146c49b60d6f512d4b87e49fe14338cL46-R46) [[7]](diffhunk://#diff-16b613160f937563ec108176f595d8f275a1d87a5b8245d84df60d775f3efebeL1) [[8]](diffhunk://#diff-16b613160f937563ec108176f595d8f275a1d87a5b8245d84df60d775f3efebeL22-R22) [[9]](diffhunk://#diff-0da62e7cc5cfcb32cc25f067fa1d50123047c239af210398f065455ab6700886L1) [[10]](diffhunk://#diff-0da62e7cc5cfcb32cc25f067fa1d50123047c239af210398f065455ab6700886L42-R41) [[11]](diffhunk://#diff-0da62e7cc5cfcb32cc25f067fa1d50123047c239af210398f065455ab6700886L57-R56) [[12]](diffhunk://#diff-483346065c074946a43c18492334bd680422a1d4cb994dc8c3cd39d0208e6016L1-R21) [[13]](diffhunk://#diff-483346065c074946a43c18492334bd680422a1d4cb994dc8c3cd39d0208e6016L28-R31) [[14]](diffhunk://#diff-483346065c074946a43c18492334bd680422a1d4cb994dc8c3cd39d0208e6016L53-R55) ### Locale Support: * Added `locale` prop to email components to dynamically set the locale. (`packages/twenty-emails/src/emails/clean-suspended-workspace.email.tsx`, `packages/twenty-emails/src/emails/warn-suspended-workspace.email.tsx`) [[1]](diffhunk://#diff-ca227a03c0aa66428daff938c743435e8a4dc3ffa960c0952f2697a23e280fdbR6-R25) [[2]](diffhunk://#diff-483346065c074946a43c18492334bd680422a1d4cb994dc8c3cd39d0208e6016L1-R21) ### SnackBar Messages: * Replaced hardcoded SnackBar messages with translatable strings using the `t` function from `@lingui/react/macro`. (`packages/twenty-front/src/modules/auth/components/VerifyEmailEffect.tsx`, `packages/twenty-front/src/modules/auth/hooks/useVerifyLogin.ts`, `packages/twenty-front/src/modules/auth/sign-in-up/hooks/useHandleResendEmailVerificationToken.ts`, `packages/twenty-front/src/modules/auth/sign-in-up/hooks/useHandleResetPassword.ts`, `packages/twenty-front/src/modules/object-record/record-field/components/LightCopyIconButton.tsx`, `packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/PhonesFieldDisplay.tsx`) [[1]](diffhunk://#diff-551f2f94eacd8856d22bab7e63dd3ad693f87e9fa9b289864802ebc387f72b42R7) [[2]](diffhunk://#diff-551f2f94eacd8856d22bab7e63dd3ad693f87e9fa9b289864802ebc387f72b42L24-R29) [[3]](diffhunk://#diff-551f2f94eacd8856d22bab7e63dd3ad693f87e9fa9b289864802ebc387f72b42L43-R51) [[4]](diffhunk://#diff-428199461992a01325159f5fbf826d845f05f3361279eccd3f1ce416e0114845R7-R15) [[5]](diffhunk://#diff-428199461992a01325159f5fbf826d845f05f3361279eccd3f1ce416e0114845L24-R26) [[6]](diffhunk://#diff-cde42d6abfed63e52c2bda09d537a6577148d0baf957fde75ceaa8657ed58403R5) [[7]](diffhunk://#diff-cde42d6abfed63e52c2bda09d537a6577148d0baf957fde75ceaa8657ed58403L16-R17) [[8]](diffhunk://#diff-cde42d6abfed63e52c2bda09d537a6577148d0baf957fde75ceaa8657ed58403L28-R33) [[9]](diffhunk://#diff-9332c1988864863f12516c2fb77e814af60bedb37c36ffa094f49afc335d5457R5-R17) [[10]](diffhunk://#diff-9332c1988864863f12516c2fb77e814af60bedb37c36ffa094f49afc335d5457L27-R33) [[11]](diffhunk://#diff-9332c1988864863f12516c2fb77e814af60bedb37c36ffa094f49afc335d5457L42-R44) [[12]](diffhunk://#diff-8d64afa825b47ab71d18e3e284408e2097f5fd2365eae84d9d25d3568c48e49cR7) [[13]](diffhunk://#diff-8d64afa825b47ab71d18e3e284408e2097f5fd2365eae84d9d25d3568c48e49cR20-R28) [[14]](diffhunk://#diff-6e4361ded2b5656afaeb1befa8b1d23a45b490a1118550da290e27cdb8ebcdceR6) [[15]](diffhunk://#diff-6e4361ded2b5656afaeb1befa8b1d23a45b490a1118550da290e27cdb8ebcdceR19-R20) [[16]](diffhunk://#diff-6e4361ded2b5656afaeb1befa8b1d23a45b490a1118550da290e27cdb8ebcdceL29-R38)
This commit is contained in:
@ -15,6 +15,7 @@ import { TableCell } from '@/ui/layout/table/components/TableCell';
|
||||
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
|
||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||
import styled from '@emotion/styled';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { useState } from 'react';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
@ -49,9 +50,11 @@ export const SettingsAdminWorkspaceContent = ({
|
||||
const { updateFeatureFlagState } = useFeatureFlagState();
|
||||
const userLookupResult = useRecoilValue(userLookupResultState);
|
||||
|
||||
const { t } = useLingui();
|
||||
|
||||
const handleImpersonate = async (workspaceId: string) => {
|
||||
if (!userLookupResult?.user.id) {
|
||||
enqueueSnackBar('Please search for a user first', {
|
||||
enqueueSnackBar(t`Please search for a user first`, {
|
||||
variant: SnackBarVariant.Error,
|
||||
});
|
||||
return;
|
||||
|
||||
@ -5,7 +5,7 @@ import { Button, IconCopy } from 'twenty-ui';
|
||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { TextInput } from '@/ui/input/components/TextInput';
|
||||
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
const StyledContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
@ -20,6 +20,7 @@ type ApiKeyInputProps = { apiKey: string };
|
||||
|
||||
export const ApiKeyInput = ({ apiKey }: ApiKeyInputProps) => {
|
||||
const theme = useTheme();
|
||||
const { t } = useLingui();
|
||||
|
||||
const { enqueueSnackBar } = useSnackBar();
|
||||
return (
|
||||
@ -31,7 +32,7 @@ export const ApiKeyInput = ({ apiKey }: ApiKeyInputProps) => {
|
||||
Icon={IconCopy}
|
||||
title="Copy"
|
||||
onClick={() => {
|
||||
enqueueSnackBar('API Key copied to clipboard', {
|
||||
enqueueSnackBar(t`API Key copied to clipboard`, {
|
||||
variant: SnackBarVariant.Success,
|
||||
icon: <IconCopy size={theme.icon.size.md} />,
|
||||
duration: 2000,
|
||||
|
||||
@ -2,12 +2,13 @@ import { Meta, StoryObj } from '@storybook/react';
|
||||
import { ComponentDecorator } from 'twenty-ui';
|
||||
|
||||
import { ApiKeyInput } from '@/settings/developers/components/ApiKeyInput';
|
||||
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
|
||||
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
|
||||
|
||||
const meta: Meta<typeof ApiKeyInput> = {
|
||||
title: 'Modules/Settings/Developers/ApiKeys/ApiKeyInput',
|
||||
component: ApiKeyInput,
|
||||
decorators: [ComponentDecorator, SnackBarDecorator],
|
||||
decorators: [ComponentDecorator, SnackBarDecorator, I18nFrontDecorator],
|
||||
args: {
|
||||
apiKey:
|
||||
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ0d2VudHktN2VkOWQyMTItMWMyNS00ZDAyLWJmMjUtNmFlY2NmN2VhNDE5IiwiaWF0IjoxNjk4MTQyODgyLCJleHAiOjE2OTk0MDE1OTksImp0aSI6ImMyMmFiNjQxLTVhOGYtNGQwMC1iMDkzLTk3MzUwYTM2YzZkOSJ9.JIe2TX5IXrdNl3n-kRFp3jyfNUE7unzXZLAzm2Gxl98',
|
||||
|
||||
@ -5,6 +5,7 @@ import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { TextInput } from '@/ui/input/components/TextInput';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { Controller, useFormContext } from 'react-hook-form';
|
||||
import { Button, H2Title, IconCopy, Section } from 'twenty-ui';
|
||||
import { REACT_APP_SERVER_BASE_URL } from '~/config';
|
||||
@ -35,6 +36,7 @@ export const SettingsSSOOIDCForm = () => {
|
||||
const { control } = useFormContext();
|
||||
const { enqueueSnackBar } = useSnackBar();
|
||||
const theme = useTheme();
|
||||
const { t } = useLingui();
|
||||
|
||||
const authorizedUrl = window.location.origin;
|
||||
const redirectionUrl = `${REACT_APP_SERVER_BASE_URL}/auth/oidc/callback`;
|
||||
@ -43,8 +45,8 @@ export const SettingsSSOOIDCForm = () => {
|
||||
<>
|
||||
<Section>
|
||||
<H2Title
|
||||
title="Client Settings"
|
||||
description="Provide your OIDC provider details"
|
||||
title={t`Client Settings`}
|
||||
description={t`Provide your OIDC provider details`}
|
||||
/>
|
||||
<StyledInputsContainer>
|
||||
<StyledContainer>
|
||||
@ -59,7 +61,7 @@ export const SettingsSSOOIDCForm = () => {
|
||||
<StyledButtonCopy>
|
||||
<Button
|
||||
Icon={IconCopy}
|
||||
title="Copy"
|
||||
title={t`Copy`}
|
||||
onClick={() => {
|
||||
enqueueSnackBar('Authorized Url copied to clipboard', {
|
||||
variant: SnackBarVariant.Success,
|
||||
@ -75,7 +77,7 @@ export const SettingsSSOOIDCForm = () => {
|
||||
<StyledLinkContainer>
|
||||
<TextInput
|
||||
readOnly={true}
|
||||
label="Redirection URI"
|
||||
label={t`Redirection URI`}
|
||||
value={redirectionUrl}
|
||||
fullWidth
|
||||
/>
|
||||
@ -83,9 +85,9 @@ export const SettingsSSOOIDCForm = () => {
|
||||
<StyledButtonCopy>
|
||||
<Button
|
||||
Icon={IconCopy}
|
||||
title="Copy"
|
||||
title={t`Copy`}
|
||||
onClick={() => {
|
||||
enqueueSnackBar('Redirect Url copied to clipboard', {
|
||||
enqueueSnackBar(t`Redirect Url copied to clipboard`, {
|
||||
variant: SnackBarVariant.Success,
|
||||
icon: <IconCopy size={theme.icon.size.md} />,
|
||||
duration: 2000,
|
||||
@ -99,8 +101,8 @@ export const SettingsSSOOIDCForm = () => {
|
||||
</Section>
|
||||
<Section>
|
||||
<H2Title
|
||||
title="Identity Provider"
|
||||
description="Enter the credentials to set the connection"
|
||||
title={t`Identity Provider`}
|
||||
description={t`Enter the credentials to set the connection`}
|
||||
/>
|
||||
<StyledInputsContainer>
|
||||
<Controller
|
||||
|
||||
@ -6,6 +6,7 @@ import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { TextInput } from '@/ui/input/components/TextInput';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { ChangeEvent, useRef } from 'react';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
@ -57,6 +58,7 @@ export const SettingsSSOSAMLForm = () => {
|
||||
const { enqueueSnackBar } = useSnackBar();
|
||||
const theme = useTheme();
|
||||
const { setValue, getValues, watch, trigger } = useFormContext();
|
||||
const { t } = useLingui();
|
||||
|
||||
const handleFileChange = async (e: ChangeEvent<HTMLInputElement>) => {
|
||||
if (isDefined(e.target.files)) {
|
||||
@ -64,7 +66,7 @@ export const SettingsSSOSAMLForm = () => {
|
||||
const samlMetadataParsed = parseSAMLMetadataFromXMLFile(text);
|
||||
e.target.value = '';
|
||||
if (!samlMetadataParsed.success) {
|
||||
return enqueueSnackBar('Invalid File', {
|
||||
return enqueueSnackBar(t`Invalid File`, {
|
||||
variant: SnackBarVariant.Error,
|
||||
duration: 2000,
|
||||
});
|
||||
@ -100,7 +102,7 @@ export const SettingsSSOSAMLForm = () => {
|
||||
`${REACT_APP_SERVER_BASE_URL}/auth/saml/metadata/${getValues('id')}`,
|
||||
);
|
||||
if (!response.ok) {
|
||||
return enqueueSnackBar('Metadata file generation failed', {
|
||||
return enqueueSnackBar(t`Metadata file generation failed`, {
|
||||
variant: SnackBarVariant.Error,
|
||||
duration: 2000,
|
||||
});
|
||||
@ -120,8 +122,8 @@ export const SettingsSSOSAMLForm = () => {
|
||||
<>
|
||||
<Section>
|
||||
<H2Title
|
||||
title="Identity Provider Metadata XML"
|
||||
description="Upload the XML file with your connection infos"
|
||||
title={t`Identity Provider Metadata XML`}
|
||||
description={t`Upload the XML file with your connection infos`}
|
||||
/>
|
||||
<StyledUploadFileContainer>
|
||||
<StyledFileInput
|
||||
@ -133,7 +135,7 @@ export const SettingsSSOSAMLForm = () => {
|
||||
<Button
|
||||
Icon={IconUpload}
|
||||
onClick={handleUploadFileClick}
|
||||
title="Upload file"
|
||||
title={t`Upload file`}
|
||||
></Button>
|
||||
{isXMLMetadataValid() && (
|
||||
<IconCheck
|
||||
@ -146,15 +148,15 @@ export const SettingsSSOSAMLForm = () => {
|
||||
</Section>
|
||||
<Section>
|
||||
<H2Title
|
||||
title="Service Provider Details"
|
||||
description="Enter the infos to set the connection"
|
||||
title={t`Service Provider Details`}
|
||||
description={t`Enter the infos to set the connection`}
|
||||
/>
|
||||
<StyledInputsContainer>
|
||||
<StyledContainer>
|
||||
<Button
|
||||
Icon={IconDownload}
|
||||
onClick={downloadMetadata}
|
||||
title="Download file"
|
||||
title={t`Download file`}
|
||||
></Button>
|
||||
</StyledContainer>
|
||||
<HorizontalSeparator text={'Or'} />
|
||||
@ -194,9 +196,9 @@ export const SettingsSSOSAMLForm = () => {
|
||||
<StyledButtonCopy>
|
||||
<Button
|
||||
Icon={IconCopy}
|
||||
title="Copy"
|
||||
title={t`Copy`}
|
||||
onClick={() => {
|
||||
enqueueSnackBar('Entity ID copied to clipboard', {
|
||||
enqueueSnackBar(t`Entity ID copied to clipboard`, {
|
||||
variant: SnackBarVariant.Success,
|
||||
icon: <IconCopy size={theme.icon.size.md} />,
|
||||
duration: 2000,
|
||||
|
||||
@ -16,6 +16,7 @@ import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { UnwrapRecoilValue } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
import { SsoIdentityProviderStatus } from '~/generated/graphql';
|
||||
@ -36,6 +37,8 @@ export const SettingsSecuritySSORowDropdownMenu = ({
|
||||
const { deleteSSOIdentityProvider } = useDeleteSSOIdentityProvider();
|
||||
const { updateSSOIdentityProvider } = useUpdateSSOIdentityProvider();
|
||||
|
||||
const { t } = useLingui();
|
||||
|
||||
const handleDeleteSSOIdentityProvider = async (
|
||||
identityProviderId: string,
|
||||
) => {
|
||||
@ -43,7 +46,7 @@ export const SettingsSecuritySSORowDropdownMenu = ({
|
||||
identityProviderId,
|
||||
});
|
||||
if (isDefined(result.errors)) {
|
||||
enqueueSnackBar('Error deleting SSO Identity Provider', {
|
||||
enqueueSnackBar(t`Error deleting SSO Identity Provider`, {
|
||||
variant: SnackBarVariant.Error,
|
||||
duration: 2000,
|
||||
});
|
||||
@ -61,7 +64,7 @@ export const SettingsSecuritySSORowDropdownMenu = ({
|
||||
: SsoIdentityProviderStatus.Active,
|
||||
});
|
||||
if (isDefined(result.errors)) {
|
||||
enqueueSnackBar('Error editing SSO Identity Provider', {
|
||||
enqueueSnackBar(t`Error editing SSO Identity Provider`, {
|
||||
variant: SnackBarVariant.Error,
|
||||
duration: 2000,
|
||||
});
|
||||
@ -82,7 +85,7 @@ export const SettingsSecuritySSORowDropdownMenu = ({
|
||||
<MenuItem
|
||||
accent="default"
|
||||
LeftIcon={IconArchive}
|
||||
text={SSOIdp.status === 'Active' ? 'Deactivate' : 'Activate'}
|
||||
text={SSOIdp.status === 'Active' ? t`Deactivate` : t`Activate`}
|
||||
onClick={() => {
|
||||
toggleSSOIdentityProviderStatus(SSOIdp.id);
|
||||
closeDropdown();
|
||||
@ -91,7 +94,7 @@ export const SettingsSecuritySSORowDropdownMenu = ({
|
||||
<MenuItem
|
||||
accent="danger"
|
||||
LeftIcon={IconTrash}
|
||||
text="Delete"
|
||||
text={t`Delete`}
|
||||
onClick={() => {
|
||||
handleDeleteSSOIdentityProvider(SSOIdp.id);
|
||||
closeDropdown();
|
||||
|
||||
Reference in New Issue
Block a user