mirror of
https://github.com/QuivrHQ/quivr.git
synced 2024-12-14 17:03:29 +03:00
feat: upgrade to plus button (#1482)
# Description Epic: #1429 User Story: #1430 ## Pour la mise en preview / prod: - Mettre à jour l'environnement ```env NEXT_PUBLIC_STRIPE_PRICING_TABLE_ID=<change-me> NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=<change-me> ``` - Activer le feature flag `monetization` (booléen) ## Screenshots (if appropriate): Button: <img width="289" alt="image" src="https://github.com/StanGirard/quivr/assets/67386567/c0f7321e-2f48-4462-aab9-fd1c6f4282cd"> Modal: <img width="843" alt="image" src="https://github.com/StanGirard/quivr/assets/67386567/28082680-1126-44db-bf77-76ae7474747f">
This commit is contained in:
parent
0bf7d36629
commit
56d1f94b62
@ -14,4 +14,7 @@ NEXT_PUBLIC_E2E_URL=http://localhost:3003
|
|||||||
NEXT_PUBLIC_E2E_EMAIL=<ignore-me-or-change-me>
|
NEXT_PUBLIC_E2E_EMAIL=<ignore-me-or-change-me>
|
||||||
NEXT_PUBLIC_E2E_PASSWORD=<ignore-me-or-change-me>
|
NEXT_PUBLIC_E2E_PASSWORD=<ignore-me-or-change-me>
|
||||||
|
|
||||||
NEXT_PUBLIC_CMS_URL=https://cms.quivr.app
|
NEXT_PUBLIC_CMS_URL=https://cms.quivr.app
|
||||||
|
|
||||||
|
NEXT_PUBLIC_STRIPE_PRICING_TABLE_ID=<ignore-me-or-change-me>
|
||||||
|
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=<ignore-me-or-change-me>
|
||||||
|
@ -17,7 +17,7 @@ export const BrainsList = (): JSX.Element => {
|
|||||||
const { t } = useTranslation(["brain", "chat"]);
|
const { t } = useTranslation(["brain", "chat"]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Sidebar showButtons={["user"]}>
|
<Sidebar showButtons={["user", "upgradeToPlus"]}>
|
||||||
<div className="flex flex-col p-2 gap-2">
|
<div className="flex flex-col p-2 gap-2">
|
||||||
<Link href="/chat">
|
<Link href="/chat">
|
||||||
<Button type="button" className="bg-primary text-white py-2 w-full">
|
<Button type="button" className="bg-primary text-white py-2 w-full">
|
||||||
|
@ -15,7 +15,7 @@ export const ChatsList = (): JSX.Element => {
|
|||||||
const { shouldDisplayWelcomeChat } = useOnboarding();
|
const { shouldDisplayWelcomeChat } = useOnboarding();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Sidebar showButtons={["myBrains", "user"]}>
|
<Sidebar showButtons={["myBrains", "user", "upgradeToPlus"]}>
|
||||||
<div className="flex flex-col flex-1 h-full" data-testid="chats-list">
|
<div className="flex flex-col flex-1 h-full" data-testid="chats-list">
|
||||||
<div className="pt-2">
|
<div className="pt-2">
|
||||||
<NewChatButton />
|
<NewChatButton />
|
||||||
|
@ -1,19 +1,23 @@
|
|||||||
import { BrainManagementButton } from "@/lib/components/Sidebar/components/SidebarFooter/components/BrainManagementButton";
|
import { BrainManagementButton } from "@/lib/components/Sidebar/components/SidebarFooter/components/BrainManagementButton";
|
||||||
|
|
||||||
|
import { UpgradeToPlus } from "./components/UpgradeToPlus";
|
||||||
import { UserButton } from "./components/UserButton";
|
import { UserButton } from "./components/UserButton";
|
||||||
|
|
||||||
export type SidebarFooterButtons = "myBrains" | "user";
|
export type SidebarFooterButtons = "myBrains" | "user" | "upgradeToPlus";
|
||||||
|
|
||||||
type SidebarFooterProps = {
|
type SidebarFooterProps = {
|
||||||
showButtons: SidebarFooterButtons[];
|
showButtons: SidebarFooterButtons[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SidebarFooter = ({showButtons}: SidebarFooterProps): JSX.Element => {
|
export const SidebarFooter = ({
|
||||||
|
showButtons,
|
||||||
|
}: SidebarFooterProps): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
<div className="bg-gray-50 dark:bg-gray-900 border-t dark:border-white/10 mt-auto p-2">
|
<div className="bg-gray-50 dark:bg-gray-900 border-t dark:border-white/10 mt-auto p-2">
|
||||||
<div className="max-w-screen-xl flex justify-center items-center flex-col">
|
<div className="max-w-screen-xl flex justify-center items-center flex-col">
|
||||||
{showButtons.includes('myBrains') && <BrainManagementButton />}
|
{showButtons.includes("myBrains") && <BrainManagementButton />}
|
||||||
{showButtons.includes('user') && <UserButton />}
|
{showButtons.includes("upgradeToPlus") && <UpgradeToPlus />}
|
||||||
|
{showButtons.includes("user") && <UserButton />}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -0,0 +1,35 @@
|
|||||||
|
import { useFeatureIsOn } from "@growthbook/growthbook-react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { FiUser } from "react-icons/fi";
|
||||||
|
|
||||||
|
import { StripePricingModal } from "@/lib/components/Stripe";
|
||||||
|
import { useUserData } from "@/lib/hooks/useUserData";
|
||||||
|
|
||||||
|
import { sidebarLinkStyle } from "../styles/SidebarLinkStyle";
|
||||||
|
|
||||||
|
export const UpgradeToPlus = (): JSX.Element => {
|
||||||
|
const { userData } = useUserData();
|
||||||
|
const is_premium = userData?.is_premium;
|
||||||
|
const featureIsOn = useFeatureIsOn("monetization");
|
||||||
|
const { t } = useTranslation("monetization");
|
||||||
|
|
||||||
|
if (!featureIsOn || is_premium === true) {
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StripePricingModal
|
||||||
|
Trigger={
|
||||||
|
<button type="button" className={sidebarLinkStyle}>
|
||||||
|
<FiUser className="w-8 h-8" />
|
||||||
|
<span>
|
||||||
|
{t("upgrade")}{" "}
|
||||||
|
<span className="rounded bg-primary/50 py-1 px-3 text-xs">
|
||||||
|
{t("new")}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,16 @@
|
|||||||
|
import { StripePricingTable } from "./components/PricingTable/PricingTable";
|
||||||
|
import { Modal } from "../../ui/Modal";
|
||||||
|
|
||||||
|
type StripePricingModalProps = {
|
||||||
|
Trigger: JSX.Element;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const StripePricingModal = ({
|
||||||
|
Trigger,
|
||||||
|
}: StripePricingModalProps): JSX.Element => {
|
||||||
|
return (
|
||||||
|
<Modal Trigger={Trigger} CloseTrigger={<div />}>
|
||||||
|
<StripePricingTable />
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,14 @@
|
|||||||
|
const PRICING_TABLE_ID = process.env.NEXT_PUBLIC_STRIPE_PRICING_TABLE_ID;
|
||||||
|
const PUBLISHABLE_KEY = process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY;
|
||||||
|
|
||||||
|
export const StripePricingTable = (): JSX.Element => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<script async src="https://js.stripe.com/v3/pricing-table.js"></script>
|
||||||
|
<stripe-pricing-table
|
||||||
|
pricing-table-id={PRICING_TABLE_ID}
|
||||||
|
publishable-key={PUBLISHABLE_KEY}
|
||||||
|
></stripe-pricing-table>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
12
frontend/lib/components/Stripe/PricingModal/components/PricingTable/Types/types.d.ts
vendored
Normal file
12
frontend/lib/components/Stripe/PricingModal/components/PricingTable/Types/types.d.ts
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
namespace JSX {
|
||||||
|
interface IntrinsicElements {
|
||||||
|
"stripe-pricing-table": React.DetailedHTMLProps<
|
||||||
|
React.HTMLAttributes<HTMLElement>,
|
||||||
|
HTMLElement
|
||||||
|
>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1
frontend/lib/components/Stripe/index.ts
Normal file
1
frontend/lib/components/Stripe/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from "./PricingModal/StripePricingModal";
|
@ -11,6 +11,7 @@ import invitation_en from "../../../public/locales/en/invitation.json";
|
|||||||
import knowlegde_en from "../../../public/locales/en/knowledge.json";
|
import knowlegde_en from "../../../public/locales/en/knowledge.json";
|
||||||
import login_en from "../../../public/locales/en/login.json";
|
import login_en from "../../../public/locales/en/login.json";
|
||||||
import logout_en from "../../../public/locales/en/logout.json";
|
import logout_en from "../../../public/locales/en/logout.json";
|
||||||
|
import monetization_en from "../../../public/locales/en/monetization.json";
|
||||||
import signUp_en from "../../../public/locales/en/signUp.json";
|
import signUp_en from "../../../public/locales/en/signUp.json";
|
||||||
import translation_en from "../../../public/locales/en/translation.json";
|
import translation_en from "../../../public/locales/en/translation.json";
|
||||||
import updatePassword_en from "../../../public/locales/en/updatePassword.json";
|
import updatePassword_en from "../../../public/locales/en/updatePassword.json";
|
||||||
@ -28,6 +29,7 @@ import invitation_es from "../../../public/locales/es/invitation.json";
|
|||||||
import knowlegde_es from "../../../public/locales/es/knowledge.json";
|
import knowlegde_es from "../../../public/locales/es/knowledge.json";
|
||||||
import login_es from "../../../public/locales/es/login.json";
|
import login_es from "../../../public/locales/es/login.json";
|
||||||
import logout_es from "../../../public/locales/es/logout.json";
|
import logout_es from "../../../public/locales/es/logout.json";
|
||||||
|
import monetization_es from "../../../public/locales/es/monetization.json";
|
||||||
import signUp_es from "../../../public/locales/es/signUp.json";
|
import signUp_es from "../../../public/locales/es/signUp.json";
|
||||||
import translation_es from "../../../public/locales/es/translation.json";
|
import translation_es from "../../../public/locales/es/translation.json";
|
||||||
import updatePassword_es from "../../../public/locales/es/updatePassword.json";
|
import updatePassword_es from "../../../public/locales/es/updatePassword.json";
|
||||||
@ -45,6 +47,7 @@ import invitation_fr from "../../../public/locales/fr/invitation.json";
|
|||||||
import knowlegde_fr from "../../../public/locales/fr/knowledge.json";
|
import knowlegde_fr from "../../../public/locales/fr/knowledge.json";
|
||||||
import login_fr from "../../../public/locales/fr/login.json";
|
import login_fr from "../../../public/locales/fr/login.json";
|
||||||
import logout_fr from "../../../public/locales/fr/logout.json";
|
import logout_fr from "../../../public/locales/fr/logout.json";
|
||||||
|
import monetization_fr from "../../../public/locales/fr/monetization.json";
|
||||||
import signUp_fr from "../../../public/locales/fr/signUp.json";
|
import signUp_fr from "../../../public/locales/fr/signUp.json";
|
||||||
import translation_fr from "../../../public/locales/fr/translation.json";
|
import translation_fr from "../../../public/locales/fr/translation.json";
|
||||||
import updatePassword_fr from "../../../public/locales/fr/updatePassword.json";
|
import updatePassword_fr from "../../../public/locales/fr/updatePassword.json";
|
||||||
@ -62,6 +65,7 @@ import invitation_ptbr from "../../../public/locales/pt-br/invitation.json";
|
|||||||
import knowlegde_ptbr from "../../../public/locales/pt-br/knowledge.json";
|
import knowlegde_ptbr from "../../../public/locales/pt-br/knowledge.json";
|
||||||
import login_ptbr from "../../../public/locales/pt-br/login.json";
|
import login_ptbr from "../../../public/locales/pt-br/login.json";
|
||||||
import logout_ptbr from "../../../public/locales/pt-br/logout.json";
|
import logout_ptbr from "../../../public/locales/pt-br/logout.json";
|
||||||
|
import monetization_ptbr from "../../../public/locales/pt-br/monetization.json";
|
||||||
import signUp_ptbr from "../../../public/locales/pt-br/signUp.json";
|
import signUp_ptbr from "../../../public/locales/pt-br/signUp.json";
|
||||||
import translation_ptbr from "../../../public/locales/pt-br/translation.json";
|
import translation_ptbr from "../../../public/locales/pt-br/translation.json";
|
||||||
import updatePassword_ptbr from "../../../public/locales/pt-br/updatePassword.json";
|
import updatePassword_ptbr from "../../../public/locales/pt-br/updatePassword.json";
|
||||||
@ -79,6 +83,7 @@ import invitation_ru from "../../../public/locales/ru/invitation.json";
|
|||||||
import knowlegde_ru from "../../../public/locales/ru/knowledge.json";
|
import knowlegde_ru from "../../../public/locales/ru/knowledge.json";
|
||||||
import login_ru from "../../../public/locales/ru/login.json";
|
import login_ru from "../../../public/locales/ru/login.json";
|
||||||
import logout_ru from "../../../public/locales/ru/logout.json";
|
import logout_ru from "../../../public/locales/ru/logout.json";
|
||||||
|
import monetization_ru from "../../../public/locales/ru/monetization.json";
|
||||||
import signUp_ru from "../../../public/locales/ru/signUp.json";
|
import signUp_ru from "../../../public/locales/ru/signUp.json";
|
||||||
import translation_ru from "../../../public/locales/ru/translation.json";
|
import translation_ru from "../../../public/locales/ru/translation.json";
|
||||||
import updatePassword_ru from "../../../public/locales/ru/updatePassword.json";
|
import updatePassword_ru from "../../../public/locales/ru/updatePassword.json";
|
||||||
@ -96,6 +101,7 @@ import invitation_zh_cn from "../../../public/locales/zh-cn/invitation.json";
|
|||||||
import knowlegde_zh_cn from "../../../public/locales/zh-cn/knowledge.json";
|
import knowlegde_zh_cn from "../../../public/locales/zh-cn/knowledge.json";
|
||||||
import login_zh_cn from "../../../public/locales/zh-cn/login.json";
|
import login_zh_cn from "../../../public/locales/zh-cn/login.json";
|
||||||
import logout_zh_cn from "../../../public/locales/zh-cn/logout.json";
|
import logout_zh_cn from "../../../public/locales/zh-cn/logout.json";
|
||||||
|
import monetization_zh_cn from "../../../public/locales/zh-cn/monetization.json";
|
||||||
import signUp_zh_cn from "../../../public/locales/zh-cn/signUp.json";
|
import signUp_zh_cn from "../../../public/locales/zh-cn/signUp.json";
|
||||||
import translation_zh_cn from "../../../public/locales/zh-cn/translation.json";
|
import translation_zh_cn from "../../../public/locales/zh-cn/translation.json";
|
||||||
import updatePassword_zh_cn from "../../../public/locales/zh-cn/updatePassword.json";
|
import updatePassword_zh_cn from "../../../public/locales/zh-cn/updatePassword.json";
|
||||||
@ -114,6 +120,7 @@ export type Translations = {
|
|||||||
invitation: typeof import("../../../public/locales/en/invitation.json");
|
invitation: typeof import("../../../public/locales/en/invitation.json");
|
||||||
login: typeof import("../../../public/locales/en/login.json");
|
login: typeof import("../../../public/locales/en/login.json");
|
||||||
logout: typeof import("../../../public/locales/en/logout.json");
|
logout: typeof import("../../../public/locales/en/logout.json");
|
||||||
|
monetization: typeof import("../../../public/locales/en/monetization.json");
|
||||||
signUp: typeof import("../../../public/locales/en/signUp.json");
|
signUp: typeof import("../../../public/locales/en/signUp.json");
|
||||||
translation: typeof import("../../../public/locales/en/translation.json");
|
translation: typeof import("../../../public/locales/en/translation.json");
|
||||||
updatePassword: typeof import("../../../public/locales/en/updatePassword.json");
|
updatePassword: typeof import("../../../public/locales/en/updatePassword.json");
|
||||||
@ -143,6 +150,7 @@ export const resources: Record<SupportedLanguages, Translations> = {
|
|||||||
invitation: invitation_en,
|
invitation: invitation_en,
|
||||||
login: login_en,
|
login: login_en,
|
||||||
logout: logout_en,
|
logout: logout_en,
|
||||||
|
monetization: monetization_en,
|
||||||
signUp: signUp_en,
|
signUp: signUp_en,
|
||||||
translation: translation_en,
|
translation: translation_en,
|
||||||
updatePassword: updatePassword_en,
|
updatePassword: updatePassword_en,
|
||||||
@ -161,6 +169,7 @@ export const resources: Record<SupportedLanguages, Translations> = {
|
|||||||
invitation: invitation_es,
|
invitation: invitation_es,
|
||||||
login: login_es,
|
login: login_es,
|
||||||
logout: logout_es,
|
logout: logout_es,
|
||||||
|
monetization: monetization_es,
|
||||||
signUp: signUp_es,
|
signUp: signUp_es,
|
||||||
translation: translation_es,
|
translation: translation_es,
|
||||||
updatePassword: updatePassword_es,
|
updatePassword: updatePassword_es,
|
||||||
@ -179,6 +188,7 @@ export const resources: Record<SupportedLanguages, Translations> = {
|
|||||||
invitation: invitation_fr,
|
invitation: invitation_fr,
|
||||||
login: login_fr,
|
login: login_fr,
|
||||||
logout: logout_fr,
|
logout: logout_fr,
|
||||||
|
monetization: monetization_fr,
|
||||||
signUp: signUp_fr,
|
signUp: signUp_fr,
|
||||||
translation: translation_fr,
|
translation: translation_fr,
|
||||||
updatePassword: updatePassword_fr,
|
updatePassword: updatePassword_fr,
|
||||||
@ -197,6 +207,7 @@ export const resources: Record<SupportedLanguages, Translations> = {
|
|||||||
invitation: invitation_ptbr,
|
invitation: invitation_ptbr,
|
||||||
login: login_ptbr,
|
login: login_ptbr,
|
||||||
logout: logout_ptbr,
|
logout: logout_ptbr,
|
||||||
|
monetization: monetization_ptbr,
|
||||||
signUp: signUp_ptbr,
|
signUp: signUp_ptbr,
|
||||||
translation: translation_ptbr,
|
translation: translation_ptbr,
|
||||||
updatePassword: updatePassword_ptbr,
|
updatePassword: updatePassword_ptbr,
|
||||||
@ -215,6 +226,7 @@ export const resources: Record<SupportedLanguages, Translations> = {
|
|||||||
invitation: invitation_ru,
|
invitation: invitation_ru,
|
||||||
login: login_ru,
|
login: login_ru,
|
||||||
logout: logout_ru,
|
logout: logout_ru,
|
||||||
|
monetization: monetization_ru,
|
||||||
signUp: signUp_ru,
|
signUp: signUp_ru,
|
||||||
translation: translation_ru,
|
translation: translation_ru,
|
||||||
updatePassword: updatePassword_ru,
|
updatePassword: updatePassword_ru,
|
||||||
@ -233,6 +245,7 @@ export const resources: Record<SupportedLanguages, Translations> = {
|
|||||||
invitation: invitation_zh_cn,
|
invitation: invitation_zh_cn,
|
||||||
login: login_zh_cn,
|
login: login_zh_cn,
|
||||||
logout: logout_zh_cn,
|
logout: logout_zh_cn,
|
||||||
|
monetization: monetization_zh_cn,
|
||||||
signUp: signUp_zh_cn,
|
signUp: signUp_zh_cn,
|
||||||
translation: translation_zh_cn,
|
translation: translation_zh_cn,
|
||||||
updatePassword: updatePassword_zh_cn,
|
updatePassword: updatePassword_zh_cn,
|
||||||
|
@ -37,7 +37,12 @@ const ContentSecurityPolicy = {
|
|||||||
"https://cdn.growthbook.io",
|
"https://cdn.growthbook.io",
|
||||||
"https://vitals.vercel-insights.com/v1/vitals",
|
"https://vitals.vercel-insights.com/v1/vitals",
|
||||||
],
|
],
|
||||||
"img-src": ["'self'", "https://www.gravatar.com","https://quivr-cms.s3.eu-west-3.amazonaws.com", "data:"],
|
"img-src": [
|
||||||
|
"'self'",
|
||||||
|
"https://www.gravatar.com",
|
||||||
|
"https://quivr-cms.s3.eu-west-3.amazonaws.com",
|
||||||
|
"data:",
|
||||||
|
],
|
||||||
"media-src": [
|
"media-src": [
|
||||||
"'self'",
|
"'self'",
|
||||||
"https://user-images.githubusercontent.com",
|
"https://user-images.githubusercontent.com",
|
||||||
@ -50,7 +55,9 @@ const ContentSecurityPolicy = {
|
|||||||
"https://va.vercel-scripts.com/",
|
"https://va.vercel-scripts.com/",
|
||||||
process.env.NEXT_PUBLIC_FRONTEND_URL,
|
process.env.NEXT_PUBLIC_FRONTEND_URL,
|
||||||
"https://www.google-analytics.com/",
|
"https://www.google-analytics.com/",
|
||||||
|
"https://js.stripe.com",
|
||||||
],
|
],
|
||||||
|
"frame-src": ["https://js.stripe.com"],
|
||||||
"frame-ancestors": ["'none'"],
|
"frame-ancestors": ["'none'"],
|
||||||
"style-src": ["'unsafe-inline'", process.env.NEXT_PUBLIC_FRONTEND_URL],
|
"style-src": ["'unsafe-inline'", process.env.NEXT_PUBLIC_FRONTEND_URL],
|
||||||
};
|
};
|
||||||
|
4
frontend/public/locales/en/monetization.json
Normal file
4
frontend/public/locales/en/monetization.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"upgrade": "Upgrade to plus",
|
||||||
|
"new": "New"
|
||||||
|
}
|
4
frontend/public/locales/es/monetization.json
Normal file
4
frontend/public/locales/es/monetization.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"upgrade": "Actualizar a plus",
|
||||||
|
"new": "Nuevo"
|
||||||
|
}
|
4
frontend/public/locales/fr/monetization.json
Normal file
4
frontend/public/locales/fr/monetization.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"upgrade": "Passer à la version plus",
|
||||||
|
"new": "Nouveau"
|
||||||
|
}
|
4
frontend/public/locales/pt-br/monetization.json
Normal file
4
frontend/public/locales/pt-br/monetization.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"upgrade": "Atualizar para o plus",
|
||||||
|
"new": "Novo"
|
||||||
|
}
|
4
frontend/public/locales/ru/monetization.json
Normal file
4
frontend/public/locales/ru/monetization.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"upgrade": "Обновить до плюса",
|
||||||
|
"new": "Новый"
|
||||||
|
}
|
4
frontend/public/locales/zh-cn/monetization.json
Normal file
4
frontend/public/locales/zh-cn/monetization.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"upgrade": "升级至高级版",
|
||||||
|
"new": "新"
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user