Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 23 additions & 3 deletions convert_po_to_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@
import re
import sys

SOLFEGE_KEY = "ti la sol fa mi re do"
SOLFEGE_TOKEN_COUNT = 7

def is_valid_solfege_translation(value):
Comment thread
walterbender marked this conversation as resolved.
"""Return True when a solfege translation has seven note labels."""
return len(value.split()) == SOLFEGE_TOKEN_COUNT

def parse_po_file(po_file):
"""Parse a .po file and return a dict of {msgid: msgstr}."""
data = {}
Expand All @@ -32,7 +39,20 @@ def parse_po_file(po_file):
elif line.startswith("msgstr"):
current_msgstr = re.findall(r'"(.*)"', line)[0]
if current_msgid is not None:
data[current_msgid] = current_msgstr or current_msgid
if (
current_msgid == SOLFEGE_KEY
and not is_valid_solfege_translation(current_msgstr)
):
print(
f"Warning: invalid solfege translation in "
f"{po_file}: expected {SOLFEGE_TOKEN_COUNT} "
f"tokens, got {len(current_msgstr.split())}. "
f"Falling back to English.",
file=sys.stderr,
)
data[current_msgid] = current_msgid
else:
data[current_msgid] = current_msgstr or current_msgid
current_msgid = None
return data

Expand Down Expand Up @@ -63,7 +83,7 @@ def convert_po_to_json(po_file, output_dir):
os.makedirs(output_dir, exist_ok=True)
with open(output_path, "w", encoding="utf-8") as f:
json.dump(combined, f, indent=2, ensure_ascii=False)
print(f"Combined ja.po + ja-kana.po {output_path}")
print(f"Combined ja.po + ja-kana.po -> {output_path}")
return # Don’t fall through to default case

# Default for all other langs
Expand All @@ -72,7 +92,7 @@ def convert_po_to_json(po_file, output_dir):
os.makedirs(output_dir, exist_ok=True)
with open(output_path, "w", encoding="utf-8") as f:
json.dump(json_data, f, indent=2, ensure_ascii=False)
print(f"Converted {po_file} {output_path}")
print(f"Converted {po_file} -> {output_path}")

