Skip to content
Merged
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
d78919c
Add files via upload
ac-mmi Feb 19, 2025
ac566eb
Add files via upload
ac-mmi Feb 20, 2025
29a4416
Merge branch 'sugarlabs:master' into master
ac-mmi Feb 21, 2025
1678672
Merge branch 'sugarlabs:master' into master
ac-mmi Feb 24, 2025
5ead512
Migrate internationalization from webL10n to i18next in JS files
ac-mmi Feb 24, 2025
b844c94
removed random encodings spoiling the .po files
ac-mmi Feb 24, 2025
5cf011e
Add files via upload
ac-mmi Feb 24, 2025
1680230
Changed the function definition of _() suited for i18next
ac-mmi Feb 24, 2025
d8bbb85
Merge branch 'internalization-js' of https://github.com/ac-mmi/musicb…
ac-mmi Feb 24, 2025
d6b1114
updated the i18next json files
ac-mmi Feb 24, 2025
e6e8c5a
changed the language switch functions to i18next standard in activity…
ac-mmi Feb 25, 2025
c21fb14
Merge branch 'master' into internalization-js
ac-mmi Mar 4, 2025
7c46058
Add files via upload
ac-mmi Mar 7, 2025
a238ef8
Add files via upload
ac-mmi Mar 7, 2025
ce5a5e5
Delete locales/ja-kana.json
ac-mmi Mar 7, 2025
c9d69ca
Delete locales/ja.json
ac-mmi Mar 7, 2025
fe630ea
Add files via upload
ac-mmi Mar 7, 2025
c25e7fb
Merge branch 'master' into internalization-js
ac-mmi Mar 7, 2025
740b4da
Merge branch 'master' into internalization-js
ac-mmi Mar 15, 2025
dcb2d7e
Merge branch 'master' into internalization-js
ac-mmi Mar 23, 2025
040a030
Add files via upload
ac-mmi Mar 24, 2025
bbaca35
Add files via upload
ac-mmi Mar 24, 2025
4540621
Add files via upload
ac-mmi Mar 24, 2025
75f50db
Add files via upload
ac-mmi Mar 24, 2025
45990f8
Update translate_ai.py
ac-mmi Mar 24, 2025
ea46f98
Add files via upload
ac-mmi Mar 30, 2025
8abe738
migration to i18next
ac-mmi Jul 24, 2025
ca1987f
migration to i18next
ac-mmi Jul 24, 2025
d4bd26f
Merge branch 'master' into i18next
ac-mmi Jul 24, 2025
25ecd9b
fixed the linting issue
ac-mmi Jul 24, 2025
72bfad3
Merge branch 'i18next' of https://github.com/ac-mmi/musicblocks into …
ac-mmi Jul 24, 2025
a0c1ef5
fixed some test cases of toolbar due to addition of TR language had t…
ac-mmi Jul 24, 2025
3b0d1d5
added newly updated TR file
ac-mmi Jul 27, 2025
d6e56e4
fix: update package-lock.json to sync with package.json
ac-mmi Jul 27, 2025
c09ab33
Merge branch 'i18next' of https://github.com/ac-mmi/musicblocks into …
ac-mmi Jul 27, 2025
13b94b9
removed old kana kanji jap files
ac-mmi Jul 27, 2025
4d88b7e
added docstring in po to json converter
ac-mmi Jul 28, 2025
97ecdee
added license and copyright in the converter file
ac-mmi Jul 28, 2025
c9695d5
added worflow for auto conversion of po to json files
ac-mmi Jul 31, 2025
b19803b
testing worflow
ac-mmi Jul 31, 2025
d01f9c9
chore(i18n): auto-update JSON files from updated PO files
github-actions[bot] Jul 31, 2025
ae4a2a4
resolved the lng change issues in japanese
ac-mmi Sep 7, 2025
cd88498
Merge branch 'i18next' of https://github.com/ac-mmi/musicblocks into …
ac-mmi Sep 7, 2025
0e9346e
solved the lang change issue for japanese kana and kanji
ac-mmi Sep 7, 2025
3e0f528
solved the issue of separate loader image in case of jap lang and adv…
ac-mmi Sep 8, 2025
19f02bf
po files updation
ac-mmi Sep 8, 2025
7b11ec7
chore(i18n): auto-update JSON files from updated PO files
github-actions[bot] Sep 8, 2025
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
53 changes: 53 additions & 0 deletions convert_po_to_json.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
"""
This script converts GNU gettext .po translation files into JSON format.
It extracts only `msgid` and `msgstr` pairs, skipping metadata and comments,
and writes them to language-specific .json files for use in localization.

Usage:
- Place .po files inside a directory (e.g., ./po)
- The script will convert all .po files in that directory into .json
and save them to the specified output directory (e.g., ./locales)
"""

