From 0ffe3122c2c07a1776e27322eb91edbfab04dda7 Mon Sep 17 00:00:00 2001 From: Adomas Ven Date: Tue, 17 Jan 2023 22:59:19 +0200 Subject: [PATCH] Refactor l10n code. Remove use of react-intl. (#2975) Strings from .dtd files are now accessible from Zotero.getMessage as the eventual move to Fluent would remove their distinction. --- chrome/content/zotero/collectionTree.jsx | 5 +-- .../content/zotero/components/annotation.jsx | 42 ++++++++----------- chrome/content/zotero/components/button.jsx | 15 ++++--- .../components/createParent/createParent.jsx | 42 ++++++++----------- .../zotero/components/progressQueueTable.jsx | 21 ++++------ .../tagSelector/tagSelectorList.jsx | 5 +-- .../zotero/components/virtualized-table.jsx | 5 +-- .../containers/tagSelectorContainer.jsx | 5 +-- chrome/content/zotero/contextPane.js | 6 +-- chrome/content/zotero/itemTree.jsx | 7 +--- chrome/content/zotero/locateManager.jsx | 27 ++++++------ .../zotero/preferences/preferences_cite.jsx | 31 +++++++------- .../zotero/preferences/preferences_export.jsx | 31 +++++++------- .../zotero/preferences/preferences_sync.jsx | 27 ++++++------ .../zotero/preferences/preferences_sync.xul | 8 ++-- chrome/content/zotero/rtfScan.jsx | 33 +++++++-------- chrome/content/zotero/xpcom/intl.js | 6 ++- chrome/locale/en-US/zotero/preferences.dtd | 8 ++-- test/tests/{zoteroIntlTest.js => intlTest.js} | 9 +++- 19 files changed, 151 insertions(+), 182 deletions(-) rename test/tests/{zoteroIntlTest.js => intlTest.js} (73%) diff --git a/chrome/content/zotero/collectionTree.jsx b/chrome/content/zotero/collectionTree.jsx index 5f67ca6b3f..84cee70c0a 100644 --- a/chrome/content/zotero/collectionTree.jsx +++ b/chrome/content/zotero/collectionTree.jsx @@ -25,7 +25,6 @@ const React = require('react'); const ReactDOM = require('react-dom'); -const { IntlProvider } = require('react-intl'); const LibraryTree = require('./libraryTree'); const VirtualizedTable = require('components/virtualized-table'); const { TreeSelectionStub } = VirtualizedTable; @@ -42,9 +41,7 @@ var CollectionTree = class CollectionTree extends LibraryTree { var ref; opts.domEl = domEl; let elem = ( - - ref = c } {...opts} /> - + ref = c } {...opts} /> ); await new Promise(resolve => ReactDOM.render(elem, domEl, resolve)); diff --git a/chrome/content/zotero/components/annotation.jsx b/chrome/content/zotero/components/annotation.jsx index cbcc61b68f..bb2f9f3d86 100644 --- a/chrome/content/zotero/components/annotation.jsx +++ b/chrome/content/zotero/components/annotation.jsx @@ -29,7 +29,6 @@ import React, { memo } from 'react'; import ReactDOM from "react-dom"; import PropTypes from 'prop-types'; import cx from 'classnames'; -import { IntlProvider, FormattedMessage } from "react-intl"; // This is a quick reimplementation of the annotation for use in the conflict resolution window. // We'll want to replace this with a single component shared between the PDF reader and the rest @@ -40,31 +39,26 @@ function AnnotationBox({ data }) { }; return ( - -
-
{Zotero.getString('itemTypes.annotation')}
-
-
-
{Zotero.Cite.getLocatorString('page')} {data.pageLabel}
-
- {data.text !== undefined - ?
{data.text}
- : ''} - {data.type == 'image' - // TODO: Localize - // TODO: Render from PDF based on position, if file is the same? Or don't - // worry about it? - ?
[image not shown]
- : ''} - {data.comment !== undefined - ?
{data.comment}
- : ''} +
+
{Zotero.getString('itemTypes.annotation')}
+
+
+
{Zotero.Cite.getLocatorString('page')} {data.pageLabel}
+ {data.text !== undefined + ?
{data.text}
+ : ''} + {data.type == 'image' + // TODO: Localize + // TODO: Render from PDF based on position, if file is the same? Or don't + // worry about it? + ?
[image not shown]
+ : ''} + {data.comment !== undefined + ?
{data.comment}
+ : ''}
- +
); } diff --git a/chrome/content/zotero/components/button.jsx b/chrome/content/zotero/components/button.jsx index 8c15d62070..ca4c6c9cab 100644 --- a/chrome/content/zotero/components/button.jsx +++ b/chrome/content/zotero/components/button.jsx @@ -27,7 +27,6 @@ const React = require('react') const { PureComponent, createElement: create } = React -const { injectIntl } = require('react-intl') const { IconDownChevron } = require('./icons') const cx = require('classnames') const { @@ -59,19 +58,19 @@ class Button extends PureComponent { } get text() { - const { intl, text } = this.props + const { text } = this.props; return text ? - intl.formatMessage({ id: text }) : - null + Zotero.getString(text) : + null; } get title() { - const { intl, title } = this.props + const { title } = this.props; return title ? - intl.formatMessage({ id: title }) : - null + Zotero.getString(title) : + null; } get menuMarker() { @@ -159,5 +158,5 @@ class Button extends PureComponent { module.exports = { ButtonGroup, - Button: injectIntl(Button) + Button } diff --git a/chrome/content/zotero/components/createParent/createParent.jsx b/chrome/content/zotero/components/createParent/createParent.jsx index 7ce4ba0c27..606ca70915 100644 --- a/chrome/content/zotero/components/createParent/createParent.jsx +++ b/chrome/content/zotero/components/createParent/createParent.jsx @@ -29,7 +29,6 @@ import React, { memo } from 'react'; import ReactDOM from "react-dom"; import PropTypes from 'prop-types'; import cx from 'classnames'; -import { IntlProvider } from "react-intl"; function CreateParent({ loading, item, toggleAccept }) { // When the input has/does not have characters toggle the accept button on the dialog @@ -43,31 +42,26 @@ function CreateParent({ loading, item, toggleAccept }) { }; return ( - -
- - { item.attachmentFilename } - -
- -
-
-
+
+ + { item.attachmentFilename } + +
+ +
+
- +
); } diff --git a/chrome/content/zotero/components/progressQueueTable.jsx b/chrome/content/zotero/components/progressQueueTable.jsx index 4406080979..53bf1a0732 100644 --- a/chrome/content/zotero/components/progressQueueTable.jsx +++ b/chrome/content/zotero/components/progressQueueTable.jsx @@ -25,7 +25,6 @@ import React, { memo, useCallback, useEffect, useRef } from 'react'; import PropTypes from 'prop-types'; import { getDOMElement } from 'components/icons'; -import { IntlProvider } from 'react-intl'; import VirtualizedTable, { renderCell } from 'components/virtualized-table'; import { noop } from './utils'; @@ -101,17 +100,15 @@ const ProgressQueueTable = ({ onActivate = noop, progressQueue }) => { }, []); // eslint-disable-line react-hooks/exhaustive-deps return ( - - - + ); }; diff --git a/chrome/content/zotero/components/tagSelector/tagSelectorList.jsx b/chrome/content/zotero/components/tagSelector/tagSelectorList.jsx index 29045e7e0f..f2a67d803d 100644 --- a/chrome/content/zotero/components/tagSelector/tagSelectorList.jsx +++ b/chrome/content/zotero/components/tagSelector/tagSelectorList.jsx @@ -25,7 +25,6 @@ const React = require('react'); const PropTypes = require('prop-types'); -const { FormattedMessage } = require('react-intl'); var { Collection } = require('react-virtualized'); // See also .tag-selector-item in _tag-selector.scss @@ -191,14 +190,14 @@ class TagList extends React.PureComponent { if (!this.props.loaded) { tagList = (
- + {Zotero.getString('zotero.tagSelector.loadingTags')}
); } else if (tagCount == 0) { tagList = (
- + {Zotero.getString('zotero.tagSelector.noTagsToDisplay')}
); } diff --git a/chrome/content/zotero/components/virtualized-table.jsx b/chrome/content/zotero/components/virtualized-table.jsx index 91a2f79eca..f6a109ae71 100644 --- a/chrome/content/zotero/components/virtualized-table.jsx +++ b/chrome/content/zotero/components/virtualized-table.jsx @@ -30,7 +30,6 @@ const PropTypes = require('prop-types'); const cx = require('classnames'); const WindowedList = require('./windowed-list'); const Draggable = require('./draggable'); -const { injectIntl } = require('react-intl'); const { IconDownChevron, getDOMElement } = require('components/icons'); const TYPING_TIMEOUT = 1000; @@ -1636,7 +1635,7 @@ function makeRowRenderer(getRowData) { function formatColumnName(column) { if (column.label in Zotero.Intl.strings) { - return Zotero.Intl.strings[column.label]; + return Zotero.getString(column.label); } else if (/^[^\s]+\w\.\w[^\s]+$/.test(column.label)) { try { @@ -1652,7 +1651,7 @@ function formatColumnName(column) { return column.label; } -module.exports = injectIntl(VirtualizedTable, { forwardRef: true }); +module.exports = VirtualizedTable; module.exports.TreeSelection = TreeSelection; module.exports.TreeSelectionStub = TreeSelectionStub; module.exports.renderCell = renderCell; diff --git a/chrome/content/zotero/containers/tagSelectorContainer.jsx b/chrome/content/zotero/containers/tagSelectorContainer.jsx index 48c3d76647..819a1d5cfc 100644 --- a/chrome/content/zotero/containers/tagSelectorContainer.jsx +++ b/chrome/content/zotero/containers/tagSelectorContainer.jsx @@ -31,7 +31,6 @@ const React = require('react'); const ReactDOM = require('react-dom'); const PropTypes = require('prop-types'); -const { IntlProvider } = require('react-intl'); const TagSelector = require('components/tagSelector.js'); const defaults = { tagColors: new Map(), @@ -762,9 +761,7 @@ Zotero.TagSelector = class TagSelectorContainer extends React.PureComponent { static init(domEl, opts) { var ref; let elem = ( - - ref = c } {...opts} /> - + ref = c } {...opts} /> ); ReactDOM.render(elem, domEl); ref.domEl = domEl; diff --git a/chrome/content/zotero/contextPane.js b/chrome/content/zotero/contextPane.js index e79c49fd71..d31a386e28 100644 --- a/chrome/content/zotero/contextPane.js +++ b/chrome/content/zotero/contextPane.js @@ -937,13 +937,13 @@ var ZoteroContextPane = new function () { // Info tab var tabInfo = document.createElement('tab'); - tabInfo.setAttribute('label', Zotero.Intl.strings['zotero.tabs.info.label']); + tabInfo.setAttribute('label', Zotero.getString('zotero.tabs.info.label')); // Tags tab var tabTags = document.createElement('tab'); - tabTags.setAttribute('label', Zotero.Intl.strings['zotero.tabs.tags.label']); + tabTags.setAttribute('label', Zotero.getString('zotero.tabs.tags.label')); // Related tab var tabRelated = document.createElement('tab'); - tabRelated.setAttribute('label', Zotero.Intl.strings['zotero.tabs.related.label']); + tabRelated.setAttribute('label', Zotero.getString('zotero.tabs.related.label')); tabs.append(tabInfo, tabTags, tabRelated); diff --git a/chrome/content/zotero/itemTree.jsx b/chrome/content/zotero/itemTree.jsx index bc3dc5bc11..3a5b68a0d8 100644 --- a/chrome/content/zotero/itemTree.jsx +++ b/chrome/content/zotero/itemTree.jsx @@ -27,7 +27,6 @@ const { noop, getDragTargetOrient } = require("components/utils"); const PropTypes = require("prop-types"); const React = require('react'); const ReactDOM = require('react-dom'); -const { IntlProvider } = require('react-intl'); const LibraryTree = require('./libraryTree'); const VirtualizedTable = require('components/virtualized-table'); const { renderCell, formatColumnName } = VirtualizedTable; @@ -49,9 +48,7 @@ var ItemTree = class ItemTree extends LibraryTree { var ref; opts.domEl = domEl; let elem = ( - - ref = c } {...opts} /> - + ref = c } {...opts} /> ); await new Promise(resolve => ReactDOM.render(elem, domEl, resolve)); @@ -3730,7 +3727,7 @@ var ItemTree = class ItemTree extends LibraryTree { // Restore Default Column Order // let menuitem = doc.createElementNS(ns, 'menuitem'); - menuitem.setAttribute('label', Zotero.Intl.strings['zotero.items.restoreColumnOrder.label']); + menuitem.setAttribute('label', Zotero.getString('zotero.items.restoreColumnOrder.label')); menuitem.setAttribute('anonid', prefix + 'restore-order'); menuitem.addEventListener('command', () => this.tree._columns.restoreDefaultOrder()); menupopup.appendChild(menuitem); diff --git a/chrome/content/zotero/locateManager.jsx b/chrome/content/zotero/locateManager.jsx index 0297e6eb08..3f84e4f8a2 100644 --- a/chrome/content/zotero/locateManager.jsx +++ b/chrome/content/zotero/locateManager.jsx @@ -24,7 +24,6 @@ */ import VirtualizedTable from 'components/virtualized-table'; -const { IntlProvider } = require('react-intl'); import React from 'react'; import ReactDOM from 'react-dom'; @@ -41,20 +40,18 @@ function init() { engines = Zotero.LocateManager.getEngines(); const domEl = document.querySelector('#locateManager-tree'); let elem = ( - - engines.length} - id="locateManager-table" - ref={ref => tree = ref} - renderItem={VirtualizedTable.makeRowRenderer(getRowData)} - showHeader={true} - multiSelect={true} - columns={columns} - disableFontSizeScaling={true} - getRowString={index => getRowData(index).name} - onActivate={handleActivate} - /> - + engines.length} + id="locateManager-table" + ref={ref => tree = ref} + renderItem={VirtualizedTable.makeRowRenderer(getRowData)} + showHeader={true} + multiSelect={true} + columns={columns} + disableFontSizeScaling={true} + getRowString={index => getRowData(index).name} + onActivate={handleActivate} + /> ); return new Promise(resolve => ReactDOM.render(elem, domEl, resolve)); } diff --git a/chrome/content/zotero/preferences/preferences_cite.jsx b/chrome/content/zotero/preferences/preferences_cite.jsx index ae9b538736..84e795574d 100644 --- a/chrome/content/zotero/preferences/preferences_cite.jsx +++ b/chrome/content/zotero/preferences/preferences_cite.jsx @@ -30,7 +30,6 @@ import FilePicker from 'zotero/modules/filePicker'; var React = require('react'); var ReactDOM = require('react-dom'); var VirtualizedTable = require('components/virtualized-table'); -var { IntlProvider } = require('react-intl'); var { makeRowRenderer } = VirtualizedTable; Zotero_Preferences.Cite = { @@ -118,22 +117,20 @@ Zotero_Preferences.Cite = { } }; let elem = ( - - this.styles.length} - id="styleManager-table" - ref={ref => this._tree = ref} - renderItem={makeRowRenderer(index => this.styles[index])} - showHeader={true} - multiSelect={true} - columns={columns} - staticColumns={true} - disableFontSizeScaling={true} - onSelectionChange={() => document.getElementById('styleManager-delete').disabled = undefined} - onKeyDown={handleKeyDown} - getRowString={index => this.styles[index].title} - /> - + this.styles.length} + id="styleManager-table" + ref={ref => this._tree = ref} + renderItem={makeRowRenderer(index => this.styles[index])} + showHeader={true} + multiSelect={true} + columns={columns} + staticColumns={true} + disableFontSizeScaling={true} + onSelectionChange={() => document.getElementById('styleManager-delete').disabled = undefined} + onKeyDown={handleKeyDown} + getRowString={index => this.styles[index].title} + /> ); await new Promise(resolve => ReactDOM.render(elem, document.getElementById("styleManager"), resolve)); } diff --git a/chrome/content/zotero/preferences/preferences_export.jsx b/chrome/content/zotero/preferences/preferences_export.jsx index cb9d73382a..d9a8c0f79c 100644 --- a/chrome/content/zotero/preferences/preferences_export.jsx +++ b/chrome/content/zotero/preferences/preferences_export.jsx @@ -28,7 +28,6 @@ var React = require('react'); var ReactDOM = require('react-dom'); var VirtualizedTable = require('components/virtualized-table'); -var { IntlProvider } = require('react-intl'); var { makeRowRenderer } = VirtualizedTable; Zotero_Preferences.Export = { @@ -492,22 +491,20 @@ Zotero_Preferences.Export = { }; let elem = ( - - this._rows.length} - id="quickCopy-siteSettings-table" - ref={ref => this._tree = ref} - renderItem={makeRowRenderer(index => this._rows[index])} - showHeader={true} - columns={columns} - staticColumns={true} - disableFontSizeScaling={true} - onSelectionChange={handleSelectionChange} - onKeyDown={handleKeyDown} - getRowString={index => this._rows[index].domain} - onActivate={(event, indices) => Zotero_Preferences.Export.showQuickCopySiteEditor()} - /> - + this._rows.length} + id="quickCopy-siteSettings-table" + ref={ref => this._tree = ref} + renderItem={makeRowRenderer(index => this._rows[index])} + showHeader={true} + columns={columns} + staticColumns={true} + disableFontSizeScaling={true} + onSelectionChange={handleSelectionChange} + onKeyDown={handleKeyDown} + getRowString={index => this._rows[index].domain} + onActivate={(event, indices) => Zotero_Preferences.Export.showQuickCopySiteEditor()} + /> ); await new Promise(resolve => ReactDOM.render(elem, document.getElementById("quickCopy-siteSettings"), resolve)); } else { diff --git a/chrome/content/zotero/preferences/preferences_sync.jsx b/chrome/content/zotero/preferences/preferences_sync.jsx index d5e6b973f9..96eebabd44 100644 --- a/chrome/content/zotero/preferences/preferences_sync.jsx +++ b/chrome/content/zotero/preferences/preferences_sync.jsx @@ -32,7 +32,6 @@ var React = require('react'); var ReactDOM = require('react-dom'); var VirtualizedTable = require('components/virtualized-table'); var { getDOMElement } = require('components/icons'); -var { IntlProvider } = require('react-intl'); var { renderCell } = VirtualizedTable; Zotero_Preferences.Sync = { @@ -319,20 +318,18 @@ Zotero_Preferences.Sync = { } }; let elem = ( - - this._rows.length} - id="librariesToSync-table" - ref={ref => this._tree = ref} - renderItem={renderItem} - showHeader={true} - columns={columns} - staticColumns={true} - getRowString={index => this._rows[index].name} - disableFontSizeScaling={true} - onKeyDown={handleKeyDown} - /> - + this._rows.length} + id="librariesToSync-table" + ref={ref => this._tree = ref} + renderItem={renderItem} + showHeader={true} + columns={columns} + staticColumns={true} + getRowString={index => this._rows[index].name} + disableFontSizeScaling={true} + onKeyDown={handleKeyDown} + /> ); ReactDOM.render(elem, document.getElementById("libraries-to-sync-tree")); diff --git a/chrome/content/zotero/preferences/preferences_sync.xul b/chrome/content/zotero/preferences/preferences_sync.xul index 3f56e1baae..a408d9d7eb 100644 --- a/chrome/content/zotero/preferences/preferences_sync.xul +++ b/chrome/content/zotero/preferences/preferences_sync.xul @@ -288,15 +288,15 @@
  • diff --git a/chrome/content/zotero/rtfScan.jsx b/chrome/content/zotero/rtfScan.jsx index 00895dac62..59b478617c 100644 --- a/chrome/content/zotero/rtfScan.jsx +++ b/chrome/content/zotero/rtfScan.jsx @@ -31,7 +31,6 @@ import FilePicker from 'zotero/modules/filePicker'; import React from 'react'; import ReactDOM from 'react-dom'; import VirtualizedTable from 'components/virtualized-table'; -import { IntlProvider } from 'react-intl'; import { getDOMElement } from 'components/icons'; /** @@ -51,9 +50,9 @@ var Zotero_RTFScan = new function() { var ids = 0; var tree; this._rows = [ - { id: 'unmapped', rtf: Zotero.Intl.strings['zotero.rtfScan.unmappedCitations.label'], collapsed: false }, - { id: 'ambiguous', rtf: Zotero.Intl.strings['zotero.rtfScan.ambiguousCitations.label'], collapsed: false }, - { id: 'mapped', rtf: Zotero.Intl.strings['zotero.rtfScan.mappedCitations.label'], collapsed: false }, + { id: 'unmapped', rtf: Zotero.getString('zotero.rtfScan.unmappedCitations.label'), collapsed: false }, + { id: 'ambiguous', rtf: Zotero.getString('zotero.rtfScan.ambiguousCitations.label'), collapsed: false }, + { id: 'mapped', rtf: Zotero.getString('zotero.rtfScan.mappedCitations.label'), collapsed: false }, ]; this._rowMap = {}; this._rows.forEach((row, index) => this._rowMap[row.id] = index); @@ -388,9 +387,9 @@ var Zotero_RTFScan = new function() { document.documentElement.currentPage = document.getElementById('intro-page'); this._rows = [ - { id: 'unmapped', rtf: Zotero.Intl.strings['zotero.rtfScan.unmappedCitations.label'], collapsed: false }, - { id: 'ambiguous', rtf: Zotero.Intl.strings['zotero.rtfScan.ambiguousCitations.label'], collapsed: false }, - { id: 'mapped', rtf: Zotero.Intl.strings['zotero.rtfScan.mappedCitations.label'], collapsed: false }, + { id: 'unmapped', rtf: Zotero.getString('zotero.rtfScan.unmappedCitations.label'), collapsed: false }, + { id: 'ambiguous', rtf: Zotero.getString('zotero.rtfScan.ambiguousCitations.label'), collapsed: false }, + { id: 'mapped', rtf: Zotero.getString('zotero.rtfScan.mappedCitations.label'), collapsed: false }, ]; this._rowMap = {}; this._rows.forEach((row, index) => this._rowMap[row.id] = index); @@ -768,17 +767,15 @@ var Zotero_RTFScan = new function() { this._initCitationTree = function () { const domEl = document.querySelector('#tree'); const elem = ( - - this._rows.length} - id="rtfScan-table" - ref={ref => tree = ref} - renderItem={this._renderItem} - showHeader={true} - columns={columns} - disableFontSizeScaling={true} - /> - + this._rows.length} + id="rtfScan-table" + ref={ref => tree = ref} + renderItem={this._renderItem} + showHeader={true} + columns={columns} + disableFontSizeScaling={true} + /> ); return new Promise(resolve => ReactDOM.render(elem, domEl, resolve)); }; diff --git a/chrome/content/zotero/xpcom/intl.js b/chrome/content/zotero/xpcom/intl.js index 8b5fe0f98c..f360eeaf98 100644 --- a/chrome/content/zotero/xpcom/intl.js +++ b/chrome/content/zotero/xpcom/intl.js @@ -55,7 +55,8 @@ Zotero.Intl = new function () { Components.utils.import("resource://gre/modules/PluralForm.jsm"); - bundle = Services.strings.createBundle('chrome://zotero/locale/zotero.properties'); + // Exposed for tests + this._bundle = bundle = Services.strings.createBundle('chrome://zotero/locale/zotero.properties'); intlProps = Services.strings.createBundle('chrome://zotero/locale/mozilla/intl.properties'); [pluralFormGet, pluralFormNumForms] = PluralForm.makeGetter(parseInt(getIntlProp('pluralRule', 1))); @@ -105,6 +106,9 @@ Zotero.Intl = new function () { } l10n = bundle.formatStringFromName(name, params, params.length); } + else if (this.strings[name]) { + return this.strings[name]; + } else { l10n = bundle.GetStringFromName(name); } diff --git a/chrome/locale/en-US/zotero/preferences.dtd b/chrome/locale/en-US/zotero/preferences.dtd index fb20dd2fa7..3b081d7ed6 100644 --- a/chrome/locale/en-US/zotero/preferences.dtd +++ b/chrome/locale/en-US/zotero/preferences.dtd @@ -76,10 +76,10 @@ - - - - + + + + diff --git a/test/tests/zoteroIntlTest.js b/test/tests/intlTest.js similarity index 73% rename from test/tests/zoteroIntlTest.js rename to test/tests/intlTest.js index 62d5038544..b54229bfee 100644 --- a/test/tests/zoteroIntlTest.js +++ b/test/tests/intlTest.js @@ -18,9 +18,16 @@ describe("Zotero.Intl", function() { it("shouldn't ignore whitespace", function () { assert.equal(Zotero.localeCompare("Chang", "Chan H"), 1); }); - + it("shouldn't ignore leading punctuation", function () { assert.equal(Zotero.localeCompare("_Abcd", "Abcd"), -1); }); }); + + it("there should not be duplicate string keys in .dtd and .properties files", function () { + let dtdStrings = Object.keys(Zotero.Intl.strings); + for (let key of dtdStrings) { + assert.throws(() => Zotero.Intl._bundle.GetStringFromName(key)); + } + }); });