Fix citing and locale selection for locales without a CSL locale

The CSL locales repository replaced locales-sr-RS.xml with script
variants, so citing with a stored or default 'sr-RS' locale loaded no
terms and failed with an et-al error. Resolve unavailable locales to
the closest available CSL locale (sr-Cyrl-RS for sr-RS) when rendering
and in locale selectors.

https://forums.zotero.org/discussion/comment/515598/#Comment_515598
This commit is contained in:
Dan Stillman 2026-07-16 11:25:44 -04:00
parent d3b04e7748
commit 6c5d160895
5 changed files with 73 additions and 8 deletions

View file

@ -144,9 +144,12 @@
val = '';
}
val = Zotero.Styles.resolveLocale(val);
this._value = val;
const styleData = this._style ? Zotero.Styles.get(this._style) : null;
this.localeListEl.value = styleData && styleData.effectiveLocale || this._value;
this.localeListEl.value = Zotero.Styles.resolveLocale(
styleData && styleData.effectiveLocale || this._value
);
}
get style() {
@ -157,7 +160,9 @@
this._style = style;
const styleData = style ? Zotero.Styles.get(style) : null;
this.localeListEl.disabled = !style || !!styleData.effectiveLocale;
this.localeListEl.value = styleData && styleData.effectiveLocale || this._value || this.fallbackLocale;
this.localeListEl.value = Zotero.Styles.resolveLocale(
styleData && styleData.effectiveLocale || this._value || this.fallbackLocale
);
}
connectedCallback() {
@ -171,7 +176,7 @@
this._value = this.getAttribute('value');
await Zotero.Styles.init();
this.fallbackLocale = Zotero.Styles?.primaryDialects[Zotero.locale] || Zotero.locale;
this.fallbackLocale = Zotero.Styles.resolveLocale(Zotero.locale);
const menuLocales = Zotero.Utilities.deepCopy(Zotero.Styles.locales);
const menuLocalesKeys = Object.keys(menuLocales).sort();

View file

@ -695,7 +695,16 @@ Zotero.Cite.System.prototype = {
lang = 'sr-Latn-RS';
break;
}
return Zotero.Cite.Locale.get(lang);
var xml = Zotero.Cite.Locale.get(lang);
if (!xml) {
// Use the closest available locale (e.g., 'sr-Cyrl-RS' for 'sr-RS',
// which doesn't have its own CSL locale)
let fallback = Zotero.Styles.resolveLocale(lang);
if (fallback && fallback != lang) {
xml = Zotero.Cite.Locale.get(fallback);
}
}
return xml;
}
};

View file

@ -532,9 +532,31 @@ Zotero.Styles = new function () {
};
};
/**
* Resolve a locale to one with an available CSL locale, using the primary
* dialect of the language or the closest available locale (e.g.,
* 'sr-Cyrl-RS' for 'sr-RS') if the exact locale isn't available
*
* @param {String} locale
* @return {String} The resolved locale, or the passed locale if it can't
* be resolved
*/
this.resolveLocale = function (locale) {
if (!_initialized || !locale) {
return locale;
}
if (locale in Zotero.Styles.locales) {
return locale;
}
return Zotero.Styles.primaryDialects[locale]
|| Zotero.Utilities.Internal.resolveLocale(
locale, Object.keys(Zotero.Styles.locales), { silent: true })
|| locale;
};
/**
* Populate menulist with locales
*
*
* @param {xul:menulist} menulist
*/
this.populateLocaleList = function (menulist) {
@ -546,8 +568,7 @@ Zotero.Styles = new function () {
menulist.selectedItem = null;
menulist.removeAllItems();
let fallbackLocale = Zotero.Styles.primaryDialects[Zotero.locale]
|| Zotero.locale;
let fallbackLocale = Zotero.Styles.resolveLocale(Zotero.locale);
let menuLocales = Zotero.Utilities.deepCopy(Zotero.Styles.locales);
let menuLocalesKeys = Object.keys(menuLocales).sort();
@ -618,7 +639,7 @@ Zotero.Styles = new function () {
if (menulist.labelElement) menulist.labelElement.disabled = false;
let selectLocale = style.effectiveLocale || prefLocale || Zotero.locale;
selectLocale = Zotero.Styles.primaryDialects[selectLocale] || selectLocale;
selectLocale = Zotero.Styles.resolveLocale(selectLocale);
// Make sure the locale we want to select is in the menulist
if (availableLocales.indexOf(selectLocale) == -1) {

View file

@ -41,6 +41,22 @@ describe("Zotero.Cite", function () {
var output = Zotero.Cite.makeFormattedBibliographyOrCitationList(cslEngine, [item], "text");
assert.include(output, 'izd');
});
it("should use closest available locale for locale without a CSL locale", async function () {
var item = new Zotero.Item;
item.fromJSON({
itemType: "book",
title: "Test Book",
edition: "2"
});
await item.saveTx();
var style = Zotero.Styles.get('http://www.zotero.org/styles/chicago-notes-bibliography');
var cslEngine = style.getCiteProc('sr-RS');
var output = Zotero.Cite.makeFormattedBibliographyOrCitationList(cslEngine, [item], "text");
assert.include(output, 'изд');
});
});
describe("#extraToCSL()", function () {

View file

@ -128,6 +128,20 @@ describe("Zotero.Styles", function () {
});
});
describe("Zotero.Styles.resolveLocale", function () {
it("should return available locale unchanged", function () {
assert.equal(Zotero.Styles.resolveLocale('de-AT'), 'de-AT');
});
it("should return primary dialect for a language code", function () {
assert.equal(Zotero.Styles.resolveLocale('fa'), 'fa-IR');
});
it("should return closest available locale for locale without a CSL locale", function () {
assert.equal(Zotero.Styles.resolveLocale('sr-RS'), 'sr-Cyrl-RS');
});
});
describe("Cached CSL.Engine instances", function () {
if (Zotero.Prefs.get('cite.useCiteprocRs')) {
this.skip();