def convert_all_po_files(po_dir, output_dir):
"""Convert all .po files in the given directory to .json format."""
Expand Down
31 changes: 31 additions & 0 deletions js/utils/__tests__/musicutils.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2058,6 +2058,10 @@ describe("splitSolfege", () => {
describe("i18nSolfege", () => {
global.SOLFNOTES = ["ti", "la", "sol", "fa", "mi", "re", "do"];

afterEach(() => {
global._.mockImplementation(str => str);
});

it("should return the internationalized version of a solfege note", () => {
expect(i18nSolfege("do")).toEqual("do");
expect(i18nSolfege("ti")).toEqual("ti");
Expand All @@ -2078,6 +2082,33 @@ describe("i18nSolfege", () => {
it("should handle null or undefined values gracefully", () => {
expect(i18nSolfege(null)).toEqual("sol"); //default
});

it("should use a complete localized solfege sequence", () => {
global._.mockImplementation(str =>
str === "ti la sol fa mi re do" ? "ती ला सोल फा मी रे दो" : str
);

expect(i18nSolfege("sol")).toEqual("सोल");
expect(i18nSolfege("do♯")).toEqual("दो♯");
});

it("should map localized solfege labels back to canonical notes", () => {
global._.mockImplementation(str =>
str === "ti la sol fa mi re do" ? "ती ला सोल फा मी रे दो" : str
);

expect(i18nSolfege("सोल")).toEqual("sol");
expect(i18nSolfege("दो♯")).toEqual("do♯");
});

it("should ignore malformed solfege translations", () => {
global._.mockImplementation(str =>
str === "ti la sol fa mi re do" ? "not a note sequence" : str
);

expect(i18nSolfege("sol")).toEqual("sol");
expect(i18nSolfege("do♯")).toEqual("do♯");
});
});

describe("splitScaleDegree", () => {
Expand Down
54 changes: 44 additions & 10 deletions js/utils/musicutils.js
Original file line number Diff line number Diff line change
Expand Up @@ -4797,14 +4797,9 @@ function getNote(
// The note should already be translated, but just in case...
// Reverse any i18n
// solfnotes_ is used in the interface for i18n
//.TRANS: the note names must be separated by single spaces
const solfnotes_ = _("ti la sol fa mi re do").split(" ");
if (solfnotes_.includes(noteArg.substr(0, 1).toLowerCase())) {
solfegePart = SOLFNOTES[solfnotes_.indexOf(noteArg.substr(0, 2).toLowerCase())];
} else if (solfnotes_.includes(noteArg.substr(0, 2).toLowerCase())) {
solfegePart = SOLFNOTES[solfnotes_.indexOf(noteArg.substr(0, 2).toLowerCase())];
} else if (solfnotes_.includes(noteArg.substr(0, 3).toLowerCase())) {
solfegePart = SOLFNOTES[solfnotes_.indexOf(noteArg.substr(0, 3).toLowerCase())];
const obj = splitI18nSolfege(noteArg);
if (SOLFNOTES.includes(obj[0])) {
solfegePart = obj[0];
} else {
solfegePart = noteArg.substr(0, 2).toLowerCase();
}
Expand Down Expand Up @@ -6441,6 +6436,40 @@ const splitSolfege = value => {
return ["sol", ""];
};

const getI18nSolfNotes = () => {
//.TRANS: the note names must be separated by single spaces
const solfnotes = _("ti la sol fa mi re do");
if (typeof solfnotes !== "string") {
return SOLFNOTES;
}

const translated = solfnotes.trim().split(/\s+/);
if (translated.length !== SOLFNOTES.length || translated.some(note => note.length === 0)) {
return SOLFNOTES;
}

return translated;
};

const splitI18nSolfege = value => {
if (value !== null && typeof value === "string") {
const solfnotes = getI18nSolfNotes();
const lowerValue = value.toLowerCase();
const matches = solfnotes
.map((note, i) => ({ note, i }))
.sort((a, b) => b.note.length - a.note.length);

for (const match of matches) {
const lowerNote = match.note.toLowerCase();
if (lowerValue === lowerNote || lowerValue.startsWith(lowerNote)) {
return [SOLFNOTES[match.i], value.slice(match.note.length)];
}
}
}

return splitSolfege(value);
};

/**
* Internationalize a solfege note using i18n.
* @function
Expand All @@ -6449,8 +6478,13 @@ const splitSolfege = value => {
*/
const i18nSolfege = note => {
// solfnotes_ is used in the interface for i18n
const solfnotes_ = _("ti la sol fa mi re do").split(" ");
const obj = splitSolfege(note);
const solfnotes_ = getI18nSolfNotes();
const sourceObj = splitSolfege(note);
const obj = splitI18nSolfege(note);

if (!SOLFNOTES.includes(sourceObj[0]) && SOLFNOTES.includes(obj[0])) {
return obj[0] + obj[1];
}

const i = SOLFNOTES.indexOf(obj[0]);
if (i !== -1) {
Expand Down
2 changes: 1 addition & 1 deletion locales/am.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion locales/ar.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion locales/bg.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion locales/bn.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion locales/de.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion locales/dz.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion locales/el.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion locales/fa.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion locales/he.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion locales/hy.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion locales/ibo.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion locales/ka.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion locales/kn.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion locales/ko.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion locales/ml.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion locales/mn.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion locales/ru.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion locales/si.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion locales/ta.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion locales/th.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion locales/ur.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion locales/zh_CN.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion locales/zh_TW.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion po/am.po
Original file line number Diff line number Diff line change
Expand Up @@ -4452,7 +4452,7 @@ msgstr ""
#.TRANS: the note names must be separated by single spaces
#.TRANS: the note names must be separated by single spaces
msgid "ti la sol fa mi re do"
msgstr ""
msgstr "ቲ ላ ሶል ፋ ሚ ሬ ዶ"

#: js/block.js:2832
#: js/blocks/IntervalsBlocks.js:627
Expand Down
2 changes: 1 addition & 1 deletion po/ar.po
Original file line number Diff line number Diff line change
Expand Up @@ -3060,7 +3060,7 @@ msgstr "الصمت"
#.TRANS: the note names must be separated by single spaces
#. AUTOTRANSLATED: Google Translate
msgid "ti la sol fa mi re do"
msgstr "سأعطيك الوحيد"
msgstr "سي لا صول فا مي ري دو"


#: js/block.js:2832
Expand Down
Loading
Loading