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",
"version": "0.18.0-rc.4",
"version": "0.18.0-rc.6",
"description": "An isomorphic UI for lemmy",
"repository": "https://github.com/LemmyNet/lemmy-ui",
"license": "AGPL-3.0",
@ -22,9 +22,16 @@
"translations:update": "git submodule update --remote --recursive"
},
"lint-staged": {
"*.{ts,tsx,js}": ["prettier --write", "eslint --fix"],
"*.{css, scss}": ["prettier --write"],
"package.json": ["sortpack"]
"*.{ts,tsx,js}": [
"prettier --write",
"eslint --fix"
],
"*.{css, scss}": [
"prettier --write"
],
"package.json": [
"sortpack"
]
},
"dependencies": {
"@babel/plugin-proposal-decorators": "^7.21.0",

View file

@ -2,7 +2,7 @@ import { initializeSite } from "@utils/app";
import { hydrate } from "inferno-hydrate";
import { Router } from "inferno-router";
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/dropdown";

View file

@ -1,12 +1,13 @@
import { initializeSite, isAuthPath } from "@utils/app";
import { getHttpBaseInternal } from "@utils/env";
import { ErrorPageData } from "@utils/types";
import fetch from "cross-fetch";
import type { Request, Response } from "express";
import { StaticRouter, matchPath } from "inferno-router";
import { renderToString } from "inferno-server";
import IsomorphicCookie from "isomorphic-cookie";
import { GetSite, GetSiteResponse, LemmyHttp } from "lemmy-js-client";
import { App } from "../../shared/components/app/app";
import { getHttpBaseInternal } from "../../shared/env";
import {
InitialFetchRequest,
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 { LemmyHttp } from "lemmy-js-client";
import { getHttpBaseInternal } from "../../shared/env";
import { wrapClient } from "../../shared/services/HttpService";
import generateManifestJson from "../utils/generate-manifest-json";
import { setForwardedHeaders } from "../utils/set-forwarded-headers";
@ -9,7 +10,7 @@ let manifest: Awaited<ReturnType<typeof generateManifestJson>> | undefined =
undefined;
export default async (req: Request, res: Response) => {
if (!manifest) {
if (!manifest || manifest.start_url !== getHttpBaseExternal()) {
const headers = setForwardedHeaders(req.headers);
const client = wrapClient(
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("/service-worker.js", ServiceWorkerHandler);
server.get("/manifest", ManifestHandler);
server.get("/manifest.webmanifest", ManifestHandler);
server.get("/css/themes/:name", ThemeHandler);
server.get("/css/themelist", ThemesListHandler);
server.get("/*", CatchAllHandler);

View file

@ -4,15 +4,20 @@ import { readdir } from "fs/promises";
const extraThemesFolder =
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)) {
const dirThemes = await readdir(extraThemesFolder);
const cssThemes = dirThemes
.filter(d => d.endsWith(".css"))
.map(d => d.replace(".css", ""));
themes.push(...cssThemes);
return themes.concat(cssThemes);
}
return themes;
}

View file

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

View file

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

View file

@ -1,8 +1,8 @@
import { getHttpBaseExternal } from "@utils/env";
import { readFile } from "fs/promises";
import { GetSiteResponse } from "lemmy-js-client";
import path from "path";
import sharp from "sharp";
import { getHttpBaseExternal } from "../../shared/env";
import { fetchIconPng } from "./fetch-icon-png";
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 { Provider } from "inferno-i18next-dess";
import { Route, Switch } from "inferno-router";
import { i18n } from "../../i18next";
import { IsoDataOptionalSite } from "../../interfaces";
import { routes } from "../../routes";
import { I18NextService } from "../../services";
import AuthGuard from "../common/auth-guard";
import ErrorGuard from "../common/error-guard";
import { ErrorPage } from "./error-page";
@ -31,13 +31,13 @@ export class App extends Component<any, any> {
return (
<>
<Provider i18next={i18n}>
<Provider i18next={I18NextService.i18n}>
<div id="app" className="lemmy-site">
<a
className="skip-link bg-light text-dark p-2 text-decoration-none position-absolute start-0 z-3"
onClick={linkEvent(this, this.handleJumpToContent)}
>
${i18n.t("jump_to_content", "Jump to content")}
${I18NextService.i18n.t("jump_to_content", "Jump to content")}
</a>
{siteView && (
<Theme defaultTheme={siteView.local_site.default_theme} />

View file

@ -2,8 +2,8 @@ import { setIsoData } from "@utils/app";
import { Component } from "inferno";
import { T } from "inferno-i18next-dess";
import { Link } from "inferno-router";
import { i18n } from "../../i18next";
import { IsoDataOptionalSite } from "../../interfaces";
import { I18NextService } from "../../services";
export class ErrorPage extends Component<any, any> {
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">
<h1>
{errorPageData
? i18n.t("error_page_title")
: i18n.t("not_found_page_title")}
? I18NextService.i18n.t("error_page_title")
: I18NextService.i18n.t("not_found_page_title")}
</h1>
{errorPageData ? (
<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>#
</T>
) : (
<p>{i18n.t("not_found_page_message")}</p>
<p>{I18NextService.i18n.t("not_found_page_message")}</p>
)}
{!errorPageData && (
<Link to="/" replace>
{i18n.t("not_found_return_home_button")}
{I18NextService.i18n.t("not_found_return_home_button")}
</Link>
)}
{errorPageData?.adminMatrixIds &&
errorPageData.adminMatrixIds.length > 0 && (
<>
<div>
{i18n.t("error_page_admin_matrix", {
{I18NextService.i18n.t("error_page_admin_matrix", {
instance:
this.isoData.site_res?.site_view.site.name ??
"this instance",

View file

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

View file

@ -11,8 +11,7 @@ import {
GetUnreadRegistrationApplicationCountResponse,
} from "lemmy-js-client";
import { donateLemmyUrl, updateUnreadCountsInterval } from "../../config";
import { i18n } from "../../i18next";
import { UserService } from "../../services";
import { I18NextService, UserService } from "../../services";
import { HttpService, RequestState } from "../../services/HttpService";
import { toast } from "../../toast";
import { Icon } from "../common/icon";
@ -102,7 +101,7 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
<NavLink
to="/inbox"
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),
formattedCount: numToSI(this.unreadInboxCount),
})}
@ -121,7 +120,7 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
<NavLink
to="/reports"
className="p-1 nav-link border-0"
title={i18n.t("unread_reports", {
title={I18NextService.i18n.t("unread_reports", {
count: Number(this.unreadReportCount),
formattedCount: numToSI(this.unreadReportCount),
})}
@ -141,10 +140,13 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
<NavLink
to="/registration_applications"
className="p-1 nav-link border-0"
title={i18n.t("unread_registration_applications", {
title={I18NextService.i18n.t(
"unread_registration_applications",
{
count: Number(this.unreadApplicationCount),
formattedCount: numToSI(this.unreadApplicationCount),
})}
}
)}
onMouseUp={linkEvent(this, handleCollapseClick)}
>
<Icon icon="clipboard" />
@ -162,7 +164,7 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
className="navbar-toggler border-0 p-1"
type="button"
aria-label="menu"
data-tippy-content={i18n.t("expand_here")}
data-tippy-content={I18NextService.i18n.t("expand_here")}
data-bs-toggle="collapse"
data-bs-target="#navbarDropdown"
aria-controls="navbarDropdown"
@ -181,10 +183,10 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
<NavLink
to="/communities"
className="nav-link"
title={i18n.t("communities")}
title={I18NextService.i18n.t("communities")}
onMouseUp={linkEvent(this, handleCollapseClick)}
>
{i18n.t("communities")}
{I18NextService.i18n.t("communities")}
</NavLink>
</li>
<li className="nav-item">
@ -198,10 +200,10 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
state: { prevPath: this.currentLocation },
}}
className="nav-link"
title={i18n.t("create_post")}
title={I18NextService.i18n.t("create_post")}
onMouseUp={linkEvent(this, handleCollapseClick)}
>
{i18n.t("create_post")}
{I18NextService.i18n.t("create_post")}
</NavLink>
</li>
{this.props.siteRes && canCreateCommunity(this.props.siteRes) && (
@ -209,22 +211,22 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
<NavLink
to="/create_community"
className="nav-link"
title={i18n.t("create_community")}
title={I18NextService.i18n.t("create_community")}
onMouseUp={linkEvent(this, handleCollapseClick)}
>
{i18n.t("create_community")}
{I18NextService.i18n.t("create_community")}
</NavLink>
</li>
)}
<li className="nav-item">
<a
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}
>
<Icon icon="heart" classes="small" />
<span className="d-inline ms-1 d-md-none ms-md-0">
{i18n.t("support_lemmy")}
{I18NextService.i18n.t("support_lemmy")}
</span>
</a>
</li>
@ -234,12 +236,12 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
<NavLink
to="/search"
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)}
>
<Icon icon="search" />
<span className="d-inline ms-1 d-md-none ms-md-0">
{i18n.t("search")}
{I18NextService.i18n.t("search")}
</span>
</NavLink>
</li>
@ -248,12 +250,12 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
<NavLink
to="/admin"
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)}
>
<Icon icon="settings" />
<span className="d-inline ms-1 d-md-none ms-md-0">
{i18n.t("admin_settings")}
{I18NextService.i18n.t("admin_settings")}
</span>
</NavLink>
</li>
@ -264,7 +266,7 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
<NavLink
className="nav-link d-inline-flex align-items-center d-md-inline-block"
to="/inbox"
title={i18n.t("unread_messages", {
title={I18NextService.i18n.t("unread_messages", {
count: Number(this.unreadInboxCount),
formattedCount: numToSI(this.unreadInboxCount),
})}
@ -272,7 +274,7 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
>
<Icon icon="bell" />
<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),
formattedCount: numToSI(this.unreadInboxCount),
})}
@ -289,7 +291,7 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
<NavLink
className="nav-link d-inline-flex align-items-center d-md-inline-block"
to="/reports"
title={i18n.t("unread_reports", {
title={I18NextService.i18n.t("unread_reports", {
count: Number(this.unreadReportCount),
formattedCount: numToSI(this.unreadReportCount),
})}
@ -297,7 +299,7 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
>
<Icon icon="shield" />
<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),
formattedCount: numToSI(this.unreadReportCount),
})}
@ -315,18 +317,26 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
<NavLink
to="/registration_applications"
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),
formattedCount: numToSI(this.unreadApplicationCount),
})}
}
)}
onMouseUp={linkEvent(this, handleCollapseClick)}
>
<Icon icon="clipboard" />
<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),
formattedCount: numToSI(this.unreadApplicationCount),
})}
formattedCount: numToSI(
this.unreadApplicationCount
),
}
)}
</span>
{this.unreadApplicationCount > 0 && (
<span className="mx-1 badge text-bg-light">
@ -357,22 +367,22 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
<NavLink
to={`/u/${person.name}`}
className="dropdown-item px-2"
title={i18n.t("profile")}
title={I18NextService.i18n.t("profile")}
onMouseUp={linkEvent(this, handleCollapseClick)}
>
<Icon icon="user" classes="me-1" />
{i18n.t("profile")}
{I18NextService.i18n.t("profile")}
</NavLink>
</li>
<li>
<NavLink
to="/settings"
className="dropdown-item px-2"
title={i18n.t("settings")}
title={I18NextService.i18n.t("settings")}
onMouseUp={linkEvent(this, handleCollapseClick)}
>
<Icon icon="settings" classes="me-1" />
{i18n.t("settings")}
{I18NextService.i18n.t("settings")}
</NavLink>
</li>
<li>
@ -384,7 +394,7 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
onClick={linkEvent(this, handleLogOut)}
>
<Icon icon="log-out" classes="me-1" />
{i18n.t("logout")}
{I18NextService.i18n.t("logout")}
</button>
</li>
</ul>
@ -397,20 +407,20 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
<NavLink
to="/login"
className="nav-link"
title={i18n.t("login")}
title={I18NextService.i18n.t("login")}
onMouseUp={linkEvent(this, handleCollapseClick)}
>
{i18n.t("login")}
{I18NextService.i18n.t("login")}
</NavLink>
</li>
<li className="nav-item">
<NavLink
to="/signup"
className="nav-link"
title={i18n.t("sign_up")}
title={I18NextService.i18n.t("sign_up")}
onMouseUp={linkEvent(this, handleCollapseClick)}
>
{i18n.t("sign_up")}
{I18NextService.i18n.t("sign_up")}
</NavLink>
</li>
</>
@ -504,7 +514,7 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
if (UserService.Instance.myUserInfo) {
document.addEventListener("DOMContentLoaded", function () {
if (!Notification) {
toast(i18n.t("notifications_error"), "danger");
toast(I18NextService.i18n.t("notifications_error"), "danger");
return;
}

View file

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

View file

@ -48,7 +48,6 @@ import {
} from "lemmy-js-client";
import moment from "moment";
import { commentTreeMaxDepth } from "../../config";
import { i18n } from "../../i18next";
import {
BanType,
CommentNodeI,
@ -57,7 +56,7 @@ import {
VoteType,
} from "../../interfaces";
import { mdToHtml, mdToHtmlNoImages } from "../../markdown";
import { UserService } from "../../services";
import { I18NextService, UserService } from "../../services";
import { setupTippy } from "../../tippy";
import { Icon, PurgeWarning, Spinner } from "../common/icon";
import { MomentTime } from "../common/moment-time";
@ -241,8 +240,8 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
const purgeTypeText =
this.state.purgeType == PurgeType.Comment
? i18n.t("purge_comment")
: `${i18n.t("purge")} ${cv.creator.name}`;
? I18NextService.i18n.t("purge_comment")
: `${I18NextService.i18n.t("purge")} ${cv.creator.name}`;
const canMod_ = canMod(
cv.creator.id,
@ -314,27 +313,27 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
)}
{this.isPostCreator && (
<div className="badge text-bg-light d-none d-sm-inline me-2">
{i18n.t("creator")}
{I18NextService.i18n.t("creator")}
</div>
)}
{isMod_ && (
<div className="badge text-bg-light d-none d-sm-inline me-2">
{i18n.t("mod")}
{I18NextService.i18n.t("mod")}
</div>
)}
{isAdmin_ && (
<div className="badge text-bg-light d-none d-sm-inline me-2">
{i18n.t("admin")}
{I18NextService.i18n.t("admin")}
</div>
)}
{cv.creator.bot_account && (
<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>
)}
{this.props.showCommunity && (
<>
<span className="mx-1">{i18n.t("to")}</span>
<span className="mx-1">{I18NextService.i18n.t("to")}</span>
<CommunityLink community={cv.community} />
<span className="mx-2"></span>
<Link className="me-2" to={`/post/${cv.post.id}`}>
@ -366,7 +365,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
) : (
<span
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),
formattedCount: numToSI(
this.commentView.counts.score
@ -426,13 +425,13 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
onClick={linkEvent(this, this.handleMarkAsRead)}
data-tippy-content={
this.commentReplyOrMentionRead
? i18n.t("mark_as_unread")
: i18n.t("mark_as_read")
? I18NextService.i18n.t("mark_as_unread")
: I18NextService.i18n.t("mark_as_read")
}
aria-label={
this.commentReplyOrMentionRead
? i18n.t("mark_as_unread")
: i18n.t("mark_as_read")
? I18NextService.i18n.t("mark_as_unread")
: I18NextService.i18n.t("mark_as_read")
}
>
{this.state.readLoading ? (
@ -456,8 +455,8 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
: "text-muted"
}`}
onClick={linkEvent(this, this.handleUpvote)}
data-tippy-content={i18n.t("upvote")}
aria-label={i18n.t("upvote")}
data-tippy-content={I18NextService.i18n.t("upvote")}
aria-label={I18NextService.i18n.t("upvote")}
aria-pressed={this.commentView.my_vote === 1}
>
{this.state.upvoteLoading ? (
@ -483,8 +482,8 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
: "text-muted"
}`}
onClick={linkEvent(this, this.handleDownvote)}
data-tippy-content={i18n.t("downvote")}
aria-label={i18n.t("downvote")}
data-tippy-content={I18NextService.i18n.t("downvote")}
aria-label={I18NextService.i18n.t("downvote")}
aria-pressed={this.commentView.my_vote === -1}
>
{this.state.downvoteLoading ? (
@ -506,8 +505,8 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
<button
className="btn btn-link btn-animate text-muted"
onClick={linkEvent(this, this.handleReplyClick)}
data-tippy-content={i18n.t("reply")}
aria-label={i18n.t("reply")}
data-tippy-content={I18NextService.i18n.t("reply")}
aria-label={I18NextService.i18n.t("reply")}
>
<Icon icon="reply1" classes="icon-inline" />
</button>
@ -515,8 +514,8 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
<button
className="btn btn-link btn-animate text-muted btn-more"
onClick={linkEvent(this, this.handleShowAdvanced)}
data-tippy-content={i18n.t("more")}
aria-label={i18n.t("more")}
data-tippy-content={I18NextService.i18n.t("more")}
aria-label={I18NextService.i18n.t("more")}
>
<Icon icon="more-vertical" classes="icon-inline" />
</button>
@ -527,7 +526,9 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
<Link
className="btn btn-link btn-animate text-muted"
to={`/create_private_message/${cv.creator.id}`}
title={i18n.t("message").toLowerCase()}
title={I18NextService.i18n
.t("message")
.toLowerCase()}
>
<Icon icon="mail" />
</Link>
@ -537,10 +538,12 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
this,
this.handleShowReportDialog
)}
data-tippy-content={i18n.t(
data-tippy-content={I18NextService.i18n.t(
"show_report_dialog"
)}
aria-label={I18NextService.i18n.t(
"show_report_dialog"
)}
aria-label={i18n.t("show_report_dialog")}
>
<Icon icon="flag" />
</button>
@ -550,8 +553,10 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
this,
this.handleBlockPerson
)}
data-tippy-content={i18n.t("block_user")}
aria-label={i18n.t("block_user")}
data-tippy-content={I18NextService.i18n.t(
"block_user"
)}
aria-label={I18NextService.i18n.t("block_user")}
>
{this.state.blockPersonLoading ? (
<Spinner />
@ -565,10 +570,14 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
className="btn btn-link btn-animate text-muted"
onClick={linkEvent(this, this.handleSaveComment)}
data-tippy-content={
cv.saved ? i18n.t("unsave") : i18n.t("save")
cv.saved
? I18NextService.i18n.t("unsave")
: I18NextService.i18n.t("save")
}
aria-label={
cv.saved ? i18n.t("unsave") : i18n.t("save")
cv.saved
? I18NextService.i18n.t("unsave")
: I18NextService.i18n.t("save")
}
>
{this.state.saveLoading ? (
@ -585,8 +594,10 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
<button
className="btn btn-link btn-animate text-muted"
onClick={linkEvent(this, this.handleViewSource)}
data-tippy-content={i18n.t("view_source")}
aria-label={i18n.t("view_source")}
data-tippy-content={I18NextService.i18n.t(
"view_source"
)}
aria-label={I18NextService.i18n.t("view_source")}
>
<Icon
icon="file-text"
@ -600,8 +611,10 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
<button
className="btn btn-link btn-animate text-muted"
onClick={linkEvent(this, this.handleEditClick)}
data-tippy-content={i18n.t("edit")}
aria-label={i18n.t("edit")}
data-tippy-content={I18NextService.i18n.t(
"edit"
)}
aria-label={I18NextService.i18n.t("edit")}
>
<Icon icon="edit" classes="icon-inline" />
</button>
@ -613,13 +626,13 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
)}
data-tippy-content={
!cv.comment.deleted
? i18n.t("delete")
: i18n.t("restore")
? I18NextService.i18n.t("delete")
: I18NextService.i18n.t("restore")
}
aria-label={
!cv.comment.deleted
? i18n.t("delete")
: i18n.t("restore")
? I18NextService.i18n.t("delete")
: I18NextService.i18n.t("restore")
}
>
{this.state.deleteLoading ? (
@ -643,13 +656,13 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
)}
data-tippy-content={
!cv.comment.distinguished
? i18n.t("distinguish")
: i18n.t("undistinguish")
? I18NextService.i18n.t("distinguish")
: I18NextService.i18n.t("undistinguish")
}
aria-label={
!cv.comment.distinguished
? i18n.t("distinguish")
: i18n.t("undistinguish")
? I18NextService.i18n.t("distinguish")
: I18NextService.i18n.t("undistinguish")
}
>
<Icon
@ -672,9 +685,9 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
this,
this.handleModRemoveShow
)}
aria-label={i18n.t("remove")}
aria-label={I18NextService.i18n.t("remove")}
>
{i18n.t("remove")}
{I18NextService.i18n.t("remove")}
</button>
) : (
<button
@ -683,12 +696,12 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
this,
this.handleRemoveComment
)}
aria-label={i18n.t("restore")}
aria-label={I18NextService.i18n.t("restore")}
>
{this.state.removeLoading ? (
<Spinner />
) : (
i18n.t("restore")
I18NextService.i18n.t("restore")
)}
</button>
)}
@ -705,9 +718,13 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
this,
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
@ -716,12 +733,12 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
this,
this.handleBanPersonFromCommunity
)}
aria-label={i18n.t("unban")}
aria-label={I18NextService.i18n.t("unban")}
>
{this.state.banLoading ? (
<Spinner />
) : (
i18n.t("unban")
I18NextService.i18n.t("unban")
)}
</button>
))}
@ -735,21 +752,25 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
)}
aria-label={
isMod_
? i18n.t("remove_as_mod")
: i18n.t("appoint_as_mod")
? I18NextService.i18n.t("remove_as_mod")
: I18NextService.i18n.t(
"appoint_as_mod"
)
}
>
{isMod_
? i18n.t("remove_as_mod")
: i18n.t("appoint_as_mod")}
? I18NextService.i18n.t("remove_as_mod")
: I18NextService.i18n.t("appoint_as_mod")}
</button>
) : (
<>
<button
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
className="btn btn-link btn-animate text-muted"
@ -757,12 +778,12 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
this,
this.handleAddModToCommunity
)}
aria-label={i18n.t("yes")}
aria-label={I18NextService.i18n.t("yes")}
>
{this.state.addModLoading ? (
<Spinner />
) : (
i18n.t("yes")
I18NextService.i18n.t("yes")
)}
</button>
<button
@ -771,9 +792,9 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
this,
this.handleCancelConfirmAppointAsMod
)}
aria-label={i18n.t("no")}
aria-label={I18NextService.i18n.t("no")}
>
{i18n.t("no")}
{I18NextService.i18n.t("no")}
</button>
</>
))}
@ -790,17 +811,21 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
this,
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
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
className="btn btn-link btn-animate text-muted"
@ -808,12 +833,12 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
this,
this.handleTransferCommunity
)}
aria-label={i18n.t("yes")}
aria-label={I18NextService.i18n.t("yes")}
>
{this.state.transferCommunityLoading ? (
<Spinner />
) : (
i18n.t("yes")
I18NextService.i18n.t("yes")
)}
</button>
<button
@ -823,9 +848,9 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
this
.handleCancelShowConfirmTransferCommunity
)}
aria-label={i18n.t("no")}
aria-label={I18NextService.i18n.t("no")}
>
{i18n.t("no")}
{I18NextService.i18n.t("no")}
</button>
</>
))}
@ -840,9 +865,11 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
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
className="btn btn-link btn-animate text-muted"
@ -850,9 +877,11 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
this,
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>
{!isBanned(cv.creator) ? (
@ -862,9 +891,11 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
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
@ -873,12 +904,14 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
this,
this.handleBanPerson
)}
aria-label={i18n.t("unban_from_site")}
aria-label={I18NextService.i18n.t(
"unban_from_site"
)}
>
{this.state.banLoading ? (
<Spinner />
) : (
i18n.t("unban_from_site")
I18NextService.i18n.t("unban_from_site")
)}
</button>
)}
@ -895,18 +928,24 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
)}
aria-label={
isAdmin_
? i18n.t("remove_as_admin")
: i18n.t("appoint_as_admin")
? I18NextService.i18n.t(
"remove_as_admin"
)
: I18NextService.i18n.t(
"appoint_as_admin"
)
}
>
{isAdmin_
? i18n.t("remove_as_admin")
: i18n.t("appoint_as_admin")}
? I18NextService.i18n.t("remove_as_admin")
: I18NextService.i18n.t(
"appoint_as_admin"
)}
</button>
) : (
<>
<button className="btn btn-link btn-animate text-muted">
{i18n.t("are_you_sure")}
{I18NextService.i18n.t("are_you_sure")}
</button>
<button
className="btn btn-link btn-animate text-muted"
@ -914,12 +953,12 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
this,
this.handleAddAdmin
)}
aria-label={i18n.t("yes")}
aria-label={I18NextService.i18n.t("yes")}
>
{this.state.addAdminLoading ? (
<Spinner />
) : (
i18n.t("yes")
I18NextService.i18n.t("yes")
)}
</button>
<button
@ -928,9 +967,9 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
this,
this.handleCancelConfirmAppointAsAdmin
)}
aria-label={i18n.t("no")}
aria-label={I18NextService.i18n.t("no")}
>
{i18n.t("no")}
{I18NextService.i18n.t("no")}
</button>
</>
))}
@ -961,7 +1000,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
<Spinner />
) : (
<>
{i18n.t("x_more_replies", {
{I18NextService.i18n.t("x_more_replies", {
count: node.comment_view.counts.child_count,
formattedCount: numToSI(
node.comment_view.counts.child_count
@ -983,22 +1022,22 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
className="visually-hidden"
htmlFor={`mod-remove-reason-${cv.comment.id}`}
>
{i18n.t("reason")}
{I18NextService.i18n.t("reason")}
</label>
<input
type="text"
id={`mod-remove-reason-${cv.comment.id}`}
className="form-control me-2"
placeholder={i18n.t("reason")}
placeholder={I18NextService.i18n.t("reason")}
value={this.state.removeReason}
onInput={linkEvent(this, this.handleModRemoveReasonChange)}
/>
<button
type="submit"
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>
</form>
)}
@ -1011,23 +1050,23 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
className="visually-hidden"
htmlFor={`report-reason-${cv.comment.id}`}
>
{i18n.t("reason")}
{I18NextService.i18n.t("reason")}
</label>
<input
type="text"
required
id={`report-reason-${cv.comment.id}`}
className="form-control me-2"
placeholder={i18n.t("reason")}
placeholder={I18NextService.i18n.t("reason")}
value={this.state.reportReason}
onInput={linkEvent(this, this.handleReportReasonChange)}
/>
<button
type="submit"
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>
</form>
)}
@ -1038,13 +1077,13 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
className="col-form-label"
htmlFor={`mod-ban-reason-${cv.comment.id}`}
>
{i18n.t("reason")}
{I18NextService.i18n.t("reason")}
</label>
<input
type="text"
id={`mod-ban-reason-${cv.comment.id}`}
className="form-control me-2"
placeholder={i18n.t("reason")}
placeholder={I18NextService.i18n.t("reason")}
value={this.state.banReason}
onInput={linkEvent(this, this.handleModBanReasonChange)}
/>
@ -1052,13 +1091,13 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
className="col-form-label"
htmlFor={`mod-ban-expires-${cv.comment.id}`}
>
{i18n.t("expires")}
{I18NextService.i18n.t("expires")}
</label>
<input
type="number"
id={`mod-ban-expires-${cv.comment.id}`}
className="form-control me-2"
placeholder={i18n.t("number_of_days")}
placeholder={I18NextService.i18n.t("number_of_days")}
value={this.state.banExpireDays}
onInput={linkEvent(this, this.handleModBanExpireDaysChange)}
/>
@ -1074,9 +1113,9 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
<label
className="form-check-label"
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>
</div>
</div>
@ -1084,19 +1123,19 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
{/* TODO hold off on expires until later */}
{/* <div class="mb-3 row"> */}
{/* <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 className="mb-3 row">
<button
type="submit"
className="btn btn-secondary"
aria-label={i18n.t("ban")}
aria-label={I18NextService.i18n.t("ban")}
>
{this.state.banLoading ? (
<Spinner />
) : (
<span>
{i18n.t("ban")} {cv.creator.name}
{I18NextService.i18n.t("ban")} {cv.creator.name}
</span>
)}
</button>
@ -1108,13 +1147,13 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
<form onSubmit={linkEvent(this, this.handlePurgeBothSubmit)}>
<PurgeWarning />
<label className="visually-hidden" htmlFor="purge-reason">
{i18n.t("reason")}
{I18NextService.i18n.t("reason")}
</label>
<input
type="text"
id="purge-reason"
className="form-control my-3"
placeholder={i18n.t("reason")}
placeholder={I18NextService.i18n.t("reason")}
value={this.state.purgeReason}
onInput={linkEvent(this, this.handlePurgeReasonChange)}
/>
@ -1209,8 +1248,8 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
});
const title = this.props.showContext
? i18n.t("show_context")
: i18n.t("link");
? I18NextService.i18n.t("show_context")
: I18NextService.i18n.t("link");
// The context button should show the parent comment by default
const parentCommentId = getCommentParentId(cv.comment) ?? cv.comment.id;
@ -1255,17 +1294,17 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
}
get pointsTippy(): string {
const points = i18n.t("number_of_points", {
const points = I18NextService.i18n.t("number_of_points", {
count: Number(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),
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),
formattedCount: numToSI(this.commentView.counts.downvotes),
});
@ -1274,15 +1313,17 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
}
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 {
const comment = this.commentView.comment;
return comment.removed
? `*${i18n.t("removed")}*`
? `*${I18NextService.i18n.t("removed")}*`
: comment.deleted
? `*${i18n.t("deleted")}*`
? `*${I18NextService.i18n.t("deleted")}*`
: comment.content;
}

View file

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

View file

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

View file

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

View file

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

View file

@ -1,5 +1,5 @@
import { Component, linkEvent } from "inferno";
import { i18n } from "../../i18next";
import { I18NextService } from "../../services";
import { EmojiMart } from "./emoji-mart";
import { Icon } from "./icon";
@ -28,8 +28,8 @@ export class EmojiPicker extends Component<EmojiPickerProps, EmojiPickerState> {
<span className="emoji-picker">
<button
className="btn btn-sm text-muted"
data-tippy-content={i18n.t("emoji")}
aria-label={i18n.t("emoji")}
data-tippy-content={I18NextService.i18n.t("emoji")}
aria-label={I18NextService.i18n.t("emoji")}
disabled={this.props.disabled}
onClick={linkEvent(this, this.togglePicker)}
>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -12,9 +12,8 @@ import {
maxUploadImages,
relTags,
} from "../../config";
import { i18n } from "../../i18next";
import { customEmojisLookup, mdToHtml, setupTribute } from "../../markdown";
import { HttpService, UserService } from "../../services";
import { HttpService, I18NextService, UserService } from "../../services";
import { setupTippy } from "../../tippy";
import { pictrsDeleteToast, toast } from "../../toast";
import { EmojiPicker } from "./emoji-picker";
@ -129,7 +128,7 @@ export class MarkdownTextArea extends Component<
// TODO add these prompts back in at some point
// <Prompt
// when={!this.props.hideNavigationWarnings && this.state.content}
// message={i18n.t("block_leaving")}
// message={I18NextService.i18n.t("block_leaving")}
// />
return (
<form
@ -161,7 +160,7 @@ export class MarkdownTextArea extends Component<
className={`mb-0 ${
UserService.Instance.myUserInfo && "pointer"
}`}
data-tippy-content={i18n.t("upload_image")}
data-tippy-content={I18NextService.i18n.t("upload_image")}
>
{this.state.imageUploadStatus ? (
<Spinner />
@ -199,7 +198,7 @@ export class MarkdownTextArea extends Component<
<a
href={markdownHelpUrl}
className="btn btn-sm text-muted font-weight-bold"
title={i18n.t("formatting_help")}
title={I18NextService.i18n.t("formatting_help")}
rel={relTags}
>
<Icon icon="help-circle" classes="icon-inline" />
@ -241,15 +240,17 @@ export class MarkdownTextArea extends Component<
animated
value={this.state.imageUploadStatus.uploaded}
max={this.state.imageUploadStatus.total}
text={i18n.t("pictures_uploded_progess", {
text={
I18NextService.i18n.t("pictures_uploded_progess", {
uploaded: this.state.imageUploadStatus.uploaded,
total: this.state.imageUploadStatus.total,
})}
}) ?? undefined
}
/>
)}
</div>
<label className="visually-hidden" htmlFor={this.id}>
{i18n.t("body")}
{I18NextService.i18n.t("body")}
</label>
</div>
</div>
@ -290,7 +291,7 @@ export class MarkdownTextArea extends Component<
className="btn btn-sm btn-secondary ms-2"
onClick={linkEvent(this, this.handleReplyCancel)}
>
{i18n.t("cancel")}
{I18NextService.i18n.t("cancel")}
</button>
)}
{this.state.content && (
@ -300,7 +301,9 @@ export class MarkdownTextArea extends Component<
}`}
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>
)}
</div>
@ -332,8 +335,8 @@ export class MarkdownTextArea extends Component<
return (
<button
className="btn btn-sm text-muted"
data-tippy-content={i18n.t(type)}
aria-label={i18n.t(type)}
data-tippy-content={I18NextService.i18n.t(type)}
aria-label={I18NextService.i18n.t(type)}
onClick={linkEvent(this, handleClick)}
disabled={this.isDisabled}
>
@ -376,7 +379,7 @@ export class MarkdownTextArea extends Component<
if (files.length > maxUploadImages) {
toast(
i18n.t("too_many_images_upload", {
I18NextService.i18n.t("too_many_images_upload", {
count: Number(maxUploadImages),
formattedCount: numToSI(maxUploadImages),
}),
@ -677,7 +680,7 @@ export class MarkdownTextArea extends Component<
handleInsertSpoiler(i: MarkdownTextArea, event: any) {
event.preventDefault();
const beforeChars = `\n::: spoiler ${i18n.t("spoiler")}\n`;
const beforeChars = `\n::: spoiler ${I18NextService.i18n.t("spoiler")}\n`;
const afterChars = "\n:::\n";
i.simpleSurroundBeforeAfter(beforeChars, afterChars);
}

View file

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

View file

@ -1,5 +1,5 @@
import { Component } from "inferno";
import { i18n } from "../../../shared/i18next";
import { I18NextService } from "../../services";
export interface IPromptProps {
when: boolean;
@ -14,7 +14,7 @@ export default class NavigationPrompt extends Component<IPromptProps, any> {
}
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();
tx.retry();
}

View file

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

View file

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

View file

@ -5,8 +5,8 @@ import {
ApproveRegistrationApplication,
RegistrationApplicationView,
} from "lemmy-js-client";
import { i18n } from "../../i18next";
import { mdToHtml } from "../../markdown";
import { I18NextService } from "../../services";
import { PersonListing } from "../person/person-listing";
import { Spinner } from "./icon";
import { MarkdownTextArea } from "./markdown-textarea";
@ -61,12 +61,14 @@ export class RegistrationApplication extends Component<
return (
<div className="registration-application">
<div>
{i18n.t("applicant")}: <PersonListing person={a.creator} />
{I18NextService.i18n.t("applicant")}:{" "}
<PersonListing person={a.creator} />
</div>
<div>
{i18n.t("created")}: <MomentTime showAgo published={ra.published} />
{I18NextService.i18n.t("created")}:{" "}
<MomentTime showAgo published={ra.published} />
</div>
<div>{i18n.t("answer")}:</div>
<div>{I18NextService.i18n.t("answer")}:</div>
<div className="md-div" dangerouslySetInnerHTML={mdToHtml(ra.answer)} />
{a.admin && (
@ -84,7 +86,7 @@ export class RegistrationApplication extends Component<
</T>
{ra.deny_reason && (
<div>
{i18n.t("deny_reason")}:{" "}
{I18NextService.i18n.t("deny_reason")}:{" "}
<div
className="md-div d-inline-flex"
dangerouslySetInnerHTML={mdToHtml(ra.deny_reason)}
@ -99,7 +101,7 @@ export class RegistrationApplication extends Component<
{this.state.denyExpanded && (
<div className="mb-3 row">
<label className="col-sm-2 col-form-label">
{i18n.t("deny_reason")}
{I18NextService.i18n.t("deny_reason")}
</label>
<div className="col-sm-10">
<MarkdownTextArea
@ -116,18 +118,26 @@ export class RegistrationApplication extends Component<
<button
className="btn btn-secondary me-2 my-2"
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>
)}
{(!ra.admin_id || (ra.admin_id && accepted)) && (
<button
className="btn btn-secondary me-2"
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>
)}
</div>

View file

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

View file

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

View file

@ -21,9 +21,8 @@ import {
ListCommunitiesResponse,
ListingType,
} from "lemmy-js-client";
import { i18n } from "../../i18next";
import { InitialFetchRequest } from "../../interfaces";
import { FirstLoadService } from "../../services/FirstLoadService";
import { FirstLoadService, I18NextService } from "../../services";
import { HttpService, RequestState } from "../../services/HttpService";
import { HtmlTags } from "../common/html-tags";
import { Spinner } from "../common/icon";
@ -86,7 +85,7 @@ export class Communities extends Component<any, CommunitiesState> {
}
get documentTitle(): string {
return `${i18n.t("communities")} - ${
return `${I18NextService.i18n.t("communities")} - ${
this.state.siteRes.site_view.site.name
}`;
}
@ -103,7 +102,9 @@ export class Communities extends Component<any, CommunitiesState> {
const { listingType, page } = this.getCommunitiesQueryParams();
return (
<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="col-auto">
<ListingTypeSelect
@ -123,16 +124,19 @@ export class Communities extends Component<any, CommunitiesState> {
>
<thead className="pointer">
<tr>
<th>{i18n.t("name")}</th>
<th className="text-right">{i18n.t("subscribers")}</th>
<th>{I18NextService.i18n.t("name")}</th>
<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 className="text-right d-none d-lg-table-cell">
{i18n.t("posts")}
{I18NextService.i18n.t("posts")}
</th>
<th className="text-right d-none d-lg-table-cell">
{i18n.t("comments")}
{I18NextService.i18n.t("comments")}
</th>
<th></th>
</tr>
@ -169,7 +173,7 @@ export class Communities extends Component<any, CommunitiesState> {
this.handleFollow
)}
>
{i18n.t("unsubscribe")}
{I18NextService.i18n.t("unsubscribe")}
</button>
)}
{cv.subscribed === "NotSubscribed" && (
@ -184,12 +188,12 @@ export class Communities extends Component<any, CommunitiesState> {
this.handleFollow
)}
>
{i18n.t("subscribe")}
{I18NextService.i18n.t("subscribe")}
</button>
)}
{cv.subscribed === "Pending" && (
<div className="text-warning d-inline-block">
{i18n.t("subscribe_pending")}
{I18NextService.i18n.t("subscribe_pending")}
</div>
)}
</td>
@ -230,7 +234,7 @@ export class Communities extends Component<any, CommunitiesState> {
id="communities-search"
className="form-control"
value={this.state.searchText}
placeholder={`${i18n.t("search")}...`}
placeholder={`${I18NextService.i18n.t("search")}...`}
onInput={linkEvent(this, this.handleSearchChange)}
required
minLength={3}
@ -238,10 +242,10 @@ export class Communities extends Component<any, CommunitiesState> {
</div>
<div className="col-auto">
<label className="visually-hidden" htmlFor="communities-search">
{i18n.t("search")}
{I18NextService.i18n.t("search")}
</label>
<button type="submit" className="btn btn-secondary">
<span>{i18n.t("search")}</span>
<span>{I18NextService.i18n.t("search")}</span>
</button>
</div>
</form>

View file

@ -7,7 +7,7 @@ import {
EditCommunity,
Language,
} from "lemmy-js-client";
import { i18n } from "../../i18next";
import { I18NextService } from "../../services";
import { Icon, Spinner } from "../common/icon";
import { ImageUploadForm } from "../common/image-upload-form";
import { LanguageSelect } from "../common/language-select";
@ -107,10 +107,10 @@ export class CommunityForm extends Component<
className="col-12 col-sm-2 col-form-label"
htmlFor="community-name"
>
{i18n.t("name")}
{I18NextService.i18n.t("name")}
<span
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" />
</span>
@ -125,7 +125,7 @@ export class CommunityForm extends Component<
required
minLength={3}
pattern="[a-z0-9_]+"
title={i18n.t("community_reqs")}
title={I18NextService.i18n.t("community_reqs")}
/>
</div>
</div>
@ -135,10 +135,10 @@ export class CommunityForm extends Component<
className="col-12 col-sm-2 col-form-label"
htmlFor="community-title"
>
{i18n.t("display_name")}
{I18NextService.i18n.t("display_name")}
<span
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" />
</span>
@ -158,11 +158,11 @@ export class CommunityForm extends Component<
</div>
<div className="mb-3 row">
<label className="col-12 col-sm-2 col-form-label">
{i18n.t("icon")}
{I18NextService.i18n.t("icon")}
</label>
<div className="col-12 col-sm-10">
<ImageUploadForm
uploadTitle={i18n.t("upload_icon")}
uploadTitle={I18NextService.i18n.t("upload_icon")}
imageSrc={this.state.form.icon}
onUpload={this.handleIconUpload}
onRemove={this.handleIconRemove}
@ -172,11 +172,11 @@ export class CommunityForm extends Component<
</div>
<div className="mb-3 row">
<label className="col-12 col-sm-2 col-form-label">
{i18n.t("banner")}
{I18NextService.i18n.t("banner")}
</label>
<div className="col-12 col-sm-10">
<ImageUploadForm
uploadTitle={i18n.t("upload_banner")}
uploadTitle={I18NextService.i18n.t("upload_banner")}
imageSrc={this.state.form.banner}
onUpload={this.handleBannerUpload}
onRemove={this.handleBannerRemove}
@ -185,12 +185,12 @@ export class CommunityForm extends Component<
</div>
<div className="mb-3 row">
<label className="col-12 col-sm-2 col-form-label" htmlFor={this.id}>
{i18n.t("sidebar")}
{I18NextService.i18n.t("sidebar")}
</label>
<div className="col-12 col-sm-10">
<MarkdownTextArea
initialContent={this.state.form.description}
placeholder={i18n.t("description")}
placeholder={I18NextService.i18n.t("description") ?? undefined}
onContentChange={this.handleCommunityDescriptionChange}
hideNavigationWarnings
allLanguages={[]}
@ -202,7 +202,7 @@ export class CommunityForm extends Component<
{this.props.enableNsfw && (
<div className="mb-3 row">
<legend className="col-form-label col-sm-2 pt-0">
{i18n.t("nsfw")}
{I18NextService.i18n.t("nsfw")}
</legend>
<div className="col-10">
<div className="form-check">
@ -219,7 +219,7 @@ export class CommunityForm extends Component<
)}
<div className="mb-3 row">
<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>
<div className="col-6">
<div className="form-check">
@ -254,9 +254,9 @@ export class CommunityForm extends Component<
{this.props.loading ? (
<Spinner />
) : this.props.community_view ? (
capitalizeFirstLetter(i18n.t("save"))
capitalizeFirstLetter(I18NextService.i18n.t("save"))
) : (
capitalizeFirstLetter(i18n.t("create"))
capitalizeFirstLetter(I18NextService.i18n.t("create"))
)}
</button>
{this.props.community_view && (
@ -265,7 +265,7 @@ export class CommunityForm extends Component<
className="btn btn-secondary"
onClick={linkEvent(this, this.handleCancel)}
>
{i18n.t("cancel")}
{I18NextService.i18n.t("cancel")}
</button>
)}
</div>

View file

@ -15,7 +15,6 @@ import {
updateCommunityBlock,
updatePersonBlock,
} from "@utils/app";
import { restoreScrollPosition, saveScrollPosition } from "@utils/browser";
import {
getPageFromString,
getQueryParams,
@ -78,14 +77,12 @@ import {
TransferCommunity,
} from "lemmy-js-client";
import { fetchLimit, relTags } from "../../config";
import { i18n } from "../../i18next";
import {
CommentViewType,
DataType,
InitialFetchRequest,
} from "../../interfaces";
import { UserService } from "../../services";
import { FirstLoadService } from "../../services/FirstLoadService";
import { FirstLoadService, I18NextService, UserService } from "../../services";
import { HttpService, RequestState } from "../../services/HttpService";
import { setupTippy } from "../../tippy";
import { toast } from "../../toast";
@ -231,10 +228,6 @@ export class Community extends Component<
setupTippy();
}
componentWillUnmount() {
saveScrollPosition(this.context);
}
static async fetchInitialData({
client,
path,
@ -331,7 +324,7 @@ export class Community extends Component<
className="btn btn-secondary d-inline-block mb-2 me-3"
onClick={linkEvent(this, this.handleShowSidebarMobile)}
>
{i18n.t("sidebar")}{" "}
{I18NextService.i18n.t("sidebar")}{" "}
<Icon
icon={
this.state.showSidebarMobile
@ -611,7 +604,6 @@ export class Community extends Component<
});
}
restoreScrollPosition(this.context);
setupTippy();
}
@ -758,14 +750,14 @@ export class Community extends Component<
async handleCommentReport(form: CreateCommentReport) {
const reportRes = await HttpService.client.createCommentReport(form);
if (reportRes.state == "success") {
toast(i18n.t("report_created"));
toast(I18NextService.i18n.t("report_created"));
}
}
async handlePostReport(form: CreatePostReport) {
const reportRes = await HttpService.client.createPostReport(form);
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(
form
);
toast(i18n.t("transfer_community"));
toast(I18NextService.i18n.t("transfer_community"));
this.updateCommunityFull(transferCommunityRes);
}
@ -880,7 +872,7 @@ export class Community extends Component<
purgeItem(purgeRes: RequestState<PurgeItemResponse>) {
if (purgeRes.state == "success") {
toast(i18n.t("purge_success"));
toast(I18NextService.i18n.t("purge_success"));
this.context.router.history.push(`/`);
}
}

View file

@ -4,8 +4,7 @@ import {
CreateCommunity as CreateCommunityI,
GetSiteResponse,
} from "lemmy-js-client";
import { i18n } from "../../i18next";
import { HttpService } from "../../services/HttpService";
import { HttpService, I18NextService } from "../../services";
import { HtmlTags } from "../common/html-tags";
import { CommunityForm } from "./community-form";
@ -26,7 +25,7 @@ export class CreateCommunity extends Component<any, CreateCommunityState> {
}
get documentTitle(): string {
return `${i18n.t("create_community")} - ${
return `${I18NextService.i18n.t("create_community")} - ${
this.state.siteRes.site_view.site.name
}`;
}
@ -40,7 +39,7 @@ export class CreateCommunity extends Component<any, CreateCommunityState> {
/>
<div className="row">
<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
onUpsertCommunity={this.handleCommunityCreate}
enableNsfw={enableNsfw(this.state.siteRes)}

View file

@ -17,9 +17,8 @@ import {
PurgeCommunity,
RemoveCommunity,
} from "lemmy-js-client";
import { i18n } from "../../i18next";
import { mdToHtml } from "../../markdown";
import { UserService } from "../../services";
import { I18NextService, UserService } from "../../services";
import { Badges } from "../common/badges";
import { BannerIconHeader } from "../common/banner-icon-header";
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" />
{i18n.t("joined")}
{I18NextService.i18n.t("joined")}
</>
)}
</button>
@ -200,23 +199,23 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
{this.state.followCommunityLoading ? (
<Spinner />
) : (
i18n.t("subscribe_pending")
I18NextService.i18n.t("subscribe_pending")
)}
</button>
)}
{community.removed && (
<small className="me-2 text-muted font-italic">
{i18n.t("removed")}
{I18NextService.i18n.t("removed")}
</small>
)}
{community.deleted && (
<small className="me-2 text-muted font-italic">
{i18n.t("deleted")}
{I18NextService.i18n.t("deleted")}
</small>
)}
{community.nsfw && (
<small className="me-2 text-muted font-italic">
{i18n.t("nsfw")}
{I18NextService.i18n.t("nsfw")}
</small>
)}
</h5>
@ -234,7 +233,7 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
mods() {
return (
<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 => (
<li key={mod.moderator.id} className="list-inline-item">
<PersonListing person={mod.moderator} />
@ -253,7 +252,7 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
}`}
to={`/create_post?communityId=${cv.community.id}`}
>
{i18n.t("create_a_post")}
{I18NextService.i18n.t("create_a_post")}
</Link>
);
}
@ -270,7 +269,7 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
{this.state.followCommunityLoading ? (
<Spinner />
) : (
i18n.t("subscribe")
I18NextService.i18n.t("subscribe")
)}
</button>
)}
@ -288,7 +287,9 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
className="btn btn-danger d-block mb-2 w-100"
onClick={linkEvent(this, this.handleBlockCommunity)}
>
{i18n.t(blocked ? "unblock_community" : "block_community")}
{I18NextService.i18n.t(
blocked ? "unblock_community" : "block_community"
)}
</button>
)}
</>
@ -315,8 +316,8 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
<button
className="btn btn-link text-muted d-inline-block"
onClick={linkEvent(this, this.handleEditClick)}
data-tippy-content={i18n.t("edit")}
aria-label={i18n.t("edit")}
data-tippy-content={I18NextService.i18n.t("edit")}
aria-label={I18NextService.i18n.t("edit")}
>
<Icon icon="edit" classes="icon-inline" />
</button>
@ -331,20 +332,20 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
this.handleShowConfirmLeaveModTeamClick
)}
>
{i18n.t("leave_mod_team")}
{I18NextService.i18n.t("leave_mod_team")}
</button>
</li>
) : (
<>
<li className="list-inline-item-action">
{i18n.t("are_you_sure")}
{I18NextService.i18n.t("are_you_sure")}
</li>
<li className="list-inline-item-action">
<button
className="btn btn-link text-muted d-inline-block"
onClick={linkEvent(this, this.handleLeaveModTeam)}
>
{i18n.t("yes")}
{I18NextService.i18n.t("yes")}
</button>
</li>
<li className="list-inline-item-action">
@ -355,7 +356,7 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
this.handleCancelLeaveModTeamClick
)}
>
{i18n.t("no")}
{I18NextService.i18n.t("no")}
</button>
</li>
</>
@ -367,13 +368,13 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
onClick={linkEvent(this, this.handleDeleteCommunity)}
data-tippy-content={
!community_view.community.deleted
? i18n.t("delete")
: i18n.t("restore")
? I18NextService.i18n.t("delete")
: I18NextService.i18n.t("restore")
}
aria-label={
!community_view.community.deleted
? i18n.t("delete")
: i18n.t("restore")
? I18NextService.i18n.t("delete")
: I18NextService.i18n.t("restore")
}
>
{this.state.deleteCommunityLoading ? (
@ -398,7 +399,7 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
className="btn btn-link text-muted d-inline-block"
onClick={linkEvent(this, this.handleModRemoveShow)}
>
{i18n.t("remove")}
{I18NextService.i18n.t("remove")}
</button>
) : (
<button
@ -408,16 +409,16 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
{this.state.removeCommunityLoading ? (
<Spinner />
) : (
i18n.t("restore")
I18NextService.i18n.t("restore")
)}
</button>
)}
<button
className="btn btn-link text-muted d-inline-block"
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>
</li>
)}
@ -426,13 +427,13 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
<form onSubmit={linkEvent(this, this.handleRemoveCommunity)}>
<div className="input-group mb-3">
<label className="col-form-label" htmlFor="remove-reason">
{i18n.t("reason")}
{I18NextService.i18n.t("reason")}
</label>
<input
type="text"
id="remove-reason"
className="form-control me-2"
placeholder={i18n.t("optional")}
placeholder={I18NextService.i18n.t("optional")}
value={this.state.removeReason}
onInput={linkEvent(this, this.handleModRemoveReasonChange)}
/>
@ -440,14 +441,14 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
{/* TODO hold off on expires for now */}
{/* <div class="mb-3 row"> */}
{/* <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 className="input-group mb-3">
<button type="submit" className="btn btn-secondary">
{this.state.removeCommunityLoading ? (
<Spinner />
) : (
i18n.t("remove_community")
I18NextService.i18n.t("remove_community")
)}
</button>
</div>
@ -460,13 +461,13 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
</div>
<div className="input-group mb-3">
<label className="visually-hidden" htmlFor="purge-reason">
{i18n.t("reason")}
{I18NextService.i18n.t("reason")}
</label>
<input
type="text"
id="purge-reason"
className="form-control me-2"
placeholder={i18n.t("reason")}
placeholder={I18NextService.i18n.t("reason")}
value={this.state.purgeReason}
onInput={linkEvent(this, this.handlePurgeReasonChange)}
/>
@ -478,9 +479,9 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
<button
type="submit"
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>
)}
</div>

View file

@ -18,10 +18,9 @@ import {
GetSiteResponse,
PersonView,
} from "lemmy-js-client";
import { i18n } from "../../i18next";
import { InitialFetchRequest } from "../../interfaces";
import { removeFromEmojiDataModel, updateEmojiDataModel } from "../../markdown";
import { FirstLoadService } from "../../services/FirstLoadService";
import { FirstLoadService, I18NextService } from "../../services";
import { HttpService, RequestState } from "../../services/HttpService";
import { toast } from "../../toast";
import { HtmlTags } from "../common/html-tags";
@ -108,7 +107,7 @@ export class AdminSettings extends Component<any, AdminSettingsState> {
}
get documentTitle(): string {
return `${i18n.t("admin_settings")} - ${
return `${I18NextService.i18n.t("admin_settings")} - ${
this.state.siteRes.site_view.site.name
}`;
}
@ -129,7 +128,7 @@ export class AdminSettings extends Component<any, AdminSettingsState> {
tabs={[
{
key: "site",
label: i18n.t("site"),
label: I18NextService.i18n.t("site"),
getNode: isSelected => (
<div
className={classNames("tab-pane show", {
@ -181,7 +180,7 @@ export class AdminSettings extends Component<any, AdminSettingsState> {
},
{
key: "taglines",
label: i18n.t("taglines"),
label: I18NextService.i18n.t("taglines"),
getNode: isSelected => (
<div
className={classNames("tab-pane", {
@ -202,7 +201,7 @@ export class AdminSettings extends Component<any, AdminSettingsState> {
},
{
key: "emojis",
label: i18n.t("emojis"),
label: I18NextService.i18n.t("emojis"),
getNode: isSelected => (
<div
className={classNames("tab-pane", {
@ -253,7 +252,7 @@ export class AdminSettings extends Component<any, AdminSettingsState> {
admins() {
return (
<>
<h5>{capitalizeFirstLetter(i18n.t("admins"))}</h5>
<h5>{capitalizeFirstLetter(I18NextService.i18n.t("admins"))}</h5>
<ul className="list-unstyled">
{this.state.siteRes.admins.map(admin => (
<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" ? (
<Spinner />
) : (
i18n.t("leave_admin_team")
I18NextService.i18n.t("leave_admin_team")
)}
</button>
);
@ -293,7 +292,7 @@ export class AdminSettings extends Component<any, AdminSettingsState> {
const bans = this.state.bannedRes.data.banned;
return (
<>
<h5>{i18n.t("banned_users")}</h5>
<h5>{I18NextService.i18n.t("banned_users")}</h5>
<ul className="list-unstyled">
{bans.map(banned => (
<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;
return s;
});
toast(i18n.t("site_saved"));
toast(I18NextService.i18n.t("site_saved"));
}
this.setState({ loading: false });
@ -340,7 +339,7 @@ export class AdminSettings extends Component<any, AdminSettingsState> {
});
if (this.state.leaveAdminTeamRes.state === "success") {
toast(i18n.t("left_admin_team"));
toast(I18NextService.i18n.t("left_admin_team"));
this.context.router.history.replace("/");
}
}

View file

@ -6,9 +6,8 @@ import {
EditCustomEmoji,
GetSiteResponse,
} from "lemmy-js-client";
import { i18n } from "../../i18next";
import { customEmojisLookup } from "../../markdown";
import { HttpService } from "../../services/HttpService";
import { HttpService, I18NextService } from "../../services";
import { pictrsDeleteToast, toast } from "../../toast";
import { EmojiMart } from "../common/emoji-mart";
import { HtmlTags } from "../common/html-tags";
@ -66,7 +65,7 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
this.handleEmojiClick = this.handleEmojiClick.bind(this);
}
get documentTitle(): string {
return i18n.t("custom_emojis");
return I18NextService.i18n.t("custom_emojis");
}
render() {
@ -76,7 +75,7 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
title={this.documentTitle}
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 && (
<div>
<EmojiMart
@ -89,15 +88,21 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
<table id="emojis_table" className="table table-sm table-hover">
<thead className="pointer">
<tr>
<th>{i18n.t("column_emoji")}</th>
<th className="text-right">{i18n.t("column_shortcode")}</th>
<th className="text-right">{i18n.t("column_category")}</th>
<th className="text-right d-lg-table-cell d-none">
{i18n.t("column_imageurl")}
<th>{I18NextService.i18n.t("column_emoji")}</th>
<th className="text-right">
{I18NextService.i18n.t("column_shortcode")}
</th>
<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 className="text-right">{i18n.t("column_alttext")}</th>
<th className="text-right d-lg-table-cell">
{i18n.t("column_keywords")}
{I18NextService.i18n.t("column_keywords")}
</th>
<th style="width:121px"></th>
</tr>
@ -215,8 +220,8 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
{ i: this, cv: cv },
this.handleEditEmojiClick
)}
data-tippy-content={i18n.t("save")}
aria-label={i18n.t("save")}
data-tippy-content={I18NextService.i18n.t("save")}
aria-label={I18NextService.i18n.t("save")}
disabled={
this.props.loading ||
!this.canEdit(cv) ||
@ -236,10 +241,10 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
{ i: this, index: index, cv: cv },
this.handleDeleteEmojiClick
)}
data-tippy-content={i18n.t("delete")}
aria-label={i18n.t("delete")}
data-tippy-content={I18NextService.i18n.t("delete")}
aria-label={I18NextService.i18n.t("delete")}
disabled={this.props.loading}
title={i18n.t("delete")}
title={I18NextService.i18n.t("delete")}
>
<Icon
icon="trash"
@ -257,7 +262,7 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
className="btn btn-sm btn-secondary me-2"
onClick={linkEvent(this, this.handleAddEmojiClick)}
>
{i18n.t("add_custom_emoji")}
{I18NextService.i18n.t("add_custom_emoji")}
</button>
<Paginator page={this.state.page} onChange={this.handlePageChange} />
@ -280,8 +285,8 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
}
getEditTooltip(cv: CustomEmojiViewForm) {
if (this.canEdit(cv)) return i18n.t("save");
else return i18n.t("custom_emoji_save_validation");
if (this.canEdit(cv)) return I18NextService.i18n.t("save");
else return I18NextService.i18n.t("custom_emoji_save_validation");
}
handlePageChange(page: number) {

View file

@ -13,7 +13,6 @@ import {
showLocal,
updatePersonBlock,
} from "@utils/app";
import { restoreScrollPosition, saveScrollPosition } from "@utils/browser";
import {
getPageFromString,
getQueryParams,
@ -73,15 +72,13 @@ import {
TransferCommunity,
} from "lemmy-js-client";
import { fetchLimit, relTags, trendingFetchLimit } from "../../config";
import { i18n } from "../../i18next";
import {
CommentViewType,
DataType,
InitialFetchRequest,
} from "../../interfaces";
import { mdToHtml } from "../../markdown";
import { UserService } from "../../services";
import { FirstLoadService } from "../../services/FirstLoadService";
import { FirstLoadService, I18NextService, UserService } from "../../services";
import { HttpService, RequestState } from "../../services/HttpService";
import { setupTippy } from "../../tippy";
import { toast } from "../../toast";
@ -197,7 +194,7 @@ const MobileButton = ({
className="btn btn-secondary d-inline-block mb-2 me-3"
onClick={onClick}
>
{i18n.t(textKey)}{" "}
{I18NextService.i18n.t(textKey)}{" "}
<Icon icon={show ? `minus-square` : `plus-square`} classes="icon-inline" />
</button>
);
@ -210,7 +207,7 @@ const LinkButton = ({
translationKey: NoOptionI18nKeys;
}) => (
<Link className="btn btn-secondary d-block" to={path}>
{i18n.t(translationKey)}
{I18NextService.i18n.t(translationKey)}
</Link>
);
@ -295,10 +292,6 @@ export class Home extends Component<any, HomeState> {
setupTippy();
}
componentWillUnmount() {
saveScrollPosition(this.context);
}
static async fetchInitialData({
client,
auth,
@ -565,10 +558,14 @@ export class Home extends Component<any, HomeState> {
className="btn btn-sm text-muted"
onClick={linkEvent(this, this.handleCollapseSubscribe)}
aria-label={
subscribedCollapsed ? i18n.t("expand") : i18n.t("collapse")
subscribedCollapsed
? I18NextService.i18n.t("expand")
: I18NextService.i18n.t("collapse")
}
data-tippy-content={
subscribedCollapsed ? i18n.t("expand") : i18n.t("collapse")
subscribedCollapsed
? I18NextService.i18n.t("expand")
: I18NextService.i18n.t("collapse")
}
aria-expanded="true"
aria-controls="sidebarSubscribedBody"
@ -798,7 +795,6 @@ export class Home extends Component<any, HomeState> {
});
}
restoreScrollPosition(this.context);
setupTippy();
}
@ -932,14 +928,14 @@ export class Home extends Component<any, HomeState> {
async handleCommentReport(form: CreateCommentReport) {
const reportRes = await HttpService.client.createCommentReport(form);
if (reportRes.state == "success") {
toast(i18n.t("report_created"));
toast(I18NextService.i18n.t("report_created"));
}
}
async handlePostReport(form: CreatePostReport) {
const reportRes = await HttpService.client.createPostReport(form);
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) {
await HttpService.client.transferCommunity(form);
toast(i18n.t("transfer_community"));
toast(I18NextService.i18n.t("transfer_community"));
}
async handleCommentReplyRead(form: MarkCommentReplyAsRead) {
@ -1030,7 +1026,7 @@ export class Home extends Component<any, HomeState> {
purgeItem(purgeRes: RequestState<PurgeItemResponse>) {
if (purgeRes.state == "success") {
toast(i18n.t("purge_success"));
toast(I18NextService.i18n.t("purge_success"));
this.context.router.history.push(`/`);
}
}

View file

@ -7,9 +7,8 @@ import {
Instance,
} from "lemmy-js-client";
import { relTags } from "../../config";
import { i18n } from "../../i18next";
import { InitialFetchRequest } from "../../interfaces";
import { FirstLoadService } from "../../services/FirstLoadService";
import { FirstLoadService, I18NextService } from "../../services";
import { HttpService, RequestState } from "../../services/HttpService";
import { HtmlTags } from "../common/html-tags";
import { Spinner } from "../common/icon";
@ -70,7 +69,9 @@ export class Instances extends Component<any, InstancesState> {
}
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() {
@ -86,18 +87,18 @@ export class Instances extends Component<any, InstancesState> {
return instances ? (
<div className="row">
<div className="col-md-6">
<h5>{i18n.t("linked_instances")}</h5>
<h5>{I18NextService.i18n.t("linked_instances")}</h5>
{this.itemList(instances.linked)}
</div>
{instances.allowed && instances.allowed.length > 0 && (
<div className="col-md-6">
<h5>{i18n.t("allowed_instances")}</h5>
<h5>{I18NextService.i18n.t("allowed_instances")}</h5>
{this.itemList(instances.allowed)}
</div>
)}
{instances.blocked && instances.blocked.length > 0 && (
<div className="col-md-6">
<h5>{i18n.t("blocked_instances")}</h5>
<h5>{I18NextService.i18n.t("blocked_instances")}</h5>
{this.itemList(instances.blocked)}
</div>
)}
@ -127,9 +128,9 @@ export class Instances extends Component<any, InstancesState> {
<table id="instances_table" className="table table-sm table-hover">
<thead className="pointer">
<tr>
<th>{i18n.t("name")}</th>
<th>{i18n.t("software")}</th>
<th>{i18n.t("version")}</th>
<th>{I18NextService.i18n.t("name")}</th>
<th>{I18NextService.i18n.t("software")}</th>
<th>{I18NextService.i18n.t("version")}</th>
</tr>
</thead>
<tbody>
@ -148,7 +149,7 @@ export class Instances extends Component<any, InstancesState> {
</table>
</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 { Component } from "inferno";
import { GetSiteResponse } from "lemmy-js-client";
import { i18n } from "../../i18next";
import { mdToHtml } from "../../markdown";
import { I18NextService } from "../../services";
import { HtmlTags } from "../common/html-tags";
interface LegalState {
@ -20,7 +20,7 @@ export class Legal extends Component<any, LegalState> {
}
get documentTitle(): string {
return i18n.t("legal_information");
return I18NextService.i18n.t("legal_information");
}
render() {

View file

@ -3,8 +3,7 @@ import { isBrowser } from "@utils/browser";
import { validEmail } from "@utils/helpers";
import { Component, linkEvent } from "inferno";
import { GetSiteResponse, LoginResponse } from "lemmy-js-client";
import { i18n } from "../../i18next";
import { UserService } from "../../services";
import { I18NextService, UserService } from "../../services";
import { HttpService, RequestState } from "../../services/HttpService";
import { toast } from "../../toast";
import { HtmlTags } from "../common/html-tags";
@ -43,7 +42,9 @@ export class Login extends Component<any, State> {
}
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 {
@ -68,13 +69,13 @@ export class Login extends Component<any, State> {
return (
<div>
<form onSubmit={linkEvent(this, this.handleLoginSubmit)}>
<h5>{i18n.t("login")}</h5>
<h5>{I18NextService.i18n.t("login")}</h5>
<div className="mb-3 row">
<label
className="col-sm-2 col-form-label"
htmlFor="login-email-or-username"
>
{i18n.t("email_or_username")}
{I18NextService.i18n.t("email_or_username")}
</label>
<div className="col-sm-10">
<input
@ -91,7 +92,7 @@ export class Login extends Component<any, State> {
</div>
<div className="mb-3 row">
<label className="col-sm-2 col-form-label" htmlFor="login-password">
{i18n.t("password")}
{I18NextService.i18n.t("password")}
</label>
<div className="col-sm-10">
<input
@ -112,9 +113,9 @@ export class Login extends Component<any, State> {
!!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>
</div>
</div>
@ -124,7 +125,7 @@ export class Login extends Component<any, State> {
className="col-sm-6 col-form-label"
htmlFor="login-totp-token"
>
{i18n.t("two_factor_token")}
{I18NextService.i18n.t("two_factor_token")}
</label>
<div className="col-sm-6">
<input
@ -146,7 +147,7 @@ export class Login extends Component<any, State> {
{this.state.loginRes.state == "loading" ? (
<Spinner />
) : (
i18n.t("login")
I18NextService.i18n.t("login")
)}
</button>
</div>
@ -172,7 +173,7 @@ export class Login extends Component<any, State> {
case "failed": {
if (loginRes.msg === "missing_totp_token") {
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 } });
@ -220,7 +221,7 @@ export class Login extends Component<any, State> {
if (email) {
const res = await HttpService.client.passwordReset({ email });
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 { Component, FormEventHandler, linkEvent } from "inferno";
import { EditSite, LocalSiteRateLimit } from "lemmy-js-client";
import { i18n } from "../../i18next";
import { I18NextService } from "../../services";
import { Spinner } from "../common/icon";
import Tabs from "../common/tabs";
@ -57,7 +57,9 @@ function RateLimits({
return (
<div role="tabpanel" className={classNames("mb-3 row", className)}>
<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
type="number"
id="rate-limit"
@ -68,7 +70,9 @@ function RateLimits({
/>
</div>
<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
type="number"
id="rate-limit-per-second"
@ -141,11 +145,11 @@ export default class RateLimitsForm extends Component<
className="rate-limit-form"
onSubmit={linkEvent(this, submitRateLimitForm)}
>
<h5>{i18n.t("rate_limit_header")}</h5>
<h5>{I18NextService.i18n.t("rate_limit_header")}</h5>
<Tabs
tabs={rateLimitTypes.map(rateLimitType => ({
key: rateLimitType,
label: i18n.t(`rate_limit_${rateLimitType}`),
label: I18NextService.i18n.t(`rate_limit_${rateLimitType}`),
getNode: isSelected => (
<RateLimits
className={classNames("tab-pane show", {
@ -176,7 +180,7 @@ export default class RateLimitsForm extends Component<
{this.props.loading ? (
<Spinner />
) : (
capitalizeFirstLetter(i18n.t("save"))
capitalizeFirstLetter(I18NextService.i18n.t("save"))
)}
</button>
</div>

View file

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

View file

@ -13,9 +13,8 @@ import {
SiteView,
} from "lemmy-js-client";
import { joinLemmyUrl } from "../../config";
import { i18n } from "../../i18next";
import { mdToHtml } from "../../markdown";
import { UserService } from "../../services";
import { I18NextService, UserService } from "../../services";
import { HttpService, RequestState } from "../../services/HttpService";
import { toast } from "../../toast";
import { HtmlTags } from "../common/html-tags";
@ -113,7 +112,7 @@ export class Signup extends Component<any, State> {
}
titleName(siteView: SiteView): string {
return i18n.t(
return I18NextService.i18n.t(
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"
htmlFor="register-username"
>
{i18n.t("username")}
{I18NextService.i18n.t("username")}
</label>
<div className="col-sm-10">
@ -172,14 +171,14 @@ export class Signup extends Component<any, State> {
required
minLength={3}
pattern="[a-zA-Z0-9_]+"
title={i18n.t("community_reqs")}
title={I18NextService.i18n.t("community_reqs")}
/>
</div>
</div>
<div className="mb-3 row">
<label className="col-sm-2 col-form-label" htmlFor="register-email">
{i18n.t("email")}
{I18NextService.i18n.t("email")}
</label>
<div className="col-sm-10">
<input
@ -188,8 +187,8 @@ export class Signup extends Component<any, State> {
className="form-control"
placeholder={
siteView.local_site.require_email_verification
? i18n.t("required")
: i18n.t("optional")
? I18NextService.i18n.t("required")
: I18NextService.i18n.t("optional")
}
value={this.state.form.email}
autoComplete="email"
@ -202,7 +201,7 @@ export class Signup extends Component<any, State> {
!validEmail(this.state.form.email) && (
<div className="mt-2 mb-0 alert alert-warning" role="alert">
<Icon icon="alert-triangle" classes="icon-inline me-2" />
{i18n.t("no_password_reset")}
{I18NextService.i18n.t("no_password_reset")}
</div>
)}
</div>
@ -213,7 +212,7 @@ export class Signup extends Component<any, State> {
className="col-sm-2 col-form-label"
htmlFor="register-password"
>
{i18n.t("password")}
{I18NextService.i18n.t("password")}
</label>
<div className="col-sm-10">
<input
@ -229,7 +228,9 @@ export class Signup extends Component<any, State> {
/>
{this.state.form.password && (
<div className={this.passwordColorClass}>
{i18n.t(this.passwordStrength as NoOptionI18nKeys)}
{I18NextService.i18n.t(
this.passwordStrength as NoOptionI18nKeys
)}
</div>
)}
</div>
@ -240,7 +241,7 @@ export class Signup extends Component<any, State> {
className="col-sm-2 col-form-label"
htmlFor="register-verify-password"
>
{i18n.t("verify_password")}
{I18NextService.i18n.t("verify_password")}
</label>
<div className="col-sm-10">
<input
@ -262,7 +263,7 @@ export class Signup extends Component<any, State> {
<div className="offset-sm-2 col-sm-10">
<div className="mt-2 alert alert-warning" role="alert">
<Icon icon="alert-triangle" classes="icon-inline me-2" />
{i18n.t("fill_out_application")}
{I18NextService.i18n.t("fill_out_application")}
</div>
{siteView.local_site.application_question && (
<div
@ -280,7 +281,7 @@ export class Signup extends Component<any, State> {
className="col-sm-2 col-form-label"
htmlFor="application_answer"
>
{i18n.t("answer")}
{I18NextService.i18n.t("answer")}
</label>
<div className="col-sm-10">
<MarkdownTextArea
@ -306,7 +307,7 @@ export class Signup extends Component<any, State> {
onChange={linkEvent(this, this.handleRegisterShowNsfwChange)}
/>
<label className="form-check-label" htmlFor="register-show-nsfw">
{i18n.t("show_nsfw")}
{I18NextService.i18n.t("show_nsfw")}
</label>
</div>
</div>
@ -345,12 +346,14 @@ export class Signup extends Component<any, State> {
return (
<div className="mb-3 row">
<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
type="button"
className="btn btn-secondary"
onClick={linkEvent(this, this.handleRegenCaptcha)}
aria-label={i18n.t("captcha")}
aria-label={I18NextService.i18n.t("captcha")}
>
<Icon icon="refresh-cw" classes="icon-refresh-cw" />
</button>
@ -384,13 +387,13 @@ export class Signup extends Component<any, State> {
className="rounded-top img-fluid"
src={this.captchaPngSrc(captchaRes)}
style="border-bottom-right-radius: 0; border-bottom-left-radius: 0;"
alt={i18n.t("captcha")}
alt={I18NextService.i18n.t("captcha")}
/>
{captchaRes.wav && (
<button
className="rounded-bottom btn btn-sm btn-secondary d-block"
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)}
type="button"
disabled={this.state.captchaPlaying}
@ -474,10 +477,10 @@ export class Signup extends Component<any, State> {
i.props.history.replace("/communities");
} else {
if (data.verify_email_sent) {
toast(i18n.t("verify_email_sent"));
toast(I18NextService.i18n.t("verify_email_sent"));
}
if (data.registration_created) {
toast(i18n.t("registration_application_sent"));
toast(I18NextService.i18n.t("registration_application_sent"));
}
i.props.history.push("/");
}

View file

@ -13,7 +13,7 @@ import {
Instance,
ListingType,
} from "lemmy-js-client";
import { i18n } from "../../i18next";
import { I18NextService } from "../../services";
import { Icon, Spinner } from "../common/icon";
import { ImageUploadForm } from "../common/image-upload-form";
import { LanguageSelect } from "../common/language-select";
@ -136,12 +136,12 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
/>
<h5>{`${
siteSetup
? capitalizeFirstLetter(i18n.t("edit"))
: capitalizeFirstLetter(i18n.t("setup"))
} ${i18n.t("your_site")}`}</h5>
? capitalizeFirstLetter(I18NextService.i18n.t("edit"))
: capitalizeFirstLetter(I18NextService.i18n.t("setup"))
} ${I18NextService.i18n.t("your_site")}`}</h5>
<div className="mb-3 row">
<label className="col-12 col-form-label" htmlFor="create-site-name">
{i18n.t("name")}
{I18NextService.i18n.t("name")}
</label>
<div className="col-12">
<input
@ -157,9 +157,11 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
</div>
</div>
<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
uploadTitle={i18n.t("upload_icon")}
uploadTitle={I18NextService.i18n.t("upload_icon")}
imageSrc={this.state.siteForm.icon}
onUpload={this.handleIconUpload}
onRemove={this.handleIconRemove}
@ -167,9 +169,11 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
/>
</div>
<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
uploadTitle={i18n.t("upload_banner")}
uploadTitle={I18NextService.i18n.t("upload_banner")}
imageSrc={this.state.siteForm.banner}
onUpload={this.handleBannerUpload}
onRemove={this.handleBannerRemove}
@ -177,7 +181,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
</div>
<div className="mb-3 row">
<label className="col-12 col-form-label" htmlFor="site-desc">
{i18n.t("description")}
{I18NextService.i18n.t("description")}
</label>
<div className="col-12">
<input
@ -191,7 +195,9 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
</div>
</div>
<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">
<MarkdownTextArea
initialContent={this.state.siteForm.sidebar}
@ -204,7 +210,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
</div>
<div className="mb-3 row">
<label className="col-12 col-form-label">
{i18n.t("legal_information")}
{I18NextService.i18n.t("legal_information")}
</label>
<div className="col-12">
<MarkdownTextArea
@ -230,7 +236,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
className="form-check-label"
htmlFor="create-site-downvotes"
>
{i18n.t("enable_downvotes")}
{I18NextService.i18n.t("enable_downvotes")}
</label>
</div>
</div>
@ -249,7 +255,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
className="form-check-label"
htmlFor="create-site-enable-nsfw"
>
{i18n.t("enable_nsfw")}
{I18NextService.i18n.t("enable_nsfw")}
</label>
</div>
</div>
@ -260,7 +266,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
className="form-check-label me-2"
htmlFor="create-site-registration-mode"
>
{i18n.t("registration_mode")}
{I18NextService.i18n.t("registration_mode")}
</label>
<select
id="create-site-registration-mode"
@ -269,17 +275,21 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
className="form-select d-inline-block w-auto"
>
<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 value={"Open"}>{i18n.t("open_registration")}</option>
<option value={"Closed"}>{i18n.t("close_registration")}</option>
</select>
</div>
</div>
{this.state.siteForm.registration_mode == "RequireApplication" && (
<div className="mb-3 row">
<label className="col-12 col-form-label">
{i18n.t("application_questionnaire")}
{I18NextService.i18n.t("application_questionnaire")}
</label>
<div className="col-12">
<MarkdownTextArea
@ -309,7 +319,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
className="form-check-label"
htmlFor="create-site-community-creation-admin-only"
>
{i18n.t("community_creation_admin_only")}
{I18NextService.i18n.t("community_creation_admin_only")}
</label>
</div>
</div>
@ -331,7 +341,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
className="form-check-label"
htmlFor="create-site-require-email-verification"
>
{i18n.t("require_email_verification")}
{I18NextService.i18n.t("require_email_verification")}
</label>
</div>
</div>
@ -353,7 +363,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
className="form-check-label"
htmlFor="create-site-email-admins"
>
{i18n.t("application_email_admins")}
{I18NextService.i18n.t("application_email_admins")}
</label>
</div>
</div>
@ -372,7 +382,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
className="form-check-label"
htmlFor="create-site-reports-email-admins"
>
{i18n.t("reports_email_admins")}
{I18NextService.i18n.t("reports_email_admins")}
</label>
</div>
</div>
@ -383,7 +393,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
className="form-check-label me-2"
htmlFor="create-site-default-theme"
>
{i18n.t("theme")}
{I18NextService.i18n.t("theme")}
</label>
<select
id="create-site-default-theme"
@ -391,7 +401,9 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
onChange={linkEvent(this, this.handleSiteDefaultTheme)}
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 => (
<option key={theme} value={theme}>
{theme}
@ -403,7 +415,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
{this.props.showLocal && (
<form className="mb-3 row">
<label className="col-sm-3 col-form-label">
{i18n.t("listing_type")}
{I18NextService.i18n.t("listing_type")}
</label>
<div className="col-sm-9">
<ListingTypeSelect
@ -429,7 +441,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
className="form-check-label"
htmlFor="create-site-private-instance"
>
{i18n.t("private_instance")}
{I18NextService.i18n.t("private_instance")}
</label>
</div>
</div>
@ -448,7 +460,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
className="form-check-label"
htmlFor="create-site-hide-modlog-mod-names"
>
{i18n.t("hide_modlog_mod_names")}
{I18NextService.i18n.t("hide_modlog_mod_names")}
</label>
</div>
</div>
@ -458,7 +470,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
className="col-12 col-form-label"
htmlFor="create-site-slur-filter-regex"
>
{i18n.t("slur_filter_regex")}
{I18NextService.i18n.t("slur_filter_regex")}
</label>
<div className="col-12">
<input
@ -485,7 +497,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
className="col-12 col-form-label"
htmlFor="create-site-actor-name"
>
{i18n.t("actor_name_max_length")}
{I18NextService.i18n.t("actor_name_max_length")}
</label>
<div className="col-12">
<input
@ -512,7 +524,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
className="form-check-label"
htmlFor="create-site-federation-enabled"
>
{i18n.t("federation_enabled")}
{I18NextService.i18n.t("federation_enabled")}
</label>
</div>
</div>
@ -537,7 +549,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
className="form-check-label"
htmlFor="create-site-federation-debug"
>
{i18n.t("federation_debug")}
{I18NextService.i18n.t("federation_debug")}
</label>
</div>
</div>
@ -547,7 +559,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
className="col-12 col-form-label"
htmlFor="create-site-federation-worker-count"
>
{i18n.t("federation_worker_count")}
{I18NextService.i18n.t("federation_worker_count")}
</label>
<div className="col-12">
<input
@ -579,7 +591,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
className="form-check-label"
htmlFor="create-site-captcha-enabled"
>
{i18n.t("captcha_enabled")}
{I18NextService.i18n.t("captcha_enabled")}
</label>
</div>
</div>
@ -591,7 +603,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
className="form-check-label me-2"
htmlFor="create-site-captcha-difficulty"
>
{i18n.t("captcha_difficulty")}
{I18NextService.i18n.t("captcha_difficulty")}
</label>
<select
id="create-site-captcha-difficulty"
@ -599,9 +611,11 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
onChange={linkEvent(this, this.handleSiteCaptchaDifficulty)}
className="form-select d-inline-block w-auto"
>
<option value="easy">{i18n.t("easy")}</option>
<option value="medium">{i18n.t("medium")}</option>
<option value="hard">{i18n.t("hard")}</option>
<option value="easy">{I18NextService.i18n.t("easy")}</option>
<option value="medium">
{I18NextService.i18n.t("medium")}
</option>
<option value="hard">{I18NextService.i18n.t("hard")}</option>
</select>
</div>
</div>
@ -616,9 +630,9 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
{this.props.loading ? (
<Spinner />
) : siteSetup ? (
capitalizeFirstLetter(i18n.t("save"))
capitalizeFirstLetter(I18NextService.i18n.t("save"))
) : (
capitalizeFirstLetter(i18n.t("create"))
capitalizeFirstLetter(I18NextService.i18n.t("create"))
)}
</button>
</div>
@ -634,7 +648,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
return (
<div className="col-12 col-md-6">
<label className="col-form-label" htmlFor={id}>
{i18n.t(key)}
{I18NextService.i18n.t(key)}
</label>
<div className="d-flex justify-content-between align-items-center">
<input

View file

@ -1,7 +1,7 @@
import { Component, linkEvent } from "inferno";
import { PersonView, Site, SiteAggregates } from "lemmy-js-client";
import { i18n } from "../../i18next";
import { mdToHtml } from "../../markdown";
import { I18NextService } from "../../services";
import { Badges } from "../common/badges";
import { BannerIconHeader } from "../common/banner-icon-header";
import { Icon } from "../common/icon";
@ -62,10 +62,14 @@ export class SiteSidebar extends Component<SiteSidebarProps, SiteSidebarState> {
className="btn btn-sm"
onClick={linkEvent(this, this.handleCollapseSidebar)}
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={
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-target="#sidebarInfoBody"
@ -104,7 +108,7 @@ export class SiteSidebar extends Component<SiteSidebarProps, SiteSidebarState> {
admins(admins: PersonView[]) {
return (
<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 => (
<li key={av.person.id} className="list-inline-item">
<PersonListing person={av.person} />

View file

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

View file

@ -46,9 +46,8 @@ import {
} from "lemmy-js-client";
import moment from "moment";
import { fetchLimit } from "../config";
import { i18n } from "../i18next";
import { InitialFetchRequest } from "../interfaces";
import { FirstLoadService } from "../services/FirstLoadService";
import { FirstLoadService, I18NextService } from "../services";
import { HttpService, RequestState } from "../services/HttpService";
import { HtmlTags } from "./common/html-tags";
import { Icon, Spinner } from "./common/icon";
@ -586,14 +585,14 @@ const Filter = ({
}) => (
<div className="col-sm-6 mb-3">
<label className="mb-2" htmlFor={`filter-${filterType}`}>
{i18n.t(`filter_by_${filterType}` as NoOptionI18nKeys)}
{I18NextService.i18n.t(`filter_by_${filterType}` as NoOptionI18nKeys)}
</label>
<SearchableSelect
id={`filter-${filterType}`}
value={value ?? 0}
options={[
{
label: i18n.t("all"),
label: I18NextService.i18n.t("all"),
value: "0",
},
].concat(options)}
@ -724,8 +723,8 @@ export class Modlog extends Component<
this.isoData.site_res.admins.some(
({ person: { id } }) => id === person.id
)
? i18n.t("admin")
: i18n.t("mod");
? I18NextService.i18n.t("admin")
: I18NextService.i18n.t("mod");
}
get documentTitle(): string {
@ -770,7 +769,7 @@ export class Modlog extends Component<
>
/c/{this.state.communityRes.data.community_view.community.name}{" "}
</Link>
<span>{i18n.t("modlog")}</span>
<span>{I18NextService.i18n.t("modlog")}</span>
</h5>
)}
<div className="row mb-2">
@ -782,9 +781,9 @@ export class Modlog extends Component<
aria-label="action"
>
<option disabled aria-hidden="true">
{i18n.t("filter_by_action")}
{I18NextService.i18n.t("filter_by_action")}
</option>
<option value={"All"}>{i18n.t("all")}</option>
<option value={"All"}>{I18NextService.i18n.t("all")}</option>
<option value={"ModRemovePost"}>Removing Posts</option>
<option value={"ModLockPost"}>Locking 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">
<thead className="pointer">
<tr>
<th> {i18n.t("time")}</th>
<th>{i18n.t("mod")}</th>
<th>{i18n.t("action")}</th>
<th> {I18NextService.i18n.t("time")}</th>
<th>{I18NextService.i18n.t("mod")}</th>
<th>{I18NextService.i18n.t("action")}</th>
</tr>
</thead>
{this.combined}

View file

@ -1,5 +1,5 @@
import { Component } from "inferno";
import { i18n } from "../../i18next";
import { I18NextService } from "../../services";
import { Icon } from "../common/icon";
interface CakeDayProps {
@ -19,6 +19,8 @@ export class CakeDay extends Component<CakeDayProps, any> {
}
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,
} from "lemmy-js-client";
import { fetchLimit, relTags } from "../../config";
import { i18n } from "../../i18next";
import { CommentViewType, InitialFetchRequest } from "../../interfaces";
import { UserService } from "../../services";
import { FirstLoadService } from "../../services/FirstLoadService";
import { FirstLoadService, I18NextService, UserService } from "../../services";
import { HttpService, RequestState } from "../../services/HttpService";
import { toast } from "../../toast";
import { CommentNodes } from "../comment/comment-nodes";
@ -187,9 +185,9 @@ export class Inbox extends Component<any, InboxState> {
get documentTitle(): string {
const mui = UserService.Instance.myUserInfo;
return mui
? `@${mui.local_user_view.person.name} ${i18n.t("inbox")} - ${
this.state.siteRes.site_view.site.name
}`
? `@${mui.local_user_view.person.name} ${I18NextService.i18n.t(
"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}
/>
<h5 className="mb-2">
{i18n.t("inbox")}
{I18NextService.i18n.t("inbox")}
{inboxRss && (
<small>
<a href={inboxRss} title="RSS" rel={relTags}>
@ -245,7 +243,7 @@ export class Inbox extends Component<any, InboxState> {
{this.state.markAllAsReadRes.state == "loading" ? (
<Spinner />
) : (
i18n.t("mark_all_as_read")
I18NextService.i18n.t("mark_all_as_read")
)}
</button>
)}
@ -296,7 +294,7 @@ export class Inbox extends Component<any, InboxState> {
checked={this.state.unreadOrAll == UnreadOrAll.Unread}
onChange={linkEvent(this, this.handleUnreadOrAllChange)}
/>
{i18n.t("unread")}
{I18NextService.i18n.t("unread")}
</label>
<label
className={`btn btn-outline-secondary pointer
@ -310,7 +308,7 @@ export class Inbox extends Component<any, InboxState> {
checked={this.state.unreadOrAll == UnreadOrAll.All}
onChange={linkEvent(this, this.handleUnreadOrAllChange)}
/>
{i18n.t("all")}
{I18NextService.i18n.t("all")}
</label>
</div>
);
@ -331,7 +329,7 @@ export class Inbox extends Component<any, InboxState> {
checked={this.state.messageType == MessageType.All}
onChange={linkEvent(this, this.handleMessageTypeChange)}
/>
{i18n.t("all")}
{I18NextService.i18n.t("all")}
</label>
<label
className={`btn btn-outline-secondary pointer
@ -345,7 +343,7 @@ export class Inbox extends Component<any, InboxState> {
checked={this.state.messageType == MessageType.Replies}
onChange={linkEvent(this, this.handleMessageTypeChange)}
/>
{i18n.t("replies")}
{I18NextService.i18n.t("replies")}
</label>
<label
className={`btn btn-outline-secondary pointer
@ -359,7 +357,7 @@ export class Inbox extends Component<any, InboxState> {
checked={this.state.messageType == MessageType.Mentions}
onChange={linkEvent(this, this.handleMessageTypeChange)}
/>
{i18n.t("mentions")}
{I18NextService.i18n.t("mentions")}
</label>
<label
className={`btn btn-outline-secondary pointer
@ -373,7 +371,7 @@ export class Inbox extends Component<any, InboxState> {
checked={this.state.messageType == MessageType.Messages}
onChange={linkEvent(this, this.handleMessageTypeChange)}
/>
{i18n.t("messages")}
{I18NextService.i18n.t("messages")}
</label>
</div>
);
@ -826,7 +824,7 @@ export class Inbox extends Component<any, InboxState> {
const res = await HttpService.client.createComment(form);
if (res.state === "success") {
toast(i18n.t("reply_sent"));
toast(I18NextService.i18n.t("reply_sent"));
this.findAndUpdateComment(res);
}
@ -837,7 +835,7 @@ export class Inbox extends Component<any, InboxState> {
const res = await HttpService.client.editComment(form);
if (res.state === "success") {
toast(i18n.t("edit"));
toast(I18NextService.i18n.t("edit"));
this.findAndUpdateComment(res);
} else if (res.state === "failed") {
toast(res.msg, "danger");
@ -849,7 +847,7 @@ export class Inbox extends Component<any, InboxState> {
async handleDeleteComment(form: DeleteComment) {
const res = await HttpService.client.deleteComment(form);
if (res.state == "success") {
toast(i18n.t("deleted"));
toast(I18NextService.i18n.t("deleted"));
this.findAndUpdateComment(res);
}
}
@ -857,7 +855,7 @@ export class Inbox extends Component<any, InboxState> {
async handleRemoveComment(form: RemoveComment) {
const res = await HttpService.client.removeComment(form);
if (res.state == "success") {
toast(i18n.t("remove_comment"));
toast(I18NextService.i18n.t("remove_comment"));
this.findAndUpdateComment(res);
}
}
@ -892,7 +890,7 @@ export class Inbox extends Component<any, InboxState> {
async handleTransferCommunity(form: TransferCommunity) {
await HttpService.client.transferCommunity(form);
toast(i18n.t("transfer_community"));
toast(I18NextService.i18n.t("transfer_community"));
}
async handleCommentReplyRead(form: MarkCommentReplyAsRead) {
@ -1004,7 +1002,7 @@ export class Inbox extends Component<any, InboxState> {
purgeItem(purgeRes: RequestState<PurgeItemResponse>) {
if (purgeRes.state == "success") {
toast(i18n.t("purge_success"));
toast(I18NextService.i18n.t("purge_success"));
this.context.router.history.push(`/`);
}
}
@ -1013,7 +1011,7 @@ export class Inbox extends Component<any, InboxState> {
res: RequestState<PrivateMessageReportResponse | CommentReportResponse>
) {
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 { Component, linkEvent } from "inferno";
import { GetSiteResponse, LoginResponse } from "lemmy-js-client";
import { i18n } from "../../i18next";
import { HttpService, UserService } from "../../services";
import { HttpService, I18NextService, UserService } from "../../services";
import { RequestState } from "../../services/HttpService";
import { HtmlTags } from "../common/html-tags";
import { Spinner } from "../common/icon";
@ -34,7 +33,7 @@ export class PasswordChange extends Component<any, State> {
}
get documentTitle(): string {
return `${i18n.t("password_change")} - ${
return `${I18NextService.i18n.t("password_change")} - ${
this.state.siteRes.site_view.site.name
}`;
}
@ -48,7 +47,7 @@ export class PasswordChange extends Component<any, State> {
/>
<div className="row">
<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()}
</div>
</div>
@ -61,7 +60,7 @@ export class PasswordChange extends Component<any, State> {
<form onSubmit={linkEvent(this, this.handlePasswordChangeSubmit)}>
<div className="mb-3 row">
<label className="col-sm-2 col-form-label" htmlFor="new-password">
{i18n.t("new_password")}
{I18NextService.i18n.t("new_password")}
</label>
<div className="col-sm-10">
<input
@ -77,7 +76,7 @@ export class PasswordChange extends Component<any, State> {
</div>
<div className="mb-3 row">
<label className="col-sm-2 col-form-label" htmlFor="verify-password">
{i18n.t("verify_password")}
{I18NextService.i18n.t("verify_password")}
</label>
<div className="col-sm-10">
<input
@ -97,7 +96,7 @@ export class PasswordChange extends Component<any, State> {
{this.state.passwordChangeRes.state == "loading" ? (
<Spinner />
) : (
capitalizeFirstLetter(i18n.t("save"))
capitalizeFirstLetter(I18NextService.i18n.t("save"))
)}
</button>
</div>

View file

@ -72,11 +72,9 @@ import {
} from "lemmy-js-client";
import moment from "moment";
import { fetchLimit, relTags } from "../../config";
import { i18n } from "../../i18next";
import { InitialFetchRequest, PersonDetailsView } from "../../interfaces";
import { mdToHtml } from "../../markdown";
import { UserService } from "../../services";
import { FirstLoadService } from "../../services/FirstLoadService";
import { FirstLoadService, I18NextService, UserService } from "../../services";
import { HttpService, RequestState } from "../../services/HttpService";
import { setupTippy } from "../../tippy";
import { toast } from "../../toast";
@ -137,7 +135,7 @@ const getCommunitiesListing = (
communityViews.length > 0 && (
<div className="card border-secondary mb-3">
<div className="card-body">
<h5>{i18n.t(translationKey)}</h5>
<h5>{I18NextService.i18n.t(translationKey)}</h5>
<ul className="list-unstyled mb-0">
{communityViews.map(({ community }) => (
<li key={community.id}>
@ -422,7 +420,7 @@ export class Profile extends Component<
checked={active}
onChange={linkEvent(this, this.handleViewChange)}
/>
{i18n.t(view.toLowerCase() as NoOptionI18nKeys)}
{I18NextService.i18n.t(view.toLowerCase() as NoOptionI18nKeys)}
</label>
);
}
@ -485,22 +483,22 @@ export class Profile extends Component<
</li>
{isBanned(pv.person) && (
<li className="list-inline-item badge text-bg-danger">
{i18n.t("banned")}
{I18NextService.i18n.t("banned")}
</li>
)}
{pv.person.deleted && (
<li className="list-inline-item badge text-bg-danger">
{i18n.t("deleted")}
{I18NextService.i18n.t("deleted")}
</li>
)}
{pv.person.admin && (
<li className="list-inline-item badge text-bg-light">
{i18n.t("admin")}
{I18NextService.i18n.t("admin")}
</li>
)}
{pv.person.bot_account && (
<li className="list-inline-item badge text-bg-light">
{i18n.t("bot_account").toLowerCase()}
{I18NextService.i18n.t("bot_account").toLowerCase()}
</li>
)}
</ul>
@ -516,7 +514,7 @@ export class Profile extends Component<
rel={relTags}
href={`https://matrix.to/#/${pv.person.matrix_user_id}`}
>
{i18n.t("send_secure_message")}
{I18NextService.i18n.t("send_secure_message")}
</a>
<Link
className={
@ -524,7 +522,7 @@ export class Profile extends Component<
}
to={`/create_private_message/${pv.person.id}`}
>
{i18n.t("send_message")}
{I18NextService.i18n.t("send_message")}
</Link>
{personBlocked ? (
<button
@ -536,7 +534,7 @@ export class Profile extends Component<
this.handleUnblockPerson
)}
>
{i18n.t("unblock_user")}
{I18NextService.i18n.t("unblock_user")}
</button>
) : (
<button
@ -548,7 +546,7 @@ export class Profile extends Component<
this.handleBlockPerson
)}
>
{i18n.t("block_user")}
{I18NextService.i18n.t("block_user")}
</button>
)}
</>
@ -563,9 +561,9 @@ export class Profile extends Component<
"d-flex align-self-start btn btn-secondary me-2"
}
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
@ -573,9 +571,9 @@ export class Profile extends Component<
"d-flex align-self-start btn btn-secondary me-2"
}
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>
))}
</div>
@ -590,13 +588,13 @@ export class Profile extends Component<
<div>
<ul className="list-inline mb-2">
<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),
formattedCount: numToSI(pv.counts.post_count),
})}
</li>
<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),
formattedCount: numToSI(pv.counts.comment_count),
})}
@ -604,7 +602,7 @@ export class Profile extends Component<
</ul>
</div>
<div className="text-muted">
{i18n.t("joined")}{" "}
{I18NextService.i18n.t("joined")}{" "}
<MomentTime
published={pv.person.published}
showAgo
@ -614,7 +612,7 @@ export class Profile extends Component<
<div className="d-flex align-items-center text-muted mb-2">
<Icon icon="cake" />
<span className="ms-2">
{i18n.t("cake_day_title")}{" "}
{I18NextService.i18n.t("cake_day_title")}{" "}
{moment
.utc(pv.person.published)
.local()
@ -623,7 +621,7 @@ export class Profile extends Component<
</div>
{!UserService.Instance.myUserInfo && (
<div className="alert alert-info" role="alert">
{i18n.t("profile_not_logged_in_alert")}
{I18NextService.i18n.t("profile_not_logged_in_alert")}
</div>
)}
</div>
@ -641,24 +639,24 @@ export class Profile extends Component<
<form onSubmit={linkEvent(this, this.handleModBanSubmit)}>
<div className="mb-3 row col-12">
<label className="col-form-label" htmlFor="profile-ban-reason">
{i18n.t("reason")}
{I18NextService.i18n.t("reason")}
</label>
<input
type="text"
id="profile-ban-reason"
className="form-control me-2"
placeholder={i18n.t("reason")}
placeholder={I18NextService.i18n.t("reason")}
value={this.state.banReason}
onInput={linkEvent(this, this.handleModBanReasonChange)}
/>
<label className="col-form-label" htmlFor={`mod-ban-expires`}>
{i18n.t("expires")}
{I18NextService.i18n.t("expires")}
</label>
<input
type="number"
id={`mod-ban-expires`}
className="form-control me-2"
placeholder={i18n.t("number_of_days")}
placeholder={I18NextService.i18n.t("number_of_days")}
value={this.state.banExpireDays}
onInput={linkEvent(this, this.handleModBanExpireDaysChange)}
/>
@ -674,9 +672,9 @@ export class Profile extends Component<
<label
className="form-check-label"
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>
</div>
</div>
@ -684,23 +682,23 @@ export class Profile extends Component<
{/* TODO hold off on expires until later */}
{/* <div class="mb-3 row"> */}
{/* <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 className="mb-3 row">
<button
type="reset"
className="btn btn-secondary me-2"
aria-label={i18n.t("cancel")}
aria-label={I18NextService.i18n.t("cancel")}
onClick={linkEvent(this, this.handleModBanSubmitCancel)}
>
{i18n.t("cancel")}
{I18NextService.i18n.t("cancel")}
</button>
<button
type="submit"
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>
</div>
</form>
@ -904,14 +902,14 @@ export class Profile extends Component<
async handleCommentReport(form: CreateCommentReport) {
const reportRes = await HttpService.client.createCommentReport(form);
if (reportRes.state === "success") {
toast(i18n.t("report_created"));
toast(I18NextService.i18n.t("report_created"));
}
}
async handlePostReport(form: CreatePostReport) {
const reportRes = await HttpService.client.createPostReport(form);
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) {
await HttpService.client.transferCommunity(form);
toast(i18n.t("transfer_community"));
toast(I18NextService.i18n.t("transfer_community"));
}
async handleCommentReplyRead(form: MarkCommentReplyAsRead) {
@ -999,7 +997,7 @@ export class Profile extends Component<
purgeItem(purgeRes: RequestState<PurgeItemResponse>) {
if (purgeRes.state == "success") {
toast(i18n.t("purge_success"));
toast(I18NextService.i18n.t("purge_success"));
this.context.router.history.push(`/`);
}
}

View file

@ -12,10 +12,8 @@ import {
RegistrationApplicationView,
} from "lemmy-js-client";
import { fetchLimit } from "../../config";
import { i18n } from "../../i18next";
import { InitialFetchRequest } from "../../interfaces";
import { UserService } from "../../services";
import { FirstLoadService } from "../../services/FirstLoadService";
import { FirstLoadService, I18NextService, UserService } from "../../services";
import { HttpService, RequestState } from "../../services/HttpService";
import { setupTippy } from "../../tippy";
import { HtmlTags } from "../common/html-tags";
@ -79,7 +77,7 @@ export class RegistrationApplications extends Component<
get documentTitle(): string {
const mui = UserService.Instance.myUserInfo;
return mui
? `@${mui.local_user_view.person.name} ${i18n.t(
? `@${mui.local_user_view.person.name} ${I18NextService.i18n.t(
"registration_applications"
)} - ${this.state.siteRes.site_view.site.name}`
: "";
@ -102,7 +100,9 @@ export class RegistrationApplications extends Component<
title={this.documentTitle}
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.applicationList(apps)}
<Paginator
@ -139,7 +139,7 @@ export class RegistrationApplications extends Component<
checked={this.state.unreadOrAll == UnreadOrAll.Unread}
onChange={linkEvent(this, this.handleUnreadOrAllChange)}
/>
{i18n.t("unread")}
{I18NextService.i18n.t("unread")}
</label>
<label
className={`btn btn-outline-secondary pointer
@ -153,7 +153,7 @@ export class RegistrationApplications extends Component<
checked={this.state.unreadOrAll == UnreadOrAll.All}
onChange={linkEvent(this, this.handleUnreadOrAllChange)}
/>
{i18n.t("all")}
{I18NextService.i18n.t("all")}
</label>
</div>
);

View file

@ -27,10 +27,13 @@ import {
ResolvePrivateMessageReport,
} from "lemmy-js-client";
import { fetchLimit } from "../../config";
import { i18n } from "../../i18next";
import { InitialFetchRequest } from "../../interfaces";
import { HttpService, UserService } from "../../services";
import { FirstLoadService } from "../../services/FirstLoadService";
import {
FirstLoadService,
HttpService,
I18NextService,
UserService,
} from "../../services";
import { RequestState } from "../../services/HttpService";
import { CommentReport } from "../comment/comment-report";
import { HtmlTags } from "../common/html-tags";
@ -134,9 +137,9 @@ export class Reports extends Component<any, ReportsState> {
get documentTitle(): string {
const mui = UserService.Instance.myUserInfo;
return mui
? `@${mui.local_user_view.person.name} ${i18n.t("reports")} - ${
this.state.siteRes.site_view.site.name
}`
? `@${mui.local_user_view.person.name} ${I18NextService.i18n.t(
"reports"
)} - ${this.state.siteRes.site_view.site.name}`
: "";
}
@ -149,7 +152,7 @@ export class Reports extends Component<any, ReportsState> {
title={this.documentTitle}
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.section}
<Paginator
@ -198,7 +201,7 @@ export class Reports extends Component<any, ReportsState> {
checked={this.state.unreadOrAll == UnreadOrAll.Unread}
onChange={linkEvent(this, this.handleUnreadOrAllChange)}
/>
{i18n.t("unread")}
{I18NextService.i18n.t("unread")}
</label>
<label
className={`btn btn-outline-secondary pointer
@ -212,7 +215,7 @@ export class Reports extends Component<any, ReportsState> {
checked={this.state.unreadOrAll == UnreadOrAll.All}
onChange={linkEvent(this, this.handleUnreadOrAllChange)}
/>
{i18n.t("all")}
{I18NextService.i18n.t("all")}
</label>
</div>
);
@ -233,7 +236,7 @@ export class Reports extends Component<any, ReportsState> {
checked={this.state.messageType == MessageType.All}
onChange={linkEvent(this, this.handleMessageTypeChange)}
/>
{i18n.t("all")}
{I18NextService.i18n.t("all")}
</label>
<label
className={`btn btn-outline-secondary pointer
@ -247,7 +250,7 @@ export class Reports extends Component<any, ReportsState> {
checked={this.state.messageType == MessageType.CommentReport}
onChange={linkEvent(this, this.handleMessageTypeChange)}
/>
{i18n.t("comments")}
{I18NextService.i18n.t("comments")}
</label>
<label
className={`btn btn-outline-secondary pointer
@ -261,7 +264,7 @@ export class Reports extends Component<any, ReportsState> {
checked={this.state.messageType == MessageType.PostReport}
onChange={linkEvent(this, this.handleMessageTypeChange)}
/>
{i18n.t("posts")}
{I18NextService.i18n.t("posts")}
</label>
{amAdmin() && (
<label
@ -281,7 +284,7 @@ export class Reports extends Component<any, ReportsState> {
}
onChange={linkEvent(this, this.handleMessageTypeChange)}
/>
{i18n.t("messages")}
{I18NextService.i18n.t("messages")}
</label>
)}
</div>

View file

@ -29,9 +29,9 @@ import {
SortType,
} from "lemmy-js-client";
import { elementUrl, emDash, relTags } from "../../config";
import { i18n, languages } from "../../i18next";
import { UserService } from "../../services";
import { HttpService, RequestState } from "../../services/HttpService";
import { I18NextService, languages } from "../../services/I18NextService";
import { setupTippy } from "../../tippy";
import { toast } from "../../toast";
import { HtmlTags } from "../common/html-tags";
@ -113,7 +113,7 @@ const Filter = ({
className="col-md-4 col-form-label"
htmlFor={`block-${filterType}-filter`}
>
{i18n.t(`block_${filterType}` as NoOptionI18nKeys)}
{I18NextService.i18n.t(`block_${filterType}` as NoOptionI18nKeys)}
</label>
<div className="col-md-8">
<SearchableSelect
@ -233,7 +233,7 @@ export class Settings extends Component<any, SettingsState> {
}
get documentTitle(): string {
return i18n.t("settings");
return I18NextService.i18n.t("settings");
}
render() {
@ -249,12 +249,12 @@ export class Settings extends Component<any, SettingsState> {
tabs={[
{
key: "settings",
label: i18n.t("settings"),
label: I18NextService.i18n.t("settings"),
getNode: this.userSettings,
},
{
key: "blocks",
label: i18n.t("blocks"),
label: I18NextService.i18n.t("blocks"),
getNode: this.blockCards,
},
]}
@ -316,11 +316,11 @@ export class Settings extends Component<any, SettingsState> {
changePasswordHtmlForm() {
return (
<>
<h5>{i18n.t("change_password")}</h5>
<h5>{I18NextService.i18n.t("change_password")}</h5>
<form onSubmit={linkEvent(this, this.handleChangePasswordSubmit)}>
<div className="mb-3 row">
<label className="col-sm-5 col-form-label" htmlFor="user-password">
{i18n.t("new_password")}
{I18NextService.i18n.t("new_password")}
</label>
<div className="col-sm-7">
<input
@ -339,7 +339,7 @@ export class Settings extends Component<any, SettingsState> {
className="col-sm-5 col-form-label"
htmlFor="user-verify-password"
>
{i18n.t("verify_password")}
{I18NextService.i18n.t("verify_password")}
</label>
<div className="col-sm-7">
<input
@ -358,7 +358,7 @@ export class Settings extends Component<any, SettingsState> {
className="col-sm-5 col-form-label"
htmlFor="user-old-password"
>
{i18n.t("old_password")}
{I18NextService.i18n.t("old_password")}
</label>
<div className="col-sm-7">
<input
@ -380,7 +380,7 @@ export class Settings extends Component<any, SettingsState> {
{this.state.changePasswordRes.state === "loading" ? (
<Spinner />
) : (
capitalizeFirstLetter(i18n.t("save"))
capitalizeFirstLetter(I18NextService.i18n.t("save"))
)}
</button>
</div>
@ -409,7 +409,7 @@ export class Settings extends Component<any, SettingsState> {
blockedUsersList() {
return (
<>
<h5>{i18n.t("blocked_users")}</h5>
<h5>{I18NextService.i18n.t("blocked_users")}</h5>
<ul className="list-unstyled mb-0">
{this.state.personBlocks.map(pb => (
<li key={pb.target.id}>
@ -421,7 +421,7 @@ export class Settings extends Component<any, SettingsState> {
{ ctx: this, recipientId: pb.target.id },
this.handleUnblockPerson
)}
data-tippy-content={i18n.t("unblock_user")}
data-tippy-content={I18NextService.i18n.t("unblock_user")}
>
<Icon icon="x" classes="icon-inline" />
</button>
@ -453,7 +453,7 @@ export class Settings extends Component<any, SettingsState> {
blockedCommunitiesList() {
return (
<>
<h5>{i18n.t("blocked_communities")}</h5>
<h5>{I18NextService.i18n.t("blocked_communities")}</h5>
<ul className="list-unstyled mb-0">
{this.state.communityBlocks.map(cb => (
<li key={cb.community.id}>
@ -465,7 +465,9 @@ export class Settings extends Component<any, SettingsState> {
{ ctx: this, communityId: cb.community.id },
this.handleUnblockCommunity
)}
data-tippy-content={i18n.t("unblock_community")}
data-tippy-content={I18NextService.i18n.t(
"unblock_community"
)}
>
<Icon icon="x" classes="icon-inline" />
</button>
@ -482,18 +484,18 @@ export class Settings extends Component<any, SettingsState> {
return (
<>
<h5>{i18n.t("settings")}</h5>
<h5>{I18NextService.i18n.t("settings")}</h5>
<form onSubmit={linkEvent(this, this.handleSaveSettingsSubmit)}>
<div className="mb-3 row">
<label className="col-sm-3 col-form-label" htmlFor="display-name">
{i18n.t("display_name")}
{I18NextService.i18n.t("display_name")}
</label>
<div className="col-sm-9">
<input
id="display-name"
type="text"
className="form-control"
placeholder={i18n.t("optional")}
placeholder={I18NextService.i18n.t("optional")}
value={this.state.saveUserSettingsForm.display_name}
onInput={linkEvent(this, this.handleDisplayNameChange)}
pattern="^(?!@)(.+)$"
@ -503,7 +505,7 @@ export class Settings extends Component<any, SettingsState> {
</div>
<div className="mb-3 row">
<label className="col-sm-3 col-form-label" htmlFor="user-bio">
{i18n.t("bio")}
{I18NextService.i18n.t("bio")}
</label>
<div className="col-sm-9">
<MarkdownTextArea
@ -518,14 +520,14 @@ export class Settings extends Component<any, SettingsState> {
</div>
<div className="mb-3 row">
<label className="col-sm-3 col-form-label" htmlFor="user-email">
{i18n.t("email")}
{I18NextService.i18n.t("email")}
</label>
<div className="col-sm-9">
<input
type="email"
id="user-email"
className="form-control"
placeholder={i18n.t("optional")}
placeholder={I18NextService.i18n.t("optional")}
value={this.state.saveUserSettingsForm.email}
onInput={linkEvent(this, this.handleEmailChange)}
minLength={3}
@ -535,7 +537,7 @@ export class Settings extends Component<any, SettingsState> {
<div className="mb-3 row">
<label className="col-sm-3 col-form-label" htmlFor="matrix-user-id">
<a href={elementUrl} rel={relTags}>
{i18n.t("matrix_user_id")}
{I18NextService.i18n.t("matrix_user_id")}
</a>
</label>
<div className="col-sm-9">
@ -552,11 +554,11 @@ export class Settings extends Component<any, SettingsState> {
</div>
<div className="mb-3 row">
<label className="col-sm-3 col-form-label">
{i18n.t("avatar")}
{I18NextService.i18n.t("avatar")}
</label>
<div className="col-sm-9">
<ImageUploadForm
uploadTitle={i18n.t("upload_avatar")}
uploadTitle={I18NextService.i18n.t("upload_avatar")}
imageSrc={this.state.saveUserSettingsForm.avatar}
onUpload={this.handleAvatarUpload}
onRemove={this.handleAvatarRemove}
@ -566,11 +568,11 @@ export class Settings extends Component<any, SettingsState> {
</div>
<div className="mb-3 row">
<label className="col-sm-3 col-form-label">
{i18n.t("banner")}
{I18NextService.i18n.t("banner")}
</label>
<div className="col-sm-9">
<ImageUploadForm
uploadTitle={i18n.t("upload_banner")}
uploadTitle={I18NextService.i18n.t("upload_banner")}
imageSrc={this.state.saveUserSettingsForm.banner}
onUpload={this.handleBannerUpload}
onRemove={this.handleBannerRemove}
@ -579,7 +581,7 @@ export class Settings extends Component<any, SettingsState> {
</div>
<div className="mb-3 row">
<label className="col-sm-3 form-label" htmlFor="user-language">
{i18n.t("interface_language")}
{I18NextService.i18n.t("interface_language")}
</label>
<div className="col-sm-9">
<select
@ -589,9 +591,11 @@ export class Settings extends Component<any, SettingsState> {
className="form-select d-inline-block w-auto"
>
<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 value="browser">{i18n.t("browser_default")}</option>
<option disabled aria-hidden="true">
</option>
@ -616,7 +620,7 @@ export class Settings extends Component<any, SettingsState> {
/>
<div className="mb-3 row">
<label className="col-sm-3 col-form-label" htmlFor="user-theme">
{i18n.t("theme")}
{I18NextService.i18n.t("theme")}
</label>
<div className="col-sm-9">
<select
@ -626,9 +630,11 @@ export class Settings extends Component<any, SettingsState> {
className="form-select d-inline-block w-auto"
>
<option disabled aria-hidden="true">
{i18n.t("theme")}
{I18NextService.i18n.t("theme")}
</option>
<option value="browser">
{I18NextService.i18n.t("browser_default")}
</option>
<option value="browser">{i18n.t("browser_default")}</option>
{this.state.themeList.map(theme => (
<option key={theme} value={theme}>
{theme}
@ -638,7 +644,9 @@ export class Settings extends Component<any, SettingsState> {
</div>
</div>
<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">
<ListingTypeSelect
type_={
@ -653,7 +661,7 @@ export class Settings extends Component<any, SettingsState> {
</form>
<form className="mb-3 row">
<label className="col-sm-3 col-form-label">
{i18n.t("sort_type")}
{I18NextService.i18n.t("sort_type")}
</label>
<div className="col-sm-9">
<SortSelect
@ -674,7 +682,7 @@ export class Settings extends Component<any, SettingsState> {
onChange={linkEvent(this, this.handleShowNsfwChange)}
/>
<label className="form-check-label" htmlFor="user-show-nsfw">
{i18n.t("show_nsfw")}
{I18NextService.i18n.t("show_nsfw")}
</label>
</div>
</div>
@ -688,7 +696,7 @@ export class Settings extends Component<any, SettingsState> {
onChange={linkEvent(this, this.handleShowScoresChange)}
/>
<label className="form-check-label" htmlFor="user-show-scores">
{i18n.t("show_scores")}
{I18NextService.i18n.t("show_scores")}
</label>
</div>
</div>
@ -702,7 +710,7 @@ export class Settings extends Component<any, SettingsState> {
onChange={linkEvent(this, this.handleShowAvatarsChange)}
/>
<label className="form-check-label" htmlFor="user-show-avatars">
{i18n.t("show_avatars")}
{I18NextService.i18n.t("show_avatars")}
</label>
</div>
</div>
@ -716,7 +724,7 @@ export class Settings extends Component<any, SettingsState> {
onChange={linkEvent(this, this.handleBotAccount)}
/>
<label className="form-check-label" htmlFor="user-bot-account">
{i18n.t("bot_account")}
{I18NextService.i18n.t("bot_account")}
</label>
</div>
</div>
@ -733,7 +741,7 @@ export class Settings extends Component<any, SettingsState> {
className="form-check-label"
htmlFor="user-show-bot-accounts"
>
{i18n.t("show_bot_accounts")}
{I18NextService.i18n.t("show_bot_accounts")}
</label>
</div>
</div>
@ -750,7 +758,7 @@ export class Settings extends Component<any, SettingsState> {
className="form-check-label"
htmlFor="user-show-read-posts"
>
{i18n.t("show_read_posts")}
{I18NextService.i18n.t("show_read_posts")}
</label>
</div>
</div>
@ -767,7 +775,7 @@ export class Settings extends Component<any, SettingsState> {
className="form-check-label"
htmlFor="user-show-new-post-notifs"
>
{i18n.t("show_new_post_notifs")}
{I18NextService.i18n.t("show_new_post_notifs")}
</label>
</div>
</div>
@ -790,7 +798,7 @@ export class Settings extends Component<any, SettingsState> {
className="form-check-label"
htmlFor="user-send-notifications-to-email"
>
{i18n.t("send_notifications_to_email")}
{I18NextService.i18n.t("send_notifications_to_email")}
</label>
</div>
</div>
@ -800,7 +808,7 @@ export class Settings extends Component<any, SettingsState> {
{this.state.saveRes.state === "loading" ? (
<Spinner />
) : (
capitalizeFirstLetter(i18n.t("save"))
capitalizeFirstLetter(I18NextService.i18n.t("save"))
)}
</button>
</div>
@ -813,12 +821,12 @@ export class Settings extends Component<any, SettingsState> {
this.handleDeleteAccountShowConfirmToggle
)}
>
{i18n.t("delete_account")}
{I18NextService.i18n.t("delete_account")}
</button>
{this.state.deleteAccountShowConfirm && (
<>
<div className="my-2 alert alert-danger" role="alert">
{i18n.t("delete_account_confirm")}
{I18NextService.i18n.t("delete_account_confirm")}
</div>
<input
type="password"
@ -839,7 +847,7 @@ export class Settings extends Component<any, SettingsState> {
{this.state.deleteAccountRes.state === "loading" ? (
<Spinner />
) : (
capitalizeFirstLetter(i18n.t("delete"))
capitalizeFirstLetter(I18NextService.i18n.t("delete"))
)}
</button>
<button
@ -849,7 +857,7 @@ export class Settings extends Component<any, SettingsState> {
this.handleDeleteAccountShowConfirmToggle
)}
>
{i18n.t("cancel")}
{I18NextService.i18n.t("cancel")}
</button>
</>
)}
@ -876,7 +884,7 @@ export class Settings extends Component<any, SettingsState> {
onChange={linkEvent(this, this.handleGenerateTotp)}
/>
<label className="form-check-label" htmlFor="user-generate-totp">
{i18n.t("set_up_two_factor")}
{I18NextService.i18n.t("set_up_two_factor")}
</label>
</div>
</div>
@ -886,7 +894,7 @@ export class Settings extends Component<any, SettingsState> {
<>
<div>
<a className="btn btn-secondary mb-2" href={totpUrl}>
{i18n.t("two_factor_link")}
{I18NextService.i18n.t("two_factor_link")}
</a>
</div>
<div className="input-group mb-3">
@ -901,7 +909,7 @@ export class Settings extends Component<any, SettingsState> {
onChange={linkEvent(this, this.handleRemoveTotp)}
/>
<label className="form-check-label" htmlFor="user-remove-totp">
{i18n.t("remove_two_factor")}
{I18NextService.i18n.t("remove_two_factor")}
</label>
</div>
</div>
@ -1050,7 +1058,7 @@ export class Settings extends Component<any, SettingsState> {
// Coerce false to undefined here, so it won't generate it.
const checked: boolean | undefined = event.target.checked || undefined;
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));
}
@ -1078,7 +1086,9 @@ export class Settings extends Component<any, SettingsState> {
handleInterfaceLangChange(i: Settings, event: any) {
const newLang = event.target.value ?? "browser";
i18n.changeLanguage(newLang === "browser" ? navigator.languages : newLang);
I18NextService.i18n.changeLanguage(
newLang === "browser" ? navigator.languages : newLang
);
i.setState(
s => ((s.saveUserSettingsForm.interface_language = event.target.value), s)
@ -1168,7 +1178,7 @@ export class Settings extends Component<any, SettingsState> {
if (saveRes.state === "success") {
UserService.Instance.login(saveRes.data);
location.reload();
toast(i18n.t("saved"));
toast(I18NextService.i18n.t("saved"));
window.scrollTo(0, 0);
}
@ -1191,7 +1201,7 @@ export class Settings extends Component<any, SettingsState> {
if (changePasswordRes.state === "success") {
UserService.Instance.login(changePasswordRes.data);
window.scrollTo(0, 0);
toast(i18n.t("password_changed"));
toast(I18NextService.i18n.t("password_changed"));
}
i.setState({ changePasswordRes });

View file

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

View file

@ -11,9 +11,8 @@ import {
GetSiteResponse,
ListCommunitiesResponse,
} from "lemmy-js-client";
import { i18n } from "../../i18next";
import { InitialFetchRequest, PostFormParams } from "../../interfaces";
import { FirstLoadService } from "../../services/FirstLoadService";
import { FirstLoadService, I18NextService } from "../../services";
import {
HttpService,
RequestState,
@ -143,7 +142,7 @@ export class CreatePost extends Component<
}
get documentTitle(): string {
return `${i18n.t("create_post")} - ${
return `${I18NextService.i18n.t("create_post")} - ${
this.state.siteRes.site_view.site.name
}`;
}
@ -171,7 +170,7 @@ export class CreatePost extends Component<
id="createPostForm"
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
onCreate={this.handlePostCreate}
params={locationState}

View file

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

View file

@ -31,9 +31,8 @@ import {
trendingFetchLimit,
webArchiveUrl,
} from "../../config";
import { i18n } from "../../i18next";
import { PostFormParams } from "../../interfaces";
import { UserService } from "../../services";
import { I18NextService, UserService } from "../../services";
import { HttpService, RequestState } from "../../services/HttpService";
import { setupTippy } from "../../tippy";
import { toast } from "../../toast";
@ -342,7 +341,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
/>
<div className="mb-3 row">
<label className="col-sm-2 col-form-label" htmlFor="post-url">
{i18n.t("url")}
{I18NextService.i18n.t("url")}
</label>
<div className="col-sm-10">
<input
@ -360,7 +359,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
className={`${
UserService.Instance.myUserInfo && "pointer"
} 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" />
</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"
rel={relTags}
>
archive.org {i18n.t("archive_link")}
archive.org {I18NextService.i18n.t("archive_link")}
</a>
<a
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"
rel={relTags}
>
ghostarchive.org {i18n.t("archive_link")}
ghostarchive.org {I18NextService.i18n.t("archive_link")}
</a>
<a
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"
rel={relTags}
>
archive.today {i18n.t("archive_link")}
archive.today {I18NextService.i18n.t("archive_link")}
</a>
</div>
)}
@ -411,17 +410,17 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
<button
className="btn btn-danger btn-sm mt-2"
onClick={linkEvent(this, handleImageDelete)}
aria-label={i18n.t("delete")}
data-tippy-content={i18n.t("delete")}
aria-label={I18NextService.i18n.t("delete")}
data-tippy-content={I18NextService.i18n.t("delete")}
>
<Icon icon="x" classes="icon-inline me-1" />
{capitalizeFirstLetter(i18n.t("delete"))}
{capitalizeFirstLetter(I18NextService.i18n.t("delete"))}
</button>
)}
{this.props.crossPosts && this.props.crossPosts.length > 0 && (
<>
<div className="my-1 text-muted small font-weight-bold">
{i18n.t("cross_posts")}
{I18NextService.i18n.t("cross_posts")}
</div>
<PostListings
showCommunity
@ -455,7 +454,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
</div>
<div className="mb-3 row">
<label className="col-sm-2 col-form-label" htmlFor="post-title">
{i18n.t("title")}
{I18NextService.i18n.t("title")}
</label>
<div className="col-sm-10">
<textarea
@ -472,7 +471,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
/>
{!validTitle(this.state.form.name) && (
<div className="invalid-feedback">
{i18n.t("invalid_post_title")}
{I18NextService.i18n.t("invalid_post_title")}
</div>
)}
{this.renderSuggestedPosts()}
@ -480,7 +479,9 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
</div>
<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">
<MarkdownTextArea
initialContent={this.state.form.body}
@ -501,7 +502,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
{!this.props.post_view && (
<div className="mb-3 row">
<label className="col-sm-2 col-form-label" htmlFor="post-community">
{i18n.t("community")}
{I18NextService.i18n.t("community")}
</label>
<div className="col-sm-10">
<SearchableSelect
@ -509,7 +510,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
value={this.state.form.community_id}
options={[
{
label: i18n.t("select_a_community"),
label: I18NextService.i18n.t("select_a_community"),
value: "",
disabled: true,
} as Choice,
@ -530,7 +531,9 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
checked={this.state.form.nsfw}
onChange={linkEvent(this, handlePostNsfwChange)}
/>
<label className="form-check-label">{i18n.t("nsfw")}</label>
<label className="form-check-label">
{I18NextService.i18n.t("nsfw")}
</label>
</div>
)}
<input
@ -553,9 +556,9 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
{this.state.loading ? (
<Spinner />
) : this.props.post_view ? (
capitalizeFirstLetter(i18n.t("save"))
capitalizeFirstLetter(I18NextService.i18n.t("save"))
) : (
capitalizeFirstLetter(i18n.t("create"))
capitalizeFirstLetter(I18NextService.i18n.t("create"))
)}
</button>
{this.props.post_view && (
@ -564,7 +567,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
className="btn btn-secondary"
onClick={linkEvent(this, handleCancel)}
>
{i18n.t("cancel")}
{I18NextService.i18n.t("cancel")}
</button>
)}
</div>
@ -590,7 +593,8 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
copySuggestedTitle
)}
>
{i18n.t("copy_suggested_title", { title: "" })} {suggestedTitle}
{I18NextService.i18n.t("copy_suggested_title", { title: "" })}{" "}
{suggestedTitle}
</div>
)
);
@ -610,7 +614,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
suggestedPosts.length > 0 && (
<>
<div className="my-1 text-muted small font-weight-bold">
{i18n.t("related_posts")}
{I18NextService.i18n.t("related_posts")}
</div>
<PostListings
showCommunity

View file

@ -1,6 +1,12 @@
import { myAuthRequired, newVote, showScores } from "@utils/app";
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 {
amAdmin,
@ -38,11 +44,9 @@ import {
TransferCommunity,
} from "lemmy-js-client";
import { relTags } from "../../config";
import { getExternalHost, getHttpBase } from "../../env";
import { i18n } from "../../i18next";
import { BanType, PostFormParams, PurgeType, VoteType } from "../../interfaces";
import { mdNoImages, mdToHtml, mdToHtmlInline } from "../../markdown";
import { UserService } from "../../services";
import { I18NextService, UserService } from "../../services";
import { setupTippy } from "../../tippy";
import { Icon, PurgeWarning, Spinner } from "../common/icon";
import { MomentTime } from "../common/moment-time";
@ -235,7 +239,8 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
}
get img() {
return this.imageSrc ? (
if (this.imageSrc) {
return (
<>
<div className="offset-sm-3 my-2 d-none d-sm-block">
<a href={this.imageSrc} className="d-inline-block">
@ -251,11 +256,25 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
</a>
</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) {
const post_view = this.postView;
return (
@ -298,9 +317,9 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
<a
href={this.imageSrc}
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)}
aria-label={i18n.t("expand_here")}
aria-label={I18NextService.i18n.t("expand_here")}
>
{this.imgThumb(this.imageSrc)}
<Icon icon="image" classes="mini-overlay" />
@ -321,17 +340,19 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
} else if (url) {
if (!this.props.hideImage && isVideo(url)) {
return (
<div className="embed-responsive embed-responsive-16by9">
<video
playsInline
muted
loop
controls
className="embed-responsive-item"
<a
className="text-body"
href={url}
title={url}
rel={relTags}
data-tippy-content={I18NextService.i18n.t("expand_here")}
onClick={linkEvent(this, this.handleImageExpandClick)}
aria-label={I18NextService.i18n.t("expand_here")}
>
<source src={url} type="video/mp4" />
</video>
<div className="thumbnail rounded bg-light d-flex justify-content-center">
<Icon icon="play" classes="d-flex align-items-center" />
</div>
</a>
);
} else {
return (
@ -347,7 +368,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
<Link
className="text-body"
to={`/post/${post.id}`}
title={i18n.t("comments")}
title={I18NextService.i18n.t("comments")}
>
<div className="thumbnail rounded bg-light d-flex justify-content-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">
<PersonListing person={post_view.creator} />
{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_ && (
<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 && (
<span className="mx-1 badge text-bg-light">
{i18n.t("bot_account").toLowerCase()}
{I18NextService.i18n.t("bot_account").toLowerCase()}
</span>
)}
{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 && (
@ -405,8 +431,8 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
this.postView.my_vote == 1 ? "text-info" : "text-muted"
}`}
onClick={linkEvent(this, this.handleUpvote)}
data-tippy-content={i18n.t("upvote")}
aria-label={i18n.t("upvote")}
data-tippy-content={I18NextService.i18n.t("upvote")}
aria-label={I18NextService.i18n.t("upvote")}
aria-pressed={this.postView.my_vote === 1}
>
{this.state.upvoteLoading ? (
@ -431,8 +457,8 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
this.postView.my_vote == -1 ? "text-danger" : "text-muted"
}`}
onClick={linkEvent(this, this.handleDownvote)}
data-tippy-content={i18n.t("downvote")}
aria-label={i18n.t("downvote")}
data-tippy-content={I18NextService.i18n.t("downvote")}
aria-label={I18NextService.i18n.t("downvote")}
aria-pressed={this.postView.my_vote === -1}
>
{this.state.downvoteLoading ? (
@ -456,7 +482,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
: "text-primary"
}`}
to={`/post/${post.id}`}
title={i18n.t("comments")}
title={I18NextService.i18n.t("comments")}
>
<span
className="d-inline"
@ -494,7 +520,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
(post.thumbnail_url && (
<button
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)}
>
<Icon
@ -507,13 +533,13 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
))}
{post.removed && (
<small className="ms-2 badge text-bg-secondary">
{i18n.t("removed")}
{I18NextService.i18n.t("removed")}
</small>
)}
{post.deleted && (
<small
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" />
</small>
@ -521,7 +547,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
{post.locked && (
<small
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" />
</small>
@ -529,8 +555,10 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
{post.featured_community && (
<small
className="unselectable pointer ms-2 text-muted font-italic"
data-tippy-content={i18n.t("featured_in_community")}
aria-label={i18n.t("featured_in_community")}
data-tippy-content={I18NextService.i18n.t(
"featured_in_community"
)}
aria-label={I18NextService.i18n.t("featured_in_community")}
>
<Icon icon="pin" classes="icon-inline text-primary" />
</small>
@ -538,15 +566,15 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
{post.featured_local && (
<small
className="unselectable pointer ms-2 text-muted font-italic"
data-tippy-content={i18n.t("featured_in_local")}
aria-label={i18n.t("featured_in_local")}
data-tippy-content={I18NextService.i18n.t("featured_in_local")}
aria-label={I18NextService.i18n.t("featured_in_local")}
>
<Icon icon="pin" classes="icon-inline text-secondary" />
</small>
)}
{post.nsfw && (
<small className="ms-2 badge text-bg-danger">
{i18n.t("nsfw")}
{I18NextService.i18n.t("nsfw")}
</small>
)}
</div>
@ -580,7 +608,9 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
return dupes && dupes.length > 0 ? (
<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 => (
<li key={pv.post.id} className="list-inline-item me-2">
<Link to={`/post/${pv.post.id}`}>
@ -615,7 +645,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
{!post.local && (
<a
className="btn btn-sm btn-animate text-muted py-0"
title={i18n.t("link")}
title={I18NextService.i18n.t("link")}
href={post.ap_id}
>
<Icon icon="fedilink" inline />
@ -674,11 +704,11 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
<button
className="btn btn-sm btn-animate text-muted py-0 dropdown-toggle"
onClick={linkEvent(this, this.handleShowAdvanced)}
data-tippy-content={i18n.t("more")}
data-tippy-content={I18NextService.i18n.t("more")}
data-bs-toggle="dropdown"
aria-expanded="false"
aria-controls="advancedButtonsDropdown"
aria-label={i18n.t("more")}
aria-label={I18NextService.i18n.t("more")}
>
<Icon icon="more-vertical" inline />
</button>
@ -718,7 +748,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
get commentsButton() {
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),
formattedCount: Number(post_view.counts.comments),
});
@ -734,7 +764,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
{post_view.counts.comments}
{this.unreadCount && (
<span className="text-muted fst-italic">
({this.unreadCount} {i18n.t("new")})
({this.unreadCount} {I18NextService.i18n.t("new")})
</span>
)}
</Link>
@ -762,7 +792,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
}`}
{...tippy}
onClick={linkEvent(this, this.handleUpvote)}
aria-label={i18n.t("upvote")}
aria-label={I18NextService.i18n.t("upvote")}
aria-pressed={this.postView.my_vote === 1}
>
{this.state.upvoteLoading ? (
@ -785,7 +815,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
}`}
onClick={linkEvent(this, this.handleDownvote)}
{...tippy}
aria-label={i18n.t("downvote")}
aria-label={I18NextService.i18n.t("downvote")}
aria-pressed={this.postView.my_vote === -1}
>
{this.state.downvoteLoading ? (
@ -813,7 +843,9 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
get saveButton() {
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 (
<button
className="btn btn-sm btn-animate text-muted py-0"
@ -846,9 +878,9 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
key: "",
search: "",
}}
title={i18n.t("cross_post")}
data-tippy-content={i18n.t("cross_post")}
aria-label={i18n.t("cross_post")}
title={I18NextService.i18n.t("cross_post")}
data-tippy-content={I18NextService.i18n.t("cross_post")}
aria-label={I18NextService.i18n.t("cross_post")}
>
<Icon icon="copy" inline />
</Link>
@ -860,10 +892,10 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
<button
className="btn btn-link btn-sm d-flex align-items-center rounded-0 dropdown-item"
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 />
{i18n.t("create_report")}
{I18NextService.i18n.t("create_report")}
</button>
);
}
@ -873,14 +905,14 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
<button
className="btn btn-link btn-sm d-flex align-items-center rounded-0 dropdown-item"
onClick={linkEvent(this, this.handleBlockPersonClick)}
aria-label={i18n.t("block_user")}
aria-label={I18NextService.i18n.t("block_user")}
>
{this.state.blockLoading ? (
<Spinner />
) : (
<Icon classes="me-1" icon="slash" inline />
)}
{i18n.t("block_user")}
{I18NextService.i18n.t("block_user")}
</button>
);
}
@ -890,17 +922,19 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
<button
className="btn btn-link btn-sm d-flex align-items-center rounded-0 dropdown-item"
onClick={linkEvent(this, this.handleEditClick)}
aria-label={i18n.t("edit")}
aria-label={I18NextService.i18n.t("edit")}
>
<Icon classes="me-1" icon="edit" inline />
{i18n.t("edit")}
{I18NextService.i18n.t("edit")}
</button>
);
}
get deleteButton() {
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 (
<button
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
className="btn btn-sm btn-animate text-muted py-0"
onClick={linkEvent(this, this.handleViewSource)}
data-tippy-content={i18n.t("view_source")}
aria-label={i18n.t("view_source")}
data-tippy-content={I18NextService.i18n.t("view_source")}
aria-label={I18NextService.i18n.t("view_source")}
>
<Icon
icon="file-text"
@ -942,7 +976,9 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
get lockButton() {
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 (
<button
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 })}
inline
/>
{label}
{capitalizeFirstLetter(label)}
</>
)}
</button>
@ -968,13 +1004,13 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
get featureButtons() {
const featuredCommunity = this.postView.post.featured_community;
const labelCommunity = featuredCommunity
? i18n.t("unfeature_from_community")
: i18n.t("feature_in_community");
? I18NextService.i18n.t("unfeature_from_community")
: I18NextService.i18n.t("feature_in_community");
const featuredLocal = this.postView.post.featured_local;
const labelLocal = featuredLocal
? i18n.t("unfeature_from_local")
: i18n.t("feature_in_local");
? I18NextService.i18n.t("unfeature_from_local")
: I18NextService.i18n.t("feature_in_local");
return (
<>
<li>
@ -995,7 +1031,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
})}
inline
/>
{i18n.t("community")}
{I18NextService.i18n.t("community")}
</>
)}
</button>
@ -1019,7 +1055,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
})}
inline
/>
{i18n.t("local")}
{I18NextService.i18n.t("local")}
</>
)}
</button>
@ -1043,9 +1079,9 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
{this.state.removeLoading ? (
<Spinner />
) : !removed ? (
i18n.t("remove")
I18NextService.i18n.t("remove")
) : (
i18n.t("restore")
I18NextService.i18n.t("restore")
)}
</button>
);
@ -1070,9 +1106,9 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
this,
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
@ -1081,9 +1117,13 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
this,
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>
))}
{!post_view.creator_banned_from_community && (
@ -1092,16 +1132,16 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
onClick={linkEvent(this, this.handleAddModToCommunity)}
aria-label={
this.creatorIsMod_
? i18n.t("remove_as_mod")
: i18n.t("appoint_as_mod")
? I18NextService.i18n.t("remove_as_mod")
: I18NextService.i18n.t("appoint_as_mod")
}
>
{this.state.addModLoading ? (
<Spinner />
) : 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>
)}
@ -1118,24 +1158,28 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
this,
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
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
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)}
>
{this.state.transferLoading ? <Spinner /> : i18n.t("yes")}
{this.state.transferLoading ? (
<Spinner />
) : (
I18NextService.i18n.t("yes")
)}
</button>
<button
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.handleCancelShowConfirmTransferCommunity
)}
aria-label={i18n.t("no")}
aria-label={I18NextService.i18n.t("no")}
>
{i18n.t("no")}
{I18NextService.i18n.t("no")}
</button>
</>
))}
@ -1158,36 +1202,36 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
<button
className="btn btn-link btn-animate text-muted py-0"
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
className="btn btn-link btn-animate text-muted py-0"
onClick={linkEvent(this, this.handleModBanSubmit)}
aria-label={i18n.t("unban_from_site")}
aria-label={I18NextService.i18n.t("unban_from_site")}
>
{this.state.banLoading ? (
<Spinner />
) : (
i18n.t("unban_from_site")
I18NextService.i18n.t("unban_from_site")
)}
</button>
)}
<button
className="btn btn-link btn-animate text-muted py-0"
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
className="btn btn-link btn-animate text-muted py-0"
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>
</>
)}
@ -1197,16 +1241,16 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
onClick={linkEvent(this, this.handleAddAdmin)}
aria-label={
this.creatorIsAdmin_
? i18n.t("remove_as_admin")
: i18n.t("appoint_as_admin")
? I18NextService.i18n.t("remove_as_admin")
: I18NextService.i18n.t("appoint_as_admin")
}
>
{this.state.addAdminLoading ? (
<Spinner />
) : 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>
)}
@ -1221,8 +1265,8 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
const post = this.postView;
const purgeTypeText =
this.state.purgeType == PurgeType.Post
? i18n.t("purge_post")
: `${i18n.t("purge")} ${post.creator.name}`;
? I18NextService.i18n.t("purge_post")
: `${I18NextService.i18n.t("purge")} ${post.creator.name}`;
return (
<>
{this.state.showRemoveDialog && (
@ -1234,22 +1278,26 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
className="visually-hidden"
htmlFor="post-listing-remove-reason"
>
{i18n.t("reason")}
{I18NextService.i18n.t("reason")}
</label>
<input
type="text"
id="post-listing-remove-reason"
className="form-control me-2"
placeholder={i18n.t("reason")}
placeholder={I18NextService.i18n.t("reason")}
value={this.state.removeReason}
onInput={linkEvent(this, this.handleModRemoveReasonChange)}
/>
<button
type="submit"
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>
</form>
)}
@ -1260,24 +1308,24 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
className="col-form-label"
htmlFor="post-listing-ban-reason"
>
{i18n.t("reason")}
{I18NextService.i18n.t("reason")}
</label>
<input
type="text"
id="post-listing-ban-reason"
className="form-control me-2"
placeholder={i18n.t("reason")}
placeholder={I18NextService.i18n.t("reason")}
value={this.state.banReason}
onInput={linkEvent(this, this.handleModBanReasonChange)}
/>
<label className="col-form-label" htmlFor={`mod-ban-expires`}>
{i18n.t("expires")}
{I18NextService.i18n.t("expires")}
</label>
<input
type="number"
id={`mod-ban-expires`}
className="form-control me-2"
placeholder={i18n.t("number_of_days")}
placeholder={I18NextService.i18n.t("number_of_days")}
value={this.state.banExpireDays}
onInput={linkEvent(this, this.handleModBanExpireDaysChange)}
/>
@ -1293,9 +1341,9 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
<label
className="form-check-label"
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>
</div>
</div>
@ -1303,19 +1351,19 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
{/* TODO hold off on expires until later */}
{/* <div class="mb-3 row"> */}
{/* <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 className="mb-3 row">
<button
type="submit"
className="btn btn-secondary"
aria-label={i18n.t("ban")}
aria-label={I18NextService.i18n.t("ban")}
>
{this.state.banLoading ? (
<Spinner />
) : (
<span>
{i18n.t("ban")} {post.creator.name}
{I18NextService.i18n.t("ban")} {post.creator.name}
</span>
)}
</button>
@ -1328,13 +1376,13 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
onSubmit={linkEvent(this, this.handleReportSubmit)}
>
<label className="visually-hidden" htmlFor="post-report-reason">
{i18n.t("reason")}
{I18NextService.i18n.t("reason")}
</label>
<input
type="text"
id="post-report-reason"
className="form-control me-2"
placeholder={i18n.t("reason")}
placeholder={I18NextService.i18n.t("reason")}
required
value={this.state.reportReason}
onInput={linkEvent(this, this.handleReportReasonChange)}
@ -1342,9 +1390,13 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
<button
type="submit"
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>
</form>
)}
@ -1355,13 +1407,13 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
>
<PurgeWarning />
<label className="visually-hidden" htmlFor="purge-reason">
{i18n.t("reason")}
{I18NextService.i18n.t("reason")}
</label>
<input
type="text"
id="purge-reason"
className="form-control me-2"
placeholder={i18n.t("reason")}
placeholder={I18NextService.i18n.t("reason")}
value={this.state.purgeReason}
onInput={linkEvent(this, this.handlePurgeReasonChange)}
/>
@ -1557,10 +1609,9 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
const body = post.body;
return body
? `${i18n.t("cross_posted_from")} ${post.ap_id}\n\n${body.replace(
/^/gm,
"> "
)}`
? `${I18NextService.i18n.t("cross_posted_from")} ${
post.ap_id
}\n\n${body.replace(/^/gm, "> ")}`
: undefined;
}
@ -1822,17 +1873,17 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
}
get pointsTippy(): string {
const points = i18n.t("number_of_points", {
const points = I18NextService.i18n.t("number_of_points", {
count: 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),
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),
formattedCount: Number(this.postView.counts.downvotes),
});

View file

@ -21,7 +21,7 @@ import {
SavePost,
TransferCommunity,
} from "lemmy-js-client";
import { i18n } from "../../i18next";
import { I18NextService } from "../../services";
import { PostListing } from "./post-listing";
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 && (
<T i18nKey="subscribe_to_communities">
#<Link to="/communities">#</Link>

View file

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

View file

@ -76,14 +76,12 @@ import {
TransferCommunity,
} from "lemmy-js-client";
import { commentTreeMaxDepth } from "../../config";
import { i18n } from "../../i18next";
import {
CommentNodeI,
CommentViewType,
InitialFetchRequest,
} from "../../interfaces";
import { UserService } from "../../services";
import { FirstLoadService } from "../../services/FirstLoadService";
import { FirstLoadService, I18NextService, UserService } from "../../services";
import { HttpService, RequestState } from "../../services/HttpService";
import { setupTippy } from "../../tippy";
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"
onClick={linkEvent(this, this.handleShowSidebarMobile)}
>
{i18n.t("sidebar")}{" "}
{I18NextService.i18n.t("sidebar")}{" "}
<Icon
icon={
this.state.showSidebarMobile
@ -438,7 +436,7 @@ export class Post extends Component<any, PostState> {
this.state.commentSort === "Hot" && "active"
}`}
>
{i18n.t("hot")}
{I18NextService.i18n.t("hot")}
<input
type="radio"
className="btn-check"
@ -452,7 +450,7 @@ export class Post extends Component<any, PostState> {
this.state.commentSort === "Top" && "active"
}`}
>
{i18n.t("top")}
{I18NextService.i18n.t("top")}
<input
type="radio"
className="btn-check"
@ -466,7 +464,7 @@ export class Post extends Component<any, PostState> {
this.state.commentSort === "New" && "active"
}`}
>
{i18n.t("new")}
{I18NextService.i18n.t("new")}
<input
type="radio"
className="btn-check"
@ -480,7 +478,7 @@ export class Post extends Component<any, PostState> {
this.state.commentSort === "Old" && "active"
}`}
>
{i18n.t("old")}
{I18NextService.i18n.t("old")}
<input
type="radio"
className="btn-check"
@ -496,7 +494,7 @@ export class Post extends Component<any, PostState> {
this.state.commentViewType === CommentViewType.Flat && "active"
}`}
>
{i18n.t("chat")}
{I18NextService.i18n.t("chat")}
<input
type="radio"
className="btn-check"
@ -595,14 +593,14 @@ export class Post extends Component<any, PostState> {
className="ps-0 d-block btn btn-link text-muted"
onClick={linkEvent(this, this.handleViewPost)}
>
{i18n.t("view_all_comments")}
{I18NextService.i18n.t("view_all_comments")}
</button>
{showContextButton && (
<button
className="ps-0 d-block btn btn-link text-muted"
onClick={linkEvent(this, this.handleViewContext)}
>
{i18n.t("show_context")}
{I18NextService.i18n.t("show_context")}
</button>
)}
</>
@ -836,14 +834,14 @@ export class Post extends Component<any, PostState> {
async handleCommentReport(form: CreateCommentReport) {
const reportRes = await HttpService.client.createCommentReport(form);
if (reportRes.state == "success") {
toast(i18n.t("report_created"));
toast(I18NextService.i18n.t("report_created"));
}
}
async handlePostReport(form: CreatePostReport) {
const reportRes = await HttpService.client.createPostReport(form);
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>) {
if (purgeRes.state == "success") {
toast(i18n.t("purge_success"));
toast(I18NextService.i18n.t("purge_success"));
this.context.router.history.push(`/`);
}
}

View file

@ -7,9 +7,8 @@ import {
GetPersonDetailsResponse,
GetSiteResponse,
} from "lemmy-js-client";
import { i18n } from "../../i18next";
import { InitialFetchRequest } from "../../interfaces";
import { FirstLoadService } from "../../services/FirstLoadService";
import { FirstLoadService, I18NextService } from "../../services";
import { HttpService, RequestState } from "../../services/HttpService";
import { toast } from "../../toast";
import { HtmlTags } from "../common/html-tags";
@ -97,7 +96,7 @@ export class CreatePrivateMessage extends Component<
get documentTitle(): string {
if (this.state.recipientRes.state == "success") {
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 {
return "";
}
@ -116,7 +115,7 @@ export class CreatePrivateMessage extends Component<
return (
<div className="row">
<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
onCreate={this.handlePrivateMessageCreate}
recipient={res.person_view.person}
@ -144,7 +143,7 @@ export class CreatePrivateMessage extends Component<
const res = await HttpService.client.createPrivateMessage(form);
if (res.state == "success") {
toast(i18n.t("message_sent"));
toast(I18NextService.i18n.t("message_sent"));
// Navigate to the front
this.context.router.history.push("/");

View file

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

View file

@ -5,8 +5,8 @@ import {
PrivateMessageReportView,
ResolvePrivateMessageReport,
} from "lemmy-js-client";
import { i18n } from "../../i18next";
import { mdToHtml } from "../../markdown";
import { I18NextService } from "../../services";
import { Icon, Spinner } from "../common/icon";
import { PersonListing } from "../person/person-listing";
@ -39,28 +39,29 @@ export class PrivateMessageReport extends Component<Props, State> {
render() {
const r = this.props.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"
);
return (
<div className="private-message-report">
<div>
{i18n.t("creator")}:{" "}
{I18NextService.i18n.t("creator")}:{" "}
<PersonListing person={r.private_message_creator} />
</div>
<div>
{i18n.t("message")}:
{I18NextService.i18n.t("message")}:
<div
className="md-div"
dangerouslySetInnerHTML={mdToHtml(pmr.original_pm_text)}
/>
</div>
<div>
{i18n.t("reporter")}: <PersonListing person={r.creator} />
{I18NextService.i18n.t("reporter")}:{" "}
<PersonListing person={r.creator} />
</div>
<div>
{i18n.t("reason")}: {pmr.reason}
{I18NextService.i18n.t("reason")}: {pmr.reason}
</div>
{r.resolver && (
<div>

View file

@ -9,9 +9,8 @@ import {
Person,
PrivateMessageView,
} from "lemmy-js-client";
import { i18n } from "../../i18next";
import { mdToHtml } from "../../markdown";
import { UserService } from "../../services";
import { I18NextService, UserService } from "../../services";
import { Icon, Spinner } from "../common/icon";
import { MomentTime } from "../common/moment-time";
import { PersonListing } from "../person/person-listing";
@ -94,7 +93,9 @@ export class PrivateMessage extends Component<
<ul className="list-inline mb-0 text-muted small">
{/* TODO refactor this */}
<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 className="list-inline-item">
<PersonListing person={otherPerson} />
@ -148,13 +149,13 @@ export class PrivateMessage extends Component<
onClick={linkEvent(this, this.handleMarkRead)}
data-tippy-content={
message_view.private_message.read
? i18n.t("mark_as_unread")
: i18n.t("mark_as_read")
? I18NextService.i18n.t("mark_as_unread")
: I18NextService.i18n.t("mark_as_read")
}
aria-label={
message_view.private_message.read
? i18n.t("mark_as_unread")
: i18n.t("mark_as_read")
? I18NextService.i18n.t("mark_as_unread")
: I18NextService.i18n.t("mark_as_read")
}
>
{this.state.readLoading ? (
@ -175,8 +176,8 @@ export class PrivateMessage extends Component<
<button
className="btn btn-link btn-animate text-muted"
onClick={linkEvent(this, this.handleReplyClick)}
data-tippy-content={i18n.t("reply")}
aria-label={i18n.t("reply")}
data-tippy-content={I18NextService.i18n.t("reply")}
aria-label={I18NextService.i18n.t("reply")}
>
<Icon icon="reply1" classes="icon-inline" />
</button>
@ -189,8 +190,8 @@ export class PrivateMessage extends Component<
<button
className="btn btn-link btn-animate text-muted"
onClick={linkEvent(this, this.handleEditClick)}
data-tippy-content={i18n.t("edit")}
aria-label={i18n.t("edit")}
data-tippy-content={I18NextService.i18n.t("edit")}
aria-label={I18NextService.i18n.t("edit")}
>
<Icon icon="edit" classes="icon-inline" />
</button>
@ -201,13 +202,13 @@ export class PrivateMessage extends Component<
onClick={linkEvent(this, this.handleDeleteClick)}
data-tippy-content={
!message_view.private_message.deleted
? i18n.t("delete")
: i18n.t("restore")
? I18NextService.i18n.t("delete")
: I18NextService.i18n.t("restore")
}
aria-label={
!message_view.private_message.deleted
? i18n.t("delete")
: i18n.t("restore")
? I18NextService.i18n.t("delete")
: I18NextService.i18n.t("restore")
}
>
{this.state.deleteLoading ? (
@ -229,8 +230,8 @@ export class PrivateMessage extends Component<
<button
className="btn btn-link btn-animate text-muted"
onClick={linkEvent(this, this.handleViewSource)}
data-tippy-content={i18n.t("view_source")}
aria-label={i18n.t("view_source")}
data-tippy-content={I18NextService.i18n.t("view_source")}
aria-label={I18NextService.i18n.t("view_source")}
>
<Icon
icon="file-text"
@ -250,13 +251,13 @@ export class PrivateMessage extends Component<
onSubmit={linkEvent(this, this.handleReportSubmit)}
>
<label className="visually-hidden" htmlFor="pm-report-reason">
{i18n.t("reason")}
{I18NextService.i18n.t("reason")}
</label>
<input
type="text"
id="pm-report-reason"
className="form-control me-2"
placeholder={i18n.t("reason")}
placeholder={I18NextService.i18n.t("reason")}
required
value={this.state.reportReason}
onInput={linkEvent(this, this.handleReportReasonChange)}
@ -264,9 +265,13 @@ export class PrivateMessage extends Component<
<button
type="submit"
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>
</form>
)}
@ -287,8 +292,8 @@ export class PrivateMessage extends Component<
<button
className="btn btn-link btn-animate text-muted py-0"
onClick={linkEvent(this, this.handleShowReportDialog)}
data-tippy-content={i18n.t("show_report_dialog")}
aria-label={i18n.t("show_report_dialog")}
data-tippy-content={I18NextService.i18n.t("show_report_dialog")}
aria-label={I18NextService.i18n.t("show_report_dialog")}
>
<Icon icon="flag" inline />
</button>
@ -297,7 +302,9 @@ export class PrivateMessage extends Component<
get messageUnlessRemoved(): string {
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) {

View file

@ -46,9 +46,8 @@ import {
SortType,
} from "lemmy-js-client";
import { fetchLimit } from "../config";
import { i18n } from "../i18next";
import { CommentViewType, InitialFetchRequest } from "../interfaces";
import { FirstLoadService } from "../services/FirstLoadService";
import { FirstLoadService, I18NextService } from "../services";
import { HttpService, RequestState } from "../services/HttpService";
import { CommentNodes } from "./comment/comment-nodes";
import { HtmlTags } from "./common/html-tags";
@ -184,13 +183,13 @@ const Filter = ({
return (
<div className="mb-3 col-sm-6">
<label className="col-form-label me-2" htmlFor={`${filterType}-filter`}>
{capitalizeFirstLetter(i18n.t(filterType))}
{capitalizeFirstLetter(I18NextService.i18n.t(filterType))}
</label>
<SearchableSelect
id={`${filterType}-filter`}
options={[
{
label: i18n.t("all"),
label: I18NextService.i18n.t("all"),
value: "0",
},
].concat(options)}
@ -228,7 +227,7 @@ function getListing(
return (
<>
<span>{listing}</span>
<span>{` - ${i18n.t(translationKey, {
<span>{` - ${I18NextService.i18n.t(translationKey, {
count: Number(count),
formattedCount: numToSI(count),
})}`}</span>
@ -448,7 +447,7 @@ export class Search extends Component<any, SearchState> {
get documentTitle(): string {
const { q } = getSearchQueryParams();
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() {
@ -460,13 +459,13 @@ export class Search extends Component<any, SearchState> {
title={this.documentTitle}
path={this.context.router.route.match.url}
/>
<h5>{i18n.t("search")}</h5>
<h5>{I18NextService.i18n.t("search")}</h5>
{this.selects}
{this.searchForm}
{this.displayResults(type)}
{this.resultsCount === 0 &&
this.state.searchRes.state === "success" && (
<span>{i18n.t("no_results")}</span>
<span>{I18NextService.i18n.t("no_results")}</span>
)}
<Paginator page={page} onChange={this.handlePageChange} />
</div>
@ -499,8 +498,8 @@ export class Search extends Component<any, SearchState> {
type="text"
className="form-control me-2 mb-2 col-sm-8"
value={this.state.searchText}
placeholder={`${i18n.t("search")}...`}
aria-label={i18n.t("search")}
placeholder={`${I18NextService.i18n.t("search")}...`}
aria-label={I18NextService.i18n.t("search")}
onInput={linkEvent(this, this.handleQChange)}
required
minLength={1}
@ -511,7 +510,7 @@ export class Search extends Component<any, SearchState> {
{this.state.searchRes.state === "loading" ? (
<Spinner />
) : (
<span>{i18n.t("search")}</span>
<span>{I18NextService.i18n.t("search")}</span>
)}
</button>
</div>
@ -540,14 +539,16 @@ export class Search extends Component<any, SearchState> {
value={type}
onChange={linkEvent(this, this.handleTypeChange)}
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">
{i18n.t("type")}
{I18NextService.i18n.t("type")}
</option>
{searchTypes.map(option => (
<option value={option} key={option}>
{i18n.t(option.toString().toLowerCase() as NoOptionI18nKeys)}
{I18NextService.i18n.t(
option.toString().toLowerCase() as NoOptionI18nKeys
)}
</option>
))}
</select>

View file

@ -24,3 +24,15 @@ export const updateUnreadCountsInterval = 30000;
export const fetchLimit = 40;
export const relTags = "noopener nofollow";
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 { default as MarkdownIt } from "markdown-it";
import markdown_it_container from "markdown-it-container";
import markdown_it_emoji from "markdown-it-emoji/bare";
// import markdown_it_emoji from "markdown-it-emoji/bare";
import markdown_it_footnote from "markdown-it-footnote";
import markdown_it_html5_embed from "markdown-it-html5-embed";
import markdown_it_sub from "markdown-it-sub";
import markdown_it_sup from "markdown-it-sup";
import Renderer from "markdown-it/lib/renderer";
import Token from "markdown-it/lib/token";
import { instanceLinkRegex } from "./config";
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() {
const markdownItConfig: MarkdownIt.Options = {
html: false,
@ -79,19 +149,20 @@ export function setupMarkdown() {
typographer: true,
};
const emojiDefs = Array.from(customEmojisLookup.entries()).reduce(
(main, [key, value]) => ({ ...main, [key]: value }),
{}
);
// const emojiDefs = Array.from(customEmojisLookup.entries()).reduce(
// (main, [key, value]) => ({ ...main, [key]: value }),
// {}
// );
md = new MarkdownIt(markdownItConfig)
.use(markdown_it_sub)
.use(markdown_it_sup)
.use(markdown_it_footnote)
.use(markdown_it_html5_embed, html5EmbedConfig)
.use(markdown_it_container, "spoiler", spoilerConfig)
.use(markdown_it_emoji, {
defs: emojiDefs,
});
.use(localInstanceLinkParser);
// .use(markdown_it_emoji, {
// defs: emojiDefs,
// });
mdNoImages = new MarkdownIt(markdownItConfig)
.use(markdown_it_sub)
@ -99,9 +170,10 @@ export function setupMarkdown() {
.use(markdown_it_footnote)
.use(markdown_it_html5_embed, html5EmbedConfig)
.use(markdown_it_container, "spoiler", spoilerConfig)
.use(markdown_it_emoji, {
defs: emojiDefs,
})
.use(localInstanceLinkParser)
// .use(markdown_it_emoji, {
// defs: emojiDefs,
// })
.disable("image");
const defaultRenderer = md.renderer.rules.image;
md.renderer.rules.image = function (

View file

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

View file

@ -1,37 +1,37 @@
import { isBrowser } from "@utils/browser";
import i18next, { i18nTyped, Resource } from "i18next";
import { UserService } from "./services/UserService";
import { ar } from "./translations/ar";
import { bg } from "./translations/bg";
import { ca } from "./translations/ca";
import { cs } from "./translations/cs";
import { da } from "./translations/da";
import { de } from "./translations/de";
import { el } from "./translations/el";
import { en } from "./translations/en";
import { eo } from "./translations/eo";
import { es } from "./translations/es";
import { eu } from "./translations/eu";
import { fa } from "./translations/fa";
import { fi } from "./translations/fi";
import { fr } from "./translations/fr";
import { ga } from "./translations/ga";
import { gl } from "./translations/gl";
import { hr } from "./translations/hr";
import { id } from "./translations/id";
import { it } from "./translations/it";
import { ja } from "./translations/ja";
import { ko } from "./translations/ko";
import { nl } from "./translations/nl";
import { oc } from "./translations/oc";
import { pl } from "./translations/pl";
import { pt } from "./translations/pt";
import { pt_BR } from "./translations/pt_BR";
import { ru } from "./translations/ru";
import { sv } from "./translations/sv";
import { vi } from "./translations/vi";
import { zh } from "./translations/zh";
import { zh_Hant } from "./translations/zh_Hant";
import i18next, { Resource } from "i18next";
import { UserService } from "../services";
import { ar } from "../translations/ar";
import { bg } from "../translations/bg";
import { ca } from "../translations/ca";
import { cs } from "../translations/cs";
import { da } from "../translations/da";
import { de } from "../translations/de";
import { el } from "../translations/el";
import { en } from "../translations/en";
import { eo } from "../translations/eo";
import { es } from "../translations/es";
import { eu } from "../translations/eu";
import { fa } from "../translations/fa";
import { fi } from "../translations/fi";
import { fr } from "../translations/fr";
import { ga } from "../translations/ga";
import { gl } from "../translations/gl";
import { hr } from "../translations/hr";
import { id } from "../translations/id";
import { it } from "../translations/it";
import { ja } from "../translations/ja";
import { ko } from "../translations/ko";
import { nl } from "../translations/nl";
import { oc } from "../translations/oc";
import { pl } from "../translations/pl";
import { pt } from "../translations/pt";
import { pt_BR } from "../translations/pt_BR";
import { ru } from "../translations/ru";
import { sv } from "../translations/sv";
import { vi } from "../translations/vi";
import { zh } from "../translations/zh";
import { zh_Hant } from "../translations/zh_Hant";
export const languages = [
{ 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,
compatibilityJSON: "v3",
supportedLngs: languages.map(l => l.code),
@ -103,5 +109,13 @@ i18next.use(LanguageDetector).init({
resources,
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 { isAuthPath } from "@utils/app";
import { isBrowser } from "@utils/browser";
import { isHttps } from "@utils/env";
import IsomorphicCookie from "isomorphic-cookie";
import jwt_decode from "jwt-decode";
import { LoginResponse, MyUserInfo } from "lemmy-js-client";
import { isHttps } from "../env";
import { i18n } from "../i18next";
import { toast } from "../toast";
import { I18NextService } from "./I18NextService";
interface Claims {
sub: number;
@ -32,7 +32,7 @@ export class UserService {
const expires = new Date();
expires.setDate(expires.getDate() + 365);
if (res.jwt) {
toast(i18n.t("logged_in"));
toast(I18NextService.i18n.t("logged_in"));
IsomorphicCookie.save("jwt", res.jwt, { expires, secure: isHttps() });
this.#setJwtInfo();
}
@ -58,7 +58,7 @@ export class UserService {
const msg = "No JWT cookie found";
if (throwErr && isBrowser()) {
console.error(msg);
toast(i18n.t("not_logged_in"), "danger");
toast(I18NextService.i18n.t("not_logged_in"), "danger");
}
return undefined;
// throw msg;

View file

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

View file

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

View file

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

View file

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

View file

@ -1,6 +1,5 @@
import { BlockCommunityResponse, MyUserInfo } from "lemmy-js-client";
import { i18n } from "../../i18next";
import { UserService } from "../../services";
import { I18NextService, UserService } from "../../services";
import { toast } from "../../toast";
export default function updateCommunityBlock(
@ -13,12 +12,20 @@ export default function updateCommunityBlock(
person: myUserInfo.local_user_view.person,
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 {
myUserInfo.community_blocks = myUserInfo.community_blocks.filter(
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 { i18n } from "../../i18next";
import { UserService } from "../../services";
import { I18NextService, UserService } from "../../services";
import { toast } from "../../toast";
export default function updatePersonBlock(
@ -13,12 +12,16 @@ export default function updatePersonBlock(
person: myUserInfo.local_user_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 {
myUserInfo.person_blocks = myUserInfo.person_blocks.filter(
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) {
const path: string = context.router.route.location.pathname;
const y = Number(sessionStorage.getItem(`scrollPosition_${path}`));
window.scrollTo(0, y);
}

View file

@ -1,5 +1,6 @@
export default function saveScrollPosition(context: any) {
const path: string = context.router.route.location.pathname;
const y = window.scrollY;
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";
}