Merge remote-tracking branch 'origin/main' into feat/add-post-body-preview-to-desktop

* origin/main: (40 commits)
  Adding jsit to codeowners.
  Cleanup, only check for /u/ if /c/ and /m/ checks fail
  Rename function to be more generic, since it parses users
  Typescript linter fixes
  bandaid fix our video embeds
  Remove pipe from community link regex
  Add missing classes
  Use shorter regex in community link parser
  Move regex pattern to config
  Update community link markdown parsing
  Fix avatar alignment issue (#1475)
  Omit user-scalable to use default
  Update getHttpBase dependency reference
  Enable users to zoom on mobile
  rethink it a bit
  rethink it a bit
  add fallback style tag
  move env utils into folder
  fix capitalization (#1467)
  Fix buildThemeList() function to ensure no duplicates (#1466)
  ...
This commit is contained in:
Jay Sitter 2023-06-22 17:37:10 -04:00
commit a9bcf0567d
97 changed files with 1478 additions and 1121 deletions

2
.github/CODEOWNERS vendored
View file

@ -1 +1 @@
* @dessalines @SleeplessOne1917 @alectrocute * @dessalines @SleeplessOne1917 @alectrocute @jsit

View file

@ -1,6 +1,6 @@
{ {
"name": "lemmy-ui", "name": "lemmy-ui",
"version": "0.18.0-rc.4", "version": "0.18.0-rc.6",
"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",
@ -22,9 +22,16 @@
"translations:update": "git submodule update --remote --recursive" "translations:update": "git submodule update --remote --recursive"
}, },
"lint-staged": { "lint-staged": {
"*.{ts,tsx,js}": ["prettier --write", "eslint --fix"], "*.{ts,tsx,js}": [
"*.{css, scss}": ["prettier --write"], "prettier --write",
"package.json": ["sortpack"] "eslint --fix"
],
"*.{css, scss}": [
"prettier --write"
],
"package.json": [
"sortpack"
]
}, },
"dependencies": { "dependencies": {
"@babel/plugin-proposal-decorators": "^7.21.0", "@babel/plugin-proposal-decorators": "^7.21.0",

View file

@ -2,7 +2,7 @@ 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 "bootstrap/js/dist/collapse"; import "bootstrap/js/dist/collapse";
import "bootstrap/js/dist/dropdown"; import "bootstrap/js/dist/dropdown";

View file

@ -1,12 +1,13 @@
import { initializeSite, isAuthPath } from "@utils/app"; import { initializeSite, isAuthPath } from "@utils/app";
import { getHttpBaseInternal } from "@utils/env";
import { ErrorPageData } from "@utils/types"; import { ErrorPageData } from "@utils/types";
import fetch from "cross-fetch";
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";
import IsomorphicCookie from "isomorphic-cookie"; import IsomorphicCookie from "isomorphic-cookie";
import { GetSite, GetSiteResponse, LemmyHttp } from "lemmy-js-client"; import { GetSite, GetSiteResponse, LemmyHttp } from "lemmy-js-client";
import { App } from "../../shared/components/app/app"; import { App } from "../../shared/components/app/app";
import { getHttpBaseInternal } from "../../shared/env";
import { import {
InitialFetchRequest, InitialFetchRequest,
IsoDataOptionalSite, IsoDataOptionalSite,

View file

@ -1,6 +1,7 @@
import { getHttpBaseExternal, getHttpBaseInternal } from "@utils/env";
import fetch from "cross-fetch";
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 { 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,7 +10,7 @@ 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( const client = wrapClient(
new LemmyHttp(getHttpBaseInternal(), { fetchFunction: fetch, headers }) new LemmyHttp(getHttpBaseInternal(), { fetchFunction: fetch, headers })

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

@ -4,15 +4,20 @@ import { readdir } from "fs/promises";
const extraThemesFolder = const extraThemesFolder =
process.env["LEMMY_UI_EXTRA_THEMES_FOLDER"] || "./extra_themes"; process.env["LEMMY_UI_EXTRA_THEMES_FOLDER"] || "./extra_themes";
const themes = ["darkly", "darkly-red", "litely", "litely-red"]; const themes: ReadonlyArray<string> = [
"darkly",
"darkly-red",
"litely",
"litely-red",
];
export async function buildThemeList(): Promise<string[]> { export async function buildThemeList(): Promise<ReadonlyArray<string>> {
if (existsSync(extraThemesFolder)) { if (existsSync(extraThemesFolder)) {
const dirThemes = await readdir(extraThemesFolder); const dirThemes = await readdir(extraThemesFolder);
const cssThemes = dirThemes const cssThemes = dirThemes
.filter(d => d.endsWith(".css")) .filter(d => d.endsWith(".css"))
.map(d => d.replace(".css", "")); .map(d => d.replace(".css", ""));
themes.push(...cssThemes); return themes.concat(cssThemes);
} }
return themes; return themes;
} }

View file

@ -4,6 +4,7 @@ import serialize from "serialize-javascript";
import sharp from "sharp"; import sharp from "sharp";
import { favIconPngUrl, favIconUrl } from "../../shared/config"; import { favIconPngUrl, favIconUrl } from "../../shared/config";
import { ILemmyConfig, IsoDataOptionalSite } from "../../shared/interfaces"; import { ILemmyConfig, IsoDataOptionalSite } from "../../shared/interfaces";
import { buildThemeList } from "./build-themes-list";
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"] || "";
@ -16,6 +17,10 @@ export async function createSsrHtml(
) { ) {
const site = isoData.site_res; const site = isoData.site_res;
const fallbackTheme = `<link rel="stylesheet" type="text/css" href="/css/themes/${
(await buildThemeList())[0]
}.css" />`;
if (!appleTouchIcon) { if (!appleTouchIcon) {
appleTouchIcon = site?.site_view.site.icon appleTouchIcon = site?.site_view.site.icon
? `data:image/png;base64,${sharp( ? `data:image/png;base64,${sharp(
@ -68,7 +73,7 @@ export async function createSsrHtml(
<!-- Required meta tags --> <!-- Required meta tags -->
<meta name="Description" content="Lemmy"> <meta name="Description" content="Lemmy">
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, user-scalable=no"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link <link
id="favicon" id="favicon"
rel="shortcut icon" rel="shortcut icon"
@ -77,7 +82,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} />
@ -85,7 +90,7 @@ export async function createSsrHtml(
<link rel="stylesheet" type="text/css" href="/static/styles/styles.css" /> <link rel="stylesheet" type="text/css" href="/static/styles/styles.css" />
<!-- Current theme and more --> <!-- Current theme and more -->
${helmet.link.toString()} ${helmet.link.toString() || fallbackTheme}
</head> </head>

View file

@ -1,3 +1,5 @@
import fetch from "cross-fetch";
export async function fetchIconPng(iconUrl: string) { export async function fetchIconPng(iconUrl: string) {
return await fetch(iconUrl) return await fetch(iconUrl)
.then(res => res.blob()) .then(res => res.blob())

View file

@ -1,8 +1,8 @@
import { getHttpBaseExternal } from "@utils/env";
import { readFile } from "fs/promises"; import { readFile } from "fs/promises";
import { GetSiteResponse } from "lemmy-js-client"; import { GetSiteResponse } from "lemmy-js-client";
import path from "path"; import path from "path";
import sharp from "sharp"; import sharp from "sharp";
import { getHttpBaseExternal } from "../../shared/env";
import { fetchIconPng } from "./fetch-icon-png"; import { fetchIconPng } from "./fetch-icon-png";
const iconSizes = [72, 96, 128, 144, 152, 192, 384, 512]; const iconSizes = [72, 96, 128, 144, 152, 192, 384, 512];

View file

@ -2,9 +2,9 @@ import { isAuthPath, setIsoData } from "@utils/app";
import { Component, RefObject, createRef, linkEvent } from "inferno"; 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 { 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

@ -2,8 +2,8 @@ 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 { 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

@ -2,7 +2,7 @@ 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 { docsUrl, joinLemmyUrl, repoUrl } from "../../config"; import { docsUrl, joinLemmyUrl, repoUrl } from "../../config";
import { i18n } from "../../i18next"; 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

@ -11,8 +11,7 @@ import {
GetUnreadRegistrationApplicationCountResponse, GetUnreadRegistrationApplicationCountResponse,
} from "lemmy-js-client"; } from "lemmy-js-client";
import { donateLemmyUrl, updateUnreadCountsInterval } from "../../config"; import { donateLemmyUrl, updateUnreadCountsInterval } from "../../config";
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 { toast } from "../../toast"; import { toast } from "../../toast";
import { Icon } from "../common/icon"; import { Icon } from "../common/icon";
@ -102,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),
})} })}
@ -121,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),
})} })}
@ -141,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" />
@ -162,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"
@ -181,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">
@ -198,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) && (
@ -209,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>
@ -234,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>
@ -248,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>
@ -264,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),
})} })}
@ -272,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),
})} })}
@ -289,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),
})} })}
@ -297,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),
})} })}
@ -315,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">
@ -357,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>
@ -384,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>
@ -397,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>
</> </>
@ -504,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

