Skip to content
This repository has been archived by the owner on Nov 18, 2024. It is now read-only.

Commit

Permalink
feat(fbt): Add ability to provide your own ViewerContext dynamically
Browse files Browse the repository at this point in the history
Reviewed By: jrwats

Differential Revision: D21593501

fbshipit-source-id: f58f54d4bf6e1ffcf217d51b6e6cb1ac1d2b80c7
  • Loading branch information
kayhadrin authored and facebook-github-bot committed May 22, 2020
1 parent f58d7c2 commit 8bb2b93
Show file tree
Hide file tree
Showing 12 changed files with 142 additions and 90 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ We haven't had the best track record of code/feature changes before this date, b
<summary>
Unreleased changes that have landed in master. Click to see more.
</summary>
- [major feat] Add ability to provide your own ViewerContext dynamically
- [fix] Render optional catch binding syntax to ES5 to fix [IE11 bug](https://github.com/facebook/fbt/pull/139)
- [feat] Convert `fbt.isFbtInstance()` to a predicate function for Flow
- [fix] Avoid generating unnecessary empty strings in fbt result contents
Expand Down
23 changes: 15 additions & 8 deletions demo-app/src/example/Example.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,22 @@

import './css/Example.css';
import classNames from 'classnames';
import fbt, {GenderConst, IntlVariations, init} from 'fbt';
import * as React from 'react';

const ExampleEnum = require('./Example$FbtEnum');

import fbt, {GenderConst, IntlVariations, IntlViewerContext, init} from 'fbt';
init({translations: require('../translatedFbts.json')});
const viewerContext = {
GENDER: IntlVariations.GENDER_UNKNOWN,
locale: 'en_US',
};

init({
translations: require('../translatedFbts.json'),
hooks: {
getViewerContext: () => viewerContext,
},
});

const LOCALES = Object.freeze({
en_US: Object.freeze({
Expand Down Expand Up @@ -85,7 +95,7 @@ export default class Example extends React.Component<Props, State> {
};

setLocale(locale: Locale) {
IntlViewerContext.locale = locale;
viewerContext.locale = locale;
this.setState({locale});
const html = document.getElementsByTagName('html')[0];
if (html != null) {
Expand Down Expand Up @@ -124,7 +134,7 @@ export default class Example extends React.Component<Props, State> {
className="neatoSelect"
onChange={(event: SyntheticUIEvent<>) => {
const vcGender = parseInt(event.target.value, 10);
IntlViewerContext.GENDER = vcGender;
viewerContext.GENDER = vcGender;
this.forceUpdate();
}}>
<option value={IntlVariations.GENDER_UNKNOWN}>
Expand Down Expand Up @@ -307,10 +317,7 @@ export default class Example extends React.Component<Props, State> {
className="bottom"
type="submit"
onClick={e => {
window.open(
'https://github.com/facebook/fbt',
'_blank',
);
window.open('https://github.com/facebook/fbt', '_blank');
}}>
{fbt('Try it out!', 'Sign up button')}
</button>
Expand Down
2 changes: 0 additions & 2 deletions runtime/nonfb/FbtPublic.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ const FbtResult = require('FbtResult');
const FbtTranslations = require('FbtTranslations');
const GenderConst = require('GenderConst');
const IntlVariations = require('IntlVariations');
const IntlViewerContext = require('IntlViewerContext');

const fbt = require('fbt');
const init = require('fbtInit');
Expand All @@ -27,6 +26,5 @@ const FbtPublic = {
GenderConst,
init,
IntlVariations,
IntlViewerContext,
};
module.exports = FbtPublic;
12 changes: 6 additions & 6 deletions runtime/nonfb/FbtTranslations.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

import type {FbtRuntimeCallInput, FbtTranslatedInput} from 'FbtHooks';

const IntlViewerContext = require('IntlViewerContext');
const FbtHooks = require('FbtHooks');

let translatedFbts = null;

Expand All @@ -29,20 +29,20 @@ const FbtTranslations = {
getTranslatedInput(input: FbtRuntimeCallInput): ?FbtTranslatedInput {
const {args, options} = input;
const hashKey = options?.hk;
const table =
translatedFbts != null && translatedFbts[IntlViewerContext.locale];
const {locale} = FbtHooks.getViewerContext();
const table = translatedFbts?.[locale];
if (__DEV__) {
if (!table && IntlViewerContext.locale !== DEFAULT_SRC_LOCALE) {
if (!table && locale !== DEFAULT_SRC_LOCALE) {
console.warn('Translations have not been provided');
}
}

if (!table || hashKey == null || table[hashKey] == null) {
if (hashKey == null || table?.[hashKey] == null) {
return null;
}
return {
table: table[hashKey],
args: args,
args,
};
},

Expand Down
6 changes: 6 additions & 0 deletions runtime/nonfb/fbtInit.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@
* @flow strict-local
* @format
*/

import type {FbtHookRegistrations} from 'FbtHooks';
import type {TranslationDict} from 'FbtTranslations';

const FbtHooks = require('FbtHooks');
const FbtResult = require('FbtResult');
const FbtTranslations = require('FbtTranslations');
const IntlViewerContext = require('IntlViewerContext'); // default VC

const getFbsResult = require('getFbsResult');

Expand All @@ -35,6 +37,10 @@ function fbtInit(input: FbtInitInput): void {
if (hooks.getTranslatedInput == null) {
hooks.getTranslatedInput = FbtTranslations.getTranslatedInput;
}
if (hooks.getViewerContext == null) {
hooks.getViewerContext = () => IntlViewerContext;
}

FbtHooks.register(hooks);
}

Expand Down
5 changes: 5 additions & 0 deletions runtime/nonfb/mocks/IntlViewerContext.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,13 @@
* @flow strict
*/

// flowlint ambiguous-object-type:error

const IntlVariations = require('IntlVariations');

// Keep this in sync with IntlViewerContext.js.flow
// It's almost the same except that the `locale` field is optional on www
// and required in the OSS version
const IntlViewerContext = {
GENDER: IntlVariations.GENDER_UNKNOWN,
locale: 'en_US',
Expand Down
13 changes: 10 additions & 3 deletions runtime/shared/FbtHooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@
* @format
*/

/* eslint-disable fb-www/flow-exact-by-default-object-types */

import type {FbtTableKey, PatternHash, PatternString} from 'FbtTable';
import type {FbtTableArg} from 'FbtTableAccessor';
import typeof IntlViewerContext from 'IntlViewerContext';

// TODO T61557741: Move these types to fbt.js when it's flow strict
export type FbtResolvedPayload = {|
Expand Down Expand Up @@ -72,15 +75,15 @@ export type FbtRuntimeCallInput = {
};

// TODO: T61015960 - getFb[st]Result should return types that are locked down
export type FbtHookRegistrations = $Shape<{
export type FbtHookRegistrations = $Shape<{|
errorListener: (context: FbtErrorContext) => IFbtErrorListener,
getFbsResult: (input: FbtResolvedPayload) => mixed,
getFbtResult: (input: FbtResolvedPayload) => mixed,
getTranslatedInput: (input: FbtRuntimeCallInput) => ?FbtTranslatedInput,
getViewerContext: () => IntlViewerContext,
logImpression: (hash: string) => void,
onTranslationOverride: (hash: string) => void,
...
}>;
|}>;

const _registrations: FbtHookRegistrations = {};
const FbtHooks = {
Expand Down Expand Up @@ -110,6 +113,10 @@ const FbtHooks = {
return _registrations.getTranslatedInput?.(input) ?? input;
},

getViewerContext(): IntlViewerContext {
return _registrations.getViewerContext();
},

register(registrations: FbtHookRegistrations): void {
Object.assign(_registrations, registrations);
},
Expand Down
63 changes: 32 additions & 31 deletions runtime/shared/IntlPunctuation.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@
* Note: Please keep this in sync with www/html/js/mobile/lib/intl-core.js.
*/

const FbtHooks = require('FbtHooks');
const IntlPhonologicalRewrites = require('IntlPhonologicalRewrites');
const IntlViewerContext = require('IntlViewerContext');

/**
* Regular expression snippet containing all the characters that we
Expand Down Expand Up @@ -77,39 +77,40 @@ const ENDS_IN_PUNCT_REGEXP = new RegExp(
']*$',
);

let _rules = [];
let _lastLocale = null;
let _rewrites = IntlPhonologicalRewrites.get(IntlViewerContext.locale);
type Rules = $ReadOnlyArray<[RegExp, (string => string) | string]>;

function _getRules(): Array<[RegExp, (string => string) | string]> {
if (
IntlViewerContext.locale != null &&
IntlViewerContext.locale != '' &&
IntlViewerContext.locale !== _lastLocale
) {
_rules = [];
_lastLocale = IntlViewerContext.locale;
_rewrites = IntlPhonologicalRewrites.get(_lastLocale);
const rulesPerLocale: {[locale: string]: ?Rules, ...} = {};

function _getMemoizedRules(localeArg: ?string): Rules {
const locale = localeArg ?? '';
let rules = rulesPerLocale[locale];
if (rules == null) {
rules = rulesPerLocale[locale] = _getRules(localeArg);
}
if (!_rules.length) {
// Process the patterns and replacements by applying metaclasses.
for (let pattern in _rewrites.patterns) {
let replacement = _rewrites.patterns[pattern];
// "Metaclasses" are shorthand for larger character classes. For example,
// _C may refer to consonants and _V to vowels for a locale.
for (const metaclass in _rewrites.meta) {
const metaclassRegexp = new RegExp(metaclass.slice(1, -1), 'g');
const characterClass = _rewrites.meta[metaclass];
pattern = pattern.replace(metaclassRegexp, characterClass);
replacement = replacement.replace(metaclassRegexp, characterClass);
}
if (replacement === 'javascript') {
replacement = match => match.slice(1).toLowerCase();
}
_rules.push([new RegExp(pattern.slice(1, -1), 'g'), replacement]);
return rules;
}

function _getRules(locale: ?string): Rules {
const rules = [];
const rewrites = IntlPhonologicalRewrites.get(locale);

// Process the patterns and replacements by applying metaclasses.
for (let pattern in rewrites.patterns) {
let replacement = rewrites.patterns[pattern];
// "Metaclasses" are shorthand for larger character classes. For example,
// _C may refer to consonants and _V to vowels for a locale.
for (const metaclass in rewrites.meta) {
const metaclassRegexp = new RegExp(metaclass.slice(1, -1), 'g');
const characterClass = rewrites.meta[metaclass];
pattern = pattern.replace(metaclassRegexp, characterClass);
replacement = replacement.replace(metaclassRegexp, characterClass);
}
if (replacement === 'javascript') {
replacement = match => match.slice(1).toLowerCase();
}
rules.push([new RegExp(pattern.slice(1, -1), 'g'), replacement]);
}
return _rules;
return rules;
}

/**
Expand All @@ -125,7 +126,7 @@ function _getRules(): Array<[RegExp, (string => string) | string]> {
* Returns: String with phonological rules applied (e.g., "Ozguri...")
*/
function applyPhonologicalRules(text: string): string {
const rules = _getRules();
const rules = _getMemoizedRules(FbtHooks.getViewerContext().locale);
let result = text;

for (let i = 0; i < rules.length; i++) {
Expand Down
9 changes: 5 additions & 4 deletions runtime/shared/IntlVariationResolverImpl.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
* @format
* @flow strict-local
*/

const FbtHooks = require('FbtHooks');
const IntlNumberType = require('IntlNumberType');
const IntlVariations = require('IntlVariations');
const IntlViewerContext = require('IntlViewerContext');

const invariant = require('invariant');

Expand All @@ -21,9 +22,9 @@ const IntlVariationResolverImpl = {
getNumberVariations(
number: number,
): Array<$FlowFixMe | $TEMPORARY$string<'*'> | $TEMPORARY$string<'_1'>> {
const numType = IntlNumberType.get(IntlViewerContext.locale).getVariation(
number,
);
const numType = IntlNumberType.get(
FbtHooks.getViewerContext().locale,
).getVariation(number);
invariant(
// eslint-disable-next-line no-bitwise
numType & IntlVariations.BITMASK_NUMBER,
Expand Down
Loading

0 comments on commit 8bb2b93

Please sign in to comment.