cuju-render 0.1.0__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.
- cuju/render/__init__.py +759 -0
- cuju/render/__main__.py +1065 -0
- cuju/render/_export/__init__.py +3 -0
- cuju/render/_export/feed.py +542 -0
- cuju/render/_export/sizes.py +157 -0
- cuju/render/_export/video.py +1618 -0
- cuju/render/_export/video_jobs.py +408 -0
- cuju/render/_format/__init__.py +3 -0
- cuju/render/_format/annotations.py +235 -0
- cuju/render/_format/codec.py +307 -0
- cuju/render/_format/keep.py +121 -0
- cuju/render/_format/recording.py +860 -0
- cuju/render/_format/writer.py +54 -0
- cuju/render/_guide/__init__.py +3 -0
- cuju/render/_guide/doctor.py +523 -0
- cuju/render/_guide/help.py +62 -0
- cuju/render/_guide/tutorial.py +254 -0
- cuju/render/_http/__init__.py +3 -0
- cuju/render/_http/commands.py +57 -0
- cuju/render/_http/headers.py +103 -0
- cuju/render/_http/launch.py +500 -0
- cuju/render/_http/launchers.py +408 -0
- cuju/render/_http/pwa.py +128 -0
- cuju/render/_http/server.py +2105 -0
- cuju/render/_http/window.py +360 -0
- cuju/render/_moments/__init__.py +3 -0
- cuju/render/_moments/captions.py +284 -0
- cuju/render/_moments/events.py +1123 -0
- cuju/render/_moments/search.py +694 -0
- cuju/render/_packages/__init__.py +3 -0
- cuju/render/_packages/glsl.py +746 -0
- cuju/render/_packages/loudness.py +334 -0
- cuju/render/_packages/pack.py +1959 -0
- cuju/render/_packages/presentation.py +1376 -0
- cuju/render/_packages/vocabulary.py +609 -0
- cuju/render/_playing/__init__.py +3 -0
- cuju/render/_playing/achievements.py +463 -0
- cuju/render/_playing/bench.py +609 -0
- cuju/render/_playing/challenges.py +1813 -0
- cuju/render/_playing/demo.py +323 -0
- cuju/render/_playing/draw.py +211 -0
- cuju/render/_playing/errors.py +20 -0
- cuju/render/_playing/game.py +645 -0
- cuju/render/_playing/limits.py +339 -0
- cuju/render/_playing/play.py +926 -0
- cuju/render/_playing/policy_worker.py +250 -0
- cuju/render/_playing/strict.py +810 -0
- cuju/render/_registry/__init__.py +3 -0
- cuju/render/_registry/clips.py +394 -0
- cuju/render/_registry/joints.py +108 -0
- cuju/render/_registry/teams.py +2058 -0
- cuju/render/_store/__init__.py +3 -0
- cuju/render/_store/home.py +70 -0
- cuju/render/_store/library.py +604 -0
- cuju/render/_store/samples.py +163 -0
- cuju/render/_text/__init__.py +3 -0
- cuju/render/_text/languages.py +637 -0
- cuju/render/_text/messages.py +1396 -0
- cuju/render/_tournaments/__init__.py +3 -0
- cuju/render/_tournaments/results.py +547 -0
- cuju/render/_tournaments/tournament.py +2742 -0
- cuju/render/_tournaments/tournament_cli.py +587 -0
- cuju/render/_tournaments/tournament_runner.py +1975 -0
- cuju/render/_version.py +6 -0
- cuju/render/_watch/__init__.py +3 -0
- cuju/render/_watch/grid.py +265 -0
- cuju/render/_watch/live.py +664 -0
- cuju/render/_watch/notebook.py +79 -0
- cuju/render/_watch/show.py +171 -0
- cuju/render/samples/sample-11v11.cjr +0 -0
- cuju/render/samples/sample-5v5.cjr +0 -0
- cuju/render/samples/teams/starter/main.py +33 -0
- cuju/render/scenarios/benchmark_full_game_11_vs_11_easy.py +46 -0
- cuju/render/scenarios/benchmark_full_game_11_vs_11_hard.py +46 -0
- cuju/render/scenarios/benchmark_full_game_11_vs_11_medium.py +46 -0
- cuju/render/scenarios/benchmark_full_game_5_vs_5_easy.py +34 -0
- cuju/render/scenarios/benchmark_full_game_5_vs_5_hard.py +34 -0
- cuju/render/scenarios/benchmark_full_game_5_vs_5_medium.py +34 -0
- cuju/render/web/assets/adboard_polygon.webp +0 -0
- cuju/render/web/assets/adboard_your_ad_here.webp +0 -0
- cuju/render/web/assets/animations/kaykit-rig-medium-combatmelee.glb +0 -0
- cuju/render/web/assets/animations/kaykit-rig-medium-general.glb +0 -0
- cuju/render/web/assets/animations/kaykit-rig-medium-movementadvanced.glb +0 -0
- cuju/render/web/assets/animations/kaykit-rig-medium-movementbasic.glb +0 -0
- cuju/render/web/assets/animations/kaykit-rig-medium-simulation.glb +0 -0
- cuju/render/web/assets/animations/kaykit-rig-medium-special.glb +0 -0
- cuju/render/web/assets/ball.glb +0 -0
- cuju/render/web/assets/challenges/3_vs_1_with_keeper.mp4 +0 -0
- cuju/render/web/assets/challenges/3_vs_1_with_keeper.webp +0 -0
- cuju/render/web/assets/challenges/corner.mp4 +0 -0
- cuju/render/web/assets/challenges/corner.webp +0 -0
- cuju/render/web/assets/challenges/counterattack_easy.mp4 +0 -0
- cuju/render/web/assets/challenges/counterattack_easy.webp +0 -0
- cuju/render/web/assets/challenges/counterattack_hard.mp4 +0 -0
- cuju/render/web/assets/challenges/counterattack_hard.webp +0 -0
- cuju/render/web/assets/challenges/empty_goal.mp4 +0 -0
- cuju/render/web/assets/challenges/empty_goal.webp +0 -0
- cuju/render/web/assets/challenges/empty_goal_close.mp4 +0 -0
- cuju/render/web/assets/challenges/empty_goal_close.webp +0 -0
- cuju/render/web/assets/challenges/manifest.json +375 -0
- cuju/render/web/assets/challenges/one_on_one.mp4 +0 -0
- cuju/render/web/assets/challenges/one_on_one.webp +0 -0
- cuju/render/web/assets/challenges/one_on_one_hard.mp4 +0 -0
- cuju/render/web/assets/challenges/one_on_one_hard.webp +0 -0
- cuju/render/web/assets/challenges/pass_and_shoot_with_keeper.mp4 +0 -0
- cuju/render/web/assets/challenges/pass_and_shoot_with_keeper.webp +0 -0
- cuju/render/web/assets/challenges/run_pass_and_shoot_with_keeper.mp4 +0 -0
- cuju/render/web/assets/challenges/run_pass_and_shoot_with_keeper.webp +0 -0
- cuju/render/web/assets/challenges/run_to_score.mp4 +0 -0
- cuju/render/web/assets/challenges/run_to_score.webp +0 -0
- cuju/render/web/assets/challenges/run_to_score_with_keeper.mp4 +0 -0
- cuju/render/web/assets/challenges/run_to_score_with_keeper.webp +0 -0
- cuju/render/web/assets/challenges/single_goal_versus_lazy.mp4 +0 -0
- cuju/render/web/assets/challenges/single_goal_versus_lazy.webp +0 -0
- cuju/render/web/assets/crowd.webp +0 -0
- cuju/render/web/assets/crowd_normal.webp +0 -0
- cuju/render/web/assets/cuju-logo-384.png +0 -0
- cuju/render/web/assets/cuju-logo.png +0 -0
- cuju/render/web/assets/cuju-logo.svg +46 -0
- cuju/render/web/assets/cuju-mark-light.svg +33 -0
- cuju/render/web/assets/cuju-mark.svg +33 -0
- cuju/render/web/assets/goal.glb +0 -0
- cuju/render/web/assets/grass.webp +0 -0
- cuju/render/web/assets/hair_black.png +0 -0
- cuju/render/web/assets/hair_blonde.png +0 -0
- cuju/render/web/assets/hair_brown.png +0 -0
- cuju/render/web/assets/hair_darkblonde.png +0 -0
- cuju/render/web/assets/hair_long01.glb +0 -0
- cuju/render/web/assets/hair_long02.glb +0 -0
- cuju/render/web/assets/hair_medium01.glb +0 -0
- cuju/render/web/assets/hair_red.png +0 -0
- cuju/render/web/assets/hair_short01.glb +0 -0
- cuju/render/web/assets/hair_short02.glb +0 -0
- cuju/render/web/assets/icons/cuju-192.png +0 -0
- cuju/render/web/assets/icons/cuju-512.png +0 -0
- cuju/render/web/assets/icons/cuju-maskable-512.png +0 -0
- cuju/render/web/assets/kit_goalkeeper.webp +0 -0
- cuju/render/web/assets/kit_mask.png +0 -0
- cuju/render/web/assets/kit_normal.webp +0 -0
- cuju/render/web/assets/manifest.json +601 -0
- cuju/render/web/assets/models/kaykit-barbarian.glb +0 -0
- cuju/render/web/assets/models/kaykit-barbarian.json +23 -0
- cuju/render/web/assets/models/kaykit-knight.glb +0 -0
- cuju/render/web/assets/models/kaykit-knight.json +23 -0
- cuju/render/web/assets/models/kaykit-mage.glb +0 -0
- cuju/render/web/assets/models/kaykit-mage.json +23 -0
- cuju/render/web/assets/models/kaykit-mannequin-medium.glb +0 -0
- cuju/render/web/assets/models/kaykit-mannequin-medium.json +23 -0
- cuju/render/web/assets/models/kaykit-ranger.glb +0 -0
- cuju/render/web/assets/models/kaykit-ranger.json +23 -0
- cuju/render/web/assets/models/kaykit-rogue-hooded.glb +0 -0
- cuju/render/web/assets/models/kaykit-rogue-hooded.json +23 -0
- cuju/render/web/assets/models/kaykit-rogue.glb +0 -0
- cuju/render/web/assets/models/kaykit-rogue.json +23 -0
- cuju/render/web/assets/models/kaykit-skeleton-mage.glb +0 -0
- cuju/render/web/assets/models/kaykit-skeleton-mage.json +23 -0
- cuju/render/web/assets/models/kaykit-skeleton-minion.glb +0 -0
- cuju/render/web/assets/models/kaykit-skeleton-minion.json +23 -0
- cuju/render/web/assets/models/kaykit-skeleton-rogue.glb +0 -0
- cuju/render/web/assets/models/kaykit-skeleton-rogue.json +23 -0
- cuju/render/web/assets/models/kaykit-skeleton-warrior.glb +0 -0
- cuju/render/web/assets/models/kaykit-skeleton-warrior.json +23 -0
- cuju/render/web/assets/pitch.webp +0 -0
- cuju/render/web/assets/pitch_normal.webp +0 -0
- cuju/render/web/assets/player.glb +0 -0
- cuju/render/web/assets/shoe.webp +0 -0
- cuju/render/web/assets/shoe_sole.png +0 -0
- cuju/render/web/assets/skin_01.png +0 -0
- cuju/render/web/assets/skin_02.png +0 -0
- cuju/render/web/assets/skin_03.png +0 -0
- cuju/render/web/assets/skin_04.png +0 -0
- cuju/render/web/assets/sounds/applause.ogg +0 -0
- cuju/render/web/assets/sounds/beach-wave-1.ogg +0 -0
- cuju/render/web/assets/sounds/beach-wave-2.ogg +0 -0
- cuju/render/web/assets/sounds/beach-wave-3.ogg +0 -0
- cuju/render/web/assets/sounds/beach-wave-4.ogg +0 -0
- cuju/render/web/assets/sounds/city-horn.ogg +0 -0
- cuju/render/web/assets/sounds/city-loop.ogg +0 -0
- cuju/render/web/assets/sounds/city-siren.ogg +0 -0
- cuju/render/web/assets/sounds/crowd-cheer.ogg +0 -0
- cuju/render/web/assets/sounds/crowd-loop.ogg +0 -0
- cuju/render/web/assets/sounds/crowd-ooh.ogg +0 -0
- cuju/render/web/assets/sounds/gulls.ogg +0 -0
- cuju/render/web/assets/sounds/neon-sting.ogg +0 -0
- cuju/render/web/assets/sounds/rain-loop.ogg +0 -0
- cuju/render/web/assets/sounds/sounds.json +46 -0
- cuju/render/web/assets/sounds/space-beep.ogg +0 -0
- cuju/render/web/assets/sounds/space-hum.ogg +0 -0
- cuju/render/web/assets/sounds/space-radio.ogg +0 -0
- cuju/render/web/assets/sounds/whistle-long.ogg +0 -0
- cuju/render/web/assets/sounds/whistle-short.ogg +0 -0
- cuju/render/web/assets/sounds/wind-loop.ogg +0 -0
- cuju/render/web/easy.css +357 -0
- cuju/render/web/export.html +23 -0
- cuju/render/web/feed.css +262 -0
- cuju/render/web/feed.html +28 -0
- cuju/render/web/flat.css +120 -0
- cuju/render/web/home.css +837 -0
- cuju/render/web/index.html +373 -0
- cuju/render/web/js/boot.js +50 -0
- cuju/render/web/js/broadcast/audio.js +2141 -0
- cuju/render/web/js/broadcast/celebrate.js +1238 -0
- cuju/render/web/js/broadcast/decisions.js +609 -0
- cuju/render/web/js/broadcast/hud.js +352 -0
- cuju/render/web/js/broadcast/show.js +3614 -0
- cuju/render/web/js/broadcast/transition.js +2231 -0
- cuju/render/web/js/broadcast/voices.js +153 -0
- cuju/render/web/js/capture.js +101 -0
- cuju/render/web/js/core/accessibility.js +90 -0
- cuju/render/web/js/core/api.js +429 -0
- cuju/render/web/js/core/comfort.js +135 -0
- cuju/render/web/js/core/i18n.js +705 -0
- cuju/render/web/js/core/icons.js +167 -0
- cuju/render/web/js/core/input-settings.js +156 -0
- cuju/render/web/js/core/keys.js +277 -0
- cuju/render/web/js/core/language-ui.js +125 -0
- cuju/render/web/js/core/menu.js +853 -0
- cuju/render/web/js/core/names.js +113 -0
- cuju/render/web/js/core/page-structure.js +110 -0
- cuju/render/web/js/core/scenarios.js +109 -0
- cuju/render/web/js/core/spoilers.js +236 -0
- cuju/render/web/js/core/typeahead.js +38 -0
- cuju/render/web/js/export/dom-picture.js +330 -0
- cuju/render/web/js/export/export-host.js +97 -0
- cuju/render/web/js/export/feed-dialog.js +191 -0
- cuju/render/web/js/export/feed-params.js +266 -0
- cuju/render/web/js/export/feed.js +363 -0
- cuju/render/web/js/export/mp4-mux.js +295 -0
- cuju/render/web/js/export/photo-core.js +313 -0
- cuju/render/web/js/export/photo.js +1314 -0
- cuju/render/web/js/export/safe-frame.js +59 -0
- cuju/render/web/js/export/size-fields.js +103 -0
- cuju/render/web/js/export/sizes.js +178 -0
- cuju/render/web/js/export/video-boot.js +55 -0
- cuju/render/web/js/export/video-browser.js +325 -0
- cuju/render/web/js/export/video-export.js +417 -0
- cuju/render/web/js/export/video-plan.js +207 -0
- cuju/render/web/js/export/video-range.js +185 -0
- cuju/render/web/js/export/webm-mux.js +198 -0
- cuju/render/web/js/feed-boot.js +151 -0
- cuju/render/web/js/feed-frame.js +101 -0
- cuju/render/web/js/help/doctor.js +488 -0
- cuju/render/web/js/help/firstrun.js +379 -0
- cuju/render/web/js/help/help-center.js +438 -0
- cuju/render/web/js/help/install-offer.js +164 -0
- cuju/render/web/js/help/install-state.js +104 -0
- cuju/render/web/js/help/lessons-core.js +207 -0
- cuju/render/web/js/help/lessons.js +463 -0
- cuju/render/web/js/help/palette.js +584 -0
- cuju/render/web/js/help/pwa.js +257 -0
- cuju/render/web/js/help/tour-core.js +241 -0
- cuju/render/web/js/help/tour.js +461 -0
- cuju/render/web/js/main.js +2513 -0
- cuju/render/web/js/pages/draw.js +188 -0
- cuju/render/web/js/pages/fieldpage.js +530 -0
- cuju/render/web/js/pages/home.js +521 -0
- cuju/render/web/js/pages/matches.js +779 -0
- cuju/render/web/js/pages/moments-ui.js +359 -0
- cuju/render/web/js/pages/queue-filters.js +63 -0
- cuju/render/web/js/pages/recording-groups.js +80 -0
- cuju/render/web/js/pages/recordings.js +1069 -0
- cuju/render/web/js/play/bench.js +347 -0
- cuju/render/web/js/play/challenges.js +1302 -0
- cuju/render/web/js/play/medals.js +127 -0
- cuju/render/web/js/play/play.js +1953 -0
- cuju/render/web/js/play/teasers.js +196 -0
- cuju/render/web/js/play/touch.js +350 -0
- cuju/render/web/js/play/tournament-layout.js +487 -0
- cuju/render/web/js/play/tournament-results.js +441 -0
- cuju/render/web/js/play/tournament-strict.js +560 -0
- cuju/render/web/js/play/tournament.js +2585 -0
- cuju/render/web/js/players/animate.js +972 -0
- cuju/render/web/js/players/clips.js +224 -0
- cuju/render/web/js/players/kaykit.js +162 -0
- cuju/render/web/js/players/kits.js +676 -0
- cuju/render/web/js/players/number-patch.js +82 -0
- cuju/render/web/js/players/players.js +2694 -0
- cuju/render/web/js/players/recolor.js +893 -0
- cuju/render/web/js/players/retarget.js +325 -0
- cuju/render/web/js/players/rig-numbers.js +174 -0
- cuju/render/web/js/presentation/animator.js +231 -0
- cuju/render/web/js/presentation/assign.js +85 -0
- cuju/render/web/js/presentation/behaviors.js +440 -0
- cuju/render/web/js/presentation/budget.js +209 -0
- cuju/render/web/js/presentation/catalog.js +1674 -0
- cuju/render/web/js/presentation/clocks.js +72 -0
- cuju/render/web/js/presentation/commands.js +301 -0
- cuju/render/web/js/presentation/contract.js +240 -0
- cuju/render/web/js/presentation/effects.js +904 -0
- cuju/render/web/js/presentation/flash.js +605 -0
- cuju/render/web/js/presentation/freeze-worklet.js +22 -0
- cuju/render/web/js/presentation/glsl.js +464 -0
- cuju/render/web/js/presentation/host.js +323 -0
- cuju/render/web/js/presentation/index.js +2066 -0
- cuju/render/web/js/presentation/loudness-worklet.js +235 -0
- cuju/render/web/js/presentation/loudness.js +740 -0
- cuju/render/web/js/presentation/moments.js +536 -0
- cuju/render/web/js/presentation/post.js +327 -0
- cuju/render/web/js/presentation/preview.js +180 -0
- cuju/render/web/js/presentation/protocol.js +174 -0
- cuju/render/web/js/presentation/reach.js +158 -0
- cuju/render/web/js/presentation/rigging.js +130 -0
- cuju/render/web/js/presentation/sandbox.js +157 -0
- cuju/render/web/js/presentation/shaders.js +235 -0
- cuju/render/web/js/presentation/stage.js +402 -0
- cuju/render/web/js/presentation/teamaudio.js +722 -0
- cuju/render/web/js/presentation/tracks.js +197 -0
- cuju/render/web/js/presentation/values.js +81 -0
- cuju/render/web/js/recording/annotation-data.js +203 -0
- cuju/render/web/js/recording/captions.js +491 -0
- cuju/render/web/js/recording/codec.js +185 -0
- cuju/render/web/js/recording/decode-worker.js +70 -0
- cuju/render/web/js/recording/decode.js +94 -0
- cuju/render/web/js/recording/episodes.js +46 -0
- cuju/render/web/js/recording/format.js +461 -0
- cuju/render/web/js/recording/live.js +348 -0
- cuju/render/web/js/recording/moments-query.js +59 -0
- cuju/render/web/js/recording/moments.js +149 -0
- cuju/render/web/js/recording/pose.js +86 -0
- cuju/render/web/js/recording/timeline.js +275 -0
- cuju/render/web/js/teams/art.js +50 -0
- cuju/render/web/js/teams/clip-editor.js +293 -0
- cuju/render/web/js/teams/editor.js +1979 -0
- cuju/render/web/js/teams/library.js +344 -0
- cuju/render/web/js/teams/painter.js +633 -0
- cuju/render/web/js/teams/surprise-ui.js +195 -0
- cuju/render/web/js/teams/surprise.js +497 -0
- cuju/render/web/js/teams/team-check.js +127 -0
- cuju/render/web/js/watch/annotations.js +270 -0
- cuju/render/web/js/watch/flat.js +366 -0
- cuju/render/web/js/watch/grid.js +436 -0
- cuju/render/web/js/watch/pitch2d.js +289 -0
- cuju/render/web/js/watch/player-tools.js +192 -0
- cuju/render/web/js/watch/presets.js +201 -0
- cuju/render/web/js/world/arenas.js +997 -0
- cuju/render/web/js/world/ball.js +204 -0
- cuju/render/web/js/world/cameras.js +982 -0
- cuju/render/web/js/world/crowd.js +376 -0
- cuju/render/web/js/world/field.js +227 -0
- cuju/render/web/js/world/framing.js +145 -0
- cuju/render/web/js/world/occlusion.js +48 -0
- cuju/render/web/js/world/pitch.js +703 -0
- cuju/render/web/js/world/scene.js +395 -0
- cuju/render/web/js/world/sky.js +325 -0
- cuju/render/web/js/world/stadium.js +1262 -0
- cuju/render/web/js/world/vendor-sky.js +367 -0
- cuju/render/web/js/world/weather.js +264 -0
- cuju/render/web/languages/en.json +4750 -0
- cuju/render/web/lessons.json +137 -0
- cuju/render/web/manifest.webmanifest +23 -0
- cuju/render/web/style.css +8399 -0
- cuju/render/web/sw.js +235 -0
- cuju/render/web/vendor/fabric/LICENSE +16 -0
- cuju/render/web/vendor/fabric/fabric.min.mjs +1 -0
- cuju/render/web/vendor/lucide/LICENSE +43 -0
- cuju/render/web/vendor/lucide/icons.js +141 -0
- cuju/render/web/vendor/three/LICENSE +21 -0
- cuju/render/web/vendor/three/build/three.core.js +60586 -0
- cuju/render/web/vendor/three/build/three.module.js +19719 -0
- cuju/render/web/vendor/three/examples/jsm/loaders/GLTFLoader.js +4925 -0
- cuju/render/web/vendor/three/examples/jsm/postprocessing/EffectComposer.js +365 -0
- cuju/render/web/vendor/three/examples/jsm/postprocessing/MaskPass.js +195 -0
- cuju/render/web/vendor/three/examples/jsm/postprocessing/OutputPass.js +148 -0
- cuju/render/web/vendor/three/examples/jsm/postprocessing/Pass.js +191 -0
- cuju/render/web/vendor/three/examples/jsm/postprocessing/RenderPass.js +193 -0
- cuju/render/web/vendor/three/examples/jsm/postprocessing/ShaderPass.js +135 -0
- cuju/render/web/vendor/three/examples/jsm/postprocessing/UnrealBloomPass.js +541 -0
- cuju/render/web/vendor/three/examples/jsm/shaders/CopyShader.js +52 -0
- cuju/render/web/vendor/three/examples/jsm/shaders/LuminosityHighPassShader.js +68 -0
- cuju/render/web/vendor/three/examples/jsm/shaders/OutputShader.js +103 -0
- cuju/render/web/vendor/three/examples/jsm/utils/BufferGeometryUtils.js +1501 -0
- cuju/render/web/vendor/three/examples/jsm/utils/SkeletonUtils.js +496 -0
- cuju_render-0.1.0.dist-info/METADATA +116 -0
- cuju_render-0.1.0.dist-info/RECORD +382 -0
- cuju_render-0.1.0.dist-info/WHEEL +4 -0
- cuju_render-0.1.0.dist-info/licenses/LICENSE +202 -0
- cuju_render-0.1.0.dist-info/licenses/LICENSES/CC-BY-4.0.txt +396 -0
- cuju_render-0.1.0.dist-info/licenses/LICENSES/CC0-1.0.txt +127 -0
- cuju_render-0.1.0.dist-info/licenses/LICENSES/ISC.txt +16 -0
- cuju_render-0.1.0.dist-info/licenses/LICENSES/MIT.txt +24 -0
- cuju_render-0.1.0.dist-info/licenses/LICENSES/Unlicense.txt +13 -0
- cuju_render-0.1.0.dist-info/licenses/NOTICE +35 -0
|
@@ -0,0 +1,1396 @@
|
|
|
1
|
+
# Copyright 2026 The cuju Authors
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
"""The messages of the server that the page shows to people, each with an id and English text.
|
|
5
|
+
|
|
6
|
+
An answer or an entry that the page shows carries the English text in `error`, as it always has,
|
|
7
|
+
and beside it the id of its message in `error_id` and the values of its placeholders in
|
|
8
|
+
`error_values`, so the page shows the text in its own language. The command line and every
|
|
9
|
+
English answer read the same text as before. A template names its placeholders in braces, such
|
|
10
|
+
as `{scenario}`, as the language files of the page do, and Python fills it the way the page does,
|
|
11
|
+
with every value as text, so both read the same.
|
|
12
|
+
|
|
13
|
+
`MESSAGES` holds every id with its English template, and the part `server` of the language
|
|
14
|
+
files holds the same ids, which `compare` checks. A template whose words agree with a number is
|
|
15
|
+
an object of plural forms, keyed `one` and `other` and by exact counts such as `=0`, as in the
|
|
16
|
+
language files, and the value `count` picks the form. A template may mark words as the language
|
|
17
|
+
files do, **strong** and `code`, which the page shows as such, while `text` reads them without the
|
|
18
|
+
marks, so the command line and English answers read plain text.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import importlib
|
|
24
|
+
import re
|
|
25
|
+
import sys
|
|
26
|
+
from typing import Any
|
|
27
|
+
|
|
28
|
+
from cuju.errors import ArgumentError
|
|
29
|
+
|
|
30
|
+
# A placeholder of a template, as the page's own runtime, web/js/core/i18n.js, reads it. Any other
|
|
31
|
+
# brace, such as that of `{"colors": [...]}`, is text.
|
|
32
|
+
_PLACEHOLDER = re.compile(r'\{([A-Za-z0-9_]+)\}')
|
|
33
|
+
# The marks of a template, strong emphasis in double asterisks and code in backticks, as the page
|
|
34
|
+
# reads them. A mark that does not close is text.
|
|
35
|
+
_MARK = re.compile(r'\*\*(.+?)\*\*|`([^`]+)`')
|
|
36
|
+
|
|
37
|
+
MESSAGES: dict[str, str | dict[str, str]] = {
|
|
38
|
+
'server.bench.challenges': 'expected a list of distinct challenges, found {challenges}',
|
|
39
|
+
'server.bench.fields': (
|
|
40
|
+
'expected teams, challenges, keep_recordings and workers, found {unknown}'
|
|
41
|
+
),
|
|
42
|
+
'server.bench.no_bench': 'no such benchmark',
|
|
43
|
+
'server.bench.teams': 'expected a list of distinct teams of the registry, found {teams}',
|
|
44
|
+
'server.bench.workers': 'expected from 1 to {most} runs at once, found {workers}',
|
|
45
|
+
'server.body_size': 'expected a request body of at most 110 MB',
|
|
46
|
+
'server.challenges.ability': 'expected the ability {ability}, found {found}',
|
|
47
|
+
'server.challenges.attempt': 'expected an attempt from 0 to {last}, found {attempt}',
|
|
48
|
+
'server.challenges.attempt_recording': (
|
|
49
|
+
'the recording {match} cannot be attempt {attempt}, {reason}'
|
|
50
|
+
),
|
|
51
|
+
'server.challenges.attempts': 'expected from 1 to {most} attempts, found {attempts}',
|
|
52
|
+
'server.challenges.default_name': 'expected default_name as true or false, found {value}',
|
|
53
|
+
'server.challenges.distinct': 'expected a distinct recording for each attempt',
|
|
54
|
+
'server.challenges.exit': 'the run ended with exit code {code}',
|
|
55
|
+
'server.challenges.exit_detail': 'the run ended with exit code {code}, {detail}',
|
|
56
|
+
'server.challenges.keep_recordings': 'expected keep_recordings as true or false, found {value}',
|
|
57
|
+
'server.challenges.left_matches': {
|
|
58
|
+
'one': 'expected the recording of the attempt played, found {matches}',
|
|
59
|
+
'other': 'expected from 1 to {count} recordings of the attempts played, found {matches}',
|
|
60
|
+
},
|
|
61
|
+
'server.challenges.matches': {
|
|
62
|
+
'one': 'expected the recording of the attempt, found {matches}',
|
|
63
|
+
'other': 'expected the {count} recordings of the attempts in order, found {matches}',
|
|
64
|
+
},
|
|
65
|
+
'server.challenges.name_length': 'expected a name of 1 to {most} characters, found {value}',
|
|
66
|
+
'server.challenges.name_text': 'expected a name as text, found {value}',
|
|
67
|
+
'server.challenges.no_challenge': 'no challenge {identifier}',
|
|
68
|
+
'server.challenges.no_provenance': 'the recording holds no provenance',
|
|
69
|
+
'server.challenges.no_run': 'no such run',
|
|
70
|
+
'server.challenges.person_fields': (
|
|
71
|
+
'expected person, matches, keep_recordings and default_name, found {unknown}'
|
|
72
|
+
),
|
|
73
|
+
'server.challenges.policy': 'expected a policy function, found {policy}',
|
|
74
|
+
'server.challenges.provenance': 'expected `{key}` {value} for attempt {attempt}, found {found}',
|
|
75
|
+
'server.challenges.recording': 'expected a recording of the library, found {match}',
|
|
76
|
+
'server.challenges.run_fields': 'expected a person with matches, or a team with attempts',
|
|
77
|
+
'server.challenges.team': 'expected a team of the registry, found {team}',
|
|
78
|
+
'server.challenges.team_or_policy': 'expected either a team of the registry or a policy',
|
|
79
|
+
'server.challenges.unknown': 'expected a challenge, one of {challenges}, found {identifier}',
|
|
80
|
+
'server.clips.animation': 'expected animation {index} to hold channels and samplers',
|
|
81
|
+
'server.clips.animations_count': 'expected at most {most} animations in a clip, found {count}',
|
|
82
|
+
'server.clips.animations_none': 'expected a clip with at least one animation, found none',
|
|
83
|
+
'server.clips.asset_version': 'expected a glTF 2.0 file, whose asset names version 2.0',
|
|
84
|
+
'server.clips.binary': 'expected a binary glTF file, .glb, found other bytes',
|
|
85
|
+
'server.clips.body': 'expected a bone for the joint body, the pelvis, which every rig has',
|
|
86
|
+
'server.clips.buffer': 'expected the binary chunk that the buffer of the file needs',
|
|
87
|
+
'server.clips.buffer_length': 'expected a binary chunk as long as the buffer of the file',
|
|
88
|
+
'server.clips.channels': 'expected the channels of animation {index} to name nodes',
|
|
89
|
+
'server.clips.chunks': 'expected one JSON chunk and at most one binary chunk after it',
|
|
90
|
+
'server.clips.count': 'expected at most {most} clips for a team',
|
|
91
|
+
'server.clips.cut': 'expected whole chunks in the binary glTF file, found a cut one',
|
|
92
|
+
'server.clips.extension': 'expected a clip without {extension}, which the page cannot read',
|
|
93
|
+
'server.clips.joints': 'expected the joints as names of bones by joint, found {joints}',
|
|
94
|
+
'server.clips.json': 'expected the JSON chunk of a glTF file, found {error}',
|
|
95
|
+
'server.clips.json_first': 'expected a binary glTF file with its JSON chunk first',
|
|
96
|
+
'server.clips.json_object': 'expected the JSON chunk of a glTF file to hold an object',
|
|
97
|
+
'server.clips.length': (
|
|
98
|
+
'expected a binary glTF file of the length its header gives, {length} bytes, found {size}'
|
|
99
|
+
),
|
|
100
|
+
'server.clips.link': 'expected a self-contained clip, found a link to {uri}',
|
|
101
|
+
'server.clips.missing_bones': 'expected bones of the clip, found {bones}',
|
|
102
|
+
'server.clips.name': (
|
|
103
|
+
'expected a clip name of up to 40 lowercase letters, digits and underscores, found {name}'
|
|
104
|
+
),
|
|
105
|
+
'server.clips.padding': 'expected chunks padded to four bytes inside the file',
|
|
106
|
+
'server.clips.size': 'expected a clip of at most 10 MB, found {size} bytes',
|
|
107
|
+
'server.clips.skin_joints': 'expected a skin with joints, found an empty one',
|
|
108
|
+
'server.clips.skinned': 'expected a skinned skeleton, a glTF file with a skin, found none',
|
|
109
|
+
'server.clips.total': (
|
|
110
|
+
'expected at most 40 MB of clips for a team, found {total} bytes with this one'
|
|
111
|
+
),
|
|
112
|
+
'server.clips.unknown': 'expected a clip of the team, found {name}',
|
|
113
|
+
'server.clips.unknown_joints': "expected joints of GRF's skeleton, found {joints}",
|
|
114
|
+
'server.clips.version': 'expected a binary glTF file of version 2, found version {version}',
|
|
115
|
+
'server.content_length': 'expected a Content-Length of digits',
|
|
116
|
+
'server.doctor.check.disk_space': 'disk space',
|
|
117
|
+
'server.doctor.check.home_folder': 'home folder',
|
|
118
|
+
'server.doctor.check.page_files': 'page files',
|
|
119
|
+
'server.doctor.check.samples': 'samples',
|
|
120
|
+
'server.doctor.check.server_file': 'server file',
|
|
121
|
+
'server.doctor.check.server_version': 'server version',
|
|
122
|
+
'server.doctor.check.video_export': 'video export',
|
|
123
|
+
'server.doctor.check.writing': 'writing',
|
|
124
|
+
'server.doctor.disk.free': '{size} GB free',
|
|
125
|
+
'server.doctor.disk.low': '{size} GB free, recordings **may fail** to save',
|
|
126
|
+
'server.doctor.disk.unknown': 'unknown',
|
|
127
|
+
'server.doctor.home.exists': '{path} exists',
|
|
128
|
+
'server.doctor.home.file': 'the home folder is a file',
|
|
129
|
+
'server.doctor.home.missing': 'the home folder is missing, the server makes it as it starts',
|
|
130
|
+
'server.doctor.home.readonly': 'the home folder is **not writable**',
|
|
131
|
+
'server.doctor.home.under': 'the home folder lies under a file',
|
|
132
|
+
'server.doctor.page.missing': 'the page is missing, **install** `cuju` again',
|
|
133
|
+
'server.doctor.page.ok': 'the files of the page are in place',
|
|
134
|
+
'server.doctor.samples': {
|
|
135
|
+
'one': '{count} sample recording ships with the package',
|
|
136
|
+
'other': '{count} sample recordings ship with the package',
|
|
137
|
+
},
|
|
138
|
+
'server.doctor.server_file.none': 'no server runs',
|
|
139
|
+
'server.doctor.server_file.running': 'the server of the folder answers',
|
|
140
|
+
'server.doctor.server_file.stale': (
|
|
141
|
+
'a stale `server.json` names a server that does not answer, the next start replaces it'
|
|
142
|
+
),
|
|
143
|
+
'server.doctor.server_version': (
|
|
144
|
+
'the server runs cuju {server}, this command {command}, **restart** the server'
|
|
145
|
+
),
|
|
146
|
+
'server.doctor.video.found': 'a browser is found, `{browser}`',
|
|
147
|
+
'server.doctor.writing.fail': (
|
|
148
|
+
'the server **cannot write** its folder, choose another with `--home`'
|
|
149
|
+
),
|
|
150
|
+
'server.doctor.writing.ok': 'the server can write its folder',
|
|
151
|
+
'server.draw.left': (
|
|
152
|
+
'expected a team of the pool that can play the left side of {scenario}, found none'
|
|
153
|
+
),
|
|
154
|
+
'server.draw.pool': 'expected the pool as a list of teams of the registry, found {pool}',
|
|
155
|
+
'server.draw.right': (
|
|
156
|
+
'expected a team of the pool that can play the right side of {scenario}, found none'
|
|
157
|
+
),
|
|
158
|
+
'server.draw.same_team': 'expected same_team as true or false, found {value}',
|
|
159
|
+
'server.draw.scenario': (
|
|
160
|
+
'expected a scenario that a team of the pool can play on each side, found none'
|
|
161
|
+
),
|
|
162
|
+
'server.feed.follow.auto': 'a live source while one streams, else the newest recording',
|
|
163
|
+
'server.feed.follow.latest': 'the newest recording of the library',
|
|
164
|
+
'server.feed.follow.live': 'the newest live source',
|
|
165
|
+
'server.feed.follow.live_named': 'the live source {name}',
|
|
166
|
+
'server.feed.follow.queue': 'the newest finished requested match',
|
|
167
|
+
'server.feed.follow.tournament': 'the newest finished match of the newest tournament',
|
|
168
|
+
'server.feed.follow.tournament_named': 'the matches of the tournament {name}',
|
|
169
|
+
'server.feed.no_live': 'No live source streams now',
|
|
170
|
+
'server.feed.no_live_named': 'No live source named {name} streams now',
|
|
171
|
+
'server.feed.no_recording': 'The library holds no recording {name}',
|
|
172
|
+
'server.feed.no_recordings': 'The library holds no recording yet',
|
|
173
|
+
'server.feed.no_request': 'No requested match has finished yet',
|
|
174
|
+
'server.feed.no_tournament': 'No tournament yet',
|
|
175
|
+
'server.feed.no_tournament_named': 'No tournament named {name}',
|
|
176
|
+
'server.feed.nothing_after': 'expected nothing after the source {source}, found {value}',
|
|
177
|
+
'server.feed.recording_name': 'expected the identifier of a recording after recording:',
|
|
178
|
+
'server.feed.source': (
|
|
179
|
+
'expected a source of {sources}, a kind with a name such as live:Evaluation, or the '
|
|
180
|
+
'identifier of a recording, found {value}'
|
|
181
|
+
),
|
|
182
|
+
'server.feed.tournament_waits': '{name} has no finished match yet',
|
|
183
|
+
'server.feed.tournament_waits_unnamed': 'The tournament has no finished match yet',
|
|
184
|
+
'server.game.answered': 'the game answered {status}',
|
|
185
|
+
'server.game.busy': 'a game already plays, quit it first',
|
|
186
|
+
'server.game.exit': 'the game ended with exit code {code}',
|
|
187
|
+
'server.game.exit_detail': 'the game ended with exit code {code}, {detail}',
|
|
188
|
+
'server.game.humans': (
|
|
189
|
+
'expected two flags for a person on the left and right, one true, found {humans}'
|
|
190
|
+
),
|
|
191
|
+
'server.game.input_fields': 'expected side, held and pressed, found {unknown}',
|
|
192
|
+
'server.game.input_list': 'expected {key} as a list of {allowed}',
|
|
193
|
+
'server.game.paused': 'expected paused as true or false, found {paused}',
|
|
194
|
+
'server.game.person': 'expected a person on at least one side',
|
|
195
|
+
'server.game.record': 'expected record as true or false, found {record}',
|
|
196
|
+
'server.game.registry_teams': 'expected two registry teams, found {teams}',
|
|
197
|
+
'server.game.side': 'expected a side that a person plays in this game, {sides}, found {side}',
|
|
198
|
+
'server.game.sides_both': '0 for the left or 1 for the right',
|
|
199
|
+
'server.game.sides_left': '0 for the left',
|
|
200
|
+
'server.game.sides_right': '1 for the right',
|
|
201
|
+
'server.game.silent': 'the game did not answer, {error}',
|
|
202
|
+
'server.game.teams': 'expected two registry teams, left and right, found {teams}',
|
|
203
|
+
'server.game.unknown': 'no such game',
|
|
204
|
+
'server.help.sample_names': 'expected names as a list of samples, found {names}',
|
|
205
|
+
'server.invalid': 'the server could not take the request, {error}',
|
|
206
|
+
'server.json': (
|
|
207
|
+
'expected a request body of JSON, found a mistake at line {line}, column {column}'
|
|
208
|
+
),
|
|
209
|
+
'server.json_object': 'expected a JSON object',
|
|
210
|
+
'server.json_query': (
|
|
211
|
+
'expected `{name}` as JSON, found a mistake at line {line}, column {column}'
|
|
212
|
+
),
|
|
213
|
+
'server.json_text': 'expected a request body of JSON in UTF-8',
|
|
214
|
+
'server.language.code': (
|
|
215
|
+
'expected a language code of letters and digits in parts joined by hyphens, such as pt or '
|
|
216
|
+
'pt-BR, found {code}'
|
|
217
|
+
),
|
|
218
|
+
'server.language.json': 'expected a language file, a JSON object with its header @language',
|
|
219
|
+
'server.language.keep': 'the language {code} ships with cuju and stays',
|
|
220
|
+
'server.language.malformed': 'expected well formed messages, found {first}',
|
|
221
|
+
'server.language.none': 'no language {code}',
|
|
222
|
+
'server.language.problem.caption': (
|
|
223
|
+
'expected a caption as a text, captions have no plural forms'
|
|
224
|
+
),
|
|
225
|
+
'server.language.problem.categories': (
|
|
226
|
+
'expected the plural categories of {language}, the file lacks {lacking}'
|
|
227
|
+
),
|
|
228
|
+
'server.language.problem.code': (
|
|
229
|
+
'expected a code of letters and digits in parts joined by hyphens, such as `pt` or '
|
|
230
|
+
'`pt-BR`, found {code}'
|
|
231
|
+
),
|
|
232
|
+
'server.language.problem.dir': 'expected `dir` as `ltr` or `rtl`, found {value}',
|
|
233
|
+
'server.language.problem.document': 'expected a JSON object keyed by message id',
|
|
234
|
+
'server.language.problem.extra': 'expected the placeholders of English, found {extra}',
|
|
235
|
+
'server.language.problem.file_code': 'expected the code {code} of the file name, found {found}',
|
|
236
|
+
'server.language.problem.form_names': (
|
|
237
|
+
'expected plural categories or exact counts such as `=0`, found {unknown}'
|
|
238
|
+
),
|
|
239
|
+
'server.language.problem.form_texts': 'expected plural forms as texts',
|
|
240
|
+
'server.language.problem.forms_expected': 'expected plural forms, as the English message has',
|
|
241
|
+
'server.language.problem.forms_lacking': 'expected the plural forms {lacking} of the language',
|
|
242
|
+
'server.language.problem.header': (
|
|
243
|
+
'expected `@language` as an object with code, name, dir and plurals'
|
|
244
|
+
),
|
|
245
|
+
'server.language.problem.id': 'expected an id of letters, digits, _, . and -',
|
|
246
|
+
'server.language.problem.kind': 'expected a text or an object of plural forms',
|
|
247
|
+
'server.language.problem.lost': 'expected the placeholders {lost} of English',
|
|
248
|
+
'server.language.problem.marks': (
|
|
249
|
+
'expected as many code spans as English, for its commands, names and shortcuts'
|
|
250
|
+
),
|
|
251
|
+
'server.language.problem.name': (
|
|
252
|
+
'expected the name of the language in itself, such as Português (Brasil)'
|
|
253
|
+
),
|
|
254
|
+
'server.language.problem.other': 'expected the plural category `other` among the plurals',
|
|
255
|
+
'server.language.problem.plurals': (
|
|
256
|
+
'expected `plurals` as a list of the categories {categories}, found {value}'
|
|
257
|
+
),
|
|
258
|
+
'server.language.problem.text': 'expected `{key}` as text, found {value}',
|
|
259
|
+
'server.language.problem.text_expected': 'expected a text, as the English message is',
|
|
260
|
+
'server.language.problem_at': '{id}, {problem}',
|
|
261
|
+
'server.language.shipped': (
|
|
262
|
+
'the language {code} ships with cuju, name your file with another code, '
|
|
263
|
+
'such as {code}-x-mine'
|
|
264
|
+
),
|
|
265
|
+
'server.language.size': 'expected a language file of at most {most} MB',
|
|
266
|
+
'server.library.favorite': 'expected favorite as true or false, found {favorite}',
|
|
267
|
+
'server.library.folder_size': (
|
|
268
|
+
'expected a folder of at most 8 names of 64 characters, found {folder}'
|
|
269
|
+
),
|
|
270
|
+
'server.library.folder_text': 'expected a folder as text, found {folder}',
|
|
271
|
+
'server.library.group_trash': 'no such group of recordings in the Trash',
|
|
272
|
+
'server.library.group_unknown': 'no such group of recordings in the library',
|
|
273
|
+
'server.library.notes': 'expected notes as text of at most ten thousand characters',
|
|
274
|
+
'server.library.samples': 'expected a sample from {shipped}, found {unknown}',
|
|
275
|
+
'server.library.tags': 'expected up to twenty tags, each of one to forty characters',
|
|
276
|
+
'server.library.title': 'expected a title with at least one character',
|
|
277
|
+
'server.library.unknown': 'expected a recording of the library, found {identifier}',
|
|
278
|
+
'server.live.answered': 'the live source answered {status}',
|
|
279
|
+
'server.live.environment': 'expected an environment from 0 to {last}, found {environment}',
|
|
280
|
+
'server.live.grid_range': (
|
|
281
|
+
'expected a first environment from 0 to {last} and a count from 1 to {most}, found '
|
|
282
|
+
'{first} and {count}'
|
|
283
|
+
),
|
|
284
|
+
'server.live.no_grid': 'this live source offers no grid, it comes from an older cuju',
|
|
285
|
+
'server.live.silent': 'the live source did not answer, {error}',
|
|
286
|
+
'server.live.unknown': 'no such live source',
|
|
287
|
+
'server.live.whole_number': 'expected a whole number for {name}, found {text}',
|
|
288
|
+
'server.loopback': 'expected a request to this server by its loopback address',
|
|
289
|
+
# Names and titles that cuju makes, which travel as labels.
|
|
290
|
+
'server.names.against': '{left} against {right}',
|
|
291
|
+
'server.names.builtin': "GRF's built-in AI",
|
|
292
|
+
'server.names.challenge_attempt': '{challenge}, attempt {attempt}, {competitor}',
|
|
293
|
+
'server.names.challenge_run': '{challenge}, {competitor}',
|
|
294
|
+
'server.names.default_person': 'Player',
|
|
295
|
+
'server.names.demo': 'Demo, the built-in AI against itself',
|
|
296
|
+
'server.names.left': 'Left',
|
|
297
|
+
'server.names.live': '{scenario}, environment {environment}, process {process}',
|
|
298
|
+
'server.names.medal_bronze': 'bronze',
|
|
299
|
+
'server.names.medal_gold': 'gold',
|
|
300
|
+
'server.names.medal_silver': 'silver',
|
|
301
|
+
'server.names.person_with': '{person} with {team}',
|
|
302
|
+
'server.names.player': 'Player {number}',
|
|
303
|
+
'server.names.python_policy': 'Python policy',
|
|
304
|
+
'server.names.random': 'Random',
|
|
305
|
+
'server.names.recorder': '{scenario}, environment {environment}',
|
|
306
|
+
'server.names.right': 'Right',
|
|
307
|
+
'server.names.run_attempts': '{value} of {attempts}',
|
|
308
|
+
'server.names.run_left': 'left after {played} of {attempts}',
|
|
309
|
+
'server.names.run_margin': 'goal difference {value}',
|
|
310
|
+
'server.names.run_medal': '{value}, {medal} medal',
|
|
311
|
+
'server.names.run_no_medal': '{value}, no medal',
|
|
312
|
+
'server.names.run_playing': 'under way, {played} of {attempts} played',
|
|
313
|
+
'server.names.run_practice': '{value}, practice',
|
|
314
|
+
'server.names.run_stopped': 'stopped after {played} of {attempts}',
|
|
315
|
+
'server.names.sample_11v11': 'Sample match, 11 vs 11',
|
|
316
|
+
'server.names.sample_5v5': 'Sample match, 5 vs 5',
|
|
317
|
+
'server.names.you': 'You',
|
|
318
|
+
'server.no_clip': 'no such clip',
|
|
319
|
+
'server.no_file': 'no such file',
|
|
320
|
+
'server.no_image': 'no such image',
|
|
321
|
+
'server.no_item': 'no such item',
|
|
322
|
+
'server.no_match': 'no such match',
|
|
323
|
+
'server.no_model': 'no such model',
|
|
324
|
+
'server.no_package_file': 'no such file of the package',
|
|
325
|
+
'server.no_resource': 'no such resource',
|
|
326
|
+
'server.no_sound': 'no such sound',
|
|
327
|
+
'server.origin': 'expected a request from the page of this server',
|
|
328
|
+
'server.play.scenario': 'expected a scenario from the renderer scenario list, found {scenario}',
|
|
329
|
+
'server.policy.action_set': 'expected the v2 action set to hand players to the AI',
|
|
330
|
+
'server.policy.error': 'the policy failed, {reason}',
|
|
331
|
+
'server.policy.error_action': {
|
|
332
|
+
'one': (
|
|
333
|
+
'the policy failed, it answered the action {value}, outside its action set of {count} '
|
|
334
|
+
'action, 0 to {last}'
|
|
335
|
+
),
|
|
336
|
+
'other': (
|
|
337
|
+
'the policy failed, it answered the action {value}, outside its action set of {count} '
|
|
338
|
+
'actions, 0 to {last}'
|
|
339
|
+
),
|
|
340
|
+
},
|
|
341
|
+
'server.policy.failed': 'the policy of {player} failed, {reason}',
|
|
342
|
+
'server.policy.failed_action': {
|
|
343
|
+
'one': (
|
|
344
|
+
'the policy of {player} failed, it answered the action {value}, outside its action set '
|
|
345
|
+
'of {count} action, 0 to {last}'
|
|
346
|
+
),
|
|
347
|
+
'other': (
|
|
348
|
+
'the policy of {player} failed, it answered the action {value}, outside its action set '
|
|
349
|
+
'of {count} actions, 0 to {last}'
|
|
350
|
+
),
|
|
351
|
+
},
|
|
352
|
+
'server.policy.first': {
|
|
353
|
+
'one': 'the policy did not answer its first observation within {count} second',
|
|
354
|
+
'other': 'the policy did not answer its first observation within {count} seconds',
|
|
355
|
+
},
|
|
356
|
+
'server.policy.load': {
|
|
357
|
+
'one': 'the policies did not load within {count} second',
|
|
358
|
+
'other': 'the policies did not load within {count} seconds',
|
|
359
|
+
},
|
|
360
|
+
'server.policy.silent': {
|
|
361
|
+
'one': 'the policy has not answered for {count} second',
|
|
362
|
+
'other': 'the policy has not answered for {count} seconds',
|
|
363
|
+
},
|
|
364
|
+
'server.preflight.failed': 'the check of the team failed, {reason}',
|
|
365
|
+
'server.preflight.left_out': (
|
|
366
|
+
'the pre-flight left out the teams that failed it, and the tournament cannot be made '
|
|
367
|
+
'with the entrants that remain, {error}'
|
|
368
|
+
),
|
|
369
|
+
'server.preflight.timeout': {
|
|
370
|
+
'one': 'the check of the team took longer than {count} minute',
|
|
371
|
+
'other': 'the check of the team took longer than {count} minutes',
|
|
372
|
+
},
|
|
373
|
+
'server.requests.cancelled': 'cancelled',
|
|
374
|
+
'server.requests.exit': 'the match ended with exit code {code}',
|
|
375
|
+
'server.requests.exit_detail': 'the match ended with exit code {code}, {detail}',
|
|
376
|
+
'server.requests.history': 'could not save the request history, {error}',
|
|
377
|
+
'server.requests.interrupted': 'interrupted by a server restart',
|
|
378
|
+
'server.requests.interrupted_playing': (
|
|
379
|
+
'interrupted by a server restart while playing, its recording may still appear in the '
|
|
380
|
+
'library'
|
|
381
|
+
),
|
|
382
|
+
'server.requests.timeout': 'the match ran past {minutes} minutes',
|
|
383
|
+
'server.samples.team': 'expected a sample team from {teams}, found {name}',
|
|
384
|
+
'server.samples.unknown': 'expected samples from {every}, found {unknown}',
|
|
385
|
+
'server.scenario': 'expected a shipped scenario, found {scenario}',
|
|
386
|
+
'server.search.date': 'expected `{key}:` as a date such as 2026-11-10, found {value}',
|
|
387
|
+
'server.search.filter': 'expected a filter of {filters}, found `{key}:`',
|
|
388
|
+
'server.search.kind': 'expected a kind of moment such as `goal` or `corner`, found {name}',
|
|
389
|
+
'server.search.limit': 'expected a limit as a whole number, found {limit}',
|
|
390
|
+
'server.search.limit_range': 'expected a limit from 1 to {most}, found {limit}',
|
|
391
|
+
'server.search.limit_text': 'expected `limit:` as a whole number, found {value}',
|
|
392
|
+
'server.search.minutes': (
|
|
393
|
+
'expected `{key}:` as minutes of the clock, such as 60, 60:30 or 90s, found {value}'
|
|
394
|
+
),
|
|
395
|
+
'server.search.score': (
|
|
396
|
+
'expected `score:` as leading, level, trailing or a score such as 2-1, found {value}'
|
|
397
|
+
),
|
|
398
|
+
'server.search.source': (
|
|
399
|
+
'expected `source:` as requested, game, challenge, recorder, live, tournament or unknown, '
|
|
400
|
+
'found {value}'
|
|
401
|
+
),
|
|
402
|
+
'server.search.target': 'expected a recording of the library or a `.cjr` file, found {target}',
|
|
403
|
+
'server.search.unknown_filter': 'expected a filter, found `{key}:`',
|
|
404
|
+
'server.seed': 'expected a seed from 0 to 2**64 - 1, found {value}',
|
|
405
|
+
'server.sizes.area': (
|
|
406
|
+
'expected a picture of at most {area} pixels, as 3840 by 2160, found {width} by {height}'
|
|
407
|
+
),
|
|
408
|
+
'server.sizes.even_height': 'expected an even height, found {value}',
|
|
409
|
+
'server.sizes.even_width': 'expected an even width, found {value}',
|
|
410
|
+
'server.sizes.graphics': (
|
|
411
|
+
'expected a scale of the graphics from {lowest} to {highest}, found {value}'
|
|
412
|
+
),
|
|
413
|
+
'server.sizes.height': 'expected a height from {lowest} to {highest}, found {value}',
|
|
414
|
+
'server.sizes.safe': (
|
|
415
|
+
'expected a safe area of {modes} or a percentage from 0 to {most}, found {value}'
|
|
416
|
+
),
|
|
417
|
+
'server.sizes.size': 'expected a size such as `1280x720` or one of {presets}, found {value}',
|
|
418
|
+
'server.sizes.width': 'expected a width from {lowest} to {highest}, found {value}',
|
|
419
|
+
'server.strict.both': '{home}, and {away}',
|
|
420
|
+
'server.strict.call_time': {
|
|
421
|
+
'one': '{player} did not answer action call {call} within {count} second',
|
|
422
|
+
'other': '{player} did not answer action call {call} within {count} seconds',
|
|
423
|
+
},
|
|
424
|
+
'server.strict.changed': 'the files of {player} changed since its pre-flight',
|
|
425
|
+
'server.strict.crash': '{player} failed in an action call, {reason}',
|
|
426
|
+
'server.strict.deterministic': (
|
|
427
|
+
'{player} answered differently when the same steps played again from the same seed'
|
|
428
|
+
),
|
|
429
|
+
'server.strict.first_time': {
|
|
430
|
+
'one': '{player} did not answer its first action call within {count} second',
|
|
431
|
+
'other': '{player} did not answer its first action call within {count} seconds',
|
|
432
|
+
},
|
|
433
|
+
'server.strict.folder_size': (
|
|
434
|
+
'{player} holds {size} MB in its folder, above the limit of {limit} MB'
|
|
435
|
+
),
|
|
436
|
+
'server.strict.invalid_count': '{player} answered a wrong count of actions, {reason}',
|
|
437
|
+
'server.strict.invalid_range': '{player} answered an action outside its action set, {reason}',
|
|
438
|
+
'server.strict.invalid_type': '{player} answered an action that is no whole number, {reason}',
|
|
439
|
+
'server.strict.load': '{player} failed to load, {reason}',
|
|
440
|
+
'server.strict.load_time': {
|
|
441
|
+
'one': '{player} did not load within {count} second',
|
|
442
|
+
'other': '{player} did not load within {count} seconds',
|
|
443
|
+
},
|
|
444
|
+
'server.strict.mean': (
|
|
445
|
+
'{player} took {mean} ms for an action call on average, above the limit of {limit} ms'
|
|
446
|
+
),
|
|
447
|
+
'server.strict.memory': '{player} used {used} MB of memory, above the limit of {limit} MB',
|
|
448
|
+
'server.strict.memory_out': '{player} ran out of its memory of {limit} MB',
|
|
449
|
+
'server.strict.process': '{player} started another process, which strict rules refuse',
|
|
450
|
+
'server.strict.reset': '{player} failed in its reset, {reason}',
|
|
451
|
+
'server.strict.reset_time': {
|
|
452
|
+
'one': '{player} did not reset within {count} second',
|
|
453
|
+
'other': '{player} did not reset within {count} seconds',
|
|
454
|
+
},
|
|
455
|
+
'server.strict.threads': (
|
|
456
|
+
'{player} ran {found} threads, above the {limit} that its process may run'
|
|
457
|
+
),
|
|
458
|
+
'server.strict.total_time': (
|
|
459
|
+
'{player} computed for {total} seconds in all, above the limit of {limit} seconds for a '
|
|
460
|
+
'match'
|
|
461
|
+
),
|
|
462
|
+
'server.strict.warning_action': (
|
|
463
|
+
'{player} answered the action {action} in {share}% of its actions.'
|
|
464
|
+
),
|
|
465
|
+
'server.strict.warning_deterministic': (
|
|
466
|
+
'{player} answered differently when the same steps played again from the same seed.'
|
|
467
|
+
),
|
|
468
|
+
'server.strict.warning_setting': 'The setting {setting} of {file} is unknown to cuju render.',
|
|
469
|
+
'server.teams.ability': 'expected an ability from 0 to 1, found {value}',
|
|
470
|
+
'server.teams.ability_pair': 'expected a pair of abilities, left and right, found {ability}',
|
|
471
|
+
'server.teams.agent_function': 'expected an agent function in the submission {identifier}',
|
|
472
|
+
'server.teams.archive.device': (
|
|
473
|
+
'expected archive members whose names open no device on Windows and end in no dot or '
|
|
474
|
+
'space, found {member}'
|
|
475
|
+
),
|
|
476
|
+
'server.teams.archive.limits': (
|
|
477
|
+
'expected an archive of at most 10,000 files and 500 MB unpacked'
|
|
478
|
+
),
|
|
479
|
+
'server.teams.archive.outside': 'expected archive members inside the archive, found {member}',
|
|
480
|
+
'server.teams.archive.plain': 'expected plain files in the archive, found {member}',
|
|
481
|
+
'server.teams.archive.tar': 'expected a readable .tar.gz archive, found {error}',
|
|
482
|
+
'server.teams.archive.zip': 'expected a readable .zip archive, found {error}',
|
|
483
|
+
'server.teams.badge': 'expected a badge of at most three characters, found {value}',
|
|
484
|
+
'server.teams.beside_count': {
|
|
485
|
+
'=0': (
|
|
486
|
+
'{team} cannot play beside a person in {scenario}, which has no controllable player on '
|
|
487
|
+
'that side'
|
|
488
|
+
),
|
|
489
|
+
'one': (
|
|
490
|
+
'{team} cannot play beside a person in {scenario}, which has {count} controllable '
|
|
491
|
+
'player on that side'
|
|
492
|
+
),
|
|
493
|
+
'other': (
|
|
494
|
+
'{team} cannot play beside a person in {scenario}, which has {count} controllable '
|
|
495
|
+
'players on that side'
|
|
496
|
+
),
|
|
497
|
+
},
|
|
498
|
+
'server.teams.beside_dynamic': 'expected dynamic assignment for {team} beside a person',
|
|
499
|
+
'server.teams.cannot_play': '{team} cannot play {scenario}, {reason}',
|
|
500
|
+
'server.teams.celebration': 'expected the name of a celebration, found {value}',
|
|
501
|
+
'server.teams.celebrations': 'expected a list of at most 32 celebrations',
|
|
502
|
+
'server.teams.choice': 'expected {name} as one of {choices}, found {value}',
|
|
503
|
+
'server.teams.color': 'expected {name} as three integers from 0 to 255, found {value}',
|
|
504
|
+
'server.teams.color_pair': 'expected colors as #rrggbb, found {old} and {new}',
|
|
505
|
+
'server.teams.colors': 'expected the colors as a dictionary by model, found {value}',
|
|
506
|
+
'server.teams.colors_count': {
|
|
507
|
+
'one': 'expected at most {count} color for {model}',
|
|
508
|
+
'other': 'expected at most {count} colors for {model}',
|
|
509
|
+
},
|
|
510
|
+
'server.teams.control.fields': (
|
|
511
|
+
'expected players, assignment, selection, goalkeeper and action_set'
|
|
512
|
+
),
|
|
513
|
+
'server.teams.control.goalkeeper_builtin': (
|
|
514
|
+
'scenario makes the goalkeeper uncontrollable, choose scenario'
|
|
515
|
+
),
|
|
516
|
+
'server.teams.control.goalkeeper_fixed': (
|
|
517
|
+
'expected fixed assignment for a goalkeeper handed to the AI'
|
|
518
|
+
),
|
|
519
|
+
'server.teams.control.goalkeeper_outside': (
|
|
520
|
+
'expected the goalkeeper outside the fixed selection'
|
|
521
|
+
),
|
|
522
|
+
'server.teams.control.goalkeeper_scenario': (
|
|
523
|
+
'scenario has a controllable goalkeeper, choose agent or builtin'
|
|
524
|
+
),
|
|
525
|
+
'server.teams.control.goalkeeper_selected': 'expected the goalkeeper in the fixed selection',
|
|
526
|
+
'server.teams.control.goalkeeper_uncontrollable': (
|
|
527
|
+
'scenario makes the goalkeeper uncontrollable'
|
|
528
|
+
),
|
|
529
|
+
'server.teams.control.legacy_goalkeeper': (
|
|
530
|
+
'expected 10 players for the earlier goalkeeper setting'
|
|
531
|
+
),
|
|
532
|
+
'server.teams.control.none_present': 'scenario holds none of the selected players',
|
|
533
|
+
'server.teams.control.players': 'expected from 1 to {most} controlled players',
|
|
534
|
+
'server.teams.control.select_player': 'scenario cannot select player {player}',
|
|
535
|
+
'server.teams.control.selection_count': 'expected one selection for each controlled player',
|
|
536
|
+
'server.teams.control.selection_distinct': 'expected distinct player indices or GRF role names',
|
|
537
|
+
'server.teams.control.selection_empty': 'expected an empty selection for dynamic assignment',
|
|
538
|
+
'server.teams.control.selection_list': 'expected a list of player indices or roles',
|
|
539
|
+
'server.teams.control.too_few': {
|
|
540
|
+
'=0': 'scenario has no controllable player',
|
|
541
|
+
'one': 'scenario has only {count} controllable player',
|
|
542
|
+
'other': 'scenario has only {count} controllable players',
|
|
543
|
+
},
|
|
544
|
+
'server.teams.controlled_players': 'expected a number of controlled players, found {players}',
|
|
545
|
+
'server.teams.crowd': 'expected the crowd as {"colors": [...]}, found {value}',
|
|
546
|
+
'server.teams.crowd_colors': 'expected one to four colors of the fans',
|
|
547
|
+
'server.teams.custom_model': 'expected an uploaded model before choosing the custom model',
|
|
548
|
+
'server.teams.duplicate_builtin': (
|
|
549
|
+
'expected a registered submission to duplicate, found the built-in AI'
|
|
550
|
+
),
|
|
551
|
+
'server.teams.editor': 'expected the JSON document of the editor, found {error}',
|
|
552
|
+
'server.teams.field.action_set': 'action set',
|
|
553
|
+
'server.teams.field.assignment': '`assignment`',
|
|
554
|
+
'server.teams.field.colors_model': 'the model of the colors',
|
|
555
|
+
'server.teams.field.fans_color': 'a color of the fans',
|
|
556
|
+
'server.teams.field.format': 'the format',
|
|
557
|
+
'server.teams.field.goalkeeper': '`goalkeeper`',
|
|
558
|
+
'server.teams.field.hair': 'the hair',
|
|
559
|
+
'server.teams.field.hair_color': 'the hair color',
|
|
560
|
+
'server.teams.field.image_slot': 'the image slot',
|
|
561
|
+
'server.teams.field.model': 'the model',
|
|
562
|
+
'server.teams.field.numbers': 'the numbers',
|
|
563
|
+
'server.teams.field.pattern': 'the pattern',
|
|
564
|
+
'server.teams.field.player_model': 'the player model',
|
|
565
|
+
'server.teams.field.skin': 'the skin',
|
|
566
|
+
'server.teams.field.sound_slot': 'the sound slot',
|
|
567
|
+
'server.teams.folder.files': 'expected the files that the index of the folder lists',
|
|
568
|
+
'server.teams.folder.index': 'expected the index of a submission folder, found {error}',
|
|
569
|
+
'server.teams.folder.index_list': (
|
|
570
|
+
'expected the index of a submission folder, a list of its files'
|
|
571
|
+
),
|
|
572
|
+
'server.teams.folder.short': 'expected the files of a submission folder, found too few bytes',
|
|
573
|
+
'server.teams.hidden_numbers': (
|
|
574
|
+
'expected hidden_numbers as a list of model names, found {value}'
|
|
575
|
+
),
|
|
576
|
+
'server.teams.image_size': 'expected an image of at most 8 MB, found {size} bytes',
|
|
577
|
+
'server.teams.image_type': 'expected a PNG, JPEG or WebP image',
|
|
578
|
+
'server.teams.model.binary': 'expected a binary glTF file, .glb, found other bytes',
|
|
579
|
+
'server.teams.model.body': (
|
|
580
|
+
'expected a bone for the joint body, the pelvis, which every rig has'
|
|
581
|
+
),
|
|
582
|
+
'server.teams.model.bones': 'expected bones of the model',
|
|
583
|
+
'server.teams.model.joints': 'expected the joints as names of bones by joint, found {joints}',
|
|
584
|
+
'server.teams.model.json': 'expected the JSON chunk of a glTF file, found {error}',
|
|
585
|
+
'server.teams.model.json_first': 'expected a binary glTF 2.0 file with its JSON chunk first',
|
|
586
|
+
'server.teams.model.json_object': 'expected the JSON chunk of a glTF file to hold an object',
|
|
587
|
+
'server.teams.model.limits': 'expected a model of the limits with a joint for body',
|
|
588
|
+
'server.teams.model.link': 'expected a self-contained model, found a link to {uri}',
|
|
589
|
+
'server.teams.model.missing_bones': 'expected bones of the model, found {bones}',
|
|
590
|
+
'server.teams.model.rigged': 'expected a rigged model, a glTF file with a skin, found none',
|
|
591
|
+
'server.teams.model.size': 'expected a model of at most 30 MB, found {size} bytes',
|
|
592
|
+
'server.teams.model.too_large': 'expected a model of at most 30 MB',
|
|
593
|
+
'server.teams.model.unknown_joints': "expected joints of GRF's skeleton, found {joints}",
|
|
594
|
+
'server.teams.name': 'expected a team name with at least one character',
|
|
595
|
+
'server.teams.player': 'expected player {index} as a dictionary, found {value}',
|
|
596
|
+
'server.teams.player_entry': 'expected an entry of a player, found {key}',
|
|
597
|
+
'server.teams.player_name': 'expected a name of at most 24 characters, found {name}',
|
|
598
|
+
'server.teams.players': {
|
|
599
|
+
'one': 'expected at most {count} player entry, found {value}',
|
|
600
|
+
'other': 'expected at most {count} player entries, found {value}',
|
|
601
|
+
},
|
|
602
|
+
'server.teams.scenario_format': 'expected a scenario of {format} for {team}, found {scenario}',
|
|
603
|
+
'server.teams.shirt_number': 'expected a shirt number from 0 to 99, found {number}',
|
|
604
|
+
'server.teams.sound_size': 'expected a sound of at most 2 MB, found {size} bytes',
|
|
605
|
+
'server.teams.sound_type': 'expected an Ogg, MP3 or WAV sound',
|
|
606
|
+
'server.teams.sound_damaged': (
|
|
607
|
+
'expected a whole {kind} sound, the file ends early or holds no sound'
|
|
608
|
+
),
|
|
609
|
+
'server.teams.style': 'expected a style as a dictionary, found {value}',
|
|
610
|
+
'server.teams.style_entry': 'expected an entry of a style, found {key}',
|
|
611
|
+
'server.teams.style_part': 'expected a part of {key}, found {part}',
|
|
612
|
+
'server.teams.style_part_object': 'expected {key} as a dictionary, found {value}',
|
|
613
|
+
'server.teams.submission.kind': (
|
|
614
|
+
'expected a submission folder, a .py file or a .tar.gz or .zip archive, found {filename}'
|
|
615
|
+
),
|
|
616
|
+
'server.teams.submission.main_folder': (
|
|
617
|
+
'expected {main} in the folder {folder}, as Kaggle requires'
|
|
618
|
+
),
|
|
619
|
+
'server.teams.submission.main_top': (
|
|
620
|
+
'expected {main} at the top of the submission, as Kaggle requires'
|
|
621
|
+
),
|
|
622
|
+
'server.teams.submission.size': 'expected a submission of at most 100 MB, found {size} bytes',
|
|
623
|
+
'server.teams.submission.unpack': 'expected a submission whose files unpack, found {error}',
|
|
624
|
+
'server.teams.unknown': 'expected a team of the registry, found {identifier}',
|
|
625
|
+
'server.teams.unknown_kaggle': 'expected a Kaggle team of the registry, found {identifier}',
|
|
626
|
+
'server.tournament.ability': (
|
|
627
|
+
'expected the ability of entrant {entrant} from 0 to 1, found {ability}'
|
|
628
|
+
),
|
|
629
|
+
'server.tournament.best_places': 'stage {stage} names best places without a position',
|
|
630
|
+
'server.tournament.bracket_byes': {
|
|
631
|
+
'one': (
|
|
632
|
+
'a bracket of {size} with byes needs from {lowest} to {size} entrants, and {count} '
|
|
633
|
+
'enters {where}'
|
|
634
|
+
),
|
|
635
|
+
'other': (
|
|
636
|
+
'a bracket of {size} with byes needs from {lowest} to {size} entrants, and {count} '
|
|
637
|
+
'enter {where}'
|
|
638
|
+
),
|
|
639
|
+
},
|
|
640
|
+
'server.tournament.bracket_exact': {
|
|
641
|
+
'one': 'a bracket of {size} needs exactly {size} entrants, and {count} enters {where}',
|
|
642
|
+
'other': 'a bracket of {size} needs exactly {size} entrants, and {count} enter {where}',
|
|
643
|
+
},
|
|
644
|
+
'server.tournament.bracket_play_in': {
|
|
645
|
+
'one': (
|
|
646
|
+
'a bracket of {size} with a play-in round needs from {size} to {highest} entrants, and '
|
|
647
|
+
'{count} enters {where}'
|
|
648
|
+
),
|
|
649
|
+
'other': (
|
|
650
|
+
'a bracket of {size} with a play-in round needs from {size} to {highest} entrants, and '
|
|
651
|
+
'{count} enter {where}'
|
|
652
|
+
),
|
|
653
|
+
},
|
|
654
|
+
'server.tournament.bracket_power': 'expected a bracket size of {where} that is a power of two',
|
|
655
|
+
'server.tournament.cancel_state': (
|
|
656
|
+
'expected a tournament that runs or stopped, found it {state}'
|
|
657
|
+
),
|
|
658
|
+
'server.tournament.cannot_play': '{entrant} cannot play {scenario} in stage {stage}, {reason}',
|
|
659
|
+
'server.tournament.cannot_play_decider': (
|
|
660
|
+
'{entrant} cannot play {scenario}, the scenario of the deciding matches of stage {stage}, '
|
|
661
|
+
'{reason}, so choose another scenario for them or settle drawn ties without them'
|
|
662
|
+
),
|
|
663
|
+
'server.tournament.cards': 'expected cards as two pairs of counts, found {cards}',
|
|
664
|
+
'server.tournament.choice': 'expected {name} from {choices}, found {value}',
|
|
665
|
+
'server.tournament.count': 'expected {name} from {lowest} to {highest}, found {value}',
|
|
666
|
+
'server.tournament.count_exact': 'expected {name} to be {lowest}, found {value}',
|
|
667
|
+
'server.tournament.defaults': 'expected the defaults as an object',
|
|
668
|
+
'server.tournament.description': 'expected a description of at most 400 characters',
|
|
669
|
+
'server.tournament.double_byes': 'a double elimination bracket fills with byes, in {where}',
|
|
670
|
+
'server.tournament.ended': 'expected a tournament that has ended, cancel it first',
|
|
671
|
+
'server.tournament.entrant_fields': 'entrant {entrant} has unknown fields {fields}',
|
|
672
|
+
'server.tournament.entrant_object': 'expected entrant {entrant} as an object, found {value}',
|
|
673
|
+
'server.tournament.entrant_team': (
|
|
674
|
+
'expected a registered team for entrant {entrant}, found {team}, the built-in AI '
|
|
675
|
+
'enters with the kind builtin'
|
|
676
|
+
),
|
|
677
|
+
'server.tournament.entrants': 'expected from 2 to {most} entrants, found {count}',
|
|
678
|
+
'server.tournament.field.added_legs': 'the legs to add to {where}',
|
|
679
|
+
'server.tournament.field.advance': 'the entrants that advance from {where}',
|
|
680
|
+
'server.tournament.field.best_count': 'the best places of position {position} of {where}',
|
|
681
|
+
'server.tournament.field.best_position': 'the position of the best places of {where}',
|
|
682
|
+
'server.tournament.field.bracket': 'the bracket of {where}',
|
|
683
|
+
'server.tournament.field.bye_points': 'the bye points of {where}',
|
|
684
|
+
'server.tournament.field.concurrency': 'the matches at once',
|
|
685
|
+
'server.tournament.field.cycles': 'the cycles of {where}',
|
|
686
|
+
'server.tournament.field.decider_length': 'the decider length of {where}',
|
|
687
|
+
'server.tournament.field.draws': 'the draws of {where}',
|
|
688
|
+
'server.tournament.field.each': 'each of {name}',
|
|
689
|
+
'server.tournament.field.entrant_kind': 'the kind of entrant {number}',
|
|
690
|
+
'server.tournament.field.entrant_name': 'an entrant name',
|
|
691
|
+
'server.tournament.field.final_legs': 'the legs of the final of {where}',
|
|
692
|
+
'server.tournament.field.forfeit_score': 'the forfeit score',
|
|
693
|
+
'server.tournament.field.format': 'the format of {where}',
|
|
694
|
+
'server.tournament.field.grand_final_reset': 'grand final reset',
|
|
695
|
+
'server.tournament.field.group_size': 'the group size of {where}',
|
|
696
|
+
'server.tournament.field.groups': 'the number of groups of {where}',
|
|
697
|
+
'server.tournament.field.ladder_rounds': 'the rounds of the ladder of {where}',
|
|
698
|
+
'server.tournament.field.laps': 'the laps of {where}',
|
|
699
|
+
'server.tournament.field.legs': 'the legs of {where}',
|
|
700
|
+
'server.tournament.field.length': 'the length of {where}',
|
|
701
|
+
'server.tournament.field.name': 'a name',
|
|
702
|
+
'server.tournament.field.points_draw': 'the points of a draw in {where}',
|
|
703
|
+
'server.tournament.field.points_loss': 'the points of a loss in {where}',
|
|
704
|
+
'server.tournament.field.points_win': 'the points of a win in {where}',
|
|
705
|
+
'server.tournament.field.pots': 'the pots of {where}',
|
|
706
|
+
'server.tournament.field.preset_title': 'a preset title',
|
|
707
|
+
'server.tournament.field.qualifiers': 'the qualifiers of group {group} of {where}',
|
|
708
|
+
'server.tournament.field.replays': 'the replays of {where}',
|
|
709
|
+
'server.tournament.field.seed': 'the seed',
|
|
710
|
+
'server.tournament.field.seeding': 'the seeding of {where}',
|
|
711
|
+
'server.tournament.field.settle': 'the settlements of {where}',
|
|
712
|
+
'server.tournament.field.strict': 'strict rules',
|
|
713
|
+
'server.tournament.field.strict_call_seconds': (
|
|
714
|
+
'the seconds of each later action call under strict rules'
|
|
715
|
+
),
|
|
716
|
+
'server.tournament.field.strict_deterministic': 'deterministic teams',
|
|
717
|
+
'server.tournament.field.strict_first_call_seconds': (
|
|
718
|
+
'the seconds of the first action call under strict rules'
|
|
719
|
+
),
|
|
720
|
+
'server.tournament.field.strict_folder_mb': (
|
|
721
|
+
"the megabytes of a team's folder under strict rules"
|
|
722
|
+
),
|
|
723
|
+
'server.tournament.field.strict_frozen': 'frozen team folders',
|
|
724
|
+
'server.tournament.field.strict_gpu': 'the GPU of a team',
|
|
725
|
+
'server.tournament.field.strict_load_seconds': 'the seconds to load under strict rules',
|
|
726
|
+
'server.tournament.field.strict_match_seconds': (
|
|
727
|
+
"the seconds of all a team's action calls of a match under strict rules"
|
|
728
|
+
),
|
|
729
|
+
'server.tournament.field.strict_mean_call_ms': (
|
|
730
|
+
'the mean milliseconds of an action call under strict rules'
|
|
731
|
+
),
|
|
732
|
+
'server.tournament.field.strict_memory_mb': (
|
|
733
|
+
"the megabytes of memory of a team's process under strict rules"
|
|
734
|
+
),
|
|
735
|
+
'server.tournament.field.strict_reset_seconds': 'the seconds of a reset under strict rules',
|
|
736
|
+
'server.tournament.field.strict_threads': 'the threads of each team under strict rules',
|
|
737
|
+
'server.tournament.field.swiss_rounds': (
|
|
738
|
+
'the rounds of the Swiss {where}, which pairs {entrants} entrants without rematches,'
|
|
739
|
+
),
|
|
740
|
+
'server.tournament.field.third_place': 'third place',
|
|
741
|
+
'server.tournament.field.tiebreakers': 'the tiebreakers of {where}',
|
|
742
|
+
'server.tournament.field.tournament_seeding': 'the seeding',
|
|
743
|
+
'server.tournament.flag': 'expected {setting} of {where} as true or false',
|
|
744
|
+
'server.tournament.foreign': (
|
|
745
|
+
'stopped, since this install did not write its file, resume it to play its matches'
|
|
746
|
+
),
|
|
747
|
+
'server.tournament.group_minimum': {
|
|
748
|
+
'one': (
|
|
749
|
+
'every group of {where} needs at least 2 entrants, and {count} entrant makes groups of '
|
|
750
|
+
'{sizes}'
|
|
751
|
+
),
|
|
752
|
+
'other': (
|
|
753
|
+
'every group of {where} needs at least 2 entrants, and {count} entrants make groups of '
|
|
754
|
+
'{sizes}'
|
|
755
|
+
),
|
|
756
|
+
},
|
|
757
|
+
'server.tournament.group_sizes': 'expected group sizes as a list of counts in {where}',
|
|
758
|
+
'server.tournament.group_total': {
|
|
759
|
+
'one': 'the group sizes of {where} hold {total} entrants, and {count} enters it',
|
|
760
|
+
'other': 'the group sizes of {where} hold {total} entrants, and {count} enter it',
|
|
761
|
+
},
|
|
762
|
+
'server.tournament.groups_advance': (
|
|
763
|
+
'the groups of {where} send {advance} on, by qualifiers and best places'
|
|
764
|
+
),
|
|
765
|
+
'server.tournament.groups_few': 'the groups of {where} send fewer than 2 entrants on',
|
|
766
|
+
'server.tournament.include': (
|
|
767
|
+
'expected the added entrants as a list of at most {most} entrants, none of them a person'
|
|
768
|
+
),
|
|
769
|
+
'server.tournament.knockout_advance': {
|
|
770
|
+
'one': 'the knockout of {where} stops at {count}, so {count} advances',
|
|
771
|
+
'other': 'the knockout of {where} stops at {count}, so {count} advance',
|
|
772
|
+
},
|
|
773
|
+
'server.tournament.knockout_stop': (
|
|
774
|
+
'expected the knockout of {where} to stop at a power of two below {size}'
|
|
775
|
+
),
|
|
776
|
+
'server.tournament.label.builtin': 'Built-in AI',
|
|
777
|
+
'server.tournament.label.builtin_ability': 'Built-in AI {ability}',
|
|
778
|
+
'server.tournament.label.challenge': 'Challenge {number}',
|
|
779
|
+
'server.tournament.label.challenges': 'Challenges',
|
|
780
|
+
'server.tournament.label.decider': '{match}, decider',
|
|
781
|
+
'server.tournament.label.final': 'Final',
|
|
782
|
+
'server.tournament.label.grand_final': 'Grand final',
|
|
783
|
+
'server.tournament.label.grand_final_reset': 'Grand final, deciding match',
|
|
784
|
+
'server.tournament.label.group': 'Group {group}',
|
|
785
|
+
'server.tournament.label.group_matchday': 'Group {group}, matchday {number}',
|
|
786
|
+
'server.tournament.label.ladder_tie': 'Round {round}, places {upper} and {lower}',
|
|
787
|
+
'server.tournament.label.lap': 'Lap {number}',
|
|
788
|
+
'server.tournament.label.leg': '{match}, leg {number}',
|
|
789
|
+
'server.tournament.label.loser_of': 'Loser of {match}',
|
|
790
|
+
'server.tournament.label.losers_final': 'Losers final',
|
|
791
|
+
'server.tournament.label.losers_round': 'Losers round {number}',
|
|
792
|
+
'server.tournament.label.matchday': 'Matchday {number}',
|
|
793
|
+
'server.tournament.label.numbered': '{name} {number}',
|
|
794
|
+
'server.tournament.label.play_in': 'Play-in {number}',
|
|
795
|
+
'server.tournament.label.play_in_round': 'Play-in round',
|
|
796
|
+
'server.tournament.label.quarter_final': 'Quarter-final {number}',
|
|
797
|
+
'server.tournament.label.quarter_finals': 'Quarter-finals',
|
|
798
|
+
'server.tournament.label.random': 'Random',
|
|
799
|
+
'server.tournament.label.replay': '{match}, replay',
|
|
800
|
+
'server.tournament.label.round': 'Round {number}',
|
|
801
|
+
'server.tournament.label.round_of': 'Round of {count}',
|
|
802
|
+
'server.tournament.label.round_of_tie': 'Round of {count}, tie {number}',
|
|
803
|
+
'server.tournament.label.semi_final': 'Semi-final {number}',
|
|
804
|
+
'server.tournament.label.semi_finals': 'Semi-finals',
|
|
805
|
+
'server.tournament.label.stage.cup': 'Cup',
|
|
806
|
+
'server.tournament.label.stage.double_elimination': 'Double elimination',
|
|
807
|
+
'server.tournament.label.stage.gauntlet': 'Gauntlet',
|
|
808
|
+
'server.tournament.label.stage.group_stage': 'Group stage',
|
|
809
|
+
'server.tournament.label.stage.groups': 'Groups',
|
|
810
|
+
'server.tournament.label.stage.king_of_the_hill': 'King of the hill',
|
|
811
|
+
'server.tournament.label.stage.knockout': 'Knockout',
|
|
812
|
+
'server.tournament.label.stage.knockout_stage': 'Knockout stage',
|
|
813
|
+
'server.tournament.label.stage.ladder': 'Ladder',
|
|
814
|
+
'server.tournament.label.stage.league': 'League',
|
|
815
|
+
'server.tournament.label.stage.league_phase': 'League phase',
|
|
816
|
+
'server.tournament.label.stage.playoffs': 'Playoffs',
|
|
817
|
+
'server.tournament.label.stage.regular_season': 'Regular season',
|
|
818
|
+
'server.tournament.label.stage.round_robin': 'Round robin',
|
|
819
|
+
'server.tournament.label.stage.swiss': 'Swiss',
|
|
820
|
+
'server.tournament.label.step': 'Step {number}',
|
|
821
|
+
'server.tournament.label.third_place': 'Third place match',
|
|
822
|
+
'server.tournament.label.tie': '{round}, tie {number}',
|
|
823
|
+
'server.tournament.label.title': '{tournament}, {match}, {home} against {away}',
|
|
824
|
+
'server.tournament.label.tournament': 'Tournament',
|
|
825
|
+
'server.tournament.label.winner_of': 'Winner of {match}',
|
|
826
|
+
'server.tournament.label.winners_final': 'Winners final',
|
|
827
|
+
'server.tournament.label.winners_round': 'Winners round {number}',
|
|
828
|
+
'server.tournament.legs_cancelled': 'a cancelled tournament takes no more legs',
|
|
829
|
+
'server.tournament.legs_format': (
|
|
830
|
+
'expected the last stage, {where}, to be a league or groups to take more legs'
|
|
831
|
+
),
|
|
832
|
+
'server.tournament.legs_most': 'the last stage, {where}, already plays the most legs, {most}',
|
|
833
|
+
'server.tournament.length': (
|
|
834
|
+
'expected a length from {lowest} to {highest} steps, found {length}'
|
|
835
|
+
),
|
|
836
|
+
'server.tournament.limit': 'expected {name} from {lowest} to {highest}, found {value}',
|
|
837
|
+
'server.tournament.list': 'expected {name} as a list without repeats, found {value}',
|
|
838
|
+
'server.tournament.match_failed': '{label} failed, {error}',
|
|
839
|
+
'server.tournament.match_failed_more': {
|
|
840
|
+
'one': '{label} failed, {error}, and {count} more match failed',
|
|
841
|
+
'other': '{label} failed, {error}, and {count} more matches failed',
|
|
842
|
+
},
|
|
843
|
+
'server.tournament.match_failed_one_more': '{label} failed, {error}, and 1 more match failed',
|
|
844
|
+
'server.tournament.match_key': 'expected a match of the tournament still to play, found {key}',
|
|
845
|
+
'server.tournament.mixed_sizes': (
|
|
846
|
+
'{five} is a team of 5v5 and {eleven} a team of 11v11, and {scenario} of stage {stage} '
|
|
847
|
+
'fits one size alone, so enter teams of one size or choose {fitting}, which fits both '
|
|
848
|
+
'sizes and the players each team controls'
|
|
849
|
+
),
|
|
850
|
+
'server.tournament.mixed_sizes_none': (
|
|
851
|
+
'{five} is a team of 5v5 and {eleven} a team of 11v11, and {scenario} of stage {stage} '
|
|
852
|
+
'fits one size alone, and no scenario fits both sizes and the players each team '
|
|
853
|
+
'controls, so enter teams of one size'
|
|
854
|
+
),
|
|
855
|
+
'server.tournament.name_taken': (
|
|
856
|
+
'expected a name of its own for entrant {entrant}, '
|
|
857
|
+
'found {name}, the name of entrant {other}'
|
|
858
|
+
),
|
|
859
|
+
'server.tournament.no_frame': 'the recording {identifier} holds no frame',
|
|
860
|
+
'server.tournament.no_replay': 'the match {key} kept no seed and actions to replay',
|
|
861
|
+
'server.tournament.no_result': 'the match gave no result',
|
|
862
|
+
'server.tournament.note.early': (
|
|
863
|
+
'The game ended before full time, so the match waits to be played again.'
|
|
864
|
+
),
|
|
865
|
+
'server.tournament.note.failed': (
|
|
866
|
+
'The game stopped, {error}. Award the match to either side, or play it again.'
|
|
867
|
+
),
|
|
868
|
+
'server.tournament.note.gone': 'The game of this match ended without a result.',
|
|
869
|
+
'server.tournament.note.unreadable': 'The result could not be read, {error}',
|
|
870
|
+
'server.tournament.object': 'expected the tournament as an object',
|
|
871
|
+
'server.tournament.other_process': 'the tournament belongs to another process',
|
|
872
|
+
'server.tournament.other_process_change': (
|
|
873
|
+
'the tournament runs in another process, {owner}, which can change it'
|
|
874
|
+
),
|
|
875
|
+
'server.tournament.other_process_runs': 'the tournament runs in another process, {owner}',
|
|
876
|
+
'server.tournament.other_process_silent': (
|
|
877
|
+
'the process {owner} that runs the tournament did not answer, send the change to its '
|
|
878
|
+
'server as {route}'
|
|
879
|
+
),
|
|
880
|
+
'server.tournament.people_page': 'the matches of people play on the page of the server',
|
|
881
|
+
'server.tournament.person_match': 'expected a match of a person',
|
|
882
|
+
'server.tournament.person_names': 'expected a name for each person',
|
|
883
|
+
'server.tournament.playing': 'the match is playing now',
|
|
884
|
+
'server.tournament.points': (
|
|
885
|
+
'expected points for a win, draw and loss in {where}, found {points}'
|
|
886
|
+
),
|
|
887
|
+
'server.tournament.preflight': 'expected the pre-flight as true or false, found {value}',
|
|
888
|
+
'server.tournament.preset': 'expected a preset from {presets}, found {name}',
|
|
889
|
+
'server.tournament.preset_name': (
|
|
890
|
+
'expected a preset name of small letters, digits, dashes and underscores that names no '
|
|
891
|
+
'shipped preset, found {name}'
|
|
892
|
+
),
|
|
893
|
+
'server.tournament.preset_object': 'expected a preset as an object',
|
|
894
|
+
'server.tournament.preset_settings': 'the preset has unknown settings {settings}',
|
|
895
|
+
'server.tournament.qualifiers': (
|
|
896
|
+
'expected the qualifiers of stage {stage} as a count or one count a group'
|
|
897
|
+
),
|
|
898
|
+
'server.tournament.rating': 'expected a rating from 0 to 10000, found {rating}',
|
|
899
|
+
'server.tournament.recording': 'expected the recording of a result as text, found {match}',
|
|
900
|
+
'server.tournament.refused': 'the process that runs it refused the change',
|
|
901
|
+
'server.tournament.result': 'expected a result with a score, found {result}',
|
|
902
|
+
'server.tournament.running': 'expected a running tournament, found it {state}',
|
|
903
|
+
'server.tournament.score': 'expected a score of two counts, found {score}',
|
|
904
|
+
'server.tournament.settings': 'the tournament has unknown settings {settings}',
|
|
905
|
+
'server.tournament.stage_ability': 'expected the ability of {where} from 0 to 1',
|
|
906
|
+
'server.tournament.stage_entrants': {
|
|
907
|
+
'=0': '{where} needs at least 2 entrants, and none reaches it',
|
|
908
|
+
'one': '{where} needs at least 2 entrants, and {count} reaches it',
|
|
909
|
+
'other': '{where} needs at least 2 entrants, and {count} reach it',
|
|
910
|
+
},
|
|
911
|
+
'server.tournament.stage_name': 'expected the name of {where} as text',
|
|
912
|
+
'server.tournament.stage_object': 'expected stage {stage} as an object, found {value}',
|
|
913
|
+
'server.tournament.stage_scenario': (
|
|
914
|
+
'expected a shipped scenario for stage {stage}, found {scenario}'
|
|
915
|
+
),
|
|
916
|
+
'server.tournament.stage_settings': 'stage {stage} has unknown settings {settings}',
|
|
917
|
+
'server.tournament.stages': 'expected from 1 to {most} stages',
|
|
918
|
+
'server.tournament.standard_ability': (
|
|
919
|
+
'expected the standard ability from 0 to 1, found {ability}'
|
|
920
|
+
),
|
|
921
|
+
'server.tournament.stopped': 'expected a stopped tournament, found it {state}',
|
|
922
|
+
'server.tournament.strict': (
|
|
923
|
+
'expected strict rules as true, false or an object of limits, found {value}'
|
|
924
|
+
),
|
|
925
|
+
'server.tournament.strict_settings': (
|
|
926
|
+
'expected limits of strict rules among threads, load_seconds, first_call_seconds, '
|
|
927
|
+
'call_seconds, reset_seconds, mean_call_ms, folder_mb, memory_mb, match_seconds, '
|
|
928
|
+
'frozen, gpu and deterministic, found {settings}'
|
|
929
|
+
),
|
|
930
|
+
'server.tournament.swiss': 'no Swiss pairing without a rematch exists for this round',
|
|
931
|
+
'server.tournament.team_format': 'it is a team of {format}',
|
|
932
|
+
'server.tournament.teammates': 'expected registered teammates, found {mates}',
|
|
933
|
+
'server.tournament.text': 'expected {name} as text, found {value}',
|
|
934
|
+
'server.tournament.text_length': 'expected {name} of 1 to {most} characters, found {value}',
|
|
935
|
+
'server.tournament.unknown': 'no such tournament',
|
|
936
|
+
'server.tournament.unknown_identifier': 'expected a tournament, found {identifier}',
|
|
937
|
+
'server.tournament.waiting': 'waiting for cuju render tournament run',
|
|
938
|
+
'server.tournament.watched': 'expected the watched matches as a list of up to 10000 keys',
|
|
939
|
+
'server.tournament.where': 'stage {number}',
|
|
940
|
+
'server.tournament.winner': 'expected the winning side as home or away, found {side}',
|
|
941
|
+
'server.trash.unknown': 'no such recording in the Trash',
|
|
942
|
+
'server.tutorial.done': 'expected done as a list of lessons from {known}, found {done}',
|
|
943
|
+
'server.tutorial.lesson': 'expected a lesson from {lessons}, found {identifier}',
|
|
944
|
+
'server.tutorial.unknown': 'expected lessons from {known}, found {unknown}',
|
|
945
|
+
'server.unexpected': 'the server **failed** unexpectedly, {error}',
|
|
946
|
+
'server.video.annotations': 'expected annotations as true or false, found {annotations}',
|
|
947
|
+
'server.video.arena': 'expected an arena of {arenas}, found {arena}',
|
|
948
|
+
'server.video.busy': 'an export already runs, wait for it or cancel it',
|
|
949
|
+
'server.video.camera': 'expected a camera of {cameras}, found {camera}',
|
|
950
|
+
'server.video.cdp_closed': '`Chrome` closed the debugging connection',
|
|
951
|
+
'server.video.cdp_method': '`Chrome` could not run {method}, {error}',
|
|
952
|
+
'server.video.cdp_refused': '`Chrome` refused the debugging connection, {header}',
|
|
953
|
+
'server.video.cdp_start': '`Chrome` closed the debugging connection at its start',
|
|
954
|
+
'server.video.chrome_exit': (
|
|
955
|
+
'`Chrome` stopped at its start with exit code {code}. `Chrome` in some containers needs '
|
|
956
|
+
'`--no-sandbox`.'
|
|
957
|
+
),
|
|
958
|
+
'server.video.chrome_page': '`Chrome` opened no page',
|
|
959
|
+
'server.video.chrome_port': '`Chrome` did not open its debugging port within {seconds} s',
|
|
960
|
+
'server.video.empty_range': (
|
|
961
|
+
'expected a range that holds frames, the recording runs from 0 to {duration} seconds, '
|
|
962
|
+
'found {start} to {end}'
|
|
963
|
+
),
|
|
964
|
+
'server.video.exit': 'the export ended with exit code {code}',
|
|
965
|
+
'server.video.exit_detail': 'the export ended with exit code {code}, {detail}',
|
|
966
|
+
'server.video.ffmpeg_exit': '`ffmpeg` failed with exit code {code}, {detail}',
|
|
967
|
+
'server.video.ffmpeg_stopped': '`ffmpeg` stopped while encoding, {detail}',
|
|
968
|
+
'server.video.fields': 'expected the fields {known}, found {unknown}',
|
|
969
|
+
'server.video.goal': 'expected a goal number from 1, found {goal}',
|
|
970
|
+
'server.video.goal_range': {
|
|
971
|
+
'=0': 'expected a goal, found none in the recording, asked for goal {goal}',
|
|
972
|
+
'one': 'expected goal 1, the only goal of the recording, found goal {goal}',
|
|
973
|
+
'other': 'expected a goal from 1 to {count}, found goal {goal}',
|
|
974
|
+
},
|
|
975
|
+
'server.video.hour': 'expected an hour from 0 to 24, found {hour}',
|
|
976
|
+
'server.video.length': 'expected at most {most} seconds of video, found {length} seconds',
|
|
977
|
+
'server.video.max_length': (
|
|
978
|
+
'expected at most {most} seconds of video, the range from {start} to {end} runs {length} '
|
|
979
|
+
'seconds, so choose a shorter range or raise the maximum length'
|
|
980
|
+
),
|
|
981
|
+
'server.video.max_megabytes': 'expected a maximum size from 1 to {most} MB, found {value}',
|
|
982
|
+
'server.video.max_seconds': (
|
|
983
|
+
'expected a maximum length above 0 and up to {most} seconds, found {value}'
|
|
984
|
+
),
|
|
985
|
+
'server.video.max_size': (
|
|
986
|
+
'the video reached its **maximum size** of {most} MB, so choose a shorter range or raise '
|
|
987
|
+
'the maximum size'
|
|
988
|
+
),
|
|
989
|
+
'server.video.missing_both': '{chrome} {ffmpeg}',
|
|
990
|
+
'server.video.missing_chrome': (
|
|
991
|
+
'Video export needs `Chrome` or `Chromium`, found neither. Install `Google Chrome` or '
|
|
992
|
+
'`Chromium`, from https://www.google.com/chrome/ or with the package manager, such as '
|
|
993
|
+
'`apt install chromium`, or name the program with `$CUJU_CHROME` in the environment of '
|
|
994
|
+
'`cuju render serve`, then **restart** the server.'
|
|
995
|
+
),
|
|
996
|
+
'server.video.missing_chrome_command': (
|
|
997
|
+
'Video export needs `Chrome` or `Chromium`, found neither. Install `Google Chrome` or '
|
|
998
|
+
'`Chromium`, from https://www.google.com/chrome/ or with the package manager, such as '
|
|
999
|
+
'`apt install chromium`, or name the program with `--chrome` or `$CUJU_CHROME`.'
|
|
1000
|
+
),
|
|
1001
|
+
'server.video.missing_ffmpeg': (
|
|
1002
|
+
'Video export needs `ffmpeg`, found none. Install `ffmpeg`, from '
|
|
1003
|
+
'https://ffmpeg.org/download.html or with the package manager, such as '
|
|
1004
|
+
'`apt install ffmpeg`, `brew install ffmpeg` or `winget install ffmpeg`, or name the '
|
|
1005
|
+
'program with `$CUJU_FFMPEG` in the environment of `cuju render serve`, then **restart** '
|
|
1006
|
+
'the server.'
|
|
1007
|
+
),
|
|
1008
|
+
'server.video.missing_ffmpeg_command': (
|
|
1009
|
+
'Video export needs `ffmpeg`, found none. Install `ffmpeg`, from '
|
|
1010
|
+
'https://ffmpeg.org/download.html or with the package manager, such as '
|
|
1011
|
+
'`apt install ffmpeg`, `brew install ffmpeg` or `winget install ffmpeg`, or name the '
|
|
1012
|
+
'program with `--ffmpeg` or `$CUJU_FFMPEG`.'
|
|
1013
|
+
),
|
|
1014
|
+
'server.video.no_frames': '{path} holds no frames',
|
|
1015
|
+
'server.video.output': 'expected an output file ending in `.mp4` or `.webm`, found {name}',
|
|
1016
|
+
'server.video.output_format': (
|
|
1017
|
+
'expected an output file ending in `{suffix}` for the format {format}, found {name}'
|
|
1018
|
+
),
|
|
1019
|
+
'server.video.format': 'expected a format of {formats}, found {format}',
|
|
1020
|
+
'server.video.no_h264': (
|
|
1021
|
+
'expected a browser that encodes H.264 for an MP4, found none in {browser}, so use `Google '
|
|
1022
|
+
'Chrome` or `Edge`, or install `ffmpeg`, which then encodes the MP4'
|
|
1023
|
+
),
|
|
1024
|
+
'server.video.no_h264_webm': (
|
|
1025
|
+
'expected a browser that encodes H.264 for an MP4, found none in {browser}, so use `Google '
|
|
1026
|
+
'Chrome` or `Edge`, ask for WebM with `--format webm`, or install `ffmpeg`, which then '
|
|
1027
|
+
'encodes the MP4'
|
|
1028
|
+
),
|
|
1029
|
+
'server.video.no_webm': (
|
|
1030
|
+
'expected a browser that encodes VP9 or VP8 for a WebM, found none in {browser}, so update '
|
|
1031
|
+
'it or use `Google Chrome` or `Edge`'
|
|
1032
|
+
),
|
|
1033
|
+
'server.video.no_encoder': (
|
|
1034
|
+
'expected a browser that encodes video, found none in {browser}, so update it or use '
|
|
1035
|
+
'`Google Chrome` or `Edge`, or install `ffmpeg`, which then encodes an MP4'
|
|
1036
|
+
),
|
|
1037
|
+
'server.video.host': 'the page of the export did not load within {seconds} s',
|
|
1038
|
+
'server.video.host_errors': 'the page of the export did not load within {seconds} s, {errors}',
|
|
1039
|
+
'server.video.encoder': "the browser's video encoder stopped, {error}",
|
|
1040
|
+
'server.video.codec': (
|
|
1041
|
+
'expected a browser that encodes the format asked for, found none at this size'
|
|
1042
|
+
),
|
|
1043
|
+
'server.video.lost': 'the browser lost the finished video before it was saved',
|
|
1044
|
+
'server.video.bytes': 'expected {size} bytes of video from the browser, found {found}',
|
|
1045
|
+
'server.video.page_draw': 'the page cannot draw in this `Chrome`, {error}',
|
|
1046
|
+
'server.video.page_failed': 'the page failed, {error}',
|
|
1047
|
+
'server.video.page_ready': 'the page did not open the recording within {seconds} s',
|
|
1048
|
+
'server.video.page_ready_errors': (
|
|
1049
|
+
'the page did not open the recording within {seconds} s, {errors}'
|
|
1050
|
+
),
|
|
1051
|
+
'server.video.program': 'expected a program at {given}, found none',
|
|
1052
|
+
'server.video.quality': 'expected a quality of {qualities}, found {quality}',
|
|
1053
|
+
'server.video.range': 'expected an end after the start, found {start} to {end}',
|
|
1054
|
+
'server.video.rate': (
|
|
1055
|
+
'expected a frame rate of whole frames a second from 1 to {most}, found {fps}'
|
|
1056
|
+
),
|
|
1057
|
+
'server.video.safe': 'expected the safe area as text or a percentage, found {safe}',
|
|
1058
|
+
'server.video.scale': 'expected the scale of the graphics as a number, found {graphics}',
|
|
1059
|
+
'server.video.score': 'expected score as true or false, found {score}',
|
|
1060
|
+
'server.video.seconds': 'expected {name} as seconds from 0, found {value}',
|
|
1061
|
+
'server.video.source': (
|
|
1062
|
+
'expected a `.cjr` file or the identifier of a recording of the library, found {source}'
|
|
1063
|
+
),
|
|
1064
|
+
'server.video.unknown': 'no such video',
|
|
1065
|
+
'server.video.unknown_match': 'expected a recording of the library, found {match}',
|
|
1066
|
+
'server.video.unreadable': '{path} is not a readable recording, {error}',
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
|
|
1070
|
+
def text(message_id: str, /, **values: Any) -> str:
|
|
1071
|
+
"""Returns the English text of a message with its placeholders filled from `values`.
|
|
1072
|
+
|
|
1073
|
+
A value is text, or a label that `label` returns, which reads as its English text.
|
|
1074
|
+
|
|
1075
|
+
Raises:
|
|
1076
|
+
KeyError: If the id is unknown, or a placeholder of its template has no value.
|
|
1077
|
+
"""
|
|
1078
|
+
template = plain(form(MESSAGES[message_id], values.get('count')))
|
|
1079
|
+
return _PLACEHOLDER.sub(lambda match: _shown(values[match.group(1)]), template)
|
|
1080
|
+
|
|
1081
|
+
|
|
1082
|
+
def plain(template: str) -> str:
|
|
1083
|
+
"""Returns a template without its marks, the words of **strong** and `code` as they are."""
|
|
1084
|
+
return _MARK.sub(lambda match: match.group(1) or match.group(2) or '', template)
|
|
1085
|
+
|
|
1086
|
+
|
|
1087
|
+
def form(template: str | dict[str, str], count: Any) -> str:
|
|
1088
|
+
"""Returns the English form of a template for a count, as the page picks it.
|
|
1089
|
+
|
|
1090
|
+
A plain template is its own form. A plural one takes the form of the exact count, such as
|
|
1091
|
+
`=0`, else `one` for a count of 1 written without decimals, else `other`.
|
|
1092
|
+
"""
|
|
1093
|
+
if isinstance(template, str):
|
|
1094
|
+
return template
|
|
1095
|
+
shown = _shown(count).strip() if count is not None else ''
|
|
1096
|
+
if f'={shown}' in template:
|
|
1097
|
+
return template[f'={shown}']
|
|
1098
|
+
return template['one'] if shown == '1' and 'one' in template else template['other']
|
|
1099
|
+
|
|
1100
|
+
|
|
1101
|
+
def counted(count: Any, one: str, other: str | None = None) -> str:
|
|
1102
|
+
"""Returns a count with its noun in English, as `1 recording` or `2 recordings`.
|
|
1103
|
+
|
|
1104
|
+
Args:
|
|
1105
|
+
count: The number, which reads as one only when it is 1 without decimals.
|
|
1106
|
+
one: The noun for one.
|
|
1107
|
+
other: The noun for any other count, by default `one` with an s.
|
|
1108
|
+
"""
|
|
1109
|
+
shown = f'{count:g}' if isinstance(count, float) else str(count)
|
|
1110
|
+
return f'{shown} {one if shown == "1" else (other or one + "s")}'
|
|
1111
|
+
|
|
1112
|
+
|
|
1113
|
+
def _shown(value: Any) -> str:
|
|
1114
|
+
"""Returns the English text of a value of a message, a label or anything else as text."""
|
|
1115
|
+
if isinstance(value, dict) and isinstance(value.get('text'), str):
|
|
1116
|
+
return value['text']
|
|
1117
|
+
return str(value)
|
|
1118
|
+
|
|
1119
|
+
|
|
1120
|
+
def _value(value: Any) -> Any:
|
|
1121
|
+
"""Returns a value of a message as it travels, a label as it is and anything else as text."""
|
|
1122
|
+
if isinstance(value, dict) and isinstance(value.get('text'), str):
|
|
1123
|
+
return value
|
|
1124
|
+
return str(value)
|
|
1125
|
+
|
|
1126
|
+
|
|
1127
|
+
def label(message_id: str, /, **values: Any) -> dict[str, Any]:
|
|
1128
|
+
"""Returns a name or a label that cuju makes, such as a round or the built-in AI, as data.
|
|
1129
|
+
|
|
1130
|
+
The label holds the message `id`, its English `text` and its `values`, so the page shows it
|
|
1131
|
+
in its own language while the command line and the files read the English text. A value may
|
|
1132
|
+
itself be a label, such as the name of an entrant inside the title of a match.
|
|
1133
|
+
|
|
1134
|
+
Raises:
|
|
1135
|
+
KeyError: If the id is unknown, or a placeholder of its template has no value.
|
|
1136
|
+
"""
|
|
1137
|
+
kept = {key: _value(value) for key, value in values.items()}
|
|
1138
|
+
return {'id': message_id, 'text': text(message_id, **kept), 'values': kept}
|
|
1139
|
+
|
|
1140
|
+
|
|
1141
|
+
def scenario(name: str) -> dict[str, Any]:
|
|
1142
|
+
"""Returns a scenario as a value of a message, which the page shows by its name there."""
|
|
1143
|
+
return {'kind': 'scenario', 'name': str(name), 'text': str(name)}
|
|
1144
|
+
|
|
1145
|
+
|
|
1146
|
+
def named(field: str, value: dict[str, Any] | str | None) -> dict[str, Any]:
|
|
1147
|
+
"""Returns the fields of a name in an answer, `field` with its English text and, for a label,
|
|
1148
|
+
`field_id` and `field_values` beside it, as the page's `serverText` reads them.
|
|
1149
|
+
|
|
1150
|
+
A name that a person gave is text and travels alone.
|
|
1151
|
+
"""
|
|
1152
|
+
if isinstance(value, dict) and isinstance(value.get('text'), str):
|
|
1153
|
+
return {
|
|
1154
|
+
field: value['text'],
|
|
1155
|
+
f'{field}_id': value.get('id'),
|
|
1156
|
+
f'{field}_values': dict(value.get('values') or {}),
|
|
1157
|
+
}
|
|
1158
|
+
return {field: value}
|
|
1159
|
+
|
|
1160
|
+
|
|
1161
|
+
# The kinds of value that the page shows by a name of its own, a scenario and a challenge.
|
|
1162
|
+
_KINDS = ('scenario', 'challenge')
|
|
1163
|
+
|
|
1164
|
+
|
|
1165
|
+
def valid_label(value: Any) -> bool:
|
|
1166
|
+
"""Returns whether a stored value is a label of a known message, its values text or labels."""
|
|
1167
|
+
if not isinstance(value, dict) or not isinstance(value.get('text'), str):
|
|
1168
|
+
return False
|
|
1169
|
+
if 'kind' in value:
|
|
1170
|
+
return value['kind'] in _KINDS and isinstance(value.get('name'), str)
|
|
1171
|
+
values = value.get('values')
|
|
1172
|
+
return (
|
|
1173
|
+
value.get('id') in MESSAGES
|
|
1174
|
+
and isinstance(values, dict)
|
|
1175
|
+
and all(isinstance(item, str) or valid_label(item) for item in values.values())
|
|
1176
|
+
and placeholders(MESSAGES[value['id']]) <= set(values)
|
|
1177
|
+
)
|
|
1178
|
+
|
|
1179
|
+
|
|
1180
|
+
def placeholders(template: Any) -> set[str]:
|
|
1181
|
+
"""Returns the names of the placeholders of a template, of every form of a plural one."""
|
|
1182
|
+
if isinstance(template, dict):
|
|
1183
|
+
return {name for text in template.values() for name in _PLACEHOLDER.findall(str(text))}
|
|
1184
|
+
return set(_PLACEHOLDER.findall(template)) if isinstance(template, str) else set()
|
|
1185
|
+
|
|
1186
|
+
|
|
1187
|
+
# The classes that only add the id of a message to another exception, by the module and name
|
|
1188
|
+
# they were defined with.
|
|
1189
|
+
_WRAPPERS: dict[tuple[str, str], type] = {}
|
|
1190
|
+
|
|
1191
|
+
|
|
1192
|
+
def _stamp(cls: type) -> None:
|
|
1193
|
+
"""Gives a class that only adds an id the module and name of the exception it stands for."""
|
|
1194
|
+
for base in cls.__mro__[1:]:
|
|
1195
|
+
if base is Message or base.__dict__.get('_wrapper'):
|
|
1196
|
+
continue
|
|
1197
|
+
cls.__module__, cls.__qualname__, cls.__name__ = (
|
|
1198
|
+
base.__module__,
|
|
1199
|
+
base.__qualname__,
|
|
1200
|
+
base.__name__,
|
|
1201
|
+
)
|
|
1202
|
+
return
|
|
1203
|
+
|
|
1204
|
+
|
|
1205
|
+
def public_names() -> None:
|
|
1206
|
+
"""Stamps every class that only adds an id again, after the public classes took their module.
|
|
1207
|
+
|
|
1208
|
+
`cuju.render` gives its public errors its own name as their module once it has imported
|
|
1209
|
+
them, so a class defined before then takes the public name here.
|
|
1210
|
+
"""
|
|
1211
|
+
for cls in _WRAPPERS.values():
|
|
1212
|
+
_stamp(cls)
|
|
1213
|
+
|
|
1214
|
+
|
|
1215
|
+
def _rebuild(defined: tuple[str, str], message_id: str, values: dict[str, Any]) -> BaseException:
|
|
1216
|
+
"""Returns the exception that `Message.__reduce__` pickled, of the class it was defined as."""
|
|
1217
|
+
module, name = defined
|
|
1218
|
+
importlib.import_module(module)
|
|
1219
|
+
cls = _WRAPPERS.get(defined) or getattr(sys.modules[module], name)
|
|
1220
|
+
return cls(message_id, **values)
|
|
1221
|
+
|
|
1222
|
+
|
|
1223
|
+
class Message:
|
|
1224
|
+
"""A mixin of exceptions whose text is a message of `MESSAGES`.
|
|
1225
|
+
|
|
1226
|
+
The exception reads as the English text, and carries the id in `message_id` and the values of
|
|
1227
|
+
its placeholders, each as text, in `values`. A class that only adds the id to another
|
|
1228
|
+
exception, which sets `_wrapper`, reads as that exception in a traceback and in its `repr`,
|
|
1229
|
+
such as `cuju.errors.ArgumentError`, and an `except` of either class catches it.
|
|
1230
|
+
"""
|
|
1231
|
+
|
|
1232
|
+
# A class that only adds the id to another exception names that exception in an answer.
|
|
1233
|
+
_wrapper = True
|
|
1234
|
+
message_id: str
|
|
1235
|
+
values: dict[str, str]
|
|
1236
|
+
|
|
1237
|
+
def __init_subclass__(cls, **options: Any) -> None:
|
|
1238
|
+
super().__init_subclass__(**options)
|
|
1239
|
+
if cls.__dict__.get('_wrapper'):
|
|
1240
|
+
cls._defined = (cls.__module__, cls.__qualname__)
|
|
1241
|
+
_WRAPPERS[cls._defined] = cls
|
|
1242
|
+
_stamp(cls)
|
|
1243
|
+
|
|
1244
|
+
def __init__(self, message_id: str, /, **values: Any) -> None:
|
|
1245
|
+
self.message_id = message_id
|
|
1246
|
+
self.values = {key: _value(value) for key, value in values.items()}
|
|
1247
|
+
super().__init__(text(message_id, **self.values))
|
|
1248
|
+
|
|
1249
|
+
def __reduce__(self) -> Any:
|
|
1250
|
+
cls = type(self)
|
|
1251
|
+
defined = cls.__dict__.get('_defined') or (cls.__module__, cls.__qualname__)
|
|
1252
|
+
return (_rebuild, (defined, self.message_id, self.values))
|
|
1253
|
+
|
|
1254
|
+
|
|
1255
|
+
class MessageError(Message, ArgumentError):
|
|
1256
|
+
"""An `ArgumentError` whose text is a message of `MESSAGES`, which the page translates."""
|
|
1257
|
+
|
|
1258
|
+
_wrapper = True
|
|
1259
|
+
|
|
1260
|
+
|
|
1261
|
+
def argument_error(error: BaseException) -> ArgumentError:
|
|
1262
|
+
"""Returns an `ArgumentError` with the text of `error`, and its id when it carries one."""
|
|
1263
|
+
message_id = getattr(error, 'message_id', None)
|
|
1264
|
+
if isinstance(message_id, str) and message_id in MESSAGES:
|
|
1265
|
+
return MessageError(message_id, **getattr(error, 'values', {}))
|
|
1266
|
+
return ArgumentError(str(error))
|
|
1267
|
+
|
|
1268
|
+
|
|
1269
|
+
def kind(error: BaseException) -> str:
|
|
1270
|
+
"""Returns the name of the exception that an answer shows, without the classes of ids.
|
|
1271
|
+
|
|
1272
|
+
A `MessageError` reads as the `ArgumentError` it stands for, so an answer of the form
|
|
1273
|
+
`ArgumentError: ...` reads as before.
|
|
1274
|
+
"""
|
|
1275
|
+
for cls in type(error).__mro__:
|
|
1276
|
+
if cls is Message or cls.__dict__.get('_wrapper'):
|
|
1277
|
+
continue
|
|
1278
|
+
return cls.__name__
|
|
1279
|
+
return type(error).__name__
|
|
1280
|
+
|
|
1281
|
+
|
|
1282
|
+
def described(error: BaseException) -> str:
|
|
1283
|
+
"""Returns the text `Kind: message` of an error, as answers and reports show it."""
|
|
1284
|
+
return f'{kind(error)}: {error}'
|
|
1285
|
+
|
|
1286
|
+
|
|
1287
|
+
def fields(message_id: str, /, **values: Any) -> dict[str, Any]:
|
|
1288
|
+
"""Returns the error fields of an answer or an entry for a message of `MESSAGES`."""
|
|
1289
|
+
shown = {key: _value(value) for key, value in values.items()}
|
|
1290
|
+
return {'error': text(message_id, **shown), 'error_id': message_id, 'error_values': shown}
|
|
1291
|
+
|
|
1292
|
+
|
|
1293
|
+
def error_fields(error: BaseException, shown: str | None = None) -> dict[str, Any]:
|
|
1294
|
+
"""Returns the error fields for an exception, with its id when it carries one.
|
|
1295
|
+
|
|
1296
|
+
Args:
|
|
1297
|
+
error: The exception.
|
|
1298
|
+
shown: The text of the answer, by default `str(error)`.
|
|
1299
|
+
"""
|
|
1300
|
+
result: dict[str, Any] = {'error': str(error) if shown is None else shown}
|
|
1301
|
+
message_id = getattr(error, 'message_id', None)
|
|
1302
|
+
if isinstance(message_id, str) and message_id in MESSAGES:
|
|
1303
|
+
result['error_id'] = message_id
|
|
1304
|
+
result['error_values'] = dict(getattr(error, 'values', {}) or {})
|
|
1305
|
+
return result
|
|
1306
|
+
|
|
1307
|
+
|
|
1308
|
+
def id_fields(error: BaseException) -> dict[str, Any]:
|
|
1309
|
+
"""Returns `error_id` and `error_values` of an exception that carries an id, else nothing."""
|
|
1310
|
+
found = error_fields(error)
|
|
1311
|
+
found.pop('error')
|
|
1312
|
+
return found
|
|
1313
|
+
|
|
1314
|
+
|
|
1315
|
+
def entry_fields(error: BaseException | str | None, shown: str | None = None) -> dict[str, Any]:
|
|
1316
|
+
"""Returns the error fields of an entry, always all three, `None` where it has none.
|
|
1317
|
+
|
|
1318
|
+
Args:
|
|
1319
|
+
error: The exception, or a text without an id, or `None` for no error.
|
|
1320
|
+
shown: The text that the entry shows for an exception, by default `str(error)`.
|
|
1321
|
+
"""
|
|
1322
|
+
result: dict[str, Any] = {'error': None, 'error_id': None, 'error_values': None}
|
|
1323
|
+
if isinstance(error, BaseException):
|
|
1324
|
+
result.update(error_fields(error, shown))
|
|
1325
|
+
elif error is not None:
|
|
1326
|
+
result['error'] = error
|
|
1327
|
+
return result
|
|
1328
|
+
|
|
1329
|
+
|
|
1330
|
+
def exit_fields(message_id: str, code: Any, detail: str = '') -> dict[str, Any]:
|
|
1331
|
+
"""Returns the error fields of a child process that ended with `code`.
|
|
1332
|
+
|
|
1333
|
+
The message `message_id` names the exit code, and `message_id` with `_detail` added names the
|
|
1334
|
+
last lines that the process wrote as well, when it wrote any.
|
|
1335
|
+
"""
|
|
1336
|
+
if detail:
|
|
1337
|
+
return fields(f'{message_id}_detail', code=code, detail=detail)
|
|
1338
|
+
return fields(message_id, code=code)
|
|
1339
|
+
|
|
1340
|
+
|
|
1341
|
+
def report_fields(report: dict[str, Any]) -> dict[str, Any]:
|
|
1342
|
+
"""Returns the error fields of an entry from the report of a child process.
|
|
1343
|
+
|
|
1344
|
+
The id and values pass on only when they name a message of `MESSAGES` with a text value for
|
|
1345
|
+
each of its placeholders, so a report of another version leaves its error without an id.
|
|
1346
|
+
"""
|
|
1347
|
+
error = report.get('error')
|
|
1348
|
+
result: dict[str, Any] = {
|
|
1349
|
+
'error': error if isinstance(error, str) else None,
|
|
1350
|
+
'error_id': None,
|
|
1351
|
+
'error_values': None,
|
|
1352
|
+
}
|
|
1353
|
+
message_id, values = report.get('error_id'), report.get('error_values')
|
|
1354
|
+
if (
|
|
1355
|
+
isinstance(error, str)
|
|
1356
|
+
and isinstance(message_id, str)
|
|
1357
|
+
and message_id in MESSAGES
|
|
1358
|
+
and isinstance(values, dict)
|
|
1359
|
+
and all(isinstance(value, str) or valid_label(value) for value in values.values())
|
|
1360
|
+
and placeholders(MESSAGES[message_id]) <= set(values)
|
|
1361
|
+
):
|
|
1362
|
+
result.update(error_id=message_id, error_values=dict(values))
|
|
1363
|
+
return result
|
|
1364
|
+
|
|
1365
|
+
|
|
1366
|
+
def compare(messages: dict[str, Any]) -> list[str]:
|
|
1367
|
+
"""Returns the differences between `MESSAGES` and the `server.` messages of a language file.
|
|
1368
|
+
|
|
1369
|
+
Args:
|
|
1370
|
+
messages: The messages of a language file, such as the parsed `web/languages/en.json`.
|
|
1371
|
+
For English each text must equal the registry's, and for another language each
|
|
1372
|
+
message must hold the same placeholders.
|
|
1373
|
+
|
|
1374
|
+
Returns:
|
|
1375
|
+
One line for each id missing from either side, each text that differs from the English,
|
|
1376
|
+
and each placeholder that differs, empty when they agree.
|
|
1377
|
+
"""
|
|
1378
|
+
english = messages.get('@language', {}).get('code', 'en') == 'en'
|
|
1379
|
+
found = {key: value for key, value in messages.items() if key.startswith('server.')}
|
|
1380
|
+
# A translation may lack messages, which the page then shows in English.
|
|
1381
|
+
problems = [f'missing {key}' for key in MESSAGES if key not in found and english]
|
|
1382
|
+
problems += [f'unknown {key}' for key in found if key not in MESSAGES]
|
|
1383
|
+
for key, template in MESSAGES.items():
|
|
1384
|
+
if key not in found:
|
|
1385
|
+
continue
|
|
1386
|
+
value = found[key]
|
|
1387
|
+
plural = isinstance(value, dict) and all(isinstance(text, str) for text in value.values())
|
|
1388
|
+
if not isinstance(value, str) and not plural:
|
|
1389
|
+
problems.append(f'{key} is not a text or an object of plural forms')
|
|
1390
|
+
elif isinstance(template, str) and plural and 'count' not in placeholders(template):
|
|
1391
|
+
problems.append(f'{key} has plural forms, and the registry has no count')
|
|
1392
|
+
elif english and value != template:
|
|
1393
|
+
problems.append(f'{key} reads {value!r}, the registry {template!r}')
|
|
1394
|
+
elif placeholders(value) != placeholders(template):
|
|
1395
|
+
problems.append(f'{key} has the placeholders {sorted(placeholders(value))}')
|
|
1396
|
+
return problems
|