test_i18n.py 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. """
  2. Tests for the Internationalization (i18n) UI component (Story 7.12).
  3. """
  4. import pytest
  5. from pathlib import Path
  6. from PyQt6.QtWidgets import QApplication
  7. from PyQt6.QtCore import QLocale
  8. from src.ui.i18n import (
  9. SupportedLanguage,
  10. TRANSLATIONS,
  11. I18nManager,
  12. get_i18n_manager,
  13. t,
  14. )
  15. @pytest.fixture
  16. def app(qtbot):
  17. """Create QApplication for tests."""
  18. return QApplication.instance() or QApplication([])
  19. @pytest.fixture
  20. def clean_manager():
  21. """Create a fresh I18nManager for testing."""
  22. import src.ui.i18n as i18n
  23. i18n._i18n_manager_instance = None
  24. manager = I18nManager()
  25. return manager
  26. class TestSupportedLanguage:
  27. """Tests for SupportedLanguage enum."""
  28. def test_language_values(self):
  29. """Test SupportedLanguage enum values."""
  30. assert SupportedLanguage.CHINESE_SIMPLIFIED.code == "zh_CN"
  31. assert SupportedLanguage.ENGLISH.code == "en_US"
  32. def test_native_names(self):
  33. """Test native language names."""
  34. assert SupportedLanguage.CHINESE_SIMPLIFIED.native_name == "简体中文"
  35. assert SupportedLanguage.ENGLISH.native_name == "English"
  36. def test_english_names(self):
  37. """Test English language names."""
  38. assert SupportedLanguage.CHINESE_SIMPLIFIED.english_name == "Chinese (Simplified)"
  39. assert SupportedLanguage.ENGLISH.english_name == "English"
  40. def test_from_locale_chinese(self):
  41. """Test getting language from Chinese locale."""
  42. locale = QLocale(QLocale.Language.Chinese, QLocale.Country.China)
  43. lang = SupportedLanguage.from_locale(locale)
  44. assert lang == SupportedLanguage.CHINESE_SIMPLIFIED
  45. def test_from_locale_english(self):
  46. """Test getting language from English locale."""
  47. locale = QLocale(QLocale.Language.English, QLocale.Country.UnitedStates)
  48. lang = SupportedLanguage.from_locale(locale)
  49. assert lang == SupportedLanguage.ENGLISH
  50. class TestTranslations:
  51. """Tests for translation strings."""
  52. def test_translations_structure(self):
  53. """Test that translations have proper structure."""
  54. for key, translations in TRANSLATIONS.items():
  55. assert "zh_CN" in translations
  56. assert "en_US" in translations
  57. def test_common_translations_exist(self):
  58. """Test that common UI strings have translations."""
  59. # Menu items
  60. assert "menu.file" in TRANSLATIONS
  61. assert "menu.edit" in TRANSLATIONS
  62. # Buttons
  63. assert "button.ok" in TRANSLATIONS
  64. assert "button.cancel" in TRANSLATIONS
  65. # Status
  66. assert "status.ready" in TRANSLATIONS
  67. assert "status.translating" in TRANSLATIONS
  68. def test_translation_values(self):
  69. """Test specific translation values."""
  70. assert TRANSLATIONS["button.ok"]["zh_CN"] == "确定"
  71. assert TRANSLATIONS["button.ok"]["en_US"] == "OK"
  72. assert TRANSLATIONS["button.cancel"]["zh_CN"] == "取消"
  73. assert TRANSLATIONS["button.cancel"]["en_US"] == "Cancel"
  74. def test_formatted_translations(self):
  75. """Test translations with format placeholders."""
  76. assert "{count}" in TRANSLATIONS["msg.added_files"]["zh_CN"]
  77. assert "{count}" in TRANSLATIONS["msg.added_files"]["en_US"]
  78. assert "{path}" in TRANSLATIONS["msg.file_not_found"]["zh_CN"]
  79. assert "{path}" in TRANSLATIONS["msg.file_not_found"]["en_US"]
  80. class TestI18nManager:
  81. """Tests for I18nManager."""
  82. def test_initialization(self, clean_manager):
  83. """Test manager initialization."""
  84. assert isinstance(clean_manager, I18nManager)
  85. # Default to Chinese
  86. assert clean_manager.current_language == SupportedLanguage.CHINESE_SIMPLIFIED
  87. def test_current_language(self, clean_manager):
  88. """Test current_language property."""
  89. assert clean_manager.current_language == SupportedLanguage.CHINESE_SIMPLIFIED
  90. clean_manager.set_language(SupportedLanguage.ENGLISH)
  91. assert clean_manager.current_language == SupportedLanguage.ENGLISH
  92. def test_current_locale(self, clean_manager):
  93. """Test current_locale property."""
  94. locale = clean_manager.current_locale
  95. assert isinstance(locale, QLocale)
  96. def test_set_language_chinese(self, clean_manager):
  97. """Test setting Chinese language."""
  98. clean_manager.set_language(SupportedLanguage.CHINESE_SIMPLIFIED)
  99. assert clean_manager.current_language == SupportedLanguage.CHINESE_SIMPLIFIED
  100. def test_set_language_english(self, clean_manager):
  101. """Test setting English language."""
  102. clean_manager.set_language(SupportedLanguage.ENGLISH)
  103. assert clean_manager.current_language == SupportedLanguage.ENGLISH
  104. def test_translate_chinese(self, clean_manager):
  105. """Test translation to Chinese."""
  106. clean_manager.set_language(SupportedLanguage.CHINESE_SIMPLIFIED)
  107. text = clean_manager.translate("button.ok")
  108. assert text == "确定"
  109. text = clean_manager.translate("button.cancel")
  110. assert text == "取消"
  111. def test_translate_english(self, clean_manager):
  112. """Test translation to English."""
  113. clean_manager.set_language(SupportedLanguage.ENGLISH)
  114. text = clean_manager.translate("button.ok")
  115. assert text == "OK"
  116. text = clean_manager.translate("button.cancel")
  117. assert text == "Cancel"
  118. def test_translate_with_format(self, clean_manager):
  119. """Test translation with format arguments."""
  120. clean_manager.set_language(SupportedLanguage.ENGLISH)
  121. text = clean_manager.translate("msg.added_files", count=5)
  122. assert "5" in text
  123. assert "file" in text
  124. def test_translate_unknown_key(self, clean_manager):
  125. """Test translating unknown key returns key itself."""
  126. text = clean_manager.translate("unknown.key")
  127. assert text == "unknown.key"
  128. def test_shorthand_t(self, clean_manager):
  129. """Test shorthand t() function."""
  130. clean_manager.set_language(SupportedLanguage.CHINESE_SIMPLIFIED)
  131. text = clean_manager.t("button.ok")
  132. assert text == "确定"
  133. def test_language_changed_signal(self, app, clean_manager, qtbot):
  134. """Test that language_changed signal is emitted."""
  135. signals = []
  136. def on_language_changed(lang):
  137. signals.append(lang)
  138. clean_manager.language_changed.connect(on_language_changed)
  139. clean_manager.set_language(SupportedLanguage.ENGLISH)
  140. assert len(signals) == 1
  141. assert signals[0] == SupportedLanguage.ENGLISH
  142. def test_retranslate_callback(self, clean_manager):
  143. """Test retranslate callbacks are called on language change."""
  144. callback_calls = []
  145. def callback():
  146. callback_calls.append(True)
  147. clean_manager.register_retranslate_callback(callback)
  148. # Change language
  149. clean_manager.set_language(SupportedLanguage.ENGLISH)
  150. assert len(callback_calls) == 1
  151. # Change again
  152. clean_manager.set_language(SupportedLanguage.CHINESE_SIMPLIFIED)
  153. assert len(callback_calls) == 2
  154. def test_get_language_menu(self, clean_manager):
  155. """Test getting language selection menu."""
  156. menu = clean_manager.get_language_menu()
  157. assert menu is not None
  158. # Should have actions for all languages
  159. assert menu.isEmpty() is False
  160. class TestModuleLevelFunctions:
  161. """Tests for module-level functions."""
  162. def test_get_i18n_manager(self):
  163. """Test getting i18n manager singleton."""
  164. import src.ui.i18n as i18n
  165. i18n._i18n_manager_instance = None
  166. manager = get_i18n_manager()
  167. assert manager is not None
  168. assert isinstance(manager, I18nManager)
  169. def test_t_function(self):
  170. """Test module-level t() function."""
  171. import src.ui.i18n as i18n
  172. i18n._i18n_manager_instance = None
  173. i18n._i18n_manager_instance = I18nManager()
  174. # Set to Chinese
  175. i18n._i18n_manager_instance.set_language(SupportedLanguage.CHINESE_SIMPLIFIED)
  176. text = t("button.ok")
  177. assert text == "确定"
  178. def test_singleton_persistence(self):
  179. """Test that singleton returns same instance."""
  180. import src.ui.i18n as i18n
  181. i18n._i18n_manager_instance = None
  182. manager1 = get_i18n_manager()
  183. manager2 = get_i18n_manager()
  184. assert manager1 is manager2
  185. class TestTranslationCoverage:
  186. """Tests to ensure translation coverage."""
  187. def test_all_categories(self):
  188. """Test translations for all UI categories."""
  189. # Menus
  190. assert any(key.startswith("menu.") for key in TRANSLATIONS)
  191. # Actions
  192. assert any(key.startswith("action.") for key in TRANSLATIONS)
  193. # Buttons
  194. assert any(key.startswith("button.") for key in TRANSLATIONS)
  195. # Labels
  196. assert any(key.startswith("label.") for key in TRANSLATIONS)
  197. # Status
  198. assert any(key.startswith("status.") for key in TRANSLATIONS)
  199. # Dialogs
  200. assert any(key.startswith("dialog.") for key in TRANSLATIONS)
  201. # Messages
  202. assert any(key.startswith("msg.") for key in TRANSLATIONS)
  203. def test_glossary_translations(self):
  204. """Test glossary-related translations."""
  205. assert "glossary.add_term" in TRANSLATIONS
  206. assert "glossary.character" in TRANSLATIONS
  207. assert "glossary.skill" in TRANSLATIONS
  208. def test_log_translations(self):
  209. """Test log-related translations."""
  210. assert "log.level_info" in TRANSLATIONS
  211. assert "log.level_error" in TRANSLATIONS
  212. def test_theme_translations(self):
  213. """Test theme-related translations."""
  214. assert "theme.light" in TRANSLATIONS
  215. assert "theme.dark" in TRANSLATIONS