@ -4,9 +4,8 @@ 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 { Icon } from "../common/icon"; import { Icon } from "../common/icon";
import { MarkdownTextArea } from "../common/markdown-textarea"; import { MarkdownTextArea } from "../common/markdown-textarea";
@ -58,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}
/> />
@ -79,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

@ -48,7 +48,6 @@ import {
} from "lemmy-js-client"; } from "lemmy-js-client";
import moment from "moment"; import moment from "moment";
import { commentTreeMaxDepth } from "../../config"; import { commentTreeMaxDepth } from "../../config";
import { i18n } from "../../i18next";
import { import {
BanType, BanType,
CommentNodeI, CommentNodeI,
@ -57,7 +56,7 @@ import {
VoteType, VoteType,
} from "../../interfaces"; } from "../../interfaces";
import { mdToHtml, mdToHtmlNoImages } from "../../markdown"; import { mdToHtml, mdToHtmlNoImages } from "../../markdown";
import { UserService } from "../../services"; import { I18NextService, UserService } from "../../services";
import { setupTippy } from "../../tippy"; import { setupTippy } from "../../tippy";
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";
@ -241,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,
@ -314,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}`}>
@ -366,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
@ -426,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 ? (
@ -456,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 ? (
@ -483,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 ? (
@ -506,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>
@ -515,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>
@ -527,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>
@ -537,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>
@ -550,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 />
@ -565,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 ? (
@ -585,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"
@ -600,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>
@ -613,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 ? (
@ -643,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
@ -672,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
@ -683,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>
)} )}
@ -705,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
@ -716,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>
))} ))}
@ -735,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"
@ -757,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
@ -771,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>
</> </>
))} ))}
@ -790,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"
@ -808,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
@ -823,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>
</> </>
))} ))}
@ -840,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"
@ -850,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) ? (
@ -862,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
@ -873,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>
)} )}
@ -895,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"
@ -914,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
@ -928,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>
</> </>
))} ))}
@ -961,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
@ -983,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>
)} )}
@ -1011,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>
)} )}
@ -1038,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)}
/> />
@ -1052,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)}
/> />
@ -1074,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>
@ -1084,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>
@ -1108,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)}
/> />
@ -1209,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;
@ -1255,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),
}); });
@ -1274,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

@ -6,8 +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 { 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

@ -5,7 +5,7 @@ import {
CommunityId, CommunityId,
SiteAggregates, SiteAggregates,
} from "lemmy-js-client"; } from "lemmy-js-client";
import { i18n } from "../../i18next"; import { I18NextService } from "../../services";
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

@ -2,7 +2,7 @@ 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 { relTags, sortingHelpUrl } from "../../config"; import { relTags, sortingHelpUrl } from "../../config";
import { i18n } from "../../i18next"; import { I18NextService } from "../../services";
import { Icon } from "./icon"; import { Icon } from "./icon";
interface CommentSortSelectProps { interface CommentSortSelectProps {
@ -42,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, 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)}
> >

View file

@ -1,9 +1,9 @@
import { httpExternalPath } from "@utils/env";
import { htmlToText } from "html-to-text"; 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 { i18n } from "../../i18next";
import { md } from "../../markdown"; import { md } from "../../markdown";
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,6 @@
import { randomStr } from "@utils/helpers"; 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 { toast } from "../../toast";
import { Icon } from "./icon"; import { Icon } from "./icon";
@ -50,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

@ -3,8 +3,7 @@ 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 { Icon } from "./icon"; import { Icon } from "./icon";
interface LanguageSelectProps { interface LanguageSelectProps {
@ -53,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">
@ -64,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}`, {
@ -103,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 { 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";
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

@ -12,9 +12,8 @@ import {
maxUploadImages, maxUploadImages,
relTags, relTags,
} from "../../config"; } from "../../config";
import { i18n } from "../../i18next";
import { customEmojisLookup, mdToHtml, setupTribute } from "../../markdown"; import { customEmojisLookup, mdToHtml, setupTribute } from "../../markdown";
import { HttpService, UserService } from "../../services"; import { HttpService, I18NextService, UserService } from "../../services";
import { setupTippy } from "../../tippy"; import { setupTippy } from "../../tippy";
import { pictrsDeleteToast, toast } from "../../toast"; import { pictrsDeleteToast, toast } from "../../toast";
import { EmojiPicker } from "./emoji-picker"; import { EmojiPicker } from "./emoji-picker";
@ -129,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
@ -161,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 />
@ -199,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" />
@ -241,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>
@ -290,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 && (
@ -300,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>
@ -332,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}
> >
@ -376,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),
}), }),
@ -677,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 { 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 { 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

@ -22,7 +22,7 @@ export class PictrsImage extends Component<PictrsImageProps, any> {
render() { render() {
return ( return (
<picture className="pictrs-image d-inline-block overflow-hidden"> <picture>
<source srcSet={this.src("webp")} type="image/webp" /> <source srcSet={this.src("webp")} type="image/webp" />
<source srcSet={this.props.src} /> <source srcSet={this.props.src} />
<source srcSet={this.src("jpg")} type="image/jpeg" /> <source srcSet={this.src("jpg")} type="image/jpeg" />
@ -31,7 +31,7 @@ export class PictrsImage extends Component<PictrsImageProps, any> {
alt={this.alt()} alt={this.alt()}
title={this.alt()} title={this.alt()}
loading="lazy" loading="lazy"
className={classNames({ className={classNames("overflow-hidden pictrs-image", {
"img-fluid": !this.props.icon && !this.props.iconOverlay, "img-fluid": !this.props.icon && !this.props.iconOverlay,
banner: this.props.banner, banner: this.props.banner,
"thumbnail rounded": "thumbnail rounded":

View file

@ -5,8 +5,8 @@ import {
ApproveRegistrationApplication, ApproveRegistrationApplication,
RegistrationApplicationView, RegistrationApplicationView,
} from "lemmy-js-client"; } from "lemmy-js-client";
import { i18n } from "../../i18next";
import { mdToHtml } from "../../markdown"; import { mdToHtml } from "../../markdown";
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";
@ -61,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 && (
@ -84,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)}
@ -99,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
@ -116,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

@ -7,7 +7,7 @@ import {
linkEvent, linkEvent,
RefObject, RefObject,
} from "inferno"; } from "inferno";
import { i18n } from "../../i18next"; import { I18NextService } from "../../services";
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

@ -2,7 +2,7 @@ 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 { relTags, sortingHelpUrl } from "../../config"; import { relTags, sortingHelpUrl } from "../../config";
import { i18n } from "../../i18next"; import { I18NextService } from "../../services";
import { Icon } from "./icon"; import { Icon } from "./icon";
interface SortSelectProps { interface SortSelectProps {
@ -41,43 +41,52 @@ 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={"TopHour"}>{I18NextService.i18n.t("top_hour")}</option>
<option value={"TopWeek"}>{i18n.t("top_week")}</option> <option value={"TopSixHour"}>
<option value={"TopMonth"}>{i18n.t("top_month")}</option> {I18NextService.i18n.t("top_six_hours")}
<option value={"TopYear"}>{i18n.t("top_year")}</option> </option>
<option value={"TopAll"}>{i18n.t("top_all")}</option> <option value={"TopTwelveHour"}>
{I18NextService.i18n.t("top_twelve_hours")}
</option>
<option value={"TopDay"}>{I18NextService.i18n.t("top_day")}</option>
<option value={"TopWeek"}>{I18NextService.i18n.t("top_week")}</option>
<option value={"TopMonth"}>
{I18NextService.i18n.t("top_month")}
</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

@ -21,9 +21,8 @@ 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 { HtmlTags } from "../common/html-tags"; import { HtmlTags } from "../common/html-tags";
import { Spinner } from "../common/icon"; import { Spinner } from "../common/icon";
@ -86,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
}`; }`;
} }
@ -103,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
@ -123,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>
@ -169,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" && (
@ -184,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>
@ -230,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}
@ -238,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

@ -7,7 +7,7 @@ import {
EditCommunity, EditCommunity,
Language, Language,
} from "lemmy-js-client"; } from "lemmy-js-client";
import { i18n } from "../../i18next"; import { I18NextService } from "../../services";
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";
@ -107,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>
@ -125,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>
@ -135,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>
@ -158,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}
@ -172,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}
@ -185,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={[]}
@ -202,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">
@ -219,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">
@ -254,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 && (
@ -265,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

@ -15,7 +15,6 @@ import {
updateCommunityBlock, updateCommunityBlock,
updatePersonBlock, updatePersonBlock,
} from "@utils/app"; } from "@utils/app";
import { restoreScrollPosition, saveScrollPosition } from "@utils/browser";
import { import {
getPageFromString, getPageFromString,
getQueryParams, getQueryParams,
@ -78,14 +77,12 @@ import {
TransferCommunity, TransferCommunity,
} from "lemmy-js-client"; } from "lemmy-js-client";
import { fetchLimit, relTags } from "../../config"; import { fetchLimit, relTags } from "../../config";
import { i18n } from "../../i18next";
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 { setupTippy } from "../../tippy"; import { setupTippy } from "../../tippy";
import { toast } from "../../toast"; import { toast } from "../../toast";
@ -231,10 +228,6 @@ export class Community extends Component<
setupTippy(); setupTippy();
} }
componentWillUnmount() {
saveScrollPosition(this.context);
}
static async fetchInitialData({ static async fetchInitialData({
client, client,
path, path,
@ -331,7 +324,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
@ -611,7 +604,6 @@ export class Community extends Component<
}); });
} }
restoreScrollPosition(this.context);
setupTippy(); setupTippy();
} }
@ -758,14 +750,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"));
} }
} }
@ -791,7 +783,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);
} }
@ -880,7 +872,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