import os
import json
import re

def convert_po_to_json(po_file, output_dir):
"""Convert a .po file to .json reading only msgid and msgstr lines."""

json_data = {}
current_msgid = None
current_msgstr = None

with open(po_file, "r", encoding="utf-8") as f:
for line in f:
# Ignore all lines except those with msgid or msgstr
line = line.strip()
if line.startswith("msgid"):
current_msgid = re.findall(r'"(.*)"', line)[0]
elif line.startswith("msgstr"):
current_msgstr = re.findall(r'"(.*)"', line)[0]
# Save the pair if msgid is present
if current_msgid is not None:
json_data[current_msgid] = current_msgstr or current_msgid
current_msgid = None # Reset for the next pair

# Extract language code (e.g., 'fr' from 'fr.po')
lang_code = os.path.splitext(os.path.basename(po_file))[0]
output_path = os.path.join(output_dir, f"{lang_code}.json")

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}")

def convert_all_po_files(po_dir, output_dir):
"""Convert all .po files in the given directory to .json format."""
for root, _, files in os.walk(po_dir):
for file in files:
if file.endswith(".po"):
convert_po_to_json(os.path.join(root, file), output_dir)

convert_all_po_files("./po", "./locales")
1 change: 1 addition & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,7 @@
<li><a id="ko"></a></li>
<li><a id="zhCN"></a></li>
<li><a id="th"></a></li>
<li><a id="tr"></a></li>
<li><a id="ayc"></a></li>
<li><a id="quz"></a></li>
<li><a id="gug"></a></li>
Expand Down
4 changes: 2 additions & 2 deletions js/__tests__/toolbar.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,14 +95,14 @@ describe("Toolbar Class", () => {
test("sets correct strings for _THIS_IS_MUSIC_BLOCKS_ true", () => {
global._THIS_IS_MUSIC_BLOCKS_ = true;
toolbar.init({});
expect(global._).toHaveBeenCalledTimes(132);
expect(global._).toHaveBeenCalledTimes(134);
expect(global._).toHaveBeenNthCalledWith(1, "About Music Blocks");
});

test("sets correct strings for _THIS_IS_MUSIC_BLOCKS_ false", () => {
global._THIS_IS_MUSIC_BLOCKS_ = false;
toolbar.init({});
expect(global._).toHaveBeenCalledTimes(114);
expect(global._).toHaveBeenCalledTimes(116);
expect(global._).toHaveBeenNthCalledWith(1, "About Turtle Blocks");
});

Expand Down
78 changes: 51 additions & 27 deletions js/activity.js
Original file line number Diff line number Diff line change
Expand Up @@ -310,19 +310,19 @@ class Activity {
let lang = "en";
if (this.storage.languagePreference !== undefined) {
lang = this.storage.languagePreference;
document.webL10n.setLanguage(lang);
i18next.changeLanguage(lang);
} else {
lang = navigator.language;
if (lang.includes("-")) {
lang = lang.slice(0, lang.indexOf("-"));
document.webL10n.setLanguage(lang);
}
i18next.changeLanguage(lang);
}
} catch (e) {
// eslint-disable-next-line no-console
console.error(e);
}


this.KeySignatureEnv = ["C", "major", false];
try {
if (this.storage.KeySignatureEnv !== undefined) {
Expand Down Expand Up @@ -563,15 +563,6 @@ class Activity {
if (helpfulWheelTop + 350 > windowHeight) {
docById("helpfulWheelDiv").style.top = (windowHeight - 350) + "px";
}
const selectedBlocksCount = this.blocks.selectedBlocks.filter(block => !block.trash).length;

if (selectedBlocksCount) {
this.helpfulWheelItems.find(ele => ele.label === "Move to trash").display = true;
this.helpfulWheelItems.find(ele => ele.label === "Duplicate").display = true;
} else {
this.helpfulWheelItems.find(ele => ele.label === "Move to trash").display = false;
this.helpfulWheelItems.find(ele => ele.label === "Duplicate").display = false;
}

docById("helpfulWheelDiv").style.display = "";

Expand Down Expand Up @@ -1421,6 +1412,14 @@ class Activity {
const confirmBtn = document.createElement("button");
confirmBtn.classList.add("confirm-button");
confirmBtn.textContent = "Confirm";
confirmBtn.style.backgroundColor = platformColor.blueButton;
confirmBtn.style.color = "white";
confirmBtn.style.border = "none";
confirmBtn.style.borderRadius = "4px";
confirmBtn.style.padding = "8px 16px";
confirmBtn.style.fontWeight = "bold";
confirmBtn.style.cursor = "pointer";
confirmBtn.style.marginRight = "16px";
confirmBtn.addEventListener("click", () => {
document.body.removeChild(modal);
clearCanvasAction();
Expand All @@ -1429,6 +1428,13 @@ class Activity {
const cancelBtn = document.createElement("button");
cancelBtn.classList.add("cancel-button");
cancelBtn.textContent = "Cancel";
cancelBtn.style.backgroundColor = "#f1f1f1";
cancelBtn.style.color = "black";
cancelBtn.style.border = "none";
cancelBtn.style.borderRadius = "4px";
cancelBtn.style.padding = "8px 16px";
cancelBtn.style.fontWeight = "bold";
cancelBtn.style.cursor = "pointer";
cancelBtn.addEventListener("click", () => {
document.body.removeChild(modal);
});
Expand Down Expand Up @@ -2977,6 +2983,7 @@ class Activity {
// note block to the active block.
this.blocks.activeBlock = this.blocks.blockList.length - 1;
};


//To create a sampler widget
this.makeSamplerWidget = (sampleName, sampleData) => {
Expand All @@ -2994,6 +3001,7 @@ class Activity {
this.blocks.loadNewBlocks(samplerStack);
};


/*
* Handles keyboard shortcuts in MB
*/
Expand Down Expand Up @@ -4265,6 +4273,33 @@ class Activity {
}, 5000);
};


const standardDurations = [
{ value: "1/1", duration: 1 },
{ value: "1/2", duration: 0.5 },
{ value: "1/4", duration: 0.25 },
{ value: "1/8", duration: 0.125 },
{ value: "1/16", duration: 0.0625 },
{ value: "1/32", duration: 0.03125 },
{ value: "1/64", duration: 0.015625 },
{ value: "1/128", duration: 0.0078125 }
];

this.getClosestStandardNoteValue = function(duration) {
let closest = standardDurations[0];
let minDiff = Math.abs(duration - closest.duration);

for (let i = 1; i < standardDurations.length; i++) {
let diff = Math.abs(duration - standardDurations[i].duration);
if (diff < minDiff) {
closest = standardDurations[i];
minDiff = diff;
}
}

return closest.value.split("/").map(Number);
};

/**
* Loads MB project from Planet.
* @param projectID {Planet project ID}
Expand Down Expand Up @@ -5653,13 +5688,7 @@ class Activity {

if (!this.helpfulWheelItems.find(ele => ele.label === "Select"))
this.helpfulWheelItems.push({label: "Select", icon: "imgsrc:data:image/svg+xml;base64," + window.btoa(base64Encode(SELECTBUTTON)), display: true, fn: this.selectMode });

if (!this.helpfulWheelItems.find(ele => ele.label === "Move to trash"))
this.helpfulWheelItems.push({label: "Move to trash", icon: "imgsrc:header-icons/empty-trash-button.svg", display: false, fn: this.deleteMultipleBlocks });

if (!this.helpfulWheelItems.find(ele => ele.label === "Duplicate"))
this.helpfulWheelItems.push({label: "Duplicate", icon: "imgsrc:header-icons/copy-button.svg" , display: false, fn: this.copyMultipleBlocks});


if (!this.helpfulWheelItems.find(ele => ele.label === "Clear"))
this.helpfulWheelItems.push({label: "Clear", icon: "imgsrc:data:image/svg+xml;base64," + window.btoa(base64Encode(CLEARBUTTON)), display: true, fn: () => this._allClear(false)});

Expand Down Expand Up @@ -6028,6 +6057,7 @@ class Activity {
// end the drag on navbar
document.getElementById("toolbars").addEventListener("mouseover", () => {this.isDragging = false;});


this.deleteMultipleBlocks = () => {
if (this.blocks.selectionModeOn) {
const blocksArray = this.blocks.selectedBlocks;
Expand Down Expand Up @@ -6090,13 +6120,15 @@ class Activity {
};



this.selectMode = () => {
this.moving = false;
this.isSelecting = !this.isSelecting;
(this.isSelecting) ? this.textMsg(_("Select is enabled.")) : this.textMsg(_("Select is disabled."));
docById("helpfulWheelDiv").style.display = "none";
};


this._create2Ddrag = () => {
this.dragArea = {};
this.selectedBlocks = [];
Expand Down Expand Up @@ -6590,8 +6622,6 @@ class Activity {
that.errorMsg(
_("Cannot load project from the file. Please check the file type.")
);
} else if (files[0].type === "audio/wav") {
this.makeSamplerWidget(files[0].name, reader.result);
} else {
const cleanData = rawData.replace("\n", " ");
let obj;
Expand Down Expand Up @@ -6708,12 +6738,6 @@ class Activity {
abcReader.readAsText(files[0]);
return;
}

if (files[0].type === "audio/wav") {
reader.readAsDataURL(files[0]);
return;
}

reader.readAsText(files[0]);
reader.readAsText(files[0]);
window.scroll(0, 0);
Expand Down
33 changes: 26 additions & 7 deletions js/languagebox.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,15 +59,11 @@
* @returns {void}
*/
ja_onclick() {
this._language = "ja";
this.activity.storage.kanaPreference = "kanji";
this.hide();
this._language = "ja";

Check warning on line 62 in js/languagebox.js

View workflow job for this annotation

GitHub Actions / Lint updated JavaScript files with ESLint

Expected indentation of 8 spaces but found 4
this.activity.storage.kanaPreference = "kanji";

Check warning on line 63 in js/languagebox.js

View workflow job for this annotation

GitHub Actions / Lint updated JavaScript files with ESLint

Expected indentation of 8 spaces but found 4
this.hide();

Check warning on line 64 in js/languagebox.js

View workflow job for this annotation

GitHub Actions / Lint updated JavaScript files with ESLint

Expected indentation of 8 spaces but found 4
}

/**
* @public
* @returns {void}
*/
kana_onclick() {
this._language = "ja";
this.activity.storage.kanaPreference = "kana";
Expand Down Expand Up @@ -154,6 +150,15 @@
this.hide();
}

/**
* @public
* @returns {void}
*/
tr_onclick() {
this._language = "tr";
this.hide();
}

/**
* @public
* @returns {void}
Expand Down Expand Up @@ -217,6 +222,7 @@
th: "รีเฟรชเบราเซอร์เพื่อเปลี่ยนการตั้งค่าภาษาของคุณ",
hi: "अपनी भाषा की वरीयता बदलने के लिए अपना ब्राउज़र ताज़ा करें",
te: "మీ భాష ప్రాధాన్యతను మార్చడానికి మీ బ్రౌజర్‌ని రిఫ్రెష్ చేయండి.",
tr: "dil tercihinizi değiştirmek için tarayıcınızı yenileyin",
ibo: "Mee ka nchọgharị gị gbanwee mmasị asụsụ gị.",
ar: "حدث المتصفح لتغيير تفضيلات اللغة.",
he: "רענן את הדפדפן כדי לשנות את העדפת השפה שלך.",
Expand All @@ -226,13 +232,26 @@
ur:"اپنی زبان کی ترجیح کو تبدیل کرنے کے لئے اپنے براؤزر کو تازہ دم کریں۔"
};
if (localStorage.getItem("languagePreference") === this._language) {
if(this._language.includes("ja"))
{
this._language=this._language.split("-")[0];
}

localStorage.setItem("languagePreference",this._language);
this.activity.textMsg(_("Music Blocks is already set to this language."));
}
else{
this.activity.storage.languagePreference = this._language;

if (this._language === "ja" && this.activity.storage.kanaPreference === "kana") {
this.activity.textMsg(MSGPrefix + MSG["kana"] + MSGSuffix);
} else {

if(this._language.includes("ja"))
{
this._language=this._language.split("-")[0];
}

this.activity.textMsg(MSGPrefix + MSG[this._language] + MSGSuffix);
}
}
Expand Down
Loading