How to add support Typescript for FBT an internationalization framework
Tags: typescript, fbt, i18n, react
Published: 2020-05-23 12:33:44

---
title: How to add support Typescript for FBT an internationalization framework
published: true
description: support Typescript for FBT an internationalization framework
tags: typescript, fbt, i18n, react,
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/7z31rdtp28pnvfxg67iq.png
---


[FBT an internationalization framework](https://facebook.github.io/fbt/docs/api_intro/) is a really powerful tool that changes the i18n game!

But when  I try using it in Typescript I faced problems. And to answer a question in the title of an article you need to know about limitations:


## Has`fbt` module a lib definition?


Let's try to use it in the project:

```js
import * as React from "react";
import fbt from "fbt";

const App = () => <fbt desc="welcome message">Hi fbt & Typescript</fbt>;
```

and you will see the next errors

```sh
TS2339: Property 'fbt' does not exist on type 'JSX.IntrinsicElements'.

TS7016: Could not find a declaration file for module 'fbt'. 
  'node_modules/fbt/lib/FbtPublic.js' implicitly has an 'any' type.
  Try `npm install @types/fbt` if it exists or add a new declaration 
  (.d.ts) file containing `declare module 'fbt';`
```

Let's try to add lib definition using npm module `@types/fbt`:

```sh
yarn add @types/fbt

[1/4] Resolving packages...

error An unexpected error occurred: "https://registry.yarnpkg.com/@types%2ffbt: Not found".
```

`"Not found"` it is classic)

**1st limitation:  As you see we need to create our own lib definition for `fbt` module & extends `JSX.IntrinsicElements` interface for support `<fbt/>` tag.**

---


## Does Typescript support XML namespaces syntax `<ftb:param>{...}</ftb:param>`?

Then let's add param declaration to find out it:

```js
import * as React from "react";
import fbt from "fbt";

const App = () => (
  <fbt desc="welcome message">
    Hi fbt & Typescript<fbt:param name="version">{"3.9.2"}</fbt:param>!
  </fbt>
);
```

And you will see lots of errors:

```sh
Error:(6, 28) TS1003: Identifier expected.
Error:(6, 64) TS1005: '>' expected.
Error:(6, 70) TS1005: ',' expected.
Error:(7,  3)  TS1109: Expression expected.
Error:(8,  1)  TS1109: Expression expected.
```

This known issue that still opened: https://github.com/microsoft/TypeScript/issues/11833

**2nd limitation: Typescript doesn't support XML namespaces syntax**

---



## Can we overcome this limitation?


1) First, need to solve **2nd limitation**:

You can use undocumented aliases for all helpers:

```js
<fbt:enum/>        <FbtEnum/>
<fbt:param/>       <FbtParam/>
<fbt:plural/>      <FbtPlural/>
<fbt:pronoun/>     <FbtPronoun/>
<fbt:name/>        <FbtName/>
<fbt:same-param/>  <FbtSameParam/>
```
See: [babel-plugin-fbt/FbtUtil.js#L91-L100](https://github.com/facebook/fbt/blob/6173a13947927b4e18179afe41eff2e4509a71aa/packages/babel-plugin-fbt/FbtUtil.js#L91-L100)


2) Then solve **1st limitation**:

2.1) Update `compilerOptions.typeRoots` to use own definitions:

```diff
// tsconfig.json
{
  "compilerOptions": {
+    "typeRoots": ["./@types", "./node_modules/@types"]
  }
}
```

2.2) And create two files:

[./@types/fbt/index.d.ts](https://gist.github.com/retyui/c3bd35220fb5c95c322df866834f0913#file-index-d-ts)
[./@types/fbt/globals.d.ts](https://gist.github.com/retyui/c3bd35220fb5c95c322df866834f0913#file-globals-d-ts)




These steps enough to Typescript start to understand "fbt syntax"

---

**It should work, right?**

NO!!! [@babel/preset-typescript](https://babeljs.io/docs/en/babel-preset-typescript) has some unclear behavior(
 
To understand the problem,
I compiled code from the first example using [babel repl](https://babeljs.io/repl) + [@babel/preset-react](https://babeljs.io/docs/en/babel-preset-react)

```js
// Before
import * as React from "react";
import fbt from "fbt";

const App = () => <fbt desc="welcome message">Hi fbt & Typescript</fbt>;
```

```js
// After
import * as React from "react";
import fbt from "fbt";

const App = () =>
  React.createElement(
    "fbt",
    { desc: "welcome message" },
    "Hi fbt & Typescript"
  );

```

`<fbt/>` => `React.createElement("fbt")` 

As you see above, a `fbt` variable in import declaration `import fbt from "fbt"` **never used**!


Then let's have a look how [@babel/preset-typescript](https://babeljs.io/docs/en/babel-preset-typescript) works with type imports:


![babel](https://dev-to-uploads.s3.amazonaws.com/i/msr0okz33vrqzpjgg0mq.png)

The main idea that `@babel/preset-typescript` remove **unused imports**


And when you combine `@babel/preset-typescript` + `babel-plugin-fbt` you will be faced with next the error when you try to compile code:

```sh
fbt is not bound. Did you forget to require('fbt')?

# or

error index.js: ./index.js: generateFormattedCodeFromAST is not a function. Run CLI with --verbose flag for more details.
TypeError: ./index.js: generateFormattedCodeFromAST is not a function
    at errorAt (./node_modules/babel-plugin-fbt/FbtUtil.js:237:21)
    at FbtFunctionCallProcessor._assertJSModuleWasAlreadyRequired (./node_modules/babel-plugin-fbt/babel-processors/FbtFunctionCallProcessor.js:158:13)
    at FbtFunctionCallProcessor.convertToFbtRuntimeCall (./node_modules/babel-plugin-fbt/babel-processors/FbtFunctionCallProcessor.js:570:10)
    at PluginPass.CallExpression (./node_modules/babel-plugin-fbt/index.js:188:18)
```

So what happened?  

1) `<fbt/>` => `React.createElement("fbt")` 
2) after that `@babel/preset-typescript` see that `import fbt from "fbt"` never used and remove it
3) then `babel-plugin-fbt` couldn't find `fbt` and throw an error


To prevent removing `fbt` import you need to patch one file `node_modules/@babel/plugin-transform-typescript/lib/index.js`

```diff
  function isImportTypeOnly({
    binding,
    programPath,
    jsxPragma
  }) {
    for (const path of binding.referencePaths) {
      if (!isInType(path)) {
        return false;
      }
    }

+    // Small fix to stop removing `import fbt from 'fbt';`
+    if (binding.identifier.name === 'fbt') {
+      return false;
+    }


    if (binding.identifier.name !== jsxPragma) {
      return true;
    }
```


How to apply patch after install? 
Use `postinstall` script:


```json
{
  "scripts": {
    "postinstall": "node path.js",
  }
}
```

```js
// path.js
const { readFileSync, writeFileSync } = require('fs');

const patch = `
    // Small fix to stop removing \`import fbt from 'fbt';\`
    if (binding.identifier.name === 'fbt') {
      return false;
    }
`;

const FILE_PATH = require.resolve(
  '@babel/plugin-transform-typescript/lib/index.js',
);

const data = readFileSync(FILE_PATH).toString();
const isAlreadyPatched = data.includes("binding.identifier.name === 'fbt'");

if (isAlreadyPatched) {
  process.exit(0);
}

writeFileSync(
  FILE_PATH,
  data.replace(
    'if (binding.identifier.name !== jsxPragma) {',
    `${patch}\nif (binding.identifier.name !== jsxPragma) {`,
  ),
);
```



Утиииии We made it!
So that enough) 
if you have any question I am glad to discuss them in the comments!

(c) MurAmur