@ -4,8 +4,7 @@ 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 { 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

@ -17,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 { mdToHtml } from "../../markdown";
import { UserService } from "../../services"; import { I18NextService, UserService } 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, PurgeWarning, Spinner } from "../common/icon"; import { Icon, PurgeWarning, Spinner } from "../common/icon";
@ -187,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>
@ -200,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>
@ -234,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} />
@ -253,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>
); );
} }
@ -270,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>
)} )}
@ -288,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>
)} )}
</> </>
@ -315,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>
@ -331,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">
@ -355,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>
</> </>
@ -367,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 ? (
@ -398,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
@ -408,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>
)} )}
@ -426,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)}
/> />
@ -440,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>
@ -460,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)}
/> />
@ -478,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

@ -18,10 +18,9 @@ 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 { removeFromEmojiDataModel, updateEmojiDataModel } from "../../markdown"; import { removeFromEmojiDataModel, updateEmojiDataModel } from "../../markdown";
import { FirstLoadService } from "../../services/FirstLoadService"; import { FirstLoadService, I18NextService } from "../../services";
import { HttpService, RequestState } from "../../services/HttpService"; import { HttpService, RequestState } from "../../services/HttpService";
import { toast } from "../../toast"; import { toast } from "../../toast";
import { HtmlTags } from "../common/html-tags"; import { HtmlTags } from "../common/html-tags";
@ -108,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
}`; }`;
} }
@ -129,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", {
@ -181,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", {
@ -202,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", {
@ -253,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">
@ -275,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>
); );
@ -293,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">
@ -319,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 });
@ -340,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

@ -6,9 +6,8 @@ import {
EditCustomEmoji, EditCustomEmoji,
GetSiteResponse, GetSiteResponse,
} from "lemmy-js-client"; } from "lemmy-js-client";
import { i18n } from "../../i18next";
import { customEmojisLookup } from "../../markdown"; import { customEmojisLookup } from "../../markdown";
import { HttpService } from "../../services/HttpService"; import { HttpService, I18NextService } from "../../services";
import { pictrsDeleteToast, toast } from "../../toast"; import { pictrsDeleteToast, toast } from "../../toast";
import { EmojiMart } from "../common/emoji-mart"; import { EmojiMart } from "../common/emoji-mart";
import { HtmlTags } from "../common/html-tags"; import { HtmlTags } from "../common/html-tags";
@ -66,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() {
@ -76,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
@ -89,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>
@ -215,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) ||
@ -236,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"
@ -257,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} />
@ -280,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

@ -13,7 +13,6 @@ import {
showLocal, showLocal,
updatePersonBlock, updatePersonBlock,
} from "@utils/app"; } from "@utils/app";
import { restoreScrollPosition, saveScrollPosition } from "@utils/browser";
import { import {
getPageFromString, getPageFromString,
getQueryParams, getQueryParams,
@ -73,15 +72,13 @@ import {
TransferCommunity, TransferCommunity,
} from "lemmy-js-client"; } from "lemmy-js-client";
import { fetchLimit, relTags, trendingFetchLimit } from "../../config"; import { fetchLimit, relTags, trendingFetchLimit } from "../../config";
import { i18n } from "../../i18next";
import { import {
CommentViewType, CommentViewType,
DataType, DataType,
InitialFetchRequest, InitialFetchRequest,
} from "../../interfaces"; } from "../../interfaces";
import { mdToHtml } from "../../markdown"; import { mdToHtml } from "../../markdown";
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 { setupTippy } from "../../tippy"; import { setupTippy } from "../../tippy";
import { toast } from "../../toast"; import { toast } from "../../toast";
@ -197,7 +194,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 +207,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>
); );
@ -295,10 +292,6 @@ export class Home extends Component<any, HomeState> {
setupTippy(); setupTippy();
} }
componentWillUnmount() {
saveScrollPosition(this.context);
}
static async fetchInitialData({ static async fetchInitialData({
client, client,
auth, auth,
@ -565,10 +558,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"
@ -798,7 +795,6 @@ export class Home extends Component<any, HomeState> {
}); });
} }
restoreScrollPosition(this.context);
setupTippy(); setupTippy();
} }
@ -932,14 +928,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 +959,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 +1026,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

