psychopy 2025.1.0__py3-none-any.whl → 2025.2.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of psychopy might be problematic. Click here for more details.

Files changed (226) hide show
  1. psychopy/VERSION +1 -1
  2. psychopy/alerts/alertsCatalogue/4810.yaml +19 -0
  3. psychopy/alerts/alertsCatalogue/alertCategories.yaml +4 -0
  4. psychopy/alerts/alertsCatalogue/alertmsg.py +15 -1
  5. psychopy/alerts/alertsCatalogue/generateAlertmsg.py +2 -2
  6. psychopy/app/Resources/classic/add_many.png +0 -0
  7. psychopy/app/Resources/classic/add_many@2x.png +0 -0
  8. psychopy/app/Resources/classic/devices.png +0 -0
  9. psychopy/app/Resources/classic/devices@2x.png +0 -0
  10. psychopy/app/Resources/classic/photometer.png +0 -0
  11. psychopy/app/Resources/classic/photometer@2x.png +0 -0
  12. psychopy/app/Resources/dark/add_many.png +0 -0
  13. psychopy/app/Resources/dark/add_many@2x.png +0 -0
  14. psychopy/app/Resources/dark/devices.png +0 -0
  15. psychopy/app/Resources/dark/devices@2x.png +0 -0
  16. psychopy/app/Resources/dark/photometer.png +0 -0
  17. psychopy/app/Resources/dark/photometer@2x.png +0 -0
  18. psychopy/app/Resources/light/add_many.png +0 -0
  19. psychopy/app/Resources/light/add_many@2x.png +0 -0
  20. psychopy/app/Resources/light/devices.png +0 -0
  21. psychopy/app/Resources/light/devices@2x.png +0 -0
  22. psychopy/app/Resources/light/photometer.png +0 -0
  23. psychopy/app/Resources/light/photometer@2x.png +0 -0
  24. psychopy/app/_psychopyApp.py +35 -13
  25. psychopy/app/builder/builder.py +88 -35
  26. psychopy/app/builder/dialogs/__init__.py +69 -220
  27. psychopy/app/builder/dialogs/dlgsCode.py +29 -8
  28. psychopy/app/builder/dialogs/paramCtrls.py +1468 -904
  29. psychopy/app/builder/validators.py +25 -17
  30. psychopy/app/coder/coder.py +12 -1
  31. psychopy/app/coder/repl.py +5 -2
  32. psychopy/app/colorpicker/__init__.py +1 -1
  33. psychopy/app/deviceManager/__init__.py +1 -0
  34. psychopy/app/deviceManager/addDialog.py +218 -0
  35. psychopy/app/deviceManager/dialog.py +185 -0
  36. psychopy/app/deviceManager/panel.py +191 -0
  37. psychopy/app/deviceManager/utils.py +60 -0
  38. psychopy/app/idle.py +7 -0
  39. psychopy/app/locale/ar_001/LC_MESSAGE/messages.mo +0 -0
  40. psychopy/app/locale/ar_001/LC_MESSAGE/messages.po +12695 -10592
  41. psychopy/app/locale/cs_CZ/LC_MESSAGE/messages.mo +0 -0
  42. psychopy/app/locale/cs_CZ/LC_MESSAGE/messages.po +10199 -24
  43. psychopy/app/locale/da_DK/LC_MESSAGE/messages.mo +0 -0
  44. psychopy/app/locale/da_DK/LC_MESSAGE/messages.po +10199 -24
  45. psychopy/app/locale/de_DE/LC_MESSAGE/messages.mo +0 -0
  46. psychopy/app/locale/de_DE/LC_MESSAGE/messages.po +11221 -9712
  47. psychopy/app/locale/el_GR/LC_MESSAGE/messages.mo +0 -0
  48. psychopy/app/locale/el_GR/LC_MESSAGE/messages.po +10200 -25
  49. psychopy/app/locale/en_NZ/LC_MESSAGE/messages.mo +0 -0
  50. psychopy/app/locale/en_NZ/LC_MESSAGE/messages.po +10200 -25
  51. psychopy/app/locale/en_US/LC_MESSAGE/messages.mo +0 -0
  52. psychopy/app/locale/en_US/LC_MESSAGE/messages.po +10195 -18
  53. psychopy/app/locale/es_CO/LC_MESSAGE/messages.mo +0 -0
  54. psychopy/app/locale/es_CO/LC_MESSAGE/messages.po +11917 -9101
  55. psychopy/app/locale/es_ES/LC_MESSAGE/messages.mo +0 -0
  56. psychopy/app/locale/es_ES/LC_MESSAGE/messages.po +11924 -9103
  57. psychopy/app/locale/es_US/LC_MESSAGE/messages.mo +0 -0
  58. psychopy/app/locale/es_US/LC_MESSAGE/messages.po +11917 -9101
  59. psychopy/app/locale/et_EE/LC_MESSAGE/messages.mo +0 -0
  60. psychopy/app/locale/et_EE/LC_MESSAGE/messages.po +11084 -9569
  61. psychopy/app/locale/fa_IR/LC_MESSAGE/messages.mo +0 -0
  62. psychopy/app/locale/fa_IR/LC_MESSAGE/messages.po +11590 -5806
  63. psychopy/app/locale/fi_FI/LC_MESSAGE/messages.mo +0 -0
  64. psychopy/app/locale/fi_FI/LC_MESSAGE/messages.po +10199 -24
  65. psychopy/app/locale/fr_FR/LC_MESSAGE/messages.mo +0 -0
  66. psychopy/app/locale/fr_FR/LC_MESSAGE/messages.po +11091 -9577
  67. psychopy/app/locale/he_IL/LC_MESSAGE/messages.mo +0 -0
  68. psychopy/app/locale/he_IL/LC_MESSAGE/messages.po +11072 -9549
  69. psychopy/app/locale/hi_IN/LC_MESSAGE/messages.mo +0 -0
  70. psychopy/app/locale/hi_IN/LC_MESSAGE/messages.po +11071 -9559
  71. psychopy/app/locale/hu_HU/LC_MESSAGE/messages.mo +0 -0
  72. psychopy/app/locale/hu_HU/LC_MESSAGE/messages.po +10200 -25
  73. psychopy/app/locale/it_IT/LC_MESSAGE/messages.mo +0 -0
  74. psychopy/app/locale/it_IT/LC_MESSAGE/messages.po +11072 -9560
  75. psychopy/app/locale/ja_JP/LC_MESSAGE/messages.mo +0 -0
  76. psychopy/app/locale/ja_JP/LC_MESSAGE/messages.po +1485 -1137
  77. psychopy/app/locale/ko_KR/LC_MESSAGE/messages.mo +0 -0
  78. psychopy/app/locale/ko_KR/LC_MESSAGE/messages.po +10199 -24
  79. psychopy/app/locale/ms_MY/LC_MESSAGE/messages.mo +0 -0
  80. psychopy/app/locale/ms_MY/LC_MESSAGE/messages.po +11463 -8757
  81. psychopy/app/locale/nl_NL/LC_MESSAGE/messages.mo +0 -0
  82. psychopy/app/locale/nl_NL/LC_MESSAGE/messages.po +10200 -25
  83. psychopy/app/locale/nn_NO/LC_MESSAGE/messages.mo +0 -0
  84. psychopy/app/locale/nn_NO/LC_MESSAGE/messages.po +10200 -25
  85. psychopy/app/locale/pl_PL/LC_MESSAGE/messages.mo +0 -0
  86. psychopy/app/locale/pl_PL/LC_MESSAGE/messages.po +10200 -25
  87. psychopy/app/locale/pt_PT/LC_MESSAGE/messages.mo +0 -0
  88. psychopy/app/locale/pt_PT/LC_MESSAGE/messages.po +11288 -9434
  89. psychopy/app/locale/ro_RO/LC_MESSAGE/messages.mo +0 -0
  90. psychopy/app/locale/ro_RO/LC_MESSAGE/messages.po +10200 -25
  91. psychopy/app/locale/ru_RU/LC_MESSAGE/messages.mo +0 -0
  92. psychopy/app/locale/ru_RU/LC_MESSAGE/messages.po +10199 -24
  93. psychopy/app/locale/sv_SE/LC_MESSAGE/messages.mo +0 -0
  94. psychopy/app/locale/sv_SE/LC_MESSAGE/messages.po +11441 -8747
  95. psychopy/app/locale/tr_TR/LC_MESSAGE/messages.mo +0 -0
  96. psychopy/app/locale/tr_TR/LC_MESSAGE/messages.po +11069 -9545
  97. psychopy/app/locale/zh_CN/LC_MESSAGE/messages.mo +0 -0
  98. psychopy/app/locale/zh_CN/LC_MESSAGE/messages.po +12085 -8268
  99. psychopy/app/locale/zh_TW/LC_MESSAGE/messages.mo +0 -0
  100. psychopy/app/locale/zh_TW/LC_MESSAGE/messages.po +11929 -8022
  101. psychopy/app/plugin_manager/dialog.py +12 -3
  102. psychopy/app/plugin_manager/packageIndex.py +303 -0
  103. psychopy/app/plugin_manager/packages.py +203 -63
  104. psychopy/app/plugin_manager/plugins.py +120 -240
  105. psychopy/app/preferencesDlg.py +6 -1
  106. psychopy/app/psychopyApp.py +16 -4
  107. psychopy/app/runner/runner.py +10 -2
  108. psychopy/app/runner/scriptProcess.py +8 -3
  109. psychopy/app/stdout/stdOutRich.py +11 -4
  110. psychopy/app/themes/icons.py +3 -0
  111. psychopy/app/utils.py +61 -0
  112. psychopy/colors.py +10 -5
  113. psychopy/data/experiment.py +133 -23
  114. psychopy/data/routine.py +12 -0
  115. psychopy/data/staircase.py +42 -20
  116. psychopy/data/trial.py +20 -12
  117. psychopy/data/utils.py +43 -3
  118. psychopy/demos/builder/Experiments/dragAndDrop/drag_and_drop.psyexp +22 -5
  119. psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solutions.xlsx +0 -0
  120. psychopy/demos/builder/Experiments/stroopVoice/stroopVoice.psyexp +2 -12
  121. psychopy/demos/builder/Feature Demos/buttonBox/buttonBoxDemo.psyexp +3 -8
  122. psychopy/demos/builder/Feature Demos/movies/movie.psyexp +220 -0
  123. psychopy/demos/builder/Feature Demos/movies/readme.md +3 -0
  124. psychopy/demos/builder/Feature Demos/visualValidator/visualValidator.psyexp +1 -2
  125. psychopy/demos/builder/Hardware/camera/camera.psyexp +3 -16
  126. psychopy/demos/builder/Hardware/microphone/microphone.psyexp +3 -16
  127. psychopy/demos/coder/hardware/hdf5_extract.py +133 -0
  128. psychopy/event.py +20 -15
  129. psychopy/experiment/_experiment.py +86 -10
  130. psychopy/experiment/components/__init__.py +3 -10
  131. psychopy/experiment/components/_base.py +9 -20
  132. psychopy/experiment/components/button/__init__.py +1 -1
  133. psychopy/experiment/components/buttonBox/__init__.py +50 -54
  134. psychopy/experiment/components/camera/__init__.py +137 -359
  135. psychopy/experiment/components/keyboard/__init__.py +17 -24
  136. psychopy/experiment/components/microphone/__init__.py +61 -110
  137. psychopy/experiment/components/movie/__init__.py +2 -3
  138. psychopy/experiment/components/serialOut/__init__.py +192 -93
  139. psychopy/experiment/components/settings/__init__.py +45 -27
  140. psychopy/experiment/components/sound/__init__.py +82 -73
  141. psychopy/experiment/components/soundsensor/__init__.py +43 -80
  142. psychopy/experiment/devices.py +303 -0
  143. psychopy/experiment/exports.py +20 -18
  144. psychopy/experiment/flow.py +7 -0
  145. psychopy/experiment/loops.py +47 -29
  146. psychopy/experiment/monitor.py +74 -0
  147. psychopy/experiment/params.py +48 -10
  148. psychopy/experiment/plugins.py +28 -108
  149. psychopy/experiment/py2js_transpiler.py +1 -1
  150. psychopy/experiment/routines/__init__.py +1 -1
  151. psychopy/experiment/routines/_base.py +59 -24
  152. psychopy/experiment/routines/audioValidator/__init__.py +19 -155
  153. psychopy/experiment/routines/visualValidator/__init__.py +25 -25
  154. psychopy/hardware/__init__.py +20 -57
  155. psychopy/hardware/button.py +15 -2
  156. psychopy/hardware/camera/__init__.py +2237 -1394
  157. psychopy/hardware/joystick/__init__.py +1 -1
  158. psychopy/hardware/keyboard.py +5 -8
  159. psychopy/hardware/listener.py +4 -1
  160. psychopy/hardware/manager.py +75 -35
  161. psychopy/hardware/microphone.py +53 -7
  162. psychopy/hardware/monitor.py +144 -0
  163. psychopy/hardware/photometer/__init__.py +156 -117
  164. psychopy/hardware/serialdevice.py +16 -2
  165. psychopy/hardware/soundsensor.py +4 -1
  166. psychopy/iohub/devices/deviceConfigValidation.py +2 -1
  167. psychopy/iohub/devices/eyetracker/hw/gazepoint/__init__.py +2 -2
  168. psychopy/iohub/devices/eyetracker/hw/gazepoint/gp3/__init__.py +1 -0
  169. psychopy/iohub/devices/eyetracker/hw/gazepoint/gp3/eyetracker.py +10 -0
  170. psychopy/iohub/devices/keyboard/darwin.py +8 -5
  171. psychopy/iohub/util/__init__.py +7 -8
  172. psychopy/localization/generateTranslationTemplate.py +208 -116
  173. psychopy/localization/messages.pot +4305 -3502
  174. psychopy/monitors/MonitorCenter.py +174 -74
  175. psychopy/plugins/__init__.py +6 -4
  176. psychopy/preferences/devices.py +80 -0
  177. psychopy/preferences/generateHints.py +2 -1
  178. psychopy/preferences/preferences.py +35 -11
  179. psychopy/scripts/psychopy-pkgutil.py +969 -0
  180. psychopy/scripts/psyexpCompile.py +1 -1
  181. psychopy/session.py +34 -38
  182. psychopy/sound/__init__.py +6 -260
  183. psychopy/sound/audioclip.py +164 -0
  184. psychopy/sound/backend_ptb.py +8 -0
  185. psychopy/sound/backend_pygame.py +10 -0
  186. psychopy/sound/backend_pysound.py +9 -0
  187. psychopy/sound/backends/__init__.py +0 -0
  188. psychopy/sound/microphone.py +3 -0
  189. psychopy/sound/sound.py +58 -0
  190. psychopy/tests/data/correctScript/python/correctNoiseStimComponent.py +1 -1
  191. psychopy/tests/data/duplicateHeaders.csv +2 -0
  192. psychopy/tests/test_app/test_builder/test_BuilderFrame.py +22 -7
  193. psychopy/tests/test_app/test_builder/test_CompileFromBuilder.py +0 -2
  194. psychopy/tests/test_data/test_utils.py +5 -1
  195. psychopy/tests/test_experiment/test_components/test_ButtonBoxComponent.py +22 -2
  196. psychopy/tests/test_hardware/test_ports.py +0 -12
  197. psychopy/tests/test_tools/test_stringtools.py +1 -1
  198. psychopy/tools/attributetools.py +12 -5
  199. psychopy/tools/fontmanager.py +17 -14
  200. psychopy/tools/gltools.py +4 -2
  201. psychopy/tools/movietools.py +43 -2
  202. psychopy/tools/stringtools.py +33 -8
  203. psychopy/tools/versionchooser.py +1 -1
  204. psychopy/validation/audio.py +5 -1
  205. psychopy/validation/visual.py +5 -1
  206. psychopy/visual/basevisual.py +8 -7
  207. psychopy/visual/circle.py +2 -2
  208. psychopy/visual/helpers.py +3 -1
  209. psychopy/visual/image.py +29 -109
  210. psychopy/visual/movies/__init__.py +1800 -313
  211. psychopy/visual/polygon.py +4 -0
  212. psychopy/visual/shape.py +2 -2
  213. psychopy/visual/window.py +35 -12
  214. psychopy/voicekey/__init__.py +41 -669
  215. psychopy/voicekey/labjack_vks.py +7 -48
  216. psychopy/voicekey/parallel_vks.py +7 -42
  217. psychopy/voicekey/vk_tools.py +114 -263
  218. {psychopy-2025.1.0.dist-info → psychopy-2025.2.1.dist-info}/METADATA +20 -13
  219. {psychopy-2025.1.0.dist-info → psychopy-2025.2.1.dist-info}/RECORD +222 -190
  220. {psychopy-2025.1.0.dist-info → psychopy-2025.2.1.dist-info}/WHEEL +1 -1
  221. psychopy/visual/movies/players/__init__.py +0 -62
  222. psychopy/visual/movies/players/ffpyplayer_player.py +0 -1401
  223. psychopy/voicekey/demo_vks.py +0 -12
  224. psychopy/voicekey/signal.py +0 -42
  225. {psychopy-2025.1.0.dist-info → psychopy-2025.2.1.dist-info}/entry_points.txt +0 -0
  226. {psychopy-2025.1.0.dist-info → psychopy-2025.2.1.dist-info}/licenses/LICENSE +0 -0
