Merge branch 'LemmyNet:main' into added-darkly-compact-552

This commit is contained in:
dankxiaobong 2023-06-22 12:29:01 +02:00 committed by GitHub
commit dfe394adca
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
175 changed files with 3040 additions and 2642 deletions

@ -1 +1 @@
Subproject commit ddf0d3a4dcfba5eddbcdb702db2470b52abb3815 Subproject commit a241fe1255a6363c7ae1ec5a09520c066745e6ce

View file

@ -1,6 +1,6 @@
{ {
"name": "lemmy-ui", "name": "lemmy-ui",
"version": "0.18.0-rc.4", "version": "0.18.0-rc.5",
"description": "An isomorphic UI for lemmy", "description": "An isomorphic UI for lemmy",
"repository": "https://github.com/LemmyNet/lemmy-ui", "repository": "https://github.com/LemmyNet/lemmy-ui",
"license": "AGPL-3.0", "license": "AGPL-3.0",
@ -66,7 +66,7 @@
"inferno-server": "^8.1.1", "inferno-server": "^8.1.1",
"isomorphic-cookie": "^1.2.4", "isomorphic-cookie": "^1.2.4",
"jwt-decode": "^3.1.2", "jwt-decode": "^3.1.2",
"lemmy-js-client": "0.18.0-rc.1", "lemmy-js-client": "0.18.0-rc.2",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"markdown-it": "^13.0.1", "markdown-it": "^13.0.1",
"markdown-it-container": "^3.0.0", "markdown-it-container": "^3.0.0",

View file

@ -1,8 +1,8 @@
import { initializeSite } from "@utils/app";
import { hydrate } from "inferno-hydrate"; import { hydrate } from "inferno-hydrate";
import { Router } from "inferno-router"; import { Router } from "inferno-router";
import { App } from "../shared/components/app/app"; import { App } from "../shared/components/app/app";
import { HistoryService } from "../shared/services/HistoryService"; import { HistoryService } from "../shared/services";
import { initializeSite } from "../shared/utils";
import "bootstrap/js/dist/collapse"; import "bootstrap/js/dist/collapse";
import "bootstrap/js/dist/dropdown"; import "bootstrap/js/dist/dropdown";

View file

@ -1,3 +1,5 @@
import { initializeSite, isAuthPath } from "@utils/app";
import { ErrorPageData } from "@utils/types";
import type { Request, Response } from "express"; import type { Request, Response } from "express";
import { StaticRouter, matchPath } from "inferno-router"; import { StaticRouter, matchPath } from "inferno-router";
import { renderToString } from "inferno-server"; import { renderToString } from "inferno-server";
@ -15,7 +17,6 @@ import {
FailedRequestState, FailedRequestState,
wrapClient, wrapClient,
} from "../../shared/services/HttpService"; } from "../../shared/services/HttpService";
import { ErrorPageData, initializeSite, isAuthPath } from "../../shared/utils";
import { createSsrHtml } from "../utils/create-ssr-html"; import { createSsrHtml } from "../utils/create-ssr-html";
import { getErrorPageData } from "../utils/get-error-page-data"; import { getErrorPageData } from "../utils/get-error-page-data";
import { setForwardedHeaders } from "../utils/set-forwarded-headers"; import { setForwardedHeaders } from "../utils/set-forwarded-headers";
@ -28,7 +29,9 @@ export default async (req: Request, res: Response) => {
const getSiteForm: GetSite = { auth }; const getSiteForm: GetSite = { auth };
const headers = setForwardedHeaders(req.headers); const headers = setForwardedHeaders(req.headers);
const client = wrapClient(new LemmyHttp(getHttpBaseInternal(), headers)); const client = wrapClient(
new LemmyHttp(getHttpBaseInternal(), { fetchFunction: fetch, headers })
);
const { path, url, query } = req; const { path, url, query } = req;

View file

@ -1,6 +1,6 @@
import type { Request, Response } from "express"; import type { Request, Response } from "express";
import { LemmyHttp } from "lemmy-js-client"; import { LemmyHttp } from "lemmy-js-client";
import { getHttpBaseInternal } from "../../shared/env"; import { getHttpBaseExternal, getHttpBaseInternal } from "../../shared/env";
import { wrapClient } from "../../shared/services/HttpService"; import { wrapClient } from "../../shared/services/HttpService";
import generateManifestJson from "../utils/generate-manifest-json"; import generateManifestJson from "../utils/generate-manifest-json";
import { setForwardedHeaders } from "../utils/set-forwarded-headers"; import { setForwardedHeaders } from "../utils/set-forwarded-headers";
@ -9,9 +9,11 @@ let manifest: Awaited<ReturnType<typeof generateManifestJson>> | undefined =
undefined; undefined;
export default async (req: Request, res: Response) => { export default async (req: Request, res: Response) => {
if (!manifest) { if (!manifest || manifest.start_url !== getHttpBaseExternal()) {
const headers = setForwardedHeaders(req.headers); const headers = setForwardedHeaders(req.headers);
const client = wrapClient(new LemmyHttp(getHttpBaseInternal(), headers)); const client = wrapClient(
new LemmyHttp(getHttpBaseInternal(), { fetchFunction: fetch, headers })
);
const site = await client.getSite({}); const site = await client.getSite({});
if (site.state === "success") { if (site.state === "success") {

View file

@ -25,7 +25,7 @@ if (!process.env["LEMMY_UI_DISABLE_CSP"] && !process.env["LEMMY_UI_DEBUG"]) {
server.get("/robots.txt", RobotsHandler); server.get("/robots.txt", RobotsHandler);
server.get("/service-worker.js", ServiceWorkerHandler); server.get("/service-worker.js", ServiceWorkerHandler);
server.get("/manifest", ManifestHandler); server.get("/manifest.webmanifest", ManifestHandler);
server.get("/css/themes/:name", ThemeHandler); server.get("/css/themes/:name", ThemeHandler);
server.get("/css/themelist", ThemesListHandler); server.get("/css/themelist", ThemesListHandler);
server.get("/*", CatchAllHandler); server.get("/*", CatchAllHandler);

View file

@ -2,8 +2,8 @@ import { Helmet } from "inferno-helmet";
import { renderToString } from "inferno-server"; import { renderToString } from "inferno-server";
import serialize from "serialize-javascript"; import serialize from "serialize-javascript";
import sharp from "sharp"; import sharp from "sharp";
import { favIconPngUrl, favIconUrl } from "../../shared/config";
import { ILemmyConfig, IsoDataOptionalSite } from "../../shared/interfaces"; import { ILemmyConfig, IsoDataOptionalSite } from "../../shared/interfaces";
import { favIconPngUrl, favIconUrl } from "../../shared/utils";
import { fetchIconPng } from "./fetch-icon-png"; import { fetchIconPng } from "./fetch-icon-png";
const customHtmlHeader = process.env["LEMMY_UI_CUSTOM_HTML_HEADER"] || ""; const customHtmlHeader = process.env["LEMMY_UI_CUSTOM_HTML_HEADER"] || "";
@ -77,7 +77,7 @@ export async function createSsrHtml(
/> />
<!-- Web app manifest --> <!-- Web app manifest -->
<link rel="manifest" href="/manifest" /> <link rel="manifest" href="/manifest.webmanifest" />
<link rel="apple-touch-icon" href=${appleTouchIcon} /> <link rel="apple-touch-icon" href=${appleTouchIcon} />
<link rel="apple-touch-startup-image" href=${appleTouchIcon} /> <link rel="apple-touch-startup-image" href=${appleTouchIcon} />

View file

@ -1,5 +1,5 @@
import { ErrorPageData } from "@utils/types";
import { GetSiteResponse } from "lemmy-js-client"; import { GetSiteResponse } from "lemmy-js-client";
import { ErrorPageData } from "../../shared/utils";
export function getErrorPageData(error: Error, site?: GetSiteResponse) { export function getErrorPageData(error: Error, site?: GetSiteResponse) {
const errorPageData: ErrorPageData = {}; const errorPageData: ErrorPageData = {};

View file

@ -1,10 +1,10 @@
import { Component, createRef, linkEvent, RefObject } from "inferno"; import { isAuthPath, setIsoData } from "@utils/app";
import { Component, RefObject, createRef, linkEvent } from "inferno";
import { Provider } from "inferno-i18next-dess"; import { Provider } from "inferno-i18next-dess";
import { Route, Switch } from "inferno-router"; import { Route, Switch } from "inferno-router";
import { i18n } from "../../i18next";
import { IsoDataOptionalSite } from "../../interfaces"; import { IsoDataOptionalSite } from "../../interfaces";
import { routes } from "../../routes"; import { routes } from "../../routes";
import { isAuthPath, setIsoData } from "../../utils"; import { I18NextService } from "../../services";
import AuthGuard from "../common/auth-guard"; import AuthGuard from "../common/auth-guard";
import ErrorGuard from "../common/error-guard"; import ErrorGuard from "../common/error-guard";
import { ErrorPage } from "./error-page"; import { ErrorPage } from "./error-page";
@ -31,13 +31,13 @@ export class App extends Component<any, any> {
return ( return (
<> <>
<Provider i18next={i18n}> <Provider i18next={I18NextService.i18n}>
<div id="app" className="lemmy-site"> <div id="app" className="lemmy-site">
<a <a
className="skip-link bg-light text-dark p-2 text-decoration-none position-absolute start-0 z-3" className="skip-link bg-light text-dark p-2 text-decoration-none position-absolute start-0 z-3"
onClick={linkEvent(this, this.handleJumpToContent)} onClick={linkEvent(this, this.handleJumpToContent)}
> >
${i18n.t("jump_to_content", "Jump to content")} ${I18NextService.i18n.t("jump_to_content", "Jump to content")}
</a> </a>
{siteView && ( {siteView && (
<Theme defaultTheme={siteView.local_site.default_theme} /> <Theme defaultTheme={siteView.local_site.default_theme} />

View file

@ -1,9 +1,9 @@
import { setIsoData } from "@utils/app";
import { Component } from "inferno"; import { Component } from "inferno";
import { T } from "inferno-i18next-dess"; import { T } from "inferno-i18next-dess";
import { Link } from "inferno-router"; import { Link } from "inferno-router";
import { i18n } from "../../i18next";
import { IsoDataOptionalSite } from "../../interfaces"; import { IsoDataOptionalSite } from "../../interfaces";
import { setIsoData } from "../../utils"; import { I18NextService } from "../../services";
export class ErrorPage extends Component<any, any> { export class ErrorPage extends Component<any, any> {
private isoData: IsoDataOptionalSite = setIsoData(this.context); private isoData: IsoDataOptionalSite = setIsoData(this.context);
@ -19,8 +19,8 @@ export class ErrorPage extends Component<any, any> {
<div className="error-page container-lg text-center"> <div className="error-page container-lg text-center">
<h1> <h1>
{errorPageData {errorPageData
? i18n.t("error_page_title") ? I18NextService.i18n.t("error_page_title")
: i18n.t("not_found_page_title")} : I18NextService.i18n.t("not_found_page_title")}
</h1> </h1>
{errorPageData ? ( {errorPageData ? (
<T i18nKey="error_page_paragraph" className="p-4" parent="p"> <T i18nKey="error_page_paragraph" className="p-4" parent="p">
@ -28,18 +28,18 @@ export class ErrorPage extends Component<any, any> {
<a href="https://matrix.to/#/#lemmy-space:matrix.org">#</a># <a href="https://matrix.to/#/#lemmy-space:matrix.org">#</a>#
</T> </T>
) : ( ) : (
<p>{i18n.t("not_found_page_message")}</p> <p>{I18NextService.i18n.t("not_found_page_message")}</p>
)} )}
{!errorPageData && ( {!errorPageData && (
<Link to="/" replace> <Link to="/" replace>
{i18n.t("not_found_return_home_button")} {I18NextService.i18n.t("not_found_return_home_button")}
</Link> </Link>
)} )}
{errorPageData?.adminMatrixIds && {errorPageData?.adminMatrixIds &&
errorPageData.adminMatrixIds.length > 0 && ( errorPageData.adminMatrixIds.length > 0 && (
<> <>
<div> <div>
{i18n.t("error_page_admin_matrix", { {I18NextService.i18n.t("error_page_admin_matrix", {
instance: instance:
this.isoData.site_res?.site_view.site.name ?? this.isoData.site_res?.site_view.site.name ??
"this instance", "this instance",

View file

@ -1,8 +1,8 @@
import { Component } from "inferno"; import { Component } from "inferno";
import { NavLink } from "inferno-router"; import { NavLink } from "inferno-router";
import { GetSiteResponse } from "lemmy-js-client"; import { GetSiteResponse } from "lemmy-js-client";
import { i18n } from "../../i18next"; import { docsUrl, joinLemmyUrl, repoUrl } from "../../config";
import { docsUrl, joinLemmyUrl, repoUrl } from "../../utils"; import { I18NextService } from "../../services";
import { VERSION } from "../../version"; import { VERSION } from "../../version";
interface FooterProps { interface FooterProps {
@ -29,36 +29,36 @@ export class Footer extends Component<FooterProps, any> {
</li> </li>
<li className="nav-item"> <li className="nav-item">
<NavLink className="nav-link" to="/modlog"> <NavLink className="nav-link" to="/modlog">
{i18n.t("modlog")} {I18NextService.i18n.t("modlog")}
</NavLink> </NavLink>
</li> </li>
{this.props.site?.site_view.local_site.legal_information && ( {this.props.site?.site_view.local_site.legal_information && (
<li className="nav-item"> <li className="nav-item">
<NavLink className="nav-link" to="/legal"> <NavLink className="nav-link" to="/legal">
{i18n.t("legal_information")} {I18NextService.i18n.t("legal_information")}
</NavLink> </NavLink>
</li> </li>
)} )}
{this.props.site?.site_view.local_site.federation_enabled && ( {this.props.site?.site_view.local_site.federation_enabled && (
<li className="nav-item"> <li className="nav-item">
<NavLink className="nav-link" to="/instances"> <NavLink className="nav-link" to="/instances">
{i18n.t("instances")} {I18NextService.i18n.t("instances")}
</NavLink> </NavLink>
</li> </li>
)} )}
<li className="nav-item"> <li className="nav-item">
<a className="nav-link" href={docsUrl}> <a className="nav-link" href={docsUrl}>
{i18n.t("docs")} {I18NextService.i18n.t("docs")}
</a> </a>
</li> </li>
<li className="nav-item"> <li className="nav-item">
<a className="nav-link" href={repoUrl}> <a className="nav-link" href={repoUrl}>
{i18n.t("code")} {I18NextService.i18n.t("code")}
</a> </a>
</li> </li>
<li className="nav-item"> <li className="nav-item">
<a className="nav-link" href={joinLemmyUrl}> <a className="nav-link" href={joinLemmyUrl}>
{i18n.t("join_lemmy")} {I18NextService.i18n.t("join_lemmy")}
</a> </a>
</li> </li>
</ul> </ul>

View file

@ -1,5 +1,6 @@
import { myAuth, showAvatars } from "@utils/app";
import { isBrowser } from "@utils/browser"; import { isBrowser } from "@utils/browser";
import { poll } from "@utils/helpers"; import { numToSI, poll } from "@utils/helpers";
import { amAdmin, canCreateCommunity } from "@utils/roles"; import { amAdmin, canCreateCommunity } from "@utils/roles";
import { Component, createRef, linkEvent } from "inferno"; import { Component, createRef, linkEvent } from "inferno";
import { NavLink } from "inferno-router"; import { NavLink } from "inferno-router";
@ -9,17 +10,10 @@ import {
GetUnreadCountResponse, GetUnreadCountResponse,
GetUnreadRegistrationApplicationCountResponse, GetUnreadRegistrationApplicationCountResponse,
} from "lemmy-js-client"; } from "lemmy-js-client";
import { i18n } from "../../i18next"; import { donateLemmyUrl, updateUnreadCountsInterval } from "../../config";
import { UserService } from "../../services"; import { I18NextService, UserService } from "../../services";
import { HttpService, RequestState } from "../../services/HttpService"; import { HttpService, RequestState } from "../../services/HttpService";
import { import { toast } from "../../toast";
donateLemmyUrl,
myAuth,
numToSI,
showAvatars,
toast,
updateUnreadCountsInterval,
} from "../../utils";
import { Icon } from "../common/icon"; import { Icon } from "../common/icon";
import { PictrsImage } from "../common/pictrs-image"; import { PictrsImage } from "../common/pictrs-image";
@ -107,7 +101,7 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
<NavLink <NavLink
to="/inbox" to="/inbox"
className="p-1 nav-link border-0 nav-messages" className="p-1 nav-link border-0 nav-messages"
title={i18n.t("unread_messages", { title={I18NextService.i18n.t("unread_messages", {
count: Number(this.state.unreadApplicationCountRes.state), count: Number(this.state.unreadApplicationCountRes.state),
formattedCount: numToSI(this.unreadInboxCount), formattedCount: numToSI(this.unreadInboxCount),
})} })}
@ -126,7 +120,7 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
<NavLink <NavLink
to="/reports" to="/reports"
className="p-1 nav-link border-0" className="p-1 nav-link border-0"
title={i18n.t("unread_reports", { title={I18NextService.i18n.t("unread_reports", {
count: Number(this.unreadReportCount), count: Number(this.unreadReportCount),
formattedCount: numToSI(this.unreadReportCount), formattedCount: numToSI(this.unreadReportCount),
})} })}
@ -146,10 +140,13 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
<NavLink <NavLink
to="/registration_applications" to="/registration_applications"
className="p-1 nav-link border-0" className="p-1 nav-link border-0"
title={i18n.t("unread_registration_applications", { title={I18NextService.i18n.t(
"unread_registration_applications",
{
count: Number(this.unreadApplicationCount), count: Number(this.unreadApplicationCount),
formattedCount: numToSI(this.unreadApplicationCount), formattedCount: numToSI(this.unreadApplicationCount),
})} }
)}
onMouseUp={linkEvent(this, handleCollapseClick)} onMouseUp={linkEvent(this, handleCollapseClick)}
> >
<Icon icon="clipboard" /> <Icon icon="clipboard" />
@ -167,7 +164,7 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
className="navbar-toggler border-0 p-1" className="navbar-toggler border-0 p-1"
type="button" type="button"
aria-label="menu" aria-label="menu"
data-tippy-content={i18n.t("expand_here")} data-tippy-content={I18NextService.i18n.t("expand_here")}
data-bs-toggle="collapse" data-bs-toggle="collapse"
data-bs-target="#navbarDropdown" data-bs-target="#navbarDropdown"
aria-controls="navbarDropdown" aria-controls="navbarDropdown"
@ -186,10 +183,10 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
<NavLink <NavLink
to="/communities" to="/communities"
className="nav-link" className="nav-link"
title={i18n.t("communities")} title={I18NextService.i18n.t("communities")}
onMouseUp={linkEvent(this, handleCollapseClick)} onMouseUp={linkEvent(this, handleCollapseClick)}
> >
{i18n.t("communities")} {I18NextService.i18n.t("communities")}
</NavLink> </NavLink>
</li> </li>
<li className="nav-item"> <li className="nav-item">
@ -203,10 +200,10 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
state: { prevPath: this.currentLocation }, state: { prevPath: this.currentLocation },
}} }}
className="nav-link" className="nav-link"
title={i18n.t("create_post")} title={I18NextService.i18n.t("create_post")}
onMouseUp={linkEvent(this, handleCollapseClick)} onMouseUp={linkEvent(this, handleCollapseClick)}
> >
{i18n.t("create_post")} {I18NextService.i18n.t("create_post")}
</NavLink> </NavLink>
</li> </li>
{this.props.siteRes && canCreateCommunity(this.props.siteRes) && ( {this.props.siteRes && canCreateCommunity(this.props.siteRes) && (
@ -214,22 +211,22 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
<NavLink <NavLink
to="/create_community" to="/create_community"
className="nav-link" className="nav-link"
title={i18n.t("create_community")} title={I18NextService.i18n.t("create_community")}
onMouseUp={linkEvent(this, handleCollapseClick)} onMouseUp={linkEvent(this, handleCollapseClick)}
> >
{i18n.t("create_community")} {I18NextService.i18n.t("create_community")}
</NavLink> </NavLink>
</li> </li>
)} )}
<li className="nav-item"> <li className="nav-item">
<a <a
className="nav-link d-inline-flex align-items-center d-md-inline-block" className="nav-link d-inline-flex align-items-center d-md-inline-block"
title={i18n.t("support_lemmy")} title={I18NextService.i18n.t("support_lemmy")}
href={donateLemmyUrl} href={donateLemmyUrl}
> >
<Icon icon="heart" classes="small" /> <Icon icon="heart" classes="small" />
<span className="d-inline ms-1 d-md-none ms-md-0"> <span className="d-inline ms-1 d-md-none ms-md-0">
{i18n.t("support_lemmy")} {I18NextService.i18n.t("support_lemmy")}
</span> </span>
</a> </a>
</li> </li>
@ -239,12 +236,12 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
<NavLink <NavLink
to="/search" to="/search"
className="nav-link d-inline-flex align-items-center d-md-inline-block" className="nav-link d-inline-flex align-items-center d-md-inline-block"
title={i18n.t("search")} title={I18NextService.i18n.t("search")}
onMouseUp={linkEvent(this, handleCollapseClick)} onMouseUp={linkEvent(this, handleCollapseClick)}
> >
<Icon icon="search" /> <Icon icon="search" />
<span className="d-inline ms-1 d-md-none ms-md-0"> <span className="d-inline ms-1 d-md-none ms-md-0">
{i18n.t("search")} {I18NextService.i18n.t("search")}
</span> </span>
</NavLink> </NavLink>
</li> </li>
@ -253,12 +250,12 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
<NavLink <NavLink
to="/admin" to="/admin"
className="nav-link d-inline-flex align-items-center d-md-inline-block" className="nav-link d-inline-flex align-items-center d-md-inline-block"
title={i18n.t("admin_settings")} title={I18NextService.i18n.t("admin_settings")}
onMouseUp={linkEvent(this, handleCollapseClick)} onMouseUp={linkEvent(this, handleCollapseClick)}
> >
<Icon icon="settings" /> <Icon icon="settings" />
<span className="d-inline ms-1 d-md-none ms-md-0"> <span className="d-inline ms-1 d-md-none ms-md-0">
{i18n.t("admin_settings")} {I18NextService.i18n.t("admin_settings")}
</span> </span>
</NavLink> </NavLink>
</li> </li>
@ -269,7 +266,7 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
<NavLink <NavLink
className="nav-link d-inline-flex align-items-center d-md-inline-block" className="nav-link d-inline-flex align-items-center d-md-inline-block"
to="/inbox" to="/inbox"
title={i18n.t("unread_messages", { title={I18NextService.i18n.t("unread_messages", {
count: Number(this.unreadInboxCount), count: Number(this.unreadInboxCount),
formattedCount: numToSI(this.unreadInboxCount), formattedCount: numToSI(this.unreadInboxCount),
})} })}
@ -277,7 +274,7 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
> >
<Icon icon="bell" /> <Icon icon="bell" />
<span className="badge text-bg-light d-inline ms-1 d-md-none ms-md-0"> <span className="badge text-bg-light d-inline ms-1 d-md-none ms-md-0">
{i18n.t("unread_messages", { {I18NextService.i18n.t("unread_messages", {
count: Number(this.unreadInboxCount), count: Number(this.unreadInboxCount),
formattedCount: numToSI(this.unreadInboxCount), formattedCount: numToSI(this.unreadInboxCount),
})} })}
@ -294,7 +291,7 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
<NavLink <NavLink
className="nav-link d-inline-flex align-items-center d-md-inline-block" className="nav-link d-inline-flex align-items-center d-md-inline-block"
to="/reports" to="/reports"
title={i18n.t("unread_reports", { title={I18NextService.i18n.t("unread_reports", {
count: Number(this.unreadReportCount), count: Number(this.unreadReportCount),
formattedCount: numToSI(this.unreadReportCount), formattedCount: numToSI(this.unreadReportCount),
})} })}
@ -302,7 +299,7 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
> >
<Icon icon="shield" /> <Icon icon="shield" />
<span className="badge text-bg-light d-inline ms-1 d-md-none ms-md-0"> <span className="badge text-bg-light d-inline ms-1 d-md-none ms-md-0">
{i18n.t("unread_reports", { {I18NextService.i18n.t("unread_reports", {
count: Number(this.unreadReportCount), count: Number(this.unreadReportCount),
formattedCount: numToSI(this.unreadReportCount), formattedCount: numToSI(this.unreadReportCount),
})} })}
@ -320,18 +317,26 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
<NavLink <NavLink
to="/registration_applications" to="/registration_applications"
className="nav-link d-inline-flex align-items-center d-md-inline-block" className="nav-link d-inline-flex align-items-center d-md-inline-block"
title={i18n.t("unread_registration_applications", { title={I18NextService.i18n.t(
"unread_registration_applications",
{
count: Number(this.unreadApplicationCount), count: Number(this.unreadApplicationCount),
formattedCount: numToSI(this.unreadApplicationCount), formattedCount: numToSI(this.unreadApplicationCount),
})} }
)}
onMouseUp={linkEvent(this, handleCollapseClick)} onMouseUp={linkEvent(this, handleCollapseClick)}
> >
<Icon icon="clipboard" /> <Icon icon="clipboard" />
<span className="badge text-bg-light d-inline ms-1 d-md-none ms-md-0"> <span className="badge text-bg-light d-inline ms-1 d-md-none ms-md-0">
{i18n.t("unread_registration_applications", { {I18NextService.i18n.t(
"unread_registration_applications",
{
count: Number(this.unreadApplicationCount), count: Number(this.unreadApplicationCount),
formattedCount: numToSI(this.unreadApplicationCount), formattedCount: numToSI(
})} this.unreadApplicationCount
),
}
)}
</span> </span>
{this.unreadApplicationCount > 0 && ( {this.unreadApplicationCount > 0 && (
<span className="mx-1 badge text-bg-light"> <span className="mx-1 badge text-bg-light">
@ -362,22 +367,22 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
<NavLink <NavLink
to={`/u/${person.name}`} to={`/u/${person.name}`}
className="dropdown-item px-2" className="dropdown-item px-2"
title={i18n.t("profile")} title={I18NextService.i18n.t("profile")}
onMouseUp={linkEvent(this, handleCollapseClick)} onMouseUp={linkEvent(this, handleCollapseClick)}
> >
<Icon icon="user" classes="me-1" /> <Icon icon="user" classes="me-1" />
{i18n.t("profile")} {I18NextService.i18n.t("profile")}
</NavLink> </NavLink>
</li> </li>
<li> <li>
<NavLink <NavLink
to="/settings" to="/settings"
className="dropdown-item px-2" className="dropdown-item px-2"
title={i18n.t("settings")} title={I18NextService.i18n.t("settings")}
onMouseUp={linkEvent(this, handleCollapseClick)} onMouseUp={linkEvent(this, handleCollapseClick)}
> >
<Icon icon="settings" classes="me-1" /> <Icon icon="settings" classes="me-1" />
{i18n.t("settings")} {I18NextService.i18n.t("settings")}
</NavLink> </NavLink>
</li> </li>
<li> <li>
@ -389,7 +394,7 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
onClick={linkEvent(this, handleLogOut)} onClick={linkEvent(this, handleLogOut)}
> >
<Icon icon="log-out" classes="me-1" /> <Icon icon="log-out" classes="me-1" />
{i18n.t("logout")} {I18NextService.i18n.t("logout")}
</button> </button>
</li> </li>
</ul> </ul>
@ -402,20 +407,20 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
<NavLink <NavLink
to="/login" to="/login"
className="nav-link" className="nav-link"
title={i18n.t("login")} title={I18NextService.i18n.t("login")}
onMouseUp={linkEvent(this, handleCollapseClick)} onMouseUp={linkEvent(this, handleCollapseClick)}
> >
{i18n.t("login")} {I18NextService.i18n.t("login")}
</NavLink> </NavLink>
</li> </li>
<li className="nav-item"> <li className="nav-item">
<NavLink <NavLink
to="/signup" to="/signup"
className="nav-link" className="nav-link"
title={i18n.t("sign_up")} title={I18NextService.i18n.t("sign_up")}
onMouseUp={linkEvent(this, handleCollapseClick)} onMouseUp={linkEvent(this, handleCollapseClick)}
> >
{i18n.t("sign_up")} {I18NextService.i18n.t("sign_up")}
</NavLink> </NavLink>
</li> </li>
</> </>
@ -509,7 +514,7 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
if (UserService.Instance.myUserInfo) { if (UserService.Instance.myUserInfo) {
document.addEventListener("DOMContentLoaded", function () { document.addEventListener("DOMContentLoaded", function () {
if (!Notification) { if (!Notification) {
toast(i18n.t("notifications_error"), "danger"); toast(I18NextService.i18n.t("notifications_error"), "danger");
return; return;
} }

View file

@ -1,11 +1,11 @@
import { myAuthRequired } from "@utils/app";
import { capitalizeFirstLetter } from "@utils/helpers";
import { Component } from "inferno"; import { Component } from "inferno";
import { T } from "inferno-i18next-dess"; import { T } from "inferno-i18next-dess";
import { Link } from "inferno-router"; import { Link } from "inferno-router";
import { CreateComment, EditComment, Language } from "lemmy-js-client"; import { CreateComment, EditComment, Language } from "lemmy-js-client";
import { i18n } from "../../i18next";
import { CommentNodeI } from "../../interfaces"; import { CommentNodeI } from "../../interfaces";
import { UserService } from "../../services"; import { I18NextService, UserService } from "../../services";
import { capitalizeFirstLetter, myAuthRequired } from "../../utils";
import { Icon } from "../common/icon"; import { Icon } from "../common/icon";
import { MarkdownTextArea } from "../common/markdown-textarea"; import { MarkdownTextArea } from "../common/markdown-textarea";
@ -57,7 +57,7 @@ export class CommentForm extends Component<CommentFormProps, any> {
disabled={this.props.disabled} disabled={this.props.disabled}
onSubmit={this.handleCommentSubmit} onSubmit={this.handleCommentSubmit}
onReplyCancel={this.props.onReplyCancel} onReplyCancel={this.props.onReplyCancel}
placeholder={i18n.t("comment_here")} placeholder={I18NextService.i18n.t("comment_here") ?? undefined}
allLanguages={this.props.allLanguages} allLanguages={this.props.allLanguages}
siteLanguages={this.props.siteLanguages} siteLanguages={this.props.siteLanguages}
/> />
@ -78,10 +78,10 @@ export class CommentForm extends Component<CommentFormProps, any> {
get buttonTitle(): string { get buttonTitle(): string {
return typeof this.props.node === "number" return typeof this.props.node === "number"
? capitalizeFirstLetter(i18n.t("post")) ? capitalizeFirstLetter(I18NextService.i18n.t("post"))
: this.props.edit : this.props.edit
? capitalizeFirstLetter(i18n.t("save")) ? capitalizeFirstLetter(I18NextService.i18n.t("save"))
: capitalizeFirstLetter(i18n.t("reply")); : capitalizeFirstLetter(I18NextService.i18n.t("reply"));
} }
handleCommentSubmit(content: string, form_id: string, language_id?: number) { handleCommentSubmit(content: string, form_id: string, language_id?: number) {

View file

@ -1,3 +1,12 @@
import {
colorList,
getCommentParentId,
myAuth,
myAuthRequired,
newVote,
showScores,
} from "@utils/app";
import { futureDaysToUnixTime, numToSI } from "@utils/helpers";
import { import {
amCommunityCreator, amCommunityCreator,
canAdmin, canAdmin,
@ -38,7 +47,7 @@ import {
TransferCommunity, TransferCommunity,
} from "lemmy-js-client"; } from "lemmy-js-client";
import moment from "moment"; import moment from "moment";
import { i18n } from "../../i18next"; import { commentTreeMaxDepth } from "../../config";
import { import {
BanType, BanType,
CommentNodeI, CommentNodeI,
@ -46,21 +55,9 @@ import {
PurgeType, PurgeType,
VoteType, VoteType,
} from "../../interfaces"; } from "../../interfaces";
import { UserService } from "../../services"; import { mdToHtml, mdToHtmlNoImages } from "../../markdown";
import { import { I18NextService, UserService } from "../../services";
colorList, import { setupTippy } from "../../tippy";
commentTreeMaxDepth,
futureDaysToUnixTime,
getCommentParentId,
mdToHtml,
mdToHtmlNoImages,
myAuth,
myAuthRequired,
newVote,
numToSI,
setupTippy,
showScores,
} from "../../utils";
import { Icon, PurgeWarning, Spinner } from "../common/icon"; import { Icon, PurgeWarning, Spinner } from "../common/icon";
import { MomentTime } from "../common/moment-time"; import { MomentTime } from "../common/moment-time";
import { CommunityLink } from "../community/community-link"; import { CommunityLink } from "../community/community-link";
@ -243,8 +240,8 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
const purgeTypeText = const purgeTypeText =
this.state.purgeType == PurgeType.Comment this.state.purgeType == PurgeType.Comment
? i18n.t("purge_comment") ? I18NextService.i18n.t("purge_comment")
: `${i18n.t("purge")} ${cv.creator.name}`; : `${I18NextService.i18n.t("purge")} ${cv.creator.name}`;
const canMod_ = canMod( const canMod_ = canMod(
cv.creator.id, cv.creator.id,
@ -316,27 +313,27 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
)} )}
{this.isPostCreator && ( {this.isPostCreator && (
<div className="badge text-bg-light d-none d-sm-inline me-2"> <div className="badge text-bg-light d-none d-sm-inline me-2">
{i18n.t("creator")} {I18NextService.i18n.t("creator")}
</div> </div>
)} )}
{isMod_ && ( {isMod_ && (
<div className="badge text-bg-light d-none d-sm-inline me-2"> <div className="badge text-bg-light d-none d-sm-inline me-2">
{i18n.t("mod")} {I18NextService.i18n.t("mod")}
</div> </div>
)} )}
{isAdmin_ && ( {isAdmin_ && (
<div className="badge text-bg-light d-none d-sm-inline me-2"> <div className="badge text-bg-light d-none d-sm-inline me-2">
{i18n.t("admin")} {I18NextService.i18n.t("admin")}
</div> </div>
)} )}
{cv.creator.bot_account && ( {cv.creator.bot_account && (
<div className="badge text-bg-light d-none d-sm-inline me-2"> <div className="badge text-bg-light d-none d-sm-inline me-2">
{i18n.t("bot_account").toLowerCase()} {I18NextService.i18n.t("bot_account").toLowerCase()}
</div> </div>
)} )}
{this.props.showCommunity && ( {this.props.showCommunity && (
<> <>
<span className="mx-1">{i18n.t("to")}</span> <span className="mx-1">{I18NextService.i18n.t("to")}</span>
<CommunityLink community={cv.community} /> <CommunityLink community={cv.community} />
<span className="mx-2"></span> <span className="mx-2"></span>
<Link className="me-2" to={`/post/${cv.post.id}`}> <Link className="me-2" to={`/post/${cv.post.id}`}>
@ -368,7 +365,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
) : ( ) : (
<span <span
className="me-1 font-weight-bold" className="me-1 font-weight-bold"
aria-label={i18n.t("number_of_points", { aria-label={I18NextService.i18n.t("number_of_points", {
count: Number(this.commentView.counts.score), count: Number(this.commentView.counts.score),
formattedCount: numToSI( formattedCount: numToSI(
this.commentView.counts.score this.commentView.counts.score
@ -428,13 +425,13 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
onClick={linkEvent(this, this.handleMarkAsRead)} onClick={linkEvent(this, this.handleMarkAsRead)}
data-tippy-content={ data-tippy-content={
this.commentReplyOrMentionRead this.commentReplyOrMentionRead
? i18n.t("mark_as_unread") ? I18NextService.i18n.t("mark_as_unread")
: i18n.t("mark_as_read") : I18NextService.i18n.t("mark_as_read")
} }
aria-label={ aria-label={
this.commentReplyOrMentionRead this.commentReplyOrMentionRead
? i18n.t("mark_as_unread") ? I18NextService.i18n.t("mark_as_unread")
: i18n.t("mark_as_read") : I18NextService.i18n.t("mark_as_read")
} }
> >
{this.state.readLoading ? ( {this.state.readLoading ? (
@ -458,8 +455,8 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
: "text-muted" : "text-muted"
}`} }`}
onClick={linkEvent(this, this.handleUpvote)} onClick={linkEvent(this, this.handleUpvote)}
data-tippy-content={i18n.t("upvote")} data-tippy-content={I18NextService.i18n.t("upvote")}
aria-label={i18n.t("upvote")} aria-label={I18NextService.i18n.t("upvote")}
aria-pressed={this.commentView.my_vote === 1} aria-pressed={this.commentView.my_vote === 1}
> >
{this.state.upvoteLoading ? ( {this.state.upvoteLoading ? (
@ -485,8 +482,8 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
: "text-muted" : "text-muted"
}`} }`}
onClick={linkEvent(this, this.handleDownvote)} onClick={linkEvent(this, this.handleDownvote)}
data-tippy-content={i18n.t("downvote")} data-tippy-content={I18NextService.i18n.t("downvote")}
aria-label={i18n.t("downvote")} aria-label={I18NextService.i18n.t("downvote")}
aria-pressed={this.commentView.my_vote === -1} aria-pressed={this.commentView.my_vote === -1}
> >
{this.state.downvoteLoading ? ( {this.state.downvoteLoading ? (
@ -508,8 +505,8 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
<button <button
className="btn btn-link btn-animate text-muted" className="btn btn-link btn-animate text-muted"
onClick={linkEvent(this, this.handleReplyClick)} onClick={linkEvent(this, this.handleReplyClick)}
data-tippy-content={i18n.t("reply")} data-tippy-content={I18NextService.i18n.t("reply")}
aria-label={i18n.t("reply")} aria-label={I18NextService.i18n.t("reply")}
> >
<Icon icon="reply1" classes="icon-inline" /> <Icon icon="reply1" classes="icon-inline" />
</button> </button>
@ -517,8 +514,8 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
<button <button
className="btn btn-link btn-animate text-muted btn-more" className="btn btn-link btn-animate text-muted btn-more"
onClick={linkEvent(this, this.handleShowAdvanced)} onClick={linkEvent(this, this.handleShowAdvanced)}
data-tippy-content={i18n.t("more")} data-tippy-content={I18NextService.i18n.t("more")}
aria-label={i18n.t("more")} aria-label={I18NextService.i18n.t("more")}
> >
<Icon icon="more-vertical" classes="icon-inline" /> <Icon icon="more-vertical" classes="icon-inline" />
</button> </button>
@ -529,7 +526,9 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
<Link <Link
className="btn btn-link btn-animate text-muted" className="btn btn-link btn-animate text-muted"
to={`/create_private_message/${cv.creator.id}`} to={`/create_private_message/${cv.creator.id}`}
title={i18n.t("message").toLowerCase()} title={I18NextService.i18n
.t("message")
.toLowerCase()}
> >
<Icon icon="mail" /> <Icon icon="mail" />
</Link> </Link>
@ -539,10 +538,12 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
this, this,
this.handleShowReportDialog this.handleShowReportDialog
)} )}
data-tippy-content={i18n.t( data-tippy-content={I18NextService.i18n.t(
"show_report_dialog"
)}
aria-label={I18NextService.i18n.t(
"show_report_dialog" "show_report_dialog"
)} )}
aria-label={i18n.t("show_report_dialog")}
> >
<Icon icon="flag" /> <Icon icon="flag" />
</button> </button>
@ -552,8 +553,10 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
this, this,
this.handleBlockPerson this.handleBlockPerson
)} )}
data-tippy-content={i18n.t("block_user")} data-tippy-content={I18NextService.i18n.t(
aria-label={i18n.t("block_user")} "block_user"
)}
aria-label={I18NextService.i18n.t("block_user")}
> >
{this.state.blockPersonLoading ? ( {this.state.blockPersonLoading ? (
<Spinner /> <Spinner />
@ -567,10 +570,14 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
className="btn btn-link btn-animate text-muted" className="btn btn-link btn-animate text-muted"
onClick={linkEvent(this, this.handleSaveComment)} onClick={linkEvent(this, this.handleSaveComment)}
data-tippy-content={ data-tippy-content={
cv.saved ? i18n.t("unsave") : i18n.t("save") cv.saved
? I18NextService.i18n.t("unsave")
: I18NextService.i18n.t("save")
} }
aria-label={ aria-label={
cv.saved ? i18n.t("unsave") : i18n.t("save") cv.saved
? I18NextService.i18n.t("unsave")
: I18NextService.i18n.t("save")
} }
> >
{this.state.saveLoading ? ( {this.state.saveLoading ? (
@ -587,8 +594,10 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
<button <button
className="btn btn-link btn-animate text-muted" className="btn btn-link btn-animate text-muted"
onClick={linkEvent(this, this.handleViewSource)} onClick={linkEvent(this, this.handleViewSource)}
data-tippy-content={i18n.t("view_source")} data-tippy-content={I18NextService.i18n.t(
aria-label={i18n.t("view_source")} "view_source"
)}
aria-label={I18NextService.i18n.t("view_source")}
> >
<Icon <Icon
icon="file-text" icon="file-text"
@ -602,8 +611,10 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
<button <button
className="btn btn-link btn-animate text-muted" className="btn btn-link btn-animate text-muted"
onClick={linkEvent(this, this.handleEditClick)} onClick={linkEvent(this, this.handleEditClick)}
data-tippy-content={i18n.t("edit")} data-tippy-content={I18NextService.i18n.t(
aria-label={i18n.t("edit")} "edit"
)}
aria-label={I18NextService.i18n.t("edit")}
> >
<Icon icon="edit" classes="icon-inline" /> <Icon icon="edit" classes="icon-inline" />
</button> </button>
@ -615,13 +626,13 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
)} )}
data-tippy-content={ data-tippy-content={
!cv.comment.deleted !cv.comment.deleted
? i18n.t("delete") ? I18NextService.i18n.t("delete")
: i18n.t("restore") : I18NextService.i18n.t("restore")
} }
aria-label={ aria-label={
!cv.comment.deleted !cv.comment.deleted
? i18n.t("delete") ? I18NextService.i18n.t("delete")
: i18n.t("restore") : I18NextService.i18n.t("restore")
} }
> >
{this.state.deleteLoading ? ( {this.state.deleteLoading ? (
@ -645,13 +656,13 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
)} )}
data-tippy-content={ data-tippy-content={
!cv.comment.distinguished !cv.comment.distinguished
? i18n.t("distinguish") ? I18NextService.i18n.t("distinguish")
: i18n.t("undistinguish") : I18NextService.i18n.t("undistinguish")
} }
aria-label={ aria-label={
!cv.comment.distinguished !cv.comment.distinguished
? i18n.t("distinguish") ? I18NextService.i18n.t("distinguish")
: i18n.t("undistinguish") : I18NextService.i18n.t("undistinguish")
} }
> >
<Icon <Icon
@ -674,9 +685,9 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
this, this,
this.handleModRemoveShow this.handleModRemoveShow
)} )}
aria-label={i18n.t("remove")} aria-label={I18NextService.i18n.t("remove")}
> >
{i18n.t("remove")} {I18NextService.i18n.t("remove")}
</button> </button>
) : ( ) : (
<button <button
@ -685,12 +696,12 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
this, this,
this.handleRemoveComment this.handleRemoveComment
)} )}
aria-label={i18n.t("restore")} aria-label={I18NextService.i18n.t("restore")}
> >
{this.state.removeLoading ? ( {this.state.removeLoading ? (
<Spinner /> <Spinner />
) : ( ) : (
i18n.t("restore") I18NextService.i18n.t("restore")
)} )}
</button> </button>
)} )}
@ -707,9 +718,13 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
this, this,
this.handleModBanFromCommunityShow this.handleModBanFromCommunityShow
)} )}
aria-label={i18n.t("ban_from_community")} aria-label={I18NextService.i18n.t(
"ban_from_community"
)}
> >
{i18n.t("ban_from_community")} {I18NextService.i18n.t(
"ban_from_community"
)}
</button> </button>
) : ( ) : (
<button <button
@ -718,12 +733,12 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
this, this,
this.handleBanPersonFromCommunity this.handleBanPersonFromCommunity
)} )}
aria-label={i18n.t("unban")} aria-label={I18NextService.i18n.t("unban")}
> >
{this.state.banLoading ? ( {this.state.banLoading ? (
<Spinner /> <Spinner />
) : ( ) : (
i18n.t("unban") I18NextService.i18n.t("unban")
)} )}
</button> </button>
))} ))}
@ -737,21 +752,25 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
)} )}
aria-label={ aria-label={
isMod_ isMod_
? i18n.t("remove_as_mod") ? I18NextService.i18n.t("remove_as_mod")
: i18n.t("appoint_as_mod") : I18NextService.i18n.t(
"appoint_as_mod"
)
} }
> >
{isMod_ {isMod_
? i18n.t("remove_as_mod") ? I18NextService.i18n.t("remove_as_mod")
: i18n.t("appoint_as_mod")} : I18NextService.i18n.t("appoint_as_mod")}
</button> </button>
) : ( ) : (
<> <>
<button <button
className="btn btn-link btn-animate text-muted" className="btn btn-link btn-animate text-muted"
aria-label={i18n.t("are_you_sure")} aria-label={I18NextService.i18n.t(
"are_you_sure"
)}
> >
{i18n.t("are_you_sure")} {I18NextService.i18n.t("are_you_sure")}
</button> </button>
<button <button
className="btn btn-link btn-animate text-muted" className="btn btn-link btn-animate text-muted"
@ -759,12 +778,12 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
this, this,
this.handleAddModToCommunity this.handleAddModToCommunity
)} )}
aria-label={i18n.t("yes")} aria-label={I18NextService.i18n.t("yes")}
> >
{this.state.addModLoading ? ( {this.state.addModLoading ? (
<Spinner /> <Spinner />
) : ( ) : (
i18n.t("yes") I18NextService.i18n.t("yes")
)} )}
</button> </button>
<button <button
@ -773,9 +792,9 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
this, this,
this.handleCancelConfirmAppointAsMod this.handleCancelConfirmAppointAsMod
)} )}
aria-label={i18n.t("no")} aria-label={I18NextService.i18n.t("no")}
> >
{i18n.t("no")} {I18NextService.i18n.t("no")}
</button> </button>
</> </>
))} ))}
@ -792,17 +811,21 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
this, this,
this.handleShowConfirmTransferCommunity this.handleShowConfirmTransferCommunity
)} )}
aria-label={i18n.t("transfer_community")} aria-label={I18NextService.i18n.t(
"transfer_community"
)}
> >
{i18n.t("transfer_community")} {I18NextService.i18n.t("transfer_community")}
</button> </button>
) : ( ) : (
<> <>
<button <button
className="btn btn-link btn-animate text-muted" className="btn btn-link btn-animate text-muted"
aria-label={i18n.t("are_you_sure")} aria-label={I18NextService.i18n.t(
"are_you_sure"
)}
> >
{i18n.t("are_you_sure")} {I18NextService.i18n.t("are_you_sure")}
</button> </button>
<button <button
className="btn btn-link btn-animate text-muted" className="btn btn-link btn-animate text-muted"
@ -810,12 +833,12 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
this, this,
this.handleTransferCommunity this.handleTransferCommunity
)} )}
aria-label={i18n.t("yes")} aria-label={I18NextService.i18n.t("yes")}
> >
{this.state.transferCommunityLoading ? ( {this.state.transferCommunityLoading ? (
<Spinner /> <Spinner />
) : ( ) : (
i18n.t("yes") I18NextService.i18n.t("yes")
)} )}
</button> </button>
<button <button
@ -825,9 +848,9 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
this this
.handleCancelShowConfirmTransferCommunity .handleCancelShowConfirmTransferCommunity
)} )}
aria-label={i18n.t("no")} aria-label={I18NextService.i18n.t("no")}
> >
{i18n.t("no")} {I18NextService.i18n.t("no")}
</button> </button>
</> </>
))} ))}
@ -842,9 +865,11 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
this, this,
this.handlePurgePersonShow this.handlePurgePersonShow
)} )}
aria-label={i18n.t("purge_user")} aria-label={I18NextService.i18n.t(
"purge_user"
)}
> >
{i18n.t("purge_user")} {I18NextService.i18n.t("purge_user")}
</button> </button>
<button <button
className="btn btn-link btn-animate text-muted" className="btn btn-link btn-animate text-muted"
@ -852,9 +877,11 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
this, this,
this.handlePurgeCommentShow this.handlePurgeCommentShow
)} )}
aria-label={i18n.t("purge_comment")} aria-label={I18NextService.i18n.t(
"purge_comment"
)}
> >
{i18n.t("purge_comment")} {I18NextService.i18n.t("purge_comment")}
</button> </button>
{!isBanned(cv.creator) ? ( {!isBanned(cv.creator) ? (
@ -864,9 +891,11 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
this, this,
this.handleModBanShow this.handleModBanShow
)} )}
aria-label={i18n.t("ban_from_site")} aria-label={I18NextService.i18n.t(
"ban_from_site"
)}
> >
{i18n.t("ban_from_site")} {I18NextService.i18n.t("ban_from_site")}
</button> </button>
) : ( ) : (
<button <button
@ -875,12 +904,14 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
this, this,
this.handleBanPerson this.handleBanPerson
)} )}
aria-label={i18n.t("unban_from_site")} aria-label={I18NextService.i18n.t(
"unban_from_site"
)}
> >
{this.state.banLoading ? ( {this.state.banLoading ? (
<Spinner /> <Spinner />
) : ( ) : (
i18n.t("unban_from_site") I18NextService.i18n.t("unban_from_site")
)} )}
</button> </button>
)} )}
@ -897,18 +928,24 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
)} )}
aria-label={ aria-label={
isAdmin_ isAdmin_
? i18n.t("remove_as_admin") ? I18NextService.i18n.t(
: i18n.t("appoint_as_admin") "remove_as_admin"
)
: I18NextService.i18n.t(
"appoint_as_admin"
)
} }
> >
{isAdmin_ {isAdmin_
? i18n.t("remove_as_admin") ? I18NextService.i18n.t("remove_as_admin")
: i18n.t("appoint_as_admin")} : I18NextService.i18n.t(
"appoint_as_admin"
)}
</button> </button>
) : ( ) : (
<> <>
<button className="btn btn-link btn-animate text-muted"> <button className="btn btn-link btn-animate text-muted">
{i18n.t("are_you_sure")} {I18NextService.i18n.t("are_you_sure")}
</button> </button>
<button <button
className="btn btn-link btn-animate text-muted" className="btn btn-link btn-animate text-muted"
@ -916,12 +953,12 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
this, this,
this.handleAddAdmin this.handleAddAdmin
)} )}
aria-label={i18n.t("yes")} aria-label={I18NextService.i18n.t("yes")}
> >
{this.state.addAdminLoading ? ( {this.state.addAdminLoading ? (
<Spinner /> <Spinner />
) : ( ) : (
i18n.t("yes") I18NextService.i18n.t("yes")
)} )}
</button> </button>
<button <button
@ -930,9 +967,9 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
this, this,
this.handleCancelConfirmAppointAsAdmin this.handleCancelConfirmAppointAsAdmin
)} )}
aria-label={i18n.t("no")} aria-label={I18NextService.i18n.t("no")}
> >
{i18n.t("no")} {I18NextService.i18n.t("no")}
</button> </button>
</> </>
))} ))}
@ -963,7 +1000,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
<Spinner /> <Spinner />
) : ( ) : (
<> <>
{i18n.t("x_more_replies", { {I18NextService.i18n.t("x_more_replies", {
count: node.comment_view.counts.child_count, count: node.comment_view.counts.child_count,
formattedCount: numToSI( formattedCount: numToSI(
node.comment_view.counts.child_count node.comment_view.counts.child_count
@ -985,22 +1022,22 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
className="visually-hidden" className="visually-hidden"
htmlFor={`mod-remove-reason-${cv.comment.id}`} htmlFor={`mod-remove-reason-${cv.comment.id}`}
> >
{i18n.t("reason")} {I18NextService.i18n.t("reason")}
</label> </label>
<input <input
type="text" type="text"
id={`mod-remove-reason-${cv.comment.id}`} id={`mod-remove-reason-${cv.comment.id}`}
className="form-control me-2" className="form-control me-2"
placeholder={i18n.t("reason")} placeholder={I18NextService.i18n.t("reason")}
value={this.state.removeReason} value={this.state.removeReason}
onInput={linkEvent(this, this.handleModRemoveReasonChange)} onInput={linkEvent(this, this.handleModRemoveReasonChange)}
/> />
<button <button
type="submit" type="submit"
className="btn btn-secondary" className="btn btn-secondary"
aria-label={i18n.t("remove_comment")} aria-label={I18NextService.i18n.t("remove_comment")}
> >
{i18n.t("remove_comment")} {I18NextService.i18n.t("remove_comment")}
</button> </button>
</form> </form>
)} )}
@ -1013,23 +1050,23 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
className="visually-hidden" className="visually-hidden"
htmlFor={`report-reason-${cv.comment.id}`} htmlFor={`report-reason-${cv.comment.id}`}
> >
{i18n.t("reason")} {I18NextService.i18n.t("reason")}
</label> </label>
<input <input
type="text" type="text"
required required
id={`report-reason-${cv.comment.id}`} id={`report-reason-${cv.comment.id}`}
className="form-control me-2" className="form-control me-2"
placeholder={i18n.t("reason")} placeholder={I18NextService.i18n.t("reason")}
value={this.state.reportReason} value={this.state.reportReason}
onInput={linkEvent(this, this.handleReportReasonChange)} onInput={linkEvent(this, this.handleReportReasonChange)}
/> />
<button <button
type="submit" type="submit"
className="btn btn-secondary" className="btn btn-secondary"
aria-label={i18n.t("create_report")} aria-label={I18NextService.i18n.t("create_report")}
> >
{i18n.t("create_report")} {I18NextService.i18n.t("create_report")}
</button> </button>
</form> </form>
)} )}
@ -1040,13 +1077,13 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
className="col-form-label" className="col-form-label"
htmlFor={`mod-ban-reason-${cv.comment.id}`} htmlFor={`mod-ban-reason-${cv.comment.id}`}
> >
{i18n.t("reason")} {I18NextService.i18n.t("reason")}
</label> </label>
<input <input
type="text" type="text"
id={`mod-ban-reason-${cv.comment.id}`} id={`mod-ban-reason-${cv.comment.id}`}
className="form-control me-2" className="form-control me-2"
placeholder={i18n.t("reason")} placeholder={I18NextService.i18n.t("reason")}
value={this.state.banReason} value={this.state.banReason}
onInput={linkEvent(this, this.handleModBanReasonChange)} onInput={linkEvent(this, this.handleModBanReasonChange)}
/> />
@ -1054,13 +1091,13 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
className="col-form-label" className="col-form-label"
htmlFor={`mod-ban-expires-${cv.comment.id}`} htmlFor={`mod-ban-expires-${cv.comment.id}`}
> >
{i18n.t("expires")} {I18NextService.i18n.t("expires")}
</label> </label>
<input <input
type="number" type="number"
id={`mod-ban-expires-${cv.comment.id}`} id={`mod-ban-expires-${cv.comment.id}`}
className="form-control me-2" className="form-control me-2"
placeholder={i18n.t("number_of_days")} placeholder={I18NextService.i18n.t("number_of_days")}
value={this.state.banExpireDays} value={this.state.banExpireDays}
onInput={linkEvent(this, this.handleModBanExpireDaysChange)} onInput={linkEvent(this, this.handleModBanExpireDaysChange)}
/> />
@ -1076,9 +1113,9 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
<label <label
className="form-check-label" className="form-check-label"
htmlFor="mod-ban-remove-data" htmlFor="mod-ban-remove-data"
title={i18n.t("remove_content_more")} title={I18NextService.i18n.t("remove_content_more")}
> >
{i18n.t("remove_content")} {I18NextService.i18n.t("remove_content")}
</label> </label>
</div> </div>
</div> </div>
@ -1086,19 +1123,19 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
{/* TODO hold off on expires until later */} {/* TODO hold off on expires until later */}
{/* <div class="mb-3 row"> */} {/* <div class="mb-3 row"> */}
{/* <label class="col-form-label">Expires</label> */} {/* <label class="col-form-label">Expires</label> */}
{/* <input type="date" class="form-control me-2" placeholder={i18n.t('expires')} value={this.state.banExpires} onInput={linkEvent(this, this.handleModBanExpiresChange)} /> */} {/* <input type="date" class="form-control me-2" placeholder={I18NextService.i18n.t('expires')} value={this.state.banExpires} onInput={linkEvent(this, this.handleModBanExpiresChange)} /> */}
{/* </div> */} {/* </div> */}
<div className="mb-3 row"> <div className="mb-3 row">
<button <button
type="submit" type="submit"
className="btn btn-secondary" className="btn btn-secondary"
aria-label={i18n.t("ban")} aria-label={I18NextService.i18n.t("ban")}
> >
{this.state.banLoading ? ( {this.state.banLoading ? (
<Spinner /> <Spinner />
) : ( ) : (
<span> <span>
{i18n.t("ban")} {cv.creator.name} {I18NextService.i18n.t("ban")} {cv.creator.name}
</span> </span>
)} )}
</button> </button>
@ -1110,13 +1147,13 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
<form onSubmit={linkEvent(this, this.handlePurgeBothSubmit)}> <form onSubmit={linkEvent(this, this.handlePurgeBothSubmit)}>
<PurgeWarning /> <PurgeWarning />
<label className="visually-hidden" htmlFor="purge-reason"> <label className="visually-hidden" htmlFor="purge-reason">
{i18n.t("reason")} {I18NextService.i18n.t("reason")}
</label> </label>
<input <input
type="text" type="text"
id="purge-reason" id="purge-reason"
className="form-control my-3" className="form-control my-3"
placeholder={i18n.t("reason")} placeholder={I18NextService.i18n.t("reason")}
value={this.state.purgeReason} value={this.state.purgeReason}
onInput={linkEvent(this, this.handlePurgeReasonChange)} onInput={linkEvent(this, this.handlePurgeReasonChange)}
/> />
@ -1211,8 +1248,8 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
}); });
const title = this.props.showContext const title = this.props.showContext
? i18n.t("show_context") ? I18NextService.i18n.t("show_context")
: i18n.t("link"); : I18NextService.i18n.t("link");
// The context button should show the parent comment by default // The context button should show the parent comment by default
const parentCommentId = getCommentParentId(cv.comment) ?? cv.comment.id; const parentCommentId = getCommentParentId(cv.comment) ?? cv.comment.id;
@ -1257,17 +1294,17 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
} }
get pointsTippy(): string { get pointsTippy(): string {
const points = i18n.t("number_of_points", { const points = I18NextService.i18n.t("number_of_points", {
count: Number(this.commentView.counts.score), count: Number(this.commentView.counts.score),
formattedCount: numToSI(this.commentView.counts.score), formattedCount: numToSI(this.commentView.counts.score),
}); });
const upvotes = i18n.t("number_of_upvotes", { const upvotes = I18NextService.i18n.t("number_of_upvotes", {
count: Number(this.commentView.counts.upvotes), count: Number(this.commentView.counts.upvotes),
formattedCount: numToSI(this.commentView.counts.upvotes), formattedCount: numToSI(this.commentView.counts.upvotes),
}); });
const downvotes = i18n.t("number_of_downvotes", { const downvotes = I18NextService.i18n.t("number_of_downvotes", {
count: Number(this.commentView.counts.downvotes), count: Number(this.commentView.counts.downvotes),
formattedCount: numToSI(this.commentView.counts.downvotes), formattedCount: numToSI(this.commentView.counts.downvotes),
}); });
@ -1276,15 +1313,17 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
} }
get expandText(): string { get expandText(): string {
return this.state.collapsed ? i18n.t("expand") : i18n.t("collapse"); return this.state.collapsed
? I18NextService.i18n.t("expand")
: I18NextService.i18n.t("collapse");
} }
get commentUnlessRemoved(): string { get commentUnlessRemoved(): string {
const comment = this.commentView.comment; const comment = this.commentView.comment;
return comment.removed return comment.removed
? `*${i18n.t("removed")}*` ? `*${I18NextService.i18n.t("removed")}*`
: comment.deleted : comment.deleted
? `*${i18n.t("deleted")}*` ? `*${I18NextService.i18n.t("deleted")}*`
: comment.content; : comment.content;
} }

View file

@ -1,3 +1,4 @@
import { colorList } from "@utils/app";
import classNames from "classnames"; import classNames from "classnames";
import { Component } from "inferno"; import { Component } from "inferno";
import { import {
@ -26,7 +27,6 @@ import {
TransferCommunity, TransferCommunity,
} from "lemmy-js-client"; } from "lemmy-js-client";
import { CommentNodeI, CommentViewType } from "../../interfaces"; import { CommentNodeI, CommentViewType } from "../../interfaces";
import { colorList } from "../../utils";
import { CommentNode } from "./comment-node"; import { CommentNode } from "./comment-node";
interface CommentNodesProps { interface CommentNodesProps {

View file

@ -1,3 +1,4 @@
import { myAuthRequired } from "@utils/app";
import { Component, InfernoNode, linkEvent } from "inferno"; import { Component, InfernoNode, linkEvent } from "inferno";
import { T } from "inferno-i18next-dess"; import { T } from "inferno-i18next-dess";
import { import {
@ -5,9 +6,8 @@ import {
CommentView, CommentView,
ResolveCommentReport, ResolveCommentReport,
} from "lemmy-js-client"; } from "lemmy-js-client";
import { i18n } from "../../i18next";
import { CommentNodeI, CommentViewType } from "../../interfaces"; import { CommentNodeI, CommentViewType } from "../../interfaces";
import { myAuthRequired } from "../../utils"; import { I18NextService } from "../../services";
import { Icon, Spinner } from "../common/icon"; import { Icon, Spinner } from "../common/icon";
import { PersonListing } from "../person/person-listing"; import { PersonListing } from "../person/person-listing";
import { CommentNode } from "./comment-node"; import { CommentNode } from "./comment-node";
@ -43,7 +43,7 @@ export class CommentReport extends Component<
render() { render() {
const r = this.props.report; const r = this.props.report;
const comment = r.comment; const comment = r.comment;
const tippyContent = i18n.t( const tippyContent = I18NextService.i18n.t(
r.comment_report.resolved ? "unresolve_report" : "resolve_report" r.comment_report.resolved ? "unresolve_report" : "resolve_report"
); );
@ -102,10 +102,11 @@ export class CommentReport extends Component<
onEditComment={() => Promise.resolve({ state: "empty" })} onEditComment={() => Promise.resolve({ state: "empty" })}
/> />
<div> <div>
{i18n.t("reporter")}: <PersonListing person={r.creator} /> {I18NextService.i18n.t("reporter")}:{" "}
<PersonListing person={r.creator} />
</div> </div>
<div> <div>
{i18n.t("reason")}: {r.comment_report.reason} {I18NextService.i18n.t("reason")}: {r.comment_report.reason}
</div> </div>
{r.resolver && ( {r.resolver && (
<div> <div>

View file

@ -1,11 +1,11 @@
import { numToSI } from "@utils/helpers";
import { Link } from "inferno-router"; import { Link } from "inferno-router";
import { import {
CommunityAggregates, CommunityAggregates,
CommunityId, CommunityId,
SiteAggregates, SiteAggregates,
} from "lemmy-js-client"; } from "lemmy-js-client";
import { i18n } from "../../i18next"; import { I18NextService } from "../../services";
import { numToSI } from "../../utils";
interface BadgesProps { interface BadgesProps {
counts: CommunityAggregates | SiteAggregates; counts: CommunityAggregates | SiteAggregates;
@ -29,66 +29,82 @@ export const Badges = ({ counts, communityId }: BadgesProps) => {
<ul className="badges my-1 list-inline"> <ul className="badges my-1 list-inline">
<li <li
className="list-inline-item badge text-bg-secondary pointer" className="list-inline-item badge text-bg-secondary pointer"
data-tippy-content={i18n.t("active_users_in_the_last_day", { data-tippy-content={I18NextService.i18n.t(
"active_users_in_the_last_day",
{
count: Number(counts.users_active_day), count: Number(counts.users_active_day),
formattedCount: numToSI(counts.users_active_day), formattedCount: numToSI(counts.users_active_day),
})} }
)}
> >
{i18n.t("number_of_users", { {I18NextService.i18n.t("number_of_users", {
count: Number(counts.users_active_day), count: Number(counts.users_active_day),
formattedCount: numToSI(counts.users_active_day), formattedCount: numToSI(counts.users_active_day),
})}{" "} })}{" "}
/ {i18n.t("day")} / {I18NextService.i18n.t("day")}
</li> </li>
<li <li
className="list-inline-item badge text-bg-secondary pointer" className="list-inline-item badge text-bg-secondary pointer"
data-tippy-content={i18n.t("active_users_in_the_last_week", { data-tippy-content={I18NextService.i18n.t(
"active_users_in_the_last_week",
{
count: Number(counts.users_active_week), count: Number(counts.users_active_week),
formattedCount: numToSI(counts.users_active_week), formattedCount: numToSI(counts.users_active_week),
})} }
)}
> >
{i18n.t("number_of_users", { {I18NextService.i18n.t("number_of_users", {
count: Number(counts.users_active_week), count: Number(counts.users_active_week),
formattedCount: numToSI(counts.users_active_week), formattedCount: numToSI(counts.users_active_week),
})}{" "} })}{" "}
/ {i18n.t("week")} / {I18NextService.i18n.t("week")}
</li> </li>
<li <li
className="list-inline-item badge text-bg-secondary pointer" className="list-inline-item badge text-bg-secondary pointer"
data-tippy-content={i18n.t("active_users_in_the_last_month", { data-tippy-content={I18NextService.i18n.t(
"active_users_in_the_last_month",
{
count: Number(counts.users_active_month), count: Number(counts.users_active_month),
formattedCount: numToSI(counts.users_active_month), formattedCount: numToSI(counts.users_active_month),
})} }
)}
> >
{i18n.t("number_of_users", { {I18NextService.i18n.t("number_of_users", {
count: Number(counts.users_active_month), count: Number(counts.users_active_month),
formattedCount: numToSI(counts.users_active_month), formattedCount: numToSI(counts.users_active_month),
})}{" "} })}{" "}
/ {i18n.t("month")} / {I18NextService.i18n.t("month")}
</li> </li>
<li <li
className="list-inline-item badge text-bg-secondary pointer" className="list-inline-item badge text-bg-secondary pointer"
data-tippy-content={i18n.t("active_users_in_the_last_six_months", { data-tippy-content={I18NextService.i18n.t(
"active_users_in_the_last_six_months",
{
count: Number(counts.users_active_half_year), count: Number(counts.users_active_half_year),
formattedCount: numToSI(counts.users_active_half_year), formattedCount: numToSI(counts.users_active_half_year),
})} }
)}
> >
{i18n.t("number_of_users", { {I18NextService.i18n.t("number_of_users", {
count: Number(counts.users_active_half_year), count: Number(counts.users_active_half_year),
formattedCount: numToSI(counts.users_active_half_year), formattedCount: numToSI(counts.users_active_half_year),
})}{" "} })}{" "}
/ {i18n.t("number_of_months", { count: 6, formattedCount: 6 })} /{" "}
{I18NextService.i18n.t("number_of_months", {
count: 6,
formattedCount: 6,
})}
</li> </li>
{isSiteAggregates(counts) && ( {isSiteAggregates(counts) && (
<> <>
<li className="list-inline-item badge text-bg-secondary"> <li className="list-inline-item badge text-bg-secondary">
{i18n.t("number_of_users", { {I18NextService.i18n.t("number_of_users", {
count: Number(counts.users), count: Number(counts.users),
formattedCount: numToSI(counts.users), formattedCount: numToSI(counts.users),
})} })}
</li> </li>
<li className="list-inline-item badge text-bg-secondary"> <li className="list-inline-item badge text-bg-secondary">
{i18n.t("number_of_communities", { {I18NextService.i18n.t("number_of_communities", {
count: Number(counts.communities), count: Number(counts.communities),
formattedCount: numToSI(counts.communities), formattedCount: numToSI(counts.communities),
})} })}
@ -97,20 +113,20 @@ export const Badges = ({ counts, communityId }: BadgesProps) => {
)} )}
{isCommunityAggregates(counts) && ( {isCommunityAggregates(counts) && (
<li className="list-inline-item badge text-bg-secondary"> <li className="list-inline-item badge text-bg-secondary">
{i18n.t("number_of_subscribers", { {I18NextService.i18n.t("number_of_subscribers", {
count: Number(counts.subscribers), count: Number(counts.subscribers),
formattedCount: numToSI(counts.subscribers), formattedCount: numToSI(counts.subscribers),
})} })}
</li> </li>
)} )}
<li className="list-inline-item badge text-bg-secondary"> <li className="list-inline-item badge text-bg-secondary">
{i18n.t("number_of_posts", { {I18NextService.i18n.t("number_of_posts", {
count: Number(counts.posts), count: Number(counts.posts),
formattedCount: numToSI(counts.posts), formattedCount: numToSI(counts.posts),
})} })}
</li> </li>
<li className="list-inline-item badge text-bg-secondary"> <li className="list-inline-item badge text-bg-secondary">
{i18n.t("number_of_comments", { {I18NextService.i18n.t("number_of_comments", {
count: Number(counts.comments), count: Number(counts.comments),
formattedCount: numToSI(counts.comments), formattedCount: numToSI(counts.comments),
})} })}
@ -120,7 +136,7 @@ export const Badges = ({ counts, communityId }: BadgesProps) => {
className="badge text-bg-primary" className="badge text-bg-primary"
to={`/modlog${communityId ? `/${communityId}` : ""}`} to={`/modlog${communityId ? `/${communityId}` : ""}`}
> >
{i18n.t("modlog")} {I18NextService.i18n.t("modlog")}
</Link> </Link>
</li> </li>
</ul> </ul>

View file

@ -1,7 +1,8 @@
import { randomStr } from "@utils/helpers";
import { Component, linkEvent } from "inferno"; import { Component, linkEvent } from "inferno";
import { CommentSortType } from "lemmy-js-client"; import { CommentSortType } from "lemmy-js-client";
import { i18n } from "../../i18next"; import { relTags, sortingHelpUrl } from "../../config";
import { randomStr, relTags, sortingHelpUrl } from "../../utils"; import { I18NextService } from "../../services";
import { Icon } from "./icon"; import { Icon } from "./icon";
interface CommentSortSelectProps { interface CommentSortSelectProps {
@ -41,21 +42,21 @@ export class CommentSortSelect extends Component<
value={this.state.sort} value={this.state.sort}
onChange={linkEvent(this, this.handleSortChange)} onChange={linkEvent(this, this.handleSortChange)}
className="sort-select form-select d-inline-block w-auto me-2 mb-2" className="sort-select form-select d-inline-block w-auto me-2 mb-2"
aria-label={i18n.t("sort_type")} aria-label={I18NextService.i18n.t("sort_type")}
> >
<option disabled aria-hidden="true"> <option disabled aria-hidden="true">
{i18n.t("sort_type")} {I18NextService.i18n.t("sort_type")}
</option> </option>
<option value={"Hot"}>{i18n.t("hot")}</option>, <option value={"Hot"}>{I18NextService.i18n.t("hot")}</option>,
<option value={"Top"}>{i18n.t("top")}</option>, <option value={"Top"}>{I18NextService.i18n.t("top")}</option>,
<option value={"New"}>{i18n.t("new")}</option> <option value={"New"}>{I18NextService.i18n.t("new")}</option>
<option value={"Old"}>{i18n.t("old")}</option> <option value={"Old"}>{I18NextService.i18n.t("old")}</option>
</select> </select>
<a <a
className="sort-select-help text-muted" className="sort-select-help text-muted"
href={sortingHelpUrl} href={sortingHelpUrl}
rel={relTags} rel={relTags}
title={i18n.t("sorting_help")} title={I18NextService.i18n.t("sorting_help")}
> >
<Icon icon="help-circle" classes="icon-inline" /> <Icon icon="help-circle" classes="icon-inline" />
</a> </a>

View file

@ -1,6 +1,6 @@
import { Component, linkEvent } from "inferno"; import { Component, linkEvent } from "inferno";
import { i18n } from "../../i18next";
import { DataType } from "../../interfaces"; import { DataType } from "../../interfaces";
import { I18NextService } from "../../services";
interface DataTypeSelectProps { interface DataTypeSelectProps {
type_: DataType; type_: DataType;
@ -44,7 +44,7 @@ export class DataTypeSelect extends Component<
checked={this.state.type_ == DataType.Post} checked={this.state.type_ == DataType.Post}
onChange={linkEvent(this, this.handleTypeChange)} onChange={linkEvent(this, this.handleTypeChange)}
/> />
{i18n.t("posts")} {I18NextService.i18n.t("posts")}
</label> </label>
<label <label
className={`pointer btn btn-outline-secondary ${ className={`pointer btn btn-outline-secondary ${
@ -58,7 +58,7 @@ export class DataTypeSelect extends Component<
checked={this.state.type_ == DataType.Comment} checked={this.state.type_ == DataType.Comment}
onChange={linkEvent(this, this.handleTypeChange)} onChange={linkEvent(this, this.handleTypeChange)}
/> />
{i18n.t("comments")} {I18NextService.i18n.t("comments")}
</label> </label>
</div> </div>
); );

View file

@ -1,5 +1,5 @@
import { Component } from "inferno"; import { Component } from "inferno";
import { getEmojiMart } from "../../utils"; import { getEmojiMart } from "../../markdown";
interface EmojiMartProps { interface EmojiMartProps {
onEmojiClick?(val: any): any; onEmojiClick?(val: any): any;

View file

@ -1,5 +1,5 @@
import { Component, linkEvent } from "inferno"; import { Component, linkEvent } from "inferno";
import { i18n } from "../../i18next"; import { I18NextService } from "../../services";
import { EmojiMart } from "./emoji-mart"; import { EmojiMart } from "./emoji-mart";
import { Icon } from "./icon"; import { Icon } from "./icon";
@ -28,8 +28,8 @@ export class EmojiPicker extends Component<EmojiPickerProps, EmojiPickerState> {
<span className="emoji-picker"> <span className="emoji-picker">
<button <button
className="btn btn-sm text-muted" className="btn btn-sm text-muted"
data-tippy-content={i18n.t("emoji")} data-tippy-content={I18NextService.i18n.t("emoji")}
aria-label={i18n.t("emoji")} aria-label={I18NextService.i18n.t("emoji")}
disabled={this.props.disabled} disabled={this.props.disabled}
onClick={linkEvent(this, this.togglePicker)} onClick={linkEvent(this, this.togglePicker)}
> >
@ -38,7 +38,8 @@ export class EmojiPicker extends Component<EmojiPickerProps, EmojiPickerState> {
{this.state.showPicker && ( {this.state.showPicker && (
<> <>
<div className="emoji-picker-container"> <div className="position-relative">
<div className="emoji-picker-container position-absolute w-100">
<EmojiMart <EmojiMart
onEmojiClick={this.handleEmojiClick} onEmojiClick={this.handleEmojiClick}
pickerOptions={{}} pickerOptions={{}}
@ -48,6 +49,7 @@ export class EmojiPicker extends Component<EmojiPickerProps, EmojiPickerState> {
onClick={linkEvent(this, this.togglePicker)} onClick={linkEvent(this, this.togglePicker)}
className="click-away-container" className="click-away-container"
/> />
</div>
</> </>
)} )}
</span> </span>

View file

@ -1,5 +1,5 @@
import { setIsoData } from "@utils/app";
import { Component } from "inferno"; import { Component } from "inferno";
import { setIsoData } from "../../utils";
import { ErrorPage } from "../app/error-page"; import { ErrorPage } from "../app/error-page";
class ErrorGuard extends Component<any, any> { class ErrorGuard extends Component<any, any> {

View file

@ -2,8 +2,8 @@ import { htmlToText } from "html-to-text";
import { Component } from "inferno"; import { Component } from "inferno";
import { Helmet } from "inferno-helmet"; import { Helmet } from "inferno-helmet";
import { httpExternalPath } from "../../env"; import { httpExternalPath } from "../../env";
import { i18n } from "../../i18next"; import { md } from "../../markdown";
import { md } from "../../utils"; import { I18NextService } from "../../services";
interface HtmlTagsProps { interface HtmlTagsProps {
title: string; title: string;
@ -21,7 +21,7 @@ export class HtmlTags extends Component<HtmlTagsProps, any> {
return ( return (
<Helmet title={this.props.title}> <Helmet title={this.props.title}>
<html lang={i18n.resolvedLanguage} /> <html lang={I18NextService.i18n.resolvedLanguage} />
{["title", "og:title", "twitter:title"].map(t => ( {["title", "og:title", "twitter:title"].map(t => (
<meta key={t} property={t} content={this.props.title} /> <meta key={t} property={t} content={this.props.title} />

View file

@ -1,6 +1,6 @@
import classNames from "classnames"; import classNames from "classnames";
import { Component } from "inferno"; import { Component } from "inferno";
import { i18n } from "../../i18next"; import { I18NextService } from "../../services";
interface IconProps { interface IconProps {
icon: string; icon: string;
@ -61,7 +61,7 @@ export class PurgeWarning extends Component<any, any> {
return ( return (
<div className="purge-warning mt-2 alert alert-danger" role="alert"> <div className="purge-warning mt-2 alert alert-danger" role="alert">
<Icon icon="alert-triangle" classes="icon-inline me-2" /> <Icon icon="alert-triangle" classes="icon-inline me-2" />
{i18n.t("purge_warning")} {I18NextService.i18n.t("purge_warning")}
</div> </div>
); );
} }

View file

@ -1,7 +1,7 @@
import { randomStr } from "@utils/helpers";
import { Component, linkEvent } from "inferno"; import { Component, linkEvent } from "inferno";
import { i18n } from "../../i18next"; import { HttpService, I18NextService, UserService } from "../../services";
import { HttpService, UserService } from "../../services"; import { toast } from "../../toast";
import { randomStr, toast } from "../../utils";
import { Icon } from "./icon"; import { Icon } from "./icon";
interface ImageUploadFormProps { interface ImageUploadFormProps {
@ -49,7 +49,7 @@ export class ImageUploadForm extends Component<
/> />
<a <a
onClick={linkEvent(this, this.handleRemoveImage)} onClick={linkEvent(this, this.handleRemoveImage)}
aria-label={i18n.t("remove")} aria-label={I18NextService.i18n.t("remove")}
> >
<Icon icon="x" classes="mini-overlay" /> <Icon icon="x" classes="mini-overlay" />
</a> </a>

View file

@ -1,9 +1,9 @@
import { selectableLanguages } from "@utils/app";
import { randomStr } from "@utils/helpers";
import classNames from "classnames"; import classNames from "classnames";
import { Component, linkEvent } from "inferno"; import { Component, linkEvent } from "inferno";
import { Language } from "lemmy-js-client"; import { Language } from "lemmy-js-client";
import { i18n } from "../../i18next"; import { I18NextService, UserService } from "../../services";
import { UserService } from "../../services/UserService";
import { randomStr, selectableLanguages } from "../../utils";
import { Icon } from "./icon"; import { Icon } from "./icon";
interface LanguageSelectProps { interface LanguageSelectProps {
@ -52,7 +52,7 @@ export class LanguageSelect extends Component<LanguageSelectProps, any> {
<div className="language-select"> <div className="language-select">
{this.props.multiple && this.props.showLanguageWarning && ( {this.props.multiple && this.props.showLanguageWarning && (
<div className="alert alert-warning" role="alert"> <div className="alert alert-warning" role="alert">
{i18n.t("undetermined_language_warning")} {I18NextService.i18n.t("undetermined_language_warning")}
</div> </div>
)} )}
<div className="mb-3 row"> <div className="mb-3 row">
@ -63,7 +63,9 @@ export class LanguageSelect extends Component<LanguageSelectProps, any> {
)} )}
htmlFor={this.id} htmlFor={this.id}
> >
{i18n.t(this.props.multiple ? "language_plural" : "language")} {I18NextService.i18n.t(
this.props.multiple ? "language_plural" : "language"
)}
</label> </label>
<div <div
className={classNames(`col-sm-${this.props.multiple ? 9 : 10}`, { className={classNames(`col-sm-${this.props.multiple ? 9 : 10}`, {
@ -102,13 +104,13 @@ export class LanguageSelect extends Component<LanguageSelectProps, any> {
})} })}
id={this.id} id={this.id}
onChange={linkEvent(this, this.handleLanguageChange)} onChange={linkEvent(this, this.handleLanguageChange)}
aria-label={i18n.t("language_select_placeholder")} aria-label={I18NextService.i18n.t("language_select_placeholder")}
multiple={this.props.multiple} multiple={this.props.multiple}
disabled={this.props.disabled} disabled={this.props.disabled}
> >
{!this.props.multiple && ( {!this.props.multiple && (
<option selected disabled hidden> <option selected disabled hidden>
{i18n.t("language_select_placeholder")} {I18NextService.i18n.t("language_select_placeholder")}
</option> </option>
)} )}
{filteredLangs.map(l => ( {filteredLangs.map(l => (

View file

@ -1,8 +1,7 @@
import { randomStr } from "@utils/helpers";
import { Component, linkEvent } from "inferno"; import { Component, linkEvent } from "inferno";
import { ListingType } from "lemmy-js-client"; import { ListingType } from "lemmy-js-client";
import { i18n } from "../../i18next"; import { I18NextService, UserService } from "../../services";
import { UserService } from "../../services";
import { randomStr } from "../../utils";
interface ListingTypeSelectProps { interface ListingTypeSelectProps {
type_: ListingType; type_: ListingType;
@ -42,7 +41,7 @@ export class ListingTypeSelect extends Component<
<div className="listing-type-select btn-group btn-group-toggle flex-wrap"> <div className="listing-type-select btn-group btn-group-toggle flex-wrap">
{this.props.showSubscribed && ( {this.props.showSubscribed && (
<label <label
title={i18n.t("subscribed_description")} title={I18NextService.i18n.t("subscribed_description")}
className={`btn btn-outline-secondary className={`btn btn-outline-secondary
${this.state.type_ == "Subscribed" && "active"} ${this.state.type_ == "Subscribed" && "active"}
${!UserService.Instance.myUserInfo ? "disabled" : "pointer"} ${!UserService.Instance.myUserInfo ? "disabled" : "pointer"}
@ -57,12 +56,12 @@ export class ListingTypeSelect extends Component<
onChange={linkEvent(this, this.handleTypeChange)} onChange={linkEvent(this, this.handleTypeChange)}
disabled={!UserService.Instance.myUserInfo} disabled={!UserService.Instance.myUserInfo}
/> />
{i18n.t("subscribed")} {I18NextService.i18n.t("subscribed")}
</label> </label>
)} )}
{this.props.showLocal && ( {this.props.showLocal && (
<label <label
title={i18n.t("local_description")} title={I18NextService.i18n.t("local_description")}
className={`pointer btn btn-outline-secondary ${ className={`pointer btn btn-outline-secondary ${
this.state.type_ == "Local" && "active" this.state.type_ == "Local" && "active"
}`} }`}
@ -75,11 +74,11 @@ export class ListingTypeSelect extends Component<
checked={this.state.type_ == "Local"} checked={this.state.type_ == "Local"}
onChange={linkEvent(this, this.handleTypeChange)} onChange={linkEvent(this, this.handleTypeChange)}
/> />
{i18n.t("local")} {I18NextService.i18n.t("local")}
</label> </label>
)} )}
<label <label
title={i18n.t("all_description")} title={I18NextService.i18n.t("all_description")}
className={`pointer btn btn-outline-secondary ${ className={`pointer btn btn-outline-secondary ${
(this.state.type_ == "All" && "active") || (this.state.type_ == "All" && "active") ||
(!this.props.showLocal && this.state.type_ == "Local" && "active") (!this.props.showLocal && this.state.type_ == "Local" && "active")
@ -93,7 +92,7 @@ export class ListingTypeSelect extends Component<
checked={this.state.type_ == "All"} checked={this.state.type_ == "All"}
onChange={linkEvent(this, this.handleTypeChange)} onChange={linkEvent(this, this.handleTypeChange)}
/> />
{i18n.t("all")} {I18NextService.i18n.t("all")}
</label> </label>
</div> </div>
); );

View file

@ -1,26 +1,21 @@
import { isBrowser } from "@utils/browser"; import { isBrowser } from "@utils/browser";
import { numToSI, randomStr } from "@utils/helpers";
import autosize from "autosize"; import autosize from "autosize";
import classNames from "classnames"; import classNames from "classnames";
import { NoOptionI18nKeys } from "i18next"; import { NoOptionI18nKeys } from "i18next";
import { Component, linkEvent } from "inferno"; import { Component, linkEvent } from "inferno";
import { Language } from "lemmy-js-client"; import { Language } from "lemmy-js-client";
import { i18n } from "../../i18next";
import { HttpService, UserService } from "../../services";
import { import {
concurrentImageUpload, concurrentImageUpload,
customEmojisLookup,
markdownFieldCharacterLimit, markdownFieldCharacterLimit,
markdownHelpUrl, markdownHelpUrl,
maxUploadImages, maxUploadImages,
mdToHtml,
numToSI,
pictrsDeleteToast,
randomStr,
relTags, relTags,
setupTippy, } from "../../config";
setupTribute, import { customEmojisLookup, mdToHtml, setupTribute } from "../../markdown";
toast, import { HttpService, I18NextService, UserService } from "../../services";
} from "../../utils"; import { setupTippy } from "../../tippy";
import { pictrsDeleteToast, toast } from "../../toast";
import { EmojiPicker } from "./emoji-picker"; import { EmojiPicker } from "./emoji-picker";
import { Icon, Spinner } from "./icon"; import { Icon, Spinner } from "./icon";
import { LanguageSelect } from "./language-select"; import { LanguageSelect } from "./language-select";
@ -133,7 +128,7 @@ export class MarkdownTextArea extends Component<
// TODO add these prompts back in at some point // TODO add these prompts back in at some point
// <Prompt // <Prompt
// when={!this.props.hideNavigationWarnings && this.state.content} // when={!this.props.hideNavigationWarnings && this.state.content}
// message={i18n.t("block_leaving")} // message={I18NextService.i18n.t("block_leaving")}
// /> // />
return ( return (
<form <form
@ -165,7 +160,7 @@ export class MarkdownTextArea extends Component<
className={`mb-0 ${ className={`mb-0 ${
UserService.Instance.myUserInfo && "pointer" UserService.Instance.myUserInfo && "pointer"
}`} }`}
data-tippy-content={i18n.t("upload_image")} data-tippy-content={I18NextService.i18n.t("upload_image")}
> >
{this.state.imageUploadStatus ? ( {this.state.imageUploadStatus ? (
<Spinner /> <Spinner />
@ -203,7 +198,7 @@ export class MarkdownTextArea extends Component<
<a <a
href={markdownHelpUrl} href={markdownHelpUrl}
className="btn btn-sm text-muted font-weight-bold" className="btn btn-sm text-muted font-weight-bold"
title={i18n.t("formatting_help")} title={I18NextService.i18n.t("formatting_help")}
rel={relTags} rel={relTags}
> >
<Icon icon="help-circle" classes="icon-inline" /> <Icon icon="help-circle" classes="icon-inline" />
@ -245,15 +240,17 @@ export class MarkdownTextArea extends Component<
animated animated
value={this.state.imageUploadStatus.uploaded} value={this.state.imageUploadStatus.uploaded}
max={this.state.imageUploadStatus.total} max={this.state.imageUploadStatus.total}
text={i18n.t("pictures_uploded_progess", { text={
I18NextService.i18n.t("pictures_uploded_progess", {
uploaded: this.state.imageUploadStatus.uploaded, uploaded: this.state.imageUploadStatus.uploaded,
total: this.state.imageUploadStatus.total, total: this.state.imageUploadStatus.total,
})} }) ?? undefined
}
/> />
)} )}
</div> </div>
<label className="visually-hidden" htmlFor={this.id}> <label className="visually-hidden" htmlFor={this.id}>
{i18n.t("body")} {I18NextService.i18n.t("body")}
</label> </label>
</div> </div>
</div> </div>
@ -294,7 +291,7 @@ export class MarkdownTextArea extends Component<
className="btn btn-sm btn-secondary ms-2" className="btn btn-sm btn-secondary ms-2"
onClick={linkEvent(this, this.handleReplyCancel)} onClick={linkEvent(this, this.handleReplyCancel)}
> >
{i18n.t("cancel")} {I18NextService.i18n.t("cancel")}
</button> </button>
)} )}
{this.state.content && ( {this.state.content && (
@ -304,7 +301,9 @@ export class MarkdownTextArea extends Component<
}`} }`}
onClick={linkEvent(this, this.handlePreviewToggle)} onClick={linkEvent(this, this.handlePreviewToggle)}
> >
{this.state.previewMode ? i18n.t("edit") : i18n.t("preview")} {this.state.previewMode
? I18NextService.i18n.t("edit")
: I18NextService.i18n.t("preview")}
</button> </button>
)} )}
</div> </div>
@ -336,8 +335,8 @@ export class MarkdownTextArea extends Component<
return ( return (
<button <button
className="btn btn-sm text-muted" className="btn btn-sm text-muted"
data-tippy-content={i18n.t(type)} data-tippy-content={I18NextService.i18n.t(type)}
aria-label={i18n.t(type)} aria-label={I18NextService.i18n.t(type)}
onClick={linkEvent(this, handleClick)} onClick={linkEvent(this, handleClick)}
disabled={this.isDisabled} disabled={this.isDisabled}
> >
@ -380,7 +379,7 @@ export class MarkdownTextArea extends Component<
if (files.length > maxUploadImages) { if (files.length > maxUploadImages) {
toast( toast(
i18n.t("too_many_images_upload", { I18NextService.i18n.t("too_many_images_upload", {
count: Number(maxUploadImages), count: Number(maxUploadImages),
formattedCount: numToSI(maxUploadImages), formattedCount: numToSI(maxUploadImages),
}), }),
@ -681,7 +680,7 @@ export class MarkdownTextArea extends Component<
handleInsertSpoiler(i: MarkdownTextArea, event: any) { handleInsertSpoiler(i: MarkdownTextArea, event: any) {
event.preventDefault(); event.preventDefault();
const beforeChars = `\n::: spoiler ${i18n.t("spoiler")}\n`; const beforeChars = `\n::: spoiler ${I18NextService.i18n.t("spoiler")}\n`;
const afterChars = "\n:::\n"; const afterChars = "\n:::\n";
i.simpleSurroundBeforeAfter(beforeChars, afterChars); i.simpleSurroundBeforeAfter(beforeChars, afterChars);
} }

View file

@ -1,7 +1,7 @@
import { capitalizeFirstLetter } from "@utils/helpers";
import { Component } from "inferno"; import { Component } from "inferno";
import moment from "moment"; import moment from "moment";
import { i18n } from "../../i18next"; import { I18NextService } from "../../services";
import { capitalizeFirstLetter } from "../../utils";
import { Icon } from "./icon"; import { Icon } from "./icon";
interface MomentTimeProps { interface MomentTimeProps {
@ -15,18 +15,18 @@ export class MomentTime extends Component<MomentTimeProps, any> {
constructor(props: any, context: any) { constructor(props: any, context: any) {
super(props, context); super(props, context);
moment.locale([...i18n.languages]); moment.locale([...I18NextService.i18n.languages]);
} }
createdAndModifiedTimes() { createdAndModifiedTimes() {
const updated = this.props.updated; const updated = this.props.updated;
let line = `${capitalizeFirstLetter(i18n.t("created"))}: ${this.format( let line = `${capitalizeFirstLetter(
this.props.published I18NextService.i18n.t("created")
)}`; )}: ${this.format(this.props.published)}`;
if (updated) { if (updated) {
line += `\n\n\n${capitalizeFirstLetter(i18n.t("modified"))} ${this.format( line += `\n\n\n${capitalizeFirstLetter(
updated I18NextService.i18n.t("modified")
)}`; )} ${this.format(updated)}`;
} }
return line; return line;
} }

View file

@ -1,5 +1,5 @@
import { Component } from "inferno"; import { Component } from "inferno";
import { i18n } from "../../../shared/i18next"; import { I18NextService } from "../../services";
export interface IPromptProps { export interface IPromptProps {
when: boolean; when: boolean;
@ -14,7 +14,7 @@ export default class NavigationPrompt extends Component<IPromptProps, any> {
} }
this.unblock = this.context.router.history.block(tx => { this.unblock = this.context.router.history.block(tx => {
if (window.confirm(i18n.t("block_leaving"))) { if (window.confirm(I18NextService.i18n.t("block_leaving") ?? undefined)) {
this.unblock(); this.unblock();
tx.retry(); tx.retry();
} }

View file

@ -1,5 +1,5 @@
import { Component, linkEvent } from "inferno"; import { Component, linkEvent } from "inferno";
import { i18n } from "../../i18next"; import { I18NextService } from "../../services";
interface PaginatorProps { interface PaginatorProps {
page: number; page: number;
@ -18,13 +18,13 @@ export class Paginator extends Component<PaginatorProps, any> {
disabled={this.props.page == 1} disabled={this.props.page == 1}
onClick={linkEvent(this, this.handlePrev)} onClick={linkEvent(this, this.handlePrev)}
> >
{i18n.t("prev")} {I18NextService.i18n.t("prev")}
</button> </button>
<button <button
className="btn btn-secondary" className="btn btn-secondary"
onClick={linkEvent(this, this.handleNext)} onClick={linkEvent(this, this.handleNext)}
> >
{i18n.t("next")} {I18NextService.i18n.t("next")}
</button> </button>
</div> </div>
); );

View file

@ -1,5 +1,5 @@
import { ThemeColor } from "@utils/types";
import classNames from "classnames"; import classNames from "classnames";
import { ThemeColor } from "../../utils";
interface ProgressBarProps { interface ProgressBarProps {
className?: string; className?: string;

View file

@ -1,11 +1,12 @@
import { myAuthRequired } from "@utils/app";
import { Component, InfernoNode, linkEvent } from "inferno"; import { Component, InfernoNode, linkEvent } from "inferno";
import { T } from "inferno-i18next-dess"; import { T } from "inferno-i18next-dess";
import { import {
ApproveRegistrationApplication, ApproveRegistrationApplication,
RegistrationApplicationView, RegistrationApplicationView,
} from "lemmy-js-client"; } from "lemmy-js-client";
import { i18n } from "../../i18next"; import { mdToHtml } from "../../markdown";
import { mdToHtml, myAuthRequired } from "../../utils"; import { I18NextService } from "../../services";
import { PersonListing } from "../person/person-listing"; import { PersonListing } from "../person/person-listing";
import { Spinner } from "./icon"; import { Spinner } from "./icon";
import { MarkdownTextArea } from "./markdown-textarea"; import { MarkdownTextArea } from "./markdown-textarea";
@ -60,12 +61,14 @@ export class RegistrationApplication extends Component<
return ( return (
<div className="registration-application"> <div className="registration-application">
<div> <div>
{i18n.t("applicant")}: <PersonListing person={a.creator} /> {I18NextService.i18n.t("applicant")}:{" "}
<PersonListing person={a.creator} />
</div> </div>
<div> <div>
{i18n.t("created")}: <MomentTime showAgo published={ra.published} /> {I18NextService.i18n.t("created")}:{" "}
<MomentTime showAgo published={ra.published} />
</div> </div>
<div>{i18n.t("answer")}:</div> <div>{I18NextService.i18n.t("answer")}:</div>
<div className="md-div" dangerouslySetInnerHTML={mdToHtml(ra.answer)} /> <div className="md-div" dangerouslySetInnerHTML={mdToHtml(ra.answer)} />
{a.admin && ( {a.admin && (
@ -83,7 +86,7 @@ export class RegistrationApplication extends Component<
</T> </T>
{ra.deny_reason && ( {ra.deny_reason && (
<div> <div>
{i18n.t("deny_reason")}:{" "} {I18NextService.i18n.t("deny_reason")}:{" "}
<div <div
className="md-div d-inline-flex" className="md-div d-inline-flex"
dangerouslySetInnerHTML={mdToHtml(ra.deny_reason)} dangerouslySetInnerHTML={mdToHtml(ra.deny_reason)}
@ -98,7 +101,7 @@ export class RegistrationApplication extends Component<
{this.state.denyExpanded && ( {this.state.denyExpanded && (
<div className="mb-3 row"> <div className="mb-3 row">
<label className="col-sm-2 col-form-label"> <label className="col-sm-2 col-form-label">
{i18n.t("deny_reason")} {I18NextService.i18n.t("deny_reason")}
</label> </label>
<div className="col-sm-10"> <div className="col-sm-10">
<MarkdownTextArea <MarkdownTextArea
@ -115,18 +118,26 @@ export class RegistrationApplication extends Component<
<button <button
className="btn btn-secondary me-2 my-2" className="btn btn-secondary me-2 my-2"
onClick={linkEvent(this, this.handleApprove)} onClick={linkEvent(this, this.handleApprove)}
aria-label={i18n.t("approve")} aria-label={I18NextService.i18n.t("approve")}
> >
{this.state.approveLoading ? <Spinner /> : i18n.t("approve")} {this.state.approveLoading ? (
<Spinner />
) : (
I18NextService.i18n.t("approve")
)}
</button> </button>
)} )}
{(!ra.admin_id || (ra.admin_id && accepted)) && ( {(!ra.admin_id || (ra.admin_id && accepted)) && (
<button <button
className="btn btn-secondary me-2" className="btn btn-secondary me-2"
onClick={linkEvent(this, this.handleDeny)} onClick={linkEvent(this, this.handleDeny)}
aria-label={i18n.t("deny")} aria-label={I18NextService.i18n.t("deny")}
> >
{this.state.denyLoading ? <Spinner /> : i18n.t("deny")} {this.state.denyLoading ? (
<Spinner />
) : (
I18NextService.i18n.t("deny")
)}
</button> </button>
)} )}
</div> </div>

View file

@ -1,3 +1,4 @@
import { Choice } from "@utils/types";
import classNames from "classnames"; import classNames from "classnames";
import { import {
ChangeEvent, ChangeEvent,
@ -6,8 +7,7 @@ import {
linkEvent, linkEvent,
RefObject, RefObject,
} from "inferno"; } from "inferno";
import { i18n } from "../../i18next"; import { I18NextService } from "../../services";
import { Choice } from "../../utils";
import { Icon, Spinner } from "./icon"; import { Icon, Spinner } from "./icon";
interface SearchableSelectProps { interface SearchableSelectProps {
@ -113,7 +113,7 @@ export class SearchableSelect extends Component<
ref={this.toggleButtonRef} ref={this.toggleButtonRef}
> >
{loading {loading
? `${i18n.t("loading")}${loadingEllipses}` ? `${I18NextService.i18n.t("loading")}${loadingEllipses}`
: options[selectedIndex].label} : options[selectedIndex].label}
</button> </button>
<div <div
@ -131,7 +131,7 @@ export class SearchableSelect extends Component<
ref={this.searchInputRef} ref={this.searchInputRef}
onInput={linkEvent(this, handleSearch)} onInput={linkEvent(this, handleSearch)}
value={searchText} value={searchText}
placeholder={`${i18n.t("search")}...`} placeholder={`${I18NextService.i18n.t("search")}...`}
/> />
</div> </div>
{!loading && {!loading &&

View file

@ -1,7 +1,8 @@
import { randomStr } from "@utils/helpers";
import { Component, linkEvent } from "inferno"; import { Component, linkEvent } from "inferno";
import { SortType } from "lemmy-js-client"; import { SortType } from "lemmy-js-client";
import { i18n } from "../../i18next"; import { relTags, sortingHelpUrl } from "../../config";
import { randomStr, relTags, sortingHelpUrl } from "../../utils"; import { I18NextService } from "../../services";
import { Icon } from "./icon"; import { Icon } from "./icon";
interface SortSelectProps { interface SortSelectProps {
@ -40,43 +41,45 @@ export class SortSelect extends Component<SortSelectProps, SortSelectState> {
value={this.state.sort} value={this.state.sort}
onChange={linkEvent(this, this.handleSortChange)} onChange={linkEvent(this, this.handleSortChange)}
className="sort-select form-select d-inline-block w-auto me-2" className="sort-select form-select d-inline-block w-auto me-2"
aria-label={i18n.t("sort_type")} aria-label={I18NextService.i18n.t("sort_type")}
> >
<option disabled aria-hidden="true"> <option disabled aria-hidden="true">
{i18n.t("sort_type")} {I18NextService.i18n.t("sort_type")}
</option> </option>
{!this.props.hideHot && [ {!this.props.hideHot && [
<option key={"Hot"} value={"Hot"}> <option key={"Hot"} value={"Hot"}>
{i18n.t("hot")} {I18NextService.i18n.t("hot")}
</option>, </option>,
<option key={"Active"} value={"Active"}> <option key={"Active"} value={"Active"}>
{i18n.t("active")} {I18NextService.i18n.t("active")}
</option>, </option>,
]} ]}
<option value={"New"}>{i18n.t("new")}</option> <option value={"New"}>{I18NextService.i18n.t("new")}</option>
<option value={"Old"}>{i18n.t("old")}</option> <option value={"Old"}>{I18NextService.i18n.t("old")}</option>
{!this.props.hideMostComments && [ {!this.props.hideMostComments && [
<option key={"MostComments"} value={"MostComments"}> <option key={"MostComments"} value={"MostComments"}>
{i18n.t("most_comments")} {I18NextService.i18n.t("most_comments")}
</option>, </option>,
<option key={"NewComments"} value={"NewComments"}> <option key={"NewComments"} value={"NewComments"}>
{i18n.t("new_comments")} {I18NextService.i18n.t("new_comments")}
</option>, </option>,
]} ]}
<option disabled aria-hidden="true"> <option disabled aria-hidden="true">
</option> </option>
<option value={"TopDay"}>{i18n.t("top_day")}</option> <option value={"TopDay"}>{I18NextService.i18n.t("top_day")}</option>
<option value={"TopWeek"}>{i18n.t("top_week")}</option> <option value={"TopWeek"}>{I18NextService.i18n.t("top_week")}</option>
<option value={"TopMonth"}>{i18n.t("top_month")}</option> <option value={"TopMonth"}>
<option value={"TopYear"}>{i18n.t("top_year")}</option> {I18NextService.i18n.t("top_month")}
<option value={"TopAll"}>{i18n.t("top_all")}</option> </option>
<option value={"TopYear"}>{I18NextService.i18n.t("top_year")}</option>
<option value={"TopAll"}>{I18NextService.i18n.t("top_all")}</option>
</select> </select>
<a <a
className="sort-select-icon text-muted" className="sort-select-icon text-muted"
href={sortingHelpUrl} href={sortingHelpUrl}
rel={relTags} rel={relTags}
title={i18n.t("sorting_help")} title={I18NextService.i18n.t("sorting_help")}
> >
<Icon icon="help-circle" classes="icon-inline" /> <Icon icon="help-circle" classes="icon-inline" />
</a> </a>

View file

@ -1,5 +1,18 @@
import { getQueryParams, getQueryString } from "@utils/helpers"; import {
editCommunity,
myAuth,
myAuthRequired,
setIsoData,
showLocal,
} from "@utils/app";
import {
getPageFromString,
getQueryParams,
getQueryString,
numToSI,
} from "@utils/helpers";
import type { QueryParams } from "@utils/types"; import type { QueryParams } from "@utils/types";
import { RouteDataResponse } from "@utils/types";
import { Component, linkEvent } from "inferno"; import { Component, linkEvent } from "inferno";
import { import {
CommunityResponse, CommunityResponse,
@ -8,20 +21,9 @@ import {
ListCommunitiesResponse, ListCommunitiesResponse,
ListingType, ListingType,
} from "lemmy-js-client"; } from "lemmy-js-client";
import { i18n } from "../../i18next";
import { InitialFetchRequest } from "../../interfaces"; import { InitialFetchRequest } from "../../interfaces";
import { FirstLoadService } from "../../services/FirstLoadService"; import { FirstLoadService, I18NextService } from "../../services";
import { HttpService, RequestState } from "../../services/HttpService"; import { HttpService, RequestState } from "../../services/HttpService";
import {
RouteDataResponse,
editCommunity,
getPageFromString,
myAuth,
myAuthRequired,
numToSI,
setIsoData,
showLocal,
} from "../../utils";
import { HtmlTags } from "../common/html-tags"; import { HtmlTags } from "../common/html-tags";
import { Spinner } from "../common/icon"; import { Spinner } from "../common/icon";
import { ListingTypeSelect } from "../common/listing-type-select"; import { ListingTypeSelect } from "../common/listing-type-select";
@ -83,7 +85,7 @@ export class Communities extends Component<any, CommunitiesState> {
} }
get documentTitle(): string { get documentTitle(): string {
return `${i18n.t("communities")} - ${ return `${I18NextService.i18n.t("communities")} - ${
this.state.siteRes.site_view.site.name this.state.siteRes.site_view.site.name
}`; }`;
} }
@ -100,7 +102,9 @@ export class Communities extends Component<any, CommunitiesState> {
const { listingType, page } = this.getCommunitiesQueryParams(); const { listingType, page } = this.getCommunitiesQueryParams();
return ( return (
<div> <div>
<h1 className="h4">{i18n.t("list_of_communities")}</h1> <h1 className="h4">
{I18NextService.i18n.t("list_of_communities")}
</h1>
<div className="row g-2 justify-content-between"> <div className="row g-2 justify-content-between">
<div className="col-auto"> <div className="col-auto">
<ListingTypeSelect <ListingTypeSelect
@ -120,16 +124,19 @@ export class Communities extends Component<any, CommunitiesState> {
> >
<thead className="pointer"> <thead className="pointer">
<tr> <tr>
<th>{i18n.t("name")}</th> <th>{I18NextService.i18n.t("name")}</th>
<th className="text-right">{i18n.t("subscribers")}</th>
<th className="text-right"> <th className="text-right">
{i18n.t("users")} / {i18n.t("month")} {I18NextService.i18n.t("subscribers")}
</th>
<th className="text-right">
{I18NextService.i18n.t("users")} /{" "}
{I18NextService.i18n.t("month")}
</th> </th>
<th className="text-right d-none d-lg-table-cell"> <th className="text-right d-none d-lg-table-cell">
{i18n.t("posts")} {I18NextService.i18n.t("posts")}
</th> </th>
<th className="text-right d-none d-lg-table-cell"> <th className="text-right d-none d-lg-table-cell">
{i18n.t("comments")} {I18NextService.i18n.t("comments")}
</th> </th>
<th></th> <th></th>
</tr> </tr>
@ -166,7 +173,7 @@ export class Communities extends Component<any, CommunitiesState> {
this.handleFollow this.handleFollow
)} )}
> >
{i18n.t("unsubscribe")} {I18NextService.i18n.t("unsubscribe")}
</button> </button>
)} )}
{cv.subscribed === "NotSubscribed" && ( {cv.subscribed === "NotSubscribed" && (
@ -181,12 +188,12 @@ export class Communities extends Component<any, CommunitiesState> {
this.handleFollow this.handleFollow
)} )}
> >
{i18n.t("subscribe")} {I18NextService.i18n.t("subscribe")}
</button> </button>
)} )}
{cv.subscribed === "Pending" && ( {cv.subscribed === "Pending" && (
<div className="text-warning d-inline-block"> <div className="text-warning d-inline-block">
{i18n.t("subscribe_pending")} {I18NextService.i18n.t("subscribe_pending")}
</div> </div>
)} )}
</td> </td>
@ -227,7 +234,7 @@ export class Communities extends Component<any, CommunitiesState> {
id="communities-search" id="communities-search"
className="form-control" className="form-control"
value={this.state.searchText} value={this.state.searchText}
placeholder={`${i18n.t("search")}...`} placeholder={`${I18NextService.i18n.t("search")}...`}
onInput={linkEvent(this, this.handleSearchChange)} onInput={linkEvent(this, this.handleSearchChange)}
required required
minLength={3} minLength={3}
@ -235,10 +242,10 @@ export class Communities extends Component<any, CommunitiesState> {
</div> </div>
<div className="col-auto"> <div className="col-auto">
<label className="visually-hidden" htmlFor="communities-search"> <label className="visually-hidden" htmlFor="communities-search">
{i18n.t("search")} {I18NextService.i18n.t("search")}
</label> </label>
<button type="submit" className="btn btn-secondary"> <button type="submit" className="btn btn-secondary">
<span>{i18n.t("search")}</span> <span>{I18NextService.i18n.t("search")}</span>
</button> </button>
</div> </div>
</form> </form>

View file

@ -1,3 +1,5 @@
import { myAuthRequired } from "@utils/app";
import { capitalizeFirstLetter, randomStr } from "@utils/helpers";
import { Component, linkEvent } from "inferno"; import { Component, linkEvent } from "inferno";
import { import {
CommunityView, CommunityView,
@ -5,8 +7,7 @@ import {
EditCommunity, EditCommunity,
Language, Language,
} from "lemmy-js-client"; } from "lemmy-js-client";
import { i18n } from "../../i18next"; import { I18NextService } from "../../services";
import { capitalizeFirstLetter, myAuthRequired, randomStr } from "../../utils";
import { Icon, Spinner } from "../common/icon"; import { Icon, Spinner } from "../common/icon";
import { ImageUploadForm } from "../common/image-upload-form"; import { ImageUploadForm } from "../common/image-upload-form";
import { LanguageSelect } from "../common/language-select"; import { LanguageSelect } from "../common/language-select";
@ -106,10 +107,10 @@ export class CommunityForm extends Component<
className="col-12 col-sm-2 col-form-label" className="col-12 col-sm-2 col-form-label"
htmlFor="community-name" htmlFor="community-name"
> >
{i18n.t("name")} {I18NextService.i18n.t("name")}
<span <span
className="position-absolute pointer unselectable ms-2 text-muted" className="position-absolute pointer unselectable ms-2 text-muted"
data-tippy-content={i18n.t("name_explain")} data-tippy-content={I18NextService.i18n.t("name_explain")}
> >
<Icon icon="help-circle" classes="icon-inline" /> <Icon icon="help-circle" classes="icon-inline" />
</span> </span>
@ -124,7 +125,7 @@ export class CommunityForm extends Component<
required required
minLength={3} minLength={3}
pattern="[a-z0-9_]+" pattern="[a-z0-9_]+"
title={i18n.t("community_reqs")} title={I18NextService.i18n.t("community_reqs")}
/> />
</div> </div>
</div> </div>
@ -134,10 +135,10 @@ export class CommunityForm extends Component<
className="col-12 col-sm-2 col-form-label" className="col-12 col-sm-2 col-form-label"
htmlFor="community-title" htmlFor="community-title"
> >
{i18n.t("display_name")} {I18NextService.i18n.t("display_name")}
<span <span
className="position-absolute pointer unselectable ms-2 text-muted" className="position-absolute pointer unselectable ms-2 text-muted"
data-tippy-content={i18n.t("display_name_explain")} data-tippy-content={I18NextService.i18n.t("display_name_explain")}
> >
<Icon icon="help-circle" classes="icon-inline" /> <Icon icon="help-circle" classes="icon-inline" />
</span> </span>
@ -157,11 +158,11 @@ export class CommunityForm extends Component<
</div> </div>
<div className="mb-3 row"> <div className="mb-3 row">
<label className="col-12 col-sm-2 col-form-label"> <label className="col-12 col-sm-2 col-form-label">
{i18n.t("icon")} {I18NextService.i18n.t("icon")}
</label> </label>
<div className="col-12 col-sm-10"> <div className="col-12 col-sm-10">
<ImageUploadForm <ImageUploadForm
uploadTitle={i18n.t("upload_icon")} uploadTitle={I18NextService.i18n.t("upload_icon")}
imageSrc={this.state.form.icon} imageSrc={this.state.form.icon}
onUpload={this.handleIconUpload} onUpload={this.handleIconUpload}
onRemove={this.handleIconRemove} onRemove={this.handleIconRemove}
@ -171,11 +172,11 @@ export class CommunityForm extends Component<
</div> </div>
<div className="mb-3 row"> <div className="mb-3 row">
<label className="col-12 col-sm-2 col-form-label"> <label className="col-12 col-sm-2 col-form-label">
{i18n.t("banner")} {I18NextService.i18n.t("banner")}
</label> </label>
<div className="col-12 col-sm-10"> <div className="col-12 col-sm-10">
<ImageUploadForm <ImageUploadForm
uploadTitle={i18n.t("upload_banner")} uploadTitle={I18NextService.i18n.t("upload_banner")}
imageSrc={this.state.form.banner} imageSrc={this.state.form.banner}
onUpload={this.handleBannerUpload} onUpload={this.handleBannerUpload}
onRemove={this.handleBannerRemove} onRemove={this.handleBannerRemove}
@ -184,12 +185,12 @@ export class CommunityForm extends Component<
</div> </div>
<div className="mb-3 row"> <div className="mb-3 row">
<label className="col-12 col-sm-2 col-form-label" htmlFor={this.id}> <label className="col-12 col-sm-2 col-form-label" htmlFor={this.id}>
{i18n.t("sidebar")} {I18NextService.i18n.t("sidebar")}
</label> </label>
<div className="col-12 col-sm-10"> <div className="col-12 col-sm-10">
<MarkdownTextArea <MarkdownTextArea
initialContent={this.state.form.description} initialContent={this.state.form.description}
placeholder={i18n.t("description")} placeholder={I18NextService.i18n.t("description") ?? undefined}
onContentChange={this.handleCommunityDescriptionChange} onContentChange={this.handleCommunityDescriptionChange}
hideNavigationWarnings hideNavigationWarnings
allLanguages={[]} allLanguages={[]}
@ -201,7 +202,7 @@ export class CommunityForm extends Component<
{this.props.enableNsfw && ( {this.props.enableNsfw && (
<div className="mb-3 row"> <div className="mb-3 row">
<legend className="col-form-label col-sm-2 pt-0"> <legend className="col-form-label col-sm-2 pt-0">
{i18n.t("nsfw")} {I18NextService.i18n.t("nsfw")}
</legend> </legend>
<div className="col-10"> <div className="col-10">
<div className="form-check"> <div className="form-check">
@ -218,7 +219,7 @@ export class CommunityForm extends Component<
)} )}
<div className="mb-3 row"> <div className="mb-3 row">
<legend className="col-form-label col-6 pt-0"> <legend className="col-form-label col-6 pt-0">
{i18n.t("only_mods_can_post_in_community")} {I18NextService.i18n.t("only_mods_can_post_in_community")}
</legend> </legend>
<div className="col-6"> <div className="col-6">
<div className="form-check"> <div className="form-check">
@ -253,9 +254,9 @@ export class CommunityForm extends Component<
{this.props.loading ? ( {this.props.loading ? (
<Spinner /> <Spinner />
) : this.props.community_view ? ( ) : this.props.community_view ? (
capitalizeFirstLetter(i18n.t("save")) capitalizeFirstLetter(I18NextService.i18n.t("save"))
) : ( ) : (
capitalizeFirstLetter(i18n.t("create")) capitalizeFirstLetter(I18NextService.i18n.t("create"))
)} )}
</button> </button>
{this.props.community_view && ( {this.props.community_view && (
@ -264,7 +265,7 @@ export class CommunityForm extends Component<
className="btn btn-secondary" className="btn btn-secondary"
onClick={linkEvent(this, this.handleCancel)} onClick={linkEvent(this, this.handleCancel)}
> >
{i18n.t("cancel")} {I18NextService.i18n.t("cancel")}
</button> </button>
)} )}
</div> </div>

View file

@ -1,7 +1,9 @@
import { showAvatars } from "@utils/app";
import { hostname } from "@utils/helpers";
import { Component } from "inferno"; import { Component } from "inferno";
import { Link } from "inferno-router"; import { Link } from "inferno-router";
import { Community } from "lemmy-js-client"; import { Community } from "lemmy-js-client";
import { hostname, relTags, showAvatars } from "../../utils"; import { relTags } from "../../config";
import { PictrsImage } from "../common/pictrs-image"; import { PictrsImage } from "../common/pictrs-image";
interface CommunityLinkProps { interface CommunityLinkProps {

View file

@ -1,5 +1,28 @@
import { getQueryParams, getQueryString } from "@utils/helpers"; import {
commentsToFlatNodes,
communityRSSUrl,
editComment,
editPost,
editWith,
enableDownvotes,
enableNsfw,
getCommentParentId,
getDataTypeString,
myAuth,
postToCommentSortType,
setIsoData,
showLocal,
updateCommunityBlock,
updatePersonBlock,
} from "@utils/app";
import { restoreScrollPosition, saveScrollPosition } from "@utils/browser";
import {
getPageFromString,
getQueryParams,
getQueryString,
} from "@utils/helpers";
import type { QueryParams } from "@utils/types"; import type { QueryParams } from "@utils/types";
import { RouteDataResponse } from "@utils/types";
import { Component, linkEvent } from "inferno"; import { Component, linkEvent } from "inferno";
import { RouteComponentProps } from "inferno-router/dist/Route"; import { RouteComponentProps } from "inferno-router/dist/Route";
import { import {
@ -54,40 +77,16 @@ import {
SortType, SortType,
TransferCommunity, TransferCommunity,
} from "lemmy-js-client"; } from "lemmy-js-client";
import { i18n } from "../../i18next"; import { fetchLimit, relTags } from "../../config";
import { import {
CommentViewType, CommentViewType,
DataType, DataType,
InitialFetchRequest, InitialFetchRequest,
} from "../../interfaces"; } from "../../interfaces";
import { UserService } from "../../services"; import { FirstLoadService, I18NextService, UserService } from "../../services";
import { FirstLoadService } from "../../services/FirstLoadService";
import { HttpService, RequestState } from "../../services/HttpService"; import { HttpService, RequestState } from "../../services/HttpService";
import { import { setupTippy } from "../../tippy";
RouteDataResponse, import { toast } from "../../toast";
commentsToFlatNodes,
communityRSSUrl,
editComment,
editPost,
editWith,
enableDownvotes,
enableNsfw,
fetchLimit,
getCommentParentId,
getDataTypeString,
getPageFromString,
myAuth,
postToCommentSortType,
relTags,
restoreScrollPosition,
saveScrollPosition,
setIsoData,
setupTippy,
showLocal,
toast,
updateCommunityBlock,
updatePersonBlock,
} from "../../utils";
import { CommentNodes } from "../comment/comment-nodes"; import { CommentNodes } from "../comment/comment-nodes";
import { BannerIconHeader } from "../common/banner-icon-header"; import { BannerIconHeader } from "../common/banner-icon-header";
import { DataTypeSelect } from "../common/data-type-select"; import { DataTypeSelect } from "../common/data-type-select";
@ -330,7 +329,7 @@ export class Community extends Component<
className="btn btn-secondary d-inline-block mb-2 me-3" className="btn btn-secondary d-inline-block mb-2 me-3"
onClick={linkEvent(this, this.handleShowSidebarMobile)} onClick={linkEvent(this, this.handleShowSidebarMobile)}
> >
{i18n.t("sidebar")}{" "} {I18NextService.i18n.t("sidebar")}{" "}
<Icon <Icon
icon={ icon={
this.state.showSidebarMobile this.state.showSidebarMobile
@ -757,14 +756,14 @@ export class Community extends Component<
async handleCommentReport(form: CreateCommentReport) { async handleCommentReport(form: CreateCommentReport) {
const reportRes = await HttpService.client.createCommentReport(form); const reportRes = await HttpService.client.createCommentReport(form);
if (reportRes.state == "success") { if (reportRes.state == "success") {
toast(i18n.t("report_created")); toast(I18NextService.i18n.t("report_created"));
} }
} }
async handlePostReport(form: CreatePostReport) { async handlePostReport(form: CreatePostReport) {
const reportRes = await HttpService.client.createPostReport(form); const reportRes = await HttpService.client.createPostReport(form);
if (reportRes.state == "success") { if (reportRes.state == "success") {
toast(i18n.t("report_created")); toast(I18NextService.i18n.t("report_created"));
} }
} }
@ -790,7 +789,7 @@ export class Community extends Component<
const transferCommunityRes = await HttpService.client.transferCommunity( const transferCommunityRes = await HttpService.client.transferCommunity(
form form
); );
toast(i18n.t("transfer_community")); toast(I18NextService.i18n.t("transfer_community"));
this.updateCommunityFull(transferCommunityRes); this.updateCommunityFull(transferCommunityRes);
} }
@ -879,7 +878,7 @@ export class Community extends Component<
purgeItem(purgeRes: RequestState<PurgeItemResponse>) { purgeItem(purgeRes: RequestState<PurgeItemResponse>) {
if (purgeRes.state == "success") { if (purgeRes.state == "success") {
toast(i18n.t("purge_success")); toast(I18NextService.i18n.t("purge_success"));
this.context.router.history.push(`/`); this.context.router.history.push(`/`);
} }
} }

View file

@ -1,11 +1,10 @@
import { enableNsfw, setIsoData } from "@utils/app";
import { Component } from "inferno"; import { Component } from "inferno";
import { import {
CreateCommunity as CreateCommunityI, CreateCommunity as CreateCommunityI,
GetSiteResponse, GetSiteResponse,
} from "lemmy-js-client"; } from "lemmy-js-client";
import { i18n } from "../../i18next"; import { HttpService, I18NextService } from "../../services";
import { HttpService } from "../../services/HttpService";
import { enableNsfw, setIsoData } from "../../utils";
import { HtmlTags } from "../common/html-tags"; import { HtmlTags } from "../common/html-tags";
import { CommunityForm } from "./community-form"; import { CommunityForm } from "./community-form";
@ -26,7 +25,7 @@ export class CreateCommunity extends Component<any, CreateCommunityState> {
} }
get documentTitle(): string { get documentTitle(): string {
return `${i18n.t("create_community")} - ${ return `${I18NextService.i18n.t("create_community")} - ${
this.state.siteRes.site_view.site.name this.state.siteRes.site_view.site.name
}`; }`;
} }
@ -40,7 +39,7 @@ export class CreateCommunity extends Component<any, CreateCommunityState> {
/> />
<div className="row"> <div className="row">
<div className="col-12 col-lg-6 offset-lg-3 mb-4"> <div className="col-12 col-lg-6 offset-lg-3 mb-4">
<h5>{i18n.t("create_community")}</h5> <h5>{I18NextService.i18n.t("create_community")}</h5>
<CommunityForm <CommunityForm
onUpsertCommunity={this.handleCommunityCreate} onUpsertCommunity={this.handleCommunityCreate}
enableNsfw={enableNsfw(this.state.siteRes)} enableNsfw={enableNsfw(this.state.siteRes)}

View file

@ -1,3 +1,5 @@
import { myAuthRequired } from "@utils/app";
import { getUnixTime, hostname } from "@utils/helpers";
import { amAdmin, amMod, amTopMod } from "@utils/roles"; import { amAdmin, amMod, amTopMod } from "@utils/roles";
import { Component, InfernoNode, linkEvent } from "inferno"; import { Component, InfernoNode, linkEvent } from "inferno";
import { T } from "inferno-i18next-dess"; import { T } from "inferno-i18next-dess";
@ -15,9 +17,8 @@ import {
PurgeCommunity, PurgeCommunity,
RemoveCommunity, RemoveCommunity,
} from "lemmy-js-client"; } from "lemmy-js-client";
import { i18n } from "../../i18next"; import { mdToHtml } from "../../markdown";
import { UserService } from "../../services"; import { I18NextService, UserService } from "../../services";
import { getUnixTime, hostname, mdToHtml, myAuthRequired } from "../../utils";
import { Badges } from "../common/badges"; import { Badges } from "../common/badges";
import { BannerIconHeader } from "../common/banner-icon-header"; import { BannerIconHeader } from "../common/banner-icon-header";
import { Icon, PurgeWarning, Spinner } from "../common/icon"; import { Icon, PurgeWarning, Spinner } from "../common/icon";
@ -185,7 +186,7 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
) : ( ) : (
<> <>
<Icon icon="check" classes="icon-inline text-success me-1" /> <Icon icon="check" classes="icon-inline text-success me-1" />
{i18n.t("joined")} {I18NextService.i18n.t("joined")}
</> </>
)} )}
</button> </button>
@ -198,23 +199,23 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
{this.state.followCommunityLoading ? ( {this.state.followCommunityLoading ? (
<Spinner /> <Spinner />
) : ( ) : (
i18n.t("subscribe_pending") I18NextService.i18n.t("subscribe_pending")
)} )}
</button> </button>
)} )}
{community.removed && ( {community.removed && (
<small className="me-2 text-muted font-italic"> <small className="me-2 text-muted font-italic">
{i18n.t("removed")} {I18NextService.i18n.t("removed")}
</small> </small>
)} )}
{community.deleted && ( {community.deleted && (
<small className="me-2 text-muted font-italic"> <small className="me-2 text-muted font-italic">
{i18n.t("deleted")} {I18NextService.i18n.t("deleted")}
</small> </small>
)} )}
{community.nsfw && ( {community.nsfw && (
<small className="me-2 text-muted font-italic"> <small className="me-2 text-muted font-italic">
{i18n.t("nsfw")} {I18NextService.i18n.t("nsfw")}
</small> </small>
)} )}
</h5> </h5>
@ -232,7 +233,7 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
mods() { mods() {
return ( return (
<ul className="list-inline small"> <ul className="list-inline small">
<li className="list-inline-item">{i18n.t("mods")}: </li> <li className="list-inline-item">{I18NextService.i18n.t("mods")}: </li>
{this.props.moderators.map(mod => ( {this.props.moderators.map(mod => (
<li key={mod.moderator.id} className="list-inline-item"> <li key={mod.moderator.id} className="list-inline-item">
<PersonListing person={mod.moderator} /> <PersonListing person={mod.moderator} />
@ -251,7 +252,7 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
}`} }`}
to={`/create_post?communityId=${cv.community.id}`} to={`/create_post?communityId=${cv.community.id}`}
> >
{i18n.t("create_a_post")} {I18NextService.i18n.t("create_a_post")}
</Link> </Link>
); );
} }
@ -268,7 +269,7 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
{this.state.followCommunityLoading ? ( {this.state.followCommunityLoading ? (
<Spinner /> <Spinner />
) : ( ) : (
i18n.t("subscribe") I18NextService.i18n.t("subscribe")
)} )}
</button> </button>
)} )}
@ -286,7 +287,9 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
className="btn btn-danger d-block mb-2 w-100" className="btn btn-danger d-block mb-2 w-100"
onClick={linkEvent(this, this.handleBlockCommunity)} onClick={linkEvent(this, this.handleBlockCommunity)}
> >
{i18n.t(blocked ? "unblock_community" : "block_community")} {I18NextService.i18n.t(
blocked ? "unblock_community" : "block_community"
)}
</button> </button>
)} )}
</> </>
@ -313,8 +316,8 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
<button <button
className="btn btn-link text-muted d-inline-block" className="btn btn-link text-muted d-inline-block"
onClick={linkEvent(this, this.handleEditClick)} onClick={linkEvent(this, this.handleEditClick)}
data-tippy-content={i18n.t("edit")} data-tippy-content={I18NextService.i18n.t("edit")}
aria-label={i18n.t("edit")} aria-label={I18NextService.i18n.t("edit")}
> >
<Icon icon="edit" classes="icon-inline" /> <Icon icon="edit" classes="icon-inline" />
</button> </button>
@ -329,20 +332,20 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
this.handleShowConfirmLeaveModTeamClick this.handleShowConfirmLeaveModTeamClick
)} )}
> >
{i18n.t("leave_mod_team")} {I18NextService.i18n.t("leave_mod_team")}
</button> </button>
</li> </li>
) : ( ) : (
<> <>
<li className="list-inline-item-action"> <li className="list-inline-item-action">
{i18n.t("are_you_sure")} {I18NextService.i18n.t("are_you_sure")}
</li> </li>
<li className="list-inline-item-action"> <li className="list-inline-item-action">
<button <button
className="btn btn-link text-muted d-inline-block" className="btn btn-link text-muted d-inline-block"
onClick={linkEvent(this, this.handleLeaveModTeam)} onClick={linkEvent(this, this.handleLeaveModTeam)}
> >
{i18n.t("yes")} {I18NextService.i18n.t("yes")}
</button> </button>
</li> </li>
<li className="list-inline-item-action"> <li className="list-inline-item-action">
@ -353,7 +356,7 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
this.handleCancelLeaveModTeamClick this.handleCancelLeaveModTeamClick
)} )}
> >
{i18n.t("no")} {I18NextService.i18n.t("no")}
</button> </button>
</li> </li>
</> </>
@ -365,13 +368,13 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
onClick={linkEvent(this, this.handleDeleteCommunity)} onClick={linkEvent(this, this.handleDeleteCommunity)}
data-tippy-content={ data-tippy-content={
!community_view.community.deleted !community_view.community.deleted
? i18n.t("delete") ? I18NextService.i18n.t("delete")
: i18n.t("restore") : I18NextService.i18n.t("restore")
} }
aria-label={ aria-label={
!community_view.community.deleted !community_view.community.deleted
? i18n.t("delete") ? I18NextService.i18n.t("delete")
: i18n.t("restore") : I18NextService.i18n.t("restore")
} }
> >
{this.state.deleteCommunityLoading ? ( {this.state.deleteCommunityLoading ? (
@ -396,7 +399,7 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
className="btn btn-link text-muted d-inline-block" className="btn btn-link text-muted d-inline-block"
onClick={linkEvent(this, this.handleModRemoveShow)} onClick={linkEvent(this, this.handleModRemoveShow)}
> >
{i18n.t("remove")} {I18NextService.i18n.t("remove")}
</button> </button>
) : ( ) : (
<button <button
@ -406,16 +409,16 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
{this.state.removeCommunityLoading ? ( {this.state.removeCommunityLoading ? (
<Spinner /> <Spinner />
) : ( ) : (
i18n.t("restore") I18NextService.i18n.t("restore")
)} )}
</button> </button>
)} )}
<button <button
className="btn btn-link text-muted d-inline-block" className="btn btn-link text-muted d-inline-block"
onClick={linkEvent(this, this.handlePurgeCommunityShow)} onClick={linkEvent(this, this.handlePurgeCommunityShow)}
aria-label={i18n.t("purge_community")} aria-label={I18NextService.i18n.t("purge_community")}
> >
{i18n.t("purge_community")} {I18NextService.i18n.t("purge_community")}
</button> </button>
</li> </li>
)} )}
@ -424,13 +427,13 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
<form onSubmit={linkEvent(this, this.handleRemoveCommunity)}> <form onSubmit={linkEvent(this, this.handleRemoveCommunity)}>
<div className="input-group mb-3"> <div className="input-group mb-3">
<label className="col-form-label" htmlFor="remove-reason"> <label className="col-form-label" htmlFor="remove-reason">
{i18n.t("reason")} {I18NextService.i18n.t("reason")}
</label> </label>
<input <input
type="text" type="text"
id="remove-reason" id="remove-reason"
className="form-control me-2" className="form-control me-2"
placeholder={i18n.t("optional")} placeholder={I18NextService.i18n.t("optional")}
value={this.state.removeReason} value={this.state.removeReason}
onInput={linkEvent(this, this.handleModRemoveReasonChange)} onInput={linkEvent(this, this.handleModRemoveReasonChange)}
/> />
@ -438,14 +441,14 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
{/* TODO hold off on expires for now */} {/* TODO hold off on expires for now */}
{/* <div class="mb-3 row"> */} {/* <div class="mb-3 row"> */}
{/* <label class="col-form-label">Expires</label> */} {/* <label class="col-form-label">Expires</label> */}
{/* <input type="date" class="form-control me-2" placeholder={i18n.t('expires')} value={this.state.removeExpires} onInput={linkEvent(this, this.handleModRemoveExpiresChange)} /> */} {/* <input type="date" class="form-control me-2" placeholder={I18NextService.i18n.t('expires')} value={this.state.removeExpires} onInput={linkEvent(this, this.handleModRemoveExpiresChange)} /> */}
{/* </div> */} {/* </div> */}
<div className="input-group mb-3"> <div className="input-group mb-3">
<button type="submit" className="btn btn-secondary"> <button type="submit" className="btn btn-secondary">
{this.state.removeCommunityLoading ? ( {this.state.removeCommunityLoading ? (
<Spinner /> <Spinner />
) : ( ) : (
i18n.t("remove_community") I18NextService.i18n.t("remove_community")
)} )}
</button> </button>
</div> </div>
@ -458,13 +461,13 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
</div> </div>
<div className="input-group mb-3"> <div className="input-group mb-3">
<label className="visually-hidden" htmlFor="purge-reason"> <label className="visually-hidden" htmlFor="purge-reason">
{i18n.t("reason")} {I18NextService.i18n.t("reason")}
</label> </label>
<input <input
type="text" type="text"
id="purge-reason" id="purge-reason"
className="form-control me-2" className="form-control me-2"
placeholder={i18n.t("reason")} placeholder={I18NextService.i18n.t("reason")}
value={this.state.purgeReason} value={this.state.purgeReason}
onInput={linkEvent(this, this.handlePurgeReasonChange)} onInput={linkEvent(this, this.handlePurgeReasonChange)}
/> />
@ -476,9 +479,9 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
<button <button
type="submit" type="submit"
className="btn btn-secondary" className="btn btn-secondary"
aria-label={i18n.t("purge_community")} aria-label={I18NextService.i18n.t("purge_community")}
> >
{i18n.t("purge_community")} {I18NextService.i18n.t("purge_community")}
</button> </button>
)} )}
</div> </div>

View file

@ -1,3 +1,11 @@
import {
fetchThemeList,
myAuthRequired,
setIsoData,
showLocal,
} from "@utils/app";
import { capitalizeFirstLetter } from "@utils/helpers";
import { RouteDataResponse } from "@utils/types";
import classNames from "classnames"; import classNames from "classnames";
import { Component, linkEvent } from "inferno"; import { Component, linkEvent } from "inferno";
import { import {
@ -10,21 +18,11 @@ import {
GetSiteResponse, GetSiteResponse,
PersonView, PersonView,
} from "lemmy-js-client"; } from "lemmy-js-client";
import { i18n } from "../../i18next";
import { InitialFetchRequest } from "../../interfaces"; import { InitialFetchRequest } from "../../interfaces";
import { FirstLoadService } from "../../services/FirstLoadService"; import { removeFromEmojiDataModel, updateEmojiDataModel } from "../../markdown";
import { FirstLoadService, I18NextService } from "../../services";
import { HttpService, RequestState } from "../../services/HttpService"; import { HttpService, RequestState } from "../../services/HttpService";
import { import { toast } from "../../toast";
RouteDataResponse,
capitalizeFirstLetter,
fetchThemeList,
myAuthRequired,
removeFromEmojiDataModel,
setIsoData,
showLocal,
toast,
updateEmojiDataModel,
} from "../../utils";
import { HtmlTags } from "../common/html-tags"; import { HtmlTags } from "../common/html-tags";
import { Spinner } from "../common/icon"; import { Spinner } from "../common/icon";
import Tabs from "../common/tabs"; import Tabs from "../common/tabs";
@ -109,7 +107,7 @@ export class AdminSettings extends Component<any, AdminSettingsState> {
} }
get documentTitle(): string { get documentTitle(): string {
return `${i18n.t("admin_settings")} - ${ return `${I18NextService.i18n.t("admin_settings")} - ${
this.state.siteRes.site_view.site.name this.state.siteRes.site_view.site.name
}`; }`;
} }
@ -130,7 +128,7 @@ export class AdminSettings extends Component<any, AdminSettingsState> {
tabs={[ tabs={[
{ {
key: "site", key: "site",
label: i18n.t("site"), label: I18NextService.i18n.t("site"),
getNode: isSelected => ( getNode: isSelected => (
<div <div
className={classNames("tab-pane show", { className={classNames("tab-pane show", {
@ -182,7 +180,7 @@ export class AdminSettings extends Component<any, AdminSettingsState> {
}, },
{ {
key: "taglines", key: "taglines",
label: i18n.t("taglines"), label: I18NextService.i18n.t("taglines"),
getNode: isSelected => ( getNode: isSelected => (
<div <div
className={classNames("tab-pane", { className={classNames("tab-pane", {
@ -203,7 +201,7 @@ export class AdminSettings extends Component<any, AdminSettingsState> {
}, },
{ {
key: "emojis", key: "emojis",
label: i18n.t("emojis"), label: I18NextService.i18n.t("emojis"),
getNode: isSelected => ( getNode: isSelected => (
<div <div
className={classNames("tab-pane", { className={classNames("tab-pane", {
@ -254,7 +252,7 @@ export class AdminSettings extends Component<any, AdminSettingsState> {
admins() { admins() {
return ( return (
<> <>
<h5>{capitalizeFirstLetter(i18n.t("admins"))}</h5> <h5>{capitalizeFirstLetter(I18NextService.i18n.t("admins"))}</h5>
<ul className="list-unstyled"> <ul className="list-unstyled">
{this.state.siteRes.admins.map(admin => ( {this.state.siteRes.admins.map(admin => (
<li key={admin.person.id} className="list-inline-item"> <li key={admin.person.id} className="list-inline-item">
@ -276,7 +274,7 @@ export class AdminSettings extends Component<any, AdminSettingsState> {
{this.state.leaveAdminTeamRes.state == "loading" ? ( {this.state.leaveAdminTeamRes.state == "loading" ? (
<Spinner /> <Spinner />
) : ( ) : (
i18n.t("leave_admin_team") I18NextService.i18n.t("leave_admin_team")
)} )}
</button> </button>
); );
@ -294,7 +292,7 @@ export class AdminSettings extends Component<any, AdminSettingsState> {
const bans = this.state.bannedRes.data.banned; const bans = this.state.bannedRes.data.banned;
return ( return (
<> <>
<h5>{i18n.t("banned_users")}</h5> <h5>{I18NextService.i18n.t("banned_users")}</h5>
<ul className="list-unstyled"> <ul className="list-unstyled">
{bans.map(banned => ( {bans.map(banned => (
<li key={banned.person.id} className="list-inline-item"> <li key={banned.person.id} className="list-inline-item">
@ -320,7 +318,7 @@ export class AdminSettings extends Component<any, AdminSettingsState> {
s.siteRes.taglines = editRes.data.taglines; s.siteRes.taglines = editRes.data.taglines;
return s; return s;
}); });
toast(i18n.t("site_saved")); toast(I18NextService.i18n.t("site_saved"));
} }
this.setState({ loading: false }); this.setState({ loading: false });
@ -341,7 +339,7 @@ export class AdminSettings extends Component<any, AdminSettingsState> {
}); });
if (this.state.leaveAdminTeamRes.state === "success") { if (this.state.leaveAdminTeamRes.state === "success") {
toast(i18n.t("left_admin_team")); toast(I18NextService.i18n.t("left_admin_team"));
this.context.router.history.replace("/"); this.context.router.history.replace("/");
} }
} }

View file

@ -1,3 +1,4 @@
import { myAuthRequired, setIsoData } from "@utils/app";
import { Component, linkEvent } from "inferno"; import { Component, linkEvent } from "inferno";
import { import {
CreateCustomEmoji, CreateCustomEmoji,
@ -5,15 +6,9 @@ import {
EditCustomEmoji, EditCustomEmoji,
GetSiteResponse, GetSiteResponse,
} from "lemmy-js-client"; } from "lemmy-js-client";
import { i18n } from "../../i18next"; import { customEmojisLookup } from "../../markdown";
import { HttpService } from "../../services/HttpService"; import { HttpService, I18NextService } from "../../services";
import { import { pictrsDeleteToast, toast } from "../../toast";
customEmojisLookup,
myAuthRequired,
pictrsDeleteToast,
setIsoData,
toast,
} from "../../utils";
import { EmojiMart } from "../common/emoji-mart"; import { EmojiMart } from "../common/emoji-mart";
import { HtmlTags } from "../common/html-tags"; import { HtmlTags } from "../common/html-tags";
import { Icon } from "../common/icon"; import { Icon } from "../common/icon";
@ -70,7 +65,7 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
this.handleEmojiClick = this.handleEmojiClick.bind(this); this.handleEmojiClick = this.handleEmojiClick.bind(this);
} }
get documentTitle(): string { get documentTitle(): string {
return i18n.t("custom_emojis"); return I18NextService.i18n.t("custom_emojis");
} }
render() { render() {
@ -80,7 +75,7 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
title={this.documentTitle} title={this.documentTitle}
path={this.context.router.route.match.url} path={this.context.router.route.match.url}
/> />
<h5 className="col-12">{i18n.t("custom_emojis")}</h5> <h5 className="col-12">{I18NextService.i18n.t("custom_emojis")}</h5>
{customEmojisLookup.size > 0 && ( {customEmojisLookup.size > 0 && (
<div> <div>
<EmojiMart <EmojiMart
@ -93,15 +88,21 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
<table id="emojis_table" className="table table-sm table-hover"> <table id="emojis_table" className="table table-sm table-hover">
<thead className="pointer"> <thead className="pointer">
<tr> <tr>
<th>{i18n.t("column_emoji")}</th> <th>{I18NextService.i18n.t("column_emoji")}</th>
<th className="text-right">{i18n.t("column_shortcode")}</th> <th className="text-right">
<th className="text-right">{i18n.t("column_category")}</th> {I18NextService.i18n.t("column_shortcode")}
<th className="text-right d-lg-table-cell d-none"> </th>
{i18n.t("column_imageurl")} <th className="text-right">
{I18NextService.i18n.t("column_category")}
</th>
<th className="text-right d-lg-table-cell d-none">
{I18NextService.i18n.t("column_imageurl")}
</th>
<th className="text-right">
{I18NextService.i18n.t("column_alttext")}
</th> </th>
<th className="text-right">{i18n.t("column_alttext")}</th>
<th className="text-right d-lg-table-cell"> <th className="text-right d-lg-table-cell">
{i18n.t("column_keywords")} {I18NextService.i18n.t("column_keywords")}
</th> </th>
<th style="width:121px"></th> <th style="width:121px"></th>
</tr> </tr>
@ -219,8 +220,8 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
{ i: this, cv: cv }, { i: this, cv: cv },
this.handleEditEmojiClick this.handleEditEmojiClick
)} )}
data-tippy-content={i18n.t("save")} data-tippy-content={I18NextService.i18n.t("save")}
aria-label={i18n.t("save")} aria-label={I18NextService.i18n.t("save")}
disabled={ disabled={
this.props.loading || this.props.loading ||
!this.canEdit(cv) || !this.canEdit(cv) ||
@ -240,10 +241,10 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
{ i: this, index: index, cv: cv }, { i: this, index: index, cv: cv },
this.handleDeleteEmojiClick this.handleDeleteEmojiClick
)} )}
data-tippy-content={i18n.t("delete")} data-tippy-content={I18NextService.i18n.t("delete")}
aria-label={i18n.t("delete")} aria-label={I18NextService.i18n.t("delete")}
disabled={this.props.loading} disabled={this.props.loading}
title={i18n.t("delete")} title={I18NextService.i18n.t("delete")}
> >
<Icon <Icon
icon="trash" icon="trash"
@ -261,7 +262,7 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
className="btn btn-sm btn-secondary me-2" className="btn btn-sm btn-secondary me-2"
onClick={linkEvent(this, this.handleAddEmojiClick)} onClick={linkEvent(this, this.handleAddEmojiClick)}
> >
{i18n.t("add_custom_emoji")} {I18NextService.i18n.t("add_custom_emoji")}
</button> </button>
<Paginator page={this.state.page} onChange={this.handlePageChange} /> <Paginator page={this.state.page} onChange={this.handlePageChange} />
@ -284,8 +285,8 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
} }
getEditTooltip(cv: CustomEmojiViewForm) { getEditTooltip(cv: CustomEmojiViewForm) {
if (this.canEdit(cv)) return i18n.t("save"); if (this.canEdit(cv)) return I18NextService.i18n.t("save");
else return i18n.t("custom_emoji_save_validation"); else return I18NextService.i18n.t("custom_emoji_save_validation");
} }
handlePageChange(page: number) { handlePageChange(page: number) {

View file

@ -1,6 +1,28 @@
import { getQueryParams, getQueryString } from "@utils/helpers"; import {
commentsToFlatNodes,
editComment,
editPost,
editWith,
enableDownvotes,
enableNsfw,
getCommentParentId,
getDataTypeString,
myAuth,
postToCommentSortType,
setIsoData,
showLocal,
updatePersonBlock,
} from "@utils/app";
import { restoreScrollPosition, saveScrollPosition } from "@utils/browser";
import {
getPageFromString,
getQueryParams,
getQueryString,
getRandomFromList,
} from "@utils/helpers";
import { canCreateCommunity } from "@utils/roles"; import { canCreateCommunity } from "@utils/roles";
import type { QueryParams } from "@utils/types"; import type { QueryParams } from "@utils/types";
import { RouteDataResponse } from "@utils/types";
import { NoOptionI18nKeys } from "i18next"; import { NoOptionI18nKeys } from "i18next";
import { Component, MouseEventHandler, linkEvent } from "inferno"; import { Component, MouseEventHandler, linkEvent } from "inferno";
import { T } from "inferno-i18next-dess"; import { T } from "inferno-i18next-dess";
@ -50,41 +72,17 @@ import {
SortType, SortType,
TransferCommunity, TransferCommunity,
} from "lemmy-js-client"; } from "lemmy-js-client";
import { i18n } from "../../i18next"; import { fetchLimit, relTags, trendingFetchLimit } from "../../config";
import { import {
CommentViewType, CommentViewType,
DataType, DataType,
InitialFetchRequest, InitialFetchRequest,
} from "../../interfaces"; } from "../../interfaces";
import { UserService } from "../../services"; import { mdToHtml } from "../../markdown";
import { FirstLoadService } from "../../services/FirstLoadService"; import { FirstLoadService, I18NextService, UserService } from "../../services";
import { HttpService, RequestState } from "../../services/HttpService"; import { HttpService, RequestState } from "../../services/HttpService";
import { import { setupTippy } from "../../tippy";
RouteDataResponse, import { toast } from "../../toast";
commentsToFlatNodes,
editComment,
editPost,
editWith,
enableDownvotes,
enableNsfw,
fetchLimit,
getCommentParentId,
getDataTypeString,
getPageFromString,
getRandomFromList,
mdToHtml,
myAuth,
postToCommentSortType,
relTags,
restoreScrollPosition,
saveScrollPosition,
setIsoData,
setupTippy,
showLocal,
toast,
trendingFetchLimit,
updatePersonBlock,
} from "../../utils";
import { CommentNodes } from "../comment/comment-nodes"; import { CommentNodes } from "../comment/comment-nodes";
import { DataTypeSelect } from "../common/data-type-select"; import { DataTypeSelect } from "../common/data-type-select";
import { HtmlTags } from "../common/html-tags"; import { HtmlTags } from "../common/html-tags";
@ -197,7 +195,7 @@ const MobileButton = ({
className="btn btn-secondary d-inline-block mb-2 me-3" className="btn btn-secondary d-inline-block mb-2 me-3"
onClick={onClick} onClick={onClick}
> >
{i18n.t(textKey)}{" "} {I18NextService.i18n.t(textKey)}{" "}
<Icon icon={show ? `minus-square` : `plus-square`} classes="icon-inline" /> <Icon icon={show ? `minus-square` : `plus-square`} classes="icon-inline" />
</button> </button>
); );
@ -210,7 +208,7 @@ const LinkButton = ({
translationKey: NoOptionI18nKeys; translationKey: NoOptionI18nKeys;
}) => ( }) => (
<Link className="btn btn-secondary d-block" to={path}> <Link className="btn btn-secondary d-block" to={path}>
{i18n.t(translationKey)} {I18NextService.i18n.t(translationKey)}
</Link> </Link>
); );
@ -565,10 +563,14 @@ export class Home extends Component<any, HomeState> {
className="btn btn-sm text-muted" className="btn btn-sm text-muted"
onClick={linkEvent(this, this.handleCollapseSubscribe)} onClick={linkEvent(this, this.handleCollapseSubscribe)}
aria-label={ aria-label={
subscribedCollapsed ? i18n.t("expand") : i18n.t("collapse") subscribedCollapsed
? I18NextService.i18n.t("expand")
: I18NextService.i18n.t("collapse")
} }
data-tippy-content={ data-tippy-content={
subscribedCollapsed ? i18n.t("expand") : i18n.t("collapse") subscribedCollapsed
? I18NextService.i18n.t("expand")
: I18NextService.i18n.t("collapse")
} }
aria-expanded="true" aria-expanded="true"
aria-controls="sidebarSubscribedBody" aria-controls="sidebarSubscribedBody"
@ -932,14 +934,14 @@ export class Home extends Component<any, HomeState> {
async handleCommentReport(form: CreateCommentReport) { async handleCommentReport(form: CreateCommentReport) {
const reportRes = await HttpService.client.createCommentReport(form); const reportRes = await HttpService.client.createCommentReport(form);
if (reportRes.state == "success") { if (reportRes.state == "success") {
toast(i18n.t("report_created")); toast(I18NextService.i18n.t("report_created"));
} }
} }
async handlePostReport(form: CreatePostReport) { async handlePostReport(form: CreatePostReport) {
const reportRes = await HttpService.client.createPostReport(form); const reportRes = await HttpService.client.createPostReport(form);
if (reportRes.state == "success") { if (reportRes.state == "success") {
toast(i18n.t("report_created")); toast(I18NextService.i18n.t("report_created"));
} }
} }
@ -963,7 +965,7 @@ export class Home extends Component<any, HomeState> {
async handleTransferCommunity(form: TransferCommunity) { async handleTransferCommunity(form: TransferCommunity) {
await HttpService.client.transferCommunity(form); await HttpService.client.transferCommunity(form);
toast(i18n.t("transfer_community")); toast(I18NextService.i18n.t("transfer_community"));
} }
async handleCommentReplyRead(form: MarkCommentReplyAsRead) { async handleCommentReplyRead(form: MarkCommentReplyAsRead) {
@ -1030,7 +1032,7 @@ export class Home extends Component<any, HomeState> {
purgeItem(purgeRes: RequestState<PurgeItemResponse>) { purgeItem(purgeRes: RequestState<PurgeItemResponse>) {
if (purgeRes.state == "success") { if (purgeRes.state == "success") {
toast(i18n.t("purge_success")); toast(I18NextService.i18n.t("purge_success"));
this.context.router.history.push(`/`); this.context.router.history.push(`/`);
} }
} }

View file

@ -1,14 +1,15 @@
import { setIsoData } from "@utils/app";
import { RouteDataResponse } from "@utils/types";
import { Component } from "inferno"; import { Component } from "inferno";
import { import {
GetFederatedInstancesResponse, GetFederatedInstancesResponse,
GetSiteResponse, GetSiteResponse,
Instance, Instance,
} from "lemmy-js-client"; } from "lemmy-js-client";
import { i18n } from "../../i18next"; import { relTags } from "../../config";
import { InitialFetchRequest } from "../../interfaces"; import { InitialFetchRequest } from "../../interfaces";
import { FirstLoadService } from "../../services/FirstLoadService"; import { FirstLoadService, I18NextService } from "../../services";
import { HttpService, RequestState } from "../../services/HttpService"; import { HttpService, RequestState } from "../../services/HttpService";
import { RouteDataResponse, relTags, setIsoData } from "../../utils";
import { HtmlTags } from "../common/html-tags"; import { HtmlTags } from "../common/html-tags";
import { Spinner } from "../common/icon"; import { Spinner } from "../common/icon";
@ -68,7 +69,9 @@ export class Instances extends Component<any, InstancesState> {
} }
get documentTitle(): string { get documentTitle(): string {
return `${i18n.t("instances")} - ${this.state.siteRes.site_view.site.name}`; return `${I18NextService.i18n.t("instances")} - ${
this.state.siteRes.site_view.site.name
}`;
} }
renderInstances() { renderInstances() {
@ -84,18 +87,18 @@ export class Instances extends Component<any, InstancesState> {
return instances ? ( return instances ? (
<div className="row"> <div className="row">
<div className="col-md-6"> <div className="col-md-6">
<h5>{i18n.t("linked_instances")}</h5> <h5>{I18NextService.i18n.t("linked_instances")}</h5>
{this.itemList(instances.linked)} {this.itemList(instances.linked)}
</div> </div>
{instances.allowed && instances.allowed.length > 0 && ( {instances.allowed && instances.allowed.length > 0 && (
<div className="col-md-6"> <div className="col-md-6">
<h5>{i18n.t("allowed_instances")}</h5> <h5>{I18NextService.i18n.t("allowed_instances")}</h5>
{this.itemList(instances.allowed)} {this.itemList(instances.allowed)}
</div> </div>
)} )}
{instances.blocked && instances.blocked.length > 0 && ( {instances.blocked && instances.blocked.length > 0 && (
<div className="col-md-6"> <div className="col-md-6">
<h5>{i18n.t("blocked_instances")}</h5> <h5>{I18NextService.i18n.t("blocked_instances")}</h5>
{this.itemList(instances.blocked)} {this.itemList(instances.blocked)}
</div> </div>
)} )}
@ -125,9 +128,9 @@ export class Instances extends Component<any, InstancesState> {
<table id="instances_table" className="table table-sm table-hover"> <table id="instances_table" className="table table-sm table-hover">
<thead className="pointer"> <thead className="pointer">
<tr> <tr>
<th>{i18n.t("name")}</th> <th>{I18NextService.i18n.t("name")}</th>
<th>{i18n.t("software")}</th> <th>{I18NextService.i18n.t("software")}</th>
<th>{i18n.t("version")}</th> <th>{I18NextService.i18n.t("version")}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -146,7 +149,7 @@ export class Instances extends Component<any, InstancesState> {
</table> </table>
</div> </div>
) : ( ) : (
<div>{i18n.t("none_found")}</div> <div>{I18NextService.i18n.t("none_found")}</div>
); );
} }
} }

View file

@ -1,7 +1,8 @@
import { setIsoData } from "@utils/app";
import { Component } from "inferno"; import { Component } from "inferno";
import { GetSiteResponse } from "lemmy-js-client"; import { GetSiteResponse } from "lemmy-js-client";
import { i18n } from "../../i18next"; import { mdToHtml } from "../../markdown";
import { mdToHtml, setIsoData } from "../../utils"; import { I18NextService } from "../../services";
import { HtmlTags } from "../common/html-tags"; import { HtmlTags } from "../common/html-tags";
interface LegalState { interface LegalState {
@ -19,7 +20,7 @@ export class Legal extends Component<any, LegalState> {
} }
get documentTitle(): string { get documentTitle(): string {
return i18n.t("legal_information"); return I18NextService.i18n.t("legal_information");
} }
render() { render() {

View file

@ -1,10 +1,11 @@
import { myAuth, setIsoData } from "@utils/app";
import { isBrowser } from "@utils/browser"; import { isBrowser } from "@utils/browser";
import { validEmail } from "@utils/helpers";
import { Component, linkEvent } from "inferno"; import { Component, linkEvent } from "inferno";
import { GetSiteResponse, LoginResponse } from "lemmy-js-client"; import { GetSiteResponse, LoginResponse } from "lemmy-js-client";
import { i18n } from "../../i18next"; import { I18NextService, UserService } from "../../services";
import { UserService } from "../../services";
import { HttpService, RequestState } from "../../services/HttpService"; import { HttpService, RequestState } from "../../services/HttpService";
import { myAuth, setIsoData, toast, validEmail } from "../../utils"; import { toast } from "../../toast";
import { HtmlTags } from "../common/html-tags"; import { HtmlTags } from "../common/html-tags";
import { Spinner } from "../common/icon"; import { Spinner } from "../common/icon";
@ -41,7 +42,9 @@ export class Login extends Component<any, State> {
} }
get documentTitle(): string { get documentTitle(): string {
return `${i18n.t("login")} - ${this.state.siteRes.site_view.site.name}`; return `${I18NextService.i18n.t("login")} - ${
this.state.siteRes.site_view.site.name
}`;
} }
get isLemmyMl(): boolean { get isLemmyMl(): boolean {
@ -66,13 +69,13 @@ export class Login extends Component<any, State> {
return ( return (
<div> <div>
<form onSubmit={linkEvent(this, this.handleLoginSubmit)}> <form onSubmit={linkEvent(this, this.handleLoginSubmit)}>
<h5>{i18n.t("login")}</h5> <h5>{I18NextService.i18n.t("login")}</h5>
<div className="mb-3 row"> <div className="mb-3 row">
<label <label
className="col-sm-2 col-form-label" className="col-sm-2 col-form-label"
htmlFor="login-email-or-username" htmlFor="login-email-or-username"
> >
{i18n.t("email_or_username")} {I18NextService.i18n.t("email_or_username")}
</label> </label>
<div className="col-sm-10"> <div className="col-sm-10">
<input <input
@ -89,7 +92,7 @@ export class Login extends Component<any, State> {
</div> </div>
<div className="mb-3 row"> <div className="mb-3 row">
<label className="col-sm-2 col-form-label" htmlFor="login-password"> <label className="col-sm-2 col-form-label" htmlFor="login-password">
{i18n.t("password")} {I18NextService.i18n.t("password")}
</label> </label>
<div className="col-sm-10"> <div className="col-sm-10">
<input <input
@ -110,9 +113,9 @@ export class Login extends Component<any, State> {
!!this.state.form.username_or_email && !!this.state.form.username_or_email &&
!validEmail(this.state.form.username_or_email) !validEmail(this.state.form.username_or_email)
} }
title={i18n.t("no_password_reset")} title={I18NextService.i18n.t("no_password_reset")}
> >
{i18n.t("forgot_password")} {I18NextService.i18n.t("forgot_password")}
</button> </button>
</div> </div>
</div> </div>
@ -122,7 +125,7 @@ export class Login extends Component<any, State> {
className="col-sm-6 col-form-label" className="col-sm-6 col-form-label"
htmlFor="login-totp-token" htmlFor="login-totp-token"
> >
{i18n.t("two_factor_token")} {I18NextService.i18n.t("two_factor_token")}
</label> </label>
<div className="col-sm-6"> <div className="col-sm-6">
<input <input
@ -144,7 +147,7 @@ export class Login extends Component<any, State> {
{this.state.loginRes.state == "loading" ? ( {this.state.loginRes.state == "loading" ? (
<Spinner /> <Spinner />
) : ( ) : (
i18n.t("login") I18NextService.i18n.t("login")
)} )}
</button> </button>
</div> </div>
@ -170,7 +173,7 @@ export class Login extends Component<any, State> {
case "failed": { case "failed": {
if (loginRes.msg === "missing_totp_token") { if (loginRes.msg === "missing_totp_token") {
i.setState({ showTotp: true }); i.setState({ showTotp: true });
toast(i18n.t("enter_two_factor_code"), "info"); toast(I18NextService.i18n.t("enter_two_factor_code"), "info");
} }
i.setState({ loginRes: { state: "failed", msg: loginRes.msg } }); i.setState({ loginRes: { state: "failed", msg: loginRes.msg } });
@ -218,7 +221,7 @@ export class Login extends Component<any, State> {
if (email) { if (email) {
const res = await HttpService.client.passwordReset({ email }); const res = await HttpService.client.passwordReset({ email });
if (res.state == "success") { if (res.state == "success") {
toast(i18n.t("reset_password_mail_sent")); toast(I18NextService.i18n.t("reset_password_mail_sent"));
} }
} }
} }

View file

@ -1,8 +1,9 @@
import { myAuthRequired } from "@utils/app";
import { capitalizeFirstLetter } from "@utils/helpers";
import classNames from "classnames"; import classNames from "classnames";
import { Component, FormEventHandler, linkEvent } from "inferno"; import { Component, FormEventHandler, linkEvent } from "inferno";
import { EditSite, LocalSiteRateLimit } from "lemmy-js-client"; import { EditSite, LocalSiteRateLimit } from "lemmy-js-client";
import { i18n } from "../../i18next"; import { I18NextService } from "../../services";
import { capitalizeFirstLetter, myAuthRequired } from "../../utils";
import { Spinner } from "../common/icon"; import { Spinner } from "../common/icon";
import Tabs from "../common/tabs"; import Tabs from "../common/tabs";
@ -56,7 +57,9 @@ function RateLimits({
return ( return (
<div role="tabpanel" className={classNames("mb-3 row", className)}> <div role="tabpanel" className={classNames("mb-3 row", className)}>
<div className="col-md-6"> <div className="col-md-6">
<label htmlFor="rate-limit">{i18n.t("rate_limit")}</label> <label htmlFor="rate-limit">
{I18NextService.i18n.t("rate_limit")}
</label>
<input <input
type="number" type="number"
id="rate-limit" id="rate-limit"
@ -67,7 +70,9 @@ function RateLimits({
/> />
</div> </div>
<div className="col-md-6"> <div className="col-md-6">
<label htmlFor="rate-limit-per-second">{i18n.t("per_second")}</label> <label htmlFor="rate-limit-per-second">
{I18NextService.i18n.t("per_second")}
</label>
<input <input
type="number" type="number"
id="rate-limit-per-second" id="rate-limit-per-second"
@ -140,11 +145,11 @@ export default class RateLimitsForm extends Component<
className="rate-limit-form" className="rate-limit-form"
onSubmit={linkEvent(this, submitRateLimitForm)} onSubmit={linkEvent(this, submitRateLimitForm)}
> >
<h5>{i18n.t("rate_limit_header")}</h5> <h5>{I18NextService.i18n.t("rate_limit_header")}</h5>
<Tabs <Tabs
tabs={rateLimitTypes.map(rateLimitType => ({ tabs={rateLimitTypes.map(rateLimitType => ({
key: rateLimitType, key: rateLimitType,
label: i18n.t(`rate_limit_${rateLimitType}`), label: I18NextService.i18n.t(`rate_limit_${rateLimitType}`),
getNode: isSelected => ( getNode: isSelected => (
<RateLimits <RateLimits
className={classNames("tab-pane show", { className={classNames("tab-pane show", {
@ -175,7 +180,7 @@ export default class RateLimitsForm extends Component<
{this.props.loading ? ( {this.props.loading ? (
<Spinner /> <Spinner />
) : ( ) : (
capitalizeFirstLetter(i18n.t("save")) capitalizeFirstLetter(I18NextService.i18n.t("save"))
)} )}
</button> </button>
</div> </div>

View file

@ -1,3 +1,4 @@
import { fetchThemeList, setIsoData } from "@utils/app";
import { Component, linkEvent } from "inferno"; import { Component, linkEvent } from "inferno";
import { Helmet } from "inferno-helmet"; import { Helmet } from "inferno-helmet";
import { import {
@ -6,10 +7,8 @@ import {
LoginResponse, LoginResponse,
Register, Register,
} from "lemmy-js-client"; } from "lemmy-js-client";
import { i18n } from "../../i18next"; import { I18NextService, UserService } from "../../services";
import { UserService } from "../../services";
import { HttpService, RequestState } from "../../services/HttpService"; import { HttpService, RequestState } from "../../services/HttpService";
import { fetchThemeList, setIsoData } from "../../utils";
import { Spinner } from "../common/icon"; import { Spinner } from "../common/icon";
import { SiteForm } from "./site-form"; import { SiteForm } from "./site-form";
@ -55,7 +54,7 @@ export class Setup extends Component<any, State> {
} }
get documentTitle(): string { get documentTitle(): string {
return `${i18n.t("setup")} - Lemmy`; return `${I18NextService.i18n.t("setup")} - Lemmy`;
} }
render() { render() {
@ -64,7 +63,7 @@ export class Setup extends Component<any, State> {
<Helmet title={this.documentTitle} /> <Helmet title={this.documentTitle} />
<div className="row"> <div className="row">
<div className="col-12 offset-lg-3 col-lg-6"> <div className="col-12 offset-lg-3 col-lg-6">
<h3>{i18n.t("lemmy_instance_setup")}</h3> <h3>{I18NextService.i18n.t("lemmy_instance_setup")}</h3>
{!this.state.doneRegisteringUser ? ( {!this.state.doneRegisteringUser ? (
this.registerUser() this.registerUser()
) : ( ) : (
@ -85,10 +84,10 @@ export class Setup extends Component<any, State> {
registerUser() { registerUser() {
return ( return (
<form onSubmit={linkEvent(this, this.handleRegisterSubmit)}> <form onSubmit={linkEvent(this, this.handleRegisterSubmit)}>
<h5>{i18n.t("setup_admin")}</h5> <h5>{I18NextService.i18n.t("setup_admin")}</h5>
<div className="mb-3 row"> <div className="mb-3 row">
<label className="col-sm-2 col-form-label" htmlFor="username"> <label className="col-sm-2 col-form-label" htmlFor="username">
{i18n.t("username")} {I18NextService.i18n.t("username")}
</label> </label>
<div className="col-sm-10"> <div className="col-sm-10">
<input <input
@ -105,7 +104,7 @@ export class Setup extends Component<any, State> {
</div> </div>
<div className="mb-3 row"> <div className="mb-3 row">
<label className="col-sm-2 col-form-label" htmlFor="email"> <label className="col-sm-2 col-form-label" htmlFor="email">
{i18n.t("email")} {I18NextService.i18n.t("email")}
</label> </label>
<div className="col-sm-10"> <div className="col-sm-10">
@ -113,7 +112,7 @@ export class Setup extends Component<any, State> {
type="email" type="email"
id="email" id="email"
className="form-control" className="form-control"
placeholder={i18n.t("optional")} placeholder={I18NextService.i18n.t("optional")}
value={this.state.form.email} value={this.state.form.email}
onInput={linkEvent(this, this.handleRegisterEmailChange)} onInput={linkEvent(this, this.handleRegisterEmailChange)}
minLength={3} minLength={3}
@ -122,7 +121,7 @@ export class Setup extends Component<any, State> {
</div> </div>
<div className="mb-3 row"> <div className="mb-3 row">
<label className="col-sm-2 col-form-label" htmlFor="password"> <label className="col-sm-2 col-form-label" htmlFor="password">
{i18n.t("password")} {I18NextService.i18n.t("password")}
</label> </label>
<div className="col-sm-10"> <div className="col-sm-10">
<input <input
@ -140,7 +139,7 @@ export class Setup extends Component<any, State> {
</div> </div>
<div className="mb-3 row"> <div className="mb-3 row">
<label className="col-sm-2 col-form-label" htmlFor="verify-password"> <label className="col-sm-2 col-form-label" htmlFor="verify-password">
{i18n.t("verify_password")} {I18NextService.i18n.t("verify_password")}
</label> </label>
<div className="col-sm-10"> <div className="col-sm-10">
<input <input
@ -162,7 +161,7 @@ export class Setup extends Component<any, State> {
{this.state.registerRes.state == "loading" ? ( {this.state.registerRes.state == "loading" ? (
<Spinner /> <Spinner />
) : ( ) : (
i18n.t("sign_up") I18NextService.i18n.t("sign_up")
)} )}
</button> </button>
</div> </div>

View file

@ -1,4 +1,6 @@
import { myAuth, setIsoData } from "@utils/app";
import { isBrowser } from "@utils/browser"; import { isBrowser } from "@utils/browser";
import { validEmail } from "@utils/helpers";
import { Options, passwordStrength } from "check-password-strength"; import { Options, passwordStrength } from "check-password-strength";
import { NoOptionI18nKeys } from "i18next"; import { NoOptionI18nKeys } from "i18next";
import { Component, linkEvent } from "inferno"; import { Component, linkEvent } from "inferno";
@ -10,17 +12,11 @@ import {
LoginResponse, LoginResponse,
SiteView, SiteView,
} from "lemmy-js-client"; } from "lemmy-js-client";
import { i18n } from "../../i18next"; import { joinLemmyUrl } from "../../config";
import { UserService } from "../../services"; import { mdToHtml } from "../../markdown";
import { I18NextService, UserService } from "../../services";
import { HttpService, RequestState } from "../../services/HttpService"; import { HttpService, RequestState } from "../../services/HttpService";
import { import { toast } from "../../toast";
joinLemmyUrl,
mdToHtml,
myAuth,
setIsoData,
toast,
validEmail,
} from "../../utils";
import { HtmlTags } from "../common/html-tags"; import { HtmlTags } from "../common/html-tags";
import { Icon, Spinner } from "../common/icon"; import { Icon, Spinner } from "../common/icon";
import { MarkdownTextArea } from "../common/markdown-textarea"; import { MarkdownTextArea } from "../common/markdown-textarea";
@ -116,7 +112,7 @@ export class Signup extends Component<any, State> {
} }
titleName(siteView: SiteView): string { titleName(siteView: SiteView): string {
return i18n.t( return I18NextService.i18n.t(
siteView.local_site.private_instance ? "apply_to_join" : "sign_up" siteView.local_site.private_instance ? "apply_to_join" : "sign_up"
); );
} }
@ -162,7 +158,7 @@ export class Signup extends Component<any, State> {
className="col-sm-2 col-form-label" className="col-sm-2 col-form-label"
htmlFor="register-username" htmlFor="register-username"
> >
{i18n.t("username")} {I18NextService.i18n.t("username")}
</label> </label>
<div className="col-sm-10"> <div className="col-sm-10">
@ -175,14 +171,14 @@ export class Signup extends Component<any, State> {
required required
minLength={3} minLength={3}
pattern="[a-zA-Z0-9_]+" pattern="[a-zA-Z0-9_]+"
title={i18n.t("community_reqs")} title={I18NextService.i18n.t("community_reqs")}
/> />
</div> </div>
</div> </div>
<div className="mb-3 row"> <div className="mb-3 row">
<label className="col-sm-2 col-form-label" htmlFor="register-email"> <label className="col-sm-2 col-form-label" htmlFor="register-email">
{i18n.t("email")} {I18NextService.i18n.t("email")}
</label> </label>
<div className="col-sm-10"> <div className="col-sm-10">
<input <input
@ -191,8 +187,8 @@ export class Signup extends Component<any, State> {
className="form-control" className="form-control"
placeholder={ placeholder={
siteView.local_site.require_email_verification siteView.local_site.require_email_verification
? i18n.t("required") ? I18NextService.i18n.t("required")
: i18n.t("optional") : I18NextService.i18n.t("optional")
} }
value={this.state.form.email} value={this.state.form.email}
autoComplete="email" autoComplete="email"
@ -205,7 +201,7 @@ export class Signup extends Component<any, State> {
!validEmail(this.state.form.email) && ( !validEmail(this.state.form.email) && (
<div className="mt-2 mb-0 alert alert-warning" role="alert"> <div className="mt-2 mb-0 alert alert-warning" role="alert">
<Icon icon="alert-triangle" classes="icon-inline me-2" /> <Icon icon="alert-triangle" classes="icon-inline me-2" />
{i18n.t("no_password_reset")} {I18NextService.i18n.t("no_password_reset")}
</div> </div>
)} )}
</div> </div>
@ -216,7 +212,7 @@ export class Signup extends Component<any, State> {
className="col-sm-2 col-form-label" className="col-sm-2 col-form-label"
htmlFor="register-password" htmlFor="register-password"
> >
{i18n.t("password")} {I18NextService.i18n.t("password")}
</label> </label>
<div className="col-sm-10"> <div className="col-sm-10">
<input <input
@ -232,7 +228,9 @@ export class Signup extends Component<any, State> {
/> />
{this.state.form.password && ( {this.state.form.password && (
<div className={this.passwordColorClass}> <div className={this.passwordColorClass}>
{i18n.t(this.passwordStrength as NoOptionI18nKeys)} {I18NextService.i18n.t(
this.passwordStrength as NoOptionI18nKeys
)}
</div> </div>
)} )}
</div> </div>
@ -243,7 +241,7 @@ export class Signup extends Component<any, State> {
className="col-sm-2 col-form-label" className="col-sm-2 col-form-label"
htmlFor="register-verify-password" htmlFor="register-verify-password"
> >
{i18n.t("verify_password")} {I18NextService.i18n.t("verify_password")}
</label> </label>
<div className="col-sm-10"> <div className="col-sm-10">
<input <input
@ -265,7 +263,7 @@ export class Signup extends Component<any, State> {
<div className="offset-sm-2 col-sm-10"> <div className="offset-sm-2 col-sm-10">
<div className="mt-2 alert alert-warning" role="alert"> <div className="mt-2 alert alert-warning" role="alert">
<Icon icon="alert-triangle" classes="icon-inline me-2" /> <Icon icon="alert-triangle" classes="icon-inline me-2" />
{i18n.t("fill_out_application")} {I18NextService.i18n.t("fill_out_application")}
</div> </div>
{siteView.local_site.application_question && ( {siteView.local_site.application_question && (
<div <div
@ -283,7 +281,7 @@ export class Signup extends Component<any, State> {
className="col-sm-2 col-form-label" className="col-sm-2 col-form-label"
htmlFor="application_answer" htmlFor="application_answer"
> >
{i18n.t("answer")} {I18NextService.i18n.t("answer")}
</label> </label>
<div className="col-sm-10"> <div className="col-sm-10">
<MarkdownTextArea <MarkdownTextArea
@ -309,7 +307,7 @@ export class Signup extends Component<any, State> {
onChange={linkEvent(this, this.handleRegisterShowNsfwChange)} onChange={linkEvent(this, this.handleRegisterShowNsfwChange)}
/> />
<label className="form-check-label" htmlFor="register-show-nsfw"> <label className="form-check-label" htmlFor="register-show-nsfw">
{i18n.t("show_nsfw")} {I18NextService.i18n.t("show_nsfw")}
</label> </label>
</div> </div>
</div> </div>
@ -348,12 +346,14 @@ export class Signup extends Component<any, State> {
return ( return (
<div className="mb-3 row"> <div className="mb-3 row">
<label className="col-sm-2" htmlFor="register-captcha"> <label className="col-sm-2" htmlFor="register-captcha">
<span className="me-2">{i18n.t("enter_code")}</span> <span className="me-2">
{I18NextService.i18n.t("enter_code")}
</span>
<button <button
type="button" type="button"
className="btn btn-secondary" className="btn btn-secondary"
onClick={linkEvent(this, this.handleRegenCaptcha)} onClick={linkEvent(this, this.handleRegenCaptcha)}
aria-label={i18n.t("captcha")} aria-label={I18NextService.i18n.t("captcha")}
> >
<Icon icon="refresh-cw" classes="icon-refresh-cw" /> <Icon icon="refresh-cw" classes="icon-refresh-cw" />
</button> </button>
@ -387,13 +387,13 @@ export class Signup extends Component<any, State> {
className="rounded-top img-fluid" className="rounded-top img-fluid"
src={this.captchaPngSrc(captchaRes)} src={this.captchaPngSrc(captchaRes)}
style="border-bottom-right-radius: 0; border-bottom-left-radius: 0;" style="border-bottom-right-radius: 0; border-bottom-left-radius: 0;"
alt={i18n.t("captcha")} alt={I18NextService.i18n.t("captcha")}
/> />
{captchaRes.wav && ( {captchaRes.wav && (
<button <button
className="rounded-bottom btn btn-sm btn-secondary d-block" className="rounded-bottom btn btn-sm btn-secondary d-block"
style="border-top-right-radius: 0; border-top-left-radius: 0;" style="border-top-right-radius: 0; border-top-left-radius: 0;"
title={i18n.t("play_captcha_audio")} title={I18NextService.i18n.t("play_captcha_audio")}
onClick={linkEvent(this, this.handleCaptchaPlay)} onClick={linkEvent(this, this.handleCaptchaPlay)}
type="button" type="button"
disabled={this.state.captchaPlaying} disabled={this.state.captchaPlaying}
@ -477,10 +477,10 @@ export class Signup extends Component<any, State> {
i.props.history.replace("/communities"); i.props.history.replace("/communities");
} else { } else {
if (data.verify_email_sent) { if (data.verify_email_sent) {
toast(i18n.t("verify_email_sent")); toast(I18NextService.i18n.t("verify_email_sent"));
} }
if (data.registration_created) { if (data.registration_created) {
toast(i18n.t("registration_application_sent")); toast(I18NextService.i18n.t("registration_application_sent"));
} }
i.props.history.push("/"); i.props.history.push("/");
} }

View file

@ -1,3 +1,5 @@
import { myAuthRequired } from "@utils/app";
import { capitalizeFirstLetter, validInstanceTLD } from "@utils/helpers";
import { import {
Component, Component,
InfernoKeyboardEvent, InfernoKeyboardEvent,
@ -11,12 +13,7 @@ import {
Instance, Instance,
ListingType, ListingType,
} from "lemmy-js-client"; } from "lemmy-js-client";
import { i18n } from "../../i18next"; import { I18NextService } from "../../services";
import {
capitalizeFirstLetter,
myAuthRequired,
validInstanceTLD,
} from "../../utils";
import { Icon, Spinner } from "../common/icon"; import { Icon, Spinner } from "../common/icon";
import { ImageUploadForm } from "../common/image-upload-form"; import { ImageUploadForm } from "../common/image-upload-form";
import { LanguageSelect } from "../common/language-select"; import { LanguageSelect } from "../common/language-select";
@ -139,12 +136,12 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
/> />
<h5>{`${ <h5>{`${
siteSetup siteSetup
? capitalizeFirstLetter(i18n.t("edit")) ? capitalizeFirstLetter(I18NextService.i18n.t("edit"))
: capitalizeFirstLetter(i18n.t("setup")) : capitalizeFirstLetter(I18NextService.i18n.t("setup"))
} ${i18n.t("your_site")}`}</h5> } ${I18NextService.i18n.t("your_site")}`}</h5>
<div className="mb-3 row"> <div className="mb-3 row">
<label className="col-12 col-form-label" htmlFor="create-site-name"> <label className="col-12 col-form-label" htmlFor="create-site-name">
{i18n.t("name")} {I18NextService.i18n.t("name")}
</label> </label>
<div className="col-12"> <div className="col-12">
<input <input
@ -160,9 +157,11 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
</div> </div>
</div> </div>
<div className="input-group mb-3"> <div className="input-group mb-3">
<label className="me-2 col-form-label">{i18n.t("icon")}</label> <label className="me-2 col-form-label">
{I18NextService.i18n.t("icon")}
</label>
<ImageUploadForm <ImageUploadForm
uploadTitle={i18n.t("upload_icon")} uploadTitle={I18NextService.i18n.t("upload_icon")}
imageSrc={this.state.siteForm.icon} imageSrc={this.state.siteForm.icon}
onUpload={this.handleIconUpload} onUpload={this.handleIconUpload}
onRemove={this.handleIconRemove} onRemove={this.handleIconRemove}
@ -170,9 +169,11 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
/> />
</div> </div>
<div className="input-group mb-3"> <div className="input-group mb-3">
<label className="me-2 col-form-label">{i18n.t("banner")}</label> <label className="me-2 col-form-label">
{I18NextService.i18n.t("banner")}
</label>
<ImageUploadForm <ImageUploadForm
uploadTitle={i18n.t("upload_banner")} uploadTitle={I18NextService.i18n.t("upload_banner")}
imageSrc={this.state.siteForm.banner} imageSrc={this.state.siteForm.banner}
onUpload={this.handleBannerUpload} onUpload={this.handleBannerUpload}
onRemove={this.handleBannerRemove} onRemove={this.handleBannerRemove}
@ -180,7 +181,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
</div> </div>
<div className="mb-3 row"> <div className="mb-3 row">
<label className="col-12 col-form-label" htmlFor="site-desc"> <label className="col-12 col-form-label" htmlFor="site-desc">
{i18n.t("description")} {I18NextService.i18n.t("description")}
</label> </label>
<div className="col-12"> <div className="col-12">
<input <input
@ -194,7 +195,9 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
</div> </div>
</div> </div>
<div className="mb-3 row"> <div className="mb-3 row">
<label className="col-12 col-form-label">{i18n.t("sidebar")}</label> <label className="col-12 col-form-label">
{I18NextService.i18n.t("sidebar")}
</label>
<div className="col-12"> <div className="col-12">
<MarkdownTextArea <MarkdownTextArea
initialContent={this.state.siteForm.sidebar} initialContent={this.state.siteForm.sidebar}
@ -207,7 +210,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
</div> </div>
<div className="mb-3 row"> <div className="mb-3 row">
<label className="col-12 col-form-label"> <label className="col-12 col-form-label">
{i18n.t("legal_information")} {I18NextService.i18n.t("legal_information")}
</label> </label>
<div className="col-12"> <div className="col-12">
<MarkdownTextArea <MarkdownTextArea
@ -233,7 +236,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
className="form-check-label" className="form-check-label"
htmlFor="create-site-downvotes" htmlFor="create-site-downvotes"
> >
{i18n.t("enable_downvotes")} {I18NextService.i18n.t("enable_downvotes")}
</label> </label>
</div> </div>
</div> </div>
@ -252,7 +255,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
className="form-check-label" className="form-check-label"
htmlFor="create-site-enable-nsfw" htmlFor="create-site-enable-nsfw"
> >
{i18n.t("enable_nsfw")} {I18NextService.i18n.t("enable_nsfw")}
</label> </label>
</div> </div>
</div> </div>
@ -263,7 +266,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
className="form-check-label me-2" className="form-check-label me-2"
htmlFor="create-site-registration-mode" htmlFor="create-site-registration-mode"
> >
{i18n.t("registration_mode")} {I18NextService.i18n.t("registration_mode")}
</label> </label>
<select <select
id="create-site-registration-mode" id="create-site-registration-mode"
@ -272,17 +275,21 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
className="form-select d-inline-block w-auto" className="form-select d-inline-block w-auto"
> >
<option value={"RequireApplication"}> <option value={"RequireApplication"}>
{i18n.t("require_registration_application")} {I18NextService.i18n.t("require_registration_application")}
</option>
<option value={"Open"}>
{I18NextService.i18n.t("open_registration")}
</option>
<option value={"Closed"}>
{I18NextService.i18n.t("close_registration")}
</option> </option>
<option value={"Open"}>{i18n.t("open_registration")}</option>
<option value={"Closed"}>{i18n.t("close_registration")}</option>
</select> </select>
</div> </div>
</div> </div>
{this.state.siteForm.registration_mode == "RequireApplication" && ( {this.state.siteForm.registration_mode == "RequireApplication" && (
<div className="mb-3 row"> <div className="mb-3 row">
<label className="col-12 col-form-label"> <label className="col-12 col-form-label">
{i18n.t("application_questionnaire")} {I18NextService.i18n.t("application_questionnaire")}
</label> </label>
<div className="col-12"> <div className="col-12">
<MarkdownTextArea <MarkdownTextArea
@ -312,7 +319,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
className="form-check-label" className="form-check-label"
htmlFor="create-site-community-creation-admin-only" htmlFor="create-site-community-creation-admin-only"
> >
{i18n.t("community_creation_admin_only")} {I18NextService.i18n.t("community_creation_admin_only")}
</label> </label>
</div> </div>
</div> </div>
@ -334,7 +341,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
className="form-check-label" className="form-check-label"
htmlFor="create-site-require-email-verification" htmlFor="create-site-require-email-verification"
> >
{i18n.t("require_email_verification")} {I18NextService.i18n.t("require_email_verification")}
</label> </label>
</div> </div>
</div> </div>
@ -356,7 +363,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
className="form-check-label" className="form-check-label"
htmlFor="create-site-email-admins" htmlFor="create-site-email-admins"
> >
{i18n.t("application_email_admins")} {I18NextService.i18n.t("application_email_admins")}
</label> </label>
</div> </div>
</div> </div>
@ -375,7 +382,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
className="form-check-label" className="form-check-label"
htmlFor="create-site-reports-email-admins" htmlFor="create-site-reports-email-admins"
> >
{i18n.t("reports_email_admins")} {I18NextService.i18n.t("reports_email_admins")}
</label> </label>
</div> </div>
</div> </div>
@ -386,7 +393,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
className="form-check-label me-2" className="form-check-label me-2"
htmlFor="create-site-default-theme" htmlFor="create-site-default-theme"
> >
{i18n.t("theme")} {I18NextService.i18n.t("theme")}
</label> </label>
<select <select
id="create-site-default-theme" id="create-site-default-theme"
@ -394,7 +401,9 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
onChange={linkEvent(this, this.handleSiteDefaultTheme)} onChange={linkEvent(this, this.handleSiteDefaultTheme)}
className="form-select d-inline-block w-auto" className="form-select d-inline-block w-auto"
> >
<option value="browser">{i18n.t("browser_default")}</option> <option value="browser">
{I18NextService.i18n.t("browser_default")}
</option>
{this.props.themeList?.map(theme => ( {this.props.themeList?.map(theme => (
<option key={theme} value={theme}> <option key={theme} value={theme}>
{theme} {theme}
@ -406,7 +415,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
{this.props.showLocal && ( {this.props.showLocal && (
<form className="mb-3 row"> <form className="mb-3 row">
<label className="col-sm-3 col-form-label"> <label className="col-sm-3 col-form-label">
{i18n.t("listing_type")} {I18NextService.i18n.t("listing_type")}
</label> </label>
<div className="col-sm-9"> <div className="col-sm-9">
<ListingTypeSelect <ListingTypeSelect
@ -432,7 +441,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
className="form-check-label" className="form-check-label"
htmlFor="create-site-private-instance" htmlFor="create-site-private-instance"
> >
{i18n.t("private_instance")} {I18NextService.i18n.t("private_instance")}
</label> </label>
</div> </div>
</div> </div>
@ -451,7 +460,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
className="form-check-label" className="form-check-label"
htmlFor="create-site-hide-modlog-mod-names" htmlFor="create-site-hide-modlog-mod-names"
> >
{i18n.t("hide_modlog_mod_names")} {I18NextService.i18n.t("hide_modlog_mod_names")}
</label> </label>
</div> </div>
</div> </div>
@ -461,7 +470,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
className="col-12 col-form-label" className="col-12 col-form-label"
htmlFor="create-site-slur-filter-regex" htmlFor="create-site-slur-filter-regex"
> >
{i18n.t("slur_filter_regex")} {I18NextService.i18n.t("slur_filter_regex")}
</label> </label>
<div className="col-12"> <div className="col-12">
<input <input
@ -488,7 +497,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
className="col-12 col-form-label" className="col-12 col-form-label"
htmlFor="create-site-actor-name" htmlFor="create-site-actor-name"
> >
{i18n.t("actor_name_max_length")} {I18NextService.i18n.t("actor_name_max_length")}
</label> </label>
<div className="col-12"> <div className="col-12">
<input <input
@ -515,7 +524,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
className="form-check-label" className="form-check-label"
htmlFor="create-site-federation-enabled" htmlFor="create-site-federation-enabled"
> >
{i18n.t("federation_enabled")} {I18NextService.i18n.t("federation_enabled")}
</label> </label>
</div> </div>
</div> </div>
@ -540,7 +549,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
className="form-check-label" className="form-check-label"
htmlFor="create-site-federation-debug" htmlFor="create-site-federation-debug"
> >
{i18n.t("federation_debug")} {I18NextService.i18n.t("federation_debug")}
</label> </label>
</div> </div>
</div> </div>
@ -550,7 +559,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
className="col-12 col-form-label" className="col-12 col-form-label"
htmlFor="create-site-federation-worker-count" htmlFor="create-site-federation-worker-count"
> >
{i18n.t("federation_worker_count")} {I18NextService.i18n.t("federation_worker_count")}
</label> </label>
<div className="col-12"> <div className="col-12">
<input <input
@ -582,7 +591,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
className="form-check-label" className="form-check-label"
htmlFor="create-site-captcha-enabled" htmlFor="create-site-captcha-enabled"
> >
{i18n.t("captcha_enabled")} {I18NextService.i18n.t("captcha_enabled")}
</label> </label>
</div> </div>
</div> </div>
@ -594,7 +603,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
className="form-check-label me-2" className="form-check-label me-2"
htmlFor="create-site-captcha-difficulty" htmlFor="create-site-captcha-difficulty"
> >
{i18n.t("captcha_difficulty")} {I18NextService.i18n.t("captcha_difficulty")}
</label> </label>
<select <select
id="create-site-captcha-difficulty" id="create-site-captcha-difficulty"
@ -602,9 +611,11 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
onChange={linkEvent(this, this.handleSiteCaptchaDifficulty)} onChange={linkEvent(this, this.handleSiteCaptchaDifficulty)}
className="form-select d-inline-block w-auto" className="form-select d-inline-block w-auto"
> >
<option value="easy">{i18n.t("easy")}</option> <option value="easy">{I18NextService.i18n.t("easy")}</option>
<option value="medium">{i18n.t("medium")}</option> <option value="medium">
<option value="hard">{i18n.t("hard")}</option> {I18NextService.i18n.t("medium")}
</option>
<option value="hard">{I18NextService.i18n.t("hard")}</option>
</select> </select>
</div> </div>
</div> </div>
@ -619,9 +630,9 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
{this.props.loading ? ( {this.props.loading ? (
<Spinner /> <Spinner />
) : siteSetup ? ( ) : siteSetup ? (
capitalizeFirstLetter(i18n.t("save")) capitalizeFirstLetter(I18NextService.i18n.t("save"))
) : ( ) : (
capitalizeFirstLetter(i18n.t("create")) capitalizeFirstLetter(I18NextService.i18n.t("create"))
)} )}
</button> </button>
</div> </div>
@ -637,7 +648,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
return ( return (
<div className="col-12 col-md-6"> <div className="col-12 col-md-6">
<label className="col-form-label" htmlFor={id}> <label className="col-form-label" htmlFor={id}>
{i18n.t(key)} {I18NextService.i18n.t(key)}
</label> </label>
<div className="d-flex justify-content-between align-items-center"> <div className="d-flex justify-content-between align-items-center">
<input <input

View file

@ -1,7 +1,7 @@
import { Component, linkEvent } from "inferno"; import { Component, linkEvent } from "inferno";
import { PersonView, Site, SiteAggregates } from "lemmy-js-client"; import { PersonView, Site, SiteAggregates } from "lemmy-js-client";
import { i18n } from "../../i18next"; import { mdToHtml } from "../../markdown";
import { mdToHtml } from "../../utils"; import { I18NextService } from "../../services";
import { Badges } from "../common/badges"; import { Badges } from "../common/badges";
import { BannerIconHeader } from "../common/banner-icon-header"; import { BannerIconHeader } from "../common/banner-icon-header";
import { Icon } from "../common/icon"; import { Icon } from "../common/icon";
@ -62,10 +62,14 @@ export class SiteSidebar extends Component<SiteSidebarProps, SiteSidebarState> {
className="btn btn-sm" className="btn btn-sm"
onClick={linkEvent(this, this.handleCollapseSidebar)} onClick={linkEvent(this, this.handleCollapseSidebar)}
aria-label={ aria-label={
this.state.collapsed ? i18n.t("expand") : i18n.t("collapse") this.state.collapsed
? I18NextService.i18n.t("expand")
: I18NextService.i18n.t("collapse")
} }
data-tippy-content={ data-tippy-content={
this.state.collapsed ? i18n.t("expand") : i18n.t("collapse") this.state.collapsed
? I18NextService.i18n.t("expand")
: I18NextService.i18n.t("collapse")
} }
data-bs-toggle="collapse" data-bs-toggle="collapse"
data-bs-target="#sidebarInfoBody" data-bs-target="#sidebarInfoBody"
@ -104,7 +108,7 @@ export class SiteSidebar extends Component<SiteSidebarProps, SiteSidebarState> {
admins(admins: PersonView[]) { admins(admins: PersonView[]) {
return ( return (
<ul className="mt-1 list-inline small mb-0"> <ul className="mt-1 list-inline small mb-0">
<li className="list-inline-item">{i18n.t("admins")}:</li> <li className="list-inline-item">{I18NextService.i18n.t("admins")}:</li>
{admins.map(av => ( {admins.map(av => (
<li key={av.person.id} className="list-inline-item"> <li key={av.person.id} className="list-inline-item">
<PersonListing person={av.person} /> <PersonListing person={av.person} />

View file

@ -1,7 +1,8 @@
import { myAuthRequired } from "@utils/app";
import { capitalizeFirstLetter } from "@utils/helpers";
import { Component, InfernoMouseEvent, linkEvent } from "inferno"; import { Component, InfernoMouseEvent, linkEvent } from "inferno";
import { EditSite, Tagline } from "lemmy-js-client"; import { EditSite, Tagline } from "lemmy-js-client";
import { i18n } from "../../i18next"; import { I18NextService } from "../../services";
import { capitalizeFirstLetter, myAuthRequired } from "../../utils";
import { HtmlTags } from "../common/html-tags"; import { HtmlTags } from "../common/html-tags";
import { Icon, Spinner } from "../common/icon"; import { Icon, Spinner } from "../common/icon";
import { MarkdownTextArea } from "../common/markdown-textarea"; import { MarkdownTextArea } from "../common/markdown-textarea";
@ -26,7 +27,7 @@ export class TaglineForm extends Component<TaglineFormProps, TaglineFormState> {
super(props, context); super(props, context);
} }
get documentTitle(): string { get documentTitle(): string {
return i18n.t("taglines"); return I18NextService.i18n.t("taglines");
} }
render() { render() {
@ -36,7 +37,7 @@ export class TaglineForm extends Component<TaglineFormProps, TaglineFormState> {
title={this.documentTitle} title={this.documentTitle}
path={this.context.router.route.match.url} path={this.context.router.route.match.url}
/> />
<h5 className="col-12">{i18n.t("taglines")}</h5> <h5 className="col-12">{I18NextService.i18n.t("taglines")}</h5>
<div className="table-responsive col-12"> <div className="table-responsive col-12">
<table id="taglines_table" className="table table-sm table-hover"> <table id="taglines_table" className="table table-sm table-hover">
<thead className="pointer"> <thead className="pointer">
@ -67,8 +68,8 @@ export class TaglineForm extends Component<TaglineFormProps, TaglineFormState> {
{ i: this, index: index }, { i: this, index: index },
this.handleEditTaglineClick this.handleEditTaglineClick
)} )}
data-tippy-content={i18n.t("edit")} data-tippy-content={I18NextService.i18n.t("edit")}
aria-label={i18n.t("edit")} aria-label={I18NextService.i18n.t("edit")}
> >
<Icon icon="edit" classes={`icon-inline`} /> <Icon icon="edit" classes={`icon-inline`} />
</button> </button>
@ -79,8 +80,8 @@ export class TaglineForm extends Component<TaglineFormProps, TaglineFormState> {
{ i: this, index: index }, { i: this, index: index },
this.handleDeleteTaglineClick this.handleDeleteTaglineClick
)} )}
data-tippy-content={i18n.t("delete")} data-tippy-content={I18NextService.i18n.t("delete")}
aria-label={i18n.t("delete")} aria-label={I18NextService.i18n.t("delete")}
> >
<Icon icon="trash" classes={`icon-inline text-danger`} /> <Icon icon="trash" classes={`icon-inline text-danger`} />
</button> </button>
@ -95,7 +96,7 @@ export class TaglineForm extends Component<TaglineFormProps, TaglineFormState> {
className="btn btn-sm btn-secondary me-2" className="btn btn-sm btn-secondary me-2"
onClick={linkEvent(this, this.handleAddTaglineClick)} onClick={linkEvent(this, this.handleAddTaglineClick)}
> >
{i18n.t("add_tagline")} {I18NextService.i18n.t("add_tagline")}
</button> </button>
</div> </div>
</div> </div>
@ -110,7 +111,7 @@ export class TaglineForm extends Component<TaglineFormProps, TaglineFormState> {
{this.props.loading ? ( {this.props.loading ? (
<Spinner /> <Spinner />
) : ( ) : (
capitalizeFirstLetter(i18n.t("save")) capitalizeFirstLetter(I18NextService.i18n.t("save"))
)} )}
</button> </button>
</div> </div>

View file

@ -1,6 +1,20 @@
import { debounce, getQueryParams, getQueryString } from "@utils/helpers"; import {
fetchUsers,
getUpdatedSearchId,
myAuth,
personToChoice,
setIsoData,
} from "@utils/app";
import {
debounce,
getIdFromString,
getPageFromString,
getQueryParams,
getQueryString,
} from "@utils/helpers";
import { amAdmin, amMod } from "@utils/roles"; import { amAdmin, amMod } from "@utils/roles";
import type { QueryParams } from "@utils/types"; import type { QueryParams } from "@utils/types";
import { Choice, RouteDataResponse } from "@utils/types";
import { NoOptionI18nKeys } from "i18next"; import { NoOptionI18nKeys } from "i18next";
import { Component, linkEvent } from "inferno"; import { Component, linkEvent } from "inferno";
import { T } from "inferno-i18next-dess"; import { T } from "inferno-i18next-dess";
@ -31,22 +45,10 @@ import {
Person, Person,
} from "lemmy-js-client"; } from "lemmy-js-client";
import moment from "moment"; import moment from "moment";
import { i18n } from "../i18next"; import { fetchLimit } from "../config";
import { InitialFetchRequest } from "../interfaces"; import { InitialFetchRequest } from "../interfaces";
import { FirstLoadService } from "../services/FirstLoadService"; import { FirstLoadService, I18NextService } from "../services";
import { HttpService, RequestState } from "../services/HttpService"; import { HttpService, RequestState } from "../services/HttpService";
import {
Choice,
RouteDataResponse,
fetchLimit,
fetchUsers,
getIdFromString,
getPageFromString,
getUpdatedSearchId,
myAuth,
personToChoice,
setIsoData,
} from "../utils";
import { HtmlTags } from "./common/html-tags"; import { HtmlTags } from "./common/html-tags";
import { Icon, Spinner } from "./common/icon"; import { Icon, Spinner } from "./common/icon";
import { MomentTime } from "./common/moment-time"; import { MomentTime } from "./common/moment-time";
@ -583,14 +585,14 @@ const Filter = ({
}) => ( }) => (
<div className="col-sm-6 mb-3"> <div className="col-sm-6 mb-3">
<label className="mb-2" htmlFor={`filter-${filterType}`}> <label className="mb-2" htmlFor={`filter-${filterType}`}>
{i18n.t(`filter_by_${filterType}` as NoOptionI18nKeys)} {I18NextService.i18n.t(`filter_by_${filterType}` as NoOptionI18nKeys)}
</label> </label>
<SearchableSelect <SearchableSelect
id={`filter-${filterType}`} id={`filter-${filterType}`}
value={value ?? 0} value={value ?? 0}
options={[ options={[
{ {
label: i18n.t("all"), label: I18NextService.i18n.t("all"),
value: "0", value: "0",
}, },
].concat(options)} ].concat(options)}
@ -721,8 +723,8 @@ export class Modlog extends Component<
this.isoData.site_res.admins.some( this.isoData.site_res.admins.some(
({ person: { id } }) => id === person.id ({ person: { id } }) => id === person.id
) )
? i18n.t("admin") ? I18NextService.i18n.t("admin")
: i18n.t("mod"); : I18NextService.i18n.t("mod");
} }
get documentTitle(): string { get documentTitle(): string {
@ -767,7 +769,7 @@ export class Modlog extends Component<
> >
/c/{this.state.communityRes.data.community_view.community.name}{" "} /c/{this.state.communityRes.data.community_view.community.name}{" "}
</Link> </Link>
<span>{i18n.t("modlog")}</span> <span>{I18NextService.i18n.t("modlog")}</span>
</h5> </h5>
)} )}
<div className="row mb-2"> <div className="row mb-2">
@ -779,9 +781,9 @@ export class Modlog extends Component<
aria-label="action" aria-label="action"
> >
<option disabled aria-hidden="true"> <option disabled aria-hidden="true">
{i18n.t("filter_by_action")} {I18NextService.i18n.t("filter_by_action")}
</option> </option>
<option value={"All"}>{i18n.t("all")}</option> <option value={"All"}>{I18NextService.i18n.t("all")}</option>
<option value={"ModRemovePost"}>Removing Posts</option> <option value={"ModRemovePost"}>Removing Posts</option>
<option value={"ModLockPost"}>Locking Posts</option> <option value={"ModLockPost"}>Locking Posts</option>
<option value={"ModFeaturePost"}>Featuring Posts</option> <option value={"ModFeaturePost"}>Featuring Posts</option>
@ -845,9 +847,9 @@ export class Modlog extends Component<
<table id="modlog_table" className="table table-sm table-hover"> <table id="modlog_table" className="table table-sm table-hover">
<thead className="pointer"> <thead className="pointer">
<tr> <tr>
<th> {i18n.t("time")}</th> <th> {I18NextService.i18n.t("time")}</th>
<th>{i18n.t("mod")}</th> <th>{I18NextService.i18n.t("mod")}</th>
<th>{i18n.t("action")}</th> <th>{I18NextService.i18n.t("action")}</th>
</tr> </tr>
</thead> </thead>
{this.combined} {this.combined}

View file

@ -1,5 +1,5 @@
import { Component } from "inferno"; import { Component } from "inferno";
import { i18n } from "../../i18next"; import { I18NextService } from "../../services";
import { Icon } from "../common/icon"; import { Icon } from "../common/icon";
interface CakeDayProps { interface CakeDayProps {
@ -19,6 +19,8 @@ export class CakeDay extends Component<CakeDayProps, any> {
} }
cakeDayTippy(): string { cakeDayTippy(): string {
return i18n.t("cake_day_info", { creator_name: this.props.creatorName }); return I18NextService.i18n.t("cake_day_info", {
creator_name: this.props.creatorName,
});
} }
} }

View file

@ -1,3 +1,17 @@
import {
commentsToFlatNodes,
editCommentReply,
editMention,
editPrivateMessage,
editWith,
enableDownvotes,
getCommentParentId,
myAuth,
myAuthRequired,
setIsoData,
updatePersonBlock,
} from "@utils/app";
import { RouteDataResponse } from "@utils/types";
import { Component, linkEvent } from "inferno"; import { Component, linkEvent } from "inferno";
import { import {
AddAdmin, AddAdmin,
@ -44,28 +58,11 @@ import {
SaveComment, SaveComment,
TransferCommunity, TransferCommunity,
} from "lemmy-js-client"; } from "lemmy-js-client";
import { i18n } from "../../i18next"; import { fetchLimit, relTags } from "../../config";
import { CommentViewType, InitialFetchRequest } from "../../interfaces"; import { CommentViewType, InitialFetchRequest } from "../../interfaces";
import { UserService } from "../../services"; import { FirstLoadService, I18NextService, UserService } from "../../services";
import { FirstLoadService } from "../../services/FirstLoadService";
import { HttpService, RequestState } from "../../services/HttpService"; import { HttpService, RequestState } from "../../services/HttpService";
import { import { toast } from "../../toast";
RouteDataResponse,
commentsToFlatNodes,
editCommentReply,
editMention,
editPrivateMessage,
editWith,
enableDownvotes,
fetchLimit,
getCommentParentId,
myAuth,
myAuthRequired,
relTags,
setIsoData,
toast,
updatePersonBlock,
} from "../../utils";
import { CommentNodes } from "../comment/comment-nodes"; import { CommentNodes } from "../comment/comment-nodes";
import { CommentSortSelect } from "../common/comment-sort-select"; import { CommentSortSelect } from "../common/comment-sort-select";
import { HtmlTags } from "../common/html-tags"; import { HtmlTags } from "../common/html-tags";
@ -188,9 +185,9 @@ export class Inbox extends Component<any, InboxState> {
get documentTitle(): string { get documentTitle(): string {
const mui = UserService.Instance.myUserInfo; const mui = UserService.Instance.myUserInfo;
return mui return mui
? `@${mui.local_user_view.person.name} ${i18n.t("inbox")} - ${ ? `@${mui.local_user_view.person.name} ${I18NextService.i18n.t(
this.state.siteRes.site_view.site.name "inbox"
}` )} - ${this.state.siteRes.site_view.site.name}`
: ""; : "";
} }
@ -224,7 +221,7 @@ export class Inbox extends Component<any, InboxState> {
path={this.context.router.route.match.url} path={this.context.router.route.match.url}
/> />
<h5 className="mb-2"> <h5 className="mb-2">
{i18n.t("inbox")} {I18NextService.i18n.t("inbox")}
{inboxRss && ( {inboxRss && (
<small> <small>
<a href={inboxRss} title="RSS" rel={relTags}> <a href={inboxRss} title="RSS" rel={relTags}>
@ -246,7 +243,7 @@ export class Inbox extends Component<any, InboxState> {
{this.state.markAllAsReadRes.state == "loading" ? ( {this.state.markAllAsReadRes.state == "loading" ? (
<Spinner /> <Spinner />
) : ( ) : (
i18n.t("mark_all_as_read") I18NextService.i18n.t("mark_all_as_read")
)} )}
</button> </button>
)} )}
@ -297,7 +294,7 @@ export class Inbox extends Component<any, InboxState> {
checked={this.state.unreadOrAll == UnreadOrAll.Unread} checked={this.state.unreadOrAll == UnreadOrAll.Unread}
onChange={linkEvent(this, this.handleUnreadOrAllChange)} onChange={linkEvent(this, this.handleUnreadOrAllChange)}
/> />
{i18n.t("unread")} {I18NextService.i18n.t("unread")}
</label> </label>
<label <label
className={`btn btn-outline-secondary pointer className={`btn btn-outline-secondary pointer
@ -311,7 +308,7 @@ export class Inbox extends Component<any, InboxState> {
checked={this.state.unreadOrAll == UnreadOrAll.All} checked={this.state.unreadOrAll == UnreadOrAll.All}
onChange={linkEvent(this, this.handleUnreadOrAllChange)} onChange={linkEvent(this, this.handleUnreadOrAllChange)}
/> />
{i18n.t("all")} {I18NextService.i18n.t("all")}
</label> </label>
</div> </div>
); );
@ -332,7 +329,7 @@ export class Inbox extends Component<any, InboxState> {
checked={this.state.messageType == MessageType.All} checked={this.state.messageType == MessageType.All}
onChange={linkEvent(this, this.handleMessageTypeChange)} onChange={linkEvent(this, this.handleMessageTypeChange)}
/> />
{i18n.t("all")} {I18NextService.i18n.t("all")}
</label> </label>
<label <label
className={`btn btn-outline-secondary pointer className={`btn btn-outline-secondary pointer
@ -346,7 +343,7 @@ export class Inbox extends Component<any, InboxState> {
checked={this.state.messageType == MessageType.Replies} checked={this.state.messageType == MessageType.Replies}
onChange={linkEvent(this, this.handleMessageTypeChange)} onChange={linkEvent(this, this.handleMessageTypeChange)}
/> />
{i18n.t("replies")} {I18NextService.i18n.t("replies")}
</label> </label>
<label <label
className={`btn btn-outline-secondary pointer className={`btn btn-outline-secondary pointer
@ -360,7 +357,7 @@ export class Inbox extends Component<any, InboxState> {
checked={this.state.messageType == MessageType.Mentions} checked={this.state.messageType == MessageType.Mentions}
onChange={linkEvent(this, this.handleMessageTypeChange)} onChange={linkEvent(this, this.handleMessageTypeChange)}
/> />
{i18n.t("mentions")} {I18NextService.i18n.t("mentions")}
</label> </label>
<label <label
className={`btn btn-outline-secondary pointer className={`btn btn-outline-secondary pointer
@ -374,7 +371,7 @@ export class Inbox extends Component<any, InboxState> {
checked={this.state.messageType == MessageType.Messages} checked={this.state.messageType == MessageType.Messages}
onChange={linkEvent(this, this.handleMessageTypeChange)} onChange={linkEvent(this, this.handleMessageTypeChange)}
/> />
{i18n.t("messages")} {I18NextService.i18n.t("messages")}
</label> </label>
</div> </div>
); );
@ -827,7 +824,7 @@ export class Inbox extends Component<any, InboxState> {
const res = await HttpService.client.createComment(form); const res = await HttpService.client.createComment(form);
if (res.state === "success") { if (res.state === "success") {
toast(i18n.t("reply_sent")); toast(I18NextService.i18n.t("reply_sent"));
this.findAndUpdateComment(res); this.findAndUpdateComment(res);
} }
@ -838,7 +835,7 @@ export class Inbox extends Component<any, InboxState> {
const res = await HttpService.client.editComment(form); const res = await HttpService.client.editComment(form);
if (res.state === "success") { if (res.state === "success") {
toast(i18n.t("edit")); toast(I18NextService.i18n.t("edit"));
this.findAndUpdateComment(res); this.findAndUpdateComment(res);
} else if (res.state === "failed") { } else if (res.state === "failed") {
toast(res.msg, "danger"); toast(res.msg, "danger");
@ -850,7 +847,7 @@ export class Inbox extends Component<any, InboxState> {
async handleDeleteComment(form: DeleteComment) { async handleDeleteComment(form: DeleteComment) {
const res = await HttpService.client.deleteComment(form); const res = await HttpService.client.deleteComment(form);
if (res.state == "success") { if (res.state == "success") {
toast(i18n.t("deleted")); toast(I18NextService.i18n.t("deleted"));
this.findAndUpdateComment(res); this.findAndUpdateComment(res);
} }
} }
@ -858,7 +855,7 @@ export class Inbox extends Component<any, InboxState> {
async handleRemoveComment(form: RemoveComment) { async handleRemoveComment(form: RemoveComment) {
const res = await HttpService.client.removeComment(form); const res = await HttpService.client.removeComment(form);
if (res.state == "success") { if (res.state == "success") {
toast(i18n.t("remove_comment")); toast(I18NextService.i18n.t("remove_comment"));
this.findAndUpdateComment(res); this.findAndUpdateComment(res);
} }
} }
@ -893,7 +890,7 @@ export class Inbox extends Component<any, InboxState> {
async handleTransferCommunity(form: TransferCommunity) { async handleTransferCommunity(form: TransferCommunity) {
await HttpService.client.transferCommunity(form); await HttpService.client.transferCommunity(form);
toast(i18n.t("transfer_community")); toast(I18NextService.i18n.t("transfer_community"));
} }
async handleCommentReplyRead(form: MarkCommentReplyAsRead) { async handleCommentReplyRead(form: MarkCommentReplyAsRead) {
@ -1005,7 +1002,7 @@ export class Inbox extends Component<any, InboxState> {
purgeItem(purgeRes: RequestState<PurgeItemResponse>) { purgeItem(purgeRes: RequestState<PurgeItemResponse>) {
if (purgeRes.state == "success") { if (purgeRes.state == "success") {
toast(i18n.t("purge_success")); toast(I18NextService.i18n.t("purge_success"));
this.context.router.history.push(`/`); this.context.router.history.push(`/`);
} }
} }
@ -1014,7 +1011,7 @@ export class Inbox extends Component<any, InboxState> {
res: RequestState<PrivateMessageReportResponse | CommentReportResponse> res: RequestState<PrivateMessageReportResponse | CommentReportResponse>
) { ) {
if (res.state == "success") { if (res.state == "success") {
toast(i18n.t("report_created")); toast(I18NextService.i18n.t("report_created"));
} }
} }

View file

@ -1,9 +1,9 @@
import { myAuth, setIsoData } from "@utils/app";
import { capitalizeFirstLetter } from "@utils/helpers";
import { Component, linkEvent } from "inferno"; import { Component, linkEvent } from "inferno";
import { GetSiteResponse, LoginResponse } from "lemmy-js-client"; import { GetSiteResponse, LoginResponse } from "lemmy-js-client";
import { i18n } from "../../i18next"; import { HttpService, I18NextService, UserService } from "../../services";
import { HttpService, UserService } from "../../services";
import { RequestState } from "../../services/HttpService"; import { RequestState } from "../../services/HttpService";
import { capitalizeFirstLetter, myAuth, setIsoData } from "../../utils";
import { HtmlTags } from "../common/html-tags"; import { HtmlTags } from "../common/html-tags";
import { Spinner } from "../common/icon"; import { Spinner } from "../common/icon";
@ -33,7 +33,7 @@ export class PasswordChange extends Component<any, State> {
} }
get documentTitle(): string { get documentTitle(): string {
return `${i18n.t("password_change")} - ${ return `${I18NextService.i18n.t("password_change")} - ${
this.state.siteRes.site_view.site.name this.state.siteRes.site_view.site.name
}`; }`;
} }
@ -47,7 +47,7 @@ export class PasswordChange extends Component<any, State> {
/> />
<div className="row"> <div className="row">
<div className="col-12 col-lg-6 offset-lg-3 mb-4"> <div className="col-12 col-lg-6 offset-lg-3 mb-4">
<h5>{i18n.t("password_change")}</h5> <h5>{I18NextService.i18n.t("password_change")}</h5>
{this.passwordChangeForm()} {this.passwordChangeForm()}
</div> </div>
</div> </div>
@ -60,7 +60,7 @@ export class PasswordChange extends Component<any, State> {
<form onSubmit={linkEvent(this, this.handlePasswordChangeSubmit)}> <form onSubmit={linkEvent(this, this.handlePasswordChangeSubmit)}>
<div className="mb-3 row"> <div className="mb-3 row">
<label className="col-sm-2 col-form-label" htmlFor="new-password"> <label className="col-sm-2 col-form-label" htmlFor="new-password">
{i18n.t("new_password")} {I18NextService.i18n.t("new_password")}
</label> </label>
<div className="col-sm-10"> <div className="col-sm-10">
<input <input
@ -76,7 +76,7 @@ export class PasswordChange extends Component<any, State> {
</div> </div>
<div className="mb-3 row"> <div className="mb-3 row">
<label className="col-sm-2 col-form-label" htmlFor="verify-password"> <label className="col-sm-2 col-form-label" htmlFor="verify-password">
{i18n.t("verify_password")} {I18NextService.i18n.t("verify_password")}
</label> </label>
<div className="col-sm-10"> <div className="col-sm-10">
<input <input
@ -96,7 +96,7 @@ export class PasswordChange extends Component<any, State> {
{this.state.passwordChangeRes.state == "loading" ? ( {this.state.passwordChangeRes.state == "loading" ? (
<Spinner /> <Spinner />
) : ( ) : (
capitalizeFirstLetter(i18n.t("save")) capitalizeFirstLetter(I18NextService.i18n.t("save"))
)} )}
</button> </button>
</div> </div>

View file

@ -1,3 +1,4 @@
import { commentsToFlatNodes } from "@utils/app";
import { Component } from "inferno"; import { Component } from "inferno";
import { import {
AddAdmin, AddAdmin,
@ -37,7 +38,7 @@ import {
TransferCommunity, TransferCommunity,
} from "lemmy-js-client"; } from "lemmy-js-client";
import { CommentViewType, PersonDetailsView } from "../../interfaces"; import { CommentViewType, PersonDetailsView } from "../../interfaces";
import { commentsToFlatNodes, setupTippy } from "../../utils"; import { setupTippy } from "../../tippy";
import { CommentNodes } from "../comment/comment-nodes"; import { CommentNodes } from "../comment/comment-nodes";
import { Paginator } from "../common/paginator"; import { Paginator } from "../common/paginator";
import { PostListing } from "../post/post-listing"; import { PostListing } from "../post/post-listing";

View file

@ -1,8 +1,10 @@
import { showAvatars } from "@utils/app";
import { hostname, isCakeDay } from "@utils/helpers";
import classNames from "classnames"; import classNames from "classnames";
import { Component } from "inferno"; import { Component } from "inferno";
import { Link } from "inferno-router"; import { Link } from "inferno-router";
import { Person } from "lemmy-js-client"; import { Person } from "lemmy-js-client";
import { hostname, isCakeDay, relTags, showAvatars } from "../../utils"; import { relTags } from "../../config";
import { PictrsImage } from "../common/pictrs-image"; import { PictrsImage } from "../common/pictrs-image";
import { CakeDay } from "./cake-day"; import { CakeDay } from "./cake-day";

View file

@ -1,6 +1,27 @@
import { getQueryParams, getQueryString } from "@utils/helpers"; import {
editComment,
editPost,
editWith,
enableDownvotes,
enableNsfw,
getCommentParentId,
myAuth,
myAuthRequired,
setIsoData,
updatePersonBlock,
} from "@utils/app";
import { restoreScrollPosition, saveScrollPosition } from "@utils/browser";
import {
capitalizeFirstLetter,
futureDaysToUnixTime,
getPageFromString,
getQueryParams,
getQueryString,
numToSI,
} from "@utils/helpers";
import { canMod, isAdmin, isBanned } from "@utils/roles"; import { canMod, isAdmin, isBanned } from "@utils/roles";
import type { QueryParams } from "@utils/types"; import type { QueryParams } from "@utils/types";
import { RouteDataResponse } from "@utils/types";
import classNames from "classnames"; import classNames from "classnames";
import { NoOptionI18nKeys } from "i18next"; import { NoOptionI18nKeys } from "i18next";
import { Component, linkEvent } from "inferno"; import { Component, linkEvent } from "inferno";
@ -50,35 +71,13 @@ import {
TransferCommunity, TransferCommunity,
} from "lemmy-js-client"; } from "lemmy-js-client";
import moment from "moment"; import moment from "moment";
import { i18n } from "../../i18next"; import { fetchLimit, relTags } from "../../config";
import { InitialFetchRequest, PersonDetailsView } from "../../interfaces"; import { InitialFetchRequest, PersonDetailsView } from "../../interfaces";
import { UserService } from "../../services"; import { mdToHtml } from "../../markdown";
import { FirstLoadService } from "../../services/FirstLoadService"; import { FirstLoadService, I18NextService, UserService } from "../../services";
import { HttpService, RequestState } from "../../services/HttpService"; import { HttpService, RequestState } from "../../services/HttpService";
import { import { setupTippy } from "../../tippy";
RouteDataResponse, import { toast } from "../../toast";
capitalizeFirstLetter,
editComment,
editPost,
editWith,
enableDownvotes,
enableNsfw,
fetchLimit,
futureDaysToUnixTime,
getCommentParentId,
getPageFromString,
mdToHtml,
myAuth,
myAuthRequired,
numToSI,
relTags,
restoreScrollPosition,
saveScrollPosition,
setIsoData,
setupTippy,
toast,
updatePersonBlock,
} from "../../utils";
import { BannerIconHeader } from "../common/banner-icon-header"; import { BannerIconHeader } from "../common/banner-icon-header";
import { HtmlTags } from "../common/html-tags"; import { HtmlTags } from "../common/html-tags";
import { Icon, Spinner } from "../common/icon"; import { Icon, Spinner } from "../common/icon";
@ -136,7 +135,7 @@ const getCommunitiesListing = (
communityViews.length > 0 && ( communityViews.length > 0 && (
<div className="card border-secondary mb-3"> <div className="card border-secondary mb-3">
<div className="card-body"> <div className="card-body">
<h5>{i18n.t(translationKey)}</h5> <h5>{I18NextService.i18n.t(translationKey)}</h5>
<ul className="list-unstyled mb-0"> <ul className="list-unstyled mb-0">
{communityViews.map(({ community }) => ( {communityViews.map(({ community }) => (
<li key={community.id}> <li key={community.id}>
@ -421,7 +420,7 @@ export class Profile extends Component<
checked={active} checked={active}
onChange={linkEvent(this, this.handleViewChange)} onChange={linkEvent(this, this.handleViewChange)}
/> />
{i18n.t(view.toLowerCase() as NoOptionI18nKeys)} {I18NextService.i18n.t(view.toLowerCase() as NoOptionI18nKeys)}
</label> </label>
); );
} }
@ -484,22 +483,22 @@ export class Profile extends Component<
</li> </li>
{isBanned(pv.person) && ( {isBanned(pv.person) && (
<li className="list-inline-item badge text-bg-danger"> <li className="list-inline-item badge text-bg-danger">
{i18n.t("banned")} {I18NextService.i18n.t("banned")}
</li> </li>
)} )}
{pv.person.deleted && ( {pv.person.deleted && (
<li className="list-inline-item badge text-bg-danger"> <li className="list-inline-item badge text-bg-danger">
{i18n.t("deleted")} {I18NextService.i18n.t("deleted")}
</li> </li>
)} )}
{pv.person.admin && ( {pv.person.admin && (
<li className="list-inline-item badge text-bg-light"> <li className="list-inline-item badge text-bg-light">
{i18n.t("admin")} {I18NextService.i18n.t("admin")}
</li> </li>
)} )}
{pv.person.bot_account && ( {pv.person.bot_account && (
<li className="list-inline-item badge text-bg-light"> <li className="list-inline-item badge text-bg-light">
{i18n.t("bot_account").toLowerCase()} {I18NextService.i18n.t("bot_account").toLowerCase()}
</li> </li>
)} )}
</ul> </ul>
@ -515,7 +514,7 @@ export class Profile extends Component<
rel={relTags} rel={relTags}
href={`https://matrix.to/#/${pv.person.matrix_user_id}`} href={`https://matrix.to/#/${pv.person.matrix_user_id}`}
> >
{i18n.t("send_secure_message")} {I18NextService.i18n.t("send_secure_message")}
</a> </a>
<Link <Link
className={ className={
@ -523,7 +522,7 @@ export class Profile extends Component<
} }
to={`/create_private_message/${pv.person.id}`} to={`/create_private_message/${pv.person.id}`}
> >
{i18n.t("send_message")} {I18NextService.i18n.t("send_message")}
</Link> </Link>
{personBlocked ? ( {personBlocked ? (
<button <button
@ -535,7 +534,7 @@ export class Profile extends Component<
this.handleUnblockPerson this.handleUnblockPerson
)} )}
> >
{i18n.t("unblock_user")} {I18NextService.i18n.t("unblock_user")}
</button> </button>
) : ( ) : (
<button <button
@ -547,7 +546,7 @@ export class Profile extends Component<
this.handleBlockPerson this.handleBlockPerson
)} )}
> >
{i18n.t("block_user")} {I18NextService.i18n.t("block_user")}
</button> </button>
)} )}
</> </>
@ -562,9 +561,9 @@ export class Profile extends Component<
"d-flex align-self-start btn btn-secondary me-2" "d-flex align-self-start btn btn-secondary me-2"
} }
onClick={linkEvent(this, this.handleModBanShow)} onClick={linkEvent(this, this.handleModBanShow)}
aria-label={i18n.t("ban")} aria-label={I18NextService.i18n.t("ban")}
> >
{capitalizeFirstLetter(i18n.t("ban"))} {capitalizeFirstLetter(I18NextService.i18n.t("ban"))}
</button> </button>
) : ( ) : (
<button <button
@ -572,9 +571,9 @@ export class Profile extends Component<
"d-flex align-self-start btn btn-secondary me-2" "d-flex align-self-start btn btn-secondary me-2"
} }
onClick={linkEvent(this, this.handleModBanSubmit)} onClick={linkEvent(this, this.handleModBanSubmit)}
aria-label={i18n.t("unban")} aria-label={I18NextService.i18n.t("unban")}
> >
{capitalizeFirstLetter(i18n.t("unban"))} {capitalizeFirstLetter(I18NextService.i18n.t("unban"))}
</button> </button>
))} ))}
</div> </div>
@ -589,13 +588,13 @@ export class Profile extends Component<
<div> <div>
<ul className="list-inline mb-2"> <ul className="list-inline mb-2">
<li className="list-inline-item badge text-bg-light"> <li className="list-inline-item badge text-bg-light">
{i18n.t("number_of_posts", { {I18NextService.i18n.t("number_of_posts", {
count: Number(pv.counts.post_count), count: Number(pv.counts.post_count),
formattedCount: numToSI(pv.counts.post_count), formattedCount: numToSI(pv.counts.post_count),
})} })}
</li> </li>
<li className="list-inline-item badge text-bg-light"> <li className="list-inline-item badge text-bg-light">
{i18n.t("number_of_comments", { {I18NextService.i18n.t("number_of_comments", {
count: Number(pv.counts.comment_count), count: Number(pv.counts.comment_count),
formattedCount: numToSI(pv.counts.comment_count), formattedCount: numToSI(pv.counts.comment_count),
})} })}
@ -603,7 +602,7 @@ export class Profile extends Component<
</ul> </ul>
</div> </div>
<div className="text-muted"> <div className="text-muted">
{i18n.t("joined")}{" "} {I18NextService.i18n.t("joined")}{" "}
<MomentTime <MomentTime
published={pv.person.published} published={pv.person.published}
showAgo showAgo
@ -613,7 +612,7 @@ export class Profile extends Component<
<div className="d-flex align-items-center text-muted mb-2"> <div className="d-flex align-items-center text-muted mb-2">
<Icon icon="cake" /> <Icon icon="cake" />
<span className="ms-2"> <span className="ms-2">
{i18n.t("cake_day_title")}{" "} {I18NextService.i18n.t("cake_day_title")}{" "}
{moment {moment
.utc(pv.person.published) .utc(pv.person.published)
.local() .local()
@ -622,7 +621,7 @@ export class Profile extends Component<
</div> </div>
{!UserService.Instance.myUserInfo && ( {!UserService.Instance.myUserInfo && (
<div className="alert alert-info" role="alert"> <div className="alert alert-info" role="alert">
{i18n.t("profile_not_logged_in_alert")} {I18NextService.i18n.t("profile_not_logged_in_alert")}
</div> </div>
)} )}
</div> </div>
@ -640,24 +639,24 @@ export class Profile extends Component<
<form onSubmit={linkEvent(this, this.handleModBanSubmit)}> <form onSubmit={linkEvent(this, this.handleModBanSubmit)}>
<div className="mb-3 row col-12"> <div className="mb-3 row col-12">
<label className="col-form-label" htmlFor="profile-ban-reason"> <label className="col-form-label" htmlFor="profile-ban-reason">
{i18n.t("reason")} {I18NextService.i18n.t("reason")}
</label> </label>
<input <input
type="text" type="text"
id="profile-ban-reason" id="profile-ban-reason"
className="form-control me-2" className="form-control me-2"
placeholder={i18n.t("reason")} placeholder={I18NextService.i18n.t("reason")}
value={this.state.banReason} value={this.state.banReason}
onInput={linkEvent(this, this.handleModBanReasonChange)} onInput={linkEvent(this, this.handleModBanReasonChange)}
/> />
<label className="col-form-label" htmlFor={`mod-ban-expires`}> <label className="col-form-label" htmlFor={`mod-ban-expires`}>
{i18n.t("expires")} {I18NextService.i18n.t("expires")}
</label> </label>
<input <input
type="number" type="number"
id={`mod-ban-expires`} id={`mod-ban-expires`}
className="form-control me-2" className="form-control me-2"
placeholder={i18n.t("number_of_days")} placeholder={I18NextService.i18n.t("number_of_days")}
value={this.state.banExpireDays} value={this.state.banExpireDays}
onInput={linkEvent(this, this.handleModBanExpireDaysChange)} onInput={linkEvent(this, this.handleModBanExpireDaysChange)}
/> />
@ -673,9 +672,9 @@ export class Profile extends Component<
<label <label
className="form-check-label" className="form-check-label"
htmlFor="mod-ban-remove-data" htmlFor="mod-ban-remove-data"
title={i18n.t("remove_content_more")} title={I18NextService.i18n.t("remove_content_more")}
> >
{i18n.t("remove_content")} {I18NextService.i18n.t("remove_content")}
</label> </label>
</div> </div>
</div> </div>
@ -683,23 +682,23 @@ export class Profile extends Component<
{/* TODO hold off on expires until later */} {/* TODO hold off on expires until later */}
{/* <div class="mb-3 row"> */} {/* <div class="mb-3 row"> */}
{/* <label class="col-form-label">Expires</label> */} {/* <label class="col-form-label">Expires</label> */}
{/* <input type="date" class="form-control me-2" placeholder={i18n.t('expires')} value={this.state.banExpires} onInput={linkEvent(this, this.handleModBanExpiresChange)} /> */} {/* <input type="date" class="form-control me-2" placeholder={I18NextService.i18n.t('expires')} value={this.state.banExpires} onInput={linkEvent(this, this.handleModBanExpiresChange)} /> */}
{/* </div> */} {/* </div> */}
<div className="mb-3 row"> <div className="mb-3 row">
<button <button
type="reset" type="reset"
className="btn btn-secondary me-2" className="btn btn-secondary me-2"
aria-label={i18n.t("cancel")} aria-label={I18NextService.i18n.t("cancel")}
onClick={linkEvent(this, this.handleModBanSubmitCancel)} onClick={linkEvent(this, this.handleModBanSubmitCancel)}
> >
{i18n.t("cancel")} {I18NextService.i18n.t("cancel")}
</button> </button>
<button <button
type="submit" type="submit"
className="btn btn-secondary" className="btn btn-secondary"
aria-label={i18n.t("ban")} aria-label={I18NextService.i18n.t("ban")}
> >
{i18n.t("ban")} {pv.person.name} {I18NextService.i18n.t("ban")} {pv.person.name}
</button> </button>
</div> </div>
</form> </form>
@ -903,14 +902,14 @@ export class Profile extends Component<
async handleCommentReport(form: CreateCommentReport) { async handleCommentReport(form: CreateCommentReport) {
const reportRes = await HttpService.client.createCommentReport(form); const reportRes = await HttpService.client.createCommentReport(form);
if (reportRes.state === "success") { if (reportRes.state === "success") {
toast(i18n.t("report_created")); toast(I18NextService.i18n.t("report_created"));
} }
} }
async handlePostReport(form: CreatePostReport) { async handlePostReport(form: CreatePostReport) {
const reportRes = await HttpService.client.createPostReport(form); const reportRes = await HttpService.client.createPostReport(form);
if (reportRes.state === "success") { if (reportRes.state === "success") {
toast(i18n.t("report_created")); toast(I18NextService.i18n.t("report_created"));
} }
} }
@ -934,7 +933,7 @@ export class Profile extends Component<
async handleTransferCommunity(form: TransferCommunity) { async handleTransferCommunity(form: TransferCommunity) {
await HttpService.client.transferCommunity(form); await HttpService.client.transferCommunity(form);
toast(i18n.t("transfer_community")); toast(I18NextService.i18n.t("transfer_community"));
} }
async handleCommentReplyRead(form: MarkCommentReplyAsRead) { async handleCommentReplyRead(form: MarkCommentReplyAsRead) {
@ -998,7 +997,7 @@ export class Profile extends Component<
purgeItem(purgeRes: RequestState<PurgeItemResponse>) { purgeItem(purgeRes: RequestState<PurgeItemResponse>) {
if (purgeRes.state == "success") { if (purgeRes.state == "success") {
toast(i18n.t("purge_success")); toast(I18NextService.i18n.t("purge_success"));
this.context.router.history.push(`/`); this.context.router.history.push(`/`);
} }
} }

View file

@ -1,3 +1,9 @@
import {
editRegistrationApplication,
myAuthRequired,
setIsoData,
} from "@utils/app";
import { RouteDataResponse } from "@utils/types";
import { Component, linkEvent } from "inferno"; import { Component, linkEvent } from "inferno";
import { import {
ApproveRegistrationApplication, ApproveRegistrationApplication,
@ -5,19 +11,11 @@ import {
ListRegistrationApplicationsResponse, ListRegistrationApplicationsResponse,
RegistrationApplicationView, RegistrationApplicationView,
} from "lemmy-js-client"; } from "lemmy-js-client";
import { i18n } from "../../i18next"; import { fetchLimit } from "../../config";
import { InitialFetchRequest } from "../../interfaces"; import { InitialFetchRequest } from "../../interfaces";
import { UserService } from "../../services"; import { FirstLoadService, I18NextService, UserService } from "../../services";
import { FirstLoadService } from "../../services/FirstLoadService";
import { HttpService, RequestState } from "../../services/HttpService"; import { HttpService, RequestState } from "../../services/HttpService";
import { import { setupTippy } from "../../tippy";
RouteDataResponse,
editRegistrationApplication,
fetchLimit,
myAuthRequired,
setIsoData,
setupTippy,
} from "../../utils";
import { HtmlTags } from "../common/html-tags"; import { HtmlTags } from "../common/html-tags";
import { Spinner } from "../common/icon"; import { Spinner } from "../common/icon";
import { Paginator } from "../common/paginator"; import { Paginator } from "../common/paginator";
@ -79,7 +77,7 @@ export class RegistrationApplications extends Component<
get documentTitle(): string { get documentTitle(): string {
const mui = UserService.Instance.myUserInfo; const mui = UserService.Instance.myUserInfo;
return mui return mui
? `@${mui.local_user_view.person.name} ${i18n.t( ? `@${mui.local_user_view.person.name} ${I18NextService.i18n.t(
"registration_applications" "registration_applications"
)} - ${this.state.siteRes.site_view.site.name}` )} - ${this.state.siteRes.site_view.site.name}`
: ""; : "";
@ -102,7 +100,9 @@ export class RegistrationApplications extends Component<
title={this.documentTitle} title={this.documentTitle}
path={this.context.router.route.match.url} path={this.context.router.route.match.url}
/> />
<h5 className="mb-2">{i18n.t("registration_applications")}</h5> <h5 className="mb-2">
{I18NextService.i18n.t("registration_applications")}
</h5>
{this.selects()} {this.selects()}
{this.applicationList(apps)} {this.applicationList(apps)}
<Paginator <Paginator
@ -139,7 +139,7 @@ export class RegistrationApplications extends Component<
checked={this.state.unreadOrAll == UnreadOrAll.Unread} checked={this.state.unreadOrAll == UnreadOrAll.Unread}
onChange={linkEvent(this, this.handleUnreadOrAllChange)} onChange={linkEvent(this, this.handleUnreadOrAllChange)}
/> />
{i18n.t("unread")} {I18NextService.i18n.t("unread")}
</label> </label>
<label <label
className={`btn btn-outline-secondary pointer className={`btn btn-outline-secondary pointer
@ -153,7 +153,7 @@ export class RegistrationApplications extends Component<
checked={this.state.unreadOrAll == UnreadOrAll.All} checked={this.state.unreadOrAll == UnreadOrAll.All}
onChange={linkEvent(this, this.handleUnreadOrAllChange)} onChange={linkEvent(this, this.handleUnreadOrAllChange)}
/> />
{i18n.t("all")} {I18NextService.i18n.t("all")}
</label> </label>
</div> </div>
); );

View file

@ -1,4 +1,12 @@
import {
editCommentReport,
editPostReport,
editPrivateMessageReport,
myAuthRequired,
setIsoData,
} from "@utils/app";
import { amAdmin } from "@utils/roles"; import { amAdmin } from "@utils/roles";
import { RouteDataResponse } from "@utils/types";
import { Component, linkEvent } from "inferno"; import { Component, linkEvent } from "inferno";
import { import {
CommentReportResponse, CommentReportResponse,
@ -18,20 +26,15 @@ import {
ResolvePostReport, ResolvePostReport,
ResolvePrivateMessageReport, ResolvePrivateMessageReport,
} from "lemmy-js-client"; } from "lemmy-js-client";
import { i18n } from "../../i18next"; import { fetchLimit } from "../../config";
import { InitialFetchRequest } from "../../interfaces"; import { InitialFetchRequest } from "../../interfaces";
import { HttpService, UserService } from "../../services";
import { FirstLoadService } from "../../services/FirstLoadService";
import { RequestState } from "../../services/HttpService";
import { import {
RouteDataResponse, FirstLoadService,
editCommentReport, HttpService,
editPostReport, I18NextService,
editPrivateMessageReport, UserService,
fetchLimit, } from "../../services";
myAuthRequired, import { RequestState } from "../../services/HttpService";
setIsoData,
} from "../../utils";
import { CommentReport } from "../comment/comment-report"; import { CommentReport } from "../comment/comment-report";
import { HtmlTags } from "../common/html-tags"; import { HtmlTags } from "../common/html-tags";
import { Spinner } from "../common/icon"; import { Spinner } from "../common/icon";
@ -134,9 +137,9 @@ export class Reports extends Component<any, ReportsState> {
get documentTitle(): string { get documentTitle(): string {
const mui = UserService.Instance.myUserInfo; const mui = UserService.Instance.myUserInfo;
return mui return mui
? `@${mui.local_user_view.person.name} ${i18n.t("reports")} - ${ ? `@${mui.local_user_view.person.name} ${I18NextService.i18n.t(
this.state.siteRes.site_view.site.name "reports"
}` )} - ${this.state.siteRes.site_view.site.name}`
: ""; : "";
} }
@ -149,7 +152,7 @@ export class Reports extends Component<any, ReportsState> {
title={this.documentTitle} title={this.documentTitle}
path={this.context.router.route.match.url} path={this.context.router.route.match.url}
/> />
<h5 className="mb-2">{i18n.t("reports")}</h5> <h5 className="mb-2">{I18NextService.i18n.t("reports")}</h5>
{this.selects()} {this.selects()}
{this.section} {this.section}
<Paginator <Paginator
@ -198,7 +201,7 @@ export class Reports extends Component<any, ReportsState> {
checked={this.state.unreadOrAll == UnreadOrAll.Unread} checked={this.state.unreadOrAll == UnreadOrAll.Unread}
onChange={linkEvent(this, this.handleUnreadOrAllChange)} onChange={linkEvent(this, this.handleUnreadOrAllChange)}
/> />
{i18n.t("unread")} {I18NextService.i18n.t("unread")}
</label> </label>
<label <label
className={`btn btn-outline-secondary pointer className={`btn btn-outline-secondary pointer
@ -212,7 +215,7 @@ export class Reports extends Component<any, ReportsState> {
checked={this.state.unreadOrAll == UnreadOrAll.All} checked={this.state.unreadOrAll == UnreadOrAll.All}
onChange={linkEvent(this, this.handleUnreadOrAllChange)} onChange={linkEvent(this, this.handleUnreadOrAllChange)}
/> />
{i18n.t("all")} {I18NextService.i18n.t("all")}
</label> </label>
</div> </div>
); );
@ -233,7 +236,7 @@ export class Reports extends Component<any, ReportsState> {
checked={this.state.messageType == MessageType.All} checked={this.state.messageType == MessageType.All}
onChange={linkEvent(this, this.handleMessageTypeChange)} onChange={linkEvent(this, this.handleMessageTypeChange)}
/> />
{i18n.t("all")} {I18NextService.i18n.t("all")}
</label> </label>
<label <label
className={`btn btn-outline-secondary pointer className={`btn btn-outline-secondary pointer
@ -247,7 +250,7 @@ export class Reports extends Component<any, ReportsState> {
checked={this.state.messageType == MessageType.CommentReport} checked={this.state.messageType == MessageType.CommentReport}
onChange={linkEvent(this, this.handleMessageTypeChange)} onChange={linkEvent(this, this.handleMessageTypeChange)}
/> />
{i18n.t("comments")} {I18NextService.i18n.t("comments")}
</label> </label>
<label <label
className={`btn btn-outline-secondary pointer className={`btn btn-outline-secondary pointer
@ -261,7 +264,7 @@ export class Reports extends Component<any, ReportsState> {
checked={this.state.messageType == MessageType.PostReport} checked={this.state.messageType == MessageType.PostReport}
onChange={linkEvent(this, this.handleMessageTypeChange)} onChange={linkEvent(this, this.handleMessageTypeChange)}
/> />
{i18n.t("posts")} {I18NextService.i18n.t("posts")}
</label> </label>
{amAdmin() && ( {amAdmin() && (
<label <label
@ -281,7 +284,7 @@ export class Reports extends Component<any, ReportsState> {
} }
onChange={linkEvent(this, this.handleMessageTypeChange)} onChange={linkEvent(this, this.handleMessageTypeChange)}
/> />
{i18n.t("messages")} {I18NextService.i18n.t("messages")}
</label> </label>
)} )}
</div> </div>

View file

@ -1,4 +1,19 @@
import { debounce } from "@utils/helpers"; import {
communityToChoice,
fetchCommunities,
fetchThemeList,
fetchUsers,
myAuth,
myAuthRequired,
personToChoice,
setIsoData,
setTheme,
showLocal,
updateCommunityBlock,
updatePersonBlock,
} from "@utils/app";
import { capitalizeFirstLetter, debounce } from "@utils/helpers";
import { Choice } from "@utils/types";
import classNames from "classnames"; import classNames from "classnames";
import { NoOptionI18nKeys } from "i18next"; import { NoOptionI18nKeys } from "i18next";
import { Component, linkEvent } from "inferno"; import { Component, linkEvent } from "inferno";
@ -13,30 +28,12 @@ import {
PersonBlockView, PersonBlockView,
SortType, SortType,
} from "lemmy-js-client"; } from "lemmy-js-client";
import { i18n, languages } from "../../i18next"; import { elementUrl, emDash, relTags } from "../../config";
import { UserService } from "../../services"; import { UserService } from "../../services";
import { HttpService, RequestState } from "../../services/HttpService"; import { HttpService, RequestState } from "../../services/HttpService";
import { import { I18NextService, languages } from "../../services/I18NextService";
Choice, import { setupTippy } from "../../tippy";
capitalizeFirstLetter, import { toast } from "../../toast";
communityToChoice,
elementUrl,
emDash,
fetchCommunities,
fetchThemeList,
fetchUsers,
myAuth,
myAuthRequired,
personToChoice,
relTags,
setIsoData,
setTheme,
setupTippy,
showLocal,
toast,
updateCommunityBlock,
updatePersonBlock,
} from "../../utils";
import { HtmlTags } from "../common/html-tags"; import { HtmlTags } from "../common/html-tags";
import { Icon, Spinner } from "../common/icon"; import { Icon, Spinner } from "../common/icon";
import { ImageUploadForm } from "../common/image-upload-form"; import { ImageUploadForm } from "../common/image-upload-form";
@ -116,7 +113,7 @@ const Filter = ({
className="col-md-4 col-form-label" className="col-md-4 col-form-label"
htmlFor={`block-${filterType}-filter`} htmlFor={`block-${filterType}-filter`}
> >
{i18n.t(`block_${filterType}` as NoOptionI18nKeys)} {I18NextService.i18n.t(`block_${filterType}` as NoOptionI18nKeys)}
</label> </label>
<div className="col-md-8"> <div className="col-md-8">
<SearchableSelect <SearchableSelect
@ -236,7 +233,7 @@ export class Settings extends Component<any, SettingsState> {
} }
get documentTitle(): string { get documentTitle(): string {
return i18n.t("settings"); return I18NextService.i18n.t("settings");
} }
render() { render() {
@ -252,12 +249,12 @@ export class Settings extends Component<any, SettingsState> {
tabs={[ tabs={[
{ {
key: "settings", key: "settings",
label: i18n.t("settings"), label: I18NextService.i18n.t("settings"),
getNode: this.userSettings, getNode: this.userSettings,
}, },
{ {
key: "blocks", key: "blocks",
label: i18n.t("blocks"), label: I18NextService.i18n.t("blocks"),
getNode: this.blockCards, getNode: this.blockCards,
}, },
]} ]}
@ -319,11 +316,11 @@ export class Settings extends Component<any, SettingsState> {
changePasswordHtmlForm() { changePasswordHtmlForm() {
return ( return (
<> <>
<h5>{i18n.t("change_password")}</h5> <h5>{I18NextService.i18n.t("change_password")}</h5>
<form onSubmit={linkEvent(this, this.handleChangePasswordSubmit)}> <form onSubmit={linkEvent(this, this.handleChangePasswordSubmit)}>
<div className="mb-3 row"> <div className="mb-3 row">
<label className="col-sm-5 col-form-label" htmlFor="user-password"> <label className="col-sm-5 col-form-label" htmlFor="user-password">
{i18n.t("new_password")} {I18NextService.i18n.t("new_password")}
</label> </label>
<div className="col-sm-7"> <div className="col-sm-7">
<input <input
@ -342,7 +339,7 @@ export class Settings extends Component<any, SettingsState> {
className="col-sm-5 col-form-label" className="col-sm-5 col-form-label"
htmlFor="user-verify-password" htmlFor="user-verify-password"
> >
{i18n.t("verify_password")} {I18NextService.i18n.t("verify_password")}
</label> </label>
<div className="col-sm-7"> <div className="col-sm-7">
<input <input
@ -361,7 +358,7 @@ export class Settings extends Component<any, SettingsState> {
className="col-sm-5 col-form-label" className="col-sm-5 col-form-label"
htmlFor="user-old-password" htmlFor="user-old-password"
> >
{i18n.t("old_password")} {I18NextService.i18n.t("old_password")}
</label> </label>
<div className="col-sm-7"> <div className="col-sm-7">
<input <input
@ -383,7 +380,7 @@ export class Settings extends Component<any, SettingsState> {
{this.state.changePasswordRes.state === "loading" ? ( {this.state.changePasswordRes.state === "loading" ? (
<Spinner /> <Spinner />
) : ( ) : (
capitalizeFirstLetter(i18n.t("save")) capitalizeFirstLetter(I18NextService.i18n.t("save"))
)} )}
</button> </button>
</div> </div>
@ -412,7 +409,7 @@ export class Settings extends Component<any, SettingsState> {
blockedUsersList() { blockedUsersList() {
return ( return (
<> <>
<h5>{i18n.t("blocked_users")}</h5> <h5>{I18NextService.i18n.t("blocked_users")}</h5>
<ul className="list-unstyled mb-0"> <ul className="list-unstyled mb-0">
{this.state.personBlocks.map(pb => ( {this.state.personBlocks.map(pb => (
<li key={pb.target.id}> <li key={pb.target.id}>
@ -424,7 +421,7 @@ export class Settings extends Component<any, SettingsState> {
{ ctx: this, recipientId: pb.target.id }, { ctx: this, recipientId: pb.target.id },
this.handleUnblockPerson this.handleUnblockPerson
)} )}
data-tippy-content={i18n.t("unblock_user")} data-tippy-content={I18NextService.i18n.t("unblock_user")}
> >
<Icon icon="x" classes="icon-inline" /> <Icon icon="x" classes="icon-inline" />
</button> </button>
@ -456,7 +453,7 @@ export class Settings extends Component<any, SettingsState> {
blockedCommunitiesList() { blockedCommunitiesList() {
return ( return (
<> <>
<h5>{i18n.t("blocked_communities")}</h5> <h5>{I18NextService.i18n.t("blocked_communities")}</h5>
<ul className="list-unstyled mb-0"> <ul className="list-unstyled mb-0">
{this.state.communityBlocks.map(cb => ( {this.state.communityBlocks.map(cb => (
<li key={cb.community.id}> <li key={cb.community.id}>
@ -468,7 +465,9 @@ export class Settings extends Component<any, SettingsState> {
{ ctx: this, communityId: cb.community.id }, { ctx: this, communityId: cb.community.id },
this.handleUnblockCommunity this.handleUnblockCommunity
)} )}
data-tippy-content={i18n.t("unblock_community")} data-tippy-content={I18NextService.i18n.t(
"unblock_community"
)}
> >
<Icon icon="x" classes="icon-inline" /> <Icon icon="x" classes="icon-inline" />
</button> </button>
@ -485,18 +484,18 @@ export class Settings extends Component<any, SettingsState> {
return ( return (
<> <>
<h5>{i18n.t("settings")}</h5> <h5>{I18NextService.i18n.t("settings")}</h5>
<form onSubmit={linkEvent(this, this.handleSaveSettingsSubmit)}> <form onSubmit={linkEvent(this, this.handleSaveSettingsSubmit)}>
<div className="mb-3 row"> <div className="mb-3 row">
<label className="col-sm-3 col-form-label" htmlFor="display-name"> <label className="col-sm-3 col-form-label" htmlFor="display-name">
{i18n.t("display_name")} {I18NextService.i18n.t("display_name")}
</label> </label>
<div className="col-sm-9"> <div className="col-sm-9">
<input <input
id="display-name" id="display-name"
type="text" type="text"
className="form-control" className="form-control"
placeholder={i18n.t("optional")} placeholder={I18NextService.i18n.t("optional")}
value={this.state.saveUserSettingsForm.display_name} value={this.state.saveUserSettingsForm.display_name}
onInput={linkEvent(this, this.handleDisplayNameChange)} onInput={linkEvent(this, this.handleDisplayNameChange)}
pattern="^(?!@)(.+)$" pattern="^(?!@)(.+)$"
@ -506,7 +505,7 @@ export class Settings extends Component<any, SettingsState> {
</div> </div>
<div className="mb-3 row"> <div className="mb-3 row">
<label className="col-sm-3 col-form-label" htmlFor="user-bio"> <label className="col-sm-3 col-form-label" htmlFor="user-bio">
{i18n.t("bio")} {I18NextService.i18n.t("bio")}
</label> </label>
<div className="col-sm-9"> <div className="col-sm-9">
<MarkdownTextArea <MarkdownTextArea
@ -521,14 +520,14 @@ export class Settings extends Component<any, SettingsState> {
</div> </div>
<div className="mb-3 row"> <div className="mb-3 row">
<label className="col-sm-3 col-form-label" htmlFor="user-email"> <label className="col-sm-3 col-form-label" htmlFor="user-email">
{i18n.t("email")} {I18NextService.i18n.t("email")}
</label> </label>
<div className="col-sm-9"> <div className="col-sm-9">
<input <input
type="email" type="email"
id="user-email" id="user-email"
className="form-control" className="form-control"
placeholder={i18n.t("optional")} placeholder={I18NextService.i18n.t("optional")}
value={this.state.saveUserSettingsForm.email} value={this.state.saveUserSettingsForm.email}
onInput={linkEvent(this, this.handleEmailChange)} onInput={linkEvent(this, this.handleEmailChange)}
minLength={3} minLength={3}
@ -538,7 +537,7 @@ export class Settings extends Component<any, SettingsState> {
<div className="mb-3 row"> <div className="mb-3 row">
<label className="col-sm-3 col-form-label" htmlFor="matrix-user-id"> <label className="col-sm-3 col-form-label" htmlFor="matrix-user-id">
<a href={elementUrl} rel={relTags}> <a href={elementUrl} rel={relTags}>
{i18n.t("matrix_user_id")} {I18NextService.i18n.t("matrix_user_id")}
</a> </a>
</label> </label>
<div className="col-sm-9"> <div className="col-sm-9">
@ -555,11 +554,11 @@ export class Settings extends Component<any, SettingsState> {
</div> </div>
<div className="mb-3 row"> <div className="mb-3 row">
<label className="col-sm-3 col-form-label"> <label className="col-sm-3 col-form-label">
{i18n.t("avatar")} {I18NextService.i18n.t("avatar")}
</label> </label>
<div className="col-sm-9"> <div className="col-sm-9">
<ImageUploadForm <ImageUploadForm
uploadTitle={i18n.t("upload_avatar")} uploadTitle={I18NextService.i18n.t("upload_avatar")}
imageSrc={this.state.saveUserSettingsForm.avatar} imageSrc={this.state.saveUserSettingsForm.avatar}
onUpload={this.handleAvatarUpload} onUpload={this.handleAvatarUpload}
onRemove={this.handleAvatarRemove} onRemove={this.handleAvatarRemove}
@ -569,11 +568,11 @@ export class Settings extends Component<any, SettingsState> {
</div> </div>
<div className="mb-3 row"> <div className="mb-3 row">
<label className="col-sm-3 col-form-label"> <label className="col-sm-3 col-form-label">
{i18n.t("banner")} {I18NextService.i18n.t("banner")}
</label> </label>
<div className="col-sm-9"> <div className="col-sm-9">
<ImageUploadForm <ImageUploadForm
uploadTitle={i18n.t("upload_banner")} uploadTitle={I18NextService.i18n.t("upload_banner")}
imageSrc={this.state.saveUserSettingsForm.banner} imageSrc={this.state.saveUserSettingsForm.banner}
onUpload={this.handleBannerUpload} onUpload={this.handleBannerUpload}
onRemove={this.handleBannerRemove} onRemove={this.handleBannerRemove}
@ -582,7 +581,7 @@ export class Settings extends Component<any, SettingsState> {
</div> </div>
<div className="mb-3 row"> <div className="mb-3 row">
<label className="col-sm-3 form-label" htmlFor="user-language"> <label className="col-sm-3 form-label" htmlFor="user-language">
{i18n.t("interface_language")} {I18NextService.i18n.t("interface_language")}
</label> </label>
<div className="col-sm-9"> <div className="col-sm-9">
<select <select
@ -592,9 +591,11 @@ export class Settings extends Component<any, SettingsState> {
className="form-select d-inline-block w-auto" className="form-select d-inline-block w-auto"
> >
<option disabled aria-hidden="true"> <option disabled aria-hidden="true">
{i18n.t("interface_language")} {I18NextService.i18n.t("interface_language")}
</option>
<option value="browser">
{I18NextService.i18n.t("browser_default")}
</option> </option>
<option value="browser">{i18n.t("browser_default")}</option>
<option disabled aria-hidden="true"> <option disabled aria-hidden="true">
</option> </option>
@ -619,7 +620,7 @@ export class Settings extends Component<any, SettingsState> {
/> />
<div className="mb-3 row"> <div className="mb-3 row">
<label className="col-sm-3 col-form-label" htmlFor="user-theme"> <label className="col-sm-3 col-form-label" htmlFor="user-theme">
{i18n.t("theme")} {I18NextService.i18n.t("theme")}
</label> </label>
<div className="col-sm-9"> <div className="col-sm-9">
<select <select
@ -629,9 +630,11 @@ export class Settings extends Component<any, SettingsState> {
className="form-select d-inline-block w-auto" className="form-select d-inline-block w-auto"
> >
<option disabled aria-hidden="true"> <option disabled aria-hidden="true">
{i18n.t("theme")} {I18NextService.i18n.t("theme")}
</option>
<option value="browser">
{I18NextService.i18n.t("browser_default")}
</option> </option>
<option value="browser">{i18n.t("browser_default")}</option>
{this.state.themeList.map(theme => ( {this.state.themeList.map(theme => (
<option key={theme} value={theme}> <option key={theme} value={theme}>
{theme} {theme}
@ -641,7 +644,9 @@ export class Settings extends Component<any, SettingsState> {
</div> </div>
</div> </div>
<form className="mb-3 row"> <form className="mb-3 row">
<label className="col-sm-3 col-form-label">{i18n.t("type")}</label> <label className="col-sm-3 col-form-label">
{I18NextService.i18n.t("type")}
</label>
<div className="col-sm-9"> <div className="col-sm-9">
<ListingTypeSelect <ListingTypeSelect
type_={ type_={
@ -656,7 +661,7 @@ export class Settings extends Component<any, SettingsState> {
</form> </form>
<form className="mb-3 row"> <form className="mb-3 row">
<label className="col-sm-3 col-form-label"> <label className="col-sm-3 col-form-label">
{i18n.t("sort_type")} {I18NextService.i18n.t("sort_type")}
</label> </label>
<div className="col-sm-9"> <div className="col-sm-9">
<SortSelect <SortSelect
@ -677,7 +682,7 @@ export class Settings extends Component<any, SettingsState> {
onChange={linkEvent(this, this.handleShowNsfwChange)} onChange={linkEvent(this, this.handleShowNsfwChange)}
/> />
<label className="form-check-label" htmlFor="user-show-nsfw"> <label className="form-check-label" htmlFor="user-show-nsfw">
{i18n.t("show_nsfw")} {I18NextService.i18n.t("show_nsfw")}
</label> </label>
</div> </div>
</div> </div>
@ -691,7 +696,7 @@ export class Settings extends Component<any, SettingsState> {
onChange={linkEvent(this, this.handleShowScoresChange)} onChange={linkEvent(this, this.handleShowScoresChange)}
/> />
<label className="form-check-label" htmlFor="user-show-scores"> <label className="form-check-label" htmlFor="user-show-scores">
{i18n.t("show_scores")} {I18NextService.i18n.t("show_scores")}
</label> </label>
</div> </div>
</div> </div>
@ -705,7 +710,7 @@ export class Settings extends Component<any, SettingsState> {
onChange={linkEvent(this, this.handleShowAvatarsChange)} onChange={linkEvent(this, this.handleShowAvatarsChange)}
/> />
<label className="form-check-label" htmlFor="user-show-avatars"> <label className="form-check-label" htmlFor="user-show-avatars">
{i18n.t("show_avatars")} {I18NextService.i18n.t("show_avatars")}
</label> </label>
</div> </div>
</div> </div>
@ -719,7 +724,7 @@ export class Settings extends Component<any, SettingsState> {
onChange={linkEvent(this, this.handleBotAccount)} onChange={linkEvent(this, this.handleBotAccount)}
/> />
<label className="form-check-label" htmlFor="user-bot-account"> <label className="form-check-label" htmlFor="user-bot-account">
{i18n.t("bot_account")} {I18NextService.i18n.t("bot_account")}
</label> </label>
</div> </div>
</div> </div>
@ -736,7 +741,7 @@ export class Settings extends Component<any, SettingsState> {
className="form-check-label" className="form-check-label"
htmlFor="user-show-bot-accounts" htmlFor="user-show-bot-accounts"
> >
{i18n.t("show_bot_accounts")} {I18NextService.i18n.t("show_bot_accounts")}
</label> </label>
</div> </div>
</div> </div>
@ -753,7 +758,7 @@ export class Settings extends Component<any, SettingsState> {
className="form-check-label" className="form-check-label"
htmlFor="user-show-read-posts" htmlFor="user-show-read-posts"
> >
{i18n.t("show_read_posts")} {I18NextService.i18n.t("show_read_posts")}
</label> </label>
</div> </div>
</div> </div>
@ -770,7 +775,7 @@ export class Settings extends Component<any, SettingsState> {
className="form-check-label" className="form-check-label"
htmlFor="user-show-new-post-notifs" htmlFor="user-show-new-post-notifs"
> >
{i18n.t("show_new_post_notifs")} {I18NextService.i18n.t("show_new_post_notifs")}
</label> </label>
</div> </div>
</div> </div>
@ -793,7 +798,7 @@ export class Settings extends Component<any, SettingsState> {
className="form-check-label" className="form-check-label"
htmlFor="user-send-notifications-to-email" htmlFor="user-send-notifications-to-email"
> >
{i18n.t("send_notifications_to_email")} {I18NextService.i18n.t("send_notifications_to_email")}
</label> </label>
</div> </div>
</div> </div>
@ -803,7 +808,7 @@ export class Settings extends Component<any, SettingsState> {
{this.state.saveRes.state === "loading" ? ( {this.state.saveRes.state === "loading" ? (
<Spinner /> <Spinner />
) : ( ) : (
capitalizeFirstLetter(i18n.t("save")) capitalizeFirstLetter(I18NextService.i18n.t("save"))
)} )}
</button> </button>
</div> </div>
@ -816,12 +821,12 @@ export class Settings extends Component<any, SettingsState> {
this.handleDeleteAccountShowConfirmToggle this.handleDeleteAccountShowConfirmToggle
)} )}
> >
{i18n.t("delete_account")} {I18NextService.i18n.t("delete_account")}
</button> </button>
{this.state.deleteAccountShowConfirm && ( {this.state.deleteAccountShowConfirm && (
<> <>
<div className="my-2 alert alert-danger" role="alert"> <div className="my-2 alert alert-danger" role="alert">
{i18n.t("delete_account_confirm")} {I18NextService.i18n.t("delete_account_confirm")}
</div> </div>
<input <input
type="password" type="password"
@ -842,7 +847,7 @@ export class Settings extends Component<any, SettingsState> {
{this.state.deleteAccountRes.state === "loading" ? ( {this.state.deleteAccountRes.state === "loading" ? (
<Spinner /> <Spinner />
) : ( ) : (
capitalizeFirstLetter(i18n.t("delete")) capitalizeFirstLetter(I18NextService.i18n.t("delete"))
)} )}
</button> </button>
<button <button
@ -852,7 +857,7 @@ export class Settings extends Component<any, SettingsState> {
this.handleDeleteAccountShowConfirmToggle this.handleDeleteAccountShowConfirmToggle
)} )}
> >
{i18n.t("cancel")} {I18NextService.i18n.t("cancel")}
</button> </button>
</> </>
)} )}
@ -879,7 +884,7 @@ export class Settings extends Component<any, SettingsState> {
onChange={linkEvent(this, this.handleGenerateTotp)} onChange={linkEvent(this, this.handleGenerateTotp)}
/> />
<label className="form-check-label" htmlFor="user-generate-totp"> <label className="form-check-label" htmlFor="user-generate-totp">
{i18n.t("set_up_two_factor")} {I18NextService.i18n.t("set_up_two_factor")}
</label> </label>
</div> </div>
</div> </div>
@ -889,7 +894,7 @@ export class Settings extends Component<any, SettingsState> {
<> <>
<div> <div>
<a className="btn btn-secondary mb-2" href={totpUrl}> <a className="btn btn-secondary mb-2" href={totpUrl}>
{i18n.t("two_factor_link")} {I18NextService.i18n.t("two_factor_link")}
</a> </a>
</div> </div>
<div className="input-group mb-3"> <div className="input-group mb-3">
@ -904,7 +909,7 @@ export class Settings extends Component<any, SettingsState> {
onChange={linkEvent(this, this.handleRemoveTotp)} onChange={linkEvent(this, this.handleRemoveTotp)}
/> />
<label className="form-check-label" htmlFor="user-remove-totp"> <label className="form-check-label" htmlFor="user-remove-totp">
{i18n.t("remove_two_factor")} {I18NextService.i18n.t("remove_two_factor")}
</label> </label>
</div> </div>
</div> </div>
@ -1053,7 +1058,7 @@ export class Settings extends Component<any, SettingsState> {
// Coerce false to undefined here, so it won't generate it. // Coerce false to undefined here, so it won't generate it.
const checked: boolean | undefined = event.target.checked || undefined; const checked: boolean | undefined = event.target.checked || undefined;
if (checked) { if (checked) {
toast(i18n.t("two_factor_setup_instructions")); toast(I18NextService.i18n.t("two_factor_setup_instructions"));
} }
i.setState(s => ((s.saveUserSettingsForm.generate_totp_2fa = checked), s)); i.setState(s => ((s.saveUserSettingsForm.generate_totp_2fa = checked), s));
} }
@ -1081,7 +1086,9 @@ export class Settings extends Component<any, SettingsState> {
handleInterfaceLangChange(i: Settings, event: any) { handleInterfaceLangChange(i: Settings, event: any) {
const newLang = event.target.value ?? "browser"; const newLang = event.target.value ?? "browser";
i18n.changeLanguage(newLang === "browser" ? navigator.languages : newLang); I18NextService.i18n.changeLanguage(
newLang === "browser" ? navigator.languages : newLang
);
i.setState( i.setState(
s => ((s.saveUserSettingsForm.interface_language = event.target.value), s) s => ((s.saveUserSettingsForm.interface_language = event.target.value), s)
@ -1171,7 +1178,7 @@ export class Settings extends Component<any, SettingsState> {
if (saveRes.state === "success") { if (saveRes.state === "success") {
UserService.Instance.login(saveRes.data); UserService.Instance.login(saveRes.data);
location.reload(); location.reload();
toast(i18n.t("saved")); toast(I18NextService.i18n.t("saved"));
window.scrollTo(0, 0); window.scrollTo(0, 0);
} }
@ -1194,7 +1201,7 @@ export class Settings extends Component<any, SettingsState> {
if (changePasswordRes.state === "success") { if (changePasswordRes.state === "success") {
UserService.Instance.login(changePasswordRes.data); UserService.Instance.login(changePasswordRes.data);
window.scrollTo(0, 0); window.scrollTo(0, 0);
toast(i18n.t("password_changed")); toast(I18NextService.i18n.t("password_changed"));
} }
i.setState({ changePasswordRes }); i.setState({ changePasswordRes });

View file

@ -1,8 +1,9 @@
import { setIsoData } from "@utils/app";
import { Component } from "inferno"; import { Component } from "inferno";
import { GetSiteResponse, VerifyEmailResponse } from "lemmy-js-client"; import { GetSiteResponse, VerifyEmailResponse } from "lemmy-js-client";
import { i18n } from "../../i18next"; import { I18NextService } from "../../services";
import { HttpService, RequestState } from "../../services/HttpService"; import { HttpService, RequestState } from "../../services/HttpService";
import { setIsoData, toast } from "../../utils"; import { toast } from "../../toast";
import { HtmlTags } from "../common/html-tags"; import { HtmlTags } from "../common/html-tags";
import { Spinner } from "../common/icon"; import { Spinner } from "../common/icon";
@ -35,7 +36,7 @@ export class VerifyEmail extends Component<any, State> {
}); });
if (this.state.verifyRes.state == "success") { if (this.state.verifyRes.state == "success") {
toast(i18n.t("email_verified")); toast(I18NextService.i18n.t("email_verified"));
this.props.history.push("/login"); this.props.history.push("/login");
} }
} }
@ -45,7 +46,7 @@ export class VerifyEmail extends Component<any, State> {
} }
get documentTitle(): string { get documentTitle(): string {
return `${i18n.t("verify_email")} - ${ return `${I18NextService.i18n.t("verify_email")} - ${
this.state.siteRes.site_view.site.name this.state.siteRes.site_view.site.name
}`; }`;
} }
@ -59,7 +60,7 @@ export class VerifyEmail extends Component<any, State> {
/> />
<div className="row"> <div className="row">
<div className="col-12 col-lg-6 offset-lg-3 mb-4"> <div className="col-12 col-lg-6 offset-lg-3 mb-4">
<h5>{i18n.t("verify_email")}</h5> <h5>{I18NextService.i18n.t("verify_email")}</h5>
{this.state.verifyRes.state == "loading" && ( {this.state.verifyRes.state == "loading" && (
<h5> <h5>
<Spinner large /> <Spinner large />

View file

@ -1,5 +1,7 @@
import { getQueryParams } from "@utils/helpers"; import { enableDownvotes, enableNsfw, myAuth, setIsoData } from "@utils/app";
import { getIdFromString, getQueryParams } from "@utils/helpers";
import type { QueryParams } from "@utils/types"; import type { QueryParams } from "@utils/types";
import { Choice, RouteDataResponse } from "@utils/types";
import { Component } from "inferno"; import { Component } from "inferno";
import { RouteComponentProps } from "inferno-router/dist/Route"; import { RouteComponentProps } from "inferno-router/dist/Route";
import { import {
@ -9,23 +11,13 @@ import {
GetSiteResponse, GetSiteResponse,
ListCommunitiesResponse, ListCommunitiesResponse,
} from "lemmy-js-client"; } from "lemmy-js-client";
import { i18n } from "../../i18next";
import { InitialFetchRequest, PostFormParams } from "../../interfaces"; import { InitialFetchRequest, PostFormParams } from "../../interfaces";
import { FirstLoadService } from "../../services/FirstLoadService"; import { FirstLoadService, I18NextService } from "../../services";
import { import {
HttpService, HttpService,
RequestState, RequestState,
WrappedLemmyHttp, WrappedLemmyHttp,
} from "../../services/HttpService"; } from "../../services/HttpService";
import {
Choice,
RouteDataResponse,
enableDownvotes,
enableNsfw,
getIdFromString,
myAuth,
setIsoData,
} from "../../utils";
import { HtmlTags } from "../common/html-tags"; import { HtmlTags } from "../common/html-tags";
import { Spinner } from "../common/icon"; import { Spinner } from "../common/icon";
import { PostForm } from "./post-form"; import { PostForm } from "./post-form";
@ -150,7 +142,7 @@ export class CreatePost extends Component<
} }
get documentTitle(): string { get documentTitle(): string {
return `${i18n.t("create_post")} - ${ return `${I18NextService.i18n.t("create_post")} - ${
this.state.siteRes.site_view.site.name this.state.siteRes.site_view.site.name
}`; }`;
} }
@ -178,7 +170,7 @@ export class CreatePost extends Component<
id="createPostForm" id="createPostForm"
className="col-12 col-lg-6 offset-lg-3 mb-4" className="col-12 col-lg-6 offset-lg-3 mb-4"
> >
<h1 className="h4">{i18n.t("create_post")}</h1> <h1 className="h4">{I18NextService.i18n.t("create_post")}</h1>
<PostForm <PostForm
onCreate={this.handlePostCreate} onCreate={this.handlePostCreate}
params={locationState} params={locationState}

View file

@ -1,8 +1,8 @@
import { Component, linkEvent } from "inferno"; import { Component, linkEvent } from "inferno";
import { Post } from "lemmy-js-client"; import { Post } from "lemmy-js-client";
import * as sanitizeHtml from "sanitize-html"; import * as sanitizeHtml from "sanitize-html";
import { i18n } from "../../i18next"; import { relTags } from "../../config";
import { relTags } from "../../utils"; import { I18NextService } from "../../services";
import { Icon } from "../common/icon"; import { Icon } from "../common/icon";
interface MetadataCardProps { interface MetadataCardProps {
@ -66,7 +66,7 @@ export class MetadataCard extends Component<
className="mt-2 btn btn-secondary text-monospace" className="mt-2 btn btn-secondary text-monospace"
onClick={linkEvent(this, this.handleIframeExpand)} onClick={linkEvent(this, this.handleIframeExpand)}
> >
{i18n.t("expand_here")} {I18NextService.i18n.t("expand_here")}
</button> </button>
)} )}
</div> </div>
@ -75,10 +75,14 @@ export class MetadataCard extends Component<
</div> </div>
)} )}
{this.state.expanded && post.embed_video_url && ( {this.state.expanded && post.embed_video_url && (
<div className="ratio ratio-16x9">
<iframe <iframe
allowFullScreen
className="post-metadata-iframe" className="post-metadata-iframe"
src={post.embed_video_url} src={post.embed_video_url}
title={post.embed_title}
></iframe> ></iframe>
</div>
)} )}
</> </>
); );

View file

@ -1,4 +1,18 @@
import { debounce } from "@utils/helpers"; import {
communityToChoice,
fetchCommunities,
myAuth,
myAuthRequired,
} from "@utils/app";
import {
capitalizeFirstLetter,
debounce,
getIdFromString,
validTitle,
validURL,
} from "@utils/helpers";
import { isImage } from "@utils/media";
import { Choice } from "@utils/types";
import autosize from "autosize"; import autosize from "autosize";
import { Component, InfernoNode, linkEvent } from "inferno"; import { Component, InfernoNode, linkEvent } from "inferno";
import { import {
@ -10,29 +24,18 @@ import {
PostView, PostView,
SearchResponse, SearchResponse,
} from "lemmy-js-client"; } from "lemmy-js-client";
import { i18n } from "../../i18next";
import { PostFormParams } from "../../interfaces";
import { UserService } from "../../services";
import { HttpService, RequestState } from "../../services/HttpService";
import { import {
Choice,
archiveTodayUrl, archiveTodayUrl,
capitalizeFirstLetter,
communityToChoice,
fetchCommunities,
getIdFromString,
ghostArchiveUrl, ghostArchiveUrl,
isImage,
myAuth,
myAuthRequired,
relTags, relTags,
setupTippy,
toast,
trendingFetchLimit, trendingFetchLimit,
validTitle,
validURL,
webArchiveUrl, webArchiveUrl,
} from "../../utils"; } from "../../config";
import { PostFormParams } from "../../interfaces";
import { I18NextService, UserService } from "../../services";
import { HttpService, RequestState } from "../../services/HttpService";
import { setupTippy } from "../../tippy";
import { toast } from "../../toast";
import { Icon, Spinner } from "../common/icon"; import { Icon, Spinner } from "../common/icon";
import { LanguageSelect } from "../common/language-select"; import { LanguageSelect } from "../common/language-select";
import { MarkdownTextArea } from "../common/markdown-textarea"; import { MarkdownTextArea } from "../common/markdown-textarea";
@ -338,7 +341,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
/> />
<div className="mb-3 row"> <div className="mb-3 row">
<label className="col-sm-2 col-form-label" htmlFor="post-url"> <label className="col-sm-2 col-form-label" htmlFor="post-url">
{i18n.t("url")} {I18NextService.i18n.t("url")}
</label> </label>
<div className="col-sm-10"> <div className="col-sm-10">
<input <input
@ -356,7 +359,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
className={`${ className={`${
UserService.Instance.myUserInfo && "pointer" UserService.Instance.myUserInfo && "pointer"
} d-inline-block float-right text-muted font-weight-bold`} } d-inline-block float-right text-muted font-weight-bold`}
data-tippy-content={i18n.t("upload_image")} data-tippy-content={I18NextService.i18n.t("upload_image")}
> >
<Icon icon="image" classes="icon-inline" /> <Icon icon="image" classes="icon-inline" />
</label> </label>
@ -377,7 +380,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
className="me-2 d-inline-block float-right text-muted small font-weight-bold" className="me-2 d-inline-block float-right text-muted small font-weight-bold"
rel={relTags} rel={relTags}
> >
archive.org {i18n.t("archive_link")} archive.org {I18NextService.i18n.t("archive_link")}
</a> </a>
<a <a
href={`${ghostArchiveUrl}/search?term=${encodeURIComponent( href={`${ghostArchiveUrl}/search?term=${encodeURIComponent(
@ -386,7 +389,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
className="me-2 d-inline-block float-right text-muted small font-weight-bold" className="me-2 d-inline-block float-right text-muted small font-weight-bold"
rel={relTags} rel={relTags}
> >
ghostarchive.org {i18n.t("archive_link")} ghostarchive.org {I18NextService.i18n.t("archive_link")}
</a> </a>
<a <a
href={`${archiveTodayUrl}/?run=1&url=${encodeURIComponent( href={`${archiveTodayUrl}/?run=1&url=${encodeURIComponent(
@ -395,7 +398,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
className="me-2 d-inline-block float-right text-muted small font-weight-bold" className="me-2 d-inline-block float-right text-muted small font-weight-bold"
rel={relTags} rel={relTags}
> >
archive.today {i18n.t("archive_link")} archive.today {I18NextService.i18n.t("archive_link")}
</a> </a>
</div> </div>
)} )}
@ -407,17 +410,17 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
<button <button
className="btn btn-danger btn-sm mt-2" className="btn btn-danger btn-sm mt-2"
onClick={linkEvent(this, handleImageDelete)} onClick={linkEvent(this, handleImageDelete)}
aria-label={i18n.t("delete")} aria-label={I18NextService.i18n.t("delete")}
data-tippy-content={i18n.t("delete")} data-tippy-content={I18NextService.i18n.t("delete")}
> >
<Icon icon="x" classes="icon-inline me-1" /> <Icon icon="x" classes="icon-inline me-1" />
{capitalizeFirstLetter(i18n.t("delete"))} {capitalizeFirstLetter(I18NextService.i18n.t("delete"))}
</button> </button>
)} )}
{this.props.crossPosts && this.props.crossPosts.length > 0 && ( {this.props.crossPosts && this.props.crossPosts.length > 0 && (
<> <>
<div className="my-1 text-muted small font-weight-bold"> <div className="my-1 text-muted small font-weight-bold">
{i18n.t("cross_posts")} {I18NextService.i18n.t("cross_posts")}
</div> </div>
<PostListings <PostListings
showCommunity showCommunity
@ -451,7 +454,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
</div> </div>
<div className="mb-3 row"> <div className="mb-3 row">
<label className="col-sm-2 col-form-label" htmlFor="post-title"> <label className="col-sm-2 col-form-label" htmlFor="post-title">
{i18n.t("title")} {I18NextService.i18n.t("title")}
</label> </label>
<div className="col-sm-10"> <div className="col-sm-10">
<textarea <textarea
@ -468,7 +471,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
/> />
{!validTitle(this.state.form.name) && ( {!validTitle(this.state.form.name) && (
<div className="invalid-feedback"> <div className="invalid-feedback">
{i18n.t("invalid_post_title")} {I18NextService.i18n.t("invalid_post_title")}
</div> </div>
)} )}
{this.renderSuggestedPosts()} {this.renderSuggestedPosts()}
@ -476,7 +479,9 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
</div> </div>
<div className="mb-3 row"> <div className="mb-3 row">
<label className="col-sm-2 col-form-label">{i18n.t("body")}</label> <label className="col-sm-2 col-form-label">
{I18NextService.i18n.t("body")}
</label>
<div className="col-sm-10"> <div className="col-sm-10">
<MarkdownTextArea <MarkdownTextArea
initialContent={this.state.form.body} initialContent={this.state.form.body}
@ -497,7 +502,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
{!this.props.post_view && ( {!this.props.post_view && (
<div className="mb-3 row"> <div className="mb-3 row">
<label className="col-sm-2 col-form-label" htmlFor="post-community"> <label className="col-sm-2 col-form-label" htmlFor="post-community">
{i18n.t("community")} {I18NextService.i18n.t("community")}
</label> </label>
<div className="col-sm-10"> <div className="col-sm-10">
<SearchableSelect <SearchableSelect
@ -505,7 +510,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
value={this.state.form.community_id} value={this.state.form.community_id}
options={[ options={[
{ {
label: i18n.t("select_a_community"), label: I18NextService.i18n.t("select_a_community"),
value: "", value: "",
disabled: true, disabled: true,
} as Choice, } as Choice,
@ -526,7 +531,9 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
checked={this.state.form.nsfw} checked={this.state.form.nsfw}
onChange={linkEvent(this, handlePostNsfwChange)} onChange={linkEvent(this, handlePostNsfwChange)}
/> />
<label className="form-check-label">{i18n.t("nsfw")}</label> <label className="form-check-label">
{I18NextService.i18n.t("nsfw")}
</label>
</div> </div>
)} )}
<input <input
@ -549,9 +556,9 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
{this.state.loading ? ( {this.state.loading ? (
<Spinner /> <Spinner />
) : this.props.post_view ? ( ) : this.props.post_view ? (
capitalizeFirstLetter(i18n.t("save")) capitalizeFirstLetter(I18NextService.i18n.t("save"))
) : ( ) : (
capitalizeFirstLetter(i18n.t("create")) capitalizeFirstLetter(I18NextService.i18n.t("create"))
)} )}
</button> </button>
{this.props.post_view && ( {this.props.post_view && (
@ -560,7 +567,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
className="btn btn-secondary" className="btn btn-secondary"
onClick={linkEvent(this, handleCancel)} onClick={linkEvent(this, handleCancel)}
> >
{i18n.t("cancel")} {I18NextService.i18n.t("cancel")}
</button> </button>
)} )}
</div> </div>
@ -586,7 +593,8 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
copySuggestedTitle copySuggestedTitle
)} )}
> >
{i18n.t("copy_suggested_title", { title: "" })} {suggestedTitle} {I18NextService.i18n.t("copy_suggested_title", { title: "" })}{" "}
{suggestedTitle}
</div> </div>
) )
); );
@ -606,7 +614,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
suggestedPosts.length > 0 && ( suggestedPosts.length > 0 && (
<> <>
<div className="my-1 text-muted small font-weight-bold"> <div className="my-1 text-muted small font-weight-bold">
{i18n.t("related_posts")} {I18NextService.i18n.t("related_posts")}
</div> </div>
<PostListings <PostListings
showCommunity showCommunity

View file

@ -1,4 +1,7 @@
import { myAuthRequired, newVote, showScores } from "@utils/app";
import { canShare, share } from "@utils/browser"; import { canShare, share } from "@utils/browser";
import { futureDaysToUnixTime, hostname, numToSI } from "@utils/helpers";
import { isImage, isVideo } from "@utils/media";
import { import {
amAdmin, amAdmin,
amCommunityCreator, amCommunityCreator,
@ -34,25 +37,12 @@ import {
SavePost, SavePost,
TransferCommunity, TransferCommunity,
} from "lemmy-js-client"; } from "lemmy-js-client";
import { relTags } from "../../config";
import { getExternalHost, getHttpBase } from "../../env"; import { getExternalHost, getHttpBase } from "../../env";
import { i18n } from "../../i18next";
import { BanType, PostFormParams, PurgeType, VoteType } from "../../interfaces"; import { BanType, PostFormParams, PurgeType, VoteType } from "../../interfaces";
import { UserService } from "../../services"; import { mdNoImages, mdToHtml, mdToHtmlInline } from "../../markdown";
import { import { I18NextService, UserService } from "../../services";
futureDaysToUnixTime, import { setupTippy } from "../../tippy";
hostname,
isImage,
isVideo,
mdNoImages,
mdToHtml,
mdToHtmlInline,
myAuthRequired,
newVote,
numToSI,
relTags,
setupTippy,
showScores,
} from "../../utils";
import { Icon, PurgeWarning, Spinner } from "../common/icon"; import { Icon, PurgeWarning, Spinner } from "../common/icon";
import { MomentTime } from "../common/moment-time"; import { MomentTime } from "../common/moment-time";
import { PictrsImage } from "../common/pictrs-image"; import { PictrsImage } from "../common/pictrs-image";
@ -307,9 +297,9 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
<a <a
href={this.imageSrc} href={this.imageSrc}
className="text-body d-inline-block position-relative mb-2" className="text-body d-inline-block position-relative mb-2"
data-tippy-content={i18n.t("expand_here")} data-tippy-content={I18NextService.i18n.t("expand_here")}
onClick={linkEvent(this, this.handleImageExpandClick)} onClick={linkEvent(this, this.handleImageExpandClick)}
aria-label={i18n.t("expand_here")} aria-label={I18NextService.i18n.t("expand_here")}
> >
{this.imgThumb(this.imageSrc)} {this.imgThumb(this.imageSrc)}
<Icon icon="image" classes="mini-overlay" /> <Icon icon="image" classes="mini-overlay" />
@ -356,7 +346,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
<Link <Link
className="text-body" className="text-body"
to={`/post/${post.id}`} to={`/post/${post.id}`}
title={i18n.t("comments")} title={I18NextService.i18n.t("comments")}
> >
<div className="thumbnail rounded bg-light d-flex justify-content-center"> <div className="thumbnail rounded bg-light d-flex justify-content-center">
<Icon icon="message-square" classes="d-flex align-items-center" /> <Icon icon="message-square" classes="d-flex align-items-center" />
@ -374,20 +364,25 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
<PersonListing person={post_view.creator} /> <PersonListing person={post_view.creator} />
{this.creatorIsMod_ && ( {this.creatorIsMod_ && (
<span className="mx-1 badge text-bg-light">{i18n.t("mod")}</span> <span className="mx-1 badge text-bg-light">
{I18NextService.i18n.t("mod")}
</span>
)} )}
{this.creatorIsAdmin_ && ( {this.creatorIsAdmin_ && (
<span className="mx-1 badge text-bg-light">{i18n.t("admin")}</span> <span className="mx-1 badge text-bg-light">
{I18NextService.i18n.t("admin")}
</span>
)} )}
{post_view.creator.bot_account && ( {post_view.creator.bot_account && (
<span className="mx-1 badge text-bg-light"> <span className="mx-1 badge text-bg-light">
{i18n.t("bot_account").toLowerCase()} {I18NextService.i18n.t("bot_account").toLowerCase()}
</span> </span>
)} )}
{this.props.showCommunity && ( {this.props.showCommunity && (
<> <>
{" "} {" "}
{i18n.t("to")} <CommunityLink community={post_view.community} /> {I18NextService.i18n.t("to")}{" "}
<CommunityLink community={post_view.community} />
</> </>
)} )}
</li> </li>
@ -421,8 +416,8 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
this.postView.my_vote == 1 ? "text-info" : "text-muted" this.postView.my_vote == 1 ? "text-info" : "text-muted"
}`} }`}
onClick={linkEvent(this, this.handleUpvote)} onClick={linkEvent(this, this.handleUpvote)}
data-tippy-content={i18n.t("upvote")} data-tippy-content={I18NextService.i18n.t("upvote")}
aria-label={i18n.t("upvote")} aria-label={I18NextService.i18n.t("upvote")}
aria-pressed={this.postView.my_vote === 1} aria-pressed={this.postView.my_vote === 1}
> >
{this.state.upvoteLoading ? ( {this.state.upvoteLoading ? (
@ -447,8 +442,8 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
this.postView.my_vote == -1 ? "text-danger" : "text-muted" this.postView.my_vote == -1 ? "text-danger" : "text-muted"
}`} }`}
onClick={linkEvent(this, this.handleDownvote)} onClick={linkEvent(this, this.handleDownvote)}
data-tippy-content={i18n.t("downvote")} data-tippy-content={I18NextService.i18n.t("downvote")}
aria-label={i18n.t("downvote")} aria-label={I18NextService.i18n.t("downvote")}
aria-pressed={this.postView.my_vote === -1} aria-pressed={this.postView.my_vote === -1}
> >
{this.state.downvoteLoading ? ( {this.state.downvoteLoading ? (
@ -472,7 +467,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
: "text-primary" : "text-primary"
}`} }`}
to={`/post/${post.id}`} to={`/post/${post.id}`}
title={i18n.t("comments")} title={I18NextService.i18n.t("comments")}
> >
<span <span
className="d-inline" className="d-inline"
@ -510,7 +505,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
(post.thumbnail_url && ( (post.thumbnail_url && (
<button <button
className="btn btn-sm text-monospace text-muted d-inline-block" className="btn btn-sm text-monospace text-muted d-inline-block"
data-tippy-content={i18n.t("expand_here")} data-tippy-content={I18NextService.i18n.t("expand_here")}
onClick={linkEvent(this, this.handleImageExpandClick)} onClick={linkEvent(this, this.handleImageExpandClick)}
> >
<Icon <Icon
@ -523,13 +518,13 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
))} ))}
{post.removed && ( {post.removed && (
<small className="ms-2 badge text-bg-secondary"> <small className="ms-2 badge text-bg-secondary">
{i18n.t("removed")} {I18NextService.i18n.t("removed")}
</small> </small>
)} )}
{post.deleted && ( {post.deleted && (
<small <small
className="unselectable pointer ms-2 text-muted font-italic" className="unselectable pointer ms-2 text-muted font-italic"
data-tippy-content={i18n.t("deleted")} data-tippy-content={I18NextService.i18n.t("deleted")}
> >
<Icon icon="trash" classes="icon-inline text-danger" /> <Icon icon="trash" classes="icon-inline text-danger" />
</small> </small>
@ -537,7 +532,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
{post.locked && ( {post.locked && (
<small <small
className="unselectable pointer ms-2 text-muted font-italic" className="unselectable pointer ms-2 text-muted font-italic"
data-tippy-content={i18n.t("locked")} data-tippy-content={I18NextService.i18n.t("locked")}
> >
<Icon icon="lock" classes="icon-inline text-danger" /> <Icon icon="lock" classes="icon-inline text-danger" />
</small> </small>
@ -545,8 +540,10 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
{post.featured_community && ( {post.featured_community && (
<small <small
className="unselectable pointer ms-2 text-muted font-italic" className="unselectable pointer ms-2 text-muted font-italic"
data-tippy-content={i18n.t("featured_in_community")} data-tippy-content={I18NextService.i18n.t(
aria-label={i18n.t("featured_in_community")} "featured_in_community"
)}
aria-label={I18NextService.i18n.t("featured_in_community")}
> >
<Icon icon="pin" classes="icon-inline text-primary" /> <Icon icon="pin" classes="icon-inline text-primary" />
</small> </small>
@ -554,15 +551,15 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
{post.featured_local && ( {post.featured_local && (
<small <small
className="unselectable pointer ms-2 text-muted font-italic" className="unselectable pointer ms-2 text-muted font-italic"
data-tippy-content={i18n.t("featured_in_local")} data-tippy-content={I18NextService.i18n.t("featured_in_local")}
aria-label={i18n.t("featured_in_local")} aria-label={I18NextService.i18n.t("featured_in_local")}
> >
<Icon icon="pin" classes="icon-inline text-secondary" /> <Icon icon="pin" classes="icon-inline text-secondary" />
</small> </small>
)} )}
{post.nsfw && ( {post.nsfw && (
<small className="ms-2 badge text-bg-danger"> <small className="ms-2 badge text-bg-danger">
{i18n.t("nsfw")} {I18NextService.i18n.t("nsfw")}
</small> </small>
)} )}
</div> </div>
@ -596,7 +593,9 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
return dupes && dupes.length > 0 ? ( return dupes && dupes.length > 0 ? (
<ul className="list-inline mb-1 small text-muted"> <ul className="list-inline mb-1 small text-muted">
<> <>
<li className="list-inline-item me-2">{i18n.t("cross_posted_to")}</li> <li className="list-inline-item me-2">
{I18NextService.i18n.t("cross_posted_to")}
</li>
{dupes.map(pv => ( {dupes.map(pv => (
<li key={pv.post.id} className="list-inline-item me-2"> <li key={pv.post.id} className="list-inline-item me-2">
<Link to={`/post/${pv.post.id}`}> <Link to={`/post/${pv.post.id}`}>
@ -631,7 +630,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
{!post.local && ( {!post.local && (
<a <a
className="btn btn-sm btn-animate text-muted py-0" className="btn btn-sm btn-animate text-muted py-0"
title={i18n.t("link")} title={I18NextService.i18n.t("link")}
href={post.ap_id} href={post.ap_id}
> >
<Icon icon="fedilink" inline /> <Icon icon="fedilink" inline />
@ -690,11 +689,11 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
<button <button
className="btn btn-sm btn-animate text-muted py-0 dropdown-toggle" className="btn btn-sm btn-animate text-muted py-0 dropdown-toggle"
onClick={linkEvent(this, this.handleShowAdvanced)} onClick={linkEvent(this, this.handleShowAdvanced)}
data-tippy-content={i18n.t("more")} data-tippy-content={I18NextService.i18n.t("more")}
data-bs-toggle="dropdown" data-bs-toggle="dropdown"
aria-expanded="false" aria-expanded="false"
aria-controls="advancedButtonsDropdown" aria-controls="advancedButtonsDropdown"
aria-label={i18n.t("more")} aria-label={I18NextService.i18n.t("more")}
> >
<Icon icon="more-vertical" inline /> <Icon icon="more-vertical" inline />
</button> </button>
@ -734,7 +733,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
get commentsButton() { get commentsButton() {
const post_view = this.postView; const post_view = this.postView;
const title = i18n.t("number_of_comments", { const title = I18NextService.i18n.t("number_of_comments", {
count: Number(post_view.counts.comments), count: Number(post_view.counts.comments),
formattedCount: Number(post_view.counts.comments), formattedCount: Number(post_view.counts.comments),
}); });
@ -746,11 +745,13 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
to={`/post/${post_view.post.id}?scrollToComments=true`} to={`/post/${post_view.post.id}?scrollToComments=true`}
data-tippy-content={title} data-tippy-content={title}
> >
<span className="me-1">
<Icon icon="message-square" classes="me-1" inline /> <Icon icon="message-square" classes="me-1" inline />
{post_view.counts.comments} {post_view.counts.comments}
</span>
{this.unreadCount && ( {this.unreadCount && (
<span className="badge text-bg-warning"> <span className="text-muted fst-italic">
({this.unreadCount} {i18n.t("new")}) ({this.unreadCount} {I18NextService.i18n.t("new")})
</span> </span>
)} )}
</Link> </Link>
@ -778,7 +779,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
}`} }`}
{...tippy} {...tippy}
onClick={linkEvent(this, this.handleUpvote)} onClick={linkEvent(this, this.handleUpvote)}
aria-label={i18n.t("upvote")} aria-label={I18NextService.i18n.t("upvote")}
aria-pressed={this.postView.my_vote === 1} aria-pressed={this.postView.my_vote === 1}
> >
{this.state.upvoteLoading ? ( {this.state.upvoteLoading ? (
@ -801,7 +802,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
}`} }`}
onClick={linkEvent(this, this.handleDownvote)} onClick={linkEvent(this, this.handleDownvote)}
{...tippy} {...tippy}
aria-label={i18n.t("downvote")} aria-label={I18NextService.i18n.t("downvote")}
aria-pressed={this.postView.my_vote === -1} aria-pressed={this.postView.my_vote === -1}
> >
{this.state.downvoteLoading ? ( {this.state.downvoteLoading ? (
@ -829,7 +830,9 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
get saveButton() { get saveButton() {
const saved = this.postView.saved; const saved = this.postView.saved;
const label = saved ? i18n.t("unsave") : i18n.t("save"); const label = saved
? I18NextService.i18n.t("unsave")
: I18NextService.i18n.t("save");
return ( return (
<button <button
className="btn btn-sm btn-animate text-muted py-0" className="btn btn-sm btn-animate text-muted py-0"
@ -862,9 +865,9 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
key: "", key: "",
search: "", search: "",
}} }}
title={i18n.t("cross_post")} title={I18NextService.i18n.t("cross_post")}
data-tippy-content={i18n.t("cross_post")} data-tippy-content={I18NextService.i18n.t("cross_post")}
aria-label={i18n.t("cross_post")} aria-label={I18NextService.i18n.t("cross_post")}
> >
<Icon icon="copy" inline /> <Icon icon="copy" inline />
</Link> </Link>
@ -876,10 +879,10 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
<button <button
className="btn btn-link btn-sm d-flex align-items-center rounded-0 dropdown-item" className="btn btn-link btn-sm d-flex align-items-center rounded-0 dropdown-item"
onClick={linkEvent(this, this.handleShowReportDialog)} onClick={linkEvent(this, this.handleShowReportDialog)}
aria-label={i18n.t("show_report_dialog")} aria-label={I18NextService.i18n.t("show_report_dialog")}
> >
<Icon classes="me-1" icon="flag" inline /> <Icon classes="me-1" icon="flag" inline />
{i18n.t("create_report")} {I18NextService.i18n.t("create_report")}
</button> </button>
); );
} }
@ -889,14 +892,14 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
<button <button
className="btn btn-link btn-sm d-flex align-items-center rounded-0 dropdown-item" className="btn btn-link btn-sm d-flex align-items-center rounded-0 dropdown-item"
onClick={linkEvent(this, this.handleBlockPersonClick)} onClick={linkEvent(this, this.handleBlockPersonClick)}
aria-label={i18n.t("block_user")} aria-label={I18NextService.i18n.t("block_user")}
> >
{this.state.blockLoading ? ( {this.state.blockLoading ? (
<Spinner /> <Spinner />
) : ( ) : (
<Icon classes="me-1" icon="slash" inline /> <Icon classes="me-1" icon="slash" inline />
)} )}
{i18n.t("block_user")} {I18NextService.i18n.t("block_user")}
</button> </button>
); );
} }
@ -906,17 +909,19 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
<button <button
className="btn btn-link btn-sm d-flex align-items-center rounded-0 dropdown-item" className="btn btn-link btn-sm d-flex align-items-center rounded-0 dropdown-item"
onClick={linkEvent(this, this.handleEditClick)} onClick={linkEvent(this, this.handleEditClick)}
aria-label={i18n.t("edit")} aria-label={I18NextService.i18n.t("edit")}
> >
<Icon classes="me-1" icon="edit" inline /> <Icon classes="me-1" icon="edit" inline />
{i18n.t("edit")} {I18NextService.i18n.t("edit")}
</button> </button>
); );
} }
get deleteButton() { get deleteButton() {
const deleted = this.postView.post.deleted; const deleted = this.postView.post.deleted;
const label = !deleted ? i18n.t("delete") : i18n.t("restore"); const label = !deleted
? I18NextService.i18n.t("delete")
: I18NextService.i18n.t("restore");
return ( return (
<button <button
className="btn btn-link btn-sm d-flex align-items-center rounded-0 dropdown-item" className="btn btn-link btn-sm d-flex align-items-center rounded-0 dropdown-item"
@ -944,8 +949,8 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
<button <button
className="btn btn-sm btn-animate text-muted py-0" className="btn btn-sm btn-animate text-muted py-0"
onClick={linkEvent(this, this.handleViewSource)} onClick={linkEvent(this, this.handleViewSource)}
data-tippy-content={i18n.t("view_source")} data-tippy-content={I18NextService.i18n.t("view_source")}
aria-label={i18n.t("view_source")} aria-label={I18NextService.i18n.t("view_source")}
> >
<Icon <Icon
icon="file-text" icon="file-text"
@ -958,7 +963,9 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
get lockButton() { get lockButton() {
const locked = this.postView.post.locked; const locked = this.postView.post.locked;
const label = locked ? i18n.t("unlock") : i18n.t("lock"); const label = locked
? I18NextService.i18n.t("unlock")
: I18NextService.i18n.t("lock");
return ( return (
<button <button
className="btn btn-link btn-sm d-flex align-items-center rounded-0 dropdown-item" className="btn btn-link btn-sm d-flex align-items-center rounded-0 dropdown-item"
@ -984,13 +991,13 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
get featureButtons() { get featureButtons() {
const featuredCommunity = this.postView.post.featured_community; const featuredCommunity = this.postView.post.featured_community;
const labelCommunity = featuredCommunity const labelCommunity = featuredCommunity
? i18n.t("unfeature_from_community") ? I18NextService.i18n.t("unfeature_from_community")
: i18n.t("feature_in_community"); : I18NextService.i18n.t("feature_in_community");
const featuredLocal = this.postView.post.featured_local; const featuredLocal = this.postView.post.featured_local;
const labelLocal = featuredLocal const labelLocal = featuredLocal
? i18n.t("unfeature_from_local") ? I18NextService.i18n.t("unfeature_from_local")
: i18n.t("feature_in_local"); : I18NextService.i18n.t("feature_in_local");
return ( return (
<> <>
<li> <li>
@ -1011,7 +1018,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
})} })}
inline inline
/> />
{i18n.t("community")} {I18NextService.i18n.t("community")}
</> </>
)} )}
</button> </button>
@ -1035,7 +1042,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
})} })}
inline inline
/> />
{i18n.t("local")} {I18NextService.i18n.t("local")}
</> </>
)} )}
</button> </button>
@ -1059,9 +1066,9 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
{this.state.removeLoading ? ( {this.state.removeLoading ? (
<Spinner /> <Spinner />
) : !removed ? ( ) : !removed ? (
i18n.t("remove") I18NextService.i18n.t("remove")
) : ( ) : (
i18n.t("restore") I18NextService.i18n.t("restore")
)} )}
</button> </button>
); );
@ -1086,9 +1093,9 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
this, this,
this.handleModBanFromCommunityShow this.handleModBanFromCommunityShow
)} )}
aria-label={i18n.t("ban_from_community")} aria-label={I18NextService.i18n.t("ban_from_community")}
> >
{i18n.t("ban_from_community")} {I18NextService.i18n.t("ban_from_community")}
</button> </button>
) : ( ) : (
<button <button
@ -1097,9 +1104,13 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
this, this,
this.handleModBanFromCommunitySubmit this.handleModBanFromCommunitySubmit
)} )}
aria-label={i18n.t("unban")} aria-label={I18NextService.i18n.t("unban")}
> >
{this.state.banLoading ? <Spinner /> : i18n.t("unban")} {this.state.banLoading ? (
<Spinner />
) : (
I18NextService.i18n.t("unban")
)}
</button> </button>
))} ))}
{!post_view.creator_banned_from_community && ( {!post_view.creator_banned_from_community && (
@ -1108,16 +1119,16 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
onClick={linkEvent(this, this.handleAddModToCommunity)} onClick={linkEvent(this, this.handleAddModToCommunity)}
aria-label={ aria-label={
this.creatorIsMod_ this.creatorIsMod_
? i18n.t("remove_as_mod") ? I18NextService.i18n.t("remove_as_mod")
: i18n.t("appoint_as_mod") : I18NextService.i18n.t("appoint_as_mod")
} }
> >
{this.state.addModLoading ? ( {this.state.addModLoading ? (
<Spinner /> <Spinner />
) : this.creatorIsMod_ ? ( ) : this.creatorIsMod_ ? (
i18n.t("remove_as_mod") I18NextService.i18n.t("remove_as_mod")
) : ( ) : (
i18n.t("appoint_as_mod") I18NextService.i18n.t("appoint_as_mod")
)} )}
</button> </button>
)} )}
@ -1134,24 +1145,28 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
this, this,
this.handleShowConfirmTransferCommunity this.handleShowConfirmTransferCommunity
)} )}
aria-label={i18n.t("transfer_community")} aria-label={I18NextService.i18n.t("transfer_community")}
> >
{i18n.t("transfer_community")} {I18NextService.i18n.t("transfer_community")}
</button> </button>
) : ( ) : (
<> <>
<button <button
className="d-inline-block me-1 btn btn-link btn-animate text-muted py-0" className="d-inline-block me-1 btn btn-link btn-animate text-muted py-0"
aria-label={i18n.t("are_you_sure")} aria-label={I18NextService.i18n.t("are_you_sure")}
> >
{i18n.t("are_you_sure")} {I18NextService.i18n.t("are_you_sure")}
</button> </button>
<button <button
className="btn btn-link btn-animate text-muted py-0 d-inline-block me-1" className="btn btn-link btn-animate text-muted py-0 d-inline-block me-1"
aria-label={i18n.t("yes")} aria-label={I18NextService.i18n.t("yes")}
onClick={linkEvent(this, this.handleTransferCommunity)} onClick={linkEvent(this, this.handleTransferCommunity)}
> >
{this.state.transferLoading ? <Spinner /> : i18n.t("yes")} {this.state.transferLoading ? (
<Spinner />
) : (
I18NextService.i18n.t("yes")
)}
</button> </button>
<button <button
className="btn btn-link btn-animate text-muted py-0 d-inline-block" className="btn btn-link btn-animate text-muted py-0 d-inline-block"
@ -1159,9 +1174,9 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
this, this,
this.handleCancelShowConfirmTransferCommunity this.handleCancelShowConfirmTransferCommunity
)} )}
aria-label={i18n.t("no")} aria-label={I18NextService.i18n.t("no")}
> >
{i18n.t("no")} {I18NextService.i18n.t("no")}
</button> </button>
</> </>
))} ))}
@ -1174,36 +1189,36 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
<button <button
className="btn btn-link btn-animate text-muted py-0" className="btn btn-link btn-animate text-muted py-0"
onClick={linkEvent(this, this.handleModBanShow)} onClick={linkEvent(this, this.handleModBanShow)}
aria-label={i18n.t("ban_from_site")} aria-label={I18NextService.i18n.t("ban_from_site")}
> >
{i18n.t("ban_from_site")} {I18NextService.i18n.t("ban_from_site")}
</button> </button>
) : ( ) : (
<button <button
className="btn btn-link btn-animate text-muted py-0" className="btn btn-link btn-animate text-muted py-0"
onClick={linkEvent(this, this.handleModBanSubmit)} onClick={linkEvent(this, this.handleModBanSubmit)}
aria-label={i18n.t("unban_from_site")} aria-label={I18NextService.i18n.t("unban_from_site")}
> >
{this.state.banLoading ? ( {this.state.banLoading ? (
<Spinner /> <Spinner />
) : ( ) : (
i18n.t("unban_from_site") I18NextService.i18n.t("unban_from_site")
)} )}
</button> </button>
)} )}
<button <button
className="btn btn-link btn-animate text-muted py-0" className="btn btn-link btn-animate text-muted py-0"
onClick={linkEvent(this, this.handlePurgePersonShow)} onClick={linkEvent(this, this.handlePurgePersonShow)}
aria-label={i18n.t("purge_user")} aria-label={I18NextService.i18n.t("purge_user")}
> >
{i18n.t("purge_user")} {I18NextService.i18n.t("purge_user")}
</button> </button>
<button <button
className="btn btn-link btn-animate text-muted py-0" className="btn btn-link btn-animate text-muted py-0"
onClick={linkEvent(this, this.handlePurgePostShow)} onClick={linkEvent(this, this.handlePurgePostShow)}
aria-label={i18n.t("purge_post")} aria-label={I18NextService.i18n.t("purge_post")}
> >
{i18n.t("purge_post")} {I18NextService.i18n.t("purge_post")}
</button> </button>
</> </>
)} )}
@ -1213,16 +1228,16 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
onClick={linkEvent(this, this.handleAddAdmin)} onClick={linkEvent(this, this.handleAddAdmin)}
aria-label={ aria-label={
this.creatorIsAdmin_ this.creatorIsAdmin_
? i18n.t("remove_as_admin") ? I18NextService.i18n.t("remove_as_admin")
: i18n.t("appoint_as_admin") : I18NextService.i18n.t("appoint_as_admin")
} }
> >
{this.state.addAdminLoading ? ( {this.state.addAdminLoading ? (
<Spinner /> <Spinner />
) : this.creatorIsAdmin_ ? ( ) : this.creatorIsAdmin_ ? (
i18n.t("remove_as_admin") I18NextService.i18n.t("remove_as_admin")
) : ( ) : (
i18n.t("appoint_as_admin") I18NextService.i18n.t("appoint_as_admin")
)} )}
</button> </button>
)} )}
@ -1237,8 +1252,8 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
const post = this.postView; const post = this.postView;
const purgeTypeText = const purgeTypeText =
this.state.purgeType == PurgeType.Post this.state.purgeType == PurgeType.Post
? i18n.t("purge_post") ? I18NextService.i18n.t("purge_post")
: `${i18n.t("purge")} ${post.creator.name}`; : `${I18NextService.i18n.t("purge")} ${post.creator.name}`;
return ( return (
<> <>
{this.state.showRemoveDialog && ( {this.state.showRemoveDialog && (
@ -1250,22 +1265,26 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
className="visually-hidden" className="visually-hidden"
htmlFor="post-listing-remove-reason" htmlFor="post-listing-remove-reason"
> >
{i18n.t("reason")} {I18NextService.i18n.t("reason")}
</label> </label>
<input <input
type="text" type="text"
id="post-listing-remove-reason" id="post-listing-remove-reason"
className="form-control me-2" className="form-control me-2"
placeholder={i18n.t("reason")} placeholder={I18NextService.i18n.t("reason")}
value={this.state.removeReason} value={this.state.removeReason}
onInput={linkEvent(this, this.handleModRemoveReasonChange)} onInput={linkEvent(this, this.handleModRemoveReasonChange)}
/> />
<button <button
type="submit" type="submit"
className="btn btn-secondary" className="btn btn-secondary"
aria-label={i18n.t("remove_post")} aria-label={I18NextService.i18n.t("remove_post")}
> >
{this.state.removeLoading ? <Spinner /> : i18n.t("remove_post")} {this.state.removeLoading ? (
<Spinner />
) : (
I18NextService.i18n.t("remove_post")
)}
</button> </button>
</form> </form>
)} )}
@ -1276,24 +1295,24 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
className="col-form-label" className="col-form-label"
htmlFor="post-listing-ban-reason" htmlFor="post-listing-ban-reason"
> >
{i18n.t("reason")} {I18NextService.i18n.t("reason")}
</label> </label>
<input <input
type="text" type="text"
id="post-listing-ban-reason" id="post-listing-ban-reason"
className="form-control me-2" className="form-control me-2"
placeholder={i18n.t("reason")} placeholder={I18NextService.i18n.t("reason")}
value={this.state.banReason} value={this.state.banReason}
onInput={linkEvent(this, this.handleModBanReasonChange)} onInput={linkEvent(this, this.handleModBanReasonChange)}
/> />
<label className="col-form-label" htmlFor={`mod-ban-expires`}> <label className="col-form-label" htmlFor={`mod-ban-expires`}>
{i18n.t("expires")} {I18NextService.i18n.t("expires")}
</label> </label>
<input <input
type="number" type="number"
id={`mod-ban-expires`} id={`mod-ban-expires`}
className="form-control me-2" className="form-control me-2"
placeholder={i18n.t("number_of_days")} placeholder={I18NextService.i18n.t("number_of_days")}
value={this.state.banExpireDays} value={this.state.banExpireDays}
onInput={linkEvent(this, this.handleModBanExpireDaysChange)} onInput={linkEvent(this, this.handleModBanExpireDaysChange)}
/> />
@ -1309,9 +1328,9 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
<label <label
className="form-check-label" className="form-check-label"
htmlFor="mod-ban-remove-data" htmlFor="mod-ban-remove-data"
title={i18n.t("remove_content_more")} title={I18NextService.i18n.t("remove_content_more")}
> >
{i18n.t("remove_content")} {I18NextService.i18n.t("remove_content")}
</label> </label>
</div> </div>
</div> </div>
@ -1319,19 +1338,19 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
{/* TODO hold off on expires until later */} {/* TODO hold off on expires until later */}
{/* <div class="mb-3 row"> */} {/* <div class="mb-3 row"> */}
{/* <label class="col-form-label">Expires</label> */} {/* <label class="col-form-label">Expires</label> */}
{/* <input type="date" class="form-control me-2" placeholder={i18n.t('expires')} value={this.state.banExpires} onInput={linkEvent(this, this.handleModBanExpiresChange)} /> */} {/* <input type="date" class="form-control me-2" placeholder={I18NextService.i18n.t('expires')} value={this.state.banExpires} onInput={linkEvent(this, this.handleModBanExpiresChange)} /> */}
{/* </div> */} {/* </div> */}
<div className="mb-3 row"> <div className="mb-3 row">
<button <button
type="submit" type="submit"
className="btn btn-secondary" className="btn btn-secondary"
aria-label={i18n.t("ban")} aria-label={I18NextService.i18n.t("ban")}
> >
{this.state.banLoading ? ( {this.state.banLoading ? (
<Spinner /> <Spinner />
) : ( ) : (
<span> <span>
{i18n.t("ban")} {post.creator.name} {I18NextService.i18n.t("ban")} {post.creator.name}
</span> </span>
)} )}
</button> </button>
@ -1344,13 +1363,13 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
onSubmit={linkEvent(this, this.handleReportSubmit)} onSubmit={linkEvent(this, this.handleReportSubmit)}
> >
<label className="visually-hidden" htmlFor="post-report-reason"> <label className="visually-hidden" htmlFor="post-report-reason">
{i18n.t("reason")} {I18NextService.i18n.t("reason")}
</label> </label>
<input <input
type="text" type="text"
id="post-report-reason" id="post-report-reason"
className="form-control me-2" className="form-control me-2"
placeholder={i18n.t("reason")} placeholder={I18NextService.i18n.t("reason")}
required required
value={this.state.reportReason} value={this.state.reportReason}
onInput={linkEvent(this, this.handleReportReasonChange)} onInput={linkEvent(this, this.handleReportReasonChange)}
@ -1358,9 +1377,13 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
<button <button
type="submit" type="submit"
className="btn btn-secondary" className="btn btn-secondary"
aria-label={i18n.t("create_report")} aria-label={I18NextService.i18n.t("create_report")}
> >
{this.state.reportLoading ? <Spinner /> : i18n.t("create_report")} {this.state.reportLoading ? (
<Spinner />
) : (
I18NextService.i18n.t("create_report")
)}
</button> </button>
</form> </form>
)} )}
@ -1371,13 +1394,13 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
> >
<PurgeWarning /> <PurgeWarning />
<label className="visually-hidden" htmlFor="purge-reason"> <label className="visually-hidden" htmlFor="purge-reason">
{i18n.t("reason")} {I18NextService.i18n.t("reason")}
</label> </label>
<input <input
type="text" type="text"
id="purge-reason" id="purge-reason"
className="form-control me-2" className="form-control me-2"
placeholder={i18n.t("reason")} placeholder={I18NextService.i18n.t("reason")}
value={this.state.purgeReason} value={this.state.purgeReason}
onInput={linkEvent(this, this.handlePurgeReasonChange)} onInput={linkEvent(this, this.handlePurgeReasonChange)}
/> />
@ -1572,10 +1595,9 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
const body = post.body; const body = post.body;
return body return body
? `${i18n.t("cross_posted_from")} ${post.ap_id}\n\n${body.replace( ? `${I18NextService.i18n.t("cross_posted_from")} ${
/^/gm, post.ap_id
"> " }\n\n${body.replace(/^/gm, "> ")}`
)}`
: undefined; : undefined;
} }
@ -1837,17 +1859,17 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
} }
get pointsTippy(): string { get pointsTippy(): string {
const points = i18n.t("number_of_points", { const points = I18NextService.i18n.t("number_of_points", {
count: Number(this.postView.counts.score), count: Number(this.postView.counts.score),
formattedCount: Number(this.postView.counts.score), formattedCount: Number(this.postView.counts.score),
}); });
const upvotes = i18n.t("number_of_upvotes", { const upvotes = I18NextService.i18n.t("number_of_upvotes", {
count: Number(this.postView.counts.upvotes), count: Number(this.postView.counts.upvotes),
formattedCount: Number(this.postView.counts.upvotes), formattedCount: Number(this.postView.counts.upvotes),
}); });
const downvotes = i18n.t("number_of_downvotes", { const downvotes = I18NextService.i18n.t("number_of_downvotes", {
count: Number(this.postView.counts.downvotes), count: Number(this.postView.counts.downvotes),
formattedCount: Number(this.postView.counts.downvotes), formattedCount: Number(this.postView.counts.downvotes),
}); });

View file

@ -21,7 +21,7 @@ import {
SavePost, SavePost,
TransferCommunity, TransferCommunity,
} from "lemmy-js-client"; } from "lemmy-js-client";
import { i18n } from "../../i18next"; import { I18NextService } from "../../services";
import { PostListing } from "./post-listing"; import { PostListing } from "./post-listing";
interface PostListingsProps { interface PostListingsProps {
@ -101,7 +101,7 @@ export class PostListings extends Component<PostListingsProps, any> {
)) ))
) : ( ) : (
<> <>
<div>{i18n.t("no_posts")}</div> <div>{I18NextService.i18n.t("no_posts")}</div>
{this.props.showCommunity && ( {this.props.showCommunity && (
<T i18nKey="subscribe_to_communities"> <T i18nKey="subscribe_to_communities">
#<Link to="/communities">#</Link> #<Link to="/communities">#</Link>

View file

@ -1,8 +1,8 @@
import { myAuthRequired } from "@utils/app";
import { Component, InfernoNode, linkEvent } from "inferno"; import { Component, InfernoNode, linkEvent } from "inferno";
import { T } from "inferno-i18next-dess"; import { T } from "inferno-i18next-dess";
import { PostReportView, PostView, ResolvePostReport } from "lemmy-js-client"; import { PostReportView, PostView, ResolvePostReport } from "lemmy-js-client";
import { i18n } from "../../i18next"; import { I18NextService } from "../../services";
import { myAuthRequired } from "../../utils";
import { Icon, Spinner } from "../common/icon"; import { Icon, Spinner } from "../common/icon";
import { PersonListing } from "../person/person-listing"; import { PersonListing } from "../person/person-listing";
import { PostListing } from "./post-listing"; import { PostListing } from "./post-listing";
@ -37,7 +37,7 @@ export class PostReport extends Component<PostReportProps, PostReportState> {
const r = this.props.report; const r = this.props.report;
const resolver = r.resolver; const resolver = r.resolver;
const post = r.post; const post = r.post;
const tippyContent = i18n.t( const tippyContent = I18NextService.i18n.t(
r.post_report.resolved ? "unresolve_report" : "resolve_report" r.post_report.resolved ? "unresolve_report" : "resolve_report"
); );
@ -89,10 +89,11 @@ export class PostReport extends Component<PostReportProps, PostReportState> {
onTransferCommunity={() => {}} onTransferCommunity={() => {}}
/> />
<div> <div>
{i18n.t("reporter")}: <PersonListing person={r.creator} /> {I18NextService.i18n.t("reporter")}:{" "}
<PersonListing person={r.creator} />
</div> </div>
<div> <div>
{i18n.t("reason")}: {r.post_report.reason} {I18NextService.i18n.t("reason")}: {r.post_report.reason}
</div> </div>
{resolver && ( {resolver && (
<div> <div>

View file

@ -1,7 +1,29 @@
import { isBrowser } from "@utils/browser"; import {
buildCommentsTree,
commentsToFlatNodes,
editComment,
editWith,
enableDownvotes,
enableNsfw,
getCommentIdFromProps,
getCommentParentId,
getDepthFromComment,
getIdFromProps,
myAuth,
setIsoData,
updateCommunityBlock,
updatePersonBlock,
} from "@utils/app";
import {
isBrowser,
restoreScrollPosition,
saveScrollPosition,
} from "@utils/browser";
import { debounce } from "@utils/helpers"; import { debounce } from "@utils/helpers";
import { isImage } from "@utils/media";
import { RouteDataResponse } from "@utils/types";
import autosize from "autosize"; import autosize from "autosize";
import { Component, createRef, linkEvent, RefObject } from "inferno"; import { Component, RefObject, createRef, linkEvent } from "inferno";
import { import {
AddAdmin, AddAdmin,
AddModToCommunity, AddModToCommunity,
@ -53,38 +75,16 @@ import {
SavePost, SavePost,
TransferCommunity, TransferCommunity,
} from "lemmy-js-client"; } from "lemmy-js-client";
import { i18n } from "../../i18next"; import { commentTreeMaxDepth } from "../../config";
import { import {
CommentNodeI, CommentNodeI,
CommentViewType, CommentViewType,
InitialFetchRequest, InitialFetchRequest,
} from "../../interfaces"; } from "../../interfaces";
import { UserService } from "../../services"; import { FirstLoadService, I18NextService, UserService } from "../../services";
import { FirstLoadService } from "../../services/FirstLoadService";
import { HttpService, RequestState } from "../../services/HttpService"; import { HttpService, RequestState } from "../../services/HttpService";
import { import { setupTippy } from "../../tippy";
buildCommentsTree, import { toast } from "../../toast";
commentsToFlatNodes,
commentTreeMaxDepth,
editComment,
editWith,
enableDownvotes,
enableNsfw,
getCommentIdFromProps,
getCommentParentId,
getDepthFromComment,
getIdFromProps,
isImage,
myAuth,
restoreScrollPosition,
RouteDataResponse,
saveScrollPosition,
setIsoData,
setupTippy,
toast,
updateCommunityBlock,
updatePersonBlock,
} from "../../utils";
import { CommentForm } from "../comment/comment-form"; import { CommentForm } from "../comment/comment-form";
import { CommentNodes } from "../comment/comment-nodes"; import { CommentNodes } from "../comment/comment-nodes";
import { HtmlTags } from "../common/html-tags"; import { HtmlTags } from "../common/html-tags";
@ -398,7 +398,7 @@ export class Post extends Component<any, PostState> {
className="btn btn-secondary d-inline-block mb-2 me-3" className="btn btn-secondary d-inline-block mb-2 me-3"
onClick={linkEvent(this, this.handleShowSidebarMobile)} onClick={linkEvent(this, this.handleShowSidebarMobile)}
> >
{i18n.t("sidebar")}{" "} {I18NextService.i18n.t("sidebar")}{" "}
<Icon <Icon
icon={ icon={
this.state.showSidebarMobile this.state.showSidebarMobile
@ -436,7 +436,7 @@ export class Post extends Component<any, PostState> {
this.state.commentSort === "Hot" && "active" this.state.commentSort === "Hot" && "active"
}`} }`}
> >
{i18n.t("hot")} {I18NextService.i18n.t("hot")}
<input <input
type="radio" type="radio"
className="btn-check" className="btn-check"
@ -450,7 +450,7 @@ export class Post extends Component<any, PostState> {
this.state.commentSort === "Top" && "active" this.state.commentSort === "Top" && "active"
}`} }`}
> >
{i18n.t("top")} {I18NextService.i18n.t("top")}
<input <input
type="radio" type="radio"
className="btn-check" className="btn-check"
@ -464,7 +464,7 @@ export class Post extends Component<any, PostState> {
this.state.commentSort === "New" && "active" this.state.commentSort === "New" && "active"
}`} }`}
> >
{i18n.t("new")} {I18NextService.i18n.t("new")}
<input <input
type="radio" type="radio"
className="btn-check" className="btn-check"
@ -478,7 +478,7 @@ export class Post extends Component<any, PostState> {
this.state.commentSort === "Old" && "active" this.state.commentSort === "Old" && "active"
}`} }`}
> >
{i18n.t("old")} {I18NextService.i18n.t("old")}
<input <input
type="radio" type="radio"
className="btn-check" className="btn-check"
@ -494,7 +494,7 @@ export class Post extends Component<any, PostState> {
this.state.commentViewType === CommentViewType.Flat && "active" this.state.commentViewType === CommentViewType.Flat && "active"
}`} }`}
> >
{i18n.t("chat")} {I18NextService.i18n.t("chat")}
<input <input
type="radio" type="radio"
className="btn-check" className="btn-check"
@ -593,14 +593,14 @@ export class Post extends Component<any, PostState> {
className="ps-0 d-block btn btn-link text-muted" className="ps-0 d-block btn btn-link text-muted"
onClick={linkEvent(this, this.handleViewPost)} onClick={linkEvent(this, this.handleViewPost)}
> >
{i18n.t("view_all_comments")} {I18NextService.i18n.t("view_all_comments")}
</button> </button>
{showContextButton && ( {showContextButton && (
<button <button
className="ps-0 d-block btn btn-link text-muted" className="ps-0 d-block btn btn-link text-muted"
onClick={linkEvent(this, this.handleViewContext)} onClick={linkEvent(this, this.handleViewContext)}
> >
{i18n.t("show_context")} {I18NextService.i18n.t("show_context")}
</button> </button>
)} )}
</> </>
@ -834,14 +834,14 @@ export class Post extends Component<any, PostState> {
async handleCommentReport(form: CreateCommentReport) { async handleCommentReport(form: CreateCommentReport) {
const reportRes = await HttpService.client.createCommentReport(form); const reportRes = await HttpService.client.createCommentReport(form);
if (reportRes.state == "success") { if (reportRes.state == "success") {
toast(i18n.t("report_created")); toast(I18NextService.i18n.t("report_created"));
} }
} }
async handlePostReport(form: CreatePostReport) { async handlePostReport(form: CreatePostReport) {
const reportRes = await HttpService.client.createPostReport(form); const reportRes = await HttpService.client.createPostReport(form);
if (reportRes.state == "success") { if (reportRes.state == "success") {
toast(i18n.t("report_created")); toast(I18NextService.i18n.t("report_created"));
} }
} }
@ -980,7 +980,7 @@ export class Post extends Component<any, PostState> {
purgeItem(purgeRes: RequestState<PurgeItemResponse>) { purgeItem(purgeRes: RequestState<PurgeItemResponse>) {
if (purgeRes.state == "success") { if (purgeRes.state == "success") {
toast(i18n.t("purge_success")); toast(I18NextService.i18n.t("purge_success"));
this.context.router.history.push(`/`); this.context.router.history.push(`/`);
} }
} }

View file

@ -1,3 +1,5 @@
import { getRecipientIdFromProps, myAuth, setIsoData } from "@utils/app";
import { RouteDataResponse } from "@utils/types";
import { Component } from "inferno"; import { Component } from "inferno";
import { import {
CreatePrivateMessage as CreatePrivateMessageI, CreatePrivateMessage as CreatePrivateMessageI,
@ -5,17 +7,10 @@ import {
GetPersonDetailsResponse, GetPersonDetailsResponse,
GetSiteResponse, GetSiteResponse,
} from "lemmy-js-client"; } from "lemmy-js-client";
import { i18n } from "../../i18next";
import { InitialFetchRequest } from "../../interfaces"; import { InitialFetchRequest } from "../../interfaces";
import { FirstLoadService } from "../../services/FirstLoadService"; import { FirstLoadService, I18NextService } from "../../services";
import { HttpService, RequestState } from "../../services/HttpService"; import { HttpService, RequestState } from "../../services/HttpService";
import { import { toast } from "../../toast";
RouteDataResponse,
getRecipientIdFromProps,
myAuth,
setIsoData,
toast,
} from "../../utils";
import { HtmlTags } from "../common/html-tags"; import { HtmlTags } from "../common/html-tags";
import { Spinner } from "../common/icon"; import { Spinner } from "../common/icon";
import { PrivateMessageForm } from "./private-message-form"; import { PrivateMessageForm } from "./private-message-form";
@ -101,7 +96,7 @@ export class CreatePrivateMessage extends Component<
get documentTitle(): string { get documentTitle(): string {
if (this.state.recipientRes.state == "success") { if (this.state.recipientRes.state == "success") {
const name_ = this.state.recipientRes.data.person_view.person.name; const name_ = this.state.recipientRes.data.person_view.person.name;
return `${i18n.t("create_private_message")} - ${name_}`; return `${I18NextService.i18n.t("create_private_message")} - ${name_}`;
} else { } else {
return ""; return "";
} }
@ -120,7 +115,7 @@ export class CreatePrivateMessage extends Component<
return ( return (
<div className="row"> <div className="row">
<div className="col-12 col-lg-6 offset-lg-3 mb-4"> <div className="col-12 col-lg-6 offset-lg-3 mb-4">
<h5>{i18n.t("create_private_message")}</h5> <h5>{I18NextService.i18n.t("create_private_message")}</h5>
<PrivateMessageForm <PrivateMessageForm
onCreate={this.handlePrivateMessageCreate} onCreate={this.handlePrivateMessageCreate}
recipient={res.person_view.person} recipient={res.person_view.person}
@ -148,7 +143,7 @@ export class CreatePrivateMessage extends Component<
const res = await HttpService.client.createPrivateMessage(form); const res = await HttpService.client.createPrivateMessage(form);
if (res.state == "success") { if (res.state == "success") {
toast(i18n.t("message_sent")); toast(I18NextService.i18n.t("message_sent"));
// Navigate to the front // Navigate to the front
this.context.router.history.push("/"); this.context.router.history.push("/");

View file

@ -1,3 +1,5 @@
import { myAuthRequired } from "@utils/app";
import { capitalizeFirstLetter } from "@utils/helpers";
import { Component, InfernoNode, linkEvent } from "inferno"; import { Component, InfernoNode, linkEvent } from "inferno";
import { T } from "inferno-i18next-dess"; import { T } from "inferno-i18next-dess";
import { import {
@ -6,13 +8,9 @@ import {
Person, Person,
PrivateMessageView, PrivateMessageView,
} from "lemmy-js-client"; } from "lemmy-js-client";
import { i18n } from "../../i18next"; import { relTags } from "../../config";
import { import { I18NextService } from "../../services";
capitalizeFirstLetter, import { setupTippy } from "../../tippy";
myAuthRequired,
relTags,
setupTippy,
} from "../../utils";
import { Icon, Spinner } from "../common/icon"; import { Icon, Spinner } from "../common/icon";
import { MarkdownTextArea } from "../common/markdown-textarea"; import { MarkdownTextArea } from "../common/markdown-textarea";
import NavigationPrompt from "../common/navigation-prompt"; import NavigationPrompt from "../common/navigation-prompt";
@ -68,7 +66,7 @@ export class PrivateMessageForm extends Component<
// TODO // TODO
// <Prompt // <Prompt
// when={!this.state.loading && this.state.content} // when={!this.state.loading && this.state.content}
// message={i18n.t("block_leaving")} // message={I18NextService.i18n.t("block_leaving")}
// /> // />
render() { render() {
@ -85,7 +83,7 @@ export class PrivateMessageForm extends Component<
{!this.props.privateMessageView && ( {!this.props.privateMessageView && (
<div className="mb-3 row"> <div className="mb-3 row">
<label className="col-sm-2 col-form-label"> <label className="col-sm-2 col-form-label">
{capitalizeFirstLetter(i18n.t("to"))} {capitalizeFirstLetter(I18NextService.i18n.t("to"))}
</label> </label>
<div className="col-sm-10 form-control-plaintext"> <div className="col-sm-10 form-control-plaintext">
@ -95,12 +93,14 @@ export class PrivateMessageForm extends Component<
)} )}
<div className="mb-3 row"> <div className="mb-3 row">
<label className="col-sm-2 col-form-label"> <label className="col-sm-2 col-form-label">
{i18n.t("message")} {I18NextService.i18n.t("message")}
<button <button
className="btn btn-link text-warning d-inline-block" className="btn btn-link text-warning d-inline-block"
onClick={linkEvent(this, this.handleShowDisclaimer)} onClick={linkEvent(this, this.handleShowDisclaimer)}
data-tippy-content={i18n.t("private_message_disclaimer")} data-tippy-content={I18NextService.i18n.t(
aria-label={i18n.t("private_message_disclaimer")} "private_message_disclaimer"
)}
aria-label={I18NextService.i18n.t("private_message_disclaimer")}
> >
<Icon icon="alert-triangle" classes="icon-inline" /> <Icon icon="alert-triangle" classes="icon-inline" />
</button> </button>
@ -144,9 +144,9 @@ export class PrivateMessageForm extends Component<
{this.state.loading ? ( {this.state.loading ? (
<Spinner /> <Spinner />
) : this.props.privateMessageView ? ( ) : this.props.privateMessageView ? (
capitalizeFirstLetter(i18n.t("save")) capitalizeFirstLetter(I18NextService.i18n.t("save"))
) : ( ) : (
capitalizeFirstLetter(i18n.t("send_message")) capitalizeFirstLetter(I18NextService.i18n.t("send_message"))
)} )}
</button> </button>
{this.props.privateMessageView && ( {this.props.privateMessageView && (
@ -155,7 +155,7 @@ export class PrivateMessageForm extends Component<
className="btn btn-secondary" className="btn btn-secondary"
onClick={linkEvent(this, this.handleCancel)} onClick={linkEvent(this, this.handleCancel)}
> >
{i18n.t("cancel")} {I18NextService.i18n.t("cancel")}
</button> </button>
)} )}
<ul className="d-inline-block float-right list-inline mb-1 text-muted font-weight-bold"> <ul className="d-inline-block float-right list-inline mb-1 text-muted font-weight-bold">

View file

@ -1,11 +1,12 @@
import { myAuthRequired } from "@utils/app";
import { Component, InfernoNode, linkEvent } from "inferno"; import { Component, InfernoNode, linkEvent } from "inferno";
import { T } from "inferno-i18next-dess"; import { T } from "inferno-i18next-dess";
import { import {
PrivateMessageReportView, PrivateMessageReportView,
ResolvePrivateMessageReport, ResolvePrivateMessageReport,
} from "lemmy-js-client"; } from "lemmy-js-client";
import { i18n } from "../../i18next"; import { mdToHtml } from "../../markdown";
import { mdToHtml, myAuthRequired } from "../../utils"; import { I18NextService } from "../../services";
import { Icon, Spinner } from "../common/icon"; import { Icon, Spinner } from "../common/icon";
import { PersonListing } from "../person/person-listing"; import { PersonListing } from "../person/person-listing";
@ -38,28 +39,29 @@ export class PrivateMessageReport extends Component<Props, State> {
render() { render() {
const r = this.props.report; const r = this.props.report;
const pmr = r.private_message_report; const pmr = r.private_message_report;
const tippyContent = i18n.t( const tippyContent = I18NextService.i18n.t(
r.private_message_report.resolved ? "unresolve_report" : "resolve_report" r.private_message_report.resolved ? "unresolve_report" : "resolve_report"
); );
return ( return (
<div className="private-message-report"> <div className="private-message-report">
<div> <div>
{i18n.t("creator")}:{" "} {I18NextService.i18n.t("creator")}:{" "}
<PersonListing person={r.private_message_creator} /> <PersonListing person={r.private_message_creator} />
</div> </div>
<div> <div>
{i18n.t("message")}: {I18NextService.i18n.t("message")}:
<div <div
className="md-div" className="md-div"
dangerouslySetInnerHTML={mdToHtml(pmr.original_pm_text)} dangerouslySetInnerHTML={mdToHtml(pmr.original_pm_text)}
/> />
</div> </div>
<div> <div>
{i18n.t("reporter")}: <PersonListing person={r.creator} /> {I18NextService.i18n.t("reporter")}:{" "}
<PersonListing person={r.creator} />
</div> </div>
<div> <div>
{i18n.t("reason")}: {pmr.reason} {I18NextService.i18n.t("reason")}: {pmr.reason}
</div> </div>
{r.resolver && ( {r.resolver && (
<div> <div>

View file

@ -1,3 +1,4 @@
import { myAuthRequired } from "@utils/app";
import { Component, InfernoNode, linkEvent } from "inferno"; import { Component, InfernoNode, linkEvent } from "inferno";
import { import {
CreatePrivateMessage, CreatePrivateMessage,
@ -8,9 +9,8 @@ import {
Person, Person,
PrivateMessageView, PrivateMessageView,
} from "lemmy-js-client"; } from "lemmy-js-client";
import { i18n } from "../../i18next"; import { mdToHtml } from "../../markdown";
import { UserService } from "../../services"; import { I18NextService, UserService } from "../../services";
import { mdToHtml, myAuthRequired } from "../../utils";
import { Icon, Spinner } from "../common/icon"; import { Icon, Spinner } from "../common/icon";
import { MomentTime } from "../common/moment-time"; import { MomentTime } from "../common/moment-time";
import { PersonListing } from "../person/person-listing"; import { PersonListing } from "../person/person-listing";
@ -93,7 +93,9 @@ export class PrivateMessage extends Component<
<ul className="list-inline mb-0 text-muted small"> <ul className="list-inline mb-0 text-muted small">
{/* TODO refactor this */} {/* TODO refactor this */}
<li className="list-inline-item"> <li className="list-inline-item">
{this.mine ? i18n.t("to") : i18n.t("from")} {this.mine
? I18NextService.i18n.t("to")
: I18NextService.i18n.t("from")}
</li> </li>
<li className="list-inline-item"> <li className="list-inline-item">
<PersonListing person={otherPerson} /> <PersonListing person={otherPerson} />
@ -147,13 +149,13 @@ export class PrivateMessage extends Component<
onClick={linkEvent(this, this.handleMarkRead)} onClick={linkEvent(this, this.handleMarkRead)}
data-tippy-content={ data-tippy-content={
message_view.private_message.read message_view.private_message.read
? i18n.t("mark_as_unread") ? I18NextService.i18n.t("mark_as_unread")
: i18n.t("mark_as_read") : I18NextService.i18n.t("mark_as_read")
} }
aria-label={ aria-label={
message_view.private_message.read message_view.private_message.read
? i18n.t("mark_as_unread") ? I18NextService.i18n.t("mark_as_unread")
: i18n.t("mark_as_read") : I18NextService.i18n.t("mark_as_read")
} }
> >
{this.state.readLoading ? ( {this.state.readLoading ? (
@ -174,8 +176,8 @@ export class PrivateMessage extends Component<
<button <button
className="btn btn-link btn-animate text-muted" className="btn btn-link btn-animate text-muted"
onClick={linkEvent(this, this.handleReplyClick)} onClick={linkEvent(this, this.handleReplyClick)}
data-tippy-content={i18n.t("reply")} data-tippy-content={I18NextService.i18n.t("reply")}
aria-label={i18n.t("reply")} aria-label={I18NextService.i18n.t("reply")}
> >
<Icon icon="reply1" classes="icon-inline" /> <Icon icon="reply1" classes="icon-inline" />
</button> </button>
@ -188,8 +190,8 @@ export class PrivateMessage extends Component<
<button <button
className="btn btn-link btn-animate text-muted" className="btn btn-link btn-animate text-muted"
onClick={linkEvent(this, this.handleEditClick)} onClick={linkEvent(this, this.handleEditClick)}
data-tippy-content={i18n.t("edit")} data-tippy-content={I18NextService.i18n.t("edit")}
aria-label={i18n.t("edit")} aria-label={I18NextService.i18n.t("edit")}
> >
<Icon icon="edit" classes="icon-inline" /> <Icon icon="edit" classes="icon-inline" />
</button> </button>
@ -200,13 +202,13 @@ export class PrivateMessage extends Component<
onClick={linkEvent(this, this.handleDeleteClick)} onClick={linkEvent(this, this.handleDeleteClick)}
data-tippy-content={ data-tippy-content={
!message_view.private_message.deleted !message_view.private_message.deleted
? i18n.t("delete") ? I18NextService.i18n.t("delete")
: i18n.t("restore") : I18NextService.i18n.t("restore")
} }
aria-label={ aria-label={
!message_view.private_message.deleted !message_view.private_message.deleted
? i18n.t("delete") ? I18NextService.i18n.t("delete")
: i18n.t("restore") : I18NextService.i18n.t("restore")
} }
> >
{this.state.deleteLoading ? ( {this.state.deleteLoading ? (
@ -228,8 +230,8 @@ export class PrivateMessage extends Component<
<button <button
className="btn btn-link btn-animate text-muted" className="btn btn-link btn-animate text-muted"
onClick={linkEvent(this, this.handleViewSource)} onClick={linkEvent(this, this.handleViewSource)}
data-tippy-content={i18n.t("view_source")} data-tippy-content={I18NextService.i18n.t("view_source")}
aria-label={i18n.t("view_source")} aria-label={I18NextService.i18n.t("view_source")}
> >
<Icon <Icon
icon="file-text" icon="file-text"
@ -249,13 +251,13 @@ export class PrivateMessage extends Component<
onSubmit={linkEvent(this, this.handleReportSubmit)} onSubmit={linkEvent(this, this.handleReportSubmit)}
> >
<label className="visually-hidden" htmlFor="pm-report-reason"> <label className="visually-hidden" htmlFor="pm-report-reason">
{i18n.t("reason")} {I18NextService.i18n.t("reason")}
</label> </label>
<input <input
type="text" type="text"
id="pm-report-reason" id="pm-report-reason"
className="form-control me-2" className="form-control me-2"
placeholder={i18n.t("reason")} placeholder={I18NextService.i18n.t("reason")}
required required
value={this.state.reportReason} value={this.state.reportReason}
onInput={linkEvent(this, this.handleReportReasonChange)} onInput={linkEvent(this, this.handleReportReasonChange)}
@ -263,9 +265,13 @@ export class PrivateMessage extends Component<
<button <button
type="submit" type="submit"
className="btn btn-secondary" className="btn btn-secondary"
aria-label={i18n.t("create_report")} aria-label={I18NextService.i18n.t("create_report")}
> >
{this.state.reportLoading ? <Spinner /> : i18n.t("create_report")} {this.state.reportLoading ? (
<Spinner />
) : (
I18NextService.i18n.t("create_report")
)}
</button> </button>
</form> </form>
)} )}
@ -286,8 +292,8 @@ export class PrivateMessage extends Component<
<button <button
className="btn btn-link btn-animate text-muted py-0" className="btn btn-link btn-animate text-muted py-0"
onClick={linkEvent(this, this.handleShowReportDialog)} onClick={linkEvent(this, this.handleShowReportDialog)}
data-tippy-content={i18n.t("show_report_dialog")} data-tippy-content={I18NextService.i18n.t("show_report_dialog")}
aria-label={i18n.t("show_report_dialog")} aria-label={I18NextService.i18n.t("show_report_dialog")}
> >
<Icon icon="flag" inline /> <Icon icon="flag" inline />
</button> </button>
@ -296,7 +302,9 @@ export class PrivateMessage extends Component<
get messageUnlessRemoved(): string { get messageUnlessRemoved(): string {
const message = this.props.private_message_view.private_message; const message = this.props.private_message_view.private_message;
return message.deleted ? `*${i18n.t("deleted")}*` : message.content; return message.deleted
? `*${I18NextService.i18n.t("deleted")}*`
: message.content;
} }
handleReplyClick(i: PrivateMessage) { handleReplyClick(i: PrivateMessage) {

View file

@ -1,5 +1,28 @@
import { debounce, getQueryParams, getQueryString } from "@utils/helpers"; import {
commentsToFlatNodes,
communityToChoice,
enableDownvotes,
enableNsfw,
fetchCommunities,
fetchUsers,
getUpdatedSearchId,
myAuth,
personToChoice,
setIsoData,
showLocal,
} from "@utils/app";
import { restoreScrollPosition, saveScrollPosition } from "@utils/browser";
import {
capitalizeFirstLetter,
debounce,
getIdFromString,
getPageFromString,
getQueryParams,
getQueryString,
numToSI,
} from "@utils/helpers";
import type { QueryParams } from "@utils/types"; import type { QueryParams } from "@utils/types";
import { Choice, RouteDataResponse } from "@utils/types";
import type { NoOptionI18nKeys } from "i18next"; import type { NoOptionI18nKeys } from "i18next";
import { Component, linkEvent } from "inferno"; import { Component, linkEvent } from "inferno";
import { import {
@ -22,32 +45,10 @@ import {
SearchType, SearchType,
SortType, SortType,
} from "lemmy-js-client"; } from "lemmy-js-client";
import { i18n } from "../i18next"; import { fetchLimit } from "../config";
import { CommentViewType, InitialFetchRequest } from "../interfaces"; import { CommentViewType, InitialFetchRequest } from "../interfaces";
import { FirstLoadService } from "../services/FirstLoadService"; import { FirstLoadService, I18NextService } from "../services";
import { HttpService, RequestState } from "../services/HttpService"; import { HttpService, RequestState } from "../services/HttpService";
import {
Choice,
RouteDataResponse,
capitalizeFirstLetter,
commentsToFlatNodes,
communityToChoice,
enableDownvotes,
enableNsfw,
fetchCommunities,
fetchLimit,
fetchUsers,
getIdFromString,
getPageFromString,
getUpdatedSearchId,
myAuth,
numToSI,
personToChoice,
restoreScrollPosition,
saveScrollPosition,
setIsoData,
showLocal,
} from "../utils";
import { CommentNodes } from "./comment/comment-nodes"; import { CommentNodes } from "./comment/comment-nodes";
import { HtmlTags } from "./common/html-tags"; import { HtmlTags } from "./common/html-tags";
import { Spinner } from "./common/icon"; import { Spinner } from "./common/icon";
@ -182,13 +183,13 @@ const Filter = ({
return ( return (
<div className="mb-3 col-sm-6"> <div className="mb-3 col-sm-6">
<label className="col-form-label me-2" htmlFor={`${filterType}-filter`}> <label className="col-form-label me-2" htmlFor={`${filterType}-filter`}>
{capitalizeFirstLetter(i18n.t(filterType))} {capitalizeFirstLetter(I18NextService.i18n.t(filterType))}
</label> </label>
<SearchableSelect <SearchableSelect
id={`${filterType}-filter`} id={`${filterType}-filter`}
options={[ options={[
{ {
label: i18n.t("all"), label: I18NextService.i18n.t("all"),
value: "0", value: "0",
}, },
].concat(options)} ].concat(options)}
@ -226,7 +227,7 @@ function getListing(
return ( return (
<> <>
<span>{listing}</span> <span>{listing}</span>
<span>{` - ${i18n.t(translationKey, { <span>{` - ${I18NextService.i18n.t(translationKey, {
count: Number(count), count: Number(count),
formattedCount: numToSI(count), formattedCount: numToSI(count),
})}`}</span> })}`}</span>
@ -446,7 +447,7 @@ export class Search extends Component<any, SearchState> {
get documentTitle(): string { get documentTitle(): string {
const { q } = getSearchQueryParams(); const { q } = getSearchQueryParams();
const name = this.state.siteRes.site_view.site.name; const name = this.state.siteRes.site_view.site.name;
return `${i18n.t("search")} - ${q ? `${q} - ` : ""}${name}`; return `${I18NextService.i18n.t("search")} - ${q ? `${q} - ` : ""}${name}`;
} }
render() { render() {
@ -458,13 +459,13 @@ export class Search extends Component<any, SearchState> {
title={this.documentTitle} title={this.documentTitle}
path={this.context.router.route.match.url} path={this.context.router.route.match.url}
/> />
<h5>{i18n.t("search")}</h5> <h5>{I18NextService.i18n.t("search")}</h5>
{this.selects} {this.selects}
{this.searchForm} {this.searchForm}
{this.displayResults(type)} {this.displayResults(type)}
{this.resultsCount === 0 && {this.resultsCount === 0 &&
this.state.searchRes.state === "success" && ( this.state.searchRes.state === "success" && (
<span>{i18n.t("no_results")}</span> <span>{I18NextService.i18n.t("no_results")}</span>
)} )}
<Paginator page={page} onChange={this.handlePageChange} /> <Paginator page={page} onChange={this.handlePageChange} />
</div> </div>
@ -497,8 +498,8 @@ export class Search extends Component<any, SearchState> {
type="text" type="text"
className="form-control me-2 mb-2 col-sm-8" className="form-control me-2 mb-2 col-sm-8"
value={this.state.searchText} value={this.state.searchText}
placeholder={`${i18n.t("search")}...`} placeholder={`${I18NextService.i18n.t("search")}...`}
aria-label={i18n.t("search")} aria-label={I18NextService.i18n.t("search")}
onInput={linkEvent(this, this.handleQChange)} onInput={linkEvent(this, this.handleQChange)}
required required
minLength={1} minLength={1}
@ -509,7 +510,7 @@ export class Search extends Component<any, SearchState> {
{this.state.searchRes.state === "loading" ? ( {this.state.searchRes.state === "loading" ? (
<Spinner /> <Spinner />
) : ( ) : (
<span>{i18n.t("search")}</span> <span>{I18NextService.i18n.t("search")}</span>
)} )}
</button> </button>
</div> </div>
@ -538,14 +539,16 @@ export class Search extends Component<any, SearchState> {
value={type} value={type}
onChange={linkEvent(this, this.handleTypeChange)} onChange={linkEvent(this, this.handleTypeChange)}
className="form-select d-inline-block w-auto mb-2" className="form-select d-inline-block w-auto mb-2"
aria-label={i18n.t("type")} aria-label={I18NextService.i18n.t("type")}
> >
<option disabled aria-hidden="true"> <option disabled aria-hidden="true">
{i18n.t("type")} {I18NextService.i18n.t("type")}
</option> </option>
{searchTypes.map(option => ( {searchTypes.map(option => (
<option value={option} key={option}> <option value={option} key={option}>
{i18n.t(option.toString().toLowerCase() as NoOptionI18nKeys)} {I18NextService.i18n.t(
option.toString().toLowerCase() as NoOptionI18nKeys
)}
</option> </option>
))} ))}
</select> </select>

26
src/shared/config.ts Normal file
View file

@ -0,0 +1,26 @@
export const favIconUrl = "/static/assets/icons/favicon.svg";
export const favIconPngUrl = "/static/assets/icons/apple-touch-icon.png";
export const repoUrl = "https://github.com/LemmyNet";
export const joinLemmyUrl = "https://join-lemmy.org";
export const donateLemmyUrl = `${joinLemmyUrl}/donate`;
export const docsUrl = `${joinLemmyUrl}/docs/en/index.html`;
export const helpGuideUrl = `${joinLemmyUrl}/docs/en/users/01-getting-started.html`; // TODO find a way to redirect to the non-en folder
export const markdownHelpUrl = `${joinLemmyUrl}/docs/en/users/02-media.html`;
export const sortingHelpUrl = `${joinLemmyUrl}/docs/en/users/03-votes-and-ranking.html`;
export const archiveTodayUrl = "https://archive.today";
export const ghostArchiveUrl = "https://ghostarchive.org";
export const webArchiveUrl = "https://web.archive.org";
export const elementUrl = "https://element.io";
export const postRefetchSeconds: number = 60 * 1000;
export const trendingFetchLimit = 6;
export const mentionDropdownFetchLimit = 10;
export const commentTreeMaxDepth = 8;
export const markdownFieldCharacterLimit = 50000;
export const maxUploadImages = 20;
export const concurrentImageUpload = 4;
export const updateUnreadCountsInterval = 30000;
export const fetchLimit = 40;
export const relTags = "noopener nofollow";
export const emDash = "\u2014";

View file

@ -1,7 +1,7 @@
import { ErrorPageData } from "@utils/types";
import { CommentView, GetSiteResponse } from "lemmy-js-client"; import { CommentView, GetSiteResponse } from "lemmy-js-client";
import type { ParsedQs } from "qs"; import type { ParsedQs } from "qs";
import { RequestState, WrappedLemmyHttp } from "./services/HttpService"; import { RequestState, WrappedLemmyHttp } from "./services/HttpService";
import { ErrorPageData } from "./utils";
/** /**
* This contains serialized data, it needs to be deserialized before use. * This contains serialized data, it needs to be deserialized before use.

307
src/shared/markdown.ts Normal file
View file

@ -0,0 +1,307 @@
import { communitySearch, personSearch } from "@utils/app";
import { isBrowser } from "@utils/browser";
import { debounce, groupBy } from "@utils/helpers";
import { CommunityTribute, PersonTribute } from "@utils/types";
import { Picker } from "emoji-mart";
import emojiShortName from "emoji-short-name";
import { CustomEmojiView } from "lemmy-js-client";
import { default as MarkdownIt } from "markdown-it";
import markdown_it_container from "markdown-it-container";
// import markdown_it_emoji from "markdown-it-emoji/bare";
import markdown_it_footnote from "markdown-it-footnote";
import markdown_it_html5_embed from "markdown-it-html5-embed";
import markdown_it_sub from "markdown-it-sub";
import markdown_it_sup from "markdown-it-sup";
import Renderer from "markdown-it/lib/renderer";
import Token from "markdown-it/lib/token";
export let Tribute: any;
export let md: MarkdownIt = new MarkdownIt();
export let mdNoImages: MarkdownIt = new MarkdownIt();
export const customEmojis: EmojiMartCategory[] = [];
export let customEmojisLookup: Map<string, CustomEmojiView> = new Map<
string,
CustomEmojiView
>();
if (isBrowser()) {
Tribute = require("tributejs");
}
export function mdToHtml(text: string) {
return { __html: md.render(text) };
}
export function mdToHtmlNoImages(text: string) {
return { __html: mdNoImages.render(text) };
}
export function mdToHtmlInline(text: string) {
return { __html: md.renderInline(text) };
}
const spoilerConfig = {
validate: (params: string) => {
return params.trim().match(/^spoiler\s+(.*)$/);
},
render: (tokens: any, idx: any) => {
var m = tokens[idx].info.trim().match(/^spoiler\s+(.*)$/);
if (tokens[idx].nesting === 1) {
// opening tag
return `<details><summary> ${md.utils.escapeHtml(m[1])} </summary>\n`;
} else {
// closing tag
return "</details>\n";
}
},
};
const html5EmbedConfig = {
html5embed: {
useImageSyntax: true, // Enables video/audio embed with ![]() syntax (default)
attributes: {
audio: 'controls preload="metadata"',
video: 'width="100%" max-height="100%" controls loop preload="metadata"',
},
},
};
export function setupMarkdown() {
const markdownItConfig: MarkdownIt.Options = {
html: false,
linkify: true,
typographer: true,
};
// const emojiDefs = Array.from(customEmojisLookup.entries()).reduce(
// (main, [key, value]) => ({ ...main, [key]: value }),
// {}
// );
md = new MarkdownIt(markdownItConfig)
.use(markdown_it_sub)
.use(markdown_it_sup)
.use(markdown_it_footnote)
.use(markdown_it_html5_embed, html5EmbedConfig)
.use(markdown_it_container, "spoiler", spoilerConfig);
// .use(markdown_it_emoji, {
// defs: emojiDefs,
// });
mdNoImages = new MarkdownIt(markdownItConfig)
.use(markdown_it_sub)
.use(markdown_it_sup)
.use(markdown_it_footnote)
.use(markdown_it_html5_embed, html5EmbedConfig)
.use(markdown_it_container, "spoiler", spoilerConfig)
// .use(markdown_it_emoji, {
// defs: emojiDefs,
// })
.disable("image");
const defaultRenderer = md.renderer.rules.image;
md.renderer.rules.image = function (
tokens: Token[],
idx: number,
options: MarkdownIt.Options,
env: any,
self: Renderer
) {
//Provide custom renderer for our emojis to allow us to add a css class and force size dimensions on them.
const item = tokens[idx] as any;
const title = item.attrs.length >= 3 ? item.attrs[2][1] : "";
const src: string = item.attrs[0][1];
const isCustomEmoji = customEmojisLookup.get(title) != undefined;
if (!isCustomEmoji) {
return defaultRenderer?.(tokens, idx, options, env, self) ?? "";
}
const alt_text = item.content;
return `<img class="icon icon-emoji" src="${src}" title="${title}" alt="${alt_text}"/>`;
};
md.renderer.rules.table_open = function () {
return '<table class="table">';
};
}
export function setupEmojiDataModel(custom_emoji_views: CustomEmojiView[]) {
const groupedEmojis = groupBy(
custom_emoji_views,
x => x.custom_emoji.category
);
for (const [category, emojis] of Object.entries(groupedEmojis)) {
customEmojis.push({
id: category,
name: category,
emojis: emojis.map(emoji => ({
id: emoji.custom_emoji.shortcode,
name: emoji.custom_emoji.shortcode,
keywords: emoji.keywords.map(x => x.keyword),
skins: [{ src: emoji.custom_emoji.image_url }],
})),
});
}
customEmojisLookup = new Map(
custom_emoji_views.map(view => [view.custom_emoji.shortcode, view])
);
}
export function updateEmojiDataModel(custom_emoji_view: CustomEmojiView) {
const emoji: EmojiMartCustomEmoji = {
id: custom_emoji_view.custom_emoji.shortcode,
name: custom_emoji_view.custom_emoji.shortcode,
keywords: custom_emoji_view.keywords.map(x => x.keyword),
skins: [{ src: custom_emoji_view.custom_emoji.image_url }],
};
const categoryIndex = customEmojis.findIndex(
x => x.id == custom_emoji_view.custom_emoji.category
);
if (categoryIndex == -1) {
customEmojis.push({
id: custom_emoji_view.custom_emoji.category,
name: custom_emoji_view.custom_emoji.category,
emojis: [emoji],
});
} else {
const emojiIndex = customEmojis[categoryIndex].emojis.findIndex(
x => x.id == custom_emoji_view.custom_emoji.shortcode
);
if (emojiIndex == -1) {
customEmojis[categoryIndex].emojis.push(emoji);
} else {
customEmojis[categoryIndex].emojis[emojiIndex] = emoji;
}
}
customEmojisLookup.set(
custom_emoji_view.custom_emoji.shortcode,
custom_emoji_view
);
}
export function removeFromEmojiDataModel(id: number) {
let view: CustomEmojiView | undefined;
for (const item of customEmojisLookup.values()) {
if (item.custom_emoji.id === id) {
view = item;
break;
}
}
if (!view) return;
const categoryIndex = customEmojis.findIndex(
x => x.id == view?.custom_emoji.category
);
const emojiIndex = customEmojis[categoryIndex].emojis.findIndex(
x => x.id == view?.custom_emoji.shortcode
);
customEmojis[categoryIndex].emojis = customEmojis[
categoryIndex
].emojis.splice(emojiIndex, 1);
customEmojisLookup.delete(view?.custom_emoji.shortcode);
}
export function getEmojiMart(
onEmojiSelect: (e: any) => void,
customPickerOptions: any = {}
) {
const pickerOptions = {
...customPickerOptions,
onEmojiSelect: onEmojiSelect,
custom: customEmojis,
};
return new Picker(pickerOptions);
}
export function setupTribute() {
return new Tribute({
noMatchTemplate: function () {
return "";
},
collection: [
// Emojis
{
trigger: ":",
menuItemTemplate: (item: any) => {
const shortName = `:${item.original.key}:`;
return `${item.original.val} ${shortName}`;
},
selectTemplate: (item: any) => {
const customEmoji = customEmojisLookup.get(
item.original.key
)?.custom_emoji;
if (customEmoji == undefined) return `${item.original.val}`;
else
return `![${customEmoji.alt_text}](${customEmoji.image_url} "${customEmoji.shortcode}")`;
},
values: Object.entries(emojiShortName)
.map(e => {
return { key: e[1], val: e[0] };
})
.concat(
Array.from(customEmojisLookup.entries()).map(k => ({
key: k[0],
val: `<img class="icon icon-emoji" src="${k[1].custom_emoji.image_url}" title="${k[1].custom_emoji.shortcode}" alt="${k[1].custom_emoji.alt_text}" />`,
}))
),
allowSpaces: false,
autocompleteMode: true,
// TODO
// menuItemLimit: mentionDropdownFetchLimit,
menuShowMinLength: 2,
},
// Persons
{
trigger: "@",
selectTemplate: (item: any) => {
const it: PersonTribute = item.original;
return `[${it.key}](${it.view.person.actor_id})`;
},
values: debounce(async (text: string, cb: any) => {
cb(await personSearch(text));
}),
allowSpaces: false,
autocompleteMode: true,
// TODO
// menuItemLimit: mentionDropdownFetchLimit,
menuShowMinLength: 2,
},
// Communities
{
trigger: "!",
selectTemplate: (item: any) => {
const it: CommunityTribute = item.original;
return `[${it.key}](${it.view.community.actor_id})`;
},
values: debounce(async (text: string, cb: any) => {
cb(await communitySearch(text));
}),
allowSpaces: false,
autocompleteMode: true,
// TODO
// menuItemLimit: mentionDropdownFetchLimit,
menuShowMinLength: 2,
},
],
});
}
interface EmojiMartCategory {
id: string;
name: string;
emojis: EmojiMartCustomEmoji[];
}
interface EmojiMartCustomEmoji {
id: string;
name: string;
keywords: string[];
skins: EmojiMartSkin[];
}
interface EmojiMartSkin {
src: string;
}

View file

@ -1,7 +1,7 @@
import { LemmyHttp } from "lemmy-js-client"; import { LemmyHttp } from "lemmy-js-client";
import { getHttpBase } from "../../shared/env"; import { getHttpBase } from "../../shared/env";
import { i18n } from "../../shared/i18next"; import { toast } from "../../shared/toast";
import { toast } from "../../shared/utils"; import { I18NextService } from "./I18NextService";
type EmptyRequestState = { type EmptyRequestState = {
state: "empty"; state: "empty";
@ -62,7 +62,7 @@ class WrappedLemmyHttpClient {
}; };
} catch (error) { } catch (error) {
console.error(`API error: ${error}`); console.error(`API error: ${error}`);
toast(i18n.t(error), "danger"); toast(I18NextService.i18n.t(error), "danger");
return { return {
state: "failed", state: "failed",
msg: error, msg: error,

View file

@ -1,37 +1,37 @@
import { isBrowser } from "@utils/browser"; import { isBrowser } from "@utils/browser";
import i18next, { i18nTyped, Resource } from "i18next"; import i18next, { Resource } from "i18next";
import { UserService } from "./services"; import { UserService } from "../services";
import { ar } from "./translations/ar"; import { ar } from "../translations/ar";
import { bg } from "./translations/bg"; import { bg } from "../translations/bg";
import { ca } from "./translations/ca"; import { ca } from "../translations/ca";
import { cs } from "./translations/cs"; import { cs } from "../translations/cs";
import { da } from "./translations/da"; import { da } from "../translations/da";
import { de } from "./translations/de"; import { de } from "../translations/de";
import { el } from "./translations/el"; import { el } from "../translations/el";
import { en } from "./translations/en"; import { en } from "../translations/en";
import { eo } from "./translations/eo"; import { eo } from "../translations/eo";
import { es } from "./translations/es"; import { es } from "../translations/es";
import { eu } from "./translations/eu"; import { eu } from "../translations/eu";
import { fa } from "./translations/fa"; import { fa } from "../translations/fa";
import { fi } from "./translations/fi"; import { fi } from "../translations/fi";
import { fr } from "./translations/fr"; import { fr } from "../translations/fr";
import { ga } from "./translations/ga"; import { ga } from "../translations/ga";
import { gl } from "./translations/gl"; import { gl } from "../translations/gl";
import { hr } from "./translations/hr"; import { hr } from "../translations/hr";
import { id } from "./translations/id"; import { id } from "../translations/id";
import { it } from "./translations/it"; import { it } from "../translations/it";
import { ja } from "./translations/ja"; import { ja } from "../translations/ja";
import { ko } from "./translations/ko"; import { ko } from "../translations/ko";
import { nl } from "./translations/nl"; import { nl } from "../translations/nl";
import { oc } from "./translations/oc"; import { oc } from "../translations/oc";
import { pl } from "./translations/pl"; import { pl } from "../translations/pl";
import { pt } from "./translations/pt"; import { pt } from "../translations/pt";
import { pt_BR } from "./translations/pt_BR"; import { pt_BR } from "../translations/pt_BR";
import { ru } from "./translations/ru"; import { ru } from "../translations/ru";
import { sv } from "./translations/sv"; import { sv } from "../translations/sv";
import { vi } from "./translations/vi"; import { vi } from "../translations/vi";
import { zh } from "./translations/zh"; import { zh } from "../translations/zh";
import { zh_Hant } from "./translations/zh_Hant"; import { zh_Hant } from "../translations/zh_Hant";
export const languages = [ export const languages = [
{ resource: ar, code: "ar", name: "العربية" }, { resource: ar, code: "ar", name: "العربية" },
@ -92,7 +92,13 @@ class LanguageDetector {
} }
} }
i18next.use(LanguageDetector).init({ export class I18NextService {
#i18n: typeof i18next;
static #instance: I18NextService;
private constructor() {
this.#i18n = i18next;
this.#i18n.use(LanguageDetector).init({
debug: false, debug: false,
compatibilityJSON: "v3", compatibilityJSON: "v3",
supportedLngs: languages.map(l => l.code), supportedLngs: languages.map(l => l.code),
@ -102,6 +108,14 @@ i18next.use(LanguageDetector).init({
fallbackLng: "en", fallbackLng: "en",
resources, resources,
interpolation: { format }, interpolation: { format },
}); });
}
export const i18n = i18next as i18nTyped; static get #Instance() {
return this.#instance ?? (this.#instance = new this());
}
public static get i18n() {
return this.#Instance.#i18n;
}
}

View file

@ -1,11 +1,12 @@
// import Cookies from 'js-cookie'; // import Cookies from 'js-cookie';
import { isAuthPath } from "@utils/app";
import { isBrowser } from "@utils/browser"; import { isBrowser } from "@utils/browser";
import IsomorphicCookie from "isomorphic-cookie"; import IsomorphicCookie from "isomorphic-cookie";
import jwt_decode from "jwt-decode"; import jwt_decode from "jwt-decode";
import { LoginResponse, MyUserInfo } from "lemmy-js-client"; import { LoginResponse, MyUserInfo } from "lemmy-js-client";
import { isHttps } from "../env"; import { isHttps } from "../env";
import { i18n } from "../i18next"; import { toast } from "../toast";
import { isAuthPath, toast } from "../utils"; import { I18NextService } from "./I18NextService";
interface Claims { interface Claims {
sub: number; sub: number;
@ -31,7 +32,7 @@ export class UserService {
const expires = new Date(); const expires = new Date();
expires.setDate(expires.getDate() + 365); expires.setDate(expires.getDate() + 365);
if (res.jwt) { if (res.jwt) {
toast(i18n.t("logged_in")); toast(I18NextService.i18n.t("logged_in"));
IsomorphicCookie.save("jwt", res.jwt, { expires, secure: isHttps() }); IsomorphicCookie.save("jwt", res.jwt, { expires, secure: isHttps() });
this.#setJwtInfo(); this.#setJwtInfo();
} }
@ -57,7 +58,7 @@ export class UserService {
const msg = "No JWT cookie found"; const msg = "No JWT cookie found";
if (throwErr && isBrowser()) { if (throwErr && isBrowser()) {
console.error(msg); console.error(msg);
toast(i18n.t("not_logged_in"), "danger"); toast(I18NextService.i18n.t("not_logged_in"), "danger");
} }
return undefined; return undefined;
// throw msg; // throw msg;

View file

@ -1,2 +1,5 @@
export { FirstLoadService } from "./FirstLoadService";
export { HistoryService } from "./HistoryService";
export { HttpService } from "./HttpService"; export { HttpService } from "./HttpService";
export { I18NextService } from "./I18NextService";
export { UserService } from "./UserService"; export { UserService } from "./UserService";

19
src/shared/tippy.ts Normal file
View file

@ -0,0 +1,19 @@
import { isBrowser } from "@utils/browser";
import tippy from "tippy.js";
export let tippyInstance: any;
if (isBrowser()) {
tippyInstance = tippy("[data-tippy-content]");
}
export function setupTippy() {
if (isBrowser()) {
tippyInstance.forEach((e: any) => e.destroy());
tippyInstance = tippy("[data-tippy-content]", {
delay: [500, 0],
// Display on "long press"
touch: ["hold", 500],
});
}
}

59
src/shared/toast.ts Normal file
View file

@ -0,0 +1,59 @@
import { isBrowser } from "@utils/browser";
import { ThemeColor } from "@utils/types";
import Toastify from "toastify-js";
import { I18NextService } from "./services";
export function toast(text: string, background: ThemeColor = "success") {
if (isBrowser()) {
const backgroundColor = `var(--bs-${background})`;
Toastify({
text: text,
backgroundColor: backgroundColor,
gravity: "bottom",
position: "left",
duration: 5000,
}).showToast();
}
}
export function pictrsDeleteToast(filename: string, deleteUrl: string) {
if (isBrowser()) {
const clickToDeleteText = I18NextService.i18n.t("click_to_delete_picture", {
filename,
});
const deletePictureText = I18NextService.i18n.t("picture_deleted", {
filename,
});
const failedDeletePictureText = I18NextService.i18n.t(
"failed_to_delete_picture",
{
filename,
}
);
const backgroundColor = `var(--bs-light)`;
const toast = Toastify({
text: clickToDeleteText,
backgroundColor: backgroundColor,
gravity: "top",
position: "right",
duration: 10000,
onClick: () => {
if (toast) {
fetch(deleteUrl).then(res => {
toast.hideToast();
if (res.ok === true) {
alert(deletePictureText);
} else {
alert(failedDeletePictureText);
}
});
}
},
close: true,
});
toast.showToast();
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,54 @@
import { getCommentParentId, getDepthFromComment } from "@utils/app";
import { CommentView } from "lemmy-js-client";
import { CommentNodeI } from "../../interfaces";
export default function buildCommentsTree(
comments: CommentView[],
parentComment: boolean
): CommentNodeI[] {
const map = new Map<number, CommentNodeI>();
const depthOffset = !parentComment
? 0
: getDepthFromComment(comments[0].comment) ?? 0;
for (const comment_view of comments) {
const depthI = getDepthFromComment(comment_view.comment) ?? 0;
const depth = depthI ? depthI - depthOffset : 0;
const node: CommentNodeI = {
comment_view,
children: [],
depth,
};
map.set(comment_view.comment.id, { ...node });
}
const tree: CommentNodeI[] = [];
// if its a parent comment fetch, then push the first comment to the top node.
if (parentComment) {
const cNode = map.get(comments[0].comment.id);
if (cNode) {
tree.push(cNode);
}
}
for (const comment_view of comments) {
const child = map.get(comment_view.comment.id);
if (child) {
const parent_id = getCommentParentId(comment_view.comment);
if (parent_id) {
const parent = map.get(parent_id);
// Necessary because blocked comment might not exist
if (parent) {
parent.children.push(child);
}
} else {
if (!parentComment) {
tree.push(child);
}
}
}
}
return tree;
}

View file

@ -0,0 +1,11 @@
import { hsl } from "@utils/helpers";
export const colorList: string[] = [
hsl(0),
hsl(50),
hsl(100),
hsl(150),
hsl(200),
hsl(250),
hsl(300),
];

View file

@ -0,0 +1,12 @@
import { CommentView } from "lemmy-js-client";
import { CommentNodeI } from "../../interfaces";
export default function commentsToFlatNodes(
comments: CommentView[]
): CommentNodeI[] {
const nodes: CommentNodeI[] = [];
for (const comment of comments) {
nodes.push({ comment_view: comment, children: [], depth: 0 });
}
return nodes;
}

View file

@ -0,0 +1,4 @@
export default function communityRSSUrl(actorId: string, sort: string): string {
const url = new URL(actorId);
return `${url.origin}/feeds${url.pathname}.xml?sort=${sort}`;
}

View file

@ -0,0 +1,14 @@
import { fetchCommunities } from "@utils/app";
import { hostname } from "@utils/helpers";
import { CommunityTribute } from "@utils/types";
export default async function communitySearch(
text: string
): Promise<CommunityTribute[]> {
const communitiesResponse = await fetchCommunities(text);
return communitiesResponse.map(cv => ({
key: `!${cv.community.name}@${hostname(cv.community.actor_id)}`,
view: cv,
}));
}

View file

@ -0,0 +1,8 @@
import { hostname } from "@utils/helpers";
import { CommunityView } from "lemmy-js-client";
export default function communitySelectName(cv: CommunityView): string {
return cv.community.local
? cv.community.title
: `${hostname(cv.community.actor_id)}/${cv.community.title}`;
}

View file

@ -0,0 +1,10 @@
import { communitySelectName } from "@utils/app";
import { Choice } from "@utils/types";
import { CommunityView } from "lemmy-js-client";
export default function communityToChoice(cv: CommunityView): Choice {
return {
value: cv.community.id.toString(),
label: communitySelectName(cv),
};
}

View file

@ -0,0 +1,25 @@
import { CommentSortType, SortType } from "lemmy-js-client";
export default function convertCommentSortType(
sort: SortType
): CommentSortType {
switch (sort) {
case "TopAll":
case "TopDay":
case "TopWeek":
case "TopMonth":
case "TopYear": {
return "Top";
}
case "New": {
return "New";
}
case "Hot":
case "Active": {
return "Hot";
}
default: {
return "Hot";
}
}
}

View file

@ -0,0 +1,9 @@
import { editListImmutable } from "@utils/helpers";
import { CommentReplyView } from "lemmy-js-client";
export default function editCommentReply(
data: CommentReplyView,
replies: CommentReplyView[]
): CommentReplyView[] {
return editListImmutable("comment_reply", data, replies);
}

View file

@ -0,0 +1,9 @@
import { editListImmutable } from "@utils/helpers";
import { CommentReportView } from "lemmy-js-client";
export default function editCommentReport(
data: CommentReportView,
reports: CommentReportView[]
): CommentReportView[] {
return editListImmutable("comment_report", data, reports);
}

View file

@ -0,0 +1,9 @@
import { editListImmutable } from "@utils/helpers";
import { CommentView } from "lemmy-js-client";
export default function editComment(
data: CommentView,
comments: CommentView[]
): CommentView[] {
return editListImmutable("comment", data, comments);
}

View file

@ -0,0 +1,9 @@
import { editListImmutable } from "@utils/helpers";
import { CommunityView } from "lemmy-js-client";
export default function editCommunity(
data: CommunityView,
communities: CommunityView[]
): CommunityView[] {
return editListImmutable("community", data, communities);
}

View file

@ -0,0 +1,9 @@
import { editListImmutable } from "@utils/helpers";
import { PersonMentionView } from "lemmy-js-client";
export default function editMention(
data: PersonMentionView,
comments: PersonMentionView[]
): PersonMentionView[] {
return editListImmutable("person_mention", data, comments);
}

View file

@ -0,0 +1,9 @@
import { editListImmutable } from "@utils/helpers";
import { PostReportView } from "lemmy-js-client";
export default function editPostReport(
data: PostReportView,
reports: PostReportView[]
) {
return editListImmutable("post_report", data, reports);
}

Some files were not shown because too many files have changed in this diff Show more