OverviewFilesCommitsIssuesDocsReleasesPeers

ollama_semantic_function_calling / ollamatools-new / keet / node_modules / @holepunchto / keet-translations / crowdin.cjs

290 lines · javascript ref:
const fs = require('fs')
const axios = require('axios')
const yaml = require('js-yaml')
const format = require('date-fns/format')
const _ = require('lodash')
const minimist = require('./minimist-ext.cjs')

const {
  _: {
    0: command,
    1: taskIdOrKeyword
  },
  mentions = {}
} = minimist(process.argv.slice(2), { json: ['mentions'] })

// Separator used in GitHub Actions
const LOG_SEPARATOR = '---'

const getProjectConfig = () => {
  try {
    const configStrings = fs.readFileSync('crowdin-project-config.json')
    return JSON.parse(configStrings)
  } catch (error) {
    console.error('Error parsing crowdin-project-config.json file. Please check crowdin-prject-config.json.example for the example.')
    process.exit(1)
  }
}

class Crowdin {
  constructor ({ config }) {
    const {
      project_id: projectId,
      api_token_env: apiTokenEnv,
      directory_id: directoryId
    } = config
    const token = process.env[apiTokenEnv]

    this.config = config
    this.token = token
    this.directoryId = directoryId
    this.api = axios.create({
      baseURL: `https://api.crowdin.com/api/v2/projects/${projectId}`,
      timeout: 30000,
      headers: { Authorization: `Bearer ${token}` }
    })
  }

  async listProjectFiles (offset, limit = 500) {
    const { data } = await this.api.get('files', {
      params: {
        directoryId: this.directoryId,
        recursion: true,
        limit,
        offset
      }
    })

    return data.data.map(({ data: item }) => ({
      id: item.id,
      path: item.path
    }))
  }

  async listAllProjectFiles () {
    const limit = 500
    const maxOffset = 5000
    let finalList = []

    for (let offset = 0; offset < maxOffset; offset += limit) {
      const data = await this.listProjectFiles(offset, limit)

      if (_.isEmpty(data)) {
        break
      }

      finalList = [
        ...finalList,
        ...data
      ]
    }

    return finalList
  }

  async createTask (title, languageId, fileIds) {
    try {
      const { data } = await this.api.post('tasks', {
        title,
        languageId,
        fileIds,
        type: 0,
        skipAssignedStrings: true
      })

      return {
        status: true,
        data: data.data
      }
    } catch (e) {
      return {
        status: false,
        message: _.get(e, 'response.data.error.message'),
        code: _.get(e, 'response.data.error.code')
      }
    }
  }

  async closeTask (id) {
    const { data } = await this.api.patch(`/tasks/${id}`, [{
      op: 'replace',
      path: '/status',
      value: 'closed'
    }])

    return data
  }

  async listTasks (offset = 0, limit = 500, status = 'todo,in_progress,done') {
    const { data } = await this.api.get('tasks', {
      params: {
        offset,
        limit
      }
    })
    console.log('Got tasks for', data.pagination, data.data.length)
    return data.data
  }

  async listAllPendingTasks () {
    const limit = 500
    const maxOffset = 5000
    let finalList = []

    for (let offset = 0; offset < maxOffset; offset += limit) {
      const data = await this.listTasks(offset, limit)

      if (_.isEmpty(data)) {
        break
      }

      finalList = [
        ...finalList,
        ...data
      ]
    }

    return finalList
  }
}

const printTranslateLogs = (taskTitle, createdTasks) => {
  if (_.isEmpty(createdTasks)) {
    console.log(LOG_SEPARATOR)
    return
  }

  console.log('Please help translate: ')
  console.log(taskTitle)
  console.log(LOG_SEPARATOR)

  createdTasks.forEach((task) => {
    const { id, targetLanguageId } = task
    const url = `https://crowdin.com/project/holepunch/tasks/${id}`
    const mention = mentions[targetLanguageId] ? `- <@${mentions[targetLanguageId]}>` : ''
    console.log(`${url} - \`${targetLanguageId}\` ${mention}`)
  })
}

