@crowdin/crowdin-apps-functions
Version:
Utility library to easily and quickly develop Crowdin App
339 lines (335 loc) • 14.9 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getStringsContext = void 0;
const axios_1 = require("axios");
const path = require("path");
const image_annotator_1 = require("./util/image-annotator");
/**
* Constructs a GraphQL query to fetch project strings and optionally translations and detailed project data.
*/
function prepareProjectDataQuery(additionalNodes, getProjectData = true) {
return `
query (
$projectId: Int!
$stringIds: [Int!]
$stringsLimit: Int!
${additionalNodes.otherLanguageTranslations ? '$languageIds: [String!]' : ''},
${additionalNodes.otherLanguageTranslations ? '$translationsLimit: Int!' : ''},
) {
viewer {
projects(first: 1, filter: { id: { equals: $projectId } }) {
edges {
node {
${getProjectData ? '...ProjectFragment' : ''}
strings(first: $stringsLimit, filter: { id: { in: $stringIds } }) {
edges {
node {
... on PlainSourceString {
id
identifier
text
context
maxLength
file {
...FileFragment
}
${(additionalNodes === null || additionalNodes === void 0 ? void 0 : additionalNodes.otherLanguageTranslations)
? `translations(first: $translationsLimit, filter: { languageId: { in: $languageIds } }) {
edges {
node {
id
text
language {
...LanguageFragment
}
}
}
}`
: ''}
}
... on PluralSourceString {
id
identifier
plurals {
zero
one
two
few
many
other
}
context
maxLength
file {
...FileFragment
}
${(additionalNodes === null || additionalNodes === void 0 ? void 0 : additionalNodes.otherLanguageTranslations)
? `translations(first: $translationsLimit, filter: { languageId: { in: $languageIds } }) {
edges {
node {
id
pluralForm
text
language {
...LanguageFragment
}
}
}
}`
: ''}
}
... on ICUSourceString {
id
identifier
text
context
maxLength
file {
...FileFragment
}
${(additionalNodes === null || additionalNodes === void 0 ? void 0 : additionalNodes.otherLanguageTranslations)
? `translations(first: $translationsLimit, filter: { languageId: { in: $languageIds } }) {
edges {
node {
id
text
language {
...LanguageFragment
}
}
}
}`
: ''}
}
}
}
}
}
}
}
}
rateLimit {
cost
limit
remaining
resetAt
}
}
fragment ProjectFragment on Project {
id
sourceLanguage {
...LanguageFragment
}
targetLanguages {
...LanguageFragment
}
name
description
}
fragment LanguageFragment on Language {
id
name
twoLettersCode
threeLettersCode
locale
pluralCategoryNames
pluralRules
pluralExamples
textDirection
dialectOf {
id
name
twoLettersCode
threeLettersCode
locale
pluralCategoryNames
pluralRules
pluralExamples
textDirection
}
}
fragment FileFragment on File {
id
name
title
context
type
path
}
`;
}
function fetchTranslationMemory({ client, projectId, expressions, sourceLanguageId, targetLanguagesIds, }) {
return __awaiter(this, void 0, void 0, function* () {
const tmSuggestions = [];
for (const targetLanguageId of targetLanguagesIds) {
const tmConcordanceSearchResult = yield client.translationMemoryApi
.withFetchAll()
.concordanceSearch(projectId, {
sourceLanguageId,
targetLanguageId,
autoSubstitution: false,
minRelevant: 60,
expressions,
});
for (const suggestion of tmConcordanceSearchResult.data) {
tmSuggestions.push(Object.assign(Object.assign({}, suggestion.data), { languageId: targetLanguageId }));
}
}
return tmSuggestions;
});
}
function fetchGlossaryTerms({ client, projectId, expressions, sourceLanguageId, targetLanguagesIds, }) {
return __awaiter(this, void 0, void 0, function* () {
const glossaryTerms = [];
for (const targetLanguageId of targetLanguagesIds) {
const glossaryConcordanceSearchResult = yield client.glossariesApi.withFetchAll().concordanceSearch(projectId, {
sourceLanguageId,
targetLanguageId,
expressions,
});
for (const term of glossaryConcordanceSearchResult.data) {
const conceptId = term.data.concept.id;
const _a = term.data, { targetTerms } = _a, termData = __rest(_a, ["targetTerms"]);
if (!glossaryTerms[conceptId]) {
glossaryTerms[conceptId] = Object.assign(Object.assign({}, termData), { targetTerms: [] });
}
glossaryTerms[conceptId].targetTerms.push(...targetTerms);
}
}
return Object.values(glossaryTerms);
});
}
function fetchScreenshots({ client, projectId, strings }) {
return __awaiter(this, void 0, void 0, function* () {
const stringIds = strings.map(str => str.id);
const screenshotsData = yield client.screenshotsApi.withFetchAll().listScreenshots(projectId, {
stringIds,
});
const annotatedScreenshots = [];
for (const screenshot of screenshotsData.data) {
const { id, url, tags, name, labels } = screenshot.data;
const imageResponse = yield axios_1.default.get(url, { responseType: 'arraybuffer' });
const buffer = Buffer.from(imageResponse.data, 'binary');
const tracks = tags
.filter((tag) => stringIds.includes(tag.stringId))
.map((tag) => {
const stringNode = strings.find(str => str.id === tag.stringId);
const text = stringNode ? stringNode.text : `ID: ${tag.stringId}`;
return {
x: tag.position.x,
y: tag.position.y,
w: tag.position.width,
h: tag.position.height,
text,
};
});
const annotator = new image_annotator_1.default(buffer);
const annotatedBuffer = yield annotator.annotate(tracks);
const extension = path.extname(url).split('?')[0]; // Handle URL query parameters
// Create a base64 data URL for the annotated image
const annotatedUrl = `data:image/${extension};base64,${annotatedBuffer.toString('base64')}`;
annotatedScreenshots.push({
id,
name,
labels,
url: annotatedUrl,
originalUrl: url,
});
}
return annotatedScreenshots;
});
}
function getStringsContext(args) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
const { client, projectId, stringIds, targetLanguagesIds, additionalNodes = {
otherLanguageTranslations: true,
glossaryTerms: true,
tmSuggestions: true,
screenshots: true,
}, stringsLimit = 2, translationsLimit = 10, } = args;
let project = {};
let strings = [];
const totalRequests = Math.ceil(stringIds.length / stringsLimit);
const query = prepareProjectDataQuery(additionalNodes);
for (let i = 0; i < totalRequests; i++) {
const start = i * stringsLimit;
const end = start + stringsLimit;
const batchStringIds = stringIds.slice(start, end);
const variables = {
projectId,
stringIds: batchStringIds,
stringsLimit: batchStringIds.length,
languageIds: targetLanguagesIds,
translationsLimit,
};
const response = yield client.graphql({ query, variables });
const projectData = response.data.viewer.projects.edges[0].node;
if (!project.id && projectData) {
project = {
id: projectData.id,
name: projectData.name,
description: projectData.description,
sourceLanguage: projectData.sourceLanguage,
targetLanguages: projectData.targetLanguages,
};
}
const stringsData = projectData.strings.edges.map((edge) => {
const _a = edge.node, { translations } = _a, restNode = __rest(_a, ["translations"]);
return Object.assign(Object.assign({}, restNode), (translations && {
translations: translations === null || translations === void 0 ? void 0 : translations.edges.map((translationEdge) => translationEdge.node),
}));
});
strings = strings.concat(stringsData);
}
const sourceLanguageId = ((_a = project.sourceLanguage) === null || _a === void 0 ? void 0 : _a.id) || 'en';
const expressions = strings.map(string => string.text);
let tmSuggestions = [];
let glossaryTerms = [];
let screenshots = [];
if (additionalNodes.tmSuggestions && strings.length > 0) {
tmSuggestions = yield fetchTranslationMemory({
client,
projectId,
expressions,
sourceLanguageId,
targetLanguagesIds,
});
}
if (additionalNodes.glossaryTerms && strings.length > 0) {
glossaryTerms = yield fetchGlossaryTerms({
client,
projectId,
expressions,
sourceLanguageId,
targetLanguagesIds,
});
}
if (additionalNodes.screenshots && strings.length > 0) {
screenshots = yield fetchScreenshots({ client, projectId, strings });
}
return Object.assign(Object.assign(Object.assign({ project,
strings }, (screenshots.length > 0 && { screenshots })), (Object.keys(tmSuggestions).length > 0 && { tmSuggestions })), (Object.keys(glossaryTerms).length > 0 && { glossaryTerms }));
});
}
exports.getStringsContext = getStringsContext;