@ -7,9 +7,8 @@ import {
Instance, Instance,
} from "lemmy-js-client"; } from "lemmy-js-client";
import { relTags } from "../../config"; import { relTags } from "../../config";
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 { HtmlTags } from "../common/html-tags"; import { HtmlTags } from "../common/html-tags";
import { Spinner } from "../common/icon"; import { Spinner } from "../common/icon";
@ -70,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() {
@ -86,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>
)} )}
@ -127,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>
@ -148,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,8 +1,8 @@
import { setIsoData } from "@utils/app"; 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 } from "../../markdown";
import { I18NextService } from "../../services";
import { HtmlTags } from "../common/html-tags"; import { HtmlTags } from "../common/html-tags";
interface LegalState { interface LegalState {
@ -20,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

@ -3,8 +3,7 @@ import { isBrowser } from "@utils/browser";
import { validEmail } from "@utils/helpers"; 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 { toast } from "../../toast"; import { toast } from "../../toast";
import { HtmlTags } from "../common/html-tags"; import { HtmlTags } from "../common/html-tags";
@ -43,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 {
@ -68,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
@ -91,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
@ -112,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>
@ -124,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
@ -146,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>
@ -172,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 } });
@ -220,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

@ -3,7 +3,7 @@ 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 { Spinner } from "../common/icon"; import { Spinner } from "../common/icon";
import Tabs from "../common/tabs"; import Tabs from "../common/tabs";
@ -57,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"
@ -68,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"
@ -141,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", {
@ -176,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

@ -7,8 +7,7 @@ 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 { 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

@ -13,9 +13,8 @@ import {
SiteView, SiteView,
} from "lemmy-js-client"; } from "lemmy-js-client";
import { joinLemmyUrl } from "../../config"; import { joinLemmyUrl } from "../../config";
import { i18n } from "../../i18next";
import { mdToHtml } from "../../markdown"; import { mdToHtml } from "../../markdown";
import { UserService } from "../../services"; import { I18NextService, UserService } from "../../services";
import { HttpService, RequestState } from "../../services/HttpService"; import { HttpService, RequestState } from "../../services/HttpService";
import { toast } from "../../toast"; import { toast } from "../../toast";
import { HtmlTags } from "../common/html-tags"; import { HtmlTags } from "../common/html-tags";
@ -113,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"
); );
} }
@ -159,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">
@ -172,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
@ -188,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"
@ -202,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>
@ -213,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
@ -229,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>
@ -240,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
@ -262,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
@ -280,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
@ -306,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>
@ -345,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>
@ -384,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}
@ -474,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

@ -13,7 +13,7 @@ import {
Instance, Instance,
ListingType, ListingType,
} from "lemmy-js-client"; } from "lemmy-js-client";
import { i18n } from "../../i18next"; import { I18NextService } from "../../services";
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";
@ -136,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
@ -157,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}
@ -167,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}
@ -177,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
@ -191,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}
@ -204,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
@ -230,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>
@ -249,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>
@ -260,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"
@ -269,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
@ -309,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>
@ -331,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>
@ -353,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>
@ -372,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>
@ -383,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"
@ -391,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}
@ -403,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
@ -429,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>
@ -448,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>
@ -458,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
@ -485,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
@ -512,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>
@ -537,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>
@ -547,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
@ -579,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>
@ -591,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"
@ -599,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>
@ -616,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>
@ -634,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 "../../markdown";
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

