2021-02-10 17:05:00 +01:00
|
|
|
import Handlebars from "handlebars/runtime";
|
2020-08-01 03:43:15 +02:00
|
|
|
|
2021-03-25 21:38:40 +01:00
|
|
|
import {i18n} from "./i18n";
|
2021-02-10 17:05:00 +01:00
|
|
|
import * as util from "./util";
|
2020-02-28 23:59:07 +01:00
|
|
|
|
2019-06-25 11:39:03 +02:00
|
|
|
// Below, we register Zulip-specific extensions to the handlebars API.
|
|
|
|
//
|
|
|
|
// IMPORTANT: When adding a new handlebars helper, update the
|
|
|
|
// knownHelpers array in the webpack config so that webpack knows your
|
|
|
|
// helper is registered at runtime and don't try to require them when
|
|
|
|
// bundling.
|
|
|
|
|
2013-08-27 20:17:42 +02:00
|
|
|
// We don't want to wait for DOM ready to register the Handlebars helpers
|
|
|
|
// below. There's no need to, as they do not access the DOM.
|
|
|
|
// Furthermore, waiting for DOM ready would introduce race conditions with
|
|
|
|
// other DOM-ready callbacks that attempt to render templates.
|
|
|
|
|
2019-06-25 02:54:18 +02:00
|
|
|
Handlebars.registerHelper({
|
2020-07-20 22:18:43 +02:00
|
|
|
eq(a, b) {
|
2020-07-15 00:34:28 +02:00
|
|
|
return a === b;
|
|
|
|
},
|
2020-07-20 22:18:43 +02:00
|
|
|
and(...args) {
|
2020-02-12 01:35:16 +01:00
|
|
|
args.pop(); // Handlebars options
|
|
|
|
if (args.length === 0) {
|
2019-06-25 02:54:18 +02:00
|
|
|
return true;
|
2013-08-13 18:08:12 +02:00
|
|
|
}
|
2020-02-12 01:35:16 +01:00
|
|
|
const last = args.pop();
|
|
|
|
for (const arg of args) {
|
|
|
|
if (!arg || Handlebars.Utils.isEmpty(arg)) {
|
|
|
|
return arg;
|
2019-06-25 02:54:18 +02:00
|
|
|
}
|
2014-02-13 21:16:43 +01:00
|
|
|
}
|
2020-02-12 01:35:16 +01:00
|
|
|
return last;
|
2019-06-25 02:54:18 +02:00
|
|
|
},
|
2020-07-20 22:18:43 +02:00
|
|
|
or(...args) {
|
2020-02-12 01:35:16 +01:00
|
|
|
args.pop(); // Handlebars options
|
|
|
|
if (args.length === 0) {
|
2019-06-25 02:54:18 +02:00
|
|
|
return false;
|
|
|
|
}
|
2020-02-12 01:35:16 +01:00
|
|
|
const last = args.pop();
|
|
|
|
for (const arg of args) {
|
|
|
|
if (arg && !Handlebars.Utils.isEmpty(arg)) {
|
|
|
|
return arg;
|
2019-06-25 02:54:18 +02:00
|
|
|
}
|
|
|
|
}
|
2020-02-12 01:35:16 +01:00
|
|
|
return last;
|
2019-06-25 02:54:18 +02:00
|
|
|
},
|
2020-07-20 22:18:43 +02:00
|
|
|
not(a) {
|
2020-07-15 00:34:28 +02:00
|
|
|
return !a || Handlebars.Utils.isEmpty(a);
|
|
|
|
},
|
2021-04-05 22:05:31 +02:00
|
|
|
["let"](options) {
|
|
|
|
// Defines locally scoped variables.
|
|
|
|
// Example usage:
|
|
|
|
// {{#let name1="value1"}}
|
|
|
|
// {{#*inline "name2"}}
|
|
|
|
// value2
|
|
|
|
// {{/inline}}
|
|
|
|
// Now {{name1}} and {{name2}} are in scope.
|
|
|
|
// {{/let}}
|
|
|
|
return options.fn({
|
|
|
|
...this,
|
|
|
|
...options.hash,
|
|
|
|
...Object.fromEntries(
|
|
|
|
Object.entries(options.fn.partials ?? {}).map(([name, value]) => [
|
|
|
|
name,
|
|
|
|
new Handlebars.SafeString(value(this)),
|
|
|
|
]),
|
|
|
|
),
|
|
|
|
});
|
|
|
|
},
|
2018-09-02 09:38:09 +02:00
|
|
|
});
|
|
|
|
|
templates: Cache translations.
For Manage Streams, when we render the subscriptions
template, a significant amount of time is taken
by the "t" helper.
Obviously for the first call, we expect "t" to be
somewhat expensive, but subsuquent calls should be
fast, but i18next seems to have some overhead.
Also, we can save a tiny bit of overhead (marking it
as a safe string) that comes from our helper.
As an aside, are we sure it's ok to mark translations
as safe strings?
To test before and after, use blueslip.timings before
and after this commit. When I tested with about 300
streams, the difference is pretty striking:
without cache: 100ms
with cache: 20ms
This is particularly interesting, since the subscriptions
templates have long strings for things like the SVG-based
checkmarks, but they're not really the bottleneck.
Unfortunately, this doesn't seem to be a huge win
elsewhere. In some places we don't call "t", but of
course those might change in the future and benefit from
the cache. And in other places we have smart widgets
that avoid rendering all N objects at one (e.g. buddy
list and list_render).
So this might be too big a hammer to speed up one
screen (albeit a really slow one). It's possible
that we should simply move the i18n.t step **outside**
of certain templates to avoid doing them in a loop.
2020-01-15 15:05:17 +01:00
|
|
|
// Note that this i18n caching strategy does not allow us to support
|
|
|
|
// live-updating the UI language without reloading the Zulip browser
|
|
|
|
// window. That constraint would be very hard to change in any case,
|
|
|
|
// though, because of how Zulip renders some strings using the backend
|
|
|
|
// Jinja2 templating engine, so we don't consider this important.
|
|
|
|
const t_cache = new Map();
|
|
|
|
|
2020-07-15 01:29:15 +02:00
|
|
|
Handlebars.registerHelper("t", (i18n_key) => {
|
2016-05-13 12:44:03 +02:00
|
|
|
// Marks a string for translation.
|
|
|
|
// Example usage:
|
|
|
|
// {{t "some English text"}}
|
templates: Cache translations.
For Manage Streams, when we render the subscriptions
template, a significant amount of time is taken
by the "t" helper.
Obviously for the first call, we expect "t" to be
somewhat expensive, but subsuquent calls should be
fast, but i18next seems to have some overhead.
Also, we can save a tiny bit of overhead (marking it
as a safe string) that comes from our helper.
As an aside, are we sure it's ok to mark translations
as safe strings?
To test before and after, use blueslip.timings before
and after this commit. When I tested with about 300
streams, the difference is pretty striking:
without cache: 100ms
with cache: 20ms
This is particularly interesting, since the subscriptions
templates have long strings for things like the SVG-based
checkmarks, but they're not really the bottleneck.
Unfortunately, this doesn't seem to be a huge win
elsewhere. In some places we don't call "t", but of
course those might change in the future and benefit from
the cache. And in other places we have smart widgets
that avoid rendering all N objects at one (e.g. buddy
list and list_render).
So this might be too big a hammer to speed up one
screen (albeit a really slow one). It's possible
that we should simply move the i18n.t step **outside**
of certain templates to avoid doing them in a loop.
2020-01-15 15:05:17 +01:00
|
|
|
|
|
|
|
const cache_result = t_cache.get(i18n_key);
|
|
|
|
if (cache_result !== undefined) {
|
|
|
|
return cache_result;
|
|
|
|
}
|
2019-11-02 00:06:25 +01:00
|
|
|
const result = i18n.t(i18n_key);
|
templates: Cache translations.
For Manage Streams, when we render the subscriptions
template, a significant amount of time is taken
by the "t" helper.
Obviously for the first call, we expect "t" to be
somewhat expensive, but subsuquent calls should be
fast, but i18next seems to have some overhead.
Also, we can save a tiny bit of overhead (marking it
as a safe string) that comes from our helper.
As an aside, are we sure it's ok to mark translations
as safe strings?
To test before and after, use blueslip.timings before
and after this commit. When I tested with about 300
streams, the difference is pretty striking:
without cache: 100ms
with cache: 20ms
This is particularly interesting, since the subscriptions
templates have long strings for things like the SVG-based
checkmarks, but they're not really the bottleneck.
Unfortunately, this doesn't seem to be a huge win
elsewhere. In some places we don't call "t", but of
course those might change in the future and benefit from
the cache. And in other places we have smart widgets
that avoid rendering all N objects at one (e.g. buddy
list and list_render).
So this might be too big a hammer to speed up one
screen (albeit a really slow one). It's possible
that we should simply move the i18n.t step **outside**
of certain templates to avoid doing them in a loop.
2020-01-15 15:05:17 +01:00
|
|
|
const safe_result = new Handlebars.SafeString(result);
|
|
|
|
t_cache.set(i18n_key, safe_result);
|
|
|
|
return safe_result;
|
2016-05-13 12:44:03 +02:00
|
|
|
});
|
|
|
|
|
2020-07-15 01:29:15 +02:00
|
|
|
Handlebars.registerHelper("tr", (context, options) => {
|
2016-05-13 12:44:03 +02:00
|
|
|
// Marks a block for translation.
|
|
|
|
// Example usage 1:
|
|
|
|
// {{#tr context}}
|
|
|
|
// <p>some English text</p>
|
|
|
|
// {{/tr}}
|
|
|
|
//
|
|
|
|
// Example usage 2:
|
|
|
|
// {{#tr context}}
|
|
|
|
// <p>This __variable__ will get value from context</p>
|
|
|
|
// {{/tr}}
|
|
|
|
//
|
|
|
|
// Notes:
|
|
|
|
// 1. `context` is very important. It can be `this` or an
|
|
|
|
// object or key of the current context.
|
|
|
|
// 2. Use `__` instead of `{{` and `}}` to declare expressions
|
2020-07-15 00:34:28 +02:00
|
|
|
const result = i18n.t(
|
|
|
|
options
|
|
|
|
.fn(context)
|
|
|
|
.trim()
|
|
|
|
.split("\n")
|
|
|
|
.map((s) => s.trim())
|
|
|
|
.join(" "),
|
|
|
|
context,
|
|
|
|
);
|
2016-05-13 12:44:03 +02:00
|
|
|
return new Handlebars.SafeString(result);
|
|
|
|
});
|
|
|
|
|
2020-02-28 23:59:07 +01:00
|
|
|
Handlebars.registerHelper(
|
|
|
|
"rendered_markdown",
|
2020-07-02 02:16:03 +02:00
|
|
|
(content) => new Handlebars.SafeString(util.clean_user_content_links(content)),
|
2020-02-28 23:59:07 +01:00
|
|
|
);
|
|
|
|
|
2020-07-15 01:29:15 +02:00
|
|
|
Handlebars.registerHelper("numberFormat", (number) => number.toLocaleString());
|