Custom Bundle Builder FreeBeta
ByAwesome CrowdinVerified Author

Your own script to build files from bundles

Install

Custom Bundle Exporter

Copy link

Bundles in Crowdin enable you to export your project strings or a subset of them into a resource file that can be utilized during the build process of your product or even delivered to the end consumer via Crowdin OTA. Crowdin natively supports several bundle exporters and also has many exporters available as apps in the Crowdin Store.

This bundle exporter allows you to easily create your own file builder. It requires you to provide a Serverless function URL such as a Vercel Edge function or AWS Lambda. The custom bundle exporter will accept a request from Crowdin and proxy it to your function, which will provide a compiled resource file that will be delivered as a translated file from Crowdin.

Once you install this app and configure your Serverless function URL, you will be able to create a Bundle in Crowdin and select this exporter.

Configuration Screen

Copy link

Crowdin Custom Bundle Exporter

Your serverless function should do the following:

  • Accept the POST request made by the custom bundle exporter.
  • Iterate through the strings array from the request payload and compile the resource file in the desired format.
  • Use the Crowdin API or any other necessary API to gather additional information needed to compile the resulting file.

Below, you can find a sample payload as well as a sample function that you can deploy to Vercel.

Sample Export Function

Copy link
export default async function handler(req, res) {
  const { body } = req;

  const result = body.strings.map(s => {
    return {
      id: s.identifier,
      text: s.translations[body.languageId].text
    }
  })

  return res.send(JSON.stringify(result));
}

Follow this guide to have it deployed to your Vercel.

Check out our minimalistic build function on Github.

Remember that your function should always return a string containing a contents of your resulting file.

This function will return plain JSON file containing Crowdin Identifier as a key and translations to the selected language as a value. Sample output would be:

[
  {
    "id": "identifier_1",
    "text": "翻訳のための文字列 1"
  },
  {
    "id": "identifier_2",
    "text": "翻訳用文字列 2"
  }
  .....
]

Useful functions when developing custom exporters

Copy link

You are welcome to contact the Crowdin Support team when you begin developing your own bundle exporter. Crowdin has 24x7 technical support available and is happy to assist you.

Placeholders

Copy link

Your resulting resource file may use its own convention to represent placeholders in text. If you use the Unified Placeholders feature of your Crowdin project, you can create a function to convert Crowdin tags to the desired ones. The function below demonstrates how to convert Crowdin unified placeholders to the convention used in Flutter's ARB resources. If you do not use Unified placeholders, you can still convert the placeholder format used in your source strings to the placeholder format you need in your resulting file.

function convertPlaceholders(string) {
  if (body.project.normalizePlaceholder) {
    //Crowdin' unified placeholders look like [%s]
    return string.replaceAll(/\[%([^\]]+)\]/gm, `{$1}`);
  } else {
    return string;
  }
}

Plurals

Copy link

Many internationalization libraries support pluralization. Even if your format does not support it (e.g. if you use your own tools for i18n or the technology does not support plurals), you can always use ICU, a Unicode standard that is implemented for most major tech stacks. If your Crowdin string has plurals, the payload would look like the following:

{
  id: 1864390,
  identifier: 'apples_count',
  context: 'apples_count [one] [other]',
  customData: '',
  maxLength: null,
  isHidden: false,
  hasPlurals: true,
  labels: [],
  text: { one: '[%s] apple', other: '[%s] apples' },
  translations: {
    uk: {
      text: {
        one: '[%s] яблучко',
        few: '[%s] яблука',
        many: '[%s] яблук',
        other: '[%s] яблука'
      },
      status: {
        one: 'approved',
        few: 'translated',
        many: 'translated',
        other: 'translated'
      }
    }
  }
}

Ukrainian language has four plural forms, all of which will be present in the payload. You can convert this object to the ICU syntax using the following or similar function

function serializeICU(identifier, translations) {
  const pluralFormsTranslations = Object.entries(translations).map(([key, value]) => ` ${key} {${value}}`).join('');

  return `{${identifier}, plural, ${pluralFormsTranslations}}`;
}

And the resulting ICU string will be like following:

{key, plural,  one {[%s] яблучко} few {[%s] яблука} many {[%s] яблук} other {[%s] яблука}}

Sample POST JSON Payload