@ -2,7 +2,7 @@ import { myAuthRequired } from "@utils/app";
import { capitalizeFirstLetter } from "@utils/helpers"; 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 { 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";
@ -27,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() {
@ -37,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">
@ -68,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>
@ -80,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>
@ -96,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>
@ -111,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

@ -46,9 +46,8 @@ import {
} from "lemmy-js-client"; } from "lemmy-js-client";
import moment from "moment"; import moment from "moment";
import { fetchLimit } from "../config"; import { fetchLimit } from "../config";
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 { HtmlTags } from "./common/html-tags"; import { HtmlTags } from "./common/html-tags";
import { Icon, Spinner } from "./common/icon"; import { Icon, Spinner } from "./common/icon";
@ -586,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)}
@ -724,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 {
@ -770,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">
@ -782,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>
@ -848,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

@ -59,10 +59,8 @@ import {
TransferCommunity, TransferCommunity,
} from "lemmy-js-client"; } from "lemmy-js-client";
import { fetchLimit, relTags } from "../../config"; import { fetchLimit, relTags } from "../../config";
import { i18n } from "../../i18next";
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 { toast } from "../../toast"; import { toast } from "../../toast";
import { CommentNodes } from "../comment/comment-nodes"; import { CommentNodes } from "../comment/comment-nodes";
@ -187,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}`
: ""; : "";
} }
@ -223,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}>
@ -245,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>
)} )}
@ -296,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
@ -310,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>
); );
@ -331,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
@ -345,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
@ -359,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
@ -373,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>
); );
@ -826,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);
} }
@ -837,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");
@ -849,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);
} }
} }
@ -857,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);
} }
} }
@ -892,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) {
@ -1004,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(`/`);
} }
} }
@ -1013,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

@ -2,8 +2,7 @@ import { myAuth, setIsoData } from "@utils/app";
import { capitalizeFirstLetter } from "@utils/helpers"; 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 { HtmlTags } from "../common/html-tags"; import { HtmlTags } from "../common/html-tags";
import { Spinner } from "../common/icon"; import { Spinner } from "../common/icon";
@ -34,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
}`; }`;
} }
@ -48,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>
@ -61,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
@ -77,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
@ -97,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

@ -72,11 +72,9 @@ import {
} from "lemmy-js-client"; } from "lemmy-js-client";
import moment from "moment"; import moment from "moment";
import { fetchLimit, relTags } from "../../config"; import { fetchLimit, relTags } from "../../config";
import { i18n } from "../../i18next";
import { InitialFetchRequest, PersonDetailsView } from "../../interfaces"; import { InitialFetchRequest, PersonDetailsView } from "../../interfaces";
import { mdToHtml } from "../../markdown"; import { mdToHtml } from "../../markdown";
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 { setupTippy } from "../../tippy"; import { setupTippy } from "../../tippy";
import { toast } from "../../toast"; import { toast } from "../../toast";
@ -137,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}>
@ -422,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>
); );
} }
@ -485,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>
@ -516,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={
@ -524,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
@ -536,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
@ -548,7 +546,7 @@ export class Profile extends Component<
this.handleBlockPerson this.handleBlockPerson
)} )}
> >
{i18n.t("block_user")} {I18NextService.i18n.t("block_user")}
</button> </button>
)} )}
</> </>
@ -563,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
@ -573,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>
@ -590,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),
})} })}
@ -604,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
@ -614,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()
@ -623,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>
@ -641,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)}
/> />
@ -674,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>
@ -684,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>
@ -904,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"));
} }
} }
@ -935,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) {
@ -999,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

@ -12,10 +12,8 @@ import {
RegistrationApplicationView, RegistrationApplicationView,
} from "lemmy-js-client"; } from "lemmy-js-client";
import { fetchLimit } from "../../config"; import { fetchLimit } from "../../config";
import { i18n } from "../../i18next";
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 { setupTippy } from "../../tippy"; import { setupTippy } from "../../tippy";
import { HtmlTags } from "../common/html-tags"; import { HtmlTags } from "../common/html-tags";
@ -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

@ -27,10 +27,13 @@ import {
ResolvePrivateMessageReport, ResolvePrivateMessageReport,
} from "lemmy-js-client"; } from "lemmy-js-client";
import { fetchLimit } from "../../config"; import { fetchLimit } from "../../config";
import { i18n } from "../../i18next";
import { InitialFetchRequest } from "../../interfaces"; import { InitialFetchRequest } from "../../interfaces";
import { HttpService, UserService } from "../../services"; import {
import { FirstLoadService } from "../../services/FirstLoadService"; FirstLoadService,
HttpService,
I18NextService,
UserService,
} from "../../services";
import { RequestState } from "../../services/HttpService"; import { RequestState } from "../../services/HttpService";
import { CommentReport } from "../comment/comment-report"; import { CommentReport } from "../comment/comment-report";
import { HtmlTags } from "../common/html-tags"; import { HtmlTags } from "../common/html-tags";
@ -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

@ -29,9 +29,9 @@ import {
SortType, SortType,
} from "lemmy-js-client"; } from "lemmy-js-client";
import { elementUrl, emDash, relTags } from "../../config"; import { elementUrl, emDash, relTags } from "../../config";
import { i18n, languages } from "../../i18next";
import { UserService } from "../../services"; import { UserService } from "../../services";
import { HttpService, RequestState } from "../../services/HttpService"; import { HttpService, RequestState } from "../../services/HttpService";
import { I18NextService, languages } from "../../services/I18NextService";
import { setupTippy } from "../../tippy"; import { setupTippy } from "../../tippy";
import { toast } from "../../toast"; import { toast } from "../../toast";
import { HtmlTags } from "../common/html-tags"; import { HtmlTags } from "../common/html-tags";
@ -113,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
@ -233,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() {
@ -249,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,
}, },
]} ]}
@ -316,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
@ -339,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
@ -358,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
@ -380,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>
@ -409,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}>
@ -421,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>
@ -453,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}>
@ -465,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>
@ -482,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="^(?!@)(.+)$"
@ -503,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
@ -518,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}
@ -535,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">
@ -552,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}
@ -566,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}
@ -579,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
@ -589,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>
@ -616,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
@ -626,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}
@ -638,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_={
@ -653,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
@ -674,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>
@ -688,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>
@ -702,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>
@ -716,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>
@ -733,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>
@ -750,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>
@ -767,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>
@ -790,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>
@ -800,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>
@ -813,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"
@ -839,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
@ -849,7 +857,7 @@ export class Settings extends Component<any, SettingsState> {
this.handleDeleteAccountShowConfirmToggle this.handleDeleteAccountShowConfirmToggle
)} )}
> >
{i18n.t("cancel")} {I18NextService.i18n.t("cancel")}
</button> </button>
</> </>
)} )}
@ -876,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>
@ -886,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">
@ -901,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>
@ -1050,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));
} }
@ -1078,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)
@ -1168,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);
} }
@ -1191,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,7 +1,7 @@
import { setIsoData } from "@utils/app"; 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 { toast } from "../../toast"; import { toast } from "../../toast";
import { HtmlTags } from "../common/html-tags"; import { HtmlTags } from "../common/html-tags";
@ -36,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");
} }
} }
@ -46,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
}`; }`;
} }
@ -60,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

@ -11,9 +11,8 @@ 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,
@ -143,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
}`; }`;
} }
@ -171,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

@ -2,7 +2,7 @@ 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 { relTags } from "../../config"; import { relTags } from "../../config";
import { i18n } from "../../i18next"; 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

@ -31,9 +31,8 @@ import {
trendingFetchLimit, trendingFetchLimit,
webArchiveUrl, webArchiveUrl,
} from "../../config"; } from "../../config";
import { i18n } from "../../i18next";
import { PostFormParams } from "../../interfaces"; import { PostFormParams } from "../../interfaces";
import { UserService } from "../../services"; import { I18NextService, UserService } from "../../services";
import { HttpService, RequestState } from "../../services/HttpService"; import { HttpService, RequestState } from "../../services/HttpService";
import { setupTippy } from "../../tippy"; import { setupTippy } from "../../tippy";
import { toast } from "../../toast"; import { toast } from "../../toast";
@ -342,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
@ -360,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>
@ -381,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(
@ -390,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(
@ -399,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>
)} )}
@ -411,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
@ -455,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
@ -472,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()}
@ -480,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}
@ -501,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
@ -509,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,
@ -530,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
@ -553,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 && (
@ -564,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>
@ -590,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>
) )
); );
@ -610,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,6 +1,12 @@
import { myAuthRequired, newVote, showScores } from "@utils/app"; 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 { getExternalHost, getHttpBase } from "@utils/env";
import {
capitalizeFirstLetter,
futureDaysToUnixTime,
hostname,
numToSI,
} from "@utils/helpers";
import { isImage, isVideo } from "@utils/media"; import { isImage, isVideo } from "@utils/media";
import { import {
amAdmin, amAdmin,
@ -38,11 +44,9 @@ import {
TransferCommunity, TransferCommunity,
} from "lemmy-js-client"; } from "lemmy-js-client";
import { relTags } from "../../config"; import { relTags } from "../../config";
import { getExternalHost, getHttpBase } from "../../env";
import { i18n } from "../../i18next";
import { BanType, PostFormParams, PurgeType, VoteType } from "../../interfaces"; import { BanType, PostFormParams, PurgeType, VoteType } from "../../interfaces";
import { mdNoImages, mdToHtml, mdToHtmlInline } from "../../markdown"; import { mdNoImages, mdToHtml, mdToHtmlInline } from "../../markdown";
import { UserService } from "../../services"; import { I18NextService, UserService } from "../../services";
import { setupTippy } from "../../tippy"; import { setupTippy } from "../../tippy";
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";
@ -235,7 +239,8 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
} }
get img() { get img() {
return this.imageSrc ? ( if (this.imageSrc) {
return (
<> <>
<div className="offset-sm-3 my-2 d-none d-sm-block"> <div className="offset-sm-3 my-2 d-none d-sm-block">
<a href={this.imageSrc} className="d-inline-block"> <a href={this.imageSrc} className="d-inline-block">
@ -251,11 +256,25 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
</a> </a>
</div> </div>
</> </>
) : (
<></>
); );
} }
const { post } = this.postView;
const { url } = post;
if (url && isVideo(url)) {
return (
<div className="embed-responsive mt-3">
<video muted controls className="embed-responsive-item col-12">
<source src={url} type="video/mp4" />
</video>
</div>
);
}
return <></>;
}
imgThumb(src: string) { imgThumb(src: string) {
const post_view = this.postView; const post_view = this.postView;
return ( return (
@ -298,9 +317,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" />
@ -321,17 +340,19 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
} else if (url) { } else if (url) {
if (!this.props.hideImage && isVideo(url)) { if (!this.props.hideImage && isVideo(url)) {
return ( return (
<div className="embed-responsive embed-responsive-16by9"> <a
<video className="text-body"
playsInline href={url}
muted title={url}
loop rel={relTags}
controls data-tippy-content={I18NextService.i18n.t("expand_here")}
className="embed-responsive-item" onClick={linkEvent(this, this.handleImageExpandClick)}
aria-label={I18NextService.i18n.t("expand_here")}
> >
<source src={url} type="video/mp4" /> <div className="thumbnail rounded bg-light d-flex justify-content-center">
</video> <Icon icon="play" classes="d-flex align-items-center" />
</div> </div>
</a>
); );
} else { } else {
return ( return (
@ -347,7 +368,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" />
@ -363,20 +384,25 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
<span className="small"> <span className="small">
<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} />
</> </>
)} )}
{post_view.post.language_id !== 0 && ( {post_view.post.language_id !== 0 && (
@ -405,8 +431,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 ? (
@ -431,8 +457,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 ? (
@ -456,7 +482,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"
@ -494,7 +520,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
@ -507,13 +533,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>
@ -521,7 +547,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>
@ -529,8 +555,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>
@ -538,15 +566,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>
@ -580,7 +608,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}`}>
@ -615,7 +645,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 />
@ -674,11 +704,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>
@ -718,7 +748,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),
}); });
@ -734,7 +764,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
{post_view.counts.comments} {post_view.counts.comments}
{this.unreadCount && ( {this.unreadCount && (
<span className="text-muted fst-italic"> <span className="text-muted fst-italic">
({this.unreadCount} {i18n.t("new")}) ({this.unreadCount} {I18NextService.i18n.t("new")})
</span> </span>
)} )}
</Link> </Link>
@ -762,7 +792,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 ? (
@ -785,7 +815,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 ? (
@ -813,7 +843,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"
@ -846,9 +878,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>
@ -860,10 +892,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>
); );
} }
@ -873,14 +905,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>
); );
} }
@ -890,17 +922,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"
@ -928,8 +962,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"
@ -942,7 +976,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"
@ -958,7 +994,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
classes={classNames("me-1", { "text-danger": locked })} classes={classNames("me-1", { "text-danger": locked })}
inline inline
/> />
{label} {capitalizeFirstLetter(label)}
</> </>
)} )}
</button> </button>
@ -968,13 +1004,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>
@ -995,7 +1031,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
})} })}
inline inline
/> />
{i18n.t("community")} {I18NextService.i18n.t("community")}
</> </>
)} )}
</button> </button>
@ -1019,7 +1055,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
})} })}
inline inline
/> />
{i18n.t("local")} {I18NextService.i18n.t("local")}
</> </>
)} )}
</button> </button>
@ -1043,9 +1079,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>
); );
@ -1070,9 +1106,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
@ -1081,9 +1117,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 && (
@ -1092,16 +1132,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>
)} )}
@ -1118,24 +1158,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"
@ -1143,9 +1187,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>
</> </>
))} ))}
@ -1158,36 +1202,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>
</> </>
)} )}
@ -1197,16 +1241,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>
)} )}
@ -1221,8 +1265,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 && (
@ -1234,22 +1278,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>
)} )}
@ -1260,24 +1308,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)}
/> />
@ -1293,9 +1341,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>
@ -1303,19 +1351,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>
@ -1328,13 +1376,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)}
@ -1342,9 +1390,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>
)} )}
@ -1355,13 +1407,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)}
/> />
@ -1557,10 +1609,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;
} }
@ -1822,17 +1873,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

