1088f7bbab
## Summary
This PR adds the `lingui/no-unlocalized-strings` ESLint rule to detect
untranslated strings and fixes translation issues across multiple
components.
## Changes
### ESLint Configuration (`eslint.config.react.mjs`)
- Added comprehensive `ignore` patterns for non-translatable strings
(CSS values, HTML attributes, technical identifiers)
- Added `ignoreNames` for props that don't need translation (className,
data-*, aria-*, etc.)
- Added `ignoreFunctions` for console methods, URL APIs, and other
non-user-facing functions
- Disabled rule for debug files, storybook, and test files
### Components Fixed (~19 files)
- Object record components (field inputs, pickers, merge dialogs)
- Settings components (accounts, admin panel)
- Serverless function components
- Record table and title cell components
## Status
🚧 **Work in Progress** - ~124 files remaining to fix
This PR is being submitted as draft to allow progressive fixing of
remaining translation issues.
## Testing
- Run `npx eslint "src/**/*.tsx"` in `packages/twenty-front` to check
remaining issues
149 lines
3.6 KiB
TypeScript
149 lines
3.6 KiB
TypeScript
import { LightCopyIconButton } from '@/object-record/record-field/ui/components/LightCopyIconButton';
|
|
import { useTheme } from '@emotion/react';
|
|
import styled from '@emotion/styled';
|
|
import { t } from '@lingui/core/macro';
|
|
import {
|
|
IconLoader,
|
|
IconSquareRoundedCheck,
|
|
IconSquareRoundedX,
|
|
} from 'twenty-ui/display';
|
|
import { CodeEditor, CoreEditorHeader } from 'twenty-ui/input';
|
|
import { AnimatedCircleLoading } from 'twenty-ui/utilities';
|
|
|
|
const StyledContainer = styled.div`
|
|
display: flex;
|
|
flex-direction: column;
|
|
flex: 1;
|
|
min-height: 200px;
|
|
`;
|
|
|
|
const StyledCodeEditorWrapper = styled.div`
|
|
display: flex;
|
|
flex: 1;
|
|
flex-direction: column;
|
|
min-height: 200px;
|
|
`;
|
|
|
|
type OutputAccent = 'default' | 'success' | 'error';
|
|
|
|
const StyledInfoContainer = styled.div`
|
|
display: flex;
|
|
font-size: ${({ theme }) => theme.font.size.md};
|
|
`;
|
|
|
|
const StyledOutput = styled.div<{ accent?: OutputAccent }>`
|
|
align-items: center;
|
|
gap: ${({ theme }) => theme.spacing(1)};
|
|
color: ${({ theme, accent }) =>
|
|
accent === 'success'
|
|
? theme.color.turquoise
|
|
: accent === 'error'
|
|
? theme.color.red
|
|
: theme.font.color.secondary};
|
|
display: flex;
|
|
`;
|
|
|
|
const StyledStatusInfo = styled.div`
|
|
display: flex;
|
|
gap: ${({ theme }) => theme.spacing(2)};
|
|
font-size: ${({ theme }) => theme.font.size.sm};
|
|
color: ${({ theme }) => theme.font.color.tertiary};
|
|
`;
|
|
|
|
export type ExecutionStatus = {
|
|
isSuccess: boolean;
|
|
isError: boolean;
|
|
successMessage?: string;
|
|
errorMessage?: string;
|
|
additionalInfo?: string;
|
|
};
|
|
|
|
type WorkflowStepExecutionResultProps = {
|
|
result: string;
|
|
language: 'plaintext' | 'json';
|
|
height?: string | number;
|
|
status: ExecutionStatus;
|
|
isTesting?: boolean;
|
|
loadingMessage?: string;
|
|
idleMessage?: string;
|
|
};
|
|
|
|
export const WorkflowStepExecutionResult = ({
|
|
result,
|
|
language,
|
|
height = '100%',
|
|
status,
|
|
isTesting = false,
|
|
loadingMessage = t`Processing...`,
|
|
idleMessage = t`Output`,
|
|
}: WorkflowStepExecutionResultProps) => {
|
|
const theme = useTheme();
|
|
|
|
const SuccessLeftNode = (
|
|
<StyledOutput accent="success">
|
|
<IconSquareRoundedCheck size={theme.icon.size.md} />
|
|
<div>
|
|
<div>{status.successMessage}</div>
|
|
{status.additionalInfo && (
|
|
<StyledStatusInfo>{status.additionalInfo}</StyledStatusInfo>
|
|
)}
|
|
</div>
|
|
</StyledOutput>
|
|
);
|
|
|
|
const ErrorLeftNode = (
|
|
<StyledOutput accent="error">
|
|
<IconSquareRoundedX size={theme.icon.size.md} />
|
|
<div>
|
|
<div>{status.errorMessage}</div>
|
|
{status.additionalInfo && (
|
|
<StyledStatusInfo>{status.additionalInfo}</StyledStatusInfo>
|
|
)}
|
|
</div>
|
|
</StyledOutput>
|
|
);
|
|
|
|
const IdleLeftNode = idleMessage;
|
|
|
|
const PendingLeftNode = isTesting && (
|
|
<StyledOutput>
|
|
<AnimatedCircleLoading>
|
|
<IconLoader size={theme.icon.size.md} />
|
|
</AnimatedCircleLoading>
|
|
<StyledInfoContainer>{loadingMessage}</StyledInfoContainer>
|
|
</StyledOutput>
|
|
);
|
|
|
|
const computeLeftNode = () => {
|
|
if (isTesting) {
|
|
return PendingLeftNode;
|
|
}
|
|
if (status.isError) {
|
|
return ErrorLeftNode;
|
|
}
|
|
if (status.isSuccess) {
|
|
return SuccessLeftNode;
|
|
}
|
|
return IdleLeftNode;
|
|
};
|
|
|
|
return (
|
|
<StyledContainer>
|
|
<CoreEditorHeader
|
|
leftNodes={[computeLeftNode()]}
|
|
rightNodes={[<LightCopyIconButton copyText={result} />]}
|
|
/>
|
|
<StyledCodeEditorWrapper>
|
|
<CodeEditor
|
|
value={result}
|
|
language={language}
|
|
height={height}
|
|
options={{ readOnly: true, domReadOnly: true }}
|
|
isLoading={isTesting}
|
|
variant="with-header"
|
|
/>
|
|
</StyledCodeEditorWrapper>
|
|
</StyledContainer>
|
|
);
|
|
};
|