@@ -10,155 +10,247 @@
10
10
  import os
11
11
  import sys
12
12
  import subprocess
13
- import codecs
14
13
  import shutil
15
14
  import git
16
15
  import babel.messages.frontend
17
- import babel.messages.pofile
16
+ import polib
18
17
  import argparse
19
18
 
20
19
  from psychopy import __version__ as psychopy_version
21
20
 
22
- #
23
- # commandline argument
24
- #
25
-
26
- parser = argparse.ArgumentParser(description='usage: generateTranslationTemplate.py [-h] [-c]')
27
- parser.add_argument('-c', '--commit', action='store_true', help='Commit messages.pot if updated.', required=False)
28
-
29
- command_args = parser.parse_args()
30
-
31
- #
32
- # hints.py must be updated to find new hints and alarts
33
- #
34
-
35
- print('Generate hints.py... ', end='')
36
- subprocess.call(['python', 'generateHints.py'], cwd='../preferences')
37
- print('Done.\nGenerate alartmsg.py... ', end='')
38
- subprocess.call(['python', 'generateAlertmsg.py'], cwd='../alerts/alertsCatalogue')
39
- print('Done.')
40
-
41
- #
42
- # Extracting messages and generating new template file
43
- #
44
-
45
- print('Generating new template file... ', end='')
21
+ locale_dir = '../app/locale/'
46
22
  new_pot_filename = 'messages_new.pot'
