psychopy 2025.1.1__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.
- psychopy/VERSION +1 -1
- psychopy/alerts/alertsCatalogue/4810.yaml +19 -0
- psychopy/alerts/alertsCatalogue/alertCategories.yaml +4 -0
- psychopy/alerts/alertsCatalogue/alertmsg.py +15 -1
- psychopy/alerts/alertsCatalogue/generateAlertmsg.py +2 -2
- psychopy/app/Resources/classic/add_many.png +0 -0
- psychopy/app/Resources/classic/add_many@2x.png +0 -0
- psychopy/app/Resources/classic/devices.png +0 -0
- psychopy/app/Resources/classic/devices@2x.png +0 -0
- psychopy/app/Resources/classic/photometer.png +0 -0
- psychopy/app/Resources/classic/photometer@2x.png +0 -0
- psychopy/app/Resources/dark/add_many.png +0 -0
- psychopy/app/Resources/dark/add_many@2x.png +0 -0
- psychopy/app/Resources/dark/devices.png +0 -0
- psychopy/app/Resources/dark/devices@2x.png +0 -0
- psychopy/app/Resources/dark/photometer.png +0 -0
- psychopy/app/Resources/dark/photometer@2x.png +0 -0
- psychopy/app/Resources/light/add_many.png +0 -0
- psychopy/app/Resources/light/add_many@2x.png +0 -0
- psychopy/app/Resources/light/devices.png +0 -0
- psychopy/app/Resources/light/devices@2x.png +0 -0
- psychopy/app/Resources/light/photometer.png +0 -0
- psychopy/app/Resources/light/photometer@2x.png +0 -0
- psychopy/app/_psychopyApp.py +35 -13
- psychopy/app/builder/builder.py +88 -35
- psychopy/app/builder/dialogs/__init__.py +69 -220
- psychopy/app/builder/dialogs/dlgsCode.py +29 -8
- psychopy/app/builder/dialogs/paramCtrls.py +1468 -904
- psychopy/app/builder/validators.py +25 -17
- psychopy/app/coder/coder.py +12 -1
- psychopy/app/coder/repl.py +5 -2
- psychopy/app/colorpicker/__init__.py +1 -1
- psychopy/app/deviceManager/__init__.py +1 -0
- psychopy/app/deviceManager/addDialog.py +218 -0
- psychopy/app/deviceManager/dialog.py +185 -0
- psychopy/app/deviceManager/panel.py +191 -0
- psychopy/app/deviceManager/utils.py +60 -0
- psychopy/app/idle.py +7 -0
- psychopy/app/locale/ar_001/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/ar_001/LC_MESSAGE/messages.po +12695 -10592
- psychopy/app/locale/cs_CZ/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/cs_CZ/LC_MESSAGE/messages.po +10199 -24
- psychopy/app/locale/da_DK/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/da_DK/LC_MESSAGE/messages.po +10199 -24
- psychopy/app/locale/de_DE/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/de_DE/LC_MESSAGE/messages.po +11221 -9712
- psychopy/app/locale/el_GR/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/el_GR/LC_MESSAGE/messages.po +10200 -25
- psychopy/app/locale/en_NZ/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/en_NZ/LC_MESSAGE/messages.po +10200 -25
- psychopy/app/locale/en_US/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/en_US/LC_MESSAGE/messages.po +10195 -18
- psychopy/app/locale/es_CO/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/es_CO/LC_MESSAGE/messages.po +11917 -9101
- psychopy/app/locale/es_ES/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/es_ES/LC_MESSAGE/messages.po +11924 -9103
- psychopy/app/locale/es_US/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/es_US/LC_MESSAGE/messages.po +11917 -9101
- psychopy/app/locale/et_EE/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/et_EE/LC_MESSAGE/messages.po +11084 -9569
- psychopy/app/locale/fa_IR/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/fa_IR/LC_MESSAGE/messages.po +11590 -5806
- psychopy/app/locale/fi_FI/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/fi_FI/LC_MESSAGE/messages.po +10199 -24
- psychopy/app/locale/fr_FR/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/fr_FR/LC_MESSAGE/messages.po +11091 -9577
- psychopy/app/locale/he_IL/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/he_IL/LC_MESSAGE/messages.po +11072 -9549
- psychopy/app/locale/hi_IN/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/hi_IN/LC_MESSAGE/messages.po +11071 -9559
- psychopy/app/locale/hu_HU/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/hu_HU/LC_MESSAGE/messages.po +10200 -25
- psychopy/app/locale/it_IT/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/it_IT/LC_MESSAGE/messages.po +11072 -9560
- psychopy/app/locale/ja_JP/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/ja_JP/LC_MESSAGE/messages.po +1485 -1137
- psychopy/app/locale/ko_KR/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/ko_KR/LC_MESSAGE/messages.po +10199 -24
- psychopy/app/locale/ms_MY/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/ms_MY/LC_MESSAGE/messages.po +11463 -8757
- psychopy/app/locale/nl_NL/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/nl_NL/LC_MESSAGE/messages.po +10200 -25
- psychopy/app/locale/nn_NO/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/nn_NO/LC_MESSAGE/messages.po +10200 -25
- psychopy/app/locale/pl_PL/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/pl_PL/LC_MESSAGE/messages.po +10200 -25
- psychopy/app/locale/pt_PT/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/pt_PT/LC_MESSAGE/messages.po +11288 -9434
- psychopy/app/locale/ro_RO/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/ro_RO/LC_MESSAGE/messages.po +10200 -25
- psychopy/app/locale/ru_RU/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/ru_RU/LC_MESSAGE/messages.po +10199 -24
- psychopy/app/locale/sv_SE/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/sv_SE/LC_MESSAGE/messages.po +11441 -8747
- psychopy/app/locale/tr_TR/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/tr_TR/LC_MESSAGE/messages.po +11069 -9545
- psychopy/app/locale/zh_CN/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/zh_CN/LC_MESSAGE/messages.po +12085 -8268
- psychopy/app/locale/zh_TW/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/zh_TW/LC_MESSAGE/messages.po +11929 -8022
- psychopy/app/plugin_manager/dialog.py +12 -3
- psychopy/app/plugin_manager/packageIndex.py +303 -0
- psychopy/app/plugin_manager/packages.py +203 -63
- psychopy/app/plugin_manager/plugins.py +120 -240
- psychopy/app/preferencesDlg.py +6 -1
- psychopy/app/psychopyApp.py +16 -4
- psychopy/app/runner/runner.py +10 -2
- psychopy/app/runner/scriptProcess.py +8 -3
- psychopy/app/stdout/stdOutRich.py +11 -4
- psychopy/app/themes/icons.py +3 -0
- psychopy/app/utils.py +61 -0
- psychopy/data/experiment.py +133 -23
- psychopy/data/routine.py +12 -0
- psychopy/data/staircase.py +42 -20
- psychopy/data/trial.py +20 -12
- psychopy/data/utils.py +42 -2
- psychopy/demos/builder/Experiments/dragAndDrop/drag_and_drop.psyexp +22 -5
- psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solutions.xlsx +0 -0
- psychopy/demos/builder/Experiments/stroopVoice/stroopVoice.psyexp +2 -12
- psychopy/demos/builder/Feature Demos/buttonBox/buttonBoxDemo.psyexp +3 -8
- psychopy/demos/builder/Feature Demos/movies/movie.psyexp +220 -0
- psychopy/demos/builder/Feature Demos/movies/readme.md +3 -0
- psychopy/demos/builder/Feature Demos/visualValidator/visualValidator.psyexp +1 -2
- psychopy/demos/builder/Hardware/camera/camera.psyexp +3 -16
- psychopy/demos/builder/Hardware/microphone/microphone.psyexp +3 -16
- psychopy/demos/coder/hardware/hdf5_extract.py +133 -0
- psychopy/event.py +20 -15
- psychopy/experiment/_experiment.py +86 -10
- psychopy/experiment/components/__init__.py +3 -10
- psychopy/experiment/components/_base.py +9 -20
- psychopy/experiment/components/button/__init__.py +1 -1
- psychopy/experiment/components/buttonBox/__init__.py +50 -54
- psychopy/experiment/components/camera/__init__.py +137 -359
- psychopy/experiment/components/keyboard/__init__.py +17 -24
- psychopy/experiment/components/microphone/__init__.py +61 -110
- psychopy/experiment/components/movie/__init__.py +2 -3
- psychopy/experiment/components/serialOut/__init__.py +192 -93
- psychopy/experiment/components/settings/__init__.py +45 -27
- psychopy/experiment/components/sound/__init__.py +82 -73
- psychopy/experiment/components/soundsensor/__init__.py +43 -80
- psychopy/experiment/devices.py +303 -0
- psychopy/experiment/exports.py +20 -18
- psychopy/experiment/flow.py +7 -0
- psychopy/experiment/loops.py +47 -29
- psychopy/experiment/monitor.py +74 -0
- psychopy/experiment/params.py +48 -10
- psychopy/experiment/plugins.py +28 -108
- psychopy/experiment/py2js_transpiler.py +1 -1
- psychopy/experiment/routines/__init__.py +1 -1
- psychopy/experiment/routines/_base.py +59 -24
- psychopy/experiment/routines/audioValidator/__init__.py +19 -155
- psychopy/experiment/routines/visualValidator/__init__.py +25 -25
- psychopy/hardware/__init__.py +20 -57
- psychopy/hardware/button.py +15 -2
- psychopy/hardware/camera/__init__.py +2237 -1394
- psychopy/hardware/joystick/__init__.py +1 -1
- psychopy/hardware/keyboard.py +5 -8
- psychopy/hardware/listener.py +4 -1
- psychopy/hardware/manager.py +75 -35
- psychopy/hardware/microphone.py +52 -6
- psychopy/hardware/monitor.py +144 -0
- psychopy/hardware/photometer/__init__.py +156 -117
- psychopy/hardware/serialdevice.py +16 -2
- psychopy/hardware/soundsensor.py +4 -1
- psychopy/iohub/devices/deviceConfigValidation.py +2 -1
- psychopy/iohub/devices/keyboard/darwin.py +8 -5
- psychopy/iohub/util/__init__.py +7 -8
- psychopy/localization/generateTranslationTemplate.py +208 -116
- psychopy/localization/messages.pot +4305 -3502
- psychopy/monitors/MonitorCenter.py +174 -74
- psychopy/plugins/__init__.py +6 -4
- psychopy/preferences/devices.py +80 -0
- psychopy/preferences/generateHints.py +2 -1
- psychopy/preferences/preferences.py +35 -11
- psychopy/scripts/psychopy-pkgutil.py +969 -0
- psychopy/scripts/psyexpCompile.py +1 -1
- psychopy/session.py +34 -38
- psychopy/sound/__init__.py +6 -260
- psychopy/sound/audioclip.py +164 -0
- psychopy/sound/backend_ptb.py +8 -0
- psychopy/sound/backend_pygame.py +10 -0
- psychopy/sound/backend_pysound.py +9 -0
- psychopy/sound/backends/__init__.py +0 -0
- psychopy/sound/microphone.py +3 -0
- psychopy/sound/sound.py +58 -0
- psychopy/tests/data/correctScript/python/correctNoiseStimComponent.py +1 -1
- psychopy/tests/data/duplicateHeaders.csv +2 -0
- psychopy/tests/test_app/test_builder/test_BuilderFrame.py +22 -7
- psychopy/tests/test_app/test_builder/test_CompileFromBuilder.py +0 -2
- psychopy/tests/test_data/test_utils.py +5 -1
- psychopy/tests/test_experiment/test_components/test_ButtonBoxComponent.py +22 -2
- psychopy/tests/test_hardware/test_ports.py +0 -12
- psychopy/tests/test_tools/test_stringtools.py +1 -1
- psychopy/tools/attributetools.py +12 -5
- psychopy/tools/fontmanager.py +17 -14
- psychopy/tools/movietools.py +43 -2
- psychopy/tools/stringtools.py +33 -8
- psychopy/tools/versionchooser.py +1 -1
- psychopy/validation/audio.py +5 -1
- psychopy/validation/visual.py +5 -1
- psychopy/visual/basevisual.py +8 -7
- psychopy/visual/circle.py +2 -2
- psychopy/visual/image.py +29 -109
- psychopy/visual/movies/__init__.py +1800 -313
- psychopy/visual/polygon.py +4 -0
- psychopy/visual/shape.py +2 -2
- psychopy/visual/window.py +34 -11
- psychopy/voicekey/__init__.py +41 -669
- psychopy/voicekey/labjack_vks.py +7 -48
- psychopy/voicekey/parallel_vks.py +7 -42
- psychopy/voicekey/vk_tools.py +114 -263
- {psychopy-2025.1.1.dist-info → psychopy-2025.2.1.dist-info}/METADATA +17 -11
- {psychopy-2025.1.1.dist-info → psychopy-2025.2.1.dist-info}/RECORD +216 -184
- {psychopy-2025.1.1.dist-info → psychopy-2025.2.1.dist-info}/WHEEL +1 -1
- psychopy/visual/movies/players/__init__.py +0 -62
- psychopy/visual/movies/players/ffpyplayer_player.py +0 -1401
- psychopy/voicekey/demo_vks.py +0 -12
- psychopy/voicekey/signal.py +0 -42
- {psychopy-2025.1.1.dist-info → psychopy-2025.2.1.dist-info}/entry_points.txt +0 -0
- {psychopy-2025.1.1.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
|
|
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
|
-
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
|
|
86
|
-
|
|
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
|
-
|
|
205
|
+
# check translation status
|
|
206
|
+
translation_status = check_translation_status(verbose=args.verbose)
|
|
91
207
|
|
|
92
|
-
#
|
|
93
|
-
# Output summary
|
|
94
|
-
#
|
|
95
208
|
|
|
96
|
-
|
|
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
|
|
134
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
152
|
-
|
|
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
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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.
|