terry-staging #16
@@ -0,0 +1,43 @@
|
|||||||
|
---
|
||||||
|
title: "Favorite Other-Language Post Redirect — Quick Fix"
|
||||||
|
type: quick-fix
|
||||||
|
date: 2026-06-04
|
||||||
|
---
|
||||||
|
|
||||||
|
# Favorite Other-Language Post Redirect — Quick Fix
|
||||||
|
|
||||||
|
## Bug
|
||||||
|
|
||||||
|
When the user is on a UI language (e.g. Chinese) and clicks a favorited post that does not have a translation in that language, the post page silently redirected to `/browse` and the user could not see the post.
|
||||||
|
|
||||||
|
## Root Cause
|
||||||
|
|
||||||
|
`src/pages/PostRedirect/index.tsx` requested `GET /api/posts/{id}?lang=<ui-lang>`. The backend returns `404` when the post has no translation in the requested language. The redirect's `.catch` silently sent the user to `/browse`, hiding the post entirely.
|
||||||
|
|
||||||
|
## Fix
|
||||||
|
|
||||||
|
`PostRedirect` now retries without the `lang` parameter on failure. If the post exists in any language, the user is taken to the post anyway, and a toast tells them the post is shown in its original language because the selected language is unavailable. If the retry also fails (post truly missing), behavior is unchanged: redirect to `/browse`.
|
||||||
|
|
||||||
|
A new i18n key `postShownInOriginalLanguage` was added in all 7 locales.
|
||||||
|
|
||||||
|
### Files Modified
|
||||||
|
|
||||||
|
- `src/pages/PostRedirect/index.tsx` — added language fallback fetch, toast notice.
|
||||||
|
- `src/locales/zh-CN.ts` — added `postShownInOriginalLanguage`.
|
||||||
|
- `src/locales/en.ts` — added `postShownInOriginalLanguage`.
|
||||||
|
- `src/locales/ja.ts` — added `postShownInOriginalLanguage`.
|
||||||
|
- `src/locales/ko.ts` — added `postShownInOriginalLanguage`.
|
||||||
|
- `src/locales/vi.ts` — added `postShownInOriginalLanguage`.
|
||||||
|
- `src/locales/id.ts` — added `postShownInOriginalLanguage`.
|
||||||
|
- `src/locales/ms.ts` — added `postShownInOriginalLanguage`.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
- `npx tsc --noEmit`
|
||||||
|
- `npm run format:check`
|
||||||
|
- `npm test` (13 files, 49 tests)
|
||||||
|
- Staging curl confirmed: `GET /api/posts/{id}?lang=en` returns `404` for a Chinese-only post, while `GET /api/posts/{id}` returns `200` with the post in its source language.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
No deploy was performed.
|
||||||
@@ -166,6 +166,8 @@ export const enDict: Dict = {
|
|||||||
favoritesSortHot: "Hot resources",
|
favoritesSortHot: "Hot resources",
|
||||||
favoritesSearchPlaceholder: "Search your favorites",
|
favoritesSearchPlaceholder: "Search your favorites",
|
||||||
favoritesUnavailable: "Unavailable",
|
favoritesUnavailable: "Unavailable",
|
||||||
|
postShownInOriginalLanguage:
|
||||||
|
"This post is not available in your selected language. Showing the original.",
|
||||||
favoritesClearFilters: "Clear filters",
|
favoritesClearFilters: "Clear filters",
|
||||||
favorites: "My Favorites",
|
favorites: "My Favorites",
|
||||||
favoritesComingSoon: "Coming Soon",
|
favoritesComingSoon: "Coming Soon",
|
||||||
|
|||||||
@@ -166,6 +166,8 @@ export const idDict: Dict = {
|
|||||||
favoritesSortHot: "Sumber populer",
|
favoritesSortHot: "Sumber populer",
|
||||||
favoritesSearchPlaceholder: "Cari favorit Anda",
|
favoritesSearchPlaceholder: "Cari favorit Anda",
|
||||||
favoritesUnavailable: "Tidak tersedia",
|
favoritesUnavailable: "Tidak tersedia",
|
||||||
|
postShownInOriginalLanguage:
|
||||||
|
"Postingan ini tidak tersedia dalam bahasa yang Anda pilih. Menampilkan bahasa aslinya.",
|
||||||
favoritesClearFilters: "Hapus filter",
|
favoritesClearFilters: "Hapus filter",
|
||||||
favorites: "Favorit Saya",
|
favorites: "Favorit Saya",
|
||||||
favoritesComingSoon: "Segera Hadir",
|
favoritesComingSoon: "Segera Hadir",
|
||||||
|
|||||||
@@ -166,6 +166,8 @@ export const jaDict: Dict = {
|
|||||||
favoritesSortHot: "人気資料",
|
favoritesSortHot: "人気資料",
|
||||||
favoritesSearchPlaceholder: "お気に入りを検索",
|
favoritesSearchPlaceholder: "お気に入りを検索",
|
||||||
favoritesUnavailable: "利用不可",
|
favoritesUnavailable: "利用不可",
|
||||||
|
postShownInOriginalLanguage:
|
||||||
|
"この投稿は選択した言語で提供されていないため、原語で表示します。",
|
||||||
favoritesClearFilters: "フィルターをクリア",
|
favoritesClearFilters: "フィルターをクリア",
|
||||||
favorites: "お気に入り",
|
favorites: "お気に入り",
|
||||||
favoritesComingSoon: "近日公開",
|
favoritesComingSoon: "近日公開",
|
||||||
|
|||||||
@@ -164,6 +164,8 @@ export const koDict: Dict = {
|
|||||||
favoritesSortHot: "인기 자료",
|
favoritesSortHot: "인기 자료",
|
||||||
favoritesSearchPlaceholder: "내 즐겨찾기 검색",
|
favoritesSearchPlaceholder: "내 즐겨찾기 검색",
|
||||||
favoritesUnavailable: "사용 불가",
|
favoritesUnavailable: "사용 불가",
|
||||||
|
postShownInOriginalLanguage:
|
||||||
|
"이 게시물은 선택하신 언어로 제공되지 않아 원본 언어로 표시됩니다.",
|
||||||
favoritesClearFilters: "필터 지우기",
|
favoritesClearFilters: "필터 지우기",
|
||||||
favorites: "내 즐겨찾기",
|
favorites: "내 즐겨찾기",
|
||||||
favoritesComingSoon: "출시 예정",
|
favoritesComingSoon: "출시 예정",
|
||||||
|
|||||||
@@ -164,6 +164,8 @@ export const msDict: Dict = {
|
|||||||
favoritesSortHot: "Sumber popular",
|
favoritesSortHot: "Sumber popular",
|
||||||
favoritesSearchPlaceholder: "Cari kegemaran anda",
|
favoritesSearchPlaceholder: "Cari kegemaran anda",
|
||||||
favoritesUnavailable: "Tidak tersedia",
|
favoritesUnavailable: "Tidak tersedia",
|
||||||
|
postShownInOriginalLanguage:
|
||||||
|
"Pos ini tidak tersedia dalam bahasa pilihan anda. Memaparkan bahasa asal.",
|
||||||
favoritesClearFilters: "Kosongkan penapis",
|
favoritesClearFilters: "Kosongkan penapis",
|
||||||
favorites: "Kegemaran Saya",
|
favorites: "Kegemaran Saya",
|
||||||
favoritesComingSoon: "Akan Hadir",
|
favoritesComingSoon: "Akan Hadir",
|
||||||
|
|||||||
@@ -163,6 +163,8 @@ export const viDict: Dict = {
|
|||||||
favoritesSortHot: "Tài nguyên hot",
|
favoritesSortHot: "Tài nguyên hot",
|
||||||
favoritesSearchPlaceholder: "Tìm trong yêu thích",
|
favoritesSearchPlaceholder: "Tìm trong yêu thích",
|
||||||
favoritesUnavailable: "Không khả dụng",
|
favoritesUnavailable: "Không khả dụng",
|
||||||
|
postShownInOriginalLanguage:
|
||||||
|
"Bài đăng này không có trong ngôn ngữ bạn chọn. Đang hiển thị bằng ngôn ngữ gốc.",
|
||||||
favoritesClearFilters: "Xóa bộ lọc",
|
favoritesClearFilters: "Xóa bộ lọc",
|
||||||
favorites: "Yêu thích của tôi",
|
favorites: "Yêu thích của tôi",
|
||||||
favoritesComingSoon: "Sắp ra mắt",
|
favoritesComingSoon: "Sắp ra mắt",
|
||||||
|
|||||||
@@ -159,6 +159,7 @@ export const zhDict: Dict = {
|
|||||||
favoritesSortHot: "热门资料",
|
favoritesSortHot: "热门资料",
|
||||||
favoritesSearchPlaceholder: "搜索我的收藏",
|
favoritesSearchPlaceholder: "搜索我的收藏",
|
||||||
favoritesUnavailable: "已下架",
|
favoritesUnavailable: "已下架",
|
||||||
|
postShownInOriginalLanguage: "该贴子暂未提供当前语言版本,以原语言显示。",
|
||||||
favoritesClearFilters: "清除筛选",
|
favoritesClearFilters: "清除筛选",
|
||||||
favorites: "我的收藏",
|
favorites: "我的收藏",
|
||||||
favoritesComingSoon: "功能即将推出",
|
favoritesComingSoon: "功能即将推出",
|
||||||
|
|||||||
@@ -5,13 +5,15 @@ import { langQuery, useI18n } from "../../i18n";
|
|||||||
import { useLocalizedPath } from "../../useLocalizedPath";
|
import { useLocalizedPath } from "../../useLocalizedPath";
|
||||||
import { MOCK_POSTS } from "../../mocks/mockPosts";
|
import { MOCK_POSTS } from "../../mocks/mockPosts";
|
||||||
import { POST_STREAM_USES_MOCK } from "../../components/messageStream/hooks/usePostStream";
|
import { POST_STREAM_USES_MOCK } from "../../components/messageStream/hooks/usePostStream";
|
||||||
|
import { useToast } from "../../components/Toast";
|
||||||
import type { Post } from "../../types/post";
|
import type { Post } from "../../types/post";
|
||||||
|
|
||||||
export function PostRedirect() {
|
export function PostRedirect() {
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
const { lang } = useI18n();
|
const { lang, t } = useI18n();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const lp = useLocalizedPath();
|
const lp = useLocalizedPath();
|
||||||
|
const { showToast } = useToast();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!id) {
|
if (!id) {
|
||||||
@@ -30,16 +32,25 @@ export function PostRedirect() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
getJSON<Post>(
|
const goToPost = (post: Post) => {
|
||||||
`/api/posts/${id}?lang=${encodeURIComponent(langQuery(lang))}`,
|
|
||||||
)
|
|
||||||
.then((post) => {
|
|
||||||
navigate(lp(`/browse?post=${encodeURIComponent(post.id)}`), {
|
navigate(lp(`/browse?post=${encodeURIComponent(post.id)}`), {
|
||||||
replace: true,
|
replace: true,
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
getJSON<Post>(
|
||||||
|
`/api/posts/${id}?lang=${encodeURIComponent(langQuery(lang))}`,
|
||||||
|
)
|
||||||
|
.then(goToPost)
|
||||||
|
.catch(() => {
|
||||||
|
getJSON<Post>(`/api/posts/${id}`)
|
||||||
|
.then((post) => {
|
||||||
|
showToast(t("postShownInOriginalLanguage"));
|
||||||
|
goToPost(post);
|
||||||
})
|
})
|
||||||
.catch(() => navigate(lp("/browse"), { replace: true }));
|
.catch(() => navigate(lp("/browse"), { replace: true }));
|
||||||
}, [id, lang, navigate, lp]);
|
});
|
||||||
|
}, [id, lang, navigate, lp, showToast, t]);
|
||||||
|
|
||||||
return <div className="text-neutral-400">…</div>;
|
return <div className="text-neutral-400">…</div>;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user