47
23
  current_pot_filename = 'messages.pot'
48
-
49
- argv = ['pybabel', '-q', 'extract',
50
- '--input-dirs=..',
51
- '--project=PsychoPy',
52
- '--version='+psychopy_version,
53
- '-k', '_translate',
54
- '-o', new_pot_filename]
55
-
56
24
  babel_frontend = babel.messages.frontend.CommandLineInterface()
57
- babel_frontend.run(argv)
58
-
59
- print('Done.')
60
25
 
61
- #
62
- # Comparing new and current pot file.
63
- #
26
+ target_files = [
27
+ 'psychopy/localization/messages.pot',
28
+ 'psychopy/preferences/hints.py',
29
+ 'psychopy/alerts/alertsCatalogue/alertmsg.py'
30
+ ]
31
+
32
+ poedit_mime_headers = {
33
+ "X-Poedit-KeywordsList": "_translate",
34
+ "X-Poedit-Basepath": "../../../..",
35
+ "X-Poedit-SourceCharset": "UTF-8",
36
+ "X-Poedit-SearchPath-0:": ".",
37
+ "X-Poedit-SearchPathExcluded-0": "app/localization/utils",
38
+ "X-Poedit-SearchPathExcluded-1": "app/Resources"
39
+ }
40
+
41
+ def generate_new_template(verbose=False):
42
+ """
43
+ Generate new POT file using pybabel.
44
+ Before running pybabel, generateHints.py and generateAlertmsg.py are called
45
+ to update lists of hints and alerts.
46
+
47
+ Returns:
48
+ None
49
+ """
50
+
51
+ # hints.py must be updated to find new hints and alarts
52
+ if verbose:
53
+ print('Generate hints.py... ', end='')
54
+ subprocess.call(['python', 'generateHints.py'], cwd='../preferences')
55
+ if verbose:
56
+ print('Done.\nGenerate alartmsg.py... ', end='')
57
+ subprocess.call(['python', 'generateAlertmsg.py'], cwd='../alerts/alertsCatalogue')
58
+ if verbose:
59
+ print('Done.')
60
+
61
+ # Extracting messages and generating new template file
62
+
63
+ if verbose:
64
+ print('Generating new template file... ', end='')
65
+ argv = ['pybabel', '-q', 'extract',
66
+ '--input-dirs=.',
67
+ '--project=PsychoPy',
68
+ '--version='+psychopy_version,
69
+ '--keyword=_translate',
70
+ '--width=79',
71
+ '--output-file=localization/'+new_pot_filename,
72
+ '--ignore-dirs="app/localization/utils app/Resources"']
73
+
74
+ os.chdir('..') # The command must be run in the parent directory.
75
+ babel_frontend.run(argv) # Run the command
76
+ os.chdir('localization') # Return to the original directory.
77
+ if verbose:
78
+ print('Done.')
79
+
80
+ def find_new_entries(verbose=False):
81
+ """
82
+ Count new entries in the new POT file.
83
+
84
+ Returns:
85
+ int: number of new entries.
86
+ int: number of entries in the new POT file.
87
+ """
88
+ if verbose:
89
+ print('Making a list of message IDs in the new template... ', end='')
90
+
91
+ new_pot_msgids = []
92
+ current_pot_msgids = []
93
+ untranslated_new = 0
94
+
95
+ if not os.path.exists(current_pot_filename):
96
+ # if current pot file doesn't exist, copy it from new pot file.
97
+ if verbose:
98
+ print('INFO: create {}... '.format(current_pot_filename), end='')
99
+ shutil.copy(new_pot_filename, current_pot_filename)
100
+
101
+ # all entries are new.
102
+ po_new = polib.pofile(new_pot_filename)
103
+ untranslated_new = len(po_new.untranslated_entries())
104
+
105
+ else:
106
+ po_new = polib.pofile(new_pot_filename)
107
+ for entry in po_new:
108
+ if entry.msgid != '':
109
+ new_pot_msgids.append(entry.msgid)
110
+
111
+ po = polib.pofile(current_pot_filename)
112
+ for entry in po:
113
+ if entry.msgid != '':
114
+ current_pot_msgids.append(entry.msgid)
115
+
116
+ for id in new_pot_msgids:
117
+ if id not in current_pot_msgids:
118
+ untranslated_new += 1
119
+
120
+ if verbose:
121
+ print('{} new entries are found. Done.'.format(untranslated_new))
122
+ return untranslated_new, len(po_new.untranslated_entries())
123
+
124
+ def merge_new_entries(verbose=False):
125
+ """
126
+ Merge new POT file to PO files.
127
+ The 'Project-Id-Version' and 'POT-Creation-Date' in the PO files will be updated.
128
+ No other metadata will be changed.
129
+
130
+ returns:
131
+ None
132
+ """
133
+ if verbose:
134
+ print('Merging new POT to PO files...')
135
+
136
+ pot = polib.pofile(new_pot_filename) # pot: for updating existing PO file
137
+ pot_new = polib.pofile(new_pot_filename, wrapwidth=79) # pot_new: for creating new PO file
138
+ pot_new.metadata.update(poedit_mime_headers) # update header of new PO file
139
+
140
+ for loc in os.listdir(locale_dir):
141
+ lc_message_dir = os.path.join(locale_dir, loc,'LC_MESSAGE')
142
+ messages_po_path = os.path.join(lc_message_dir, 'messages.po')
143
+ if not os.path.exists(lc_message_dir) or not os.path.isdir(lc_message_dir):
144
+ print('WARNING: {} is not a valid locale directory.'.format(loc))
145
+ continue
146
+ if os.path.exists(os.path.join(locale_dir, loc, 'LC_MESSAGE','messages.po')):
147
+ if verbose:
148
+ print(' merge new POT to {}...'.format(messages_po_path))
149
+ po = polib.pofile(messages_po_path, wrapwidth=79)
150
+ po.merge(pot) #merge
151
+ # update Project-Id-Version and POT-Creation-Date
152
+ po.metadata['Project-Id-Version'] = pot.metadata['Project-Id-Version']
153
+ po.metadata['POT-Creation-Date'] = pot.metadata['POT-Creation-Date']
154
+ po.save() # overwrite
155
+ else:
156
+ # creating new PO file: need to modify "Language" metadata
157
+ pot_new.metadata['Language'] = loc[:loc.find('_')]
158
+ pot_new.save(messages_po_path)
159
+
160
+ if verbose:
161
+ print('Done.')
162
+
163
+ def check_translation_status(verbose=False):
164
+ """
165
+ Count translated messages in the PO files.
166
+
167
+ returns:
168
+ dict: number of translated messages (key: locale code)
169
+ """
170
+ status = {}
171
+
172
+ if verbose:
173
+ print('Checking current PO files...', end='')
174
+ for loc in os.listdir(locale_dir):
175
+ lc_message_dir = os.path.join(locale_dir, loc,'LC_MESSAGE')
176
+ messages_po_path = os.path.join(lc_message_dir, 'messages.po')
177
+ if os.path.exists(os.path.join(locale_dir, loc, 'LC_MESSAGE','messages.po')):
178
+ po = polib.pofile(messages_po_path)
179
+ translated = len(po.translated_entries())
180
+ status[loc] = translated
181
+
182
+ if verbose:
183
+ print('Done.')
184
+ return status
64
185
 
