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">
<label className="file-uploader">
<input type="file" onChange={onFileChange} />
{isUploading ? "Uploading..." : "Add File"}
{isUploading ? <FormattedMessage defaultMessage="Uploading..." /> : <FormattedMessage defaultMessage="Add File" />}
</label>
<div className="file-uploader-preview">
{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 [content, setContent] = useState(card?.content ?? "");
const [link, setLink] = useState(card?.link ?? "");
const { formatMessage } = useIntl();
return (
<div className="new-card">
@ -195,7 +196,7 @@ function CardDialog({ header, cta, cancelCta, card, onSave, onCancel }: CardDial
type="text"
value={title}
onChange={e => setTitle(e.target.value)}
placeholder="e.g. about me"
placeholder={formatMessage({ defaultMessage: "e.g. about me" })}
/>
</div>
<div className="form-control">
@ -220,7 +221,10 @@ function CardDialog({ header, cta, cancelCta, card, onSave, onCancel }: CardDial
<label htmlFor="card-content">
<FormattedMessage defaultMessage="Content" />
</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">
<FormattedMessage
defaultMessage="Supports {markdown}"

View File

@ -5,18 +5,34 @@ import enMessages from "translations/en.json";
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 truncatedLocale = locale.toLowerCase().split(/[_-]+/)[0];
const matchLang = (lng: string) => {
const matchLang = async (lng: string) => {
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 "en":
return enMessages;
}
};
return matchLang(locale) ?? matchLang(truncatedLocale) ?? enMessages;
return matchLang(locale) ?? matchLang(truncatedLocale) ?? Promise.resolve(enMessages);
};
export const IntlProvider = ({ children }: { children: ReactNode }) => {
@ -24,8 +40,7 @@ export const IntlProvider = ({ children }: { children: ReactNode }) => {
const [messages, setMessages] = useState<Record<string, string>>(enMessages);
useEffect(() => {
const msg = getMessages(locale);
setMessages(msg);
getMessages(locale).then(a => setMessages(a as Record<string, string>));
}, [locale]);
return (

View File

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

View File

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