@ -2,7 +2,7 @@ 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 { 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

@ -76,14 +76,12 @@ import {
TransferCommunity, TransferCommunity,
} from "lemmy-js-client"; } from "lemmy-js-client";
import { commentTreeMaxDepth } from "../../config"; import { commentTreeMaxDepth } from "../../config";
import { i18n } from "../../i18next";
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 { setupTippy } from "../../tippy"; import { setupTippy } from "../../tippy";
import { toast } from "../../toast"; import { toast } from "../../toast";
@ -400,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
@ -438,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"
@ -452,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"
@ -466,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"
@ -480,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"
@ -496,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"
@ -595,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>
)} )}
</> </>
@ -836,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"));
} }
} }
@ -982,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

@ -7,9 +7,8 @@ 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 { toast } from "../../toast"; import { toast } from "../../toast";
import { HtmlTags } from "../common/html-tags"; import { HtmlTags } from "../common/html-tags";
@ -97,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 "";
} }
@ -116,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}
@ -144,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

@ -9,7 +9,7 @@ import {
PrivateMessageView, PrivateMessageView,
} from "lemmy-js-client"; } from "lemmy-js-client";
import { relTags } from "../../config"; import { relTags } from "../../config";
import { i18n } from "../../i18next"; import { I18NextService } from "../../services";
import { setupTippy } from "../../tippy"; import { setupTippy } from "../../tippy";
import { Icon, Spinner } from "../common/icon"; import { Icon, Spinner } from "../common/icon";
import { MarkdownTextArea } from "../common/markdown-textarea"; import { MarkdownTextArea } from "../common/markdown-textarea";
@ -66,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() {
@ -83,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">
@ -93,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>
@ -142,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 && (
@ -153,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

@ -5,8 +5,8 @@ import {
PrivateMessageReportView, PrivateMessageReportView,
ResolvePrivateMessageReport, ResolvePrivateMessageReport,
} from "lemmy-js-client"; } from "lemmy-js-client";
import { i18n } from "../../i18next";
import { mdToHtml } from "../../markdown"; import { mdToHtml } from "../../markdown";
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";
@ -39,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

@ -9,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 { mdToHtml } from "../../markdown";
import { UserService } from "../../services"; import { I18NextService, UserService } from "../../services";
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";
@ -94,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} />
@ -148,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 ? (
@ -175,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>
@ -189,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>
@ -201,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 ? (
@ -229,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"
@ -250,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)}
@ -264,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>
)} )}
@ -287,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>
@ -297,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

@ -46,9 +46,8 @@ import {
SortType, SortType,
} from "lemmy-js-client"; } from "lemmy-js-client";
import { fetchLimit } from "../config"; import { fetchLimit } from "../config";
import { i18n } from "../i18next";
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 { CommentNodes } from "./comment/comment-nodes"; import { CommentNodes } from "./comment/comment-nodes";
import { HtmlTags } from "./common/html-tags"; import { HtmlTags } from "./common/html-tags";
@ -184,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)}
@ -228,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>
@ -448,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() {
@ -460,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>
@ -499,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}
@ -511,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>
@ -540,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>

View file

