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

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

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

View file

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

View file

@ -1,8 +1,8 @@
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 { initializeSite } from "../shared/utils";
import { HistoryService } from "../shared/services";
import "bootstrap/js/dist/collapse";
import "bootstrap/js/dist/dropdown";

View file

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

View file

@ -1,6 +1,6 @@
import type { Request, Response } from "express";
import { LemmyHttp } from "lemmy-js-client";
import { getHttpBaseInternal } from "../../shared/env";
import { getHttpBaseExternal, 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,9 +9,11 @@ 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(), headers));
const client = wrapClient(
new LemmyHttp(getHttpBaseInternal(), { fetchFunction: fetch, headers })
);
const site = await client.getSite({});
if (site.state === "success") {

View file

@ -25,7 +25,7 @@ if (!process.env["LEMMY_UI_DISABLE_CSP"] && !process.env["LEMMY_UI_DEBUG"]) {
server.get("/robots.txt", RobotsHandler);
server.get("/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

@ -2,8 +2,8 @@ import { Helmet } from "inferno-helmet";
import { renderToString } from "inferno-server";
import serialize from "serialize-javascript";
import sharp from "sharp";
import { favIconPngUrl, favIconUrl } from "../../shared/config";
import { ILemmyConfig, IsoDataOptionalSite } from "../../shared/interfaces";
import { favIconPngUrl, favIconUrl } from "../../shared/utils";
import { fetchIconPng } from "./fetch-icon-png";
const customHtmlHeader = process.env["LEMMY_UI_CUSTOM_HTML_HEADER"] || "";
@ -77,7 +77,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} />

View file

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

View file

@ -1,10 +1,10 @@
import { Component, createRef, linkEvent, RefObject } from "inferno";
import { isAuthPath, setIsoData } from "@utils/app";
import { Component, RefObject, createRef, linkEvent } from "inferno";
import { Provider } from "inferno-i18next-dess";
import { Route, Switch } from "inferno-router";
import { i18n } from "../../i18next";
import { IsoDataOptionalSite } from "../../interfaces";
import { routes } from "../../routes";
import { isAuthPath, setIsoData } from "../../utils";
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

@ -1,9 +1,9 @@
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 { setIsoData } from "../../utils";
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

@ -1,8 +1,8 @@
import { Component } from "inferno";
import { NavLink } from "inferno-router";
import { GetSiteResponse } from "lemmy-js-client";
import { i18n } from "../../i18next";
import { docsUrl, joinLemmyUrl, repoUrl } from "../../utils";
import { docsUrl, joinLemmyUrl, repoUrl } from "../../config";
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

@ -1,5 +1,6 @@
import { myAuth, showAvatars } from "@utils/app";
import { isBrowser } from "@utils/browser";
import { poll } from "@utils/helpers";
import { numToSI, poll } from "@utils/helpers";
import { amAdmin, canCreateCommunity } from "@utils/roles";
import { Component, createRef, linkEvent } from "inferno";
import { NavLink } from "inferno-router";
@ -9,17 +10,10 @@ import {
GetUnreadCountResponse,
GetUnreadRegistrationApplicationCountResponse,
} from "lemmy-js-client";
import { i18n } from "../../i18next";
import { UserService } from "../../services";
import { donateLemmyUrl, updateUnreadCountsInterval } from "../../config";
import { I18NextService, UserService } from "../../services";
import { HttpService, RequestState } from "../../services/HttpService";
import {
donateLemmyUrl,
myAuth,
numToSI,
showAvatars,
toast,
updateUnreadCountsInterval,
} from "../../utils";
import { toast } from "../../toast";
import { Icon } from "../common/icon";
import { PictrsImage } from "../common/pictrs-image";
@ -107,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),
})}
@ -126,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),
})}
@ -146,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", {
count: Number(this.unreadApplicationCount),
formattedCount: numToSI(this.unreadApplicationCount),
})}
title={I18NextService.i18n.t(
"unread_registration_applications",
{
count: Number(this.unreadApplicationCount),
formattedCount: numToSI(this.unreadApplicationCount),
}
)}
onMouseUp={linkEvent(this, handleCollapseClick)}
>
<Icon icon="clipboard" />
@ -167,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"
@ -186,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">
@ -203,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) && (
@ -214,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>
@ -239,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>
@ -253,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>
@ -269,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),
})}
@ -277,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),
})}
@ -294,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),
})}
@ -302,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),
})}
@ -320,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", {
count: Number(this.unreadApplicationCount),
formattedCount: numToSI(this.unreadApplicationCount),
})}
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", {
count: Number(this.unreadApplicationCount),
formattedCount: numToSI(this.unreadApplicationCount),
})}
{I18NextService.i18n.t(
"unread_registration_applications",
{
count: Number(this.unreadApplicationCount),
formattedCount: numToSI(
this.unreadApplicationCount
),
}
)}
</span>
{this.unreadApplicationCount > 0 && (
<span className="mx-1 badge text-bg-light">
@ -362,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>
@ -389,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>
@ -402,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>
</>
@ -509,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

@ -1,11 +1,11 @@
import { myAuthRequired } from "@utils/app";
import { capitalizeFirstLetter } from "@utils/helpers";
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 { capitalizeFirstLetter, myAuthRequired } from "../../utils";
import { I18NextService, UserService } from "../../services";
import { Icon } from "../common/icon";
import { MarkdownTextArea } from "../common/markdown-textarea";
@ -57,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}
/>
@ -78,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

@ -1,3 +1,12 @@
import {
colorList,
getCommentParentId,
myAuth,
myAuthRequired,
newVote,
showScores,
} from "@utils/app";
import { futureDaysToUnixTime, numToSI } from "@utils/helpers";
import {
amCommunityCreator,
canAdmin,
@ -38,7 +47,7 @@ import {
TransferCommunity,
} from "lemmy-js-client";
import moment from "moment";
import { i18n } from "../../i18next";
import { commentTreeMaxDepth } from "../../config";
import {
BanType,
CommentNodeI,
@ -46,21 +55,9 @@ import {
PurgeType,
VoteType,
} from "../../interfaces";
import { UserService } from "../../services";
import {
colorList,
commentTreeMaxDepth,
futureDaysToUnixTime,
getCommentParentId,
mdToHtml,
mdToHtmlNoImages,
myAuth,
myAuthRequired,
newVote,
numToSI,
setupTippy,
showScores,
} from "../../utils";
import { mdToHtml, mdToHtmlNoImages } from "../../markdown";
import { I18NextService, UserService } from "../../services";
import { setupTippy } from "../../tippy";
import { Icon, PurgeWarning, Spinner } from "../common/icon";
import { MomentTime } from "../common/moment-time";
import { CommunityLink } from "../community/community-link";
@ -243,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,
@ -316,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}`}>
@ -368,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
@ -428,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 ? (
@ -458,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 ? (
@ -485,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 ? (
@ -508,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>
@ -517,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>
@ -529,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>
@ -539,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>
@ -552,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 />
@ -567,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 ? (
@ -587,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"
@ -602,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>
@ -615,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 ? (
@ -645,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
@ -674,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
@ -685,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>
)}
@ -707,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
@ -718,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>
))}
@ -737,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"
@ -759,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
@ -773,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>
</>
))}
@ -792,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"
@ -810,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
@ -825,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>
</>
))}
@ -842,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"
@ -852,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) ? (
@ -864,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
@ -875,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>
)}
@ -897,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"
@ -916,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
@ -930,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>
</>
))}
@ -963,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
@ -985,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>
)}
@ -1013,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>
)}
@ -1040,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)}
/>
@ -1054,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)}
/>
@ -1076,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>
@ -1086,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>
@ -1110,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)}
/>
@ -1211,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;
@ -1257,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),
});
@ -1276,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

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

View file

@ -1,3 +1,4 @@
import { myAuthRequired } from "@utils/app";
import { Component, InfernoNode, linkEvent } from "inferno";
import { T } from "inferno-i18next-dess";
import {
@ -5,9 +6,8 @@ import {
CommentView,
ResolveCommentReport,
} from "lemmy-js-client";
import { i18n } from "../../i18next";
import { CommentNodeI, CommentViewType } from "../../interfaces";
import { myAuthRequired } from "../../utils";
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

@ -1,11 +1,11 @@
import { numToSI } from "@utils/helpers";
import { Link } from "inferno-router";
import {
CommunityAggregates,
CommunityId,
SiteAggregates,
} from "lemmy-js-client";
import { i18n } from "../../i18next";
import { numToSI } from "../../utils";
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", {
count: Number(counts.users_active_day),
formattedCount: numToSI(counts.users_active_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", {
count: Number(counts.users_active_week),
formattedCount: numToSI(counts.users_active_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", {
count: Number(counts.users_active_month),
formattedCount: numToSI(counts.users_active_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", {
count: Number(counts.users_active_half_year),
formattedCount: numToSI(counts.users_active_half_year),
})}
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

@ -1,7 +1,8 @@
import { randomStr } from "@utils/helpers";
import { Component, linkEvent } from "inferno";
import { CommentSortType } from "lemmy-js-client";
import { i18n } from "../../i18next";
import { randomStr, relTags, sortingHelpUrl } from "../../utils";
import { relTags, sortingHelpUrl } from "../../config";
import { I18NextService } from "../../services";
import { Icon } from "./icon";
interface CommentSortSelectProps {
@ -41,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 } from "inferno";
import { getEmojiMart } from "../../utils";
import { getEmojiMart } from "../../markdown";
interface EmojiMartProps {
onEmojiClick?(val: any): any;

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)}
>
@ -38,16 +38,18 @@ export class EmojiPicker extends Component<EmojiPickerProps, EmojiPickerState> {
{this.state.showPicker && (
<>
<div className="emoji-picker-container">
<EmojiMart
onEmojiClick={this.handleEmojiClick}
pickerOptions={{}}
></EmojiMart>
<div className="position-relative">
<div className="emoji-picker-container position-absolute w-100">
<EmojiMart
onEmojiClick={this.handleEmojiClick}
pickerOptions={{}}
></EmojiMart>
</div>
<div
onClick={linkEvent(this, this.togglePicker)}
className="click-away-container"
/>
</div>
<div
onClick={linkEvent(this, this.togglePicker)}
className="click-away-container"
/>
</>
)}
</span>

View file

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

View file

@ -2,8 +2,8 @@ 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 "../../utils";
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,7 @@
import { randomStr } from "@utils/helpers";
import { Component, linkEvent } from "inferno";
import { i18n } from "../../i18next";
import { HttpService, UserService } from "../../services";
import { randomStr, toast } from "../../utils";
import { HttpService, I18NextService, UserService } from "../../services";
import { toast } from "../../toast";
import { Icon } from "./icon";
interface ImageUploadFormProps {
@ -49,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

@ -1,9 +1,9 @@
import { selectableLanguages } from "@utils/app";
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 { randomStr, selectableLanguages } from "../../utils";
import { I18NextService, UserService } from "../../services";
import { Icon } from "./icon";
interface LanguageSelectProps {
@ -52,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">
@ -63,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}`, {
@ -102,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 { randomStr } from "../../utils";
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

@ -1,26 +1,21 @@
import { isBrowser } from "@utils/browser";
import { numToSI, randomStr } from "@utils/helpers";
import autosize from "autosize";
import classNames from "classnames";
import { NoOptionI18nKeys } from "i18next";
import { Component, linkEvent } from "inferno";
import { Language } from "lemmy-js-client";
import { i18n } from "../../i18next";
import { HttpService, UserService } from "../../services";
import {
concurrentImageUpload,
customEmojisLookup,
markdownFieldCharacterLimit,
markdownHelpUrl,
maxUploadImages,
mdToHtml,
numToSI,
pictrsDeleteToast,
randomStr,
relTags,
setupTippy,
setupTribute,
toast,
} from "../../utils";
} from "../../config";
import { customEmojisLookup, mdToHtml, setupTribute } from "../../markdown";
import { HttpService, I18NextService, UserService } from "../../services";
import { setupTippy } from "../../tippy";
import { pictrsDeleteToast, toast } from "../../toast";
import { EmojiPicker } from "./emoji-picker";
import { Icon, Spinner } from "./icon";
import { LanguageSelect } from "./language-select";
@ -133,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
@ -165,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 />
@ -203,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" />
@ -245,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", {
uploaded: this.state.imageUploadStatus.uploaded,
total: this.state.imageUploadStatus.total,
})}
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>
@ -294,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 && (
@ -304,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>
@ -336,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}
>
@ -380,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),
}),
@ -681,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 { capitalizeFirstLetter } from "../../utils";
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

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

