Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
2180c36
i18n: wrap ~76 user-facing strings with tr() across Schedules tab
Ski90Moo May 4, 2026
aff35f2
ci: trigger build
Ski90Moo May 4, 2026
b1c5733
i18n: add comprehensive Spanish translation proof-of-concept (issue #…
Ski90Moo May 9, 2026
bce5a70
test: add GTest suite for i18n translation (issue #680)
Ski90Moo May 9, 2026
cdf9504
i18n: add full translations for 18 languages + 4 new languages (issue…
Ski90Moo May 23, 2026
d239658
i18n: fix remaining macumber/Copilot review issues (issue #680)
Ski90Moo May 23, 2026
7eb3da0
chore: untrack Qt-generated moc_*.cpp files
Ski90Moo May 23, 2026
f03e2ee
chore: ignore Qt-generated moc_*.cpp files
Ski90Moo May 23, 2026
4808ad1
test: add IddCoverageForAllFields test (issue #680)
Ski90Moo May 23, 2026
59cad72
test: check IDD coverage across all language .ts files (issue #680)
Ski90Moo May 23, 2026
b2a2d08
docs: update PR 873 review responses
Ski90Moo May 23, 2026
7e3be47
ci: add GitHub Actions translation drift check (issue #680)
Ski90Moo May 23, 2026
518b74f
docs: update PR 873 responses with translation CI details
Ski90Moo May 23, 2026
d74d92b
i18n: wrap hardcoded 'Merging Models' dialog titles with tr() (issue …
Ski90Moo May 23, 2026
05a157e
refactor: replace QCoreApplication::translate lambda/verbose calls wi…
Ski90Moo May 23, 2026
8d3ebce
fix: add missing QStringList include to Translation_GTest.cpp
Ski90Moo May 24, 2026
dce7d74
chore: remove internal PR review tracking document
Ski90Moo May 24, 2026
37c8d6b
i18n: add dialog translations missing from PR #873
Ski90Moo May 26, 2026
95999ac
Merge branch 'openstudiocoalition:develop' into feat/i18n
Ski90Moo May 29, 2026
f498e3d
i18n: add tr() strings for 'Units Conversion' DView dialog (issue #680)
Ski90Moo May 30, 2026
69ba3d8
docs: add TRANSLATION_WORKFLOW.md with full lupdate pipeline
Ski90Moo May 30, 2026
5ecd60b
i18n: wrap ~76 user-facing strings with tr() across Schedules tab
Ski90Moo May 4, 2026
0eb98ab
ci: trigger build
Ski90Moo May 4, 2026
e1325c6
i18n: add comprehensive Spanish translation proof-of-concept (issue #…
Ski90Moo May 9, 2026
7120cec
test: add GTest suite for i18n translation (issue #680)
Ski90Moo May 9, 2026
bb6971b
i18n: add full translations for 18 languages + 4 new languages (issue…
Ski90Moo May 23, 2026
bc632ee
i18n: fix remaining macumber/Copilot review issues (issue #680)
Ski90Moo May 23, 2026
1cb2d14
chore: untrack Qt-generated moc_*.cpp files
Ski90Moo May 23, 2026
c459d59
chore: ignore Qt-generated moc_*.cpp files
Ski90Moo May 23, 2026
022173d
test: add IddCoverageForAllFields test (issue #680)
Ski90Moo May 23, 2026
e89d786
test: check IDD coverage across all language .ts files (issue #680)
Ski90Moo May 23, 2026
f5f991b
docs: update PR 873 review responses
Ski90Moo May 23, 2026
1443729
ci: add GitHub Actions translation drift check (issue #680)
Ski90Moo May 23, 2026
7914996
docs: update PR 873 responses with translation CI details
Ski90Moo May 23, 2026
b2cbc1c
i18n: wrap hardcoded 'Merging Models' dialog titles with tr() (issue …
Ski90Moo May 23, 2026
72a4889
refactor: replace QCoreApplication::translate lambda/verbose calls wi…
Ski90Moo May 23, 2026
9b1953d
fix: add missing QStringList include to Translation_GTest.cpp
Ski90Moo May 24, 2026
3edfdb7
chore: remove internal PR review tracking document
Ski90Moo May 24, 2026
a1fcdfd
i18n: add dialog translations missing from PR #873
Ski90Moo May 26, 2026
55a10ef
i18n: add tr() strings for 'Units Conversion' DView dialog (issue #680)
Ski90Moo May 30, 2026
ecb06de
docs: add TRANSLATION_WORKFLOW.md with full lupdate pipeline
Ski90Moo May 30, 2026
4865c9e
fix: restore white menu bar background after language switch (Qt 6)
Ski90Moo May 31, 2026
dbd5fd5
fix: restore white menu bar background after language switch (Qt 6)
Ski90Moo May 31, 2026
0585d1f
style: apply clang-format 18.1.3 to PR-touched files
Ski90Moo Jun 1, 2026
3bce8c5
feat: add missing translation helper scripts
Ski90Moo Jun 1, 2026
4c22eb1
fix: register 4 new languages in CMakeLists and clean up .gitignore
Ski90Moo Jun 1, 2026
31f9508
fix: address jmarrec review — display, whitespace, newlines, loop dialog
Ski90Moo Jun 1, 2026
b70dc87
docs: add note to TRANSLATION_WORKFLOW.md clarifying AI-assisted scope
Ski90Moo Jun 1, 2026
3e1c01c
fix: correct de.pak typo in German WebEngine locales block
Ski90Moo Jun 2, 2026
d3b7090
feat: improve translation tooling from Danish language test
Ski90Moo Jun 2, 2026
f6642ba
docs: document adding a new language in TRANSLATION_WORKFLOW.md
Ski90Moo Jun 2, 2026
5ac6599
fix: normalize XML escaping in all 18 .ts translation files
Ski90Moo Jun 2, 2026
ac13885
feat: add definition-aware output variable retranslation pipeline
Ski90Moo Jun 4, 2026
2578068
refactor: consolidate translation tooling in translations/ directory
Ski90Moo Jun 6, 2026
60af71b
fix: restore human translations and update ES/FR with improved machin…
Ski90Moo Jun 6, 2026
eea0cab
feat: complete definition-aware retranslation of all 18 languages
Ski90Moo Jun 10, 2026
9804a2f
Merge remote-tracking branch 'origin/develop' into develop
Ski90Moo Jun 10, 2026
e56fd33
Merge remote-tracking branch 'Ski90Moo/develop' into ski90moo-develop
macumber Jun 20, 2026
dbaa496
Merge remote-tracking branch 'origin/develop' into ski90moo-develop
macumber Jun 20, 2026
4becf4c
Add retries for code signing
macumber Jun 21, 2026
7b68e15
Fix other mac issue
macumber Jun 21, 2026
f5bd8b8
Another try
macumber Jun 21, 2026
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
46 changes: 46 additions & 0 deletions .github/workflows/translation_check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: Translation Check

on:
push:
branches: [ master, develop ]
pull_request:
branches: [ master, develop ]

jobs:
check:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Install Qt6 lupdate
run: |
sudo apt-get update -q
sudo apt-get install -y qt6-l10n-tools

- name: Run lupdate against all .ts files
run: |
# -locations none: suppress line-number comments so only genuine
# string additions/removals cause a git diff change.
lupdate6 src/ \
-ts translations/OpenStudioApp_*.ts \
-extensions cpp,hpp \
-locations none \
2>&1 | tee lupdate_output.txt

- name: Detect vanished or new unfinished strings
run: |
# If lupdate changed any .ts file, strings were either added to source
# (new type="unfinished" stubs) or removed from source (type="obsolete").
# Either way the commit that introduced the drift should not merge until
# the .ts files are updated and translations are provided.
python3 ci/check_translations.py

- name: Upload artifacts on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: translation-check-${{ github.sha }}
path: |
lupdate_output.txt
translation_check.patch
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ profile/

*.sublime-workspace

# Qt-generated files
src/openstudio_lib/moc_*.cpp

*.sublime-project
cmake-build-debug

Expand Down
27 changes: 23 additions & 4 deletions CMake/CPackSignAndNotarizeDmg.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -32,22 +32,41 @@ if(APPLE AND CPACK_GENERATOR STREQUAL "IFW")
message(FATAL_ERROR "CPACK_CODESIGNING_MACOS_IDENTIFIER is required, this should not have happened")
endif()

# CPACK_PACKAGE_FILES can contain relative paths; resolve them to absolute so
# EXISTS checks and codesign/notarize steps work regardless of cwd.
set(_RESOLVED_PACKAGE_FILES "")
foreach(CPACK_PACKAGE_FILE ${CPACK_PACKAGE_FILES})
if(NOT IS_ABSOLUTE "${CPACK_PACKAGE_FILE}")
set(CPACK_PACKAGE_FILE "${CPACK_TOPLEVEL_DIRECTORY}/${CPACK_PACKAGE_FILE}")
endif()
if(NOT EXISTS "${CPACK_PACKAGE_FILE}")
message(STATUS "File does not exist: ${CPACK_PACKAGE_FILE}")

# wait in case file has not been written to disk yet
execute_process(COMMAND ${CMAKE_COMMAND} -E sleep 10)

if(NOT EXISTS "${CPACK_PACKAGE_FILE}")
message(FATAL_ERROR "File still does not exist: ${CPACK_PACKAGE_FILE}")
# The IFW generator can produce a DMG whose name differs from the
# computed CPACK_PACKAGE_FILE (e.g. different arch/OS suffix tokens).
# Fall back to a glob of the IFW output directory.
get_filename_component(_IFW_DIR "${CPACK_PACKAGE_FILE}" DIRECTORY)
file(GLOB _DMG_CANDIDATES "${_IFW_DIR}/OpenStudioApplication-*.dmg")
list(LENGTH _DMG_CANDIDATES _N_CANDIDATES)
if (_N_CANDIDATES EQUAL 0)
message(FATAL_ERROR "File still does not exist and no DMG found in ${_IFW_DIR}: ${CPACK_PACKAGE_FILE}")
elseif (_N_CANDIDATES GREATER 1)
message(FATAL_ERROR "Multiple DMGs found in ${_IFW_DIR}; cannot determine which to sign:\n${_DMG_CANDIDATES}")
else ()
list(GET _DMG_CANDIDATES 0 CPACK_PACKAGE_FILE)
message(STATUS "DMG name mismatch — resolved to '${CPACK_PACKAGE_FILE}'")
endif ()
endif()

endif()
list(APPEND _RESOLVED_PACKAGE_FILES "${CPACK_PACKAGE_FILE}")
endforeach()

codesign_files_macos(
FILES ${CPACK_PACKAGE_FILES}
FILES ${_RESOLVED_PACKAGE_FILES}
SIGNING_IDENTITY ${CPACK_CODESIGNING_DEVELOPPER_ID_APPLICATION}
IDENTIFIER "${CPACK_CODESIGNING_MACOS_IDENTIFIER}.DmgInstaller"
FORCE
Expand All @@ -56,7 +75,7 @@ if(APPLE AND CPACK_GENERATOR STREQUAL "IFW")

if(CPACK_CODESIGNING_NOTARY_PROFILE_NAME)
notarize_files_macos(
FILES ${CPACK_PACKAGE_FILES}
FILES ${_RESOLVED_PACKAGE_FILES}
NOTARY_PROFILE_NAME ${CPACK_CODESIGNING_NOTARY_PROFILE_NAME}
STAPLE
VERIFY
Expand Down
22 changes: 19 additions & 3 deletions CMake/CodeSigning.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -223,9 +223,25 @@ function(codesign_files_macos)
message(FATAL_ERROR "Can't sign ${path}, no file exists at that path.")
endif ()

execute_process(COMMAND ${cmd} "${path}" RESULT_VARIABLE res)
if (NOT res EQUAL 0)
message(FATAL_ERROR "Can't sign ${path}, command '${cmd}' failed")
# Retry codesign up to 5 times with a delay, since Apple's timestamp server can be flaky
set(_CODESIGN_SUCCESS FALSE)
foreach(_ATTEMPT RANGE 1 5)
execute_process(
COMMAND ${cmd} "${path}"
RESULT_VARIABLE _CODESIGN_RESULT
OUTPUT_VARIABLE _CODESIGN_OUTPUT
ERROR_VARIABLE _CODESIGN_ERROR
)
if (_CODESIGN_RESULT EQUAL 0)
set(_CODESIGN_SUCCESS TRUE)
break()
endif ()
message(WARNING "codesign attempt ${_ATTEMPT} failed: ${_CODESIGN_ERROR}. Retrying in 15s...")
execute_process(COMMAND ${CMAKE_COMMAND} -E sleep 15)
endforeach()

if (NOT _CODESIGN_SUCCESS)
message(FATAL_ERROR "Can't sign ${path}, command '${cmd}' failed after 5 attempts:\n${_CODESIGN_ERROR}")
endif ()
endforeach()

Expand Down
73 changes: 73 additions & 0 deletions ci/check_translations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#!/usr/bin/env python3
"""
Called by translation_check.yml after lupdate has run.

Checks whether lupdate changed any OpenStudioApp_*.ts file by running
`git diff translations/`. If there are changes it means:

- type="unfinished" stubs were ADDED → new tr() calls in source with no
translation yet. Fix: run translate_skeleton.py / translate_all_languages.py.

- type="obsolete" entries were ADDED → tr() calls removed from source but
still present as dead entries in the .ts files. Fix: run
`lupdate -no-obsolete` and commit the cleaned-up .ts files.

If the diff is empty, all .ts files are in sync with the C++ source.
Exits with code 1 if drift is detected so CI fails.
"""

import re
import subprocess
import sys


def run(cmd: list[str]) -> str:
return subprocess.check_output(cmd, text=True)


def main() -> int:
# Produce a diff of everything lupdate may have touched.
diff = run(["git", "diff", "translations/"])

if not diff:
print("All .ts files are in sync with the C++ source. ✓")
return 0

# Save patch as artifact for inspection.
with open("translation_check.patch", "w") as f:
f.write(diff)

# Classify what changed so the error message is actionable.
added_unfinished = len(re.findall(r'^\+.*type="unfinished"', diff, re.MULTILINE))
added_obsolete = len(re.findall(r'^\+.*type="obsolete"', diff, re.MULTILINE))

# Summarise changed files.
stat = run(["git", "diff", "--stat", "translations/"])
print("lupdate changed the following .ts files:")
print(stat)

if added_unfinished:
print(f" {added_unfinished} new empty unfinished string(s) detected.")
print(" These are new tr() calls in C++ source that have no translation yet.")
print(" Fix: run translate_skeleton.py (Spanish) or translate_all_languages.py")
print(" to batch-translate the new strings, then commit the updated .ts files.")
print()

if added_obsolete:
print(f" {added_obsolete} new obsolete string(s) detected.")
print(" These are tr() calls that were removed from C++ source.")
print(" Fix: run `lupdate src/ -no-obsolete -ts translations/OpenStudioApp_*.ts`")
print(" then commit the cleaned-up .ts files.")
print()

if not added_unfinished and not added_obsolete:
# Location comments or other minor changes — still fail so the dev is aware.
print(" .ts files changed in an unexpected way (not unfinished/obsolete).")
print(" Review translation_check.patch for details.")

print("Translation check FAILED. Commit the updated .ts files to fix CI.")
return 1


if __name__ == "__main__":
sys.exit(main())
28 changes: 26 additions & 2 deletions src/model_editor/InspectorGadget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@
#include <QIntValidator>
#include <QLabel>
#include <QLineEdit>
#include <QCoreApplication>
#include <QLocale>
#include <QPainter>
#include <QPushButton>
#include <QRadioButton>
Expand All @@ -53,6 +55,28 @@
#include <QVBoxLayout>

using namespace openstudio;

// Returns "Traducción (Field Name)" when the app locale is non-English and
// a translation exists in the "IDD" context of the loaded .qm file;
// otherwise returns the plain English field name.
//
// When translated, returns an HTML-formatted string with the translated name
// on the first line and the original English name in smaller gray italics on
// the second line — so a non-English speaker can cross-reference EnergyPlus
// documentation or .idf/.osm files without switching the application language.
//
// To add or refine translations: add entries to the IDD context in
// OpenStudioApp_<lang>.ts and recompile with lrelease — no C++ changes needed.
static QString iddFieldDisplayName(const std::string& englishName) {
const QString qname = QString::fromStdString(englishName);
if (QLocale().language() != QLocale::English) {
const QString translated = QCoreApplication::translate("IDD", englishName.c_str());
if (translated != qname) {
return translated.toHtmlEscaped() + "<br><i style='color:gray'>" + qname.toHtmlEscaped() + "</i>";
}
}
return qname;
}
using namespace openstudio::model;

const char* InspectorGadget::s_indexSlotName = "indexSlot";
Expand Down Expand Up @@ -507,7 +531,7 @@ void InspectorGadget::layoutText(QVBoxLayout* layout, QWidget* parent, openstudi
auto* vbox = new QVBoxLayout();
frame->setLayout(vbox);

auto* label = new QLabel(QString(name.c_str()), parent);
auto* label = new QLabel(iddFieldDisplayName(name), parent);
label->setWordWrap(true);
vbox->addWidget(label);

Expand Down Expand Up @@ -751,7 +775,7 @@ void InspectorGadget::layoutComboBox(QVBoxLayout* layout, QWidget* parent, opens
auto* frame = new QFrame(parent);
auto* vbox = new QVBoxLayout();
frame->setLayout(vbox);
auto* label = new QLabel(QString(name.c_str()), parent);
auto* label = new QLabel(iddFieldDisplayName(name), parent);
label->setWordWrap(true);

QComboBox* combo = new IGComboBox(parent);
Expand Down
1 change: 1 addition & 0 deletions src/openstudio_app/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,7 @@ set(${target_name}_test_src
test/OpenStudioAppFixture.hpp
test/OpenStudioAppFixture.cpp
test/Resources_GTest.cpp
test/Translation_GTest.cpp
test/Units_GTest.cpp
)

Expand Down
6 changes: 3 additions & 3 deletions src/openstudio_app/ExternalToolsDialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,18 @@ ExternalToolsDialog::ExternalToolsDialog(const openstudio::path& t_dviewPath) :
mainLayout->setColumnMinimumWidth(1, 400);

int row = 0;
auto* title = new QLabel("Change External Tools");
auto* title = new QLabel(tr("Change External Tools"));
title->setObjectName("H1");
mainLayout->addWidget(title, row, 0, 1, 3);

// Dview
++row;
mainLayout->addWidget(new QLabel("Path to DView"), row, 0);
mainLayout->addWidget(new QLabel(tr("Path to DView")), row, 0);

m_dviewPathLineEdit->setText(QString::fromStdString(toString(t_dviewPath)));
mainLayout->addWidget(m_dviewPathLineEdit, row, 1);

auto* changeDviewButton = new QPushButton("Change");
auto* changeDviewButton = new QPushButton(tr("Change"));
connect(changeDviewButton, &QPushButton::clicked, this, [this] { ExternalToolsDialog::onChangeClicked(m_dviewPathLineEdit, "DView"); });
mainLayout->addWidget(changeDviewButton, row, 2);

Expand Down
8 changes: 4 additions & 4 deletions src/openstudio_app/LibraryDialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ LibraryDialog::LibraryDialog(const std::vector<openstudio::path>& paths, const s
auto* mainLayout = new QVBoxLayout();
setLayout(mainLayout);

auto* title = new QLabel("Change Default Libraries");
auto* title = new QLabel(tr("Change Default Libraries"));
title->setObjectName("H1");
mainLayout->addWidget(title);

Expand All @@ -39,17 +39,17 @@ LibraryDialog::LibraryDialog(const std::vector<openstudio::path>& paths, const s
auto* addRemoveLayout = new QHBoxLayout();
mainLayout->addLayout(addRemoveLayout);

auto* add = new QPushButton("Add");
auto* add = new QPushButton(tr("Add"));
addRemoveLayout->addWidget(add, 0);
connect(add, &QPushButton::clicked, this, &LibraryDialog::onAdd);

auto* remove = new QPushButton("Remove");
auto* remove = new QPushButton(tr("Remove"));
addRemoveLayout->addWidget(remove, 0);
connect(remove, &QPushButton::clicked, this, &LibraryDialog::onRemove);

addRemoveLayout->addStretch(1);

auto* restore = new QPushButton("Restore Defaults");
auto* restore = new QPushButton(tr("Restore Defaults"));
addRemoveLayout->addWidget(restore, 0, Qt::AlignRight);
connect(restore, &QPushButton::clicked, this, &LibraryDialog::onRestore);

Expand Down
9 changes: 6 additions & 3 deletions src/openstudio_app/OpenStudioApp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1478,10 +1478,13 @@ bool OpenStudioApp::switchLanguage(const QString& rLanguage) {
}
}

if (m_currLang == QString("fa")) {
// Force Right to Left display. This is not done automatically like in Arabic because qt itself isn't translated (no qt_fa.qm / qt_base_fa.qm)
qDebug() << "Forcing RightToLeft";
if (m_currLang == "ar" || m_currLang == "fa" || m_currLang == "he") {
// Force Right to Left display for RTL languages that don't have qt_XX.qm files
// to trigger it automatically.
qDebug() << "Forcing RightToLeft for" << m_currLang;
OpenStudioApp::setLayoutDirection(Qt::RightToLeft);
} else {
OpenStudioApp::setLayoutDirection(Qt::LeftToRight);
}

return true;
Expand Down
Loading
Loading