const taskCreate = async (crowdin, projectConfig) => {
  try {
    const projectFiles = await crowdin.listAllProjectFiles()
    const date = format(Date.now(), 'yyyy-MM-dd')
    const taskTitle = `${projectConfig.name} updated on ${date} - ${Date.now()}`
    const fileIds = projectFiles.map((file) => file.id)

    console.log('creating task', taskTitle)
    const createdTasks = []
    for (const languageId of projectConfig.supported_langs) {
      const taskCreatedResult = await crowdin.createTask(taskTitle, languageId, fileIds)
      if (taskCreatedResult.status === true) {
        console.log('Created task: ', languageId, taskCreatedResult.data.id)
        createdTasks.push(taskCreatedResult.data)
      } else {
        console.log('No task created: ', taskCreatedResult.message)
      }
    }

    printTranslateLogs(taskTitle, createdTasks)
  } catch (error) {
    console.log(error)
    console.log('Response error', _.get(error, 'response.data'))
  }
}

const taskCloseById = async (crowdin) => {
  const id = taskIdOrKeyword
  if (!id) {
    console.error('No ID found. Example command: ./crowdin.js task-close-by-id 123')
    process.exit(1)
  }
  try {
    const res = await crowdin.closeTask(id)
    console.log(res)
  } catch (error) {
    console.log(error)
    console.log('Response error', JSON.stringify(_.get(error, 'response.data')))
  }
}

const taskClose = async (crowdin) => {
  const idToFind = taskIdOrKeyword

  if (!idToFind) {
    console.error('No ID found. Example command: ./crowdin.js task-close 123')
    process.exit(1)
  }

  try {
    const res = await crowdin.listAllPendingTasks()
    console.log('Total pending tasks: ', res.length)
    const taskToClose = res.filter((task) => task.data.title.includes(idToFind))
    for (const task of taskToClose) {
      console.log('closing task', task.data.id)
      await crowdin.closeTask(task.data.id)
    }
  } catch (error) {
    console.log(error)
    console.log('Response error', JSON.stringify(_.get(error, 'response.data')))
  }
}

const searchTasks = async (crowdin) => {
  const keyword = taskIdOrKeyword

  if (!keyword) {
    console.error('No keyword found. Example command: ./crowdin.js task-search bfx-ui-pay')
    process.exit(1)
  }

  try {
    const res = await crowdin.listAllPendingTasks()
    console.log('Total pending tasks: ', res.length)
    const taskToShow = res.filter((task) => task.data.title.includes(keyword))
    for (const task of taskToShow) {
      console.log(task.data.id)
      console.log(task.data.title)
      console.log(task.data.translationUrl)
      console.log('------')
    }
  } catch (error) {
    console.log(error)
    console.log('Response error', JSON.stringify(_.get(error, 'response.data')))
  }
}

const run = async () => {
  const crowdinConfig = yaml.load(fs.readFileSync('crowdin.yml', 'utf8'))
  const projectConfig = getProjectConfig()
  crowdinConfig.directory_id = projectConfig.directory_id
  const crowdin = new Crowdin({ config: crowdinConfig })

  switch (command) {
    case 'task-create':
      await taskCreate(crowdin, projectConfig)
      break

    case 'task-close-by-id':
      await taskCloseById(crowdin)
      break

    case 'task-close':
      await taskClose(crowdin)
      break

    case 'task-search':
      await searchTasks(crowdin)
      break

    default:
      console.log('Available commands: ')
      console.log('task-create: Create tasks and log on console')
      console.log('task-close-by-id: Close a task by its ID')
      console.log('task-close: Close a task by our own ID from task-create command')
      console.log('task-search: Search pending tasks by keyword')
      break
  }
}

run()