View file

@ -1,11 +1,12 @@
import { myAuthRequired } from "@utils/app";
import { Component, InfernoNode, linkEvent } from "inferno";
import { T } from "inferno-i18next-dess";
import {
ApproveRegistrationApplication,
RegistrationApplicationView,
} from "lemmy-js-client";
import { i18n } from "../../i18next";
import { mdToHtml, myAuthRequired } from "../../utils";
import { mdToHtml } from "../../markdown";
import { I18NextService } from "../../services";
import { PersonListing } from "../person/person-listing";
import { Spinner } from "./icon";
import { MarkdownTextArea } from "./markdown-textarea";
@ -60,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 && (
@ -83,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)}
@ -98,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
@ -115,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

@ -1,3 +1,4 @@
import { Choice } from "@utils/types";
import classNames from "classnames";
import {
ChangeEvent,
@ -6,8 +7,7 @@ import {
linkEvent,
RefObject,
} from "inferno";
import { i18n } from "../../i18next";
import { Choice } from "../../utils";
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

@ -1,7 +1,8 @@
import { randomStr } from "@utils/helpers";
import { Component, linkEvent } from "inferno";
import { SortType } from "lemmy-js-client";
import { i18n } from "../../i18next";
import { randomStr, relTags, sortingHelpUrl } from "../../utils";
import { relTags, sortingHelpUrl } from "../../config";
import { I18NextService } from "../../services";
import { Icon } from "./icon";
interface SortSelectProps {
@ -40,43 +41,45 @@ 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={"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

@ -1,5 +1,18 @@
import { getQueryParams, getQueryString } from "@utils/helpers";
import {
editCommunity,
myAuth,
myAuthRequired,
setIsoData,
showLocal,
} from "@utils/app";
import {
getPageFromString,
getQueryParams,
getQueryString,
numToSI,
} from "@utils/helpers";
import type { QueryParams } from "@utils/types";
import { RouteDataResponse } from "@utils/types";
import { Component, linkEvent } from "inferno";
import {
CommunityResponse,
@ -8,20 +21,9 @@ 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 {
RouteDataResponse,
editCommunity,
getPageFromString,
myAuth,
myAuthRequired,
numToSI,
setIsoData,
showLocal,
} from "../../utils";
import { HtmlTags } from "../common/html-tags";
import { Spinner } from "../common/icon";
import { ListingTypeSelect } from "../common/listing-type-select";
@ -83,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
}`;
}
@ -100,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
@ -120,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>
@ -166,7 +173,7 @@ export class Communities extends Component<any, CommunitiesState> {
this.handleFollow
)}
>
{i18n.t("unsubscribe")}
{I18NextService.i18n.t("unsubscribe")}
</button>
)}
{cv.subscribed === "NotSubscribed" && (
@ -181,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>
@ -227,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}
@ -235,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

@ -1,3 +1,5 @@
import { myAuthRequired } from "@utils/app";
import { capitalizeFirstLetter, randomStr } from "@utils/helpers";
import { Component, linkEvent } from "inferno";
import {
CommunityView,
@ -5,8 +7,7 @@ import {
EditCommunity,
Language,
} from "lemmy-js-client";
import { i18n } from "../../i18next";
import { capitalizeFirstLetter, myAuthRequired, randomStr } from "../../utils";
import { I18NextService } from "../../services";
import { Icon, Spinner } from "../common/icon";
import { ImageUploadForm } from "../common/image-upload-form";
import { LanguageSelect } from "../common/language-select";
@ -106,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>
@ -124,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>
@ -134,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>
@ -157,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}
@ -171,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}
@ -184,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={[]}
@ -201,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">
@ -218,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">
@ -253,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 && (
@ -264,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

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

View file

@ -1,5 +1,28 @@
import { getQueryParams, getQueryString } from "@utils/helpers";
import {
commentsToFlatNodes,
communityRSSUrl,
editComment,
editPost,
editWith,
enableDownvotes,
enableNsfw,
getCommentParentId,
getDataTypeString,
myAuth,
postToCommentSortType,
setIsoData,
showLocal,
updateCommunityBlock,
updatePersonBlock,
} from "@utils/app";
import { restoreScrollPosition, saveScrollPosition } from "@utils/browser";
import {
getPageFromString,
getQueryParams,
getQueryString,
} from "@utils/helpers";
import type { QueryParams } from "@utils/types";
import { RouteDataResponse } from "@utils/types";
import { Component, linkEvent } from "inferno";
import { RouteComponentProps } from "inferno-router/dist/Route";
import {
@ -54,40 +77,16 @@ import {
SortType,
TransferCommunity,
} from "lemmy-js-client";
import { i18n } from "../../i18next";
import { fetchLimit, relTags } from "../../config";
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 {
RouteDataResponse,
commentsToFlatNodes,
communityRSSUrl,
editComment,
editPost,
editWith,
enableDownvotes,
enableNsfw,
fetchLimit,
getCommentParentId,
getDataTypeString,
getPageFromString,
myAuth,
postToCommentSortType,
relTags,
restoreScrollPosition,
saveScrollPosition,
setIsoData,
setupTippy,
showLocal,
toast,
updateCommunityBlock,
updatePersonBlock,
} from "../../utils";
import { setupTippy } from "../../tippy";
import { toast } from "../../toast";
import { CommentNodes } from "../comment/comment-nodes";
import { BannerIconHeader } from "../common/banner-icon-header";
import { DataTypeSelect } from "../common/data-type-select";
@ -330,7 +329,7 @@ export class Community extends Component<
className="btn btn-secondary d-inline-block mb-2 me-3"
onClick={linkEvent(this, this.handleShowSidebarMobile)}
>
{i18n.t("sidebar")}{" "}
{I18NextService.i18n.t("sidebar")}{" "}
<Icon
icon={
this.state.showSidebarMobile
@ -757,14 +756,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"));
}
}
@ -790,7 +789,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);
}
@ -879,7 +878,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

@ -1,11 +1,10 @@
import { enableNsfw, setIsoData } from "@utils/app";
import { Component } from "inferno";
import {
CreateCommunity as CreateCommunityI,
GetSiteResponse,
} from "lemmy-js-client";
import { i18n } from "../../i18next";
import { HttpService } from "../../services/HttpService";
import { enableNsfw, setIsoData } from "../../utils";
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

@ -1,3 +1,5 @@
import { myAuthRequired } from "@utils/app";
import { getUnixTime, hostname } from "@utils/helpers";
import { amAdmin, amMod, amTopMod } from "@utils/roles";
import { Component, InfernoNode, linkEvent } from "inferno";
import { T } from "inferno-i18next-dess";
@ -15,9 +17,8 @@ import {
PurgeCommunity,
RemoveCommunity,
} from "lemmy-js-client";
import { i18n } from "../../i18next";
import { UserService } from "../../services";
import { getUnixTime, hostname, mdToHtml, myAuthRequired } from "../../utils";
import { mdToHtml } from "../../markdown";
import { I18NextService, UserService } from "../../services";
import { Badges } from "../common/badges";
import { BannerIconHeader } from "../common/banner-icon-header";
import { Icon, PurgeWarning, Spinner } from "../common/icon";
@ -185,7 +186,7 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
) : (
<>
<Icon icon="check" classes="icon-inline text-success me-1" />
{i18n.t("joined")}
{I18NextService.i18n.t("joined")}
</>
)}
</button>
@ -198,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>
@ -232,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} />
@ -251,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>
);
}
@ -268,7 +269,7 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
{this.state.followCommunityLoading ? (
<Spinner />
) : (
i18n.t("subscribe")
I18NextService.i18n.t("subscribe")
)}
</button>
)}
@ -286,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>
)}
</>
@ -313,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>
@ -329,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">
@ -353,7 +356,7 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
this.handleCancelLeaveModTeamClick
)}
>
{i18n.t("no")}
{I18NextService.i18n.t("no")}
</button>
</li>
</>
@ -365,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 ? (
@ -396,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
@ -406,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>
)}
@ -424,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)}
/>
@ -438,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>
@ -458,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)}
/>
@ -476,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

@ -1,3 +1,11 @@
import {
fetchThemeList,
myAuthRequired,
setIsoData,
showLocal,
} from "@utils/app";
import { capitalizeFirstLetter } from "@utils/helpers";
import { RouteDataResponse } from "@utils/types";
import classNames from "classnames";
import { Component, linkEvent } from "inferno";
import {
@ -10,21 +18,11 @@ import {
GetSiteResponse,
PersonView,
} from "lemmy-js-client";
import { i18n } from "../../i18next";
import { InitialFetchRequest } from "../../interfaces";
import { FirstLoadService } from "../../services/FirstLoadService";
import { removeFromEmojiDataModel, updateEmojiDataModel } from "../../markdown";
import { FirstLoadService, I18NextService } from "../../services";
import { HttpService, RequestState } from "../../services/HttpService";
import {
RouteDataResponse,
capitalizeFirstLetter,
fetchThemeList,
myAuthRequired,
removeFromEmojiDataModel,
setIsoData,
showLocal,
toast,
updateEmojiDataModel,
} from "../../utils";
import { toast } from "../../toast";
import { HtmlTags } from "../common/html-tags";
import { Spinner } from "../common/icon";
import Tabs from "../common/tabs";
@ -109,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
}`;
}
@ -130,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", {
@ -182,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", {
@ -203,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", {
@ -254,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">
@ -276,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>
);
@ -294,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">
@ -320,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 });
@ -341,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

