diff --git a/app/[locale]/(authenticated)/profile/actions.ts b/app/[locale]/(authenticated)/profile/actions.ts new file mode 100644 index 0000000..b240d3c --- /dev/null +++ b/app/[locale]/(authenticated)/profile/actions.ts @@ -0,0 +1,87 @@ +"use server"; + +import * as z from "zod"; +import { Scrypt } from "oslo/password"; +import { getLocale, getTranslations } from "next-intl/server"; +import { FormState } from "@/app/components/Form"; +import { revalidatePath } from "next/cache"; +import { parseForm } from "@/app/form-parser"; +import mailer from "@/app/mailer"; +import { query } from "@/shared/db"; + +const scrypt = new Scrypt(); + +const profileValidationSchema = z + .object({ + email: z.string().email().min(1), + name: z.string().min(1), + password: z.union([z.string().min(8), z.literal("")]).optional(), + confirm_password: z.string().optional(), + }) + .refine((data) => data.password === data.confirm_password, { + path: ["confirm_password"], + }); + +export default async function updateProfile( + _prevState: FormState, + data: FormData +): Promise { + const t = await getTranslations("ProfileView"); + const parsedData = parseForm(data) as Record; + const request = profileValidationSchema.safeParse(parsedData, { + errorMap: (error) => { + switch (error.path[0]) { + case "email": + if (error.code === "invalid_string") { + return { message: t("errors.email_format") }; + } else { + return { message: t("errors.email_required") }; + } + case "name": + return { message: t("errors.name_required") }; + case "password": + return { message: t("errors.password_format") }; + case "confirm_password": + return { message: t("errors.password_confirmation") }; + default: + return { message: "Invalid" }; + } + }, + }); + if (!request.success) { + return { state: "error", validation: request.error.flatten().fieldErrors }; + } + + if (parsedData.password) { + await mailer.sendEmail({ + userId: parsedData.user_id, + subject: "Password Changed", + text: `Your password for Global Bible Tools has changed.`, + html: `Your password for Global Bible Tools has changed.`, + }); + await query( + `UPDATE "User" + SET "email" = $1, + "name" = $2, + "hashedPassword" = $3 + WHERE "id" = $4`, + [ + parsedData.email, + parsedData.name, + await scrypt.hash(parsedData.password), + parsedData.user_id, + ] + ); + } else { + await query( + `UPDATE "User" + SET "email" = $1, + "name" = $2 + WHERE "id" = $3`, + [parsedData.email, parsedData.name, parsedData.user_id] + ); + } + + revalidatePath(`/${await getLocale()}/profile`); + return { state: "success", message: t("profile_updated") }; +} diff --git a/app/[locale]/(authenticated)/profile/page.tsx b/app/[locale]/(authenticated)/profile/page.tsx index 54e20fa..91b4947 100644 --- a/app/[locale]/(authenticated)/profile/page.tsx +++ b/app/[locale]/(authenticated)/profile/page.tsx @@ -1,12 +1,108 @@ -import { query } from "@/shared/db" -import { verifySession } from "@/app/session" +import FormLabel from "@/app/components/FormLabel"; +import TextInput from "@/app/components/TextInput"; +import ViewTitle from "@/app/components/ViewTitle"; +import { verifySession } from "@/app/session"; +import { ResolvingMetadata, Metadata } from "next"; +import { getTranslations } from "next-intl/server"; +import FieldError from "@/app/components/FieldError"; +import Button from "@/app/components/Button"; +import { notFound } from "next/navigation"; +import Form from "@/app/components/Form"; +import updateProfile from "./actions"; +import { query } from "@/shared/db"; + +export async function generateMetadata( + _: any, + parent: ResolvingMetadata +): Promise { + const t = await getTranslations("ProfileView"); + const { title } = await parent; + + return { + title: `${t("title")} | ${title?.absolute}`, + }; +} export default async function ProfileView() { - const session = await verifySession() - const result = session ? await query<{ name?: string, email: string }>(`SELECT name, email FROM "User" WHERE id = $1`, [session.user.id]) : undefined - const user = result?.rows[0] + const session = await verifySession(); + if (!session) notFound(); + + const result = await query<{ name?: string; email: string }>( + `SELECT name, email FROM "User" WHERE id = $1`, + [session.user.id] + ); + const user = result?.rows[0]; + + const t = await getTranslations("ProfileView"); - return
- {user && `${user.name} - ${user.email}`} + return ( +
+
+ {t("title")} +
+ +
+ {t("form.email")} + + +
+
+ {t("form.name")} + + +
+
+ {t("form.password")} + + +
+
+ + {t("form.confirm_password")} + + + +
+
+ +
+
+
+ ); } diff --git a/app/[locale]/(public)/reset-password/page.tsx b/app/[locale]/(public)/reset-password/page.tsx index cd7fa43..d105551 100644 --- a/app/[locale]/(public)/reset-password/page.tsx +++ b/app/[locale]/(public)/reset-password/page.tsx @@ -2,86 +2,96 @@ import ModalView, { ModalViewTitle } from "@/app/components/ModalView"; import Button from "@/app/components/Button"; import FormLabel from "@/app/components/FormLabel"; import TextInput from "@/app/components/TextInput"; -import FieldError from '@/app/components/FieldError'; -import { verifySession } from '@/app/session'; -import { notFound, redirect, RedirectType } from 'next/navigation'; -import { getLocale, getTranslations } from 'next-intl/server'; +import FieldError from "@/app/components/FieldError"; +import { verifySession } from "@/app/session"; +import { notFound, redirect, RedirectType } from "next/navigation"; +import { getLocale, getTranslations } from "next-intl/server"; import { Metadata, ResolvingMetadata } from "next"; import { query } from "@/shared/db"; import { resetPassword } from "./actions"; import Form from "@/app/components/Form"; -export async function generateMetadata(_: any, parent: ResolvingMetadata): Promise { - const t = await getTranslations("ResetPasswordPage") - const { title } = await parent +export async function generateMetadata( + _: any, + parent: ResolvingMetadata +): Promise { + const t = await getTranslations("ResetPasswordPage"); + const { title } = await parent; return { - title: `${t("title")} | ${title?.absolute}` - } + title: `${t("title")} | ${title?.absolute}`, + }; } -export default async function ResetPasswordPage({ searchParams }: { searchParams: { token?: string } }) { - const t = await getTranslations('ResetPasswordPage'); - const locale = await getLocale() +export default async function ResetPasswordPage({ + searchParams, +}: { + searchParams: { token?: string }; +}) { + const t = await getTranslations("ResetPasswordPage"); + const locale = await getLocale(); - const session = await verifySession(); - if (session) { - redirect(`/${locale}/interlinear`, RedirectType.replace) - } + const session = await verifySession(); + if (session) { + redirect(`/${locale}/interlinear`, RedirectType.replace); + } - if (!searchParams.token) { - notFound() - } + if (!searchParams.token) { + notFound(); + } - const tokenQuery = await query( - `SELECT FROM "ResetPasswordToken" WHERE token = $1 + const tokenQuery = await query( + `SELECT FROM "ResetPasswordToken" WHERE token = $1 AND expires > (EXTRACT(EPOCH FROM CURRENT_TIMESTAMP)::bigint * 1000::bigint) `, - [searchParams.token] - ) - if (tokenQuery.rows.length === 0) { - notFound() - } + [searchParams.token] + ); + if (tokenQuery.rows.length === 0) { + notFound(); + } - return - {t("actions.log_in")} + {t("actions.log_in")} } > - {t('title')} -
- -
- - {t('form.password')} - - - -
-
- - {t('form.confirm_password').toUpperCase()} - - - -
- -
+ {t("title")} +
+ +
+ {t("form.password")} + + +
+
+ + {t("form.confirm_password").toUpperCase()} + + + +
+ +
+ ); } diff --git a/messages/en.json b/messages/en.json index a7b412b..2092389 100644 --- a/messages/en.json +++ b/messages/en.json @@ -1,425 +1,444 @@ -{ - "AcceptInvitePage": { - "title": "Accept Invite", - "form": { - "email": "Email", - "first_name": "First Name", - "last_name": "Last Name", - "password": "Password", - "confirm_password": "Confirm Password", - "submit": "Sign Up" - }, - "errors": { - "email_required": "Please enter your email.", - "email_format": "Please enter a valid email.", - "password_required": "Please enter your password.", - "password_format": "Please enter a password with at least 8 characters.", - "confirm_password_required": "Please enter your password again.", - "confirm_password_mismatch": "Your password does not match.", - "first_name_required": "Please enter your first name.", - "last_name_required": "Please enter your last name." - }, - "not_found": "We couldn't find your invitation to Global Bible Tools.", - "actions": { - "log_in": "Log In" - } - }, - "AdminLanguagesPage": { - "title": "Languages", - "headers": { - "language": "Language", - "ot_progress": "OT Progress", - "nt_progress": "NT Progress" - }, - "actions": { - "add_language": "Add Language", - "manage": "Manage" - } - }, - "AdminLayout": { - "title": "Admin", - "links": { - "languages": "Languages", - "users": "Users" - } - }, - "AdminUsersPage": { - "title": "Users", - "links": { - "add_user": "Invite User" - }, - "headers": { - "name": "Name", - "email": "Email", - "role": "Role" - }, - "errors": { - "invalid_request": "Invalid request" - }, - "role": "{role, select, ADMIN {Admin} other {}}", - "email_status": "{status, select, BOUNCED {Bounced} COMPLAINED {Complained} UNVERIFIED {Unverified} other {}}" - }, - "AuthenticatedLayout": { - "app_name": "Global Bible Tools", - "links": { - "interlinear": "Interlinear", - "admin": "Admin", - "profile": "Profile", - "log_out": "Log Out" - } - }, - "Error": { - "tab": "Error", - "title": "Something went wrong!", - "help": "An unknown error has occurred. Please contact support for assistance.", - "actions": { - "reload": "Reload", - "log_in": "Log In" - } - }, - "Flash": { - "close": "Close", - "success": "Success", - "error": "Error" - }, - "ForgotPasswordPage": { - "title": "Forgot Password", - "form": { - "email": "Email", - "submit": "Reset Password" - }, - "errors": { - "email_required": "Please enter your email." - }, - "actions": { - "log_in": "Log In" - } - }, - "InviteLanguageUserPage": { - "title": "Invite User", - "form": { - "email": "Email", - "role": "Role", - "submit": "Invite" - }, - "role": "{role, select, ADMIN {Admin} TRANSLATOR {Translator} other {}}" - }, - "InviteUserPage": { - "title": "Invite User", - "form": { - "email": "Email", - "submit": "Invite" - }, - "errors": { - "email_required": "Please enter your email.", - "email_format": "Please enter a valid email.", - "user_exists": "This user already exists." - } - }, - "LanguageImportPage": { - "title": "Import", - "form": { - "language": "Language", - "submit": "Import" - }, - "errors": { - "language_required": "Please select a language to import." - }, - "status": { - "fail": "Error importing glosses", - "running": "Gloss import running", - "success": "Imported glosses successfully!" - }, - "actions": { - "reset": "Import Again" - } - }, - "LanguageLayout": { - "links": { - "settings": "Settings", - "users": "Users", - "reports": "Reports", - "import": "Import" - } - }, - "LanguageReportsPage": { - "title": "Reports" - }, - "LanguageSettingsPage": { - "title": "Settings", - "form": { - "name": "Name", - "code": "Code", - "font": "Font", - "text_direction": "Text Direction", - "text_direction_option": "{dir, select, ltr {Left to Right} rtl {Right to Left} other {}}", - "translation_placeholder": "Select Translation" - }, - "errors": { - "name_required": "Please enter the language name.", - "font_required": "Please select a font.", - "text_direction_required": "Please select a text direction." - }, - "headings": { - "identification": "Identification", - "text": "Text", - "bible_translation": "Bible Translations" - }, - "name_description": "The name of the language is shown throughout the app. We prefer it to be written in the language rather than English.", - "code_description": "The language code follows ISO 639-3 which uniquely identifies languages across systems.", - "text_description": "Configure the text of the language as you would like it to appear in the platform.", - "translation_description": "These translation are used in the platform when showing a passage from the Bible.", - "saving": "Saving", - "saved": "Saved" - }, - "LanguageUsersPage": { - "title": "Users", - "links": { - "invite_user": "Invite User", - "remove": "Remove User" - }, - "headers": { - "name": "Name", - "role": "Role" - }, - "role": "{role, select, ADMIN {Admin} TRANSLATOR {Translator} other {}}" - }, - "LoginPage": { - "title": "Log In", - "form": { - "email": "Email", - "password": "Password", - "submit": "Log In" - }, - "errors": { - "email_required": "Please enter your email.", - "password_required": "Please enter your password." - }, - "forgot_password": "Forgot Password?" - }, - "ModalView": { - "app_name": "Global Bible Tools" - }, - "NewLanguagePage": { - "title": "Add Language", - "errors": { - "name_required": "Please enter the language name.", - "code_size": "The language code must be 3 characters.", - "language_exists": "This language already exists." - }, - "form": { - "code": "Code", - "name": "Name", - "submit": "Add" - } - }, - "ResetPasswordPage": { - "title": "Reset Password", - "form": { - "password": "Password", - "confirm_password": "Confirm Password", - "submit": "Reset Password" - }, - "errors": { - "password_required": "Please enter your password.", - "password_format": "Please enter a password with at least 8 characters.", - "confirm_password_required": "Please enter your password again.", - "confirm_password_mismatch": "Your password does not match." - }, - "not_found": "We couldn't find your password reset.", - "actions": { - "log_in": "Log In" - } - }, - "RichTextInput": { - "bold_tooltip": "Bold", - "italic_tooltip": "Italic", - "strike_tooltip": "Strikethough", - "bullet_list_tooltip": "Bullet List", - "ordered_list_tooltip": "Ordered List", - "indent_tooltip": "Indent", - "outdent_tooltip": "Outdent" - }, - "RootLayout": { - "app_name": "Global Bible Tools" - }, - "RootNotFoundPage": { - "title": "Not Found" - }, - "TranslateWord": { - "approve_tooltip": "Approve", - "revoke_tooltip": "Revoke", - "approved": "Approved", - "saving": "Saving", - "open_note_tooltip": "Jump to Note" - }, - "TranslationProgressBar": { - "progress": "{approvedCount}/{wordCount} words ({percent}%)" - }, - "TranslationSidebar": { - "tabs": { - "lexicon": "Lexicon", - "notes": "Notes" - }, - "close": "Close", - "notes": { - "translator_notes": "Translator Notes", - "footnotes": "Footnotes", - "note_description": "Updated at {timestamp} by {authorName}", - "saving": "Saving..." - }, - "book_names": [ - "Genesis", - "Exodus", - "Leviticus", - "Numbers", - "Deuteronomy", - "Joshua", - "Judges", - "Ruth", - "1 Samuel", - "2 Samuel", - "1 Kings", - "2 Kings", - "1 Chronicles", - "2 Chronicles", - "Ezra", - "Nehemiah", - "Esther", - "Job", - "Psalm", - "Proverbs", - "Ecclesiastes", - "Song of Songs", - "Isaiah", - "Jeremiah", - "Lamentations", - "Ezekiel", - "Daniel", - "Hosea", - "Joel", - "Amos", - "Obadiah", - "Jonah", - "Micah", - "Nahum", - "Habakkuk", - "Zephaniah", - "Haggai", - "Zechariah", - "Malachi", - "Matthew", - "Mark", - "Luke", - "John", - "Acts", - "Romans", - "1 Corinthians", - "2 Corinthians", - "Galatians", - "Ephesians", - "Philippians", - "Colossians", - "1 Thessalonians", - "2 Thessalonians", - "1 Timothy", - "2 Timothy", - "Titus", - "Philemon", - "Hebrews", - "James", - "1 Peter", - "2 Peter", - "1 John", - "2 John", - "3 John", - "Jude", - "Revelation" - ] - }, - "TranslationToolbar": { - "verse": "Verse", - "previous_verse": "Previous Verse", - "next_verse": "Next Verse", - "next_unapproved": "Next Unapproved", - "language": "Language", - "manage_language": "Manage", - "approve_all": "Approve All", - "link_words": "Link Words", - "unlink_words": "Unlink Words", - "verse_reference": "{bookId, select, 1 {Genesis} 2 {Exodus} 3 {Leviticus} 4 {Numbers} 5 {Deuteronomy} 6 {Joshua} 7 {Judges} 8 {Ruth} 9 {1 Samuel} 10 {2 Samuel} 11 {1 Kings} 12 {2 Kings} 13 {1 Chronicles} 14 {2 Chronicles} 15 {Ezra} 16 {Nehemiah} 17 {Esther} 18 {Job} 19 {Psalm} 20 {Proverbs} 21 {Ecclesiastes} 22 {Song of Songs} 23 {Isaiah} 24 {Jeremiah} 25 {Lamentations} 26 {Ezekiel} 27 {Daniel} 28 {Hosea} 29 {Joel} 30 {Amos} 31 {Obadiah} 32 {Jonah} 33 {Micah} 34 {Nahum} 35 {Habakkuk} 36 {Zephaniah} 37 {Haggai} 38 {Zechariah} 39 {Malachi} 40 {Matthew} 41 {Mark} 42 {Luke} 43 {John} 44 {Acts} 45 {Romans} 46 {1 Corinthians} 47 {2 Corinthians} 48 {Galatians} 49 {Ephesians} 50 {Philippians} 51 {Colossians} 52 {1 Thessalonians} 53 {2 Thessalonians} 54 {1 Timothy} 55 {2 Timothy} 56 {Titus} 57 {Philemon} 58 {Hebrews} 59 {James} 60 {1 Peter} 61 {2 Peter} 62 {1 John} 63 {2 John} 64 {3 John} 65 {Jude} 66 {Revelation} other {}} {chapter}:{verse}", - "book_names": [ - "Genesis", - "Exodus", - "Leviticus", - "Numbers", - "Deuteronomy", - "Joshua", - "Judges", - "Ruth", - "1 Samuel", - "2 Samuel", - "1 Kings", - "2 Kings", - "1 Chronicles", - "2 Chronicles", - "Ezra", - "Nehemiah", - "Esther", - "Job", - "Psalm", - "Proverbs", - "Ecclesiastes", - "Song of Songs", - "Isaiah", - "Jeremiah", - "Lamentations", - "Ezekiel", - "Daniel", - "Hosea", - "Joel", - "Amos", - "Obadiah", - "Jonah", - "Micah", - "Nahum", - "Habakkuk", - "Zephaniah", - "Haggai", - "Zechariah", - "Malachi", - "Matthew", - "Mark", - "Luke", - "John", - "Acts", - "Romans", - "1 Corinthians", - "2 Corinthians", - "Galatians", - "Ephesians", - "Philippians", - "Colossians", - "1 Thessalonians", - "2 Thessalonians", - "1 Timothy", - "2 Timothy", - "Titus", - "Philemon", - "Hebrews", - "James", - "1 Peter", - "2 Peter", - "1 John", - "2 John", - "3 John", - "Jude", - "Revelation" - ] - }, - "VersesPreview": { - "reference": "{bookId, select, 1 {Genesis} 2 {Exodus} 3 {Leviticus} 4 {Numbers} 5 {Deuteronomy} 6 {Joshua} 7 {Judges} 8 {Ruth} 9 {1 Samuel} 10 {2 Samuel} 11 {1 Kings} 12 {2 Kings} 13 {1 Chronicles} 14 {2 Chronicles} 15 {Ezra} 16 {Nehemiah} 17 {Esther} 18 {Job} 19 {Psalm} 20 {Proverbs} 21 {Ecclesiastes} 22 {Song of Songs} 23 {Isaiah} 24 {Jeremiah} 25 {Lamentations} 26 {Ezekiel} 27 {Daniel} 28 {Hosea} 29 {Joel} 30 {Amos} 31 {Obadiah} 32 {Jonah} 33 {Micah} 34 {Nahum} 35 {Habakkuk} 36 {Zephaniah} 37 {Haggai} 38 {Zechariah} 39 {Malachi} 40 {Matthew} 41 {Mark} 42 {Luke} 43 {John} 44 {Acts} 45 {Romans} 46 {1 Corinthians} 47 {2 Corinthians} 48 {Galatians} 49 {Ephesians} 50 {Philippians} 51 {Colossians} 52 {1 Thessalonians} 53 {2 Thessalonians} 54 {1 Timothy} 55 {2 Timothy} 56 {Titus} 57 {Philemon} 58 {Hebrews} 59 {James} 60 {1 Peter} 61 {2 Peter} 62 {1 John} 63 {2 John} 64 {3 John} 65 {Jude} 66 {Revelation} other {}} {chapterNumber}:{verseNumber}", - "not_found": "Not Found", - "close": "Close" - } -} +{ + "AcceptInvitePage": { + "title": "Accept Invite", + "form": { + "email": "Email", + "first_name": "First Name", + "last_name": "Last Name", + "password": "Password", + "confirm_password": "Confirm Password", + "submit": "Sign Up" + }, + "errors": { + "email_required": "Please enter your email.", + "email_format": "Please enter a valid email.", + "password_required": "Please enter your password.", + "password_format": "Please enter a password with at least 8 characters.", + "confirm_password_required": "Please enter your password again.", + "confirm_password_mismatch": "Your password does not match.", + "first_name_required": "Please enter your first name.", + "last_name_required": "Please enter your last name." + }, + "not_found": "We couldn't find your invitation to Global Bible Tools.", + "actions": { + "log_in": "Log In" + } + }, + "AdminLanguagesPage": { + "title": "Languages", + "headers": { + "language": "Language", + "ot_progress": "OT Progress", + "nt_progress": "NT Progress" + }, + "actions": { + "add_language": "Add Language", + "manage": "Manage" + } + }, + "AdminLayout": { + "title": "Admin", + "links": { + "languages": "Languages", + "users": "Users" + } + }, + "AdminUsersPage": { + "title": "Users", + "links": { + "add_user": "Invite User" + }, + "headers": { + "name": "Name", + "email": "Email", + "role": "Role" + }, + "errors": { + "invalid_request": "Invalid request" + }, + "role": "{role, select, ADMIN {Admin} other {}}", + "email_status": "{status, select, BOUNCED {Bounced} COMPLAINED {Complained} UNVERIFIED {Unverified} other {}}" + }, + "AuthenticatedLayout": { + "app_name": "Global Bible Tools", + "links": { + "interlinear": "Interlinear", + "admin": "Admin", + "profile": "Profile", + "log_out": "Log Out" + } + }, + "Error": { + "tab": "Error", + "title": "Something went wrong!", + "help": "An unknown error has occurred. Please contact support for assistance.", + "actions": { + "reload": "Reload", + "log_in": "Log In" + } + }, + "Flash": { + "close": "Close", + "success": "Success", + "error": "Error" + }, + "ForgotPasswordPage": { + "title": "Forgot Password", + "form": { + "email": "Email", + "submit": "Reset Password" + }, + "errors": { + "email_required": "Please enter your email." + }, + "actions": { + "log_in": "Log In" + } + }, + "InviteLanguageUserPage": { + "title": "Invite User", + "form": { + "email": "Email", + "role": "Role", + "submit": "Invite" + }, + "role": "{role, select, ADMIN {Admin} TRANSLATOR {Translator} other {}}" + }, + "InviteUserPage": { + "title": "Invite User", + "form": { + "email": "Email", + "submit": "Invite" + }, + "errors": { + "email_required": "Please enter your email.", + "email_format": "Please enter a valid email.", + "user_exists": "This user already exists." + } + }, + "LanguageImportPage": { + "title": "Import", + "form": { + "language": "Language", + "submit": "Import" + }, + "errors": { + "language_required": "Please select a language to import." + }, + "status": { + "fail": "Error importing glosses", + "running": "Gloss import running", + "success": "Imported glosses successfully!" + }, + "actions": { + "reset": "Import Again" + } + }, + "LanguageLayout": { + "links": { + "settings": "Settings", + "users": "Users", + "reports": "Reports", + "import": "Import" + } + }, + "LanguageReportsPage": { + "title": "Reports" + }, + "LanguageSettingsPage": { + "title": "Settings", + "form": { + "name": "Name", + "code": "Code", + "font": "Font", + "text_direction": "Text Direction", + "text_direction_option": "{dir, select, ltr {Left to Right} rtl {Right to Left} other {}}", + "translation_placeholder": "Select Translation" + }, + "errors": { + "name_required": "Please enter the language name.", + "font_required": "Please select a font.", + "text_direction_required": "Please select a text direction." + }, + "headings": { + "identification": "Identification", + "text": "Text", + "bible_translation": "Bible Translations" + }, + "name_description": "The name of the language is shown throughout the app. We prefer it to be written in the language rather than English.", + "code_description": "The language code follows ISO 639-3 which uniquely identifies languages across systems.", + "text_description": "Configure the text of the language as you would like it to appear in the platform.", + "translation_description": "These translation are used in the platform when showing a passage from the Bible.", + "saving": "Saving", + "saved": "Saved" + }, + "LanguageUsersPage": { + "title": "Users", + "links": { + "invite_user": "Invite User", + "remove": "Remove User" + }, + "headers": { + "name": "Name", + "role": "Role" + }, + "role": "{role, select, ADMIN {Admin} TRANSLATOR {Translator} other {}}" + }, + "LoginPage": { + "title": "Log In", + "form": { + "email": "Email", + "password": "Password", + "submit": "Log In" + }, + "errors": { + "email_required": "Please enter your email.", + "password_required": "Please enter your password." + }, + "forgot_password": "Forgot Password?" + }, + "ModalView": { + "app_name": "Global Bible Tools" + }, + "NewLanguagePage": { + "title": "Add Language", + "errors": { + "name_required": "Please enter the language name.", + "code_size": "The language code must be 3 characters.", + "language_exists": "This language already exists." + }, + "form": { + "code": "Code", + "name": "Name", + "submit": "Add" + } + }, + "ProfileView": { + "form": { + "confirm_password": "Confirm Password", + "email": "Email", + "name": "Name", + "password": "Password", + "submit": "Update" + }, + "errors": { + "email_required": "Please enter your email.", + "email_format": "Please enter a valid email.", + "name_required": "Please input your name", + "password_confirmation": "Your password confirmation does not match.", + "password_format": "Your password should be at least 8 characters." + }, + "profile_updated": "Profile updated successfully!", + "title": "Profile", + "update_profile": "Update Profile" + }, + "ResetPasswordPage": { + "title": "Reset Password", + "form": { + "password": "Password", + "confirm_password": "Confirm Password", + "submit": "Reset Password" + }, + "errors": { + "password_required": "Please enter your password.", + "password_format": "Please enter a password with at least 8 characters.", + "confirm_password_required": "Please enter your password again.", + "confirm_password_mismatch": "Your password does not match." + }, + "not_found": "We couldn't find your password reset.", + "actions": { + "log_in": "Log In" + } + }, + "RichTextInput": { + "bold_tooltip": "Bold", + "italic_tooltip": "Italic", + "strike_tooltip": "Strikethough", + "bullet_list_tooltip": "Bullet List", + "ordered_list_tooltip": "Ordered List", + "indent_tooltip": "Indent", + "outdent_tooltip": "Outdent" + }, + "RootLayout": { + "app_name": "Global Bible Tools" + }, + "RootNotFoundPage": { + "title": "Not Found" + }, + "TranslateWord": { + "approve_tooltip": "Approve", + "revoke_tooltip": "Revoke", + "approved": "Approved", + "saving": "Saving", + "open_note_tooltip": "Jump to Note" + }, + "TranslationProgressBar": { + "progress": "{approvedCount}/{wordCount} words ({percent}%)" + }, + "TranslationSidebar": { + "tabs": { + "lexicon": "Lexicon", + "notes": "Notes" + }, + "close": "Close", + "notes": { + "translator_notes": "Translator Notes", + "footnotes": "Footnotes", + "note_description": "Updated at {timestamp} by {authorName}", + "saving": "Saving..." + }, + "book_names": [ + "Genesis", + "Exodus", + "Leviticus", + "Numbers", + "Deuteronomy", + "Joshua", + "Judges", + "Ruth", + "1 Samuel", + "2 Samuel", + "1 Kings", + "2 Kings", + "1 Chronicles", + "2 Chronicles", + "Ezra", + "Nehemiah", + "Esther", + "Job", + "Psalm", + "Proverbs", + "Ecclesiastes", + "Song of Songs", + "Isaiah", + "Jeremiah", + "Lamentations", + "Ezekiel", + "Daniel", + "Hosea", + "Joel", + "Amos", + "Obadiah", + "Jonah", + "Micah", + "Nahum", + "Habakkuk", + "Zephaniah", + "Haggai", + "Zechariah", + "Malachi", + "Matthew", + "Mark", + "Luke", + "John", + "Acts", + "Romans", + "1 Corinthians", + "2 Corinthians", + "Galatians", + "Ephesians", + "Philippians", + "Colossians", + "1 Thessalonians", + "2 Thessalonians", + "1 Timothy", + "2 Timothy", + "Titus", + "Philemon", + "Hebrews", + "James", + "1 Peter", + "2 Peter", + "1 John", + "2 John", + "3 John", + "Jude", + "Revelation" + ] + }, + "TranslationToolbar": { + "verse": "Verse", + "previous_verse": "Previous Verse", + "next_verse": "Next Verse", + "next_unapproved": "Next Unapproved", + "language": "Language", + "manage_language": "Manage", + "approve_all": "Approve All", + "link_words": "Link Words", + "unlink_words": "Unlink Words", + "verse_reference": "{bookId, select, 1 {Genesis} 2 {Exodus} 3 {Leviticus} 4 {Numbers} 5 {Deuteronomy} 6 {Joshua} 7 {Judges} 8 {Ruth} 9 {1 Samuel} 10 {2 Samuel} 11 {1 Kings} 12 {2 Kings} 13 {1 Chronicles} 14 {2 Chronicles} 15 {Ezra} 16 {Nehemiah} 17 {Esther} 18 {Job} 19 {Psalm} 20 {Proverbs} 21 {Ecclesiastes} 22 {Song of Songs} 23 {Isaiah} 24 {Jeremiah} 25 {Lamentations} 26 {Ezekiel} 27 {Daniel} 28 {Hosea} 29 {Joel} 30 {Amos} 31 {Obadiah} 32 {Jonah} 33 {Micah} 34 {Nahum} 35 {Habakkuk} 36 {Zephaniah} 37 {Haggai} 38 {Zechariah} 39 {Malachi} 40 {Matthew} 41 {Mark} 42 {Luke} 43 {John} 44 {Acts} 45 {Romans} 46 {1 Corinthians} 47 {2 Corinthians} 48 {Galatians} 49 {Ephesians} 50 {Philippians} 51 {Colossians} 52 {1 Thessalonians} 53 {2 Thessalonians} 54 {1 Timothy} 55 {2 Timothy} 56 {Titus} 57 {Philemon} 58 {Hebrews} 59 {James} 60 {1 Peter} 61 {2 Peter} 62 {1 John} 63 {2 John} 64 {3 John} 65 {Jude} 66 {Revelation} other {}} {chapter}:{verse}", + "book_names": [ + "Genesis", + "Exodus", + "Leviticus", + "Numbers", + "Deuteronomy", + "Joshua", + "Judges", + "Ruth", + "1 Samuel", + "2 Samuel", + "1 Kings", + "2 Kings", + "1 Chronicles", + "2 Chronicles", + "Ezra", + "Nehemiah", + "Esther", + "Job", + "Psalm", + "Proverbs", + "Ecclesiastes", + "Song of Songs", + "Isaiah", + "Jeremiah", + "Lamentations", + "Ezekiel", + "Daniel", + "Hosea", + "Joel", + "Amos", + "Obadiah", + "Jonah", + "Micah", + "Nahum", + "Habakkuk", + "Zephaniah", + "Haggai", + "Zechariah", + "Malachi", + "Matthew", + "Mark", + "Luke", + "John", + "Acts", + "Romans", + "1 Corinthians", + "2 Corinthians", + "Galatians", + "Ephesians", + "Philippians", + "Colossians", + "1 Thessalonians", + "2 Thessalonians", + "1 Timothy", + "2 Timothy", + "Titus", + "Philemon", + "Hebrews", + "James", + "1 Peter", + "2 Peter", + "1 John", + "2 John", + "3 John", + "Jude", + "Revelation" + ] + }, + "VersesPreview": { + "reference": "{bookId, select, 1 {Genesis} 2 {Exodus} 3 {Leviticus} 4 {Numbers} 5 {Deuteronomy} 6 {Joshua} 7 {Judges} 8 {Ruth} 9 {1 Samuel} 10 {2 Samuel} 11 {1 Kings} 12 {2 Kings} 13 {1 Chronicles} 14 {2 Chronicles} 15 {Ezra} 16 {Nehemiah} 17 {Esther} 18 {Job} 19 {Psalm} 20 {Proverbs} 21 {Ecclesiastes} 22 {Song of Songs} 23 {Isaiah} 24 {Jeremiah} 25 {Lamentations} 26 {Ezekiel} 27 {Daniel} 28 {Hosea} 29 {Joel} 30 {Amos} 31 {Obadiah} 32 {Jonah} 33 {Micah} 34 {Nahum} 35 {Habakkuk} 36 {Zephaniah} 37 {Haggai} 38 {Zechariah} 39 {Malachi} 40 {Matthew} 41 {Mark} 42 {Luke} 43 {John} 44 {Acts} 45 {Romans} 46 {1 Corinthians} 47 {2 Corinthians} 48 {Galatians} 49 {Ephesians} 50 {Philippians} 51 {Colossians} 52 {1 Thessalonians} 53 {2 Thessalonians} 54 {1 Timothy} 55 {2 Timothy} 56 {Titus} 57 {Philemon} 58 {Hebrews} 59 {James} 60 {1 Peter} 61 {2 Peter} 62 {1 John} 63 {2 John} 64 {3 John} 65 {Jude} 66 {Revelation} other {}} {chapterNumber}:{verseNumber}", + "not_found": "Not Found", + "close": "Close" + } +} diff --git a/package-lock.json b/package-lock.json index 2fe6d4d..71e594e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5118,9 +5118,9 @@ } }, "node_modules/micromatch": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", - "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "dependencies": { "braces": "^3.0.3",