the browser-facing portion of osu!
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge branch 'master' into feature/nomination-updates-rebased

bakaneko b00743a9 1fc5223c

+1461 -1340
+3 -3
.env.example
··· 295 295 # BEATMAPSET_UPLOAD_BONUS_PER_RANKED_SUPPORTER=1 296 296 # BEATMAPSET_UPLOAD_BONUS_PER_RANKED_MAX_SUPPORTER=12 297 297 298 - # RECAPTCHA_SECRET= 299 - # RECAPTCHA_SITEKEY= 300 - # RECAPTCHA_THRESHOLD= 298 + # CAPTCHA_THRESHOLD= 299 + # TURNSTILE_SITE_KEY= 300 + # TURNSTILE_SECRET_KEY= 301 301 302 302 # TWITCH_CLIENT_ID= 303 303 # TWITCH_CLIENT_SECRET=
+9 -1
app/Http/Controllers/RankingController.php
··· 215 215 break; 216 216 217 217 default: 218 - $ranking = json_collection($stats, 'UserStatistics', ['user', 'user.cover', 'user.country']); 218 + $includes = ['user', 'user.cover', 'user.country']; 219 + if ($this->country !== null) { 220 + $includes[] = 'country_rank'; 221 + $startRank = (max($page, 1) - 1) * static::PAGE_SIZE + 1; 222 + foreach ($stats as $index => $entry) { 223 + $entry->countryRank = $startRank + $index; 224 + } 225 + } 226 + $ranking = json_collection($stats, 'UserStatistics', $includes); 219 227 break; 220 228 } 221 229
+4 -4
app/Http/Controllers/SessionsController.php
··· 10 10 use App\Models\User; 11 11 use App\Transformers\CurrentUserTransformer; 12 12 use Auth; 13 - use NoCaptcha; 13 + use romanzipp\Turnstile\Validator as TurnstileValidator; 14 14 15 15 class SessionsController extends Controller 16 16 { ··· 27 27 { 28 28 $request = request(); 29 29 30 - $params = get_params($request->all(), null, ['username:string', 'password:string', 'remember:bool', 'g-recaptcha-response:string']); 30 + $params = get_params($request->all(), null, ['username:string', 'password:string', 'remember:bool', 'cf-turnstile-response:string']); 31 31 $username = presence(trim($params['username'] ?? null)); 32 32 $password = presence($params['password'] ?? null); 33 33 $remember = $params['remember'] ?? false; ··· 45 45 } 46 46 47 47 if (captcha_login_triggered()) { 48 - $token = presence($params['g-recaptcha-response'] ?? null); 48 + $token = presence($params['cf-turnstile-response'] ?? null); 49 49 $validCaptcha = false; 50 50 51 51 if ($token !== null) { 52 - $validCaptcha = NoCaptcha::verifyResponse($token); 52 + $validCaptcha = (new TurnstileValidator())->validate($token)->isValid(); 53 53 } 54 54 55 55 if (!$validCaptcha) {
+3 -4
app/Http/Controllers/UsersController.php
··· 33 33 use App\Transformers\UserTransformer; 34 34 use Auth; 35 35 use Illuminate\Database\Eloquent\Relations\MorphTo; 36 - use NoCaptcha; 37 36 use Request; 37 + use romanzipp\Turnstile\Validator as TurnstileValidator; 38 38 use Sentry\State\Scope; 39 39 use Symfony\Component\HttpKernel\Exception\HttpException; 40 40 ··· 250 250 $rawParams = request()->all(); 251 251 252 252 if (captcha_enabled()) { 253 - static $captchaField = 'g-recaptcha-response'; 254 - $token = $rawParams[$captchaField] ?? null; 253 + $token = get_string($rawParams['cf-turnstile-response'] ?? null) ?? ''; 255 254 256 - $validCaptcha = NoCaptcha::verifyResponse($token); 255 + $validCaptcha = (new TurnstileValidator())->validate($token)->isValid(); 257 256 258 257 if (!$validCaptcha) { 259 258 return abort(422, 'invalid captcha');
+8 -2
app/Libraries/Search/BeatmapsetSearch.php
··· 429 429 ->where('user_id', $this->params->user->getKey()) 430 430 ->whereIn('ruleset_id', $this->getSelectedModes()); 431 431 432 + $showLegacyOnly = ScoreSearchParams::showLegacyForUser($this->params->user) ?? false; 433 + if ($showLegacyOnly) { 434 + $scoreField = 'legacy_total_score'; 435 + $query->where('legacy_score_id', '>', 0); 436 + } else { 437 + $scoreField = 'total_score'; 438 + } 439 + 432 440 if ($rank === null) { 433 441 return $query->distinct('beatmap_id')->pluck('beatmap_id'); 434 442 } 435 443 436 444 $topScores = []; 437 - $showLegacyOnly = ScoreSearchParams::showLegacyForUser($this->params->user) ?? false; 438 - $scoreField = $showLegacyOnly ? 'legacy_total_score' : 'total_score'; 439 445 foreach ($query->get() as $score) { 440 446 $prevScore = $topScores[$score->beatmap_id] ?? null; 441 447
+1 -1
app/Models/Score/Model.php
··· 28 28 'date' => 'datetime', 29 29 'pass' => 'bool', 30 30 'perfect' => 'bool', 31 - 'replay' => 'bool', 31 + 'replay' => 'bool', // for best model 32 32 ]; 33 33 protected $primaryKey = 'score_id'; 34 34
+11 -3
app/Models/Solo/Score.php
··· 260 260 throw new InvariantException("'{$this->rank}' is not a valid rank."); 261 261 } 262 262 263 - foreach (['total_score', 'accuracy', 'max_combo', 'passed'] as $field) { 263 + if ($this->accuracy === null || $this->accuracy < 0 || $this->accuracy > 1) { 264 + throw new InvariantException('Invalid accuracy.'); 265 + } 266 + 267 + // unsigned int (as per the column) 268 + if ($this->total_score === null || $this->total_score < 0 || $this->total_score > 4294967295) { 269 + throw new InvariantException('Invalid total_score.'); 270 + } 271 + 272 + foreach (['max_combo', 'passed'] as $field) { 264 273 if (!present($this->$field)) { 265 274 throw new InvariantException("field missing: '{$field}'"); 266 275 } ··· 352 361 ? LegacyScore\Best\Model::getClass($this->getMode()) 353 362 : LegacyScore\Model::getClass($this->getMode()); 354 363 364 + // Only attributes available to best model (and `pass`). 355 365 $score = new $scoreClass([ 356 366 'beatmap_id' => $this->beatmap_id, 357 - 'beatmapset_id' => $this->beatmap?->beatmapset_id ?? 0, 358 367 'countmiss' => $statistics->miss, 359 368 'date' => $this->ended_at_json, 360 369 'enabled_mods' => app('mods')->idsToBitset(array_column($data->mods, 'acronym')), ··· 364 373 'pp' => $this->pp, 365 374 'replay' => $this->has_replay, 366 375 'score' => $this->legacy_total_score, 367 - 'scorechecksum' => "\0", 368 376 'user_id' => $this->user_id, 369 377 ]); 370 378
+6 -1
app/Models/UserStatistics/Model.php
··· 33 33 34 34 public $timestamps = false; 35 35 public $incrementing = false; 36 + /** 37 + * allows preloading the value if it's known through other means 38 + * (for use in RankingController) 39 + */ 40 + public int $countryRank; 36 41 37 42 const UPDATED_AT = 'last_update'; 38 43 ··· 160 165 161 166 public function countryRank() 162 167 { 163 - return $this->memoize(__FUNCTION__, function () { 168 + return $this->countryRank ?? $this->memoize(__FUNCTION__, function () { 164 169 if (!$this->isRanked()) { 165 170 return; 166 171 }
+3 -3
app/helpers.php
··· 187 187 188 188 function captcha_enabled() 189 189 { 190 - return $GLOBALS['cfg']['captcha']['sitekey'] !== '' && $GLOBALS['cfg']['captcha']['secret'] !== ''; 190 + return $GLOBALS['cfg']['turnstile']['site_key'] !== '' && $GLOBALS['cfg']['turnstile']['secret_key'] !== ''; 191 191 } 192 192 193 193 function captcha_login_triggered() ··· 196 196 return false; 197 197 } 198 198 199 - if ($GLOBALS['cfg']['captcha']['threshold'] === 0) { 199 + if ($GLOBALS['cfg']['osu']['captcha']['threshold'] === 0) { 200 200 $triggered = true; 201 201 } else { 202 202 $loginAttempts = LoginAttempt::find(request()->getClientIp()); 203 - $triggered = $loginAttempts && $loginAttempts->failed_attempts >= $GLOBALS['cfg']['captcha']['threshold']; 203 + $triggered = $loginAttempts && $loginAttempts->failed_attempts >= $GLOBALS['cfg']['osu']['captcha']['threshold']; 204 204 } 205 205 206 206 return $triggered;
+1 -1
composer.json
··· 17 17 "require": { 18 18 "ext-ds": "*", 19 19 "ext-redis": "*", 20 - "anhskohbo/no-captcha": "^3.2", 21 20 "chaseconey/laravel-datadog-helper": ">=1.2.0", 22 21 "egulias/email-validator": "*", 23 22 "elasticsearch/elasticsearch": "^7.12.0", ··· 42 41 "maennchen/zipstream-php": "^2.1", 43 42 "mariuzzo/laravel-js-localization": "*", 44 43 "paypal/paypal-checkout-sdk": "*", 44 + "romanzipp/laravel-turnstile": "^1.3", 45 45 "sentry/sentry-laravel": "*", 46 46 "symfony/yaml": "*", 47 47 "tightenco/ziggy": "^1.8",
+66 -65
composer.lock
··· 4 4 "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 5 "This file is @generated automatically" 6 6 ], 7 - "content-hash": "ac8c48619f98cf4c57d2cd95737a86b7", 7 + "content-hash": "5262cdb052aa730861db0fdbc3cc2e20", 8 8 "packages": [ 9 - { 10 - "name": "anhskohbo/no-captcha", 11 - "version": "3.5.0", 12 - "source": { 13 - "type": "git", 14 - "url": "https://github.com/anhskohbo/no-captcha.git", 15 - "reference": "81302b9ddfb4ee5904b66f24bf3e11ea74fa0b92" 16 - }, 17 - "dist": { 18 - "type": "zip", 19 - "url": "https://api.github.com/repos/anhskohbo/no-captcha/zipball/81302b9ddfb4ee5904b66f24bf3e11ea74fa0b92", 20 - "reference": "81302b9ddfb4ee5904b66f24bf3e11ea74fa0b92", 21 - "shasum": "" 22 - }, 23 - "require": { 24 - "guzzlehttp/guzzle": "^6.2|^7.0", 25 - "illuminate/support": "^5.0|^6.0|^7.0|^8.0|^9.0|^10.0", 26 - "php": ">=5.5.5" 27 - }, 28 - "require-dev": { 29 - "phpunit/phpunit": "~4.8|^9.5.10" 30 - }, 31 - "type": "library", 32 - "extra": { 33 - "laravel": { 34 - "providers": [ 35 - "Anhskohbo\\NoCaptcha\\NoCaptchaServiceProvider" 36 - ], 37 - "aliases": { 38 - "NoCaptcha": "Anhskohbo\\NoCaptcha\\Facades\\NoCaptcha" 39 - } 40 - } 41 - }, 42 - "autoload": { 43 - "psr-4": { 44 - "Anhskohbo\\NoCaptcha\\": "src/" 45 - } 46 - }, 47 - "notification-url": "https://packagist.org/downloads/", 48 - "license": [ 49 - "MIT" 50 - ], 51 - "authors": [ 52 - { 53 - "name": "anhskohbo", 54 - "email": "anhskohbo@gmail.com" 55 - } 56 - ], 57 - "description": "No CAPTCHA reCAPTCHA For Laravel.", 58 - "keywords": [ 59 - "captcha", 60 - "laravel", 61 - "laravel4", 62 - "laravel5", 63 - "laravel6", 64 - "no-captcha", 65 - "recaptcha" 66 - ], 67 - "support": { 68 - "issues": "https://github.com/anhskohbo/no-captcha/issues", 69 - "source": "https://github.com/anhskohbo/no-captcha/tree/3.5.0" 70 - }, 71 - "time": "2023-02-15T16:07:08+00:00" 72 - }, 73 9 { 74 10 "name": "aws/aws-crt-php", 75 11 "version": "v1.2.4", ··· 7404 7340 } 7405 7341 ], 7406 7342 "time": "2023-11-16T16:16:50+00:00" 7343 + }, 7344 + { 7345 + "name": "romanzipp/laravel-turnstile", 7346 + "version": "1.3.0", 7347 + "source": { 7348 + "type": "git", 7349 + "url": "https://github.com/romanzipp/Laravel-Turnstile.git", 7350 + "reference": "f377d8a889c0c70512e4eb1f90229fed8b15430b" 7351 + }, 7352 + "dist": { 7353 + "type": "zip", 7354 + "url": "https://api.github.com/repos/romanzipp/Laravel-Turnstile/zipball/f377d8a889c0c70512e4eb1f90229fed8b15430b", 7355 + "reference": "f377d8a889c0c70512e4eb1f90229fed8b15430b", 7356 + "shasum": "" 7357 + }, 7358 + "require": { 7359 + "ext-json": "*", 7360 + "guzzlehttp/guzzle": "^7.0", 7361 + "illuminate/support": "^5.5|^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", 7362 + "php": "^8.0" 7363 + }, 7364 + "require-dev": { 7365 + "friendsofphp/php-cs-fixer": "^3.0", 7366 + "mockery/mockery": "^1.5", 7367 + "orchestra/testbench": ">=3.8", 7368 + "phpstan/phpstan": "^0.12.99|^1.0", 7369 + "phpunit/phpunit": "^8.0|^9.0", 7370 + "romanzipp/php-cs-fixer-config": "^3.0" 7371 + }, 7372 + "type": "library", 7373 + "extra": { 7374 + "laravel": { 7375 + "providers": [ 7376 + "romanzipp\\Turnstile\\Providers\\TurnstileServiceProvider" 7377 + ] 7378 + } 7379 + }, 7380 + "autoload": { 7381 + "psr-4": { 7382 + "romanzipp\\Turnstile\\": "src" 7383 + } 7384 + }, 7385 + "notification-url": "https://packagist.org/downloads/", 7386 + "license": [ 7387 + "MIT" 7388 + ], 7389 + "authors": [ 7390 + { 7391 + "name": "romanzipp", 7392 + "email": "ich@ich.wtf", 7393 + "homepage": "https://ich.wtf" 7394 + } 7395 + ], 7396 + "description": "Cloudflare Turnstile package for Laravel", 7397 + "support": { 7398 + "issues": "https://github.com/romanzipp/Laravel-Turnstile/issues", 7399 + "source": "https://github.com/romanzipp/Laravel-Turnstile/tree/1.3.0" 7400 + }, 7401 + "funding": [ 7402 + { 7403 + "url": "https://github.com/romanzipp", 7404 + "type": "github" 7405 + } 7406 + ], 7407 + "time": "2024-03-19T08:23:07+00:00" 7407 7408 }, 7408 7409 { 7409 7410 "name": "sentry/sdk",
-10
config/captcha.php
··· 1 - <?php 2 - 3 - return [ 4 - 'secret' => env('RECAPTCHA_SECRET', ''), 5 - 'sitekey' => env('RECAPTCHA_SITEKEY', ''), 6 - 'threshold' => get_int(env('RECAPTCHA_THRESHOLD')) ?? 2, 7 - 'options' => [ 8 - 'timeout' => 30, 9 - ], 10 - ];
+3
config/osu.php
··· 82 82 'key' => presence(env('CAMO_KEY')), 83 83 'prefix' => env('CAMO_PREFIX', 'https://i.ppy.sh/'), 84 84 ], 85 + 'captcha' => [ 86 + 'threshold' => get_int(env('CAPTCHA_THRESHOLD')) ?? 2, 87 + ], 85 88 'chat' => [ 86 89 'channel_limit' => get_int(env('CHAT_CHANNEL_LIMIT')) ?? 10000, 87 90 'message_length_limit' => get_int(env('CHAT_MESSAGE_LENGTH_LIMIT')) ?? 450,
+12
config/turnstile.php
··· 1 + <?php 2 + 3 + return [ 4 + 'site_key' => env('TURNSTILE_SITE_KEY') ?? '', 5 + 'secret_key' => env('TURNSTILE_SECRET_KEY') ?? '', 6 + 7 + // Include visitor IP adresse in verify challenge data 8 + 'include_ip' => false, 9 + 10 + // Allow access in case the HTTP request fails with an 5xx error 11 + 'allow_on_failure' => false, 12 + ];
+1 -1
package.json
··· 14 14 "@fortawesome/fontawesome-free": "^5.6.3", 15 15 "@types/autosize": "^4.0.1", 16 16 "@types/bootstrap": "^3.3.0", 17 + "@types/cloudflare-turnstile": "^0.1.5", 17 18 "@types/d3": "^7.1.0", 18 - "@types/grecaptcha": "^3.0.1", 19 19 "@types/is-hotkey": "^0.1.1", 20 20 "@types/jasmine": "^3.3.13", 21 21 "@types/jquery": "^3.3.0",
+6 -7
public/images/layout/osu-lazer-logo-white.svg
··· 1 - <svg width="350" height="350" viewBox="0 0 350 350" fill="none" xmlns="http://www.w3.org/2000/svg"> 2 - <path d="M273.056 127.904C277.465 127.904 280.488 131.178 280.488 135.713V177.657C280.488 182.192 277.465 185.467 273.056 185.467C268.521 185.467 265.624 182.192 265.624 177.657V135.713C265.624 131.178 268.521 127.904 273.056 127.904ZM273.056 212.17C267.64 212.17 263.357 207.887 263.357 202.471C263.357 197.181 267.64 192.898 273.056 192.898C278.472 192.898 282.755 197.181 282.755 202.471C282.755 207.887 278.472 212.17 273.056 212.17Z" fill="white"/> 3 - <path d="M245.912 148.183C250.447 148.183 253.344 151.458 253.344 155.866V183.703C253.344 204.234 240.685 212.17 225.318 212.17C209.825 212.17 197.167 204.234 197.167 183.703V155.866C197.167 151.458 200.064 148.183 204.599 148.183C209.007 148.183 212.03 151.458 212.03 155.866V182.696C212.03 193.906 216.753 198.314 225.318 198.314C233.757 198.314 238.48 193.906 238.48 182.696V155.866C238.48 151.458 241.503 148.183 245.912 148.183Z" fill="white"/> 4 - <path d="M155.023 165.187C155.023 169.344 159.306 170.856 167.493 172.745C178.829 175.516 189.662 178.413 189.662 192.394C189.662 205.998 179.585 212.17 165.1 212.17C153.134 212.17 144.443 208.013 140.412 203.227C137.263 199.448 137.641 196.173 140.664 193.276C144.443 189.623 147.717 191.135 149.859 193.024C153.26 196.173 157.416 199.448 165.478 199.448C171.524 199.448 175.302 197.433 175.302 193.402C175.302 189.371 171.272 187.986 160.691 185.089C150.237 182.192 140.916 179.421 140.916 166.951C140.916 152.969 152.252 147.175 164.848 147.175C172.027 147.175 180.215 149.065 185.253 154.481C187.394 156.622 189.032 159.645 185.253 163.802C181.474 167.707 178.703 166.573 176.058 164.558C173.665 162.794 169.508 159.771 163.21 159.771C158.928 159.771 155.023 161.157 155.023 165.187Z" fill="white"/> 5 - <path d="M100.145 212.17C80.9995 212.17 67.27 198.315 67.27 179.673C67.27 160.905 80.9995 147.175 100.145 147.175C119.291 147.175 133.02 160.905 133.02 179.673C133.02 198.315 119.291 212.17 100.145 212.17ZM100.145 198.315C111.23 198.315 118.157 190.253 118.157 179.673C118.157 169.092 111.23 160.905 100.145 160.905C89.0609 160.905 82.1332 169.092 82.1332 179.673C82.1332 190.253 89.0609 198.315 100.145 198.315Z" fill="white"/> 6 - <path d="M332.5 175C332.5 261.985 261.985 332.5 175 332.5C88.0152 332.5 17.5 261.985 17.5 175C17.5 88.0152 88.0152 17.5 175 17.5C261.985 17.5 332.5 88.0152 332.5 175ZM33.25 175C33.25 253.286 96.7136 316.75 175 316.75C253.286 316.75 316.75 253.286 316.75 175C316.75 96.7136 253.286 33.25 175 33.25C96.7136 33.25 33.25 96.7136 33.25 175Z" fill="white"/> 7 - <path d="M90.265 179.848C90.265 185.264 94.5476 189.547 99.9638 189.547C105.38 189.547 109.663 185.264 109.663 179.848C109.663 174.557 105.38 170.275 99.9638 170.275C94.5476 170.275 90.265 174.557 90.265 179.848Z" fill="white"/> 1 + <svg width="1000" height="1000" viewBox="0 0 1000 1000" fill="none" xmlns="http://www.w3.org/2000/svg"> 2 + <path d="M780.145 365C792.842 365 801.549 374.432 801.549 387.492V508.295C801.549 521.355 792.842 530.787 780.145 530.787C767.086 530.787 758.742 521.355 758.742 508.295V387.492C758.742 374.432 767.086 365 780.145 365ZM780.145 607.694C764.546 607.694 752.212 595.36 752.212 579.761C752.212 564.525 764.546 552.19 780.145 552.19C795.745 552.19 808.079 564.525 808.079 579.761C808.079 595.36 795.745 607.694 780.145 607.694Z" fill="white"/> 3 + <path d="M701.968 423.406C715.028 423.406 723.372 432.838 723.372 445.535V525.708C723.372 584.84 686.915 607.694 642.656 607.694C598.035 607.694 561.578 584.84 561.578 525.708V445.535C561.578 432.838 569.922 423.406 582.982 423.406C595.679 423.406 604.385 432.838 604.385 445.535V522.806C604.385 555.092 617.988 567.789 642.656 567.789C666.962 567.789 680.565 555.092 680.565 522.806V445.535C680.565 432.838 689.271 423.406 701.968 423.406Z" fill="white"/> 4 + <path d="M440.2 472.381C440.2 484.352 452.534 488.706 476.115 494.147C508.764 502.128 539.963 510.472 539.963 550.74C539.963 589.919 510.941 607.695 469.222 607.695C434.759 607.695 409.727 595.723 398.119 581.938C389.049 571.055 390.138 561.623 398.844 553.279C409.727 542.759 419.159 547.112 425.327 552.553C435.121 561.623 447.093 571.055 470.31 571.055C487.723 571.055 498.607 565.25 498.607 553.642C498.607 542.033 486.998 538.043 456.525 529.699C426.415 521.355 399.57 513.374 399.57 477.46C399.57 437.192 432.219 420.504 468.496 420.504C489.174 420.504 512.755 425.946 527.266 441.545C533.433 447.712 538.149 456.419 527.266 468.39C516.382 479.636 508.401 476.371 500.783 470.567C493.891 465.488 481.919 456.782 463.78 456.782C451.446 456.782 440.2 460.772 440.2 472.381Z" fill="white"/> 5 + <path d="M281.815 607.695C229.509 607.695 192 567.661 192 513.797C192 459.569 229.509 419.899 281.815 419.899C334.122 419.899 371.631 459.569 371.631 513.797C371.631 567.661 334.122 607.695 281.815 607.695ZM281.815 567.661C312.098 567.661 331.025 544.368 331.025 513.797C331.025 483.225 312.098 459.569 281.815 459.569C251.533 459.569 232.606 483.225 232.606 513.797C232.606 544.368 251.533 567.661 281.815 567.661Z" fill="white"/> 6 + <path d="M950 500C950 748.528 748.528 950 500 950C251.472 950 50 748.528 50 500C50 251.472 251.472 50 500 50C748.528 50 950 251.472 950 500ZM95 500C95 723.675 276.325 905 500 905C723.675 905 905 723.675 905 500C905 276.325 723.675 95 500 95C276.325 95 95 276.325 95 500Z" fill="white"/> 8 7 </svg>
+1 -1
resources/css/bem/beatmap-owner-editor.less
··· 59 59 60 60 &--static { 61 61 // defined here so it doesn't trigger tooltip in addition to usercard 62 - .u-ellipsis-overflow(); 62 + .ellipsis-overflow(); 63 63 background-color: transparent; 64 64 padding-left: 0; 65 65 padding-right: 0;
+5
resources/css/bem/contest-voting-list.less
··· 125 125 } 126 126 } 127 127 128 + &__title-link { 129 + .link-white(); 130 + display: block; 131 + } 132 + 128 133 &__votes-bar { 129 134 position: absolute; 130 135 height: 100%;
-1
resources/css/bem/nav2.less
··· 58 58 background-size: contain; 59 59 background-repeat: no-repeat; 60 60 transition: all 100ms ease-in-out; 61 - will-change: opacity, transform; 62 61 63 62 background-image: var(--nav-logo); 64 63
+1 -1
resources/css/bem/play-detail.less
··· 65 65 } 66 66 67 67 &__beatmap { 68 - .u-ellipsis-overflow(); 68 + .ellipsis-overflow(); 69 69 color: @yellow-dark; 70 70 } 71 71
+6
resources/css/functions.less
··· 10 10 height: @diameter; 11 11 } 12 12 13 + .ellipsis-overflow() { 14 + white-space: nowrap; 15 + text-overflow: ellipsis; 16 + overflow: hidden; 17 + } 18 + 13 19 .fade-element(@duration, @type: ease-in-out, @target: all) { 14 20 transition: @target @duration @type; 15 21 }
+2 -4
resources/css/utilities.less
··· 23 23 } 24 24 25 25 .u-ellipsis-overflow { 26 - white-space: nowrap !important; 27 - text-overflow: ellipsis !important; 28 - overflow: hidden !important; 26 + .ellipsis-overflow() !important; 29 27 } 30 28 31 29 .u-ellipsis-overflow-desktop { 32 30 @media @desktop { 33 - .u-ellipsis-overflow(); 31 + .ellipsis-overflow() !important; 34 32 } 35 33 } 36 34
+6 -7
resources/js/beatmap-discussions/discussions-state.ts
··· 288 288 @computed 289 289 get unresolvedDiscussionCounts() { 290 290 const byBeatmap: Partial<Record<number, number>> = {}; 291 - const byMode: Record<GameMode, number> = { 292 - fruits: 0, 293 - mania: 0, 294 - osu: 0, 295 - taiko: 0, 296 - }; 291 + const byMode: Partial<Record<GameMode, number>> = {}; 292 + // show at least 0 for available rulesets 293 + this.store.beatmaps.forEach((beatmap) => { 294 + byMode[beatmap.mode] ??= 0; 295 + }); 297 296 298 297 for (const discussion of this.nonDeletedDiscussions) { 299 298 if (discussion.beatmap_id != null && discussion.can_be_resolved && !discussion.resolved) { ··· 301 300 302 301 const mode = this.store.beatmaps.get(discussion.beatmap_id)?.mode; 303 302 if (mode != null) { 304 - byMode[mode]++; 303 + byMode[mode] = (byMode[mode] ?? 0) + 1; 305 304 } 306 305 } 307 306 }
+1 -1
resources/js/comments-index/index.tsx
··· 25 25 } 26 26 27 27 render() { 28 - const comments = this.controller.getComments(this.controller.state.commentIdsByParentId[0] ?? []); 28 + const comments = this.controller.getComments(this.controller.state.commentIdsByParentId[-1] ?? []); 29 29 30 30 return comments.length === 0 31 31 ? (
+1 -1
resources/js/comments-show/index.tsx
··· 24 24 } 25 25 26 26 render() { 27 - const comment = this.controller.getComments(this.controller.state.commentIdsByParentId[0])[0]; 27 + const comment = this.controller.getComments(this.controller.state.commentIdsByParentId[-1])[0]; 28 28 29 29 if (comment == null) { 30 30 throw new Error('missing comment');
+55 -23
resources/js/components/comment.tsx
··· 8 8 import { action, computed, makeObservable, observable } from 'mobx'; 9 9 import { observer } from 'mobx-react'; 10 10 import CommentModel from 'models/comment'; 11 + import { canModerateComments } from 'models/comment'; 11 12 import { deletedUserJson } from 'models/user'; 12 13 import core from 'osu-core-singleton'; 13 14 import * as React from 'react'; ··· 107 108 return this.props.controller.isVoting(this.props.comment); 108 109 } 109 110 111 + @computed 110 112 private get replies() { 111 - const ids = this.props.controller.state.commentIdsByParentId[this.props.comment.id]; 113 + return this.props.controller.getReplies(this.props.comment); 114 + } 115 + 116 + @computed 117 + private get shouldRender() { 118 + if (!this.props.comment.isDeleted) { 119 + return true; 120 + } 121 + 122 + // always render in single comment page 123 + if (this.props.showToolbar) { 124 + return true; 125 + } 112 126 113 - return this.props.controller.getComments(ids); 127 + if (canModerateComments() && core.userPreferences.get('comments_show_deleted')) { 128 + return true; 129 + } 130 + 131 + const replies = this.replies; 132 + 133 + return replies.length > 0 && replies.some((reply) => reply.isVisible); 134 + } 135 + 136 + @computed 137 + private get shouldRenderContent() { 138 + if (this.props.comment.messageHtml == null) { 139 + return false; 140 + } 141 + 142 + if (canModerateComments() && core.userPreferences.get('comments_show_deleted')) { 143 + return true; 144 + } 145 + 146 + return !this.props.comment.isDeleted; 114 147 } 115 148 116 149 @computed ··· 138 171 } 139 172 140 173 render() { 174 + if (!this.shouldRender) { 175 + return null; 176 + } 177 + 141 178 return ( 142 179 <div className={classWithModifiers( 143 180 'comment', ··· 250 287 ); 251 288 } 252 289 253 - private readonly renderComment = (comment: CommentModel) => { 254 - if (comment == null || (comment.isDeleted && !core.userPreferences.get('comments_show_deleted'))) { 255 - return; 256 - } 257 - 258 - return ( 259 - <Comment 260 - key={comment.id} 261 - comment={comment} 262 - controller={this.props.controller} 263 - depth={this.props.depth + 1} 264 - expandReplies={this.props.expandReplies} 265 - modifiers={this.props.modifiers} 266 - /> 267 - ); 268 - }; 290 + private readonly renderComment = (comment: CommentModel) => ( 291 + <Comment 292 + key={comment.id} 293 + comment={comment} 294 + controller={this.props.controller} 295 + depth={this.props.depth + 1} 296 + expandReplies={this.props.expandReplies} 297 + modifiers={this.props.modifiers} 298 + /> 299 + ); 269 300 270 301 private renderCommentableMeta() { 271 302 if (!this.props.showCommentableMeta) return; ··· 476 507 modifiers={this.props.modifiers} 477 508 /> 478 509 </div> 479 - : this.props.comment.messageHtml != null && 510 + : this.shouldRenderContent && 480 511 <> 481 512 <div 482 513 className='comment__message' 483 514 dangerouslySetInnerHTML={{ 484 - __html: this.props.comment.messageHtml, 515 + __html: this.props.comment.messageHtml ?? '', 485 516 }} 486 517 /> 487 518 {this.isLongContent && this.renderToggleClipButton()} ··· 494 525 </div> 495 526 </div> 496 527 497 - {this.props.comment.repliesCount > 0 && 528 + {this.props.comment.visibleReplyCount > 0 && 498 529 <div className={classWithModifiers('comment__replies', { 499 530 hidden: !this.expandReplies, 500 531 indented: this.props.depth < maxDepth, ··· 510 541 label={this.replies.length === 0 ? trans('comments.load_replies') : undefined} 511 542 modifiers={this.props.modifiers} 512 543 parent={this.props.comment} 513 - total={this.props.comment.repliesCount} 544 + total={this.props.comment.visibleReplyCount} 514 545 /> 515 546 </div> 516 547 } ··· 595 626 } 596 627 597 628 private renderRepliesText() { 598 - if (this.props.comment.repliesCount === 0) return; 629 + if (this.props.comment.visibleReplyCount === 0) return; 599 630 600 631 let label: string; 601 632 let callback: () => void; ··· 715 746 716 747 private renderToolbar() { 717 748 if (!this.props.showToolbar) return; 749 + if (!canModerateComments()) return; 718 750 719 751 return ( 720 752 <div className='comment__toolbar'>
+16 -13
resources/js/components/comments-controller.ts
··· 151 151 } else { 152 152 this.state = initialState(); 153 153 const initialBundle = JSON.parse(stateEl.text) as CommentBundleJson; 154 - this.loadBundle(initialBundle, false, true); 154 + this.loadBundle(initialBundle, true, true); 155 155 } 156 156 157 157 makeObservable(this); ··· 305 305 this.state = initialState(); 306 306 this.nextState = {}; 307 307 this.xhr = {}; 308 - this.loadBundle(bundle, false, true); 308 + this.loadBundle(bundle, true, true); 309 309 core.userPreferences.set('comments_sort', this.state.sort); 310 310 })); 311 311 } ··· 414 414 return ret; 415 415 } 416 416 417 + getReplies(comment: Comment) { 418 + const ids = this.state.commentIdsByParentId[comment.id]; 419 + 420 + return this.getComments(ids); 421 + } 422 + 417 423 getUser(id: number | null | undefined) { 418 424 return id == null ? undefined : this.state.users[id]; 419 425 } ··· 460 466 @action 461 467 private loadBundle(bundle: CommentBundleJson, append = true, initial = false) { 462 468 if (initial) { 463 - this.state.commentIdsByParentId[0] = []; 464 - const comments = this.state.commentIdsByParentId[0]; 465 - bundle.comments.forEach((comment) => { 466 - comments.push(comment.id); 467 - this.addComment(comment); 468 - }); 469 + // for initial page of comment index and show 470 + this.state.commentIdsByParentId[-1] = bundle.comments.map((comment) => comment.id); 469 471 this.state.sort = bundle.sort; 470 - } else { 471 - bundle.comments.forEach((comment) => { 472 - this.addCommentId(comment, append); 473 - this.addComment(comment); 474 - }); 472 + append = true; 475 473 } 474 + 475 + bundle.comments.forEach((comment) => { 476 + this.addCommentId(comment, append); 477 + this.addComment(comment); 478 + }); 476 479 477 480 bundle.included_comments.forEach((comment) => { 478 481 this.addCommentId(comment, true);
+5 -4
resources/js/components/comments.tsx
··· 3 3 4 4 import { observer } from 'mobx-react'; 5 5 import CommentModel from 'models/comment'; 6 + import { canModerateComments } from 'models/comment'; 6 7 import core from 'osu-core-singleton'; 7 8 import * as React from 'react'; 8 9 import { classWithModifiers, mergeModifiers, Modifiers } from 'utils/css'; ··· 109 110 } 110 111 111 112 private renderComment(comment: CommentModel, expandReplies?: boolean) { 112 - if (comment.isDeleted && !core.userPreferences.get('comments_show_deleted')) { 113 - return; 114 - } 115 - 116 113 return ( 117 114 <Comment 118 115 key={comment.id} ··· 161 158 } 162 159 163 160 private renderShowDeletedToggle() { 161 + if (!canModerateComments()) { 162 + return null; 163 + } 164 + 164 165 const iconClass = core.userPreferences.get('comments_show_deleted') 165 166 ? 'fas fa-check-square' 166 167 : 'far fa-square';
+5
resources/js/components/deleted-comments-count.tsx
··· 2 2 // See the LICENCE file in the repository root for full licence text. 3 3 4 4 import Comment from 'models/comment'; 5 + import { canModerateComments } from 'models/comment'; 5 6 import * as React from 'react'; 6 7 import { classWithModifiers, Modifiers } from 'utils/css'; 7 8 import { transChoice } from 'utils/lang'; ··· 13 14 14 15 export default class DeletedCommentsCount extends React.Component<Props> { 15 16 render() { 17 + if (!canModerateComments()) { 18 + return null; 19 + } 20 + 16 21 const deletedCount = this.props.comments.filter((c) => c.deletedAt != null).length; 17 22 18 23 if (deletedCount === 0) {
+1 -6
resources/js/components/notification-icon.tsx
··· 12 12 type?: string; 13 13 } 14 14 15 - function format(count: number) { 16 - // combination of latency and delays processing marking as read can cause the display count to go negative. 17 - return formatNumber(count > 0 ? count : 0); 18 - } 19 - 20 15 export default function NotificationIcon(props: Props) { 21 16 const modifiers = { 22 17 glow: props.count > 0, ··· 27 22 <span className={classWithModifiers('notification-icon', modifiers)}> 28 23 <i className={props.iconClassName} /> 29 24 <span className='notification-icon__count'> 30 - {props.ready ? format(props.count) : '...'} 25 + {props.ready ? formatNumber(props.count) : '...'} 31 26 </span> 32 27 </span> 33 28 );
+21 -17
resources/js/contest-voting/entry.coffee
··· 18 18 19 19 return null if @props.hideIfNotVoted && !selected 20 20 21 - if @props.contest.type == 'external' 22 - link_icon = 'fa-external-link-alt' 23 - entry_title = 24 - a 25 - rel: 'nofollow noreferrer' 26 - target: '_blank' 27 - href: @props.entry.preview, 28 - @props.entry.title 29 - else 30 - link_icon = 'fa-download' 31 - entry_title = @props.entry.title 21 + link_icon = if @props.contest.type == 'external' then 'fa-external-link-alt' else 'fa-download' 32 22 33 23 if @props.contest.show_votes 34 24 relativeVotePercentage = _.round((@props.entry.results.votes / @props.winnerVotes)*100, 2) ··· 49 39 if @props.contest.submitted_beatmaps 50 40 a href: route('beatmapsets.show', beatmapset: @props.entry.preview), className: 'contest-voting-list__icon contest-voting-list__icon--submitted-beatmaps', style: { background: "url(https://b.ppy.sh/thumb/#{@props.entry.preview}.jpg)" }, 51 41 span className: 'contest-voting-list__link contest-voting-list__link--shadowed', 52 - i className: "fal fa-fw fa-lg fa-#{@props.contest.link_icon}" 42 + i className: "fas fa-fw fa-lg fa-#{@props.contest.link_icon}" 53 43 else 54 44 div className: 'contest-voting-list__icon contest-voting-list__icon--bg', 55 45 a ··· 61 51 if @props.contest.show_votes 62 52 div className: 'contest-voting-list__title contest-voting-list__title--show-votes', 63 53 div className: 'contest-voting-list__votes-bar', style: { width: "#{relativeVotePercentage}%" } 64 - div className: 'u-relative u-ellipsis-overflow', entry_title 65 - @renderUserLink() 54 + @renderTitle() 66 55 else 67 - div className: 'contest-voting-list__title', 68 - div className: 'u-ellipsis-overflow', entry_title 69 - @renderUserLink() 56 + div className: 'contest-voting-list__title', @renderTitle() 70 57 71 58 if !@props.contest.judged 72 59 div className: "contest__voting-star#{if @props.contest.show_votes then ' contest__voting-star--dark-bg' else ''}", ··· 90 77 target: '_blank' 91 78 i className: 'fas fa-fw fa-lg fa-external-link-alt' 92 79 80 + renderTitle: -> 81 + el React.Fragment, null, 82 + if @props.contest.type == 'external' 83 + a 84 + className: 'contest-voting-list__title-link u-ellipsis-overflow u-relative' 85 + rel: 'nofollow noreferrer' 86 + target: '_blank' 87 + href: @props.entry.preview, 88 + @props.entry.title 89 + else if @props.options.showLink && @props.entry.preview && @props.contest.submitted_beatmaps 90 + a 91 + className: 'contest-voting-list__title-link u-ellipsis-overflow u-relative', 92 + href: route('beatmapsets.show', beatmapset: @props.entry.preview) 93 + @props.entry.title 94 + else 95 + div className: 'u-relative u-ellipsis-overflow', @props.entry.title 96 + @renderUserLink() 93 97 94 98 renderUserLink: -> 95 99 return null unless @props.entry.user?.id?
-84
resources/js/core-legacy/account-edit-avatar.coffee
··· 1 - # Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0. 2 - # See the LICENCE file in the repository root for full licence text. 3 - 4 - import { route } from 'laroute' 5 - import { fileuploadFailCallback } from 'utils/ajax' 6 - 7 - export default class AccountEditAvatar 8 - constructor: -> 9 - $(document).on 'turbolinks:load', @initialize 10 - $(document).on 'turbolinks:before-cache', @rollback 11 - 12 - $.subscribe 'dragenterGlobal', @overlayStart 13 - $.subscribe 'dragendGlobal', @overlayEnd 14 - $(document).on 'dragenter', '.js-account-edit-avatar', @overlayEnter 15 - $(document).on 'dragover', '.js-account-edit-avatar', @overlayHover 16 - 17 - @main = document.getElementsByClassName('js-account-edit-avatar') 18 - 19 - 20 - $button: -> 21 - $('.js-account-edit-avatar__button') 22 - 23 - 24 - initialize: => 25 - return if !@main[0]? 26 - 27 - @isAvailable = true 28 - 29 - @$main = $(@main) 30 - 31 - @$button().fileupload 32 - url: route('account.avatar') 33 - dataType: 'json' 34 - dropZone: @$main 35 - 36 - submit: => 37 - @main[0].classList.add 'js-account-edit-avatar--saving' 38 - $.publish 'dragendGlobal' 39 - 40 - done: (_e, data) => 41 - $.publish 'user:update', data.result 42 - 43 - fail: fileuploadFailCallback 44 - 45 - complete: => 46 - @main[0].classList.remove 'js-account-edit-avatar--saving' 47 - 48 - 49 - overlayEnd: => 50 - return if !@isAvailable 51 - 52 - @main[0].classList.remove 'js-account-edit-avatar--start' 53 - 54 - 55 - overlayEnter: => 56 - @dragging ?= true 57 - 58 - 59 - overlayHover: => 60 - return if !@dragging 61 - 62 - @main[0].classList.add 'js-account-edit-avatar--hover' 63 - 64 - # see GlobalDrag 65 - Timeout.clear @overlayLeaveTimeout 66 - @overlayLeaveTimeout = Timeout.set 100, @overlayLeave 67 - 68 - 69 - overlayLeave: => 70 - @dragging = null 71 - @main[0].classList.remove 'js-account-edit-avatar--hover' 72 - 73 - 74 - overlayStart: => 75 - return if !@isAvailable 76 - 77 - @main[0].classList.add 'js-account-edit-avatar--start' 78 - 79 - 80 - rollback: => 81 - return if !@isAvailable 82 - 83 - @isAvailable = false 84 - @$button().fileupload 'destroy'
-126
resources/js/core-legacy/account-edit.coffee
··· 1 - # Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0. 2 - # See the LICENCE file in the repository root for full licence text. 3 - 4 - import { route } from 'laroute' 5 - 6 - export default class AccountEdit 7 - constructor: -> 8 - $(document).on 'input change', '.js-account-edit', @initializeUpdate 9 - 10 - $(document).on 'ajax:error', '.js-account-edit', @ajaxError 11 - $(document).on 'ajax:send', '.js-account-edit', @ajaxSaving 12 - $(document).on 'ajax:success', '.js-account-edit', @ajaxSaved 13 - $(document).on 'ajax:success', '.js-user-preferences-update', @ajaxUserPreferencesUpdate 14 - 15 - 16 - initializeUpdate: (e) => 17 - form = e.currentTarget 18 - 19 - return if form.dataset.accountEditAutoSubmit != '1' 20 - 21 - @abortUpdate form 22 - @saving form 23 - form.debouncedUpdate ?= _.debounce @update, 1000 24 - form.debouncedUpdate form 25 - 26 - 27 - ajaxError: (e) => 28 - @clearState e.currentTarget 29 - 30 - 31 - ajaxSaving: (e) => 32 - @saving e.currentTarget 33 - 34 - 35 - ajaxSaved: (e) => 36 - @saved e.currentTarget 37 - 38 - 39 - ajaxUserPreferencesUpdate: (_e, user) -> 40 - $.publish 'user:update', user 41 - 42 - 43 - clearState: (el) => 44 - el.dataset.accountEditState = '' 45 - 46 - 47 - getValue: (form) -> 48 - if form.dataset.accountEditType == 'array' 49 - prevValue = null 50 - 51 - value = [''] 52 - for checkbox in form.querySelectorAll('input') 53 - value.push(checkbox.value) if checkbox.checked 54 - else if form.dataset.accountEditType == 'radio' 55 - prevValue = form.dataset.lastValue 56 - 57 - for checkbox in form.querySelectorAll('input[type="radio"]') 58 - if checkbox.checked 59 - value = checkbox.value 60 - break 61 - else 62 - prevValue = form.dataset.lastValue 63 - 64 - input = form.querySelector('.js-account-edit__input') 65 - if input.type == 'checkbox' 66 - value = input.checked 67 - else 68 - value = input.value 69 - 70 - { value, prevValue } 71 - 72 - 73 - getMultiValue: (form) -> 74 - data = {} 75 - 76 - for checkbox in form.querySelectorAll('.js-account-edit__input') 77 - data[checkbox.name] = checkbox.checked 78 - 79 - data 80 - 81 - 82 - saved: (el) => 83 - el.dataset.accountEditState = 'saved' 84 - 85 - el.savedTimeout = Timeout.set 3000, => 86 - @clearState el 87 - 88 - 89 - saving: (el) => 90 - el.dataset.accountEditState = 'saving' 91 - 92 - 93 - abortUpdate: (form) => 94 - Timeout.clear form.savedTimeout 95 - form.updating?.abort() 96 - @clearState form 97 - 98 - 99 - update: (form) => 100 - if form.dataset.accountEditType == 'multi' 101 - data = @getMultiValue(form) 102 - else 103 - { value, prevValue } = @getValue(form) 104 - 105 - return @clearState(form) if value == prevValue 106 - input = form.querySelector('.js-account-edit__input') 107 - field = form.dataset.field ? input.name 108 - form.dataset.lastValue = value 109 - data = "#{field}": value 110 - 111 - url = form.dataset.url ? route('account.update') 112 - 113 - form.updating = $.ajax url, 114 - method: 'PUT' 115 - data: data 116 - 117 - .done (data) => 118 - @saved form 119 - $(form).trigger 'ajax:success', data 120 - 121 - .fail (xhr, status) => 122 - return if status == 'abort' 123 - 124 - form.lastValue = prevValue 125 - @clearState form 126 - $(form).trigger 'ajax:error', [xhr, status]
+2 -1
resources/js/core-legacy/nav2.coffee
··· 5 5 import { fadeToggle } from 'utils/fade' 6 6 7 7 export default class Nav2 8 - constructor: (@clickMenu) -> 8 + constructor: (@clickMenu, @captcha) -> 9 9 @menuBg = document.getElementsByClassName('js-nav2--menu-bg') 10 10 11 11 $.subscribe 'click-menu:current', @autoCenterPopup ··· 30 30 popup.classList.remove 'hidden' 31 31 currentPopup = popup 32 32 link = document.querySelector(".js-click-menu[data-click-menu-target='#{@currentMenu}']") 33 + @captcha.renderAll() 33 34 34 35 return if !currentPopup? 35 36
+83
resources/js/core/account-edit-avatar.ts
··· 1 + // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0. 2 + // See the LICENCE file in the repository root for full licence text. 3 + 4 + import CurrentUserJson from 'interfaces/current-user-json'; 5 + import { route } from 'laroute'; 6 + import OsuCore from 'osu-core'; 7 + import { fileuploadFailCallback } from 'utils/ajax'; 8 + 9 + const hoverClass = 'js-account-edit-avatar--hover'; 10 + const savingClass = 'js-account-edit-avatar--saving'; 11 + const startClass = 'js-account-edit-avatar--start'; 12 + 13 + export default class AccountEditAvatar { 14 + private dragging = false; 15 + private element: HTMLElement | null = null; 16 + private overlayLeaveTimeout?: number; 17 + 18 + constructor(private readonly core: OsuCore) { 19 + $(document).on('turbolinks:load', this.initialize); 20 + $(document).on('turbolinks:before-cache', this.rollback); 21 + 22 + $.subscribe('dragenterGlobal', this.overlayStart); 23 + $.subscribe('dragendGlobal', this.overlayEnd); 24 + $(document).on('dragenter', '.js-account-edit-avatar', this.overlayEnter); 25 + $(document).on('dragover', '.js-account-edit-avatar', this.overlayHover); 26 + } 27 + 28 + private readonly initialize = () => { 29 + this.element = document.querySelector('.js-account-edit-avatar'); 30 + const element = this.element; 31 + if (element == null) return; 32 + 33 + $('.js-account-edit-avatar__button').fileupload({ 34 + always: () => { 35 + element.classList.remove(savingClass); 36 + }, 37 + dataType: 'json', 38 + done: (_e, data) => { 39 + const json = data.result as CurrentUserJson; 40 + this.core.setCurrentUser(json); 41 + }, 42 + dropZone: $(element), 43 + fail: fileuploadFailCallback, 44 + submit: () => { 45 + element.classList.add(savingClass); 46 + $.publish('dragendGlobal'); 47 + }, 48 + url: route('account.avatar'), 49 + }); 50 + }; 51 + 52 + private readonly overlayEnd = () => { 53 + this.element?.classList.remove(startClass); 54 + }; 55 + 56 + private readonly overlayEnter = () => { 57 + this.dragging = true; 58 + }; 59 + 60 + private readonly overlayHover = () => { 61 + if (!this.dragging) return; 62 + 63 + this.element?.classList.add(hoverClass); 64 + 65 + // see GlobalDrag 66 + window.clearTimeout(this.overlayLeaveTimeout); 67 + this.overlayLeaveTimeout = window.setTimeout(this.overlayLeave, 100); 68 + }; 69 + 70 + private readonly overlayLeave = () => { 71 + this.dragging = false; 72 + this.element?.classList.remove(hoverClass); 73 + }; 74 + 75 + private readonly overlayStart = () => { 76 + this.element?.classList.add(startClass); 77 + }; 78 + 79 + private readonly rollback = () => { 80 + if (this.element == null) return; 81 + $('.js-account-edit-avatar__button').fileupload('destroy'); 82 + }; 83 + }
+133
resources/js/core/account-edit-state.ts
··· 1 + // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0. 2 + // See the LICENCE file in the repository root for full licence text. 3 + 4 + import CurrentUserJson from 'interfaces/current-user-json'; 5 + import { route } from 'laroute'; 6 + import { debounce } from 'lodash'; 7 + import OsuCore from 'osu-core'; 8 + import { onError } from 'utils/ajax'; 9 + 10 + const inputSelector = '.js-account-edit__input'; 11 + const requiresName = new Set(['array', 'radio']) as Set<unknown>; 12 + 13 + export default class AccountEditState { 14 + readonly debouncedUpdate; 15 + 16 + private timeout?: number; 17 + private xhr?: JQuery.jqXHR<CurrentUserJson | null>; 18 + 19 + constructor(private readonly container: HTMLElement, private readonly core: OsuCore) { 20 + this.debouncedUpdate = debounce(this.update, 1000); 21 + if (requiresName.has(this.dataset.accountEditType) && this.dataset.field == null) { 22 + throw new Error('data-field required'); 23 + } 24 + } 25 + 26 + private get dataset() { 27 + return this.container.dataset; 28 + } 29 + 30 + private get fieldName() { 31 + if (this.dataset.field != null) { 32 + return this.dataset.field; 33 + } 34 + 35 + const input = this.container.querySelector<HTMLInputElement>(inputSelector); 36 + if (input == null) { 37 + throw new Error('missing input name'); 38 + } 39 + 40 + return input.name; 41 + } 42 + 43 + clear() { 44 + window.clearTimeout(this.timeout); 45 + this.dataset.accountEditState = ''; 46 + } 47 + 48 + onInput() { 49 + this.xhr?.abort(); 50 + 51 + this.saving(); 52 + this.debouncedUpdate(); 53 + } 54 + 55 + saved() { 56 + window.clearTimeout(this.timeout); 57 + this.dataset.accountEditState = 'saved'; 58 + this.timeout = window.setTimeout(() => this.clear(), 3000); 59 + } 60 + 61 + saving() { 62 + window.clearTimeout(this.timeout); 63 + this.dataset.accountEditState = 'saving'; 64 + } 65 + 66 + private getData() { 67 + let value: string | string[] | undefined; 68 + 69 + switch (this.dataset.accountEditType) { 70 + case 'multi': { 71 + const data: Partial<Record<string, boolean>> = {}; 72 + 73 + for (const checkbox of this.container.querySelectorAll<HTMLInputElement>(inputSelector)) { 74 + data[checkbox.name] = checkbox.checked; 75 + } 76 + 77 + return data; 78 + } 79 + case 'array': 80 + value = ['']; 81 + 82 + for (const checkbox of this.container.querySelectorAll('input')) { 83 + if (checkbox.checked) { 84 + value.push(checkbox.value); 85 + } 86 + } 87 + break; 88 + 89 + case 'radio': 90 + for (const checkbox of this.container.querySelectorAll<HTMLInputElement>('input[type="radio"]')) { 91 + if (checkbox.checked) { 92 + value = checkbox.value; 93 + break; 94 + } 95 + } 96 + 97 + if (value == null) { 98 + throw new Error('missing radio value'); 99 + } 100 + 101 + break; 102 + 103 + default: { 104 + const input = this.container.querySelector<HTMLInputElement>(inputSelector); 105 + if (input == null) { 106 + throw new Error('missing input'); 107 + } 108 + 109 + value = input.type === 'checkbox' ? String(input.checked) : input.value; 110 + } 111 + } 112 + 113 + return { [this.fieldName]: value }; 114 + } 115 + 116 + private readonly update = () => { 117 + this.xhr = $.ajax(this.dataset.url ?? route('account.update'), { 118 + data: this.getData(), 119 + method: 'PUT', 120 + }); 121 + 122 + this.xhr.done((response) => { 123 + if (this.dataset.userPreferencesUpdate === '1' && response != null) { 124 + this.core.setCurrentUser(response); 125 + } 126 + 127 + this.saved(); 128 + }).fail((xhr) => { 129 + this.clear(); 130 + onError(xhr); 131 + }); 132 + }; 133 + }
+43
resources/js/core/account-edit.ts
··· 1 + // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0. 2 + // See the LICENCE file in the repository root for full licence text. 3 + 4 + import OsuCore from 'osu-core'; 5 + import AccountEditState from './account-edit-state'; 6 + 7 + type ContainerEvent = JQuery.TriggeredEvent<unknown, unknown, AccountEditHTMLElement, unknown>; 8 + 9 + const autoSubmitClassSelector = '.js-account-edit-auto-submit'; 10 + const classSelector = '.js-account-edit'; 11 + 12 + interface AccountEditHTMLElement extends HTMLElement { 13 + state?: AccountEditState; 14 + } 15 + 16 + export default class AccountEdit { 17 + constructor(private readonly core: OsuCore) { 18 + $(document).on('input change', autoSubmitClassSelector, this.handleInputChange); 19 + $(document).on('ajax:error', classSelector, this.handleAjaxError); 20 + $(document).on('ajax:send', classSelector, this.handleAjaxSend); 21 + $(document).on('ajax:success', classSelector, this.handleAjaxSuccess); 22 + } 23 + 24 + private getState(e: ContainerEvent) { 25 + return e.currentTarget.state ??= new AccountEditState(e.currentTarget, this.core); 26 + } 27 + 28 + private readonly handleAjaxError = (e: ContainerEvent) => { 29 + this.getState(e).clear(); 30 + }; 31 + 32 + private readonly handleAjaxSend = (e: ContainerEvent) => { 33 + this.getState(e).saving(); 34 + }; 35 + 36 + private readonly handleAjaxSuccess = (e: ContainerEvent) => { 37 + this.getState(e).saved(); 38 + }; 39 + 40 + private readonly handleInputChange = (e: ContainerEvent) => { 41 + this.getState(e).onInput(); 42 + }; 43 + }
+30 -5
resources/js/core/captcha.ts
··· 3 3 4 4 import { htmlElementOrNull } from 'utils/html'; 5 5 6 + function isVisible(el: HTMLElement) { 7 + const rect = el.getBoundingClientRect(); 8 + if (rect.x === 0 && rect.y === 0 && rect.width === 0 && rect.height === 0) { 9 + return false; 10 + } 11 + 12 + const style = window.getComputedStyle(el); 13 + 14 + return style.pointerEvents !== 'none'; 15 + } 16 + 6 17 export default class Captcha { 7 18 private sitekey = ''; 8 19 ··· 37 48 }; 38 49 39 50 isEnabled = (container: HTMLDivElement) => this.isTriggered(container) && 40 - typeof(grecaptcha) === 'object' && 41 - typeof(grecaptcha.render) === 'function' && 51 + typeof(turnstile) === 'object' && 52 + typeof(turnstile.render) === 'function' && 42 53 this.sitekey !== ''; 43 54 44 55 isLoaded = (container: HTMLDivElement) => container.innerHTML !== ''; ··· 46 57 isTriggered = (container: HTMLDivElement) => container.dataset.captchaTriggered === '1'; 47 58 48 59 render = (container: HTMLDivElement) => { 60 + if (!isVisible(container)) { 61 + return; 62 + } 49 63 if (this.isEnabled(container) && !this.isLoaded(container)) { 50 64 const disableSubmit = () => this.disableSubmit(container); 51 - const id = grecaptcha.render(container, { 65 + const id = turnstile.render(container, { 52 66 callback: () => this.enableSubmit(container), 53 67 'error-callback': disableSubmit, 54 68 'expired-callback': disableSubmit, 69 + language: window.currentLocale, 55 70 sitekey: this.sitekey, 56 71 theme: 'dark', 57 72 }); 58 - container.dataset.captchaId = id.toString(); 73 + if (id == null) { 74 + throw new Error('failed setting up turnstile widget'); 75 + } 76 + container.dataset.captchaId = id; 77 + $(document).one('turbolinks:before-cache', () => this.remove(container)); 59 78 60 79 disableSubmit(); 61 80 } ··· 63 82 64 83 reset = (container: HTMLDivElement) => { 65 84 if (this.isEnabled(container)) { 66 - grecaptcha.reset(+(container.dataset.captchaId ?? '')); 85 + turnstile.reset(container.dataset.captchaId ?? ''); 67 86 this.disableSubmit(container); 68 87 } 69 88 }; ··· 77 96 78 97 container.dataset.captchaTriggered = '1'; 79 98 this.render(container); 99 + }; 100 + 101 + private readonly remove = (container: HTMLDivElement) => { 102 + const id = container.dataset.captchaId; 103 + delete(container.dataset.captchaId); 104 + turnstile.remove(id ?? ''); 80 105 }; 81 106 82 107 private readonly renderAll = () => {
+1 -5
resources/js/main.coffee
··· 1 1 # Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0. 2 2 # See the LICENCE file in the repository root for full licence text. 3 3 4 - import AccountEditAvatar from 'core-legacy/account-edit-avatar' 5 - import AccountEdit from 'core-legacy/account-edit' 6 4 import BbcodePreview from 'core-legacy/bbcode-preview' 7 5 import BeatmapPack from 'core-legacy/beatmap-pack' 8 6 import ChangelogChartLoader from 'core-legacy/changelog-chart-loader' ··· 57 55 BeatmapPack.initialize() 58 56 StoreCheckout.initialize() 59 57 60 - window.accountEdit ?= new AccountEdit 61 - window.accountEditAvatar ?= new AccountEditAvatar 62 58 window.bbcodePreview ?= new BbcodePreview 63 59 window.changelogChartLoader ?= new ChangelogChartLoader 64 60 window.checkboxValidation ?= new CheckboxValidation ··· 84 80 window.forumPostsSeek ?= new ForumPostsSeek(window.forum) 85 81 window.forumTopicPostJump ?= new ForumTopicPostJump(window.forum) 86 82 window.forumTopicReply ?= new ForumTopicReply(bbcodePreview: window.bbcodePreview, forum: window.forum, stickyFooter: osuCore.stickyFooter) 87 - window.nav2 ?= new Nav2(osuCore.clickMenu) 83 + window.nav2 ?= new Nav2(osuCore.clickMenu, osuCore.captcha) 88 84 89 85 90 86 $(document).on 'change', '.js-url-selector', (e) ->
+22 -1
resources/js/models/comment.ts
··· 6 6 import { computed, makeObservable } from 'mobx'; 7 7 import core from 'osu-core-singleton'; 8 8 9 + export function canModerateComments(): boolean { 10 + return core.currentUser != null && (core.currentUser.is_admin || core.currentUser.is_moderator); 11 + } 12 + 9 13 export type CommentSort = 'new' | 'old' | 'top'; 10 14 11 15 export default class Comment { ··· 66 70 67 71 @computed 68 72 get canModerate() { 69 - return core.currentUser != null && (core.currentUser.is_admin || core.currentUser.is_moderator); 73 + return canModerateComments(); 70 74 } 71 75 72 76 @computed ··· 125 129 } 126 130 127 131 @computed 132 + get isVisible(): boolean { 133 + return !this.isDeleted || this.visibleReplyCount > 0; 134 + } 135 + 136 + @computed 128 137 get isOwner() { 129 138 return core.currentUser != null && this.userId === core.currentUser.id; 139 + } 140 + 141 + @computed 142 + get visibleReplyCount() { 143 + const baseCount = this.repliesCount; 144 + if (canModerateComments()) { 145 + return baseCount; 146 + } 147 + 148 + const deletedCount = this.controller.getReplies(this).filter((reply) => !reply.isVisible).length; 149 + 150 + return Math.max(0, baseCount - deletedCount); 130 151 } 131 152 132 153 toJson(): CommentJson {
+11 -1
resources/js/models/notification-type.ts
··· 30 30 @observable isLoading = false; 31 31 @observable isMarkingAsRead = false; 32 32 @observable stacks = new Map<string, NotificationStack>(); 33 - @observable total = 0; 33 + @observable private _total = 0; 34 34 35 35 @computed get hasMore() { 36 36 // undefined means not loaded yet. ··· 49 49 50 50 @computed get isEmpty() { 51 51 return this.total <= 0; 52 + } 53 + 54 + @computed get total() { 55 + // combination of latency and delays processing marking as read can cause the display count to go negative. 56 + return this._total > 0 ? this._total : 0; 57 + } 58 + 59 + set total(val: number) { 60 + this._total = val; 52 61 } 53 62 54 63 @computed get stackNotificationCount() { ··· 91 100 removeStack(stack: NotificationStack) { 92 101 const exists = this.stacks.delete(stack.id); 93 102 if (exists) this.total -= stack.total; 103 + 94 104 return exists; 95 105 } 96 106
+19 -13
resources/js/osu-core.ts
··· 3 3 4 4 import { BeatmapsetSearchController } from 'beatmaps/beatmapset-search-controller'; 5 5 import ChatWorker from 'chat/chat-worker'; 6 + import AccountEdit from 'core/account-edit'; 7 + import AccountEditAvatar from 'core/account-edit-avatar'; 6 8 import AccountEditBlocklist from 'core/account-edit-blocklist'; 7 9 import BrowserTitleWithNotificationCount from 'core/browser-title-with-notification-count'; 8 10 import Captcha from 'core/captcha'; ··· 42 44 43 45 // will this replace main.coffee eventually? 44 46 export default class OsuCore { 47 + readonly accountEdit; 48 + readonly accountEditAvatar; 45 49 readonly accountEditBlocklist; 46 50 readonly beatmapsetSearchController; 47 51 readonly browserTitleWithNotificationCount; ··· 132 136 // TODO: requires dynamic imports to lazy load modules. 133 137 this.dataStore = new RootDataStore(); 134 138 this.accountEditBlocklist = new AccountEditBlocklist(this); 139 + this.accountEdit = new AccountEdit(this); 140 + this.accountEditAvatar = new AccountEditAvatar(this); 135 141 this.userLoginObserver = new UserLoginObserver(); 136 142 this.windowFocusObserver = new WindowFocusObserver(); 137 143 ··· 147 153 } 148 154 } 149 155 156 + @action 157 + readonly setCurrentUser = (userOrEmpty: typeof window.currentUser) => { 158 + const user = userOrEmpty.id == null ? undefined : userOrEmpty; 159 + 160 + if (user != null) { 161 + this.dataStore.userStore.update(user); 162 + } 163 + this.socketWorker.setUserId(user?.id ?? null); 164 + this.currentUser = user; 165 + window.currentUser = userOrEmpty; 166 + this.userPreferences.setUser(this.currentUser); 167 + }; 168 + 150 169 readonly updateCurrentUser = () => { 151 170 // Remove from DOM so only new data is parsed on navigation. 152 171 const currentUser = parseJsonNullable<typeof window.currentUser>('json-current-user', true); ··· 158 177 159 178 private readonly onCurrentUserUpdate = (event: unknown, user: CurrentUserJson) => { 160 179 this.setCurrentUser(user); 161 - }; 162 - 163 - @action 164 - private readonly setCurrentUser = (userOrEmpty: typeof window.currentUser) => { 165 - const user = userOrEmpty.id == null ? undefined : userOrEmpty; 166 - 167 - if (user != null) { 168 - this.dataStore.userStore.update(user); 169 - } 170 - this.socketWorker.setUserId(user?.id ?? null); 171 - this.currentUser = user; 172 - window.currentUser = userOrEmpty; 173 - this.userPreferences.setUser(this.currentUser); 174 180 }; 175 181 }
+2 -2
resources/lang/cs/accounts.php
··· 10 10 11 11 'avatar' => [ 12 12 'title' => 'Avatar', 13 - 'rules' => 'Prosím ujistěte se, že Váš avatar dodržuje :link.<br/>To znamená, že musí být <strong>vhodný pro všechny věkové kategorie</strong>. Tj. žádný nahota či nenávistný obsah.', 14 - 'rules_link' => 'pravidla komunity', 13 + 'rules' => 'Ujisti se prosím, že tvůj avatar dodržuje :link.<br/>To znamená, že musí být <strong>vhodný pro všechny věkové kategorie</strong>. Tj. žádná nahota, žádný urážlivý či sugestivní obsah.', 14 + 'rules_link' => 'kritéria vizuálního obsahu', 15 15 ], 16 16 17 17 'email' => [
+3 -3
resources/lang/cs/models.php
··· 4 4 // See the LICENCE file in the repository root for full licence text. 5 5 6 6 return [ 7 - 'not_found' => "", 7 + 'not_found' => "Danou :model se nepodařilo najít.", 8 8 9 9 'name' => [ 10 - 'App\Models\Beatmap' => '', 11 - 'App\Models\Beatmapset' => '', 10 + 'App\Models\Beatmap' => 'obtížnost beatmapy', 11 + 'App\Models\Beatmapset' => 'beatmapu', 12 12 ], 13 13 ];
+1 -1
resources/lang/de/home.php
··· 13 13 'see_more_news' => 'mehr Neuigkeiten anzeigen', 14 14 15 15 'slogan' => [ 16 - 'main' => 'das beste free-to-play Rhythmusspiel', 16 + 'main' => 'das besteste free-to-play rhythmusspiel', 17 17 'sub' => 'Rhythmus ist nur ein Klick entfernt', 18 18 ], 19 19 ],
+3 -3
resources/lang/de/models.php
··· 4 4 // See the LICENCE file in the repository root for full licence text. 5 5 6 6 return [ 7 - 'not_found' => "", 7 + 'not_found' => "Angegebenes Item (:model) konnte nicht gefunden werden.", 8 8 9 9 'name' => [ 10 - 'App\Models\Beatmap' => '', 11 - 'App\Models\Beatmapset' => '', 10 + 'App\Models\Beatmap' => 'Schwierigkeitsgrad', 11 + 'App\Models\Beatmapset' => 'Beatmap', 12 12 ], 13 13 ];
+2 -2
resources/lang/es-419/accounts.php
··· 10 10 11 11 'avatar' => [ 12 12 'title' => 'Avatar', 13 - 'rules' => 'Por favor, asegúrate de que tu avatar se adhiera a :link.<br/>Esto significa que debe ser <strong>adecuado para todas las edades</strong>. Es decir, sin desnudez, profanidad o contenido sugestivo.', 14 - 'rules_link' => 'las reglas de la comunidad', 13 + 'rules' => 'Por favor, asegúrate de que tu avatar se adhiera a :link.<br/>Esto significa que debe ser <strong>adecuado para todas las edades</strong>. Es decir, sin desnudos, contenido ofensivo o sugerente.', 14 + 'rules_link' => 'las consideraciones de contenido visual', 15 15 ], 16 16 17 17 'email' => [
+103 -103
resources/lang/es-419/common.php
··· 4 4 // See the LICENCE file in the repository root for full licence text. 5 5 6 6 return [ 7 - 'confirmation' => '', 8 - 'confirmation_unsaved' => '', 9 - 'saved' => '', 7 + 'confirmation' => '¿Estás seguro?', 8 + 'confirmation_unsaved' => 'Se perderán todos los cambios sin guardar. ¿Estás seguro?', 9 + 'saved' => 'Guardado', 10 10 11 11 'array_and' => [ 12 - 'words_connector' => '', 13 - 'two_words_connector' => '', 14 - 'last_word_connector' => '', 12 + 'words_connector' => ', ', 13 + 'two_words_connector' => ' y ', 14 + 'last_word_connector' => ', y ', 15 15 ], 16 16 17 17 'badges' => [ 18 - 'new' => '', 18 + 'new' => 'NUEVO', 19 19 ], 20 20 21 21 'buttons' => [ 22 - 'admin' => '', 23 - 'authorise' => '', 24 - 'authorising' => '', 25 - 'back_to_previous' => '', 26 - 'back_to_top' => '', 27 - 'cancel' => '', 28 - 'change' => '', 29 - 'clear' => '', 30 - 'click_to_copy' => '', 31 - 'click_to_copy_copied' => '', 32 - 'close' => '', 33 - 'collapse' => '', 34 - 'delete' => '', 35 - 'edit' => '', 36 - 'expand' => '', 37 - 'hide' => '', 38 - 'permalink' => '', 39 - 'pin' => '', 40 - 'post' => '', 41 - 'read_more' => '', 42 - 'refresh' => '', 43 - 'reply' => '', 44 - 'reply_reopen' => '', 45 - 'reply_resolve' => '', 46 - 'reset' => '', 47 - 'restore' => '', 48 - 'retry' => '', 49 - 'save' => '', 50 - 'saving' => '', 51 - 'search' => '', 52 - 'see_more' => '', 53 - 'show' => '', 54 - 'show_deleted' => '', 55 - 'show_less' => '', 56 - 'show_more' => '', 57 - 'show_more_options' => '', 58 - 'submit' => '', 59 - 'unpin' => '', 60 - 'update' => '', 61 - 'upload_image' => '', 22 + 'admin' => 'Administrador', 23 + 'authorise' => 'Autorizar', 24 + 'authorising' => 'Autorizando...', 25 + 'back_to_previous' => 'Volver a la posición anterior', 26 + 'back_to_top' => 'Volver al principio', 27 + 'cancel' => 'Cancelar', 28 + 'change' => 'cambiar', 29 + 'clear' => 'Borrar', 30 + 'click_to_copy' => 'haz clic para copiarlo al portapapeles', 31 + 'click_to_copy_copied' => '¡copiado al portapapeles!', 32 + 'close' => 'Cerrar', 33 + 'collapse' => 'contraer', 34 + 'delete' => 'Eliminar', 35 + 'edit' => 'Editar', 36 + 'expand' => 'expandir', 37 + 'hide' => 'ocultar', 38 + 'permalink' => 'enlace permanente', 39 + 'pin' => 'fijar', 40 + 'post' => 'Publicar', 41 + 'read_more' => 'leer más', 42 + 'refresh' => 'Actualizar', 43 + 'reply' => 'Responder', 44 + 'reply_reopen' => 'Responder y reabrir', 45 + 'reply_resolve' => 'Responder y resolver', 46 + 'reset' => 'Reiniciar', 47 + 'restore' => 'Restaurar', 48 + 'retry' => 'Volver a intentar', 49 + 'save' => 'Guardar', 50 + 'saving' => 'Guardando...', 51 + 'search' => 'Buscar', 52 + 'see_more' => 'ver más', 53 + 'show' => 'mostrar', 54 + 'show_deleted' => 'Mostrar eliminados', 55 + 'show_less' => 'mostrar menos', 56 + 'show_more' => 'mostrar más', 57 + 'show_more_options' => 'mostrar más opciones', 58 + 'submit' => 'Enviar', 59 + 'unpin' => 'desfijar', 60 + 'update' => 'Actualizar', 61 + 'upload_image' => 'subir imagen', 62 62 63 63 'watch' => [ 64 - 'to_0' => '', 65 - 'to_1' => '', 64 + 'to_0' => 'Dejar de ver', 65 + 'to_1' => 'Ver', 66 66 ], 67 67 ], 68 68 69 69 'count' => [ 70 - 'badges' => '', 71 - 'days' => '', 72 - 'hour_short_unit' => '', 73 - 'hours' => '', 74 - 'item' => '', 75 - 'minute_short_unit' => '', 76 - 'minutes' => '', 77 - 'months' => '', 78 - 'notifications' => '', 79 - 'plus_others' => '', 80 - 'post' => '', 81 - 'second_short_unit' => '', 82 - 'star_priority' => '', 83 - 'update' => '', 84 - 'view' => '', 85 - 'years' => '', 70 + 'badges' => ':count_delimited insignia|:count_delimited insignias', 71 + 'days' => ':count_delimited día|:count_delimited días', 72 + 'hour_short_unit' => 'hr|hrs', 73 + 'hours' => ':count_delimited hora|:count_delimited horas', 74 + 'item' => ':count_delimited unidad|::count_delimited unidades', 75 + 'minute_short_unit' => 'min|mins', 76 + 'minutes' => ':count_delimited minuto|:count_delimited minutos', 77 + 'months' => ':count_delimited mes|:count_delimited meses', 78 + 'notifications' => ':count_delimited notificación|:count_delimited notificaciones', 79 + 'plus_others' => '¡+ :count_delimited!|¡+ otros :count_delimited favoritos!', 80 + 'post' => ':count_delimited publicación|:count_delimited publicaciones', 81 + 'second_short_unit' => 'seg|segs', 82 + 'star_priority' => 'prioridad de :count_delimited estrella|prioridad de :count_delimited estrellas', 83 + 'update' => ':count_delimited actualización|:count_delimited actualizaciones', 84 + 'view' => ':count_delimited visita|:count_delimited visitas', 85 + 'years' => ':count_delimited año|:count_delimited años', 86 86 ], 87 87 88 88 'countdown' => [ 89 - 'days' => '', 90 - 'hours' => '', 91 - 'minutes' => '', 92 - 'seconds' => '', 89 + 'days' => 'días', 90 + 'hours' => 'horas', 91 + 'minutes' => 'minutos', 92 + 'seconds' => 'segundos', 93 93 ], 94 94 95 95 'datetime' => [ 96 96 'year_month' => [ 97 - 'moment' => '', 98 - 'php' => '', 97 + 'moment' => 'MMMM [de] YYYY', 98 + 'php' => 'MMMM y', 99 99 ], 100 100 'year_month_short' => [ 101 - 'moment' => '', 101 + 'moment' => 'MMM YYYY', 102 102 ], 103 103 ], 104 104 105 105 'device' => [ 106 - 'keyboard' => '', 107 - 'mouse' => '', 108 - 'tablet' => '', 109 - 'touch' => '', 106 + 'keyboard' => 'teclado', 107 + 'mouse' => 'ratón', 108 + 'tablet' => 'tableta', 109 + 'touch' => 'pantalla táctil', 110 110 ], 111 111 112 112 'dropzone' => [ 113 - 'target' => '', 113 + 'target' => 'suelta aquí para subirlo', 114 114 ], 115 115 116 116 'input' => [ 117 - 'search' => '', 117 + 'search' => 'buscar...', 118 118 ], 119 119 120 120 'pagination' => [ 121 - 'previous' => '', 122 - 'next' => '', 121 + 'previous' => 'anterior', 122 + 'next' => 'siguiente', 123 123 ], 124 124 125 125 'score_count' => [ 126 - 'count_100' => '', 127 - 'count_300' => '', 128 - 'count_50' => '', 126 + 'count_100' => '100', 127 + 'count_300' => '300', 128 + 'count_50' => '50', 129 129 'count_geki' => '', 130 - 'count_katu' => '', 131 - 'count_miss' => '', 130 + 'count_katu' => '200', 131 + 'count_miss' => 'Fallo', 132 132 ], 133 133 134 134 'scoreboard_time' => [ 135 - 'd' => '', 136 - 'dd' => '', 137 - 'h' => '', 138 - 'hh' => '', 139 - 'm' => '', 140 - 'mm' => '', 141 - 'month' => '', 142 - 'months' => '', 135 + 'd' => '%d d', 136 + 'dd' => '%d d', 137 + 'h' => '%d h', 138 + 'hh' => '%d h', 139 + 'm' => 'ahora', 140 + 'mm' => 'ahora', 141 + 'month' => '%d m', 142 + 'months' => '%d m', 143 143 'past' => '', 144 - 's' => '', 145 - 'y' => '', 146 - 'yy' => '', 144 + 's' => 'ahora', 145 + 'y' => '%dy', 146 + 'yy' => '%dy', 147 147 ], 148 148 149 149 'time' => [ 150 - 'days_ago' => '', 151 - 'hours_ago' => '', 152 - 'now' => '', 153 - 'remaining' => '', 150 + 'days_ago' => 'hace :count_delimited día|hace :count_delimited días', 151 + 'hours_ago' => 'hace :count_delimited hora|hace :count_delimited horas', 152 + 'now' => 'ahora', 153 + 'remaining' => 'Tiempo restante', 154 154 ], 155 155 156 156 'title' => [ 157 - 'notice' => '', 157 + 'notice' => 'Aviso', 158 158 ], 159 159 160 160 'wrong_user' => [ 161 - '_' => '', 162 - 'logout_link' => '', 161 + '_' => 'Has iniciado sesión como :user. :logout_link.', 162 + 'logout_link' => 'Haz clic aquí para iniciar sesión como otro usuario', 163 163 ], 164 164 ];
+64 -64
resources/lang/es-419/community.php
··· 6 6 return [ 7 7 'support' => [ 8 8 'convinced' => [ 9 - 'title' => '', 10 - 'support' => '', 11 - 'gift' => '', 12 - 'instructions' => '', 9 + 'title' => '¡Me has convencido! :D', 10 + 'support' => 'apoya a osu!', 11 + 'gift' => 'o regálale un soporte a otro jugador', 12 + 'instructions' => 'haz clic en el botón del corazón para ir a la osu!store', 13 13 ], 14 14 'why-support' => [ 15 - 'title' => '', 15 + 'title' => '¿Por qué debería apoyar a osu!? ¿A dónde va el dinero?', 16 16 17 17 'team' => [ 18 - 'title' => '', 19 - 'description' => '', 18 + 'title' => 'Apoyar al equipo', 19 + 'description' => 'Un pequeño equipo desarrolla y mantiene osu! Tu apoyo les ayuda a, ya sabes... vivir.', 20 20 ], 21 21 'infra' => [ 22 - 'title' => '', 23 - 'description' => '', 22 + 'title' => 'Infraestructura del servidor', 23 + 'description' => 'Las contribuciones se destinan a los servidores para el funcionamiento del sitio web, los servicios multijugador, las tablas de clasificación en línea, etc.', 24 24 ], 25 25 'featured-artists' => [ 26 - 'title' => '', 27 - 'description' => '', 28 - 'link_text' => '', 26 + 'title' => 'Artistas destacados', 27 + 'description' => 'Con tu apoyo, podremos acercarnos a más artistas increíbles y obtener más licencias de música para osu!', 28 + 'link_text' => 'Ver la lista actual &raquo;', 29 29 ], 30 30 'ads' => [ 31 - 'title' => '', 32 - 'description' => '', 31 + 'title' => 'Mantener osu! autosuficiente', 32 + 'description' => 'Tus contribuciones ayudan a mantener el juego independiente y completamente libre de anuncios y patrocinadores externos.', 33 33 ], 34 34 'tournaments' => [ 35 - 'title' => '', 36 - 'description' => '', 37 - 'link_text' => '', 35 + 'title' => 'Torneos oficiales', 36 + 'description' => 'Ayuda a financiar el funcionamiento (y los premios) de las osu! World Cups oficiales.', 37 + 'link_text' => 'Explorar torneos &raquo;', 38 38 ], 39 39 'bounty-program' => [ 40 - 'title' => '', 41 - 'description' => '', 42 - 'link_text' => '', 40 + 'title' => 'Programa de recompensas de colaboración abierta', 41 + 'description' => 'Apoya a los contribuidores de la comunidad que han dado su tiempo y esfuerzo para ayudar a hacer que osu! sea mejor.', 42 + 'link_text' => 'Descubre más &raquo;', 43 43 ], 44 44 ], 45 45 'perks' => [ 46 - 'title' => '', 46 + 'title' => '¡Genial! ¿Qué beneficios obtengo?', 47 47 'osu_direct' => [ 48 - 'title' => '', 49 - 'description' => '', 48 + 'title' => 'osu!direct', 49 + 'description' => 'Obtén acceso rápido y sencillo para buscar y descargar mapas sin tener que salir del juego.', 50 50 ], 51 51 52 52 'friend_ranking' => [ 53 - 'title' => '', 54 - 'description' => "", 53 + 'title' => 'Clasificación entre amigos', 54 + 'description' => "Compara tus resultados con los de tus amigos en la tabla de clasificación de un mapa, tanto en el juego como en el sitio web.", 55 55 ], 56 56 57 57 'country_ranking' => [ 58 - 'title' => '', 59 - 'description' => '', 58 + 'title' => 'Clasificación nacional', 59 + 'description' => 'Conquista tu país antes de conquistar el mundo.', 60 60 ], 61 61 62 62 'mod_filtering' => [ 63 - 'title' => '', 64 - 'description' => '', 63 + 'title' => 'Filtrado por mods', 64 + 'description' => '¿Asociarte solo con personas que juegan con HDHR? ¡No hay problema!', 65 65 ], 66 66 67 67 'auto_downloads' => [ 68 - 'title' => '', 69 - 'description' => '', 68 + 'title' => 'Descargas automáticas', 69 + 'description' => '¡Los mapas se descargarán automáticamente en las partidas multijugador, cuando estés viendo a otros jugadores, o cuando hagas clic en los enlaces correspondientes en el chat!', 70 70 ], 71 71 72 72 'upload_more' => [ 73 - 'title' => '', 74 - 'description' => '', 73 + 'title' => 'Sube más', 74 + 'description' => 'Espacios para mapas pendientes adicionales (por mapa clasificado) hasta un máximo de 10.', 75 75 ], 76 76 77 77 'early_access' => [ 78 - 'title' => '', 79 - 'description' => '', 78 + 'title' => 'Acceso anticipado', 79 + 'description' => '¡Obtén acceso anticipado a nuevas versiones con nuevas funciones antes de que se hagan públicas!<br/><br/>¡Esto también incluye el acceso anticipado a las nuevas funciones del sitio web!', 80 80 ], 81 81 82 82 'customisation' => [ 83 - 'title' => '', 84 - 'description' => "", 83 + 'title' => 'Personalización', 84 + 'description' => "Destaca subiendo una imagen de portada personalizada o creando una sección '¡yo!' totalmente personalizable dentro de tu perfil de usuario.", 85 85 ], 86 86 87 87 'beatmap_filters' => [ 88 - 'title' => '', 89 - 'description' => '', 88 + 'title' => 'Filtros en la búsqueda de mapas', 89 + 'description' => 'Filtra las búsquedas de mapas por mapas jugados y no jugados, o por grado conseguido.', 90 90 ], 91 91 92 92 'yellow_fellow' => [ 93 - 'title' => '', 94 - 'description' => '', 93 + 'title' => 'Color amarillo en el chat', 94 + 'description' => 'Resalta dentro del chat del juego con un amarillo brillante en tu nombre de usuario.', 95 95 ], 96 96 97 97 'speedy_downloads' => [ 98 - 'title' => '', 99 - 'description' => '', 98 + 'title' => 'Descargas rápidas', 99 + 'description' => 'Restricciones de descarga más permisivas, especialmente al utilizar osu!direct.', 100 100 ], 101 101 102 102 'change_username' => [ 103 - 'title' => '', 104 - 'description' => '', 103 + 'title' => 'Cambiar nombre de usuario', 104 + 'description' => 'Un cambio de nombre gratuito se incluye con tu primera compra de supporter.', 105 105 ], 106 106 107 107 'skinnables' => [ 108 - 'title' => '', 109 - 'description' => '', 108 + 'title' => 'Más elementos de personalización', 109 + 'description' => 'Más elementos personalizables en el juego, como el fondo del menú principal.', 110 110 ], 111 111 112 112 'feature_votes' => [ 113 - 'title' => '', 114 - 'description' => '', 113 + 'title' => 'Votos para nuevas funciones', 114 + 'description' => 'Votos para solicitudes de funciones. (2 al mes)', 115 115 ], 116 116 117 117 'sort_options' => [ 118 - 'title' => '', 119 - 'description' => '', 118 + 'title' => 'Opciones de orden', 119 + 'description' => 'La posibilidad de ver las clasificaciones de tu país, amigos o mods en el juego.', 120 120 ], 121 121 122 122 'more_favourites' => [ 123 - 'title' => '', 124 - 'description' => '', 123 + 'title' => 'Más favoritos', 124 + 'description' => 'El número máximo de mapas que puedes marcar como favorito aumenta de :normally &rarr; :supporter', 125 125 ], 126 126 'more_friends' => [ 127 - 'title' => '', 128 - 'description' => '', 127 + 'title' => 'Más amigos', 128 + 'description' => 'El número máximo de amigos que puedes tener aumenta de :normally &rarr; :supporter', 129 129 ], 130 130 'more_beatmaps' => [ 131 - 'title' => '', 132 - 'description' => '', 131 + 'title' => 'Subir más mapas', 132 + 'description' => 'El número de mapas pendientes que puedes tener a la vez se calcula a partir de un valor base más una bonificación adicional por cada mapa clasificado que tengas actualmente (hasta un límite).<br/><br/>Normalmente esto es :base más :bonus por mapa clasificado (hasta :bonus_max). Con supporter, esto aumenta a :supporter_base más :supporter_bonus por cada mapa clasificado (hasta :supporter_bonus_max).', 133 133 ], 134 134 'friend_filtering' => [ 135 - 'title' => '', 136 - 'description' => '', 135 + 'title' => 'Tablas de clasificación entre amigos', 136 + 'description' => '¡Compite con tus amigos y compara tus puntuaciones con las de ellos!', 137 137 ], 138 138 139 139 ], 140 140 'supporter_status' => [ 141 - 'contribution' => '', 142 - 'gifted' => "", 143 - 'not_yet' => "", 144 - 'valid_until' => '', 145 - 'was_valid_until' => '', 141 + 'contribution' => '¡Gracias por tu apoyo hasta ahora! ¡Has contribuido con :dollars con la compra de :tags etiquetas!', 142 + 'gifted' => "Has regalado :giftedTags de tus compras (eso es un valor de :giftedDollars), ¡qué generoso!", 143 + 'not_yet' => "Nunca has tenido una etiqueta de osu!supporter :(", 144 + 'valid_until' => '¡Tu etiqueta de osu!supporter actual es válida hasta el :date!', 145 + 'was_valid_until' => 'Tu etiqueta de osu!supporter fue válida hasta el :date.', 146 146 ], 147 147 ], 148 148 ];
+47 -47
resources/lang/es-419/contest.php
··· 5 5 6 6 return [ 7 7 'header' => [ 8 - 'small' => '', 9 - 'large' => '', 8 + 'small' => 'Compite de más formas que solo presionando círculos.', 9 + 'large' => 'Concursos de la comunidad', 10 10 ], 11 11 12 12 'index' => [ 13 - 'nav_title' => '', 13 + 'nav_title' => 'listado', 14 14 ], 15 15 16 16 'judge' => [ 17 - 'hide_judged' => '', 18 - 'nav_title' => '', 19 - 'no_current_vote' => '', 20 - 'update' => '', 17 + 'hide_judged' => 'ocultar las inscripciones evaluadas', 18 + 'nav_title' => 'evaluar', 19 + 'no_current_vote' => 'aún no has votado.', 20 + 'update' => 'actualizar', 21 21 'validation' => [ 22 - 'missing_score' => '', 23 - 'contest_vote_judged' => '', 22 + 'missing_score' => 'puntuación faltante', 23 + 'contest_vote_judged' => 'no puedes votar en concursos ya evaluados', 24 24 ], 25 - 'voted' => '', 25 + 'voted' => 'Ya has votado por esta opción.', 26 26 ], 27 27 28 28 'judge_results' => [ 29 - '_' => '', 30 - 'creator' => '', 31 - 'score' => '', 32 - 'total_score' => '', 29 + '_' => 'Resultados de la evaluación', 30 + 'creator' => 'creador', 31 + 'score' => 'Puntuación', 32 + 'total_score' => 'puntuación total', 33 33 ], 34 34 35 35 'voting' => [ 36 - 'judge_link' => '', 37 - 'judged_notice' => '', 38 - 'login_required' => '', 39 - 'over' => '', 40 - 'show_voted_only' => '', 36 + 'judge_link' => 'Eres un juez en este concurso. ¡Evalúa las inscripciones aquí!', 37 + 'judged_notice' => 'Este concurso está utilizando el sistema de evaluación, los jueces están procesando actualmente las inscripciones.', 38 + 'login_required' => 'Inicia sesión para votar.', 39 + 'over' => 'El plazo de votación para este concurso ha finalizado', 40 + 'show_voted_only' => 'Mostrar mis votos', 41 41 42 42 'best_of' => [ 43 - 'none_played' => "", 43 + 'none_played' => "¡Parece que no has jugado ningún mapa que cumpla con los requisitos de este concurso!", 44 44 ], 45 45 46 46 'button' => [ 47 - 'add' => '', 48 - 'remove' => '', 49 - 'used_up' => '', 47 + 'add' => 'Votar', 48 + 'remove' => 'Quitar voto', 49 + 'used_up' => 'Ya has usado todos tus votos', 50 50 ], 51 51 52 52 'progress' => [ 53 - '_' => '', 53 + '_' => ':used / :max votos usados', 54 54 ], 55 55 56 56 'requirement' => [ 57 57 'playlist_beatmapsets' => [ 58 - 'incomplete_play' => '', 58 + 'incomplete_play' => 'Debes jugar todos los mapas en las listas de juego especificadas antes de votar', 59 59 ], 60 60 ], 61 61 ], 62 62 63 63 'entry' => [ 64 - '_' => '', 65 - 'login_required' => '', 66 - 'silenced_or_restricted' => '', 67 - 'preparation' => '', 68 - 'drop_here' => '', 69 - 'download' => '', 64 + '_' => 'inscripción', 65 + 'login_required' => 'Inicia sesión para participar en el concurso.', 66 + 'silenced_or_restricted' => 'No puedes participar en los concursos mientras estés restringido o silenciado.', 67 + 'preparation' => 'Estamos preparando este concurso actualmente. ¡Espera pacientemente!', 68 + 'drop_here' => 'Suelta tu inscripción aquí', 69 + 'download' => 'Descargar archivo .osz', 70 70 71 71 'wrong_type' => [ 72 - 'art' => '', 73 - 'beatmap' => '', 74 - 'music' => '', 72 + 'art' => 'Solo se aceptan archivos .jpg y .png en este concurso.', 73 + 'beatmap' => 'Solo se aceptan archivos .osu en este concurso.', 74 + 'music' => 'Solo se aceptan archivos .mp3 en este concurso.', 75 75 ], 76 76 77 - 'wrong_dimensions' => '', 78 - 'too_big' => '', 77 + 'wrong_dimensions' => 'Los envíos para este concurso deben ser de :widthx:height', 78 + 'too_big' => 'Los envíos para este concurso solo pueden ser de hasta :limit.', 79 79 ], 80 80 81 81 'beatmaps' => [ 82 - 'download' => '', 82 + 'download' => 'Descargar inscripción', 83 83 ], 84 84 85 85 'vote' => [ 86 - 'list' => '', 87 - 'count' => '', 88 - 'points' => '', 86 + 'list' => 'votos', 87 + 'count' => ':count_delimited voto|:count_delimited votos', 88 + 'points' => ':count_delimited punto|:count_delimited puntos', 89 89 ], 90 90 91 91 'dates' => [ 92 - 'ended' => '', 93 - 'ended_no_date' => '', 92 + 'ended' => 'Finalizó el :date', 93 + 'ended_no_date' => 'Finalizado', 94 94 95 95 'starts' => [ 96 - '_' => '', 97 - 'soon' => '', 96 + '_' => 'Comienza el :date', 97 + 'soon' => 'soon™', 98 98 ], 99 99 ], 100 100 101 101 'states' => [ 102 - 'entry' => '', 103 - 'voting' => '', 104 - 'results' => '', 102 + 'entry' => 'Inscripción abierta', 103 + 'voting' => 'Votación iniciada', 104 + 'results' => 'Resultados', 105 105 ], 106 106 ];
+16 -16
resources/lang/es-419/errors.php
··· 4 4 // See the LICENCE file in the repository root for full licence text. 5 5 6 6 return [ 7 - 'load_failed' => '', 8 - 'missing_route' => '', 9 - 'no_restricted_access' => '', 10 - 'supporter_only' => '', 11 - 'unknown' => '', 7 + 'load_failed' => 'Error al cargar los datos.', 8 + 'missing_route' => 'URL no válida o método de solicitud incorrecto.', 9 + 'no_restricted_access' => 'No podrás realizar esta acción mientras tu cuenta esté en estado restringido.', 10 + 'supporter_only' => 'Debes ser un osu!supporter para utilizar esta función.', 11 + 'unknown' => 'Se produjo un error desconocido.', 12 12 13 13 'codes' => [ 14 - 'http-401' => '', 15 - 'http-403' => '', 16 - 'http-404' => '', 17 - 'http-429' => '', 14 + 'http-401' => 'Inicia sesión para continuar.', 15 + 'http-403' => 'Acceso denegado.', 16 + 'http-404' => 'No encontrado.', 17 + 'http-429' => 'Demasiados intentos. Inténtalo de nuevo más tarde.', 18 18 ], 19 19 'account' => [ 20 20 'profile-order' => [ 21 - 'generic' => '', 21 + 'generic' => 'Se produjo un error. Intenta actualizar la página.', 22 22 ], 23 23 ], 24 24 'beatmaps' => [ 25 - 'invalid_mode' => '', 26 - 'standard_converts_only' => '', 25 + 'invalid_mode' => 'Modo especificado no válido.', 26 + 'standard_converts_only' => 'No hay puntuaciones disponibles para el modo solicitado en esta dificultad del mapa.', 27 27 ], 28 28 'checkout' => [ 29 - 'generic' => '', 29 + 'generic' => 'Se produjo un error mientras se preparaba el pago.', 30 30 ], 31 31 'search' => [ 32 - 'default' => '', 33 - 'invalid_cursor_exception' => '', 34 - 'operation_timeout_exception' => '', 32 + 'default' => 'No se ha podido obtener ningún resultado, inténtalo de nuevo más tarde.', 33 + 'invalid_cursor_exception' => 'Se ha especificado un parámetro para el cursor no válido.', 34 + 'operation_timeout_exception' => 'La búsqueda está más saturada de lo habitual, inténtalo de nuevo más tarde.', 35 35 ], 36 36 ];
+19 -19
resources/lang/es-419/events.php
··· 4 4 // See the LICENCE file in the repository root for full licence text. 5 5 6 6 return [ 7 - 'achievement' => '', 8 - 'beatmap_playcount' => '', 9 - 'beatmapset_approve' => '', 10 - 'beatmapset_delete' => '', 11 - 'beatmapset_revive' => '', 12 - 'beatmapset_update' => '', 13 - 'beatmapset_upload' => '', 14 - 'empty' => "", 15 - 'rank' => '', 16 - 'rank_lost' => '', 17 - 'user_support_again' => '', 18 - 'user_support_first' => '', 19 - 'user_support_gift' => '', 20 - 'username_change' => '', 7 + 'achievement' => '<strong>¡<strong><em>:user</em></strong> ha desbloqueado la medalla «<strong>:achievement</strong>»!', 8 + 'beatmap_playcount' => '¡:beatmap ha sido jugado :count veces!', 9 + 'beatmapset_approve' => '¡:beatmapset por <strong>:user</strong> ha sido :approval!', 10 + 'beatmapset_delete' => ':beatmapset ha sido eliminado.', 11 + 'beatmapset_revive' => ':beatmapset ha sido revivido del sueño eterno por <strong>:user</strong>.', 12 + 'beatmapset_update' => '<strong><em>:user</em></strong> ha actualizado el mapa «<em>:beatmapset</em>»', 13 + 'beatmapset_upload' => '<strong><em>:user</em></strong> ha enviado un nuevo mapa «:beatmapset»', 14 + 'empty' => "¡Este usuario no ha hecho nada notable recientemente!", 15 + 'rank' => ':user consiguió el :rank en :beatmap (:mode)', 16 + 'rank_lost' => '<strong><em>:user</em></strong> ha perdido el primer puesto en <em>:beatmap</em> (:mode)', 17 + 'user_support_again' => '<strong>:user</strong> ha elegido apoyar a osu! una vez más - ¡gracias por tu generosidad!', 18 + 'user_support_first' => '<strong>:user</strong> ha apoyado a osu! - ¡gracias por tu generosidad!', 19 + 'user_support_gift' => '¡<strong>:user</strong> ha recibido un osu!supporter! como regalo!', 20 + 'username_change' => '¡<strong>:previousUsername</strong> ha cambiado su nombre de usuario a <strong><em>:user</em></strong>!', 21 21 22 22 'beatmapset_status' => [ 23 - 'approved' => '', 24 - 'loved' => '', 25 - 'qualified' => '', 26 - 'ranked' => '', 23 + 'approved' => 'aprobado', 24 + 'loved' => 'amado', 25 + 'qualified' => 'calificado', 26 + 'ranked' => 'clasificado', 27 27 ], 28 28 29 29 'value' => [ 30 - 'rank' => '', 30 + 'rank' => 'puesto #:rank', 31 31 ], 32 32 ];
+14 -14
resources/lang/es-419/follows.php
··· 5 5 6 6 return [ 7 7 'comment' => [ 8 - 'empty' => '', 9 - 'page_title' => '', 10 - 'title' => '', 8 + 'empty' => 'No hay comentarios observados.', 9 + 'page_title' => 'lista de seguimiento de comentarios', 10 + 'title' => 'comentarios', 11 11 12 12 'table' => [ 13 - 'latest_comment_empty' => '', 14 - 'latest_comment_value' => '', 13 + 'latest_comment_empty' => 'sin comentarios', 14 + 'latest_comment_value' => ':time por :username', 15 15 ], 16 16 ], 17 17 18 18 'forum_topic' => [ 19 - 'title' => '', 19 + 'title' => 'temas del foro', 20 20 ], 21 21 22 22 'index' => [ 23 - 'title_compact' => '', 23 + 'title_compact' => 'listas de seguimiento', 24 24 ], 25 25 26 26 'mapping' => [ 27 - 'empty' => '', 28 - 'followers' => '', 29 - 'page_title' => '', 30 - 'title' => '', 31 - 'to_0' => '', 32 - 'to_1' => '', 27 + 'empty' => 'No hay mappers observados.', 28 + 'followers' => 'suscriptores de mapeo', 29 + 'page_title' => 'lista de seguimiento de mappers', 30 + 'title' => 'mappers', 31 + 'to_0' => 'dejar de notificarme cuando este usuario suba un nuevo mapa', 32 + 'to_1' => 'notificarme cuando este usuario suba un nuevo mapa', 33 33 ], 34 34 35 35 'modding' => [ 36 - 'title' => '', 36 + 'title' => 'discusiones de mapas', 37 37 ], 38 38 ];
+202 -202
resources/lang/es-419/forum.php
··· 4 4 // See the LICENCE file in the repository root for full licence text. 5 5 6 6 return [ 7 - 'pinned_topics' => '', 8 - 'slogan' => "", 9 - 'subforums' => '', 10 - 'title' => '', 7 + 'pinned_topics' => 'Temas fijados', 8 + 'slogan' => "es peligroso jugar solo.", 9 + 'subforums' => 'Subforos', 10 + 'title' => 'Foros', 11 11 12 12 'covers' => [ 13 - 'edit' => '', 13 + 'edit' => 'Editar portada', 14 14 15 15 'create' => [ 16 - '_' => '', 17 - 'button' => '', 18 - 'info' => '', 16 + '_' => 'Establecer imagen de portada', 17 + 'button' => 'Subir portada', 18 + 'info' => 'El tamaño de la portada debe ser de :dimensions. También puedes soltar tu imagen aquí para subirla.', 19 19 ], 20 20 21 21 'destroy' => [ 22 - '_' => '', 23 - 'confirm' => '', 22 + '_' => 'Eliminar portada', 23 + 'confirm' => '¿Seguro que quieres eliminar la imagen de portada?', 24 24 ], 25 25 ], 26 26 27 27 'forums' => [ 28 - 'forums' => '', 29 - 'latest_post' => '', 28 + 'forums' => 'Foros', 29 + 'latest_post' => 'Último mensaje', 30 30 31 31 'index' => [ 32 - 'title' => '', 32 + 'title' => 'Índice del foro', 33 33 ], 34 34 35 35 'topics' => [ 36 - 'empty' => '', 36 + 'empty' => '¡No hay temas!', 37 37 ], 38 38 ], 39 39 40 40 'mark_as_read' => [ 41 - 'forum' => '', 42 - 'forums' => '', 43 - 'busy' => '', 41 + 'forum' => 'Marcar foro como leído', 42 + 'forums' => 'Marcar foros como leídos', 43 + 'busy' => 'Marcando como leído...', 44 44 ], 45 45 46 46 'post' => [ 47 - 'confirm_destroy' => '', 48 - 'confirm_restore' => '', 49 - 'edited' => '', 50 - 'posted_at' => '', 51 - 'posted_by_in' => '', 47 + 'confirm_destroy' => '¿Quieres eliminar la publicación?', 48 + 'confirm_restore' => '¿Quieres restaurar la publicación?', 49 + 'edited' => 'Última vez editado por :user :when, editado :count_delimited vez en total.|Última vez editado por :user :when, editado :count_delimited veces en total.', 50 + 'posted_at' => 'publicado :when', 51 + 'posted_by_in' => 'publicado por :username en :forum', 52 52 53 53 'actions' => [ 54 - 'destroy' => '', 55 - 'edit' => '', 56 - 'report' => '', 57 - 'restore' => '', 54 + 'destroy' => 'Eliminar publicación', 55 + 'edit' => 'Editar publicación', 56 + 'report' => 'Reportar publicación', 57 + 'restore' => 'Restaurar publicación', 58 58 ], 59 59 60 60 'create' => [ 61 61 'title' => [ 62 - 'reply' => '', 62 + 'reply' => 'Nueva respuesta', 63 63 ], 64 64 ], 65 65 66 66 'info' => [ 67 - 'post_count' => '', 68 - 'topic_starter' => '', 67 + 'post_count' => ':count_delimited publicación|:count_delimited publicaciones', 68 + 'topic_starter' => 'Creador del tema', 69 69 ], 70 70 ], 71 71 72 72 'search' => [ 73 - 'go_to_post' => '', 74 - 'post_number_input' => '', 75 - 'total_posts' => '', 73 + 'go_to_post' => 'Ir a la publicación', 74 + 'post_number_input' => 'introducir el número de la publicación', 75 + 'total_posts' => ':posts_count publicaciones totales', 76 76 ], 77 77 78 78 'topic' => [ 79 - 'confirm_destroy' => '', 80 - 'confirm_restore' => '', 81 - 'deleted' => '', 82 - 'go_to_latest' => '', 83 - 'has_replied' => '', 84 - 'in_forum' => '', 85 - 'latest_post' => '', 86 - 'latest_reply_by' => '', 87 - 'new_topic' => '', 88 - 'new_topic_login' => '', 89 - 'post_reply' => '', 90 - 'reply_box_placeholder' => '', 91 - 'reply_title_prefix' => '', 92 - 'started_by' => '', 93 - 'started_by_verbose' => '', 79 + 'confirm_destroy' => '¿Quieres eliminar el tema?', 80 + 'confirm_restore' => '¿Quieres restaurar el tema?', 81 + 'deleted' => 'tema eliminado', 82 + 'go_to_latest' => 'ver la última publicación', 83 + 'has_replied' => 'Has respondido a este tema', 84 + 'in_forum' => 'en :forum', 85 + 'latest_post' => ':when por :user', 86 + 'latest_reply_by' => 'última respuesta por :user', 87 + 'new_topic' => 'Nuevo tema', 88 + 'new_topic_login' => 'Inicia sesión para publicar un nuevo tema', 89 + 'post_reply' => 'Publicar', 90 + 'reply_box_placeholder' => 'Escribe aquí para responder', 91 + 'reply_title_prefix' => 'Re', 92 + 'started_by' => 'por :user', 93 + 'started_by_verbose' => 'iniciado por :user', 94 94 95 95 'actions' => [ 96 - 'destroy' => '', 97 - 'restore' => '', 96 + 'destroy' => 'Eliminar tema', 97 + 'restore' => 'Restaurar tema', 98 98 ], 99 99 100 100 'create' => [ 101 - 'close' => '', 102 - 'preview' => '', 101 + 'close' => 'Cerrar', 102 + 'preview' => 'Vista previa', 103 103 // TL note: this is used in the topic reply preview, when 104 104 // the user goes back from previewing to editing the reply 105 - 'preview_hide' => '', 106 - 'submit' => '', 105 + 'preview_hide' => 'Editar', 106 + 'submit' => 'Publicar', 107 107 108 108 'necropost' => [ 109 - 'default' => '', 109 + 'default' => 'Este tema ha estado inactivo durante mucho tiempo. Solo publica algo aquí si tiene una razón específica para hacerlo.', 110 110 111 111 'new_topic' => [ 112 - '_' => "", 113 - 'create' => '', 112 + '_' => "Este tema ha estado inactivo durante mucho tiempo. Si no tienes una razón específica para publicar algo aquí, mejor :create en su lugar.", 113 + 'create' => 'crea un nuevo tema', 114 114 ], 115 115 ], 116 116 117 117 'placeholder' => [ 118 - 'body' => '', 119 - 'title' => '', 118 + 'body' => 'Escribe el contenido de la publicación aquí', 119 + 'title' => 'Haz clic aquí para poner el título', 120 120 ], 121 121 ], 122 122 123 123 'jump' => [ 124 - 'enter' => '', 125 - 'first' => '', 126 - 'last' => '', 127 - 'next' => '', 128 - 'previous' => '', 124 + 'enter' => 'haz clic para introducir el número de la publicación', 125 + 'first' => 'ir a la primera publicación', 126 + 'last' => 'ir a la última publicación', 127 + 'next' => 'saltar las 10 publicaciones siguientes', 128 + 'previous' => 'retroceder 10 publicaciones', 129 129 ], 130 130 131 131 'logs' => [ 132 - '_' => '', 133 - 'button' => '', 132 + '_' => 'Registros de temas', 133 + 'button' => 'Buscar en los registros de los temas', 134 134 135 135 'columns' => [ 136 - 'action' => '', 137 - 'date' => '', 138 - 'user' => '', 136 + 'action' => 'Acción', 137 + 'date' => 'Fecha', 138 + 'user' => 'Usuario', 139 139 ], 140 140 141 141 'data' => [ 142 - 'add_tag' => '', 143 - 'announcement' => '', 144 - 'edit_topic' => '', 145 - 'fork' => '', 146 - 'pin' => '', 147 - 'post_operation' => '', 148 - 'remove_tag' => '', 149 - 'source_forum_operation' => '', 150 - 'unpin' => '', 142 + 'add_tag' => 'etiqueta «:tag» añadida', 143 + 'announcement' => 'tema fijado y marcado como un anuncio', 144 + 'edit_topic' => 'a :title', 145 + 'fork' => 'de :topic', 146 + 'pin' => 'tema fijado', 147 + 'post_operation' => 'publicado por :username', 148 + 'remove_tag' => 'etiqueta «:tag» eliminada', 149 + 'source_forum_operation' => 'de :forum', 150 + 'unpin' => 'tema no fijado', 151 151 ], 152 152 153 - 'no_results' => '', 153 + 'no_results' => 'no se encontraron registros...', 154 154 155 155 'operations' => [ 156 - 'delete_post' => '', 157 - 'delete_topic' => '', 158 - 'edit_topic' => '', 159 - 'edit_poll' => '', 160 - 'fork' => '', 161 - 'issue_tag' => '', 162 - 'lock' => '', 163 - 'merge' => '', 164 - 'move' => '', 165 - 'pin' => '', 166 - 'post_edited' => '', 167 - 'restore_post' => '', 168 - 'restore_topic' => '', 169 - 'split_destination' => '', 170 - 'split_source' => '', 171 - 'topic_type' => '', 172 - 'topic_type_changed' => '', 173 - 'unlock' => '', 174 - 'unpin' => '', 175 - 'user_lock' => '', 176 - 'user_unlock' => '', 156 + 'delete_post' => 'Publicación eliminada', 157 + 'delete_topic' => 'Tema eliminado', 158 + 'edit_topic' => 'Título del tema cambiado', 159 + 'edit_poll' => 'Encuesta del tema editada', 160 + 'fork' => 'Tema copiado', 161 + 'issue_tag' => 'Etiqueta colocada', 162 + 'lock' => 'Tema cerrado', 163 + 'merge' => 'Publicaciones fusionadas en este tema', 164 + 'move' => 'Tema movido', 165 + 'pin' => 'Tema fijado', 166 + 'post_edited' => 'Publicación editada', 167 + 'restore_post' => 'Publicación restaurada', 168 + 'restore_topic' => 'Tema restaurado', 169 + 'split_destination' => 'Publicaciones separadas movidas', 170 + 'split_source' => 'Publicaciones separadas', 171 + 'topic_type' => 'Tipo del tema establecido', 172 + 'topic_type_changed' => 'Tipo del tema cambiado', 173 + 'unlock' => 'Tema abierto', 174 + 'unpin' => 'Tema no fijado', 175 + 'user_lock' => 'Tema propio cerrado', 176 + 'user_unlock' => 'Tema propio abierto', 177 177 ], 178 178 ], 179 179 180 180 'post_edit' => [ 181 - 'cancel' => '', 182 - 'post' => '', 181 + 'cancel' => 'Cancelar', 182 + 'post' => 'Guardar', 183 183 ], 184 184 ], 185 185 186 186 'topic_watches' => [ 187 187 'index' => [ 188 - 'title_compact' => '', 188 + 'title_compact' => 'lista de seguimiento de temas del foro', 189 189 190 190 'box' => [ 191 - 'total' => '', 192 - 'unread' => '', 191 + 'total' => 'Temas suscritos', 192 + 'unread' => 'Temas con nuevas respuestas', 193 193 ], 194 194 195 195 'info' => [ 196 - 'total' => '', 197 - 'unread' => '', 196 + 'total' => 'Te has suscrito a :total temas.', 197 + 'unread' => 'Tienes :unread respuestas sin leer a temas suscritos.', 198 198 ], 199 199 ], 200 200 201 201 'topic_buttons' => [ 202 202 'remove' => [ 203 - 'confirmation' => '', 204 - 'title' => '', 203 + 'confirmation' => '¿Cancelar suscripción al tema?', 204 + 'title' => 'Cancelar suscripción', 205 205 ], 206 206 ], 207 207 ], 208 208 209 209 'topics' => [ 210 - '_' => '', 210 + '_' => 'Temas', 211 211 212 212 'actions' => [ 213 - 'login_reply' => '', 214 - 'reply' => '', 215 - 'reply_with_quote' => '', 216 - 'search' => '', 213 + 'login_reply' => 'Inicia sesión para responder', 214 + 'reply' => 'Responder', 215 + 'reply_with_quote' => 'Citar publicación y responder', 216 + 'search' => 'Buscar', 217 217 ], 218 218 219 219 'create' => [ 220 - 'create_poll' => '', 220 + 'create_poll' => 'Creación de una encuesta', 221 221 222 - 'preview' => '', 222 + 'preview' => 'Vista previa', 223 223 224 224 'create_poll_button' => [ 225 - 'add' => '', 226 - 'remove' => '', 225 + 'add' => 'Crear una encuesta', 226 + 'remove' => 'Cancelar la creación de la encuesta', 227 227 ], 228 228 229 229 'poll' => [ 230 - 'hide_results' => '', 231 - 'hide_results_info' => '', 232 - 'length' => '', 233 - 'length_days_suffix' => '', 234 - 'length_info' => '', 235 - 'max_options' => '', 236 - 'max_options_info' => '', 237 - 'options' => '', 238 - 'options_info' => '', 239 - 'title' => '', 240 - 'vote_change' => '', 241 - 'vote_change_info' => '', 230 + 'hide_results' => 'Ocultar los resultados de la encuesta.', 231 + 'hide_results_info' => 'Solo se mostrarán después de que finalice la encuesta.', 232 + 'length' => 'Duración de la encuesta', 233 + 'length_days_suffix' => 'días', 234 + 'length_info' => 'Dejar en blanco para una encuesta sin fin', 235 + 'max_options' => 'Opciones por usuario', 236 + 'max_options_info' => 'Este es el número de opciones que cada usuario podrá seleccionar al votar.', 237 + 'options' => 'Opciones', 238 + 'options_info' => 'Escribe cada opción en una nueva línea. Puedes añadir hasta 10 opciones.', 239 + 'title' => 'Pregunta', 240 + 'vote_change' => 'Permitir que se pueda volver a votar.', 241 + 'vote_change_info' => 'Si se activa, los usuarios podrán cambiar su voto.', 242 242 ], 243 243 ], 244 244 245 245 'edit_title' => [ 246 - 'start' => '', 246 + 'start' => 'Editar título', 247 247 ], 248 248 249 249 'index' => [ 250 - 'feature_votes' => '', 251 - 'replies' => '', 252 - 'views' => '', 250 + 'feature_votes' => 'prioridad de estrella', 251 + 'replies' => 'respuestas', 252 + 'views' => 'vistas', 253 253 ], 254 254 255 255 'issue_tag_added' => [ 256 - 'to_0' => '', 257 - 'to_0_done' => '', 258 - 'to_1' => '', 259 - 'to_1_done' => '', 256 + 'to_0' => 'Eliminar etiqueta «added»', 257 + 'to_0_done' => 'Etiqueta «added» eliminada', 258 + 'to_1' => 'Añadir etiqueta «added»', 259 + 'to_1_done' => 'Etiqueta «added» añadida', 260 260 ], 261 261 262 262 'issue_tag_assigned' => [ 263 - 'to_0' => '', 264 - 'to_0_done' => '', 265 - 'to_1' => '', 266 - 'to_1_done' => '', 263 + 'to_0' => 'Eliminar etiqueta «assigned»', 264 + 'to_0_done' => 'Etiqueta «assigned» eliminada', 265 + 'to_1' => 'Añadir etiqueta «assigned»', 266 + 'to_1_done' => 'Etiqueta «assigned» añadida', 267 267 ], 268 268 269 269 'issue_tag_confirmed' => [ 270 - 'to_0' => '', 271 - 'to_0_done' => '', 272 - 'to_1' => '', 273 - 'to_1_done' => '', 270 + 'to_0' => 'Eliminar etiqueta «confirmed»', 271 + 'to_0_done' => 'Etiqueta «confirmed» eliminada', 272 + 'to_1' => 'Añadir etiqueta «confirmed»', 273 + 'to_1_done' => 'Etiqueta «confirmed» añadida', 274 274 ], 275 275 276 276 'issue_tag_duplicate' => [ 277 - 'to_0' => '', 278 - 'to_0_done' => '', 279 - 'to_1' => '', 280 - 'to_1_done' => '', 277 + 'to_0' => 'Eliminar etiqueta «duplicate»', 278 + 'to_0_done' => 'Etiqueta «duplicate» eliminada', 279 + 'to_1' => 'Añadir etiqueta «duplicate»', 280 + 'to_1_done' => 'Etiqueta «duplicate» añadida', 281 281 ], 282 282 283 283 'issue_tag_invalid' => [ 284 - 'to_0' => '', 285 - 'to_0_done' => '', 286 - 'to_1' => '', 287 - 'to_1_done' => '', 284 + 'to_0' => 'Eliminar etiqueta «invalid»', 285 + 'to_0_done' => 'Etiqueta «invalid» eliminada', 286 + 'to_1' => 'Añadir etiqueta «invalid»', 287 + 'to_1_done' => 'Etiqueta «invalid» añadida', 288 288 ], 289 289 290 290 'issue_tag_resolved' => [ 291 - 'to_0' => '', 292 - 'to_0_done' => '', 293 - 'to_1' => '', 294 - 'to_1_done' => '', 291 + 'to_0' => 'Eliminar etiqueta «resolved»', 292 + 'to_0_done' => 'Etiqueta «resolved» eliminada', 293 + 'to_1' => 'Añadir etiqueta «resolved»', 294 + 'to_1_done' => 'Etiqueta «resolved» añadida', 295 295 ], 296 296 297 297 'lock' => [ 298 - 'is_locked' => '', 299 - 'to_0' => '', 300 - 'to_0_confirm' => '', 301 - 'to_0_done' => '', 302 - 'to_1' => '', 303 - 'to_1_confirm' => '', 304 - 'to_1_done' => '', 298 + 'is_locked' => 'Este tema está cerrado y no se puede responder a él', 299 + 'to_0' => 'Abrir tema', 300 + 'to_0_confirm' => '¿Abrir el tema?', 301 + 'to_0_done' => 'El tema ha sido abierto', 302 + 'to_1' => 'Cerrar tema', 303 + 'to_1_confirm' => '¿Cerrar el tema?', 304 + 'to_1_done' => 'El tema ha sido cerrado', 305 305 ], 306 306 307 307 'moderate_move' => [ 308 - 'title' => '', 308 + 'title' => 'Mover a otro foro', 309 309 ], 310 310 311 311 'moderate_pin' => [ 312 - 'to_0' => '', 313 - 'to_0_confirm' => '', 314 - 'to_0_done' => '', 315 - 'to_1' => '', 316 - 'to_1_confirm' => '', 317 - 'to_1_done' => '', 318 - 'to_2' => '', 319 - 'to_2_confirm' => '', 320 - 'to_2_done' => '', 312 + 'to_0' => 'Desfijar tema', 313 + 'to_0_confirm' => '¿Desfijar el tema?', 314 + 'to_0_done' => 'El tema ha sido desfijado', 315 + 'to_1' => 'Fijar tema', 316 + 'to_1_confirm' => '¿Fijar el tema?', 317 + 'to_1_done' => 'El tema ha sido fijado', 318 + 'to_2' => 'Fijar tema y marcarlo como anuncio', 319 + 'to_2_confirm' => '¿Fijar el tema y marcarlo como un anuncio?', 320 + 'to_2_done' => 'El tema ha sido fijado y marcado como un anuncio', 321 321 ], 322 322 323 323 'moderate_toggle_deleted' => [ 324 - 'show' => '', 325 - 'hide' => '', 324 + 'show' => 'Mostrar publicaciones eliminadas', 325 + 'hide' => 'Ocultar publicaciones eliminadas', 326 326 ], 327 327 328 328 'show' => [ 329 - 'deleted-posts' => '', 330 - 'total_posts' => '', 329 + 'deleted-posts' => 'Publicaciones eliminadas', 330 + 'total_posts' => 'Publicaciones totales', 331 331 332 332 'feature_vote' => [ 333 - 'current' => '', 334 - 'do' => '', 333 + 'current' => 'Prioridad actual: +:count', 334 + 'do' => 'Apoyar esta solicitud', 335 335 336 336 'info' => [ 337 - '_' => '', 338 - 'feature_request' => '', 339 - 'supporters' => '', 337 + '_' => 'Esto es una :feature_request. Las solicitudes de funciones pueden ser votadas por los :supporters.', 338 + 'feature_request' => 'solicitud de función', 339 + 'supporters' => 'supporters', 340 340 ], 341 341 342 342 'user' => [ 343 - 'count' => '', 344 - 'current' => '', 345 - 'not_enough' => "", 343 + 'count' => '{0} cero votos|{1} :count_delimited voto|[2,*] :count_delimited votos', 344 + 'current' => 'Tienes :votes restantes.', 345 + 'not_enough' => "No tienes más votos restantes", 346 346 ], 347 347 ], 348 348 349 349 'poll' => [ 350 - 'edit' => '', 351 - 'edit_warning' => '', 352 - 'vote' => '', 350 + 'edit' => 'Edición de la encuesta', 351 + 'edit_warning' => '¡Editar la encuesta eliminará los resultados actuales!', 352 + 'vote' => 'Votar', 353 353 354 354 'button' => [ 355 - 'change_vote' => '', 356 - 'edit' => '', 357 - 'view_results' => '', 358 - 'vote' => '', 355 + 'change_vote' => 'Cambiar voto', 356 + 'edit' => 'Editar encuesta', 357 + 'view_results' => 'Saltar a resultados', 358 + 'vote' => 'Votar', 359 359 ], 360 360 361 361 'detail' => [ 362 - 'end_time' => '', 363 - 'ended' => '', 364 - 'results_hidden' => '', 365 - 'total' => '', 362 + 'end_time' => 'La encuesta terminará el :time', 363 + 'ended' => 'Encuesta terminada el :time', 364 + 'results_hidden' => 'Los resultados se mostrarán después de que finalice la encuesta.', 365 + 'total' => 'Votos totales: :count', 366 366 ], 367 367 ], 368 368 ], 369 369 370 370 'watch' => [ 371 - 'to_not_watching' => '', 372 - 'to_watching' => '', 373 - 'to_watching_mail' => '', 374 - 'tooltip_mail_disable' => '', 375 - 'tooltip_mail_enable' => '', 371 + 'to_not_watching' => 'No guardar', 372 + 'to_watching' => 'Guardar', 373 + 'to_watching_mail' => 'Guardar y recibir notificaciones', 374 + 'tooltip_mail_disable' => 'Las notificaciones están activadas. Haz clic para desactivarlas', 375 + 'tooltip_mail_enable' => 'Las notificaciones están desactivadas. Haz clic para activarlas', 376 376 ], 377 377 ], 378 378 ];
+5 -5
resources/lang/es-419/friends.php
··· 4 4 // See the LICENCE file in the repository root for full licence text. 5 5 6 6 return [ 7 - 'title_compact' => '', 8 - 'too_many' => '', 7 + 'title_compact' => 'amigos', 8 + 'too_many' => 'Límite de amigos alcanzado', 9 9 10 10 'buttons' => [ 11 - 'add' => '', 12 - 'disabled' => '', 13 - 'remove' => '', 11 + 'add' => 'añadir amigo', 12 + 'disabled' => 'seguidores', 13 + 'remove' => 'eliminar amigo', 14 14 ], 15 15 ];
+81 -81
resources/lang/es-419/home.php
··· 5 5 6 6 return [ 7 7 'landing' => [ 8 - 'download' => '', 9 - 'online' => '', 10 - 'peak' => '', 11 - 'players' => '', 12 - 'title' => '', 13 - 'see_more_news' => '', 8 + 'download' => 'Descargar ahora', 9 + 'online' => '<strong>:players</strong> actualmente en línea en <strong>:games</strong> partidas', 10 + 'peak' => 'Pico, :count usuarios en línea', 11 + 'players' => '<strong>:count</strong> usuarios registrados', 12 + 'title' => 'bienvenido', 13 + 'see_more_news' => 'ver más novedades', 14 14 15 15 'slogan' => [ 16 - 'main' => '', 17 - 'sub' => '', 16 + 'main' => 'el mejor juego de ritmo gratis', 17 + 'sub' => 'el ritmo está a un solo clic de distancia', 18 18 ], 19 19 ], 20 20 21 21 'search' => [ 22 - 'advanced_link' => '', 23 - 'button' => '', 24 - 'empty_result' => '', 25 - 'keyword_required' => '', 26 - 'placeholder' => '', 27 - 'title' => '', 22 + 'advanced_link' => 'Búsqueda avanzada', 23 + 'button' => 'Buscar', 24 + 'empty_result' => '¡No se ha encontrado nada!', 25 + 'keyword_required' => 'Se requiere una palabra clave de búsqueda', 26 + 'placeholder' => 'escribe para buscar', 27 + 'title' => 'buscar', 28 28 29 29 'beatmapset' => [ 30 - 'login_required' => '', 31 - 'more' => '', 32 - 'more_simple' => '', 33 - 'title' => '', 30 + 'login_required' => 'Inicia sesión para buscar mapas', 31 + 'more' => 'Hay :count mapas más en los resultados', 32 + 'more_simple' => 'Ver más resultados de mapas', 33 + 'title' => 'Mapas', 34 34 ], 35 35 36 36 'forum_post' => [ 37 - 'all' => '', 38 - 'link' => '', 39 - 'login_required' => '', 40 - 'more_simple' => '', 41 - 'title' => '', 37 + 'all' => 'Todos los foros', 38 + 'link' => 'Buscar en el foro', 39 + 'login_required' => 'Inicia sesión para buscar en el foro', 40 + 'more_simple' => 'Ver más resultados de los foros', 41 + 'title' => 'Foro', 42 42 43 43 'label' => [ 44 - 'forum' => '', 45 - 'forum_children' => '', 46 - 'include_deleted' => '', 47 - 'topic_id' => '', 48 - 'username' => '', 44 + 'forum' => 'buscar en el foro', 45 + 'forum_children' => 'incluir subforos', 46 + 'include_deleted' => 'incluir publicaciones eliminadas', 47 + 'topic_id' => 'tema #', 48 + 'username' => 'autor', 49 49 ], 50 50 ], 51 51 52 52 'mode' => [ 53 - 'all' => '', 54 - 'beatmapset' => '', 55 - 'forum_post' => '', 56 - 'user' => '', 57 - 'wiki_page' => '', 53 + 'all' => 'todos', 54 + 'beatmapset' => 'mapa', 55 + 'forum_post' => 'foro', 56 + 'user' => 'jugador', 57 + 'wiki_page' => 'wiki', 58 58 ], 59 59 60 60 'user' => [ 61 - 'login_required' => '', 62 - 'more' => '', 63 - 'more_simple' => '', 64 - 'more_hidden' => '', 65 - 'title' => '', 61 + 'login_required' => 'Inicia sesión para buscar usuarios', 62 + 'more' => ':count jugadores más coinciden con la búsqueda', 63 + 'more_simple' => 'Ver más resultados de jugadores', 64 + 'more_hidden' => 'La búsqueda de jugadores está limitada a :max jugadores. Intenta refinar tus términos de búsqueda.', 65 + 'title' => 'Jugadores', 66 66 ], 67 67 68 68 'wiki_page' => [ 69 - 'link' => '', 70 - 'more_simple' => '', 71 - 'title' => '', 69 + 'link' => 'Buscar en la wiki', 70 + 'more_simple' => 'Ver más resultados de la wiki', 71 + 'title' => 'Wiki', 72 72 ], 73 73 ], 74 74 75 75 'download' => [ 76 - 'action' => '', 77 - 'action_lazer' => '', 78 - 'action_lazer_description' => '', 79 - 'action_lazer_info' => '', 80 - 'action_lazer_title' => '', 81 - 'action_title' => '', 82 - 'for_os' => '', 83 - 'macos-fallback' => '', 84 - 'mirror' => '', 85 - 'or' => '', 86 - 'os_version_or_later' => '', 87 - 'other_os' => '', 88 - 'quick_start_guide' => '', 89 - 'tagline' => "", 90 - 'video-guide' => '', 76 + 'action' => 'Descargar osu!', 77 + 'action_lazer' => 'Descargar osu!(lazer)', 78 + 'action_lazer_description' => 'la próxima gran actualización de osu!', 79 + 'action_lazer_info' => 'revisa esta página para más información', 80 + 'action_lazer_title' => 'probar osu!(lazer)', 81 + 'action_title' => 'descargar osu!', 82 + 'for_os' => 'para :os', 83 + 'macos-fallback' => 'usuarios de macOS', 84 + 'mirror' => 'enlace alternativo', 85 + 'or' => 'o', 86 + 'os_version_or_later' => ':os_version o posterior', 87 + 'other_os' => 'otras plataformas', 88 + 'quick_start_guide' => 'guía de inicio rápido', 89 + 'tagline' => "¡vamos a<br>empezar!", 90 + 'video-guide' => 'guía en video', 91 91 92 92 'help' => [ 93 - '_' => '', 94 - 'help_forum_link' => '', 95 - 'support_button' => '', 93 + '_' => 'si tienes problemas al iniciar el juego o al crear una cuenta, :help_forum_link o :support_button.', 94 + 'help_forum_link' => 'consulta el foro de ayuda', 95 + 'support_button' => 'contacta al soporte', 96 96 ], 97 97 98 98 'os' => [ 99 - 'windows' => '', 100 - 'macos' => '', 101 - 'linux' => '', 99 + 'windows' => 'para Windows', 100 + 'macos' => 'para macOS', 101 + 'linux' => 'para Linux', 102 102 ], 103 103 'steps' => [ 104 104 'register' => [ 105 - 'title' => '', 106 - 'description' => '', 105 + 'title' => 'crear una cuenta', 106 + 'description' => 'sigue las instrucciones al iniciar el juego para iniciar sesión o crear una cuenta nueva', 107 107 ], 108 108 'download' => [ 109 - 'title' => '', 110 - 'description' => '', 109 + 'title' => 'instalar el juego', 110 + 'description' => '¡haz clic en el botón de arriba para descargar el instalador y luego poder ejecutarlo!', 111 111 ], 112 112 'beatmaps' => [ 113 - 'title' => '', 113 + 'title' => 'obtener mapas', 114 114 'description' => [ 115 - '_' => '', 116 - 'browse' => '', 115 + '_' => '¡:browse por la amplia biblioteca de mapas creados por los usuarios y empieza a jugar!', 116 + 'browse' => 'navega', 117 117 ], 118 118 ], 119 119 ], 120 120 ], 121 121 122 122 'user' => [ 123 - 'title' => '', 123 + 'title' => 'panel', 124 124 'news' => [ 125 - 'title' => '', 126 - 'error' => '', 125 + 'title' => 'Novedades', 126 + 'error' => 'Error al cargar las novedades, intenta actualizar la página...', 127 127 ], 128 128 'header' => [ 129 129 'stats' => [ 130 - 'friends' => '', 131 - 'games' => '', 132 - 'online' => '', 130 + 'friends' => 'Amigos en línea', 131 + 'games' => 'Partidas', 132 + 'online' => 'Usuarios en línea', 133 133 ], 134 134 ], 135 135 'beatmaps' => [ 136 - 'new' => '', 137 - 'popular' => '', 138 - 'by_user' => '', 136 + 'new' => 'Nuevos mapas clasificados', 137 + 'popular' => 'Mapas populares', 138 + 'by_user' => 'por :user', 139 139 ], 140 140 'buttons' => [ 141 - 'download' => '', 142 - 'support' => '', 143 - 'store' => '', 141 + 'download' => 'Descargar osu!', 142 + 'support' => 'Apoyar a osu!', 143 + 'store' => 'osu!store', 144 144 ], 145 145 ], 146 146 ];
+92 -92
resources/lang/es-419/layout.php
··· 5 5 6 6 return [ 7 7 'audio' => [ 8 - 'autoplay' => '', 8 + 'autoplay' => 'Reproducir la siguiente pista automáticamente', 9 9 ], 10 10 11 11 'defaults' => [ 12 - 'page_description' => '', 12 + 'page_description' => 'osu! - ¡El ritmo está a un solo *clic* de distancia! Con Ouendan/EBA, Taiko y modos de juego originales, así como un editor de niveles totalmente funcional.', 13 13 ], 14 14 15 15 'header' => [ 16 16 'admin' => [ 17 - 'beatmapset' => '', 18 - 'beatmapset_covers' => '', 19 - 'contest' => '', 20 - 'contests' => '', 21 - 'root' => '', 17 + 'beatmapset' => 'conjunto de mapas', 18 + 'beatmapset_covers' => 'portadas del conjunto de mapas', 19 + 'contest' => 'concurso', 20 + 'contests' => 'concursos', 21 + 'root' => 'consola', 22 22 ], 23 23 24 24 'artists' => [ 25 - 'index' => '', 25 + 'index' => 'listado', 26 26 ], 27 27 28 28 'beatmapsets' => [ 29 - 'show' => '', 30 - 'discussions' => '', 29 + 'show' => 'información', 30 + 'discussions' => 'discusión', 31 31 ], 32 32 33 33 'changelog' => [ 34 - 'index' => '', 34 + 'index' => 'listado', 35 35 ], 36 36 37 37 'help' => [ 38 - 'index' => '', 39 - 'sitemap' => '', 38 + 'index' => 'índice', 39 + 'sitemap' => 'Mapa del sitio', 40 40 ], 41 41 42 42 'store' => [ 43 - 'cart' => '', 44 - 'orders' => '', 45 - 'products' => '', 43 + 'cart' => 'carrito', 44 + 'orders' => 'historial de pedidos', 45 + 'products' => 'productos', 46 46 ], 47 47 48 48 'tournaments' => [ 49 - 'index' => '', 49 + 'index' => 'listado', 50 50 ], 51 51 52 52 'users' => [ 53 - 'modding' => '', 54 - 'playlists' => '', 55 - 'realtime' => '', 56 - 'show' => '', 53 + 'modding' => 'modding', 54 + 'playlists' => 'listas de juego', 55 + 'realtime' => 'multijugador', 56 + 'show' => 'información', 57 57 ], 58 58 ], 59 59 60 60 'gallery' => [ 61 - 'close' => '', 62 - 'fullscreen' => '', 63 - 'zoom' => '', 64 - 'previous' => '', 65 - 'next' => '', 61 + 'close' => 'Cerrar (Esc)', 62 + 'fullscreen' => 'Activar/desactivar pantalla completa', 63 + 'zoom' => 'Acercar/alejar', 64 + 'previous' => 'Anterior (flecha izquierda)', 65 + 'next' => 'Siguiente (flecha derecha)', 66 66 ], 67 67 68 68 'menu' => [ 69 69 'beatmaps' => [ 70 - '_' => '', 70 + '_' => 'mapas', 71 71 ], 72 72 'community' => [ 73 - '_' => '', 74 - 'dev' => '', 73 + '_' => 'comunidad', 74 + 'dev' => 'desarrollo', 75 75 ], 76 76 'help' => [ 77 - '_' => '', 78 - 'getAbuse' => '', 79 - 'getFaq' => '', 80 - 'getRules' => '', 81 - 'getSupport' => '', 77 + '_' => 'ayuda', 78 + 'getAbuse' => 'reportar abuso', 79 + 'getFaq' => 'preguntas frecuentes', 80 + 'getRules' => 'reglas', 81 + 'getSupport' => 'no, en serio, ¡necesito ayuda!', 82 82 ], 83 83 'home' => [ 84 - '_' => '', 85 - 'team' => '', 84 + '_' => 'inicio', 85 + 'team' => 'equipo', 86 86 ], 87 87 'rankings' => [ 88 - '_' => '', 88 + '_' => 'clasificaciones', 89 89 ], 90 90 'store' => [ 91 - '_' => '', 91 + '_' => 'tienda', 92 92 ], 93 93 ], 94 94 95 95 'footer' => [ 96 96 'general' => [ 97 - '_' => '', 98 - 'home' => '', 99 - 'changelog-index' => '', 100 - 'beatmaps' => '', 101 - 'download' => '', 97 + '_' => 'General', 98 + 'home' => 'Inicio', 99 + 'changelog-index' => 'Registro de cambios', 100 + 'beatmaps' => 'Lista de mapas', 101 + 'download' => 'Descargar osu!', 102 102 ], 103 103 'help' => [ 104 - '_' => '', 105 - 'faq' => '', 106 - 'forum' => '', 107 - 'livestreams' => '', 108 - 'report' => '', 109 - 'wiki' => '', 104 + '_' => 'Ayuda y comunidad', 105 + 'faq' => 'Preguntas frecuentes', 106 + 'forum' => 'Foros de la comunidad', 107 + 'livestreams' => 'Transmisiones en vivo', 108 + 'report' => 'Reportar un problema', 109 + 'wiki' => 'Wiki', 110 110 ], 111 111 'legal' => [ 112 - '_' => '', 113 - 'copyright' => '', 112 + '_' => 'Legal y estado', 113 + 'copyright' => 'Derechos de autor (DMCA)', 114 114 'jp_sctl' => '', 115 - 'privacy' => '', 116 - 'server_status' => '', 117 - 'source_code' => '', 118 - 'terms' => '', 115 + 'privacy' => 'Privacidad', 116 + 'server_status' => 'Estado del servidor', 117 + 'source_code' => 'Código fuente', 118 + 'terms' => 'Términos', 119 119 ], 120 120 ], 121 121 122 122 'errors' => [ 123 123 '400' => [ 124 - 'error' => '', 124 + 'error' => 'Parámetro de solicitud no válido', 125 125 'description' => '', 126 126 ], 127 127 '404' => [ 128 - 'error' => '', 129 - 'description' => "", 128 + 'error' => 'Página no encontrada', 129 + 'description' => "¡Lo sentimos, la página que has solicitado no está aquí!", 130 130 ], 131 131 '403' => [ 132 - 'error' => "", 133 - 'description' => '', 132 + 'error' => "No deberías estar aquí.", 133 + 'description' => 'Aunque podrías intentar volver atrás.', 134 134 ], 135 135 '401' => [ 136 - 'error' => "", 137 - 'description' => '', 136 + 'error' => "No deberías estar aquí.", 137 + 'description' => 'Aunque podrías intentar volver atrás. O tal vez iniciar sesión.', 138 138 ], 139 139 '405' => [ 140 - 'error' => '', 141 - 'description' => "", 140 + 'error' => 'Página no encontrada', 141 + 'description' => "¡Lo sentimos, la página que has solicitado no está aquí!", 142 142 ], 143 143 '422' => [ 144 - 'error' => '', 144 + 'error' => 'Parámetro de solicitud no válido', 145 145 'description' => '', 146 146 ], 147 147 '429' => [ 148 - 'error' => '', 148 + 'error' => 'Se ha superado el límite de frecuencia', 149 149 'description' => '', 150 150 ], 151 151 '500' => [ 152 - 'error' => '', 153 - 'description' => "", 152 + 'error' => '¡Oh no! ¡Algo se ha roto! ;_;', 153 + 'description' => "Se nos notifica automáticamente de cada error.", 154 154 ], 155 155 'fatal' => [ 156 - 'error' => '', 157 - 'description' => "", 156 + 'error' => '¡Oh no! ¡Algo se ha roto (gravemente)! ;_;', 157 + 'description' => "Se nos notifica automáticamente de cada error.", 158 158 ], 159 159 '503' => [ 160 - 'error' => '', 161 - 'description' => "", 160 + 'error' => '¡Fuera de servicio por mantenimiento!', 161 + 'description' => "El mantenimiento suele durar entre 5 segundos y 10 minutos. Si permanecemos fuera de servicio durante más tiempo, consulta :link para obtener más información.", 162 162 'link' => [ 163 163 'text' => '', 164 164 'href' => '', 165 165 ], 166 166 ], 167 167 // used by sentry if it returns an error 168 - 'reference' => "", 168 + 'reference' => "Por si acaso, ¡aquí tienes un código que le puedes dar al soporte técnico!", 169 169 ], 170 170 171 171 'popup_login' => [ 172 - 'button' => '', 172 + 'button' => 'iniciar sesión / registrarme', 173 173 174 174 'login' => [ 175 - 'forgot' => "", 176 - 'password' => '', 177 - 'title' => '', 178 - 'username' => '', 175 + 'forgot' => "He olvidado mis datos", 176 + 'password' => 'contraseña', 177 + 'title' => 'Inicia sesión para continuar', 178 + 'username' => 'nombre de usuario', 179 179 180 180 'error' => [ 181 - 'email' => "", 182 - 'password' => '', 181 + 'email' => "El nombre de usuario o la dirección de correo electrónico no existen", 182 + 'password' => 'Contraseña incorrecta', 183 183 ], 184 184 ], 185 185 186 186 'register' => [ 187 - 'download' => '', 188 - 'info' => '', 189 - 'title' => "", 187 + 'download' => 'Descargar', 188 + 'info' => '¡Descarga osu! para crear tu propia cuenta!', 189 + 'title' => "¿No tienes una cuenta?", 190 190 ], 191 191 ], 192 192 193 193 'popup_user' => [ 194 194 'links' => [ 195 - 'account-edit' => '', 196 - 'follows' => '', 197 - 'friends' => '', 198 - 'legacy_score_only_toggle' => '', 199 - 'legacy_score_only_toggle_tooltip' => '', 200 - 'logout' => '', 201 - 'profile' => '', 195 + 'account-edit' => 'Configuración', 196 + 'follows' => 'Listas de seguimiento', 197 + 'friends' => 'Amigos', 198 + 'legacy_score_only_toggle' => 'Modo lazer', 199 + 'legacy_score_only_toggle_tooltip' => 'El modo lazer muestra las puntuaciones establecidas desde lazer con un nuevo algoritmo de puntuación', 200 + 'logout' => 'Cerrar sesión', 201 + 'profile' => 'Mi perfil', 202 202 ], 203 203 ], 204 204 205 205 'popup_search' => [ 206 - 'initial' => '', 207 - 'retry' => '', 206 + 'initial' => '¡Escribe para buscar!', 207 + 'retry' => 'Búsqueda fallida. Haz clic aquí para reintentar.', 208 208 ], 209 209 ];
+11 -11
resources/lang/es-419/legacy_api_key.php
··· 4 4 // See the LICENCE file in the repository root for full licence text. 5 5 6 6 return [ 7 - 'new' => '', 8 - 'none' => '', 7 + 'new' => 'Nueva clave de la API heredada', 8 + 'none' => 'Sin clave.', 9 9 10 10 'docs' => [ 11 - '_' => '', 12 - 'github' => '', 11 + '_' => 'La documentación está disponible en :github.', 12 + 'github' => 'GitHub', 13 13 ], 14 14 15 15 'form' => [ 16 - 'create' => '', 16 + 'create' => 'Crear clave', 17 17 ], 18 18 19 19 'view' => [ 20 - 'hide' => '', 21 - 'show' => '', 22 - 'delete' => '', 20 + 'hide' => 'Ocultar clave', 21 + 'show' => 'Mostrar clave', 22 + 'delete' => 'Eliminar', 23 23 ], 24 24 25 25 'warning' => [ 26 - 'line1' => '', 27 - 'line2' => "", 28 - 'line3' => '', 26 + 'line1' => 'No le des esto a otros.', 27 + 'line2' => "Es equivalente a dar tu contraseña.", 28 + 'line3' => 'Tu cuenta puede verse comprometida.', 29 29 ], 30 30 ];
+10 -10
resources/lang/es-419/legacy_irc_key.php
··· 4 4 // See the LICENCE file in the repository root for full licence text. 5 5 6 6 return [ 7 - 'confirm_new' => '', 8 - 'new' => '', 9 - 'none' => '', 7 + 'confirm_new' => '¿Crear una nueva contraseña para el IRC?', 8 + 'new' => 'Nueva contraseña heredada para el IRC', 9 + 'none' => 'Contraseña del IRC no establecida.', 10 10 11 11 'form' => [ 12 - 'server_host' => '', 13 - 'server_port' => '', 14 - 'token' => '', 15 - 'username' => '', 12 + 'server_host' => 'servidor', 13 + 'server_port' => 'puerto', 14 + 'token' => 'contraseña del servidor', 15 + 'username' => 'nombre de usuario', 16 16 ], 17 17 18 18 'view' => [ 19 - 'hide' => '', 20 - 'show' => '', 21 - 'delete' => '', 19 + 'hide' => 'Ocultar contraseña', 20 + 'show' => 'Mostrar contraseña', 21 + 'delete' => 'Eliminar', 22 22 ], 23 23 ];
+5 -5
resources/lang/es-419/livestreams.php
··· 5 5 6 6 return [ 7 7 'promote' => [ 8 - 'pin' => '', 9 - 'unpin' => "", 8 + 'pin' => '¿Estás seguro de que quieres promocionar esta transmisión en vivo?', 9 + 'unpin' => "¿Seguro que quieres quitar la promoción de esta transmisión en vivo?", 10 10 ], 11 11 12 12 'top-headers' => [ 13 - 'headline' => '', 14 - 'description' => '', 13 + 'headline' => 'Transmisiones en vivo', 14 + 'description' => 'Los datos son obtenidos de twitch.tv cada cinco minutos basándose en la lista del directorio. ¡Siéntete libre de empezar a transmitir y aparecer en la lista! Para obtener más información sobre cómo empezar a transmitir, consulta :link.', 15 15 16 - 'link' => '', 16 + 'link' => 'la página de la wiki sobre las transmisiones en vivo', 17 17 ], 18 18 ];
+11 -11
resources/lang/es-419/mail.php
··· 5 5 6 6 return [ 7 7 'beatmapset_update_notice' => [ 8 - 'new' => '', 9 - 'subject' => '', 10 - 'unwatch' => '', 11 - 'visit' => '', 8 + 'new' => 'Te informamos de que ha habido una nueva actualización en el mapa «:title» desde tu última visita.', 9 + 'subject' => 'Nueva actualización en el mapa «:title»', 10 + 'unwatch' => 'Si ya no quieres recibir más notificaciones de este mapa, puedes hacer clic en el enlace «Dejar de ver» que se encuentra en la página de arriba, o desde la página de la lista de seguimiento de modding:', 11 + 'visit' => 'Visita la página de discusión aquí:', 12 12 ], 13 13 14 14 'common' => [ 15 - 'closing' => '', 16 - 'hello' => '', 17 - 'report' => '', 18 - 'ignore' => '', 15 + 'closing' => 'Saludos,', 16 + 'hello' => 'Hola :user,', 17 + 'report' => 'Responde a este correo INMEDIATAMENTE si no has solicitado este cambio.', 18 + 'ignore' => 'Si no solicitaste esto, puedes ignorar este correo.', 19 19 ], 20 20 21 21 'donation_thanks' => [ 22 - 'benefit_more' => '', 23 - 'feedback' => "", 24 - 'keep_free' => '', 22 + 'benefit_more' => '¡Además, con el tiempo aparecerán nuevas ventajas para los supporters!', 23 + 'feedback' => "Si tienes alguna pregunta o comentario, no dudes en responder a este correo; ¡te responderé lo antes posible!", 24 + 'keep_free' => 'Es gracias a personas como tú que osu! es capaz de mantener el juego y la comunidad funcionando sin ningún tipo de anuncios ni pagos forzados.', 25 25 'keep_running' => '', 26 26 'subject' => '', 27 27 'translation' => '',
+3 -3
resources/lang/es-419/models.php
··· 4 4 // See the LICENCE file in the repository root for full licence text. 5 5 6 6 return [ 7 - 'not_found' => "", 7 + 'not_found' => "No se ha podido encontrar :model especificado.", 8 8 9 9 'name' => [ 10 - 'App\Models\Beatmap' => '', 11 - 'App\Models\Beatmapset' => '', 10 + 'App\Models\Beatmap' => 'la dificultad del mapa', 11 + 'App\Models\Beatmapset' => 'el mapa', 12 12 ], 13 13 ];
+2 -2
resources/lang/es/accounts.php
··· 10 10 11 11 'avatar' => [ 12 12 'title' => 'Avatar', 13 - 'rules' => 'Por favor, asegúrate de que tu avatar se adhiera a :link.<br/>Esto significa que debe ser <strong>adecuado para todas las edades</strong>. Es decir, sin desnudez, profanidad o contenido sugestivo.', 14 - 'rules_link' => 'las reglas de la comunidad', 13 + 'rules' => 'Por favor, asegúrate de que tu avatar se adhiera a :link.<br/>Esto significa que debe ser <strong>adecuado para todas las edades</strong>. Es decir, sin desnudez, contenido ofensivo o sugerente.', 14 + 'rules_link' => 'las consideraciones de contenido visual', 15 15 ], 16 16 17 17 'email' => [
+2 -2
resources/lang/es/common.php
··· 5 5 6 6 return [ 7 7 'confirmation' => '¿Estás seguro?', 8 - 'confirmation_unsaved' => 'Se perderán todos los cambios sin guardar. ¿Está seguro?', 8 + 'confirmation_unsaved' => 'Se perderán todos los cambios sin guardar. ¿Estás seguro?', 9 9 'saved' => 'Guardado', 10 10 11 11 'array_and' => [ ··· 158 158 ], 159 159 160 160 'wrong_user' => [ 161 - '_' => 'Ha iniciado sesión como :user. :logout_link.', 161 + '_' => 'Has iniciado sesión como :user. :logout_link.', 162 162 'logout_link' => 'Haz clic aquí para iniciar sesión como otro usuario', 163 163 ], 164 164 ];
+4 -4
resources/lang/es/community.php
··· 6 6 return [ 7 7 'support' => [ 8 8 'convinced' => [ 9 - 'title' => '¡me has convencido! :D', 9 + 'title' => '¡Me has convencido! :D', 10 10 'support' => 'apoyar a osu!', 11 11 'gift' => 'o regalar supporter a otros jugadores', 12 12 'instructions' => 'haz clic al botón del corazón para proceder a la osu!store', ··· 37 37 'link_text' => 'Explorar torneos &raquo;', 38 38 ], 39 39 'bounty-program' => [ 40 - 'title' => 'Programa de tecompensas de código abierto', 41 - 'description' => 'Apoya a los contribuyentes de la comunidad que han dado su tiempo y esfuerzo para ayudar a hacer que osu! sea mejor.', 40 + 'title' => 'Programa de recompensas de código abierto', 41 + 'description' => 'Apoya a los contribuidores de la comunidad que han dado su tiempo y esfuerzo para ayudar a hacer que osu! sea mejor.', 42 42 'link_text' => 'Descubre más &raquo;', 43 43 ], 44 44 ], ··· 61 61 62 62 'mod_filtering' => [ 63 63 'title' => 'Filtrar por mods', 64 - 'description' => '¿Asociarte solo con personas que juegan con mods HDHR? ¡No hay problema!', 64 + 'description' => '¿Asociarte solo con personas que juegan con HDHR? ¡No hay problema!', 65 65 ], 66 66 67 67 'auto_downloads' => [
+5 -5
resources/lang/es/contest.php
··· 55 55 56 56 'requirement' => [ 57 57 'playlist_beatmapsets' => [ 58 - 'incomplete_play' => 'Debe jugar todos los mapas en las listas de juego especificadas antes de votar', 58 + 'incomplete_play' => 'Debes jugar todos los mapas en las listas de juego especificadas antes de votar', 59 59 ], 60 60 ], 61 61 ], 62 62 63 63 'entry' => [ 64 64 '_' => 'entrada', 65 - 'login_required' => 'Inicie sesión para participar en el concurso.', 66 - 'silenced_or_restricted' => 'No puede participar en los concursos mientras esté restringido o silenciado.', 67 - 'preparation' => 'Estamos preparando este concurso actualmente. Por favor, ¡Espera pacientemente!', 65 + 'login_required' => 'Inicia sesión para participar en el concurso.', 66 + 'silenced_or_restricted' => 'No puedes participar en los concursos mientras estés restringido o silenciado.', 67 + 'preparation' => 'Estamos preparando este concurso actualmente. ¡Espera pacientemente!', 68 68 'drop_here' => 'Suelta tu entrada aquí', 69 69 'download' => 'Descargar archivo .osz', 70 70 ··· 100 100 101 101 'states' => [ 102 102 'entry' => 'Entrada abierta', 103 - 'voting' => 'Votación Iniciada', 103 + 'voting' => 'Votación iniciada', 104 104 'results' => 'Resultados', 105 105 ], 106 106 ];
+1 -1
resources/lang/es/errors.php
··· 6 6 return [ 7 7 'load_failed' => 'Error al cargar los datos.', 8 8 'missing_route' => 'URL no válida o método de solicitud incorrecto.', 9 - 'no_restricted_access' => 'No puede realizar esta acción mientras su cuenta esté en un estado restringido.', 9 + 'no_restricted_access' => 'No puedes realizar esta acción mientras tu cuenta esté en un estado restringido.', 10 10 'supporter_only' => 'Debes ser un osu!supporter para usar esta característica.', 11 11 'unknown' => 'Se produjo un error desconocido.', 12 12
+33 -33
resources/lang/es/forum.php
··· 44 44 ], 45 45 46 46 'post' => [ 47 - 'confirm_destroy' => '¿Realmente desea eliminar la publicación?', 47 + 'confirm_destroy' => '¿Realmente deseas eliminar la publicación?', 48 48 'confirm_restore' => '¿Realmente desea restaurar la publicación?', 49 49 'edited' => 'Última edición por :user :when, editado :count_delimited vez en total.|Última edición por :user :when, editado :count_delimited veces en total.', 50 50 'posted_at' => 'publicado :when', ··· 80 80 'confirm_restore' => '¿Realmente desea restaurar el tema?', 81 81 'deleted' => 'tema eliminado', 82 82 'go_to_latest' => 'ver la última publicación', 83 - 'has_replied' => 'Ha respondido a este tema', 83 + 'has_replied' => 'Has respondido a este tema', 84 84 'in_forum' => 'en :forum', 85 85 'latest_post' => ':when por :user', 86 86 'latest_reply_by' => 'última respuesta por :user', 87 87 'new_topic' => 'Nuevo tema', 88 - 'new_topic_login' => 'Inicie sesión para publicar un nuevo tema', 88 + 'new_topic_login' => 'Inicia sesión para publicar un nuevo tema', 89 89 'post_reply' => 'Publicar', 90 90 'reply_box_placeholder' => 'Escribe aquí para responder', 91 91 'reply_title_prefix' => 'Re', ··· 109 109 'default' => 'Este tema ha estado inactivo durante mucho tiempo. Solo publique aquí si tiene una razón específica para hacerlo.', 110 110 111 111 'new_topic' => [ 112 - '_' => "Este tema ha estado inactivo durante mucho tiempo. Si no tiene una razón específica para publicar aquí, por favor :create en su lugar.", 113 - 'create' => 'cree un nuevo tema', 112 + '_' => "Este tema ha estado inactivo durante mucho tiempo. Si no tienes una razón específica para publicar aquí, por favor :create en su lugar.", 113 + 'create' => 'crea un nuevo tema', 114 114 ], 115 115 ], 116 116 ··· 210 210 '_' => 'Temas', 211 211 212 212 'actions' => [ 213 - 'login_reply' => 'Inicie sesión para responder', 213 + 'login_reply' => 'Inicia sesión para responder', 214 214 'reply' => 'Responder', 215 215 'reply_with_quote' => 'Citar publicación y responder', 216 216 'search' => 'Buscar', ··· 235 235 'max_options' => 'Opciones por usuario', 236 236 'max_options_info' => 'Este es el número de opciones que un usuario puede seleccionar al votar.', 237 237 'options' => 'Opciones', 238 - 'options_info' => 'Escriba cada opción en una nueva línea. Puede introducir hasta 10 opciones.', 238 + 'options_info' => 'Escribe cada opción en una nueva línea. Puedes introducir hasta 10 opciones.', 239 239 'title' => 'Pregunta', 240 240 'vote_change' => 'Permitir volver a votar.', 241 241 'vote_change_info' => 'Si está activado, los usuarios podrán cambiar su voto.', ··· 253 253 ], 254 254 255 255 'issue_tag_added' => [ 256 - 'to_0' => 'Eliminar etiqueta «agregado»', 257 - 'to_0_done' => 'Etiqueta «agregado» eliminada', 258 - 'to_1' => 'Añadir etiqueta «agregado»', 259 - 'to_1_done' => 'Etiqueta «agregado» añadida', 256 + 'to_0' => 'Eliminar etiqueta «added»', 257 + 'to_0_done' => 'Etiqueta «added» eliminada', 258 + 'to_1' => 'Añadir etiqueta «added»', 259 + 'to_1_done' => 'Etiqueta «added» añadida', 260 260 ], 261 261 262 262 'issue_tag_assigned' => [ 263 - 'to_0' => 'Eliminar etiqueta «asignado»', 264 - 'to_0_done' => 'Etiqueta «asignado» eliminada', 265 - 'to_1' => 'Añadir etiqueta «asignado»', 266 - 'to_1_done' => 'Etiqueta «asignado» añadida', 263 + 'to_0' => 'Eliminar etiqueta «assigned»', 264 + 'to_0_done' => 'Etiqueta «assigned» eliminada', 265 + 'to_1' => 'Añadir etiqueta «assigned»', 266 + 'to_1_done' => 'Etiqueta «assigned» añadida', 267 267 ], 268 268 269 269 'issue_tag_confirmed' => [ 270 - 'to_0' => 'Eliminar etiqueta «confirmado»', 271 - 'to_0_done' => 'Etiqueta «confirmado» eliminada', 272 - 'to_1' => 'Añadir etiqueta «confirmado»', 273 - 'to_1_done' => 'Etiqueta «confirmado» añadida', 270 + 'to_0' => 'Eliminar etiqueta «confirmed»', 271 + 'to_0_done' => 'Etiqueta «confirmed» eliminada', 272 + 'to_1' => 'Añadir etiqueta «confirmed»', 273 + 'to_1_done' => 'Etiqueta «confirmed» añadida', 274 274 ], 275 275 276 276 'issue_tag_duplicate' => [ 277 - 'to_0' => 'Eliminar etiqueta «duplicado»', 278 - 'to_0_done' => 'Etiqueta «duplicado» eliminada', 279 - 'to_1' => 'Añadir etiqueta «duplicado»', 280 - 'to_1_done' => 'Etiqueta «duplicado» añadida', 277 + 'to_0' => 'Eliminar etiqueta «duplicate»', 278 + 'to_0_done' => 'Etiqueta «duplicate» eliminada', 279 + 'to_1' => 'Añadir etiqueta «duplicate»', 280 + 'to_1_done' => 'Etiqueta «duplicate» añadida', 281 281 ], 282 282 283 283 'issue_tag_invalid' => [ 284 - 'to_0' => 'Eliminar etiqueta «inválido»', 285 - 'to_0_done' => 'Etiqueta «inválido» eliminada', 286 - 'to_1' => 'Añadir etiqueta «inválido»', 287 - 'to_1_done' => 'Etiqueta «inválido» añadida', 284 + 'to_0' => 'Eliminar etiqueta «invalid»', 285 + 'to_0_done' => 'Etiqueta «invalid» eliminada', 286 + 'to_1' => 'Añadir etiqueta «invalid»', 287 + 'to_1_done' => 'Etiqueta «invalid» añadida', 288 288 ], 289 289 290 290 'issue_tag_resolved' => [ 291 - 'to_0' => 'Eliminar etiqueta «resuelto»', 292 - 'to_0_done' => 'Etiqueta «resuelto» eliminada', 293 - 'to_1' => 'Añadir etiqueta «resuelto»', 294 - 'to_1_done' => 'Etiqueta «resuelto» añadida', 291 + 'to_0' => 'Eliminar etiqueta «resolved»', 292 + 'to_0_done' => 'Etiqueta «resolved» eliminada', 293 + 'to_1' => 'Añadir etiqueta «resolved»', 294 + 'to_1_done' => 'Etiqueta «resolved» añadida', 295 295 ], 296 296 297 297 'lock' => [ ··· 371 371 'to_not_watching' => 'No marcado', 372 372 'to_watching' => 'Marcado', 373 373 'to_watching_mail' => 'Marcado con aviso de notificaciones', 374 - 'tooltip_mail_disable' => 'Notificación activada. Haga clic para desactivar', 375 - 'tooltip_mail_enable' => 'Notificación desactivada. Haga clic para activar', 374 + 'tooltip_mail_disable' => 'Las notificaciones están activadas. Haz clic para desactivarlas', 375 + 'tooltip_mail_enable' => 'Las notificaciones están desactivadas. Haz clic para activarlas', 376 376 ], 377 377 ], 378 378 ];
+1 -1
resources/lang/es/layout.php
··· 185 185 186 186 'register' => [ 187 187 'download' => 'Descargar', 188 - 'info' => '¡Descargue osu! para crear su propia cuenta!', 188 + 'info' => '¡Descarga osu! para crear tu propia cuenta!', 189 189 'title' => "¿No tienes una cuenta?", 190 190 ], 191 191 ],
+2 -2
resources/lang/es/livestreams.php
··· 5 5 6 6 return [ 7 7 'promote' => [ 8 - 'pin' => '¿Seguro que desea promocionar esta transmisión en vivo?', 8 + 'pin' => '¿Seguro que deseas promocionar esta transmisión en vivo?', 9 9 'unpin' => "¿Seguro que quieres quitar la promoción de esta transmisión en vivo?", 10 10 ], 11 11 12 12 'top-headers' => [ 13 13 'headline' => 'Transmisiones en vivo', 14 - 'description' => 'Los datos son obtenidos de twitch.tv cada cinco minutos basados en el listado del directorio. ¡Siéntase libre de empezar a transmitir y de aparecer en la lista! Para obtener más información sobre cómo empezar a transmitir, revise :link.', 14 + 'description' => 'Los datos son obtenidos de twitch.tv cada cinco minutos basados en el listado del directorio. ¡Siéntete libre de empezar a transmitir y de aparecer en la lista! Para obtener más información sobre cómo empezar a transmitir, revisa :link.', 15 15 16 16 'link' => 'la página de la wiki de transmisiones en vivo', 17 17 ],
+3 -3
resources/lang/es/models.php
··· 4 4 // See the LICENCE file in the repository root for full licence text. 5 5 6 6 return [ 7 - 'not_found' => "", 7 + 'not_found' => "No se ha podido encontrar :model especificado.", 8 8 9 9 'name' => [ 10 - 'App\Models\Beatmap' => '', 11 - 'App\Models\Beatmapset' => '', 10 + 'App\Models\Beatmap' => 'la dificultad del mapa', 11 + 'App\Models\Beatmapset' => 'el mapa', 12 12 ], 13 13 ];
+2 -2
resources/lang/fi/models.php
··· 7 7 'not_found' => "", 8 8 9 9 'name' => [ 10 - 'App\Models\Beatmap' => '', 11 - 'App\Models\Beatmapset' => '', 10 + 'App\Models\Beatmap' => 'rytmikartan vaikeustaso', 11 + 'App\Models\Beatmapset' => 'rytmikartta', 12 12 ], 13 13 ];
+2 -2
resources/lang/fr/accounts.php
··· 10 10 11 11 'avatar' => [ 12 12 'title' => 'Avatar', 13 - 'rules' => 'Veuillez vous assurer que votre avatar correspond aux :link.<br/>Cela signifie qu\'il doit être <strong>adapté à tous les âges</strong>. c\'est-à-dire pas de nudité, de profanation ou de contenu suggestif.', 14 - 'rules_link' => 'règles de la communauté', 13 + 'rules' => 'Veuillez vous assurer que votre avatar correspond aux :link.<br/>Cela signifie qu\'il doit être <strong>adapté à tous les âges</strong>. C\'est-à-dire pas de nudité, de profanation ou de contenu suggestif.', 14 + 'rules_link' => 'Considérations relatives au contenu visuel', 15 15 ], 16 16 17 17 'email' => [
+3 -3
resources/lang/fr/models.php
··· 4 4 // See the LICENCE file in the repository root for full licence text. 5 5 6 6 return [ 7 - 'not_found' => "", 7 + 'not_found' => "La :model spécifiée n'a pas pu être trouvée.", 8 8 9 9 'name' => [ 10 - 'App\Models\Beatmap' => '', 11 - 'App\Models\Beatmapset' => '', 10 + 'App\Models\Beatmap' => 'difficulté', 11 + 'App\Models\Beatmapset' => 'beatmap', 12 12 ], 13 13 ];
+2 -2
resources/lang/he/accounts.php
··· 38 38 ], 39 39 40 40 'profile' => [ 41 - 'country' => '', 41 + 'country' => 'מדינה', 42 42 'title' => 'פרופיל', 43 43 44 44 'country_change' => [ ··· 65 65 'github_user' => [ 66 66 'info' => "", 67 67 'link' => '', 68 - 'title' => '', 68 + 'title' => 'גיטהאב', 69 69 'unlink' => '', 70 70 71 71 'error' => [
+3 -3
resources/lang/hu/models.php
··· 4 4 // See the LICENCE file in the repository root for full licence text. 5 5 6 6 return [ 7 - 'not_found' => "", 7 + 'not_found' => "Megadott :model nem található.", 8 8 9 9 'name' => [ 10 - 'App\Models\Beatmap' => '', 11 - 'App\Models\Beatmapset' => '', 10 + 'App\Models\Beatmap' => 'beatmap nehézség', 11 + 'App\Models\Beatmapset' => 'beatmap', 12 12 ], 13 13 ];
+3 -3
resources/lang/id/models.php
··· 4 4 // See the LICENCE file in the repository root for full licence text. 5 5 6 6 return [ 7 - 'not_found' => "", 7 + 'not_found' => ":model yang ditentukan tidak dapat ditemukan.", 8 8 9 9 'name' => [ 10 - 'App\Models\Beatmap' => '', 11 - 'App\Models\Beatmapset' => '', 10 + 'App\Models\Beatmap' => 'tingkat kesulitan beatmap', 11 + 'App\Models\Beatmapset' => 'beatmap', 12 12 ], 13 13 ];
+9 -9
resources/lang/kk-KZ/events.php
··· 10 10 'beatmapset_delete' => ':beatmapset жойылды.', 11 11 'beatmapset_revive' => '', 12 12 'beatmapset_update' => '', 13 - 'beatmapset_upload' => '', 14 - 'empty' => "", 15 - 'rank' => '', 16 - 'rank_lost' => '', 17 - 'user_support_again' => '', 18 - 'user_support_first' => '', 19 - 'user_support_gift' => '', 20 - 'username_change' => '', 13 + 'beatmapset_upload' => '<strong><em>:user</em></strong> жаңа ":beatmapset" картаны жүктеді', 14 + 'empty' => "Осы пайдаланушы жақында маңызды ештеңе жасаған жоқ!", 15 + 'rank' => ':beatmap (:mode) картасында :user :rank алды', 16 + 'rank_lost' => '<strong><em>:user</em></strong> <em>:beatmap</em> (:mode) картасында бірінші орынды жоғалтты', 17 + 'user_support_again' => '<strong>:user</strong> тағы да osu!-ды қолдады - колдауыңызға рахмет!', 18 + 'user_support_first' => '<strong>:user</strong> osu!-ды қолдады - қолдауыңызға рахмет!', 19 + 'user_support_gift' => '<strong>:user</strong> osu!supporter сыйлық ретінде алды!', 20 + 'username_change' => '<strong>:previousUsername</strong> пайдаланушы атын <strong><em>:user</em></strong>-ға өзгертті!', 21 21 22 22 'beatmapset_status' => [ 23 23 'approved' => 'қабылданған', ··· 27 27 ], 28 28 29 29 'value' => [ 30 - 'rank' => '', 30 + 'rank' => '#:rank орын', 31 31 ], 32 32 ];
+16 -16
resources/lang/kk-KZ/forum.php
··· 4 4 // See the LICENCE file in the repository root for full licence text. 5 5 6 6 return [ 7 - 'pinned_topics' => '', 8 - 'slogan' => "", 7 + 'pinned_topics' => 'Бекітілген тақырыптар', 8 + 'slogan' => "жалғыз ойнау қауіпті. ", 9 9 'subforums' => '', 10 10 'title' => 'Форум', 11 11 12 12 'covers' => [ 13 - 'edit' => '', 13 + 'edit' => 'Мұқабаны өзгерту', 14 14 15 15 'create' => [ 16 - '_' => '', 17 - 'button' => '', 16 + '_' => 'Мұқаба сүретін қою', 17 + 'button' => 'Мұқабаны жүктеу', 18 18 'info' => '', 19 19 ], 20 20 21 21 'destroy' => [ 22 - '_' => '', 22 + '_' => 'Мұқабаны жою', 23 23 'confirm' => '', 24 24 ], 25 25 ], 26 26 27 27 'forums' => [ 28 - 'forums' => '', 29 - 'latest_post' => '', 28 + 'forums' => 'Форум', 29 + 'latest_post' => 'Соңғы жазба', 30 30 31 31 'index' => [ 32 32 'title' => '', ··· 46 46 'post' => [ 47 47 'confirm_destroy' => 'Жазбаны жою?', 48 48 'confirm_restore' => 'Жазбаны қалпына келтіру?', 49 - 'edited' => '', 50 - 'posted_at' => '', 51 - 'posted_by_in' => '', 49 + 'edited' => 'Соңғы рет :user :when өзгертті, жалпы :count_delimited рет өзгертілген.', 50 + 'posted_at' => ':when жарияланған ', 51 + 'posted_by_in' => ':forum-да :username жариялаған', 52 52 53 53 'actions' => [ 54 54 'destroy' => 'Постты жою', ··· 72 72 'search' => [ 73 73 'go_to_post' => 'Жазбаға өту', 74 74 'post_number_input' => 'жазба номерін жазыңыз ', 75 - 'total_posts' => '', 75 + 'total_posts' => 'барлығы :posts_count жазба', 76 76 ], 77 77 78 78 'topic' => [ 79 79 'confirm_destroy' => '', 80 80 'confirm_restore' => '', 81 - 'deleted' => '', 82 - 'go_to_latest' => '', 83 - 'has_replied' => '', 84 - 'in_forum' => '', 81 + 'deleted' => 'жойылған тақырып', 82 + 'go_to_latest' => 'соңғы жазбаны қарау', 83 + 'has_replied' => 'Сіз осы тақырыпқа жауап бердіңіз', 84 + 'in_forum' => ':forum-да', 85 85 'latest_post' => '', 86 86 'latest_reply_by' => '', 87 87 'new_topic' => 'Жаңа тақырып',
+1 -1
resources/lang/kk-KZ/layout.php
··· 82 82 ], 83 83 'home' => [ 84 84 '_' => 'басты бет', 85 - 'team' => '', 85 + 'team' => 'команда', 86 86 ], 87 87 'rankings' => [ 88 88 '_' => '',
+1 -1
resources/lang/kk-KZ/matches.php
··· 11 11 'in-progress' => '', 12 12 'in_progress_spinner_label' => '', 13 13 'loading-events' => '', 14 - 'winner' => '', 14 + 'winner' => ':team жеңді', 15 15 'winner_by' => '', 16 16 17 17 'events' => [
+1 -1
resources/lang/kk-KZ/tournament.php
··· 23 23 ], 24 24 25 25 'show' => [ 26 - 'banner' => '', 26 + 'banner' => 'Командаңызды қолдаңыз', 27 27 'entered' => '', 28 28 'info_page' => '', 29 29 'login_to_register' => '',
+3 -3
resources/lang/ro/models.php
··· 4 4 // See the LICENCE file in the repository root for full licence text. 5 5 6 6 return [ 7 - 'not_found' => "", 7 + 'not_found' => ":model specificat nu a putut fi găsit(ă).", 8 8 9 9 'name' => [ 10 - 'App\Models\Beatmap' => '', 11 - 'App\Models\Beatmapset' => '', 10 + 'App\Models\Beatmap' => 'Dificultatea beatmap-ului', 11 + 'App\Models\Beatmapset' => 'Beatmap-ul', 12 12 ], 13 13 ];
+2 -2
resources/lang/ru/accounts.php
··· 10 10 11 11 'avatar' => [ 12 12 'title' => 'Аватар', 13 - 'rules' => 'Пожалуйста, убедитесь, что ваш аватар соответствует :link.<br/>Это означает, что он обязан <strong>подходить для всех возрастов</strong>, т.е. не должен содержать наготы, ненормативной лексики или вызывающего контента.', 14 - 'rules_link' => 'правилам сообщества', 13 + 'rules' => 'Убедитесь, что ваш аватар соответствует :link.<br/>Это означает, что он обязан <strong>подходить для всех возрастов</strong>, т.е. не должен содержать наготы, ненормативной лексики или вызывающего контента.', 14 + 'rules_link' => 'критериям визуального содержания', 15 15 ], 16 16 17 17 'email' => [
+3 -3
resources/lang/ru/models.php
··· 4 4 // See the LICENCE file in the repository root for full licence text. 5 5 6 6 return [ 7 - 'not_found' => "", 7 + 'not_found' => "Указанная :model не найдена.", 8 8 9 9 'name' => [ 10 - 'App\Models\Beatmap' => '', 11 - 'App\Models\Beatmapset' => '', 10 + 'App\Models\Beatmap' => 'сложность карты', 11 + 'App\Models\Beatmapset' => 'карта', 12 12 ], 13 13 ];
+1 -1
resources/lang/sr/accounts.php
··· 20 20 'title' => 'Имејл', 21 21 'locked' => [ 22 22 '_' => 'Molimo kontaktirajte :accounts ukoliko treba da ažurirate Vašu imejl adresu.', 23 - 'accounts' => 'tim za podršku naloga', 23 + 'accounts' => 'тим за подршку налога', 24 24 ], 25 25 ], 26 26
+3 -3
resources/lang/vi/models.php
··· 4 4 // See the LICENCE file in the repository root for full licence text. 5 5 6 6 return [ 7 - 'not_found' => "", 7 + 'not_found' => "Không thể tìm thấy :model được chỉ định.", 8 8 9 9 'name' => [ 10 - 'App\Models\Beatmap' => '', 11 - 'App\Models\Beatmapset' => '', 10 + 'App\Models\Beatmap' => 'độ khó beatmap', 11 + 'App\Models\Beatmapset' => 'beatmap', 12 12 ], 13 13 ];
+3 -3
resources/lang/zh-tw/models.php
··· 4 4 // See the LICENCE file in the repository root for full licence text. 5 5 6 6 return [ 7 - 'not_found' => "", 7 + 'not_found' => "找不到指定的 :model。", 8 8 9 9 'name' => [ 10 - 'App\Models\Beatmap' => '', 11 - 'App\Models\Beatmapset' => '', 10 + 'App\Models\Beatmap' => '圖譜難度', 11 + 'App\Models\Beatmapset' => '圖譜', 12 12 ], 13 13 ];
+3 -3
resources/lang/zh/models.php
··· 4 4 // See the LICENCE file in the repository root for full licence text. 5 5 6 6 return [ 7 - 'not_found' => "", 7 + 'not_found' => "无法找到指定的 :model。", 8 8 9 9 'name' => [ 10 - 'App\Models\Beatmap' => '', 11 - 'App\Models\Beatmapset' => '', 10 + 'App\Models\Beatmap' => '谱面难度', 11 + 'App\Models\Beatmapset' => '谱面', 12 12 ], 13 13 ];
+1 -1
resources/views/accounts/_edit_entry_simple.blade.php
··· 6 6 $user = auth()->user(); 7 7 $value = $user->$field; 8 8 @endphp 9 - <div class="account-edit-entry js-account-edit js-form-error js-form-error--field" data-account-edit-auto-submit="1" data-skip-ajax-error-popup="1"> 9 + <div class="account-edit-entry js-account-edit js-account-edit-auto-submit js-form-error js-form-error--field" data-skip-ajax-error-popup="1"> 10 10 <input 11 11 class="account-edit-entry__input js-account-edit__input" 12 12 name="user[{{ $field }}]"
+4 -8
resources/views/accounts/_edit_notifications.blade.php
··· 16 16 <div class="account-edit__input-groups"> 17 17 <div class="account-edit__input-group"> 18 18 <div 19 - class="account-edit-entry account-edit-entry--no-label js-account-edit" 20 - data-account-edit-auto-submit="1" 19 + class="account-edit-entry account-edit-entry--no-label js-account-edit js-account-edit-auto-submit" 21 20 data-url="{{ route('account.options') }}" 22 21 > 23 22 <label class="account-edit-entry__checkbox"> ··· 38 37 </div> 39 38 40 39 <div 41 - class="account-edit-entry account-edit-entry--no-label js-account-edit" 42 - data-account-edit-auto-submit="1" 40 + class="account-edit-entry account-edit-entry--no-label js-account-edit js-account-edit-auto-submit" 43 41 data-url="{{ route('account.notification-options') }}" 44 42 > 45 43 <label class="account-edit-entry__checkbox"> ··· 72 70 {{ osu_trans("accounts.notifications.$notificationType") }} 73 71 </div> 74 72 <form 75 - class="account-edit-entry__checkboxes js-account-edit" 76 - data-account-edit-auto-submit="1" 73 + class="account-edit-entry__checkboxes js-account-edit js-account-edit-auto-submit" 77 74 data-account-edit-type="array" 78 75 data-url="{{ route('account.notification-options') }}" 79 76 data-field="{{ "user_notification_option[{$notificationType}][details][modes]" }}" ··· 110 107 </div> 111 108 112 109 <form 113 - class="account-edit-entry__delivery-options js-account-edit" 114 - data-account-edit-auto-submit="1" 110 + class="account-edit-entry__delivery-options js-account-edit js-account-edit-auto-submit" 115 111 data-account-edit-type="multi" 116 112 data-url="{{ route('account.notification-options') }}" 117 113 >
+9 -5
resources/views/accounts/_edit_options.blade.php
··· 20 20 {{ osu_trans('accounts.options.beatmapset_download._') }} 21 21 </div> 22 22 <form 23 - class="account-edit-entry__checkboxes account-edit-entry__checkboxes--vertical js-account-edit" 24 - data-account-edit-auto-submit="1" 23 + class="account-edit-entry__checkboxes account-edit-entry__checkboxes--vertical js-account-edit js-account-edit-auto-submit" 25 24 data-account-edit-type="radio" 26 25 data-url="{{ route('account.options') }}" 27 26 data-field="user_profile_customization[beatmapset_download]" ··· 64 63 65 64 <div class="account-edit__input-group"> 66 65 <div 67 - class="account-edit-entry account-edit-entry--no-label js-account-edit js-user-preferences-update" 68 - data-account-edit-auto-submit="1" 66 + class="account-edit-entry account-edit-entry--no-label js-account-edit js-account-edit-auto-submit" 69 67 data-url="{{ route('account.options') }}" 68 + data-user-preferences-update="1" 70 69 > 71 70 <label class="account-edit-entry__checkbox"> 72 71 @include('objects._switch', ['locals' => [ ··· 87 86 </div> 88 87 89 88 <div class="account-edit__input-group"> 90 - <div class="account-edit-entry account-edit-entry--no-label js-account-edit js-user-preferences-update" data-url="{{ route('account.options') }}" data-account-edit-auto-submit="1" data-skip-ajax-error-popup="1"> 89 + <div 90 + class="account-edit-entry account-edit-entry--no-label js-account-edit js-account-edit-auto-submit" 91 + data-url="{{ route('account.options') }}" 92 + data-skip-ajax-error-popup="1" 93 + data-user-preferences-update="1" 94 + > 91 95 <label class="account-edit-entry__checkbox"> 92 96 @include('objects._switch', ['locals' => [ 93 97 'additionalClass'=> 'js-account-edit__input',
+1 -2
resources/views/accounts/_edit_playstyles.blade.php
··· 13 13 <div class="account-edit__input-group"> 14 14 <div class="account-edit-entry account-edit-entry--no-label"> 15 15 <form 16 - class="account-edit-entry__checkboxes js-account-edit" 17 - data-account-edit-auto-submit="1" 16 + class="account-edit-entry__checkboxes js-account-edit js-account-edit-auto-submit" 18 17 data-account-edit-type="array" 19 18 data-url="{{ route('account.options') }}" 20 19 data-field="user[osu_playstyle]"
+2 -4
resources/views/accounts/_edit_privacy.blade.php
··· 12 12 <div class="account-edit__input-groups"> 13 13 <div class="account-edit__input-group"> 14 14 <div 15 - class="account-edit-entry account-edit-entry--no-label js-account-edit" 16 - data-account-edit-auto-submit="1" 15 + class="account-edit-entry account-edit-entry--no-label js-account-edit js-account-edit-auto-submit" 17 16 data-url="{{ route('account.options') }}" 18 17 > 19 18 <label class="account-edit-entry__checkbox"> ··· 34 33 </div> 35 34 36 35 <div 37 - class="account-edit-entry account-edit-entry--no-label js-account-edit" 38 - data-account-edit-auto-submit="1" 36 + class="account-edit-entry account-edit-entry--no-label js-account-edit js-account-edit-auto-submit" 39 37 data-url="{{ route('account.options') }}" 40 38 > 41 39 <label class="account-edit-entry__checkbox">
+3 -4
resources/views/objects/_captcha_script.blade.php
··· 3 3 See the LICENCE file in the repository root for full licence text. 4 4 --}} 5 5 {{-- 6 - we're explicitly avoiding NoCaptcha::renderJs here in order to use recaptcha.net instead of google.com (as the latter is blocked in mainland china) 7 - see: https://developers.google.com/recaptcha/docs/faq#can-i-use-recaptcha-globally 6 + use turbolinks initialiser 8 7 --}} 9 8 <script> 10 - osuCore.turbolinksReload.load('https://www.recaptcha.net/recaptcha/api.js?render=explicit&onload=initCaptcha&hl={{Lang::getLocale()}}'); 11 - function initCaptcha() { osuCore.captcha.init('{{$GLOBALS['cfg']['captcha']['sitekey']}}'); } 9 + osuCore.turbolinksReload.load('https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit&onload=initCaptcha'); 10 + function initCaptcha() { osuCore.captcha.init('{{ $GLOBALS['cfg']['turnstile']['site_key'] }}'); } 12 11 </script>
+14 -14
tests/Models/Multiplayer/UserScoreAggregateTest.php
··· 26 26 27 27 // first play 28 28 $scoreLink = $this->addPlay($user, $playlistItem, [ 29 - 'accuracy' => 1, 29 + 'accuracy' => 0.5, 30 30 'passed' => true, 31 31 'total_score' => 10, 32 32 ]); 33 33 34 34 $agg = UserScoreAggregate::new($user, $this->room); 35 35 $this->assertSame(1, $agg->completed); 36 - $this->assertSame(1.0, $agg->accuracy); 36 + $this->assertSame(0.5, $agg->accuracy); 37 37 $this->assertSame(10, $agg->total_score); 38 38 $this->assertSame($scoreLink->getKey(), $agg->last_score_id); 39 39 40 40 // second, higher score play 41 41 $scoreLink2 = $this->addPlay($user, $playlistItem, [ 42 - 'accuracy' => 2, 42 + 'accuracy' => 1, 43 43 'passed' => true, 44 44 'total_score' => 100, 45 45 ]); 46 46 47 47 $agg->refresh(); 48 48 $this->assertSame(1, $agg->completed); 49 - $this->assertSame(2.0, $agg->accuracy); 49 + $this->assertSame(1.0, $agg->accuracy); 50 50 $this->assertSame(100, $agg->total_score); 51 51 $this->assertSame($scoreLink2->getKey(), $agg->last_score_id); 52 52 } ··· 58 58 59 59 // first play 60 60 $scoreLink = $this->addPlay($user, $playlistItem, [ 61 - 'accuracy' => 1, 61 + 'accuracy' => 0.5, 62 62 'passed' => true, 63 63 'total_score' => 10, 64 64 ]); 65 65 66 66 $agg = UserScoreAggregate::new($user, $this->room); 67 67 $this->assertSame(1, $agg->completed); 68 - $this->assertSame(1.0, $agg->accuracy); 68 + $this->assertSame(0.5, $agg->accuracy); 69 69 $this->assertSame(10, $agg->total_score); 70 70 $this->assertSame($scoreLink->getKey(), $agg->last_score_id); 71 71 72 72 // second, lower score play 73 73 $this->addPlay($user, $playlistItem, [ 74 - 'accuracy' => 2, 74 + 'accuracy' => 1, 75 75 'passed' => true, 76 76 'total_score' => 1, 77 77 ]); 78 78 79 79 $agg->refresh(); 80 80 $this->assertSame(1, $agg->completed); 81 - $this->assertSame(1.0, $agg->accuracy); 81 + $this->assertSame(0.5, $agg->accuracy); 82 82 $this->assertSame(10, $agg->total_score); 83 83 $this->assertSame($scoreLink->getKey(), $agg->last_score_id); 84 84 } ··· 91 91 92 92 // first playlist item 93 93 $this->addPlay($user, $playlistItem, [ 94 - 'accuracy' => 1, 94 + 'accuracy' => 0.5, 95 95 'passed' => true, 96 96 'total_score' => 10, 97 97 ]); 98 98 99 99 $agg = UserScoreAggregate::new($user, $this->room); 100 100 $this->assertSame(1, $agg->completed); 101 - $this->assertSame(1.0, $agg->accuracy); 102 - $this->assertSame(1.0, $agg->averageAccuracy()); 101 + $this->assertSame(0.5, $agg->accuracy); 102 + $this->assertSame(0.5, $agg->averageAccuracy()); 103 103 $this->assertSame(10, $agg->total_score); 104 104 105 105 // second playlist item 106 106 $scoreLink = $this->addPlay($user, $playlistItem2, [ 107 - 'accuracy' => 2, 107 + 'accuracy' => 1, 108 108 'passed' => true, 109 109 'total_score' => 100, 110 110 ]); 111 111 112 112 $agg->refresh(); 113 113 $this->assertSame(2, $agg->completed); 114 - $this->assertSame(3.0, $agg->accuracy); 115 - $this->assertSame(1.5, $agg->averageAccuracy()); 114 + $this->assertSame(1.5, $agg->accuracy); 115 + $this->assertSame(0.75, $agg->averageAccuracy()); 116 116 $this->assertSame(110, $agg->total_score); 117 117 $this->assertSame($scoreLink->getKey(), $agg->last_score_id); 118 118 }
+12 -73
yarn.lock
··· 9 9 dependencies: 10 10 "@babel/highlight" "^7.10.4" 11 11 12 - "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.16.7": 13 - version "7.22.10" 14 - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.10.tgz#1c20e612b768fefa75f6e90d6ecb86329247f0a3" 15 - integrity sha512-/KKIMG4UEL35WmI9OlvMhurwtytjvXoFcGNrOvyG9zIzA8YmPjVtIZUf7b05+TPO7G7/GEmLHDaoCgACHl9hhA== 16 - dependencies: 17 - "@babel/highlight" "^7.22.10" 18 - chalk "^2.4.2" 19 - 20 - "@babel/code-frame@^7.22.13": 12 + "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.16.7", "@babel/code-frame@^7.22.13": 21 13 version "7.22.13" 22 14 resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.13.tgz#e3c1c099402598483b7a8c46a721d1038803755e" 23 15 integrity sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w== ··· 47 39 semver "^5.4.1" 48 40 source-map "^0.5.0" 49 41 50 - "@babel/generator@^7.11.4": 51 - version "7.11.4" 52 - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.11.4.tgz#1ec7eec00defba5d6f83e50e3ee72ae2fee482be" 53 - integrity sha512-Rn26vueFx0eOoz7iifCN2UHT6rGtnkSGWSoDRIy8jZN3B91PzeSULbswfLoOWuTuAcNwpG/mxy+uCTDnZ9Mp1g== 54 - dependencies: 55 - "@babel/types" "^7.11.0" 56 - jsesc "^2.5.1" 57 - source-map "^0.5.0" 58 - 59 - "@babel/generator@^7.23.0": 42 + "@babel/generator@^7.11.4", "@babel/generator@^7.23.0": 60 43 version "7.23.0" 61 44 resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.0.tgz#df5c386e2218be505b34837acbcb874d7a983420" 62 45 integrity sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g== ··· 138 121 "@babel/template" "^7.10.4" 139 122 "@babel/types" "^7.10.4" 140 123 141 - "@babel/helper-split-export-declaration@^7.11.0": 142 - version "7.11.0" 143 - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz#f8a491244acf6a676158ac42072911ba83ad099f" 144 - integrity sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg== 145 - dependencies: 146 - "@babel/types" "^7.11.0" 147 - 148 - "@babel/helper-split-export-declaration@^7.22.6": 124 + "@babel/helper-split-export-declaration@^7.11.0", "@babel/helper-split-export-declaration@^7.22.6": 149 125 version "7.22.6" 150 126 resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c" 151 127 integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g== ··· 157 133 resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f" 158 134 integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw== 159 135 160 - "@babel/helper-validator-identifier@^7.10.4", "@babel/helper-validator-identifier@^7.22.5": 161 - version "7.22.5" 162 - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz#9544ef6a33999343c8740fa51350f30eeaaaf193" 163 - integrity sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ== 164 - 165 136 "@babel/helper-validator-identifier@^7.22.20": 166 137 version "7.22.20" 167 138 resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" ··· 176 147 "@babel/traverse" "^7.10.4" 177 148 "@babel/types" "^7.10.4" 178 149 179 - "@babel/highlight@^7.10.4", "@babel/highlight@^7.22.10": 180 - version "7.22.10" 181 - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.10.tgz#02a3f6d8c1cb4521b2fd0ab0da8f4739936137d7" 182 - integrity sha512-78aUtVcT7MUscr0K5mIEnkwxPE0MaxkR5RxRwuHaQ+JuU5AmTPhY+do2mdzVTnIJJpyBglql2pehuBIWHug+WQ== 183 - dependencies: 184 - "@babel/helper-validator-identifier" "^7.22.5" 185 - chalk "^2.4.2" 186 - js-tokens "^4.0.0" 187 - 188 - "@babel/highlight@^7.22.13": 150 + "@babel/highlight@^7.10.4", "@babel/highlight@^7.22.13": 189 151 version "7.22.20" 190 152 resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.20.tgz#4ca92b71d80554b01427815e06f2df965b9c1f54" 191 153 integrity sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg== ··· 194 156 chalk "^2.4.2" 195 157 js-tokens "^4.0.0" 196 158 197 - "@babel/parser@^7.10.4", "@babel/parser@^7.11.4", "@babel/parser@^7.7.5": 198 - version "7.11.4" 199 - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.11.4.tgz#6fa1a118b8b0d80d0267b719213dc947e88cc0ca" 200 - integrity sha512-MggwidiH+E9j5Sh8pbrX5sJvMcsqS5o+7iB42M9/k0CD63MjYbdP4nhSh7uB5wnv2/RVzTZFTxzF/kIa5mrCqA== 201 - 202 - "@babel/parser@^7.22.15", "@babel/parser@^7.23.0": 159 + "@babel/parser@^7.11.4", "@babel/parser@^7.22.15", "@babel/parser@^7.23.0", "@babel/parser@^7.7.5": 203 160 version "7.23.0" 204 161 resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.0.tgz#da950e622420bf96ca0d0f2909cdddac3acd8719" 205 162 integrity sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw== ··· 211 168 dependencies: 212 169 regenerator-runtime "^0.13.11" 213 170 214 - "@babel/template@^7.10.4", "@babel/template@^7.7.4": 215 - version "7.10.4" 216 - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.4.tgz#3251996c4200ebc71d1a8fc405fba940f36ba278" 217 - integrity sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA== 218 - dependencies: 219 - "@babel/code-frame" "^7.10.4" 220 - "@babel/parser" "^7.10.4" 221 - "@babel/types" "^7.10.4" 222 - 223 - "@babel/template@^7.22.15": 171 + "@babel/template@^7.10.4", "@babel/template@^7.22.15", "@babel/template@^7.7.4": 224 172 version "7.22.15" 225 173 resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38" 226 174 integrity sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w== ··· 245 193 debug "^4.1.0" 246 194 globals "^11.1.0" 247 195 248 - "@babel/types@^7.10.4", "@babel/types@^7.11.0": 249 - version "7.11.0" 250 - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.11.0.tgz#2ae6bf1ba9ae8c3c43824e5861269871b206e90d" 251 - integrity sha512-O53yME4ZZI0jO1EVGtF1ePGl0LHirG4P1ibcD80XyzZcKhcMFeCXmh4Xb1ifGBIV233Qg12x4rBfQgA+tmOukA== 252 - dependencies: 253 - "@babel/helper-validator-identifier" "^7.10.4" 254 - lodash "^4.17.19" 255 - to-fast-properties "^2.0.0" 256 - 257 - "@babel/types@^7.22.15", "@babel/types@^7.22.5", "@babel/types@^7.23.0": 196 + "@babel/types@^7.10.4", "@babel/types@^7.11.0", "@babel/types@^7.22.15", "@babel/types@^7.22.5", "@babel/types@^7.23.0": 258 197 version "7.23.0" 259 198 resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.0.tgz#8c1f020c9df0e737e4e247c0619f58c68458aaeb" 260 199 integrity sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg== ··· 461 400 integrity sha512-LS05hVAAsX86qbHg7W+ydwBlNHrVCoFw6wEP3/uW4eYmRXl08bWmPeN/+onM+8qZTFfDgUlG/OItJI8SW972oQ== 462 401 dependencies: 463 402 "@types/jquery" "*" 403 + 404 + "@types/cloudflare-turnstile@^0.1.5": 405 + version "0.1.5" 406 + resolved "https://registry.yarnpkg.com/@types/cloudflare-turnstile/-/cloudflare-turnstile-0.1.5.tgz#9687f5bf504a0a190baa7b420af4c2721a01abf0" 407 + integrity sha512-DA8YspYP0jxpY1QUX5VhKLAPAJh9lxpN1Ym/XZMRqaRIb0lSJMvjTcxLMUv++jAKLFjySfcrweTS19XuRuuOUQ== 464 408 465 409 "@types/cookie@^0.4.1": 466 410 version "0.4.1" ··· 722 666 dependencies: 723 667 "@types/minimatch" "*" 724 668 "@types/node" "*" 725 - 726 - "@types/grecaptcha@^3.0.1": 727 - version "3.0.1" 728 - resolved "https://registry.yarnpkg.com/@types/grecaptcha/-/grecaptcha-3.0.1.tgz#359c46be0baeac15ae4021f002d1c0c9374ae3e4" 729 - integrity sha512-eMA/2quQoxwSe8oOBB1H6KNXNqginzt9BHAt2vVVUoQswZNct2QwSAmEMsN/VHj/XSNxM3p+Py15B7omEaAC9w== 730 669 731 670 "@types/hast@^2.0.0": 732 671 version "2.3.4"