@ -1,3 +1,4 @@
import { myAuthRequired, setIsoData } from "@utils/app";
import { Component, linkEvent } from "inferno";
import {
CreateCustomEmoji,
@ -5,15 +6,9 @@ import {
EditCustomEmoji,
GetSiteResponse,
} from "lemmy-js-client";
import { i18n } from "../../i18next";
import { HttpService } from "../../services/HttpService";
import {
customEmojisLookup,
myAuthRequired,
pictrsDeleteToast,
setIsoData,
toast,
} from "../../utils";
import { customEmojisLookup } from "../../markdown";
import { HttpService, I18NextService } from "../../services";
import { pictrsDeleteToast, toast } from "../../toast";
import { EmojiMart } from "../common/emoji-mart";
import { HtmlTags } from "../common/html-tags";
import { Icon } from "../common/icon";
@ -70,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() {
@ -80,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
@ -93,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>
@ -219,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) ||
@ -240,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"
@ -261,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} />
@ -284,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

@ -1,6 +1,28 @@
import { getQueryParams, getQueryString } from "@utils/helpers";
import {
commentsToFlatNodes,
editComment,
editPost,
editWith,
enableDownvotes,
enableNsfw,
getCommentParentId,
getDataTypeString,
myAuth,
postToCommentSortType,
setIsoData,
showLocal,
updatePersonBlock,
} from "@utils/app";
import { restoreScrollPosition, saveScrollPosition } from "@utils/browser";
import {
getPageFromString,
getQueryParams,
getQueryString,
getRandomFromList,
} from "@utils/helpers";
import { canCreateCommunity } from "@utils/roles";
import type { QueryParams } from "@utils/types";
import { RouteDataResponse } from "@utils/types";
import { NoOptionI18nKeys } from "i18next";
import { Component, MouseEventHandler, linkEvent } from "inferno";
import { T } from "inferno-i18next-dess";
@ -50,41 +72,17 @@ import {
SortType,
TransferCommunity,
} from "lemmy-js-client";
import { i18n } from "../../i18next";
import { fetchLimit, relTags, trendingFetchLimit } from "../../config";
import {
CommentViewType,
DataType,
InitialFetchRequest,
} from "../../interfaces";
import { UserService } from "../../services";
import { FirstLoadService } from "../../services/FirstLoadService";
import { mdToHtml } from "../../markdown";
import { FirstLoadService, I18NextService, UserService } from "../../services";
import { HttpService, RequestState } from "../../services/HttpService";
import {
RouteDataResponse,
commentsToFlatNodes,
editComment,
editPost,
editWith,
enableDownvotes,
enableNsfw,
fetchLimit,
getCommentParentId,
getDataTypeString,
getPageFromString,
getRandomFromList,
mdToHtml,
myAuth,
postToCommentSortType,
relTags,
restoreScrollPosition,
saveScrollPosition,
setIsoData,
setupTippy,
showLocal,
toast,
trendingFetchLimit,
updatePersonBlock,
} from "../../utils";
import { setupTippy } from "../../tippy";
import { toast } from "../../toast";
import { CommentNodes } from "../comment/comment-nodes";
import { DataTypeSelect } from "../common/data-type-select";
import { HtmlTags } from "../common/html-tags";
@ -197,7 +195,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 +208,7 @@ const LinkButton = ({
translationKey: NoOptionI18nKeys;
}) => (
<Link className="btn btn-secondary d-block" to={path}>
{i18n.t(translationKey)}
{I18NextService.i18n.t(translationKey)}
</Link>
);
@ -565,10 +563,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"
@ -932,14 +934,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 +965,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 +1032,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

