diff --git a/web/src/bundles/app.ts b/web/src/bundles/app.ts index 1c90d1105c..98d566c093 100644 --- a/web/src/bundles/app.ts +++ b/web/src/bundles/app.ts @@ -9,7 +9,7 @@ import "jquery-validation"; // Import app JS import "../setup.ts"; import "../reload.ts"; -import "../templates.js"; +import "../templates.ts"; import "../zulip_test.ts"; // Import styles diff --git a/web/src/templates.js b/web/src/templates.ts similarity index 65% rename from web/src/templates.js rename to web/src/templates.ts index ef4c6553c6..e866d036d1 100644 --- a/web/src/templates.js +++ b/web/src/templates.ts @@ -20,7 +20,7 @@ Handlebars.registerHelper({ eq(a, b) { return a === b; }, - and(...args) { + and(...args: unknown[]) { args.pop(); // Handlebars options if (args.length === 0) { return true; @@ -33,7 +33,7 @@ Handlebars.registerHelper({ } return last; }, - or(...args) { + or(...args: unknown[]) { args.pop(); // Handlebars options if (args.length === 0) { return false; @@ -51,7 +51,9 @@ Handlebars.registerHelper({ }, }); -Handlebars.registerHelper("t", function (message) { +type Context = Record; + +Handlebars.registerHelper("t", function (this: Context, message: string) { // Marks a string for translation. // Example usage 1: // {{t "some English text"}} @@ -68,10 +70,19 @@ Handlebars.registerHelper("t", function (message) { .map((s) => s.trim()) .join(" "); 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. // Example usage 1: // {{#tr}} @@ -92,19 +103,29 @@ Handlebars.registerHelper("tr", function (options) { .map((s) => s.trim()) .join(" "); const descriptor = {id: message, defaultMessage: message}; + const partials: Partial string>> = + "partials" in options.fn && + typeof options.fn.partials === "object" && + options.fn.partials !== null + ? options.fn.partials + : {}; const result = intl.formatMessage(descriptor, { ...default_html_elements, ...Object.fromEntries( - Object.entries(options.fn.partials ?? {}).map(([name, value]) => [ + Object.entries(partials).map(([name, value]) => [ name, - (s) => value(this, {data: {"partial-block": () => s.join("")}}), + (content_html: string[]) => + value!(this, {data: {"partial-block": () => content_html.join("")}}), ]), ), ...Object.fromEntries( - Object.entries(this).map(([key, value]) => [ - key, - Handlebars.Utils.escapeExpression(value), - ]), + Object.entries(this).flatMap(([key, value]): [string, string | number | Date][] => + typeof value === "string" + ? [[key, Handlebars.Utils.escapeExpression(value)]] + : typeof value === "number" || value instanceof Date + ? [[key, value]] + : [], + ), ), }); return new Handlebars.SafeString(result); @@ -112,13 +133,14 @@ Handlebars.registerHelper("tr", function (options) { Handlebars.registerHelper( "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) => { - hotkeys.pop(); // Handlebars options +Handlebars.registerHelper("tooltip_hotkey_hints", (...args) => { + args.pop(); // Handlebars options + const hotkeys: string[] = args; let hotkey_hints = ""; common.adjust_mac_hotkey_hints(hotkeys); for (const hotkey of hotkeys) { @@ -128,8 +150,9 @@ Handlebars.registerHelper("tooltip_hotkey_hints", (...hotkeys) => { return new Handlebars.SafeString(result); }); -Handlebars.registerHelper("popover_hotkey_hints", (...hotkeys) => { - hotkeys.pop(); // Handlebars options +Handlebars.registerHelper("popover_hotkey_hints", (...args) => { + args.pop(); // Handlebars options + const hotkeys: string[] = args; let hotkey_hints = ""; common.adjust_mac_hotkey_hints(hotkeys); const shift_hotkey_exists = common.adjust_shift_hotkey(hotkeys); @@ -138,7 +161,7 @@ Handlebars.registerHelper("popover_hotkey_hints", (...hotkeys) => { } if (shift_hotkey_exists) { return new Handlebars.SafeString( - `${hotkey_hints}`, + `${hotkey_hints}`, ); } return new Handlebars.SafeString( diff --git a/web/tests/i18n.test.cjs b/web/tests/i18n.test.cjs index c06fc1c8ad..570c65d59f 100644 --- a/web/tests/i18n.test.cjs +++ b/web/tests/i18n.test.cjs @@ -22,12 +22,12 @@ page_params.translation_data = { // Re-register Zulip extensions so extensions registered previously with // 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; // here we do a quick sanity check on the engine itself. // `i18n.ts` initializes FormatJS and is imported by -// `templates.js`. +// `templates.ts`. unmock_module("../src/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 // 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 // `notification_settings.hbs`. mock_template("settings/user_notification_settings.hbs", true, (data, html) => { diff --git a/web/tests/lib/index.cjs b/web/tests/lib/index.cjs index 21728b569e..e6401a62e1 100644 --- a/web/tests/lib/index.cjs +++ b/web/tests/lib/index.cjs @@ -83,7 +83,7 @@ handlebars.hook_require(); const noop = function () {}; -require("../../src/templates.js"); // register Zulip extensions +require("../../src/templates.ts"); // register Zulip extensions async function run_one_module(file) { zjquery.clear_initialize_function(); diff --git a/web/tests/lib/namespace.cjs b/web/tests/lib/namespace.cjs index 895423e525..7356c2f664 100644 --- a/web/tests/lib/namespace.cjs +++ b/web/tests/lib/namespace.cjs @@ -209,7 +209,7 @@ exports.zrequire = function (short_fn) { short_fn, "templates", ` - There is no need to zrequire templates.js. + There is no need to zrequire templates.ts. The test runner automatically registers the Handlebars extensions. diff --git a/web/webpack.config.ts b/web/webpack.config.ts index 11b507a317..c10f2afc24 100644 --- a/web/webpack.config.ts +++ b/web/webpack.config.ts @@ -167,7 +167,7 @@ const config = ( ignoreHelpers: true, // Tell webpack not to explicitly require these. knownHelpers: [ - // The ones below are defined in web/src/templates.js + // The ones below are defined in web/src/templates.ts "eq", "and", "or",