Copy link
{
  languageId: 'ja',
  req: {
    jobType: 'build-file',
    organization: {
      id: 200000009,
      domain: 'serhiy',
      baseUrl: 'https://serhiy.crowdin.com',
      apiBaseUrl: 'https://serhiy.api.crowdin.com'
    },
    project: {
      id: 398,
      identifier: '1a57678bc6171e5713595b83e8ef8b90',
      name: 'Unified Placeholders and Bundles'
    },
    file: { id: 14854, name: 'strings.xml', path: '' },
    customSrxContents: null,
    sourceLanguage: {
      id: 'en',
      name: 'English',
      editorCode: 'en',
      twoLettersCode: 'en',
      threeLettersCode: 'eng',
      locale: 'en-US',
      androidCode: 'en-rUS',
      osxCode: 'en.lproj',
      osxLocale: 'en',
      pluralCategoryNames: [ 'one', 'other' ],
      pluralRules: '(n != 1)'
    },
    targetLanguages: [
      {
        id: 'ja',
        name: 'Japanese',
        editorCode: 'ja',
        twoLettersCode: 'ja',
        threeLettersCode: 'jpn',
        locale: 'ja-JP',
        androidCode: 'ja-rJP',
        osxCode: 'ja.lproj',
        osxLocale: 'ja',
        pluralCategoryNames: [ 'other' ],
        pluralRules: '0'
      }
    ],
    strings: [
      {
        id: 1864380,
        identifier: 'identifier_1',
        context: 'identifier_1',
        customData: '',
        maxLength: null,
        isHidden: false,
        hasPlurals: false,
        labels: [],
        text: 'String for translation 1',
        translations: { ja: { text: '翻訳のための文字列 1', status: 'translated' } }
      }
  ],
  context: {
    jwtPayload: {
      aud: 'WKcvYqBU59RGSRzjoMzn',
      sub: '2',
      domain: 'serhiy',
      context: { project_id: 398, organization_id: 200000009, user_id: 2 },
      iat: 1672494603.015501,
      exp: 1672495563.015501
    },
    clientId: 'serhiy__398__2',
    crowdinId: 'serhiy'
  },
  project: {
    id: 398,
    groupId: 2,
    userId: 2,
    sourceLanguageId: 'en',
    targetLanguageIds: [ 'ja', 'uk' ],
    name: 'Unified Placeholders and Bundles',
    identifier: '1a57678bc6171e5713595b83e8ef8b90',
    description: '',
    logo: 'data:image/png;base64,iV ...',
    background: null,
    isExternal: false,
    externalType: null,
    workflowId: 201,
    hasCrowdsourcing: false,
    publicDownloads: true,
    createdAt: '2022-12-28T08:24:56+00:00',
    updatedAt: '2022-12-28T08:24:56+00:00',
    lastActivity: '2022-12-31T07:11:12+00:00',
    targetLanguages: [
      {
        id: 'ja',
        name: 'Japanese',
        editorCode: 'ja',
        twoLettersCode: 'ja',
        threeLettersCode: 'jpn',
        locale: 'ja-JP',
        androidCode: 'ja-rJP',
        osxCode: 'ja.lproj',
        osxLocale: 'ja',
        pluralCategoryNames: [ 'other' ],
        pluralRules: '0',
        pluralExamples: [ '0-999; 1.2...' ],
        textDirection: 'ltr',
        dialectOf: null
      }
    ],
    translateDuplicates: 0,
    tagsDetection: 0,
    glossaryAccess: false,
    isMtAllowed: true,
    hiddenStringsProofreadersAccess: true,
    autoSubstitution: true,
    exportTranslatedOnly: false,
    skipUntranslatedStrings: false,
    skipUntranslatedFiles: false,
    exportWithMinApprovalsCount: 0,
    exportStringsThatPassedWorkflow: false,
    autoTranslateDialects: false,
    normalizePlaceholder: true,
    saveMetaInfoInSource: true,
    inContext: false,
    inContextProcessHiddenStrings: true,
    inContextPseudoLanguageId: null,
    inContextPseudoLanguage: null,
    isSuspended: false,
    qaCheckIsActive: true,
    qaCheckCategories: {
      empty: true,
      size: true,
      tags: true,
      spaces: true,
      variables: true,
      punctuation: true,
      symbolRegister: true,
      specialSymbols: true,
      wrongTranslation: true,
      spellcheck: true,
      icu: true,
      terms: false,
      duplicate: true,
      ftl: true
    },
    customQaCheckIds: [],
    languageMapping: {},
    delayedWorkflowStart: false,
    notificationSettings: {
      translatorNewStrings: false,
      managerNewStrings: false,
      managerLanguageCompleted: false
    }
  }
}

Crowdin is a platform that helps you manage and translate content into different languages. Integrate Crowdin with your repo, CMS, or other systems. Source content is always up to date for your translators, and translated content is returned automatically.

Learn More
Categories
Development
File Formats
Works with
  • Crowdin Enterprise
  • crowdin.com
Details

Released on Dec 31, 2022

Updated on Feb 9, 2023

Published by Awesome Crowdin

Identifier:custom-export