@ -1,14 +1,15 @@
import { setIsoData } from "@utils/app";
import { RouteDataResponse } from "@utils/types";
import { Component } from "inferno";
import {
GetFederatedInstancesResponse,
GetSiteResponse,
Instance,
} from "lemmy-js-client";
import { i18n } from "../../i18next";
import { relTags } from "../../config";
import { InitialFetchRequest } from "../../interfaces";
import { FirstLoadService } from "../../services/FirstLoadService";
import { FirstLoadService, I18NextService } from "../../services";
import { HttpService, RequestState } from "../../services/HttpService";
import { RouteDataResponse, relTags, setIsoData } from "../../utils";
import { HtmlTags } from "../common/html-tags";
import { Spinner } from "../common/icon";
@ -68,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() {
@ -84,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>
)}
@ -125,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>
@ -146,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,7 +1,8 @@
import { setIsoData } from "@utils/app";
import { Component } from "inferno";
import { GetSiteResponse } from "lemmy-js-client";
import { i18n } from "../../i18next";
import { mdToHtml, setIsoData } from "../../utils";
import { mdToHtml } from "../../markdown";
import { I18NextService } from "../../services";
import { HtmlTags } from "../common/html-tags";
interface LegalState {
@ -19,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

@ -1,10 +1,11 @@
import { myAuth, setIsoData } from "@utils/app";
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 { myAuth, setIsoData, toast, validEmail } from "../../utils";
import { toast } from "../../toast";
import { HtmlTags } from "../common/html-tags";
import { Spinner } from "../common/icon";
@ -41,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 {
@ -66,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
@ -89,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
@ -110,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>
@ -122,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
@ -144,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>
@ -170,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 } });
@ -218,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

@ -1,8 +1,9 @@
import { myAuthRequired } from "@utils/app";
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 { capitalizeFirstLetter, myAuthRequired } from "../../utils";
import { I18NextService } from "../../services";
import { Spinner } from "../common/icon";
import Tabs from "../common/tabs";
@ -56,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"
@ -67,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"
@ -140,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", {
@ -175,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

@ -1,3 +1,4 @@
import { fetchThemeList, setIsoData } from "@utils/app";
import { Component, linkEvent } from "inferno";
import { Helmet } from "inferno-helmet";
import {
@ -6,10 +7,8 @@ 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 { fetchThemeList, setIsoData } from "../../utils";
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

@ -1,4 +1,6 @@
import { myAuth, setIsoData } from "@utils/app";
import { isBrowser } from "@utils/browser";
import { validEmail } from "@utils/helpers";
import { Options, passwordStrength } from "check-password-strength";
import { NoOptionI18nKeys } from "i18next";
import { Component, linkEvent } from "inferno";
@ -10,17 +12,11 @@ import {
LoginResponse,
SiteView,
} from "lemmy-js-client";
import { i18n } from "../../i18next";
import { UserService } from "../../services";
import { joinLemmyUrl } from "../../config";
import { mdToHtml } from "../../markdown";
import { I18NextService, UserService } from "../../services";
import { HttpService, RequestState } from "../../services/HttpService";
import {
joinLemmyUrl,
mdToHtml,
myAuth,
setIsoData,
toast,
validEmail,
} from "../../utils";
import { toast } from "../../toast";
import { HtmlTags } from "../common/html-tags";
import { Icon, Spinner } from "../common/icon";
import { MarkdownTextArea } from "../common/markdown-textarea";
@ -116,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"
);
}
@ -162,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">
@ -175,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
@ -191,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"
@ -205,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>
@ -216,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
@ -232,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>
@ -243,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
@ -265,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
@ -283,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
@ -309,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>
@ -348,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>
@ -387,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}
@ -477,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

