fix(i18n): add missing translation keys, update lang/locale logic

This commit is contained in:
Amruth Pillai 2022-03-11 08:43:20 +01:00
parent 8bc7d2599e
commit 7d8828a358
No known key found for this signature in database
GPG Key ID: E3C57DF9B80855AD
35 changed files with 124 additions and 113 deletions

24
.vscode/settings.json vendored
View File

@ -1,15 +1,25 @@
{ {
"css.validate": false, "css.validate": false,
"scss.validate": false,
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
"source.fixAll.eslint": true "source.fixAll.eslint": true
}, },
"editor.defaultFormatter": "esbenp.prettier-vscode", "editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true, "editor.formatOnSave": true,
"editor.wordWrap": "on", "editor.wordWrap": "on",
"eslint.workingDirectories": ["schema", "client", "server"], "eslint.workingDirectories": [
"i18n-ally.enabledFrameworks": ["i18next"], "schema",
"i18n-ally.localesPaths": ["client/public/locales"], "client",
"i18n-ally.sourceLanguage": "en", "server"
"i18n-ally.keystyle": "nested" ],
} "i18n-ally.enabledFrameworks": [
"react"
],
"i18n-ally.keystyle": "nested",
"i18n-ally.localesPaths": [
"client/public/locales"
],
"i18n-ally.namespace": true,
"i18n-ally.pathMatcher": "{locale}/{namespaces}.{ext}",
"i18n-ally.sortKeys": true,
"scss.validate": false
}

View File

