Set up localization with feature flag control (#9649)
Refers #8128 Changes Introduced: - Added i18n configuration. - Added a feature flag for localization. - Enabled language switching based on the flag. --------- Co-authored-by: Félix Malfait <felix@twenty.com>
This commit is contained in:
committed by
GitHub
parent
b81ffcc77c
commit
f44b31573a
@ -2,7 +2,7 @@
|
||||
"jsc": {
|
||||
"target": "es2017",
|
||||
"parser": {
|
||||
"syntax": "typescript",
|
||||
"syntax": "typescript",
|
||||
"tsx": true,
|
||||
"decorators": false,
|
||||
"dynamicImport": false
|
||||
@ -19,6 +19,19 @@
|
||||
"hidden": {
|
||||
"jest": true
|
||||
}
|
||||
},
|
||||
"experimental": {
|
||||
"plugins": [
|
||||
[
|
||||
"@lingui/swc-plugin",
|
||||
{
|
||||
"runtimeModules": {
|
||||
"i18n": ["@lingui/core", "i18n"],
|
||||
"trans": ["@lingui/react", "Trans"]
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"module": {
|
||||
|
||||
@ -12,7 +12,16 @@ const jestConfig: JestConfigWithTsJest = {
|
||||
testEnvironment: 'jsdom',
|
||||
transformIgnorePatterns: ['../../node_modules/'],
|
||||
transform: {
|
||||
'^.+\\.(ts|js|tsx|jsx)$': '@swc/jest',
|
||||
'^.+\\.(ts|js|tsx|jsx)$': [
|
||||
'@swc/jest',
|
||||
{
|
||||
jsc: {
|
||||
experimental: {
|
||||
plugins: [], // Disable Lingui plugin during tests
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
moduleNameMapper: {
|
||||
'\\.(jpg|jpeg|png|gif|webp|svg|svg\\?react)$':
|
||||
|
||||
12
packages/twenty-front/lingui.config.ts
Normal file
12
packages/twenty-front/lingui.config.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { defineConfig } from '@lingui/cli';
|
||||
|
||||
export default defineConfig({
|
||||
sourceLocale: 'en',
|
||||
locales: ['fr', 'en', 'pt', 'de', 'it', 'es', 'zh'],
|
||||
catalogs: [
|
||||
{
|
||||
path: '<rootDir>/src/locales/{locale}/messages',
|
||||
include: ['src'],
|
||||
},
|
||||
],
|
||||
});
|
||||
@ -153,6 +153,20 @@
|
||||
"configurations": {
|
||||
"ci": {}
|
||||
}
|
||||
},
|
||||
"lingui:extract": {
|
||||
"executor": "nx:run-commands",
|
||||
"options": {
|
||||
"cwd": "{projectRoot}",
|
||||
"command": "lingui extract"
|
||||
}
|
||||
},
|
||||
"lingui:compile": {
|
||||
"executor": "nx:run-commands",
|
||||
"options": {
|
||||
"cwd": "{projectRoot}",
|
||||
"command": "lingui compile --typescript"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -330,7 +330,8 @@ export enum FeatureFlagKey {
|
||||
IsPostgreSqlIntegrationEnabled = 'IsPostgreSQLIntegrationEnabled',
|
||||
IsStripeIntegrationEnabled = 'IsStripeIntegrationEnabled',
|
||||
IsUniqueIndexesEnabled = 'IsUniqueIndexesEnabled',
|
||||
IsWorkflowEnabled = 'IsWorkflowEnabled'
|
||||
IsWorkflowEnabled = 'IsWorkflowEnabled',
|
||||
IsLocalizationEnabled = 'IsLocalizationEnabled'
|
||||
}
|
||||
|
||||
export type FieldConnection = {
|
||||
|
||||
1
packages/twenty-front/src/locales/de/messages.ts
Normal file
1
packages/twenty-front/src/locales/de/messages.ts
Normal file
@ -0,0 +1 @@
|
||||
/*eslint-disable*/import type{Messages}from"@lingui/core";export const messages=JSON.parse("{\"vXIe7J\":[\"Language\"],\"AXTJAW\":[\"Select your preferred language\"]}")as Messages;
|
||||
16
packages/twenty-front/src/locales/en/messages.po
Normal file
16
packages/twenty-front/src/locales/en/messages.po
Normal file
@ -0,0 +1,16 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"POT-Creation-Date: 2025-01-16 16:50+0100\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Generator: @lingui/cli\n"
|
||||
"Language: en\n"
|
||||
|
||||
#: src/pages/settings/profile/appearance/components/SettingsExperience.tsx:51
|
||||
msgid "Language"
|
||||
msgstr "Language"
|
||||
|
||||
#: src/pages/settings/profile/appearance/components/SettingsExperience.tsx:52
|
||||
msgid "Select your preferred language"
|
||||
msgstr "Select your preferred language"
|
||||
1
packages/twenty-front/src/locales/en/messages.ts
Normal file
1
packages/twenty-front/src/locales/en/messages.ts
Normal file
@ -0,0 +1 @@
|
||||
/*eslint-disable*/import type{Messages}from"@lingui/core";export const messages=JSON.parse("{\"vXIe7J\":[\"Language\"],\"AXTJAW\":[\"Select your preferred language\"]}")as Messages;
|
||||
1
packages/twenty-front/src/locales/es/messages.ts
Normal file
1
packages/twenty-front/src/locales/es/messages.ts
Normal file
@ -0,0 +1 @@
|
||||
/*eslint-disable*/import type{Messages}from"@lingui/core";export const messages=JSON.parse("{\"vXIe7J\":[\"Language\"],\"AXTJAW\":[\"Select your preferred language\"]}")as Messages;
|
||||
16
packages/twenty-front/src/locales/fr/messages.po
Normal file
16
packages/twenty-front/src/locales/fr/messages.po
Normal file
@ -0,0 +1,16 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"POT-Creation-Date: 2025-01-16 16:50+0100\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Generator: @lingui/cli\n"
|
||||
"Language: fr\n"
|
||||
|
||||
#: src/pages/settings/profile/appearance/components/SettingsExperience.tsx:51
|
||||
msgid "Language"
|
||||
msgstr "Langue"
|
||||
|
||||
#: src/pages/settings/profile/appearance/components/SettingsExperience.tsx:52
|
||||
msgid "Select your preferred language"
|
||||
msgstr "Sélectionnez votre langue préférée"
|
||||
1
packages/twenty-front/src/locales/fr/messages.ts
Normal file
1
packages/twenty-front/src/locales/fr/messages.ts
Normal file
@ -0,0 +1 @@
|
||||
/*eslint-disable*/import type{Messages}from"@lingui/core";export const messages=JSON.parse("{\"vXIe7J\":[\"Langue\"],\"AXTJAW\":[\"Sélectionnez votre langue préférée\"]}")as Messages;
|
||||
1
packages/twenty-front/src/locales/it/messages.ts
Normal file
1
packages/twenty-front/src/locales/it/messages.ts
Normal file
@ -0,0 +1 @@
|
||||
/*eslint-disable*/import type{Messages}from"@lingui/core";export const messages=JSON.parse("{\"vXIe7J\":[\"Language\"],\"AXTJAW\":[\"Select your preferred language\"]}")as Messages;
|
||||
1
packages/twenty-front/src/locales/pt/messages.ts
Normal file
1
packages/twenty-front/src/locales/pt/messages.ts
Normal file
@ -0,0 +1 @@
|
||||
/*eslint-disable*/import type{Messages}from"@lingui/core";export const messages=JSON.parse("{\"vXIe7J\":[\"Language\"],\"AXTJAW\":[\"Select your preferred language\"]}")as Messages;
|
||||
1
packages/twenty-front/src/locales/zh/messages.ts
Normal file
1
packages/twenty-front/src/locales/zh/messages.ts
Normal file
@ -0,0 +1 @@
|
||||
/*eslint-disable*/import type{Messages}from"@lingui/core";export const messages=JSON.parse("{\"vXIe7J\":[\"Language\"],\"AXTJAW\":[\"Select your preferred language\"]}")as Messages;
|
||||
@ -5,29 +5,41 @@ import { RecoilDebugObserverEffect } from '@/debug/components/RecoilDebugObserve
|
||||
import { AppErrorBoundary } from '@/error-handler/components/AppErrorBoundary';
|
||||
import { ExceptionHandlerProvider } from '@/error-handler/components/ExceptionHandlerProvider';
|
||||
import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope';
|
||||
import { i18n } from '@lingui/core';
|
||||
import { I18nProvider } from '@lingui/react';
|
||||
import { HelmetProvider } from 'react-helmet-async';
|
||||
import { RecoilRoot } from 'recoil';
|
||||
import { RecoilURLSyncJSON } from 'recoil-sync';
|
||||
import { IconsProvider } from 'twenty-ui';
|
||||
import { messages as enMessages } from '../../../locales/en/messages';
|
||||
import { messages as frMessages } from '../../../locales/fr/messages';
|
||||
|
||||
i18n.load({
|
||||
en: enMessages,
|
||||
fr: frMessages,
|
||||
});
|
||||
i18n.activate('fr');
|
||||
|
||||
export const App = () => {
|
||||
return (
|
||||
<RecoilRoot>
|
||||
<RecoilURLSyncJSON location={{ part: 'queryParams' }}>
|
||||
<AppErrorBoundary>
|
||||
<CaptchaProvider>
|
||||
<RecoilDebugObserverEffect />
|
||||
<ApolloDevLogEffect />
|
||||
<SnackBarProviderScope snackBarManagerScopeId="snack-bar-manager">
|
||||
<IconsProvider>
|
||||
<ExceptionHandlerProvider>
|
||||
<HelmetProvider>
|
||||
<AppRouter />
|
||||
</HelmetProvider>
|
||||
</ExceptionHandlerProvider>
|
||||
</IconsProvider>
|
||||
</SnackBarProviderScope>
|
||||
</CaptchaProvider>
|
||||
<I18nProvider i18n={i18n}>
|
||||
<CaptchaProvider>
|
||||
<RecoilDebugObserverEffect />
|
||||
<ApolloDevLogEffect />
|
||||
<SnackBarProviderScope snackBarManagerScopeId="snack-bar-manager">
|
||||
<IconsProvider>
|
||||
<ExceptionHandlerProvider>
|
||||
<HelmetProvider>
|
||||
<AppRouter />
|
||||
</HelmetProvider>
|
||||
</ExceptionHandlerProvider>
|
||||
</IconsProvider>
|
||||
</SnackBarProviderScope>
|
||||
</CaptchaProvider>
|
||||
</I18nProvider>
|
||||
</AppErrorBoundary>
|
||||
</RecoilURLSyncJSON>
|
||||
</RecoilRoot>
|
||||
|
||||
@ -123,11 +123,11 @@ const SettingsProfile = lazy(() =>
|
||||
})),
|
||||
);
|
||||
|
||||
const SettingsAppearance = lazy(() =>
|
||||
const SettingsExperience = lazy(() =>
|
||||
import(
|
||||
'~/pages/settings/profile/appearance/components/SettingsAppearance'
|
||||
'~/pages/settings/profile/appearance/components/SettingsExperience'
|
||||
).then((module) => ({
|
||||
default: module.SettingsAppearance,
|
||||
default: module.SettingsExperience,
|
||||
})),
|
||||
);
|
||||
|
||||
@ -278,7 +278,7 @@ export const SettingsRoutes = ({
|
||||
<Suspense fallback={<SettingsSkeletonLoader />}>
|
||||
<Routes>
|
||||
<Route path={SettingsPath.ProfilePage} element={<SettingsProfile />} />
|
||||
<Route path={SettingsPath.Appearance} element={<SettingsAppearance />} />
|
||||
<Route path={SettingsPath.Experience} element={<SettingsExperience />} />
|
||||
<Route path={SettingsPath.Accounts} element={<SettingsAccounts />} />
|
||||
<Route path={SettingsPath.NewAccount} element={<SettingsNewAccount />} />
|
||||
<Route
|
||||
|
||||
@ -60,6 +60,7 @@ import { useRedirectToWorkspaceDomain } from '@/domain-manager/hooks/useRedirect
|
||||
import { domainConfigurationState } from '@/domain-manager/states/domainConfigurationState';
|
||||
import { isAppWaitingForFreshObjectMetadataState } from '@/object-metadata/states/isAppWaitingForFreshObjectMetadataState';
|
||||
import { workspaceAuthProvidersState } from '@/workspace/states/workspaceAuthProvidersState';
|
||||
import { i18n } from '@lingui/core';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
|
||||
export const useAuth = () => {
|
||||
@ -279,6 +280,7 @@ export const useAuth = () => {
|
||||
)
|
||||
: TimeFormat[detectTimeFormat()],
|
||||
});
|
||||
i18n.activate(workspaceMember.locale ?? 'en');
|
||||
}
|
||||
|
||||
const workspace = user.currentWorkspace ?? null;
|
||||
|
||||
@ -76,6 +76,6 @@ export const Main: Story = {};
|
||||
export const Settings: Story = {
|
||||
args: {
|
||||
mobileNavigationDrawer: 'settings',
|
||||
routePath: '/settings/appearance',
|
||||
routePath: '/settings/experience',
|
||||
},
|
||||
};
|
||||
|
||||
@ -16,8 +16,8 @@ import {
|
||||
IconPrinter,
|
||||
IconSettings,
|
||||
} from 'twenty-ui';
|
||||
import { FeatureFlag, FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
import { FeatureFlagKey } from '~/generated/graphql';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
import { FeatureFlag, FeatureFlagKey } from '~/generated/graphql';
|
||||
|
||||
export const useRecordShowContainerTabs = (
|
||||
loading: boolean,
|
||||
|
||||
@ -106,7 +106,7 @@ export const SettingsNavigationDrawerItems = () => {
|
||||
/>
|
||||
<SettingsNavigationDrawerItem
|
||||
label="Experience"
|
||||
path={SettingsPath.Appearance}
|
||||
path={SettingsPath.Experience}
|
||||
Icon={IconColorSwatch}
|
||||
/>
|
||||
<NavigationDrawerItemGroup>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
export enum SettingsPath {
|
||||
ProfilePage = 'profile',
|
||||
Appearance = 'appearance',
|
||||
Experience = 'experience',
|
||||
Accounts = 'accounts',
|
||||
NewAccount = 'accounts/new',
|
||||
AccountsCalendars = 'accounts/calendars',
|
||||
|
||||
@ -142,7 +142,7 @@ export const Settings: Story = {
|
||||
/>
|
||||
<NavigationDrawerItem
|
||||
label="Appearance"
|
||||
to={getSettingsPagePath(SettingsPath.Appearance)}
|
||||
to={getSettingsPagePath(SettingsPath.Experience)}
|
||||
Icon={IconColorSwatch}
|
||||
/>
|
||||
<NavigationDrawerItemGroup>
|
||||
|
||||
@ -16,6 +16,7 @@ import { detectTimeZone } from '@/localization/utils/detectTimeZone';
|
||||
import { getDateFormatFromWorkspaceDateFormat } from '@/localization/utils/getDateFormatFromWorkspaceDateFormat';
|
||||
import { getTimeFormatFromWorkspaceTimeFormat } from '@/localization/utils/getTimeFormatFromWorkspaceTimeFormat';
|
||||
import { ColorScheme } from '@/workspace-member/types/WorkspaceMember';
|
||||
import { i18n } from '@lingui/core';
|
||||
import { WorkspaceMember } from '~/generated-metadata/graphql';
|
||||
import { useGetCurrentUserQuery } from '~/generated/graphql';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
@ -91,6 +92,8 @@ export const UserProviderEffect = () => {
|
||||
? getTimeFormatFromWorkspaceTimeFormat(workspaceMember.timeFormat)
|
||||
: TimeFormat[detectTimeFormat()],
|
||||
});
|
||||
|
||||
i18n.activate(workspaceMember.locale ?? 'en');
|
||||
}
|
||||
|
||||
if (isDefined(workspaceMembers)) {
|
||||
|
||||
@ -7,15 +7,15 @@ import {
|
||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||
|
||||
import { userEvent, within } from '@storybook/test';
|
||||
import { SettingsAppearance } from '../profile/appearance/components/SettingsAppearance';
|
||||
import { SettingsExperience } from '../profile/appearance/components/SettingsExperience';
|
||||
|
||||
Date.now = () => new Date('2022-06-13T12:33:37.000Z').getTime();
|
||||
|
||||
const meta: Meta<PageDecoratorArgs> = {
|
||||
title: 'Pages/Settings/SettingsAppearance',
|
||||
component: SettingsAppearance,
|
||||
title: 'Pages/Settings/SettingsExperience',
|
||||
component: SettingsExperience,
|
||||
decorators: [PageDecorator],
|
||||
args: { routePath: '/settings/appearance' },
|
||||
args: { routePath: '/settings/experience' },
|
||||
parameters: {
|
||||
msw: graphqlMocks,
|
||||
date: new Date(2021, 1, 1),
|
||||
@ -24,13 +24,13 @@ const meta: Meta<PageDecoratorArgs> = {
|
||||
|
||||
export default meta;
|
||||
|
||||
export type Story = StoryObj<typeof SettingsAppearance>;
|
||||
export type Story = StoryObj<typeof SettingsExperience>;
|
||||
|
||||
export const Default: Story = {
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
await canvas.findByText('Appearance', undefined, {
|
||||
await canvas.findAllByText('Experience', undefined, {
|
||||
timeout: 3000,
|
||||
});
|
||||
|
||||
@ -0,0 +1,96 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
||||
import { Select } from '@/ui/input/components/Select';
|
||||
|
||||
import { i18n } from '@lingui/core';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
import { logError } from '~/utils/logError';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: ${({ theme }) => theme.spacing(4)};
|
||||
`;
|
||||
|
||||
export const LocalePicker = () => {
|
||||
const [currentWorkspaceMember, setCurrentWorkspaceMember] = useRecoilState(
|
||||
currentWorkspaceMemberState,
|
||||
);
|
||||
|
||||
const { updateOneRecord } = useUpdateOneRecord({
|
||||
objectNameSingular: CoreObjectNameSingular.WorkspaceMember,
|
||||
});
|
||||
|
||||
const updateWorkspaceMember = async (changedFields: any) => {
|
||||
if (!currentWorkspaceMember?.id) {
|
||||
throw new Error('User is not logged in');
|
||||
}
|
||||
|
||||
try {
|
||||
await updateOneRecord({
|
||||
idToUpdate: currentWorkspaceMember.id,
|
||||
updateOneRecordInput: changedFields,
|
||||
});
|
||||
} catch (error) {
|
||||
logError(error);
|
||||
}
|
||||
};
|
||||
|
||||
if (!isDefined(currentWorkspaceMember)) return;
|
||||
|
||||
const handleLocaleChange = (value: string) => {
|
||||
setCurrentWorkspaceMember({
|
||||
...currentWorkspaceMember,
|
||||
...{ locale: value },
|
||||
});
|
||||
updateWorkspaceMember({ locale: value });
|
||||
|
||||
i18n.activate(value);
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledContainer>
|
||||
<Select
|
||||
dropdownId="preferred-locale"
|
||||
dropdownWidthAuto
|
||||
fullWidth
|
||||
value={i18n.locale}
|
||||
options={[
|
||||
{
|
||||
label: 'Portuguese',
|
||||
value: 'pt',
|
||||
},
|
||||
{
|
||||
label: 'French',
|
||||
value: 'fr',
|
||||
},
|
||||
{
|
||||
label: 'German',
|
||||
value: 'de',
|
||||
},
|
||||
{
|
||||
label: 'Italian',
|
||||
value: 'it',
|
||||
},
|
||||
{
|
||||
label: 'Spanish',
|
||||
value: 'es',
|
||||
},
|
||||
{
|
||||
label: 'English',
|
||||
value: 'en',
|
||||
},
|
||||
{
|
||||
label: 'Chinese',
|
||||
value: 'zh',
|
||||
},
|
||||
]}
|
||||
onChange={(value) => handleLocaleChange(value)}
|
||||
/>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
@ -5,11 +5,22 @@ import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
|
||||
import { useColorScheme } from '@/ui/theme/hooks/useColorScheme';
|
||||
import { DateTimeSettings } from '~/pages/settings/profile/appearance/components/DateTimeSettings';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
|
||||
export const SettingsAppearance = () => {
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { FeatureFlagKey } from '~/generated/graphql';
|
||||
import { DateTimeSettings } from '~/pages/settings/profile/appearance/components/DateTimeSettings';
|
||||
import { LocalePicker } from '~/pages/settings/profile/appearance/components/LocalePicker';
|
||||
|
||||
export const SettingsExperience = () => {
|
||||
const { colorScheme, setColorScheme } = useColorScheme();
|
||||
|
||||
const isLocalizationEnabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsLocalizationEnabled,
|
||||
);
|
||||
|
||||
const { t } = useLingui();
|
||||
|
||||
return (
|
||||
<SubMenuTopBarContainer
|
||||
title="Experience"
|
||||
@ -33,6 +44,16 @@ export const SettingsAppearance = () => {
|
||||
/>
|
||||
<DateTimeSettings />
|
||||
</Section>
|
||||
|
||||
{isLocalizationEnabled && (
|
||||
<Section>
|
||||
<H2Title
|
||||
title={t`Language`}
|
||||
description={t`Select your preferred language`}
|
||||
/>
|
||||
<LocalePicker />
|
||||
</Section>
|
||||
)}
|
||||
</SettingsPageContainer>
|
||||
</SubMenuTopBarContainer>
|
||||
);
|
||||
@ -24,7 +24,10 @@ import { RecoilDebugObserverEffect } from '@/debug/components/RecoilDebugObserve
|
||||
import { ObjectMetadataItemsProvider } from '@/object-metadata/components/ObjectMetadataItemsProvider';
|
||||
import { PrefetchDataProvider } from '@/prefetch/components/PrefetchDataProvider';
|
||||
import { WorkspaceProviderEffect } from '@/workspace/components/WorkspaceProviderEffect';
|
||||
import { i18n } from '@lingui/core';
|
||||
import { I18nProvider } from '@lingui/react';
|
||||
import { IconsProvider } from 'twenty-ui';
|
||||
import { messages as enMessages } from '../../locales/en/messages';
|
||||
import { FullHeightStorybookLayout } from '../FullHeightStorybookLayout';
|
||||
|
||||
export type PageDecoratorArgs = {
|
||||
@ -63,35 +66,42 @@ const ApolloStorybookDevLogEffect = () => {
|
||||
return <></>;
|
||||
};
|
||||
|
||||
i18n.load({
|
||||
en: enMessages,
|
||||
});
|
||||
i18n.activate('en');
|
||||
|
||||
const Providers = () => {
|
||||
return (
|
||||
<RecoilRoot>
|
||||
<SnackBarProviderScope snackBarManagerScopeId="snack-bar-manager">
|
||||
<RecoilDebugObserverEffect />
|
||||
<ApolloProvider client={mockedApolloClient}>
|
||||
<ApolloStorybookDevLogEffect />
|
||||
<ClientConfigProviderEffect />
|
||||
<ClientConfigProvider>
|
||||
<UserProviderEffect />
|
||||
<WorkspaceProviderEffect />
|
||||
<UserProvider>
|
||||
<ApolloMetadataClientMockedProvider>
|
||||
<ObjectMetadataItemsProvider>
|
||||
<FullHeightStorybookLayout>
|
||||
<HelmetProvider>
|
||||
<SnackBarProviderScope snackBarManagerScopeId="snack-bar-manager">
|
||||
<IconsProvider>
|
||||
<PrefetchDataProvider>
|
||||
<Outlet />
|
||||
</PrefetchDataProvider>
|
||||
</IconsProvider>
|
||||
</SnackBarProviderScope>
|
||||
</HelmetProvider>
|
||||
</FullHeightStorybookLayout>
|
||||
</ObjectMetadataItemsProvider>
|
||||
</ApolloMetadataClientMockedProvider>
|
||||
</UserProvider>
|
||||
</ClientConfigProvider>
|
||||
<I18nProvider i18n={i18n}>
|
||||
<ApolloStorybookDevLogEffect />
|
||||
<ClientConfigProviderEffect />
|
||||
<ClientConfigProvider>
|
||||
<UserProviderEffect />
|
||||
<WorkspaceProviderEffect />
|
||||
<UserProvider>
|
||||
<ApolloMetadataClientMockedProvider>
|
||||
<ObjectMetadataItemsProvider>
|
||||
<FullHeightStorybookLayout>
|
||||
<HelmetProvider>
|
||||
<SnackBarProviderScope snackBarManagerScopeId="snack-bar-manager">
|
||||
<IconsProvider>
|
||||
<PrefetchDataProvider>
|
||||
<Outlet />
|
||||
</PrefetchDataProvider>
|
||||
</IconsProvider>
|
||||
</SnackBarProviderScope>
|
||||
</HelmetProvider>
|
||||
</FullHeightStorybookLayout>
|
||||
</ObjectMetadataItemsProvider>
|
||||
</ApolloMetadataClientMockedProvider>
|
||||
</UserProvider>
|
||||
</ClientConfigProvider>
|
||||
</I18nProvider>
|
||||
</ApolloProvider>
|
||||
</SnackBarProviderScope>
|
||||
</RecoilRoot>
|
||||
|
||||
@ -15,8 +15,8 @@ describe('title-utils', () => {
|
||||
expect(getPageTitleFromPath('/settings/profile')).toBe(
|
||||
SettingsPageTitles.Profile,
|
||||
);
|
||||
expect(getPageTitleFromPath('/settings/appearance')).toBe(
|
||||
SettingsPageTitles.Appearance,
|
||||
expect(getPageTitleFromPath('/settings/experience')).toBe(
|
||||
SettingsPageTitles.Experience,
|
||||
);
|
||||
expect(getPageTitleFromPath('/settings/accounts')).toBe(
|
||||
SettingsPageTitles.Accounts,
|
||||
|
||||
@ -4,7 +4,7 @@ import { SettingsPath } from '@/types/SettingsPath';
|
||||
|
||||
export enum SettingsPageTitles {
|
||||
Accounts = 'Account - Settings',
|
||||
Appearance = 'Appearance - Settings',
|
||||
Experience = 'Experience - Settings',
|
||||
Profile = 'Profile - Settings',
|
||||
Objects = 'Data model - Settings',
|
||||
Members = 'Members - Settings',
|
||||
@ -17,7 +17,7 @@ export enum SettingsPageTitles {
|
||||
|
||||
enum SettingsPathPrefixes {
|
||||
Accounts = `${AppBasePath.Settings}/${SettingsPath.Accounts}`,
|
||||
Appearance = `${AppBasePath.Settings}/${SettingsPath.Appearance}`,
|
||||
Experience = `${AppBasePath.Settings}/${SettingsPath.Experience}`,
|
||||
Profile = `${AppBasePath.Settings}/${SettingsPath.ProfilePage}`,
|
||||
Objects = `${AppBasePath.Settings}/${SettingsPath.Objects}`,
|
||||
Members = `${AppBasePath.Settings}/${SettingsPath.WorkspaceMembersPage}`,
|
||||
@ -49,8 +49,8 @@ export const getPageTitleFromPath = (pathname: string): string => {
|
||||
return 'Create Workspace';
|
||||
case AppPath.CreateProfile:
|
||||
return 'Create Profile';
|
||||
case SettingsPathPrefixes.Appearance:
|
||||
return SettingsPageTitles.Appearance;
|
||||
case SettingsPathPrefixes.Experience:
|
||||
return SettingsPageTitles.Experience;
|
||||
case SettingsPathPrefixes.Accounts:
|
||||
return SettingsPageTitles.Accounts;
|
||||
case SettingsPathPrefixes.Profile:
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
/* eslint-disable no-console */
|
||||
import { lingui } from '@lingui/vite-plugin';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import react from '@vitejs/plugin-react-swc';
|
||||
import wyw from '@wyw-in-js/vite';
|
||||
@ -93,11 +94,17 @@ export default defineConfig(({ command, mode }) => {
|
||||
},
|
||||
|
||||
plugins: [
|
||||
react({ jsxImportSource: '@emotion/react' }),
|
||||
react({
|
||||
jsxImportSource: '@emotion/react',
|
||||
plugins: [['@lingui/swc-plugin', {}]],
|
||||
}),
|
||||
tsconfigPaths({
|
||||
projects: ['tsconfig.json', '../twenty-ui/tsconfig.json'],
|
||||
}),
|
||||
svgr(),
|
||||
lingui({
|
||||
configPath: path.resolve(__dirname, './lingui.config.ts'),
|
||||
}),
|
||||
checker(checkers),
|
||||
// TODO: fix this, we have to restrict the include to only the components that are using linaria
|
||||
// Otherwise the build will fail because wyw tries to include emotion styled components
|
||||
|
||||
@ -75,6 +75,11 @@ export const seedFeatureFlags = async (
|
||||
workspaceId: workspaceId,
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
key: FeatureFlagKey.IsLocalizationEnabled,
|
||||
workspaceId: workspaceId,
|
||||
value: true,
|
||||
},
|
||||
])
|
||||
.execute();
|
||||
};
|
||||
|
||||
@ -15,4 +15,5 @@ export enum FeatureFlagKey {
|
||||
IsCommandMenuV2Enabled = 'IS_COMMAND_MENU_V2_ENABLED',
|
||||
IsCrmMigrationEnabled = 'IS_CRM_MIGRATION_ENABLED',
|
||||
IsJsonFilterEnabled = 'IS_JSON_FILTER_ENABLED',
|
||||
IsLocalizationEnabled = 'IS_LOCALIZATION_ENABLED',
|
||||
}
|
||||
|
||||
@ -8,4 +8,4 @@
|
||||
"http://localhost:3001/settings/developers/webhooks/41a8ad80-265a-425a-93da-35452d0ac83d","en-US","/settings/developers/webhooks/41a8ad80-265a-425a-93da-35452d0ac83d","http://localhost:3001/settings/developers/webhooks/41a8ad80-265a-425a-93da-35452d0ac83d","8fc30143-b648-4fc0-afe7-e55e1c452003","Europe/Paris","2024-10-17 12:38:02.518","Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36","20202020-9e3b-46d4-a556-88b9ddc2b034","1","20202020-1c25-4d02-bf25-6aeccf7ea419"
|
||||
"http://localhost:3001/settings/developers/webhooks/41a8ad80-265a-425a-93da-35452d0ac83d","en-US","/settings/developers/webhooks/41a8ad80-265a-425a-93da-35452d0ac83d","http://localhost:3001/settings/developers/webhooks/41a8ad80-265a-425a-93da-35452d0ac83d","8fc30143-b648-4fc0-afe7-e55e1c452003","Europe/Paris","2024-10-17 12:41:11.844","Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36","20202020-9e3b-46d4-a556-88b9ddc2b034","1","20202020-1c25-4d02-bf25-6aeccf7ea419"
|
||||
"http://localhost:3001/settings/profile","en-US","/settings/profile","http://localhost:3001/settings/developers/webhooks/41a8ad80-265a-425a-93da-35452d0ac83d","8fc30143-b648-4fc0-afe7-e55e1c452003","Europe/Paris","2024-10-17 12:41:23.864","Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36","20202020-9e3b-46d4-a556-88b9ddc2b034","1","20202020-1c25-4d02-bf25-6aeccf7ea419"
|
||||
"http://localhost:3001/settings/appearance","en-US","/settings/appearance","http://localhost:3001/settings/developers/webhooks/41a8ad80-265a-425a-93da-35452d0ac83d","8fc30143-b648-4fc0-afe7-e55e1c452003","Europe/Paris","2024-10-17 12:41:25.972","Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36","20202020-9e3b-46d4-a556-88b9ddc2b034","1","20202020-1c25-4d02-bf25-6aeccf7ea419"
|
||||
"http://localhost:3001/settings/experience","en-US","/settings/experience","http://localhost:3001/settings/developers/webhooks/41a8ad80-265a-425a-93da-35452d0ac83d","8fc30143-b648-4fc0-afe7-e55e1c452003","Europe/Paris","2024-10-17 12:41:25.972","Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36","20202020-9e3b-46d4-a556-88b9ddc2b034","1","20202020-1c25-4d02-bf25-6aeccf7ea419"
|
||||
|
||||
|
@ -28,6 +28,13 @@ npx nx run twenty-front:graphql:generate
|
||||
npx nx run twenty-front:lint # pass --fix to fix lint errors
|
||||
```
|
||||
|
||||
## Translations
|
||||
|
||||
```bash
|
||||
npx nx run twenty-front:lingui:extract
|
||||
npx nx run twenty-front:lingui:compile
|
||||
```
|
||||
|
||||
### Test
|
||||
|
||||
```bash
|
||||
|
||||
Reference in New Issue
Block a user