@ -1,3 +1,5 @@
import { myAuthRequired } from "@utils/app";
import { capitalizeFirstLetter, validInstanceTLD } from "@utils/helpers";
import {
Component,
InfernoKeyboardEvent,
@ -11,12 +13,7 @@ import {
Instance,
ListingType,
} from "lemmy-js-client";
import { i18n } from "../../i18next";
import {
capitalizeFirstLetter,
myAuthRequired,
validInstanceTLD,
} from "../../utils";
import { I18NextService } from "../../services";
import { Icon, Spinner } from "../common/icon";
import { ImageUploadForm } from "../common/image-upload-form";
import { LanguageSelect } from "../common/language-select";
@ -139,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
@ -160,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}
@ -170,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}
@ -180,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
@ -194,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}
@ -207,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
@ -233,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>
@ -252,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>
@ -263,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"
@ -272,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
@ -312,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>
@ -334,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>
@ -356,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>
@ -375,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>
@ -386,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"
@ -394,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}
@ -406,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
@ -432,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>
@ -451,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>
@ -461,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
@ -488,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
@ -515,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>
@ -540,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>
@ -550,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
@ -582,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>
@ -594,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"
@ -602,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>
@ -619,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>
@ -637,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 "../../utils";
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

@ -1,7 +1,8 @@
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 { capitalizeFirstLetter, myAuthRequired } from "../../utils";
import { I18NextService } from "../../services";
import { HtmlTags } from "../common/html-tags";
import { Icon, Spinner } from "../common/icon";
import { MarkdownTextArea } from "../common/markdown-textarea";
@ -26,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() {
@ -36,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">
@ -67,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>
@ -79,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>
@ -95,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>
@ -110,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

@ -1,6 +1,20 @@
import { debounce, getQueryParams, getQueryString } from "@utils/helpers";
import {
fetchUsers,
getUpdatedSearchId,
myAuth,
personToChoice,
setIsoData,
} from "@utils/app";
import {
debounce,
getIdFromString,
getPageFromString,
getQueryParams,
getQueryString,
} from "@utils/helpers";
import { amAdmin, amMod } from "@utils/roles";
import type { QueryParams } from "@utils/types";
import { Choice, RouteDataResponse } from "@utils/types";
import { NoOptionI18nKeys } from "i18next";
import { Component, linkEvent } from "inferno";
import { T } from "inferno-i18next-dess";
@ -31,22 +45,10 @@ import {
Person,
} from "lemmy-js-client";
import moment from "moment";
import { i18n } from "../i18next";
import { fetchLimit } from "../config";
import { InitialFetchRequest } from "../interfaces";
import { FirstLoadService } from "../services/FirstLoadService";
import { FirstLoadService, I18NextService } from "../services";
import { HttpService, RequestState } from "../services/HttpService";
import {
Choice,
RouteDataResponse,
fetchLimit,
fetchUsers,
getIdFromString,
getPageFromString,
getUpdatedSearchId,
myAuth,
personToChoice,
setIsoData,
} from "../utils";
import { HtmlTags } from "./common/html-tags";
import { Icon, Spinner } from "./common/icon";
import { MomentTime } from "./common/moment-time";
@ -583,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)}
@ -721,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 {
@ -767,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">
@ -779,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>
@ -845,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

@ -1,3 +1,17 @@
import {
commentsToFlatNodes,
editCommentReply,
editMention,
editPrivateMessage,
editWith,
enableDownvotes,
getCommentParentId,
myAuth,
myAuthRequired,
setIsoData,
updatePersonBlock,
} from "@utils/app";
import { RouteDataResponse } from "@utils/types";
import { Component, linkEvent } from "inferno";
import {
AddAdmin,
@ -44,28 +58,11 @@ import {
SaveComment,
TransferCommunity,
} from "lemmy-js-client";
import { i18n } from "../../i18next";
import { fetchLimit, relTags } from "../../config";
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 {
RouteDataResponse,
commentsToFlatNodes,
editCommentReply,
editMention,
editPrivateMessage,
editWith,
enableDownvotes,
fetchLimit,
getCommentParentId,
myAuth,
myAuthRequired,
relTags,
setIsoData,
toast,
updatePersonBlock,
} from "../../utils";
import { toast } from "../../toast";
import { CommentNodes } from "../comment/comment-nodes";
import { CommentSortSelect } from "../common/comment-sort-select";
import { HtmlTags } from "../common/html-tags";
@ -188,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}`
: "";
}
@ -224,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}>
@ -246,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>
)}
@ -297,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
@ -311,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>
);
@ -332,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
@ -346,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
@ -360,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
@ -374,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>
);
@ -827,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);
}
@ -838,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");
@ -850,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);
}
}
@ -858,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);
}
}
@ -893,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) {
@ -1005,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(`/`);
}
}
@ -1014,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

@ -1,9 +1,9 @@
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 { capitalizeFirstLetter, myAuth, setIsoData } from "../../utils";
import { HtmlTags } from "../common/html-tags";
import { Spinner } from "../common/icon";
@ -33,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
}`;
}
@ -47,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>
@ -60,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
@ -76,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
@ -96,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

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

View file

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

View file

