Skip to content

Commit

Permalink
Internationalize commondatabuilder. (#1383)
Browse files Browse the repository at this point in the history
This internationalizes commondatabuilder, allowing us to easily translate strings in Django choices by supplying an `internationalizeLabels: true` option for entries in `common-data/config.ts`.  It sets this flag for the US State choices and translates New Mexico to Spanish to ensure that it works as expected.
  • Loading branch information
toolness authored May 6, 2020
1 parent 2f61377 commit a7a4a0f
Show file tree
Hide file tree
Showing 9 changed files with 566 additions and 67 deletions.
1 change: 1 addition & 0 deletions common-data/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ const config: DjangoChoicesTypescriptConfig = {
jsonFilename: "us-state-choices.json",
typeName: "USStateChoice",
exportLabels: true,
internationalizeLabels: true,
filterOut: ["AS", "GU", "MP", "VI"],
},
],
Expand Down
107 changes: 55 additions & 52 deletions common-data/us-state-choices.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
// This file was auto-generated by commondatabuilder.
// Please don't edit it.

import { t } from "@lingui/macro";
import { li18n } from '../frontend/lib/i18n-lingui';

export type USStateChoice = "AK"|"AL"|"AZ"|"AR"|"CA"|"CO"|"CT"|"DE"|"DC"|"FL"|"GA"|"HI"|"ID"|"IL"|"IN"|"IA"|"KS"|"KY"|"LA"|"ME"|"MD"|"MA"|"MI"|"MN"|"MS"|"MO"|"MT"|"NE"|"NV"|"NH"|"NJ"|"NM"|"NY"|"NC"|"ND"|"OH"|"OK"|"OR"|"PA"|"PR"|"RI"|"SC"|"SD"|"TN"|"TX"|"UT"|"VT"|"VA"|"WA"|"WV"|"WI"|"WY";

export const USStateChoices: USStateChoice[] = [
Expand Down Expand Up @@ -70,57 +73,57 @@ export type USStateChoiceLabels = {

export function getUSStateChoiceLabels(): USStateChoiceLabels {
return {
AK: "Alaska",
AL: "Alabama",
AZ: "Arizona",
AR: "Arkansas",
CA: "California",
CO: "Colorado",
CT: "Connecticut",
DE: "Delaware",
DC: "District of Columbia",
FL: "Florida",
GA: "Georgia",
HI: "Hawaii",
ID: "Idaho",
IL: "Illinois",
IN: "Indiana",
IA: "Iowa",
KS: "Kansas",
KY: "Kentucky",
LA: "Louisiana",
ME: "Maine",
MD: "Maryland",
MA: "Massachusetts",
MI: "Michigan",
MN: "Minnesota",
MS: "Mississippi",
MO: "Missouri",
MT: "Montana",
NE: "Nebraska",
NV: "Nevada",
NH: "New Hampshire",
NJ: "New Jersey",
NM: "New Mexico",
NY: "New York",
NC: "North Carolina",
ND: "North Dakota",
OH: "Ohio",
OK: "Oklahoma",
OR: "Oregon",
PA: "Pennsylvania",
PR: "Puerto Rico",
RI: "Rhode Island",
SC: "South Carolina",
SD: "South Dakota",
TN: "Tennessee",
TX: "Texas",
UT: "Utah",
VT: "Vermont",
VA: "Virginia",
WA: "Washington",
WV: "West Virginia",
WI: "Wisconsin",
WY: "Wyoming",
AK: li18n._(t`Alaska`),
AL: li18n._(t`Alabama`),
AZ: li18n._(t`Arizona`),
AR: li18n._(t`Arkansas`),
CA: li18n._(t`California`),
CO: li18n._(t`Colorado`),
CT: li18n._(t`Connecticut`),
DE: li18n._(t`Delaware`),
DC: li18n._(t`District of Columbia`),
FL: li18n._(t`Florida`),
GA: li18n._(t`Georgia`),
HI: li18n._(t`Hawaii`),
ID: li18n._(t`Idaho`),
IL: li18n._(t`Illinois`),
IN: li18n._(t`Indiana`),
IA: li18n._(t`Iowa`),
KS: li18n._(t`Kansas`),
KY: li18n._(t`Kentucky`),
LA: li18n._(t`Louisiana`),
ME: li18n._(t`Maine`),
MD: li18n._(t`Maryland`),
MA: li18n._(t`Massachusetts`),
MI: li18n._(t`Michigan`),
MN: li18n._(t`Minnesota`),
MS: li18n._(t`Mississippi`),
MO: li18n._(t`Missouri`),
MT: li18n._(t`Montana`),
NE: li18n._(t`Nebraska`),
NV: li18n._(t`Nevada`),
NH: li18n._(t`New Hampshire`),
NJ: li18n._(t`New Jersey`),
NM: li18n._(t`New Mexico`),
NY: li18n._(t`New York`),
NC: li18n._(t`North Carolina`),
ND: li18n._(t`North Dakota`),
OH: li18n._(t`Ohio`),
OK: li18n._(t`Oklahoma`),
OR: li18n._(t`Oregon`),
PA: li18n._(t`Pennsylvania`),
PR: li18n._(t`Puerto Rico`),
RI: li18n._(t`Rhode Island`),
SC: li18n._(t`South Carolina`),
SD: li18n._(t`South Dakota`),
TN: li18n._(t`Tennessee`),
TX: li18n._(t`Texas`),
UT: li18n._(t`Utah`),
VT: li18n._(t`Vermont`),
VA: li18n._(t`Virginia`),
WA: li18n._(t`Washington`),
WV: li18n._(t`West Virginia`),
WI: li18n._(t`Wisconsin`),
WY: li18n._(t`Wyoming`),
};
}
38 changes: 31 additions & 7 deletions frontend/commondatabuilder/commondatabuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ export type DjangoChoicesTypescriptFileConfig = {
/** Whether to export the labels in the JSON file to TS. */
exportLabels: boolean;

/** Whether to internationalize the labels in the JSON file. */
internationalizeLabels?: boolean;

/**
* Filter out the given values from either the given list of choices, or anything
* that matches the given regular expression.
Expand Down Expand Up @@ -116,6 +119,7 @@ export function createDjangoChoicesTypescriptFiles(
}
const ts = createDjangoChoicesTypescript(choices, fileConfig.typeName, {
exportLabels: fileConfig.exportLabels,
internationalizeLabels: fileConfig.internationalizeLabels,
});
const tsFilename = replaceExt(fileConfig.jsonFilename, ".ts");
const tsPath = path.join(config.rootDir, tsFilename);
Expand All @@ -127,15 +131,23 @@ export function createDjangoChoicesTypescriptFiles(
});
}

function createInternationalizedStringLiteral(value: string): string {
const backtickedValue = "`" + JSON.stringify(value).slice(1, -1) + "`";
return `li18n._(t${backtickedValue})`;
}

/**
* Return a list of TypeScript lines that create a function
* which returns a mapping from choice values to their labels.
*
* Note that this is a function rather than a constant because
* we want to be able to add code that localizes the
* labels at runtime if needed.
* we need to localize the labels at runtime if needed.
*/
function createLabelExporter(choices: DjangoChoices, name: string): string[] {
function createLabelExporter(
choices: DjangoChoices,
name: string,
options: CreateOptions
): string[] {
const lines = [];
lines.push(
`export type ${name}Labels = {`,
Expand All @@ -147,17 +159,24 @@ function createLabelExporter(choices: DjangoChoices, name: string): string[] {
` return {`
);
for (let [name, label] of choices) {
lines.push(` ${name}: ${JSON.stringify(label)},`);
const value = options.internationalizeLabels
? createInternationalizedStringLiteral(label)
: JSON.stringify(label);
lines.push(` ${name}: ${value},`);
}
lines.push(" };", "}\n");
return lines;
}

type CreateOptions = {
exportLabels: boolean;
internationalizeLabels: boolean;
};

const defaultOptions: CreateOptions = { exportLabels: true };
const defaultOptions: CreateOptions = {
exportLabels: true,
internationalizeLabels: false,
};

/**
* Return a list of TypeScript lines that define a type
Expand All @@ -168,11 +187,16 @@ export function createDjangoChoicesTypescript(
name: string,
options: Partial<CreateOptions> = {}
): string {
const { exportLabels } = Object.assign({}, defaultOptions, options);
const finalOptions = Object.assign({}, defaultOptions, options);
const { exportLabels, internationalizeLabels } = finalOptions;
const lines = [
`// This file was auto-generated by commondatabuilder.`,
`// Please don't edit it.\n`,
];
if (exportLabels && internationalizeLabels) {
lines.push(`import { t } from "@lingui/macro";`);
lines.push(`import { li18n } from '../frontend/lib/i18n-lingui';\n`);
}
const choiceKeys = choices.map((choice) => choice[0]);
const quotedChoiceKeys = choiceKeys.map((key) => JSON.stringify(key));
lines.push(`export type ${name} = ${quotedChoiceKeys.join("|")};\n`);
Expand All @@ -190,7 +214,7 @@ export function createDjangoChoicesTypescript(
`}\n`
);
if (exportLabels) {
lines.push(...createLabelExporter(choices, name));
lines.push(...createLabelExporter(choices, name, finalOptions));
}
return lines.join("\n");
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,34 @@ export function getBoroughChoiceLabels(): BoroughChoiceLabels {
}
"
`;
exports[`commondatabuilder only internationalizes labels if configured to 1`] = `
"// This file was auto-generated by commondatabuilder.
// Please don't edit it.
import { t } from \\"@lingui/macro\\";
import { li18n } from '../frontend/lib/i18n-lingui';
export type Foo = \\"THINGY\\";
export const Foos: Foo[] = [
\\"THINGY\\"
];
const FooSet: Set<String> = new Set(Foos);
export function isFoo(choice: string): choice is Foo {
return FooSet.has(choice);
}
export type FooLabels = {
[k in Foo]: string;
};
export function getFooLabels(): FooLabels {
return {
THINGY: li18n._(t\`I am thingy\`),
};
}
"
`;
18 changes: 18 additions & 0 deletions frontend/commondatabuilder/tests/commondatabuilder.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,24 @@ describe("commondatabuilder", () => {
})
).not.toMatch(/getFooLabels/);
});

it("only internationalizes labels if configured to", () => {
expect(
createDjangoChoicesTypescript([], "Foo", {
exportLabels: true,
})
).not.toMatch(/lingui/);
const ts = createDjangoChoicesTypescript(
[["THINGY", "I am thingy"]],
"Foo",
{
exportLabels: true,
internationalizeLabels: true,
}
);
expect(ts).toMatch(/lingui/);
expect(ts).toMatchSnapshot();
});
});

it("current common data JSON files are synced with TS files", () => {
Expand Down
16 changes: 11 additions & 5 deletions frontend/lib/tests/i18n-lingui.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,14 @@ import ReactTestingLibraryPal from "./rtl-pal";
import { LinguiI18n, li18n } from "../i18n-lingui";
import { Trans, t } from "@lingui/macro";
import { wait } from "@testing-library/react";
import { getUSStateChoiceLabels } from "../../../common-data/us-state-choices";

describe("<LinguiI18n>", () => {
afterEach(ReactTestingLibraryPal.cleanup);

const helloWorldJSX = (
<LinguiI18n>
<Trans>Hello world</Trans>
</LinguiI18n>
);
const linguified = (el: JSX.Element) => <LinguiI18n>{el}</LinguiI18n>;

const helloWorldJSX = linguified(<Trans>Hello world</Trans>);

it("Works in English", async () => {
i18n.initialize("en");
Expand All @@ -29,4 +28,11 @@ describe("<LinguiI18n>", () => {
expect(li18n.language).toBe("es");
expect(li18n._(t`Hello world`)).toBe("Hola mundo");
});

it("localizes commondatabuilder choices", async () => {
i18n.initialize("es");
const NewMexico = () => <p>{getUSStateChoiceLabels()["NM"]}</p>;
const pal = new ReactTestingLibraryPal(linguified(<NewMexico />));
await wait(() => pal.rr.getByText("Nuevo México"));
});
});
2 changes: 1 addition & 1 deletion lingui.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ assert(presets && plugins);
module.exports = {
extractBabelOptions: { presets, plugins },
localeDir: "locales/",
srcPathDirs: ["frontend/lib/"],
srcPathDirs: ["frontend/lib/", "common-data/"],
format: "po",
sourceLocale: "en",
};
Loading

0 comments on commit a7a4a0f

Please sign in to comment.