diff --git a/components/MarkdownExtended.js b/components/MarkdownExtended.js index 36fde654..6c773e17 100644 --- a/components/MarkdownExtended.js +++ b/components/MarkdownExtended.js @@ -4,25 +4,88 @@ import remarkGfm from 'remark-gfm' import 'github-markdown-css/github-markdown-light.css' -function MarkdownExtended({ children, className }) { - const content = (typeof children === 'string' ? children : '') - .replace(/< *br *\/?>/gi, '\n') - .replaceAll('\\n', '\n') +import TaContentInfo from './Panel/Resources/TAContentInfo' - const convertYoutube = (props) => { +function MarkdownExtended({ children, className, onLinkClick, config, setItem }) { + const convertRcLinksToMarkdownLinks = (text) => { + if (!config?.resource?.repo) return text + const locale = config.resource.repo.split('_')[0] + return text + .replace(/\*\//g, locale + '/') + .replace(/\[\[(rc:\/\/\S+?)\]\]/g, (_, url) => `[${url}](${url})`) + .replace(/\[(\d+:\d+)\]\(\.\.\/\d+\/\d+\.md\)/g, (_, time) => time) + } + + const content = convertRcLinksToMarkdownLinks( + (typeof children === 'string' ? children : '') + .replace(/< *br *\/?>/gi, '\n') + .replaceAll('\\n', '\n') + ) + + const handleLinkClick = (href) => { + if (href.endsWith('.md')) { + onLinkClick?.(href) + } else if (href.startsWith('rc://')) { + onLinkClick?.(href) + } + } + + const parsingRef = (props) => { function getVideoID(userInput) { - var res = userInput.match( + const res = userInput.match( /^.*(?:(?:youtu.be\/)|(?:v\/)|(?:\/u\/\w\/)|(?:embed\/)|(?:watch\?))\??v?=?([^#\&\?]*).*/ ) - if (res) return res[1] - return false + return res ? res[1] : false } + + const href = props?.node?.properties?.href + if (href?.includes('translate') && href.includes('ta')) { + return ( + + {props.children} + + ) + } + + if (href?.includes('translate')) { + return ( + { + e.preventDefault() + handleLinkClick(href) + }} + > + {props.children} + + ) + } + + if (props.href.endsWith('.md')) { + return ( + { + e.preventDefault() + handleLinkClick(props.href) + }} + > + {props.children} + + ) + } + const youtubeId = getVideoID(props?.href) return youtubeId ? ( @@ -36,7 +99,7 @@ function MarkdownExtended({ children, className }) { rehypePlugins={[rehypeRaw]} className={className} components={{ - a: convertYoutube, + a: parsingRef, }} remarkPlugins={[remarkGfm]} > diff --git a/components/ModalInSideBar.js b/components/ModalInSideBar.js index 6a9ab7a6..7f970d3c 100644 --- a/components/ModalInSideBar.js +++ b/components/ModalInSideBar.js @@ -7,6 +7,7 @@ function ModalInSideBar({ modalTitle, buttonTitle, collapsed, + contentClassName = 'p-4', }) { return ( <> @@ -31,7 +32,9 @@ function ModalInSideBar({ -
+
{children}
diff --git a/components/Panel/Resources/TAContentInfo.js b/components/Panel/Resources/TAContentInfo.js new file mode 100644 index 00000000..c824c057 --- /dev/null +++ b/components/Panel/Resources/TAContentInfo.js @@ -0,0 +1,72 @@ +import { useEffect, useRef, useState } from 'react' + +import { getFile } from 'utils/apiHelper' +import { getWordsAcademy } from 'utils/helper' + +import Loading from 'public/icons/progress.svg' + +function TaContentInfo({ href, config, setItem, returnImmediately = false }) { + const [words, setWords] = useState(null) + const [isLoading, setIsLoading] = useState(true) + const hrefRef = useRef(href) + + useEffect(() => { + if (!config || !config.resource) { + console.error('Config object is missing or invalid.') + return + } + + const getData = async () => { + setIsLoading(true) + try { + const zip = await getFile({ + owner: config.resource.owner, + repo: config.resource.repo.split('_')[0] + '_ta', + commit: config.resource.commit, + apiUrl: '/api/git/ta', + }) + const fetchedWords = await getWordsAcademy({ + zip, + href: hrefRef.current, + }) + setWords(fetchedWords) + } catch (error) { + console.error('Error fetching data:', error) + } finally { + setIsLoading(false) + } + } + + getData() + }, [config, returnImmediately, setItem]) + + if (isLoading) { + return ( + + ) + } + + if (!words) return null + + const description = words['title'] || words.title || hrefRef.current + const title = words['sub-title'] || words.sub || hrefRef.current + const text = words['01'] || hrefRef.current + const item = { title, text, type: 'ta' } + + if (returnImmediately) { + return null + } + + return ( +
{ + e.preventDefault() + setItem?.(item) + }} + > + {description} +
+ ) +} +export default TaContentInfo diff --git a/components/Panel/Resources/TN.js b/components/Panel/Resources/TN.js index f7f8ff0c..eab89f5d 100644 --- a/components/Panel/Resources/TN.js +++ b/components/Panel/Resources/TN.js @@ -7,11 +7,15 @@ import { useQuotesTranslation } from '@texttree/tn-quote' import { Placeholder, TNTWLContent } from '../UI' -import { filterNotes } from 'utils/helper' +import { getFile } from 'utils/apiHelper' +import { filterNotes, getWordsAcademy } from 'utils/helper' import { useGetResource, useScroll } from 'utils/hooks' function TN({ config, url, toolName }) { const [item, setItem] = useState(null) + const [href, setHref] = useState(null) + + const [parentItem, setParentItem] = useState(null) const [tnotes, setTnotes] = useState([]) const { isLoading, data } = useGetResource({ config, url }) const { extraTNotes, setTnotes: updateTnotes } = useQuotesTranslation({ @@ -28,6 +32,42 @@ function TN({ config, url, toolName }) { config.mainResource.repo, }, }) + + useEffect(() => { + const fetchWordData = async () => { + if (href && data.length > 0) { + if (href?.includes('-')) { + const result = href.split('/')[1] + const zip = await getFile({ + owner: config.resource.owner, + repo: config.resource.repo.split('_')[0] + '_ta', + commit: config.resource.commit, + apiUrl: '/api/git/ta', + }) + + const hrefNew = `rc://${config.resource.repo.split('_')[0]}/ta/man/translate/${result}` + const fetchedWords = await getWordsAcademy({ + zip, + href: hrefNew, + }) + const title = fetchedWords?.['sub-title'] || fetchedWords?.sub || href + const text = fetchedWords?.['01'] || href + const type = 'ta' + const item = { + title, + text, + type, + } + setItem?.(item) + } + } + } + + fetchWordData() + + // eslint-disable-next-line + }, [href, config.resource.repo]) + useEffect(() => { if (extraTNotes) { const _data = [] @@ -50,10 +90,18 @@ function TN({ config, url, toolName }) { ) : (
{item ? ( - + ) : ( { handleSaveScroll(verseNumber, note.ID) + setParentItem({ + text: note.Note, + title: note.Quote || note.origQuote, + type: 'tn', + }) setItem({ text: note.Note, title: note.Quote || note.origQuote, + type: 'tn', }) }} > diff --git a/components/Panel/Resources/TWL.js b/components/Panel/Resources/TWL.js index afebf3c8..540ad4e7 100644 --- a/components/Panel/Resources/TWL.js +++ b/components/Panel/Resources/TWL.js @@ -6,13 +6,65 @@ import ReactMarkdown from 'react-markdown' import { Placeholder, TNTWLContent } from '../UI' import { getFile } from 'utils/apiHelper' -import { checkLSVal, filterNotes, getWords } from 'utils/helper' +import { checkLSVal, filterNotes, getWord, getWords, getWordsAcademy } from 'utils/helper' import { useGetResource, useScroll } from 'utils/hooks' import Down from 'public/icons/arrow-down.svg' function TWL({ config, url, toolName }) { const [item, setItem] = useState(null) + const [parentItem, setParentItem] = useState(null) + + const [href, setHref] = useState(null) + const [zip, setZip] = useState(null) + useEffect(() => { + const fetchWordData = async () => { + if (href && data.length > 0) { + if (href?.includes('-')) { + const result = href.split('/')[1] + const zip = await getFile({ + owner: config.resource.owner, + repo: config.resource.repo.split('_')[0] + '_ta', + commit: config.resource.commit, + apiUrl: '/api/git/ta', + }) + + const hrefNew = `rc://${config.resource.repo.split('_')[0]}/ta/man/translate/${result}` + const fetchedWords = await getWordsAcademy({ + zip, + href: hrefNew, + }) + const title = fetchedWords?.['sub-title'] || fetchedWords?.sub || href + const text = fetchedWords?.['01'] || href + const type = 'ta' + const item = { + title, + text, + type, + } + setItem?.(item) + } else { + const word = await getWord({ + zip, + repo: config.resource.repo.slice(0, -1).replace('obs-', ''), + TWLink: href, + }) + const newItem = { + title: word?.title || '', + text: word?.text || '', + type: 'tw', + } + + setItem(newItem) + } + } + } + + fetchWordData() + + // eslint-disable-next-line + }, [href, config.resource.repo]) + const { isLoading, data } = useGetResource({ config, url }) const [wordObjects, setWordObjects] = useState([]) const [isLoadingTW, setIsLoadingTW] = useState(false) @@ -25,6 +77,7 @@ function TWL({ config, url, toolName }) { commit: config.resource.commit, apiUrl: '/api/git/tw', }) + setZip(zip) const words = await getWords({ zip, repo: config.resource.repo.slice(0, -1).replace('obs-', ''), @@ -48,7 +101,7 @@ function TWL({ config, url, toolName }) { id: ID, title, text, - url: TWLink, + url: TWLink, // TODO уточнить где используется isRepeatedInBook, isRepeatedInChapter, isRepeatedInVerse, @@ -67,9 +120,17 @@ function TWL({ config, url, toolName }) { return ( <>
- + { return checkLSVal('filter_words', 'disabled', 'string') }) @@ -149,7 +209,8 @@ function TWLList({ setItem, data, toolName, isLoading }) { } hover:bg-th-secondary-100 ${highlightId === 'id' + item.id ? 'bg-th-secondary-100' : ''} `} onClick={() => { handleSaveScroll(verseNumber, item.id) - setItem({ text: item.text, title: item.title }) + setParentItem(item) + setItem({ text: item.text, title: item.title, type: 'twl' }) }} > {item.title} diff --git a/components/Panel/UI/TAContent.js b/components/Panel/UI/TAContent.js new file mode 100644 index 00000000..7c9873af --- /dev/null +++ b/components/Panel/UI/TAContent.js @@ -0,0 +1,55 @@ +import ReactMarkdown from 'react-markdown' + +import MarkdownExtended from 'components/MarkdownExtended' + +import Back from 'public/icons/left.svg' + +function TAContent({ item, setHref, config, goBack }) { + const handleBackClick = () => { + if (goBack) { + goBack() + } + } + + return ( +
+
+
+ {![ + 'Что такое «Академия перевода»?', + 'What is unfoldingWord® Translation Academy?', + ].includes(item?.title) && ( + + )} +
+ + {item?.title} + +
+
+ +
+ + {item?.text} + +
+
+
+ ) +} + +export default TAContent diff --git a/components/Panel/UI/TNTWLContent.js b/components/Panel/UI/TNTWLContent.js index 8c10d481..97dc8d67 100644 --- a/components/Panel/UI/TNTWLContent.js +++ b/components/Panel/UI/TNTWLContent.js @@ -1,12 +1,31 @@ +import React, { useEffect, useRef } from 'react' + import ReactMarkdown from 'react-markdown' import MarkdownExtended from 'components/MarkdownExtended' import Back from 'public/icons/left.svg' -function TNTWLContent({ setItem, item }) { +function TNTWLContent({ setItem, item, parentItem, setParentItem, setHref, config }) { + const contentRef = useRef(null) + + useEffect(() => { + if (contentRef.current) { + contentRef.current.scrollTop = 0 + } + }, [item, parentItem]) + + const handleBackClick = () => { + setItem(item.type === 'ta' || item.type === 'tw' ? parentItem : null) + setParentItem(null) + if (setHref) { + setHref(null) + } + } + return (
setItem(null)} + onClick={handleBackClick} >
@@ -27,7 +46,14 @@ function TNTWLContent({ setItem, item }) { )}
- {item?.text} + + {item?.text} +
) } diff --git a/components/Panel/UI/TaTopics.js b/components/Panel/UI/TaTopics.js new file mode 100644 index 00000000..4587890e --- /dev/null +++ b/components/Panel/UI/TaTopics.js @@ -0,0 +1,117 @@ +import { useEffect, useRef, useState } from 'react' + +import { useRouter } from 'next/router' + +import TaContentInfo from '../Resources/TAContentInfo' +import TAContent from './TAContent' + +import { getFile } from 'utils/apiHelper' +import { academyLinks } from 'utils/config' +import { getWordsAcademy, resolvePath } from 'utils/helper' + +import Loading from 'public/icons/progress.svg' + +function TaTopics() { + const { locale } = useRouter() + + const config = locale === 'ru' ? academyLinks['ru'] : academyLinks['en'] + + const [href, setHref] = useState('intro/ta-intro') + const [item, setItem] = useState(null) + const [history, setHistory] = useState([]) + const [loading, setLoading] = useState(false) + const scrollRef = useRef(null) + + const updateHref = (newRelativePath) => { + const { absolutePath } = resolvePath(config.base, href, newRelativePath) + const newHref = absolutePath.replace(config.base + '/', '') + + if (newHref === href) { + setHref('') + setTimeout(() => setHref(newHref), 0) + } else { + setHistory((prev) => [...prev, href]) + setHref(newHref) + } + } + + const goBack = () => { + setHistory((prev) => { + const newHistory = [...prev] + const lastHref = newHistory.pop() + if (lastHref) setHref(lastHref) + return newHistory + }) + } + + useEffect(() => { + const getData = async () => { + setLoading(true) + try { + const zip = await getFile({ + owner: config.resource.owner, + repo: config.resource.repo.split('_')[0] + '_ta', + commit: config.resource.commit, + apiUrl: '/api/git/ta', + }) + + const fetchedWords = await getWordsAcademy({ + zip, + href: `${config.base}/${href}`, + }) + + const title = fetchedWords?.['sub-title'] || href + const text = fetchedWords?.['01'] || href + const item = { + title, + text, + type: 'ta', + } + setItem?.(item) + } catch (error) { + console.error('Error fetching data:', error) + } finally { + setLoading(false) + } + } + + getData() + }, [href, config.base, config.resource]) + + useEffect(() => { + if (scrollRef.current) { + const firstChild = scrollRef.current.firstElementChild + if (firstChild) { + firstChild.scrollIntoView({ behavior: 'auto', block: 'start' }) + } + } + }, [item]) + + return ( +
+ {loading && ( +
+ +
+ )} +
+ updateHref(newRelativePath)} + setItem={setItem} + goBack={goBack} + parentItem={item} + /> +
+ +
+ ) +} + +export default TaTopics diff --git a/components/Panel/UI/index.js b/components/Panel/UI/index.js index ed0a5199..cbfb4633 100644 --- a/components/Panel/UI/index.js +++ b/components/Panel/UI/index.js @@ -1,3 +1,4 @@ export { default as Placeholder } from './Placeholder' export { default as AutoSizeTextArea } from './AutoSizeTextArea' export { default as TNTWLContent } from './TNTWLContent' +export { default as TAContent } from './TAContent' diff --git a/components/SideBar.js b/components/SideBar.js index 61ac9586..d5ce8610 100644 --- a/components/SideBar.js +++ b/components/SideBar.js @@ -13,6 +13,7 @@ import AboutProject from './AboutProject' import AvatarSelector from './AvatarSelector' import ModalInSideBar from './ModalInSideBar' import { PersonalNotes } from './Panel' +import TaTopics from './Panel/UI/TaTopics' import ProjectCreate from './ProjectCreate' import SignOut from './SignOut' import Feedback from './StartPage/Feedback' @@ -24,6 +25,7 @@ import TranslatorImage from './TranslatorImage' import { useCurrentUser } from 'lib/UserContext' import About from 'public/icons/about.svg' +import AcademicCap from 'public/icons/academicCap.svg' import Account from 'public/icons/account.svg' import Burger from 'public/icons/burger.svg' import Camera from 'public/icons/camera.svg' @@ -61,6 +63,7 @@ function SideBar({ setIsOpenSideBar, access, isOpenSideBar }) { aboutVersion: false, avatarSelector: false, notepad: false, + tAcademy: false, writeToUs: false, about: false, } @@ -76,6 +79,7 @@ function SideBar({ setIsOpenSideBar, access, isOpenSideBar }) { aboutVersion: false, avatarSelector: false, notepad: false, + tAcademy: false, writeToUs: false, about: false, }) @@ -134,7 +138,9 @@ function SideBar({ setIsOpenSideBar, access, isOpenSideBar }) { > e.stopPropagation()} onMouseEnter={() => { @@ -148,6 +154,7 @@ function SideBar({ setIsOpenSideBar, access, isOpenSideBar }) { modalsSidebarState.aboutVersion || modalsSidebarState.avatarSelector || modalsSidebarState.writeToUs || + modalsSidebarState.tAcademy || modalsSidebarState.about ) { return @@ -290,6 +297,50 @@ function SideBar({ setIsOpenSideBar, access, isOpenSideBar }) {
+ +
{ + openModal('tAcademy') + setShowAbout(false) + }} + > +
+ +
+ { + setModalsSidebarState((prev) => ({ + ...prev, + tAcademy: value, + })) + setCollapsed(!value) + setIsOpenSideBar(value) + }} + modalTitle={t('translationAcademy')} + buttonTitle={t('translationAcademy')} + collapsed={collapsed} + contentClassName="mt-4 p-0" + > + + +
+
{user?.is_admin && ( + + diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 1a26420e..b4dfdbcf 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -1,6 +1,8 @@ { + "About": "About", "AboutStep": "About step", "AboutTranslation": "About translation", + "Account": "Account", "Added": "Add", "AddWord": "Add word", "AllNotes": "All notes", @@ -46,6 +48,7 @@ "Create": "Create", "CreateBook": "Create a book", "CreateFailed": "Creation Error", + "CreateProject": "Create project", "Delete": "Delete", "Description": "Description", "DescriptionConfession": "consistent with the historical symbols of faith:
The Apostolic Creed, the Nicene Creed, and the Athanasian Creed; as well as the Lausanne Covenant.", @@ -85,7 +88,10 @@ "info": "Info", "intro": "Introduction to the chapter", "IntroStep": "Intro", + "Language": "Language", + "LEVEL": "LEVEL", "LevelChecks": "Verification levels", + "LEVELLanguages": "Languages of the LEVEL", "LevelTranslationChecks": "Levels of translation verification", "ListResources": "List of resources", "Loading": "Loading", @@ -117,6 +123,7 @@ "personalNotes": "Personal notes", "PlatformForBibleTranslate": "Guided Bible translation platform", "Progress": "Progress", + "Projects": "Projects", "ProjectSettings": "Project settings", "Properties": "Properties", "Questions": "Questions", @@ -146,6 +153,7 @@ "Step": "Step", "StudyNotes": "Study Notes", "Subtitle": "Subtitle", + "Symbols": "Symbols", "TableOfContents": "Table Of Contents", "teamNotes": "Team notes", "TextDescriptionWord": "Write the meaning of the word", @@ -155,6 +163,7 @@ "tquestions": "tQuestions", "Training": "Training", "translate": "Editor", + "translationAcademy": "Translation Academy", "TranslationGoal": "The purpose of the translation", "TranslationLink": "Link to the translation", "Translator": "Translator", @@ -165,8 +174,6 @@ "Upload": "Upload", "UploadAvatar": "Upload avatar", "UploadFailed": "Upload failed", - "LEVEL": "LEVEL", - "LEVELLanguages": "Languages of the LEVEL", "Ver": "ver", "Verse": "Verse", "Verse_few": "{{count}} verses", @@ -184,11 +191,5 @@ "WithoutImages": "Without images", "WordExist": "Duplicated word exists:", "WrongResource": "Wrong resource", - "Yes": "Yes", - "Symbols": "Symbols", - "Account": "Account", - "Projects": "Projects", - "CreateProject": "Create project", - "About": "About", - "Language": "Language" + "Yes": "Yes" } diff --git a/public/locales/es/common.json b/public/locales/es/common.json index 573d5cd2..9b48cd32 100644 --- a/public/locales/es/common.json +++ b/public/locales/es/common.json @@ -1,6 +1,8 @@ { + "About": "Sobre el proyecto", "AboutStep": "Acerca del paso", "AboutTranslation": "Acerca de la traducción", + "Account": "Cuenta", "Added": "Add", "AddWord": "Añada palabra", "AllNotes": "All notes", @@ -46,6 +48,7 @@ "Create": "Create", "CreateBook": "Create a book", "CreateFailed": "Creation Error", + "CreateProject": "Crear un proyecto", "Delete": "Eliminar", "Description": "Descripción", "DescriptionConfession": "consistent with the historical symbols of faith:
The Apostolic Creed, the Nicene Creed, and the Athanasian Creed; as well as the Lausanne Covenant.", @@ -85,7 +88,10 @@ "info": "Info", "intro": "Intro", "IntroStep": "Intro", + "Language": "Idioma", + "LEVEL": "LEVEL", "LevelChecks": "Niveles de verificación", + "LEVELLanguages": "Languages of the LEVEL", "LevelTranslationChecks": "Niveles de verificación de traducción", "ListResources": "List of resources", "Loading": "Loading", @@ -117,6 +123,7 @@ "personalNotes": "Personal notes", "PlatformForBibleTranslate": "Plataforma de traducción bíblica guiada", "Progress": "Progress", + "Projects": "Proyectos", "ProjectSettings": "Project settings", "Properties": "Properties", "Questions": "Questions", @@ -146,6 +153,7 @@ "Step": "Step", "StudyNotes": "Study Notes", "Subtitle": "Subtítulo", + "Symbols": "Símbolos", "TableOfContents": "Tabla de contenido", "teamNotes": "Team notes", "TextDescriptionWord": "Write the meaning of the word", @@ -155,6 +163,7 @@ "tquestions": "tQuestions", "Training": "Training", "translate": "Editor", + "translationAcademy": "Academia de traducción", "TranslationGoal": "The purpose of the translation", "TranslationLink": "Enlace a la traducción", "Translator": "Traductor", @@ -165,8 +174,6 @@ "Upload": "Subir", "UploadAvatar": "Subir avatar", "UploadFailed": "No subir", - "LEVEL": "LEVEL", - "LEVELLanguages": "Languages of the LEVEL", "Ver": "ver", "Verse": "Verso", "Verse_few": "{{count}} versículos", @@ -184,11 +191,5 @@ "WithoutImages": "Without images", "WordExist": "Duplicated word exists:", "WrongResource": "Wrong resource", - "Yes": "Yes", - "Symbols": "Símbolos", - "Account": "Cuenta", - "Projects": "Proyectos", - "CreateProject": "Crear un proyecto", - "About": "Sobre el proyecto", - "Language": "Idioma" + "Yes": "Yes" } diff --git a/public/locales/ru/common.json b/public/locales/ru/common.json index 1bb7ddff..65c7dec0 100644 --- a/public/locales/ru/common.json +++ b/public/locales/ru/common.json @@ -1,6 +1,8 @@ { + "About": "О проекте", "AboutStep": "О шаге", "AboutTranslation": "О переводе", + "Account": "Личный кабинет", "Added": "Добавить", "AddWord": "Добавить слово", "AllNotes": "Все заметки", @@ -46,6 +48,7 @@ "Create": "Создать", "CreateBook": "Создать книгу", "CreateFailed": "Ошибка при создании", + "CreateProject": "Создать проект", "Delete": "Удалить", "Description": "Описание", "DescriptionConfession": "согласуется с историческими символами веры:
Апостольский символ веры, Никейский символ веры, и Афанасьевский символ веры; а также Lausanne Covenant.", @@ -85,7 +88,10 @@ "info": "Инфо", "intro": "Введение в главу", "IntroStep": "Введение", + "Language": "Язык", + "LEVEL": "LEVEL", "LevelChecks": "Уровни проверки", + "LEVELLanguages": "LEVEL языки", "LevelTranslationChecks": "Уровни проверки перевода", "ListResources": "Список ресурсов", "Loading": "Загрузка", @@ -117,6 +123,7 @@ "personalNotes": "Блокнот", "PlatformForBibleTranslate": "Платформа для пошагового перевода Библии", "Progress": "Прогресс", + "Projects": "Проекты", "ProjectSettings": "Настройки проекта", "Properties": "Свойства", "Questions": "Вопросы", @@ -146,6 +153,7 @@ "Step": "Шаг", "StudyNotes": "Study Notes", "Subtitle": "Подзаголовок", + "Symbols": "Символы", "TableOfContents": "Содержание", "teamNotes": "Тимнот", "TextDescriptionWord": "Напишите значение слова", @@ -155,6 +163,7 @@ "tquestions": "tQuestions", "Training": "Обучение", "translate": "Редактор", + "translationAcademy": "Академия перевода", "TranslationGoal": "Цель перевода", "TranslationLink": "Ссылка на перевод", "Translator": "Переводчик", @@ -165,8 +174,6 @@ "Upload": "Загрузить", "UploadAvatar": "Загрузить аватар", "UploadFailed": "Ошибка загрузки", - "LEVEL": "LEVEL", - "LEVELLanguages": "LEVEL языки", "Ver": "ст.", "Verse": "Стих", "Verse_few": "{{count}} стиха", @@ -184,11 +191,5 @@ "WithoutImages": "Без изображений", "WordExist": "Такое слово уже существует:", "WrongResource": "Ошибка загрузки ресурса", - "Yes": "Да", - "Symbols": "Символы", - "Account": "Личный кабинет", - "Projects": "Проекты", - "CreateProject": "Создать проект", - "About": "О проекте", - "Language": "Язык" + "Yes": "Да" } diff --git a/public/locales/ru/project-edit.json b/public/locales/ru/project-edit.json index 1c4807b4..f19a411a 100644 --- a/public/locales/ru/project-edit.json +++ b/public/locales/ru/project-edit.json @@ -34,7 +34,6 @@ "RemovingCoordinator": "Удаление координатора", "RemovingModerator": "Удаление модератора", "RemovingTranslator": "Удаление переводчика", - "ResourcesList": "Список ресурсов", "Steps": "Шаги", "Summary": "Резюме", "Tools": "Инструменты", diff --git a/public/updates_en.md b/public/updates_en.md index 69a59c80..c7ea5702 100644 --- a/public/updates_en.md +++ b/public/updates_en.md @@ -1,3 +1,13 @@ +# Version 0.26.0 +## Date: 27.11.2024 + +### **Added:** +- Translation Academy in sidebar + +### **Changed** +- inside tWords, cross-references between words work +- links to articles of the Translation Academy work inside tWords and tNotes + # Version 0.25.1 ## Date: 11/08/2024 diff --git a/public/updates_es.md b/public/updates_es.md index 39be20b9..f19d96f6 100644 --- a/public/updates_es.md +++ b/public/updates_es.md @@ -1,3 +1,13 @@ +# Versión 0.26.0 +## Date: 27.11.2024 + +### **Agregado:** +- Academia de traducción en sidbar + +### * * Modificado** +- dentro de tWords funcionan los enlaces cruzados entre palabras +- dentro de tWords y tNotes funcionan enlaces a artículos De la Academia de traducción + # Versión 0.25.1 ## Date: 11/08/2024 diff --git a/public/updates_ru.md b/public/updates_ru.md index ac2dd9de..f0b319d5 100644 --- a/public/updates_ru.md +++ b/public/updates_ru.md @@ -1,3 +1,13 @@ +# Версия 0.26.0 +## Date: 27.11.2024 + +### **Добавлено:** +- Академия перевода в сайдбаре + +### **Изменено** +- внутри tWords работают перекрестные ссылки между словами +- внутри tWords и tNotes работают ссылки на статьи Академии перевода + # Версия 0.25.1 ## Date: 08.11.2024 diff --git a/utils/config.js b/utils/config.js index 2f6b224c..af07fd76 100644 --- a/utils/config.js +++ b/utils/config.js @@ -258,3 +258,20 @@ export const bookChapters = { rev: 22, obs: 50, } + +export const academyLinks = { + ru: { + resource: { + repo: 'ru_tn', + owner: 'ru_gl', + }, + base: 'rc://ru/ta/man', + }, + en: { + resource: { + repo: 'en_tn', + owner: 'unfoldingWord', + }, + base: 'rc://en/ta/man', + }, +} diff --git a/utils/helper.js b/utils/helper.js index af99ac50..a34e6a51 100644 --- a/utils/helper.js +++ b/utils/helper.js @@ -676,6 +676,47 @@ export const validationBrief = (brief_data) => { return { error: null } } +const transformHref = (href) => { + if (href.startsWith('rc://')) { + const parts = href.slice(5).split('/') + return `${parts[0]}_${parts[1]}/${parts[3]}/${parts[4]}` + } + return href +} + +export const getWordsAcademy = async ({ zip, href }) => { + if (!zip || !href) { + console.error('The archive is not provided.') + return {} + } + const transformedHref = transformHref(href) + const targetFiles = [ + `${transformedHref}/title.md`, + `${transformedHref}/01.md`, + `${transformedHref}/sub-title.md`, + ] + + const results = await Promise.all( + targetFiles.map(async (filePath) => { + const file = zip.files[filePath] + if (!file) { + console.warn(`The ${filePath} file was not found.`) + return { path: filePath, content: null } + } + + const content = await file.async('text') + return { path: filePath, content } + }) + ) + + const fileObject = results.reduce((acc, { path, content }) => { + const key = path.split('/').pop().replace('.md', '') + acc[key] = content + return acc + }, {}) + return fileObject +} + export const getWords = async ({ zip, repo, wordObjects }) => { if (!zip || !repo || !wordObjects) { return [] @@ -699,6 +740,26 @@ export const getWords = async ({ zip, repo, wordObjects }) => { return await Promise.all(promises) } +export const getWord = async ({ zip, repo, TWLink }) => { + if (!zip || !repo || !TWLink) { + return null + } + let uriMd = repo + '/bible/' + TWLink + uriMd = uriMd.replace('/../', '/') + + try { + const markdown = await zip.files[uriMd].async('string') + const splitter = markdown?.search('\n') + return { + title: markdown?.slice(0, splitter), + text: markdown?.slice(splitter), + } + } catch (error) { + console.error('Error fetching markdown:', error) + return null + } +} + export const stepValidation = (step) => { try { const obj = JSON.parse(JSON.stringify(step)) @@ -872,3 +933,28 @@ export const getImageUrl = (imageUrl) => { } return '' } + +export function resolvePath(base, currentPath, relativePath) { + let absolutePath = currentPath.startsWith('/') ? currentPath : `/${currentPath}` + const relativeParts = relativePath.split('/') + + if (relativeParts.length && relativeParts[relativeParts.length - 1].includes('.')) { + relativeParts.pop() + } + + const absoluteParts = absolutePath.split('/').filter(Boolean) + + relativeParts.forEach((segment) => { + if (segment === '..') { + absoluteParts.pop() + } else if (segment && segment !== '.') { + absoluteParts.push(segment) + } + }) + + absolutePath = `${base}/${absoluteParts.join('/')}` + + const updatedCurrentPath = `/${absoluteParts.join('/')}` + + return { absolutePath, updatedCurrentPath } +}