Setup translations

This commit is contained in:
Kieran 2023-08-28 15:45:44 +01:00
parent b96002abe9
commit 2754cb1581
Signed by: Kieran
GPG Key ID: DE71CEB3925BE941
5 changed files with 41 additions and 13 deletions

View File

@ -78,7 +78,7 @@ export function FileUploader({ defaultImage, onClear, onFileUpload }: FileUpload
<div className="file-uploader-container"> <div className="file-uploader-container">
<label className="file-uploader"> <label className="file-uploader">
<input type="file" onChange={onFileChange} /> <input type="file" onChange={onFileChange} />
{isUploading ? "Uploading..." : "Add File"} {isUploading ? <FormattedMessage defaultMessage="Uploading..." /> : <FormattedMessage defaultMessage="Add File" />}
</label> </label>
<div className="file-uploader-preview"> <div className="file-uploader-preview">
{img?.length > 0 && ( {img?.length > 0 && (

View File

@ -180,6 +180,7 @@ function CardDialog({ header, cta, cancelCta, card, onSave, onCancel }: CardDial
const [image, setImage] = useState(card?.image ?? ""); const [image, setImage] = useState(card?.image ?? "");
const [content, setContent] = useState(card?.content ?? ""); const [content, setContent] = useState(card?.content ?? "");
const [link, setLink] = useState(card?.link ?? ""); const [link, setLink] = useState(card?.link ?? "");
const { formatMessage } = useIntl();
return ( return (
<div className="new-card"> <div className="new-card">
@ -195,7 +196,7 @@ function CardDialog({ header, cta, cancelCta, card, onSave, onCancel }: CardDial
type="text" type="text"
value={title} value={title}
onChange={e => setTitle(e.target.value)} onChange={e => setTitle(e.target.value)}
placeholder="e.g. about me" placeholder={formatMessage({ defaultMessage: "e.g. about me" })}
/> />
</div> </div>
<div className="form-control"> <div className="form-control">
@ -220,7 +221,10 @@ function CardDialog({ header, cta, cancelCta, card, onSave, onCancel }: CardDial
<label htmlFor="card-content"> <label htmlFor="card-content">
<FormattedMessage defaultMessage="Content" /> <FormattedMessage defaultMessage="Content" />
</label> </label>
<textarea placeholder="Start typing..." value={content} onChange={e => setContent(e.target.value)} /> <textarea
placeholder={formatMessage({ defaultMessage: "Start typing" })}
value={content}
onChange={e => setContent(e.target.value)} />
<span className="help-text"> <span className="help-text">
<FormattedMessage <FormattedMessage
defaultMessage="Supports {markdown}" defaultMessage="Supports {markdown}"

View File

@ -5,18 +5,34 @@ import enMessages from "translations/en.json";
const DefaultLocale = "en-US"; const DefaultLocale = "en-US";
async function importLang(code: string) {
const src = await import(`translations/${code}.json`);
const typed = src.default as Record<string, { defaultMessage: string }>;
const ent = Object.entries(typed).map(([k, v]) => [k, v.defaultMessage]);
return Object.fromEntries(ent) as Record<string, string>;
}
const getMessages = (locale: string) => { const getMessages = (locale: string) => {
const truncatedLocale = locale.toLowerCase().split(/[_-]+/)[0]; const truncatedLocale = locale.toLowerCase().split(/[_-]+/)[0];
const matchLang = (lng: string) => { const matchLang = async (lng: string) => {
switch (lng) { switch (lng) {
case "de":
case "de-DE":
return await importLang("de_DE");
case "es":
case "es-ES":
return await importLang("es_ES");
case "th":
case "th-TH":
return await importLang("th_TH");
case DefaultLocale: case DefaultLocale:
case "en": case "en":
return enMessages; return enMessages;
} }
}; };
return matchLang(locale) ?? matchLang(truncatedLocale) ?? enMessages; return matchLang(locale) ?? matchLang(truncatedLocale) ?? Promise.resolve(enMessages);
}; };
export const IntlProvider = ({ children }: { children: ReactNode }) => { export const IntlProvider = ({ children }: { children: ReactNode }) => {
@ -24,8 +40,7 @@ export const IntlProvider = ({ children }: { children: ReactNode }) => {
const [messages, setMessages] = useState<Record<string, string>>(enMessages); const [messages, setMessages] = useState<Record<string, string>>(enMessages);
useEffect(() => { useEffect(() => {
const msg = getMessages(locale); getMessages(locale).then(a => setMessages(a as Record<string, string>));
setMessages(msg);
}, [locale]); }, [locale]);
return ( return (

View File

@ -83,10 +83,10 @@ export function LayoutPage() {
</Helmet> </Helmet>
<header> <header>
<div className="logo" onClick={() => navigate("/")}></div> <div className="logo" onClick={() => navigate("/")}></div>
<div className="paper"> {/*<div className="paper">
<input className="search-input" type="text" placeholder="Search" /> <input className="search-input" type="text" placeholder="Search" />
<Icon name="search" size={15} /> <Icon name="search" size={15} />
</div> </div>*/}
<div className="f-grow">{/* Future menu items go here */}</div> <div className="f-grow">{/* Future menu items go here */}</div>
<div className="header-right"> <div className="header-right">
{loggedIn()} {loggedIn()}

View File

@ -1,4 +1,5 @@
import "./root.css"; import "./root.css";
import { FormattedMessage } from "react-intl";
import { useCallback } from "react"; import { useCallback } from "react";
import type { NostrEvent } from "@snort/system"; import type { NostrEvent } from "@snort/system";
@ -31,7 +32,9 @@ export function RootPage() {
<div className="homepage"> <div className="homepage">
{hasFollowingLive && ( {hasFollowingLive && (
<> <>
<h2 className="divider line one-line">Following</h2> <h2 className="divider line one-line">
<FormattedMessage defaultMessage="Following" />
</h2>
<div className="video-grid"> <div className="video-grid">
{following.map(e => ( {following.map(e => (
<VideoTile ev={e} key={e.id} /> <VideoTile ev={e} key={e.id} />
@ -66,7 +69,9 @@ export function RootPage() {
))} ))}
{hasFollowingLive && liveNow.length > 0 && ( {hasFollowingLive && liveNow.length > 0 && (
<> <>
<h2 className="divider line one-line">Live</h2> <h2 className="divider line one-line">
<FormattedMessage defaultMessage="Live" />
</h2>
<div className="video-grid"> <div className="video-grid">
{liveNow {liveNow
.filter(e => !mutedHosts.has(getHost(e))) .filter(e => !mutedHosts.has(getHost(e)))
@ -78,7 +83,9 @@ export function RootPage() {
)} )}
{plannedEvents.length > 0 && ( {plannedEvents.length > 0 && (
<> <>
<h2 className="divider line one-line">Planned</h2> <h2 className="divider line one-line">
<FormattedMessage defaultMessage="Planned" />
</h2>
<div className="video-grid"> <div className="video-grid">
{plannedEvents.map(e => ( {plannedEvents.map(e => (
<VideoTile ev={e} key={e.id} /> <VideoTile ev={e} key={e.id} />
@ -88,7 +95,9 @@ export function RootPage() {
)} )}
{endedEvents.length > 0 && ( {endedEvents.length > 0 && (
<> <>
<h2 className="divider line one-line">Ended</h2> <h2 className="divider line one-line">
<FormattedMessage defaultMessage="Ended" />
</h2>
<div className="video-grid"> <div className="video-grid">
{endedEvents.map(e => ( {endedEvents.map(e => (
<VideoTile ev={e} key={e.id} /> <VideoTile ev={e} key={e.id} />