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({
-
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 5b99e762..d677fe53 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'
@@ -60,6 +62,7 @@ function SideBar({ setIsOpenSideBar, access, isOpenSideBar }) {
aboutVersion: false,
avatarSelector: false,
notepad: false,
+ tAcademy: false,
writeToUs: false,
about: false,
}
@@ -75,6 +78,7 @@ function SideBar({ setIsOpenSideBar, access, isOpenSideBar }) {
aboutVersion: false,
avatarSelector: false,
notepad: false,
+ tAcademy: false,
writeToUs: false,
about: false,
})
@@ -133,7 +137,9 @@ function SideBar({ setIsOpenSideBar, access, isOpenSideBar }) {
>
e.stopPropagation()}
onMouseEnter={() => {
@@ -147,6 +153,7 @@ function SideBar({ setIsOpenSideBar, access, isOpenSideBar }) {
modalsSidebarState.aboutVersion ||
modalsSidebarState.avatarSelector ||
modalsSidebarState.writeToUs ||
+ modalsSidebarState.tAcademy ||
modalsSidebarState.about
) {
return
@@ -387,6 +394,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 }
+}