65
- print('Maing a list of message IDs in the new template... ', end='')
66
186
 
67
- new_pot_message_ids = []
68
- current_pot_id = []
69
- untranslated_new = []
70
-
71
- with codecs.open(new_pot_filename, 'r', 'utf-8') as fp:
72
- for message in babel.messages.pofile.read_po(fp):
73
- if message.id:
74
- new_pot_message_ids.append(message.id)
187
+ parser = argparse.ArgumentParser(description='usage: generateTranslationTemplate.py [-h] [-c]')
188
+ parser.add_argument('-c', '--commit', action='store_true', help='Commit messages.pot if updated.', required=False)
189
+ parser.add_argument('-v', '--verbose', action='store_true', help='Show detailed processing information.', required=False)
75
190
 
76
- if not os.path.exists(current_pot_filename):
77
- # if current pot file doesn't exist, copy it from new pot file.
78
- shutil.copy(new_pot_filename, current_pot_filename)
191
+ args = parser.parse_args()
79
192
 
80
- with codecs.open(current_pot_filename, 'r', 'utf-8') as fp:
81
- for message in babel.messages.pofile.read_po(fp):
82
- if message.id:
83
- current_pot_id.append(message.id)
193
+ parent_dir, current_dir = os.path.split(os.getcwd())
194
+ if current_dir != 'localization' or os.path.split(parent_dir)[1] != 'psychopy':
195
+ print('Error: this script must be run in psychopy/localization directory.')
196
+ sys.exit(-1)
84
197
 
