Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions i18next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,23 @@ use(Backend)
.use(initReactI18next)
.init({
fallbackLng: "en",
load: "languageOnly",
detection: {
order: ["localStorage", "cookie"],
caches: ["localStorage", "cookie"],
},
ns: ["common"],
defaultNS: "common",
interpolation: {
escapeValue: false,
},
backend: {
loadPath: "/locales/{{lng}}/{{ns}}.json",
queryStringParams: { v: "2" },
requestOptions: {
cache: "no-store",
},
},
});

export default I18n;
7 changes: 7 additions & 0 deletions nginx.conf
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,13 @@ server {
expires 1y;
add_header Cache-Control "public, immutable";
}

# Locale JSON is not content-hashed. Do not SPA-fallback missing
# languages to index.html (i18next would parse HTML as translations).
location /locales/ {
add_header Cache-Control "no-cache";
try_files $uri =404;
}

# Health check endpoint
location /health {
Expand Down
2 changes: 1 addition & 1 deletion src/components/VersionDiff/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import VersionDiff from "./";
describe("VersionDiff Component", () => {
test("renders an empty state when there are no hunks", () => {
render(<VersionDiff hunks={[]} />);
expect(screen.getByText("version_diff.no_line_changes")).toBeDefined();
expect(screen.getByText("No line changes")).toBeDefined();
});

test("renders changed lines with line numbers", () => {
Expand Down
8 changes: 6 additions & 2 deletions src/components/VersionDiff/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,15 @@ const signForType = (type: DiffLineType): string => {
};

const VersionDiff: FC<VersionDiffProps> = ({ hunks }) => {
const { t } = useTranslation(["models"]);
const { t } = useTranslation(["models"], { useSuspense: false });

if (!hunks.length) {
return (
<div className={styles.empty}>{t("version_diff.no_line_changes")}</div>
<div className={styles.empty}>
{t("models:version_diff.no_line_changes", {
defaultValue: "No line changes",
})}
</div>
);
}

Expand Down
6 changes: 2 additions & 4 deletions src/components/VersionsList/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,17 +79,15 @@ describe("VersionsList Component", () => {
render(<VersionsList onRestore={() => {}} />);
expect(screen.getByText("+1")).toBeDefined();
expect(screen.getByText("-1")).toBeDefined();
expect(screen.getByText("version_diff.initial_version")).toBeDefined();
expect(screen.getByText("Initial version")).toBeDefined();
});

test("previews restore then confirms", () => {
const mockOnRestore = vi.fn();
render(<VersionsList onRestore={mockOnRestore} />);
fireEvent.click(screen.getByText("common:words.restore"));
expect(mockOnRestore).not.toHaveBeenCalled();
expect(
screen.getByText("version_diff.restore_preview_title")
).toBeDefined();
expect(screen.getByText("Restore this version?")).toBeDefined();
fireEvent.click(screen.getByTestId("confirm-restore"));
expect(mockOnRestore).toHaveBeenCalledWith(
mockVersions[1].checksum,
Expand Down
78 changes: 57 additions & 21 deletions src/components/VersionsList/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,41 @@ import type { TableProps } from "antd";
const { Title } = Typography;
const EMPTY_IDS: string[] = [];

const VERSION_DIFF_EN = {
changes: "Changes",
changed_files: "Files",
initial_version: "Initial version",
no_file_changes: "No file changes",
no_line_changes: "No line changes",
compared_to_previous: "Line changes from the previous version",
added: "Added",
removed: "Removed",
modified: "Modified",
unchanged: "Unchanged",
compare: "Compare",
compare_from: "From (base)",
compare_to: "To",
compare_caption: "Line changes between the selected versions",
same_version: "Select two different versions",
restore_preview_title: "Restore this version?",
restore_preview_caption: "Line changes versus the current version",
confirm_restore: "Restore",
unknown_user: "Unknown user",
restored_from: "Restored from {{time}}",
} as const;

type VersionDiffKey = keyof typeof VERSION_DIFF_EN;

const versionDiffT = (
t: (key: string, options?: Record<string, unknown>) => string,
key: VersionDiffKey,
options?: Record<string, unknown>
) =>
t(`models:version_diff.${key}`, {
defaultValue: VERSION_DIFF_EN[key],
...options,
});

type VersionUser = {
display_name?: string | null;
avatarUrl?: string | null;
Expand Down Expand Up @@ -65,13 +100,13 @@ const VersionFileChanges: FC<{
caption: string;
loading?: boolean;
}> = ({ diff, caption, loading }) => {
const { t } = useTranslation(["models"]);
const { t } = useTranslation(["models"], { useSuspense: false });
const files = diff?.changedFiles ?? [];

if (!files.length) {
return (
<div className={styles.noChanges}>
{t("version_diff.no_file_changes")}
{versionDiffT(t, "no_file_changes")}
</div>
);
}
Expand All @@ -85,7 +120,7 @@ const VersionFileChanges: FC<{
<YAMLIcon />
{value}
<Tag color={kindTagColor[file.kind]}>
{t(`version_diff.${file.kind}`)}
{versionDiffT(t, file.kind)}
</Tag>
</Space>
),
Expand Down Expand Up @@ -120,7 +155,7 @@ const VersionFileChanges: FC<{
};

const VersionsList: FC<VersionsListProps> = ({ onRestore, branch }) => {
const { t } = useTranslation(["models", "common"]);
const { t } = useTranslation(["models", "common"], { useSuspense: false });
const [restoreTarget, setRestoreTarget] = useState<Version | null>(null);
const [compareFromId, setCompareFromId] = useState<string>();
const [compareToId, setCompareToId] = useState<string>();
Expand Down Expand Up @@ -281,15 +316,15 @@ const VersionsList: FC<VersionsListProps> = ({ onRestore, branch }) => {
if (diff.isInitial) {
return (
<span className={styles.initialVersion}>
{t("version_diff.initial_version")}
{versionDiffT(t, "initial_version")}
</span>
);
}

if (!diff.changedFiles.length) {
return (
<span className={styles.noChanges}>
{t("version_diff.no_file_changes")}
{versionDiffT(t, "no_file_changes")}
</span>
);
}
Expand All @@ -299,7 +334,7 @@ const VersionsList: FC<VersionsListProps> = ({ onRestore, branch }) => {
<span className={styles.addedCount}>+{diff.addedLines}</span>
<span className={styles.removedCount}>-{diff.removedLines}</span>
<span className={styles.filesHint}>
{t("version_diff.changed_files")}: {diff.changedFiles.length}
{versionDiffT(t, "changed_files")}: {diff.changedFiles.length}
</span>
</div>
);
Expand Down Expand Up @@ -333,8 +368,7 @@ const VersionsList: FC<VersionsListProps> = ({ onRestore, branch }) => {
{restoredFrom && (
<Tooltip title={formatTime(restoredFrom.created_at)}>
<Tag className={styles.restoreTag} color="blue">
{t("models:version_diff.restored_from", {
defaultValue: "Restored from {{time}}",
{versionDiffT(t, "restored_from", {
time: formatTime(
restoredFrom.created_at,
"YYYY-MM-DD HH:mm"
Expand All @@ -353,7 +387,7 @@ const VersionsList: FC<VersionsListProps> = ({ onRestore, branch }) => {
key: "created_at",
render: (value, record) => {
const user = record.user as VersionUser | null | undefined;
const name = authorLabel(user, t("version_diff.unknown_user"));
const name = authorLabel(user, versionDiffT(t, "unknown_user"));

return (
<div className={styles.createdBlock}>
Expand All @@ -377,7 +411,7 @@ const VersionsList: FC<VersionsListProps> = ({ onRestore, branch }) => {
},
},
{
title: t("version_diff.changes"),
title: versionDiffT(t, "changes"),
key: "changes",
render: (_, record) => renderChangeSummary(diffsById.get(record.id)),
},
Expand Down Expand Up @@ -410,8 +444,8 @@ const VersionsList: FC<VersionsListProps> = ({ onRestore, branch }) => {
diff={diff}
caption={
diff?.isInitial
? t("version_diff.initial_version")
: t("version_diff.compared_to_previous")
? versionDiffT(t, "initial_version")
: versionDiffT(t, "compared_to_previous")
}
loading={fetching}
/>
Expand All @@ -434,11 +468,11 @@ const VersionsList: FC<VersionsListProps> = ({ onRestore, branch }) => {
{totalCount > 1 && (
<Space className={styles.compareBar} size={8} wrap>
<span className={styles.compareLabel}>
{t("version_diff.compare")}
{versionDiffT(t, "compare")}
</span>
<Select
className={styles.compareSelect}
placeholder={t("version_diff.compare_from")}
placeholder={versionDiffT(t, "compare_from")}
value={compareFromId}
options={compareSelectOptions}
onChange={setCompareFromId}
Expand All @@ -448,7 +482,7 @@ const VersionsList: FC<VersionsListProps> = ({ onRestore, branch }) => {
/>
<Select
className={styles.compareSelect}
placeholder={t("version_diff.compare_to")}
placeholder={versionDiffT(t, "compare_to")}
value={compareToId}
options={compareSelectOptions}
onChange={setCompareToId}
Expand All @@ -459,12 +493,14 @@ const VersionsList: FC<VersionsListProps> = ({ onRestore, branch }) => {
</Space>
)}
{compareFromId && compareToId && compareFromId === compareToId && (
<div className={styles.noChanges}>{t("version_diff.same_version")}</div>
<div className={styles.noChanges}>
{versionDiffT(t, "same_version")}
</div>
)}
{compareDiff && (
<VersionFileChanges
diff={compareDiff}
caption={t("version_diff.compare_caption")}
caption={versionDiffT(t, "compare_caption")}
loading={compareFetching}
/>
)}
Expand All @@ -485,7 +521,7 @@ const VersionsList: FC<VersionsListProps> = ({ onRestore, branch }) => {
/>
<Modal
open={Boolean(restoreTarget)}
title={t("version_diff.restore_preview_title")}
title={versionDiffT(t, "restore_preview_title")}
onCancel={() => setRestoreTarget(null)}
zIndex={2000}
width={920}
Expand All @@ -499,13 +535,13 @@ const VersionsList: FC<VersionsListProps> = ({ onRestore, branch }) => {
onClick={confirmRestore}
data-testid="confirm-restore"
>
{t("version_diff.confirm_restore")}
{versionDiffT(t, "confirm_restore")}
</Button>,
]}
>
<VersionFileChanges
diff={restorePreviewDiff}
caption={t("version_diff.restore_preview_caption")}
caption={versionDiffT(t, "restore_preview_caption")}
/>
</Modal>
</Space>
Expand Down
3 changes: 2 additions & 1 deletion tests.setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ vi.mock("react-i18next", () => ({
// this mock makes sure any components using the translate hook can use it without a warning being shown
useTranslation: () => {
return {
t: (str: string) => str,
t: (str: string, options?: { defaultValue?: string }) =>
options?.defaultValue ?? str,
i18n: {
changeLanguage: () => new Promise(() => {}),
language: "en",
Expand Down