Repository: photoview/photoview
Pull request #1187: Add a basic zh-TW Traditional Chinese translation
Author: login: PeterDaveHello
Labels: name: good first issue
description: Good for newcomers
name: UI
description: Related to the frontend web ui written in Javascript
name: javascript
description: Pull requests that update Javascript code
name: improvement
description: Something, enhancing the product, but not a feature
Created: 2025-04-01T16:28:44Z | Merged: 2025-04-22T16:09:49Z

This pull request introduces support for Traditional Chinese (Taiwan) in the application. The changes include updates to language configuration, user preferences, and the addition of new translation files.

Please note that the login inside these two files is to handle the situation of there being more than one Traditional Chinese translation; the original one is for Hong Kong, and the new one is for Taiwan. The Traditional Chinese handling logic was revised with help from Claude 3.7 Sonnet, and I could use some help to review and verify if it's a good idea here. Thanks!

- `ui/src/Pages/SettingsPage/UserPreferences.tsx`
- `ui/src/localization.ts`

Summary generated by GitHub Copilot just for reference:

> Language support enhancements:
> 
> * [`ui/i18next-parser.config.js`](diffhunk://#diff-f9ca7b8554c7b990ec753e7538ebcb98123ad8c4b41868d134a0a56ca480c51aR22): Added 'zh-TW' to the list of supported languages.
> * [`ui/src/Pages/SettingsPage/UserPreferences.tsx`](diffhunk://#diff-740323c4962bb5a30398c78e303076b53cb029750f2031ed8dc7f399e6d9353aL34-R43): Updated the language preferences to include '繁體中文 (台灣)' and modified the label for '繁體中文 (香港)'.
> 
> User preferences and storage:
> 
> * [`ui/src/Pages/SettingsPage/UserPreferences.tsx`](diffhunk://#diff-740323c4962bb5a30398c78e303076b53cb029750f2031ed8dc7f399e6d9353aR184-R187): Added logic to store the selected Traditional Chinese variant in local storage.
> * [`ui/src/localization.ts`](diffhunk://#diff-e128007e6475dfb43c1b098c8f4f52fac99698755b8bbec0a6f69131cae9fc43R119-R134): Modified the translation loading function to check for the Traditional Chinese variant in local storage and load the appropriate translation file.
> 
> Translation files:
> 
> * [`ui/src/extractedTranslations/zh-TW/translation.json`](diffhunk://#diff-7412b3d778117ba04323b23857927d8c28956ec17668b48071e26d8a484e2c2eR1-R359): Added a comprehensive set of translations for Traditional Chinese (Taiwan).
> * [`ui/src/extractedTranslations/zh-TW/translation_old.json`](diffhunk://#diff-617d334bd953b86973c5e213e01eacaa16fe554b1bfe21f8fe08315c213d46daR1-R85): Added additional translations for Traditional Chinese (Taiwan).

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Expanded language support with Traditional Chinese (Taiwan) added, offering a new localized experience.
  - Updated language preferences now allow users to select their preferred Traditional Chinese variant with settings that persist the choice.
  - Introduced comprehensive translation updates across the user interface for improved clarity and consistency.

- **Refactor**
  - Enhanced the language selection process to dynamically load the appropriate translation variant based on user preferences.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/api/graphql/models/generated.go b/api/graphql/models/generated.go
index 6cd2a2d52..251dbf6a9 100644
--- a/api/graphql/models/generated.go
+++ b/api/graphql/models/generated.go
@@ -111,21 +111,22 @@ type TimelineGroup struct {
 type LanguageTranslation string
 
 const (
-	LanguageTranslationEnglish            LanguageTranslation = "English"
-	LanguageTranslationFrench             LanguageTranslation = "French"
-	LanguageTranslationItalian            LanguageTranslation = "Italian"
-	LanguageTranslationSwedish            LanguageTranslation = "Swedish"
-	LanguageTranslationDanish             LanguageTranslation = "Danish"
-	LanguageTranslationSpanish            LanguageTranslation = "Spanish"
-	LanguageTranslationPolish             LanguageTranslation = "Polish"
-	LanguageTranslationUkrainian          LanguageTranslation = "Ukrainian"
-	LanguageTranslationGerman             LanguageTranslation = "German"
-	LanguageTranslationRussian            LanguageTranslation = "Russian"
-	LanguageTranslationTraditionalChinese LanguageTranslation = "TraditionalChinese"
-	LanguageTranslationSimplifiedChinese  LanguageTranslation = "SimplifiedChinese"
-	LanguageTranslationPortuguese         LanguageTranslation = "Portuguese"
-	LanguageTranslationBasque             LanguageTranslation = "Basque"
-	LanguageTranslationTurkish            LanguageTranslation = "Turkish"
+	LanguageTranslationEnglish              LanguageTranslation = "English"
+	LanguageTranslationFrench               LanguageTranslation = "French"
+	LanguageTranslationItalian              LanguageTranslation = "Italian"
+	LanguageTranslationSwedish              LanguageTranslation = "Swedish"
+	LanguageTranslationDanish               LanguageTranslation = "Danish"
+	LanguageTranslationSpanish              LanguageTranslation = "Spanish"
+	LanguageTranslationPolish               LanguageTranslation = "Polish"
+	LanguageTranslationUkrainian            LanguageTranslation = "Ukrainian"
+	LanguageTranslationGerman               LanguageTranslation = "German"
+	LanguageTranslationRussian              LanguageTranslation = "Russian"
+	LanguageTranslationTraditionalChineseTw LanguageTranslation = "TraditionalChineseTW"
+	LanguageTranslationTraditionalChineseHk LanguageTranslation = "TraditionalChineseHK"
+	LanguageTranslationSimplifiedChinese    LanguageTranslation = "SimplifiedChinese"
+	LanguageTranslationPortuguese           LanguageTranslation = "Portuguese"
+	LanguageTranslationBasque               LanguageTranslation = "Basque"
+	LanguageTranslationTurkish              LanguageTranslation = "Turkish"
 )
 
 var AllLanguageTranslation = []LanguageTranslation{
@@ -139,7 +140,8 @@ var AllLanguageTranslation = []LanguageTranslation{
 	LanguageTranslationUkrainian,
 	LanguageTranslationGerman,
 	LanguageTranslationRussian,
-	LanguageTranslationTraditionalChinese,
+	LanguageTranslationTraditionalChineseTw,
+	LanguageTranslationTraditionalChineseHk,
 	LanguageTranslationSimplifiedChinese,
 	LanguageTranslationPortuguese,
 	LanguageTranslationBasque,
@@ -148,7 +150,7 @@ var AllLanguageTranslation = []LanguageTranslation{
 
 func (e LanguageTranslation) IsValid() bool {
 	switch e {
-	case LanguageTranslationEnglish, LanguageTranslationFrench, LanguageTranslationItalian, LanguageTranslationSwedish, LanguageTranslationDanish, LanguageTranslationSpanish, LanguageTranslationPolish, LanguageTranslationUkrainian, LanguageTranslationGerman, LanguageTranslationRussian, LanguageTranslationTraditionalChinese, LanguageTranslationSimplifiedChinese, LanguageTranslationPortuguese, LanguageTranslationBasque, LanguageTranslationTurkish:
+	case LanguageTranslationEnglish, LanguageTranslationFrench, LanguageTranslationItalian, LanguageTranslationSwedish, LanguageTranslationDanish, LanguageTranslationSpanish, LanguageTranslationPolish, LanguageTranslationUkrainian, LanguageTranslationGerman, LanguageTranslationRussian, LanguageTranslationTraditionalChineseTw, LanguageTranslationTraditionalChineseHk, LanguageTranslationSimplifiedChinese, LanguageTranslationPortuguese, LanguageTranslationBasque, LanguageTranslationTurkish:
 		return true
 	}
 	return false
diff --git a/api/graphql/resolvers/user.graphql b/api/graphql/resolvers/user.graphql
index 821ac035c..474106260 100644
--- a/api/graphql/resolvers/user.graphql
+++ b/api/graphql/resolvers/user.graphql
@@ -21,7 +21,8 @@ enum LanguageTranslation {
   Ukrainian,
   German,
   Russian,
-  TraditionalChinese,
+  TraditionalChineseTW,
+  TraditionalChineseHK,
   SimplifiedChinese,
   Portuguese,
   Basque,
diff --git a/ui/i18next-parser.config.js b/ui/i18next-parser.config.js
index cbe975274..f9678be02 100644
--- a/ui/i18next-parser.config.js
+++ b/ui/i18next-parser.config.js
@@ -19,6 +19,7 @@ module.exports = {
     'sv',
     'zh-CN',
     'zh-HK',
+    'zh-TW',
   ],
   input: 'src/**/*.{js,ts,jsx,tsx}',
   output: 'src/extractedTranslations/$LOCALE/$NAMESPACE.json',
diff --git a/ui/src/Pages/SettingsPage/UserPreferences.tsx b/ui/src/Pages/SettingsPage/UserPreferences.tsx
index ca6900da8..1522fb094 100644
--- a/ui/src/Pages/SettingsPage/UserPreferences.tsx
+++ b/ui/src/Pages/SettingsPage/UserPreferences.tsx
@@ -31,9 +31,15 @@ const languagePreferences = [
   { key: 9, label: 'Русский', flag: 'ru', value: LanguageTranslation.Russian },
   {
     key: 10,
-    label: '繁體中文',
+    label: '繁體中文 (香港)',
     flag: 'zh-HK',
-    value: LanguageTranslation.TraditionalChinese,
+    value: LanguageTranslation.TraditionalChineseHK,
+  },
+  {
+    key: 16,
+    label: '繁體中文 (台灣)',
+    flag: 'zh-TW',
+    value: LanguageTranslation.TraditionalChineseTW,
   },
   {
     key: 11,
diff --git a/ui/src/__generated__/globalTypes.ts b/ui/src/__generated__/globalTypes.ts
index 17cc6f34a..f74c6fca0 100644
--- a/ui/src/__generated__/globalTypes.ts
+++ b/ui/src/__generated__/globalTypes.ts
@@ -23,7 +23,8 @@ export enum LanguageTranslation {
   SimplifiedChinese = "SimplifiedChinese",
   Spanish = "Spanish",
   Swedish = "Swedish",
-  TraditionalChinese = "TraditionalChinese",
+  TraditionalChineseTW = "TraditionalChineseTW",
+  TraditionalChineseHK = "TraditionalChineseHK",
   Turkish = "Turkish",
   Ukrainian = "Ukrainian",
 }
diff --git a/ui/src/extractedTranslations/zh-TW/translation.json b/ui/src/extractedTranslations/zh-TW/translation.json
new file mode 100644
index 000000000..73a53e5dc
--- /dev/null
+++ b/ui/src/extractedTranslations/zh-TW/translation.json
@@ -0,0 +1,359 @@
+{
+  "album_filter": {
+    "only_favorites": "僅顯示我的最愛",
+    "sort": "排序",
+    "sort_by": "排序方式",
+    "sorting_options": {
+      "date_imported": "匯入日期",
+      "date_shot": "拍攝日期",
+      "title": "標題",
+      "type": "類型"
+    }
+  },
+  "general": {
+    "action": {
+      "add": "新增",
+      "cancel": "取消",
+      "remove": "移除",
+      "save": "儲存"
+    },
+    "loading": {
+      "album": "正在載入相簿",
+      "default": "載入中...",
+      "page": "正在載入頁面",
+      "paginate": {
+        "faces": "載入更多人物中",
+        "media": "載入更多媒體中"
+      },
+      "shares": "載入分享項目中..."
+    }
+  },
+  "header": {
+    "search": {
+      "loading": "正在載入搜尋結果...",
+      "no_results": "找不到符合的結果",
+      "placeholder": "搜尋",
+      "result_type": {
+        "albums": "相簿",
+        "media": "媒體"
+      }
+    }
+  },
+  "login_page": {
+    "field": {
+      "password": "密碼",
+      "submit": "登入",
+      "username": "使用者名稱"
+    },
+    "initial_setup": {
+      "field": {
+        "photo_path": {
+          "label": "相片路徑",
+          "placeholder": "/path/to/photos"
+        },
+        "submit": "設定 Photoview"
+      },
+      "title": "初始設定"
+    },
+    "welcome": "歡迎使用 Photoview"
+  },
+  "meta": {
+    "description": "適合個人伺服器使用的簡潔易用相簿系統"
+  },
+  "people_page": {
+    "action_label": {
+      "change_label": "變更標籤",
+      "detach_images": "移除人臉",
+      "merge_people": "合併人臉",
+      "move_faces": "移動人臉"
+    },
+    "face_group": {
+      "label_placeholder": "標籤",
+      "unlabeled": "未標示",
+      "unlabeled_person": "未標示的人物"
+    },
+    "modal": {
+      "action": {
+        "merge": "合併"
+      },
+      "detach_image_faces": {
+        "action": {
+          "detach": "移除人臉",
+          "select_images": "選擇要移除的相片"
+        },
+        "description": "將選取的相片從此人臉群組中移除，並移至新的人臉群組",
+        "title": "移除相片中的人臉"
+      },
+      "merge_face_groups": {
+        "description": "此人臉群組中的所有相片將會合併至選取的人臉群組。",
+        "destination_table": {
+          "title": "選擇目標人臉"
+        },
+        "title": "合併人臉群組"
+      },
+      "move_image_faces": {
+        "description": "將此人臉群組中選取的相片移至其他人臉群組",
+        "destination_face_group_table": {
+          "move_action": "移動相片中的人臉",
+          "title": "選擇目標人臉群組"
+        },
+        "image_select_table": {
+          "next_action": "下一步",
+          "title": "選擇要移動的相片"
+        },
+        "title": "移動相片中的人臉"
+      }
+    },
+    "recognize_unlabeled_faces_button": "辨識未標示的人臉",
+    "tableselect_face_group": {
+      "search_faces_placeholder": "搜尋人臉..."
+    },
+    "tableselect_image_faces": {
+      "search_images_placeholder": "搜尋相片..."
+    }
+  },
+  "photos_page": {
+    "title": "時間軸"
+  },
+  "places_page": {
+    "title": "地點"
+  },
+  "routes": {
+    "page_not_found": "找不到頁面"
+  },
+  "settings": {
+    "concurrent_workers": {
+      "description": "允許同時執行的掃描工作數量上限",
+      "title": "掃描器平行工作數"
+    },
+    "logout": "登出",
+    "periodic_scanner": {
+      "checkbox_label": "啟用定期掃描",
+      "field": {
+        "description": "掃描器對所有使用者執行自動掃描的頻率",
+        "label": "定期掃描間隔"
+      },
+      "interval_unit": {
+        "days": "天",
+        "hour": "小時",
+        "minutes": "分鐘",
+        "months": "個月",
+        "seconds": "秒"
+      },
+      "title": "定期掃描"
+    },
+    "scanner": {
+      "description": "將會掃描所有使用者以取得新增或更新的媒體",
+      "scan_all_users": "掃描所有使用者",
+      "title": "掃描器"
+    },
+    "user_preferences": {
+      "change_language": {
+        "description": "變更此使用者的網站語言",
+        "label": "網站語言"
+      },
+      "language_selector": {
+        "placeholder": "選擇語言"
+      },
+      "theme": {
+        "auto": {
+          "label": "依系統環境"
+        },
+        "dark": {
+          "label": "深色"
+        },
+        "description": "變更網站外觀",
+        "light": {
+          "label": "淺色"
+        },
+        "title": "佈景主題偏好設定"
+      },
+      "title": "使用者偏好設定"
+    },
+    "users": {
+      "add_user": {
+        "submit": "新增使用者"
+      },
+      "confirm_delete_user": {
+        "action": "刪除 {{user}}",
+        "description": "<0>您確定要刪除 <1></1> 嗎？</0><p>此動作無法復原</p>",
+        "title": "刪除使用者"
+      },
+      "password_reset": {
+        "description": "變更 <1>{{username}}</1> 的密碼",
+        "form": {
+          "label": "新密碼",
+          "placeholder": "密碼",
+          "submit": "變更密碼"
+        },
+        "title": "變更密碼"
+      },
+      "table": {
+        "column_names": {
+          "action": "動作",
+          "capabilities": "權限",
+          "photo_path": "相片路徑",
+          "username": "使用者名稱"
+        },
+        "new_user": "新增使用者",
+        "row": {
+          "action": {
+            "change_password": "變更密碼",
+            "delete": "刪除",
+            "edit": "編輯",
+            "scan": "掃描"
+          }
+        }
+      },
+      "title": "使用者"
+    },
+    "version_info": {
+      "build_date_title": "建置日期",
+      "title": "Photoview 版本",
+      "version_title": "發行版本"
+    }
+  },
+  "share_page": {
+    "media": {
+      "title": "已分享的媒體"
+    },
+    "protected_share": {
+      "description": "此分享內容已設定密碼保護。",
+      "password_required_error": "請輸入密碼",
+      "title": "受保護的分享"
+    },
+    "share_not_found": "找不到這個分享內容",
+    "share_not_found_description": "此分享內容可能已過期或已被刪除。",
+    "wrong_password": "密碼錯誤，請重試。"
+  },
+  "sidebar": {
+    "album": {
+      "album_cover": "相簿封面",
+      "download": {
+        "high-resolutions": {
+          "description": "RAW 檔的高解析度 JPEG 圖片",
+          "title": "高解析度"
+        },
+        "originals": {
+          "description": "原始相片和影片",
+          "title": "原始檔案"
+        },
+        "thumbnails": {
+          "description": "低解析度相片，不包含影片",
+          "title": "縮圖"
+        },
+        "web-videos": {
+          "description": "已針對網頁最佳化的影片",
+          "title": "轉檔後的影片"
+        }
+      },
+      "reset_cover": "重設封面相片",
+      "set_cover": "設為封面相片",
+      "title_placeholder": "相簿標題"
+    },
+    "download": {
+      "filesize": {
+        "byte_one": "{{count}} 位元組",
+        "byte_other": "{{count}} 位元組",
+        "giga_byte_one": "{{count}} GB",
+        "giga_byte_other": "{{count}} GB",
+        "kilo_byte_one": "{{count}} KB",
+        "kilo_byte_other": "{{count}} KB",
+        "mega_byte_one": "{{count}} MB",
+        "mega_byte_other": "{{count}} MB",
+        "tera_byte_one": "{{count}} TB",
+        "tera_byte_other": "{{count}} TB"
+      },
+      "table_columns": {
+        "dimensions": "尺寸",
+        "file_size": "大小",
+        "file_type": "類型",
+        "name": "名稱"
+      },
+      "title": "下載"
+    },
+    "location": {
+      "title": "位置資訊"
+    },
+    "media": {
+      "album_path": "相簿路徑",
+      "exif": {
+        "exposure_program": {
+          "action_program": "動作模式",
+          "aperture_priority": "光圈先決",
+          "bulb": "B 快門",
+          "creative_program": "創意模式",
+          "landscape_mode": "風景模式",
+          "manual": "手動",
+          "normal_program": "一般模式",
+          "not_defined": "未定義",
+          "portrait_mode": "人像模式",
+          "shutter_priority": "快門先決"
+        },
+        "flash": {
+          "auto": "自動",
+          "did_not_fire": "未閃光",
+          "fired": "已閃光",
+          "no_flash": "無閃光燈",
+          "no_flash_function": "無閃光燈功能",
+          "off": "關閉",
+          "on": "開啟",
+          "red_eye_reduction": "降低紅眼",
+          "return_detected": "偵測到反光",
+          "return_not_detected": "未偵測到反光"
+        },
+        "name": {
+          "aperture": "光圈",
+          "camera": "相機",
+          "coordinates": "座標",
+          "date_shot": "拍攝日期",
+          "exposure": "曝光",
+          "exposure_program": "模式",
+          "flash": "閃光燈",
+          "focal_length": "焦距",
+          "iso": "ISO",
+          "lens": "鏡頭",
+          "maker": "製造商"
+        }
+      }
+    },
+    "people": {
+      "action_label": {
+        "detach_image": "移除相片",
+        "merge_face": "合併人臉",
+        "move_face": "移動人臉"
+      },
+      "confirm_image_detach": "您確定要移除這張相片嗎？",
+      "title": "人物"
+    },
+    "sharing": {
+      "add_share": "新增分享",
+      "copy_link": "複製連結",
+      "delete": "刪除",
+      "more": "更多",
+      "no_shares_found": "找不到任何分享內容",
+      "public_link": "公開連結",
+      "title": "分享選項"
+    }
+  },
+  "sidemenu": {
+    "albums": "相簿",
+    "people": "人物",
+    "photos": "時間軸",
+    "places": "地點",
+    "settings": "設定"
+  },
+  "timeline_filter": {
+    "date": {
+      "dropdown_all": "從今天起",
+      "dropdown_year": "{{year}} 年以及更早",
+      "label": "日期"
+    }
+  },
+  "title": {
+    "loading_album": "正在載入相簿",
+    "login": "登入",
+    "people": "人物",
+    "settings": "設定"
+  }
+}
diff --git a/ui/src/extractedTranslations/zh-TW/translation_old.json b/ui/src/extractedTranslations/zh-TW/translation_old.json
new file mode 100644
index 000000000..33568b767
--- /dev/null
+++ b/ui/src/extractedTranslations/zh-TW/translation_old.json
@@ -0,0 +1,85 @@
+{
+  "albums_page": {
+    "title": "相簿"
+  },
+  "general": {
+    "action": {
+      "delete": "刪除",
+      "more": "更多"
+    },
+    "loading": {
+      "media": "正在載入媒體",
+      "timeline": "正在載入時間軸"
+    }
+  },
+  "header": {
+    "search": {
+      "result_type": {
+        "photos": "相片"
+      }
+    }
+  },
+  "people_page": {
+    "face_group": {
+      "action": {
+        "add_label": "新增標籤",
+        "change_label": "修改標籤",
+        "detach_face": "分離人臉",
+        "merge_face": "合併人臉",
+        "move_faces": "移動人臉"
+      }
+    },
+    "modal": {
+      "merge_people_groups": {
+        "description": "此人臉群組中的所有圖片都會合併至所選的人臉群組。",
+        "destination_table": {
+          "title": "選擇目標人臉"
+        },
+        "title": "合併人臉群組"
+      }
+    },
+    "table": {
+      "select_face_group": {
+        "search_faces_placeholder": "搜尋人臉..."
+      },
+      "select_image_faces": {
+        "search_images_placeholder": "搜尋圖片..."
+      }
+    }
+  },
+  "settings": {
+    "users": {
+      "table": {
+        "column_names": {
+          "admin": "管理員"
+        }
+      }
+    }
+  },
+  "sidebar": {
+    "album": {
+      "cover_photo": "相簿封面",
+      "title": "相簿選項"
+    },
+    "download": {
+      "filesize": {
+        "byte": "{{count}} 位元組",
+        "byte_plural": "{{count}} 位元組",
+        "giga_byte": "{{count}} GB",
+        "giga_byte_plural": "{{count}} GB",
+        "kilo_byte": "{{count}} KB",
+        "kilo_byte_plural": "{{count}} KB",
+        "mega_byte": "{{count}} MB",
+        "mega_byte_plural": "{{count}} MB",
+        "tera_byte": "{{count}} TB",
+        "tera_byte_plural": "{{count}} TB"
+      }
+    },
+    "media": {
+      "album": "相簿"
+    },
+    "sharing": {
+      "table_header": "公開分享"
+    }
+  }
+}
diff --git a/ui/src/localization.ts b/ui/src/localization.ts
index 937221726..fc22bc51b 100644
--- a/ui/src/localization.ts
+++ b/ui/src/localization.ts
@@ -115,13 +115,17 @@ export const loadTranslations = () => {
           i18n.changeLanguage('ru')
         })
         return
-      case LanguageTranslation.TraditionalChinese:
-        import('./extractedTranslations/zh-HK/translation.json').then(
-          language => {
-            i18n.addResourceBundle('zh-HK', 'translation', language)
-            i18n.changeLanguage('zh-HK')
-          }
-        )
+      case LanguageTranslation.TraditionalChineseTW:
+        import('./extractedTranslations/zh-TW/translation.json').then(language => {
+          i18n.addResourceBundle('zh-TW', 'translation', language)
+          i18n.changeLanguage('zh-TW')
+        })
+        return
+      case LanguageTranslation.TraditionalChineseHK:
+        import('./extractedTranslations/zh-HK/translation.json').then(language => {
+          i18n.addResourceBundle('zh-HK', 'translation', language)
+          i18n.changeLanguage('zh-HK')
+        })
         return
       case LanguageTranslation.SimplifiedChinese:
         import('./extractedTranslations/zh-CN/translation.json').then(


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>🧹 Nitpick comments (1)</summary><blockquote>

<details>
<summary>ui/src/extractedTranslations/zh-TW/translation.json (1)</summary><blockquote>

`1-360`: **Comprehensive Addition of Traditional Chinese (Taiwan) Translations**  
The new JSON translation file is well structured and covers a wide range of UI sections—from album filtering and general actions to detailed pages like login, settings, and sidebar navigation. The organization into logical sections (e.g., `"album_filter"`, `"general"`, `"header"`, etc.) is clear and mirrors the expected UI components, which should help maintain consistency across locales.  


**Consistency Check for Placeholders and Terminology**  
Placeholders such as `{{year}}` and `{{count}}` are used across different sections (e.g., in `"timeline_filter"` and `"download"`). Ensure these placeholders exactly match the ones used in other locale files so that dynamic substitution works seamlessly at runtime. Also, review the terminologies (e.g., "相簿", "設定", etc.) against existing traditional Chinese translations in the system to maintain consistency in tone and vocabulary.  


**Readability and Cultural Appropriateness**  
The translations appear accurate and culturally adjusted for a Traditional Chinese (Taiwan) audience. It would be beneficial to have a native speaker review the nuances and subtleties in phrases (if not already done) to guarantee that the UI communicates effectively with the target audience.  


**Documentation and Future Maintenance**  
Consider adding a short header comment within the file (or accompanying documentation) to note that this file serves the zh-TW locale. This can help future contributors quickly understand its purpose, especially when multiple Chinese variants are supported.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 862ac5af393697329fb955ad2787ac4181112b46 and aa8f54691815aadb5763f487b1e09307148892f7.

</details>

<details>
<summary>📒 Files selected for processing (5)</summary>

* `ui/i18next-parser.config.js` (1 hunks)
* `ui/src/Pages/SettingsPage/UserPreferences.tsx` (2 hunks)
* `ui/src/extractedTranslations/zh-TW/translation.json` (1 hunks)
* `ui/src/extractedTranslations/zh-TW/translation_old.json` (1 hunks)
* `ui/src/localization.ts` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Definitions (1)</summary>

<details>
<summary>ui/src/Pages/SettingsPage/UserPreferences.tsx (1)</summary><blockquote>

<details>
<summary>api/graphql/models/generated.go (1)</summary>

* `LanguageTranslation` (111-111)

</details>

</blockquote></details>

</details><details>
<summary>🪛 Biome (1.9.4)</summary>

<details>
<summary>ui/src/localization.ts</summary>

[error] 119-119: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.


(lint/correctness/noSwitchDeclarations)

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (5)</summary><blockquote>

<details>
<summary>ui/i18next-parser.config.js (1)</summary>

`22-22`: **Addition of zh-TW locale looks good!**

The locale has been correctly added to the i18next parser configuration, enabling the extraction of Traditional Chinese (Taiwan) translations.

</details>
<details>
<summary>ui/src/Pages/SettingsPage/UserPreferences.tsx (3)</summary>

`34-35`: **Good clarification for the Traditional Chinese (Hong Kong) label.**

Updating the label from just '繁體中文' to '繁體中文 (香港)' helps clearly differentiate between the two Traditional Chinese variants now that Taiwan is also supported.

---

`38-43`: **Well-structured addition of Traditional Chinese (Taiwan) option.**

The new language option is properly formatted and includes all necessary properties: key, label, flag, and value. This implementation allows users to select Traditional Chinese (Taiwan) as their preferred language.

---

`184-187`: **Effective implementation of Traditional Chinese variant storage.**

The code correctly identifies when a user selects Traditional Chinese and stores the specific variant (zh-HK or zh-TW) in localStorage. This works seamlessly with the updated localization logic to load the appropriate translation file.

</details>
<details>
<summary>ui/src/extractedTranslations/zh-TW/translation_old.json (1)</summary>

`1-85`: **Translation file for Traditional Chinese (Taiwan) looks good, but naming is unclear.**

The translations appear comprehensive and appropriate for Traditional Chinese (Taiwan). However, the filename `translation_old.json` is unusual. Typically, translation files are named `translation.json`. 


Could you clarify the purpose of this file and its naming convention? The localization.ts code imports `./extractedTranslations/zh-TW/translation.json`, not `translation_old.json`. Is there also a main `translation.json` file for zh-TW, and if so, what's the relationship between these files?

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-01T16:31:32Z
user: login: PeterDaveHello
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T16:39:07Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-01T16:40:06Z
user: login: PeterDaveHello
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T16:52:58Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-01T16:53:49Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>🧹 Nitpick comments (2)</summary><blockquote>

<details>
<summary>ui/src/localization.ts (2)</summary><blockquote>

`118-123`: **Add error handling for Traditional Chinese (Taiwan) translation loading.**

The implementation correctly loads the Traditional Chinese (Taiwan) translation file, but lacks error handling which is consistent with other language imports in this file.

Consider adding error handling to the translation import:

```diff
  case LanguageTranslation.TraditionalChineseTW:
    import('./extractedTranslations/zh-TW/translation.json').then(language => {
      i18n.addResourceBundle('zh-TW', 'translation', language)
      i18n.changeLanguage('zh-TW')
-   })
+   }).catch(error => {
+     console.error('Failed to load zh-TW translations:', error)
+     // Fallback to English or another available language
+     i18n.changeLanguage('en')
+   })
    return
```

---

`124-129`: **Add error handling for Traditional Chinese (Hong Kong) translation loading.**

Similar to the Taiwan variant, the Hong Kong translation loading lacks error handling.

Consider adding error handling to the translation import:

```diff
  case LanguageTranslation.TraditionalChineseHK:
    import('./extractedTranslations/zh-HK/translation.json').then(language => {
      i18n.addResourceBundle('zh-HK', 'translation', language)
      i18n.changeLanguage('zh-HK')
-   })
+   }).catch(error => {
+     console.error('Failed to load zh-HK translations:', error)
+     // Fallback to English or another available language
+     i18n.changeLanguage('en')
+   })
    return
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 18360e71ae9bc454f14f79e20dcefa4971ffad81 and aa39cd7b436d3c2a0c4f43041c698c120ee0fc16.

</details>

<details>
<summary>⛔ Files ignored due to path filters (1)</summary>

* `ui/src/__generated__/globalTypes.ts` is excluded by `!**/__generated__/**`

</details>

<details>
<summary>📒 Files selected for processing (7)</summary>

* `api/graphql/models/generated.go` (3 hunks)
* `api/graphql/resolvers/user.graphql` (1 hunks)
* `ui/i18next-parser.config.js` (1 hunks)
* `ui/src/Pages/SettingsPage/UserPreferences.tsx` (1 hunks)
* `ui/src/extractedTranslations/zh-TW/translation.json` (1 hunks)
* `ui/src/extractedTranslations/zh-TW/translation_old.json` (1 hunks)
* `ui/src/localization.ts` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>📓 Path-based instructions (2)</summary>

<details>
<summary>`**/graphql/resolvers/*`: Do not provide any suggestions for this auto-generated code</summary>

> `**/graphql/resolvers/*`: Do not provide any suggestions for this auto-generated code

- `api/graphql/resolvers/user.graphql`

</details>
<details>
<summary>`**/generated.go`: Do not provide any suggestions for this auto-generated code</summary>

> `**/generated.go`: Do not provide any suggestions for this auto-generated code

- `api/graphql/models/generated.go`

</details>

</details><details>
<summary>🧠 Learnings (2)</summary>

<details>
<summary>📓 Common learnings</summary>

```
Learnt from: PeterDaveHello
PR: photoview/photoview#1187
File: ui/src/localization.ts:119-134
Timestamp: 2025-04-01T16:40:05.143Z
Learning: The Photoview application supports different variants of Traditional Chinese (Hong Kong and Taiwan) through localStorage with the 'traditional-chinese-variant' key to determine which translation file to load.
```

</details>
<details>
<summary>ui/src/localization.ts (1)</summary>

```
Learnt from: PeterDaveHello
PR: photoview/photoview#1187
File: ui/src/localization.ts:119-134
Timestamp: 2025-04-01T16:40:05.143Z
Learning: The Photoview application supports different variants of Traditional Chinese (Hong Kong and Taiwan) through localStorage with the 'traditional-chinese-variant' key to determine which translation file to load.
```

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (7)</summary><blockquote>

<details>
<summary>ui/i18next-parser.config.js (1)</summary>

`22-22`: **Support added for Traditional Chinese (Taiwan) locale.**

The 'zh-TW' locale has been correctly added to the list of supported languages, allowing the i18next parser to extract and process translation strings for Traditional Chinese (Taiwan).

</details>
<details>
<summary>ui/src/Pages/SettingsPage/UserPreferences.tsx (2)</summary>

`34-37`: **Clear labeling for Traditional Chinese (Hong Kong).**

The label has been appropriately updated to explicitly specify the region, and the value now correctly references the new enum value `TraditionalChineseHK`.

---

`38-43`: **Traditional Chinese (Taiwan) option properly added.**

The new language option for Traditional Chinese (Taiwan) has been added with the correct label, flag identifier, and enum value. The implementation follows the established pattern for language preferences in the application.

</details>
<details>
<summary>api/graphql/resolvers/user.graphql (1)</summary>

`24-25`: **GraphQL schema updated with distinct Traditional Chinese variants.**

The enum `LanguageTranslation` has been properly updated to include two separate entries for Traditional Chinese: one for Taiwan (`TraditionalChineseTW`) and one for Hong Kong (`TraditionalChineseHK`). This change aligns with the frontend updates and provides clear differentiation between the two variants.

</details>
<details>
<summary>ui/src/localization.ts (1)</summary>

`118-129`: **Implementation differs from previously used localStorage approach.**

Based on the retrieved learning, the application previously used localStorage with the 'traditional-chinese-variant' key to determine which Traditional Chinese variant to load. The current implementation takes a different approach by directly mapping GraphQL enum values to specific translations.

Can you confirm this change in approach is intentional? The current implementation simplifies the code by directly mapping enum values to translations but removes the ability for users to switch between Traditional Chinese variants without changing their user preferences in the database.

</details>
<details>
<summary>api/graphql/models/generated.go (1)</summary>

`1-320`: **Auto-generated file—no review required.**

Per coding guidelines, no suggestions or comments are provided for auto-generated code.

</details>
<details>
<summary>ui/src/extractedTranslations/zh-TW/translation.json (1)</summary>

`1-359`: **Translation file structure and content look good.**

The Traditional Chinese (Taiwan) translation file is well-structured, complete, and uses appropriate terminology for zh-TW. No issues found.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-15T14:39:00Z
user: login: googollee
body: Look good to me.
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-21T19:07:40Z
user: login: kkovaletp
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-22T16:09:25Z

Review comments:
diff_hunk: @@ -116,12 +116,22 @@ export const loadTranslations = () => {
         })
         return
       case LanguageTranslation.TraditionalChinese:
-        import('./extractedTranslations/zh-HK/translation.json').then(
-          language => {
-            i18n.addResourceBundle('zh-HK', 'translation', language)
-            i18n.changeLanguage('zh-HK')
-          }
-        )
+        const traditionalVariant = localStorage.getItem('traditional-chinese-variant') || 'zh-HK'
+        if (traditionalVariant === 'zh-TW') {
+          import('./extractedTranslations/zh-TW/translation.json').then(
+            language => {
+              i18n.addResourceBundle('zh-TW', 'translation', language)
+              i18n.changeLanguage('zh-TW')
+            }
+          )
+        } else {
+          import('./extractedTranslations/zh-HK/translation.json').then(
+            language => {
+              i18n.addResourceBundle('zh-HK', 'translation', language)
+              i18n.changeLanguage('zh-HK')
+            }
+          )
+        }
path: ui/src/localization.ts
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**The localization logic for Traditional Chinese variants is well implemented, but has a minor issue.**

The implementation correctly checks localStorage for the preferred Traditional Chinese variant and loads the appropriate translation file. However, there's a potential scope issue with the variable declaration in the switch statement.


Please wrap the entire case block in curly braces to address the scope issue:

```diff
  case LanguageTranslation.TraditionalChinese:
+   {
      const traditionalVariant = localStorage.getItem('traditional-chinese-variant') || 'zh-HK'
      if (traditionalVariant === 'zh-TW') {
        import('./extractedTranslations/zh-TW/translation.json').then(
          language => {
            i18n.addResourceBundle('zh-TW', 'translation', language)
            i18n.changeLanguage('zh-TW')
          }
        )
      } else {
        import('./extractedTranslations/zh-HK/translation.json').then(
          language => {
            i18n.addResourceBundle('zh-HK', 'translation', language)
            i18n.changeLanguage('zh-HK')
          }
        )
      }
+   }
    return
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
  case LanguageTranslation.TraditionalChinese:
   {
        const traditionalVariant = localStorage.getItem('traditional-chinese-variant') || 'zh-HK'
        if (traditionalVariant === 'zh-TW') {
          import('./extractedTranslations/zh-TW/translation.json').then(
            language => {
              i18n.addResourceBundle('zh-TW', 'translation', language)
              i18n.changeLanguage('zh-TW')
            }
          )
        } else {
          import('./extractedTranslations/zh-HK/translation.json').then(
            language => {
              i18n.addResourceBundle('zh-HK', 'translation', language)
              i18n.changeLanguage('zh-HK')
            }
          )
        }
   }
    return
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

[error] 119-119: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.


(lint/correctness/noSwitchDeclarations)

</details>

</details>

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-04-01T16:31:32Z
author_association: NONE
diff_hunk: @@ -116,12 +116,22 @@ export const loadTranslations = () => {
         })
         return
       case LanguageTranslation.TraditionalChinese:
-        import('./extractedTranslations/zh-HK/translation.json').then(
-          language => {
-            i18n.addResourceBundle('zh-HK', 'translation', language)
-            i18n.changeLanguage('zh-HK')
-          }
-        )
+        const traditionalVariant = localStorage.getItem('traditional-chinese-variant') || 'zh-HK'
+        if (traditionalVariant === 'zh-TW') {
+          import('./extractedTranslations/zh-TW/translation.json').then(
+            language => {
+              i18n.addResourceBundle('zh-TW', 'translation', language)
+              i18n.changeLanguage('zh-TW')
+            }
+          )
+        } else {
+          import('./extractedTranslations/zh-HK/translation.json').then(
+            language => {
+              i18n.addResourceBundle('zh-HK', 'translation', language)
+              i18n.changeLanguage('zh-HK')
+            }
+          )
+        }
path: ui/src/localization.ts
user: login: PeterDaveHello
body: Thank you! Do you have any other suggestions I should consider?
created_at: 2025-04-01T16:39:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -116,12 +116,22 @@ export const loadTranslations = () => {
         })
         return
       case LanguageTranslation.TraditionalChinese:
-        import('./extractedTranslations/zh-HK/translation.json').then(
-          language => {
-            i18n.addResourceBundle('zh-HK', 'translation', language)
-            i18n.changeLanguage('zh-HK')
-          }
-        )
+        const traditionalVariant = localStorage.getItem('traditional-chinese-variant') || 'zh-HK'
+        if (traditionalVariant === 'zh-TW') {
+          import('./extractedTranslations/zh-TW/translation.json').then(
+            language => {
+              i18n.addResourceBundle('zh-TW', 'translation', language)
+              i18n.changeLanguage('zh-TW')
+            }
+          )
+        } else {
+          import('./extractedTranslations/zh-HK/translation.json').then(
+            language => {
+              i18n.addResourceBundle('zh-HK', 'translation', language)
+              i18n.changeLanguage('zh-HK')
+            }
+          )
+        }
path: ui/src/localization.ts
user: login: coderabbitai[bot]
body: <details>
<summary>🧩 Analysis chain</summary>

🏁 Script executed:
```
#!/bin/bash
# Check how translation files are imported in the codebase
echo "Checking for translation_old.json import patterns:"
rg "translation_old\.json" --type ts

echo "Checking for localStorage usage patterns:"
rg "localStorage\.get" --type ts

echo "Checking for error handling in translation imports:"
rg "import\(.*translation.*\).then" -A5 --type ts
```

Length of output: 6674

---



</details>

@PeterDaveHello I've examined your code further and have a few additional suggestions for enhancing the Traditional Chinese locale support:

1. **Add error handling for translation file imports**: None of the language imports currently handle potential loading failures. Consider adding `.catch()` handlers:

```typescript
import('./extractedTranslations/zh-TW/translation.json')
  .then(language => {
    i18n.addResourceBundle('zh-TW', 'translation', language)
    i18n.changeLanguage('zh-TW')
  })
  .catch(error => {
    console.error('Failed to load zh-TW translations:', error)
    // Fallback to another language or show an error message
  })
```

2. **Add explanatory comments**: The Traditional Chinese variant handling is unique compared to other languages in the codebase. Consider adding a comment explaining this special case:

```typescript
// Handle different Traditional Chinese variants (Hong Kong and Taiwan)
// based on user preference stored in localStorage
```

3. **Validate localStorage value**: Consider explicitly validating the value from localStorage to handle unexpected values:

```typescript
const traditionalVariant = localStorage.getItem('traditional-chinese-variant')
if (traditionalVariant === 'zh-TW') {
  // Load zh-TW
} else if (traditionalVariant === 'zh-HK' || traditionalVariant === null) {
  // Load zh-HK (default)
} else {
  console.warn(`Unexpected Traditional Chinese variant: ${traditionalVariant}, falling back to zh-HK`)
  // Load zh-HK
}
```

4. **Investigate translation_old.json usage**: You've added both `translation.json` and `translation_old.json` for zh-TW, but I couldn't find imports for any `translation_old.json` files in the codebase. You might want to verify if these files are needed or how they're used.

---

<details>
<summary>✏️ Learnings added</summary>

```
Learnt from: PeterDaveHello
PR: photoview/photoview#1187
File: ui/src/localization.ts:119-134
Timestamp: 2025-04-01T16:40:04.860Z
Learning: The Photoview application supports different variants of Traditional Chinese (Hong Kong and Taiwan) through localStorage with the 'traditional-chinese-variant' key to determine which translation file to load.
```

> Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure [review instructions](https://docs.coderabbit.ai/guides/review-instructions). You can manage existing learnings in the [app](https://app.coderabbit.ai/learnings).

</details>

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-04-01T16:40:06Z
author_association: NONE
diff_hunk: @@ -116,12 +116,22 @@ export const loadTranslations = () => {
         })
         return
       case LanguageTranslation.TraditionalChinese:
-        import('./extractedTranslations/zh-HK/translation.json').then(
-          language => {
-            i18n.addResourceBundle('zh-HK', 'translation', language)
-            i18n.changeLanguage('zh-HK')
-          }
-        )
+        const traditionalVariant = localStorage.getItem('traditional-chinese-variant') || 'zh-HK'
+        if (traditionalVariant === 'zh-TW') {
+          import('./extractedTranslations/zh-TW/translation.json').then(
+            language => {
+              i18n.addResourceBundle('zh-TW', 'translation', language)
+              i18n.changeLanguage('zh-TW')
+            }
+          )
+        } else {
+          import('./extractedTranslations/zh-HK/translation.json').then(
+            language => {
+              i18n.addResourceBundle('zh-HK', 'translation', language)
+              i18n.changeLanguage('zh-HK')
+            }
+          )
+        }
path: ui/src/localization.ts
user: login: PeterDaveHello
body: Maybe I should remove the "old" translation.
created_at: 2025-04-01T16:52:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -116,12 +116,22 @@ export const loadTranslations = () => {
         })
         return
       case LanguageTranslation.TraditionalChinese:
-        import('./extractedTranslations/zh-HK/translation.json').then(
-          language => {
-            i18n.addResourceBundle('zh-HK', 'translation', language)
-            i18n.changeLanguage('zh-HK')
-          }
-        )
+        const traditionalVariant = localStorage.getItem('traditional-chinese-variant') || 'zh-HK'
+        if (traditionalVariant === 'zh-TW') {
+          import('./extractedTranslations/zh-TW/translation.json').then(
+            language => {
+              i18n.addResourceBundle('zh-TW', 'translation', language)
+              i18n.changeLanguage('zh-TW')
+            }
+          )
+        } else {
+          import('./extractedTranslations/zh-HK/translation.json').then(
+            language => {
+              i18n.addResourceBundle('zh-HK', 'translation', language)
+              i18n.changeLanguage('zh-HK')
+            }
+          )
+        }
path: ui/src/localization.ts
user: login: coderabbitai[bot]
body: <details>
<summary>🧩 Analysis chain</summary>

After analyzing the search results, I'll provide more targeted advice about whether removing the "translation_old.json" file is appropriate.

---

🏁 Script executed:
```
#!/bin/bash
# Check how translation_old.json files are used in the codebase
echo "Checking for imports or references to translation_old.json:"
rg "translation_old\.json" --type ts

echo "Checking for other translation files in the project:"
find ui/src/extractedTranslations -name "translation_old.json" | sort

echo "Checking if there's any specific load mechanism for *_old.json files:"
rg "addResourceBundle|i18next\.init" -A10 -B10 --type ts
```

Length of output: 8427

---



</details>

@PeterDaveHello I checked how the translation files are used in the codebase, and here's what I found:

1. **No direct imports**: There are no explicit imports of any `translation_old.json` files in the TypeScript code.

2. **Consistent pattern**: All 13 languages in the project (including zh-HK and zh-CN) have both `translation.json` and `translation_old.json` files.

3. **Loading mechanism**: The application only explicitly loads the `translation.json` files through `addResourceBundle`.

Since all other languages follow this pattern, I recommend either:

1. **Keep both files** for consistency with the rest of the codebase
2. **Investigate further** to understand the purpose of these `_old` files before removing them

If you determine these files aren't needed for any functionality, you could consider cleaning up all `translation_old.json` files in a separate PR rather than just removing the zh-TW one.

Would you like me to help you investigate further why these files exist in the project?

---

<details>
<summary>🧠 Learnings used</summary>

```
Learnt from: PeterDaveHello
PR: photoview/photoview#1187
File: ui/src/localization.ts:119-134
Timestamp: 2025-04-01T16:40:05.143Z
Learning: The Photoview application supports different variants of Traditional Chinese (Hong Kong and Taiwan) through localStorage with the 'traditional-chinese-variant' key to determine which translation file to load.
```

</details>

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-04-01T16:53:49Z
author_association: NONE
diff_hunk: @@ -0,0 +1,85 @@
+{
+  "albums_page": {
+    "title": "相簿"
+  },
+  "general": {
+    "action": {
+      "delete": "刪除",
+      "more": "更多"
+    },
+    "loading": {
+      "media": "正在載入媒體",
+      "timeline": "正在載入時間軸"
+    }
+  },
+  "header": {
+    "search": {
+      "result_type": {
+        "photos": "相片"
+      }
+    }
+  },
+  "people_page": {
+    "face_group": {
+      "action": {
+        "add_label": "新增標籤",
+        "change_label": "修改標籤",
+        "detach_face": "分離人臉",
+        "merge_face": "合併人臉",
+        "move_faces": "移動人臉"
+      }
+    },
+    "modal": {
+      "merge_people_groups": {
+        "description": "此人臉群組中的所有圖片都會合併至所選的人臉群組。",
+        "destination_table": {
+          "title": "選擇目標人臉"
+        },
+        "title": "合併人臉群組"
+      }
+    },
+    "table": {
+      "select_face_group": {
+        "search_faces_placeholder": "搜尋人臉..."
+      },
+      "select_image_faces": {
+        "search_images_placeholder": "搜尋圖片..."
+      }
+    }
+  },
+  "settings": {
+    "users": {
+      "table": {
+        "column_names": {
+          "admin": "管理員"
+        }
+      }
+    }
+  },
+  "sidebar": {
+    "album": {
+      "cover_photo": "相簿封面",
+      "title": "相簿選項"
+    },
+    "download": {
+      "filesize": {
+        "byte": "{{count}} 位元組",
+        "byte_plural": "{{count}} 位元組",
+        "giga_byte": "{{count}} GB",
+        "giga_byte_plural": "{{count}} GB",
+        "kilo_byte": "{{count}} KB",
+        "kilo_byte_plural": "{{count}} KB",
+        "mega_byte": "{{count}} MB",
+        "mega_byte_plural": "{{count}} MB",
+        "tera_byte": "{{count}} TB",
+        "tera_byte_plural": "{{count}} TB"
+      }
+    },
+    "media": {
+      "album": "相簿"
+    },
+    "sharing": {
+      "table_header": "公開分享"
+    }
+  }
+}
path: ui/src/extractedTranslations/zh-TW/translation_old.json
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Clarify or remove legacy translation file.**

The presence of both `translation.json` and `translation_old.json` for zh-TW may cause confusion. If `translation_old.json` is not required for migration or reference, consider removing it to avoid ambiguity.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-15T14:38:59Z
author_association: NONE

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-04-01T16:28:55Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough
This update introduces support for two distinct Traditional Chinese variants—Taiwan and Hong Kong—across the application. The i18next parser configuration now includes the locale "zh-TW". Language preferences on the settings page are updated to differentiate between Traditional Chinese (Hong Kong) and Traditional Chinese (Taiwan), with corresponding new entries. New translation files for "zh-TW" have been added. The translation loading logic is enhanced to conditionally import either the "zh-TW" or "zh-HK" translations based on the selected variant stored in local storage. The backend GraphQL schema and Go models reflect this split by replacing the single Traditional Chinese language enum with two separate enums.

## Changes

| File(s) | Change Summary |
| --- | --- |
| `ui/i18next-parser.config.js` | Added the locale `"zh-TW"` to the `locales` array to support Traditional Chinese (Taiwan). |
| `ui/src/Pages/SettingsPage/UserPreferences.tsx` | Updated `languagePreferences` array to rename Traditional Chinese (Hong Kong) label to `"繁體中文 (香港)"` with value `TraditionalChineseHK`; added new entry for Traditional Chinese (Taiwan) with key `16`, label `"繁體中文 (台灣)"`, flag `"zh-TW"`, and value `TraditionalChineseTW`. |
| `ui/src/extractedTranslations/zh-TW/translation.json` and `translation_old.json` | Added new translation files containing UI text translations for various sections in Traditional Chinese (zh-TW). |
| `ui/src/localization.ts` | Modified `loadTranslations` function to distinguish between `TraditionalChineseTW` and `TraditionalChineseHK`, loading the corresponding translation JSON files based on the selected variant stored in local storage. |
| `api/graphql/models/generated.go` | Split `LanguageTranslationTraditionalChinese` enum into two constants: `LanguageTranslationTraditionalChineseTw` and `LanguageTranslationTraditionalChineseHk`; updated `AllLanguageTranslation` slice and `IsValid()` method accordingly. |
| `api/graphql/resolvers/user.graphql` | Updated `LanguageTranslation` enum by removing `TraditionalChinese` and adding `TraditionalChineseTW` and `TraditionalChineseHK` entries. |

## Sequence Diagram(s)
```mermaid
sequenceDiagram
    participant User
    participant Pref as UserPreferences
    participant LS as localStorage
    participant Loc as loadTranslations

    User->>Pref: Select Traditional Chinese (zh-TW)
    Pref->>LS: Set "traditional-chinese-variant" = "zh-TW"
    Note over LS: User selection saved

    User->>Loc: Initiate translation loading
    Loc->>LS: Retrieve "traditional-chinese-variant"
    alt Variant is "zh-TW"
        Loc->>Loc: Load zh-TW translations
    else
        Loc->>Loc: Load zh-HK translations
    end
```

<!-- walkthrough_end -->

<!-- announcements_start -->

> [!TIP]
> <details>
> <summary>⚡💬 Agentic Chat (Pro Plan, General Availability)</summary>
> 
> - We're introducing multi-step agentic chat in review comments and issue comments, within and outside of PR's. This feature enhances review and issue discussions with the CodeRabbit agentic chat by enabling advanced interactions, including the ability to create pull requests directly from comments and add commits to existing pull requests.
> 
> </details>

<!-- announcements_end -->
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details>
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---



<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=photoview/photoview&utm_content=1187):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: NONE
user: login: kkovaletp
created_at: 2025-04-01T19:33:08Z
body: @PeterDaveHello, thanks for the contribution!
I see that you have already started the review of the AI suggestions. If that makes sense, please implement, if not - explain in the thread.
Besides that, I'm curious, why do we need to introduce a new logic with the translation variants instead of defining the new translation as a new completely independent one in the list with the clear label, so that all users, interested in Chinese UI, can easily differentiate them and choose the right one manually?
Regarding the local storage: is this a new behavior compared to other translations? Will the zh-TW translation always be loaded and stored in the local storage even if not used?

@googollee, by any chance, are you familiar with the Traditional Chinese (Taiwan) language? Can you please review the correctness of the translation from the language perspective?

Regarding the code review: We're still looking for a maintainer of the frontend part of the product, so all Frontend-related PRs are now in the queue, waiting for the review.
author_association: CONTRIBUTOR
user: login: codecov[bot]
created_at: 2025-04-01T19:37:37Z
body: ## [Codecov](https://app.codecov.io/gh/photoview/photoview/pull/1187?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=photoview) Report
Attention: Patch coverage is `47.61905%` with `11 lines` in your changes missing coverage. Please review.
> Project coverage is 56.93%. Comparing base [(`18360e7`)](https://app.codecov.io/gh/photoview/photoview/commit/18360e71ae9bc454f14f79e20dcefa4971ffad81?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=photoview) to head [(`aa39cd7`)](https://app.codecov.io/gh/photoview/photoview/commit/aa39cd7b436d3c2a0c4f43041c698c120ee0fc16?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=photoview).
> Report is 2 commits behind head on master.

| [Files with missing lines](https://app.codecov.io/gh/photoview/photoview/pull/1187?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=photoview) | Patch % | Lines |
|---|---|---|
| [ui/src/localization.ts](https://app.codecov.io/gh/photoview/photoview/pull/1187?src=pr&el=tree&filepath=ui%2Fsrc%2Flocalization.ts&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=photoview#diff-dWkvc3JjL2xvY2FsaXphdGlvbi50cw==) | 0.00% | [11 Missing :warning: ](https://app.codecov.io/gh/photoview/photoview/pull/1187?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=photoview) |

<details><summary>Additional details and impacted files</summary>


```diff
@@           Coverage Diff           @@
##           master    #1187   +/-   ##
=======================================
  Coverage   56.93%   56.93%           
=======================================
  Files         207      207           
  Lines       17723    17734   +11     
  Branches      560      560           
=======================================
+ Hits        10090    10097    +7     
- Misses       7323     7327    +4     
  Partials      310      310           
```

| [Flag](https://app.codecov.io/gh/photoview/photoview/pull/1187/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=photoview) | Coverage Δ | |
|---|---|---|
| [api-mysql](https://app.codecov.io/gh/photoview/photoview/pull/1187/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=photoview) | `35.47% <ø> (ø)` | |
| [api-postgres](https://app.codecov.io/gh/photoview/photoview/pull/1187/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=photoview) | `35.54% <ø> (ø)` | |
| [api-sqlite](https://app.codecov.io/gh/photoview/photoview/pull/1187/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=photoview) | `35.52% <ø> (ø)` | |
| [ui](https://app.codecov.io/gh/photoview/photoview/pull/1187/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=photoview) | `68.80% <47.61%> (-0.01%)` | :arrow_down: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=photoview#carryforward-flags-in-the-pull-request-comment) to find out more.

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/photoview/photoview/pull/1187?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=photoview).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=photoview).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :package: [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
</details>
author_association: NONE
user: login: PeterDaveHello
created_at: 2025-04-02T20:01:04Z
body: @CodiumAI-Agent /review
author_association: CONTRIBUTOR
user: login: QodoAI-Agent
created_at: 2025-04-02T20:01:50Z
body: ## PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

<table>
<tr><td>⏱️&nbsp;<strong>Estimated effort to review</strong>: 3 🔵🔵🔵⚪⚪</td></tr>
<tr><td>🧪&nbsp;<strong>No relevant tests</strong></td></tr>
<tr><td>🔒&nbsp;<strong>No security concerns identified</strong></td></tr>
<tr><td>⚡&nbsp;<strong>Recommended focus areas for review</strong><br><br>

<details><summary><a href='https://github.com/photoview/photoview/pull/1187/files#diff-740323c4962bb5a30398c78e303076b53cb029750f2031ed8dc7f399e6d9353aR34-R43'><strong>Ambiguous Enum Usage</strong></a>

Both zh-HK and zh-TW variants use the same LanguageTranslation value. Consider using a distinct enum value or clearer abstraction to prevent potential confusion and maintenance issues.
</summary>

```typescript
  label: '繁體中文 (香港)',
  flag: 'zh-HK',
  value: LanguageTranslation.TraditionalChinese,
},
{
  key: 16,
  label: '繁體中文 (台灣)',
  flag: 'zh-TW',
  value: LanguageTranslation.TraditionalChinese,
},
```

</details>

<details><summary><a href='https://github.com/photoview/photoview/pull/1187/files#diff-617d334bd953b86973c5e213e01eacaa16fe554b1bfe21f8fe08315c213d46daR1-R85'><strong>Legacy File</strong></a>

The addition of a translation_old.json alongside the new translation file may lead to confusion. Confirm whether this file is still required or if it should be removed.
</summary>

```json
{
  "albums_page": {
    "title": "相簿"
  },
  "general": {
    "action": {
      "delete": "刪除",
      "more": "更多"
    },
    "loading": {
      "media": "正在載入媒體",
      "timeline": "正在載入時間軸"
    }
  },
  "header": {
    "search": {
      "result_type": {
        "photos": "相片"
      }
    }
  },
  "people_page": {
    "face_group": {
      "action": {
        "add_label": "新增標籤",
        "change_label": "修改標籤",
        "detach_face": "分離人臉",
        "merge_face": "合併人臉",
        "move_faces": "移動人臉"
      }
    },
    "modal": {
      "merge_people_groups": {
        "description": "此人臉群組中的所有圖片都會合併至所選的人臉群組。",
        "destination_table": {
          "title": "選擇目標人臉"
        },
        "title": "合併人臉群組"
      }
    },
    "table": {
      "select_face_group": {
        "search_faces_placeholder": "搜尋人臉..."
      },
      "select_image_faces": {
        "search_images_placeholder": "搜尋圖片..."
      }
    }
  },
  "settings": {
    "users": {
      "table": {
        "column_names": {
          "admin": "管理員"
        }
      }
    }
  },
  "sidebar": {
    "album": {
      "cover_photo": "相簿封面",
      "title": "相簿選項"
    },
    "download": {
      "filesize": {
        "byte": "{{count}} 位元組",
        "byte_plural": "{{count}} 位元組",
        "giga_byte": "{{count}} GB",
        "giga_byte_plural": "{{count}} GB",
        "kilo_byte": "{{count}} KB",
        "kilo_byte_plural": "{{count}} KB",
        "mega_byte": "{{count}} MB",
        "mega_byte_plural": "{{count}} MB",
        "tera_byte": "{{count}} TB",
        "tera_byte_plural": "{{count}} TB"
      }
    },
    "media": {
      "album": "相簿"
    },
    "sharing": {
      "table_header": "公開分享"
    }
  }
}
```

</details>

</td></tr>
</table>

author_association: NONE
user: login: PeterDaveHello
created_at: 2025-04-02T21:05:08Z
body: @CodiumAI-Agent /improve


author_association: CONTRIBUTOR
user: login: QodoAI-Agent
created_at: 2025-04-02T21:05:48Z
body: ## PR Code Suggestions ✨

No code suggestions found for the PR.
author_association: NONE
user: login: PeterDaveHello
created_at: 2025-04-04T05:59:24Z
body: > @PeterDaveHello, thanks for the contribution! I see that you have already started the review of the AI suggestions. If that makes sense, please implement, if not - explain in the thread. Besides that, I'm curious, why do we need to introduce a new logic with the translation variants instead of defining the new translation as a new completely independent one in the list with the clear label, so that all users, interested in Chinese UI, can easily differentiate them and choose the right one manually? Regarding the local storage: is this a new behavior compared to other translations? Will the zh-TW translation always be loaded and stored in the local storage even if not used?

Hi @kkovaletp, thanks for your question!

The reason I initially introduced this logic was simply because the label "TraditionalChinese" was already occupied by zh-HK. Since I wasn't very familiar with the project's structure at that time, I asked AI for suggestions, and that's why the translation variant logic was introduced.

However, I think your suggestion makes sense. Perhaps we could rename zh-TW and zh-HK to "TraditionalChineseTW" and "TraditionalChineseHK" respectively, to avoid this issue and make it clearer for users to differentiate between the Traditional Chinese variants.

What do you think about this approach?

author_association: CONTRIBUTOR
user: login: QodoAI-Agent
created_at: 2025-04-04T06:00:11Z
body: ## PR Code Suggestions ✨

No code suggestions found for the PR.
author_association: NONE
user: login: kkovaletp
created_at: 2025-04-04T09:05:10Z
body: > we could rename zh-TW and zh-HK to "TraditionalChineseTW" and "TraditionalChineseHK" respectively

Yes, I like this way. Just make sure that they both are written nicely and clearly in the UI too. The labels are written in the language, they represent, but the meaning might be something like "Traditional Chinese (Hong Kong)" and "Traditional Chinese (Taiwan)". Maybe there are better representations in the Traditional Chinese languages to distinguish between these 2 dialects...
author_association: CONTRIBUTOR
user: login: googollee
created_at: 2025-04-15T09:05:16Z
body: I agreed on the rename `LanguageTranslation.TraditionalChinese` -> `LanguageTranslation.TraditionalChineseTW`/`LanguageTranslation.TraditionalChineseHK`.

While I can read Traditional Chinese, I'm not a native speaker and may miss subtle regional differences. My approach will be to approve the initial translation and then gather feedback.
author_association: CONTRIBUTOR
user: login: PeterDaveHello
created_at: 2025-04-15T14:37:22Z
body: PR updated!
author_association: CONTRIBUTOR
user: login: PeterDaveHello
created_at: 2025-04-15T14:37:33Z
body: @coderabbitai full review
author_association: CONTRIBUTOR
user: login: coderabbitai[bot]
created_at: 2025-04-15T14:37:38Z
body: <!-- This is an auto-generated reply by CodeRabbit -->
<details>
<summary>:white_check_mark: Actions performed</summary>

Full review triggered.

</details>
author_association: NONE
user: login: PeterDaveHello
created_at: 2025-04-15T14:37:59Z
body: @CodiumAI-Agent /review
author_association: CONTRIBUTOR
user: login: PeterDaveHello
created_at: 2025-04-15T14:38:08Z
body: @CodiumAI-Agent /improve
author_association: CONTRIBUTOR
user: login: QodoAI-Agent
created_at: 2025-04-15T14:38:49Z
body: ## PR Code Suggestions ✨

No code suggestions found for the PR.
author_association: NONE
user: login: PeterDaveHello
created_at: 2025-04-15T17:07:34Z
body: @kkovaletp please feel free to let me know if there's anything else I should do ;)
author_association: CONTRIBUTOR
user: login: QodoAI-Agent
created_at: 2025-04-15T17:08:29Z
body: ## PR Code Suggestions ✨

No code suggestions found for the PR.
author_association: NONE
user: login: kkovaletp
created_at: 2025-04-15T18:54:19Z
body: @PeterDaveHello, thanks for the changes. Let's see reviews from others as well
author_association: CONTRIBUTOR
user: login: PeterDaveHello
created_at: 2025-04-19T20:19:26Z
body: @kkovaletp By the way, I also sent another PR: https://github.com/photoview/photoview/pull/1194. It's just something I noticed while working on the translation.
author_association: CONTRIBUTOR