85
- for id in new_pot_message_ids:
86
- if id not in current_pot_id:
87
- untranslated_new.append(id)
198
+ generate_new_template(verbose=args.verbose)
199
+ num_new_entries, num_total_entries = find_new_entries(verbose=args.verbose)
88
200
 
201
+ # if there are new entries, merge POT to existing PO files.
202
+ if num_new_entries > 0:
203
+ merge_new_entries(verbose=args.verbose)
89
204
 
90
- print('Done.')
205
+ # check translation status
206
+ translation_status = check_translation_status(verbose=args.verbose)
91
207
 
92
- #
93
- # Output summary
94
- #
95
208
 
96
- print('Checking current PO files...')
97
-
98
- n_untranslated_locale = []
99
-
100
- for root, dirs, files in os.walk('../app/locale/'):
101
- for file in files:
102
- if file=='messages.po':
103
- po_message_ids = []
104
- n_untranslated = 0
105
- locale_identifier = os.path.basename(os.path.dirname(root))
106
- print('{}: '.format(locale_identifier), end='')
107
- try:
108
- with codecs.open(os.path.join(root, file), 'r', 'utf-8') as fp:
109
- catalog = babel.messages.pofile.read_po(fp)
110
- for message in catalog:
111
- if message.id:
112
- po_message_ids.append(message.id)
113
- # found in the new POT, but not translated
114
- if message.id in new_pot_message_ids and message.string == '':
115
- n_untranslated += 1
116
- for id in new_pot_message_ids:
117
- # not found in the current PO (it must be untranslated)
118
- if id not in po_message_ids:
119
- n_untranslated += 1
120
- n_untranslated_locale.append((locale_identifier, n_untranslated))
121
- except ValueError:
122
- # If date strings in PO file is wrong (e.g. empty string),
123
- # read_po() raises ValueError.
124
- print('Skip.')
125
- else:
126
- print('Ok.')
127
-
128
- n_messages = len(new_pot_message_ids)
129
- summary_message = '\nNumber of messages in *.py files: {}\n'.format(n_messages)
130
- summary_message += 'New message(s): {}\n\n'.format(len(untranslated_new))
209
+ summary_message = '\nNumber of messages in *.py files: {}\n'.format(num_total_entries)
210
+ summary_message += 'New message(s): {}\n\n'.format(num_new_entries)
131
211
  summary_message += 'Untranslated message(s)\n'
