Skip to content

Commit 33fdb2e

Browse files
authored
Merge pull request #126 from lcomplete/dev
feat: Add i18n multi-language support and enhance Twitter settings
2 parents 46fa354 + 7016904 commit 33fdb2e

75 files changed

Lines changed: 2158 additions & 889 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/copilot-instructions.md

Lines changed: 0 additions & 98 deletions
This file was deleted.

app/client/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,16 @@
3838
"cors": "^2.8.5",
3939
"date-fns": "^2.29.3",
4040
"formik": "^2.2.9",
41+
"i18next": "23.16.8",
42+
"i18next-browser-languagedetector": "7.2.2",
4143
"moment": "^2.29.4",
4244
"notistack": "^2.0.8",
4345
"postcss": "^8.4.14",
4446
"react": "^18.2.0",
4547
"react-beautiful-dnd": "^13.1.1",
4648
"react-date-range": "^1.4.0",
4749
"react-dom": "^18.2.0",
50+
"react-i18next": "14.1.3",
4851
"react-intersection-observer": "^9.4.0",
4952
"react-markdown": "^10.1.0",
5053
"react-photo-view": "^1.2.3",

app/client/src/api/batchOrganize.ts

Lines changed: 58 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -83,20 +83,58 @@ type ApiResult<T> = {
8383
data: T;
8484
};
8585

86+
type ErrorResponse = {
87+
message?: string;
88+
error?: {
89+
message?: string;
90+
};
91+
};
92+
93+
function redirectToSignIn() {
94+
const from = `${globalThis.location.pathname}${globalThis.location.search}`;
95+
globalThis.location.href = `/signin?from=${encodeURIComponent(from)}`;
96+
}
97+
98+
function normalizeApiError(error: unknown, fallbackMessage: string): Error {
99+
if (axios.isAxiosError(error)) {
100+
if (error.response?.status === 401) {
101+
redirectToSignIn();
102+
return new Error("Unauthorized");
103+
}
104+
const responseData = error.response?.data as ErrorResponse | undefined;
105+
return new Error(
106+
responseData?.message ||
107+
responseData?.error?.message ||
108+
error.message ||
109+
fallbackMessage
110+
);
111+
}
112+
113+
if (error instanceof Error) {
114+
return error;
115+
}
116+
117+
return new Error(fallbackMessage);
118+
}
119+
86120
/**
87121
* Filter pages with pagination for batch operations.
88122
*/
89123
export async function filterPages(
90124
query: BatchFilterQuery
91125
): Promise<BatchFilterResult> {
92-
const res = await axios.post<ApiResult<BatchFilterResult>>(
93-
"/api/page/batch/filter",
94-
query
95-
);
96-
if (res.data.code !== 0) {
97-
throw new Error(res.data.message || "Failed to filter pages.");
126+
try {
127+
const res = await axios.post<ApiResult<BatchFilterResult>>(
128+
"/api/page/batch/filter",
129+
query
130+
);
131+
if (res.data.code !== 0) {
132+
throw new Error(res.data.message || "Failed to filter pages.");
133+
}
134+
return res.data.data;
135+
} catch (error) {
136+
throw normalizeApiError(error, "Failed to filter pages.");
98137
}
99-
return res.data.data;
100138
}
101139

102140
/**
@@ -105,21 +143,22 @@ export async function filterPages(
105143
export async function batchMoveToCollection(
106144
request: BatchMoveRequest
107145
): Promise<BatchMoveResult> {
108-
const res = await axios.post<ApiResult<BatchMoveResult>>(
109-
"/api/page/batch/moveToCollection",
110-
request
111-
);
112-
if (res.data.code !== 0) {
113-
throw new Error(res.data.message || "Failed to move pages.");
146+
try {
147+
const res = await axios.post<ApiResult<BatchMoveResult>>(
148+
"/api/page/batch/moveToCollection",
149+
request
150+
);
151+
if (res.data.code !== 0) {
152+
throw new Error(res.data.message || "Failed to move pages.");
153+
}
154+
return res.data.data;
155+
} catch (error) {
156+
throw normalizeApiError(error, "Failed to move pages.");
114157
}
115-
return res.data.data;
116158
}
117159

118160
/**
119-
* Collected time mode options with labels for UI.
161+
* Collected time mode values for UI.
120162
*/
121-
export const COLLECTED_AT_MODE_OPTIONS: { value: CollectedAtMode; label: string }[] = [
122-
{ value: "KEEP", label: "Keep Original" },
123-
{ value: "USE_PUBLISH_TIME", label: "Use Publish Time" },
124-
];
163+
export const COLLECTED_AT_MODES: CollectedAtMode[] = ["KEEP", "USE_PUBLISH_TIME"];
125164

app/client/src/components/BatchPageItemList.tsx

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,13 @@ interface BatchPageItemListProps {
5656
readonly selectAll?: boolean;
5757
/** Callback when an item's selection changes */
5858
readonly onSelectItem?: (id: number, selected: boolean) => void;
59+
/** Optional translated labels */
60+
readonly labels?: {
61+
openOriginal?: string;
62+
author?: string;
63+
collected?: string;
64+
published?: string;
65+
};
5966
}
6067

6168
export default function BatchPageItemList({
@@ -64,6 +71,7 @@ export default function BatchPageItemList({
6471
selectedIds = new Set(),
6572
selectAll = false,
6673
onSelectItem,
74+
labels,
6775
}: BatchPageItemListProps) {
6876
return (
6977
<Box className="space-y-2">
@@ -102,7 +110,7 @@ export default function BatchPageItemList({
102110
>
103111
{title}
104112
</a>
105-
<Tooltip title="Open original URL">
113+
<Tooltip title={labels?.openOriginal || "Open original URL"}>
106114
<IconButton
107115
size="small"
108116
href={item.url}
@@ -122,9 +130,9 @@ export default function BatchPageItemList({
122130

123131
{/* Metadata */}
124132
<Box className="text-xs text-gray-400 mt-1 flex gap-4 flex-wrap">
125-
{item.author && <span>Author: {item.author}</span>}
126-
{item.collectedAt && <span>Collected: <SmartMoment dt={item.collectedAt} /></span>}
127-
{pubTime && <span>Published: <SmartMoment dt={pubTime} /></span>}
133+
{item.author && <span>{labels?.author || "Author"}: {item.author}</span>}
134+
{item.collectedAt && <span>{labels?.collected || "Collected"}: <SmartMoment dt={item.collectedAt} /></span>}
135+
{pubTime && <span>{labels?.published || "Published"}: <SmartMoment dt={pubTime} /></span>}
128136
</Box>
129137
</Box>
130138
</Box>

0 commit comments

Comments
 (0)