Settings: added lang and region picker dialog
This commit is contained in:
		
							parent
							
								
									55705b0a6d
								
							
						
					
					
						commit
						e56dc4bf7b
					
				
					 10 changed files with 316 additions and 75 deletions
				
			
		
							
								
								
									
										
											BIN
										
									
								
								bun.lockb
									
										
									
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								bun.lockb
									
										
									
									
									
								
							
										
											Binary file not shown.
										
									
								
							|  | @ -42,6 +42,7 @@ | ||||||
|     "date-fns": "^3.6.0", |     "date-fns": "^3.6.0", | ||||||
|     "fast-average-color": "^9.4.0", |     "fast-average-color": "^9.4.0", | ||||||
|     "hammerjs": "^2.0.8", |     "hammerjs": "^2.0.8", | ||||||
|  |     "iso-639-1": "^3.1.3", | ||||||
|     "masto": "^6.8.0", |     "masto": "^6.8.0", | ||||||
|     "nanostores": "^0.11.3", |     "nanostores": "^0.11.3", | ||||||
|     "solid-js": "^1.8.22", |     "solid-js": "^1.8.22", | ||||||
|  |  | ||||||
|  | @ -12,11 +12,16 @@ export function useSignedInProfiles() { | ||||||
|   }); |   }); | ||||||
|   return [ |   return [ | ||||||
|     () => { |     () => { | ||||||
|  |       try { | ||||||
|         const value = accessor(); |         const value = accessor(); | ||||||
|       if (!value) { |         if (value) { | ||||||
|         return sessions().map((x) => ({ ...x, inf: x.account.inf })); |  | ||||||
|       } |  | ||||||
|           return value; |           return value; | ||||||
|  |         } | ||||||
|  |       } catch (reason) { | ||||||
|  |         console.error("useSignedInProfiles: update acct info failed", reason); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       return sessions().map((x) => ({ ...x, inf: x.account.inf })); | ||||||
|     }, |     }, | ||||||
|     tools, |     tools, | ||||||
|   ] as const; |   ] as const; | ||||||
|  |  | ||||||
|  | @ -20,9 +20,9 @@ async function synchronised( | ||||||
|   await navigator.locks.request(name, callback); |   await navigator.locks.request(name, callback); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export const SUPPORTED_LANGS = ["en", "zh-Hans"]; | export const SUPPORTED_LANGS = ["en", "zh-Hans"] as const; | ||||||
| 
 | 
 | ||||||
| export const SUPPORTED_REGIONS = ["en_US", "en_GB", "zh_CN"]; | export const SUPPORTED_REGIONS = ["en_US", "en_GB", "zh_CN"] as const; | ||||||
| 
 | 
 | ||||||
| const DEFAULT_LANG = "en"; | const DEFAULT_LANG = "en"; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										117
									
								
								src/settings/ChooseLang.tsx
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								src/settings/ChooseLang.tsx
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,117 @@ | ||||||
|  | import { createMemo, For, type Component, type JSX } from "solid-js"; | ||||||
|  | import Scaffold from "../material/Scaffold"; | ||||||
|  | import { | ||||||
|  |   AppBar, | ||||||
|  |   Checkbox, | ||||||
|  |   IconButton, | ||||||
|  |   List, | ||||||
|  |   ListItem, | ||||||
|  |   ListItemButton, | ||||||
|  |   ListItemSecondaryAction, | ||||||
|  |   ListItemText, | ||||||
|  |   ListSubheader, | ||||||
|  |   Radio, | ||||||
|  |   Switch, | ||||||
|  |   Toolbar, | ||||||
|  | } from "@suid/material"; | ||||||
|  | import { Close as CloseIcon } from "@suid/icons-material"; | ||||||
|  | import iso639_1 from "iso-639-1"; | ||||||
|  | import { | ||||||
|  |   autoMatchLangTag, | ||||||
|  |   createTranslator, | ||||||
|  |   SUPPORTED_LANGS, | ||||||
|  | } from "../platform/i18n"; | ||||||
|  | import { Title } from "../material/typography"; | ||||||
|  | import type { Template } from "@solid-primitives/i18n"; | ||||||
|  | 
 | ||||||
|  | type ChooseLangProps = { | ||||||
|  |   code?: string; | ||||||
|  |   onCodeChange: (ncode?: string) => void; | ||||||
|  |   onClose?: JSX.EventHandlerUnion<HTMLButtonElement, MouseEvent>; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const ChooseLang: Component<ChooseLangProps> = (props) => { | ||||||
|  |   const [t] = createTranslator( | ||||||
|  |     () => import("./i18n/lang-names.json"), | ||||||
|  |     (code) => | ||||||
|  |       import(`./i18n/${code}.json`) as Promise<{ | ||||||
|  |         default: Record<string, string | undefined> & { | ||||||
|  |           ["lang.auto"]: Template<{ detected: string }>; | ||||||
|  |         }; | ||||||
|  |       }>, | ||||||
|  |   ); | ||||||
|  | 
 | ||||||
|  |   const unsupportedLangCodes = createMemo(() => { | ||||||
|  |     return iso639_1.getAllCodes().filter((x) => !["zh", "en"].includes(x)); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   const matchedLangCode = createMemo(() => autoMatchLangTag()); | ||||||
|  | 
 | ||||||
|  |   return ( | ||||||
|  |     <Scaffold | ||||||
|  |       topbar={ | ||||||
|  |         <AppBar position="static"> | ||||||
|  |           <Toolbar | ||||||
|  |             variant="dense" | ||||||
|  |             sx={{ paddingTop: "var(--safe-area-inset-top, 0px)" }} | ||||||
|  |           > | ||||||
|  |             <IconButton color="inherit" onClick={props.onClose} disableRipple> | ||||||
|  |               <CloseIcon /> | ||||||
|  |             </IconButton> | ||||||
|  |             <Title>{t("Choose Language")}</Title> | ||||||
|  |           </Toolbar> | ||||||
|  |         </AppBar> | ||||||
|  |       } | ||||||
|  |     > | ||||||
|  |       <List | ||||||
|  |         sx={{ | ||||||
|  |           paddingBottom: "var(--safe-area-inset-bottom, 0)", | ||||||
|  |         }} | ||||||
|  |       > | ||||||
|  |         <ListItemButton | ||||||
|  |           onClick={() => { | ||||||
|  |             props.onCodeChange(props.code ? undefined : matchedLangCode()); | ||||||
|  |           }} | ||||||
|  |         > | ||||||
|  |           <ListItemText> | ||||||
|  |             {t("lang.auto", { | ||||||
|  |               detected: t(`lang.${matchedLangCode()}`) ?? matchedLangCode(), | ||||||
|  |             })} | ||||||
|  |           </ListItemText> | ||||||
|  |           <ListItemSecondaryAction> | ||||||
|  |             <Switch checked={typeof props.code === "undefined"} /> | ||||||
|  |           </ListItemSecondaryAction> | ||||||
|  |         </ListItemButton> | ||||||
|  |         <List subheader={<ListSubheader>{t("Supported")}</ListSubheader>}> | ||||||
|  |           <For each={SUPPORTED_LANGS}> | ||||||
|  |             {(code) => ( | ||||||
|  |               <ListItemButton | ||||||
|  |                 disabled={typeof props.code === "undefined"} | ||||||
|  |                 onClick={[props.onCodeChange, code]} | ||||||
|  |               > | ||||||
|  |                 <ListItemText>{t(`lang.${code}`)}</ListItemText> | ||||||
|  |                 <ListItemSecondaryAction> | ||||||
|  |                   <Radio | ||||||
|  |                     checked={props.code === code || (props.code === undefined && matchedLangCode() == code)} | ||||||
|  |                   /> | ||||||
|  |                 </ListItemSecondaryAction> | ||||||
|  |               </ListItemButton> | ||||||
|  |             )} | ||||||
|  |           </For> | ||||||
|  |         </List> | ||||||
|  | 
 | ||||||
|  |         <List subheader={<ListSubheader>{t("Unsupported")}</ListSubheader>}> | ||||||
|  |           <For each={unsupportedLangCodes()}> | ||||||
|  |             {(code) => ( | ||||||
|  |               <ListItem> | ||||||
|  |                 <ListItemText>{iso639_1.getNativeName(code)}</ListItemText> | ||||||
|  |               </ListItem> | ||||||
|  |             )} | ||||||
|  |           </For> | ||||||
|  |         </List> | ||||||
|  |       </List> | ||||||
|  |     </Scaffold> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export default ChooseLang; | ||||||
							
								
								
									
										110
									
								
								src/settings/ChooseRegion.tsx
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								src/settings/ChooseRegion.tsx
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,110 @@ | ||||||
|  | import { createMemo, For, type Component, type JSX } from "solid-js"; | ||||||
|  | import Scaffold from "../material/Scaffold"; | ||||||
|  | import { | ||||||
|  |   AppBar, | ||||||
|  |   Checkbox, | ||||||
|  |   IconButton, | ||||||
|  |   List, | ||||||
|  |   ListItem, | ||||||
|  |   ListItemButton, | ||||||
|  |   ListItemSecondaryAction, | ||||||
|  |   ListItemText, | ||||||
|  |   ListSubheader, | ||||||
|  |   Radio, | ||||||
|  |   Switch, | ||||||
|  |   Toolbar, | ||||||
|  | } from "@suid/material"; | ||||||
|  | import { Close as CloseIcon } from "@suid/icons-material"; | ||||||
|  | import iso639_1 from "iso-639-1"; | ||||||
|  | import { | ||||||
|  |   autoMatchLangTag, | ||||||
|  |   autoMatchRegion, | ||||||
|  |   createTranslator, | ||||||
|  |   SUPPORTED_LANGS, | ||||||
|  |   SUPPORTED_REGIONS, | ||||||
|  | } from "../platform/i18n"; | ||||||
|  | import { Title } from "../material/typography"; | ||||||
|  | import type { Template } from "@solid-primitives/i18n"; | ||||||
|  | 
 | ||||||
|  | type ChooseRegionProps = { | ||||||
|  |   code?: string; | ||||||
|  |   onCodeChange: (ncode?: string) => void; | ||||||
|  |   onClose?: JSX.EventHandlerUnion<HTMLButtonElement, MouseEvent>; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const ChooseRegion: Component<ChooseRegionProps> = (props) => { | ||||||
|  |   const [t] = createTranslator( | ||||||
|  |     () => import("./i18n/lang-names.json"), | ||||||
|  |     (code) => | ||||||
|  |       import(`./i18n/${code}.json`) as Promise<{ | ||||||
|  |         default: Record<string, string | undefined> & { | ||||||
|  |           ["lang.auto"]: Template<{ detected: string }>; | ||||||
|  |         }; | ||||||
|  |       }>, | ||||||
|  |   ); | ||||||
|  | 
 | ||||||
|  |   const unsupportedLangCodes = createMemo(() => { | ||||||
|  |     return iso639_1.getAllCodes().filter((x) => !["zh", "en"].includes(x)); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   const matchedRegionCode = createMemo(() => autoMatchRegion()); | ||||||
|  | 
 | ||||||
|  |   return ( | ||||||
|  |     <Scaffold | ||||||
|  |       topbar={ | ||||||
|  |         <AppBar position="static"> | ||||||
|  |           <Toolbar | ||||||
|  |             variant="dense" | ||||||
|  |             sx={{ paddingTop: "var(--safe-area-inset-top, 0px)" }} | ||||||
|  |           > | ||||||
|  |             <IconButton color="inherit" onClick={props.onClose} disableRipple> | ||||||
|  |               <CloseIcon /> | ||||||
|  |             </IconButton> | ||||||
|  |             <Title>{t("Choose Language")}</Title> | ||||||
|  |           </Toolbar> | ||||||
|  |         </AppBar> | ||||||
|  |       } | ||||||
|  |     > | ||||||
|  |       <List | ||||||
|  |         sx={{ | ||||||
|  |           paddingBottom: "var(--safe-area-inset-bottom, 0)", | ||||||
|  |         }} | ||||||
|  |       > | ||||||
|  |         <ListItemButton | ||||||
|  |           onClick={() => { | ||||||
|  |             props.onCodeChange(props.code ? undefined : matchedRegionCode()); | ||||||
|  |           }} | ||||||
|  |         > | ||||||
|  |           <ListItemText> | ||||||
|  |             {t("region.auto", { | ||||||
|  |               detected: t(`region.${matchedRegionCode()}`) ?? matchedRegionCode(), | ||||||
|  |             })} | ||||||
|  |           </ListItemText> | ||||||
|  |           <ListItemSecondaryAction> | ||||||
|  |             <Switch checked={typeof props.code === "undefined"} /> | ||||||
|  |           </ListItemSecondaryAction> | ||||||
|  |         </ListItemButton> | ||||||
|  | 
 | ||||||
|  |         <List subheader={<ListSubheader>{t("Supported")}</ListSubheader>}> | ||||||
|  |           <For each={SUPPORTED_REGIONS}> | ||||||
|  |             {(code) => ( | ||||||
|  |               <ListItemButton | ||||||
|  |                 disabled={typeof props.code === "undefined"} | ||||||
|  |                 onClick={[props.onCodeChange, code]} | ||||||
|  |               > | ||||||
|  |                 <ListItemText>{t(`region.${code}`)}</ListItemText> | ||||||
|  |                 <ListItemSecondaryAction> | ||||||
|  |                   <Radio | ||||||
|  |                     checked={props.code === code || (props.code === undefined && matchedRegionCode() == code)} | ||||||
|  |                   /> | ||||||
|  |                 </ListItemSecondaryAction> | ||||||
|  |               </ListItemButton> | ||||||
|  |             )} | ||||||
|  |           </For> | ||||||
|  |         </List> | ||||||
|  |       </List> | ||||||
|  |     </Scaffold> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export default ChooseRegion; | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| import { For, Show, type ParentComponent } from "solid-js"; | import { createSignal, For, Show, type ParentComponent } from "solid-js"; | ||||||
| import Scaffold from "../material/Scaffold.js"; | import Scaffold from "../material/Scaffold.js"; | ||||||
| import { | import { | ||||||
|   AppBar, |   AppBar, | ||||||
|  | @ -40,10 +40,13 @@ import { | ||||||
|   useDateFnLocale, |   useDateFnLocale, | ||||||
| } from "../platform/i18n.jsx"; | } from "../platform/i18n.jsx"; | ||||||
| import { type Template } from "@solid-primitives/i18n"; | import { type Template } from "@solid-primitives/i18n"; | ||||||
|  | import BottomSheet from "../material/BottomSheet.jsx"; | ||||||
|  | import ChooseLang from "./ChooseLang.jsx"; | ||||||
|  | import ChooseRegion from "./ChooseRegion.jsx"; | ||||||
| 
 | 
 | ||||||
| type Strings = { | type Strings = { | ||||||
|   ["lang.auto"]: Template<{detected: string}> |   ["lang.auto"]: Template<{ detected: string }>; | ||||||
| } & Record<string, string | undefined> | } & Record<string, string | undefined>; | ||||||
| 
 | 
 | ||||||
| const Settings: ParentComponent = () => { | const Settings: ParentComponent = () => { | ||||||
|   const [t] = createTranslator( |   const [t] = createTranslator( | ||||||
|  | @ -51,7 +54,7 @@ const Settings: ParentComponent = () => { | ||||||
|       import(`./i18n/${code}.json`) as Promise<{ |       import(`./i18n/${code}.json`) as Promise<{ | ||||||
|         default: Strings; |         default: Strings; | ||||||
|       }>, |       }>, | ||||||
|     () => import(`./i18n/lang-names.json`) |     () => import(`./i18n/lang-names.json`), | ||||||
|   ); |   ); | ||||||
|   const navigate = useNavigate(); |   const navigate = useNavigate(); | ||||||
|   const settings$ = useStore($settings); |   const settings$ = useStore($settings); | ||||||
|  | @ -59,6 +62,8 @@ const Settings: ParentComponent = () => { | ||||||
|     needRefresh: [needRefresh], |     needRefresh: [needRefresh], | ||||||
|   } = useRegisterSW(); |   } = useRegisterSW(); | ||||||
|   const dateFnLocale = useDateFnLocale(); |   const dateFnLocale = useDateFnLocale(); | ||||||
|  |   const [langPickerOpen, setLangPickerOpen] = createSignal(false); | ||||||
|  |   const [regionPickerOpen, setRegionPickerOpen] = createSignal(false); | ||||||
| 
 | 
 | ||||||
|   const [profiles] = useSignedInProfiles(); |   const [profiles] = useSignedInProfiles(); | ||||||
| 
 | 
 | ||||||
|  | @ -98,7 +103,7 @@ const Settings: ParentComponent = () => { | ||||||
|             <ListItemButton disabled> |             <ListItemButton disabled> | ||||||
|               <ListItemText>{t("All Notifications")}</ListItemText> |               <ListItemText>{t("All Notifications")}</ListItemText> | ||||||
|               <ListItemSecondaryAction> |               <ListItemSecondaryAction> | ||||||
|                 <Switch value={false} disabled/> |                 <Switch value={false} disabled /> | ||||||
|               </ListItemSecondaryAction> |               </ListItemSecondaryAction> | ||||||
|             </ListItemButton> |             </ListItemButton> | ||||||
|             <Divider /> |             <Divider /> | ||||||
|  | @ -114,13 +119,13 @@ const Settings: ParentComponent = () => { | ||||||
|                 <ListItemButton disabled> |                 <ListItemButton disabled> | ||||||
|                   <ListItemText>{t("Notifications")}</ListItemText> |                   <ListItemText>{t("Notifications")}</ListItemText> | ||||||
|                   <ListItemSecondaryAction> |                   <ListItemSecondaryAction> | ||||||
|                     <Switch value={false} disabled/> |                     <Switch value={false} disabled /> | ||||||
|                   </ListItemSecondaryAction> |                   </ListItemSecondaryAction> | ||||||
|                 </ListItemButton> |                 </ListItemButton> | ||||||
|                 <Divider /> |                 <Divider /> | ||||||
|                 <ListItemButton onClick={[doSignOut, acct]}> |                 <ListItemButton onClick={[doSignOut, acct]}> | ||||||
|                   <ListItemIcon> |                   <ListItemIcon> | ||||||
|                     <Logout/> |                     <Logout /> | ||||||
|                   </ListItemIcon> |                   </ListItemIcon> | ||||||
|                   <ListItemText>{t("Sign out")}</ListItemText> |                   <ListItemText>{t("Sign out")}</ListItemText> | ||||||
|                 </ListItemButton> |                 </ListItemButton> | ||||||
|  | @ -150,65 +155,55 @@ const Settings: ParentComponent = () => { | ||||||
|         </li> |         </li> | ||||||
|         <li> |         <li> | ||||||
|           <ListSubheader>{t("This Application")}</ListSubheader> |           <ListSubheader>{t("This Application")}</ListSubheader> | ||||||
|           <ListItem> |           <ListItemButton onClick={[setLangPickerOpen, true]}> | ||||||
|             <ListItemIcon> |             <ListItemIcon> | ||||||
|               <TranslateIcon /> |               <TranslateIcon /> | ||||||
|             </ListItemIcon> |             </ListItemIcon> | ||||||
|             <ListItemText>{t("Language")}</ListItemText> |             <ListItemText | ||||||
|             <ListItemSecondaryAction> |               secondary={ | ||||||
|               <NativeSelect |                 settings$().language === undefined | ||||||
|                 value={settings$().language || "xauto"} |                   ? t("lang.auto", { | ||||||
|                 onChange={(e) => { |                       detected: | ||||||
|                   $settings.setKey( |                         t("lang." + autoMatchLangTag()) ?? autoMatchLangTag(), | ||||||
|                     "language", |                     }) | ||||||
|                     e.currentTarget.value === "xauto" |                   : t("lang." + settings$().language) | ||||||
|                       ? undefined |               } | ||||||
|                       : e.currentTarget.value, |  | ||||||
|                   ); |  | ||||||
|                 }} |  | ||||||
|             > |             > | ||||||
|                 <option value={"xauto"}> |               {t("Language")} | ||||||
|                   {t("lang.auto", { |             </ListItemText> | ||||||
|                     detected: t("lang." + autoMatchLangTag()) ?? autoMatchLangTag(), |           </ListItemButton> | ||||||
|                   })} |           <BottomSheet open={langPickerOpen()}> | ||||||
|                 </option> |             <ChooseLang | ||||||
|                 <For each={SUPPORTED_LANGS}> |               code={settings$().language} | ||||||
|                   {(code) => <option value={code}>{t("lang." + code)}</option>} |               onCodeChange={(nval) => $settings.setKey("language", nval)} | ||||||
|                 </For> |               onClose={[setLangPickerOpen, false]} | ||||||
|               </NativeSelect> |             /> | ||||||
|             </ListItemSecondaryAction> |           </BottomSheet> | ||||||
|           </ListItem> |  | ||||||
|           <Divider /> |           <Divider /> | ||||||
|           <ListItem> |           <ListItemButton onClick={[setRegionPickerOpen, true]}> | ||||||
|             <ListItemIcon> |             <ListItemIcon> | ||||||
|               <PublicIcon /> |               <PublicIcon /> | ||||||
|             </ListItemIcon> |             </ListItemIcon> | ||||||
|             <ListItemText>{t("Region")}</ListItemText> |             <ListItemText | ||||||
|             <ListItemSecondaryAction> |               secondary={ | ||||||
|               <NativeSelect |                 settings$().region === undefined | ||||||
|                 value={settings$().region} |                   ? t("region.auto", { | ||||||
|                 onChange={(e) => { |                       detected: | ||||||
|                   $settings.setKey( |                         t("region." + autoMatchRegion()) ?? autoMatchRegion(), | ||||||
|                     "region", |                     }) | ||||||
|                     e.currentTarget.value === "xauto" |                   : t("region." + settings$().region) | ||||||
|                       ? undefined |               } | ||||||
|                       : e.currentTarget.value, |  | ||||||
|                   ); |  | ||||||
|                 }} |  | ||||||
|             > |             > | ||||||
|                 <option value={"xauto"}> |               {t("Region")} | ||||||
|                   {t("region.auto", { |             </ListItemText> | ||||||
|                     detected: t("region." + autoMatchRegion()) ?? autoMatchRegion(), |           </ListItemButton> | ||||||
|                   })} |           <BottomSheet open={regionPickerOpen()}> | ||||||
|                 </option> |             <ChooseRegion | ||||||
|                 <For each={SUPPORTED_REGIONS}> |               code={settings$().region} | ||||||
|                   {(code) => ( |               onCodeChange={(nval) => $settings.setKey("region", nval)} | ||||||
|                     <option value={code}>{t("region." + code)}</option> |               onClose={[setRegionPickerOpen, false]} | ||||||
|                   )} |             /> | ||||||
|                 </For> |           </BottomSheet> | ||||||
|               </NativeSelect> |  | ||||||
|             </ListItemSecondaryAction> |  | ||||||
|           </ListItem> |  | ||||||
|           <Divider /> |           <Divider /> | ||||||
|           <ListItem> |           <ListItem> | ||||||
|             <ListItemText secondary={t("About Tutu.2nd")}> |             <ListItemText secondary={t("About Tutu.2nd")}> | ||||||
|  |  | ||||||
|  | @ -14,12 +14,17 @@ | ||||||
|   "version": "Using v{{packageVersion}} (built on {{builtAt}}, {{buildMode}})", |   "version": "Using v{{packageVersion}} (built on {{builtAt}}, {{buildMode}})", | ||||||
|   "Language": "Language", |   "Language": "Language", | ||||||
|   "Region": "Region", |   "Region": "Region", | ||||||
|   "lang.auto": "Auto({{detected}})", |   "lang.auto": "(Auto) {{detected}}", | ||||||
|   "region.auto": "Auto({{detected}})", |   "region.auto": "(Auto) {{detected}}", | ||||||
|   "region.en_GB": "Great Britan (English)", |   "region.en_GB": "Great Britan (English)", | ||||||
|   "region.en_US": "United States (English)", |   "region.en_US": "United States (English)", | ||||||
|   "region.zh_CN": "China Mainland (Chinese)", |   "region.zh_CN": "China Mainland (Chinese)", | ||||||
|   "datefmt": "yyyy/MM/dd", |   "datefmt": "yyyy/MM/dd", | ||||||
|   "Sign out": "Sign out", |   "Sign out": "Sign out", | ||||||
|   "Notifications": "Notifications" |   "Notifications": "Notifications", | ||||||
|  | 
 | ||||||
|  |   "Choose Language": "Choose Language", | ||||||
|  |   "Supported": "Supported", | ||||||
|  |   "Unsupported": "Unsupported", | ||||||
|  |   "Choose Region": "Choose Region" | ||||||
| } | } | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| { | { | ||||||
|   "Settings": "设置", |   "Settings": "设置", | ||||||
|   "Accounts": "所有账号", |   "Accounts": "所有账户", | ||||||
|   "All Notifications": "所有通知", |   "All Notifications": "所有通知", | ||||||
|   "Sign in...": "登录新账户...", |   "Sign in...": "登录新账户...", | ||||||
|   "Reading": "阅读", |   "Reading": "阅读", | ||||||
|  | @ -14,12 +14,17 @@ | ||||||
|   "version": "正在使用 v{{packageVersion}} ({{builtAt}}构建, {{buildMode}})", |   "version": "正在使用 v{{packageVersion}} ({{builtAt}}构建, {{buildMode}})", | ||||||
|   "Language": "语言", |   "Language": "语言", | ||||||
|   "Region": "区域", |   "Region": "区域", | ||||||
|   "lang.auto": "自动({{detected}})", |   "lang.auto": "(自动){{detected}}", | ||||||
|   "region.auto": "自动({{detected}})", |   "region.auto": "(自动){{detected}}", | ||||||
|   "region.en_GB": "英国和苏格兰(英语)", |   "region.en_GB": "英国和苏格兰(英语)", | ||||||
|   "region.en_US": "美国(英语)", |   "region.en_US": "美国(英语)", | ||||||
|   "region.zh_CN": "中国大陆(中文)", |   "region.zh_CN": "中国大陆(中文)", | ||||||
|   "datefmt": "yyyy年MM月dd日", |   "datefmt": "yyyy年MM月dd日", | ||||||
|   "Sign out": "登出此账户", |   "Sign out": "登出此账户", | ||||||
|   "Notifications": "通知" |   "Notifications": "通知", | ||||||
|  | 
 | ||||||
|  |   "Choose Language": "选择语言", | ||||||
|  |   "Supported": "已支持", | ||||||
|  |   "Unsupported": "尚未支持", | ||||||
|  |   "Choose Region": "选择区域" | ||||||
| } | } | ||||||
|  | @ -10,7 +10,8 @@ import { | ||||||
|   children, |   children, | ||||||
|   Suspense, |   Suspense, | ||||||
|   Match, |   Match, | ||||||
|   Switch as JsSwitch |   Switch as JsSwitch, | ||||||
|  |   ErrorBoundary | ||||||
| } from "solid-js"; | } from "solid-js"; | ||||||
| import { useDocumentTitle } from "../utils"; | import { useDocumentTitle } from "../utils"; | ||||||
| import { type mastodon } from "masto"; | import { type mastodon } from "masto"; | ||||||
|  | @ -125,7 +126,9 @@ const TimelinePanel: Component<{ | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   return ( |   return ( | ||||||
|     <> |     <ErrorBoundary fallback={(err, reset) => { | ||||||
|  |       return <p>Oops: {String(err)}</p> | ||||||
|  |     }}> | ||||||
|       <PullDownToRefresh |       <PullDownToRefresh | ||||||
|         linkedElement={scrollLinked()} |         linkedElement={scrollLinked()} | ||||||
|         loading={snapshot.loading} |         loading={snapshot.loading} | ||||||
|  | @ -202,7 +205,7 @@ const TimelinePanel: Component<{ | ||||||
|           </Match> |           </Match> | ||||||
|         </JsSwitch> |         </JsSwitch> | ||||||
|       </div> |       </div> | ||||||
|     </> |     </ErrorBoundary> | ||||||
|   ); |   ); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue