templates: Convert module to TypeScript.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
This commit is contained in:
Anders Kaseorg
2024-11-27 13:16:39 -08:00
committed by Tim Abbott
parent 19f5ea8832
commit cf7e420847
6 changed files with 48 additions and 25 deletions

View File

@@ -9,7 +9,7 @@ import "jquery-validation";
// Import app JS // Import app JS
import "../setup.ts"; import "../setup.ts";
import "../reload.ts"; import "../reload.ts";
import "../templates.js"; import "../templates.ts";
import "../zulip_test.ts"; import "../zulip_test.ts";
// Import styles // Import styles

View File

@@ -20,7 +20,7 @@ Handlebars.registerHelper({
eq(a, b) { eq(a, b) {
return a === b; return a === b;
}, },
and(...args) { and(...args: unknown[]) {
args.pop(); // Handlebars options args.pop(); // Handlebars options
if (args.length === 0) { if (args.length === 0) {
return true; return true;
@@ -33,7 +33,7 @@ Handlebars.registerHelper({
} }
return last; return last;
}, },
or(...args) { or(...args: unknown[]) {
args.pop(); // Handlebars options args.pop(); // Handlebars options
if (args.length === 0) { if (args.length === 0) {
return false; return false;
@@ -51,7 +51,9 @@ Handlebars.registerHelper({
}, },
}); });
Handlebars.registerHelper("t", function (message) { type Context = Record<string, unknown>;
Handlebars.registerHelper("t", function (this: Context, message: string) {
// Marks a string for translation. // Marks a string for translation.
// Example usage 1: // Example usage 1:
// {{t "some English text"}} // {{t "some English text"}}
@@ -68,10 +70,19 @@ Handlebars.registerHelper("t", function (message) {
.map((s) => s.trim()) .map((s) => s.trim())
.join(" "); .join(" ");
const descriptor = {id: message, defaultMessage: message}; const descriptor = {id: message, defaultMessage: message};
return intl.formatMessage(descriptor, this); return intl.formatMessage(
descriptor,
Object.fromEntries(
Object.entries(this).flatMap(([key, value]) =>
typeof value === "string" || typeof value === "number" || value instanceof Date
? [[key, value]]
: [],
),
),
);
}); });
Handlebars.registerHelper("tr", function (options) { Handlebars.registerHelper("tr", function (this: Context, options: Handlebars.HelperOptions) {
// Marks a block for translation. // Marks a block for translation.
// Example usage 1: // Example usage 1:
// {{#tr}} // {{#tr}}
@@ -92,19 +103,29 @@ Handlebars.registerHelper("tr", function (options) {
.map((s) => s.trim()) .map((s) => s.trim())
.join(" "); .join(" ");
const descriptor = {id: message, defaultMessage: message}; const descriptor = {id: message, defaultMessage: message};
const partials: Partial<Record<string, (context: Context, options: unknown) => string>> =
"partials" in options.fn &&
typeof options.fn.partials === "object" &&
options.fn.partials !== null
? options.fn.partials
: {};
const result = intl.formatMessage(descriptor, { const result = intl.formatMessage(descriptor, {
...default_html_elements, ...default_html_elements,
...Object.fromEntries( ...Object.fromEntries(
Object.entries(options.fn.partials ?? {}).map(([name, value]) => [ Object.entries(partials).map(([name, value]) => [
name, name,
(s) => value(this, {data: {"partial-block": () => s.join("")}}), (content_html: string[]) =>
value!(this, {data: {"partial-block": () => content_html.join("")}}),
]), ]),
), ),
...Object.fromEntries( ...Object.fromEntries(
Object.entries(this).map(([key, value]) => [ Object.entries(this).flatMap(([key, value]): [string, string | number | Date][] =>
key, typeof value === "string"
Handlebars.Utils.escapeExpression(value), ? [[key, Handlebars.Utils.escapeExpression(value)]]
]), : typeof value === "number" || value instanceof Date
? [[key, value]]
: [],
),
), ),
}); });
return new Handlebars.SafeString(result); return new Handlebars.SafeString(result);
@@ -112,13 +133,14 @@ Handlebars.registerHelper("tr", function (options) {
Handlebars.registerHelper( Handlebars.registerHelper(
"rendered_markdown", "rendered_markdown",
(content) => new Handlebars.SafeString(postprocess_content(content)), (content: string) => new Handlebars.SafeString(postprocess_content(content)),
); );
Handlebars.registerHelper("numberFormat", (number) => number.toLocaleString()); Handlebars.registerHelper("numberFormat", (number: number) => number.toLocaleString());
Handlebars.registerHelper("tooltip_hotkey_hints", (...hotkeys) => { Handlebars.registerHelper("tooltip_hotkey_hints", (...args) => {
hotkeys.pop(); // Handlebars options args.pop(); // Handlebars options
const hotkeys: string[] = args;
let hotkey_hints = ""; let hotkey_hints = "";
common.adjust_mac_hotkey_hints(hotkeys); common.adjust_mac_hotkey_hints(hotkeys);
for (const hotkey of hotkeys) { for (const hotkey of hotkeys) {
@@ -128,8 +150,9 @@ Handlebars.registerHelper("tooltip_hotkey_hints", (...hotkeys) => {
return new Handlebars.SafeString(result); return new Handlebars.SafeString(result);
}); });
Handlebars.registerHelper("popover_hotkey_hints", (...hotkeys) => { Handlebars.registerHelper("popover_hotkey_hints", (...args) => {
hotkeys.pop(); // Handlebars options args.pop(); // Handlebars options
const hotkeys: string[] = args;
let hotkey_hints = ""; let hotkey_hints = "";
common.adjust_mac_hotkey_hints(hotkeys); common.adjust_mac_hotkey_hints(hotkeys);
const shift_hotkey_exists = common.adjust_shift_hotkey(hotkeys); const shift_hotkey_exists = common.adjust_shift_hotkey(hotkeys);
@@ -138,7 +161,7 @@ Handlebars.registerHelper("popover_hotkey_hints", (...hotkeys) => {
} }
if (shift_hotkey_exists) { if (shift_hotkey_exists) {
return new Handlebars.SafeString( return new Handlebars.SafeString(
`<span class="popover-menu-hotkey-hints popover-contains-shift-hotkey" data-hotkey-hints="${hotkeys}">${hotkey_hints}</span>`, `<span class="popover-menu-hotkey-hints popover-contains-shift-hotkey" data-hotkey-hints="${hotkeys.join(",")}">${hotkey_hints}</span>`,
); );
} }
return new Handlebars.SafeString( return new Handlebars.SafeString(

View File

@@ -22,12 +22,12 @@ page_params.translation_data = {
// Re-register Zulip extensions so extensions registered previously with // Re-register Zulip extensions so extensions registered previously with
// mocked i18n.ts do not interfere with following tests. // mocked i18n.ts do not interfere with following tests.
require("../src/templates.js"); require("../src/templates.ts");
// All of our other tests stub out i18n activity; // All of our other tests stub out i18n activity;
// here we do a quick sanity check on the engine itself. // here we do a quick sanity check on the engine itself.
// `i18n.ts` initializes FormatJS and is imported by // `i18n.ts` initializes FormatJS and is imported by
// `templates.js`. // `templates.ts`.
unmock_module("../src/i18n"); unmock_module("../src/i18n");
const {$t, $t_html, get_language_name, get_language_list_columns, initialize} = zrequire("i18n"); const {$t, $t_html, get_language_name, get_language_list_columns, initialize} = zrequire("i18n");
@@ -102,7 +102,7 @@ run_test("{{#tr}} to tag for translation", ({mock_template}) => {
// We're actually testing `notification_settings.hbs` here which // We're actually testing `notification_settings.hbs` here which
// is imported as a partial in the file below. We want to test // is imported as a partial in the file below. We want to test
// the partial handling logic in `templates.js`, that's why we // the partial handling logic in `templates.ts`, that's why we
// test the file below instead of directly testing // test the file below instead of directly testing
// `notification_settings.hbs`. // `notification_settings.hbs`.
mock_template("settings/user_notification_settings.hbs", true, (data, html) => { mock_template("settings/user_notification_settings.hbs", true, (data, html) => {

View File

@@ -83,7 +83,7 @@ handlebars.hook_require();
const noop = function () {}; const noop = function () {};
require("../../src/templates.js"); // register Zulip extensions require("../../src/templates.ts"); // register Zulip extensions
async function run_one_module(file) { async function run_one_module(file) {
zjquery.clear_initialize_function(); zjquery.clear_initialize_function();

View File

@@ -209,7 +209,7 @@ exports.zrequire = function (short_fn) {
short_fn, short_fn,
"templates", "templates",
` `
There is no need to zrequire templates.js. There is no need to zrequire templates.ts.
The test runner automatically registers the The test runner automatically registers the
Handlebars extensions. Handlebars extensions.

View File

@@ -167,7 +167,7 @@ const config = (
ignoreHelpers: true, ignoreHelpers: true,
// Tell webpack not to explicitly require these. // Tell webpack not to explicitly require these.
knownHelpers: [ knownHelpers: [
// The ones below are defined in web/src/templates.js // The ones below are defined in web/src/templates.ts
"eq", "eq",
"and", "and",
"or", "or",