@ -1,6 +1,27 @@
import { getQueryParams, getQueryString } from "@utils/helpers";
import {
editComment,
editPost,
editWith,
enableDownvotes,
enableNsfw,
getCommentParentId,
myAuth,
myAuthRequired,
setIsoData,
updatePersonBlock,
} from "@utils/app";
import { restoreScrollPosition, saveScrollPosition } from "@utils/browser";
import {
capitalizeFirstLetter,
futureDaysToUnixTime,
getPageFromString,
getQueryParams,
getQueryString,
numToSI,
} from "@utils/helpers";
import { canMod, isAdmin, isBanned } from "@utils/roles";
import type { QueryParams } from "@utils/types";
import { RouteDataResponse } from "@utils/types";
import classNames from "classnames";
import { NoOptionI18nKeys } from "i18next";
import { Component, linkEvent } from "inferno";
@ -50,35 +71,13 @@ import {
TransferCommunity,
} from "lemmy-js-client";
import moment from "moment";
import { i18n } from "../../i18next";
import { fetchLimit, relTags } from "../../config";
import { InitialFetchRequest, PersonDetailsView } from "../../interfaces";
import { UserService } from "../../services";
import { FirstLoadService } from "../../services/FirstLoadService";
import { mdToHtml } from "../../markdown";
import { FirstLoadService, I18NextService, UserService } from "../../services";
import { HttpService, RequestState } from "../../services/HttpService";
import {
RouteDataResponse,
capitalizeFirstLetter,
editComment,
editPost,
editWith,
enableDownvotes,
enableNsfw,
fetchLimit,
futureDaysToUnixTime,
getCommentParentId,
getPageFromString,
mdToHtml,
myAuth,
myAuthRequired,
numToSI,
relTags,
restoreScrollPosition,
saveScrollPosition,
setIsoData,
setupTippy,
toast,
updatePersonBlock,
} from "../../utils";
import { setupTippy } from "../../tippy";
import { toast } from "../../toast";
import { BannerIconHeader } from "../common/banner-icon-header";
import { HtmlTags } from "../common/html-tags";
import { Icon, Spinner } from "../common/icon";
@ -136,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}>
@ -421,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>
);
}
@ -484,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>
@ -515,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={
@ -523,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
@ -535,7 +534,7 @@ export class Profile extends Component<
this.handleUnblockPerson
)}
>
{i18n.t("unblock_user")}
{I18NextService.i18n.t("unblock_user")}
</button>
) : (
<button
@ -547,7 +546,7 @@ export class Profile extends Component<
this.handleBlockPerson
)}
>
{i18n.t("block_user")}
{I18NextService.i18n.t("block_user")}
</button>
)}
</>
@ -562,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
@ -572,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>
@ -589,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),
})}
@ -603,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
@ -613,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()
@ -622,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>
@ -640,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)}
/>
@ -673,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>
@ -683,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>
@ -903,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"));
}
}
@ -934,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) {
@ -998,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

@ -1,3 +1,9 @@
import {
editRegistrationApplication,
myAuthRequired,
setIsoData,
} from "@utils/app";
import { RouteDataResponse } from "@utils/types";
import { Component, linkEvent } from "inferno";
import {
ApproveRegistrationApplication,
@ -5,19 +11,11 @@ import {
ListRegistrationApplicationsResponse,
RegistrationApplicationView,
} from "lemmy-js-client";
import { i18n } from "../../i18next";
import { fetchLimit } from "../../config";
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 {
RouteDataResponse,
editRegistrationApplication,
fetchLimit,
myAuthRequired,
setIsoData,
setupTippy,
} from "../../utils";
import { setupTippy } from "../../tippy";
import { HtmlTags } from "../common/html-tags";
import { Spinner } from "../common/icon";
import { Paginator } from "../common/paginator";
@ -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

@ -1,4 +1,12 @@
import {
editCommentReport,
editPostReport,
editPrivateMessageReport,
myAuthRequired,
setIsoData,
} from "@utils/app";
import { amAdmin } from "@utils/roles";
import { RouteDataResponse } from "@utils/types";
import { Component, linkEvent } from "inferno";
import {
CommentReportResponse,
@ -18,20 +26,15 @@ import {
ResolvePostReport,
ResolvePrivateMessageReport,
} from "lemmy-js-client";
import { i18n } from "../../i18next";
import { fetchLimit } from "../../config";
import { InitialFetchRequest } from "../../interfaces";
import { HttpService, UserService } from "../../services";
import { FirstLoadService } from "../../services/FirstLoadService";
import { RequestState } from "../../services/HttpService";
import {
RouteDataResponse,
editCommentReport,
editPostReport,
editPrivateMessageReport,
fetchLimit,
myAuthRequired,
setIsoData,
} from "../../utils";
FirstLoadService,
HttpService,
I18NextService,
UserService,
} from "../../services";
import { RequestState } from "../../services/HttpService";
import { CommentReport } from "../comment/comment-report";
import { HtmlTags } from "../common/html-tags";
import { Spinner } from "../common/icon";
@ -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

@ -1,4 +1,19 @@
import { debounce } from "@utils/helpers";
import {
communityToChoice,
fetchCommunities,
fetchThemeList,
fetchUsers,
myAuth,
myAuthRequired,
personToChoice,
setIsoData,
setTheme,
showLocal,
updateCommunityBlock,
updatePersonBlock,
} from "@utils/app";
import { capitalizeFirstLetter, debounce } from "@utils/helpers";
import { Choice } from "@utils/types";
import classNames from "classnames";
import { NoOptionI18nKeys } from "i18next";
import { Component, linkEvent } from "inferno";
@ -13,30 +28,12 @@ import {
PersonBlockView,
SortType,
} from "lemmy-js-client";
import { i18n, languages } from "../../i18next";
import { elementUrl, emDash, relTags } from "../../config";
import { UserService } from "../../services";
import { HttpService, RequestState } from "../../services/HttpService";
import {
Choice,
capitalizeFirstLetter,
communityToChoice,
elementUrl,
emDash,
fetchCommunities,
fetchThemeList,
fetchUsers,
myAuth,
myAuthRequired,
personToChoice,
relTags,
setIsoData,
setTheme,
setupTippy,
showLocal,
toast,
updateCommunityBlock,
updatePersonBlock,
} from "../../utils";
import { I18NextService, languages } from "../../services/I18NextService";
import { setupTippy } from "../../tippy";
import { toast } from "../../toast";
import { HtmlTags } from "../common/html-tags";
import { Icon, Spinner } from "../common/icon";
import { ImageUploadForm } from "../common/image-upload-form";
@ -116,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
@ -236,7 +233,7 @@ export class Settings extends Component<any, SettingsState> {
}
get documentTitle(): string {
return i18n.t("settings");
return I18NextService.i18n.t("settings");
}
render() {
@ -252,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,
},
]}
@ -319,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
@ -342,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
@ -361,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
@ -383,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>
@ -412,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}>
@ -424,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>
@ -456,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}>
@ -468,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>
@ -485,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="^(?!@)(.+)$"
@ -506,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
@ -521,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}
@ -538,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">
@ -555,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}
@ -569,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}
@ -582,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
@ -592,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>
@ -619,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
@ -629,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}
@ -641,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_={
@ -656,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
@ -677,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>
@ -691,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>
@ -705,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>
@ -719,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>
@ -736,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>
@ -753,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>
@ -770,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>
@ -793,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>
@ -803,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>
@ -816,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"
@ -842,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
@ -852,7 +857,7 @@ export class Settings extends Component<any, SettingsState> {
this.handleDeleteAccountShowConfirmToggle
)}
>
{i18n.t("cancel")}
{I18NextService.i18n.t("cancel")}
</button>
</>
)}
@ -879,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>
@ -889,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">
@ -904,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>
@ -1053,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));
}
@ -1081,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)
@ -1171,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);
}
@ -1194,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,8 +1,9 @@
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 { setIsoData, toast } from "../../utils";
import { toast } from "../../toast";
import { HtmlTags } from "../common/html-tags";
import { Spinner } from "../common/icon";
@ -35,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");
}
}
@ -45,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
}`;
}
@ -59,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

@ -1,5 +1,7 @@
import { getQueryParams } from "@utils/helpers";
import { enableDownvotes, enableNsfw, myAuth, setIsoData } from "@utils/app";
import { getIdFromString, getQueryParams } from "@utils/helpers";
import type { QueryParams } from "@utils/types";
import { Choice, RouteDataResponse } from "@utils/types";
import { Component } from "inferno";
import { RouteComponentProps } from "inferno-router/dist/Route";
import {
@ -9,23 +11,13 @@ 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,
WrappedLemmyHttp,
} from "../../services/HttpService";
import {
Choice,
RouteDataResponse,
enableDownvotes,
enableNsfw,
getIdFromString,
myAuth,
setIsoData,
} from "../../utils";
import { HtmlTags } from "../common/html-tags";
import { Spinner } from "../common/icon";
import { PostForm } from "./post-form";
@ -150,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
}`;
}
@ -178,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

@ -1,8 +1,8 @@
import { Component, linkEvent } from "inferno";
import { Post } from "lemmy-js-client";
import * as sanitizeHtml from "sanitize-html";
import { i18n } from "../../i18next";
import { relTags } from "../../utils";
import { relTags } from "../../config";
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 && (
<iframe
className="post-metadata-iframe"
src={post.embed_video_url}
></iframe>
<div className="ratio ratio-16x9">
<iframe
allowFullScreen
className="post-metadata-iframe"
src={post.embed_video_url}
title={post.embed_title}
></iframe>
</div>
)}
</>
);

View file

