UNPKG

@crowdin/crowdin-apps-functions

Version:

Utility library to easily and quickly develop Crowdin App

339 lines (335 loc) 14.9 kB
"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;