Export a zipped file of the translations on a project

This commit is contained in:
kngako
2023-01-19 00:40:13 +02:00
parent 83c282bc73
commit eebf9266a9
6 changed files with 813 additions and 104 deletions

View File

@@ -1,6 +1,11 @@
const express = require('express')
const XL = require('excel4node');
const XLSX = require("xlsx");
const markdownProducer = require("../../../lib/markdown-producer")
const fs = require("fs")
const archiver = require('archiver');
const path = require("path")
const os = require("os")
module.exports = function (options) {
const db = options.db;
@@ -1181,7 +1186,7 @@ module.exports = function (options) {
{
association: db.TranslationChapter.Chapter,
},
request.query.sheets ? {
request.query.sheets || request.query.markdown ? {
association: db.TranslationChapter.TranslationChunks,
include: [
{
@@ -1366,6 +1371,98 @@ module.exports = function (options) {
} else {
response.redirect(`/projects/${request.params.id}`)
}
} else if (request.query.markdown) {
// Create temporary directory for export...
const directoryNamePrefix = `${project.name}-`
const tempExportDirectory = fs.mkdtempSync(
path.join(os.tmpdir(), directoryNamePrefix)
)
console.log("Saving to: ", tempExportDirectory)
translationChapters.forEach((translationChapter, index) => {
// Save to files to temp directory...
const translationChapterTextContent = markdownProducer.produceMarkdownString(translationChapter)
// TODO: Create artifact sub directory...
// Create lang sub directory...
const dialectDirectoryPath = path.join(
tempExportDirectory,
translationChapter.translationArtifactVersion.dialect.name
)
if (!fs.existsSync(dialectDirectoryPath)) {
fs.mkdirSync(
dialectDirectoryPath
)
}
fs.writeFileSync(
path.join(dialectDirectoryPath, `${translationChapter.chapter.name}${translationChapter.chapter.name.endsWith(".md") ? "" : ".md"}`),
translationChapterTextContent
)
});
// Zip tempExportDirectory and download the zipped file...
const tempZipDirectory = fs.mkdtempSync(
path.join(os.tmpdir(), directoryNamePrefix)
)
const zippedFilePath = path.join(tempZipDirectory, `${project.name}.zip`)
const zippedFile = fs.createWriteStream(
zippedFilePath
);
const archive = archiver('zip', {
zlib: { level: 9 } // Sets the compression level.
});
// listen for all archive data to be written
// 'close' event is fired only when a file descriptor is involved
zippedFile.on('close', function() {
console.log(archive.pointer() + ' total bytes');
console.log('archiver has been finalized and the zippedFile file descriptor has closed.');
response.set('Content-Disposition', `attachment; filename="${project.name}.zip"`);
response.sendFile(
zippedFilePath
)
});
// This event is fired when the data source is drained no matter what was the data source.
// It is not part of this library but rather from the NodeJS Stream API.
// @see: https://nodejs.org/api/stream.html#stream_event_end
zippedFile.on('end', function() {
console.log('Data has been drained');
});
// good practice to catch warnings (ie stat failures and other non-blocking errors)
archive.on('warning', function(err) {
console.log("Waring: ", err)
if (err.code === 'ENOENT') {
// log warning
} else {
// throw error
throw err;
}
});
// good practice to catch this error explicitly
archive.on('error', function(err) {
console.log("Error: ", err)
throw err;
});
// pipe archive data to the file
archive.pipe(zippedFile);
// append files from a sub-directory, putting its contents at the root of archive
archive.directory(tempExportDirectory, false);
// finalize the archive (ie we are done appending files but streams have to finish yet)
// 'close', 'end' or 'finish' may be fired right after calling this method so register to them beforehand
archive.finalize();
} else {
response.display("project-tracker", {
user: request.user,

View File

@@ -1,41 +1,12 @@
const express = require('express')
const { Op } = require("sequelize")
const idGenerator = require('mantra-db-models/src/lib/id-generator');
const Token = require('markdown-it/lib/token');
const md = require("markdown-it")().disable(['link'])
const markdownProducer = require("../../../lib/markdown-producer")
module.exports = function (options) {
const db = options.db;
var router = express.Router();
const produceTranslatedChapterOutput = (translationChapter) => {
return md.parse(
translationChapter.chapter.originalText
).map(token => {
if (token.content) {
const translationChunk = translationChapter.translationChunks.find(tc => {
return tc.text == token.content
})
if (translationChunk) {
token.content = translationChunk.translation.text
if (token.children.length == 1) {
token.children[0].content = translationChunk.translation.text
} else {
const newChild = new Token(
"text"
)
newChild.content = translationChunk.translation.text
token.children = [
newChild
]
}
}
}
return token
})
}
router.route('/:id')
.get(function(request, response, next) {
db.TranslationArtifactVersion.findByPk(request.params.id, {
@@ -249,16 +220,11 @@ module.exports = function (options) {
]
}).then(translationChapter => {
if (translationChapter) {
const tokens = produceTranslatedChapterOutput(translationChapter)
// TODO: Make markdown string...
response.display("translate-chapter-view", {
user: request.user,
pageTitle: `Translate Chapter ${translationChapter.name}`,
translatedChapterOutput: md.renderer.render(
tokens,
{}
),
translatedChapterOutput: markdownProducer.produceHtml(translationChapter),
translationChapter: translationChapter
})
} else {
@@ -306,16 +272,8 @@ module.exports = function (options) {
]
}).then(translationChapter => {
if (translationChapter) {
// TODO: Parse markdown...
const tokens = produceTranslatedChapterOutput(translationChapter)
// TODO: Make markdown string...
const translatedChapterContent = md.renderer.render(
tokens,
{}
)
response.contentType = "text/plain"
response.send(translatedChapterContent)
response.send(markdownProducer.produceHtml(translationChapter))
} else {
next()
}
@@ -361,54 +319,8 @@ module.exports = function (options) {
]
}).then(translationChapter => {
if (translationChapter) {
// TODO: Parse markdown...
const tokens = produceTranslatedChapterOutput(translationChapter)
// TODO: Make markdown string...
// collect tokens...
response.contentType = "text/plain"
response.send(tokens.reduce(
(accumulator, token) => {
if (token.type == "list_item_close") {
return accumulator
}
if (token.type == "ordered_list_open" || token.type == "bullet_list_open") {
return accumulator
}
if (token.type == "bullet_list_close" || token.type == "ordered_list_close") {
return accumulator + "\n"
}
if (token.type == "paragraph_close") {
if (token.level == 0) {
return accumulator + "\n\n"
} else {
return accumulator + "\n"
}
}
if (token.type == "list_item_open") {
return accumulator + `${token.info}${token.markup} `
}
if (token.type == "inline") {
return accumulator + token.content
}
if (token.type == "heading_open") {
return `${accumulator}${token.markup} `
}
if (token.type == "heading_close") {
return `${accumulator}\n`
}
return accumulator + token.markup
},
""
))
response.send(markdownProducer.produceMarkdownString(tokens))
} else {
next()
}

View File

@@ -32,6 +32,18 @@ block content
button.btn.black(type="submit")
i.material-icons.left file_download
span export to spreadsheets
.row
.col.s12
form(action=urlEndpoint, method="get")
input(type="hidden", name="markdown", value="true")
each val, key in query
if key != "markdown"
input(type="hidden", name=key, value=val)
button.btn.black(type="submit")
i.material-icons.left file_download
span export translations to markdown
.row
.col.s12
a.btn.black(href=`/projects/${project.id}/import`)