132
212
 
133
- for locale_identifier, n in n_untranslated_locale:
134
- summary_message += ' {}:{:>8} ({:>5.1f}%)\n'.format(locale_identifier, n, 100*n/n_messages)
213
+ for loc in translation_status.keys():
214
+ n = translation_status[loc]
215
+ summary_message += ' {}:{:>8} ({:>5.1f}%)\n'.format(loc, n, 100*n/num_total_entries)
135
216
 
136
217
  # output to stdout
137
- sys.stdout.write(summary_message)
218
+ print(summary_message)
138
219
 
139
-
140
- #
141
220
  # Update current pot file only if new strings were found.
142
- #
143
-
144
221
 
145
- if len(untranslated_new) > 0:
222
+ if num_new_entries > 0:
146
223
  # replace current pot file with new one.
147
224
  os.remove(current_pot_filename)
148
225
  os.rename(new_pot_filename, current_pot_filename)
149
226
 
150
227
  # add and commit template file if --commit is given
151
- if command_args.commit:
152
- sys.stdout.write('\nCommit messages.pot...\n')
228
+ if args.commit:
229
+ if args.verbose:
230
+ sys.stdout.write('\nCommit messages.pot...\n')
231
+
153
232
  repo = git.Repo('../../')
154
-
155
-
156
- pot_file_path = 'psychopy/localization/' + current_pot_filename
157
- print(pot_file_path)
158
- print([item.a_path for item in repo.index.diff(None)])
159
- if pot_file_path in repo.untracked_files or pot_file_path in [item.a_path for item in repo.index.diff(None)]:
160
- repo.index.add([pot_file_path])
161
- repo.index.commit('ENH: Translation template is updated')
233
+ updated = [item.a_path for item in repo.index.diff(None)]
234
+ added = False
235
+
236
+ # add if messages.pot is untracked or updated
237
+ for file_path in target_files:
238
+ if file_path in repo.untracked_files or file_path in updated:
239
+ if args.verbose:
240
+ print(' add {}'.format(file_path))
241
+ repo.index.add(file_path)
242
+ added = True
243
+
244
+ for loc in os.listdir(locale_dir):
245
+ file_path = '/'.join(('psychopy/app/locale', loc,'LC_MESSAGE', 'messages.po'))
246
+ if file_path in repo.untracked_files or file_path in updated:
247
+ if args.verbose:
248
+ print(' add {}'.format(file_path))
249
+ repo.index.add(file_path)
250
+ added = True
251
+
252
+ if added:
253
+ repo.index.commit('ENH: Translation template is updated\n\n{}'.format(summary_message))
162
254
 
163
255
  else:
164
256
  # keep current pot file and remove new one.