Compare commits

..
1 Commits
Author SHA1 Message Date
prastoin 3050bc2d21 chore(all): sed version 0.42.0 2025-02-14 13:52:30 +01:00
3428 changed files with 88202 additions and 242591 deletions
-65
View File
@@ -1,65 +0,0 @@
# Twenty Development Rules
This directory contains Twenty's development guidelines and best practices. The rules are organized into several key categories:
## Guidelines Structure
### 1. Architecture and Structure
- `architecture.md`: Project overview, technology stack, and infrastructure setup
- `file-structure-guidelines.md`: File and directory organization patterns
### 2. Code Style and Development
- `typescript-guidelines.md`: TypeScript best practices and conventions
- `code-style-guidelines.md`: General coding standards and style guide
### 3. React Development
- `react-general-guidelines.md`: Core React development principles and patterns
- `react-state-management-guidelines.md`: State management approaches and best practices
### 4. Testing
- `testing-guidelines.md`: Testing strategies, patterns, and best practices
### 5. Internationalization
- `translations.md`: Translation workflow, i18n setup, and string management
## Common Development Commands
### Frontend Commands
```bash
# Testing
npx nx test twenty-front # Run unit tests
npx nx storybook:build twenty-front # Build Storybook
npx nx storybook:serve-and-test:static # Run Storybook tests
# Development
npx nx lint twenty-front # Run linter
npx nx typecheck twenty-front # Type checking
npx nx run twenty-front:graphql:generate # Generate GraphQL types
```
### Backend Commands
```bash
# Database
npx nx database:reset twenty-server # Reset database
npx nx run twenty-server:database:init:prod # Initialize database
npx nx run twenty-server:database:migrate:prod # Run migrations
# Development
npx nx run twenty-server:start # Start the server
npx nx run twenty-server:lint # Run linter (add --fix to auto-fix)
npx nx run twenty-server:typecheck # Type checking
npx nx run twenty-server:test # Run unit tests
npx nx run twenty-server:test:integration:with-db-reset # Run integration tests
# Migrations
npx nx run twenty-server:typeorm migration:generate src/database/typeorm/metadata/migrations/[name] -d src/database/typeorm/metadata/metadata.datasource.ts
# Workspace
npx nx run twenty-server:command workspace:sync-metadata -f # Sync metadata
```
## Usage
These rules are automatically attached to relevant files in your workspace through Cursor's context system. They help maintain consistency and quality across the Twenty codebase.
For the most up-to-date version of these guidelines, always refer to the files in this directory.
-97
View File
@@ -1,97 +0,0 @@
# Twenty Project Architecture
## Overview
Twenty is an open-source CRM built with modern technologies, using TypeScript for both frontend and backend development. This document outlines the core architectural decisions and structure of the project.
## Monorepo Structure
The project is organized as a monorepo using nx, with the following main packages:
### Main Packages
- `packages/twenty-front`: Main Frontend application
- Technology: React
- Purpose: Provides the main user interface for the CRM
- Key responsibilities: User interactions, state management, data display
- `packages/twenty-server`: Main Backend application
- Technology: NestJS
- Purpose: Handles business logic, data persistence, and API
- Key responsibilities: Data processing, authentication, API endpoints
- `packages/twenty-website`: Marketing Website and Documentation
- Technology: NextJS
- Purpose: Public-facing website and documentation
- Key responsibilities: Marketing content, documentation, SEO
- `packages/twenty-ui`: UI Component Library
- Technology: React
- Purpose: Shared UI components and design system
- Key responsibilities: Reusable components, design consistency
- `packages/twenty-shared`: Shared Utilities
- Purpose: Cross-package shared code between frontend and backend
- Contents: Utils, constants, types, interfaces
## Core Technology Stack
### Package Management
- Package Manager: yarn
- Monorepo Tool: nx
- Benefits: Consistent dependency management, shared configurations
### Database Layer
- Primary Database: PostgreSQL
- Schema Structure:
- Core schema: Main application data
- Metadata schema: Configuration and customization data
- Workspace schemas: One schema per tenant, containing tenant-specific data
- ORM Layer:
- TypeORM: For core and metadata schemas
- Purpose: Type-safe database operations for system data
- Benefits: Strong typing, migration support
- TwentyORM: For workspace schemas
- Purpose: Manages tenant-specific entities and customizations
- Benefits: Dynamic entity management, per-tenant customization
- Example: Entities like CompanyWorkspaceEntity are managed per workspace
### State Management
- Frontend State: Recoil
- Purpose: Global state management
- Use cases: User preferences, UI state, cached data
### Data Layer
- API Technology: GraphQL
- Client: Apollo Client
- Purpose: Data fetching and caching
- Benefits: Type safety, efficient data loading
### Infrastructure
- Cache: Redis
- Purpose: High-performance caching layer
- Use cases: Session data, frequent queries
- Authentication: JWT
- Purpose: Secure user authentication
- Implementation: Token-based auth flow
- Queue System: BullMQ
- Purpose: Background job processing
- Use cases: Emails, exports, imports
- Storage: S3/Local Filesystem
- Purpose: File storage and management
- Flexibility: Configurable for cloud or local storage
### Testing Infrastructure
- Backend Testing:
- Framework: Jest
- API Testing: Supertest
- Coverage: Unit tests, integration tests
- Frontend Testing:
- Framework: Jest
- Component Testing: Storybook
- API Mocking: MSW (Mock Service Worker)
- End-to-End Testing:
- Framework: Playwright
- Coverage: Critical user journeys
-259
View File
@@ -1,259 +0,0 @@
# Code Style Guidelines
## Core Code Style Principles
Twenty emphasizes clean, readable, and maintainable code. This document outlines our code style conventions and best practices.
## Control Flow
### Early Returns
- Use early returns to reduce nesting
- Handle edge cases first
```typescript
// ✅ Correct
const processUser = (user: User | null) => {
if (!user) return null;
if (!user.isActive) return null;
return {
id: user.id,
name: user.name,
};
};
// ❌ Incorrect
const processUser = (user: User | null) => {
if (user) {
if (user.isActive) {
return {
id: user.id,
name: user.name,
};
}
}
return null;
};
```
### No Nested Ternaries
- Avoid nested ternary operators
- Use if statements or early returns
```typescript
// ✅ Correct
const getUserDisplay = (user: User) => {
if (!user.name) return 'Anonymous';
if (!user.isActive) return 'Inactive User';
return user.name;
};
// ❌ Incorrect
const getUserDisplay = (user: User) =>
user.name
? user.isActive
? user.name
: 'Inactive User'
: 'Anonymous';
```
### No Else-If Chains
- Use switch statements or lookup objects
- Keep conditions flat
```typescript
// ✅ Correct
const getStatusColor = (status: Status): string => {
switch (status) {
case 'success':
return 'green';
case 'warning':
return 'yellow';
case 'error':
return 'red';
default:
return 'gray';
}
};
// Or using a lookup object
const statusColors: Record<Status, string> = {
success: 'green',
warning: 'yellow',
error: 'red',
default: 'gray',
};
// ❌ Incorrect
const getStatusColor = (status: Status): string => {
if (status === 'success') {
return 'green';
} else if (status === 'warning') {
return 'yellow';
} else if (status === 'error') {
return 'red';
} else {
return 'gray';
}
};
```
## Operators and Expressions
### Optional Chaining Over &&
- Use optional chaining for null/undefined checks
- Clearer intent and better type safety
```typescript
// ✅ Correct
const userName = user?.name;
const userAddress = user?.address?.street;
// ❌ Incorrect
const userName = user && user.name;
const userAddress = user && user.address && user.address.street;
```
## Function Design
### Small Focused Functions
- Keep functions small and single-purpose
- Extract complex logic into helper functions
```typescript
// ✅ Correct
const validateUser = (user: User) => {
if (!isValidName(user.name)) return false;
if (!isValidEmail(user.email)) return false;
if (!isValidAge(user.age)) return false;
return true;
};
const isValidName = (name: string) => {
return name.length >= 2 && /^[a-zA-Z\s]*$/.test(name);
};
const isValidEmail = (email: string) => {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
};
const isValidAge = (age: number) => {
return age >= 18 && age <= 120;
};
// ❌ Incorrect
const validateUser = (user: User) => {
if (user.name.length < 2 || !/^[a-zA-Z\s]*$/.test(user.name)) return false;
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(user.email)) return false;
if (user.age < 18 || user.age > 120) return false;
return true;
};
```
## Naming and Documentation
### Clear Variable Names
- Use descriptive, intention-revealing names
- Avoid abbreviations unless common
```typescript
// ✅ Correct
const isUserActive = user.status === 'active';
const hasRequiredPermissions = user.permissions.includes('admin');
const userDisplayName = user.name || 'Anonymous';
// ❌ Incorrect
const active = user.status === 'active';
const hasPerm = user.permissions.includes('admin');
const udn = user.name || 'Anonymous';
```
### No Console.logs in Commits
- Remove all console.logs before committing
- Use proper logging/error tracking in production
```typescript
// ❌ Incorrect - Don't commit these
console.log('user:', user);
console.log('debug:', someValue);
// ✅ Correct - Use proper logging
logger.info('User action completed', { userId: user.id });
logger.error('Operation failed', { error });
```
### Minimal Comments
- Write self-documenting code
- Use comments only for complex business logic
```typescript
// ✅ Correct
// Calculate pro-rated amount based on billing cycle
const calculateProRatedAmount = (amount: number, daysLeft: number, totalDays: number) => {
return (amount * daysLeft) / totalDays;
};
// ❌ Incorrect - Unnecessary comments
// Get the user's name
const getUserName = (user: User) => user.name;
// Check if user is active
const isUserActive = (user: User) => user.status === 'active';
```
## Error Handling
### Proper Error Handling
- Use try-catch blocks appropriately
- Provide meaningful error messages
```typescript
// ✅ Correct
const fetchUserData = async (userId: string) => {
try {
const response = await api.get(`/users/${userId}`);
return response.data;
} catch (error) {
logger.error('Failed to fetch user data', {
userId,
error: error instanceof Error ? error.message : 'Unknown error',
});
throw new UserFetchError('Failed to fetch user data');
}
};
// ❌ Incorrect
const fetchUserData = async (userId: string) => {
try {
const response = await api.get(`/users/${userId}`);
return response.data;
} catch (error) {
console.log('error:', error);
throw error;
}
};
```
## Code Organization
### Logical Grouping
- Group related code together
- Maintain consistent organization
```typescript
// ✅ Correct
class UserService {
// Properties
private readonly api: Api;
private readonly logger: Logger;
// Constructor
constructor(api: Api, logger: Logger) {
this.api = api;
this.logger = logger;
}
// Public methods
public async getUser(id: string): Promise<User> {
// Implementation
}
public async updateUser(user: User): Promise<User> {
// Implementation
}
// Private helpers
private validateUser(user: User): boolean {
// Implementation
}
}
```
-207
View File
@@ -1,207 +0,0 @@
# File Structure Guidelines
## Core File Structure Principles
Twenty follows a modular and organized file structure that promotes maintainability and scalability. This document outlines our file organization conventions and best practices.
## Component Organization
### One Component Per File
- Each component should have its own file
- File name should match component name
```typescript
// ✅ Correct
// UserProfile.tsx
export const UserProfile = () => {
return <div>...</div>;
};
// ❌ Incorrect
// users.tsx
export const UserProfile = () => {
return <div>...</div>;
};
export const UserList = () => {
return <div>...</div>;
};
```
## Directory Structure
### Feature Modules
- Place features in `modules/` directory
- Group related components and logic
```
modules/
├── users/
│ ├── components/
│ │ ├── UserList.tsx
│ │ ├── UserCard.tsx
│ │ └── UserProfile.tsx
│ ├── hooks/
│ │ └── useUser.ts
│ ├── states/
│ │ └── userStates.ts
│ └── types/
│ └── user.ts
├── workspace/
│ ├── components/
│ ├── hooks/
│ └── states/
└── settings/
├── components/
├── hooks/
└── states/
```
### Hooks Organization
- Place hooks in `hooks/` directory
- Group by feature or global usage
```
hooks/
├── useClickOutside.ts
├── useDebounce.ts
└── features/
├── users/
│ ├── useUserActions.ts
│ └── useUserData.ts
└── workspace/
└── useWorkspaceSettings.ts
```
### State Management
- Place state definitions in `states/` directory
- Organize by feature
```
states/
├── global/
│ ├── theme.ts
│ └── navigation.ts
├── users/
│ ├── atoms.ts
│ └── selectors.ts
└── workspace/
├── atoms.ts
└── selectors.ts
```
### Types Organization
- Place types in `types/` directory
- Group by domain or feature
```
types/
├── common.ts
├── api.ts
└── features/
├── user.ts
├── workspace.ts
└── settings.ts
```
## Naming Conventions
### Component Files
- Use PascalCase for component files
- Use descriptive, feature-specific names
```
components/
├── UserProfile.tsx
├── UserProfileHeader.tsx
└── UserProfileContent.tsx
```
### Non-Component Files
- Use camelCase for non-component files
- Use clear, descriptive names
```
hooks/
├── useClickOutside.ts
└── useDebounce.ts
utils/
├── dateFormatter.ts
└── stringUtils.ts
```
## Module Structure
### Feature Module Organization
- Consistent structure across features
- Clear separation of concerns
```
modules/users/
├── components/
│ ├── UserList/
│ │ ├── UserList.tsx
│ │ ├── UserListItem.tsx
│ │ └── UserListHeader.tsx
│ └── UserProfile/
│ ├── UserProfile.tsx
│ └── UserProfileHeader.tsx
├── hooks/
│ ├── useUserList.ts
│ └── useUserProfile.ts
├── states/
│ ├── atoms.ts
│ └── selectors.ts
├── types/
│ └── user.ts
└── utils/
└── userFormatter.ts
```
## Best Practices
### Import Organization
- Group imports by type
- Maintain consistent order
```typescript
// External dependencies
import { useState } from 'react';
import { styled } from '@emotion/styled';
// Internal modules
import { useUser } from '~/modules/users/hooks';
import { userState } from '~/modules/users/states';
// Local imports
import { UserAvatar } from './UserAvatar';
import { type UserProfileProps } from './types';
```
### Path Aliases
- Use path aliases for better imports
- Avoid deep relative paths
```typescript
// ✅ Correct
import { Button } from '~/components/Button';
import { useUser } from '~/modules/users/hooks';
// ❌ Incorrect
import { Button } from '../../../components/Button';
import { useUser } from '../../../modules/users/hooks';
```
### Component Co-location
- Keep related files close together
- Use index files for public APIs
```
components/UserProfile/
├── UserProfile.tsx
├── UserProfileHeader.tsx
├── UserProfileContent.tsx
├── styles.ts
├── types.ts
└── index.ts
```
### Test File Location
- Place test files next to implementation
- Use `.test.ts` or `.spec.ts` extension
```
components/
├── UserProfile.tsx
├── UserProfile.test.tsx
├── UserProfile.stories.tsx
└── types.ts
```
-220
View File
@@ -1,220 +0,0 @@
# React Guidelines
## Core React Principles
Twenty follows modern React best practices with a focus on functional components and clean, maintainable code. This document outlines our React conventions and best practices.
## Component Structure
### Functional Components Only
- Use functional components exclusively
- No class components allowed
```typescript
// ✅ Correct
export const UserProfile = ({ user }: UserProfileProps) => {
return (
<StyledContainer>
<h1>{user.name}</h1>
</StyledContainer>
);
};
// ❌ Incorrect
export class UserProfile extends React.Component<UserProfileProps> {
render() {
return (
<StyledContainer>
<h1>{this.props.user.name}</h1>
</StyledContainer>
);
}
}
```
### Named Exports
- Use named exports exclusively
- No default exports
```typescript
// ✅ Correct
export const Button = ({ label }: ButtonProps) => {
return <button>{label}</button>;
};
// ❌ Incorrect
export default function Button({ label }: ButtonProps) {
return <button>{label}</button>;
}
```
## State and Effects
### Event Handlers Over useEffect
- Prefer event handlers for state updates
- Avoid useEffect for state synchronization
```typescript
// ✅ Correct
const UserForm = () => {
const handleSubmit = async (data: FormData) => {
await updateUser(data);
refreshUserList();
};
return <Form onSubmit={handleSubmit} />;
};
// ❌ Incorrect
const UserForm = () => {
useEffect(() => {
if (formData) {
updateUser(formData);
}
}, [formData]);
return <Form />;
};
```
## Component Design
### Small, Focused Components
- Keep components small and single-purpose
- Extract reusable logic into custom hooks
```typescript
// ✅ Correct
const UserCard = ({ user }: UserCardProps) => {
return (
<StyledCard>
<UserAvatar user={user} />
<UserInfo user={user} />
<UserActions user={user} />
</StyledCard>
);
};
// ❌ Incorrect
const UserCard = ({ user }: UserCardProps) => {
return (
<StyledCard>
{/* Too much logic in one component */}
<img src={user.avatar} />
<div>{user.name}</div>
<div>{user.email}</div>
<button onClick={() => handleEdit(user)}>Edit</button>
<button onClick={() => handleDelete(user)}>Delete</button>
{/* More complex logic... */}
</StyledCard>
);
};
```
## Props
### Prop Naming
- Use clear, descriptive prop names
- Follow React conventions (onClick, isActive, etc.)
```typescript
// ✅ Correct
type ButtonProps = {
onClick: () => void;
isDisabled?: boolean;
isLoading?: boolean;
};
// ❌ Incorrect
type ButtonProps = {
clickHandler: () => void;
disabled?: boolean;
loading?: boolean;
};
```
### Prop Destructuring
- Destructure props with proper typing
- Use TypeScript for prop types
```typescript
// ✅ Correct
const Button = ({ onClick, isDisabled, children }: ButtonProps) => {
return (
<button onClick={onClick} disabled={isDisabled}>
{children}
</button>
);
};
// ❌ Incorrect
const Button = (props: ButtonProps) => {
return (
<button onClick={props.onClick} disabled={props.isDisabled}>
{props.children}
</button>
);
};
```
## Performance Optimization
### Memoization
- Use memo for expensive computations
- Avoid premature optimization
```typescript
// ✅ Correct - Complex computation
const MemoizedChart = memo(({ data }: ChartProps) => {
// Complex rendering logic
return <ComplexChart data={data} />;
});
// ❌ Incorrect - Unnecessary memoization
const MemoizedText = memo(({ text }: { text: string }) => {
return <span>{text}</span>;
});
```
### Event Handlers
- Use callback refs for DOM manipulation
- Memoize callbacks when needed
```typescript
// ✅ Correct
const UserList = () => {
const handleScroll = useCallback((event: UIEvent) => {
// Complex scroll handling
}, []);
return <div onScroll={handleScroll}>{/* content */}</div>;
};
```
## Error Handling
### Error Boundaries
- Use error boundaries for component error handling
- Provide meaningful fallback UIs
```typescript
// ✅ Correct
const ErrorFallback = ({ error }: { error: Error }) => (
<StyledError>
<h2>Something went wrong</h2>
<pre>{error.message}</pre>
</StyledError>
);
const SafeComponent = () => (
<ErrorBoundary FallbackComponent={ErrorFallback}>
<ComponentThatMightError />
</ErrorBoundary>
);
```
### Loading States
- Handle loading states gracefully
- Provide meaningful loading indicators
```typescript
// ✅ Correct
const UserProfile = () => {
const { data: user, isLoading, error } = useUser();
if (isLoading) return <LoadingSpinner />;
if (error) return <ErrorMessage error={error} />;
if (!user) return <NotFound />;
return <UserProfileContent user={user} />;
};
```
@@ -1,219 +0,0 @@
# State Management Guidelines
## Core State Management Principles
Twenty uses a combination of Recoil for global state and Apollo Client for server state management. This document outlines our state management conventions and best practices.
## Global State Management
### Recoil Usage
- Use Recoil for global application state
- Keep atoms small and focused
```typescript
// ✅ Correct
// states/theme.ts
export const themeState = atom<'light' | 'dark'>({
key: 'themeState',
default: 'light',
});
// states/user.ts
export const userState = atom<User | null>({
key: 'userState',
default: null,
});
// ❌ Incorrect
// states/globalState.ts
export const globalState = atom({
key: 'globalState',
default: {
theme: 'light',
user: null,
settings: {},
// ... many other unrelated pieces of state
},
});
```
### Atom Organization
- Place atoms in the `states/` directory
- Group related atoms in feature-specific files
```typescript
// states/workspace/atoms.ts
export const workspaceIdState = atom<string>({
key: 'workspaceIdState',
default: '',
});
export const workspaceSettingsState = atom<WorkspaceSettings>({
key: 'workspaceSettingsState',
default: defaultSettings,
});
```
## Server State Management
### Apollo Client Usage
- Use Apollo Client for all GraphQL operations
- Leverage Apollo's caching capabilities
```typescript
// ✅ Correct
const { data, loading } = useQuery(GET_USER_QUERY, {
variables: { id },
fetchPolicy: 'cache-first',
});
// ❌ Incorrect
const [user, setUser] = useState(null);
useEffect(() => {
fetch('/api/user/' + id).then(setUser);
}, [id]);
```
### Query Organization
- Separate operation files
- Use fragments for shared fields
```typescript
// queries/user.ts
export const UserFragment = gql`
fragment UserFields on User {
id
name
email
}
`;
export const GET_USER = gql`
query GetUser($id: ID!) {
user(id: $id) {
...UserFields
}
}
${UserFragment}
`;
```
## State Management Best Practices
### Multiple Small Atoms
- Prefer multiple small atoms over prop drilling
- Keep atoms focused on specific features
```typescript
// ✅ Correct
export const selectedViewState = atom<string>({
key: 'selectedViewState',
default: '',
});
export const viewFiltersState = atom<ViewFilters>({
key: 'viewFiltersState',
default: {},
});
// ❌ Incorrect - Prop drilling
const ViewContainer = ({ selectedView, filters, onViewChange }) => {
return (
<ViewHeader view={selectedView} onViewChange={onViewChange}>
<ViewContent>
<ViewFilters filters={filters} />
</ViewContent>
</ViewHeader>
);
};
```
### No useRef for State
- Never use useRef for state management
- Use proper state management tools
```typescript
// ✅ Correct
const [count, setCount] = useState(0);
// or
const [count, setCount] = useRecoilState(countState);
// ❌ Incorrect
const countRef = useRef(0);
```
### Data Fetching
- Extract data fetching to sibling components
- Keep components focused on presentation
```typescript
// ✅ Correct
const UserProfileContainer = () => {
const { data, loading } = useQuery(GET_USER);
if (loading) return <LoadingSpinner />;
return <UserProfile user={data.user} />;
};
const UserProfile = ({ user }: UserProfileProps) => {
return <div>{user.name}</div>;
};
// ❌ Incorrect
const UserProfile = () => {
const { data, loading } = useQuery(GET_USER);
if (loading) return <LoadingSpinner />;
return <div>{data.user.name}</div>;
};
```
### Hook Usage
- Use appropriate hooks for state access
- Choose between useRecoilValue and useRecoilState based on needs
```typescript
// ✅ Correct - Read-only access
const theme = useRecoilValue(themeState);
// ✅ Correct - Read-write access
const [theme, setTheme] = useRecoilState(themeState);
// ❌ Incorrect - Using state setter when only reading
const [theme, _] = useRecoilState(themeState);
```
## Performance Considerations
### Selector Usage
- Use selectors for derived state
- Memoize complex calculations
```typescript
// ✅ Correct
const filteredUsersState = selector({
key: 'filteredUsersState',
get: ({ get }) => {
const users = get(usersState);
const filter = get(userFilterState);
return users.filter(user =>
user.name.toLowerCase().includes(filter.toLowerCase())
);
},
});
// ❌ Incorrect - Calculating in component
const UserList = () => {
const users = useRecoilValue(usersState);
const filter = useRecoilValue(userFilterState);
const filteredUsers = users.filter(user =>
user.name.toLowerCase().includes(filter.toLowerCase())
);
return <List users={filteredUsers} />;
};
```
### Cache Management
- Configure appropriate cache policies
- Handle cache invalidation properly
```typescript
// ✅ Correct
const [updateUser] = useMutation(UPDATE_USER, {
update: (cache, { data }) => {
cache.modify({
id: cache.identify(data.updateUser),
fields: {
name: () => data.updateUser.name,
},
});
},
});
```
-253
View File
@@ -1,253 +0,0 @@
# Testing Guidelines
## Core Testing Principles
Twenty follows a comprehensive testing strategy across all packages, ensuring high-quality, maintainable code. This document outlines our testing conventions and best practices.
## Testing Stack
### Backend Testing
- Primary Framework: Jest
- API Testing: Supertest
- Coverage Requirements: 80% minimum
### Frontend Testing
- Component Testing: Jest + React Testing Library
- Visual Testing: Storybook
- API Mocking: MSW (Mock Service Worker)
### End-to-End Testing
- Framework: Playwright
- Coverage: Critical user journeys
- Cross-browser testing
## Test Organization
### Test File Location
- Co-locate tests with implementation files
- Use consistent naming patterns
```
src/
├── components/
│ ├── UserProfile.tsx
│ ├── UserProfile.test.tsx
│ └── UserProfile.stories.tsx
```
### Test File Naming
- Use `.test.ts(x)` for unit/integration tests
- Use `.spec.ts(x)` for E2E tests
- Use `.stories.tsx` for Storybook stories
## Unit Testing
### Component Testing
- Test behavior, not implementation
- Use React Testing Library best practices
```typescript
// ✅ Correct
test('displays user name when provided', () => {
render(<UserProfile user={{ name: 'John Doe' }} />);
expect(screen.getByText('John Doe')).toBeInTheDocument();
});
// ❌ Incorrect - Testing implementation details
test('sets the text content', () => {
const { container } = render(<UserProfile user={{ name: 'John Doe' }} />);
expect(container.querySelector('h1').textContent).toBe('John Doe');
});
```
### Hook Testing
- Use `renderHook` from @testing-library/react-hooks
- Test all possible states
```typescript
// ✅ Correct
test('useUser hook manages user state', () => {
const { result } = renderHook(() => useUser());
act(() => {
result.current.setUser({ id: '1', name: 'John' });
});
expect(result.current.user).toEqual({ id: '1', name: 'John' });
});
```
### Mocking
- Mock external dependencies
- Use jest.mock for module mocking
```typescript
// ✅ Correct
jest.mock('~/services/api', () => ({
fetchUser: jest.fn().mockResolvedValue({ id: '1', name: 'John' }),
}));
test('fetches and displays user', async () => {
render(<UserProfile userId="1" />);
expect(await screen.findByText('John')).toBeInTheDocument();
});
```
## Integration Testing
### API Testing
- Test complete request/response cycles
- Use Supertest for backend API testing
```typescript
// ✅ Correct
describe('GET /api/users/:id', () => {
it('returns user when found', async () => {
const response = await request(app)
.get('/api/users/1')
.expect(200);
expect(response.body).toEqual({
id: '1',
name: 'John Doe',
});
});
it('returns 404 when user not found', async () => {
await request(app)
.get('/api/users/999')
.expect(404);
});
});
```
## E2E Testing
### Test Structure
- Organize by user journey
- Use page objects for reusability
```typescript
// pages/login.ts
export class LoginPage {
async login(email: string, password: string) {
await this.page.fill('[data-testid="email-input"]', email);
await this.page.fill('[data-testid="password-input"]', password);
await this.page.click('[data-testid="login-button"]');
}
}
// tests/auth.spec.ts
test('user can login successfully', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.login('user@example.com', 'password');
await expect(page).toHaveURL('/dashboard');
});
```
### Test Data
- Use dedicated test environments
- Reset state between tests
```typescript
// ✅ Correct
beforeEach(async () => {
await resetDatabase();
await seedTestData();
});
test('user workflow', async ({ page }) => {
// Test with clean, predictable state
});
```
## Visual Testing
### Storybook Guidelines
- Create stories for all components
- Document component variants
```typescript
// Button.stories.tsx
export default {
title: 'Components/Button',
component: Button,
} as Meta;
export const Primary = {
args: {
variant: 'primary',
label: 'Primary Button',
},
};
export const Secondary = {
args: {
variant: 'secondary',
label: 'Secondary Button',
},
};
```
### Visual Regression
- Use Storybook's visual regression testing
- Review changes carefully
```typescript
// jest.config.js
module.exports = {
preset: 'jest-image-snapshot',
setupFilesAfterEnv: ['<rootDir>/setup-tests.ts'],
};
// Button.visual.test.tsx
describe('Button', () => {
it('matches visual snapshot', async () => {
const image = await page.screenshot();
expect(image).toMatchImageSnapshot();
});
});
```
## Test Quality
### Test Data Attributes
- Use data-testid for test selectors
- Avoid selecting by CSS classes
```typescript
// ✅ Correct
<button data-testid="submit-button">Submit</button>
// In tests
const button = screen.getByTestId('submit-button');
// ❌ Incorrect
const button = container.querySelector('.submit-btn');
```
### Assertion Best Practices
- Use explicit assertions
- Test both positive and negative cases
```typescript
// ✅ Correct
test('form validation', async () => {
render(<UserForm />);
// Negative case
await userEvent.click(screen.getByText('Submit'));
expect(screen.getByText('Name is required')).toBeInTheDocument();
// Positive case
await userEvent.type(screen.getByLabelText('Name'), 'John Doe');
await userEvent.click(screen.getByText('Submit'));
expect(screen.queryByText('Name is required')).not.toBeInTheDocument();
});
```
### Coverage Requirements
- A new feature should have at least 80% coverage
- Focus on critical paths
- Run coverage reports in CI
```typescript
// jest.config.js
module.exports = {
coverageThreshold: {
global: {
statements: 80,
branches: 80,
functions: 80,
lines: 80,
},
},
};
```
-162
View File
@@ -1,162 +0,0 @@
# Translation Guidelines
## Core Translation Principles
Twenty uses Lingui for internationalization (i18n) and Crowdin for translation management. This document outlines our translation workflow and best practices.
## Technology Stack
### Translation Tools
- **Framework**: @lingui/react
- **Translation Management**: Crowdin
- **Workflow**: GitHub Actions for automation
### Package Structure
Translation files are managed in multiple packages:
- `twenty-front`: Frontend translations
- `twenty-server`: Backend translations
- `twenty-emails`: Email template translations
## Translation Process
### Adding New Strings
#### Using Lingui Macros
- Use `<Trans>` for components
- Use `t` macro for strings outside JSX
```typescript
// ✅ Correct - In JSX
import { Trans } from '@lingui/react/macro';
const WelcomeMessage = () => (
<h1>
<Trans>Welcome to Twenty</Trans>
</h1>
);
// ✅ Correct - Outside JSX
import { t } from '@lingui/react/macro';
const getMessage = () => {
return t`Welcome to Twenty`;
};
// ❌ Incorrect - Don't use raw strings
const WelcomeMessage = () => (
<h1>Welcome to Twenty</h1>
);
```
### String Guidelines
#### What to Translate
- User interface text
- Error messages
- Notifications
- Email content
#### What Not to Translate
- Variables
- Test data/mocks
### Translation Workflow
#### 1. Extracting Translations
- Automatically triggered on main branch changes
- Can be manually triggered in GitHub Actions
- Process:
```bash
# Extract new strings
nx run twenty-front:lingui:extract
nx run twenty-server:lingui:extract
nx run twenty-emails:lingui:extract
```
#### 2. Translation Management
- Translations are managed in Crowdin
- Changes are synced every 2 hours
- Process:
1. New strings are uploaded to Crowdin
2. Translators work on translations
3. Translations are pulled back to the repository
#### 3. Compiling Translations
- Happens automatically in CI/CD
- Required before running the application
```bash
# Compile translations
nx run twenty-front:lingui:compile
nx run twenty-server:lingui:compile
nx run twenty-emails:lingui:compile
```
## Best Practices
### String Management
#### Use Placeholders
- Use placeholders for dynamic content
```typescript
// ✅ Correct
<Trans>Hello {userName},</Trans>
// ❌ Incorrect - String concatenation
<Trans>Hello </Trans>{userName},
```
#### Provide Context
- Lingui provides powerfulway to add context for translators but we don't use them as of today.
### Code Organization
#### Translation Files
- Keep translation files organized by feature
- Use consistent naming patterns
```
src/
├── locales/
│ ├── en/
│ │ ├── messages.po
│ │ └── messages.js
│ └── fr/
│ ├── messages.po
│ └── messages.js
```
### Quality Assurance
#### Strict Mode
- Use --strict mode when compiling to identify missing translations
#### Testing Translations
- Test with different locales
- Verify string interpolation
- Check layout with different language lengths
## Automation
### GitHub Actions
#### Pull Workflow
- Runs every 2 hours
- Downloads new translations from Crowdin
- Creates PR if changes detected
- Can be manually triggered with force pull option
#### Push Workflow
- Runs on main branch changes
- Extracts and uploads new strings
- Compiles translations
- Creates PR with changes
### Error Handling
#### Missing Translations
- Development: Shown in original language
- Production: Falls back to default language
- Strict mode in CI catches missing translations
#### Compilation Errors
- Addressed before merging
- PR created for fixing missing translations
- Automated testing in CI pipeline
-172
View File
@@ -1,172 +0,0 @@
# TypeScript Guidelines
## Core TypeScript Principles
Twenty enforces strict TypeScript usage to ensure type safety and maintainable code. This document outlines our TypeScript conventions and best practices.
## Type Safety
### Strict Typing
- **No 'any' type allowed**
- TypeScript strict mode enabled
- noImplicitAny enabled
```typescript
// ✅ Correct
function processUser(user: User) {
return user.name;
}
// ❌ Incorrect
function processUser(user: any) {
return user.name;
}
```
### Type Definitions
#### Types over Interfaces
- Use `type` for all type definitions
- Exception: When extending third-party interfaces
```typescript
// ✅ Correct
type User = {
id: string;
name: string;
email: string;
};
// ❌ Incorrect
interface User {
id: string;
name: string;
email: string;
}
```
### String Literals over Enums
- Use string literal unions instead of enums
- Exception: GraphQL enums
```typescript
// ✅ Correct
type UserRole = 'admin' | 'user' | 'guest';
// ❌ Incorrect
enum UserRole {
Admin = 'admin',
User = 'user',
Guest = 'guest',
}
```
## Naming Conventions
### Component Props
- Suffix component prop types with 'Props'
- Keep props focused and single-purpose
```typescript
// ✅ Correct
type ButtonProps = {
label: string;
onClick: () => void;
variant?: 'primary' | 'secondary';
};
// ❌ Incorrect
type ButtonParameters = {
label: string;
onClick: () => void;
variant?: 'primary' | 'secondary';
};
```
## Type Inference
### Leverage TypeScript Inference
- Use type inference when types are clear
- Explicitly type when inference is ambiguous
```typescript
// ✅ Correct - Clear inference
const users = ['John', 'Jane']; // inferred as string[]
// ✅ Correct - Explicit typing needed
const processUser = (user: User): UserResponse => {
// Complex processing
return response;
};
// ❌ Incorrect - Unnecessary explicit typing
const users: string[] = ['John', 'Jane'];
```
## Best Practices
### Type Guards
- Use type guards for runtime type checking
- Prefer discriminated unions
```typescript
// ✅ Correct
type Success = {
type: 'success';
data: User;
};
type Error = {
type: 'error';
message: string;
};
type Result = Success | Error;
function handleResult(result: Result) {
if (result.type === 'success') {
// TypeScript knows result.data exists
console.log(result.data);
}
}
```
### Generics
- Use generics for reusable type patterns
- Keep generic names descriptive
```typescript
// ✅ Correct
type ApiResponse<TData> = {
data: TData;
status: number;
message: string;
};
// ❌ Incorrect
type ApiResponse<T> = {
data: T;
status: number;
message: string;
};
```
### Type Exports
- Export types when they're used across files
- Keep type definitions close to their usage
```typescript
// types.ts
export type User = {
id: string;
name: string;
};
// UserComponent.tsx
import { type User } from './types';
```
### Utility Types
- Leverage TypeScript utility types
- Create custom utility types for repeated patterns
```typescript
// Built-in utility types
type UserPartial = Partial<User>;
type UserReadonly = Readonly<User>;
// Custom utility types
type NonNullableProperties<T> = {
[P in keyof T]: NonNullable<T[P]>;
};
```
+99
View File
@@ -0,0 +1,99 @@
# Twenty Development Rules
## General
- Twenty is an open source CRM built with Typescript (React, NestJS)
### Main Packages / folders
- packages/twenty-front: Main Frontend (React)
- packages/twenty-server: Main Backend (NestJS)
- packages/twenty-website: Marketing website + docs (NextJS)
- packages/twenty-ui: UI library (React)
- packages/twenty-shared: Shared utils, constants, types
### Development Stack
- Package Manager: yarn
- Monorepo: nx
- Database: PostgreSQL + TypeORM (core, metadata schemas)
- State Management: Recoil
- Data Management: Apollo / GraphQL
- Cache: redis
- Auth: JWT
- Queue: BullMQ
- Storage: S3/local filesystem
- Testing Backend: Jest, Supertest
- Testing Frontend: Jest, Storybook, MSW
- Testing E2E: Playwright
## Styling
- Use @emotion/styled, never CSS classes/Tailwind
- Prefix styled components with 'Styled'
- Keep styled components at top of file
- Use Theme object for colors/spacing/typography
- Use Theme values instead of px/rem
- Use mq helper for media queries
## TypeScript
- No 'any' type - use proper typing
- Use type over interface
- String literals over enums (except GraphQL)
- Props suffix for component props types
- Use type inference when possible
- Enable strict mode and noImplicitAny
## React
- Only functional components
- Named exports only
- No useEffect, prefer event handlers
- Small focused components
- Proper prop naming (onClick, isActive)
- Destructure props with types
## State Management
- Recoil for global state
- Apollo Client for GraphQL/server state
- Atoms in states/ directory
- Multiple small atoms over prop drilling
- No useRef for state
- Extract data fetching to siblings
- useRecoilValue/useRecoilState appropriately
## File Structure
- One component per file
- Features in modules/
- Hooks in hooks/
- States in states/
- Types in types/
- PascalCase components, camelCase others
## Translation
- Use @lingui/react/macro
- Use <Trans> within components
- Use t`string` elsewhere (from useLingui hook)
- Don't translate metadata (field names, object names, etc)
- Don't translate mocks
## Code Style
- Early returns
- No nested ternaries
- No else-if
- Optional chaining over &&
- Small focused functions
- Clear variable names
- No console.logs in commits
- Few comments, prefer code readability
## GraphQL
- Use gql tag
- Separate operation files
- Proper codegen typing
- Consistent naming (getX, updateX)
- Use fragments
- Use generated types
## Testing
- Test every feature
- React Testing Library
- Test behavior not implementation
- Mock external deps
- Use data-testid
- Follow naming conventions
-1
View File
@@ -10,7 +10,6 @@ module.exports = {
'lingui',
],
rules: {
'lingui/no-single-variables-to-translate': 'off',
'func-style': ['error', 'declaration', { allowArrowFunctions: true }],
'no-console': ['warn', { allow: ['group', 'groupCollapsed', 'groupEnd'] }],
'no-control-regex': 0,
@@ -7,9 +7,6 @@ inputs:
required: false
tasks:
required: true
configuration:
required: false
default: 'ci'
runs:
using: "composite"
@@ -18,4 +15,4 @@ runs:
uses: nrwl/nx-set-shas@v4
- name: Run affected command
shell: bash
run: npx nx affected --nxBail --configuration=${{ inputs.configuration }} -t=${{ inputs.tasks }} --parallel=${{ inputs.parallel }} --exclude='*,!tag:${{ inputs.tag }}'
run: npx nx affected --nxBail --configuration=ci -t=${{ inputs.tasks }} --parallel=${{ inputs.parallel }} --exclude='*,!tag:${{ inputs.tag }}'
@@ -20,7 +20,7 @@ runs:
id: cache-primary-key-builder
shell: bash
run: |
echo "CACHE_PRIMARY_KEY_PREFIX=v2-${{ inputs.key }}-${{ github.ref_name }}" >> "${GITHUB_OUTPUT}"
echo "CACHE_PRIMARY_KEY_PREFIX=${{ inputs.key }}-${{ github.ref_name }}" >> "${GITHUB_OUTPUT}"
- name: Restore cache
uses: actions/cache/restore@v4
id: restore-cache
@@ -25,8 +25,8 @@ runs:
id: cache-node-modules
uses: actions/cache/restore@v4
with:
key: v2-${{ steps.globals.outputs.CACHE_KEY_PREFIX }}-${{github.sha}}
restore-keys: v2-${{ steps.globals.outputs.CACHE_KEY_PREFIX }}-
key: ${{ steps.globals.outputs.CACHE_KEY_PREFIX }}-${{github.sha}}
restore-keys: ${{ steps.globals.outputs.CACHE_KEY_PREFIX }}-
path: ${{ steps.globals.outputs.PATH_TO_CACHE }}
- name: Install Dependencies
if: ${{ steps.cache-node-modules.outputs.cache-hit != 'true' && steps.cache-node-modules.outputs.cache-matched-key == '' }}
+10 -21
View File
@@ -108,14 +108,15 @@ jobs:
- name: GraphQL / Check for Pending Generation
if: steps.changed-files.outputs.any_changed == 'true'
run: |
# Run GraphQL generation commands
npx nx run twenty-front:graphql:generate
npx nx run twenty-front:graphql:generate --configuration=metadata
# Check if any files were modified
if ! git diff --quiet; then
echo "::error::GraphQL schema changes detected. Please run 'npx nx run twenty-front:graphql:generate' and 'npx nx run twenty-front:graphql:generate --configuration=metadata' and commit the changes."
exit 1
GRAPHQL_GENERATE_OUTPUT=$(npx nx run twenty-front:graphql:generate || true)
GRAPHQL_METADATA_OUTPUT=$(npx nx run twenty-front:graphql:generate --configuration=metadata || true)
if [[ $GRAPHQL_GENERATE_OUTPUT == *"No changes detected"* && $GRAPHQL_METADATA_OUTPUT == *"No changes detected"* ]]; then
echo "GraphQL generation check passed."
else
echo "::error::Unexpected GraphQL changes detected. Please run the required commands and commit the changes."
echo "$GRAPHQL_GENERATE_OUTPUT"
echo "$GRAPHQL_METADATA_OUTPUT"
exit 1
fi
- name: Save server setup
uses: ./.github/workflows/actions/save-cache
@@ -170,7 +171,6 @@ jobs:
- 6379:6379
env:
NX_REJECT_UNKNOWN_LOCAL_CACHE: 0
NODE_ENV: test
steps:
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@v4
@@ -184,26 +184,15 @@ jobs:
echo "BILLING_STRIPE_API_KEY=test-api-key" >> .env.test
echo "BILLING_STRIPE_BASE_PLAN_PRODUCT_ID=test-base-plan-product-id" >> .env.test
echo "BILLING_STRIPE_WEBHOOK_SECRET=test-webhook-secret" >> .env.test
echo "BILLING_PLAN_REQUIRED_LINK=http://localhost:3001/stripe-redirection" >> .env.test
- name: Restore server setup
uses: ./.github/workflows/actions/restore-cache
with:
key: ${{ env.SERVER_SETUP_CACHE_KEY }}
- name: Server / Build
run: npx nx build twenty-server
- name: Build dependencies
run: |
npx nx build twenty-shared
npx nx build twenty-emails
- name: Server / Create Test DB
run: |
PGPASSWORD=postgres psql -h localhost -p 5432 -U postgres -d postgres -c 'CREATE DATABASE "test";'
- name: Server / Run Integration Tests
uses: ./.github/workflows/actions/nx-affected
with:
tag: scope:backend
tasks: 'test:integration'
configuration: 'with-db-reset'
tasks: 'test:integration:with-db-reset'
- name: Server / Upload reset-logs file
if: always()
uses: actions/upload-artifact@v4
@@ -30,6 +30,10 @@ jobs:
yq eval '.services.server.build.dockerfile = "./packages/twenty-docker/twenty/Dockerfile"' -i docker-compose.yml
yq eval '.services.server.restart = "no"' -i docker-compose.yml
yq eval 'del(.services.db.image)' -i docker-compose.yml
yq eval '.services.db.build.context = "../../"' -i docker-compose.yml
yq eval '.services.db.build.dockerfile = "./packages/twenty-docker/twenty-postgres-spilo/Dockerfile"' -i docker-compose.yml
echo "Setting up .env file..."
cp .env.example .env
echo "Generating secrets..."
+26
View File
@@ -0,0 +1,26 @@
name: CI Tinybird
on:
push:
branches:
- main
paths:
- 'package.json'
- 'packages/twenty-tinybird/**'
pull_request:
paths:
- 'package.json'
- 'packages/twenty-tinybird/**'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
ci:
uses: tinybirdco/ci/.github/workflows/ci.yml@main
with:
data_project_dir: packages/twenty-tinybird
secrets:
tb_admin_token: ${{ secrets.TB_ADMIN_TOKEN }}
tb_host: https://api.eu-central-1.aws.tinybird.co
+2 -6
View File
@@ -20,7 +20,7 @@ jobs:
website-build:
needs: changed-files-check
if: needs.changed-files-check.outputs.any_changed == 'true'
timeout-minutes: 10
timeout-minutes: 3
runs-on: ubuntu-latest
services:
postgres:
@@ -56,13 +56,9 @@ jobs:
run: npx nx build twenty-website
env:
DATABASE_PG_URL: postgres://postgres:postgres@localhost:5432/default
KEYSTATIC_GITHUB_CLIENT_ID: xxx
KEYSTATIC_GITHUB_CLIENT_SECRET: xxx
KEYSTATIC_SECRET: xxx
NEXT_PUBLIC_KEYSTATIC_GITHUB_APP_SLUG: xxx
ci-website-status-check:
if: always() && !cancelled()
timeout-minutes: 10
timeout-minutes: 1
runs-on: ubuntu-latest
needs: [changed-files-check, website-build]
steps:
+19 -29
View File
@@ -1,7 +1,7 @@
# Pull down translations from Crowdin every two hours or when triggered manually.
# When force_pull input is true, translations will be pulled regardless of compilation status.
name: 'Pull translations from Crowdin'
name: 'Pull translations'
on:
schedule:
@@ -53,22 +53,15 @@ jobs:
# Strict mode fails if there are missing translations.
- name: Compile translations
id: compile_translations_strict
id: compile_translations
run: |
npx nx run twenty-server:lingui:compile --strict
npx nx run twenty-emails:lingui:compile --strict
npx nx run twenty-front:lingui:compile --strict
continue-on-error: true
- name: Stash any changes before pulling translations
run: |
git config --global user.name 'github-actions'
git config --global user.email 'github-actions@twenty.com'
git add .
git stash
- name: Pull translations from Crowdin
if: inputs.force_pull || steps.compile_translations_strict.outcome == 'failure'
if: inputs.force_pull || steps.compile_translations.outcome == 'failure'
uses: crowdin/github-action@v2
with:
upload_sources: false
@@ -76,6 +69,8 @@ jobs:
download_translations: true
export_only_approved: false
localization_branch_name: i18n
commit_message: 'i18n - translations'
pull_request_title: 'i18n - translations'
base_url: 'https://twenty.api.crowdin.com'
auto_approve_imported: false
import_eq_suggestions: false
@@ -84,48 +79,43 @@ jobs:
skip_untranslated_strings: false
skip_untranslated_files: false
push_translations: true
create_pull_request: false
skip_ref_checkout: true
create_pull_request: true
skip_ref_checkout: false
dryrun_action: false
github_base_url: 'github.com'
github_user_name: 'Crowdin Bot'
github_user_email: 'support+bot@crowdin.com'
env:
GITHUB_TOKEN: ${{ github.token }}
CROWDIN_PROJECT_ID: '1'
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
# As the files are extracted from a Docker container, they belong to root:root
# We need to fix this before the next steps
- name: Fix file permissions
run: sudo chown -R runner:docker .
- name: Compile translations
id: compile_translations
if: inputs.force_pull || steps.compile_translations_strict.outcome == 'failure'
if: inputs.force_pull || steps.compile_translations.outcome == 'failure'
run: |
npx nx run twenty-server:lingui:compile
npx nx run twenty-emails:lingui:compile
npx nx run twenty-front:lingui:compile
git status
- name: Check and commit compiled files
id: check_changes
run: |
git config --global user.name 'github-actions'
git config --global user.email 'github-actions@twenty.com'
git add .
if ! git diff --staged --quiet --exit-code; then
git commit -m "chore: compile translations"
git commit -m "chore: compile translations [skip ci]"
echo "changes_detected=true" >> $GITHUB_OUTPUT
else
echo "changes_detected=false" >> $GITHUB_OUTPUT
fi
- name: Push changes
if: steps.compile_translations.outputs.changes_detected == 'true'
if: steps.check_changes.outputs.changes_detected == 'true'
run: git push origin HEAD:i18n
- name: Create pull request
if: steps.compile_translations.outputs.changes_detected == 'true'
run: |
if git diff --name-only origin/main..HEAD | grep -q .; then
gh pr create -B main -H i18n --title 'i18n - translations' --body 'Created by Github action' || true
else
echo "No file differences between branches, skipping PR creation"
fi
if: steps.check_changes.outputs.changes_detected == 'true'
run: gh pr create -B main -H i18n --title 'i18n - translations' --body 'Created by Github action' || true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+15 -33
View File
@@ -1,4 +1,4 @@
name: 'Push translations to Crowdin'
name: 'Extract translations when there is a push to main, and upload them to Crowdin'
on:
workflow_dispatch:
@@ -16,7 +16,6 @@ jobs:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -41,8 +40,14 @@ jobs:
npx nx run twenty-emails:lingui:extract
npx nx run twenty-front:lingui:extract
- name: Check and commit extracted files
id: check_extract_changes
- name: Compile translations
run: |
npx nx run twenty-server:lingui:compile
npx nx run twenty-emails:lingui:compile
npx nx run twenty-front:lingui:compile
- name: Check and commit any files created
id: check_changes
run: |
git config --global user.name 'github-actions'
git config --global user.email 'github-actions@twenty.com'
@@ -54,31 +59,12 @@ jobs:
echo "changes_detected=false" >> $GITHUB_OUTPUT
fi
- name: Compile translations
run: |
npx nx run twenty-server:lingui:compile
npx nx run twenty-emails:lingui:compile
npx nx run twenty-front:lingui:compile
- name: Check and commit compiled files
id: check_compile_changes
run: |
git config --global user.name 'github-actions'
git config --global user.email 'github-actions@twenty.com'
git add .
if ! git diff --staged --quiet --exit-code; then
git commit -m "chore: compile translations"
echo "changes_detected=true" >> $GITHUB_OUTPUT
else
echo "changes_detected=false" >> $GITHUB_OUTPUT
fi
- name: Push changes and create remote branch if needed
if: steps.check_extract_changes.outputs.changes_detected == 'true' || steps.check_compile_changes.outputs.changes_detected == 'true'
if: steps.check_changes.outputs.changes_detected == 'true'
run: git push origin HEAD:i18n
- name: Upload missing translations
if: steps.check_extract_changes.outputs.changes_detected == 'true'
if: steps.check_changes.outputs.changes_detected == 'true'
uses: crowdin/github-action@v2
with:
upload_sources: true
@@ -92,13 +78,9 @@ jobs:
# Visit https://crowdin.com/settings#api-key to create this token
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
- name: Create a pull request
if: steps.check_extract_changes.outputs.changes_detected == 'true' || steps.check_compile_changes.outputs.changes_detected == 'true'
run: |
if git diff --name-only origin/main..HEAD | grep -q .; then
gh pr create -B main -H i18n --title 'i18n - translations' --body 'Created by Github action' || true
else
echo "No file differences between branches, skipping PR creation"
fi
if: steps.check_changes.outputs.changes_detected == 'true'
run: gh pr create -B main -H i18n --title 'i18n - translations' --body 'Created by Github action' || true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+1 -1
View File
@@ -1 +1 @@
18.17.1
18.17.1
+1 -1
View File
@@ -64,7 +64,7 @@
{
"type": "node",
"request": "launch",
"name": "twenty-server - debug integration test file (to launch with test file open)",
"name": "Debug Integration Test File",
"runtimeExecutable": "npx",
"runtimeArgs": [
"nx",
-4
View File
@@ -1,7 +1,3 @@
enableConstraintsChecks: true
enableHardenedMode: true
enableInlineHunks: true
nodeLinker: node-modules
+8 -6
View File
@@ -1,5 +1,4 @@
{
"private": true,
"dependencies": {
"@air/react-drag-to-select": "^5.0.8",
"@apollo/client": "^3.7.17",
@@ -25,6 +24,8 @@
"@jsdevtools/rehype-toc": "^3.0.2",
"@linaria/core": "^6.2.0",
"@linaria/react": "^6.2.1",
"@lingui/core": "^5.1.2",
"@lingui/react": "^5.1.2",
"@mdx-js/react": "^3.0.0",
"@microsoft/microsoft-graph-client": "^3.0.7",
"@nestjs/apollo": "^11.0.5",
@@ -38,7 +39,7 @@
"@nestjs/passport": "^9.0.3",
"@nestjs/platform-express": "^9.0.0",
"@nestjs/serve-static": "^4.0.1",
"@nestjs/terminus": "^11.0.0",
"@nestjs/terminus": "^9.2.2",
"@nestjs/typeorm": "^10.0.0",
"@nx/eslint-plugin": "^17.2.8",
"@octokit/graphql": "^7.0.2",
@@ -52,7 +53,7 @@
"@sniptt/guards": "^0.2.0",
"@stoplight/elements": "^8.0.5",
"@swc/jest": "^0.2.29",
"@tabler/icons-react": "^3.31.0",
"@tabler/icons-react": "^2.44.0",
"@types/dompurify": "^3.0.5",
"@types/facepaint": "^1.2.5",
"@types/lodash.camelcase": "^4.3.7",
@@ -206,6 +207,9 @@
"@graphql-codegen/typescript": "^3.0.4",
"@graphql-codegen/typescript-operations": "^3.0.4",
"@graphql-codegen/typescript-react-apollo": "^3.3.7",
"@lingui/cli": "^5.1.2",
"@lingui/swc-plugin": "^5.1.0",
"@lingui/vite-plugin": "^5.1.2",
"@microsoft/microsoft-graph-types": "^2.40.0",
"@nestjs/cli": "^9.0.0",
"@nestjs/schematics": "^9.0.0",
@@ -289,7 +293,6 @@
"@typescript-eslint/utils": "6.21.0",
"@vitejs/plugin-react-swc": "^3.5.0",
"@vitest/ui": "1.4.0",
"@yarnpkg/types": "^4.0.0",
"chromatic": "^6.18.0",
"concurrently": "^8.2.2",
"cross-var": "^1.1.0",
@@ -355,8 +358,7 @@
"graphql": "16.8.0",
"type-fest": "4.10.1",
"typescript": "5.3.3",
"prosemirror-model": "1.23.0",
"yjs": "13.6.18"
"prosemirror-model": "1.23.0"
},
"version": "0.2.1",
"nx": {},
@@ -1,4 +1,5 @@
import { isDefined } from 'twenty-shared/utils';
import { isDefined } from 'twenty-shared';
// Open options page programmatically in a new tab.
// chrome.runtime.onInstalled.addListener((details) => {
// if (details.reason === 'install') {
@@ -1,4 +1,5 @@
import { isDefined } from 'twenty-shared/utils';
import { isDefined } from 'twenty-shared';
interface CustomDiv extends HTMLDivElement {
onClickHandler: (newHandler: () => void) => void;
}
@@ -1,10 +1,10 @@
import { isDefined } from 'twenty-shared';
import { createDefaultButton } from '~/contentScript/createButton';
import changeSidePanelUrl from '~/contentScript/utils/changeSidepanelUrl';
import extractCompanyLinkedinLink from '~/contentScript/utils/extractCompanyLinkedinLink';
import extractDomain from '~/contentScript/utils/extractDomain';
import { createCompany, fetchCompany } from '~/db/company.db';
import { CompanyInput } from '~/db/types/company.types';
import { isDefined } from 'twenty-shared/utils';
export const checkIfCompanyExists = async () => {
const { tab: activeTab } = await chrome.runtime.sendMessage({
@@ -1,9 +1,9 @@
import { isDefined } from 'twenty-shared';
import { createDefaultButton } from '~/contentScript/createButton';
import changeSidePanelUrl from '~/contentScript/utils/changeSidepanelUrl';
import extractFirstAndLastName from '~/contentScript/utils/extractFirstAndLastName';
import { createPerson, fetchPerson } from '~/db/person.db';
import { PersonInput } from '~/db/types/person.types';
import { isDefined } from 'twenty-shared/utils';
export const checkIfPersonExists = async () => {
const { tab: activeTab } = await chrome.runtime.sendMessage({
@@ -1,6 +1,6 @@
import { isDefined } from 'twenty-shared';
import { insertButtonForCompany } from '~/contentScript/extractCompanyProfile';
import { insertButtonForPerson } from '~/contentScript/extractPersonProfile';
import { isDefined } from 'twenty-shared/utils';
// Inject buttons into the DOM when SPA is reloaded on the resource url.
// e.g. reload the page when on https://www.linkedin.com/in/mabdullahabaid/
@@ -1,4 +1,5 @@
import { isDefined } from 'twenty-shared/utils';
import { isDefined } from 'twenty-shared';
const btn = document.getElementById('twenty-settings-btn');
if (!isDefined(btn)) {
const div = document.createElement('div');
@@ -1,4 +1,5 @@
import { isDefined } from 'twenty-shared/utils';
import { isDefined } from 'twenty-shared';
const changeSidePanelUrl = async (url: string) => {
if (isDefined(url)) {
chrome.storage.local.set({ navigateSidepanel: 'sidepanel' });
@@ -1,4 +1,7 @@
import { isDefined } from 'twenty-shared/utils';
// Extract "https://www.linkedin.com/company/twenty/" from any of the following urls, which the user can visit while on the company page.
import { isDefined } from 'twenty-shared';
// "https://www.linkedin.com/company/twenty/" "https://www.linkedin.com/company/twenty/about/" "https://www.linkedin.com/company/twenty/people/".
const extractCompanyLinkedinLink = (activeTabUrl: string) => {
// Regular expression to match the company ID
@@ -1,3 +1,4 @@
import { isDefined } from 'twenty-shared';
import {
ExchangeAuthCodeInput,
ExchangeAuthCodeResponse,
@@ -5,7 +6,6 @@ import {
} from '~/db/types/auth.types';
import { EXCHANGE_AUTHORIZATION_CODE } from '~/graphql/auth/mutations';
import { callMutation } from '~/utils/requestDb';
import { isDefined } from 'twenty-shared/utils';
export const exchangeAuthorizationCode = async (
exchangeAuthCodeInput: ExchangeAuthCodeInput,
@@ -1,3 +1,4 @@
import { isDefined } from 'twenty-shared';
import {
CompanyInput,
CreateCompanyResponse,
@@ -8,7 +9,6 @@ import { CREATE_COMPANY } from '~/graphql/company/mutations';
import { FIND_COMPANY } from '~/graphql/company/queries';
import { callMutation, callQuery } from '../utils/requestDb';
import { isDefined } from 'twenty-shared/utils';
export const fetchCompany = async (
companyfilerInput: CompanyFilterInput,
@@ -1,3 +1,4 @@
import { isDefined } from 'twenty-shared';
import {
CreatePersonResponse,
FindPersonResponse,
@@ -8,7 +9,6 @@ import { CREATE_PERSON } from '~/graphql/person/mutations';
import { FIND_PERSON } from '~/graphql/person/queries';
import { callMutation, callQuery } from '../utils/requestDb';
import { isDefined } from 'twenty-shared/utils';
export const fetchPerson = async (
personFilterData: PersonFilterInput,
@@ -1,7 +1,8 @@
import { ApolloClient, InMemoryCache } from '@apollo/client';
import { isDefined } from 'twenty-shared';
import { Tokens } from '~/db/types/auth.types';
import { RENEW_TOKEN } from '~/graphql/auth/mutations';
import { isDefined } from 'twenty-shared/utils';
export const renewToken = async (
appToken: string,
@@ -1,7 +1,8 @@
import { useEffect, useState } from 'react';
import { isDefined } from 'twenty-shared';
import Settings from '~/options/Settings';
import Sidepanel from '~/options/Sidepanel';
import { isDefined } from 'twenty-shared/utils';
const App = () => {
const [currentScreen, setCurrentScreen] = useState('');
@@ -3,8 +3,8 @@ import { useEffect, useState } from 'react';
import { MainButton } from '@/ui/input/button/MainButton';
import { TextInput } from '@/ui/input/components/TextInput';
import { isDefined } from 'twenty-shared';
import { clearStore } from '~/utils/apolloClient';
import { isDefined } from 'twenty-shared/utils';
const StyledWrapper = styled.div`
align-items: center;
@@ -2,7 +2,7 @@ import styled from '@emotion/styled';
import { useCallback, useEffect, useRef, useState } from 'react';
import { MainButton } from '@/ui/input/button/MainButton';
import { isDefined } from 'twenty-shared/utils';
import { isDefined } from 'twenty-shared';
const StyledIframe = styled.iframe`
display: block;
@@ -42,6 +42,7 @@ const StyledInput = styled.input`
flex: 1;
border: none;
outline: none;
font-family: Arial, sans-serif;
font-size: 14px;
&::placeholder {
@@ -1,7 +1,8 @@
import styled from '@emotion/styled';
import { motion } from 'framer-motion';
import { useEffect, useState } from 'react';
import { isDefined } from 'twenty-shared/utils';
import { isDefined } from 'twenty-shared';
export type ToggleSize = 'small' | 'medium';
@@ -1,7 +1,8 @@
import { ApolloClient, from, HttpLink, InMemoryCache } from '@apollo/client';
import { setContext } from '@apollo/client/link/context';
import { onError } from '@apollo/client/link/error';
import { isDefined } from 'twenty-shared/utils';
import { isDefined } from 'twenty-shared';
export const clearStore = () => {
chrome.storage.local.remove([
@@ -1,7 +1,8 @@
import { OperationVariables } from '@apollo/client';
import { DocumentNode } from 'graphql';
import { isDefined } from 'twenty-shared';
import getApolloClient from '~/utils/apolloClient';
import { isDefined } from 'twenty-shared/utils';
export const callQuery = async <T>(
query: DocumentNode,
+5 -18
View File
@@ -1,27 +1,14 @@
# Makefile for building Twenty CRM docker images.
# Set the tag and/or target build platform using make command-line variables assignments.
#
# Optional make variables:
# PLATFORM - defaults to 'linux/amd64'
# TAG - defaults to 'latest'
#
# Example: make
# Example: make PLATFORM=linux/aarch64 TAG=my-tag
PLATFORM ?= linux/amd64
TAG ?= latest
prod-build:
@cd ../.. && docker build -f ./packages/twenty-docker/twenty/Dockerfile --platform $(PLATFORM) --tag twenty:$(TAG) . && cd -
@cd ../.. && docker build -f ./packages/twenty-docker/twenty/Dockerfile --tag twenty . && cd -
prod-run:
@docker run -d -p 3000:3000 --name twenty twenty:$(TAG)
@docker run -d -p 3000:3000 --name twenty twenty
prod-postgres-run:
@docker run -d -p 5432:5432 -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres --name twenty-postgres twenty-postgres:$(TAG)
@docker run -d -p 5432:5432 -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres --name twenty-postgres twenty-postgres
prod-website-build:
@cd ../.. && docker build -f ./packages/twenty-docker/twenty-website/Dockerfile --platform $(PLATFORM) --tag twenty-website:$(TAG) . && cd -
@cd ../.. && docker build -f ./packages/twenty-docker/twenty-website/Dockerfile --tag twenty-website . && cd -
prod-website-run:
@docker run -d -p 3000:3000 --name twenty-website twenty-website:$(TAG)
@docker run -d -p 3000:3000 --name twenty-website twenty-website
+8 -58
View File
@@ -30,32 +30,7 @@ services:
STORAGE_S3_NAME: ${STORAGE_S3_NAME}
STORAGE_S3_ENDPOINT: ${STORAGE_S3_ENDPOINT}
APP_SECRET: ${APP_SECRET:-replace_me_with_a_random_string}
# MESSAGING_PROVIDER_GMAIL_ENABLED: ${MESSAGING_PROVIDER_GMAIL_ENABLED}
# CALENDAR_PROVIDER_GOOGLE_ENABLED: ${CALENDAR_PROVIDER_GOOGLE_ENABLED}
# AUTH_GOOGLE_CLIENT_ID: ${AUTH_GOOGLE_CLIENT_ID}
# AUTH_GOOGLE_CLIENT_SECRET: ${AUTH_GOOGLE_CLIENT_SECRET}
# AUTH_GOOGLE_CALLBACK_URL: ${AUTH_GOOGLE_CALLBACK_URL}
# AUTH_GOOGLE_APIS_CALLBACK_URL: ${AUTH_GOOGLE_APIS_CALLBACK_URL}
# CALENDAR_PROVIDER_MICROSOFT_ENABLED: ${CALENDAR_PROVIDER_MICROSOFT_ENABLED}
# MESSAGING_PROVIDER_MICROSOFT_ENABLED: ${MESSAGING_PROVIDER_MICROSOFT_ENABLED}
# AUTH_MICROSOFT_ENABLED: ${AUTH_MICROSOFT_ENABLED}
# AUTH_MICROSOFT_CLIENT_ID: ${AUTH_MICROSOFT_CLIENT_ID}
# AUTH_MICROSOFT_CLIENT_SECRET: ${AUTH_MICROSOFT_CLIENT_SECRET}
# AUTH_MICROSOFT_CALLBACK_URL: ${AUTH_MICROSOFT_CALLBACK_URL}
# AUTH_MICROSOFT_APIS_CALLBACK_URL: ${AUTH_MICROSOFT_APIS_CALLBACK_URL}
# EMAIL_FROM_ADDRESS: ${EMAIL_FROM_ADDRESS:-contact@yourdomain.com}
# EMAIL_FROM_NAME: ${EMAIL_FROM_NAME:-"John from YourDomain"}
# EMAIL_SYSTEM_ADDRESS: ${EMAIL_SYSTEM_ADDRESS:-system@yourdomain.com}
# EMAIL_DRIVER: ${EMAIL_DRIVER:-smtp}
# EMAIL_SMTP_HOST: ${EMAIL_SMTP_HOST:-smtp.gmail.com}
# EMAIL_SMTP_PORT: ${EMAIL_SMTP_PORT:-465}
# EMAIL_SMTP_USER: ${EMAIL_SMTP_USER:-}
# EMAIL_SMTP_PASSWORD: ${EMAIL_SMTP_PASSWORD:-}
APP_SECRET: ${APP_SECRET}
depends_on:
change-vol-ownership:
condition: service_completed_successfully
@@ -70,8 +45,6 @@ services:
worker:
image: twentycrm/twenty:${TAG:-latest}
volumes:
- server-local-data:/app/packages/twenty-server/${STORAGE_LOCAL_PATH:-.local-storage}
command: ["yarn", "worker:prod"]
environment:
PG_DATABASE_URL: postgres://${PG_DATABASE_USER:-postgres}:${PG_DATABASE_PASSWORD:-postgres}@${PG_DATABASE_HOST:-db}:${PG_DATABASE_PORT:-5432}/default
@@ -84,32 +57,7 @@ services:
STORAGE_S3_NAME: ${STORAGE_S3_NAME}
STORAGE_S3_ENDPOINT: ${STORAGE_S3_ENDPOINT}
APP_SECRET: ${APP_SECRET:-replace_me_with_a_random_string}
# MESSAGING_PROVIDER_GMAIL_ENABLED: ${MESSAGING_PROVIDER_GMAIL_ENABLED}
# CALENDAR_PROVIDER_GOOGLE_ENABLED: ${CALENDAR_PROVIDER_GOOGLE_ENABLED}
# AUTH_GOOGLE_CLIENT_ID: ${AUTH_GOOGLE_CLIENT_ID}
# AUTH_GOOGLE_CLIENT_SECRET: ${AUTH_GOOGLE_CLIENT_SECRET}
# AUTH_GOOGLE_CALLBACK_URL: ${AUTH_GOOGLE_CALLBACK_URL}
# AUTH_GOOGLE_APIS_CALLBACK_URL: ${AUTH_GOOGLE_APIS_CALLBACK_URL}
# CALENDAR_PROVIDER_MICROSOFT_ENABLED: ${CALENDAR_PROVIDER_MICROSOFT_ENABLED}
# MESSAGING_PROVIDER_MICROSOFT_ENABLED: ${MESSAGING_PROVIDER_MICROSOFT_ENABLED}
# AUTH_MICROSOFT_ENABLED: ${AUTH_MICROSOFT_ENABLED}
# AUTH_MICROSOFT_CLIENT_ID: ${AUTH_MICROSOFT_CLIENT_ID}
# AUTH_MICROSOFT_CLIENT_SECRET: ${AUTH_MICROSOFT_CLIENT_SECRET}
# AUTH_MICROSOFT_CALLBACK_URL: ${AUTH_MICROSOFT_CALLBACK_URL}
# AUTH_MICROSOFT_APIS_CALLBACK_URL: ${AUTH_MICROSOFT_APIS_CALLBACK_URL}
# EMAIL_FROM_ADDRESS: ${EMAIL_FROM_ADDRESS:-contact@yourdomain.com}
# EMAIL_FROM_NAME: ${EMAIL_FROM_NAME:-"John from YourDomain"}
# EMAIL_SYSTEM_ADDRESS: ${EMAIL_SYSTEM_ADDRESS:-system@yourdomain.com}
# EMAIL_DRIVER: ${EMAIL_DRIVER:-smtp}
# EMAIL_SMTP_HOST: ${EMAIL_SMTP_HOST:-smtp.gmail.com}
# EMAIL_SMTP_PORT: ${EMAIL_SMTP_PORT:-465}
# EMAIL_SMTP_USER: ${EMAIL_SMTP_USER:-}
# EMAIL_SMTP_PASSWORD: ${EMAIL_SMTP_PASSWORD:-}
APP_SECRET: ${APP_SECRET}
depends_on:
db:
condition: service_healthy
@@ -118,12 +66,14 @@ services:
restart: always
db:
image: postgres:16
image: twentycrm/twenty-postgres-spilo:${TAG:-latest}
volumes:
- db-data:/var/lib/postgresql/data
- db-data:/home/postgres/pgdata
environment:
POSTGRES_USER: ${PG_DATABASE_USER:-postgres}
POSTGRES_PASSWORD: ${PG_DATABASE_PASSWORD:-postgres}
PGUSER_SUPERUSER: ${PG_DATABASE_USER:-postgres}
PGPASSWORD_SUPERUSER: ${PG_DATABASE_PASSWORD:-postgres}
ALLOW_NOSSL: "true"
SPILO_PROVIDER: "local"
healthcheck:
test: pg_isready -U ${PG_DATABASE_USER:-postgres} -h localhost -d postgres
interval: 5s
@@ -12,11 +12,6 @@ COPY ./packages/twenty-website/package.json /app/packages/twenty-website/package
RUN yarn
ENV KEYSTATIC_GITHUB_CLIENT_ID="<fake build value>"
ENV KEYSTATIC_GITHUB_CLIENT_SECRET="<fake build value>"
ENV KEYSTATIC_SECRET="<fake build value>"
ENV NEXT_PUBLIC_KEYSTATIC_GITHUB_APP_SLUG="<fake build value>"
COPY ./packages/twenty-website /app/packages/twenty-website
RUN npx nx build twenty-website
@@ -31,9 +26,4 @@ WORKDIR /app/packages/twenty-website
LABEL org.opencontainers.image.source=https://github.com/twentyhq/twenty
LABEL org.opencontainers.image.description="This image provides a consistent and reproducible environment for the website."
RUN chown -R 1000 /app
# Use non root user with uid 1000
USER 1000
CMD ["/bin/sh", "-c", "npx nx start"]
-5
View File
@@ -7,13 +7,11 @@ WORKDIR /app
COPY ./package.json ./yarn.lock ./.yarnrc.yml ./tsconfig.base.json ./nx.json /app/
COPY ./.yarn/releases /app/.yarn/releases
COPY ./.prettierrc /app/
COPY ./packages/twenty-emails/package.json /app/packages/twenty-emails/
COPY ./packages/twenty-server/package.json /app/packages/twenty-server/
COPY ./packages/twenty-server/patches /app/packages/twenty-server/patches
COPY ./packages/twenty-ui/package.json /app/packages/twenty-ui/
COPY ./packages/twenty-shared/package.json /app/packages/twenty-shared/
COPY ./packages/twenty-shared/patches /app/packages/twenty-shared/patches
COPY ./packages/twenty-front/package.json /app/packages/twenty-front/
# Install all dependencies
@@ -68,9 +66,6 @@ ENV REACT_APP_SERVER_BASE_URL $REACT_APP_SERVER_BASE_URL
ARG SENTRY_RELEASE
ENV SENTRY_RELEASE $SENTRY_RELEASE
ARG APP_VERSION
ENV APP_VERSION $APP_VERSION
# Copy built applications from previous stages
COPY --chown=1000 --from=twenty-server-build /app /app
COPY --chown=1000 --from=twenty-server-build /app/packages/twenty-server /app/packages/twenty-server
+2 -3
View File
@@ -10,9 +10,8 @@ if [ "${DISABLE_DB_MIGRATIONS}" != "true" ] && [ ! -f /app/docker-data/db_status
PGPASS=$(echo $PG_DATABASE_URL | awk -F ':' '{print $3}' | awk -F '@' '{print $1}')
PGHOST=$(echo $PG_DATABASE_URL | awk -F '@' '{print $2}' | awk -F ':' '{print $1}')
PGPORT=$(echo $PG_DATABASE_URL | awk -F ':' '{print $4}' | awk -F '/' '{print $1}')
PGDATABASE=$(echo $PG_DATABASE_URL | awk -F ':' '{print $4}' | awk -F '/' '{print $2}')
PGPASSWORD=${PGPASS} psql -h ${PGHOST} -p ${PGPORT} -U ${PGUSER} -d postgres -tc "SELECT 1 FROM pg_database WHERE datname = '${PGDATABASE}'" | grep -q 1 || \
PGPASSWORD=${PGPASS} psql -h ${PGHOST} -p ${PGPORT} -U ${PGUSER} -d postgres -c "CREATE DATABASE \"${PGDATABASE}\""
PGPASSWORD=${PGPASS} psql -h ${PGHOST} -p ${PGPORT} -U ${PGUSER} -d postgres -tc "SELECT 1 FROM pg_database WHERE datname = 'default'" | grep -q 1 || \
PGPASSWORD=${PGPASS} psql -h ${PGHOST} -p ${PGPORT} -U ${PGUSER} -d postgres -c "CREATE DATABASE \"default\""
# Run setup and migration scripts
NODE_OPTIONS="--max-old-space-size=1500" tsx ./scripts/setup-db.ts
+15 -1
View File
@@ -3,4 +3,18 @@ FRONTEND_BASE_URL=http://localhost:3001
BACKEND_BASE_URL=http://localhost:3000
DEFAULT_LOGIN=tim@apple.dev
DEFAULT_PASSWORD=Applecar2025
WEBSITE_URL=https://twenty.com
WEBSITE_URL=https://twenty.com
# === DO NOT USE, WORK IN PROGRESS ===
# This URL must have trailing forward slash as all REST API endpoints have object after it
# Documentation for REST API: https://twenty.com/developers/rest-api/core#/
# REST_API_BASE_URL=http://localhost:3000/rest/
# Documentation for GraphQL API: https://twenty.com/developers/graphql/core
# GRAPHQL_BASE_URL=http://localhost:3000/graphql
# Without this key, all API tests will fail, to generate this key
# Log in to Twenty workspace, go to Settings > Developers, generate new key and paste it here
# In order to use it, header Authorization: Bearer token must be used
# This key works for both REST and GraphQL API
# API_DEV_KEY=fill_with_proper_key
@@ -17,7 +17,7 @@ export class WorkflowVisualizerPage {
readonly deactivateWorkflowButton: Locator;
readonly addTriggerButton: Locator;
readonly commandMenu: Locator;
readonly workflowNameLabel: Locator;
readonly workflowNameButton: Locator;
readonly triggerNode: Locator;
readonly background: Locator;
readonly useAsDraftButton: Locator;
@@ -51,7 +51,7 @@ export class WorkflowVisualizerPage {
'record-created': 'Record is Created',
'record-updated': 'Record is Updated',
'record-deleted': 'Record is Deleted',
manual: 'Launch manually',
manual: 'Manual Trigger',
};
constructor({ page, workflowName }: { page: Page; workflowName: string }) {
@@ -68,9 +68,9 @@ export class WorkflowVisualizerPage {
});
this.addTriggerButton = page.getByText('Add a Trigger');
this.commandMenu = page.getByTestId('command-menu');
this.workflowNameLabel = page
.getByTestId('top-bar-title')
.getByText(this.workflowName);
this.workflowNameButton = page.getByRole('button', {
name: this.workflowName,
});
this.triggerNode = this.#page.getByTestId('rf__node-trigger');
this.background = page.locator('.react-flow__pane');
this.useAsDraftButton = page.getByRole('button', { name: 'Use as draft' });
@@ -100,23 +100,15 @@ export class WorkflowVisualizerPage {
}
async waitForWorkflowVisualizerLoad() {
await expect(this.workflowNameLabel).toBeVisible();
await expect(this.workflowNameButton).toBeVisible();
}
async goToWorkflowVisualizerPage() {
await this.#page.goto(`/`);
await Promise.all([
this.#page.goto(`/object/workflow/${this.workflowId}`),
const workflowsLink = this.#page.getByRole('link', { name: 'Workflows' });
await workflowsLink.click();
const workflowLink = this.#page
.getByTestId(`row-id-${this.workflowId}`)
.getByRole('link', { name: this.workflowName });
await workflowLink.click();
await this.waitForWorkflowVisualizerLoad();
this.waitForWorkflowVisualizerLoad(),
]);
}
async createInitialTrigger(trigger: WorkflowTriggerType) {
@@ -140,10 +132,8 @@ export class WorkflowVisualizerPage {
const actionToCreateOption = this.commandMenu.getByText(actionName);
await actionToCreateOption.click();
const createWorkflowStepResponse = await this.#page.waitForResponse(
(response) => {
const [createWorkflowStepResponse] = await Promise.all([
this.#page.waitForResponse((response) => {
if (!response.url().endsWith('/graphql')) {
return false;
}
@@ -151,14 +141,19 @@ export class WorkflowVisualizerPage {
const requestBody = response.request().postDataJSON();
return requestBody.operationName === 'CreateWorkflowVersionStep';
},
);
}),
actionToCreateOption.click(),
]);
const createWorkflowStepResponseBody =
await createWorkflowStepResponse.json();
const createdStepId =
createWorkflowStepResponseBody.data.createWorkflowVersionStep.id;
await expect(
this.#page.getByTestId('command-menu').getByRole('textbox').first(),
).toHaveValue(createdActionName);
const createdActionNode = this.#page
.locator('.react-flow__node.selected')
.getByText(createdActionName);
@@ -236,16 +231,6 @@ export class WorkflowVisualizerPage {
this.getDeleteNodeButton(this.triggerNode).click(),
]);
}
async closeSidePanel() {
const closeButton = this.#page.getByRole('button', {
name: 'Close command menu',
});
await closeButton.click();
await expect(this.#page.getByTestId('command-menu')).not.toBeVisible();
}
}
export const test = base.extend<{ workflowVisualizer: WorkflowVisualizerPage }>(
@@ -25,7 +25,7 @@ export const test = base.extend<{ screenshotHook: void }>({
),
});
},
{ auto: true },
{ auto: true }, // automatic fixture runs with every test
],
});
@@ -44,7 +44,7 @@ export class InsertFieldData {
'//label[contains(., "POST CODE")]/../div[last()]/input',
);
this.countrySelect = page.locator(
'//span[contains(., "COUNTRY")]/../div[last()]/div',
'//span[contains(., "COUNTRY")]/../div[last()]/input',
);
this.arrayValueInput = page.locator("//input[@placeholder='Enter value']");
this.arrayAddValueButton = page.locator(
@@ -1,55 +1,5 @@
import { Locator, Page } from '@playwright/test';
export class StripePage {
private readonly cardNumberInput: Locator;
private readonly cardExpiryInput: Locator;
private readonly cardCvcInput: Locator;
private readonly cardholderNameInput: Locator;
private readonly startTrialButton: Locator;
private readonly cancelSubscriptionButton: Locator;
private readonly confirmButton: Locator;
private readonly returnToTwentyLink: Locator;
constructor(public readonly page: Page) {
this.cardNumberInput = page.getByPlaceholder('1234 1234 1234 1234');
this.cardExpiryInput = page.getByPlaceholder('MM / YY');
this.cardCvcInput = page.getByPlaceholder('CVC');
this.cardholderNameInput = page.getByPlaceholder('Full name on card');
this.startTrialButton = page.getByTestId('hosted-payment-submit-button');
this.cancelSubscriptionButton = page.locator(
'a[data-test="cancel-subscription"]',
);
this.confirmButton = page.getByTestId('confirm');
this.returnToTwentyLink = page.getByTestId('return-to-business-link');
}
async fillCardNumber(number: string) {
await this.cardNumberInput.fill(number);
}
async fillCardExpiry(expiry: string) {
await this.cardExpiryInput.fill(expiry);
}
async fillCardCvc(cvc: string) {
await this.cardCvcInput.fill(cvc);
}
async fillCardholderName(name: string) {
await this.cardholderNameInput.fill(name);
}
async startTrial() {
await this.startTrialButton.click();
}
async clickCancelSubscription() {
await this.cancelSubscriptionButton.click();
await this.confirmButton.click();
await this.page.getByTestId('cancellation_reason_cancel').click();
}
async returnToTwenty() {
await this.returnToTwentyLink.click();
}
// TODO: implement all necessary methods (staging/sandbox page - does it differ anyhow from normal page?)
}
@@ -23,6 +23,7 @@ export class MainPage {
private readonly hiddenFieldsButton: Locator;
private readonly editFieldsButton: Locator;
private readonly importButton: Locator;
private readonly exportButton: Locator;
private readonly deletedRecordsButton: Locator;
private readonly createNewRecordButton: Locator;
private readonly addToFavoritesButton: Locator;
@@ -66,12 +67,16 @@ export class MainPage {
this.importButton = page
.getByTestId('tooltip')
.filter({ hasText: /^Import$/ });
this.exportButton = page
.getByTestId('tooltip')
.filter({ hasText: /^Export$/ });
this.deletedRecordsButton = page
.getByTestId('tooltip')
.filter({ hasText: /^Deleted */ });
this.createNewRecordButton = page.getByTestId('add-button');
this.addToFavoritesButton = page.getByText('Add to favorites');
this.deleteFromFavoritesButton = page.getByText('Delete from favorites');
this.exportBottomBarButton = page.getByText('Export');
this.deleteRecordsButton = page.getByText('Delete');
}
@@ -161,6 +166,10 @@ export class MainPage {
await this.importButton.click();
}
async clickExportButton() {
await this.exportButton.click();
}
async clickDeletedRecordsButton() {
await this.deletedRecordsButton.click();
}
@@ -6,7 +6,6 @@ export class AccountsSection {
private readonly addBlocklistField: Locator;
private readonly addBlocklistButton: Locator;
private readonly connectWithGoogleButton: Locator;
private readonly connectWithMicrosoftButton: Locator;
constructor(public readonly page: Page) {
this.page = page;
@@ -23,9 +22,6 @@ export class AccountsSection {
this.connectWithGoogleButton = page.getByRole('button', {
name: 'Connect with Google',
});
this.connectWithMicrosoftButton = page.getByRole('button', {
name: 'Connect with Microsoft',
});
}
async clickAddAccount() {
@@ -55,8 +51,4 @@ export class AccountsSection {
async linkGoogleAccount() {
await this.connectWithGoogleButton.click();
}
async linkMicrosoftAccount() {
await this.connectWithMicrosoftButton.click();
}
}
@@ -1,64 +0,0 @@
import { Locator, Page } from '@playwright/test';
export class APIsSection {
private readonly createAPIKeyButton: Locator;
private readonly regenerateAPIKeyButton: Locator;
private readonly nameOfAPIKeyInput: Locator;
private readonly expirationDateAPIKeySelect: Locator;
private readonly cancelButton: Locator;
private readonly saveButton: Locator;
private readonly deleteButton: Locator;
constructor(public readonly page: Page) {
this.page = page;
this.createAPIKeyButton = page.getByRole('link', {
name: 'Create API Key',
});
this.nameOfAPIKeyInput = page
.getByPlaceholder('E.g. backoffice integration')
.first();
this.expirationDateAPIKeySelect = page.locator(
'div[aria-controls="object-field-type-select-options"]',
);
this.regenerateAPIKeyButton = page.getByRole('button', {
name: 'Regenerate Key',
});
this.cancelButton = page.getByRole('button', { name: 'Cancel' });
this.saveButton = page.getByRole('button', { name: 'Save' });
this.deleteButton = page.getByRole('button', { name: 'Delete' });
}
async createAPIKey() {
await this.createAPIKeyButton.click();
}
async typeAPIKeyName(name: string) {
await this.nameOfAPIKeyInput.clear();
await this.nameOfAPIKeyInput.fill(name);
}
async selectAPIExpirationDate(date: string) {
await this.expirationDateAPIKeySelect.click();
await this.page.getByText(date, { exact: true }).click();
}
async regenerateAPIKey() {
await this.regenerateAPIKeyButton.click();
}
async deleteAPIKey() {
await this.deleteButton.click();
}
async clickCancelButton() {
await this.cancelButton.click();
}
async clickSaveButton() {
await this.saveButton.click();
}
async checkAPIKeyDetails(name: string) {
await this.page.locator(`//a/div[contains(.,'${name}')][first()]`).click();
}
}
@@ -0,0 +1,123 @@
import { Locator, Page } from '@playwright/test';
export class DevelopersSection {
private readonly readDocumentationButton: Locator;
private readonly createAPIKeyButton: Locator;
private readonly regenerateAPIKeyButton: Locator;
private readonly nameOfAPIKeyInput: Locator;
private readonly expirationDateAPIKeySelect: Locator;
private readonly createWebhookButton: Locator;
private readonly webhookURLInput: Locator;
private readonly webhookDescription: Locator;
private readonly webhookFilterObjectSelect: Locator;
private readonly webhookFilterActionSelect: Locator;
private readonly cancelButton: Locator;
private readonly saveButton: Locator;
private readonly deleteButton: Locator;
constructor(public readonly page: Page) {
this.page = page;
this.readDocumentationButton = page.getByRole('link', {
name: 'Read documentation',
});
this.createAPIKeyButton = page.getByRole('link', {
name: 'Create API Key',
});
this.createWebhookButton = page.getByRole('link', {
name: 'Create Webhook',
});
this.nameOfAPIKeyInput = page
.getByPlaceholder('E.g. backoffice integration')
.first();
this.expirationDateAPIKeySelect = page
.locator('div')
.filter({ hasText: /^Never$/ })
.nth(3); // good enough if expiration date will change only 1 time
this.regenerateAPIKeyButton = page.getByRole('button', {
name: 'Regenerate Key',
});
this.webhookURLInput = page.getByPlaceholder('URL');
this.webhookDescription = page.getByPlaceholder('Write a description');
this.webhookFilterObjectSelect = page
.locator('div')
.filter({ hasText: /^All Objects$/ })
.nth(3); // works only for first filter
this.webhookFilterActionSelect = page
.locator('div')
.filter({ hasText: /^All Actions$/ })
.nth(3); // works only for first filter
this.cancelButton = page.getByRole('button', { name: 'Cancel' });
this.saveButton = page.getByRole('button', { name: 'Save' });
this.deleteButton = page.getByRole('button', { name: 'Delete' });
}
async openDocumentation() {
await this.readDocumentationButton.click();
}
async createAPIKey() {
await this.createAPIKeyButton.click();
}
async typeAPIKeyName(name: string) {
await this.nameOfAPIKeyInput.clear();
await this.nameOfAPIKeyInput.fill(name);
}
async selectAPIExpirationDate(date: string) {
await this.expirationDateAPIKeySelect.click();
await this.page.getByText(date, { exact: true }).click();
}
async regenerateAPIKey() {
await this.regenerateAPIKeyButton.click();
}
async deleteAPIKey() {
await this.deleteButton.click();
}
async deleteWebhook() {
await this.deleteButton.click();
}
async createWebhook() {
await this.createWebhookButton.click();
}
async typeWebhookURL(url: string) {
await this.webhookURLInput.fill(url);
}
async typeWebhookDescription(description: string) {
await this.webhookDescription.fill(description);
}
async selectWebhookObject(object: string) {
// TODO: finish
}
async selectWebhookAction(action: string) {
// TODO: finish
}
async deleteWebhookFilter() {
// TODO: finish
}
async clickCancelButton() {
await this.cancelButton.click();
}
async clickSaveButton() {
await this.saveButton.click();
}
async checkAPIKeyDetails(name: string) {
await this.page.locator(`//a/div[contains(.,'${name}')][first()]`).click();
}
async checkWebhookDetails(name: string) {
await this.page.locator(`//a/div[contains(.,'${name}')][first()]`).click();
}
}
@@ -7,12 +7,11 @@ export class ExperienceSection {
private readonly dateFormatDropdown: Locator;
private readonly timeFormatDropdown: Locator;
private readonly searchInput: Locator;
private readonly languageDropdown: Locator;
constructor(public readonly page: Page) {
this.page = page;
this.lightThemeButton = page.locator('div[variant="Light"]').first();
this.darkThemeButton = page.locator('div[variant="Dark"]').first();
this.lightThemeButton = page.getByText('AaLight'); // it works
this.darkThemeButton = page.getByText('AaDark');
this.timezoneDropdown = page.locator(
'//span[contains(., "Time zone")]/../div/div/div',
);
@@ -23,9 +22,6 @@ export class ExperienceSection {
'//span[contains(., "Time format")]/../div/div/div',
);
this.searchInput = page.getByPlaceholder('Search');
this.languageDropdown = page.locator(
'//h2[contains(., "Language")]/../../../div/div/div',
);
}
async changeThemeToLight() {
@@ -56,9 +52,4 @@ export class ExperienceSection {
await this.timeFormatDropdown.click();
await this.page.getByText(timeFormat, { exact: true }).click();
}
async selectLanguage(language: string) {
await this.languageDropdown.click();
await this.page.getByText(language, { exact: true }).click();
}
}
@@ -0,0 +1,159 @@
import { Locator, Page } from '@playwright/test';
export class FunctionsSection {
private readonly newFunctionButton: Locator;
private readonly functionNameInput: Locator;
private readonly functionDescriptionInput: Locator;
private readonly editorTab: Locator;
private readonly codeEditorField: Locator;
private readonly resetButton: Locator;
private readonly publishButton: Locator;
private readonly testButton: Locator;
private readonly testTab: Locator;
private readonly runFunctionButton: Locator;
private readonly inputField: Locator;
private readonly settingsTab: Locator;
private readonly searchVariableInput: Locator;
private readonly addVariableButton: Locator;
private readonly nameVariableInput: Locator;
private readonly valueVariableInput: Locator;
private readonly cancelVariableButton: Locator;
private readonly saveVariableButton: Locator;
private readonly editVariableButton: Locator;
private readonly deleteVariableButton: Locator;
private readonly cancelButton: Locator;
private readonly saveButton: Locator;
private readonly deleteButton: Locator;
constructor(public readonly page: Page) {
this.newFunctionButton = page.getByRole('button', { name: 'New Function' });
this.functionNameInput = page.getByPlaceholder('Name');
this.functionDescriptionInput = page.getByPlaceholder('Description');
this.editorTab = page.getByTestId('tab-editor');
this.codeEditorField = page.getByTestId('dummyInput'); // TODO: fix
this.resetButton = page.getByRole('button', { name: 'Reset' });
this.publishButton = page.getByRole('button', { name: 'Publish' });
this.testButton = page.getByRole('button', { name: 'Test' });
this.testTab = page.getByTestId('tab-test');
this.runFunctionButton = page.getByRole('button', { name: 'Run Function' });
this.inputField = page.getByTestId('dummyInput'); // TODO: fix
this.settingsTab = page.getByTestId('tab-settings');
this.searchVariableInput = page.getByPlaceholder('Search a variable');
this.addVariableButton = page.getByRole('button', { name: 'Add Variable' });
this.nameVariableInput = page.getByPlaceholder('Name').nth(1);
this.valueVariableInput = page.getByPlaceholder('Value');
this.cancelVariableButton = page.locator('.css-uwqduk').first(); // TODO: fix
this.saveVariableButton = page.locator('.css-uwqduk').nth(1); // TODO: fix
this.editVariableButton = page.getByText('Edit', { exact: true });
this.deleteVariableButton = page.getByText('Delete', { exact: true });
this.cancelButton = page.getByRole('button', { name: 'Cancel' });
this.saveButton = page.getByRole('button', { name: 'Save' });
this.deleteButton = page.getByRole('button', { name: 'Delete function' });
}
async clickNewFunction() {
await this.newFunctionButton.click();
}
async typeFunctionName(name: string) {
await this.functionNameInput.fill(name);
}
async typeFunctionDescription(description: string) {
await this.functionDescriptionInput.fill(description);
}
async checkFunctionDetails(name: string) {
await this.page.getByRole('link', { name: `${name} nodejs18.x` }).click();
}
async clickEditorTab() {
await this.editorTab.click();
}
async clickResetButton() {
await this.resetButton.click();
}
async clickPublishButton() {
await this.publishButton.click();
}
async clickTestButton() {
await this.testButton.click();
}
async typeFunctionCode() {
// TODO: finish once utils are merged
}
async clickTestTab() {
await this.testTab.click();
}
async runFunction() {
await this.runFunctionButton.click();
}
async typeFunctionInput() {
// TODO: finish once utils are merged
}
async clickSettingsTab() {
await this.settingsTab.click();
}
async searchVariable(name: string) {
await this.searchVariableInput.fill(name);
}
async addVariable() {
await this.addVariableButton.click();
}
async typeVariableName(name: string) {
await this.nameVariableInput.fill(name);
}
async typeVariableValue(value: string) {
await this.valueVariableInput.fill(value);
}
async editVariable(name: string) {
await this.page
.locator(
`//div[@data-testid='tooltip' and contains(., '${name}')]/../../div[last()]/div/div/button`,
)
.click();
await this.editVariableButton.click();
}
async deleteVariable(name: string) {
await this.page
.locator(
`//div[@data-testid='tooltip' and contains(., '${name}')]/../../div[last()]/div/div/button`,
)
.click();
await this.deleteVariableButton.click();
}
async cancelVariable() {
await this.cancelVariableButton.click();
}
async saveVariable() {
await this.saveVariableButton.click();
}
async clickCancelButton() {
await this.cancelButton.click();
}
async clickSaveButton() {
await this.saveButton.click();
}
async clickDeleteButton() {
await this.deleteButton.click();
}
}
@@ -4,8 +4,6 @@ export class GeneralSection {
private readonly workspaceNameField: Locator;
private readonly supportSwitch: Locator;
private readonly deleteWorkspaceButton: Locator;
private readonly customizeDomainButton: Locator;
private readonly subdomainInput: Locator;
constructor(public readonly page: Page) {
this.page = page;
@@ -14,12 +12,6 @@ export class GeneralSection {
this.deleteWorkspaceButton = page.getByRole('button', {
name: 'Delete workspace',
});
this.customizeDomainButton = page.getByRole('button', {
name: 'Customize domain',
});
this.subdomainInput = page.locator(
'//div[contains(.,".twenty-main.com")]/../input',
);
}
async changeWorkspaceName(workspaceName: string) {
@@ -34,10 +26,4 @@ export class GeneralSection {
async clickDeleteWorkSpaceButton() {
await this.deleteWorkspaceButton.click();
}
async changeSubdomain(subdomain: string) {
await this.customizeDomainButton.click();
await this.subdomainInput.fill(subdomain);
await this.page.getByRole('button', { name: 'Save' }).click();
}
}
@@ -29,7 +29,6 @@ export class NewFieldSection {
private readonly relationFieldLink: Locator;
private readonly relationTypeSelect: Locator;
private readonly objectDestinationSelect: Locator;
private readonly relationIconSelect: Locator;
private readonly relationFieldNameInput: Locator;
private readonly fullNameFieldLink: Locator;
private readonly UUIDFieldLink: Locator;
@@ -226,11 +225,6 @@ export class NewFieldSection {
await this.page.getByTestId('tooltip').filter({ hasText: name }).click();
}
async selectRelationIcon(name: string) {
await this.relationIconSelect.click();
await this.page.getByTitle(name).click();
}
async typeRelationName(name: string) {
await this.relationFieldNameInput.clear();
await this.relationFieldNameInput.fill(name);
@@ -1,22 +0,0 @@
import { Locator, Page } from '@playwright/test';
export class RolesSection {
private readonly page: Page;
private readonly createRoleButton: Locator;
private readonly defaultRoleDropdown: Locator;
constructor(page: Page) {
this.page = page;
this.createRoleButton = page.getByRole('button', { name: 'Create Role' });
this.defaultRoleDropdown = page.getByTestId('tooltip');
}
async clickCreateRoleButton() {
await this.createRoleButton.click();
}
async selectDefaultRole(role: string) {
await this.defaultRoleDropdown.click();
await this.page.getByTestId('tooltip').getByText(role).click();
}
}
@@ -1,28 +1,10 @@
import { Locator, Page } from '@playwright/test';
export class SecuritySection {
private readonly googleToggle: Locator;
private readonly microsoftToggle: Locator;
private readonly passwordToggle: Locator;
private readonly inviteByLinkToggle: Locator;
constructor(public readonly page: Page) {
this.googleToggle = page.getByLabel('Google');
this.microsoftToggle = page.getByLabel('Microsoft');
this.passwordToggle = page.getByLabel('Password');
this.inviteByLinkToggle = page.getByLabel('Invite by Link');
}
async toggleGoogle() {
await this.googleToggle.click();
}
async toggleMicrosoft() {
await this.microsoftToggle.click();
}
async togglePassword() {
await this.passwordToggle.click();
this.inviteByLinkToggle = page.locator('input[type="checkbox"]').nth(1);
}
async toggleInviteByLink() {
@@ -1,65 +0,0 @@
import { Locator, Page } from '@playwright/test';
export class WebhooksSection {
private readonly createWebhookButton: Locator;
private readonly webhookURLInput: Locator;
private readonly webhookDescription: Locator;
private readonly deleteButton: Locator;
constructor(public readonly page: Page) {
this.page = page;
this.createWebhookButton = page.getByRole('link', {
name: 'Create Webhook',
});
this.webhookURLInput = page.getByPlaceholder('URL');
this.webhookDescription = page.getByPlaceholder('Write a description');
this.deleteButton = page.getByRole('button', { name: 'Delete' });
}
async deleteWebhook() {
await this.deleteButton.click();
}
async createWebhook() {
await this.createWebhookButton.click();
}
async typeWebhookURL(url: string) {
await this.webhookURLInput.clear();
await this.webhookURLInput.fill(url);
}
async typeWebhookDescription(description: string) {
await this.webhookDescription.fill(description);
}
async selectWebhookObject(index: number, object: string) {
await this.page
.locator(
`//div[aria-controls="object-webhook-type-select-${index}-options"]`,
)
.click();
await this.page.getByRole('option').getByText(object).click();
}
async selectWebhookAction(index: number, action: string) {
await this.page
.locator(
`//div[aria-controls="operation-webhook-type-select-${index}-options"]`,
)
.click();
await this.page.getByRole('option').getByText(action).click();
}
async deleteWebhookFilter(index: number) {
await this.page
.locator(
`//div[aria-controls="object-webhook-type-select-${index}-options"]/../..//button`,
)
.click();
}
async checkWebhookDetails(name: string) {
await this.page.locator(`//a/div[contains(.,'${name}')][first()]`).click();
}
}
@@ -9,15 +9,13 @@ export class SettingsPage {
private readonly calendarsLink: Locator;
private readonly generalLink: Locator;
private readonly membersLink: Locator;
private readonly rolesLink: Locator;
private readonly dataModelLink: Locator;
private readonly integrationsLink: Locator;
private readonly developersLink: Locator;
private readonly functionsLink: Locator;
private readonly securityLink: Locator;
private readonly apisLink: Locator;
private readonly webhooksLink: Locator;
private readonly adminPanelLink: Locator;
private readonly labLink: Locator;
private readonly integrationsLink: Locator;
private readonly releasesLink: Locator;
private readonly logoutLink: Locator;
private readonly advancedToggle: Locator;
constructor(public readonly page: Page) {
@@ -30,15 +28,13 @@ export class SettingsPage {
this.calendarsLink = page.getByRole('link', { name: 'Calendars' });
this.generalLink = page.getByRole('link', { name: 'General' });
this.membersLink = page.getByRole('link', { name: 'Members' });
this.rolesLink = page.getByRole('link', { name: 'Roles' });
this.dataModelLink = page.getByRole('link', { name: 'Data model' });
this.developersLink = page.getByRole('link', { name: 'Developers' });
this.functionsLink = page.getByRole('link', { name: 'Functions' });
this.integrationsLink = page.getByRole('link', { name: 'Integrations' });
this.securityLink = page.getByRole('link', { name: 'Security' });
this.apisLink = page.getByRole('link', { name: 'APIs' });
this.webhooksLink = page.getByRole('link', { name: 'Webhooks' });
this.adminPanelLink = page.getByRole('link', { name: 'Admin Panel' });
this.labLink = page.getByRole('link', { name: 'Lab' });
this.releasesLink = page.getByRole('link', { name: 'Releases' });
this.logoutLink = page.getByText('Logout');
this.advancedToggle = page.locator('input[type="checkbox"]').first();
}
@@ -74,42 +70,34 @@ export class SettingsPage {
await this.membersLink.click();
}
async goToRolesSection() {
await this.rolesLink.click();
}
async goToDataModelSection() {
await this.dataModelLink.click();
}
async goToIntegrationsSection() {
await this.integrationsLink.click();
async goToDevelopersSection() {
await this.developersLink.click();
}
async goToFunctionsSection() {
await this.functionsLink.click();
}
async goToSecuritySection() {
await this.securityLink.click();
}
async goToAPIsSection() {
await this.apisLink.click();
}
async goToWebhooksSection() {
await this.webhooksLink.click();
}
async goToAdminPanelSection() {
await this.adminPanelLink.click();
}
async goToLabSection() {
await this.labLink.click();
async goToIntegrationsSection() {
await this.integrationsLink.click();
}
async goToReleasesIntegration() {
await this.releasesLink.click();
}
async logout() {
await this.logoutLink.click();
}
async toggleAdvancedSettings() {
await this.advancedToggle.click();
}
+2 -2
View File
@@ -1,11 +1,11 @@
{
"name": "twenty-e2e-testing",
"version": "0.50.1",
"version": "0.42.0",
"description": "",
"author": "",
"private": true,
"license": "AGPL-3.0",
"devDependencies": {
"@playwright/test": "^1.51.0"
"@playwright/test": "^1.49.0"
}
}
@@ -0,0 +1,16 @@
import { expect, test } from '@playwright/test';
test.fixme(
'Check if demo account is working properly @demo-only',
async ({ page }) => {
await page.goto('https://app.twenty-next.com/');
await page.getByRole('button', { name: 'Continue with Email' }).click();
await page.getByRole('button', { name: 'Continue', exact: true }).click();
await page.getByRole('button', { name: 'Sign in' }).click();
await expect(page.getByText('Welcome to Twenty')).not.toBeVisible();
await page.waitForTimeout(5000);
await expect(page.getByText('Servers on a coffee break')).not.toBeVisible({
timeout: 5000,
});
},
);
@@ -23,13 +23,10 @@ test('Create workflow', async ({ page }) => {
return requestBody.operationName === 'CreateOneWorkflow';
}),
createWorkflowButton.click(),
await createWorkflowButton.click(),
]);
const recordName = page.getByTestId('top-bar-title').getByTestId('tooltip');
await recordName.click();
const nameInput = page.getByTestId('top-bar-title').getByRole('textbox');
const nameInput = page.getByRole('textbox');
await nameInput.fill(NEW_WORKFLOW_NAME);
const workflowDiagramContainer = page.locator('.react-flow__renderer');
@@ -39,9 +36,7 @@ test('Create workflow', async ({ page }) => {
const newWorkflowId = body.data.createWorkflow.id;
try {
const workflowName = page
.getByTestId('top-bar-title')
.getByText(NEW_WORKFLOW_NAME);
const workflowName = page.getByRole('button', { name: NEW_WORKFLOW_NAME });
await expect(workflowName).toBeVisible();
@@ -1,60 +0,0 @@
import { expect } from '@playwright/test';
import { test } from '../lib/fixtures/blank-workflow';
test.fixme(
'The workflow run visualizer shows the executed draft version without the last draft changes',
async ({ workflowVisualizer, page }) => {
await workflowVisualizer.createInitialTrigger('manual');
const manualTriggerAvailabilitySelect = page.getByRole('button', {
name: 'When record(s) are selected',
});
await manualTriggerAvailabilitySelect.click();
const alwaysAvailableOption = page.getByText(
'When no record(s) are selected',
);
await alwaysAvailableOption.click();
await workflowVisualizer.closeSidePanel();
const { createdStepId: firstStepId } =
await workflowVisualizer.createStep('create-record');
await workflowVisualizer.closeSidePanel();
const launchTestButton = page.getByLabel('Test Workflow');
await launchTestButton.click();
const goToExecutionPageLink = page.getByRole('link', {
name: 'View execution details',
});
const executionPageUrl = await goToExecutionPageLink.getAttribute('href');
expect(executionPageUrl).not.toBeNull();
await workflowVisualizer.deleteStep(firstStepId);
await page.goto(executionPageUrl!);
const workflowRunName = page.getByText('Execution of v1');
await expect(workflowRunName).toBeVisible();
const flowTab = page.getByText('Flow', { exact: true });
await flowTab.click();
const executedFirstStepNode = workflowVisualizer.getStepNode(firstStepId);
await expect(executedFirstStepNode).toBeVisible();
await executedFirstStepNode.click();
await expect(
workflowVisualizer.commandMenu.getByRole('textbox').first(),
).toHaveValue('Create Record');
},
);
@@ -86,92 +86,90 @@ test('Use an old version as draft', async ({ workflowVisualizer, page }) => {
await expect(workflowVisualizer.getAllStepNodes()).toHaveCount(1);
});
test('Use an old version as draft while having a pending draft version', async ({
workflowVisualizer,
page,
}) => {
await workflowVisualizer.createInitialTrigger('record-created');
test.fixme(
'Use an old version as draft while having a pending draft version',
async ({ workflowVisualizer, page }) => {
await workflowVisualizer.createInitialTrigger('record-created');
await workflowVisualizer.createStep('create-record');
await workflowVisualizer.createStep('create-record');
await workflowVisualizer.background.click();
await workflowVisualizer.background.click();
await Promise.all([
expect(workflowVisualizer.workflowStatus).toHaveText('Active'),
await Promise.all([
expect(workflowVisualizer.workflowStatus).toHaveText('Active'),
workflowVisualizer.activateWorkflowButton.click(),
]);
workflowVisualizer.activateWorkflowButton.click(),
]);
await Promise.all([
expect(workflowVisualizer.workflowStatus).toHaveText('Draft'),
await Promise.all([
expect(workflowVisualizer.workflowStatus).toHaveText('Draft'),
workflowVisualizer.createStep('delete-record'),
]);
workflowVisualizer.createStep('delete-record'),
]);
await expect(workflowVisualizer.triggerNode).toContainText(
'Record is Created',
);
await expect(workflowVisualizer.getAllStepNodes()).toContainText([
'Create Record',
'Delete Record',
]);
await expect(workflowVisualizer.getAllStepNodes()).toHaveCount(2);
await expect(workflowVisualizer.useAsDraftButton).not.toBeVisible();
await expect(workflowVisualizer.triggerNode).toContainText(
'Record is Created',
);
await expect(workflowVisualizer.getAllStepNodes()).toContainText([
'Create Record',
'Delete Record',
]);
await expect(workflowVisualizer.getAllStepNodes()).toHaveCount(2);
await expect(workflowVisualizer.useAsDraftButton).not.toBeVisible();
await workflowVisualizer.closeSidePanel();
const workflowsLink = page.getByRole('link', { name: 'Workflows' });
await workflowsLink.click();
const workflowsLink = page.getByRole('link', { name: 'Workflows' });
await workflowsLink.click();
const recordTableRowForWorkflow = page.getByRole('row', {
name: workflowVisualizer.workflowName,
});
const recordTableRowForWorkflow = page.getByRole('row', {
name: workflowVisualizer.workflowName,
});
const linkToWorkflow = recordTableRowForWorkflow.getByRole('link', {
name: workflowVisualizer.workflowName,
});
expect(linkToWorkflow).toBeVisible();
const linkToWorkflow = recordTableRowForWorkflow.getByRole('link', {
name: workflowVisualizer.workflowName,
});
expect(linkToWorkflow).toBeVisible();
const linkToFirstWorkflowVersion = recordTableRowForWorkflow.getByRole(
'link',
{
name: 'v1',
},
);
const linkToFirstWorkflowVersion = recordTableRowForWorkflow.getByRole(
'link',
{
name: 'v1',
},
);
await linkToFirstWorkflowVersion.click();
await linkToFirstWorkflowVersion.click();
await expect(workflowVisualizer.workflowStatus).toHaveText('Active');
await expect(workflowVisualizer.useAsDraftButton).toBeVisible();
await expect(workflowVisualizer.triggerNode).toContainText(
'Record is Created',
);
await expect(workflowVisualizer.getAllStepNodes()).toContainText([
'Create Record',
]);
await expect(workflowVisualizer.getAllStepNodes()).toHaveCount(1);
await expect(workflowVisualizer.workflowStatus).toHaveText('Active');
await expect(workflowVisualizer.useAsDraftButton).toBeVisible();
await expect(workflowVisualizer.triggerNode).toContainText(
'Record is Created',
);
await expect(workflowVisualizer.getAllStepNodes()).toContainText([
'Create Record',
]);
await expect(workflowVisualizer.getAllStepNodes()).toHaveCount(1);
await Promise.all([
expect(workflowVisualizer.overrideDraftButton).toBeVisible(),
await Promise.all([
expect(workflowVisualizer.overrideDraftButton).toBeVisible(),
workflowVisualizer.useAsDraftButton.click(),
]);
workflowVisualizer.useAsDraftButton.click(),
]);
await Promise.all([
page.waitForURL(`/object/workflow/${workflowVisualizer.workflowId}`),
await Promise.all([
page.waitForURL(`/object/workflow/${workflowVisualizer.workflowId}`),
workflowVisualizer.overrideDraftButton.click(),
]);
workflowVisualizer.overrideDraftButton.click(),
]);
await expect(workflowVisualizer.workflowStatus).toHaveText('Draft');
await expect(workflowVisualizer.useAsDraftButton).not.toBeVisible();
await expect(workflowVisualizer.triggerNode).toContainText(
'Record is Created',
);
await expect(workflowVisualizer.getAllStepNodes()).toContainText([
'Create Record',
]);
await expect(workflowVisualizer.getAllStepNodes()).toHaveCount(1);
await expect(workflowVisualizer.activateWorkflowButton).toBeVisible();
await expect(workflowVisualizer.discardDraftButton).toBeVisible();
});
await expect(workflowVisualizer.workflowStatus).toHaveText('Draft');
await expect(workflowVisualizer.useAsDraftButton).not.toBeVisible();
await expect(workflowVisualizer.triggerNode).toContainText(
'Record is Created',
);
await expect(workflowVisualizer.getAllStepNodes()).toContainText([
'Create Record',
]);
await expect(workflowVisualizer.getAllStepNodes()).toHaveCount(1);
await expect(workflowVisualizer.activateWorkflowButton).toBeVisible();
await expect(workflowVisualizer.discardDraftButton).toBeVisible();
},
);
@@ -1,13 +1,12 @@
import { defineConfig } from '@lingui/conf';
import { formatter } from '@lingui/format-po';
import { APP_LOCALES, SOURCE_LOCALE } from 'twenty-shared/translations';
import { defineConfig } from '@lingui/cli';
import { APP_LOCALES } from 'twenty-shared';
export default defineConfig({
sourceLocale: 'en',
locales: Object.values(APP_LOCALES),
pseudoLocale: 'pseudo-en',
fallbackLocales: {
default: SOURCE_LOCALE,
'pseudo-en': 'en',
},
extractorParserOptions: {
tsExperimentalDecorators: true,
@@ -20,5 +19,4 @@ export default defineConfig({
],
catalogsMergePath: '<rootDir>/src/locales/generated/{locale}',
compileNamespace: 'ts',
format: formatter({ lineNumbers: false, printLinguiId: true }),
});
+1 -8
View File
@@ -1,6 +1,6 @@
{
"name": "twenty-emails",
"version": "0.50.1",
"version": "0.42.0",
"description": "",
"author": "",
"private": true,
@@ -10,15 +10,8 @@
"build": "npx vite build"
},
"dependencies": {
"@lingui/core": "^5.1.2",
"@lingui/react": "^5.1.2",
"twenty-shared": "workspace:*"
},
"devDependencies": {
"@lingui/cli": "^5.1.2",
"@lingui/swc-plugin": "^5.1.0",
"@lingui/vite-plugin": "^5.1.2"
},
"exports": {
".": {
"import": "./dist/index.mjs",
@@ -5,6 +5,7 @@ import { PropsWithChildren } from 'react';
import { BaseHead } from 'src/components/BaseHead';
import { Logo } from 'src/components/Logo';
import { APP_LOCALES, SOURCE_LOCALE } from 'twenty-shared';
import { messages as afMessages } from '../locales/generated/af-ZA';
import { messages as arMessages } from '../locales/generated/ar-SA';
import { messages as caMessages } from '../locales/generated/ca-ES';
@@ -36,7 +37,6 @@ import { messages as ukMessages } from '../locales/generated/uk-UA';
import { messages as viMessages } from '../locales/generated/vi-VN';
import { messages as zhHansMessages } from '../locales/generated/zh-CN';
import { messages as zhHantMessages } from '../locales/generated/zh-TW';
import { APP_LOCALES, SOURCE_LOCALE } from 'twenty-shared/translations';
type BaseEmailProps = PropsWithChildren<{
width?: number;
@@ -3,35 +3,30 @@ import { BaseEmail } from 'src/components/BaseEmail';
import { CallToAction } from 'src/components/CallToAction';
import { MainText } from 'src/components/MainText';
import { Title } from 'src/components/Title';
import { APP_LOCALES } from 'twenty-shared/translations';
type CleanSuspendedWorkspaceEmailProps = {
daysSinceInactive: number;
inactiveDaysBeforeDelete: number;
userName: string;
workspaceDisplayName: string | undefined;
locale: keyof typeof APP_LOCALES;
};
export const CleanSuspendedWorkspaceEmail = ({
daysSinceInactive,
inactiveDaysBeforeDelete,
userName,
workspaceDisplayName,
locale,
}: CleanSuspendedWorkspaceEmailProps) => {
const helloString = userName?.length > 1 ? `Hello ${userName}` : 'Hello';
return (
<BaseEmail width={333} locale={locale}>
<Title value={<Trans>Deleted Workspace</Trans>} />
<BaseEmail width={333} locale="en">
<Title value="Deleted Workspace 🥺" />
<MainText>
{userName?.length > 1 ? (
<Trans>Dear {userName},</Trans>
) : (
<Trans>Hello,</Trans>
)}
{helloString},
<br />
<br />
<Trans>
Your workspace <b>{workspaceDisplayName}</b> has been deleted as your
subscription expired {daysSinceInactive} days ago.
subscription expired {inactiveDaysBeforeDelete} days ago.
</Trans>
<br />
<br />
@@ -44,7 +39,7 @@ export const CleanSuspendedWorkspaceEmail = ({
</MainText>
<CallToAction
href="https://app.twenty.com/"
value={<Trans>Create a new workspace</Trans>}
value="Create a new workspace"
/>
</BaseEmail>
);
@@ -1,10 +1,11 @@
import { t } from '@lingui/core/macro';
import { Trans } from '@lingui/react/macro';
import { BaseEmail } from 'src/components/BaseEmail';
import { CallToAction } from 'src/components/CallToAction';
import { Link } from 'src/components/Link';
import { MainText } from 'src/components/MainText';
import { Title } from 'src/components/Title';
import { APP_LOCALES } from 'twenty-shared/translations';
import { APP_LOCALES } from 'twenty-shared';
type PasswordResetLinkEmailProps = {
duration: string;
@@ -19,8 +20,8 @@ export const PasswordResetLinkEmail = ({
}: PasswordResetLinkEmailProps) => {
return (
<BaseEmail locale={locale}>
<Title value={<Trans>Reset your password 🗝</Trans>} />
<CallToAction href={link} value={<Trans>Reset</Trans>} />
<Title value={t`Reset your password 🗝`} />
<CallToAction href={link} value={t`Reset`} />
<MainText>
<Trans>
This link is only valid for the next {duration}. If the link does not
@@ -6,7 +6,7 @@ import { BaseEmail } from 'src/components/BaseEmail';
import { CallToAction } from 'src/components/CallToAction';
import { MainText } from 'src/components/MainText';
import { Title } from 'src/components/Title';
import { APP_LOCALES } from 'twenty-shared/translations';
import { APP_LOCALES } from 'twenty-shared';
type PasswordUpdateNotifyEmailProps = {
userName: string;
@@ -26,7 +26,7 @@ export const PasswordUpdateNotifyEmail = ({
return (
<BaseEmail locale={locale}>
<Title value={<Trans>Password updated</Trans>} />
<Title value={t`Password updated`} />
<MainText>
{helloString},
<br />
@@ -43,7 +43,7 @@ export const PasswordUpdateNotifyEmail = ({
</Trans>
<br />
</MainText>
<CallToAction value={<Trans>Connect to Twenty</Trans>} href={link} />
<CallToAction value={t`Connect to Twenty`} href={link} />
</BaseEmail>
);
};
@@ -1,3 +1,4 @@
import { t } from '@lingui/core/macro';
import { Trans } from '@lingui/react/macro';
import { BaseEmail } from 'src/components/BaseEmail';
@@ -5,7 +6,7 @@ import { CallToAction } from 'src/components/CallToAction';
import { Footer } from 'src/components/Footer';
import { MainText } from 'src/components/MainText';
import { Title } from 'src/components/Title';
import { APP_LOCALES } from 'twenty-shared/translations';
import { APP_LOCALES } from 'twenty-shared';
type SendEmailVerificationLinkEmailProps = {
link: string;
@@ -18,8 +19,8 @@ export const SendEmailVerificationLinkEmail = ({
}: SendEmailVerificationLinkEmailProps) => {
return (
<BaseEmail width={333} locale={locale}>
<Title value={<Trans>Confirm your email address</Trans>} />
<CallToAction href={link} value={<Trans>Verify Email</Trans>} />
<Title value={t`Confirm your email address`} />
<CallToAction href={link} value={t`Verify Email`} />
<br />
<br />
<MainText>
@@ -1,3 +1,4 @@
import { t } from '@lingui/core/macro';
import { Trans } from '@lingui/react/macro';
import { Img } from '@react-email/components';
import { emailTheme } from 'src/common-style';
@@ -11,8 +12,7 @@ import { MainText } from 'src/components/MainText';
import { Title } from 'src/components/Title';
import { WhatIsTwenty } from 'src/components/WhatIsTwenty';
import { capitalize } from 'src/utils/capitalize';
import { APP_LOCALES } from 'twenty-shared/translations';
import { getImageAbsoluteURI } from 'twenty-shared/utils';
import { APP_LOCALES, getImageAbsoluteURI } from 'twenty-shared';
type SendInviteLinkEmailProps = {
link: string;
@@ -39,7 +39,7 @@ export const SendInviteLinkEmail = ({
return (
<BaseEmail width={333} locale={locale}>
<Title value={<Trans>Join your team on Twenty</Trans>} />
<Title value={t`Join your team on Twenty`} />
<MainText>
{capitalize(sender.firstName)} (
<Link
@@ -47,14 +47,14 @@ export const SendInviteLinkEmail = ({
value={sender.email}
color={emailTheme.font.colors.blue}
/>
) <Trans>has invited you to join a workspace called </Trans>
)<Trans>has invited you to join a workspace called </Trans>
<b>{workspace.name}</b>
<br />
</MainText>
<HighlightedContainer>
{workspaceLogo && <Img src={workspaceLogo} width={40} height={40} />}
{workspace.name && <HighlightedText value={workspace.name} />}
<CallToAction href={link} value={<Trans>Accept invite</Trans>} />
<CallToAction href={link} value={t`Accept invite`} />
</HighlightedContainer>
<WhatIsTwenty />
</BaseEmail>
@@ -1,69 +0,0 @@
import { t } from '@lingui/core/macro';
import { Trans } from '@lingui/react/macro';
import { Img } from '@react-email/components';
import { emailTheme } from 'src/common-style';
import { BaseEmail } from 'src/components/BaseEmail';
import { CallToAction } from 'src/components/CallToAction';
import { HighlightedContainer } from 'src/components/HighlightedContainer';
import { HighlightedText } from 'src/components/HighlightedText';
import { Link } from 'src/components/Link';
import { MainText } from 'src/components/MainText';
import { Title } from 'src/components/Title';
import { WhatIsTwenty } from 'src/components/WhatIsTwenty';
import { capitalize } from 'src/utils/capitalize';
import { APP_LOCALES } from 'twenty-shared/translations';
import { getImageAbsoluteURI } from 'twenty-shared/utils';
type SendApprovedAccessDomainValidationProps = {
link: string;
domain: string;
workspace: { name: string | undefined; logo: string | undefined };
sender: {
email: string;
firstName: string;
lastName: string;
};
serverUrl: string;
locale: keyof typeof APP_LOCALES;
};
export const SendApprovedAccessDomainValidation = ({
link,
domain,
workspace,
sender,
serverUrl,
locale,
}: SendApprovedAccessDomainValidationProps) => {
const workspaceLogo = workspace.logo
? getImageAbsoluteURI({ imageUrl: workspace.logo, baseUrl: serverUrl })
: null;
return (
<BaseEmail width={333} locale={locale}>
<Title value={t`Validate domain`} />
<MainText>
{capitalize(sender.firstName)} (
<Link
href={`mailto:${sender.email}`}
value={sender.email}
color={emailTheme.font.colors.blue}
/>
) <Trans>Please validate this domain to allow users with</Trans>{' '}
<b>@{domain}</b>{' '}
<Trans>
email addresses to join your workspace without requiring an
invitation.
</Trans>
<br />
</MainText>
<HighlightedContainer>
{workspaceLogo && <Img src={workspaceLogo} width={40} height={40} />}
{workspace.name && <HighlightedText value={workspace.name} />}
<CallToAction href={link} value={t`Validate domain`} />
</HighlightedContainer>
<WhatIsTwenty />
</BaseEmail>
);
};
@@ -1,16 +1,15 @@
import { t } from '@lingui/core/macro';
import { Trans } from '@lingui/react/macro';
import { BaseEmail } from 'src/components/BaseEmail';
import { CallToAction } from 'src/components/CallToAction';
import { MainText } from 'src/components/MainText';
import { Title } from 'src/components/Title';
import { APP_LOCALES } from 'twenty-shared/translations';
type WarnSuspendedWorkspaceEmailProps = {
daysSinceInactive: number;
inactiveDaysBeforeDelete: number;
userName: string;
workspaceDisplayName: string | undefined;
locale: keyof typeof APP_LOCALES;
};
export const WarnSuspendedWorkspaceEmail = ({
@@ -18,7 +17,6 @@ export const WarnSuspendedWorkspaceEmail = ({
inactiveDaysBeforeDelete,
userName,
workspaceDisplayName,
locale,
}: WarnSuspendedWorkspaceEmailProps) => {
const daysLeft = inactiveDaysBeforeDelete - daysSinceInactive;
const dayOrDays = daysLeft > 1 ? 'days' : 'day';
@@ -27,8 +25,8 @@ export const WarnSuspendedWorkspaceEmail = ({
const helloString = userName?.length > 1 ? `Hello ${userName}` : 'Hello';
return (
<BaseEmail width={333} locale={locale}>
<Title value={<Trans>Suspended Workspace </Trans>} />
<BaseEmail width={333} locale="en">
<Title value="Suspended Workspace 😴" />
<MainText>
{helloString},
<br />
@@ -52,7 +50,7 @@ export const WarnSuspendedWorkspaceEmail = ({
</MainText>
<CallToAction
href="https://app.twenty.com/settings/billing"
value={<Trans>Update your subscription</Trans>}
value={t`Update your subscription`}
/>
</BaseEmail>
);
-1
View File
@@ -4,4 +4,3 @@ export * from './emails/password-update-notify.email';
export * from './emails/send-email-verification-link.email';
export * from './emails/send-invite-link.email';
export * from './emails/warn-suspended-workspace.email';
export * from './emails/validate-approved-access-domain.email';
+28 -103
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: aa\n"
"Project-Id-Version: cf448e737e0d6d7b78742f963d761c61\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2025-01-01 00:00\n"
"PO-Revision-Date: 2025-02-12 07:13\n"
"Last-Translator: \n"
"Language-Team: Afar\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
@@ -18,178 +18,103 @@ msgstr ""
"X-Crowdin-File: /packages/twenty-emails/src/locales/en.po\n"
"X-Crowdin-File-ID: 27\n"
#. js-lingui-id: 4WPI3S
#: src/emails/send-invite-link.email.tsx
#: src/emails/send-invite-link.email.tsx:57
msgid "Accept invite"
msgstr "crwdns1:0crwdne1:0"
#. js-lingui-id: Yxj+Uc
#: src/emails/clean-suspended-workspace.email.tsx
#: src/emails/clean-suspended-workspace.email.tsx:33
msgid "All data in this workspace has been permanently deleted."
msgstr "crwdns3:0crwdne3:0"
#. js-lingui-id: RPHFhC
#: src/emails/send-email-verification-link.email.tsx
#: src/emails/send-email-verification-link.email.tsx:22
msgid "Confirm your email address"
msgstr "crwdns5:0crwdne5:0"
#. js-lingui-id: nvkBPN
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:46
msgid "Connect to Twenty"
msgstr "crwdns7:0crwdne7:0"
#. js-lingui-id: jPQSEz
#: src/emails/clean-suspended-workspace.email.tsx
msgid "Create a new workspace"
msgstr "crwdns4865:0crwdne4865:0"
#. js-lingui-id: JRzgV7
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:24
#~ msgid "Dear"
#~ msgstr "Dear"
#. js-lingui-id: Lm5BBI
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:24
msgid "Dear {userName}"
msgstr "crwdns9:0{userName}crwdne9:0"
#. js-lingui-id: lIdkf2
#: src/emails/clean-suspended-workspace.email.tsx
msgid "Dear {userName},"
msgstr "crwdns4867:0{userName}crwdne4867:0"
#. js-lingui-id: NTwcnq
#: src/emails/clean-suspended-workspace.email.tsx
msgid "Deleted Workspace"
msgstr "crwdns4869:0crwdne4869:0"
#. js-lingui-id: S3uuQj
#: src/emails/validate-approved-access-domain.email.tsx
msgid "email addresses to join your workspace without requiring an invitation."
msgstr "crwdns5423:0crwdne5423:0"
#. js-lingui-id: tGme7M
#: src/emails/send-invite-link.email.tsx
#: src/emails/send-invite-link.email.tsx:50
msgid "has invited you to join a workspace called "
msgstr "crwdns11:0crwdne11:0"
#. js-lingui-id: uzTaYi
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:24
msgid "Hello"
msgstr "crwdns13:0crwdne13:0"
#. js-lingui-id: Xa0d85
#: src/emails/clean-suspended-workspace.email.tsx
msgid "Hello,"
msgstr "crwdns4871:0crwdne4871:0"
#. js-lingui-id: eE1nG1
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:40
msgid "If you did not initiate this change, please contact your workspace owner immediately."
msgstr "crwdns15:0crwdne15:0"
#. js-lingui-id: Gz91L8
#: src/emails/warn-suspended-workspace.email.tsx
#: src/emails/warn-suspended-workspace.email.tsx:46
msgid "If you wish to continue using Twenty, please update your subscription within the next {remainingDays} {dayOrDays}."
msgstr "crwdns17:0{remainingDays}crwdnd17:0{dayOrDays}crwdne17:0"
#. js-lingui-id: 0weyko
#: src/emails/clean-suspended-workspace.email.tsx
#: src/emails/clean-suspended-workspace.email.tsx:36
msgid "If you wish to use Twenty again, you can create a new workspace."
msgstr "crwdns19:0crwdne19:0"
#. js-lingui-id: 7JuhZQ
#: src/emails/warn-suspended-workspace.email.tsx
#: src/emails/warn-suspended-workspace.email.tsx:34
msgid "It appears that your workspace <0>{workspaceDisplayName}</0> has been suspended for {daysSinceInactive} days."
msgstr "crwdns21:0{workspaceDisplayName}crwdnd21:0{daysSinceInactive}crwdne21:0"
#. js-lingui-id: PviVyk
#: src/emails/send-invite-link.email.tsx
#: src/emails/send-invite-link.email.tsx:42
msgid "Join your team on Twenty"
msgstr "crwdns23:0crwdne23:0"
#. js-lingui-id: ogtYkT
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:29
msgid "Password updated"
msgstr "crwdns25:0crwdne25:0"
#. js-lingui-id: Yucjaa
#: src/emails/validate-approved-access-domain.email.tsx
msgid "Please validate this domain to allow users with"
msgstr "crwdns5425:0crwdne5425:0"
#. js-lingui-id: u3Ns4p
#: src/emails/validate-approved-access-domain.email.tsx
#~ msgid "Please validate this domain to allow users with <0>@{domain}</0> email addresses to join your workspace without requiring an invitation."
#~ msgstr "Please validate this domain to allow users with <0>@{domain}</0> email addresses to join your workspace without requiring an invitation."
#. js-lingui-id: OfhWJH
#: src/emails/password-reset-link.email.tsx
#: src/emails/password-reset-link.email.tsx:24
msgid "Reset"
msgstr "crwdns27:0crwdne27:0"
#. js-lingui-id: RE5NiU
#: src/emails/password-reset-link.email.tsx
#: src/emails/password-reset-link.email.tsx:23
msgid "Reset your password 🗝"
msgstr "crwdns29:0crwdne29:0"
#. js-lingui-id: UBadaJ
#: src/emails/warn-suspended-workspace.email.tsx
msgid "Suspended Workspace "
msgstr "crwdns4875:0crwdne4875:0"
#. js-lingui-id: 7yDt8q
#: src/emails/send-email-verification-link.email.tsx
#: src/emails/send-email-verification-link.email.tsx:27
msgid "Thanks for registering for an account on Twenty! Before we get started, we just need to confirm that this is you. Click above to verify your email address."
msgstr "crwdns31:0crwdne31:0"
#. js-lingui-id: igorB1
#: src/emails/warn-suspended-workspace.email.tsx
#: src/emails/warn-suspended-workspace.email.tsx:40
msgid "The workspace will be deactivated in {remainingDays} {dayOrDays}, and all its data will be deleted."
msgstr "crwdns33:0{remainingDays}crwdnd33:0{dayOrDays}crwdne33:0"
#. js-lingui-id: 7OEHy1
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:34
msgid "This is a confirmation that password for your account ({email}) was successfully changed on {formattedDate}."
msgstr "crwdns35:0{email}crwdnd35:0{formattedDate}crwdne35:0"
#. js-lingui-id: wSOsS+
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:36
#~ msgid "This is a confirmation that password for your account ({email}) was successfully changed on {formattedDate}.<0/><1/>If you did not initiate this change, please contact your workspace owner immediately."
#~ msgstr "This is a confirmation that password for your account ({email}) was successfully changed on {formattedDate}.<0/><1/>If you did not initiate this change, please contact your workspace owner immediately."
#. js-lingui-id: R4gMjN
#: src/emails/password-reset-link.email.tsx
#: src/emails/password-reset-link.email.tsx:26
msgid "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:"
msgstr "crwdns37:0{duration}crwdne37:0"
#. js-lingui-id: 2oA637
#: src/emails/password-reset-link.email.tsx
#: src/emails/password-reset-link.email.tsx:28
#~ msgid "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:<0/>"
#~ msgstr "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:<0/>"
#. js-lingui-id: H0v4yC
#: src/emails/warn-suspended-workspace.email.tsx
#: src/emails/warn-suspended-workspace.email.tsx:53
msgid "Update your subscription"
msgstr "crwdns39:0crwdne39:0"
#. js-lingui-id: QbiUqd
#: src/emails/validate-approved-access-domain.email.tsx
#: src/emails/validate-approved-access-domain.email.tsx
msgid "Validate domain"
msgstr "crwdns4877:0crwdne4877:0"
#. js-lingui-id: wCKkSr
#: src/emails/send-email-verification-link.email.tsx
#: src/emails/send-email-verification-link.email.tsx:23
msgid "Verify Email"
msgstr "crwdns41:0crwdne41:0"
#. js-lingui-id: 9MqLGX
#: src/emails/clean-suspended-workspace.email.tsx
msgid "Your workspace <0>{workspaceDisplayName}</0> has been deleted as your subscription expired {daysSinceInactive} days ago."
msgstr "crwdns4845:0{workspaceDisplayName}crwdnd4845:0{daysSinceInactive}crwdne4845:0"
#: src/emails/clean-suspended-workspace.email.tsx:27
msgid "Your workspace <0>{workspaceDisplayName}</0> has been deleted as your subscription expired {inactiveDaysBeforeDelete} days ago."
msgstr "crwdns43:0{workspaceDisplayName}crwdnd43:0{inactiveDaysBeforeDelete}crwdne43:0"
#. js-lingui-id: KFmFrQ
#: src/emails/clean-suspended-workspace.email.tsx
#~ msgid "Your workspace <0>{workspaceDisplayName}</0> has been deleted as your subscription expired {inactiveDaysBeforeDelete} days ago."
#~ msgstr "Your workspace <0>{workspaceDisplayName}</0> has been deleted as your subscription expired {inactiveDaysBeforeDelete} days ago."
+29 -103
View File
@@ -8,7 +8,8 @@ msgstr ""
"Language: af\n"
"Project-Id-Version: cf448e737e0d6d7b78742f963d761c61\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2025-01-01 00:00\n"
"PO-Revision-Date: 2025-01-01 00:00
"
"Last-Translator: \n"
"Language-Team: Afrikaans\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
@@ -18,178 +19,103 @@ msgstr ""
"X-Crowdin-File: /packages/twenty-emails/src/locales/en.po\n"
"X-Crowdin-File-ID: 27\n"
#. js-lingui-id: 4WPI3S
#: src/emails/send-invite-link.email.tsx
#: src/emails/send-invite-link.email.tsx:57
msgid "Accept invite"
msgstr "Aanvaar uitnodiging"
#. js-lingui-id: Yxj+Uc
#: src/emails/clean-suspended-workspace.email.tsx
#: src/emails/clean-suspended-workspace.email.tsx:33
msgid "All data in this workspace has been permanently deleted."
msgstr "Alle data in hierdie werkruimte is permanent verwyder."
#. js-lingui-id: RPHFhC
#: src/emails/send-email-verification-link.email.tsx
#: src/emails/send-email-verification-link.email.tsx:22
msgid "Confirm your email address"
msgstr "Bevestig jou e-posadres"
#. js-lingui-id: nvkBPN
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:46
msgid "Connect to Twenty"
msgstr "Konnekteer met Twenty"
#. js-lingui-id: jPQSEz
#: src/emails/clean-suspended-workspace.email.tsx
msgid "Create a new workspace"
msgstr "Skep 'n nuwe werksruimte"
#. js-lingui-id: JRzgV7
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:24
#~ msgid "Dear"
#~ msgstr "Dear"
#. js-lingui-id: Lm5BBI
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:24
msgid "Dear {userName}"
msgstr "Liewe {userName}"
#. js-lingui-id: lIdkf2
#: src/emails/clean-suspended-workspace.email.tsx
msgid "Dear {userName},"
msgstr "Liewe {userName},"
#. js-lingui-id: NTwcnq
#: src/emails/clean-suspended-workspace.email.tsx
msgid "Deleted Workspace"
msgstr "Verwyderde Werkruimte"
#. js-lingui-id: S3uuQj
#: src/emails/validate-approved-access-domain.email.tsx
msgid "email addresses to join your workspace without requiring an invitation."
msgstr "e-posadresse om by jou werkruimte aan te sluit sonder om 'n uitnodiging te vereis."
#. js-lingui-id: tGme7M
#: src/emails/send-invite-link.email.tsx
#: src/emails/send-invite-link.email.tsx:50
msgid "has invited you to join a workspace called "
msgstr "het jou genooi om aan te sluit by 'n werkruimte genaamd "
#. js-lingui-id: uzTaYi
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:24
msgid "Hello"
msgstr "Hallo"
#. js-lingui-id: Xa0d85
#: src/emails/clean-suspended-workspace.email.tsx
msgid "Hello,"
msgstr "Hallo,"
#. js-lingui-id: eE1nG1
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:40
msgid "If you did not initiate this change, please contact your workspace owner immediately."
msgstr "As jy nie hierdie verandering geïnisieer het nie, kontak asseblief jou werkruimte-eienaar dadelik."
#. js-lingui-id: Gz91L8
#: src/emails/warn-suspended-workspace.email.tsx
#: src/emails/warn-suspended-workspace.email.tsx:46
msgid "If you wish to continue using Twenty, please update your subscription within the next {remainingDays} {dayOrDays}."
msgstr "As jy wil voortgaan om Twenty te gebruik, moet asseblief jou intekening binne die volgende {remainingDays} {dayOrDays} opdateer."
#. js-lingui-id: 0weyko
#: src/emails/clean-suspended-workspace.email.tsx
#: src/emails/clean-suspended-workspace.email.tsx:36
msgid "If you wish to use Twenty again, you can create a new workspace."
msgstr "As jy Twenty weer wil gebruik, kan jy 'n nuwe werkruimte skep."
#. js-lingui-id: 7JuhZQ
#: src/emails/warn-suspended-workspace.email.tsx
#: src/emails/warn-suspended-workspace.email.tsx:34
msgid "It appears that your workspace <0>{workspaceDisplayName}</0> has been suspended for {daysSinceInactive} days."
msgstr "Dit blyk dat jou werkruimte <0>{workspaceDisplayName}</0> vir {daysSinceInactive} dae opgeskort is."
#. js-lingui-id: PviVyk
#: src/emails/send-invite-link.email.tsx
#: src/emails/send-invite-link.email.tsx:42
msgid "Join your team on Twenty"
msgstr "Sluit aan by jou span op Twenty"
#. js-lingui-id: ogtYkT
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:29
msgid "Password updated"
msgstr "Wagwoord opgedateer"
#. js-lingui-id: Yucjaa
#: src/emails/validate-approved-access-domain.email.tsx
msgid "Please validate this domain to allow users with"
msgstr "Valideer asseblief hierdie domein om gebruikers met"
#. js-lingui-id: u3Ns4p
#: src/emails/validate-approved-access-domain.email.tsx
#~ msgid "Please validate this domain to allow users with <0>@{domain}</0> email addresses to join your workspace without requiring an invitation."
#~ msgstr "Please validate this domain to allow users with <0>@{domain}</0> email addresses to join your workspace without requiring an invitation."
#. js-lingui-id: OfhWJH
#: src/emails/password-reset-link.email.tsx
#: src/emails/password-reset-link.email.tsx:24
msgid "Reset"
msgstr "Stel terug"
#. js-lingui-id: RE5NiU
#: src/emails/password-reset-link.email.tsx
#: src/emails/password-reset-link.email.tsx:23
msgid "Reset your password 🗝"
msgstr "Stel jou wagwoord terug 🗝"
#. js-lingui-id: UBadaJ
#: src/emails/warn-suspended-workspace.email.tsx
msgid "Suspended Workspace "
msgstr "Geskorsde Werkruimte "
#. js-lingui-id: 7yDt8q
#: src/emails/send-email-verification-link.email.tsx
#: src/emails/send-email-verification-link.email.tsx:27
msgid "Thanks for registering for an account on Twenty! Before we get started, we just need to confirm that this is you. Click above to verify your email address."
msgstr "Dankie dat jy registreer het vir 'n rekening op Twenty! Voordat ons begin, moet ons net bevestig dat dit jy is. Klik bo om jou e-posadres te verifieer."
#. js-lingui-id: igorB1
#: src/emails/warn-suspended-workspace.email.tsx
#: src/emails/warn-suspended-workspace.email.tsx:40
msgid "The workspace will be deactivated in {remainingDays} {dayOrDays}, and all its data will be deleted."
msgstr "Die werkruimte sal gedeaktiveer word in {remainingDays} {dayOrDays}, en al sy data sal verwyder word."
#. js-lingui-id: 7OEHy1
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:34
msgid "This is a confirmation that password for your account ({email}) was successfully changed on {formattedDate}."
msgstr "Dit is 'n bevestiging dat die wagwoord vir jou rekening ({email}) suksesvol verander is op {formattedDate}."
#. js-lingui-id: wSOsS+
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:36
#~ msgid "This is a confirmation that password for your account ({email}) was successfully changed on {formattedDate}.<0/><1/>If you did not initiate this change, please contact your workspace owner immediately."
#~ msgstr "This is a confirmation that password for your account ({email}) was successfully changed on {formattedDate}.<0/><1/>If you did not initiate this change, please contact your workspace owner immediately."
#. js-lingui-id: R4gMjN
#: src/emails/password-reset-link.email.tsx
#: src/emails/password-reset-link.email.tsx:26
msgid "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:"
msgstr "Hierdie skakel is slegs geldig vir die volgende {duration}. Indien die skakel nie werk nie, kan jy die aanmeldverifiëringskakel direk gebruik:"
#. js-lingui-id: 2oA637
#: src/emails/password-reset-link.email.tsx
#: src/emails/password-reset-link.email.tsx:28
#~ msgid "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:<0/>"
#~ msgstr "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:<0/>"
#. js-lingui-id: H0v4yC
#: src/emails/warn-suspended-workspace.email.tsx
#: src/emails/warn-suspended-workspace.email.tsx:53
msgid "Update your subscription"
msgstr "Opdateer jou intekening"
#. js-lingui-id: QbiUqd
#: src/emails/validate-approved-access-domain.email.tsx
#: src/emails/validate-approved-access-domain.email.tsx
msgid "Validate domain"
msgstr "Valideer domein"
#. js-lingui-id: wCKkSr
#: src/emails/send-email-verification-link.email.tsx
#: src/emails/send-email-verification-link.email.tsx:23
msgid "Verify Email"
msgstr "Verifieer E-pos"
#. js-lingui-id: 9MqLGX
#: src/emails/clean-suspended-workspace.email.tsx
msgid "Your workspace <0>{workspaceDisplayName}</0> has been deleted as your subscription expired {daysSinceInactive} days ago."
msgstr "Jou werkruimte <0>{workspaceDisplayName}</0> is verwyder aangesien jou intekening {daysSinceInactive} dae gelede verval het."
#: src/emails/clean-suspended-workspace.email.tsx:27
msgid "Your workspace <0>{workspaceDisplayName}</0> has been deleted as your subscription expired {inactiveDaysBeforeDelete} days ago."
msgstr "Jou werkruimte <0>{workspaceDisplayName}</0> is verwyder aangesien jou intekening {inactiveDaysBeforeDelete} dae gelede verval het."
#. js-lingui-id: KFmFrQ
#: src/emails/clean-suspended-workspace.email.tsx
#~ msgid "Your workspace <0>{workspaceDisplayName}</0> has been deleted as your subscription expired {inactiveDaysBeforeDelete} days ago."
#~ msgstr "Your workspace <0>{workspaceDisplayName}</0> has been deleted as your subscription expired {inactiveDaysBeforeDelete} days ago."
+29 -103
View File
@@ -8,7 +8,8 @@ msgstr ""
"Language: ar\n"
"Project-Id-Version: cf448e737e0d6d7b78742f963d761c61\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2025-01-01 00:00\n"
"PO-Revision-Date: 2025-01-01 00:00
"
"Last-Translator: \n"
"Language-Team: Arabic\n"
"Plural-Forms: nplurals=6; plural=(n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5);\n"
@@ -18,178 +19,103 @@ msgstr ""
"X-Crowdin-File: /packages/twenty-emails/src/locales/en.po\n"
"X-Crowdin-File-ID: 27\n"
#. js-lingui-id: 4WPI3S
#: src/emails/send-invite-link.email.tsx
#: src/emails/send-invite-link.email.tsx:57
msgid "Accept invite"
msgstr "قبول الدعوة"
#. js-lingui-id: Yxj+Uc
#: src/emails/clean-suspended-workspace.email.tsx
#: src/emails/clean-suspended-workspace.email.tsx:33
msgid "All data in this workspace has been permanently deleted."
msgstr "تم حذف جميع البيانات في هذه الواجهة بشكل دائم."
#. js-lingui-id: RPHFhC
#: src/emails/send-email-verification-link.email.tsx
#: src/emails/send-email-verification-link.email.tsx:22
msgid "Confirm your email address"
msgstr "تأكد من عنوان بريدك الإلكتروني"
#. js-lingui-id: nvkBPN
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:46
msgid "Connect to Twenty"
msgstr "الاتصال بـ Twenty"
#. js-lingui-id: jPQSEz
#: src/emails/clean-suspended-workspace.email.tsx
msgid "Create a new workspace"
msgstr "إنشاء مساحة عمل جديدة"
#. js-lingui-id: JRzgV7
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:24
#~ msgid "Dear"
#~ msgstr "Dear"
#. js-lingui-id: Lm5BBI
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:24
msgid "Dear {userName}"
msgstr "عزيزي {userName}"
#. js-lingui-id: lIdkf2
#: src/emails/clean-suspended-workspace.email.tsx
msgid "Dear {userName},"
msgstr "عزيزي {userName},"
#. js-lingui-id: NTwcnq
#: src/emails/clean-suspended-workspace.email.tsx
msgid "Deleted Workspace"
msgstr "مساحة العمل المحذوفة"
#. js-lingui-id: S3uuQj
#: src/emails/validate-approved-access-domain.email.tsx
msgid "email addresses to join your workspace without requiring an invitation."
msgstr "عناوين البريد الإلكتروني للانضمام إلى مساحة العمل الخاصة بك دون الحاجة إلى دعوة."
#. js-lingui-id: tGme7M
#: src/emails/send-invite-link.email.tsx
#: src/emails/send-invite-link.email.tsx:50
msgid "has invited you to join a workspace called "
msgstr "لقد دُعيت للإنضمام إلى مساحة عمل تسمى "
#. js-lingui-id: uzTaYi
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:24
msgid "Hello"
msgstr "مرحبًا"
#. js-lingui-id: Xa0d85
#: src/emails/clean-suspended-workspace.email.tsx
msgid "Hello,"
msgstr "مرحبًا,"
#. js-lingui-id: eE1nG1
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:40
msgid "If you did not initiate this change, please contact your workspace owner immediately."
msgstr "إذا لم تكن قد بدأت هذا التغيير، يرجى الاتصال بمالك مساحة العمل الخاصة بك على الفور."
#. js-lingui-id: Gz91L8
#: src/emails/warn-suspended-workspace.email.tsx
#: src/emails/warn-suspended-workspace.email.tsx:46
msgid "If you wish to continue using Twenty, please update your subscription within the next {remainingDays} {dayOrDays}."
msgstr "إذا كنت ترغب في الاستمرار في استخدام Twenty، يُرجى تحديث اشتراكك خلال الأيام {remainingDays} {dayOrDays} القادمة."
#. js-lingui-id: 0weyko
#: src/emails/clean-suspended-workspace.email.tsx
#: src/emails/clean-suspended-workspace.email.tsx:36
msgid "If you wish to use Twenty again, you can create a new workspace."
msgstr "إذا كنت ترغب في استخدام Twenty مرة أخرى، يمكنك إنشاء مساحة عمل جديدة."
#. js-lingui-id: 7JuhZQ
#: src/emails/warn-suspended-workspace.email.tsx
#: src/emails/warn-suspended-workspace.email.tsx:34
msgid "It appears that your workspace <0>{workspaceDisplayName}</0> has been suspended for {daysSinceInactive} days."
msgstr "يبدو أن مساحة عملك <0>{workspaceDisplayName}</0> قد تم تعليقها لمدة {daysSinceInactive} أيام."
#. js-lingui-id: PviVyk
#: src/emails/send-invite-link.email.tsx
#: src/emails/send-invite-link.email.tsx:42
msgid "Join your team on Twenty"
msgstr "انضم إلى فريقك في Twenty"
#. js-lingui-id: ogtYkT
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:29
msgid "Password updated"
msgstr "تم تحديث كلمة المرور"
#. js-lingui-id: Yucjaa
#: src/emails/validate-approved-access-domain.email.tsx
msgid "Please validate this domain to allow users with"
msgstr "يرجى التحقق من صحة هذا النطاق للسماح للمستخدمين ب"
#. js-lingui-id: u3Ns4p
#: src/emails/validate-approved-access-domain.email.tsx
#~ msgid "Please validate this domain to allow users with <0>@{domain}</0> email addresses to join your workspace without requiring an invitation."
#~ msgstr "Please validate this domain to allow users with <0>@{domain}</0> email addresses to join your workspace without requiring an invitation."
#. js-lingui-id: OfhWJH
#: src/emails/password-reset-link.email.tsx
#: src/emails/password-reset-link.email.tsx:24
msgid "Reset"
msgstr "إعادة تعيين"
#. js-lingui-id: RE5NiU
#: src/emails/password-reset-link.email.tsx
#: src/emails/password-reset-link.email.tsx:23
msgid "Reset your password 🗝"
msgstr "إعادة تعيين كلمة مرورك 🗝"
#. js-lingui-id: UBadaJ
#: src/emails/warn-suspended-workspace.email.tsx
msgid "Suspended Workspace "
msgstr "فاعِل مساحة العمل المعلّقة "
#. js-lingui-id: 7yDt8q
#: src/emails/send-email-verification-link.email.tsx
#: src/emails/send-email-verification-link.email.tsx:27
msgid "Thanks for registering for an account on Twenty! Before we get started, we just need to confirm that this is you. Click above to verify your email address."
msgstr "شكراً لتسجيلك لحساب على Twenty! قبل أن نبدأ، نحتاج فقط إلى التأكد من أن هذا أنت. انقر أعلاه للتحقق من عنوان بريدك الإلكتروني."
#. js-lingui-id: igorB1
#: src/emails/warn-suspended-workspace.email.tsx
#: src/emails/warn-suspended-workspace.email.tsx:40
msgid "The workspace will be deactivated in {remainingDays} {dayOrDays}, and all its data will be deleted."
msgstr "سيتم تعطيل مساحة العمل في غضون {remainingDays} {dayOrDays}، وسيتم حذف كل بياناتها."
#. js-lingui-id: 7OEHy1
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:34
msgid "This is a confirmation that password for your account ({email}) was successfully changed on {formattedDate}."
msgstr "هذا تأكيد أن كلمة مرور حسابك ({email}) قد تم تغييرها بنجاح في {formattedDate}."
#. js-lingui-id: wSOsS+
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:36
#~ msgid "This is a confirmation that password for your account ({email}) was successfully changed on {formattedDate}.<0/><1/>If you did not initiate this change, please contact your workspace owner immediately."
#~ msgstr "This is a confirmation that password for your account ({email}) was successfully changed on {formattedDate}.<0/><1/>If you did not initiate this change, please contact your workspace owner immediately."
#. js-lingui-id: R4gMjN
#: src/emails/password-reset-link.email.tsx
#: src/emails/password-reset-link.email.tsx:26
msgid "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:"
msgstr "هذا الرابط صالح فقط للمدة {duration} القادمة. إذا لم يعمل الرابط، يمكنك استخدام رابط التحقق من تسجيل الدخول مباشرة:"
#. js-lingui-id: 2oA637
#: src/emails/password-reset-link.email.tsx
#: src/emails/password-reset-link.email.tsx:28
#~ msgid "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:<0/>"
#~ msgstr "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:<0/>"
#. js-lingui-id: H0v4yC
#: src/emails/warn-suspended-workspace.email.tsx
#: src/emails/warn-suspended-workspace.email.tsx:53
msgid "Update your subscription"
msgstr "تحديث الاشتراك"
#. js-lingui-id: QbiUqd
#: src/emails/validate-approved-access-domain.email.tsx
#: src/emails/validate-approved-access-domain.email.tsx
msgid "Validate domain"
msgstr "تحقق من النطاق"
#. js-lingui-id: wCKkSr
#: src/emails/send-email-verification-link.email.tsx
#: src/emails/send-email-verification-link.email.tsx:23
msgid "Verify Email"
msgstr "تحقق من البريد الإلكتروني"
#. js-lingui-id: 9MqLGX
#: src/emails/clean-suspended-workspace.email.tsx
msgid "Your workspace <0>{workspaceDisplayName}</0> has been deleted as your subscription expired {daysSinceInactive} days ago."
msgstr "تم حذف مساحة العمل <0>{workspaceDisplayName}</0> كون اشتراكك قد انتهى منذ {daysSinceInactive} أيام."
#: src/emails/clean-suspended-workspace.email.tsx:27
msgid "Your workspace <0>{workspaceDisplayName}</0> has been deleted as your subscription expired {inactiveDaysBeforeDelete} days ago."
msgstr "تم حذف مساحة العمل <0>{workspaceDisplayName}</0> كون اشتراكك قد انتهى منذ {inactiveDaysBeforeDelete} أيام."
#. js-lingui-id: KFmFrQ
#: src/emails/clean-suspended-workspace.email.tsx
#~ msgid "Your workspace <0>{workspaceDisplayName}</0> has been deleted as your subscription expired {inactiveDaysBeforeDelete} days ago."
#~ msgstr "Your workspace <0>{workspaceDisplayName}</0> has been deleted as your subscription expired {inactiveDaysBeforeDelete} days ago."
+29 -103
View File
@@ -8,7 +8,8 @@ msgstr ""
"Language: ca\n"
"Project-Id-Version: cf448e737e0d6d7b78742f963d761c61\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2025-01-01 00:00\n"
"PO-Revision-Date: 2025-01-01 00:00
"
"Last-Translator: \n"
"Language-Team: Catalan\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
@@ -18,178 +19,103 @@ msgstr ""
"X-Crowdin-File: /packages/twenty-emails/src/locales/en.po\n"
"X-Crowdin-File-ID: 27\n"
#. js-lingui-id: 4WPI3S
#: src/emails/send-invite-link.email.tsx
#: src/emails/send-invite-link.email.tsx:57
msgid "Accept invite"
msgstr "Accepta la invitació"
#. js-lingui-id: Yxj+Uc
#: src/emails/clean-suspended-workspace.email.tsx
#: src/emails/clean-suspended-workspace.email.tsx:33
msgid "All data in this workspace has been permanently deleted."
msgstr "Totes les dades en aquest espai de treball s'han esborrat permanentment."
#. js-lingui-id: RPHFhC
#: src/emails/send-email-verification-link.email.tsx
#: src/emails/send-email-verification-link.email.tsx:22
msgid "Confirm your email address"
msgstr "Confirma la teva adreça de correu electrònic"
#. js-lingui-id: nvkBPN
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:46
msgid "Connect to Twenty"
msgstr "Connecta't a Twenty"
#. js-lingui-id: jPQSEz
#: src/emails/clean-suspended-workspace.email.tsx
msgid "Create a new workspace"
msgstr "Crea un nou espai de treball"
#. js-lingui-id: JRzgV7
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:24
#~ msgid "Dear"
#~ msgstr "Dear"
#. js-lingui-id: Lm5BBI
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:24
msgid "Dear {userName}"
msgstr "Benvolgut {userName}"
#. js-lingui-id: lIdkf2
#: src/emails/clean-suspended-workspace.email.tsx
msgid "Dear {userName},"
msgstr "Benvolgut {userName},"
#. js-lingui-id: NTwcnq
#: src/emails/clean-suspended-workspace.email.tsx
msgid "Deleted Workspace"
msgstr "Espai de treball esborrat"
#. js-lingui-id: S3uuQj
#: src/emails/validate-approved-access-domain.email.tsx
msgid "email addresses to join your workspace without requiring an invitation."
msgstr "adreces de correu electrònic per unir-se al teu espai de treball sense requerir una invitació."
#. js-lingui-id: tGme7M
#: src/emails/send-invite-link.email.tsx
#: src/emails/send-invite-link.email.tsx:50
msgid "has invited you to join a workspace called "
msgstr "t'ha convidat a unir-te a un espai de treball anomenat "
#. js-lingui-id: uzTaYi
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:24
msgid "Hello"
msgstr "Hola"
#. js-lingui-id: Xa0d85
#: src/emails/clean-suspended-workspace.email.tsx
msgid "Hello,"
msgstr "Hola,"
#. js-lingui-id: eE1nG1
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:40
msgid "If you did not initiate this change, please contact your workspace owner immediately."
msgstr "Si no has iniciat aquest canvi, si us plau contacta amb el propietari de l'espai de treball immediatament."
#. js-lingui-id: Gz91L8
#: src/emails/warn-suspended-workspace.email.tsx
#: src/emails/warn-suspended-workspace.email.tsx:46
msgid "If you wish to continue using Twenty, please update your subscription within the next {remainingDays} {dayOrDays}."
msgstr "Si vols continuar utilitzant Twenty, si us plau actualitza la teva subscripció en els propers {remainingDays} {dayOrDays}."
#. js-lingui-id: 0weyko
#: src/emails/clean-suspended-workspace.email.tsx
#: src/emails/clean-suspended-workspace.email.tsx:36
msgid "If you wish to use Twenty again, you can create a new workspace."
msgstr "Si vols utilitzar Twenty de nou, pots crear un nou espai de treball."
#. js-lingui-id: 7JuhZQ
#: src/emails/warn-suspended-workspace.email.tsx
#: src/emails/warn-suspended-workspace.email.tsx:34
msgid "It appears that your workspace <0>{workspaceDisplayName}</0> has been suspended for {daysSinceInactive} days."
msgstr "Sembla que el teu espai de treball <0>{workspaceDisplayName}</0> ha estat suspès per {daysSinceInactive} dies."
#. js-lingui-id: PviVyk
#: src/emails/send-invite-link.email.tsx
#: src/emails/send-invite-link.email.tsx:42
msgid "Join your team on Twenty"
msgstr "Uneix-te al teu equip a Twenty"
#. js-lingui-id: ogtYkT
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:29
msgid "Password updated"
msgstr "Contrasenya actualitzada"
#. js-lingui-id: Yucjaa
#: src/emails/validate-approved-access-domain.email.tsx
msgid "Please validate this domain to allow users with"
msgstr "Si us plau, valida aquest domini per permetre als usuaris amb"
#. js-lingui-id: u3Ns4p
#: src/emails/validate-approved-access-domain.email.tsx
#~ msgid "Please validate this domain to allow users with <0>@{domain}</0> email addresses to join your workspace without requiring an invitation."
#~ msgstr "Please validate this domain to allow users with <0>@{domain}</0> email addresses to join your workspace without requiring an invitation."
#. js-lingui-id: OfhWJH
#: src/emails/password-reset-link.email.tsx
#: src/emails/password-reset-link.email.tsx:24
msgid "Reset"
msgstr "Restableix"
#. js-lingui-id: RE5NiU
#: src/emails/password-reset-link.email.tsx
#: src/emails/password-reset-link.email.tsx:23
msgid "Reset your password 🗝"
msgstr "Restableix la teva contrasenya 🗝"
#. js-lingui-id: UBadaJ
#: src/emails/warn-suspended-workspace.email.tsx
msgid "Suspended Workspace "
msgstr "Espai de treball suspès "
#. js-lingui-id: 7yDt8q
#: src/emails/send-email-verification-link.email.tsx
#: src/emails/send-email-verification-link.email.tsx:27
msgid "Thanks for registering for an account on Twenty! Before we get started, we just need to confirm that this is you. Click above to verify your email address."
msgstr "Gràcies per registrar-te a Twenty! Abans de començar, només necessitem confirmar que ets tu. Clica a sobre per verificar la teva adreça de correu electrònic."
#. js-lingui-id: igorB1
#: src/emails/warn-suspended-workspace.email.tsx
#: src/emails/warn-suspended-workspace.email.tsx:40
msgid "The workspace will be deactivated in {remainingDays} {dayOrDays}, and all its data will be deleted."
msgstr "L'espai de treball es desactivarà en {remainingDays} {dayOrDays}, i totes les seves dades seran eliminades."
#. js-lingui-id: 7OEHy1
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:34
msgid "This is a confirmation that password for your account ({email}) was successfully changed on {formattedDate}."
msgstr "Això és una confirmació que la contrasenya del teu compte ({email}) s'ha canviat amb èxit el {formattedDate}."
#. js-lingui-id: wSOsS+
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:36
#~ msgid "This is a confirmation that password for your account ({email}) was successfully changed on {formattedDate}.<0/><1/>If you did not initiate this change, please contact your workspace owner immediately."
#~ msgstr "This is a confirmation that password for your account ({email}) was successfully changed on {formattedDate}.<0/><1/>If you did not initiate this change, please contact your workspace owner immediately."
#. js-lingui-id: R4gMjN
#: src/emails/password-reset-link.email.tsx
#: src/emails/password-reset-link.email.tsx:26
msgid "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:"
msgstr "Aquest enllaç només és vàlid durant els propers {duration}. Si l'enllaç no funciona, pots fer servir directament l'enllaç de verificació d'inici de sessió:"
#. js-lingui-id: 2oA637
#: src/emails/password-reset-link.email.tsx
#: src/emails/password-reset-link.email.tsx:28
#~ msgid "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:<0/>"
#~ msgstr "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:<0/>"
#. js-lingui-id: H0v4yC
#: src/emails/warn-suspended-workspace.email.tsx
#: src/emails/warn-suspended-workspace.email.tsx:53
msgid "Update your subscription"
msgstr "Actualitza la teva subscripció"
#. js-lingui-id: QbiUqd
#: src/emails/validate-approved-access-domain.email.tsx
#: src/emails/validate-approved-access-domain.email.tsx
msgid "Validate domain"
msgstr "Valida el domini"
#. js-lingui-id: wCKkSr
#: src/emails/send-email-verification-link.email.tsx
#: src/emails/send-email-verification-link.email.tsx:23
msgid "Verify Email"
msgstr "Verifica el correu electrònic"
#. js-lingui-id: 9MqLGX
#: src/emails/clean-suspended-workspace.email.tsx
msgid "Your workspace <0>{workspaceDisplayName}</0> has been deleted as your subscription expired {daysSinceInactive} days ago."
msgstr "El teu espai de treball <0>{workspaceDisplayName}</0> s'ha eliminat ja que la teva subscripció va caducar fa {daysSinceInactive} dies."
#: src/emails/clean-suspended-workspace.email.tsx:27
msgid "Your workspace <0>{workspaceDisplayName}</0> has been deleted as your subscription expired {inactiveDaysBeforeDelete} days ago."
msgstr "El teu espai de treball <0>{workspaceDisplayName}</0> s'ha eliminat ja que la teva subscripció va caducar fa {inactiveDaysBeforeDelete} dies."
#. js-lingui-id: KFmFrQ
#: src/emails/clean-suspended-workspace.email.tsx
#~ msgid "Your workspace <0>{workspaceDisplayName}</0> has been deleted as your subscription expired {inactiveDaysBeforeDelete} days ago."
#~ msgstr "Your workspace <0>{workspaceDisplayName}</0> has been deleted as your subscription expired {inactiveDaysBeforeDelete} days ago."
+29 -103
View File
@@ -8,7 +8,8 @@ msgstr ""
"Language: cs\n"
"Project-Id-Version: cf448e737e0d6d7b78742f963d761c61\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2025-01-01 00:00\n"
"PO-Revision-Date: 2025-01-01 00:00
"
"Last-Translator: \n"
"Language-Team: Czech\n"
"Plural-Forms: nplurals=4; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 3;\n"
@@ -18,178 +19,103 @@ msgstr ""
"X-Crowdin-File: /packages/twenty-emails/src/locales/en.po\n"
"X-Crowdin-File-ID: 27\n"
#. js-lingui-id: 4WPI3S
#: src/emails/send-invite-link.email.tsx
#: src/emails/send-invite-link.email.tsx:57
msgid "Accept invite"
msgstr "Přijměte pozvánku"
#. js-lingui-id: Yxj+Uc
#: src/emails/clean-suspended-workspace.email.tsx
#: src/emails/clean-suspended-workspace.email.tsx:33
msgid "All data in this workspace has been permanently deleted."
msgstr "Všechna data v tomto pracovním prostoru byla trvale odstraněna."
#. js-lingui-id: RPHFhC
#: src/emails/send-email-verification-link.email.tsx
#: src/emails/send-email-verification-link.email.tsx:22
msgid "Confirm your email address"
msgstr "Potvrďte svou e-mailovou adresu"
#. js-lingui-id: nvkBPN
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:46
msgid "Connect to Twenty"
msgstr "Připojte se k Twenty"
#. js-lingui-id: jPQSEz
#: src/emails/clean-suspended-workspace.email.tsx
msgid "Create a new workspace"
msgstr "Vytvořit nový pracovní prostor"
#. js-lingui-id: JRzgV7
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:24
#~ msgid "Dear"
#~ msgstr "Dear"
#. js-lingui-id: Lm5BBI
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:24
msgid "Dear {userName}"
msgstr "Vážený {userName}"
#. js-lingui-id: lIdkf2
#: src/emails/clean-suspended-workspace.email.tsx
msgid "Dear {userName},"
msgstr "Vážený {userName},"
#. js-lingui-id: NTwcnq
#: src/emails/clean-suspended-workspace.email.tsx
msgid "Deleted Workspace"
msgstr "Smazaný pracovní prostor"
#. js-lingui-id: S3uuQj
#: src/emails/validate-approved-access-domain.email.tsx
msgid "email addresses to join your workspace without requiring an invitation."
msgstr "e-mailové adresy ke vstupu do vašeho pracovního prostoru bez nutnosti pozvánky."
#. js-lingui-id: tGme7M
#: src/emails/send-invite-link.email.tsx
#: src/emails/send-invite-link.email.tsx:50
msgid "has invited you to join a workspace called "
msgstr "vás pozval do pracovního prostoru s názvem "
#. js-lingui-id: uzTaYi
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:24
msgid "Hello"
msgstr "Dobrý den"
#. js-lingui-id: Xa0d85
#: src/emails/clean-suspended-workspace.email.tsx
msgid "Hello,"
msgstr "Dobrý den,"
#. js-lingui-id: eE1nG1
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:40
msgid "If you did not initiate this change, please contact your workspace owner immediately."
msgstr "Pokud jste tuto změnu neiniciovali, prosím, okamžitě kontaktujte majitele vašeho pracovního prostoru."
#. js-lingui-id: Gz91L8
#: src/emails/warn-suspended-workspace.email.tsx
#: src/emails/warn-suspended-workspace.email.tsx:46
msgid "If you wish to continue using Twenty, please update your subscription within the next {remainingDays} {dayOrDays}."
msgstr "Pokud si přejete dále používat Twenty, prosím, aktualizujte své předplatné během příštích {remainingDays} {dayOrDays}."
#. js-lingui-id: 0weyko
#: src/emails/clean-suspended-workspace.email.tsx
#: src/emails/clean-suspended-workspace.email.tsx:36
msgid "If you wish to use Twenty again, you can create a new workspace."
msgstr "Pokud si přejete znovu použít Twenty, můžete vytvořit nový pracovní prostor."
#. js-lingui-id: 7JuhZQ
#: src/emails/warn-suspended-workspace.email.tsx
#: src/emails/warn-suspended-workspace.email.tsx:34
msgid "It appears that your workspace <0>{workspaceDisplayName}</0> has been suspended for {daysSinceInactive} days."
msgstr "Zdá se, že váš pracovní prostor <0>{workspaceDisplayName}</0> byl pozastaven na dobu {daysSinceInactive} dní."
#. js-lingui-id: PviVyk
#: src/emails/send-invite-link.email.tsx
#: src/emails/send-invite-link.email.tsx:42
msgid "Join your team on Twenty"
msgstr "Připojte se k svému týmu na Twenty"
#. js-lingui-id: ogtYkT
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:29
msgid "Password updated"
msgstr "Heslo bylo aktualizováno"
#. js-lingui-id: Yucjaa
#: src/emails/validate-approved-access-domain.email.tsx
msgid "Please validate this domain to allow users with"
msgstr "Prosím, ověřte tuto doménu pro umožnění přístupu uživatelům s"
#. js-lingui-id: u3Ns4p
#: src/emails/validate-approved-access-domain.email.tsx
#~ msgid "Please validate this domain to allow users with <0>@{domain}</0> email addresses to join your workspace without requiring an invitation."
#~ msgstr "Please validate this domain to allow users with <0>@{domain}</0> email addresses to join your workspace without requiring an invitation."
#. js-lingui-id: OfhWJH
#: src/emails/password-reset-link.email.tsx
#: src/emails/password-reset-link.email.tsx:24
msgid "Reset"
msgstr "Obnovit"
#. js-lingui-id: RE5NiU
#: src/emails/password-reset-link.email.tsx
#: src/emails/password-reset-link.email.tsx:23
msgid "Reset your password 🗝"
msgstr "Obnovte své heslo 🗝"
#. js-lingui-id: UBadaJ
#: src/emails/warn-suspended-workspace.email.tsx
msgid "Suspended Workspace "
msgstr "Pozastavený pracovní prostor "
#. js-lingui-id: 7yDt8q
#: src/emails/send-email-verification-link.email.tsx
#: src/emails/send-email-verification-link.email.tsx:27
msgid "Thanks for registering for an account on Twenty! Before we get started, we just need to confirm that this is you. Click above to verify your email address."
msgstr "Děkujeme za registraci účtu na Twenty! Než začneme, musíme potvrdit, že jste to vy. Klikněte výše k ověření vaší e-mailové adresy."
#. js-lingui-id: igorB1
#: src/emails/warn-suspended-workspace.email.tsx
#: src/emails/warn-suspended-workspace.email.tsx:40
msgid "The workspace will be deactivated in {remainingDays} {dayOrDays}, and all its data will be deleted."
msgstr "Pracovní prostor bude deaktivován za {remainingDays} {dayOrDays} a všechna jeho data budou smazána."
#. js-lingui-id: 7OEHy1
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:34
msgid "This is a confirmation that password for your account ({email}) was successfully changed on {formattedDate}."
msgstr "Toto je potvrzení, že heslo k vašemu účtu ({email}) bylo úspěšně změněno {formattedDate}."
#. js-lingui-id: wSOsS+
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:36
#~ msgid "This is a confirmation that password for your account ({email}) was successfully changed on {formattedDate}.<0/><1/>If you did not initiate this change, please contact your workspace owner immediately."
#~ msgstr "This is a confirmation that password for your account ({email}) was successfully changed on {formattedDate}.<0/><1/>If you did not initiate this change, please contact your workspace owner immediately."
#. js-lingui-id: R4gMjN
#: src/emails/password-reset-link.email.tsx
#: src/emails/password-reset-link.email.tsx:26
msgid "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:"
msgstr "Tento odkaz je platný pouze následující {duration}. Pokud odkaz nefunguje, můžete přímo použít odkaz pro ověření přihlášení:"
#. js-lingui-id: 2oA637
#: src/emails/password-reset-link.email.tsx
#: src/emails/password-reset-link.email.tsx:28
#~ msgid "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:<0/>"
#~ msgstr "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:<0/>"
#. js-lingui-id: H0v4yC
#: src/emails/warn-suspended-workspace.email.tsx
#: src/emails/warn-suspended-workspace.email.tsx:53
msgid "Update your subscription"
msgstr "Aktualizujte své předplatné"
#. js-lingui-id: QbiUqd
#: src/emails/validate-approved-access-domain.email.tsx
#: src/emails/validate-approved-access-domain.email.tsx
msgid "Validate domain"
msgstr "Ověřit doménu"
#. js-lingui-id: wCKkSr
#: src/emails/send-email-verification-link.email.tsx
#: src/emails/send-email-verification-link.email.tsx:23
msgid "Verify Email"
msgstr "Ověřit e-mail"
#. js-lingui-id: 9MqLGX
#: src/emails/clean-suspended-workspace.email.tsx
msgid "Your workspace <0>{workspaceDisplayName}</0> has been deleted as your subscription expired {daysSinceInactive} days ago."
msgstr "Váš pracovní prostor <0>{workspaceDisplayName}</0> byl odstraněn, protože vaše předplatné vypršelo před {daysSinceInactive} dny."
#: src/emails/clean-suspended-workspace.email.tsx:27
msgid "Your workspace <0>{workspaceDisplayName}</0> has been deleted as your subscription expired {inactiveDaysBeforeDelete} days ago."
msgstr "Váš pracovní prostor <0>{workspaceDisplayName}</0> byl odstraněn, protože vaše předplatné vypršelo před {inactiveDaysBeforeDelete} dny."
#. js-lingui-id: KFmFrQ
#: src/emails/clean-suspended-workspace.email.tsx
#~ msgid "Your workspace <0>{workspaceDisplayName}</0> has been deleted as your subscription expired {inactiveDaysBeforeDelete} days ago."
#~ msgstr "Your workspace <0>{workspaceDisplayName}</0> has been deleted as your subscription expired {inactiveDaysBeforeDelete} days ago."
+29 -103
View File
@@ -8,7 +8,8 @@ msgstr ""
"Language: da\n"
"Project-Id-Version: cf448e737e0d6d7b78742f963d761c61\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2025-01-01 00:00\n"
"PO-Revision-Date: 2025-01-01 00:00
"
"Last-Translator: \n"
"Language-Team: Danish\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
@@ -18,178 +19,103 @@ msgstr ""
"X-Crowdin-File: /packages/twenty-emails/src/locales/en.po\n"
"X-Crowdin-File-ID: 27\n"
#. js-lingui-id: 4WPI3S
#: src/emails/send-invite-link.email.tsx
#: src/emails/send-invite-link.email.tsx:57
msgid "Accept invite"
msgstr "Accepter invitation"
#. js-lingui-id: Yxj+Uc
#: src/emails/clean-suspended-workspace.email.tsx
#: src/emails/clean-suspended-workspace.email.tsx:33
msgid "All data in this workspace has been permanently deleted."
msgstr "Alle data i dette arbejdsområde er blevet permanent slettet."
#. js-lingui-id: RPHFhC
#: src/emails/send-email-verification-link.email.tsx
#: src/emails/send-email-verification-link.email.tsx:22
msgid "Confirm your email address"
msgstr "Bekræft din e-mailadresse"
#. js-lingui-id: nvkBPN
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:46
msgid "Connect to Twenty"
msgstr "Forbind til Twenty"
#. js-lingui-id: jPQSEz
#: src/emails/clean-suspended-workspace.email.tsx
msgid "Create a new workspace"
msgstr "Opret et nyt arbejdsområde"
#. js-lingui-id: JRzgV7
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:24
#~ msgid "Dear"
#~ msgstr "Dear"
#. js-lingui-id: Lm5BBI
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:24
msgid "Dear {userName}"
msgstr "Kære {userName}"
#. js-lingui-id: lIdkf2
#: src/emails/clean-suspended-workspace.email.tsx
msgid "Dear {userName},"
msgstr "Kære {userName},"
#. js-lingui-id: NTwcnq
#: src/emails/clean-suspended-workspace.email.tsx
msgid "Deleted Workspace"
msgstr "Slettet arbejdsområde"
#. js-lingui-id: S3uuQj
#: src/emails/validate-approved-access-domain.email.tsx
msgid "email addresses to join your workspace without requiring an invitation."
msgstr "emailadresser for at tilslutte din arbejdsområde uden at kræve en invitation."
#. js-lingui-id: tGme7M
#: src/emails/send-invite-link.email.tsx
#: src/emails/send-invite-link.email.tsx:50
msgid "has invited you to join a workspace called "
msgstr "har inviteret dig til at deltage i et arbejdsområde kaldet "
#. js-lingui-id: uzTaYi
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:24
msgid "Hello"
msgstr "Hej"
#. js-lingui-id: Xa0d85
#: src/emails/clean-suspended-workspace.email.tsx
msgid "Hello,"
msgstr "Hej,"
#. js-lingui-id: eE1nG1
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:40
msgid "If you did not initiate this change, please contact your workspace owner immediately."
msgstr "Hvis du ikke har initieret denne ændring, bedes du straks kontakte ejeren af dit arbejdsområde."
#. js-lingui-id: Gz91L8
#: src/emails/warn-suspended-workspace.email.tsx
#: src/emails/warn-suspended-workspace.email.tsx:46
msgid "If you wish to continue using Twenty, please update your subscription within the next {remainingDays} {dayOrDays}."
msgstr "Hvis du ønsker at fortsætte med at bruge Twenty, opdater da dit abonnement inden for de næste {remainingDays} {dayOrDays}."
#. js-lingui-id: 0weyko
#: src/emails/clean-suspended-workspace.email.tsx
#: src/emails/clean-suspended-workspace.email.tsx:36
msgid "If you wish to use Twenty again, you can create a new workspace."
msgstr "Hvis du ønsker at bruge Twenty igen, kan du oprette et nyt arbejdsområde."
#. js-lingui-id: 7JuhZQ
#: src/emails/warn-suspended-workspace.email.tsx
#: src/emails/warn-suspended-workspace.email.tsx:34
msgid "It appears that your workspace <0>{workspaceDisplayName}</0> has been suspended for {daysSinceInactive} days."
msgstr "Det ser ud til, at dit arbejdsområde <0>{workspaceDisplayName}</0> er blevet suspenderet i {daysSinceInactive} dage."
#. js-lingui-id: PviVyk
#: src/emails/send-invite-link.email.tsx
#: src/emails/send-invite-link.email.tsx:42
msgid "Join your team on Twenty"
msgstr "Deltag i dit team på Twenty"
#. js-lingui-id: ogtYkT
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:29
msgid "Password updated"
msgstr "Adgangskode opdateret"
#. js-lingui-id: Yucjaa
#: src/emails/validate-approved-access-domain.email.tsx
msgid "Please validate this domain to allow users with"
msgstr "Bekræft venligst dette domæne for at tillade brugere med"
#. js-lingui-id: u3Ns4p
#: src/emails/validate-approved-access-domain.email.tsx
#~ msgid "Please validate this domain to allow users with <0>@{domain}</0> email addresses to join your workspace without requiring an invitation."
#~ msgstr "Please validate this domain to allow users with <0>@{domain}</0> email addresses to join your workspace without requiring an invitation."
#. js-lingui-id: OfhWJH
#: src/emails/password-reset-link.email.tsx
#: src/emails/password-reset-link.email.tsx:24
msgid "Reset"
msgstr "Nulstil"
#. js-lingui-id: RE5NiU
#: src/emails/password-reset-link.email.tsx
#: src/emails/password-reset-link.email.tsx:23
msgid "Reset your password 🗝"
msgstr "Nulstil din adgangskode 🗝"
#. js-lingui-id: UBadaJ
#: src/emails/warn-suspended-workspace.email.tsx
msgid "Suspended Workspace "
msgstr "Suspenderet arbejdsområde "
#. js-lingui-id: 7yDt8q
#: src/emails/send-email-verification-link.email.tsx
#: src/emails/send-email-verification-link.email.tsx:27
msgid "Thanks for registering for an account on Twenty! Before we get started, we just need to confirm that this is you. Click above to verify your email address."
msgstr "Tak fordi du har registreret en konto på Twenty! Før vi starter, skal vi bare bekræfte, at det er dig. Klik ovenfor for at bekræfte din e-mailadresse."
#. js-lingui-id: igorB1
#: src/emails/warn-suspended-workspace.email.tsx
#: src/emails/warn-suspended-workspace.email.tsx:40
msgid "The workspace will be deactivated in {remainingDays} {dayOrDays}, and all its data will be deleted."
msgstr "Arbejdsområdet vil blive deaktiveret om {remainingDays} {dayOrDays}, og alle dens data vil blive slettet."
#. js-lingui-id: 7OEHy1
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:34
msgid "This is a confirmation that password for your account ({email}) was successfully changed on {formattedDate}."
msgstr "Dette er en bekræftelse på, at adgangskoden til din konto ({email}) er blevet ændret den {formattedDate}."
#. js-lingui-id: wSOsS+
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:36
#~ msgid "This is a confirmation that password for your account ({email}) was successfully changed on {formattedDate}.<0/><1/>If you did not initiate this change, please contact your workspace owner immediately."
#~ msgstr "This is a confirmation that password for your account ({email}) was successfully changed on {formattedDate}.<0/><1/>If you did not initiate this change, please contact your workspace owner immediately."
#. js-lingui-id: R4gMjN
#: src/emails/password-reset-link.email.tsx
#: src/emails/password-reset-link.email.tsx:26
msgid "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:"
msgstr "Dette link er kun gyldigt i de næste {duration}. Hvis linket ikke fungerer, kan du bruge loginbekræftelseslinket direkte:"
#. js-lingui-id: 2oA637
#: src/emails/password-reset-link.email.tsx
#: src/emails/password-reset-link.email.tsx:28
#~ msgid "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:<0/>"
#~ msgstr "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:<0/>"
#. js-lingui-id: H0v4yC
#: src/emails/warn-suspended-workspace.email.tsx
#: src/emails/warn-suspended-workspace.email.tsx:53
msgid "Update your subscription"
msgstr "Opdater dit abonnement"
#. js-lingui-id: QbiUqd
#: src/emails/validate-approved-access-domain.email.tsx
#: src/emails/validate-approved-access-domain.email.tsx
msgid "Validate domain"
msgstr "Bekræft domæne"
#. js-lingui-id: wCKkSr
#: src/emails/send-email-verification-link.email.tsx
#: src/emails/send-email-verification-link.email.tsx:23
msgid "Verify Email"
msgstr "Bekræft e-mail"
#. js-lingui-id: 9MqLGX
#: src/emails/clean-suspended-workspace.email.tsx
msgid "Your workspace <0>{workspaceDisplayName}</0> has been deleted as your subscription expired {daysSinceInactive} days ago."
msgstr "Dit arbejdsområde <0>{workspaceDisplayName}</0> er blevet slettet, da dit abonnement udløb for {daysSinceInactive} dage siden."
#: src/emails/clean-suspended-workspace.email.tsx:27
msgid "Your workspace <0>{workspaceDisplayName}</0> has been deleted as your subscription expired {inactiveDaysBeforeDelete} days ago."
msgstr "Dit arbejdsområde <0>{workspaceDisplayName}</0> er blevet slettet, da dit abonnement udløb for {inactiveDaysBeforeDelete} dage siden."
#. js-lingui-id: KFmFrQ
#: src/emails/clean-suspended-workspace.email.tsx
#~ msgid "Your workspace <0>{workspaceDisplayName}</0> has been deleted as your subscription expired {inactiveDaysBeforeDelete} days ago."
#~ msgstr "Your workspace <0>{workspaceDisplayName}</0> has been deleted as your subscription expired {inactiveDaysBeforeDelete} days ago."
+29 -103
View File
@@ -8,7 +8,8 @@ msgstr ""
"Language: de\n"
"Project-Id-Version: cf448e737e0d6d7b78742f963d761c61\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2025-01-01 00:00\n"
"PO-Revision-Date: 2025-01-01 00:00
"
"Last-Translator: \n"
"Language-Team: German\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
@@ -18,178 +19,103 @@ msgstr ""
"X-Crowdin-File: /packages/twenty-emails/src/locales/en.po\n"
"X-Crowdin-File-ID: 27\n"
#. js-lingui-id: 4WPI3S
#: src/emails/send-invite-link.email.tsx
#: src/emails/send-invite-link.email.tsx:57
msgid "Accept invite"
msgstr "Einladung akzeptieren"
#. js-lingui-id: Yxj+Uc
#: src/emails/clean-suspended-workspace.email.tsx
#: src/emails/clean-suspended-workspace.email.tsx:33
msgid "All data in this workspace has been permanently deleted."
msgstr "Alle Daten in diesem Workspace wurden dauerhaft gelöscht."
#. js-lingui-id: RPHFhC
#: src/emails/send-email-verification-link.email.tsx
#: src/emails/send-email-verification-link.email.tsx:22
msgid "Confirm your email address"
msgstr "Bestätigen Sie Ihre E-Mail-Adresse"
#. js-lingui-id: nvkBPN
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:46
msgid "Connect to Twenty"
msgstr "Mit Twenty verbinden"
#. js-lingui-id: jPQSEz
#: src/emails/clean-suspended-workspace.email.tsx
msgid "Create a new workspace"
msgstr "Erstellen Sie einen neuen Arbeitsbereich"
#. js-lingui-id: JRzgV7
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:24
#~ msgid "Dear"
#~ msgstr "Dear"
#. js-lingui-id: Lm5BBI
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:24
msgid "Dear {userName}"
msgstr "Sehr geehrte/r {userName}"
#. js-lingui-id: lIdkf2
#: src/emails/clean-suspended-workspace.email.tsx
msgid "Dear {userName},"
msgstr "Sehr geehrte/r {userName},"
#. js-lingui-id: NTwcnq
#: src/emails/clean-suspended-workspace.email.tsx
msgid "Deleted Workspace"
msgstr "Gelöschter Arbeitsbereich"
#. js-lingui-id: S3uuQj
#: src/emails/validate-approved-access-domain.email.tsx
msgid "email addresses to join your workspace without requiring an invitation."
msgstr "E-Mail-Adressen, um Ihrem Arbeitsbereich ohne Einladung beizutreten."
#. js-lingui-id: tGme7M
#: src/emails/send-invite-link.email.tsx
#: src/emails/send-invite-link.email.tsx:50
msgid "has invited you to join a workspace called "
msgstr "hat Sie eingeladen, einem Workspace namens "
#. js-lingui-id: uzTaYi
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:24
msgid "Hello"
msgstr "Hallo"
#. js-lingui-id: Xa0d85
#: src/emails/clean-suspended-workspace.email.tsx
msgid "Hello,"
msgstr "Hallo,"
#. js-lingui-id: eE1nG1
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:40
msgid "If you did not initiate this change, please contact your workspace owner immediately."
msgstr "Wenn Sie diese Änderung nicht veranlasst haben, kontaktieren Sie bitte umgehend den Eigentümer Ihres Workspaces."
#. js-lingui-id: Gz91L8
#: src/emails/warn-suspended-workspace.email.tsx
#: src/emails/warn-suspended-workspace.email.tsx:46
msgid "If you wish to continue using Twenty, please update your subscription within the next {remainingDays} {dayOrDays}."
msgstr "Wenn Sie Twenty weiterhin nutzen möchten, aktualisieren Sie bitte Ihr Abonnement innerhalb der nächsten {remainingDays} {dayOrDays}."
#. js-lingui-id: 0weyko
#: src/emails/clean-suspended-workspace.email.tsx
#: src/emails/clean-suspended-workspace.email.tsx:36
msgid "If you wish to use Twenty again, you can create a new workspace."
msgstr "Wenn Sie Twenty erneut nutzen möchten, können Sie einen neuen Workspace erstellen."
#. js-lingui-id: 7JuhZQ
#: src/emails/warn-suspended-workspace.email.tsx
#: src/emails/warn-suspended-workspace.email.tsx:34
msgid "It appears that your workspace <0>{workspaceDisplayName}</0> has been suspended for {daysSinceInactive} days."
msgstr "Es scheint, dass Ihr Workspace <0>{workspaceDisplayName}</0> seit {daysSinceInactive} Tagen gesperrt ist."
#. js-lingui-id: PviVyk
#: src/emails/send-invite-link.email.tsx
#: src/emails/send-invite-link.email.tsx:42
msgid "Join your team on Twenty"
msgstr "Treten Sie Ihrem Team auf Twenty bei"
#. js-lingui-id: ogtYkT
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:29
msgid "Password updated"
msgstr "Passwort wurde aktualisiert"
#. js-lingui-id: Yucjaa
#: src/emails/validate-approved-access-domain.email.tsx
msgid "Please validate this domain to allow users with"
msgstr "Bitte validieren Sie diese Domain, um Benutzern mit"
#. js-lingui-id: u3Ns4p
#: src/emails/validate-approved-access-domain.email.tsx
#~ msgid "Please validate this domain to allow users with <0>@{domain}</0> email addresses to join your workspace without requiring an invitation."
#~ msgstr "Please validate this domain to allow users with <0>@{domain}</0> email addresses to join your workspace without requiring an invitation."
#. js-lingui-id: OfhWJH
#: src/emails/password-reset-link.email.tsx
#: src/emails/password-reset-link.email.tsx:24
msgid "Reset"
msgstr "Zurücksetzen"
#. js-lingui-id: RE5NiU
#: src/emails/password-reset-link.email.tsx
#: src/emails/password-reset-link.email.tsx:23
msgid "Reset your password 🗝"
msgstr "Setzen Sie Ihr Passwort zurück 🗝"
#. js-lingui-id: UBadaJ
#: src/emails/warn-suspended-workspace.email.tsx
msgid "Suspended Workspace "
msgstr "Ausgesetzter Arbeitsbereich "
#. js-lingui-id: 7yDt8q
#: src/emails/send-email-verification-link.email.tsx
#: src/emails/send-email-verification-link.email.tsx:27
msgid "Thanks for registering for an account on Twenty! Before we get started, we just need to confirm that this is you. Click above to verify your email address."
msgstr "Vielen Dank für Ihre Registrierung bei Twenty! Bevor wir beginnen, müssen wir nur bestätigen, dass Sie es sind. Klicken Sie oben, um Ihre E-Mail-Adresse zu verifizieren."
#. js-lingui-id: igorB1
#: src/emails/warn-suspended-workspace.email.tsx
#: src/emails/warn-suspended-workspace.email.tsx:40
msgid "The workspace will be deactivated in {remainingDays} {dayOrDays}, and all its data will be deleted."
msgstr "Der Workspace wird in {remainingDays} {dayOrDays} deaktiviert, und alle Daten werden gelöscht."
#. js-lingui-id: 7OEHy1
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:34
msgid "This is a confirmation that password for your account ({email}) was successfully changed on {formattedDate}."
msgstr "Dies ist eine Bestätigung, dass das Passwort für Ihr Konto ({email}) am {formattedDate} erfolgreich geändert wurde."
#. js-lingui-id: wSOsS+
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:36
#~ msgid "This is a confirmation that password for your account ({email}) was successfully changed on {formattedDate}.<0/><1/>If you did not initiate this change, please contact your workspace owner immediately."
#~ msgstr "This is a confirmation that password for your account ({email}) was successfully changed on {formattedDate}.<0/><1/>If you did not initiate this change, please contact your workspace owner immediately."
#. js-lingui-id: R4gMjN
#: src/emails/password-reset-link.email.tsx
#: src/emails/password-reset-link.email.tsx:26
msgid "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:"
msgstr "Dieser Link ist nur für die nächsten {duration} gültig. Wenn der Link nicht funktioniert, können Sie den Anmeldebestätigungslink direkt verwenden:"
#. js-lingui-id: 2oA637
#: src/emails/password-reset-link.email.tsx
#: src/emails/password-reset-link.email.tsx:28
#~ msgid "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:<0/>"
#~ msgstr "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:<0/>"
#. js-lingui-id: H0v4yC
#: src/emails/warn-suspended-workspace.email.tsx
#: src/emails/warn-suspended-workspace.email.tsx:53
msgid "Update your subscription"
msgstr "Aktualisieren Sie Ihr Abonnement"
#. js-lingui-id: QbiUqd
#: src/emails/validate-approved-access-domain.email.tsx
#: src/emails/validate-approved-access-domain.email.tsx
msgid "Validate domain"
msgstr "Domain validieren"
#. js-lingui-id: wCKkSr
#: src/emails/send-email-verification-link.email.tsx
#: src/emails/send-email-verification-link.email.tsx:23
msgid "Verify Email"
msgstr "E-Mail verifizieren"
#. js-lingui-id: 9MqLGX
#: src/emails/clean-suspended-workspace.email.tsx
msgid "Your workspace <0>{workspaceDisplayName}</0> has been deleted as your subscription expired {daysSinceInactive} days ago."
msgstr "Ihr Workspace <0>{workspaceDisplayName}</0> wurde gelöscht, da Ihr Abonnement vor {daysSinceInactive} Tagen abgelaufen ist."
#: src/emails/clean-suspended-workspace.email.tsx:27
msgid "Your workspace <0>{workspaceDisplayName}</0> has been deleted as your subscription expired {inactiveDaysBeforeDelete} days ago."
msgstr "Ihr Workspace <0>{workspaceDisplayName}</0> wurde gelöscht, da Ihr Abonnement vor {inactiveDaysBeforeDelete} Tagen abgelaufen ist."
#. js-lingui-id: KFmFrQ
#: src/emails/clean-suspended-workspace.email.tsx
#~ msgid "Your workspace <0>{workspaceDisplayName}</0> has been deleted as your subscription expired {inactiveDaysBeforeDelete} days ago."
#~ msgstr "Your workspace <0>{workspaceDisplayName}</0> has been deleted as your subscription expired {inactiveDaysBeforeDelete} days ago."
+29 -103
View File
@@ -8,7 +8,8 @@ msgstr ""
"Language: el\n"
"Project-Id-Version: cf448e737e0d6d7b78742f963d761c61\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2025-01-01 00:00\n"
"PO-Revision-Date: 2025-01-01 00:00
"
"Last-Translator: \n"
"Language-Team: Greek\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
@@ -18,178 +19,103 @@ msgstr ""
"X-Crowdin-File: /packages/twenty-emails/src/locales/en.po\n"
"X-Crowdin-File-ID: 27\n"
#. js-lingui-id: 4WPI3S
#: src/emails/send-invite-link.email.tsx
#: src/emails/send-invite-link.email.tsx:57
msgid "Accept invite"
msgstr "Αποδοχή πρόσκλησης"
#. js-lingui-id: Yxj+Uc
#: src/emails/clean-suspended-workspace.email.tsx
#: src/emails/clean-suspended-workspace.email.tsx:33
msgid "All data in this workspace has been permanently deleted."
msgstr "Όλα τα δεδομένα σε αυτόν τον χώρο εργασίας έχουν διαγραφεί μόνιμα."
#. js-lingui-id: RPHFhC
#: src/emails/send-email-verification-link.email.tsx
#: src/emails/send-email-verification-link.email.tsx:22
msgid "Confirm your email address"
msgstr "Επιβεβαιώστε τη διεύθυνση email σας"
#. js-lingui-id: nvkBPN
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:46
msgid "Connect to Twenty"
msgstr "Συνδεθείτε στο Twenty"
#. js-lingui-id: jPQSEz
#: src/emails/clean-suspended-workspace.email.tsx
msgid "Create a new workspace"
msgstr "Δημιουργία νέου χώρου εργασίας"
#. js-lingui-id: JRzgV7
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:24
#~ msgid "Dear"
#~ msgstr "Dear"
#. js-lingui-id: Lm5BBI
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:24
msgid "Dear {userName}"
msgstr "Αγαπητέ/ή {userName}"
#. js-lingui-id: lIdkf2
#: src/emails/clean-suspended-workspace.email.tsx
msgid "Dear {userName},"
msgstr "Αγαπητέ/ή {userName},"
#. js-lingui-id: NTwcnq
#: src/emails/clean-suspended-workspace.email.tsx
msgid "Deleted Workspace"
msgstr "Διαγραμμένος Χώρος Εργασίας"
#. js-lingui-id: S3uuQj
#: src/emails/validate-approved-access-domain.email.tsx
msgid "email addresses to join your workspace without requiring an invitation."
msgstr "διευθύνσεις ηλεκτρονικού ταχυδρομείου για να συμμετάσχουν στο χώρο εργασίας σας χωρίς να απαιτείται πρόσκληση."
#. js-lingui-id: tGme7M
#: src/emails/send-invite-link.email.tsx
#: src/emails/send-invite-link.email.tsx:50
msgid "has invited you to join a workspace called "
msgstr "σας έχει προσκαλέσει να συμμετάσχετε σε έναν χώρο εργασίας με την ονομασία "
#. js-lingui-id: uzTaYi
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:24
msgid "Hello"
msgstr "Γεια σας"
#. js-lingui-id: Xa0d85
#: src/emails/clean-suspended-workspace.email.tsx
msgid "Hello,"
msgstr "Γεια σας,"
#. js-lingui-id: eE1nG1
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:40
msgid "If you did not initiate this change, please contact your workspace owner immediately."
msgstr "Εάν δεν έχετε προβεί εσείς σε αυτήν την αλλαγή, παρακαλώ επικοινωνήστε άμεσα με τον ιδιοκτήτη του χώρου εργασίας σας."
#. js-lingui-id: Gz91L8
#: src/emails/warn-suspended-workspace.email.tsx
#: src/emails/warn-suspended-workspace.email.tsx:46
msgid "If you wish to continue using Twenty, please update your subscription within the next {remainingDays} {dayOrDays}."
msgstr "Εάν επιθυμείτε να συνεχίστε να χρησιμοποιείτε το Twenty, παρακαλούμε ενημερώστε την συνδρομή σας εντός των επόμενων {remainingDays} {dayOrDays}."
#. js-lingui-id: 0weyko
#: src/emails/clean-suspended-workspace.email.tsx
#: src/emails/clean-suspended-workspace.email.tsx:36
msgid "If you wish to use Twenty again, you can create a new workspace."
msgstr "Εάν επιθυμείτε να χρησιμοποιήσετε ξανά το Twenty, μπορείτε να δημιουργήσετε ένα νέο χώρο εργασίας."
#. js-lingui-id: 7JuhZQ
#: src/emails/warn-suspended-workspace.email.tsx
#: src/emails/warn-suspended-workspace.email.tsx:34
msgid "It appears that your workspace <0>{workspaceDisplayName}</0> has been suspended for {daysSinceInactive} days."
msgstr "Φαίνεται ότι ο χώρος εργασίας σας <0>{workspaceDisplayName}</0> έχει ανασταλεί για {daysSinceInactive} ημέρες."
#. js-lingui-id: PviVyk
#: src/emails/send-invite-link.email.tsx
#: src/emails/send-invite-link.email.tsx:42
msgid "Join your team on Twenty"
msgstr "Συμμετέχετε στην ομάδα σας στο Twenty"
#. js-lingui-id: ogtYkT
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:29
msgid "Password updated"
msgstr "Ο κωδικός έχει ενημερωθεί"
#. js-lingui-id: Yucjaa
#: src/emails/validate-approved-access-domain.email.tsx
msgid "Please validate this domain to allow users with"
msgstr "Παρακαλώ επικυρώστε αυτόν τον τομέα για να επιτρέψετε στους χρήστες με"
#. js-lingui-id: u3Ns4p
#: src/emails/validate-approved-access-domain.email.tsx
#~ msgid "Please validate this domain to allow users with <0>@{domain}</0> email addresses to join your workspace without requiring an invitation."
#~ msgstr "Please validate this domain to allow users with <0>@{domain}</0> email addresses to join your workspace without requiring an invitation."
#. js-lingui-id: OfhWJH
#: src/emails/password-reset-link.email.tsx
#: src/emails/password-reset-link.email.tsx:24
msgid "Reset"
msgstr "Επαναφορά"
#. js-lingui-id: RE5NiU
#: src/emails/password-reset-link.email.tsx
#: src/emails/password-reset-link.email.tsx:23
msgid "Reset your password 🗝"
msgstr "Επαναφέρετε τον κωδικό σας 🗝"
#. js-lingui-id: UBadaJ
#: src/emails/warn-suspended-workspace.email.tsx
msgid "Suspended Workspace "
msgstr "Ανασταλμένος Χώρος Εργασίας "
#. js-lingui-id: 7yDt8q
#: src/emails/send-email-verification-link.email.tsx
#: src/emails/send-email-verification-link.email.tsx:27
msgid "Thanks for registering for an account on Twenty! Before we get started, we just need to confirm that this is you. Click above to verify your email address."
msgstr "Σας ευχαριστούμε που εγγραφήκατε για έναν λογαριασμό στο Twenty! Πριν ξεκινήσουμε, πρέπει μόνο να επιβεβαιώσουμε ότι αυτός είστε εσείς. Κάντε κλικ παραπάνω για να επαληθεύσετε τη διεύθυνση email σας."
#. js-lingui-id: igorB1
#: src/emails/warn-suspended-workspace.email.tsx
#: src/emails/warn-suspended-workspace.email.tsx:40
msgid "The workspace will be deactivated in {remainingDays} {dayOrDays}, and all its data will be deleted."
msgstr "Ο χώρος εργασίας θα απενεργοποιηθεί σε {remainingDays} {dayOrDays}, και όλα τα δεδομένα του θα διαγραφούν."
#. js-lingui-id: 7OEHy1
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:34
msgid "This is a confirmation that password for your account ({email}) was successfully changed on {formattedDate}."
msgstr "Αυτό είναι ένα επιβεβαίωση ότι ο κωδικός για τον λογαριασμό σας ({email}) άλλαξε με επιτυχία στις {formattedDate}."
#. js-lingui-id: wSOsS+
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:36
#~ msgid "This is a confirmation that password for your account ({email}) was successfully changed on {formattedDate}.<0/><1/>If you did not initiate this change, please contact your workspace owner immediately."
#~ msgstr "This is a confirmation that password for your account ({email}) was successfully changed on {formattedDate}.<0/><1/>If you did not initiate this change, please contact your workspace owner immediately."
#. js-lingui-id: R4gMjN
#: src/emails/password-reset-link.email.tsx
#: src/emails/password-reset-link.email.tsx:26
msgid "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:"
msgstr "Αυτός ο σύνδεσμος ισχύει μόνο για τις επόμενες {duration}. Εάν ο σύνδεσμος δεν λειτουργεί, μπορείτε να χρησιμοποιήσετε τον σύνδεσμο επαλήθευσης σύνδεσης απευθείας:"
#. js-lingui-id: 2oA637
#: src/emails/password-reset-link.email.tsx
#: src/emails/password-reset-link.email.tsx:28
#~ msgid "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:<0/>"
#~ msgstr "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:<0/>"
#. js-lingui-id: H0v4yC
#: src/emails/warn-suspended-workspace.email.tsx
#: src/emails/warn-suspended-workspace.email.tsx:53
msgid "Update your subscription"
msgstr "Ενημερώστε τη συνδρομή σας"
#. js-lingui-id: QbiUqd
#: src/emails/validate-approved-access-domain.email.tsx
#: src/emails/validate-approved-access-domain.email.tsx
msgid "Validate domain"
msgstr "Επικύρωση τομέα"
#. js-lingui-id: wCKkSr
#: src/emails/send-email-verification-link.email.tsx
#: src/emails/send-email-verification-link.email.tsx:23
msgid "Verify Email"
msgstr "Επαληθεύστε το Email"
#. js-lingui-id: 9MqLGX
#: src/emails/clean-suspended-workspace.email.tsx
msgid "Your workspace <0>{workspaceDisplayName}</0> has been deleted as your subscription expired {daysSinceInactive} days ago."
msgstr "Ο χώρος εργασίας σας <0>{workspaceDisplayName}</0> έχει διαγραφεί καθώς η συνδρομή σας έληξε {daysSinceInactive} ημέρες πριν."
#: src/emails/clean-suspended-workspace.email.tsx:27
msgid "Your workspace <0>{workspaceDisplayName}</0> has been deleted as your subscription expired {inactiveDaysBeforeDelete} days ago."
msgstr "Ο χώρος εργασίας σας <0>{workspaceDisplayName}</0> έχει διαγραφεί καθώς η συνδρομή σας έληξε {inactiveDaysBeforeDelete} ημέρες πριν."
#. js-lingui-id: KFmFrQ
#: src/emails/clean-suspended-workspace.email.tsx
#~ msgid "Your workspace <0>{workspaceDisplayName}</0> has been deleted as your subscription expired {inactiveDaysBeforeDelete} days ago."
#~ msgstr "Your workspace <0>{workspaceDisplayName}</0> has been deleted as your subscription expired {inactiveDaysBeforeDelete} days ago."
+27 -103
View File
@@ -13,178 +13,102 @@ msgstr ""
"Language-Team: \n"
"Plural-Forms: \n"
#. js-lingui-id: 4WPI3S
#: src/emails/send-invite-link.email.tsx
#: src/emails/send-invite-link.email.tsx:57
msgid "Accept invite"
msgstr "Accept invite"
#. js-lingui-id: Yxj+Uc
#: src/emails/clean-suspended-workspace.email.tsx
#: src/emails/clean-suspended-workspace.email.tsx:33
msgid "All data in this workspace has been permanently deleted."
msgstr "All data in this workspace has been permanently deleted."
#. js-lingui-id: RPHFhC
#: src/emails/send-email-verification-link.email.tsx
#: src/emails/send-email-verification-link.email.tsx:22
msgid "Confirm your email address"
msgstr "Confirm your email address"
#. js-lingui-id: nvkBPN
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:46
msgid "Connect to Twenty"
msgstr "Connect to Twenty"
#. js-lingui-id: jPQSEz
#: src/emails/clean-suspended-workspace.email.tsx
msgid "Create a new workspace"
msgstr "Create a new workspace"
#. js-lingui-id: JRzgV7
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:24
#~ msgid "Dear"
#~ msgstr "Dear"
#. js-lingui-id: Lm5BBI
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:24
msgid "Dear {userName}"
msgstr "Dear {userName}"
#. js-lingui-id: lIdkf2
#: src/emails/clean-suspended-workspace.email.tsx
msgid "Dear {userName},"
msgstr "Dear {userName},"
#. js-lingui-id: NTwcnq
#: src/emails/clean-suspended-workspace.email.tsx
msgid "Deleted Workspace"
msgstr "Deleted Workspace"
#. js-lingui-id: S3uuQj
#: src/emails/validate-approved-access-domain.email.tsx
msgid "email addresses to join your workspace without requiring an invitation."
msgstr "email addresses to join your workspace without requiring an invitation."
#. js-lingui-id: tGme7M
#: src/emails/send-invite-link.email.tsx
#: src/emails/send-invite-link.email.tsx:50
msgid "has invited you to join a workspace called "
msgstr "has invited you to join a workspace called "
#. js-lingui-id: uzTaYi
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:24
msgid "Hello"
msgstr "Hello"
#. js-lingui-id: Xa0d85
#: src/emails/clean-suspended-workspace.email.tsx
msgid "Hello,"
msgstr "Hello,"
#. js-lingui-id: eE1nG1
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:40
msgid "If you did not initiate this change, please contact your workspace owner immediately."
msgstr "If you did not initiate this change, please contact your workspace owner immediately."
#. js-lingui-id: Gz91L8
#: src/emails/warn-suspended-workspace.email.tsx
#: src/emails/warn-suspended-workspace.email.tsx:46
msgid "If you wish to continue using Twenty, please update your subscription within the next {remainingDays} {dayOrDays}."
msgstr "If you wish to continue using Twenty, please update your subscription within the next {remainingDays} {dayOrDays}."
#. js-lingui-id: 0weyko
#: src/emails/clean-suspended-workspace.email.tsx
#: src/emails/clean-suspended-workspace.email.tsx:36
msgid "If you wish to use Twenty again, you can create a new workspace."
msgstr "If you wish to use Twenty again, you can create a new workspace."
#. js-lingui-id: 7JuhZQ
#: src/emails/warn-suspended-workspace.email.tsx
#: src/emails/warn-suspended-workspace.email.tsx:34
msgid "It appears that your workspace <0>{workspaceDisplayName}</0> has been suspended for {daysSinceInactive} days."
msgstr "It appears that your workspace <0>{workspaceDisplayName}</0> has been suspended for {daysSinceInactive} days."
#. js-lingui-id: PviVyk
#: src/emails/send-invite-link.email.tsx
#: src/emails/send-invite-link.email.tsx:42
msgid "Join your team on Twenty"
msgstr "Join your team on Twenty"
#. js-lingui-id: ogtYkT
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:29
msgid "Password updated"
msgstr "Password updated"
#. js-lingui-id: Yucjaa
#: src/emails/validate-approved-access-domain.email.tsx
msgid "Please validate this domain to allow users with"
msgstr "Please validate this domain to allow users with"
#. js-lingui-id: u3Ns4p
#: src/emails/validate-approved-access-domain.email.tsx
#~ msgid "Please validate this domain to allow users with <0>@{domain}</0> email addresses to join your workspace without requiring an invitation."
#~ msgstr "Please validate this domain to allow users with <0>@{domain}</0> email addresses to join your workspace without requiring an invitation."
#. js-lingui-id: OfhWJH
#: src/emails/password-reset-link.email.tsx
#: src/emails/password-reset-link.email.tsx:24
msgid "Reset"
msgstr "Reset"
#. js-lingui-id: RE5NiU
#: src/emails/password-reset-link.email.tsx
#: src/emails/password-reset-link.email.tsx:23
msgid "Reset your password 🗝"
msgstr "Reset your password 🗝"
#. js-lingui-id: UBadaJ
#: src/emails/warn-suspended-workspace.email.tsx
msgid "Suspended Workspace "
msgstr "Suspended Workspace "
#. js-lingui-id: 7yDt8q
#: src/emails/send-email-verification-link.email.tsx
#: src/emails/send-email-verification-link.email.tsx:27
msgid "Thanks for registering for an account on Twenty! Before we get started, we just need to confirm that this is you. Click above to verify your email address."
msgstr "Thanks for registering for an account on Twenty! Before we get started, we just need to confirm that this is you. Click above to verify your email address."
#. js-lingui-id: igorB1
#: src/emails/warn-suspended-workspace.email.tsx
#: src/emails/warn-suspended-workspace.email.tsx:40
msgid "The workspace will be deactivated in {remainingDays} {dayOrDays}, and all its data will be deleted."
msgstr "The workspace will be deactivated in {remainingDays} {dayOrDays}, and all its data will be deleted."
#. js-lingui-id: 7OEHy1
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:34
msgid "This is a confirmation that password for your account ({email}) was successfully changed on {formattedDate}."
msgstr "This is a confirmation that password for your account ({email}) was successfully changed on {formattedDate}."
#. js-lingui-id: wSOsS+
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:36
#~ msgid "This is a confirmation that password for your account ({email}) was successfully changed on {formattedDate}.<0/><1/>If you did not initiate this change, please contact your workspace owner immediately."
#~ msgstr "This is a confirmation that password for your account ({email}) was successfully changed on {formattedDate}.<0/><1/>If you did not initiate this change, please contact your workspace owner immediately."
#. js-lingui-id: R4gMjN
#: src/emails/password-reset-link.email.tsx
#: src/emails/password-reset-link.email.tsx:26
msgid "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:"
msgstr "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:"
#. js-lingui-id: 2oA637
#: src/emails/password-reset-link.email.tsx
#: src/emails/password-reset-link.email.tsx:28
#~ msgid "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:<0/>"
#~ msgstr "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:<0/>"
#. js-lingui-id: H0v4yC
#: src/emails/warn-suspended-workspace.email.tsx
#: src/emails/warn-suspended-workspace.email.tsx:53
msgid "Update your subscription"
msgstr "Update your subscription"
#. js-lingui-id: QbiUqd
#: src/emails/validate-approved-access-domain.email.tsx
#: src/emails/validate-approved-access-domain.email.tsx
msgid "Validate domain"
msgstr "Validate domain"
#. js-lingui-id: wCKkSr
#: src/emails/send-email-verification-link.email.tsx
#: src/emails/send-email-verification-link.email.tsx:23
msgid "Verify Email"
msgstr "Verify Email"
#. js-lingui-id: 9MqLGX
#: src/emails/clean-suspended-workspace.email.tsx
msgid "Your workspace <0>{workspaceDisplayName}</0> has been deleted as your subscription expired {daysSinceInactive} days ago."
msgstr "Your workspace <0>{workspaceDisplayName}</0> has been deleted as your subscription expired {daysSinceInactive} days ago."
#. js-lingui-id: KFmFrQ
#: src/emails/clean-suspended-workspace.email.tsx
#~ msgid "Your workspace <0>{workspaceDisplayName}</0> has been deleted as your subscription expired {inactiveDaysBeforeDelete} days ago."
#~ msgstr "Your workspace <0>{workspaceDisplayName}</0> has been deleted as your subscription expired {inactiveDaysBeforeDelete} days ago."
#: src/emails/clean-suspended-workspace.email.tsx:27
msgid "Your workspace <0>{workspaceDisplayName}</0> has been deleted as your subscription expired {inactiveDaysBeforeDelete} days ago."
msgstr "Your workspace <0>{workspaceDisplayName}</0> has been deleted as your subscription expired {inactiveDaysBeforeDelete} days ago."
+29 -103
View File
@@ -8,7 +8,8 @@ msgstr ""
"Language: es\n"
"Project-Id-Version: cf448e737e0d6d7b78742f963d761c61\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2025-01-01 00:00\n"
"PO-Revision-Date: 2025-01-01 00:00
"
"Last-Translator: \n"
"Language-Team: Spanish\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
@@ -18,178 +19,103 @@ msgstr ""
"X-Crowdin-File: /packages/twenty-emails/src/locales/en.po\n"
"X-Crowdin-File-ID: 27\n"
#. js-lingui-id: 4WPI3S
#: src/emails/send-invite-link.email.tsx
#: src/emails/send-invite-link.email.tsx:57
msgid "Accept invite"
msgstr "Aceptar invitación"
#. js-lingui-id: Yxj+Uc
#: src/emails/clean-suspended-workspace.email.tsx
#: src/emails/clean-suspended-workspace.email.tsx:33
msgid "All data in this workspace has been permanently deleted."
msgstr "Todos los datos de este espacio de trabajo han sido eliminados permanentemente."
#. js-lingui-id: RPHFhC
#: src/emails/send-email-verification-link.email.tsx
#: src/emails/send-email-verification-link.email.tsx:22
msgid "Confirm your email address"
msgstr "Confirma tu dirección de correo electrónico"
#. js-lingui-id: nvkBPN
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:46
msgid "Connect to Twenty"
msgstr "Conéctate a Twenty"
#. js-lingui-id: jPQSEz
#: src/emails/clean-suspended-workspace.email.tsx
msgid "Create a new workspace"
msgstr "Crea un nuevo espacio de trabajo"
#. js-lingui-id: JRzgV7
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:24
#~ msgid "Dear"
#~ msgstr "Dear"
#. js-lingui-id: Lm5BBI
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:24
msgid "Dear {userName}"
msgstr "Estimado/a {userName}"
#. js-lingui-id: lIdkf2
#: src/emails/clean-suspended-workspace.email.tsx
msgid "Dear {userName},"
msgstr "Estimado/a {userName},"
#. js-lingui-id: NTwcnq
#: src/emails/clean-suspended-workspace.email.tsx
msgid "Deleted Workspace"
msgstr "Espacio de trabajo eliminado"
#. js-lingui-id: S3uuQj
#: src/emails/validate-approved-access-domain.email.tsx
msgid "email addresses to join your workspace without requiring an invitation."
msgstr "direcciones de correo electrónico para unirse a su espacio de trabajo sin requerir una invitación."
#. js-lingui-id: tGme7M
#: src/emails/send-invite-link.email.tsx
#: src/emails/send-invite-link.email.tsx:50
msgid "has invited you to join a workspace called "
msgstr "te ha invitado a unirte a un espacio de trabajo llamado "
#. js-lingui-id: uzTaYi
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:24
msgid "Hello"
msgstr "Hola"
#. js-lingui-id: Xa0d85
#: src/emails/clean-suspended-workspace.email.tsx
msgid "Hello,"
msgstr "Hola,"
#. js-lingui-id: eE1nG1
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:40
msgid "If you did not initiate this change, please contact your workspace owner immediately."
msgstr "Si no iniciaste este cambio, contacta inmediatamente al propietario de tu espacio de trabajo."
#. js-lingui-id: Gz91L8
#: src/emails/warn-suspended-workspace.email.tsx
#: src/emails/warn-suspended-workspace.email.tsx:46
msgid "If you wish to continue using Twenty, please update your subscription within the next {remainingDays} {dayOrDays}."
msgstr "Si deseas seguir usando Twenty, actualiza tu suscripción en los próximos {remainingDays} {dayOrDays}."
#. js-lingui-id: 0weyko
#: src/emails/clean-suspended-workspace.email.tsx
#: src/emails/clean-suspended-workspace.email.tsx:36
msgid "If you wish to use Twenty again, you can create a new workspace."
msgstr "Si deseas usar Twenty nuevamente, puedes crear un nuevo espacio de trabajo."
#. js-lingui-id: 7JuhZQ
#: src/emails/warn-suspended-workspace.email.tsx
#: src/emails/warn-suspended-workspace.email.tsx:34
msgid "It appears that your workspace <0>{workspaceDisplayName}</0> has been suspended for {daysSinceInactive} days."
msgstr "Parece que tu espacio de trabajo <0>{workspaceDisplayName}</0> ha estado suspendido durante {daysSinceInactive} días."
#. js-lingui-id: PviVyk
#: src/emails/send-invite-link.email.tsx
#: src/emails/send-invite-link.email.tsx:42
msgid "Join your team on Twenty"
msgstr "Únete a tu equipo en Twenty"
#. js-lingui-id: ogtYkT
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:29
msgid "Password updated"
msgstr "Contraseña actualizada"
#. js-lingui-id: Yucjaa
#: src/emails/validate-approved-access-domain.email.tsx
msgid "Please validate this domain to allow users with"
msgstr "Por favor, valide este dominio para permitir a los usuarios con"
#. js-lingui-id: u3Ns4p
#: src/emails/validate-approved-access-domain.email.tsx
#~ msgid "Please validate this domain to allow users with <0>@{domain}</0> email addresses to join your workspace without requiring an invitation."
#~ msgstr "Please validate this domain to allow users with <0>@{domain}</0> email addresses to join your workspace without requiring an invitation."
#. js-lingui-id: OfhWJH
#: src/emails/password-reset-link.email.tsx
#: src/emails/password-reset-link.email.tsx:24
msgid "Reset"
msgstr "Restablecer"
#. js-lingui-id: RE5NiU
#: src/emails/password-reset-link.email.tsx
#: src/emails/password-reset-link.email.tsx:23
msgid "Reset your password 🗝"
msgstr "Restablece tu contraseña 🗝"
#. js-lingui-id: UBadaJ
#: src/emails/warn-suspended-workspace.email.tsx
msgid "Suspended Workspace "
msgstr "Espacio de trabajo suspendido "
#. js-lingui-id: 7yDt8q
#: src/emails/send-email-verification-link.email.tsx
#: src/emails/send-email-verification-link.email.tsx:27
msgid "Thanks for registering for an account on Twenty! Before we get started, we just need to confirm that this is you. Click above to verify your email address."
msgstr "¡Gracias por registrarte en Twenty! Antes de comenzar, solo necesitamos confirmar que eres tú. Haz clic arriba para verificar tu dirección de correo electrónico."
#. js-lingui-id: igorB1
#: src/emails/warn-suspended-workspace.email.tsx
#: src/emails/warn-suspended-workspace.email.tsx:40
msgid "The workspace will be deactivated in {remainingDays} {dayOrDays}, and all its data will be deleted."
msgstr "El espacio de trabajo se desactivará en {remainingDays} {dayOrDays} y se eliminarán todos sus datos."
#. js-lingui-id: 7OEHy1
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:34
msgid "This is a confirmation that password for your account ({email}) was successfully changed on {formattedDate}."
msgstr "Esta es una confirmación de que la contraseña de tu cuenta ({email}) se cambió correctamente el {formattedDate}."
#. js-lingui-id: wSOsS+
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:36
#~ msgid "This is a confirmation that password for your account ({email}) was successfully changed on {formattedDate}.<0/><1/>If you did not initiate this change, please contact your workspace owner immediately."
#~ msgstr "This is a confirmation that password for your account ({email}) was successfully changed on {formattedDate}.<0/><1/>If you did not initiate this change, please contact your workspace owner immediately."
#. js-lingui-id: R4gMjN
#: src/emails/password-reset-link.email.tsx
#: src/emails/password-reset-link.email.tsx:26
msgid "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:"
msgstr "Este enlace solo es válido por los próximos {duration}. Si el enlace no funciona, puedes usar directamente el enlace de verificación de inicio de sesión:"
#. js-lingui-id: 2oA637
#: src/emails/password-reset-link.email.tsx
#: src/emails/password-reset-link.email.tsx:28
#~ msgid "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:<0/>"
#~ msgstr "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:<0/>"
#. js-lingui-id: H0v4yC
#: src/emails/warn-suspended-workspace.email.tsx
#: src/emails/warn-suspended-workspace.email.tsx:53
msgid "Update your subscription"
msgstr "Actualiza tu suscripción"
#. js-lingui-id: QbiUqd
#: src/emails/validate-approved-access-domain.email.tsx
#: src/emails/validate-approved-access-domain.email.tsx
msgid "Validate domain"
msgstr "Validar dominio"
#. js-lingui-id: wCKkSr
#: src/emails/send-email-verification-link.email.tsx
#: src/emails/send-email-verification-link.email.tsx:23
msgid "Verify Email"
msgstr "Verificar correo electrónico"
#. js-lingui-id: 9MqLGX
#: src/emails/clean-suspended-workspace.email.tsx
msgid "Your workspace <0>{workspaceDisplayName}</0> has been deleted as your subscription expired {daysSinceInactive} days ago."
msgstr "Tu espacio de trabajo <0>{workspaceDisplayName}</0> ha sido eliminado porque tu suscripción caducó hace {daysSinceInactive} días."
#: src/emails/clean-suspended-workspace.email.tsx:27
msgid "Your workspace <0>{workspaceDisplayName}</0> has been deleted as your subscription expired {inactiveDaysBeforeDelete} days ago."
msgstr "Tu espacio de trabajo <0>{workspaceDisplayName}</0> ha sido eliminado porque tu suscripción caducó hace {inactiveDaysBeforeDelete} días."
#. js-lingui-id: KFmFrQ
#: src/emails/clean-suspended-workspace.email.tsx
#~ msgid "Your workspace <0>{workspaceDisplayName}</0> has been deleted as your subscription expired {inactiveDaysBeforeDelete} days ago."
#~ msgstr "Your workspace <0>{workspaceDisplayName}</0> has been deleted as your subscription expired {inactiveDaysBeforeDelete} days ago."
+29 -103
View File
@@ -8,7 +8,8 @@ msgstr ""
"Language: fi\n"
"Project-Id-Version: cf448e737e0d6d7b78742f963d761c61\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2025-01-01 00:00\n"
"PO-Revision-Date: 2025-01-01 00:00
"
"Last-Translator: \n"
"Language-Team: Finnish\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
@@ -18,178 +19,103 @@ msgstr ""
"X-Crowdin-File: /packages/twenty-emails/src/locales/en.po\n"
"X-Crowdin-File-ID: 27\n"
#. js-lingui-id: 4WPI3S
#: src/emails/send-invite-link.email.tsx
#: src/emails/send-invite-link.email.tsx:57
msgid "Accept invite"
msgstr "Hyväksy kutsu"
#. js-lingui-id: Yxj+Uc
#: src/emails/clean-suspended-workspace.email.tsx
#: src/emails/clean-suspended-workspace.email.tsx:33
msgid "All data in this workspace has been permanently deleted."
msgstr "Kaikki tiedot tässä työtilassa on pysyvästi poistettu."
#. js-lingui-id: RPHFhC
#: src/emails/send-email-verification-link.email.tsx
#: src/emails/send-email-verification-link.email.tsx:22
msgid "Confirm your email address"
msgstr "Vahvista sähköpostiosoitteesi"
#. js-lingui-id: nvkBPN
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:46
msgid "Connect to Twenty"
msgstr "Yhdistä Twenty"
#. js-lingui-id: jPQSEz
#: src/emails/clean-suspended-workspace.email.tsx
msgid "Create a new workspace"
msgstr "Luo uusi työtila"
#. js-lingui-id: JRzgV7
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:24
#~ msgid "Dear"
#~ msgstr "Dear"
#. js-lingui-id: Lm5BBI
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:24
msgid "Dear {userName}"
msgstr "Hyvä {userName}"
#. js-lingui-id: lIdkf2
#: src/emails/clean-suspended-workspace.email.tsx
msgid "Dear {userName},"
msgstr "Hyvä {userName},"
#. js-lingui-id: NTwcnq
#: src/emails/clean-suspended-workspace.email.tsx
msgid "Deleted Workspace"
msgstr "Poistettu työtila"
#. js-lingui-id: S3uuQj
#: src/emails/validate-approved-access-domain.email.tsx
msgid "email addresses to join your workspace without requiring an invitation."
msgstr "sähköpostiosoitteet liittymään työtilaasi ilman kutsua."
#. js-lingui-id: tGme7M
#: src/emails/send-invite-link.email.tsx
#: src/emails/send-invite-link.email.tsx:50
msgid "has invited you to join a workspace called "
msgstr "on kutsunut sinut liittymään työtilaan nimeltä "
#. js-lingui-id: uzTaYi
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:24
msgid "Hello"
msgstr "Hei"
#. js-lingui-id: Xa0d85
#: src/emails/clean-suspended-workspace.email.tsx
msgid "Hello,"
msgstr "Hei,"
#. js-lingui-id: eE1nG1
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:40
msgid "If you did not initiate this change, please contact your workspace owner immediately."
msgstr "Jos et itse aloittanut tätä muutosta, ota heti yhteyttä työtilasi omistajaan."
#. js-lingui-id: Gz91L8
#: src/emails/warn-suspended-workspace.email.tsx
#: src/emails/warn-suspended-workspace.email.tsx:46
msgid "If you wish to continue using Twenty, please update your subscription within the next {remainingDays} {dayOrDays}."
msgstr "Jos haluat jatkaa Twenty:n käyttöä, päivitä tilauksesi seuraavan {remainingDays} {dayOrDays} kuluessa."
#. js-lingui-id: 0weyko
#: src/emails/clean-suspended-workspace.email.tsx
#: src/emails/clean-suspended-workspace.email.tsx:36
msgid "If you wish to use Twenty again, you can create a new workspace."
msgstr "Jos haluat käyttää Twentyä uudelleen, voit luoda uuden työtilan."
#. js-lingui-id: 7JuhZQ
#: src/emails/warn-suspended-workspace.email.tsx
#: src/emails/warn-suspended-workspace.email.tsx:34
msgid "It appears that your workspace <0>{workspaceDisplayName}</0> has been suspended for {daysSinceInactive} days."
msgstr "Vaikuttaa siltä, että työtilasi <0>{workspaceDisplayName}</0> on ollut jäädytettynä {daysSinceInactive} päivää."
#. js-lingui-id: PviVyk
#: src/emails/send-invite-link.email.tsx
#: src/emails/send-invite-link.email.tsx:42
msgid "Join your team on Twenty"
msgstr "Liity tiimiisi Twentyssä"
#. js-lingui-id: ogtYkT
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:29
msgid "Password updated"
msgstr "Salasana päivitetty"
#. js-lingui-id: Yucjaa
#: src/emails/validate-approved-access-domain.email.tsx
msgid "Please validate this domain to allow users with"
msgstr "Vahvista tämä verkkotunnus, jotta tämä sallitaan käyttäjille, joilla on"
#. js-lingui-id: u3Ns4p
#: src/emails/validate-approved-access-domain.email.tsx
#~ msgid "Please validate this domain to allow users with <0>@{domain}</0> email addresses to join your workspace without requiring an invitation."
#~ msgstr "Please validate this domain to allow users with <0>@{domain}</0> email addresses to join your workspace without requiring an invitation."
#. js-lingui-id: OfhWJH
#: src/emails/password-reset-link.email.tsx
#: src/emails/password-reset-link.email.tsx:24
msgid "Reset"
msgstr "Nollaa"
#. js-lingui-id: RE5NiU
#: src/emails/password-reset-link.email.tsx
#: src/emails/password-reset-link.email.tsx:23
msgid "Reset your password 🗝"
msgstr "Nollaa salasanasi 🗝"
#. js-lingui-id: UBadaJ
#: src/emails/warn-suspended-workspace.email.tsx
msgid "Suspended Workspace "
msgstr "Keskeytetty työtila "
#. js-lingui-id: 7yDt8q
#: src/emails/send-email-verification-link.email.tsx
#: src/emails/send-email-verification-link.email.tsx:27
msgid "Thanks for registering for an account on Twenty! Before we get started, we just need to confirm that this is you. Click above to verify your email address."
msgstr "Kiitos, että rekisteröidyit Twenty:n käyttäjäksi! Ennen kuin aloitamme, meidän täytyy vahvistaa, että kyseessä on sinä. Klikkaa yllä vahvistaaksesi sähköpostiosoitteesi."
#. js-lingui-id: igorB1
#: src/emails/warn-suspended-workspace.email.tsx
#: src/emails/warn-suspended-workspace.email.tsx:40
msgid "The workspace will be deactivated in {remainingDays} {dayOrDays}, and all its data will be deleted."
msgstr "Työtila poistetaan käytöstä {remainingDays} {dayOrDays} kuluttua, ja kaikki sen tiedot poistetaan."
#. js-lingui-id: 7OEHy1
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:34
msgid "This is a confirmation that password for your account ({email}) was successfully changed on {formattedDate}."
msgstr "Tämä on vahvistus siitä, että tilisi ({email}) salasana on vaihdettu onnistuneesti {formattedDate}."
#. js-lingui-id: wSOsS+
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:36
#~ msgid "This is a confirmation that password for your account ({email}) was successfully changed on {formattedDate}.<0/><1/>If you did not initiate this change, please contact your workspace owner immediately."
#~ msgstr "This is a confirmation that password for your account ({email}) was successfully changed on {formattedDate}.<0/><1/>If you did not initiate this change, please contact your workspace owner immediately."
#. js-lingui-id: R4gMjN
#: src/emails/password-reset-link.email.tsx
#: src/emails/password-reset-link.email.tsx:26
msgid "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:"
msgstr "Tämä linkki on voimassa vain seuraavat {duration}. Jos linkki ei toimi, voit käyttää suoraan kirjautumisen varmennuslinkkiä:"
#. js-lingui-id: 2oA637
#: src/emails/password-reset-link.email.tsx
#: src/emails/password-reset-link.email.tsx:28
#~ msgid "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:<0/>"
#~ msgstr "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:<0/>"
#. js-lingui-id: H0v4yC
#: src/emails/warn-suspended-workspace.email.tsx
#: src/emails/warn-suspended-workspace.email.tsx:53
msgid "Update your subscription"
msgstr "Päivitä tilauksesi"
#. js-lingui-id: QbiUqd
#: src/emails/validate-approved-access-domain.email.tsx
#: src/emails/validate-approved-access-domain.email.tsx
msgid "Validate domain"
msgstr "Vahvista verkkotunnus"
#. js-lingui-id: wCKkSr
#: src/emails/send-email-verification-link.email.tsx
#: src/emails/send-email-verification-link.email.tsx:23
msgid "Verify Email"
msgstr "Vahvista sähköposti"
#. js-lingui-id: 9MqLGX
#: src/emails/clean-suspended-workspace.email.tsx
msgid "Your workspace <0>{workspaceDisplayName}</0> has been deleted as your subscription expired {daysSinceInactive} days ago."
msgstr "Työtilasi <0>{workspaceDisplayName}</0> on poistettu, koska tilauksesi vanheni {daysSinceInactive} päivää sitten."
#: src/emails/clean-suspended-workspace.email.tsx:27
msgid "Your workspace <0>{workspaceDisplayName}</0> has been deleted as your subscription expired {inactiveDaysBeforeDelete} days ago."
msgstr "Työtilasi <0>{workspaceDisplayName}</0> on poistettu, koska tilauksesi vanheni {inactiveDaysBeforeDelete} päivää sitten."
#. js-lingui-id: KFmFrQ
#: src/emails/clean-suspended-workspace.email.tsx
#~ msgid "Your workspace <0>{workspaceDisplayName}</0> has been deleted as your subscription expired {inactiveDaysBeforeDelete} days ago."
#~ msgstr "Your workspace <0>{workspaceDisplayName}</0> has been deleted as your subscription expired {inactiveDaysBeforeDelete} days ago."
+29 -103
View File
@@ -8,7 +8,8 @@ msgstr ""
"Language: fr\n"
"Project-Id-Version: cf448e737e0d6d7b78742f963d761c61\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2025-01-01 00:00\n"
"PO-Revision-Date: 2025-01-01 00:00
"
"Last-Translator: \n"
"Language-Team: French\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
@@ -18,178 +19,103 @@ msgstr ""
"X-Crowdin-File: /packages/twenty-emails/src/locales/en.po\n"
"X-Crowdin-File-ID: 27\n"
#. js-lingui-id: 4WPI3S
#: src/emails/send-invite-link.email.tsx
#: src/emails/send-invite-link.email.tsx:57
msgid "Accept invite"
msgstr "Accepter l'invitation"
#. js-lingui-id: Yxj+Uc
#: src/emails/clean-suspended-workspace.email.tsx
#: src/emails/clean-suspended-workspace.email.tsx:33
msgid "All data in this workspace has been permanently deleted."
msgstr "Toutes les données de cet espace de travail ont été supprimées définitivement."
#. js-lingui-id: RPHFhC
#: src/emails/send-email-verification-link.email.tsx
#: src/emails/send-email-verification-link.email.tsx:22
msgid "Confirm your email address"
msgstr "Confirmez votre adresse e-mail"
#. js-lingui-id: nvkBPN
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:46
msgid "Connect to Twenty"
msgstr "Connectez-vous à Twenty"
#. js-lingui-id: jPQSEz
#: src/emails/clean-suspended-workspace.email.tsx
msgid "Create a new workspace"
msgstr "Créez un nouvel espace de travail"
#. js-lingui-id: JRzgV7
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:24
#~ msgid "Dear"
#~ msgstr "Dear"
#. js-lingui-id: Lm5BBI
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:24
msgid "Dear {userName}"
msgstr "Cher {userName}"
#. js-lingui-id: lIdkf2
#: src/emails/clean-suspended-workspace.email.tsx
msgid "Dear {userName},"
msgstr "Cher {userName},"
#. js-lingui-id: NTwcnq
#: src/emails/clean-suspended-workspace.email.tsx
msgid "Deleted Workspace"
msgstr "Espace de travail supprimé"
#. js-lingui-id: S3uuQj
#: src/emails/validate-approved-access-domain.email.tsx
msgid "email addresses to join your workspace without requiring an invitation."
msgstr "adresses e-mail pour rejoindre votre espace de travail sans nécessiter d'invitation."
#. js-lingui-id: tGme7M
#: src/emails/send-invite-link.email.tsx
#: src/emails/send-invite-link.email.tsx:50
msgid "has invited you to join a workspace called "
msgstr "vous a invité à rejoindre un espace de travail appelé "
#. js-lingui-id: uzTaYi
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:24
msgid "Hello"
msgstr "Bonjour"
#. js-lingui-id: Xa0d85
#: src/emails/clean-suspended-workspace.email.tsx
msgid "Hello,"
msgstr "Bonjour,"
#. js-lingui-id: eE1nG1
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:40
msgid "If you did not initiate this change, please contact your workspace owner immediately."
msgstr "Si vous n'avez pas initié ce changement, veuillez contacter immédiatement le propriétaire de votre espace de travail."
#. js-lingui-id: Gz91L8
#: src/emails/warn-suspended-workspace.email.tsx
#: src/emails/warn-suspended-workspace.email.tsx:46
msgid "If you wish to continue using Twenty, please update your subscription within the next {remainingDays} {dayOrDays}."
msgstr "Pour continuer à utiliser Twenty, veuillez mettre à jour votre abonnement dans les {remainingDays} {dayOrDays}."
#. js-lingui-id: 0weyko
#: src/emails/clean-suspended-workspace.email.tsx
#: src/emails/clean-suspended-workspace.email.tsx:36
msgid "If you wish to use Twenty again, you can create a new workspace."
msgstr "Pour réutiliser Twenty, vous pouvez créer un nouvel espace de travail."
#. js-lingui-id: 7JuhZQ
#: src/emails/warn-suspended-workspace.email.tsx
#: src/emails/warn-suspended-workspace.email.tsx:34
msgid "It appears that your workspace <0>{workspaceDisplayName}</0> has been suspended for {daysSinceInactive} days."
msgstr "Il semble que votre espace de travail <0>{workspaceDisplayName}</0> soit suspendu depuis {daysSinceInactive} jours."
#. js-lingui-id: PviVyk
#: src/emails/send-invite-link.email.tsx
#: src/emails/send-invite-link.email.tsx:42
msgid "Join your team on Twenty"
msgstr "Rejoignez votre équipe sur Twenty"
#. js-lingui-id: ogtYkT
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:29
msgid "Password updated"
msgstr "Mot de passe mis à jour"
#. js-lingui-id: Yucjaa
#: src/emails/validate-approved-access-domain.email.tsx
msgid "Please validate this domain to allow users with"
msgstr "Veuillez valider ce domaine pour permettre aux utilisateurs avec"
#. js-lingui-id: u3Ns4p
#: src/emails/validate-approved-access-domain.email.tsx
#~ msgid "Please validate this domain to allow users with <0>@{domain}</0> email addresses to join your workspace without requiring an invitation."
#~ msgstr "Please validate this domain to allow users with <0>@{domain}</0> email addresses to join your workspace without requiring an invitation."
#. js-lingui-id: OfhWJH
#: src/emails/password-reset-link.email.tsx
#: src/emails/password-reset-link.email.tsx:24
msgid "Reset"
msgstr "Réinitialiser"
#. js-lingui-id: RE5NiU
#: src/emails/password-reset-link.email.tsx
#: src/emails/password-reset-link.email.tsx:23
msgid "Reset your password 🗝"
msgstr "Réinitialisez votre mot de passe 🗝"
#. js-lingui-id: UBadaJ
#: src/emails/warn-suspended-workspace.email.tsx
msgid "Suspended Workspace "
msgstr "Espace de travail suspendu "
#. js-lingui-id: 7yDt8q
#: src/emails/send-email-verification-link.email.tsx
#: src/emails/send-email-verification-link.email.tsx:27
msgid "Thanks for registering for an account on Twenty! Before we get started, we just need to confirm that this is you. Click above to verify your email address."
msgstr "Merci de vous être inscrit sur Twenty ! Avant de commencer, nous devons confirmer votre identité. Cliquez ci-dessus pour vérifier votre adresse e-mail."
#. js-lingui-id: igorB1
#: src/emails/warn-suspended-workspace.email.tsx
#: src/emails/warn-suspended-workspace.email.tsx:40
msgid "The workspace will be deactivated in {remainingDays} {dayOrDays}, and all its data will be deleted."
msgstr "L'espace de travail sera désactivé dans {remainingDays} {dayOrDays} et toutes ses données seront supprimées."
#. js-lingui-id: 7OEHy1
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:34
msgid "This is a confirmation that password for your account ({email}) was successfully changed on {formattedDate}."
msgstr "Ceci est une confirmation que le mot de passe de votre compte ({email}) a été modifié avec succès le {formattedDate}."
#. js-lingui-id: wSOsS+
#: src/emails/password-update-notify.email.tsx
#: src/emails/password-update-notify.email.tsx:36
#~ msgid "This is a confirmation that password for your account ({email}) was successfully changed on {formattedDate}.<0/><1/>If you did not initiate this change, please contact your workspace owner immediately."
#~ msgstr "This is a confirmation that password for your account ({email}) was successfully changed on {formattedDate}.<0/><1/>If you did not initiate this change, please contact your workspace owner immediately."
#. js-lingui-id: R4gMjN
#: src/emails/password-reset-link.email.tsx
#: src/emails/password-reset-link.email.tsx:26
msgid "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:"
msgstr "Ce lien n'est valable que pour les {duration} suivants. Si le lien ne fonctionne pas, vous pouvez utiliser directement le lien de vérification de connexion :"
#. js-lingui-id: 2oA637
#: src/emails/password-reset-link.email.tsx
#: src/emails/password-reset-link.email.tsx:28
#~ msgid "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:<0/>"
#~ msgstr "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:<0/>"
#. js-lingui-id: H0v4yC
#: src/emails/warn-suspended-workspace.email.tsx
#: src/emails/warn-suspended-workspace.email.tsx:53
msgid "Update your subscription"
msgstr "Mettez à jour votre abonnement"
#. js-lingui-id: QbiUqd
#: src/emails/validate-approved-access-domain.email.tsx
#: src/emails/validate-approved-access-domain.email.tsx
msgid "Validate domain"
msgstr "Valider le domaine"
#. js-lingui-id: wCKkSr
#: src/emails/send-email-verification-link.email.tsx
#: src/emails/send-email-verification-link.email.tsx:23
msgid "Verify Email"
msgstr "Vérifiez l'e-mail"
#. js-lingui-id: 9MqLGX
#: src/emails/clean-suspended-workspace.email.tsx
msgid "Your workspace <0>{workspaceDisplayName}</0> has been deleted as your subscription expired {daysSinceInactive} days ago."
msgstr "Votre espace de travail <0>{workspaceDisplayName}</0> a été supprimé car votre abonnement a expiré il y a {daysSinceInactive} jours."
#: src/emails/clean-suspended-workspace.email.tsx:27
msgid "Your workspace <0>{workspaceDisplayName}</0> has been deleted as your subscription expired {inactiveDaysBeforeDelete} days ago."
msgstr "Votre espace de travail <0>{workspaceDisplayName}</0> a été supprimé car votre abonnement a expiré il y a {inactiveDaysBeforeDelete} jours."
#. js-lingui-id: KFmFrQ
#: src/emails/clean-suspended-workspace.email.tsx
#~ msgid "Your workspace <0>{workspaceDisplayName}</0> has been deleted as your subscription expired {inactiveDaysBeforeDelete} days ago."
#~ msgstr "Your workspace <0>{workspaceDisplayName}</0> has been deleted as your subscription expired {inactiveDaysBeforeDelete} days ago."
@@ -1 +1 @@
/*eslint-disable*/import type{Messages}from"@lingui/core";export const messages=JSON.parse("{\"4WPI3S\":[\"Aanvaar uitnodiging\"],\"Yxj+Uc\":[\"Alle data in hierdie werkruimte is permanent verwyder.\"],\"RPHFhC\":[\"Bevestig jou e-posadres\"],\"nvkBPN\":[\"Konnekteer met Twenty\"],\"jPQSEz\":[\"Skep 'n nuwe werksruimte\"],\"JRzgV7\":[\"Dear\"],\"Lm5BBI\":[\"Liewe \",[\"userName\"]],\"lIdkf2\":[\"Liewe \",[\"userName\"],\",\"],\"NTwcnq\":[\"Verwyderde Werkruimte\"],\"S3uuQj\":[\"e-posadresse om by jou werkruimte aan te sluit sonder om 'n uitnodiging te vereis.\"],\"tGme7M\":[\"het jou genooi om aan te sluit by 'n werkruimte genaamd \"],\"uzTaYi\":[\"Hallo\"],\"Xa0d85\":[\"Hallo,\"],\"eE1nG1\":[\"As jy nie hierdie verandering geïnisieer het nie, kontak asseblief jou werkruimte-eienaar dadelik.\"],\"Gz91L8\":[\"As jy wil voortgaan om Twenty te gebruik, moet asseblief jou intekening binne die volgende \",[\"remainingDays\"],\" \",[\"dayOrDays\"],\" opdateer.\"],\"0weyko\":[\"As jy Twenty weer wil gebruik, kan jy 'n nuwe werkruimte skep.\"],\"7JuhZQ\":[\"Dit blyk dat jou werkruimte <0>\",[\"workspaceDisplayName\"],\"</0> vir \",[\"daysSinceInactive\"],\" dae opgeskort is.\"],\"PviVyk\":[\"Sluit aan by jou span op Twenty\"],\"ogtYkT\":[\"Wagwoord opgedateer\"],\"Yucjaa\":[\"Valideer asseblief hierdie domein om gebruikers met\"],\"u3Ns4p\":[\"Please validate this domain to allow users with <0>@\",[\"domain\"],\"</0> email addresses to join your workspace without requiring an invitation.\"],\"OfhWJH\":[\"Stel terug\"],\"RE5NiU\":[\"Stel jou wagwoord terug 🗝\"],\"UBadaJ\":[\"Geskorsde Werkruimte \"],\"7yDt8q\":[\"Dankie dat jy registreer het vir 'n rekening op Twenty! Voordat ons begin, moet ons net bevestig dat dit jy is. Klik bo om jou e-posadres te verifieer.\"],\"igorB1\":[\"Die werkruimte sal gedeaktiveer word in \",[\"remainingDays\"],\" \",[\"dayOrDays\"],\", en al sy data sal verwyder word.\"],\"7OEHy1\":[\"Dit is 'n bevestiging dat die wagwoord vir jou rekening (\",[\"email\"],\") suksesvol verander is op \",[\"formattedDate\"],\".\"],\"wSOsS+\":[\"This is a confirmation that password for your account (\",[\"email\"],\") was successfully changed on \",[\"formattedDate\"],\".<0/><1/>If you did not initiate this change, please contact your workspace owner immediately.\"],\"R4gMjN\":[\"Hierdie skakel is slegs geldig vir die volgende \",[\"duration\"],\". Indien die skakel nie werk nie, kan jy die aanmeldverifiëringskakel direk gebruik:\"],\"2oA637\":[\"This link is only valid for the next \",[\"duration\"],\". If the link does not work, you can use the login verification link directly:<0/>\"],\"H0v4yC\":[\"Opdateer jou intekening\"],\"QbiUqd\":[\"Valideer domein\"],\"wCKkSr\":[\"Verifieer E-pos\"],\"9MqLGX\":[\"Jou werkruimte <0>\",[\"workspaceDisplayName\"],\"</0> is verwyder aangesien jou intekening \",[\"daysSinceInactive\"],\" dae gelede verval het.\"],\"KFmFrQ\":[\"Your workspace <0>\",[\"workspaceDisplayName\"],\"</0> has been deleted as your subscription expired \",[\"inactiveDaysBeforeDelete\"],\" days ago.\"]}")as Messages;
/*eslint-disable*/import type{Messages}from"@lingui/core";export const messages=JSON.parse("{\"4WPI3S\":[\"Aanvaar uitnodiging\"],\"Yxj+Uc\":[\"Alle data in hierdie werkruimte is permanent verwyder.\"],\"RPHFhC\":[\"Bevestig jou e-posadres\"],\"nvkBPN\":[\"Konnekteer met Twenty\"],\"JRzgV7\":[\"Dear\"],\"Lm5BBI\":[\"Liewe \",[\"userName\"]],\"tGme7M\":[\"het jou genooi om aan te sluit by 'n werkruimte genaamd \"],\"uzTaYi\":[\"Hallo\"],\"eE1nG1\":[\"As jy nie hierdie verandering geïnisieer het nie, kontak asseblief jou werkruimte-eienaar dadelik.\"],\"Gz91L8\":[\"As jy wil voortgaan om Twenty te gebruik, moet asseblief jou intekening binne die volgende \",[\"remainingDays\"],\" \",[\"dayOrDays\"],\" opdateer.\"],\"0weyko\":[\"As jy Twenty weer wil gebruik, kan jy 'n nuwe werkruimte skep.\"],\"7JuhZQ\":[\"Dit blyk dat jou werkruimte <0>\",[\"workspaceDisplayName\"],\"</0> vir \",[\"daysSinceInactive\"],\" dae opgeskort is.\"],\"PviVyk\":[\"Sluit aan by jou span op Twenty\"],\"ogtYkT\":[\"Wagwoord opgedateer\"],\"OfhWJH\":[\"Stel terug\"],\"RE5NiU\":[\"Stel jou wagwoord terug 🗝\"],\"7yDt8q\":[\"Dankie dat jy registreer het vir 'n rekening op Twenty! Voordat ons begin, moet ons net bevestig dat dit jy is. Klik bo om jou e-posadres te verifieer.\"],\"igorB1\":[\"Die werkruimte sal gedeaktiveer word in \",[\"remainingDays\"],\" \",[\"dayOrDays\"],\", en al sy data sal verwyder word.\"],\"7OEHy1\":[\"Dit is 'n bevestiging dat die wagwoord vir jou rekening (\",[\"email\"],\") suksesvol verander is op \",[\"formattedDate\"],\".\"],\"wSOsS+\":[\"This is a confirmation that password for your account (\",[\"email\"],\") was successfully changed on \",[\"formattedDate\"],\".<0/><1/>If you did not initiate this change, please contact your workspace owner immediately.\"],\"R4gMjN\":[\"Hierdie skakel is slegs geldig vir die volgende \",[\"duration\"],\". Indien die skakel nie werk nie, kan jy die aanmeldverifiëringskakel direk gebruik:\"],\"2oA637\":[\"This link is only valid for the next \",[\"duration\"],\". If the link does not work, you can use the login verification link directly:<0/>\"],\"H0v4yC\":[\"Opdateer jou intekening\"],\"wCKkSr\":[\"Verifieer E-pos\"],\"KFmFrQ\":[\"Jou werkruimte <0>\",[\"workspaceDisplayName\"],\"</0> is verwyder aangesien jou intekening \",[\"inactiveDaysBeforeDelete\"],\" dae gelede verval het.\"]}")as Messages;
@@ -1 +1 @@
/*eslint-disable*/import type{Messages}from"@lingui/core";export const messages=JSON.parse("{\"4WPI3S\":[\"قبول الدعوة\"],\"Yxj+Uc\":[\"تم حذف جميع البيانات في هذه الواجهة بشكل دائم.\"],\"RPHFhC\":[\"تأكد من عنوان بريدك الإلكتروني\"],\"nvkBPN\":[\"الاتصال بـ Twenty\"],\"jPQSEz\":[\"إنشاء مساحة عمل جديدة\"],\"JRzgV7\":[\"Dear\"],\"Lm5BBI\":[\"عزيزي \",[\"userName\"]],\"lIdkf2\":[\"عزيزي \",[\"userName\"],\",\"],\"NTwcnq\":[\"مساحة العمل المحذوفة\"],\"S3uuQj\":[\"عناوين البريد الإلكتروني للانضمام إلى مساحة العمل الخاصة بك دون الحاجة إلى دعوة.\"],\"tGme7M\":[\"لقد دُعيت للإنضمام إلى مساحة عمل تسمى \"],\"uzTaYi\":[\"مرحبًا\"],\"Xa0d85\":[\"مرحبًا,\"],\"eE1nG1\":[\"إذا لم تكن قد بدأت هذا التغيير، يرجى الاتصال بمالك مساحة العمل الخاصة بك على الفور.\"],\"Gz91L8\":[\"إذا كنت ترغب في الاستمرار في استخدام Twenty، يُرجى تحديث اشتراكك خلال الأيام \",[\"remainingDays\"],\" \",[\"dayOrDays\"],\" القادمة.\"],\"0weyko\":[\"إذا كنت ترغب في استخدام Twenty مرة أخرى، يمكنك إنشاء مساحة عمل جديدة.\"],\"7JuhZQ\":[\"يبدو أن مساحة عملك <0>\",[\"workspaceDisplayName\"],\"</0> قد تم تعليقها لمدة \",[\"daysSinceInactive\"],\" أيام.\"],\"PviVyk\":[\"انضم إلى فريقك في Twenty\"],\"ogtYkT\":[\"تم تحديث كلمة المرور\"],\"Yucjaa\":[\"يرجى التحقق من صحة هذا النطاق للسماح للمستخدمين ب\"],\"u3Ns4p\":[\"Please validate this domain to allow users with <0>@\",[\"domain\"],\"</0> email addresses to join your workspace without requiring an invitation.\"],\"OfhWJH\":[\"إعادة تعيين\"],\"RE5NiU\":[\"إعادة تعيين كلمة مرورك 🗝\"],\"UBadaJ\":[\"فاعِل مساحة العمل المعلّقة \"],\"7yDt8q\":[\"شكراً لتسجيلك لحساب على Twenty! قبل أن نبدأ، نحتاج فقط إلى التأكد من أن هذا أنت. انقر أعلاه للتحقق من عنوان بريدك الإلكتروني.\"],\"igorB1\":[\"سيتم تعطيل مساحة العمل في غضون \",[\"remainingDays\"],\" \",[\"dayOrDays\"],\"، وسيتم حذف كل بياناتها.\"],\"7OEHy1\":[\"هذا تأكيد أن كلمة مرور حسابك (\",[\"email\"],\") قد تم تغييرها بنجاح في \",[\"formattedDate\"],\".\"],\"wSOsS+\":[\"This is a confirmation that password for your account (\",[\"email\"],\") was successfully changed on \",[\"formattedDate\"],\".<0/><1/>If you did not initiate this change, please contact your workspace owner immediately.\"],\"R4gMjN\":[\"هذا الرابط صالح فقط للمدة \",[\"duration\"],\" القادمة. إذا لم يعمل الرابط، يمكنك استخدام رابط التحقق من تسجيل الدخول مباشرة:\"],\"2oA637\":[\"This link is only valid for the next \",[\"duration\"],\". If the link does not work, you can use the login verification link directly:<0/>\"],\"H0v4yC\":[\"تحديث الاشتراك\"],\"QbiUqd\":[\"تحقق من النطاق\"],\"wCKkSr\":[\"تحقق من البريد الإلكتروني\"],\"9MqLGX\":[\"تم حذف مساحة العمل <0>\",[\"workspaceDisplayName\"],\"</0> كون اشتراكك قد انتهى منذ \",[\"daysSinceInactive\"],\" أيام.\"],\"KFmFrQ\":[\"Your workspace <0>\",[\"workspaceDisplayName\"],\"</0> has been deleted as your subscription expired \",[\"inactiveDaysBeforeDelete\"],\" days ago.\"]}")as Messages;
/*eslint-disable*/import type{Messages}from"@lingui/core";export const messages=JSON.parse("{\"4WPI3S\":[\"قبول الدعوة\"],\"Yxj+Uc\":[\"تم حذف جميع البيانات في هذه الواجهة بشكل دائم.\"],\"RPHFhC\":[\"تأكد من عنوان بريدك الإلكتروني\"],\"nvkBPN\":[\"الاتصال بـ Twenty\"],\"JRzgV7\":[\"Dear\"],\"Lm5BBI\":[\"عزيزي \",[\"userName\"]],\"tGme7M\":[\"لقد دُعيت للإنضمام إلى مساحة عمل تسمى \"],\"uzTaYi\":[\"مرحبًا\"],\"eE1nG1\":[\"إذا لم تكن قد بدأت هذا التغيير، يرجى الاتصال بمالك مساحة العمل الخاصة بك على الفور.\"],\"Gz91L8\":[\"إذا كنت ترغب في الاستمرار في استخدام Twenty، يُرجى تحديث اشتراكك خلال الأيام \",[\"remainingDays\"],\" \",[\"dayOrDays\"],\" القادمة.\"],\"0weyko\":[\"إذا كنت ترغب في استخدام Twenty مرة أخرى، يمكنك إنشاء مساحة عمل جديدة.\"],\"7JuhZQ\":[\"يبدو أن مساحة عملك <0>\",[\"workspaceDisplayName\"],\"</0> قد تم تعليقها لمدة \",[\"daysSinceInactive\"],\" أيام.\"],\"PviVyk\":[\"انضم إلى فريقك في Twenty\"],\"ogtYkT\":[\"تم تحديث كلمة المرور\"],\"OfhWJH\":[\"إعادة تعيين\"],\"RE5NiU\":[\"إعادة تعيين كلمة مرورك 🗝\"],\"7yDt8q\":[\"شكراً لتسجيلك لحساب على Twenty! قبل أن نبدأ، نحتاج فقط إلى التأكد من أن هذا أنت. انقر أعلاه للتحقق من عنوان بريدك الإلكتروني.\"],\"igorB1\":[\"سيتم تعطيل مساحة العمل في غضون \",[\"remainingDays\"],\" \",[\"dayOrDays\"],\"، وسيتم حذف كل بياناتها.\"],\"7OEHy1\":[\"هذا تأكيد أن كلمة مرور حسابك (\",[\"email\"],\") قد تم تغييرها بنجاح في \",[\"formattedDate\"],\".\"],\"wSOsS+\":[\"This is a confirmation that password for your account (\",[\"email\"],\") was successfully changed on \",[\"formattedDate\"],\".<0/><1/>If you did not initiate this change, please contact your workspace owner immediately.\"],\"R4gMjN\":[\"هذا الرابط صالح فقط للمدة \",[\"duration\"],\" القادمة. إذا لم يعمل الرابط، يمكنك استخدام رابط التحقق من تسجيل الدخول مباشرة:\"],\"2oA637\":[\"This link is only valid for the next \",[\"duration\"],\". If the link does not work, you can use the login verification link directly:<0/>\"],\"H0v4yC\":[\"تحديث الاشتراك\"],\"wCKkSr\":[\"تحقق من البريد الإلكتروني\"],\"KFmFrQ\":[\"تم حذف مساحة العمل <0>\",[\"workspaceDisplayName\"],\"</0> كون اشتراكك قد انتهى منذ \",[\"inactiveDaysBeforeDelete\"],\" أيام.\"]}")as Messages;
@@ -1 +1 @@
/*eslint-disable*/import type{Messages}from"@lingui/core";export const messages=JSON.parse("{\"4WPI3S\":[\"Accepta la invitació\"],\"Yxj+Uc\":[\"Totes les dades en aquest espai de treball s'han esborrat permanentment.\"],\"RPHFhC\":[\"Confirma la teva adreça de correu electrònic\"],\"nvkBPN\":[\"Connecta't a Twenty\"],\"jPQSEz\":[\"Crea un nou espai de treball\"],\"JRzgV7\":[\"Dear\"],\"Lm5BBI\":[\"Benvolgut \",[\"userName\"]],\"lIdkf2\":[\"Benvolgut \",[\"userName\"],\",\"],\"NTwcnq\":[\"Espai de treball esborrat\"],\"S3uuQj\":[\"adreces de correu electrònic per unir-se al teu espai de treball sense requerir una invitació.\"],\"tGme7M\":[\"t'ha convidat a unir-te a un espai de treball anomenat \"],\"uzTaYi\":[\"Hola\"],\"Xa0d85\":[\"Hola,\"],\"eE1nG1\":[\"Si no has iniciat aquest canvi, si us plau contacta amb el propietari de l'espai de treball immediatament.\"],\"Gz91L8\":[\"Si vols continuar utilitzant Twenty, si us plau actualitza la teva subscripció en els propers \",[\"remainingDays\"],\" \",[\"dayOrDays\"],\".\"],\"0weyko\":[\"Si vols utilitzar Twenty de nou, pots crear un nou espai de treball.\"],\"7JuhZQ\":[\"Sembla que el teu espai de treball <0>\",[\"workspaceDisplayName\"],\"</0> ha estat suspès per \",[\"daysSinceInactive\"],\" dies.\"],\"PviVyk\":[\"Uneix-te al teu equip a Twenty\"],\"ogtYkT\":[\"Contrasenya actualitzada\"],\"Yucjaa\":[\"Si us plau, valida aquest domini per permetre als usuaris amb\"],\"u3Ns4p\":[\"Please validate this domain to allow users with <0>@\",[\"domain\"],\"</0> email addresses to join your workspace without requiring an invitation.\"],\"OfhWJH\":[\"Restableix\"],\"RE5NiU\":[\"Restableix la teva contrasenya 🗝\"],\"UBadaJ\":[\"Espai de treball suspès \"],\"7yDt8q\":[\"Gràcies per registrar-te a Twenty! Abans de començar, només necessitem confirmar que ets tu. Clica a sobre per verificar la teva adreça de correu electrònic.\"],\"igorB1\":[\"L'espai de treball es desactivarà en \",[\"remainingDays\"],\" \",[\"dayOrDays\"],\", i totes les seves dades seran eliminades.\"],\"7OEHy1\":[\"Això és una confirmació que la contrasenya del teu compte (\",[\"email\"],\") s'ha canviat amb èxit el \",[\"formattedDate\"],\".\"],\"wSOsS+\":[\"This is a confirmation that password for your account (\",[\"email\"],\") was successfully changed on \",[\"formattedDate\"],\".<0/><1/>If you did not initiate this change, please contact your workspace owner immediately.\"],\"R4gMjN\":[\"Aquest enllaç només és vàlid durant els propers \",[\"duration\"],\". Si l'enllaç no funciona, pots fer servir directament l'enllaç de verificació d'inici de sessió:\"],\"2oA637\":[\"This link is only valid for the next \",[\"duration\"],\". If the link does not work, you can use the login verification link directly:<0/>\"],\"H0v4yC\":[\"Actualitza la teva subscripció\"],\"QbiUqd\":[\"Valida el domini\"],\"wCKkSr\":[\"Verifica el correu electrònic\"],\"9MqLGX\":[\"El teu espai de treball <0>\",[\"workspaceDisplayName\"],\"</0> s'ha eliminat ja que la teva subscripció va caducar fa \",[\"daysSinceInactive\"],\" dies.\"],\"KFmFrQ\":[\"Your workspace <0>\",[\"workspaceDisplayName\"],\"</0> has been deleted as your subscription expired \",[\"inactiveDaysBeforeDelete\"],\" days ago.\"]}")as Messages;
/*eslint-disable*/import type{Messages}from"@lingui/core";export const messages=JSON.parse("{\"4WPI3S\":[\"Accept invite\"],\"Yxj+Uc\":[\"All data in this workspace has been permanently deleted.\"],\"RPHFhC\":[\"Confirm your email address\"],\"nvkBPN\":[\"Connect to Twenty\"],\"JRzgV7\":[\"Dear\"],\"Lm5BBI\":[\"Dear \",[\"userName\"]],\"tGme7M\":[\"has invited you to join a workspace called \"],\"uzTaYi\":[\"Hello\"],\"eE1nG1\":[\"If you did not initiate this change, please contact your workspace owner immediately.\"],\"Gz91L8\":[\"If you wish to continue using Twenty, please update your subscription within the next \",[\"remainingDays\"],\" \",[\"dayOrDays\"],\".\"],\"0weyko\":[\"If you wish to use Twenty again, you can create a new workspace.\"],\"7JuhZQ\":[\"It appears that your workspace <0>\",[\"workspaceDisplayName\"],\"</0> has been suspended for \",[\"daysSinceInactive\"],\" days.\"],\"PviVyk\":[\"Join your team on Twenty\"],\"ogtYkT\":[\"Password updated\"],\"OfhWJH\":[\"Reset\"],\"RE5NiU\":[\"Reset your password 🗝\"],\"7yDt8q\":[\"Thanks for registering for an account on Twenty! Before we get started, we just need to confirm that this is you. Click above to verify your email address.\"],\"igorB1\":[\"The workspace will be deactivated in \",[\"remainingDays\"],\" \",[\"dayOrDays\"],\", and all its data will be deleted.\"],\"7OEHy1\":[\"This is a confirmation that password for your account (\",[\"email\"],\") was successfully changed on \",[\"formattedDate\"],\".\"],\"wSOsS+\":[\"This is a confirmation that password for your account (\",[\"email\"],\") was successfully changed on \",[\"formattedDate\"],\".<0/><1/>If you did not initiate this change, please contact your workspace owner immediately.\"],\"R4gMjN\":[\"This link is only valid for the next \",[\"duration\"],\". If the link does not work, you can use the login verification link directly:\"],\"2oA637\":[\"This link is only valid for the next \",[\"duration\"],\". If the link does not work, you can use the login verification link directly:<0/>\"],\"H0v4yC\":[\"Update your subscription\"],\"wCKkSr\":[\"Verify Email\"],\"KFmFrQ\":[\"Your workspace <0>\",[\"workspaceDisplayName\"],\"</0> has been deleted as your subscription expired \",[\"inactiveDaysBeforeDelete\"],\" days ago.\"]}")as Messages;
@@ -1 +1 @@
/*eslint-disable*/import type{Messages}from"@lingui/core";export const messages=JSON.parse("{\"4WPI3S\":[\"Přijměte pozvánku\"],\"Yxj+Uc\":[\"Všechna data v tomto pracovním prostoru byla trvale odstraněna.\"],\"RPHFhC\":[\"Potvrďte svou e-mailovou adresu\"],\"nvkBPN\":[\"Připojte se k Twenty\"],\"jPQSEz\":[\"Vytvořit nový pracovní prostor\"],\"JRzgV7\":[\"Dear\"],\"Lm5BBI\":[\"Vážený \",[\"userName\"]],\"lIdkf2\":[\"Vážený \",[\"userName\"],\",\"],\"NTwcnq\":[\"Smazaný pracovní prostor\"],\"S3uuQj\":[\"e-mailové adresy ke vstupu do vašeho pracovního prostoru bez nutnosti pozvánky.\"],\"tGme7M\":[\"vás pozval do pracovního prostoru s názvem \"],\"uzTaYi\":[\"Dobrý den\"],\"Xa0d85\":[\"Dobrý den,\"],\"eE1nG1\":[\"Pokud jste tuto změnu neiniciovali, prosím, okamžitě kontaktujte majitele vašeho pracovního prostoru.\"],\"Gz91L8\":[\"Pokud si přejete dále používat Twenty, prosím, aktualizujte své předplatné během příštích \",[\"remainingDays\"],\" \",[\"dayOrDays\"],\".\"],\"0weyko\":[\"Pokud si přejete znovu použít Twenty, můžete vytvořit nový pracovní prostor.\"],\"7JuhZQ\":[\"Zdá se, že váš pracovní prostor <0>\",[\"workspaceDisplayName\"],\"</0> byl pozastaven na dobu \",[\"daysSinceInactive\"],\" dní.\"],\"PviVyk\":[\"Připojte se k svému týmu na Twenty\"],\"ogtYkT\":[\"Heslo bylo aktualizováno\"],\"Yucjaa\":[\"Prosím, ověřte tuto doménu pro umožnění přístupu uživatelům s\"],\"u3Ns4p\":[\"Please validate this domain to allow users with <0>@\",[\"domain\"],\"</0> email addresses to join your workspace without requiring an invitation.\"],\"OfhWJH\":[\"Obnovit\"],\"RE5NiU\":[\"Obnovte své heslo 🗝\"],\"UBadaJ\":[\"Pozastavený pracovní prostor \"],\"7yDt8q\":[\"Děkujeme za registraci účtu na Twenty! Než začneme, musíme potvrdit, že jste to vy. Klikněte výše k ověření vaší e-mailové adresy.\"],\"igorB1\":[\"Pracovní prostor bude deaktivován za \",[\"remainingDays\"],\" \",[\"dayOrDays\"],\" a všechna jeho data budou smazána.\"],\"7OEHy1\":[\"Toto je potvrzení, že heslo k vašemu účtu (\",[\"email\"],\") bylo úspěšně změněno \",[\"formattedDate\"],\".\"],\"wSOsS+\":[\"This is a confirmation that password for your account (\",[\"email\"],\") was successfully changed on \",[\"formattedDate\"],\".<0/><1/>If you did not initiate this change, please contact your workspace owner immediately.\"],\"R4gMjN\":[\"Tento odkaz je platný pouze následující \",[\"duration\"],\". Pokud odkaz nefunguje, můžete přímo použít odkaz pro ověření přihlášení:\"],\"2oA637\":[\"This link is only valid for the next \",[\"duration\"],\". If the link does not work, you can use the login verification link directly:<0/>\"],\"H0v4yC\":[\"Aktualizujte své předplatné\"],\"QbiUqd\":[\"Ověřit doménu\"],\"wCKkSr\":[\"Ověřit e-mail\"],\"9MqLGX\":[\"Váš pracovní prostor <0>\",[\"workspaceDisplayName\"],\"</0> byl odstraněn, protože vaše předplatné vypršelo před \",[\"daysSinceInactive\"],\" dny.\"],\"KFmFrQ\":[\"Your workspace <0>\",[\"workspaceDisplayName\"],\"</0> has been deleted as your subscription expired \",[\"inactiveDaysBeforeDelete\"],\" days ago.\"]}")as Messages;
/*eslint-disable*/import type{Messages}from"@lingui/core";export const messages=JSON.parse("{\"4WPI3S\":[\"Přijměte pozvánku\"],\"Yxj+Uc\":[\"Všechna data v tomto pracovním prostoru byla trvale odstraněna.\"],\"RPHFhC\":[\"Potvrďte svou e-mailovou adresu\"],\"nvkBPN\":[\"Připojte se k Twenty\"],\"JRzgV7\":[\"Dear\"],\"Lm5BBI\":[\"Vážený \",[\"userName\"]],\"tGme7M\":[\"vás pozval do pracovního prostoru s názvem \"],\"uzTaYi\":[\"Dobrý den\"],\"eE1nG1\":[\"Pokud jste tuto změnu neiniciovali, prosím, okamžitě kontaktujte majitele vašeho pracovního prostoru.\"],\"Gz91L8\":[\"Pokud si přejete dále používat Twenty, prosím, aktualizujte své předplatné během příštích \",[\"remainingDays\"],\" \",[\"dayOrDays\"],\".\"],\"0weyko\":[\"Pokud si přejete znovu použít Twenty, můžete vytvořit nový pracovní prostor.\"],\"7JuhZQ\":[\"Zdá se, že váš pracovní prostor <0>\",[\"workspaceDisplayName\"],\"</0> byl pozastaven na dobu \",[\"daysSinceInactive\"],\" dní.\"],\"PviVyk\":[\"Připojte se k svému týmu na Twenty\"],\"ogtYkT\":[\"Heslo bylo aktualizováno\"],\"OfhWJH\":[\"Obnovit\"],\"RE5NiU\":[\"Obnovte své heslo 🗝\"],\"7yDt8q\":[\"Děkujeme za registraci účtu na Twenty! Než začneme, musíme potvrdit, že jste to vy. Klikněte výše k ověření vaší e-mailové adresy.\"],\"igorB1\":[\"Pracovní prostor bude deaktivován za \",[\"remainingDays\"],\" \",[\"dayOrDays\"],\" a všechna jeho data budou smazána.\"],\"7OEHy1\":[\"Toto je potvrzení, že heslo k vašemu účtu (\",[\"email\"],\") bylo úspěšně změněno \",[\"formattedDate\"],\".\"],\"wSOsS+\":[\"This is a confirmation that password for your account (\",[\"email\"],\") was successfully changed on \",[\"formattedDate\"],\".<0/><1/>If you did not initiate this change, please contact your workspace owner immediately.\"],\"R4gMjN\":[\"Tento odkaz je platný pouze následující \",[\"duration\"],\". Pokud odkaz nefunguje, můžete přímo použít odkaz pro ověření přihlášení:\"],\"2oA637\":[\"This link is only valid for the next \",[\"duration\"],\". If the link does not work, you can use the login verification link directly:<0/>\"],\"H0v4yC\":[\"Aktualizujte své předplatné\"],\"wCKkSr\":[\"Ověřit e-mail\"],\"KFmFrQ\":[\"Váš pracovní prostor <0>\",[\"workspaceDisplayName\"],\"</0> byl odstraněn, protože vaše předplatné vypršelo před \",[\"inactiveDaysBeforeDelete\"],\" dny.\"]}")as Messages;
@@ -1 +1 @@
/*eslint-disable*/import type{Messages}from"@lingui/core";export const messages=JSON.parse("{\"4WPI3S\":[\"Accepter invitation\"],\"Yxj+Uc\":[\"Alle data i dette arbejdsområde er blevet permanent slettet.\"],\"RPHFhC\":[\"Bekræft din e-mailadresse\"],\"nvkBPN\":[\"Forbind til Twenty\"],\"jPQSEz\":[\"Opret et nyt arbejdsområde\"],\"JRzgV7\":[\"Dear\"],\"Lm5BBI\":[\"Kære \",[\"userName\"]],\"lIdkf2\":[\"Kære \",[\"userName\"],\",\"],\"NTwcnq\":[\"Slettet arbejdsområde\"],\"S3uuQj\":[\"emailadresser for at tilslutte din arbejdsområde uden at kræve en invitation.\"],\"tGme7M\":[\"har inviteret dig til at deltage i et arbejdsområde kaldet \"],\"uzTaYi\":[\"Hej\"],\"Xa0d85\":[\"Hej,\"],\"eE1nG1\":[\"Hvis du ikke har initieret denne ændring, bedes du straks kontakte ejeren af dit arbejdsområde.\"],\"Gz91L8\":[\"Hvis du ønsker at fortsætte med at bruge Twenty, opdater da dit abonnement inden for de næste \",[\"remainingDays\"],\" \",[\"dayOrDays\"],\".\"],\"0weyko\":[\"Hvis du ønsker at bruge Twenty igen, kan du oprette et nyt arbejdsområde.\"],\"7JuhZQ\":[\"Det ser ud til, at dit arbejdsområde <0>\",[\"workspaceDisplayName\"],\"</0> er blevet suspenderet i \",[\"daysSinceInactive\"],\" dage.\"],\"PviVyk\":[\"Deltag i dit team på Twenty\"],\"ogtYkT\":[\"Adgangskode opdateret\"],\"Yucjaa\":[\"Bekræft venligst dette domæne for at tillade brugere med\"],\"u3Ns4p\":[\"Please validate this domain to allow users with <0>@\",[\"domain\"],\"</0> email addresses to join your workspace without requiring an invitation.\"],\"OfhWJH\":[\"Nulstil\"],\"RE5NiU\":[\"Nulstil din adgangskode 🗝\"],\"UBadaJ\":[\"Suspenderet arbejdsområde \"],\"7yDt8q\":[\"Tak fordi du har registreret en konto på Twenty! Før vi starter, skal vi bare bekræfte, at det er dig. Klik ovenfor for at bekræfte din e-mailadresse.\"],\"igorB1\":[\"Arbejdsområdet vil blive deaktiveret om \",[\"remainingDays\"],\" \",[\"dayOrDays\"],\", og alle dens data vil blive slettet.\"],\"7OEHy1\":[\"Dette er en bekræftelse på, at adgangskoden til din konto (\",[\"email\"],\") er blevet ændret den \",[\"formattedDate\"],\".\"],\"wSOsS+\":[\"This is a confirmation that password for your account (\",[\"email\"],\") was successfully changed on \",[\"formattedDate\"],\".<0/><1/>If you did not initiate this change, please contact your workspace owner immediately.\"],\"R4gMjN\":[\"Dette link er kun gyldigt i de næste \",[\"duration\"],\". Hvis linket ikke fungerer, kan du bruge loginbekræftelseslinket direkte:\"],\"2oA637\":[\"This link is only valid for the next \",[\"duration\"],\". If the link does not work, you can use the login verification link directly:<0/>\"],\"H0v4yC\":[\"Opdater dit abonnement\"],\"QbiUqd\":[\"Bekræft domæne\"],\"wCKkSr\":[\"Bekræft e-mail\"],\"9MqLGX\":[\"Dit arbejdsområde <0>\",[\"workspaceDisplayName\"],\"</0> er blevet slettet, da dit abonnement udløb for \",[\"daysSinceInactive\"],\" dage siden.\"],\"KFmFrQ\":[\"Your workspace <0>\",[\"workspaceDisplayName\"],\"</0> has been deleted as your subscription expired \",[\"inactiveDaysBeforeDelete\"],\" days ago.\"]}")as Messages;
/*eslint-disable*/import type{Messages}from"@lingui/core";export const messages=JSON.parse("{\"4WPI3S\":[\"Accepter invitation\"],\"Yxj+Uc\":[\"Alle data i dette arbejdsområde er blevet permanent slettet.\"],\"RPHFhC\":[\"Bekræft din e-mailadresse\"],\"nvkBPN\":[\"Forbind til Twenty\"],\"JRzgV7\":[\"Dear\"],\"Lm5BBI\":[\"Kære \",[\"userName\"]],\"tGme7M\":[\"har inviteret dig til at deltage i et arbejdsområde kaldet \"],\"uzTaYi\":[\"Hej\"],\"eE1nG1\":[\"Hvis du ikke har initieret denne ændring, bedes du straks kontakte ejeren af dit arbejdsområde.\"],\"Gz91L8\":[\"Hvis du ønsker at fortsætte med at bruge Twenty, opdater da dit abonnement inden for de næste \",[\"remainingDays\"],\" \",[\"dayOrDays\"],\".\"],\"0weyko\":[\"Hvis du ønsker at bruge Twenty igen, kan du oprette et nyt arbejdsområde.\"],\"7JuhZQ\":[\"Det ser ud til, at dit arbejdsområde <0>\",[\"workspaceDisplayName\"],\"</0> er blevet suspenderet i \",[\"daysSinceInactive\"],\" dage.\"],\"PviVyk\":[\"Deltag i dit team på Twenty\"],\"ogtYkT\":[\"Adgangskode opdateret\"],\"OfhWJH\":[\"Nulstil\"],\"RE5NiU\":[\"Nulstil din adgangskode 🗝\"],\"7yDt8q\":[\"Tak fordi du har registreret en konto på Twenty! Før vi starter, skal vi bare bekræfte, at det er dig. Klik ovenfor for at bekræfte din e-mailadresse.\"],\"igorB1\":[\"Arbejdsområdet vil blive deaktiveret om \",[\"remainingDays\"],\" \",[\"dayOrDays\"],\", og alle dens data vil blive slettet.\"],\"7OEHy1\":[\"Dette er en bekræftelse på, at adgangskoden til din konto (\",[\"email\"],\") er blevet ændret den \",[\"formattedDate\"],\".\"],\"wSOsS+\":[\"This is a confirmation that password for your account (\",[\"email\"],\") was successfully changed on \",[\"formattedDate\"],\".<0/><1/>If you did not initiate this change, please contact your workspace owner immediately.\"],\"R4gMjN\":[\"Dette link er kun gyldigt i de næste \",[\"duration\"],\". Hvis linket ikke fungerer, kan du bruge loginbekræftelseslinket direkte:\"],\"2oA637\":[\"This link is only valid for the next \",[\"duration\"],\". If the link does not work, you can use the login verification link directly:<0/>\"],\"H0v4yC\":[\"Opdater dit abonnement\"],\"wCKkSr\":[\"Bekræft e-mail\"],\"KFmFrQ\":[\"Dit arbejdsområde <0>\",[\"workspaceDisplayName\"],\"</0> er blevet slettet, da dit abonnement udløb for \",[\"inactiveDaysBeforeDelete\"],\" dage siden.\"]}")as Messages;
@@ -1 +1 @@
/*eslint-disable*/import type{Messages}from"@lingui/core";export const messages=JSON.parse("{\"4WPI3S\":[\"Einladung akzeptieren\"],\"Yxj+Uc\":[\"Alle Daten in diesem Workspace wurden dauerhaft gelöscht.\"],\"RPHFhC\":[\"Bestätigen Sie Ihre E-Mail-Adresse\"],\"nvkBPN\":[\"Mit Twenty verbinden\"],\"jPQSEz\":[\"Erstellen Sie einen neuen Arbeitsbereich\"],\"JRzgV7\":[\"Dear\"],\"Lm5BBI\":[\"Sehr geehrte/r \",[\"userName\"]],\"lIdkf2\":[\"Sehr geehrte/r \",[\"userName\"],\",\"],\"NTwcnq\":[\"Gelöschter Arbeitsbereich\"],\"S3uuQj\":[\"E-Mail-Adressen, um Ihrem Arbeitsbereich ohne Einladung beizutreten.\"],\"tGme7M\":[\"hat Sie eingeladen, einem Workspace namens \"],\"uzTaYi\":[\"Hallo\"],\"Xa0d85\":[\"Hallo,\"],\"eE1nG1\":[\"Wenn Sie diese Änderung nicht veranlasst haben, kontaktieren Sie bitte umgehend den Eigentümer Ihres Workspaces.\"],\"Gz91L8\":[\"Wenn Sie Twenty weiterhin nutzen möchten, aktualisieren Sie bitte Ihr Abonnement innerhalb der nächsten \",[\"remainingDays\"],\" \",[\"dayOrDays\"],\".\"],\"0weyko\":[\"Wenn Sie Twenty erneut nutzen möchten, können Sie einen neuen Workspace erstellen.\"],\"7JuhZQ\":[\"Es scheint, dass Ihr Workspace <0>\",[\"workspaceDisplayName\"],\"</0> seit \",[\"daysSinceInactive\"],\" Tagen gesperrt ist.\"],\"PviVyk\":[\"Treten Sie Ihrem Team auf Twenty bei\"],\"ogtYkT\":[\"Passwort wurde aktualisiert\"],\"Yucjaa\":[\"Bitte validieren Sie diese Domain, um Benutzern mit\"],\"u3Ns4p\":[\"Please validate this domain to allow users with <0>@\",[\"domain\"],\"</0> email addresses to join your workspace without requiring an invitation.\"],\"OfhWJH\":[\"Zurücksetzen\"],\"RE5NiU\":[\"Setzen Sie Ihr Passwort zurück 🗝\"],\"UBadaJ\":[\"Ausgesetzter Arbeitsbereich \"],\"7yDt8q\":[\"Vielen Dank für Ihre Registrierung bei Twenty! Bevor wir beginnen, müssen wir nur bestätigen, dass Sie es sind. Klicken Sie oben, um Ihre E-Mail-Adresse zu verifizieren.\"],\"igorB1\":[\"Der Workspace wird in \",[\"remainingDays\"],\" \",[\"dayOrDays\"],\" deaktiviert, und alle Daten werden gelöscht.\"],\"7OEHy1\":[\"Dies ist eine Bestätigung, dass das Passwort für Ihr Konto (\",[\"email\"],\") am \",[\"formattedDate\"],\" erfolgreich geändert wurde.\"],\"wSOsS+\":[\"This is a confirmation that password for your account (\",[\"email\"],\") was successfully changed on \",[\"formattedDate\"],\".<0/><1/>If you did not initiate this change, please contact your workspace owner immediately.\"],\"R4gMjN\":[\"Dieser Link ist nur für die nächsten \",[\"duration\"],\" gültig. Wenn der Link nicht funktioniert, können Sie den Anmeldebestätigungslink direkt verwenden:\"],\"2oA637\":[\"This link is only valid for the next \",[\"duration\"],\". If the link does not work, you can use the login verification link directly:<0/>\"],\"H0v4yC\":[\"Aktualisieren Sie Ihr Abonnement\"],\"QbiUqd\":[\"Domain validieren\"],\"wCKkSr\":[\"E-Mail verifizieren\"],\"9MqLGX\":[\"Ihr Workspace <0>\",[\"workspaceDisplayName\"],\"</0> wurde gelöscht, da Ihr Abonnement vor \",[\"daysSinceInactive\"],\" Tagen abgelaufen ist.\"],\"KFmFrQ\":[\"Your workspace <0>\",[\"workspaceDisplayName\"],\"</0> has been deleted as your subscription expired \",[\"inactiveDaysBeforeDelete\"],\" days ago.\"]}")as Messages;
/*eslint-disable*/import type{Messages}from"@lingui/core";export const messages=JSON.parse("{\"4WPI3S\":[\"Einladung akzeptieren\"],\"Yxj+Uc\":[\"Alle Daten in diesem Workspace wurden dauerhaft gelöscht.\"],\"RPHFhC\":[\"Bestätigen Sie Ihre E-Mail-Adresse\"],\"nvkBPN\":[\"Mit Twenty verbinden\"],\"JRzgV7\":[\"Dear\"],\"Lm5BBI\":[\"Sehr geehrte/r \",[\"userName\"]],\"tGme7M\":[\"hat Sie eingeladen, einem Workspace namens \"],\"uzTaYi\":[\"Hallo\"],\"eE1nG1\":[\"Wenn Sie diese Änderung nicht veranlasst haben, kontaktieren Sie bitte umgehend den Eigentümer Ihres Workspaces.\"],\"Gz91L8\":[\"Wenn Sie Twenty weiterhin nutzen möchten, aktualisieren Sie bitte Ihr Abonnement innerhalb der nächsten \",[\"remainingDays\"],\" \",[\"dayOrDays\"],\".\"],\"0weyko\":[\"Wenn Sie Twenty erneut nutzen möchten, können Sie einen neuen Workspace erstellen.\"],\"7JuhZQ\":[\"Es scheint, dass Ihr Workspace <0>\",[\"workspaceDisplayName\"],\"</0> seit \",[\"daysSinceInactive\"],\" Tagen gesperrt ist.\"],\"PviVyk\":[\"Treten Sie Ihrem Team auf Twenty bei\"],\"ogtYkT\":[\"Passwort wurde aktualisiert\"],\"OfhWJH\":[\"Zurücksetzen\"],\"RE5NiU\":[\"Setzen Sie Ihr Passwort zurück 🗝\"],\"7yDt8q\":[\"Vielen Dank für Ihre Registrierung bei Twenty! Bevor wir beginnen, müssen wir nur bestätigen, dass Sie es sind. Klicken Sie oben, um Ihre E-Mail-Adresse zu verifizieren.\"],\"igorB1\":[\"Der Workspace wird in \",[\"remainingDays\"],\" \",[\"dayOrDays\"],\" deaktiviert, und alle Daten werden gelöscht.\"],\"7OEHy1\":[\"Dies ist eine Bestätigung, dass das Passwort für Ihr Konto (\",[\"email\"],\") am \",[\"formattedDate\"],\" erfolgreich geändert wurde.\"],\"wSOsS+\":[\"This is a confirmation that password for your account (\",[\"email\"],\") was successfully changed on \",[\"formattedDate\"],\".<0/><1/>If you did not initiate this change, please contact your workspace owner immediately.\"],\"R4gMjN\":[\"Dieser Link ist nur für die nächsten \",[\"duration\"],\" gültig. Wenn der Link nicht funktioniert, können Sie den Anmeldebestätigungslink direkt verwenden:\"],\"2oA637\":[\"This link is only valid for the next \",[\"duration\"],\". If the link does not work, you can use the login verification link directly:<0/>\"],\"H0v4yC\":[\"Aktualisieren Sie Ihr Abonnement\"],\"wCKkSr\":[\"E-Mail verifizieren\"],\"KFmFrQ\":[\"Ihr Workspace <0>\",[\"workspaceDisplayName\"],\"</0> wurde gelöscht, da Ihr Abonnement vor \",[\"inactiveDaysBeforeDelete\"],\" Tagen abgelaufen ist.\"]}")as Messages;
@@ -1 +1 @@
/*eslint-disable*/import type{Messages}from"@lingui/core";export const messages=JSON.parse("{\"4WPI3S\":[\"Αποδοχή πρόσκλησης\"],\"Yxj+Uc\":[\"Όλα τα δεδομένα σε αυτόν τον χώρο εργασίας έχουν διαγραφεί μόνιμα.\"],\"RPHFhC\":[\"Επιβεβαιώστε τη διεύθυνση email σας\"],\"nvkBPN\":[\"Συνδεθείτε στο Twenty\"],\"jPQSEz\":[\"Δημιουργία νέου χώρου εργασίας\"],\"JRzgV7\":[\"Dear\"],\"Lm5BBI\":[\"Αγαπητέ/ή \",[\"userName\"]],\"lIdkf2\":[\"Αγαπητέ/ή \",[\"userName\"],\",\"],\"NTwcnq\":[\"Διαγραμμένος Χώρος Εργασίας\"],\"S3uuQj\":[\"διευθύνσεις ηλεκτρονικού ταχυδρομείου για να συμμετάσχουν στο χώρο εργασίας σας χωρίς να απαιτείται πρόσκληση.\"],\"tGme7M\":[\"σας έχει προσκαλέσει να συμμετάσχετε σε έναν χώρο εργασίας με την ονομασία \"],\"uzTaYi\":[\"Γεια σας\"],\"Xa0d85\":[\"Γεια σας,\"],\"eE1nG1\":[\"Εάν δεν έχετε προβεί εσείς σε αυτήν την αλλαγή, παρακαλώ επικοινωνήστε άμεσα με τον ιδιοκτήτη του χώρου εργασίας σας.\"],\"Gz91L8\":[\"Εάν επιθυμείτε να συνεχίστε να χρησιμοποιείτε το Twenty, παρακαλούμε ενημερώστε την συνδρομή σας εντός των επόμενων \",[\"remainingDays\"],\" \",[\"dayOrDays\"],\".\"],\"0weyko\":[\"Εάν επιθυμείτε να χρησιμοποιήσετε ξανά το Twenty, μπορείτε να δημιουργήσετε ένα νέο χώρο εργασίας.\"],\"7JuhZQ\":[\"Φαίνεται ότι ο χώρος εργασίας σας <0>\",[\"workspaceDisplayName\"],\"</0> έχει ανασταλεί για \",[\"daysSinceInactive\"],\" ημέρες.\"],\"PviVyk\":[\"Συμμετέχετε στην ομάδα σας στο Twenty\"],\"ogtYkT\":[\"Ο κωδικός έχει ενημερωθεί\"],\"Yucjaa\":[\"Παρακαλώ επικυρώστε αυτόν τον τομέα για να επιτρέψετε στους χρήστες με\"],\"u3Ns4p\":[\"Please validate this domain to allow users with <0>@\",[\"domain\"],\"</0> email addresses to join your workspace without requiring an invitation.\"],\"OfhWJH\":[\"Επαναφορά\"],\"RE5NiU\":[\"Επαναφέρετε τον κωδικό σας 🗝\"],\"UBadaJ\":[\"Ανασταλμένος Χώρος Εργασίας \"],\"7yDt8q\":[\"Σας ευχαριστούμε που εγγραφήκατε για έναν λογαριασμό στο Twenty! Πριν ξεκινήσουμε, πρέπει μόνο να επιβεβαιώσουμε ότι αυτός είστε εσείς. Κάντε κλικ παραπάνω για να επαληθεύσετε τη διεύθυνση email σας.\"],\"igorB1\":[\"Ο χώρος εργασίας θα απενεργοποιηθεί σε \",[\"remainingDays\"],\" \",[\"dayOrDays\"],\", και όλα τα δεδομένα του θα διαγραφούν.\"],\"7OEHy1\":[\"Αυτό είναι ένα επιβεβαίωση ότι ο κωδικός για τον λογαριασμό σας (\",[\"email\"],\") άλλαξε με επιτυχία στις \",[\"formattedDate\"],\".\"],\"wSOsS+\":[\"This is a confirmation that password for your account (\",[\"email\"],\") was successfully changed on \",[\"formattedDate\"],\".<0/><1/>If you did not initiate this change, please contact your workspace owner immediately.\"],\"R4gMjN\":[\"Αυτός ο σύνδεσμος ισχύει μόνο για τις επόμενες \",[\"duration\"],\". Εάν ο σύνδεσμος δεν λειτουργεί, μπορείτε να χρησιμοποιήσετε τον σύνδεσμο επαλήθευσης σύνδεσης απευθείας:\"],\"2oA637\":[\"This link is only valid for the next \",[\"duration\"],\". If the link does not work, you can use the login verification link directly:<0/>\"],\"H0v4yC\":[\"Ενημερώστε τη συνδρομή σας\"],\"QbiUqd\":[\"Επικύρωση τομέα\"],\"wCKkSr\":[\"Επαληθεύστε το Email\"],\"9MqLGX\":[\"Ο χώρος εργασίας σας <0>\",[\"workspaceDisplayName\"],\"</0> έχει διαγραφεί καθώς η συνδρομή σας έληξε \",[\"daysSinceInactive\"],\" ημέρες πριν.\"],\"KFmFrQ\":[\"Your workspace <0>\",[\"workspaceDisplayName\"],\"</0> has been deleted as your subscription expired \",[\"inactiveDaysBeforeDelete\"],\" days ago.\"]}")as Messages;
/*eslint-disable*/import type{Messages}from"@lingui/core";export const messages=JSON.parse("{\"4WPI3S\":[\"Αποδοχή πρόσκλησης\"],\"Yxj+Uc\":[\"Όλα τα δεδομένα σε αυτόν τον χώρο εργασίας έχουν διαγραφεί μόνιμα.\"],\"RPHFhC\":[\"Επιβεβαιώστε τη διεύθυνση email σας\"],\"nvkBPN\":[\"Συνδεθείτε στο Twenty\"],\"JRzgV7\":[\"Dear\"],\"Lm5BBI\":[\"Αγαπητέ/ή \",[\"userName\"]],\"tGme7M\":[\"σας έχει προσκαλέσει να συμμετάσχετε σε έναν χώρο εργασίας με την ονομασία \"],\"uzTaYi\":[\"Γεια σας\"],\"eE1nG1\":[\"Εάν δεν έχετε προβεί εσείς σε αυτήν την αλλαγή, παρακαλώ επικοινωνήστε άμεσα με τον ιδιοκτήτη του χώρου εργασίας σας.\"],\"Gz91L8\":[\"Εάν επιθυμείτε να συνεχίστε να χρησιμοποιείτε το Twenty, παρακαλούμε ενημερώστε την συνδρομή σας εντός των επόμενων \",[\"remainingDays\"],\" \",[\"dayOrDays\"],\".\"],\"0weyko\":[\"Εάν επιθυμείτε να χρησιμοποιήσετε ξανά το Twenty, μπορείτε να δημιουργήσετε ένα νέο χώρο εργασίας.\"],\"7JuhZQ\":[\"Φαίνεται ότι ο χώρος εργασίας σας <0>\",[\"workspaceDisplayName\"],\"</0> έχει ανασταλεί για \",[\"daysSinceInactive\"],\" ημέρες.\"],\"PviVyk\":[\"Συμμετέχετε στην ομάδα σας στο Twenty\"],\"ogtYkT\":[\"Ο κωδικός έχει ενημερωθεί\"],\"OfhWJH\":[\"Επαναφορά\"],\"RE5NiU\":[\"Επαναφέρετε τον κωδικό σας 🗝\"],\"7yDt8q\":[\"Σας ευχαριστούμε που εγγραφήκατε για έναν λογαριασμό στο Twenty! Πριν ξεκινήσουμε, πρέπει μόνο να επιβεβαιώσουμε ότι αυτός είστε εσείς. Κάντε κλικ παραπάνω για να επαληθεύσετε τη διεύθυνση email σας.\"],\"igorB1\":[\"Ο χώρος εργασίας θα απενεργοποιηθεί σε \",[\"remainingDays\"],\" \",[\"dayOrDays\"],\", και όλα τα δεδομένα του θα διαγραφούν.\"],\"7OEHy1\":[\"Αυτό είναι ένα επιβεβαίωση ότι ο κωδικός για τον λογαριασμό σας (\",[\"email\"],\") άλλαξε με επιτυχία στις \",[\"formattedDate\"],\".\"],\"wSOsS+\":[\"This is a confirmation that password for your account (\",[\"email\"],\") was successfully changed on \",[\"formattedDate\"],\".<0/><1/>If you did not initiate this change, please contact your workspace owner immediately.\"],\"R4gMjN\":[\"Αυτός ο σύνδεσμος ισχύει μόνο για τις επόμενες \",[\"duration\"],\". Εάν ο σύνδεσμος δεν λειτουργεί, μπορείτε να χρησιμοποιήσετε τον σύνδεσμο επαλήθευσης σύνδεσης απευθείας:\"],\"2oA637\":[\"This link is only valid for the next \",[\"duration\"],\". If the link does not work, you can use the login verification link directly:<0/>\"],\"H0v4yC\":[\"Ενημερώστε τη συνδρομή σας\"],\"wCKkSr\":[\"Επαληθεύστε το Email\"],\"KFmFrQ\":[\"Ο χώρος εργασίας σας <0>\",[\"workspaceDisplayName\"],\"</0> έχει διαγραφεί καθώς η συνδρομή σας έληξε \",[\"inactiveDaysBeforeDelete\"],\" ημέρες πριν.\"]}")as Messages;

Some files were not shown because too many files have changed in this diff Show More