Fix Dropdown list cannot be created with values.

**Fixed Dropdown custom field options not being addable / saving as empty.**
Creating a "Dropdown" custom field showed the "List Options" box, but pressing
Enter (or typing and saving) never added any option, and on the card the only
selectable value was `(none)`. The custom-fields sidebar component was migrated
from `BlazeComponent` to a plain `Template`, but the template still iterates the
options with `{{#each dropdownItems.get}}` — under BlazeComponent that resolved
to the instance's `dropdownItems` ReactiveVar, whereas a plain Template does not
expose instance variables to the template, so the list rendered nothing. Because
the options were never rendered as inputs, `getDropdownItems()` then overwrote
the ReactiveVar with the empty DOM on save and every entered value was dropped.
Fixed by re-adding the missing `dropdownItems` helper (returning the ReactiveVar),
matching the pattern the other migrated templates already use. Thanks to Claude.

**Fixed `Exception in global helper _` when opening the Create Custom Field
popup (and any translation containing a literal `%`).** i18n is configured with
a global sprintf post-processor (`postProcess: ["sprintf"]`), so every
translation is run through `i18next-sprintf-postprocessor`. The help text
`custom-field-stringtemplate-format` (`"Format (use %{value} as placeholder)"`)
contains a literal `%{value}` that sprintf cannot parse, so `vsprintf` threw and
crashed the global Blaze `_` translation helper — breaking that popup and any
string (in any language, including user translation overrides) that contains a
stray `%`. `TAPi18n.__` now retries without the sprintf post-processor when it
throws, returning the raw string (so `%{value}` is shown literally) instead of
crashing.

Thanks to rouceto1 and Claude.

Fixes #6357
main
Lauri Ojansivu 5 days ago
parent 8ff9a41f2d
commit e01f99eb52
  1. 9
      client/components/sidebar/sidebarCustomFields.js
  2. 30
      imports/i18n/tap.js

@ -139,6 +139,15 @@ Template.createCustomFieldPopup.helpers({
});
},
// The template iterates `{{#each dropdownItems.get}}`. This component used to
// be a BlazeComponent, where `dropdownItems` resolved to the instance's
// ReactiveVar; after the migration to a plain Template that binding rendered
// nothing (so dropdown options never appeared and were lost on save). Expose
// the ReactiveVar as a helper again so `dropdownItems.get` works.
dropdownItems() {
return Template.instance().dropdownItems;
},
getDropdownItems() {
return getDropdownItems(Template.instance());
},

@ -105,18 +105,30 @@ export const TAPi18n = {
// Return translation by key
__(key, options, language) {
this.current.dep.depend();
const translation = this.i18n.t(key, {
...options,
lng: language,
});
// The global sprintf post-processor (`postProcess: ["sprintf"]`) throws when
// a translation value contains a '%' sequence it cannot parse, e.g. the
// literal "%{value}" used in some help texts. That would crash the caller
// (the global Blaze '_' helper). Retry without sprintf so such strings
// render literally instead of throwing.
const translate = (lng, extra = {}) => {
const opts = { ...options, ...extra, lng };
try {
return this.i18n.t(key, opts);
} catch (e) {
try {
return this.i18n.t(key, { ...opts, postProcess: false });
} catch (e2) {
return key;
}
}
};
const translation = translate(language);
// If the selected language misses the key, fallback explicitly to English.
if (translation === key) {
const englishFallback = this.i18n.t(key, {
...options,
lng: DEFAULT_LANGUAGE,
fallbackLng: false,
});
const englishFallback = translate(DEFAULT_LANGUAGE, { fallbackLng: false });
if (englishFallback !== key) {
return englishFallback;

Loading…
Cancel
Save