Skip to content

Commit d6580a5

Browse files
authored
Migrate @fluent/react to TypeScript (#458)
* Migrate @fluent/react to TypeScript * Fix paths in fluent-gecko * Return ReactElement from Localized React typings (wrongly) expect the return type of functional components to be a ReactElement, thus making () => "Hello" invalid. This is incorrect; as of React 16, it's possible to return plain strings from functional components and Component.render(). See DefinitelyTyped#18912 and TypeScript#21699. To work around this, annotate the return value of Localized to be of type ReactElement, and whenever a string or null are returned, wrap them in Fragments. * Add the build make target * Fix compat.js make target * Export props interfaces * Export MarkupParser type * Fix return type of withLocalization * Convert elems keys to lowercase; lookup via Element.localName * Use Node.nodeName.toLowerCase() for elems lookup * Add a comment about bundle.getMessage(id)! * Move src/vendor out of src/
1 parent d986155 commit d6580a5

35 files changed

+275
-197
lines changed

eslint_src.json

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,6 @@
2222
"no-unreachable": 1,
2323
"no-unexpected-multiline": 2,
2424
"no-sparse-arrays": 2,
25-
"complexity": [
26-
1,
27-
18
28-
],
2925
"dot-location": [
3026
2,
3127
"property"

fluent-bundle/makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ test: esm/.compiled
2323
--require esm \
2424
test/**/*_test.js
2525

26+
.PHONY: build
2627
build: index.js compat.js
2728

2829
index.js: esm/.compiled

fluent-dedent/makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ test: esm/.compiled
2323
--require esm \
2424
test/**/*_test.js
2525

26+
.PHONY: build
2627
build: index.js compat.js
2728

2829
index.js: esm/.compiled

fluent-dom/makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ test:
1111
--require ./test/index \
1212
test/**/*_test.js
1313

14+
.PHONY: build
1415
build: index.js compat.js
1516

1617
index.js: $(SOURCES)

fluent-gecko/makefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ test:
1212
--require esm \
1313
test/**/*_test.js
1414

15+
.PHONY: build
1516
build: Fluent.jsm FluentSyntax.jsm Localization.jsm DOMLocalization.jsm l10n.js fluent-react.js
1617

1718
Fluent.jsm: $(SOURCES)
@@ -53,6 +54,7 @@ l10n.js: $(SOURCES)
5354
@echo -e " $(OK) $@ built"
5455

5556
fluent-react.js: $(SOURCES)
57+
$(MAKE) -sC ../fluent-react compile
5658
@rollup $(CURDIR)/src/fluent-react.js \
5759
--config ./vendor_config.js \
5860
--output.intro "/* $(call version,fluent-react) */" \

fluent-gecko/src/fluent-react.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
export * from "../../fluent-react/src/index.js";
1+
export * from "../../fluent-react/esm/index.js";

fluent-langneg/makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ test:
99
--require esm \
1010
test/**/*_test.js
1111

12+
.PHONY: build
1213
build: index.js compat.js
1314

1415
index.js: $(SOURCES)

fluent-react/.esdoc.json

Lines changed: 0 additions & 19 deletions
This file was deleted.

fluent-react/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1+
esm/
12
/index.js
23
/compat.js

fluent-react/.npmignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
.nyc_output
22
coverage
3-
docs
3+
esm/.compiled
44
examples
5+
src
56
test
67
makefile
78
babel.config.js
89
compat_config.js
10+
tsconfig.json

fluent-react/makefile

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,60 @@
11
PACKAGE := @fluent/react
22
GLOBAL := FluentReact
3-
DEPS := @fluent/sequence:FluentSequence,cached-iterable:CachedIterable,react:React,prop-types:PropTypes
43

54
include ../common.mk
65

7-
build: index.js compat.js
6+
lint:
7+
@eslint --config $(ROOT)/eslint_ts.json --max-warnings 0 src/*.ts
8+
@eslint --config $(ROOT)/eslint_test.json --max-warnings 0 test/
9+
@echo -e " $(OK) lint"
10+
11+
.PHONY: compile
12+
compile: esm/.compiled
13+
14+
esm/.compiled: $(SOURCES)
15+
@tsc
16+
@touch $@
17+
@echo -e " $(OK) esm/ compiled"
818

9-
test:
19+
.PHONY: test
20+
test: esm/.compiled
1021
@jest --collect-coverage
1122

12-
index.js: $(SOURCES)
13-
@rollup $(CURDIR)/src/index.js \
23+
.PHONY: build
24+
build: index.js compat.js
25+
26+
index.js: esm/.compiled
27+
@rollup $(CURDIR)/esm/index.js \
1428
--config $(ROOT)/bundle_config.js \
1529
--banner "/* $(PACKAGE)@$(VERSION) */" \
1630
--amd.id $(PACKAGE) \
1731
--name $(GLOBAL) \
18-
--globals $(DEPS) \
32+
--globals @fluent/sequence:FluentSequence,cached-iterable:CachedIterable,react:React,prop-types:PropTypes \
1933
--output.file $@
2034
@echo -e " $(OK) $@ built"
2135

22-
compat.js: $(SOURCES)
23-
@rollup $(CURDIR)/src/index.js \
36+
compat.js: esm/.compiled
37+
@rollup $(CURDIR)/esm/index.js \
2438
--config $(CURDIR)/compat_config.js \
2539
--banner "/* $(PACKAGE)@$(VERSION) */" \
2640
--amd.id $(PACKAGE) \
2741
--name $(GLOBAL) \
28-
--globals @fluent/sequence/compat:FluentSequence,cached-iterable/compat:CachedIterable,$(DEPS) \
42+
--globals @fluent/sequence/compat:FluentSequence,cached-iterable/compat:CachedIterable,react:React,prop-types:PropTypes \
2943
--output.file $@
3044
@echo -e " $(OK) $@ built"
3145

32-
lint: _lint
33-
html: _html
34-
clean: _clean
46+
html:
47+
@typedoc src \
48+
--out ../html/react \
49+
--mode file \
50+
--excludeNotExported \
51+
--excludePrivate \
52+
--logger none \
53+
--hideGenerator
54+
@echo -e " $(OK) html built"
55+
56+
clean:
57+
@rm -f esm/*.js esm/*.d.ts esm/.compiled
58+
@rm -f index.js compat.js
59+
@rm -rf .nyc_output coverage
60+
@echo -e " $(OK) clean"

fluent-react/package.json

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,10 @@
1515
"email": "[email protected]"
1616
}
1717
],
18-
"directories": {
19-
"lib": "./src"
20-
},
18+
"type": "commonjs",
2119
"main": "./index.js",
22-
"module": "./src/index.js",
20+
"module": "./esm/index.js",
21+
"types": "./esm/index.d.ts",
2322
"repository": {
2423
"type": "git",
2524
"url": "https://github.com/projectfluent/fluent.js.git"
@@ -47,7 +46,7 @@
4746
},
4847
"dependencies": {
4948
"cached-iterable": "^0.2.1",
50-
"@fluent/sequence": "0.4.0",
49+
"@fluent/sequence": "0.5.0",
5150
"prop-types": "^15.6.0"
5251
},
5352
"peerDependencies": {
@@ -58,6 +57,8 @@
5857
"@babel/preset-env": "^7.5.5",
5958
"@babel/preset-react": "7.0.0",
6059
"@fluent/bundle": "^0.15.0",
60+
"@types/react": "^16.9.22",
61+
"@types/react-dom": "^16.9.5",
6162
"babel-jest": "^24.8.0",
6263
"babel-plugin-transform-rename-import": "^2.2.0",
6364
"jest": "^24.8.0",

fluent-react/src/cached-iterable.d.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
declare module "cached-iterable" {
2+
export class CachedSyncIterable<T> implements Iterable<T> {
3+
static from<U>(iterable: Iterable<U>): CachedSyncIterable<U>;
4+
constructor(iterable: Iterable<T>);
5+
[Symbol.iterator](): Iterator<T>;
6+
touchNext(count: number): IteratorResult<T>;
7+
}
8+
9+
export class CachedAsyncIterable<T> implements AsyncIterable<T> {
10+
static from<U>(iterable: Iterable<U>): CachedAsyncIterable<U>;
11+
constructor(iterable: Iterable<T>);
12+
[Symbol.asyncIterator](): AsyncIterator<T>;
13+
touchNext(count: number): Promise<IteratorResult<T>>;
14+
}
15+
}

fluent-react/src/context.js

Lines changed: 0 additions & 4 deletions
This file was deleted.

fluent-react/src/context.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import { createContext } from "react";
2+
import { ReactLocalization } from "./localization";
3+
4+
export let FluentContext = createContext(new ReactLocalization([], null));

fluent-react/src/index.js renamed to fluent-react/src/index.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
* components for more information.
1818
*/
1919

20-
export { default as LocalizationProvider } from "./provider";
21-
export { default as withLocalization } from "./with_localization";
22-
export { default as Localized } from "./localized";
20+
export { MemoLocalizationProvider as LocalizationProvider } from "./provider";
21+
export { withLocalization, WithLocalizationProps } from "./with_localization";
22+
export { Localized, LocalizedProps } from "./localized";
23+
export { MarkupParser } from "./markup";

fluent-react/src/localization.js renamed to fluent-react/src/localization.ts

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
import { FluentBundle, FluentArgument } from "@fluent/bundle";
12
import { mapBundleSync } from "@fluent/sequence";
23
import { CachedSyncIterable } from "cached-iterable";
3-
import createParseMarkup from "./markup";
4+
import { createParseMarkup, MarkupParser } from "./markup";
45

56
/*
67
* `ReactLocalization` handles translation formatting and fallback.
@@ -13,23 +14,32 @@ import createParseMarkup from "./markup";
1314
* The `ReactLocalization` class instances are exposed to `Localized` elements
1415
* via the `LocalizationProvider` component.
1516
*/
16-
export default class ReactLocalization {
17-
constructor(bundles, parseMarkup = createParseMarkup()) {
17+
export class ReactLocalization {
18+
public bundles: Iterable<FluentBundle>;
19+
public parseMarkup: MarkupParser | null;
20+
21+
constructor(
22+
bundles: Iterable<FluentBundle>,
23+
parseMarkup: MarkupParser | null = createParseMarkup()
24+
) {
1825
this.bundles = CachedSyncIterable.from(bundles);
1926
this.parseMarkup = parseMarkup;
2027
}
2128

22-
getBundle(id) {
29+
getBundle(id: string): FluentBundle | null {
2330
return mapBundleSync(this.bundles, id);
2431
}
2532

26-
getString(id, args, fallback) {
33+
getString(
34+
id: string,
35+
args?: Record<string, FluentArgument> | null,
36+
fallback?: string
37+
): string {
2738
const bundle = this.getBundle(id);
28-
2939
if (bundle) {
3040
const msg = bundle.getMessage(id);
3141
if (msg && msg.value) {
32-
let errors = [];
42+
let errors: Array<Error> = [];
3343
let value = bundle.formatPattern(msg.value, args, errors);
3444
for (let error of errors) {
3545
this.reportError(error);
@@ -43,7 +53,7 @@ export default class ReactLocalization {
4353

4454
// XXX Control this via a prop passed to the LocalizationProvider.
4555
// See https://github.com/projectfluent/fluent.js/issues/411.
46-
reportError(error) {
56+
reportError(error: Error): void {
4757
/* global console */
4858
// eslint-disable-next-line no-console
4959
console.warn(`[@fluent/react] ${error.name}: ${error.message}`);

0 commit comments

Comments
 (0)