@ -74,19 +74,19 @@ const ArtboardController: React.FC<ReactZoomPanPinchRef> = ({ zoomIn, zoomOut, c
})} })}
> >
<div className={styles.controller}> <div className={styles.controller}>
<Tooltip arrow placement="top" title={t<string>('builder.controller.tooltip.zoom-in')}> <Tooltip arrow placement="top" title={t('builder.controller.tooltip.zoom-in') as string}>
<ButtonBase onClick={() => zoomIn(0.25)}> <ButtonBase onClick={() => zoomIn(0.25)}>
<ZoomIn fontSize="medium" /> <ZoomIn fontSize="medium" />
</ButtonBase> </ButtonBase>
</Tooltip> </Tooltip>
<Tooltip arrow placement="top" title={t<string>('builder.controller.tooltip.zoom-out')}> <Tooltip arrow placement="top" title={t('builder.controller.tooltip.zoom-out') as string}>
<ButtonBase onClick={() => zoomOut(0.25)}> <ButtonBase onClick={() => zoomOut(0.25)}>
<ZoomOut fontSize="medium" /> <ZoomOut fontSize="medium" />
</ButtonBase> </ButtonBase>
</Tooltip> </Tooltip>
<Tooltip arrow placement="top" title={t<string>('builder.controller.tooltip.center-artboard')}> <Tooltip arrow placement="top" title={t('builder.controller.tooltip.center-artboard') as string}>
<ButtonBase onClick={() => centerView(0.95)}> <ButtonBase onClick={() => centerView(0.95)}>
<FilterCenterFocus fontSize="medium" /> <FilterCenterFocus fontSize="medium" />
</ButtonBase> </ButtonBase>
@ -96,7 +96,7 @@ const ArtboardController: React.FC<ReactZoomPanPinchRef> = ({ zoomIn, zoomOut, c
{isDesktop && ( {isDesktop && (
<> <>
<Tooltip arrow placement="top" title={t<string>('builder.controller.tooltip.toggle-orientation')}> <Tooltip arrow placement="top" title={t('builder.controller.tooltip.toggle-orientation') as string}>
<ButtonBase onClick={handleTogglePageOrientation}> <ButtonBase onClick={handleTogglePageOrientation}>
{orientation === 'vertical' ? ( {orientation === 'vertical' ? (
<AlignHorizontalCenter fontSize="medium" /> <AlignHorizontalCenter fontSize="medium" />
@ -106,13 +106,13 @@ const ArtboardController: React.FC<ReactZoomPanPinchRef> = ({ zoomIn, zoomOut, c
</ButtonBase> </ButtonBase>
</Tooltip> </Tooltip>
<Tooltip arrow placement="top" title={t<string>('builder.controller.tooltip.toggle-page-break-line')}> <Tooltip arrow placement="top" title={t('builder.controller.tooltip.toggle-page-break-line') as string}>
<ButtonBase onClick={handleTogglePageBreakLine}> <ButtonBase onClick={handleTogglePageBreakLine}>
<InsertPageBreak fontSize="medium" /> <InsertPageBreak fontSize="medium" />
</ButtonBase> </ButtonBase>
</Tooltip> </Tooltip>
<Tooltip arrow placement="top" title={t<string>('builder.controller.tooltip.toggle-sidebars')}> <Tooltip arrow placement="top" title={t('builder.controller.tooltip.toggle-sidebars') as string}>
<ButtonBase onClick={handleToggleSidebar}> <ButtonBase onClick={handleToggleSidebar}>
<ViewSidebar fontSize="medium" /> <ViewSidebar fontSize="medium" />
</ButtonBase> </ButtonBase>
@ -122,13 +122,13 @@ const ArtboardController: React.FC<ReactZoomPanPinchRef> = ({ zoomIn, zoomOut, c
</> </>
)} )}
<Tooltip arrow placement="top" title={t<string>('builder.controller.tooltip.copy-link')}> <Tooltip arrow placement="top" title={t('builder.controller.tooltip.copy-link') as string}>
<ButtonBase onClick={handleCopyLink}> <ButtonBase onClick={handleCopyLink}>
<Link fontSize="medium" /> <Link fontSize="medium" />
</ButtonBase> </ButtonBase>
</Tooltip> </Tooltip>
<Tooltip arrow placement="top" title={t<string>('builder.controller.tooltip.export-pdf')}> <Tooltip arrow placement="top" title={t('builder.controller.tooltip.export-pdf') as string}>
<ButtonBase onClick={handleExportPDF} disabled={isLoading}> <ButtonBase onClick={handleExportPDF} disabled={isLoading}>
<Download fontSize="medium" /> <Download fontSize="medium" />
</ButtonBase> </ButtonBase>

View File

@ -184,7 +184,7 @@ const Header = () => {
<ListItemText>{t('builder.header.menu.share-link')}</ListItemText> <ListItemText>{t('builder.header.menu.share-link')}</ListItemText>
</MenuItem> </MenuItem>
) : ( ) : (
<Tooltip arrow placement="right" title={t<string>('builder.header.menu.tooltips.share-link')}> <Tooltip arrow placement="right" title={t('builder.header.menu.tooltips.share-link') as string}>
<div> <div>
<MenuItem> <MenuItem>
<ListItemIcon> <ListItemIcon>
@ -196,7 +196,7 @@ const Header = () => {
</Tooltip> </Tooltip>
)} )}
<Tooltip arrow placement="right" title={t<string>('builder.header.menu.tooltips.delete')}> <Tooltip arrow placement="right" title={t('builder.header.menu.tooltips.delete') as string}>
<MenuItem onClick={handleDelete}> <MenuItem onClick={handleDelete}>
<ListItemIcon> <ListItemIcon>
<Delete className="scale-90" /> <Delete className="scale-90" />

View File

@ -58,14 +58,14 @@ const PhotoFilters = () => {
<div className="flex items-center"> <div className="flex items-center">
<FormControlLabel <FormControlLabel
label={t<string>('builder.leftSidebar.sections.basics.photo-filters.effects.grayscale.label')} label={t('builder.leftSidebar.sections.basics.photo-filters.effects.grayscale.label') as string}
control={ control={
<Checkbox color="secondary" checked={grayscale} onChange={(_, value) => handleSetGrayscale(value)} /> <Checkbox color="secondary" checked={grayscale} onChange={(_, value) => handleSetGrayscale(value)} />
} }
/> />
<FormControlLabel <FormControlLabel
label={t<string>('builder.leftSidebar.sections.basics.photo-filters.effects.border.label')} label={t('builder.leftSidebar.sections.basics.photo-filters.effects.border.label') as string}
control={<Checkbox color="secondary" checked={border} onChange={(_, value) => handleSetBorder(value)} />} control={<Checkbox color="secondary" checked={border} onChange={(_, value) => handleSetBorder(value)} />}
/> />
</div> </div>

View File

@ -67,8 +67,8 @@ const PhotoUpload: React.FC = () => {
<Tooltip <Tooltip
title={ title={
isEmpty(photo.url) isEmpty(photo.url)
? t<string>('builder.leftSidebar.sections.basics.photo-upload.tooltip.upload') ? (t('builder.leftSidebar.sections.basics.photo-upload.tooltip.upload') as string)
: t<string>('builder.leftSidebar.sections.basics.photo-upload.tooltip.remove') : (t('builder.leftSidebar.sections.basics.photo-upload.tooltip.remove') as string)
} }
> >
<Avatar sx={{ width: 96, height: 96 }} src={photo.url} /> <Avatar sx={{ width: 96, height: 96 }} src={photo.url} />

View File

@ -32,7 +32,7 @@ const SectionSettings: React.FC<Props> = ({ path }) => {
return ( return (
<div> <div>
<Tooltip title={t<string>('builder.common.columns.tooltip')}> <Tooltip title={t('builder.common.columns.tooltip') as string}>
<ButtonBase onClick={handleClick} sx={{ padding: 1, borderRadius: 1 }} className="opacity-50 hover:opacity-75"> <ButtonBase onClick={handleClick} sx={{ padding: 1, borderRadius: 1 }} className="opacity-50 hover:opacity-75">
<ViewWeek /> <span className="ml-1.5 text-xs">{columns}</span> <ViewWeek /> <span className="ml-1.5 text-xs">{columns}</span>
</ButtonBase> </ButtonBase>

View File

@ -62,7 +62,7 @@ const Layout = () => {
path="metadata.layout" path="metadata.layout"
name={t('builder.rightSidebar.sections.layout.heading')} name={t('builder.rightSidebar.sections.layout.heading')}
action={ action={
<Tooltip title={t<string>('builder.rightSidebar.sections.layout.tooltip.reset-layout')}> <Tooltip title={t('builder.rightSidebar.sections.layout.tooltip.reset-layout') as string}>
<IconButton onClick={handleResetLayout}> <IconButton onClick={handleResetLayout}>
<Restore /> <Restore />
</IconButton> </IconButton>
@ -81,7 +81,7 @@ const Layout = () => {
<div className={clsx(styles.delete, { hidden: pageIndex === 0 })}> <div className={clsx(styles.delete, { hidden: pageIndex === 0 })}>
<Tooltip <Tooltip
title={t<string>('builder.common.actions.delete', { token: t('builder.common.glossary.page') })} title={t('builder.common.actions.delete', { token: t('builder.common.glossary.page') }) as string}
> >
<IconButton size="small" onClick={() => handleDeletePage(pageIndex)}> <IconButton size="small" onClick={() => handleDeletePage(pageIndex)}>
<Close fontSize="small" /> <Close fontSize="small" />

View File

@ -20,25 +20,24 @@ import { useMutation } from 'react-query';
import Heading from '@/components/shared/Heading'; import Heading from '@/components/shared/Heading';
import ThemeSwitch from '@/components/shared/ThemeSwitch'; import ThemeSwitch from '@/components/shared/ThemeSwitch';
import { Language, languages } from '@/config/languages'; import { Language, languageMap, languages } from '@/config/languages';
import { ServerError } from '@/services/axios'; import { ServerError } from '@/services/axios';
import queryClient from '@/services/react-query'; import queryClient from '@/services/react-query';
import { loadSampleData, LoadSampleDataParams, resetResume, ResetResumeParams } from '@/services/resume'; import { loadSampleData, LoadSampleDataParams, resetResume, ResetResumeParams } from '@/services/resume';
import { setLanguage, setTheme, togglePageBreakLine, togglePageOrientation } from '@/store/build/buildSlice'; import { setTheme, togglePageBreakLine, togglePageOrientation } from '@/store/build/buildSlice';
import { useAppDispatch, useAppSelector } from '@/store/hooks'; import { useAppDispatch, useAppSelector } from '@/store/hooks';
import { setResumeState } from '@/store/resume/resumeSlice'; import { setResumeState } from '@/store/resume/resumeSlice';
import { dateFormatOptions } from '@/utils/date'; import { dateFormatOptions } from '@/utils/date';
const Settings = () => { const Settings = () => {
const router = useRouter();
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { locale, ...router } = useRouter();
const resume = useAppSelector((state) => state.resume); const resume = useAppSelector((state) => state.resume);
const theme = useAppSelector((state) => state.build.theme); const theme = useAppSelector((state) => state.build.theme);
const language = useAppSelector((state) => state.build.language);
const breakLine = useAppSelector((state) => state.build.page.breakLine); const breakLine = useAppSelector((state) => state.build.page.breakLine);
const orientation = useAppSelector((state) => state.build.page.orientation); const orientation = useAppSelector((state) => state.build.page.orientation);
@ -62,14 +61,12 @@ const Settings = () => {
dispatch(setResumeState({ path: 'metadata.date.format', value })); dispatch(setResumeState({ path: 'metadata.date.format', value }));
const handleChangeLanguage = (value: Language | null) => { const handleChangeLanguage = (value: Language | null) => {
const { pathname, asPath, query } = router; const { pathname, asPath, query, push } = router;
const code = value?.code || 'en'; const code = value?.code || 'en';
dayjs.locale(code);
dispatch(setLanguage({ language: code }));
document.cookie = `NEXT_LOCALE=${code}; path=/; expires=2147483647`; document.cookie = `NEXT_LOCALE=${code}; path=/; expires=2147483647`;
router.push({ pathname, query }, asPath, { locale: code }); push({ pathname, query }, asPath, { locale: code });
}; };
const handleLoadSampleData = async () => { const handleLoadSampleData = async () => {
@ -132,7 +129,7 @@ const Settings = () => {
disableClearable disableClearable
className="my-2 w-full" className="my-2 w-full"
options={languages} options={languages}
value={language} value={languageMap[locale ?? 'en']}
isOptionEqualToValue={(a, b) => a.code === b.code} isOptionEqualToValue={(a, b) => a.code === b.code}
onChange={(_, value) => handleChangeLanguage(value)} onChange={(_, value) => handleChangeLanguage(value)}
renderInput={(params) => <TextField {...params} />} renderInput={(params) => <TextField {...params} />}

View File

@ -63,7 +63,7 @@ const Sharing = () => {
<div className="mt-1 flex w-full"> <div className="mt-1 flex w-full">
<FormControlLabel <FormControlLabel
label={t<string>('builder.rightSidebar.sections.sharing.short-url.label')} label={t('builder.rightSidebar.sections.sharing.short-url.label') as string}
control={ control={
<Checkbox className="mr-1" checked={showShortUrl} onChange={(_, value) => setShowShortUrl(value)} /> <Checkbox className="mr-1" checked={showShortUrl} onChange={(_, value) => setShowShortUrl(value)} />
} }

View File

@ -161,7 +161,7 @@ const ResumePreview: React.FC<Props> = ({ resume }) => {
<ListItemText>{t('dashboard.resume.menu.share-link')}</ListItemText> <ListItemText>{t('dashboard.resume.menu.share-link')}</ListItemText>
</MenuItem> </MenuItem>
) : ( ) : (
<Tooltip arrow placement="right" title={t<string>('dashboard.resume.menu.tooltips.share-link')}> <Tooltip arrow placement="right" title={t('dashboard.resume.menu.tooltips.share-link') as string}>
<div> <div>
<MenuItem> <MenuItem>
<ListItemIcon> <ListItemIcon>
@ -173,7 +173,7 @@ const ResumePreview: React.FC<Props> = ({ resume }) => {
</Tooltip> </Tooltip>
)} )}
<Tooltip arrow placement="right" title={t<string>('dashboard.resume.menu.tooltips.delete')}> <Tooltip arrow placement="right" title={t('dashboard.resume.menu.tooltips.delete') as string}>
<MenuItem onClick={handleDelete}> <MenuItem onClick={handleDelete}>
<ListItemIcon> <ListItemIcon>
<DeleteOutline className="scale-90" /> <DeleteOutline className="scale-90" />

View File

@ -12,7 +12,7 @@ const ColorAvatar: React.FC<Props> = ({ color, size = 20, onClick }) => {
return ( return (
<IconButton onClick={handleClick}> <IconButton onClick={handleClick}>
<Avatar sx={{ bgcolor: color, width: size, height: size }}> </Avatar> <Avatar sx={{ bgcolor: color, width: size, height: size }}>&nbsp;</Avatar>
</IconButton> </IconButton>
); );
}; };

View File

@ -72,19 +72,19 @@ const Heading: React.FC<Props> = ({
})} })}
> >
{isEditable && ( {isEditable && (
<Tooltip title={t<string>('builder.common.tooltip.rename-section')}> <Tooltip title={t('builder.common.tooltip.rename-section') as string}>
<IconButton onClick={toggleEditMode}>{editMode ? <Check /> : <DriveFileRenameOutline />}</IconButton> <IconButton onClick={toggleEditMode}>{editMode ? <Check /> : <DriveFileRenameOutline />}</IconButton>
</Tooltip> </Tooltip>
)} )}
{isHideable && ( {isHideable && (
<Tooltip title={t<string>('builder.common.tooltip.toggle-visibility')}> <Tooltip title={t('builder.common.tooltip.toggle-visibility') as string}>
<IconButton onClick={toggleVisibility}>{visibility ? <Visibility /> : <VisibilityOff />}</IconButton> <IconButton onClick={toggleVisibility}>{visibility ? <Visibility /> : <VisibilityOff />}</IconButton>
</Tooltip> </Tooltip>
)} )}
{isDeletable && ( {isDeletable && (
<Tooltip title={t<string>('builder.common.tooltip.delete-section')}> <Tooltip title={t('builder.common.tooltip.delete-section') as string}>
<IconButton onClick={handleDelete}> <IconButton onClick={handleDelete}>
<Delete /> <Delete />
</IconButton> </IconButton>

View File

@ -1,19 +1,17 @@
import { Language } from '@mui/icons-material'; import { Language } from '@mui/icons-material';
import { IconButton, Popover } from '@mui/material'; import { IconButton, Popover } from '@mui/material';
import dayjs from 'dayjs';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { useTranslation } from 'next-i18next';
import { MouseEvent, useState } from 'react'; import { MouseEvent, useState } from 'react';
import { languages } from '@/config/languages'; import { languages } from '@/config/languages';
import { setLanguage } from '@/store/build/buildSlice';
import { useAppDispatch } from '@/store/hooks';
import styles from './LanguageSwitcher.module.scss'; import styles from './LanguageSwitcher.module.scss';
const LanguageSwitcher = () => { const LanguageSwitcher = () => {
const router = useRouter(); const router = useRouter();
const dispatch = useAppDispatch(); const { t } = useTranslation();
const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null); const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null);
@ -24,10 +22,7 @@ const LanguageSwitcher = () => {
const handleChangeLanguage = (locale: string) => { const handleChangeLanguage = (locale: string) => {
const { pathname, asPath, query } = router; const { pathname, asPath, query } = router;
dayjs.locale(locale);
dispatch(setLanguage({ language: locale }));
document.cookie = `NEXT_LOCALE=${locale}; path=/; expires=2147483647`; document.cookie = `NEXT_LOCALE=${locale}; path=/; expires=2147483647`;
router.push({ pathname, query }, asPath, { locale }); router.push({ pathname, query }, asPath, { locale });
}; };
@ -59,7 +54,7 @@ const LanguageSwitcher = () => {
))} ))}
<a href="https://translate.rxresu.me" target="_blank" rel="noreferrer" className={styles.language}> <a href="https://translate.rxresu.me" target="_blank" rel="noreferrer" className={styles.language}>
Missing your language? {t('common.footer.language.missing')}
</a> </a>
</div> </div>
</div> </div>

View File

@ -3,6 +3,7 @@ import { Divider, IconButton, ListItemIcon, ListItemText, Menu, MenuItem, Toolti
import { ListItem as ListItemType } from '@reactive-resume/schema'; import { ListItem as ListItemType } from '@reactive-resume/schema';
import clsx from 'clsx'; import clsx from 'clsx';
import isFunction from 'lodash/isFunction'; import isFunction from 'lodash/isFunction';
import { useTranslation } from 'next-i18next';
import React, { useRef, useState } from 'react'; import React, { useRef, useState } from 'react';
import { DropTargetMonitor, useDrag, useDrop, XYCoord } from 'react-dnd'; import { DropTargetMonitor, useDrag, useDrop, XYCoord } from 'react-dnd';
@ -26,6 +27,8 @@ type Props = {
}; };
const ListItem: React.FC<Props> = ({ item, index, title, subtitle, onMove, onEdit, onDelete, onDuplicate }) => { const ListItem: React.FC<Props> = ({ item, index, title, subtitle, onMove, onEdit, onDelete, onDuplicate }) => {
const { t } = useTranslation();
const ref = useRef<HTMLDivElement>(null); const ref = useRef<HTMLDivElement>(null);
const [anchorEl, setAnchorEl] = useState<Element | null>(null); const [anchorEl, setAnchorEl] = useState<Element | null>(null);
@ -122,29 +125,25 @@ const ListItem: React.FC<Props> = ({ item, index, title, subtitle, onMove, onEdi
<ListItemIcon> <ListItemIcon>
<DriveFileRenameOutline className="scale-90" /> <DriveFileRenameOutline className="scale-90" />
</ListItemIcon> </ListItemIcon>
<ListItemText>Edit</ListItemText> <ListItemText>{t('builder.common.list.actions.edit')}</ListItemText>
</MenuItem> </MenuItem>
<MenuItem onClick={() => handleDuplicate(item)}> <MenuItem onClick={() => handleDuplicate(item)}>
<ListItemIcon> <ListItemIcon>
<FileCopy className="scale-90" /> <FileCopy className="scale-90" />
</ListItemIcon> </ListItemIcon>
<ListItemText>Duplicate</ListItemText> <ListItemText>{t('builder.common.list.actions.duplicate')}</ListItemText>
</MenuItem> </MenuItem>
<Divider /> <Divider />
<Tooltip <Tooltip arrow placement="right" title={t('builder.common.tooltip.delete-item') as string}>
arrow
placement="right"
title="Are you sure you want to delete this item? This is an irreversible action."
>
<div> <div>
<MenuItem onClick={() => handleDelete(item)}> <MenuItem onClick={() => handleDelete(item)}>
<ListItemIcon> <ListItemIcon>
<DeleteOutline className="scale-90" /> <DeleteOutline className="scale-90" />
</ListItemIcon> </ListItemIcon>
<ListItemText>Delete</ListItemText> <ListItemText>{t('builder.common.list.actions.delete')}</ListItemText>
</MenuItem> </MenuItem>
</div> </div>
</Tooltip> </Tooltip>

View File

@ -2,6 +2,9 @@
export const FONTS_QUERY = 'fonts'; export const FONTS_QUERY = 'fonts';
export const RESUMES_QUERY = 'resumes'; export const RESUMES_QUERY = 'resumes';
// Regular Expressions
export const VALID_URL_REGEX = /[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/;
// Date Formats // Date Formats
export const FILENAME_TIMESTAMP = 'DDMMYYYYHHmmss'; export const FILENAME_TIMESTAMP = 'DDMMYYYYHHmmss';

View File

@ -13,6 +13,7 @@ import { Controller, useForm } from 'react-hook-form';
import BaseModal from '@/components/shared/BaseModal'; import BaseModal from '@/components/shared/BaseModal';
import MarkdownSupported from '@/components/shared/MarkdownSupported'; import MarkdownSupported from '@/components/shared/MarkdownSupported';
import { VALID_URL_REGEX } from '@/constants/index';
import { useAppDispatch, useAppSelector } from '@/store/hooks'; import { useAppDispatch, useAppSelector } from '@/store/hooks';
import { setModalState } from '@/store/modal/modalSlice'; import { setModalState } from '@/store/modal/modalSlice';
import { addItem, editItem } from '@/store/resume/resumeSlice'; import { addItem, editItem } from '@/store/resume/resumeSlice';
@ -34,9 +35,7 @@ const schema = Joi.object<FormData>().keys({
title: Joi.string().required(), title: Joi.string().required(),
awarder: Joi.string().required(), awarder: Joi.string().required(),
date: Joi.string().allow(''), date: Joi.string().allow(''),
url: Joi.string() url: Joi.string().pattern(VALID_URL_REGEX, { name: 'valid URL' }).allow(''),
.pattern(/[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/, { name: 'valid URL' })
.allow(''),
summary: Joi.string().allow(''), summary: Joi.string().allow(''),
}); });
@ -154,6 +153,7 @@ const AwardModal: React.FC = () => {
render={({ field, fieldState }) => ( render={({ field, fieldState }) => (
<TextField <TextField
label={t('builder.common.form.url.label')} label={t('builder.common.form.url.label')}
placeholder="https://"
error={!!fieldState.error} error={!!fieldState.error}
helperText={fieldState.error?.message} helperText={fieldState.error?.message}
{...field} {...field}

View File

@ -13,6 +13,7 @@ import { Controller, useForm } from 'react-hook-form';
import BaseModal from '@/components/shared/BaseModal'; import BaseModal from '@/components/shared/BaseModal';
import MarkdownSupported from '@/components/shared/MarkdownSupported'; import MarkdownSupported from '@/components/shared/MarkdownSupported';
import { VALID_URL_REGEX } from '@/constants/index';
import { useAppDispatch, useAppSelector } from '@/store/hooks'; import { useAppDispatch, useAppSelector } from '@/store/hooks';
import { setModalState } from '@/store/modal/modalSlice'; import { setModalState } from '@/store/modal/modalSlice';
import { addItem, editItem } from '@/store/resume/resumeSlice'; import { addItem, editItem } from '@/store/resume/resumeSlice';
@ -34,9 +35,7 @@ const schema = Joi.object<FormData>().keys({
name: Joi.string().required(), name: Joi.string().required(),
issuer: Joi.string().required(), issuer: Joi.string().required(),
date: Joi.string().allow(''), date: Joi.string().allow(''),
url: Joi.string() url: Joi.string().pattern(VALID_URL_REGEX, { name: 'valid URL' }).allow(''),
.pattern(/[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/, { name: 'valid URL' })
.allow(''),
summary: Joi.string().allow(''), summary: Joi.string().allow(''),
}); });
@ -154,6 +153,7 @@ const CertificateModal: React.FC = () => {
render={({ field, fieldState }) => ( render={({ field, fieldState }) => (
<TextField <TextField
label={t('builder.common.form.url.label')} label={t('builder.common.form.url.label')}
placeholder="https://"
error={!!fieldState.error} error={!!fieldState.error}
helperText={fieldState.error?.message} helperText={fieldState.error?.message}
{...field} {...field}

View File

@ -14,6 +14,7 @@ import { Controller, useForm } from 'react-hook-form';
import ArrayInput from '@/components/shared/ArrayInput'; import ArrayInput from '@/components/shared/ArrayInput';
import BaseModal from '@/components/shared/BaseModal'; import BaseModal from '@/components/shared/BaseModal';
import MarkdownSupported from '@/components/shared/MarkdownSupported'; import MarkdownSupported from '@/components/shared/MarkdownSupported';
import { VALID_URL_REGEX } from '@/constants/index';
import { useAppDispatch, useAppSelector } from '@/store/hooks'; import { useAppDispatch, useAppSelector } from '@/store/hooks';
import { setModalState } from '@/store/modal/modalSlice'; import { setModalState } from '@/store/modal/modalSlice';
import { addItem, editItem } from '@/store/resume/resumeSlice'; import { addItem, editItem } from '@/store/resume/resumeSlice';
@ -47,9 +48,7 @@ const schema = Joi.object<FormData>().keys({
start: Joi.string().allow(''), start: Joi.string().allow(''),
end: Joi.string().allow(''), end: Joi.string().allow(''),
}), }),
url: Joi.string() url: Joi.string().pattern(VALID_URL_REGEX, { name: 'valid URL' }).allow(''),
.pattern(/[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/, { name: 'valid URL' })
.allow(''),
level: Joi.string().allow(''), level: Joi.string().allow(''),
levelNum: Joi.number().min(0).max(10), levelNum: Joi.number().min(0).max(10),
summary: Joi.string().allow(''), summary: Joi.string().allow(''),
@ -194,6 +193,7 @@ const CustomModal: React.FC = () => {
render={({ field, fieldState }) => ( render={({ field, fieldState }) => (
<TextField <TextField
label={t('builder.common.form.url.label')} label={t('builder.common.form.url.label')}
placeholder="https://"
className="col-span-2" className="col-span-2"
error={!!fieldState.error} error={!!fieldState.error}
helperText={fieldState.error?.message} helperText={fieldState.error?.message}

View File

@ -14,6 +14,7 @@ import { Controller, useForm } from 'react-hook-form';
import ArrayInput from '@/components/shared/ArrayInput'; import ArrayInput from '@/components/shared/ArrayInput';
import BaseModal from '@/components/shared/BaseModal'; import BaseModal from '@/components/shared/BaseModal';
import MarkdownSupported from '@/components/shared/MarkdownSupported'; import MarkdownSupported from '@/components/shared/MarkdownSupported';
import { VALID_URL_REGEX } from '@/constants/index';
import { useAppDispatch, useAppSelector } from '@/store/hooks'; import { useAppDispatch, useAppSelector } from '@/store/hooks';
import { setModalState } from '@/store/modal/modalSlice'; import { setModalState } from '@/store/modal/modalSlice';
import { addItem, editItem } from '@/store/resume/resumeSlice'; import { addItem, editItem } from '@/store/resume/resumeSlice';
@ -46,9 +47,7 @@ const schema = Joi.object<FormData>().keys({
start: Joi.string().allow(''), start: Joi.string().allow(''),
end: Joi.string().allow(''), end: Joi.string().allow(''),
}), }),
url: Joi.string() url: Joi.string().pattern(VALID_URL_REGEX, { name: 'valid URL' }).allow(''),
.pattern(/[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/, { name: 'valid URL' })
.allow(''),
summary: Joi.string().allow(''), summary: Joi.string().allow(''),
courses: Joi.array().items(Joi.string().optional()), courses: Joi.array().items(Joi.string().optional()),
}); });
@ -217,6 +216,7 @@ const EducationModal: React.FC = () => {
render={({ field, fieldState }) => ( render={({ field, fieldState }) => (
<TextField <TextField
label={t('builder.common.form.url.label')} label={t('builder.common.form.url.label')}
placeholder="https://"
className="col-span-2" className="col-span-2"
error={!!fieldState.error} error={!!fieldState.error}
helperText={fieldState.error?.message} helperText={fieldState.error?.message}

View File

@ -10,6 +10,7 @@ import { useEffect, useMemo } from 'react';
import { Controller, useForm } from 'react-hook-form'; import { Controller, useForm } from 'react-hook-form';
import BaseModal from '@/components/shared/BaseModal'; import BaseModal from '@/components/shared/BaseModal';
import { VALID_URL_REGEX } from '@/constants/index';
import { useAppDispatch, useAppSelector } from '@/store/hooks'; import { useAppDispatch, useAppSelector } from '@/store/hooks';
import { setModalState } from '@/store/modal/modalSlice'; import { setModalState } from '@/store/modal/modalSlice';
import { addItem, editItem } from '@/store/resume/resumeSlice'; import { addItem, editItem } from '@/store/resume/resumeSlice';
@ -21,17 +22,14 @@ const path = 'sections.profile';
const defaultState: FormData = { const defaultState: FormData = {
network: '', network: '',
username: '', username: '',
url: 'https://', url: '',
}; };
const schema = Joi.object<FormData>({ const schema = Joi.object<FormData>({
id: Joi.string(), id: Joi.string(),
network: Joi.string().required(), network: Joi.string().required(),
username: Joi.string().required(), username: Joi.string().required(),
url: Joi.string() url: Joi.string().pattern(VALID_URL_REGEX, { name: 'valid URL' }).allow(''),
.pattern(/[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/, { name: 'valid URL' })
.default('https://')
.allow(''),
}); });
const ProfileModal: React.FC = () => { const ProfileModal: React.FC = () => {
@ -131,6 +129,7 @@ const ProfileModal: React.FC = () => {
<TextField <TextField
label={t('builder.common.form.url.label')} label={t('builder.common.form.url.label')}
className="col-span-2" className="col-span-2"
placeholder="https://"
error={!!fieldState.error} error={!!fieldState.error}
helperText={fieldState.error?.message} helperText={fieldState.error?.message}
{...field} {...field}

View File

@ -14,6 +14,7 @@ import { Controller, useForm } from 'react-hook-form';
import ArrayInput from '@/components/shared/ArrayInput'; import ArrayInput from '@/components/shared/ArrayInput';
import BaseModal from '@/components/shared/BaseModal'; import BaseModal from '@/components/shared/BaseModal';
import MarkdownSupported from '@/components/shared/MarkdownSupported'; import MarkdownSupported from '@/components/shared/MarkdownSupported';
import { VALID_URL_REGEX } from '@/constants/index';
import { useAppDispatch, useAppSelector } from '@/store/hooks'; import { useAppDispatch, useAppSelector } from '@/store/hooks';
import { setModalState } from '@/store/modal/modalSlice'; import { setModalState } from '@/store/modal/modalSlice';
import { addItem, editItem } from '@/store/resume/resumeSlice'; import { addItem, editItem } from '@/store/resume/resumeSlice';
@ -42,9 +43,7 @@ const schema = Joi.object<FormData>().keys({
start: Joi.string().allow(''), start: Joi.string().allow(''),
end: Joi.string().allow(''), end: Joi.string().allow(''),
}), }),
url: Joi.string() url: Joi.string().pattern(VALID_URL_REGEX, { name: 'valid URL' }).allow(''),
.pattern(/[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/, { name: 'valid URL' })
.allow(''),
summary: Joi.string().allow(''), summary: Joi.string().allow(''),
keywords: Joi.array().items(Joi.string().optional()), keywords: Joi.array().items(Joi.string().optional()),
}); });
@ -187,6 +186,7 @@ const ProjectModal: React.FC = () => {
render={({ field, fieldState }) => ( render={({ field, fieldState }) => (
<TextField <TextField
label={t('builder.common.form.url.label')} label={t('builder.common.form.url.label')}
placeholder="https://"
className="col-span-2" className="col-span-2"
error={!!fieldState.error} error={!!fieldState.error}
helperText={fieldState.error?.message} helperText={fieldState.error?.message}

View File

@ -13,6 +13,7 @@ import { Controller, useForm } from 'react-hook-form';
import BaseModal from '@/components/shared/BaseModal'; import BaseModal from '@/components/shared/BaseModal';
import MarkdownSupported from '@/components/shared/MarkdownSupported'; import MarkdownSupported from '@/components/shared/MarkdownSupported';
import { VALID_URL_REGEX } from '@/constants/index';
import { useAppDispatch, useAppSelector } from '@/store/hooks'; import { useAppDispatch, useAppSelector } from '@/store/hooks';
import { setModalState } from '@/store/modal/modalSlice'; import { setModalState } from '@/store/modal/modalSlice';
import { addItem, editItem } from '@/store/resume/resumeSlice'; import { addItem, editItem } from '@/store/resume/resumeSlice';
@ -34,9 +35,7 @@ const schema = Joi.object<FormData>().keys({
name: Joi.string().required(), name: Joi.string().required(),
publisher: Joi.string().required(), publisher: Joi.string().required(),
date: Joi.string().allow(''), date: Joi.string().allow(''),
url: Joi.string() url: Joi.string().pattern(VALID_URL_REGEX, { name: 'valid URL' }).allow(''),
.pattern(/[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/, { name: 'valid URL' })
.allow(''),
summary: Joi.string().allow(''), summary: Joi.string().allow(''),
}); });
@ -154,6 +153,7 @@ const PublicationModal: React.FC = () => {
render={({ field, fieldState }) => ( render={({ field, fieldState }) => (
<TextField <TextField
label={t('builder.common.form.url.label')} label={t('builder.common.form.url.label')}
placeholder="https://"
error={!!fieldState.error} error={!!fieldState.error}
helperText={fieldState.error?.message} helperText={fieldState.error?.message}
{...field} {...field}

View File

@ -13,6 +13,7 @@ import { Controller, useForm } from 'react-hook-form';
import BaseModal from '@/components/shared/BaseModal'; import BaseModal from '@/components/shared/BaseModal';
import MarkdownSupported from '@/components/shared/MarkdownSupported'; import MarkdownSupported from '@/components/shared/MarkdownSupported';
import { VALID_URL_REGEX } from '@/constants/index';
import { useAppDispatch, useAppSelector } from '@/store/hooks'; import { useAppDispatch, useAppSelector } from '@/store/hooks';
import { setModalState } from '@/store/modal/modalSlice'; import { setModalState } from '@/store/modal/modalSlice';
import { addItem, editItem } from '@/store/resume/resumeSlice'; import { addItem, editItem } from '@/store/resume/resumeSlice';
@ -40,9 +41,7 @@ const schema = Joi.object<FormData>().keys({
start: Joi.string().allow(''), start: Joi.string().allow(''),
end: Joi.string().allow(''), end: Joi.string().allow(''),
}), }),
url: Joi.string() url: Joi.string().pattern(VALID_URL_REGEX, { name: 'valid URL' }).allow(''),
.pattern(/[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/, { name: 'valid URL' })
.allow(''),
summary: Joi.string().allow(''), summary: Joi.string().allow(''),
}); });
@ -184,6 +183,7 @@ const VolunteerModal: React.FC = () => {
render={({ field, fieldState }) => ( render={({ field, fieldState }) => (
<TextField <TextField
label={t('builder.common.form.url.label')} label={t('builder.common.form.url.label')}
placeholder="https://"
className="col-span-2" className="col-span-2"
error={!!fieldState.error} error={!!fieldState.error}
helperText={fieldState.error?.message} helperText={fieldState.error?.message}

View File

@ -13,6 +13,7 @@ import { Controller, useForm } from 'react-hook-form';
import BaseModal from '@/components/shared/BaseModal'; import BaseModal from '@/components/shared/BaseModal';
import MarkdownSupported from '@/components/shared/MarkdownSupported'; import MarkdownSupported from '@/components/shared/MarkdownSupported';
import { VALID_URL_REGEX } from '@/constants/index';
import { useAppDispatch, useAppSelector } from '@/store/hooks'; import { useAppDispatch, useAppSelector } from '@/store/hooks';
import { setModalState } from '@/store/modal/modalSlice'; import { setModalState } from '@/store/modal/modalSlice';
import { addItem, editItem } from '@/store/resume/resumeSlice'; import { addItem, editItem } from '@/store/resume/resumeSlice';
@ -40,9 +41,7 @@ const schema = Joi.object<FormData>().keys({
start: Joi.string().allow(''), start: Joi.string().allow(''),
end: Joi.string().allow(''), end: Joi.string().allow(''),
}), }),
url: Joi.string() url: Joi.string().pattern(VALID_URL_REGEX, { name: 'valid URL' }).allow(''),
.pattern(/[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/, { name: 'valid URL' })
.allow(''),
summary: Joi.string().allow(''), summary: Joi.string().allow(''),
}); });
@ -184,6 +183,7 @@ const WorkModal: React.FC = () => {
render={({ field, fieldState }) => ( render={({ field, fieldState }) => (
<TextField <TextField
label={t('builder.common.form.url.label')} label={t('builder.common.form.url.label')}
placeholder="https://"
className="col-span-2" className="col-span-2"
error={!!fieldState.error} error={!!fieldState.error}
helperText={fieldState.error?.message} helperText={fieldState.error?.message}

View File

@ -125,7 +125,7 @@ const CreateResumeModal: React.FC = () => {
<FormGroup> <FormGroup>
<FormControlLabel <FormControlLabel
label={t<string>('modals.dashboard.create-resume.form.public.label')} label={t('modals.dashboard.create-resume.form.public.label') as string}
control={ control={
<Controller <Controller
name="isPublic" name="isPublic"

View File

@ -84,7 +84,7 @@ const ImportExternalModal: React.FC = () => {
<p className="mb-2"> <p className="mb-2">
<Trans t={t} i18nKey="modals.dashboard.import-external.linkedin.body"> <Trans t={t} i18nKey="modals.dashboard.import-external.linkedin.body">
You can save time by exporting your data from LinkedIn and using it to auto-fill fields on Reactive Resume. You can save time by exporting your data from LinkedIn and using it to auto-fill fields on Reactive Resume.
Head on over to the Head over to the
<a <a
href="https://www.linkedin.com/psettings/member-data" href="https://www.linkedin.com/psettings/member-data"
className="underline" className="underline"
@ -93,7 +93,7 @@ const ImportExternalModal: React.FC = () => {
> >
Data Privacy Data Privacy
</a> </a>
section on LinkedIn and request an archive of your data. Once it is available, upload the ZIP archive below. section on LinkedIn and request an archive of your data. Once it is available, upload the ZIP file below.
</Trans> </Trans>
</p> </p>

View File

@ -61,12 +61,18 @@
"page": "Page" "page": "Page"
}, },
"list": { "list": {
"empty-text": "This list is empty." "empty-text": "This list is empty.",
"actions": {
"edit": "Edit",
"duplicate": "Duplicate",
"delete": "Delete"
}
}, },
"tooltip": { "tooltip": {
"delete-section": "Delete Section", "delete-section": "Delete Section",
"rename-section": "Rename Section", "rename-section": "Rename Section",
"toggle-visibility": "Toggle Visibility" "toggle-visibility": "Toggle Visibility",
"delete-item": "Are you sure you want to delete this item? This is an irreversible action."
} }
}, },
"controller": { "controller": {

View File

@ -5,9 +5,11 @@
"logout": "Logout" "logout": "Logout"
} }
}, },
"description": "Reactive Resume is a free and open source resume builder that's built to make the mundane tasks of creating, updating and sharing your resume as easy as 1, 2, 3.",
"footer": { "footer": {
"credit": "A passion project by <1>Amruth Pillai</1>", "credit": "A passion project by <1>Amruth Pillai</1>",
"language": {
"missing": "Missing your language?"
},
"license": "By the community, for the community." "license": "By the community, for the community."
}, },
"markdown": { "markdown": {

View File

@ -106,7 +106,7 @@
"actions": { "actions": {
"upload-archive": "Upload ZIP Archive" "upload-archive": "Upload ZIP Archive"
}, },
"body": "You can save time by exporting your data from LinkedIn and using it to auto-fill fields on Reactive Resume. Head on over to the <1>Data Privacy</1> section on LinkedIn and request an archive of your data. Once it is available, upload the ZIP archive below.", "body": "You can save time by exporting your data from LinkedIn and using it to auto-fill fields on Reactive Resume. Head over to the <1>Data Privacy</1> section on LinkedIn and request an archive of your data. Once it is available, upload the ZIP file below.",
"heading": "Import From LinkedIn" "heading": "Import From LinkedIn"
}, },
"reactive-resume": { "reactive-resume": {

View File

@ -5,7 +5,6 @@
"logout": "ಲಾಗ್ ಔಟ್" "logout": "ಲಾಗ್ ಔಟ್"
} }
}, },
"description": "ಪ್ರತಿಕ್ರಿಯಾತ್ಮಕ ರೇಸುಮೆವು ಉಚಿತ ಮತ್ತು ಮುಕ್ತ ಮೂಲ ರೇಸುಮೆ ಬಿಲ್ಡರ್ ಆಗಿದ್ದು, ನಿಮ್ಮ ರೇಸುಮೆ ಅನ್ನು 1, 2, 3 ರಂತೆ ಸುಲಭವಾಗಿ ರಚಿಸುವ, ನವೀಕರಿಸುವ ಮತ್ತು ಹಂಚಿಕೊಳ್ಳುವ ಪ್ರಾಪಂಚಿಕ ಕಾರ್ಯಗಳನ್ನು ಮಾಡಲು ನಿರ್ಮಿಸಲಾಗಿದೆ.",
"footer": { "footer": {
"credit": "<1>ಅಮೃತ್ ಪಿಳ್ಳೈ</1> ಅವರು ಉತ್ಸಾಹದಿಂದ ಮಾಡಿರುವ ಪ್ರಾಜೆಕ್ಟ್", "credit": "<1>ಅಮೃತ್ ಪಿಳ್ಳೈ</1> ಅವರು ಉತ್ಸಾಹದಿಂದ ಮಾಡಿರುವ ಪ್ರಾಜೆಕ್ಟ್",
"license": "ಸಮುದಾಯದಿಂದ, ಸಮುದಾಯಕ್ಕಾಗಿ." "license": "ಸಮುದಾಯದಿಂದ, ಸಮುದಾಯಕ್ಕಾಗಿ."

View File

@ -11,7 +11,7 @@
} }
}, },
"heading": "ನಿಮ್ಮ ಪಾಸ್ವರ್ಡ್ ಮರೆತಿರುವಿರಾ?", "heading": "ನಿಮ್ಮ ಪಾಸ್ವರ್ಡ್ ಮರೆತಿರುವಿರಾ?",
"help-text": "%1 ರ ಜೊತೆ ಜೋಡಣೆಯಾಗಿರುವ ಖಾತೆ ಇದ್ದಲ್ಲಿ, ನೀವು ನಿಮ್ಮ ಗುಪ್ತಪದ ಮರುಹೊಂದಿಕೆ ಕೊಂಡಿಯನ್ನು ಹೊಂದಿರುವ ಮಿಂಚೆಯನ್ನು ಪಡೆಯುವಿರಿ." "help-text": "ಖಾತೆಯು ಅಸ್ತಿತ್ವದಲ್ಲಿದ್ದರೆ, ನಿಮ್ಮ ಪಾಸ್‌ವರ್ಡ್ ಅನ್ನು ಮರುಹೊಂದಿಸಲು ಲಿಂಕ್‌ನೊಂದಿಗೆ ಇಮೇಲ್ ಅನ್ನು ನೀವು ಸ್ವೀಕರಿಸುತ್ತೀರಿ."
}, },
"login": { "login": {
"actions": { "actions": {
@ -104,16 +104,16 @@
}, },
"linkedin": { "linkedin": {
"actions": { "actions": {
"upload-archive": "ಜಿಪ್ (ZIP) ಆರ್ಕೈವ್ ಅನ್ನು ಅಪ್‌ಲೋಡ್ ಮಾಡಿ" "upload-archive": "ಜಿಪ್ ಆರ್ಕೈವ್ ಅನ್ನು ಅಪ್‌ಲೋಡ್ ಮಾಡಿ"
}, },
"body": "ಲಿಂಕ್ಡ್‌ಇನ್‌ನಿಂದ ನಿಮ್ಮ ಡೇಟಾವನ್ನು ರಫ್ತು ಮಾಡುವ ಮೂಲಕ ಮತ್ತು ರಿಯಾಕ್ಟಿವ್ ರೆಸ್ಯೂಮ್‌ನಲ್ಲಿ ಕ್ಷೇತ್ರಗಳನ್ನು ಸ್ವಯಂ ತುಂಬಲು ಅದನ್ನು ಬಳಸುವ ಮೂಲಕ ನೀವು ಸಮಯವನ್ನು ಉಳಿಸಬಹುದು. <1>ಡೇಟಾ ಗೌಪ್ಯತೆಗೆ ಹೋಗಿ</1> ಲಿಂಕ್ಡ್‌ಇನ್‌ನಲ್ಲಿ ವಿಭಾಗ ಮತ್ತು ನಿಮ್ಮ ಡೇಟಾದ ಆರ್ಕೈವ್ ಅನ್ನು ವಿನಂತಿಸಿ. ಒಮ್ಮೆ ಅದು ಲಭ್ಯವಾದ ನಂತರ, ಕೆಳಗಿನ ಜಿಪ್ (ZIP) ಆರ್ಕೈವ್ ಅನ್ನು ಅಪ್‌ಲೋಡ್ ಮಾಡಿ.", "body": "ಲಿಂಕ್ಡ್‌ಇನ್‌ನಿಂದ ನಿಮ್ಮ ಡೇಟಾವನ್ನು ರಫ್ತು ಮಾಡುವ ಮೂಲಕ ಮತ್ತು ರಿಯಾಕ್ಟಿವ್ ರೆಸ್ಯೂಮ್‌ನಲ್ಲಿ ಕ್ಷೇತ್ರಗಳನ್ನು ಸ್ವಯಂ ತುಂಬಲು ಅದನ್ನು ಬಳಸುವ ಮೂಲಕ ನೀವು ಸಮಯವನ್ನು ಉಳಿಸಬಹುದು. <1>ಡೇಟಾ ಗೌಪ್ಯತೆಗೆ ಹೋಗಿ</1> ಲಿಂಕ್ಡ್‌ಇನ್‌ನಲ್ಲಿ ವಿಭಾಗ ಮತ್ತು ನಿಮ್ಮ ಡೇಟಾದ ಆರ್ಕೈವ್ ಅನ್ನು ವಿನಂತಿಸಿ. ಒಮ್ಮೆ ಅದು ಲಭ್ಯವಾದ ನಂತರ, ಕೆಳಗಿನ ಜಿಪ್ ಆರ್ಕೈವ್ ಅನ್ನು ಅಪ್‌ಲೋಡ್ ಮಾಡಿ.",
"heading": "ಲಿಂಕ್ಡಿನ್(LinkedIn) ನಿಂದ ಆಮದು ಮಾಡಿಕೊಳ್ಳಿ" "heading": "ಲಿಂಕ್ಡಿನ್ ನಿಂದ ಆಮದು ಮಾಡಿಕೊಳ್ಳಿ"
}, },
"reactive-resume": { "reactive-resume": {
"actions": { "actions": {
"upload-json": "ಜೆಸನ್ ಅನ್ನು ಅಪ್‌ಲೋಡ್ ಮಾಡಿ" "upload-json": "ಜೆಸನ್ ಅನ್ನು ಅಪ್‌ಲೋಡ್ ಮಾಡಿ"
}, },
"body": "ನೀವು ರಿಯಾಕ್ಟಿವ್ ರೆಸ್ಯೂಮ್‌ನ ಪ್ರಸ್ತುತ ಆವೃತ್ತಿಯೊಂದಿಗೆ ರಫ್ತು ಮಾಡಲಾದ ಜೆಸನ್(JSON) ಅನ್ನು ಹೊಂದಿದ್ದರೆ, ಮತ್ತೆ ಸಂಪಾದಿಸಬಹುದಾದ ಆವೃತ್ತಿಯನ್ನು ಪಡೆಯಲು ನೀವು ಅದನ್ನು ಇಲ್ಲಿಗೆ ಆಮದು ಮಾಡಿಕೊಳ್ಳಬಹುದು. ರಿಯಾಕ್ಟಿವ್ ರೆಸ್ಯೂಮ್‌ನ ಹಿಂದಿನ ಆವೃತ್ತಿಗಳು ದುರದೃಷ್ಟವಶಾತ್ ಸದ್ಯಕ್ಕೆ ಬೆಂಬಲಿತವಾಗಿಲ್ಲ.", "body": "ನೀವು ರಿಯಾಕ್ಟಿವ್ ರೆಸ್ಯೂಮ್‌ನ ಪ್ರಸ್ತುತ ಆವೃತ್ತಿಯೊಂದಿಗೆ ರಫ್ತು ಮಾಡಲಾದ ಜೆಸನ್ ಅನ್ನು ಹೊಂದಿದ್ದರೆ, ಮತ್ತೆ ಸಂಪಾದಿಸಬಹುದಾದ ಆವೃತ್ತಿಯನ್ನು ಪಡೆಯಲು ನೀವು ಅದನ್ನು ಇಲ್ಲಿಗೆ ಆಮದು ಮಾಡಿಕೊಳ್ಳಬಹುದು. ರಿಯಾಕ್ಟಿವ್ ರೆಸ್ಯೂಮ್‌ನ ಹಿಂದಿನ ಆವೃತ್ತಿಗಳು ದುರದೃಷ್ಟವಶಾತ್ ಸದ್ಯಕ್ಕೆ ಬೆಂಬಲಿತವಾಗಿಲ್ಲ.",
"heading": "ಜೆಸನ್ ರೆಸ್ಯೂಮ್‌ನಿಂದ ಆಮದು ಮಾಡಿಕೊಳ್ಳಿ" "heading": "ಜೆಸನ್ ರೆಸ್ಯೂಮ್‌ನಿಂದ ಆಮದು ಮಾಡಿಕೊಳ್ಳಿ"
} }
}, },

View File

@ -5,7 +5,6 @@
"logout": "வெளியேறு" "logout": "வெளியேறு"
} }
}, },
"description": "ரியாக்டிவ் ரெஸ்யூம் என்பது ஒரு இலவச மற்றும் ஓப்பன் சோர்ஸ் ரெஸ்யூம் பில்டராகும், இது உங்கள் விண்ணப்பத்தை 1, 2, 3 என எளிதாக உருவாக்குவது, புதுப்பித்தல் மற்றும் பகிர்வது போன்ற சர்வ சாதாரணமான பணிகளைச் செய்ய உருவாக்கப்பட்டுள்ளது.",
"footer": { "footer": {
"credit": "<1>அம்ருத் பிள்ளை</1>யின் திட்டம்", "credit": "<1>அம்ருத் பிள்ளை</1>யின் திட்டம்",
"license": "சமூகத்தால், சமூகத்திற்காக." "license": "சமூகத்தால், சமூகத்திற்காக."

View File

@ -1,8 +1,6 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import merge from 'lodash/merge'; import merge from 'lodash/merge';
import { Language, languageMap } from '@/config/languages';
export type Theme = 'light' | 'dark'; export type Theme = 'light' | 'dark';
export type Sidebar = 'left' | 'right'; export type Sidebar = 'left' | 'right';
@ -13,7 +11,6 @@ export type Orientation = 'horizontal' | 'vertical';
export type BuildState = { export type BuildState = {
theme?: Theme; theme?: Theme;
language: Language;
sidebar: Record<Sidebar, SidebarState>; sidebar: Record<Sidebar, SidebarState>;
page: { page: {
breakLine: boolean; breakLine: boolean;
@ -22,7 +19,6 @@ export type BuildState = {
}; };
const initialState: BuildState = { const initialState: BuildState = {
language: languageMap['en'],
sidebar: { sidebar: {
left: { open: false }, left: { open: false },
right: { open: false }, right: { open: false },
@ -35,8 +31,6 @@ const initialState: BuildState = {
type SetThemePayload = { theme: Theme }; type SetThemePayload = { theme: Theme };
type SetLanguagePayload = { language: string };
type ToggleSidebarPayload = { sidebar: Sidebar }; type ToggleSidebarPayload = { sidebar: Sidebar };
type SetSidebarStatePayload = { sidebar: Sidebar; state: SidebarState }; type SetSidebarStatePayload = { sidebar: Sidebar; state: SidebarState };
@ -50,11 +44,6 @@ export const buildSlice = createSlice({
state.theme = theme; state.theme = theme;
}, },
setLanguage: (state, action: PayloadAction<SetLanguagePayload>) => {
const { language } = action.payload;
state.language = languageMap[language];
},
toggleSidebar: (state, action: PayloadAction<ToggleSidebarPayload>) => { toggleSidebar: (state, action: PayloadAction<ToggleSidebarPayload>) => {
const { sidebar } = action.payload; const { sidebar } = action.payload;
@ -76,7 +65,7 @@ export const buildSlice = createSlice({
}, },
}); });
export const { setTheme, setLanguage, toggleSidebar, setSidebarState, togglePageBreakLine, togglePageOrientation } = export const { setTheme, toggleSidebar, setSidebarState, togglePageBreakLine, togglePageOrientation } =
buildSlice.actions; buildSlice.actions;
export default buildSlice.reducer; export default buildSlice.reducer;

View File

@ -0,0 +1,8 @@
export const getCookie = (name: string): string | undefined => {
const value = `; ${document.cookie}`;
const parts = value.split(`; ${name}=`);
if (parts.length === 2) {
return parts.at(-1);
}
};

View File

@ -1,14 +1,19 @@
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import relativeTime from 'dayjs/plugin/relativeTime'; import relativeTime from 'dayjs/plugin/relativeTime';
import { useRouter } from 'next/router';
import { useEffect } from 'react'; import { useEffect } from 'react';
const DateWrapper: React.FC = ({ children }) => { const DateWrapper: React.FC = ({ children }) => {
const { locale } = useRouter();
useEffect(() => { useEffect(() => {
dayjs.extend(relativeTime); dayjs.extend(relativeTime);
// Locales // Locales
require('dayjs/locale/kn'); require('dayjs/locale/kn');
}, []);
locale && dayjs.locale(locale);
}, [locale]);
return <>{children}</>; return <>{children}</>;
}; };