@ -1,4 +1,18 @@
import { debounce } from "@utils/helpers";
import {
communityToChoice,
fetchCommunities,
myAuth,
myAuthRequired,
} from "@utils/app";
import {
capitalizeFirstLetter,
debounce,
getIdFromString,
validTitle,
validURL,
} from "@utils/helpers";
import { isImage } from "@utils/media";
import { Choice } from "@utils/types";
import autosize from "autosize";
import { Component, InfernoNode, linkEvent } from "inferno";
import {
@ -10,29 +24,18 @@ import {
PostView,
SearchResponse,
} from "lemmy-js-client";
import { i18n } from "../../i18next";
import { PostFormParams } from "../../interfaces";
import { UserService } from "../../services";
import { HttpService, RequestState } from "../../services/HttpService";
import {
Choice,
archiveTodayUrl,
capitalizeFirstLetter,
communityToChoice,
fetchCommunities,
getIdFromString,
ghostArchiveUrl,
isImage,
myAuth,
myAuthRequired,
relTags,
setupTippy,
toast,
trendingFetchLimit,
validTitle,
validURL,
webArchiveUrl,
} from "../../utils";
} from "../../config";
import { PostFormParams } from "../../interfaces";
import { I18NextService, UserService } from "../../services";
import { HttpService, RequestState } from "../../services/HttpService";
import { setupTippy } from "../../tippy";
import { toast } from "../../toast";
import { Icon, Spinner } from "../common/icon";
import { LanguageSelect } from "../common/language-select";
import { MarkdownTextArea } from "../common/markdown-textarea";
@ -338,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
@ -356,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>
@ -377,7 +380,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
className="me-2 d-inline-block float-right text-muted small font-weight-bold"
rel={relTags}
>
archive.org {i18n.t("archive_link")}
archive.org {I18NextService.i18n.t("archive_link")}
</a>
<a
href={`${ghostArchiveUrl}/search?term=${encodeURIComponent(
@ -386,7 +389,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
className="me-2 d-inline-block float-right text-muted small font-weight-bold"
rel={relTags}
>
ghostarchive.org {i18n.t("archive_link")}
ghostarchive.org {I18NextService.i18n.t("archive_link")}
</a>
<a
href={`${archiveTodayUrl}/?run=1&url=${encodeURIComponent(
@ -395,7 +398,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
className="me-2 d-inline-block float-right text-muted small font-weight-bold"
rel={relTags}
>
archive.today {i18n.t("archive_link")}
archive.today {I18NextService.i18n.t("archive_link")}
</a>
</div>
)}
@ -407,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
@ -451,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
@ -468,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()}
@ -476,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}
@ -497,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
@ -505,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,
@ -526,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
@ -549,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 && (
@ -560,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>
@ -586,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>
)
);
@ -606,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,4 +1,7 @@
import { myAuthRequired, newVote, showScores } from "@utils/app";
import { canShare, share } from "@utils/browser";
import { futureDaysToUnixTime, hostname, numToSI } from "@utils/helpers";
import { isImage, isVideo } from "@utils/media";
import {
amAdmin,
amCommunityCreator,
@ -34,25 +37,12 @@ import {
SavePost,
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 { UserService } from "../../services";
import {
futureDaysToUnixTime,
hostname,
isImage,
isVideo,
mdNoImages,
mdToHtml,
mdToHtmlInline,
myAuthRequired,
newVote,
numToSI,
relTags,
setupTippy,
showScores,
} from "../../utils";
import { mdNoImages, mdToHtml, mdToHtmlInline } from "../../markdown";
import { I18NextService, UserService } from "../../services";
import { setupTippy } from "../../tippy";
import { Icon, PurgeWarning, Spinner } from "../common/icon";
import { MomentTime } from "../common/moment-time";
import { PictrsImage } from "../common/pictrs-image";
@ -307,9 +297,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" />
@ -356,7 +346,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" />
@ -374,20 +364,25 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
<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} />
</>
)}
</li>
@ -421,8 +416,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 ? (
@ -447,8 +442,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 ? (
@ -472,7 +467,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"
@ -510,7 +505,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
@ -523,13 +518,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>
@ -537,7 +532,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>
@ -545,8 +540,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>
@ -554,15 +551,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>
@ -596,7 +593,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}`}>
@ -631,7 +630,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 />
@ -690,11 +689,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>
@ -734,7 +733,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),
});
@ -746,11 +745,13 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
to={`/post/${post_view.post.id}?scrollToComments=true`}
data-tippy-content={title}
>
<Icon icon="message-square" classes="me-1" inline />
{post_view.counts.comments}
<span className="me-1">
<Icon icon="message-square" classes="me-1" inline />
{post_view.counts.comments}
</span>
{this.unreadCount && (
<span className="badge text-bg-warning">
({this.unreadCount} {i18n.t("new")})
<span className="text-muted fst-italic">
({this.unreadCount} {I18NextService.i18n.t("new")})
</span>
)}
</Link>
@ -778,7 +779,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 ? (
@ -801,7 +802,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 ? (
@ -829,7 +830,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"
@ -862,9 +865,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>
@ -876,10 +879,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>
);
}
@ -889,14 +892,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>
);
}
@ -906,17 +909,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"
@ -944,8 +949,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"
@ -958,7 +963,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"
@ -984,13 +991,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>
@ -1011,7 +1018,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
})}
inline
/>
{i18n.t("community")}
{I18NextService.i18n.t("community")}
</>
)}
</button>
@ -1035,7 +1042,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
})}
inline
/>
{i18n.t("local")}
{I18NextService.i18n.t("local")}
</>
)}
</button>
@ -1059,9 +1066,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>
);
@ -1086,9 +1093,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
@ -1097,9 +1104,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 && (
@ -1108,16 +1119,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>
)}
@ -1134,24 +1145,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"
@ -1159,9 +1174,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>
</>
))}
@ -1174,36 +1189,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>
</>
)}
@ -1213,16 +1228,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>
)}
@ -1237,8 +1252,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 && (
@ -1250,22 +1265,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>
)}
@ -1276,24 +1295,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)}
/>
@ -1309,9 +1328,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>
@ -1319,19 +1338,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>
@ -1344,13 +1363,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)}
@ -1358,9 +1377,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>
)}
@ -1371,13 +1394,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)}
/>
@ -1572,10 +1595,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;
}
@ -1837,17 +1859,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

@ -1,8 +1,8 @@
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 { myAuthRequired } from "../../utils";
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

@ -1,7 +1,29 @@
import { isBrowser } from "@utils/browser";
import {
buildCommentsTree,
commentsToFlatNodes,
editComment,
editWith,
enableDownvotes,
enableNsfw,
getCommentIdFromProps,
getCommentParentId,
getDepthFromComment,
getIdFromProps,
myAuth,
setIsoData,
updateCommunityBlock,
updatePersonBlock,
} from "@utils/app";
import {
isBrowser,
restoreScrollPosition,
saveScrollPosition,
} from "@utils/browser";
import { debounce } from "@utils/helpers";
import { isImage } from "@utils/media";
import { RouteDataResponse } from "@utils/types";
import autosize from "autosize";
import { Component, createRef, linkEvent, RefObject } from "inferno";
import { Component, RefObject, createRef, linkEvent } from "inferno";
import {
AddAdmin,
AddModToCommunity,
@ -53,38 +75,16 @@ import {
SavePost,
TransferCommunity,
} from "lemmy-js-client";
import { i18n } from "../../i18next";
import { commentTreeMaxDepth } from "../../config";
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 {
buildCommentsTree,
commentsToFlatNodes,
commentTreeMaxDepth,
editComment,
editWith,
enableDownvotes,
enableNsfw,
getCommentIdFromProps,
getCommentParentId,
getDepthFromComment,
getIdFromProps,
isImage,
myAuth,
restoreScrollPosition,
RouteDataResponse,
saveScrollPosition,
setIsoData,
setupTippy,
toast,
updateCommunityBlock,
updatePersonBlock,
} from "../../utils";
import { setupTippy } from "../../tippy";
import { toast } from "../../toast";
import { CommentForm } from "../comment/comment-form";
import { CommentNodes } from "../comment/comment-nodes";
import { HtmlTags } from "../common/html-tags";
@ -398,7 +398,7 @@ export class Post extends Component<any, PostState> {
className="btn btn-secondary d-inline-block mb-2 me-3"
onClick={linkEvent(this, this.handleShowSidebarMobile)}
>
{i18n.t("sidebar")}{" "}
{I18NextService.i18n.t("sidebar")}{" "}
<Icon
icon={
this.state.showSidebarMobile
@ -436,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"
@ -450,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"
@ -464,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"
@ -478,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"
@ -494,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"
@ -593,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>
)}
</>
@ -834,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"));
}
}
@ -980,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

@ -1,3 +1,5 @@
import { getRecipientIdFromProps, myAuth, setIsoData } from "@utils/app";
import { RouteDataResponse } from "@utils/types";
import { Component } from "inferno";
import {
CreatePrivateMessage as CreatePrivateMessageI,
@ -5,17 +7,10 @@ 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 {
RouteDataResponse,
getRecipientIdFromProps,
myAuth,
setIsoData,
toast,
} from "../../utils";
import { toast } from "../../toast";
import { HtmlTags } from "../common/html-tags";
import { Spinner } from "../common/icon";
import { PrivateMessageForm } from "./private-message-form";
@ -101,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 "";
}
@ -120,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}
@ -148,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

@ -1,3 +1,5 @@
import { myAuthRequired } from "@utils/app";
import { capitalizeFirstLetter } from "@utils/helpers";
import { Component, InfernoNode, linkEvent } from "inferno";
import { T } from "inferno-i18next-dess";
import {
@ -6,13 +8,9 @@ import {
Person,
PrivateMessageView,
} from "lemmy-js-client";
import { i18n } from "../../i18next";
import {
capitalizeFirstLetter,
myAuthRequired,
relTags,
setupTippy,
} from "../../utils";
import { relTags } from "../../config";
import { I18NextService } from "../../services";
import { setupTippy } from "../../tippy";
import { Icon, Spinner } from "../common/icon";
import { MarkdownTextArea } from "../common/markdown-textarea";
import NavigationPrompt from "../common/navigation-prompt";
@ -68,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() {
@ -85,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">
@ -95,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>
@ -144,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 && (
@ -155,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

@ -1,11 +1,12 @@
import { myAuthRequired } from "@utils/app";
import { Component, InfernoNode, linkEvent } from "inferno";
import { T } from "inferno-i18next-dess";
import {
PrivateMessageReportView,
ResolvePrivateMessageReport,
} from "lemmy-js-client";
import { i18n } from "../../i18next";
import { mdToHtml, myAuthRequired } from "../../utils";
import { mdToHtml } from "../../markdown";
import { I18NextService } from "../../services";
import { Icon, Spinner } from "../common/icon";
import { PersonListing } from "../person/person-listing";
@ -38,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

@ -1,3 +1,4 @@
import { myAuthRequired } from "@utils/app";
import { Component, InfernoNode, linkEvent } from "inferno";
import {
CreatePrivateMessage,
@ -8,9 +9,8 @@ import {
Person,
PrivateMessageView,
} from "lemmy-js-client";
import { i18n } from "../../i18next";
import { UserService } from "../../services";
import { mdToHtml, myAuthRequired } from "../../utils";
import { mdToHtml } from "../../markdown";
import { I18NextService, UserService } from "../../services";
import { Icon, Spinner } from "../common/icon";
import { MomentTime } from "../common/moment-time";
import { PersonListing } from "../person/person-listing";
@ -93,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} />
@ -147,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 ? (
@ -174,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>
@ -188,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>
@ -200,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 ? (
@ -228,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"
@ -249,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)}
@ -263,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>
)}
@ -286,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>
@ -296,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

@ -1,5 +1,28 @@
import { debounce, getQueryParams, getQueryString } from "@utils/helpers";
import {
commentsToFlatNodes,
communityToChoice,
enableDownvotes,
enableNsfw,
fetchCommunities,
fetchUsers,
getUpdatedSearchId,
myAuth,
personToChoice,
setIsoData,
showLocal,
} from "@utils/app";
import { restoreScrollPosition, saveScrollPosition } from "@utils/browser";
import {
capitalizeFirstLetter,
debounce,
getIdFromString,
getPageFromString,
getQueryParams,
getQueryString,
numToSI,
} from "@utils/helpers";
import type { QueryParams } from "@utils/types";
import { Choice, RouteDataResponse } from "@utils/types";
import type { NoOptionI18nKeys } from "i18next";
import { Component, linkEvent } from "inferno";
import {
@ -22,32 +45,10 @@ import {
SearchType,
SortType,
} from "lemmy-js-client";
import { i18n } from "../i18next";
import { fetchLimit } from "../config";
import { CommentViewType, InitialFetchRequest } from "../interfaces";
import { FirstLoadService } from "../services/FirstLoadService";
import { FirstLoadService, I18NextService } from "../services";
import { HttpService, RequestState } from "../services/HttpService";
import {
Choice,
RouteDataResponse,
capitalizeFirstLetter,
commentsToFlatNodes,
communityToChoice,
enableDownvotes,
enableNsfw,
fetchCommunities,
fetchLimit,
fetchUsers,
getIdFromString,
getPageFromString,
getUpdatedSearchId,
myAuth,
numToSI,
personToChoice,
restoreScrollPosition,
saveScrollPosition,
setIsoData,
showLocal,
} from "../utils";
import { CommentNodes } from "./comment/comment-nodes";
import { HtmlTags } from "./common/html-tags";
import { Spinner } from "./common/icon";
@ -182,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)}
@ -226,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>
@ -446,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() {
@ -458,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>
@ -497,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}
@ -509,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>
@ -538,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>

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

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

View file

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

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

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

View file

@ -1,7 +1,7 @@
import { LemmyHttp } from "lemmy-js-client";
import { getHttpBase } from "../../shared/env";
import { i18n } from "../../shared/i18next";
import { toast } from "../../shared/utils";
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";
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,16 +92,30 @@ class LanguageDetector {
}
}
i18next.use(LanguageDetector).init({
debug: false,
compatibilityJSON: "v3",
supportedLngs: languages.map(l => l.code),
nonExplicitSupportedLngs: true,
// load: 'languageOnly',
// initImmediate: false,
fallbackLng: "en",
resources,
interpolation: { format },
});
export class I18NextService {
#i18n: typeof i18next;
static #instance: I18NextService;
export const i18n = i18next as i18nTyped;
private constructor() {
this.#i18n = i18next;
this.#i18n.use(LanguageDetector).init({
debug: false,
compatibilityJSON: "v3",
supportedLngs: languages.map(l => l.code),
nonExplicitSupportedLngs: true,
// load: 'languageOnly',
// initImmediate: false,
fallbackLng: "en",
resources,
interpolation: { format },
});
}
static get #Instance() {
return this.#instance ?? (this.#instance = new this());
}
public static get i18n() {
return this.#Instance.#i18n;
}
}

View file

@ -1,11 +1,12 @@
// import Cookies from 'js-cookie';
import { isAuthPath } from "@utils/app";
import { isBrowser } from "@utils/browser";
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 { isAuthPath, toast } from "../utils";
import { toast } from "../toast";
import { I18NextService } from "./I18NextService";
interface Claims {
sub: number;
@ -31,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();
}
@ -57,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";

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

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

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

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

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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