translate(frontend): navbar

This commit is contained in:
이강준
2022-11-27 13:09:59 +09:00
parent 8dfcc1f505
commit 4bdb48d8f6
3 changed files with 181 additions and 116 deletions
+76 -55
View File
@@ -24,6 +24,8 @@ import { faPlus } from "@fortawesome/free-solid-svg-icons";
import { decryptAssymmetric, encryptAssymmetric } from "../utilities/crypto"; import { decryptAssymmetric, encryptAssymmetric } from "../utilities/crypto";
import Button from "./buttons/Button"; import Button from "./buttons/Button";
import useTranslation from "next-translate/useTranslation";
export default function Layout({ children }) { export default function Layout({ children }) {
const router = useRouter(); const router = useRouter();
const [workspaceList, setWorkspaceList] = useState([]); const [workspaceList, setWorkspaceList] = useState([]);
@@ -34,6 +36,8 @@ export default function Layout({ children }) {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [error, setError] = useState(false); const [error, setError] = useState(false);
const { t } = useTranslation();
function closeModal() { function closeModal() {
setIsOpen(false); setIsOpen(false);
} }
@@ -48,9 +52,7 @@ export default function Layout({ children }) {
setLoading(true); setLoading(true);
setTimeout(() => setLoading(false), 1500); setTimeout(() => setLoading(false), 1500);
const workspaces = await getWorkspaces(); const workspaces = await getWorkspaces();
const currentWorkspaces = workspaces.map( const currentWorkspaces = workspaces.map((workspace) => workspace.name);
(workspace) => workspace.name
);
if (!currentWorkspaces.includes(workspaceName)) { if (!currentWorkspaces.includes(workspaceName)) {
const newWorkspace = await createWorkspace( const newWorkspace = await createWorkspace(
workspaceName, workspaceName,
@@ -104,7 +106,7 @@ export default function Layout({ children }) {
setIsOpen(false); setIsOpen(false);
setNewWorkspaceName(""); setNewWorkspaceName("");
} else { } else {
setError("A project with this name already exists."); setError(t("common:error_project-already-exists"));
setLoading(false); setLoading(false);
} }
} }
@@ -119,29 +121,32 @@ export default function Layout({ children }) {
"/dashboard/" + "/dashboard/" +
workspaceMapping[workspaceSelected] + workspaceMapping[workspaceSelected] +
"?Development", "?Development",
title: "Secrets", title: t("common:nav.menu.secrets"),
emoji: <FontAwesomeIcon icon={faHouse} />, emoji: <FontAwesomeIcon icon={faHouse} />,
}, },
{ {
href: "/users/" + workspaceMapping[workspaceSelected], href: "/users/" + workspaceMapping[workspaceSelected],
title: "Members", title: t("common:nav.menu.members"),
emoji: <FontAwesomeIcon icon={faUser} />, emoji: <FontAwesomeIcon icon={faUser} />,
}, },
{ {
href: "/integrations/" + workspaceMapping[workspaceSelected], href: "/integrations/" + workspaceMapping[workspaceSelected],
title: "Integrations", title: t("common:nav.menu.integrations"),
emoji: <FontAwesomeIcon icon={faLink} />, emoji: <FontAwesomeIcon icon={faLink} />,
}, },
{ {
href: "/settings/project/" + workspaceMapping[workspaceSelected], href: "/settings/project/" + workspaceMapping[workspaceSelected],
title: "Project Settings", title: t("common:nav.menu.project-settings"),
emoji: <FontAwesomeIcon icon={faGear} />, emoji: <FontAwesomeIcon icon={faGear} />,
}, },
]; ];
useEffect(async () => { useEffect(async () => {
// Put a user in a workspace if they're not in one yet // Put a user in a workspace if they're not in one yet
if (localStorage.getItem("orgData.id") == null || localStorage.getItem("orgData.id") == "") { if (
localStorage.getItem("orgData.id") == null ||
localStorage.getItem("orgData.id") == ""
) {
const userOrgs = await getOrganizations(); const userOrgs = await getOrganizations();
localStorage.setItem("orgData.id", userOrgs[0]._id); localStorage.setItem("orgData.id", userOrgs[0]._id);
} }
@@ -150,7 +155,11 @@ export default function Layout({ children }) {
orgId: localStorage.getItem("orgData.id"), orgId: localStorage.getItem("orgData.id"),
}); });
let userWorkspaces = orgUserProjects; let userWorkspaces = orgUserProjects;
if (userWorkspaces.length == 0 && (router.asPath != "/noprojects" && !router.asPath.includes("settings"))) { if (
userWorkspaces.length == 0 &&
router.asPath != "/noprojects" &&
!router.asPath.includes("settings")
) {
router.push("/noprojects"); router.push("/noprojects");
} else if (router.asPath != "/noprojects") { } else if (router.asPath != "/noprojects") {
const intendedWorkspaceId = router.asPath const intendedWorkspaceId = router.asPath
@@ -158,7 +167,9 @@ export default function Layout({ children }) {
[router.asPath.split("/").length - 1].split("?")[0]; [router.asPath.split("/").length - 1].split("?")[0];
// If a user is not a member of a workspace they are trying to access, just push them to one of theirs // If a user is not a member of a workspace they are trying to access, just push them to one of theirs
if (intendedWorkspaceId != "heroku" && !userWorkspaces if (
intendedWorkspaceId != "heroku" &&
!userWorkspaces
.map((workspace) => workspace._id) .map((workspace) => workspace._id)
.includes(intendedWorkspaceId) .includes(intendedWorkspaceId)
) { ) {
@@ -207,7 +218,10 @@ export default function Layout({ children }) {
workspaceMapping[workspaceSelected] + workspaceMapping[workspaceSelected] +
"?Development" "?Development"
); );
localStorage.setItem("projectData.id", workspaceMapping[workspaceSelected]) localStorage.setItem(
"projectData.id",
workspaceMapping[workspaceSelected]
);
} }
} catch (error) { } catch (error) {
console.log(error); console.log(error);
@@ -224,10 +238,10 @@ export default function Layout({ children }) {
<div className="py-6"></div> <div className="py-6"></div>
<div className="flex justify-center w-full mt-7 mb-8 bg-bunker-600 w-full h-full flex flex-col items-center px-4"> <div className="flex justify-center w-full mt-7 mb-8 bg-bunker-600 w-full h-full flex flex-col items-center px-4">
<div className="text-gray-400 self-start ml-1 mb-1 text-xs font-semibold tracking-wide"> <div className="text-gray-400 self-start ml-1 mb-1 text-xs font-semibold tracking-wide">
PROJECT {t("common:nav.menu.project")}
</div> </div>
{workspaceList.length>0 {workspaceList.length > 0 ? (
? <Listbox <Listbox
selected={workspaceSelected} selected={workspaceSelected}
onChange={setWorkspaceSelected} onChange={setWorkspaceSelected}
data={workspaceList} data={workspaceList}
@@ -235,58 +249,67 @@ export default function Layout({ children }) {
text="" text=""
workspaceMapping={workspaceMapping} workspaceMapping={workspaceMapping}
/> />
: <Button ) : (
<Button
text="Add Project" text="Add Project"
onButtonPressed={openModal} onButtonPressed={openModal}
color="mineshaft" color="mineshaft"
size="md" size="md"
icon={faPlus} icon={faPlus}
/> />
} )}
</div> </div>
<ul> <ul>
{workspaceList.length > 0 && menuItems.map(({ href, title, emoji }) => ( {workspaceList.length > 0 &&
<li className="mt-1.5 mx-2" key={title}> menuItems.map(({ href, title, emoji }) => (
{router.asPath.split("/")[1] === <li className="mt-1.5 mx-2" key={title}>
href.split("/")[1] && {router.asPath.split("/")[1] ===
(["project", "billing", "org", "personal"].includes( href.split("/")[1] &&
router.asPath.split("/")[2] ([
) "project",
? router.asPath.split("/")[2] === "billing",
href.split("/")[2] "org",
: true) ? ( "personal",
<div ].includes(
className={`flex p-2 text-white text-sm rounded cursor-pointer bg-mineshaft-50/10`} router.asPath.split("/")[2]
> )
<div className="bg-primary w-1 rounded-xl mr-1"></div> ? router.asPath.split(
<p className="ml-2 mr-4"> "/"
{emoji} )[2] === href.split("/")[2]
</p> : true) ? (
{title}
</div>
) : router.asPath == "/noprojects" ? (
<div
className={`flex p-2 text-white text-sm rounded`}
>
<p className="ml-2 mr-4">
{emoji}
</p>
{title}
</div>
) : (
<Link href={href}>
<div <div
className={`flex p-2 text-white text-sm rounded cursor-pointer hover:bg-mineshaft-50/5`} className={`flex p-2 text-white text-sm rounded cursor-pointer bg-mineshaft-50/10`}
>
<div className="bg-primary w-1 rounded-xl mr-1"></div>
<p className="ml-2 mr-4">
{emoji}
</p>
{title}
</div>
) : router.asPath ==
"/noprojects" ? (
<div
className={`flex p-2 text-white text-sm rounded`}
> >
<p className="ml-2 mr-4"> <p className="ml-2 mr-4">
{emoji} {emoji}
</p> </p>
{title} {title}
</div> </div>
</Link> ) : (
)} <Link href={href}>
</li> <div
))} className={`flex p-2 text-white text-sm rounded cursor-pointer hover:bg-mineshaft-50/5`}
>
<p className="ml-2 mr-4">
{emoji}
</p>
{title}
</div>
</Link>
)}
</li>
))}
</ul> </ul>
</nav> </nav>
</aside> </aside>
@@ -308,9 +331,7 @@ export default function Layout({ children }) {
className="text-gray-300 text-7xl mb-8" className="text-gray-300 text-7xl mb-8"
/> />
<p className="text-gray-200 px-6 text-center text-lg max-w-sm"> <p className="text-gray-200 px-6 text-center text-lg max-w-sm">
{" "} {` ${t("common:need-login")} `}
To use Infisical, please log in through a device with larger
dimensions.{" "}
</p> </p>
</div> </div>
</> </>
@@ -1,4 +1,4 @@
import React, { useState, Fragment, useEffect } from "react"; import React, { useState, Fragment, useEffect, useMemo } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import Image from "next/image"; import Image from "next/image";
@@ -12,8 +12,8 @@ import {
faCoins, faCoins,
faRightFromBracket, faRightFromBracket,
faEnvelope, faEnvelope,
faPlus, faPlus,
faAngleDown faAngleDown,
} from "@fortawesome/free-solid-svg-icons"; } from "@fortawesome/free-solid-svg-icons";
import { faSlack, faGithub } from "@fortawesome/free-brands-svg-icons"; import { faSlack, faGithub } from "@fortawesome/free-brands-svg-icons";
import { Menu, Transition } from "@headlessui/react"; import { Menu, Transition } from "@headlessui/react";
@@ -21,26 +21,30 @@ import getUser from "../../pages/api/user/getUser";
import getOrganizations from "../../pages/api/organization/getOrgs"; import getOrganizations from "../../pages/api/organization/getOrgs";
import getOrganization from "../../pages/api/organization/GetOrg"; import getOrganization from "../../pages/api/organization/GetOrg";
import guidGenerator from "../utilities/randomId"; import guidGenerator from "../utilities/randomId";
import useTranslation from "next-translate/useTranslation";
const supportOptions = [ /**
* @param {(key: string) => string} t
*/
const supportOptions = (t) => [
[ [
<FontAwesomeIcon className="text-lg pl-1.5 pr-3" icon={faSlack} />, <FontAwesomeIcon className="text-lg pl-1.5 pr-3" icon={faSlack} />,
"[NEW] Join Slack Forum", t("common:nav.support.slack"),
"https://join.slack.com/t/infisical/shared_invite/zt-1dgg63ln8-G7PCNJdCymAT9YF3j1ewVA", "https://join.slack.com/t/infisical/shared_invite/zt-1dgg63ln8-G7PCNJdCymAT9YF3j1ewVA",
], ],
[ [
<FontAwesomeIcon className="text-lg pl-1.5 pr-3" icon={faBook} />, <FontAwesomeIcon className="text-lg pl-1.5 pr-3" icon={faBook} />,
"Read Docs", t("common:nav.support.docs"),
"https://infisical.com/docs/getting-started/introduction", "https://infisical.com/docs/getting-started/introduction",
], ],
[ [
<FontAwesomeIcon className="text-lg pl-1.5 pr-3" icon={faGithub} />, <FontAwesomeIcon className="text-lg pl-1.5 pr-3" icon={faGithub} />,
"Open a GitHub Issue", t("common:nav.support.issue"),
"https://github.com/Infisical/infisical-cli/issues", "https://github.com/Infisical/infisical-cli/issues",
], ],
[ [
<FontAwesomeIcon className="text-lg pl-1.5 pr-3" icon={faEnvelope} />, <FontAwesomeIcon className="text-lg pl-1.5 pr-3" icon={faEnvelope} />,
"Send us an Email", t("common:nav.support.email"),
"mailto:[email protected]", "mailto:[email protected]",
], ],
]; ];
@@ -51,6 +55,10 @@ export default function Navbar({ onButtonPressed }) {
const [orgs, setOrgs] = useState([]); const [orgs, setOrgs] = useState([]);
const [currentOrg, setCurrentOrg] = useState([]); const [currentOrg, setCurrentOrg] = useState([]);
const { t } = useTranslation("common");
const supportOptionsList = useMemo(() => supportOptions(t), [t]);
useEffect(async () => { useEffect(async () => {
const userData = await getUser(); const userData = await getUser();
setUser(userData); setUser(userData);
@@ -87,7 +95,7 @@ export default function Navbar({ onButtonPressed }) {
</div> </div>
<div className="relative flex justify-start items-center mx-2 z-40"> <div className="relative flex justify-start items-center mx-2 z-40">
<Menu as="div" className="relative inline-block text-left"> <Menu as="div" className="relative inline-block text-left">
<div className="mr-4"> <div className="mr-4">
<Menu.Button className="inline-flex w-full justify-center rounded-md px-2 py-2 text-sm font-medium text-gray-200 rounded-md hover:bg-white/10 duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75"> <Menu.Button className="inline-flex w-full justify-center rounded-md px-2 py-2 text-sm font-medium text-gray-200 rounded-md hover:bg-white/10 duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75">
<FontAwesomeIcon <FontAwesomeIcon
className="text-xl" className="text-xl"
@@ -105,19 +113,17 @@ export default function Navbar({ onButtonPressed }) {
leaveTo="transform opacity-0 scale-95" leaveTo="transform opacity-0 scale-95"
> >
<Menu.Items className="absolute right-0 mt-0.5 w-64 origin-top-right rounded-md bg-bunker border border-mineshaft-700 shadow-lg ring-1 ring-black z-20 ring-opacity-5 focus:outline-none px-2 py-1.5"> <Menu.Items className="absolute right-0 mt-0.5 w-64 origin-top-right rounded-md bg-bunker border border-mineshaft-700 shadow-lg ring-1 ring-black z-20 ring-opacity-5 focus:outline-none px-2 py-1.5">
{supportOptions.map((option) => ( {supportOptionsList.map(([icon, text, url]) => (
<a <a
key={guidGenerator()} key={guidGenerator()}
target="_blank" target="_blank"
rel="noopener" rel="noopener"
href={option[2]} href={url}
className="font-normal text-gray-300 duration-200 rounded-md w-full flex items-center py-0.5" className="font-normal text-gray-300 duration-200 rounded-md w-full flex items-center py-0.5"
> >
<div className="relative flex justify-start items-center cursor-pointer select-none py-2 px-2 rounded-md text-gray-400 hover:bg-white/10 duration-200 hover:text-gray-200 w-full"> <div className="relative flex justify-start items-center cursor-pointer select-none py-2 px-2 rounded-md text-gray-400 hover:bg-white/10 duration-200 hover:text-gray-200 w-full">
{option[0]} {icon}
<div className="text-sm"> <div className="text-sm">{text}</div>
{option[1]}
</div>
</div> </div>
</a> </a>
))} ))}
@@ -128,7 +134,10 @@ export default function Navbar({ onButtonPressed }) {
<div> <div>
<Menu.Button className="inline-flex w-full justify-center rounded-md pr-2 pl-2 py-2 text-sm font-medium text-gray-200 rounded-md hover:bg-white/10 duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75"> <Menu.Button className="inline-flex w-full justify-center rounded-md pr-2 pl-2 py-2 text-sm font-medium text-gray-200 rounded-md hover:bg-white/10 duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75">
{user?.firstName} {user?.lastName} {user?.firstName} {user?.lastName}
<FontAwesomeIcon icon={faAngleDown} className="ml-2 mt-1 text-sm text-gray-300 hover:text-lime-100"/> <FontAwesomeIcon
icon={faAngleDown}
className="ml-2 mt-1 text-sm text-gray-300 hover:text-lime-100"
/>
</Menu.Button> </Menu.Button>
</div> </div>
<Transition <Transition
@@ -143,15 +152,17 @@ export default function Navbar({ onButtonPressed }) {
<Menu.Items className="absolute right-0 mt-0.5 w-64 origin-top-right divide-y divide-gray-700 rounded-md bg-bunker border border-mineshaft-700 shadow-lg ring-1 ring-black z-20 ring-opacity-5 focus:outline-none"> <Menu.Items className="absolute right-0 mt-0.5 w-64 origin-top-right divide-y divide-gray-700 rounded-md bg-bunker border border-mineshaft-700 shadow-lg ring-1 ring-black z-20 ring-opacity-5 focus:outline-none">
<div className="px-1 py-1 "> <div className="px-1 py-1 ">
<div className="text-gray-400 self-start ml-2 mt-2 text-xs font-semibold tracking-wide"> <div className="text-gray-400 self-start ml-2 mt-2 text-xs font-semibold tracking-wide">
SIGNED IN AS {t("common:nav.user.signed-in-as")}
</div> </div>
<div <div
onClick={() => onClick={() =>
router.push( router.push(
"/settings/personal/" + router.query.id "/settings/personal/" +
router.query.id
) )
} }
className="flex flex-row items-center px-1 mx-1 my-1 hover:bg-white/5 cursor-pointer rounded-md"> className="flex flex-row items-center px-1 mx-1 my-1 hover:bg-white/5 cursor-pointer rounded-md"
>
<div className="bg-white/10 h-8 w-9 rounded-full flex items-center justify-center text-gray-300"> <div className="bg-white/10 h-8 w-9 rounded-full flex items-center justify-center text-gray-300">
{user?.firstName?.charAt(0)} {user?.firstName?.charAt(0)}
</div> </div>
@@ -159,7 +170,8 @@ export default function Navbar({ onButtonPressed }) {
<div> <div>
<p className="text-gray-300 px-2 pt-1 text-sm"> <p className="text-gray-300 px-2 pt-1 text-sm">
{" "} {" "}
{user?.firstName} {user?.lastName} {user?.firstName}{" "}
{user?.lastName}
</p> </p>
<p className="text-gray-400 px-2 pb-1 text-xs"> <p className="text-gray-400 px-2 pb-1 text-xs">
{" "} {" "}
@@ -175,7 +187,7 @@ export default function Navbar({ onButtonPressed }) {
</div> </div>
<div className="px-2 pt-2"> <div className="px-2 pt-2">
<div className="text-gray-400 self-start ml-2 mt-2 text-xs font-semibold tracking-wide"> <div className="text-gray-400 self-start ml-2 mt-2 text-xs font-semibold tracking-wide">
CURRENT ORGANIZATION {t("common:nav.user.current-organization")}
</div> </div>
<div <div
onClick={() => onClick={() =>
@@ -216,7 +228,7 @@ export default function Navbar({ onButtonPressed }) {
icon={faCoins} icon={faCoins}
/> />
<div className="text-sm"> <div className="text-sm">
Usage & Billing {t("common:nav.user.usage-billing")}
</div> </div>
</div> </div>
</button> </button>
@@ -235,51 +247,58 @@ export default function Navbar({ onButtonPressed }) {
className="relative flex justify-start cursor-pointer select-none py-2 pl-10 pr-4 rounded-md text-gray-400 hover:bg-primary/100 duration-200 hover:text-black hover:font-semibold mt-1" className="relative flex justify-start cursor-pointer select-none py-2 pl-10 pr-4 rounded-md text-gray-400 hover:bg-primary/100 duration-200 hover:text-black hover:font-semibold mt-1"
> >
<span className="rounded-lg absolute inset-y-0 left-0 flex items-center pl-3 pr-4"> <span className="rounded-lg absolute inset-y-0 left-0 flex items-center pl-3 pr-4">
<FontAwesomeIcon icon={faPlus} className="ml-1" /> <FontAwesomeIcon
icon={faPlus}
className="ml-1"
/>
</span> </span>
<div className="text-sm ml-1"> <div className="text-sm ml-1">
Invite Members {t("common:nav.user.invite")}
</div> </div>
</div> </div>
</button> </button>
</div> </div>
{orgs?.length > 1 && <div className="px-1 pt-1"> {orgs?.length > 1 && (
<div className="text-gray-400 self-start ml-2 mt-2 text-xs font-semibold tracking-wide"> <div className="px-1 pt-1">
OTHER ORGANIZATIONS <div className="text-gray-400 self-start ml-2 mt-2 text-xs font-semibold tracking-wide">
</div> {t(
<div className="flex flex-col items-start px-1 mt-3 mb-2"> "common:nav.user.other-organizations"
{orgs )}
.filter( </div>
(org) => <div className="flex flex-col items-start px-1 mt-3 mb-2">
org._id != {orgs
localStorage.getItem( .filter(
"orgData.id" (org) =>
) org._id !=
) localStorage.getItem(
.map((org) => ( "orgData.id"
<div )
key={guidGenerator()} )
onClick={() => { .map((org) => (
localStorage.setItem( <div
"orgData.id", key={guidGenerator()}
org._id onClick={() => {
); localStorage.setItem(
router.reload(); "orgData.id",
}} org._id
className="flex flex-row justify-start items-center hover:bg-white/5 w-full p-1.5 cursor-pointer rounded-md" );
> router.reload();
<div className="bg-white/10 h-7 w-8 rounded-md flex items-center justify-center text-gray-300"> }}
{org.name.charAt(0)} className="flex flex-row justify-start items-center hover:bg-white/5 w-full p-1.5 cursor-pointer rounded-md"
>
<div className="bg-white/10 h-7 w-8 rounded-md flex items-center justify-center text-gray-300">
{org.name.charAt(0)}
</div>
<div className="flex items-center justify-between w-full">
<p className="text-gray-300 px-2 text-sm">
{org.name}
</p>
</div>
</div> </div>
<div className="flex items-center justify-between w-full"> ))}
<p className="text-gray-300 px-2 text-sm"> </div>
{org.name}
</p>
</div>
</div>
))}
</div> </div>
</div>} )}
<div className="px-1 py-1"> <div className="px-1 py-1">
<Menu.Item> <Menu.Item>
{({ active }) => ( {({ active }) => (
@@ -296,7 +315,7 @@ export default function Navbar({ onButtonPressed }) {
className="text-lg ml-1.5 mr-3" className="text-lg ml-1.5 mr-3"
icon={faRightFromBracket} icon={faRightFromBracket}
/> />
Log Out {t("common:logout")}
</div> </div>
</button> </button>
)} )}
+25
View File
@@ -11,11 +11,36 @@
"og-description": "Infisical a simple end-to-end encrypted platform that enables teams to sync and manage API-keys and environemntal variables. Works with Node.js, Next.js, Gatsby, Nest.js..." "og-description": "Infisical a simple end-to-end encrypted platform that enables teams to sync and manage API-keys and environemntal variables. Works with Node.js, Next.js, Gatsby, Nest.js..."
} }
}, },
"nav": {
"support": {
"slack": "[NEW] Join Slack Forum",
"docs": "Read Docs",
"issue": "Open a Github Issue",
"email": "Send us an Email"
},
"user": {
"signed-in-as": "SIGNED IN AS",
"current-organization": "CURRENT ORGANIZATION",
"usage-billing": "Usage & Billing",
"invite": "Invite Members",
"other-organizations": "OTHER ORGANIZATION"
},
"menu": {
"project": "PROJECT",
"secrets": "Secrets",
"members": "Members",
"integrations": "Integrations",
"project-settings": "Project Settings"
}
},
"error_project-already-exists": "A project with this name already exists.",
"need-login": " To use Infisical, please log in through a device with larger dimensions. ",
"email": "Email", "email": "Email",
"password": "Password", "password": "Password",
"verify": "Verify", "verify": "Verify",
"first-name": "First Name", "first-name": "First Name",
"last-name": "Last Name", "last-name": "Last Name",
"logout": "Log Out",
"validate-required": "Please input your {{name}}", "validate-required": "Please input your {{name}}",
"password-validate": "Password should contain at least:", "password-validate": "Password should contain at least:",
"password-validate-length": "14 characters", "password-validate-length": "14 characters",