@ -24,3 +24,15 @@ export const updateUnreadCountsInterval = 30000;
export const fetchLimit = 40; export const fetchLimit = 40;
export const relTags = "noopener nofollow"; export const relTags = "noopener nofollow";
export const emDash = "\u2014"; export const emDash = "\u2014";
/**
* Accepted formats:
* !community@server.com
* /c/community@server.com
* /m/community@server.com
* /u/username@server.com
*/
export const instanceLinkRegex =
/(\/[cmu]\/|!)[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g;
export const testHost = "0.0.0.0:8536";

View file

@ -1,66 +0,0 @@
import { isBrowser } from "@utils/browser";
const testHost = "0.0.0.0:8536";
function getInternalHost() {
return !isBrowser()
? process.env.LEMMY_UI_LEMMY_INTERNAL_HOST ?? testHost
: testHost; // used for local dev
}
export function getExternalHost() {
return isBrowser()
? `${window.location.hostname}${
["1234", "1235"].includes(window.location.port)
? ":8536"
: window.location.port == ""
? ""
: `:${window.location.port}`
}`
: process.env.LEMMY_UI_LEMMY_EXTERNAL_HOST || testHost;
}
function getSecure() {
return (
isBrowser()
? window.location.protocol.includes("https")
: process.env.LEMMY_UI_HTTPS === "true"
)
? "s"
: "";
}
function getHost() {
return isBrowser() ? getExternalHost() : getInternalHost();
}
function getBaseLocal(s = "") {
return `http${s}://${getHost()}`;
}
export function getHttpBaseInternal() {
return getBaseLocal(); // Don't use secure here
}
export function getHttpBaseExternal() {
return `http${getSecure()}://${getExternalHost()}`;
}
export function getHttpBase() {
return getBaseLocal(getSecure());
}
export function isHttps() {
return getSecure() === "s";
}
console.log(`httpbase: ${getHttpBase()}`);
console.log(`isHttps: ${isHttps()}`);
// This is for html tags, don't include port
export function httpExternalPath(path: string) {
return `http${getSecure()}://${getExternalHost().replace(
/:\d+/g,
""
)}${path}`;
}

View file

@ -7,13 +7,14 @@ import emojiShortName from "emoji-short-name";
import { CustomEmojiView } from "lemmy-js-client"; import { CustomEmojiView } from "lemmy-js-client";
import { default as MarkdownIt } from "markdown-it"; import { default as MarkdownIt } from "markdown-it";
import markdown_it_container from "markdown-it-container"; import markdown_it_container from "markdown-it-container";
import markdown_it_emoji from "markdown-it-emoji/bare"; // import markdown_it_emoji from "markdown-it-emoji/bare";
import markdown_it_footnote from "markdown-it-footnote"; import markdown_it_footnote from "markdown-it-footnote";
import markdown_it_html5_embed from "markdown-it-html5-embed"; import markdown_it_html5_embed from "markdown-it-html5-embed";
import markdown_it_sub from "markdown-it-sub"; import markdown_it_sub from "markdown-it-sub";
import markdown_it_sup from "markdown-it-sup"; import markdown_it_sup from "markdown-it-sup";
import Renderer from "markdown-it/lib/renderer"; import Renderer from "markdown-it/lib/renderer";
import Token from "markdown-it/lib/token"; import Token from "markdown-it/lib/token";
import { instanceLinkRegex } from "./config";
export let Tribute: any; export let Tribute: any;
@ -72,6 +73,75 @@ const html5EmbedConfig = {
}, },
}; };
function localInstanceLinkParser(md: MarkdownIt) {
md.core.ruler.push("replace-text", state => {
for (let i = 0; i < state.tokens.length; i++) {
if (state.tokens[i].type !== "inline") {
continue;
}
const inlineTokens: Token[] = state.tokens[i].children || [];
for (let j = inlineTokens.length - 1; j >= 0; j--) {
if (
inlineTokens[j].type === "text" &&
new RegExp(instanceLinkRegex).test(inlineTokens[j].content)
) {
const text = inlineTokens[j].content;
const matches = Array.from(text.matchAll(instanceLinkRegex));
let lastIndex = 0;
const newTokens: Token[] = [];
let linkClass = "community-link";
for (const match of matches) {
// If there is plain text before the match, add it as a separate token
if (match.index !== undefined && match.index > lastIndex) {
const textToken = new state.Token("text", "", 0);
textToken.content = text.slice(lastIndex, match.index);
newTokens.push(textToken);
}
let href;
if (match[0].startsWith("!")) {
href = "/c/" + match[0].substring(1);
} else if (match[0].startsWith("/m/")) {
href = "/c/" + match[0].substring(3);
} else {
href = match[0];
if (match[0].startsWith("/u/")) {
linkClass = "user-link";
}
}
const linkOpenToken = new state.Token("link_open", "a", 1);
linkOpenToken.attrs = [
["href", href],
["class", linkClass],
];
const textToken = new state.Token("text", "", 0);
textToken.content = match[0];
const linkCloseToken = new state.Token("link_close", "a", -1);
newTokens.push(linkOpenToken, textToken, linkCloseToken);
lastIndex =
(match.index !== undefined ? match.index : 0) + match[0].length;
}
// If there is plain text after the last match, add it as a separate token
if (lastIndex < text.length) {
const textToken = new state.Token("text", "", 0);
textToken.content = text.slice(lastIndex);
newTokens.push(textToken);
}
inlineTokens.splice(j, 1, ...newTokens);
}
}
}
});
}
export function setupMarkdown() { export function setupMarkdown() {
const markdownItConfig: MarkdownIt.Options = { const markdownItConfig: MarkdownIt.Options = {
html: false, html: false,
@ -79,19 +149,20 @@ export function setupMarkdown() {
typographer: true, typographer: true,
}; };
const emojiDefs = Array.from(customEmojisLookup.entries()).reduce( // const emojiDefs = Array.from(customEmojisLookup.entries()).reduce(
(main, [key, value]) => ({ ...main, [key]: value }), // (main, [key, value]) => ({ ...main, [key]: value }),
{} // {}
); // );
md = new MarkdownIt(markdownItConfig) md = new MarkdownIt(markdownItConfig)
.use(markdown_it_sub) .use(markdown_it_sub)
.use(markdown_it_sup) .use(markdown_it_sup)
.use(markdown_it_footnote) .use(markdown_it_footnote)
.use(markdown_it_html5_embed, html5EmbedConfig) .use(markdown_it_html5_embed, html5EmbedConfig)
.use(markdown_it_container, "spoiler", spoilerConfig) .use(markdown_it_container, "spoiler", spoilerConfig)
.use(markdown_it_emoji, { .use(localInstanceLinkParser);
defs: emojiDefs, // .use(markdown_it_emoji, {
}); // defs: emojiDefs,
// });
mdNoImages = new MarkdownIt(markdownItConfig) mdNoImages = new MarkdownIt(markdownItConfig)
.use(markdown_it_sub) .use(markdown_it_sub)
@ -99,9 +170,10 @@ export function setupMarkdown() {
.use(markdown_it_footnote) .use(markdown_it_footnote)
.use(markdown_it_html5_embed, html5EmbedConfig) .use(markdown_it_html5_embed, html5EmbedConfig)
.use(markdown_it_container, "spoiler", spoilerConfig) .use(markdown_it_container, "spoiler", spoilerConfig)
.use(markdown_it_emoji, { .use(localInstanceLinkParser)
defs: emojiDefs, // .use(markdown_it_emoji, {
}) // defs: emojiDefs,
// })
.disable("image"); .disable("image");
const defaultRenderer = md.renderer.rules.image; const defaultRenderer = md.renderer.rules.image;
md.renderer.rules.image = function ( md.renderer.rules.image = function (

View file

@ -1,7 +1,7 @@
import { getHttpBase } from "@utils/env";
import { LemmyHttp } from "lemmy-js-client"; import { LemmyHttp } from "lemmy-js-client";
import { getHttpBase } from "../../shared/env";
import { i18n } from "../../shared/i18next";
import { toast } from "../../shared/toast"; import { toast } from "../../shared/toast";
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/UserService"; 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,12 +1,12 @@
// import Cookies from 'js-cookie'; // import Cookies from 'js-cookie';
import { isAuthPath } from "@utils/app"; import { isAuthPath } from "@utils/app";
import { isBrowser } from "@utils/browser"; import { isBrowser } from "@utils/browser";
import { isHttps } from "@utils/env";
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 { i18n } from "../i18next";
import { toast } from "../toast"; import { toast } from "../toast";
import { I18NextService } from "./I18NextService";
interface Claims { interface Claims {
sub: number; sub: number;
@ -32,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();
} }
@ -58,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";

View file

@ -1,7 +1,7 @@
import { isBrowser } from "@utils/browser"; import { isBrowser } from "@utils/browser";
import { ThemeColor } from "@utils/types"; import { ThemeColor } from "@utils/types";
import Toastify from "toastify-js"; import Toastify from "toastify-js";
import { i18n } from "./i18next"; import { I18NextService } from "./services";
export function toast(text: string, background: ThemeColor = "success") { export function toast(text: string, background: ThemeColor = "success") {
if (isBrowser()) { if (isBrowser()) {
@ -18,13 +18,18 @@ export function toast(text: string, background: ThemeColor = "success") {
export function pictrsDeleteToast(filename: string, deleteUrl: string) { export function pictrsDeleteToast(filename: string, deleteUrl: string) {
if (isBrowser()) { if (isBrowser()) {
const clickToDeleteText = i18n.t("click_to_delete_picture", { filename }); const clickToDeleteText = I18NextService.i18n.t("click_to_delete_picture", {
const deletePictureText = i18n.t("picture_deleted", {
filename, filename,
}); });
const failedDeletePictureText = i18n.t("failed_to_delete_picture", { const deletePictureText = I18NextService.i18n.t("picture_deleted", {
filename, filename,
}); });
const failedDeletePictureText = I18NextService.i18n.t(
"failed_to_delete_picture",
{
filename,
}
);
const backgroundColor = `var(--bs-light)`; const backgroundColor = `var(--bs-light)`;

View file

@ -5,6 +5,9 @@ export default function convertCommentSortType(
): CommentSortType { ): CommentSortType {
switch (sort) { switch (sort) {
case "TopAll": case "TopAll":
case "TopHour":
case "TopSixHour":
case "TopTwelveHour":
case "TopDay": case "TopDay":
case "TopWeek": case "TopWeek":
case "TopMonth": case "TopMonth":

View file

@ -1,11 +1,10 @@
import { GetSiteResponse } from "lemmy-js-client"; import { GetSiteResponse } from "lemmy-js-client";
import { i18n } from "../../i18next";
import { setupEmojiDataModel, setupMarkdown } from "../../markdown"; import { setupEmojiDataModel, setupMarkdown } from "../../markdown";
import { UserService } from "../../services"; import { I18NextService, UserService } from "../../services";
export default function initializeSite(site?: GetSiteResponse) { export default function initializeSite(site?: GetSiteResponse) {
UserService.Instance.myUserInfo = site?.my_user; UserService.Instance.myUserInfo = site?.my_user;
i18n.changeLanguage(); I18NextService.i18n.changeLanguage();
if (site) { if (site) {
setupEmojiDataModel(site.custom_emojis ?? []); setupEmojiDataModel(site.custom_emojis ?? []);
} }

View file

@ -1,6 +1,5 @@
import { BlockCommunityResponse, MyUserInfo } from "lemmy-js-client"; import { BlockCommunityResponse, MyUserInfo } from "lemmy-js-client";
import { i18n } from "../../i18next"; import { I18NextService, UserService } from "../../services";
import { UserService } from "../../services";
import { toast } from "../../toast"; import { toast } from "../../toast";
export default function updateCommunityBlock( export default function updateCommunityBlock(
@ -13,12 +12,20 @@ export default function updateCommunityBlock(
person: myUserInfo.local_user_view.person, person: myUserInfo.local_user_view.person,
community: data.community_view.community, community: data.community_view.community,
}); });
toast(`${i18n.t("blocked")} ${data.community_view.community.name}`); toast(
`${I18NextService.i18n.t("blocked")} ${
data.community_view.community.name
}`
);
} else { } else {
myUserInfo.community_blocks = myUserInfo.community_blocks.filter( myUserInfo.community_blocks = myUserInfo.community_blocks.filter(
i => i.community.id !== data.community_view.community.id i => i.community.id !== data.community_view.community.id
); );
toast(`${i18n.t("unblocked")} ${data.community_view.community.name}`); toast(
`${I18NextService.i18n.t("unblocked")} ${
data.community_view.community.name
}`
);
} }
} }
} }

View file

@ -1,6 +1,5 @@
import { BlockPersonResponse, MyUserInfo } from "lemmy-js-client"; import { BlockPersonResponse, MyUserInfo } from "lemmy-js-client";
import { i18n } from "../../i18next"; import { I18NextService, UserService } from "../../services";
import { UserService } from "../../services";
import { toast } from "../../toast"; import { toast } from "../../toast";
export default function updatePersonBlock( export default function updatePersonBlock(
@ -13,12 +12,16 @@ export default function updatePersonBlock(
person: myUserInfo.local_user_view.person, person: myUserInfo.local_user_view.person,
target: data.person_view.person, target: data.person_view.person,
}); });
toast(`${i18n.t("blocked")} ${data.person_view.person.name}`); toast(
`${I18NextService.i18n.t("blocked")} ${data.person_view.person.name}`
);
} else { } else {
myUserInfo.person_blocks = myUserInfo.person_blocks.filter( myUserInfo.person_blocks = myUserInfo.person_blocks.filter(
i => i.target.id !== data.person_view.person.id i => i.target.id !== data.person_view.person.id
); );
toast(`${i18n.t("unblocked")} ${data.person_view.person.name}`); toast(
`${I18NextService.i18n.t("unblocked")} ${data.person_view.person.name}`
);
} }
} }
} }

View file

@ -1,5 +1,6 @@
export default function restoreScrollPosition(context: any) { export default function restoreScrollPosition(context: any) {
const path: string = context.router.route.location.pathname; const path: string = context.router.route.location.pathname;
const y = Number(sessionStorage.getItem(`scrollPosition_${path}`)); const y = Number(sessionStorage.getItem(`scrollPosition_${path}`));
window.scrollTo(0, y); window.scrollTo(0, y);
} }

View file

@ -1,5 +1,6 @@
export default function saveScrollPosition(context: any) { export default function saveScrollPosition(context: any) {
const path: string = context.router.route.location.pathname; const path: string = context.router.route.location.pathname;
const y = window.scrollY; const y = window.scrollY;
sessionStorage.setItem(`scrollPosition_${path}`, y.toString()); sessionStorage.setItem(`scrollPosition_${path}`, y.toString());
} }

View file

@ -0,0 +1,5 @@
import { getHost } from "@utils/env";
export default function getBaseLocal(s = "") {
return `http${s}://${getHost()}`;
}

View file

@ -0,0 +1,14 @@
import { isBrowser } from "@utils/browser";
import { testHost } from "../../config";
export default function getExternalHost() {
return isBrowser()
? `${window.location.hostname}${
["1234", "1235"].includes(window.location.port)
? ":8536"
: window.location.port == ""
? ""
: `:${window.location.port}`
}`
: process.env.LEMMY_UI_LEMMY_EXTERNAL_HOST || testHost;
}

6
src/shared/utils/env/get-host.ts vendored Normal file
View file

@ -0,0 +1,6 @@
import { isBrowser } from "@utils/browser";
import { getExternalHost, getInternalHost } from "@utils/env";
export default function getHost() {
return isBrowser() ? getExternalHost() : getInternalHost();
}

View file

@ -0,0 +1,5 @@
import { getExternalHost, getSecure } from "@utils/env";
export default function getHttpBaseExternal() {
return `http${getSecure()}://${getExternalHost()}`;
}

View file

@ -0,0 +1,5 @@
import { getBaseLocal } from "@utils/env";
export default function getHttpBaseInternal() {
return getBaseLocal(); // Don't use secure here
}

5
src/shared/utils/env/get-http-base.ts vendored Normal file
View file

@ -0,0 +1,5 @@
import { getBaseLocal, getSecure } from "@utils/env";
export default function getHttpBase() {
return getBaseLocal(getSecure());
}

View file

@ -0,0 +1,8 @@
import { isBrowser } from "@utils/browser";
import { testHost } from "../../config";
export default function getInternalHost() {
return !isBrowser()
? process.env.LEMMY_UI_LEMMY_INTERNAL_HOST ?? testHost
: testHost; // used for local dev
}

11
src/shared/utils/env/get-secure.ts vendored Normal file
View file

@ -0,0 +1,11 @@
import { isBrowser } from "@utils/browser";
export default function getSecure() {
return (
isBrowser()
? window.location.protocol.includes("https")
: process.env.LEMMY_UI_HTTPS === "true"
)
? "s"
: "";
}

View file

@ -0,0 +1,9 @@
import { getExternalHost, getSecure } from "@utils/env";
// This is for html tags, don't include port
export default function httpExternalPath(path: string) {
return `http${getSecure()}://${getExternalHost().replace(
/:\d+/g,
""
)}${path}`;
}

23
src/shared/utils/env/index.ts vendored Normal file
View file

@ -0,0 +1,23 @@
import getBaseLocal from "./get-base-local";
import getExternalHost from "./get-external-host";
import getHost from "./get-host";
import getHttpBase from "./get-http-base";
import getHttpBaseExternal from "./get-http-base-external";
import getHttpBaseInternal from "./get-http-base-internal";
import getInternalHost from "./get-internal-host";
import getSecure from "./get-secure";
import httpExternalPath from "./http-external-path";
import isHttps from "./is-https";
export {
getBaseLocal,
getExternalHost,
getHost,
getHttpBase,
getHttpBaseExternal,
getHttpBaseInternal,
getInternalHost,
getSecure,
httpExternalPath,
isHttps,
};

5
src/shared/utils/env/is-https.ts vendored Normal file
View file

@ -0,0 +1,5 @@
import { getSecure } from "@utils/env";
export default function isHttps() {
return getSecure() === "s";
}