\nV té době se také můžete vydat na normální výpravu. Tvé úkoly a dovednosti se budou počítat do obou bojů, jak na výpravě, tak se světovou příšerou.\n
\nSvětová příšera ti nikdy nijak neublíží. Místo toho má lištu vzteku, která se plní, když uživatelé neplní své Denní úkoly. Když se lišta vzteku naplní, příšera zaútočí na jednu z postav v naší zemi a její obrázek se změní.\n
\nPokud se chceš dočíst něco o [minulých světových příšerách](http://habitica.wikia.com/wiki/World_Bosses), koukni na wiki.",
- "iosFaqStillNeedHelp": "If you have a question that isn't on this list or on the [Wiki FAQ](http://habitica.wikia.com/wiki/FAQ), come ask in the Tavern chat under Menu > Tavern! We're happy to help.",
- "webFaqStillNeedHelp": "If you have a question that isn't on this list or on the [Wiki FAQ](http://habitica.wikia.com/wiki/FAQ), come ask in the [Newbies Guild](https://habitica.com/#/options/groups/guilds/5481ccf3-5d2d-48a9-a871-70a7380cee5a)! We're happy to help."
+ "iosFaqStillNeedHelp": "Jestli máš otázku, která není na tomto seznamu nebo na [Wiki FAQ] (http://habitica.wikia.com/wiki/FAQ), zeptej se v Krčmě v menu > Krčma! Jsme rádi když můžeme pomoct.",
+ "webFaqStillNeedHelp": "Pokud máš otázku, která není v seznamu nebo na [Wiki](http://habitica.wikia.com/wiki/FAQ), přijď se zeptat do [Cechu pro nováčky - Newbies Guild] (https://habitica.com/#/options/groups/guilds/5481ccf3-5d2d-48a9-a871-70a7380cee5a)! Rádi ti pomůžeme."
}
\ No newline at end of file
diff --git a/common/locales/cs/front.json b/common/locales/cs/front.json
index 90cc691493..f4f6957b23 100644
--- a/common/locales/cs/front.json
+++ b/common/locales/cs/front.json
@@ -28,6 +28,7 @@
"communityReddit": "Reddit",
"companyAbout": "Jak to funguje",
"companyBlog": "Blog",
+ "devBlog": "Developer Blog",
"companyDonate": "Přispět",
"companyExtensions": "Rozšíření",
"companyPrivacy": "Soukromí",
@@ -51,6 +52,7 @@
"featureSocialHeading": "Sociální hra",
"featuredIn": "Zmíněno v",
"featuresHeading": "Také tu máme...",
+ "footerDevs": "Developers",
"footerCommunity": "Komunita",
"footerCompany": "Společnost",
"footerMobile": "Mobilní aplikace",
@@ -124,7 +126,7 @@
"motivate1": "Motivuj se k nemožnému.",
"motivate2": "Zorganizuj se. Motivuj se. Sbírej zlato.",
"passConfirm": "Potvrdit heslo",
- "passMan": "In case you are using a password manager (like 1Password) and have problems logging in, try typing your username and password manually.",
+ "passMan": "Pokud využíváš správce hesel (např. 1Password) a máš problém s přihlášením, zkus zadat uživatelské jméno a heslo ručně.",
"password": "Heslo",
"playButton": "Hraj",
"playButtonFull": "Prozkoumej zemi Habitica",
@@ -165,7 +167,7 @@
"teams": "Týmy",
"terms": "Podmínkami",
"testimonialHeading": "Co o nás říkají...",
- "localStorageTryFirst": "If you are experiencing problems with Habitica, click the button below to clear local storage for this website (other websites will not be affected). You will need to log in again after doing this, so first be sure that you know your log-in details, which can be found at Settings -> <%= linkStart %>Site<%= linkEnd %>.",
+ "localStorageTryFirst": "Pokud máš problém s programem Habitica, stisknutím níže umístěného tlačítka můžeš vymazat lokální cache této stránky (ostatní webové stránky nebudou ovlivněny). Poté se budeš muset znovu přihlásit, proto se nejprve ujisti, že znáš své přihlašovací jméno a heslo, které najdeš na Nastavení -> <%= linkStart %>Stránka<%= linkEnd %>.",
"localStorageTryNext": "Pokud problém přetrvává, prosíme <%= linkStart %>Nahlaš chybu<%= linkEnd %>, pokud jsi to ještě neudělal.",
"localStorageClearing": "Čistím lokální úložiště",
"localStorageClearingExplanation": "Čistím lokální úložiště tvého prohlížeče. Budeš odhlášen a přesměrován na domovskou stránku. Prosím, čekej.",
@@ -182,6 +184,7 @@
"zelahQuote": "[Program Habitica] mi pomáhá rozhodnout se, jestli jít do postele a získat za to body, nebo zůstat vzhůru a přijít o zdraví!",
"reportAccountProblems": "Nahlásit problémy z účtem",
"reportCommunityIssues": "Nahlásit problém v komunitě",
+ "subscriptionPaymentIssues": "Subscription and Payment Issues",
"generalQuestionsSite": "Obecné otázky o stránce",
"businessInquiries": "Obchodní poptávka",
"merchandiseInquiries": "Poptávka po zboží",
@@ -195,7 +198,7 @@
"landingCopy3": "Přidej se k <%= userCount %> lidí, kteří se baví při zlepšování svých životů!",
"alreadyHaveAccount": "Už tu mám účet!",
"getStartedNow": "Začni hned!",
- "altAttrNavLogo": "Habitica home",
+ "altAttrNavLogo": "Úvodní stránka země Habitica",
"altAttrLifehacker": "Lifehacker",
"altAttrNewYorkTimes": "The New York Times",
"altAttrMakeUseOf": "MakeUseOf",
@@ -222,5 +225,32 @@
"altAttrWebstorm": "WebStorm",
"altAttrGithub": "GitHub",
"altAttrTrello": "Trello",
- "altAttrSlack": "Slack"
+ "altAttrSlack": "Slack",
+ "missingAuthHeaders": "Missing authentication headers.",
+ "missingAuthParams": "Missing authentication parameters.",
+ "missingUsernameEmail": "Missing username or email.",
+ "missingEmail": "Missing email.",
+ "missingUsername": "Missing username.",
+ "missingPassword": "Missing password.",
+ "missingNewPassword": "Missing new password.",
+ "wrongPassword": "Wrong password.",
+ "notAnEmail": "Invalid email address.",
+ "emailTaken": "Email address is already used in an account.",
+ "newEmailRequired": "Missing new email address.",
+ "usernameTaken": "Username already taken.",
+ "passwordConfirmationMatch": "Password confirmation doesn't match password.",
+ "invalidLoginCredentials": "Incorrect username and/or email and/or password.",
+ "passwordReset": "If we have your email on file, your password reset link has been sent to your email.",
+ "passwordResetEmailSubject": "Password Reset for Habitica",
+ "passwordResetEmailText": "Password for <%= username %> has been reset to <%= newPassword %> . Important! Both username and password are case-sensitive -- you must enter both exactly as shown here. We recommend copying and pasting both instead of typing them. Log in at <%= baseUrl %>. After you have logged in, head to <%= baseUrl %>/#/options/settings/settings and change your password.",
+ "passwordResetEmailHtml": "Password for <%= username %> has been reset to <%= newPassword %>.
Important! Both username and password are case-sensitive -- you must enter both exactly as shown here. We recommend copying and pasting both instead of typing them.
Log in at <%= baseUrl %>. After you have logged in, head to <%= baseUrl %>/#/options/settings/settings and change your password.",
+ "invalidLoginCredentialsLong": "Uh-oh - your username or password is incorrect.\n- Make sure your username or email is typed correctly.\n- You may have signed up with Facebook, not email. Double-check by trying Facebook login.\n- If you forgot your password, click \"Forgot Password\".",
+ "invalidCredentials": "There is no account that uses those credentials.",
+ "accountSuspended": "Account has been suspended, please contact leslie@habitica.com with your User ID \"<%= userId %>\" for assistance.",
+ "onlyFbSupported": "Only Facebook is supported currently.",
+ "cantDetachFb": "Account lacks another authentication method, can't detach Facebook.",
+ "onlySocialAttachLocal": "Local authentication can be added to only a social account.",
+ "invalidReqParams": "Invalid request parameters.",
+ "memberIdRequired": "\"member\" must be a valid UUID.",
+ "heroIdRequired": "\"heroId\" must be a valid UUID."
}
\ No newline at end of file
diff --git a/common/locales/cs/gear.json b/common/locales/cs/gear.json
index 0ed7e7d12a..9afc42691c 100644
--- a/common/locales/cs/gear.json
+++ b/common/locales/cs/gear.json
@@ -184,11 +184,13 @@
"weaponArmoireBarristerGavelText": "Soudní palička",
"weaponArmoireBarristerGavelNotes": "Pořádek! Zvyšuje Sílu a Obranu o <%= attrs %> každou. Začarovaná almara: Set soudce (předmět 3 ze 3)",
"weaponArmoireJesterBatonText": "Šaškovský obušek",
- "weaponArmoireJesterBatonNotes": "With a wave of your baton and some witty repartee, even the most complicated situations become clear. Increases Intelligence and Perception by <%= attrs %> each. Enchanted Armoire: Jester Set (Item 3 of 3).",
- "weaponArmoireMiningPickaxText": "Mining Pickax",
- "weaponArmoireMiningPickaxNotes": "Mine the maximum amount of gold from your tasks! Increases Perception by <%= per %>. Enchanted Armoire: Miner Set (Item 3 of 3).",
- "weaponArmoireBasicLongbowText": "Basic Longbow",
- "weaponArmoireBasicLongbowNotes": "A serviceable hand-me-down bow. Increases Strength by <%= str %>. Enchanted Armoire: Basic Archer Set (Item 1 of 3).",
+ "weaponArmoireJesterBatonNotes": "S mávnutím tvého obušku a nějakou tou chytrou poznámkou, se i ty nejkomplikovanější situace stanou jasnými. Zvyšuje Vnímání a Inteligenci o <%= attrs %>. Začarovaná almara: Šaškův set (předmět 3 ze 3).",
+ "weaponArmoireMiningPickaxText": "Krumpáč",
+ "weaponArmoireMiningPickaxNotes": "Vytěžte ze svých úkolů co nejvíc zlata! Zvyšuje Vnímání o <%= per %>. Začarovaná almara: Hornický set (předmět 3 ze 3).",
+ "weaponArmoireBasicLongbowText": "Základní dlouhý luk",
+ "weaponArmoireBasicLongbowNotes": "Funkční použitý luk. Zvyšuje Sílu o <%= str %>. Začarovaná almara: Základní lučištnický set (předmět 1 ze 3).",
+ "weaponArmoireHabiticanDiplomaText": "Habitican Diploma",
+ "weaponArmoireHabiticanDiplomaNotes": "A certificate of significant achievement -- well done! Increases Intelligence by <%= int %>. Enchanted Armoire: Graduate Set (Item 1 of 3).",
"armor": "zbroj",
"armorBase0Text": "Obyčejné oblečení",
"armorBase0Notes": "Běžné oblečení. Nenabízí žádný bonus.",
@@ -252,8 +254,8 @@
"armorSpecialBirthdayNotes": "Vše nejlepší té nejlepší zemi, Habitica! Oblečte Absurdní Party Kostýmy, a oslavte tento báječný den. Neposkytuje žádný bonus.",
"armorSpecialBirthday2015Text": "Komické párty kostýmy",
"armorSpecialBirthday2015Notes": "Vše nejlepší té nejlepší zemi, Habitica! Oblečte Absurdní Party Kostýmy, a oslavte tento báječný den. Neposkytuje žádný bonus.",
- "armorSpecialBirthday2016Text": "Ridiculous Party Robes",
- "armorSpecialBirthday2016Notes": "Happy Birthday, Habitica! Wear these Ridiculous Party Robes to celebrate this wonderful day. Confers no benefit.",
+ "armorSpecialBirthday2016Text": "Legrační párty kostýmy",
+ "armorSpecialBirthday2016Notes": "Šťastné narozeniny, Habitico! Oblečte si tyto legrační párty kostýmy abyste oslavili tento skvělý den. Neposkytuje žádný bonus.",
"armorSpecialGaymerxText": "Zbroj duhového bojovníka",
"armorSpecialGaymerxNotes": "Ku příležitosti oslav GaymerX je tato speciální brnění zdobené zářivým, barevným, duhovým vzorem! GaymerX je herní veletrh oslavující LGBTQ a hry a je otevřený všem.",
"armorSpecialSpringRogueText": "Elegantní kočičí oblek",
@@ -320,10 +322,10 @@
"armorSpecialWinter2016MageNotes": "Ten nejmoudřejší z čarodějů je v zimě pořádně zachumlaný. Zvyšuje Inteligenci o <%= int %>. Limitovaná edice zimní výbavy 2015-2016.",
"armorSpecialWinter2016HealerText": "Sváteční vílí plášť",
"armorSpecialWinter2016HealerNotes": "Sváteční víly se halí do svých křídel, aby se chránily, zatímco po zemi Habitica létají pomocí křídel na hlavě rychlostí až 160km/h, doručují dárky a házejí na lidi konfety. Jak vtipné. Zvyšuje Obranu o <%= con %>. Limitovaná edice zimní výbavy 2015-2016.",
- "armorSpecialSpring2016RogueText": "Canine Camo Suit",
- "armorSpecialSpring2016RogueNotes": "A clever pup knows to choose a brighter guise for concealment when everything is green and vibrant. Increases Perception by <%= per %>. Limited Edition 2016 Spring Gear.",
- "armorSpecialSpring2016WarriorText": "Mighty Mail",
- "armorSpecialSpring2016WarriorNotes": "Though you be but little, you are fierce! Increases Constitution by <%= con %>. Limited Edition 2016 Spring Gear.",
+ "armorSpecialSpring2016RogueText": "Maskáčový oblek",
+ "armorSpecialSpring2016RogueNotes": "Chytré štěně ví, že si má vybrat jasnější převlek pro to aby se skryl, když je vše zelené a zvučné. Zvyšuje vnímání o <%= per %>. Limitované edice Jarní výbavy 2016.",
+ "armorSpecialSpring2016WarriorText": "Mocné brnění",
+ "armorSpecialSpring2016WarriorNotes": "Jsi sice malý, ale zato urputný! Zvýšuje Obranu o <%= con %>. Limitovaná edice Jarní výbavy 2016.",
"armorSpecialSpring2016MageText": "Grand Malkin Robes",
"armorSpecialSpring2016MageNotes": "Brightly colored, so you won't be mistaken for a necromouser. Increases Intelligence by <%= int %>. Limited Edition 2016 Spring Gear.",
"armorSpecialSpring2016HealerText": "Fluffy Bunny Breeches",
@@ -364,6 +366,10 @@
"armorMystery201512Notes": "Přivolej ledové plameny zimy! Nepřináší žádný benefit. Předmět pro předplatitele prosinec 2015.",
"armorMystery201603Text": "Lucky Suit",
"armorMystery201603Notes": "This suit is sewn from thousands of four-leafed clovers! Confers no benefit. March 2016 Subscriber Item.",
+ "armorMystery201604Text": "Armor o' Leaves",
+ "armorMystery201604Notes": "You, too, can be a small but fearsome leaf puff. Confers no benefit. April 2016 Subscriber Item.",
+ "armorMystery201605Text": "Marching Bard Uniform",
+ "armorMystery201605Notes": "Unlike the traditional bards who join adventuring parties, bards who join Habitican marching bands are known for grand parades, not dungeon raids. Confers no benefit. May 2016 Subscriber Item.",
"armorMystery301404Text": "Steampunk oblek",
"armorMystery301404Notes": "Elegantní a fešácký, joj! Nepřináší žádný benefit. Předmět pro předplatitele únor 3015.",
"armorArmoireLunarArmorText": "Uklidňující měsíční brnění",
@@ -394,6 +400,8 @@
"armorArmoireMinerOverallsNotes": "They may seem worn, but they are enchanted to repel dirt. Increases Constitution by <%= con %>. Enchanted Armoire: Miner Set (Item 2 of 3).",
"armorArmoireBasicArcherArmorText": "Basic Archer Armor",
"armorArmoireBasicArcherArmorNotes": "This camouflaged vest lets you slip unnoticed through the forests. Increases Perception by <%= per %>. Enchanted Armoire: Basic Archer Set (Item 2 of 3).",
+ "armorArmoireGraduateRobeText": "Graduate Robe",
+ "armorArmoireGraduateRobeNotes": "Congratulations! This weighty robe hangs heavy with all the knowledge you have accrued. Increases Intelligence by <%= int %>. Enchanted Armoire: Graduate Set (Item 2 of 3).",
"headgear": "Pokrývka hlavy",
"headBase0Text": "Žádná přilba",
"headBase0Notes": "Žádná pokrývka hlavy",
@@ -565,6 +573,10 @@
"headMystery201602Notes": "Shield your identity from all your admirers. Confers no benefit. February 2016 Subscriber Item.",
"headMystery201603Text": "Lucky Hat",
"headMystery201603Notes": "This top hat is a magical good-luck charm. Confers no benefit. March 2016 Subscriber Item.",
+ "headMystery201604Text": "Crown o' Flowers",
+ "headMystery201604Notes": "These woven flowers make a surprisingly strong helm! Confers no benefit. April 2016 Subscriber Item.",
+ "headMystery201605Text": "Marching Bard Hat",
+ "headMystery201605Notes": "Seventy-six dragons led the big parade, with a hundred and ten gryphons close at hand! Confers no benefit. May 2016 Subscriber Item.",
"headMystery301404Text": "Fešný cylindr",
"headMystery301404Notes": "Fešný cylindr pro ty největší džentlmeny. Předmět pro předplatitele leden 2015. Nepřináší žádný benefit.",
"headMystery301405Text": "Obyčejný cylindr",
@@ -613,6 +625,8 @@
"headArmoireMinerHelmetNotes": "Protect your head from falling tasks! Increases Intelligence by <%= int %>. Enchanted Armoire: Miner Set (Item 1 of 3).",
"headArmoireBasicArcherCapText": "Basic Archer Cap",
"headArmoireBasicArcherCapNotes": "No archer would be complete without a jaunty cap! Increases Perception by <%= per %>. Enchanted Armoire: Basic Archer Set (Item 3 of 3).",
+ "headArmoireGraduateCapText": "Graduate Cap",
+ "headArmoireGraduateCapNotes": "Congratulations! Your deep thoughts have earned you this thinking cap. Increases Intelligence by <%= int %>. Enchanted Armoire: Graduate Set (Item 3 of 3).",
"offhand": "štít v ruce",
"shieldBase0Text": "Bez štítu v ruce",
"shieldBase0Notes": "Bez štítu nebo druhé zbraně.",
@@ -673,7 +687,7 @@
"shieldSpecialWinter2015HealerText": "Uklidňující štít",
"shieldSpecialWinter2015HealerNotes": "Teto štít odráží ledové větry. Zvyšuje Obranu o <%= con %>. Limitovaná edice zimní výbavy 2014-2015.",
"shieldSpecialSpring2015RogueText": "Explozivní kník",
- "shieldSpecialSpring2015RogueNotes": "Nenech se ošálit tím zvukem - tahle výbušnina umí kopnout. Zvyšuje Sílu o <%= str %>. Limitovaná edice Jarní výbavy 2015.",
+ "shieldSpecialSpring2015RogueNotes": "Don't let the sound fool you - these explosives pack a punch. Increases Strength by <%= str %>. Limited Edition 2015 Spring Gear.",
"shieldSpecialSpring2015WarriorText": "Disk na jídlo",
"shieldSpecialSpring2015WarriorNotes": "Hoď ho po nepřátelích... nebo ho drž, protože se naplní dobrůtkami na večeři. Zvyšuje Obranu o <%= con %>. Limitovaná edice Jarní výbavy 2015.",
"shieldSpecialSpring2015HealerText": "Vzorovaný polštář",
@@ -696,14 +710,14 @@
"shieldSpecialWinter2016WarriorNotes": "Použij tento štít k odrážení útoků, nebo se na něj triumfálně projeď do bitvy! Zvyšuje Obranu o <%= con %>. Limitovaná edice zimní výbavy 2015-2016.",
"shieldSpecialWinter2016HealerText": "Skřítkův dárek",
"shieldSpecialWinter2016HealerNotes": "Otevři ho otevři ho otevři ho otevři ho otevři ho otevři ho!!!!!!!!! Zvyšuje Obranu o <%= con %>. Limitovaná edice zimní výbavy 2015-2016.",
- "shieldSpecialSpring2016RogueText": "Fire Bolas",
+ "shieldSpecialSpring2016RogueText": "Ohnivé boly",
"shieldSpecialSpring2016RogueNotes": "You've mastered the ball, the club, and the knife. Now you advance to juggling fire! Awoo! Increases Strength <%= str %>. Limited Edition 2016 Spring Gear.",
- "shieldSpecialSpring2016WarriorText": "Cheese Wheel",
- "shieldSpecialSpring2016WarriorNotes": "You braved fiendish traps to procure this defense-boosting food. Increases Constitution by <%= con %>. Limited Edition 2016 Spring Gear.",
- "shieldSpecialSpring2016HealerText": "Floral Buckler",
- "shieldSpecialSpring2016HealerNotes": "The April Fool claims this little shield will block Shiny Seeds. Don't believe him. Increases Constitution by <%= con %>. Limited Edition 2016 Spring Gear.",
- "shieldMystery201601Text": "Resolution Slayer",
- "shieldMystery201601Notes": "This blade can be used to parry away all distractions. Confers no benefit. January 2016 Subscriber Item.",
+ "shieldSpecialSpring2016WarriorText": "Sýrové kolečko",
+ "shieldSpecialSpring2016WarriorNotes": "Odvážil jsi se projít pastmi zlořádů, aby jsi obstaral toto obranu-zvětšující jídlo. Zvyšuje Obranu o <%= con %>. Limitovaná edice Jarní výbavy 2016.",
+ "shieldSpecialSpring2016HealerText": "Květinový štít",
+ "shieldSpecialSpring2016HealerNotes": "Vtipálek ti řekl, že tento malinký štít zablokuje Lesklá semínka. Nevěř mu. Zvyšuje Obranu o <%= con %>. Limitovaná edice Jarní výbavy 2016.",
+ "shieldMystery201601Text": "Kat odhodlání",
+ "shieldMystery201601Notes": "Tento meč lze použít k odražení všech rozptýlení. Neposkytuje žádný bonus. Předplatitelský předmět ledna 2016.",
"shieldMystery301405Text": "Štít z hodin",
"shieldMystery301405Notes": "Čas je na tvé straně s tímhle štítem z hodin! Nepřináší žádný benefit. Předmět pro předplatitele červen 3015.",
"shieldArmoireGladiatorShieldText": "Štít gladiátora",
@@ -714,8 +728,10 @@
"shieldArmoireRoyalCaneNotes": "Sláva vládci, oslavovaného v písních! Zvyšuje Obranu, Inteligenci a Vnímání o <%= attrs %> každé. Začarovaná almara: Královský set (předmět 2 ze 3).",
"shieldArmoireDragonTamerShieldText": "Štít krotitele draků",
"shieldArmoireDragonTamerShieldNotes": "Znejisti své nepřátele tímto štítem ve tvaru draka. Zvyšuje Vnímání o <%= per %>. Začarovaná almara: Set krotitele draků (předmět 2 ze 3).",
- "shieldArmoireMysticLampText": "Mystic Lamp",
- "shieldArmoireMysticLampNotes": "Light the darkest caves with this mystic lamp! Increases Perception by <%= per %>. Enchanted Armoire: Independent Item.",
+ "shieldArmoireMysticLampText": "Mystická lampa",
+ "shieldArmoireMysticLampNotes": "Osviť i tu nejtemnější jeskyni s touto mystickou lampou! Zvyšuje vnímání o <%= per %>. Začarovaná almara: Nezávislý předmět.",
+ "shieldArmoireFloralBouquetText": "Bouquet o' Flowers",
+ "shieldArmoireFloralBouquetNotes": "Not much help in battle, but aren't they beautiful? Increases Constitution by <%= con %>. Enchanted Armoire: Independent Item.",
"back": "Příslušenství na záda",
"backBase0Text": "Bez příslušenství na zádech",
"backBase0Notes": "Bez příslušenství na zádech.",
@@ -731,8 +747,8 @@
"backMystery201507Notes": "Zasurfuj si od Svědomitých doků a prožeň vlny v Zátoce Nekompletnosti! Nepřináší žádný benefit. Předmět pro předplatitele červenec 2015.",
"backMystery201510Text": "Ocas goblina",
"backMystery201510Notes": "Chápavý a mocný! Nepřináší žádný bonus. Výbava pro předplatitele říjen 2015.",
- "backMystery201602Text": "Heartbreaker Cape",
- "backMystery201602Notes": "With a swish of your cape, your enemies fall before you! Confers no benefit. February 2016 Subscriber Item.",
+ "backMystery201602Text": "Plášť lamače srdcí",
+ "backMystery201602Notes": "Se švihnutím tvého pláště před tebou padají tvoji nepřátelé. Neposkytuje žádný bonus. Předplatitelský předmět únoru 2016.",
"backSpecialWonderconRedText": "Mocná kápě",
"backSpecialWonderconRedNotes": "Skví se silou a krásou. Speciální edice běžné zbroje. Nepřináší žádný benefit.",
"backSpecialWonderconBlackText": "Záludná kápě",
@@ -779,16 +795,16 @@
"headAccessorySpecialSpring2015MageNotes": "Tyto uši poctivě naslouchají, jestli někde kolem není nějaký mág, který odhaluje kouzla. Nepřináší žádný benefit. Limitovaná edice Jarní výbavy 2015.",
"headAccessorySpecialSpring2015HealerText": "Zelené kočičí uši",
"headAccessorySpecialSpring2015HealerNotes": "Z těchto roztomilých kočičí uší všichni zezelenají závistí. Nepřináší žádný benefit. Limitovaná edice Jarní výbavy 2015.",
- "headAccessorySpecialSpring2016RogueText": "Green Dog Ears",
- "headAccessorySpecialSpring2016RogueNotes": "With these, you can keep track of tricky Mages even if they turn invisible! Confers no benefit. Limited Edition 2016 Spring Gear.",
- "headAccessorySpecialSpring2016WarriorText": "Red Mouse Ears",
- "headAccessorySpecialSpring2016WarriorNotes": "To better hear your theme song across clamorous battlefields. Confers no benefit. Limited Edition 2016 Spring Gear.",
- "headAccessorySpecialSpring2016MageText": "Yellow Cat Ears",
- "headAccessorySpecialSpring2016MageNotes": "These sharp ears can detect the minute hum of ambient Mana, or the muted footfalls of a Rogue. Confers no benefit. Limited Edition 2016 Spring Gear.",
- "headAccessorySpecialSpring2016HealerText": "Purple Bunny Ears",
- "headAccessorySpecialSpring2016HealerNotes": "They stand like flags above the fray, letting others know where to run for help. Confers no benefit. Limited Edition 2016 Spring Gear.",
+ "headAccessorySpecialSpring2016RogueText": "Zelené psí uši",
+ "headAccessorySpecialSpring2016RogueNotes": "S tímto předmětem můžeš sledovat lstivé kouzelníky, i když jsou neviditelní! Neposkytuje žádný bonus. Limitovaná edice Jarní výbavy 2016.",
+ "headAccessorySpecialSpring2016WarriorText": "Červené myší uši",
+ "headAccessorySpecialSpring2016WarriorNotes": "Aby jsi lépe slyšel svou znělku přes hlučná bojiště. Neposkytuje žádný bonus. Limitovaná edice Jarní výbavy 2016.",
+ "headAccessorySpecialSpring2016MageText": "Žluté kočičí uši",
+ "headAccessorySpecialSpring2016MageNotes": "Tyto ostré uši mohou zachytit chvilkový hukot okolní many, nebo tiché krůčky ničemy. Neposkytuje žádný bonus. Limitovaná edice Jarní výbavy 2016.",
+ "headAccessorySpecialSpring2016HealerText": "Fialové králičí uši",
+ "headAccessorySpecialSpring2016HealerNotes": "Stojí jako vlajky nad bitvou, nechávající ostatní vědět kam utíkat. Neposkytuje žádný bonus. Limitovaná edice Jarní výbavy 2016.",
"headAccessoryBearEarsText": "Medvědí uši",
- "headAccessoryBearEarsNotes": "These ears make you look like a brave bear! Confers no benefit.",
+ "headAccessoryBearEarsNotes": "S těmito ouškama vypadáš jako statečný medvěd! Neposkytuje žádný bonus.",
"headAccessoryCactusEarsText": "Kaktusové uši",
"headAccessoryCactusEarsNotes": "Tyhle uši z tebe udělají pichlavý kaktus! Nepřináší žádný benefit.",
"headAccessoryFoxEarsText": "Liščí uši",
@@ -815,11 +831,25 @@
"headAccessoryMystery201510Notes": "Tyto strašlivé rohy jsou trochu od slizu. Nepřináší žádný bonus. Výbava pro předplatitele říjen 2015.",
"headAccessoryMystery301405Text": "Brýle na čele",
"headAccessoryMystery301405Notes": "\"Brýle jsou na oči,\" říkali. \"Nikdo nechce nosit brýle na čele,\" říkali. Ha! Teď jsi jim to natřel! Nepřináší žádný benefit. Předmět pro předplatitele srpen 3015.",
- "headAccessoryArmoireComicalArrowText": "Comical Arrow",
- "headAccessoryArmoireComicalArrowNotes": "This whimsical item doesn't provide a stat boost, but it sure is good for a laugh! Confers no benefit. Enchanted Armoire: Independent Item.",
+ "headAccessoryArmoireComicalArrowText": "Komický šíp",
+ "headAccessoryArmoireComicalArrowNotes": "Tento dekorativní předmět sice neposkytuje žádný bonus ke statům, ale rozhodně je skvělý pro smích! Neposkytuje žádnou výhodu. Začarovaná almara: Nezávislý předmět.",
"eyewear": "Brýle",
"eyewearBase0Text": "Žádné vybavení pro oči",
"eyewearBase0Notes": "Žádné vybavení pro oči.",
+ "eyewearSpecialBlackTopFrameText": "Black Standard Eyeglasses",
+ "eyewearSpecialBlackTopFrameNotes": "Glasses with a black frame above the lenses. Confers no benefit.",
+ "eyewearSpecialBlueTopFrameText": "Blue Standard Eyeglasses",
+ "eyewearSpecialBlueTopFrameNotes": "Glasses with a blue frame above the lenses. Confers no benefit.",
+ "eyewearSpecialGreenTopFrameText": "Green Standard Eyeglasses",
+ "eyewearSpecialGreenTopFrameNotes": "Glasses with a green frame above the lenses. Confers no benefit.",
+ "eyewearSpecialPinkTopFrameText": "Pink Standard Eyeglasses",
+ "eyewearSpecialPinkTopFrameNotes": "Glasses with a pink frame above the lenses. Confers no benefit.",
+ "eyewearSpecialRedTopFrameText": "Red Standard Eyeglasses",
+ "eyewearSpecialRedTopFrameNotes": "Glasses with a red frame above the lenses. Confers no benefit.",
+ "eyewearSpecialWhiteTopFrameText": "White Standard Eyeglasses",
+ "eyewearSpecialWhiteTopFrameNotes": "Glasses with a white frame above the lenses. Confers no benefit.",
+ "eyewearSpecialYellowTopFrameText": "Yellow Standard Eyeglasses",
+ "eyewearSpecialYellowTopFrameNotes": "Glasses with a yellow frame above the lenses. Confers no benefit.",
"eyewearSpecialSummerRogueText": "Ničemné brýle",
"eyewearSpecialSummerRogueNotes": "Člověk nemusí být mořský červ aby poznal, že je stylová! Nepřidává žádný bonus. Limitovaná edice 2014 Letní Výbava.",
"eyewearSpecialSummerWarriorText": "Úžasná páska na oko",
diff --git a/common/locales/cs/generic.json b/common/locales/cs/generic.json
index fd5954bdff..60a81ff281 100644
--- a/common/locales/cs/generic.json
+++ b/common/locales/cs/generic.json
@@ -33,7 +33,7 @@
"italics": "*kurzíva*",
"bold": "**tučně**",
"strikethrough": "~~přeškrtnuto~~",
- "emojiExample": ":smajlík:",
+ "emojiExample": ":smile:",
"markdownLinkEx": "[Program Habitica je super!](https://habitica.com)",
"markdownImageEx": "",
"unorderedListHTML": "+ První položka + Druhá položka + Třetí položka",
@@ -116,7 +116,7 @@
"audioTheme_luneFoxTheme": "Téma od LuneFox",
"askQuestion": "Položit otázku",
"reportBug": "Nahlásit chybu",
- "HabiticaWiki": "The Habitica Wiki",
+ "HabiticaWiki": "Habitica Wiki",
"HabiticaWikiFrontPage": "http://habitica.wikia.com/wiki/Habitica_Wiki",
"contributeToHRPG": "Přispět Habitica",
"overview": "Přehled pro nové uživatele",
@@ -137,8 +137,8 @@
"achievementStressbeastText": "Pomož přemoci Zavrženíhodnou Strespříšeru při příležitosti Zimní říše divů 2014!",
"achievementBurnout": "Vychutnej si kvetoucí pole",
"achievementBurnoutText": "Pomož přemoci Vyhoření a obnovit Vyčerpané duchy během Podzimního festivalu 2015!",
- "achievementBewilder": "Savior of Mistiflying",
- "achievementBewilderText": "Helped defeat the Be-Wilder during the 2016 Spring Fling Event!",
+ "achievementBewilder": "Zachránce Mistiflyingu",
+ "achievementBewilderText": "Přispěl k poražení Be-Wildera během akce Jarního Hodu 2016!",
"checkOutProgress": "Koukejte, jaký pokrok se mi povedl v zemi Habitica!",
"cardReceived": "Obdržel jsi přání!",
"cardReceivedFrom": "<%= cardType %> od <%= userName %>",
@@ -160,12 +160,12 @@
"thankyou3": "Jsem velmi vděčný - děkuji!",
"thankyouCardAchievementTitle": "Velmi vděčný",
"thankyouCardAchievementText": "Díky za vděčnost! Poslal nebo odeslal <%= cards %> děkovných přání.",
- "birthdayCard": "Birthday Card",
- "birthdayCardExplanation": "You both receive the Birthday Bonanza achievement!",
- "birthdayCardNotes": "Send a birthday card to a party member.",
- "birthday0": "Happy birthday to you!",
- "birthdayCardAchievementTitle": "Birthday Bonanza",
- "birthdayCardAchievementText": "Many happy returns! Sent or received <%= cards %> birthday cards.",
+ "birthdayCard": "Narozeninové přání",
+ "birthdayCardExplanation": "Oba získáváte ocenění Velkého narozeninového zisku!",
+ "birthdayCardNotes": "Pošli narozeninové přání členu skupiny.",
+ "birthday0": "Šťastné narozeniny!",
+ "birthdayCardAchievementTitle": "Velký narozeninový zisk",
+ "birthdayCardAchievementText": "Spoustu šťastných návratů! Posláno nebo přijato <%= cards %> narozeninových karet.",
"streakAchievement": "Získal jsi ocenění za sérii!",
"firstStreakAchievement": "21-denní série úspěšnosti",
"streakAchievementCount": "<%= streaks %> 21-denních sérií úspěšnosti",
@@ -178,5 +178,5 @@
"raisePetShare": "Mazlíček mi vyrostl v silné zvíře, protože jsem svědomitě plnil úkoly!",
"wonChallengeShare": "V Habitica jsem vyhrál výzvu!",
"achievementShare": "Získal jsem v zemi Habitica nové ocenění!",
- "orderBy": "Order By <%= item %>"
+ "orderBy": "Seřadit podle <%= item %>"
}
\ No newline at end of file
diff --git a/common/locales/cs/groups.json b/common/locales/cs/groups.json
index 1f85d0bfff..c534fd1aa5 100644
--- a/common/locales/cs/groups.json
+++ b/common/locales/cs/groups.json
@@ -1,5 +1,5 @@
{
- "tavern": "Tavern Chat",
+ "tavern": "Chat v krčmě",
"innCheckOut": "Odhlásit se z hostince",
"innCheckIn": "Odpočívat v hostinci",
"innText": "Odpočíváš v Hostinci! Zatímco tu budeš, tvé Denní úkoly ti na konci dne nijak neublíží, ale vždy se resetují. Ale pozor: pokud jsi v boji s příšerou, ublíží ti nesplněné úkoly tvých přátel v družině, pokud také nejsou v Hostinci! Navíc, jakákoliv újma, kterou uštědříš příšeře (nebo nasbírané předměty) se ti nepřipíšou dokud se z Hostince neodhlásíš.",
@@ -92,6 +92,7 @@
"send": "Poslat",
"messageSentAlert": "Zpráva odeslána",
"pmHeading": "Soukromá zpráva pro <%= name %>",
+ "pmsMarkedRead": "Tvé soukromé zprávy byly označeny jako přečtené.",
"clearAll": "Vymazat všechny zprávy",
"confirmDeleteAllMessages": "Jsi si jistý, že chceš vymazat všechny zprávy ve schránce? Ostatní uživatelé stále uvidí všechny zprávy, které jsi jim poslal.",
"optOutPopover": "Nemáš rád soukromé zprávy? Klikni pro úplné odhlášení",
@@ -99,9 +100,18 @@
"unblock": "Odblokovat",
"pm-reply": "Poslat odpověď",
"inbox": "Příchozí zprávy",
+ "messageRequired": "Je požadována zpráva.",
+ "toUserIDRequired": "Je požadováno ID uživatele",
+ "gemAmountRequired": "Je požadován určitý počet drahokamů",
+ "notAuthorizedToSendMessageToThisUser": "Nelze posílat zprávy tomuto uživateli.",
+ "privateMessageGiftIntro": "Dobrý den, <%= receiverName %>, <%= senderName %> vám poslal",
+ "privateMessageGiftGemsMessage": "<%= gemAmount %> drahokamů!",
+ "privateMessageGiftSubscriptionMessage": "<%= numberOfMonths %> měsíců předplatného!",
+ "cannotSendGemsToYourself": "Nemůžete sám sobě poslat drahokamy. Raději zkuste předplatné.",
+ "badAmountOfGemsToSend": "Částka musí být mezi 1 a vaším současným počtem drahokamů.",
"abuseFlag": "Nahlaš porušení Zásad komunity",
"abuseFlagModalHeading": "Nahlásit <%= name %> za porušení?",
- "abuseFlagModalBody": "Are you sure you want to report this post? You should ONLY report a post that violates the <%= firstLinkStart %>Community Guidelines<%= linkEnd %> and/or <%= secondLinkStart %>Terms of Service<%= linkEnd %>. Inappropriately reporting a post is a violation of the Community Guidelines and may give you an infraction. Appropriate reasons to flag a post include but are not limited to:
swearing, religous oaths
bigotry, slurs
adult topics
violence, including as a joke
spam, nonsensical messages
",
+ "abuseFlagModalBody": "Opravdu chceš nahlásit tento příspěvek? Měl bys hlásit POUZE příspěvky, které porušují <%= firstLinkStart %>Zásady komunity<%= linkEnd %> a/nebo <%= secondLinkStart %>Pravidla používání<%= linkEnd %>. Neoprávněné hlášení příspěvku porušuje Zásady komunity a může být trestáno. Oprávněné důvody pro nahlášení příspěvku jsou například:
nadávky, urážky náboženství
předsudky, urážky
nevhodná témata
násilí, i když je myšleno jako vtip
spam, nesmyslné zprávy
",
"abuseFlagModalButton": "Nahlásit porušení pravidel",
"abuseReported": "Děkujeme za nahlášení tohoto příspěvku. Moderátoři byli upozorněni.",
"abuseAlreadyReported": "Již jsi tento příspěvek nahlásil.",
@@ -150,6 +160,30 @@
"requestAcceptGuidelines": "Pokud chceš přidávat zprávy v Krčmě nebo v chatu v jakékoliv družině nebo cechu, prosíme, nejprve si přečti <%= linkStart %>Zásady komunity<%= linkEnd %> a poté klikni na tlačítko níže na znamení, že jim rozumíš.",
"partyUpName": "Party Up",
"partyOnName": "Party On",
- "partyUpAchievement": "Joined a Party with another person! Have fun battling monsters and supporting each other.",
- "partyOnAchievement": "Joined a Party with at least four people! Enjoy your increased accountability as you unite with your friends to vanquish your foes!"
+ "partyUpAchievement": "Připojil jsi se k Družině s jiným hráčem! Měj zábavu s porážením příšer a pomáhání si navzájem.",
+ "partyOnAchievement": "Připojil jsi se k Družině s alespoň čtyřmi dalšími hráči! Užij jsi tvoji zvýšenou odpovědnost jak se spojíte s tvými přáteli aby jste porazili vaše nepřítele!",
+ "groupIdRequired": "\"groupId\" must be a valid UUID",
+ "groupNotFound": "Skupina nenalezena.",
+ "groupTypesRequired": "You must supply a valid \"type\" query string.",
+ "questLeaderCannotLeaveGroup": "Nemůžete opustit družinu, když jste začali výpravu. Nejdříve zrušte výpravu.",
+ "cannotLeaveWhileActiveQuest": "Nemůžete opustit družinu během výpravy. Nejdříve, prosím opusťte výpravu.",
+ "onlyLeaderCanRemoveMember": "Pouze vůdce družiny může odebrat člena!",
+ "memberCannotRemoveYourself": "Nemůžete se sám odebrat!",
+ "groupMemberNotFound": "Uživatel nenalezen mezi členy skupiny.",
+ "mustBeGroupMember": "Musí být členem skupiny.",
+ "keepOrRemoveAll": "req.query.keep must be either \"keep-all\" or \"remove-all\"",
+ "keepOrRemove": "req.query.keep must be either \"keep\" or \"remove\"",
+ "canOnlyInviteEmailUuid": "Lze použít pouze uuids nebo emaily.",
+ "inviteMissingEmail": "Chybějící emailová adresa v pozvánce.",
+ "partyMustbePrivate": "Družiny musí být soukromé.",
+ "userAlreadyInGroup": "Uživatel již je ve skupině.",
+ "userAlreadyInvitedToGroup": "Uživatel byl již pozván do skupiny.",
+ "userAlreadyPendingInvitation": "Uživatel přijal pozvánku.",
+ "userAlreadyInAParty": "Uživatel již je v družině.",
+ "userWithIDNotFound": "Uživatel s id „<%= userId %>\" nenalezen.",
+ "userHasNoLocalRegistration": "User does not have a local registration (username, email, password).",
+ "uuidsMustBeAnArray": "User ID invites must be an array.",
+ "emailsMustBeAnArray": "Email address invites must be an array.",
+ "canOnlyInviteMaxInvites": "Můžete pozvat pouze „<%= maxInvites %>\" najednou.",
+ "onlyCreatorOrAdminCanDeleteChat": "Nemáte oprávnění k smazání této zprávy."
}
\ No newline at end of file
diff --git a/common/locales/cs/limited.json b/common/locales/cs/limited.json
index 6d8c6317e4..54f54d82eb 100644
--- a/common/locales/cs/limited.json
+++ b/common/locales/cs/limited.json
@@ -5,7 +5,7 @@
"annoyingFriends": "Otravní přátelé",
"annoyingFriendsText": "Byl členy družiny zkoulován <%= snowballs %> krát.",
"alarmingFriends": "Strašliví přátelé",
- "alarmingFriendsText": "Byl <%= spookDust %>krát vyděšen členy družiny.",
+ "alarmingFriendsText": "Got spooked <%= spookySparkles %> times by party members.",
"agriculturalFriends": "Zemědělští přátelé",
"agriculturalFriendsText": "Byl kamarády transformován <%= seeds %>krát na kytku.",
"aquaticFriends": "Podvodní přátelé",
@@ -67,10 +67,11 @@
"witchyWizardSet": "Čarodějný čaroděj (Mág)",
"mummyMedicSet": "Mumie medika (Léčitel)",
"vampireSmiterSet": "Lovec upírů (Zloděj)",
- "bewareDogSet": "Beware Dog (Warrior)",
+ "bewareDogSet": "Strážný Pes (Válečník)",
"magicianBunnySet": "Mágův králíček (Mág)",
"comfortingKittySet": "Utěšující kotě (Léčitel)",
- "sneakySqueakerSet": "Sneaky Squeaker (Rogue)",
+ "sneakySqueakerSet": "Záludný Kníkač (Zloděj)",
"fallEventAvailability": "Dostupné do 31. října",
- "winterEventAvailability": "Dostupné do 31. prosince"
+ "winterEventAvailability": "Dostupné do 31. prosince",
+ "springEventAvailability": "Dostupné do 31. května"
}
\ No newline at end of file
diff --git a/common/locales/cs/maintenance.json b/common/locales/cs/maintenance.json
new file mode 100644
index 0000000000..b085037351
--- /dev/null
+++ b/common/locales/cs/maintenance.json
@@ -0,0 +1,34 @@
+{
+ "habiticaBackSoon": "Nebojte se, Habitica bude brzy zpět!",
+ "importantMaintenance": "Provádíme důležitou údržbu, která dle našich odhadů bude trvat do <%= localDate %> ve vašem časovém pásmu.",
+ "maintenance": "Údržba",
+ "maintenanceMoreInfo": "Chcete více informací ohledně údržby? <%= linkStart %>Podívejte se na naši stránku<%= linkEnd %>.",
+ "noDamageKeepStreaks": "Neutrpíte ŽÁDNÉ poškození a neztratíte vaše série úspěšnosti!",
+ "thanksForPatience": "Děkujeme za vaší trpělivost.",
+ "twitterMaintenanceUpdates": "Pro nejnovější novinky sledujte náš Twitter, kde budeme přidávat více informací.",
+ "veteranPetAward": "Po údržbě obdržíte Veteránského mazlíčka!",
+
+ "maintenanceInfoTitle": "Informace o nacházející údržbě Habiticy",
+ "maintenanceInfoWhat": "Co se děje?",
+ "maintenanceInfoWhatText": "21. května bude Habitica většinu dne mimo provoz, z důvodu údržby. Během víkendu neutrpíte žádné poškození a to, ani když se včas nepřihlásíte a neodškrtnete si své denní úkoly. Budeme se snažit zprovoznit Habiticu co nejdříve, novinky ohledně údržby budeme přidávat na náš Twitter účet. Po údržbě obdržíte Veteránského mazlíčka!",
+ "maintenanceInfoWhy": "Proč se to děje?",
+ "maintenanceInfoWhyText": "Pár posledních měsíců jsme Habiticu v pozadí důkladně rekonstruovali. Specificky, jsme přepsali API. I když to na povrchu možná nevypadá o tolik jinak, pod povrchem je to naprosto nový svět. V budoucnosti nám toto povolí vytvořit mnohem snadněji mnoho funkcí a zlepšit celkovou hratelnost!",
+ "maintenanceInfoTechDetails": "Chcete více detailů ohledně technické stránky procesu? Navštivte Kovárnu, náš blog o vývoji.",
+ "maintenanceInfoMore": "Více informací",
+ "maintenanceInfoAccountChanges": "Jaké změn, ve svém účtu, si budu moci všimnout, až bude přepis dokončen? ",
+ "maintenanceInfoAccountChangesText": "Ze začátku nenarazíte na žádné znatelné změny, kromě vylepšení výkonu funkcí jako jsou například Výzvy. Všimnete-li si nějakých změn, které tu nemají být, pošlete nám email admin@habitica.com a my je pro vás vyřešíme!",
+ "maintenanceInfoAddFeatures": "Jaké vylepšení se přidají do Habiticy?",
+ "maintenanceInfoAddFeaturesText": "Dokončení tohoto přepisu nám dovolí začít budovat vylepšený chat a Cechy, plány pro organizace a rodiny a další produktivní funkce, například Měsíčky a možnost si nahrát včerejší aktivitu! To jsou všechno funkce, které jsou jednotlivé, takže bude trvat déle je zavést,pokud bychom však nezačali s tímto přepisem, nebylo by možné je začít provádět.",
+ "maintenanceInfoHowLong": "Jako dlouho bude údržba trvat?",
+ "maintenanceInfoHowLongText": "Musíme převést úkoly a data pro 1.3 milióny uživatelů Habiticy - nejednoduchý úkol! Očekáváme, že se údržba bude konat od (20:00 UTC) do (05:00 UTC). Buďte si jistí, že děláme co je v našich silách, abychom vše stihli co nejrychleji. Můžete sledovat aktualizace na našem Twitteru.",
+ "maintenanceInfoStatsAffected": "Co se stane s mými Denními úkoly, Sériemi úspěšnosti, Bonusy a Výpravami?",
+ "maintenanceInfoStatsAffectedText1": "Během víkendu neutrpíte ŽÁDNÉ poškození a neztratíte vaše série úspěšnosti, váš sen se však resetuje normálně! Denní úkoly, které jste odškrtli budou odškrtnuté, bonusy se resetují, atd. Jste-li na sběratelské výpravě, stále budete nalézat předměty, jste-li v bitvě s Bossem, stále budete působit poškození, ovšem Boss nebude působit poškození vám. (I příšery potřebují pauzu!)",
+ "maintenanceInfoStatsAffectedText2": "Po dlouhém přemýšlení se náš tým rozhodl, že toto je nejférovější cesta jak si poradit se skutečností, že mnoho uživatelů nebude schopno si odškrtnout své Denní úkoly během údržby. Omlouváme se za veškeré nepříjemnosti!",
+ "maintenanceInfoSeeTasks": "Co když potřebuji vidět svůj list úkolů?",
+ "maintenanceInfoSeeTasksText": "Budete-li potřebovat vidět váš seznam úkolů v sobotu, abyste věděli co dělat, doporučujeme vám si ho před začátkem údržby vyfotit.",
+ "maintenanceInfoRarePet": "Jakého vzácného mazlíčka dostanu?",
+ "maintenanceInfoRarePetText": "Abychom vám poděkovali za vaší trpělivost během prostoje, každý dostane vzácného Veteránského mazlíčka. Pokud jste žádného nikdy předtím nedostali, obdržíte Veteránského vlka. Máte-li už Veteránského vlka, obdržíte Veteránského tygra. Máte-li už oba tyto mazlíčky, obdržíte Veteránského mazlíčka, kterého ještě nikdo nikdy neviděl! Může trvat i několik hodin, po dokončení migrace serveru, než se váš mazlíček objeví ale nebojte se, každý jednoho dostane.",
+ "maintenanceInfoWho": "Kdo pracoval na tomto obrovském projektu?",
+ "maintenanceInfoWhoText": "Děkujeme za optání! Bylo to vedeno naším úžasným přispěvatelem jménem paglias, s hodně pomocí od hráčů, kteří si říkají Blade, TheHollidayInn, SabreCat, Victor Pudeyev, TheUnknown, a Alys.",
+ "maintenanceInfoTesting": "Tato nová verze byla také neúnavně testovaná skupinou úžasných dobrovolníků. Děkujeme vám - bez vás bychom to nezvládli."
+}
diff --git a/common/locales/cs/npc.json b/common/locales/cs/npc.json
index ee095e17c1..8baefe3679 100644
--- a/common/locales/cs/npc.json
+++ b/common/locales/cs/npc.json
@@ -16,11 +16,31 @@
"displayPotionForGold": "Chceš prodat <%= itemType %> lektvar?",
"sellForGold": "Prodat za <%= gold %> zlaťáky",
"buyGems": "Kup drahokamy",
- "purchaseGems": "Purchase Gems",
+ "purchaseGems": "Koupit drahokamy",
"justin": "Justin",
"ian": "Ian",
"ianText": "Vítej v obchodě s Výpravami! Můžeš tu s přáteli využít svitky s výpravami k bojům s monstry. V klidu si prohlédni všechny Výpravy, které tu prodáváme!",
"ianBrokenText": "Vítej v obchodě s Výpravami... Můžeš tu s přáteli využít svitky s výpravami k bojům s monstry... V klidu si prohlédni všechny Výpravy, které tu prodáváme...",
+ "missingKeyParam": "\"req.params.key\" is required.",
+ "itemNotFound": "Item \"<%= key %>\" not found.",
+ "cannotBuyItem": "You can't buy this item.",
+ "missingTypeKeyEquip": "\"key\" and \"type\" are required parameters.",
+ "missingPetFoodFeed": "\"pet\" and \"food\" are required parameters.",
+ "invalidPetName": "Invalid pet name supplied.",
+ "missingEggHatchingPotionHatch": "\"egg\" and \"hatchingPotion\" are required parameters.",
+ "invalidTypeEquip": "\"type\" must be one of 'equipped', 'pet', 'mount', 'costume'.",
+ "mustPurchaseToSet": "Musíte koupit <%= val %> k nastavení na <%= key %>.",
+ "typeRequired": "Je požadován typ",
+ "keyRequired": "Je požadován klíč",
+ "notAccteptedType": "Type must be in [eggs, hatchingPotions, food, quests, gear]",
+ "contentKeyNotFound": "Klíč nenalezen pro Obsah <%= type %>",
+ "plusOneGem": "+1 Drahokam",
+ "typeNotSellable": "Nelze prodat. Lze prodat pouze <%= acceptedTypes %>",
+ "userItemsKeyNotFound": "Klíč nenalezen v user.items <%= type %>",
+ "pathRequired": "Je požadována cesta k vláknu",
+ "unlocked": "Předměty byly odemčeny",
+ "alreadyUnlocked": "Celý set je již odemčen.",
+ "alreadyUnlockedPart": "Celý set je již částečně odemčen.",
"USD": "(USD)",
"newStuff": "Nové věci",
"cool": "Připomeň mi to později",
@@ -31,11 +51,11 @@
"donationDesc": "20 drahokamů, Příspěvek vývojářům",
"payWithCard": "Zaplatit kartou",
"payNote": "Poznámka: Zpracování platby přes PayPal někdy trvá delší dobu. Doporučujeme platit kartou.",
- "card": "Credit Card (using Stripe)",
+ "card": "Platební kartou (za použití proužku)",
"amazonInstructions": "Klikni pro zaplacení přes Amazon platby",
- "paymentMethods": "Purchase using",
+ "paymentMethods": "Platební metody:",
"classGear": "Vybavení pro tvé povolání",
- "classGearText": "First: don't panic! Your old gear is in your inventory, and you're now wearing the apprentice equipment of your new class. Wearing your class's gear grants you a 50% bonus to stats. However, feel free to switch back to your old gear.",
+ "classGearText": "Za prvé: Nepanikař! Tvé staré vybavení je ve tvém inventáři a nyní nosíš začátečnické vybavení svého nového povolání. Nošení vybavení tvého povolání ti přidává 50% bonus k vlastnostem. Neboj se však vrátit ke svému starému vybavení.",
"classStats": "Toto jsou statistiky tvého povolání, ovlivňují hratelnost. Pokaždé, když postoupíš o úroveň výš, dostaneš jeden bod, který můžeš přiřadit k určité statistice. Najeď myší na každou statistiku pro více informací.",
"autoAllocate": "Připisovat automaticky",
"autoAllocateText": "Pokud je zaškrtnuto 'automatické připisování', získává tvůj avatar body automaticky na základě atributů tvých úkolů, které najdeš v ÚKOL > Editovat > Pokročilé > Attributy. Např. pokud budeš chodit často do posilovny a tvoje denní 'posilovna' je nastavena na 'fyzický', budeš automaticky získávat sílu.",
@@ -45,7 +65,7 @@
"moreClass": "Pro více informací o systému povolání, přejdi na",
"tourWelcome": "Vítej v zemi Habitica! Tohle tvůj Úkolníček. Odškrtni si úkol abys mohl pokračovat!",
"tourExp": "Skvělá práce! Odškrtnutí úkolu ti přidává Zkušenost a Zlaťáky!",
- "tourDailies": "This column is for Daily tasks. To proceed, enter a task you should do every day! Sample Dailies: Make Bed, Floss, Check Work Email",
+ "tourDailies": "Tento sloupec je pro denní úkoly. Přidej sem úkol, který bys měl plnit každý den! Příklady denních úkolů: Ustlat postel, Použít dentální nit, Zkontrolovat pracovní e-mail",
"tourCron": "Úžasné! Tvé Denní úkoly se budou resetovat každý den.",
"tourHP": "Bacha! Když nesplníš Denní úkol do půlnoci, tak ti ublíží!",
"tourHabits": "Tenhle sloupeček je pro dobré zvyky a zlozvyky, které děláš několikrát denně! Abys mohl pokračovat, klikni na tužku abys mohl upravit názvy, potom klikni na fajfku, aby se ti změny uložily.",
@@ -64,6 +84,7 @@
"tourPetsPage": "Tohle je Stáj! Po 3. úrovni si tu budeš moci líhnout Mazlíčky za pomocí vajíček a líhnoucích lektvarů. Když si vylíhneš Mazlíčka na Trhu, objeví se tady! Klikni na Mazlíčka a přidáš si ho ke svému avataru. Krm ho jídlem, které budeš nacházet po 3. úrovni, a oni vyrostou v silná zvířata, na kterých se budeš moci projet.",
"tourMountsPage": "Jakmile dostatečně nakrmíš mazlíčka, objeví se tady a budeš se na něm moci projet. (Mazlíčci, zvířata k osedlání a jídlo jsou k dispozici po 3. úrovni.) Klikni na zvíře, které si chceš osedlat!",
"tourEquipmentPage": "Tady se ti ukládá vybavení! Tvá Bojová zbroj ovlivňuje tvé statistiky. Pokud chceš, aby se ti zobrazovalo jiné vybavení na tvém avataru aniž by se ti statistiky nějak ovlivnily, klikni na \"Povolit kostým.\"",
+ "equipmentAlreadyOwned": "Tuto část vybavení již vlastníte",
"tourOkay": "Ok!",
"tourAwesome": "Skvělé!",
"tourSplendid": "Velkolepé!",
diff --git a/common/locales/cs/pets.json b/common/locales/cs/pets.json
index 30de302ec2..1ef2ea83bd 100644
--- a/common/locales/cs/pets.json
+++ b/common/locales/cs/pets.json
@@ -12,6 +12,7 @@
"etherealLion": "Éterický lev",
"veteranWolf": "Vlk veterán",
"veteranTiger": "Tygr veterán",
+ "veteranLion": "Veteran Lion",
"cerberusPup": "Štěně Kerbera",
"hydra": "Hydra",
"mantisShrimp": "Strašek paví",
@@ -19,7 +20,7 @@
"orca": "Kosatka",
"royalPurpleGryphon": "Vznešený fialový gryf",
"phoenix": "Fénix",
- "bumblebee": "Bumblebee",
+ "magicalBee": "Magická včela",
"rarePetPop1": "Klikni na zlatou packu pro více informací o tom, jak získat toto vzácné zvíře za přispívání programu Habitica!",
"rarePetPop2": "Jak získat toto zvíře!",
"potion": "<%= potionType %> lektvar",
@@ -41,7 +42,7 @@
"stableBeastMasterProgress": "Pokrok Pána šelem: nalezl <%= number %> mazlíčků",
"beastAchievement": "Získal jsi ocenění \"Pán šelem\" za získání všech mazlíčků!",
"beastMasterName": "Pán šelem",
- "beastMasterText": "Nalezl všech 90 zvířátek ( šíleně obtížné, zaslouží si uznání!)",
+ "beastMasterText": "Nalezl všech 90 mazlíčků ( šíleně obtížné, zaslouží si uznání!)",
"beastMasterText2": "a vypustil své mazlíčky celkem <%= count %>krát",
"mountMasterProgress": "Pokrok Pána zvířat",
"stableMountMasterProgress": "Pokrok krotitele zvířat: zkroceno <%= number %> zvířat",
@@ -62,6 +63,7 @@
"hatchedPet": "Vylíhl se ti <%= potion %> <%= egg %>!",
"displayNow": "Zobrazit hned",
"displayLater": "Zobrazit později",
+ "petNotOwned": "Nevlastníte tohoto mazlíčka.",
"earnedCompanion": "Za svou pracovitost sis vysloužil nového kamaráda. Nakrm ho, aby vyrostl!",
"feedPet": "Myslíš, že <%= name %> si pochutná na něčem takovém jako je <%= article %><%= text %> ?",
"useSaddle": "Koho osedláme? Bude to <%= pet %>?",
@@ -83,5 +85,8 @@
"petKeyBoth": "Vypusť oba",
"confirmPetKey": "Jsi si jistý?",
"petKeyNeverMind": "Ještě ne",
+ "petsReleased": "Mazlíčci propuštěni.",
+ "mountsAndPetsReleased": "Zvířata k osedlání a mazlíčci propuštěni",
+ "mountsReleased": "Zvířata k osedlání propuštěna",
"gemsEach": "drahokamů každý"
}
\ No newline at end of file
diff --git a/common/locales/cs/quests.json b/common/locales/cs/quests.json
index 283d8db5d6..332ffaf336 100644
--- a/common/locales/cs/quests.json
+++ b/common/locales/cs/quests.json
@@ -38,7 +38,7 @@
"bossDmg2": "Jen ti, kteří přijmou pozvání, budou bojovat proti příšeře a rozdělí si odměnu za výpravu.",
"bossDmg1Broken": "Každý splnění Denní úkol, úkol z Úkolníčku a každý pozitivní zvyk zraní příšeru... Zraň jí víc červenějšími úkoly nebo Brutální ranou nebo Vzplanutím ohňů... Příšera zraní každého účastníka výpravy za každý nesplněný Denní úkol (újma je násobena jeho Silou) navíc k normálnímu zranění, takže udržuj skupinu zdravou plněním úkolů. Veškerá zranění příšeře i vám se přičítají na kronu (na konci dne).",
"bossDmg2Broken": "Jen ti, kteří přijmou pozvání, budou bojovat proti příšeře a rozdělí si odměnu za výpravu...",
- "tavernBossInfo": "Complete Dailies and To-Dos and score positive Habits to damage the World Boss! Incomplete Dailies fill the Rage Bar. When the Rage bar is full, the World Boss will attack an NPC. A World Boss will never damage individual players or accounts in any way. Only active accounts not resting in the Inn will have their tasks tallied.",
+ "tavernBossInfo": "Plň denní úkoly a úkoly v úkolníčku a buduj zvyky aby jsi zranil Světovou příšeru! Nesplněné denní úkoly živí příšeřin vztek. Když je vzteku již moc, příšera zaútočí na nějakou nehráčskou postavu. Světová příšera nikdy nijak nezraní jednotlivé hráče nebo jejich účty. Pouze aktivní hráči, kteří neodpočívají v Hostinci, se mohou podílet na boji proti příšeře.",
"tavernBossInfoBroken": "Splň denní úkoly a úkoly v Úkolníčku a zraň tak Světovou příšeru... Nesplněné denní úkoly naplní lištu Výpadu... Když se tato lišta naplní, příšera zaútočí na nějakou nehráčskou postavu... Světová příšera nikdy nezraní jednotlivé hráče... Pouze aktivní hráči, kteří neodpočívají v Hostinci se mohou podílet na zranění příšery...",
"bossColl1": "Pro získání předmětů plň své pozitivní úkoly. Předměty z výpravy budeš nacházet stejně jako normální předměty, uvidíš je však až další den, kdy se vše, co jste našli, shromáždí a přidá na hromadu.",
"bossColl2": "Jen ti, kteří přijmou pozvání, mohou sbírat předměty a rozdělit si odměnu za výpravu.",
@@ -78,5 +78,24 @@
"whichQuestStart": "Na kterou výpravu se chceš vydat?",
"getMoreQuests": "Získej více výprav",
"unlockedAQuest": "Odemkl jsi výpravu!",
- "leveledUpReceivedQuest": "Dosáhl jsi úrovně <%= level %> a získal jsi svitek s výpravou!"
+ "leveledUpReceivedQuest": "Dosáhl jsi úrovně <%= level %> a získal jsi svitek s výpravou!",
+ "questInvitationDoesNotExist": "Zatím vám nebyla poslána žádná pozvánka na výpravu.",
+ "questInviteNotFound": "Nebyla nalezena žádná pozvánka na výpravu.",
+ "guildQuestsNotSupported": "Cechy nelze pozvat na výpravy.",
+ "questNotFound": "Výprava „<%= key %>\" nenalezena.",
+ "questNotOwned": "Nevlastníte tento svitek výpravy.",
+ "questNotGoldPurchasable": "Výpravu „<%= key %>\" nelze koupit na zlato.",
+ "questLevelTooHigh": "Pro začátek výpravy musíte mít úroveň <%= level %>.",
+ "questAlreadyUnderway": "Vaše družina se již účastní výpravy. Až dokončíte současnou výpravu zkuste to znova.",
+ "questAlreadyAccepted": "Již jsi přijal pozvánku na výpravu.",
+ "noActiveQuestToLeave": "Žádné aktivní výpravy k opuštění.",
+ "questLeaderCannotLeaveQuest": "Vůdce výpravy nemůže opustit výpravu.",
+ "notPartOfQuest": "Nejsi součástí výpravy.",
+ "noActiveQuestToAbort": "Žádné aktivní výpravy ke zrušení.",
+ "onlyLeaderAbortQuest": "Pouze družina vůdce výpravy může zrušit výpravu.",
+ "questAlreadyRejected": "Jíž jsi zamítl pozvání na výpravu.",
+ "cantCancelActiveQuest": "Nemůžete odřeknout aktivní výpravu, použijte funkci zrušení.",
+ "onlyLeaderCancelQuest": "Pouze družina vůdce může odřeknout výpravu.",
+ "questNotPending": "Nelze začít žádnou výpravu.",
+ "questOrGroupLeaderOnlyStartQuest": "Pouze vůdce výpravy nebo vůdce skupiny může započít výpravu."
}
\ No newline at end of file
diff --git a/common/locales/cs/questscontent.json b/common/locales/cs/questscontent.json
index ca671feb9a..010582d44a 100644
--- a/common/locales/cs/questscontent.json
+++ b/common/locales/cs/questscontent.json
@@ -1,12 +1,12 @@
{
"questEvilSantaText": "Santa - Lovec kožešin",
- "questEvilSantaNotes": "You hear agonized roars deep in the icefields. You follow the growls - punctuated by the sound of cackling - to a clearing in the woods, where you see a fully-grown polar bear. She's caged and shackled, fighting for her life. Dancing atop the cage is a malicious little imp wearing a castaway costume. Vanquish Trapper Santa, and save the beast!",
+ "questEvilSantaNotes": "Hluboko na ledových pláních slyšíš zoufalý řev. Sleduješ vrčení přerušované chichotáním a dojdeš na mýtinu v lese, kde uvidíš velkou lední medvědici. Spoutaná v kleci bojuje o život. Nahoře na kleci tančí zlomyslný malý skrček ve zbytcích vánočního kostýmu. Poraž Santu, lovce kožešin, a zachraň to zvíře!",
"questEvilSantaCompletion": "Trapper Santa squeals in anger, and bounces off into the night. The grateful she-bear, through roars and growls, tries to tell you something. You take her back to the stables, where Matt Boch the Beast Master listens to her tale with a gasp of horror. She has a cub! He ran off into the icefields when mama bear was captured.",
"questEvilSantaBoss": "Santa - Lovec kožešin",
"questEvilSantaDropBearCubPolarMount": "Lední medvěd (zkrocený)",
"questEvilSanta2Text": "Najdi mládě",
"questEvilSanta2Notes": "When Trapper Santa captured the polar bear mount, her cub ran off into the icefields. You hear twig-snaps and snow crunch through the crystalline sound of the forest. Paw prints! You start racing to follow the trail. Find all the prints and broken twigs, and retrieve the cub!",
- "questEvilSanta2Completion": "You've found the cub! It will keep you company forever.",
+ "questEvilSanta2Completion": "Našel jsi mládě! Bude navždy s tebou.",
"questEvilSanta2CollectTracks": "Stopy",
"questEvilSanta2CollectBranches": "Polámané větvičky",
"questEvilSanta2DropBearCubPolarPet": "Lední medvěd (mazlíček)",
@@ -36,7 +36,7 @@
"questRatUnlockText": "Odemyká vejce myšáka na Trhu",
"questOctopusText": "Volání Octothulu",
"questOctopusNotes": "@Urse, vyděšený mladý písař, tě požádal o pomoc při průzkumu záhadné jeskyně na břehu moře. Mezi třpytícími se tůňkami se tkví obrovská brána ze stalaktitů a stalagmitů. Když se k té bráně přibližujete, začne se u ní točit tmavý vodní vír. Užasle zíráte jak se z něj vynořuje sépiovitý drak. \"Ulepený zplozenec hvězd se probudil,\" zaječí @Urse šíleně. \"Po všech těch věcích je velký Octothulu znovu volný a lační po potěšení!\"",
- "questOctopusCompletion": "With a final blow, the creature slips away into the whirlpool from which it came. You cannot tell if @Urse is happy with your victory or saddened to see the beast go. Wordlessly, your companion points to three slimy, gargantuan eggs in a nearby tidepool, set in a nest of gold coins. \"Probably just octopus eggs,\" you say nervously. As you return home, @Urse frantically scribbles in a journal and you suspect this is not the last time you will hear of the great Octothulu.",
+ "questOctopusCompletion": "S posledním zásahem, se potvora vytratila do víru, ze kterého vzešla. Nejste si jistý jak se @Urse cítí, zda-li je šťastný, protože jste vyhráli nebo smutný, protože bestie zmizela. Bez jediného slova váš společník ukazuje na tři obrovská slizká vejce v nedalekém přílivovém jezírku, nacházejícím se v hroudě zlatých mincí. „Snad jsou to jen vajíčka nějakých chobotnic\", nervózně podotknete. Když se vracíte domu, @Urse šíleně čmárá do deníku, a vy tušíte, že tohle není naposledy co slyšíte o monstru zvaném „Octothulu\".",
"questOctopusBoss": "Octothulu",
"questOctopusDropOctopusEgg": "Chobotnice (Vejce)",
"questOctopusUnlockText": "Odemyká vejce chobotnice na Trhu",
@@ -140,7 +140,7 @@
"questAtom3Notes": "S ohlušujícím řevem a pěti lahodnými typy sýrů vyletujících z její tlamy se Nesvačinvá příšera rozpadá. \"JAK SE OPOVAŽUJETE!\" zahřmí hlas zpod vodní hladiny. Modrá postava v hábitu držící štětku na záchod se vynoří z vody. Špinavé prádlo se začne vynořovat na hladinu jezera. \"Já jsem Prádlomancr!\" naštvaně se představí. \"To máte tedy odvahu umýt mé úžasně špinavé prádlo, zničit mého mazlíčka a vstoupit na mé území v takových čistých hadrech. Připravte se pocítit mokrý hněv mé magie proti čistotě!\"",
"questAtom3Completion": "Zákeřný Prádlomancr byl poražen! Čisté prádlo před vámi padá na hromádky. Už to tu vypadá mnohem lépe. Když začnete pomalu procházet skrz čistě vyprané brnění, všimnete si lesku kovu a váš zrak spočine na zářící helmě. Původní majitel tohoto zářícího předmětu může být neznámý, ale jakmile si ji nasadíš, cítíš přívětivou přítomnost štědrého ducha. Škoda, že si na ní původní majitel nenašil jmenovku.",
"questAtom3Boss": "Prádlomancr",
- "questAtom3DropPotion": "Base Hatching Potion",
+ "questAtom3DropPotion": "Základní líhnoucí lektvar",
"questOwlText": "Sýček",
"questOwlNotes": "The Tavern light is lit 'til dawn Until one eve the glow is gone! How can we see for our all-nighters? @Twitching cries, \"I need some fighters! See that Night-Owl, starry foe? Fight with haste and do not slow! We'll drive its shadow from our door, And make the night shine bright once more!\"",
"questOwlCompletion": "Sýček před úsvitem mizí, Ale i tak cití, jak na tebe jde zývnutí. Možá je čas na odpočinek? A pak na své posteli uvidíš hnízdo! Sýček ví, že může být skvělé Dokončit práci a zůstat vzhůru déle, Ale tví noví mazlíčky budou jemně pípat Aby ti řekli, kdy je čas jít spát.",
@@ -280,33 +280,45 @@
"questUnicornBoss": "Královna jednorožců",
"questUnicornDropUnicornEgg": "Jednorožec (vejce)",
"questUnicornUnlockText": "Odemyká vejce jednorožce na Trhu",
- "questSabretoothText": "The Sabre Cat",
+ "questSabretoothText": "Šavlozubá kočka",
"questSabretoothNotes": "A roaring monster is terrorizing Habitica! The creature stalks through the wilds and woods, then bursts forth to attack before vanishing again. It's been hunting innocent pandas and frightening the flying pigs into fleeing their pens to roost in the trees. @Inventrix and @icefelis explain that the Zombie Sabre Cat was set free while they were excavating in the ancient, untouched ice-fields of the Stoikalm Steppes. \"It was perfectly friendly at first – I don't know what happened. Please, you have to help us recapture it! Only a champion of Habitica can subdue this prehistoric beast!\"",
"questSabretoothCompletion": "After a long and tiring battle, you wrestle the Zombie Sabre Cat to the ground. As you are finally able to approach, you notice a nasty cavity in one of its sabre teeth. Realising the true cause of the cat's wrath, you're able to get the cavity filled by @Fandekasp, and advise everyone to avoid feeding their friend sweets in future. The Sabre Cat flourishes, and in gratitude, its tamers send you a generous reward – a clutch of sabretooth eggs!",
- "questSabretoothBoss": "Zombie Sabre Cat",
- "questSabretoothDropSabretoothEgg": "Sabretooth (Egg)",
- "questSabretoothUnlockText": "Unlocks purchasable Sabretooth eggs in the Market",
- "questMonkeyText": "Monstrous Mandrill and the Mischief Monkeys",
+ "questSabretoothBoss": "Zombie šavlozubá kočka",
+ "questSabretoothDropSabretoothEgg": "Šavlozubec (vejce)",
+ "questSabretoothUnlockText": "Odemyká vejce šavlozubce na Trhu",
+ "questMonkeyText": "Obludný mandril a opice neplechy",
"questMonkeyNotes": "The Sloensteadi Savannah is being torn apart by the Monstrous Mandrill and his Mischief Monkeys! They shriek loudly enough to drown out the sound of approaching deadlines, encouraging everyone to avoid their duties and keep monkeying around. Alas, plenty of people ape this bad behavior. If no one stops these primates, soon everyone's tasks will be as red as the Monstrous Mandrill's face!
\"It will take a dedicated adventurer to resist them,\" says @yamato.
\"Quick, let's get this monkey off everyone's backs!\" @Oneironaut yells, and you charge into battle.",
- "questMonkeyCompletion": "You did it! No bananas for those fiends today. Overwhelmed by your diligence, the monkeys flee in panic. \"Look,\" says @Misceo. \"They left a few eggs behind.\"
@Leephon grins. \"Maybe a well-trained pet monkey can help you as much as the wild ones hinder you!\"",
- "questMonkeyBoss": "Monstrous Mandrill",
- "questMonkeyDropMonkeyEgg": "Monkey (Egg)",
- "questMonkeyUnlockText": "Unlocks purchasable Monkey eggs in the Market",
- "questSnailText": "The Snail of Drudgery Sludge",
+ "questMonkeyCompletion": "Zvládl jsi to! Ti zlosynové dnes žádné banány nedostanou. Přemoceni tvou pracovitostí, opice v panice prchají. „Podívej,\" říká @Misceo. „Nechali za sebou pár vajec.\"
@Leephon se šklebí. „Možná ti vytrénovaná opice pomůže stejně tak, jako ta divoká uškodí!\"",
+ "questMonkeyBoss": "Obludný mandril",
+ "questMonkeyDropMonkeyEgg": "Opice (vejce)",
+ "questMonkeyUnlockText": "Odemyká vejce opic na Trhu",
+ "questSnailText": "Šnek otrockého bahna",
"questSnailNotes": "You're excited to begin questing in the abandoned Dungeons of Drudgery, but as soon as you enter, you feel the ground under your feet start to suck at your boots. You look up to the path ahead and see Habiticans mired in slime. @Overomega yells, \"They have too many unimportant tasks and dailies, and they're getting stuck on things that don't matter! Pull them out!\"
\"You need to find the source of the ooze,\" @Pfeffernusse agrees, \"or the tasks that they cannot accomplish will drag them down forever!\"
Pulling out your weapon, you wade through the gooey mud.... and encounter the fearsome Snail of Drudgery Sludge.",
"questSnailCompletion": "You bring your weapon down on the great Snail's shell, cracking it in two, releasing a flood of water. The slime is washed away, and the Habiticans around you rejoice. \"Look!\" says @Misceo. \"There's a small group of snail eggs in the remnants of the muck.\"",
- "questSnailBoss": "Snail of Drudgery Sludge",
- "questSnailDropSnailEgg": "Snail (Egg)",
- "questSnailUnlockText": "Unlocks purchasable Snail eggs in the Market",
- "questBewilderText": "The Be-Wilder",
+ "questSnailBoss": "Šnek otrockého bahna",
+ "questSnailDropSnailEgg": "Šnek (vejce)",
+ "questSnailUnlockText": "Odemyká vejce šneka na Trhu",
+ "questBewilderText": "Be-Wilder",
"questBewilderNotes": "The party begins like any other.
The appetizers are excellent, the music is swinging, and even the dancing elephants have become routine. Habiticans laugh and frolic amid the overflowing floral centerpieces, happy to have a distraction from their least-favorite tasks, and the April Fool whirls among them, eagerly providing an amusing trick here and a witty twist there.
As the Mistiflying clock tower strikes midnight, the April Fool leaps onto the stage to give a speech.
“Friends! Enemies! Tolerant acquaintances! Lend me your ears.” The crowd chuckles as animal ears sprout from their heads, and they pose with their new accessories.
“As you know,” the Fool continues, “my confusing illusions usually only last a single day. But I’m pleased to announce that I’ve discovered a shortcut that will guarantee us non-stop fun, without having to deal with the pesky weight of our responsibilities. Charming Habiticans, meet my magical new friend... the Be-Wilder!”
Lemoness pales suddenly, dropping her hors d'oeuvres. “Wait! Don’t trust--”
But suddenly mists are pouring into the room, glittering and thick, and they swirl around the April Fool, coalescing into cloudy feathers and a stretching neck. The crowd is speechless as an monstrous bird unfolds before them, its wings shimmering with illusions. It lets out a horrible screeching laugh.
“Oh, it has been ages since a Habitican has been foolish enough to summon me! How wonderful it feels, to have a tangible form at last.”
Buzzing in terror, the magic bees of Mistiflying flee the floating city, which sags from the sky. One by one, the brilliant spring flowers wither up and wisp away.
“My dearest friends, why so alarmed?” crows the Be-Wilder, beating its wings. “There’s no need to toil for your rewards any more. I’ll just give you all the things that you desire!”
A rain of coins pours from the sky, hammering into the ground with brutal force, and the crowd screams and flees for cover. “Is this a joke?” Baconsaur shouts, as the gold smashes through windows and shatters roof shingles.
PainterProphet ducks as lightning bolts crackle overhead, and fog blots out the sun. “No! This time, I don’t think it is!”
Quickly, Habiticans, don’t let this World Boss distract us from our goals! Stay focused on the tasks that you need to complete so we can rescue Mistiflying -- and hopefully, ourselves.",
"questBewilderCompletion": "The Be-Wilder is DEFEATED!
We've done it! The Be-Wilder lets out a ululating cry as it twists in the air, shedding feathers like falling rain. Slowly, gradually, it coils into a cloud of sparkling mist. As the newly-revealed sun pierces the fog, it burns away, revealing the coughing, mercifully human forms of Bailey, Matt, Alex.... and the April Fool himself.
Mistiflying is saved!
The April Fool has enough shame to look a bit sheepish. “Oh, hm,” he says. “Perhaps I got a little…. carried away.”
The crowd mutters. Sodden flowers wash up on sidewalks. Somewhere in the distance, a roof collapses with a spectacular splash.
“Er, yes,” the April Fool says. “That is. What I meant to say was, I’m dreadfully sorry.” He heaves a sigh. “I suppose it can’t all be fun and games, after all. It might not hurt to focus occasionally. Maybe I’ll get a head start on next year’s pranking.”
Redphoenix coughs meaningfully.
“I mean, get a head start on this year’s spring cleaning!” the April Fool says. “Nothing to fear, I’ll have Habit City in spit-shape soon. Luckily nobody is better than I at dual-wielding mops.”
Encouraged, the marching band starts up.
It isn’t long before all is back to normal in Habit City. Plus, now that the Be-Wilder has evaporated, the magical bees of Mistiflying bustle back to work, and soon the flowers are blooming and the city is floating once more.
As Habiticans cuddle the magical fuzzy bees, the April Fool’s eyes light up. “Oho, I’ve had a thought! Why don’t you all keep some of these fuzzy Bee Pets and Mounts? It’s a gift that perfectly symbolizes the balance between hard work and sweet rewards, if I’m going to get all boring and allegorical on you.” He winks. “Besides, they don’t have stingers! Fool’s honor.”",
"questBewilderCompletionChat": "`The Be-Wilder is DEFEATED!`\n\nWe've done it! The Be-Wilder lets out a ululating cry as it twists in the air, shedding feathers like falling rain. Slowly, gradually, it coils into a cloud of sparkling mist. As the newly-revealed sun pierces the fog, it burns away, revealing the coughing, mercifully human forms of Bailey, Matt, Alex.... and the April Fool himself.\n\n`Mistiflying is saved!`\n\nThe April Fool has enough shame to look a bit sheepish. “Oh, hm,” he says. “Perhaps I got a little…. carried away.”\n\nThe crowd mutters. Sodden flowers wash up on sidewalks. Somewhere in the distance, a roof collapses with a spectacular splash.\n\n“Er, yes,” the April Fool says. “That is. What I meant to say was, I’m dreadfully sorry.” He heaves a sigh. “I suppose it can’t all be fun and games, after all. It might not hurt to focus occasionally. Maybe I’ll get a head start on next year’s pranking.”\n\nRedphoenix coughs meaningfully.\n\n“I mean, get a head start on this year’s spring cleaning!” the April Fool says. “Nothing to fear, I’ll have Habit City in spit-shape soon. Luckily nobody is better than I at dual-wielding mops.”\n\nEncouraged, the marching band starts up.\n\nIt isn’t long before all is back to normal in Habit City. Plus, now that the Be-Wilder has evaporated, the magical bees of Mistiflying bustle back to work, and soon the flowers are blooming and the city is floating once more.\n\nAs Habiticans cuddle the magical fuzzy bees, the April Fool’s eyes light up. “Oho, I’ve had a thought! Why don’t you all keep some of these fuzzy Bee Pets and Mounts? It’s a gift that perfectly symbolizes the balance between hard work and sweet rewards, if I’m going to get all boring and allegorical on you.” He winks. “Besides, they don’t have stingers! Fool’s honor.”",
- "questBewilderBossRageTitle": "Beguilement Strike",
- "questBewilderBossRageDescription": "When this gauge fills, The Be-Wilder will unleash its Beguilement Strike on Habitica!",
- "questBewilderDropBumblebeePet": "Bumblebee (Pet)",
- "questBewilderDropBumblebeeMount": "Bumblebee (Mount)",
+ "questBewilderBossRageTitle": "Výpad okouzlení",
+ "questBewilderBossRageDescription": "Když se tato lišta naplní, Be-Wilder provede Výpad rozptýlení na Habitiku!",
+ "questBewilderDropBumblebeePet": "Kouzelná včela (mazlíček)",
+ "questBewilderDropBumblebeeMount": "Kouzelná včela (zkrocená)",
"questBewilderBossRageMarket": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nOh no! Despite our best efforts, we've gotten distracted by the Be-Wilder’s charming illusions and have forgotten to do some of our Dailies! With a cackling cry, the shining bird beats its wings, raising a swarm of mist around Alex the Merchant. When the fog clears, he has been possessed! “Have some free samples!” he shouts gleefully, and begins to hurl exploding eggs and potions at fleeing Habiticans. Not the most favorable of sales, to be sure.\n\nHurry! Let's stay focused on our Dailies to defeat this monster before it possesses someone else.",
"questBewilderBossRageStables": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nAhh!!! Once again the Be-Wilder has dazzled us into neglecting our Dailies, and now it has attacked Matt the Beast Master! With a swirl of mist, Matt transforms into a terrifying winged creature, and all the pets and mounts howl sadly in their stables. Quickly, stay focused on your tasks to defeat this dastardly distraction!",
- "questBewilderBossRageBailey": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nLook out! In the middle of reporting the news, Bailey the Town Crier has been possessed by the Be-Wilder! She lets out an evil, uninformative screech as she rises into the air. Now how will we know what’s going on?\n\nDon't give up... we're so close to defeating this bothersome bird for once and for all!"
+ "questBewilderBossRageBailey": "`Be-Wilder použil Výpad okouzlení!`\n\nPozor! Když se vás Bailey The Town Crier pokusila varovat Be-Wilder jí posedl! Při stoupání do vzduchu začala strašlivě pištět. Jak budeme teď vědět co se děje?\n\nNevzdávej to... jsme tak blízko k poražení tohohle otravného ptáka jednou provždy!",
+ "questFalconText": "Ptáci otálení",
+ "questFalconNotes": "Hora Habitica je zastíněna rostoucí horou úkolů. Bývalo to místo pikniků a užívání si pocitu úspěchu, dokud se nezanedbané úkoly nestaly nekontrolovatelnými. Nyní je hora domovem ptáků otálení, zlých příšer, které bráním Habiťanům v plnění úkolů.
„Je to příliš těžké!\", krákají na @JonArinbjorn a @Onheiron. „Udělat to teď bude trvat příliš dlouho! Je jedno počkáš-li do zítřka! Proč radši neděláš něco zábavného?\"
Nikdy víc, přísaháš. Ty zdoláš svou horu úkolů a porazíš ptáky otálení!",
+ "questFalconCompletion": "Když konečně porazíte ptáky otálení, posadíte se, abyste si užili výhled a váš zasloužený odpočinek.
„Vau!\", řekla @Trogdorina. „Zvládl jsi to!\"
@Squish se přidá, „Vem si tyto vajíčka, které jsem našel, jako odměnu.\"",
+ "questFalconBoss": "Ptáci otálení",
+ "questFalconDropFalconEgg": "Sokol (vejce)",
+ "questFalconUnlockText": "Odemyká vejce sokola na Trhu",
+ "questTreelingText": "The Tangle Tree",
+ "questTreelingNotes": "It's the annual Garden Competition, and everyone is talking about the mysterious project which @aurakami has promised to unveil. You join the crowd on the day of the big announcement, and marvel at the introduction of a moving tree. @fuzzytrees explains that the tree will help with garden maintenance, showing how it can mow the lawn, trim the hedge and prune the roses all at the same time – until the tree suddenly goes wild, turning its secateurs on its creator! The crowd panics as everyone tries to flee, but you aren't afraid – you leap forward, ready to do battle.",
+ "questTreelingCompletion": "You dust yourself off as the last few leaves drift to the floor. In spite of the upset, the Garden Competition is now safe – although the tree you just reduced to a heap of wood chips won't be winning any prizes! \"Still a few kinks to work out there,\" @PainterProphet says. \"Perhaps someone else would do a better job of training the saplings. Do you fancy a go?\"",
+ "questTreelingBoss": "Tangle Tree",
+ "questTreelingDropTreelingEgg": "Treeling (Egg)",
+ "questTreelingUnlockText": "Unlocks purchasable Treeling eggs in the Market"
}
\ No newline at end of file
diff --git a/common/locales/cs/rebirth.json b/common/locales/cs/rebirth.json
index 855c764d6f..afd92722aa 100644
--- a/common/locales/cs/rebirth.json
+++ b/common/locales/cs/rebirth.json
@@ -5,14 +5,14 @@
"rebirthStartOver": "Znovuzrození obnoví tvou postavu na úrovni 1.",
"rebirthAdvList1": "Bylo ti obnoveno plné zdraví.",
"rebirthAdvList2": "Nemáš žádné zkušenosti, zlaťáky, ani vybavení (kromě předmětů zdarma, jako například Záhadné předměty).",
- "rebirthAdvList3": "Tvé zvyky, denní úkoly a úkoly v úkolníčku se resetují na žluté a série úspěšnosti se resetují také.",
+ "rebirthAdvList3": "Vaše zvyky, denní úkoly, a úkoly se stanou žlutými, vaše řada úspěchů se resetuje, nezahrnuje výzvy.",
"rebirthAdvList4": "Začínáš s povoláním válečníka, dokud nezískáš novou třídu.",
"rebirthInherit": "Tvá nová postava zdědila pár věcí od svého předchůdce:",
"rebirthInList1": "Úkoly, historie a nastavení zůstavají stejné.",
"rebirthInList2": "Členství ve výzvách, družině a v ceších zůstávají.",
"rebirthInList3": "Drahokamy, úrovně podpory a úrovně pomoci zůstanou.",
"rebirthInList4": "Předměty získané za drahokamy nebo za úkoly (jako mazlíčci a zkrocená zvířata) zůstávají, i když se k nim nedostaneš, dokud je znovu neodemkneš.",
- "rebirthInList5": "Vybavení z limitovaných edic, které jsi si koupil, mohou být koupeny znovu, dokonce i když akce skončila. Aby jsi si mohl znovu koupit vybavení pro určité povolání, musíš nejprve zvolit správné povolání.",
+ "rebirthInList5": "Vybavení z limitovaných edic, které jsi si koupil, mohou být koupeny znovu, dokonce i když akce skončila. Aby sis mohl znovu koupit vybavení pro určité povolání, musíš nejprve zvolit správné povolání.",
"rebirthEarnAchievement": "Také dostaneš ocenění za začátek nového dobrodružství.",
"beReborn": "Buď znovuzrozen",
"rebirthAchievement": "Začal jsi nové dobrodružství! Tohle je Znovuzrození číslo <%= number %> a nejvyšší úroveň, které jsi dosáhl je <%= level %>. K dosažení dalšího ocenění začni nové dobrodružství až dosáhneš ještě vyšší úrovně!",
@@ -24,5 +24,6 @@
"rebirthPop": "Začni s novou postavou na úrovni 1 a přitom si zachovej ocenění, sběratelské předměty a úkoly s historií.",
"rebirthName": "Koule znovuzrození",
"reborn": "Znovuzrozen, maximální úroveň <%= reLevel %>",
- "confirmReborn": "Jsi si jistý?"
+ "confirmReborn": "Jsi si jistý?",
+ "rebirthComplete": "Byl jste znovuzrozen!"
}
\ No newline at end of file
diff --git a/common/locales/cs/settings.json b/common/locales/cs/settings.json
index 3b5a335674..00bf8bec68 100644
--- a/common/locales/cs/settings.json
+++ b/common/locales/cs/settings.json
@@ -47,6 +47,7 @@
"customDayStart": "Vlastní začátek dne",
"changeCustomDayStart": "Změnit vlastní začátek dne?",
"sureChangeCustomDayStart": "Jsi si jistý, že chceš změnit svůj vlastní začátek dne?",
+ "customDayStartHasChanged": "Your custom day start has changed.",
"nextCron": "Tve Denní úkoly se resetují ve chvíli, kdy se po <%= time %> vrátíš do země Habitica. Ujisti se, že jsi je všechny stihnul dokončit před tímto časem!",
"customDayStartInfo1": "Při základním nastavení program Habitica kontroluje a resetuje tvé Denní úkoly každý den o půlnoci ve tvé časové zóně. Tento čas můžeš změnit zde.",
"misc": "Ostatní",
@@ -61,12 +62,23 @@
"newUsername": "Nové přihlašovací jméno",
"dangerZone": "Nebezpečná zóna",
"resetText1": "POZOR! Tímto resetujete mnoho částí svého účtu. Silně nedoporučujeme tuto možnost používat, ale vyhovuje to některým uživatelům, kteří si se stránkou na začátku trochu hrají.",
- "resetText2": "Přijdeš o všechny své úrovně, zlato a zkušenosti. Všechny tvé úkoly se permanentně smažou a ztratíš všechna historická data svých úkolů. Přijdeš o veškeré své vybavení, ale budeš si ho moci všechno znovu koupit, a to včetně předmětů z limitovaných edic nebo předplatitelské záhadné předměty které již vlastníš (pro znovuzakoupení některého vybavení musí mít tvoje postava odpovídající povolání). Ponecháš si aktuální povolání, mazlíčky a zkrocená zvířata. Možná bude lepší místo toho použít Kouli znovuzrození, která je bezpečnější a zachová tvoje úkoly.",
+ "resetText2": "Ztratíte veškeré úrovně, zlato, a zkušenosti. Všechny vaše úkoly (kromě těch z výzev) budou navždy vymazány a ztratíte všechny jejich data. Přijdete o všechno své vybavení ale budete si ho moci opět koupit, včetně všeho vybavení limitované edice nebo předplatitelských Záhadných předmětů, které již vlastníte (budete muset mít správné povolání k opakovanému koupení výbavy pro specifické povolání). Zůstane vám vaše povolání, mazlíčci a zvířata k osedlání. Zvažte použití Orbu znovuzrození, což je mnohem bezpečnější možnost, která vám zanechá vaše úkoly",
"deleteText": "Jsi si jist? Tímto smažeš svůj účet navždy a již nebude moci být obnoven! Budeš se muset znovu registrovat a vytvořit nový účet. Zakoupené a použité drahokamy nebudou proplaceny a budou ztraceny. Pokud si jsi absolutně jist, napiš <%= deleteWord %> do řádku níže.",
"API": "API",
+ "APIv3": "API v3",
+ "APIv2": "API v2 - Deprecated",
"APIText": "Zkopíruj je pro použití v aplikacích třetích stran. Avšak, stejně jako bys nikomu neřekl své heslo, tak nikomu neříkej svůj API Token. Někdy můžeš být požádán o své uživatelské ID, ale nikdy neuveřejňuj svůj API Token tam, kde ho uvidí ostatní a to včetně Githubu.",
"APIToken": "API token (toto je heslo - přečti si upozornění nahoře!)",
+ "thirdPartyApps": "Programy třetí strany",
+ "dataToolDesc": "Webová stránka, která zobrazuje určité informace z tvého Habitica účtu, jako statistiky o tvých úkolech, vybavení a schopnostech.",
+ "beeminder": "Beeminder",
+ "beeminderDesc": "Nech Beeminder aby automaticky monitoroval tvoje úkoly v Habitice. Můžeš se rozhodnout pro udržení určitého čísla udělaných úkolů za den nebo týden nebo se můžeš rozhodnout pro postupné plnění nedokončených úkolů. (Použití Beeminder je pod hrozbou placení opravdových peněz. Ale jenom možná máš rád Beeminder krásné grafy.)",
+ "chromeChatExtension": "Přídavek chatu pro Chrome",
+ "chromeChatExtensionDesc": "The Chrome Chat Extension pro Habiticu přidává chytrý chat do habitica.com. Dovoluje uživatelům chatovat v Krčmě, jejich Družině, a jakémukoliv Cechu, v kterém jsou.",
+ "otherExtensions": "Jiné Rozšíření",
+ "otherDesc": "Najdi další aplikace, rozšíření a nástroje na Habitica wiki.",
"resetDo": "Udělej to, zresetuj můj účet!",
+ "resetComplete": "Reset dokončen!",
"fixValues": "Opravit hodnoty",
"fixValuesText1": "Pokud narazíš na chybu nebo uděláš chybu, která nespravedlivě ovlivní tvou postavu (újma, kterou si nezasloužíš, zlaťáky, které jsi neměl získat, atd.), zde můžeš ručně opravit hodnoty. Ano, je díky tomu možné podvádět, ale podvedl bys jen sám sebe!",
"fixValuesText2": "Všimni si, že zde není možné obnovit série úspěšnosti za jednotlivé úkoly. To se dá udělat v rozšířeném nastavení denního úkolu, kde najdeš Obnovit šňůru.",
@@ -96,18 +108,19 @@
"emailNotifications": "Emailová upozornění",
"wonChallenge": "Vyhrál jsi výzvu!",
"newPM": "Obdržena soukromá zpráva",
+ "sentGems": "Drahokamy odeslány!",
"giftedGems": "Darované drahokamy",
"giftedGemsInfo": "<%= amount %> Drahokamů - od <%= name %>",
"giftedSubscription": "Darované předplatné",
"invitedParty": "Pozván do Družiny",
"invitedGuild": "Pozván do Cechu",
"importantAnnouncements": "Tvůj účet není aktivní",
- "weeklyRecaps": "Summaries of your account activity in the past week (Note: this is currently disabled due to performance issues, but we hope to have this back up and sending e-mails again soon!)",
+ "weeklyRecaps": "Shrnutí aktivity tvého účtu za poslední týden (Poznámka: momentálně vypnuto kvůli problémům s výkonem, ale doufáme že se to vyřeší a budeme brzy znovu posílat e-maily!)",
"questStarted": "Tvá výprava započla",
"invitedQuest": "Pozván na Výpravu",
"kickedGroup": "Vykopnut z družiny",
"remindersToLogin": "Upomínky k přihlášení do země Habitica",
- "subscribeUsing": "Subscribe using",
+ "subscribeUsing": "Použití předplatného",
"unsubscribedSuccessfully": "Úspěšně odepsán",
"unsubscribedTextUsers": "Úspěšně ses odepsal ze všech emailu ze země Habitica. Můžeš povolit pouze ty emaily, které chceš dostávat v Nastavení (musíš být přihlášen).",
"unsubscribedTextOthers": "Již nedostaneš ze země Habitica žádný další email.",
@@ -115,7 +128,7 @@
"unsubscribeAllEmailsText": "Zaškrtnutím toho políčka potvrzuji, že rozumím tomu, že kvůli odhlášení se z emailů mě nebude moci nikdy nikdo kontaktovat emailem o důležitých změnách na stránce nebo v mém účtu ze země Habitica.",
"correctlyUnsubscribedEmailType": "Úspěšně ses odhlásil z \"<%= emailType %>\" emailů.",
"subscriptionRateText": "Opakovaně $<%= price %> každý/é <%= months %> měsíc(e)",
- "recurringText": "recurring",
+ "recurringText": "opakující se",
"benefits": "Benefity",
"coupon": "Kupón",
"couponPlaceholder": "Zadej kód z kupónu",
@@ -135,12 +148,17 @@
"webhooks": "Webhooks",
"enabled": "Povoleno",
"webhookURL": "Webhook URL",
+ "invalidUrl": "Neplatné url",
+ "invalidEnabled": "the \"enabled\" parameter should be a boolean",
+ "regIdRequired": "RegId is required",
+ "pushDeviceAdded": "Push device added successfully",
+ "pushDeviceAlreadyAdded": "The user already has the push device",
"add": "Přidat",
"buyGemsGoldCap": "Strop zvýšen na <%= amount %>",
"mysticHourglass": "<%= amount %> Mystických přesýpacích hodin",
"mysticHourglassText": "Mystické přesýpací hodiny ti umožní koupit si záhadné sety předmětů z předchozích měsíců.",
"purchasedPlanId": "Opakovaně $<%= price %> každé <%= months %> měsíce (<%= plan %>)",
- "purchasedPlanExtraMonths": "You have <%= months %> months of extra subscription credit.",
+ "purchasedPlanExtraMonths": "Máš <%= months %> měsíců dalšího kreditu předlatného",
"consecutiveSubscription": "Nepřetržité předplatné",
"consecutiveMonths": "Počet po sobě jdoucích měsíců:",
"gemCapExtra": "Strop drahokamů extra:",
@@ -149,5 +167,5 @@
"amazonPayments": "Amazon platby",
"timezone": "Časové pásmo",
"timezoneUTC": "Habitica používá časové pásmo nastavené na tvém PC, což je : <%= utc %>",
- "timezoneInfo": "Pokud je časové pásmo špatně, nejprve znovu načti tuto stránku v prohlížeči, aby Habitica dostala ty nejčerstvější informace. Pokud je pásmo stále špatně, uprav ho ve svém počítači a znovu stránku načti.
Pokud používáš program Habitica na dalších počítačích nebo mobilních zařízeních, musí být na nich všech časové pásmo stejné. Pokud se tvé denní úkoly resetují ve špatný čas, musíš zkontrolovat nastavení časového pásma na všech mobilních zařízeních a počítačích, na který program Habitica používáš."
+ "timezoneInfo": "If that time zone is wrong, first reload this page using your browser's reload or refresh button to ensure that Habitica has the most recent information. If it is still wrong, adjust the time zone on your PC and then reload this page again.
If you use Habitica on other PCs or mobile devices, the time zone must be the same on them all. If your Dailies have been resetting at the wrong time, repeat this check on all other PCs and on a browser on your mobile devices."
}
\ No newline at end of file
diff --git a/common/locales/cs/spells.json b/common/locales/cs/spells.json
index 67ebf729bd..1fdc4e499b 100644
--- a/common/locales/cs/spells.json
+++ b/common/locales/cs/spells.json
@@ -35,8 +35,8 @@
"spellSpecialSnowballAuraNotes": "Hoď sněhovou koulí po členu družiny, co by se tak mohlo stát? Vydrží mu až do druhého dne.",
"spellSpecialSaltText": "Sůl",
"spellSpecialSaltNotes": "Někdo po tobě hodil sněhovou kouli. Ha ha, strašně vtipné. A teď ze mě dostaňte ten sníh!",
- "spellSpecialSpookDustText": "Strašidelné jiskry",
- "spellSpecialSpookDustNotes": "Změň kamaráda na poletující deku s očima!",
+ "spellSpecialSpookySparklesText": "Spooky Sparkles",
+ "spellSpecialSpookySparklesNotes": "Turn a friend into a floating blanket with eyes!",
"spellSpecialOpaquePotionText": "Neprůhledný lektvar",
"spellSpecialOpaquePotionNotes": "Zruš účinky strašidelných jisker.",
"spellSpecialShinySeedText": "Třpytivé semínko",
@@ -46,5 +46,11 @@
"spellSpecialSeafoamText": "Mořská pěna",
"spellSpecialSeafoamNotes": "Přeměň svého kamaráda na mořskou příšerku!",
"spellSpecialSandText": "Písek",
- "spellSpecialSandNotes": "Zvrať efekt mořské pěny."
+ "spellSpecialSandNotes": "Zvrať efekt mořské pěny.",
+ "spellNotFound": "Schopnost „<%= spellId %>\" nenalezena.",
+ "partyNotFound": "Družina nenalezena",
+ "targetIdUUID": "„cílovéId\" musí být platné ID uživatele. ",
+ "challengeTasksNoCast": "Casting a skill on challenge tasks is not allowed.",
+ "spellNotOwned": "Nevlastníte tuto schopnost.",
+ "spellLevelTooHigh": "Pro použití této schopnosti musíte být úroveň <%= level %>."
}
\ No newline at end of file
diff --git a/common/locales/cs/subscriber.json b/common/locales/cs/subscriber.json
index 5e4216b827..1e50cf7ab2 100644
--- a/common/locales/cs/subscriber.json
+++ b/common/locales/cs/subscriber.json
@@ -4,11 +4,13 @@
"subDescription": "Nakupuj drahokamy za zlaťáky, získej každý měsíc tajemné předměty, uchovávej si historii svého pokroku, zdvojnásob možnost získaných věcí za den, podpoř vývojáře. Klikni pro více informací.",
"buyGemsGold": "Kup drahokamy za zlato",
"buyGemsGoldText": "Obchodník Alexander ti prodá drahokamy za cenu <%= gemCost %> zlaťáků za drahokam. Jeho měsíční dodávky jsou zprvu omezeny na <%= gemLimit %> drahokamů za měsíc, ale každé tři měsíce souvislého předplatného se jejich počet navýší o 5 a může dosáhnout maxima až 50 drahokamů za měsíc!",
- "retainHistory": "Ponech si další položky historie.",
- "retainHistoryText": "Umožňuje vidět dodělané úkoly pro delší dobu.",
+ "mustSubscribeToPurchaseGems": "Must subscribe to purchase gems with GP",
+ "reachedGoldToGemCap": "You've reached the Gold=>Gem conversion cap <%= convCap %> for this month. We have this to prevent abuse / farming. The cap will reset within the first three days of next month.",
+ "retainHistory": "Ponech si další položky historie",
+ "retainHistoryText": "Umožňuje vidět splněné úkoly po delší dobu.",
"doubleDrops": "Denně získáš dvakrát více předmětů",
"doubleDropsText": "Najdi všechny mazlíčky rychleji!",
- "mysteryItem": "Jedinečné měsíční předměty.",
+ "mysteryItem": "Jedinečné předměty každý měsíc",
"mysteryItemText": "Každý měsíc obdržíš unikátní předmět pro svůj avatar! Navíc za každé tři měsíce nepřerušeného předplatného ti Tajemní cestovatelé časem zaručí přístup k historickým (a futuristickým!) předmětům.",
"supportDevs": "Podporuje vývojáře",
"supportDevsText": "Tvé předplatné pomáhá udržet program Habitica prosperující a pomáhá financovat vývoj nových funkcí. Děkujeme za tvou štědrost!",
@@ -28,7 +30,8 @@
"subscribed": "Předplaceno",
"manageSub": "Klikni pro správu předplatného",
"cancelSub": "Zrušit předplatné",
- "canceledSubscription": "Zrušené předplatné.",
+ "canceledSubscription": "Zrušené předplatné",
+ "cancelingSubscription": "Canceling the subscription",
"adminSub": "Administrátorské předplatné",
"morePlans": "Více plánů Již brzy",
"organizationSub": "Soukromá organizace",
@@ -47,25 +50,25 @@
"timeSupportText": "Budeme poskytovat podporu se zaškolením, opravou chyb, instalací a s požadavky na nové funkce.",
"gameFeatures": "Herní funkce",
"gold2Gem": "Drahokamy ke koupi za zlato",
- "gold2GemText": "Members will be able to purchase Gems with gold, meaning none of your participants need to buy anything with real money.",
- "infiniteGem": "Infinite leader Gems",
- "infiniteGemText": "We will provide the organization leaders with as many Gems as they need, for things like challenge prizes, guild-creation, etc.",
+ "gold2GemText": "Uživatelé budou moci nakupovat drahokamy za zlaťáky, takže žádný z tvých účastníků nemusí kupovat nic za reálné peníze.",
+ "infiniteGem": "Neomezené množství drahokamů pro vedoucího.",
+ "infiniteGemText": "Vedoucím organizace poskytneme tolik drahokamů, kolik budou potřebovat, pro věci např.: ceny pro výzvy, vytvoření cechu atd.",
"notYetPlan": "Plán ještě není k dispozici, ale klikni zde, abys nás kontaktoval a my ti dáme vědět.",
"contactUs": "Kontaktuj nás",
"checkout": "Zaplatit",
"sureCancelSub": "Jsi si jistý, že chceš zrušit předplatné?",
"subCanceled": "Předplatné bude neaktivní od",
- "buyGemsGoldTitle": "To Buy Gems with Gold",
- "becomeSubscriber": "Become a Subscriber",
+ "buyGemsGoldTitle": "koupit drahokamy za zlato",
+ "becomeSubscriber": "Staň se předplatitelem",
"subGemPop": "Jakožto předplatitel programu Habitica si můžeš koupit drahokamy za zlaťáky. V koutku ikonu drahokamu uvidíš, kolik drahokamů si můžeš koupit.",
"subGemName": "Drahokamy předplatitele",
- "freeGemsTitle": "Obtain Gems for Free",
- "maxBuyGems": "You have bought all the Gems you can this month. More will become available within the first three days of next month. Thanks for subscribing!",
- "buyGemsAllow1": "You can buy",
- "buyGemsAllow2": "more Gems this month",
- "purchaseGemsSeparately": "Purchase Additional Gems",
- "subFreeGemsHow": "Habitica players can earn Gems for free by winning challenges that award Gems as a prize, or as a contributor reward by helping the development of Habitica.",
- "seeSubscriptionDetails": "Go to Settings > Subscription to see your subscription details!",
+ "freeGemsTitle": "Získej Drahokamy zadarmoq",
+ "maxBuyGems": "Koupil sis všechny drahokamy které jsi tento měsíc mohl. Další si můžeš koupit až v prvních třech dnech dalšího měsíce. Děkujeme za předplacení!",
+ "buyGemsAllow1": "Můžeš si koupit",
+ "buyGemsAllow2": "Více drahokamů tento měsíc",
+ "purchaseGemsSeparately": "Koupit si další drahokamy",
+ "subFreeGemsHow": "Hráči programu Habitica mohou získat drahokamy zdarma tím, že vyhrají Výzvu, která nabízí drahokam jako cenu, nebo jako ocenění Přispěvatele za pomoc v rozvoji země Habitica.",
+ "seeSubscriptionDetails": "Přejdi na Nastavení -> Předplatné propodrobnosti o svém předplatném!",
"timeTravelers": "Cestovatelé časem",
"timeTravelersTitleNoSub": "<%= linkStartTyler %>Tyler<%= linkEnd %> a <%= linkStartVicky %>Vicky<%= linkEnd %>",
"timeTravelersTitle": "Záhadní cestovatelé časem",
@@ -73,6 +76,9 @@
"timeTravelersPopover": "Vidíme, že máš mystické přesýpací hodiny, tak pro Tebe rádi budeme cestovat časem! Prosím, vyber si mazlíčka, zvíře, nebo záhadný set předmětů, který se ti líbí. Můžeš si vybrat z minulých setů tady! Pokud se ti nelíbí, možná by sis vybral z našich trendy futuristických setů Steampunk předmětů?",
"timeTravelersAlreadyOwned": "Gratulujeme! Teď máš vše, co cestovatelé časem nabízejí. Děkujeme za podporu stránek!",
"mysticHourglassPopover": "Díky Mystickým Přesýpacím hodinám si můžeš koupit limitované předměty, jako záhadné předměty měsíce nebo odměny z boje se světovými příšerami, z minulosti!",
+ "mysterySetNotFound": "Mystery set not found, or set already owned.",
+ "mysteryItemIsEmpty": "Mystery items are empty",
+ "mysteryItemOpened": "Mystery item opened.",
"mysterySet201402": "Okřídlený set poslíčka",
"mysterySet201403": "Set lesáka",
"mysterySet201404": "Měsíční motýlí set",
@@ -96,9 +102,11 @@
"mysterySet201510": "Set rohatého goblina",
"mysterySet201511": "Set dřevěného bojovníka",
"mysterySet201512": "Set se zimním plamenem",
- "mysterySet201601": "Champion of Resolution Set",
- "mysterySet201602": "Heartbreaker Set",
- "mysterySet201603": "Lucky Clover Set",
+ "mysterySet201601": "Set Šampióna předsevzetí",
+ "mysterySet201602": "Set Lamače srdcí",
+ "mysterySet201603": "Set Šťastného trojlístku",
+ "mysterySet201604": "Leaf Warrior Set",
+ "mysterySet201605": "Marching Bard Set",
"mysterySet301404": "Standardní steampunkový set",
"mysterySet301405": "Set steampunkových doplňků",
"mysterySetwondercon": "Wondercon",
@@ -110,9 +118,25 @@
"hourglassBuyItemConfirm": "Koupit tento předmět za 1 přesýpací hodiny?",
"petsAlreadyOwned": "Tohoto mazlíčka již máš.",
"mountsAlreadyOwned": "Toto zvíře již máš.",
- "typeNotAllowedHourglass": "Tento typ předmětu nelze koupit za mystické přesýpací hodiny. Za ně si můžeš koupit:",
+ "typeNotAllowedHourglass": "Item type not supported for purchase with Mystic Hourglass. Allowed types: <%= allowedTypes %>",
"petsNotAllowedHourglass": "Mazlíčka si nelze koupit za mystické přesýpací hodiny.",
"mountsNotAllowedHourglass": "Zvíře si nelze koupit za mystické přesýpací hodiny-",
"hourglassPurchase": "Koupil sis předmět za mystické přesýpací hodiny.",
- "hourglassPurchaseSet": "Koupil sis set předmětů za mystické přesýpací hodiny!"
+ "hourglassPurchaseSet": "Koupil sis set předmětů za mystické přesýpací hodiny!",
+ "missingUnsubscriptionCode": "Missing unsubscription code.",
+ "missingSubscription": "User does not have a plan subscription",
+ "missingSubscriptionCode": "Missing subscription code. Possible values: basic_earned, basic_3mo, basic_6mo, google_6mo, basic_12mo.",
+ "cannotDeleteActiveAccount": "You have an active subscription, cancel your plan before deleting your account.",
+ "paymentNotSuccessful": "The payment was not successful",
+ "planNotActive": "The plan hasn't activated yet (due to a PayPal bug). It will begin <%= nextBillingDate %>, after which you can cancel to retain your full benefits",
+ "notAllowedHourglass": "Pet/Mount not available for purchase with Mystic Hourglass.",
+ "readCard": "<%= cardType %> has been read",
+ "cardTypeRequired": "Card type required",
+ "cardTypeNotAllowed": "Unknown card type.",
+ "invalidCoupon": "Invalid coupon code.",
+ "couponUsed": "Coupon code already used.",
+ "noSudoAccess": "You don't have sudo access.",
+ "couponCodeRequired": "The coupon code is required.",
+ "eventRequired": "\"req.params.event\" is required.",
+ "countRequired": "\"req.query.count\" is required."
}
\ No newline at end of file
diff --git a/common/locales/cs/tasks.json b/common/locales/cs/tasks.json
index 00d7bf6149..2a64f1522a 100644
--- a/common/locales/cs/tasks.json
+++ b/common/locales/cs/tasks.json
@@ -73,6 +73,7 @@
"clearTags": "Vyčistit",
"hideTags": "Schovat",
"showTags": "Ukázat",
+ "toRequired": "You must supply a to value",
"startDate": "Datum začátku",
"startDateHelpTitle": "Kdy by měl tento úkol začít?",
"startDateHelp": "Nastav datum, ve kterém začne úkol platit. Nebude muset být splněn dříve.",
@@ -88,8 +89,9 @@
"fortifyName": "Posilňující lektvar",
"fortifyPop": "Obnoví všechny úkoly do neutrální (žluté) barvy a obnoví ztracené zdraví.",
"fortify": "Posilnění",
- "fortifyText": "Posilnění vrátí všechny tvé úkoly do neutrálního (žlutého) stavu, ve kterém byly v okamžiku přidání, a doplní zdraví na maximum. Považuj to za poslední možnou variantu. Červené úkoly poskytují dobrou motivaci ke zlepšení. Ale pokud tě všechna ta červená naplňuje zoufalstvím a začátek každého dne se ukáže jako smrtící, investuj drahokamy a oddechni si.",
+ "fortifyText": "Zdokonalení vrátí všechny vaše úkoly, kromě výzev, do neutrálního (žlutého) stavu, jako byste si je zrovna přidali, a doplní vám životy. Toto je skvělé pokud vám vaše červené úkoly dělají hru příliš těžkou, nebo pokud vám vaše modré úkoly dělají hru příliš jednoduchou. Motivuje-li vás nový začátek víc, utraťte drahokamy a oddechněte si!",
"confirmFortify": "Jsi si jistý?",
+ "fortifyComplete": "Zdokonalení dokončeno!",
"sureDelete": "Jsi si jistý, že chceš smazat <%= taskType %> s textem \"<%= taskText %>\"?",
"streakCoins": "Bonus za sérii!",
"pushTaskToTop": "Posuň úkol nahoru. Stiskni ctrl nebo cmd a posuneš ho dolů.",
@@ -112,5 +114,18 @@
"rewardHelp2": "Vybavení ovlivňuje tvé statistiky (<%= linkStart %>Avatar > Statistiky<%= linkEnd %>).",
"rewardHelp3": "V průběhu světových událostí se speciální vybavení zobrazí zde.",
"rewardHelp4": "Neboj se vytvořit si své Odměny! Koukni na na ukázku tady.",
- "clickForHelp": "Klikni pro nápovědu"
+ "clickForHelp": "Klikni pro nápovědu",
+ "taskIdRequired": "\"taskId\" must be a valid UUID.",
+ "taskNotFound": "Úkol nenalezen.",
+ "invalidTaskType": "Typ úkolu musí být „zvyk\", „denní úkol\", „úkol\" nebo „odměna\".",
+ "cantDeleteChallengeTasks": "Úkol náležící výzvě nelze vymazat.",
+ "checklistOnlyDailyTodo": "Seznamy jsou podporovány pouze pro denní úkoly a úkoly",
+ "checklistItemNotFound": "Na seznamu nenalezen žádný předmět s tímto id.",
+ "itemIdRequired": "\"itemId\" must be a valid UUID.",
+ "tagNotFound": "No tag item was found with given id.",
+ "tagIdRequired": "\"tagId\" must be a valid UUID corresponding to a tag belonging to the user.",
+ "positionRequired": "Je požadována „pozice\", který musí být číslem.",
+ "cantMoveCompletedTodo": "Nelze pohnout s dokončeným ůkolem.",
+ "directionUpDown": "Je požadován „směr\", který musí být 'nahoru' nebo 'dolu'",
+ "alreadyTagged": "Tento úkoly jste již označili tímto štítkem."
}
\ No newline at end of file
diff --git a/common/locales/da/backgrounds.json b/common/locales/da/backgrounds.json
index fd70b6f5f2..badaa91857 100644
--- a/common/locales/da/backgrounds.json
+++ b/common/locales/da/backgrounds.json
@@ -148,17 +148,24 @@
"backgroundGrandStaircaseText": "Hovedtrappen",
"backgroundGrandStaircaseNotes": "Bevæg dig ned af hovedtrappen.",
"backgrounds032016": "SÆT 22: Udgivet marts 2016",
- "backgroundDeepMineText": "Dyb mine",
+ "backgroundDeepMineText": "Dyb Mine",
"backgroundDeepMineNotes": "Find dyrebare metaller i en dyb mine",
"backgroundRainforestText": "Regnskov",
"backgroundRainforestNotes": "Vov dig ind i en regnskov.",
- "backgroundStoneCircleText": "Cirkel af sten",
- "backgroundStoneCircleNotes": "Kast magi i en cirkel af sten.",
- "backgrounds042016": "SET 23: Released April 2016",
- "backgroundArcheryRangeText": "Archery Range",
- "backgroundArcheryRangeNotes": "Practice on the Archery Range.",
- "backgroundGiantFlowersText": "Giant Flowers",
+ "backgroundStoneCircleText": "Cirkel af Sten",
+ "backgroundStoneCircleNotes": "Kast magi i en Cirkel af Sten.",
+ "backgrounds042016": "SÆT 23: Udgivet april 2016",
+ "backgroundArcheryRangeText": "Blueskydningsbane",
+ "backgroundArcheryRangeNotes": "Træning på Bueskydningsbanen.",
+ "backgroundGiantFlowersText": "Kæmpe Blomster",
"backgroundGiantFlowersNotes": "Frolic atop Giant Flowers.",
- "backgroundRainbowsEndText": "End of the Rainbow",
- "backgroundRainbowsEndNotes": "Discover gold at the End of the Rainbow."
+ "backgroundRainbowsEndText": "Enden af Regnbuen",
+ "backgroundRainbowsEndNotes": "Find guld ved Enden af Regnbuen",
+ "backgrounds052016": "SET 24: Released May 2016",
+ "backgroundBeehiveText": "Beehive",
+ "backgroundBeehiveNotes": "Buzz and dance in a Beehive.",
+ "backgroundGazeboText": "Gazebo",
+ "backgroundGazeboNotes": "Battle a Gazebo.",
+ "backgroundTreeRootsText": "Tree Roots",
+ "backgroundTreeRootsNotes": "Explore the Tree Roots."
}
\ No newline at end of file
diff --git a/common/locales/da/challenge.json b/common/locales/da/challenge.json
index eeb4234c07..1ce35db90c 100644
--- a/common/locales/da/challenge.json
+++ b/common/locales/da/challenge.json
@@ -63,5 +63,20 @@
"congratulations": "Tillykke!",
"hurray": "Hurra!",
"noChallengeOwner": "ingen ejer",
- "noChallengeOwnerPopover": "Denne udfordring har ikke nogen ejer, da personen, der oprettede udfordringen, har slettet sin brugerkonto."
+ "noChallengeOwnerPopover": "Denne udfordring har ikke nogen ejer, da personen, der oprettede udfordringen, har slettet sin brugerkonto.",
+ "challengeMemberNotFound": "User not found among challenge's members",
+ "onlyGroupLeaderChal": "Only the group leader can create challenges",
+ "tavChalsMinPrize": "Prize must be at least 1 Gem for Tavern challenges.",
+ "cantAfford": "You can't afford this prize. Purchase more gems or lower the prize amount.",
+ "challengeIdRequired": "\"challengeId\" must be a valid UUID.",
+ "winnerIdRequired": "\"winnerId\" must be a valid UUID.",
+ "challengeNotFound": "Challenge not found.",
+ "onlyLeaderDeleteChal": "Only the challenge leader can delete it.",
+ "onlyLeaderUpdateChal": "Only the challenge leader can update it.",
+ "winnerNotFound": "Winner with id \"<%= userId %>\" not found or not part of the challenge.",
+ "noCompletedTodosChallenge": "\"includeComepletedTodos\" is not supported when fetching a challenge tasks.",
+ "userTasksNoChallengeId": "When \"tasksOwner\" is \"user\" \"challengeId\" can't be passed.",
+ "onlyChalLeaderEditTasks": "Tasks belonging to a challenge can only be edited by the leader.",
+ "userAlreadyInChallenge": "User is already participating in this challenge.",
+ "cantOnlyUnlinkChalTask": "Only broken challenges tasks can be unlinked."
}
\ No newline at end of file
diff --git a/common/locales/da/character.json b/common/locales/da/character.json
index fa6419b69a..aa4606fbab 100644
--- a/common/locales/da/character.json
+++ b/common/locales/da/character.json
@@ -1,4 +1,5 @@
{
+ "communityGuidelinesWarning": "Please keep in mind that your Display Name, profile photo, and blurb must comply with the Community Guidelines (e.g. no profanity, no adult topics, no insults, etc). If you have any questions about whether or not something is appropriate, feel free to email leslie@habitica.com!",
"statsAch": "Stats og præstationer",
"profile": "Profil",
"avatar": "Customize Avatar",
@@ -34,7 +35,7 @@
"beard": "Skæg",
"mustache": "Overskæg",
"flower": "Blomst",
- "wheelchair": "Wheelchair",
+ "wheelchair": "Kørestol",
"basicSkins": "Basis-skins",
"rainbowSkins": "Regnbue-skins",
"pastelSkins": "Pastel-skins",
@@ -109,6 +110,7 @@
"mage": "Magiker",
"mystery": "Mystisk",
"changeClass": "Skift klasse, Refunder attributpoint",
+ "lvl10ChangeClass": "To change class you must be at least level 10.",
"levelPopover": "Hvert niveau giver dig et point du kan tildele en attribut efter eget valg. Du kan gøre det manuelt, eller lade spillet bestemme for dig ved hjælp af en af de automatiske tildelingsmuligheder.",
"unallocated": "Ufordelte attributpoint",
"haveUnallocated": "Du har <%= points %> ubrugt(e) attributpoint",
@@ -164,5 +166,7 @@
"int": "INT",
"showQuickAllocation": "Show stat allocation",
"hideQuickAllocation": "Hide stat allocation",
- "quickAllocationLevelPopover": "Each level earns you one point to assign to an attribute of your choice. You can do so manually, or let the game decide for you using one of the Automatic Allocation options found in User -> Stats."
+ "quickAllocationLevelPopover": "Each level earns you one point to assign to an attribute of your choice. You can do so manually, or let the game decide for you using one of the Automatic Allocation options found in User -> Stats.",
+ "invalidAttribute": "\"<%= attr %>\" is not a valid attribute.",
+ "notEnoughAttrPoints": "You don't have enough attribute points."
}
\ No newline at end of file
diff --git a/common/locales/da/content.json b/common/locales/da/content.json
index b2442460ac..383af6e097 100644
--- a/common/locales/da/content.json
+++ b/common/locales/da/content.json
@@ -31,7 +31,7 @@
"dropEggCactusAdjective": "en stikkende",
"dropEggBearCubText": "Bjørneunge",
"dropEggBearCubMountText": "Bjørn",
- "dropEggBearCubAdjective": "a brave",
+ "dropEggBearCubAdjective": "en modig",
"questEggGryphonText": "Grif",
"questEggGryphonMountText": "Grif",
"questEggGryphonAdjective": "en stolt",
@@ -46,7 +46,7 @@
"questEggEggAdjective": "et farverigt",
"questEggRatText": "Rotte",
"questEggRatMountText": "Rotte",
- "questEggRatAdjective": "a sociable",
+ "questEggRatAdjective": "en social",
"questEggOctopusText": "Blæksprutte",
"questEggOctopusMountText": "Blæksprutte",
"questEggOctopusAdjective": "en glat",
@@ -110,9 +110,15 @@
"questEggMonkeyText": "Abe",
"questEggMonkeyMountText": "Abe",
"questEggMonkeyAdjective": "en fræk",
- "questEggSnailText": "Snail",
- "questEggSnailMountText": "Snail",
- "questEggSnailAdjective": "a slow but steady",
+ "questEggSnailText": "Snegl",
+ "questEggSnailMountText": "Snegl",
+ "questEggSnailAdjective": "en langsom men udholden",
+ "questEggFalconText": "Falk",
+ "questEggFalconMountText": "Falk",
+ "questEggFalconAdjective": "en hurtig",
+ "questEggTreelingText": "Treeling",
+ "questEggTreelingMountText": "Treeling",
+ "questEggTreelingAdjective": "a leafy",
"eggNotes": "Find en udrugningseliksir til at hælde på dit æg, og det vil udklække <%= eggAdjective(locale) %> <%= eggText(locale) %>.",
"hatchingPotionBase": "Almindelig",
"hatchingPotionWhite": "Hvid",
@@ -126,6 +132,7 @@
"hatchingPotionGolden": "Gylden",
"hatchingPotionSpooky": "Uhyggelig",
"hatchingPotionPeppermint": "Pebermynte",
+ "hatchingPotionFloral": "Floral",
"hatchingPotionNotes": "Hæld på et æg, og det vil udklække til et <%= potText(locale) %> kæledyr.",
"premiumPotionAddlNotes": "Kan ikke bruges på quest-æg.",
"foodMeat": "Kød",
diff --git a/common/locales/da/contrib.json b/common/locales/da/contrib.json
index e2657d6127..6d37d19d13 100644
--- a/common/locales/da/contrib.json
+++ b/common/locales/da/contrib.json
@@ -28,15 +28,19 @@
"helped": "Hjalp Habit med at vokse",
"helpedText1": "Hjalp Habitica med at vokse ved at udfylde",
"helpedText2": "dette spørgeskema.",
- "hall": "Hall of Heroes",
+ "hall": "Heltenes Sal",
"contribTitle": "Bidragsydertitel (fx, \"Grovsmed\")",
"contribLevel": "Bidragstrin",
"contribHallText": "1-7 for normale bidragsydere, 8 for moderatorer, 9 for ansatte. Dette har betydning for, hvilke varer, kæledyr og ridedyr, der er tilgængelige. Bestemmer også navne-farve. Trin 8 og 9 får automatisk administratorstatus.",
- "hallContributors": "Hall of Contributors",
+ "hallContributors": "Bidragydernes Sal",
"hallPatrons": "Protektorernes Sal",
"rewardUser": "Beløn bruger",
- "UUID": "Unikt Bruger-ID",
+ "UUID": "User ID",
"loadUser": "Indlæs bruger",
+ "noAdminAccess": "You don't have admin access.",
+ "pageMustBeNumber": "req.query.page must be a number",
+ "userNotFound": "User not found.",
+ "invalidUUID": "UUID must be valid",
"title": "Titel",
"moreDetails": "Flere detaljer (1-7)",
"moreDetails2": "flere detaljer (8-9)",
@@ -52,8 +56,8 @@
"visitHeroes": "Besøg Heltenes Sal (bidragsydere og støtter)",
"conLearn": "Lær mere om bidragsyderbelønninger",
"conLearnHow": "Lær hvordan du kan bidrage til Habitica",
- "surveysSingle": "Hjalp Habitica med at vokse ved at udfylde et spørgeskema. Der er ingen aktive spørgeskemaer.",
- "surveysMultiple": "Hjalp Habitica med at vokse ved at udfylde <%= surveys %> spørgeskemaer. Der er ingen aktive spørgeskemaer.",
+ "surveysSingle": "Helped Habitica grow, either by filling out a survey or helping with a major testing effort. Thank you!",
+ "surveysMultiple": "Helped Habitica grow on <%= surveys %> occasions, either by filling out a survey or helping with a major testing effort. Thank you!",
"currentSurvey": "Nuværende Spørgeskema",
"surveyWhen": "Emblemet gives til alle medvirkende, når spørgeskemaerne er blevet gennemgået ultimo marts.",
"blurbInbox": "Her bliver dine private beskeder gemt! Du kan sende en besked til nogen ved at klikke på det lille brev-ikon ved siden af deres navn i Værtshuset, gruppe-, eller klanchat. Hvis du har modtaget en upassende privatbesked, skal du sende en e-mail med et skærmbillede til Lemoness (leslie@habitica.com)",
diff --git a/common/locales/da/death.json b/common/locales/da/death.json
index 811335733f..26b0d39798 100644
--- a/common/locales/da/death.json
+++ b/common/locales/da/death.json
@@ -12,5 +12,6 @@
"losingHealthQuickly": "Mister du hurtigt Liv?",
"lowHealthTips3": "Ufærdige Daglige skader dig i løbet af natten, så pas på med at tilføje for mange til at starte med!",
"lowHealthTips4": "Hvis en Daglig ikke er forfalden på en given dag, kan du deaktivere den ved at klikke på blyanten.",
- "goodLuck": "Held og lykke!"
+ "goodLuck": "Held og lykke!",
+ "cannotRevive": "Cannot revive if not dead"
}
\ No newline at end of file
diff --git a/common/locales/da/front.json b/common/locales/da/front.json
index d15c12e7e0..4bf549896f 100644
--- a/common/locales/da/front.json
+++ b/common/locales/da/front.json
@@ -28,6 +28,7 @@
"communityReddit": "Reddit",
"companyAbout": "Hvordan det Virker",
"companyBlog": "Blog",
+ "devBlog": "Developer Blog",
"companyDonate": "Donér",
"companyExtensions": "Udvidelser",
"companyPrivacy": "Fortrolighed",
@@ -51,6 +52,7 @@
"featureSocialHeading": "Socialt spil",
"featuredIn": "Omtalt i",
"featuresHeading": "Vi har også...",
+ "footerDevs": "Developers",
"footerCommunity": "Fællesskab",
"footerCompany": "Firma",
"footerMobile": "Mobil",
@@ -182,6 +184,7 @@
"zelahQuote": "Med [Habitica] bliver jeg lokket til at komme i seng i ordentlig tid. Jeg vil gerne have de ekstra XP for at komme tidligt i seng, og undgå at miste liv på grund af en sen aften!",
"reportAccountProblems": "Rapportér Konto-problemer",
"reportCommunityIssues": "Rapportér Fællesskabs-problemer",
+ "subscriptionPaymentIssues": "Subscription and Payment Issues",
"generalQuestionsSite": "Generelle spørgsmål om sitet",
"businessInquiries": "Erhvervs-henvendelser",
"merchandiseInquiries": "Merchandise-henvendelser",
@@ -222,5 +225,32 @@
"altAttrWebstorm": "WebStorm",
"altAttrGithub": "GitHub",
"altAttrTrello": "Trello",
- "altAttrSlack": "Slack"
+ "altAttrSlack": "Slack",
+ "missingAuthHeaders": "Missing authentication headers.",
+ "missingAuthParams": "Missing authentication parameters.",
+ "missingUsernameEmail": "Missing username or email.",
+ "missingEmail": "Missing email.",
+ "missingUsername": "Missing username.",
+ "missingPassword": "Missing password.",
+ "missingNewPassword": "Missing new password.",
+ "wrongPassword": "Wrong password.",
+ "notAnEmail": "Invalid email address.",
+ "emailTaken": "Email address is already used in an account.",
+ "newEmailRequired": "Missing new email address.",
+ "usernameTaken": "Username already taken.",
+ "passwordConfirmationMatch": "Password confirmation doesn't match password.",
+ "invalidLoginCredentials": "Incorrect username and/or email and/or password.",
+ "passwordReset": "If we have your email on file, your password reset link has been sent to your email.",
+ "passwordResetEmailSubject": "Password Reset for Habitica",
+ "passwordResetEmailText": "Password for <%= username %> has been reset to <%= newPassword %> . Important! Both username and password are case-sensitive -- you must enter both exactly as shown here. We recommend copying and pasting both instead of typing them. Log in at <%= baseUrl %>. After you have logged in, head to <%= baseUrl %>/#/options/settings/settings and change your password.",
+ "passwordResetEmailHtml": "Password for <%= username %> has been reset to <%= newPassword %>.
Important! Both username and password are case-sensitive -- you must enter both exactly as shown here. We recommend copying and pasting both instead of typing them.
Log in at <%= baseUrl %>. After you have logged in, head to <%= baseUrl %>/#/options/settings/settings and change your password.",
+ "invalidLoginCredentialsLong": "Uh-oh - your username or password is incorrect.\n- Make sure your username or email is typed correctly.\n- You may have signed up with Facebook, not email. Double-check by trying Facebook login.\n- If you forgot your password, click \"Forgot Password\".",
+ "invalidCredentials": "There is no account that uses those credentials.",
+ "accountSuspended": "Account has been suspended, please contact leslie@habitica.com with your User ID \"<%= userId %>\" for assistance.",
+ "onlyFbSupported": "Only Facebook is supported currently.",
+ "cantDetachFb": "Account lacks another authentication method, can't detach Facebook.",
+ "onlySocialAttachLocal": "Local authentication can be added to only a social account.",
+ "invalidReqParams": "Invalid request parameters.",
+ "memberIdRequired": "\"member\" must be a valid UUID.",
+ "heroIdRequired": "\"heroId\" must be a valid UUID."
}
\ No newline at end of file
diff --git a/common/locales/da/gear.json b/common/locales/da/gear.json
index b4708066ce..5e85b380a1 100644
--- a/common/locales/da/gear.json
+++ b/common/locales/da/gear.json
@@ -1,5 +1,5 @@
{
- "set": "Set",
+ "set": "Sæt",
"weapon": "våben",
"weaponBase0Text": "Intet våben",
"weaponBase0Notes": "Intet våben.",
@@ -145,11 +145,11 @@
"weaponSpecialWinter2016HealerNotes": "WHEEEEEEEEEE!!!!!!! HAPPY WINTER WONDERLAND!!!!!!!! Increases Intelligence by <%= int %>. Limited Edition 2015-2016 Winter Gear.",
"weaponSpecialSpring2016RogueText": "Fire Bolas",
"weaponSpecialSpring2016RogueNotes": "You've mastered the ball, the club, and the knife. Now you advance to juggling fire! Awoo! Increases Strength by <%= str %>. Limited Edition 2016 Spring Gear.",
- "weaponSpecialSpring2016WarriorText": "Cheese Mallet",
+ "weaponSpecialSpring2016WarriorText": "Ostehammer",
"weaponSpecialSpring2016WarriorNotes": "No one has as many friends as the mouse with tender cheeses. Increases Strength by <%= str %>. Limited Edition 2016 Spring Gear.",
- "weaponSpecialSpring2016MageText": "Staff of Bells",
+ "weaponSpecialSpring2016MageText": "Klokkernes Stav",
"weaponSpecialSpring2016MageNotes": "Abra-cat-abra! So dazzling, you might mesmerize yourself! Ooh... it jingles... Increases Intelligence by <%= int %> and Perception by <%= per %>. Limited Edition 2016 Spring Gear.",
- "weaponSpecialSpring2016HealerText": "Spring Flower Wand",
+ "weaponSpecialSpring2016HealerText": "Forårsblomst Tryllestav",
"weaponSpecialSpring2016HealerNotes": "With a wave and a wink, you bring the fields and forests into bloom! Or bop troublesome mice on the head. Increases Intelligence by <%= int %>. Limited Edition 2016 Spring Gear.",
"weaponMystery201411Text": "Høstfests-Høtyv",
"weaponMystery201411Notes": "Stik dine fjender eller grib for dig af dine yndlingsretter - denne høtyv kan det hele! Giver ingen bonusser. November 2014 Abonnentvare.",
@@ -189,6 +189,8 @@
"weaponArmoireMiningPickaxNotes": "Mine the maximum amount of gold from your tasks! Increases Perception by <%= per %>. Enchanted Armoire: Miner Set (Item 3 of 3).",
"weaponArmoireBasicLongbowText": "Basic Longbow",
"weaponArmoireBasicLongbowNotes": "A serviceable hand-me-down bow. Increases Strength by <%= str %>. Enchanted Armoire: Basic Archer Set (Item 1 of 3).",
+ "weaponArmoireHabiticanDiplomaText": "Habitican Diploma",
+ "weaponArmoireHabiticanDiplomaNotes": "A certificate of significant achievement -- well done! Increases Intelligence by <%= int %>. Enchanted Armoire: Graduate Set (Item 1 of 3).",
"armor": "rustning",
"armorBase0Text": "Almindeligt tøj",
"armorBase0Notes": "Helt normalt tøj. Giver ingen bonusser.",
@@ -362,8 +364,12 @@
"armorMystery201511Notes": "Considering this armor was carved directly from a magical log, it's surprisingly comfortable. Confers no benefit. November 2015 Subscriber Item.",
"armorMystery201512Text": "Kold ilds rustning",
"armorMystery201512Notes": "Summon the icy flames of winter! Confers no benefit. December 2015 Subscriber Item.",
- "armorMystery201603Text": "Lucky Suit",
+ "armorMystery201603Text": "Lykkedragt",
"armorMystery201603Notes": "This suit is sewn from thousands of four-leafed clovers! Confers no benefit. March 2016 Subscriber Item.",
+ "armorMystery201604Text": "Armor o' Leaves",
+ "armorMystery201604Notes": "You, too, can be a small but fearsome leaf puff. Confers no benefit. April 2016 Subscriber Item.",
+ "armorMystery201605Text": "Marching Bard Uniform",
+ "armorMystery201605Notes": "Unlike the traditional bards who join adventuring parties, bards who join Habitican marching bands are known for grand parades, not dungeon raids. Confers no benefit. May 2016 Subscriber Item.",
"armorMystery301404Text": "Steampunk-dragt",
"armorMystery301404Notes": "Nydelig og elegant, selvfølgelig! Giver ingen bonusser. Februar 3015 Abonnentting.",
"armorArmoireLunarArmorText": "Beroligende Måne-rustning",
@@ -394,6 +400,8 @@
"armorArmoireMinerOverallsNotes": "They may seem worn, but they are enchanted to repel dirt. Increases Constitution by <%= con %>. Enchanted Armoire: Miner Set (Item 2 of 3).",
"armorArmoireBasicArcherArmorText": "Basic Archer Armor",
"armorArmoireBasicArcherArmorNotes": "This camouflaged vest lets you slip unnoticed through the forests. Increases Perception by <%= per %>. Enchanted Armoire: Basic Archer Set (Item 2 of 3).",
+ "armorArmoireGraduateRobeText": "Graduate Robe",
+ "armorArmoireGraduateRobeNotes": "Congratulations! This weighty robe hangs heavy with all the knowledge you have accrued. Increases Intelligence by <%= int %>. Enchanted Armoire: Graduate Set (Item 2 of 3).",
"headgear": "hovedbeklædning",
"headBase0Text": "Ingen Hjelm",
"headBase0Notes": "Ingen hovedbeklædning",
@@ -563,8 +571,12 @@
"headMystery201601Notes": "Stay resolute, brave champion! Confers no benefit. January 2016 Subscriber Item.",
"headMystery201602Text": "Hjerteknuserhætte",
"headMystery201602Notes": "Shield your identity from all your admirers. Confers no benefit. February 2016 Subscriber Item.",
- "headMystery201603Text": "Lucky Hat",
+ "headMystery201603Text": "Lykkehat",
"headMystery201603Notes": "This top hat is a magical good-luck charm. Confers no benefit. March 2016 Subscriber Item.",
+ "headMystery201604Text": "Crown o' Flowers",
+ "headMystery201604Notes": "These woven flowers make a surprisingly strong helm! Confers no benefit. April 2016 Subscriber Item.",
+ "headMystery201605Text": "Marching Bard Hat",
+ "headMystery201605Notes": "Seventy-six dragons led the big parade, with a hundred and ten gryphons close at hand! Confers no benefit. May 2016 Subscriber Item.",
"headMystery301404Text": "Smart Tophat",
"headMystery301404Notes": "En smart tophat for de fineste folk! Giver ingen bonusser. Januar 3015 Abonnentting.",
"headMystery301405Text": "Simpel Tophat",
@@ -613,6 +625,8 @@
"headArmoireMinerHelmetNotes": "Protect your head from falling tasks! Increases Intelligence by <%= int %>. Enchanted Armoire: Miner Set (Item 1 of 3).",
"headArmoireBasicArcherCapText": "Basic Archer Cap",
"headArmoireBasicArcherCapNotes": "No archer would be complete without a jaunty cap! Increases Perception by <%= per %>. Enchanted Armoire: Basic Archer Set (Item 3 of 3).",
+ "headArmoireGraduateCapText": "Graduate Cap",
+ "headArmoireGraduateCapNotes": "Congratulations! Your deep thoughts have earned you this thinking cap. Increases Intelligence by <%= int %>. Enchanted Armoire: Graduate Set (Item 3 of 3).",
"offhand": "skjoldhånds-udstyr",
"shieldBase0Text": "Intet Skjoldhånds-udstyr",
"shieldBase0Notes": "Intet skjold eller andet våben.",
@@ -673,7 +687,7 @@
"shieldSpecialWinter2015HealerText": "Lindrende Skjold",
"shieldSpecialWinter2015HealerNotes": "Dette skjold skærmer mod isnende vind. Øger Konstitution med <%= con %>. Specielt 2014-2015 Vinterudstyr.",
"shieldSpecialSpring2015RogueText": "Eksploderende Piv",
- "shieldSpecialSpring2015RogueNotes": "Lad ikke lyden snyde dig - disse eksplosiver slår hårdt! Øger Styrke med <%= str %>. Specielt 2015 Forårsudstyr.",
+ "shieldSpecialSpring2015RogueNotes": "Don't let the sound fool you - these explosives pack a punch. Increases Strength by <%= str %>. Limited Edition 2015 Spring Gear.",
"shieldSpecialSpring2015WarriorText": "Tallerken-diskus",
"shieldSpecialSpring2015WarriorNotes": "Smid det efter dine fjender... eller bare hold det, fordi det bliver fyldt med lækkert hundemad ved spisetid. Øger Konstitution med <%= con %>. Specielt 2015 Forårsudstyr.",
"shieldSpecialSpring2015HealerText": "Mønstret Pude",
@@ -698,7 +712,7 @@
"shieldSpecialWinter2016HealerNotes": "Åbn den åbn den åbn den åbn den åbn den åbn den!!!!!!!!! Øger Konstitution med <%= con %>. Specielt 2015-2016 Vinterudstyr.",
"shieldSpecialSpring2016RogueText": "Fire Bolas",
"shieldSpecialSpring2016RogueNotes": "You've mastered the ball, the club, and the knife. Now you advance to juggling fire! Awoo! Increases Strength <%= str %>. Limited Edition 2016 Spring Gear.",
- "shieldSpecialSpring2016WarriorText": "Cheese Wheel",
+ "shieldSpecialSpring2016WarriorText": "Ostehjul",
"shieldSpecialSpring2016WarriorNotes": "You braved fiendish traps to procure this defense-boosting food. Increases Constitution by <%= con %>. Limited Edition 2016 Spring Gear.",
"shieldSpecialSpring2016HealerText": "Floral Buckler",
"shieldSpecialSpring2016HealerNotes": "The April Fool claims this little shield will block Shiny Seeds. Don't believe him. Increases Constitution by <%= con %>. Limited Edition 2016 Spring Gear.",
@@ -716,6 +730,8 @@
"shieldArmoireDragonTamerShieldNotes": "Distract enemies with this dragon-shaped shield. Increases Perception by <%= per %>. Enchanted Armoire: Dragon Tamer Set (Item 2 of 3).",
"shieldArmoireMysticLampText": "Mystic Lamp",
"shieldArmoireMysticLampNotes": "Light the darkest caves with this mystic lamp! Increases Perception by <%= per %>. Enchanted Armoire: Independent Item.",
+ "shieldArmoireFloralBouquetText": "Bouquet o' Flowers",
+ "shieldArmoireFloralBouquetNotes": "Not much help in battle, but aren't they beautiful? Increases Constitution by <%= con %>. Enchanted Armoire: Independent Item.",
"back": "Ryg-udstyr",
"backBase0Text": "Intet Ryg-udstyr",
"backBase0Notes": "Intet Ryg-udstyr.",
@@ -779,13 +795,13 @@
"headAccessorySpecialSpring2015MageNotes": "Disse ører lytter intenst, hvis der nu skulle være en magiker i gang med at afsløre hemmeligheder et sted. Giver ingen bonusser. Specielt 2015 Forårsudstyr.",
"headAccessorySpecialSpring2015HealerText": "Grønne Katteører",
"headAccessorySpecialSpring2015HealerNotes": "Disse nuttede katteører gør andre grønne af misundelse. Giver ingen bonusser. Specielt 2015 Forårsudstyr.",
- "headAccessorySpecialSpring2016RogueText": "Green Dog Ears",
+ "headAccessorySpecialSpring2016RogueText": "Grønne Hundeøre",
"headAccessorySpecialSpring2016RogueNotes": "With these, you can keep track of tricky Mages even if they turn invisible! Confers no benefit. Limited Edition 2016 Spring Gear.",
- "headAccessorySpecialSpring2016WarriorText": "Red Mouse Ears",
+ "headAccessorySpecialSpring2016WarriorText": "Røde Musseøre",
"headAccessorySpecialSpring2016WarriorNotes": "To better hear your theme song across clamorous battlefields. Confers no benefit. Limited Edition 2016 Spring Gear.",
- "headAccessorySpecialSpring2016MageText": "Yellow Cat Ears",
+ "headAccessorySpecialSpring2016MageText": "Gule katteøre",
"headAccessorySpecialSpring2016MageNotes": "These sharp ears can detect the minute hum of ambient Mana, or the muted footfalls of a Rogue. Confers no benefit. Limited Edition 2016 Spring Gear.",
- "headAccessorySpecialSpring2016HealerText": "Purple Bunny Ears",
+ "headAccessorySpecialSpring2016HealerText": "Lilla Kaninøre",
"headAccessorySpecialSpring2016HealerNotes": "They stand like flags above the fray, letting others know where to run for help. Confers no benefit. Limited Edition 2016 Spring Gear.",
"headAccessoryBearEarsText": "Bjørneører",
"headAccessoryBearEarsNotes": "These ears make you look like a brave bear! Confers no benefit.",
@@ -820,6 +836,20 @@
"eyewear": "Øjenbeklædning",
"eyewearBase0Text": "Ingen Øjenbeklædning",
"eyewearBase0Notes": "Ingen Øjenbeklædning.",
+ "eyewearSpecialBlackTopFrameText": "Black Standard Eyeglasses",
+ "eyewearSpecialBlackTopFrameNotes": "Glasses with a black frame above the lenses. Confers no benefit.",
+ "eyewearSpecialBlueTopFrameText": "Blue Standard Eyeglasses",
+ "eyewearSpecialBlueTopFrameNotes": "Glasses with a blue frame above the lenses. Confers no benefit.",
+ "eyewearSpecialGreenTopFrameText": "Green Standard Eyeglasses",
+ "eyewearSpecialGreenTopFrameNotes": "Glasses with a green frame above the lenses. Confers no benefit.",
+ "eyewearSpecialPinkTopFrameText": "Pink Standard Eyeglasses",
+ "eyewearSpecialPinkTopFrameNotes": "Glasses with a pink frame above the lenses. Confers no benefit.",
+ "eyewearSpecialRedTopFrameText": "Red Standard Eyeglasses",
+ "eyewearSpecialRedTopFrameNotes": "Glasses with a red frame above the lenses. Confers no benefit.",
+ "eyewearSpecialWhiteTopFrameText": "White Standard Eyeglasses",
+ "eyewearSpecialWhiteTopFrameNotes": "Glasses with a white frame above the lenses. Confers no benefit.",
+ "eyewearSpecialYellowTopFrameText": "Yellow Standard Eyeglasses",
+ "eyewearSpecialYellowTopFrameNotes": "Glasses with a yellow frame above the lenses. Confers no benefit.",
"eyewearSpecialSummerRogueText": "Skurkeagtig Øjeklap",
"eyewearSpecialSummerRogueNotes": "Man behøver ikke at være en slyngel for at se, hvor tjekket den er! Giver ingen bonusser. Specielt 2014 Sommerudstyr.",
"eyewearSpecialSummerWarriorText": "Elegant Øjeklap",
diff --git a/common/locales/da/groups.json b/common/locales/da/groups.json
index 19b85aa871..615fb94513 100644
--- a/common/locales/da/groups.json
+++ b/common/locales/da/groups.json
@@ -92,6 +92,7 @@
"send": "Send",
"messageSentAlert": "Besked afsendt",
"pmHeading": "Privat besked til <%= name %>",
+ "pmsMarkedRead": "Your private messages have been marked as read",
"clearAll": "Slet Alle Beskeder",
"confirmDeleteAllMessages": "Er du sikker på, at du vil slette alle beskeder i din indbakke? Andre brugere kan stadig se beskeder, du har sendt til dem.",
"optOutPopover": "Vil du ikke modtage private beskeder? Klik for at afmelde",
@@ -99,6 +100,15 @@
"unblock": "Fjern blokering",
"pm-reply": "Send svar",
"inbox": "Indbakke",
+ "messageRequired": "A message is required.",
+ "toUserIDRequired": "A User ID is required",
+ "gemAmountRequired": "A number of gems is required",
+ "notAuthorizedToSendMessageToThisUser": "Can't send message to this user.",
+ "privateMessageGiftIntro": "Hello <%= receiverName %>, <%= senderName %> has sent you",
+ "privateMessageGiftGemsMessage": "<%= gemAmount %> gems!",
+ "privateMessageGiftSubscriptionMessage": "<%= numberOfMonths %> months of subscription!",
+ "cannotSendGemsToYourself": "Cannot send gems to yourself. Try a subscription instead.",
+ "badAmountOfGemsToSend": "Amount must be within 1 and your current number of gems.",
"abuseFlag": "Anmeld overtrædelse af Retningslinjer for Fællesskabet",
"abuseFlagModalHeading": "Anmeld <%= name %> for overtrædelse?",
"abuseFlagModalBody": "Are you sure you want to report this post? You should ONLY report a post that violates the <%= firstLinkStart %>Community Guidelines<%= linkEnd %> and/or <%= secondLinkStart %>Terms of Service<%= linkEnd %>. Inappropriately reporting a post is a violation of the Community Guidelines and may give you an infraction. Appropriate reasons to flag a post include but are not limited to:
swearing, religous oaths
bigotry, slurs
adult topics
violence, including as a joke
spam, nonsensical messages
",
@@ -151,5 +161,29 @@
"partyUpName": "Party Up",
"partyOnName": "Party On",
"partyUpAchievement": "Joined a Party with another person! Have fun battling monsters and supporting each other.",
- "partyOnAchievement": "Joined a Party with at least four people! Enjoy your increased accountability as you unite with your friends to vanquish your foes!"
+ "partyOnAchievement": "Joined a Party with at least four people! Enjoy your increased accountability as you unite with your friends to vanquish your foes!",
+ "groupIdRequired": "\"groupId\" must be a valid UUID",
+ "groupNotFound": "Group not found.",
+ "groupTypesRequired": "You must supply a valid \"type\" query string.",
+ "questLeaderCannotLeaveGroup": "You cannot leave your party when you have started a quest. Abort the quest first.",
+ "cannotLeaveWhileActiveQuest": "You cannot leave party during an active quest. Please leave the quest first.",
+ "onlyLeaderCanRemoveMember": "Only group leader can remove a member!",
+ "memberCannotRemoveYourself": "You cannot remove yourself!",
+ "groupMemberNotFound": "User not found among group's members",
+ "mustBeGroupMember": "Must be member of the group.",
+ "keepOrRemoveAll": "req.query.keep must be either \"keep-all\" or \"remove-all\"",
+ "keepOrRemove": "req.query.keep must be either \"keep\" or \"remove\"",
+ "canOnlyInviteEmailUuid": "Can only invite using uuids or emails.",
+ "inviteMissingEmail": "Missing email address in invite.",
+ "partyMustbePrivate": "Parties must be private",
+ "userAlreadyInGroup": "User already in that group.",
+ "userAlreadyInvitedToGroup": "User already invited to that group.",
+ "userAlreadyPendingInvitation": "User already pending invitation.",
+ "userAlreadyInAParty": "User already in a party.",
+ "userWithIDNotFound": "User with id \"<%= userId %>\" not found.",
+ "userHasNoLocalRegistration": "User does not have a local registration (username, email, password).",
+ "uuidsMustBeAnArray": "User ID invites must be an array.",
+ "emailsMustBeAnArray": "Email address invites must be an array.",
+ "canOnlyInviteMaxInvites": "You can only invite \"<%= maxInvites %>\" at a time",
+ "onlyCreatorOrAdminCanDeleteChat": "Not authorized to delete this message!"
}
\ No newline at end of file
diff --git a/common/locales/da/limited.json b/common/locales/da/limited.json
index 0721539efe..5b7f3c63e7 100644
--- a/common/locales/da/limited.json
+++ b/common/locales/da/limited.json
@@ -5,7 +5,7 @@
"annoyingFriends": "Irriterende venner",
"annoyingFriendsText": "Blev ramt af <%= snowballs %> snebolde af gruppemedlemmer.",
"alarmingFriends": "Foruroligende Venner",
- "alarmingFriendsText": "Blev skræmt <%= spookDust %> gange af gruppemedlemmer.",
+ "alarmingFriendsText": "Got spooked <%= spookySparkles %> times by party members.",
"agriculturalFriends": "Landbrugsvenner",
"agriculturalFriendsText": "Blev lavet om til en blomst <%= seeds %> gange af gruppemedlemmer.",
"aquaticFriends": "Våde Venner",
@@ -72,5 +72,6 @@
"comfortingKittySet": "Comforting Kitty (Healer)",
"sneakySqueakerSet": "Sneaky Squeaker (Rogue)",
"fallEventAvailability": "Tilgængelig indtil 31. oktober",
- "winterEventAvailability": "Tilgængelig indtil 31. december"
+ "winterEventAvailability": "Tilgængelig indtil 31. december",
+ "springEventAvailability": "Available until May 31"
}
\ No newline at end of file
diff --git a/common/locales/da/maintenance.json b/common/locales/da/maintenance.json
new file mode 100644
index 0000000000..efdb524cd2
--- /dev/null
+++ b/common/locales/da/maintenance.json
@@ -0,0 +1,34 @@
+{
+ "habiticaBackSoon": "Don't worry, Habitica will be back soon!",
+ "importantMaintenance": "We are doing important maintenance that we estimate will last until <%= localDate %> in your timezone.",
+ "maintenance": "Maintenance",
+ "maintenanceMoreInfo": "Want more information about the maintenance? <%= linkStart %>Check out our info page<%= linkEnd %>.",
+ "noDamageKeepStreaks": "You will NOT take damage or lose streaks!",
+ "thanksForPatience": "Thanks for your patience!",
+ "twitterMaintenanceUpdates": "For the most recent updates, watch our Twitter, where we will be posting status information.",
+ "veteranPetAward": "At the end, you will receive a Veteran pet!",
+
+ "maintenanceInfoTitle": "Information about Upcoming Maintenance to Habitica",
+ "maintenanceInfoWhat": "What is happening?",
+ "maintenanceInfoWhatText": "On May 21, Habitica will be down for maintenance for most of the day. You will not take any damage or have your account harmed during that weekend, even if you can’t log in to check off your Dailies in time! We will be working very hard to make the downtime as short as possible, and will be posting regular updates on our Twitter account. At the end of the downtime, to thank everyone for their patience, you will all receive a rare pet!",
+ "maintenanceInfoWhy": "Why is this happening?",
+ "maintenanceInfoWhyText": "For the past several months, we have been thoroughly revamping Habitica behind-the-scenes. Specifically, we have rewritten the API. While it may not look much different on the surface, it’s a whole new world underneath. This will allow us WAY more flexibility when we want to build features in the future, and lead to improved performance!",
+ "maintenanceInfoTechDetails": "Want more details on the technical side of the process? Visit The Forge, our dev blog.",
+ "maintenanceInfoMore": "More Information",
+ "maintenanceInfoAccountChanges": "What changes will I see to my account after the rewrite is complete?",
+ "maintenanceInfoAccountChangesText": "At first, there won’t be any notable changes aside from performance improvements for features such as Challenges. If you notice any changes that shouldn’t be there, email us at admin@habitica.com and we will investigate them for you!",
+ "maintenanceInfoAddFeatures": "What kind of features will this allow Habitica to add?",
+ "maintenanceInfoAddFeaturesText": "Completing this rewrite will allow us to start building out improved chat and Guilds, plans for organizations and families, and additional productivity features like Monthlies and the ability to record yesterday’s activity! Those are all involved features on their own, so it will take time to build them, but until we were finished with this rewrite, there was no way we could start them.",
+ "maintenanceInfoHowLong": "How long will the maintenance take?",
+ "maintenanceInfoHowLongText": "We have to migrate tasks and data for all 1.3 million Habitica users -- not an easy task! We anticipate that it will take place between approximately 1pm Pacific Time (8pm UTC) and 10pm Pacific Time (5am UTC). Rest assured that we’re doing everything we can to make it go as quickly as possible! You can follow updates on our Twitter.",
+ "maintenanceInfoStatsAffected": "How will my Dailies, Streaks, Buffs, and Quests be affected?",
+ "maintenanceInfoStatsAffectedText1": "You will NOT take any damage or lose any streaks that weekend, but otherwise, your day will reset normally! Dailies that you checked will become unchecked, buffs will reset, etc. If you are in a Collection Quest, you will still find items. If you are in a Boss Battle, you will still deal damage to the Boss, but the Boss will not deal damage to you. (Even monsters need a break!)",
+ "maintenanceInfoStatsAffectedText2": "After a lot of thought, our team concluded that this was the most fair way to handle the fact that many users will not be able to check off their Dailies normally during the maintenance. We’re sorry for any inconvenience this causes!",
+ "maintenanceInfoSeeTasks": "What if I need to see my task list?",
+ "maintenanceInfoSeeTasksText": "If you know that you will need to see your task list on Saturday to remind yourself what you have to do, we recommend that before the maintenance begins, you take a screenshot of your tasks so that you can use it as a reference.",
+ "maintenanceInfoRarePet": "What kind of rare pet will I receive?",
+ "maintenanceInfoRarePetText": "To thank you for your patience during the downtime, everyone will get a rare Veteran Pet. If you’ve never received a Veteran Pet before, you will receive a Veteran Wolf. If you already have a Veteran Wolf, you will receive a Veteran Tiger. And if you already have a Veteran Wolf and a Veteran Tiger, you will receive a never-before-seen Veteran pet! After the migration is completed, it may take several hours for your pet to show up, but never fear, everyone will get one.",
+ "maintenanceInfoWho": "Who worked on this massive project?",
+ "maintenanceInfoWhoText": "We’re glad you asked! It was spearheaded by our amazing contributor paglias, with lots of help from Blade, TheHollidayInn, SabreCat, Victor Pudeyev, TheUnknown, and Alys.",
+ "maintenanceInfoTesting": "The new version was also tirelessly tested by a bunch of our amazing open-source volunteers. Thank you -- we couldn't have done this without you."
+}
diff --git a/common/locales/da/npc.json b/common/locales/da/npc.json
index 191b732a0e..7749e2dfbf 100644
--- a/common/locales/da/npc.json
+++ b/common/locales/da/npc.json
@@ -21,6 +21,26 @@
"ian": "Ian",
"ianText": "Velkommen til Quest-butikken! Her kan du aktivere Quest-skriftruller for at kæmpe mod monstre sammen med dine venner. Husk at gennemse det fine udvalg af Quest-skriftruller til højre.",
"ianBrokenText": "Velkommen til quest-butikken... Her kan du aktivere quest-skriftruller for at kæmpe mod monstre sammen med dine venner... Husk at gennemse det fine udvalg af quest-skriftruller til højre...",
+ "missingKeyParam": "\"req.params.key\" is required.",
+ "itemNotFound": "Item \"<%= key %>\" not found.",
+ "cannotBuyItem": "You can't buy this item.",
+ "missingTypeKeyEquip": "\"key\" and \"type\" are required parameters.",
+ "missingPetFoodFeed": "\"pet\" and \"food\" are required parameters.",
+ "invalidPetName": "Invalid pet name supplied.",
+ "missingEggHatchingPotionHatch": "\"egg\" and \"hatchingPotion\" are required parameters.",
+ "invalidTypeEquip": "\"type\" must be one of 'equipped', 'pet', 'mount', 'costume'.",
+ "mustPurchaseToSet": "Must purchase <%= val %> to set it on <%= key %>.",
+ "typeRequired": "Type is required",
+ "keyRequired": "Key is required",
+ "notAccteptedType": "Type must be in [eggs, hatchingPotions, food, quests, gear]",
+ "contentKeyNotFound": "Key not found for Content <%= type %>",
+ "plusOneGem": "+1 Gem",
+ "typeNotSellable": "Type is not sellable. Must be one of the following <%= acceptedTypes %>",
+ "userItemsKeyNotFound": "Key not found for user.items <%= type %>",
+ "pathRequired": "Path string is required",
+ "unlocked": "Items have been unlocked",
+ "alreadyUnlocked": "Full set already unlocked.",
+ "alreadyUnlockedPart": "Full set already partially unlocked.",
"USD": "(USD)",
"newStuff": "Nye Ting",
"cool": "Fortæl Mig Senere",
@@ -64,6 +84,7 @@
"tourPetsPage": "Dette er Stalden! Efter niveau 3 kan du udruge kæledyr ved at bruge æg og udrugningseliksirer. Når du udruger et kæledyr i Markedet, vil det dukke op her! Klik på billedet af kæledyret for at føje det til din avatar. Giv dem mad, som du også kan finde efter niveau 3, og de vil vokse sig til kraftfulde ridedyr.",
"tourMountsPage": "Når du har fodret et kæledyr med nok mad bliver det til et ridedyr, der vil bo her. (Kæledyr, ridedyr og mad er tilgængelige efter niveau 3). Klik på et ridedyr for at sætte dig i sadlen!",
"tourEquipmentPage": "Dette er hvor dit Udstyr er opbevaret! Dit kampudstyr påvirker dine stats. Hvis du vil have andet udstyr på din avatar uden at ændre dine stats skal du klikke på \"Brug kostume\".",
+ "equipmentAlreadyOwned": "You already own that piece of equipment",
"tourOkay": "Okay!",
"tourAwesome": "Fedt!",
"tourSplendid": "Godt!",
diff --git a/common/locales/da/pets.json b/common/locales/da/pets.json
index 4e8997694c..7da511fc21 100644
--- a/common/locales/da/pets.json
+++ b/common/locales/da/pets.json
@@ -12,6 +12,7 @@
"etherealLion": "Æterisk Løve",
"veteranWolf": "Veteranulv",
"veteranTiger": "Veterantiger",
+ "veteranLion": "Veteran Lion",
"cerberusPup": "Kerberoshvalp",
"hydra": "Hydra",
"mantisShrimp": "Knælerreje",
@@ -19,7 +20,7 @@
"orca": "Spækhugger",
"royalPurpleGryphon": "Royal Lilla Grif",
"phoenix": "Føniks",
- "bumblebee": "Bumblebee",
+ "magicalBee": "Magical Bee",
"rarePetPop1": "Klik på den gyldne pote for at læse om, hvordan du kan opnå dette sjældne kæledyr ved at bidrage til Habitica!",
"rarePetPop2": "Sådan får du dette kæledyr!",
"potion": "<%= potionType %> Eliksir",
@@ -62,6 +63,7 @@
"hatchedPet": "Du har udklækket et <%= potion %> <%= egg %>!",
"displayNow": "Vis nu",
"displayLater": "Vis senere",
+ "petNotOwned": "You do not own this pet.",
"earnedCompanion": "Med all din produktivitet har du fortjent en ny følgesvend. Giv den mad for at få den til at vokse!",
"feedPet": "Giv <%= text %> til <%= name %>?",
"useSaddle": "Giv <%= pet %> sadel på?",
@@ -83,5 +85,8 @@
"petKeyBoth": "Slip Begge Fri",
"confirmPetKey": "Er du sikker?",
"petKeyNeverMind": "Ikke endnu",
+ "petsReleased": "Pets released.",
+ "mountsAndPetsReleased": "Mounts and pets released",
+ "mountsReleased": "Mounts released",
"gemsEach": "ædelsten hver"
}
\ No newline at end of file
diff --git a/common/locales/da/quests.json b/common/locales/da/quests.json
index dc956de97f..ac8cd4b9aa 100644
--- a/common/locales/da/quests.json
+++ b/common/locales/da/quests.json
@@ -78,5 +78,24 @@
"whichQuestStart": "Hvilken quest vil du starte?",
"getMoreQuests": "Få flere quests",
"unlockedAQuest": "Du har fundet en quest!",
- "leveledUpReceivedQuest": "Du er steget til Niveau <%=level%> og har modtaget en quest-skriftrulle!"
+ "leveledUpReceivedQuest": "Du er steget til Niveau <%=level%> og har modtaget en quest-skriftrulle!",
+ "questInvitationDoesNotExist": "No quest invitation has been sent out yet.",
+ "questInviteNotFound": "No quest invitation found.",
+ "guildQuestsNotSupported": "Guilds cannot be invited on quests.",
+ "questNotFound": "Quest \"<%= key %>\" not found.",
+ "questNotOwned": "You don't own that quest scroll.",
+ "questNotGoldPurchasable": "Quest \"<%= key %>\" is not a Gold-purchasable quest.",
+ "questLevelTooHigh": "You must be level <%= level %> to begin this quest.",
+ "questAlreadyUnderway": "Your party is already on a quest. Try again when the current quest has ended.",
+ "questAlreadyAccepted": "You already accepted the quest invitation.",
+ "noActiveQuestToLeave": "No active quest to leave",
+ "questLeaderCannotLeaveQuest": "Quest leader cannot leave quest",
+ "notPartOfQuest": "You are not part of the quest",
+ "noActiveQuestToAbort": "There is no active quest to abort.",
+ "onlyLeaderAbortQuest": "Only the group or quest leader can abort a quest.",
+ "questAlreadyRejected": "You already rejected the quest invitation.",
+ "cantCancelActiveQuest": "You can not cancel an active quest, use the abort functionality.",
+ "onlyLeaderCancelQuest": "Only the group or quest leader can cancel the quest.",
+ "questNotPending": "There is no quest to start.",
+ "questOrGroupLeaderOnlyStartQuest": "Only the quest leader or group leader can force start the quest"
}
\ No newline at end of file
diff --git a/common/locales/da/questscontent.json b/common/locales/da/questscontent.json
index 07a7061a87..7c00347b1b 100644
--- a/common/locales/da/questscontent.json
+++ b/common/locales/da/questscontent.json
@@ -304,9 +304,21 @@
"questBewilderCompletionChat": "`The Be-Wilder is DEFEATED!`\n\nWe've done it! The Be-Wilder lets out a ululating cry as it twists in the air, shedding feathers like falling rain. Slowly, gradually, it coils into a cloud of sparkling mist. As the newly-revealed sun pierces the fog, it burns away, revealing the coughing, mercifully human forms of Bailey, Matt, Alex.... and the April Fool himself.\n\n`Mistiflying is saved!`\n\nThe April Fool has enough shame to look a bit sheepish. “Oh, hm,” he says. “Perhaps I got a little…. carried away.”\n\nThe crowd mutters. Sodden flowers wash up on sidewalks. Somewhere in the distance, a roof collapses with a spectacular splash.\n\n“Er, yes,” the April Fool says. “That is. What I meant to say was, I’m dreadfully sorry.” He heaves a sigh. “I suppose it can’t all be fun and games, after all. It might not hurt to focus occasionally. Maybe I’ll get a head start on next year’s pranking.”\n\nRedphoenix coughs meaningfully.\n\n“I mean, get a head start on this year’s spring cleaning!” the April Fool says. “Nothing to fear, I’ll have Habit City in spit-shape soon. Luckily nobody is better than I at dual-wielding mops.”\n\nEncouraged, the marching band starts up.\n\nIt isn’t long before all is back to normal in Habit City. Plus, now that the Be-Wilder has evaporated, the magical bees of Mistiflying bustle back to work, and soon the flowers are blooming and the city is floating once more.\n\nAs Habiticans cuddle the magical fuzzy bees, the April Fool’s eyes light up. “Oho, I’ve had a thought! Why don’t you all keep some of these fuzzy Bee Pets and Mounts? It’s a gift that perfectly symbolizes the balance between hard work and sweet rewards, if I’m going to get all boring and allegorical on you.” He winks. “Besides, they don’t have stingers! Fool’s honor.”",
"questBewilderBossRageTitle": "Beguilement Strike",
"questBewilderBossRageDescription": "When this gauge fills, The Be-Wilder will unleash its Beguilement Strike on Habitica!",
- "questBewilderDropBumblebeePet": "Bumblebee (Pet)",
- "questBewilderDropBumblebeeMount": "Bumblebee (Mount)",
+ "questBewilderDropBumblebeePet": "Magical Bee (Pet)",
+ "questBewilderDropBumblebeeMount": "Magical Bee (Mount)",
"questBewilderBossRageMarket": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nOh no! Despite our best efforts, we've gotten distracted by the Be-Wilder’s charming illusions and have forgotten to do some of our Dailies! With a cackling cry, the shining bird beats its wings, raising a swarm of mist around Alex the Merchant. When the fog clears, he has been possessed! “Have some free samples!” he shouts gleefully, and begins to hurl exploding eggs and potions at fleeing Habiticans. Not the most favorable of sales, to be sure.\n\nHurry! Let's stay focused on our Dailies to defeat this monster before it possesses someone else.",
"questBewilderBossRageStables": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nAhh!!! Once again the Be-Wilder has dazzled us into neglecting our Dailies, and now it has attacked Matt the Beast Master! With a swirl of mist, Matt transforms into a terrifying winged creature, and all the pets and mounts howl sadly in their stables. Quickly, stay focused on your tasks to defeat this dastardly distraction!",
- "questBewilderBossRageBailey": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nLook out! In the middle of reporting the news, Bailey the Town Crier has been possessed by the Be-Wilder! She lets out an evil, uninformative screech as she rises into the air. Now how will we know what’s going on?\n\nDon't give up... we're so close to defeating this bothersome bird for once and for all!"
+ "questBewilderBossRageBailey": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nLook out! In the middle of reporting the news, Bailey the Town Crier has been possessed by the Be-Wilder! She lets out an evil, uninformative screech as she rises into the air. Now how will we know what’s going on?\n\nDon't give up... we're so close to defeating this bothersome bird for once and for all!",
+ "questFalconText": "The Birds of Preycrastination",
+ "questFalconNotes": "Mt. Habitica is being overshadowed by a looming mountain of To-Dos. It used to be a place to picnic and enjoy a sense of accomplishment, until the neglected tasks grew out of control. Now it's home to fearsome Birds of Preycrastination, foul creatures which stop Habiticans from completing their tasks!
\"It's too hard!\" they caw at @JonArinbjorn and @Onheiron. \"It'll take too long to do right now! It won't make any difference if you wait until tomorrow! Why don't you do something fun instead?\"
No more, you vow. You will climb your personal mountain of To-Dos and defeat the Birds of Preycrastination!",
+ "questFalconCompletion": "Having finally triumphed over the Birds of Preycrastination, you settle down to enjoy the view and your well-earned rest.
\"Wow!\" says @Trogdorina. \"You won!\"
@Squish adds, \"Here, take these eggs I found as a reward.\"",
+ "questFalconBoss": "Birds of Preycrastination",
+ "questFalconDropFalconEgg": "Falcon (Egg)",
+ "questFalconUnlockText": "Unlocks purchasable Falcon eggs in the Market",
+ "questTreelingText": "The Tangle Tree",
+ "questTreelingNotes": "It's the annual Garden Competition, and everyone is talking about the mysterious project which @aurakami has promised to unveil. You join the crowd on the day of the big announcement, and marvel at the introduction of a moving tree. @fuzzytrees explains that the tree will help with garden maintenance, showing how it can mow the lawn, trim the hedge and prune the roses all at the same time – until the tree suddenly goes wild, turning its secateurs on its creator! The crowd panics as everyone tries to flee, but you aren't afraid – you leap forward, ready to do battle.",
+ "questTreelingCompletion": "You dust yourself off as the last few leaves drift to the floor. In spite of the upset, the Garden Competition is now safe – although the tree you just reduced to a heap of wood chips won't be winning any prizes! \"Still a few kinks to work out there,\" @PainterProphet says. \"Perhaps someone else would do a better job of training the saplings. Do you fancy a go?\"",
+ "questTreelingBoss": "Tangle Tree",
+ "questTreelingDropTreelingEgg": "Treeling (Egg)",
+ "questTreelingUnlockText": "Unlocks purchasable Treeling eggs in the Market"
}
\ No newline at end of file
diff --git a/common/locales/da/rebirth.json b/common/locales/da/rebirth.json
index ca786a86f5..eea6816f8b 100644
--- a/common/locales/da/rebirth.json
+++ b/common/locales/da/rebirth.json
@@ -5,7 +5,7 @@
"rebirthStartOver": "Genfødsel genstarter din karakter fra Niveau 1.",
"rebirthAdvList1": "Du vender tilbage til fuldt Liv.",
"rebirthAdvList2": "Du har ingen Erfaring, Guld eller Udstyr (med undtagelse af gratis ting såsom Mystiske Varer)",
- "rebirthAdvList3": "Dine Vaner, Daglige og To-Dos nulstilles til gul, og striber nulstilles.",
+ "rebirthAdvList3": "Your Habits, Dailies, and To-Dos reset to yellow, and streaks reset, except for challenge tasks.",
"rebirthAdvList4": "Din startklasse er Kriger, indtil du kan vælge en ny Klasse.",
"rebirthInherit": "Din nye karakter arver nogle få ting fra sin forgænger:",
"rebirthInList1": "Opgaver, historik og indstillinger forbliver.",
@@ -24,5 +24,6 @@
"rebirthPop": "Begynd en ny karakter på Niveau 1 mens du beholder præstationer, samlerobjekter, og opgaver med historik.",
"rebirthName": "Genfødselskugle",
"reborn": "Genfødt, højeste Niveau <%= reLevel %>",
- "confirmReborn": "Er du sikker?"
+ "confirmReborn": "Er du sikker?",
+ "rebirthComplete": "You have been reborn!"
}
\ No newline at end of file
diff --git a/common/locales/da/settings.json b/common/locales/da/settings.json
index fb75d23833..4f48dd224f 100644
--- a/common/locales/da/settings.json
+++ b/common/locales/da/settings.json
@@ -47,6 +47,7 @@
"customDayStart": "Brugerdefineret Dagstart",
"changeCustomDayStart": "Skift Brugerdefineret Dagstart?",
"sureChangeCustomDayStart": "Er du sikker på, at du vil ændre din brugerdefinerede dagsstart?",
+ "customDayStartHasChanged": "Your custom day start has changed.",
"nextCron": "Dine Daglige vil blive nulstillet første gang du bruger Habitica efter <%= time %>. Vær sikker på, at du har færdiggjort dine Daglige før dette tidspunkt!",
"customDayStartInfo1": "Som udgangspunkt vil Habitica tjekke og nulstille dine Daglige ved midnat hver dag. Du kan ændre dette her.",
"misc": "Diverse",
@@ -61,12 +62,23 @@
"newUsername": "Nyt Loginnavn",
"dangerZone": "Farezone",
"resetText1": "ADVARSEL! Dette nulstiller mange dele af din konto. Vi fraråder på det kraftigste dette, men nogen finder det brugbart i begyndelsen, efter at have spillet i kort tid.",
- "resetText2": "Du vil miste alle dine niveauer, dit guld og dine erfaringspoint. Alle dine opgaver vil blive slettet permanent og du vil miste alle dine opgavers historiske data. Du vil miste alt dit udstyr, men du kan købe det hele igen, inklusive alle specielle udgaver og abonnenters mystiske varer som du allerede ejer (du skal dog være den korrekte klasse for at købe klassespecifikt udstyr). Du vil beholde din nuværende klasse og dine kæledyr og ridedyr. Du vil måske foretrække at bruge en Genfødselskugle i stedet, hvilket er et meget sikrere valg og vil lade dig beholde dine opgaver.",
+ "resetText2": "You will lose all your levels, gold, and experience points. All your tasks (except those from challenges) will be deleted permanently and you will lose all of their historical data. You will lose all your equipment but you will be able to buy it all back, including all limited edition equipment or subscriber Mystery items that you already own (you will need to be in the correct class to re-buy class-specific gear). You will keep your current class and your pets and mounts. You might prefer to use an Orb of Rebirth instead, which is a much safer option and which will preserve your tasks.",
"deleteText": "Er du sikker? Dette vil slette din konto for evigt, og den kan aldrig blive genskabt! Hvis du vil bruge Habitica igen skal du oprette en ny konto. Opsparede eller brugte Ædelsten vil ikke blive refunderet. Hvis du er helt sikker, skriv <%= deleteWord %> i tekstfeltet herunder.",
"API": "API",
+ "APIv3": "API v3",
+ "APIv2": "API v2 - Deprecated",
"APIText": "Kopiér disse til brug i tredjeparts-applikationer. Dog skal du tænke på din API Nøgle som et kodeord, og lade være med at dele den offentligt. Du kan nogen gange blive bedt om dit Bruger ID, men skriv aldrig din API Nøgle hvor andre kan se den, heller ikke på Github.",
"APIToken": "API Nøgle (det er et kodeord - se advarsel ovenfor!)",
+ "thirdPartyApps": "Tredjeparts Apps",
+ "dataToolDesc": "En side som viser dig visse oplysninger om din Habitica bruger, såsom statistisk omkring dine opgaver, udstyr og skills.",
+ "beeminder": "Beeminder",
+ "beeminderDesc": "Let Beeminder automatically monitor your Habitica To-Dos. You can commit to maintaining a target number of To-Dos completed per day or per week, or you can commit to gradually reducing your remaining number of uncompleted To-Dos. (By \"commit\" Beeminder means under threat of paying actual money! But you may also just like Beeminder's fancy graphs.)",
+ "chromeChatExtension": "Chrome Chat Udvidelse",
+ "chromeChatExtensionDesc": "The Chrome Chat Extension for Habitica adds an intuitive chat box to all of habitica.com. It allows users to chat in the Tavern, their party, and any guilds they are in.",
+ "otherExtensions": "Other Extensions",
+ "otherDesc": "Find andre apps, udvidelser og værktøjer på Habitica wiki'en.",
"resetDo": "Gør det, nulstil min konto!",
+ "resetComplete": "Reset complete!",
"fixValues": "Ret Værdier",
"fixValuesText1": "Hvis du er løbet ind i en fejl eller har lavet en bommert, der har ændret din karakter på en unfair måde (såsom skade du ikke skulle have taget, Guld du ikke har tjent rigtigt osv.), kan du manuelt rette tallene her. Ja, det gør det muligt at snyde, så brug denne mulighed fornuftigt, ellers vil du bare sabotere din egen vaneopbygning.",
"fixValuesText2": "Vær opmærksom på at du ikke kan genskabe Striber her. For at gøre det kan du rette den Daglige og se under Avancerede Indstillinger, hvor du vil finde et \"Genskab Stribe\"-felt.",
@@ -96,13 +108,14 @@
"emailNotifications": "Mail-notifikationer",
"wonChallenge": "Du har vundet en Udfordring!",
"newPM": "Modtaget Privatbesked",
+ "sentGems": "Sent gems!",
"giftedGems": "Ædelstensgave",
"giftedGemsInfo": "<%= amount %> Ædelsten - af <%= name %>",
"giftedSubscription": "Abonnementsgave",
"invitedParty": "Gruppeinvitation",
"invitedGuild": "Klaninvitation",
"importantAnnouncements": "Din konto er inaktiv",
- "weeklyRecaps": "Summaries of your account activity in the past week (Note: this is currently disabled due to performance issues, but we hope to have this back up and sending e-mails again soon!)",
+ "weeklyRecaps": "Oversigt over aktiviteter på din konto den sidste uge (Bemærk: dette er midlertidigt slået fra grundet ydelsesproblemer, men vi håber snarest at have det oppe at køre og sende emails igen!)",
"questStarted": "Jeres Quest er Begyndt",
"invitedQuest": "Inviteret til Quest",
"kickedGroup": "Fjernet fra gruppe",
@@ -135,6 +148,11 @@
"webhooks": "Webhooks",
"enabled": "Aktiveret",
"webhookURL": "Webhook URL",
+ "invalidUrl": "invalid url",
+ "invalidEnabled": "the \"enabled\" parameter should be a boolean",
+ "regIdRequired": "RegId is required",
+ "pushDeviceAdded": "Push device added successfully",
+ "pushDeviceAlreadyAdded": "The user already has the push device",
"add": "Tilføj",
"buyGemsGoldCap": "Maksimum hævet til <%= amount %>",
"mysticHourglass": "<%= amount %> Mystiske Timeglas",
@@ -148,6 +166,6 @@
"paypal": "PayPal",
"amazonPayments": "Amazon Payments",
"timezone": "Tidszone",
- "timezoneUTC": "Habitica uses the time zone set on your PC, which is: <%= utc %>",
- "timezoneInfo": "If that time zone is wrong, first reload this page using your browser's reload or refresh button to ensure that Habitica has the most recent information. If it is still wrong, adjust the time zone on your PC and then reload this page again.
If you use Habitica on other PCs or mobile devices, the time zone must be the same on them all. If your Dailies have been reseting at the wrong time, repeat this check on all other PCs and on a browser on your mobile devices."
+ "timezoneUTC": "Habitica bruger tidszonen sat på din PC, hvilken er: <%= utc %>",
+ "timezoneInfo": "If that time zone is wrong, first reload this page using your browser's reload or refresh button to ensure that Habitica has the most recent information. If it is still wrong, adjust the time zone on your PC and then reload this page again.
If you use Habitica on other PCs or mobile devices, the time zone must be the same on them all. If your Dailies have been resetting at the wrong time, repeat this check on all other PCs and on a browser on your mobile devices."
}
\ No newline at end of file
diff --git a/common/locales/da/spells.json b/common/locales/da/spells.json
index 416939e635..54be25f1c5 100644
--- a/common/locales/da/spells.json
+++ b/common/locales/da/spells.json
@@ -35,8 +35,8 @@
"spellSpecialSnowballAuraNotes": "Kast en snebold efter et gruppemedlem! Kom nu, hvad kan der ske? Holder indtil medlemmet skifter dag.",
"spellSpecialSaltText": "Salt",
"spellSpecialSaltNotes": "Nogen har kastet en snebold på dig! Haha, skideskægt... Fjern nu bare sneen fra mig!",
- "spellSpecialSpookDustText": "Skræmmende Gnister",
- "spellSpecialSpookDustNotes": "Gør din ven til et svævende lagen med øjne!",
+ "spellSpecialSpookySparklesText": "Spooky Sparkles",
+ "spellSpecialSpookySparklesNotes": "Turn a friend into a floating blanket with eyes!",
"spellSpecialOpaquePotionText": "Uigennemsigtig Eliksir",
"spellSpecialOpaquePotionNotes": "Fjerner effekterne af Skræmmende Gnister.",
"spellSpecialShinySeedText": "Skinnende Frø",
@@ -46,5 +46,11 @@
"spellSpecialSeafoamText": "Havskum",
"spellSpecialSeafoamNotes": "Gør en ven til et søuhyre!",
"spellSpecialSandText": "Sand",
- "spellSpecialSandNotes": "Fjerner effekterne af Havskum."
+ "spellSpecialSandNotes": "Fjerner effekterne af Havskum.",
+ "spellNotFound": "Skill \"<%= spellId %>\" not found.",
+ "partyNotFound": "Party not found",
+ "targetIdUUID": "\"targetId\" must be a valid User ID.",
+ "challengeTasksNoCast": "Casting a skill on challenge tasks is not allowed.",
+ "spellNotOwned": "You don't own this skill.",
+ "spellLevelTooHigh": "You must be level <%= level %> to use this skill."
}
\ No newline at end of file
diff --git a/common/locales/da/subscriber.json b/common/locales/da/subscriber.json
index ac8418e1ec..a0357a9f0c 100644
--- a/common/locales/da/subscriber.json
+++ b/common/locales/da/subscriber.json
@@ -4,6 +4,8 @@
"subDescription": "Køb ædelsten for guld, få månedlige mystiske ting, behold din fremskridtshistorik, daglig drop-grænse fordoblet, støt udviklerne. Klik for mere info.",
"buyGemsGold": "Køb Ædelsten med Guld",
"buyGemsGoldText": "Købmanden Alexander vil sælge dig Ædelsten til en pris på <%= gemCost %> guld pr. ædelsten. Hans månedlige levering er til at starte med begrænset til <%= gemLimit %> Ædelsten pr. måned, men begrænsningen stiger med 5 Ædelsten hver tredje måned med fortsat abonnement, op til et maksimum på 50 Ædelsten pr. måned!",
+ "mustSubscribeToPurchaseGems": "Must subscribe to purchase gems with GP",
+ "reachedGoldToGemCap": "You've reached the Gold=>Gem conversion cap <%= convCap %> for this month. We have this to prevent abuse / farming. The cap will reset within the first three days of next month.",
"retainHistory": "Behold yderligere historik",
"retainHistoryText": "Gør dine færdiggjorte To-Dos og opgavehistorik tilgængelige i længere tid.",
"doubleDrops": "Daglig drop-grænse fordoblet",
@@ -29,6 +31,7 @@
"manageSub": "Klik for at bestyre dit abonnement",
"cancelSub": "Annullér Abonnement",
"canceledSubscription": "Abonnement annulleret",
+ "cancelingSubscription": "Canceling the subscription",
"adminSub": "Administratorabonnementer",
"morePlans": "More Plans Coming Soon",
"organizationSub": "Privat Organisation",
@@ -73,6 +76,9 @@
"timeTravelersPopover": "Vi kan se du har et Mystisk Timeglas, så vi vil gerne rejse tilbage i tiden for dig! Vælg venligst hvilket kæledyr, ridedyr eller Mystisk Sæt du vil have. Du kan se en liste af tidligere sæt her! Hvis det ikke er nok kan du måske interesseres i et af vores moderigtige futuristiske Steampunk-sæt?",
"timeTravelersAlreadyOwned": "Tillykke! Du ejer allerede alting de Tidsrejsende kan få fat i. Tak for at støtte siden!",
"mysticHourglassPopover": "Mystiske Timeglas giver dig adgang til at købe visse tidsbegrænsede varer, såsom tidligere måneders Abonnent-sæt og præmier fra tidligere verdensbosser.",
+ "mysterySetNotFound": "Mystery set not found, or set already owned.",
+ "mysteryItemIsEmpty": "Mystery items are empty",
+ "mysteryItemOpened": "Mystery item opened.",
"mysterySet201402": "Bevinget Sendebudssæt",
"mysterySet201403": "Skovvandrersæt",
"mysterySet201404": "Tusmørkesommerfuglesæt",
@@ -99,6 +105,8 @@
"mysterySet201601": "Champion of Resolution Set",
"mysterySet201602": "Heartbreaker Set",
"mysterySet201603": "Lucky Clover Set",
+ "mysterySet201604": "Leaf Warrior Set",
+ "mysterySet201605": "Marching Bard Set",
"mysterySet301404": "Steampunk Standardsæt",
"mysterySet301405": "Steampunk Tilbehørssæt",
"mysterySetwondercon": "Wondercon",
@@ -110,9 +118,25 @@
"hourglassBuyItemConfirm": "Køb denne ting for 1 Mystisk Timeglas?",
"petsAlreadyOwned": "Ejer allerede dette Kæledyr.",
"mountsAlreadyOwned": "Ejer allerede dette Ridedyr.",
- "typeNotAllowedHourglass": "Denne type kan ikke købes for Mystiske Timeglas. Tilladte typer:",
+ "typeNotAllowedHourglass": "Item type not supported for purchase with Mystic Hourglass. Allowed types: <%= allowedTypes %>",
"petsNotAllowedHourglass": "Kæledyr kan ikke købes for Mystiske Timeglas.",
"mountsNotAllowedHourglass": "Ridedyr kan ikke købes for Mystiske Timeglas.",
"hourglassPurchase": "Købte en ting for et Mystisk Timeglas!",
- "hourglassPurchaseSet": "Købte et sæt for et Mystisk Timeglas!"
+ "hourglassPurchaseSet": "Købte et sæt for et Mystisk Timeglas!",
+ "missingUnsubscriptionCode": "Missing unsubscription code.",
+ "missingSubscription": "User does not have a plan subscription",
+ "missingSubscriptionCode": "Missing subscription code. Possible values: basic_earned, basic_3mo, basic_6mo, google_6mo, basic_12mo.",
+ "cannotDeleteActiveAccount": "You have an active subscription, cancel your plan before deleting your account.",
+ "paymentNotSuccessful": "The payment was not successful",
+ "planNotActive": "The plan hasn't activated yet (due to a PayPal bug). It will begin <%= nextBillingDate %>, after which you can cancel to retain your full benefits",
+ "notAllowedHourglass": "Pet/Mount not available for purchase with Mystic Hourglass.",
+ "readCard": "<%= cardType %> has been read",
+ "cardTypeRequired": "Card type required",
+ "cardTypeNotAllowed": "Unknown card type.",
+ "invalidCoupon": "Invalid coupon code.",
+ "couponUsed": "Coupon code already used.",
+ "noSudoAccess": "You don't have sudo access.",
+ "couponCodeRequired": "The coupon code is required.",
+ "eventRequired": "\"req.params.event\" is required.",
+ "countRequired": "\"req.query.count\" is required."
}
\ No newline at end of file
diff --git a/common/locales/da/tasks.json b/common/locales/da/tasks.json
index 6c2259624d..eb797ec9af 100644
--- a/common/locales/da/tasks.json
+++ b/common/locales/da/tasks.json
@@ -73,6 +73,7 @@
"clearTags": "Ryd",
"hideTags": "Skjul",
"showTags": "Vis",
+ "toRequired": "You must supply a to value",
"startDate": "Startdato",
"startDateHelpTitle": "Hvornår skal denne opgave starte?",
"startDateHelp": "Sæt datoen for, hvornår denne opgave begynder. Opgaven er ikke aktuel før den valgte dag.",
@@ -88,8 +89,9 @@
"fortifyName": "Forstærkningseliksir",
"fortifyPop": "Returnerer alle opgaver til neutral værdi (gul farve) og giver alt tabt liv tilbage.",
"fortify": "Forstærk",
- "fortifyText": "Forstærk vil returnere alle dine opgaver til en neutral (gul) værdi, som om du lige har tilføjet dem, og give dig fuldt Liv tilbage. Dette er godt hvis alle dine røde opgaver gør spillet for hårdt, eller alle dine blå opgaver gør det for nemt. Hvis du hellere vil starte forfra, så brug Ædelsten og bliv benådiget!",
+ "fortifyText": "Fortify will return all your tasks, except challenge tasks, to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!",
"confirmFortify": "Er du sikker?",
+ "fortifyComplete": "Fortify complete!",
"sureDelete": "Er du sikker på at du vil slette denne <%= taskType %> med teksten \"<%= taskText %>\"?",
"streakCoins": "Stribebonus!",
"pushTaskToTop": "Flyt opgave til toppen. Hold ctrl eller cmd nede for at flytte til bunden.",
@@ -112,5 +114,18 @@
"rewardHelp2": "Udstyr påvirker dine stats (<%= linkStart %>Bruger > Stats<%= linkEnd %>).",
"rewardHelp3": "Specielt Udstyr vil dukke op her når der er Verdensevents.",
"rewardHelp4": "Vær ikke bange for at oprette selvvalgte Belønninger! Se nogle eksempler her.",
- "clickForHelp": "Klik for hjælp"
+ "clickForHelp": "Klik for hjælp",
+ "taskIdRequired": "\"taskId\" must be a valid UUID.",
+ "taskNotFound": "Task not found.",
+ "invalidTaskType": "Task type must be one of \"habit\", \"daily\", \"todo\", \"reward\".",
+ "cantDeleteChallengeTasks": "A task belonging to a challenge can't be deleted.",
+ "checklistOnlyDailyTodo": "Checklists are supported only on dailies and todos",
+ "checklistItemNotFound": "No checklist item was found with given id.",
+ "itemIdRequired": "\"itemId\" must be a valid UUID.",
+ "tagNotFound": "No tag item was found with given id.",
+ "tagIdRequired": "\"tagId\" must be a valid UUID corresponding to a tag belonging to the user.",
+ "positionRequired": "\"position\" is required and must be a number.",
+ "cantMoveCompletedTodo": "Can't move a completed todo.",
+ "directionUpDown": "\"direction\" is required and must be 'up' or 'down'",
+ "alreadyTagged": "The task is already tagged with given tag."
}
\ No newline at end of file
diff --git a/common/locales/de/backgrounds.json b/common/locales/de/backgrounds.json
index 78916b57e5..a1b6cc5074 100644
--- a/common/locales/de/backgrounds.json
+++ b/common/locales/de/backgrounds.json
@@ -13,7 +13,7 @@
"backgroundOpenWatersText": "Offenes Meer",
"backgroundOpenWatersNotes": "Genieße das offene Meer.",
"backgroundSeafarerShipText": "Seeschiff",
- "backgroundSeafarerShipNotes": "Segle an Board eines Seeschiffes",
+ "backgroundSeafarerShipNotes": "Segle an Bord eines Seeschiffes",
"backgrounds082014": "Set 3: Veröffentlicht im August 2014",
"backgroundCloudsText": "Wolken",
"backgroundCloudsNotes": "Fliege durch die Wolken.",
@@ -44,11 +44,11 @@
"backgroundSunsetMeadowNotes": "Bewundere die Wiesen im Abendrot",
"backgrounds122014": "Set 7: Veröffentlicht im Dezember 2014",
"backgroundIcebergText": "Eisberg",
- "backgroundIcebergNotes": "Treibe dahin auf einem Eisberg",
+ "backgroundIcebergNotes": "Treibe dahin auf einem Eisberg.",
"backgroundTwinklyLightsText": "Glitzernde Winterlichter",
- "backgroundTwinklyLightsNotes": "Spaziere unter festlich geschmückten Bäumen herum",
+ "backgroundTwinklyLightsNotes": "Spaziere unter festlich geschmückten Bäumen herum.",
"backgroundSouthPoleText": "Südpol",
- "backgroundSouthPoleNotes": "Besuche den eisigen Südpol",
+ "backgroundSouthPoleNotes": "Besuche den eisigen Südpol.",
"backgrounds012015": "Set 8: Veröffentlicht im Januar 2015",
"backgroundIceCaveText": "Eishöhle",
"backgroundIceCaveNotes": "Steig in eine Eishöhle hinab.",
@@ -154,11 +154,18 @@
"backgroundRainforestNotes": "Wage Dich in einen Regenwald.",
"backgroundStoneCircleText": "Steinkreis",
"backgroundStoneCircleNotes": "Wirke Zauber in einem Steinkreis.",
- "backgrounds042016": "SET 23: Released April 2016",
- "backgroundArcheryRangeText": "Archery Range",
- "backgroundArcheryRangeNotes": "Practice on the Archery Range.",
- "backgroundGiantFlowersText": "Giant Flowers",
- "backgroundGiantFlowersNotes": "Frolic atop Giant Flowers.",
- "backgroundRainbowsEndText": "End of the Rainbow",
- "backgroundRainbowsEndNotes": "Discover gold at the End of the Rainbow."
+ "backgrounds042016": "Set 23: Veröffentlicht im April 2016",
+ "backgroundArcheryRangeText": "Bogen-Schießplatz",
+ "backgroundArcheryRangeNotes": "Übe auf dem Bogen-Schießplatz.",
+ "backgroundGiantFlowersText": "Riesenblumen",
+ "backgroundGiantFlowersNotes": "Tolle über Riesenblumen.",
+ "backgroundRainbowsEndText": "Ende des Regenbogens",
+ "backgroundRainbowsEndNotes": "Entdecke Gold am Ende des Regenbogens.",
+ "backgrounds052016": "Set 24: Veröffentlicht im Mai 2016",
+ "backgroundBeehiveText": "Bienenstock",
+ "backgroundBeehiveNotes": "Summe und tanze in einem Bienenstock.",
+ "backgroundGazeboText": "Aussichtspunkt",
+ "backgroundGazeboNotes": "Erklimm einen Aussichtspunkt.",
+ "backgroundTreeRootsText": "Baumwurzeln",
+ "backgroundTreeRootsNotes": "Erforsche die Baumwurzeln."
}
\ No newline at end of file
diff --git a/common/locales/de/challenge.json b/common/locales/de/challenge.json
index 3204e43d72..ed7c319ecc 100644
--- a/common/locales/de/challenge.json
+++ b/common/locales/de/challenge.json
@@ -16,7 +16,7 @@
"selectWinner": "Wähle einen Gewinner und beende den Wettbewerb.",
"deleteOrSelect": "Löschen oder Gewinner wählen",
"endChallenge": "Wettbewerb beenden",
- "challengeDiscription": "Dies sind die Aufgaben des Wettbewerbs, die Deiner Aufgabenseite hinzugefügt werden, wenn Du dem Wettbewerb beitrittst. Die untenstehenden Beispielaufgaben ändern ihre Farben und erhalten Graphen, die dir den Gesamtfortschritt der Gruppe zeigen.",
+ "challengeDiscription": "Dies sind die Aufgaben des Wettbewerbs, die Deiner Aufgabenseite hinzugefügt werden, wenn Du dem Wettbewerb beitrittst. Die untenstehenden Beispielaufgaben ändern ihre Farben und erhalten Graphen, die Dir den Gesamtfortschritt der Gruppe zeigen.",
"hows": "Wie läuft es bei Euch allen?",
"filter": "Filter",
"groups": "Gruppen",
@@ -29,7 +29,7 @@
"createChallenge": "Wettbewerb erstellen",
"discard": "Verwerfen",
"challengeTitle": "Titel des Wettbewerbs",
- "challengeTag": "Tag Name",
+ "challengeTag": "Tag-Name",
"challengeTagPop": "Wettbewerbe erscheinen als Tag-Liste und Aufgabenbeschreibungen. Also ist es einerseits sinnvoll, oben einen beschreibenden Titel zu wählen, andererseits brauchst Du aber auch eine \"Kurzfassung\". Dazu kannst Du zum Beispiel \"Verliere 5 Kilo in 3 Monaten\" mit \"-5kg\" abkürzen. (Klicke auf das '?' für mehr Informationen).",
"challengeDescr": "Beschreibung",
"prize": "Preis",
@@ -47,7 +47,7 @@
"sureDelChaTavern": "Willst Du diesen Wetbewerb wirklich löschen? Deine Edelsteine werden nicht erstattet.",
"removeTasks": "Aufgabe entfernen",
"keepTasks": "Aufgabe behalten",
- "closeCha": "Wettbewerb auswählen und ...",
+ "closeCha": "Wettbewerb schließen und ...",
"leaveCha": "Wettbewerb verlassen und ...",
"challengedOwnedFilterHeader": "Besitz",
"challengedOwnedFilter": "In Deinem Besitz",
@@ -63,5 +63,21 @@
"congratulations": "Gratulation!",
"hurray": "Hurra!",
"noChallengeOwner": "Kein Besitzer",
- "noChallengeOwnerPopover": "Dieser Wettbewerb hat keinen Besitzer, da der Spieler, der den Wettbewerb erstellt hat, sein Benutzerkonto gelöscht hat."
+ "noChallengeOwnerPopover": "Dieser Wettbewerb hat keinen Besitzer, da der Spieler, der den Wettbewerb erstellt hat, sein Benutzerkonto gelöscht hat.",
+ "challengeMemberNotFound": "Benutzer wurde nicht unter den Wettbewerbsteilnehmern gefunden",
+ "onlyGroupLeaderChal": "Nur der Gruppenleiter kann neue Wettbewerbe erstellen",
+ "tavChalsMinPrize": "Der Preis für Gasthaus-Wettbewerbe muss mindestens 1 Edelstein sein.",
+ "cantAfford": "Du kannst Dir diesen Preis nicht leisten. Kaufe mehr Edelsteine oder verringere den Preis.",
+ "challengeIdRequired": "\"challengeId\" muss eine gültige UUID sein.",
+ "winnerIdRequired": "\"winnerId\" muss eine gültige UUID sein.",
+ "challengeNotFound": "Wettbewerb nicht gefunden.",
+ "onlyLeaderDeleteChal": "Nur der Wettbewerbsleiter kann es löschen.",
+ "onlyLeaderUpdateChal": "Nur der Wettbewerbsleiter kann es aktualisieren.",
+ "winnerNotFound": "Gewinner mit ID \"<%= userId %>\" nicht gefunden, oder nimmt nicht am Wettbewerb teil.",
+ "noCompletedTodosChallenge": "\"includeComepletedTodos\" wird beim Herunterladen von Wettbewerbsaufgaben nicht unterstützt.",
+ "userTasksNoChallengeId": "Wenn \"tasksOwner\" \"user\" ist, kann \"challengeId\" nicht angegeben werden.",
+ "onlyChalLeaderEditTasks": "Aufgaben, die zu einem Wettbewerb gehören, können nur vom Leiter bearbeitet werden.",
+ "userAlreadyInChallenge": "Der Benutzer nimmt bereits an diesem Wettbewerb teil.",
+ "cantOnlyUnlinkChalTask": "Nur ungültige Wettbewerbsaufgaben können entfernt werden.",
+ "shortNameTooShort": "Tag-Namen müssen mindestens 3 Zeichen lang sein."
}
\ No newline at end of file
diff --git a/common/locales/de/character.json b/common/locales/de/character.json
index 8f7f4373e1..97c5ca8a81 100644
--- a/common/locales/de/character.json
+++ b/common/locales/de/character.json
@@ -1,4 +1,5 @@
{
+ "communityGuidelinesWarning": "Bitte denke daran, dass Dein angezeigter Name, Dein Profilbild und Dein Profiltext die Community-Richtlinien einhalten müssen (das heißt keine Fluchwörter, keine sexuellen Themen, keine Beleidigungen, usw.). Wenn Du Fragen hast, ob etwas angebracht ist oder nicht, schreib einfach eine E-Mail an leslie@habitica.com!",
"statsAch": "Werte & Erfolge",
"profile": "Profil",
"avatar": "Avatar anpassen",
@@ -8,8 +9,8 @@
"displayPhoto": "Foto",
"displayBlurb": "Über mich ...",
"displayBlurbPlaceholder": "Stelle Dich bitte vor",
- "photoUrl": "Foto Url",
- "imageUrl": "Bild Url",
+ "photoUrl": "Foto-Url",
+ "imageUrl": "Bild-Url",
"inventory": "Inventar",
"social": "Soziales",
"lvl": "Lvl.",
@@ -19,7 +20,7 @@
"bodySlim": "Dünn",
"bodyBroad": "Kräftig",
"unlockSet": "Set freischalten - <%= cost %>",
- "locked": "Stück",
+ "locked": "gesperrt",
"shirts": "Shirts",
"specialShirts": "Besondere Shirts",
"bodyHead": "Frisuren und Haarfarben",
@@ -28,26 +29,26 @@
"bodyHair": "Haar",
"hairBangs": "Frisur vorne",
"hairBase": "Frisur hinten",
- "hairSet1": "Frisuren Set 1",
- "hairSet2": "Frisuren Set 2",
+ "hairSet1": "Frisurenset 1",
+ "hairSet2": "Frisurenset 2",
"bodyFacialHair": "Gesichtsbehaarung",
"beard": "Bart",
"mustache": "Schnurrbart",
"flower": "Blume",
- "wheelchair": "Wheelchair",
+ "wheelchair": "Rollstuhl",
"basicSkins": "Normale Hautfarben",
- "rainbowSkins": "Regenbogen Hautfarben",
- "pastelSkins": "Pastell Farben",
- "spookySkins": "Unheimliche Hautfaren",
+ "rainbowSkins": "Regenbogen-Hautfarben",
+ "pastelSkins": "Pastell-Hautfarben",
+ "spookySkins": "Unheimliche Hautfarben",
"supernaturalSkins": "Übernatürliche Hautfarben",
"splashySkins": "Sensationelle Hautfarben",
- "rainbowColors": "Regenbogen Farben",
+ "rainbowColors": "Regenbogenfarben",
"shimmerColors": "Schimmernde Farben",
"hauntedColors": "Spukfarben",
"winteryColors": "Winterfarben",
"equipment": "Ausrüstung",
"equipmentBonus": "Ausrüstung",
- "equipmentBonusText": "Attributboni, die von Deiner Kampfausrüstung stammen. Im Ausrüstungs Tab unter Inventar kannst Du Deine Kampfausrüstung zusammenstellen.",
+ "equipmentBonusText": "Attributboni, die von Deiner Kampfausrüstung stammen. Im Ausrüstungs-Tab unter Inventar kannst Du Deine Kampfausrüstung zusammenstellen.",
"classBonus": "Klassenbonus der Ausrüstung",
"classBonusText": "Deine Klasse (Krieger, wenn Du keine andere Klasse freigeschaltet oder gewählt hast) kann ihre eigene Ausrüstung besser nutzen, als die Ausrüstung anderer Klassen. Ausrüstung Deiner aktuellen Klasse verleiht einen 50% größeren Attributbonus als klassenfremde Ausrüstung.",
"classEquipBonus": "Klassenbonus",
@@ -58,7 +59,7 @@
"costumeText": "Wenn Du das Aussehen einer anderen Ausrüstung Deiner Kampfausrüstung vorziehst, dann klicke auf die \"Verkleidung tragen\" Box um über Deiner Kampfausrüstung andere Ausrüstungsgegenstände zu tragen.",
"useCostume": "Verkleidung tragen",
"useCostumeInfo1": "Klicke \"Verkleidung tragen\" an, um Deinem Avatar Gegenstände aus Deinem Inventar anzuziehen, ohne dass sich das auf die Statuswerte Deiner Kampfausrüstung auswirkt! Das bedeutet dass Du Dich links für die besten Statuswerte ausrüsten kannst, und Dich rechts mit Deiner Ausrüstung richtig in Schale werfen kannst.",
- "useCostumeInfo2": "Wenn Du \"Verkleidung tragen\" anklickst, wird Dein Avatar erst mal ziemlich einfach aussehen ... aber keine Sorge! Wenn Du nach linkst guckst, kannst Du sehen dass Du immer noch mit Deine Kampfausrüstung ausgerüstet bist. Jetzt kannst Du Dich herausputzen! Alles was Du rechts anziehst wird Deine Statuswerte nicht beeinflussen, aber dir dabei helfen, den perfekten Look zu finden. Probier verschiedene Kombinationen aus, mische verschiedene Sets, und passe Dein Kostüm an Deine Haustiere, Reittiere und Hintergründe an.
Noch Fragen? Lies auf der Kostümseite in der Wiki nach. Du hast das perfekte Ensemble gefunden? Führe es in der Costume Carnival Gilde oder im Gasthaus vor!",
+ "useCostumeInfo2": "Wenn Du \"Verkleidung tragen\" anklickst, wird Dein Avatar erst mal ziemlich einfach aussehen ... aber keine Sorge! Wenn Du nach linkst guckst, kannst Du sehen dass Du immer noch mit Deine Kampfausrüstung ausgerüstet bist. Jetzt kannst Du Dich herausputzen! Alles was Du rechts anziehst wird Deine Statuswerte nicht beeinflussen, aber Dir dabei helfen, den perfekten Look zu finden. Probier verschiedene Kombinationen aus, mische verschiedene Sets, und passe Dein Kostüm an Deine Haustiere, Reittiere und Hintergründe an.
Noch Fragen? Lies auf der Kostümseite in der Wiki nach. Du hast das perfekte Ensemble gefunden? Führe es in der Costume Carnival Gilde oder im Gasthaus vor!",
"gearAchievement": "Du hast den Erfolg \"Ultimative Ausrüstung\" erhalten, da Du die beste Ausrüstung erworben hast! Du hast die folgenden Sets vollständig:",
"moreGearAchievements": "Um mehr Abzeichen „Ultimative Ausrüstung“ zu erhalten, ändere Deine Klasse auf Deiner Statuswerteseite und kaufe die gesamte Ausrüstung Deiner neuen Klasse!",
"armoireUnlocked": "Du hast außerdem den Verzauberten Schrank freigeschaltet! Klicke auf den Verzauberten Schrank um eine zufällige Ausrüstung zu erhalten. Er kann Dir auch zufällige Erfahrungspunkte oder Nahrung geben.",
@@ -109,6 +110,7 @@
"mage": "Magier",
"mystery": "Überraschung",
"changeClass": "Wechsle die Klasse und erhalte alle Attributpunkte zurück",
+ "lvl10ChangeClass": "Um Deine Klasse zu ändern, musst Du mindestens auf Level 10 sein.",
"levelPopover": "Jedes Level erhältst Du einen Punkt, den Du in ein Attribut Deiner Wahl setzen kannst. Du kannst Deine Punkte manuell verteilen, oder das Spiel entscheiden lassen indem Du eines der vorgegebenen Verteilungsmuster auswählst.",
"unallocated": "Freie Attributpunkte",
"haveUnallocated": "Du hast <%= points %> freie(n) Attributpunkt(e)",
@@ -136,7 +138,7 @@
"streaksFrozenText": "Strähnen täglicher Aufgaben werden am Ende des Tages nicht zurückgesetzt.",
"respawn": "Auferstehen!",
"youDied": "Du bist gestorben!",
- "dieText": "Du hast ein Level, Dein gesamtes Gold und einen zufälliges Teil Deiner Ausrüstung verloren. Erhebe Dich, Habiteer, und versuche es erneut! Gebiete diesen schlechten Gewohnheiten Einhalt, sei gewissenhaft bei der Erfüllung Deiner täglichen Aufgaben und halte Dir den Tod mit Heiltränken vom Leib, wenn Du einmal straucheln solltest!",
+ "dieText": "Du hast ein Level, Dein gesamtes Gold und einen zufälliges Teil Deiner Ausrüstung verloren. Erhebe Dich, Habiticaner, und versuche es erneut! Gebiete diesen schlechten Gewohnheiten Einhalt, sei gewissenhaft bei der Erfüllung Deiner täglichen Aufgaben und halte Dir den Tod mit Heiltränken vom Leib, wenn Du einmal straucheln solltest!",
"sureReset": "Bist Du sicher? Dies wird Deine Klasse und Attributpunkte zurücksetzen (Du erhältst alle Punkte zurück, um sie neu zuzuweisen). Es kostet 3 Edelsteine.",
"purchaseFor": "Für <%= cost %> Edelsteine erwerben?",
"notEnoughMana": "Nicht genug Mana.",
@@ -162,7 +164,9 @@
"con": "Ausdauer",
"per": "Wahrnehmung",
"int": "Intelligenz",
- "showQuickAllocation": "Zeige Statuswertverteilung",
- "hideQuickAllocation": "Verstecke Statuswertverteilung",
- "quickAllocationLevelPopover": "Jedes Level erhältst Du einen Punkt, den Du in ein Attribut Deiner Wahl setzen kannst. Du kannst Deine Punkte manuell verteilen, oder das Spiel entscheiden lassen indem Du eines der vorgegebenen Verteilungsmuster unter Benutzer-> Werte&Erfolge auswählst."
+ "showQuickAllocation": "Statuswertverteilung einblenden",
+ "hideQuickAllocation": "Statuswertverteilung ausblenden",
+ "quickAllocationLevelPopover": "Mit jedem Level erhältst Du einen Punkt, den Du einem Attribut Deiner Wahl zuweisen kannst. Du kannst Deine Punkte manuell verteilen, oder das Spiel entscheiden lassen indem Du eines der vorgegebenen Verteilungsmuster unter Benutzer -> Werte&Erfolge auswählst.",
+ "invalidAttribute": "\"<%= attr %>\" ist keine gültige Eigenschaft.",
+ "notEnoughAttrPoints": "Du hast nicht genügend Eigenschaftspunkte."
}
\ No newline at end of file
diff --git a/common/locales/de/communityguidelines.json b/common/locales/de/communityguidelines.json
index 2f1ad75af8..4cb131549d 100644
--- a/common/locales/de/communityguidelines.json
+++ b/common/locales/de/communityguidelines.json
@@ -3,14 +3,14 @@
"tavernCommunityGuidelinesPlaceholder": "Freundliche Erinnerung: Dieser Chat ist für alle Altersgruppen, also bitte benutze eine angemessene Sprache und poste nur angemessenen Inhalt! Falls Du Fragen hast, sieh bitte in den Community-Richtlinien weiter unten nach.",
"commGuideHeadingWelcome": "Willkommen in Habitica!",
"commGuidePara001": "Sei gegrüßt, Abenteurer! Willkommen in Habitica, dem Land der Produktivität, des gesunden Lebens und dem gelegentlich randalierenden Greif. Wir sind eine fröhliche Gemeinschaft voller hilfreicher Menschen, die sich auf ihrem Weg der persönlichen Entwicklung gegenseitig unterstützen.",
- "commGuidePara002": "Damit hier jeder sicher, glücklich und produktiv sein kann, gibt es ein paar Richtlinien. Wir haben uns große Mühe gegeben, sie möglichst nett und leicht verständlich zu formulieren. Bitte nimm dir die Zeit, sie durchzulesen.",
+ "commGuidePara002": "Damit hier jeder sicher, glücklich und produktiv sein kann, gibt es ein paar Richtlinien. Wir haben uns große Mühe gegeben, sie möglichst nett und leicht verständlich zu formulieren. Bitte nimm Dir die Zeit, sie durchzulesen.",
"commGuidePara003": "Diese Regeln gelten an allen sozialen Orten die wir verwenden, unter anderem (aber nicht nur) bei Trello, GitHub, Transifex und dem Wiki. Manchmal werden unvorhergesehende Situationen auftreten, wie ein neuer Krisenherd oder ein bösartiger Totenbeschwörer. Wenn das passiert, werden die Moderatoren reagieren, indem sie diese Richtlinien überarbeiten, um die Gemeinschaft vor neuen Gefahren zu schützen. Hab keine Angst: Du wirst von Bailey informiert werden, wenn sich die Richtlinien ändern.",
"commGuidePara004": "Zum Mitschreiben, halte Deinen Federkiel und Deine Schriftrolle bereit. Los geht's!",
"commGuideHeadingBeing": "Ein Habiticaner sein",
- "commGuidePara005": "Habitica ist vor allem eine Webseite die sich der persönlichen Weiterentwicklung verschrieben hat. Deshalb haben wir das Glück, dass sich hier eine der wärmsten, freundlichsten, höflichsten und unterstützenden Gemeinschaften im Internet versammelt hat. Es gibt viele Eigenschaften, die Habiticaner auszeichnen. Einige der häufigsten und bemerkenswertesten sind:",
+ "commGuidePara005": "Habitica ist vor allem eine Webseite die sich der persönlichen Weiterentwicklung verschrieben hat. Deshalb haben wir das Glück, dass sich hier eine der wärmsten, freundlichsten, höflichsten und unterstützendsten Gemeinschaften im Internet versammelt hat. Es gibt viele Eigenschaften, die Habiticaner auszeichnen. Einige der häufigsten und bemerkenswertesten sind:",
"commGuideList01A": "Ein hilfsbereiter Geist. Viele Menschen verwenden viel Zeit und Energie darauf, neuen Mitgliedern der Gemeinschaft zu helfen und sie anzuleiten. Zum Beispiel gibt es eine Newbies-Gilde, die sehr gern Fragen von neuen Mitgliedern beantwortet. Sei kein Frosch und hilf mit!",
"commGuideList01B": "Das Verhalten des Fleißigen. Alle Habiticaner arbeiten hart, um ihr Leben zu verbessern und helfen darüber hinaus, die Seite weiter zu verbessern. Da wir ein Open-Source-Projekt sind arbeiten wir alle ständig daran, die Seite bestmöglich zu verbessern.",
- "commGuideList01C": "Unterstützendes Verhalten Habiticaner spenden einander Beifall in siegreichen Zeiten und ermutigen einander in schwierigen Zeiten. Wir leihen uns gegenseitig Stärke, sind füreinander da an und lernen voneinander. In Gruppen unterstützen wir uns mit Zaubersprüchen; in Chaträumen ermuntern wir uns mit freundlichen und unterstützenden Worten.",
+ "commGuideList01C": "Unterstützendes Verhalten. Habiticaner spenden einander Beifall in siegreichen Zeiten und ermutigen einander in schwierigen Zeiten. Wir leihen uns gegenseitig Stärke, sind füreinander da an und lernen voneinander. In Gruppen unterstützen wir uns mit Zaubersprüchen; in Chaträumen ermuntern wir uns mit freundlichen und unterstützenden Worten.",
"commGuideList01D": "Respektvoller Umgang. Wir haben alle unterschiedliche Hintergründe, Fähigkeiten und Meinungen. Das macht unsere Gemeinschaft aus! Habiticaner respektieren diese Unterschiede und feiern sie. Schau öfter vorbei und schon bald wirst Du Freunde mit den unterschiedlichsten Hintergründen kennen lernen.",
"commGuideHeadingMeet": "Triff die Mods!",
"commGuidePara006": "In Habitica haben sich einige unermüdliche Ritter mit den Mitarbeitern zusammengetan, um die Gemeinschaft ruhig, zufrieden und frei von Trollen zu halten. Jeder von ihnen hat einen Spezialbereich und kann manchmal in andere Bereiche berufen werden. Mitarbeiter und Mods werden offizielle Statements oft mit den Worten \"Mod Talk\" oder \"Mod Hat On\" kennzeichnen.",
@@ -19,7 +19,7 @@
"commGuidePara009": "Die derzeitigen Mitarbeiter sind (von links nach rechts):",
"commGuidePara009a": "bei Trello",
"commGuidePara009b": "bei GitHub",
- "commGuidePara010": "Es gibt außerdem mehrere Moderatoren, welche die Mitarbeitern unterstützen. Sie wurden sorgfältig ausgewählt, also behandle sie mit Respekt und höre dir ihre Vorschläge an.",
+ "commGuidePara010": "Es gibt außerdem mehrere Moderatoren, welche die Mitarbeiter unterstützen. Sie wurden sorgfältig ausgewählt, also behandle sie mit Respekt und höre Dir ihre Vorschläge an.",
"commGuidePara011": "Die derzeitigen Moderatoren sind (von links nach rechts):",
"commGuidePara011a": "im Gasthaus-Chat",
"commGuidePara011b": "auf GitHub/im Wiki",
@@ -27,39 +27,39 @@
"commGuidePara011d": "auf GitHub",
"commGuidePara012": "Falls es bei Deinem Kontakt mit einem Moderator zu Problemen gekommen oder Du Bedenken bei einem bestimmten Moderator hegst, sende bitte eine E-Mail an Lemoness (leslie@habitica.com).",
"commGuidePara013": "In einer so großen Gemeinschaft wie Habitica ist es so, dass die Menschen kommen und gehen. So kommt es vor, dass ein Moderator seinen noblen Umhang ablegt, um sich zu entspannen. Diese Nutzer sind emeritierte Moderatoren. Sie handeln nicht mehr mit der Befugnis eines Moderators, aber wir würdigen ihre Arbeit weiterhin!",
- "commGuidePara014": "Eremetierte Moderatoren:",
+ "commGuidePara014": "Emeritierte Moderatoren:",
"commGuideHeadingPublicSpaces": "Öffentliche Orte in Habitica",
- "commGuidePara015": "Habitica hat zwei Sorten sozialer Orte: Öffentliche und private. Öffentliche Orte umfassen das Gasthaus, öffentliche Gilden, GitHub, Trello und die Wiki. Private Orte sind private Gilden, der Gruppenchat und private Nachrichten. Alle Anzeigenamen müssen den Community-Richtlinien für öffentliche Orte entsprechen. Um Deinen Anzeigenamen zu ändern, gehe auf der Webseite zu Benutzer > Profil und klicke auf den \"Bearbeiten\"-Button.",
+ "commGuidePara015": "Habitica hat zwei Sorten sozialer Orte: Öffentliche und private. Öffentliche Orte umfassen das Gasthaus, öffentliche Gilden, GitHub, Trello und das Wiki. Private Orte sind private Gilden, der Gruppenchat und private Nachrichten. Alle Anzeigenamen müssen den Community-Richtlinien für öffentliche Orte entsprechen. Um Deinen Anzeigenamen zu ändern, gehe auf der Webseite zu Benutzer > Profil und klicke auf den \"Bearbeiten\"-Knopf.",
"commGuidePara016": "Wenn Du Dich durch die öffentlichen Orte in Habitica bewegst, gibt es ein paar allgemeine Regeln, damit jeder sicher und glücklich ist. Diese sollten für einen Abenteurer wie Dich einfach sein!",
- "commGuidePara017": "Respektiert einander. Seit höflich, nett und hilfsbereit. Vergesst nicht: Habiticaner haben die verschiedensten Hintergründe und haben sehr unterschiedliche Erfahrungen gemacht. Das macht Habitica so besonders! Eine Gemeinschaft aufzubauen bedeutet, sich gegenseitig zu respektieren und unsere Unterschiede genauso zu feiern wie unsere Gemeinsamkeiten. Hier sind ein paar einfache Möglichkeiten, Respekt zu zeigen:",
+ "commGuidePara017": "Respektiert einander. Seid höflich, nett und hilfsbereit. Vergesst nicht: Habiticaner haben die verschiedensten Hintergründe und haben sehr unterschiedliche Erfahrungen gemacht. Das macht Habitica so besonders! Eine Gemeinschaft aufzubauen bedeutet, sich gegenseitig zu respektieren und unsere Unterschiede genauso zu feiern wie unsere Gemeinsamkeiten. Hier sind ein paar einfache Möglichkeiten, Respekt zu zeigen:",
"commGuideList02A": "Befolge alle allgemeinen Geschäftsbedingungen.",
"commGuideList02B": "Poste bitte keine Bilder und keine Texte, die Gewalt darstellen, andere einschüchtern, oder eindeutige/andeutend sexuell sind, nichts diskriminierendes, fanatisches, rassistisches, sexistisches, keinen Hass und keine Belästigung, sowie nichts was Individuen oder Gruppen schadet. Auch nicht als Scherz. Das bezieht sowohl Sprüche als auch Stellungnahmen mit ein. Nicht jeder hat den gleichen Humor, so könnte etwas, dass Du als Witz wahrnimmst für jemand anderen verletzend sein. Attackiert eure täglichen Aufgaben, nicht einander.",
"commGuideList02C": "Haltet Gespräche für alle Altersgruppen angemessen. Wir haben viele junge Habiticaner, die diese Seite benutzen! Wir wollen sie nicht ihrer Unschuld berauben oder Habiticaner an der Erreichung ihrer Ziele hindern.",
"commGuideList02D": " Vermeide vulgäre Ausdrücke. Dazu gehören auch mildere, religiöse Verwünschungen, die anderweitig akzeptabel gewesen wären. Wir haben Menschen aus allen religiösen und kulturellen Hintergründen und wünschen uns, dass sich alle im öffentlichen Raum wohl fühlen. Verbale Angriffe jeder Art werden strenge Konsequenzen haben, insbesondere auch, da sie unsere Nutzungsbedingungen verletzen.",
"commGuideList02E": " Meidet heftig umstrittene Diskussionen außerhalb der Back Corner. Wenn jemand eurer Meinung nach etwas unhöfliches oder schmerzliches gesagt hat, geht nicht auf ihn ein. Ein einziges, höfliches Kommentar wie \"Dieser Witz war unangebracht\" ist in Ordnung, aber unfreundlich auf Kommentare zu reagieren steigert nur die Anspannung und macht Habitica zu einem negativem Ort. Nettigkeit und Höflichkeit helfen anderen zu verstehen von wo ihr kommt.",
"commGuideList02F": "Befolge unmittelbar jegliche Anliegen der Moderatoren um eine Diskussion zu beenden oder um es zur Back Corner zu verschieben. Letzte Bemerkungen, Abschiedsworte und endgültige Fazite sollten dann abschließend an eurem \"Tisch\" in der Back Corner (höflich) abgegeben werden, falls erlaubt.",
- "commGuideList02G": "Denk erst mal gründlich nach bevor Du wütend reagierst wenn dir jemand sagt, dass etwas was Du getan oder gesagt hast ihm/ihr nicht gefallen hat. Es zeigt große Stärke, sich ehrlich bei jemandem zu entschuldigen. Wenn Du findest, dass die Art, wie er/sie dir geantwortet hat unangemessen war, kontaktiere einen Mod statt ihn/sie öffentlich damit zu konfrontieren.",
- "commGuideList02H": "Heftig umstrittene Konversationen sollten den Moderatoren gemeldet werden. Wenn ihr der Meinung seit, dass eine Diskussion anfängt auszuarten und überaus emotional, oder sogar verletzend wird, verwickelt euch nicht noch weiter in das Gespräch. Schreibt stattdessen eine E-Mail an leslie@habitica.com, um es uns wissen zu lassen. Es ist unsere Aufgabe euch sicher zu halten.",
- "commGuideList02I": "Poste keinen Spam. Spamming umfasst unter anderem: Den gleichen Kommentar oder die gleiche Frage an unterschiedlichen Orten posten, Links ohne Erklärung oder Kontext posten, sinnlose Nachrichten posten, oder viele Nachrichten hintereinander posten. Das Betteln nach Edelsteinen oder einem Abonnement wird ebenfalls als Spamming betrachtet.",
- "commGuidePara019": "An privaten Orten haben Benutzer die Freiheit, alle möglichen Themen zu besprechen, solange diese nicht den AGB widersprechen. Dies umfasst das Posten von diskriminierenden, gewalttätigen oder einschüchternden Inhalten. Beachte dass Wettbewerbsnamen im öffentlichen Profil des Gewinners anzeigt werden, daher müssen ALLE Wettbewerbsnamen den Community-Richtlinien für öffentliche Orte entsprechen, auch wenn sie an privaten Orten genutzt werden.",
+ "commGuideList02G": "Denk zuerst gründlich nach bevor Du wütend reagierst wenn Dir jemand sagt, dass etwas was Du getan oder gesagt hast ihm/ihr nicht gefallen hat. Es zeigt große Stärke, sich ehrlich bei jemandem zu entschuldigen. Wenn Du findest, dass die Art, wie er/sie Dir geantwortet hat unangemessen war, kontaktiere einen Mod statt ihn/sie öffentlich damit zu konfrontieren.",
+ "commGuideList02H": "Heftig umstrittene Konversationen sollten den Moderatoren gemeldet werden. Wenn ihr der Meinung seid, dass eine Diskussion anfängt auszuarten und überaus emotional, oder sogar verletzend wird, verwickelt euch nicht noch weiter in das Gespräch. Schreibt stattdessen eine E-Mail an leslie@habitica.com, um es uns wissen zu lassen. Es ist unsere Aufgabe euch sicher zu halten.",
+ "commGuideList02I": "Poste keinen Spam. Spamming umfasst unter anderem: Den gleichen Kommentar oder die gleiche Frage an unterschiedlichen Orten zu posten, Links ohne Erklärung oder Kontext zu posten, sinnlose Nachrichten zu posten, oder viele Nachrichten hintereinander zu posten. Das Betteln nach Edelsteinen oder einem Abonnement wird ebenfalls als Spamming betrachtet.",
+ "commGuidePara019": "An privaten Orten haben Benutzer die Freiheit, alle möglichen Themen zu besprechen, solange diese nicht den AGB widersprechen. Dies umfasst das Posten von diskriminierenden, gewalttätigen oder einschüchternden Inhalten. Beachte, dass Wettbewerbsnamen im öffentlichen Profil des Gewinners angezeigt werden, daher müssen ALLE Wettbewerbsnamen den Community-Richtlinien für öffentliche Orte entsprechen, auch wenn sie an privaten Orten genutzt werden.",
"commGuidePara020": "Für private Nachrichten (PNs/PMs) gibt es einige zusätzliche Richtlinien. Falls Dich jemand geblockt hat, kontaktiere ihn nicht über andere Wege, um ihn oder sie zu bitten Dich nicht mehr zu blocken. Außerdem solltest Du keine PNs schicken, wenn Du Hilfe mit der Seite, also \"Support\" brauchst (allgemein zugängliche Antworten auf diese Fragen im Gasthaus oder Forum kommen der Gemeinschaft zu gute). Schließlich schicke bitte keine PNs in denen Du um Edelsteine oder ein Abonnement bettelst, dies kann als Spamming betrachtet werden.",
"commGuidePara021": "Manche öffentliche Orte in Habitica haben außerdem noch weitere Regeln.",
"commGuideHeadingTavern": "Das Gasthaus",
- "commGuidePara022": "Das Gasthaus ist der Treffpunkt für Habiticaner, um sich unter die Leute zu mischen. Daniel der Barkeeper hält diesen Ort sauber und Lemoness zaubert dir gerne ein Glas Limonade während Du Dich entspannst und chattest. Und denk dran ...",
- "commGuidePara023": "Die Gespräche sind meist lockere Unterhaltungen oder drehen sich um Produktivität und Life Improvement.",
- "commGuidePara024": "Da der Gasthaus-Chat nur 200 Nachrichten halten kann ist er kein guter Ort für lange Gespräche über bestimmte Themen, besonders sensible Themen (z.B. Politik, Religion, Depression, ob Koboldjagen verboten werden sollte, usw.). Diese Gespräche sollten woanders geführt werden, z.B. in einer passenden Gilde oder in der Back Corner (mehr Information unten).",
+ "commGuidePara022": "Das Gasthaus ist der Treffpunkt für Habiticaner, um sich unter die Leute zu mischen. Daniel der Barkeeper hält diesen Ort sauber und Lemoness zaubert Dir gerne ein Glas Limonade während Du Dich entspannst und chattest. Und denk dran ...",
+ "commGuidePara023": "Die Gespräche sind meist lockere Unterhaltungen oder drehen sich um Produktivität oder Tipps zur Lebensverbesserung.",
+ "commGuidePara024": "Da der Gasthaus-Chat nur 200 Nachrichten halten kann ist er kein guter Ort für lange Gespräche über bestimmte Themen, besonders sensible Themen (z. B. Politik, Religion, Depression, ob Koboldjagen verboten werden sollte, usw.). Diese Gespräche sollten woanders geführt werden, z. B. in einer passenden Gilde oder in der Back Corner (mehr Information unten).",
"commGuidePara027": "Sprecht im Gasthaus nicht über irgendwelche suchterzeugenden Dinge. Viele Menschen benutzen Habitica, um zu versuchen, ihre schlechten Gewohnheiten zu beenden. Zu hören, wie andere über suchterzeugende/illegale Substanzen sprechen, kann das für sie viel schwerer machen! Respektiert die anderen Gasthaus-Gäste und berücksichtige das. Dies gilt u.a. für Rauchen, Alkohol, Pornografie, Glücksspiel und Drogen.",
"commGuideHeadingPublicGuilds": "Öffentliche Gilden",
"commGuidePara029": "Öffentliche Gilden sind ziemlich ähnlich wie das Gasthaus, außer dass die Gespräche dort nicht so allgemein sind, sondern sich um ein bestimmtes Thema drehen. Der öffentliche Gildenchat sollte sich auf dieses Thema konzentrieren. Zum Beispiel könnte es sein, dass Mitglieder der Wordsmith-Gilde genervt sind, wenn sich das Gespräch plötzlich um Gärtnern statt um Schreiben dreht, und eine Drachenliebhaber-Gilde interessiert sich wahrscheinlich nicht dafür, antike Runen zu entziffern. Manche Gilden sind dabei lockerer als andere, aber versuch generell beim Thema zu bleiben!",
- "commGuidePara031": "Manche öffentlichen Gilden werden sensible Themen enthalten, z.B. Depression, Religion, Politik, usw. Das ist ok solang die Gespräche darüber keine der AGB oder der Regeln für öffentliche Orte brechen und solange sie beim Thema bleiben.",
- "commGuidePara033": "Öffentlice Gilden dürfen keinen 18+ Inhalt enthalten. Wenn eine Gilde plant, regelmäßig sensible Themen zu diskutieren, sollte sie dies in ihrem Gildentitel erwähnen. Dies hat das Ziel, Habitica sicher und angenehm für jeden zu gestalten. Wenn besagte Gilde verschiedene sensible Themen bespricht, ist es den anderen Habiticanern gegenüber respektvoll, eine Warnung vor den Kommentar zu schreiben (z.B. \"Achtung: Spricht das Thema Selbstverletzung an\"). Außerdem sollte besagte sensible Sache dem Thema entsprechend sein -- Selbstverletzung in einer Gilde, die sich auf den Kampf gegen Depressionen fokussiert hat, zu erwähnen, mag Sinn machen, aber scheint weniger geeignet für eine Musikgilde. Wenn Du jemanden siehst, der sogar nach mehreren Hinweisen wiederholt gegen die Richtlinien verstößt, schicke eine Mail mit Screenshots an leslie@habitica.com.",
+ "commGuidePara031": "Manche öffentlichen Gilden werden sensible Themen enthalten, z. B. Depression, Religion, Politik, usw. Das ist ok solange die Gespräche darüber keine der AGB oder der Regeln für öffentliche Orte brechen und solange sie beim Thema bleiben.",
+ "commGuidePara033": "Öffentlice Gilden dürfen keinen 18+ Inhalt enthalten. Wenn eine Gilde plant, regelmäßig sensible Themen zu diskutieren, sollte sie dies in ihrem Gildentitel erwähnen. Dies hat das Ziel, Habitica sicher und angenehm für jeden zu gestalten. Wenn besagte Gilde verschiedene sensible Themen bespricht, ist es den anderen Habiticanern gegenüber respektvoll, eine Warnung vor den Kommentar zu schreiben (z. B. \"Achtung: Spricht das Thema Selbstverletzung an\"). Außerdem sollte besagte sensible Sache dem Thema entsprechend sein -- Selbstverletzung in einer Gilde, die sich auf den Kampf gegen Depressionen fokussiert hat, zu erwähnen, mag Sinn machen, aber scheint weniger geeignet für eine Musikgilde. Wenn Du jemanden siehst, der sogar nach mehreren Hinweisen wiederholt gegen die Richtlinien verstößt, schicke eine Mail mit Screenshots an leslie@habitica.com.",
"commGuidePara035": "Es sollte niemals eine Gilde, egal ob öffentlich oder privat, gegründet werden, die als Ziel hat, ein Individuum oder eine Gruppe anzugreifen. So eine Gilde zu erstellen führt zu einer sofortigen Accountsperre. Bekämpfe schlechte Angewohnheiten, nicht Deine Mitabenteurer!",
"commGuidePara037": "Alle Gasthaus-Wettbewerbe und Wettbewerbe öffentlicher Gilden müssen sich ebenfalls an diese Regeln halten.",
"commGuideHeadingBackCorner": "Die Back Corner",
"commGuidePara038": "Manchmal werden Gespräche zu lang, off-topic, oder sensibel, um in einem öffentlichen Ort fortgeführt zu werden, ohne dass sich Nutzer unwohl fühlen. In diesem Fall wird das Gespräch in die Back Corner Gilde geschickt. Es ist wichtig, zu wissen, dass es überhaupt keine Strafe ist, in die Back Corner Gilde geschickt zu werden!. Viele Habiticaner hängen dort gerne herum und besprechen Themen in aller Ausführlichkeit.",
"commGuidePara039": "Die Back Corner Gilde ist ein freier öffentlicher Ort, in dem man sensible Themen oder lange Gespräche führen kann und der sorgfältig moderiert wird. Die Regeln für öffentliche Orte gelten auch hier, genau wie die AGB. Nur weil wir lange Mäntel tragen und uns in einer Ecke treffen heißt das nicht, dass alles erlaubt ist! Könntest Du mir mal diese glimmende Kerze herüberreichen?",
"commGuideHeadingTrello": "Trello Boards",
- "commGuidePara040": "Trello dient als offenes Forum für Vorschläge und Diskussionen von Seiten-Features. Habitica wird durch Leuten in Form von tapferen Mitwirkenden regiert -- wir alle bauen die Seite zusammen. Trello ist das System, das die Techniken für unseren Wahnsinn anbietet. In Rücksicht darauf, versuche Dein Bestes, um alle Deine Gedanken in ein Kommentar einzugrenzen, anstelle mehrmals in Folge auf die gleiche Karte zu kommentieren. Wenn dir etwas neues einfällt, kannst Du gerne Deinen ursprünglichen Kommentar umändern. Bitte habt Erbarmen für diejenigen von uns, die eine Benachrichtigung nach jedem neuen Kommentar bekommen. Unser Posteingang kann nur soviel aushalten.",
+ "commGuidePara040": "Trello dient als offenes Forum für Vorschläge und Diskussionen von Seiten-Features. Habitica wird durch Leuten in Form von tapferen Mitwirkenden regiert -- wir alle bauen die Seite zusammen. Trello ist das System, das die Techniken für unseren Wahnsinn anbietet. In Rücksicht darauf, versuche Dein Bestes, um alle Deine Gedanken in ein Kommentar einzugrenzen, anstelle mehrmals in Folge auf die gleiche Karte zu kommentieren. Wenn Dir etwas neues einfällt, kannst Du gerne Deinen ursprünglichen Kommentar umändern. Bitte habt Erbarmen für diejenigen von uns, die eine Benachrichtigung nach jedem neuen Kommentar bekommen. Unser Posteingang kann nur soviel aushalten.",
"commGuidePara041": "Habitica verwendet fünf verschiedene Trello boards:",
"commGuideList03A": "Das Main Board ist ein Ort, um neue Features vorzuschlagen und darüber abzustimmen.",
"commGuideList03B": "Das Mobile Board ist ein Ort, um neue Features für die Handy-App vorzuschlagen und darüber abzustimmen.",
@@ -120,7 +120,7 @@
"commGuideHeadingSevereConsequences": "Beispiele für schwere Konsequenzen",
"commGuideList09A": "Accountsperren",
"commGuideList09B": "Accountlöschungen",
- "commGuideList09C": "Der Aufstieg in höhere Mitwirkendenstufen kann dauerhaft verwehrt (\"eingefrohren\") werden",
+ "commGuideList09C": "Der Aufstieg in höhere Mitwirkendenstufen kann dauerhaft verwehrt (\"eingefroren\") werden",
"commGuideHeadingModerateConsequences": "Beispiele für mittlere Konsequenzen",
"commGuideList10A": "Beschränkte Berechtigung zum öffentlichen Chatten",
"commGuideList10B": "Beschränkte Bereichtigung zum privaten Chatten",
@@ -150,15 +150,15 @@
"commGuidePara065": "Moderatoren werden, von Mitarbeitern und schon existierenden Moderatoren aus den Mitwirkenden mit siebtem Rang ausgewählt. Auch wenn Mitwirkende mit siebtem Rang hart im Interesse der Seite gearbeitet haben, haben nicht alle die Autorität eines Moderators.",
"commGuidePara066": "Es gibt ein paar wichtige Hinweise zu den Stufen für Mitwirkende:",
"commGuideList13A": "Ränge sind Ermessenssache. Sie werden nach ermessen der Moderatoren, auf Grund verschiedener Faktoren, wie zum Beispiel unserer Wahrnehmung der Arbeit die Du für die tust und den Wert dieser für die Gemeinschaft. Wir behalten uns das Recht vor sie Speziallevel, Titel und Belohnungen nach unserem Ermessen zu ändern.",
- "commGuideList13B": "Levels werden schwieriger je weiter Du voranschreitest. Wenn Du ein Monster geschaffen hast, oder einen kleinen Bug behoben hast, hast Du zwar genug um dir den ersten Mitwirkenden-Level zu verdienen, aber noch nicht genug um dir den Nächsten zu holen. So wie in jedem RPG, kommen mit steigendem Level auch steigende Herausforderungen!",
+ "commGuideList13B": "Levels werden schwieriger je weiter Du voranschreitest. Wenn Du ein Monster geschaffen hast, oder einen kleinen Bug behoben hast, hast Du zwar genug um Dir den ersten Mitwirkenden-Level zu verdienen, aber noch nicht genug um Dir den Nächsten zu holen. So wie in jedem RPG, kommen mit steigendem Level auch steigende Herausforderungen!",
"commGuideList13C": " Levels fangen nicht einfach \"von Neu an\". Beim Festlegen der Schwierigkeit, schauen wir auf alle Deine Beiträge, sodass Leute, die ein bisschen Pixel Art machen, dann einen kleinen Bug beheben, dann noch mit der Wiki plätschern nicht weiter voranschreiten wie Leute, die hart an einer Aufgabe arbeiten. Damit bleibt alles fair!",
"commGuideList13D": "Nutzer, die auf Bewährung sind können nicht zum nächsten Rang aufsteigen. Sofern Verstöße vorliegen, haben Moderatoren das Recht das Aufsteigen eines Nutzers einzuschränken. Sollte dieser Fall eintreten, wird der Benutzer immer über diese Entscheidung informiert werden und auch darüber, mit welchen Schritten er sich bewähren kann. Durch Verstöße und Bewährung können Ränge auch entzogen werden.",
"commGuideHeadingFinal": "Der Letzte Absatz",
- "commGuidePara067": "Hier habt Ihr sie, tapfere Habiticaner -- die Community-Richtlinien! Wischt euch den Schweiß aus dem Gesicht und gebt euch einige Erfahrungspunkte fürs Durchlesen. Wenn ihr irgendwelche Fragen oder Anliegen bezüglich der Community-Richtlinien hast, schreibt Lemoness (leslie@habitica.com) eine Email. Sie hilft euch gerne euer Anliegen zu klären.",
+ "commGuidePara067": "Hier habt Ihr sie, tapfere Habiticaner -- die Community-Richtlinien! Wischt euch den Schweiß aus dem Gesicht und gebt euch einige Erfahrungspunkte fürs Durchlesen. Wenn ihr irgendwelche Fragen oder Anliegen bezüglich der Community-Richtlinien hast, schreibt Lemoness (leslie@habitica.com) eine E-Mail. Sie hilft euch gerne euer Anliegen zu klären.",
"commGuidePara068": "Nun voran, mutiger Abendteurer und besiege einige täglichen Aufgaben!",
"commGuideHeadingLinks": "Nützliche Links",
"commGuidePara069": "Die folgenden talentierten Künstler haben bei diesen Illustrationen mitgewirkt:",
- "commGuideLink01": "Die Newbies Gilde",
+ "commGuideLink01": "Die Newbies-Gilde",
"commGuideLink01description": "eine Gilde für neue Nutzer, um Fragen zu stellen!",
"commGuideLink02": "Die Back Corner Gilde",
"commGuideLink02description": "eine Gilde für das Diskutieren von langen oder sensiblen Themen",
diff --git a/common/locales/de/content.json b/common/locales/de/content.json
index 54611e45f9..f8642776b6 100644
--- a/common/locales/de/content.json
+++ b/common/locales/de/content.json
@@ -3,7 +3,7 @@
"potionNotes": "Heilt um 15 Lebenspunkte (wird sofort angewendet)",
"armoireText": "Verzauberter Schrank",
"armoireNotesFull": "Öffne den Schrank um zufällig spezielle Gegenstände, Erfahrung oder Nahrung zu erhalten! Verbleibende Ausrüstungsgegenstände:",
- "armoireLastItem": "Du hast das letzte Stück der seltenen Ausrüstung im verzauberten Schrank gefunden.",
+ "armoireLastItem": "Du hast das letzte Stück seltener Ausrüstung im verzauberten Schrank gefunden.",
"armoireNotesEmpty": "Im verzauberten Schrank gibt es jeweils in der ersten Woche eines Monats neue Ausrüstung. Bis dahin, klicke weiter für Erfahrung und Essen.",
"dropEggWolfText": "Wolf",
"dropEggWolfMountText": "Wolf",
@@ -77,8 +77,8 @@
"questEggBunnyText": "Hase",
"questEggBunnyMountText": "Hase",
"questEggBunnyAdjective": "ein knudeliger",
- "questEggSlimeText": "Marshmallow Schleim",
- "questEggSlimeMountText": "Marshmallow Schleim",
+ "questEggSlimeText": "Marshmallow-Schleim",
+ "questEggSlimeMountText": "Marshmallow-Schleim",
"questEggSlimeAdjective": "ein süßer",
"questEggSheepText": "Schafbock",
"questEggSheepMountText": "Schafbock",
@@ -101,8 +101,8 @@
"questEggSnakeText": "Taipan",
"questEggSnakeMountText": "Taipan",
"questEggSnakeAdjective": "ein schlängelnder",
- "questEggUnicornText": "Einhorn-Hengst",
- "questEggUnicornMountText": "Geflügelter Einhorn-Hengst",
+ "questEggUnicornText": "Einhorn-Pegasus",
+ "questEggUnicornMountText": "Geflügelter Einhorn-Pegasus",
"questEggUnicornAdjective": "ein magischer",
"questEggSabretoothText": "Säbelzahntiger",
"questEggSabretoothMountText": "Säbelzahntiger",
@@ -110,9 +110,15 @@
"questEggMonkeyText": "Affe",
"questEggMonkeyMountText": "Affe",
"questEggMonkeyAdjective": "ein schelmischer",
- "questEggSnailText": "Schneckerich",
- "questEggSnailMountText": "Schneckerich",
+ "questEggSnailText": "Schnegel",
+ "questEggSnailMountText": "Schnegel",
"questEggSnailAdjective": "ein langsamer, aber beständiger",
+ "questEggFalconText": "Falke",
+ "questEggFalconMountText": "Falke",
+ "questEggFalconAdjective": "ein flinker",
+ "questEggTreelingText": "Bäumlein",
+ "questEggTreelingMountText": "Bäumlein",
+ "questEggTreelingAdjective": "ein blättriger",
"eggNotes": "Finde einen Schlüpftrank, den Du über dieses Ei gießen kannst, damit <%= eggAdjective(locale) %> <%= eggText(locale) %> schlüpfen kann.",
"hatchingPotionBase": "Normaler",
"hatchingPotionWhite": "Weißer",
@@ -126,6 +132,7 @@
"hatchingPotionGolden": "Goldener",
"hatchingPotionSpooky": "unheimlicher",
"hatchingPotionPeppermint": "Pfefferminz",
+ "hatchingPotionFloral": "Geblümt",
"hatchingPotionNotes": "Gieße das über ein Ei und es wird ein <%= potText(locale) %> Haustier daraus schlüpfen.",
"premiumPotionAddlNotes": "Nicht auf Eier von Quest-Haustieren anwendbar.",
"foodMeat": "Fleisch",
diff --git a/common/locales/de/contrib.json b/common/locales/de/contrib.json
index 066c00a1d7..474aa825af 100644
--- a/common/locales/de/contrib.json
+++ b/common/locales/de/contrib.json
@@ -1,64 +1,68 @@
{
"friend": "Freund",
- "friendFirst": "Wenn \nDein erster Beitrag angenommen wird, erhältst Du ein Habitica Mitwirkende(r) Abzeichen. Im Gasthaus wird Dein Name, dafür dass Du ein Mitwirkender bist, stolz angezeigt. Als Belohnung für Deine Bemühungen erhältst Du außerdem 3 Edelsteine.",
+ "friendFirst": "Wenn Dein erster Beitrag angenommen wird, erhältst Du ein Habitica-Mitwirkende(r)-Abzeichen. Im Gasthaus wird Dein Name, dafür dass Du ein Mitwirkender bist, stolz angezeigt. Als Belohnung für Deine Bemühungen erhältst Du außerdem 3 Edelsteine.",
"friendSecond": "Wenn das zweite Bündel Deiner Beiträge angenommen wurde, kannst Du die Kristallrüstung im Belohnungs-Shop kaufen. Als Belohnung Deiner fortwährenden Arbeit erhältst Du außerdem 3 Edelsteine.",
"elite": "Elite",
"eliteThird": "Wenn das dritte Bündel Deiner Beiträge angenommen wurde, kannst Du den Kristallhelm im Belohnungs-Shop kaufen. Als Belohnung Deiner fortwährenden Arbeit erhältst Du außerdem 3 Edelsteine.",
"eliteFourth": "Wenn das vierte Bündel Deiner Beiträge angenommen wurde, kannst Du das Kristallschwert im Belohnungs-Shop kaufen. Als Belohnung Deiner fortwährenden Arbeit erhältst Du außerdem 4 Edelsteine.",
"champion": "Champion",
"championFifth": "Wenn das fünfte Bündel Deiner Beiträge angenommen wurde, kannst Du den Kristallschild im Belohnungs-Shop kaufen. Als Belohnung Deiner fortwährenden Arbeit erhältst Du außerdem 4 Edelsteine.",
- "championSixth": "Wenn das sechste Bündel Deiner Beiträge angenommen wurde, erhältst Du ein Hydra Haustier. Außerdem erhältst Du 4 Edelsteine.",
+ "championSixth": "Wenn das sechste Bündel Deiner Beiträge angenommen wurde, erhältst Du ein Hydra-Haustier. Außerdem erhältst Du 4 Edelsteine.",
"legendary": "Legendär",
"legSeventh": "Wenn Dein siebtes Bündel Deiner Beiträge angenommen wurde, erhältst Du 4 Edelsteine und wirst ein Mitglied der ehrenhaften Mitwirkenden-Gilde. Du wirst in geheime \"Behind-the-Scenes-Details\" von Habitica eingeweiht! Weitere Beiträge werden Deinen Rang nicht erhöhen, allerdings wirst Du vielleicht Edelstein-Belohnungen und Ehrentitel erringen.",
"moderator": "Moderator",
"guardian": "Wächter",
- "guardianText": "Moderatoren werden sorgfältig aus den höheren Mitwirkenden Rängen ausgesucht, also bitte zeige ihnen gegenüber Respekt und höre auf ihre Vorschläge.",
+ "guardianText": "Moderatoren werden sorgfältig aus den höheren Mitwirkenden-Rängen ausgesucht, also bitte zeige ihnen gegenüber Respekt und höre auf ihre Vorschläge.",
"staff": "Mitarbeiter",
"heroic": "Heroisch",
- "heroicText": "Den Helden Rang tragen Habitica-Mitarbeiter und Mitwirkende auf Mitarbeiter-Level. Hast Du diesen Titel errungen, dann wurdest Du dazu berufen (oder von uns angestellt!).",
+ "heroicText": "Den Helden-Rang tragen Habitica-Mitarbeiter und Mitwirkende auf Mitarbeiter-Level. Hast Du diesen Titel errungen, dann wurdest Du dazu berufen (oder von uns angestellt!).",
"npcText": "NPCs haben Habitica über Kickstarter mitfinanziert. Du kannst ihre Avatare sehen, wie sie über die Features des Spiels wachen.",
"modalContribAchievement": "Erfolg als Mitwirkender!",
"contribModal": "<%= name %>, Du bist fantastisch! Du hast den Rang <%= level %> als Mitwirkender errungen, weil Du bei Habitica mithilfst. Sieh",
"contribLink": "welche Preise hast Du für Deinen Beitrag verdient!",
"contribName": "Mitwirkender",
- "contribText": "Hat zu Habitica beigetragen (Code, Design, Pixelkunst, Rechtsrat, Dokumentationen, etc.). Willst Du dieses Abzeichen auch haben?",
+ "contribText": "Hat zu Habitica beigetragen (Code, Design, Pixelkunst, Rechtsrat, Dokumentationen, usw.). Willst Du dieses Abzeichen auch haben?",
"readMore": "Lies mehr",
"kickstartName": "Kickstarter Träger - Level $<%= tier %>",
- "kickstartText": "Hat das Kickstarter Project mitfinanziert",
+ "kickstartText": "Hat das Kickstarter-Projekt mitfinanziert",
"helped": "Hat Habitica geholfen zu wachsen",
"helpedText1": "Hat Habitica geholfen zu wachsen durch Teilnahme an",
"helpedText2": "dieser Befragung.",
"hall": "Halle der Helden",
- "contribTitle": "Mitwirkender Titel (z.B., \"Schmied\")",
- "contribLevel": "Mitwirkender Level",
+ "contribTitle": "Mitwirkenden-Titel (z. B. \"Schmied\")",
+ "contribLevel": "Mitwirkender-Level",
"contribHallText": "1-7 für normale Mitwirkende, 8 für Moderatoren, 9 für Mitarbeiter. Diese Stufe entscheidet, welche Gegenstände, Haustiere und Reittiere verfügbar sind und welche Farbe das Namensschild hat. Stufe 8 und 9 erhalten automatisch den Administratorstatus.",
"hallContributors": "Halle der Mitwirkenden",
"hallPatrons": "Halle der Schirmherren",
"rewardUser": "Spieler belohnen",
- "UUID": "UUID",
+ "UUID": "Benutzer-ID",
"loadUser": "Spieler laden",
+ "noAdminAccess": "Du hast keine Administratorrechte.",
+ "pageMustBeNumber": "req.query.page muss eine Zahl sein",
+ "userNotFound": "Benutzer nicht gefunden.",
+ "invalidUUID": "UUID muss gültig sein",
"title": "Titel",
"moreDetails": "Mehr Details (1-7)",
"moreDetails2": "mehr Details (8-9)",
"contributions": "Beiträge",
"admin": "Verwalter",
- "notGems": "ist in US-Dollar nicht in Edelsteinen. Ist diese Zahl zum Beispiel 1, bedeutet das 4 Edelsteine. Benutze diese Option nur, wenn Du Spielern von Hand Edelsteine verleihen willst, nicht wenn Du Mitwirkenden Ränge vergibst. Beim Vergeben der Ränge werden dem Spieler automatisch Edelsteine hinzugefügt.",
+ "notGems": "ist in US-Dollar nicht in Edelsteinen. Ist diese Zahl zum Beispiel 1, bedeutet das 4 Edelsteine. Benutze diese Option nur, wenn Du Spielern von Hand Edelsteine verleihen willst, nicht wenn Du Mitwirkenden-Ränge vergibst. Beim Vergeben der Ränge werden dem Spieler automatisch Edelsteine hinzugefügt.",
"gamemaster": "Spielleiter (Mitarbeiter/Moderator)",
- "backerTier": "Träger Stufe",
+ "backerTier": "Trägerstufe",
"balance": "Saldo",
"tierPop": "Klicke die Abzeichen für Details.",
- "playerTiers": "Spieler Stufen",
+ "playerTiers": "Spielerstufen",
"tier": "Level",
"visitHeroes": "Besuche die Halle der Helden (Mitwirkende und Träger)",
"conLearn": "Erfahre mehr über Belohnungen für Mitwirkende",
"conLearnHow": "Erfahre mehr, wie Du bei Habitica mitwirken kannst.",
- "surveysSingle": "Hat Habitica durch die Teilnahme an einer Umfrage geholfen zu wachsen. Es gibt keine aktuellen Umfragen.",
- "surveysMultiple": "Hat Habitica durch Teilnahme an <%= surveys %> Umfragen geholfen zu wachsen. Es gibt keine aktiven Umfragen.",
+ "surveysSingle": "Helped Habitica grow, either by filling out a survey or helping with a major testing effort. Thank you!",
+ "surveysMultiple": "Helped Habitica grow on <%= surveys %> occasions, either by filling out a survey or helping with a major testing effort. Thank you!",
"currentSurvey": "Aktuelle Umfrage",
"surveyWhen": "Das Abzeichen wird gegen Ende März verliehen, wenn alle Umfragen bearbeitet wurden.",
"blurbInbox": "Hier werden Deine privaten Nachrichten (PN) gespeichert! Du kannst jemandem eine Nachricht schreiben, indem Du auf den Umschlag neben seinem Namen im Gasthaus-, Gruppen- oder Gildenchat klickst. Falls Du eine unangemessene PN erhältst, schicke eine E-Mail mit einem Screenshot davon an Lemoness\n(leslie@habitica.com)",
"blurbGuildsPage": "Gilden sind Chatgruppen mit einem gemeinsamen Interesse. Sie sind von Spielern für Spieler erstellt worden. Durchblättere die Liste und tritt den Gilden bei, die Dich interessieren.",
"blurbChallenges": "Wettbewerbe werden von anderen Spielern erstellt. Wenn Du einem Wettbewerb beitrittst, werden seine Aufgaben Deinem Aufgabenmenü hinzugefügt, und wenn Du einen Wettbewerb gewinnst erhältst Du einen Erfolg und oft auch einen Edelstein-Preis!",
- "blurbHallPatrons": "Dies ist die Halle der Unterstützer, in der wir die edlen Abenteurer ehren, die Habitica's ursprüngliche Kickstarter-Kampagne unterstützt haben. Wir danken ihnen für die Hilfe Habitica zum Leben zu erwecken!",
- "blurbHallContributors": "Dies ist die Halle der Helden, in der Open-Source Unterstützter von Habitica geehrt werden. Durch Code, Kunst, Musik, schreiben, oder auch nur Hilfsbereitschaft haben sie Edelsteine, exklusive Ausstattung verdient und angesehene Titel erlangt. Auch Du kannst Habitica unterstützen! Hier erfährst Du mehr dazu."
+ "blurbHallPatrons": "Dies ist die Halle der Schirmherren, in der wir die edlen Abenteurer ehren, die Habiticas ursprüngliche Kickstarter-Kampagne unterstützt haben. Wir danken ihnen für die Hilfe Habitica zum Leben zu erwecken!",
+ "blurbHallContributors": "Dies ist die Halle der Mitwirkenden, in der Open-Source-Unterstützer von Habitica geehrt werden. Durch Code, Kunst, Musik, Schreiben, oder auch nur Hilfsbereitschaft haben sie Edelsteine, exklusive Ausrüstung und angesehene Titel verdient. Auch Du kannst Habitica unterstützen! Hier erfährst Du mehr dazu."
}
\ No newline at end of file
diff --git a/common/locales/de/death.json b/common/locales/de/death.json
index 0af326aa0d..8a46a7bdf0 100644
--- a/common/locales/de/death.json
+++ b/common/locales/de/death.json
@@ -10,7 +10,8 @@
"lowHealthTips1": "Steige ein Level auf um Dich vollständig zu heilen!",
"lowHealthTips2": "Kaufe Dir einen Heiltrank aus der Belohnungsspalte um 15 Lebenspunkte wiederherzustellen.",
"losingHealthQuickly": "Verlierst Du Deine Lebenspunkte zu schnell?",
- "lowHealthTips3": "Unnvollständige Tägliche Aufgaben schaden Dir über Nacht, also sei vorsichtig und füge anfangs nicht zu viele hinzu!",
- "lowHealthTips4": "Wenn eine Tägliche Aufgabe an einem gewissen Tag nicht erledigt werden muss, kannst Du sie deaktivieren, indem Du auf das Bleistiftsymbol klickst.",
- "goodLuck": "Viel Glück!"
+ "lowHealthTips3": "Unnvollständige tägliche Aufgaben schaden Dir über Nacht, also sei vorsichtig und füge anfangs nicht zu viele hinzu!",
+ "lowHealthTips4": "Wenn eine tägliche Aufgabe an einem gewissen Tag nicht erledigt werden muss, kannst Du sie deaktivieren, indem Du auf das Bleistiftsymbol klickst.",
+ "goodLuck": "Viel Glück!",
+ "cannotRevive": "Wiederbelebung ist nicht möglich, wenn Du nicht tot bist."
}
\ No newline at end of file
diff --git a/common/locales/de/defaulttasks.json b/common/locales/de/defaulttasks.json
index 777fbfc454..0e18d73944 100644
--- a/common/locales/de/defaulttasks.json
+++ b/common/locales/de/defaulttasks.json
@@ -5,7 +5,7 @@
"defaultHabit2Notes": "Beispiel für schlechte Gewohnheiten: - Rauchen - Dinge aufschieben",
"defaultHabit3Text": "Treppe/Aufzug nehmen (Klicke den Bleistift zum Bearbeiten)",
"defaultHabit3Notes": "Beispiel für gute oder schlechte Gewohnheiten: +/- Treppen/Aufzug benutzt ; +/- Wasser/Limonade getrunken",
- "defaultTodoNotes": "Du kannst diese einmalige Aufgabe entweder abhaken, sie bearbeiten, oder löschen.",
+ "defaultTodoNotes": "Du kannst dieses To-Do entweder abhaken, es bearbeiten, oder löschen.",
"defaultTodo1Text": "Habitica beitreten (Hake mich ab!)",
"defaultReward1Text": "15 Minuten Pause",
"defaultReward1Notes": "Individuelle Belohnungen können viele Formen annehmen. Manche Leute verzichten auf ihre Lieblingsserien bis sie genügend Gold gesammelt haben um sie sich leisten zu können.",
diff --git a/common/locales/de/faq.json b/common/locales/de/faq.json
index b8e34051cc..7666173417 100644
--- a/common/locales/de/faq.json
+++ b/common/locales/de/faq.json
@@ -1,44 +1,44 @@
{
"frequentlyAskedQuestions": "Häufig gestellte Fragen",
"faqQuestion0": "Ich bin verwirrt. Wo bekomme ich einen Überblick?",
- "iosFaqAnswer0": "Als erstes erstellt Du Aufgaben, die Du im täglichen Leben erledigen möchtest. Sobald Du die Aufgaben im Alltag erledigt hast, hakst Du sie ab und erhältst Erfahrung und Gold. Gold wird benutzt um Ausrüstungen und Gegenstände zu kaufen, sowie selbst erstellte Belohnungen. Erfahrung lässt Deinen Charakter im Level aufsteigen und schaltet Inhalte wie Haustiere, Fähigkeiten und Quests frei! Du kannst Deinen Charakter im Menü > Avatar anpassen gestalten. \n\nEinige grundsätzliche Wege zu kommunizieren: klicke das (+) in der oberen rechten Ecke um eine neue Aufgabe hinzuzufügen. Klicke auf eine existierende Aufgabe um sie zu bearbeiten und streiche darüber um sie zu löschen. Du kannst auch Aufgaben sortieren, indem Du die Schilder in der oberen linken Ecke verwendest, sowie das Erweitern und Verkürzen der Checklisten, indem Du auf die Checklisten-Sprechblase klickst.",
- "webFaqAnswer0": "Als erstes erstellst Du Aufgaben, die Du im täglichen Leben erledigen möchtest. Sobald Du die Aufgaben im Alltag erledigt hast, hakst Du sie ab und erhältst Erfahrung und Gold. Gold wird benutzt um Ausrüstungen und Gegenstände zu kaufen, sowie selbst erstellte Belohnungen. Erfahrung lässt Deinen Charakter im Level aufsteigen und schaltet Inhalte wie Haustiere, Fähigkeiten und Quests frei! Schau dir die Schritt-für-Schritt Übersicht des Spiels für mehr Infos an [Hilfe -> Übersicht für neue Nutzer](https://habitica.com/static/overview).",
+ "iosFaqAnswer0": "Als erstes erstellst Du Aufgaben, die Du im täglichen Leben erledigen möchtest. Sobald Du die Aufgaben im Alltag erledigt hast, hakst Du sie ab und erhältst Erfahrung und Gold. Gold wird benutzt um Ausrüstung und Gegenstände zu kaufen, sowie selbst erstellte Belohnungen. Erfahrung lässt Deinen Charakter im Level aufsteigen und schaltet Inhalte wie Haustiere, Fähigkeiten und Quests frei! Du kannst Deinen Charakter im Menü > Avatar anpassen gestalten. \n\nEinige grundsätzliche Wege zu kommunizieren: klicke das (+) in der oberen rechten Ecke um eine neue Aufgabe hinzuzufügen. Klicke auf eine existierende Aufgabe um sie zu bearbeiten und streiche darüber um sie zu löschen. Du kannst auch Aufgaben sortieren, indem Du die Schilder in der oberen linken Ecke verwendest, sowie das Erweitern und Verkürzen der Checklisten, indem Du auf die Checklisten-Sprechblase klickst.",
+ "webFaqAnswer0": "Als erstes erstellst Du Aufgaben, die Du im täglichen Leben erledigen möchtest. Sobald Du die Aufgaben im Alltag erledigt hast, hakst Du sie ab und erhältst Erfahrung und Gold. Gold wird benutzt um Ausrüstung und Gegenstände zu kaufen, sowie selbst erstellte Belohnungen. Erfahrung lässt Deinen Charakter im Level aufsteigen und schaltet Inhalte wie Haustiere, Fähigkeiten und Quests frei! Schau Dir die Schritt-für-Schritt Übersicht des Spiels für mehr Infos an [Hilfe -> Übersicht für neue Nutzer](https://habitica.com/static/overview).",
"faqQuestion1": "Wie erstelle ich meine Aufgaben?",
- "iosFaqAnswer1": "Gute Gewohnheiten (die mit einem +) sind Aufgaben, die Du mehrmals am Tag wiederholen kannst, zum Beispiel Gemüse essen. Schlechte Angewohnheiten (die mit einem -) sind Aufgaben, die Du vermeiden solltest, zum Beispiel Fingernägel kauen. Gewohnheiten mit einem + und einem - haben eine gute und eine schlechte Seite, wie die Treppe zu nehmen vs. den Aufzug zu nehmen. Gute Gewohnheiten bringen Erfahrung und Gold. Schlechte Gewohnheiten ziehen Gesundheit ab.\n\nTägliche Aufgaben sind Aufgaben, die Du jeden Tag machen musst, zum Beispiel Zähne zu putzen oder Deine E-Mails abrufen. Du kannst die Tage, an denen eine tägliche Aufgabe fällig ist, anpassen, indem Du bearbeiten klickst. Wenn Du eine tägliche Aufgabe, die fällig ist, auslässt, wird Deinem Charakter über Nacht Schaden zugefügt. Sei vorsichtig und füge nicht zu viele tägliche Aufgaben auf einmal hinzu. \n\nTo-Dos ist Deine Aufgabenliste. Ein To-Do zu erledigen, bringt dir Gold und Erfahrung. Du verlierst niemals Gesundheit bei To-Dos. Du kannst ein Ablaufdatum bei jedem To-Do hinzufügen, indem Du bearbeiten klickst.",
- "webFaqAnswer1": "Gute Gewohnheiten (die, die ein haben) sind Aufgaben, die Du mehrmals am Tag wiederholen kannst, wie Gemüse essen. Schlechte Angewohnheiten (die, die ein haben) sind Aufgaben, die man vermeiden sollte, wie Fingernägel kauen. Gewohnheiten mit einem und einem haben eine gute und eine schlechte Seite, wie die Treppe zu nehmen vs den Aufzug zu nehmen. Gute Gewohnheiten gewähren Erfahrung und Gold. Schlechte Gewohnheiten ziehen Gesundheit ab.\n
\nTägliche Aufgaben müssen jeden Tag erledigt werden, wie Zähne putzen oder E-Mails abrufen. Du kannst die Tage an denen eine tögliche Aufgabe fällig ist, anpassen, indem Du \"Bearbeiten\" klickst. Wenn Du eine tägliche Aufgabe auslässt, die fällig ist, wird Deinem Charakter über Nacht Schaden zugefügt. Sei also vorsichtig und füge nicht zu viele tägliche Aufgaben auf einmal hinzu. \n
To-Dos ist Deine Aufgabenliste. Ein To-Do zu erledigen, bringt Dir Gold und Erfahrung. Du verlierst niemals Gesundheit bei To-Dos. Du kannst ein Ablaufdatum bei jedem To-Do hinzufügen, indem Du \"Bearbeiten\" klickst.",
+ "iosFaqAnswer1": "Gute Gewohnheiten (die mit einem +) sind Aufgaben, die Du mehrmals am Tag wiederholen kannst, wie zum Beispiel Gemüse essen. Schlechte Angewohnheiten (die mit einem -) sind Aufgaben, die Du vermeiden solltest, wie zum Beispiel Fingernägel kauen. Gewohnheiten mit einem + und einem - haben eine gute und eine schlechte Seite, wie die Treppe zu nehmen bzw. den Aufzug zu nehmen. Gute Gewohnheiten werden mit Erfahrung und Gold belohnt. Schlechte Angewohnheiten ziehen Gesundheit ab.\n\nTägliche Aufgaben sind Aufgaben, die Du jeden Tag machen musst, wie zum Beispiel Deine Zähne zu putzen oder Deine E-Mails abzurufen. Du kannst die Tage, an denen eine tägliche Aufgabe fällig ist, anpassen, indem Du auf Bearbeiten klickst. Wenn Du eine tägliche Aufgabe, die fällig ist, auslässt, wird Deinem Charakter über Nacht Schaden zugefügt. Sei vorsichtig und füge nicht zu viele tägliche Aufgaben auf einmal hinzu!\n\nTo-Dos sind Deine Aufgabenlisten. Ein To-Do zu erledigen, bringt Dir Gold und Erfahrung. Du verlierst niemals Gesundheit durch To-Dos. Du kannst ein Ablaufdatum bei jedem To-Do hinzufügen, indem Du auf Bearbeiten klickst.",
+ "webFaqAnswer1": "Gute Gewohnheiten (die, die ein haben) sind Aufgaben, die Du mehrmals am Tag wiederholen kannst, wie Gemüse essen. Schlechte Angewohnheiten (die, die ein haben) sind Aufgaben, die man vermeiden sollte, wie Fingernägel kauen. Gewohnheiten mit einem und einem haben eine gute und eine schlechte Seite, wie die Treppe statt des Aufzugs zu nehmen. Gute Gewohnheiten gewähren Erfahrung und Gold. Schlechte Gewohnheiten ziehen Gesundheit ab.\n
\nTägliche Aufgaben müssen jeden Tag erledigt werden, wie Zähne putzen oder E-Mails abrufen. Du kannst die Tage anpassen an denen eine tägliche Aufgabe fällig ist, indem Du \"Bearbeiten\" klickst. Wenn Du eine tägliche Aufgabe auslässt, die fällig ist, wird Deinem Charakter über Nacht Schaden zugefügt. Sei also vorsichtig und füge nicht zu viele tägliche Aufgaben auf einmal hinzu. \n
To-Dos ist Deine Aufgabenliste. Ein To-Do zu erledigen, bringt Dir Gold und Erfahrung. Du verlierst niemals Gesundheit bei To-Dos. Du kannst ein Ablaufdatum bei jedem To-Do hinzufügen, indem Du \"Bearbeiten\" klickst.",
"faqQuestion2": "Wo finde ich Beispielaufgaben?",
"iosFaqAnswer2": "Das Wiki hat vier Listen mit Beispielaufgaben, die Du als Inspiration nutzen kannst:\n
\n* [Sample Habits](http://habitica.wikia.com/wiki/Sample_Habits)\n* [Sample Dailies](http://habitica.wikia.com/wiki/Sample_Dailies)\n* [Sample To-Dos](http://habitica.wikia.com/wiki/Sample_To-Dos)\n* [Sample Custom Rewards](http://habitica.wikia.com/wiki/Sample_Custom_Rewards)",
"webFaqAnswer2": "Das Wiki hat vier Listen mit Beispielaufgaben, die Du als Inspiration nutzen kannst:\n* [Sample Habits](http://habitica.wikia.com/wiki/Sample_Habits) \n* [Sample Dailies](http://habitica.wikia.com/wiki/Sample_Dailies)\n* [Sample To-Dos](http://habitica.wikia.com/wiki/Sample_To-Dos)\n* [Sample Custom Rewards](http://habitica.wikia.com/wiki/Sample_Custom_Rewards)",
"faqQuestion3": "Warum ändern meine Aufgaben die Farbe?",
- "iosFaqAnswer3": "Deine Aufgaben verändern die Farbe je nachdem wie gut Du diese zur Zeit erfüllst! Jede neue Aufgabe besitzt anfangs die neutrale Farbe Gelb. Erledigst Du tägliche Aufgaben oder gute Gewohnheiten regelmäßig, werden diese blau. Verfehlst Du eine tägliche Aufgabe oder gibst Du einer schlechten Gewohnheit nach, werden die Aufgaben rot. Je röter die Aufgabe ist, desto mehr Belohnung bekommst Du für sie, allerdings verletzen Dich tägliche Aufgaben und schlechte Gewohnheiten umso mehr! Das hilft dir Dich zu motivieren diese Aufgaben zu erledigen.",
- "webFaqAnswer3": "Deine Aufgaben verändern die Farbe je nachdem wie gut Du diese zur Zeit erfüllst! Jede neue Aufgabe besitzt anfangs die neutrale Farbe Gelb. Erledigst Du tägliche Aufgaben oder gute Gewohnheiten regelmäßig, werden diese blau. Verfehlst Du eine tägliche Aufgabe oder gibst Du einer schlechten Gewohnheit nach, werden die Aufgaben rot. Je röter die Aufgabe ist, desto mehr Belohnung bekommst Du für sie, allerdings verletzen Dich tägliche Aufgaben und schlechte Gewohnheiten umso mehr! Das hilft dir Dich zu motivieren diese Aufgaben zu erledigen.",
- "faqQuestion4": "Warum hat mein Avatar Lebenspunkte verloren und wie kann ich sie wieder auffüllen?",
- "iosFaqAnswer4": "Es gibt verschiedene Dinge, die verursachen, dass Du Schaden erleidest. Erstens, wenn Du tägliche Aufgaben über Nacht unerledigt lässt, werden sie dir Schaden zufügen. Zweitens, wenn Du eine schlechte Gewohnheit anklickst, fügt sie dir ebenfalls Schaden zu. Zuletzt, wenn Du in einem Bosskampf mit Deiner Gruppe bist und einer Deiner Gruppenmitglieder hat seine täglichen Aufgaben nicht erledigt, dann wird Dich der Boss angreifen.\n\nDer gewöhnliche Weg, um Leben zu gewinnen, ist im Level aufzusteigen, was Deine komplette Gesundheit wiederherstellt. Du kannst auch Heiltränke mit Gold in Laden kaufen. Dazu kommt, ab Level 10 oder höher, kannst Du wählen, ob Du ein Heiler werden möchtest, dann lernst Du Heilfähigkeiten. Wenn Du in einer Gruppe mit einem Heiler bist, kann dieser Dich genauso heilen.",
- "webFaqAnswer4": "Es gibt verschiedene Dinge, die verursachen, dass Du Schaden erleidest. Als erstes fügen dir tägliche Aufgaben, die über Nacht unerledigt bleiben, Schaden zu. Zweitens fügt es dir auch Schaden zu, wenn Du eine schlechte Gewohnheit anklickst. Drittens wird Dich in einem Bosskampf mit Deiner Gruppe der Boss angreifen, wenn eines Deiner Gruppenmitglieder seine täglichen Aufgaben nicht erledigt hat.\n
\nDer gewöhnliche Weg um Leben zu gewinnen ist im Level aufzusteigen, was Deine komplette Gesundheit wiederherstellt. Du kannst auch Heiltränke mit Gold in Shop kaufen. Dazu kommt, dass Du ab Level 10 oder höher wählen kannst, ob Du ein Heiler werden möchtest, dann lernst Du Heilfähigkeiten. Wenn Du in einer Gruppe (under Social > Party) mit einem Heiler bist, kann dieser Dich genauso heilen.",
+ "iosFaqAnswer3": "Deine Aufgaben verändern die Farbe je nachdem wie gut Du diese zur Zeit erfüllst! Jede neue Aufgabe besitzt anfangs die neutrale Farbe Gelb. Erledigst Du tägliche Aufgaben oder gute Gewohnheiten regelmäßig, werden diese blau. Verpasst Du eine tägliche Aufgabe oder gibst Du einer schlechten Gewohnheit nach, werden die Aufgaben rot. Je röter die Aufgabe ist, desto mehr Belohnung bekommst Du für sie, allerdings verletzen Dich tägliche Aufgaben und schlechte Gewohnheiten umso mehr! Das hilft Dir Dich zu motivieren diese Aufgaben zu erledigen.",
+ "webFaqAnswer3": "Deine Aufgaben verändern die Farbe je nachdem wie gut Du diese zur Zeit erfüllst! Jede neue Aufgabe besitzt anfangs die neutrale Farbe Gelb. Erledigst Du tägliche Aufgaben oder gute Gewohnheiten regelmäßig, werden diese blau. Verpasst Du eine tägliche Aufgabe oder gibst Du einer schlechten Gewohnheit nach, werden die Aufgaben rot. Je röter die Aufgabe ist, desto mehr Belohnung bekommst Du für sie, allerdings verletzen Dich tägliche Aufgaben und schlechte Gewohnheiten umso mehr! Das hilft Dir Dich zu motivieren diese Aufgaben zu erledigen.",
+ "faqQuestion4": "Warum hat mein Avatar Lebenspunkte verloren, und wie kann ich sie wieder auffüllen?",
+ "iosFaqAnswer4": "Es gibt verschiedene Dinge, welche Dir Schaden zufügen. Erstens, wenn Du tägliche Aufgaben über Nacht unerledigt lässt, werden sie Dir schaden. Zweitens, wenn Du eine schlechte Gewohnheit anklickst, fügt sie Dir ebenfalls Schaden zu. Zuletzt, wenn Du in einem Bosskampf mit Deiner Gruppe bist und einer Deiner Gruppenmitglieder hat seine täglichen Aufgaben nicht erledigt, dann wird Dich der Boss angreifen.\n\nDer gewöhnliche Weg, um Leben zu gewinnen, ist im Level aufzusteigen, was Deine komplette Gesundheit wiederherstellt. Du kannst auch Heiltränke mit Gold im Laden kaufen. Zudem kannst Du, ab Level 10 oder höher, wählen, ob Du ein Heiler werden möchtest, wodurch Du Heilfähigkeiten erlernst. Wenn Du in einer Gruppe mit einem Heiler bist, kann dieser Dich genauso heilen.",
+ "webFaqAnswer4": "Es gibt verschiedene Dinge, die verursachen, dass Du Schaden erleidest. Als erstes fügen Dir tägliche Aufgaben, die über Nacht unerledigt bleiben, Schaden zu. Zweitens fügt es Dir auch Schaden zu, wenn Du eine schlechte Gewohnheit anklickst. Drittens wird Dich in einem Bosskampf mit Deiner Gruppe der Boss angreifen, wenn eines Deiner Gruppenmitglieder seine täglichen Aufgaben nicht erledigt hat.\n
\nDer gewöhnliche Weg um Leben zu gewinnen ist im Level aufzusteigen, was Deine komplette Gesundheit wiederherstellt. Du kannst auch Heiltränke mit Gold in Shop kaufen. Dazu kommt, dass Du ab Level 10 oder höher wählen kannst, ob Du ein Heiler werden möchtest, dann lernst Du Heilfähigkeiten. Wenn Du in einer Gruppe (unter Soziales > Gruppe) mit einem Heiler bist, kann dieser Dich genauso heilen.",
"faqQuestion5": "Wie spiele ich Habitica mit meinen Freunden?",
- "iosFaqAnswer5": "Am Besten lädst Du sie in eine Gruppe mit dir ein! Gruppen können zusammen Quests bestreiten, Monster bekämpfen und sich gegenseitig mit Zaubern unterstützen. Geh auf Menü > Gruppe und klicke auf \"Neue Gruppe erstellen\" wenn Du noch keine Gruppe hast. Dann klicke auf die Mitgliederliste und lade sie ein indem Du oben rechts auf einladen klickst und ihre Benutzer ID (die aus einer langen Zahlen-, Buchstabenkombination besteht, welche sie unter Einstellungen > Konto Details bei der App und unter Einstellungen > API auf der Webseite finden können) eingibst. Außerdem kannst Du auf der Webseite Freunde auch per E-Mail einladen, was für die App auch noch folgen wird.\n\nAuf der Webseite können Du und Deine Freunde auch Gilden beitreten, welche öffentliche Chat-Räume sind. Gilden werden der App auch noch hinzugefügt werden.",
- "webFaqAnswer5": "Am besten lädst Du sie in eine Gruppe mit dir ein (unter Soziales > Gruppe)! Gruppen können zusammen Quests antreten, Monster bekämpfen und Fähigkeiten benutzen um einander zu helfen. Ihr könnt außerdem gemeinsam einer Gilde beitreten (unter Soziales > Gilden). Gilden sind Chat-Räume, deren Mitglieder gemeinsame Ziele verfolgen, und können privat oder öffentlich sein. Du kannst in sovielen Gilden sein, wie Du möchtest, aber nur in einer Gruppe.\n
\nGenauere Informationen findest Du auf den Wiki-Seiten für [Parties](http://habitrpg.wikia.com/wiki/Party) und [Guilds](http://habitrpg.wikia.com/wiki/Guilds).",
+ "iosFaqAnswer5": "Am Besten lädst Du sie in eine Gruppe mit Dir ein! Gruppen können zusammen Quests bestreiten, Monster bekämpfen und sich gegenseitig mit Zaubern unterstützen. Geh auf Menü > Gruppe und klicke auf \"Neue Gruppe erstellen\" wenn Du noch keine Gruppe hast. Dann klicke auf die Mitgliederliste und lade sie ein indem Du oben rechts auf einladen klickst und ihre Benutzer-ID (die aus einer langen Zahlen-, Buchstabenkombination besteht, welche sie unter Einstellungen > Konto Details bei der App und unter Einstellungen > API auf der Webseite finden können) eingibst. Außerdem kannst Du auf der Webseite Freunde auch per E-Mail einladen, was für die App auch noch folgen wird.\n\nAuf der Webseite können Du und Deine Freunde auch Gilden beitreten, welche öffentliche Chat-Räume sind. Gilden werden der App auch noch hinzugefügt werden.",
+ "webFaqAnswer5": "Am besten lädst Du sie in eine Gruppe mit Dir ein, unter Soziales > Gruppe! Gruppen können zusammen Quests bestreiten, Monster bekämpfen und Fähigkeiten benutzen um einander zu helfen. Ihr könnt außerdem gemeinsam einer Gilde beitreten (unter Soziales > Gilden). Gilden sind Chat-Räume, deren Mitglieder gemeinsame Ziele verfolgen, und können privat oder öffentlich sein. Du kannst in so vielen Gilden sein, wie Du möchtest, aber nur in einer Gruppe.\n
\nGenauere Informationen findest Du auf den Wiki-Seiten für [Parties](http://habitrpg.wikia.com/wiki/Party) und [Guilds](http://habitrpg.wikia.com/wiki/Guilds).",
"faqQuestion6": "Woher bekomme ich Haustiere oder Reittiere?",
"iosFaqAnswer6": "Wenn Du Level 3 erreichst, wirst Du das Beutesystem freischalten. Jedes Mal wenn Du eine Aufgabe erledigst, hast Du eine zufällige Chance ein Ei, einen Schlüpftrank oder Futter zu erhalten. Diese werden unter Inventar > Marktplatz gespeichert. \n\nUm ein Haustier auszubrüten benötigst Du ein Ei und einen Schlüpftrank. Klicke auf das Ei, um die Spezies auszuwählen, welche Du schlüpfen lassen möchtest, und klicke anschließend auf den Schlüpftrank, um die Farbe zu bestimmen! Um es Deinem Avatar hinzuzufügen, gehe zu Inventar > Haustiere und klicke auf das gewünschte Tier.\n\nDu kannst Deine Haustiere unter Inventar > Haustiere auch füttern, sodass sie zu Reittieren heranwachsen. Klicke auf ein Haustier und dann auf das Futter aus dem Menü auf der rechten Seite, um es zu füttern! Damit es zu einem Reittier heranwächst, musst Du Dein Haustier mehrmals füttern, wenn Du jedoch sein bevorzugtes Futter herausfindest, wächst es schneller. Dies kannst Du entweder durch ausprobieren selbst herausfinden oder [im Wiki nachschauen - Vorsicht: Spoiler!](http://de.habitica.wikia.com/wiki/Futter#Bevorzugtes_Futter). Wenn Du ein Reittier erhalten hast, gehe zu Inventar > Reittiere und klicke das Tier an, um es Deinem Avatar hinzuzufügen.\n\nDu kannst auch Eier für Quest-Haustiere erhalten, indem Du bestimmte Quests abschließt. (Siehe weiter unten, um mehr über Quests zu erfahren.)",
- "webFaqAnswer6": "Wenn Du Level 3 erreichst, wirst Du das Beutesystem freischalten. Jedes Mal wenn Du eine Aufgabe erledigst, hast Du eine zufällige Chance ein Ei, einen Schlüpftrank oder Futter zu erhalten. Diese werden unter Inventar > Marktplatz gespeichert.\n
\nUm ein Haustier auszubrüten benötigst Du ein Ei und einen Schlüpftrank. Klicke auf das Ei, um die Spezies auszuwählen, welche zu schlüpfen lassen möchtest, und clicke anschließend auf den Schlüpftrank, um die Farbe zu bestimmen! Um es Deinem Avatar hinzuzufügen, gehe zu Inventar > Haustiere und klicke auf das gewünschte Tier.\n
\nDu kannst Deine Haustiere unter Inventar > Haustiere auch füttern, sodass sie zu Reittieren heranwachsen. Klicke auf ein Haustier und dann auf das Futter aus dem Menü auf der rechten Seite, um es zu füttern! Damit es zu einem Reittier heranwächst, musst Du Dein Haustier mehrmals füttern, wenn Du jedoch sein bevorzugtes Futter herausfindest, wächst es schneller. Dies kannst Du entweder durch ausprobieren selbst herausfinden oder [im Wiki nachschauen Vorsicht: Spoiler!](http://de.habitica.wikia.com/wiki/Futter#Bevorzugtes_Futter). Wenn Du ein Reittier erhalten hast, gehe zu Inventar > Mounts und clicke das Tier an, um es Deinem Avatar hinzuzufügen.\n
\nDu kannst auch Eier für Quest-Haustiere erhalten, indem Du bestimmte Quests abschließt. (Siehe weiter unten, um mehr über Quests zu erfahren.)",
+ "webFaqAnswer6": "Wenn Du Level 3 erreichst, wirst Du das Beutesystem freischalten. Jedes Mal wenn Du eine Aufgabe erledigst, hast Du eine zufällige Chance ein Ei, einen Schlüpftrank oder Futter zu erhalten. Diese werden unter Inventar > Marktplatz gespeichert.\n
\nUm ein Haustier auszubrüten benötigst Du ein Ei und einen Schlüpftrank. Klicke auf das Ei, um die Spezies auszuwählen, welche zu schlüpfen lassen möchtest, und klicke anschließend auf den Schlüpftrank, um die Farbe zu bestimmen! Um es Deinem Avatar hinzuzufügen, gehe zu Inventar > Haustiere und klicke auf das gewünschte Tier.\n
\nDu kannst Deine Haustiere unter Inventar > Haustiere auch füttern, sodass sie zu Reittieren heranwachsen. Klicke auf ein Haustier und dann auf das Futter aus dem Menü auf der rechten Seite, um es zu füttern! Damit es zu einem Reittier heranwächst, musst Du Dein Haustier mehrmals füttern. Wenn Du jedoch sein bevorzugtes Futter herausfindest, wächst es schneller. Dies kannst Du entweder durch Ausprobieren selbst herausfinden oder [im Wiki nachschauen Vorsicht: Spoiler!](http://de.habitica.wikia.com/wiki/Futter#Bevorzugtes_Futter). Wenn Du ein Reittier erhalten hast, gehe zu Inventar > Reittiere und klicke das Tier an, um es Deinem Avatar hinzuzufügen.\n
\nDu kannst auch Eier für Quest-Haustiere erhalten, indem Du bestimmte Quests abschließt. (Siehe weiter unten, um mehr über Quests zu erfahren.)",
"faqQuestion7": "Wie werde ich ein Krieger, Magier, Schurke oder Heiler?",
- "iosFaqAnswer7": "Wenn Du Level 10 erreichst, kannst Du wählen, ob Du Krieger, Magier, Schurke oder Heiler werden möchtest. (Alle Spieler beginnen standardmäßig als Krieger.) Jede Klasse hat unterschiedliche Ausrüstungsoptionen, unterschiedliche Fähigkeiten, die sie ab Level 11 verwenden können, und unterschiedliche Vorteile. Krieger fügen Bossen leicht Schaden zu, halten mehr Schaden von ihren Aufgaben aus und helfen ihrer Gruppe widerstandsfähiger zu werden. Magier schaden Bossen ebenfalls leicht, steigen schnell Level auf und können Mana für sich und ihre Gruppe wiederherstellen. Schurken erhalten das meiste Geld, finden die meiste Beute und können ihrer Gruppe helfen, dies ebenfalls zu tun. Zum Schluss können Heiler sich selbst und ihre Gruppe heilen. \n\nWenn Du nicht direkt eine Klasse auswählen möchtest -- zum Beispiel, wenn Du gerade dabei bist die gesamte Ausrüstung für Deine aktuelle Klasse zu kaufen -- kannst Du \"Später auswählen\" klicken und die Klasse unter Benutzer > Werte später auswählen.",
- "webFaqAnswer7": "Wenn Du Level 10 erreichst, kannst Du wählen, ob Du Krieger, Magier, Schurke oder Heiler werden möchtest. (Alle Spieler beginnen standardmäßig als Krieger.) Jede Klasse hat unterschiedliche Ausrüstungsoptionen, unterschiedliche Fähigkeiten, die sie ab Level 11 verwenden können, und unterschiedliche Vorteile. Krieger fügen Bossen leicht Schaden zu, halten mehr Schaden von ihren Aufgaben aus und helfen ihrer Gruppe widerstandsfähiger zu werden. Magier schaden Bossen ebenfalls leicht, steigen schnell Level auf und können Mana für sich und ihre Gruppe wiederherstellen. Schurken erhalten das meiste Geld, finden die meiste Beute und können ihrer Gruppe helfen, dies ebenfalls zu tun. Heiler können sich selbst und ihre Gruppe heilen.\n
\nWenn Du nicht direkt eine Klasse auswählen möchtest -- zum Beispiel, wenn Du gerade dabei bist die gesamte Ausrüstung für Deine aktuelle Klasse zu kaufen -- kannst Du \"Opt Out\" klicken und die Klasse unter Benutzer > Werte später aktivieren.",
+ "iosFaqAnswer7": "Wenn Du Level 10 erreichst, kannst Du wählen, ob Du Krieger, Magier, Schurke oder Heiler werden möchtest. (Alle Spieler beginnen standardmäßig als Krieger.) Jede Klasse hat unterschiedliche Ausrüstungsoptionen, unterschiedliche Fähigkeiten, die sie ab Level 11 verwenden können, und unterschiedliche Vorteile. Krieger fügen Bossen leicht Schaden zu, halten mehr Schaden von ihren Aufgaben aus und helfen ihrer Gruppe widerstandsfähiger zu werden. Magier schaden Bossen ebenfalls leicht, steigen schnell Level auf und können Mana für ihre Gruppe wiederherstellen. Schurken erhalten das meiste Geld, finden die meiste Beute und können ihrer Gruppe helfen, dies ebenfalls zu tun. Zum Schluss können Heiler sich selbst und ihre Gruppe heilen. \n\nWenn Du nicht direkt eine Klasse auswählen möchtest -- zum Beispiel, wenn Du gerade dabei bist die gesamte Ausrüstung für Deine aktuelle Klasse zu kaufen -- kannst Du \"Später auswählen\" klicken und die Klasse unter Benutzer > Werte später auswählen.",
+ "webFaqAnswer7": "Wenn Du Level 10 erreichst, kannst Du wählen, ob Du Krieger, Magier, Schurke oder Heiler werden möchtest. (Alle Spieler beginnen standardmäßig als Krieger.) Jede Klasse hat unterschiedliche Ausrüstungsoptionen, unterschiedliche Fähigkeiten, die sie ab Level 11 verwenden können, und unterschiedliche Vorteile. Krieger fügen Bossen leicht Schaden zu, halten mehr Schaden von ihren Aufgaben aus und helfen ihrer Gruppe widerstandsfähiger zu werden. Magier schaden Bossen ebenfalls leicht, steigen schnell Level auf und können Mana für ihre Gruppe wiederherstellen. Schurken erhalten das meiste Geld, finden die meiste Beute und können ihrer Gruppe helfen, dies ebenfalls zu tun. Heiler können sich selbst und ihre Gruppe heilen.\n
\nWenn Du nicht direkt eine Klasse auswählen möchtest -- zum Beispiel, wenn Du gerade dabei bist die gesamte Ausrüstung für Deine aktuelle Klasse zu kaufen -- kannst Du \"Opt Out\" klicken und die Klasse unter Benutzer > Werte später aktivieren.",
"faqQuestion8": "Was ist die blaue Statistik-Leiste, die in der Kopfzeile nach Level 10 erscheint?",
- "iosFaqAnswer8": "Der blaue Balken, der erschienen ist, als Du Level 10 erreicht hast und eine Klasse gewählt hast, ist Dein Manabalken. Mit dem Erreichen weiterer Level wirst Du spezielle Fähigkeiten freischalten, die Dich Mana kosten. Jede Klasse hat verschiedene Fähigkeiten, die Du nach Level 11 unter Menu > Use Skills erscheinen. Im Gegensatz zu Deinem Lebensbalken füllt sich Dein Mana nicht automatisch beim Stufenanstieg wieder auf. Stattdessen füllt sich Dein Mana durch das Erfüllen von täglichen und einmaligen Aufgaben und Gewohnheiten wieder auf. Gehst Du schlechten Gewohnheiten nach, verlierst Du dafür Mana. Außerdem regenerierst Du etwas Mana über Nacht -- abhängig von der Anzahl der täglichen Aufgaben, die Du erfüllt hast. Je mehr Aufgaben, desto mehr Mana regenerierst Du.",
- "webFaqAnswer8": "Die blaue Leiste, die erschien als Du Level 10 erreicht hast und eine Klasse gewählt hast, ist Deine Manaleiste. Während Du weiter im Level aufsteigst, wirst Du spezielle Fähigkeiten freischalten, die Dich beim Benutzen Mana kosten. Jede Klasse hat verschiedene Fähigkeiten, die nach dem 11. Levelaufstieg unter einen speziellen Absatz unter der Belohnungsspalte erscheint. Im Gegensatz zu Deiner Lebensleiste füllt sich Dein Mana nicht automatisch beim Stufenanstieg wieder auf. Stattdessen füllt sich Mana durch das Erfüllen von Deinen täglichen und einmaligen Aufgaben und Gewohnheiten und sie geht verloren, wenn Du schlechten Gewohnheiten nachgehst. Außerdem regenerierst Du etwas Mana über Nacht -- abhängig von der Anzahl der täglichen Aufgaben, die Du erfüllt hast. Je mehr Aufgaben, desto mehr Mana regenerierst Du.",
+ "iosFaqAnswer8": "Der blaue Balken, der erschienen ist, als Du Level 10 erreicht hast und eine Klasse gewählt hast, ist Dein Manabalken. Mit dem Erreichen weiterer Level wirst Du spezielle Fähigkeiten freischalten, die Dich Mana kosten. Jede Klasse hat verschiedene Fähigkeiten, die nach Level 11 unter Ausrüstung & Fähigkeiten erscheinen. Im Gegensatz zu Deinem Lebensbalken füllt sich Dein Mana nicht automatisch beim Stufenanstieg wieder auf. Stattdessen füllt sich Dein Mana durch das Erfüllen von Gewohnheiten, täglichen Aufgaben und To-Dos wieder auf. Gehst Du schlechten Gewohnheiten nach, verlierst Du dafür Mana. Außerdem regenerierst Du etwas Mana über Nacht -- abhängig von der Anzahl der täglichen Aufgaben, die Du erfüllt hast. Je mehr Aufgaben, desto mehr Mana regenerierst Du.",
+ "webFaqAnswer8": "Die blaue Leiste, die erschien als Du Level 10 erreicht hast und eine Klasse gewählt hast, ist Deine Manaleiste. Während Du weiter im Level aufsteigst, wirst Du spezielle Fähigkeiten freischalten, die Dich beim Benutzen Mana kosten. Jede Klasse hat verschiedene Fähigkeiten, die nach dem 11. Levelaufstieg unter einen speziellen Absatz unter der Belohnungsspalte erscheint. Im Gegensatz zu Deiner Lebensleiste füllt sich Dein Mana nicht automatisch beim Stufenanstieg wieder auf. Stattdessen füllt sich Mana durch das Erfüllen Deiner Gewohnheiten, täglichen Aufgaben und To-Tos wieder auf, und sie geht verloren, wenn Du schlechten Gewohnheiten nachgehst. Außerdem regenerierst Du etwas Mana über Nacht -- abhängig von der Anzahl der täglichen Aufgaben, die Du erfüllt hast. Je mehr Aufgaben, desto mehr Mana regenerierst Du.",
"faqQuestion9": "Wie bekämpfe ich Monster und gehe auf Quests?",
"iosFaqAnswer9": "Zuerst musst Du eine Gruppe gründen oder einer Gruppe beitreten (unter Soziales > Gruppe). Obwohl Du Monster auch alleine bekämpfen kannst, empfehlen wir in einer Gruppe mit anderen Mitspielern zu spielen, da dies die Quests viel einfacher macht. Zusätzlich ist es sehr motivierend einen Freund zu haben, der einen anspornt seine Aufgaben zu erledigen.\n\nAls nächstes benötigst Du eine Questrolle, welche unter Inventar > Quests gelagert werden. Es gibt drei Möglichkeiten an Schriftrollen zu kommen: \n\n- Bei Erreichen von Level 15 erhältst Du eine Questreihe, d.h. drei zusammenhängende Quests. Weitere Questreihen werden bei den Leveln 30, 40 und 60 freigeschaltet. \n- Wenn Du Leute in Deine Gruppe einlädst, bekommst Du als Belohnung die Basi-List-Schriftrolle!\n- Du kannst Quests auf der Quest-Seite der [Website](https://habitica.com/#/options/inventory/quests) für Gold und Edelsteine kaufen. (Wir werden diese Funktion in einem späteren Update der App hinzufügen.)\n\nUm den Boss zu bekämpfen oder Gegenstände bei einer Sammelquest zu sammeln, musst Du einfach Deine Aufgaben normal erledigen und sie werden über Nacht in Schaden umgerechnet. (Möglicherweise ist es erforderlich die Seite neu zu laden, um den Effekt auf den Lebensbalken des Bosses zu sehen.) Wenn Du einen Boss bekämpfst und tägliche Aufgaben nicht erledigst, wird der Boss eurer Gruppe zur gleichen Zeit Schaden zufügen wie ihr dem Boss.\n\nAb Level 11 erhalten Magier und Krieger Fähigkeiten, welche dem Boss zusätzlichen Schaden zufügen, also sind dies auf Level 10 ausgezeichnete Klassen zu wählen, wenn ihr großen Schaden anrichten wollt.",
- "webFaqAnswer9": "Zuerst musst Du eine Gruppe gründen oder einer Gruppe beitreten (unter Soziales > Gruppe). Obwohl Du Monster auch alleine bekämpfen kannst, empfehlen wir in einer Gruppe mit anderen Mitspielern zu spielen, da dies die Quests viel einfacher macht. Zusätzlich ist es sehr motivierend einen Freund zu haben, der einen anspornt seine Aufgaben zu erledigen.\n
\nAls nächstes benötigst Du eine Questrolle, welche unter Inventar > Quests gespeichert sind. Es gibt drei Möglichkeiten an Rollen zu kommen:\n
\n* Wenn Du Mitspieler zu Deiner Gruppe einlädst, erhälst Du die Basi-List-Rolle.\n* Bei Erreichen von Level 15 erhälst Du eine Questreihe, d.h. drei zusammenhängende Quests. Weitere Questreihen werden bei den Leveln 30, 40 und 60 freigeschaltet.\n* Du kanst Quests auf der Quest-Seite (Inventar > Quests) für Gold und Edelsteine kaufen.\n
\nUm den Boss zu bekämpfen oder Gegenstände bei einer Sammelquest zu sammeln, musst Du einfach Deine Aufgaben normal erledigen und sie werden über Nacht in Schaden umgerechnet. (Möglicherweise ist es erforderlich die Seite neu zu laden, um den Effekt auf den Lebensbalken des Bosses zu sehen.) Wenn Du einen Boss bekämpfst und tägliche Aufgaben nicht erledigst, wird der Boss eurer Gruppe zur gleichen Zeit Schaden zufügen wie ihr dem Boss.\n
\nAb Level 11 erhalten Magier und Krieger Fäghigkeiten, welche dem Boss zusätzlichen Schaden zufügen, also sind dies ausgezeichnete Klassen, welche Du ab Level 10 wählen sollte, wenn Du viel Bossen viel Schaden zufügen möchtest.",
+ "webFaqAnswer9": "Zuerst musst Du eine Gruppe gründen oder einer Gruppe beitreten (unter Soziales > Gruppe). Obwohl Du Monster auch alleine bekämpfen kannst, empfehlen wir in einer Gruppe mit anderen Mitspielern zu spielen, da dies die Quests viel einfacher macht. Zusätzlich ist es sehr motivierend einen Freund zu haben, der einen anspornt seine Aufgaben zu erledigen.\n
\nAls nächstes benötigst Du eine Questrolle, welche unter Inventar > Quests gespeichert sind. Es gibt drei Möglichkeiten an Rollen zu kommen:\n
\n* Wenn Du Mitspieler zu Deiner Gruppe einlädst, erhältst Du die Basi-List-Rolle.\n* Bei Erreichen von Level 15 erhältst Du eine Questreihe, d.h. drei zusammenhängende Quests. Weitere Questreihen werden bei den Leveln 30, 40 und 60 freigeschaltet.\n* Du kanst Quests auf der Quest-Seite (Inventar > Quests) für Gold und Edelsteine kaufen.\n
\nUm den Boss zu bekämpfen oder Gegenstände bei einer Sammelquest zu sammeln, musst Du einfach Deine Aufgaben normal erledigen und sie werden über Nacht in Schaden umgerechnet. (Möglicherweise ist es erforderlich die Seite neu zu laden, um den Effekt auf den Lebensbalken des Bosses zu sehen.) Wenn Du einen Boss bekämpfst und tägliche Aufgaben nicht erledigst, wird der Boss eurer Gruppe zur gleichen Zeit Schaden zufügen wie ihr dem Boss.\n
\nAb Level 11 erhalten Magier und Krieger Fäghigkeiten, welche dem Boss zusätzlichen Schaden zufügen, also sind dies ausgezeichnete Klassen, welche Du ab Level 10 wählen sollte, wenn Du viel Bossen viel Schaden zufügen möchtest.",
"faqQuestion10": "Was sind Edelsteine und wie bekomme ich welche?",
- "iosFaqAnswer10": "Edelsteine können mit echtem Geld gekauft werden, indem man auf das Edelstein-Symbol oben in der Kopfzeile klickt. Wenn Menschen Edelsteine kaufen, helfen sie uns, die Webseite zu betreiben. Wir sind sehr dankbar für ihre Unterstützung!\n\nAlternativ zum direkten Kauf von Edelsteinen gibt es drei andere Möglichkeiten, Edelsteine zu erhalten:\n\n* Gewinne einen Wettbewerb auf der [Webseite](https://habitica.com) eines anderen Spielers unter Soziales > Wettbewerbe. (Wir werden Wettbewerbe in einem zukünftigen Update in die App integrieren!)\n* Abonniere diese [Webseite](https://habitica.com/#/options/settings/subscription) und schalte die Fähigkeit frei, eine bestimmte Anzahl von Edelsteinen pro Monat zu kaufen.\n* Trage mit Deinen Fähigkeiten zum Habitica Projekt bei. Für mehr Informationen siehe im Wiki nach: [An Habitica mitarbeiten](http://habitica.wikia.com/wiki/Contributing_to_Habitica).\n\nBeachte, dass Gegenstände, die mit Edelsteinen gekauft werden keine statistischen Vorteile bringen, sodass Spieler die Seite auch ohne sie benutzen können!",
- "webFaqAnswer10": "Edelsteine werden [mit realem Geld gekauft](https://habitica.com/#/options/settings/subscription), jedoch können [Abonnenten](https://habitica.com/#/options/settings/subscription) sie mit Gold kaufen. Wenn Menschen abonnieren oder Edelsteine kaufen, helfen sie uns die Seite zu betreiben. Wir sind sehr dankbar für ihre Unterstützung!\n
\nZusätzlich zum Kaufen von Edelsteinen oder Abonnieren gibt es zwei andere Möglichkeiten für einen Spieler an Edelsteine zu kommen.\n
\n* Gewinne einen Wettbewerb eines anderen Spielers unter Soziales > Wettbewerbe.\n* Trage mit Deinen Fähigkeiten zum Habitica Projekt bei. Für mehr Details, sieh im Wiki nach: [An Habitica mitarbeiten](http://habitica.wikia.com/wiki/Contributing_to_Habitica)\n
\nBeachte, dass Gegenstände, die mit Edelsteinen gekauft werden keine statistischen Vorteile bringen, sodass Spieler die Seite auch ohne sie benutzen können!",
+ "iosFaqAnswer10": "Edelsteine können mit echtem Geld gekauft werden, indem man auf das Edelstein-Symbol oben in der Kopfzeile klickt. Wenn Menschen Edelsteine kaufen, helfen sie uns, die Webseite zu betreiben. Wir sind sehr dankbar für ihre Unterstützung!\n\nAlternativ zum direkten Kauf von Edelsteinen gibt es drei andere Möglichkeiten, Edelsteine zu erhalten:\n\n* Gewinne einen Wettbewerb auf der [Webseite](https://habitica.com) eines anderen Spielers unter Soziales > Wettbewerbe. (Wir werden Wettbewerbe in einem zukünftigen Update in die App integrieren!)\n* Abonniere diese [Webseite](https://habitica.com/#/options/settings/subscription) und schalte die Fähigkeit frei, eine bestimmte Anzahl von Edelsteinen pro Monat zu kaufen.\n* Trage mit Deinen Fähigkeiten zum Habitica Projekt bei. Für mehr Informationen sieh im Wiki nach: [An Habitica mitarbeiten](http://habitica.wikia.com/wiki/Contributing_to_Habitica).\n\nBeachte, dass Gegenstände, die mit Edelsteinen gekauft werden keine statistischen Vorteile bringen, sodass Spieler die Seite auch ohne sie benutzen können!",
+ "webFaqAnswer10": "Edelsteine werden [mit realem Geld gekauft](https://habitica.com/#/options/settings/subscription), jedoch können [Abonnenten](https://habitica.com/#/options/settings/subscription) sie mit Gold kaufen. Wenn Menschen abonnieren oder Edelsteine kaufen, helfen sie uns die Seite zu betreiben. Wir sind sehr dankbar für ihre Unterstützung!\n
\nZusätzlich zum Kauf von Edelsteinen oder Abonnieren gibt es zwei weitere Möglichkeiten für einen Spieler an Edelsteine zu kommen.\n
\n* Gewinne einen Wettbewerb eines anderen Spielers unter Soziales > Wettbewerbe.\n* Trage mit Deinen Fähigkeiten zum Habitica-Projekt bei. Für mehr Details, sieh im Wiki nach: [An Habitica mitarbeiten](http://habitica.wikia.com/wiki/Contributing_to_Habitica)\n
\nBeachte, dass Gegenstände, die mit Edelsteinen gekauft werden keine statistischen Vorteile bringen, sodass Spieler die Seite auch ohne sie benutzen können!",
"faqQuestion11": "Wie melde ich einen Fehler oder schlage ein Feature vor?",
- "iosFaqAnswer11": "Um einen Fehler zu melden, ein Feature vorzuschlagen oder Feedback zu senden, gehe im Menü unter Hilfe > Melde einen Fehler und Hilfe > Feature vorschlagen! Wir werden alles tun, um dir zu helfen.",
+ "iosFaqAnswer11": "Um einen Fehler zu melden, ein Feature vorzuschlagen oder Feedback zu senden, gehe im Menü unter Hilfe > Melde einen Fehler und Hilfe > Feature vorschlagen! Wir werden alles tun, um Dir zu helfen.",
"webFaqAnswer11": "Gemeldete Fehler werden in GitHub gesammelt. Gehe zu [Hilfe > Melde einen Fehler](https://github.com/HabitRPG/habitrpg/issues/2760) und folge den Anweisungen. Keine Sorge, wir werden ihn so bald wie möglich beheben!\n
\nVorgeschlagene Features werden in Trello gesammelt. Gehe zu [Hilfe > Feature vorschlagen](https://trello.com/c/odmhIqyW/440-read-first-table-of-contents) und folge den Anweisungen. Tadaa!",
"faqQuestion12": "Wie bekämpfe ich einen Weltboss?",
- "iosFaqAnswer12": "Welt-Bosse sind spezielle Monster, welche im Gasthaus erscheinen. Alle aktiven Benutzer kämpfen automatisch gegen den Boss und ihre Aufgaben und Fähigkeiten werden dem Boss wie üblich schaden.\n\nDu kannst Dich gleichzeitig in einer normalen Quest befinden. Deine Aufgaben und Fähigkeiten werden sowohl dem Welt-Boss wie auch dem Boss/der Sammelquest schaden.\n\nEin Welt-Boss wird niemals Dich oder Deinen Account verletzten. Stattdessen hat es einen Wutbalken, welcher sich füllt, wenn Benutzer ihre täglichen Aufgaben überspringen. Falls der Wutbalken gefüllt ist, wird der Welt-Boss einen der Nicht-Spieler-Charakter der Seite angreifen und ihr Aussehen wird sich verändern.\n\n Du kannst mehr über [past World Bosses](http://habitica.wikia.com/wiki/World_Bosses) in der Wiki erfahren",
- "webFaqAnswer12": "Welt-Bosse sind spezielle Monster, welche im Gasthaus erscheinen. Alle aktiven Benutzer kämpfen automatisch gegen den Boss und ihre Aufgaben und Fähigkeiten werden dem Boss wie üblich schaden. \n
\nDu kannst Dich gleichzeitig in einer normalen Quest befinden. Deine Aufgaben und Fähigkeiten werden sowohl bei dem Welt-Boss, sowie auch bei dem Boss/der Sammelquest von Deiner Gruppe dazugezählt. \n
\nEin Welt-Boss wird niemals Dich oder Deinen Account verletzten. Stattdessen hat es einen Wutbalken, welcher sich füllt, wenn Benutzer ihre täglichen Aufgaben überspringen. Falls der Wutbalken gefüllt ist, wird der Welt-Boss einen der Nicht-Spieler-Charakter der Seite angreifen und ihr Aussehen wird sich verändern.\n
\nDu kannst mehr über [frühere Welt-Bösse](http://habitica.wikia.com/wiki/World_Bosses) in der Wiki erfahren",
- "iosFaqStillNeedHelp": "Wenn Du eine Frage hast, die hier oder im [Wiki FAQ](http://habitica.wikia.com/wiki/FAQ) nicht beantwortet wurde, stelle sie im Gasthaus unter Soziales > Gasthaus-Chat! Wir helfen dir gerne.",
- "webFaqStillNeedHelp": "Wenn Du eine Frage hast, die hier oder im [Wiki FAQ](http://habitica.wikia.com/wiki/FAQ) nicht beantwortet wurde, stelle sie in der [Newbies Gilde](https://habitica.com/#/options/groups/guilds/5481ccf3-5d2d-48a9-a871-70a7380cee5a)! Wir helfen dir gerne."
+ "iosFaqAnswer12": "Weltbosse sind spezielle Monster, welche im Gasthaus erscheinen. Alle aktiven Benutzer kämpfen automatisch gegen den Boss und ihre Aufgaben und Fähigkeiten werden dem Boss wie üblich schaden.\n\nDu kannst Dich gleichzeitig in einer normalen Quest befinden. Deine Aufgaben und Fähigkeiten werden sowohl dem Weltboss wie auch dem Boss/der Sammelquest schaden.\n\nEin Weltboss wird niemals Dich oder Deinen Account verletzten. Stattdessen hat es einen Wutbalken, welcher sich füllt, wenn Benutzer ihre täglichen Aufgaben überspringen. Falls der Raserei-Balken gefüllt ist, wird der Weltboss einen der Nicht-Spieler-Charakter der Seite angreifen und ihr Aussehen wird sich verändern.\n\n Du kannst mehr über [vergangene Weltbosse](http://habitica.wikia.com/wiki/World_Bosses) im Wiki erfahren",
+ "webFaqAnswer12": "Weltbosse sind spezielle Monster, welche im Gasthaus erscheinen. Alle aktiven Benutzer kämpfen automatisch gegen den Boss und ihre Aufgaben und Fähigkeiten werden dem Boss wie üblich schaden. \n
\nDu kannst Dich gleichzeitig in einer normalen Quest befinden. Deine Aufgaben und Fähigkeiten werden sowohl beim Weltboss, sowie auch bei dem Boss/der Sammelquest von Deiner Gruppe dazugezählt. \n
\nEin Weltboss wird niemals Dich oder Deinen Account verletzten. Stattdessen hat dieser einen Raserei-Balken, welcher sich füllt, wenn Benutzer ihre täglichen Aufgaben überspringen. Falls der Raserei-Balken gefüllt ist, wird der Weltboss einen der Nicht-Spieler-Charakter der Seite angreifen und ihr Aussehen wird sich verändern.\n
\nDu kannst mehr über [frühere Weltbosse](http://habitica.wikia.com/wiki/World_Bosses) im Wiki erfahren",
+ "iosFaqStillNeedHelp": "Wenn Du eine Frage hast, die hier oder im [Wiki FAQ](http://habitica.wikia.com/wiki/FAQ) nicht beantwortet wurde, stelle sie im Gasthaus unter Soziales > Gasthaus-Chat! Wir helfen Dir gerne.",
+ "webFaqStillNeedHelp": "Wenn Du eine Frage hast, die hier oder im [Wiki-FAQ](http://habitica.wikia.com/wiki/FAQ) nicht beantwortet wurde, stelle sie in der [Newbies-Gilde](https://habitica.com/#/options/groups/guilds/5481ccf3-5d2d-48a9-a871-70a7380cee5a)! Wir helfen Dir gerne."
}
\ No newline at end of file
diff --git a/common/locales/de/front.json b/common/locales/de/front.json
index c78605c28b..0b5612552f 100644
--- a/common/locales/de/front.json
+++ b/common/locales/de/front.json
@@ -3,7 +3,7 @@
"accept1Terms": "Durch Klicken auf diesen Knopf, stimme ich den",
"accept2Terms": "zu, sowie der",
"alexandraQuote": "Ich konnte während meiner Rede in Madrid nicht NICHT über [Habitica] reden. Ein absolutes Muss für Freiberufler, die trotzdem einen Chef brauchen.",
- "althaireQuote": "Kontinuierlich eine Quest zu haben, motiviert mich dazu alle meine täglichen und einmaligen Aufgaben zu erledigen. Meine größte Motivation ist, meine Gruppe nicht im Stich zu lassen.",
+ "althaireQuote": "Kontinuierlich eine Quest zu haben, motiviert mich dazu alle meine täglichen Aufgaben und To-Dos zu erledigen. Meine größte Motivation ist, meine Gruppe nicht im Stich zu lassen.",
"andeeliaoQuote": "Großartiges Produkt, ich habe gerade erst vor einigen Tagen angefangen und gehe jetzt schon bewusster und produktiver mit meiner Zeit um!",
"autumnesquirrelQuote": "Ich schiebe bei der Arbeit und im Haushalt weniger auf und zahle meine Rechnungen rechtzeitig.",
"businessSample1": "1 Seite des Inventars überprüfen",
@@ -16,7 +16,7 @@
"choreSample2": "20 Minuten Hausarbeit",
"choreSample3": "Geschirr abspülen",
"choreSample4": "Einen Raum aufräumen",
- "choreSample5": "Geschirr abspülen und trocknen",
+ "choreSample5": "Eine Ladung Wäsche waschen und trocknen",
"chores": "Arbeiten im Haushalt",
"clearBrowserData": "Browserdaten löschen",
"communityBug": "Bug mitteilen",
@@ -26,21 +26,22 @@
"communityForum": "Forum",
"communityKickstarter": "Kickstarter",
"communityReddit": "Reddit",
- "companyAbout": "Wie's funktioniert",
+ "companyAbout": "Wie es funktioniert",
"companyBlog": "Blog",
+ "devBlog": "Entwicklerblog",
"companyDonate": "Spenden",
"companyExtensions": "Erweiterungen",
"companyPrivacy": "Datenschutz",
"companyTerms": "AGB",
"companyVideos": "Videos",
"contribUse": "Habitica-Mitwirkende nutzen",
- "dragonsilverQuote": "Ich habe unzählige Zeit- und Aufgabenerfassungssysteme ausprobiert… [Habitica] ist das Einzige, das mir wirklich hilft, Dinge zu erledigen, anstatt sie nur aufzuschreiben.",
+ "dragonsilverQuote": "Ich habe unzählige Zeit- und Aufgabenerfassungssysteme ausprobiert … [Habitica] ist das Einzige, das mir wirklich hilft Dinge zu erledigen, anstatt sie nur aufzuschreiben.",
"dreimQuote": "Bevor ich [Habitica] letzten Sommer entdeckt habe, war ich bereits durch die Hälfte meiner Prüfungen gefallen. Dank der täglichen Aufgaben war ich dazu in der Lage, mich zu organisieren und zu disziplinieren und ich habe letzten Monat wirklich alle meine Prüfungen mit sehr guten Noten bestanden.",
"elmiQuote": "Jeden Morgen freue ich mich aufzustehen und etwas Gold zu verdienen!",
"email": "E-Mail",
"emailNewPass": "E-Mail neues Passwort",
"evagantzQuote": "Mein erster Zahnarztbesuch, bei dem die Assistentin tatsächlich begeistert über meine Zahnseide-Gewohnheiten war. Danke [Habitica]!",
- "examplesHeading": "Spieler benutzen Habitica, um Folgendes zu organisieren ...",
+ "examplesHeading": "Spieler benutzen Habitica, um folgendes zu organisieren ...",
"featureAchievementByline": "Etwas total großartiges gemacht? Erhalte ein Abzeichen und prahle damit!",
"featureAchievementHeading": "Erfolgsabzeichen",
"featureEquipByline": "Kaufe Gegenstände in limitierter Auflage, Zaubertränke und andere virtuelle Leckerbissen auf unserem Marktplatz mit den Belohnungen für abgeschlossene Aufgaben!",
@@ -51,12 +52,13 @@
"featureSocialHeading": "Gemeinsames Spielen",
"featuredIn": "Vorgestellt in",
"featuresHeading": "Weitere Funktionen …",
+ "footerDevs": "Entwickler",
"footerCommunity": "Community",
"footerCompany": "Unternehmen",
"footerMobile": "Mobil",
"footerSocial": "Soziales",
"forgotPass": "Passwort vergessen",
- "frabjabulousQuote": "[Habitica] ist der Grund, warum ich einen tollen, gut bezahlten Job gefunden habe ... und noch erstaunlicher ist, dass ich jetzt täglich Zahnseide benutze!",
+ "frabjabulousQuote": "[Habitica] ist der Grund, warum ich einen tollen, gut bezahlten Job gefunden habe... und noch erstaunlicher ist, dass ich jetzt täglich Zahnseide benutze!",
"free": "Kostenlos spielen",
"gamifyButton": "Gestalte Dein Leben spielerisch!",
"goalSample1": "1 Stunde Klavier üben",
@@ -73,7 +75,7 @@
"healthSample5": "Für 1 Stunde ins Schwitzen kommen",
"history": "Verlauf",
"infhQuote": "[Habitica] hat mir geholfen, Struktur in mein Leben an der Universität zu bringen.",
- "invalidEmail": "Um das Passwort zurückzusetzen, ist eine gültige Email-Adresse notwendig.",
+ "invalidEmail": "Um das Passwort zurückzusetzen, ist eine gültige E-Mail-Adresse notwendig.",
"irishfeet123Quote": "Ich hatte die schrecklichen Angewohnheiten, nach Mahlzeiten nicht aufzuräumen und Tassen in der ganzen Wohnung stehen zu lassen. [Habitica] hat mich davon geheilt!",
"joinOthers": "Schließe Dich <%= userCount %> Leuten an, die Spaß dabei haben, ihre Ziele zu erreichen!",
"kazuiQuote": "Vor [Habitica] kam ich mit meiner Dissertation nicht weiter und war unzufrieden mit meiner Selbstdisziplin bei Hausarbeiten, Vokabellernen und dem Studium der Go-Theorie. Es hat sich herausgestellt, dass das Aufteilen der Aufgaben in kleinere, machbare Checklisten etwas ist, das mich motiviert und zum konstanten Arbeiten anregt.",
@@ -83,20 +85,20 @@
"landingend3": ". Sucht Ihr eine nichtöffentliche Variante? Versucht unsere",
"landingend4": "die ideal sind für Familien, Lehrer, Selbsthilfegruppen und Gewerbe.",
"landingfeatureslink": "unserer Features",
- "landingp1": "Das Problem der meisten Produktivitätsapps auf dem Markt ist, dass sie keinen Anreiz bieten, sie dauerhaft zu benutzen. Habitica löst dieses Problem, indem es das Aufbauen von Gewohnheiten zum Spiel macht. Indem es Erfolge belohnt und Misserfolge bestraft, bietet Habitica eine Motivation für Ihre täglichen Aktivitäten.",
- "landingp2": "Jedes Mal, wenn Du eine gute Angewohnheit trainierst, eine tägliche Aufgabe erfüllst oder Dich um eine alte Aufgabe kümmerst, belohnt Dich Habitica sofort mit Erfahrungspunkten und Gold. Durch Erfahrungspunkte steigst Du im Level auf, verbesserst Deine Charakterwerte und schaltest weitere Features wie Klassen und Haustiere frei. Gold kann für Spielgegenstände, die Deinem Charakter nützen, ausgegeben werden oder für persönliche Belohnungen, die Du zur Motivation erstellen kannst. Wenn dir sogar der kleinste Erfolg eine sofortige Belohnung verspricht, wirst Du Deine Aufgaben immer weniger aufschieben.",
+ "landingp1": "Das Problem der meisten Produktivitäts-Apps auf dem Markt ist, dass sie keinen Anreiz bieten, sie dauerhaft zu benutzen. Habitica löst dieses Problem, indem es das Aufbauen von Gewohnheiten zum Spiel macht. Durch Belohnen von Erfolgen und Bestrafen von Misserfolgen, bietet Habitica eine Motivation für Ihre täglichen Aktivitäten.",
+ "landingp2": "Jedes Mal, wenn Du eine gute Angewohnheit trainierst, eine tägliche Aufgabe erfüllst oder Dich um ein altes To-Do kümmerst, belohnt Dich Habitica sofort mit Erfahrungspunkten und Gold. Durch Erfahrungspunkte steigst Du im Level auf, verbesserst Deine Charakterwerte und schaltest weitere Features wie Klassen und Haustiere frei. Gold kann für Spielgegenstände, die Deinem Charakter nützen, ausgegeben werden oder für persönliche Belohnungen, die Du zur Motivation erstellen kannst. Wenn Dir sogar der kleinste Erfolg eine sofortige Belohnung verspricht, wirst Du Deine Aufgaben immer weniger aufschieben.",
"landingp2header": "Sofortige Belohnung",
"landingp3": "Jedes Mal, wenn Du einer schlechten Angewohnheit nachgibst oder Deine täglichen Aufgaben vernachlässigst, verlierst Du Lebenspunkte. Wenn Deine Lebenspunkte zu weit sinken, stirbst Du und verlierst einen Teil Deine Fortschritts. Indem es Konsequenzen setzt, kann Habitica dabei helfen, schlechte Angewohnheiten und ständiges Hinausschieben zu beenden, bevor sie zu Problemen in Deinem Leben werden.",
"landingp3header": "Konsequenzen",
- "landingp4": "Mit einer lebendigen Community bietet Habitica die Verbindlichkeit, die Du brauchst, um auf Deine Aufgaben konzentriert zu bleiben. Mit dem Gruppensystem kannst Du eine Gruppe Deiner besten Freunde zur Unterstützung rufen. Das Gildensystem erlaubt es dir, Spieler mit ähnlichen Interessen oder Hindernissen zu finden, damit ihr eure Ziele gemeinsam erreichen und Tipps, um eure Probleme anzugehen, austauschen könnt. Auf Habitica steht die Community für die Unterstützung und die Verbindlichkeit, die Du brauchst, um Erfolg zu haben.",
+ "landingp4": "Mit einer lebendigen Community bietet Habitica die Verbindlichkeit, die Du brauchst, um auf Deine Aufgaben konzentriert zu bleiben. Mit dem Gruppensystem kannst Du eine Gruppe Deiner besten Freunde zur Unterstützung rufen. Das Gildensystem erlaubt es Dir, Spieler mit ähnlichen Interessen oder Hindernissen zu finden, damit ihr eure Ziele gemeinsam erreichen und Tipps, um eure Probleme anzugehen, austauschen könnt. Auf Habitica steht die Community für die Unterstützung und die Verbindlichkeit, die Du brauchst, um Erfolg zu haben.",
"landingp4header": "Verantwortung",
- "leadText": "Habitica ist eine kostenlose Anwendung zur Gewohnheitsbildung und Steigerung der Produktivität, die Dein Leben wie ein Spiel behandelt. Mit Belohnungen und Bestrafungen als Motivation und einem starken sozialen Netzwerk als Inspiration kann Habitica dir helfen, Deine Ziele zu erreichen und gesund, fleißig und glücklich zu werden.",
+ "leadText": "Habitica ist eine kostenlose Anwendung zur Gewohnheitsbildung und Steigerung der Produktivität, die Dein Leben wie ein Spiel behandelt. Mit Belohnungen und Bestrafungen als Motivation und einem starken sozialen Netzwerk als Inspiration kann Habitica Dir helfen, Deine Ziele zu erreichen und gesund, fleißig und glücklich zu werden.",
"login": "Einloggen",
"loginAndReg": "Einloggen / Registrieren",
"loginFacebookAlt": "Einloggen / Registrieren mit Facebook",
"logout": "Ausloggen",
"marketing1Header": "Verbessern Sie Ihre Lebensführung Durch Ein Spiel",
- "marketing1Lead1": "Habitica ist ein Videospiel, welches dir dabei hilft. Deine Gewohnheiten im realen Leben zu verbessern. Es \"gamifiziert\" Dein Leben, indem es all Deine Aufgaben (Gewohnheiten, tägliche Aufgaben und einmalige Aufgaben) in kleine Monster verwandelt, die Du besiegen musst. Je besser Du Dich dabei anstellst, umso weiter kommst Du im Spiel. Wenn Du in Deinem realen Leben nachlässt, beginnt Dein Charakter im Spiel zurückzufallen.",
+ "marketing1Lead1": "Habitica ist ein Videospiel, welches Dir dabei hilft. Deine Gewohnheiten im realen Leben zu verbessern. Es \"gamifiziert\" Dein Leben, indem es all Deine Aufgaben (Gewohnheiten, tägliche Aufgaben und To-Dos) in kleine Monster verwandelt, die Du besiegen musst. Je besser Du Dich dabei anstellst, umso weiter kommst Du im Spiel. Wenn Du in Deinem realen Leben nachlässt, beginnt Dein Charakter im Spiel zurückzufallen.",
"marketing1Lead2": "Bekomme coole Ausrüstung. Verbessere Deine Gewohnheiten um Deinen Avatar auszustatten. Zeige Deine coole Ausrüstung die Du verdient hast.",
"marketing1Lead2Title": "Bekomme coole Ausrüstung",
"marketing1Lead3": "Finde zufällige Preise. Für einige ist es das Glücksspiel bzw. das System der \"zufälligen Belohnung\", welches sie motiviert. Habitica beinhaltet alle Verstärkungstypen: positive, negative, vorhersehbare und zufällige.",
@@ -108,14 +110,14 @@
"marketing2Lead3": "In Wettbewerben kannst Du gegen Freunde und Unbekannte antreten. Wer am besten ist, gewinnt am Ende eines Wettbewerbs spezielle Preise.",
"marketing3Header": "Apps",
"marketing3Lead1": "Die iPhone & Android Apps erlauben Dir Aufgaben unterwegs abzuhaken. Es ist uns klar, dass es lästig sein kann, sich immer auf der Seite einloggen zu müssen.",
- "marketing3Lead2": "Andere Werkzeuge von Drittanbietern binden Habitica an verschiedene Aspekte Deines Lebens an. Unsere API stellt eine einfache Integration mit Sachen wie der Chrome-Erweiterung zur Verfügung, die dir Punkte abzieht, wenn Du auf unproduktiven Websites surfst und dir welche einbringt, wenn Du auf produktiven Seiten unterwegs bist. Mehr dazu hier",
+ "marketing3Lead2": "Andere Werkzeuge von Drittanbietern binden Habitica an verschiedene Aspekte Deines Lebens an. Unsere API stellt eine einfache Integration mit Sachen wie der Chrome-Erweiterung zur Verfügung, die Dir Punkte abzieht, wenn Du auf unproduktiven Websites surfst und Dir welche einbringt, wenn Du auf produktiven Seiten unterwegs bist. Mehr dazu hier",
"marketing4Header": "Verwendung zur Organisation",
"marketing4Lead1": "Bildung ist einer der besten Bereiche für Gamifizierung. Wir wissen alle, wie sehr Schüler heutzutage an ihren Handys und Spielen hängen. Nutze diese Macht! Lass Deine Schüler in freundlichen Wettbewerben gegeneinander antreten und belohne gutes Verhalten mit seltenen Preisen. Beobachte, wie sich ihre Noten und ihr Verhalten verbessern.",
"marketing4Lead1Title": "Betrachtungwinkel Ausbildung",
"marketing4Lead2": "Die Kosten für medizinische Versorgung steigen und irgendjemand muss sie tragen. Zahlreiche Pläne wurden entwickelt, um Kosten zu reduzieren und das Wohlbefinden zu verbessern. Wir glauben, dass Habitica einen wesentlichen Beitrag zu gesünderen Lebensstilen leisten kann.",
"marketing4Lead2Title": "Betrachtungswinkel Gesundheit und Erholung",
"marketing4Lead3-1": "Wollen Sie ihr Leben einmal als Spiel betrachten?",
- "marketing4Lead3-2": "Wollen Sie eine Gruppe für Ausbildung, Wohlbefinden etc. leiten?",
+ "marketing4Lead3-2": "Wollen Sie eine Gruppe für Ausbildung, Wohlbefinden usw. leiten?",
"marketing4Lead3-3": "Wollen Sie mehr wissen?",
"marketing4Lead3Title": "Mache Alles zum Spiel",
"mobileAndroid": "Android",
@@ -166,7 +168,7 @@
"terms": "AGB",
"testimonialHeading": "Was andere sagen…",
"localStorageTryFirst": "Wenn Du Probleme mit Habitica hast, drücke den Knopf unter \"Lokale Daten löschen\" (andere Internetseiten sind davon nicht betroffen). Du wirst Dich einmal neu einloggen müssen, damit das funktioniert, also halte Deine Logindaten bereit. Du kannst sie unter Einstellungen -> <%= linkStart %>Seite<%= linkEnd %> einsehen.",
- "localStorageTryNext": "Falls das Problem weiterhin besteht, <%= linkStart %>melde bitte einen Fehler<%= linkEnd %>, falls Du das noch nicht getan hast.",
+ "localStorageTryNext": "Wenn das Problem weiterhin besteht, <%= linkStart %>melde bitte einen Fehler<%= linkEnd %>, falls Du das noch nicht getan hast.",
"localStorageClearing": "Lokale Daten werden gelöscht",
"localStorageClearingExplanation": "Die lokalen Daten Deines Browsers wurden gelöscht. Du wirst nun ausgeloggt und zur Startseite weitergeleitet. Bitte warten.",
"localStorageClear": "Lokale Daten löschen",
@@ -175,21 +177,22 @@
"unlockByline1": "Erreiche Deine Ziele und steige Level auf.",
"unlockByline2": "Schalte neue, motivierende Werkzeuge frei, wie zum Beispiel Haustiere, zufällige Belohnungen, Zaubersprüche und mehr!",
"unlockHeadline": "Je mehr Du tust, desto mehr neue Inhalte kannst Du freigeschalten!",
- "useUUID": "Benutze UUID / API Token (Für Facebook Benutzer)",
+ "useUUID": "Benutze UUID / API Token (Für Facebook-Benutzer)",
"username": "Benutzername",
"watchVideos": "Sehen Sie sich die Videos an",
"work": "Arbeit",
"zelahQuote": "Mit [Habitica] kann ich mich davon überzeugen, rechtzeitig ins Bett zu gehen, denn wenn ich früh ins Bett gehe, verdiene ich Punkte und wenn ich zu lange aufbleibe, verliere ich Leben.",
"reportAccountProblems": "Melde Probleme mit Deinem Konto",
"reportCommunityIssues": "Melde Community-Probleme",
+ "subscriptionPaymentIssues": "Abonnement- und Zahlungsschwierigkeiten",
"generalQuestionsSite": "Generelle Fragen über die Webseite.",
"businessInquiries": "Geschäftsanfragen.",
"merchandiseInquiries": "Anfragen zu Handelswaren und Vermarktung",
"marketingInquiries": "Marketing-/Soziale Netzwerke Anfragen",
"tweet": "Tweet",
"apps": "Apps",
- "checkOutMobileApps": "Schau dir unsere Apps an!",
- "imagine1": "Stell dir vor, Dein Leben zu verbessern wäre so unterhaltsam, wie ein Spiel zu spielen.",
+ "checkOutMobileApps": "Schau Dir unsere Apps an!",
+ "imagine1": "Stell Dir vor, Dein Leben zu verbessern wäre so unterhaltsam, wie ein Spiel zu spielen.",
"landingCopy1": "Mache Fortschritte im Spiel, indem Du Deine Aufgaben im echten Leben erledigst.",
"landingCopy2": "Wenn Du mit Freunden Monster bekämpfst, bleibst Du für das Erreichen Deiner Ziele verantwortlich.",
"landingCopy3": "Schließe Dich <%= userCount %> Leuten an, die Spaß dabei haben, ihr Leben zu verbessern.",
@@ -197,7 +200,7 @@
"getStartedNow": "Jetzt loslegen!",
"altAttrNavLogo": "Habitica-Startseite",
"altAttrLifehacker": "Lifehacker",
- "altAttrNewYorkTimes": "The New York Times",
+ "altAttrNewYorkTimes": "Die New York Times",
"altAttrMakeUseOf": "MakeUseOf",
"altAttrForbes": "Forbes",
"altAttrCnet": "CNet",
@@ -222,5 +225,32 @@
"altAttrWebstorm": "WebStorm",
"altAttrGithub": "GitHub",
"altAttrTrello": "Trello",
- "altAttrSlack": "Slack"
+ "altAttrSlack": "Slack",
+ "missingAuthHeaders": "Authentifizierungsheader fehlen.",
+ "missingAuthParams": "Authentifizierungsparameter fehlen.",
+ "missingUsernameEmail": "Fehlender Benutzername oder E-Mail.",
+ "missingEmail": "Fehlende E-Mail.",
+ "missingUsername": "Fehlender Benutzername.",
+ "missingPassword": "Fehlendes Passwort.",
+ "missingNewPassword": "Fehlendes neues Passwort.",
+ "wrongPassword": "Falsches Passwort.",
+ "notAnEmail": "Ungültige E-Mail-Adresse.",
+ "emailTaken": "Diese E-Mail-Adresse wird bereits von einem Konto verwendet.",
+ "newEmailRequired": "Fehlender neuer Benutzername.",
+ "usernameTaken": "Benutzername schon vergeben.",
+ "passwordConfirmationMatch": "Die Passwörter stimmen nicht überein.",
+ "invalidLoginCredentials": "Falscher Benutzername und/oder falsches Passwort.",
+ "passwordReset": "Wenn wir Deine E-Mail-Adresse kennen, wurde Dein Passwort-Wiederherstellungs-Link dorthin verschickt.",
+ "passwordResetEmailSubject": "Passwort Reset für Habitica",
+ "passwordResetEmailText": "Das Passwort für <%= username %> wurde zurückgesetzt auf <%= newPassword %> . Wichtig! Benutzername und Passwort berücksichtigen beide gross-klein-Schreibung -- Du must beides exakt so eingeben wie hier angegeben. Wir empfehlen copy-paste zu benutzen, statt beides einzutippen. Hier einloggen: <%= baseUrl %>. Nachdem Du Dich eingeloggt hast, gehe zu <%= baseUrl %>/#/options/settings/settings um Dein Passwort zu ändern.",
+ "passwordResetEmailHtml": "Das Passwort für <%= username %> wurde zurückgesetzt auf <%= newPassword %>.
Wichtig! Benutzername und Passwort berücksichtigen beide Gross-klein-Schreibung -- Du must beides exakt so eingeben wie hier angegeben. Wir empfehlen copy-paste zu benutzen, statt beides einzutippen.
Hier einloggen: <%= baseUrl %>. Nachdem Du Dich eingeloggt hast, gehe zu <%= baseUrl %>/#/options/settings/settings um Dein Passwort zu ändern.",
+ "invalidLoginCredentialsLong": "Oh-oh - Dein Benutzername oder Passwort ist nicht korrekt.\n- überprüfe die korrekte Schreibweise Deines Benutzernamens oder Deiner E-Mailadresse.\n-Es ist möglich, dass Du Dich mit Facebook registriert statt mit Deiner E-Mail. Probier mit Deinem Facebook-Login anzumelden.\n- Wenn Du Dein Passwort vergessen hast, klicke auf \"Passwort vergessen.\"",
+ "invalidCredentials": "Es gibt kein Konto, das diese Anmeldedaten verwendet.",
+ "accountSuspended": "Dein Konto wurde suspendiert, bitte kontaktiere leslie@habitica.com mit Deiner User ID \"<%= userId %>\" für Hifle.",
+ "onlyFbSupported": "Im Moment wird nur Facebook unterstützt.",
+ "cantDetachFb": "Ohne eine andere Anmelde-Methode kann Facebook nicht von diesem Konto gelöst werden.",
+ "onlySocialAttachLocal": "Lokale Authentifizierung kann nur zu einem Social-Media-Konto hinzugefügt werden.",
+ "invalidReqParams": "Ungültige Requestparameter.",
+ "memberIdRequired": "\"member\" muss eine gültige UUID sein.",
+ "heroIdRequired": "\"herold\" muss eine gültige UUID sein."
}
\ No newline at end of file
diff --git a/common/locales/de/gear.json b/common/locales/de/gear.json
index dc5e743c72..e3c71e3fdb 100644
--- a/common/locales/de/gear.json
+++ b/common/locales/de/gear.json
@@ -30,7 +30,7 @@
"weaponRogue5Text": "Ninjato",
"weaponRogue5Notes": "Filigran und tödlich wie die Ninjas selbst. Erhöht Stärke um <%= str %>.",
"weaponRogue6Text": "Hakenschwert",
- "weaponRogue6Notes": "Diese Waffe ist schwer zu meistern aber ideal um Gegner zu entwaffnen und zu behindern. Erhöht Stärke um <%= str %>.",
+ "weaponRogue6Notes": "Diese Waffe ist schwer zu meistern, aber ideal um Gegner zu entwaffnen und zu behindern. Erhöht Stärke um <%= str %>.",
"weaponWizard0Text": "Lehrlingsstab",
"weaponWizard0Notes": "Übungsstab. Gewährt keinen Attributbonus.",
"weaponWizard1Text": "Holzstab",
@@ -69,88 +69,88 @@
"weaponSpecial3Notes": "Versammlungen, Monster, Leiden: geschafft! Zerstampft! Erhöht Stärke, Intelligenz und Ausdauer um jeweils <%= attrs %>.",
"weaponSpecialCriticalText": "Bedrohlicher Hammer der Bug-Vernichtung",
"weaponSpecialCriticalNotes": "Dieser Meisterkämpfer schlachtete ein bösartiges Github-Monster, dem bereits viele Krieger erlagen. Dieser Hammer, der aus den Knochen von Bug gefertigt ist, teilt mächtige, todbringende Hiebe aus. Erhöht Stärke und Wahrnehmung um jeweils <%= attrs %>.",
- "weaponSpecialTridentOfCrashingTidesText": "Dreizack der Brechenden Gezeiten",
- "weaponSpecialTridentOfCrashingTidesNotes": "Gibt dir die Fähigkeit Fische zu befehligen und Deine Aufgaben mit kraftvollen Stichen zu attackieren. Erhöht Intelligenz um <%= int %>.",
+ "weaponSpecialTridentOfCrashingTidesText": "Dreizack der brechenden Gezeiten",
+ "weaponSpecialTridentOfCrashingTidesNotes": "Gibt Dir die Fähigkeit Fische zu befehligen und Deine Aufgaben mit kraftvollen Stichen zu attackieren. Erhöht Intelligenz um <%= int %>.",
"weaponSpecialYetiText": "Speer des Yeti-Zähmers",
- "weaponSpecialYetiNotes": "Dieser Speer erlaubt dem Träger, jeden Yeti zu bändigen. Erhöht Stärke um <%= str %>. Limited Edition 2013-2014 Winterausrüstung.",
+ "weaponSpecialYetiNotes": "Dieser Speer erlaubt dem Träger, jeden Yeti zu bändigen. Erhöht Stärke um <%= str %>. Begrenzte Auflage 2013-2014, Winterausrüstung.",
"weaponSpecialSkiText": "Schaft des Ski-ssassinen",
- "weaponSpecialSkiNotes": "Zermalmt ganze Horden von Gegnern! Außerdem hilft es den Träger dabei, schöne Parallelschwünge zu fahren. Erhöht Stärke um <%= str %>. Limited Edition 2013-2014 Winterausrüstung.",
+ "weaponSpecialSkiNotes": "Zermalmt ganze Horden von Gegnern! Außerdem hilft es den Träger dabei, schöne Parallelschwünge zu fahren. Erhöht Stärke um <%= str %>. Begrenzte Auflage 2013-2014, Winterausrüstung.",
"weaponSpecialCandycaneText": "Zuckerstangenstab",
- "weaponSpecialCandycaneNotes": "Ein mächtiger Zauberstab. Mächtig LECKER, wollten wir sagen! Zweihändige Waffe. Erhöht Intelligenz um <%= int %> und Wahrnehmung um <%= per %>. Limited Edition 2013-2014 Winterausrüstung.",
+ "weaponSpecialCandycaneNotes": "Ein mächtiger Zauberstab. Mächtig LECKER, wollten wir sagen! Zweihändige Waffe. Erhöht Intelligenz um <%= int %> und Wahrnehmung um <%= per %>. Begrenzte Auflage 2013-2014, Winterausrüstung.",
"weaponSpecialSnowflakeText": "Zauberstab der Schneeflocke",
- "weaponSpecialSnowflakeNotes": "Dieser Zauberstab funkelt vor unerschöpflicher Heilkraft. Erhöht Intelligenz um <%= int %>. Limited Edition 2013-2014 Winter-Set.",
+ "weaponSpecialSnowflakeNotes": "Dieser Zauberstab funkelt vor unerschöpflicher Heilkraft. Erhöht Intelligenz um <%= int %>. Begrenzte Auflage 2013-2014, Winterausrüstung.",
"weaponSpecialSpringRogueText": "Hakenkrallen",
- "weaponSpecialSpringRogueNotes": "Sehr nützlich um hohe Gebäude zu erklimmen und ebenso um Teppiche zu zerfetzen. Erhöht Stärke um <%= str %>. Limited Edition 2014 Frühlings-Set.",
+ "weaponSpecialSpringRogueNotes": "Sehr nützlich um hohe Gebäude zu erklimmen und ebenso um Teppiche zu zerfetzen. Erhöht Stärke um <%= str %>. Begrenzte Auflage 2014, Frühlingsausrüstung.",
"weaponSpecialSpringWarriorText": "Karottenschwert",
- "weaponSpecialSpringWarriorNotes": "Mit diesem mächtigen Schwert werden Gegner mit Leichtigkeit zerstückelt, oder der Hunger für zwischendurch gestillt. Erhöht Stärke um <%= str %>. Limited Edition 2014 Frühlings-Set.",
+ "weaponSpecialSpringWarriorNotes": "Mit diesem mächtigen Schwert werden Gegner mit Leichtigkeit zerstückelt, oder der Hunger für zwischendurch gestillt. Erhöht Stärke um <%= str %>. Begrenzte Auflage 2014, Frühlingsausrüstung.",
"weaponSpecialSpringMageText": "Schweizer Käsestab",
- "weaponSpecialSpringMageNotes": "Nur die tapfersten aller Nagetiere können ihren Hunger bezwingen um diesen mächtigen Zauberstab zu verwenden. Erhöht Intelligenz um <%= int %> und Wahrnehmung um <%= per %>. Limited Edition 2014 Frühlings-Set.",
+ "weaponSpecialSpringMageNotes": "Nur die tapfersten aller Nagetiere können ihren Hunger bezwingen um diesen mächtigen Zauberstab zu verwenden. Erhöht Intelligenz um <%= int %> und Wahrnehmung um <%= per %>. Begrenzte Auflage 2014, Frühlingsausrüstung.",
"weaponSpecialSpringHealerText": "Wurfknochen",
- "weaponSpecialSpringHealerNotes": "Hol' Stöckchen! Erhöht Intelligenz um <%= int %>. Limited Edition 2014 Frühlingsausrüstung.",
+ "weaponSpecialSpringHealerNotes": "Hol' Stöckchen! Erhöht Intelligenz um <%= int %>. Begrenzte Auflage 2014, Frühlingsausrüstung.",
"weaponSpecialSummerRogueText": "Piratensäbel",
- "weaponSpecialSummerRogueNotes": "Genug! Du wirst diese täglichen Aufgaben über die Planke gehen lassen! Erhöht Stärke um <%= str %>. Limited Edition 2014 Sommerausrüstung.",
+ "weaponSpecialSummerRogueNotes": "Genug! Du wirst diese täglichen Aufgaben über die Planke gehen lassen! Erhöht Stärke um <%= str %>. Begrenzte Auflage 2014, Sommerausrüstung.",
"weaponSpecialSummerWarriorText": "Seefahrermesser",
- "weaponSpecialSummerWarriorNotes": "Es gibt keine Aufgabe auf irgendeiner To-Do-Liste die es wagen würde, sich mit diesem rauen Messer anzulegen! Erhöht Stärke um <%= str %>. Limited Edition 2014 Sommerausrüstung.",
+ "weaponSpecialSummerWarriorNotes": "Es gibt keine Aufgabe auf irgendeiner To-Do-Liste die es wagen würde, sich mit diesem rauen Messer anzulegen! Erhöht Stärke um <%= str %>. Begrenzte Auflage 2014, Sommerausrüstung.",
"weaponSpecialSummerMageText": "Algenfänger",
- "weaponSpecialSummerMageNotes": "Dieser Dreizack wird benutzt, um Algen effektiv aufzuspießen, für besonders ergiebiges Algenernten! Erhöht Intelligenz um <%= int %> und Wahrnehmung um <%= per %>. Limited Edition 2014 Sommerausrüstung.",
+ "weaponSpecialSummerMageNotes": "Dieser Dreizack wird benutzt, um Algen effektiv aufzuspießen, für besonders ergiebiges Algenernten! Erhöht Intelligenz um <%= int %> und Wahrnehmung um <%= per %>. Begrenzte Auflage 2014, Sommerausrüstung.",
"weaponSpecialSummerHealerText": "Zauberstab der Untiefen",
- "weaponSpecialSummerHealerNotes": "Dieser Zauberstab, gefertigt aus Aquamarin und lebendigen Korallen, ist sehr anziehend für Fischschwärme. Erhöht Intelligenz um <%= int %>. Limited Edition 2014 Sommerausrüstung.",
+ "weaponSpecialSummerHealerNotes": "Dieser Zauberstab, gefertigt aus Aquamarin und lebendigen Korallen, ist sehr anziehend für Fischschwärme. Erhöht Intelligenz um <%= int %>. Begrenzte Auflage 2014, Sommerausrüstung.",
"weaponSpecialFallRogueText": "Silberner Pflock",
- "weaponSpecialFallRogueNotes": "Befördert Untote dauerhaft ins Jenseits. Notfalls auch gegen Werwölfe einsetzbar - Vielseitigkeit kann nie schaden. Erhöht Stärke um <%= str %>. Limited Edition 2014 Herbstausrüstung.",
+ "weaponSpecialFallRogueNotes": "Befördert Untote dauerhaft ins Jenseits. Notfalls auch gegen Werwölfe einsetzbar - Vielseitigkeit kann nie schaden. Erhöht Stärke um <%= str %>. Begrenzte Auflage 2014, Herbstausrüstung.",
"weaponSpecialFallWarriorText": "Greifarm der Wissenschaft",
- "weaponSpecialFallWarriorNotes": "Es gibt keine Aufgabe auf irgendeiner To-Do-Liste die es wagen würde, sich mit diesem rauen Messer anzulegen! Erhöht Stärke um <%= str %>. Limited Edition 2014 Sommerausrüstung.",
+ "weaponSpecialFallWarriorNotes": "Es gibt keine Aufgabe auf irgendeiner To-Do-Liste die es wagen würde, sich mit diesem rauen Messer anzulegen! Erhöht Stärke um <%= str %>. Begrenzte Auflage 2014, Sommerausrüstung.",
"weaponSpecialFallMageText": "Fliegender Besen",
- "weaponSpecialFallMageNotes": "Dieser fliegende Besen ist schneller als ein Drache! Erhöht Intelligenz um <%= int %> und Wahrnehmung um <%= per %>. Limited Edition 2014 Herbstausrüstung.",
+ "weaponSpecialFallMageNotes": "Dieser fliegende Besen ist schneller als ein Drache! Erhöht Intelligenz um <%= int %> und Wahrnehmung um <%= per %>. Begrenzte Auflage 2014, Herbstausrüstung.",
"weaponSpecialFallHealerText": "Skarabäus Zauberstab",
- "weaponSpecialFallHealerNotes": "Der Skarabäus auf diesem Stab schützt und heilt den Besitzer. Erhöht Intelligenz um <%= int %>. Limited Edition 2014 Herbstausrüstung.",
+ "weaponSpecialFallHealerNotes": "Der Skarabäus auf diesem Stab schützt und heilt den Besitzer. Erhöht Intelligenz um <%= int %>. Begrenzte Auflage 2014, Herbstausrüstung.",
"weaponSpecialWinter2015RogueText": "Eiszapfen",
- "weaponSpecialWinter2015RogueNotes": "Du hast Sie wirklich, wahrhaftig und ungelogen gerade vom Boden aufgelesen. Erhöht Stärke um <%= str %>. Limited Edition 2014-2015 Winterausrüstung.",
+ "weaponSpecialWinter2015RogueNotes": "Du hast Sie wirklich, wahrhaftig und ungelogen gerade vom Boden aufgelesen. Erhöht Stärke um <%= str %>. Begrenzte Auflage 2014-2015, Winterausrüstung.",
"weaponSpecialWinter2015WarriorText": "Gummibonbonschwert",
- "weaponSpecialWinter2015WarriorNotes": "Dieses leckere Schwert lockt wahrscheinlich Monster an ... aber Du bist der Herausforderung gewachsen! Erhöht Stärke um <%= str %>. Limited Edition 2014-2015 Winterausrüstung.",
+ "weaponSpecialWinter2015WarriorNotes": "Dieses leckere Schwert lockt wahrscheinlich Monster an ... aber Du bist der Herausforderung gewachsen! Erhöht Stärke um <%= str %>. Begrenzte Auflage 2014-2015, Winterausrüstung.",
"weaponSpecialWinter2015MageText": "Stab des Winterleuchtens",
- "weaponSpecialWinter2015MageNotes": "Das Licht dieses Kristallstabs füllt die Herzen mit Freude. Erhöht Intelligenz um <%= int %> und Wahrnehmung um <%= per %>. Limited Edition 2014-2015 Winterausrüstung.",
+ "weaponSpecialWinter2015MageNotes": "Das Licht dieses Kristallstabs füllt die Herzen mit Freude. Erhöht Intelligenz um <%= int %> und Wahrnehmung um <%= per %>. Begrenzte Auflage 2014-2015, Winterausrüstung.",
"weaponSpecialWinter2015HealerText": "Beruhigendes Zepter",
- "weaponSpecialWinter2015HealerNotes": "Dieses Zepter wärmt schmerzende Muskeln und lindert Stress. Erhöht Intelligenz um <%= int %>. Limited Edition 2014-2015 Winterausrüstung.",
+ "weaponSpecialWinter2015HealerNotes": "Dieses Zepter wärmt schmerzende Muskeln und lindert Stress. Erhöht Intelligenz um <%= int %>. Begrenzte Auflage 2014-2015, Winterausrüstung.",
"weaponSpecialSpring2015RogueText": "Explodierender Kreischer",
- "weaponSpecialSpring2015RogueNotes": "Lass Dich nicht vom Geräusch täuschen - dieser Sprengstoff hat wirklich Wumms. Erhöht Stärke um <%= str %>. Limited Edition 2015 Frühlingsausrüstung.",
+ "weaponSpecialSpring2015RogueNotes": "Lass Dich nicht vom Geräusch täuschen - dieser Sprengstoff hat wirklich Wumms. Erhöht Stärke um <%= str %>. Begrenzte Auflage 2015, Frühlingsausrüstung.",
"weaponSpecialSpring2015WarriorText": "Knochenkeule",
- "weaponSpecialSpring2015WarriorNotes": "Es ist eine echte, richtige Knochenkeule für echt kämpferische Hundis und es ist definitiv kein Kauspielzeug, das Du von der Jahreszeitenzauberin erhalten hast, denn wer ist ein guter Hund? Weeeeer ist ein guter Hund? Genau!!! Du bist ein guter Hund!!! Erhöht Stärke um <%= str %>. Limited Edition 2015 Frühlingsausrüstung.",
+ "weaponSpecialSpring2015WarriorNotes": "Es ist eine echte, richtige Knochenkeule für echt kämpferische Hundis und es ist definitiv kein Kauspielzeug, das Du von der Jahreszeitenzauberin erhalten hast, denn wer ist ein guter Hund? Weeeeer ist ein guter Hund? Genau!!! Du bist ein guter Hund!!! Erhöht Stärke um <%= str %>. Begrenzte Auflage 2015, Frühlingsausrüstung.",
"weaponSpecialSpring2015MageText": "Zauberstab eines Magiers",
- "weaponSpecialSpring2015MageNotes": "Beschwöre Dir eine Karotte mit diesem schicken Zauberstab. Erhöht Intelligenz um <%= int %> und Wahrnehmung um <%= per %>. Limited Edition 2015 Frühlingsausrüstung.",
+ "weaponSpecialSpring2015MageNotes": "Beschwöre Dir eine Karotte mit diesem schicken Zauberstab. Erhöht Intelligenz um <%= int %> und Wahrnehmung um <%= per %>. Begrenzte Auflage 2015, Frühlingsausrüstung.",
"weaponSpecialSpring2015HealerText": "Katzenrassel",
- "weaponSpecialSpring2015HealerNotes": "Wenn Du sie schüttelst macht sie ein faszinierendes Klimpergeräusch, was JEDEN über Stunden hinweg unterhalten würde. Erhöht Intelligenz um <%= int %>. Limited Edition 2015 Frühlingsausrüstung.",
+ "weaponSpecialSpring2015HealerNotes": "Wenn Du sie schüttelst macht sie ein faszinierendes Klimpergeräusch, was JEDEN über Stunden hinweg unterhalten würde. Erhöht Intelligenz um <%= int %>. Begrenzte Auflage 2015, Frühlingsausrüstung.",
"weaponSpecialSummer2015RogueText": "Feuernde Koralle",
- "weaponSpecialSummer2015RogueNotes": "Diese zur Art der Feuerkorallen gehörende Waffe hat die Fähigkeit ihr Gift durch Wasser hindurch wirken zu lassen. Erhöht Stärke um <%= str %>. Limited Edition 2015 Sommerausrüstung.",
+ "weaponSpecialSummer2015RogueNotes": "Diese zur Art der Feuerkorallen gehörende Waffe hat die Fähigkeit ihr Gift durch Wasser hindurch wirken zu lassen. Erhöht Stärke um <%= str %>. Begrenzte Auflage 2015, Sommerausrüstung.",
"weaponSpecialSummer2015WarriorText": "Sonnenschwertfisch",
- "weaponSpecialSummer2015WarriorNotes": "Der Sonnenschwertfisch ist eine gefürchtete Waffe, vorausgesetzt sie kann dazu gebracht werden nicht mehr herumzuzappeln. Erhöht Stärke um <%= str %>. Limited Edition 2015 Sommerausrüstung.",
+ "weaponSpecialSummer2015WarriorNotes": "Der Sonnenschwertfisch ist eine gefürchtete Waffe, vorausgesetzt sie kann dazu gebracht werden nicht mehr herumzuzappeln. Erhöht Stärke um <%= str %>. Begrenzte Auflage 2015, Sommerausrüstung.",
"weaponSpecialSummer2015MageText": "Wahrsagerstab",
- "weaponSpecialSummer2015MageNotes": "Versteckte Kräfte schimmern in den Juwelen dieses Stabs. Erhöht Intelligenz um <%= int %> und Wahrnehmung um <%= per %>. Limited Edition 2015 Sommerausrüstung.",
+ "weaponSpecialSummer2015MageNotes": "Versteckte Kräfte schimmern in den Juwelen dieses Stabs. Erhöht Intelligenz um <%= int %> und Wahrnehmung um <%= per %>. Begrenzte Auflage 2015, Sommerausrüstung.",
"weaponSpecialSummer2015HealerText": "Zauberstab der Wellen",
- "weaponSpecialSummer2015HealerNotes": "Heilt Seekrankheit und Reiseübelkeit! Erhöht Intelligenz um <%= int %>. Limited Edition 2015 Sommerausrüstung.",
+ "weaponSpecialSummer2015HealerNotes": "Heilt Seekrankheit und Reiseübelkeit! Erhöht Intelligenz um <%= int %>. Begrenzte Auflage 2015, Sommerausrüstung.",
"weaponSpecialFall2015RogueText": "Kampfaxt",
- "weaponSpecialFall2015RogueNotes": "Furchterregende tägliche Aufgaben ducken sich unter den Schlägen dieser Axt. Erhöht Stärke um <%= str %>. Limited Edition 2015 Herbstausrüstung.",
+ "weaponSpecialFall2015RogueNotes": "Furchterregende To-Dos ducken sich unter den Schlägen dieser Axt. Erhöht Stärke um <%= str %>. Begrenzte Auflage 2015, Herbstausrüstung.",
"weaponSpecialFall2015WarriorText": "Holzplanke",
- "weaponSpecialFall2015WarriorNotes": "Super um Dinge in Kornfeldern hochzuheben und/oder Aufgaben zu verprügeln. Erhöht die Stärke um <%=str%>. Begrenzte Auflage 2015 Herbstausrüstung",
+ "weaponSpecialFall2015WarriorNotes": "Super um Dinge in Kornfeldern hochzuheben und/oder Aufgaben zu verprügeln. Erhöht die Stärke um <%=str%>. Begrenzte Auflage 2015, Herbstausrüstung",
"weaponSpecialFall2015MageText": "Verzauberter Faden",
- "weaponSpecialFall2015MageNotes": "Eine starke Stichhexe kann dieses verzauberte Garn kontrollieren, ohne es zu berühren. Erhöht Intelligenz um <%= int %> und Wahrnehmung um <%= per %>. Limited Edition 2015 Herbstausrüstung.",
+ "weaponSpecialFall2015MageNotes": "Eine starke Stichhexe kann dieses verzauberte Garn kontrollieren, ohne es zu berühren. Erhöht Intelligenz um <%= int %> und Wahrnehmung um <%= per %>. Begrenzte Auflage 2015, Herbstausrüstung.",
"weaponSpecialFall2015HealerText": "Sumpfschleimtrank",
- "weaponSpecialFall2015HealerNotes": "Perfekt gebraut! Jetzt musst Du nur noch Dich selbst überzeugen ihn zu trinken. Erhöht Intelligenz um <%= int %>, Limited Edition 2015 Herbstausrüstung.",
+ "weaponSpecialFall2015HealerNotes": "Perfekt gebraut! Jetzt musst Du nur noch Dich selbst überzeugen ihn zu trinken. Erhöht Intelligenz um <%= int %>, Begrenzte Auflage 2015, Herbstausrüstung.",
"weaponSpecialWinter2016RogueText": "Kakaobecher",
- "weaponSpecialWinter2016RogueNotes": "Wärmendes Getränk oder kochendes Wurfgeschoss? Du entscheidest… Erhöht Stärke um <%= str %>. Limited Edition 2015-2016 Winterausrüstung.",
+ "weaponSpecialWinter2016RogueNotes": "Wärmendes Getränk oder kochendes Wurfgeschoss? Du entscheidest… Erhöht Stärke um <%= str %>. Begrenzte Auflage 2015-2016, Winterausrüstung.",
"weaponSpecialWinter2016WarriorText": "Stabile Schaufel",
- "weaponSpecialWinter2016WarriorNotes": "Schaufle überfällige Aufgaben aus dem Weg! Erhöht Stärke um <%= str %>. Limited Edition 2015-2016 Winterausrüstung.",
+ "weaponSpecialWinter2016WarriorNotes": "Schaufle überfällige Aufgaben aus dem Weg! Erhöht Stärke um <%= str %>. Begrenzte Auflage 2015-2016, Winterausrüstung.",
"weaponSpecialWinter2016MageText": "Magisches Snowboard",
- "weaponSpecialWinter2016MageNotes": "Deine Tricks sind so abgefahren, das muss Zauberei sein! Erhöht Intelligenz um <%= int %> und Wahrnehmung um <%= per %>. Limited Edition 2015-2016 Winterausrüstung",
+ "weaponSpecialWinter2016MageNotes": "Deine Tricks sind so abgefahren, das muss Zauberei sein! Erhöht Intelligenz um <%= int %> und Wahrnehmung um <%= per %>. Begrenzte Auflage 2015-2016, Winterausrüstung",
"weaponSpecialWinter2016HealerText": "Konfettikanone",
- "weaponSpecialWinter2016HealerNotes": "JUHUUUUU!!! FRÖHLICHES WINTER-WUNDERLAND!!!! Erhöht Intelligenz um <%= int %>. Limited Edition 2015-2016 Winterausrüstung.",
- "weaponSpecialSpring2016RogueText": "Feuer Bolas",
- "weaponSpecialSpring2016RogueNotes": "Du hast sowohl den Ball, die Keule, als auch das Messer gemeistert. Jetzt bist Du bereit mit Feuer zu jonglieren! Awoo! Erhöht Stärke um <%= str %>. Limitierte Edition 2016 Frühlingsausrüstung.",
+ "weaponSpecialWinter2016HealerNotes": "JUHUUUUU!!! FRÖHLICHES WINTER-WUNDERLAND!!!! Erhöht Intelligenz um <%= int %>. Begrenzte Auflage 2015-2016, Winterausrüstung.",
+ "weaponSpecialSpring2016RogueText": "Feuerbolas",
+ "weaponSpecialSpring2016RogueNotes": "Du hast sowohl den Ball, die Keule, als auch das Messer gemeistert. Jetzt bist Du bereit mit Feuer zu jonglieren! Awoo! Erhöht Stärke um <%= str %>. Begrenzte Auflage 2016, Frühlingsausrüstung.",
"weaponSpecialSpring2016WarriorText": "Käsehammer",
- "weaponSpecialSpring2016WarriorNotes": "Keiner hat so viele Freunde wie die Maus mit ihrem herzhaften Käse. Erhöht Stärke um <%= str %>. Limitierte Edition 2016 Frühlingsausrüstung.",
- "weaponSpecialSpring2016MageText": "Stab der Glocken",
- "weaponSpecialSpring2016MageNotes": "Abra-ka-dabra! So umwerfend, Du könntest Dich selbst Hypnotisieren! Ooh ... es klingelt ... Erhöht Intelligenz um <%= int %> und Wahrnehmung um <%= per %>. Limitierte Edition 2016 Frühlingsausrüstung.",
+ "weaponSpecialSpring2016WarriorNotes": "Keiner hat so viele Freunde wie die Maus mit ihrem herzhaften Käse. Erhöht Stärke um <%= str %>. Begrenzte Auflage 2016, Frühlingsausrüstung.",
+ "weaponSpecialSpring2016MageText": "Glockenstab",
+ "weaponSpecialSpring2016MageNotes": "Abra-katz-dabra! So umwerfend, Du könntest Dich selbst hypnotisieren! Ooh ... es klingelt ... Erhöht Intelligenz um <%= int %> und Wahrnehmung um <%= per %>. Limitierte Edition 2016 Frühlingsausrüstung.",
"weaponSpecialSpring2016HealerText": "Frühlingsblumen-Stab",
- "weaponSpecialSpring2016HealerNotes": "Mit einen winken und ein zwinkern, bringst Du die Felder und Wälder zum erblühen! Oder gib ärgerlichen Mäusen eins auf den Schädel. Erhöht Intelligenz um <%= int %>. Limitierte Edition 2016 Frühlingsausrüstung.",
+ "weaponSpecialSpring2016HealerNotes": "Mit einem Winken und einem Zwinkern, bringst Du die Felder und Wälder zum Erblühen! Oder gib ärgerlichen Mäusen eins auf den Schädel. Erhöht Intelligenz um <%= int %>. Begrenzte Auflage 2016, Frühlingsausrüstung.",
"weaponMystery201411Text": "Forke des Feierns",
"weaponMystery201411Notes": "Ersteche Deine Feinde oder verschling Dein Lieblingsessen - diese flexible Forke ist universell einsetzbar! Gewährt keinen Attributbonus. November 2014 Abonnentengegenstand.",
"weaponMystery201502Text": "Schimmernder Flügelstab der Liebe und auch der Wahrheit",
@@ -164,18 +164,18 @@
"weaponArmoireLunarSceptreText": "Besänftigendes Mondzepter",
"weaponArmoireLunarSceptreNotes": "Die heilende Kraft dieses Zauberstabs nimmt wie der Mond ab und zu. Erhöht Ausdauer um <%= con %> und Intelligenz um <%= int %>. Verzauberter Schrank: Beruhigendes Mondset (Gegenstand 3 von 3).",
"weaponArmoireRancherLassoText": "Viehzüchterlasso",
- "weaponArmoireRancherLassoNotes": "Lassos: das ideale Werkzeug zum Einfangen und Zäumen. Erhöht Stärke um <%= str %>, Wahrnehmung um <%= per %> und Intelligenz um <%= int %>. Verzauberter Schrank: Viehzüchter-Set (Gegenstand 3 von 3).",
+ "weaponArmoireRancherLassoNotes": "Lassos: das ideale Werkzeug zum Einfangen und Zäumen. Erhöht Stärke um <%= str %>, Wahrnehmung um <%= per %> und Intelligenz um <%= int %>. Verzauberter Schrank: Viehzüchterausrüstung (Gegenstand 3 von 3).",
"weaponArmoireMythmakerSwordText": "Sagenumwobenes Schwert",
"weaponArmoireMythmakerSwordNotes": "Obwohl es möglicherweise unbedeutend aussieht, hat dieses Schwert viele mystische Helden hervorgebracht. Erhöht Wahrnehmung und Stärke jeweils um <%= attrs %>. Verzauberter Schrank: Goldene-Toga-Set (Gegenstand 3 von 3).",
"weaponArmoireIronCrookText": "Eiserner Hirtenstab",
"weaponArmoireIronCrookNotes": "Dieser mit Leidenschaft gehämmerte eiserne Hirtenstab ist nützlich zum Schafe hüten. Erhöht Wahrnehmung und Stärke jeweils um <%= attrs %>. Verzauberter Schrank: Gehörntes Eisen-Set (Gegenstand 3 von 3)",
- "weaponArmoireGoldWingStaffText": "Goldener geflügelter Stab",
+ "weaponArmoireGoldWingStaffText": "Goldener Flügelstab",
"weaponArmoireGoldWingStaffNotes": "Die Flügel dieses Stabes flattern und drehen sich ständig. Erhöht alle Attribute um <%= attrs %>. Verzauberter Schrank: Unabhängiger Gegenstand.",
- "weaponArmoireBatWandText": "Fledermaus Zauberstab",
- "weaponArmoireBatWandNotes": "Dieser Zauberstab kann jede Aufgabe in eine Fledermaus verwandeln. Schwinge ihn und schau zu wie sie davonfliegen. Erhöht Intelligenz um <%= int %> und Wahrnehmung um <%= per %>. Verzauberter Schrank: Unabhängiger Gegenstand.",
+ "weaponArmoireBatWandText": "Fledermaus-Zauberstab",
+ "weaponArmoireBatWandNotes": "Dieser Zauberstab kann jede Aufgabe in eine Fledermaus verwandeln! Schwinge ihn und schau zu wie sie davonfliegen. Erhöht Intelligenz um <%= int %> und Wahrnehmung um <%= per %>. Verzauberter Schrank: Unabhängiger Gegenstand.",
"weaponArmoireShepherdsCrookText": "Hirtenstab",
"weaponArmoireShepherdsCrookNotes": "Nützlich um Greife zu hüten. Erhöht Ausdauer um <%= con %>. Verzauberter Schrank: Hirten-Set (Gegenstand 1 von 3)",
- "weaponArmoireCrystalCrescentStaffText": "Kristallener Mondsichelstab",
+ "weaponArmoireCrystalCrescentStaffText": "Kristalliner Mondsichelstab",
"weaponArmoireCrystalCrescentStaffNotes": "Beschwöre die Macht des Sichelmondes herbei mit diesem glänzenden Stab! Erhöht Intelligenz und Stärke jeweils um <%= attrs %>. Verzauberter Schrank: Kristallines Mondsichelset (Gegenstand 3 von 3)",
"weaponArmoireBlueLongbowText": "Blauer Langbogen",
"weaponArmoireBlueLongbowNotes": "Fertig ... Zielen ... Feuer! Dieser Bogen hat eine große Reichweite. Erhöht Wahrnehmung um <%= per %>, Ausdauer um <%= con %> und Stärke um <%= str %>. Verzauberter Schrank: Unabhängiger Gegenstand.",
@@ -187,8 +187,10 @@
"weaponArmoireJesterBatonNotes": "Durch ein Fuchteln mit Deinem Stab und einigen schlagfertigen Antworten, klären sich sogar die kompliziertesten Situationen. Erhöht Intelligenz und Wahrnehmung jeweils um <%= attrs %>. Verzauberter Schrank: Narrenset (Gegenstand 3 von 3).",
"weaponArmoireMiningPickaxText": "Spitzhacke",
"weaponArmoireMiningPickaxNotes": "Grabe das Maximum an Gold aus Deinen Aufgaben heraus! Erhöht Wahrnehmung um <%= per %>. Verzauberter Schrank: Bergmannsset (Gegenstand 3 von 3)",
- "weaponArmoireBasicLongbowText": "Basic Longbow",
- "weaponArmoireBasicLongbowNotes": "A serviceable hand-me-down bow. Increases Strength by <%= str %>. Enchanted Armoire: Basic Archer Set (Item 1 of 3).",
+ "weaponArmoireBasicLongbowText": "Einfacher Langbogen",
+ "weaponArmoireBasicLongbowNotes": "Ein nützlicher, gebrauchter Bogen. Erhöht Stärke um <%= str %>.\nVerzauberter Schrank: Standard-Bogenschützenset (Gegenstand 1 von 3).",
+ "weaponArmoireHabiticanDiplomaText": "Habiticaner-Diplom",
+ "weaponArmoireHabiticanDiplomaNotes": "Ein wohlverdientes Zertifikat -- gut gemacht! Erhöht Intelligenz um <&=int %>. Verzauberter Schrank: Doktoranden-Set (Gegenstand 1 von 3).",
"armor": "Rüstung",
"armorBase0Text": "Schlichte Kleidung",
"armorBase0Notes": "Gewöhnliches Kleidungsstück. Gewährt keinen Attributbonus.",
@@ -199,7 +201,7 @@
"armorWarrior3Text": "Plattenpanzer",
"armorWarrior3Notes": "Ein alles in Stahl einschließender Anzug, der Stolz eines jeden Ritters. Erhöht Ausdauer um <%= con %>.",
"armorWarrior4Text": "Glutrüstung",
- "armorWarrior4Notes": "Schwere Plattenrüstung, die durch Verteidigungszaubern glüht. Erhöht Ausdauer um <%= con %>.",
+ "armorWarrior4Notes": "Schwere Plattenrüstung, die durch Verteidigungszauber glüht. Erhöht Ausdauer um <%= con %>.",
"armorWarrior5Text": "Güldene Rüstung",
"armorWarrior5Notes": "Obwohl sie zeremoniell wirkt, gibt es keine Klinge, die sie durchstoßen kann. Erhöht Ausdauer um <%= con %>.",
"armorRogue1Text": "Geölte Lederrüstung",
@@ -239,95 +241,95 @@
"armorSpecial2Text": "Jean Chalard's edle Tunika",
"armorSpecial2Notes": "Macht Dich besonders flauschig! Erhöht Ausdauer und Intelligenz um jeweils <%= attrs %>.",
"armorSpecialFinnedOceanicArmorText": "Geschuppte Meeresrüstung",
- "armorSpecialFinnedOceanicArmorNotes": "Obwohl empfindlich, macht diese Rüstung Deine Haut bei Berührung so gefährlich wie Feuerkorallen. Erhöht die Stärke um <%= str %>.",
+ "armorSpecialFinnedOceanicArmorNotes": "Obwohl empfindlich, macht diese Rüstung Deine Haut bei Berührung so gefährlich wie Feuerkorallen. Erhöht Stärke um <%= str %>.",
"armorSpecialYetiText": "Robe des Yeti-Zähmers",
- "armorSpecialYetiNotes": "Flauschig und wild. Erhöht Ausdauer um <%= con %>. Limited Edition 2013-2014 Winterausrüstung.",
+ "armorSpecialYetiNotes": "Flauschig und wild. Erhöht Ausdauer um <%= con %>. Begrenzte Auflage 2013-2014, Winterausrüstung.",
"armorSpecialSkiText": "Parka des Ski-ssassinen",
- "armorSpecialSkiNotes": "Voller versteckter Dolche und Skipistenkarten. Erhöht Wahrnehmung um <%= per %>. Limited Edition 2013-2014 Winterausrüstung.",
+ "armorSpecialSkiNotes": "Voller versteckter Dolche und Skipistenkarten. Erhöht Wahrnehmung um <%= per %>. Begrenzte Auflage 2013-2014, Winterausrüstung.",
"armorSpecialCandycaneText": "Zuckerstangenrobe",
- "armorSpecialCandycaneNotes": "Gesponnen aus Zucker und Seide. Erhöht Intelligenz um <%= int %>. Limited Edition 2013-2014 Winterausrüstung.",
+ "armorSpecialCandycaneNotes": "Gesponnen aus Zucker und Seide. Erhöht Intelligenz um <%= int %>. Begrenzte Auflage 2013-2014, Winterausrüstung.",
"armorSpecialSnowflakeText": "Schneeflockengewand",
- "armorSpecialSnowflakeNotes": "Ein Gewand, das Dich selbst im kältesten Schneesturm warm hält. Erhöht Ausdauer um <%= con %>. Limited Edition 2013-2014 Winterausrüstung.",
+ "armorSpecialSnowflakeNotes": "Ein Gewand, das Dich selbst im kältesten Schneesturm warm hält. Erhöht Ausdauer um <%= con %>. Begrenzte Auflage 2013-2014, Winterausrüstung.",
"armorSpecialBirthdayText": "Ulkiges Festgewand",
- "armorSpecialBirthdayNotes": "Alles Gute zum Geburtstag, Habitica! Trage diese absurden Party Roben um diesen wundervollen Tag zu feiern. Gewährt keinen Attributbonus.",
+ "armorSpecialBirthdayNotes": "Alles Gute zum Geburtstag, Habitica! Trage diese absurden Partyroben um diesen wundervollen Tag zu feiern. Gewährt keinen Attributbonus.",
"armorSpecialBirthday2015Text": "Alberne Party Roben",
- "armorSpecialBirthday2015Notes": "Alles Gute zum Geburtstag, Habitica! Trage diese albernen Party Roben um diesen wundervollen Tag zu feiern. Gewährt keinen Attributbonus.",
+ "armorSpecialBirthday2015Notes": "Alles Gute zum Geburtstag, Habitica! Trage diese albernen Partyroben um diesen wundervollen Tag zu feiern. Gewährt keinen Attributbonus.",
"armorSpecialBirthday2016Text": "Lächerliches Festgewand",
- "armorSpecialBirthday2016Notes": "Alles Gute zum Geburtstag, Habitica! Trage dieses Lächerliche Festgewand, um diesen wundervollen Tag zu feiern. Gewährt keinen Attributbonus.",
+ "armorSpecialBirthday2016Notes": "Alles Gute zum Geburtstag, Habitica! Trage dieses lächerliche Festgewand, um diesen wundervollen Tag zu feiern. Gewährt keinen Attributbonus.",
"armorSpecialGaymerxText": "Regenbogenkriegerrüstung",
"armorSpecialGaymerxNotes": "Zur Feier der GaymerX-Konferenz, ist diese besondere Rüstung mit einem strahlenden, bunten Regenbogen verziert! GaymerX ist eine Spiele-Konvention, die LGBTQ und das Spielen an sich feiert und die offen für alle ist. Sie findet statt im InterContinental in der Stadtmitte von SanFrancisco vom 11. bis 13. Juli.",
"armorSpecialSpringRogueText": "Geschmeidiger Katzenanzug",
- "armorSpecialSpringRogueNotes": "Perfekt gepflegt. Erhöht Wahrnehmung um <%= per %>. Limited Edition 2014 Frühlingsausrüstung.",
+ "armorSpecialSpringRogueNotes": "Perfekt gepflegt. Erhöht Wahrnehmung um <%= per %>. Begrenzte Auflage 2014, Frühlingsausrüstung.",
"armorSpecialSpringWarriorText": "Hartkleerüstung",
- "armorSpecialSpringWarriorNotes": "Weich wie Klee, hart wie Stahl. Erhöht Ausdauer um <%= con %>. Limited Edition 2014 Frühlingsausrüstung.",
+ "armorSpecialSpringWarriorNotes": "Weich wie Klee, hart wie Stahl. Erhöht Ausdauer um <%= con %>. Begrenzte Auflage 2014, Frühlingsausrüstung.",
"armorSpecialSpringMageText": "Nagetierrobe",
- "armorSpecialSpringMageNotes": "Mäuse sind putzig! Erhöht Intelligenz um <%= int %>. Limited Edition 2014 Frühlingsausrüstung.",
+ "armorSpecialSpringMageNotes": "Mäuse sind putzig! Erhöht Intelligenz um <%= int %>. Begrenzte Auflage 2014, Frühlingsausrüstung.",
"armorSpecialSpringHealerText": "Flauschige Welpenrobe",
- "armorSpecialSpringHealerNotes": "Warum und kuschelig, aber schützt den Träger dennoch vor Schaden. Erhöht Ausdauer um <%= con %>. Limited Edition 2014 Frühlingsausrüstung.",
+ "armorSpecialSpringHealerNotes": "Warum und kuschelig, aber schützt den Träger dennoch vor Schaden. Erhöht Ausdauer um <%= con %>. Begrenzte Auflage 2014, Frühlingsausrüstung.",
"armorSpecialSummerRogueText": "Piratenrobe",
- "armorSpecialSummerRogueNotes": "Dies Gewand sein sehr gemütlich, yarrrrrr! Erhöht Wahrnehmung um <%= per %>. Limited Edition 2014 Sommerausrüstung.",
+ "armorSpecialSummerRogueNotes": "Dies Gewand sein sehr gemütlich, yarrrrrr! Erhöht Wahrnehmung um <%= per %>. Begrenzte Auflage 2014, Sommerausrüstung.",
"armorSpecialSummerWarriorText": "Abenteurergewand",
- "armorSpecialSummerWarriorNotes": "Vollständig mit Schnalle und Zierbuchstaben. Erhöht Ausdauer um <%= con %> Punkte. Limited Edition 2014 Sommerausrüstung.",
+ "armorSpecialSummerWarriorNotes": "Vollständig mit Schnalle und Zierbuchstaben. Erhöht Ausdauer um <%= con %> Punkte. Begrenzte Auflage 2014, Sommerausrüstung.",
"armorSpecialSummerMageText": "Smaragdschwanz",
- "armorSpecialSummerMageNotes": "Dieses Gewand aus schimmernden Schuppen verwandelt den Träger in einen echten Meermagier! Erhöht Intelligenz um <%= int %> Punkte. Limited Edition 2014 Sommerausrüstung.",
+ "armorSpecialSummerMageNotes": "Dieses Gewand aus schimmernden Schuppen verwandelt den Träger in einen echten Meermagier! Erhöht Intelligenz um <%= int %> Punkte. Begrenzte Auflage 2014, Sommerausrüstung.",
"armorSpecialSummerHealerText": "Schwanz des Meerheilers",
- "armorSpecialSummerHealerNotes": "Dieses Gewand aus schimmernden Schuppen verwandelt seinen Träger in einen echten Meerheiler! Erhöht Ausdauer um <%= con %>. Limited Edition 2014 Sommerausrüstung.",
+ "armorSpecialSummerHealerNotes": "Dieses Gewand aus schimmernden Schuppen verwandelt seinen Träger in einen echten Meerheiler! Erhöht Ausdauer um <%= con %>. Begrenzte Auflage 2014, Sommerausrüstung.",
"armorSpecialFallRogueText": "Blutrote Roben",
- "armorSpecialFallRogueNotes": "Bunt. Brandneu. Blutdurstig. Erhöht Wahrnehmung um <%= per %>. Limited Edition 2014 Herbstausrüstung.",
+ "armorSpecialFallRogueNotes": "Bunt. Brandneu. Blutdurstig. Erhöht Wahrnehmung um <%= per %>. Begrenzte Auflage 2014, Herbstausrüstung.",
"armorSpecialFallWarriorText": "Labormantel der Wissenschaft",
- "armorSpecialFallWarriorNotes": "Schützt vor Zaubertrankflecken und Schlimmeren. Erhöht Ausdauer um <%= con %>. Limited Edition 2014 Herbstausrüstung.",
+ "armorSpecialFallWarriorNotes": "Schützt vor Zaubertrankflecken und Schlimmeren. Erhöht Ausdauer um <%= con %>. Begrenzte Auflage 2014, Herbstausrüstung.",
"armorSpecialFallMageText": "Zauberhafte Zaubererrobe",
- "armorSpecialFallMageNotes": "Diese Robe hat zahlreiche Taschen für einen Extravorrat von Lurchaugen und Froschzungen. Erhöht Intelligenz um <%= int %>. Limited Edition 2014 Herbstausrüstung.",
+ "armorSpecialFallMageNotes": "Diese Robe hat zahlreiche Taschen für einen Extravorrat von Lurchaugen und Froschzungen. Erhöht Intelligenz um <%= int %>. Begrenzte Auflage 2014, Herbstausrüstung.",
"armorSpecialFallHealerText": "Bandagenrüstung",
- "armorSpecialFallHealerNotes": "Wirf Dich vor-verarztet in die Schlacht! Erhöht Ausdauer um <%= con %>. Limited Edition 2014 Herbstausrüstung.",
+ "armorSpecialFallHealerNotes": "Wirf Dich vor-verarztet in die Schlacht! Erhöht Ausdauer um <%= con %>. Begrenzte Auflage 2014, Herbstausrüstung.",
"armorSpecialWinter2015RogueText": "Eiszapfen-Drachenrüstung",
- "armorSpecialWinter2015RogueNotes": "Diese Rüstung ist eiskalt, aber das ist es definitiv wert, wenn Du erst die unermesslichen Reichtümer im Zentrum des Eiszapfen-Drachennestes abräumst. Nicht, dass Du irgendwie nach solchen unermesslichen Reichtümern suchen würdest. Du bist nämlich wirklich, wahrhaftig und ungelogen ein total echter Eiszapfen-Drache, okay?! Hör auf zu fragen! Erhöht Wahrnehmung um <%= per %>. Limited Edition 2014-2015 Winterausrüstung",
+ "armorSpecialWinter2015RogueNotes": "Diese Rüstung ist eiskalt, aber das ist es definitiv wert, wenn Du erst die unermesslichen Reichtümer im Zentrum des Eiszapfen-Drachennestes abräumst. Nicht, dass Du irgendwie nach solchen unermesslichen Reichtümern suchen würdest. Du bist nämlich wirklich, wahrhaftig und ungelogen ein total echter Eiszapfen-Drache, okay?! Hör auf zu fragen! Erhöht Wahrnehmung um <%= per %>. Begrenzte Auflage 2014-2015, Winterausrüstung",
"armorSpecialWinter2015WarriorText": "Lebkuchenrüstung",
- "armorSpecialWinter2015WarriorNotes": "Gemütlich und warm, frisch aus dem Ofen! Erhöht Ausdauer um <%= con %>. Limited Edition 2014-2015 Winterausrüstung.",
+ "armorSpecialWinter2015WarriorNotes": "Gemütlich und warm, frisch aus dem Ofen! Erhöht Ausdauer um <%= con %>. Begrenzte Auflage 2014-2015, Winterausrüstung.",
"armorSpecialWinter2015MageText": "Boreale Robe",
- "armorSpecialWinter2015MageNotes": "In dieser Robe sind die schimmernden Lichter des Nordens sichtbar. Erhöht Intelligenz um <%= int %>. Limited Edition 2014-2015 Winterausrüstung.",
+ "armorSpecialWinter2015MageNotes": "In dieser Robe sind die schimmernden Lichter des Nordens sichtbar. Erhöht Intelligenz um <%= int %>. Begrenzte Auflage 2014-2015, Winterausrüstung.",
"armorSpecialWinter2015HealerText": "Schlittschuhoutfit",
- "armorSpecialWinter2015HealerNotes": "Eislaufen ist sehr entspannend. Für den Fall, dass Dich die Eiszapfen-Drachen angreifen, solltest Du es allerdings nicht ohne diese Schutzausrüstung probieren. Erhöht Ausdauer um <%= con %>. Limited Edition 2014-2015 Winterausrüstung",
+ "armorSpecialWinter2015HealerNotes": "Eislaufen ist sehr entspannend. Für den Fall, dass Dich die Eiszapfen-Drachen angreifen, solltest Du es allerdings nicht ohne diese Schutzausrüstung probieren. Erhöht Ausdauer um <%= con %>. Begrenzte Auflage 2014-2015, Winterausrüstung",
"armorSpecialSpring2015RogueText": "Quietschige Robe",
- "armorSpecialSpring2015RogueNotes": "Pelzig, weich und ganz sicher nicht brennbar. Erhöht Wahrnehmung um <%= per %>. Limited Edition 2015 Frühlingsausrüstung.",
+ "armorSpecialSpring2015RogueNotes": "Pelzig, weich und ganz sicher nicht brennbar. Erhöht Wahrnehmung um <%= per %>. Begrenzte Auflage 2015, Frühlingsausrüstung.",
"armorSpecialSpring2015WarriorText": "Hüterrüstung",
- "armorSpecialSpring2015WarriorNotes": "Nur der wildeste Hund darf so fluffig sein. Erhöht Ausdauer um <%= con %>. Limited Edition 2015 Frühlingsausrüstung.",
+ "armorSpecialSpring2015WarriorNotes": "Nur der wildeste Hund darf so fluffig sein. Erhöht Ausdauer um <%= con %>. Begrenzte Auflage 2015, Frühlingsausrüstung.",
"armorSpecialSpring2015MageText": "Häschenanzug des Magiers",
- "armorSpecialSpring2015MageNotes": "Dein Baumwollmantel passt zu Deinem Baumwollschwanz! Erhöht Intelligenz um <%= int %>. Limited Edition 2015 Frühlingsausrüstung.",
+ "armorSpecialSpring2015MageNotes": "Dein Baumwollmantel passt zu Deinem Baumwollschwanz! Erhöht Intelligenz um <%= int %>. Begrenzte Auflage 2015, Frühlingsausrüstung.",
"armorSpecialSpring2015HealerText": "Trostspendender Katzenanzug",
- "armorSpecialSpring2015HealerNotes": "Dieser weiche Katzenanzug ist bequem und so beruhigend wie Pfefferminztee. Erhöht Ausdauer um <%= con %>. Limited Edition 2015 Frühlingsausrüstung.",
+ "armorSpecialSpring2015HealerNotes": "Dieser weiche Katzenanzug ist bequem und so beruhigend wie Pfefferminztee. Erhöht Ausdauer um <%= con %>. Begrenzte Auflage 2015, Frühlingsausrüstung.",
"armorSpecialSummer2015RogueText": "Rubinfarbener Schwanz",
- "armorSpecialSummer2015RogueNotes": "Dieses Kleidungsstück aus schimmernden Schuppen verwandelt seinen Träger in einen echten Abtrünnigen des Riffs! Erhöht Wahrnehmung um <%= per %>. Limited Edition 2015 Sommerausrüstung.",
+ "armorSpecialSummer2015RogueNotes": "Dieses Kleidungsstück aus schimmernden Schuppen verwandelt seinen Träger in einen echten Abtrünnigen des Riffs! Erhöht Wahrnehmung um <%= per %>. Begrenzte Auflage 2015, Sommerausrüstung.",
"armorSpecialSummer2015WarriorText": "Goldener Schwanz",
- "armorSpecialSummer2015WarriorNotes": "Dieses Gewand aus schimmernden Schuppen verwandelt seinen Träger in einen echten Sonnenbarsch-Krieger! Erhöht Ausdauer um <%= con %>. Limited Edition 2015 Sommerausrüstung.",
+ "armorSpecialSummer2015WarriorNotes": "Dieses Gewand aus schimmernden Schuppen verwandelt seinen Träger in einen echten Sonnenbarsch-Krieger! Erhöht Ausdauer um <%= con %>. Begrenzte Auflage 2015, Sommerausrüstung.",
"armorSpecialSummer2015MageText": "Wahrsagerrobe",
- "armorSpecialSummer2015MageNotes": "Versteckte Macht liegt in diesen Puffärmeln. Erhöht Intelligenz um <%= int %>. Limited Edition 2015 Sommerausrüstung.",
+ "armorSpecialSummer2015MageNotes": "Versteckte Macht liegt in diesen Puffärmeln. Erhöht Intelligenz um <%= int %>. Begrenzte Auflage 2015, Sommerausrüstung.",
"armorSpecialSummer2015HealerText": "Matrosenrüstung",
- "armorSpecialSummer2015HealerNotes": "Mit dieser Rüstung weiß jeder, dass Du ein ehrlicher Handelsseemann bist, der niemals davon träumen würde sich wie ein Taugenichts zu benehmen. Erhöht Ausdauer um <%= con %>. Limited Edition 2015 Sommerausrüstung.",
+ "armorSpecialSummer2015HealerNotes": "Mit dieser Rüstung weiß jeder, dass Du ein ehrlicher Handelsseemann bist, der niemals davon träumen würde sich wie ein Taugenichts zu benehmen. Erhöht Ausdauer um <%= con %>. Begrenzte Auflage 2015, Sommerausrüstung.",
"armorSpecialFall2015RogueText": "Kampfrüstung",
- "armorSpecialFall2015RogueNotes": "Flieg in den Kampf! Erhöht Wahrnehmung um <%= per %>. Limited Edition 2015 Herbstausrüstung.",
+ "armorSpecialFall2015RogueNotes": "Flieg in den Kampf! Erhöht Wahrnehmung um <%= per %>. Begrenzte Auflage 2015, Herbstausrüstung.",
"armorSpecialFall2015WarriorText": "Vogelscheuchenrüstung",
- "armorSpecialFall2015WarriorNotes": "Obwohl sie nur mit Stroh ausgestopft ist, ist diese Rüstung extrem mächtig! Erhöht Ausdauer um <%= con %>. Limited Edition 2015 Herbstausrüstung.",
+ "armorSpecialFall2015WarriorNotes": "Obwohl sie nur mit Stroh ausgestopft ist, ist diese Rüstung extrem mächtig! Erhöht Ausdauer um <%= con %>. Begrenzte Auflage 2015, Herbstausrüstung.",
"armorSpecialFall2015MageText": "Genähte Roben",
- "armorSpecialFall2015MageNotes": "Jede Masche dieser Rüstung schimmert mit Zauberei. Erhöht Intelligenz um <%= int %>. Limited Edition 2015 Herbstausrüstung.",
+ "armorSpecialFall2015MageNotes": "Jede Masche dieser Rüstung schimmert mit Zauberei. Erhöht Intelligenz um <%= int %>. Begrenzte Auflage 2015, Herbstausrüstung.",
"armorSpecialFall2015HealerText": "Roben des Tränkebrauers",
- "armorSpecialFall2015HealerNotes": "Wie bitte? Natürlich war das ein Trank der Ausdauer. Nein, Du verwandelst Dich definitiv nicht in einen Frosch! Sei nicht albern. Erhöht Ausdauer um <%= con %>. Limited Edition 2015 Herbstausrüstung.",
+ "armorSpecialFall2015HealerNotes": "Wie bitte? Natürlich war das ein Trank der Ausdauer. Nein, Du verwandelst Dich definitiv nicht in einen Frosch! Sei nicht qualbern. Erhöht Ausdauer um <%= con %>. Begrenzte Auflage 2015, Herbstausrüstung.",
"armorSpecialWinter2016RogueText": "Kakaorüstung",
- "armorSpecialWinter2016RogueNotes": "Diese Lederrüstung hält Dich schön warm. Ist sie tatsächlich aus Kakao? Das wirst Du nie herausfinden. Erhöht Wahrnehmung um <%= per %>. Limited Edition 2015-2016 Winterausrüstung.",
+ "armorSpecialWinter2016RogueNotes": "Diese Lederrüstung hält Dich schön warm. Ist sie tatsächlich aus Kakao? Das wirst Du nie herausfinden. Erhöht Wahrnehmung um <%= per %>. Begrenzte Auflage 2015-2016, Winterausrüstung.",
"armorSpecialWinter2016WarriorText": "Schneemannanzug",
- "armorSpecialWinter2016WarriorNotes": "Brr! Diese gepolsterte Rüstung ist wirklich leistungsfähig… bis sie schmilzt. Erhöht Ausdauer um <%= con %>. Limited Edition 2015-2016 Winterausrüstung.",
+ "armorSpecialWinter2016WarriorNotes": "Brr! Diese gepolsterte Rüstung ist wirklich leistungsfähig … bis sie schmilzt. Erhöht Ausdauer um <%= con %>. Begrenzte Auflage 2015-2016, Winterausrüstung.",
"armorSpecialWinter2016MageText": "Snowboarder-Anorak",
- "armorSpecialWinter2016MageNotes": "Der weiseste Zauberer packt sich im Winter warm ein. Erhöht Intelligenz um <%= int %>. Limited Edition 2015-2016 Winterausrüstung.",
+ "armorSpecialWinter2016MageNotes": "Der weiseste Zauberer packt sich im Winter warm ein. Erhöht Intelligenz um <%= int %>. Begrenzte Auflage 2015-2016, Winterausrüstung.",
"armorSpecialWinter2016HealerText": "Festlicher Feenumhang",
- "armorSpecialWinter2016HealerNotes": "Festliche Feen hüllen sich zum Schutz in ihre großen Körperflügel, während sie mit ihren kleinen Kopfflügeln über Habitica sausen, Geschenke verteilen und überall Konfetti streuen. Wie süß! Erhöht Ausdauer um <%= con %>. Limited Edition 2015-2016 Winterausrüstung.",
- "armorSpecialSpring2016RogueText": "Hunde Tarnanzug",
+ "armorSpecialWinter2016HealerNotes": "Festliche Feen hüllen sich zum Schutz in ihre großen Körperflügel, während sie mit ihren kleinen Kopfflügeln über Habitica sausen, Geschenke verteilen und überall Konfetti streuen. Wie süß! Erhöht Ausdauer um <%= con %>. Begrenzte Auflage 2015-2016, Winterausrüstung.",
+ "armorSpecialSpring2016RogueText": "Hunde-Tarnanzug",
"armorSpecialSpring2016RogueNotes": "Ein weiser Welpe weiß, dass man eine schillernde Tarnung wählt, wenn alles um einen herum grün ist und lebendig erstrahlt. Erhöht Wahrnehmung um <%= per %>. Limitierte Edition 2016 Frühlingsausrüstung.",
"armorSpecialSpring2016WarriorText": "Mächtiges Kettenhemd",
- "armorSpecialSpring2016WarriorNotes": "Magst Du auch klein sein, Du bist nicht klein zu kriegen! Erhöht Ausdauer um <%= con %>. Limited Edition 2016 Frühlingsausrüstung.",
- "armorSpecialSpring2016MageText": "Prachtvolle Lumpen Roben",
+ "armorSpecialSpring2016WarriorNotes": "Magst Du auch klein sein, Du bist nicht klein zu kriegen! Erhöht Ausdauer um <%= con %>. Begrenzte Auflage 2016, Frühlingsausrüstung.",
+ "armorSpecialSpring2016MageText": "Prachtvolle Lumpenroben",
"armorSpecialSpring2016MageNotes": "Strahlende Farben, damit Du nicht mit einer Nekromaus verwechselt wirst. Erhöht Intelligenz um <%= int %>. Limitierte Edition 2016 Frühlingsausrüstung.",
- "armorSpecialSpring2016HealerText": "Fluffige Häschen Kniehose",
- "armorSpecialSpring2016HealerNotes": "Hü-Hüpf! Hüpfe von Hügel zu Hügel und heile alle Hilfsbedürftigen! Erhöht Ausdauer um <%= con %>. Limited Edition 2016 Frühlingsausrüstung.",
+ "armorSpecialSpring2016HealerText": "Fluffige Häschen-Kniehose",
+ "armorSpecialSpring2016HealerNotes": "Hü-Hüpf! Hüpfe von Hügel zu Hügel und heile alle Hilfsbedürftigen! Erhöht Ausdauer um <%= con %>. Begrenzte Auflage 2016, Frühlingsausrüstung.",
"armorMystery201402Text": "Robe des Nachrichtenbringers",
"armorMystery201402Notes": "Schimmernd, stabil und mit vielen Taschen für Briefe. Gewährt keinen Attributbonus. Februar 2014 Abonnentengegenstand.",
"armorMystery201403Text": "Waldwanderer-Rüstung",
@@ -353,17 +355,21 @@
"armorMystery201504Text": "Bienenrobe",
"armorMystery201504Notes": "In dieser Robe wirst Du fleißig sein wie eine Biene! Gewährt keinen Attributbonus. April 2015 Abonnentengegenstand.",
"armorMystery201506Text": "Taucheranzug",
- "armorMystery201506Notes": "Schnorchel durch ein Korallenriff mit diesem knallbunten Taucheranzug! Gewährt keinen Attributsbonus. Limited Edition 2015 Sommerausrüstung.",
+ "armorMystery201506Notes": "Schnorchel durch ein Korallenriff mit diesem knallbunten Taucheranzug! Gewährt keinen Attributsbonus. Begrenzte Auflage 2015, Sommerausrüstung.",
"armorMystery201508Text": "Gepardenkostüm",
"armorMystery201508Notes": "Sei schnell wie der Blitz im flauschigen Gepardenkostüm! Gewährt keinen Attributbonus. Abonnentengegenstand, August 2015",
- "armorMystery201509Text": "Werwolfverkleidung",
+ "armorMystery201509Text": "Werwolfkostüm",
"armorMystery201509Notes": "Das IST doch ein Kostüm, nicht wahr? Gewährt keinen Attributbonus. Abonnentengegenstand, September 2015.",
"armorMystery201511Text": "Holzrüstung",
"armorMystery201511Notes": "Wenn man bedenkt, dass diese Rüstung direkt aus einem magischen Baumstamm geschnitzt wurde, ist sie erstaunlich bequem. Gewährt keinen Attributbonus. Abonnentengegenstand, November 2015.",
- "armorMystery201512Text": "Rüstung des Kalten Feuers",
- "armorMystery201512Notes": "Beschwöre die eisigen Flammen des Winters herbei! Gewährt keinen Attributbonus. Dezember 2015 Abonnentengegenstand.",
+ "armorMystery201512Text": "Rüstung des kalten Feuers",
+ "armorMystery201512Notes": "Beschwöre die eisigen Flammen des Winters herbei! Gewährt keinen Attributbonus. Abonnentengegenstand, Dezember 2015.",
"armorMystery201603Text": "Glück-Anzug",
"armorMystery201603Notes": "Dieser Anzug wurde aus tausenden vierblättrigen Kleeblättern zusammengenäht! Gewährt keinen Attributbonus. Abonnentengegenstand, März 2016.",
+ "armorMystery201604Text": "Blätter-Rüstung",
+ "armorMystery201604Notes": "Auch Du kannst ein kleiner, aber furchteinflössender Blätterhaufen sein. Gewährt keinen Attributbonus. April 2006 Abonnentengegenstand.",
+ "armorMystery201605Text": "Marching Bard Uniform",
+ "armorMystery201605Notes": "Unlike the traditional bards who join adventuring parties, bards who join Habitican marching bands are known for grand parades, not dungeon raids. Confers no benefit. May 2016 Subscriber Item.",
"armorMystery301404Text": "Steampunkanzug",
"armorMystery301404Notes": "Adrett und schneidig, hoho! Gewährt keinen Attributbonus. Februar 3015 Abonnentengegenstand.",
"armorArmoireLunarArmorText": "Beruhigende Mondrüstung",
@@ -385,15 +391,17 @@
"armorArmoireCrystalCrescentRobesText": "Kristalline Mondsichelroben",
"armorArmoireCrystalCrescentRobesNotes": "Diese magischen Roben leuchten bei Nacht. Erhöht Ausdauer und Wahrnehmung jeweils um <%= attrs %>. Verzauberter Schrank: Kristallines Mondsichelset (Gegenstand 2 von 3)",
"armorArmoireDragonTamerArmorText": "Drachenzähmer-Rüstung",
- "armorArmoireDragonTamerArmorNotes": "Durch diese robuste Rüstung dringt keine Flamme. Erhöht Ausdauer um <%= con %>. Verzauberter Schrank: Drachenzähmer Set (Gegenstand 3 von 3).",
+ "armorArmoireDragonTamerArmorNotes": "Durch diese robuste Rüstung dringt keine Flamme. Erhöht Ausdauer um <%= con %>. Verzauberter Schrank: Drachenzähmer-Set (Gegenstand 3 von 3).",
"armorArmoireBarristerRobesText": "Richterrobe",
"armorArmoireBarristerRobesNotes": "Äußerst seriös und stattlich. Erhöht Ausdauer um <%= con %>. Verzauberter Schrank: Richterset (Gegenstand 2 von 3).",
"armorArmoireJesterCostumeText": "Narrenkostüm",
"armorArmoireJesterCostumeNotes": "Tra-la-la! Trotz des Aussehens dieses Kostüms, bist Du kein Narr. Erhöht Intelligenz um <%= int %>. Verzauberter Schrank: Narrenset (Gegenstand 2 von 3).",
"armorArmoireMinerOverallsText": "Arbeitsanzug des Bergmanns",
"armorArmoireMinerOverallsNotes": "Er sieht vielleicht abgetragen aus, aber er wurde Schmutz abweisend verzaubert. Erhöht Ausdauer um <%=con %>. Verzauberter Schrank: Bergmannsset (Gegenstand 2 von 3).",
- "armorArmoireBasicArcherArmorText": "Basic Archer Armor",
- "armorArmoireBasicArcherArmorNotes": "This camouflaged vest lets you slip unnoticed through the forests. Increases Perception by <%= per %>. Enchanted Armoire: Basic Archer Set (Item 2 of 3).",
+ "armorArmoireBasicArcherArmorText": "Einfache Bogenschützen-Rüstung",
+ "armorArmoireBasicArcherArmorNotes": "Diese Tarnweste lässt Dich unbemerkt durch den Wald schleichen. Erhöht Wahrnehmung um <%= per %>.\nVerzauberter Schrank: Standard-Bogenschützenset (Gegenstand 2 von 3).",
+ "armorArmoireGraduateRobeText": "Doktorenrobe",
+ "armorArmoireGraduateRobeNotes": "Gratulation! Diese Robe hängt schwer mit all dem Wissen, das Du angehäuft hast. Erhöht Intelligenz um <&=int %>. Verzauberter Schrank: Doktoranden-Set (Gegenstand 2 von 3).",
"headgear": "Kopfschutz",
"headBase0Text": "Kein Helm.",
"headBase0Notes": "Keine Kopfbedeckung.",
@@ -444,51 +452,51 @@
"headSpecial2Text": "Namenloser Helm",
"headSpecial2Notes": "Ein Andenken an jene, die gegeben haben ohne eine Gegenleistung zu verlangen. Erhöht Intelligenz und Stärke um jeweils <%= attrs %>.",
"headSpecialFireCoralCircletText": "Feuerkorallendiadem",
- "headSpecialFireCoralCircletNotes": "Dieser Reif, der von Habiticas größten Alchimisten gestaltet wurde, erlaubt dir unter Wasser zu atmen und nach Schätzen zu tauchen! Erhöht Wahrnehmung um <%= per %>.",
+ "headSpecialFireCoralCircletNotes": "Dieser Reif, der von Habiticas größten Alchimisten gestaltet wurde, erlaubt Dir unter Wasser zu atmen und nach Schätzen zu tauchen! Erhöht Wahrnehmung um <%= per %>.",
"headSpecialNyeText": "Ulkiger Festhut",
"headSpecialNyeNotes": "Du hast einen ulkigen Partyhut erhalten! Trage ihn mit Stolz bei Deinem Rutsch ins neue Jahr! Gewährt keinen Attributbonus.",
"headSpecialYetiText": "Helm des Yeti-Zähmers",
- "headSpecialYetiNotes": "Eine niedliche, aber gleichzeitig furchterregende Kopfbedeckung. Erhöht Stärke um <%= str %>. Limited Edition 2013-2014 Winterausrüstung.",
+ "headSpecialYetiNotes": "Eine niedliche, aber gleichzeitig furchterregende Kopfbedeckung. Erhöht Stärke um <%= str %>. Begrenzte Auflage 2013-2014, Winterausrüstung.",
"headSpecialSkiText": "Kapuze des Ski-ssassinen",
- "headSpecialSkiNotes": "Hält Deine Identität geheim ... und Dein Gesicht schön warm. Erhöht Wahrnehmung um <%= per %>. Limited Edition 2013-2014 Winterausrüstung",
+ "headSpecialSkiNotes": "Hält Deine Identität geheim ... und Dein Gesicht schön warm. Erhöht Wahrnehmung um <%= per %>. Begrenzte Auflage 2013-2014, Winterausrüstung",
"headSpecialCandycaneText": "Zuckerstangenhut",
- "headSpecialCandycaneNotes": "Der leckerste Hut der Welt. Berüchtigt dafür, dass er mysteriös auftaucht und wieder verschwindet. Erhöht Wahrnehmung um <%= per %>. Limited Edition 2013-2014 Winterausrüstung.",
+ "headSpecialCandycaneNotes": "Der leckerste Hut der Welt. Berüchtigt dafür, dass er mysteriös auftaucht und wieder verschwindet. Erhöht Wahrnehmung um <%= per %>. Begrenzte Auflage 2013-2014, Winterausrüstung.",
"headSpecialSnowflakeText": "Schneeflockenkrone",
- "headSpecialSnowflakeNotes": "Der Träger dieser Krone wird niemals frieren. Erhöht Intelligenz um <%= int %>. Limited Edition 2013-2014 Winterausrüstung.",
+ "headSpecialSnowflakeNotes": "Der Träger dieser Krone wird niemals frieren. Erhöht Intelligenz um <%= int %>. Begrenzte Auflage 2013-2014, Winterausrüstung.",
"headSpecialSpringRogueText": "Verstohlene Kätzchenmaske",
- "headSpecialSpringRogueNotes": "Niemand wird je darauf kommen, dass Du ein Katzeneinbrecher bist! Erhöht Wahrnehmung um <%= per %>. Limited Edition 2014 Frühlingsausrüstung.",
+ "headSpecialSpringRogueNotes": "Niemand wird je darauf kommen, dass Du ein Katzeneinbrecher bist! Erhöht Wahrnehmung um <%= per %>. Begrenzte Auflage 2014, Frühlingsausrüstung.",
"headSpecialSpringWarriorText": "Hartkleehelm",
- "headSpecialSpringWarriorNotes": "Dieser Helm, der aus süßem Wiesenklee geschmiedet wurde, kann auch dem mächtigsten Hieb standhalten. Erhöht Stärke um <%= str %>. Limited Edition 2014 Frühlingsausrüstung.",
+ "headSpecialSpringWarriorNotes": "Dieser Helm, der aus süßem Wiesenklee geschmiedet wurde, kann auch dem mächtigsten Hieb standhalten. Erhöht Stärke um <%= str %>. Begrenzte Auflage 2014, Frühlingsausrüstung.",
"headSpecialSpringMageText": "Schweizer Käsehut",
- "headSpecialSpringMageNotes": "Dieser Hut enthält eine Menge mächtige Magie! Versuche, ihn nicht anzuknabbern. Erhöht Wahrnehmung um <%= per %>. Limited Edition 2014 Frühlingsausrüstung.",
+ "headSpecialSpringMageNotes": "Dieser Hut enthält eine Menge mächtige Magie! Versuche, ihn nicht anzuknabbern. Erhöht Wahrnehmung um <%= per %>. Begrenzte Auflage 2014, Frühlingsausrüstung.",
"headSpecialSpringHealerText": "Krone der Freundschaft",
- "headSpecialSpringHealerNotes": "Diese Krone symbolisiert Treue und Kameradschaft. Ein Hund ist schließlich der beste Freund des Abenteurers! Erhöht Intelligenz um <%= int %>. Limited Edition 2014 Frühlingsausrüstung.",
+ "headSpecialSpringHealerNotes": "Diese Krone symbolisiert Treue und Kameradschaft. Ein Hund ist schließlich der beste Freund des Abenteurers! Erhöht Intelligenz um <%= int %>. Begrenzte Auflage 2014, Frühlingsausrüstung.",
"headSpecialSummerRogueText": "Piratenhut",
- "headSpecialSummerRogueNotes": "Nur die produktivsten Piraten können diesen herrlichen Hut tragen. Erhöht die Wahrnehmung um <%= per %>. Limited Edition 2014 Sommerausrüstung.",
+ "headSpecialSummerRogueNotes": "Nur die produktivsten Piraten können diesen herrlichen Hut tragen. Erhöht die Wahrnehmung um <%= per %>. Begrenzte Auflage 2014, Sommerausrüstung.",
"headSpecialSummerWarriorText": "Abenteurerkopftuch",
- "headSpecialSummerWarriorNotes": "Dieses weiche, salzige Tuch gibt dem Träger Stärke. Erhöht Stärke um <%= str %>. Limited Edition 2014 Sommerausrüstung.",
+ "headSpecialSummerWarriorNotes": "Dieses weiche, salzige Tuch gibt dem Träger Stärke. Erhöht Stärke um <%= str %>. Begrenzte Auflage 2014, Sommerausrüstung.",
"headSpecialSummerMageText": "Algenumwickelter Hut",
- "headSpecialSummerMageNotes": "Was könnte magischer sein als ein mit Algen umwickelter Hut? Erhöht Wahrnehmung um <%= per %>. Limited Edition 2014 Sommerausrüstung.",
+ "headSpecialSummerMageNotes": "Was könnte magischer sein als ein mit Algen umwickelter Hut? Erhöht Wahrnehmung um <%= per %>. Begrenzte Auflage 2014, Sommerausrüstung.",
"headSpecialSummerHealerText": "Korallenkrone",
- "headSpecialSummerHealerNotes": "Gibt dem Träger die Fähigkeit, beschädigte Korallenriffe zu heilen. Erhöht Intelligenz um <%= int %>. Limited Edition 2014 Sommerausrüstung.",
+ "headSpecialSummerHealerNotes": "Gibt dem Träger die Fähigkeit, beschädigte Korallenriffe zu heilen. Erhöht Intelligenz um <%= int %>. Begrenzte Auflage 2014, Sommerausrüstung.",
"headSpecialFallRogueText": "Blutrote Kapuze",
- "headSpecialFallRogueNotes": "Die Identität eines Vampirjägers muss stets geschützt sein. Erhöht Wahrnehmung um <%= per %>. Limited Edition 2014 Herbstausrüstung.",
+ "headSpecialFallRogueNotes": "Die Identität eines Vampirjägers muss stets geschützt sein. Erhöht Wahrnehmung um <%= per %>. Begrenzte Auflage 2014, Herbstausrüstung.",
"headSpecialFallWarriorText": "Monstermaske der Wissenschaft",
- "headSpecialFallWarriorNotes": "Aufsetzen und sich wohlfühlen. Fast wie neu. Erhöht Stärke um <%= str %>. Limited Edition 2014 Herbstausrüstung.",
+ "headSpecialFallWarriorNotes": "Aufsetzen und sich wohlfühlen. Fast wie neu. Erhöht Stärke um <%= str %>. Begrenzte Auflage 2014, Herbstausrüstung.",
"headSpecialFallMageText": "Spitzer Hut",
- "headSpecialFallMageNotes": "Da steckt Magie in jedem Faden. Erhöht Wahrnehmung um <%= per %>. Limited Edition 2014 Herbstausrüstung.",
+ "headSpecialFallMageNotes": "Da steckt Magie in jedem Faden. Erhöht Wahrnehmung um <%= per %>. Begrenzte Auflage 2014, Herbstausrüstung.",
"headSpecialFallHealerText": "Kopfverband",
- "headSpecialFallHealerNotes": "Antibakteriell und modebewusst. Erhöht Intelligenz um <%= int %>. Limited Edition 2014 Herbstausrüstung.",
+ "headSpecialFallHealerNotes": "Antibakteriell und modebewusst. Erhöht Intelligenz um <%= int %>. Begrenzte Auflage 2014, Herbstausrüstung.",
"headSpecialNye2014Text": "Alberner Partyhut",
"headSpecialNye2014Notes": "Du hast einen albernen Partyhut erhalten! Trag ihn mit Stolz, während Du ins neue Jahr hineinfeierst! Gewährt keinen Attributbonus.",
"headSpecialWinter2015RogueText": "Eiszapfen-Drachenmaske",
- "headSpecialWinter2015RogueNotes": "Du bist wirklich, wahrhaftig und zweifellos ein total echter Eiszapfen-Drache. Du dringst nicht in das Nest der Eiszapfen-Drachen ein. Du hast absolut gar kein Interesse an den unermesslichen Reichtümern, die Gerüchten zufolge in ihren kalten Tunneln lagern sollen. Rawr. Erhöht Wahrnehmung um <%= per %>. Limited Edition 2014-2015 Winterausrüstung",
+ "headSpecialWinter2015RogueNotes": "Du bist wirklich, wahrhaftig und zweifellos ein total echter Eiszapfen-Drache. Du dringst nicht in das Nest der Eiszapfen-Drachen ein. Du hast absolut gar kein Interesse an den unermesslichen Reichtümern, die Gerüchten zufolge in ihren kalten Tunneln lagern sollen. Rawr. Erhöht Wahrnehmung um <%= per %>. Begrenzte Auflage 2014-2015, Winterausrüstung",
"headSpecialWinter2015WarriorText": "Lebkuchenhelm",
- "headSpecialWinter2015WarriorNotes": "Denk, denk, denk, bis dir der Kopf brummt. Erhöht Stärke um <%= str %>. Limited Edition 2014-2015 Winterausrüstung.",
+ "headSpecialWinter2015WarriorNotes": "Denk, denk, denk, bis Dir der Kopf brummt. Erhöht Stärke um <%= str %>. Begrenzte Auflage 2014-2015, Winterausrüstung.",
"headSpecialWinter2015MageText": "Nordlichthelm",
- "headSpecialWinter2015MageNotes": "Das Gewebe dieses Hutes bewegt sich und leuchtet, während der Träger lernt. Erhöht Wahrnehmung um <%= per %>. Limited Edition 2014-2015 Winterausrüstung",
+ "headSpecialWinter2015MageNotes": "Das Gewebe dieses Hutes bewegt sich und leuchtet, während der Träger lernt. Erhöht Wahrnehmung um <%= per %>. Begrenzte Auflage 2014-2015, Winterausrüstung",
"headSpecialWinter2015HealerText": "Kuschelige Ohrenwärmer",
- "headSpecialWinter2015HealerNotes": "Diese warmen Ohrenschützer schützen vor Kälte und störendem Krach. Erhöht Intelligenz um <%= int %>. Limited Edition 2014-2015 Winterausrüstung",
+ "headSpecialWinter2015HealerNotes": "Diese warmen Ohrenschützer schützen vor Kälte und störendem Krach. Erhöht Intelligenz um <%= int %>. Begrenzte Auflage 2014-2015, Winterausrüstung",
"headSpecialSpring2015RogueText": "Feuerfester Helm",
"headSpecialSpring2015RogueNotes": "Feuer? HAH! Du quiekst erbittert angesichts des Feuers. Erhöht Wahrnehmung um <%= per %>. Limitierte Ausgabe 2015 Frühlingsausrüstung.",
"headSpecialSpring2015WarriorText": "Hütehelm",
@@ -496,40 +504,40 @@
"headSpecialSpring2015MageText": "Hut des Bühnenmagiers",
"headSpecialSpring2015MageNotes": "Was kam zuerst, das Häschen oder der Hut? Erhöht Wahrnehmung um <%= per %>. Limitierte Auflage 2015 Frühlingsausrüstung.",
"headSpecialSpring2015HealerText": "Trostspendende Krone",
- "headSpecialSpring2015HealerNotes": "Die Perle im Zentrum dieser Krone beruhigt und tröstet jene, die sich in ihrer Nähe befinden. Erhöht Intelligenz um <%= int %>. Limited Edition 2015 Frühlingsausrüstung.",
+ "headSpecialSpring2015HealerNotes": "Die Perle im Zentrum dieser Krone beruhigt und tröstet jene, die sich in ihrer Nähe befinden. Erhöht Intelligenz um <%= int %>. Begrenzte Auflage 2015, Frühlingsausrüstung.",
"headSpecialSummer2015RogueText": "Abtrünnigenhut",
- "headSpecialSummer2015RogueNotes": "Dieser Piratenhut fiel über Bord und wird jetzt von Feuerkorallenstückchen geschmückt. Erhöht Wahrnehmung um <%= per %>. Limited Edition 2015 Sommerausrüstung.",
+ "headSpecialSummer2015RogueNotes": "Dieser Piratenhut fiel über Bord und wird jetzt von Feuerkorallenstückchen geschmückt. Erhöht Wahrnehmung um <%= per %>. Begrenzte Auflage 2015, Sommerausrüstung.",
"headSpecialSummer2015WarriorText": "Juwelenhelm des Meeres",
- "headSpecialSummer2015WarriorNotes": "Dieser starke und ansehnliche Helm aus Tiefseemetall wurde von den Kunsthandwerkern aus Dilatory hergestellt. Erhöht Stärke um <%= str %>. Limited Edition 2015 Sommerausrüstung.",
+ "headSpecialSummer2015WarriorNotes": "Dieser starke und ansehnliche Helm aus Tiefseemetall wurde von den Kunsthandwerkern aus Dilatory hergestellt. Erhöht Stärke um <%= str %>. Begrenzte Auflage 2015, Sommerausrüstung.",
"headSpecialSummer2015MageText": "Wahrsagertuch",
- "headSpecialSummer2015MageNotes": "In den Fäden dieses Schals schlummert verborgene Kraft. Erhöht Wahrnehmung um <%= per %>. Limited Edition 2015 Sommerausrüstung.",
+ "headSpecialSummer2015MageNotes": "In den Fäden dieses Schals schlummert verborgene Kraft. Erhöht Wahrnehmung um <%= per %>. Begrenzte Auflage 2015, Sommerausrüstung.",
"headSpecialSummer2015HealerText": "Matrosenkappe",
- "headSpecialSummer2015HealerNotes": "Hast Du die Matrosenkappe sicher auf Deinem Kopf, kannst Du sogar durch die stürmischsten Gewässer steuern! Erhöht Intelligenz um <%= int %>. Limited Edition 2015 Sommerausrüstung.",
+ "headSpecialSummer2015HealerNotes": "Hast Du die Matrosenkappe sicher auf Deinem Kopf, kannst Du sogar durch die stürmischsten Gewässer steuern! Erhöht Intelligenz um <%= int %>. Begrenzte Auflage 2015, Sommerausrüstung.",
"headSpecialFall2015RogueText": "Geflügelter Kampfhelm",
- "headSpecialFall2015RogueNotes": "Orte Deine Feinde mit diesem mächtigen Helm durch Echos! Erhöht Wahrnehmung um <%= per %>. Limited Edition 2015 Herbstausrüstung.",
+ "headSpecialFall2015RogueNotes": "Orte Deine Feinde mit diesem mächtigen Helm durch Echos! Erhöht Wahrnehmung um <%= per %>. Begrenzte Auflage 2015, Herbstausrüstung.",
"headSpecialFall2015WarriorText": "Vogelscheuchenhut",
- "headSpecialFall2015WarriorNotes": "Jeder würde diesen Hut wollen - wenn sie denn nur ein Gehirn hätten. Erhöht Stärke um <%= str %>. Limited Edition 2015 Herbstausrüstung.",
+ "headSpecialFall2015WarriorNotes": "Jeder würde diesen Hut wollen - wenn sie denn nur ein Gehirn hätten. Erhöht Stärke um <%= str %>. Begrenzte Auflage 2015, Herbstausrüstung.",
"headSpecialFall2015MageText": "Genähter Hut",
- "headSpecialFall2015MageNotes": "Dieser Hut wurde mit jedem Nadelstich stärker. Erhöht Wahrnehmung um <%= per %>. Limited Edition 2015 Herbstausrüstung.",
+ "headSpecialFall2015MageNotes": "Dieser Hut wurde mit jedem Nadelstich stärker. Erhöht Wahrnehmung um <%= per %>. Begrenzte Auflage 2015, Herbstausrüstung.",
"headSpecialFall2015HealerText": "Froschhut",
- "headSpecialFall2015HealerNotes": "Dies ist ein sehr ernster Hut, der nur den meist fortgeschrittenen Zaubertranksbrauern würdig ist. Erhöht Intelligenz um <%= int %>. Limited Edition 2015 Herbstausrüstung.",
+ "headSpecialFall2015HealerNotes": "Dies ist ein sehr ernster Hut, der nur den meist fortgeschrittenen Zaubertranksbrauern würdig ist. Erhöht Intelligenz um <%= int %>. Begrenzte Auflage 2015, Herbstausrüstung.",
"headSpecialNye2015Text": "Lächerlicher Partyhut",
"headSpecialNye2015Notes": "Du hast einen lächerlichen Partyhut erhalten! Trag ihn mit Stolz, während Du ins neue Jahr hineinfeierst! Gewährt keinen Attributbonus.",
"headSpecialWinter2016RogueText": "Kakaohelm",
- "headSpecialWinter2016RogueNotes": "Der schützende Schal an diesem gemütlichen Helm wird nur abgenommen um warme Wintergetränke zu trinken. Erhöht Wahrnehmung um <%= per %>. Limited Edition 2015-2016 Winterausrüstung.",
+ "headSpecialWinter2016RogueNotes": "Der schützende Schal an diesem gemütlichen Helm wird nur abgenommen um warme Wintergetränke zu trinken. Erhöht Wahrnehmung um <%= per %>. Begrenzte Auflage 2015-2016, Winterausrüstung.",
"headSpecialWinter2016WarriorText": "Schneemannmütze",
- "headSpecialWinter2016WarriorNotes": "Brr! Dieser mächtige Helm ist wirklich leistungsfähig… bis er schmilzt. Erhöht Stärke um <%= str %>. Limited Edition 2015-2016 Winterausrüstung.",
- "headSpecialWinter2016MageText": "Snowboarder Kapuze",
- "headSpecialWinter2016MageNotes": "Schützt Deine Augen vor Schnee während Du Zaubersprüche webst. Erhöht Wahrnehmung um <%= per %>. Limited Edition 2015-2016 Winterausrüstung.",
+ "headSpecialWinter2016WarriorNotes": "Brr! Dieser mächtige Helm ist wirklich leistungsfähig… bis er schmilzt. Erhöht Stärke um <%= str %>. Begrenzte Auflage 2015-2016, Winterausrüstung.",
+ "headSpecialWinter2016MageText": "Snowboarder-Kapuze",
+ "headSpecialWinter2016MageNotes": "Schützt Deine Augen vor Schnee während Du Zaubersprüche webst. Erhöht Wahrnehmung um <%= per %>. Begrenzte Auflage 2015-2016, Winterausrüstung.",
"headSpecialWinter2016HealerText": "Feenflügelhelm",
- "headSpecialWinter2016HealerNotes": "DieseFlügelflatternsoschnelldasssieverschwimmen! Erhöht Intelligenz um <%= int %>. Limited Edition 2015-2016 Winterausrüstung.",
- "headSpecialSpring2016RogueText": "Guter Hund Maske",
+ "headSpecialWinter2016HealerNotes": "DieseFlügelflatternsoschnelldasssieverschwimmen! Erhöht Intelligenz um <%= int %>. Begrenzte Auflage 2015-2016, Winterausrüstung.",
+ "headSpecialSpring2016RogueText": "Guter-Hund-Maske",
"headSpecialSpring2016RogueNotes": "Ohh, was für ein süßer Welpe! Komm her und lass Dich über Deinen Kopf streicheln ... Hey, wo ist mein ganzes Gold hin? Erhöht Wahrnehmung um <%= per %>. Limitierte Edition 2016 Frühlingsausrüstung.",
- "headSpecialSpring2016WarriorText": "Maus Wächter Helm",
- "headSpecialSpring2016WarriorNotes": "Niemals wieder wird man dir eins über den Schädel ziehen! Lass sie nur kommen! Erhöht Stärke um <%= str %>. Limited Edition 2016 Frühlingsausrüstung.",
- "headSpecialSpring2016MageText": "Prachtvoller Lumpen Hut",
- "headSpecialSpring2016MageNotes": "Aufmachung, um Dich von den schnöden Wald und Wiesen Magiern dieser Welt abzuheben. Erhöht Wahrnehmung um <%= per %>. Limitierte Ausgabe 2016 Frühlingsausrüstung.",
- "headSpecialSpring2016HealerText": "Blumendiadem",
+ "headSpecialSpring2016WarriorText": "Mauswächter-Helm",
+ "headSpecialSpring2016WarriorNotes": "Niemals wieder wird man Dir eins über den Schädel ziehen! Lass sie nur kommen! Erhöht Stärke um <%= str %>. Begrenzte Auflage 2016, Frühlingsausrüstung.",
+ "headSpecialSpring2016MageText": "Prachtvoller Lumpenhut",
+ "headSpecialSpring2016MageNotes": "Aufmachung, um Dich von den schnöden Wald- und Wiesen-Magiern dieser Welt abzuheben. Erhöht Wahrnehmung um <%= per %>. Limitierte Ausgabe 2016 Frühlingsausrüstung.",
+ "headSpecialSpring2016HealerText": "Blütendiadem",
"headSpecialSpring2016HealerNotes": "Es schimmert mit der Kraft neuen Lebens bereit empor zu sprießen. Erhöht Intelligenz um <%= int %>. Limitierte Edition 2016 Frühlingsausrüstung.",
"headSpecialGaymerxText": "Regenbogenkriegerhelm",
"headSpecialGaymerxNotes": "Zur Feier der GaymerX-Konferenz ist dieser spezielle Helm dekoriert mit einem strahlenden, farbenfrohen Regenbogenmuster! GaymerX ist eine Videospiel-Tagung, die LGBTQ und Videospiele feiert und für alle offen ist.",
@@ -552,19 +560,23 @@
"headMystery201505Text": "Grüner Ritterhelm",
"headMystery201505Notes": "Die grüne Feder auf diesem Eisenhelm winkt stolz. Gewährt keinen Attributbonus. Mai 2015 Abonnentengegenstand.",
"headMystery201508Text": "Gepardenhut",
- "headMystery201508Notes": "Dieser Gepardenhut ist sehr flauschig! Gewährt keinen Attributbonus. Abonnentengegenstand August 2015",
+ "headMystery201508Notes": "Dieser Gepardenhut ist sehr flauschig! Gewährt keinen Attributbonus. Abonnentengegenstand, August 2015",
"headMystery201509Text": "Werwolfmaske",
"headMystery201509Notes": "Das IST doch eine Maske, nicht wahr? Gewährt keinen Attributbonus. Abonnentengegenstand, September 2015.",
"headMystery201511Text": "Baumstammkrone",
- "headMystery201511Notes": "Zähle die Ringe, um zu erfahren, wie alt diese Krone ist. Gewährt keinen Attributbonus. Abonnentengegenstand vom November 2015.",
+ "headMystery201511Notes": "Zähle die Ringe, um zu erfahren, wie alt diese Krone ist. Gewährt keinen Attributbonus. Abonnentengegenstand, November 2015.",
"headMystery201512Text": "Winterflamme",
- "headMystery201512Notes": "Diese Flammen brennen kalt mit purem Verstand. Gewährt keinen Attributbonus. Dezember 2015 Abonnentengegenstand.",
+ "headMystery201512Notes": "Diese Flammen brennen kalt mit purem Verstand. Gewährt keinen Attributbonus. Abonnentengegenstand, Dezember 2015.",
"headMystery201601Text": "Helm der wahren Entschlossenheit",
- "headMystery201601Notes": "Bleibe entschlossen, tapferer Held! Gewährt keinen Attributbonus. Januar 2016 Abonnentengegenstand.",
+ "headMystery201601Notes": "Bleibe entschlossen, tapferer Held! Gewährt keinen Attributbonus. Abonnentengegenstand, Januar 2016.",
"headMystery201602Text": "Herzensbrecher-Haube",
- "headMystery201602Notes": "Halte Deine Identität vor all Deinen Verehrern geheim. Gewährt keinen Attributbonus. Februar 2016 Abonnentengegenstand.",
+ "headMystery201602Notes": "Halte Deine Identität vor all Deinen Verehrern geheim. Gewährt keinen Attributbonus. Abonnentengegenstand, Februar 2016.",
"headMystery201603Text": "Glück-Hut",
- "headMystery201603Notes": "Dieser Zylinder ist ein magischer Glücksbringer. Gewährt keinen Attributbonus. März 2016 Abonnentengegenstand",
+ "headMystery201603Notes": "Dieser Zylinder ist ein magischer Glücksbringer. Gewährt keinen Attributbonus. Abonnentengegenstand, März 2016.",
+ "headMystery201604Text": "Blumenkrone",
+ "headMystery201604Notes": "Diese geflochtenen Blumen bilden einen erstaunlich robusten Helm! Gewährt keinen Attributbonus. April 2006 Abonnentengegenstand.",
+ "headMystery201605Text": "Marching Bard Hat",
+ "headMystery201605Notes": "Seventy-six dragons led the big parade, with a hundred and ten gryphons close at hand! Confers no benefit. May 2016 Subscriber Item.",
"headMystery301404Text": "Schicker Zylinder",
"headMystery301404Notes": "Ein schicker Zylinder für die feinsten Gentlemänner und -frauen! Januar 3015 Abonnentengegenstand. Gewährt keinen Attributbonus.",
"headMystery301405Text": "Einfacher Zylinder",
@@ -572,15 +584,15 @@
"headArmoireLunarCrownText": "Beruhigende Mondkrone",
"headArmoireLunarCrownNotes": "Diese Krone stärkt die Gesundheit und schärft die Sinne, besonders bei Vollmond. Erhöht Ausdauer um <%= con %> und Wahrnehmung um <%= per %>. Verzauberter Schrank: Beruhigendes Mondset (Gegenstand 1 von 3).",
"headArmoireRedHairbowText": "Rote Haarschleife",
- "headArmoireRedHairbowNotes": "Werde stark, taff und schlau, während Du diese hübsche Rote Haarschleife trägst! Erhöht Stärke um <%= str %>, Ausdauer um <%= con %> und Intelligenz um <%= int %>. Verzauberter Schrank: Unabhängiger Gegenstand.",
+ "headArmoireRedHairbowNotes": "Werde stark, taff und schlau, während Du diese hübsche rote Haarschleife trägst! Erhöht Stärke um <%= str %>, Ausdauer um <%= con %> und Intelligenz um <%= int %>. Verzauberter Schrank: Unabhängiger Gegenstand.",
"headArmoireVioletFloppyHatText": "Lila Schlapphut",
- "headArmoireVioletFloppyHatNotes": "Viele Zaubersprüche wurden in diesen einfachen Hut genäht, um ihm eine angenehme violette Farbe zu verpassen. Erhöht Wahrnehmung um <%= per %>, Intelligenz um <%= int %> und Ausdauer um <%= con %>. Verzauberter Schrank: Unabhängiger Gegenstand.",
+ "headArmoireVioletFloppyHatNotes": "Viele Zaubersprüche wurden in diesen einfachen Hut gewirkt, um ihm eine angenehme violette Farbe zu geben. Erhöht Wahrnehmung um <%= per %>, Intelligenz um <%= int %> und Ausdauer um <%= con %>. Verzauberter Schrank: Unabhängiger Gegenstand.",
"headArmoireGladiatorHelmText": "Gladiatorenhelm",
"headArmoireGladiatorHelmNotes": "Um ein Gladiator zu sein, musst Du nicht nur stark sein ... sondern auch gerissen. Erhöht Intelligenz um <%= int %> und Wahrnehmung um <%= per %>. Verzauberter Schrank: Gladiatorset (Gegenstand 1 von 3).",
"headArmoireRancherHatText": "Farmerhut",
"headArmoireRancherHatNotes": "Treibe Deine Haus- und Reittiere zusammen, während Du diesen zauberhaften Farmerhut trägst. Erhöht Stärke um <%= str %>, Wahrnehmung um <%= per %> und Intelligenz um <%= int %>. Verzauberter Schrank: Viehzüchter-Set (Gegenstand 1 von 3).",
"headArmoireBlueHairbowText": "Blaue Haarschleife",
- "headArmoireBlueHairbowNotes": "Werde scharfsinnig, taff und klug, wenn Du diese wunderschöne Blaue Haarschleife trägst! Erhöht Wahrnehmung um <%= per %>, Ausdauer um <%= con %> und Intelligenz um <%= int %>. Verzauberter Schrank: Unabhäniger Gegenstand.",
+ "headArmoireBlueHairbowNotes": "Werde scharfsinnig, taff und klug, wenn Du diese wunderschöne blaue Haarschleife trägst! Erhöht Wahrnehmung um <%= per %>, Ausdauer um <%= con %> und Intelligenz um <%= int %>. Verzauberter Schrank: Unabhäniger Gegenstand.",
"headArmoireRoyalCrownText": "Königliche Krone",
"headArmoireRoyalCrownNotes": "Ein Hoch auf den mächtigen und starken Herrscher! Erhöht Stärke um <%= str %>. Verzauberter Schrank: Königsset (Gegenstand 1 von 3).",
"headArmoireGoldenLaurelsText": "Goldene Lorbeeren",
@@ -588,14 +600,14 @@
"headArmoireHornedIronHelmText": "Gehörnter Eisenhelm",
"headArmoireHornedIronHelmNotes": "Dieser mit Leidenschaft aus Eisen gehämmerte, gehörnte Helm ist fast unzerbrechlich. Erhöht Ausdauer um <%= con %> und Stärke um <%= str %>. Verzauberter Schrank: Gehörntes Eisenset (Gegenstand 1 von 3)",
"headArmoireYellowHairbowText": "Gelbe Haarschleife",
- "headArmoireYellowHairbowNotes": "Werde scharfsinnig, taff und klug, wenn Du diese bezaubernde Gelbe Haarschleife trägst! Erhöht Wahrnehmung, Stärke und Intelligenz jeweils um <%= attrs %>. Verzauberter Schrank: Unabhängiger Gegenstand",
+ "headArmoireYellowHairbowNotes": "Werde scharfsinnig, taff und klug, wenn Du diese bezaubernde gelbe Haarschleife trägst! Erhöht Wahrnehmung, Stärke und Intelligenz jeweils um <%= attrs %>. Verzauberter Schrank: Unabhängiger Gegenstand",
"headArmoireRedFloppyHatText": "Roter Schlapphut",
- "headArmoireRedFloppyHatNotes": "Viele Zaubersprüche wurden auf diesen Hut gewirkt, die ihm seine leuchtend rote Farbe geben. Erhöht Ausdauer, Intelligenz und Wahrnehmung um jeweils <%= attrs %>. Verzauberter Schrank: Unabhängiger Gegenstand",
+ "headArmoireRedFloppyHatNotes": "Viele Zaubersprüche wurden auf diesen Hut gewirkt, um ihm seine leuchtend rote Farbe zu geben. Erhöht Ausdauer, Intelligenz und Wahrnehmung um jeweils <%= attrs %>. Verzauberter Schrank: Unabhängiger Gegenstand",
"headArmoirePlagueDoctorHatText": "Pestarzt-Hut",
- "headArmoirePlagueDoctorHatNotes": "Ein authentischer Hut wie ihn Ärzte tragen, die die Pest der Prokrastination bekämpfen! Erhöht Stärke um <%= str %>, Intelligenz um <%= str %> und Ausdauer um <%= con %>. Verzauberter Schrank: Pestarzt-Set (Gegenstand 1 von 3).",
- "headArmoireBlackCatText": "schwarzer Katzenhut",
+ "headArmoirePlagueDoctorHatNotes": "Ein authentischer Hut wie ihn Ärzte tragen, die die Pest des Aufschubs bekämpfen! Erhöht Stärke um <%= str %>, Intelligenz um <%= int %> und Ausdauer um <%= con %>. Verzauberter Schrank: Pestarzt-Set (Gegenstand 1 von 3).",
+ "headArmoireBlackCatText": "Schwarzer Katzenhut",
"headArmoireBlackCatNotes": "Dieser schwarze Hut ... schnurrt. Und sein Schwanz zuckt. Und er atmet? Okay, Du hast einfach bloß eine schlafende Katze auf dem Kopf. Erhöht Intelligenz und Wahrnehmung um jeweils <%= attrs %>. Verzauberter Schrank: Unabhängiger Gegenstand.",
- "headArmoireOrangeCatText": "oranger Katzenhut",
+ "headArmoireOrangeCatText": "Orangener Katzenhut",
"headArmoireOrangeCatNotes": "Dieser orangene Hut ... schnurrt. Und sein Schwanz zuckt. Und er atmet? Okay, Du hast einfach bloß eine schlafende Katze auf dem Kopf. Erhöht Intelligenz und Wahrnehmung um jeweils <%= attrs %>. Verzauberter Schrank: Unabhängiger Gegenstand.",
"headArmoireBlueFloppyHatText": "Blauer Schlapphut",
"headArmoireBlueFloppyHatNotes": "Viele Zaubersprüche wurden auf diesen Hut gewirkt, um ihm seine strahlend blaue Farbe zu geben. Erhöht Ausdauer, Intelligenz und Wahrnehmung um jeweils <%= attrs %>. Verzauberter Schrank: Unabhängiger Gegenstand",
@@ -603,16 +615,18 @@
"headArmoireShepherdHeaddressNotes": "Manchmal lieben es die Greifen, die Du hütest, auf dieser Kopfbedeckung herumzukauen, aber Du wirkst damit nichtsdestotrotz intelligenter. Erhöht Intelligenz um <%= int %>. Verzauberter Schrank: Hirten-Set (Gegenstand 3 von 3)",
"headArmoireCrystalCrescentHatText": "Kristalliner Mondsichelhut",
"headArmoireCrystalCrescentHatNotes": "Das Design auf diesem Hut nimmt mit den Mondphasen zu und ab. Erhöht Intelligenz und Wahrnehmung jeweils um <%= attrs %>. Verzauberter Schrank: Kristallines Mondsichelset (Gegenstand 1 von 3).",
- "headArmoireDragonTamerHelmText": "Drachenzähmer Helm",
- "headArmoireDragonTamerHelmNotes": "Du siehst genau wie ein Drache aus. Die perfekte Tarnung ... Erhöht Intelligenz um <%= int %>. Verzauberter Schrank: Drachenzähmer Set (Gegenstand 1 von 3)",
+ "headArmoireDragonTamerHelmText": "Drachenzähmer-Helm",
+ "headArmoireDragonTamerHelmNotes": "Du siehst genau wie ein Drache aus. Die perfekte Tarnung ... Erhöht Intelligenz um <%= int %>. Verzauberter Schrank: Drachenzähmer-Set (Gegenstand 1 von 3)",
"headArmoireBarristerWigText": "Richterperücke",
"headArmoireBarristerWigNotes": "Schon allein diese gelockte Perücke reicht, um dem wildesten Feind Angst einzujagen. Erhöht Stärke um <%= str %>. Verzauberter Schrank: Richterset (Gegenstand 1 von 3).",
"headArmoireJesterCapText": "Narrenkappe",
- "headArmoireJesterCapNotes": "Die Glöckchen an dieser Kappe könnten Dein Gegenüber ablenken, doch dir helfen sie einfach Dich zu konzentrieren. Erhöht Wahrnehmung um <%= per %>. Verzauberter Schrank: Narrenset (Gegenstand 1 von 3).",
+ "headArmoireJesterCapNotes": "Die Glöckchen an dieser Kappe könnten Dein Gegenüber ablenken, doch Dir helfen sie einfach Dich zu konzentrieren. Erhöht Wahrnehmung um <%= per %>. Verzauberter Schrank: Narrenset (Gegenstand 1 von 3).",
"headArmoireMinerHelmetText": "Bergmannshelm",
"headArmoireMinerHelmetNotes": "Schütze Deinen Kopf vor herunterfallenden Aufgaben! Erhöht Intelligenz um <%=int %>. Verzauberter Schrank: Bergmannsset (Gegenstand 1 von 3).",
- "headArmoireBasicArcherCapText": "Basic Archer Cap",
- "headArmoireBasicArcherCapNotes": "No archer would be complete without a jaunty cap! Increases Perception by <%= per %>. Enchanted Armoire: Basic Archer Set (Item 3 of 3).",
+ "headArmoireBasicArcherCapText": "Kappe des Bogenschützen",
+ "headArmoireBasicArcherCapNotes": "Kein Bogenschütze ist vollständig ohne diese leichte Kappe.\nErhöht Wahrnehmung um <%= per %>.\nVerzauberter Schrank: Standard-Bogenschützenset (Gegenstand 3 von 3).",
+ "headArmoireGraduateCapText": "Doktorandenhut",
+ "headArmoireGraduateCapNotes": "Gratulation! Für Dein tiefes Nachdenken hast Du diese Denkkappe erhalten. Erhöht Intelligenz um <&=int %>. Verzauberter Schrank: Doktoranden-Set (Gegenstand 3 von 3).",
"offhand": "Schildhand-Gegenstand",
"shieldBase0Text": "Keine Schildhand-Ausrüstung",
"shieldBase0Notes": "Kein Schild oder keine zweite Waffe.",
@@ -645,77 +659,79 @@
"shieldSpecialGoldenknightText": "Mustaines Morgenstern des Meilenstein-Zerquetschens",
"shieldSpecialGoldenknightNotes": "Meetings, Monster und Malaise: Alles erledigt! Zu Brei! Erhöht Ausdauer und Wahrnehmung jeweils um <%= attrs %>.",
"shieldSpecialYetiText": "Schild des Yeti-Zähmers",
- "shieldSpecialYetiNotes": "Dieser Schild reflektiert das Licht vom Schnee. Erhöht Ausdauer um <%= con %>. Limited Edition 2013-2014 Winterausrüstung",
+ "shieldSpecialYetiNotes": "Dieser Schild reflektiert das Licht vom Schnee. Erhöht Ausdauer um <%= con %>. Begrenzte Auflage 2013-2014, Winterausrüstung",
"shieldSpecialSnowflakeText": "Schneeflockenschild",
- "shieldSpecialSnowflakeNotes": "Jeder Schild ist ein Unikat. Erhöht Ausdauer um <%= con %>. Limited Edition 2013-2014 Winterausrüstung",
+ "shieldSpecialSnowflakeNotes": "Jeder Schild ist ein Unikat. Erhöht Ausdauer um <%= con %>. Begrenzte Auflage 2013-2014, Winterausrüstung",
"shieldSpecialSpringRogueText": "Hakenkrallen",
- "shieldSpecialSpringRogueNotes": "Sehr nützlich um hohe Gebäude zu erklimmen und ebenso um Teppiche zu zerfetzen. Erhöht Stärke um <%= str %>. Limited Edition 2014 Frühlingsausrüstung.",
+ "shieldSpecialSpringRogueNotes": "Sehr nützlich um hohe Gebäude zu erklimmen und ebenso um Teppiche zu zerfetzen. Erhöht Stärke um <%= str %>. Begrenzte Auflage 2014, Frühlingsausrüstung.",
"shieldSpecialSpringWarriorText": "Eischild",
- "shieldSpecialSpringWarriorNotes": "Dieses Schild zerbricht niemals, egal wie hart Du es schlägst! Erhöht Ausdauer um <%= con %>. Limited Edition 2014 Frühlingsausrüstung.",
+ "shieldSpecialSpringWarriorNotes": "Dieses Schild zerbricht niemals, egal wie hart Du es schlägst! Erhöht Ausdauer um <%= con %>. Begrenzte Auflage 2014, Frühlingsausrüstung.",
"shieldSpecialSpringHealerText": "Qietscheball des höchsten Schutzes",
- "shieldSpecialSpringHealerNotes": "Erzeugt ein entsetzliches, andauerndes Quietschen wenn man hineinbeißt und verscheucht so alle Gegner. Erhöht Ausdauer um <%= con %>. Limited Edition 2014 Frühlingsausrüstung.",
+ "shieldSpecialSpringHealerNotes": "Erzeugt ein entsetzliches, andauerndes Quietschen wenn man hineinbeißt und verscheucht so alle Gegner. Erhöht Ausdauer um <%= con %>. Begrenzte Auflage 2014, Frühlingsausrüstung.",
"shieldSpecialSummerRogueText": "Piratensäbel",
- "shieldSpecialSummerRogueNotes": "Avast! Du wirst diese täglichen Aufgaben über die Planke gehen lassen! Erhöht Stärke um <%= str %>. Limited Edition 2014 Sommerausrüstung.",
+ "shieldSpecialSummerRogueNotes": "Avast! Du wirst diese täglichen Aufgaben über die Planke gehen lassen! Erhöht Stärke um <%= str %>. Begrenzte Auflage 2014, Sommerausrüstung.",
"shieldSpecialSummerWarriorText": "Treibholzschild",
- "shieldSpecialSummerWarriorNotes": "Dieses Schild, das aus dem Holz untergegangener Schiffe hergestellt wurde, kann selbst die stürmischsten täglichen Aufgaben abschrecken. Erhöht Ausdauer um <%= con %>. Limited Edition 2014 Sommerausrüstung.",
+ "shieldSpecialSummerWarriorNotes": "Dieses Schild, das aus dem Holz untergegangener Schiffe hergestellt wurde, kann selbst die stürmischsten täglichen Aufgaben abschrecken. Erhöht Ausdauer um <%= con %>. Begrenzte Auflage 2014, Sommerausrüstung.",
"shieldSpecialSummerHealerText": "Schild der Untiefen",
- "shieldSpecialSummerHealerNotes": "Niemand wird es wagen, das Korallenriff anzugreifen, wenn er vor diesem glänzenden Schild steht! Erhöht Ausdauer um <%= con %>. Limited Edition 2014 Sommerausrüstung.",
+ "shieldSpecialSummerHealerNotes": "Niemand wird es wagen, das Korallenriff anzugreifen, wenn er vor diesem glänzenden Schild steht! Erhöht Ausdauer um <%= con %>. Begrenzte Auflage 2014, Sommerausrüstung.",
"shieldSpecialFallRogueText": "Silberner Pflock",
- "shieldSpecialFallRogueNotes": "Befördert Untote dauerhaft ins Jenseits. Notfalls auch gegen Werwölfe einsetzbar - Vielseitigkeit kann nie schaden. Erhöht Stärke um <%= str %>. Limited Edition 2014 Herbstausrüstung.",
+ "shieldSpecialFallRogueNotes": "Befördert Untote dauerhaft ins Jenseits. Notfalls auch gegen Werwölfe einsetzbar - Vielseitigkeit kann nie schaden. Erhöht Stärke um <%= str %>. Begrenzte Auflage 2014, Herbstausrüstung.",
"shieldSpecialFallWarriorText": "Machtvoller Trank der Wissenschaft",
- "shieldSpecialFallWarriorNotes": "Neigt rätselhafterweise dazu, auf Laborkittel verschüttet zu werden. Erhöht Ausdauer um <%= con %>. Limited Edition 2014 Herbstausrüstung.",
+ "shieldSpecialFallWarriorNotes": "Neigt rätselhafterweise dazu, auf Laborkittel verschüttet zu werden. Erhöht Ausdauer um <%= con %>. Begrenzte Auflage 2014, Herbstausrüstung.",
"shieldSpecialFallHealerText": "Juwelenschild",
- "shieldSpecialFallHealerNotes": "Dieser glitzernde Schild wurde in einem uralten Grab gefunden. Erhöht Ausdauer um <%= con %>. Limited Edition 2014 Herbstausrüstung.",
+ "shieldSpecialFallHealerNotes": "Dieser glitzernde Schild wurde in einem uralten Grab gefunden. Erhöht Ausdauer um <%= con %>. Begrenzte Auflage 2014, Herbstausrüstung.",
"shieldSpecialWinter2015RogueText": "Eiszapfen",
- "shieldSpecialWinter2015RogueNotes": "Du hast Sie wirklich, wahrhaftig und ungelogen gerade vom Boden aufgelesen. Erhöhen Stärke um <%= str %>. Limited Edition 2014-2015 Winterausrüstung.",
+ "shieldSpecialWinter2015RogueNotes": "Du hast Sie wirklich, wahrhaftig und ungelogen gerade vom Boden aufgelesen. Erhöhen Stärke um <%= str %>. Begrenzte Auflage 2014-2015, Winterausrüstung.",
"shieldSpecialWinter2015WarriorText": "Gummibonbonschild",
- "shieldSpecialWinter2015WarriorNotes": "Dieser scheinbar zuckersüße Schild besteht in Wirklichkeit aus nahrhaftem, gallertartigem Gemüse. Erhöht Ausdauer um <%= con %>. Limited Edition 2014-2015 Winterausrüstung",
+ "shieldSpecialWinter2015WarriorNotes": "Dieser scheinbar zuckersüße Schild besteht in Wirklichkeit aus nahrhaftem, gallertartigem Gemüse. Erhöht Ausdauer um <%= con %>. Begrenzte Auflage 2014-2015, Winterausrüstung",
"shieldSpecialWinter2015HealerText": "Beruhigendes Schild",
- "shieldSpecialWinter2015HealerNotes": "Dieser Schild wehrt den kalten Wind ab. Erhöht Ausdauer um <%= con %>. Limited Edition 2014-2015 Winterausrüstung",
+ "shieldSpecialWinter2015HealerNotes": "Dieser Schild wehrt den kalten Wind ab. Erhöht Ausdauer um <%= con %>. Begrenzte Auflage 2014-2015, Winterausrüstung",
"shieldSpecialSpring2015RogueText": "Explodierendes Quieken",
- "shieldSpecialSpring2015RogueNotes": "Lass Dich nicht täuschen von dem was Du hörst - dieser Sprengstoff packt einen Wumms. Erhöht Stärke um <%= str %>. Limited Edition 2015 Frühlingsausrüstung.",
+ "shieldSpecialSpring2015RogueNotes": "Lass Dich nicht täuschen - dieser Sprengstoff hat ordentlich Kraft. Erhöht Stärke um <%= str %>. Limited Edition Gegenstand Frühjahr 2015.",
"shieldSpecialSpring2015WarriorText": "Tellerdiskus",
- "shieldSpecialSpring2015WarriorNotes": "Schleudere ihn auf Deine Feinde ... oder halte ihn einfach in der Hand, denn er wird sich zur Essenszeit mit leckerem Trockenfutter füllen. Erhöht Ausdauer um <%= con %>. Limited Edition 2015 Frühlingsausrüstung.",
+ "shieldSpecialSpring2015WarriorNotes": "Schleudere ihn auf Deine Feinde ... oder halte ihn einfach in der Hand, denn er wird sich zur Essenszeit mit leckerem Trockenfutter füllen. Erhöht Ausdauer um <%= con %>. Begrenzte Auflage 2015, Frühlingsausrüstung.",
"shieldSpecialSpring2015HealerText": "Gemustertes Kissen",
- "shieldSpecialSpring2015HealerNotes": "Du kannst Dich auf diesem weichen Kissen ausruhen oder Du kannst es mit Deinen furchterregenden Klauen zerreißen. Fauch! Erhöht Ausdauer um <%= con %>. Limited Edition 2015 Frühlingsausrüstung.",
+ "shieldSpecialSpring2015HealerNotes": "Du kannst Dich auf diesem weichen Kissen ausruhen oder Du kannst es mit Deinen furchterregenden Klauen zerreißen. Fauch! Erhöht Ausdauer um <%= con %>. Begrenzte Auflage 2015, Frühlingsausrüstung.",
"shieldSpecialSummer2015RogueText": "Feuernde Koralle",
- "shieldSpecialSummer2015RogueNotes": "Diese Gattung der feuernden Koralle hat die Fähigkeit, ihr Gift durch das Wasser zu schießen. Erhöht Stärke um <%= str %>. Limited Edition 2015 Sommerausrüstung.",
+ "shieldSpecialSummer2015RogueNotes": "Diese Gattung der feuernden Koralle hat die Fähigkeit, ihr Gift durch das Wasser zu schießen. Erhöht Stärke um <%= str %>. Begrenzte Auflage 2015, Sommerausrüstung.",
"shieldSpecialSummer2015WarriorText": "Sonnenbarsch-Schild",
- "shieldSpecialSummer2015WarriorNotes": "Dieser, wie Sand und Meer glänzende, Schild aus Tiefseemetall wurde von den Kunsthandwerkern aus Dilatory hergestellt. Erhöht Ausdauer um <%= con %>. Limited Edition 2015 Sommerausrüstung.",
+ "shieldSpecialSummer2015WarriorNotes": "Dieser, wie Sand und Meer glänzende, Schild aus Tiefseemetall wurde von den Kunsthandwerkern aus Dilatory hergestellt. Erhöht Ausdauer um <%= con %>. Begrenzte Auflage 2015, Sommerausrüstung.",
"shieldSpecialSummer2015HealerText": "Eingefasster Schild",
- "shieldSpecialSummer2015HealerNotes": "Benutze dieses Schild um Bilgenratten wegzuschlagen. Erhöht Ausdauer um <%= con %>. Limited Edition 2015 Sommerausrüstung.",
+ "shieldSpecialSummer2015HealerNotes": "Benutze dieses Schild um Bilgenratten wegzuschlagen. Erhöht Ausdauer um <%= con %>. Begrenzte Auflage 2015, Sommerausrüstung.",
"shieldSpecialFall2015RogueText": "Kampfaxt",
- "shieldSpecialFall2015RogueNotes": "Furchterregende tägliche Aufgaben ducken sich unter den Schlägen dieser Axt. Erhöht Stärke um <%= str %>. Limited Edition 2015 Herbstausrüstung.",
+ "shieldSpecialFall2015RogueNotes": "Furchterregende To-Dos ducken sich unter den Schlägen dieser Axt. Erhöht Stärke um <%= str %>. Begrenzte Auflage 2015, Herbstausrüstung.",
"shieldSpecialFall2015WarriorText": "Vogelfutterbeutel",
- "shieldSpecialFall2015WarriorNotes": "Ja Du solltest die Krähen ERSCHRECKEN, aber ein paar Freunde zu gewinnen kann nicht schaden! Erhöht Ausdauer um <%= con %>. Limited Edition 2015 Herbstausrüstung.",
+ "shieldSpecialFall2015WarriorNotes": "Ja, Du solltest die Krähen ERSCHRECKEN, aber ein paar Freunde zu gewinnen kann nicht schaden! Erhöht Ausdauer um <%= con %>. Begrenzte Auflage 2015, Herbstausrüstung.",
"shieldSpecialFall2015HealerText": "Rührstab",
- "shieldSpecialFall2015HealerNotes": "Mit diesem Stab kannst Du alles umrühren, ohne dass er schmilzt, sich auflöst oder in Flammen ausbricht. Er kann ebenso dazu benutzt werden heftig in bösen Aufgaben herumzustochern. Erhöht Ausdauer um <%= con %>. Limited Edition 2015 Herbstausrüstung.",
+ "shieldSpecialFall2015HealerNotes": "Mit diesem Stab kannst Du alles umrühren, ohne dass er schmilzt, sich auflöst oder in Flammen ausbricht. Er kann ebenso dazu benutzt werden heftig in bösen Aufgaben herumzustochern. Erhöht Ausdauer um <%= con %>. Begrenzte Auflage 2015, Herbstausrüstung.",
"shieldSpecialWinter2016RogueText": "Kakaobecher",
- "shieldSpecialWinter2016RogueNotes": "Wärmendes Getränk oder kochendes Wurfgeschoss? Du entscheidest… Erhöht Stärke um <%= str %>. Limited Edition 2015-2016 Winterausrüstung.",
+ "shieldSpecialWinter2016RogueNotes": "Wärmendes Getränk oder kochendes Wurfgeschoss? Du entscheidest … Erhöht Stärke um <%= str %>. Begrenzte Auflage 2015-2016, Winterausrüstung.",
"shieldSpecialWinter2016WarriorText": "Schlittenschild",
- "shieldSpecialWinter2016WarriorNotes": "Benutze diesen Schlitten um Angriffe abzublocken oder um darauf triumphierend in die Schlacht zu fahren! Erhöht Ausdauer um <%= con %>. Limited Edition 2015-2016 Winterausrüstung.",
- "shieldSpecialWinter2016HealerText": "Pixie Geschenk",
- "shieldSpecialWinter2016HealerNotes": "Öffne es öffne es öffne es öffne es öffne es öffne es!!!!!!!!! Erhöht Ausdauer um <%= con %>. Limited Edition 2015-2016 Winterausrüstung.",
- "shieldSpecialSpring2016RogueText": "Feuer Bolas",
- "shieldSpecialSpring2016RogueNotes": "Du hast sowohl den Ball, die Keule und das Messer gemeistert. Jetzt bist Du bereit mit Feuer zu jonglieren! Awoo! Erhöht Stärke um <%= str %>. Limitierte Edition 2016 Frühlingsausrüstung.",
+ "shieldSpecialWinter2016WarriorNotes": "Benutze diesen Schlitten um Angriffe abzublocken oder um darauf triumphierend in die Schlacht zu fahren! Erhöht Ausdauer um <%= con %>. Begrenzte Auflage 2015-2016, Winterausrüstung.",
+ "shieldSpecialWinter2016HealerText": "Pixie-Geschenk",
+ "shieldSpecialWinter2016HealerNotes": "Öffne es öffne es öffne es öffne es öffne es öffne es!!!!!!!!! Erhöht Ausdauer um <%= con %>. Begrenzte Auflage 2015-2016, Winterausrüstung.",
+ "shieldSpecialSpring2016RogueText": "Feuerbolas",
+ "shieldSpecialSpring2016RogueNotes": "Du hast sowohl den Ball, die Keule und das Messer gemeistert. Jetzt bist Du bereit mit Feuer zu jonglieren! Awoo! Erhöht Stärke um <%= str %>. Begrenzte Auflage 2016, Frühlingsausrüstung.",
"shieldSpecialSpring2016WarriorText": "Käserad",
- "shieldSpecialSpring2016WarriorNotes": "Du musstest viele teuflische Fallen überwinden um diese verteidigungs-boostende Nahrung zu ergattern. Erhöht Ausdauer um <%= con %>. Limited Edition 2016 Frühlingsausrüstung.",
+ "shieldSpecialSpring2016WarriorNotes": "Du musstest viele teuflische Fallen überwinden um diese verteidigungs-boostende Nahrung zu ergattern. Erhöht Ausdauer um <%= con %>. Begrenzte Auflage 2016, Frühlingsausrüstung.",
"shieldSpecialSpring2016HealerText": "Kleiner Blumenschild",
- "shieldSpecialSpring2016HealerNotes": "Der April Fool behauptet, dass dieser kleine Schild Shiny Seeds abwehren kann. Glaub ihm kein Wort! Erhöht Ausdauer um <%= con %>. Limited Edition 2016 Frühlingsausrüstung.",
+ "shieldSpecialSpring2016HealerNotes": "Der April-Scherzkeks behauptet, dass dieser kleine Schild die schimmernde Saat abwehren kann. Glaub ihm kein Wort! Erhöht Ausdauer um <%= con %>. Begrenzte Auflage 2016, Frühlingsausrüstung.",
"shieldMystery201601Text": "Töter der Vorsätze",
- "shieldMystery201601Notes": "Diese Klinge kann zur Entfernung aller Ablenkungen verwendet werden. Gewährt keinen Attributbonus. Januar 2016 Abonnentengegenstand.",
+ "shieldMystery201601Notes": "Diese Klinge kann zur Entfernung aller Ablenkungen verwendet werden. Gewährt keinen Attributbonus. Abonnentengegenstand, Januar 2016.",
"shieldMystery301405Text": "Uhrenschild",
"shieldMystery301405Notes": "Die Zeit ist auf Deiner Seite mit diesem gewaltigen Uhrenschild! Gewährt keinen Attributbonus. Juni 3015 Abonnentengegenstand.",
"shieldArmoireGladiatorShieldText": "Gladiatorschild",
"shieldArmoireGladiatorShieldNotes": "Um ein Gladiator zu sein, musst Du ... naja, egal, schlag sie einfach mit Deinem Schild. Erhöht Ausdauer um <%= con %> und Stärke um <%= str %>. Verzauberter Schrank: Gladiatorset (Gegenstand 3 von 3).",
"shieldArmoireMidnightShieldText": "Mitternachtsschild",
- "shieldArmoireMidnightShieldNotes": "Dieses Schild ist am mächtigsten um Punkt Mitternacht! Erhöht Ausdauer um <%= con %> und Stärke um <%= str %>. Verzauberter Schrank: Unabhängiger Gegenstand.",
+ "shieldArmoireMidnightShieldNotes": "Dieser Schild ist am mächtigsten um Punkt Mitternacht! Erhöht Ausdauer um <%= con %> und Stärke um <%= str %>. Verzauberter Schrank: Unabhängiger Gegenstand.",
"shieldArmoireRoyalCaneText": "Königlicher Stock",
"shieldArmoireRoyalCaneNotes": "Ein Hoch auf den besungenen Herrscher! Erhöht Ausdauer, Intelligenz und Wahrnehmung jeweils um <%= attrs %>. Verzauberter Schrank: Königsset (Gegenstand 2 von 3).",
- "shieldArmoireDragonTamerShieldText": "Drachenzähmer Schild",
+ "shieldArmoireDragonTamerShieldText": "Drachenzähmer-Schild",
"shieldArmoireDragonTamerShieldNotes": "Lenke Deine Feinde mit diesem Schild in Drachenform ab. Erhöht Wahrnehmung um <%= per %>. Verzauberter Schrank: Drachenzähmer-Set (Gegenstand 2 von 3).",
"shieldArmoireMysticLampText": "Wunderlampe",
"shieldArmoireMysticLampNotes": "Erleuchte die dunkelsten Höhlen mit dieser Wunderlampe! Erhöht Wahrnehmung um <%= per %>. Verzauberter Schrank: Unabhängiger Gegenstand.",
+ "shieldArmoireFloralBouquetText": "Blumenstrauss",
+ "shieldArmoireFloralBouquetNotes": "Hilft nicht viel in der Schlacht, aber sind er nicht einfach schön? Erhöht Ausdauer um <%= con %>. Verzauberter Schrank: Unabhängiger Gegenstand.",
"back": "Rückenschmuck",
"backBase0Text": "Kein Rückenschmuck",
"backBase0Notes": "Kein Rückenschmuck.",
@@ -730,9 +746,9 @@
"backMystery201507Text": "Cooles Surfboard",
"backMystery201507Notes": "Brande vor den Fleißigen Docks und reite die Wellen der Unvollständigkeitsbucht! Gewährt keinen Attributbonus. Abonnentengegenstand, Juli 2015.",
"backMystery201510Text": "Koboldschwanz",
- "backMystery201510Notes": "Zum Greifen geeignet und mächtig! Gewährt keinen Attributbonus. Oktober 2015 Abonnentengegenstand.",
+ "backMystery201510Notes": "Zum Greifen geeignet und mächtig! Gewährt keinen Attributbonus. Abonnentengegenstand, Oktober 2015.",
"backMystery201602Text": "Herzensbrecher-Umhang",
- "backMystery201602Notes": "Beim Rascheln Deines Umhangs liegen Deine Feinde Dir zu Füßen! Gewährt keinen Attributbonus. Februar 2016 Abonnentengegenstand.",
+ "backMystery201602Notes": "Beim Rascheln Deines Umhangs liegen Deine Feinde Dir zu Füßen! Gewährt keinen Attributbonus. Abonnentengegenstand, Februar 2016.",
"backSpecialWonderconRedText": "Mächtiger Umhang",
"backSpecialWonderconRedNotes": "Strotzt vor Stärke und Schönheit. Gewährt keinen Attributbonus. Special Edition Convention-Gegenstand.",
"backSpecialWonderconBlackText": "Tückischer Umhang",
@@ -747,62 +763,62 @@
"bodySpecialWonderconBlackText": "Ebenholzkragen",
"bodySpecialWonderconBlackNotes": "Ein fescher Ebenholzkragen! Gewährt keinen Attributbonus. Special Edition Convention-Gegenstand.",
"bodySpecialSummerMageText": "Glänzender Kurzumhang",
- "bodySpecialSummerMageNotes": "Weder Salzwasser noch frisches Wasser kann diesen metallischen Kurzumhang beflecken. Gewährt keinen Attributbonus. Limited Edition 2014 Sommerausrüstung.",
+ "bodySpecialSummerMageNotes": "Weder Salzwasser noch frisches Wasser kann diesen metallischen Kurzumhang beflecken. Gewährt keinen Attributbonus. Begrenzte Auflage 2014, Sommerausrüstung.",
"bodySpecialSummerHealerText": "Korallenkragen",
- "bodySpecialSummerHealerNotes": "Limited Edition 2014 Sommer-Set. Ein stylischer Kragen aus lebendigen Korallen! Gewährt keinen Attributbonus.",
+ "bodySpecialSummerHealerNotes": "Begrenzte Auflage 2014, Sommer-Set. Ein stylischer Kragen aus lebendigen Korallen! Gewährt keinen Attributbonus.",
"bodySpecialSummer2015RogueText": "Abtrünnigenschärpe",
- "bodySpecialSummer2015RogueNotes": "Du kannst kein richtiger Abtrünniger sein ohne Ausdruckskraft ... und einer Schärpe. Gewährt keinen Attributbonus. Limited Edition 2015 Sommerausrüstung.",
+ "bodySpecialSummer2015RogueNotes": "Du kannst kein richtiger Abtrünniger sein ohne Ausdruckskraft ... und einer Schärpe. Gewährt keinen Attributbonus. Begrenzte Auflage 2015, Sommerausrüstung.",
"bodySpecialSummer2015WarriorText": "Meeresstacheln",
- "bodySpecialSummer2015WarriorNotes": "Jeder Stachel gibt Quallengift ab, um den Träger zu verteidigen. Gewährt keinen Attributbonus. Limited Edition 2015 Sommerausrüstung.",
+ "bodySpecialSummer2015WarriorNotes": "Jeder Stachel gibt Quallengift ab, um den Träger zu verteidigen. Gewährt keinen Attributbonus. Begrenzte Auflage 2015, Sommerausrüstung.",
"bodySpecialSummer2015MageText": "Goldene Schnalle",
- "bodySpecialSummer2015MageNotes": "Diese Schnalle besitzt überhaupt keine Stärke, aber sie glänzt! Gewährt keinen Attributbonus. Limited Edition 2015 Sommerausrüstung.",
+ "bodySpecialSummer2015MageNotes": "Diese Schnalle besitzt überhaupt keine Stärke, aber sie glänzt! Gewährt keinen Attributbonus. Begrenzte Auflage 2015, Sommerausrüstung.",
"bodySpecialSummer2015HealerText": "Matrosenhalstuch",
- "bodySpecialSummer2015HealerNotes": "Yo ho ho? No, no, no! Gewährt keinen Attributbonus. Limited Edition 2015 Sommerausrüstung.",
+ "bodySpecialSummer2015HealerNotes": "Yo ho ho? No, no, no! Gewährt keinen Attributbonus. Begrenzte Auflage 2015, Sommerausrüstung.",
"headAccessory": "Kopfschmuck",
"accessories": "Accessoires",
"animalEars": "Tierohren",
"headAccessoryBase0Text": "Kein Kopfschmuck",
"headAccessoryBase0Notes": "Kein Kopfschmuck",
"headAccessorySpecialSpringRogueText": "Lila Katzenohren",
- "headAccessorySpecialSpringRogueNotes": "Diese Katzenohren sind aufgestellt um eventuelle Bedrohungen zu orten. Gewährt keinen Attributbonus. Limited Edition 2014 Frühlingsausrüstung.",
+ "headAccessorySpecialSpringRogueNotes": "Diese Katzenohren sind aufgestellt um eventuelle Bedrohungen zu orten. Gewährt keinen Attributbonus. Begrenzte Auflage 2014, Frühlingsausrüstung.",
"headAccessorySpecialSpringWarriorText": "Grüne Hasenohren",
- "headAccessorySpecialSpringWarriorNotes": "Hasenohren, die jedes Karottenknacken aufs genaueste wahrnehmen. Gewährt keinen Attributbonus. Limited Edition 2014 Frühlingsausrüstung.",
+ "headAccessorySpecialSpringWarriorNotes": "Hasenohren, die jedes Karottenknacken aufs genaueste wahrnehmen. Gewährt keinen Attributbonus. Begrenzte Auflage 2014, Frühlingsausrüstung.",
"headAccessorySpecialSpringMageText": "Blaue Mausohren",
- "headAccessorySpecialSpringMageNotes": "Diese runden Mauseohren sind seidenweich. Gewährt keinen Attributbonus. Limited Edition 2014 Frühlingsausrüstung.",
+ "headAccessorySpecialSpringMageNotes": "Diese runden Mauseohren sind seidenweich. Gewährt keinen Attributbonus. Begrenzte Auflage 2014, Frühlingsausrüstung.",
"headAccessorySpecialSpringHealerText": "Gelbe Hundeohren",
- "headAccessorySpecialSpringHealerNotes": "Niedliche Schlappohren. Komm, spiel mit mir! Gewährt keinen Attributbonus. Limited Edition 2014 Frühlingsausrüstung.",
+ "headAccessorySpecialSpringHealerNotes": "Niedliche Schlappohren. Komm, spiel mit mir! Gewährt keinen Attributbonus. Begrenzte Auflage 2014, Frühlingsausrüstung.",
"headAccessorySpecialSpring2015RogueText": "Gelbe Mauseohren",
- "headAccessorySpecialSpring2015RogueNotes": "Diese Ohren stählen sich gegen Explosionsgeräusche. Gewährt keinen Attributbonus. Limited Edition 2015 Frühlingsausrüstung.",
+ "headAccessorySpecialSpring2015RogueNotes": "Diese Ohren stählen sich gegen Explosionsgeräusche. Gewährt keinen Attributbonus. Begrenzte Auflage 2015, Frühlingsausrüstung.",
"headAccessorySpecialSpring2015WarriorText": "Violette Hundeohren",
- "headAccessorySpecialSpring2015WarriorNotes": "Sie sind violett. Es sind Hundeohren. Verschwende Deine Zeit nicht mit weiteren Unsinnigkeiten. Gewährt keinen Attributbonus. Limited Edition 2015 Frühlingsausrüstung.",
+ "headAccessorySpecialSpring2015WarriorNotes": "Sie sind violett. Es sind Hundeohren. Verschwende Deine Zeit nicht mit weiteren Unsinnigkeiten. Gewährt keinen Attributbonus. Begrenzte Auflage 2015, Frühlingsausrüstung.",
"headAccessorySpecialSpring2015MageText": "Blaue Häschenohren",
- "headAccessorySpecialSpring2015MageNotes": "Diese Ohren lauschen eifrig, falls irgendwo ein Magier Geheimnisse offenbart. Gewährt keinen Attributbonus. Limited Edition 2015 Frühlingsausrüstung.",
+ "headAccessorySpecialSpring2015MageNotes": "Diese Ohren lauschen eifrig, falls irgendwo ein Magier Geheimnisse offenbart. Gewährt keinen Attributbonus. Begrenzte Auflage 2015, Frühlingsausrüstung.",
"headAccessorySpecialSpring2015HealerText": "Grüne Kätzchenohren",
- "headAccessorySpecialSpring2015HealerNotes": "Diese süßen Kätzchenohren machen andere grün vor Neid. Gewährt keinen Attributbonus. Limited Edition 2015 Frühlingsausrüstung.",
+ "headAccessorySpecialSpring2015HealerNotes": "Diese süßen Kätzchenohren machen andere grün vor Neid. Gewährt keinen Attributbonus. Begrenzte Auflage 2015, Frühlingsausrüstung.",
"headAccessorySpecialSpring2016RogueText": "Grüne Hundeohren",
- "headAccessorySpecialSpring2016RogueNotes": "HIermit kannst Du gewiefte Magier, sogar wenn sie sich unsichtbar machen, im Auge behalten! Gewährt keinen Attributbonus. Limited Edition 2016 Frühlingsausrüstung.",
- "headAccessorySpecialSpring2016WarriorText": "Rote Mauseohren",
- "headAccessorySpecialSpring2016WarriorNotes": "Für den idealen Sound Deines 'Theme Songs' inmitten ohrenbetäubender Schlachtfelder. Gewährt keinen Attributbonus. Limited Edition 2016 Frühlingsausrüstung.",
+ "headAccessorySpecialSpring2016RogueNotes": "Hiermit kannst Du gewiefte Magier im Auge behalten, sogar wenn sie sich unsichtbar machen! Gewährt keinen Attributbonus. Begrenzte Auflage 2016, Frühlingsausrüstung.",
+ "headAccessorySpecialSpring2016WarriorText": "Rote Mausohren",
+ "headAccessorySpecialSpring2016WarriorNotes": "Für den idealen Sound Deines 'Theme Songs' inmitten ohrenbetäubender Schlachtfelder. Gewährt keinen Attributbonus. Begrenzte Auflage 2016, Frühlingsausrüstung.",
"headAccessorySpecialSpring2016MageText": "Gelbe Katzenohren",
- "headAccessorySpecialSpring2016MageNotes": "Diese wachsamen Ohren lassen Dich sogar das kleinste Summen von atmosphärischem Mana wahrnehmen, oder die gedämpften Schritte eines Schurken. Gewährt keinen Attributbonus. Limited Edition 2016 Frühlingsausrüstung.",
+ "headAccessorySpecialSpring2016MageNotes": "Diese wachsamen Ohren lassen Dich sogar das kleinste Summen von atmosphärischem Mana wahrnehmen, oder die gedämpften Schritte eines Schurken. Gewährt keinen Attributbonus. Begrenzte Auflage 2016, Frühlingsausrüstung.",
"headAccessorySpecialSpring2016HealerText": "Violette Hasenohren",
- "headAccessorySpecialSpring2016HealerNotes": "Sie stehen wie Flaggen über dem Getümmel um zu markieren wo einem Hilfe zukommen kann. Gewährt keinen Attributbonus. Limited Edition 2016 Frühlingsausrüstung.",
+ "headAccessorySpecialSpring2016HealerNotes": "Sie stehen wie Flaggen über dem Getümmel, um zu markieren wo einem Hilfe zukommen kann. Gewährt keinen Attributbonus. Begrenzte Auflage 2016, Frühlingsausrüstung.",
"headAccessoryBearEarsText": "Bärchenohren",
"headAccessoryBearEarsNotes": "Diese Ohren verleihen Dir das Aussehen eines tapferen Bären! Gewährt keinen Attributbonus.",
"headAccessoryCactusEarsText": "Kaktusohren",
"headAccessoryCactusEarsNotes": "Diese Ohren verleihen Dir das Aussehen eines stacheligen Kaktus! Gewährt keinen Attributbonus.",
"headAccessoryFoxEarsText": "Fuchsohren",
- "headAccessoryFoxEarsNotes": "Diese Ohren verleihen dir das Aussehen eines listigen Fuchses! Gewährt keinen Attributbonus.",
+ "headAccessoryFoxEarsNotes": "Diese Ohren verleihen Dir das Aussehen eines listigen Fuchses! Gewährt keinen Attributbonus.",
"headAccessoryLionEarsText": "Löwenohren",
- "headAccessoryLionEarsNotes": "Diese Ohren verleihen dir das Aussehen eines königlichen Löwen! Gewährt keinen Attributbonus.",
+ "headAccessoryLionEarsNotes": "Diese Ohren verleihen Dir das Aussehen eines königlichen Löwen! Gewährt keinen Attributbonus.",
"headAccessoryPandaEarsText": "Pandaohren",
- "headAccessoryPandaEarsNotes": "Diese Ohren verleihen dir das Aussehen eines sanftmütigen Pandas! Gewährt keinen Attributbonus.",
+ "headAccessoryPandaEarsNotes": "Diese Ohren verleihen Dir das Aussehen eines sanftmütigen Pandas! Gewährt keinen Attributbonus.",
"headAccessoryPigEarsText": "Schweinchenohren",
- "headAccessoryPigEarsNotes": "Diese Ohren verleihen dir das Aussehen eines drolligen Schweinchens! Gewährt keinen Attributbonus.",
+ "headAccessoryPigEarsNotes": "Diese Ohren verleihen Dir das Aussehen eines drolligen Schweinchens! Gewährt keinen Attributbonus.",
"headAccessoryTigerEarsText": "Tigerohren",
- "headAccessoryTigerEarsNotes": "Diese Ohren verleihen dir das Aussehen eines wilden Tigers! Gewährt keinen Attributbonus.",
+ "headAccessoryTigerEarsNotes": "Diese Ohren verleihen Dir das Aussehen eines wilden Tigers! Gewährt keinen Attributbonus.",
"headAccessoryWolfEarsText": "Wolfsohren",
- "headAccessoryWolfEarsNotes": "Diese Ohren verleihen dir das Aussehen eines loyalen Wolfes! Gewährt keinen Attributbonus.",
+ "headAccessoryWolfEarsNotes": "Diese Ohren verleihen Dir das Aussehen eines loyalen Wolfes! Gewährt keinen Attributbonus.",
"headAccessoryMystery201403Text": "Waldwanderergeweih",
"headAccessoryMystery201403Notes": "Diese Geweihe schimmern in Moos und Flechten. Gewährt keinen Attributbonus. März 2014 Abonnentengegenstand.",
"headAccessoryMystery201404Text": "Schmetterlingsfühler des Zwielichts",
@@ -815,21 +831,35 @@
"headAccessoryMystery201510Notes": "Diese schreckenerregenden Hörner sind ein wenig schleimig. Gewährt keinen Attributbonus. Oktober 2015 Abonnentengegenstand.",
"headAccessoryMystery301405Text": "Kopf-Brille",
"headAccessoryMystery301405Notes": "\"Brillen sind für die Augen,\" haben sie gesagt. \"Niemand will Brillen, die man nur auf dem Kopf tragen kann,\" haben sie gesagt. Ha! Da hast Du es ihnen aber ordentlich gezeigt! Gewährt keinen Attributbonus. August 3015 Abonnentengegenstand.",
- "headAccessoryArmoireComicalArrowText": "Comical Arrow",
- "headAccessoryArmoireComicalArrowNotes": "This whimsical item doesn't provide a stat boost, but it sure is good for a laugh! Confers no benefit. Enchanted Armoire: Independent Item.",
+ "headAccessoryArmoireComicalArrowText": "Komischer Pfeil",
+ "headAccessoryArmoireComicalArrowNotes": "Dieser wunderliche Gegenstand bietet keinen Status-Bonus, aber er ist sicher gut für einen Lacher.\nGewährt keinen Attributbonus.\nVerzauberter Schrank: Unabhängiger Gegenstand.",
"eyewear": "Brillen",
"eyewearBase0Text": "Keine Brille",
"eyewearBase0Notes": "Keine Brille.",
+ "eyewearSpecialBlackTopFrameText": "Schwarze Standardbrille",
+ "eyewearSpecialBlackTopFrameNotes": "Brille mit einem schwarzen Gestell über den Linsen. Gewährt keinen Attributbonus.",
+ "eyewearSpecialBlueTopFrameText": "Blaue Standardbrille",
+ "eyewearSpecialBlueTopFrameNotes": "Brille mit einem blauen Gestell über den Linsen. Gewährt keinen Attributbonus.",
+ "eyewearSpecialGreenTopFrameText": "Grüne Standardbrille",
+ "eyewearSpecialGreenTopFrameNotes": "Brille mit einem grünen Gestell über den Linsen. Gewährt keinen Attributbonus.",
+ "eyewearSpecialPinkTopFrameText": "Pinke Standardbrille",
+ "eyewearSpecialPinkTopFrameNotes": "Brille mit einem pinken Gestell über den Linsen. Gewährt keinen Attributbonus.",
+ "eyewearSpecialRedTopFrameText": "Rote Standardbrille",
+ "eyewearSpecialRedTopFrameNotes": "Brille mit einem roten Gestell über den Linsen. Gewährt keinen Attributbonus.",
+ "eyewearSpecialWhiteTopFrameText": "Weisse Standardbrille",
+ "eyewearSpecialWhiteTopFrameNotes": "Brille mit einem weissen Gestell über den Linsen. Gewährt keinen Attributbonus.",
+ "eyewearSpecialYellowTopFrameText": "Gelbe Standardbrille",
+ "eyewearSpecialYellowTopFrameNotes": "Brille mit einem gelben Gestell über den Linsen. Gewährt keinen Attributbonus.",
"eyewearSpecialSummerRogueText": "Schurkische Augenklappe",
- "eyewearSpecialSummerRogueNotes": "Man muss kein Halunke sein um zu sehen, wie stilvoll das ist! Gewährt keinen Attributbonus. Limited Edition 2014 Sommerausrüstung.",
+ "eyewearSpecialSummerRogueNotes": "Man muss kein Halunke sein um zu sehen, wie stilvoll das ist! Gewährt keinen Attributbonus. Begrenzte Auflage 2014, Sommerausrüstung.",
"eyewearSpecialSummerWarriorText": "Schneidige Augenklappe",
- "eyewearSpecialSummerWarriorNotes": "Man muss kein Schlingel sein um zu sehen, wie stilvoll das ist! Gewährt keinen Attributbonus. Limited Edition 2014 Sommerausrüstung.",
+ "eyewearSpecialSummerWarriorNotes": "Man muss kein Schlingel sein um zu sehen, wie stilvoll das ist! Gewährt keinen Attributbonus. Begrenzte Auflage 2014, Sommerausrüstung.",
"eyewearSpecialWonderconRedText": "Mächtige Maske",
"eyewearSpecialWonderconRedNotes": "Was für ein beeindruckender Kopfschmuck! Gewährt keinen Attributbonus. Special Edition Convention-Rüstung.",
"eyewearSpecialWonderconBlackText": "Tückische Maske",
"eyewearSpecialWonderconBlackNotes": "Deine Vorhaben sind mit Sicherheit absolut rechtmäßig. Gewährt keinen Attributbonus. Special Edition Convention Gegenstand.",
"eyewearMystery201503Text": "Aquamarinblaue Brille",
- "eyewearMystery201503Notes": "Pikse dir mit diesen schimmernden Edelsteinen nicht ins Auge! Gewährt keinen Attributbonus. März 2015 Abonnentengegenstand.",
+ "eyewearMystery201503Notes": "Pikse Dir mit diesen schimmernden Edelsteinen nicht ins Auge! Gewährt keinen Attributbonus. März 2015 Abonnentengegenstand.",
"eyewearMystery201506Text": "Neonfarbener Schnorchel",
"eyewearMystery201506Notes": "Mit diesem neonfarbenen Schnorchel kann der Träger unter Wasser sehen. Gewährt keinen Attributbonus. Abonnentengegenstand, Juni 2015.",
"eyewearMystery201507Text": "Coole Sonnenbrille",
@@ -839,5 +869,5 @@
"eyewearMystery301405Text": "Monokel",
"eyewearMystery301405Notes": "Es gibt nichts schickeres vor den Augen als ein Monokel - außer vielleicht einer Brille. Gewährt keinen Attributbonus. April 3015 Abonnentengegenstand.",
"eyewearArmoirePlagueDoctorMaskText": "Pestarzt-Maske",
- "eyewearArmoirePlagueDoctorMaskNotes": "Eine authentische Maske wie sie Ärzte tragen, die die Pest der Prokrastination bekämpfen! Gewährt keinen Attributbonus. Verzauberter Schrank: Pestarzt-Set (Gegenstand 2 von 3)."
+ "eyewearArmoirePlagueDoctorMaskNotes": "Eine authentische Maske wie sie Ärzte tragen, die die Pest des Aufschubs bekämpfen! Gewährt keinen Attributbonus. Verzauberter Schrank: Pestarzt-Set (Gegenstand 2 von 3)."
}
\ No newline at end of file
diff --git a/common/locales/de/generic.json b/common/locales/de/generic.json
index c6c71eea35..3cba5836dd 100644
--- a/common/locales/de/generic.json
+++ b/common/locales/de/generic.json
@@ -8,7 +8,7 @@
"titleBackgrounds": "Ambiente",
"titleStats": "Werte & Erfolge",
"titleProfile": "Profil",
- "titleInbox": "Postfach",
+ "titleInbox": "Posteingang",
"titleTavern": "Gasthaus",
"titleParty": "Gruppe",
"titleHeroes": "Halle der Helden",
@@ -21,7 +21,7 @@
"titleMounts": "Reittiere",
"titleEquipment": "Ausrüstung",
"titleTimeTravelers": "Mysteriöse Zeitreisende",
- "titleSeasonalShop": "Saisonaler Shop",
+ "titleSeasonalShop": "Jahreszeitenmarkt",
"titleSettings": "Einstellungen",
"expandToolbar": "Werkzeugleiste erweitern",
"collapseToolbar": "Werkzeugleiste minimieren",
@@ -90,7 +90,7 @@
"achievementDilatory": "Retter von Dilatory",
"achievementDilatoryText": "Hat beim 2014 Summer Splash Event dabei geholfen, den Schreckensdrachen von Dilatory zu besiegen!",
"costumeContest": "Kostümwettbewerbsteilnehmer",
- "costumeContestText": "Hat am Habitoween Kostümwettbewerb teilgenommen. Schau einige Einträge im Habitica Blog an!",
+ "costumeContestText": "Hat am Habitoween-Kostümwettbewerb teilgenommen. Schau einige Einträge im Habitica Blog an!",
"costumeContestTextPlural": "Hat an <%= number %> Habitoween Kostümwettbewerben teilgenommen. Schau einige Einträge im Habitica Blog an!",
"memberSince": "- Teilnehmer seit",
"lastLoggedIn": "- Zuletzt eingeloggt",
@@ -116,7 +116,7 @@
"audioTheme_luneFoxTheme": "Lunefox's Motiv",
"askQuestion": "Stelle eine Frage",
"reportBug": "Melde einen Fehler",
- "HabiticaWiki": "Das Habitica Wiki",
+ "HabiticaWiki": "Das Habitica-Wiki",
"HabiticaWikiFrontPage": "http://habitica.wikia.com/wiki/Habitica_Wiki",
"contributeToHRPG": "Wirke bei Habitica mit",
"overview": "Übersicht für neue Nutzer",
@@ -137,8 +137,8 @@
"achievementStressbeastText": "Hat geholfen während des Winter Wunderland 2014 das Abscheuliche Stressbiest zu besiegen!",
"achievementBurnout": "Retter der Blühenden Felder",
"achievementBurnoutText": "Hat beim 2015 Herbstfestival Event dabei geholfen, Burnout zu besiegen und die Geister der Erschöpfung wiederherzustellen!",
- "achievementBewilder": "Savior of Mistiflying",
- "achievementBewilderText": "Helped defeat the Be-Wilder during the 2016 Spring Fling Event!",
+ "achievementBewilder": "Retter von Mistiflying",
+ "achievementBewilderText": "Hat beim Frühlingsevent 2016 geholfen, den Verwirrer zu besiegen.",
"checkOutProgress": "Schau Dir meinen Fortschritt in Habitica an!",
"cardReceived": "Du hast eine Karte erhalten!",
"cardReceivedFrom": "<%= cardType %>, Von: <%= cardType %>",
@@ -165,7 +165,7 @@
"birthdayCardNotes": "Schicke einem Gruppenmitglied eine Geburtstagskarte.",
"birthday0": "Alles Gute zum Geburtstag!",
"birthdayCardAchievementTitle": "Geburtstags-Bonanza",
- "birthdayCardAchievementText": "Viele fröhliche Wiedersehen! Verschicke, oder erhalte <%= cards %> Geburtstagsgrußkarten.",
+ "birthdayCardAchievementText": "Viele fröhliche Wiedersehen! Hat <%= cards %> Geburtstagsgrußkarten verschickt oder erhalten.",
"streakAchievement": "Du hast einen Strähnenerfolg erzielt!",
"firstStreakAchievement": "21-Tage Strähne",
"streakAchievementCount": "<%= streaks %> 21-Tage-Strähnen",
diff --git a/common/locales/de/groups.json b/common/locales/de/groups.json
index 176c811951..3f7d51a212 100644
--- a/common/locales/de/groups.json
+++ b/common/locales/de/groups.json
@@ -2,7 +2,7 @@
"tavern": "Gasthaus-Chat",
"innCheckOut": "Das Gasthaus verlassen",
"innCheckIn": "Im Gasthaus erholen",
- "innText": "Du erholst Dich im Gasthaus! Während Du dort verweilst, werden dir Deine täglichen Aufgaben keinen Schaden zufügen, aber trotzdem täglich aktualisiert werden. Vorsicht: Wenn Du an einem Bosskampf teilnimmst, erhältst Du weiterhin Schaden für die verpassten Aufgaben Deiner Gruppenmitglieder, sofern sich diese nicht auch im Gasthaus befinden! Außerdem wird der Schaden, den Du dem Boss zufügst, (und gefundene Gegenstände) erst angewendet, wenn Du das Gasthaus verlässt.",
+ "innText": "Du erholst Dich im Gasthaus! Während Du dort verweilst, werden Dir Deine täglichen Aufgaben keinen Schaden zufügen, aber trotzdem täglich aktualisiert werden. Vorsicht: Wenn Du an einem Bosskampf teilnimmst, erhältst Du weiterhin Schaden für die verpassten Aufgaben Deiner Gruppenmitglieder, sofern sich diese nicht auch im Gasthaus befinden! Außerdem wird der Schaden, den Du dem Boss zufügst, (und gefundene Gegenstände) erst angewendet, wenn Du das Gasthaus verlässt.",
"innTextBroken": "Du erholst Dich im Gasthaus, schätze ich ... Während Du dort verweilst, werden Dir Deine täglichen Aufgaben keinen Schaden zufügen, aber trotzdem täglich aktualisiert ... Wenn Du an einem Bosskampf teilnimmst, erhältst Du weiterhin Schaden für die verpassten Aufgaben Deiner Gruppenmitglieder ... sofern sich diese nicht auch im Gasthaus befinden ... Außerdem wird Dein Schaden am Boss (oder gesammelte Gegenstände) nicht berücksichtigt, bis Du das Gasthaus verlässt ... So müde ...",
"lfgPosts": "Nach Gruppeneinträgen suchen",
"tutorial": "Anleitung",
@@ -10,26 +10,26 @@
"wiki": "Wiki",
"reportAP": "Problem melden",
"requestAF": "Feature vorschlagen",
- "community": "Community Forum",
+ "community": "Community-Forum",
"dataTool": "Werkzeug zur Datenanzeige",
"resources": "Ressourcen",
- "askQuestionNewbiesGuild": "Stelle eine Frage (Die Neulinggilde)",
+ "askQuestionNewbiesGuild": "Stelle eine Frage (Die Newbies-Gilde)",
"tavernTalk": "Gasthausgespräche",
"tavernAlert1": "Achtung: Wenn Du einen Bug meldest, sehen die Entwickler es hier nicht. Bitte",
- "tavernAlert2": "nutze GitHub stattdessen",
- "moderatorIntro1": "Die Moderatoren des Gasthauses und der Gruppe sind;",
+ "tavernAlert2": "nutze stattdessen GitHub",
+ "moderatorIntro1": "Die Moderatoren des Gasthauses und der Gruppe sind:",
"communityGuidelines": "Community-Richtlinien",
- "communityGuidelinesRead1": "Bitte lese unsere",
+ "communityGuidelinesRead1": "Bitte lies unsere",
"communityGuidelinesRead2": "vor dem Chatten.",
"party": "Gruppe",
"createAParty": "Gruppe erstellen",
"updatedParty": "Gruppeneinstellungen wurden aktualisiert.",
- "noPartyText": "Entweder bist Du noch in keiner Gruppe oder Deine Gruppe dauert etwas länger, um zu laden. Du kannst eine neue Gruppe erstellen und Freunde einladen. Wenn Du stattdessen einer bestehenden Gruppe beitreten willst, sag ihnen, dass sie Deine persönliche User ID eingeben sollen und komm dann zurück hierher um nach der Einladung zu suchen:",
+ "noPartyText": "Entweder bist Du noch in keiner Gruppe oder Deine Gruppe dauert etwas länger, um zu laden. Du kannst eine neue Gruppe erstellen und Freunde einladen. Wenn Du stattdessen einer bestehenden Gruppe beitreten willst, sag ihnen, dass sie Deine persönliche Benutzer-ID eingeben sollen und komm dann zurück hierher um nach der Einladung zu suchen:",
"LFG": "Um Deine Gruppe bekannt zu machen oder um eine zu finden, der Du beitreten kannst, gehe zur <%= linkStart %>Gruppe gesucht<%= linkEnd %>-Gilde.",
- "wantExistingParty": "Willst Du Dich bestehenden Gruppen anschließen? Besuche die <%= linkStart %> Gruppe gesucht <%= linkEnd %>-Gilde und poste diese Benutzer ID:",
- "joinExistingParty": "Trete der Gruppe eines anderen bei",
+ "wantExistingParty": "Willst Du Dich bestehenden Gruppen anschließen? Besuche die <%= linkStart %> Gruppe gesucht <%= linkEnd %>-Gilde und poste diese Benutzer-ID:",
+ "joinExistingParty": "Tritt der Gruppe eines anderen bei",
"create": "Erstellen",
- "userId": "Benutzer ID",
+ "userId": "Benutzer-ID",
"invite": "Einladen",
"leave": "Verlassen",
"invitedTo": "Du wurdest zu <%= name %> eingeladen.",
@@ -48,9 +48,9 @@
"newGroupName": "<%= groupType %> Name",
"groupName": "Gruppenname",
"groupLeader": "Gruppenleiter",
- "groupID": "Gruppen ID",
+ "groupID": "Gruppen-ID",
"groupDescr": "Beschreibung auf der öffentlichen Gildenliste (Markdown Okay)",
- "logoUrl": "Logo URL",
+ "logoUrl": "Logo-URL",
"assignLeader": "Gruppenleiter bestimmen",
"members": "Mitglieder",
"partyList": "Reihenfolge der Gruppenmitglieder in der Kopfzeile",
@@ -78,7 +78,7 @@
"sortJoined": "Nach Beitrittsdatum sortieren",
"sortName": "Nach Avatarname sortieren",
"sortBackgrounds": "Nach Hintergrund sortieren",
- "sortHabitrpgJoined": "Sortieren nach Habitica Beitrittsdatum",
+ "sortHabitrpgJoined": "Sortieren nach Habitica-Beitrittsdatum",
"sortHabitrpgLastLoggedIn": "Sortieren nach wer war zuletzt eingeloggt",
"ascendingSort": "Aufsteigend sortieren",
"descendingSort": "Absteigend sortieren",
@@ -92,6 +92,7 @@
"send": "Abschicken",
"messageSentAlert": "Nachricht abgeschickt",
"pmHeading": "Private Nachricht an <%= name %>",
+ "pmsMarkedRead": "Deine private Nachricht wurde als gelesen markiert",
"clearAll": "Lösche alle Nachrichten",
"confirmDeleteAllMessages": "Bist Du sicher, dass Du alle Nachrichten im Posteingang löschen möchtest? Andere Benutzer können immer noch die Nachrichten sehen, die Du ihnen geschickt hast.",
"optOutPopover": "Du willst keine privaten Nachrichten? Klick hier um sie zu deaktivieren",
@@ -99,6 +100,15 @@
"unblock": "Ent-Blockieren",
"pm-reply": "Eine Antwort schicken",
"inbox": "Postfach",
+ "messageRequired": "Eine Nachricht wird benötigt.",
+ "toUserIDRequired": "Eine Benutzer-ID wird benötigt",
+ "gemAmountRequired": "Eine Anzahl an Edelsteinen wird benötigt",
+ "notAuthorizedToSendMessageToThisUser": "Nachricht kann nicht an diesen Benutzer verschickt werden.",
+ "privateMessageGiftIntro": "Hallo <%= receiverName %>, Hallo <%= receiverName %>, hat Dir geschickt:",
+ "privateMessageGiftGemsMessage": "<%= gemAmount %> Edelsteine!",
+ "privateMessageGiftSubscriptionMessage": "<%= numberOfMonths %> Monate Abonnement!",
+ "cannotSendGemsToYourself": "Edelsteine können nicht an Dich selbst geschickt werden. Versuche es stattdessen mit einem Abonnement.",
+ "badAmountOfGemsToSend": "Die Menge muss zwischen 1 und deiner aktuellen Edelsteinanzahl liegen.",
"abuseFlag": "Verletzung der Community-Richtlinien melden",
"abuseFlagModalHeading": "<%= name %> melden?",
"abuseFlagModalBody": "Möchtest Du diesen Beitrag wirklich melden? DU solltest AUSSCHLIESSLICH Beiträge melden, die unsere <%= firstLinkStart %>Community-Richtlinien<%= linkEnd %> und/oder unsere <%= secondLinkStart %>Nutzungsbedingungen<%= linkEnd %> verletzen. Das ungerechtfertigte Melden von Beiträgen stellt eine Verletzung der Community-Richtlinien dar und kann geahndet werden. Gute Gründe einen Beitraqg zu melden sind unter anderem:
Fluchen, Religiöse Schwüre
Intoleranz, herabwürdigende Bezeichnung jeglicher Ethnien
Nicht jugendfreie Themen
Gewalt, auch innerhalb eines Witzes
Spam, unsinnige Nachrichten
",
@@ -118,12 +128,12 @@
"inviteByEmailExplanation": "Wenn Freunde über Deine E-Mail zu Habitica stoßen werden sie automatisch zu Deiner Gruppe eingeladen!",
"inviteFriendsNow": "Jetzt Freunde einladen",
"inviteFriendsLater": "Später Freunde einladen",
- "inviteAlertInfo": "Wenn Du bereits Freunde bei Habitica hast, kannst Du die über Ihre Benutzer ID hier einladen.",
+ "inviteAlertInfo": "Wenn Du bereits Freunde bei Habitica hast, kannst Du sie über Ihre Benutzer-ID hier einladen.",
"inviteExistUser": "Bestehende Benutzer einladen",
"byColon": "Von:",
"inviteNewUsers": "Neue Nutzer einladen",
"sendInvitations": "Einladungen verschicken",
- "invitationsSent": "Einladungen versendet!",
+ "invitationsSent": "Einladungen verschickt!",
"inviteAlertInfo2": "Oder teile diesen Link (kopieren/einfügen):",
"sendGiftHeading": "Sende Geschenk an <%= name %>",
"sendGiftGemsBalance": "Von <%= number %> Edelsteinen",
@@ -151,5 +161,29 @@
"partyUpName": "Party!",
"partyOnName": "Riesenparty!",
"partyUpAchievement": "Du bist einer Gruppe mit einer anderen Person beigetreten! Viel Spaß beim Kampf gegen Monster und gegenseitigen Unterstützen.",
- "partyOnAchievement": "Du bist einer Gruppe mit mindestens vier Personen beigetreten! Genieße die gestiegene Zurechenbarkeit, wenn Du Dich mit Deinen Freunden vereinst, um eure Feinde zu bezwingen!"
+ "partyOnAchievement": "Du bist einer Gruppe mit mindestens vier Personen beigetreten! Genieße die gestiegene Zurechenbarkeit, wenn Du Dich mit Deinen Freunden vereinst, um eure Feinde zu bezwingen!",
+ "groupIdRequired": "\"groupId\" muss eine gültige UUID sein",
+ "groupNotFound": "Gruppe nicht gefunden.",
+ "groupTypesRequired": "Du musst einen gültigen \"Type\" Suchbegriff eingeben.",
+ "questLeaderCannotLeaveGroup": "Du kannst Deine Gruppe nicht verlassen, wenn Du eine Quest gestartet hat. Brich die Quest zuvor ab.",
+ "cannotLeaveWhileActiveQuest": "Du kannst Deine Gruppe nicht während einer aktiven Quest verlassen. Bitte verlasse zuerst die Quest.",
+ "onlyLeaderCanRemoveMember": "Nur der Gruppenleiter kann Mitglieder entfernen!",
+ "memberCannotRemoveYourself": "Du kannst Dich nicht selbst entfernen!",
+ "groupMemberNotFound": "Benutzer nicht unter den Gruppenmitgliedern gefunden",
+ "mustBeGroupMember": "Muss ein Mitglied der Gruppe sein.",
+ "keepOrRemoveAll": "req.query.keep muss entweder \"keep-all\" oder \"remove-all\" sein",
+ "keepOrRemove": "req.query.keep muss entweder \"keep\" oder \"remove\" sein",
+ "canOnlyInviteEmailUuid": "Es kann nur mittels UUID oder E-Mail eingeladen werden.",
+ "inviteMissingEmail": "Fehlende E-Mail-Adresse zum Einladen.",
+ "partyMustbePrivate": "Gruppen müssen privat sein",
+ "userAlreadyInGroup": "Benutzer bereits in dieser Gruppe.",
+ "userAlreadyInvitedToGroup": "Benutzer bereits zu dieser Gruppe eingeladen.",
+ "userAlreadyPendingInvitation": "Benutzereinladung noch unbeantwortet.",
+ "userAlreadyInAParty": "Benutzer bereits in einer Gruppe.",
+ "userWithIDNotFound": "Benutzer mit ID \"<%= userId %>\" nicht gefunden",
+ "userHasNoLocalRegistration": "Benutzer ist lokal nicht registriert (Benutzername, E-Mail, Passwort).",
+ "uuidsMustBeAnArray": "Benutzer-ID-Einladungen müssen ein Array sein.",
+ "emailsMustBeAnArray": "E-Mail-Adress-Einladungen müssen ein Array sein.",
+ "canOnlyInviteMaxInvites": "Du kannst nur \"<%= maxInvites %>\" Benutzer gleichzeitig einladen",
+ "onlyCreatorOrAdminCanDeleteChat": "Löschen der Nachricht nicht erlaubt!"
}
\ No newline at end of file
diff --git a/common/locales/de/limited.json b/common/locales/de/limited.json
index b58ec70166..5138b4eee0 100644
--- a/common/locales/de/limited.json
+++ b/common/locales/de/limited.json
@@ -1,24 +1,24 @@
{
"limitedEdition": "Limitierte Edition",
- "seasonalEdition": "Jahreszeiten Edition",
+ "seasonalEdition": "Jahreszeiten-Edition",
"winterColors": "Winterfarben",
"annoyingFriends": "Nervige Freunde",
"annoyingFriendsText": "Hat sich <%= snowballs %> Schneebälle von Gruppenmitgliedern eingefangen.",
"alarmingFriends": "Unheimliche Freunde",
- "alarmingFriendsText": "Wurde <%= spookDust %> mal von Gruppenmitgliedern erschreckt.",
+ "alarmingFriendsText": "Wurde <%= spookySparkles %> mal von Gruppenmitgliedern erschreckt.",
"agriculturalFriends": "Gartenbegeisterte Freunde",
"agriculturalFriendsText": "Wurde <%= seeds %> mal von Gruppenmitgliedern in eine Blume verwandelt.",
"aquaticFriends": "Wasserliebende Freunde",
"aquaticFriendsText": "Wurde <%= seafoam %> mal von Gruppenmitgliedern mit Meeresschaum nassgespritzt.",
"valentineCard": "Valentinstagskarte",
- "valentineCardExplanation": "Dafür, dass ihr so ein zuckersüßes Gedicht ertragen habt, erhaltet ihr beide das \"Liebevolle Freunde\" Abzeichen",
+ "valentineCardExplanation": "Dafür, dass ihr so ein zuckersüßes Gedicht ertragen habt, erhaltet ihr beide das Abzeichen \"Liebevolle Freunde\"!",
"valentineCardNotes": "Einem Gruppenmitglied eine Valentinskarte schicken.",
- "valentine0": "\"Rosen sind rot\n\nMeine Aufgabenliste ist klein\n\nIch bin froh mit dir \n\nIn einer Gruppe zu sein!\"",
+ "valentine0": "\"Rosen sind rot\n\nMeine Aufgaben sind blau\n\nIch bin froh mit Dir\n\nin einer Gruppe zu sein!\"",
"valentine1": "\"Rosen sind rot\n\nKämpfe bringen Zaster\n\nLos, schließ Dich mir an\n\nUnd wir besiegen die Laster!",
"valentine2": "\"Rosen sind rot\n\nDieses Gedicht ist mir hold\n\nIch hoffe Du magst es\n\nDenn mich kostet's 10 Gold.\"",
"valentine3": "\"Rosen sind rot\n\nEisdrachen sind blau\n\nOhne Deine Begleitung\n\nIst Habitica grau!\"",
"valentineCardAchievementTitle": "Heißgeliebte Freunde",
- "valentineCardAchievementText": "Ohh, Deine Freunde und Du, ihr müsst euch ja wirklich gern haben. Du hast insgesamt <%= cards %> Valentinstagskarten gesendet und bekommen.",
+ "valentineCardAchievementText": "Ohh, Deine Freunde und Du, ihr müsst euch ja wirklich gern haben! Du hast insgesamt <%= cards %> Valentinstagskarten gesendet und bekommen.",
"polarBear": "Eisbär",
"turkey": "Truthahn",
"gildedTurkey": "Vergoldeter Truthahn",
@@ -33,7 +33,7 @@
"seasonalShopFallText": "Willkommen auf dem Jahreszeitenmarkt!! Momentan haben wir Herbstgegenstände auf Lager. Alles hier wird während des Herbstevents, das jedes Jahr stattfindet, verfügbar sein. Allerdings nur bis zum 31. Oktober, also stocke jetzt Deine Vorräte auf, ansonsten musst Du bis nächstes Jahr warten.",
"seasonalShopWinterText": "Willkommen auf dem Jahreszeitenmarkt!! Momentan haben wir Saisonale Edition Gegenstände auf Lager. Alles hier wird während des Winter-Wunderlandes, das jedes Jahr stattfindet, verfügbar sein. Allerdings nur bis 31. Januar, also decke Dich jetzt ein, sonst musst Du bis nächstes Jahr warten!",
"seasonalShopFallTextBroken": "Willkommen auf dem Jahreszeitenmarkt!! Momentan haben wir Herbstgegenstände auf Lager. Alles hier wird während des Herbstevents, das jedes Jahr stattfindet, verfügbar sein. Allerdings nur bis zum 31. Oktober, also stocke jetzt Deine Vorräte auf, ansonsten musst Du bis nächstes Jahr warten ... *hach*",
- "seasonalShopRebirth": "Wenn Du die Sphäre der Wiedergeburt benutzt hast, kannst Du diesen Ausrüstungsgegenstand in der Belohnungsspalte erneut kaufen. Anfangs wirst Du nur Gegenstände für Deine momentane Klasse (Krieger) kaufen können, aber keine Sorge, die anderen klassenspezifischen Gegenstände werden verfügbar, sobald Du zu der jeweiligen Klasse wechselst.",
+ "seasonalShopRebirth": "Wenn Du die Sphäre der Wiedergeburt benutzt hast, kannst Du diesen Ausrüstungsgegenstand in der Belohnungsspalte erneut kaufen. Anfangs wirst Du nur Gegenstände für Deine momentane Klasse (Krieger) kaufen können, aber keine Sorge, die anderen klassenspezifischen Gegenstände werden verfügbar, sobald Du zur jeweiligen Klasse wechselst.",
"candycaneSet": "Zuckerstange (Magier)",
"skiSet": "Ski-Attentäter (Schurke)",
"snowflakeSet": "Schneeflocke (Heiler)",
@@ -44,7 +44,7 @@
"gingerbreadSet": "Lebkuchenkrieger (Krieger)",
"toAndFromCard": "An: <%= toName %>, Von: <%= fromName %>",
"nyeCard": "Neujahrskarte",
- "nyeCardExplanation": "Dafür, dass ihr gemeinsam Neujahr gefeiert habt, erhaltet ihr beide die \"Alte Bekannte\" Auszeichnung!",
+ "nyeCardExplanation": "Dafür, dass ihr gemeinsam Neujahr gefeiert habt, erhaltet ihr beide die Auszeichnung \"Alte Bekannte\"!",
"nyeCardNotes": "Einem Gruppenmitglied eine Neujahrskarte schicken.",
"seasonalItems": "Saisonaler Artikel",
"nyeCardAchievementTitle": "Alte(r) Bekannte(r)",
@@ -72,5 +72,6 @@
"comfortingKittySet": "Tröstendes Kätzchen (Heiler)",
"sneakySqueakerSet": "Raffinierter Raffzahn (Schurke)",
"fallEventAvailability": "Verfügbar bis zum 31. Oktober",
- "winterEventAvailability": "Verfügbar bis zum 31. Dezember"
+ "winterEventAvailability": "Verfügbar bis zum 31. Dezember",
+ "springEventAvailability": "Verfügbar bis zum 31. Mai"
}
\ No newline at end of file
diff --git a/common/locales/de/maintenance.json b/common/locales/de/maintenance.json
new file mode 100644
index 0000000000..2d357319d4
--- /dev/null
+++ b/common/locales/de/maintenance.json
@@ -0,0 +1,34 @@
+{
+ "habiticaBackSoon": "Keine Sorge, Habitica ist bald zurück!",
+ "importantMaintenance": "Wir müssen wichtige Wartungen durchführen, die vermutlich bis <%= localDate %> in Deiner Zeitzone dauern.",
+ "maintenance": "Wartungsarbeiten",
+ "maintenanceMoreInfo": "Willst Du mehr über die Wartungsarbeiten wissen? <%= linkStart %>Schau auf unsere Infoseite<%= linkEnd %>.",
+ "noDamageKeepStreaks": "Du wirst KEINEN Schaden erleiden oder Strähnen verlieren!",
+ "thanksForPatience": "Danke für Deine Geduld!",
+ "twitterMaintenanceUpdates": "Für die neuesten Nachrichten, schau auf Twitter, wo wir Statusinformationen posten werden.",
+ "veteranPetAward": "Am Ende wirst Du ein Veteranenhaustier erhalten!",
+
+ "maintenanceInfoTitle": "Information zu bevorstehenden Wartungsarbeiten auf Habitica",
+ "maintenanceInfoWhat": "Was passiert?",
+ "maintenanceInfoWhatText": "Am 21. Mai wird Habitica für eine Weile wegen Wartungsarbeiten unerreichbar sein. Du wirst keinen Schaden erleiden und Dein Konto wird nicht negativ beeinflusst, selbst wenn Du Dich nicht rechtzeitig einloggen kannst, um Deine täglichen Aufgaben abzuhaken! Wir arbeiten hart daran, die Auszeit so kurz wie möglich zu halten und werden regelmäßige Updates auf unserem Twitter Account posten. Am Ende der Wartungszeit wirst Du als Dankeschön für Deine Geduld ein seltenes Haustier erhalten!",
+ "maintenanceInfoWhy": "Warum passiert das?",
+ "maintenanceInfoWhyText": "In den letzten Monaten haben wir Habitica hinter den Kulissen gründlich aufgeräumt. Wir haben vor allem die API neu geschrieben. Obwohl es auf der Oberfläche nicht wirklich anders aussieht, findet sich darunter eine komplett neue Welt. Das wird uns SEHR viel mehr Flexibilität beim Erstellen neuer Features bringen und führt zu verbesserter Performance!",
+ "maintenanceInfoTechDetails": "Möchtest Du mehr über die technische Seite des Prozesses erfahren? Besuche Die Schmiede, unseren Entwicklerblog.",
+ "maintenanceInfoMore": "Mehr Informationen",
+ "maintenanceInfoAccountChanges": "Welche Änderungen werde ich in meinem Account sehen, wenn die Wartungsarbeiten fertig sind?",
+ "maintenanceInfoAccountChangesText": "Anfangs wird es keine merklichen Änderungen außer den Performanceverbesserungen für Features wie Wettbewerbe geben. Falls Du Änderungen bemerkst, die es nicht geben sollte, schreib uns eine E-Mail unter admin@habitica.com und wir werden sie für Dich untersuchen!",
+ "maintenanceInfoAddFeatures": "Welche Features werden dadurch auf Habitica möglich?",
+ "maintenanceInfoAddFeaturesText": "Mit Abschluss dieser Wartungen wird es möglich, den Chat und die Gilden zu verbessern, die Pläne für Organisationen und Familien und mehr Produktivitätsfeatures umzusetzen, wie monatliche Aufgaben und die Möglichkeit, gestrige Aktivitäten aufzuzeichnen! Das alles sind große Features, es wird also etwas Dauern, sie umzusetzen, aber bis zum Ende dieser Wartungsarbeiten gab es keinen Weg, sie überhaupt anzufangen.",
+ "maintenanceInfoHowLong": "Wie lange werden die Wartungsarbeiten dauern?",
+ "maintenanceInfoHowLongText": "Wir müssen die Aufgaben und Daten aller 1,3 Millionen Habitica-Benutzer migrieren -- keine einfache Aufgabe! Wir nehmen an dass wir etwa von 13:00 Pacific Time (20:00 UTC) and 22:00 Pacific Time (05:00 UTC) benötigen. Wir werden alles tun um das so schnell wie möglich über die Bühne zu bringen! Du kannst den Updates auf Twitter folgen.",
+ "maintenanceInfoStatsAffected": "Wie werden meine täglichen Aufgaben, Strähnen, aktivierte Bonusse und Quests beeinflusst?",
+ "maintenanceInfoStatsAffectedText1": "Du wirst an diesem Wochenende KEINEN Schaden erleiden oder Strähnen verlieren, ansonsten wird der Tag aber ganz normal abgehandelt! Tägliche Aufgaben, die Du erledigt hast, werden wieder geöffnet, aktivierte Bonusse zurückgesetzt usw. Wenn Du in einer Sammelqueste bist, wirst Du weiterhin Gegenstände finden. In einem Bosskampf wirst Du dem Boss Schaden zufügen aber der Boss wird Dir dieses mal nichts anhaben (schließlich brauchen auch Monster mal eine Pause!).",
+ "maintenanceInfoStatsAffectedText2": "Nach langen Überlegungen hat unser Team beschlossen, dass dies der fairste Weg ist, da viele Benutzer durch die Wartungsarbeiten ihre täglichen Aufgaben nicht normal abhaken können. Wir bedauern jegliche Unannehmlichkeiten, die dadurch entstehen!",
+ "maintenanceInfoSeeTasks": "Aber was mache ich, wenn ich meine Aufgabenliste sehen möchte?",
+ "maintenanceInfoSeeTasksText": "Wenn Du weißt, dass du deine Aufgabenliste am Samstag ansehen musst um Dich an deine Pflichten zu erinnern, dann empfehlen wir Dir vor den Wartungsarbeiten einen Screenshot Deiner Aufgaben zu erstellen, damit du nachsehen kannst.",
+ "maintenanceInfoRarePet": "Welches seltene Tier werde ich bekommen?",
+ "maintenanceInfoRarePetText": "Um Dir für Deine Geduld während der Wartungszeit zu danken, bekommt jeder ein seltenes Veteranenhaustier. Falls Du noch nie eines bekommen hast, wirst Du einen Veteranenwolf bekommen. Falls Du schon einen hast, bekommst Du einen Veteranentiger. Und falls Du schon sowohl einen Veteranenwolf, als auch einen Veteranentiger besitzt, bekommst Du ein noch-nie-gesehenes Veteranentier! Nach der Migration kann es allerdings einige Stunden dauern, bis Dein neues Haustier auftaucht. Aber keine Angst! Du bekommst eins.",
+ "maintenanceInfoWho": "Wer arbeitete an diesem Riesenprojekt?",
+ "maintenanceInfoWhoText": "Wir freuen uns, dass Du fragst! Es wurde von unserem tollen Mitwirkenden paglias angeführt, mit viel Hilfe von Blade, TheHollidayInn, SabreCat, Victor Pudeyev, TheUnknown, und Alys.",
+ "maintenanceInfoTesting": "Die neue Version wurde auch unermüdlich von einer Reihe unserer tollen Open Source Freiwilligen getestet. Vielen Dank -- wir hätten es nicht ohne Euch geschafft!"
+}
diff --git a/common/locales/de/messages.json b/common/locales/de/messages.json
index 2eb77db106..f56229d287 100644
--- a/common/locales/de/messages.json
+++ b/common/locales/de/messages.json
@@ -8,14 +8,14 @@
"messageNotAvailable": "Dieser Gegenstand steht momentan nicht zum Verkauf.",
"messageCannotFeedPet": "Dieses Haustier will und kann nicht gefüttert werden.",
"messageAlreadyMount": "Du hast dieses Reittier bereits. Füttere lieber ein anderes Haustier.",
- "messageEvolve": "<%= egg %> wurde gezähmt, es ist an der Zeit aufzusitzen!",
+ "messageEvolve": "<%= egg %> wurde gezähmt. Es ist an der Zeit aufzusitzen!",
"messageLikesFood": "<%= egg %> ist begeistert. <%= foodText %> ist seine Lieblingsspeise!",
"messageDontEnjoyFood": "<%= egg %> ist von <%= foodText %> nicht gerade begeistert, aber würgt das Futter hinunter.",
"messageBought": "<%= itemText %> gekauft",
"messageEquipped": "<%= itemText %> angelegt.",
"messageUnEquipped": "<%= itemText %> abgelegt.",
"messageMissingEggPotion": "Dir fehlt entweder dieses Ei oder dieser Trank",
- "messageInvalidEggPotionCombo": "Du kannst Quest Haustier-Eier nicht mit magischen Schlüpftränken schlüpfen lassen! Versuche es mit einem anderen Ei.",
+ "messageInvalidEggPotionCombo": "Du kannst Quest-Haustier-Eier nicht mit magischen Schlüpftränken schlüpfen lassen! Versuche es mit einem anderen Ei.",
"messageAlreadyPet": "Du hast dieses Haustier bereits. Versuche doch eine andere Kombination.",
"messageHatched": "Ein Ei ist ausgeschlüpft! Besichtige die Ställe um Dein Haustier auszuwählen.",
"messageNotEnoughGold": "Nicht genug Gold",
@@ -28,7 +28,7 @@
"messageDropMysteryItem": "Du öffnest die Kiste und findest <%= dropText %>!",
"messageFoundQuest": "Du hast die Quest \"<%= questText %>\" gefunden!",
"messageAlreadyPurchasedGear": "Du hast diesen Gegenstand in der Vergangenheit bereits gekauft, besitzt ihn aktuell aber nicht mehr. Du kannst diesen in der Spalte \"Belohnungen\" Deiner Aufgabenseite erneut kaufen.",
- "messageAlreadyOwnGear": "Du besitzt den Gegenstand schon. Gehe zur Ausrüstungsseite, um ihn anzulegen.",
+ "messageAlreadyOwnGear": "Du besitzt diesen Gegenstand schon. Gehe zur Ausrüstungsseite um ihn anzulegen.",
"armoireEquipment": "<%= image %> Du hast ein Stück seltener Ausrüstung im verzauberten Schrank gefunden: <%= dropText %>! Großartig!",
"armoireFood": "<%= image %> Du wühlst im verzauberten Schrank herum und findest <%= dropArticle %><%= dropText %>. Was macht das denn da drin?",
"armoireExp": "Du ringst mit dem verzauberten Schrank und gewinnst Erfahrung. Nimm das!",
@@ -39,7 +39,7 @@
"messageAuthEmailTaken": "E-Mail existiert bereits",
"messageAuthNoUserFound": "Kein Benutzer gefunden.",
"messageAuthMustBeLoggedIn": "Du musst angemeldet sein.",
- "messageAuthMustIncludeTokens": "Deine Anfrage muss ein Token und eine UID (Benutzer-Kennung) beinhalten",
+ "messageAuthMustIncludeTokens": "Deine Anfrage muss ein Token und eine UID (Benutzer-ID) beinhalten",
"messageGroupNotFound": "Gruppe nicht gefunden, oder Du hast keine Zugriffsrechte.",
"messageGroupAlreadyInParty": "Bereits einer Gruppe beigetreten, versuche die Seite neu zu laden.",
"messageGroupOnlyLeaderCanUpdate": "Nur der Gruppenleiter kann die Gruppe aktualisieren!",
@@ -48,9 +48,9 @@
"messageGroupChatBlankMessage": "Du kannst keine leere Nachricht versenden",
"messageGroupChatLikeOwnMessage": "\"Gefällt mir\" funktioniert nicht bei eigenen Nachrichten. Sei nicht so einer.",
"messageGroupChatFlagOwnMessage": "Du kannst Deine eigene Nachricht nicht melden.",
- "messageGroupChatFlagAlreadyReported": "Du hast diese Nachricht bereits gemeldet.",
+ "messageGroupChatFlagAlreadyReported": "Du hast diese Nachricht bereits gemeldet",
"messageGroupChatNotFound": "Nachricht wurde nicht gefunden!",
"messageGroupChatAdminClearFlagCount": "Nur Admins können den Zählmarker zurücksetzen!",
- "messageUserOperationProtected": "Pfad `<%= operation %>` nicht gespeichert, da dieser geschützt ist.",
+ "messageUserOperationProtected": "Pfad `<%= operation %>` wurde nicht gespeichert, da dieser geschützt ist.",
"messageUserOperationNotFound": "<%= operation %> Operation nicht gefunden"
}
\ No newline at end of file
diff --git a/common/locales/de/npc.json b/common/locales/de/npc.json
index db5799c0b2..ed614ae095 100644
--- a/common/locales/de/npc.json
+++ b/common/locales/de/npc.json
@@ -1,16 +1,16 @@
{
"npc": "NPC",
- "npcText": "Hat die Kickstarter Kampagne auf dem höchsten Level mitgetragen!",
+ "npcText": "Hat die Kickstarter-Kampagne auf dem höchsten Level mitgetragen!",
"mattBoch": "Matt Boch",
- "mattShall": "Soll ich dir Dein Ross bringen, <%= name %>? Sobald Du einem Haustier so viel Futter gegeben hast, dass es zu einem Reittier werden konnte, wird es hier erscheinen. Klicke auf ein Reittier um aufzusteigen!",
+ "mattShall": "Soll ich Dir Dein Ross bringen, <%= name %>? Sobald Du einem Haustier so viel Futter gegeben hast, dass es zu einem Reittier werden konnte, wird es hier erscheinen. Klicke auf ein Reittier um aufzusteigen!",
"mattBochText1": "Willkommen im Stall! Ich bin Matt, der Bestienmeister. Ab Level 3 kannst Du mit Hilfe von Eiern und Tränken Haustiere ausbrüten. Wenn Du auf dem Marktplatz ein Haustier schlüpfen lässt, wird es hier erscheinen! Klicke auf ein Haustier, um es Deinem Avatar hinzuzufügen. Füttere Deine Tiere mit dem Futter, das Du ab Level 3 findest, damit sie zu mächtigen Reittieren heranwachsen.",
"daniel": "Daniel",
- "danielText": "Willkommen im Gasthaus! Setz Dich und triff die Einheimischen. Willst Du Dich ausruhen (Urlaub? Krankheit?), dann besorge ich dir ein schönes Zimmer. Solange Du dort eingecheckt bist, werden Deine täglichen Aufgaben dir am Ende des Tages keinen Schaden zufügen, Du kannst sie trotzdem noch abhaken.",
+ "danielText": "Willkommen im Gasthaus! Setz Dich und triff die Einheimischen. Willst Du Dich ausruhen (Urlaub? Krankheit?), dann besorge ich Dir ein schönes Zimmer. Solange Du dort eingecheckt bist, werden Deine täglichen Aufgaben Dir am Ende des Tages keinen Schaden zufügen, Du kannst sie trotzdem noch abhaken.",
"danielText2": "Sei gewarnt: Falls Du an einer Boss-Quest teilnimmst, wird Dir der Boss immer noch Schaden für die nicht abgehakten Aufgaben Deiner Gruppenmitglieder zufügen! Außerdem wird der Schaden, den Du dem Boss zufügst (sowie gefundene Gegenstände) erst angerechnet, wenn Du das Gasthaus verlässt.",
- "danielTextBroken": "Willkommen im Gasthaus ... Willst Du Dich ausruhen, dann besorge ich dir ein schönes Zimmer ... Solange Du dort eingecheckt bist, werden Deine täglichen Aufgaben dir am Ende des Tages keinen Schaden zufügen, Du kannst sie trotzdem noch abhaken ... wenn Du die Kraft dazu hast ...",
+ "danielTextBroken": "Willkommen im Gasthaus ... schätze ich ... Wenn Du Dich ausruhen möchtest, dann besorge ich Dir ein schönes Zimmer ... Solange Du dort eingecheckt bist, werden Deine täglichen Aufgaben Dir am Ende des Tages keinen Schaden zufügen, Du kannst sie trotzdem noch abhaken ... wenn Du die Kraft dazu hast ...",
"danielText2Broken": "Oh ... Falls Du an einer Boss-Quest teilnimmst, wird Dir der Boss immer noch Schaden für die nicht abgehakten Aufgaben Deiner Gruppenmitglieder zufügen ... Außerdem wird der Schaden, den Du dem Boss zufügst (sowie gefundene Gegenstände) erst angerechnet, wenn Du das Gasthaus verlässt ...",
"alexander": "Alexander der Händler",
- "welcomeMarket": "Willkommen auf dem Marktplatz! Kaufe schwer zu findende Eier und Tränke! Verkaufe Überflüssiges! Gib' wichtige Dienste in Auftrag! Komm' und schau', was wir anzubieten haben.",
+ "welcomeMarket": "Willkommen auf dem Marktplatz! Kaufe schwer zu findende Eier und Tränke! Verkaufe Überflüssiges! Gib wichtige Dienste in Auftrag! Komm und schau, was wir anzubieten haben.",
"displayItemForGold": "Möchtest Du ein <%= itemType %> verkaufen?",
"displayEggForGold": "Möchtest Du ein <%= itemType %> Ei verkaufen?",
"displayPotionForGold": "Möchtest Du einen <%= itemType %> Trank verkaufen?",
@@ -19,8 +19,28 @@
"purchaseGems": "Kaufe Edelsteine",
"justin": "Justin",
"ian": "Jan",
- "ianText": "Willkommen beim Quest Shop! Hier kannst Du Questschriftrollen besorgen, um mit Deinen Freunden Monster zu bekämpfen. Durchstöbere unsere große Anzahl an Schriftrollen und investiere in die Richtige!",
- "ianBrokenText": "Willkommen beim Quest Shop ... Hier kannst Du Questschriftrollen besorgen, um mit Deinen Freunden Monster zu bekämpfen ... Durchstöbere unsere große Anzahl an Schriftrollen und investiere in die Richtige ...",
+ "ianText": "Willkommen im Quest-Shop! Hier kannst Du Questschriftrollen besorgen um mit Deinen Freunden Monster zu bekämpfen. Durchstöbere unsere große Anzahl an Schriftrollen und investiere in die Richtige!",
+ "ianBrokenText": "Willkommen im Quest-Shop ... Hier kannst Du Questschriftrollen besorgen, um mit Deinen Freunden Monster zu bekämpfen ... Durchstöbere unsere große Anzahl an Schriftrollen und investiere in die Richtige ...",
+ "missingKeyParam": "\"req.params.key\" wird benötigt.",
+ "itemNotFound": "Eintrag \"<%= key %>\" nicht gefunden.",
+ "cannotBuyItem": "Du kannst diesen Gegenstand nicht kaufen.",
+ "missingTypeKeyEquip": "\"Schlüssel\" und \"Typ\" sind erforderliche Parameter.",
+ "missingPetFoodFeed": "\"Haustier\" und \"Futter\" sind erforderliche Parameter.",
+ "invalidPetName": "Haustier-Name ist ungültig.",
+ "missingEggHatchingPotionHatch": "\"Ei\" und \"Schlüpftrank\" sind erforderliche Parameter.",
+ "invalidTypeEquip": "\"Typ\" muss eines der folgenden sein: 'ausgerüstet', 'Haustier', 'Reittier', 'Kostüm'.",
+ "mustPurchaseToSet": "Du musst <%= val %> kaufen, um es auf <%= key %> zu setzen.",
+ "typeRequired": "Typ ist erforderlich",
+ "keyRequired": "Schlüssel ist erforderlich",
+ "notAccteptedType": "Typ muss hierin sein: [Eier, Schlüpftränke, Futter, Quests, Ausrüstung]",
+ "contentKeyNotFound": "Schlüssel für Inhalt <%= type %> nicht gefunden",
+ "plusOneGem": "+1 Edelstein",
+ "typeNotSellable": "Typ ist nicht verkäuflich. Dieser muss eines der folgenden sein: ",
+ "userItemsKeyNotFound": "Schlüssel für user.items <%= type %> nicht gefunden",
+ "pathRequired": "Pfad ist erforderlich",
+ "unlocked": "Gegenstände wurden freigeschaltet",
+ "alreadyUnlocked": "Komplettes Set ist bereits freigeschaltet.",
+ "alreadyUnlockedPart": "Set ist bereits teilweise freigeschaltet.",
"USD": "(USD)",
"newStuff": "Neuigkeiten",
"cool": "Erzähl es mir später",
@@ -30,46 +50,47 @@
"donateText3": "Als Open-Source-Projekt sind wir auf die Hilfe unserer Benutzer angewiesen. Das Geld, was Du für Edelsteine ausgibst, hilft uns dabei unsere Server am Laufen zu halten, ein paar Mitarbeiter zu bezahlen, neue Features zu entwickeln und unseren ehrenamtlichen Programmierern Anreize zu bieten. Vielen Dank für Deine Großzügigkeit!",
"donationDesc": "20 Edelsteine, Spende an die Entwickler",
"payWithCard": "Mit Kreditkarte bezahlen",
- "payNote": "Achtung: PayPal braucht manchmal viel Zeit. Wir empfehlen, eine Kreditkarte zu benutzen",
+ "payNote": "Achtung: PayPal braucht manchmal viel Zeit. Wir empfehlen, eine Kreditkarte zu benutzen.",
"card": "Kreditkarte (mit Streifen)",
"amazonInstructions": "Klicke hier um über Amazon Payments zu zahlen.",
"paymentMethods": "Kauf mit",
"classGear": "Klassenausrüstung",
"classGearText": "Zuallererst: Keine Panik! Deine alte Ausrüstung ist in Deinem Inventar und Du trägst jetzt die Lehrlingsausrüstung Deiner neuen Klasse. Die Ausrüstung Deiner Klasse zu tragen verleiht Dir einen 50% Bonus auf ihre Werte. Aber Du kannst jederzeit zu Deiner alten Ausrüstung zurückwechseln.",
- "classStats": "Dies sind die Statuswerte Deiner Klasse; sie beeinflussen das Spiel. Jedes Mal, wenn Du einen Level aufsteigst, erhältst Du einen Punkt, den Du einem Statuswert zuordnen kannst. Bewege die Maus über die Statuswerte für weitere Informationen.",
+ "classStats": "Dies sind die Statuswerte Deiner Klasse; sie beeinflussen das Spiel. Jedes Mal, wenn Du einen Level aufsteigst, erhältst Du einen Punkt, den Du einem Statuswert zuordnen kannst. Bewege die Maus über jeden der Statuswerte für weitere Informationen.",
"autoAllocate": "Automatische Verteilung",
- "autoAllocateText": "Falls Du eine automatische Verteilung wählst, werden die Punkte automatisch verteilt, abhängig von den Attributen Deiner Aufgaben, die Du unter Aufgabe > Bearbeiten > Erweitert > Attribute finden kannst. Ein Beispiel: Wenn Du oft im Fitnessstudio bist und die entsprechende Aufgabe auf \"körperlich\" gesetzt ist, bekommst Du automatisch Stärke.",
+ "autoAllocateText": "Falls Du eine automatische Verteilung wählst, werden die Punkte automatisch vergeben, abhängig von den Attributen Deiner Aufgaben, die Du unter Aufgabe > Bearbeiten > Erweitert > Attribute finden kannst. Ein Beispiel: Wenn Du oft im Fitnessstudio bist und die entsprechende Aufgabe auf \"körperlich\" gesetzt ist, bekommst Du automatisch Stärke.",
"spells": "Fähigkeiten",
- "spellsText": "Du kannst Klassenspezifische Fähigkeiten freischalten. Du wirst die erste mit Level 11 kennen lernen. Dein Mana regeneriert sich 10 Punkte pro Tag plus 1 Punkt pro erfüllte",
- "toDo": "Aufgabe",
+ "spellsText": "Du kannst klassenspezifische Fähigkeiten freischalten. Du wirst die erste mit Level 11 kennenlernen. Dein Mana regeneriert sich 10 Punkte pro Tag plus 1 Punkt pro erfüllte",
+ "toDo": "To-Do",
"moreClass": "Für mehr Informationen über das Klassensystem klicke",
"tourWelcome": "Willkommen in Habitica! Dies ist Deine Aufgabenliste. Hake eine Aufgabe ab, um weiterzumachen!",
"tourExp": "Gut gemacht! Eine Aufgabe abzuhaken bringt Dir Erfahrung und Gold ein!",
- "tourDailies": "Diese Spalte ist für tägliche Aufgaben. Um weiterzumachen füge eine Aufgabe hinzu, die Du jeden Tag erledigen möchtest. Beispiele für tägliche Aufgaben sind: Das Bett machen, Zahnseide benutzen, Geschäfts-Mails durchschauen",
+ "tourDailies": "Diese Spalte ist für tägliche Aufgaben. Um weiterzumachen füge eine Aufgabe hinzu, die Du jeden Tag erledigen möchtest. Beispiele für tägliche Aufgaben sind: Das Bett machen, Zahnseide benutzen, Geschäfts-E-Mails durchschauen",
"tourCron": "Großartig! Deine täglichen Aufgaben werden jeden Tag zurückgesetzt.",
"tourHP": "Pass auf! Wenn Du eine tägliche Aufgabe nicht bis Mitternacht erfüllt hast, wird sie Dir Schaden zufügen!",
"tourHabits": "Diese Spalte ist für gute und für schlechte Gewohnheiten, denen Du mehrmals am Tag nachgehst! Um weiterzumachen, klicke auf das Stift-Icon einer Gewohnheit. So kannst Du die Gewohnheit bearbeiten. Ändere die Namen und klicke anschließend auf das Häkchen, um die Änderung zu speichern.",
"tourStats": "Gute Gewohnheiten bringen Erfahrung und Gold! Schlechte Gewohnheiten verringern Deine Lebenspunkte.",
"tourGP": "Um weiterzumachen, kaufe das Übungschwert mit dem Gold, das Du gerade verdient hast!",
- "tourAvatar": "Passe Deinen Avatar an
Dein Avatar repräsentiert Dich.
Passe ihn jetzt an, oder kehre später hierhin zurück.
Dein Avatar sieht erstmal ganz einfach aus, bis Du dir Deine erste Ausrüstung verdient hast!
",
+ "tourAvatar": "Passe Deinen Avatar an
Dein Avatar repräsentiert Dich.
Passe ihn jetzt an, oder kehre später hierhin zurück.
Dein Avatar sieht erstmal ganz einfach aus, bis Du Dir Deine erste Ausrüstung verdient hast!
",
"tourScrollDown": "Gehe sicher, dass Du auch ganz nach unten scrollst um alle Optionen zu sehen! Klicke erneut auf Deinen Avatar um zur Aufgabenseite zurückzukehren.",
- "tourMuchMore": "Wenn Du Aufgaben erledigt hast, kannst Du mit Freunden eine Gruppe gründen, Dich in den Gilden nach Gespräche über verschiedene Themen umsehen, Wettbewerben beitreten und vieles mehr!",
+ "tourMuchMore": "Wenn Du Aufgaben erledigt hast, kannst Du mit Freunden eine Gruppe gründen, Dich in den Gilden nach Gesprächen über verschiedene Themen umsehen, Wettbewerben beitreten und vieles mehr!",
"tourStatsPage": "Auf dieser Seite kannst Du Deine Statuswerte im Auge behalten. Erreiche neue Erfolge indem Du die aufgelisteten Aufgaben erledigst.",
"tourTavernPage": "Willkommen im Gasthaus, einem Gesprächsraum für alle Altersklassen! Falls Du krank bist oder verreist, kannst Du verhindern, dass Du Leben durch nicht erledigte tägliche Aufgaben verlierst, indem Du auf \"Im Gasthaus erholen\" klickst. Komm herein und sage Hallo!",
"tourPartyPage": "Deine Gruppe wird Dir dabei helfen weiterhin verantwortungsbewusst Deine Aufgaben zu erledigen. Lade Freunde ein um neue Quest Rollen freizuschalten!",
- "tourGuildsPage": "Gilden sind Chatgruppen mit einem gemeinsamen Interesse. Sie sind von Spielern für Spieler erstellt worden. Durchblättere die Liste und tritt den Gilden bei, die Dich interessieren. Teste die bekannte Newbies Gilde, in der jeder Fragen über Habitica stellen kann!",
+ "tourGuildsPage": "Gilden sind Chatgruppen mit einem gemeinsamen Interesse. Sie sind von Spielern für Spieler erstellt worden. Durchblättere die Liste und tritt den Gilden bei, die Dich interessieren. Teste die bekannte Newbies-Gilde, in der jeder Fragen über Habitica stellen kann!",
"tourChallengesPage": "Wettbewerbe sind themenbezogene Aufgabenlisten, welche von Benutzern erstellt wurden! Wenn Du an einem Wettbewerb teilnimmst, werden die Aufgaben Deinem Konto hinzugefügt. Trete gegen andere Benutzer an, um Edelsteine zu gewinnen!",
"tourMarketPage": "Sobald Du Level 4 erreichst, erhältst Du manchmal als zufällige Belohnung für erledigte Aufgaben Eier und Schlüpftränke. Diese erscheinen hier - nutze sie um Haustiere auszubrüten. Du kannst außerdem Gegenstände vom Marktplatz kaufen.",
"tourHallPage": "Willkommen in der Halle der Helden, in der Mitwirkende an Habitica geehrt werden. Durch Code, Design, Musik, Text oder einfach durch Hilfsbereitschaft, haben sie Edelsteine, exklusive Ausrüstungen und angesehene Titel verdient. Auch Du kannst bei Habitica mitwirken!",
"tourPetsPage": "Dies ist der Stall! Ab Level 4 kannst Du Haustiere schlüpfen lassen indem Du Eier und Tränke verwendest. Wenn Du ein Haustier im Marktplatz ausgebrütet hast, wird es hier erscheinen! Klicke auf das Bild eines Haustieres, um es Deinem Avatar hinzuzufügen. Füttere die Tiere mit dem Essen, das Du ab Level 4 findest und sie werden zu mächtigen Reittieren heranwachsen.",
"tourMountsPage": "Hast Du ein Haustier genug gefüttert, wird es sich zu einem Reittier weiterentwickeln und hier erscheinen. (Haustiere, Reittiere und Futter stehen nach Level 4 zur Verfügung.) Klicke auf ein Reittier um aufzusatteln!",
"tourEquipmentPage": "Hier wird Deine Ausrüstung gelagert! Deine Kampfausrüstung beeinflusst Deine Statuswerte. Wenn Du eine andere Ausrüstung an Deinem Avatar zeigen willst ohne Deine Statuswerte zu verändern, klicke auf \"Verkleidung tragen\".",
+ "equipmentAlreadyOwned": "Du besitzt diesen Ausrüstungsgegenstand bereits",
"tourOkay": "Okay!",
"tourAwesome": "Großartig!",
"tourSplendid": "Bestens!",
"tourNifty": "Raffiniert!",
"tourAvatarProceed": "Zeige mir meine Aufgaben!",
- "tourToDosBrief": "To-Do Liste
Hake To-Dos ab, um Gold und Erfahrung zu verdienen!
Durch nicht erledigte To-Dos verlierst Du niemals Leben.
",
+ "tourToDosBrief": "To-Do-Liste
Hake To-Dos ab, um Gold und Erfahrung zu verdienen!
Durch nicht erledigte To-Dos verlierst Du niemals Leben.
",
"tourDailiesBrief": "Tägliche Aufgaben
Tägliche Aufgaben wiederholen sich an jedem Tag.
Du verlierst Leben, wenn Du tägliche Aufgaben nicht beendest.
",
diff --git a/common/locales/de/pets.json b/common/locales/de/pets.json
index ca45b5444e..ca77a54390 100644
--- a/common/locales/de/pets.json
+++ b/common/locales/de/pets.json
@@ -6,12 +6,13 @@
"questPets": "Quest-Haustiere",
"mounts": "Reittiere",
"mountsTamed": "Reittiere gezähmt",
- "questMounts": "Quest Reittiere",
+ "questMounts": "Quest-Reittiere",
"magicMounts": "Magische Reittiere",
"rareMounts": "Seltene Reittiere",
"etherealLion": "Ätherischer Löwe",
"veteranWolf": "Veteranwolf",
- "veteranTiger": "Veteranentiger",
+ "veteranTiger": "Veterantiger",
+ "veteranLion": "Veteranlöwe",
"cerberusPup": "Zerberuswelpe",
"hydra": "Hydra",
"mantisShrimp": "Fangschreckenkrebs",
@@ -19,7 +20,7 @@
"orca": "Schwertwal",
"royalPurpleGryphon": "Königlicher purpurfarbener Greif",
"phoenix": "Phönix",
- "bumblebee": "Bumblebee",
+ "magicalBee": "Magische Biene",
"rarePetPop1": "Klicke auf den goldenen Pfotenabdruck, um zu sehen, wie Du diese seltenen Haustiere erhalten kannst, indem Du bei Habitica mitwirkst!",
"rarePetPop2": "Wie erhält man dieses Haustier?",
"potion": "<%= potionType %> Trank",
@@ -35,11 +36,11 @@
"foodText": "Futter",
"food": "Futter und magische Sättel",
"noFood": "Du hast im Moment weder Futter noch magische Sättel.",
- "dropsExplanation": "Erhalte diese Gegenständer schneller mit Edelsteinen, wenn Du nicht warten möchtest, bis Du sie als Beute für erfüllte Aufgaben erhälst. Erfahre mehr über das Beutesystem.",
+ "dropsExplanation": "Erhalte diese Gegenstände schneller mit Edelsteinen, wenn Du nicht warten möchtest, bis Du sie als Beute für erfüllte Aufgaben erhältst. Erfahre mehr über das Beutesystem.",
"premiumPotionNoDropExplanation": "Magische Schlüpftränke können nicht auf Eier, die Du durch Quests erhalten hast, angewendet werden. Magische Schlüpftränke können nur gekauft und nicht durch zufällige Beute erworben werden.",
"beastMasterProgress": "\"Meister aller Bestien\" Fortschritt",
- "stableBeastMasterProgress": "Meister aller Bestien Forstschritt: <%= number %> Haustiere gefunden",
- "beastAchievement": "Du hast den \"Meister aller Bestien\" Erfolg dafür erhalten, dass Du alle Haustiere gesammelt hast!",
+ "stableBeastMasterProgress": "Meister aller Bestien Fortschritt: <%= number %> Haustiere gefunden",
+ "beastAchievement": "Du hast den Erfolg \"Meister aller Bestien\" dafür erhalten, dass Du alle Haustiere gesammelt hast!",
"beastMasterName": "Meister aller Bestien",
"beastMasterText": "Hat alle 90 Haustiere gesammelt (unglaublich schwer, gratuliere diesem Spieler!)",
"beastMasterText2": "und hat seine Haustiere insgesamt <%= count %> mal freigelassen",
@@ -50,19 +51,20 @@
"mountMasterText": "Hat alle 90 Reittiere gezähmt (noch viel schwieriger, gratuliere diesem Spieler!)",
"mountMasterText2": "und hat alle 90 seiner Reittiere insgesamt <%= count %> mal freigelassen",
"beastMountMasterName": "Meister aller Bestien und Reittiere",
- "triadBingoName": "Triaden Bingo",
+ "triadBingoName": "Triaden-Bingo",
"triadBingoText": "Hat alle 90 Haustiere gesammelt, alle 90 Reittiere gezähmt, und WIEDER alle 90 Haustiere gefunden (WIE HAST DU DAS GESCHAFFT?!)",
"triadBingoText2": "und hat alle Tiere aus seinem Stall <%= count %> mal freigelassen",
- "triadBingoAchievement": "Du hast den Erfolg \"Triaden Bingo\" erhalten, da Du alle Haustiere gefunden, alle Reittiere gezähmt und wieder alle Haustiere gefunden hast!",
- "dropsEnabled": "Beutesystem Aktiviert!",
+ "triadBingoAchievement": "Du hast den Erfolg \"Triaden-Bingo\" erhalten, da Du alle Haustiere gefunden, alle Reittiere gezähmt und wieder alle Haustiere gefunden hast!",
+ "dropsEnabled": "Beutesystem aktiviert!",
"itemDrop": "Du hast einen Gegenstand gefunden!",
"firstDrop": "Du hast das Beutesystem freigeschaltet! Ab jetzt hast Du jedes mal wenn Du eine Aufgabe abhakst eine kleine Chance einen Gegenstand zu finden, wie zum Beispiel Eier, Schlüpftränke und Futter! Du hast eben ein <%= eggText %> Ei gefunden! <%= eggNotes %>",
"useGems": "Du willst ein bestimmtes Haustier haben, möchtest aber nicht länger warten, bis Du die richtigen Gegenstände gefunden hast? Mit Edelsteinen kannst Du im Inventar > Marktplatz die entsprechenden Gegenstände kaufen!",
"hatchAPot": "Willst Du ein <%= potion %> <%= egg %> ausbrüten?",
- "hatchedPet": "Du hast ein <%= potion %> <%= egg %> ausgebrütet!",
+ "hatchedPet": "Du hast einen <%= potion %> <%= egg %> ausgebrütet!",
"displayNow": "Jetzt anzeigen",
"displayLater": "Später anzeigen",
- "earnedCompanion": "Durch Deine Produktivität hast Du einen neuen Begleiter erhalten. Füttere ihn, damit er wächst.",
+ "petNotOwned": "Du hast dieses Haustier noch nicht.",
+ "earnedCompanion": "Durch all Deine Produktivität hast Du einen neuen Begleiter erhalten. Füttere ihn damit er wächst!",
"feedPet": "'Verfüttere <%= article %> <%= text %> an <%= name %>?",
"useSaddle": "Einen magischen Sattel auf <%= pet %> anwenden?",
"raisedPet": "Du hast einen <%= pet %> aufgezogen!",
@@ -83,5 +85,8 @@
"petKeyBoth": "Lasse meine Haus- und meine Reittiere frei",
"confirmPetKey": "Bist Du sicher?",
"petKeyNeverMind": "Noch nicht.",
+ "petsReleased": "Haustiere freigelassen.",
+ "mountsAndPetsReleased": "Reit- und Haustiere freigelassen",
+ "mountsReleased": "Reittiere freigelassen",
"gemsEach": "Edelsteine jeweils"
}
\ No newline at end of file
diff --git a/common/locales/de/quests.json b/common/locales/de/quests.json
index 12643d914f..05e4fa71cb 100644
--- a/common/locales/de/quests.json
+++ b/common/locales/de/quests.json
@@ -13,11 +13,11 @@
"youReceived": "Du hast folgendes erhalten:",
"dropQuestCongrats": "Gratulation zum Erwerb dieser Questschriftrolle! Du kannst nun Deine Gruppe dazu einladen die Quest zu starten oder Du kommst irgendwann darauf zurück unter Inventar > Quests.",
"questSend": "Indem Du auf \"Einladen\" klickst sendest Du eine Einladung an Deine Gruppenmitglieder. Sobald alle Mitglieder diese angenommen oder abgelehnt haben beginnt die Quest. Der Status ist unter Soziales > Gruppe zu finden.",
- "questSendBroken": "Indem Du auf \"Einladen\" klickst sendest Du eine Einladung an Deine Gruppenmitglieder... Sobald alle Mitglieder diese angenommen oder abgelehnt haben beginnt die Quest... Der Status ist unter Soziales > Gruppe zu finden...",
+ "questSendBroken": "Indem Du auf \"Einladen\" klickst, sendest Du eine Einladung an Deine Gruppenmitglieder ... Sobald alle Mitglieder diese angenommen oder abgelehnt haben beginnt die Quest ... Der Status ist unter Soziales > Gruppe zu finden ...",
"inviteParty": "Lade Gruppe zur Quest ein",
"questInvitation": "Quest-Einladung:",
"questInvitationTitle": "Quest-Einladung",
- "questInvitationInfo": "Einladung zu der Quest <%= quest %>",
+ "questInvitationInfo": "Einladung zur Quest <%= quest %>",
"askLater": "Später fragen",
"questLater": "Quest später starten",
"buyQuest": "Quest kaufen",
@@ -25,25 +25,25 @@
"rejected": "Abgelehnt",
"pending": "Ausstehend",
"questStart": "Sobald alle Mitglieder die Einladung entweder angenommen oder abgelehnt haben beginnt die Quest. Nur diejenigen, die die Einladung \"angenommen\" haben können an der Quest teilnehmen und die Belohnungen kassieren. Wenn Mitglieder zu lange überlegen (inaktiv?), kannst Du die Quest ohne sie starten indem Du auf \"Beginnen\" klickst. Der Quest-Besitzer kann die Quest auch abbrechen und die Questrolle zurückerhalten indem er \"Abbrechen\" klickt.",
- "questStartBroken": "Sobald alle Mitglieder die Einladung entweder angenommen oder abgelehnt haben beginnt die Quest… Nur diejenigen, die die Einladung \"angenommen\" haben können an der Quest teilnehmen und die Belohnungen kassieren… Wenn Mitglieder zu lange überlegen (inaktiv?), kann der Quest-Besitzer die Quest ohne sie starten indem er auf \"Beginnen\" klickt… Der Quest-Besitzer kann die Quest auch abbrechen und die Questschriftrolle zurückerhalten indem er auf \"Abbrechen\" klickt ...",
+ "questStartBroken": "Sobald alle Mitglieder die Einladung entweder angenommen oder abgelehnt haben beginnt die Quest … Nur diejenigen, die die Einladung \"angenommen\" haben können an der Quest teilnehmen und die Belohnungen kassieren … Wenn Mitglieder zu lange überlegen (inaktiv?), kann der Quest-Besitzer die Quest ohne sie starten indem er auf \"Beginnen\" klickt … Der Quest-Besitzer kann die Quest auch abbrechen und die Questschriftrolle zurückerhalten indem er auf \"Abbrechen\" klickt ...",
"begin": "Beginnen",
- "bossHP": "Boss Lebenspunkte",
- "bossStrength": "Boss Stärke",
- "rage": "Zorn",
+ "bossHP": "Boss-Lebenspunkte",
+ "bossStrength": "Boss-Stärke",
+ "rage": "Raserei",
"collect": "Sammle ein",
"collected": "Gesammelt",
"collectionItems": "<%= number %> <%= items %>",
"itemsToCollect": "Zu sammelnde Gegenstände",
- "bossDmg1": "Jede erledigte tägliche Aufgabe und Aufgabe und jede positive Gewohnheit fügt dem Boss Schaden zu. Mit röteren Aufgaben, Gewaltschlag oder Flammenstoß kannst Du ihm noch stärkeren Schaden zufügen. Für jede tägliche Aufgabe, die Du nicht erledigt hast, wird der Boss jedem Teilnehmer der Quest Schaden zufügen (multipliziert mit der Stärke des Bosses), der zu Deinem normalen Schaden noch dazukommt. Deshalb sorge dafür, dass Deine Gruppe gesund bleibt, indem Du Deine täglichen Aufgaben erledigst! Jeder Schaden, der dem Boss zugefügt wird und den er zufügt, wird zu Cron berechnet (Dein individueller Tagesbeginn).",
+ "bossDmg1": "Jede erledigte tägliche Aufgabe, jedes To-Do und jede positive Gewohnheit fügt dem Boss Schaden zu. Mit röteren Aufgaben, Gewaltschlag oder Flammenstoß kannst Du ihm noch stärkeren Schaden zufügen. Für jede tägliche Aufgabe, die Du nicht erledigt hast, wird der Boss jedem Teilnehmer der Quest Schaden zufügen (multipliziert mit der Stärke des Bosses), der zu Deinem normalen Schaden noch dazukommt. Deshalb sorge dafür, dass Deine Gruppe gesund bleibt, indem Du Deine täglichen Aufgaben erledigst! Jeder Schaden, der dem Boss zugefügt wird und den er zufügt, wird zu Cron berechnet (Dein individueller Tagesbeginn).",
"bossDmg2": "Nur Teilnehmer kämpfen gegen den Boss und bekommen ihren Anteil an der Beute.",
- "bossDmg1Broken": "Jede erledigte tägliche und einmalige Aufgabe und jede positive Gewohnheit fügt dem Boss Schaden zu... Mit röteren Aufgaben, Gewaltschlag oder Flammenstoß kannst Du ihm noch stärkeren Schaden zufügen... Für jede tägliche Aufgabe die Du nicht erledigt hast, wird der Boss jedem Teilnehmer der Quest Schaden zufügen (multipliziert mit der Stärke des Bosses) der zu Deinem normalen Schaden noch dazukommt, sorge deshalb dafür, dass Deine Gruppe gesund bleibt, indem Du Deine täglichen Aufgaben erledigst... Jeder Schaden, der dem Boss zugefügt wird und den er zufügt, wird zu Cron berechnet (Dein individueller Tagesbeginn)...",
- "bossDmg2Broken": "Nur Teilnehmer kämpfen gegen den Boss und erhalten ihren Anteil an der Beute...",
- "tavernBossInfo": "Complete Dailies and To-Dos and score positive Habits to damage the World Boss! Incomplete Dailies fill the Rage Bar. When the Rage bar is full, the World Boss will attack an NPC. A World Boss will never damage individual players or accounts in any way. Only active accounts not resting in the Inn will have their tasks tallied.",
- "tavernBossInfoBroken": "Erledige tägliche und einmalige Aufgaben und punkte mit guten Gewohnheiten, um dem Weltboss zu schaden... Unerledigte tägliche Aufgaben füllen die Erschöpfungsschlagleiste... Wenn sie voll ist, greift der Weltboss einen NPC an... Ein Weltboss wird niemals einzelnen Spielern oder Accounts auf irgendeine Weise schaden ... Nur die Aufgaben aktiver Accounts, welche sich nicht im Gasthaus erholen, werden aufsummiert...",
- "bossColl1": "Um Gegenstände zu sammeln musst Du Deine Aufgaben erfüllen. Quest Gegenstände sind genauso wie normale Gegenstände zu finden, aber Du kannst das Ergebnis erst zum Tageswechsel sehen. Dann wird alles, was jeder Teilnehmer gefunden hat zusammengerechnet und dem Quest zugerechnet.",
+ "bossDmg1Broken": "Jede erledigte tägliche Aufgabe, jedes To-Do und jede positive Gewohnheit fügt dem Boss Schaden zu ... Mit röteren Aufgaben, Gewaltschlag oder Flammenstoß kannst Du ihm noch stärkeren Schaden zufügen ... Für jede tägliche Aufgabe die Du nicht erledigt hast, wird der Boss jedem Teilnehmer der Quest Schaden zufügen (multipliziert mit der Stärke des Bosses) der zu Deinem normalen Schaden noch dazukommt, sorge deshalb dafür, dass Deine Gruppe gesund bleibt, indem Du Deine täglichen Aufgaben erledigst ... Jeder Schaden, der dem Boss zugefügt wird und den er zufügt, wird zu Cron berechnet (Dein individueller Tagesbeginn) ...",
+ "bossDmg2Broken": "Nur Teilnehmer kämpfen gegen den Boss und erhalten ihren Anteil an der Beute ...",
+ "tavernBossInfo": "Erledige tägliche Aufgaben und To-Dos und punkte mit guten Gewohnheiten, um dem Weltboss zu schaden! Unerledigte tägliche Aufgaben füllen die Erschöpfungsschlagleiste. Wenn sie voll ist, greift der Weltboss einen NPC an.\n\nEin Weltboss wird niemals einzelnen Spielern oder Accounts auf irgendeine Weise schaden. Nur die Aufgaben aktiver Accounts, welche sich nicht im Gasthaus erholen, werden aufsummiert.",
+ "tavernBossInfoBroken": "Erledige tägliche Aufgaben und To-Dos und punkte mit guten Gewohnheiten, um dem Weltboss zu schaden ... Unerledigte tägliche Aufgaben füllen die Erschöpfungsschlagleiste ... Wenn sie voll ist, greift der Weltboss einen NPC an ... Ein Weltboss wird niemals einzelnen Spielern oder Accounts auf irgendeine Weise schaden ... Nur die Aufgaben aktiver Accounts, welche sich nicht im Gasthaus erholen, werden aufsummiert ...",
+ "bossColl1": "Um Gegenstände zu sammeln musst Du Deine Aufgaben erfüllen. Questgegenstände sind genauso wie normale Gegenstände zu finden, aber Du kannst das Ergebnis erst zum Tageswechsel sehen. Dann wird alles, was jeder Teilnehmer gefunden hat zusammengerechnet und dem Quest zugerechnet.",
"bossColl2": "Nur Teilnehmer können Gegenstände sammeln und erhalten ihren Anteil an der Beute.",
- "bossColl1Broken": "Um Gegenstände zu sammeln müssen positive Aufgaben erledigt werden... Quest-Gegenstände erhält man genau wie normale Gegenstände; jedoch kannst Du erst am nächsten Tag sehen wie viele Du gesammelt hast, da erst dann alle zusammengezählt und dem Quest-Fortschritt hinzugefügt werden...",
- "bossColl2Broken": "Nur Teilnehmer können Gegenstände sammeln und erhalten ihren Anteil an der Beute...",
+ "bossColl1Broken": "Um Gegenstände zu sammeln müssen positive Aufgaben erledigt werden ... Quest-Gegenstände erhält man genau wie normale Gegenstände; jedoch kannst Du erst am nächsten Tag sehen wie viele Du gesammelt hast, da erst dann alle zusammengezählt und dem Quest-Fortschritt hinzugefügt werden ...",
+ "bossColl2Broken": "Nur Teilnehmer können Gegenstände sammeln und erhalten ihren Anteil an der Beute ...",
"abort": "Abbrechen",
"leaveQuest": "Quest verlassen",
"sureLeave": "Willst Du die aktive Quest wirklich verlassen? Dein kompletter Questfortschritt wird verloren gehen.",
@@ -53,8 +53,8 @@
"questOwnerNotInPendingQuestParty": "Der Quest-Besitzer hat die Gruppe verlassen und kann die Quest nicht mehr starten. Es wird empfohlen, dass Du sie jetzt abbrichst. Der Quest-Besitzer wird die Questrolle zurück bekommen.",
"questOwnerNotInRunningQuestParty": "Der Quest-Besitzer hat die Gruppe verlassen. Wenn Du willst, kannst Du die Quest abbrechen, aber Du kannst sie auch weiterlaufen lassen und alle übrigen Teilnehmer werden die Questbelohnungen erhalten wenn sie die Quest geschafft haben.",
"questParticipants": "Teilnehmer",
- "scrolls": "Quest Rollen",
- "noScrolls": "Du hast im Moment keine Quest Rollen.",
+ "scrolls": "Quest-Schriftrollen",
+ "noScrolls": "Du hast im Moment keine Quest-Schriftrollen.",
"scrollsText1": "Quests erfordern Gruppen. Wenn Du ein Quest allein erfüllen willst, dann musst Du",
"scrollsText2": "eine leere Gruppe erstellen",
"scrollsPre": "Du hast diese Quest noch nicht freigeschaltet!",
@@ -71,12 +71,31 @@
"doubleSureAbort": "Bist Du wirklich wirklich sicher? Sei' ganz sicher, dass sie Dich nicht für immer hassen werden!",
"questWarning": "Wenn neue Mitglieder der Gruppe beitreten, bevor das Quest anfängt, werden auch sie eine Einladung bekommen. Aber sobald das Quest angefangen hat, können neue Gruppenmitglieder dem Quest nicht mehr beitreten.",
"questWarningBroken": "Wenn neue Mitglieder der Gruppe beitreten bevor die Quest beginnt werden auch sie eine Einladung erhalten... Aber sobald die Quest angefangen hat können ihr keine neuen Gruppenmitglieder mehr beitreten...",
- "bossRageTitle": "Zorn",
+ "bossRageTitle": "Raserei",
"bossRageDescription": "Wenn sich dieser Balken füllt, wird der Boss eine Spezialattacke ausführen",
"startAQuest": "STARTE EINE QUEST",
"startQuest": "Quest starten",
"whichQuestStart": "Welche Quest willst Du starten?",
"getMoreQuests": "Erhalte mehr Quests",
"unlockedAQuest": "Du hast eine Quest freigeschaltet!",
- "leveledUpReceivedQuest": "Du hast Level <%= level %> erreicht und eine Questschriftrolle erhalten!"
+ "leveledUpReceivedQuest": "Du hast Level <%= level %> erreicht und eine Questschriftrolle erhalten!",
+ "questInvitationDoesNotExist": "Es wurde noch keine Questeinladung verschickt.",
+ "questInviteNotFound": "Keine Questeinladung gefunden.",
+ "guildQuestsNotSupported": "Gilden können nicht zu Quests eingeladen werden.",
+ "questNotFound": "Quest \"<%= key %>\" nicht gefunden.",
+ "questNotOwned": "Du besitzt diese Quest-Schriftrolle nicht.",
+ "questNotGoldPurchasable": "Quest \"<%= key %>\" ist nicht mit Gold käuflich.",
+ "questLevelTooHigh": "Du musst Level <%= level %> sein, um diese Quest zu starten.",
+ "questAlreadyUnderway": "Deine Gruppe nimmt bereits an einer Quest teil. Versuche es erneut, wenn die Quest abgeschlossen ist.",
+ "questAlreadyAccepted": "Du hast die Questeinladung bereits angenommen.",
+ "noActiveQuestToLeave": "Keine Quest zum Verlassen aktiv",
+ "questLeaderCannotLeaveQuest": "Quest-Leiter können die Quest nicht verlassen",
+ "notPartOfQuest": "Du nimmst nicht an der Quest teil",
+ "noActiveQuestToAbort": "Es ist keine Quest zum Abbruch aktiv.",
+ "onlyLeaderAbortQuest": "Nur der Gruppen- oder Quest-Leiter kann die Quest abbrechen.",
+ "questAlreadyRejected": "Du hast die Questeinladung bereits abgelehnt.",
+ "cantCancelActiveQuest": "Du kannst eine aktive Quest nicht zurückziehen, verwende die Funktion Abbrechen.",
+ "onlyLeaderCancelQuest": "Nur der Gruppen- oder Quest-Leiter kann die Quest zurückziehen.",
+ "questNotPending": "Es ist keine Quest zum Start vorhanden.",
+ "questOrGroupLeaderOnlyStartQuest": "Nur der Quest- oder Gruppen-Leiter kann den Quest-Start erzwingen"
}
\ No newline at end of file
diff --git a/common/locales/de/questscontent.json b/common/locales/de/questscontent.json
index b362499921..e2fe6f21cf 100644
--- a/common/locales/de/questscontent.json
+++ b/common/locales/de/questscontent.json
@@ -1,14 +1,14 @@
{
"questEvilSantaText": "Wildernder Weihnachtswichtel",
"questEvilSantaNotes": "Tief im Eisfeld hörst Du gequältes Brüllen. Du folgst dem Knurren - unterstrichen von einem Schnattern - zu einer Waldlichtung, auf welcher Du eine ausgewachsene Polarbärin siehst. Sie kämpft um ihr Leben, gefesselt und eingesperrt. Oben auf dem Käfig tanzt ein bösartiger kleiner Kobold, der kostümiert wie ein Schiffbrüchiger ist. Bezwinge den Wildernden Weihnachtswichtel und rette das Tier!",
- "questEvilSantaCompletion": "Vor Wut kreischend springt der Wildernde Weihnachtswichtel in den dunklen Wald davon. Die Bärin versucht dir mit Brüllen und Knurren etwas zu verstehen zu geben. Du nimmst sie mit zurück zu den Ställen, wo Matt Boch, der Tierflüsterer, der Bärin zuhört. Mit Schrecken vernimmst Du, dass die Bärin ein Junges hat! Als sie gefangen genommen wurde, lief es in die Eissteppe davon. Hilf ihr, das Junge zu finden!",
+ "questEvilSantaCompletion": "Vor Wut kreischend springt der Wildernde Weihnachtswichtel in den dunklen Wald davon. Die Bärin versucht Dir mit Brüllen und Knurren etwas zu verstehen zu geben. Du nimmst sie mit zurück zu den Ställen, wo Matt Boch, der Bestienmeister, der Bärin zuhört. Mit Schrecken vernimmst Du, dass die Bärin ein Junges hat! Als sie gefangen genommen wurde, lief es in die Eissteppe davon. Hilf ihr, das Junge zu finden!",
"questEvilSantaBoss": "Wildernder Weihnachtswichtel",
"questEvilSantaDropBearCubPolarMount": "Eisbär (Reittier)",
"questEvilSanta2Text": "Finde das Jungtier",
- "questEvilSanta2Notes": "Mama Bärs Jungtier ist geflohen, als sie vom Wilderndem Weihnachtswichtel gefangen wurde. Am Waldesrand hält sie verzweifelt ihre Nase in die Luft. Du hörst Zweige knacken und Schneestapfen in der Tiefe des stillen Waldes. Pfotenabdrücke! Mama Bär und Du laufen los, um der Spur zu folgen. Finde alle Spuren und gebrochenen Zweige, um das Jungtier aufzuspüren!",
+ "questEvilSanta2Notes": "Mama Bärs Jungtier ist geflohen, als sie vom Wilderndem Weihnachtswichtel gefangen wurde. Du hörst Zweige knacken und Schneestapfen in der Tiefe des stillen Waldes. Pfotenabdrücke! Mama Bär und Du laufen los, um der Spur zu folgen. Finde alle Spuren und abgeknickten Zweige, um das Jungtier aufzuspüren!",
"questEvilSanta2Completion": "Du hast das Jungtier gefunden! Es wird Dir für immer Gesellschaft leisten.",
"questEvilSanta2CollectTracks": "Spuren",
- "questEvilSanta2CollectBranches": "Zerbrochene Zweige",
+ "questEvilSanta2CollectBranches": "Abgebrochene Zweige",
"questEvilSanta2DropBearCubPolarPet": "Eisbär (Haustier)",
"questGryphonText": "Der Feuergreif",
"questGryphonNotes": "Der große Herr aller Bestien, baconsaur, kommt zu Deiner Gruppe, denn er sucht nach Hilfe. \"Abenteurer, bitte, ihr müsst mir helfen! Mein preisgekrönter Greif ist ausgebrochen und terrorisiert nun Habit City! Wenn ihr sie aufhaltet, kann ich euch mit einigen von ihren Eiern belohnen!\"",
@@ -17,7 +17,7 @@
"questGryphonDropGryphonEgg": "Greif (Ei)",
"questGryphonUnlockText": "Ermöglicht den Kauf von Greifeneiern auf dem Marktplatz",
"questHedgehogText": "Das Igelmonster",
- "questHedgehogNotes": "Igel gehören einer kuriosen Gruppe von Tieren an. Zwar sind sie die liebevollsten Haustiere, die sich ein Habiteer wünschen kann, aber es gibt ein Gerücht wonach sie, wenn man sie nach Mitternacht mit Milch füttert, ein wenig gereizt werden. Und fünfzig mal größer. Und inventrix hat genau das gemacht - Hoppla.",
+ "questHedgehogNotes": "Igel gehören einer kuriosen Gruppe von Tieren an. Zwar sind sie die liebevollsten Haustiere, die sich ein Habiticaner wünschen kann, aber es gibt ein Gerücht wonach sie, wenn man sie nach Mitternacht mit Milch füttert, ein wenig gereizt werden. Und fünfzig mal größer. Und inventrix hat genau das gemacht - Hoppla.",
"questHedgehogCompletion": "Eure Gruppe hat das Igelweibchen beruhigt! Es hoppelt zurück zu seinen Eiern, nachdem es auf seine normale Größe geschrumpft ist. Quietschend kehrt es mit einigen Eiern zurück und stupst sie vorsichtig in eure Richtung. Hoffentlich mögen Igel Milch!",
"questHedgehogBoss": "Igelmonster",
"questHedgehogDropHedgehogEgg": "Igel (Ei)",
@@ -47,19 +47,19 @@
"questHarpyDropParrotEgg": "Papagei (Ei)",
"questHarpyUnlockText": "Ermöglicht den Kauf von Papageieneiern auf dem Marktplatz",
"questRoosterText": "Hahnenkampf",
- "questRoosterNotes": "Jahrelang nutzte der Farmer @extrajordanary Hähne als Wecker. Doch nun ist ein riesiger Hahn aufgetaucht, der lauter kräht als je einer davor - und alle Einwohner Habitikas weckt! Die unausgeschlafenen Habitikaner mühen sich durch ihre täglichen Aufgaben. @Pandoro beschließt, dass nun die Zeit gekommen sei, dem ein Ende zu bereiten. \"Bitte, gibt es jemanden, der diesem Hahn beibringen kann, leise zu krähen?\" Du meldest Dich freiwillig und näherst Dich dem Hahn eines frühen Morgens - aber er dreht sich um, schlägt mit seinen gigantischen Flügeln, zeigt seine scharfen Krallen und kräht einen Schlachtruf.",
- "questRoosterCompletion": "Mit Raffinesse und Stärke ist es Dir gelungen, die wilde Bestie zu zähmen. Die Ohren des Hahnes, die bisher mit Federn und halbvergessenen Aufgaben verstopft waren, sind nun offen wie ein Scheunentor. Er kräht Dich leise an und kuschelt seinen Schnabel an Deine Schulter. Am nächsten Tag willst Du wieder aufbrechen, aber @EmeraldOx rennt auf Dich zu, in der Hand einen bedeckten Korb. \"Warte! Als ich diesen Morgen ins Bauernhaus kam, hatte der Hahn dies hier an die Tür geschoben, hinter der Du geschlafen hast. Ich glaube, er will, dass Du sie kriegst.\"\nDu öffnest den Korb und siehst drei zierliche Eier.",
+ "questRoosterNotes": "Jahrelang nutzte der Farmer @extrajordanary Hähne als Wecker. Doch nun ist ein riesiger Hahn aufgetaucht, der lauter kräht als je einer davor - und alle Einwohner Habiticas weckt! Die unausgeschlafenen Habiticaner mühen sich durch ihre täglichen Aufgaben. @Pandoro beschließt, dass nun die Zeit gekommen sei, dem ein Ende zu bereiten. \"Bitte, gibt es jemanden, der diesem Hahn beibringen kann, leise zu krähen?\" Du meldest Dich freiwillig und näherst Dich dem Hahn eines frühen Morgens - aber er dreht sich um, schlägt mit seinen gigantischen Flügeln, zeigt seine scharfen Krallen und kräht einen Schlachtruf.",
+ "questRoosterCompletion": "Mit Raffinesse und Stärke ist es Dir gelungen, die wilde Bestie zu zähmen. Die Ohren des Hahnes, die bisher mit Federn und halbvergessenen Aufgaben verstopft waren, sind nun offen wie ein Scheunentor. Er kräht Dich leise an und kuschelt seinen Schnabel an Deine Schulter. Am nächsten Tag willst Du wieder aufbrechen, aber @EmeraldOx rennt auf Dich zu, in der Hand einen bedeckten Korb. \"Warte! Als ich diesen Morgen ins Bauernhaus kam, hatte der Hahn dies hier an die Tür geschoben, hinter der Du geschlafen hast. Ich glaube, er will, dass Du sie bekommst.\"\nDu öffnest den Korb und siehst drei zierliche Eier.",
"questRoosterBoss": "Hahn",
"questRoosterDropRoosterEgg": "Hahn (Ei)",
"questRoosterUnlockText": "Ermöglicht den Kauf von Hahneiern auf dem Marktplatz",
"questSpiderText": "Die eisige Arachnoide",
"questSpiderNotes": "Das Wetter kühlt sich ab und leichter Frost beginnt auf den Fensterscheiben der Einwohner von Habitica in Form von filigranen Netzen zu erscheinen ... außer bei @Arcosine, dessen Fenster komplett zugefroren sind und bei dem sich die Frostspinne eingenistet hat. Oh je!",
- "questSpiderCompletion": "Die Frostspinne bricht zusammen. Von ihr bleibt nur ein kleiner Haufen Frost und ein paar ihrer verzauberten Eiersäcke. @Arcosine bietet sie dir recht hastig als Belohnung an--vielleicht könntest Du ein paar ungefährliche Spinnen als Deine Haustiere aufziehen?",
+ "questSpiderCompletion": "Die Frostspinne bricht zusammen. Von ihr bleibt nur ein kleiner Haufen Frost und ein paar ihrer verzauberten Eiersäcke übrig. @Arcosine bietet sie Dir recht hastig als Belohnung an -- vielleicht könntest Du ein paar ungefährliche Spinnen als Deine Haustiere aufziehen?",
"questSpiderBoss": "Spinne",
"questSpiderDropSpiderEgg": "Spinne (Ei)",
"questSpiderUnlockText": "Ermöglicht den Kauf von Spinneneiern auf dem Marktplatz",
"questVice1Text": "Laster, Teil 1: Befreie Dich vom Einfluss des Drachen",
- "questVice1Notes": "
Sie sagen, es liegt der Schrecken in den Höhlen von Habitica. Ein Monster, deren reine Präsenz den Willen der Helden des Landes so verdreht, dass sie zu schlechten Angewohnheiten und Faulheit gezwungen werden! Das Monster ist ein gewaltiger Drache, der aus den Schatten selbst besteht: Vice, der Schatten-Wyrm. Mutige Habiteers, erhebt euch und zerschlagt das Biest ein für alle Mal, aber nur, wenn ihr daran glaubt, gegen seine immense Kraft bestehen zu können.
Vice, Teil 1:
Wie kann man das Monster besiegen, wenn es schon diese Macht über Dich besitzt? Werde nicht Opfer der Faulheit und Laster! Arbeite hart gegen den dunklen Einfluss des Drachens, und zerschlage seinen Einfluss!",
+ "questVice1Notes": "
Sie sagen, es liegt der Schrecken in den Höhlen von Habitica. Ein Monster, deren reine Präsenz den Willen der Helden des Landes so verdreht, dass sie zu schlechten Angewohnheiten und Faulheit gezwungen werden! Das Monster ist ein gewaltiger Drache, der aus den Schatten selbst besteht: Vice, der Schatten-Wyrm. Mutige Habiticaner, erhebt euch und zerschlagt das Biest ein für alle Mal, aber nur, wenn ihr daran glaubt, gegen seine immense Kraft bestehen zu können.
Vice, Teil 1:
Wie kann man das Monster besiegen, wenn es schon diese Macht über Dich besitzt? Werde nicht Opfer der Faulheit und Laster! Arbeite hart gegen den dunklen Einfluss des Drachens, und zerschlage seinen Einfluss!
",
"questVice1Boss": "Lasters Schatten",
"questVice1DropVice2Quest": "Laster Teil 2 (Rolle)",
"questVice2Text": "Laster, Teil 2: Finde den Hort des Wyrmes",
@@ -74,36 +74,36 @@
"questVice3DropDragonEgg": "Drache (Ei)",
"questVice3DropShadeHatchingPotion": "Schatten Schlüpftrank",
"questMoonstone1Text": "Die Mondsteinkette, Teil 1: Die Mondsteinkette",
- "questMoonstone1Notes": "Eine furchtbares Leiden hat die Habiticaner befallen. Längst totgeglaubte schlechte Angewohnheiten melden sich mit aller Macht zurück. Geschirr bleibt dreckig liegen, ungelesene Lehrbücher stapeln sich ungelesen in die Höhe und die Aufschieberitis ist außer Kontrolle geraten!
Du verfolgst einige Deiner eigenen zurückgekehrten schlechten Angewohnheiten bis zu den Sümpfen der Stagnation und enttarnst die Übeltäterin: die geisterhafte Totenbeschwörerin Recidivate. Mit gezückten Waffen stürmst Du auf sie zu, aber sie gleiten nutzlos durch ihren Spektralkörper.
\"Versuch's erst gar nicht\" faucht sie mit einem trockenen Krächzen. \"Ohne eine Kette aus Mondsteinen bin ich unbesiegbar - und Meisterschmuckhersteller @aurakami hat die Mondsteine vor langer Zeit über ganz Habitica verstreut! Nach Luft schnappend tritts Du den Rückzug an ... aber Du bist dir im Klaren, was Du zu tun hast.",
+ "questMoonstone1Notes": "Ein furchtbares Leiden hat die Habiticaner befallen. Längst totgeglaubte schlechte Angewohnheiten melden sich mit aller Macht zurück. Geschirr bleibt dreckig liegen, Lehrbücher stapeln sich ungelesen in die Höhe und die Aufschieberitis ist außer Kontrolle geraten!
Du verfolgst einige Deiner eigenen zurückgekehrten schlechten Angewohnheiten bis zu den Sümpfen der Stagnation und enttarnst die Übeltäterin: die geisterhafte Totenbeschwörerin Recidivate. Mit gezückten Waffen stürmst Du auf sie zu, aber sie gleiten nutzlos durch ihren Spektralkörper.
\"Versuch's erst gar nicht\" faucht sie mit einem trockenen Krächzen. \"Ohne eine Kette aus Mondsteinen bin ich unbesiegbar - und Meisterschmuckhersteller @aurakami hat die Mondsteine vor langer Zeit über ganz Habitica verstreut! Nach Luft schnappend trittst Du den Rückzug an ... aber Du bist Dir im Klaren, was Du zu tun hast.",
"questMoonstone1CollectMoonstone": "Mondsteine",
"questMoonstone1DropMoonstone2Quest": "Die Mondsteinkette Teil 2: Die Totenbeschwörerin Recidivate (Quest-Rolle)",
"questMoonstone2Text": "Die Mondsteinkette Teil 2: Die Totenbeschwörerin Recidivate",
- "questMoonstone2Notes": "Der tapfere Waffenschmied @Inventrix hilft dir, aus den verzauberten Mondsteinen eine Kette zu formen. Du bist endlich bereit, Recidivate entgegenzutreten, aber kaum, dass Du die Sümpfe der Stagnation betrittst, läuft dir ein fürchterlicher Schauer über den Rücken.
Verrottetes Fleisch flüstert in Dein Ohr. \"Wieder zurückgekehrt? Wie entzückend ... \" Du drehst Dich und schlägst zu, und im Licht der Mondsteinkette trifft Deine Waffe auf festes Fleisch. \"Du magst mich einmal mehr an diese Welt gebunden haben,\" knurrt Recidivate, \"aber jetzt ist Deine Zeit gekommen, sie zu verlassen!\"",
+ "questMoonstone2Notes": "Der tapfere Waffenschmied @Inventrix hilft Dir, aus den verzauberten Mondsteinen eine Kette zu formen. Du bist endlich bereit, Recidivate entgegenzutreten, aber kaum, dass Du die Sümpfe der Stagnation betrittst, läuft Dir ein fürchterlicher Schauer über den Rücken.
Verrottetes Fleisch flüstert in Dein Ohr. \"Wieder zurückgekehrt? Wie entzückend ...\" Du drehst Dich und schlägst zu, und im Licht der Mondsteinkette trifft Deine Waffe auf festes Fleisch. \"Du magst mich einmal mehr an diese Welt gebunden haben,\" knurrt Recidivate, \"aber jetzt ist Deine Zeit gekommen, sie zu verlassen!\"",
"questMoonstone2Boss": "Die Totenbeschwörerin",
"questMoonstone2DropMoonstone3Quest": "Die Mondsteinkette Teil 3: Recidivate Transformiert (Rolle)",
"questMoonstone3Text": "Die Mondsteinkette Teil 3: Recidivate transformiert",
- "questMoonstone3Notes": "Recidivate sackt zu Boden und Du schlägst mit der Mondsteinkette nach ihr. Zu Deinem Entsetzen reißt Rückfall die Steine an sich und ihre Augen leuchten vor Triumph.
\"Törichte Kreatur des Fleisches!\", schreit sie. \"Es ist wahr, die Mondsteine werden mich wieder in eine körperliche Form zurückversetzen, aber anders, als Du dir vorgestellt hast. So wie der Vollmond in der Dunkelheit heranwächst, wird auch meine Macht zunehmen, und aus den Schatten rufe ich Deinen fürchterlichsten Feind hervor!\"
Ein übler, grüner Nebel steigt aus dem Sumpf empor, Recidivate Körper verkrümmt und windet sich und nimmt eine Form an, die Dich mit Schrecken erfüllt - der untote Körper von Laster ist wiederauferstanden.",
- "questMoonstone3Completion": "Du atmest schwer und Schweiß brennt in Deinen Augen, als der untote Wyrm zusammenbricht und Rückfalls Überreste sich in einen dünnen, grauen Nebel auflösen, den eine frische Brise bald zerstreut. In der Ferne hörst Du die Schlachtrufe von Habiticanern, die ihre schlechten Gewohnheiten ein für allemal besiegt haben.
@Baconsaur, der Herr aller Bestien, landet mit seinem Greif neben dir. \"Ich habe das Ende Deines Kampfes vom Himmel aus beobachtet und es hat mich sehr berührt. Bitte nimm diese verzauberte Tunika – Deine Tapferkeit zeugt von einem edlen Herzen und ich glaube, dass Du dazu bestimmt bist, sie zu tragen.\"",
+ "questMoonstone3Notes": "Recidivate sackt zu Boden und Du schlägst mit der Mondsteinkette nach ihr. Zu Deinem Entsetzen reißt Rückfall die Steine an sich und ihre Augen leuchten vor Triumph.
\"Törichte Kreatur des Fleisches!\", schreit sie. \"Es ist wahr, die Mondsteine werden mich wieder in eine körperliche Form zurückversetzen, aber anders, als Du Dir vorgestellt hast. So wie der Vollmond in der Dunkelheit heranwächst, wird auch meine Macht zunehmen, und aus den Schatten rufe ich Deinen fürchterlichsten Feind hervor!\"
Ein übler, grüner Nebel steigt aus dem Sumpf empor, Recidivate Körper verkrümmt und windet sich und nimmt eine Form an, die Dich mit Schrecken erfüllt - der untote Körper von Laster ist wiederauferstanden.",
+ "questMoonstone3Completion": "Du atmest schwer und Schweiß brennt in Deinen Augen, als der untote Wyrm zusammenbricht und Rückfalls Überreste sich in einen dünnen, grauen Nebel auflösen, den eine frische Brise bald zerstreut. In der Ferne hörst Du die Schlachtrufe von Habiticanern, die ihre schlechten Gewohnheiten ein für allemal besiegt haben.
@Baconsaur, der Herr aller Bestien, landet mit seinem Greif neben Dir. \"Ich habe das Ende Deines Kampfes vom Himmel aus beobachtet und es hat mich sehr berührt. Bitte nimm diese verzauberte Tunika – Deine Tapferkeit zeugt von einem edlen Herzen und ich glaube, dass Du dazu bestimmt bist, sie zu tragen.\"",
"questMoonstone3Boss": "Nekro-Laster",
"questMoonstone3DropRottenMeat": "Verrottetes Fleisch (Futter)",
"questMoonstone3DropZombiePotion": "Zombifizierter Schlüpftrank",
"questGoldenknight1Text": "Der goldene Ritter, Teil 1: Ein ernstes Gespräch",
- "questGoldenknight1Notes": "Die goldene Ritterin ist Habiticanern mit ihrer Kritik ganz schön auf die Nerven gegangen. Nicht alle täglichen Aufgaben erledigt? Eine negative Gewohnheit angeklickt? Sie nimmt dies als Anlass Dich zu bedrängen, dass Du doch ihrem Beispiel folgen sollst. Sie ist das leuchtende Beispiel eines perfekten Habiticaners und Du bist nichts als ein Versager. Das ist ja mal gar nicht nett! Jeder macht Fehler. Man sollte deshalb nicht mit solcher Kritik drangsaliert werden. Vielleicht solltest Du einige Zeugenaussagen von verletzten Habiticanern zusammentragen und die goldene Ritterin mal ordentlich zurechtweisen!",
+ "questGoldenknight1Notes": "Die goldene Ritterin ist Habiticanern mit ihrer Kritik ganz schön auf die Nerven gegangen. Nicht alle täglichen Aufgaben erledigt? Eine negative Gewohnheit angeklickt? Sie nimmt dies zum Anlass Dich zu bedrängen, dass Du doch ihrem Beispiel folgen sollst. Sie ist das leuchtende Beispiel eines perfekten Habiticaners und Du bist nichts als ein Versager. Das ist ja mal gar nicht nett! Jeder macht Fehler. Man sollte deshalb nicht mit solcher Kritik drangsaliert werden. Vielleicht solltest Du einige Zeugenaussagen von verletzten Habiticanern zusammentragen und die goldene Ritterin mal ordentlich zurechtweisen!",
"questGoldenknight1CollectTestimony": "Zeugenaussagen",
- "questGoldenknight1DropGoldenknight2Quest": "Die goldene Ritter-Kette, Teil 2: Der goldene Ritter (Rolle)",
+ "questGoldenknight1DropGoldenknight2Quest": "Die goldene Ritterin-Reihe, Teil 2: Die Goldene Ritterin (Schriftrolle)",
"questGoldenknight2Text": "Der goldene Ritter, Teil 2: Gold Ritter",
- "questGoldenknight2Notes": "Mit hunderten Zeugenaussagen von Habiticanern bewaffnet, konfrontierst Du die goldene Ritterin. Du fängst an, ihr die Beschwerden der Habiticaner eine nach der anderen vorzulesen. \"Und @Pfeffernusse sagt, dass Deine ständige Prahlerei-\" Die Ritterin hebt ihre Hand, um Dich zum Schweigen zu bringen und spottet \"Ich bitte Dich, diese Leute sind einfach nur neidisch auf meinen Erfolg. Statt sich zu beschweren, sollten sie einfach so hart arbeiten wie ich! Vielleicht zeige ich dir mal, wie stark Du werden kannst, wenn Du so fleißig bist wie ich!\" Sie hebt ihren Morgenstern und setzt zum Angriff an!",
+ "questGoldenknight2Notes": "Mit hunderten Zeugenaussagen von Habiticanern bewaffnet, konfrontierst Du die goldene Ritterin. Du fängst an, ihr die Beschwerden der Habiticaner eine nach der anderen vorzulesen. \"Und @Pfeffernusse sagt, dass Deine ständige Prahlerei-\" Die Ritterin hebt ihre Hand, um Dich zum Schweigen zu bringen und spottet \"Ich bitte Dich, diese Leute sind einfach nur neidisch auf meinen Erfolg. Statt sich zu beschweren, sollten sie einfach so hart arbeiten wie ich! Vielleicht zeige ich Dir mal, wie stark Du werden kannst, wenn Du so fleißig bist wie ich!\" Sie hebt ihren Morgenstern und setzt zum Angriff an!",
"questGoldenknight2Boss": "Goldene Ritterin",
"questGoldenknight2DropGoldenknight3Quest": "Die Goldene Ritterin-Kette Teil 3: Der Eiserne Ritter (Rolle)",
"questGoldenknight3Text": "Der goldene Ritter, Teil 3: Der eiserne Ritter",
- "questGoldenknight3Notes": "@Jon Arinbjorn schreit laut auf, um Deine Aufmerksamkeit zu erlangen. Nach Deiner Schlacht ist eine neue Figur aufgetaucht. Ein Ritter, gerüstet in schwarz geflecktem Eisen, kommt dir langsam mit einem Schwert in der Hand entgegen. Die goldene Ritterin ruft ihm zu: \"Vater, nein!\" Aber der Ritter zeigt keinerlei Anzeichen anzuhalten. Sie wendet sich an Dich: \"Es tut mir Leid. Ich war ein Narr und zu stolz zu erkennen, wie grausam ich war. Aber mein Vater ist noch viel grausamer als ich es je sein könnte. Wenn er nicht aufgehalten wird, dann wird er uns alle vernichten. Hier, nimm meinen Morgenstern und halte den eisernen Ritter auf!\"",
- "questGoldenknight3Completion": "Mit einem befriedigenden Klirren fällt der Eiserne Ritter zu Knie und sackt zusammen. \"Du bist ziemlich stark,\" keucht er. \"Mir wurde heute Bescheidenheit gelehrt.\" Die Goldene Ritterin kommt zu dir und sagt, \"Ich danke dir. Ich glaube, wir beide haben aus der Begegnung mit dir etwas Bescheidenheit gelernt. Ich werde mit meinem Vater sprechen und ihm die Beschwerden über uns erklären. Vielleicht sollten wir damit anfangen, uns bei den anderen Habiticanern zu entschuldigen.\" Sie denkt darüber nach und wendet sich dann wieder dir zu. \"Hier: als ein Geschenk für Dich gebe ich dir meinen Morgenstern. Er gehört jetzt dir.\"",
+ "questGoldenknight3Notes": "@Jon Arinbjorn schreit laut auf, um Deine Aufmerksamkeit zu erlangen. Nach Deiner Schlacht ist eine neue Figur aufgetaucht. Ein Ritter, gerüstet in schwarz geflecktem Eisen, kommt Dir langsam mit einem Schwert in der Hand entgegen. Die goldene Ritterin ruft ihm zu: \"Vater, nein!\" Aber der Ritter zeigt keinerlei Anzeichen anzuhalten. Sie wendet sich an Dich: \"Es tut mir Leid. Ich war ein Narr und zu stolz zu erkennen, wie grausam ich war. Aber mein Vater ist noch viel grausamer als ich es je sein könnte. Wenn er nicht aufgehalten wird, dann wird er uns alle vernichten. Hier, nimm meinen Morgenstern und halte den eisernen Ritter auf!\"",
+ "questGoldenknight3Completion": "Mit einem befriedigenden Klirren fällt der Eiserne Ritter zu Knie und sackt zusammen. \"Du bist ziemlich stark,\" keucht er. \"Mir wurde heute Bescheidenheit gelehrt.\" Die Goldene Ritterin kommt zu Dir und sagt, \"Ich danke Dir. Ich glaube, wir beide haben aus der Begegnung mit Dir etwas Bescheidenheit gelernt. Ich werde mit meinem Vater sprechen und ihm die Beschwerden über uns erklären. Vielleicht sollten wir damit anfangen, uns bei den anderen Habiticanern zu entschuldigen.\" Sie denkt darüber nach und wendet sich dann wieder Dir zu. \"Hier: als ein Geschenk für Dich gebe ich Dir meinen Morgenstern. Er gehört jetzt Dir.\"",
"questGoldenknight3Boss": "Der Eiserne Ritter",
"questGoldenknight3DropHoney": "Honig (Futter)",
"questGoldenknight3DropGoldenPotion": "Goldener Schlüpftrank",
"questGoldenknight3DropWeapon": "Mustaines Meilenstein-matschender Morgenstern (Schildhand-Waffe)",
"questBasilistText": "Der Basi-List",
- "questBasilistNotes": "Da ist ein Aufruhr auf dem Marktplatz - es sieht ganz so aus, als ob man lieber in die andere Richtung rennen sollte. Da Du aber ein mutiger Abenteurer bist, rennst Du stattdessen darauf zu und entdeckst einen Basi-List, der sich aus einem Haufen unerledigter Aufgaben geformt hat! Alle umstehenden Habiticaner sind aus Angst vor der Länge des Basi-Lists gelähmt und können nicht anfangen zu arbeiten. Von irgendwo in der Nähe hörst Du @Arcosine schreien: \"Schnell! Erledige Deine einmaligen und täglichen Aufgaben, um dem Monster die Zähne zu entfernen, bevor sich jemand am Papier schneidet!\" Greife schnell an, Abenteurer, und hake etwas ab - aber Vorsicht! Wenn Du irgendwelche täglichen Aufgaben nicht erledigst, wird der Basi-List Dich und Deine Gruppe angreifen!",
+ "questBasilistNotes": "Da ist ein Aufruhr auf dem Marktplatz - es sieht ganz so aus, als ob man lieber in die andere Richtung rennen sollte. Da Du aber ein mutiger Abenteurer bist, rennst Du stattdessen darauf zu und entdeckst einen Basi-List, der sich aus einem Haufen unerledigter Aufgaben geformt hat! Alle umstehenden Habiticaner sind aus Angst vor der Länge des Basi-Lists gelähmt und können nicht anfangen zu arbeiten. Von irgendwo in der Nähe hörst Du @Arcosine schreien: \"Schnell! Erledige Deine To-Dos und täglichen Aufgaben, um dem Monster die Zähne zu entfernen, bevor sich jemand am Papier schneidet!\" Greife schnell an, Abenteurer, und hake etwas ab - aber Vorsicht! Wenn Du irgendwelche täglichen Aufgaben nicht erledigst, wird der Basi-List Dich und Deine Gruppe angreifen!",
"questBasilistCompletion": "Der Basi-List ist in Papierschnitzel zerfallen, die sanft in Regenbogenfarben schimmern. \"Puh!\" sagt @Arcosine. \"Gut, dass ihr gerade hier wart!\" Du fühlst Dich erfahrener als vorher und sammelst ein paar verstreute Goldstücke zwischen den Papierstücken auf.",
"questBasilistBoss": "Der Basi-List",
"questEggHuntText": "Eierjagd",
@@ -124,7 +124,7 @@
"questDilatoryCompletion": "'Der Sieg über den Schreckensdrachen von Dilatory'\n\nWir haben es geschafft! Der Schreckensdrachen bricht mit einem allerletzten Gebrüll zusammen und schwimmt weit, weit fort. Gruppen jubelnder Habiticaner stehen an den Küsten! Wir haben Matt, Daniel und Alex gefunden, ihre Gebäude wieder aufzubauen. Aber was ist das? \n\n'Die Bürger kehren zurück!'\n\nJetzt wo der Drache geflohen ist, steigen im Meer tausende glitzernde Farben auf. Es ist ein Regenbogenschwarm von Fangschreckenkrebsen.. und zwischen ihnen, hunderte von Wassermenschen! \n\n\"Wir sind die verlorenen Bürger von Dilatory!\" erklärt ihr Anführer, Manta. \"Als Dilatory sank, verwandelten uns die Fangschreckenkrebse mit einem Zauberspruch in Wassermenschen, sodass wir überleben konnten. Aber in seinem Zorn fing uns der Schreckensdrache alle in der dunklen Meeresspalte. Wir waren dort hunderte Jahre gefangen - aber jetzt sind wir endlich frei und können unsere Stadt wieder aufbauen! \n\n\"Als Dankeschön,\" sagt @Ottl, \"Bitte nimm dieses Fangschreckenkrebs-Haustier, dieses Fangschreckenkrebs-Reittier und EXP, Gold und unsere ewige Dankbarkeit an!\"\n\n'Belohnungen'\n* Fangschreckenkrebs-Haustier\n* Fangschreckenkrebs-Reittier\n* Schokolade, blaue Zuckerwatte, pinke Zuckerwatte, Fisch, Honig, Fleisch, Milch, Kartoffel, verdorbenes Fleisch, Erdbeere",
"questSeahorseText": "Das Dilatory Rennen",
"questSeahorseNotes": "Es ist der Tag des Derbys, und Habiticaner von überall auf dem Kontinent sind nach Dilatory gereist, um ihre Seepferdchen-Haustieren gegeneinander antreten zu lassen! Plötzlich bricht auf der Rennstrecke ein großes Spritzen und Fauchen aus und Du hörst Seepferdchenpfleger @Kiwibot über das Tosen der Wellen hinwegbrüllen. \"Das Treffen der Seepferdchen hat einen kämpferischen Seehengst angelockt!\" schreit sie. \"Er trampelt durch die Ställe und zerstört die uralte Rennstrecke! Kann ihn irgendwer beruhigen?\"",
- "questSeahorseCompletion": "Der jetzt zahme Seehengst schwimmt gefügig zu Dir. \"Oh, sieh doch!\" sagt Kiwibot. \"Er will, dass wir uns um seine Kinder kümmern.\" Sie gibt dir drei Eier. \"Zieh sie gut auf,\" sagt sie. \"Du bist beim Dilatory Derby jederzeit willkommen!\"",
+ "questSeahorseCompletion": "Der jetzt zahme Seehengst schwimmt gefügig zu Dir. \"Oh, sieh doch!\" sagt Kiwibot. \"Er will, dass wir uns um seine Kinder kümmern.\" Sie gibt Dir drei Eier. \"Zieh sie gut auf,\" sagt sie. \"Du bist beim Dilatory Derby jederzeit willkommen!\"",
"questSeahorseBoss": "Seehengst",
"questSeahorseDropSeahorseEgg": "Seehengst (Ei)",
"questSeahorseUnlockText": "Ermöglicht den Kauf von Seehengsteiern auf dem Marktplatz",
@@ -138,23 +138,23 @@
"questAtom2Drop": "Der Wäschebeschwörer (Questschriftrolle)",
"questAtom3Text": "Angriff des Banalen, Teil 3: Der Wäschebeschwörer",
"questAtom3Notes": "Mit einem ohrenbetäubenden Schrei und fünf leckere Arten von Käse spuckend zerfällt das Monster vom KochLess in Stücke. \"DU WAGST ES!?\" dröhnt eine Stimme von unter dem See. Eine blaue Gestalt, erhebt sich in eine Robe gekleidet aus dem Wasser und schwingt eine magische Toilettenbürste. Schmutzige Wäsche beginnt im See aufzusteigen. \"Ich bin der Wäschebeschwörer!\" verkündet die Gestalt ärgerlich. \"Du traust Dir ja ganz schön was zu - einfach so mein wunderbar schmutziges Geschirr abzuspülen, mein Haustier zu verscheuchen und mein Reich mit solch sauberer Kleidung zu betreten. Nimm' Dich in Acht! Spüre den durchnässten Zorn meiner Anti-Wäsche Magie!\"",
- "questAtom3Completion": "Der böse Wäschebeschwörer ist besiegt! Saubere Wäsche sammelt sich überall haufenweise. Alles sieht recht ordentlich aus. Wie Du durch die frisch gebügelten Rüstungen watest fällt dir ein metallischer Schein ins Auge und Du bemerkst einen glänzenden Helm. Der ursprüngliche Eigentümer dieses glänzenden Schatzes mag unbekannt sein, aber als Du ihn aufsetzt bemerkst Du die wärmende Gegenwart eines freizügigen Geistes. Zu schade, dass niemand ein Namensschild angenäht hat.",
+ "questAtom3Completion": "Der böse Wäschebeschwörer ist besiegt! Saubere Wäsche sammelt sich überall haufenweise. Alles sieht recht ordentlich aus. Wie Du durch die frisch gebügelten Rüstungen watest fällt Dir ein metallischer Schein ins Auge und Du bemerkst einen glänzenden Helm. Der ursprüngliche Eigentümer dieses glänzenden Schatzes mag unbekannt sein, aber als Du ihn aufsetzt bemerkst Du die wärmende Gegenwart eines freizügigen Geistes. Zu schade, dass niemand ein Namensschild angenäht hat.",
"questAtom3Boss": "Der Wäschebeschwörer",
"questAtom3DropPotion": "Standard-Schlüpftrank",
"questOwlText": "Die Nachteule",
"questOwlNotes": "Das Gasthauslicht brennt bis in die Morgenstunden doch eines Nachts ist das Leuchten verschwunden! Wo sollen denn jetzt alle Nachtschwärmer hin? @Twitching ruft, \"Wir müssen in den Kampfe ziehn!\" Siehst Du diese Nachteule, den gefiederten Feind? Kämpfe schnell, kämpfe gut, mach sie klein! Wir werden ihren Schatten schnell verscheuchen, und die Nacht wieder hell erleuchten!\"",
- "questOwlCompletion": "Die Nachteul' verblasst, bevor die Nacht vergeht Doch dennoch fühlst Du, wie es in dir gähnt. Vielleicht ist es Zeit, sich hinzulegen? In Deinem Bett siehst Du ein Nestchen liegen! Die Nachteul' weiß, es ist sehr schön spät zu arbeiten und spät ins Bett zu gehn, Aber Deine neuen Haustiere werden leise krähen und dir dann sagen, \"Du sollst schlafen gehen\".",
+ "questOwlCompletion": "Die Nachteul' verblasst, bevor die Nacht vergeht Doch dennoch fühlst Du, wie es in Dir gähnt. Vielleicht ist es Zeit, sich hinzulegen? In Deinem Bett siehst Du ein Nestchen liegen! Die Nachteul' weiß, es ist sehr schön spät zu arbeiten und spät ins Bett zu gehn, Aber Deine neuen Haustiere werden leise krähen und Dir dann sagen, \"Du sollst schlafen gehen\".",
"questOwlBoss": "Die Nachteule",
"questOwlDropOwlEgg": "Eule (Ei)",
"questOwlUnlockText": "Ermöglicht den Kauf von Euleneiern auf dem Marktplatz",
"questPenguinText": "Der Federvieh-Frost",
- "questPenguinNotes": "Obwohl es auf der Südspitze von Habitica ein heißer Sommertag ist, hat eine unnatürliche Kälte den Lively Lake befallen. Man hört das Heulen von starken, eisigen Winden und das Ufer fängt an zuzufrieren. Eisspitzen brechen aus dem Boden und verdrängen Gras und Dreck. @Melynnrose und @Breadstrings rennen zu dir hinüber.
\"Hilfe!\" sagt @Melynnrose. \"Wir haben einen riesigen Pinguin hergebracht, um den See zuzufrieren damit wir alle schlittschuhlaufen können, aber uns sind die Fische ausgegangen, mit denen wir ihn gefüttert haben!\"
\"Er wurde wütend und friert mit seinem Eis-Atem alles zu, was er sieht!\" sagt @Breadstrings. \"Bitte, Du musst ihn überwältigen bevor wir alle von Eis bedeckt sind!\" Sieht aus, als ob Du das Gemüt dieses Pinguins ... etwas abkühlen musst.",
+ "questPenguinNotes": "Obwohl es auf der Südspitze von Habitica ein heißer Sommertag ist, hat eine unnatürliche Kälte den Lively Lake befallen. Man hört das Heulen von starken, eisigen Winden und das Ufer fängt an zuzufrieren. Eisspitzen brechen aus dem Boden und verdrängen Gras und Dreck. @Melynnrose und @Breadstrings rennen zu Dir hinüber.
\"Hilfe!\" sagt @Melynnrose. \"Wir haben einen riesigen Pinguin hergebracht, um den See zuzufrieren damit wir alle schlittschuhlaufen können, aber uns sind die Fische ausgegangen, mit denen wir ihn gefüttert haben!\"
\"Er wurde wütend und friert mit seinem Eis-Atem alles zu, was er sieht!\" sagt @Breadstrings. \"Bitte, Du musst ihn überwältigen bevor wir alle von Eis bedeckt sind!\" Sieht aus, als ob Du das Gemüt dieses Pinguins ... etwas abkühlen musst.",
"questPenguinCompletion": "Mit der Niederlage des Pinguins beginnt das Eis zu schmelzen. Der riesige Pinguin setzt sich im Sonnenschein auf den Boden und schlürft einen Eimer Fische herunter. Er gleitet über den See und lässt dabei mit einem leichten Pusten nach unten glattes, glitzerndes Eis entstehen. Was für ein komischer Vogel! \"Es scheint so, als hätte er einige Eier hinterlassen,\" sagt @Painter de Cluster.
@Rattify lacht. \"Vielleicht werden diese Pinguine ein bisschen ... gechillter sein?\"",
"questPenguinBoss": "Frostpinguin",
"questPenguinDropPenguinEgg": "Pinguin (Ei)",
"questPenguinUnlockText": "Ermöglicht den Kauf von Pinguineiern auf dem Marktplatz",
"questStressbeastText": "Das Schreckliche Stressbiest aus den Stoïstillen Steppen",
- "questStressbeastNotes": "Erfülle tägliche, sowie einmalige Aufgaben um dem Welt-Bossmonster Schaden zuzufügen! Unerfüllte tägliche Aufgaben füllen die Stressschlag Leiste. Ist die Leiste voll, wird der Weltboss einen NPC angreifen. Ein Weltboss wird einzelnen Spielern oder Accounts auf keine Weise Schaden zufügen. Nur die nicht erfüllten täglichen Aufgaben von aktiven Spielern, die sich nicht im Gasthaus ausruhen zählen.
~*~
Das erste was wir vernehmen sind die Schritte, langsam und donnernd. Einer nach dem anderen öffnen die Habiticaner ihre Haustüren und blicken dem entgegen und die Worte bleiben uns im Halse stecken.
Wir alle kennen das Stressbiest, natürlich - winzige, fiese Kreaturen, die uns im ungünstigsten Augenblick angreifen. Aber das? Das hier ragt in den Himmel hinauf, höher als die Gebäude, mit Pranken, die ohne Probleme einen Drachen zerschmettern könnten. Frostsplitter regnen aus dem stinkenden Fell herab und sein Gebrüll entfesselt einen eisigen Sturm, der die Dächer von unseren Häusern hebt. Von so einem gewaltigen Monster sprechen nur unsere ältesten Legenden.
\"Gebt acht, Habiticaner!\", ruft SabreCat, \"Verbarrikadiert euch in euren Häusern - dies ist das schreckliche Stressbiest!\"
\"Dieses Ding muss Jahrhunderte von Stress in sich tragen!\", sagt Kiwibot, während er die Türen des Gasthauses verrammelt und die Fenster zuschlägt.
\"Die Stoïstillen Steppen\", meint Lemnos mit grimmigem Gesicht, \"Die ganze Zeit dachten wir sie wären ein friedlicher Ort, aber sie müssen ihren Stress irgendwo versteckt haben. Über Generationen hinweg ist das hier aus ihm geworden, und nun hat es sich befreit und griff sie an - und uns!\"
Es gibt nur eine Möglichkeit das Stressbiest zu vertreiben, schrecklich oder nicht, und die ist es, es mit erfüllten täglichen und einmaligen Aufgaben anzugreifen! Wir müssen zusammen stehen um gegen diesen furchteinflößenden Feind zu bestehen - geht sicher, dass ihr eure Aufgaben nicht unerfüllt lasst, das könnte das Stressbiest so sehr reizen, dass es vielleicht anfängt um sich zu schlagen ...",
+ "questStressbeastNotes": "Erfülle tägliche Aufgaben und To-Dos um dem Weltbossmonster Schaden zuzufügen! Unerfüllte tägliche Aufgaben füllen die Stressschlag Leiste. Ist die Leiste voll, wird der Weltboss einen NPC angreifen. Ein Weltboss wird einzelnen Spielern oder Accounts auf keine Weise Schaden zufügen. Nur die nicht erfüllten täglichen Aufgaben von aktiven Spielern, die sich nicht im Gasthaus ausruhen zählen.
~*~
Das erste was wir vernehmen sind die Schritte, langsam und donnernd. Einer nach dem anderen öffnen die Habiticaner ihre Haustüren und blicken dem entgegen und die Worte bleiben uns im Halse stecken.
Wir alle kennen das Stressbiest, natürlich - winzige, fiese Kreaturen, die uns im ungünstigsten Augenblick angreifen. Aber das? Das hier ragt in den Himmel hinauf, höher als die Gebäude, mit Pranken, die ohne Probleme einen Drachen zerschmettern könnten. Frostsplitter regnen aus dem stinkenden Fell herab und sein Gebrüll entfesselt einen eisigen Sturm, der die Dächer von unseren Häusern hebt. Von so einem gewaltigen Monster sprechen nur unsere ältesten Legenden.
\"Gebt acht, Habiticaner!\", ruft SabreCat, \"Verbarrikadiert euch in euren Häusern - dies ist das schreckliche Stressbiest!\"
\"Dieses Ding muss Jahrhunderte von Stress in sich tragen!\", sagt Kiwibot, während er die Türen des Gasthauses verrammelt und die Fenster zuschlägt.
\"Die Stoïstillen Steppen\", meint Lemnos mit grimmigem Gesicht, \"Die ganze Zeit dachten wir sie wären ein friedlicher Ort, aber sie müssen ihren Stress irgendwo versteckt haben. Über Generationen hinweg ist das hier aus ihm geworden, und nun hat es sich befreit und griff sie an - und uns!\"
Es gibt nur eine Möglichkeit das Stressbiest zu vertreiben, schrecklich oder nicht, und die ist es, es mit erfüllten täglichen und einmaligen Aufgaben anzugreifen! Wir müssen zusammen stehen um gegen diesen furchteinflößenden Feind zu bestehen - geht sicher, dass ihr eure Aufgaben nicht unerfüllt lasst, das könnte das Stressbiest so sehr reizen, dass es vielleicht anfängt um sich zu schlagen ...",
"questStressbeastBoss": "Das schreckliche Stressbiest",
"questStressbeastBossRageTitle": "Stressschlag",
"questStressbeastBossRageDescription": "Wenn sich diese Anzeige füllt, entfesselt das Schreckliche Stressbiest seinen Stressschlag auf Habitica!",
@@ -168,11 +168,11 @@
"questStressbeastCompletionChat": "'Das schreckliche Stressbiest ist BESIEGT!'\n\nWir haben es geschafft! Mit einem letzten Aufheulen löst sich das Schreckliche Stressbiest in eine Wolke aus Schnee auf. Die Flocken schweben sachte zu Boden, während jubelnde Habiticaner ihre Haustiere und Reittiere umarmen. Unsere Tiere und unsere NPCs sind gerettet!!\n\nStoïstill ist gerettet!\n\nSabreCat spricht sanft mit einem kleinen Säbelzahntiger. \"Bitte finde die Einwohner der Stoïstillen Steppe und führe sie zu uns\", sagt er. Einige Stunden später kehrt der Säbelzahntiger zurück. Ihm folgt gemächlich eine Herde von Mammutreitern. Du erkennst den Anführer der Reiter. Es ist Lady Glaciate, die Anführerin von Stoïstill.\n\nTapfere Habiticaner\", spricht sie, \"Meine Einwohner und ich sind Euch zu tiefsten Dank verpflichtet und wir müssen uns entschuldigen. Bei dem Versuch unsere Steppen vor Unruhe zu schützen, haben wir heimlich all unseren Stress in die Frostberge verbannt. Wir hatten ja keine Ahnung, dass sich der Stress dort über Generationen sammeln und schließlich zu dem Stressbiest werden würde, dem Ihr begegnet seid! Als es ausbrach, waren wir alle unter den Trümmern seines Gefängnisses in den Bergen gefangen und es begann unsere geliebten Tiere zu jagen.\" Ihr trauriger Blick folgt dem herabrieselndem Schnee. \"Wir haben durch unsere Dummheit alle in Gefahr gebracht. Seid versichert, dass wir in Zukunft mit unseren Problemen zu Euch kommen werden - bevor die Probleme zu Euch kommen.\"\n\nSie dreht sich zu @Baconsaur, der mit einigen der Babymammuts kuschelt. \"Wir haben Euren Tieren etwas Leckeres zu essen mitgebracht, um uns für den Schrecken, den wir verursachten, zu entschuldigen und als ein Symbol des Vertrauens werden wir einige unserer Haus- und Reittiere bei Euch lassen. Wir wissen, dass Ihr Euch gut um sie kümmern werdet.\"",
"questTRexText": "König der Dinosaurier",
"questTRexNotes": "Jetzt, wo uralte Kreaturen der Stoïkalm Steppen überall in Habitica umherstreifen, hat @Urse beschlossen, einen ausgewachsenen Tyrannosaurus zu adoptieren. Was kann dabei schon schiefgehen?
Alles.",
- "questTRexCompletion": "Der wilde Dinosaurier hört endlich zu toben und zu randalieren auf, setzt sich ruhig hin und fängt an sich mit den riesigen Hähnen anzufreunden. @Urse strahlt ihn an. \"Sie sind doch gar keine so schlimmen Haustiere! Sie brauchen einfach nur ein bisschen Disziplin. Hier, nimm dir ein paar Tyrannosaurus-Eier mit!\"",
+ "questTRexCompletion": "Der wilde Dinosaurier hört endlich zu toben und zu randalieren auf, setzt sich ruhig hin und fängt an sich mit den riesigen Hähnen anzufreunden. @Urse strahlt ihn an. \"Sie sind doch gar keine so schlimmen Haustiere! Sie brauchen einfach nur ein bisschen Disziplin. Hier, nimm Dir ein paar Tyrannosaurus-Eier mit!\"",
"questTRexBoss": "Fleischerner Tyrannosaurus",
"questTRexUndeadText": "Der Dinosaurier aus den Tiefen der Erde",
"questTRexUndeadNotes": "Während die uralten Dinosaurier der Stoïstillen Steppen durch HabitCity wandern, hört man auf ein mal einen angstvollen Schrei aus Richtung des Grand Museums. @Baconsaur ruft: \"Das Tyrannosaurus Skelett im Museum fängt an sich zu bewegen! Es muss die Anwesenheit seiner Artgenossen gewittert haben!\" Das knöcherne Biest bleckt seine monströsen Zähne und klappert auf Dich zu. Wie wirst Du in der Lage sein eine Kreatur zu besiegen, die bereits tot ist? Du wirst schnell zuschlagen müssen, bevor sie sich selbst heilt!",
- "questTRexUndeadCompletion": "Die glühenden Augen des Tyrannosaurus werden dunkel und trüb und er begibt sich zurück zu seinem Sockel. Alle atmen vor Erleichterung auf. \"Seht nur!\", sagt @Baconsaur, \"einige der Fossilisieneier sind plötzlich wie neu! Vielleicht werden sie ja für Dich ausschlüpfen.\"",
+ "questTRexUndeadCompletion": "Die glühenden Augen des Tyrannosaurus werden dunkel und trüb und er begibt sich zurück zu seinem Sockel. Alle atmen vor Erleichterung auf. \"Seht nur!\", sagt @Baconsaur, \"einige der versteinerten Eier sind plötzlich wie neu! Vielleicht werden sie ja für Dich ausschlüpfen.\"",
"questTRexUndeadBoss": "Skelettierter Tyrannosaurus",
"questTRexUndeadRageTitle": "Knöcherne Heilung",
"questTRexUndeadRageDescription": "Diese Leiste füllt sich, wenn Du Deine täglichen Aufgaben nicht erfüllst. Wenn sie voll ist, heilt sich der skelettierte Tyrannosaurus um 30% seiner übrigen Lebenspunkte.",
@@ -180,7 +180,7 @@
"questTRexDropTRexEgg": "Tyrannosaurus (Ei)",
"questTRexUnlockText": "Ermöglicht den Kauf von Tyrannosauruseier auf dem Marktplatz.",
"questRockText": "Entkomme dem Höhlenungetüm",
- "questRockNotes": "Beim Durchqueren des Habitica Mäandergebirges schlagen Deine Freunde und Du ein Lager in einer Höhle auf, welche mit funkelnden Kristallen übersät ist. Als Du jedoch am nächsten Morgen aufwachst ist der Eingang verschwunden und der Höhlenboden unter dir beginnt sich zu bewegen.
\"Der Berg lebt!\" schreit Dein Kamerad @pfeffernusse. \"Das sind keine Kristalle - das sind Zähne!\"
@Painter de Cluster ergreift Deine Hand. \"Wir müssen einen anderen Weg nach draußen finden. Bleib bei mir und lasse Dich nicht ablenken, sonst sind wir vielleicht für immer hier drinnen gefangen!\"",
+ "questRockNotes": "Beim Durchqueren des Habitica Mäandergebirges schlagen Deine Freunde und Du ein Lager in einer Höhle auf, welche mit funkelnden Kristallen übersät ist. Als Du jedoch am nächsten Morgen aufwachst ist der Eingang verschwunden und der Höhlenboden unter Dir beginnt sich zu bewegen.
\"Der Berg lebt!\" schreit Dein Kamerad @pfeffernusse. \"Das sind keine Kristalle - das sind Zähne!\"
@Painter de Cluster ergreift Deine Hand. \"Wir müssen einen anderen Weg nach draußen finden. Bleib bei mir und lasse Dich nicht ablenken, sonst sind wir vielleicht für immer hier drinnen gefangen!\"",
"questRockBoss": "Kristallkoloss",
"questRockCompletion": "Dank Deiner harten Arbeit konntest Du zu guter Letzt einen sicheren Weg durch den lebenden Berg finden. \nNach der langen Dunkelheit genießt Du die wärmenden Sonnenstrahlen, als Dich Dein Freund @intune auf ein Funkeln am Boden nahe der Höhle aufmerksam macht.\nDas Funkeln kommt von einem kleinen Stein, der von einer Goldader durchzogen ist. \nWährend Du ihn aufhebst siehst Du, dass um ihn herum weitere merkwürdig geformte Steine liegen. Sind das ... Eier?",
"questRockDropRockEgg": "Fels (Ei)",
@@ -195,8 +195,8 @@
"questSlimeNotes": "Wie immer arbeitest Du gut gelaunt an Deinen Aufgaben, als Du plötzlich bemerkst, wie Du Dich immer langsamer bewegst. \"Als würde man durch einen Sumpf wandern\", grummelt @Leephon, \"Nein, das fühlt sich eher so an als ob man durch Glibber watet!\" @starsystemic meint: \"Der schleimige Glibberkönig hat dieses Zeug über ganz Habitica verteilt. Es verstopft die Arbeitsschritte. Alles wird verlangsamt.\" Du siehst Dich um und bemerkst, dass die Straßen sich langsam mit durchsichtigem Glibber in allen Farben füllen und die Habiticaner daran hindert ihre Aufgaben zu erledigen. Im Gegensatz zu den meisten anderen, die die Flucht ergreifen, nimmst Du einen Mop zur Hand und machst Dich bereit für die Schlacht.",
"questSlimeBoss": "Glibberkönig",
"questSlimeCompletion": "Mit einem letzten Mopstoß stößt Du den Glibberkönig in die Falle, einen riesigen Donut, den @Overomega, @LordDarkly und @Shaner, die gewitzten Anführer der Feingebäck-Gilde, herangebracht haben. Anerkennend klopfen Dir die Habiticaner auf den Rücken, als Du fühlst, wie Dir jemand etwas in die Tasche rutschen lässt. Es ist die Belohnung für Deinen süßen Erfolg: drei Marshmallow Schleim Eier.",
- "questSlimeDropSlimeEgg": "Marshmallow Schleim (Ei)",
- "questSlimeUnlockText": "Ermöglicht den Kauf von Schleim Eier auf dem Marktplatz",
+ "questSlimeDropSlimeEgg": "Marshmallow-Schleim (Ei)",
+ "questSlimeUnlockText": "Ermöglicht den Kauf von Schleim-Eiern auf dem Marktplatz",
"questSheepText": "Der Donnerbock",
"questSheepNotes": "Als Du mit Deinen Freunden durch das ländliche Aufgabistan wanderst und eine \"kurze Pause\" von Deinen Verpflichtungen einlegst, findest Du einen gemütlichen Garnladen. Du bist so in Deine Aufgabenaufschieberei vertieft, dass Du die Unheil verkündenden Wolken am Horizont kaum bemerkst. \"Ich habe ein schlechtes Gefühl bei diesem Wetter\", murmelt @Misceo und Du schaust nach oben. Stürmischen Wolken brauen sich zusammen und sie sehen fast aus wie ... \"Wir haben keine Zeit, in die Wolken zu schauen\", ruft @starsystemic. \"Er greift an!\" Der Donnerbock rast los und schleudert Blitze direkt auf Dich zu!",
"questSheepBoss": "Donnerbock",
@@ -204,7 +204,7 @@
"questSheepDropSheepEgg": "Schaf (Ei)",
"questSheepUnlockText": "Ermöglicht den Kauf von Schafseiern auf dem Marktplatz",
"questKrakenText": "Der Kraken von Unfertik",
- "questKrakenNotes": "Als Du durch die Unfertige Bucht segelst, ist es ein warmer, sonniger Tag, aber Deine Gedanken sind voller Sorgen über alles was Du noch zu erledigen hast. Es scheint so, als ob sobald eine Aufgabe erledigt ist, eine andere auftaucht, und dann noch eine ...
Plötzlich wird das Boot von einem furchtbaren Ruck erschüttert, und schleimige Tentakeln schlängeln sich an allen Seiten aus dem Wasser! \"Der Kraken von Unfertik greift uns an!\" schreit Wolvenhalo.
\"Schnell!\" ruft dir Lemoness zu. \"Schlage so viele Tentakeln und Aufgaben nieder wie Du kannst, bevor neue auftauchen und ihren Platz einnehmen!\"",
+ "questKrakenNotes": "Als Du durch die Unfertige Bucht segelst, ist es ein warmer, sonniger Tag, aber Deine Gedanken sind voller Sorgen über alles was Du noch zu erledigen hast. Es scheint so, als ob sobald eine Aufgabe erledigt ist, eine andere auftaucht, und dann noch eine ...
Plötzlich wird das Boot von einem furchtbaren Ruck erschüttert, und schleimige Tentakeln schlängeln sich an allen Seiten aus dem Wasser! \"Der Kraken von Unfertik greift uns an!\" schreit Wolvenhalo.
\"Schnell!\" ruft Dir Lemoness zu. \"Schlage so viele Tentakeln und Aufgaben nieder wie Du kannst, bevor neue auftauchen und ihren Platz einnehmen!\"",
"questKrakenBoss": "Der Kraken von Unfertik",
"questKrakenCompletion": "Als der Kraken flieht, treiben mehrere Eier an die Wasseroberfläche. Lemoness untersucht sie zunächst argwöhnisch, dann ruft sie freudestrahlend \"Tintenfischeier! Hier, nimm sie als Belohnung für alles was Du erledigt hast!\"",
"questKrakenDropCuttlefishEgg": "Tintenfisch (Ei)",
@@ -212,7 +212,7 @@
"questWhaleText": "Jammern des Wals",
"questWhaleNotes": "Du kommst am Diligent Hafen an und hoffst, dass Du mit einem U-Boot zum Dilatory Derby fahren kannst. Plötzlich zwingt Dich ein ohrenbetäubendes Grölen dazu anzuhalten und Deine Ohren zuzuhalten. \"Wal in Sicht!\", schreit Kapitän @krazjega, während er auf einen riesigen, klagenden Wal deutet. \"Es ist nicht sicher U-Boote herauszuschicken während sie um sich schlägt!\"
\"Schnell\", ruft @UncommonCriminal. \"Hilf mir die arme Kreatur zu beruhigen, damit wir herausfinden können, wieso sie diesen ganzen Krach macht!\"",
"questWhaleBoss": "Jammernder Wal",
- "questWhaleCompletion": "Nach der ganzen harten Arbeit verstummt das donnernde Jammern des Wales. \"Scheint als wäre sie in der Flut schlechter Angewohnheiten fast ertrunken\", erklärt @zoebeagle, \"Dank Deiner ununterbrochenen Bemühungen konnten wir die Fluten abwenden!\" Als Du in das U-Boot steigen willst fallen dir in der ruhigen See einige Eier auf.",
+ "questWhaleCompletion": "Nach der ganzen harten Arbeit verstummt das donnernde Jammern des Wales. \"Scheint als wäre sie in der Flut schlechter Angewohnheiten fast ertrunken\", erklärt @zoebeagle, \"Dank Deiner ununterbrochenen Bemühungen konnten wir die Fluten abwenden!\" Als Du in das U-Boot steigen willst fallen Dir in der ruhigen See einige Eier auf.",
"questWhaleDropWhaleEgg": "Wal (Ei)",
"questWhaleUnlockText": "Ermöglicht den Kauf von Waleiern auf dem Marktplatz",
"questDilatoryDistress1Text": "Dilatory in Gefahr, Teil 1: Flaschenpost",
@@ -222,7 +222,7 @@
"questDilatoryDistress1CollectBlueFins": "Blaue Schwanzflossen",
"questDilatoryDistress1DropArmor": "Flossenbesetztes Ozeangewand (Rüstung)",
"questDilatoryDistress2Text": "Dilatory in Gefahr, Teil 2: Kreaturen der Gletscherspalte",
- "questDilatoryDistress2Notes": "Die Belagerung kann meilenweit entfernt gesehen werden: tausende körperlose Schädel drängen sich durch ein Portal zwischen den Wänden der Spalte und streben Dilatory entgegen.
Du triffst König Manta mit eingefallenen Augen und besorgtem Gesicht in seinem Kriegszimmer. \"Meine Tochter Adva verschwand in der dunklen Meeresspalte kurz bevor die Belagerung begann. Bitte finde sie und bringe sie sicher wieder zurück! Ich werde dir mein Feuerkorallendiadem leihen, um dir zu helfen. Wenn Du erfolgreich bist, ist es deins.\"",
+ "questDilatoryDistress2Notes": "Die Belagerung kann meilenweit entfernt gesehen werden: tausende körperlose Schädel drängen sich durch ein Portal zwischen den Wänden der Spalte und streben Dilatory entgegen.
Du triffst König Manta mit eingefallenen Augen und besorgtem Gesicht in seinem Kriegszimmer. \"Meine Tochter Adva verschwand in der dunklen Meeresspalte kurz bevor die Belagerung begann. Bitte finde sie und bringe sie sicher wieder zurück! Ich werde Dir mein Feuerkorallendiadem leihen, um Dir zu helfen. Wenn Du erfolgreich bist, ist es deins.\"",
"questDilatoryDistress2Completion": "Du bezwingst die albtraumhalfte Totenschädelhorde, aber Du hast nicht das Gefühl näher dran zu sein Adva zu finden. Du fragst @Kiwibot, den königlichen Fährtensucher, ob sie irgendeine Idee hat. \"Die Fangschreckenkrebse, welche die Stadt verteidigen, sollten Advas Flucht gesehen haben\", antwortet @Kiwibot. \"Versuche ihnen in die dunkle Meeresspalte zu folgen.\"",
"questDilatoryDistress2Boss": "Wasserschädel Schwarm",
"questDilatoryDistress2RageTitle": "Schwarmnachwuchs",
@@ -233,7 +233,7 @@
"questDilatoryDistress2DropHeadgear": "Feuerkorallendiadem (Kopfbedeckung)",
"questDilatoryDistress3Text": "Dilatory in Gefahr, Teil 3: Nicht nur ein Dienstmädchen",
"questDilatoryDistress3Notes": "Du folgst den Fangschreckenkrebsen tief in die Meeresspalte und entdeckst eine Unterwasserfestung. Prinzessin Adva, von weiteren Wasserschädeln begleitet, erwartet Dich in der Haupthalle. \"Mein Vater hat euch gesandt, oder? Berichtet ihm, dass ich es ablehne zurückzukehren. Ich bin damit zufrieden hier zu bleiben und Zauberei zu betreiben. Verschwinde jetzt oder Du wirst den Zorn der neuen Meereskönigin spüren!\" Adva scheint unnachgiebig zu sein, aber während sie spricht bemerkst Du einen seltsamen, bedrohlich glühenden Rubinanhänger an ihrem Hals ... Vielleicht würden ihre Wahnvorstellungen aufhören, wenn Du ihn zerbrichst?",
- "questDilatoryDistress3Completion": "Schließlich gelingt es dir, den verhexten Anhänger von Advas Hals zu nehmen und Du wirfst ihn weg. Adva fasst sich an den Kopf: \"Wo bin ich? Was ist hier passiert?\" Nachdem sie Deine Geschichte gehört hat, runzelt sie ihre Stirn: \"Dieses Amulett wurde mir von einer seltsamen Botschafterin überreicht - Eine Dame namens 'Tzina'. Danach erinnere ich mich an nichts mehr!\"
Zurück in Dilatory ist Manta überglücklich über Deinen Erfolg. \"Erlaube mir, Dich mit diesem Dreizack und diesem Schild zu belohnen! Ich habe sie bei @aiseant und @starsystemic als Geschenk für Adva anfertigen lassen, aber ... Ich möchte in nächster Zeit lieber keine Waffen mehr in ihre Hände geben.",
+ "questDilatoryDistress3Completion": "Schließlich gelingt es Dir, den verhexten Anhänger von Advas Hals zu nehmen und Du wirfst ihn weg. Adva fasst sich an den Kopf: \"Wo bin ich? Was ist hier passiert?\" Nachdem sie Deine Geschichte gehört hat, runzelt sie ihre Stirn: \"Dieses Amulett wurde mir von einer seltsamen Botschafterin überreicht - Eine Dame namens 'Tzina'. Danach erinnere ich mich an nichts mehr!\"
Zurück in Dilatory ist Manta überglücklich über Deinen Erfolg. \"Erlaube mir, Dich mit diesem Dreizack und diesem Schild zu belohnen! Ich habe sie bei @aiseant und @starsystemic als Geschenk für Adva anfertigen lassen, aber ... Ich möchte in nächster Zeit lieber keine Waffen mehr in ihre Hände geben.",
"questDilatoryDistress3Boss": "Adva, die putschende Meerjungfrau",
"questDilatoryDistress3DropFish": "Fisch (Futter)",
"questDilatoryDistress3DropWeapon": "Dreizack der zerschmetternden Gezeiten (Waffe)",
@@ -261,7 +261,7 @@
"questBurnoutDropPhoenixMount": "Phönix (Reittier)",
"questBurnoutBossRageQuests": "`Burnout benutzt ERSCHÖPFUNGSSCHLAG!`\n\nOh nein! Trotz unseren besten Bemühungen haben wir einige tägliche Aufgaben nicht ablaufen lassen und nun ist Burnout voller Energie entflammt! Mit einen knisterenden Knurren umhüllt es Ian den Quest Herr in einen Welle von Spektralflammen. Während die gefallende Questrollen qualmen, klärt sich der Rauch und Du siehst, dass Ian seiner Energie aufgezehrt wurde und sich in einen herumtreibenden Erschöpfungsgeist verwandelte.\n\nNur die Bezwingung von Burnout kann den Zauber brechen und unseren geliebten Questmeister wiederherstellen. Lasst uns unsere täglichen Aufgaben abchecken und dieses Monster besiegen bevor es nochmals angreift!",
"questBurnoutBossRageSeasonalShop": "Burnout benutzt ERSCHÖPFUNGSSCHLAG!\n\nAhh!!! Unsere unerledigten, täglichen Aufgaben haben die Flammen von Burnout genährt und nun hat es genug Energie, um nochmal anzugreifen! Es lässt ein Meer von Spektralflammen los, welches den seasonalen Shop verbrennt. Du bist erschrocken zu sehen, dass die heitere Jahreszeitenzauberin in einen schlaffen Erschöpfungsgeist transformiert wurde.\n\nWir müssen die NPCs retten! Schnell, Habiticaner, erledige Deine Aufgaben und besiege Burnout bevor es ein drittes Mal zuschlägt!",
- "questBurnoutBossRageTavern": "`Burnout benutzt ERSCHÖPFUNGSSCHLAG!`\n\nVIele Habiticaner haben sich vor Burnout im Gasthaus versteckt, aber nicht mehr lange! Mit einen kreischenden Schrei harkt Burnout das Gasthaus mit seinen weiß glühenden Händen. Während der Gasthaus-Schutzherr flieht, wurde Daniel von Burnouts Klauen gegriffen und transformiert sich direkt vor dir in einen Erschöpfungsgeist!.\n\nDieser unbeherrschte Horror ging schon viel zu lange. Gib nicht auf ... wir sind so nah dran Burnout ein für alle Mal zu bezwingen!",
+ "questBurnoutBossRageTavern": "`Burnout benutzt ERSCHÖPFUNGSSCHLAG!`\n\nVIele Habiticaner haben sich vor Burnout im Gasthaus versteckt, aber nicht mehr lange! Mit einen kreischenden Schrei harkt Burnout das Gasthaus mit seinen weiß glühenden Händen. Während der Gasthaus-Schutzherr flieht, wurde Daniel von Burnouts Klauen gegriffen und transformiert sich direkt vor Dir in einen Erschöpfungsgeist!.\n\nDieser unbeherrschte Horror ging schon viel zu lange. Gib nicht auf ... wir sind so nah dran Burnout ein für alle Mal zu bezwingen!",
"questFrogText": "Sumpf des Chaos-Froschs",
"questFrogNotes": "Als Du Dich mit Deinen Freunden durch den Sumpf des Stillstands schlägst, deutet @starsystemic auf ein großes Schild: \"Auf dem Weg bleiben -- wenn möglich.\"
\"Das ist sicher nicht schwer\" sagt @RosemonkeyCT. \"Er ist breit und frei.\"
Aber als Ihr weitergeht, bemerkst Du, dass der Weg langsam immer mehr vom Moor des Sumpfs eingenommen wird, übersät mit Stücken blauen Gerölls und Müll. Irgendwann ist es unmöglich voranzukommen.
Als Du Dich umsiehst und Dich fragst, wie es hier so verschmutzt werden konnte, ruft @Jon Arjinborn: \"Passt auf!\" Ein wütender Frosch springt aus dem Schlamm hervor, bekleidet mit dreckigen Lumpen und von blauem Feuer entfacht. Du musst diesen giftigen Chaos-Frosch überwältigen um weiterzukommen!",
"questFrogCompletion": "Der Frosch kauert sich zurück ins Moor, besiegt. Als er sich langsam davonschleicht, löst sich der blaue Schleim auf und gibt den Weg vor euch frei.
In der Mitte des Weges befinden sich drei makellose Eier. \"Man kann sogar die winzigen Kaulquappen durch die klare Schale hindurch erkennen!\" sagt @Breadstrings. \"Hier, Du solltest sie nehmen.\"",
@@ -292,21 +292,33 @@
"questMonkeyBoss": "Monströser Mandrill",
"questMonkeyDropMonkeyEgg": "Affe (Ei)",
"questMonkeyUnlockText": "Ermöglicht den Kauf von Affeneiern auf dem Marktplatz",
- "questSnailText": "Der Schneckerich der schlammigen Schinderei",
- "questSnailNotes": "Du freust Dich, Deinen Quest in den verlassenen Schinderverliesen zu beginnen, aber kaum, dass Du die Verliese betrittst, fühlst Du wie der Grund unter dir an Deinen Stiefeln zu saugen beginnt. Du betrachtest den Pfad vor dir und siehst in Schleim versunkene Habiticaner. @Overomega brüllt, \"Sie haben zu viele unwichtige Aufgaben, und sie bleiben an Dingen hängen, die nicht wichtig sind! Zieh sie raus!
\"Du musst die Quelle des Schlamms finden,\" stimmt @Pfeffernusse zu, \"oder die nicht erfüllbaren Aufgaben ziehen sie ewig hinunter!\"
Deine Waffe ziehend watest Du durch den zählen Schlamm ... und triffst auf den fürchterlichen Schneckerich der schlammigen Schinderei.",
+ "questSnailText": "Die Schnecke der Schlamm-Schinderei",
+ "questSnailNotes": "Du freust Dich, Deinen Quest in den verlassenen Schinderverliesen zu beginnen, aber kaum, dass Du die Verliese betrittst, fühlst Du wie der Grund unter Dir an Deinen Stiefeln zu saugen beginnt. Du betrachtest den Pfad vor Dir und siehst in Schleim versunkene Habiticaner. @Overomega brüllt, \"Sie haben zu viele unwichtige Aufgaben, und sie bleiben an Dingen hängen, die nicht wichtig sind! Zieh sie raus!
\"Du musst die Quelle des Schlamms finden,\" stimmt @Pfeffernusse zu, \"oder die nicht erfüllbaren Aufgaben ziehen sie ewig hinunter!\"
Deine Waffe ziehend watest Du durch den zählen Schlamm ... und triffst auf die fürchterliche Schnecke der Schlamm-Schinderei.",
"questSnailCompletion": "Du lässt Deine Waffe auf den Schneckenpanzer niederfahren, wobei dieser in zwei Teile zerbricht und eine Flutwelle Wasser freigibt. Der Schleim wird weggespült und die Habiticaner um Dich herum jubeln. \"Sieh!\" sagt @Misceo. \"Dort liegen einige Schneckeneier in den Überresten des Unrats.\"",
- "questSnailBoss": "Schneckerich der schlammigen Schinderei",
+ "questSnailBoss": "Schnecke der Schlamm-Schinderei",
"questSnailDropSnailEgg": "Schnecke (Ei)",
"questSnailUnlockText": "Ermöglicht den Kauf von Schneckeneiern auf dem Marktplatz",
- "questBewilderText": "The Be-Wilder",
- "questBewilderNotes": "The party begins like any other.
The appetizers are excellent, the music is swinging, and even the dancing elephants have become routine. Habiticans laugh and frolic amid the overflowing floral centerpieces, happy to have a distraction from their least-favorite tasks, and the April Fool whirls among them, eagerly providing an amusing trick here and a witty twist there.
As the Mistiflying clock tower strikes midnight, the April Fool leaps onto the stage to give a speech.
“Friends! Enemies! Tolerant acquaintances! Lend me your ears.” The crowd chuckles as animal ears sprout from their heads, and they pose with their new accessories.
“As you know,” the Fool continues, “my confusing illusions usually only last a single day. But I’m pleased to announce that I’ve discovered a shortcut that will guarantee us non-stop fun, without having to deal with the pesky weight of our responsibilities. Charming Habiticans, meet my magical new friend... the Be-Wilder!”
Lemoness pales suddenly, dropping her hors d'oeuvres. “Wait! Don’t trust--”
But suddenly mists are pouring into the room, glittering and thick, and they swirl around the April Fool, coalescing into cloudy feathers and a stretching neck. The crowd is speechless as an monstrous bird unfolds before them, its wings shimmering with illusions. It lets out a horrible screeching laugh.
“Oh, it has been ages since a Habitican has been foolish enough to summon me! How wonderful it feels, to have a tangible form at last.”
Buzzing in terror, the magic bees of Mistiflying flee the floating city, which sags from the sky. One by one, the brilliant spring flowers wither up and wisp away.
“My dearest friends, why so alarmed?” crows the Be-Wilder, beating its wings. “There’s no need to toil for your rewards any more. I’ll just give you all the things that you desire!”
A rain of coins pours from the sky, hammering into the ground with brutal force, and the crowd screams and flees for cover. “Is this a joke?” Baconsaur shouts, as the gold smashes through windows and shatters roof shingles.
PainterProphet ducks as lightning bolts crackle overhead, and fog blots out the sun. “No! This time, I don’t think it is!”
Quickly, Habiticans, don’t let this World Boss distract us from our goals! Stay focused on the tasks that you need to complete so we can rescue Mistiflying -- and hopefully, ourselves.",
- "questBewilderCompletion": "The Be-Wilder is DEFEATED!
We've done it! The Be-Wilder lets out a ululating cry as it twists in the air, shedding feathers like falling rain. Slowly, gradually, it coils into a cloud of sparkling mist. As the newly-revealed sun pierces the fog, it burns away, revealing the coughing, mercifully human forms of Bailey, Matt, Alex.... and the April Fool himself.
Mistiflying is saved!
The April Fool has enough shame to look a bit sheepish. “Oh, hm,” he says. “Perhaps I got a little…. carried away.”
The crowd mutters. Sodden flowers wash up on sidewalks. Somewhere in the distance, a roof collapses with a spectacular splash.
“Er, yes,” the April Fool says. “That is. What I meant to say was, I’m dreadfully sorry.” He heaves a sigh. “I suppose it can’t all be fun and games, after all. It might not hurt to focus occasionally. Maybe I’ll get a head start on next year’s pranking.”
Redphoenix coughs meaningfully.
“I mean, get a head start on this year’s spring cleaning!” the April Fool says. “Nothing to fear, I’ll have Habit City in spit-shape soon. Luckily nobody is better than I at dual-wielding mops.”
Encouraged, the marching band starts up.
It isn’t long before all is back to normal in Habit City. Plus, now that the Be-Wilder has evaporated, the magical bees of Mistiflying bustle back to work, and soon the flowers are blooming and the city is floating once more.
As Habiticans cuddle the magical fuzzy bees, the April Fool’s eyes light up. “Oho, I’ve had a thought! Why don’t you all keep some of these fuzzy Bee Pets and Mounts? It’s a gift that perfectly symbolizes the balance between hard work and sweet rewards, if I’m going to get all boring and allegorical on you.” He winks. “Besides, they don’t have stingers! Fool’s honor.”",
- "questBewilderCompletionChat": "`The Be-Wilder is DEFEATED!`\n\nWe've done it! The Be-Wilder lets out a ululating cry as it twists in the air, shedding feathers like falling rain. Slowly, gradually, it coils into a cloud of sparkling mist. As the newly-revealed sun pierces the fog, it burns away, revealing the coughing, mercifully human forms of Bailey, Matt, Alex.... and the April Fool himself.\n\n`Mistiflying is saved!`\n\nThe April Fool has enough shame to look a bit sheepish. “Oh, hm,” he says. “Perhaps I got a little…. carried away.”\n\nThe crowd mutters. Sodden flowers wash up on sidewalks. Somewhere in the distance, a roof collapses with a spectacular splash.\n\n“Er, yes,” the April Fool says. “That is. What I meant to say was, I’m dreadfully sorry.” He heaves a sigh. “I suppose it can’t all be fun and games, after all. It might not hurt to focus occasionally. Maybe I’ll get a head start on next year’s pranking.”\n\nRedphoenix coughs meaningfully.\n\n“I mean, get a head start on this year’s spring cleaning!” the April Fool says. “Nothing to fear, I’ll have Habit City in spit-shape soon. Luckily nobody is better than I at dual-wielding mops.”\n\nEncouraged, the marching band starts up.\n\nIt isn’t long before all is back to normal in Habit City. Plus, now that the Be-Wilder has evaporated, the magical bees of Mistiflying bustle back to work, and soon the flowers are blooming and the city is floating once more.\n\nAs Habiticans cuddle the magical fuzzy bees, the April Fool’s eyes light up. “Oho, I’ve had a thought! Why don’t you all keep some of these fuzzy Bee Pets and Mounts? It’s a gift that perfectly symbolizes the balance between hard work and sweet rewards, if I’m going to get all boring and allegorical on you.” He winks. “Besides, they don’t have stingers! Fool’s honor.”",
- "questBewilderBossRageTitle": "Beguilement Strike",
- "questBewilderBossRageDescription": "When this gauge fills, The Be-Wilder will unleash its Beguilement Strike on Habitica!",
- "questBewilderDropBumblebeePet": "Bumblebee (Pet)",
- "questBewilderDropBumblebeeMount": "Bumblebee (Mount)",
- "questBewilderBossRageMarket": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nOh no! Despite our best efforts, we've gotten distracted by the Be-Wilder’s charming illusions and have forgotten to do some of our Dailies! With a cackling cry, the shining bird beats its wings, raising a swarm of mist around Alex the Merchant. When the fog clears, he has been possessed! “Have some free samples!” he shouts gleefully, and begins to hurl exploding eggs and potions at fleeing Habiticans. Not the most favorable of sales, to be sure.\n\nHurry! Let's stay focused on our Dailies to defeat this monster before it possesses someone else.",
- "questBewilderBossRageStables": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nAhh!!! Once again the Be-Wilder has dazzled us into neglecting our Dailies, and now it has attacked Matt the Beast Master! With a swirl of mist, Matt transforms into a terrifying winged creature, and all the pets and mounts howl sadly in their stables. Quickly, stay focused on your tasks to defeat this dastardly distraction!",
- "questBewilderBossRageBailey": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nLook out! In the middle of reporting the news, Bailey the Town Crier has been possessed by the Be-Wilder! She lets out an evil, uninformative screech as she rises into the air. Now how will we know what’s going on?\n\nDon't give up... we're so close to defeating this bothersome bird for once and for all!"
+ "questBewilderText": "Der Verwirrer",
+ "questBewilderNotes": "Die Party beginnt wie jede Andere.
Die Appettithäppchen sind exzellent, die Musik sorgt für lockere Stimmung und sogar an die tanzenden Elefanten hat man sich gewöhnt. Habiticaner lachen ausgelassen zwischen den ausladenden Blumengestecken, froh um eine Ablenkung von Ihren meist-gehassten Aufgaben, und der April-Scherzkeks wirbelt zwischen ihnen herum, eifrig Scherze und Schabernack treibend.
Als die Uhr des Mistiflying Towers Mitternacht schlägt, springt der April-Scherzkeks auf die Bühne um eine Rede zu halten.
\"Freunde! Feinde! Tolerante Bekannte! Leiht mir euer Ohr!\" Als aus ihren Köpfen Tierohren spriessen, kichert die Schar und posiert mit ihren neuen Verkleidungen.
\"Wie ihr alle wisst,\" fährt der April-Scherzkeks weiter, \"dauern meine verwirrenden Illusionen normalerweise nur einen Tag. Deshalb freue ich mich besonders euch mitzuteilen, dass ich eine Abkürzung gefunden habe, die uns Spaß ohne Ende verspricht, ohne dass wir uns um die mühsame Last unserer Pflichten sorgen müssen. Liebe Habiticaner, ich stelle euch hiermit meinen neuen, magischen Freund vor: den Verwirrer!\"
Lemoness wird plötzlich bleich und lässt ihre Häppchen fallen. \"Wartet! Traut ihm ni--\"
Doch schon stürzen Nebelschwaden in den Saal, dick und glitzernd. Sie wirbeln um den April-Scherzkeks und vereinen sich zu verschwommenen Federn und einem langen Hals. The Menge ist sprachlos, während sich vor ihr ein monströser Vogel entfaltet, seine Flügel schimmernd vor lauter Illusionen. Er gibt ein fürchterliches, kreischendes Lachen von sich.
\"Oh, es ist schon Ewigkeiten her, seit ein Habiticaner töricht genug war, mich herbeizurufen! Wie wundervoll es ist, endlich eine greifbare Form zu haben!\"
Erschreckt summend fliehen die magischen Bienen von Mistiflying aus der fliegenden Stadt, die aus dem Himmel sackt. Eine nach der anderen verwelken die leuchtenden Frühlingsblumen und schrumpfen weg.
\"Meine liebsten Freunde, warum so beunruhigt?\" krächzt der Verwirrer, während er mit den Flügeln schlägt. \"Es gibt keinen Grund mehr, euch für eure Belohnungen abzurackern. Ich werde euch alles geben, was ihr euch wünscht!\"
Ein Münzenschauer stürzt aus dem Himmel und hämmert sich mit brutaler Kraft in den Boden. Die Menge schreit und sucht Deckung. \"Soll das ein Scherz sein?\" ruft Baconsaur, während das Gold Fenster einschlägt und die Schindeln auf den Dächern zerschmettert.
PainterProphet duckt sich, als Blitze über den Himmel schießen und Nebel die Sonne verdunkelt. \"Nein! Diesmal ist es glaube ich kein Scherz!\"
Schnell, Habiticaner, lasst diesen Welt-Bösewicht euch nicht von euren Zielen ablenken! Konzentriert euch auf das, was ihr zu erledigen habt, um Mistiflying zu retten -- und hoffentlich euch selbst.",
+ "questBewilderCompletion": "Der Verwirrer ist BESIEGT!
Wir haben es geschafft! Der Verwirrer stößt einen heulenden Schrei aus, windet sich in der Luft und verliert büschelweise Federn. Langsam, nach und nach wickelt er sich zu einer funkelnden Nebelwoke auf. Die enthüllte Sonne durchdingt den Nebel, vertreibt ihn und enthüllt die hustenden, glücklicherweise menschlichen Formen von Matt, Bailey, Alex ... und dem April-Scherzkeks persönlich.
Mistiflying ist gerettet!
Der April-Scherzkeks schämt sich immerhin genug, um etwas verlegen dazustehen. \"Oh, ähm,\" sagt er. \"Vielleicht habe ich mich ein wenig ... gehen lassen.\"”
Die Menschenmenge murrt. Durchnässte Blumen werden auf Gehsteigen angeschwemmt. Irgendwo in der Ferne stürzt ein Dach mit spektakulärem Krachen ein.
\"Ähm, ja,\" sagt der April-Scherzkeks. \"Also, was ich sagen wollte, es tut mir schrecklich leid.\" Er seufzt schwer. \"Ich vermute mal, es kann doch nicht nur Spaß und Spiel geben. Es kann wohl nicht schaden, sich ab und zu mal auf etwas zu konzentrieren. Vielleicht kann ich ja schon mal den nächsten Aprilscherz vorbereiten.\"
Redphoenix räuspert sich vielsagend.
\"Ich meine, den nächsten Frühjahrsputz!\" korrigiert sich der April-Scherzkeks. \"Habt keine Angst, ich werde Habit City rasch wieder blitzblank haben. Niemand ist besser als ich mit dem Doppelmopp.\"
Ermuntert beginnt die Kapelle zu spielen.
Es dauert nicht lange bis alles in Habit City wieder seinen normalen Lauf nimmt. Außerdem, jetzt wo der Verwirrer pulverisiert ist, sind die magischen Bienen von Mistiflying wieder eifrig am Werk und schon bald blühen die Blumen und die Stadt schwebt von Neuem.
Wie die Habiticaner die magischen, flaumigen Bienen knuddeln, beginnen die Augen des April-Scherzkeks zu leuchten. \"Oho, mir kommt eine Idee! Warum haltet ihr euch eigentlich keine dieser flaumigen Bienen als Haustiere und Reittiere? Das ist ein Geschenk, das das Gleichgewicht zwischen harter Arbeit und süßer Belohnung symbolisiert, wenn ich das mal so langweilig und sinnbildlich sagen darf.\" Er zwinkert. \"Und außerdem haben sie keinen Stachel! Narrenehrenwort.\"",
+ "questBewilderCompletionChat": "`Der Verwirrer is BESIEGT!`\n\nWir haben es geschafft! Der Verwirrer stößt einen heulenden Schrei aus, windet sich in der Luft und verliert büschelweise Federn. Langsam, nach und nach wickelt er sich zu einer funkelnden Nebelwoke auf. Die enthüllte Sonne durchdingt den Nebel, vertreibt ihn und enthüllt die hustenden, glücklicherweise menschlichen Formen von Matt, Bailey, Alex ... und dem April-Scherzkeks persönlich.\n\n`Mistiflying ist gerettet!`\n\nDer April-Scherzkeks schämt sich immerhin genug, um etwas verlegen dazustehen. \"Oh, ähm,\" sagt er. \"Vielleicht habe ich mich ein wenig ... gehen lassen.\"\n\nDie Menschenmenge murrt. Durchnässte Blumen werden auf Gehsteigen angeschwemmt. Irgendwo in der Ferne stürzt ein Dach mit spektakulärem Krachen ein.\n\n\"Ähm, ja,\" sagt der April-Scherzkeks. \"Also, was ich sagen wollte, es tut mir schrecklich leid.\" Er seufzt schwer. \"Ich vermute mal, es kann doch nicht nur Spaß und Spiel geben. Es kann wohl nicht schaden, sich ab und zu mal auf etwas zu konzentrieren. Vielleicht kann ich ja schon mal den nächsten Aprilscherz vorbereiten.\"\n\nRedphoenix räuspert sich vielsagend.\n\n\"Ich meine, den nächsten Frühjahrsputz!\" korrigiert sich der April-Scherzkeks. \"Habt keine Angst, ich werde Habit City rasch wieder blitzblank haben. Niemand ist besser als ich mit dem Doppelmopp.\"\n\nErmuntert beginnt die Kapelle zu spielen\n\nEs dauert nicht lange bis alles in Habit City wieder seinen normalen Lauf nimmt. Außerdem, jetzt wo der Verwirrer pulverisiert ist, sind die magischen Bienen von Mistiflying wieder eifrig am Werk und schon bald blühen die Blumen und schwebt die Stadt von Neuem.\n\nWie die Habiticaner die magischen, flaumigen Bienen knuddeln, beginnen die Augen des April-Scherzkeks zu leuchten. \"Oho, mir kommt eine Idee! Warum haltet ihr euch eigentlich keine dieser flaumigen Bienen als Haustiere und Reittiere? Das ist ein Geschenk, das das Gleichgewicht zwischen harter Arbeit und süßer Belohnung symbolisiert, wenn ich das mal so langweilig und sinnbildlich sagen darf.\" Er zwinkert. \"Und außerdem haben sie keinen Stachel! Narrenehrenwort.\"",
+ "questBewilderBossRageTitle": "Betörungsschlag",
+ "questBewilderBossRageDescription": "Wenn dieser Balken voll ist, wird der Verwirrer seinen Wirrschlag über Habitica entfesseln!",
+ "questBewilderDropBumblebeePet": "Magische Biene (Haustier)",
+ "questBewilderDropBumblebeeMount": "Magische Biene (Reittier)",
+ "questBewilderBossRageMarket": "`Der Verwirrer benutzt BETÖRUNGSSCHLAG!`\n\nOh nein! Trotz unseren höchsten Anstrengungen wurden wir durch des Verwirrers Illusionen abgelenkt und haben unsere täglichen Aufgaben vergessen! Mit schnatterndem Geschrei schlägt der schillernde Vogel mit seinen Flügeln und wirbelt eine Nebelschwade um Alex den Händler auf. Wie sich der Nebel verzogen hat, ist Alex besessen! \"Hier sind ein paar Gratismuster!\" ruft er schadenfroh und beginnt, explodierende Eier und Tränke auf die fliehenden Habiticaner zu werfen. Nicht unbedingt das, was man als vorteilhaften Ausverkauf bezeichnen würde.\n\nSchnell! Konzentrieren wir uns auf unsere täglichen Aufgaben um dieses Monster zu besiegen, bevor es noch jemanden in seinen Bann zieht.",
+ "questBewilderBossRageStables": "`Der Verwirrer benutzt den Betörungsschlag!`\n\nAhh!! Der Verwirrer hat uns erneut geblendet und uns die tägliche Aufgaben vernachlässigen lassen. Jetzt hat er Matt den Bestienmeister angegriffen! Unter einem Nebelschleier verwandelt sich Matt in ein furchteinflößendes, geflügeltes Wesen und alle Haus- und Reittiere heulen traurig in ihren Ställen auf. Rasch, bleibt auf eure Aufgaben fokussiert, um diese niederträchtige Ablenkung zu besiegen!",
+ "questBewilderBossRageBailey": "`Der Verwirrer benutzt den Betörungsschlag!`\n\nPass auf! Mitten in seinem Nachrichten-Vortrag wurde Bailey die Marktschreierin vom Verwirrer besessen! Sie stößt einen bösartigen, nicht im geringsten informativen Schrei aus, während sie sich in die Lüfte erhebt. Wie werden wir nun über Neuigkeiten informiert werden?\n\nGib nicht auf ... wir sind nah dran, diesen lästigen Vogel ein für alle mal zu besiegen!",
+ "questFalconText": "Die Zeitraubvögel",
+ "questFalconNotes": "Der Mount Habitica wird von einem aufragenden Berg von To-Dos überschattet. Das war mal ein Picknick-Platz, an dem man ein Gefühl von Vollendung genießen konnte. Aber nun sind die vernachlässigten Aufgaben außer Kontrolle geraten und die furchteinflößenden Zeitraubvögel, die Habiticaner davon abhalten ihre Aufgaben zu erledigen, haben sich hier eingenistet.
\"Das ist zu schwierig!\" krächzen sie zu @JonArinbjorn und @Onheiron. \"Das braucht jetzt gerade zu lange! Das kann auch noch bis morgen warten! Warum hast Du jetzt nicht lieber ein bisschen Spaß?\"
Nie mehr, schwörst Du Dir. Du wirst Deinen persönlichen To-Do-Berg besteigen und die Zeitraubvögel besiegen!",
+ "questFalconCompletion": "Jetzt da Du die Zeitraubvögel endlich besiegt hast, setzt Du Dich hin um die Aussicht und Deine wohlverdiente Ruhepause zu genießen.
\"Wow!\" sagt @Trogdorina. \"Du hast gewonnen!\"
@Squish fügt hinzu: \"Hier, nimm diese Eier, die ich gefunden habe, als Belohnung.\"",
+ "questFalconBoss": "Zeitraubvögel",
+ "questFalconDropFalconEgg": "Falke (Ei)",
+ "questFalconUnlockText": "Ermöglicht den Kauf von Falkeneiern auf dem Marktplatz.",
+ "questTreelingText": "Das Baumgewirr",
+ "questTreelingNotes": "Der alljährliche Gartenwettbewerb finder statt, und jeder redet über das geheimnisvolle Projekt, das @aurakami zu enthüllen verspochen hat. Du mischst Dich am Tag der Kundgebung unter die Menge und staunst über die Vorstellung eines sich bewegenden Baumes. @fuzzytrees erklärt, dass der Baum bei der Gartenpflege hilft, und demonstriert wie er den Rasen mäht, die Hecken stutzt und die Rosen schneidet, alles zu selben Zeit - bis der Baum plötzlich außer Kontrolle gerät und seine Rebschere gegen ihren Schöpfer richtet! Die Menge gerät in Panik, als alle zu fliehen versuchen, nicht aber Du - Du springst vorwärts, bereit Dich in den Kampf zu stürzen.",
+ "questTreelingCompletion": "Du machst Dich sauber, als die letzten paar Blätter auf den Boden fallen. Trotz des Ärgers ist der Gartenwettbewerb nun sicher - obwohl der Baum, den Du eben auf ein paar Holzspäne zurückgestutzt hast, wohl kaum noch einen Preis erlangen mag! \"Hier müssen wohl noch ein paar Kleinigkeiten verbessert werden\", meint @PainterProphet. \"Vielleicht gelingt es ja jemand anders besser die jungen Bäume zu trainieren. Hast Du Lust darauf?\"",
+ "questTreelingBoss": "Baumgewirr",
+ "questTreelingDropTreelingEgg": "Bäumlein (Ei)",
+ "questTreelingUnlockText": "Ermöglicht den Kauf von Bäumlingeiern auf dem Marktplatz"
}
\ No newline at end of file
diff --git a/common/locales/de/rebirth.json b/common/locales/de/rebirth.json
index 349d265829..25dfc98d4e 100644
--- a/common/locales/de/rebirth.json
+++ b/common/locales/de/rebirth.json
@@ -5,14 +5,14 @@
"rebirthStartOver": "Wiedergeburt setzt Deinen Charakter auf Level 1 zurück.",
"rebirthAdvList1": "Du erhältst volle Lebenspunkte.",
"rebirthAdvList2": "Du hast weder Erfahrung noch Gold oder Ausrüstung (mit Ausnahme von kostenlosen Gegenständen, wie zum Beispiel den geheimnisvollen Gegenständen).",
- "rebirthAdvList3": "Deine Gewohnheiten, täglichen Aufgaben und Aufgaben werden auf Gelb gesetzt und Strähnen starten von vorn.",
+ "rebirthAdvList3": "Deine Gewohnheiten, täglichen Aufgaben und To-Dos werden wieder gelb und alle Strähnen zurückgesetzt, ausgenommen Wettbewerbs-Aufgaben.",
"rebirthAdvList4": "Du hast die Anfangsklasse Krieger bis Du eine neue Klasse freigeschaltet hast.",
"rebirthInherit": "Dein neuer Charakter erbt ein paar Dinge von seinem Vorgänger:",
"rebirthInList1": "Aufgaben, Verlauf und Einstellungen bleiben bestehen.",
"rebirthInList2": "Wettbewerb-, Gilden- und Gruppenmitgliedschaften bleiben bestehen.",
"rebirthInList3": "Edelsteine, Träger-Stufen, Mitwirkender-Level bleiben bestehen.",
- "rebirthInList4": "Gegenstände die mit Edelsteinen oder zufällig gefunden wurden bleiben bestehen (wie z.B. Haus- und Reittiere), allerdings kannst Du sie erst anwählen, wenn das Feature wieder freigeschaltet ist.",
- "rebirthInList5": "Gekaufte Limited Edition Ausrüstung kann zurückgegeben werden, auch wenn das Event bereits beendet ist. Um Klassen-spezifische Ausrüstung zurückzugeben, musst Du zunächst in die korrekte Klasse wechseln.",
+ "rebirthInList4": "Gegenstände die mit Edelsteinen oder zufällig gefunden wurden bleiben bestehen (wie z. B. Haus- und Reittiere), allerdings kannst Du sie erst anwählen, wenn das Feature wieder freigeschaltet ist.",
+ "rebirthInList5": "Gekaufte Ausrüstung mit begrenzter Auflage kann zurückgegeben werden, auch wenn das Event bereits beendet ist. Um Klassen-spezifische Ausrüstung zurückzugeben, musst Du zunächst in die korrekte Klasse wechseln.",
"rebirthEarnAchievement": "Du erhältst den Erfolg für das Beginnen eines neuen Abenteuers!",
"beReborn": "Werde wiedergeboren",
"rebirthAchievement": "Du hast ein neues Abenteuer begonnen! Das ist Deine <%= number %>. Wiedergeburt. Dein höchstes jemals erreichtes Level ist <%= level %>. Um diesen Erfolg zu stapeln, beginne Dein nächstes Abenteuer wenn Du ein noch höheres Level erreicht hast!",
@@ -24,5 +24,6 @@
"rebirthPop": "Starte einen neuen Charakter mit Level 1, aber behalte Erfolge, Sammlungen und Aufgaben mit Verlauf bei.",
"rebirthName": "Sphäre der Wiedergeburt",
"reborn": "Wiedergeboren, max. Level <%= reLevel %>",
- "confirmReborn": "Bist Du sicher?"
+ "confirmReborn": "Bist Du sicher?",
+ "rebirthComplete": "Du wurdest wiedergeboren!"
}
\ No newline at end of file
diff --git a/common/locales/de/settings.json b/common/locales/de/settings.json
index a1f333879c..32515261b3 100644
--- a/common/locales/de/settings.json
+++ b/common/locales/de/settings.json
@@ -11,10 +11,10 @@
"dailyDueDefaultView": "Setze Standardansicht der täglichen Aufgaben auf \"Fällig\"",
"dailyDueDefaultViewPop": "Mit dieser Option änderst Du die Standardansicht der täglichen Aufgaben zu \"Fällig\" statt zu \"Alle\"",
"reverseChatOrder": "Zeige die Chat-Nachrichten in umgekehrter Reihenfolge",
- "startCollapsed": "Tag-Liste in Aufgaben standartmäßig verdecken",
+ "startCollapsed": "Tag-Liste in Aufgaben standardmäßig verdecken",
"startCollapsedPop": "Mit dieser Option wird die Liste der Tags verdeckt, wenn Du eine Aufgabe das erste mal bearbeitest.",
- "startAdvCollapsed": "Erweiterte Optionen standartmäßig verdecken",
- "startAdvCollapsedPop": "Mit dieser Option werden die erweiterten Optionen verdeckt, wenn Du eine Aufgabe das erste mal bearbeitest.",
+ "startAdvCollapsed": "Erweiterte Optionen standardmäßig verdecken",
+ "startAdvCollapsedPop": "Mit dieser Option werden erweiterte Optionen ausgeblendet, wenn Du eine Aufgabe das erste mal bearbeitest.",
"dontShowAgain": "Nicht mehr anzeigen",
"suppressLevelUpModal": "Beim Levelaufstieg kein Popup anzeigen",
"suppressHatchPetModal": "Beim Schlüpfen eines Haustiers kein Popup anzeigen",
@@ -32,41 +32,53 @@
"resetAccount": "Konto zurücksetzen",
"resetAccPop": "Starte neu, dabei werden alle Level, Gold, Ausrüstung, Verlauf und Aufgaben entfernt.",
"deleteAccount": "Konto löschen",
- "deleteAccPop": "Kündige und entferne Dein Habitica Konto.",
- "qrCode": "QR Code",
- "dataExport": "Daten Exportieren",
- "saveData": "Hier sind ein paar Möglichkeiten Deine Habitica Dateien zu sichern.",
- "habitHistory": "Habitica Verlauf",
+ "deleteAccPop": "Kündige und entferne Dein Habitica-Konto.",
+ "qrCode": "QR-Code",
+ "dataExport": "Daten exportieren",
+ "saveData": "Hier sind ein paar Möglichkeiten Deine Habitica.Daten zu sichern.",
+ "habitHistory": "Habitica-Verlauf",
"exportHistory": "Verlauf exportieren:",
"csv": "(CSV)",
- "userData": "Benutzer Daten",
- "exportUserData": "Benutzer Daten exportieren:",
+ "userData": "Benutzerdaten",
+ "exportUserData": "Benutzerdaten exportieren:",
"export": "Exportieren",
"xml": "(XML)",
"json": "(JSON)",
"customDayStart": "Tageswechsel einstellen",
"changeCustomDayStart": "Tageswechsel ändern?",
"sureChangeCustomDayStart": "Willst Du Deinen Tageswechsel wirklich ändern?",
+ "customDayStartHasChanged": "Dein persönlicher Tagesstart wurde geändert.",
"nextCron": "Deine täglichen Aufgaben werden das nächste Mal überprüft und zurückgesetzt, wenn Du Habitica das erste Mal nach <%= time %> nutzt. Stelle sicher, dass Du Deine täglichen Aufgaben vor diesem Zeitpunkt erledigt hast.",
"customDayStartInfo1": "Normalerweise werden Deine täglichen Aufgaben jeden Tag um Mitternacht Deiner Ortszeit von Habitica überprüft und zurückgesetzt. Du kannst diesen Zeitpunkt hier ändern.",
"misc": "Verschiedenes",
"showHeader": "Header anzeigen",
"changePass": "Passwort ändern",
- "changeUsername": "Login Name ändern",
- "changeEmail": "Email Adresse ändern",
- "newEmail": "Neue Email Adresse",
+ "changeUsername": "Login-Name ändern",
+ "changeEmail": "E-Mail-Adresse ändern",
+ "newEmail": "Neue E-Mail-Adresse",
"oldPass": "Altes Passwort",
"newPass": "Neues Passwort",
"confirmPass": "Neues Passwort bestätigen",
"newUsername": "Neuen Login Name erstellen",
"dangerZone": "Gefahrenzone",
"resetText1": "WARNUNG! Es werden große Teile Deines Accounts zurückgesetzt. Wir raten dringend davon ab. Jedoch finden einige Spieler diese Funktion sinnvoll, um nach einem anfänglichen Testen der Seite neu beginnen zu können.",
- "resetText2": "Du verlierst alle Level, Dein Gold und Erfahrungspunkte. Darüber hinaus werden alle aktuellen und vergangenen Aufgaben gelöscht. Außerdem verlierst Du Deine Ausrüstung, die Du Dir jedoch zurück kaufen kannst. Darunter fallen Sonderausrüstungsgegenstände oder Mystery-Gegenstände von Abonnenten (Du musst in der gleichen Klasse sein, um klassenabhängige Gegenstände zurück kaufen zu können). Du behältst Deine aktuelle Klasse, Deine Haus- und Reittiere. Statt dieser Option empfehlen wir die Kugel der Wiedergeburt zu verwenden, da dadurch alle Deine Aufgaben erhalten bleiben.",
+ "resetText2": "Du wirst alle Level, Gold und Erfahrungspunkte verlieren. Alle Aufgaben (außer Wettbewerbsaufgaben) werden dauerhaft gelöscht und ihr Verlauf geht verloren. Du wirst die gesamte Ausrüstung verlieren, kannst diese jedoch wiedererwerben, einschließlich der begrenzten Auflagen und Abonnement-Gegenstände, die Du bereits besitzt (Du musst in die richtige Klasse wechseln, um klassenspezifische Ausrüstung wiederzuerwerben). Deine aktuelle Klasse, sowie Deine Haus- und Reittiere bleiben erhalten. Du könntest die Sphäre der Wiedergeburt stattdessen bevorzugen, welche einen sichereren Weg darstellt und Deine Aufgaben beibehält.",
"deleteText": "Bist Du sicher? Das wird Dein Konto für immer löschen und es kann nicht wiederhergestellt werden! Wenn Du Habitica wieder verwenden möchtest, musst Du ein neues Konto registrieren. Gesparte oder verbrauchte Edelsteine werden nicht ersetzt. Wenn Du absolut sicher bist, dann tippe <%= deleteWord %> in die Text-Box unten ein.",
"API": "API",
- "APIText": "Kopiere sie zur Anwendung in Applikationen von Drittanbietern. Sieh Dein API Token aber als Passwort an und verbreite es nicht. Du wirst vielleicht gelegentlich nach Deiner User ID gefragt, aber poste niemals Dein API Token öffentlich wo es andere sehen können, auch nicht auf Github.",
+ "APIv3": "API v3",
+ "APIv2": "API v2 - Veraltet",
+ "APIText": "Kopiere sie zur Anwendung in Applikationen von Drittanbietern. Sieh Dein API Token aber als Passwort an und verbreite es nicht. Du wirst vielleicht gelegentlich nach Deiner Benutzer-ID gefragt, aber poste niemals Dein API Token öffentlich wo es andere sehen können, auch nicht auf Github.",
"APIToken": "API Token (Das ist ein Passwort - die obige Warnung gilt auch hier!)",
+ "thirdPartyApps": "Apps von Drittanbietern",
+ "dataToolDesc": "Eine Webseite, die Dir Informationen aus Deinem Habitica-Konto anzeigt, z.B. Statistiken über Deine Aufgaben, Deine Ausrüstung und Fähigkeiten.",
+ "beeminder": "Beeminder",
+ "beeminderDesc": "Lass Beeminder Deine Habitica-To-Dos automatisch überwachen. Du kannst Dich verpflichten, eine tägliche oder wöchentliche Mindestanzahl von To-Dos zu erledigen, oder Du kannst Dich engagieren, die Zahl Deiner unerledigten To-Dos allmählich zu verringern. (Unter \"verpflichten\" versteht Beeminder, dass Du echtes Geld zahlst, wenn Du Deine Versprechen nicht hältst! Aber Du kannst auch einfach die ausgefallenen Grafiken von Beeminder bewundern.)",
+ "chromeChatExtension": "Chrome Chat-Erweiterung",
+ "chromeChatExtensionDesc": "Die Chrome Chat-Erweiterung für Habitica fügt eine intuitive Chat-Box zu habitica.com hinzu. Damit kannst Du in der Taverne chatten, mit Deiner Party und mit allen Gilden, zu denen Du gehörst.",
+ "otherExtensions": "Andere Erweiterungen",
+ "otherDesc": "Finde andere Apps, Erweiterungen und Hilfsprogramme auf dem Habitica Wiki.",
"resetDo": "Ja, setzt mein Konto jetzt zurück!",
+ "resetComplete": "Zurückgesetzt!",
"fixValues": "Werte reparieren",
"fixValuesText1": "Wenn Du Opfer eines Bugs geworden bist, oder einen Fehler gemacht hast, der Deinen Charakter unfair beeinflusst hat, (Schaden den Du nicht hättest nehmen dürfen, Gold das Du nicht verdient hast, usw.), dann kannst Du das hier manuell korrigieren. Ja, das eröffnet die Möglichkeit zu cheaten: verwende dieses Feature mit Bedacht, oder Du verdirbst Dir das Ausbilden Deiner Gewohnheiten!",
"fixValuesText2": "Beachte, dass Du hier keine Strähnen einzelner Aufgaben wiederherstellen kannst. Um das zu tun, bearbeite eine tägliche Aufgabe unter erweiterte Optionen. Dort wirst Du ein Strähne wiederherstellen Feld finden.",
@@ -84,25 +96,26 @@
"addedLocalAuth": "Lokale Authentifizierung erfolgreich hinzugefügt",
"data": "Daten",
"exportData": "Daten exportieren",
- "emailChange1": "Um Deine Email-Adresse zu ändern, schicke bitte eine Email an",
+ "emailChange1": "Um Deine E-Mail-Adresse zu ändern, schicke bitte eine E-Mail an",
"emailChange2": "admin@habitica.com",
- "emailChange3": "Bitte gib sowohl Deine alte und neue Email-Adresse, als auch Deine Benutzer ID an.",
+ "emailChange3": "Bitte gib sowohl Deine alte und neue E-Mail-Adresse, als auch Deine Benutzer-ID an.",
"usernameOrEmail": "Login Name oder E-Mail-Adresse",
"email": "E-Mail",
"registeredWithFb": "Mit Facebook registriert",
"loginNameDescription1": "Dies hier benutzt Du, um Dich bei Habitica einzuloggen. Gehe zu",
"loginNameDescription2": "Benutzer->Profil",
"loginNameDescription3": "um den Namen, der bei Deinem Avatar und bei Deinen Chat Nachrichten steht zu ändern.",
- "emailNotifications": "Email Benachrichtigungen",
+ "emailNotifications": "E-Mail-Benachrichtigungen",
"wonChallenge": "Du hast einen Wettbewerb gewonnen!",
"newPM": "Du hast eine private Nachricht erhalten",
+ "sentGems": "Edelsteine gesendet!",
"giftedGems": "Verschenkte Edelsteine",
"giftedGemsInfo": "<%= amount %> Edelsteine - von <%= name %>",
"giftedSubscription": "Verschenkte Abonnements",
"invitedParty": "In die Gruppe eingeladen",
"invitedGuild": "In die Gilde eingeladen",
"importantAnnouncements": "Dein Konto ist inaktiv",
- "weeklyRecaps": "Zusammenfassung Deiner Account aktivitäten in dieser Woche (Notiz: Ist zurzeit wegen Performance Problemen deaktiviert, Wir hoffen das es bald wieder zurück ist und werden demnächst wieder e-mails verschicken!)",
+ "weeklyRecaps": "Zusammenfassung Deiner Account-Aktivitäten in dieser Woche (Notiz: Ist zurzeit wegen Performance-Problemen deaktiviert. Wir hoffen, dass es bald wieder zurück ist und werden demnächst wieder E-Mails verschicken!)",
"questStarted": "Dein Quest hat begonnen",
"invitedQuest": "Zu einem Quest eingeladen",
"kickedGroup": "Aus Gruppe entfernt.",
@@ -111,7 +124,7 @@
"unsubscribedSuccessfully": "Erfolgreich abgemeldet!",
"unsubscribedTextUsers": "Du hast Dich erfolgreich von allen Habitica E-Mails abgemeldet. In den Einstellungen kannst Du angeben welche E-Mails Du erhalten willst (Anmeldung erforderlich).",
"unsubscribedTextOthers": "Du wirst keine weitere E-Mails von Habitica erhalten.",
- "unsubscribeAllEmails": "Häkchen setzen, um keine weiteren Emails zu erhalten",
+ "unsubscribeAllEmails": "Häkchen setzen, um keine weiteren E-Mails zu erhalten",
"unsubscribeAllEmailsText": "Indem ich hier ein Häkchen gesetzt habe, bestätige ich, dass ich verstanden habe, dass ich aus allen Habitica E-Mail-Listen ausgetragen wurde. Habitica kann mir keine E-Mails mehr zu wichtigen Änderungen der Seite oder meines Accounts schicken.",
"correctlyUnsubscribedEmailType": "Erfolgreich \"<%= emailType %>\"-E-Mails abbestellt.",
"subscriptionRateText": "Abonnement über <%= price %> USD pro <%= months %> Monat(e)",
@@ -133,12 +146,17 @@
"generate": "Erstelle",
"getCodes": "Codes erhalten!",
"webhooks": "Webhooks",
- "enabled": "Aktivieren",
- "webhookURL": "Webhook URL",
+ "enabled": "Aktiviert",
+ "webhookURL": "Webhook-URL",
+ "invalidUrl": "Ungültige Url",
+ "invalidEnabled": "Der Parameter \"enabled\" muss ein boolescher Ausdruck sein.",
+ "regIdRequired": "RegId erforderlich",
+ "pushDeviceAdded": "Push-Gerät erfolgreich hinzugefügt.",
+ "pushDeviceAlreadyAdded": "Der Benutzer hat bereits ein Push-Gerät.",
"add": "Hinzufügen",
"buyGemsGoldCap": "Obergrenze wurde auf <%= amount %> angehoben",
"mysticHourglass": "<%= amount %> mystische Sanduhr",
- "mysticHourglassText": "Mystische Sanduhren erlauben dir ein Überraschungsgegenstandsset früherer Monate zu kaufen.",
+ "mysticHourglassText": "Mystische Sanduhren erlauben Dir ein Überraschungsgegenstandsset früherer Monate zu kaufen.",
"purchasedPlanId": "Abonnement über <%= price %> USD pro <%= months %> Monat(e) (Abgerechnet über: <%= plan %>)",
"purchasedPlanExtraMonths": "Du hast <%= months %> Monate zusätzliches Abonnement-Guthaben.",
"consecutiveSubscription": "Fortlaufendes Abonnement",
@@ -149,5 +167,5 @@
"amazonPayments": "Amazon-Zahlungen",
"timezone": "Zeitzone",
"timezoneUTC": "Habitica verwendet die Zeitzone, welche an Deinem PC eingestellt ist: <%= utc %>",
- "timezoneInfo": "Wenn diese Zeitzone falsch ist, lade diese Seite neu, indem Du den Neu Laden Button Deines Browsers drückst, um sicher zu stellen, dass Habitica die aktuellsten Daten hat. Wenn die Zeitzone weiterhin falsch ist, passe die Zeitzone an Deinem PC an und lade diese Seite noch einmal neu.
Wenn Du Habitica auch auf anderen PCs oder mobilen Geräten verwendest, muss die Zeitzone auf allen Geräten gleich sein. Wenn sich Deine täglichen Aufgaben zur falschen Zeit zurückgestellt haben, wiederhole diese Überprüfung auf allen anderen PCs und in einem Browser Deines mobilen Gerätes."
+ "timezoneInfo": "Wenn diese Zeitzone falsch ist, lade die Seite mit Hilfe Deines Browsers erneut, um sicherzustellen, dass Habitica die aktuellen Informationen darstellt. Ist diese immernoch falsch, passe die Zeitzone Deines PCs an und lade die Seite erneut.
Wenn Du Habitica auf anderen PCs oder Mobilgeräten verwendest, muss die Zeitzone auf allen übereinstimmen. Wenn Deine täglichen Aufgaben zur falschen Zeit zurückgesetzt werden, wiederhole diese Prüfung auf allen anderen PCs und in einem Browser Deiner Mobilgeräte."
}
\ No newline at end of file
diff --git a/common/locales/de/spells.json b/common/locales/de/spells.json
index fd638957c6..99c107fddf 100644
--- a/common/locales/de/spells.json
+++ b/common/locales/de/spells.json
@@ -1,16 +1,16 @@
{
"spellWizardFireballText": "Flammenstoß",
- "spellWizardFireballNotes": "Flammen schießen aus Deinen Händen. Du erhältst XP und fügst Bossen zusätzlichen Schaden zu! Klicke auf eine Aufgabe, um sie zu verzaubern. (Basiert auf: INT Wert)",
+ "spellWizardFireballNotes": "Flammen schießen aus Deinen Händen. Du erhältst XP und fügst Bossen zusätzlichen Schaden zu! Klicke auf eine Aufgabe, um sie zu verzaubern. (Basiert auf: INT)",
"spellWizardMPHealText": "Ätherischer Schwall",
- "spellWizardMPHealNotes": "Du opferst Mana um Deinen Freunden zu helfen. Der Rest Deiner Gruppe erhält MP! (Basiert auf: INT Wert)",
+ "spellWizardMPHealNotes": "Du opferst Mana um Deinen Freunden zu helfen. Der Rest Deiner Gruppe erhält MP! (Basiert auf: INT)",
"spellWizardEarthText": "Erdbeben",
- "spellWizardEarthNotes": "Deine mentalen Kräfte bringen die Erde zum beben. Deine ganze Gruppe erhält Bonus Intelligenzpunkte! (Basiert auf: INT ohne Boni)",
+ "spellWizardEarthNotes": "Deine mentalen Kräfte bringen die Erde zum Beben. Deine ganze Gruppe erhält Bonus Intelligenzpunkte! (Basiert auf: INT ohne Boni)",
"spellWizardFrostText": "Klirrender Frost",
- "spellWizardFrostNotes": "Eine Eisschicht überzieht Deine Aufgaben. Keine Deiner Strähnen wird morgen auf null zurückgesetzt! (Einmal gewirkt wirkt der Effekt auf all Deine Strähnen)",
+ "spellWizardFrostNotes": "Eine Eisschicht überzieht Deine Aufgaben. Keine Deiner Strähnen wird morgen auf Null zurückgesetzt! (Einmal gewirkt wirkt der Effekt auf all Deine Strähnen)",
"spellWarriorSmashText": "Gewaltschlag",
"spellWarriorSmashNotes": "Du triffst eine Aufgabe mit aller Kraft. Sie wird blauer/weniger rot, und Du fügst Bossen extra Schaden zu! Klicke auf eine Aufgabe, um sie anzugreifen. (Basiert auf: STR)",
"spellWarriorDefensiveStanceText": "Verteidigungstellung",
- "spellWarriorDefensiveStanceNotes": "Du bereitest Dich auf den Ansturm Deiner Aufgaben vor. Du erhälst einen Ausdauerbonus! (Basiert auf: CON ohne Boni)",
+ "spellWarriorDefensiveStanceNotes": "Du bereitest Dich auf den Ansturm Deiner Aufgaben vor. Du erhältst einen Ausdauerbonus! (Basiert auf: CON ohne Boni)",
"spellWarriorValorousPresenceText": "Tapferer Charakter",
"spellWarriorValorousPresenceNotes": "Deine Anwesenheit ermutigt Deine Gruppe. Deine ganze Gruppe erhält einen Stärkebonus (Basiert auf: STR ohne Boni)",
"spellWarriorIntimidateText": "Einschüchternder Blick",
@@ -35,16 +35,22 @@
"spellSpecialSnowballAuraNotes": "Wirf einen Schneeball auf ein Gruppenmitglied! Was kann dabei schon schief gehen? Dauert bis zum Tageswechsel des Gruppenmitglieds an.",
"spellSpecialSaltText": "Salz",
"spellSpecialSaltNotes": "Jemand hat Dich mit Schnee eingeseift. Haha, sehr lustig. Jetzt ist aber Schluss!",
- "spellSpecialSpookDustText": "Spukglitzer",
- "spellSpecialSpookDustNotes": "Verwandle einen Freund in ein schwebendes Bettlaken mit Augen!",
+ "spellSpecialSpookySparklesText": "Spukglitzer",
+ "spellSpecialSpookySparklesNotes": "Verwandle einen Freund in ein schwebendes Bettlaken mit Augen!",
"spellSpecialOpaquePotionText": "Trank der Entgeisterung",
- "spellSpecialOpaquePotionNotes": "Beendet den Effekt von Spukglitter",
+ "spellSpecialOpaquePotionNotes": "Beendet den Effekt von Spukglitzer.",
"spellSpecialShinySeedText": "Schimmernde Saat",
"spellSpecialShinySeedNotes": "Verwandle einen Freund in eine fröhliche Blume!",
- "spellSpecialPetalFreePotionText": "Blütenfrei Trank",
+ "spellSpecialPetalFreePotionText": "Blütenfrei-Trank",
"spellSpecialPetalFreePotionNotes": "Beendet den Effekt der schimmernden Saat.",
"spellSpecialSeafoamText": "Meeresschaum",
"spellSpecialSeafoamNotes": "Verwandle einen Freund in ein Meereslebewesen!",
"spellSpecialSandText": "Sand",
- "spellSpecialSandNotes": "Beendet den Effekt von Meeresschaum."
+ "spellSpecialSandNotes": "Beendet den Effekt von Meeresschaum.",
+ "spellNotFound": "Fähigkeit \"<%= spellId %>\" nicht gefunden.",
+ "partyNotFound": "Gruppe nicht gefunden",
+ "targetIdUUID": "\"targetId\" muss eine gültige Benutzer-ID sein.",
+ "challengeTasksNoCast": "Fähigkeiten auf Wettbewerbsaufgaben anzuwenden ist nicht erlaubt.",
+ "spellNotOwned": "Du besitzt diese Fähigkeit nicht.",
+ "spellLevelTooHigh": "Du musst Level <%= level %> sein, um diese Fähigkeit zu benutzen."
}
\ No newline at end of file
diff --git a/common/locales/de/subscriber.json b/common/locales/de/subscriber.json
index eefcf0312e..6ba2cef264 100644
--- a/common/locales/de/subscriber.json
+++ b/common/locales/de/subscriber.json
@@ -1,11 +1,13 @@
{
- "subscription": "Abonnements",
+ "subscription": "Abonnement",
"subscriptions": "Abonnements",
"subDescription": "Kaufe Edelsteine mit Gold, bekomme monatlich Überraschungsgegenstände, behalte Deinen Fortschrittsverlauf, verdopple das tägliche Beutelimit und unterstütze die Entwickler. Klicke um mehr zu erfahren.",
"buyGemsGold": "Kaufe Edelsteine mit Gold",
"buyGemsGoldText": "Alexander der Händler verkauft Dir Edelsteine zum Preis von <%= gemCost %> Goldstücken pro Edelstein. Seine Lieferungen sind anfänglich auf <%= gemLimit %> Edelsteine pro Monat beschränkt, aber dieses Limit erhöht sich um 5 Edelsteine für alle drei Monate, die Du ein fortlaufendes Abo hast, bis zu einem Maximum von 50 Edelsteinen pro Monat!",
- "retainHistory": "Erhalte zusätzliche Einträge der Vergangenheit",
- "retainHistoryText": "Macht abgeschlossene einmalige Aufgaben und den Aufgabenverlauf länger verfügbar.",
+ "mustSubscribeToPurchaseGems": "Du musst ein Abonnement abschließen, um Edelsteine mit Gold zu kaufen",
+ "reachedGoldToGemCap": "Du hast die Obergrenze <%= convCap %> an Umwandlungen Gold => Edelsteine für diesen Monat erreicht. Diese existiert um Missbrauch / Farming zu unterbinden. Die Obergrenze wird in den ersten drei Tagen des nächsten Monats zurückgesetzt.",
+ "retainHistory": "Behalte zusätzliche Verlaufeinträge",
+ "retainHistoryText": "Macht abgeschlossene To-Dos und den Aufgabenverlauf länger verfügbar.",
"doubleDrops": "Du kannst doppelt so viele Gegenstände pro Tag finden",
"doubleDropsText": "Fülle Deine Ställe schneller!",
"mysteryItem": "Einzigartige monatliche Gegenstände",
@@ -15,9 +17,9 @@
"monthUSD": "USD($) / Monat",
"organization": "Organisation",
"groupPlans": "Gemeinschaftliche Pläne",
- "indivPlan1": "Für Privatpersonen ist Habitica kostenlos. Auch für kleine Interessengruppen kann die kostenlose (oder billige)",
+ "indivPlan1": "Für Privatpersonen ist Habitica kostenlos. Auch für kleine Interessengruppen kann die kostenlose (oder günstige)",
"indivPlan2": "verwendet werden um Teilnehmer zu motivieren ihr Verhalten zu verändern. Denke nur an Gruppen von Autoren, künstlerische Wettbewerbe und mehr.",
- "groupText1": "Aber einige Gruppenleiter wollen mehr Kontrolle, Datenschutz, Sicherheit und Unterstützung. Beispielsweise Familien, Gruppen mit Gesundheits- oder Erholungszielen, Angestelltengruppen und mehr. Die Planung erlauben selbstständige Instanzen von Habitica für Deine Gruppe oder Organisation, sicher und unabhängig von",
+ "groupText1": "Aber einige Gruppenleiter wollen mehr Kontrolle, Datenschutz, Sicherheit und Unterstützung. Beispielsweise Familien, Gruppen mit Gesundheits- oder Erholungszielen, Angestelltengruppen und mehr. Diese Pläne erlauben selbstständige Instanzen von Habitica für Deine Gruppe oder Organisation, sicher und unabhängig von",
"groupText2": "Weiter unten sind weitere Boni für solche Pläne aufgelistet, trete mit uns in Verbindung um mehr Informationen zu erhalten!",
"planFamily": "Familien (in naher Zukunft)",
"planGroup": "Gruppen (in naher Zukunft)",
@@ -29,23 +31,24 @@
"manageSub": "Klicke um Abonnements zu verwalten",
"cancelSub": "Abonnement beenden",
"canceledSubscription": "Abonnement storniert",
- "adminSub": "Administrator Abonnements",
+ "cancelingSubscription": "Abonnement abbrechen",
+ "adminSub": "Administrator-Abonnements",
"morePlans": "Weitere Abonnements Bald verfügbar!",
"organizationSub": "Private Organisationen",
"organizationSubText": "Mitglieder der Organisation nehmen außerhalb von Habitica teil, das bringt Deinen Teilnehmern einen Konzentrationsvorteil.",
"hostingType": "Art des Hostings",
- "hostingTypeText": "Shared hosting bedeutet, Deine Organisation verwendet die selbe Datenbank wie Habitica, obwohl Du nicht mit Habitica interagierst. Dedicated bedeutet, Du bekommst Deine eigene Datenbank und Server. Du kannst wählen, ob Du Habitica auf Deinem Server oder Datenbank hosten möchtest, oder ob wir es auf unserem Server installieren.",
+ "hostingTypeText": "Shared hosting bedeutet, Deine Organisation verwendet dieselbe Datenbank wie Habitica, obwohl Du nicht mit Habitica interagierst. Dediziert bedeutet, Du bekommst Deine eigene Datenbank und Server. Du kannst wählen, ob Du Habitica auf Deinem Server oder Datenbank hosten möchtest, oder ob wir es auf unserem Server installieren.",
"dedicated": "Dediziert",
"customDomain": "Wählbare Domain",
"customDomainText": "Wir können Dir wahlweise eine eigene Domain für die Intallation zur Verfügung stellen.",
"maxPlayers": "Max. Teilnehmer",
"maxPlayersText": "Die maximale Anzahl von Spielern in Deiner privaten Organisation.",
"unlimited": "Unbeschränkt",
- "priSupport": "Vorrängige Unterstützung bei Tickets & Hosting",
+ "priSupport": "Vorrangige Unterstützung bei Tickets & Hosting",
"priSupportText": "Du bist der erste der Unterstützung erhält.",
- "timeSupport": "Unterstützung in Stunden / Monat",
- "timeSupportText": "Wir werden Unterstützung bieten für Training, Bugs, Installation und Fragen nach Features",
- "gameFeatures": "Spiel Features",
+ "timeSupport": "Unterstützung in Stunden/Monat",
+ "timeSupportText": "Wir werden Unterstützung bieten für Training, Bugs, Installation und Feature-Anfragen.",
+ "gameFeatures": "Spiel-Features",
"gold2Gem": "Edelsteine können mit Gold erworben werden",
"gold2GemText": "Mitglieder können Edelsteine mit Gold erwerben, das bedeutet, keiner Deiner Teilnehmer muss irgendetwas mit echtem Geld kaufen.",
"infiniteGem": "Unendlich viele Edelsteine für den Anführer",
@@ -64,7 +67,7 @@
"buyGemsAllow1": "Du kannst noch",
"buyGemsAllow2": "weitere Edelsteine in diesem Monat erwerben",
"purchaseGemsSeparately": "Zusätzliche Edelsteine kaufen",
- "subFreeGemsHow": "Habitica Spieler können kostenlose Edelsteine verdienen, indem sie Wettbewerbe gewinnen, die Edelsteine als Siegespreise verleihen, oder als eine Belohnung für Mitwirkende, indem sie bei der Entwickelung von Habitica helfen.",
+ "subFreeGemsHow": "Habitica-Spieler können kostenlose Edelsteine verdienen, indem sie Wettbewerbe gewinnen, die Edelsteine als Siegesprämie verleihen, oder als eine Belohnung für Mitwirkende, indem sie bei der Entwickelung von Habitica helfen.",
"seeSubscriptionDetails": "Gehe zu Einstellungen > Abonnement um die Details Deines Abonnements zu sehen!",
"timeTravelers": "Mysteriöse Zeitreisende",
"timeTravelersTitleNoSub": "<%= linkStartTyler %>Tyler<%= linkEnd %> und <%= linkStartVicky %>Vicky<%= linkEnd %>",
@@ -73,6 +76,9 @@
"timeTravelersPopover": "Wir sehen Du hast eine mystische Sanduhr, deshalb werden wir gerne für Dich zurück durch die Zeit reisen! Bitte wähle das Haustier, Reittier oder mystische Gegenstandsset, dass Du haben möchtest. Die Liste aller vergangenen Gegenstandssets kannst Du hier finden! Wenn diese Dich nicht zufrieden stellen können wir Dich vielleicht für eines unserer futuristischen Steampunk Gegenstandssets interessieren?",
"timeTravelersAlreadyOwned": "Herzlichen Glückwunsch! Du besitzt bereits alles, was die Zeitreisenden gerade anbieten können. Danke, dass Du die Seite unterstützt!",
"mysticHourglassPopover": "Eine mystische Sanduhr erlaubt Dir Gegenstände zu kaufen, welche in der Vergangenheit nur zeitlich begrenzt zur Verfügung standen. Dies sind beispielsweise die Überraschungs-Abonnenten-Sets und Belohnungen ehemaligerWeltbosse.",
+ "mysterySetNotFound": "Mysteriöses Set nicht gefunden, oder Set wurde bereits erworben.",
+ "mysteryItemIsEmpty": "Mysteriöse Gegenstände sind leer",
+ "mysteryItemOpened": "Überraschungsgegenstand geöffnet.",
"mysterySet201402": "Geflügelter-Bote-Set",
"mysterySet201403": "Waldläufer-Set",
"mysterySet201404": "Schmetterling-des-Zwielichts-Set",
@@ -99,6 +105,8 @@
"mysterySet201601": "Entschlossener-Held-Set",
"mysterySet201602": "Herzensbrecher-Set",
"mysterySet201603": "Glücksklee-Set",
+ "mysterySet201604": "Blattkrieger-Set",
+ "mysterySet201605": "Marching Bard Set",
"mysterySet301404": "Steampunk-Standard-Set",
"mysterySet301405": "Steampunk-Zubehör-Set",
"mysterySetwondercon": "Wondercon",
@@ -110,9 +118,25 @@
"hourglassBuyItemConfirm": "Möchtest Du diesen Gegenstand für 1 Mystische Sanduhr kaufen?",
"petsAlreadyOwned": "Bereits im Besitz dieses Haustieres.",
"mountsAlreadyOwned": "Bereits im Besitz dieses Rettieres.",
- "typeNotAllowedHourglass": "Diese Art Gegenstand ist nicht für den Kauf mit einer Mystischen Sanduhr geeignet. Geeignete Arten von Gegenständen:",
+ "typeNotAllowedHourglass": "Gegenstandsart wird nicht zum Kauf von mystischen Sanduhren unterstützt. Erlaubte Typen sind: ",
"petsNotAllowedHourglass": "Das Haustier ist nicht für den Kauf mit einer Mystischen Sanduhr geeignet.",
"mountsNotAllowedHourglass": "Das Reittier ist nicht für den Kauf mit einer Mystischen Sanduhr geeignet.",
"hourglassPurchase": "Gegenstand mit einer Mystischen Sanduhr gekauft!",
- "hourglassPurchaseSet": "Hat ein Set Gegenstände mit einer Mystischen Sanduhr gekauft!"
+ "hourglassPurchaseSet": "Hat ein Set Gegenstände mit einer Mystischen Sanduhr gekauft!",
+ "missingUnsubscriptionCode": "Fehlender Austragungscode.",
+ "missingSubscription": "Benutzer hat keinen Abonnement-Plan",
+ "missingSubscriptionCode": "Fehlender Abonnementcode. Gültige Werte: ",
+ "cannotDeleteActiveAccount": "Du hast ein aktives Abonnement, beende Deinen Plan bevor Du Deinen Account löschst.",
+ "paymentNotSuccessful": "Die Zahlung war nicht erfolgreich",
+ "planNotActive": "Der Plan wurde noch nicht aktiviert (aufgrund eines PayPal-Bugs). Er startet am <%= nextBillingDate %>. Danach kannst Du diesen abbrechen oder die vollen Leistungen nutzen.",
+ "notAllowedHourglass": "Haustier/Reittier nicht zum Kauf mit mystischen Sanduhren verfügbar.",
+ "readCard": "<%= cardType %> wurde gelesen",
+ "cardTypeRequired": "Kartentyp erforderlich",
+ "cardTypeNotAllowed": "Unbekannter Kartentyp.",
+ "invalidCoupon": "Ungültiger Gutschein-Code",
+ "couponUsed": "Gutschein wurde bereits eingelöst.",
+ "noSudoAccess": "Du hast keine sudo-Rechte.",
+ "couponCodeRequired": "Coupon-Code erforderlich.",
+ "eventRequired": "\"req.params.event\" erforderlich.",
+ "countRequired": "\"req.query.count\" erforderlich."
}
\ No newline at end of file
diff --git a/common/locales/de/tasks.json b/common/locales/de/tasks.json
index 08a84694de..a2507cdbab 100644
--- a/common/locales/de/tasks.json
+++ b/common/locales/de/tasks.json
@@ -1,7 +1,7 @@
{
"clearCompleted": "Erfüllte Aufgaben entfernen",
- "lotOfToDos": "Erfüllte Aufgaben werden automatisch nach 3 Tagen archiviert. Du kannst auf sie zugreifen unter Einstellungen > Exportieren.",
- "deleteToDosExplanation": "Wenn Du den unteren Knopf klickst, werden alle Deine erfüllten und archivierten Aufgaben dauerhaft gelöscht. Exportiere sie vorher, wenn Du sie nicht verlieren möchtest.",
+ "lotOfToDos": "Erfüllte To-Dos werden automatisch nach 3 Tagen archiviert. Du kannst auf sie zugreifen unter Einstellungen > Exportieren.",
+ "deleteToDosExplanation": "Wenn Du den unteren Knopf klickst, werden alle Deine erfüllten und archivierten To-Dos dauerhaft gelöscht. Exportiere sie vorher, wenn Du sie nicht verlieren möchtest.",
"addmultiple": "Mehrere hinzufügen",
"addsingle": "Einzelne hinzufügen",
"habit": "Gewohnheit",
@@ -17,7 +17,7 @@
"checklistText": "Zerlege eine Aufgabe in kleinere Teile! Checklisten erhöhen die Erfahrung und das Gold, das Du für eine Aufgabe bekommst, und verringern den Schaden, den eine tägliche Aufgabe verursacht.",
"expandCollapse": "Auf-/Zuklappen",
"text": "Titel",
- "extraNotes": "Extra Notizen",
+ "extraNotes": "Zusatznotizen",
"direction/Actions": "Leitung/Aktionen",
"advancedOptions": "Erweiterte Optionen",
"difficulty": "Schwierigkeit",
@@ -30,7 +30,7 @@
"attributes": "Eigenschaften",
"physical": "Körperlich",
"mental": "Mental",
- "otherExamples": "z.B. berufliche Unternehmungen, Hobbies, Finanzielles, usw.",
+ "otherExamples": "z. B. berufliche Unternehmungen, Hobbies, Finanzielles, usw.",
"progress": "Fortschritt",
"daily": "Tägliche Aufgabe",
"dailies": "Tägliche Aufgaben",
@@ -41,15 +41,15 @@
"repeatEvery": "Wiederhole alle",
"repeatHelpTitle": "Wie oft soll diese Aufgabe wiederholt werden?",
"dailyRepeatHelpContent": "Diese Aufgabe wird alle X Tage fällig werden. Du kannst diesen Wert unten bestimmen.",
- "weeklyRepeatHelpContent": "Diese Aufgabe wird an den unten hervorgehobenen Tagen fällig werden. Klicke auf einen Tag um ihn zu aktivieren / deaktivieren.",
+ "weeklyRepeatHelpContent": "Diese Aufgabe wird an den unten hervorgehobenen Tagen fällig werden. Klicke auf einen Tag um ihn zu aktivieren/deaktivieren.",
"repeatDays": "Alle X Tage",
"repeatWeek": "An bestimmten Tagen der Woche",
"day": "Tag",
"days": "Tage",
"restoreStreak": "Strähne wiederherstellen",
- "todo": "Aufgabe",
- "todos": "Aufgaben",
- "newTodo": "Neuer To-Do Eintrag",
+ "todo": "To-Do",
+ "todos": "To-Dos",
+ "newTodo": "Neuer To-Do-Eintrag",
"newTodoBulk": "Neue To-Do Einträge (einer pro Zeile)",
"dueDate": "Frist",
"remaining": "Aktiv",
@@ -73,6 +73,7 @@
"clearTags": "Aufräumen",
"hideTags": "Verbergen",
"showTags": "Anzeigen",
+ "toRequired": "Du musst einen gültigen Endwert eingeben",
"startDate": "Starttermin",
"startDateHelpTitle": "Wann soll diese Aufgabe beginnen?",
"startDateHelp": "Setze das Datum fest, ab dem diese Aufgabe in Kraft tritt. Davor wird sie nicht fällig werden.",
@@ -88,29 +89,43 @@
"fortifyName": "Verstärkungstrank",
"fortifyPop": "Setzt alle Aufgaben auf den Anfangswert (gelb) zurück und füllt Deine Lebenspunkte wieder auf.",
"fortify": "Verstärken",
- "fortifyText": "Stärken setzt alle Aufgaben auf einen neutralen (gelben) Status zurück, als hättest Du sie gerade erst hinzugefügt, und füllt Deine Gesundheit vollkommen auf. Das ist klasse, wenn Deine ganzen roten Aufgaben das Spiel zu schwer oder Deine ganzen blauen Aufgaben das Spiel zu einfach machen. Wenn ein neuer Start motivierender klingt, gib Deine Edelsteine aus und hol' erstmal Luft!",
+ "fortifyText": "Stärken setzt alle Deine Aufgaben ausser Wettbewerbs-Aufgaben auf einen neutralen Status (gelb) zurück, wie wenn Du sie gerade erstellt hättest, und füllt Deine Lebenspunkte wieder ganz auf. Das ist toll wenn all die roten Aufgaben Dir das Leben zu schwer machen, oder all die blauen Aufgaben das Spiel zu einfach erscheinen lassen. Wenn ein Neuanfang gerade den grössten Motivationsschub darstellt, gib ein paar Edelsteine aus und gönn' Dir eine Atempause!",
"confirmFortify": "Bist Du sicher?",
+ "fortifyComplete": "Verstärkung abgeschlossen!",
"sureDelete": "Willst Du <%= taskType %> mit dem Text \"<%= taskText %>\" wirklich löschen?",
"streakCoins": "Strähnenbonus!",
"pushTaskToTop": "Verschiebe die Aufgabe nach oben. Halte dabei strg oder cmd, um sie nach unten zu verschieben.",
"emptyTask": "Gib der Aufgabe zunächst einen Titel.",
- "dailiesRestingInInn": "Du ruhst Dich im Gasthaus aus! Deine täglichen Aufgaben werden dir heute Nacht keinen Schaden zufügen, sie werden sich aber dennoch täglich aktualisieren. Falls Du Dich in einer Quest befindest, wirst Du keinen Schaden austeilen/Gegenstände finden bis Du das Gasthaus wieder verlässt, allerdings kannst Du durch einen Boss verletzt werden, wenn Deine Gruppe tägliche Aufgaben nicht erledigt.",
+ "dailiesRestingInInn": "Du ruhst Dich im Gasthaus aus! Deine täglichen Aufgaben werden Dir heute Nacht keinen Schaden zufügen, sie werden sich aber dennoch täglich aktualisieren. Falls Du Dich in einer Quest befindest, wirst Du keinen Schaden austeilen/Gegenstände finden bis Du das Gasthaus wieder verlässt, allerdings kannst Du durch einen Boss verletzt werden, wenn Deine Gruppe tägliche Aufgaben nicht erledigt.",
"habitHelp1": "Gute Gewohnheiten sind Aktivitäten, die Du oft machen sollst. Sie bringen Gold und Erfahrung immer wenn Du auf <%= plusIcon %> klickst.",
- "habitHelp2": "Schlechte Gewohnheiten sind Aktivitäten, die Du vermeiden solltest. Sie ziehen dir jedes mal Leben ab, wenn Du auf <%= minusIcon %> klickst.",
- "habitHelp3": "Schau dir zur Inspiration diese Gewohnheitsbeispiele an!",
- "newbieGuild": "Du hast weitere Fragen? Du kannst sie in der <%= linkStart %>Newbies Gilde<%= linkEnd %> loswerden!",
+ "habitHelp2": "Schlechte Gewohnheiten sind Aktivitäten, die Du vermeiden solltest. Sie ziehen Dir jedes mal Leben ab, wenn Du auf <%= minusIcon %> klickst.",
+ "habitHelp3": "Schau Dir zur Inspiration diese Gewohnheitsbeispiele an!",
+ "newbieGuild": "Du hast weitere Fragen? Du kannst sie in der <%= linkStart %>Newbies-Gilde<%= linkEnd %> loswerden!",
"dailyHelp1": "Tägliche Aufgaben wiederholen sich an<%= emphasisStart %>allen Tagen<%= emphasisEnd %>, an denen sie aktiv sind. Klicke den <%= pencilIcon %>, um die Tage zu ändern, an denen eine tägliche Aufgabe aktiv ist.",
"dailyHelp2": "Wenn Du aktive tägliche Aufgaben nicht beendest, verlierst Du am Ende des Tages Gesundheit.",
"dailyHelp3": "Tägliche Aufgaben werden <%= emphasisStart %>röter<%= emphasisEnd %>, wenn Du sie nicht verpasst, und <%= emphasisStart %>blauer<%= emphasisEnd %>, wenn Du sie erledigst. Je röter die tägliche Aufgabe ist, desto größer fällt Deine Belohnung aus ... oder Deine Bestrafung.",
"dailyHelp4": "Um die Uhrzeit für den Beginn des neuen Tages festzulegen, gehe zu <%= linkStart %> Einstellungen > Seite<%= linkEnd %> > Tageswechsel einstellen.",
- "dailyHelp5": "Schau dir zur Inspiration diese Beispiele von täglichen Aufgaben an!",
+ "dailyHelp5": "Schau Dir zur Inspiration diese Beispiele täglicher Aufgaben an!",
"toDoHelp1": "Einmalige Aufgaben sind anfangs gelb, werden aber röter (wertvoller) je später sie beendet werden.",
- "toDoHelp2": "To-Dos werden dir niemals Schaden zufügen! Sie verleihen nur Gold und Erfahrung.",
- "toDoHelp3": "Einmalige Aufgaben mit der Kontrollliste in kleinere Teilaufgaben zu unterteilen macht sie weniger beängstigend, und wird Deine Punkte erhöhen!",
- "toDoHelp4": "Zur Inspiration kannst Du dir diese Beispiele von einmaligen Aufgaben durchsehen!",
+ "toDoHelp2": "To-Dos werden Dir niemals Schaden zufügen! Sie verleihen nur Gold und Erfahrung.",
+ "toDoHelp3": "To-Dos mit der Kontrollliste in kleinere Teilaufgaben zu unterteilen macht sie weniger beängstigend, und wird Deine Punkte erhöhen!",
+ "toDoHelp4": "Zur Inspiration kannst Du Dir diese Beispiele von To-Dos durchsehen!",
"rewardHelp1": "Die Ausrüstung, die Du für Deinen Avatar kaufst, ist unter <%= linkStart %>Inventar > Ausrüstung<%= linkEnd %> gelagert.",
"rewardHelp2": "Ausrüstungsgegenstände beeinflussen Deine Statuswerte (<%= linkStart %>Benutzer > Werte<%= linkEnd %>).",
- "rewardHelp3": "Während weltweiten Events wird hier Spezialausrüstung erscheinen.",
- "rewardHelp4": "Scheue Dich nicht davor eigene Belohnungen hinzuzufügen! Sieh dir einige Beispielbelohnungen durch.",
- "clickForHelp": "Klicke hier für Hilfe"
+ "rewardHelp3": "Während Welt-Events wird hier Spezialausrüstung erscheinen.",
+ "rewardHelp4": "Scheue Dich nicht davor eigene Belohnungen hinzuzufügen! Sieh Dir einige Beispielbelohnungen durch.",
+ "clickForHelp": "Klicke hier für Hilfe",
+ "taskIdRequired": "\"taskId\" muss eine gültige UUID sein.",
+ "taskNotFound": "Aufgabe nicht gefunden.",
+ "invalidTaskType": "Task-Typ muss eines der folgenden sein: \"Gewohnheit\", \"tägliche Aufgabe\", \"To-Do\", \"Belohnung\".",
+ "cantDeleteChallengeTasks": "Eine Aufgabe, die zu einem Wettbewerb gehört, kann nicht gelöscht werden.",
+ "checklistOnlyDailyTodo": "Checklisten werden nur in täglichen Aufgaben und To-Dos unterstützt.",
+ "checklistItemNotFound": "Es wurde kein Checklisten-Eintrag mit dieser ID gefunden.",
+ "itemIdRequired": "\"idemId\" muss eine gültige UUID sein.",
+ "tagNotFound": "Kein Tag mit dieser ID gefunden.",
+ "tagIdRequired": "\"tagId\" muss eine gültige UUID sein, passend zu einem dem Benutzer zugeordneten Tag.",
+ "positionRequired": "\"position\" wird benötigt und muss eine Zahl sein.",
+ "cantMoveCompletedTodo": "Abgeschlossenes To-Do kann nicht verschoben werden.",
+ "directionUpDown": "\"Richtung\" wird benötigt und muss 'up' oder 'down' sein.",
+ "alreadyTagged": "Die Aufgabe besitzt bereits dieses Tag."
}
\ No newline at end of file
diff --git a/common/locales/en/backgrounds.json b/common/locales/en/backgrounds.json
index 5033629a35..10038ce146 100644
--- a/common/locales/en/backgrounds.json
+++ b/common/locales/en/backgrounds.json
@@ -183,6 +183,21 @@
"backgroundGiantFlowersText": "Giant Flowers",
"backgroundGiantFlowersNotes": "Frolic atop Giant Flowers.",
"backgroundRainbowsEndText": "End of the Rainbow",
- "backgroundRainbowsEndNotes": "Discover gold at the End of the Rainbow."
-}
+ "backgroundRainbowsEndNotes": "Discover gold at the End of the Rainbow.",
+ "backgrounds052016": "SET 24: Released May 2016",
+ "backgroundBeehiveText": "Beehive",
+ "backgroundBeehiveNotes": "Buzz and dance in a Beehive.",
+ "backgroundGazeboText": "Gazebo",
+ "backgroundGazeboNotes": "Battle a Gazebo.",
+ "backgroundTreeRootsText": "Tree Roots",
+ "backgroundTreeRootsNotes": "Explore the Tree Roots.",
+
+ "backgrounds062016": "SET 25: Released June 2016",
+ "backgroundLighthouseShoreText": "Lighthouse Shore",
+ "backgroundLighthouseShoreNotes": "Stroll down the Lighthouse Shore.",
+ "backgroundLilypadText": "Lilypad",
+ "backgroundLilypadNotes": "Hop on a Lilypad.",
+ "backgroundWaterfallRockText": "Waterfall Rock",
+ "backgroundWaterfallRockNotes": "Splash on a Waterfall Rock."
+}
diff --git a/common/locales/en/challenge.json b/common/locales/en/challenge.json
index d82085f1c1..c014e1f6db 100644
--- a/common/locales/en/challenge.json
+++ b/common/locales/en/challenge.json
@@ -63,5 +63,21 @@
"congratulations": "Congratulations!",
"hurray": "Hurray!",
"noChallengeOwner": "no owner",
- "noChallengeOwnerPopover": "This challenge does not have an owner because the person who created the challenge deleted their account."
+ "noChallengeOwnerPopover": "This challenge does not have an owner because the person who created the challenge deleted their account.",
+ "challengeMemberNotFound": "User not found among challenge's members",
+ "onlyGroupLeaderChal": "Only the group leader can create challenges",
+ "tavChalsMinPrize": "Prize must be at least 1 Gem for Tavern challenges.",
+ "cantAfford": "You can't afford this prize. Purchase more gems or lower the prize amount.",
+ "challengeIdRequired": "\"challengeId\" must be a valid UUID.",
+ "winnerIdRequired": "\"winnerId\" must be a valid UUID.",
+ "challengeNotFound": "Challenge not found.",
+ "onlyLeaderDeleteChal": "Only the challenge leader can delete it.",
+ "onlyLeaderUpdateChal": "Only the challenge leader can update it.",
+ "winnerNotFound": "Winner with id \"<%= userId %>\" not found or not part of the challenge.",
+ "noCompletedTodosChallenge": "\"includeComepletedTodos\" is not supported when fetching challenge tasks.",
+ "userTasksNoChallengeId": "When \"tasksOwner\" is \"user\" \"challengeId\" can't be passed.",
+ "onlyChalLeaderEditTasks": "Tasks belonging to a challenge can only be edited by the leader.",
+ "userAlreadyInChallenge": "User is already participating in this challenge.",
+ "cantOnlyUnlinkChalTask": "Only broken challenges tasks can be unlinked.",
+ "shortNameTooShort": "Tag Name must have at least 3 characters."
}
diff --git a/common/locales/en/character.json b/common/locales/en/character.json
index 95e3221a86..50e3022b0c 100644
--- a/common/locales/en/character.json
+++ b/common/locales/en/character.json
@@ -1,4 +1,5 @@
{
+ "communityGuidelinesWarning": "Please keep in mind that your Display Name, profile photo, and blurb must comply with the Community Guidelines (e.g. no profanity, no adult topics, no insults, etc). If you have any questions about whether or not something is appropriate, feel free to email leslie@habitica.com!",
"statsAch": "Stats & Achievements",
"profile": "Profile",
"avatar": "Customize Avatar",
@@ -109,6 +110,7 @@
"mage": "Mage",
"mystery": "Mystery",
"changeClass": "Change Class, Refund Attribute Points",
+ "lvl10ChangeClass": "To change class you must be at least level 10.",
"levelPopover": "Each level earns you one point to assign to an attribute of your choice. You can do so manually, or let the game decide for you using one of the Automatic Allocation options.",
"unallocated": "Unallocated Attribute Points",
"haveUnallocated": "You have <%= points %> unallocated Attribute Point(s)",
@@ -164,5 +166,7 @@
"int": "INT",
"showQuickAllocation": "Show stat allocation",
"hideQuickAllocation": "Hide stat allocation",
- "quickAllocationLevelPopover": "Each level earns you one point to assign to an attribute of your choice. You can do so manually, or let the game decide for you using one of the Automatic Allocation options found in User -> Stats."
+ "quickAllocationLevelPopover": "Each level earns you one point to assign to an attribute of your choice. You can do so manually, or let the game decide for you using one of the Automatic Allocation options found in User -> Stats.",
+ "invalidAttribute": "\"<%= attr %>\" is not a valid attribute.",
+ "notEnoughAttrPoints": "You don't have enough attribute points."
}
diff --git a/common/locales/en/content.json b/common/locales/en/content.json
index 3eeedb30e4..ce73ded316 100644
--- a/common/locales/en/content.json
+++ b/common/locales/en/content.json
@@ -159,6 +159,14 @@
"questEggSnailMountText": "Snail",
"questEggSnailAdjective": "a slow but steady",
+ "questEggFalconText": "Falcon",
+ "questEggFalconMountText": "Falcon",
+ "questEggFalconAdjective": "a swift",
+
+ "questEggTreelingText": "Treeling",
+ "questEggTreelingMountText": "Treeling",
+ "questEggTreelingAdjective": "a leafy",
+
"eggNotes": "Find a hatching potion to pour on this egg, and it will hatch into <%= eggAdjective(locale) %> <%= eggText(locale) %>.",
"hatchingPotionBase": "Base",
@@ -173,6 +181,7 @@
"hatchingPotionGolden": "Golden",
"hatchingPotionSpooky": "Spooky",
"hatchingPotionPeppermint": "Peppermint",
+ "hatchingPotionFloral": "Floral",
"hatchingPotionNotes": "Pour this on an egg, and it will hatch as a <%= potText(locale) %> pet.",
"premiumPotionAddlNotes": "Not usable on quest pet eggs.",
@@ -214,5 +223,4 @@
"foodSaddleNotes": "Instantly raises one of your pets into a mount.",
"foodNotes": "Feed this to a pet and it may grow into a sturdy steed."
-
}
diff --git a/common/locales/en/contrib.json b/common/locales/en/contrib.json
index f309618458..f05e2f10e7 100644
--- a/common/locales/en/contrib.json
+++ b/common/locales/en/contrib.json
@@ -35,8 +35,12 @@
"hallContributors": "Hall of Contributors",
"hallPatrons": "Hall of Patrons",
"rewardUser": "Reward User",
- "UUID": "UUID",
+ "UUID": "User ID",
"loadUser": "Load User",
+ "noAdminAccess": "You don't have admin access.",
+ "pageMustBeNumber": "req.query.page must be a number",
+ "userNotFound": "User not found.",
+ "invalidUUID": "UUID must be valid",
"title": "Title",
"moreDetails": "More details (1-7)",
"moreDetails2": "more details (8-9)",
@@ -52,8 +56,8 @@
"visitHeroes": "Visit the Hall of Heroes (contributors and backers)",
"conLearn": "Learn more about contributor rewards",
"conLearnHow": "Learn how to contribute to Habitica",
- "surveysSingle": "Helped Habitica grow by filling out a survey. There are no active surveys.",
- "surveysMultiple": "Helped Habitica grow by filling out <%= surveys %> surveys. There are no active surveys.",
+ "surveysSingle": "Helped Habitica grow, either by filling out a survey or helping with a major testing effort. Thank you!",
+ "surveysMultiple": "Helped Habitica grow on <%= surveys %> occasions, either by filling out a survey or helping with a major testing effort. Thank you!",
"currentSurvey": "Current Survey",
"surveyWhen": "The badge will be awarded to all participants when surveys have been processed, in late March.",
"blurbInbox": "This is where your private messages are stored! You can send someone a message by clicking on the envelope icon next to their name in Tavern, Party, or Guild Chat. If you've received an inappropriate PM, you should email a screenshot of it to Lemoness (leslie@habitica.com)",
diff --git a/common/locales/en/death.json b/common/locales/en/death.json
index dc131e74f2..b6723981f2 100644
--- a/common/locales/en/death.json
+++ b/common/locales/en/death.json
@@ -12,6 +12,6 @@
"losingHealthQuickly": "Losing Health quickly?",
"lowHealthTips3": "Incomplete Dailies hurt you overnight, so be careful not to add too many at first!",
"lowHealthTips4": "If a Daily isn't due on a certain day, you can disable it by clicking the pencil icon.",
- "goodLuck": "Good luck!"
+ "goodLuck": "Good luck!",
+ "cannotRevive": "Cannot revive if not dead"
}
-
diff --git a/common/locales/en/front.json b/common/locales/en/front.json
index 704e708765..74591fa39b 100644
--- a/common/locales/en/front.json
+++ b/common/locales/en/front.json
@@ -28,6 +28,7 @@
"communityReddit": "Reddit",
"companyAbout": "How it Works",
"companyBlog": "Blog",
+ "devBlog": "Developer Blog",
"companyDonate": "Donate",
"companyExtensions": "Extensions",
"companyPrivacy": "Privacy",
@@ -51,6 +52,7 @@
"featureSocialHeading": "Social play",
"featuredIn": "Featured in",
"featuresHeading": "We also feature...",
+ "footerDevs": "Developers",
"footerCommunity": "Community",
"footerCompany": "Company",
"footerMobile": "Mobile",
@@ -182,6 +184,7 @@
"zelahQuote": "With [Habitica], I can be persuaded to go to bed on time by the thought of gaining points for an early night or losing health for a late one!",
"reportAccountProblems": "Report Account Problems",
"reportCommunityIssues": "Report Community Issues",
+ "subscriptionPaymentIssues": "Subscription and Payment Issues",
"generalQuestionsSite": "General Questions about the Site",
"businessInquiries": "Business Inquiries",
"merchandiseInquiries": "Merchandise Inquiries",
@@ -222,5 +225,32 @@
"altAttrWebstorm": "WebStorm",
"altAttrGithub": "GitHub",
"altAttrTrello": "Trello",
- "altAttrSlack": "Slack"
+ "altAttrSlack": "Slack",
+ "missingAuthHeaders": "Missing authentication headers.",
+ "missingAuthParams": "Missing authentication parameters.",
+ "missingUsernameEmail": "Missing username or email.",
+ "missingEmail": "Missing email.",
+ "missingUsername": "Missing username.",
+ "missingPassword": "Missing password.",
+ "missingNewPassword": "Missing new password.",
+ "wrongPassword": "Wrong password.",
+ "notAnEmail": "Invalid email address.",
+ "emailTaken": "Email address is already used in an account.",
+ "newEmailRequired": "Missing new email address.",
+ "usernameTaken": "Username already taken.",
+ "passwordConfirmationMatch": "Password confirmation doesn't match password.",
+ "invalidLoginCredentials": "Incorrect username and/or email and/or password.",
+ "passwordReset": "If we have your email on file, your password reset link has been sent to your email.",
+ "passwordResetEmailSubject": "Password Reset for Habitica",
+ "passwordResetEmailText": "Password for <%= username %> has been reset to <%= newPassword %> . Important! Both username and password are case-sensitive -- you must enter both exactly as shown here. We recommend copying and pasting both instead of typing them. Log in at <%= baseUrl %>. After you have logged in, head to <%= baseUrl %>/#/options/settings/settings and change your password.",
+ "passwordResetEmailHtml": "Password for <%= username %> has been reset to <%= newPassword %>.
Important! Both username and password are case-sensitive -- you must enter both exactly as shown here. We recommend copying and pasting both instead of typing them.
Log in at <%= baseUrl %>. After you have logged in, head to <%= baseUrl %>/#/options/settings/settings and change your password.",
+ "invalidLoginCredentialsLong": "Uh-oh - your username or password is incorrect.\n- Make sure your username or email is typed correctly.\n- You may have signed up with Facebook, not email. Double-check by trying Facebook login.\n- If you forgot your password, click \"Forgot Password\".",
+ "invalidCredentials": "There is no account that uses those credentials.",
+ "accountSuspended": "Account has been suspended, please contact leslie@habitica.com with your User ID \"<%= userId %>\" for assistance.",
+ "onlyFbSupported": "Only Facebook is supported currently.",
+ "cantDetachFb": "Account lacks another authentication method, can't detach Facebook.",
+ "onlySocialAttachLocal": "Local authentication can be added to only a social account.",
+ "invalidReqParams": "Invalid request parameters.",
+ "memberIdRequired": "\"member\" must be a valid UUID.",
+ "heroIdRequired": "\"heroId\" must be a valid UUID."
}
diff --git a/common/locales/en/gear.json b/common/locales/en/gear.json
index 0c7595006c..cf8c5ec8e1 100644
--- a/common/locales/en/gear.json
+++ b/common/locales/en/gear.json
@@ -208,6 +208,10 @@
"weaponArmoireMiningPickaxNotes": "Mine the maximum amount of gold from your tasks! Increases Perception by <%= per %>. Enchanted Armoire: Miner Set (Item 3 of 3).",
"weaponArmoireBasicLongbowText": "Basic Longbow",
"weaponArmoireBasicLongbowNotes": "A serviceable hand-me-down bow. Increases Strength by <%= str %>. Enchanted Armoire: Basic Archer Set (Item 1 of 3).",
+ "weaponArmoireHabiticanDiplomaText": "Habitican Diploma",
+ "weaponArmoireHabiticanDiplomaNotes": "A certificate of significant achievement -- well done! Increases Intelligence by <%= int %>. Enchanted Armoire: Graduate Set (Item 1 of 3).",
+ "weaponArmoireSandySpadeText": "Sandy Spade",
+ "weaponArmoireSandySpadeNotes": "A tool for digging, as well as flicking sand into the eyes of enemy monsters. Increases Strength by <%= str %>. Enchanted Armoire: Seaside Set (Item 1 of 3).",
"armor": "armor",
@@ -402,6 +406,10 @@
"armorMystery201512Notes": "Summon the icy flames of winter! Confers no benefit. December 2015 Subscriber Item.",
"armorMystery201603Text": "Lucky Suit",
"armorMystery201603Notes": "This suit is sewn from thousands of four-leafed clovers! Confers no benefit. March 2016 Subscriber Item.",
+ "armorMystery201604Text": "Armor o' Leaves",
+ "armorMystery201604Notes": "You, too, can be a small but fearsome leaf puff. Confers no benefit. April 2016 Subscriber Item.",
+ "armorMystery201605Text": "Marching Bard Uniform",
+ "armorMystery201605Notes": "Unlike the traditional bards who join adventuring parties, bards who join Habitican marching bands are known for grand parades, not dungeon raids. Confers no benefit. May 2016 Subscriber Item.",
"armorMystery301404Text": "Steampunk Suit",
"armorMystery301404Notes": "Dapper and dashing, wot! Confers no benefit. February 3015 Subscriber Item.",
@@ -433,6 +441,10 @@
"armorArmoireMinerOverallsNotes": "They may seem worn, but they are enchanted to repel dirt. Increases Constitution by <%= con %>. Enchanted Armoire: Miner Set (Item 2 of 3).",
"armorArmoireBasicArcherArmorText": "Basic Archer Armor",
"armorArmoireBasicArcherArmorNotes": "This camouflaged vest lets you slip unnoticed through the forests. Increases Perception by <%= per %>. Enchanted Armoire: Basic Archer Set (Item 2 of 3).",
+ "armorArmoireGraduateRobeText": "Graduate Robe",
+ "armorArmoireGraduateRobeNotes": "Congratulations! This weighty robe hangs heavy with all the knowledge you have accrued. Increases Intelligence by <%= int %>. Enchanted Armoire: Graduate Set (Item 2 of 3).",
+ "armorArmoireStripedSwimsuitText": "Striped Swimsuit",
+ "armorArmoireStripedSwimsuitNotes": "What could be more fun than battling sea monsters on the beach? Increases Constitution by <%= con %>. Enchanted Armoire: Seaside Set (Item 2 of 3).",
"headgear": "headgear",
@@ -623,6 +635,10 @@
"headMystery201602Notes": "Shield your identity from all your admirers. Confers no benefit. February 2016 Subscriber Item.",
"headMystery201603Text": "Lucky Hat",
"headMystery201603Notes": "This top hat is a magical good-luck charm. Confers no benefit. March 2016 Subscriber Item.",
+ "headMystery201604Text": "Crown o' Flowers",
+ "headMystery201604Notes": "These woven flowers make a surprisingly strong helm! Confers no benefit. April 2016 Subscriber Item.",
+ "headMystery201605Text": "Marching Bard Hat",
+ "headMystery201605Notes": "Seventy-six dragons led the big parade, with a hundred and ten gryphons close at hand! Confers no benefit. May 2016 Subscriber Item.",
"headMystery301404Text": "Fancy Top Hat",
"headMystery301404Notes": "A fancy top hat for the finest of gentlefolk! January 3015 Subscriber Item. Confers no benefit.",
"headMystery301405Text": "Basic Top Hat",
@@ -672,6 +688,10 @@
"headArmoireMinerHelmetNotes": "Protect your head from falling tasks! Increases Intelligence by <%= int %>. Enchanted Armoire: Miner Set (Item 1 of 3).",
"headArmoireBasicArcherCapText": "Basic Archer Cap",
"headArmoireBasicArcherCapNotes": "No archer would be complete without a jaunty cap! Increases Perception by <%= per %>. Enchanted Armoire: Basic Archer Set (Item 3 of 3).",
+ "headArmoireGraduateCapText": "Graduate Cap",
+ "headArmoireGraduateCapNotes": "Congratulations! Your deep thoughts have earned you this thinking cap. Increases Intelligence by <%= int %>. Enchanted Armoire: Graduate Set (Item 3 of 3).",
+ "headArmoireGreenFloppyHatText": "Green Floppy Hat",
+ "headArmoireGreenFloppyHatNotes": "Many spells have been sewn into this simple hat, giving it a gorgeous green color. Increases Constitution, Intelligence, and Perception by <%= attrs %> each. Enchanted Armoire: Independent Item.",
"offhand": "shield-hand item",
@@ -744,7 +764,7 @@
"shieldSpecialWinter2015HealerNotes": "This shield deflects the freezing wind. Increases Constitution by <%= con %>. Limited Edition 2014-2015 Winter Gear.",
"shieldSpecialSpring2015RogueText": "Exploding Squeak",
- "shieldSpecialSpring2015RogueNotes": "Don't let the sound fool you - these explosives pack a punch. Increases Strength <%= str %>. Limited Edition 2015 Spring Gear.",
+ "shieldSpecialSpring2015RogueNotes": "Don't let the sound fool you - these explosives pack a punch. Increases Strength by <%= str %>. Limited Edition 2015 Spring Gear.",
"shieldSpecialSpring2015WarriorText": "Dish Discus",
"shieldSpecialSpring2015WarriorNotes": "Hurl it at your enemies.... or just hold it, because it will fill up with yummy kibble at dinnertime. Increases Constitution by <%= con %>. Limited Edition 2015 Spring Gear.",
"shieldSpecialSpring2015HealerText": "Patterned Pillow",
@@ -793,6 +813,10 @@
"shieldArmoireDragonTamerShieldNotes": "Distract enemies with this dragon-shaped shield. Increases Perception by <%= per %>. Enchanted Armoire: Dragon Tamer Set (Item 2 of 3).",
"shieldArmoireMysticLampText": "Mystic Lamp",
"shieldArmoireMysticLampNotes": "Light the darkest caves with this mystic lamp! Increases Perception by <%= per %>. Enchanted Armoire: Independent Item.",
+ "shieldArmoireFloralBouquetText": "Bouquet o' Flowers",
+ "shieldArmoireFloralBouquetNotes": "Not much help in battle, but aren't they beautiful? Increases Constitution by <%= con %>. Enchanted Armoire: Independent Item.",
+ "shieldArmoireSandyBucketText": "Sandy Bucket",
+ "shieldArmoireSandyBucketNotes": "Good for storing all that Gold that you'll earn from completing tasks! Increases Perception by <%= per %>. Enchanted Armoire: Seaside Set (Item 3 of 3).",
"back": "Back Accessory",
"backBase0Text": "No Back Accessory",
@@ -914,6 +938,21 @@
"eyewearBase0Text": "No Eyewear",
"eyewearBase0Notes": "No Eyewear.",
+ "eyewearSpecialBlackTopFrameText": "Black Standard Eyeglasses",
+ "eyewearSpecialBlackTopFrameNotes": "Glasses with a black frame above the lenses. Confers no benefit.",
+ "eyewearSpecialBlueTopFrameText": "Blue Standard Eyeglasses",
+ "eyewearSpecialBlueTopFrameNotes": "Glasses with a blue frame above the lenses. Confers no benefit.",
+ "eyewearSpecialGreenTopFrameText": "Green Standard Eyeglasses",
+ "eyewearSpecialGreenTopFrameNotes": "Glasses with a green frame above the lenses. Confers no benefit.",
+ "eyewearSpecialPinkTopFrameText": "Pink Standard Eyeglasses",
+ "eyewearSpecialPinkTopFrameNotes": "Glasses with a pink frame above the lenses. Confers no benefit.",
+ "eyewearSpecialRedTopFrameText": "Red Standard Eyeglasses",
+ "eyewearSpecialRedTopFrameNotes": "Glasses with a red frame above the lenses. Confers no benefit.",
+ "eyewearSpecialWhiteTopFrameText": "White Standard Eyeglasses",
+ "eyewearSpecialWhiteTopFrameNotes": "Glasses with a white frame above the lenses. Confers no benefit.",
+ "eyewearSpecialYellowTopFrameText": "Yellow Standard Eyeglasses",
+ "eyewearSpecialYellowTopFrameNotes": "Glasses with a yellow frame above the lenses. Confers no benefit.",
+
"eyewearSpecialSummerRogueText": "Roguish Eyepatch",
"eyewearSpecialSummerRogueNotes": "It doesn't take a scallywag to see how stylish this is! Confers no benefit. Limited Edition 2014 Summer Gear.",
"eyewearSpecialSummerWarriorText": "Dashing Eyepatch",
diff --git a/common/locales/en/groups.json b/common/locales/en/groups.json
index 901c813d96..cc9bcf9cd7 100644
--- a/common/locales/en/groups.json
+++ b/common/locales/en/groups.json
@@ -15,8 +15,8 @@
"resources": "Resources",
"askQuestionNewbiesGuild": "Ask a Question (Newbies Guild)",
"tavernTalk": "Tavern Talk",
- "tavernAlert1": "Note: if you're reporting a bug, the developers won't see it here. Please",
- "tavernAlert2": "use GitHub instead",
+ "tavernAlert1": "To report a bug, visit",
+ "tavernAlert2": "the Report a Bug Guild",
"moderatorIntro1": "Tavern and guild moderators are: ",
"communityGuidelines": "Community Guidelines",
"communityGuidelinesRead1": "Please read our",
@@ -92,6 +92,7 @@
"send": "Send",
"messageSentAlert": "Message sent",
"pmHeading": "Private message to <%= name %>",
+ "pmsMarkedRead": "Your private messages have been marked as read",
"clearAll": "Delete All Messages",
"confirmDeleteAllMessages": "Are you sure you want to delete all messages in your inbox? Other users will still see messages you have sent to them.",
"optOutPopover": "Don't like private messages? Click to completely opt out",
@@ -99,6 +100,15 @@
"unblock": "Un-block",
"pm-reply": "Send a reply",
"inbox": "Inbox",
+ "messageRequired": "A message is required.",
+ "toUserIDRequired": "A User ID is required",
+ "gemAmountRequired": "A number of gems is required",
+ "notAuthorizedToSendMessageToThisUser": "Can't send message to this user.",
+ "privateMessageGiftIntro": "Hello <%= receiverName %>, <%= senderName %> has sent you ",
+ "privateMessageGiftGemsMessage": "<%= gemAmount %> gems! ",
+ "privateMessageGiftSubscriptionMessage": "<%= numberOfMonths %> months of subscription! ",
+ "cannotSendGemsToYourself": "Cannot send gems to yourself. Try a subscription instead.",
+ "badAmountOfGemsToSend": "Amount must be within 1 and your current number of gems.",
"abuseFlag": "Report violation of Community Guidelines",
"abuseFlagModalHeading": "Report <%= name %> for violation?",
"abuseFlagModalBody": "Are you sure you want to report this post? You should ONLY report a post that violates the <%= firstLinkStart %>Community Guidelines<%= linkEnd %> and/or <%= secondLinkStart %>Terms of Service<%= linkEnd %>. Inappropriately reporting a post is a violation of the Community Guidelines and may give you an infraction. Appropriate reasons to flag a post include but are not limited to:
swearing, religous oaths
bigotry, slurs
adult topics
violence, including as a joke
spam, nonsensical messages
",
@@ -151,6 +161,30 @@
"partyUpName": "Party Up",
"partyOnName": "Party On",
"partyUpAchievement": "Joined a Party with another person! Have fun battling monsters and supporting each other.",
- "partyOnAchievement": "Joined a Party with at least four people! Enjoy your increased accountability as you unite with your friends to vanquish your foes!"
+ "partyOnAchievement": "Joined a Party with at least four people! Enjoy your increased accountability as you unite with your friends to vanquish your foes!",
+ "groupIdRequired": "\"groupId\" must be a valid UUID",
+ "groupNotFound": "Group not found.",
+ "groupTypesRequired": "You must supply a valid \"type\" query string.",
+ "questLeaderCannotLeaveGroup": "You cannot leave your party when you have started a quest. Abort the quest first.",
+ "cannotLeaveWhileActiveQuest": "You cannot leave party during an active quest. Please leave the quest first.",
+ "onlyLeaderCanRemoveMember": "Only group leader can remove a member!",
+ "memberCannotRemoveYourself": "You cannot remove yourself!",
+ "groupMemberNotFound": "User not found among group's members",
+ "mustBeGroupMember": "Must be member of the group.",
+ "keepOrRemoveAll": "req.query.keep must be either \"keep-all\" or \"remove-all\"",
+ "keepOrRemove": "req.query.keep must be either \"keep\" or \"remove\"",
+ "canOnlyInviteEmailUuid": "Can only invite using uuids or emails.",
+ "inviteMissingEmail": "Missing email address in invite.",
+ "partyMustbePrivate": "Parties must be private",
+ "userAlreadyInGroup": "User already in that group.",
+ "cannotInviteSelfToGroup": "You cannot invite yourself to a group.",
+ "userAlreadyInvitedToGroup": "User already invited to that group.",
+ "userAlreadyPendingInvitation": "User already pending invitation.",
+ "userAlreadyInAParty": "User already in a party.",
+ "userWithIDNotFound": "User with id \"<%= userId %>\" not found.",
+ "userHasNoLocalRegistration": "User does not have a local registration (username, email, password).",
+ "uuidsMustBeAnArray": "User ID invites must be an array.",
+ "emailsMustBeAnArray": "Email address invites must be an array.",
+ "canOnlyInviteMaxInvites": "You can only invite \"<%= maxInvites %>\" at a time",
+ "onlyCreatorOrAdminCanDeleteChat": "Not authorized to delete this message!"
}
-
diff --git a/common/locales/en/limited.json b/common/locales/en/limited.json
index 687d0e607e..d9f3ff5ba0 100644
--- a/common/locales/en/limited.json
+++ b/common/locales/en/limited.json
@@ -5,7 +5,7 @@
"annoyingFriends": "Annoying Friends",
"annoyingFriendsText": "Got snowballed <%= snowballs %> times by party members.",
"alarmingFriends": "Alarming Friends",
- "alarmingFriendsText": "Got spooked <%= spookDust %> times by party members.",
+ "alarmingFriendsText": "Got spooked <%= spookySparkles %> times by party members.",
"agriculturalFriends": "Agricultural Friends",
"agriculturalFriendsText": "Got transformed into a flower <%= seeds %> times by party members.",
"aquaticFriends": "Aquatic Friends",
@@ -72,5 +72,6 @@
"comfortingKittySet": "Comforting Kitty (Healer)",
"sneakySqueakerSet": "Sneaky Squeaker (Rogue)",
"fallEventAvailability": "Available until October 31",
- "winterEventAvailability": "Available until December 31"
+ "winterEventAvailability": "Available until December 31",
+ "springEventAvailability": "Available until May 31"
}
diff --git a/common/locales/en/maintenance.json b/common/locales/en/maintenance.json
new file mode 100644
index 0000000000..20b3410de2
--- /dev/null
+++ b/common/locales/en/maintenance.json
@@ -0,0 +1,34 @@
+{
+ "habiticaBackSoon": "Don't worry, Habitica will be back soon!",
+ "importantMaintenance": "We are doing important maintenance that we estimate will last until 10pm Pacific Time (5am UTC).",
+ "maintenance": "Maintenance",
+ "maintenanceMoreInfo": "Want more information about the maintenance? <%= linkStart %>Check out our info page<%= linkEnd %>.",
+ "noDamageKeepStreaks": "You will NOT take damage or lose streaks!",
+ "thanksForPatience": "Thanks for your patience!",
+ "twitterMaintenanceUpdates": "For the most recent updates, watch our Twitter, where we will be posting status information.",
+ "veteranPetAward": "At the end, you will receive a Veteran pet!",
+
+ "maintenanceInfoTitle": "Information about Upcoming Maintenance to Habitica",
+ "maintenanceInfoWhat": "What is happening?",
+ "maintenanceInfoWhatText": "On May 21, Habitica will be down for maintenance for most of the day. You will not take any damage or have your account harmed during that weekend, even if you can’t log in to check off your Dailies in time! We will be working very hard to make the downtime as short as possible, and will be posting regular updates on our Twitter account. At the end of the downtime, to thank everyone for their patience, you will all receive a rare pet!",
+ "maintenanceInfoWhy": "Why is this happening?",
+ "maintenanceInfoWhyText": "For the past several months, we have been thoroughly revamping Habitica behind-the-scenes. Specifically, we have rewritten the API. While it may not look much different on the surface, it’s a whole new world underneath. This will allow us WAY more flexibility when we want to build features in the future, and lead to improved performance!",
+ "maintenanceInfoTechDetails": "Want more details on the technical side of the process? Visit The Forge, our dev blog.",
+ "maintenanceInfoMore": "More Information",
+ "maintenanceInfoAccountChanges": "What changes will I see to my account after the rewrite is complete?",
+ "maintenanceInfoAccountChangesText": "At first, there won’t be any notable changes aside from performance improvements for features such as Challenges. If you notice any changes that shouldn’t be there, email us at admin@habitica.com and we will investigate them for you!",
+ "maintenanceInfoAddFeatures": "What kind of features will this allow Habitica to add?",
+ "maintenanceInfoAddFeaturesText": "Completing this rewrite will allow us to start building out improved chat and Guilds, plans for organizations and families, and additional productivity features like Monthlies and the ability to record yesterday’s activity! Those are all involved features on their own, so it will take time to build them, but until we were finished with this rewrite, there was no way we could start them.",
+ "maintenanceInfoHowLong": "How long will the maintenance take?",
+ "maintenanceInfoHowLongText": "We have to migrate tasks and data for all 1.3 million Habitica users -- not an easy task! We anticipate that it will take place between approximately 1pm Pacific Time (8pm UTC) and 10pm Pacific Time (5am UTC). Rest assured that we’re doing everything we can to make it go as quickly as possible! You can follow updates on our Twitter.",
+ "maintenanceInfoStatsAffected": "How will my Dailies, Streaks, Buffs, and Quests be affected?",
+ "maintenanceInfoStatsAffectedText1": "You will NOT take any damage or lose any streaks that weekend, but otherwise, your day will reset normally! Dailies that you checked will become unchecked, buffs will reset, etc. If you are in a Collection Quest, you will still find items. If you are in a Boss Battle, you will still deal damage to the Boss, but the Boss will not deal damage to you. (Even monsters need a break!)",
+ "maintenanceInfoStatsAffectedText2": "After a lot of thought, our team concluded that this was the most fair way to handle the fact that many users will not be able to check off their Dailies normally during the maintenance. We’re sorry for any inconvenience this causes!",
+ "maintenanceInfoSeeTasks": "What if I need to see my task list?",
+ "maintenanceInfoSeeTasksText": "If you know that you will need to see your task list on Saturday to remind yourself what you have to do, we recommend that before the maintenance begins, you take a screenshot of your tasks so that you can use it as a reference.",
+ "maintenanceInfoRarePet": "What kind of rare pet will I receive?",
+ "maintenanceInfoRarePetText": "To thank you for your patience during the downtime, everyone will get a rare Veteran Pet. If you’ve never received a Veteran Pet before, you will receive a Veteran Wolf. If you already have a Veteran Wolf, you will receive a Veteran Tiger. And if you already have a Veteran Wolf and a Veteran Tiger, you will receive a never-before-seen Veteran pet! After the migration is completed, it may take several hours for your pet to show up, but never fear, everyone will get one.",
+ "maintenanceInfoWho": "Who worked on this massive project?",
+ "maintenanceInfoWhoText": "We’re glad you asked! It was spearheaded by our amazing contributor paglias, with lots of help from Blade, TheHollidayInn, SabreCat, Victor Pudeyev, TheUnknown, and Alys.",
+ "maintenanceInfoTesting": "The new version was also tirelessly tested by a bunch of our amazing open-source volunteers. Thank you -- we couldn't have done this without you."
+}
diff --git a/common/locales/en/npc.json b/common/locales/en/npc.json
index 656b0c5e35..55ad9377c4 100644
--- a/common/locales/en/npc.json
+++ b/common/locales/en/npc.json
@@ -21,6 +21,28 @@
"ian": "Ian",
"ianText": "Welcome to the Quest Shop! Here you can use Quest Scrolls to battle monsters with your friends. Be sure to check out our fine array of Quest Scrolls for purchase on the right!",
"ianBrokenText": "Welcome to the Quest Shop... Here you can use Quest Scrolls to battle monsters with your friends... Be sure to check out our fine array of Quest Scrolls for purchase on the right...",
+
+ "missingKeyParam": "\"req.params.key\" is required.",
+ "itemNotFound": "Item \"<%= key %>\" not found.",
+ "cannotBuyItem": "You can't buy this item.",
+ "missingTypeKeyEquip": "\"key\" and \"type\" are required parameters.",
+ "missingPetFoodFeed": "\"pet\" and \"food\" are required parameters.",
+ "invalidPetName": "Invalid pet name supplied.",
+ "missingEggHatchingPotionHatch": "\"egg\" and \"hatchingPotion\" are required parameters.",
+ "invalidTypeEquip": "\"type\" must be one of 'equipped', 'pet', 'mount', 'costume'.",
+ "mustPurchaseToSet": "Must purchase <%= val %> to set it on <%= key %>.",
+ "typeRequired": "Type is required",
+ "keyRequired": "Key is required",
+ "notAccteptedType": "Type must be in [eggs, hatchingPotions, food, quests, gear]",
+ "contentKeyNotFound": "Key not found for Content <%= type %>",
+ "plusOneGem": "+1 Gem",
+ "typeNotSellable": "Type is not sellable. Must be one of the following <%= acceptedTypes %>",
+ "userItemsKeyNotFound": "Key not found for user.items <%= type %>",
+ "pathRequired": "Path string is required",
+ "unlocked": "Items have been unlocked",
+ "alreadyUnlocked": "Full set already unlocked.",
+ "alreadyUnlockedPart": "Full set already partially unlocked.",
+
"USD": "(USD)",
"newStuff": "New Stuff",
"cool": "Tell Me Later",
@@ -67,6 +89,7 @@
"tourPetsPage": "This is the Stable! After level 3, you can hatch pets using eggs and potions. When you hatch a pet in the Market, it will appear here! Click a pet's image to add it to your avatar. Feed them with the food you find after level 3, and they'll grow into powerful mounts.",
"tourMountsPage": "Once you've fed a pet enough food to turn it into a mount, it will appear here. (Pets, mounts, and food are available after level 3.) Click a mount to saddle up!",
"tourEquipmentPage": "This is where your Equipment is stored! Your Battle Gear affects your stats. If you want to show different Equipment on your avatar without changing your stats, click \"Enable Costume.\"",
+ "equipmentAlreadyOwned": "You already own that piece of equipment",
"tourOkay": "Okay!",
"tourAwesome": "Awesome!",
diff --git a/common/locales/en/pets.json b/common/locales/en/pets.json
index 56adb53262..7d13c8bc53 100644
--- a/common/locales/en/pets.json
+++ b/common/locales/en/pets.json
@@ -12,6 +12,7 @@
"etherealLion": "Ethereal Lion",
"veteranWolf": "Veteran Wolf",
"veteranTiger": "Veteran Tiger",
+ "veteranLion": "Veteran Lion",
"cerberusPup": "Cerberus Pup",
"hydra": "Hydra",
"mantisShrimp": "Mantis Shrimp",
@@ -19,7 +20,7 @@
"orca": "Orca",
"royalPurpleGryphon": "Royal Purple Gryphon",
"phoenix": "Phoenix",
- "bumblebee": "Bumblebee",
+ "magicalBee": "Magical Bee",
"rarePetPop1": "Click the gold paw to learn more about how you can obtain this rare pet through contributing to Habitica!",
"rarePetPop2": "How to Get this Pet!",
"potion": "<%= potionType %> Potion",
@@ -62,6 +63,7 @@
"hatchedPet": "You hatched a <%= potion %> <%= egg %>!",
"displayNow": "Display Now",
"displayLater": "Display Later",
+ "petNotOwned": "You do not own this pet.",
"earnedCompanion": "With all your productivity, you've earned a new companion. Feed it to make it grow!",
"feedPet": "Feed <%= article %><%= text %> to your <%= name %>?",
"useSaddle": "Saddle <%= pet %>?",
@@ -83,5 +85,8 @@
"petKeyBoth": "Release Both",
"confirmPetKey": "Are you sure?",
"petKeyNeverMind": "Not Yet",
+ "petsReleased": "Pets released.",
+ "mountsAndPetsReleased": "Mounts and pets released",
+ "mountsReleased": "Mounts released",
"gemsEach": "gems each"
}
diff --git a/common/locales/en/quests.json b/common/locales/en/quests.json
index 98b3a10b52..307c5bbf6b 100644
--- a/common/locales/en/quests.json
+++ b/common/locales/en/quests.json
@@ -78,5 +78,25 @@
"whichQuestStart": "Which quest do you want to start?",
"getMoreQuests": "Get more quests",
"unlockedAQuest": "You unlocked a quest!",
- "leveledUpReceivedQuest": "You leveled up to Level <%= level %> and received a quest scroll!"
+ "leveledUpReceivedQuest": "You leveled up to Level <%= level %> and received a quest scroll!",
+ "questInvitationDoesNotExist": "No quest invitation has been sent out yet.",
+ "questInviteNotFound": "No quest invitation found.",
+ "guildQuestsNotSupported": "Guilds cannot be invited on quests.",
+ "questNotFound": "Quest \"<%= key %>\" not found.",
+ "questNotOwned": "You don't own that quest scroll.",
+ "questNotGoldPurchasable": "Quest \"<%= key %>\" is not a Gold-purchasable quest.",
+ "questLevelTooHigh": "You must be level <%= level %> to begin this quest.",
+ "questAlreadyUnderway": "Your party is already on a quest. Try again when the current quest has ended.",
+ "questAlreadyAccepted": "You already accepted the quest invitation.",
+ "noActiveQuestToLeave": "No active quest to leave",
+ "questLeaderCannotLeaveQuest": "Quest leader cannot leave quest",
+ "notPartOfQuest": "You are not part of the quest",
+ "noActiveQuestToAbort": "There is no active quest to abort.",
+ "onlyLeaderAbortQuest": "Only the group or quest leader can abort a quest.",
+ "questAlreadyRejected": "You already rejected the quest invitation.",
+ "cantCancelActiveQuest": "You can not cancel an active quest, use the abort functionality.",
+ "onlyLeaderCancelQuest": "Only the group or quest leader can cancel the quest.",
+ "questInvitationDoesNotExist": "No quest invitation has been sent out yet.",
+ "questNotPending": "There is no quest to start.",
+ "questOrGroupLeaderOnlyStartQuest": "Only the quest leader or group leader can force start the quest"
}
diff --git a/common/locales/en/questsContent.json b/common/locales/en/questsContent.json
index c9657e5f3b..e1cfc8a9b6 100644
--- a/common/locales/en/questsContent.json
+++ b/common/locales/en/questsContent.json
@@ -354,9 +354,23 @@
"questBewilderCompletionChat": "`The Be-Wilder is DEFEATED!`\n\nWe've done it! The Be-Wilder lets out a ululating cry as it twists in the air, shedding feathers like falling rain. Slowly, gradually, it coils into a cloud of sparkling mist. As the newly-revealed sun pierces the fog, it burns away, revealing the coughing, mercifully human forms of Bailey, Matt, Alex.... and the April Fool himself.\n\n`Mistiflying is saved!`\n\nThe April Fool has enough shame to look a bit sheepish. “Oh, hm,” he says. “Perhaps I got a little…. carried away.”\n\nThe crowd mutters. Sodden flowers wash up on sidewalks. Somewhere in the distance, a roof collapses with a spectacular splash.\n\n“Er, yes,” the April Fool says. “That is. What I meant to say was, I’m dreadfully sorry.” He heaves a sigh. “I suppose it can’t all be fun and games, after all. It might not hurt to focus occasionally. Maybe I’ll get a head start on next year’s pranking.”\n\nRedphoenix coughs meaningfully.\n\n“I mean, get a head start on this year’s spring cleaning!” the April Fool says. “Nothing to fear, I’ll have Habit City in spit-shape soon. Luckily nobody is better than I at dual-wielding mops.”\n\nEncouraged, the marching band starts up.\n\nIt isn’t long before all is back to normal in Habit City. Plus, now that the Be-Wilder has evaporated, the magical bees of Mistiflying bustle back to work, and soon the flowers are blooming and the city is floating once more.\n\nAs Habiticans cuddle the magical fuzzy bees, the April Fool’s eyes light up. “Oho, I’ve had a thought! Why don’t you all keep some of these fuzzy Bee Pets and Mounts? It’s a gift that perfectly symbolizes the balance between hard work and sweet rewards, if I’m going to get all boring and allegorical on you.” He winks. “Besides, they don’t have stingers! Fool’s honor.”",
"questBewilderBossRageTitle": "Beguilement Strike",
"questBewilderBossRageDescription": "When this gauge fills, The Be-Wilder will unleash its Beguilement Strike on Habitica!",
- "questBewilderDropBumblebeePet": "Bumblebee (Pet)",
- "questBewilderDropBumblebeeMount": "Bumblebee (Mount)",
+ "questBewilderDropBumblebeePet": "Magical Bee (Pet)",
+ "questBewilderDropBumblebeeMount": "Magical Bee (Mount)",
"questBewilderBossRageMarket": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nOh no! Despite our best efforts, we've gotten distracted by the Be-Wilder’s charming illusions and have forgotten to do some of our Dailies! With a cackling cry, the shining bird beats its wings, raising a swarm of mist around Alex the Merchant. When the fog clears, he has been possessed! “Have some free samples!” he shouts gleefully, and begins to hurl exploding eggs and potions at fleeing Habiticans. Not the most favorable of sales, to be sure.\n\nHurry! Let's stay focused on our Dailies to defeat this monster before it possesses someone else.",
"questBewilderBossRageStables": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nAhh!!! Once again the Be-Wilder has dazzled us into neglecting our Dailies, and now it has attacked Matt the Beast Master! With a swirl of mist, Matt transforms into a terrifying winged creature, and all the pets and mounts howl sadly in their stables. Quickly, stay focused on your tasks to defeat this dastardly distraction!",
- "questBewilderBossRageBailey": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nLook out! In the middle of reporting the news, Bailey the Town Crier has been possessed by the Be-Wilder! She lets out an evil, uninformative screech as she rises into the air. Now how will we know what’s going on?\n\nDon't give up... we're so close to defeating this bothersome bird for once and for all!"
+ "questBewilderBossRageBailey": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nLook out! In the middle of reporting the news, Bailey the Town Crier has been possessed by the Be-Wilder! She lets out an evil, uninformative screech as she rises into the air. Now how will we know what’s going on?\n\nDon't give up... we're so close to defeating this bothersome bird for once and for all!",
+
+ "questFalconText": "The Birds of Preycrastination",
+ "questFalconNotes": "Mt. Habitica is being overshadowed by a looming mountain of To-Dos. It used to be a place to picnic and enjoy a sense of accomplishment, until the neglected tasks grew out of control. Now it's home to fearsome Birds of Preycrastination, foul creatures which stop Habiticans from completing their tasks!
\"It's too hard!\" they caw at @JonArinbjorn and @Onheiron. \"It'll take too long to do right now! It won't make any difference if you wait until tomorrow! Why don't you do something fun instead?\"
No more, you vow. You will climb your personal mountain of To-Dos and defeat the Birds of Preycrastination!",
+ "questFalconCompletion": "Having finally triumphed over the Birds of Preycrastination, you settle down to enjoy the view and your well-earned rest.
\"Wow!\" says @Trogdorina. \"You won!\"
@Squish adds, \"Here, take these eggs I found as a reward.\"",
+ "questFalconBoss": "Birds of Preycrastination",
+ "questFalconDropFalconEgg": "Falcon (Egg)",
+ "questFalconUnlockText": "Unlocks purchasable Falcon eggs in the Market",
+
+ "questTreelingText": "The Tangle Tree",
+ "questTreelingNotes": "It's the annual Garden Competition, and everyone is talking about the mysterious project which @aurakami has promised to unveil. You join the crowd on the day of the big announcement, and marvel at the introduction of a moving tree. @fuzzytrees explains that the tree will help with garden maintenance, showing how it can mow the lawn, trim the hedge and prune the roses all at the same time – until the tree suddenly goes wild, turning its secateurs on its creator! The crowd panics as everyone tries to flee, but you aren't afraid – you leap forward, ready to do battle.",
+ "questTreelingCompletion": "You dust yourself off as the last few leaves drift to the floor. In spite of the upset, the Garden Competition is now safe – although the tree you just reduced to a heap of wood chips won't be winning any prizes! \"Still a few kinks to work out there,\" @PainterProphet says. \"Perhaps someone else would do a better job of training the saplings. Do you fancy a go?\"",
+ "questTreelingBoss": "Tangle Tree",
+ "questTreelingDropTreelingEgg": "Treeling (Egg)",
+ "questTreelingUnlockText": "Unlocks purchasable Treeling eggs in the Market"
}
diff --git a/common/locales/en/rebirth.json b/common/locales/en/rebirth.json
index c158232395..f56c9d6279 100644
--- a/common/locales/en/rebirth.json
+++ b/common/locales/en/rebirth.json
@@ -5,7 +5,7 @@
"rebirthStartOver": "Rebirth starts your character over from Level 1.",
"rebirthAdvList1": "You return to full Health.",
"rebirthAdvList2": "You have no Experience, Gold, or Equipment (with the exception of free items like Mystery items).",
- "rebirthAdvList3": "Your Habits, Dailies, and To-Dos reset to yellow, and streaks reset.",
+ "rebirthAdvList3": "Your Habits, Dailies, and To-Dos reset to yellow, and streaks reset, except for challenge tasks.",
"rebirthAdvList4": "You have the starting class of Warrior until you earn a new class.",
"rebirthInherit": "Your new character inherits a few things from their predecessor:",
"rebirthInList1": "Tasks, history, and settings remain.",
@@ -24,5 +24,6 @@
"rebirthPop": "Begin a new character at Level 1 while retaining achievements, collectibles, and tasks with history.",
"rebirthName": "Orb of Rebirth",
"reborn": "Reborn, max level <%= reLevel %>",
- "confirmReborn": "Are you sure?"
+ "confirmReborn": "Are you sure?",
+ "rebirthComplete": "You have been reborn!"
}
diff --git a/common/locales/en/settings.json b/common/locales/en/settings.json
index a5ea38a2d6..727c04774c 100644
--- a/common/locales/en/settings.json
+++ b/common/locales/en/settings.json
@@ -47,6 +47,7 @@
"customDayStart": "Custom Day Start",
"changeCustomDayStart": "Change Custom Day Start?",
"sureChangeCustomDayStart": "Are you sure you want to change your custom day start?",
+ "customDayStartHasChanged": "Your custom day start has changed.",
"nextCron": "Your Dailies will next reset the first time you use Habitica after <%= time %>. Make sure you have completed your Dailies before this time!",
"customDayStartInfo1": "Habitica defaults to check and reset your Dailies at midnight in your own time zone each day. You can customize that time here.",
"misc": "Misc",
@@ -61,12 +62,23 @@
"newUsername": "New Login Name",
"dangerZone": "Danger Zone",
"resetText1": "WARNING! This resets many parts of your account. This is highly discouraged, but some people find it useful in the beginning after playing with the site for a short time.",
- "resetText2": "You will lose all your levels, gold, and experience points. All your tasks will be deleted permanently and you will lose all of your task's historical data. You will lose all your equipment but you will be able to buy it all back, including all limited edition equipment or subscriber Mystery items that you already own (you will need to be in the correct class to re-buy class-specific gear). You will keep your current class and your pets and mounts. You might prefer to use an Orb of Rebirth instead, which is a much safer option and which will preserve your tasks.",
+ "resetText2": "You will lose all your levels, gold, and experience points. All your tasks (except those from challenges) will be deleted permanently and you will lose all of their historical data. You will lose all your equipment but you will be able to buy it all back, including all limited edition equipment or subscriber Mystery items that you already own (you will need to be in the correct class to re-buy class-specific gear). You will keep your current class and your pets and mounts. You might prefer to use an Orb of Rebirth instead, which is a much safer option and which will preserve your tasks.",
"deleteText": "Are you sure? This will delete your account forever, and it can never be restored! You will need to register a new account to use Habitica again. Banked or spent Gems will not be refunded. If you're absolutely certain, type <%= deleteWord %> into the text box below.",
"API": "API",
+ "APIv3": "API v3",
+ "APIv2": "API v2 - Deprecated",
"APIText": "Copy these for use in third party applications. However, think of your API Token like a password, and do not share it publicly. You may occasionally be asked for your User ID, but never post your API Token where others can see it, including on Github.",
"APIToken": "API Token (this is a password - see warning above!)",
+ "thirdPartyApps": "Third Party Apps",
+ "dataToolDesc": "A webpage that shows you certain information from your Habitica account, such as statistics about your tasks, equipment, and skills.",
+ "beeminder": "Beeminder",
+ "beeminderDesc": "Let Beeminder automatically monitor your Habitica To-Dos. You can commit to maintaining a target number of To-Dos completed per day or per week, or you can commit to gradually reducing your remaining number of uncompleted To-Dos. (By \"commit\" Beeminder means under threat of paying actual money! But you may also just like Beeminder's fancy graphs.)",
+ "chromeChatExtension": "Chrome Chat Extension",
+ "chromeChatExtensionDesc": "The Chrome Chat Extension for Habitica adds an intuitive chat box to all of habitica.com. It allows users to chat in the Tavern, their party, and any guilds they are in.",
+ "otherExtensions": "Other Extensions",
+ "otherDesc": "Find other apps, extensions, and tools on the Habitica wiki.",
"resetDo": "Do it, reset my account!",
+ "resetComplete": "Reset complete!",
"fixValues": "Fix Values",
"fixValuesText1": "If you've encountered a bug or made a mistake that unfairly changed your character (damage you shouldn't have taken, Gold you didn't really earn, etc.), you can manually correct your numbers here. Yes, this makes it possible to cheat: use this feature wisely, or you'll sabotage your own habit-building!",
"fixValuesText2": "Note that you cannot restore Streaks on individual tasks here. To do that, edit the Daily and go to Advanced Options, where you will find a Restore Streak field.",
@@ -96,6 +108,7 @@
"emailNotifications": "Email Notifications",
"wonChallenge": "You won a Challenge!",
"newPM": "Received Private Message",
+ "sentGems": "Sent gems!",
"giftedGems": "Gifted Gems",
"giftedGemsInfo": "<%= amount %> Gems - by <%= name %>",
"giftedSubscription": "Gifted Subscription",
@@ -136,6 +149,11 @@
"webhooks": "Webhooks",
"enabled": "Enabled",
"webhookURL": "Webhook URL",
+ "invalidUrl": "invalid url",
+ "invalidEnabled": "the \"enabled\" parameter should be a boolean",
+ "regIdRequired": "RegId is required",
+ "pushDeviceAdded": "Push device added successfully",
+ "pushDeviceAlreadyAdded": "The user already has the push device",
"add": "Add",
"buyGemsGoldCap": "Cap raised to <%= amount %>",
"mysticHourglass": "<%= amount %> Mystic Hourglass",
@@ -150,5 +168,5 @@
"amazonPayments": "Amazon Payments",
"timezone": "Time Zone",
"timezoneUTC": "Habitica uses the time zone set on your PC, which is: <%= utc %>",
- "timezoneInfo": "If that time zone is wrong, first reload this page using your browser's reload or refresh button to ensure that Habitica has the most recent information. If it is still wrong, adjust the time zone on your PC and then reload this page again.
If you use Habitica on other PCs or mobile devices, the time zone must be the same on them all. If your Dailies have been reseting at the wrong time, repeat this check on all other PCs and on a browser on your mobile devices."
+ "timezoneInfo": "If that time zone is wrong, first reload this page using your browser's reload or refresh button to ensure that Habitica has the most recent information. If it is still wrong, adjust the time zone on your PC and then reload this page again.
If you use Habitica on other PCs or mobile devices, the time zone must be the same on them all. If your Dailies have been resetting at the wrong time, repeat this check on all other PCs and on a browser on your mobile devices."
}
diff --git a/common/locales/en/spells.json b/common/locales/en/spells.json
index 9ecd005ec9..5eb5d18be1 100644
--- a/common/locales/en/spells.json
+++ b/common/locales/en/spells.json
@@ -52,8 +52,8 @@
"spellSpecialSaltText": "Salt",
"spellSpecialSaltNotes": "Someone has snowballed you. Ha ha, very funny. Now get this snow off me!",
- "spellSpecialSpookDustText": "Spooky Sparkles",
- "spellSpecialSpookDustNotes": "Turn a friend into a floating blanket with eyes!",
+ "spellSpecialSpookySparklesText": "Spooky Sparkles",
+ "spellSpecialSpookySparklesNotes": "Turn a friend into a floating blanket with eyes!",
"spellSpecialOpaquePotionText": "Opaque Potion",
"spellSpecialOpaquePotionNotes": "Cancel the effects of Spooky Sparkles.",
@@ -65,6 +65,12 @@
"spellSpecialSeafoamText": "Seafoam",
"spellSpecialSeafoamNotes": "Turn a friend into a sea creature!",
"spellSpecialSandText": "Sand",
- "spellSpecialSandNotes": "Cancel the effects of Seafoam."
+ "spellSpecialSandNotes": "Cancel the effects of Seafoam.",
+ "spellNotFound": "Skill \"<%= spellId %>\" not found.",
+ "partyNotFound": "Party not found",
+ "targetIdUUID": "\"targetId\" must be a valid User ID.",
+ "challengeTasksNoCast": "Casting a skill on challenge tasks is not allowed.",
+ "spellNotOwned": "You don't own this skill.",
+ "spellLevelTooHigh": "You must be level <%= level %> to use this skill."
}
diff --git a/common/locales/en/subscriber.json b/common/locales/en/subscriber.json
index 087213d679..9561c42a4b 100644
--- a/common/locales/en/subscriber.json
+++ b/common/locales/en/subscriber.json
@@ -4,6 +4,8 @@
"subDescription": "Buy Gems with gold, get monthly mystery items, retain progress history, double daily drop-caps, support the devs. Click for more info.",
"buyGemsGold": "Buy Gems with Gold",
"buyGemsGoldText": "Alexander the Merchant will sell you Gems at a cost of <%= gemCost %> gold per gem. His monthly shipments are initially capped at <%= gemLimit %> Gems per month, but this cap increases by 5 Gems for every three months of consecutive subscription, up to a maximum of 50 Gems per month!",
+ "mustSubscribeToPurchaseGems": "Must subscribe to purchase gems with GP",
+ "reachedGoldToGemCap": "You've reached the Gold=>Gem conversion cap <%= convCap %> for this month. We have this to prevent abuse / farming. The cap will reset within the first three days of next month.",
"retainHistory": "Retain additional history entries",
"retainHistoryText": "Makes completed To-Dos and task history available for longer.",
"doubleDrops": "Daily drop-caps doubled",
@@ -29,6 +31,7 @@
"manageSub": "Click to manage subscription",
"cancelSub": "Cancel Subscription",
"canceledSubscription": "Canceled Subscription",
+ "cancelingSubscription": "Canceling the subscription",
"adminSub": "Administrator Subscriptions",
"morePlans": "More Plans Coming Soon",
"organizationSub": "Private Organization",
@@ -73,6 +76,9 @@
"timeTravelersPopover": "We see you have a Mystic Hourglass, so we will happily travel back in time for you! Please choose the pet, mount, or Mystery Item Set you would like. You can see a list of the past item sets here! If those don't satisfy you, perhaps you'd be interested in one of our fashionably futuristic Steampunk Item Sets?",
"timeTravelersAlreadyOwned": "Congratulations! You already own everything the Time Travelers currently offer. Thanks for supporting the site!",
"mysticHourglassPopover": "A Mystic Hourglass allows you to purchase certain limited-time items, such as monthly Mystery Item Sets and awards from world bosses, from the past!",
+ "mysterySetNotFound": "Mystery set not found, or set already owned.",
+ "mysteryItemIsEmpty": "Mystery items are empty",
+ "mysteryItemOpened": "Mystery item opened.",
"mysterySet201402": "Winged Messenger Set",
"mysterySet201403": "Forest Walker Set",
@@ -100,6 +106,8 @@
"mysterySet201601": "Champion of Resolution Set",
"mysterySet201602": "Heartbreaker Set",
"mysterySet201603": "Lucky Clover Set",
+ "mysterySet201604": "Leaf Warrior Set",
+ "mysterySet201605": "Marching Bard Set",
"mysterySet301404": "Steampunk Standard Set",
"mysterySet301405": "Steampunk Accessories Set",
@@ -113,9 +121,25 @@
"hourglassBuyItemConfirm": "Buy this item for 1 Mystic Hourglass?",
"petsAlreadyOwned": "Pet already owned.",
"mountsAlreadyOwned": "Mount already owned.",
- "typeNotAllowedHourglass": "Item type not supported for purchase with Mystic Hourglass. Allowed types: ",
+ "typeNotAllowedHourglass": "Item type not supported for purchase with Mystic Hourglass. Allowed types: <%= allowedTypes %>",
"petsNotAllowedHourglass": "Pet not available for purchase with Mystic Hourglass.",
"mountsNotAllowedHourglass": "Mount not available for purchase with Mystic Hourglass.",
"hourglassPurchase": "Purchased an item using a Mystic Hourglass!",
- "hourglassPurchaseSet": "Purchased an item set using a Mystic Hourglass!"
+ "hourglassPurchaseSet": "Purchased an item set using a Mystic Hourglass!",
+ "missingUnsubscriptionCode": "Missing unsubscription code.",
+ "missingSubscription": "User does not have a plan subscription",
+ "missingSubscriptionCode": "Missing subscription code. Possible values: basic_earned, basic_3mo, basic_6mo, google_6mo, basic_12mo.",
+ "cannotDeleteActiveAccount": "You have an active subscription, cancel your plan before deleting your account.",
+ "paymentNotSuccessful": "The payment was not successful",
+ "planNotActive": "The plan hasn't activated yet (due to a PayPal bug). It will begin <%= nextBillingDate %>, after which you can cancel to retain your full benefits",
+ "notAllowedHourglass": "Pet/Mount not available for purchase with Mystic Hourglass.",
+ "readCard": "<%= cardType %> has been read",
+ "cardTypeRequired": "Card type required",
+ "cardTypeNotAllowed": "Unknown card type.",
+ "invalidCoupon": "Invalid coupon code.",
+ "couponUsed": "Coupon code already used.",
+ "noSudoAccess": "You don't have sudo access.",
+ "couponCodeRequired": "The coupon code is required.",
+ "eventRequired": "\"req.params.event\" is required.",
+ "countRequired": "\"req.query.count\" is required."
}
diff --git a/common/locales/en/tasks.json b/common/locales/en/tasks.json
index cedee6dec6..f45ddd03b2 100644
--- a/common/locales/en/tasks.json
+++ b/common/locales/en/tasks.json
@@ -73,6 +73,7 @@
"clearTags": "Clear",
"hideTags": "Hide",
"showTags": "Show",
+ "toRequired": "You must supply a to value",
"startDate": "Start Date",
"startDateHelpTitle": "When should this task start?",
"startDateHelp": "Set the date for which this task takes effect. Will not be due on earlier days.",
@@ -88,8 +89,9 @@
"fortifyName": "Fortify Potion",
"fortifyPop": "Return all tasks to neutral value (yellow color), and restore all lost Health.",
"fortify": "Fortify",
- "fortifyText": "Fortify will return all your tasks to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!",
+ "fortifyText": "Fortify will return all your tasks, except challenge tasks, to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!",
"confirmFortify": "Are you sure?",
+ "fortifyComplete": "Fortify complete!",
"sureDelete": "Are you sure you want to delete the <%= taskType %> with the text \"<%= taskText %>\"?",
"streakCoins": "Streak Bonus!",
"pushTaskToTop": "Push task to top. Hold ctrl or cmd to push to bottom.",
@@ -112,5 +114,18 @@
"rewardHelp2": "Equipment affects your stats (<%= linkStart %>Avatar > Stats<%= linkEnd %>).",
"rewardHelp3": "Special equipment will appear here during World Events.",
"rewardHelp4": "Don't be afraid to set custom Rewards! Check out some samples here.",
- "clickForHelp": "Click for help"
+ "clickForHelp": "Click for help",
+ "taskIdRequired": "\"taskId\" must be a valid UUID.",
+ "taskNotFound": "Task not found.",
+ "invalidTaskType": "Task type must be one of \"habit\", \"daily\", \"todo\", \"reward\".",
+ "cantDeleteChallengeTasks": "A task belonging to a challenge can't be deleted.",
+ "checklistOnlyDailyTodo": "Checklists are supported only on dailies and todos",
+ "checklistItemNotFound": "No checklist item was found with given id.",
+ "itemIdRequired": "\"itemId\" must be a valid UUID.",
+ "tagNotFound": "No tag item was found with given id.",
+ "tagIdRequired": "\"tagId\" must be a valid UUID corresponding to a tag belonging to the user.",
+ "positionRequired": "\"position\" is required and must be a number.",
+ "cantMoveCompletedTodo": "Can't move a completed todo.",
+ "directionUpDown": "\"direction\" is required and must be 'up' or 'down'.",
+ "alreadyTagged": "The task is already tagged with given tag."
}
diff --git a/common/locales/en@pirate/backgrounds.json b/common/locales/en@pirate/backgrounds.json
index 718328153a..faf5beb3ad 100644
--- a/common/locales/en@pirate/backgrounds.json
+++ b/common/locales/en@pirate/backgrounds.json
@@ -141,24 +141,31 @@
"backgroundWinterNightText": "Wintry Night",
"backgroundWinterNightNotes": "Look at th' star o' a Wintry Night",
"backgrounds022016": "SET 21: Released February 2016",
- "backgroundBambooForestText": "Bamboo Forest",
- "backgroundBambooForestNotes": "Stroll through the Bamboo Forest.",
+ "backgroundBambooForestText": "Bamboo F",
+ "backgroundBambooForestNotes": "Stroll through th' Bamboo Forest.",
"backgroundCozyLibraryText": "Cozy Library",
- "backgroundCozyLibraryNotes": "Read in the Cozy Library.",
- "backgroundGrandStaircaseText": "Grand Staircase",
- "backgroundGrandStaircaseNotes": "Stride down the Grand Staircase.",
+ "backgroundCozyLibraryNotes": "Read in th' Cozy Library.",
+ "backgroundGrandStaircaseText": "Gran' Staircase",
+ "backgroundGrandStaircaseNotes": "Stride down th' Gran' Staircase.",
"backgrounds032016": "SET 22: Released March 2016",
- "backgroundDeepMineText": "Deep Mine",
- "backgroundDeepMineNotes": "Find precious metals in a Deep Mine.",
+ "backgroundDeepMineText": "Deeply Mine! Not yours!",
+ "backgroundDeepMineNotes": "Land Lubbers' be stealin' Gold and Treasure be in a Mine Deep under dah Sea",
"backgroundRainforestText": "Rainforest",
"backgroundRainforestNotes": "Venture into a Rainforest.",
- "backgroundStoneCircleText": "Circle of Stones",
+ "backgroundStoneCircleText": "Swirl of Stones",
"backgroundStoneCircleNotes": "Cast spells in a Circle of Stones.",
"backgrounds042016": "SET 23: Released April 2016",
"backgroundArcheryRangeText": "Archery Range",
- "backgroundArcheryRangeNotes": "Practice on the Archery Range.",
+ "backgroundArcheryRangeNotes": "Practice upon the Archery Range.",
"backgroundGiantFlowersText": "Giant Flowers",
"backgroundGiantFlowersNotes": "Frolic atop Giant Flowers.",
- "backgroundRainbowsEndText": "End of the Rainbow",
- "backgroundRainbowsEndNotes": "Discover gold at the End of the Rainbow."
+ "backgroundRainbowsEndText": "End o' th' Rainbow",
+ "backgroundRainbowsEndNotes": "Plunder treasure at th' End o' th' Rainbow.",
+ "backgrounds052016": "SET 24: Released May 2016",
+ "backgroundBeehiveText": "Beehive",
+ "backgroundBeehiveNotes": "Buzz and dance in a Beehive.",
+ "backgroundGazeboText": "Gazebo",
+ "backgroundGazeboNotes": "Battle a Gazebo.",
+ "backgroundTreeRootsText": "Tree Roots",
+ "backgroundTreeRootsNotes": "Explore the Tree Roots."
}
\ No newline at end of file
diff --git a/common/locales/en@pirate/challenge.json b/common/locales/en@pirate/challenge.json
index d385e82eac..105976019a 100644
--- a/common/locales/en@pirate/challenge.json
+++ b/common/locales/en@pirate/challenge.json
@@ -63,5 +63,20 @@
"congratulations": "Congratulations!",
"hurray": "Hurray!",
"noChallengeOwner": "no owner",
- "noChallengeOwnerPopover": "This challenge does not have an owner because th' person who created th' challenge deleted their account."
+ "noChallengeOwnerPopover": "This challenge does not have an owner because th' person who created th' challenge deleted their account.",
+ "challengeMemberNotFound": "User not found among challenge's members",
+ "onlyGroupLeaderChal": "Only the group leader can create challenges",
+ "tavChalsMinPrize": "Prize must be at least 1 Gem for Tavern challenges.",
+ "cantAfford": "You can't afford this prize. Purchase more gems or lower the prize amount.",
+ "challengeIdRequired": "\"challengeId\" must be a valid UUID.",
+ "winnerIdRequired": "\"winnerId\" must be a valid UUID.",
+ "challengeNotFound": "Challenge not found.",
+ "onlyLeaderDeleteChal": "Only the challenge leader can delete it.",
+ "onlyLeaderUpdateChal": "Only the challenge leader can update it.",
+ "winnerNotFound": "Winner with id \"<%= userId %>\" not found or not part of the challenge.",
+ "noCompletedTodosChallenge": "\"includeComepletedTodos\" is not supported when fetching a challenge tasks.",
+ "userTasksNoChallengeId": "When \"tasksOwner\" is \"user\" \"challengeId\" can't be passed.",
+ "onlyChalLeaderEditTasks": "Tasks belonging to a challenge can only be edited by the leader.",
+ "userAlreadyInChallenge": "User is already participating in this challenge.",
+ "cantOnlyUnlinkChalTask": "Only broken challenges tasks can be unlinked."
}
\ No newline at end of file
diff --git a/common/locales/en@pirate/character.json b/common/locales/en@pirate/character.json
index ec13ae75ed..c54e3123b7 100644
--- a/common/locales/en@pirate/character.json
+++ b/common/locales/en@pirate/character.json
@@ -1,4 +1,5 @@
{
+ "communityGuidelinesWarning": "Please keep in mind that yer Display Name, profile photo, an' blurb must comply wit' th' Community Guidelines (e.g. no profanity, no adult topics, no insults, etc). If ye have any questions about whether or not somethin' be appropriate, feel free t' email leslie@habitica.com!",
"statsAch": "Stats & Achievements",
"profile": "Profile",
"avatar": "Customize Avatar",
@@ -109,6 +110,7 @@
"mage": "Magician",
"mystery": "Mystery",
"changeClass": "Change Class, Refund Attribute Points",
+ "lvl10ChangeClass": "T'\u001d change class ye must be at least level 10.",
"levelPopover": "Each level earns ye one point to assign to an attribute 'o ye choice. ye can do so manually, or let th' game decide fer ye usin' one 'o th' Automatic Allocation options.",
"unallocated": "Unallocated Attribute Points",
"haveUnallocated": "Ye have <%= points %> unallocated Attribute Point(s)",
@@ -164,5 +166,7 @@
"int": "INT",
"showQuickAllocation": "Show stat allocation",
"hideQuickAllocation": "Hide stat allocation",
- "quickAllocationLevelPopover": "Each level earns ye one point t' assign t' an attribute o' yer choice. Ye can do so manually, or let th' game decide for ye using one o' th' Automatic Allocation options found in User -> Stats."
+ "quickAllocationLevelPopover": "Each level earns ye one point t' assign t' an attribute o' yer choice. Ye can do so manually, or let th' game decide for ye using one o' th' Automatic Allocation options found in User -> Stats.",
+ "invalidAttribute": "\"<%= attr %>\" is not a valid attribute.",
+ "notEnoughAttrPoints": "Ye don't have enough attribute points."
}
\ No newline at end of file
diff --git a/common/locales/en@pirate/content.json b/common/locales/en@pirate/content.json
index 0cf306ced2..55d3092a34 100644
--- a/common/locales/en@pirate/content.json
+++ b/common/locales/en@pirate/content.json
@@ -113,6 +113,12 @@
"questEggSnailText": "Snail",
"questEggSnailMountText": "Snail",
"questEggSnailAdjective": "a slow but steady",
+ "questEggFalconText": "Falcon",
+ "questEggFalconMountText": "Falcon",
+ "questEggFalconAdjective": "a swift",
+ "questEggTreelingText": "Treeling",
+ "questEggTreelingMountText": "Treeling",
+ "questEggTreelingAdjective": "a leafy",
"eggNotes": "Find ye hatchin' potion to pourrrr on this egg, an' it'll hatch into <%= eggAdjective(locale) %> <%= eggText(locale) %>.",
"hatchingPotionBase": "Base",
"hatchingPotionWhite": "White",
@@ -126,6 +132,7 @@
"hatchingPotionGolden": "Golden",
"hatchingPotionSpooky": "Spooky",
"hatchingPotionPeppermint": "Peppermint",
+ "hatchingPotionFloral": "Floral",
"hatchingPotionNotes": "Pour this on an egg, an' it'll hatch as a <%= potText(locale) %> pet.",
"premiumPotionAddlNotes": "Ye canna use this on quest pet eggs.",
"foodMeat": "Meat",
diff --git a/common/locales/en@pirate/contrib.json b/common/locales/en@pirate/contrib.json
index fdc58d8655..9e25a32d49 100644
--- a/common/locales/en@pirate/contrib.json
+++ b/common/locales/en@pirate/contrib.json
@@ -35,8 +35,12 @@
"hallContributors": "Deck o' Contributors",
"hallPatrons": "Hall o' Patrons",
"rewardUser": "Reward User",
- "UUID": "UUID",
+ "UUID": "User ID",
"loadUser": "Load User",
+ "noAdminAccess": "You don't have admin access.",
+ "pageMustBeNumber": "req.query.page must be a number",
+ "userNotFound": "User not found.",
+ "invalidUUID": "UUID must be valid",
"title": "Title",
"moreDetails": "More details (1-7)",
"moreDetails2": "more details (8-9)",
@@ -52,8 +56,8 @@
"visitHeroes": "Visit th' Hall 'o Captains (contributors 'n backers)",
"conLearn": "Learn more 'bout contributor rewards",
"conLearnHow": "Learn how t' contribute t' Habitica",
- "surveysSingle": "Helped Habitica grow by fillin' out a survey. There be no active surveys.",
- "surveysMultiple": "Helped Habitica grow by fillin' out <%= surveys %> surveys. There be no active surveys.",
+ "surveysSingle": "Helped Habitica grow, either by filling out a survey or helping with a major testing effort. Thank you!",
+ "surveysMultiple": "Helped Habitica grow on <%= surveys %> occasions, either by filling out a survey or helping with a major testing effort. Thank you!",
"currentSurvey": "Current Survey",
"surveyWhen": "The badge will be awarded t' all participants when surveys have been processed, in late March.",
"blurbInbox": "This be where yer private messages be stored! Ye c'n send someone a message by clickin' on th' envelope icon next t' their name in Tavern, Party, or Guild Chat. If you've received an inappropriate PM, you should email a screenshot of it to Lemoness (leslie@habitica.com)",
diff --git a/common/locales/en@pirate/death.json b/common/locales/en@pirate/death.json
index 035344a9be..c02ee7cadd 100644
--- a/common/locales/en@pirate/death.json
+++ b/common/locales/en@pirate/death.json
@@ -12,5 +12,6 @@
"losingHealthQuickly": "Losin' Health quickly?",
"lowHealthTips3": "Incomplete Dailies hurt ye overnight, so be careful not t' add too many at first!",
"lowHealthTips4": "If a Daily isn't due on a certain day, ye can disable it by clickin' th' pencil icon.",
- "goodLuck": "Best o' luck!"
+ "goodLuck": "Best o' luck!",
+ "cannotRevive": "Cannot revive if not dead"
}
\ No newline at end of file
diff --git a/common/locales/en@pirate/faq.json b/common/locales/en@pirate/faq.json
index dbcfc558e3..57bdca5da3 100644
--- a/common/locales/en@pirate/faq.json
+++ b/common/locales/en@pirate/faq.json
@@ -14,22 +14,22 @@
"webFaqAnswer3": "Yer tasks c'n change colour based on how yer currently accomplishin' them! Ev'ry new task starts out as a neutral yella. Perform yer Dailies er positive Habits more often, an' they move toward blue. Miss yer Daily, or give in to yer bad Habit, an' th' task moves toward red. Th' redder th' task, th' more rewards it'll give ye, but it it's a Daily or bad Habit, the more it'll hurt ye! This'll help motivate ye to finish the tasks that're givin' ye trouble.",
"faqQuestion4": "Why did me avatar lose health, an' how did I regain it?",
"iosFaqAnswer4": "There be several things that c'n cause ye to take damage. First, if ye left Dailies incomplete overnight, they will damage ye. Second, if ye jab at a bad Habit, it will damage ye. Finally, if ye be in a Boss Battle with yer Crew and one of yer Crew-mates did not complete all their Dailies, th' Boss will attack ye.\n\n Th' main way to heal is t' gain a level, which restores all yer health. Ye can also buy a Health Potion with Doubloons from the Loot column. Furthermore, at level 10 or above, ye can choose to be a Doc, and then ye will learn healing skills. If you are in a Party with a Doc, they can heal ye as well.",
- "webFaqAnswer4": "There are several things that can cause you to take damage. First, if you left Dailies incomplete overnight, they will damage you. Second, if you click a bad Habit, it will damage you. Finally, if you are in a Boss Battle with your party and one of your party mates did not complete all their Dailies, the Boss will attack you.\n
\n The main way to heal is to gain a level, which restores all your Health. You can also buy a Health Potion with Gold from the Rewards column. Plus, at level 10 or above, you can choose to become a Healer, and then you will learn healing skills. If you are in a party (under Social > Party) with a Healer, they can heal you as well.",
+ "webFaqAnswer4": "Thar be several things that can cause ye t' take damage. First, if ye left Dailies incomplete overnight, they will damage ye. Second, if ye click a bad Habit, it will damage ye. Finally, if in a Boss Battle with yer crew ye be an' one o' yer crew mates did not complete all their Dailies, th' Boss will attack ye.\n
\n Th' main way t' heal is t' gain a level, which restores all yer Health. Ye can also buy a Health Potion with Gold from th' Rewards column. Plus, at level 10 or above, ye can choose t' become a Doc, an' then ye will learn healing skills. If ye be in a crew (under Social > Crew) with a Doc, they can heal ye as well.",
"faqQuestion5": "How c'n I play Habitica wit' my mates?",
- "iosFaqAnswer5": "The best way is to invite them to a Party with you! Parties can go on quests, battle monsters, and cast skills to support each other. Go to Menu > Party and click \"Create New Party\" if you don't already have a Party. Then tap on the Members list, and tap Invite in the upper right-hand corner to invite your friends by entering their User ID (a string of numbers and letters that they can find under Settings > Account Details on the app, and Settings > API on the website). On the website, you can also invite friends via email, which we will add to the app in a future update.\n\nOn the website, you and your friends can also join Guilds, which are public chat rooms. Guilds will be added to the app in a future update!",
- "webFaqAnswer5": "The best way is to invite them to a party with you, under Social > Party! Parties can go on quests, battle monsters, and cast skills to support each other. You can also join guilds together (Social > Guilds). Guilds are chat rooms focusing on a shared interest or the pursuit of a common goal, and can be public or private. You can join as many guilds as you'd like, but only one party.\n
\n For more detailed info, check out the wiki pages on [Parties](http://habitrpg.wikia.com/wiki/Party) and [Guilds](http://habitrpg.wikia.com/wiki/Guilds).",
+ "iosFaqAnswer5": "Th' best way is t' invite them t' a Crew with ye! Crews can go on adventures, battle monsters, an' cast skills t' support each other. Go to Menu > Crew an' click \"Create New Crew\" if ye don't already have a Crew. Then tap on th' Members list, an' tap Invite in th' upper right-hand corner t' invite yer mates by enterin' their User ID (a string o' numbers an' letters that they can find under Settings > Account Details on th' app, an' Settings > API on th' website). On th' website, ye can also invite mates via email, which we will add t' the app in a future update.\n\nOn th' website, ye an' yer mates can also join Alliances, which be public chat rooms. Alliances will be added t' th' app in a future update!",
+ "webFaqAnswer5": "Th' best way is t' invite them t' a crew wit ye, under Social > Crew! Crews can go on adventures, battle monsters, an' cast skills t' support each other. Ye can also join alliances together (Social > Alliances). Alliances be chat rooms focusin' on a shared interest or th' pursuit o' a common goal, an' can be public or private. Ye can join as many alliances as ye like, but on'y one crew.\n
\n For more detailed info, check out the wiki pages on [Crews](http://habitrpg.wikia.com/wiki/Party) and [Alliances](http://habitrpg.wikia.com/wiki/Guilds).",
"faqQuestion6": "How do I get a Pet or Mount?",
- "iosFaqAnswer6": "At level 3, you will unlock the Drop System. Every time you complete a task, you'll have a random chance at receiving an egg, a hatching potion, or a piece of food. They will be stored in Menu > Items.\n\n To hatch a Pet, you'll need an egg and a hatching potion. Tap on the egg to determine the species you want to hatch, and select \"Hatch Egg.\" Then choose a hatching potion to determine its color! Go to Menu > Pets to equip your new Pet to your avatar by clicking on it. \n\n You can also grow your Pets into Mounts by feeding them under Menu > Pets. Tap on a Pet, and then select \"Feed Pet\"! You'll have to feed a pet many times before it becomes a Mount, but if you can figure out its favorite food, it will grow more quickly. Use trial and error, or [see the spoilers here](http://habitica.wikia.com/wiki/Food#Food_Preferences). Once you have a Mount, go to Menu > Mounts and tap on it to equip it to your avatar.\n\n You can also get eggs for Quest Pets by completing certain Quests. (See below to learn more about Quests.)",
- "webFaqAnswer6": "At level 3, you will unlock the Drop System. Every time you complete a task, you'll have a random chance at receiving an egg, a hatching potion, or a piece of food. They will be stored in Inventory > Market.\n
\n To hatch a Pet, you'll need an egg and a hatching potion. Click on the egg to determine the species you want to hatch, and then click on the hatching potion to determine its color! Go to Inventory > Pets to equip it to your avatar by clicking on it.\n
\n You can also grow your Pets into Mounts by feeding them under Inventory > Pets. Click on a Pet, and then click on a piece of food from the right-hand menu to feed it! You'll have to feed a pet many times before it becomes a Mount, but if you can figure out its favorite food, it will grow more quickly. Use trial and error, or [see the spoilers here](http://habitica.wikia.com/wiki/Food#Food_Preferences). Once you have a Mount, go to Inventory > Mounts and click on it to equip it to your avatar.\n
\n You can also get eggs for Quest Pets by completing certain Quests. (See below to learn more about Quests.)",
+ "iosFaqAnswer6": "At level 3, ye will unlock th' Drop System. Every time ye complete a task, ye have a random chance at receivin' an egg, a hatchin' potion, or a piece o' food. They will be stored in Menu > Items.\n\n T' hatch a Pet, ye'll be needin' an egg an' a hatchin' potion. Tap on th' egg t' determine the species ye want t' hatch, an' select \"Hatch Egg.\" Then choose a hatchin' potion t' determine its color! Go to Menu > Pets t' equip yer new Pet t' yer avatar by clickin' on it. \n\n Ye can also grow yer Pets into Mounts by feedin' them under Menu > Pets. Tap on a Pet, an' then select \"Feed Pet\"! Ye'll have t' feed a pet many times before it becomes a Mount, but if ye can figure out its favorite food, it will grow more quickly. Use trial an' error, or [see the spoilers here](http://habitica.wikia.com/wiki/Food#Food_Preferences). Once ye have a Mount, go t' Menu > Mounts an' tap on it t' equip it t' yer avatar.\n\n Ye can also get eggs for Quest Pets by completin' certain Quests. (See below t' learn more about Quests.)",
+ "webFaqAnswer6": "At level 3, ye will unlock th' Drop System. Every time ye complete a task, ye have a random chance at receivin' an egg, a hatchin' potion, or a piece o' food. They will be stored in Inventory > Market.\n
\n To hatch a Pet, ye'll be needin' an egg an' a hatchin' potion. Click on th' egg t' determine the species ye want t' hatch, an' then click on th' hatching potion t' determine its color! Go t' Inventory > Pets t' equip it t' yer avatar by clickin' on it.\n
\n Ye can also grow yer Pets into Mounts by feedin' them under Inventory > Pets. Click on a Pet, an' then click on a piece of food from th' right-hand menu t' feed it! Ye'll be havin' t' feed a pet many times before it becomes a Mount, but if ye can figure out its favorite food, it will grow more quickly. Use trial and error, or [see th' spoilers here](http://habitica.wikia.com/wiki/Food#Food_Preferences). Once ye have a Mount, go t' Inventory > Mounts an' click on it t' equip it t' yer avatar.\n
\n Ye can also get eggs for Quest Pets by completin' certain Quests. (See below t' learn more about Quests.)",
"faqQuestion7": "How c'n I become a Warrior, Mage, Rogue, or Healer?",
- "iosFaqAnswer7": "At level 10, you can choose to become a Warrior, Mage, Rogue, or Healer. (All players start as Warriors by default.) Each Class has different equipment options, different Skills that they can cast after level 11, and different advantages. Warriors can easily damage Bosses, withstand more damage from their tasks, and help make their Party tougher. Mages can also easily damage Bosses, as well as level up quickly and restore Mana for their party. Rogues earn the most gold and find the most item drops, and they can help their Party do the same. Finally, Healers can heal themselves and their Party members.\n\n If you don't want to choose a Class immediately -- for example, if you are still working to buy all the gear of your current class -- you can click “Decide Later” and choose later under Menu > Choose Class.",
- "webFaqAnswer7": "At level 10, you can choose to become a Warrior, Mage, Rogue, or Healer. (All players start as Warriors by default.) Each Class has different equipment options, different Skills that they can cast after level 11, and different advantages. Warriors can easily damage Bosses, withstand more damage from their tasks, and help make their party tougher. Mages can also easily damage Bosses, as well as level up quickly and restore Mana for their party. Rogues earn the most Gold and find the most item drops, and they can help their party do the same. Finally, Healers can heal themselves and their party members.\n
\n If you don't want to choose a Class immediately -- for example, if you are still working to buy all the gear of your current class -- you can click \"Opt Out\" and re-enable it later under User > Stats.",
+ "iosFaqAnswer7": "At level 10, ye can choose t' become a Warrior, Magician, Scallywag, or Doc. (All players start as Warriors by default.) Each Class be havin' different raiment options, different Skills that they can cast after level 11, an' different advantages. Warriors can easily damage Bosses, withstand more damage from their tasks, an' help make their Crew tougher. Magicians can also easily damage Bosses, as well as level up quickly an' restore Mana for their crew. Scallywags earn th' most doubloons an' find th' most loot, an' they can help their Crew do the same. Finally, Docs can heal themselves an' their Crew mates.\n\n If ye don't want t' choose a Class immediately -- for example, if ye still be workin' t' buy all th' gear o' yer current class -- ye can click “Decide Later” an' choose later under Menu > Choose Class.",
+ "webFaqAnswer7": "At level 10, ye can choose t' become a Warrior, Magician, Scallywag, or Doc. (All players start as Warriors by default.) Each Class be havin' different raiment options, different Skills that they can cast after level 11, an' different advantages. Warriors can easily damage Bosses, withstand more damage from their tasks, an' help make their crew tougher. Magicians can also easily damage Bosses, as well as level up quickly an' restore Mana for their crew. Scallywags earn th' most Doubloons an' find th' most loot, an' they can help their crew do th' same. Finally, Docs can heal themselves an' their crew mates.\n
\n If ye don't want t' choose a Class immediately -- for example, if ye still be workin' t' buy all th' gear o' yer current class -- ye can click \"Opt Out\" an' re-enable it later under User > Stats.",
"faqQuestion8": "What's the blue stat bar that appears in th' Header after level 10?",
"iosFaqAnswer8": "Th' blue bar, what appear'd when ye hit level 10 an' chose a Class, is yer Mana bar. As ye continue to level up, ye will unlock special Skills, which cost Mana t' use. Each Class has diff'rent Skills, what appear after level 11 under Menu > Use Skills. Unlike yer health bar, yer Mana bar doesn't reset when ye gain a level. Instead, Mana is gained when ye complete Good Habits, Dailies, an' To-Dos, an' lost when ye indulge Bad Habits. Ye'll also regain some Mana o'ernight -- th' more Dailies ye completed, th' more ye'll gain.",
- "webFaqAnswer8": "The blue bar that appeared when you hit level 10 and chose a Class is your Mana bar. As you continue to level up, you will unlock special Skills that cost Mana to use. Each Class has different Skills, which appear after level 11 in a special section in the Rewards Column. Unlike your Health bar, your Mana bar does not reset when you gain a level. Instead, Mana is gained when you complete Good Habits, Dailies, and To-Dos, and lost when you indulge bad Habits. You’ll also regain some Mana overnight -- the more Dailies you completed, the more you will gain.",
+ "webFaqAnswer8": "Th' blue bar that appear'd when ye hit level 10 'n chose a Class is yer Mana bar. As ye continue t' level up, ye will unlock special Skills that cost Mana to use. Each Class has different Skills, which be appearin' after level 11 in a special section in th' Rewards Column. Unlike yer Health bar, yer Mana bar don't be reset when ye gain a level. Instead, Mana be gained when ye complete Good Habits, Dailies, 'n To-Dos, and lost when ye indulge bad Habits. Ye’ll also regain some Mana o'ernight -- th' more Dailies ye be completin', th' more ye will gain.",
"faqQuestion9": "How do I fight monsters an' go on Quests?",
- "iosFaqAnswer9": "First, you need to join or start a Party (see above). Although you can battle monsters alone, we recommend playing in a group, because this will make Quests much easier. Plus, having a friend to cheer you on as you accomplish your tasks is very motivating!\n\n Next, you need a Quest Scroll, which are stored under Menu > Items. There are three ways to get a scroll:\n\n - At level 15, you get a Quest-line, aka three linked quests. More Quest-lines unlock at levels 30, 40, and 60 respectively. \n - When you invite people to your Party, you'll be rewarded with the Basi-List Scroll!\n - You can buy Quests from the Quests Page on the [website](https://habitica.com/#/options/inventory/quests) for Gold and Gems. (We will add this feature to the app in a future update.)\n\n To battle the Boss or collect items for a Collection Quest, simply complete your tasks normally, and they will be tallied into damage overnight. (Reloading by pulling down on the screen may be required to see the Boss's health bar go down.) If you are fighting a Boss and you missed any Dailies, the Boss will damage your Party at the same time that you damage the Boss. \n\n After level 11 Mages and Warriors will gain Skills that allow them to deal additional damage to the Boss, so these are excellent classes to choose at level 10 if you want to be a heavy hitter.",
- "webFaqAnswer9": "First, you need to join or start a party (under Social > Party). Although you can battle monsters alone, we recommend playing in a group, because this will make quests much easier. Plus, having a friend to cheer you on as you accomplish your tasks is very motivating!\n
\n Next, you need a Quest Scroll, which are stored under Inventory > Quests. There are three ways to get a scroll:\n
\n * When you invite people to your party, you’ll be rewarded with the Basi-List Scroll!\n * At level 15, you get a Quest-line, i.e., three linked quests. More Quest-lines unlock at levels 30, 40, and 60 respectively.\n * You can buy Quests from the Quests Page (Inventory > Quests) for Gold and Gems.\n
\n To battle the Boss or collect items for a Collection Quest, simply complete your tasks normally, and they will be tallied into damage overnight. (Reloading may be required to see the Boss's Health bar go down.) If you are fighting a Boss and you missed any Dailies, the Boss will damage your party at the same time that you damage the Boss.\n
\n After level 11 Mages and Warriors will gain Skills that allow them to deal additional damage to the Boss, so these are excellent classes to choose at level 10 if you want to be a heavy hitter.",
+ "iosFaqAnswer9": "First, ye need t' join or start a Crew (see above). Although ye can battle monsters alone, we recommend playing in a group, because this will make Quests much easier. Plus, havin' a mate t' cheer ye on as you accomplish your tasks is very motivatin'!\n\n Next, ye need a Quest Scroll, which be stored under Menu > Items. Thar be three ways t' get a scroll:\n\n - At level 15, ye get a Quest-line, aka three linked quests. More Quest-lines unlock at levels 30, 40, an' 60 respectively. \n - When ye invite people t' yer Crew, ye'll be rewarded with th' Basi-List Scroll!\n - Ye can buy Quests from th' Quests Page on th' [website](https://habitica.com/#/options/inventory/quests) for Doubloons and Sapphires. (We will add this feature t' th' app in a future update.)\n\n T' battle th' Boss or collect items for a Collection Quest, simply complete yer tasks normally, an' they will be tallied into damage o'ernight. (Reloading by pulling down on th' screen may be required t' see th' Boss's health bar go down.) If ye be fightin' a Boss an' ye missed any Dailies, th' Boss'll damage yer Crew at th' same time that ye damage th' Boss. \n\n After level 11 Magicians an' Warriors will gain Skills that allow 'em t' deal additional damage t' th' Boss, so these be excellent classes t' choose at level 10 if ye want t' be a heavy hitter.",
+ "webFaqAnswer9": "First, ye need to join or start a crew (under Social > Crew). Although ye can battle monsters alone, we recommend playin' in a group, because this'll make quests much easier. Plus, havin' a mate t' cheer ye on as ye accomplish yer tasks be very motivatin'!\n
\n Next, ye need a Quest Scroll, which are stored under Inventory > Quests. There be three ways t' get a scroll:\n
\n * When ye invite people t' yer crew, ye’ll be rewarded with th' Basi-List Scroll!\n * At level 15, ye get a Quest-line, i.e., three linked quests. More Quest-lines unlock at levels 30, 40, and 60 respectively.\n * Ye can buy Quests from th' Quests Page (Inventory > Quests) for Doubloons an' Sapphires.\n
\n T' battle th' Boss or collect items for a Collection Quest, simply complete yer tasks normally, an' they will be tallied into damage overnight. (Reloading may be required t' see th' Boss's Health bar go down.) If ye be fightin' a Boss an' ye missed any Dailies, th' Boss'll damage yer crew at th' same time that ye damage th' Boss.\n
\n After level 11 Magicians an' Warriors'll gain Skills that allow 'em t' deal additional damage t' th' Boss, so these be excellent classes t' choose at level 10 if ye want t' be a heavy hitter.",
"faqQuestion10": "What be Gems, an' how do ye get 'em?",
"iosFaqAnswer10": "Gems are purchased with real money by tapping on the gem icon in the header. When people buy gems, they are helping us to keep the site running. We're very grateful for their support!\n\n In addition to buying gems directly, there are three other ways players can gain gems:\n\n * Win a Challenge on the [website](https://habitica.com) that has been set up by another player under Social > Challenges. (We will be adding Challenges to the app in a future update!)\n * Subscribe on the [website](https://habitica.com/#/options/settings/subscription) and unlock the ability to buy a certain number of gems per month.\n * Contribute your skills to the Habitica project. See this wiki page for more details: [Contributing to Habitica](http://habitica.wikia.com/wiki/Contributing_to_Habitica).\n\n Keep in mind that items purchased with gems do not offer any statistical advantages, so players can still make use of the app without them!",
"webFaqAnswer10": "Gems be [purchased with real money](https://habitica.com/#/options/settings/subscription), but [subscribers](https://habitica.com/#/options/settings/subscription) c'n buy 'em wit' Gold. When people subscribe or buy Gems, they be helpin' us to keep th' site runnin'. We be very grateful fer their support!\n\n
\n\nIn addition to buyin' Gems directly or bein' a subscriber, there be two other ways players can gain Gems:\n\n
\n\n* Win a Challenge that has been set up by another player under Social > Challenges.\n\n* Contribute yer skills to the Habitica project. See this wiki page fer more details: [Contributing to Habitica](http://habitica.wikia.com/wiki/Contributing_to_Habitica)\n\n
\n\nKeep in mind that items purchased wit' Gems do not offer any statistical advantages, so players c'n still make use of th' site without 'em!",
@@ -39,6 +39,6 @@
"faqQuestion12": "How can ye battle a World Boss?",
"iosFaqAnswer12": "World Bosses are special monsters that appear in the Tavern. All active users are automatically battling the Boss, and their tasks and skills will damage the Boss as usual.\n\n You can also be in a normal Quest at the same time. Your tasks and skills will count towards both the World Boss and the Boss/Collection Quest in your party.\n\n A World Boss will never hurt you or your account in any way. Instead, it has a Rage Bar that fills when users skip Dailies. If its Rage bar fills, it will attack one of the Non-Player Characters around the site and their image will change.\n\n You can read more about [past World Bosses](http://habitica.wikia.com/wiki/World_Bosses) on the wiki.",
"webFaqAnswer12": "World Bosses are special monsters that appear in the Tavern. All active users are automatically battling the Boss, and their tasks and skills will damage the Boss as usual.\n
\n You can also be in a normal Quest at the same time. Your tasks and skills will count towards both the World Boss and the Boss/Collection Quest in your party.\n
\n A World Boss will never hurt you or your account in any way. Instead, it has a Rage Bar that fills when users skip Dailies. If its Rage bar fills, it will attack one of the Non-Player Characters around the site and their image will change.\n
\n You can read more about [past World Bosses](http://habitica.wikia.com/wiki/World_Bosses) on the wiki.",
- "iosFaqStillNeedHelp": "If you have a question that isn't on this list or on the [Wiki FAQ](http://habitica.wikia.com/wiki/FAQ), come ask in the Tavern chat under Menu > Tavern! We're happy to help.",
- "webFaqStillNeedHelp": "If you have a question that isn't on this list or on the [Wiki FAQ](http://habitica.wikia.com/wiki/FAQ), come ask in the [Newbies Guild](https://habitica.com/#/options/groups/guilds/5481ccf3-5d2d-48a9-a871-70a7380cee5a)! We're happy to help."
+ "iosFaqStillNeedHelp": "If ye 'ave a question that don't be on 'tis list or on th' [Wiki FAQ](http://habitica.wikia.com/wiki/FAQ), come ask in th' Tavern chat under Menu > Tavern! We be happy t' help.",
+ "webFaqStillNeedHelp": "If ye 'ave a question that don't be on 'tis list or on th' [Wiki FAQ](http://habitica.wikia.com/wiki/FAQ), come ask in th' [Newbies Guild](https://habitica.com/#/options/groups/guilds/5481ccf3-5d2d-48a9-a871-70a7380cee5a)! We be happy to help."
}
\ No newline at end of file
diff --git a/common/locales/en@pirate/front.json b/common/locales/en@pirate/front.json
index 408d70b0f6..bc8ed45290 100644
--- a/common/locales/en@pirate/front.json
+++ b/common/locales/en@pirate/front.json
@@ -28,6 +28,7 @@
"communityReddit": "Reddit",
"companyAbout": "How it Works",
"companyBlog": "Blog",
+ "devBlog": "Developer Blog",
"companyDonate": "Donate",
"companyExtensions": "Extensions",
"companyPrivacy": "Privacy",
@@ -51,6 +52,7 @@
"featureSocialHeading": "Social play",
"featuredIn": "Featured in",
"featuresHeading": "We also be featurin'...",
+ "footerDevs": "Developers",
"footerCommunity": "Community",
"footerCompany": "Company",
"footerMobile": "Mobile",
@@ -182,6 +184,7 @@
"zelahQuote": "With [Habitica], I can be persuaded to go to bed on time by the thought of gaining points for an early night or losing health for a late one!",
"reportAccountProblems": "Report Account Problems",
"reportCommunityIssues": "Report Community Issues",
+ "subscriptionPaymentIssues": "Subscription and Payment Issues",
"generalQuestionsSite": "General Questions about th' Site",
"businessInquiries": "Business Inquiries",
"merchandiseInquiries": "Merchandise Inquiries",
@@ -222,5 +225,32 @@
"altAttrWebstorm": "WebStorm",
"altAttrGithub": "GitHub",
"altAttrTrello": "Trello",
- "altAttrSlack": "Slack"
+ "altAttrSlack": "Slack",
+ "missingAuthHeaders": "Missing authentication headers.",
+ "missingAuthParams": "Missing authentication parameters.",
+ "missingUsernameEmail": "Missing username or email.",
+ "missingEmail": "Missing email.",
+ "missingUsername": "Missing username.",
+ "missingPassword": "Missing password.",
+ "missingNewPassword": "Missing new password.",
+ "wrongPassword": "Wrong password.",
+ "notAnEmail": "Invalid email address.",
+ "emailTaken": "Email address is already used in an account.",
+ "newEmailRequired": "Missing new email address.",
+ "usernameTaken": "Username already taken.",
+ "passwordConfirmationMatch": "Password confirmation doesn't match password.",
+ "invalidLoginCredentials": "Incorrect username and/or email and/or password.",
+ "passwordReset": "If we have your email on file, your password reset link has been sent to your email.",
+ "passwordResetEmailSubject": "Password Reset for Habitica",
+ "passwordResetEmailText": "Password for <%= username %> has been reset to <%= newPassword %> . Important! Both username and password are case-sensitive -- you must enter both exactly as shown here. We recommend copying and pasting both instead of typing them. Log in at <%= baseUrl %>. After you have logged in, head to <%= baseUrl %>/#/options/settings/settings and change your password.",
+ "passwordResetEmailHtml": "Password for <%= username %> has been reset to <%= newPassword %>.
Important! Both username and password are case-sensitive -- you must enter both exactly as shown here. We recommend copying and pasting both instead of typing them.
Log in at <%= baseUrl %>. After you have logged in, head to <%= baseUrl %>/#/options/settings/settings and change your password.",
+ "invalidLoginCredentialsLong": "Uh-oh - your username or password is incorrect.\n- Make sure your username or email is typed correctly.\n- You may have signed up with Facebook, not email. Double-check by trying Facebook login.\n- If you forgot your password, click \"Forgot Password\".",
+ "invalidCredentials": "There is no account that uses those credentials.",
+ "accountSuspended": "Account has been suspended, please contact leslie@habitica.com with your User ID \"<%= userId %>\" for assistance.",
+ "onlyFbSupported": "Only Facebook is supported currently.",
+ "cantDetachFb": "Account lacks another authentication method, can't detach Facebook.",
+ "onlySocialAttachLocal": "Local authentication can be added to only a social account.",
+ "invalidReqParams": "Invalid request parameters.",
+ "memberIdRequired": "\"member\" must be a valid UUID.",
+ "heroIdRequired": "\"heroId\" must be a valid UUID."
}
\ No newline at end of file
diff --git a/common/locales/en@pirate/gear.json b/common/locales/en@pirate/gear.json
index 9148f4811f..e3587155c4 100644
--- a/common/locales/en@pirate/gear.json
+++ b/common/locales/en@pirate/gear.json
@@ -189,6 +189,8 @@
"weaponArmoireMiningPickaxNotes": "Mine the maximum amount of gold from your tasks! Increases Perception by <%= per %>. Enchanted Armoire: Miner Set (Item 3 of 3).",
"weaponArmoireBasicLongbowText": "Basic Longbow",
"weaponArmoireBasicLongbowNotes": "A serviceable hand-me-down bow. Increases Strength by <%= str %>. Enchanted Armoire: Basic Archer Set (Item 1 of 3).",
+ "weaponArmoireHabiticanDiplomaText": "Habitican Diploma",
+ "weaponArmoireHabiticanDiplomaNotes": "A certificate of significant achievement -- well done! Increases Intelligence by <%= int %>. Enchanted Armoire: Graduate Set (Item 1 of 3).",
"armor": "armor",
"armorBase0Text": "Plain Slops",
"armorBase0Notes": "Ordinary slops. Don't benefit ye.",
@@ -364,6 +366,10 @@
"armorMystery201512Notes": "Summon th' icy flames of winter! Don't benefit ye. December 2015 Subscriber Item.",
"armorMystery201603Text": "Lucky Suit",
"armorMystery201603Notes": "This suit is sewn from thousands of four-leafed clovers! Confers no benefit. March 2016 Subscriber Item.",
+ "armorMystery201604Text": "Armor o' Leaves",
+ "armorMystery201604Notes": "You, too, can be a small but fearsome leaf puff. Confers no benefit. April 2016 Subscriber Item.",
+ "armorMystery201605Text": "Marching Bard Uniform",
+ "armorMystery201605Notes": "Unlike the traditional bards who join adventuring parties, bards who join Habitican marching bands are known for grand parades, not dungeon raids. Confers no benefit. May 2016 Subscriber Item.",
"armorMystery301404Text": "Steampunk Suit",
"armorMystery301404Notes": "Dapper an' dashing, wot! Don't benefit ye. February 3015 Subscriber Item.",
"armorArmoireLunarArmorText": "Soothin' Lunar Armor",
@@ -394,6 +400,8 @@
"armorArmoireMinerOverallsNotes": "They may seem worn, but they are enchanted to repel dirt. Increases Constitution by <%= con %>. Enchanted Armoire: Miner Set (Item 2 of 3).",
"armorArmoireBasicArcherArmorText": "Basic Archer Armor",
"armorArmoireBasicArcherArmorNotes": "This camouflaged vest lets you slip unnoticed through the forests. Increases Perception by <%= per %>. Enchanted Armoire: Basic Archer Set (Item 2 of 3).",
+ "armorArmoireGraduateRobeText": "Graduate Robe",
+ "armorArmoireGraduateRobeNotes": "Congratulations! This weighty robe hangs heavy with all the knowledge you have accrued. Increases Intelligence by <%= int %>. Enchanted Armoire: Graduate Set (Item 2 of 3).",
"headgear": "headgear",
"headBase0Text": "No Helm",
"headBase0Notes": "No headgear.",
@@ -565,6 +573,10 @@
"headMystery201602Notes": "Shield your identity from all your admirers. Confers no benefit. February 2016 Subscriber Item.",
"headMystery201603Text": "Lucky Hat",
"headMystery201603Notes": "This top hat is a magical good-luck charm. Confers no benefit. March 2016 Subscriber Item.",
+ "headMystery201604Text": "Crown o' Flowers",
+ "headMystery201604Notes": "These woven flowers make a surprisingly strong helm! Confers no benefit. April 2016 Subscriber Item.",
+ "headMystery201605Text": "Marching Bard Hat",
+ "headMystery201605Notes": "Seventy-six dragons led the big parade, with a hundred and ten gryphons close at hand! Confers no benefit. May 2016 Subscriber Item.",
"headMystery301404Text": "Fancy Top Hat",
"headMystery301404Notes": "A fancy top hat fer th' finest o' gentlefolk! January 3015 Subscriber Item. Don't benefit ye.",
"headMystery301405Text": "Basic Top Hat",
@@ -613,6 +625,8 @@
"headArmoireMinerHelmetNotes": "Protect your head from falling tasks! Increases Intelligence by <%= int %>. Enchanted Armoire: Miner Set (Item 1 of 3).",
"headArmoireBasicArcherCapText": "Basic Archer Cap",
"headArmoireBasicArcherCapNotes": "No archer would be complete without a jaunty cap! Increases Perception by <%= per %>. Enchanted Armoire: Basic Archer Set (Item 3 of 3).",
+ "headArmoireGraduateCapText": "Graduate Cap",
+ "headArmoireGraduateCapNotes": "Congratulations! Your deep thoughts have earned you this thinking cap. Increases Intelligence by <%= int %>. Enchanted Armoire: Graduate Set (Item 3 of 3).",
"offhand": "shield-hand item",
"shieldBase0Text": "No Shield-Hand Equipment",
"shieldBase0Notes": "No shield or second weapon.",
@@ -673,7 +687,7 @@
"shieldSpecialWinter2015HealerText": "Soothin' Shield",
"shieldSpecialWinter2015HealerNotes": "This shield deflects th' freezing wind. Increases Constitution by <%= con %>. Limited Edition 2014-2015 Winter Gear.",
"shieldSpecialSpring2015RogueText": "Explodin' Squeak",
- "shieldSpecialSpring2015RogueNotes": "Don't let th' sound fool ye - these explosives pack a punch. Increases Strength <%= str %>. Limited Edition 2015 Spring Gear.",
+ "shieldSpecialSpring2015RogueNotes": "Don't let the sound fool you - these explosives pack a punch. Increases Strength by <%= str %>. Limited Edition 2015 Spring Gear.",
"shieldSpecialSpring2015WarriorText": "Dish Discus",
"shieldSpecialSpring2015WarriorNotes": "Hurl it at yer enemies.... or just hold it, because it will fill up wit' yummy kibble at dinnertime. Increases Constitution by <%= con %>. Limited Edition 2015 Spring Gear.",
"shieldSpecialSpring2015HealerText": "Patterned Pillow",
@@ -716,6 +730,8 @@
"shieldArmoireDragonTamerShieldNotes": "Distract enemies with yer dragon-shaped shield. Increases Perception by <%= per %>. Enchanted Chest: Dragon Tamer Set (Item 2 of 3).",
"shieldArmoireMysticLampText": "Mystic Lamp",
"shieldArmoireMysticLampNotes": "Light the darkest caves with this mystic lamp! Increases Perception by <%= per %>. Enchanted Armoire: Independent Item.",
+ "shieldArmoireFloralBouquetText": "Bouquet o' Flowers",
+ "shieldArmoireFloralBouquetNotes": "Not much help in battle, but aren't they beautiful? Increases Constitution by <%= con %>. Enchanted Armoire: Independent Item.",
"back": "Back Accessory",
"backBase0Text": "No Back Accessory",
"backBase0Notes": "No Back Accessory.",
@@ -820,6 +836,20 @@
"eyewear": "Eyewear",
"eyewearBase0Text": "No Eyewear",
"eyewearBase0Notes": "No Eyewear.",
+ "eyewearSpecialBlackTopFrameText": "Black Standard Eyeglasses",
+ "eyewearSpecialBlackTopFrameNotes": "Glasses with a black frame above the lenses. Confers no benefit.",
+ "eyewearSpecialBlueTopFrameText": "Blue Standard Eyeglasses",
+ "eyewearSpecialBlueTopFrameNotes": "Glasses with a blue frame above the lenses. Confers no benefit.",
+ "eyewearSpecialGreenTopFrameText": "Green Standard Eyeglasses",
+ "eyewearSpecialGreenTopFrameNotes": "Glasses with a green frame above the lenses. Confers no benefit.",
+ "eyewearSpecialPinkTopFrameText": "Pink Standard Eyeglasses",
+ "eyewearSpecialPinkTopFrameNotes": "Glasses with a pink frame above the lenses. Confers no benefit.",
+ "eyewearSpecialRedTopFrameText": "Red Standard Eyeglasses",
+ "eyewearSpecialRedTopFrameNotes": "Glasses with a red frame above the lenses. Confers no benefit.",
+ "eyewearSpecialWhiteTopFrameText": "White Standard Eyeglasses",
+ "eyewearSpecialWhiteTopFrameNotes": "Glasses with a white frame above the lenses. Confers no benefit.",
+ "eyewearSpecialYellowTopFrameText": "Yellow Standard Eyeglasses",
+ "eyewearSpecialYellowTopFrameNotes": "Glasses with a yellow frame above the lenses. Confers no benefit.",
"eyewearSpecialSummerRogueText": "Roguish Eyepatch",
"eyewearSpecialSummerRogueNotes": "It don't take a scallywag t' see how stylish this be! Don't benefit ye. Limited Edition 2014 Summer Gear.",
"eyewearSpecialSummerWarriorText": "Dashin' Eyepatch",
diff --git a/common/locales/en@pirate/generic.json b/common/locales/en@pirate/generic.json
index 1d2e9ac662..f1e7311c27 100644
--- a/common/locales/en@pirate/generic.json
+++ b/common/locales/en@pirate/generic.json
@@ -137,8 +137,8 @@
"achievementStressbeastText": "Helped defeat th' Abominable Stressbeast durin' th' 2014 Winter Wonderland Event!",
"achievementBurnout": "Savior o' th' Flourishing Fields",
"achievementBurnoutText": "Helped defeat Burnout and restore the Exhaust Spirits during the 2015 Fall Festival Event!",
- "achievementBewilder": "Savior of Mistiflying",
- "achievementBewilderText": "Helped defeat the Be-Wilder during the 2016 Spring Fling Event!",
+ "achievementBewilder": "Savior o' Mistiflyin'",
+ "achievementBewilderText": "Helped defeat th' Be-Wilder durin' th' 2016 Spring Fling Event!",
"checkOutProgress": "Check out me progress in Habitica!",
"cardReceived": "Received a card!",
"cardReceivedFrom": "<%= cardType %> from <%= userName %>",
diff --git a/common/locales/en@pirate/groups.json b/common/locales/en@pirate/groups.json
index e68147dcc5..091abcb25b 100644
--- a/common/locales/en@pirate/groups.json
+++ b/common/locales/en@pirate/groups.json
@@ -92,6 +92,7 @@
"send": "Send",
"messageSentAlert": "Message sent",
"pmHeading": "Private message t' <%= name %>",
+ "pmsMarkedRead": "Your private messages have been marked as read",
"clearAll": "Delete All Messages",
"confirmDeleteAllMessages": "Are ye sure ye want t' delete all messages in yer inbox? Other users will still see messages ye've sent t' them.",
"optOutPopover": "Don't like private messages? Click t' completely opt out",
@@ -99,6 +100,15 @@
"unblock": "Un-block",
"pm-reply": "Send a reply",
"inbox": "Inbox",
+ "messageRequired": "A message is required.",
+ "toUserIDRequired": "A User ID is required",
+ "gemAmountRequired": "A number of gems is required",
+ "notAuthorizedToSendMessageToThisUser": "Can't send message to this user.",
+ "privateMessageGiftIntro": "Hello <%= receiverName %>, <%= senderName %> has sent you",
+ "privateMessageGiftGemsMessage": "<%= gemAmount %> gems!",
+ "privateMessageGiftSubscriptionMessage": "<%= numberOfMonths %> months of subscription!",
+ "cannotSendGemsToYourself": "Cannot send gems to yourself. Try a subscription instead.",
+ "badAmountOfGemsToSend": "Amount must be within 1 and your current number of gems.",
"abuseFlag": "Report violation o' Rules o' th' Sea",
"abuseFlagModalHeading": "Report <%= name %> fer violation?",
"abuseFlagModalBody": "Are ye sure ye want t' report this post? Ye should ONLY report a post that violates th' <%= firstLinkStart %>Community Guidelines<%= linkEnd %> an'/or <%= secondLinkStart %>Terms of Service<%= linkEnd %>. Inappropriately reportin' a post be a violation o' th' Community Guidelines an'\u001d may give ye an infraction. Appropriate reasons t' flag a post include but not be limited to:
swearing, religous oaths
bigotry, slurs
adult topics
violence, including as a joke
spam, nonsensical messages
",
@@ -151,5 +161,29 @@
"partyUpName": "Crew Aloft",
"partyOnName": "Crew Ahead",
"partyUpAchievement": "Joined a Crew with a mate! Have fun battlin' monsters an' supportin' each other.",
- "partyOnAchievement": "Joined a Crew with at least four mates! Enjoy yer increased accountability as ye unite with yer friends t' vanquish yer foes!"
+ "partyOnAchievement": "Joined a Crew with at least four mates! Enjoy yer increased accountability as ye unite with yer friends t' vanquish yer foes!",
+ "groupIdRequired": "\"groupId\" must be a valid UUID",
+ "groupNotFound": "Group not found.",
+ "groupTypesRequired": "You must supply a valid \"type\" query string.",
+ "questLeaderCannotLeaveGroup": "You cannot leave your party when you have started a quest. Abort the quest first.",
+ "cannotLeaveWhileActiveQuest": "You cannot leave party during an active quest. Please leave the quest first.",
+ "onlyLeaderCanRemoveMember": "Only group leader can remove a member!",
+ "memberCannotRemoveYourself": "You cannot remove yourself!",
+ "groupMemberNotFound": "User not found among group's members",
+ "mustBeGroupMember": "Must be member of the group.",
+ "keepOrRemoveAll": "req.query.keep must be either \"keep-all\" or \"remove-all\"",
+ "keepOrRemove": "req.query.keep must be either \"keep\" or \"remove\"",
+ "canOnlyInviteEmailUuid": "Can only invite using uuids or emails.",
+ "inviteMissingEmail": "Missing email address in invite.",
+ "partyMustbePrivate": "Parties must be private",
+ "userAlreadyInGroup": "User already in that group.",
+ "userAlreadyInvitedToGroup": "User already invited to that group.",
+ "userAlreadyPendingInvitation": "User already pending invitation.",
+ "userAlreadyInAParty": "User already in a party.",
+ "userWithIDNotFound": "User with id \"<%= userId %>\" not found.",
+ "userHasNoLocalRegistration": "User does not have a local registration (username, email, password).",
+ "uuidsMustBeAnArray": "User ID invites must be an array.",
+ "emailsMustBeAnArray": "Email address invites must be an array.",
+ "canOnlyInviteMaxInvites": "You can only invite \"<%= maxInvites %>\" at a time",
+ "onlyCreatorOrAdminCanDeleteChat": "Not authorized to delete this message!"
}
\ No newline at end of file
diff --git a/common/locales/en@pirate/limited.json b/common/locales/en@pirate/limited.json
index 3e8d614c2c..60f4260ced 100644
--- a/common/locales/en@pirate/limited.json
+++ b/common/locales/en@pirate/limited.json
@@ -5,7 +5,7 @@
"annoyingFriends": "Annoyin' Mates",
"annoyingFriendsText": "Got snowballed <%= snowballs %> times by crew-mates.",
"alarmingFriends": "Alarmin' Buckos",
- "alarmingFriendsText": "Got spooked <%= spookDust %> times by crew mates.",
+ "alarmingFriendsText": "Got spooked <%= spookySparkles %> times by party members.",
"agriculturalFriends": "Agricultural Friends",
"agriculturalFriendsText": "Got transformed into a flower <%= seeds %> times by party members.",
"aquaticFriends": "Friends o' th' Sea",
@@ -68,9 +68,10 @@
"mummyMedicSet": "Mummy Medic (Doc)",
"vampireSmiterSet": "Vampire Smiter (Scallywag)",
"bewareDogSet": "Beware Dog (Warrior)",
- "magicianBunnySet": "Magician's Bunny (Mage)",
- "comfortingKittySet": "Comforting Kitty (Healer)",
- "sneakySqueakerSet": "Sneaky Squeaker (Rogue)",
+ "magicianBunnySet": "Magician's Bunny (Wizard)",
+ "comfortingKittySet": "Comfortin' Kitty (Doc)",
+ "sneakySqueakerSet": "Sneaky Squeaker (Scallywag)",
"fallEventAvailability": "Available 'til October 31",
- "winterEventAvailability": "Available until December 31"
+ "winterEventAvailability": "Available until December 31",
+ "springEventAvailability": "Available until May 31"
}
\ No newline at end of file
diff --git a/common/locales/en@pirate/maintenance.json b/common/locales/en@pirate/maintenance.json
new file mode 100644
index 0000000000..e6de4fb076
--- /dev/null
+++ b/common/locales/en@pirate/maintenance.json
@@ -0,0 +1,34 @@
+{
+ "habiticaBackSoon": "Don' ye worry, Habitica be back before before ye flash yer deadlights!",
+ "importantMaintenance": "We be doin' important maintenance that we estimate gunna last 'til <%= localDate %> in ye timezone.",
+ "maintenance": "Maintenance",
+ "maintenanceMoreInfo": "Want more information 'bout th' maintenance? <%= linkStart %>Ye better check out the rutters<%= linkEnd %>.",
+ "noDamageKeepStreaks": "You will NOT take damage or lose streaks!",
+ "thanksForPatience": "Thanks for your patience!",
+ "twitterMaintenanceUpdates": "For the most recent updates, watch our Twitter, where we will be posting status information.",
+ "veteranPetAward": "At the end, you will receive a Veteran pet!",
+
+ "maintenanceInfoTitle": "Information about Upcoming Maintenance to Habitica",
+ "maintenanceInfoWhat": "What is happening?",
+ "maintenanceInfoWhatText": "On May 21, Habitica will be down for maintenance for most of the day. You will not take any damage or have your account harmed during that weekend, even if you can’t log in to check off your Dailies in time! We will be working very hard to make the downtime as short as possible, and will be posting regular updates on our Twitter account. At the end of the downtime, to thank everyone for their patience, you will all receive a rare pet!",
+ "maintenanceInfoWhy": "Why is this happening?",
+ "maintenanceInfoWhyText": "For the past several months, we have been thoroughly revamping Habitica behind-the-scenes. Specifically, we have rewritten the API. While it may not look much different on the surface, it’s a whole new world underneath. This will allow us WAY more flexibility when we want to build features in the future, and lead to improved performance!",
+ "maintenanceInfoTechDetails": "Want more details on the technical side of the process? Visit The Forge, our dev blog.",
+ "maintenanceInfoMore": "More Information",
+ "maintenanceInfoAccountChanges": "What changes will I see to my account after the rewrite is complete?",
+ "maintenanceInfoAccountChangesText": "At first, there won’t be any notable changes aside from performance improvements for features such as Challenges. If you notice any changes that shouldn’t be there, email us at admin@habitica.com and we will investigate them for you!",
+ "maintenanceInfoAddFeatures": "What kind of features will this allow Habitica to add?",
+ "maintenanceInfoAddFeaturesText": "Completing this rewrite will allow us to start building out improved chat and Guilds, plans for organizations and families, and additional productivity features like Monthlies and the ability to record yesterday’s activity! Those are all involved features on their own, so it will take time to build them, but until we were finished with this rewrite, there was no way we could start them.",
+ "maintenanceInfoHowLong": "How long will the maintenance take?",
+ "maintenanceInfoHowLongText": "We have to migrate tasks and data for all 1.3 million Habitica users -- not an easy task! We anticipate that it will take place between approximately 1pm Pacific Time (8pm UTC) and 10pm Pacific Time (5am UTC). Rest assured that we’re doing everything we can to make it go as quickly as possible! You can follow updates on our Twitter.",
+ "maintenanceInfoStatsAffected": "How will my Dailies, Streaks, Buffs, and Quests be affected?",
+ "maintenanceInfoStatsAffectedText1": "You will NOT take any damage or lose any streaks that weekend, but otherwise, your day will reset normally! Dailies that you checked will become unchecked, buffs will reset, etc. If you are in a Collection Quest, you will still find items. If you are in a Boss Battle, you will still deal damage to the Boss, but the Boss will not deal damage to you. (Even monsters need a break!)",
+ "maintenanceInfoStatsAffectedText2": "After a lot of thought, our team concluded that this was the most fair way to handle the fact that many users will not be able to check off their Dailies normally during the maintenance. We’re sorry for any inconvenience this causes!",
+ "maintenanceInfoSeeTasks": "What if I need to see my task list?",
+ "maintenanceInfoSeeTasksText": "If you know that you will need to see your task list on Saturday to remind yourself what you have to do, we recommend that before the maintenance begins, you take a screenshot of your tasks so that you can use it as a reference.",
+ "maintenanceInfoRarePet": "What kind of rare pet will I receive?",
+ "maintenanceInfoRarePetText": "To thank you for your patience during the downtime, everyone will get a rare Veteran Pet. If you’ve never received a Veteran Pet before, you will receive a Veteran Wolf. If you already have a Veteran Wolf, you will receive a Veteran Tiger. And if you already have a Veteran Wolf and a Veteran Tiger, you will receive a never-before-seen Veteran pet! After the migration is completed, it may take several hours for your pet to show up, but never fear, everyone will get one.",
+ "maintenanceInfoWho": "Who worked on this massive project?",
+ "maintenanceInfoWhoText": "We’re glad you asked! It was spearheaded by our amazing contributor paglias, with lots of help from Blade, TheHollidayInn, SabreCat, Victor Pudeyev, TheUnknown, and Alys.",
+ "maintenanceInfoTesting": "The new version was also tirelessly tested by a bunch of our amazing open-source volunteers. Thank you -- we couldn't have done this without you."
+}
diff --git a/common/locales/en@pirate/npc.json b/common/locales/en@pirate/npc.json
index 0dc2eebb31..539c29018e 100644
--- a/common/locales/en@pirate/npc.json
+++ b/common/locales/en@pirate/npc.json
@@ -21,6 +21,26 @@
"ian": "Shipmate Ian",
"ianText": "Welcome t' th' Quest Shop! Here ye can use Quest Scrolls t' battle monsters with yer mates. Be sure t' check out our fine array of Quest Scrolls for purchase t' th' starboard!",
"ianBrokenText": "Ahoy! Welcome to the Quest Shop... Here ye can use Quest Scrolls to fight monsters wit' yer mates... Be sure t' check out our fine array of Quest Scrolls fer purchase on th' right...",
+ "missingKeyParam": "\"req.params.key\" is required.",
+ "itemNotFound": "Item \"<%= key %>\" not found.",
+ "cannotBuyItem": "You can't buy this item.",
+ "missingTypeKeyEquip": "\"key\" and \"type\" are required parameters.",
+ "missingPetFoodFeed": "\"pet\" and \"food\" are required parameters.",
+ "invalidPetName": "Invalid pet name supplied.",
+ "missingEggHatchingPotionHatch": "\"egg\" and \"hatchingPotion\" are required parameters.",
+ "invalidTypeEquip": "\"type\" must be one of 'equipped', 'pet', 'mount', 'costume'.",
+ "mustPurchaseToSet": "Must purchase <%= val %> to set it on <%= key %>.",
+ "typeRequired": "Type is required",
+ "keyRequired": "Key is required",
+ "notAccteptedType": "Type must be in [eggs, hatchingPotions, food, quests, gear]",
+ "contentKeyNotFound": "Key not found for Content <%= type %>",
+ "plusOneGem": "+1 Gem",
+ "typeNotSellable": "Type is not sellable. Must be one of the following <%= acceptedTypes %>",
+ "userItemsKeyNotFound": "Key not found for user.items <%= type %>",
+ "pathRequired": "Path string is required",
+ "unlocked": "Items have been unlocked",
+ "alreadyUnlocked": "Full set already unlocked.",
+ "alreadyUnlockedPart": "Full set already partially unlocked.",
"USD": "(USD)",
"newStuff": "New Stuff",
"cool": "Ye Be tellin' Me Later",
@@ -64,6 +84,7 @@
"tourPetsPage": "This be th' Stable! After level 3, ye can hatch pets usin' eggs an' potions. When ye hatch a pet in th' Market, it will appear here! Click a pet's image t' add it to yer avatar. Feed 'em with th' food ye find after level 3, an' they'll grow into powerful mounts.",
"tourMountsPage": "Once ye've fed a pet enough food t' turn it into a mount, it will appear here. (Pets, mounts, an' food are available after level 3.) Click a mount t' saddle up!",
"tourEquipmentPage": "This be where yer Equipment be stored! Yer Battle Gear affects yer stats. If ye want t' show different Equipment on yer avatar without changin' yer stats, click \"Enable Costume.\"",
+ "equipmentAlreadyOwned": "You already own that piece of equipment",
"tourOkay": "Aye!",
"tourAwesome": "Awesome!",
"tourSplendid": "Splendid!",
diff --git a/common/locales/en@pirate/pets.json b/common/locales/en@pirate/pets.json
index 41c9a9b593..0eefe31c8f 100644
--- a/common/locales/en@pirate/pets.json
+++ b/common/locales/en@pirate/pets.json
@@ -12,6 +12,7 @@
"etherealLion": "Ethereal Lion",
"veteranWolf": "Veteran Wolf",
"veteranTiger": "Veteran Tiger",
+ "veteranLion": "Veteran Lion",
"cerberusPup": "Cerberus Pup",
"hydra": "Hydra",
"mantisShrimp": "Mantis Shrimp",
@@ -19,7 +20,7 @@
"orca": "Orca",
"royalPurpleGryphon": "Royal Purple Gryphon",
"phoenix": "Phoenix",
- "bumblebee": "Bumblebee",
+ "magicalBee": "Magical Bee",
"rarePetPop1": "Click th' gold paw t' learn more 'bout how ye can obtain 'tis rare pet through contributin' t' Habitica!",
"rarePetPop2": "How t' Get this Pet!",
"potion": "<%= potionType %> Potion",
@@ -62,6 +63,7 @@
"hatchedPet": "Ye hatched a <%= potion %> <%= egg %>!",
"displayNow": "Display Now",
"displayLater": "Display Later",
+ "petNotOwned": "You do not own this pet.",
"earnedCompanion": "Wit' all yer productivity, ye've earned a new companion. Feed it t' make it grow!",
"feedPet": "Feed <%= article %><%= text %> t' yer <%= name %>?",
"useSaddle": "Saddle <%= pet %>?",
@@ -83,5 +85,8 @@
"petKeyBoth": "Release Both",
"confirmPetKey": "Be ye positive?",
"petKeyNeverMind": "Not Yet",
+ "petsReleased": "Pets released.",
+ "mountsAndPetsReleased": "Mounts and pets released",
+ "mountsReleased": "Mounts released",
"gemsEach": "sapphires each"
}
\ No newline at end of file
diff --git a/common/locales/en@pirate/quests.json b/common/locales/en@pirate/quests.json
index cc2dbb2504..8e9999a692 100644
--- a/common/locales/en@pirate/quests.json
+++ b/common/locales/en@pirate/quests.json
@@ -38,7 +38,7 @@
"bossDmg2": "Only participants will fight th' boss an' share in the adventure's loot.",
"bossDmg1Broken": "Each completed Daily and To-Do and each positive Habit hurts the boss... Hurt it more with redder tasks or Brutal Smash and Burst of Flames... The boss will deal damage to every quest participant for every Daily you've missed (multiplied by the boss's Strength) in addition to your regular damage, so keep your party healthy by completing your Dailies... All damage to and from a boss is tallied on cron (your day roll-over)...",
"bossDmg2Broken": "Only participants will fight the boss an' share in the quest loot...",
- "tavernBossInfo": "Complete Dailies and To-Dos and score positive Habits to damage the World Boss! Incomplete Dailies fill the Rage Bar. When the Rage bar is full, the World Boss will attack an NPC. A World Boss will never damage individual players or accounts in any way. Only active accounts not resting in the Inn will have their tasks tallied.",
+ "tavernBossInfo": "Complete Dailies an' To'-Dos an' score positive Habits t' damage th' World Boss! Incomplete Dailies fill th' Rage Bar. When th' Rage bar be full, th' World Boss will attack an NPC. A World Boss will never damage individual players or accounts in any way. Only active accounts not resting in Quarters will have their tasks tallied.",
"tavernBossInfoBroken": "Complete Dailies an' T'-Dos an' score positive Habits t' damage th' World Boss... Incomplete Dailies fill th' Exhaust Strike Bar... When th' Exhaust Strike bar be full, th' World Boss'll attack an NPC... A World Boss'll never damage individual players or accounts in any way... Only active accounts not restin' in th' Inn will have their tasks tallied...",
"bossColl1": "T' collect items, do ye positive tasks. Quest items drop just like normal items; however, ye won't spy wit' ye eye th' loot 'til th' next day, then everythin' ye've found gunna be tallied up 'n contributed to th' pile.",
"bossColl2": "Only participants can collect items an' share in th' adventure's loot.",
@@ -78,5 +78,24 @@
"whichQuestStart": "Which adventure do ye want t' start?",
"getMoreQuests": "Get more adventures",
"unlockedAQuest": "Ye unlocked a quest!",
- "leveledUpReceivedQuest": "Ye leveled up t' Level <%= level %> an' received a quest scroll!"
+ "leveledUpReceivedQuest": "Ye leveled up t' Level <%= level %> an' received a quest scroll!",
+ "questInvitationDoesNotExist": "No quest invitation has been sent out yet.",
+ "questInviteNotFound": "No quest invitation found.",
+ "guildQuestsNotSupported": "Guilds cannot be invited on quests.",
+ "questNotFound": "Quest \"<%= key %>\" not found.",
+ "questNotOwned": "You don't own that quest scroll.",
+ "questNotGoldPurchasable": "Quest \"<%= key %>\" is not a Gold-purchasable quest.",
+ "questLevelTooHigh": "You must be level <%= level %> to begin this quest.",
+ "questAlreadyUnderway": "Your party is already on a quest. Try again when the current quest has ended.",
+ "questAlreadyAccepted": "You already accepted the quest invitation.",
+ "noActiveQuestToLeave": "No active quest to leave",
+ "questLeaderCannotLeaveQuest": "Quest leader cannot leave quest",
+ "notPartOfQuest": "You are not part of the quest",
+ "noActiveQuestToAbort": "There is no active quest to abort.",
+ "onlyLeaderAbortQuest": "Only the group or quest leader can abort a quest.",
+ "questAlreadyRejected": "You already rejected the quest invitation.",
+ "cantCancelActiveQuest": "You can not cancel an active quest, use the abort functionality.",
+ "onlyLeaderCancelQuest": "Only the group or quest leader can cancel the quest.",
+ "questNotPending": "There is no quest to start.",
+ "questOrGroupLeaderOnlyStartQuest": "Only the quest leader or group leader can force start the quest"
}
\ No newline at end of file
diff --git a/common/locales/en@pirate/questscontent.json b/common/locales/en@pirate/questscontent.json
index 3de548428d..4512de51bd 100644
--- a/common/locales/en@pirate/questscontent.json
+++ b/common/locales/en@pirate/questscontent.json
@@ -59,7 +59,7 @@
"questSpiderDropSpiderEgg": "Spider (Egg)",
"questSpiderUnlockText": "Unlocks purchasable spider eggs in the Market",
"questVice1Text": "Vice, Part 1: Free Yourself of the Dragon's Influence",
- "questVice1Notes": "
They say there lies a terrible evil in the caverns of Mt. Habitica. A monster whose presence twists the wills of the strong heroes of the land, turning them towards bad habits and laziness! The beast is a grand dragon of immense power and comprised of the shadows themselves: Vice, the treacherous Shadow Wyrm. Brave Habiteers, stand up and defeat this foul beast once and for all, but only if you believe you can stand against its immense power.
Vice Part 1:
How can you expect to fight the beast if it already has control over you? Don't fall victim to laziness and vice! Work hard to fight against the dragon's dark influence and dispel his hold on you!
",
+ "questVice1Notes": "
There be tales o' a terrible evil in the caverns o' Mt. Habitica. A monster whose presence twists t' wills o' the lubbers o' the land, turnin' them towards bad habits and laziness! The beast is a grand dragon o' immense power and comprised o' the shadows themselves: Vice, the treacherous Shadow Wyrm. Brave Habiteers, send this beast t' Davy Jones' locker, but only if ye believe ye can stand against its immense power.
Vice Part 1:
How can ye expect t' fight the beast if it already has control o'er ye? Don't fall victim t' laziness and vice! Work hard to fight against the dragon's dark influence and dispel his hold on ye!
",
"questVice1Boss": "Vice's Shade",
"questVice1DropVice2Quest": "Vice Part 2 (Scroll)",
"questVice2Text": "Vice, Part 2: Find the Lair of the Wyrm",
@@ -74,16 +74,16 @@
"questVice3DropDragonEgg": "Dragon (Egg)",
"questVice3DropShadeHatchingPotion": "Shade Hatchin' Potion",
"questMoonstone1Text": "Th' Moonstone Chain, Part 1: Th' Moonstone Chain",
- "questMoonstone1Notes": "A terrible affliction has struck Habiticans. Bad Habits thought long-dead are rising back up with a vengeance. Dishes lie unwashed, textbooks linger unread, and procrastination runs rampant!
You track some of your own returning Bad Habits to the Swamps of Stagnation and discover the culprit: the ghostly Necromancer, Recidivate. You rush in, weapons swinging, but they slide through her specter uselessly.
\"Don’t bother,\" she hisses with a dry rasp. \"Without a chain of moonstones, nothing can harm me – and master jeweler @aurakami scattered all the moonstones across Habitica long ago!\" Panting, you retreat... but you know what you must do.",
+ "questMoonstone1Notes": "A terrible affliction has struck Habiticans. Bad Habits thought long-in Davy Jones' treasure chest be risin' back up wit' a vengeance. Dishes lie unwashed, textbooks linger unread, 'n procrastination runs rampant!
Ye track some 'o ye own returnin' Bad Habits to th' Swamps 'o Stagnation 'n discover th' culprit: th' ghostly Necromancer, Recidivate. Ye rush in, weapons swingin', but they slide through 'er specter uselessly.
\"Don't bother,\" she hisses wit' a dry rasp. \"Wit'out a chain 'o moonstones, nothin' can harm me – 'n master jeweler @aurakami scattered all th' moonstones across 't lands o' Habitica long ago!\" Pantin', ye retreat... but ye be knowin' what ye must do.",
"questMoonstone1CollectMoonstone": "Moonstones",
"questMoonstone1DropMoonstone2Quest": "Th' Moonstone Chain Part 2: Recidivate th' Necromancer (Scroll)",
"questMoonstone2Text": "Th' Moonstone Chain, Part 2: Recidivate Th' Necromancer",
- "questMoonstone2Notes": "The brave weaponsmith @Inventrix helps you fashion the enchanted moonstones into a chain. You’re ready to confront Recidivate at last, but as you enter the Swamps of Stagnation, a terrible chill sweeps over you.
Rotting breath whispers in your ear. \"Back again? How delightful...\" You spin and lunge, and under the light of the moonstone chain, your weapon strikes solid flesh. \"You may have bound me to the world once more,\" Recidivate snarls, \"but now it is time for you to leave it!\"",
+ "questMoonstone2Notes": "Th' brave weaponsmith @Inventrix helps ye fashion th' enchanted moonstones into a chain. Ye're ready to confront Recidivate at last, but as ye enter th' Swamps 'o Stagnation, a terrible chill sweeps over ye.
Rottin' breath whispers in ye ear. \"Back again? How delightful...\" Ye spin 'n lunge, 'n under th' light 'o th' moonstone chain, ye weapon strikes solid flesh. \"Ye may have bound me to th' seven seas once more,\" Recidivate snarls, \"but now it be the hour fer ye to th' plank!\"",
"questMoonstone2Boss": "Th' Necromancer",
"questMoonstone2DropMoonstone3Quest": "Th' Moonstone Chain Part 3: Recidivate Transformed (Scroll)",
"questMoonstone3Text": "Th' Moonstone Chain, Part 3: Recidivate Transformed",
- "questMoonstone3Notes": "Recidivate crumples to the ground, and you strike at her with the moonstone chain. To your horror, Recidivate seizes the gems, eyes burning with triumph.
\"Foolish creature of flesh!\" she shouts. \"These moonstones will restore me to a physical form, true, but not as you imagined. As the full moon waxes from the dark, so too does my power flourish, and from the shadows I summon the specter of your most feared foe!\"
A sickly green fog rises from the swamp, and Recidivate’s body writhes and contorts into a shape that fills you with dread – the undead body of Vice, horribly reborn.",
- "questMoonstone3Completion": "Your breath comes hard and sweat stings your eyes as the undead Wyrm collapses. The remains of Recidivate dissipate into a thin grey mist that clears quickly under the onslaught of a refreshing breeze, and you hear the distant, rallying cries of Habiticans defeating their Bad Habits for once and for all.
@Baconsaur the beast master swoops down on a gryphon. \"I saw the end of your battle from the sky, and I was greatly moved. Please, take this enchanted tunic – your bravery speaks of a noble heart, and I believe you were meant to have it.\"",
+ "questMoonstone3Notes": "Recidivate crumples to th' ground, 'n ye strike at her wit' th' moonstone chain. To ye horror, Recidivate seizes th' gems, eyes burnin' wit' triumph.
\"Ye foolish creature 'o flesh!\" she be shoutin'. \"These moonstones gunna restore me to a physical form, true, but not as ye imagined. As th' full moon waxes from th' dark, so too does me power flourish, 'n from th' shadows I summon th' specter 'o ye most feared foe!\"
Avast! A sickly green fog rises from th' swamp, 'n Recidivate's body writhes 'n contorts into a shape that fills ye wit' dread – th' undead body 'o Vice, horribly back from Davy Jones' Locker!",
+ "questMoonstone3Completion": "Yer breath comes 'ard 'n sweat stin's ye eyes as th' undead Wyrm collapses. Th' remains 'o Recidivate dissipate into a thin grey mist that clears quickly under th' onslaught 'o a refreshin' breeze, 'n ye hear th' distant, rallyin' cries 'o Habiticans defeatin' their Bad Habits fer once 'n fer all.
@Baconsaur th' beast master swoops below on a winged lion. \"Arrr! I saw th' end 'o ye battle from th' skies, 'n I was greatly pleased. Please, take 'tis enchanted tunic – ye bravery speaks 'o a corsair's heart, 'n I believe ye were meant to have it.\"",
"questMoonstone3Boss": "Necro-Vice",
"questMoonstone3DropRottenMeat": "Rotten Meat (Foodstuffs)",
"questMoonstone3DropZombiePotion": "Zombie Hatchin' Potion",
@@ -140,7 +140,7 @@
"questAtom3Notes": "Wit' a deafenin' cry, 'n five delicious types 'o cheese burstin' from its mouth, th' SnackLess Monster falls to pieces. \"HOW DARE ye!\" booms a voice from beneath th' rum's surface. A robed, blue figure emerges from th' rum, wieldin' a magic toilet brush. Filthy laundry fightin' begins to bubble up to th' surface 'o th' lake. \"I be th' Laundromancer!\" he angrily announces. \"ye have some nerve - washin' me delightfully dirty dishes, destroyin' me pet, 'n enterin' me domain wit' such spit shine clothes. Prepare to feel th' soggy wrath 'o me anti-laundry fightin' magic!\"",
"questAtom3Completion": "Th' wicked Laundromancer has be defeated! spit shine laundry fightin' falls in piles all around ye. Thin's be lookin' much better around here. As ye begin to wade through th' freshly pressed armor, a glint 'o metal catches ye eye, 'n ye gaze falls upon a gleamin' helm. th' original owner 'o 'tis shinin' item may be unknown, but as ye put it on, ye feel th' warmin' presence 'o a generous devil's henchman. Too bad they didn't sew on a nametag.",
"questAtom3Boss": "Th' Laundromancer",
- "questAtom3DropPotion": "Base Hatching Potion",
+ "questAtom3DropPotion": "Simple Potion o' Hatchin'",
"questOwlText": "The Night-Owl",
"questOwlNotes": "The Tavern light is lit 'til dawn Until one eve the glow is gone! How can we see for our all-nighters? @Twitching cries, \"I need some fighters! See that Night-Owl, starry foe? Fight with haste and do not slow! We'll drive its shadow from our door, And make the night shine bright once more!\"",
"questOwlCompletion": "The Night-Owl fades before the dawn, But even so, you feel a yawn. Perhaps it's time to get some rest? Then on your bed, you see a nest! A Night-Owl knows it can be great To finish work and stay up late, But your new pets will softly peep To tell you when it's time to sleep.",
@@ -256,57 +256,69 @@
"questBurnoutCompletionChat": "`Burnout is DEFEATED!`\n\nWith a great, soft sigh, Burnout slowly releases the ardent energy that was fueling its fire. As the monster curls quietly into ashes, its stolen energy shimmers through the air, rejuvenating the Exhaust Spirits and returning them to their true forms.\n\nIan, Daniel, and the Seasonal Sorceress cheer as Habiticans rush to greet them, and all the missing citizens of the Flourishing Fields embrace their friends and families. The final Exhaust Spirit transforms into the Joyful Reaper herself!\n\n\"Look!\" whispers @Baconsaur, as the ashes begin to glitter. Slowly, they resolve into hundreds of shining phoenixes!\n\nOne of the glowing birds alights on the Joyful Reaper's skeletal arm, and she grins at it. \"It has been a long time since I've had the exquisite privilege to behold a phoenix in the Flourishing Fields,\" she says. \"Although given recent occurrences, I must say, this is highly thematically appropriate!\"\n\nHer tone sobers, although (naturally) her grin remains. \"We're known for being hard-working here, but we are also known for our feasts and festivities. Rather ironic, I suppose, that as we strove to plan a spectacular party, we refused to permit ourselves any time for fun. We certainly won't make the same mistake twice!\"\n\nShe claps her hands. \"Now - let's celebrate!\"\n\nAll Habiticans receive:\n\nPhoenix Pet\nPhoenix Mount\nAchievement: Savior of the Flourishing Fields\nBasic Candy\nVanilla Candy\nSand Candy\nCinnamon Candy\nChocolate Candy\nRotten Candy\nSour Pink Candy\nSour Blue Candy\nHoney Candy",
"questBurnoutBoss": "Burnout",
"questBurnoutBossRageTitle": "Exhaust Strike",
- "questBurnoutBossRageDescription": "When this gauge fills, Burnout will unleash its Exhaust Strike on Habitica!",
+ "questBurnoutBossRageDescription": "When 'tis gauge be filled, Burnout will unleash its Exhaust Strike o'er th' seven seas 'o Habitica!",
"questBurnoutDropPhoenixPet": "Phoenix (Pet)",
"questBurnoutDropPhoenixMount": "Phoenix (Mount)",
"questBurnoutBossRageQuests": "`Burnout uses EXHAUST STRIKE!`\n\nOh no! Despite our best efforts, we've let some Dailies get away from us, and now Burnout is inflamed with energy! With a crackling snarl, it engulfs Ian the Quest Master in a surge of spectral fire. As fallen quest scrolls smolder, the smoke clears, and you see that Ian has been drained of energy and turned into a drifting Exhaust Spirit!\n\nOnly defeating Burnout can break the spell and restore our beloved Quest Master. Let's keep our Dailies in check and defeat this monster before it attacks again!",
"questBurnoutBossRageSeasonalShop": "`Burnout uses EXHAUST STRIKE!`\n\nAhh!!! Our incomplete Dailies have fed the flames of Burnout, and now it has enough energy to strike again! It lets loose a gout of spectral flame that sears the Seasonal Shop. You're horrified to see that the cheery Seasonal Sorceress has been transformed into a drooping Exhaust Spirit.\n\nWe have to rescue our NPCs! Hurry, Habiticans, complete your tasks and defeat Burnout before it strikes for a third time!",
"questBurnoutBossRageTavern": "`Burnout uses EXHAUST STRIKE!`\n\nMany Habiticans have been hiding from Burnout in the Tavern, but no longer! With a screeching howl, Burnout rakes the Tavern with its white-hot hands. As the Tavern patrons flee, Daniel is caught in Burnout's grip, and transforms into an Exhaust Spirit right in front of you!\n\nThis hot-headed horror has gone on for too long. Don't give up... we're so close to vanquishing Burnout for once and for all!",
- "questFrogText": "Swamp of the Clutter Frog",
+ "questFrogText": "Swamp o' t'Clutter Frog",
"questFrogNotes": "As you and your friends are slogging through the Swamps of Stagnation, @starsystemic points at a large sign. \"Stay on the path -- if you can.\"
\"Surely that isn't hard!\" @RosemonkeyCT says. \"It's broad and clear.\"
But as you continue, you notice that path is gradually overtaken by the muck of the swamp, laced with bits of strange blue debris and clutter, until it's impossible to proceed.
As you look around, wondering how it got this messy, @Jon Arjinborn shouts, \"Look out!\" An angry frog leaps from the sludge, clad in dirty laundry and lit by blue fire. You will have to overcome this poisonous Clutter Frog to progress!",
"questFrogCompletion": "The frog cowers back into the muck, defeated. As it slinks away, the blue slime fades, leaving the way ahead clear.
Sitting in the middle of the path are three pristine eggs. \"You can even see the tiny tadpoles and through the clear casing!\" @Breadstrings says. \"Here, you should take them.\"",
"questFrogBoss": "Clutter Frog",
"questFrogDropFrogEgg": "Frog (Egg)",
- "questFrogUnlockText": "Unlocks purchasable Frog eggs in the Market",
- "questSnakeText": "The Serpent of Distraction",
+ "questFrogUnlockText": "Unlocks purchasable Frog eggs in t' Market",
+ "questSnakeText": "Th' Serpent o' Distraction",
"questSnakeNotes": "It takes a hardy soul to live in the Sand Dunes of Distraction. The arid desert is hardly a productive place, and the shimmering dunes have led many a traveler astray. However, something has even the locals spooked. The sands have been shifting and upturning entire villages. Residents claim a monster with an enormous serpentine body lies in wait under the sands, and they have all pooled together a reward for whomever will help them find and stop it. The much-lauded snake charmers @EmeraldOx and @PainterProphet have agreed to help you summon the beast. Can you stop the Serpent of Distraction?",
"questSnakeCompletion": "With assistance from the charmers, you banish the Serpent of Distraction. Though you were happy to help the inhabitants of the Dunes, you can't help but feel a little sad for your fallen foe. While you contemplate the sights, @LordDarkly approaches you. \"Thank you! It's not much, but I hope this can express our gratitude properly.\" He hands you some Gold and... some Snake eggs! You will see that majestic animal again after all.",
- "questSnakeBoss": "Serpent of Distraction",
+ "questSnakeBoss": "Serpent o' Distraction",
"questSnakeDropSnakeEgg": "Snake (Egg)",
- "questSnakeUnlockText": "Unlocks purchasable Snake eggs in the Market",
- "questUnicornText": "Convincing the Unicorn Queen",
+ "questSnakeUnlockText": "Unlocks purchasable Snake eggs in th' Market",
+ "questUnicornText": "Convincing th' Unicorn Queen",
"questUnicornNotes": "Conquest Creek has become muddied, destroying Habit City's fresh water supply! Luckily, @Lukreja knows an old legend that claims that a unicorn's horn can purify the foulest of waters. Together with your intrepid guide @UncommonCriminal, you hike through the frozen peaks of the Meandering Mountains. Finally, at the icy summit of Mount Habitica itself, you find the Unicorn Queen amid the glittering snows. \"Your pleas are compelling,\" she tells you. \"But first you must prove that you are worthy of my aid!\"",
"questUnicornCompletion": "Impressed by your diligence and strength, the Unicorn Queen at last agrees that your cause is worthy. She allows you to ride on her back as she soars to the source of Conquest Creek. As she lowers her golden horn to the befouled waters, a brilliant blue light rises from the water’s surface. It is so blinding that you are forced to close your eyes. When you open them a moment later, the unicorn is gone. However, @rosiesully lets out a cry of delight: the water is now clear, and three shining eggs rest at the creek’s edge.",
- "questUnicornBoss": "The Unicorn Queen",
+ "questUnicornBoss": "Th' Unicorn Queen",
"questUnicornDropUnicornEgg": "Unicorn (Egg)",
- "questUnicornUnlockText": "Unlocks purchasable Unicorn eggs in the Market",
- "questSabretoothText": "The Sabre Cat",
+ "questUnicornUnlockText": "Unlocks purchasable Unicorn eggs in th' Market",
+ "questSabretoothText": "Th' Sabre Cat",
"questSabretoothNotes": "A roaring monster is terrorizing Habitica! The creature stalks through the wilds and woods, then bursts forth to attack before vanishing again. It's been hunting innocent pandas and frightening the flying pigs into fleeing their pens to roost in the trees. @Inventrix and @icefelis explain that the Zombie Sabre Cat was set free while they were excavating in the ancient, untouched ice-fields of the Stoikalm Steppes. \"It was perfectly friendly at first – I don't know what happened. Please, you have to help us recapture it! Only a champion of Habitica can subdue this prehistoric beast!\"",
"questSabretoothCompletion": "After a long and tiring battle, you wrestle the Zombie Sabre Cat to the ground. As you are finally able to approach, you notice a nasty cavity in one of its sabre teeth. Realising the true cause of the cat's wrath, you're able to get the cavity filled by @Fandekasp, and advise everyone to avoid feeding their friend sweets in future. The Sabre Cat flourishes, and in gratitude, its tamers send you a generous reward – a clutch of sabretooth eggs!",
"questSabretoothBoss": "Zombie Sabre Cat",
"questSabretoothDropSabretoothEgg": "Sabretooth (Egg)",
- "questSabretoothUnlockText": "Unlocks purchasable Sabretooth eggs in the Market",
- "questMonkeyText": "Monstrous Mandrill and the Mischief Monkeys",
+ "questSabretoothUnlockText": "Unlocks purchasable Sabretooth eggs in th' Market",
+ "questMonkeyText": "Monstrous Mandrill 'n th' Mischief Monkeys",
"questMonkeyNotes": "The Sloensteadi Savannah is being torn apart by the Monstrous Mandrill and his Mischief Monkeys! They shriek loudly enough to drown out the sound of approaching deadlines, encouraging everyone to avoid their duties and keep monkeying around. Alas, plenty of people ape this bad behavior. If no one stops these primates, soon everyone's tasks will be as red as the Monstrous Mandrill's face!
\"It will take a dedicated adventurer to resist them,\" says @yamato.
\"Quick, let's get this monkey off everyone's backs!\" @Oneironaut yells, and you charge into battle.",
"questMonkeyCompletion": "You did it! No bananas for those fiends today. Overwhelmed by your diligence, the monkeys flee in panic. \"Look,\" says @Misceo. \"They left a few eggs behind.\"
@Leephon grins. \"Maybe a well-trained pet monkey can help you as much as the wild ones hinder you!\"",
"questMonkeyBoss": "Monstrous Mandrill",
"questMonkeyDropMonkeyEgg": "Monkey (Egg)",
- "questMonkeyUnlockText": "Unlocks purchasable Monkey eggs in the Market",
- "questSnailText": "The Snail of Drudgery Sludge",
+ "questMonkeyUnlockText": "Unlocks purchasable Monkey eggs in th' Market",
+ "questSnailText": "The Snail o' Drudgery Sludge",
"questSnailNotes": "You're excited to begin questing in the abandoned Dungeons of Drudgery, but as soon as you enter, you feel the ground under your feet start to suck at your boots. You look up to the path ahead and see Habiticans mired in slime. @Overomega yells, \"They have too many unimportant tasks and dailies, and they're getting stuck on things that don't matter! Pull them out!\"
\"You need to find the source of the ooze,\" @Pfeffernusse agrees, \"or the tasks that they cannot accomplish will drag them down forever!\"
Pulling out your weapon, you wade through the gooey mud.... and encounter the fearsome Snail of Drudgery Sludge.",
"questSnailCompletion": "You bring your weapon down on the great Snail's shell, cracking it in two, releasing a flood of water. The slime is washed away, and the Habiticans around you rejoice. \"Look!\" says @Misceo. \"There's a small group of snail eggs in the remnants of the muck.\"",
- "questSnailBoss": "Snail of Drudgery Sludge",
+ "questSnailBoss": "Snail o' Drudgery Sludge",
"questSnailDropSnailEgg": "Snail (Egg)",
- "questSnailUnlockText": "Unlocks purchasable Snail eggs in the Market",
- "questBewilderText": "The Be-Wilder",
+ "questSnailUnlockText": "Unlocks purchasable Snail eggs in th' Market",
+ "questBewilderText": "Th' Be-Wilder",
"questBewilderNotes": "The party begins like any other.
The appetizers are excellent, the music is swinging, and even the dancing elephants have become routine. Habiticans laugh and frolic amid the overflowing floral centerpieces, happy to have a distraction from their least-favorite tasks, and the April Fool whirls among them, eagerly providing an amusing trick here and a witty twist there.
As the Mistiflying clock tower strikes midnight, the April Fool leaps onto the stage to give a speech.
“Friends! Enemies! Tolerant acquaintances! Lend me your ears.” The crowd chuckles as animal ears sprout from their heads, and they pose with their new accessories.
“As you know,” the Fool continues, “my confusing illusions usually only last a single day. But I’m pleased to announce that I’ve discovered a shortcut that will guarantee us non-stop fun, without having to deal with the pesky weight of our responsibilities. Charming Habiticans, meet my magical new friend... the Be-Wilder!”
Lemoness pales suddenly, dropping her hors d'oeuvres. “Wait! Don’t trust--”
But suddenly mists are pouring into the room, glittering and thick, and they swirl around the April Fool, coalescing into cloudy feathers and a stretching neck. The crowd is speechless as an monstrous bird unfolds before them, its wings shimmering with illusions. It lets out a horrible screeching laugh.
“Oh, it has been ages since a Habitican has been foolish enough to summon me! How wonderful it feels, to have a tangible form at last.”
Buzzing in terror, the magic bees of Mistiflying flee the floating city, which sags from the sky. One by one, the brilliant spring flowers wither up and wisp away.
“My dearest friends, why so alarmed?” crows the Be-Wilder, beating its wings. “There’s no need to toil for your rewards any more. I’ll just give you all the things that you desire!”
A rain of coins pours from the sky, hammering into the ground with brutal force, and the crowd screams and flees for cover. “Is this a joke?” Baconsaur shouts, as the gold smashes through windows and shatters roof shingles.
PainterProphet ducks as lightning bolts crackle overhead, and fog blots out the sun. “No! This time, I don’t think it is!”
Quickly, Habiticans, don’t let this World Boss distract us from our goals! Stay focused on the tasks that you need to complete so we can rescue Mistiflying -- and hopefully, ourselves.",
"questBewilderCompletion": "The Be-Wilder is DEFEATED!
We've done it! The Be-Wilder lets out a ululating cry as it twists in the air, shedding feathers like falling rain. Slowly, gradually, it coils into a cloud of sparkling mist. As the newly-revealed sun pierces the fog, it burns away, revealing the coughing, mercifully human forms of Bailey, Matt, Alex.... and the April Fool himself.
Mistiflying is saved!
The April Fool has enough shame to look a bit sheepish. “Oh, hm,” he says. “Perhaps I got a little…. carried away.”
The crowd mutters. Sodden flowers wash up on sidewalks. Somewhere in the distance, a roof collapses with a spectacular splash.
“Er, yes,” the April Fool says. “That is. What I meant to say was, I’m dreadfully sorry.” He heaves a sigh. “I suppose it can’t all be fun and games, after all. It might not hurt to focus occasionally. Maybe I’ll get a head start on next year’s pranking.”
Redphoenix coughs meaningfully.
“I mean, get a head start on this year’s spring cleaning!” the April Fool says. “Nothing to fear, I’ll have Habit City in spit-shape soon. Luckily nobody is better than I at dual-wielding mops.”
Encouraged, the marching band starts up.
It isn’t long before all is back to normal in Habit City. Plus, now that the Be-Wilder has evaporated, the magical bees of Mistiflying bustle back to work, and soon the flowers are blooming and the city is floating once more.
As Habiticans cuddle the magical fuzzy bees, the April Fool’s eyes light up. “Oho, I’ve had a thought! Why don’t you all keep some of these fuzzy Bee Pets and Mounts? It’s a gift that perfectly symbolizes the balance between hard work and sweet rewards, if I’m going to get all boring and allegorical on you.” He winks. “Besides, they don’t have stingers! Fool’s honor.”",
"questBewilderCompletionChat": "`The Be-Wilder is DEFEATED!`\n\nWe've done it! The Be-Wilder lets out a ululating cry as it twists in the air, shedding feathers like falling rain. Slowly, gradually, it coils into a cloud of sparkling mist. As the newly-revealed sun pierces the fog, it burns away, revealing the coughing, mercifully human forms of Bailey, Matt, Alex.... and the April Fool himself.\n\n`Mistiflying is saved!`\n\nThe April Fool has enough shame to look a bit sheepish. “Oh, hm,” he says. “Perhaps I got a little…. carried away.”\n\nThe crowd mutters. Sodden flowers wash up on sidewalks. Somewhere in the distance, a roof collapses with a spectacular splash.\n\n“Er, yes,” the April Fool says. “That is. What I meant to say was, I’m dreadfully sorry.” He heaves a sigh. “I suppose it can’t all be fun and games, after all. It might not hurt to focus occasionally. Maybe I’ll get a head start on next year’s pranking.”\n\nRedphoenix coughs meaningfully.\n\n“I mean, get a head start on this year’s spring cleaning!” the April Fool says. “Nothing to fear, I’ll have Habit City in spit-shape soon. Luckily nobody is better than I at dual-wielding mops.”\n\nEncouraged, the marching band starts up.\n\nIt isn’t long before all is back to normal in Habit City. Plus, now that the Be-Wilder has evaporated, the magical bees of Mistiflying bustle back to work, and soon the flowers are blooming and the city is floating once more.\n\nAs Habiticans cuddle the magical fuzzy bees, the April Fool’s eyes light up. “Oho, I’ve had a thought! Why don’t you all keep some of these fuzzy Bee Pets and Mounts? It’s a gift that perfectly symbolizes the balance between hard work and sweet rewards, if I’m going to get all boring and allegorical on you.” He winks. “Besides, they don’t have stingers! Fool’s honor.”",
"questBewilderBossRageTitle": "Beguilement Strike",
- "questBewilderBossRageDescription": "When this gauge fills, The Be-Wilder will unleash its Beguilement Strike on Habitica!",
- "questBewilderDropBumblebeePet": "Bumblebee (Pet)",
- "questBewilderDropBumblebeeMount": "Bumblebee (Mount)",
+ "questBewilderBossRageDescription": "When 'tis gauge be filled, The Be-Wilder will unleash its Beguilement Strike o'er th' seven seas 'o Habitica!",
+ "questBewilderDropBumblebeePet": "Magical Bee (Pet)",
+ "questBewilderDropBumblebeeMount": "Magical Bee (Mount)",
"questBewilderBossRageMarket": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nOh no! Despite our best efforts, we've gotten distracted by the Be-Wilder’s charming illusions and have forgotten to do some of our Dailies! With a cackling cry, the shining bird beats its wings, raising a swarm of mist around Alex the Merchant. When the fog clears, he has been possessed! “Have some free samples!” he shouts gleefully, and begins to hurl exploding eggs and potions at fleeing Habiticans. Not the most favorable of sales, to be sure.\n\nHurry! Let's stay focused on our Dailies to defeat this monster before it possesses someone else.",
"questBewilderBossRageStables": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nAhh!!! Once again the Be-Wilder has dazzled us into neglecting our Dailies, and now it has attacked Matt the Beast Master! With a swirl of mist, Matt transforms into a terrifying winged creature, and all the pets and mounts howl sadly in their stables. Quickly, stay focused on your tasks to defeat this dastardly distraction!",
- "questBewilderBossRageBailey": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nLook out! In the middle of reporting the news, Bailey the Town Crier has been possessed by the Be-Wilder! She lets out an evil, uninformative screech as she rises into the air. Now how will we know what’s going on?\n\nDon't give up... we're so close to defeating this bothersome bird for once and for all!"
+ "questBewilderBossRageBailey": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nLook out! In the middle of reporting the news, Bailey the Town Crier has been possessed by the Be-Wilder! She lets out an evil, uninformative screech as she rises into the air. Now how will we know what’s going on?\n\nDon't give up... we're so close to defeating this bothersome bird for once and for all!",
+ "questFalconText": "Th' Birds o' Preycrastination",
+ "questFalconNotes": "Mt. Habitica is being overshadowed by a looming mountain of To-Dos. It used to be a place to picnic and enjoy a sense of accomplishment, until the neglected tasks grew out of control. Now it's home to fearsome Birds of Preycrastination, foul creatures which stop Habiticans from completing their tasks!
\"It's too hard!\" they caw at @JonArinbjorn and @Onheiron. \"It'll take too long to do right now! It won't make any difference if you wait until tomorrow! Why don't you do something fun instead?\"
No more, you vow. You will climb your personal mountain of To-Dos and defeat the Birds of Preycrastination!",
+ "questFalconCompletion": "Having finally triumphed over the Birds of Preycrastination, you settle down to enjoy the view and your well-earned rest.
\"Wow!\" says @Trogdorina. \"You won!\"
@Squish adds, \"Here, take these eggs I found as a reward.\"",
+ "questFalconBoss": "Birds o' Preycrastination",
+ "questFalconDropFalconEgg": "Falcon (Egg)",
+ "questFalconUnlockText": "Unlocks purchasable Falcon eggs in th' Market",
+ "questTreelingText": "The Tangle Tree",
+ "questTreelingNotes": "It's the annual Garden Competition, and everyone is talking about the mysterious project which @aurakami has promised to unveil. You join the crowd on the day of the big announcement, and marvel at the introduction of a moving tree. @fuzzytrees explains that the tree will help with garden maintenance, showing how it can mow the lawn, trim the hedge and prune the roses all at the same time – until the tree suddenly goes wild, turning its secateurs on its creator! The crowd panics as everyone tries to flee, but you aren't afraid – you leap forward, ready to do battle.",
+ "questTreelingCompletion": "You dust yourself off as the last few leaves drift to the floor. In spite of the upset, the Garden Competition is now safe – although the tree you just reduced to a heap of wood chips won't be winning any prizes! \"Still a few kinks to work out there,\" @PainterProphet says. \"Perhaps someone else would do a better job of training the saplings. Do you fancy a go?\"",
+ "questTreelingBoss": "Tangle Tree",
+ "questTreelingDropTreelingEgg": "Treeling (Egg)",
+ "questTreelingUnlockText": "Unlocks purchasable Treeling eggs in the Market"
}
\ No newline at end of file
diff --git a/common/locales/en@pirate/rebirth.json b/common/locales/en@pirate/rebirth.json
index 060fc542ef..ac7df66fbe 100644
--- a/common/locales/en@pirate/rebirth.json
+++ b/common/locales/en@pirate/rebirth.json
@@ -5,7 +5,7 @@
"rebirthStartOver": "Rebirth starts yer character over from Level 1.",
"rebirthAdvList1": "Ye return t' full Health.",
"rebirthAdvList2": "Ye 'ave no Experience, Doubloons, or Equipment (with th' exception of free items like Mystery Items)",
- "rebirthAdvList3": "Yer Habits, Dailies, an' T'-Dos reset t' yellow, an' streaks reset.",
+ "rebirthAdvList3": "Your Habits, Dailies, and To-Dos reset to yellow, and streaks reset, except for challenge tasks.",
"rebirthAdvList4": "Ye have the startin' class o' Mercenary 'til ye earn a new class.",
"rebirthInherit": "Yer new character inherits a few things from their predecessor:",
"rebirthInList1": "Tasks, history, an' settings remain.",
@@ -24,5 +24,6 @@
"rebirthPop": "Begin a new character at Level 1 whilst retainin' achievements, collectibles, an' tasks with history.",
"rebirthName": "Orb o' Rebirth",
"reborn": "Reborn, max level <%= reLevel %>",
- "confirmReborn": "Be ye positive?"
+ "confirmReborn": "Be ye positive?",
+ "rebirthComplete": "You have been reborn!"
}
\ No newline at end of file
diff --git a/common/locales/en@pirate/settings.json b/common/locales/en@pirate/settings.json
index 586fccc5f5..39201b964d 100644
--- a/common/locales/en@pirate/settings.json
+++ b/common/locales/en@pirate/settings.json
@@ -47,6 +47,7 @@
"customDayStart": "Custom Day Start",
"changeCustomDayStart": "Change Custom Day Start, matey?",
"sureChangeCustomDayStart": "Arr ye sure ye want t' change yer custom day start?",
+ "customDayStartHasChanged": "Your custom day start has changed.",
"nextCron": "Yer Dailies will next reset the first time you use Habitica after <%= time %>. Ye better be sure ye've completed yer Dailies by that time!",
"customDayStartInfo1": "Habitica defaults t' check an' reset yer Dailies at midnight in yer own time zone ev'ry day. Ye c'n customize that time here.",
"misc": "Misc",
@@ -61,12 +62,23 @@
"newUsername": "New Login Name",
"dangerZone": "Danger Zone",
"resetText1": "AHOY! 'tis resets many parts 'o ye account. 'tis be highly discouraged, but some people find it useful in th' beginnin' after playin' wit' th' site fer a short time.",
- "resetText2": "Ye gunna lose all ye levels, doubloons, 'n experience points. All yer tasks gunna be deleted permanently 'n ye gunna lose all 'o ye task's historical data. Ye gunna lose all yer equipment but yer gunna be able t' buy it all back, includin' all limited edition equipment or subscriber Mystery items that ye already own (ye gunna need to be in th' correct class to re-buy class-specific gear). Ye gunna keep yer current class 'n ye pets 'n mounts. Ye might prefer to use an Orb 'o Rebirth instead, which be a much safer option 'n which will preserve yer tasks.",
+ "resetText2": "You will lose all your levels, gold, and experience points. All your tasks (except those from challenges) will be deleted permanently and you will lose all of their historical data. You will lose all your equipment but you will be able to buy it all back, including all limited edition equipment or subscriber Mystery items that you already own (you will need to be in the correct class to re-buy class-specific gear). You will keep your current class and your pets and mounts. You might prefer to use an Orb of Rebirth instead, which is a much safer option and which will preserve your tasks.",
"deleteText": "Be ye sure? 'tis gunna delete ye account forever, 'n it can never be restored! Y e gunna need t' register a new account t' use Habitica again. Banked or spent Gems gunna not be refunded. If ye br absolutely certain, type <%= deleteWord %> into th' text box below.",
"API": "API",
+ "APIv3": "API v3",
+ "APIv2": "API v2 - Deprecated",
"APIText": "Copy these fer use in third parrrty applications. However, think 'o ye API Token like a passcode, 'n do not share it publicly. Ye may occasionally be asked fer yer User ID, but never message yer API Token whar others can spy wit' ye eye it, includin' on Github.",
"APIToken": "API Token ('tis be a passcode - see warnin' above!)",
+ "thirdPartyApps": "Apps o' other crews",
+ "dataToolDesc": "A webpage that be showin' ye certain information from yer Habitica account, such as statistics about yer tasks, equipment, 'n skills.",
+ "beeminder": "Beeminder",
+ "beeminderDesc": "Let Beeminder automatically monitor yer Habitica To-Dos. Ye can commit to maintaining a target number of To-Dos completed per sundown or per week, or ye can commit to gradually reducing yer remaining number o' uncompleted To-Dos. (By \"commit\" Beeminder means under threat of paying actual money! But ye may also just like Beeminder's fancy graphs.)",
+ "chromeChatExtension": "Chrome Chat Extension",
+ "chromeChatExtensionDesc": "Th' Chrome Chat Extension fer Habitica adds n' intuitive chat box t' all o' habitica.com. It allows pirates to chat in th' Tavern, their party, 'n any guilds they be in.",
+ "otherExtensions": "Oth'r Extensions",
+ "otherDesc": "Find oth'r apps, extensions, 'n tools on th' Habitica wiki.",
"resetDo": "Do it, reset me account!",
+ "resetComplete": "Reset complete!",
"fixValues": "Fix Values",
"fixValuesText1": "If ye've encountered a bug or made a mistake that unfairly changed yer character (damage ye shouldn't have taken, Doubloons ye didn't really earn, etc.), ye can manually correct yer numbers here. Yarr, 'tis makes it possible to cheat: use 'tis weapon wisely, or ye'll sabotage yer own habit-buildin'!",
"fixValuesText2": "Note that ye cannot restore Streaks on individual tasks here. T' do that, edit th' Daily 'n be off to Advanced Options, whar ye gunna find a Restore Streak field.",
@@ -96,6 +108,7 @@
"emailNotifications": "Email Notifications",
"wonChallenge": "Ye won a Challenge!",
"newPM": "Received Private Message",
+ "sentGems": "Sent gems!",
"giftedGems": "Gifted Sapphires",
"giftedGemsInfo": "<%= amount %> Sapphires - by <%= name %>",
"giftedSubscription": "Gifted Subscription",
@@ -135,6 +148,11 @@
"webhooks": "Webhooks",
"enabled": "Enabled",
"webhookURL": "Webhook URL",
+ "invalidUrl": "invalid url",
+ "invalidEnabled": "the \"enabled\" parameter should be a boolean",
+ "regIdRequired": "RegId is required",
+ "pushDeviceAdded": "Push device added successfully",
+ "pushDeviceAlreadyAdded": "The user already has the push device",
"add": "Add",
"buyGemsGoldCap": "Cap raised to <%= amount %>",
"mysticHourglass": "<%= amount %> Mystic Hourglass",
@@ -149,5 +167,5 @@
"amazonPayments": "Amazon Payments",
"timezone": "Time Zone",
"timezoneUTC": "Habitica uses the time zone set on yer PC, which is: <%= utc %>",
- "timezoneInfo": "If that time zone is wrong, first reload this page usin' yer browser's reload or refresh button to ensure that Habitica has the most recent information. If it is still wrong, adjust the time zone on your PC and then reload this page again.
If you use Habitica on other PCs or mobile devices, the time zone must be the same on them all. If your Dailies have been resetting at the wrong time, repeat this check on all other PCs and on a browser on your mobile devices."
+ "timezoneInfo": "If that time zone is wrong, first reload this page using your browser's reload or refresh button to ensure that Habitica has the most recent information. If it is still wrong, adjust the time zone on your PC and then reload this page again.
If you use Habitica on other PCs or mobile devices, the time zone must be the same on them all. If your Dailies have been resetting at the wrong time, repeat this check on all other PCs and on a browser on your mobile devices."
}
\ No newline at end of file
diff --git a/common/locales/en@pirate/spells.json b/common/locales/en@pirate/spells.json
index 207b9dc918..e850c5644c 100644
--- a/common/locales/en@pirate/spells.json
+++ b/common/locales/en@pirate/spells.json
@@ -35,8 +35,8 @@
"spellSpecialSnowballAuraNotes": "Throw a snowball at a crew mate! Could anythin' go wrong? Lasts 'til mate's new day.",
"spellSpecialSaltText": "Sea Salt",
"spellSpecialSaltNotes": "Someone has snowballed ye. Ha ha, extra hardyharhar. Now get this snow off me!",
- "spellSpecialSpookDustText": "Spooky Sparkles",
- "spellSpecialSpookDustNotes": "Turn a friend into a floating blanket with eyes!",
+ "spellSpecialSpookySparklesText": "Spooky Sparkles",
+ "spellSpecialSpookySparklesNotes": "Turn a friend into a floating blanket with eyes!",
"spellSpecialOpaquePotionText": "Murky Potion",
"spellSpecialOpaquePotionNotes": "Cancel th' effects 'o Spooky Sparkles.",
"spellSpecialShinySeedText": "Shiny Seed",
@@ -46,5 +46,11 @@
"spellSpecialSeafoamText": "Seafoam",
"spellSpecialSeafoamNotes": "Turn a friend into a sea creature!",
"spellSpecialSandText": "Sand",
- "spellSpecialSandNotes": "Cancel th' effects o' Seafoam."
+ "spellSpecialSandNotes": "Cancel th' effects o' Seafoam.",
+ "spellNotFound": "Skill \"<%= spellId %>\" not found.",
+ "partyNotFound": "Party not found",
+ "targetIdUUID": "\"targetId\" must be a valid User ID.",
+ "challengeTasksNoCast": "Casting a skill on challenge tasks is not allowed.",
+ "spellNotOwned": "You don't own this skill.",
+ "spellLevelTooHigh": "You must be level <%= level %> to use this skill."
}
\ No newline at end of file
diff --git a/common/locales/en@pirate/subscriber.json b/common/locales/en@pirate/subscriber.json
index 1e8c3df388..95166c4f72 100644
--- a/common/locales/en@pirate/subscriber.json
+++ b/common/locales/en@pirate/subscriber.json
@@ -4,6 +4,8 @@
"subDescription": "Buy Gems wit' gold, get monthly mystery loot, retain progress history, double daily drop-caps, support th' devs. Click fer more info.",
"buyGemsGold": "Buy Gems with Doubloons",
"buyGemsGoldText": "Alexander th' Merchant 'll sell ye gems at a cost of <%= gemCost %> gold a gem. His monthly shipments are initially capped at <%= gemLimit %>; gems a month, but this cap goes up by 5 gems f'r every three months of consecutive subscription, up to a maximum of 50 gems a month!",
+ "mustSubscribeToPurchaseGems": "Must subscribe to purchase gems with GP",
+ "reachedGoldToGemCap": "You've reached the Gold=>Gem conversion cap <%= convCap %> for this month. We have this to prevent abuse / farming. The cap will reset within the first three days of next month.",
"retainHistory": "Retain additional history entries",
"retainHistoryText": "Makes completed To-Dos an' task history available fer longer.",
"doubleDrops": "Daily loot-caps doubled",
@@ -29,6 +31,7 @@
"manageSub": "Click to manage subscription",
"cancelSub": "Cancel Subscription",
"canceledSubscription": "Canceled Subscription",
+ "cancelingSubscription": "Canceling the subscription",
"adminSub": "Administrator Subscriptions",
"morePlans": "More Plans Be Comin' Soon!",
"organizationSub": "Private Organization",
@@ -73,6 +76,9 @@
"timeTravelersPopover": "We see ye have a Mystic Hourglass, so we will happily travel back in time for you! Please choose the pet, mount, or Mystery Item Set ye'd like. Ye can see a list of th' past item sets here! If those don't satisfy ye, perhaps ye'd be interested in one of our fashionably futuristic Steampunk Item Sets?",
"timeTravelersAlreadyOwned": "Ahoy! Ye already own everything the Time Travelers currently offer. Many thanks for supportin' th' site!",
"mysticHourglassPopover": "A Mystic Hourglass allows ye to purchase certain limited-time items, such as monthly Mystery Item Sets and awards from world bosses, from th' past!",
+ "mysterySetNotFound": "Mystery set not found, or set already owned.",
+ "mysteryItemIsEmpty": "Mystery items are empty",
+ "mysteryItemOpened": "Mystery item opened.",
"mysterySet201402": "Winged Messenger Set",
"mysterySet201403": "Forest Walker Set",
"mysterySet201404": "Twilight Butterfly Set",
@@ -99,6 +105,8 @@
"mysterySet201601": "Champion of Resolution Set",
"mysterySet201602": "Heartbreaker Set",
"mysterySet201603": "Lucky Clover Set",
+ "mysterySet201604": "Leaf Warrior Set",
+ "mysterySet201605": "Marching Bard Set",
"mysterySet301404": "Steampunk Standard Set",
"mysterySet301405": "Steampunk Accessories Set",
"mysterySetwondercon": "Wondercon",
@@ -110,9 +118,25 @@
"hourglassBuyItemConfirm": "Buy this item for 1 Mystic Hourglass?",
"petsAlreadyOwned": "Ahoy! Ye already own this pet.",
"mountsAlreadyOwned": "Ahoy! Ye already own this mount.",
- "typeNotAllowedHourglass": "Item type be not supported fer purchase with th' Mystic Hourglass. Allowed types arrre:",
+ "typeNotAllowedHourglass": "Item type not supported for purchase with Mystic Hourglass. Allowed types: <%= allowedTypes %>",
"petsNotAllowedHourglass": "This pet be not available for purchase with th' Mystic Hourglass.",
"mountsNotAllowedHourglass": "This mount be not available for purchase with th' Mystic Hourglass.",
"hourglassPurchase": "Ye purchased an item using a Mystic Hourglass!",
- "hourglassPurchaseSet": "Ye purchased an item set using a Mystic Hourglass!"
+ "hourglassPurchaseSet": "Ye purchased an item set using a Mystic Hourglass!",
+ "missingUnsubscriptionCode": "Missing unsubscription code.",
+ "missingSubscription": "User does not have a plan subscription",
+ "missingSubscriptionCode": "Missing subscription code. Possible values: basic_earned, basic_3mo, basic_6mo, google_6mo, basic_12mo.",
+ "cannotDeleteActiveAccount": "You have an active subscription, cancel your plan before deleting your account.",
+ "paymentNotSuccessful": "The payment was not successful",
+ "planNotActive": "The plan hasn't activated yet (due to a PayPal bug). It will begin <%= nextBillingDate %>, after which you can cancel to retain your full benefits",
+ "notAllowedHourglass": "Pet/Mount not available for purchase with Mystic Hourglass.",
+ "readCard": "<%= cardType %> has been read",
+ "cardTypeRequired": "Card type required",
+ "cardTypeNotAllowed": "Unknown card type.",
+ "invalidCoupon": "Invalid coupon code.",
+ "couponUsed": "Coupon code already used.",
+ "noSudoAccess": "You don't have sudo access.",
+ "couponCodeRequired": "The coupon code is required.",
+ "eventRequired": "\"req.params.event\" is required.",
+ "countRequired": "\"req.query.count\" is required."
}
\ No newline at end of file
diff --git a/common/locales/en@pirate/tasks.json b/common/locales/en@pirate/tasks.json
index 30e6bf5943..dc4efa86f5 100644
--- a/common/locales/en@pirate/tasks.json
+++ b/common/locales/en@pirate/tasks.json
@@ -73,6 +73,7 @@
"clearTags": "Clear",
"hideTags": "Hide",
"showTags": "Show",
+ "toRequired": "You must supply a to value",
"startDate": "Start Date",
"startDateHelpTitle": "When should this task start?",
"startDateHelp": "Set th' date for which this task takes effect. Will not be due on earlier days.",
@@ -88,8 +89,9 @@
"fortifyName": "Fortify Potion",
"fortifyPop": "Return all tasks t' neutral value (yellow color), 'n restore all lost Health.",
"fortify": "Fortify",
- "fortifyText": "Fortify will return all yer tasks t' a neutral (yellow) state, as if ye'd jus' added 'em, an' top yer Health off t' full. This is great if all yer red tasks be makin' th' game too hard, or all yer blue tasks be making th' game too easy. If startin' fresh sounds much more motivatin', spend th' Gems an' catch a reprieve!",
+ "fortifyText": "Fortify will return all your tasks, except challenge tasks, to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!",
"confirmFortify": "Be ye positive?",
+ "fortifyComplete": "Fortify complete!",
"sureDelete": "Ye be sure ye want t' delete th' <%= taskType %> wi' th' text \"<%= taskText %>\"?",
"streakCoins": "Streak Bonus!",
"pushTaskToTop": "Push task t' top. Hold ctrl or cmd t' push down t' Davey Jones' locker.",
@@ -112,5 +114,18 @@
"rewardHelp2": "Equipment affects yer stats (<%= linkStart %>Avatar > Stats<%= linkEnd %>).",
"rewardHelp3": "Special equipment will appear here durin' World Events.",
"rewardHelp4": "Don't be afraid t' set custom Rewards! Check out some samples here.",
- "clickForHelp": "Click fer help"
+ "clickForHelp": "Click fer help",
+ "taskIdRequired": "\"taskId\" must be a valid UUID.",
+ "taskNotFound": "Task not found.",
+ "invalidTaskType": "Task type must be one of \"habit\", \"daily\", \"todo\", \"reward\".",
+ "cantDeleteChallengeTasks": "A task belonging to a challenge can't be deleted.",
+ "checklistOnlyDailyTodo": "Checklists are supported only on dailies and todos",
+ "checklistItemNotFound": "No checklist item was found with given id.",
+ "itemIdRequired": "\"itemId\" must be a valid UUID.",
+ "tagNotFound": "No tag item was found with given id.",
+ "tagIdRequired": "\"tagId\" must be a valid UUID corresponding to a tag belonging to the user.",
+ "positionRequired": "\"position\" is required and must be a number.",
+ "cantMoveCompletedTodo": "Can't move a completed todo.",
+ "directionUpDown": "\"direction\" is required and must be 'up' or 'down'",
+ "alreadyTagged": "The task is already tagged with given tag."
}
\ No newline at end of file
diff --git a/common/locales/en_GB/backgrounds.json b/common/locales/en_GB/backgrounds.json
index f50f8067fb..67ce671c6b 100644
--- a/common/locales/en_GB/backgrounds.json
+++ b/common/locales/en_GB/backgrounds.json
@@ -156,9 +156,16 @@
"backgroundStoneCircleNotes": "Cast spells in a Circle of Stones.",
"backgrounds042016": "SET 23: Released April 2016",
"backgroundArcheryRangeText": "Archery Range",
- "backgroundArcheryRangeNotes": "Practice on the Archery Range.",
+ "backgroundArcheryRangeNotes": "Practise on the Archery Range.",
"backgroundGiantFlowersText": "Giant Flowers",
"backgroundGiantFlowersNotes": "Frolic atop Giant Flowers.",
"backgroundRainbowsEndText": "End of the Rainbow",
- "backgroundRainbowsEndNotes": "Discover gold at the End of the Rainbow."
+ "backgroundRainbowsEndNotes": "Discover gold at the End of the Rainbow.",
+ "backgrounds052016": "SET 24: Released May 2016",
+ "backgroundBeehiveText": "Beehive",
+ "backgroundBeehiveNotes": "Buzz and dance in a Beehive.",
+ "backgroundGazeboText": "Gazebo",
+ "backgroundGazeboNotes": "Battle a Gazebo.",
+ "backgroundTreeRootsText": "Tree Roots",
+ "backgroundTreeRootsNotes": "Explore the Tree Roots."
}
\ No newline at end of file
diff --git a/common/locales/en_GB/challenge.json b/common/locales/en_GB/challenge.json
index 3b0ee8f47f..d2522308e2 100644
--- a/common/locales/en_GB/challenge.json
+++ b/common/locales/en_GB/challenge.json
@@ -63,5 +63,21 @@
"congratulations": "Congratulations!",
"hurray": "Hurray!",
"noChallengeOwner": "no owner",
- "noChallengeOwnerPopover": "This challenge does not have an owner because the person who created the challenge deleted their account."
+ "noChallengeOwnerPopover": "This challenge does not have an owner because the person who created the challenge deleted their account.",
+ "challengeMemberNotFound": "User not found among challenge's members",
+ "onlyGroupLeaderChal": "Only the group leader can create challenges",
+ "tavChalsMinPrize": "Prize must be at least 1 Gem for Tavern challenges.",
+ "cantAfford": "You can't afford this prize. Purchase more gems or lower the prize amount.",
+ "challengeIdRequired": "\"challengeId\" must be a valid UUID.",
+ "winnerIdRequired": "\"winnerId\" must be a valid UUID.",
+ "challengeNotFound": "Challenge not found.",
+ "onlyLeaderDeleteChal": "Only the challenge leader can delete it.",
+ "onlyLeaderUpdateChal": "Only the challenge leader can update it.",
+ "winnerNotFound": "Winner with id \"<%= userId %>\" not found or not part of the challenge.",
+ "noCompletedTodosChallenge": "\"includeComepletedTodos\" is not supported when fetching challenge tasks.",
+ "userTasksNoChallengeId": "When \"tasksOwner\" is \"user\" \"challengeId\" can't be passed.",
+ "onlyChalLeaderEditTasks": "Tasks belonging to a challenge can only be edited by the leader.",
+ "userAlreadyInChallenge": "User is already participating in this challenge.",
+ "cantOnlyUnlinkChalTask": "Only broken challenges tasks can be un-linked. ",
+ "shortNameTooShort": "Tag Name must have at least 3 characters."
}
\ No newline at end of file
diff --git a/common/locales/en_GB/character.json b/common/locales/en_GB/character.json
index 2215a0fd13..a759e7f243 100644
--- a/common/locales/en_GB/character.json
+++ b/common/locales/en_GB/character.json
@@ -1,4 +1,5 @@
{
+ "communityGuidelinesWarning": "Please keep in mind that your Display Name, profile photo, and blurb must comply with the Community Guidelines (e.g. no profanity, no adult topics, no insults, etc). If you have any questions about whether or not something is appropriate, feel free to email leslie@habitica.com!",
"statsAch": "Stats & Achievements",
"profile": "Profile",
"avatar": "Customise Avatar",
@@ -109,6 +110,7 @@
"mage": "Mage",
"mystery": "Mystery",
"changeClass": "Change Class, Refund Attribute Points",
+ "lvl10ChangeClass": "To change class you must be at least level 10.",
"levelPopover": "Each level earns you one point to assign to an attribute of your choice. You can do so manually, or let the game decide for you using one of the Automatic Allocation options.",
"unallocated": "Unallocated Attribute Points",
"haveUnallocated": "You have <%= points %> unallocated Attribute Point(s)",
@@ -164,5 +166,7 @@
"int": "INT",
"showQuickAllocation": "Show stats allocation",
"hideQuickAllocation": "Hide stats allocation",
- "quickAllocationLevelPopover": "Each level earns you one point to assign to an attribute of your choice. You can do so manually, or let the game decide for you using one of the Automatic Allocation options found in User -> Stats."
+ "quickAllocationLevelPopover": "Each level earns you one point to assign to an attribute of your choice. You can do so manually, or let the game decide for you using one of the Automatic Allocation options found in User -> Stats.",
+ "invalidAttribute": "\"<%= attr %>\" is not a valid attribute.",
+ "notEnoughAttrPoints": "You don't have enough attribute points."
}
\ No newline at end of file
diff --git a/common/locales/en_GB/content.json b/common/locales/en_GB/content.json
index 366ee70abb..fe7af68ebb 100644
--- a/common/locales/en_GB/content.json
+++ b/common/locales/en_GB/content.json
@@ -113,6 +113,12 @@
"questEggSnailText": "Snail",
"questEggSnailMountText": "Snail",
"questEggSnailAdjective": "a slow but steady",
+ "questEggFalconText": "Falcon",
+ "questEggFalconMountText": "Falcon",
+ "questEggFalconAdjective": "a swift",
+ "questEggTreelingText": "Treeling",
+ "questEggTreelingMountText": "Treeling",
+ "questEggTreelingAdjective": "a leafy",
"eggNotes": "Find a hatching potion to pour on this egg, and it will hatch into <%= eggAdjective(locale) %> <%= eggText(locale) %>.",
"hatchingPotionBase": "Base",
"hatchingPotionWhite": "White",
@@ -126,6 +132,7 @@
"hatchingPotionGolden": "Golden",
"hatchingPotionSpooky": "Spooky",
"hatchingPotionPeppermint": "Peppermint",
+ "hatchingPotionFloral": "Floral",
"hatchingPotionNotes": "Pour this on an egg, and it will hatch as a <%= potText(locale) %> pet.",
"premiumPotionAddlNotes": "Not usable on quest pet eggs.",
"foodMeat": "Meat",
diff --git a/common/locales/en_GB/contrib.json b/common/locales/en_GB/contrib.json
index 230956f41e..450ebf46a0 100644
--- a/common/locales/en_GB/contrib.json
+++ b/common/locales/en_GB/contrib.json
@@ -35,8 +35,12 @@
"hallContributors": "Hall of Contributors",
"hallPatrons": "Hall of Patrons",
"rewardUser": "Reward User",
- "UUID": "UUID",
+ "UUID": "User ID",
"loadUser": "Load User",
+ "noAdminAccess": "You don't have admin access.",
+ "pageMustBeNumber": "req.query.page must be a number",
+ "userNotFound": "User not found.",
+ "invalidUUID": "UUID must be valid",
"title": "Title",
"moreDetails": "More details (1-7)",
"moreDetails2": "more details (8-9)",
@@ -52,8 +56,8 @@
"visitHeroes": "Visit the Hall of Heroes (contributors and backers)",
"conLearn": "Learn more about contributor rewards",
"conLearnHow": "Learn how to contribute to Habitica",
- "surveysSingle": "Helped Habitica grow by filling in a survey. There are no active surveys.",
- "surveysMultiple": "Helped Habitica grow by filling in <%= surveys %> surveys. There are no active surveys.",
+ "surveysSingle": "Helped Habitica grow, either by filling out a survey or helping with a major testing effort. Thank you!",
+ "surveysMultiple": "Helped Habitica grow on <%= surveys %> occasions, either by filling out a survey or helping with a major testing effort. Thank you!",
"currentSurvey": "Current Survey",
"surveyWhen": "The badge will be awarded to all participants when surveys have been processed, in late March.",
"blurbInbox": "This is where your private messages are stored! You can send someone a message by clicking on the envelope icon next to their name in Tavern, Party, or Guild Chat. If you've received an inappropriate PM, you should email a screenshot of it to Lemoness (leslie@habitica.com)",
diff --git a/common/locales/en_GB/death.json b/common/locales/en_GB/death.json
index 7e13d289d8..9a7b0a26c9 100644
--- a/common/locales/en_GB/death.json
+++ b/common/locales/en_GB/death.json
@@ -12,5 +12,6 @@
"losingHealthQuickly": "Losing Health quickly?",
"lowHealthTips3": "Incomplete Dailies hurt you overnight, so be careful not to add too many at first!",
"lowHealthTips4": "If a Daily isn't due on a certain day, you can disable it by clicking the pencil icon.",
- "goodLuck": "Good luck!"
+ "goodLuck": "Good luck!",
+ "cannotRevive": "Cannot revive if not dead"
}
\ No newline at end of file
diff --git a/common/locales/en_GB/front.json b/common/locales/en_GB/front.json
index 76922b4d44..55791369df 100644
--- a/common/locales/en_GB/front.json
+++ b/common/locales/en_GB/front.json
@@ -28,6 +28,7 @@
"communityReddit": "Reddit",
"companyAbout": "How It Works",
"companyBlog": "Blog",
+ "devBlog": "Developer Blog",
"companyDonate": "Donate",
"companyExtensions": "Extensions",
"companyPrivacy": "Privacy",
@@ -51,6 +52,7 @@
"featureSocialHeading": "Social play",
"featuredIn": "Featured in",
"featuresHeading": "We also feature...",
+ "footerDevs": "Developers",
"footerCommunity": "Community",
"footerCompany": "Company",
"footerMobile": "Mobile",
@@ -182,6 +184,7 @@
"zelahQuote": "With [Habitica], I can be persuaded to go to bed on time by the thought of gaining points for an early night or losing health for a late one!",
"reportAccountProblems": "Report Account Problems",
"reportCommunityIssues": "Report Community Issues",
+ "subscriptionPaymentIssues": "Subscription and Payment Issues",
"generalQuestionsSite": "General Questions about the Site",
"businessInquiries": "Business Inquiries",
"merchandiseInquiries": "Merchandise Inquiries",
@@ -222,5 +225,32 @@
"altAttrWebstorm": "WebStorm",
"altAttrGithub": "GitHub",
"altAttrTrello": "Trello",
- "altAttrSlack": "Slack"
+ "altAttrSlack": "Slack",
+ "missingAuthHeaders": "Missing authentication headers.",
+ "missingAuthParams": "Missing authentication parameters.",
+ "missingUsernameEmail": "Missing username or email.",
+ "missingEmail": "Missing email.",
+ "missingUsername": "Missing username.",
+ "missingPassword": "Missing password.",
+ "missingNewPassword": "Missing new password.",
+ "wrongPassword": "Wrong password.",
+ "notAnEmail": "Invalid email address.",
+ "emailTaken": "Email address is already used in an account.",
+ "newEmailRequired": "Missing new email address.",
+ "usernameTaken": "Username already taken.",
+ "passwordConfirmationMatch": "Password confirmation doesn't match password.",
+ "invalidLoginCredentials": "Incorrect username and/or email and/or password.",
+ "passwordReset": "If we have your email on file, your password reset link has been sent to your email.",
+ "passwordResetEmailSubject": "Password Reset for Habitica",
+ "passwordResetEmailText": "Password for <%= username %> has been reset to <%= newPassword %> . Important! Both username and password are case-sensitive -- you must enter both exactly as shown here. We recommend copying and pasting both instead of typing them. Log in at <%= baseUrl %>. After you have logged in, head to <%= baseUrl %>/#/options/settings/settings and change your password.",
+ "passwordResetEmailHtml": "Password for <%= username %> has been reset to <%= newPassword %>.
Important! Both username and password are case-sensitive -- you must enter both exactly as shown here. We recommend copying and pasting both instead of typing them.
Log in at <%= baseUrl %>. After you have logged in, head to <%= baseUrl %>/#/options/settings/settings and change your password.",
+ "invalidLoginCredentialsLong": "Uh-oh - your username or password is incorrect.\n- Make sure your username or email is typed correctly.\n- You may have signed up with Facebook, not email. Double-check by trying Facebook login.\n- If you forgot your password, click \"Forgot Password\".",
+ "invalidCredentials": "There is no account that uses those credentials.",
+ "accountSuspended": "Account has been suspended, please contact leslie@habitica.com with your User ID \"<%= userId %>\" for assistance.",
+ "onlyFbSupported": "Only Facebook is supported currently.",
+ "cantDetachFb": "Account lacks another authentication method, can't detach Facebook.",
+ "onlySocialAttachLocal": "Local authentication can be added to only a social account.",
+ "invalidReqParams": "Invalid request parameters.",
+ "memberIdRequired": "\"member\" must be a valid UUID.",
+ "heroIdRequired": "\"heroId\" must be a valid UUID."
}
\ No newline at end of file
diff --git a/common/locales/en_GB/gear.json b/common/locales/en_GB/gear.json
index a20cfa82cf..2656c124d3 100644
--- a/common/locales/en_GB/gear.json
+++ b/common/locales/en_GB/gear.json
@@ -189,6 +189,8 @@
"weaponArmoireMiningPickaxNotes": "Mine the maximum amount of gold from your tasks! Increases Perception by <%= per %>. Enchanted Armoire: Miner Set (Item 3 of 3).",
"weaponArmoireBasicLongbowText": "Basic Longbow",
"weaponArmoireBasicLongbowNotes": "A serviceable hand-me-down bow. Increases Strength by <%= str %>. Enchanted Armoire: Basic Archer Set (Item 1 of 3).",
+ "weaponArmoireHabiticanDiplomaText": "Habitican Diploma",
+ "weaponArmoireHabiticanDiplomaNotes": "A certificate of significant achievement -- well done! Increases Intelligence by <%= int %>. Enchanted Armoire: Graduate Set (Item 1 of 3).",
"armor": "armour",
"armorBase0Text": "Plain Clothing",
"armorBase0Notes": "Ordinary clothing. Confers no benefit.",
@@ -364,6 +366,10 @@
"armorMystery201512Notes": "Summon the icy flames of winter! Confers no benefit. December 2015 Subscriber Item.",
"armorMystery201603Text": "Lucky Suit",
"armorMystery201603Notes": "This suit is sewn from thousands of four-leafed clovers! Confers no benefit. March 2016 Subscriber Item.",
+ "armorMystery201604Text": "Armour o' Leaves",
+ "armorMystery201604Notes": "You, too, can be a small but fearsome leaf puff. Confers no benefit. April 2016 Subscriber Item.",
+ "armorMystery201605Text": "Marching Bard Uniform",
+ "armorMystery201605Notes": "Unlike the traditional bards who join adventuring parties, bards who join Habitican marching bands are known for grand parades, not dungeon raids. Confers no benefit. May 2016 Subscriber Item.",
"armorMystery301404Text": "Steampunk Suit",
"armorMystery301404Notes": "Dapper and dashing, wot! Confers no benefit. February 3015 Subscriber Item.",
"armorArmoireLunarArmorText": "Soothing Lunar Armour",
@@ -392,8 +398,10 @@
"armorArmoireJesterCostumeNotes": "Tra-la-la! Despite the look of this costume, you are no fool. Increases Intelligence by <%= int %>. Enchanted Armoire: Jester Set (Item 2 of 3).",
"armorArmoireMinerOverallsText": "Miner Overalls",
"armorArmoireMinerOverallsNotes": "They may seem worn, but they are enchanted to repel dirt. Increases Constitution by <%= con %>. Enchanted Armoire: Miner Set (Item 2 of 3).",
- "armorArmoireBasicArcherArmorText": "Basic Archer Armor",
+ "armorArmoireBasicArcherArmorText": "Basic Archer Armour",
"armorArmoireBasicArcherArmorNotes": "This camouflaged vest lets you slip unnoticed through the forests. Increases Perception by <%= per %>. Enchanted Armoire: Basic Archer Set (Item 2 of 3).",
+ "armorArmoireGraduateRobeText": "Academic Robe",
+ "armorArmoireGraduateRobeNotes": "Congratulations! This weighty robe hangs heavy with all the knowledge you have accrued. Increases Intelligence by <%= int %>. Enchanted Armoire: Graduate Set (Item 2 of 3).",
"headgear": "headgear",
"headBase0Text": "No Helm",
"headBase0Notes": "No headgear.",
@@ -565,6 +573,10 @@
"headMystery201602Notes": "Shield your identity from all your admirers. Confers no benefit. February 2016 Subscriber Item.",
"headMystery201603Text": "Lucky Hat",
"headMystery201603Notes": "This top hat is a magical good-luck charm. Confers no benefit. March 2016 Subscriber Item.",
+ "headMystery201604Text": "Crown o' Flowers",
+ "headMystery201604Notes": "These woven flowers make a surprisingly strong helm! Confers no benefit. April 2016 Subscriber Item.",
+ "headMystery201605Text": "Marching Bard Hat",
+ "headMystery201605Notes": "Seventy-six dragons led the big parade, with a hundred and ten gryphons close at hand! Confers no benefit. May 2016 Subscriber Item.",
"headMystery301404Text": "Fancy Top Hat",
"headMystery301404Notes": "A fancy top hat for the finest of gentlefolk! January 3015 Subscriber Item. Confers no benefit.",
"headMystery301405Text": "Basic Top Hat",
@@ -613,6 +625,8 @@
"headArmoireMinerHelmetNotes": "Protect your head from falling tasks! Increases Intelligence by <%= int %>. Enchanted Armoire: Miner Set (Item 1 of 3).",
"headArmoireBasicArcherCapText": "Basic Archer Cap",
"headArmoireBasicArcherCapNotes": "No archer would be complete without a jaunty cap! Increases Perception by <%= per %>. Enchanted Armoire: Basic Archer Set (Item 3 of 3).",
+ "headArmoireGraduateCapText": "Mortar Board",
+ "headArmoireGraduateCapNotes": "Congratulations! Your deep thoughts have earned you this thinking cap. Increases Intelligence by <%= int %>. Enchanted Armoire: Graduate Set (Item 3 of 3).",
"offhand": "shield-hand item",
"shieldBase0Text": "No Shield-Hand Equipment",
"shieldBase0Notes": "No shield or second weapon.",
@@ -673,7 +687,7 @@
"shieldSpecialWinter2015HealerText": "Soothing Shield",
"shieldSpecialWinter2015HealerNotes": "This shield deflects the freezing wind. Increases Constitution by <%= con %>. Limited Edition 2014-2015 Winter Gear.",
"shieldSpecialSpring2015RogueText": "Exploding Squeak",
- "shieldSpecialSpring2015RogueNotes": "Don't let the sound fool you - these explosives pack a punch. Increases Strength <%= str %>. Limited Edition 2015 Spring Gear.",
+ "shieldSpecialSpring2015RogueNotes": "Don't let the sound fool you - these explosives pack a punch. Increases Strength by <%= str %>. Limited Edition 2015 Spring Gear.",
"shieldSpecialSpring2015WarriorText": "Dish Discus",
"shieldSpecialSpring2015WarriorNotes": "Hurl it at your enemies.... or just hold it, because it will fill up with tasty grub at dinnertime. Increases Constitution by <%= con %>. Limited Edition 2015 Spring Gear.",
"shieldSpecialSpring2015HealerText": "Patterned Pillow",
@@ -716,6 +730,8 @@
"shieldArmoireDragonTamerShieldNotes": "Distract enemies with this dragon-shaped shield. Increases Perception by <%= per %>. Enchanted Armoire: Dragon Tamer Set (Item 2 of 3).",
"shieldArmoireMysticLampText": "Mystic Lamp",
"shieldArmoireMysticLampNotes": "Light the darkest caves with this mystic lamp! Increases Perception by <%= per %>. Enchanted Armoire: Independent Item.",
+ "shieldArmoireFloralBouquetText": "Bouquet o' Flowers",
+ "shieldArmoireFloralBouquetNotes": "Not much help in battle, but aren't they beautiful? Increases Constitution by <%= con %>. Enchanted Armoire: Independent Item.",
"back": "Back Accessory",
"backBase0Text": "No Back Accessory",
"backBase0Notes": "No Back Accessory.",
@@ -820,6 +836,20 @@
"eyewear": "Eyewear",
"eyewearBase0Text": "No Eyewear",
"eyewearBase0Notes": "No Eyewear.",
+ "eyewearSpecialBlackTopFrameText": "Black Standard Eyeglasses",
+ "eyewearSpecialBlackTopFrameNotes": "Glasses with a black frame above the lenses. Confers no benefit.",
+ "eyewearSpecialBlueTopFrameText": "Blue Standard Eyeglasses",
+ "eyewearSpecialBlueTopFrameNotes": "Glasses with a blue frame above the lenses. Confers no benefit.",
+ "eyewearSpecialGreenTopFrameText": "Green Standard Eyeglasses",
+ "eyewearSpecialGreenTopFrameNotes": "Glasses with a green frame above the lenses. Confers no benefit.",
+ "eyewearSpecialPinkTopFrameText": "Pink Standard Eyeglasses",
+ "eyewearSpecialPinkTopFrameNotes": "Glasses with a pink frame above the lenses. Confers no benefit.",
+ "eyewearSpecialRedTopFrameText": "Red Standard Eyeglasses",
+ "eyewearSpecialRedTopFrameNotes": "Glasses with a red frame above the lenses. Confers no benefit.",
+ "eyewearSpecialWhiteTopFrameText": "White Standard Eyeglasses",
+ "eyewearSpecialWhiteTopFrameNotes": "Glasses with a white frame above the lenses. Confers no benefit.",
+ "eyewearSpecialYellowTopFrameText": "Yellow Standard Eyeglasses",
+ "eyewearSpecialYellowTopFrameNotes": "Glasses with a yellow frame above the lenses. Confers no benefit.",
"eyewearSpecialSummerRogueText": "Roguish Eyepatch",
"eyewearSpecialSummerRogueNotes": "It doesn't take a scallywag to see how stylish this is! Confers no benefit. Limited Edition 2014 Summer Gear.",
"eyewearSpecialSummerWarriorText": "Dashing Eyepatch",
diff --git a/common/locales/en_GB/generic.json b/common/locales/en_GB/generic.json
index 6bdefc0429..fc7ffa0d42 100644
--- a/common/locales/en_GB/generic.json
+++ b/common/locales/en_GB/generic.json
@@ -137,7 +137,7 @@
"achievementStressbeastText": "Helped defeat the Abominable Stressbeast during the 2014 Winter Wonderland Event!",
"achievementBurnout": "Saviour of the Flourishing Fields",
"achievementBurnoutText": "Helped defeat Burnout and restore the Exhaust Spirits during the 2015 Fall Festival Event!",
- "achievementBewilder": "Savior of Mistiflying",
+ "achievementBewilder": "Saviour of Mistiflying",
"achievementBewilderText": "Helped defeat the Be-Wilder during the 2016 Spring Fling Event!",
"checkOutProgress": "Check out my progress in Habitica!",
"cardReceived": "Received a card!",
diff --git a/common/locales/en_GB/groups.json b/common/locales/en_GB/groups.json
index 21c72afa57..d5109da655 100644
--- a/common/locales/en_GB/groups.json
+++ b/common/locales/en_GB/groups.json
@@ -92,6 +92,7 @@
"send": "Send",
"messageSentAlert": "Message sent",
"pmHeading": "Private message to <%= name %>",
+ "pmsMarkedRead": "Your private messages have been marked as read",
"clearAll": "Delete All Messages",
"confirmDeleteAllMessages": "Are you sure you want to delete all messages in your inbox? Other users will still see messages you have sent to them.",
"optOutPopover": "Don't like private messages? Click to completely opt out",
@@ -99,6 +100,15 @@
"unblock": "Un-block",
"pm-reply": "Send a reply",
"inbox": "Inbox",
+ "messageRequired": "A message is required.",
+ "toUserIDRequired": "A User ID is required",
+ "gemAmountRequired": "A number of gems is required",
+ "notAuthorizedToSendMessageToThisUser": "Can't send message to this user.",
+ "privateMessageGiftIntro": "Hello <%= receiverName %>, <%= senderName %> has sent you",
+ "privateMessageGiftGemsMessage": "<%= gemAmount %> gems!",
+ "privateMessageGiftSubscriptionMessage": "<%= numberOfMonths %> months of subscription!",
+ "cannotSendGemsToYourself": "Cannot send gems to yourself. Try a subscription instead.",
+ "badAmountOfGemsToSend": "Amount must be within 1 and your current number of gems.",
"abuseFlag": "Report violation of Community Guidelines",
"abuseFlagModalHeading": "Report <%= name %> for violation?",
"abuseFlagModalBody": "Are you sure you want to report this post? You should ONLY report a post that violates the <%= firstLinkStart %>Community Guidelines<%= linkEnd %> and/or <%= secondLinkStart %>Terms of Service<%= linkEnd %>. Inappropriately reporting a post is a violation of the Community Guidelines and may give you an infraction. Appropriate reasons to flag a post include but are not limited to:
swearing, religious oaths
bigotry, slurs
adult topics
violence, including as a joke
spam, nonsensical messages
",
@@ -151,5 +161,29 @@
"partyUpName": "Party Up",
"partyOnName": "Party On",
"partyUpAchievement": "Joined a Party with another person! Have fun battling monsters and supporting each other.",
- "partyOnAchievement": "Joined a Party with at least four people! Enjoy your increased accountability as you unite with your friends to vanquish your foes!"
+ "partyOnAchievement": "Joined a Party with at least four people! Enjoy your increased accountability as you unite with your friends to vanquish your foes!",
+ "groupIdRequired": "\"groupId\" must be a valid UUID",
+ "groupNotFound": "Group not found.",
+ "groupTypesRequired": "You must supply a valid \"type\" query string.",
+ "questLeaderCannotLeaveGroup": "You cannot leave your party when you have started a quest. Abort the quest first.",
+ "cannotLeaveWhileActiveQuest": "You cannot leave party during an active quest. Please leave the quest first.",
+ "onlyLeaderCanRemoveMember": "Only the group leader can remove a member!",
+ "memberCannotRemoveYourself": "You cannot remove yourself!",
+ "groupMemberNotFound": "User not found among group's members.",
+ "mustBeGroupMember": "Must be member of the group.",
+ "keepOrRemoveAll": "req.query.keep must be either \"keep-all\" or \"remove-all\"",
+ "keepOrRemove": "req.query.keep must be either \"keep\" or \"remove\"",
+ "canOnlyInviteEmailUuid": "Can only invite using UUIDs or emails.",
+ "inviteMissingEmail": "Missing email address in invite.",
+ "partyMustbePrivate": "Parties must be private.",
+ "userAlreadyInGroup": "User already in that group.",
+ "userAlreadyInvitedToGroup": "User already invited to that group.",
+ "userAlreadyPendingInvitation": "User already pending invitation.",
+ "userAlreadyInAParty": "User already in a party.",
+ "userWithIDNotFound": "User with ID \"<%= userId %>\" not found.",
+ "userHasNoLocalRegistration": "User does not have a local registration (username, email, password).",
+ "uuidsMustBeAnArray": "User ID invites must be an array.",
+ "emailsMustBeAnArray": "Email address invites must be an array.",
+ "canOnlyInviteMaxInvites": "You can only invite \"<%= maxInvites %>\" at a time.",
+ "onlyCreatorOrAdminCanDeleteChat": "Not authorised to delete this message!"
}
\ No newline at end of file
diff --git a/common/locales/en_GB/limited.json b/common/locales/en_GB/limited.json
index fdb9f1dc51..f6e200f995 100644
--- a/common/locales/en_GB/limited.json
+++ b/common/locales/en_GB/limited.json
@@ -5,7 +5,7 @@
"annoyingFriends": "Annoying Friends",
"annoyingFriendsText": "Got snowballed <%= snowballs %> times by party members.",
"alarmingFriends": "Alarming Friends",
- "alarmingFriendsText": "Got spooked <%= spookDust %> times by party members.",
+ "alarmingFriendsText": "Got spooked <%= spookySparkles %> times by party members.",
"agriculturalFriends": "Agricultural Mates",
"agriculturalFriendsText": "Got transformed into a flower <%= seeds %> times by party members.",
"aquaticFriends": "Aquatic Friends",
@@ -72,5 +72,6 @@
"comfortingKittySet": "Comforting Kitty (Healer)",
"sneakySqueakerSet": "Sneaky Squeaker (Rogue)",
"fallEventAvailability": "Available until 31 October",
- "winterEventAvailability": "Available until 31 December"
+ "winterEventAvailability": "Available until 31 December",
+ "springEventAvailability": "Available until 31 May"
}
\ No newline at end of file
diff --git a/common/locales/en_GB/maintenance.json b/common/locales/en_GB/maintenance.json
new file mode 100644
index 0000000000..615a9a2f95
--- /dev/null
+++ b/common/locales/en_GB/maintenance.json
@@ -0,0 +1,34 @@
+{
+ "habiticaBackSoon": "Don't worry, Habitica will be back soon!",
+ "importantMaintenance": "We are doing important maintenance that we estimate will last until <%= localDate %> in your timezone.",
+ "maintenance": "Maintenance",
+ "maintenanceMoreInfo": "Want more information about the maintenance? <%= linkStart %>Check out our info page<%= linkEnd %>.",
+ "noDamageKeepStreaks": "You will NOT take damage or lose streaks!",
+ "thanksForPatience": "Thanks for your patience!",
+ "twitterMaintenanceUpdates": "For the most recent updates, watch our Twitter, where we will be posting status information.",
+ "veteranPetAward": "At the end, you will receive a Veteran pet!",
+
+ "maintenanceInfoTitle": "Information about Upcoming Maintenance to Habitica",
+ "maintenanceInfoWhat": "What is happening?",
+ "maintenanceInfoWhatText": "On May 21, Habitica will be down for maintenance for most of the day. You will not take any damage or have your account harmed during that weekend, even if you can’t log in to check off your Dailies in time! We will be working very hard to make the downtime as short as possible, and will be posting regular updates on our Twitter account. At the end of the downtime, to thank everyone for their patience, you will all receive a rare pet!",
+ "maintenanceInfoWhy": "Why is this happening?",
+ "maintenanceInfoWhyText": "For the past several months, we have been thoroughly revamping Habitica behind-the-scenes. Specifically, we have rewritten the API. While it may not look much different on the surface, it’s a whole new world underneath. This will allow us WAY more flexibility when we want to build features in the future, and lead to improved performance!",
+ "maintenanceInfoTechDetails": "Want more details on the technical side of the process? Visit The Forge, our dev blog.",
+ "maintenanceInfoMore": "More Information",
+ "maintenanceInfoAccountChanges": "What changes will I see to my account after the rewrite is complete?",
+ "maintenanceInfoAccountChangesText": "At first, there won’t be any notable changes aside from performance improvements for features such as Challenges. If you notice any changes that shouldn’t be there, email us at admin@habitica.com and we will investigate them for you!",
+ "maintenanceInfoAddFeatures": "What kind of features will this allow Habitica to add?",
+ "maintenanceInfoAddFeaturesText": "Completing this rewrite will allow us to start building out improved chat and Guilds, plans for organisations and families, and additional productivity features like Monthlies and the ability to record yesterday’s activity! Those are all involved features on their own, so it will take time to build them, but until we were finished with this rewrite, there was no way we could start them.",
+ "maintenanceInfoHowLong": "How long will the maintenance take?",
+ "maintenanceInfoHowLongText": "We have to migrate tasks and data for all 1.3 million Habitica users -- not an easy task! We anticipate that it will take place between approximately 1pm Pacific Time (8pm UTC) and 10pm Pacific Time (5am UTC). Rest assured that we’re doing everything we can to make it go as quickly as possible! You can follow updates on our Twitter.",
+ "maintenanceInfoStatsAffected": "How will my Dailies, Streaks, Boosts, and Quests be affected?",
+ "maintenanceInfoStatsAffectedText1": "You will NOT take any damage or lose any streaks that weekend, but otherwise, your day will reset normally! Dailies that you checked will become unticked, boosts will reset, etc. If you are in a Collection Quest, you will still find items. If you are in a Boss Battle, you will still deal damage to the Boss, but the Boss will not deal damage to you. (Even monsters need a break!)",
+ "maintenanceInfoStatsAffectedText2": "After a lot of thought, our team concluded that this was the most fair way to handle the fact that many users will not be able to tick off their Dailies normally during the maintenance. We’re sorry for any inconvenience this causes!",
+ "maintenanceInfoSeeTasks": "What if I need to see my task list?",
+ "maintenanceInfoSeeTasksText": "If you know that you will need to see your task list on Saturday to remind yourself what you have to do, we recommend that before the maintenance begins, you take a screenshot of your tasks so that you can use it as a reference.",
+ "maintenanceInfoRarePet": "What kind of rare pet will I receive?",
+ "maintenanceInfoRarePetText": "To thank you for your patience during the downtime, everyone will get a rare Veteran Pet. If you’ve never received a Veteran Pet before, you will receive a Veteran Wolf. If you already have a Veteran Wolf, you will receive a Veteran Tiger. And if you already have a Veteran Wolf and a Veteran Tiger, you will receive a never-before-seen Veteran pet! After the migration is completed, it may take several hours for your pet to show up, but never fear, everyone will get one.",
+ "maintenanceInfoWho": "Who worked on this massive project?",
+ "maintenanceInfoWhoText": "We’re glad you asked! It was spearheaded by our amazing contributor paglias, with lots of help from Blade, TheHollidayInn, SabreCat, Victor Pudeyev, TheUnknown, and Alys.",
+ "maintenanceInfoTesting": "The new version was also tirelessly tested by a bunch of our amazing open-source volunteers. Thank you -- we couldn't have done this without you."
+}
diff --git a/common/locales/en_GB/npc.json b/common/locales/en_GB/npc.json
index 0409eabdc3..4b71431ca4 100644
--- a/common/locales/en_GB/npc.json
+++ b/common/locales/en_GB/npc.json
@@ -21,6 +21,26 @@
"ian": "Ian",
"ianText": "Welcome to the Quest Shop! Here you can use Quest Scrolls to battle monsters with your friends. Be sure to check out our fine array of Quest Scrolls for purchase on the right!",
"ianBrokenText": "Welcome to the Quest Shop... Here you can use Quest Scrolls to battle monsters with your friends... Be sure to check out our fine array of Quest Scrolls for purchase on the right...",
+ "missingKeyParam": "\"req.params.key\" is required.",
+ "itemNotFound": "Item \"<%= key %>\" not found.",
+ "cannotBuyItem": "You can't buy this item.",
+ "missingTypeKeyEquip": "\"key\" and \"type\" are required parameters.",
+ "missingPetFoodFeed": "\"pet\" and \"food\" are required parameters.",
+ "invalidPetName": "Invalid pet name supplied.",
+ "missingEggHatchingPotionHatch": "\"egg\" and \"hatchingPotion\" are required parameters.",
+ "invalidTypeEquip": "\"type\" must be one of 'equipped', 'pet', 'mount', 'costume'.",
+ "mustPurchaseToSet": "Must purchase <%= val %> to set it on <%= key %>.",
+ "typeRequired": "Type is required",
+ "keyRequired": "Key is required",
+ "notAccteptedType": "Type must be in [eggs, hatchingPotions, food, quests, gear]",
+ "contentKeyNotFound": "Key not found for Content <%= type %>",
+ "plusOneGem": "+1 Gem",
+ "typeNotSellable": "Type is not sellable. Must be one of the following <%= acceptedTypes %>",
+ "userItemsKeyNotFound": "Key not found for user.items <%= type %>",
+ "pathRequired": "Path string is required",
+ "unlocked": "Items have been unlocked",
+ "alreadyUnlocked": "Full set already unlocked.",
+ "alreadyUnlockedPart": "Full set already partially unlocked.",
"USD": "(USD)",
"newStuff": "New Stuff",
"cool": "Tell Me Later",
@@ -64,6 +84,7 @@
"tourPetsPage": "This is the Stable! After level 3, you can hatch pets using eggs and potions. When you hatch a pet in the Market, it will appear here! Click a pet's image to add it to your avatar. Feed them with the food you find after level 3, and they'll grow into powerful mounts.",
"tourMountsPage": "Once you've fed a pet enough food to turn it into a mount, it will appear here. (Pets, mounts, and food are available after level 3.) Click a mount to saddle up!",
"tourEquipmentPage": "This is where your Equipment is stored! Your Battle Gear affects your stats. If you want to show different Equipment on your avatar without changing your stats, click \"Enable Costume.\"",
+ "equipmentAlreadyOwned": "You already own that piece of equipment",
"tourOkay": "Okay!",
"tourAwesome": "Awesome!",
"tourSplendid": "Splendid!",
diff --git a/common/locales/en_GB/pets.json b/common/locales/en_GB/pets.json
index f69cbc3b93..1a7d7ce97b 100644
--- a/common/locales/en_GB/pets.json
+++ b/common/locales/en_GB/pets.json
@@ -12,6 +12,7 @@
"etherealLion": "Ethereal Lion",
"veteranWolf": "Veteran Wolf",
"veteranTiger": "Veteran Tiger",
+ "veteranLion": "Veteran Lion",
"cerberusPup": "Cerberus Pup",
"hydra": "Hydra",
"mantisShrimp": "Mantis Shrimp",
@@ -19,7 +20,7 @@
"orca": "Orca",
"royalPurpleGryphon": "Royal Purple Gryphon",
"phoenix": "Phoenix",
- "bumblebee": "Bumblebee",
+ "magicalBee": "Magical Bee",
"rarePetPop1": "Click the gold paw to learn more about how you can obtain this rare pet through contributing to Habitica!",
"rarePetPop2": "How to Get this Pet!",
"potion": "<%= potionType %> Potion",
@@ -62,6 +63,7 @@
"hatchedPet": "You hatched a <%= potion %> <%= egg %>!",
"displayNow": "Display Now",
"displayLater": "Display Later",
+ "petNotOwned": "You do not own this pet.",
"earnedCompanion": "With all your productivity, you've earned a new companion. Feed it to make it grow!",
"feedPet": "Feed <%= article %><%= text %> to your <%= name %>?",
"useSaddle": "Saddle <%= pet %>?",
@@ -83,5 +85,8 @@
"petKeyBoth": "Release Both",
"confirmPetKey": "Are you sure?",
"petKeyNeverMind": "Not Yet",
+ "petsReleased": "Pets released.",
+ "mountsAndPetsReleased": "Mounts and pets released",
+ "mountsReleased": "Mounts released",
"gemsEach": "gems each"
}
\ No newline at end of file
diff --git a/common/locales/en_GB/quests.json b/common/locales/en_GB/quests.json
index 4f2dc684e8..6078a58b0e 100644
--- a/common/locales/en_GB/quests.json
+++ b/common/locales/en_GB/quests.json
@@ -78,5 +78,24 @@
"whichQuestStart": "Which quest do you want to start?",
"getMoreQuests": "Get more quests",
"unlockedAQuest": "You unlocked a quest!",
- "leveledUpReceivedQuest": "You leveled up to Level <%= level %> and received a quest scroll!"
+ "leveledUpReceivedQuest": "You leveled up to Level <%= level %> and received a quest scroll!",
+ "questInvitationDoesNotExist": "No quest invitation has been sent out yet.",
+ "questInviteNotFound": "No quest invitation found.",
+ "guildQuestsNotSupported": "Guilds cannot be invited on quests.",
+ "questNotFound": "Quest \"<%= key %>\" not found.",
+ "questNotOwned": "You don't own that quest scroll.",
+ "questNotGoldPurchasable": "Quest \"<%= key %>\" is not a Gold-purchasable quest.",
+ "questLevelTooHigh": "You must be level <%= level %> to begin this quest.",
+ "questAlreadyUnderway": "Your party is already on a quest. Try again when the current quest has ended.",
+ "questAlreadyAccepted": "You already accepted the quest invitation.",
+ "noActiveQuestToLeave": "No active quest to leave",
+ "questLeaderCannotLeaveQuest": "Quest leader cannot leave quest",
+ "notPartOfQuest": "You are not part of the quest",
+ "noActiveQuestToAbort": "There is no active quest to abort.",
+ "onlyLeaderAbortQuest": "Only the group or quest leader can abort a quest.",
+ "questAlreadyRejected": "You already rejected the quest invitation.",
+ "cantCancelActiveQuest": "You can not cancel an active quest, use the abort functionality.",
+ "onlyLeaderCancelQuest": "Only the group or quest leader can cancel the quest.",
+ "questNotPending": "There is no quest to start.",
+ "questOrGroupLeaderOnlyStartQuest": "Only the quest leader or group leader can force start the quest"
}
\ No newline at end of file
diff --git a/common/locales/en_GB/questscontent.json b/common/locales/en_GB/questscontent.json
index 1c5ed37730..02d8f8f93c 100644
--- a/common/locales/en_GB/questscontent.json
+++ b/common/locales/en_GB/questscontent.json
@@ -1,11 +1,11 @@
{
"questEvilSantaText": "Trapper Santa",
- "questEvilSantaNotes": "You hear agonised roars deep in the icefields. You follow the growls - punctuated by the sound of cackling - to a clearing in the woods, where you see a fully-grown polar bear. She's caged and shackled, fighting for her life. Dancing atop the cage is a malicious little imp wearing a castaway costume. Vanquish Trapper Santa, and save the beast!",
- "questEvilSantaCompletion": "Trapper Santa squeals in anger, and bounces off into the night. The grateful she-bear, through roars and growls, tries to tell you something. You take her back to the stables, where Matt Boch the Beast Master listens to her tale with a gasp of horror. She has a cub! He ran off into the icefields when mama bear was captured.",
+ "questEvilSantaNotes": "You hear agonised roars deep in the ice-fields. You follow the growls - punctuated by the sound of cackling - to a clearing in the woods, where you see a fully-grown polar bear. She's caged and shackled, fighting for her life. Dancing atop the cage is a malicious little imp wearing a castaway costume. Vanquish Trapper Santa, and save the beast!",
+ "questEvilSantaCompletion": "Trapper Santa squeals in anger, and bounces off into the night. The grateful she-bear, through roars and growls, tries to tell you something. You take her back to the stables, where Matt Boch the Beast Master listens to her tale with a gasp of horror. She has a cub! He ran off into the ice-fields when mama bear was captured.",
"questEvilSantaBoss": "Trapper Santa",
"questEvilSantaDropBearCubPolarMount": "Polar Bear (Mount)",
"questEvilSanta2Text": "Find The Cub",
- "questEvilSanta2Notes": "When Trapper Santa captured the polar bear mount, her cub ran off into the icefields. You hear twig-snaps and snow crunch through the crystalline sound of the forest. Paw prints! You start racing to follow the trail. Find all the prints and broken twigs, and retrieve the cub!",
+ "questEvilSanta2Notes": "When Trapper Santa captured the polar bear mount, her cub ran off into the ice-fields. You hear twig-snaps and snow-crunching through the crystalline sound of the forest. Paw prints! You start racing to follow the trail. Find all the prints and broken twigs, and retrieve the cub!",
"questEvilSanta2Completion": "You've found the cub! It will keep you company forever.",
"questEvilSanta2CollectTracks": "Tracks",
"questEvilSanta2CollectBranches": "Broken Twigs",
@@ -36,7 +36,7 @@
"questRatUnlockText": "Unlocks purchasable rat eggs in the Market",
"questOctopusText": "The Call of Octothulu",
"questOctopusNotes": "@Urse, a wild-eyed young scribe, has asked for your help exploring a mysterious cave by the sea shore. Among the twilight tidepools stands a massive gate of stalactites and stalagmites. As you near the gate, a dark whirlpool begins to spin at its base. You stare in awe as a squid-like dragon rises through the maw. \"The sticky spawn of the stars has awakened,\" roars @Urse madly. \"After vigintillions of years, the great Octothulu is loose again, and ravening for delight!\"",
- "questOctopusCompletion": "With a final blow, the creature slips away into the whirlpool from which it came. You cannot tell if @Urse is happy with your victory or saddened to see the beast go. Wordlessly, your companion points to three slimy, gargantuan eggs in a nearby tidepool, set in a nest of gold coins. \"Probably just octopus eggs,\" you say nervously. As you return home, @Urse frantically scribbles in a journal and you suspect this is not the last time you will hear of the great Octothulu.",
+ "questOctopusCompletion": "With a final blow, the creature slips away into the whirlpool from which it came. You cannot tell if @Urse is happy with your victory or saddened to see the beast go. Wordlessly, your companion points to three slimy, gargantuan eggs in a nearby tide-pool, set in a nest of gold coins. \"Probably just octopus eggs,\" you say nervously. As you return home, @Urse frantically scribbles in a journal and you suspect this is not the last time you will hear of the great Octothulu.",
"questOctopusBoss": "Octothulu",
"questOctopusDropOctopusEgg": "Octopus (Egg)",
"questOctopusUnlockText": "Unlocks purchasable octopus eggs in the Market",
@@ -299,14 +299,26 @@
"questSnailDropSnailEgg": "Snail (Egg)",
"questSnailUnlockText": "Unlocks purchasable Snail eggs in the Market",
"questBewilderText": "The Be-Wilder",
- "questBewilderNotes": "The party begins like any other.
The appetizers are excellent, the music is swinging, and even the dancing elephants have become routine. Habiticans laugh and frolic amid the overflowing floral centerpieces, happy to have a distraction from their least-favorite tasks, and the April Fool whirls among them, eagerly providing an amusing trick here and a witty twist there.
As the Mistiflying clock tower strikes midnight, the April Fool leaps onto the stage to give a speech.
“Friends! Enemies! Tolerant acquaintances! Lend me your ears.” The crowd chuckles as animal ears sprout from their heads, and they pose with their new accessories.
“As you know,” the Fool continues, “my confusing illusions usually only last a single day. But I’m pleased to announce that I’ve discovered a shortcut that will guarantee us non-stop fun, without having to deal with the pesky weight of our responsibilities. Charming Habiticans, meet my magical new friend... the Be-Wilder!”
Lemoness pales suddenly, dropping her hors d'oeuvres. “Wait! Don’t trust--”
But suddenly mists are pouring into the room, glittering and thick, and they swirl around the April Fool, coalescing into cloudy feathers and a stretching neck. The crowd is speechless as an monstrous bird unfolds before them, its wings shimmering with illusions. It lets out a horrible screeching laugh.
“Oh, it has been ages since a Habitican has been foolish enough to summon me! How wonderful it feels, to have a tangible form at last.”
Buzzing in terror, the magic bees of Mistiflying flee the floating city, which sags from the sky. One by one, the brilliant spring flowers wither up and wisp away.
“My dearest friends, why so alarmed?” crows the Be-Wilder, beating its wings. “There’s no need to toil for your rewards any more. I’ll just give you all the things that you desire!”
A rain of coins pours from the sky, hammering into the ground with brutal force, and the crowd screams and flees for cover. “Is this a joke?” Baconsaur shouts, as the gold smashes through windows and shatters roof shingles.
PainterProphet ducks as lightning bolts crackle overhead, and fog blots out the sun. “No! This time, I don’t think it is!”
Quickly, Habiticans, don’t let this World Boss distract us from our goals! Stay focused on the tasks that you need to complete so we can rescue Mistiflying -- and hopefully, ourselves.",
- "questBewilderCompletion": "The Be-Wilder is DEFEATED!
We've done it! The Be-Wilder lets out a ululating cry as it twists in the air, shedding feathers like falling rain. Slowly, gradually, it coils into a cloud of sparkling mist. As the newly-revealed sun pierces the fog, it burns away, revealing the coughing, mercifully human forms of Bailey, Matt, Alex.... and the April Fool himself.
Mistiflying is saved!
The April Fool has enough shame to look a bit sheepish. “Oh, hm,” he says. “Perhaps I got a little…. carried away.”
The crowd mutters. Sodden flowers wash up on sidewalks. Somewhere in the distance, a roof collapses with a spectacular splash.
“Er, yes,” the April Fool says. “That is. What I meant to say was, I’m dreadfully sorry.” He heaves a sigh. “I suppose it can’t all be fun and games, after all. It might not hurt to focus occasionally. Maybe I’ll get a head start on next year’s pranking.”
Redphoenix coughs meaningfully.
“I mean, get a head start on this year’s spring cleaning!” the April Fool says. “Nothing to fear, I’ll have Habit City in spit-shape soon. Luckily nobody is better than I at dual-wielding mops.”
Encouraged, the marching band starts up.
It isn’t long before all is back to normal in Habit City. Plus, now that the Be-Wilder has evaporated, the magical bees of Mistiflying bustle back to work, and soon the flowers are blooming and the city is floating once more.
As Habiticans cuddle the magical fuzzy bees, the April Fool’s eyes light up. “Oho, I’ve had a thought! Why don’t you all keep some of these fuzzy Bee Pets and Mounts? It’s a gift that perfectly symbolizes the balance between hard work and sweet rewards, if I’m going to get all boring and allegorical on you.” He winks. “Besides, they don’t have stingers! Fool’s honor.”",
- "questBewilderCompletionChat": "`The Be-Wilder is DEFEATED!`\n\nWe've done it! The Be-Wilder lets out a ululating cry as it twists in the air, shedding feathers like falling rain. Slowly, gradually, it coils into a cloud of sparkling mist. As the newly-revealed sun pierces the fog, it burns away, revealing the coughing, mercifully human forms of Bailey, Matt, Alex.... and the April Fool himself.\n\n`Mistiflying is saved!`\n\nThe April Fool has enough shame to look a bit sheepish. “Oh, hm,” he says. “Perhaps I got a little…. carried away.”\n\nThe crowd mutters. Sodden flowers wash up on sidewalks. Somewhere in the distance, a roof collapses with a spectacular splash.\n\n“Er, yes,” the April Fool says. “That is. What I meant to say was, I’m dreadfully sorry.” He heaves a sigh. “I suppose it can’t all be fun and games, after all. It might not hurt to focus occasionally. Maybe I’ll get a head start on next year’s pranking.”\n\nRedphoenix coughs meaningfully.\n\n“I mean, get a head start on this year’s spring cleaning!” the April Fool says. “Nothing to fear, I’ll have Habit City in spit-shape soon. Luckily nobody is better than I at dual-wielding mops.”\n\nEncouraged, the marching band starts up.\n\nIt isn’t long before all is back to normal in Habit City. Plus, now that the Be-Wilder has evaporated, the magical bees of Mistiflying bustle back to work, and soon the flowers are blooming and the city is floating once more.\n\nAs Habiticans cuddle the magical fuzzy bees, the April Fool’s eyes light up. “Oho, I’ve had a thought! Why don’t you all keep some of these fuzzy Bee Pets and Mounts? It’s a gift that perfectly symbolizes the balance between hard work and sweet rewards, if I’m going to get all boring and allegorical on you.” He winks. “Besides, they don’t have stingers! Fool’s honor.”",
+ "questBewilderNotes": "The party begins like any other.
The appetisers are excellent, the music is swinging, and even the dancing elephants have become routine. Habiticans laugh and frolic amid the overflowing floral centrepieces, happy to have a distraction from their least-favourite tasks, and the April Fool whirls among them, eagerly providing an amusing trick here and a witty twist there.
As the Mistiflying clock tower strikes midnight, the April Fool leaps onto the stage to give a speech.
“Friends! Enemies! Tolerant acquaintances! Lend me your ears.” The crowd chuckles as animal ears sprout from their heads, and they pose with their new accessories.
“As you know,” the Fool continues, “my confusing illusions usually only last a single day. But I’m pleased to announce that I’ve discovered a shortcut that will guarantee us non-stop fun, without having to deal with the pesky weight of our responsibilities. Charming Habiticans, meet my magical new friend... the Be-Wilder!”
Lemoness pales suddenly, dropping her hors d'oeuvres. “Wait! Don’t trust--”
But suddenly mists are pouring into the room, glittering and thick, and they swirl around the April Fool, coalescing into cloudy feathers and a stretching neck. The crowd is speechless as an monstrous bird unfolds before them, its wings shimmering with illusions. It lets out a horrible screeching laugh.
“Oh, it has been ages since a Habitican has been foolish enough to summon me! How wonderful it feels, to have a tangible form at last.”
Buzzing in terror, the magic bees of Mistiflying flee the floating city, which sags from the sky. One by one, the brilliant spring flowers wither up and wisp away.
“My dearest friends, why so alarmed?” crows the Be-Wilder, beating its wings. “There’s no need to toil for your rewards any more. I’ll just give you all the things that you desire!”
A rain of coins pours from the sky, hammering into the ground with brutal force, and the crowd screams and flees for cover. “Is this a joke?” Baconsaur shouts, as the gold smashes through windows and shatters roof shingles.
PainterProphet ducks as lightning bolts crackle overhead, and fog blots out the sun. “No! This time, I don’t think it is!”
Quickly, Habiticans, don’t let this World Boss distract us from our goals! Stay focused on the tasks that you need to complete so we can rescue Mistiflying -- and hopefully, ourselves.",
+ "questBewilderCompletion": "The Be-Wilder is DEFEATED!
We've done it! The Be-Wilder lets out a ululating cry as it twists in the air, shedding feathers like falling rain. Slowly, gradually, it coils into a cloud of sparkling mist. As the newly-revealed sun pierces the fog, it burns away, revealing the coughing, mercifully human forms of Bailey, Matt, Alex.... and the April Fool himself.
Mistiflying is saved!
The April Fool has enough shame to look a bit sheepish. “Oh, hm,” he says. “Perhaps I got a little…. carried away.”
The crowd mutters. Sodden flowers wash up on pavements. Somewhere in the distance, a roof collapses with a spectacular splash.
“Er, yes,” the April Fool says. “That is. What I meant to say was, I’m dreadfully sorry.” He heaves a sigh. “I suppose it can’t all be fun and games, after all. It might not hurt to focus occasionally. Maybe I’ll get a head start on next year’s pranking.”
Redphoenix coughs meaningfully.
“I mean, get a head start on this year’s spring cleaning!” the April Fool says. “Nothing to fear, I’ll have Habit City in spit-shape soon. Luckily nobody is better than I at dual-wielding mops.”
Encouraged, the marching band starts up.
It isn’t long before all is back to normal in Habit City. Plus, now that the Be-Wilder has evaporated, the magical bees of Mistiflying bustle back to work, and soon the flowers are blooming and the city is floating once more.
As Habiticans cuddle the magical fuzzy bees, the April Fool’s eyes light up. “Oho, I’ve had a thought! Why don’t you all keep some of these fuzzy Bee Pets and Mounts? It’s a gift that perfectly symbolizes the balance between hard work and sweet rewards, if I’m going to get all boring and allegorical on you.” He winks. “Besides, they don’t have stingers! Fool’s honour.”",
+ "questBewilderCompletionChat": "`The Be-Wilder is DEFEATED!`\n\nWe've done it! The Be-Wilder lets out a ululating cry as it twists in the air, shedding feathers like falling rain. Slowly, gradually, it coils into a cloud of sparkling mist. As the newly-revealed sun pierces the fog, it burns away, revealing the coughing, mercifully human forms of Bailey, Matt, Alex.... and the April Fool himself.\n\n`Mistiflying is saved!`\n\nThe April Fool has enough shame to look a bit sheepish. “Oh, hm,” he says. “Perhaps I got a little…. carried away.”\n\nThe crowd mutters. Sodden flowers wash up on pavements. Somewhere in the distance, a roof collapses with a spectacular splash.\n\n“Er, yes,” the April Fool says. “That is. What I meant to say was, I’m dreadfully sorry.” He heaves a sigh. “I suppose it can’t all be fun and games, after all. It might not hurt to focus occasionally. Maybe I’ll get a head start on next year’s pranking.”\n\nRedphoenix coughs meaningfully.\n\n“I mean, get a head start on this year’s spring cleaning!” the April Fool says. “Nothing to fear, I’ll have Habit City in spit-shape soon. Luckily nobody is better than I at dual-wielding mops.”\n\nEncouraged, the marching band starts up.\n\nIt isn’t long before all is back to normal in Habit City. Plus, now that the Be-Wilder has evaporated, the magical bees of Mistiflying bustle back to work, and soon the flowers are blooming and the city is floating once more.\n\nAs Habiticans cuddle the magical fuzzy bees, the April Fool’s eyes light up. “Oho, I’ve had a thought! Why don’t you all keep some of these fuzzy Bee Pets and Mounts? It’s a gift that perfectly symbolizes the balance between hard work and sweet rewards, if I’m going to get all boring and allegorical on you.” He winks. “Besides, they don’t have stingers! Fool’s honour.”",
"questBewilderBossRageTitle": "Beguilement Strike",
"questBewilderBossRageDescription": "When this gauge fills, The Be-Wilder will unleash its Beguilement Strike on Habitica!",
- "questBewilderDropBumblebeePet": "Bumblebee (Pet)",
- "questBewilderDropBumblebeeMount": "Bumblebee (Mount)",
- "questBewilderBossRageMarket": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nOh no! Despite our best efforts, we've gotten distracted by the Be-Wilder’s charming illusions and have forgotten to do some of our Dailies! With a cackling cry, the shining bird beats its wings, raising a swarm of mist around Alex the Merchant. When the fog clears, he has been possessed! “Have some free samples!” he shouts gleefully, and begins to hurl exploding eggs and potions at fleeing Habiticans. Not the most favorable of sales, to be sure.\n\nHurry! Let's stay focused on our Dailies to defeat this monster before it possesses someone else.",
+ "questBewilderDropBumblebeePet": "Magical Bee (Pet)",
+ "questBewilderDropBumblebeeMount": "Magical Bee (Mount)",
+ "questBewilderBossRageMarket": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nOh no! Despite our best efforts, we've gotten distracted by the Be-Wilder’s charming illusions and have forgotten to do some of our Dailies! With a cackling cry, the shining bird beats its wings, raising a swarm of mist around Alex the Merchant. When the fog clears, he has been possessed! “Have some free samples!” he shouts gleefully, and begins to hurl exploding eggs and potions at fleeing Habiticans. Not the most favourable of sales, to be sure.\n\nHurry! Let's stay focused on our Dailies to defeat this monster before it possesses someone else.",
"questBewilderBossRageStables": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nAhh!!! Once again the Be-Wilder has dazzled us into neglecting our Dailies, and now it has attacked Matt the Beast Master! With a swirl of mist, Matt transforms into a terrifying winged creature, and all the pets and mounts howl sadly in their stables. Quickly, stay focused on your tasks to defeat this dastardly distraction!",
- "questBewilderBossRageBailey": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nLook out! In the middle of reporting the news, Bailey the Town Crier has been possessed by the Be-Wilder! She lets out an evil, uninformative screech as she rises into the air. Now how will we know what’s going on?\n\nDon't give up... we're so close to defeating this bothersome bird for once and for all!"
+ "questBewilderBossRageBailey": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nLook out! In the middle of reporting the news, Bailey the Town Crier has been possessed by the Be-Wilder! She lets out an evil, uninformative screech as she rises into the air. Now how will we know what’s going on?\n\nDon't give up... we're so close to defeating this bothersome bird for once and for all!",
+ "questFalconText": "The Birds of Preycrastination",
+ "questFalconNotes": "Mt. Habitica is being overshadowed by a looming mountain of To-Dos. It used to be a place to picnic and enjoy a sense of accomplishment, until the neglected tasks grew out of control. Now it's home to fearsome Birds of Preycrastination, foul creatures which stop Habiticans from completing their tasks!
\"It's too hard!\" they caw at @JonArinbjorn and @Onheiron. \"It'll take too long to do right now! It won't make any difference if you wait until tomorrow! Why don't you do something fun instead?\"
No more, you vow. You will climb your personal mountain of To-Dos and defeat the Birds of Preycrastination!",
+ "questFalconCompletion": "Having finally triumphed over the Birds of Preycrastination, you settle down to enjoy the view and your well-earned rest.
\"Wow!\" says @Trogdorina. \"You won!\"
@Squish adds, \"Here, take these eggs I found as a reward.\"",
+ "questFalconBoss": "Birds of Preycrastination",
+ "questFalconDropFalconEgg": "Falcon (Egg)",
+ "questFalconUnlockText": "Unlocks purchasable Falcon eggs in the Market",
+ "questTreelingText": "The Tangle Tree",
+ "questTreelingNotes": "It's the annual Garden Competition, and everyone is talking about the mysterious project which @aurakami has promised to unveil. You join the crowd on the day of the big announcement, and marvel at the introduction of a moving tree. @fuzzytrees explains that the tree will help with garden maintenance, showing how it can mow the lawn, trim the hedge and prune the roses all at the same time – until the tree suddenly goes wild, turning its secateurs on its creator! The crowd panics as everyone tries to flee, but you aren't afraid – you leap forward, ready to do battle.",
+ "questTreelingCompletion": "You dust yourself off as the last few leaves drift to the floor. In spite of the upset, the Garden Competition is now safe – although the tree you just reduced to a heap of wood chips won't be winning any prizes! \"Still a few kinks to work out there,\" @PainterProphet says. \"Perhaps someone else would do a better job of training the saplings. Do you fancy a go?\"",
+ "questTreelingBoss": "Tangle Tree",
+ "questTreelingDropTreelingEgg": "Treeling (Egg)",
+ "questTreelingUnlockText": "Unlocks purchasable Treeling eggs in the Market"
}
\ No newline at end of file
diff --git a/common/locales/en_GB/rebirth.json b/common/locales/en_GB/rebirth.json
index 45f69559bc..47d24e889a 100644
--- a/common/locales/en_GB/rebirth.json
+++ b/common/locales/en_GB/rebirth.json
@@ -5,7 +5,7 @@
"rebirthStartOver": "Rebirth starts your character over from Level 1.",
"rebirthAdvList1": "You return to full Health.",
"rebirthAdvList2": "You have no Experience, Gold, or Equipment (with the exception of free items like Mystery items).",
- "rebirthAdvList3": "Your Habits, Dailies, and To-Dos reset to yellow, and streaks reset.",
+ "rebirthAdvList3": "Your Habits, Dailies, and To-Dos reset to yellow, and streaks reset, except for challenge tasks.",
"rebirthAdvList4": "You have the starting class of Warrior until you earn a new class.",
"rebirthInherit": "Your new character inherits a few things from their predecessor:",
"rebirthInList1": "Tasks, history, and settings remain.",
@@ -24,5 +24,6 @@
"rebirthPop": "Begin a new character at Level 1 while retaining achievements, collectibles, and tasks with history.",
"rebirthName": "Orb of Rebirth",
"reborn": "Reborn, max level <%= reLevel %>",
- "confirmReborn": "Are you sure?"
+ "confirmReborn": "Are you sure?",
+ "rebirthComplete": "You have been reborn!"
}
\ No newline at end of file
diff --git a/common/locales/en_GB/settings.json b/common/locales/en_GB/settings.json
index a4c58379d9..adb566d6e4 100644
--- a/common/locales/en_GB/settings.json
+++ b/common/locales/en_GB/settings.json
@@ -47,6 +47,7 @@
"customDayStart": "Custom Day Start",
"changeCustomDayStart": "Change Custom Day Start?",
"sureChangeCustomDayStart": "Are you sure you want to change your custom day start?",
+ "customDayStartHasChanged": "Your custom day start has changed.",
"nextCron": "Your Dailies will next reset the first time you use Habitica after <%= time %>. Make sure you have completed your Dailies before this time!",
"customDayStartInfo1": "Habitica defaults to check and reset your Dailies at midnight in your own time zone each day. You can customise that time here.",
"misc": "Misc",
@@ -61,12 +62,23 @@
"newUsername": "New Login Name",
"dangerZone": "Danger Zone",
"resetText1": "WARNING! This resets many parts of your account. This is highly discouraged, but some people find it useful in the beginning after playing with the site for a short time.",
- "resetText2": "You will lose all your levels, gold, and experience points. All your tasks will be deleted permanently and you will lose all of your task's historical data. You will lose all your equipment but you will be able to buy it all back, including all limited edition equipment or subscriber Mystery items that you already own (you will need to be in the correct class to re-buy class-specific gear). You will keep your current class and your pets and mounts. You might prefer to use an Orb of Rebirth instead, which is a much safer option and which will preserve your tasks.",
+ "resetText2": "You will lose all your levels, gold, and experience points. All your tasks (except those from challenges) will be deleted permanently and you will lose all of their historical data. You will lose all your equipment but you will be able to buy it all back, including all limited edition equipment or subscriber Mystery items that you already own (you will need to be in the correct class to re-buy class-specific gear). You will keep your current class and your pets and mounts. You might prefer to use an Orb of Rebirth instead, which is a much safer option and which will preserve your tasks.",
"deleteText": "Are you sure? This will delete your account forever, and it can never be restored! You will need to register a new account to use Habitica again. Banked or spent Gems will not be refunded. If you're absolutely certain, type <%= deleteWord %> into the text box below.",
"API": "API",
+ "APIv3": "API v3",
+ "APIv2": "API v2 - Deprecated",
"APIText": "Copy these for use in third party applications. However, think of your API Token like a password, and do not share it publicly. You may occasionally be asked for your User ID, but never post your API Token where others can see it, including on Github.",
"APIToken": "API Token (this is a password—see warning above!)",
+ "thirdPartyApps": "Third Party Apps",
+ "dataToolDesc": "A webpage that shows you certain information from your Habitica account, such as statistics about your tasks, equipment, and skills.",
+ "beeminder": "Beeminder",
+ "beeminderDesc": "Let Beeminder automatically monitor your Habitica To-Dos. You can commit to maintaining a target number of To-Dos completed per day or per week, or you can commit to gradually reducing your remaining number of uncompleted To-Dos. (By \"commit\" Beeminder means under threat of paying actual money! But you may also just like Beeminder's fancy graphs.)",
+ "chromeChatExtension": "Chrome Chat Extension",
+ "chromeChatExtensionDesc": "The Chrome Chat Extension for Habitica adds an intuitive chat box to all of habitica.com. It allows users to chat in the Tavern, their party, and any guilds they are in.",
+ "otherExtensions": "Other Extensions",
+ "otherDesc": "Find other apps, extensions, and tools on the Habitica wiki.",
"resetDo": "Do it, reset my account!",
+ "resetComplete": "Reset complete!",
"fixValues": "Fix Values",
"fixValuesText1": "If you've encountered a bug or made a mistake that unfairly changed your character (damage you shouldn't have taken, Gold you didn't really earn, etc.), you can manually correct your numbers here. Yes, this makes it possible to cheat: use this feature wisely, or you'll sabotage your own habit-building!",
"fixValuesText2": "Note that you cannot restore Streaks on individual tasks here. To do that, edit the Daily and go to Advanced Options, where you will find a Restore Streak field.",
@@ -96,6 +108,7 @@
"emailNotifications": "Email Notifications",
"wonChallenge": "You won a Challenge!",
"newPM": "Received Private Message",
+ "sentGems": "Sent gems!",
"giftedGems": "Gifted Gems",
"giftedGemsInfo": "<%= amount %> Gems - by <%= name %>",
"giftedSubscription": "Gifted Subscription",
@@ -135,6 +148,11 @@
"webhooks": "Webhooks",
"enabled": "Enabled",
"webhookURL": "Webhook URL",
+ "invalidUrl": "invalid URL",
+ "invalidEnabled": "the \"enabled\" parameter should be a boolean",
+ "regIdRequired": "RegId is required",
+ "pushDeviceAdded": "Push device added successfully",
+ "pushDeviceAlreadyAdded": "The user already has the push device",
"add": "Add",
"buyGemsGoldCap": "Cap raised to <%= amount %>",
"mysticHourglass": "<%= amount %> Mystic Hourglass",
@@ -149,5 +167,5 @@
"amazonPayments": "Amazon Payments",
"timezone": "Time Zone",
"timezoneUTC": "Habitica uses the time zone set on your PC, which is: <%= utc %>",
- "timezoneInfo": "If that time zone is wrong, first reload this page using your browser's reload or refresh button to ensure that Habitica has the most recent information. If it is still wrong, adjust the time zone on your PC and then reload this page again.
If you use Habitica on other PCs or mobile devices, the time zone must be the same on them all. If your Dailies have been reseting at the wrong time, repeat this check on all other PCs and on a browser on your mobile devices."
+ "timezoneInfo": "If that time zone is wrong, first reload this page using your browser's reload or refresh button to ensure that Habitica has the most recent information. If it is still wrong, adjust the time zone on your PC and then reload this page again.
If you use Habitica on other PCs or mobile devices, the time zone must be the same on them all. If your Dailies have been resetting at the wrong time, repeat this check on all other PCs and on a browser on your mobile devices."
}
\ No newline at end of file
diff --git a/common/locales/en_GB/spells.json b/common/locales/en_GB/spells.json
index 2ca4c58ba5..95821167bc 100644
--- a/common/locales/en_GB/spells.json
+++ b/common/locales/en_GB/spells.json
@@ -35,8 +35,8 @@
"spellSpecialSnowballAuraNotes": "Throw a snowball at a party member! What could possibly go wrong? Lasts until member's new day.",
"spellSpecialSaltText": "Salt",
"spellSpecialSaltNotes": "Someone has snowballed you. Ha ha, very funny. Now get this snow off me!",
- "spellSpecialSpookDustText": "Spooky Sparkles",
- "spellSpecialSpookDustNotes": "Turn a friend into a floating blanket with eyes!",
+ "spellSpecialSpookySparklesText": "Spooky Sparkles",
+ "spellSpecialSpookySparklesNotes": "Turn a friend into a floating blanket with eyes!",
"spellSpecialOpaquePotionText": "Opaque Potion",
"spellSpecialOpaquePotionNotes": "Cancel the effects of Spooky Sparkles.",
"spellSpecialShinySeedText": "Shiny Seed",
@@ -46,5 +46,11 @@
"spellSpecialSeafoamText": "Seafoam",
"spellSpecialSeafoamNotes": "Turn a friend into a sea creature!",
"spellSpecialSandText": "Sand",
- "spellSpecialSandNotes": "Cancel the effects of Seafoam."
+ "spellSpecialSandNotes": "Cancel the effects of Seafoam.",
+ "spellNotFound": "Skill \"<%= spellId %>\" not found.",
+ "partyNotFound": "Party not found",
+ "targetIdUUID": "\"targetId\" must be a valid User ID.",
+ "challengeTasksNoCast": "Casting a skill on challenge tasks is not allowed.",
+ "spellNotOwned": "You don't own this skill.",
+ "spellLevelTooHigh": "You must be level <%= level %> to use this skill."
}
\ No newline at end of file
diff --git a/common/locales/en_GB/subscriber.json b/common/locales/en_GB/subscriber.json
index 06980ccf1f..4beb0c8c48 100644
--- a/common/locales/en_GB/subscriber.json
+++ b/common/locales/en_GB/subscriber.json
@@ -4,6 +4,8 @@
"subDescription": "Buy Gems with gold, get monthly mystery items, retain progress history, double daily drop-caps, support the devs. Click for more info.",
"buyGemsGold": "Buy Gems with Gold",
"buyGemsGoldText": "Alexander the Merchant will sell you Gems at a cost of <%= gemCost %> gold per gem. His monthly shipments are initially capped at <%= gemLimit %> Gems per month, but this cap increases by 5 Gems for every three months of consecutive subscription, up to a maximum of 50 Gems per month!",
+ "mustSubscribeToPurchaseGems": "Must subscribe to purchase gems with GP",
+ "reachedGoldToGemCap": "You've reached the Gold=>Gem conversion cap <%= convCap %> for this month. We have this to prevent abuse / farming. The cap will reset within the first three days of next month.",
"retainHistory": "Retain additional history entries",
"retainHistoryText": "Makes completed To-Dos and task history available for longer.",
"doubleDrops": "Daily drop-caps doubled",
@@ -29,6 +31,7 @@
"manageSub": "Click to manage subscription",
"cancelSub": "Cancel Subscription",
"canceledSubscription": "Cancelled Subscription",
+ "cancelingSubscription": "Canceling the subscription",
"adminSub": "Administrator Subscriptions",
"morePlans": "More Plans Coming Soon",
"organizationSub": "Private Organisation",
@@ -73,6 +76,9 @@
"timeTravelersPopover": "We see you have a Mystic Hourglass, so we will happily travel back in time for you! Please choose the pet, mount, or Mystery Item Set you would like. You can see a list of the past item sets here! If those don't satisfy you, perhaps you'd be interested in one of our fashionably futuristic Steampunk Item Sets?",
"timeTravelersAlreadyOwned": "Congratulations! You already own everything the Time Travelers currently offer. Thanks for supporting the site!",
"mysticHourglassPopover": "A Mystic Hourglass allows you to purchase certain limited-time items, such as monthly Mystery Item Sets and awards from world bosses, from the past!",
+ "mysterySetNotFound": "Mystery set not found, or set already owned.",
+ "mysteryItemIsEmpty": "Mystery items are empty",
+ "mysteryItemOpened": "Mystery item opened.",
"mysterySet201402": "Winged Messenger Set",
"mysterySet201403": "Forest Walker Set",
"mysterySet201404": "Twilight Butterfly Set",
@@ -99,6 +105,8 @@
"mysterySet201601": "Champion of Resolution Set",
"mysterySet201602": "Heartbreaker Set",
"mysterySet201603": "Lucky Clover Set",
+ "mysterySet201604": "Leaf Warrior Set",
+ "mysterySet201605": "Marching Bard Set",
"mysterySet301404": "Steampunk Standard Set",
"mysterySet301405": "Steampunk Accessories Set",
"mysterySetwondercon": "Wondercon",
@@ -110,9 +118,25 @@
"hourglassBuyItemConfirm": "Buy this item for 1 Mystic Hourglass?",
"petsAlreadyOwned": "Pet already owned.",
"mountsAlreadyOwned": "Mount already owned.",
- "typeNotAllowedHourglass": "Item type not supported for purchase with Mystic Hourglass. Allowed types:",
+ "typeNotAllowedHourglass": "Item type not supported for purchase with Mystic Hourglass. Allowed types: <%= allowedTypes %>",
"petsNotAllowedHourglass": "Pet not available for purchase with Mystic Hourglass.",
"mountsNotAllowedHourglass": "Mount not available for purchase with Mystic Hourglass.",
"hourglassPurchase": "Purchased an item using a Mystic Hourglass!",
- "hourglassPurchaseSet": "Purchased an item set using a Mystic Hourglass!"
+ "hourglassPurchaseSet": "Purchased an item set using a Mystic Hourglass!",
+ "missingUnsubscriptionCode": "Missing un-subscribe code.",
+ "missingSubscription": "User does not have a subscription plan.",
+ "missingSubscriptionCode": "Missing subscription code. Possible values: basic_earned, basic_3mo, basic_6mo, google_6mo, basic_12mo.",
+ "cannotDeleteActiveAccount": "You have an active subscription. Cancel your plan before deleting your account.",
+ "paymentNotSuccessful": "The payment was not successful.",
+ "planNotActive": "The plan hasn't activated yet (due to a PayPal bug). It will begin <%= nextBillingDate %>, after which you can cancel to retain your full benefits.",
+ "notAllowedHourglass": "Pet/Mount not available for purchase with Mystic Hourglass.",
+ "readCard": "<%= cardType %> has been read",
+ "cardTypeRequired": "Card type required",
+ "cardTypeNotAllowed": "Unknown card type.",
+ "invalidCoupon": "Invalid coupon code.",
+ "couponUsed": "Coupon code already used.",
+ "noSudoAccess": "You don't have 'sudo' access.",
+ "couponCodeRequired": "The coupon code is required.",
+ "eventRequired": "\"req.params.event\" is required.",
+ "countRequired": "\"req.query.count\" is required."
}
\ No newline at end of file
diff --git a/common/locales/en_GB/tasks.json b/common/locales/en_GB/tasks.json
index c480073453..0c81c4ef46 100644
--- a/common/locales/en_GB/tasks.json
+++ b/common/locales/en_GB/tasks.json
@@ -73,6 +73,7 @@
"clearTags": "Clear",
"hideTags": "Hide",
"showTags": "Show",
+ "toRequired": "You must supply a to value",
"startDate": "Start Date",
"startDateHelpTitle": "When should this task start?",
"startDateHelp": "Set the date for which this task takes effect. Will not be due on earlier days.",
@@ -88,8 +89,9 @@
"fortifyName": "Fortify Potion",
"fortifyPop": "Return all tasks to neutral value (yellow colour), and restore all lost Health.",
"fortify": "Fortify",
- "fortifyText": "Fortify will return all your tasks to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!",
+ "fortifyText": "Fortify will return all your tasks, except challenge tasks, to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!",
"confirmFortify": "Are you sure?",
+ "fortifyComplete": "Fortify complete!",
"sureDelete": "Are you sure you want to delete the <%= taskType %> with the text \"<%= taskText %>\"?",
"streakCoins": "Streak Bonus!",
"pushTaskToTop": "Push task to top. Hold ctrl or cmd to push to bottom.",
@@ -112,5 +114,18 @@
"rewardHelp2": "Equipment affects your stats (<%= linkStart %>Avatar > Stats<%= linkEnd %>).",
"rewardHelp3": "Special equipment will appear here during World Events.",
"rewardHelp4": "Don't be afraid to set custom Rewards! Check out some samples here.",
- "clickForHelp": "Click for help"
+ "clickForHelp": "Click for help",
+ "taskIdRequired": "\"taskId\" must be a valid UUID.",
+ "taskNotFound": "Task not found.",
+ "invalidTaskType": "Task type must be one of \"habit\", \"daily\", \"todo\", \"reward\".",
+ "cantDeleteChallengeTasks": "A task belonging to a challenge can't be deleted.",
+ "checklistOnlyDailyTodo": "Checklists are supported only on dailies and todos",
+ "checklistItemNotFound": "No checklist item was found with given id.",
+ "itemIdRequired": "\"itemId\" must be a valid UUID.",
+ "tagNotFound": "No tag item was found with given ID.",
+ "tagIdRequired": "\"tagId\" must be a valid UUID corresponding to a tag belonging to the user.",
+ "positionRequired": "\"position\" is required and must be a number.",
+ "cantMoveCompletedTodo": "Can't move a completed to-do.",
+ "directionUpDown": "\"direction\" is required and must be 'up' or 'down'",
+ "alreadyTagged": "The task is already tagged with given tag."
}
\ No newline at end of file
diff --git a/common/locales/es/backgrounds.json b/common/locales/es/backgrounds.json
index 33ec8d95ce..b1bfb7eef4 100644
--- a/common/locales/es/backgrounds.json
+++ b/common/locales/es/backgrounds.json
@@ -58,11 +58,11 @@
"backgroundSnowyPinesNotes": "Refúgiate entre pinos nevados.",
"backgrounds022015": "SET 9: Lanzado en febrero de 2015",
"backgroundBlacksmithyText": "Forjador",
- "backgroundBlacksmithyNotes": "Trabajo en la forja",
+ "backgroundBlacksmithyNotes": "Trabaja en la forja",
"backgroundCrystalCaveText": "Cueva de Cristal",
"backgroundCrystalCaveNotes": "Explora la Cueva de Cristal.",
"backgroundDistantCastleText": "Castillo Distante",
- "backgroundDistantCastleNotes": "Defiende el Castillo Distante.",
+ "backgroundDistantCastleNotes": "Defienda un Castillo Distante.",
"backgrounds032015": "SET 10: Lanzado en marzo de 2015",
"backgroundSpringRainText": "Lluvia de Primavera",
"backgroundSpringRainNotes": "Baila bajo la Lluvia de Primavera.",
@@ -74,14 +74,14 @@
"backgroundCherryTreesText": "Cerezos",
"backgroundCherryTreesNotes": "Admira los Cerezos en flor.",
"backgroundFloralMeadowText": "Prado Floreciente",
- "backgroundFloralMeadowNotes": "Ve de picnic a un Prado Floreciente.",
+ "backgroundFloralMeadowNotes": "Ve de Picnic a un Prado Floreciente.",
"backgroundGumdropLandText": "País de las Gominolas",
"backgroundGumdropLandNotes": "Mordisquea el paisaje del País de las Gominolas.",
"backgrounds052015": "SET 12: Lanzado en mayo de 2015",
"backgroundMarbleTempleText": "Templo de Mármol",
- "backgroundMarbleTempleNotes": "Posa frente a un Templo de Mármol.",
+ "backgroundMarbleTempleNotes": "Posa enfrente de un Templo de Mármol.",
"backgroundMountainLakeText": "Lago de Montaña",
- "backgroundMountainLakeNotes": "Mete los pies en el Lago de Montaña.",
+ "backgroundMountainLakeNotes": "Mete los dedos en el Lago de Montaña.",
"backgroundPagodasText": "Pagodas",
"backgroundPagodasNotes": "Escala a lo alto de las Pagodas.",
"backgrounds062015": "SET 13: Lanzado en junio del 2015",
@@ -112,7 +112,7 @@
"backgroundStableNotes": "Atender monturas en el Establo de Habitica.",
"backgroundTavernText": "Taberna de Habitica",
"backgroundTavernNotes": "Visita la Taberna de Habitica.",
- "backgrounds102015": "SET 17: lanzado en octobre del 2015",
+ "backgrounds102015": "Conjunto 17: Lanzado en octobre 2015",
"backgroundHarvestMoonText": "Luna LLena",
"backgroundHarvestMoonNotes": "Charla bajo la Luna LLena.",
"backgroundSlimySwampText": "Pantano Pringoso",
@@ -128,21 +128,21 @@
"backgroundSunsetOasisNotes": "Contempla el Ocaso en el Oasis",
"backgrounds122015": "Set 19: Lanzado en Diciembre del 2015",
"backgroundAlpineSlopesText": "Laderas Alpinas",
- "backgroundAlpineSlopesNotes": "Esquía en las Laderas Alpinas",
+ "backgroundAlpineSlopesNotes": "Esquía en las Laderas Alpinas.",
"backgroundSnowySunriseText": "Amanecer Nevado",
- "backgroundSnowySunriseNotes": "Contempla el Amanecer Nevado",
+ "backgroundSnowySunriseNotes": "Contempla el Amanecer Nevado.",
"backgroundWinterTownText": "Pueblo Invernal",
"backgroundWinterTownNotes": "Adentrarse en el Pueblo Invernal.\n",
- "backgrounds012016": "SET 20: Lanzado en Enero 2016",
+ "backgrounds012016": "Conjunto 20: Lanzado en enero 2016",
"backgroundFrozenLakeText": "Lago Congelado",
"backgroundFrozenLakeNotes": "Patinar en un Lago Congelado",
"backgroundSnowmanArmyText": "Ejercito de Hombres de Nieve",
"backgroundSnowmanArmyNotes": "Liderar un Ejercito de Hombres de Nieve",
"backgroundWinterNightText": "Noche de Invierno",
"backgroundWinterNightNotes": "Mirar las estrellas de una Noche de Invierno",
- "backgrounds022016": "SET 21: lanzado en febrero del 2016",
+ "backgrounds022016": "Conjunto 21: Lanzado en febrero 2016",
"backgroundBambooForestText": "Bosque de Bambú",
- "backgroundBambooForestNotes": "Pasea por el Bosque de Bambú.",
+ "backgroundBambooForestNotes": "Vuelta por el Bosque de Bambú.",
"backgroundCozyLibraryText": "Biblioteca Acogedora",
"backgroundCozyLibraryNotes": "Lee en la Biblioteca Acogedora.",
"backgroundGrandStaircaseText": "Gran Escalinata",
@@ -150,15 +150,22 @@
"backgrounds032016": "SET 22: lanzado en marzo del 2016",
"backgroundDeepMineText": "Mina Profunda",
"backgroundDeepMineNotes": "Encuentra metales preciosos en esta Mina Profunda.",
- "backgroundRainforestText": "Selva Tropical",
- "backgroundRainforestNotes": "Adéntrate en la Selva Tropical.",
+ "backgroundRainforestText": "Bosque Tropical",
+ "backgroundRainforestNotes": "Aventurarse a la Selva Tropical.",
"backgroundStoneCircleText": "Círculo de Piedras",
"backgroundStoneCircleNotes": "Lanza hechizos en un Círculo de Piedras.",
- "backgrounds042016": "SET 23: Released April 2016",
- "backgroundArcheryRangeText": "Archery Range",
- "backgroundArcheryRangeNotes": "Practice on the Archery Range.",
- "backgroundGiantFlowersText": "Giant Flowers",
- "backgroundGiantFlowersNotes": "Frolic atop Giant Flowers.",
- "backgroundRainbowsEndText": "End of the Rainbow",
- "backgroundRainbowsEndNotes": "Discover gold at the End of the Rainbow."
+ "backgrounds042016": "Conjunto 23: Lanzado en abril 2016",
+ "backgroundArcheryRangeText": "Campo de tiro",
+ "backgroundArcheryRangeNotes": "Entrena en el Campo de Tiro",
+ "backgroundGiantFlowersText": "Flores Gigantes",
+ "backgroundGiantFlowersNotes": "Juguetear encima de Flores Gigantes.",
+ "backgroundRainbowsEndText": "Fin del Arcoíris",
+ "backgroundRainbowsEndNotes": "Descubre oro al Fin del Arcoíris.",
+ "backgrounds052016": "JUEGO 24: Lanzado en Mayo de 2016",
+ "backgroundBeehiveText": "Colmena",
+ "backgroundBeehiveNotes": "Zumba y baila en una Colmena.",
+ "backgroundGazeboText": "Kiosko",
+ "backgroundGazeboNotes": "Pelea en un Kiosko.",
+ "backgroundTreeRootsText": "Raíces de árbol",
+ "backgroundTreeRootsNotes": "Explora las raíces del árbol."
}
\ No newline at end of file
diff --git a/common/locales/es/challenge.json b/common/locales/es/challenge.json
index 6c4b3453c5..fe41f204a0 100644
--- a/common/locales/es/challenge.json
+++ b/common/locales/es/challenge.json
@@ -1,22 +1,22 @@
{
"challenge": "Desafío",
- "brokenChaLink": "Enlace al Desafío roto",
- "brokenTask": "Enlace al Desafío roto: esta tarea era parte de un desafío pero ha sido eliminada del mismo. ¿Qué deseas hacer?",
- "keepIt": "Mantenerla",
+ "brokenChaLink": "Enlace al desafío roto",
+ "brokenTask": "Enlace al desafío interrumpido: esta tarea formaba parte de un desafío, pero se ha eliminado de él. ¿Qué deseas hacer?",
+ "keepIt": "Conservarla",
"removeIt": "Eliminarla",
- "brokenChallenge": "Enlace al Desafío roto: esta tarea era parte de un desafío pero el desafío (o grupo) ha sido eliminado. ¿Qué hacemos con las tareas del Desafío?",
- "keepThem": "Mantenerlas",
+ "brokenChallenge": "Enlace al desafío interrumpido: esta tarea formaba parte de un desafío, pero el desafío (o el grupo) se ha eliminado. ¿Qué hacemos con las tareas del desafío?",
+ "keepThem": "Conservarlas",
"removeThem": "Eliminarlas",
- "challengeCompleted": "Este Desafío ha sido completado y el ganador es ¡<%= user %>! ¿Qué hacemos con las tareas del Desafío?",
- "unsubChallenge": "Enlace al Desafío roto: esta tarea era parte de un Desafío, pero te has dado de baja del mismo. ¿Qué hacemos con las tareas del Desafío?",
- "challengeWinner": "Fue el ganador en los siguientes desafíos",
+ "challengeCompleted": "Este desafío ha finalizado y el ganador es ¡<%= user %>! ¿Qué hacemos con las tareas del desafío?",
+ "unsubChallenge": "Enlace al desafío interrumpido: esta tarea formaba parte de un desafío, pero ya no participas en él. ¿Qué hacemos con las tareas del desafío?",
+ "challengeWinner": "Ha ganado los siguientes desafíos",
"challenges": "Desafíos",
"noChallenges": "Ningún desafio todavía, visita",
"toCreate": "para crear uno.",
"selectWinner": "Seleccionar un ganador y cerrar el desafío:",
- "deleteOrSelect": "Borrar o seleccionar el ganador",
+ "deleteOrSelect": "Borrar o seleccionar al ganador",
"endChallenge": "Terminar desafío",
- "challengeDiscription": "Estas son las tareas de Desafío que serán añadidas a tu lista de tareas cuando te unas al Desafío. Las tareas de Desafío de abajo cambiarán de color y tendrán gráficas de acuerdo al progreso general del grupo.",
+ "challengeDiscription": "Estas son las tareas del desafío que se añadirán a tu panel de tareas cuando te apuntes a él. Las tareas de ejemplo del desafío, más abajo, cambiarán de color y tendrán gráficos para que puedas consultar el progreso general del grupo.",
"hows": "¿Qué tal vamos?",
"filter": "Filtro",
"groups": "Grupos",
@@ -25,43 +25,58 @@
"membership": "Membresía",
"participating": "Participando",
"notParticipating": "No participando",
- "either": "Cualquiera",
- "createChallenge": "Crear Desafío",
+ "either": "Ambos",
+ "createChallenge": "Crear desafío",
"discard": "Descartar",
- "challengeTitle": "Título del Desafío",
+ "challengeTitle": "Título del desafío",
"challengeTag": "Etiqueta",
- "challengeTagPop": "Los Desafíos aparecen en la lista de etiquetas y en los consejos de tarea. Así que, aunque el título debería ser descriptivo, también se necesita una «abreviatura». Por ejemplo, «Perder 5 kilos en 3 meses» se puede convertir en «-5 kg» (pulsar en «?» proveerá más información).",
+ "challengeTagPop": "Los desafíos aparecen en las listas de etiquetas y en las explicaciones de las tareas. Así que, aunque el título debería ser descriptivo, también se necesita una versión abreviada. Por ejemplo, \"Perder 5 kilos en 3 meses\" puede abreviarse como \"-5 kg\". (Haz clic para obtener más información).",
"challengeDescr": "Descripción",
"prize": "Premio",
- "prizePop": "Si es posible para alguien 'ganar' tu desafío, puedes, opcionalmente, recompensar a dicho ganador con un premio en Gemas. El máximo número de Gemas que puedes entregar es el número de Gemas que tú posees (más el número de Gemas del Gremio, si tú creaste el Gremio de este desafío). Nota: este premio no puede ser cambiado más adelante. ",
- "prizePopTavern": "Si es posible 'ganar' tu desafío, puedes premiar al ganador con Gemas. Máximo = número de Gemas que posees. Nota: este premio no puede ser cambiado más adelante, y no te será reembolsado si el Desafío es cancelado.",
- "publicChallenges": "Mínimo 1 Gema para desafíos públicos (ayuda a prevenir el spam, de verdad que sí).",
+ "prizePop": "Si alguien puede \"ganar\" tu desafío, tienes la opción de recompensar al ganador con un premio en gemas. La cantidad máxima de gemas que puedes conceder es el número de gemas que poseas (más el número de gemas del gremio, si fuiste tú quien creó el gremio de este desafío). Nota: El premio no se puede cambiar más adelante. ",
+ "prizePopTavern": "Si alguien puede \"ganar\" tu desafío, tienes la opción de recompensar al ganador con un premio en gemas. Máximo = número de gemas que posees. Nota: El premio no se puede cambiar más adelante y si un desafío de la Taberna se cancela, no recuperarás las gemas.",
+ "publicChallenges": "Mínimo: 1 gema para desafíos públicos (ayuda a prevenir el spam, en serio).",
"officialChallenge": "Desafío oficial de Habitica",
- "by": "por",
+ "by": "de",
"participants": "Participantes: <%= membercount %>",
- "join": "Unirse",
+ "join": "Participar",
"exportChallengeCSV": "Exportar a CSV",
- "selectGroup": "Por favor seleccione un grupo",
+ "selectGroup": "Selecciona un grupo",
"challengeCreated": "Desafío creado",
"sureDelCha": "¿Seguro que quieres eliminar este desafío?",
"sureDelChaTavern": "¿Seguro que quieres eliminar este desafío? No recuperarás las gemas.",
- "removeTasks": "Eliminar Tareas",
- "keepTasks": "Mantener Tareas",
- "closeCha": "Cerrar desafío y...",
- "leaveCha": "Dejar el desafio y...",
+ "removeTasks": "Eliminar tareas",
+ "keepTasks": "Conservar tareas",
+ "closeCha": "Cerrar el desafío y...",
+ "leaveCha": "Abandonar el desafío y...",
"challengedOwnedFilterHeader": "Propiedad",
"challengedOwnedFilter": "En posesión",
"challengedNotOwnedFilter": "Fuera de posesión",
- "challengedEitherOwnedFilter": "Cualquiera",
+ "challengedEitherOwnedFilter": "Ambos",
"backToChallenges": "Volver a todos los desafíos",
"prizeValue": "Premio: <%= gemcount %> <%= gemicon %>",
"clone": "Clonar",
- "challengeNotEnoughGems": "No tienes suficientes Gemas para publicar este Desafío.",
+ "challengeNotEnoughGems": "No tienes suficientes gemas para publicar este desafío.",
"noPermissionEditChallenge": "No tienes permiso para editar este desafío",
"noPermissionDeleteChallenge": "No tienes permiso para eliminar este desafío",
- "noPermissionCloseChallenge": "No tienes permiso para cerrar este desafío o reto.",
- "congratulations": "Felicidades!",
- "hurray": "Muy bien!",
+ "noPermissionCloseChallenge": "No tienes permiso para cerrar este desafío",
+ "congratulations": "¡Enhorabuena!",
+ "hurray": "¡Muy bien!",
"noChallengeOwner": "sin dueño",
- "noChallengeOwnerPopover": "Este Desafío no tiene dueño o la persona que lo creó borró su cuenta."
+ "noChallengeOwnerPopover": "Este desafío no tiene dueño porque la persona que lo creó ha eliminado su cuenta.",
+ "challengeMemberNotFound": "User not found among challenge's members",
+ "onlyGroupLeaderChal": "Only the group leader can create challenges",
+ "tavChalsMinPrize": "Prize must be at least 1 Gem for Tavern challenges.",
+ "cantAfford": "You can't afford this prize. Purchase more gems or lower the prize amount.",
+ "challengeIdRequired": "\"challengeId\" must be a valid UUID.",
+ "winnerIdRequired": "\"winnerId\" must be a valid UUID.",
+ "challengeNotFound": "Challenge not found.",
+ "onlyLeaderDeleteChal": "Only the challenge leader can delete it.",
+ "onlyLeaderUpdateChal": "Only the challenge leader can update it.",
+ "winnerNotFound": "Winner with id \"<%= userId %>\" not found or not part of the challenge.",
+ "noCompletedTodosChallenge": "\"includeComepletedTodos\" is not supported when fetching a challenge tasks.",
+ "userTasksNoChallengeId": "When \"tasksOwner\" is \"user\" \"challengeId\" can't be passed.",
+ "onlyChalLeaderEditTasks": "Tasks belonging to a challenge can only be edited by the leader.",
+ "userAlreadyInChallenge": "User is already participating in this challenge.",
+ "cantOnlyUnlinkChalTask": "Only broken challenges tasks can be unlinked."
}
\ No newline at end of file
diff --git a/common/locales/es/character.json b/common/locales/es/character.json
index ac5d398b79..c399625c9b 100644
--- a/common/locales/es/character.json
+++ b/common/locales/es/character.json
@@ -1,5 +1,6 @@
{
- "statsAch": "Estadísticas y Logros",
+ "communityGuidelinesWarning": "Ten en cuenta que tu Nombre de Usuario, foto de perfil y sobre mí deben ajustarse a Normas de la Comunidad (p.ej. nada de obscenidades, temas adultos, insultos, etc.) Si tienes alguna pregunta sobre si algo es apropiado o no, ¡puedes mandar un correo electrónico a leslie@habitica.com!",
+ "statsAch": "Estadísticas y logros",
"profile": "Perfil",
"avatar": "Personalizar avatar",
"other": "Otro",
@@ -34,7 +35,7 @@
"beard": "Barba",
"mustache": "Bigote",
"flower": "Flor",
- "wheelchair": "Wheelchair",
+ "wheelchair": "Silla de ruedas",
"basicSkins": "Pieles Básicas",
"rainbowSkins": "Pieles Arcoiris",
"pastelSkins": "Pieles pastel",
@@ -66,7 +67,7 @@
"ultimGearText": "Has conseguido el conjunto de armas y armadura de mayor nivel en las siguientes clases:",
"level": "Nivel",
"levelUp": "¡Subiste de Nivel!",
- "gainedLevel": "Has ganado un nivel!",
+ "gainedLevel": "¡Has subido de nivel!",
"leveledUp": "Por cumplir tus tareas en la vida real has subido al Nivel <%= level %>!",
"fullyHealed": "Has recobrado tu salud!",
"huzzah": "Oh si!",
@@ -109,6 +110,7 @@
"mage": "Mago",
"mystery": "Misterio",
"changeClass": "Cambiar Clase, Devolver Puntos de Atributo",
+ "lvl10ChangeClass": "Para cambiar de clase debes estar como mínimo en el nivel 10.",
"levelPopover": "Con cada nivel consigues un punto para asignar a un atributo a tu elección. Lo puedes asignar manualmente o dejar que el juego decida por tí usando una de las opciones de Asignación Automática.",
"unallocated": "Puntos de Atributo no Asignados",
"haveUnallocated": "Tienes <%= points %> puntos de Atributo(s) no asignados",
@@ -164,5 +166,7 @@
"int": "INT",
"showQuickAllocation": "Mostrar asignación de puntos",
"hideQuickAllocation": "Ocultar asignación de puntos",
- "quickAllocationLevelPopover": "Con cada nivel, ganas un punto que puedes asignar al atributo que elijas. Puedes hacerlo de forma manual o dejar que el juego decida por ti con una de las opciones de asignación automática que encontrarás en Usuario -> Estadísticas."
+ "quickAllocationLevelPopover": "Con cada nivel, ganas un punto que puedes asignar al atributo que elijas. Puedes hacerlo de forma manual o dejar que el juego decida por ti con una de las opciones de asignación automática que encontrarás en Usuario -> Estadísticas.",
+ "invalidAttribute": "\"<%= attr %>\" no es un atributo válido.",
+ "notEnoughAttrPoints": "No tienes suficientes puntos de atributo."
}
\ No newline at end of file
diff --git a/common/locales/es/content.json b/common/locales/es/content.json
index b4a393ca00..dd6c876f50 100644
--- a/common/locales/es/content.json
+++ b/common/locales/es/content.json
@@ -113,6 +113,12 @@
"questEggSnailText": "Caracol",
"questEggSnailMountText": "Caracol",
"questEggSnailAdjective": "un lento pero conciso",
+ "questEggFalconText": "Halcón",
+ "questEggFalconMountText": "Halcón",
+ "questEggFalconAdjective": "un veloz",
+ "questEggTreelingText": "Brote",
+ "questEggTreelingMountText": "Brote",
+ "questEggTreelingAdjective": "un frondoso",
"eggNotes": "Encuentra una poción de eclosión para verter en este huevo y eclosionará en <%= eggAdjective(locale) %> <%= eggText(locale) %>.",
"hatchingPotionBase": "Base",
"hatchingPotionWhite": "Blanco",
@@ -126,6 +132,7 @@
"hatchingPotionGolden": "de Oro",
"hatchingPotionSpooky": "Escalofriante",
"hatchingPotionPeppermint": "Menta",
+ "hatchingPotionFloral": "Floral",
"hatchingPotionNotes": "Echa esto en un huevo, y eclosionará como una mascota <%= potText(locale) %>.",
"premiumPotionAddlNotes": "No es usable en huevos de mascota de misión.",
"foodMeat": "Carne",
diff --git a/common/locales/es/contrib.json b/common/locales/es/contrib.json
index 15f1dc317d..1a7398feea 100644
--- a/common/locales/es/contrib.json
+++ b/common/locales/es/contrib.json
@@ -35,8 +35,12 @@
"hallContributors": "Salón de los Colaboradores",
"hallPatrons": "Salón de Patrocinadores",
"rewardUser": "Recompensar Usuario",
- "UUID": "UUID",
+ "UUID": "N.º de Usuario",
"loadUser": "Cargar Usuario",
+ "noAdminAccess": "No tienes acceso de administrador.",
+ "pageMustBeNumber": "req.query.page debe ser un número",
+ "userNotFound": "Usuario no encontrado.",
+ "invalidUUID": "UUID debe ser válido",
"title": "Título",
"moreDetails": "Más detalles (1-7)",
"moreDetails2": "Más detalles (8-9)",
@@ -52,8 +56,8 @@
"visitHeroes": "Visite el Salón de Héroes (colaboradores y patrocinadores)",
"conLearn": "Aprenda más acerca de las recompensas de colaborador",
"conLearnHow": "Aprenda cómo contribuir a Habitica",
- "surveysSingle": "Ayudó al crecimiento de Habitica rellenando una encuesta. No hay encuestas activas.",
- "surveysMultiple": "Ayudó al crecimiento de Habitica rellenando <%= surveys %> encuestas. No hay encuestas activas.",
+ "surveysSingle": "Helped Habitica grow, either by filling out a survey or helping with a major testing effort. Thank you!",
+ "surveysMultiple": "Helped Habitica grow on <%= surveys %> occasions, either by filling out a survey or helping with a major testing effort. Thank you!",
"currentSurvey": "Encuesta Actual",
"surveyWhen": "Este logro será concedido a todos los participantes cuando la encuesta haya sido procesada a finales de marzo.",
"blurbInbox": "Aquí se guardan tus mensajes privados. Para enviar un mensaje a alguien, haz clic en el icono del sobre que aparece junto a su nombre en el chat de la Taberna, un grupo o un gremio. Si has recibido un mensaje privado inapropiado, envíale una captura de pantalla del mensaje a Lemoness (leslie@habitica.com).",
diff --git a/common/locales/es/death.json b/common/locales/es/death.json
index 6aba43d6db..99b19e26d1 100644
--- a/common/locales/es/death.json
+++ b/common/locales/es/death.json
@@ -12,5 +12,6 @@
"losingHealthQuickly": "¿Estás perdiendo salud muy rápido?",
"lowHealthTips3": "Las tareas diarias que no cumples te dañan por la noche: no añadas demasiadas al principio.",
"lowHealthTips4": "Si tienes alguna tarea diaria que no sea necesario realizar un día concreto, haz clic en el icono del lápiz y desactiva ese día.",
- "goodLuck": "¡Suerte!"
+ "goodLuck": "¡Suerte!",
+ "cannotRevive": "No se puede revivir si el jugador no está muerto."
}
\ No newline at end of file
diff --git a/common/locales/es/front.json b/common/locales/es/front.json
index 2ba8eb581d..fc74f0511b 100644
--- a/common/locales/es/front.json
+++ b/common/locales/es/front.json
@@ -1,102 +1,104 @@
{
- "FAQ": "Preguntas Frecuentes",
- "accept1Terms": "Al hacer clíck en el botón de abajo, acepto los",
+ "FAQ": "Preguntas frecuentes",
+ "accept1Terms": "Al hacer clic en el botón de abajo, acepto los",
"accept2Terms": "y la",
- "alexandraQuote": "No pude NO hablar de [Habitica] durante mi conferencia en Madrid. Herramienta imprescindible para autónomos que todavía necesitan un jefe.",
+ "alexandraQuote": "No pude NO hablar de [Habitica] durante mi conferencia en Madrid. Una herramienta imprescindible para los autónomos que aún necesitan un jefe.",
"althaireQuote": "Estar siempre en una misión realmente me motiva a hacer todas mis tareas diarias y pendientes. Mi mayor motivación es no decepcionar a mi grupo.",
- "andeeliaoQuote": "Genial producto, empecé hace unos pocos días y ya soy más productivo y consciente de mi tiempo!",
- "autumnesquirrelQuote": "Estoy postergando menos en el trabajo en las tareas domésticas y pago las cuentas a tiempo.",
- "businessSample1": "Confirmar 1 página de Inventario",
- "businessSample2": "20 minutos Archivando",
- "businessSample3": "Ordenar y Procesar Bandeja de Entrada",
- "businessSample4": "Preparar 1 Documento para el Cliente",
- "businessSample5": "Llamar Clientes/Postponer llamadas de teléfono",
- "businessText": "Usa Habitica en tu negocio",
- "choreSample1": "Poner Ropa Sucia en el Cesto",
- "choreSample2": "20 minutos de Tareas del Hogar",
- "choreSample3": "Lavar los Platos",
- "choreSample4": "Recoger Una Habitación",
- "choreSample5": "Lavar y Secar la Colada",
+ "andeeliaoQuote": "Es genial, empecé hace unos pocos días y ¡ya soy más productivo y más consciente de mi tiempo!",
+ "autumnesquirrelQuote": "Vagueo menos en el trabajo y las tareas domésticas, y llevo todos los pagos al día.",
+ "businessSample1": "Confirmar 1 página de inventario",
+ "businessSample2": "Archivar 20 minutos",
+ "businessSample3": "Ordenar y procesar la bandeja de entrada",
+ "businessSample4": "Preparar 1 documento para un cliente",
+ "businessSample5": "Llamar a clientes / posponer llamadas de teléfono",
+ "businessText": "Usa Habitica en tu empresa",
+ "choreSample1": "Poner ropa sucia en el cesto",
+ "choreSample2": "20 minutos de tareas del hogar",
+ "choreSample3": "Lavar los platos",
+ "choreSample4": "Recoger una habitación",
+ "choreSample5": "Poner una lavadora y tenderla",
"chores": "Tareas de casa",
"clearBrowserData": "Borrar datos del navegador",
- "communityBug": "Enviar un Informe de Error",
- "communityExtensions": "Complementos y Extensiones",
+ "communityBug": "Enviar un informe de error",
+ "communityExtensions": "Complementos y extensiones",
"communityFacebook": "Facebook",
- "communityFeature": "Solicitar una Característica",
+ "communityFeature": "Solicitar una función",
"communityForum": "Foro",
"communityKickstarter": "Kickstarter",
"communityReddit": "Reddit",
- "companyAbout": "Cómo Funciona",
+ "companyAbout": "Cómo funciona",
"companyBlog": "Blog",
+ "devBlog": "Blog de desarrolladores",
"companyDonate": "Donar",
"companyExtensions": "Extensiones",
"companyPrivacy": "Privacidad",
"companyTerms": "Condiciones",
"companyVideos": "Vídeos",
"contribUse": "Los colaboradores de Habitica utilizan:",
- "dragonsilverQuote": "No puedo decir cuánto tiempo y sistemas de seguimiento de tareas he probado a lo largo de las décadas...[Habitica] es lo único que he utilizado que realmente me ayuda a terminar mis tareas más allá de simplemente listarlas.",
+ "dragonsilverQuote": "Ya no sé cuánto tiempo ni cuántos sistemas para guardar tareas he probado a lo largo de décadas... [Habitica] es el único que me ayuda realmente a terminar mis tareas y no solo apuntarlas.",
"dreimQuote": "Cuando descubrí [Habitica] el verano pasado, acababa de suspender la mitad de mis exámenes. Gracias a las tareas diarias, conseguí organizarme y tener disciplina y, hace un mes, aprobé todo.",
- "elmiQuote": "¡Cada mañana estoy deseando levantarme para ganar un poco de oro!",
- "email": "Correo Electrónico",
- "emailNewPass": "Enviar Nueva Contraseña",
- "evagantzQuote": "La primera cita con mi dentista en la que el higienista estaba realmente excitado con mis hábitos de uso del hilo dental. ¡Gracias [Habitica]!",
- "examplesHeading": "Los Jugadores usan Habitica para gestionar...",
- "featureAchievementByline": "¿Haces algo totalmente genial? ¡Consigue una insignia y muéstrala!",
- "featureAchievementHeading": "Logro de Insignias",
- "featureEquipByline": "¡Compra ediciones limitadas de equipo, pociones, y otras golosinas virtuales en nuestro Mercado con tus premios de tareas!",
+ "elmiQuote": "Todas las mañanas me apetece levantarme para ganar un poco de oro.",
+ "email": "Correo electrónico",
+ "emailNewPass": "Enviar nueva contraseña",
+ "evagantzQuote": "Es la primera vez que voy al dentista y no me echa la bronca por no usar hilo dental. ¡Gracias, [Habitica]!",
+ "examplesHeading": "Los jugadores usan Habitica para gestionar...",
+ "featureAchievementByline": "¿Has hecho algo increíble? ¡Consigue una insignia y exhíbela!",
+ "featureAchievementHeading": "Insignias de logros",
+ "featureEquipByline": "Compra ediciones limitadas de equipo, pociones y otros objetos virtuales en nuestro Mercado, gracias a las recompensas que obtienes por cumplir tus tareas.",
"featureEquipHeading": "Equipo y extras",
- "featurePetByline": "Huevos y artículos aparecen cuando completas tus tareas. ¡Se lo más productivo posible para coleccionar mascotas y monturas!",
- "featurePetHeading": "Mascotas y Monturas",
- "featureSocialByline": "Únete a grupos con intereses comunes que compartan tu opinión. Crea Desafios para competir contra otros usuarios.",
+ "featurePetByline": "Cuando completas tus tareas, aparecen huevos y artículos. Para coleccionar mascotas y monturas, ¡sé tan productivo como puedas!",
+ "featurePetHeading": "Mascotas y monturas",
+ "featureSocialByline": "Únete a grupos con intereses comunes. Crea desafíos para competir contra otros usuarios.",
"featureSocialHeading": "Juego social",
- "featuredIn": "Destacado",
- "featuresHeading": "También contamos con...",
+ "featuredIn": "Hablan de nosotros en",
+ "featuresHeading": "También tenemos...",
+ "footerDevs": "Desarrolladores",
"footerCommunity": "Comunidad",
- "footerCompany": "Compañía",
+ "footerCompany": "Empresa",
"footerMobile": "Móvil",
"footerSocial": "Social",
- "forgotPass": "Olvidé mi Contraseña",
- "frabjabulousQuote": "[Habitica] es la razón por la que conseguí un trabajo genial y bien pagado... y algo más milagroso todavía ¡ahora uso el hilo dental a diario!",
+ "forgotPass": "Olvidé mi contraseña",
+ "frabjabulousQuote": "[Habitica] es la razón por la que conseguí un trabajo genial y bien pagado... y algo más milagroso todavía: ahora no me olvido ni un solo día de usar hilo dental.",
"free": "Únete gratis",
- "gamifyButton": "¡Convierte tu vida en un juego hoy!",
- "goalSample1": "Practicar Piano durante 1 Hora",
- "goalSample2": "Trabajar en un artículo para su publicación",
- "goalSample3": "Trabajar en artículos del blog",
+ "gamifyButton": "Convierte tu vida en un juego",
+ "goalSample1": "Practicar piano 1 hora",
+ "goalSample2": "Trabajar en un artículo para publicar",
+ "goalSample3": "Escribir entrada del blog",
"goalSample4": "Lección de japonés en Duolingo",
- "goalSample5": "Leer un Artículo Informativo",
+ "goalSample5": "Leer un artículo informativo",
"goals": "Metas",
"health": "Salud",
- "healthSample1": "Beber Agua/Refresco",
- "healthSample2": "Masticar Chicle/Fumar",
+ "healthSample1": "Beber agua/refresco",
+ "healthSample2": "Masticar chicle / fumar",
"healthSample3": "Utilizar escaleras/ascensor",
"healthSample4": "Comer comida saludable/basura",
- "healthSample5": "Romper a sudar durante 1 hora",
+ "healthSample5": "Hacer ejercicio 1 hora",
"history": "Historia",
- "infhQuote": "[Habitica] realmente me ha ayudado a estructurar mi vida universitaria",
- "invalidEmail": "Se requiere una dirección de correo electrónico válida para resetear la contraseña.",
- "irishfeet123Quote": "He tenido terribles hábitos limpiando mi casa completamente tras las comidas y dejándome tazas por todas partes. ¡[Habitica] ha curado eso!",
- "joinOthers": "¡Únete a las <%= userCount %> personas que hacen que completar objetivos sea divertido!",
- "kazuiQuote": "Antes de [Habitica], estaba atascado con mi tesis, así como insatisfecho con mi disciplina personal en cuanto a tareas domésticas y cosas como aprender vocabulario y estudiar teoría de Go. Resulta que romper estas tareas en pequeñas y manejables listas es exactamente lo que me mantiene motivado y constantemente trabajando.",
+ "infhQuote": "[Habitica] me ha ayudado muchísimo a organizarme para terminar mi posgrado.",
+ "invalidEmail": "Para restablecer la contraseña, se necesita una dirección de correo electrónico válida.",
+ "irishfeet123Quote": "Antes llevaba fatal la limpieza de la casa: después de comer, dejaba todo tirado y había vasos por todas partes. Con [Habitica], ya no soy así.",
+ "joinOthers": "Alcanzar tus metas puede ser divertido: únete a las <%= userCount %> personas que ya lo han comprobado.",
+ "kazuiQuote": "Antes de usar [Habitica], estaba atascado con mi tesis e insatisfecho con mi disciplina personal: las tareas domésticas, aprender vocabulario, estudiar teoría de Go... Parece que dividir esas tareas en listas más pequeñas y manejables es la forma de mantenerme motivado y trabajar con constancia.",
"landingadminlink": "paquetes administrativos",
"landingend": "¿Todavía no estás convencido?",
- "landingend2": "Vea una lista más detallada de",
- "landingend3": "¿Estás buscando una experiencia más privada? Echale un vistazo a nuestros",
- "landingend4": "que son perfectos para familias, maestros, grupos de apoyo, y negocios.",
+ "landingend2": "Mira una lista más detallada de",
+ "landingend3": ". ¿Buscas algo menos público? Echa un vistazo a nuestros",
+ "landingend4": "que son perfectos para familias, maestros, grupos de apoyo y empresas.",
"landingfeatureslink": "nuestras características",
- "landingp1": "El problema con la mayoría de las aplicaciones de productividad en el mercado es que no ofrecen ningún incentivo para seguir usándolas. ¡Habitica soluciona esto haciendo que crear hábitos sea divertido! Premiándote por tus éxitos y penalizándote por tus despistes, Habitica proporciona motivación externa para completar tus tareas diarias.",
- "landingp2": "Cada vez que refuerces un hábito positivo, completes una tarea diaria o te encargues de una antigua tarea pendiente, HabitPRG te recompensa con puntos de experiencia y oro. Conforme vas ganando experiencia, subes de nivel, mejorando tus estadísticas y desbloqueando más características, como clases, mascotas... El oro se puede gastar en objetos que cambian tu experiencia de juego o en recompensas personalizadas que tu has creado para motivarte. Cuando los más pequeños éxitos te premian con una recompensa inmediata, tienes menos tendencia a dejar cosas sin hacer.",
- "landingp2header": "Recompensa Inmediata",
- "landingp3": "Siempre que realices un mal hábito o falles al completar una de tus tareas diarias, perderás salud. Si tu salud cae demasiado, perderás alguno de los progresos realizados. Proporcionando consecuencias inmediatas, Habitica puede ayudar a romper malos hábitos y ciclos de procrastinación antes de que causen problemas en el mundo real.",
+ "landingp1": "El problema con la mayoría de las aplicaciones de productividad que hay en el mercado es que no ofrecen ningún incentivo para seguir usándolas. Habitica sí, ya que hace que crear hábitos sea divertido. Te premia por tus éxitos y te penaliza por tus deslices, y es una motivación externa para completar tus tareas cotidianas.",
+ "landingp2": "Cada vez que refuerzas un hábito positivo, completas una tarea diaria o te encargas de una tarea que lleva mucho tiempo pendiente, Habitica te recompensa con puntos de experiencia y oro. Conforme vas ganando experiencia, subes de nivel, con lo que mejoran tus estadísticas y desbloqueas más funciones, como las clases, las mascotas... El oro se puede gastar en objetos que cambian tu forma de jugar o en las recompensas personalizadas que crees para motivarte. Cuando cualquier éxito, por pequeño que sea, te ofrece una recompensa inmediata, tienes menos tendencia a dejar las cosas sin hacer.",
+ "landingp2header": "Recompensa inmediata",
+ "landingp3": "Siempre que realices un mal hábito o no completes una de tus tareas diarias, perderás salud. Si tu salud empeora demasiado, perderás parte de tus progresos. En Habitica, las consecuencias de tus acciones son inmediatas, lo que puede ayudar a romper malos hábitos y ciclos de procrastinación antes de que causen problemas en el mundo real.",
"landingp3header": "Consecuencias",
- "landingp4": "Con una comunidad activa, Habitica proporciona la responsabilidad que necesitas para seguir tus tareas. Con el sistema de grupos, puedes traer a un grupo de amigos más cercanos para animarte. El sistema de gremios te permite encontrar gente con intereses u obstáculos similares, así puedes compartir tus metas e intercambiar trucos de cómo abordar tus problemas- En Habitica, la comunidad significa que tienes el apoyo y la responsabilidad que necesitar para triunfar.",
+ "landingp4": "En Habitica, con una comunidad de usuarios activos, te proporciona la responsabilidad que necesitas para no dejar de lado tus tareas. Con el sistema de grupos, puedes traer a un grupo de buenos amigos para animarte. El sistema de gremios te permite encontrar a gente con intereses u obstáculos similares, para que puedas compartir tus metas e intercambiar trucos sobre cómo abordar los problemas. En Habitica, gracias a la comunidad de usuarios, tienes el apoyo y la responsabilidad que necesitas para hacer las cosas bien.",
"landingp4header": "Responsabilidad",
- "leadText": "Habitica es un creador de hábitos gratuito y una aplicación de productividad que trata tu vida real como un juego. Con recompensas dentro del juego y castigos para motivarte y una sólida red social para inspirarte, Habitica puede ayudarte a alcanzar tus objetivos de volverte saludable, trabajador y feliz.",
- "login": "Entrar",
- "loginAndReg": "Entrar / Crear cuenta",
- "loginFacebookAlt": "Entrar / Regístrate con Facebook",
- "logout": "Cerrar Sesión",
+ "leadText": "Habitica es una aplicación gratuita de productividad y formación de hábitos que trata tu vida real como un juego. Con recompensas dentro del juego y castigos para motivarte, además de una sólida red social para inspirarte, Habitica te ayuda a alcanzar tus objetivos y llevar una vida más sana, responsable y feliz.",
+ "login": "Iniciar sesión",
+ "loginAndReg": "Iniciar sesión o registrarse",
+ "loginFacebookAlt": "Iniciar sesión o registrarse con Facebook",
+ "logout": "Cerrar sesión",
"marketing1Header": "Mejora tus hábitos jugando",
- "marketing1Lead1": "Habitica es un videojuego pensado para mejorar tus hábitos en la vida real que \"gamifica\" tu vida, convirtiendo todas tus tareas (hábitos, tareas diarias y pendientes) en pequeños monstruos que debes conquistar. Cuanto mejor eres en esto, más progresas en el juego. Si fallas en la vida real, tu personaje empezará a sufrir las consecuencias en el juego.",
+ "marketing1Lead1": "Habitica es un videojuego pensado para mejorar tus hábitos en la vida real que \"gamifica\" tu vida, convirtiendo todas tus tareas (hábitos, tareas diarias y pendientes) en pequeños monstruos a los que debes vencer. Cuanto mejor lo hagas, más progresarás en el juego. Si no cumples algo en la vida real, tu personaje empezará a sufrir las consecuencias en el juego.",
"marketing1Lead2": "Obtén increíbles equipos. Mejora tus hábitos para construir tu avatar. Muestra el increíble armamento que has conseguido",
"marketing1Lead2Title": "Obtén increíbles equipos",
"marketing1Lead3": "Encuentra premios aleatorios. Para algunos, las apuestas son las que los motiva, en un sistema llamado \"recompensa estocástica\". Habitica se acomoda a todos los estilos de refuerzo: positivo, negativo, predictivo y aleatorio.",
@@ -182,6 +184,7 @@
"zelahQuote": "Con [Habitica], Puedo irme a la cama a tiempo pensando en ganar puntos por acostarme pronto o perder salud por hacerlo tarde.",
"reportAccountProblems": "Informar de problemas de Cuenta",
"reportCommunityIssues": "Informar de problemas de la Comunidad",
+ "subscriptionPaymentIssues": "Subscription and Payment Issues",
"generalQuestionsSite": "Preguntas Generales sobre la Web",
"businessInquiries": "Consultas de Empresas",
"merchandiseInquiries": "Consultas de Merchandise",
@@ -222,5 +225,32 @@
"altAttrWebstorm": "WebStorm",
"altAttrGithub": "GitHub",
"altAttrTrello": "Trello",
- "altAttrSlack": "Slack"
+ "altAttrSlack": "Slack",
+ "missingAuthHeaders": "Missing authentication headers.",
+ "missingAuthParams": "Missing authentication parameters.",
+ "missingUsernameEmail": "Missing username or email.",
+ "missingEmail": "Missing email.",
+ "missingUsername": "Missing username.",
+ "missingPassword": "Missing password.",
+ "missingNewPassword": "Missing new password.",
+ "wrongPassword": "Wrong password.",
+ "notAnEmail": "Invalid email address.",
+ "emailTaken": "Email address is already used in an account.",
+ "newEmailRequired": "Missing new email address.",
+ "usernameTaken": "Username already taken.",
+ "passwordConfirmationMatch": "Password confirmation doesn't match password.",
+ "invalidLoginCredentials": "Incorrect username and/or email and/or password.",
+ "passwordReset": "If we have your email on file, your password reset link has been sent to your email.",
+ "passwordResetEmailSubject": "Password Reset for Habitica",
+ "passwordResetEmailText": "Password for <%= username %> has been reset to <%= newPassword %> . Important! Both username and password are case-sensitive -- you must enter both exactly as shown here. We recommend copying and pasting both instead of typing them. Log in at <%= baseUrl %>. After you have logged in, head to <%= baseUrl %>/#/options/settings/settings and change your password.",
+ "passwordResetEmailHtml": "Password for <%= username %> has been reset to <%= newPassword %>.
Important! Both username and password are case-sensitive -- you must enter both exactly as shown here. We recommend copying and pasting both instead of typing them.
Log in at <%= baseUrl %>. After you have logged in, head to <%= baseUrl %>/#/options/settings/settings and change your password.",
+ "invalidLoginCredentialsLong": "Uh-oh - your username or password is incorrect.\n- Make sure your username or email is typed correctly.\n- You may have signed up with Facebook, not email. Double-check by trying Facebook login.\n- If you forgot your password, click \"Forgot Password\".",
+ "invalidCredentials": "There is no account that uses those credentials.",
+ "accountSuspended": "Account has been suspended, please contact leslie@habitica.com with your User ID \"<%= userId %>\" for assistance.",
+ "onlyFbSupported": "Only Facebook is supported currently.",
+ "cantDetachFb": "Account lacks another authentication method, can't detach Facebook.",
+ "onlySocialAttachLocal": "Local authentication can be added to only a social account.",
+ "invalidReqParams": "Invalid request parameters.",
+ "memberIdRequired": "\"member\" must be a valid UUID.",
+ "heroIdRequired": "\"heroId\" must be a valid UUID."
}
\ No newline at end of file
diff --git a/common/locales/es/gear.json b/common/locales/es/gear.json
index b65be33894..2e0953745b 100644
--- a/common/locales/es/gear.json
+++ b/common/locales/es/gear.json
@@ -144,13 +144,13 @@
"weaponSpecialWinter2016HealerText": "Cañón de confeti",
"weaponSpecialWinter2016HealerNotes": "¡¡¡¡¡¡¡WIIIIIIIIIIIIIIIII!!!!!!! ¡¡¡¡¡¡¡FELIZ WINTER WONDERLAND!!!!!!! Incrementa la Inteligencia por <%= int %>. Equipamiento de Edición Limitada de Invierno 2015-2016.",
"weaponSpecialSpring2016RogueText": "Bolas de Fuego",
- "weaponSpecialSpring2016RogueNotes": "You've mastered the ball, the club, and the knife. Now you advance to juggling fire! Awoo! Increases Strength by <%= str %>. Limited Edition 2016 Spring Gear.",
- "weaponSpecialSpring2016WarriorText": "Cheese Mallet",
+ "weaponSpecialSpring2016RogueNotes": "Ya dominas la bola, la porra y el cuchillo. ¡Ahora pasarás a los malabares con fuego! ¡Woo! Incrementa la Fuerza un <%= str %>. Equipación de Edición Limitada de Primavera de 2015",
+ "weaponSpecialSpring2016WarriorText": "Mazo de queso",
"weaponSpecialSpring2016WarriorNotes": "Nadie tiene tantos amigos como el ratón con tiernos quesos. Incrementa la Fuerza en <%= str %>. Equipamiento de Edición Limitada de Primavera 2016.",
- "weaponSpecialSpring2016MageText": "Staff of Bells",
- "weaponSpecialSpring2016MageNotes": "Abra-cat-abra! So dazzling, you might mesmerize yourself! Ooh... it jingles... Increases Intelligence by <%= int %> and Perception by <%= per %>. Limited Edition 2016 Spring Gear.",
+ "weaponSpecialSpring2016MageText": "Bastón de campanas",
+ "weaponSpecialSpring2016MageNotes": "¡Abra-gat-abra! ¡Estás tan resplandeciente que te vas a encandilar a ti mismo! Oh... tintinea... Incrementa tu Inteligencia un <%= int %> y tu Percepción un <%= per %>. Equipación de Primavera 2016, Edición Limitada.",
"weaponSpecialSpring2016HealerText": "Varita de Flor de Primavera",
- "weaponSpecialSpring2016HealerNotes": "With a wave and a wink, you bring the fields and forests into bloom! Or bop troublesome mice on the head. Increases Intelligence by <%= int %>. Limited Edition 2016 Spring Gear.",
+ "weaponSpecialSpring2016HealerNotes": "¡Con un golpe de varita haces que florezcan los campos y los bosques! O golpeas en la cabeza a molestos ratones. Incrementa tu Inteligencia un <%= int %>. Equipación de Primavera 2016, Edición Limitada.",
"weaponMystery201411Text": "Horca de Banquete",
"weaponMystery201411Notes": "Clávasela a tus enemigos o ataca tus comidas favoritas - ¡esta horca versátil vale para todo! No confiere ningún beneficio. Artículo de suscriptor de noviembre 2014.",
"weaponMystery201502Text": "Báculo Reluciente Alado del Amor y También de la Verdad",
@@ -187,8 +187,10 @@
"weaponArmoireJesterBatonNotes": "Con una sacudida de tu bastón y una conversación ingeniosa, hasta las situaciones más complicadas se pueden calmar. Incrementa la Inteligencia y la Percepción por <%= attrs %> cada una. Armario Encantado: Conjunto de Bufón (Artículo 3 de 3).",
"weaponArmoireMiningPickaxText": "Pico de Minero",
"weaponArmoireMiningPickaxNotes": "Obtén la cantidad máxima de oro que puedas de tus tareas! Aumenta Percepción <%= per %>. Armario Encantado: Kit del Minero (Objeto 3 de 3).",
- "weaponArmoireBasicLongbowText": "Basic Longbow",
- "weaponArmoireBasicLongbowNotes": "A serviceable hand-me-down bow. Increases Strength by <%= str %>. Enchanted Armoire: Basic Archer Set (Item 1 of 3).",
+ "weaponArmoireBasicLongbowText": "Arco longo básico",
+ "weaponArmoireBasicLongbowNotes": "Un útil arco de segunda mano. Incrementa tu Fuerza un <%= str %>. Armario Encantado: Juego Básico de Arquero (Artículo 1 de 3).",
+ "weaponArmoireHabiticanDiplomaText": "Habitican Diploma",
+ "weaponArmoireHabiticanDiplomaNotes": "A certificate of significant achievement -- well done! Increases Intelligence by <%= int %>. Enchanted Armoire: Graduate Set (Item 1 of 3).",
"armor": "armadura",
"armorBase0Text": "Ropa normal",
"armorBase0Notes": "Ropa normal. No otorga ningún beneficio.",
@@ -320,14 +322,14 @@
"armorSpecialWinter2016MageNotes": "Todo buen mago sabe protegerse bien del viento invernal. Suma <%= int %> de inteligencia. Edición limitada, invierno 2015-2016.",
"armorSpecialWinter2016HealerText": "Capa de hada festiva",
"armorSpecialWinter2016HealerNotes": "Las hadas festivas se envuelven en sus alas corporales para protegerse y usan las alas de la cabeza para aprovechar el viento de cara. Así, vuelan por Habitica a velocidades de hasta 160 km/h, entregando regalos y cubriendo a todos de confetti. Vamos, graciosísimas. Suma <%= con %> de constitución. Edición limitada, invierno 2015-2016.",
- "armorSpecialSpring2016RogueText": "Canine Camo Suit",
- "armorSpecialSpring2016RogueNotes": "A clever pup knows to choose a brighter guise for concealment when everything is green and vibrant. Increases Perception by <%= per %>. Limited Edition 2016 Spring Gear.",
+ "armorSpecialSpring2016RogueText": "Traje camuflajeado canino",
+ "armorSpecialSpring2016RogueNotes": "Un cachorro inteligente sabe escoger un disfraz más vívido para pasar desapercibido cuando todo es verde y vibrante. Incrementa la Percepción por <%= per %>. Equipamiento de Edición Limitada de Primavera 2016.",
"armorSpecialSpring2016WarriorText": "Correo Poderoso",
- "armorSpecialSpring2016WarriorNotes": "Though you be but little, you are fierce! Increases Constitution by <%= con %>. Limited Edition 2016 Spring Gear.",
- "armorSpecialSpring2016MageText": "Grand Malkin Robes",
- "armorSpecialSpring2016MageNotes": "Brightly colored, so you won't be mistaken for a necromouser. Increases Intelligence by <%= int %>. Limited Edition 2016 Spring Gear.",
- "armorSpecialSpring2016HealerText": "Fluffy Bunny Breeches",
- "armorSpecialSpring2016HealerNotes": "Hippity hop! Bound from hill to hill, healing those in need. Increases Constitution by <%= con %>. Limited Edition 2016 Spring Gear.",
+ "armorSpecialSpring2016WarriorNotes": "¡A pesar de ser pequeño, eres feroz! Incrementa la Constitución por <%= con %>. Equipamiento de Edición Limitada de Primavera 2016.",
+ "armorSpecialSpring2016MageText": "Gran Túnica Felina",
+ "armorSpecialSpring2016MageNotes": "Con colores vivos, para que no te confundan con un necromante. Incrementa la Inteligencia por <%= int %>. Equipamiento de Edición Limitada de Primavera 2016.",
+ "armorSpecialSpring2016HealerText": "Pantalones Peludos de Conejito",
+ "armorSpecialSpring2016HealerNotes": "¡Boing boing! Saltando de colina en colina, sanando a quienes lo necesiten. Incrementan la Constitución por <%= con %>. Equipamiento de Edición Limitada de Primavera 2016.",
"armorMystery201402Text": "Túnica de Mensajero",
"armorMystery201402Notes": "Reluciente y fuerte, esta túnica tiene muchos bolsillos para llevar cartas. No proporciona ningún beneficio. Artículo de suscriptor de febrero 2014.",
"armorMystery201403Text": "Armadura del Caminante del Bosque",
@@ -362,8 +364,12 @@
"armorMystery201511Notes": "Si tenemos en cuenta que esta armadura se ha tallado a partir de un tronco mágico directamente, resulta sorprendentemente cómoda. No aporta ningún beneficio. Artículo del suscriptor de septiembre del 2015.",
"armorMystery201512Text": "Armadura de fuego frío",
"armorMystery201512Notes": "¡Reúne a las heladas llamas del invierno! No aporta ningún beneficio. Artículo del suscriptor de diciembre del 2015.",
- "armorMystery201603Text": "Lucky Suit",
- "armorMystery201603Notes": "This suit is sewn from thousands of four-leafed clovers! Confers no benefit. March 2016 Subscriber Item.",
+ "armorMystery201603Text": "Traje de la suerte",
+ "armorMystery201603Notes": "¡Este traje está cosido de miles de tréboles de cuatro hojas! No otorga ningún beneficio. Artículo de Suscriptor de Marzo 2016.",
+ "armorMystery201604Text": "Armor o' Leaves",
+ "armorMystery201604Notes": "You, too, can be a small but fearsome leaf puff. Confers no benefit. April 2016 Subscriber Item.",
+ "armorMystery201605Text": "Marching Bard Uniform",
+ "armorMystery201605Notes": "Unlike the traditional bards who join adventuring parties, bards who join Habitican marching bands are known for grand parades, not dungeon raids. Confers no benefit. May 2016 Subscriber Item.",
"armorMystery301404Text": "Traje Steampunk",
"armorMystery301404Notes": "¡Sofisticado y elegante! No otorga ningún beneficio. Artículo de suscriptor de febrero 3015.",
"armorArmoireLunarArmorText": "Armadura lunar reconfortante",
@@ -392,8 +398,10 @@
"armorArmoireJesterCostumeNotes": "¡Tra-la-la! A pesar del aspecto de este disfraz, no eres un bufón. Incrementa la Inteligencia por <%= int %>. Armario Encantado: Conjunto de Bufón (Artículo 2 de 3).",
"armorArmoireMinerOverallsText": "Overoles de Minero",
"armorArmoireMinerOverallsNotes": "Pueden parecer usados, pero han sido hechizados para repeler la suciedad. Aumenta la Constitución <%= con %>. Armario encantado: Kit del Minero (Objeto 2 de 3).",
- "armorArmoireBasicArcherArmorText": "Basic Archer Armor",
- "armorArmoireBasicArcherArmorNotes": "This camouflaged vest lets you slip unnoticed through the forests. Increases Perception by <%= per %>. Enchanted Armoire: Basic Archer Set (Item 2 of 3).",
+ "armorArmoireBasicArcherArmorText": "Armadura Básica de Arquero",
+ "armorArmoireBasicArcherArmorNotes": "Este chaleco de camuflaje te permite pasar desapercibido en los bosques. Incrementa la Percepción por <%= per %>. Armario Encantado: Conjunto Básico de Arquero (Artículo 2 de 3).",
+ "armorArmoireGraduateRobeText": "Graduate Robe",
+ "armorArmoireGraduateRobeNotes": "Congratulations! This weighty robe hangs heavy with all the knowledge you have accrued. Increases Intelligence by <%= int %>. Enchanted Armoire: Graduate Set (Item 2 of 3).",
"headgear": "cubrecabeza",
"headBase0Text": "Sin casco",
"headBase0Notes": "Sin equipo de cabeza.",
@@ -523,14 +531,14 @@
"headSpecialWinter2016MageNotes": "Mantiene la nieve fuera de tus ojos mientras conjuras hechizos. Incrementa la Percepción por <%= per %>. Equipamiento de Edición Limitada de Invierno 2015-2016.",
"headSpecialWinter2016HealerText": "Yelmo con Alas de Hada",
"headSpecialWinter2016HealerNotes": "¡Estasalasbatentanrápidoqueniseven! Aumenta la Inteligencia en <%= int %>. Equipo de Invierno Edición Limitada 2015-2016.",
- "headSpecialSpring2016RogueText": "Good Doggy Mask",
- "headSpecialSpring2016RogueNotes": "Aww, what a cute puppy! Come here and let me pet your head. ...Hey, where did all my Gold go? Increases Perception by <%= per %>. Limited Edition 2016 Spring Gear.",
- "headSpecialSpring2016WarriorText": "Mouse Guard Helm",
- "headSpecialSpring2016WarriorNotes": "Never again shall you be bopped on the head! Let them try! Increases Strength by <%= str %>. Limited Edition 2016 Spring Gear.",
- "headSpecialSpring2016MageText": "Grand Malkin Hat",
- "headSpecialSpring2016MageNotes": "Apparel to set you above the mere alley-mages of the world. Increases Perception by <%= per %>. Limited Edition 2016 Spring Gear.",
- "headSpecialSpring2016HealerText": "Blossom Diadem",
- "headSpecialSpring2016HealerNotes": "It glints with the potential of new life ready to burst forth. Increases Intelligence by <%= int %>. Limited Edition 2016 Spring Gear.",
+ "headSpecialSpring2016RogueText": "Máscara del Perrito Bueno",
+ "headSpecialSpring2016RogueNotes": "Edition 2016 Spring Gear. Aww, ¡qué perrito más lindo! Ven aquí y déjame acariciarte... Ey, ¿Dónde está mi Oro? Incrementa la Percepción por <%= per %>. Equipamiento de Edición Limitada de Primavera 2016.",
+ "headSpecialSpring2016WarriorText": "Casco de Ratón Guardián",
+ "headSpecialSpring2016WarriorNotes": "¡Nunca más serás golpeado en la cabeza! ¡Deja que lo intenten! Incrementa la Fuerza por <%= str %>. Equipamiento de Edición Limitada de Primavera 2016.",
+ "headSpecialSpring2016MageText": "Gran Sombrero Felino",
+ "headSpecialSpring2016MageNotes": "Una vestimenta que te pone por encima de los meros magos callejeros del mundo. Incrementa la Percepción por <%= per %>. Equipamiento de Edición Limitada de Primavera 2016.",
+ "headSpecialSpring2016HealerText": "Diadema Floreciente",
+ "headSpecialSpring2016HealerNotes": "Destella con el potencial de una nueva vida, lista para brotar. Incrementa la Inteligencia por <%= int %>. Equipamiento de Edición Limitada de Primavera 2016.",
"headSpecialGaymerxText": "Casco de Guerrero de Arco Iris",
"headSpecialGaymerxNotes": "Con motivo de la celebración por la Conferencia GaymerX, ¡este casco especial está decorado con un radiante y colorido estampado arco iris! GaymerX es una convención de juegos que celebra a la gente LGBTQ y a los videojuegos, y está abierta a todo el público.",
"headMystery201402Text": "Casco alado",
@@ -563,8 +571,12 @@
"headMystery201601Notes": "¡No abandones tu determinación, valiente campeón! No otorga ningún beneficio. Artículo de Suscriptor de Enero 2016.",
"headMystery201602Text": "Capucha de Rompecorazones",
"headMystery201602Notes": "Esconde tu identidad de todos tus admiradores. No otorga ningún beneficio. Artículo de suscriptor de febrero de 2016.",
- "headMystery201603Text": "Lucky Hat",
- "headMystery201603Notes": "This top hat is a magical good-luck charm. Confers no benefit. March 2016 Subscriber Item.",
+ "headMystery201603Text": "Sombrero de Suerte",
+ "headMystery201603Notes": "Esta galera es un amuleto mágico para la buena suerte. No otorga ningún beneficio. Artículo de Suscriptor de Marzo 2016.",
+ "headMystery201604Text": "Crown o' Flowers",
+ "headMystery201604Notes": "These woven flowers make a surprisingly strong helm! Confers no benefit. April 2016 Subscriber Item.",
+ "headMystery201605Text": "Marching Bard Hat",
+ "headMystery201605Notes": "Seventy-six dragons led the big parade, with a hundred and ten gryphons close at hand! Confers no benefit. May 2016 Subscriber Item.",
"headMystery301404Text": "Sombrero de copa sofisticado",
"headMystery301404Notes": "¡Un sofisticado sombrero de copa solo para los más refinados caballeros! No otorga ningún beneficio. Artículo de Suscriptor de Enero del 3015",
"headMystery301405Text": "Sombrero de copa básico",
@@ -611,8 +623,10 @@
"headArmoireJesterCapNotes": "Los cascabeles de este gorro pueden distraer a tus oponentes, pero a ti sólo te ayudan a concentrarte. Incrementa la Percepción por <%= per %>. Armario Encantado: Conjunto de Bufón (Artículo 1 de 3).",
"headArmoireMinerHelmetText": "Casco de Minero",
"headArmoireMinerHelmetNotes": "Protege tu cabeza de las tareas que te están cayendo! Aumenta la Inteligencia <%= int %>. Armario Encantado: Kit del Minero (Objeto 1 de 3).",
- "headArmoireBasicArcherCapText": "Basic Archer Cap",
- "headArmoireBasicArcherCapNotes": "No archer would be complete without a jaunty cap! Increases Perception by <%= per %>. Enchanted Armoire: Basic Archer Set (Item 3 of 3).",
+ "headArmoireBasicArcherCapText": "Gorra Básica de Arquero",
+ "headArmoireBasicArcherCapNotes": "¡Ningún arquero estaría completo sin un distinguido gorro! Incrementa la Percepción por <%= per %>. Armario Encantado: Conjunto Básico de Arquero (Artículo 3 de 3).",
+ "headArmoireGraduateCapText": "Graduate Cap",
+ "headArmoireGraduateCapNotes": "Congratulations! Your deep thoughts have earned you this thinking cap. Increases Intelligence by <%= int %>. Enchanted Armoire: Graduate Set (Item 3 of 3).",
"offhand": "objeto para la mano del escudo",
"shieldBase0Text": "Sin equipamiento en la mano del escudo",
"shieldBase0Notes": "Sin escudo o arma secundaria.",
@@ -673,7 +687,7 @@
"shieldSpecialWinter2015HealerText": "Escudo reconfortante",
"shieldSpecialWinter2015HealerNotes": "Este escudo desvía el viento helado. Aumenta la Constitución en <%= con %>. Equipo de Invierno 2014-2015 Edición Limitada.",
"shieldSpecialSpring2015RogueText": "Sigilo Explosivo",
- "shieldSpecialSpring2015RogueNotes": "No dejes que el sonido te engañe - Estos explosivos dan un buen golpe. Aumenta la Fuerza en <%= str %>. Equipo de Primavera Edición Limitada 2015.",
+ "shieldSpecialSpring2015RogueNotes": "Don't let the sound fool you - these explosives pack a punch. Increases Strength by <%= str %>. Limited Edition 2015 Spring Gear.",
"shieldSpecialSpring2015WarriorText": "Plato Disco",
"shieldSpecialSpring2015WarriorNotes": "Lánzaselo a tus enemigos... o sostenlo en las manos, porque se llenará de rico pienso a la hora de cenar. Aumenta la constitución en <%= con %>. Equipo de Primavera 2015 Edición Limitada.",
"shieldSpecialSpring2015HealerText": "Almohada Decorada",
@@ -697,11 +711,11 @@
"shieldSpecialWinter2016HealerText": "Regalo de Hada",
"shieldSpecialWinter2016HealerNotes": "¡¡¡¡¡¡¡¡¡Ábrelo ábrelo ábrelo ábrelo ábrelo ábrelo!!!!!!!!! Incrementa la Constitución por <%= con %>. Equipamiento de Edición Limitada de Invierno 2015-2016.",
"shieldSpecialSpring2016RogueText": "Bolas de Fuego",
- "shieldSpecialSpring2016RogueNotes": "You've mastered the ball, the club, and the knife. Now you advance to juggling fire! Awoo! Increases Strength <%= str %>. Limited Edition 2016 Spring Gear.",
- "shieldSpecialSpring2016WarriorText": "Cheese Wheel",
- "shieldSpecialSpring2016WarriorNotes": "You braved fiendish traps to procure this defense-boosting food. Increases Constitution by <%= con %>. Limited Edition 2016 Spring Gear.",
- "shieldSpecialSpring2016HealerText": "Floral Buckler",
- "shieldSpecialSpring2016HealerNotes": "The April Fool claims this little shield will block Shiny Seeds. Don't believe him. Increases Constitution by <%= con %>. Limited Edition 2016 Spring Gear.",
+ "shieldSpecialSpring2016RogueNotes": "Has dominado el balón, el garrote y el cuchillo. ¡Ahora avanza y haz malabares con fuego! ¡Awoo! Incrementan la Fuerza por <%= str %>. Equipamiento de Edición Limitada de Primavera 2016.",
+ "shieldSpecialSpring2016WarriorText": "Rueda de queso",
+ "shieldSpecialSpring2016WarriorNotes": "Te enfrentaste a diabólicas trampas para conseguir esta comida que aumenta la defensa. Incrementa la Constitución por <%= con %>. Equipamiento de Edición Limitada de Primavera 2016.",
+ "shieldSpecialSpring2016HealerText": "Escudo Floral",
+ "shieldSpecialSpring2016HealerNotes": "El Santo Inocente afirma que este pequeño escudo bloqueará las Semillas Radiantes. No le creas. Incrementa la Constitución por <%= con %>. Equipamiento de Edición Limitada de Primavera 2016.",
"shieldMystery201601Text": "Destructora de Resoluciones",
"shieldMystery201601Notes": "Esta espada se puede usar para desviar a todas las distracciones. No otorga ningún beneficio. Artículo de Suscriptor de Enero 2016.",
"shieldMystery301405Text": "Escudo Reloj",
@@ -716,6 +730,8 @@
"shieldArmoireDragonTamerShieldNotes": "Distrae a tus enemigos con este escudo con forma de dragón. Incrementa la Percepción por <%= per %>. Armario Encantado: Conjunto de Domador de Dragones (Artículo 2 de 3).",
"shieldArmoireMysticLampText": "Lámpara Mística",
"shieldArmoireMysticLampNotes": "Ilumina las cuevas más oscuras con esta lámpara mística! Aumenta Percepción <%= per %>. Armario Encantado: Objeto Independiente.",
+ "shieldArmoireFloralBouquetText": "Bouquet o' Flowers",
+ "shieldArmoireFloralBouquetNotes": "Not much help in battle, but aren't they beautiful? Increases Constitution by <%= con %>. Enchanted Armoire: Independent Item.",
"back": "Accesorio en la Espalda",
"backBase0Text": "Sin Accesorio en la Espalda",
"backBase0Notes": "Sin Accesorio en la Espalda",
@@ -779,14 +795,14 @@
"headAccessorySpecialSpring2015MageNotes": "Estas orejas escuchan atentamente, en el caso de que algún mago esté revelando algún secreto. No confieren ningún beneficio. Equipamiento de Primavera 2015 Edición limitada",
"headAccessorySpecialSpring2015HealerText": "Orejas de Gatito Verdes",
"headAccessorySpecialSpring2015HealerNotes": "Estas adorables orejas harán que los demás se pongan verdes de envidia. No otorga ningún beneficio. Equipamiento de Verano Edición Limitada del 2015.",
- "headAccessorySpecialSpring2016RogueText": "Green Dog Ears",
- "headAccessorySpecialSpring2016RogueNotes": "With these, you can keep track of tricky Mages even if they turn invisible! Confers no benefit. Limited Edition 2016 Spring Gear.",
+ "headAccessorySpecialSpring2016RogueText": "Orejas de Perro Verdes",
+ "headAccessorySpecialSpring2016RogueNotes": "¡Con éstas podrás mantener a astutos magos en la mira aunque se vuelvan invisibles! No otorgan ningún beneficio. Equipamiento de Edición Limitada de Primavera 2016.",
"headAccessorySpecialSpring2016WarriorText": "Orejas de Ratón Rojas",
- "headAccessorySpecialSpring2016WarriorNotes": "To better hear your theme song across clamorous battlefields. Confers no benefit. Limited Edition 2016 Spring Gear.",
+ "headAccessorySpecialSpring2016WarriorNotes": "Para que puedas escuchar mejor tu banda sonora en los ruidosos campos de batalla. No confieren ningún beneficio. Equipamiento de Edición Limitada de Primavera 2016.",
"headAccessorySpecialSpring2016MageText": "Orejas de Gato Amarillas",
"headAccessorySpecialSpring2016MageNotes": "Estas puntiagudas orejas pueden detectar el insignificante zumbido del Mana ambiente, o las silenciosas pisadas de un Pícaro. No confiere ningún beneficio. Equipamiento Edición Limitada de Primavera 2016.",
"headAccessorySpecialSpring2016HealerText": "Orejas de Conejito Moradas",
- "headAccessorySpecialSpring2016HealerNotes": "They stand like flags above the fray, letting others know where to run for help. Confers no benefit. Limited Edition 2016 Spring Gear.",
+ "headAccessorySpecialSpring2016HealerNotes": "Altas como banderas en una batalla, permiten a otros ver dónde conseguir ayuda. No confieren ningún beneficio. Equipamiento de Edición Limitada de Primavera 2016.",
"headAccessoryBearEarsText": "Orejas de oso",
"headAccessoryBearEarsNotes": "¡Estas orejas te hacen parecer un oso valiente! No confiere ningún beneficio.",
"headAccessoryCactusEarsText": "Orejas de cactus",
@@ -815,11 +831,25 @@
"headAccessoryMystery201510Notes": "Estos cuernos aterradores son ligeramente babosos. No otorgan ningún beneficio. Artículo de Suscriptor de Octubre 2015.",
"headAccessoryMystery301405Text": "Gafas para la Cabeza",
"headAccessoryMystery301405Notes": "\"Las gafas son para los ojos\" dijeron, \"Nadie quiere gafas que solo se puedan llevar en la cabeza\" dijeron. ¡Ja! ¡Demuéstrales que eso no es así! No confiere ningún beneficio. Artículo de suscriptor de agosto de 3015.",
- "headAccessoryArmoireComicalArrowText": "Comical Arrow",
- "headAccessoryArmoireComicalArrowNotes": "This whimsical item doesn't provide a stat boost, but it sure is good for a laugh! Confers no benefit. Enchanted Armoire: Independent Item.",
+ "headAccessoryArmoireComicalArrowText": "Flecha Cómica",
+ "headAccessoryArmoireComicalArrowNotes": "Este extravagante objeto no provee ninguna mejora a tus estadísticas, ¡pero sí que te hace reír! No otorga ningún beneficio. Armario Encantado: Artículo Independiente.",
"eyewear": "Gafas",
"eyewearBase0Text": "Sin Gafas.",
"eyewearBase0Notes": "Sin Gafas.",
+ "eyewearSpecialBlackTopFrameText": "Black Standard Eyeglasses",
+ "eyewearSpecialBlackTopFrameNotes": "Glasses with a black frame above the lenses. Confers no benefit.",
+ "eyewearSpecialBlueTopFrameText": "Blue Standard Eyeglasses",
+ "eyewearSpecialBlueTopFrameNotes": "Glasses with a blue frame above the lenses. Confers no benefit.",
+ "eyewearSpecialGreenTopFrameText": "Green Standard Eyeglasses",
+ "eyewearSpecialGreenTopFrameNotes": "Glasses with a green frame above the lenses. Confers no benefit.",
+ "eyewearSpecialPinkTopFrameText": "Pink Standard Eyeglasses",
+ "eyewearSpecialPinkTopFrameNotes": "Glasses with a pink frame above the lenses. Confers no benefit.",
+ "eyewearSpecialRedTopFrameText": "Red Standard Eyeglasses",
+ "eyewearSpecialRedTopFrameNotes": "Glasses with a red frame above the lenses. Confers no benefit.",
+ "eyewearSpecialWhiteTopFrameText": "White Standard Eyeglasses",
+ "eyewearSpecialWhiteTopFrameNotes": "Glasses with a white frame above the lenses. Confers no benefit.",
+ "eyewearSpecialYellowTopFrameText": "Yellow Standard Eyeglasses",
+ "eyewearSpecialYellowTopFrameNotes": "Glasses with a yellow frame above the lenses. Confers no benefit.",
"eyewearSpecialSummerRogueText": "Parche Picaresco",
"eyewearSpecialSummerRogueNotes": "¡No hace falta ser un diablillo para apreciar lo distinguido que es! No otorga ningún beneficio. Equipo de Verano 2014 Edición Limitada",
"eyewearSpecialSummerWarriorText": "Parche Apuesto",
diff --git a/common/locales/es/generic.json b/common/locales/es/generic.json
index 67b8c43724..b3248b658e 100644
--- a/common/locales/es/generic.json
+++ b/common/locales/es/generic.json
@@ -1,14 +1,14 @@
{
"languageName": "Español",
- "stringNotFound": "Cadena de caracteres '<%= string %>' no se ha encontrado.",
+ "stringNotFound": "No se encontró la cadena '<%= string %>'.",
"titleIndex": "Habitica | El juego de rol de tu vida",
"habitica": "Habitica",
- "titleTasks": "Quehaceres",
+ "titleTasks": "Tareas",
"titleAvatar": "Avatar",
- "titleBackgrounds": "Ambientes",
- "titleStats": "Estadísticas y Logros",
+ "titleBackgrounds": "Fondos",
+ "titleStats": "Estadísticas y logros",
"titleProfile": "Perfil",
- "titleInbox": "Bandeja",
+ "titleInbox": "Bandeja de entrada",
"titleTavern": "Taberna",
"titleParty": "Grupo",
"titleHeroes": "Salón de los Héroes",
@@ -17,109 +17,109 @@
"titleChallenges": "Desafíos",
"titleDrops": "Mercado",
"titleQuests": "Misiones",
- "titlePets": "Mascotes",
+ "titlePets": "Mascotas",
"titleMounts": "Monturas",
"titleEquipment": "Equipo",
"titleTimeTravelers": "Viajeros del tiempo",
"titleSeasonalShop": "Tienda de temporada",
"titleSettings": "Configuración",
- "expandToolbar": "Abrir barra de herramientas",
- "collapseToolbar": "Cerrar barra de herramientas",
+ "expandToolbar": "Expandir barra de herramientas",
+ "collapseToolbar": "Contraer barra de herramientas",
"markdownBlurb": "Habitica emplea el formato Markdown en los mensajes. Para obtener más información, consulta el resumen del formato Markdown.",
"showFormattingHelp": "Mostrar ayuda de formato",
"hideFormattingHelp": "Ocultar ayuda de formato",
- "youType": "Escribes:",
+ "youType": "Si escribes:",
"youSee": "Ves:",
"italics": "*Cursiva*",
"bold": "**Negrita**",
"strikethrough": "~~Tachado~~",
- "emojiExample": ":sonrisa:",
+ "emojiExample": ":smile:",
"markdownLinkEx": "[¡Habitica es genial!](https://habitica.com)",
"markdownImageEx": "",
- "unorderedListHTML": "+ Primer objeto + Segundo objeto + Tercer objeto",
- "unorderedListMarkdown": "+ Primer objeto\n+ Segundo objeto\n+ Tercer objeto",
+ "unorderedListHTML": "+ Primer elemento + Segundo elemento + Tercer elemento",
+ "unorderedListMarkdown": "+ Primer elemento\n+ Segundo elemento\n+ Tercer elemento",
"code": "`código`",
"achievements": "Logros",
"modalAchievement": "¡Logro!",
"special": "Especial",
- "site": "Sitio",
+ "site": "Sitio web",
"help": "Ayuda",
"user": "Usuario",
"market": "Mercado",
- "subscriberItem": "Objeto Misterioso",
- "newSubscriberItem": "Nuevo Objeto Misterioso",
+ "subscriberItem": "Objeto misterioso",
+ "newSubscriberItem": "Nuevo objeto misterioso",
"subscriberItemText": "Cada mes, los suscriptores reciben un objeto misterioso, que se suele publicar una semana antes de cada fin de mes, aproximadamente. Para más información, consulta la página de la wiki sobre los objetos misteriosos.",
"all": "Todo",
"none": "Ninguno",
"or": "O",
"and": "y",
"loginSuccess": "Has iniciado sesión.",
- "youSure": "¿Estás seguro?",
+ "youSure": "¿Seguro?",
"submit": "Enviar",
"close": "Cerrar",
- "saveAndClose": "Guardar y Cerrar",
+ "saveAndClose": "Guardar y cerrar",
"cancel": "Cancelar",
- "ok": "OK",
+ "ok": "Aceptar",
"add": "Añadir",
"undo": "Deshacer",
"continue": "Continuar",
"accept": "Aceptar",
"reject": "Rechazar",
"neverMind": "No importa",
- "buyMoreGems": "Comprar más Gemas.",
- "notEnoughGems": "No hay suficientes gemas.",
- "alreadyHave": "Ups! Ya tienes este artículo. No necesitas comprarlo otra vez!",
+ "buyMoreGems": "Comprar más gemas",
+ "notEnoughGems": "No tienes suficientes gemas",
+ "alreadyHave": "Vaya, ya tienes este artículo. No necesitas comprarlo otra vez.",
"delete": "Eliminar",
"gemsPopoverTitle": "Gemas",
"gems": "Gemas",
- "gemButton": "Tienes <%= number %> Gemas.",
+ "gemButton": "Tienes <%= number %> gemas.",
"moreInfo": "Más información",
"showMoreMore": "(mostrar más)",
"showMoreLess": "(mostrar menos)",
- "gemsWhatFor": "¡Haz clic para comprar Gemas! Las Gemas te permiten comprar artículos especiales como Misiones, personalizaciones del avatar y equipo estacional.",
+ "gemsWhatFor": "¡Haz clic para comprar gemas! Las gemas te permiten comprar artículos especiales como misiones, personalizaciones del avatar y equipos de temporada.",
"veteran": "Veterano",
"veteranText": "Ha sobrevivido a Habit The Grey (nuestro sito web pre-Angular) y se ha ganado muchas cicatrices por sus fallos.",
"originalUser": "¡Usuario original!",
- "originalUserText": "Uno de los primeros usuarios... ¡digamos que fue un alpha tester!",
- "habitBirthday": "Festejo de Cumpleaños de Habitica",
- "habitBirthdayText": "Participó en el Festejo de Cumpleaños de Habitica",
- "habitBirthdayPluralText": "¡Participó en <%= number %> Festejo(s) de Cumpleaños de Habitica!",
+ "originalUserText": "Uno de los primerísimos usuarios... ¡Este sí que es un alpha tester!",
+ "habitBirthday": "Fiesta de cumpleaños de Habitica",
+ "habitBirthdayText": "Participó en la fiesta de compleaños de Habitica",
+ "habitBirthdayPluralText": "¡Participó en <%= number %> fiestas de cumpleaños de Habitica!",
"habiticaDay": "Bautizo de Habitica",
- "habiticaDaySingularText": "¡Celebró el Bautizo de Habitica! Gracias por ser un magnífico usuario.",
- "habiticaDayPluralText": "¡ Celebró <%= number %> Bautizos! Gracias por ser un magnífico usuario.",
+ "habiticaDaySingularText": "¡Celebró el bautizo de Habitica! Gracias por ser un usuario magnífico.",
+ "habiticaDayPluralText": "¡Celebró <%= number %> bautizos! Gracias por ser un usuario magnífico.",
"achievementDilatory": "Salvador de Dilatoria",
"achievementDilatoryText": "¡Ayudó a derrotar al Dread Drag'on de Dilatoria durante el evento Summer Splash del 2014!",
"costumeContest": "Participante disfrazado",
- "costumeContestText": "Ha participado en el Concurso de Disfraces de Habitoween. Puedes ver algunas de las candidaturas en el blog de Habitica.",
+ "costumeContestText": "Ha participado en el concurso de disfraces de Habitoween. Puedes ver algunas de las candidaturas en el blog de Habitica.",
"costumeContestTextPlural": "Ha participado en <%= number %> concursos de disfraces de Habitoween. Mira algunas de las candidaturas en el blog de Habitica.",
"memberSince": "- Miembro desde",
- "lastLoggedIn": "- Última conexión",
- "notPorted": "Esta función no se ha traido del sitio original",
- "buyThis": "¿Comprar este <%= text %> con <%= price %> de tus <%= gems %> Gemas?",
- "noReachServer": "Error en la conexión al servidor, intentalo más tarde.",
+ "lastLoggedIn": "- Última conexión el",
+ "notPorted": "Esta función aún no se ha traído del sitio original.",
+ "buyThis": "¿Quieres comprar este <%= text %> con <%= price %> de tus <%= gems %> gemas?",
+ "noReachServer": "No se puede acceder al servidor. Inténtalo de nuevo más tarde.",
"errorUpCase": "ERROR:",
"newPassSent": "Nueva contraseña enviada.",
- "serverUnreach": "Error en la conexión al servidor.",
+ "serverUnreach": "No se puede acceder al servidor.",
"requestError": "Vaya, se ha producido un error. Actualiza la página; puede que tu última acción no se haya guardado correctamente.",
- "seeConsole": "Si el eror persiste, por favor reportelo en Ayuda> Notificar un error. Si eres familiar con la consola de tu navegador, por favor incluye los mensajes de error.",
+ "seeConsole": "Si el error persiste, informa de él en Ayuda > Notificar un error. Si sabes cómo usar la consola del navegador, incluye los mensajes de error.",
"error": "Error",
"menu": "Menú",
"notifications": "Notificaciones",
"noNotifications": "No tienes mensajes nuevos.",
- "clear": "Cerrar",
- "endTour": "Terminar Tour",
+ "clear": "Borrar",
+ "endTour": "Terminar visita guiada",
"audioTheme": "Tema de audio",
- "audioTheme_off": "Apagar",
+ "audioTheme_off": "Desactivado",
"audioTheme_danielTheBard": "Daniel el Bardo",
"audioTheme_wattsTheme": "Tema Watts",
"audioTheme_gokulTheme": "Tema Gokul",
"audioTheme_luneFoxTheme": "Tema LuneFox",
- "askQuestion": "Hacer una Pregunta",
+ "askQuestion": "Hacer una pregunta",
"reportBug": "Notificar un error",
- "HabiticaWiki": "La Wiki de Habitica",
- "HabiticaWikiFrontPage": "http://habitica.wikia.com/wiki/Habitica_Wiki",
- "contributeToHRPG": "Contribuir con Habitica",
- "overview": "Resumen para Nuevos Usuarios",
+ "HabiticaWiki": "La wiki de Habitica",
+ "HabiticaWikiFrontPage": "http://es.habitica.wikia.com/wiki/Habitica_Wiki",
+ "contributeToHRPG": "Colaborar con Habitica",
+ "overview": "Introducción para nuevos usuarios",
"January": "Enero",
"February": "Febrero",
"March": "Marzo",
@@ -132,44 +132,44 @@
"October": "Octubre",
"November": "Noviembre",
"December": "Diciembre",
- "dateFormat": "Formato de Fecha",
+ "dateFormat": "Formato de fecha",
"achievementStressbeast": "Salvador de Stoïkalm",
"achievementStressbeastText": "¡Ayudó a derrotar a la Abominable Bestia del Estrés durante el evento Winter Wonderland de 2014!",
"achievementBurnout": "Salvador de los Campos Florecientes",
"achievementBurnoutText": "¡Ayudó a derrotar al Burnout y restaurar los Espíritus del Cansancio durante el evento Fall Festival de 2015!",
- "achievementBewilder": "Savior of Mistiflying",
- "achievementBewilderText": "Helped defeat the Be-Wilder during the 2016 Spring Fling Event!",
- "checkOutProgress": "¡Comprobar mi progreso en Habitica!",
+ "achievementBewilder": "Salvador de Calavuelos",
+ "achievementBewilderText": "¡Ayudó a derrotar al Apa-bullador durante el Evento de Primavera de 2016!",
+ "checkOutProgress": "Consultar mi progreso en Habitica",
"cardReceived": "¡Recibiste una tarjeta!",
"cardReceivedFrom": "<%= cardType %> de <%= userName %>",
- "greetingCard": "Tarjeta de Felicitación",
+ "greetingCard": "Tarjeta de saludo",
"greetingCardExplanation": "Ambos recibís el logro Alegres Amigotes",
- "greetingCardNotes": "Enviar una tarjeta de felicitación a un miembro del grupo.",
- "greeting0": "¡Hola, tú!",
+ "greetingCardNotes": "Enviar una tarjeta para mandar un saludo a un miembro del grupo.",
+ "greeting0": "¡Hola!",
"greeting1": "Solo quería saludar :)",
"greeting2": "(saluda con emoción)",
"greeting3": "¡Ey!",
"greetingCardAchievementTitle": "Alegre Amigote",
- "greetingCardAchievementText": "¡Felicidades! Envió o recibió <%= cards %> tarjetas de felicitación.",
- "thankyouCard": "Tarjeta de Agradecimiento",
- "thankyouCardExplanation": "Los dos recibiréis el logro Agradablemente Agradecido",
+ "greetingCardAchievementText": "¿Qué tal? Envió o recibió <%= cards %> tarjetas de saludo.",
+ "thankyouCard": "Tarjeta de agradecimiento",
+ "thankyouCardExplanation": "Los dos recibís el logro Agradablemente Agradecido",
"thankyouCardNotes": "Envía una tarjeta de agradecimiento a un miembro de tu grupo.",
"thankyou0": "¡Muchas gracias!",
"thankyou1": "¡Gracias, gracias, gracias!",
"thankyou2": "¡Un millón de gracias!",
- "thankyou3": "Estoy muy agradecido - ¡gracias!",
+ "thankyou3": "Estoy muy agradecido: ¡gracias!",
"thankyouCardAchievementTitle": "Agradablemente Agradecido",
"thankyouCardAchievementText": "¡Gracias por las gracias! Envió o recibió <%= cards %> tarjetas de agradecimiento.",
"birthdayCard": "Tarjeta de cumpleaños",
"birthdayCardExplanation": "Ambos recibís el logro Prosperidad Cumpleañera",
- "birthdayCardNotes": "Enviar una tarjeta de cumpleaños a un miembro del grupo.",
+ "birthdayCardNotes": "Envía una tarjeta de cumpleaños a un miembro del grupo.",
"birthday0": "¡Feliz cumpleaños!",
"birthdayCardAchievementTitle": "Prosperidad Cumpleañera",
"birthdayCardAchievementText": "¡Que cumplas muchos más! Ha enviado o recibido <%= cards %> felicitaciones de cumpleaños.",
"streakAchievement": "¡Has obtenido un logro de racha!",
"firstStreakAchievement": "Racha de 21 días",
"streakAchievementCount": "<%= streaks %> rachas de 21 días",
- "twentyOneDays": "¡Has completado tus Tareas Diarias durante 21 días seguidos!",
+ "twentyOneDays": "¡Has completado tus tareas diarias durante 21 días seguidos!",
"dontBreakStreak": "Excelente trabajo. ¡No estropees la racha!",
"dontStop": "¡No te detengas ahora!",
"levelUpShare": "¡Subí de nivel en Habitica al mejorar en mis hábitos en la vida real!",
diff --git a/common/locales/es/groups.json b/common/locales/es/groups.json
index ec48b1265e..5a9b518c7a 100644
--- a/common/locales/es/groups.json
+++ b/common/locales/es/groups.json
@@ -92,6 +92,7 @@
"send": "Enviar",
"messageSentAlert": "Mensaje enviado",
"pmHeading": "Mensaje privado a <%= name %>",
+ "pmsMarkedRead": "Your private messages have been marked as read",
"clearAll": "Eliminar todos los mensajes",
"confirmDeleteAllMessages": "¿Estás seguro que quieres eliminar todos los mensajes de tu bandeja de entrada? Otros usuarios seguirán viendo los mensajes que les mandaste.",
"optOutPopover": "¿No te gusta mensajes privados? Haz click para optar completamente fuera",
@@ -99,6 +100,15 @@
"unblock": "Desbloquear",
"pm-reply": "Enviar una respuesta",
"inbox": "Bandeja de entrada",
+ "messageRequired": "A message is required.",
+ "toUserIDRequired": "A User ID is required",
+ "gemAmountRequired": "A number of gems is required",
+ "notAuthorizedToSendMessageToThisUser": "Can't send message to this user.",
+ "privateMessageGiftIntro": "Hello <%= receiverName %>, <%= senderName %> has sent you",
+ "privateMessageGiftGemsMessage": "<%= gemAmount %> gems!",
+ "privateMessageGiftSubscriptionMessage": "<%= numberOfMonths %> months of subscription!",
+ "cannotSendGemsToYourself": "Cannot send gems to yourself. Try a subscription instead.",
+ "badAmountOfGemsToSend": "Amount must be within 1 and your current number of gems.",
"abuseFlag": "Notificación de violación de las normas de la comunidad",
"abuseFlagModalHeading": "¿Notificar a <%= name %> por infracción?",
"abuseFlagModalBody": "Estás seguro/a de que quieres denunciar este mensaje? SOLO deberías denunciar un mensaje que viole las <%= firstLinkStart %> Normas de la Comunidad <%= linkEnd %> y/o los <%= secondLinkStart %> Términos de Servicio <%= linkEnd %>. Denunciar inapropiadamente un mensaje es una violación de las Normas de la Comunidad y puede provocarte una infracción. Razones apropiadas para señalar un mensaje incluyen pero no están limitadas a:
insultar, juramentos religiosos
fanatismo, farfullar
temas adultos
violencia, incluso en bromas
spam, mensajes sin sentido
.",
@@ -151,5 +161,29 @@
"partyUpName": "¡Ese grupo!",
"partyOnName": "¡Toma grupo!",
"partyUpAchievement": "Se unió a un grupo con otra persona. Pasadlo bien luchando contra monstruos y apoyándoos mutuamente.",
- "partyOnAchievement": "Se unió a un grupo con cuatro personas o más. Disfrutad de lo responsables que sois entre todos y venced a vuestros enemigos con la ayuda de vuestros amigos."
+ "partyOnAchievement": "Se unió a un grupo con cuatro personas o más. Disfrutad de lo responsables que sois entre todos y venced a vuestros enemigos con la ayuda de vuestros amigos.",
+ "groupIdRequired": "\"groupId\" must be a valid UUID",
+ "groupNotFound": "Group not found.",
+ "groupTypesRequired": "You must supply a valid \"type\" query string.",
+ "questLeaderCannotLeaveGroup": "You cannot leave your party when you have started a quest. Abort the quest first.",
+ "cannotLeaveWhileActiveQuest": "You cannot leave party during an active quest. Please leave the quest first.",
+ "onlyLeaderCanRemoveMember": "Only group leader can remove a member!",
+ "memberCannotRemoveYourself": "You cannot remove yourself!",
+ "groupMemberNotFound": "User not found among group's members",
+ "mustBeGroupMember": "Must be member of the group.",
+ "keepOrRemoveAll": "req.query.keep must be either \"keep-all\" or \"remove-all\"",
+ "keepOrRemove": "req.query.keep must be either \"keep\" or \"remove\"",
+ "canOnlyInviteEmailUuid": "Can only invite using uuids or emails.",
+ "inviteMissingEmail": "Missing email address in invite.",
+ "partyMustbePrivate": "Parties must be private",
+ "userAlreadyInGroup": "User already in that group.",
+ "userAlreadyInvitedToGroup": "User already invited to that group.",
+ "userAlreadyPendingInvitation": "User already pending invitation.",
+ "userAlreadyInAParty": "User already in a party.",
+ "userWithIDNotFound": "User with id \"<%= userId %>\" not found.",
+ "userHasNoLocalRegistration": "User does not have a local registration (username, email, password).",
+ "uuidsMustBeAnArray": "User ID invites must be an array.",
+ "emailsMustBeAnArray": "Email address invites must be an array.",
+ "canOnlyInviteMaxInvites": "You can only invite \"<%= maxInvites %>\" at a time",
+ "onlyCreatorOrAdminCanDeleteChat": "Not authorized to delete this message!"
}
\ No newline at end of file
diff --git a/common/locales/es/limited.json b/common/locales/es/limited.json
index 40d473d0f4..85611b5873 100644
--- a/common/locales/es/limited.json
+++ b/common/locales/es/limited.json
@@ -5,7 +5,7 @@
"annoyingFriends": "Amigos Molestos",
"annoyingFriendsText": "Tus compañeros te han lanzado <%= snowballs %> bolas de nieve.",
"alarmingFriends": "Amigos Alarmantes",
- "alarmingFriendsText": "Tus compañeros the han asustado <%= spookDust %> veces.",
+ "alarmingFriendsText": "Tus compañeros de grupo te han asustado <%= spookySparkles %> veces.",
"agriculturalFriends": "Amigos Agrícolas",
"agriculturalFriendsText": "Tus compañeros te han transformado en flor <%= seeds %> veces.",
"aquaticFriends": "Amigos Acuáticos",
@@ -72,5 +72,6 @@
"comfortingKittySet": "Gatito Reconfortante (Sanador)",
"sneakySqueakerSet": "Chirriador Escurridizo (Pícaro)",
"fallEventAvailability": "Disponible hasta el 31 de octubre",
- "winterEventAvailability": "Disponible hasta el 31 de diciembre"
+ "winterEventAvailability": "Disponible hasta el 31 de diciembre",
+ "springEventAvailability": "Disponible hasta el 31 de mayo."
}
\ No newline at end of file
diff --git a/common/locales/es/maintenance.json b/common/locales/es/maintenance.json
new file mode 100644
index 0000000000..4511823e9f
--- /dev/null
+++ b/common/locales/es/maintenance.json
@@ -0,0 +1,34 @@
+{
+ "habiticaBackSoon": "No te preocupes, Habitica estará de vuelta muy pronto.",
+ "importantMaintenance": "Estamos realizando tareas de mantenimiento importantes y calculamos que durarán hasta el <%= localDate %> en tu zona horaria.",
+ "maintenance": "Mantenimiento",
+ "maintenanceMoreInfo": "¿Quieres ver más detalles sobre el mantenimiento? <%= linkStart %>Consulta nuestra página de información<%= linkEnd %>.",
+ "noDamageKeepStreaks": "NO sufrirás daños ni se interrumpirán tus rachas.",
+ "thanksForPatience": "¡Gracias por tu paciencia!",
+ "twitterMaintenanceUpdates": "Para ver las últimas novedades, consulta nuestro Twitter, donde iremos publicando información sobre el estado de estos cambios.",
+ "veteranPetAward": "Cuando terminemos, ¡recibirás una mascota de veterano!",
+
+ "maintenanceInfoTitle": "Información sobre próximas tareas de mantenimiento en Habitica",
+ "maintenanceInfoWhat": "¿Qué va a ocurrir?",
+ "maintenanceInfoWhatText": "El 21 de mayo, Habitica estará fuera de servicio por mantenimiento la mayor parte del día. No sufrirás ningún daño ni nada perjudicará a tu cuenta durante ese fin de semana, aunque no puedas iniciar sesión para tachar tus tareas diarias a tiempo. Nosotros estaremos trabajando mucho para que esta interrupción dure lo menos posible e iremos publicando los avances en nuestra cuenta de Twitter. Cuando finalice este proceso, todos recibirán una mascota excepcional como gesto de agradecimiento por su paciencia.",
+ "maintenanceInfoWhy": "¿Por qué ocurre esto?",
+ "maintenanceInfoWhyText": "Hace varios meses que estamos realizando profundas mejoras en Habitica entre bambalinas. En concreto, hemos reescrito la API. Aunque tal vez por fuera no parezca muy distinto, por dentro es todo completamente nuevo. Esto nos permitirá tener MUCHÍSIMA más flexibilidad para añadir funciones de ahora en adelante, además de mejorar el rendimiento.",
+ "maintenanceInfoTechDetails": "¿Quieres saber más sobre la parte técnica de este proceso? Visita The Forge, nuestro blog de desarrolladores.",
+ "maintenanceInfoMore": "Más información",
+ "maintenanceInfoAccountChanges": "¿Qué cambios veré en mi cuenta una vez que haya terminado la reescritura?",
+ "maintenanceInfoAccountChangesText": "Al principio, no habrá cambios importantes aparte de las mejoras en el rendimiento de algunas funciones, como los desafíos. Si observas algún cambio que no debería haber aparecido, escríbenos a admin@habitica.com y nos encargaremos de investigarlo.",
+ "maintenanceInfoAddFeatures": "¿Qué tipo de funciones se podrán añadir a Habitica después de esto?",
+ "maintenanceInfoAddFeaturesText": "Al completar la reescritura, podremos empezar a crear un chat y unos gremios mejorados, planes para organizaciones y familias, y funciones de productividad adicionales, como tareas mensuales y la posibilidad de registrar lo que hiciste el día anterior. Todas estas funciones son bastante complejas, de modo que llevará tiempo desarrollarlas, pero sin tener terminada esta reescritura, es imposible empezar con ello.",
+ "maintenanceInfoHowLong": "¿Cuánto durará el mantenimiento?",
+ "maintenanceInfoHowLongText": "Tenemos que migrar las tareas y los datos de los 1,3 millones de usuarios de Habitica, lo cual no es precisamente sencillo. Calculamos que esto tendrá lugar, más o menos, entre las 22 h y las 7 h, hora de España peninsular (de 20 h a 5 h UTC). Por supuesto, haremos todo lo que esté en nuestras manos por terminar lo antes posible. Puedes seguir los avances en nuestro Twitter.",
+ "maintenanceInfoStatsAffected": "¿Cómo afectará esto a mis tareas diarias, rachas, mejoras y misiones?",
+ "maintenanceInfoStatsAffectedText1": "NO sufrirás daños ni se interrumpirá ninguna racha ese fin de semana y, por lo demás, el día se restablecerá como de costumbre. Las tareas diarias que marcaste se desmarcarán, las mejoras se restablecerán, etc. Si estás participando en una misión de recopilar objetos, seguirás encontrándolos. Si te encuentras en una batalla contra un jefe, seguirás infligiéndole daño, pero él no te atacará. (¡Hasta los monstruos necesitan un descanso de vez en cuando!)",
+ "maintenanceInfoStatsAffectedText2": "Después de darle muchas vueltas, llegamos a la conclusión de que esta era la forma más justa de actuar ante el hecho de que muchos usuarios no podrán marcar sus tareas diarias con normalidad durante las tareas de mantenimiento. Sentimos las molestias que esto pueda causar.",
+ "maintenanceInfoSeeTasks": "¿Y si necesito ver mi lista de tareas?",
+ "maintenanceInfoSeeTasksText": "Si sabes que vas a necesitar tu lista de tareas el sábado para recordar lo que tienes que hacer, te recomendamos que, antes de que comience el mantenimiento, hagas una captura de pantalla de tus tareas para poder consultarla luego.",
+ "maintenanceInfoRarePet": "¿Qué tipo de mascota excepcional recibiré?",
+ "maintenanceInfoRarePetText": "Como agradecimiento por la paciencia durante este proceso, todos recibirán una mascota excepcional de veterano. Si es la primera vez que recibes una mascota de veterano, te tocará un lobo veterano. Si ya lo tienes, obtendrás un tigre veterano. Y si ya has conseguido los dos, ¡recibirás una mascota de veterano que aún no ha visto la luz! Una vez finalizada la migración, es posible que la mascota tarde unas horas en aparecer, pero no temas, que todo el mundo recibirá la suya.",
+ "maintenanceInfoWho": "¿Quién ha colaborado en este gigantesco proyecto?",
+ "maintenanceInfoWhoText": "Nos alegra que te hagas esa pregunta. El cabecilla de todo esto es nuestro increíble colaborador paglias, con la inmensa ayuda de Blade, TheHollidayInn, SabreCat, Victor Pudeyev, TheUnknown y Alys.",
+ "maintenanceInfoTesting": "Además, un grupo de magníficos voluntarios de código abierto ha probado diligentemente la nueva versión. ¡Gracias! Sin ellos, no habría sido posible."
+}
diff --git a/common/locales/es/messages.json b/common/locales/es/messages.json
index 5774aaf4e1..f3676b9118 100644
--- a/common/locales/es/messages.json
+++ b/common/locales/es/messages.json
@@ -10,7 +10,7 @@
"messageAlreadyMount": "Ya tienes esa montura. Intenta alimentar a otra mascota.",
"messageEvolve": "Has dominado a <%= egg %>, ¡vamos a dar una vuelta!",
"messageLikesFood": "¡A <%= egg %> le encanta <%= foodText %>!",
- "messageDontEnjoyFood": "<%= egg %> come <%= foodText %> pero no parece disfrutarlo.",
+ "messageDontEnjoyFood": "Tu <%= egg %> come <%= foodText %>, pero no parece que le guste.",
"messageBought": "Has comprado <%= itemText %>",
"messageEquipped": "<%= itemText %> equipado.",
"messageUnEquipped": "Te has quitado <%= itemText %>.",
diff --git a/common/locales/es/npc.json b/common/locales/es/npc.json
index b8b2481a26..cd1804d290 100644
--- a/common/locales/es/npc.json
+++ b/common/locales/es/npc.json
@@ -14,39 +14,59 @@
"displayItemForGold": "¿Quieres vender tu <%= itemType %>?",
"displayEggForGold": "¿Quieres vender un huevo <%= itemType %>?",
"displayPotionForGold": "¿Quieres vender una poción <%= itemType %>?",
- "sellForGold": "Véndelo por <%= gold %> de oro",
+ "sellForGold": "Venderlo por <%= gold %> de oro",
"buyGems": "Comprar Gemas",
"purchaseGems": "Comprar gemas",
"justin": "Justin",
"ian": "Ian",
"ianText": "¡Bienvenido a la Tienda de Misiones! Aquí puedes usar los Pergaminos de Misión para combatir monstruos con tus amigos. ¡Echa un vistazo al magnífico catálogo de Pergaminos de Misión que puedes encontrar a la derecha!",
"ianBrokenText": "Te damos la bienvenida a la Tienda de Misiones. Aquí podrás usar tus Pergaminos de misión para combatir monstruos con tus amigos. Asegúrate de echar un vistazo a nuestra gran colección de Pergaminos de misión a la venta.",
+ "missingKeyParam": "\"req.params.key\" is required.",
+ "itemNotFound": "Item \"<%= key %>\" not found.",
+ "cannotBuyItem": "You can't buy this item.",
+ "missingTypeKeyEquip": "\"key\" and \"type\" are required parameters.",
+ "missingPetFoodFeed": "\"pet\" and \"food\" are required parameters.",
+ "invalidPetName": "Invalid pet name supplied.",
+ "missingEggHatchingPotionHatch": "\"egg\" and \"hatchingPotion\" are required parameters.",
+ "invalidTypeEquip": "\"type\" must be one of 'equipped', 'pet', 'mount', 'costume'.",
+ "mustPurchaseToSet": "Must purchase <%= val %> to set it on <%= key %>.",
+ "typeRequired": "Type is required",
+ "keyRequired": "Key is required",
+ "notAccteptedType": "Type must be in [eggs, hatchingPotions, food, quests, gear]",
+ "contentKeyNotFound": "Key not found for Content <%= type %>",
+ "plusOneGem": "+1 Gem",
+ "typeNotSellable": "Type is not sellable. Must be one of the following <%= acceptedTypes %>",
+ "userItemsKeyNotFound": "Key not found for user.items <%= type %>",
+ "pathRequired": "Path string is required",
+ "unlocked": "Items have been unlocked",
+ "alreadyUnlocked": "Full set already unlocked.",
+ "alreadyUnlockedPart": "Full set already partially unlocked.",
"USD": "(USD)",
- "newStuff": "Cosas Nuevas",
- "cool": "Ya me lo dirás",
- "dismissAlert": "Ignora Esta Alerta",
+ "newStuff": "Cosas nuevas",
+ "cool": "Posponer",
+ "dismissAlert": "Descartar este aviso",
"donateText1": "Añade 20 Gemas a tu cuenta. Las Gemas se usan para comprar objetos especiales en el juego, como camisetas y peinados.",
"donateText2": "Ayuda a Habitica",
"donateText3": "Habitica es un proyecto de código abierto que depende del soporte de sus usuarios. El dinero que gastes en gemas nos ayuda a mantener activos los servidores, mantener al pequeño grupo de personal, desarrollar nuevas caracteristicas y proveer incentivos para nuestros programadores voluntarios. ¡Gracias por tu generosidad!",
- "donationDesc": "20 Gemas, Donación a Habitica",
+ "donationDesc": "20 gemas, donación a Habitica",
"payWithCard": "Pagar con una tarjeta de crédito",
"payNote": "Nota: Paypal a veces toma mucho tiempo autorizando. Recomendamos pagar con tarjeta de crédito.",
"card": "Tarjeta de crédito (a través de Stripe)",
- "amazonInstructions": "Haz click en el botón para pagar con Amazon Payments",
+ "amazonInstructions": "Haz clic en el botón para pagar con Amazon Payments",
"paymentMethods": "Comprar con",
"classGear": "Equipo de clase",
"classGearText": "Antes que nada, no te preocupes. El equipamiento que tenías está en tu inventario y ahora llevas el equipo de aprendiz de tu nueva clase. Si llevas objetos pertenecientes a tu clase, obtendrás una bonificación del 50% en tus estadísticas. Pero, si quieres, puedes volver a usar el equipamiento anterior sin ningún problema.",
"classStats": "Éstas son las estadísticas de tu clase; afectan al gameplay. Cada vez que subas de nivel obtendrás un punto para asignar a una estadística en concreto. Mueve el puntero sobre cada estadística para más información.",
- "autoAllocate": "Asignación Automática",
+ "autoAllocate": "Asignación automática",
"autoAllocateText": "Si 'distribución automática' está seleccionado, tu avatar gana estadísticas automáticamente basado en los atributos de sus tareas, que puedes encontrar en TAREA > Editar > Avanzado > Atributos Por ejemplo, si haces ejercicio con frecuencia, y si tu Diaria de 'Ejercicio' está fijado en 'Físico', vas a ganar Fuerza automáticamente.",
"spells": "Hechizos",
"spellsText": "Ahora podrás desbloquear hechizos especificos para tu clase. El primero se desbloquea a nivel 11. Tu maná se rellena 10 puntos al día, mas 1 punto por cada completado",
- "toDo": "Tarea Pendiente",
- "moreClass": "Para mas información sobre el sistema de clase, mira en",
+ "toDo": "Tarea pendiente",
+ "moreClass": "Para ver más información sobre el sistema de clases, consulta",
"tourWelcome": "¡Bienvenido a Habitica! Esta es tu lista de Tareas Pendientes. ¡Tacha alguna tarea realizada para continuar!",
"tourExp": "¡Buen trabajo! ¡Tachar tareas te da Experiencia y Oro!",
"tourDailies": "Esta columna es la de las tareas diarias. Para continuar, añade una tarea que debas hacer todos los días. Ejemplos de tareas diarias: hacer la cama, usar hilo dental o consultar el correo del trabajo",
- "tourCron": "¡Espléndido! Tus Tareas Diarias se restaurarán cada día.",
+ "tourCron": "¡Espléndido! Tus tareas diarias se restaurarán cada día.",
"tourHP": "¡Atención! Si no completas tus Tareas Diarias antes de medianoche, ¡te dañarán!",
"tourHabits": "¡Esta columna es para los buenos y malos hábitos que haces varias veces al día! Para continuar haz click al lápiz para editar los nombres, después haz click en el tick para guardarlo.",
"tourStats": "¡Los Buenos Hábitos dan Experiencia y Oro! Los Malos Hábitos quitan vida.",
@@ -64,11 +84,12 @@
"tourPetsPage": "This is the Stable! After level 4, you can hatch pets using eggs and potions. When you hatch a pet in the Market, it will appear here! Click a pet's image to add it to your avatar. Feed them with the food you find after level 4, and they'll grow into powerful mounts.",
"tourMountsPage": "Once you've fed a pet enough food to turn it into a mount, it will appear here. (Pets, mounts, and food are available after level 4.) Click a mount to saddle up!",
"tourEquipmentPage": "¡Aquí es donde guardamos tu Equipo! Tu Equipo de Batalla afecta a tus atributos. Si quieres mostrar un Equipo diferente en tu avatar sin cambiar tus atributos, marca \"Llevar disfraz\".",
+ "equipmentAlreadyOwned": "You already own that piece of equipment",
"tourOkay": "¡Muy bien!",
"tourAwesome": "¡Estupendo!",
"tourSplendid": "¡Esplendido!",
"tourNifty": "¡Ingenioso!",
- "tourAvatarProceed": "¡Muestrame mis tareas!",
+ "tourAvatarProceed": "¡Muéstrame mis tareas!",
"tourToDosBrief": "Lista de Tareas Pendientes
¡Marca las Tareas Pendientes para ganar Oro y Experiencia!
Las Tareas Pendientes nunca hacen que tu avatar pierda Salud.
",
"tourDailiesBrief": "Tareas Diarias
Las Tareas Diarias se repiten cada día.
Pierdes Salud si te saltas las Tareas Diarias.
",
"tourDailiesProceed": "¡Tendré cuidado!",
diff --git a/common/locales/es/pets.json b/common/locales/es/pets.json
index dfa62ceefa..6777aa57b9 100644
--- a/common/locales/es/pets.json
+++ b/common/locales/es/pets.json
@@ -12,6 +12,7 @@
"etherealLion": "León Etéreo",
"veteranWolf": "Lobo Veterano",
"veteranTiger": "Tigre veterano",
+ "veteranLion": "León veterano",
"cerberusPup": "Cachorro de Cerbero",
"hydra": "Hidra",
"mantisShrimp": "Mantis marina",
@@ -19,7 +20,7 @@
"orca": "Orca",
"royalPurpleGryphon": "Grifo real morado",
"phoenix": "Fénix",
- "bumblebee": "Bumblebee",
+ "magicalBee": "Abeja mágica",
"rarePetPop1": "¡Haz clic en la pata dorada para saber cómo puedes conseguir esta mascota contribuyendo con Habitica!",
"rarePetPop2": "¡Como Obtener Esta Mascota!",
"potion": "Poción <%= potionType %>",
@@ -62,6 +63,7 @@
"hatchedPet": "¡Ha nacido un/a <%= egg %> <%= potion %>!",
"displayNow": "Llevar ahora",
"displayLater": "Llevar más adelante",
+ "petNotOwned": "No eres dueño de esta mascota.",
"earnedCompanion": "Tu productividad te ha granjeado un nuevo acompañante. Aliméntalo y crecerá.",
"feedPet": "¿Dar de comer <%= article %><%= text %>a su <%= name %>?",
"useSaddle": "¿Ensillar <%= pet %>?",
@@ -83,5 +85,8 @@
"petKeyBoth": "Soltar ambas",
"confirmPetKey": "¿Está seguro?",
"petKeyNeverMind": "Aún no",
+ "petsReleased": "Mascotas liberadas.",
+ "mountsAndPetsReleased": "Monturas y mascotas liberadas.",
+ "mountsReleased": "Monturas liberadas.",
"gemsEach": "gemas cada uno"
}
\ No newline at end of file
diff --git a/common/locales/es/quests.json b/common/locales/es/quests.json
index f8452787ab..f555a731cb 100644
--- a/common/locales/es/quests.json
+++ b/common/locales/es/quests.json
@@ -38,7 +38,7 @@
"bossDmg2": "Solo los participantes pelearan contra el jefe y compartiran el botín de la misión.",
"bossDmg1Broken": "Todas las tareas diarias y pendientes, así como los hábitos positivos, hacen daño al jefe. Puedes hacerle más daño cuanto más roja es la tarea, y utilizando Golpe brutal o Estallido de llamas. El jefe causará daños a cada participante de la misión por cada tarea diaria que no hayáis cumplido (multiplicadas por la fuerza del jefe) además del daño que recibís habitualmente: para que todos sigáis sanos, cumplid las tareas diarias. Todos los daños causados y sufridos por un jefe se aplican en el cron (el cambio de día del usuario).",
"bossDmg2Broken": "Solo los participantes pelearán contra el jefe y compartirán el botín de la misión...",
- "tavernBossInfo": "Complete Dailies and To-Dos and score positive Habits to damage the World Boss! Incomplete Dailies fill the Rage Bar. When the Rage bar is full, the World Boss will attack an NPC. A World Boss will never damage individual players or accounts in any way. Only active accounts not resting in the Inn will have their tasks tallied.",
+ "tavernBossInfo": "Completa tus tareas diarias y marca hábitos positivos para herir al Jefe de Mundo. Cuando la barra de Rabia se llene, el Jefe de Mundo atacará a un PNJ. Un Jefe de Mundo nunca herirá a jugadores individuales o a las cuentas. Sólo se les computarán las tareas a las cuentas activas que no estén descansando en la Taberna.",
"tavernBossInfoBroken": "Completa tus tareas diarias y pendientes, y cumple tus hábitos positivos para herir al jefe mundial. Las tareas diarias que no se cumplan aumentarán la Barra de golpes de cansancio. Cuando esa barra se llene, el jefe mundial atacará a un PNJ. Los jefes mundiales nunca provocan ningún daño a jugadores sueltos. Solamente cuentan las tareas de los usuarios activos que no estén descansando en la Taberna.",
"bossColl1": "Para conseguir objetos de misión, haz tus tareas. Los objetos de misión aparecen como los demás; sin embargo, no los verás hasta el día siguiente, en ese momento todo lo que hayais encontrado se pondrá en común.",
"bossColl2": "Solo los participantes pueden coger objetos y su parte del botín de la misión.",
@@ -78,5 +78,24 @@
"whichQuestStart": "¿Qué misión quieres emprender?",
"getMoreQuests": "Conseguir más misiones",
"unlockedAQuest": "¡Has desbloqueado una misión!",
- "leveledUpReceivedQuest": "¡Has subido a Nivel <%= level %> y has recibido un pergamino de misión!"
+ "leveledUpReceivedQuest": "¡Has subido a Nivel <%= level %> y has recibido un pergamino de misión!",
+ "questInvitationDoesNotExist": "No se ha enviado invitación de misión todavía.",
+ "questInviteNotFound": "Invitación a misión no encontrada.",
+ "guildQuestsNotSupported": "Los gremios no pueden ser invitados a las misiones.",
+ "questNotFound": "Misión \"<%= key %>\" no encontrada.",
+ "questNotOwned": "No posees ese pergamino de misión.",
+ "questNotGoldPurchasable": "La misión \"<%= key %>\" no es una misión por oro.",
+ "questLevelTooHigh": "Debes ser nivel <%= level %> para empezar esta misión",
+ "questAlreadyUnderway": "Tu grupo ya está en una misión. Inténtalo de nuevo cuando la misión haya terminado.",
+ "questAlreadyAccepted": "Ya has aceptado la invitación de misión",
+ "noActiveQuestToLeave": "No hay misión activa de la que salir.",
+ "questLeaderCannotLeaveQuest": "El líder de la misión no puede salir de la misión.",
+ "notPartOfQuest": "No formas parte de la misión.",
+ "noActiveQuestToAbort": "No hay misión activa que abortar.",
+ "onlyLeaderAbortQuest": "Sólo el líder de grupo o misión puede abortar una misión.",
+ "questAlreadyRejected": "Ya has declinado la invitación de misión.",
+ "cantCancelActiveQuest": "No puedes cancelar una misión activa, usa la función de abortar.",
+ "onlyLeaderCancelQuest": "Sólo el líder de grupo o misión puede cancelar la misión.",
+ "questNotPending": "No hay misión que empezar.",
+ "questOrGroupLeaderOnlyStartQuest": "Sólo el líder de grupo o misión puede forzar empezar la misión."
}
\ No newline at end of file
diff --git a/common/locales/es/questscontent.json b/common/locales/es/questscontent.json
index 2efa073a8d..37789c944a 100644
--- a/common/locales/es/questscontent.json
+++ b/common/locales/es/questscontent.json
@@ -74,30 +74,30 @@
"questVice3DropDragonEgg": "Dragón (Huevo)",
"questVice3DropShadeHatchingPotion": "Poción de eclosión sombría.",
"questMoonstone1Text": "La Cadena de Piedra Lunar, Parte 1: La Cadena de Piedra Lunar",
- "questMoonstone1Notes": "A terrible affliction has struck Habiticans. Bad Habits thought long-dead are rising back up with a vengeance. Dishes lie unwashed, textbooks linger unread, and procrastination runs rampant!
You track some of your own returning Bad Habits to the Swamps of Stagnation and discover the culprit: the ghostly Necromancer, Recidivate. You rush in, weapons swinging, but they slide through her specter uselessly.
\"Don’t bother,\" she hisses with a dry rasp. \"Without a chain of moonstones, nothing can harm me – and master jeweler @aurakami scattered all the moonstones across Habitica long ago!\" Panting, you retreat... but you know what you must do.",
+ "questMoonstone1Notes": "Una terrible aflicción ha golpeado a los Habiticanos. Malos Hábitos que se creían muertos hace tiempo se han levantado de nuevo en venganza. Los platos se encuentran sin lavar, los libros de texto permanecen sin leer, ¡y la procrastinación corre sin nadie que la detenga!
Sigues el rastro de algunos de tus propios Malos Hábitos a las Ciénagas del Estancamiento y descubres a la culpable: la fantasmal Necromante, Reincidencia. Te lanzas a atacarla, pero tus armas atraviesan su cuerpo espectral inútilmente.
\"No te molestes,\" susurra con un tono áspero y seco. \"Sin una cadena de piedras lunares, nada puede hacerme daño – ¡y el maestro joyero @aurakami dispersó todas las piedras lunares a través de Habitica hace mucho tiempo!\" Jadeante, te retiras... pero sabes qué es lo que debes hacer.",
"questMoonstone1CollectMoonstone": "Piedras Lunares",
"questMoonstone1DropMoonstone2Quest": "La Cadena de la Piedra Lunar Parte 2: Haz Retroceder al Nigromante (Pergamino)",
"questMoonstone2Text": "La Cadena de la Piedra Lunar Parte 2: Haz Retroceder al Nigromante",
- "questMoonstone2Notes": "The brave weaponsmith @Inventrix helps you fashion the enchanted moonstones into a chain. You’re ready to confront Recidivate at last, but as you enter the Swamps of Stagnation, a terrible chill sweeps over you.
Rotting breath whispers in your ear. \"Back again? How delightful...\" You spin and lunge, and under the light of the moonstone chain, your weapon strikes solid flesh. \"You may have bound me to the world once more,\" Recidivate snarls, \"but now it is time for you to leave it!\"",
+ "questMoonstone2Notes": "El valiente armero @Inventrix te ayuda a dar forma a las piedras lunares encantadas hasta hacerlas una cadena. Estás listo para confrontar finalmente a Reincidencia, pero en cuanto entras a las Ciénagas del Estancamiento, te recorre un terrible escalofrío.
Un soplo hediondo susurra en tu oído. \"¿Has regresado? Qué deleite...\" Giras y atacas, y bajo la luz de la cadena de piedra lunar, tu arma golpea carne sólida. \"Tal vez me hayas atado al mundo una vez más,\" gruñe Reincidencia, \"¡pero ahora es tiempo de que termines!\"",
"questMoonstone2Boss": "El nigromante",
"questMoonstone2DropMoonstone3Quest": "La Cadena de la Piedra Lunar Parte 3: Retraso Trasformado (Pergamino)",
"questMoonstone3Text": "La Cadena de Piedra Lunar, Parte 3: Reincidencia Transformada",
- "questMoonstone3Notes": "Recidivate crumples to the ground, and you strike at her with the moonstone chain. To your horror, Recidivate seizes the gems, eyes burning with triumph.
\"Foolish creature of flesh!\" she shouts. \"These moonstones will restore me to a physical form, true, but not as you imagined. As the full moon waxes from the dark, so too does my power flourish, and from the shadows I summon the specter of your most feared foe!\"
A sickly green fog rises from the swamp, and Recidivate’s body writhes and contorts into a shape that fills you with dread – the undead body of Vice, horribly reborn.",
- "questMoonstone3Completion": "Your breath comes hard and sweat stings your eyes as the undead Wyrm collapses. The remains of Recidivate dissipate into a thin grey mist that clears quickly under the onslaught of a refreshing breeze, and you hear the distant, rallying cries of Habiticans defeating their Bad Habits for once and for all.
@Baconsaur the beast master swoops down on a gryphon. \"I saw the end of your battle from the sky, and I was greatly moved. Please, take this enchanted tunic – your bravery speaks of a noble heart, and I believe you were meant to have it.\"",
+ "questMoonstone3Notes": "Reincidencia se desploma al suelo, y la golpeas con tu cadena de piedra lunar. Para tu horror, Reincidencia se apodera de las gemas, sus ojos ardiendo triunfantes.
\"¡Tonta criatura de carne!\" grita. \"Estas piedras lunares me restaurarán a mi forma física, es cierto, pero no como tú imaginaste. A medida que la luna crece en la oscuridad, también crecen mis poderes, ¡y de las sombras convoco al espectro de tu más temido enemigo!\"
Una enfermiza neblina verde se levanta de la ciénaga, y el cuerpo de Reincidencia se retuerce y se contorsiona en una forma que te llena de terror – el cuerpo no-muerto de Vicio, horriblemente renacido.",
+ "questMoonstone3Completion": "Respiras difícilmente y el sudor hace que ardan tus ojos mentras el Guivre colapsa. Los restos de Reincidencia se desvanecen formando una fina bruma gris que desaparece rápidamente bajo la ráfaga de una refrescante brisa, y escuchas en la distancia los gritos de multitudes de Habiticanos derrotando a sus Malos Hábitos de una vez por todas.
@Baconsaur, el maestro de las bestias, se abalanza montado en un grifo. \"Vi el final de tu batalla desde el cielo, y fue increíblemente conmovedora. Por favor, toma esta túnica encantada – tu valentía habla de un noble corazón, y creo que estabas destinado a tenerla.\"",
"questMoonstone3Boss": "Necro-Vicio",
"questMoonstone3DropRottenMeat": "Carne podrida (Comida)",
"questMoonstone3DropZombiePotion": "Poción de eclosión Zombie",
"questGoldenknight1Text": "El Caballero Dorado, Parte 1: Una Conversación con Stern.",
- "questGoldenknight1Notes": "The Golden Knight has been getting on poor Habiticans' cases. Didn't do all of your Dailies? Checked off a negative Habit? She will use this as a reason to harass you about how you should follow her example. She is the shining example of a perfect Habitican, and you are naught but a failure. Well, that is not nice at all! Everyone makes mistakes. They should not have to be met with such negativity for it. Perhaps it is time you gather some testimonies from hurt Habiticans and give the Golden Knight a stern talking-to!",
+ "questGoldenknight1Notes": "La Dama de Oro ha estado molestando a los pobres Habiticanos. ¿No cumpliste todas tus Diarias? ¿Marcaste un Hábito negativo? Ella lo usará como razón para acosarte y decir que tienes que seguir su ejemplo. Ella es el ejemplo brillante de un Habiticano perfecto y tú no eres más que un fracaso. Bueno, ¡eso no es para nada agradable! Todos cometen errores, y no deberían ser tratados con tanta negatividad por ello. ¡Tal vez es hora de reunir unos cuantos testimonios de Habiticanos ofendidos y darle a la Dama de Oro una severa reprimenda!",
"questGoldenknight1CollectTestimony": "Testimonios",
"questGoldenknight1DropGoldenknight2Quest": "La Cadena de la Dama de Oro Parte 2: Dama de Oro (Pergamino)",
"questGoldenknight2Text": "El Caballero Dorado, Parte 2: Caballero Dorado",
- "questGoldenknight2Notes": "Armed with hundreds of Habitican's testimonies, you finally confront the Golden Knight. You begin to recite the Habitcan's complaints to her, one by one. \"And @Pfeffernusse says that your constant bragging-\" The knight raises her hand to silence you and scoffs, \"Please, these people are merely jealous of my success. Instead of complaining, they should simply work as hard as I! Perhaps I shall show you the power you can attain through diligence such as mine!\" She raises her morningstar and prepares to attack you!",
+ "questGoldenknight2Notes": "Armado con cientos de testimonios de Habiticanos, enfrentas finalmente a la Dama de Oro. Empiezas a recitar las quejas de los Habiticanos, una por una. \"Y @Pfeffernusse dice que tus constantes fanfarronadas-\" Ella alza su mano para silenciarte y se burla, \"Por favor, estas personas simplemente están celosas de mi éxito. En lugar de quejarse, ¡deberían trabajar tan duro como yo! ¡Quizás pueda mostrarte el poder que puedes obtener mediante una diligencia como la mía!\" Entonces levanta su lucero del alba, ¡y se prepara para atacarte!",
"questGoldenknight2Boss": "Caballero de Oro",
"questGoldenknight2DropGoldenknight3Quest": "La Cadena del Caballero Dorado Parte 3: El Caballero de Hierro (Pergamino)",
"questGoldenknight3Text": "El Caballero Dorado, Parte 3: El Caballero de Hierro",
- "questGoldenknight3Notes": "@Jon Arinbjorn cries out to you to get your attention. In the aftermath of your battle, a new figure has appeared. A knight coated in stained-black iron slowly approaches you with sword in hand. The Golden Knight shouts to the figure, \"Father, no!\" but the knight shows no signs of stopping. She turns to you and says, \"I am sorry. I have been a fool, with a head too big to see how cruel I have been. But my father is crueler than I could ever be. If he isn't stopped he'll destroy us all. Here, use my morningstar and halt the Iron Knight!\"",
- "questGoldenknight3Completion": "With a satisfying clang, the Iron Knight falls to his knees and slumps over. \"You are quite strong,\" he pants. \"I have been humbled, today.\" The Golden Knight approaches you and says, \"Thank you. I believe we have gained some humility from our encounter with you. I will speak with my father and explain the complaints against us. Perhaps, we should begin apologizing to the other Habiticans.\" She mulls over in thought before turning back to you. \"Here: as our gift to you, I want you to keep my morningstar. It is yours now.\"",
+ "questGoldenknight3Notes": "@Jon Arinbjorn grita para llamar tu atención. En los momentos siguientes a tu batalla, una nueva figura ha aparecido. Un caballero revestido de hierro teñido de negro se aproxima a ti lentamente con su espada en mano. La Dama de Oro vocifera hacia la figura \"¡Padre, no!\" pero el caballero no parece querer detenerse. Ella se vuelve hacia ti y dice \"Lo siento. He sido una tonta, con un ego demasiado grande para ver lo cruel que he sido. Pero mi padre es aún más cruel de lo que yo jamás podría ser. Si nadie lo detiene nos destruirá a todos. ¡Toma, usa mi lucero del alba y termina con el Caballero de Hierro!\" ",
+ "questGoldenknight3Completion": "Con un satisfactorio sonido metálico, el Caballero de Hierro cae de rodillas y se desploma. \"Eres bastante fuerte\", jadea. \"Hoy me han humillado\". La Dama de Oro se acerca a ti y dice: \"Gracias. Creo que hemos ganado algo de humildad al enfrentarnos contigo. Hablaré con mi padre y le explicaré las quejas sobre nosotros. Quizás deberíamos empezar por disculparnos ante los otros Habiticanos\". Se detiene a pensar por un momento antes de volverse nuevamente hacia ti. \"Ten: como obsequio, quiero que te quedes con mi lucero del alba. Es tuyo ahora.\"",
"questGoldenknight3Boss": "El Caballero de Hierro",
"questGoldenknight3DropHoney": "Miel (Comida)",
"questGoldenknight3DropGoldenPotion": "Poción de Eclosión Dorado",
@@ -112,7 +112,7 @@
"questEggHuntCollectPlainEgg": "Huevos lisos",
"questEggHuntDropPlainEgg": "Huevo liso",
"questDilatoryText": "El Aterrador Drag'on de la Dilación",
- "questDilatoryNotes": "We should have heeded the warnings.
Dark shining eyes. Ancient scales. Massive jaws, and flashing teeth. We've awoken something horrifying from the crevasse: the Dread Drag'on of Dilatory! Screaming Habiticans fled in all directions when it reared out of the sea, its terrifyingly long neck extending hundreds of feet out of the water as it shattered windows with its searing roar.
\"This must be what dragged Dilatory down!\" yells Lemoness. \"It wasn't the weight of the neglected tasks - the Dark Red Dailies just attracted its attention!\"
\"It's surging with magical energy!\" @Baconsaur cries. \"To have lived this long, it must be able to heal itself! How can we defeat it?\"
Why, the same way we defeat all beasts - with productivity! Quickly, Habitica, band together and strike through your tasks, and all of us will battle this monster together. (There's no need to abandon previous quests - we believe in your ability to double-strike!) It won't attack us individually, but the more Dailies we skip, the closer we get to triggering its Neglect Strike - and I don't like the way it's eyeing the Tavern....",
+ "questDilatoryNotes": "Debimos haberle hecho caso a las advertencias.
Ojos brillantes oscuros. Escamas prehistóricas. Una mandíbula enorme, y dientes centelleantes. Hemos despertado algo horripilante de la brecha: ¡el Temido Drag'on de Dilatoria! Los Habiticanos huyeron en todas direcciones gritando cuando se alzó del mar, extendiendo su cuello terriblemente largo cientos de metros fuera del agua mientras destruía ventanas con un rugido abrasador.
\"¡Debe ser esto lo que hundió a Dilatoria!\" grita Lemoness. \"No fue el peso de las tareas descuidadas - ¡las Diarias de color rojo oscuro sólo atrajeron su atención!\"
\"¡Se está llenando de energía mágica!\" @Baconsaur grita. \"Si vivió durante tanto tiempo, ¡seguramente puede sanarse! ¿Cómo vamos a vencerlo?\"
Bueno, de la misma manera que acabamos con todas las bestias - ¡con la productividad! Rápido, Habitica, únanse y ataquen a través de sus tareas, todos batallaremos este monstruo juntos. (No tienes que abandonar tus misiones previas - ¡creemos en tu habilidad de atacar al doble!) Él no nos atacará individualmente, pero mientras más Diarias omitimos, más nos acercamos a su Ataque de Negligencia - y no me gusta la manera en que está mirando la Taberna...",
"questDilatoryBoss": "El Aterrador Drag'on de la Dilación",
"questDilatoryBossRageTitle": "Ataque de Negligencia",
"questDilatoryBossRageDescription": "Cuando esta barra se complete, el Aterrador Drag'on de la Dilación desatará un inmenso castigo sobre las tierras de Habítica",
@@ -142,7 +142,7 @@
"questAtom3Boss": "El Lavadomante",
"questAtom3DropPotion": "Poción de Eclosión Básica",
"questOwlText": "La Lechuza",
- "questOwlNotes": "The Tavern light is lit 'til dawn Until one eve the glow is gone! How can we see for our all-nighters? @Twitching cries, \"I need some fighters! See that Night-Owl, starry foe? Fight with haste and do not slow! We'll drive its shadow from our door, And make the night shine bright once more!\"",
+ "questOwlNotes": "La luz de la Taberna queda encendida hasta el amanecer ¡Hasta que una tarde su brillo comienza a desaparecer! ¿Cómo podremos ver en la noche oscura? @Twitching grita, \"¡Necesito guerreros que luchen con premura! ¿Ven a ese Búho Nocturno por las estrellas iluminado? ¡Peleen con valentía hasta que sea derrotado! A su sombra de nuestra puerta alejaremos, ¡y una vez más la noche brillar haremos!\"",
"questOwlCompletion": "Junto al amanecer la Lechuza desaparece, \nMas tan solo un bostezo en tu cara acontece. \n¿Quizás sea la hora de un descanso bien merecido? \nPero al llegar a tu cama, ¡vislumbras un nido! \nUna Lechuza bien conoce cómo ser atenta \nPara acabar la tarea y mantenerte alerta, \nPero tus nuevas mascotas piarán suavemente \nY así recordarte cuando en tu catre habrás de meterte.",
"questOwlBoss": "La Lechuza",
"questOwlDropOwlEgg": "Búho (Huevo)",
@@ -292,21 +292,33 @@
"questMonkeyBoss": "Mandril Monstruoso",
"questMonkeyDropMonkeyEgg": "Mono (Huevo)",
"questMonkeyUnlockText": "Desbloquea la compra de huevos de mono en el Mercado",
- "questSnailText": "The Snail of Drudgery Sludge",
- "questSnailNotes": "You're excited to begin questing in the abandoned Dungeons of Drudgery, but as soon as you enter, you feel the ground under your feet start to suck at your boots. You look up to the path ahead and see Habiticans mired in slime. @Overomega yells, \"They have too many unimportant tasks and dailies, and they're getting stuck on things that don't matter! Pull them out!\"
\"You need to find the source of the ooze,\" @Pfeffernusse agrees, \"or the tasks that they cannot accomplish will drag them down forever!\"
Pulling out your weapon, you wade through the gooey mud.... and encounter the fearsome Snail of Drudgery Sludge.",
- "questSnailCompletion": "You bring your weapon down on the great Snail's shell, cracking it in two, releasing a flood of water. The slime is washed away, and the Habiticans around you rejoice. \"Look!\" says @Misceo. \"There's a small group of snail eggs in the remnants of the muck.\"",
- "questSnailBoss": "Snail of Drudgery Sludge",
+ "questSnailText": "El Caracol del Cieno de Rutinaria",
+ "questSnailNotes": "Estás ansioso por empezar una misión en los Calabozos abandonados de Rutinaria, pero apenas entras, sientes que el suelo debajo tuyo comienza a succionar tus botas. Levantas la vista y ves a Habiticanos atrapados en lodo. @Overomega grita, \"¡Tienen demasiadas tareas y diarias insignificantes, y se están atascando en cosas que no importan! ¡Ayúdalos a salir!\"
\"Tienes que encontrar la fuente del lodo,\" coincide @Pfeffernusse, \"¡o las tareas que no puedan completar los hundirán por siempre!\"
Con tu arma en mano, vadeas a través del barro pegajoso... y te encuentras con el temible Caracol del Cieno de Rutinaria.",
+ "questSnailCompletion": "Descargas un golpe sobre la concha del gran Caracol, partiéndola en dos, soltando un montón de agua. El cieno es arrastrado y lavado por la corriente, y los Habiticanos alrededor tuyo se regocijan. \"¡Miren!\" dice @Misceo. \"Hay un pequeño conjunto de huevos de caracol en los restos del lodo.\"",
+ "questSnailBoss": " Caracol del Cieno de Rutinaria",
"questSnailDropSnailEgg": "Caracol (Huevo)",
"questSnailUnlockText": "Desbloquea la compra de huevos de Caracol en el Mercado",
- "questBewilderText": "The Be-Wilder",
- "questBewilderNotes": "The party begins like any other.
The appetizers are excellent, the music is swinging, and even the dancing elephants have become routine. Habiticans laugh and frolic amid the overflowing floral centerpieces, happy to have a distraction from their least-favorite tasks, and the April Fool whirls among them, eagerly providing an amusing trick here and a witty twist there.
As the Mistiflying clock tower strikes midnight, the April Fool leaps onto the stage to give a speech.
“Friends! Enemies! Tolerant acquaintances! Lend me your ears.” The crowd chuckles as animal ears sprout from their heads, and they pose with their new accessories.
“As you know,” the Fool continues, “my confusing illusions usually only last a single day. But I’m pleased to announce that I’ve discovered a shortcut that will guarantee us non-stop fun, without having to deal with the pesky weight of our responsibilities. Charming Habiticans, meet my magical new friend... the Be-Wilder!”
Lemoness pales suddenly, dropping her hors d'oeuvres. “Wait! Don’t trust--”
But suddenly mists are pouring into the room, glittering and thick, and they swirl around the April Fool, coalescing into cloudy feathers and a stretching neck. The crowd is speechless as an monstrous bird unfolds before them, its wings shimmering with illusions. It lets out a horrible screeching laugh.
“Oh, it has been ages since a Habitican has been foolish enough to summon me! How wonderful it feels, to have a tangible form at last.”
Buzzing in terror, the magic bees of Mistiflying flee the floating city, which sags from the sky. One by one, the brilliant spring flowers wither up and wisp away.
“My dearest friends, why so alarmed?” crows the Be-Wilder, beating its wings. “There’s no need to toil for your rewards any more. I’ll just give you all the things that you desire!”
A rain of coins pours from the sky, hammering into the ground with brutal force, and the crowd screams and flees for cover. “Is this a joke?” Baconsaur shouts, as the gold smashes through windows and shatters roof shingles.
PainterProphet ducks as lightning bolts crackle overhead, and fog blots out the sun. “No! This time, I don’t think it is!”
Quickly, Habiticans, don’t let this World Boss distract us from our goals! Stay focused on the tasks that you need to complete so we can rescue Mistiflying -- and hopefully, ourselves.",
- "questBewilderCompletion": "The Be-Wilder is DEFEATED!
We've done it! The Be-Wilder lets out a ululating cry as it twists in the air, shedding feathers like falling rain. Slowly, gradually, it coils into a cloud of sparkling mist. As the newly-revealed sun pierces the fog, it burns away, revealing the coughing, mercifully human forms of Bailey, Matt, Alex.... and the April Fool himself.
Mistiflying is saved!
The April Fool has enough shame to look a bit sheepish. “Oh, hm,” he says. “Perhaps I got a little…. carried away.”
The crowd mutters. Sodden flowers wash up on sidewalks. Somewhere in the distance, a roof collapses with a spectacular splash.
“Er, yes,” the April Fool says. “That is. What I meant to say was, I’m dreadfully sorry.” He heaves a sigh. “I suppose it can’t all be fun and games, after all. It might not hurt to focus occasionally. Maybe I’ll get a head start on next year’s pranking.”
Redphoenix coughs meaningfully.
“I mean, get a head start on this year’s spring cleaning!” the April Fool says. “Nothing to fear, I’ll have Habit City in spit-shape soon. Luckily nobody is better than I at dual-wielding mops.”
Encouraged, the marching band starts up.
It isn’t long before all is back to normal in Habit City. Plus, now that the Be-Wilder has evaporated, the magical bees of Mistiflying bustle back to work, and soon the flowers are blooming and the city is floating once more.
As Habiticans cuddle the magical fuzzy bees, the April Fool’s eyes light up. “Oho, I’ve had a thought! Why don’t you all keep some of these fuzzy Bee Pets and Mounts? It’s a gift that perfectly symbolizes the balance between hard work and sweet rewards, if I’m going to get all boring and allegorical on you.” He winks. “Besides, they don’t have stingers! Fool’s honor.”",
- "questBewilderCompletionChat": "`The Be-Wilder is DEFEATED!`\n\nWe've done it! The Be-Wilder lets out a ululating cry as it twists in the air, shedding feathers like falling rain. Slowly, gradually, it coils into a cloud of sparkling mist. As the newly-revealed sun pierces the fog, it burns away, revealing the coughing, mercifully human forms of Bailey, Matt, Alex.... and the April Fool himself.\n\n`Mistiflying is saved!`\n\nThe April Fool has enough shame to look a bit sheepish. “Oh, hm,” he says. “Perhaps I got a little…. carried away.”\n\nThe crowd mutters. Sodden flowers wash up on sidewalks. Somewhere in the distance, a roof collapses with a spectacular splash.\n\n“Er, yes,” the April Fool says. “That is. What I meant to say was, I’m dreadfully sorry.” He heaves a sigh. “I suppose it can’t all be fun and games, after all. It might not hurt to focus occasionally. Maybe I’ll get a head start on next year’s pranking.”\n\nRedphoenix coughs meaningfully.\n\n“I mean, get a head start on this year’s spring cleaning!” the April Fool says. “Nothing to fear, I’ll have Habit City in spit-shape soon. Luckily nobody is better than I at dual-wielding mops.”\n\nEncouraged, the marching band starts up.\n\nIt isn’t long before all is back to normal in Habit City. Plus, now that the Be-Wilder has evaporated, the magical bees of Mistiflying bustle back to work, and soon the flowers are blooming and the city is floating once more.\n\nAs Habiticans cuddle the magical fuzzy bees, the April Fool’s eyes light up. “Oho, I’ve had a thought! Why don’t you all keep some of these fuzzy Bee Pets and Mounts? It’s a gift that perfectly symbolizes the balance between hard work and sweet rewards, if I’m going to get all boring and allegorical on you.” He winks. “Besides, they don’t have stingers! Fool’s honor.”",
- "questBewilderBossRageTitle": "Beguilement Strike",
- "questBewilderBossRageDescription": "When this gauge fills, The Be-Wilder will unleash its Beguilement Strike on Habitica!",
- "questBewilderDropBumblebeePet": "Bumblebee (Pet)",
- "questBewilderDropBumblebeeMount": "Bumblebee (Mount)",
- "questBewilderBossRageMarket": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nOh no! Despite our best efforts, we've gotten distracted by the Be-Wilder’s charming illusions and have forgotten to do some of our Dailies! With a cackling cry, the shining bird beats its wings, raising a swarm of mist around Alex the Merchant. When the fog clears, he has been possessed! “Have some free samples!” he shouts gleefully, and begins to hurl exploding eggs and potions at fleeing Habiticans. Not the most favorable of sales, to be sure.\n\nHurry! Let's stay focused on our Dailies to defeat this monster before it possesses someone else.",
- "questBewilderBossRageStables": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nAhh!!! Once again the Be-Wilder has dazzled us into neglecting our Dailies, and now it has attacked Matt the Beast Master! With a swirl of mist, Matt transforms into a terrifying winged creature, and all the pets and mounts howl sadly in their stables. Quickly, stay focused on your tasks to defeat this dastardly distraction!",
- "questBewilderBossRageBailey": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nLook out! In the middle of reporting the news, Bailey the Town Crier has been possessed by the Be-Wilder! She lets out an evil, uninformative screech as she rises into the air. Now how will we know what’s going on?\n\nDon't give up... we're so close to defeating this bothersome bird for once and for all!"
+ "questBewilderText": "El Obnubilave",
+ "questBewilderNotes": "La fiesta comienza como cualquier otra.
Los aperitivos son excelentes, la música es jovial, e incluso los elefantes danzantes se han vuelto normales. Los Habiticanos ríen y se divierten en medio de los centros de mesa florales desbordantes, felices de poder distraerse lejos de sus tareas más desagradables, y el Santo Inocente da vueltas entre ellos, mostrando con entusiasmo un truco entretenido por aquí y un ingenioso giro por allá.
Al tiempo que el reloj de la torre de Desconcertaire da la medianoche, el Santo Inocente se sube de un salto al escenario para dar un discurso.
\"¡Amigos! ¡Enemigos! ¡Conocidos tolerantes! Escuchen con atención.\" La multitud se ríe por lo bajo mientras orejas de animales brotan de sus cabezas, y todos posan con sus nuevos accesorios.
\"Como saben,\" el Inocente continúa, \"mis ilusiones confusas generalmente duran sólo un día. Pero estoy feliz de anunciar que he descubierto un atajo que nos garantizará diversión sin fin, sin tener que lidiar con el molesto peso de nuestras responsabilidades. Encantadores Habiticanos, conozcan a mi nuevo y mágico amigo... ¡el Obnubilave!\"
Lemoness palidece repentinamente, dejando caer sus aperitivos. \"¡Esperen! No confíen en--\"
Pero de forma súbita una neblina se vierte en la sala, brillante y densa, y se arremolina alrededor del Santo Inocente, fusionándose en forma de plumas nubosas y un cuello estirado. La multitud se queda sin palabras mientras una monstruosa ave aparece frente a ella, sus alas centelleando llenas de ilusiones. El ave deja escapar una horrible risa chirriante.
\"¡Ah, han pasado siglos desde que un Habiticano fue lo suficientemente tonto como para convocarme! Qué fabuloso se siente tener al fin una forma tangible.\"
Zumbando aterrorizadas, las abejas mágicas de Desconcertaire huyen de la ciudad flotante, que se alza en el cielo. Una por una, las radiantes flores primaverales se marchitan y desaparecen en una voluta de humo.
\"Mis queridos amigos, ¿por qué están tan asustados?\" cacarea el Obnubilave, agitando sus alas. \"Ya no hay necesidad de trabajar duro para conseguir recompensas. ¡Yo les daré todas las cosas que desean!\"
Una lluvia de monedas cae del cielo, golpeando el suelo con una fuerza brutal, y la muchedumbre grita y corre a buscar refugio. \"¿Esto es una broma?\" aúlla Baconsaur, mientras el oro rompe ventanas y destroza tejados.
PainterProphet se agacha al mismo tiempo que rayos restallan por encima suyo y una niebla bloquea el sol. \"¡No! ¡Esta vez, no creo que lo sea!\"
Rápido, Habiticanos, ¡no dejemos que este Jefe Global nos distraiga de nuestros objetivos! Manténganse enfocados en las tareas que deben completar para que podamos rescatar a Desconcertaire -- y, con suerte, a nosotros mismos. ",
+ "questBewilderCompletion": "¡El Obnubilave ha sido DERROTADO!
¡Lo logramos! El Obnubilave deja escapar un aullido mientras se retuerce en el aire, perdiendo plumas que caen como gotas de lluvia. Lenta y gradualmente, se enrolla formando una nube de niebla centelleante. Mientras el sol recién despejado atraviesa la niebla, ésta es consumida por el calor, revelando las formas afortunadamente humanas de Bailey, Matt, Alex... y del mismísimo Santo Inocente.
¡Desconcertaire ha sido salvada!
El Santo Inocente tiene el suficiente remordimiento como para mostrarse avergonzado. \"Ah, hm,\" dice. \"Tal vez... me dejé llevar un poco.\"
La multitud murmura. Flores empapadas son arrastradas a las aceras. A la distancia, en alguna parte, un techo se derrumba con un ruido espectacular.
\"Eh, sí,\" dice el Santo Inocente. \"Lo que quise decir es que lo siento terriblemente.\" Deja salir un suspiro. \"Supongo que no todo en la vida puede ser diversión. No me haría daño enfocarme de vez en cuando. Quizás me adelante para la broma del año que viene.\"
Redphoenix tose exageradamente.
\"Quiero decir, ¡quizás me adelante para la limpieza general!\" dice el Santo Inocente. \"No hay nada que temer, pronto dejaré a Ciudad Hábito como nueva. Por suerte nadie me supera en el manejo de dos trapeadores al mismo tiempo.\"
Animada, la banda de música comienza a tocar.
No pasa mucho tiempo hasta que todo vuelve a la normalidad en Ciudad Hábito. Además, ahora que el Obnubilave se ha evaporado, las abejas mágicas de Desconcertaire vuelven a trabajar con un ritmo ajetreado, y pronto las flores brotan y la ciudad flota una vez más.
Mientras los Habiticanos abrazan a las peludas abejas mágicas, los ojos del Santo Inocente se iluminan. \"¡Ajá, se me ha ocurrido algo! ¿Por qué no se quedan con algunas de estas peluditas abejas como mascotas y monturas? Es un regalo que simboliza perfectamente el equilibrio entre el trabajo duro y las dulces recompensas, si me voy a poner aburrido y alegórico.\" El Inocente guiña. \"Además, ¡no tienen aguijones! Palabra de Inocente.\"",
+ "questBewilderCompletionChat": "`¡El Obnubilave ha sido DERROTADO!`\n\n¡Lo logramos! El Obnubilave deja escapar un aullido mientras se retuerce en el aire, perdiendo plumas que caen como gotas de lluvia. Lenta y gradualmente, se enrolla formando una nube de niebla centelleante. Mientras el sol recién despejado atraviesa la niebla, ésta es consumida por el calor, revelando las formas afortunadamente humanas de Bailey, Matt, Alex... y del mismísimo Santo Inocente.\n\n`¡Desconcertaire ha sido salvada!`\n\nEl Santo Inocente tiene el suficiente remordimiento como para mostrarse avergonzado. \"Ah, hm,\" dice. \"Tal vez... me dejé llevar un poco.\"\n\nLa multitud murmura. Flores empapadas son arrastradas a las aceras. A la distancia, en alguna parte, un techo se derrumba con un ruido espectacular.\n\n\"Eh, sí,\" dice el Santo Inocente. \"Lo que quise decir es que lo siento terriblemente.\" Deja salir un suspiro. \"Supongo que no todo en la vida puede ser diversión. No me haría daño enfocarme de vez en cuando. Quizás me adelante para la broma del año que viene.\"\n\nRedphoenix tose exageradamente.\n\n\"Quiero decir, ¡quizás me adelante para la limpieza general!\" dice el Santo Inocente. \"No hay nada que temer, pronto dejaré a Ciudad Hábito como nueva. Por suerte nadie me supera en el manejo de dos trapeadores al mismo tiempo.\"\n\nAnimada, la banda de música comienza a tocar.\n\nNo pasa mucho tiempo hasta que todo vuelve a la normalidad en Ciudad Hábito. Además, ahora que el Obnubilave se ha evaporado, las abejas mágicas de Desconcertaire vuelven a trabajar con un ritmo ajetreado, y pronto las flores brotan y la ciudad flota una vez más.\n\nMientras los Habiticanos abrazan a las peludas abejas mágicas, los ojos del Santo Inocente se iluminan. \"¡Ajá, se me ha ocurrido algo! ¿Por qué no se quedan con algunas de estas peluditas abejas como mascotas y monturas? Es un regalo que simboliza perfectamente el equilibrio entre el trabajo duro y las dulces recompensas, si me voy a poner aburrido y alegórico.\" El Inocente guiña. \"Además, ¡no tienen aguijones! Palabra de Inocente.\"",
+ "questBewilderBossRageTitle": "Ataque Engañador",
+ "questBewilderBossRageDescription": "Cuando esta barra se llene, ¡El Obnubilave desatará su Ataque Engañador sobre Habitica!",
+ "questBewilderDropBumblebeePet": "Abeja mágica (mascota)",
+ "questBewilderDropBumblebeeMount": "Abeja Mágica (Montura)",
+ "questBewilderBossRageMarket": "`¡El Obnubilave usa ATAQUE ENGAÑADOR!`\n \n¡Oh no! A pesar de nuestros mejores esfuerzos, ¡nos hemos distraído con las cautivadoras ilusiones del Obnubilave y hemos olvidado completar algunas de nuestras Diarias! Con un alarido chirriante, el ave resplandeciente bate sus alas, levantando una nube de neblina alrededor de Alex el Comerciante. Cuando la niebla desaparece, ¡Alex ha sido poseído! \"¡Llévense algunas muestras gratis!\" grita alegremente, y comienza a arrojar pociones y huevos explosivos a los Habiticanos que huyen. No es la venta más prometedora, eso está claro.\n\n¡De prisa! Mantengámonos enfocados en nuestras Diarias para derrotar a este monstruo antes de que posea a alguien más.",
+ "questBewilderBossRageStables": "`¡El Obnubilave usa ATAQUE ENGAÑADOR!`\n\n¡¡¡Ahh!!! Una vez más el Obnubilave nos ha deslumbrado y ha hecho que descuidemos nuestras Diarias, ¡y ahora ha atacado a Matt el Maestro de las Bestias! Con un remolino de niebla, Matt se transforma en una aterradora criatura alada, y todas las mascotas y monturas aúllan tristemente en sus establos. Rápido, ¡manténganse enfocados en sus tareas para vencer a esta cruel distracción!",
+ "questBewilderBossRageBailey": "`¡El Obnubilave usa ATAQUE ENGAÑADOR!`\n\n¡Cuidado! En medio del informe de las noticias, ¡Bailey la Pregonera ha sido poseída por el Obnubilave! Bailey deja escapar un chillido maligno y poco informativo a medida que asciende en el aire. Ahora, ¿cómo sabremos qué está ocurriendo?\n\nNo se rindan... ¡estamos tan cerca de derrotar a esta molesta ave de una vez por todas!",
+ "questFalconText": "Las Aves de la Procrastinación",
+ "questFalconNotes": "El Monte Habitica ha sido ensombrecido por una montaña de pendientes. Solía ser un lugar para ir de picnic y disfrutar de una sensación de logro, hasta que las tareas descuidadas se salieron de control. Ahora es el hogar de las temibles Aves de la Procrastinación, ¡criaturas terribles que no permiten a los Habiticanos completar sus tareas!
\"¡Es demasiado difícil!\" les graznan a @JonArinbjorn y a @Onheiron. \"¡Tomará mucho tiempo si lo hacen ahora! ¡No habrá diferencia si lo dejan para mañana! ¿Por qué mejor no hacen algo divertido?\"
No más, juras. ¡Escalarás tu montaña personal de Pendientes y vencerás a las Aves de la Procrastinación!",
+ "questFalconCompletion": "Habiendo triunfado finalmente sobre las Aves de la Procrastinación, te sientas para disfrutar la vista y un merecido descanso.
\"¡Guau!\" dice @Trogdorina. \"¡Ganaste!\"
@Squish agrega, \"Ten, llévate estos huevos que encontré como recompensa.\"",
+ "questFalconBoss": "Aves de la Procrastinación",
+ "questFalconDropFalconEgg": "Halcón (Huevo)",
+ "questFalconUnlockText": "Desbloquea huevos de Halcón adquiribles en el Mercado",
+ "questTreelingText": "La Enredadera",
+ "questTreelingNotes": "Es la Competición Anual de Jardines, y todo el mundo está hablando del misterioso proyecto que @aurakami ha prometido revelar. Te unes a la muchedumbre el día del gran anuncio, y te maravillas con la presentación de un árbol viviente. @fuzzytrees explica que el árbol ayudará con el cuidado del jardín, mostrando cómo puede cortar el césped, recortar los setos y podar las rosas al mismo tiempo... Hasta que el árbol se vuelve loco, dirigiendo sus podaderas a su creador. El público entra en pánico y todo el mundo intenta escapar, pero tú no tienes miedo... Te acercas de un salto, preparado para luchar.",
+ "questTreelingCompletion": "Te sacudes el polvo mientras las últimas hojas caen al suelo. A pesar del revuelo, la Competición de Jardines está a salvo ahora... ¡aunque el árbol que acabas de dejar hecho astillas no parece que vaya a ganar muchos premios! \"Aun quedan detalles por ultimar,\" dice @PainterProphet. \"Quizás otra persona entrenaría mejor a los pimpollos. ¿Quieres probar?\"",
+ "questTreelingBoss": "Enredadera",
+ "questTreelingDropTreelingEgg": "Brote (Huevo)",
+ "questTreelingUnlockText": "Desbloquea la compra de huevos de Brote en el Mercado"
}
\ No newline at end of file
diff --git a/common/locales/es/rebirth.json b/common/locales/es/rebirth.json
index 6bbcbbb300..a0e524b88c 100644
--- a/common/locales/es/rebirth.json
+++ b/common/locales/es/rebirth.json
@@ -5,7 +5,7 @@
"rebirthStartOver": "Con el renacimiento, tu personaje vuelve al nivel 1.",
"rebirthAdvList1": "Restauras completamente tu salud.",
"rebirthAdvList2": "No tienes experiencia, oro ni equipos (salvo los artículos gratuitos, como los objetos misteriosos).",
- "rebirthAdvList3": "Tus hábitos, Diarias, y Quehaceres se vuelven amarillos, y las rachas se restablecen.",
+ "rebirthAdvList3": "Your Habits, Dailies, and To-Dos reset to yellow, and streaks reset, except for challenge tasks.",
"rebirthAdvList4": "Al principio tienes la clase de Guerrero hasta que consigues una clase nueva.",
"rebirthInherit": "Tu nuevo personaje hereda unas pocas cosas de su predecesor:",
"rebirthInList1": "Las tareas, el historial, y los ajustes permanecen.",
@@ -24,5 +24,6 @@
"rebirthPop": "Comienza con un personaje en Nivel 1, manteniendo logros, coleccionables, y tareas con historial.",
"rebirthName": "Esfera de Renacimiento",
"reborn": "Renacido, nivel máximo <%= reLevel %>",
- "confirmReborn": "¿Está seguro?"
+ "confirmReborn": "¿Está seguro?",
+ "rebirthComplete": "You have been reborn!"
}
\ No newline at end of file
diff --git a/common/locales/es/settings.json b/common/locales/es/settings.json
index 254586733a..53ba76f0ed 100644
--- a/common/locales/es/settings.json
+++ b/common/locales/es/settings.json
@@ -47,6 +47,7 @@
"customDayStart": "Comienzo de Día Personalizado",
"changeCustomDayStart": "¿Cambiar inicio de día?",
"sureChangeCustomDayStart": "¿Seguro que quieres cambiar la hora a la que empieza cada día?",
+ "customDayStartHasChanged": "Your custom day start has changed.",
"nextCron": "Tus tareas diarias se restablecerán la próxima vez que uses Habitica a partir del <%= time %>. ¡No te olvides de completar las tareas diarias antes de esa hora!",
"customDayStartInfo1": "De forma predeterminada, Habitica comprueba si has realizado las tareas diarias y las restablece todos los días a medianoche, de acuerdo con tu zona horaria. Aquí puedes cambiar la hora a la que lo hace.",
"misc": "Varios",
@@ -61,12 +62,23 @@
"newUsername": "Nuevo Nombre de Usuario",
"dangerZone": "Zona Peligrosa",
"resetText1": "¡PELIGRO! Eso resetea muchas partes de tu cuenta. Esto es altamente desalentador, pero algunas personas lo encuentran útil al principio después de jugar durante un tiempo.",
- "resetText2": "Perderás todos tus niveles, oro y bonus de experiencia. Todas tus tareas serán borradas permanentemente y perderás todos los datos históricos de tus tareas. Perderás todo tu equipo pero podrás comprarlo de nuevo, incluyendo todos los equipos de edición limitada o objetos Misteriosos por suscribirse que ya poseas (necesitarás estar en la clase correcta para re-comprar un equipo de una clase específica). Mantendrás tu clase actual con tus mascotas y monturas. Quizás prefieras usar en su lugar un Orbe de Renacimiento, el cual es una opción mucho más segura y que mantendrá todas tus tareas.",
+ "resetText2": "You will lose all your levels, gold, and experience points. All your tasks (except those from challenges) will be deleted permanently and you will lose all of their historical data. You will lose all your equipment but you will be able to buy it all back, including all limited edition equipment or subscriber Mystery items that you already own (you will need to be in the correct class to re-buy class-specific gear). You will keep your current class and your pets and mounts. You might prefer to use an Orb of Rebirth instead, which is a much safer option and which will preserve your tasks.",
"deleteText": "¿Estás seguro? Esto borrará tu cuenta para siempre, y no podrá ser recuperada. Necesitarás registrarte de nuevo para volver a usar Habitica. Las Gemas no serán reembolsadas. Si estás seguro escribe <%= deleteWord %> en la casilla de abajo.",
"API": "API",
+ "APIv3": "API v3",
+ "APIv2": "API v2 - Deprecated",
"APIText": "Copie esto para usarlo en aplicaciones de terceros. Sin embargo, pensá en tu API Token como una contraseña, no la compartas en público. En ocasiones se te solicitará tu ID de Usuario, pero nunca publiques tu API Token donde otros puedan verla, incluyendo Github.",
"APIToken": "API Token (esto es una contraseña - mira la advertencia de arriba!)",
+ "thirdPartyApps": "Aplicaciones de terceros",
+ "dataToolDesc": "Una página que te muestra determinada información de tu cuenta de Habitica, como estadísticas sobre tus tareas, tu equipo o tus habilidades.",
+ "beeminder": "Beeminder",
+ "beeminderDesc": "Deja que Beeminder haga un seguimiento de tus tareas pendientes. Tú puedes comprometerte a mantener un número de quehaceres completados por día o por semana, o puedes comprometerte a reducir gradualmente tu número de quehaceres sin completar. (¡Y en Beeminder \"comprometerse a\" es so pena de pagar dinero real! Aunque puede que simplemente te gusten los gráficos sofisticados de Beeminder.)",
+ "chromeChatExtension": "Extensión de chat para Chrome",
+ "chromeChatExtensionDesc": "La Extensión de chat para Chrome de Habitica añade una caja de diálogo a todas las páginas de habitica.com. Permite a los usuarios chatear en la Taberna, su grupo y cualquier gremio al que pertenezcan.",
+ "otherExtensions": "Otras Extensiones",
+ "otherDesc": "Encuentra más aplicaciones, extensiones y herramientas en la wiki de Habitica.",
"resetDo": "¡Adelante! ¡Reinicia mi cuenta!",
+ "resetComplete": "Reset complete!",
"fixValues": "Ajustar valores",
"fixValuesText1": "Si has encontrado o cometido un error que injustamente cambió tu personaje (daño que no merecias, oro que no ganaste, etc.), puedes ajustar tus números correctos aquí. Si, puedes hacer trampa: usa esta función con discreción, o arruinarás la creación de tus hábitos!",
"fixValuesText2": "Ten en cuenta que no puedes restaurar Rachas de tareas individuales aquí. Para hacer eso, entra en el menú de edición de la tarea Diaria y ve a opciones avanzadas, allí encontraras el campo para Restaurar Rachas.",
@@ -96,6 +108,7 @@
"emailNotifications": "Notificaciones por Correo Electrónico",
"wonChallenge": "¡Has ganado un desafío!",
"newPM": "Mensaje Privado Recibido",
+ "sentGems": "Sent gems!",
"giftedGems": "Gemas Regaladas",
"giftedGemsInfo": "<%= amount %> Gemas - por <%= name %>",
"giftedSubscription": "Suscripción Regalada",
@@ -135,6 +148,11 @@
"webhooks": "Webhooks",
"enabled": "Habilitado",
"webhookURL": "URL del Webhook",
+ "invalidUrl": "invalid url",
+ "invalidEnabled": "the \"enabled\" parameter should be a boolean",
+ "regIdRequired": "RegId is required",
+ "pushDeviceAdded": "Push device added successfully",
+ "pushDeviceAlreadyAdded": "The user already has the push device",
"add": "Añadir",
"buyGemsGoldCap": "Tope aumentado a <%= amount %>",
"mysticHourglass": "<%= amount %> Reloj de Arena Místico",
@@ -149,5 +167,5 @@
"amazonPayments": "Pagos con Amazon",
"timezone": "Zona horaria",
"timezoneUTC": "Habitica usa la zona horaria de tu PC, que es: <%= utc %>",
- "timezoneInfo": "Si esa zona horaria es incorrecta, primero actualiza esta página usando el botón de actualizar de tu navegador para asegurar que Habitica tiene la información más reciente. Si sigue siendo incorrecta, ajusta la zona horaria en tu PC y luego actualiza la página una vez más.
Si usas Habitica en otras PCs o dispositivos móviles, la zona horaria tiene que ser la misma en todos ellos. Si tus Diarias se han estado reiniciando en el horario incorrecto, repite esta prueba en todas las otras PCs y en el navegador de tus dispositivos móviles."
+ "timezoneInfo": "If that time zone is wrong, first reload this page using your browser's reload or refresh button to ensure that Habitica has the most recent information. If it is still wrong, adjust the time zone on your PC and then reload this page again.
If you use Habitica on other PCs or mobile devices, the time zone must be the same on them all. If your Dailies have been resetting at the wrong time, repeat this check on all other PCs and on a browser on your mobile devices."
}
\ No newline at end of file
diff --git a/common/locales/es/spells.json b/common/locales/es/spells.json
index dfa3d033ec..6f2783ea06 100644
--- a/common/locales/es/spells.json
+++ b/common/locales/es/spells.json
@@ -35,8 +35,8 @@
"spellSpecialSnowballAuraNotes": "¡Lanza una bola de nieve a un miembro de tu grupo! ¿Que podría salir mal? Dura hasta el final del día de ese miembro.",
"spellSpecialSaltText": "Sal",
"spellSpecialSaltNotes": "Alguien te ha lanzado una bola de nieve. Ja Ja, muy gracioso. ¡Ahora quítame esto de encima!",
- "spellSpecialSpookDustText": "Brillantina espeluznante",
- "spellSpecialSpookDustNotes": "¡Convierte un amigo en una manta flotante con ojos!",
+ "spellSpecialSpookySparklesText": "Spooky Sparkles",
+ "spellSpecialSpookySparklesNotes": "Turn a friend into a floating blanket with eyes!",
"spellSpecialOpaquePotionText": "Poción Opaco",
"spellSpecialOpaquePotionNotes": "Cancela los efectos de la brillantina espeluznante.",
"spellSpecialShinySeedText": "Semilla Brillante",
@@ -46,5 +46,11 @@
"spellSpecialSeafoamText": "Espuma de mar",
"spellSpecialSeafoamNotes": "¡Transforma a un amigo en una criatura marina!",
"spellSpecialSandText": "Arena",
- "spellSpecialSandNotes": "Cancela los efectos de la espuma de mar"
+ "spellSpecialSandNotes": "Cancela los efectos de la espuma de mar",
+ "spellNotFound": "Skill \"<%= spellId %>\" not found.",
+ "partyNotFound": "Party not found",
+ "targetIdUUID": "\"targetId\" must be a valid User ID.",
+ "challengeTasksNoCast": "Casting a skill on challenge tasks is not allowed.",
+ "spellNotOwned": "You don't own this skill.",
+ "spellLevelTooHigh": "You must be level <%= level %> to use this skill."
}
\ No newline at end of file
diff --git a/common/locales/es/subscriber.json b/common/locales/es/subscriber.json
index d49f938b66..31ba696a9f 100644
--- a/common/locales/es/subscriber.json
+++ b/common/locales/es/subscriber.json
@@ -4,6 +4,8 @@
"subDescription": "Comprar Joyas con Oro",
"buyGemsGold": "Comprar Gemas con oro",
"buyGemsGoldText": "Alejandro el Mercader le venderá Joyas por <%= gemCost %> por joya. Sus envíos mensuales tienen un límite de <%= gemLimit %> Joyas por mes. Sin embargo, este límite aumenta cada mes en 5 Joyas por cada tres meses de suscripción consecutiva, ¡hasta un máximo de 50 Joyas al mes!",
+ "mustSubscribeToPurchaseGems": "Must subscribe to purchase gems with GP",
+ "reachedGoldToGemCap": "You've reached the Gold=>Gem conversion cap <%= convCap %> for this month. We have this to prevent abuse / farming. The cap will reset within the first three days of next month.",
"retainHistory": "Retener entradas adicionales al historial",
"retainHistoryText": "Hace que las tareas completadas y el historial de tareas estén disponibles durante más tiempo.",
"doubleDrops": "Duplica la cantidad de botines diarios",
@@ -29,6 +31,7 @@
"manageSub": "Clic para modificar tu suscripción",
"cancelSub": "Cancelar suscripción",
"canceledSubscription": "Suscripciones canceladas",
+ "cancelingSubscription": "Canceling the subscription",
"adminSub": "Suscripciones de administradores",
"morePlans": "Más planes próximamente",
"organizationSub": "Organización Privada",
@@ -73,6 +76,9 @@
"timeTravelersPopover": "Vemos que tienes un Reloj de Arena Místico, así que felizmente te llevaremos al pasado! Por favor, elije la Mascota, Montura o Juego de Artículos Misteriosos que deseas cambiar. Puedes ver una lista de los objetos del pasado Aquí! Si esos no te satisfacen, estarias interesado en alguno de nuestros futurísticos Sets de Objetos Steampunk que están TAN a la moda?",
"timeTravelersAlreadyOwned": "¡Enhorabuena! Ya tienes todo lo que ofrecen actualmente los viajeros del tiempo. ¡Gracias por hacer esto posible!",
"mysticHourglassPopover": "Con el Reloj de arena místico, puedes comprar ciertos artículos del pasado que estuvieron disponibles por tiempo limitado, como los conjuntos mensuales de artículos misteriosos y los premios de jefes mundiales.",
+ "mysterySetNotFound": "Mystery set not found, or set already owned.",
+ "mysteryItemIsEmpty": "Mystery items are empty",
+ "mysteryItemOpened": "Mystery item opened.",
"mysterySet201402": "Túnica de Mensajero",
"mysterySet201403": "Conjunto del Bosquero",
"mysterySet201404": "Conjunto de la Mariposa Crepuscular",
@@ -98,7 +104,9 @@
"mysterySet201512": "Conjunto de la Llama Invernal",
"mysterySet201601": "Juego de Campeón de Resoluciones",
"mysterySet201602": "Conjunto de Rompecorazones",
- "mysterySet201603": "Lucky Clover Set",
+ "mysterySet201603": "Juego del Trébol afortunado",
+ "mysterySet201604": "Leaf Warrior Set",
+ "mysterySet201605": "Marching Bard Set",
"mysterySet301404": "El Conjunto Steampunk",
"mysterySet301405": "Accesorios Steampunk",
"mysterySetwondercon": "Wondercon",
@@ -110,9 +118,25 @@
"hourglassBuyItemConfirm": "¿Quieres comprar este artículo por 1 reloj de arena místico?",
"petsAlreadyOwned": "Ya tienes esa mascota.",
"mountsAlreadyOwned": "Ya tienes esa montura.",
- "typeNotAllowedHourglass": "Este tipo de artículo no se puede comprar con relojes de arena místicos. Puedes comprar:",
+ "typeNotAllowedHourglass": "Item type not supported for purchase with Mystic Hourglass. Allowed types: <%= allowedTypes %>",
"petsNotAllowedHourglass": "Esa mascota no se puede comprar con relojes de arena místicos.",
"mountsNotAllowedHourglass": "Esa montura no se puede comprar con relojes de arena místicos.",
"hourglassPurchase": "¡Has comprado un artículo con un reloj de arena místico!",
- "hourglassPurchaseSet": "¡Has comprado un conjunto de artículos con un reloj de arena místico!"
+ "hourglassPurchaseSet": "¡Has comprado un conjunto de artículos con un reloj de arena místico!",
+ "missingUnsubscriptionCode": "Missing unsubscription code.",
+ "missingSubscription": "User does not have a plan subscription",
+ "missingSubscriptionCode": "Missing subscription code. Possible values: basic_earned, basic_3mo, basic_6mo, google_6mo, basic_12mo.",
+ "cannotDeleteActiveAccount": "You have an active subscription, cancel your plan before deleting your account.",
+ "paymentNotSuccessful": "The payment was not successful",
+ "planNotActive": "The plan hasn't activated yet (due to a PayPal bug). It will begin <%= nextBillingDate %>, after which you can cancel to retain your full benefits",
+ "notAllowedHourglass": "Pet/Mount not available for purchase with Mystic Hourglass.",
+ "readCard": "<%= cardType %> has been read",
+ "cardTypeRequired": "Card type required",
+ "cardTypeNotAllowed": "Unknown card type.",
+ "invalidCoupon": "Invalid coupon code.",
+ "couponUsed": "Coupon code already used.",
+ "noSudoAccess": "You don't have sudo access.",
+ "couponCodeRequired": "The coupon code is required.",
+ "eventRequired": "\"req.params.event\" is required.",
+ "countRequired": "\"req.query.count\" is required."
}
\ No newline at end of file
diff --git a/common/locales/es/tasks.json b/common/locales/es/tasks.json
index d8a03d58b7..d35e8c9e6b 100644
--- a/common/locales/es/tasks.json
+++ b/common/locales/es/tasks.json
@@ -4,36 +4,36 @@
"deleteToDosExplanation": "Si le das clic al botón de abajo, todas sus tareas completadas y tareas archivadas se borrarán permanentemente. Expórtalas primero si quieres llevar un registro de ellas.",
"addmultiple": "Añade varias",
"addsingle": "Añade una",
- "habit": "hábito",
+ "habit": "Hábito",
"habits": "Hábitos",
- "newHabit": "Nuevo Hábito",
- "newHabitBulk": "Hábitos Nuevos (uno por línea)",
+ "newHabit": "Nuevo hábito",
+ "newHabitBulk": "Nuevos hábitos (uno por línea)",
"yellowred": "Débil",
"greenblue": "Fuerte",
"edit": "Editar",
"save": "Guardar",
- "addChecklist": "Aňadir Lista",
+ "addChecklist": "Aňadir lista",
"checklist": "Lista",
"checklistText": "¡Divide una tarea en pedazos más pequeños! Las listas aumentan la Experiencia y el Oro conseguidos por una Tarea Pendiente y reducen el daño causado por una Diaria.",
- "expandCollapse": "Expandir/Colapsar",
+ "expandCollapse": "Expandir/contraer",
"text": "Título",
- "extraNotes": "Notas Extra",
- "direction/Actions": "Dirección/Acciones",
- "advancedOptions": "Opciones Avanzadas",
+ "extraNotes": "Notas adicionales",
+ "direction/Actions": "Dirección/acciones",
+ "advancedOptions": "Opciones avanzadas",
"difficulty": "Dificultad",
"difficultyHelpTitle": "¿Qué dificultad tiene esta tarea?",
"difficultyHelpContent": "Cuanto más dura sea una tarea, más Experiencia y Oro ganas al completarla... ¡pero más daño hace si es una Diaria o un Mal Hábito!",
"trivial": "Trivial",
"easy": "Fácil",
- "medium": "Intermedio",
+ "medium": "Intermedia",
"hard": "Difícil",
"attributes": "Atributos",
- "physical": "Físicos",
+ "physical": "Físicas",
"mental": "Mentales",
"otherExamples": "Ej, actividades profesional, pasatiempos, finanzas, etc.",
"progress": "Progreso",
- "daily": "tarea diaria",
- "dailies": "tareas Diarias",
+ "daily": "Tarea diaria",
+ "dailies": "Tareas diarias",
"newDaily": "Nueva tarea Diaria",
"newDailyBulk": "Nuevas Diarias (una por línea)",
"streakCounter": "Contador de Rachas",
@@ -73,6 +73,7 @@
"clearTags": "Limpiar",
"hideTags": "Ocultar",
"showTags": "Mostrar",
+ "toRequired": "You must supply a to value",
"startDate": "Fecha de Inicio",
"startDateHelpTitle": "¿Cuándo debería empezar esta tarea?",
"startDateHelp": "Fija la fecha para la que esta tarea toma efecto. No se acabará el plazo en días anteriores.",
@@ -88,8 +89,9 @@
"fortifyName": "Poción de Fortalecimiento.",
"fortifyPop": "Devuelve todas sus tareas a un valor neutral (amarillo), y recupera toda la salud perdida.",
"fortify": "Fortalecer",
- "fortifyText": "Fortalecer devolverá todas tus tareas a un estado neutral (amarillo), como si las acabaras de añadir, y rellena tu Salud al máximo. Esto es genial si tus tareas rojas están haciendo el juego demasiado duro, o tus tareas azules lo están haciendo demasiado fácil. Si empezar de cero te suena mucho más motivante, ¡gasta las Gemas y pilla un indulto!",
+ "fortifyText": "Fortify will return all your tasks, except challenge tasks, to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!",
"confirmFortify": "¿Está seguro?",
+ "fortifyComplete": "Fortify complete!",
"sureDelete": "¿Seguro? ¿Eliminar <%= taskType %> con el texto \"<%= taskText %>\"?",
"streakCoins": "¡Bonus de Racha!",
"pushTaskToTop": "Desplazar tarea hacia arriba. Mantenga ctrl o cmd para desplazar hasta el final.",
@@ -112,5 +114,18 @@
"rewardHelp2": "El Equipamiento afecta a tus Estadísticas (<%= linkStart %>Usuario > Estadísticas<%= linkEnd %>).",
"rewardHelp3": "El equipamiento especial aparecerá aquí durante los Eventos Mundiales.",
"rewardHelp4": "",
- "clickForHelp": "Haz click para obtener ayuda"
+ "clickForHelp": "Haz click para obtener ayuda",
+ "taskIdRequired": "\"taskId\" must be a valid UUID.",
+ "taskNotFound": "Task not found.",
+ "invalidTaskType": "Task type must be one of \"habit\", \"daily\", \"todo\", \"reward\".",
+ "cantDeleteChallengeTasks": "A task belonging to a challenge can't be deleted.",
+ "checklistOnlyDailyTodo": "Checklists are supported only on dailies and todos",
+ "checklistItemNotFound": "No checklist item was found with given id.",
+ "itemIdRequired": "\"itemId\" must be a valid UUID.",
+ "tagNotFound": "No tag item was found with given id.",
+ "tagIdRequired": "\"tagId\" must be a valid UUID corresponding to a tag belonging to the user.",
+ "positionRequired": "\"position\" is required and must be a number.",
+ "cantMoveCompletedTodo": "Can't move a completed todo.",
+ "directionUpDown": "\"direction\" is required and must be 'up' or 'down'",
+ "alreadyTagged": "The task is already tagged with given tag."
}
\ No newline at end of file
diff --git a/common/locales/es_419/backgrounds.json b/common/locales/es_419/backgrounds.json
index f5d780abea..eedee12564 100644
--- a/common/locales/es_419/backgrounds.json
+++ b/common/locales/es_419/backgrounds.json
@@ -3,7 +3,7 @@
"backgrounds062014": "CONJUNTO 1: Lanzado en junio 2014",
"backgroundBeachText": "Playa",
"backgroundBeachNotes": "Holgazanea en una playa tibia.",
- "backgroundFairyRingText": "Anillo de Hadas",
+ "backgroundFairyRingText": "Anillo de hadas",
"backgroundFairyRingNotes": "Baila en un anillo de hadas.",
"backgroundForestText": "Bosque",
"backgroundForestNotes": "Pasea por un bosque veraniego.",
@@ -12,98 +12,98 @@
"backgroundCoralReefNotes": "Nada en un arrecife de coral.",
"backgroundOpenWatersText": "Mar Abierto",
"backgroundOpenWatersNotes": "Disfruta del mar abierto.",
- "backgroundSeafarerShipText": "Barco Marinero",
+ "backgroundSeafarerShipText": "Barco marinero",
"backgroundSeafarerShipNotes": "Navega a bordo de un barco marinero.",
"backgrounds082014": "CONJUNTO 3: Lanzado en agosto 2014",
"backgroundCloudsText": "Nubes",
"backgroundCloudsNotes": "Vuela por las nubes.",
- "backgroundDustyCanyonsText": "Cañón Polvoriento",
+ "backgroundDustyCanyonsText": "Cañón polvoriento",
"backgroundDustyCanyonsNotes": "Vaga por un cañón polvoriento.",
"backgroundVolcanoText": "Volcán",
"backgroundVolcanoNotes": "Caliéntate dentro de un volcán.",
"backgrounds092014": "CONJUNTO 4: Lanzado en septiembre 2014",
- "backgroundThunderstormText": "Tormenta Eléctrica",
+ "backgroundThunderstormText": "Tormenta eléctrica",
"backgroundThunderstormNotes": "Conduce relámpagos en una tormenta eléctrica.",
- "backgroundAutumnForestText": "Bosque Otoñal",
+ "backgroundAutumnForestText": "Bosque otoñal",
"backgroundAutumnForestNotes": "Pasea por un bosque otoñal.",
- "backgroundHarvestFieldsText": "Campos de Cosecha",
+ "backgroundHarvestFieldsText": "Campos de cosecha",
"backgroundHarvestFieldsNotes": "Cultiva tus campos de cosecha.",
"backgrounds102014": "CONJUNTO 5: Lanzado en octubre 2014",
"backgroundGraveyardText": "Cementerio",
"backgroundGraveyardNotes": "Visita un cementerio escalofriante.",
- "backgroundHauntedHouseText": "Casa Embrujada",
+ "backgroundHauntedHouseText": "Casa embrujada",
"backgroundHauntedHouseNotes": "Escabúllete por una casa embrujada.",
- "backgroundPumpkinPatchText": "Huerto de Calabazas",
+ "backgroundPumpkinPatchText": "Huerto de calabazas",
"backgroundPumpkinPatchNotes": "Talla calabazas de Halloween en un huerto de calabazas.",
"backgrounds112014": "CONJUNTO 6: Lanzado en noviembre 2014",
- "backgroundHarvestFeastText": "Banquete de Cosecha",
+ "backgroundHarvestFeastText": "Banquete de cosecha",
"backgroundHarvestFeastNotes": "Disfruta de un banquete de cosecha.",
- "backgroundStarrySkiesText": "Cielo Estrellado",
+ "backgroundStarrySkiesText": "Cielo estrellado",
"backgroundStarrySkiesNotes": "Contempla el cielo estrellado.",
- "backgroundSunsetMeadowText": "Atardecer en el Prado",
+ "backgroundSunsetMeadowText": "Atardecer en el prado",
"backgroundSunsetMeadowNotes": "Admira un atardecer en el prado.",
"backgrounds122014": "CONJUNTO 7: Lanzado en diciembre 2014",
"backgroundIcebergText": "Témpano",
"backgroundIcebergNotes": "Piérdete en un témpano.",
- "backgroundTwinklyLightsText": "Luces Centelleantes Invernales",
+ "backgroundTwinklyLightsText": "Luces centelleantes invernales",
"backgroundTwinklyLightsNotes": "Pasea entre árboles adornados con luces festivas.",
"backgroundSouthPoleText": "Polo Sur",
"backgroundSouthPoleNotes": "Visita el glacial polo sur.",
"backgrounds012015": "CONJUNTO 8: Lanzado en enero 2015",
- "backgroundIceCaveText": "Cueva de Hielo",
+ "backgroundIceCaveText": "Cueva de hielo",
"backgroundIceCaveNotes": "Desciende a una cueva de hielo.",
- "backgroundFrigidPeakText": "Cima Congelada",
+ "backgroundFrigidPeakText": "Cima congelada",
"backgroundFrigidPeakNotes": "Alcanza el pico de una cima congelada.",
- "backgroundSnowyPinesText": "Pinos Nevados",
+ "backgroundSnowyPinesText": "Pinos nevados",
"backgroundSnowyPinesNotes": "Refúgiate en medio de unos pinos nevados.",
- "backgrounds022015": "Conjunto 9: Lanzado en febrero 2015",
+ "backgrounds022015": "CONJUNTO 9: Lanzado en febrero 2015",
"backgroundBlacksmithyText": "Herrería",
"backgroundBlacksmithyNotes": "Trabaja en la herrería.",
- "backgroundCrystalCaveText": "Cueva de Cristal",
+ "backgroundCrystalCaveText": "Cueva de cristal",
"backgroundCrystalCaveNotes": "Explora una cueva de cristal.",
- "backgroundDistantCastleText": "Castillo Distante",
+ "backgroundDistantCastleText": "Castillo distante",
"backgroundDistantCastleNotes": "Defiende un distante castillo.",
"backgrounds032015": "CONJUNTO 10: Lanzado en marzo 2015",
- "backgroundSpringRainText": "Lluvia de Primavera",
+ "backgroundSpringRainText": "Lluvia de primavera",
"backgroundSpringRainNotes": "Baila en la lluvia de primavera.",
"backgroundStainedGlassText": "Vitral",
"backgroundStainedGlassNotes": "Admira algunos vitrales.",
- "backgroundRollingHillsText": "Colinas Ondulantes",
+ "backgroundRollingHillsText": "Colinas ondulantes",
"backgroundRollingHillsNotes": "Diviértete en las colinas ondulantes.",
"backgrounds042015": "CONJUNTO 11: Lanzado en abril 2015",
"backgroundCherryTreesText": "Cerezos",
"backgroundCherryTreesNotes": "Admira los cerezos en flor.",
- "backgroundFloralMeadowText": "Prado en Flor",
+ "backgroundFloralMeadowText": "Prado en flor",
"backgroundFloralMeadowNotes": "Haz un picnic en un prado en flor.",
- "backgroundGumdropLandText": "Tierra de Gominolas",
- "backgroundGumdropLandNotes": "Mordisquea el paisaje de la Tierra de Gominolas.",
+ "backgroundGumdropLandText": "Tierra de gominolas",
+ "backgroundGumdropLandNotes": "Mordisquea el paisaje de la tierra de gominolas.",
"backgrounds052015": "CONJUNTO 12: Lanzado en mayo 2015",
- "backgroundMarbleTempleText": "Templo de Mármol",
+ "backgroundMarbleTempleText": "Templo de mármol",
"backgroundMarbleTempleNotes": "Posa frente a un templo de mármol.",
- "backgroundMountainLakeText": "Lago de Montaña",
+ "backgroundMountainLakeText": "Lago de montaña",
"backgroundMountainLakeNotes": "Sumerge tus dedos en un lago de montaña.",
"backgroundPagodasText": "Pagodas",
"backgroundPagodasNotes": "Escala a lo más alto de las pagodas.",
"backgrounds062015": "CONJUNTO 13: Lanzado en junio 2015",
- "backgroundDriftingRaftText": "Balsa a la Deriva",
+ "backgroundDriftingRaftText": "Balsa a la deriva",
"backgroundDriftingRaftNotes": "Rema una balsa a la deriva.",
- "backgroundShimmeryBubblesText": "Burbujas Brillantes",
+ "backgroundShimmeryBubblesText": "Burbujas brillantes",
"backgroundShimmeryBubblesNotes": "Flota en un océano de burbujas brillantes.",
- "backgroundIslandWaterfallsText": "Cascada Isleña",
+ "backgroundIslandWaterfallsText": "Cascada isleña",
"backgroundIslandWaterfallsNotes": "Haz un picnic cerca de una cascada isleña.",
"backgrounds072015": "CONJUNTO 14: Lanzado en julio 2015",
- "backgroundDilatoryRuinsText": "Ruinas de Dilatoria",
+ "backgroundDilatoryRuinsText": "Ruinas de dilatoria",
"backgroundDilatoryRuinsNotes": "Sumérgete en las ruinas de Dilatoria.",
- "backgroundGiantWaveText": "Ola Gigante",
+ "backgroundGiantWaveText": "Ola gigante",
"backgroundGiantWaveNotes": "¡Haz surf en una ola gigante!",
- "backgroundSunkenShipText": "Barco Hundido",
+ "backgroundSunkenShipText": "Barco hundido",
"backgroundSunkenShipNotes": "Explora un barco hundido.",
"backgrounds082015": "CONJUNTO 15: Lanzado en agosto 2015",
"backgroundPyramidsText": "Pirámides",
"backgroundPyramidsNotes": "Admira las pirámides.",
- "backgroundSunsetSavannahText": "Atardecer en la Sabana",
+ "backgroundSunsetSavannahText": "Atardecer en la sabana",
"backgroundSunsetSavannahNotes": "Acecha al atardecer en la sabana.",
- "backgroundTwinklyPartyLightsText": "Luces Festivas Centelleantes",
+ "backgroundTwinklyPartyLightsText": "Luces festivas centelleantes",
"backgroundTwinklyPartyLightsNotes": "¡Baila bajo las luces festivas centelleantes!",
"backgrounds092015": "CONJUNTO 16: Lanzado en septiembre 2015",
"backgroundMarketText": "Mercado de Habitica",
@@ -113,52 +113,59 @@
"backgroundTavernText": "Taberna de Habitica",
"backgroundTavernNotes": "Visita la taberna de Habitica.",
"backgrounds102015": "CONJUNTO 17: Lanzado en octubre 2015",
- "backgroundHarvestMoonText": "Luna de Cosecha",
+ "backgroundHarvestMoonText": "Luna de cosecha",
"backgroundHarvestMoonNotes": "Carcajea bajo la luna de cosecha.",
- "backgroundSlimySwampText": "Pantano Fangoso",
+ "backgroundSlimySwampText": "Pantano fangoso",
"backgroundSlimySwampNotes": "Esfuérzate a través de un pantano fangoso.",
- "backgroundSwarmingDarknessText": "Nube Oscura",
- "backgroundSwarmingDarknessNotes": "Tiembla en la nube oscura.",
+ "backgroundSwarmingDarknessText": "Enjambre oscura",
+ "backgroundSwarmingDarknessNotes": "Tiembla en la enjambre oscura.",
"backgrounds112015": "CONJUNTO 18: Lanzado en noviembre de 2015",
- "backgroundFloatingIslandsText": "Islas Flotantes",
+ "backgroundFloatingIslandsText": "Islas flotantes",
"backgroundFloatingIslandsNotes": "Salta a través de las islas flotantes.",
- "backgroundNightDunesText": "Dunas Nocturnas",
+ "backgroundNightDunesText": "Dunas nocturnas",
"backgroundNightDunesNotes": "Camina pacíficamente atravesando las dunas nocturnas.",
- "backgroundSunsetOasisText": "Oasis en el Ocaso",
+ "backgroundSunsetOasisText": "Oasis en el ocaso",
"backgroundSunsetOasisNotes": "Disfruta de un oasis en el ocaso.",
"backgrounds122015": "CONJUNTO 19: Lanzado en Diciembre 2015",
- "backgroundAlpineSlopesText": "Pendientes Alpinas",
- "backgroundAlpineSlopesNotes": "Esquía en las pendientes alpinas.",
- "backgroundSnowySunriseText": "Amanecer Nevado",
+ "backgroundAlpineSlopesText": "Cuestas alpinas",
+ "backgroundAlpineSlopesNotes": "Esquía en las cuestas alpinas.",
+ "backgroundSnowySunriseText": "Amanecer nevado",
"backgroundSnowySunriseNotes": "Contempla el amanecer nevado.",
- "backgroundWinterTownText": "Pueblo Invernal",
+ "backgroundWinterTownText": "Pueblo invernal",
"backgroundWinterTownNotes": "Muévete con prisa a través de un pueblo invernal.",
"backgrounds012016": "CONJUNTO 20: Lanzado en enero 2016",
- "backgroundFrozenLakeText": "Lago Helado",
+ "backgroundFrozenLakeText": "Lago helado",
"backgroundFrozenLakeNotes": "Patina sobre un lago helado.",
- "backgroundSnowmanArmyText": "Ejército de Muñecos de Nieve",
- "backgroundSnowmanArmyNotes": "Dirige a un ejército de muñecos de nieve.",
- "backgroundWinterNightText": "Noche Invernal",
+ "backgroundSnowmanArmyText": "Ejército de hombres de nieve",
+ "backgroundSnowmanArmyNotes": "Dirige a un ejército de hombres de nieve.",
+ "backgroundWinterNightText": "Noche invernal",
"backgroundWinterNightNotes": "Mira las estrellas en una noche invernal.",
"backgrounds022016": "CONJUNTO 21: Lanzado en febrero 2016",
- "backgroundBambooForestText": "Bosque de Bambú",
+ "backgroundBambooForestText": "Bosque de bambú",
"backgroundBambooForestNotes": "Pasea por el bosque de bambú.",
- "backgroundCozyLibraryText": "Biblioteca Acogedora",
+ "backgroundCozyLibraryText": "Biblioteca acogedora",
"backgroundCozyLibraryNotes": "Lee en la biblioteca acogedora.",
- "backgroundGrandStaircaseText": "Gran Escalera",
+ "backgroundGrandStaircaseText": "Gran escalera",
"backgroundGrandStaircaseNotes": "Baja a zancadas por la gran escalera.",
"backgrounds032016": "CONJUNTO 22: Lanzado en marzo 2016",
- "backgroundDeepMineText": "Mina Profunda ",
+ "backgroundDeepMineText": "Mina profunda ",
"backgroundDeepMineNotes": "Encuentra metales preciosos en una mina profunda.",
"backgroundRainforestText": "Selva ",
"backgroundRainforestNotes": "Aventúrate en una selva.",
- "backgroundStoneCircleText": "Círculo de Piedras",
+ "backgroundStoneCircleText": "Círculo de piedras",
"backgroundStoneCircleNotes": "Conjura hechizos en un círculo de piedras.",
- "backgrounds042016": "SET 23: Released April 2016",
- "backgroundArcheryRangeText": "Archery Range",
- "backgroundArcheryRangeNotes": "Practice on the Archery Range.",
- "backgroundGiantFlowersText": "Giant Flowers",
- "backgroundGiantFlowersNotes": "Frolic atop Giant Flowers.",
- "backgroundRainbowsEndText": "End of the Rainbow",
- "backgroundRainbowsEndNotes": "Discover gold at the End of the Rainbow."
+ "backgrounds042016": "CONJUNTO 23: Lanzado en abril 2016",
+ "backgroundArcheryRangeText": "Campo de tiro al arco",
+ "backgroundArcheryRangeNotes": "Practica en el campo de tiro al arco.",
+ "backgroundGiantFlowersText": "Flores gigantes",
+ "backgroundGiantFlowersNotes": "Diviértete sobre flores gigantes.",
+ "backgroundRainbowsEndText": "Final del arco iris",
+ "backgroundRainbowsEndNotes": "Descubre oro al final del arco iris.",
+ "backgrounds052016": "SET 24: Released May 2016",
+ "backgroundBeehiveText": "Beehive",
+ "backgroundBeehiveNotes": "Buzz and dance in a Beehive.",
+ "backgroundGazeboText": "Gazebo",
+ "backgroundGazeboNotes": "Battle a Gazebo.",
+ "backgroundTreeRootsText": "Tree Roots",
+ "backgroundTreeRootsNotes": "Explore the Tree Roots."
}
\ No newline at end of file
diff --git a/common/locales/es_419/challenge.json b/common/locales/es_419/challenge.json
index 541496b908..4a02a4981e 100644
--- a/common/locales/es_419/challenge.json
+++ b/common/locales/es_419/challenge.json
@@ -7,7 +7,7 @@
"brokenChallenge": "Enlace al Desafío roto: esta tarea era parte de un desafío pero el desafío (o grupo) ha sido eliminado. ¿Qué hacemos con las tareas huérfanas?",
"keepThem": "Conservarlas",
"removeThem": "Eliminarlas",
- "challengeCompleted": "Este Desafío ha sido completado ¡y el ganador es <%= user %>! ¿Qué hacemos con las tareas huérfanas?",
+ "challengeCompleted": "Este desafío ha sido completado ¡y el ganador es <%= user %>! ¿Qué hacemos con las tareas huérfanas?",
"unsubChallenge": "Enlace al Desafío roto: esta tarea era parte de un Desafío, pero te has dado de baja del mismo. ¿Qué hacemos con las tareas huérfanas?",
"challengeWinner": "Fue el ganador en los siguientes desafíos",
"challenges": "Desafíos",
@@ -63,5 +63,20 @@
"congratulations": "¡Felicitaciones!",
"hurray": "¡Hurra!",
"noChallengeOwner": "sin dueño",
- "noChallengeOwnerPopover": "Este desafío no tiene un dueño porque la persona que lo creó eliminó su cuenta."
+ "noChallengeOwnerPopover": "Este desafío no tiene un dueño porque la persona que lo creó eliminó su cuenta.",
+ "challengeMemberNotFound": "Usuario no encontrado entre los miembros del desafío.",
+ "onlyGroupLeaderChal": "Solo el líder del grupo puede crear desafíos.",
+ "tavChalsMinPrize": "El premio debe ser al menos 1 Gema para los desafíos de la Taberna.",
+ "cantAfford": "You can't afford this prize. Purchase more gems or lower the prize amount.",
+ "challengeIdRequired": "\"challengeId\" debe ser un UUID válido.",
+ "winnerIdRequired": "\"winnerId\" debe ser un UUID válido.",
+ "challengeNotFound": "Desafío no encontrado.",
+ "onlyLeaderDeleteChal": "Solo el líder del desafío puede eliminarlo.",
+ "onlyLeaderUpdateChal": "Solo el líder del desafío puede actualizarlo.",
+ "winnerNotFound": "El ganador con el ID \"<%= userId %>\" no ha sido encontrado o no es parte del desafío.",
+ "noCompletedTodosChallenge": "\"includeComepletedTodos\" is not supported when fetching a challenge tasks.",
+ "userTasksNoChallengeId": "When \"tasksOwner\" is \"user\" \"challengeId\" can't be passed.",
+ "onlyChalLeaderEditTasks": "Tareas pertenecientes a un desafío solo pueden ser editadas por el líder.",
+ "userAlreadyInChallenge": "El usuario ya está participando en este desafío.",
+ "cantOnlyUnlinkChalTask": "Only broken challenges tasks can be unlinked."
}
\ No newline at end of file
diff --git a/common/locales/es_419/character.json b/common/locales/es_419/character.json
index 3d366e3409..6ca1f30d9a 100644
--- a/common/locales/es_419/character.json
+++ b/common/locales/es_419/character.json
@@ -1,7 +1,8 @@
{
+ "communityGuidelinesWarning": "Por favor ten en cuenta que tu nombre para mostrar, foto de perfil, y resumen sobre mí deben cumplir con las Normas de la comunidad (ej. ninguna blasfemia, ningún tema de adultos, ningún insulto, etc.) Si tienes alguna pregunta sobre si algo es adecuado o no, ¡no dudes en mandar un correo electrónico a leslie@habitica.com!",
"statsAch": "Estadísticas y Logros",
"profile": "Perfil",
- "avatar": "Personalizar Avatar",
+ "avatar": "Personalizar avatar",
"other": "Otro",
"fullName": "Nombre completo",
"displayName": "Nombre para mostrar",
@@ -28,13 +29,13 @@
"bodyHair": "Pelo",
"hairBangs": "Flequillo",
"hairBase": "Base",
- "hairSet1": "Conjunto de Peinados 1",
- "hairSet2": "Conjunto de Peinados 2",
- "bodyFacialHair": "Vello Facial",
+ "hairSet1": "Conjunto de peinados 1",
+ "hairSet2": "Conjunto de peinados 2",
+ "bodyFacialHair": "Vello facial",
"beard": "Barba",
"mustache": "Bigote",
"flower": "Flor",
- "wheelchair": "Wheelchair",
+ "wheelchair": "Silla de ruedas",
"basicSkins": "Pieles básicas",
"rainbowSkins": "Pieles arco iris",
"pastelSkins": "Pieles pastel",
@@ -48,21 +49,21 @@
"equipment": "Equipamiento",
"equipmentBonus": "Equipamiento",
"equipmentBonusText": "Bonus a los atributos proporcionados por tu equipamiento. Revisa la pestaña de Equipamiento en Inventario para seleccionar tu equipo.",
- "classBonus": "Bonus de Equipamiento de Clase",
+ "classBonus": "Bonus de Equipamiento de clase",
"classBonusText": "Tu clase (Guerrero, si no has desbloqueado y seleccionado otra clase) usa su propio equipamento de manera más eficaz que el de otras clases. El equipamento de tu clase actual da un 50% más de puntos al atributo al que bonifica.",
- "classEquipBonus": "Bonus de Clase",
- "battleGear": "Equipamiento de Combate",
+ "classEquipBonus": "Bonus de clase",
+ "battleGear": "Equipamiento de combate",
"battleGearText": "Este es el equipamiento que usas en combate, afecta los resultados cuando interactúas con tus tareas.",
"autoEquipBattleGear": "Auto-equipar nuevos objetos",
"costume": "Disfraz",
"costumeText": "Si prefieres el aspecto de otro equipo al que estás usando, marca la casilla \"Usar Disfraz\" para llevarlo como disfraz mientras usas tu equipo de batalla por debajo.",
"useCostume": "Usar Disfraz",
- "useCostumeInfo1": "¡Haz click en \"Usar Disfraz\" para equipar a tu avatar sin afectar las estadísticas que tu Equipo de Combate te da! Esto significa que a tu izquierda puedes equiparte para tener las mejores estadísiticas, y a tu derecha vestir a tu avatar con tus ítems favoritos.",
- "useCostumeInfo2": "Una vez que hagas clic en \"Usar Disfraz\" tu avatar parecerá básico... ¡pero no te preocupes! Si te fijas en la izquierda verás que tu Equipo de Combate permanece equipado. ¡Ahora puedes volver las cosas entretenidas! Los objetos que selecciones a la derecha no afectarán tus estadísticas, pero pueden hacer que te veas genial. Experimenta con diferentes combos, mezclando conjuntos y coordinando tu disfraz con tus mascotas, monturas y fondos.
¿Tienes más preguntas? Visita la página de Disfraces en la wiki. ¿Has encontrado el conjunto perfecto? ¡Muéstralo con orgullo en el gremio de Carnaval de Disfraces o alardea en la Taberna!",
- "gearAchievement": "¡Has conseguido el logro \"Equipo Definitivo\" por llegar al máximo conjunto de equipo para una clase! Has conseguido los siguientes conjuntos completos:",
- "moreGearAchievements": "Para conseguir más medallas de Equipo Definitivo, cambia de clase en tu página de estadísticas ¡y compra equipamiento para tu nueva clase!",
- "armoireUnlocked": "¡Has desbloqueado el Armario Encantado! ¡Haz clic en la Recompensa de Armario Encantado para tener oportunidad de ganar equipo especial! También puede ser que recibas Experiencia o comida.",
- "ultimGearName": "Equipo Definitivo",
+ "useCostumeInfo1": "¡Haz click en \"Usar Disfraz\" para equipar a tu avatar sin afectar las estadísticas que tu Equipamiento de combate te da! Esto significa que a tu izquierda puedes equiparte para tener las mejores estadísiticas, y a tu derecha vestir a tu avatar con tus ítems favoritos.",
+ "useCostumeInfo2": "Una vez que hagas clic en \"Usar Disfraz\" tu avatar parecerá básico... ¡pero no te preocupes! Si te fijas en la izquierda verás que tu Equipamiento de combate permanece equipado. ¡Ahora puedes volver las cosas entretenidas! Los objetos que selecciones a la derecha no afectarán tus estadísticas, pero pueden hacer que te veas genial. Experimenta con diferentes combos, mezclando conjuntos y coordinando tu disfraz con tus mascotas, monturas y fondos.
¿Tienes más preguntas? Visita la página de Disfraces en la wiki. ¿Has encontrado el conjunto perfecto? ¡Muéstralo con orgullo en el gremio de Carnaval de Disfraces o alardea en la Taberna!",
+ "gearAchievement": "¡Has conseguido el logro \"Equipamiento definitivo\" por llegar al máximo conjunto de equipo para una clase! Has conseguido los siguientes conjuntos completos:",
+ "moreGearAchievements": "Para conseguir más medallas de Equipamiento definitivo, cambia de clase en tu página de estadísticas ¡y compra equipamiento para tu nueva clase!",
+ "armoireUnlocked": "¡Has desbloqueado el Armario encantado! ¡Haz clic en la Recompensa del armario encantado para tener oportunidad de ganar equipameinto especial! También puede ser que recibas experiencia o comida.",
+ "ultimGearName": "Equipamiento definitivo",
"ultimGearText": "Ha llegado al máximo conjunto de arma y armadura para las siguientes clases:",
"level": "Nivel",
"levelUp": "¡Subiste de nivel!",
@@ -95,12 +96,12 @@
"intText": "La Inteligencia aumenta la Experiencia que recibes y, una vez que hayas desbloqueado las Clases, determina el Maná máximo disponible para las habilidades de clase.",
"levelBonus": "Bonus de Nivel",
"levelBonusText": "Cada atributo recibe un bonus de la mitad de (tu Nivel menos 1).",
- "allocatedPoints": "Puntos Asignados",
+ "allocatedPoints": "Puntos asignados",
"allocatedPointsText": "Puntos de atributo que has conseguido y asignado. Asigna puntos usando la columna de Estilo de Personaje.",
"allocated": "Asignados",
"buffs": "Mejoras",
"buffsText": "Bonus temporales a los atributos dados por las habilidades y los logros. Éstos desaparecen cuando termina el día. Las habilidades que has desbloqueado aparecen en la lista de Recompensas de tu página de Tareas.",
- "characterBuild": "Estilo de Personaje",
+ "characterBuild": "Creación de personaje",
"class": "Clase",
"experience": "Experiencia",
"warrior": "Guerrero",
@@ -108,7 +109,8 @@
"rogue": "Pícaro",
"mage": "Mago",
"mystery": "Misterio",
- "changeClass": "Cambiar Clase, devolver puntos de atributo",
+ "changeClass": "Cambiar clase, devolver puntos de atributo",
+ "lvl10ChangeClass": "To change class you must be at least level 10.",
"levelPopover": "Con cada nivel consigues un punto para asignar a un atributo de tu elección. Lo puedes asignar manualmente o dejar que el juego decida por ti usando una de las opciones de Asignación Automática.",
"unallocated": "Puntos de Atributo no asignados",
"haveUnallocated": "Tienes <%= points %> Punto(s) de Atributo no asignado(s).",
@@ -132,7 +134,7 @@
"select": "Seleccionar",
"stealth": "Sigilo",
"stealthNewDay": "Cuando empiece un nuevo día, evitarás el daño de las Diarias sin cumplir.",
- "streaksFrozen": "Rachas Congeladas",
+ "streaksFrozen": "Rachas congeladas",
"streaksFrozenText": "No se reiniciarán las Rachas de las Diarias sin cumplir al final del día.",
"respawn": "¡Reaparecer!",
"youDied": "¡Has muerto!",
@@ -142,14 +144,14 @@
"notEnoughMana": "No tienes maná suficiente.",
"invalidTarget": "Objetivo inválido",
"youCast": "Lanzas <%= spell %>.",
- "youCastTarget": "Conjuras <%= spell %> sobre <%= target %>.",
- "youCastParty": "Conjuras <%= spell %> para el grupo.",
+ "youCastTarget": "Lanzas <%= spell %> sobre <%= target %>.",
+ "youCastParty": "Lanzas <%= spell %> para el grupo.",
"critBonus": "¡Golpe Crítico! Bonus:",
"displayNameDescription1": "Éste es el que aparecerá en los mensajes que publiques en la Taverna, Gremios o Chats de Grupo, junto con lo que se muestre en tu avatar. Ve a",
"displayNameDescription2": "Ajustes -> Sitio",
"displayNameDescription3": "y desplázate hacia abajo a la sección de Registro para cambiar tu nombre de usuario.",
- "unequipBattleGear": "Quitar Equipo de Batalla",
- "unequipCostume": "Quitar Disfraz",
+ "unequipBattleGear": "Quitar Equipamient de batalla",
+ "unequipCostume": "Quitar disfraz",
"unequipPetMountBackground": "Quitar Mascota, Montura, Fondo",
"animalSkins": "Pieles de animales",
"chooseClassHeading": "¡Elige tu Clase! U opta por no tener una, y elige luego.",
@@ -164,5 +166,7 @@
"int": "INT",
"showQuickAllocation": "Mostrar asignación de estadísticas",
"hideQuickAllocation": "Ocultar asignación de estadísticas",
- "quickAllocationLevelPopover": "Con cada nivel consigues un punto para asignar a un atributo de tu elección. Lo puedes asignar manualmente o dejar que el juego decida por ti usando una de las opciones de Asignación Automática que se encuentran en Usuario -> Estadísticas."
+ "quickAllocationLevelPopover": "Con cada nivel consigues un punto para asignar a un atributo de tu elección. Lo puedes asignar manualmente o dejar que el juego decida por ti usando una de las opciones de Asignación Automática que se encuentran en Usuario -> Estadísticas.",
+ "invalidAttribute": "\"<%= attr %>\" no es un atributo válido.",
+ "notEnoughAttrPoints": "No tienes suficientes Puntos de Atributo."
}
\ No newline at end of file
diff --git a/common/locales/es_419/communityguidelines.json b/common/locales/es_419/communityguidelines.json
index 46e79d17c0..831110e426 100644
--- a/common/locales/es_419/communityguidelines.json
+++ b/common/locales/es_419/communityguidelines.json
@@ -1,6 +1,6 @@
{
- "iAcceptCommunityGuidelines": "Acepto cumplir con las Normas de la Comunidad",
- "tavernCommunityGuidelinesPlaceholder": "Recordatorio amigable: este es un chat para todas las edades, ¡así que mantén un contenido y lenguaje apropiados! Consulta las Normas de la Comunidad debajo si tienes preguntas.",
+ "iAcceptCommunityGuidelines": "Acepto cumplir con las Normas de la comunidad",
+ "tavernCommunityGuidelinesPlaceholder": "Recordatorio amigable: este es un chat para todas las edades, ¡así que mantén un contenido y lenguaje apropiados! Consulta las Normas de la comunidad debajo si tienes preguntas.",
"commGuideHeadingWelcome": "¡Bienvenido a Habitica!",
"commGuidePara001": "¡Saludos aventurero! Bienvenido a Habitica, la tierra de productividad, vida saludable, y el ataque ocasional de un grifo. Tenemos una alegre comunidad llena de gente servicial brindando apoyo el uno al otro en su camino a la autosuperación.",
"commGuidePara002": "Para ayudar a mantener a todos seguros, felices, y productivos en la comunidad, nosotros tenemos algunas pautas. Las hemos diseñado cuidadosamente para hacerlas lo más amigables y fáciles de leer. Por favor toma el tiempo de leerlas.",
@@ -129,7 +129,7 @@
"commGuideList10E": "Degradación de los Niveles de Colaborador",
"commGuideList10F": "Poner a usuarios en \"Período de prueba\"",
"commGuideHeadingMinorConsequences": "Ejemplos de consecuencias menores",
- "commGuideList11A": "Recordatorios de las Normas del Espacio Público",
+ "commGuideList11A": "Recordatorios de las Normas del espacio público",
"commGuideList11B": "Advertencias",
"commGuideList11C": "Solicitudes",
"commGuideList11D": "Supresiones (los Mods/el Staff pueden borrar contenido problemático)",
@@ -140,25 +140,25 @@
"commGuidePara063": "Si no entiendes la naturaleza o consecuencias de tu infracción, pide ayuda al Staff/los Moderadores para que puedas evitar cometer infracciones en el futuro.",
"commGuideHeadingContributing": "Contribuyendo a Habitica",
"commGuidePara064": "Habitica es un proyecto de código abierto, ¡lo cual significa que cualquier Habiticano puede echar una mano! Los que lo hagan serán recompensados de acuerdo a los siguientes niveles de recompensas:",
- "commGuideList12A": "Medalla de Colaborador de Habitica, más 3 Gemas",
- "commGuideList12B": "Armadura de Colaborador, más 3 Gemas.",
- "commGuideList12C": "Yelmo de Colaborador, más 3 Gemas.",
- "commGuideList12D": "Espada de Colaborador, más 4 Gemas.",
- "commGuideList12E": "Escudo de Colaborador, más 4 Gemas.",
+ "commGuideList12A": "Medalla de colaborador de Habitica, añade 3 Gemas",
+ "commGuideList12B": "Armadura de colaborador, añade 3 Gemas.",
+ "commGuideList12C": "Casco de colaborador, añade 3 Gemas.",
+ "commGuideList12D": "Espada de colaborador, añade 4 Gemas.",
+ "commGuideList12E": "Escudo de colaborador, añade 4 Gemas.",
"commGuideList12F": "Mascota de Colaborador, más 4 Gemas.",
- "commGuideList12G": "Invitación al Gremio de Colaboradores, más 4 Gemas.",
- "commGuidePara065": "Los Mods son elegidos de entre colaboradores de Nivel Siete por el Staff y Moderadores preexistentes. Ten en cuenta que aunque los Colaboradores de Nivel Siete han trabajado duro en representación del sitio, no todos hablan con la autoridad de un Mod.",
- "commGuidePara066": "Hay algunas cosas importantes que mencionar sobre los Niveles de Colaborador:",
- "commGuideList13A": "Los Niveles son discrecionales. Son asignados a discreción de los Moderadores, en base a muchos factores, incluyendo nuestra percepción del trabajo que haces y su valor en la comunidad. Nos reservamos el derecho de cambiar los niveles, títulos y recompensas específicos a nuestra discreción.",
- "commGuideList13B": "Los Niveles se vuelven más difíciles a medida que progresas. Si creaste un monstruo, o arreglaste un pequeño fallo, eso puede ser suficiente para obtener tu primer nivel de colaborador, pero no para conseguir el siguiente. Como en cualquier buen RPG, ¡el aumento de nivel viene con un desafío más grande!",
- "commGuideList13C": "Los Niveles no \"vuelven a comenzar\" en cada campo. Cuando aumentamos la dificultad, tenemos en cuenta todas tus contribuciones, así gente que hace algo de arte, luego arregla un pequeño fallo o después incursiona un poco en la wiki no avanza más rápido que la gente que trabaja duro en una sola tarea. ¡Esto ayuda a mantener la imparcialidad!",
+ "commGuideList12G": "Invitación al Gremio de colaboradores, añade 4 Gemas.",
+ "commGuidePara065": "Los Mods son elegidos de entre colaboradores de nivel siete por el Staff y Moderadores preexistentes. Ten en cuenta que aunque los colaboradores de nivel siete han trabajado duro en representación del sitio, no todos hablan con la autoridad de un Mod.",
+ "commGuidePara066": "Hay algunas cosas importantes que mencionar sobre los niveles de colaborador:",
+ "commGuideList13A": "Los niveles son discrecionales. Son asignados a discreción de los Moderadores, en base a muchos factores, incluyendo nuestra percepción del trabajo que haces y su valor en la comunidad. Nos reservamos el derecho de cambiar los niveles, títulos y recompensas específicos a nuestra discreción.",
+ "commGuideList13B": "Los niveles se vuelven más difíciles a medida que progresas. Si creaste un monstruo, o arreglaste un pequeño fallo, eso puede ser suficiente para obtener tu primer nivel de colaborador, pero no para conseguir el siguiente. Como en cualquier buen RPG, ¡el aumento de nivel viene con un desafío más grande!",
+ "commGuideList13C": "Los niveles no \"vuelven a comenzar\" en cada campo. Cuando aumentamos la dificultad, tenemos en cuenta todas tus contribuciones, así gente que hace algo de arte, luego arregla un pequeño fallo o después incursiona un poco en la wiki no avanza más rápido que la gente que trabaja duro en una sola tarea. ¡Esto ayuda a mantener la imparcialidad!",
"commGuideList13D": "Los usuarios en período de prueba no pueden ser promovidos al siguiente nivel. Los Mods tienen el derecho de congelar el avance del usuario debido a infracciones. Si esto ocurre, el usuario siempre será informado de la decisión, y de cómo corregirla. Los niveles también pueden ser removidos como resultado de infracciones o del período de prueba.",
- "commGuideHeadingFinal": "La Sección Final",
- "commGuidePara067": "Así que aquí las tienes, valiente Habiticano -- ¡las Normas de la Comunidad! Sécate el sudor de la frente y regálate algunos PX por haber leído todo. Si tienes cualquier pregunta o duda acerca de estas Normas de la Comunidad, por favor envía un email a Lemoness (leslie@habitica.com) y ella estará feliz de ayudarte a esclarecer dudas.",
+ "commGuideHeadingFinal": "La sección final",
+ "commGuidePara067": "Así que aquí las tienes, valiente Habiticano -- ¡las normas de la comunidad! Sécate el sudor de la frente y regálate algunos PX por haber leído todo. Si tienes cualquier pregunta o duda acerca de estas normas de la comunidad, por favor envía un email a Lemoness (leslie@habitica.com) y ella estará feliz de ayudarte a esclarecer dudas.",
"commGuidePara068": "Ahora ¡pónte en marcha, valiente aventurero, y vence algunas Diarias!",
"commGuideHeadingLinks": "Links útiles",
"commGuidePara069": "Los siguientes artistas talentosos contribuyeron a estas ilustraciones:",
- "commGuideLink01": "El Gremio de los Novatos",
+ "commGuideLink01": "El Gremio de los novatos",
"commGuideLink01description": "¡un gremio para nuevos usuarios para hacer preguntas!",
"commGuideLink02": "El Gremio la Trastienda",
"commGuideLink02description": "un gremio para la discusión de temas largos o sensibles.",
@@ -166,13 +166,13 @@
"commGuideLink03description": "la colección más grande de información sobre Habitica.",
"commGuideLink04": "GitHub",
"commGuideLink04description": "¡para informes de fallos o programas útiles!",
- "commGuideLink05": "El Trello Principal",
+ "commGuideLink05": "El Trello principal",
"commGuideLink05description": "para solicitar características del sitio.",
- "commGuideLink06": "El Trello Móvil",
+ "commGuideLink06": "El Trello móvil",
"commGuideLink06description": "para solicitar características de la app.",
- "commGuideLink07": "El Trello de Arte",
+ "commGuideLink07": "El Trello de arte",
"commGuideLink07description": "para enviar pixel art.",
- "commGuideLink08": "El Trello de Misiones",
+ "commGuideLink08": "El Trello de misiones",
"commGuideLink08description": "para enviar escritos para misiones.",
"lastUpdated": "Actualizado por última vez"
}
\ No newline at end of file
diff --git a/common/locales/es_419/content.json b/common/locales/es_419/content.json
index baf6125fd8..ec04bf5d93 100644
--- a/common/locales/es_419/content.json
+++ b/common/locales/es_419/content.json
@@ -1,5 +1,5 @@
{
- "potionText": "Poción de Salud",
+ "potionText": "Poción curativa",
"potionNotes": "Recuperar 15 de Salud (Uso instantáneo)",
"armoireText": "Armario Encantado",
"armoireNotesFull": "¡Abre el Armario para recibir aleatoriamente equipamiento especial, experiencia o comida! Artículos de equipamiento restantes:",
@@ -8,20 +8,20 @@
"dropEggWolfText": "Lobo",
"dropEggWolfMountText": "Lobo",
"dropEggWolfAdjective": "un leal",
- "dropEggTigerCubText": "Cachorro de Tigre",
+ "dropEggTigerCubText": "Cachorro de tigre",
"dropEggTigerCubMountText": "Tigre",
"dropEggTigerCubAdjective": "un feroz",
- "dropEggPandaCubText": "Cachorro de Panda",
+ "dropEggPandaCubText": "Cachorro de panda",
"dropEggPandaCubMountText": "Panda",
"dropEggPandaCubAdjective": "un amable",
- "dropEggLionCubText": "Cachorro de León",
+ "dropEggLionCubText": "Cachorro de león",
"dropEggLionCubMountText": "León",
"dropEggLionCubAdjective": "un majestuoso",
"dropEggFoxText": "Zorro",
"dropEggFoxMountText": "Zorro",
"dropEggFoxAdjective": "un astuto",
- "dropEggFlyingPigText": "Cerdo Volador",
- "dropEggFlyingPigMountText": "Cerdo Volador",
+ "dropEggFlyingPigText": "Cerdo volador",
+ "dropEggFlyingPigMountText": "Cerdo volador",
"dropEggFlyingPigAdjective": "un caprichoso",
"dropEggDragonText": "Dragón",
"dropEggDragonMountText": "Dragón",
@@ -29,7 +29,7 @@
"dropEggCactusText": "Cactus",
"dropEggCactusMountText": "Cactus",
"dropEggCactusAdjective": "un espinoso",
- "dropEggBearCubText": "Cachorro de Oso",
+ "dropEggBearCubText": "Cachorro de oso",
"dropEggBearCubMountText": "Oso",
"dropEggBearCubAdjective": "un valiente",
"questEggGryphonText": "Grifo",
@@ -42,7 +42,7 @@
"questEggDeerMountText": "Ciervo",
"questEggDeerAdjective": "un elegante",
"questEggEggText": "Huevo",
- "questEggEggMountText": "Cesto de Huevos",
+ "questEggEggMountText": "Cesto de huevos",
"questEggEggAdjective": "un colorido",
"questEggRatText": "Rata",
"questEggRatMountText": "Rata",
@@ -50,8 +50,8 @@
"questEggOctopusText": "Pulpo",
"questEggOctopusMountText": "Pulpo",
"questEggOctopusAdjective": "un resbaladizo",
- "questEggSeahorseText": "Caballito de Mar",
- "questEggSeahorseMountText": "Caballito de Mar",
+ "questEggSeahorseText": "Caballito de mar",
+ "questEggSeahorseMountText": "Caballito de mar",
"questEggSeahorseAdjective": "un increíble",
"questEggParrotText": "Loro",
"questEggParrotMountText": "Loro",
@@ -77,8 +77,8 @@
"questEggBunnyText": "Conejito",
"questEggBunnyMountText": "Conejito",
"questEggBunnyAdjective": "un mimoso",
- "questEggSlimeText": "Baba de Malvavisco",
- "questEggSlimeMountText": "Baba de Malvavisco",
+ "questEggSlimeText": "Baba de malvavisco",
+ "questEggSlimeMountText": "Baba de malvavisco",
"questEggSlimeAdjective": "una dulce",
"questEggSheepText": "Oveja",
"questEggSheepMountText": "Oveja",
@@ -102,10 +102,10 @@
"questEggSnakeMountText": "Serpiente",
"questEggSnakeAdjective": "una escurridiza",
"questEggUnicornText": "Unicornio",
- "questEggUnicornMountText": "Unicornio Alado",
+ "questEggUnicornMountText": "Unicornio alado",
"questEggUnicornAdjective": "un mágico",
- "questEggSabretoothText": "Dientes de Sable",
- "questEggSabretoothMountText": "Dientes de Sable",
+ "questEggSabretoothText": "Diente de sable",
+ "questEggSabretoothMountText": "Diente de sable",
"questEggSabretoothAdjective": "un feroz",
"questEggMonkeyText": "Mono",
"questEggMonkeyMountText": "Mono",
@@ -113,19 +113,26 @@
"questEggSnailText": "Caracol",
"questEggSnailMountText": "Caracol",
"questEggSnailAdjective": "un lento pero firme",
+ "questEggFalconText": "Halcón",
+ "questEggFalconMountText": "Halcón",
+ "questEggFalconAdjective": "un veloz",
+ "questEggTreelingText": "Arbolito",
+ "questEggTreelingMountText": "Arbolito",
+ "questEggTreelingAdjective": "un frondoso",
"eggNotes": "Encuentra una poción de eclosión para verter sobre este huevo y se convertirá en <%= eggAdjective(locale) %> <%= eggText(locale) %>.",
- "hatchingPotionBase": "Básic@",
- "hatchingPotionWhite": "Blanc@",
+ "hatchingPotionBase": "Básico",
+ "hatchingPotionWhite": "Blanco",
"hatchingPotionDesert": "Desierto",
- "hatchingPotionRed": "Roj@",
+ "hatchingPotionRed": "Rojo",
"hatchingPotionShade": "Sombra",
"hatchingPotionSkeleton": "Esqueleto",
"hatchingPotionZombie": "Zombi",
- "hatchingPotionCottonCandyPink": "Algodón de Azúcar Rosa",
- "hatchingPotionCottonCandyBlue": "Algodón de Azúcar Azul",
- "hatchingPotionGolden": "Dorad@",
+ "hatchingPotionCottonCandyPink": "Algodón de azúcar rosa",
+ "hatchingPotionCottonCandyBlue": "Algodón de azúcar azul",
+ "hatchingPotionGolden": "Dorado",
"hatchingPotionSpooky": "Escalofriante",
"hatchingPotionPeppermint": "Menta",
+ "hatchingPotionFloral": "Floral",
"hatchingPotionNotes": "Vierte esto sobre un huevo, y nacerá una mascota <%= potText(locale) %>.",
"premiumPotionAddlNotes": "No se puede usar con huevos de mascotas de misión.",
"foodMeat": "Carne",
@@ -135,17 +142,17 @@
"foodChocolate": "Chocolate",
"foodFish": "Pescado",
"foodRottenMeat": "Carne Podrida",
- "foodCottonCandyPink": "Algodón de Azúcar Rosa",
- "foodCottonCandyBlue": "Algodón de Azúcar Azul",
+ "foodCottonCandyPink": "Algodón de azúcar rosa",
+ "foodCottonCandyBlue": "Algodón de azúcar azul",
"foodHoney": "Miel",
- "foodCakeSkeleton": "Pastel de Huesos",
- "foodCakeBase": "Pastel Básico",
- "foodCakeCottonCandyBlue": "Pastel de Caramelo Azul",
- "foodCakeCottonCandyPink": "Pastel de Caramelo Rosa",
- "foodCakeShade": "Pastel de Chocolate",
- "foodCakeWhite": "Pastel de Crema",
- "foodCakeGolden": "Pastel de Miel",
- "foodCakeZombie": "Pastel Podrido",
+ "foodCakeSkeleton": "Pastel de huesos",
+ "foodCakeBase": "Pastel básico",
+ "foodCakeCottonCandyBlue": "Pastel de caramelo azul",
+ "foodCakeCottonCandyPink": "Pastel de caramelo rosa",
+ "foodCakeShade": "Pastel de chocolate",
+ "foodCakeWhite": "Pastel de crema",
+ "foodCakeGolden": "Pastel de miel",
+ "foodCakeZombie": "Pastel podrido",
"foodCakeDesert": "Pastel de Arena",
"foodCakeRed": "Pastel de Fresa",
"foodCandySkeleton": "Caramelo de Huesos",
diff --git a/common/locales/es_419/contrib.json b/common/locales/es_419/contrib.json
index 0d6d42820c..26a34553c7 100644
--- a/common/locales/es_419/contrib.json
+++ b/common/locales/es_419/contrib.json
@@ -1,13 +1,13 @@
{
"friend": "Amigo",
"friendFirst": "Cuando se implemente tu primera contribución, recibirás la medalla de Colaborador de Habitica. Tu nombre en el chat de la Taberna mostrará orgullosamente que eres un colaborador. Como premio por tu trabajo también recibirás 3 Gemas.",
- "friendSecond": "Cuando se implemente tu segunda contribución, la Armadura de Cristal estará disponible en la tienda de Recompensas. Como premio por tu trabajo continuo también recibirás 3 Gemas.",
+ "friendSecond": "Cuando se implemente tu segunda contribución, la Armadura de cristal estará disponible en la tienda de Recompensas. Como premio por tu trabajo continuo también recibirás 3 Gemas.",
"elite": "Élite",
- "eliteThird": "Cuando se implemente tu tercera contribución, el Casco de Cristal estará disponible en la tienda de Recompensas. Como premio por tu trabajo continuo también recibirás 3 Gemas.",
- "eliteFourth": "Cuando se implemente tu cuarta contribución, la Espada de Cristal estará disponible en la tienda de Recompensas. Como premio por tu trabajo continuo también recibirás 4 Gemas.",
+ "eliteThird": "Cuando se implemente tu tercera contribución, el Casco de cristal estará disponible en la tienda de Recompensas. Como premio por tu trabajo continuo también recibirás 3 Gemas.",
+ "eliteFourth": "Cuando se implemente tu cuarta contribución, la Espada de cristal estará disponible en la tienda de Recompensas. Como premio por tu trabajo continuo también recibirás 4 Gemas.",
"champion": "Campeón",
- "championFifth": "Cuando se implemente tu quinta contribución, el Escudo de Cristal estará disponible en la tienda de Recompensas. Como premio por tu trabajo continuo también recibirás 4 Gemas.",
- "championSixth": "Cuando se implemente tu sexta contribución, recibirás una Mascota Hidra. También recibirás 4 Gemas.",
+ "championFifth": "Cuando se implemente tu quinta contribución, el Escudo de cristal estará disponible en la tienda de Recompensas. Como premio por tu trabajo continuo también recibirás 4 Gemas.",
+ "championSixth": "Cuando se implemente tu sexta contribución, recibirás una Mascota hidra. También recibirás 4 Gemas.",
"legendary": "Legendario",
"legSeventh": "Cuando se implemente tu séptima contribución, ¡recibirás 4 Gemas y pasarás a formar parte del honorable Gremio de Colaboradores y estarás al tanto de los detalles tras bambalinas de Habitica! Las contribuciones posteriores no incrementarán tu nivel, pero podrás seguir ganando Gemas y títulos.",
"moderator": "Moderador",
@@ -22,43 +22,47 @@
"contribLink": "los premios que has ganado por tu colaboración!",
"contribName": "Colaborador",
"contribText": "Ha contribuido a Habitica (código, diseño, arte, asesoramiento legal, etc.). ¿Quieres esta medalla?",
- "readMore": "Leer Más",
+ "readMore": "Leer más",
"kickstartName": "Sponsor de Kickstarter - Nivel $<%= tier %>",
"kickstartText": "Respaldó el proyecto Kickstarter",
"helped": "Ayudó con el crecimiento de Habit",
"helpedText1": "Ayudó a mejorar Habitica completando",
"helpedText2": "esta encuesta.",
- "hall": "Salón de Héroes",
+ "hall": "Salón de héroes",
"contribTitle": "Título de colaborador (p ej, \"Herrero\")",
"contribLevel": "Nivel de colaborador",
"contribHallText": "1-7 para los colaboradores normales, 8 para moderadores, 9 para el personal. Esto determina cuáles artículos, mascotas y monturas están disponibles. También determina el color de la etiqueta de nombre. A los de nivel 8 y 9 se les da estatus de administrador automáticamente.",
- "hallContributors": "Salón de Colaboradores",
- "hallPatrons": "Salón de Patrocinadores",
+ "hallContributors": "Salón de colaboradores",
+ "hallPatrons": "Salón de patrocinadores",
"rewardUser": "Recompensar usuario",
- "UUID": "UUID",
+ "UUID": "ID de usuario",
"loadUser": "Cargar usuario",
+ "noAdminAccess": "No tienes acceso de administrador.",
+ "pageMustBeNumber": "req.query.page debe ser un número.",
+ "userNotFound": "Usuario no encontrado.",
+ "invalidUUID": "UUID debe ser válido.",
"title": "Título",
"moreDetails": "Más detalles (1-7)",
"moreDetails2": "más detalles (8-9)",
"contributions": "Contribuciones",
"admin": "Administrador",
"notGems": "es en dólares, no en Gemas. Es decir, si este número es 1 significa 4 gemas. Utiliza esta opción sólo cuando otorgas gemas a los jugadores de forma manual, no lo uses al otorgar niveles de colaborador. Los niveles de colaborador agregarán gemas automáticamente.",
- "gamemaster": "Maestro del Juego (personal/moderador)",
+ "gamemaster": "Maestro del juego (personal/moderador)",
"backerTier": "Nivel de sponsor",
"balance": "Saldo",
"tierPop": "Haz clic en las etiquetas de nivel para los detalles.",
"playerTiers": "Niveles de jugador",
"tier": "Nivel",
- "visitHeroes": "Visita el Salón de Héroes (colaboradores y patrocinadores)",
+ "visitHeroes": "Visita el Salón de héroes (colaboradores y patrocinadores)",
"conLearn": "Aprende más acerca de las recompensas de colaborador",
"conLearnHow": "Aprende cómo contribuir a Habitica",
- "surveysSingle": "Ayudó a mejorar Habitica llenando una encuesta. No hay encuestas activas.",
- "surveysMultiple": "Ayudó a mejorar Habitica llenando <%= surveys %> encuestas. No hay encuestas activas.",
+ "surveysSingle": "Helped Habitica grow, either by filling out a survey or helping with a major testing effort. Thank you!",
+ "surveysMultiple": "Helped Habitica grow on <%= surveys %> occasions, either by filling out a survey or helping with a major testing effort. Thank you!",
"currentSurvey": "Encuesta actual",
"surveyWhen": "La medalla será otorgada a todos los participantes cuando las encuestas sean procesadas, a finales de Marzo.",
"blurbInbox": "¡Aquí es donde se guardan tus mensajes privados! Puedes enviarle un mensaje a alguien simplemente haciendo clic en el sobre al lado de su nombre en el chat de la Taberna, Equipo o Gremio. Si has recibido un MP inapropiado, deberías enviar una captura de pantalla a Lemoness (leslie@habitica.com)",
"blurbGuildsPage": "Los Gremios son salas de chat con temas de interés común creados por jugadores para jugadores. ¡Echa un vistazo a la lista y únete a los Gremios que te interesen!",
"blurbChallenges": "Los Desafíos son creados por los usuarios. Al unirte a un Desafío éste añadirá tareas a tu cuenta, ¡y si ganas el Desafío obtendrás un logro y algunas veces gemas!",
- "blurbHallPatrons": "Éste es el Salón de Patrocinadores, donde honramos a los nobles aventureros que apoyaron al Kickstarter original de Habitica. ¡Les agradecemos por ayudarnos a dar vida a Habitica!",
- "blurbHallContributors": "Éste es el Salón de Colaboradores, donde los colaboradores del código abierto de Habitica son honrados. Ya sea a través de código, arte, música, escritura o simplemente ayudando, ellos han obtenido gemas, equipamento exclusivo y títulos prestigiosos. ¡Tú también puedes contribuir! Averigua más aquí. "
+ "blurbHallPatrons": "Éste es el Salón de patrocinadores, donde honramos a los nobles aventureros que apoyaron al Kickstarter original de Habitica. ¡Les agradecemos por ayudarnos a dar vida a Habitica!",
+ "blurbHallContributors": "Éste es el Salón de colaboradores, donde los colaboradores del código abierto de Habitica son honrados. Ya sea a través de código, arte, música, escritura o simplemente ayudando, ellos han obtenido gemas, equipamento exclusivo y títulos prestigiosos. ¡Tú también puedes contribuir! Averigua más aquí. "
}
\ No newline at end of file
diff --git a/common/locales/es_419/death.json b/common/locales/es_419/death.json
index d8db55e6ae..0ef87ab713 100644
--- a/common/locales/es_419/death.json
+++ b/common/locales/es_419/death.json
@@ -1,16 +1,17 @@
{
- "lostAllHealth": "¡Te quedaste sin Salud!",
+ "lostAllHealth": "¡Te quedaste sin salud!",
"dontDespair": "¡No desesperes!",
"deathPenaltyDetails": "Perdiste un Nivel, tu Oro y un artículo de Equipamiento, ¡pero puedes recuperarlos trabajando duro! Buena suerte -- lo harás muy bien.",
- "refillHealthTryAgain": "Recuperar Salud e intentar otra vez",
+ "refillHealthTryAgain": "Recuperar salud e intentar de nuevo",
"dyingOftenTips": "¿Te pasa seguido? ¡Aquí hay algunos consejos!",
- "losingHealthWarning": "Cuidado - ¡Estás perdiendo Salud!",
- "losingHealthWarning2": "¡No dejes que tu Salud llegue a cero! Si esto sucede, perderás un nivel, tu oro y un artículo de equipamiento.",
- "toRegainHealth": "Para recuperar Salud:",
+ "losingHealthWarning": "Cuidado - ¡Estás perdiendo salud!",
+ "losingHealthWarning2": "¡No dejes que tu salud llegue a cero! Si esto sucede, perderás un nivel, tu Oro y un artículo de equipamiento.",
+ "toRegainHealth": "Para recuperar salud:",
"lowHealthTips1": "¡Sube de nivel para sanar completamente!",
- "lowHealthTips2": "Compra una Poción de Salud de la columna de Recompensas para recuperar 15 Puntos de Vida.",
- "losingHealthQuickly": "¿Estás perdiendo Salud rápidamente?",
+ "lowHealthTips2": "Compra una Poción desSalud de la columna de Recompensas para recuperar 15 Puntos de vida.",
+ "losingHealthQuickly": "¿Estás perdiendo salud rápidamente?",
"lowHealthTips3": "Las Diarias incompletas te hacen daño por la noche, así que ¡sé cuidadoso de no añadir demasiadas al principio!",
"lowHealthTips4": "Si una Diaria no vence en un día en específico, puedes desactivarla haciendo clic en el ícono del lápiz.",
- "goodLuck": "¡Buena suerte!"
+ "goodLuck": "¡Buena suerte!",
+ "cannotRevive": "No puede ser revivido si no está muerto"
}
\ No newline at end of file
diff --git a/common/locales/es_419/front.json b/common/locales/es_419/front.json
index e4622a1af5..4284b98fab 100644
--- a/common/locales/es_419/front.json
+++ b/common/locales/es_419/front.json
@@ -28,6 +28,7 @@
"communityReddit": "Reddit",
"companyAbout": "Cómo funciona",
"companyBlog": "Blog",
+ "devBlog": "Blog de Desarrollador",
"companyDonate": "Donar",
"companyExtensions": "Extensiones",
"companyPrivacy": "Privacidad",
@@ -51,6 +52,7 @@
"featureSocialHeading": "Juego social",
"featuredIn": "Como lo viste en",
"featuresHeading": "También contamos con...",
+ "footerDevs": "Desarrolladores",
"footerCommunity": "Comunidad",
"footerCompany": "Compañía",
"footerMobile": "Móvil",
@@ -182,6 +184,7 @@
"zelahQuote": "¡Con [Habitica] puedo ser persuadido de ir a la cama a tiempo con la idea de ganar puntos por acostarme temprano o perder salud al acostarme tarde!",
"reportAccountProblems": "Reportar problemas con tu cuenta",
"reportCommunityIssues": "Reportar problemas en la comunidad",
+ "subscriptionPaymentIssues": "Problemas de Suscripción y Pago",
"generalQuestionsSite": "Preguntas generales acerca del sitio",
"businessInquiries": "Preguntas sobre negocios",
"merchandiseInquiries": "Preguntas sobre marketing y merchandising",
@@ -222,5 +225,32 @@
"altAttrWebstorm": "WebStorm",
"altAttrGithub": "GitHub",
"altAttrTrello": "Trello",
- "altAttrSlack": "Slack"
+ "altAttrSlack": "Slack",
+ "missingAuthHeaders": "Faltan encabezados de autenticación.",
+ "missingAuthParams": "Faltan parámetros de autenticación.",
+ "missingUsernameEmail": "Faltan el correo electrónico o nombre de usuario.",
+ "missingEmail": "Falta el correo electrónico.",
+ "missingUsername": "Falta el nombre de usuario.",
+ "missingPassword": "Falta la contraseña.",
+ "missingNewPassword": "Falta la contraseña nueva.",
+ "wrongPassword": "Contraseña incorrecta.",
+ "notAnEmail": "Dirección de correo electrónico inválida.",
+ "emailTaken": "Esta dirección de correo electrónico ya está en uso.",
+ "newEmailRequired": "Missing new email address.",
+ "usernameTaken": "Username already taken.",
+ "passwordConfirmationMatch": "Password confirmation doesn't match password.",
+ "invalidLoginCredentials": "Incorrect username and/or email and/or password.",
+ "passwordReset": "If we have your email on file, your password reset link has been sent to your email.",
+ "passwordResetEmailSubject": "Password Reset for Habitica",
+ "passwordResetEmailText": "Password for <%= username %> has been reset to <%= newPassword %> . Important! Both username and password are case-sensitive -- you must enter both exactly as shown here. We recommend copying and pasting both instead of typing them. Log in at <%= baseUrl %>. After you have logged in, head to <%= baseUrl %>/#/options/settings/settings and change your password.",
+ "passwordResetEmailHtml": "Password for <%= username %> has been reset to <%= newPassword %>.
Important! Both username and password are case-sensitive -- you must enter both exactly as shown here. We recommend copying and pasting both instead of typing them.
Log in at <%= baseUrl %>. After you have logged in, head to <%= baseUrl %>/#/options/settings/settings and change your password.",
+ "invalidLoginCredentialsLong": "Uh-oh - your username or password is incorrect.\n- Make sure your username or email is typed correctly.\n- You may have signed up with Facebook, not email. Double-check by trying Facebook login.\n- If you forgot your password, click \"Forgot Password\".",
+ "invalidCredentials": "There is no account that uses those credentials.",
+ "accountSuspended": "Account has been suspended, please contact leslie@habitica.com with your User ID \"<%= userId %>\" for assistance.",
+ "onlyFbSupported": "Only Facebook is supported currently.",
+ "cantDetachFb": "Account lacks another authentication method, can't detach Facebook.",
+ "onlySocialAttachLocal": "Local authentication can be added to only a social account.",
+ "invalidReqParams": "Invalid request parameters.",
+ "memberIdRequired": "\"member\" must be a valid UUID.",
+ "heroIdRequired": "\"heroId\" must be a valid UUID."
}
\ No newline at end of file
diff --git a/common/locales/es_419/gear.json b/common/locales/es_419/gear.json
index 771ec35730..69bd990d1a 100644
--- a/common/locales/es_419/gear.json
+++ b/common/locales/es_419/gear.json
@@ -143,14 +143,14 @@
"weaponSpecialWinter2016MageNotes": "¡Tus movimientos son tan geniales que debe ser magia! Incrementa la Inteligencia por <%= int %> y la Percepción por <%= per %>. Equipamiento de Edición Limitada de Invierno 2015-2016.",
"weaponSpecialWinter2016HealerText": "Cañón de Confeti",
"weaponSpecialWinter2016HealerNotes": "¡¡¡¡¡¡¡WIIIIIIIIIIIIIIIII!!!!!!! ¡¡¡¡¡¡¡FELIZ WINTER WONDERLAND!!!!!!! Incrementa la Inteligencia por <%= int %>. Equipamiento de Edición Limitada de Invierno 2015-2016.",
- "weaponSpecialSpring2016RogueText": "Maza de Fuego",
- "weaponSpecialSpring2016RogueNotes": "Usted ha dominado el balón, el club, y el cuchillo. Ahora se avanza al malabares con fuego! Awoo! Aumenta la fuerza <% =% str>. Equipamiento de la Primavera, edición limitada 2016.",
- "weaponSpecialSpring2016WarriorText": "Maza de Queso",
- "weaponSpecialSpring2016WarriorNotes": "Nadie tiene tantos amigos como un ratón con quesos tiernos. Incrementa la Fuerza por <%= str %>. Edición Limitada de Verano 2016.",
+ "weaponSpecialSpring2016RogueText": "Boleadoras de Fuego",
+ "weaponSpecialSpring2016RogueNotes": "Has dominado el balón, el garrote y el cuchillo. ¡Ahora avanza y haz malabares con fuego! ¡Awoo! Incrementan la Fuerza por <%= str %>. Equipamiento de Edición Limitada de Primavera 2016.",
+ "weaponSpecialSpring2016WarriorText": "Mazo de Queso",
+ "weaponSpecialSpring2016WarriorNotes": "Nadie tiene tantos amigos como un ratón con quesos tiernos. Incrementa la Fuerza por <%= str %>. Equipamiento de Edición Limitada de Primavera 2016.",
"weaponSpecialSpring2016MageText": "Báculo de Campanas",
- "weaponSpecialSpring2016MageNotes": "¡Abracadabra! Tan deslumbrante, es posible hipnotizar a ti mismo! Oh ... esto tintinea ... aumenta la Inteligencia por <% = int%> y Percepción por <% =% por>. Equipamiento de la Primavera, edición limitada 2016.",
+ "weaponSpecialSpring2016MageNotes": "¡Abragatabra! ¡Tan deslumbrante que puedes llegar a hipnotizarte a ti mismo! Ooh... tintinea... Incrementa la Inteligencia por <%= Int %> y la Percepción por <%= per %>. Equipamiento de Edición Limitada de Primavera 2016.",
"weaponSpecialSpring2016HealerText": "Varita de Flores de la Primavera",
- "weaponSpecialSpring2016HealerNotes": "Con un ademán de la mano y un guiño, ¡haces florecer los campos y bosques! O golpeas la cabeza de ratones. Incrementa la Inteligencia por <%= int %>. Equipo de Edición limitada de Primavera 2016.",
+ "weaponSpecialSpring2016HealerNotes": "Con un ademán de la mano y un guiño, ¡haces florecer los campos y bosques! O das coscorrones a ratones molestos. Incrementa la Inteligencia por <%= int %>. Equipamiento de Edición Limitada de Primavera 2016.",
"weaponMystery201411Text": "Horqueta de Banquete",
"weaponMystery201411Notes": "Atraviesa a tus enemigos o ataca tu comida favorita - ¡esta horqueta versátil lo hace todo! No otorga ningún beneficio. Artículo de Suscriptor de Noviembre 2014.",
"weaponMystery201502Text": "Reluciente Báculo Alado del Amor y También de la Verdad",
@@ -187,8 +187,10 @@
"weaponArmoireJesterBatonNotes": "Con una sacudida de tu bastón y una conversación ingeniosa, hasta las situaciones más complicadas se pueden calmar. Incrementa la Inteligencia y la Percepción por <%= attrs %> cada una. Armario Encantado: Conjunto de Bufón (Artículo 3 de 3).",
"weaponArmoireMiningPickaxText": "Pico de Minero",
"weaponArmoireMiningPickaxNotes": "¡Extrae la máxima cantidad de oro de tus tareas! Incrementa la Percepción por <%= per %>. Armario Encantado: Conjunto de Minero (Artículo 3 de 3).",
- "weaponArmoireBasicLongbowText": "Basic Longbow",
- "weaponArmoireBasicLongbowNotes": "A serviceable hand-me-down bow. Increases Strength by <%= str %>. Enchanted Armoire: Basic Archer Set (Item 1 of 3).",
+ "weaponArmoireBasicLongbowText": "Arco Largo Básico",
+ "weaponArmoireBasicLongbowNotes": "Un arco útil de segunda mano. Incrementa la Fuerza por <%=str%>. Armario Encantado: Conjunto Básico de Arquero (Artículo 1 de 3).",
+ "weaponArmoireHabiticanDiplomaText": "Diploma Habiticano",
+ "weaponArmoireHabiticanDiplomaNotes": "A certificate of significant achievement -- well done! Increases Intelligence by <%= int %>. Enchanted Armoire: Graduate Set (Item 1 of 3).",
"armor": "armadura",
"armorBase0Text": "Ropa Simple",
"armorBase0Notes": "Ropa común. No otorga ningún beneficio.",
@@ -320,14 +322,14 @@
"armorSpecialWinter2016MageNotes": "El mago más sabio se mantiene bien abrigado en el viento invernal. Incrementa la Inteligencia por <%= int %>. Equipamiento de Edición Limitada de Invierno 2015-2016.",
"armorSpecialWinter2016HealerText": "Capa de Hada Festiva",
"armorSpecialWinter2016HealerNotes": "Las Hadas Festivas envuelven sus alas corporales alrededor suyo como protección mientras usan las alas de su cabeza para remontar vientos en contra y volar sobre Habitica a velocidades de hasta 100 millas por hora, repartiendo regalos y cubriendo a todos con confeti. Qué gracioso. Incrementa la Constitución por <%= con %>. Equipamiento de Edición Limitada de Invierno 2015-2016.",
- "armorSpecialSpring2016RogueText": "Traje de camuflaje canino",
- "armorSpecialSpring2016RogueNotes": "Un cachorro inteligente sabe escoger un atuendo más brillante para pasar desapercibido cuando todo es verde y vibrante. Incrementa la Percepción por <%= per %>. Equipamiento de Edición Limitada de Primavera 2016.",
- "armorSpecialSpring2016WarriorText": "Cota de malla poderosa",
- "armorSpecialSpring2016WarriorNotes": "Though you be but little, you are fierce! Increases Constitution by <%= con %>. Limited Edition 2016 Spring Gear.",
- "armorSpecialSpring2016MageText": "Magnífica Túnica Raído",
- "armorSpecialSpring2016MageNotes": "Brightly colored, so you won't be mistaken for a necromouser. Increases Intelligence by <%= int %>. Limited Edition 2016 Spring Gear.",
- "armorSpecialSpring2016HealerText": "Pantalones de Conejito Mullido",
- "armorSpecialSpring2016HealerNotes": "Hippity hop! Bound from hill to hill, healing those in need. Increases Constitution by <%= con %>. Limited Edition 2016 Spring Gear.",
+ "armorSpecialSpring2016RogueText": "Traje de Camuflaje Canino",
+ "armorSpecialSpring2016RogueNotes": "Un cachorro inteligente sabe escoger un disfraz más vívido para pasar desapercibido cuando todo es verde y vibrante. Incrementa la Percepción por <%= per %>. Equipamiento de Edición Limitada de Primavera 2016.",
+ "armorSpecialSpring2016WarriorText": "Cota de Malla Poderosa",
+ "armorSpecialSpring2016WarriorNotes": "¡A pesar de ser pequeño, eres feroz! Incrementa la Constitución por <%= con %>. Equipamiento de Edición Limitada de Primavera 2016.",
+ "armorSpecialSpring2016MageText": "Gran Túnica Felina",
+ "armorSpecialSpring2016MageNotes": "Con colores vivos, para que no te confundan con un necromante. Incrementa la Inteligencia por <%= int %>. Equipamiento de Edición Limitada de Primavera 2016.",
+ "armorSpecialSpring2016HealerText": "Pantalones Peludos de Conejito",
+ "armorSpecialSpring2016HealerNotes": "¡Boing boing! Saltando de colina en colina, sanando a quienes lo necesiten. Incrementan la Constitución por <%= con %>. Equipamiento de Edición Limitada de Primavera 2016.",
"armorMystery201402Text": "Túnica de Mensajero",
"armorMystery201402Notes": "Reluciente y fuerte, esta túnica tiene muchos bolsillos para llevar cartas. No otorga ningún beneficio. Artículo de Suscriptor de Febrero 2014.",
"armorMystery201403Text": "Armadura de Caminante del Bosque",
@@ -363,7 +365,11 @@
"armorMystery201512Text": "Armadura de Fuego Frío",
"armorMystery201512Notes": "¡Invoca a las heladas llamas invernales! No otorga ningún beneficio. Artículo de Suscriptor de Diciembre 2015.",
"armorMystery201603Text": "Traje de la Suerte",
- "armorMystery201603Notes": "¡El traje está hecho de miles de tréboles de cuatro hojas! No confiere ningún beneficio. Artículo de Suscriptor de Marzo 2016.",
+ "armorMystery201603Notes": "¡Este traje está hecho de miles de tréboles de cuatro hojas! No otorga ningún beneficio. Artículo de Suscriptor de Marzo 2016.",
+ "armorMystery201604Text": "Armadura de Hojas",
+ "armorMystery201604Notes": "You, too, can be a small but fearsome leaf puff. Confers no benefit. April 2016 Subscriber Item.",
+ "armorMystery201605Text": "Marching Bard Uniform",
+ "armorMystery201605Notes": "Unlike the traditional bards who join adventuring parties, bards who join Habitican marching bands are known for grand parades, not dungeon raids. Confers no benefit. May 2016 Subscriber Item.",
"armorMystery301404Text": "Traje Steampunk",
"armorMystery301404Notes": "¡Sofisticado y elegante! No otorga ningún beneficio. Artículo de Suscriptor de Febrero 3015.",
"armorArmoireLunarArmorText": "Armadura Lunar Relajante",
@@ -392,8 +398,10 @@
"armorArmoireJesterCostumeNotes": "¡Tra-la-la! A pesar del aspecto de este disfraz, no eres un bufón. Incrementa la Inteligencia por <%= int %>. Armario Encantado: Conjunto de Bufón (Artículo 2 de 3).",
"armorArmoireMinerOverallsText": "Overoles de Minero",
"armorArmoireMinerOverallsNotes": "Puede que parezcan usados, pero están encantados para repeler mugre. Incrementan la Constitución por <%= con %>. Armario Encantado: Conjunto de Minero (Artículo 2 de 3).",
- "armorArmoireBasicArcherArmorText": "Basic Archer Armor",
- "armorArmoireBasicArcherArmorNotes": "This camouflaged vest lets you slip unnoticed through the forests. Increases Perception by <%= per %>. Enchanted Armoire: Basic Archer Set (Item 2 of 3).",
+ "armorArmoireBasicArcherArmorText": "Armadura Básica de Arquero",
+ "armorArmoireBasicArcherArmorNotes": "Este chaleco de camuflaje te permite pasar desapercibido en los bosques. Incrementa la Percepción por <%= per %>. Armario Encantado: Conjunto Básico de Arquero (Artículo 2 de 3).",
+ "armorArmoireGraduateRobeText": "Túnica de graduado",
+ "armorArmoireGraduateRobeNotes": "Congratulations! This weighty robe hangs heavy with all the knowledge you have accrued. Increases Intelligence by <%= int %>. Enchanted Armoire: Graduate Set (Item 2 of 3).",
"headgear": "equipamiento para la cabeza",
"headBase0Text": "Sin yelmo",
"headBase0Notes": "Sin equipamiento para la cabeza.",
@@ -524,13 +532,13 @@
"headSpecialWinter2016HealerText": "Yelmo con Alas de Hada",
"headSpecialWinter2016HealerNotes": "¡Estasalasseagitantanrápidoquesevenborrosas! Incrementa la Inteligencia por <%= int %>. Equipamiento de Edición Limitada de Invierno 2015-2016.",
"headSpecialSpring2016RogueText": "Máscara de Buen Perrito",
- "headSpecialSpring2016RogueNotes": "Aww, ¡que perrito mas lindo! Ven aquí, y déjame acariciarte... Hey, ¿A dónde se ha ido mi oro? Incrementa la Percepción por <%= per %>. Equipo de Edición limitada de Primavera 2016.",
- "headSpecialSpring2016WarriorText": "Yelmo de Guardia Ratón",
- "headSpecialSpring2016WarriorNotes": "¡Nunca más serás golpeado en la cabeza! ¡Deja que lo intenten! Incrementa la Fuerza por <%= str %>. Equipo de Edición limitada de Primavera 2016.",
- "headSpecialSpring2016MageText": "Magnífico Sombrero Raído",
- "headSpecialSpring2016MageNotes": "Ropas para ponerte por encima de los meros magos callejeros del mundo. Incrementa la Percepción por <%= per %>. Equipo de Edición limitada de Primavera 2016.",
+ "headSpecialSpring2016RogueNotes": "Aww, ¡qué perrito más lindo! Ven aquí y déjame acariciarte... Ey, ¿dónde está mi oro? Incrementa la Percepción por <%= per %>. Equipamiento de Edición Limitada de Primavera 2016.",
+ "headSpecialSpring2016WarriorText": "Yelmo de Ratón Guardián",
+ "headSpecialSpring2016WarriorNotes": "¡Nunca más serás golpeado en la cabeza! ¡Deja que lo intenten! Incrementa la Fuerza por <%= str %>. Equipamiento de Edición Limitada de Primavera 2016.",
+ "headSpecialSpring2016MageText": "Gran Sombrero Felino",
+ "headSpecialSpring2016MageNotes": "Una vestimenta que te pone por encima de los meros magos callejeros del mundo. Incrementa la Percepción por <%= per %>. Equipamiento de Edición Limitada de Primavera 2016.",
"headSpecialSpring2016HealerText": "Diadema Floreciente",
- "headSpecialSpring2016HealerNotes": "Brilla con el potencial de una nueva vida, lista para estallar. Incrementa la Inteligencia por <%= int %>. Equipo de Edición limitada de Primavera 2016.",
+ "headSpecialSpring2016HealerNotes": "Destella con el potencial de una nueva vida, lista para brotar. Incrementa la Inteligencia por <%= int %>. Equipamiento de Edición Limitada de Primavera 2016.",
"headSpecialGaymerxText": "Yelmo de Guerrero Arco Iris",
"headSpecialGaymerxNotes": "Con motivo de la celebración por la Conferencia GaymerX, ¡este casco especial está decorado con un radiante y colorido estampado arco iris! GaymerX es una convención de juegos que celebra a la gente LGBTQ y a los videojuegos, y está abierta a todo el público.",
"headMystery201402Text": "Yelmo Alado",
@@ -564,7 +572,11 @@
"headMystery201602Text": "Capucha Rompecorazones ",
"headMystery201602Notes": "Protege tu identidad de todos tus admiradores. No otorga ningún beneficio. Artículo de Suscriptor de Febrero 2016. ",
"headMystery201603Text": "Sombrero de la Suerte",
- "headMystery201603Notes": "This top hat is a magical good-luck charm. Confers no benefit. March 2016 Subscriber Item.",
+ "headMystery201603Notes": "Esta galera es un amuleto mágico para la buena suerte. No otorga ningún beneficio. Artículo de Suscriptor de Marzo 2016.",
+ "headMystery201604Text": "Crown o' Flowers",
+ "headMystery201604Notes": "These woven flowers make a surprisingly strong helm! Confers no benefit. April 2016 Subscriber Item.",
+ "headMystery201605Text": "Marching Bard Hat",
+ "headMystery201605Notes": "Seventy-six dragons led the big parade, with a hundred and ten gryphons close at hand! Confers no benefit. May 2016 Subscriber Item.",
"headMystery301404Text": "Galera Elegante",
"headMystery301404Notes": "¡Una galera elegante para los señores más sofisticados! Artículo de Suscriptor de Enero 3015. No otorga ningún beneficio.",
"headMystery301405Text": "Galera Básica",
@@ -611,8 +623,10 @@
"headArmoireJesterCapNotes": "Los cascabeles de este gorro pueden distraer a tus oponentes, pero a ti sólo te ayudan a concentrarte. Incrementa la Percepción por <%= per %>. Armario Encantado: Conjunto de Bufón (Artículo 1 de 3).",
"headArmoireMinerHelmetText": "Casco de Minero",
"headArmoireMinerHelmetNotes": "¡Protege tu cabeza de las tareas que caen! Incrementa la Inteligencia por <%= int %>. Armario Encantado: Conjunto de Minero (Artículo 1 de 3).",
- "headArmoireBasicArcherCapText": "Basic Archer Cap",
- "headArmoireBasicArcherCapNotes": "No archer would be complete without a jaunty cap! Increases Perception by <%= per %>. Enchanted Armoire: Basic Archer Set (Item 3 of 3).",
+ "headArmoireBasicArcherCapText": "Gorro Básico de Arquero",
+ "headArmoireBasicArcherCapNotes": "¡Ningún arquero estaría completo sin un distinguido gorro! Incrementa la Percepción por <%= per %>. Armario Encantado: Conjunto Básico de Arquero (Artículo 3 de 3).",
+ "headArmoireGraduateCapText": "Graduate Cap",
+ "headArmoireGraduateCapNotes": "Congratulations! Your deep thoughts have earned you this thinking cap. Increases Intelligence by <%= int %>. Enchanted Armoire: Graduate Set (Item 3 of 3).",
"offhand": "artículo adicional",
"shieldBase0Text": "Sin equipamiento adicional",
"shieldBase0Notes": "Sin escudo o arma secundaria.",
@@ -673,7 +687,7 @@
"shieldSpecialWinter2015HealerText": "Escudo Relajante",
"shieldSpecialWinter2015HealerNotes": "Este escudo desvía el viento helado. Incrementa la Constitución por <%= con %>. Equipamiento de Edición Limitada de Invierno 2014-2015.",
"shieldSpecialSpring2015RogueText": "Explosivo Chirriante",
- "shieldSpecialSpring2015RogueNotes": "No dejes que el sonido te engañe - estos explosivos están que arden. Incrementa la Fuerza por <%= str %>. Equipamiento de Edición Limitada de Primavera 2015.",
+ "shieldSpecialSpring2015RogueNotes": "Don't let the sound fool you - these explosives pack a punch. Increases Strength by <%= str %>. Limited Edition 2015 Spring Gear.",
"shieldSpecialSpring2015WarriorText": "Disco de Plato",
"shieldSpecialSpring2015WarriorNotes": "Arrójalo a tus enemigos... o simplemente sostenlo, porque se llenará de deliciosas croquetas a la hora de comer. Incrementa la Constitución por <%= con %>. Equipamiento de Edición Limitada de Primavera 2015.",
"shieldSpecialSpring2015HealerText": "Almohada Estampada",
@@ -696,12 +710,12 @@
"shieldSpecialWinter2016WarriorNotes": "Utiliza este trineo para bloquear ataques, ¡o deslízate con él hacia la batalla! Incrementa la Constitución por <%= con %>. Equipamiento de Edición Limitada de Invierno 2015-2016.",
"shieldSpecialWinter2016HealerText": "Regalo de Hada",
"shieldSpecialWinter2016HealerNotes": "¡¡¡¡¡¡¡¡¡Ábrelo ábrelo ábrelo ábrelo ábrelo ábrelo!!!!!!!!! Incrementa la Constitución por <%= con %>. Equipamiento de Edición Limitada de Invierno 2015-2016.",
- "shieldSpecialSpring2016RogueText": "Maza de Fuego",
- "shieldSpecialSpring2016RogueNotes": "You've mastered the ball, the club, and the knife. Now you advance to juggling fire! Awoo! Increases Strength <%= str %>. Limited Edition 2016 Spring Gear.",
+ "shieldSpecialSpring2016RogueText": "Boleadoras de Fuego",
+ "shieldSpecialSpring2016RogueNotes": "Has dominado el balón, el garrote y el cuchillo. ¡Ahora avanza y haz malabares con fuego! ¡Awoo! Incrementan la Fuerza por <%= str %>. Equipamiento de Edición Limitada de Primavera 2016.",
"shieldSpecialSpring2016WarriorText": "Rueda de Queso",
- "shieldSpecialSpring2016WarriorNotes": "You braved fiendish traps to procure this defense-boosting food. Increases Constitution by <%= con %>. Limited Edition 2016 Spring Gear.",
- "shieldSpecialSpring2016HealerText": "Floral Buckler",
- "shieldSpecialSpring2016HealerNotes": "The April Fool claims this little shield will block Shiny Seeds. Don't believe him. Increases Constitution by <%= con %>. Limited Edition 2016 Spring Gear.",
+ "shieldSpecialSpring2016WarriorNotes": "Te enfrentaste a diabólicas trampas para conseguir esta comida que aumenta la defensa. Incrementa la Constitución por <%= con %>. Equipamiento de Edición Limitada de Primavera 2016.",
+ "shieldSpecialSpring2016HealerText": "Broquel Floral",
+ "shieldSpecialSpring2016HealerNotes": "El Santo Inocente afirma que este pequeño escudo bloqueará las Semillas Radiantes. No le creas. Incrementa la Constitución por <%= con %>. Equipamiento de Edición Limitada de Primavera 2016.",
"shieldMystery201601Text": "Destructora de Resoluciones",
"shieldMystery201601Notes": "Esta espada se puede usar para desviar a todas las distracciones. No otorga ningún beneficio. Artículo de Suscriptor de Enero 2016.",
"shieldMystery301405Text": "Escudo Reloj",
@@ -716,6 +730,8 @@
"shieldArmoireDragonTamerShieldNotes": "Distrae a tus enemigos con este escudo con forma de dragón. Incrementa la Percepción por <%= per %>. Armario Encantado: Conjunto de Domador de Dragones (Artículo 2 de 3).",
"shieldArmoireMysticLampText": "Lámpara Mística",
"shieldArmoireMysticLampNotes": "¡Ilumina las cuevas más oscuras con esta lámpara mística! Incrementa la Percepción por <%= per %>. Armario Encantado: Artículo Independiente.",
+ "shieldArmoireFloralBouquetText": "Bouquet o' Flowers",
+ "shieldArmoireFloralBouquetNotes": "Not much help in battle, but aren't they beautiful? Increases Constitution by <%= con %>. Enchanted Armoire: Independent Item.",
"back": "Accesorio para espalda",
"backBase0Text": "Sin accesorio para espalda",
"backBase0Notes": "Sin accesorio para espalda.",
@@ -779,16 +795,16 @@
"headAccessorySpecialSpring2015MageNotes": "Estas orejas escuchan atentamente, en caso de que algún mago esté revelando secretos. No otorgan ningún beneficio. Equipamiento de Edición Limitada de Primavera 2015.",
"headAccessorySpecialSpring2015HealerText": "Orejas de Gatito Verdes",
"headAccessorySpecialSpring2015HealerNotes": "Estas adorables orejas de gatito pondrán a los demás verdes de envidia. No otorgan ningún beneficio. Equipamiento de Edición Limitada de Primavera 2015.",
- "headAccessorySpecialSpring2016RogueText": "Orejas de Perro Verde",
- "headAccessorySpecialSpring2016RogueNotes": "With these, you can keep track of tricky Mages even if they turn invisible! Confers no benefit. Limited Edition 2016 Spring Gear.",
+ "headAccessorySpecialSpring2016RogueText": "Orejas de Perro Verdes",
+ "headAccessorySpecialSpring2016RogueNotes": "¡Con éstas podrás mantener a astutos magos en la mira aunque se vuelvan invisibles! No otorgan ningún beneficio. Equipamiento de Edición Limitada de Primavera 2016.",
"headAccessorySpecialSpring2016WarriorText": "Orejas de Ratón Rojas",
- "headAccessorySpecialSpring2016WarriorNotes": "To better hear your theme song across clamorous battlefields. Confers no benefit. Limited Edition 2016 Spring Gear.",
+ "headAccessorySpecialSpring2016WarriorNotes": "Para que puedas escuchar mejor tu banda sonora en los ruidosos campos de batalla. No confieren ningún beneficio. Equipamiento de Edición Limitada de Primavera 2016.",
"headAccessorySpecialSpring2016MageText": "Orejas de Gato Amarillas",
- "headAccessorySpecialSpring2016MageNotes": "These sharp ears can detect the minute hum of ambient Mana, or the muted footfalls of a Rogue. Confers no benefit. Limited Edition 2016 Spring Gear.",
+ "headAccessorySpecialSpring2016MageNotes": "Estos oídos agudos pueden detectar el ínfimo zumbido del Maná del ambiente, o los pasos silenciosos de un Pícaro. No otorgan ningún beneficio. Equipamiento de Edición Limitada de Primavera 2016.",
"headAccessorySpecialSpring2016HealerText": "Orejas de Conejito Violeta",
- "headAccessorySpecialSpring2016HealerNotes": "They stand like flags above the fray, letting others know where to run for help. Confers no benefit. Limited Edition 2016 Spring Gear.",
+ "headAccessorySpecialSpring2016HealerNotes": "Altas como banderas en una batalla, permiten a otros ver dónde conseguir ayuda. No confieren ningún beneficio. Equipamiento de Edición Limitada de Primavera 2016.",
"headAccessoryBearEarsText": "Orejas de Oso",
- "headAccessoryBearEarsNotes": "These ears make you look like a brave bear! Confers no benefit.",
+ "headAccessoryBearEarsNotes": "¡Estas orejas te hacen ver como un valiente oso! No otorgan ningún beneficio.",
"headAccessoryCactusEarsText": "Orejas de Cactus",
"headAccessoryCactusEarsNotes": "¡Estas orejas te hacen parecer un cactus espinoso! No otorgan ningún beneficio.",
"headAccessoryFoxEarsText": "Orejas de Zorro",
@@ -815,11 +831,25 @@
"headAccessoryMystery201510Notes": "Estos cuernos aterradores son ligeramente babosos. No otorgan ningún beneficio. Artículo de Suscriptor de Octubre 2015.",
"headAccessoryMystery301405Text": "Gafas para la Cabeza",
"headAccessoryMystery301405Notes": "\"Las gafas son para tus ojos\", dijeron. \"Nadie quiere gafas que sólo se puedan usar sobre la cabeza\", dijeron. ¡Ja! ¡Claramente les demostraste que estaban equivocados! No otorgan ningún beneficio. Artículo de Suscriptor de Agosto 3015.",
- "headAccessoryArmoireComicalArrowText": "Comical Arrow",
- "headAccessoryArmoireComicalArrowNotes": "This whimsical item doesn't provide a stat boost, but it sure is good for a laugh! Confers no benefit. Enchanted Armoire: Independent Item.",
+ "headAccessoryArmoireComicalArrowText": "Flecha Cómica",
+ "headAccessoryArmoireComicalArrowNotes": "Este extravagante objeto no provee ninguna mejora a tus estadísticas, ¡pero sí que te hace reír! No otorga ningún beneficio. Armario Encantado: Artículo Independiente.",
"eyewear": "Accesorios para ojos",
"eyewearBase0Text": "Sin accesorios para ojos",
"eyewearBase0Notes": "Sin accesorios para ojos.",
+ "eyewearSpecialBlackTopFrameText": "Black Standard Eyeglasses",
+ "eyewearSpecialBlackTopFrameNotes": "Glasses with a black frame above the lenses. Confers no benefit.",
+ "eyewearSpecialBlueTopFrameText": "Blue Standard Eyeglasses",
+ "eyewearSpecialBlueTopFrameNotes": "Glasses with a blue frame above the lenses. Confers no benefit.",
+ "eyewearSpecialGreenTopFrameText": "Green Standard Eyeglasses",
+ "eyewearSpecialGreenTopFrameNotes": "Glasses with a green frame above the lenses. Confers no benefit.",
+ "eyewearSpecialPinkTopFrameText": "Pink Standard Eyeglasses",
+ "eyewearSpecialPinkTopFrameNotes": "Glasses with a pink frame above the lenses. Confers no benefit.",
+ "eyewearSpecialRedTopFrameText": "Red Standard Eyeglasses",
+ "eyewearSpecialRedTopFrameNotes": "Glasses with a red frame above the lenses. Confers no benefit.",
+ "eyewearSpecialWhiteTopFrameText": "White Standard Eyeglasses",
+ "eyewearSpecialWhiteTopFrameNotes": "Glasses with a white frame above the lenses. Confers no benefit.",
+ "eyewearSpecialYellowTopFrameText": "Yellow Standard Eyeglasses",
+ "eyewearSpecialYellowTopFrameNotes": "Glasses with a yellow frame above the lenses. Confers no benefit.",
"eyewearSpecialSummerRogueText": "Parche Pícaro",
"eyewearSpecialSummerRogueNotes": "¡No se necesita de un gamberro para ver lo elegante que es! No otorga ningún beneficio. Equipamiento de Edición Limitada de Verano 2014.",
"eyewearSpecialSummerWarriorText": "Parche Elegante",
diff --git a/common/locales/es_419/generic.json b/common/locales/es_419/generic.json
index 42c9143178..7947abb589 100644
--- a/common/locales/es_419/generic.json
+++ b/common/locales/es_419/generic.json
@@ -137,8 +137,8 @@
"achievementStressbeastText": "¡Ayudó a vencer a la Abominable Bestia del Estrés durante el Evento Winter Wonderland 2014!",
"achievementBurnout": "Salvador de los Campos Florecientes",
"achievementBurnoutText": "¡Ayudó a vencer a Burnout y a recuperar a los Espíritus Consumidos durante el Festival de Otoño 2015!",
- "achievementBewilder": "Savior of Mistiflying",
- "achievementBewilderText": "Helped defeat the Be-Wilder during the 2016 Spring Fling Event!",
+ "achievementBewilder": "Salvador de Desconcertaire",
+ "achievementBewilderText": "¡Ayudó a vencer al Obnubilave durante el Evento Fiesta de Primavera 2016!",
"checkOutProgress": "¡Echa un vistazo a mi progreso en Habitica!",
"cardReceived": "¡Recibiste una tarjeta! ",
"cardReceivedFrom": "<%= cardType %> de <%= userName %>",
diff --git a/common/locales/es_419/groups.json b/common/locales/es_419/groups.json
index fbc82cc459..a8171a95da 100644
--- a/common/locales/es_419/groups.json
+++ b/common/locales/es_419/groups.json
@@ -92,6 +92,7 @@
"send": "Enviar",
"messageSentAlert": "Mensaje enviado",
"pmHeading": "Mensaje privado a <%= name %>",
+ "pmsMarkedRead": "Your private messages have been marked as read",
"clearAll": "Borrar todos los mensajes",
"confirmDeleteAllMessages": "¿Estás seguro de que quieres borrar todos los mensajes de tu bandeja de entrada? Otros usuarios todavía podrán ver los mensajes que les mandaste.",
"optOutPopover": "¿No te gustan los mensajes privados? Haz clic para dejar de recibirlos.",
@@ -99,6 +100,15 @@
"unblock": "Desbloquear",
"pm-reply": "Enviar una respuesta",
"inbox": "Bandeja de Entrada",
+ "messageRequired": "A message is required.",
+ "toUserIDRequired": "A User ID is required",
+ "gemAmountRequired": "A number of gems is required",
+ "notAuthorizedToSendMessageToThisUser": "Can't send message to this user.",
+ "privateMessageGiftIntro": "Hello <%= receiverName %>, <%= senderName %> has sent you",
+ "privateMessageGiftGemsMessage": "<%= gemAmount %> gems!",
+ "privateMessageGiftSubscriptionMessage": "<%= numberOfMonths %> months of subscription!",
+ "cannotSendGemsToYourself": "Cannot send gems to yourself. Try a subscription instead.",
+ "badAmountOfGemsToSend": "Amount must be within 1 and your current number of gems.",
"abuseFlag": "Reportar una violación de las Normas de la Comunidad",
"abuseFlagModalHeading": "¿Reportar una violación hecha por <%= name %>?",
"abuseFlagModalBody": "¿Estás seguro de que quieres denunciar esta publicación? Deberías denunciar una publicación SÓLO cuando viola las <%= firstLinkStart %>Normas de la Comunidad<%= linkEnd %> y/o los <%= secondLinkStart %>Términos de Servicio<%= linkEnd %>. Denunciar una publicación de manera indebida es una violación de las Normas de la Comunidad y puede resultar en una sanción. Algunas de las razones apropiadas por las cuales denunciar una publicación son:
usar malas palabras, blasfemias religiosas
intolerancia, epítetos racistas/sexistas/etc.
temas adultos
violencia, incluso en forma de broma
spam, mensajes sin sentido
",
@@ -151,5 +161,29 @@
"partyUpName": "Equipados",
"partyOnName": "Equipazo",
"partyUpAchievement": "¡Se unió a un Equipo con otra persona! Diviértanse luchando contra monstruos y apoyándose el uno al otro.",
- "partyOnAchievement": "¡Se unió a un Equipo con al menos cuatro personas! ¡Disfruta tu responsabilidad aumentada al unirte con tus amigos para vencer a tus enemigos!"
+ "partyOnAchievement": "¡Se unió a un Equipo con al menos cuatro personas! ¡Disfruta tu responsabilidad aumentada al unirte con tus amigos para vencer a tus enemigos!",
+ "groupIdRequired": "\"groupId\" must be a valid UUID",
+ "groupNotFound": "Group not found.",
+ "groupTypesRequired": "You must supply a valid \"type\" query string.",
+ "questLeaderCannotLeaveGroup": "You cannot leave your party when you have started a quest. Abort the quest first.",
+ "cannotLeaveWhileActiveQuest": "You cannot leave party during an active quest. Please leave the quest first.",
+ "onlyLeaderCanRemoveMember": "Only group leader can remove a member!",
+ "memberCannotRemoveYourself": "You cannot remove yourself!",
+ "groupMemberNotFound": "User not found among group's members",
+ "mustBeGroupMember": "Must be member of the group.",
+ "keepOrRemoveAll": "req.query.keep must be either \"keep-all\" or \"remove-all\"",
+ "keepOrRemove": "req.query.keep must be either \"keep\" or \"remove\"",
+ "canOnlyInviteEmailUuid": "Can only invite using uuids or emails.",
+ "inviteMissingEmail": "Missing email address in invite.",
+ "partyMustbePrivate": "Parties must be private",
+ "userAlreadyInGroup": "User already in that group.",
+ "userAlreadyInvitedToGroup": "User already invited to that group.",
+ "userAlreadyPendingInvitation": "User already pending invitation.",
+ "userAlreadyInAParty": "User already in a party.",
+ "userWithIDNotFound": "User with id \"<%= userId %>\" not found.",
+ "userHasNoLocalRegistration": "User does not have a local registration (username, email, password).",
+ "uuidsMustBeAnArray": "User ID invites must be an array.",
+ "emailsMustBeAnArray": "Email address invites must be an array.",
+ "canOnlyInviteMaxInvites": "You can only invite \"<%= maxInvites %>\" at a time",
+ "onlyCreatorOrAdminCanDeleteChat": "Not authorized to delete this message!"
}
\ No newline at end of file
diff --git a/common/locales/es_419/limited.json b/common/locales/es_419/limited.json
index 4d8bebc7d9..ba5e9828da 100644
--- a/common/locales/es_419/limited.json
+++ b/common/locales/es_419/limited.json
@@ -5,7 +5,7 @@
"annoyingFriends": "Amigos Molestos",
"annoyingFriendsText": "Fue alcanzado por <%= snowballs %> bolas de nieve lanzadas por sus compañeros de equipo.",
"alarmingFriends": "Amigos Alarmantes",
- "alarmingFriendsText": "Fue espantado <%= spookDust %> veces por sus compañeros de equipo.",
+ "alarmingFriendsText": "Got spooked <%= spookySparkles %> times by party members.",
"agriculturalFriends": "Amigos Agrícolas",
"agriculturalFriendsText": "Fue transformado en una flor <%= seeds %> veces por sus compañeros de equipo.",
"aquaticFriends": "Amigos Acuáticos",
@@ -72,5 +72,6 @@
"comfortingKittySet": "Gatito Reconfortante (Sanador)",
"sneakySqueakerSet": "Chirriador Sigiloso (Pícaro)",
"fallEventAvailability": "Disponible hasta el 31 de octubre",
- "winterEventAvailability": "Disponible hasta el 31 de diciembre"
+ "winterEventAvailability": "Disponible hasta el 31 de diciembre",
+ "springEventAvailability": "Available until May 31"
}
\ No newline at end of file
diff --git a/common/locales/es_419/maintenance.json b/common/locales/es_419/maintenance.json
new file mode 100644
index 0000000000..a13177bc4d
--- /dev/null
+++ b/common/locales/es_419/maintenance.json
@@ -0,0 +1,34 @@
+{
+ "habiticaBackSoon": "No te preocupes, ¡Habitica volverá pronto!",
+ "importantMaintenance": "Estamos realizando tareas importantes de mantenimiento que creemos que durarán hasta el <%= localDate %> en tu zona horaria.",
+ "maintenance": "Mantenimiento",
+ "maintenanceMoreInfo": "¿Quieres saber más sobre el mantenimiento? <%= linkStart %>Echa un vistazo a nuestra página de información<%= linkEnd %>.",
+ "noDamageKeepStreaks": "¡NO sufrirás ningún daño ni perderás tus rachas!",
+ "thanksForPatience": "¡Gracias por tu paciencia!",
+ "twitterMaintenanceUpdates": "Para las actualizaciones más recientes, sigue nuestro Twitter, donde estaremos publicando información sobre el estado del mantenimiento.",
+ "veteranPetAward": "Cuando terminemos, ¡recibirás una mascota veterana!",
+
+ "maintenanceInfoTitle": "Información sobre próximas tareas de mantenimiento en Habitica",
+ "maintenanceInfoWhat": "¿Qué va a ocurrir?",
+ "maintenanceInfoWhatText": "El 21 de mayo, Habitica estará fuera de servicio por mantenimiento la mayor parte del día. No sufrirás ningún daño ni nada perjudicará a tu cuenta durante ese fin de semana, ¡incluso aunque no puedas iniciar sesión para marcar tus Diarias a tiempo! Nosotros estaremos trabajando mucho para que esta interrupción dure lo menos posible e iremos publicando los avances en nuestra cuenta de Twitter. Cuando finalice este proceso, ¡todos recibirán una mascota rara como gesto de agradecimiento por su paciencia!",
+ "maintenanceInfoWhy": "¿Por qué ocurrirá esto?",
+ "maintenanceInfoWhyText": "Durante varios meses estuvimos renovando Habitica tras bambalinas. Para ser específicos, hemos reescrito la API. Aunque tal vez por fuera no parezca muy distinta, por dentro es un mundo completamente nuevo. ¡Esto nos permitirá tener MUCHÍSIMA más flexibilidad para añadir funciones de ahora en adelante, además de mejorar el rendimiento!",
+ "maintenanceInfoTechDetails": "¿Quieres más detalles sobre el lado técnico del proceso? Visita La Forja, nuestro blog de desarrolladores.",
+ "maintenanceInfoMore": "Más información",
+ "maintenanceInfoAccountChanges": "¿Qué cambios notaré en mi cuenta cuando el mantenimiento termine?",
+ "maintenanceInfoAccountChangesText": "Al principio, no habrá cambios importantes aparte de las mejoras en el rendimiento de algunas funciones, como los desafíos. Si observas algún cambio que no debería haber aparecido, escríbenos a admin@habitica.com ¡y nos encargaremos de investigarlo!",
+ "maintenanceInfoAddFeatures": "¿Qué tipo de funciones se podrán añadir a Habitica después de esto?",
+ "maintenanceInfoAddFeaturesText": "Al completar el mantenimiento, podremos empezar a crear un chat y unos gremios mejorados, planes para organizaciones y familias, y funciones de productividad adicionales, ¡como tareas mensuales y la posibilidad de registrar lo que hiciste el día anterior! Todas estas funciones son bastante complejas, de modo que llevará tiempo desarrollarlas, pero sin tener terminada esta reescritura, es imposible empezar con ello.",
+ "maintenanceInfoHowLong": "¿Cuánto durará el mantenimiento?",
+ "maintenanceInfoHowLongText": "Tenemos que migrar las tareas y los datos de 1,3 millones de usuarios de Habitica -- ¡no es una tarea fácil! Calculamos que esto tendrá lugar aproximadamente entre la 1 pm (8 pm UTC) y las 10 pm (5 am UTC), Hora Estándar del Pacífico. Por supuesto, ¡haremos todo lo que esté en nuestras manos por terminar lo antes posible! Puedes seguir los avances en nuestro Twitter.",
+ "maintenanceInfoStatsAffected": "¿Cómo serán afectadas mis Diarias, rachas, mejoras y misiones?",
+ "maintenanceInfoStatsAffectedText1": "NO sufrirás ningún daño ni perderás ninguna racha ese fin de semana, pero aparte de eso, ¡el día se reiniciará normalmente! Las Diarias que marcaste se desmarcarán, las mejoras se restablecerán, etc. Si estás participando en una misión de recolección, seguirás encontrando objetos. Si te encuentras en una batalla contra un Jefe, seguirás infligiéndole daño, pero él no te hará daño a ti. (¡Hasta los monstruos necesitan un descanso!)",
+ "maintenanceInfoStatsAffectedText2": "Después de mucho pensar, llegamos a la conclusión de que ésta era la forma más justa de actuar ante el hecho de que muchos usuarios no podrán marcar sus Diarias normalmente durante el mantenimiento. ¡Sentimos las molestias que esto pueda causar!",
+ "maintenanceInfoSeeTasks": "¿Qué hago si necesito ver mi lista de tareas?",
+ "maintenanceInfoSeeTasksText": "Si sabes que vas a necesitar tu lista de tareas el sábado para recordar lo que tienes que hacer, te recomendamos que antes de que comience el mantenimiento hagas una captura de pantalla de tus tareas para poder consultarla luego.",
+ "maintenanceInfoRarePet": "¿Qué tipo de mascota rara recibiré?",
+ "maintenanceInfoRarePetText": "Como agradecimiento por su paciencia durante este proceso, todos recibirán una mascota rara veterana. Si nunca has recibido una mascota veterana, obtendrás un Lobo Veterano. Si ya tienes uno, recibirás un Tigre Veterano. Y si ya tienes ambos, ¡obtendrás una mascota veterana nunca antes vista! Completada la migración, es posible que la mascota tarde unas horas en aparecer, pero no temas, todo el mundo recibirá una.",
+ "maintenanceInfoWho": "¿Quién trabajó en este enorme proyecto?",
+ "maintenanceInfoWhoText": "¡Nos alegra que preguntes! El cabecilla de todo esto es nuestro increíble colaborador paglias, con la inmensa ayuda de Blade, TheHollidayInn, SabreCat, Victor Pudeyev, TheUnknown y Alys.",
+ "maintenanceInfoTesting": "La nueva versión también fue testeada de forma incansable por un grupo de nuestros excelentes voluntarios de código abierto. Gracias -- no podríamos haber hecho esto sin ustedes."
+}
diff --git a/common/locales/es_419/npc.json b/common/locales/es_419/npc.json
index 02438b1da0..aae2575e51 100644
--- a/common/locales/es_419/npc.json
+++ b/common/locales/es_419/npc.json
@@ -21,6 +21,26 @@
"ian": "Ian",
"ianText": "¡Bienvenido a la Tienda de Misiones! Aquí puedes usar Pergaminos de Misiones para luchar contra monstruos con tus amigos. ¡Asegúrate de echar un vistazo a nuestra fina selección de Pergaminos de Misiones disponibles a la derecha!",
"ianBrokenText": "Bienvenido a la Tienda de Misiones... Aquí puedes utilizar Pergaminos de Misión para luchar contra monstruos con tus amigos... Asegúrate de echar un vistazo a nuestra fina selección de Pergaminos de Misión a la derecha...",
+ "missingKeyParam": "\"req.params.key\" is required.",
+ "itemNotFound": "Item \"<%= key %>\" not found.",
+ "cannotBuyItem": "You can't buy this item.",
+ "missingTypeKeyEquip": "\"key\" and \"type\" are required parameters.",
+ "missingPetFoodFeed": "\"pet\" and \"food\" are required parameters.",
+ "invalidPetName": "Invalid pet name supplied.",
+ "missingEggHatchingPotionHatch": "\"egg\" and \"hatchingPotion\" are required parameters.",
+ "invalidTypeEquip": "\"type\" must be one of 'equipped', 'pet', 'mount', 'costume'.",
+ "mustPurchaseToSet": "Must purchase <%= val %> to set it on <%= key %>.",
+ "typeRequired": "Type is required",
+ "keyRequired": "Key is required",
+ "notAccteptedType": "Type must be in [eggs, hatchingPotions, food, quests, gear]",
+ "contentKeyNotFound": "Key not found for Content <%= type %>",
+ "plusOneGem": "+1 Gem",
+ "typeNotSellable": "Type is not sellable. Must be one of the following <%= acceptedTypes %>",
+ "userItemsKeyNotFound": "Key not found for user.items <%= type %>",
+ "pathRequired": "Path string is required",
+ "unlocked": "Items have been unlocked",
+ "alreadyUnlocked": "Full set already unlocked.",
+ "alreadyUnlockedPart": "Full set already partially unlocked.",
"USD": "(USD)",
"newStuff": "Cosas nuevas",
"cool": "Dímelo más tarde",
@@ -64,6 +84,7 @@
"tourPetsPage": "¡Éste es el Establo! Después del nivel 3 puedes obtener mascotas utilizando huevos y pociones. Cuando hayas utilizado una poción de eclosión sobre un huevo en el Mercado, ¡tu mascota aparecerá aquí! Haz clic sobre la imagen de una mascota para añadirla a tu avatar. Aliméntalas con la comida que encuentres después del nivel 4, y crecerán hasta convertirse en poderosas monturas.",
"tourMountsPage": "Una vez que hayas alimentado a una mascota lo suficiente, ésta se transformará en una montura y aparecerá aquí. (Las mascotas, monturas y comida están disponibles después del nivel 3). ¡Haz clic sobre una montura para ensillarla!",
"tourEquipmentPage": "¡Aquí es donde se guarda tu equipamiento! Tu Equipamiento de Batalla afecta tus estadísticas. Si quieres lucir un equipamiento distinto sin afectar tus estadísticas, utiliza la opción \"Usar disfraz\".",
+ "equipmentAlreadyOwned": "You already own that piece of equipment",
"tourOkay": "¡Okay!",
"tourAwesome": "¡Genial!",
"tourSplendid": "¡Espléndido!",
diff --git a/common/locales/es_419/pets.json b/common/locales/es_419/pets.json
index 07ac2a3999..8a91740ab2 100644
--- a/common/locales/es_419/pets.json
+++ b/common/locales/es_419/pets.json
@@ -12,6 +12,7 @@
"etherealLion": "León Etéreo",
"veteranWolf": "Lobo Veterano",
"veteranTiger": "Tigre Veterano ",
+ "veteranLion": "Veteran Lion",
"cerberusPup": "Cachorro Cerbero",
"hydra": "Hidra",
"mantisShrimp": "Mantis Marina",
@@ -19,7 +20,7 @@
"orca": "Orca",
"royalPurpleGryphon": "Grifo Real Morado",
"phoenix": "Fénix",
- "bumblebee": "Bumblebee",
+ "magicalBee": "Abeja Mágica",
"rarePetPop1": "¡Haz clic en la pata dorada para aprender más sobre cómo obtener esta mascota rara contribuyendo a Habitica!",
"rarePetPop2": "¡Cómo obtener esta mascota!",
"potion": "Poción <%= potionType %>",
@@ -62,6 +63,7 @@
"hatchedPet": "¡Eclosionaste un <%= egg %> <%= potion %>!",
"displayNow": "Mostrar Ahora",
"displayLater": "Mostrar Más Tarde",
+ "petNotOwned": "You do not own this pet.",
"earnedCompanion": "Con toda tu productividad, has obtenido un nuevo compañero. ¡Aliméntalo para hacerlo crecer!",
"feedPet": "¿Dar de comer <%= article %><%= text %> a tu <%= name %>?",
"useSaddle": "¿Ensillar <%= pet %>?",
@@ -83,5 +85,8 @@
"petKeyBoth": "Liberar a ambas",
"confirmPetKey": "¿Estás seguro?",
"petKeyNeverMind": "Todavía no",
+ "petsReleased": "Pets released.",
+ "mountsAndPetsReleased": "Mounts and pets released",
+ "mountsReleased": "Mounts released",
"gemsEach": "gemas cada uno"
}
\ No newline at end of file
diff --git a/common/locales/es_419/quests.json b/common/locales/es_419/quests.json
index 1ee6a75f9e..021f65cbcd 100644
--- a/common/locales/es_419/quests.json
+++ b/common/locales/es_419/quests.json
@@ -38,7 +38,7 @@
"bossDmg2": "Sólo los participantes lucharán contra el Jefe y compartirán el botín de la misión.",
"bossDmg1Broken": "Cada Diaria o Pendiente completada y cada Hábito positivo lastima al jefe... Hazle aún más daño con las tareas más rojas o con Golpe Brutal y Explosión de Llamas... El Jefe hará daño a todos los participantes de la misión por cada Diaria que no completes (multiplicada por la Fuerza del jefe) además de tu daño regular, así que mantén a tu grupo sano completando tus Diarias... Todo daño a y de un jefe es aplicado en el cron (tu cambio de día)...",
"bossDmg2Broken": "Sólo los participantes lucharán contra el Jefe y compartirán el botín de la misión...",
- "tavernBossInfo": "Complete Dailies and To-Dos and score positive Habits to damage the World Boss! Incomplete Dailies fill the Rage Bar. When the Rage bar is full, the World Boss will attack an NPC. A World Boss will never damage individual players or accounts in any way. Only active accounts not resting in the Inn will have their tasks tallied.",
+ "tavernBossInfo": "¡Completa Diarias y Pendientes y marca Hábitos positivos para dañar al Jefe Global! Las Diarias incompletas llenan la Barra de Ira. Cuando esta barra se llene, el Jefe Global atacará a un PNJ. Un Jefe Global nunca dañará a jugadores o cuentas individuales de ninguna forma. Sólo las cuentas activas que no estén descansando en la Posada tendrán sus tareas bajo conteo.",
"tavernBossInfoBroken": "Completa Diarias y Pendientes y marca Hábitos positivos para dañar al Jefe Global... Las Diarias incompletas llenan la barra de Ataque Consumidor... Cuando esta barra se llene, el Jefe Global atacará a un PNJ... Un Jefe Global nunca dañará a jugadores o cuentas individuales de ninguna forma... Sólo las cuentas activas que no estén descansando en la Posada tendrán sus tareas bajo conteo...",
"bossColl1": "Para recolectar artículos, haz tus tareas positivas. Los objetos de misiones caen como los objetos normales; sin embargo no verás estos artículos hasta el próximo día, cuando todo lo que encontraste será sumado y aportado al montón.",
"bossColl2": "Sólo los participantes pueden recolectar objetos y compartir el botín de la misión.",
@@ -78,5 +78,24 @@
"whichQuestStart": "¿Cuál misión quieres empezar?",
"getMoreQuests": "Obtén más misiones",
"unlockedAQuest": "¡Has desbloqueado una misión!",
- "leveledUpReceivedQuest": "¡Subiste al Nivel <%= level %> y recibiste un pergamino de misión!"
+ "leveledUpReceivedQuest": "¡Subiste al Nivel <%= level %> y recibiste un pergamino de misión!",
+ "questInvitationDoesNotExist": "No quest invitation has been sent out yet.",
+ "questInviteNotFound": "No quest invitation found.",
+ "guildQuestsNotSupported": "Guilds cannot be invited on quests.",
+ "questNotFound": "Quest \"<%= key %>\" not found.",
+ "questNotOwned": "You don't own that quest scroll.",
+ "questNotGoldPurchasable": "Quest \"<%= key %>\" is not a Gold-purchasable quest.",
+ "questLevelTooHigh": "You must be level <%= level %> to begin this quest.",
+ "questAlreadyUnderway": "Your party is already on a quest. Try again when the current quest has ended.",
+ "questAlreadyAccepted": "You already accepted the quest invitation.",
+ "noActiveQuestToLeave": "No active quest to leave",
+ "questLeaderCannotLeaveQuest": "Quest leader cannot leave quest",
+ "notPartOfQuest": "You are not part of the quest",
+ "noActiveQuestToAbort": "There is no active quest to abort.",
+ "onlyLeaderAbortQuest": "Only the group or quest leader can abort a quest.",
+ "questAlreadyRejected": "You already rejected the quest invitation.",
+ "cantCancelActiveQuest": "You can not cancel an active quest, use the abort functionality.",
+ "onlyLeaderCancelQuest": "Only the group or quest leader can cancel the quest.",
+ "questNotPending": "There is no quest to start.",
+ "questOrGroupLeaderOnlyStartQuest": "Only the quest leader or group leader can force start the quest"
}
\ No newline at end of file
diff --git a/common/locales/es_419/questscontent.json b/common/locales/es_419/questscontent.json
index 38bdccad37..a503e34745 100644
--- a/common/locales/es_419/questscontent.json
+++ b/common/locales/es_419/questscontent.json
@@ -59,7 +59,7 @@
"questSpiderDropSpiderEgg": "Araña (Huevo)",
"questSpiderUnlockText": "Desbloquea huevos de Araña adquiribles en el Mercado",
"questVice1Text": "Vicio, Parte 1: Libérate de la Influencia del Dragón",
- "questVice1Notes": "
Dicen que un terrible mal encontrase en las cavernas del Monte Habitica. Un monstruo cuya presencia tuerce la determinación de los fuertes héroes de la tierra, conduciéndolos al camino de los malos hábitos y la pereza! La bestia es un gran dragón de inmenso poder y integrado por sus propias sombras: Vício, el traicionero Guivre Sombrío. Valientes Habiteros, levántense y derroten a esta bestia infame de una vez por todas, pero sólo si usted cree que pueden mantenerse firmes contra su inmenso poder.
Vicio Parte 1:
¿Cómo puedes pretender enfrentarte a la bestia si ya tiene control sobre ti? !No caigas víctima de la pereza y el vicio! !Trabaja duro para luchar contra la oscura influencia del dragón y disipar su control sobre ti!
",
+ "questVice1Notes": "
Dicen que yace un terrible mal en las cavernas del Monte Habitica. Un monstruo cuya presencia retuerce la voluntad de los grandes héroes de estas tierras, ¡conduciéndolos a los malos hábitos y a la pereza! La bestia es un gran dragón de inmenso poder y compuesto de las mismísimas sombras. Vicio, el traicionero Guivre Sombrío. Valientes Habiteros, levántense y venzan a esta bestia infame de una vez por todas, pero sólo si creen que pueden mantenerse firmes contra su inmenso poder.
Vicio Parte 1:
¿Cómo puedes pretender enfrentarte a la bestia si ya tiene control sobre ti? ¡No caigas víctima de la pereza y el vicio! ¡Trabaja duro para luchar contra la oscura influencia del dragón y disipar su control sobre ti!
",
"questVice1Boss": "Sombra de Vicio",
"questVice1DropVice2Quest": "Vicio Parte 2 (Pergamino)",
"questVice2Text": "Vicio, Parte 2: Encuentra la Guarida del Guivre",
@@ -74,30 +74,30 @@
"questVice3DropDragonEgg": "Dragón (Huevo)",
"questVice3DropShadeHatchingPotion": "Poción de eclosión de Sombra",
"questMoonstone1Text": "La Cadena de Piedra Lunar, Parte 1: La Cadena de Piedra Lunar",
- "questMoonstone1Notes": "A terrible affliction has struck Habiticans. Bad Habits thought long-dead are rising back up with a vengeance. Dishes lie unwashed, textbooks linger unread, and procrastination runs rampant!
You track some of your own returning Bad Habits to the Swamps of Stagnation and discover the culprit: the ghostly Necromancer, Recidivate. You rush in, weapons swinging, but they slide through her specter uselessly.
\"Don’t bother,\" she hisses with a dry rasp. \"Without a chain of moonstones, nothing can harm me – and master jeweler @aurakami scattered all the moonstones across Habitica long ago!\" Panting, you retreat... but you know what you must do.",
+ "questMoonstone1Notes": "Una terrible aflicción ha golpeado a los Habiticanos. Malos Hábitos que se creían muertos hace tiempo se han levantado de nuevo en venganza. Los platos se encuentran sin lavar, los libros de texto permanecen sin leer, ¡y la procrastinación corre sin nadie que la detenga!
Sigues el rastro de algunos de tus propios Malos Hábitos a las Ciénagas del Estancamiento y descubres a la culpable: la fantasmal Necromante, Reincidencia. Te lanzas a atacarla, pero tus armas atraviesan su cuerpo espectral inútilmente.
\"No te molestes,\" susurra con un tono áspero y seco. \"Sin una cadena de piedras lunares, nada puede hacerme daño – ¡y el maestro joyero @aurakami dispersó todas las piedras lunares a través de Habitica hace mucho tiempo!\" Jadeante, te retiras... pero sabes qué es lo que debes hacer.",
"questMoonstone1CollectMoonstone": "Piedras lunares",
"questMoonstone1DropMoonstone2Quest": "La Cadena de Piedra Lunar Parte 2: Reincidencia la Necromante (Pergamino)",
"questMoonstone2Text": "La Cadena de Piedra Lunar, Parte 2: Reincidencia la Necromante",
- "questMoonstone2Notes": "The brave weaponsmith @Inventrix helps you fashion the enchanted moonstones into a chain. You’re ready to confront Recidivate at last, but as you enter the Swamps of Stagnation, a terrible chill sweeps over you.
Rotting breath whispers in your ear. \"Back again? How delightful...\" You spin and lunge, and under the light of the moonstone chain, your weapon strikes solid flesh. \"You may have bound me to the world once more,\" Recidivate snarls, \"but now it is time for you to leave it!\"",
+ "questMoonstone2Notes": "El valiente armero @Inventrix te ayuda a dar forma a las piedras lunares encantadas hasta hacerlas una cadena. Estás listo para confrontar finalmente a Reincidencia, pero en cuanto entras a las Ciénagas del Estancamiento, te recorre un terrible escalofrío.
Un soplo hediondo susurra en tu oído. \"¿Has regresado? Qué deleite...\" Giras y atacas, y bajo la luz de la cadena de piedra lunar, tu arma golpea carne sólida. \"Tal vez me hayas atado al mundo una vez más,\" gruñe Reincidencia, \"¡pero ahora es tiempo de que termines!\"",
"questMoonstone2Boss": "La Necromante",
"questMoonstone2DropMoonstone3Quest": "La Cadena de Piedra Lunar Parte 3: Reincidencia Transformada (Pergamino)",
"questMoonstone3Text": "La Cadena de Piedra Lunar, Parte 3: Reincidencia Transformada",
- "questMoonstone3Notes": "Recidivate crumples to the ground, and you strike at her with the moonstone chain. To your horror, Recidivate seizes the gems, eyes burning with triumph.
\"Foolish creature of flesh!\" she shouts. \"These moonstones will restore me to a physical form, true, but not as you imagined. As the full moon waxes from the dark, so too does my power flourish, and from the shadows I summon the specter of your most feared foe!\"
A sickly green fog rises from the swamp, and Recidivate’s body writhes and contorts into a shape that fills you with dread – the undead body of Vice, horribly reborn.",
- "questMoonstone3Completion": "Your breath comes hard and sweat stings your eyes as the undead Wyrm collapses. The remains of Recidivate dissipate into a thin grey mist that clears quickly under the onslaught of a refreshing breeze, and you hear the distant, rallying cries of Habiticans defeating their Bad Habits for once and for all.
@Baconsaur the beast master swoops down on a gryphon. \"I saw the end of your battle from the sky, and I was greatly moved. Please, take this enchanted tunic – your bravery speaks of a noble heart, and I believe you were meant to have it.\"",
+ "questMoonstone3Notes": "Reincidencia se desploma al suelo, y la golpeas con tu cadena de piedra lunar. Para tu horror, Reincidencia se apodera de las gemas, sus ojos ardiendo triunfantes.
\"¡Tonta criatura de carne!\" grita. \"Estas piedras lunares me restaurarán a mi forma física, es cierto, pero no como tú imaginaste. A medida que la luna crece en la oscuridad, también crecen mis poderes, ¡y de las sombras convoco al espectro de tu más temido enemigo!\"
Una enfermiza neblina verde se levanta de la ciénaga, y el cuerpo de Reincidencia se retuerce y se contorsiona en una forma que te llena de terror – el cuerpo no-muerto de Vicio, horriblemente renacido.",
+ "questMoonstone3Completion": "Respiras difícilmente y el sudor hace que ardan tus ojos mentras el Guivre colapsa. Los restos de Reincidencia se desvanecen formando una fina bruma gris que desaparece rápidamente bajo la ráfaga de una refrescante brisa, y escuchas en la distancia los gritos de multitudes de Habiticanos derrotando a sus Malos Hábitos de una vez por todas.
@Baconsaur, el maestro de las bestias, se abalanza montado en un grifo. \"Vi el final de tu batalla desde el cielo, y fue increíblemente conmovedora. Por favor, toma esta túnica encantada – tu valentía habla de un noble corazón, y creo que estabas destinado a tenerla.\"",
"questMoonstone3Boss": "Necrovicio",
"questMoonstone3DropRottenMeat": "Carne podrida (Comida)",
"questMoonstone3DropZombiePotion": "Poción de eclosión Zombi",
"questGoldenknight1Text": "La Dama de Oro, Parte 1: Un Regaño Severo",
- "questGoldenknight1Notes": "The Golden Knight has been getting on poor Habiticans' cases. Didn't do all of your Dailies? Checked off a negative Habit? She will use this as a reason to harass you about how you should follow her example. She is the shining example of a perfect Habitican, and you are naught but a failure. Well, that is not nice at all! Everyone makes mistakes. They should not have to be met with such negativity for it. Perhaps it is time you gather some testimonies from hurt Habiticans and give the Golden Knight a stern talking-to!",
+ "questGoldenknight1Notes": "La Dama de Oro ha estado molestando a los pobres Habiticanos. ¿No cumpliste todas tus Diarias? ¿Marcaste un Hábito negativo? Ella lo usará como razón para acosarte y decir que tienes que seguir su ejemplo. Ella es el ejemplo brillante de un Habiticano perfecto y tú no eres más que un fracaso. Bueno, ¡eso no es para nada agradable! Todos cometen errores, y no deberían ser tratados con tanta negatividad por ello. ¡Tal vez es hora de reunir unos cuantos testimonios de Habiticanos ofendidos y darle a la Dama de Oro una severa reprimenda!",
"questGoldenknight1CollectTestimony": "Testimonios",
"questGoldenknight1DropGoldenknight2Quest": "La Cadena de la Dama de Oro Parte 2: Dama de Oro (Pergamino)",
"questGoldenknight2Text": "La Dama de Oro, Parte 2: Dama de Oro",
- "questGoldenknight2Notes": "Armed with hundreds of Habitican's testimonies, you finally confront the Golden Knight. You begin to recite the Habitcan's complaints to her, one by one. \"And @Pfeffernusse says that your constant bragging-\" The knight raises her hand to silence you and scoffs, \"Please, these people are merely jealous of my success. Instead of complaining, they should simply work as hard as I! Perhaps I shall show you the power you can attain through diligence such as mine!\" She raises her morningstar and prepares to attack you!",
+ "questGoldenknight2Notes": "Armado con cientos de testimonios de Habiticanos, enfrentas finalmente a la Dama de Oro. Empiezas a recitar las quejas de los Habiticanos, una por una. \"Y @Pfeffernusse dice que tus constantes fanfarronadas-\" Ella alza su mano para silenciarte y se burla, \"Por favor, estas personas simplemente están celosas de mi éxito. En lugar de quejarse, ¡deberían trabajar tan duro como yo! ¡Quizás pueda mostrarte el poder que puedes obtener mediante una diligencia como la mía!\" Entonces levanta su lucero del alba, ¡y se prepara para atacarte!",
"questGoldenknight2Boss": "Dama de Oro",
"questGoldenknight2DropGoldenknight3Quest": "La Cadena de la Dama de Oro Parte 3: El Caballero de Hierro (Pergamino)",
"questGoldenknight3Text": "La Dama de Oro, Parte 3: El Caballero de Hierro",
- "questGoldenknight3Notes": "@Jon Arinbjorn cries out to you to get your attention. In the aftermath of your battle, a new figure has appeared. A knight coated in stained-black iron slowly approaches you with sword in hand. The Golden Knight shouts to the figure, \"Father, no!\" but the knight shows no signs of stopping. She turns to you and says, \"I am sorry. I have been a fool, with a head too big to see how cruel I have been. But my father is crueler than I could ever be. If he isn't stopped he'll destroy us all. Here, use my morningstar and halt the Iron Knight!\"",
- "questGoldenknight3Completion": "With a satisfying clang, the Iron Knight falls to his knees and slumps over. \"You are quite strong,\" he pants. \"I have been humbled, today.\" The Golden Knight approaches you and says, \"Thank you. I believe we have gained some humility from our encounter with you. I will speak with my father and explain the complaints against us. Perhaps, we should begin apologizing to the other Habiticans.\" She mulls over in thought before turning back to you. \"Here: as our gift to you, I want you to keep my morningstar. It is yours now.\"",
+ "questGoldenknight3Notes": "@Jon Arinbjorn grita para llamar tu atención. En los momentos siguientes a tu batalla, una nueva figura ha aparecido. Un caballero revestido de hierro teñido de negro se aproxima a ti lentamente con su espada en mano. La Dama de Oro vocifera hacia la figura \"¡Padre, no!\" pero el caballero no parece querer detenerse. Ella se vuelve hacia ti y dice \"Lo siento. He sido una tonta, con un ego demasiado grande para ver lo cruel que he sido. Pero mi padre es aún más cruel de lo que yo jamás podría ser. Si nadie lo detiene nos destruirá a todos. ¡Toma, usa mi lucero del alba y termina con el Caballero de Hierro!\"",
+ "questGoldenknight3Completion": "Con un satisfactorio sonido metálico, el Caballero de Hierro cae de rodillas y se desploma. \"Eres bastante fuerte\", jadea. \"Hoy me han humillado\". La Dama de Oro se acerca a ti y dice: \"Gracias. Creo que hemos ganado algo de humildad al enfrentarnos contigo. Hablaré con mi padre y le explicaré las quejas sobre nosotros. Quizás deberíamos empezar por disculparnos ante los otros Habiticanos\". Se detiene a pensar por un momento antes de volverse nuevamente hacia ti. \"Ten: como obsequio, quiero que te quedes con mi lucero del alba. Es tuyo ahora.\"",
"questGoldenknight3Boss": "El Caballero de Hierro",
"questGoldenknight3DropHoney": "Miel (Comida)",
"questGoldenknight3DropGoldenPotion": "Poción de eclosión Dorada",
@@ -112,7 +112,7 @@
"questEggHuntCollectPlainEgg": "Huevos Simples",
"questEggHuntDropPlainEgg": "Huevo Simple",
"questDilatoryText": "El Temido Drag'on de Dilatoria",
- "questDilatoryNotes": "We should have heeded the warnings.
Dark shining eyes. Ancient scales. Massive jaws, and flashing teeth. We've awoken something horrifying from the crevasse: the Dread Drag'on of Dilatory! Screaming Habiticans fled in all directions when it reared out of the sea, its terrifyingly long neck extending hundreds of feet out of the water as it shattered windows with its searing roar.
\"This must be what dragged Dilatory down!\" yells Lemoness. \"It wasn't the weight of the neglected tasks - the Dark Red Dailies just attracted its attention!\"
\"It's surging with magical energy!\" @Baconsaur cries. \"To have lived this long, it must be able to heal itself! How can we defeat it?\"
Why, the same way we defeat all beasts - with productivity! Quickly, Habitica, band together and strike through your tasks, and all of us will battle this monster together. (There's no need to abandon previous quests - we believe in your ability to double-strike!) It won't attack us individually, but the more Dailies we skip, the closer we get to triggering its Neglect Strike - and I don't like the way it's eyeing the Tavern....",
+ "questDilatoryNotes": "Debimos haberle hecho caso a las advertencias.
Ojos brillantes oscuros. Escamas prehistóricas. Una mandíbula enorme, y dientes centelleantes. Hemos despertado algo horripilante de la brecha: ¡el Temido Drag'on de Dilatoria! Los Habiticanos huyeron en todas direcciones gritando cuando se alzó del mar, extendiendo su cuello terriblemente largo cientos de metros fuera del agua mientras destruía ventanas con un rugido abrasador.
\"¡Debe ser esto lo que hundió a Dilatoria!\" grita Lemoness. \"No fue el peso de las tareas descuidadas - ¡las Diarias de color rojo oscuro sólo atrajeron su atención!\"
\"¡Se está llenando de energía mágica!\" @Baconsaur grita. \"Si vivió durante tanto tiempo, ¡seguramente puede sanarse! ¿Cómo vamos a vencerlo?\"
Bueno, de la misma manera que acabamos con todas las bestias - ¡con la productividad! Rápido, Habitica, únanse y ataquen a través de sus tareas, todos batallaremos este monstruo juntos. (No tienes que abandonar tus misiones previas - ¡creemos en tu habilidad de atacar al doble!) Él no nos atacará individualmente, pero mientras más Diarias omitimos, más nos acercamos a su Ataque de Negligencia - y no me gusta la manera en que está mirando la Taberna...",
"questDilatoryBoss": "El Temido Drag'on de Dilatoria",
"questDilatoryBossRageTitle": "Ataque de Negligencia",
"questDilatoryBossRageDescription": "Cuando esta barra se llene, el Temido Drag'on de Dilatoria desatará grandes estragos en el terreno de Habitica",
@@ -142,7 +142,7 @@
"questAtom3Boss": "El Lavandomante",
"questAtom3DropPotion": "Poción de eclosión Básica",
"questOwlText": "El Búho Nocturno",
- "questOwlNotes": "The Tavern light is lit 'til dawn Until one eve the glow is gone! How can we see for our all-nighters? @Twitching cries, \"I need some fighters! See that Night-Owl, starry foe? Fight with haste and do not slow! We'll drive its shadow from our door, And make the night shine bright once more!\"",
+ "questOwlNotes": "La luz de la Taberna queda encendida hasta el amanecer ¡Hasta que una tarde su brillo comienza a desaparecer! ¿Cómo podremos ver en la noche oscura? @Twitching grita, \"¡Necesito guerreros que luchen con premura! ¿Ven a ese Búho Nocturno por las estrellas iluminado? ¡Peleen con valentía hasta que sea derrotado! A su sombra de nuestra puerta alejaremos, ¡y una vez más la noche brillar haremos!\"",
"questOwlCompletion": "El Búho Nocturno se desvanece antes del amanecer, Pero aún así, sientes un bostezo emerger. Quizás sea el momento de tomarse un descanso bien merecido. Sin embargo, cuando llegas a tu cama, ¡encuentras un nido! Un Búho Nocturno sabe que no es mala idea Quedarse despierto hasta tarde y terminar una tarea, Pero a tus nuevas mascotas piar podrás oír Porque estarán intentando decirte que es tiempo de dormir.",
"questOwlBoss": "El Búho Nocturno",
"questOwlDropOwlEgg": "Búho (Huevo)",
@@ -298,15 +298,27 @@
"questSnailBoss": "Caracol del Cieno de Rutinaria",
"questSnailDropSnailEgg": "Caracol (Huevo)",
"questSnailUnlockText": "Desbloquea huevos de Caracol adquiribles en el Mercado",
- "questBewilderText": "The Be-Wilder",
- "questBewilderNotes": "The party begins like any other.
The appetizers are excellent, the music is swinging, and even the dancing elephants have become routine. Habiticans laugh and frolic amid the overflowing floral centerpieces, happy to have a distraction from their least-favorite tasks, and the April Fool whirls among them, eagerly providing an amusing trick here and a witty twist there.
As the Mistiflying clock tower strikes midnight, the April Fool leaps onto the stage to give a speech.
“Friends! Enemies! Tolerant acquaintances! Lend me your ears.” The crowd chuckles as animal ears sprout from their heads, and they pose with their new accessories.
“As you know,” the Fool continues, “my confusing illusions usually only last a single day. But I’m pleased to announce that I’ve discovered a shortcut that will guarantee us non-stop fun, without having to deal with the pesky weight of our responsibilities. Charming Habiticans, meet my magical new friend... the Be-Wilder!”
Lemoness pales suddenly, dropping her hors d'oeuvres. “Wait! Don’t trust--”
But suddenly mists are pouring into the room, glittering and thick, and they swirl around the April Fool, coalescing into cloudy feathers and a stretching neck. The crowd is speechless as an monstrous bird unfolds before them, its wings shimmering with illusions. It lets out a horrible screeching laugh.
“Oh, it has been ages since a Habitican has been foolish enough to summon me! How wonderful it feels, to have a tangible form at last.”
Buzzing in terror, the magic bees of Mistiflying flee the floating city, which sags from the sky. One by one, the brilliant spring flowers wither up and wisp away.
“My dearest friends, why so alarmed?” crows the Be-Wilder, beating its wings. “There’s no need to toil for your rewards any more. I’ll just give you all the things that you desire!”
A rain of coins pours from the sky, hammering into the ground with brutal force, and the crowd screams and flees for cover. “Is this a joke?” Baconsaur shouts, as the gold smashes through windows and shatters roof shingles.
PainterProphet ducks as lightning bolts crackle overhead, and fog blots out the sun. “No! This time, I don’t think it is!”
Quickly, Habiticans, don’t let this World Boss distract us from our goals! Stay focused on the tasks that you need to complete so we can rescue Mistiflying -- and hopefully, ourselves.",
- "questBewilderCompletion": "The Be-Wilder is DEFEATED!
We've done it! The Be-Wilder lets out a ululating cry as it twists in the air, shedding feathers like falling rain. Slowly, gradually, it coils into a cloud of sparkling mist. As the newly-revealed sun pierces the fog, it burns away, revealing the coughing, mercifully human forms of Bailey, Matt, Alex.... and the April Fool himself.
Mistiflying is saved!
The April Fool has enough shame to look a bit sheepish. “Oh, hm,” he says. “Perhaps I got a little…. carried away.”
The crowd mutters. Sodden flowers wash up on sidewalks. Somewhere in the distance, a roof collapses with a spectacular splash.
“Er, yes,” the April Fool says. “That is. What I meant to say was, I’m dreadfully sorry.” He heaves a sigh. “I suppose it can’t all be fun and games, after all. It might not hurt to focus occasionally. Maybe I’ll get a head start on next year’s pranking.”
Redphoenix coughs meaningfully.
“I mean, get a head start on this year’s spring cleaning!” the April Fool says. “Nothing to fear, I’ll have Habit City in spit-shape soon. Luckily nobody is better than I at dual-wielding mops.”
Encouraged, the marching band starts up.
It isn’t long before all is back to normal in Habit City. Plus, now that the Be-Wilder has evaporated, the magical bees of Mistiflying bustle back to work, and soon the flowers are blooming and the city is floating once more.
As Habiticans cuddle the magical fuzzy bees, the April Fool’s eyes light up. “Oho, I’ve had a thought! Why don’t you all keep some of these fuzzy Bee Pets and Mounts? It’s a gift that perfectly symbolizes the balance between hard work and sweet rewards, if I’m going to get all boring and allegorical on you.” He winks. “Besides, they don’t have stingers! Fool’s honor.”",
- "questBewilderCompletionChat": "`The Be-Wilder is DEFEATED!`\n\nWe've done it! The Be-Wilder lets out a ululating cry as it twists in the air, shedding feathers like falling rain. Slowly, gradually, it coils into a cloud of sparkling mist. As the newly-revealed sun pierces the fog, it burns away, revealing the coughing, mercifully human forms of Bailey, Matt, Alex.... and the April Fool himself.\n\n`Mistiflying is saved!`\n\nThe April Fool has enough shame to look a bit sheepish. “Oh, hm,” he says. “Perhaps I got a little…. carried away.”\n\nThe crowd mutters. Sodden flowers wash up on sidewalks. Somewhere in the distance, a roof collapses with a spectacular splash.\n\n“Er, yes,” the April Fool says. “That is. What I meant to say was, I’m dreadfully sorry.” He heaves a sigh. “I suppose it can’t all be fun and games, after all. It might not hurt to focus occasionally. Maybe I’ll get a head start on next year’s pranking.”\n\nRedphoenix coughs meaningfully.\n\n“I mean, get a head start on this year’s spring cleaning!” the April Fool says. “Nothing to fear, I’ll have Habit City in spit-shape soon. Luckily nobody is better than I at dual-wielding mops.”\n\nEncouraged, the marching band starts up.\n\nIt isn’t long before all is back to normal in Habit City. Plus, now that the Be-Wilder has evaporated, the magical bees of Mistiflying bustle back to work, and soon the flowers are blooming and the city is floating once more.\n\nAs Habiticans cuddle the magical fuzzy bees, the April Fool’s eyes light up. “Oho, I’ve had a thought! Why don’t you all keep some of these fuzzy Bee Pets and Mounts? It’s a gift that perfectly symbolizes the balance between hard work and sweet rewards, if I’m going to get all boring and allegorical on you.” He winks. “Besides, they don’t have stingers! Fool’s honor.”",
- "questBewilderBossRageTitle": "Beguilement Strike",
- "questBewilderBossRageDescription": "When this gauge fills, The Be-Wilder will unleash its Beguilement Strike on Habitica!",
- "questBewilderDropBumblebeePet": "Bumblebee (Pet)",
- "questBewilderDropBumblebeeMount": "Bumblebee (Mount)",
- "questBewilderBossRageMarket": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nOh no! Despite our best efforts, we've gotten distracted by the Be-Wilder’s charming illusions and have forgotten to do some of our Dailies! With a cackling cry, the shining bird beats its wings, raising a swarm of mist around Alex the Merchant. When the fog clears, he has been possessed! “Have some free samples!” he shouts gleefully, and begins to hurl exploding eggs and potions at fleeing Habiticans. Not the most favorable of sales, to be sure.\n\nHurry! Let's stay focused on our Dailies to defeat this monster before it possesses someone else.",
- "questBewilderBossRageStables": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nAhh!!! Once again the Be-Wilder has dazzled us into neglecting our Dailies, and now it has attacked Matt the Beast Master! With a swirl of mist, Matt transforms into a terrifying winged creature, and all the pets and mounts howl sadly in their stables. Quickly, stay focused on your tasks to defeat this dastardly distraction!",
- "questBewilderBossRageBailey": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nLook out! In the middle of reporting the news, Bailey the Town Crier has been possessed by the Be-Wilder! She lets out an evil, uninformative screech as she rises into the air. Now how will we know what’s going on?\n\nDon't give up... we're so close to defeating this bothersome bird for once and for all!"
+ "questBewilderText": "El Obnubilave",
+ "questBewilderNotes": "La fiesta comienza como cualquier otra.
Los aperitivos son excelentes, la música es jovial, e incluso los elefantes danzantes se han vuelto normales. Los Habiticanos ríen y se divierten en medio de los centros de mesa florales desbordantes, felices de poder distraerse lejos de sus tareas más desagradables, y el Santo Inocente da vueltas entre ellos, mostrando con entusiasmo un truco entretenido por aquí y un ingenioso giro por allá.
Al tiempo que el reloj de la torre de Desconcertaire da la medianoche, el Santo Inocente se sube de un salto al escenario para dar un discurso.
\"¡Amigos! ¡Enemigos! ¡Conocidos tolerantes! Escuchen con atención.\" La multitud se ríe por lo bajo mientras orejas de animales brotan de sus cabezas, y todos posan con sus nuevos accesorios.
\"Como saben,\" el Inocente continúa, \"mis ilusiones confusas generalmente duran sólo un día. Pero estoy feliz de anunciar que he descubierto un atajo que nos garantizará diversión sin fin, sin tener que lidiar con el molesto peso de nuestras responsabilidades. Encantadores Habiticanos, conozcan a mi nuevo y mágico amigo... ¡el Obnubilave!\"
Lemoness palidece repentinamente, dejando caer sus aperitivos. \"¡Esperen! No confíen en--\"
Pero de forma súbita una neblina se vierte en la sala, brillante y densa, y se arremolina alrededor del Santo Inocente, fusionándose en forma de plumas nubosas y un cuello estirado. La multitud se queda sin palabras mientras una monstruosa ave aparece frente a ella, sus alas centelleando llenas de ilusiones. El ave deja escapar una horrible risa chirriante.
\"¡Ah, han pasado siglos desde que un Habiticano fue lo suficientemente tonto como para convocarme! Qué fabuloso se siente tener al fin una forma tangible.\"
Zumbando aterrorizadas, las abejas mágicas de Desconcertaire huyen de la ciudad flotante, que se alza en el cielo. Una por una, las radiantes flores primaverales se marchitan y desaparecen en una voluta de humo.
\"Mis queridos amigos, ¿por qué están tan asustados?\" cacarea el Obnubilave, agitando sus alas. \"Ya no hay necesidad de trabajar duro para conseguir recompensas. ¡Yo les daré todas las cosas que desean!\"
Una lluvia de monedas cae del cielo, golpeando el suelo con una fuerza brutal, y la muchedumbre grita y corre a buscar refugio. \"¿Esto es una broma?\" aúlla Baconsaur, mientras el oro rompe ventanas y destroza tejados.
PainterProphet se agacha al mismo tiempo que rayos restallan por encima suyo y una niebla bloquea el sol. \"¡No! ¡Esta vez, no creo que lo sea!\"
Rápido, Habiticanos, ¡no dejemos que este Jefe Global nos distraiga de nuestros objetivos! Manténganse enfocados en las tareas que deben completar para que podamos rescatar a Desconcertaire -- y, con suerte, a nosotros mismos.",
+ "questBewilderCompletion": "¡El Obnubilave ha sido DERROTADO!
¡Lo logramos! El Obnubilave deja escapar un aullido mientras se retuerce en el aire, perdiendo plumas que caen como gotas de lluvia. Lenta y gradualmente, se enrolla formando una nube de niebla centelleante. Mientras el sol recién despejado atraviesa la niebla, ésta es consumida por el calor, revelando las formas afortunadamente humanas de Bailey, Matt, Alex... y del mismísimo Santo Inocente.
¡Desconcertaire ha sido salvada!
El Santo Inocente tiene el suficiente remordimiento como para mostrarse avergonzado. \"Ah, hm,\" dice. \"Tal vez... me dejé llevar un poco.\"
La multitud murmura. Flores empapadas son arrastradas a las aceras. A la distancia, en alguna parte, un techo se derrumba con un ruido espectacular.
\"Eh, sí,\" dice el Santo Inocente. \"Lo que quise decir es que lo siento terriblemente.\" Deja salir un suspiro. \"Supongo que no todo en la vida puede ser diversión. No me haría daño enfocarme de vez en cuando. Quizás me adelante para la broma del año que viene.\"
Redphoenix tose exageradamente.
\"Quiero decir, ¡quizás me adelante para la limpieza general!\" dice el Santo Inocente. \"No hay nada que temer, pronto dejaré a Ciudad Hábito como nueva. Por suerte nadie me supera en el manejo de dos trapeadores al mismo tiempo.\"
Animada, la banda de música comienza a tocar.
No pasa mucho tiempo hasta que todo vuelve a la normalidad en Ciudad Hábito. Además, ahora que el Obnubilave se ha evaporado, las abejas mágicas de Desconcertaire vuelven a trabajar con un ritmo ajetreado, y pronto las flores brotan y la ciudad flota una vez más.
Mientras los Habiticanos abrazan a las peludas abejas mágicas, los ojos del Santo Inocente se iluminan. \"¡Ajá, se me ha ocurrido algo! ¿Por qué no se quedan con algunas de estas peluditas abejas como mascotas y monturas? Es un regalo que simboliza perfectamente el equilibrio entre el trabajo duro y las dulces recompensas, si me voy a poner aburrido y alegórico.\" El Inocente guiña. \"Además, ¡no tienen aguijones! Palabra de Inocente.\"",
+ "questBewilderCompletionChat": "`¡El Obnubilave ha sido DERROTADO!`\n\n¡Lo logramos! El Obnubilave deja escapar un aullido mientras se retuerce en el aire, perdiendo plumas que caen como gotas de lluvia. Lenta y gradualmente, se enrolla formando una nube de niebla centelleante. Mientras el sol recién despejado atraviesa la niebla, ésta es consumida por el calor, revelando las formas afortunadamente humanas de Bailey, Matt, Alex... y del mismísimo Santo Inocente.\n\n`¡Desconcertaire ha sido salvada!`\n\nEl Santo Inocente tiene el suficiente remordimiento como para mostrarse avergonzado. \"Ah, hm,\" dice. \"Tal vez... me dejé llevar un poco.\"\n\nLa multitud murmura. Flores empapadas son arrastradas a las aceras. A la distancia, en alguna parte, un techo se derrumba con un ruido espectacular.\n\n\"Eh, sí,\" dice el Santo Inocente. \"Lo que quise decir es que lo siento terriblemente.\" Deja salir un suspiro. \"Supongo que no todo en la vida puede ser diversión. No me haría daño enfocarme de vez en cuando. Quizás me adelante para la broma del año que viene.\"\n\nRedphoenix tose exageradamente.\n\n\"Quiero decir, ¡quizás me adelante para la limpieza general!\" dice el Santo Inocente. \"No hay nada que temer, pronto dejaré a Ciudad Hábito como nueva. Por suerte nadie me supera en el manejo de dos trapeadores al mismo tiempo.\"\n\nAnimada, la banda de música comienza a tocar.\n\nNo pasa mucho tiempo hasta que todo vuelve a la normalidad en Ciudad Hábito. Además, ahora que el Obnubilave se ha evaporado, las abejas mágicas de Desconcertaire vuelven a trabajar con un ritmo ajetreado, y pronto las flores brotan y la ciudad flota una vez más.\n\nMientras los Habiticanos abrazan a las peludas abejas mágicas, los ojos del Santo Inocente se iluminan. \"¡Ajá, se me ha ocurrido algo! ¿Por qué no se quedan con algunas de estas peluditas abejas como mascotas y monturas? Es un regalo que simboliza perfectamente el equilibrio entre el trabajo duro y las dulces recompensas, si me voy a poner aburrido y alegórico.\" El Inocente guiña. \"Además, ¡no tienen aguijones! Palabra de Inocente.\"",
+ "questBewilderBossRageTitle": "Ataque Engañador",
+ "questBewilderBossRageDescription": "Cuando esta barra se llene, ¡el Obnubilave desatará su Ataque Engañador sobre Habitica!",
+ "questBewilderDropBumblebeePet": "Abeja Mágica (Mascota)",
+ "questBewilderDropBumblebeeMount": "Abeja Mágica (Montura)",
+ "questBewilderBossRageMarket": "`¡El Obnubilave usa ATAQUE ENGAÑADOR!`\n\n¡Oh no! A pesar de nuestros mejores esfuerzos, ¡nos hemos distraído con las cautivadoras ilusiones del Obnubilave y hemos olvidado completar algunas de nuestras Diarias! Con un alarido chirriante, el ave resplandeciente bate sus alas, levantando una nube de neblina alrededor de Alex el Comerciante. Cuando la niebla desaparece, ¡Alex ha sido poseído! \"¡Llévense algunas muestras gratis!\" grita alegremente, y comienza a arrojar pociones y huevos explosivos a los Habiticanos que huyen. No es la venta más prometedora, eso está claro.\n\n¡De prisa! Mantengámonos enfocados en nuestras Diarias para derrotar a este monstruo antes de que posea a alguien más.",
+ "questBewilderBossRageStables": "`¡El Obnubilave usa ATAQUE ENGAÑADOR!`\n\n¡¡¡Ahh!!! Una vez más el Obnubilave nos ha deslumbrado y ha hecho que descuidemos nuestras Diarias, ¡y ahora ha atacado a Matt el Maestro de las Bestias! Con un remolino de niebla, Matt se transforma en una aterradora criatura alada, y todas las mascotas y monturas aúllan tristemente en sus establos. Rápido, ¡manténganse enfocados en sus tareas para vencer a esta cruel distracción!",
+ "questBewilderBossRageBailey": "`¡El Obnubilave usa ATAQUE ENGAÑADOR!`\n\n¡Cuidado! En medio del informe de las noticias, ¡Bailey la Pregonera ha sido poseída por el Obnubilave! Bailey deja escapar un chillido maligno y poco informativo a medida que asciende en el aire. Ahora, ¿cómo sabremos qué está ocurriendo?\n\nNo se rindan... ¡estamos tan cerca de derrotar a esta molesta ave de una vez por todas!",
+ "questFalconText": "Las Aves de la Procrastinación",
+ "questFalconNotes": "El Monte Habitica ha sido ensombrecido por una montaña de pendientes. Solía ser un lugar para ir de picnic y disfrutar de una sensación de logro, hasta que las tareas descuidadas se salieron de control. Ahora es el hogar de las temibles Aves de la Procrastinación, ¡criaturas terribles que no permiten a los Habiticanos completar sus tareas!
\"¡Es demasiado difícil!\" les graznan a @JonArinbjorn y a @Onheiron. \"¡Tomará mucho tiempo si lo hacen ahora! ¡No habrá diferencia si lo dejan para mañana! ¿Por qué mejor no hacen algo divertido?\"
No más, juras. ¡Escalarás tu montaña personal de Pendientes y vencerás a las Aves de la Procrastinación!",
+ "questFalconCompletion": "Habiendo triunfado finalmente sobre las Aves de la Procrastinación, te sientas para disfrutar la vista y un merecido descanso.
\"¡Guau!\" dice @Trogdorina. \"¡Ganaste!\"
@Squish agrega, \"Ten, llévate estos huevos que encontré como recompensa.\"",
+ "questFalconBoss": "Aves de la Procrastinación",
+ "questFalconDropFalconEgg": "Halcón (Huevo)",
+ "questFalconUnlockText": "Desbloquea huevos de Halcón adquiribles en el Mercado",
+ "questTreelingText": "The Tangle Tree",
+ "questTreelingNotes": "It's the annual Garden Competition, and everyone is talking about the mysterious project which @aurakami has promised to unveil. You join the crowd on the day of the big announcement, and marvel at the introduction of a moving tree. @fuzzytrees explains that the tree will help with garden maintenance, showing how it can mow the lawn, trim the hedge and prune the roses all at the same time – until the tree suddenly goes wild, turning its secateurs on its creator! The crowd panics as everyone tries to flee, but you aren't afraid – you leap forward, ready to do battle.",
+ "questTreelingCompletion": "You dust yourself off as the last few leaves drift to the floor. In spite of the upset, the Garden Competition is now safe – although the tree you just reduced to a heap of wood chips won't be winning any prizes! \"Still a few kinks to work out there,\" @PainterProphet says. \"Perhaps someone else would do a better job of training the saplings. Do you fancy a go?\"",
+ "questTreelingBoss": "Tangle Tree",
+ "questTreelingDropTreelingEgg": "Treeling (Egg)",
+ "questTreelingUnlockText": "Unlocks purchasable Treeling eggs in the Market"
}
\ No newline at end of file
diff --git a/common/locales/es_419/rebirth.json b/common/locales/es_419/rebirth.json
index a589e7fc01..4eb6ad75e5 100644
--- a/common/locales/es_419/rebirth.json
+++ b/common/locales/es_419/rebirth.json
@@ -5,7 +5,7 @@
"rebirthStartOver": "Renacimiento reinicia a tu personaje al nivel 1.",
"rebirthAdvList1": "Tu barra de Salud vuelve a estar llena.",
"rebirthAdvList2": "No tienes Experiencia, Oro ni Equipamiento (con la excepción de artículos gratis como los Artículos Misteriosos).",
- "rebirthAdvList3": "Tus Hábitos, Diarias, y Pendientes vuelven al color amarillo, y las rachas se reinician.",
+ "rebirthAdvList3": "Your Habits, Dailies, and To-Dos reset to yellow, and streaks reset, except for challenge tasks.",
"rebirthAdvList4": "Comienzas como Guerrero hasta que consigas una nueva clase.",
"rebirthInherit": "Tu nuevo personaje hereda unas pocas cosas de su predecesor:",
"rebirthInList1": "Las tareas, los historiales y los ajustes permanecen.",
@@ -24,5 +24,6 @@
"rebirthPop": "Comenzar con un personaje nuevo desde el Nivel 1 conservando logros, coleccionables y tareas con historial.",
"rebirthName": "Esfera de Renacimiento",
"reborn": "Renacido, nivel máximo <%= reLevel %>",
- "confirmReborn": "¿Estás seguro?"
+ "confirmReborn": "¿Estás seguro?",
+ "rebirthComplete": "You have been reborn!"
}
\ No newline at end of file
diff --git a/common/locales/es_419/settings.json b/common/locales/es_419/settings.json
index 93a241c488..cae930985e 100644
--- a/common/locales/es_419/settings.json
+++ b/common/locales/es_419/settings.json
@@ -47,6 +47,7 @@
"customDayStart": "Comienzo de día personalizado",
"changeCustomDayStart": "¿Cambiar día de inicio personalizado?",
"sureChangeCustomDayStart": "¿Estás seguro de que quieres cambiar tu día de inicio personalizado?",
+ "customDayStartHasChanged": "Your custom day start has changed.",
"nextCron": "Tus Diarias se reiniciarán la primera vez que uses Habitica después de <%= time %>. ¡Asegúrate de haber completado tus Diarias antes de esa hora!",
"customDayStartInfo1": "Habitica por defecto chequea y reinicia tus Diarias a la medianoche en tu propia zona horaria cada día. Puedes personalizar ese horario aquí.",
"misc": "Varios",
@@ -61,12 +62,23 @@
"newUsername": "Nuevo nombre de usuario",
"dangerZone": "Zona peligrosa",
"resetText1": "¡AVISO! Esto reinicia muchas partes de tu cuenta. Esto no es recomendable, pero ha resultado útil para algunas personas al principio después de jugar con el sitio web por un corto tiempo.",
- "resetText2": "Perderás todos tus niveles, oro, y puntos de experiencia. Todas tus tareas y su historial serán borrados permanentemente. Perderás todo tu equipamiento pero podrás comprarlo de nuevo en el futuro, incluyendo todo el equipamiento de edición limitada o Artículos Misteriosos de suscriptor que ya posees (tendrás que estar en la clase correcta para poder volver a comprar equipamiento limitado a cierta clases). Te quedarás con tu clase actual y tus mascotas y monturas. Es aconsejable usar una Esfera de Renacimiento en lugar de reiniciar tu cuenta, ya que es una alternativa más segura y preservará tus tareas.",
+ "resetText2": "You will lose all your levels, gold, and experience points. All your tasks (except those from challenges) will be deleted permanently and you will lose all of their historical data. You will lose all your equipment but you will be able to buy it all back, including all limited edition equipment or subscriber Mystery items that you already own (you will need to be in the correct class to re-buy class-specific gear). You will keep your current class and your pets and mounts. You might prefer to use an Orb of Rebirth instead, which is a much safer option and which will preserve your tasks.",
"deleteText": "¿Estás seguro? ¡Esto borrará tu cuenta para siempre, y no podrá ser recuperada! Necesitarás registrar una cuenta nueva para volver a usar Habitica. Las Gemas no serán reembolsadas. Si estás absolutamente seguro escribe <%= deleteWord %> en la casilla de abajo.",
"API": "API",
+ "APIv3": "API v3",
+ "APIv2": "API v2 - Deprecated",
"APIText": "Puedes copiar éstos para usarlos en aplicaciones de terceros. Sin embargo, considera tu Ficha API como una contraseña y no la compartas públicamente. De vez en cuando se te puede pedir tu ID de usuario, pero nunca publiques tu Ficha API donde otros puedan verla, incluyendo en Github.",
"APIToken": "Ficha API (ésta es una contraseña - ¡lee el aviso de arriba!)",
+ "thirdPartyApps": "Aplicaciones de terceros",
+ "dataToolDesc": "Una página web que te muestra cierta información de tu cuenta de Habitica, como estadísticas de tus tareas, equipamiento y habilidades.",
+ "beeminder": "Beeminder",
+ "beeminderDesc": "Deja que Beeminder monitoree automáticamente tus Pendientes de Habitica. Puedes comprometerte a mantener un número de Pendientes realizadas por día o por semana, o puedes comprometerte a reducir gradualmente el número restante de Pendientes sin completar. (¡Al decir \"comprometerte\" Beeminder se refiere a estar bajo el peligro de pagar dinero real! Pero también puede que sólo te gusten los gráficos sofisticados de Beeminder.)",
+ "chromeChatExtension": "Extensión de chat de Chrome",
+ "chromeChatExtensionDesc": "La extensión de chat de Chrome para Habitica agrega una ventana de chat intuitiva en todo habitica.com. Esto permite a los usuarios charlar en la taberna, en su equipo y en cualquier gremio del que formen parte.",
+ "otherExtensions": "Otras extensiones",
+ "otherDesc": "Encuentra otras aplicaciones, extensiones y herramientas en la wiki de Habitica.",
"resetDo": "¡Adelante, reinicia mi cuenta!",
+ "resetComplete": "Reset complete!",
"fixValues": "Ajustar valores",
"fixValuesText1": "Si has encontrado o cometido un error que injustamente cambió a tu personaje (daño que no merecías, oro que no ganaste, etc.), puedes corregir tus números aquí. Sí, esto permite que puedas hacer trampa: usa esta función con discreción, ¡o arruinarás la creación de tus hábitos!",
"fixValuesText2": "Ten en cuenta que no puedes restaurar Rachas de tareas individuales aquí. Para hacer eso, entra en el menú de edición de la Diaria y ve a Opciones Avanzadas, donde encontrarás el campo para restaurar rachas.",
@@ -96,6 +108,7 @@
"emailNotifications": "Notificaciones vía correo electrónico",
"wonChallenge": "¡Ganaste un Desafío!",
"newPM": "Mensaje privado recibido",
+ "sentGems": "Sent gems!",
"giftedGems": "Gemas regaladas",
"giftedGemsInfo": "<%= amount %> Gemas - por <%= name %>",
"giftedSubscription": "Suscripción regalada",
@@ -135,6 +148,11 @@
"webhooks": "Webhooks",
"enabled": "Activado",
"webhookURL": "URL del Webhook",
+ "invalidUrl": "invalid url",
+ "invalidEnabled": "the \"enabled\" parameter should be a boolean",
+ "regIdRequired": "RegId is required",
+ "pushDeviceAdded": "Push device added successfully",
+ "pushDeviceAlreadyAdded": "The user already has the push device",
"add": "Agregar",
"buyGemsGoldCap": "Tope aumentado a <%= amount %>",
"mysticHourglass": "<%= amount %> Reloj de Arena Místico",
@@ -149,5 +167,5 @@
"amazonPayments": "Amazon Payments",
"timezone": "Zona horaria",
"timezoneUTC": "Habitica utiliza la zona horaria de tu PC, la cual es <%= utc %>",
- "timezoneInfo": "Si esa zona horaria es incorrecta, primero actualiza esta página usando el botón de actualizar de tu navegador para asegurar que Habitica tiene la información más reciente. Si sigue siendo incorrecta, ajusta la zona horaria en tu PC y luego actualiza la página una vez más.
Si usas Habitica en otras PCs o dispositivos móviles, la zona horaria tiene que ser la misma en todos ellos. Si tus Diarias se han estado reiniciando en el horario incorrecto, repite esta prueba en todas las otras PCs y en el navegador de tus dispositivos móviles."
+ "timezoneInfo": "If that time zone is wrong, first reload this page using your browser's reload or refresh button to ensure that Habitica has the most recent information. If it is still wrong, adjust the time zone on your PC and then reload this page again.
If you use Habitica on other PCs or mobile devices, the time zone must be the same on them all. If your Dailies have been resetting at the wrong time, repeat this check on all other PCs and on a browser on your mobile devices."
}
\ No newline at end of file
diff --git a/common/locales/es_419/spells.json b/common/locales/es_419/spells.json
index 6a852113ee..d82e355a3b 100644
--- a/common/locales/es_419/spells.json
+++ b/common/locales/es_419/spells.json
@@ -35,8 +35,8 @@
"spellSpecialSnowballAuraNotes": "¡Lanza una bola de nieve a un miembro de tu equipo! ¿Qué podría salir mal? Dura hasta el nuevo día de tu compañero.",
"spellSpecialSaltText": "Sal",
"spellSpecialSaltNotes": "Alguien te ha lanzado una bola de nieve. Ja ja, muy gracioso. ¡Ahora quítame esta nieve de encima!",
- "spellSpecialSpookDustText": "Brillantina Espeluznante",
- "spellSpecialSpookDustNotes": "¡Convierte a un amigo en una manta flotante con ojos!",
+ "spellSpecialSpookySparklesText": "Spooky Sparkles",
+ "spellSpecialSpookySparklesNotes": "Turn a friend into a floating blanket with eyes!",
"spellSpecialOpaquePotionText": "Poción Opaca",
"spellSpecialOpaquePotionNotes": "Cancela los efectos de la Brillantina Espeluznante.",
"spellSpecialShinySeedText": "Semilla Radiante",
@@ -46,5 +46,11 @@
"spellSpecialSeafoamText": "Espuma de Mar",
"spellSpecialSeafoamNotes": "¡Convierte a un amigo en una criatura marina!",
"spellSpecialSandText": "Arena",
- "spellSpecialSandNotes": "Cancela los efectos de la Espuma de Mar."
+ "spellSpecialSandNotes": "Cancela los efectos de la Espuma de Mar.",
+ "spellNotFound": "Skill \"<%= spellId %>\" not found.",
+ "partyNotFound": "Party not found",
+ "targetIdUUID": "\"targetId\" must be a valid User ID.",
+ "challengeTasksNoCast": "Casting a skill on challenge tasks is not allowed.",
+ "spellNotOwned": "You don't own this skill.",
+ "spellLevelTooHigh": "You must be level <%= level %> to use this skill."
}
\ No newline at end of file
diff --git a/common/locales/es_419/subscriber.json b/common/locales/es_419/subscriber.json
index 6ecad3dd2c..4952211ebe 100644
--- a/common/locales/es_419/subscriber.json
+++ b/common/locales/es_419/subscriber.json
@@ -4,6 +4,8 @@
"subDescription": "Compra gemas con oro, obtén objetos misteriosos mensuales, guarda tu historial de progreso, duplica tu máximo de botines diarios, apoya a los desarrolladores. Haz clic para más información.",
"buyGemsGold": "Compra Gemas con oro",
"buyGemsGoldText": "Alexander el Comerciante te venderá gemas a un costo de <%= gemCost %> oro por gema. Sus envíos mensuales tienen un tope inicial de <%= gemLimit %> gemas por mes, pero este tope aumenta de a 5 gemas por cada tres meses de suscripción continua, ¡hasta un máximo de 50 gemas por mes!",
+ "mustSubscribeToPurchaseGems": "Must subscribe to purchase gems with GP",
+ "reachedGoldToGemCap": "You've reached the Gold=>Gem conversion cap <%= convCap %> for this month. We have this to prevent abuse / farming. The cap will reset within the first three days of next month.",
"retainHistory": "Conserva entradas de historial adicionales",
"retainHistoryText": "Hace que tus Pendientes completadas y tu historial de tareas estén disponibles por más tiempo.",
"doubleDrops": "Topes diarios de botines duplicados",
@@ -29,6 +31,7 @@
"manageSub": "Haz clic para administrar la suscripción",
"cancelSub": "Cancelar la suscripción",
"canceledSubscription": "Suscripción Cancelada",
+ "cancelingSubscription": "Canceling the subscription",
"adminSub": "Suscripción de administradores",
"morePlans": "Más planes Próximamente",
"organizationSub": "Organización privada",
@@ -73,6 +76,9 @@
"timeTravelersPopover": "Vemos que tienes un Reloj de Arena Místico, ¡asi que con gusto viajaremos al pasado por ti! Por favor elige una mascota, montura, o Conjunto de Artículos Misteriosos que desees. ¡Puedes ver una lista de conjuntos previos aquí! Si ninguno te satisface, ¿podemos ofrecerte uno de nuestros Conjuntos Steampunk futuristas?",
"timeTravelersAlreadyOwned": "¡Felicitaciones! Ya tienes todo lo que los Viajeros del Tiempo ofrecen en este momento. ¡Gracias por apoyar al sitio!",
"mysticHourglassPopover": "Un Reloj de Arena Místico te permite comprar ciertos artículos disponibles por tiempo limitado, como los Conjuntos de Artículos Misteriosos mensuales y premios de los jefes globales pasados!",
+ "mysterySetNotFound": "Mystery set not found, or set already owned.",
+ "mysteryItemIsEmpty": "Mystery items are empty",
+ "mysteryItemOpened": "Mystery item opened.",
"mysterySet201402": "Conjunto de Mensajero Alado",
"mysterySet201403": "Conjunto de Caminante del Bosque",
"mysterySet201404": "Conjunto de Mariposa Crepuscular",
@@ -99,6 +105,8 @@
"mysterySet201601": "Conjunto de Campeón de Resoluciones",
"mysterySet201602": "Conjunto de Rompecorazones",
"mysterySet201603": "Conjunto de Trébol de la Suerte",
+ "mysterySet201604": "Leaf Warrior Set",
+ "mysterySet201605": "Marching Bard Set",
"mysterySet301404": "Conjunto Steampunk Estándar ",
"mysterySet301405": "Conjunto de Accesorios Steampunk",
"mysterySetwondercon": "WonderCon",
@@ -110,9 +118,25 @@
"hourglassBuyItemConfirm": "¿Comprar este artículo por 1 Reloj de Arena Místico?",
"petsAlreadyOwned": "Ya posees esta mascota.",
"mountsAlreadyOwned": "Ya posees esta montura.",
- "typeNotAllowedHourglass": "Este tipo de artículo no puede ser comprado con un Reloj de Arena Místico. Tipos de artículos permitidos:",
+ "typeNotAllowedHourglass": "Item type not supported for purchase with Mystic Hourglass. Allowed types: <%= allowedTypes %>",
"petsNotAllowedHourglass": "Mascota no disponible para la compra con un Reloj de Arena Místico.",
"mountsNotAllowedHourglass": "Montura no disponible para la compra con un Reloj de Arena Místico.",
"hourglassPurchase": "¡Compraste un artículo usando un Reloj de Arena Místico!",
- "hourglassPurchaseSet": "¡Compraste un conjunto de artículos usando un Reloj de Arena Místico!"
+ "hourglassPurchaseSet": "¡Compraste un conjunto de artículos usando un Reloj de Arena Místico!",
+ "missingUnsubscriptionCode": "Missing unsubscription code.",
+ "missingSubscription": "User does not have a plan subscription",
+ "missingSubscriptionCode": "Missing subscription code. Possible values: basic_earned, basic_3mo, basic_6mo, google_6mo, basic_12mo.",
+ "cannotDeleteActiveAccount": "You have an active subscription, cancel your plan before deleting your account.",
+ "paymentNotSuccessful": "The payment was not successful",
+ "planNotActive": "The plan hasn't activated yet (due to a PayPal bug). It will begin <%= nextBillingDate %>, after which you can cancel to retain your full benefits",
+ "notAllowedHourglass": "Pet/Mount not available for purchase with Mystic Hourglass.",
+ "readCard": "<%= cardType %> has been read",
+ "cardTypeRequired": "Card type required",
+ "cardTypeNotAllowed": "Unknown card type.",
+ "invalidCoupon": "Invalid coupon code.",
+ "couponUsed": "Coupon code already used.",
+ "noSudoAccess": "You don't have sudo access.",
+ "couponCodeRequired": "The coupon code is required.",
+ "eventRequired": "\"req.params.event\" is required.",
+ "countRequired": "\"req.query.count\" is required."
}
\ No newline at end of file
diff --git a/common/locales/es_419/tasks.json b/common/locales/es_419/tasks.json
index d72c04599e..83bdbbafd6 100644
--- a/common/locales/es_419/tasks.json
+++ b/common/locales/es_419/tasks.json
@@ -73,6 +73,7 @@
"clearTags": "Borrar",
"hideTags": "Ocultar",
"showTags": "Mostrar",
+ "toRequired": "You must supply a to value",
"startDate": "Día de inicio",
"startDateHelpTitle": "¿Cuándo debería comenzar esta tarea?",
"startDateHelp": "Ajusta la fecha en la que esta tarea comenzará a tener efecto. No estará prevista para días anteriores.",
@@ -88,8 +89,9 @@
"fortifyName": "Poción de Fortalecimiento",
"fortifyPop": "Devuelve todas tus tareas a un valor neutral (amarillo), y recupera toda la salud perdida.",
"fortify": "Fortalecer",
- "fortifyText": "Fortalecer devuelve todas tus tareas a un valor neutral (amarillo), como si las acabaras de añadir, y llena a tope tu salud. Esto es excelente si todas tus tareas rojas están dificultando mucho el juego, o si todas tus tareas azules están haciéndolo muy fácil. Si empezar de cero suena mucho más motivador, ¡gasta las gemas y tómate un respiro!",
+ "fortifyText": "Fortify will return all your tasks, except challenge tasks, to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!",
"confirmFortify": "¿Estás seguro?",
+ "fortifyComplete": "Fortify complete!",
"sureDelete": "¿Estás seguro de que quieres eliminar el/la <%= taskType %> que posee el texto \"<%= taskText %>\"?",
"streakCoins": "¡Bonus de racha!",
"pushTaskToTop": "Enviar la tarea al inicio de la lista. Mantén presionado ctrl (o cmd) para enviar al final.",
@@ -112,5 +114,18 @@
"rewardHelp2": "El Equipamiento afecta tus estadísticas (<%= linkStart %>Avatar > Estadísticas<%= linkEnd %>).",
"rewardHelp3": "Equipamiento especial aparecerá aquí durante Eventos Globales.",
"rewardHelp4": "¡No tengas miedo de crear Recompensas personalizadas! Echa un vistazo a algunos ejemplos aquí.",
- "clickForHelp": "Clic para obtener ayuda"
+ "clickForHelp": "Clic para obtener ayuda",
+ "taskIdRequired": "\"taskId\" must be a valid UUID.",
+ "taskNotFound": "Task not found.",
+ "invalidTaskType": "Task type must be one of \"habit\", \"daily\", \"todo\", \"reward\".",
+ "cantDeleteChallengeTasks": "A task belonging to a challenge can't be deleted.",
+ "checklistOnlyDailyTodo": "Checklists are supported only on dailies and todos",
+ "checklistItemNotFound": "No checklist item was found with given id.",
+ "itemIdRequired": "\"itemId\" must be a valid UUID.",
+ "tagNotFound": "No tag item was found with given id.",
+ "tagIdRequired": "\"tagId\" must be a valid UUID corresponding to a tag belonging to the user.",
+ "positionRequired": "\"position\" is required and must be a number.",
+ "cantMoveCompletedTodo": "Can't move a completed todo.",
+ "directionUpDown": "\"direction\" is required and must be 'up' or 'down'",
+ "alreadyTagged": "The task is already tagged with given tag."
}
\ No newline at end of file
diff --git a/common/locales/fr/backgrounds.json b/common/locales/fr/backgrounds.json
index b9bd207af0..66113e1887 100644
--- a/common/locales/fr/backgrounds.json
+++ b/common/locales/fr/backgrounds.json
@@ -154,11 +154,18 @@
"backgroundRainforestNotes": "Aventurez-vous dans une Forêt Tropicale.",
"backgroundStoneCircleText": "Cercle de Pierres",
"backgroundStoneCircleNotes": "Jetez des sorts dans un Cercle de Pierres.",
- "backgrounds042016": "SET 23: Released April 2016",
- "backgroundArcheryRangeText": "Archery Range",
- "backgroundArcheryRangeNotes": "Practice on the Archery Range.",
- "backgroundGiantFlowersText": "Giant Flowers",
- "backgroundGiantFlowersNotes": "Frolic atop Giant Flowers.",
- "backgroundRainbowsEndText": "End of the Rainbow",
- "backgroundRainbowsEndNotes": "Discover gold at the End of the Rainbow."
+ "backgrounds042016": "Ensemble 23 : Sorti en avril 2016",
+ "backgroundArcheryRangeText": "Terrain de Tir à l'arc",
+ "backgroundArcheryRangeNotes": "S'entraîner sur le Terrain de Tir à l'arc",
+ "backgroundGiantFlowersText": "Fleurs Géantes",
+ "backgroundGiantFlowersNotes": "Batifoler sur des Fleurs Géantes",
+ "backgroundRainbowsEndText": "L'Extrêmité de l'Arc-en-ciel",
+ "backgroundRainbowsEndNotes": "Découvrez de l'or à l'Extrêmité de l'Arc-en-ciel",
+ "backgrounds052016": "Ensemble 24 : Sorti en mai 2016",
+ "backgroundBeehiveText": "Ruche",
+ "backgroundBeehiveNotes": "Bourdonnez et dansez dans une ruche.",
+ "backgroundGazeboText": "Belvédère",
+ "backgroundGazeboNotes": "Combattez dans un belvédère.",
+ "backgroundTreeRootsText": "Racines d'arbre",
+ "backgroundTreeRootsNotes": "Explorez les racines d'un arbre."
}
\ No newline at end of file
diff --git a/common/locales/fr/challenge.json b/common/locales/fr/challenge.json
index b88ace32fc..78b8c0f564 100644
--- a/common/locales/fr/challenge.json
+++ b/common/locales/fr/challenge.json
@@ -63,5 +63,20 @@
"congratulations": "Félicitations !",
"hurray": "Hourra !",
"noChallengeOwner": "Sans propriétaire",
- "noChallengeOwnerPopover": "Ce défi n'a pas de propriétaire, parce que la personne qui a créé le défi a supprimé son compte."
+ "noChallengeOwnerPopover": "Ce défi n'a pas de propriétaire, parce que la personne qui a créé le défi a supprimé son compte.",
+ "challengeMemberNotFound": "L'utilisateur n'a pas été trouvé parmi les membres du défi",
+ "onlyGroupLeaderChal": "Seul le responsable du groupe peut créer des défis",
+ "tavChalsMinPrize": "La récompense doit être d'au moins 1 gemme pour les défis de la Taverne.",
+ "cantAfford": "Vous ne pouvez pas payer cette récompense. Achetez plus de gemmes ou diminuez le montant de la récompense.",
+ "challengeIdRequired": "\"challengeId\" doit être un UUID valide.",
+ "winnerIdRequired": "\"winnerId\" doit être un UUID valide.",
+ "challengeNotFound": "Défi non trouvé.",
+ "onlyLeaderDeleteChal": "Seul le responsable du défi peut le supprimer.",
+ "onlyLeaderUpdateChal": "Seul le responsable du défi peut le mettre à jour.",
+ "winnerNotFound": "Le vainqueur avec l'identifiant \"<%= userId %> n'a pas été trouvé ou ne fait pas partie du défi.",
+ "noCompletedTodosChallenge": "\"includeComepletedTodos\" n'est pas supporté lors de la récupération des tâches d'un défi.",
+ "userTasksNoChallengeId": "Lorsque \"tasksOwner\" est \"user\", \"challengeId\" ne peut être passé.",
+ "onlyChalLeaderEditTasks": "Les tâches appartenant à un défi ne peuvent être modifiées que par son responsable.",
+ "userAlreadyInChallenge": "L'utilisateur participe déjà à ce défi.",
+ "cantOnlyUnlinkChalTask": "Seules les tâches des défis cassés peuvent être déliées."
}
\ No newline at end of file
diff --git a/common/locales/fr/character.json b/common/locales/fr/character.json
index 9202d8e8a6..12972d98b2 100644
--- a/common/locales/fr/character.json
+++ b/common/locales/fr/character.json
@@ -1,4 +1,5 @@
{
+ "communityGuidelinesWarning": "Gardez à l'esprit, s'il-vous-plaît, que votre nom d'utilisateur, votre photo de profil et votre résumé doivent obéir aux Règles de Vie en Communauté (par exemple, grossièretés, sujets adultes, insultes, etc. sont interdits). Si vous ne savez pas si quelque chose est inconvenant ou non, n'hésitez pas à envoyer un courriel à leslie@habitica.com!",
"statsAch": "Caractéristiques & Succès",
"profile": "Profil",
"avatar": "Personnalisez votre avatar",
@@ -34,7 +35,7 @@
"beard": "Barbe",
"mustache": "Moustache",
"flower": "Fleur",
- "wheelchair": "Wheelchair",
+ "wheelchair": "Fauteuil roulant",
"basicSkins": "Peaux de base",
"rainbowSkins": "Peaux arc-en-ciel",
"pastelSkins": "Peaux Pastel",
@@ -58,7 +59,7 @@
"costumeText": "Si vous préférez l'apparence d'un équipement différent de celui que vous avez équipé, choisissez \"Utiliser un Costume\" pour le porter en costume tout en conservant les bonus de votre équipement.",
"useCostume": "Utiliser un Costume",
"useCostumeInfo1": "Cliquez sur \"Utiliser un Costume\" pour mettre des vêtements à votre avatar sans modifier les stats de votre Tenue de Combat! Vous pouvez donc vous armer des meilleures stats à gauche et déguiser votre avatar avec l'équipement à droite.",
- "useCostumeInfo2": "Lorsque vous cliquez sur \"Utiliser un Costume\", votre avatar aura l'air plutôt basique... mais pas d'inquiétude ! Si vous regardez à gauche, vous verrez que votre Tenue de Combat est toujours active. Ensuite, vous pouvez faire jouer votre imagination ! Tout ce que vous activez à droite ne modifiera pas vos stats mais peut vous donner un look d'enfer. Essayez différentes associations en mélangeant les ensembles et en accordant votre Costume avec vos familiers, montures et arrière-plans.
Vous avez d'autres questions ? Allez voir la page sur les Costumes sur le Wiki. Vous avez trouvé l'ensemble parfait ? Exhibez-le sur la guilde du Festival des Costumes ou fanfaronnez à la Taverne !",
+ "useCostumeInfo2": "Lorsque vous cliquez sur \"Utiliser un Costume\", votre avatar aura l'air plutôt basique... mais pas d'inquiétude ! Si vous regardez à gauche, vous verrez que votre Tenue de Combat est toujours active. Ensuite, vous pouvez faire jouer votre imagination ! Tout ce que vous activez à droite ne modifiera pas vos stats mais peut vous donner un look d'enfer. Essayez différentes associations en mélangeant les ensembles et en accordant votre Costume avec vos familiers, montures et arrière-plans.
Vous avez d'autres questions ? Allez voir la page sur les Costumes sur le Wiki. Vous avez trouvé l'ensemble parfait ? Exhibez-le sur la guilde du Festival des Costumes ou fanfaronnez à la Taverne !",
"gearAchievement": "Vous avez gagné le succès \"Armé jusqu'aux dents\" pour avoir atteint le niveau maximal de l'ensemble d'équipement de votre classe ! Vous avez acquis les ensembles complets suivants:",
"moreGearAchievements": "Pour obtenir plus de badges \"Armé jusqu'aux dents\", changez de classe sur votre page Caractéristiques & Succès et achetez peu à peu l'équipement de votre nouvelle classe !",
"armoireUnlocked": "Vous avez aussi déverrouillé l'Armoire Enchantée! Cliquez sur la Récompense Armoire Enchantée pour tenter de trouver une pièce d'Équipement spécial au hasard! Vous avez aussi des chances de tomber sur de l'XP ou de la nourriture.",
@@ -109,6 +110,7 @@
"mage": "Mage",
"mystery": "Mystère",
"changeClass": "Changer de classe et rembourser les points d'attribut.",
+ "lvl10ChangeClass": "Vous devez être au moins au niveau 10 pour changer de classe.",
"levelPopover": "Vous pouvez affecter un point à un attribut de votre choix à chaque niveau gagné. Cette attribution peut se faire manuellement ou en laissant le jeu décider pour vous, en utilisant une des options d'Attribution Automatique.",
"unallocated": "Points d'Attribut non alloués",
"haveUnallocated": "Vous avez <%= points %> Point(s) d'Attribut non alloué(s).",
@@ -164,5 +166,7 @@
"int": "INT",
"showQuickAllocation": "Voir la répartition des statistiques",
"hideQuickAllocation": "Cacher la répartition des statistiques",
- "quickAllocationLevelPopover": "Chaque niveau vous rapporte un point que vous pouvez assigner à un attribut de votre choix. Vous pouvez le faire manuellement ou laissez le jeu décider pour vous, en utilisant les options d'Attribution Automatique qui se trouvent dans Utilisateur -> Caractéristiques."
+ "quickAllocationLevelPopover": "Chaque niveau vous rapporte un point que vous pouvez assigner à un attribut de votre choix. Vous pouvez le faire manuellement ou laissez le jeu décider pour vous, en utilisant les options d'Attribution Automatique qui se trouvent dans Utilisateur -> Caractéristiques.",
+ "invalidAttribute": "\"<%= attr %>\" n'est pas un attribut valide.",
+ "notEnoughAttrPoints": "Vous n'avez pas assez de points d'attribut."
}
\ No newline at end of file
diff --git a/common/locales/fr/content.json b/common/locales/fr/content.json
index 3c65cfcbb5..e23935ffc9 100644
--- a/common/locales/fr/content.json
+++ b/common/locales/fr/content.json
@@ -113,6 +113,12 @@
"questEggSnailText": "Escargot",
"questEggSnailMountText": "Escargot",
"questEggSnailAdjective": "Un lent mais régulier",
+ "questEggFalconText": "Faucon",
+ "questEggFalconMountText": "Faucon",
+ "questEggFalconAdjective": "un rapide",
+ "questEggTreelingText": "Arbrisseau",
+ "questEggTreelingMountText": "Arbrisseau",
+ "questEggTreelingAdjective": "un feuillu",
"eggNotes": "Trouvez une potion d’éclosion à verser sur cet œuf et il en sortira <%= eggAdjective(locale) %> <%= eggText(locale) %>.",
"hatchingPotionBase": "de Base",
"hatchingPotionWhite": "Blanc",
@@ -126,6 +132,7 @@
"hatchingPotionGolden": "Doré",
"hatchingPotionSpooky": "Effrayant",
"hatchingPotionPeppermint": "Menthe poivrée",
+ "hatchingPotionFloral": "Floral",
"hatchingPotionNotes": "Versez-la sur un œuf et il en sortira un familier <%= potText(locale) %>.",
"premiumPotionAddlNotes": "N'est pas utilisable sur les œufs de quête.",
"foodMeat": "Viande",
diff --git a/common/locales/fr/contrib.json b/common/locales/fr/contrib.json
index f28b96f770..dfffda3394 100644
--- a/common/locales/fr/contrib.json
+++ b/common/locales/fr/contrib.json
@@ -35,8 +35,12 @@
"hallContributors": "Panthéon des contributeurs",
"hallPatrons": "Panthéon des Sponsors",
"rewardUser": "Récompenser l'Utilisateur",
- "UUID": "UUID",
+ "UUID": "ID d'utilisateur",
"loadUser": "Charger l'Utilisateur",
+ "noAdminAccess": "Vous n'avez pas l'accès administrateur.",
+ "pageMustBeNumber": "req.query.page doit être un nombre",
+ "userNotFound": "Utilisateur introuvable.",
+ "invalidUUID": "UUID doit être valide.",
"title": "Titre",
"moreDetails": "Plus de détails (1-7)",
"moreDetails2": "plus de détails (8-9)",
@@ -52,13 +56,13 @@
"visitHeroes": "Visitez le Panthéon des Héros (contributeurs et soutiens)",
"conLearn": "En savoir plus à propos des récompenses des contributeurs",
"conLearnHow": "En savoir plus sur les façons de contribuer à Habitica",
- "surveysSingle": "A aidé Habitica à croître en participant à une enquête. Il n'y a actuellement pas d'enquête en cours.",
- "surveysMultiple": "A aidé Habitica à croître en participant à <%= surveys %> enquêtes. Il n'y a actuellement pas d'enquête en cours.",
+ "surveysSingle": "Helped Habitica grow, either by filling out a survey or helping with a major testing effort. Thank you!",
+ "surveysMultiple": "Helped Habitica grow on <%= surveys %> occasions, either by filling out a survey or helping with a major testing effort. Thank you!",
"currentSurvey": "Enquête Actuelle",
"surveyWhen": "Le badge sera attribué à tou·te·s les participant·e·s de l'enquête lorsque celle-ci aura été traitée, fin Mars.",
"blurbInbox": "Voici où sont stockés vos messages privés ! Vous pouvez envoyer un message à quelqu'un en cliquant sur l'icône en forme d'enveloppe à côté de leur nom dans l'espace de discution de la Taverne, de l'Équipe ou de la Guilde. Si vous avez reçu un message inapproprié, vous pouvez en envoyer une capture d'écran par mail à Lemoness (leslie@habitica.com)",
"blurbGuildsPage": "Les Guildes sont des groupes de discussion créés par les joueurs pour les joueurs pour regrouper les personnes autour de leurs centres d'intérêt. Jetez un œil à la liste et rejoignez les Guildes qui vous intéressent !",
"blurbChallenges": "Les Défis sont des listes de tâches créées par des membres ! Rejoindre un défi ajoutera ses tâches aux vôtres dans la page des tâches ; gagner un Défi vous fera gagner un succès ainsi que, souvent, des gemmes !",
"blurbHallPatrons": "Voici le Panthéon des Sponsors, où nous rendons hommage aux nobles aventuriers qui ont soutenu le Kickstarter original de Habitica. Nous les remercions de nous avoir aidés à donner la vie à Habitica !",
- "blurbHallContributors": "Voici le Panthéon des Contributeurs, où sont honorés tous les contributeurs \"open-source\" d'Habitica. Que ce soit grâce à du code, de l'art, de la musique, de l'écriture ou même simplement de l'aide, ils ont gagné des gemmes, de l'équipement exclusif, et des titres prestigieux. Vous pouvez, vous aussi, contribuer à Habitica ! Apprenez-en plus ici."
+ "blurbHallContributors": "Voici le Panthéon des Contributeurs, où sont honorés tous les contributeurs \"open-source\" d'Habitica. Que ce soit grâce à du code, de l'art, de la musique, de l'écriture ou même simplement de l'aide, ils ont gagné des gemmes, de l'équipement exclusif, et des titres prestigieux. Vous pouvez, vous aussi, contribuer à Habitica ! Apprenez-en plus ici."
}
\ No newline at end of file
diff --git a/common/locales/fr/death.json b/common/locales/fr/death.json
index 0b324693f1..eaf99dd47c 100644
--- a/common/locales/fr/death.json
+++ b/common/locales/fr/death.json
@@ -3,7 +3,7 @@
"dontDespair": "Ne désespérez pas !",
"deathPenaltyDetails": "Vous avez perdu un niveau, de l'or, et une pièce d'équipement, mais vous pouvez tous les récupérer en travaillant dur ! Bonne chance--vous y arriverez.",
"refillHealthTryAgain": "Reprendre vie et essayer à nouveau",
- "dyingOftenTips": "Cela vous arrive trop souvent ? Voici quelques astuces !",
+ "dyingOftenTips": "Cela vous arrive trop souvent ? Voici quelques astuces !",
"losingHealthWarning": "Attention - Vous perdez de la Vie !",
"losingHealthWarning2": "Ne laissez pas votre Vie tomber à zéro ! Si cela arrive, vous perdrez un niveau, votre Or, et un élément d'équipement.",
"toRegainHealth": "Pour regagner de la Vie :",
@@ -12,5 +12,6 @@
"losingHealthQuickly": "Vous perdez de la Vie rapidement ?",
"lowHealthTips3": "Les tâches Quotidiennes non accomplies vous infligent des dégâts pendant la nuit, faites attention à ne pas en créer trop au début !",
"lowHealthTips4": "Si une tâche Quotidienne n'est pas due certains jours, vous pouvez la désactiver en cliquant sur l'icône en forme de crayon.",
- "goodLuck": "Bonne chance !"
+ "goodLuck": "Bonne chance !",
+ "cannotRevive": "Impossible de ressusciter si vous n'êtes pas mort"
}
\ No newline at end of file
diff --git a/common/locales/fr/faq.json b/common/locales/fr/faq.json
index fa9462c45d..d2b23e6eab 100644
--- a/common/locales/fr/faq.json
+++ b/common/locales/fr/faq.json
@@ -7,8 +7,8 @@
"iosFaqAnswer1": "Les bonnes Habitudes (celles avec un +) sont les tâches que vous pouvez réaliser plusieurs fois par jour, comme manger des légumes. Les mauvaises Habitudes (celles avec un -) sont les tâches que vous devez éviter, comme vous ronger les ongles. Les Habitudes avec un + et un - ont un bon côté et un mauvais côté, comme prendre l'escalier / prendre l'ascenseur. Les bonnes Habitudes vous récompensent avec de l'expérience et de l'or. Les mauvaises habitudes vous font perdre de la Santé.\n\nLes tâches Quotidiennes sont des tâches que vous devez réaliser chaque jour, comme vous brosser les dents ou vérifier vos courriels. Vous pouvez ajuster les jours où une Quotidienne doit être réalisée en la modifiant. Si vous ratez une Quotidienne qui doit être réalisée, votre avatar subira des dégâts pendant la nuit. Faites attentions à ne pas ajouter trop de Quotidiennes à la fois !\n\nLes tâches À Faire sont votre liste de tâches et de projets. Compléter une tâche À Faire vous récompensera avec de l'or et de l'expérience. Vous ne perdrez jamais de Santé avec les tâches À Faire. Vous pouvez ajouter une date d'échéance à une tâche À Faire en la modifiant.",
"webFaqAnswer1": "Les bonnes Habitudes (celles avec un ) sont les tâches que vous pouvez réaliser plusieurs fois par jour, comme manger des légumes. Les mauvaises Habitudes (celles avec un ) sont les tâches que vous devez éviter, comme vous ronger les ongles. Les Habitudes avec un et un ont un bon côté et un mauvais côté, comme prendre l'escalier / prendre l'ascenseur. Les bonnes Habitudes vous récompensent avec de l'expérience et de l'or. Les mauvaises habitudes vous font perdre de la Santé.\n
\nLes tâches Quotidiennes sont des tâches que vous devez réaliser chaque jour, comme vous brosser les dents ou vérifier vos courriels. Vous pouvez ajuster les jours où une Quotidienne doit être réalisée en cliquant sur le crayon pour la modifier. Si vous ratez une Quotidienne qui doit être réalisée, votre avatar subira des dégâts pendant la nuit. Faites attentions à ne pas ajouter trop de Quotidiennes à la fois !\n
\nLes tâches À Faire sont votre liste de tâches et de projets. Compléter une tâche À Faire vous récompensera avec de l'or et de l'expérience. Vous ne perdrez jamais de Santé avec les tâches À Faire. Vous pouvez ajouter une date d'échéance à une tâche À Faire en cliquant sur le crayon pour la modifier.",
"faqQuestion2": "Quelques exemples de tâches ?",
- "iosFaqAnswer2": "Il y a quatre listes d'exemples de tâches sur le wiki pour vous inspirer :\n
\n* [Exemples d'Habitudes](http://habitica.wikia.com/wiki/Sample_Habits)\n* [Exemples de Quotidiennes](http://habitica.wikia.com/wiki/Sample_Dailies)\n* [Exemples de tâches À Faire](http://habitica.wikia.com/wiki/Sample_To-Dos)\n* [Exemples de Récompenses](http://habitica.wikia.com/wiki/Sample_Custom_Rewards)",
- "webFaqAnswer2": "Il y a quatre listes d'exemples de tâches pour vous inspirer :\n* [Exemples d'Habitudes](http://habitica.wikia.com/wiki/Sample_Habits)\n* [Exemples de Quotidiennes](http://habitica.wikia.com/wiki/Sample_Dailies)\n* [Exemples de tâches À Faire](http://habitica.wikia.com/wiki/Sample_To-Dos)\n* [Exemples de Récompenses](http://habitica.wikia.com/wiki/Sample_Custom_Rewards)",
+ "iosFaqAnswer2": "Il y a quatre listes d'exemples de tâches sur le wiki pour vous inspirer :\n
\n* [Exemples d'Habitudes](http://habitica.wikia.com/wiki/Sample_Habits)\n* [Exemples de Quotidiennes](http://fr.habitica.wikia.com/wiki/Exemples_de_Quotidiennes)\n* [Exemples de tâches À Faire](http://habitica.wikia.com/wiki/Sample_To-Dos)\n* [Exemples de Récompenses](http://habitica.wikia.com/wiki/Sample_Custom_Rewards)",
+ "webFaqAnswer2": "Il y a quatre listes d'exemples de tâches pour vous inspirer :\n* [Exemples d'Habitudes](http://habitica.wikia.com/wiki/Sample_Habits)\n* [Exemples de Quotidiennes](http://fr.habitica.wikia.com/wiki/Exemples_de_Quotidiennes)\n* [Exemples de tâches À Faire](http://habitica.wikia.com/wiki/Sample_To-Dos)\n* [Exemples de Récompenses](http://habitica.wikia.com/wiki/Sample_Custom_Rewards)",
"faqQuestion3": "Pourquoi mes tâches changent-elles de couleur ?",
"iosFaqAnswer3": "Vos tâches changent de couleur en fonction de la manière dont vous les effectuez en ce moment ! Chaque nouvelle tâche est créée neutre, donc jaune. Si vous effectuez des tâches Quotidiennes ou des Habitudes positives plus fréquemment, elles changeront de couleur jusqu'à devenir bleues. Si vous manquez une tâche Quotidienne ou cédez à une mauvaise Habitude, la couleur de la tâche change vers le rouge. Plus une tâche est rouge, plus elle vous fera gagner de récompenses, mais si c'est une tâche Quotidienne ou une mauvaise Habitude, elle vous blessera aussi d'autant plus ! Cela vous aide à trouver la motivation pour effectuer les tâches qui vous posent problème. ",
"webFaqAnswer3": "Vos tâches changent de couleur en fonction de la manière dont vous êtes en train de les effectuer ! Chaque nouvelle tâche est créée en jaune. Si vous effectuez des tâches Quotidiennes ou des Habitudes positives plus fréquemment, elles changeront de couleur jusqu'à devenir bleues. Si vous manquez une tâche Quotidienne ou cédez à une mauvaise Habitude, la couleur de la tâche change vers le rouge. Plus une tâche est rouge, plus elle vous fera gagner de récompenses, mais si c'est une tâche Quotidienne ou une mauvaise Habitude, elle vous blessera aussi d'autant plus ! Cela vous aide à trouver la motivation pour effectuer les tâches qui vous posent problème. ",
@@ -17,10 +17,10 @@
"webFaqAnswer4": "Il y a plusieurs choses qui peuvent vous causer des dégâts. D'abord, si vous laissez de tâches Quotidiennes incomplètes pendant la nuit, elles vous causeront des dégâts. Ensuite, si vous réalisez une mauvaise Habitude, elle vous causera des dégâts. Enfin, si vous et votre Équipe affrontez un Boss, et qu'un de vos compagnons n'a pas réalisé toutes ses tâches quotidiennes, Le Boss vous attaquera.\n
\nLa meilleure façon de guérir est de gagner un niveau, qui vous rendra toute votre santé. Vous pouvez aussi acheter une Potion de Santé avec de l'or à partir de la colonne Récompenses. En plus, au delà du niveau 10, vous pouvez choisir de devenir un Guérisseur, et vous pourrez apprendre les compétences de guérison. Si vous êtes en Équipe (dans Social > Équipe) avec un Guérisseur, il peut aussi vous guérir.",
"faqQuestion5": "Comment jouer à Habitica avec mes amis ?",
"iosFaqAnswer5": "Le meilleur moyen est de les inviter dans une Équipe avec vous. Les Équipes peuvent partir en quêtes, combattre des monstres et lancer des sorts pour s’entraider. Si vous n'avez pas encore d'Équipe, allez dans Social > Équipe et cliquez sur « Créer une nouvelle Équipe », puis cliquez sur la liste de membres et finalement cliquez sur Inviter dans le coin supérieur droit pour inviter vos amis en inscrivant leur ID d'utilisateur (une chaîne de nombres et de lettres que vous pouvez trouver sous Paramètres > Détails du compte, dans l'application, ou sous Paramètres > API sur le site Web). Sur le site Web, vous pouvez également inviter des amis par courriel. Cette option sera ajoutée à l'application lors d'une mise à jour ultérieure.\n\nSur le site Web, vos amis et vous pouvez aussi rejoindre des Guildes, qui sont des salons de discussion publics. Les Guildes seront ajoutés à l'application lors d'une mise à jour ultérieure !",
- "webFaqAnswer5": "Le meilleur moyen est de les inviter dans une Équipe avec vous, en passant par Social > Équipe ! Les Équipes peuvent partir en quêtes, combattre des monstres, et utiliser leurs compétences pour s'entraider. Vous et vos amis pouvez aussi rejoindre des Guildes (Social > Guildes), qui sont des salons de discussion sur un intérêt partagé ou à la poursuite d'un même but, et peuvent être publiques ou privées. Vous pouvez joindre autant de guildes que vous voulez, mais seulement une équipe.\n
\nPour des informations plus précises, lisez sur le wiki les pages à propos des [Équipes](http://habitrpg.wikia.com/wiki/Party) et des [Guildes](http://habitrpg.wikia.com/wiki/Guilds).",
+ "webFaqAnswer5": "Le meilleur moyen est de les inviter dans une Équipe avec vous, en passant par Social > Équipe ! Les Équipes peuvent partir en quêtes, combattre des monstres, et utiliser leurs compétences pour s'entraider. Vous et vos amis pouvez aussi rejoindre des Guildes (Social > Guildes), qui sont des salons de discussion sur un intérêt partagé ou à la poursuite d'un même but, et peuvent être publiques ou privées. Vous pouvez joindre autant de guildes que vous voulez, mais seulement une équipe.\n
\nPour des informations plus précises, lisez sur le wiki les pages à propos des [Équipes](http://fr.habitica.wikia.com/wiki/%C3%89quipe) et des [Guildes](http://fr.habitica.wikia.com/wiki/Guildes).",
"faqQuestion6": "Comment obtenir des Familiers ou des Montures ?",
- "iosFaqAnswer6": "A partir du niveau 3, vous débloquerez le Système de Butin. Chaque fois que vous réaliserez une tâche, vous aurez une chance aléatoire de recevoir un œuf, une potion d'éclosion, ou un morceau de nourriture. Ils seront stockés dans Menu > Objets.\n\nPour faire éclore un Familier, vous aurez besoin d'un œuf et d'une potion d'éclosion. Sélectionnez l’œuf pour déterminer l'espèce que vous voulez faire éclore, et choisissez \"Faire éclore l'Œuf.\" Puis choisissez la potion d'éclosion pour déterminer sa couleur ! Allez dans Menu > Familiers pour équiper votre nouveau Familier en cliquant dessus.\n\nVous pouvez aussi faire grandir vos Familiers en Montures en les nourrissant dans Menu > Familiers, et en sélectionnant \"Nourrir le Familier\" ! Vous devrez nourrir un familier plusieurs fois avant qu'il ne devienne une Monture, mais si vous devinez sa nourriture préférée, il grandira plus vite. Faites des essais, ou [lisez les spoilers ici](http://habitica.wikia.com/wiki/Food#Food_Preferences). Une fois que vous avez obtenu une monture, allez dans Menu > Montures et sélectionnez la pour l'équiper.\n\nVous pouvez aussi gagner des œufs pour les Quêtes de Familiers en accomplissant certaines Quêtes. (Voir ci-dessous pour en apprendre plus sur les Quêtes.)",
- "webFaqAnswer6": "A partir du niveau 3, vous débloquerez le Système de Butin. Chaque fois que vous réaliserez une tâche,, vous aurez une chance aléatoire de recevoir un œuf, une potion d'éclosion, ou un morceau de nourriture. Ils seront stockés dans Inventaire > Marché.\n
\nPour faire éclore un Familier, vous aurez besoin d'un œuf et d'une potion d'éclosion. Cliquez l’œuf pour déterminer l'animal que vous voulez faire éclore, et puis cliquez la potion d'éclosion pour déterminer sa couleur ! Allez dans Inventaire > Familiers pour équiper votre nouveau Familier en cliquant dessus.\n
\nVous pouvez aussi faire grandir vos Familiers en Montures en les nourrissant dans Inventaire > Familiers. Cliquez sur un Familier, puis cliquez sur un morceau de nourriture sur le menu de droite ! Vous devrez nourrir un familier plusieurs fois avant qu'il ne devienne une Monture, mais si vous devinez sa nourriture préférée, il grandira plus vite. Faites des essais, ou [lisez les spoilers ici](http://habitica.wikia.com/wiki/Food#Food_Preferences). Une fois que vous avez obtenu une monture, allez dans Inventaire > Montures et sélectionnez la pour l'équiper.\n
\nVous pouvez aussi gagner des œufs pour les Quêtes de Familiers en accomplissant certaines Quêtes. (Voir ci-dessous pour en apprendre plus sur les Quêtes.)",
+ "iosFaqAnswer6": "A partir du niveau 3, vous débloquerez le Système de Butin. Chaque fois que vous réaliserez une tâche, vous aurez une chance aléatoire de recevoir un œuf, une potion d'éclosion, ou un morceau de nourriture. Ils seront stockés dans Menu > Objets.\n\nPour faire éclore un Familier, vous aurez besoin d'un œuf et d'une potion d'éclosion. Sélectionnez l’œuf pour déterminer l'espèce que vous voulez faire éclore, et choisissez \"Faire éclore l'Œuf.\" Puis choisissez la potion d'éclosion pour déterminer sa couleur ! Allez dans Menu > Familiers pour équiper votre nouveau Familier en cliquant dessus.\n\nVous pouvez aussi faire grandir vos Familiers en Montures en les nourrissant dans Menu > Familiers, et en sélectionnant \"Nourrir le Familier\" ! Vous devrez nourrir un familier plusieurs fois avant qu'il ne devienne une Monture, mais si vous devinez sa nourriture préférée, il grandira plus vite. Faites des essais, ou [lisez les spoilers ici](http://fr.habitica.wikia.com/wiki/Nourriture#Pr.C3.A9f.C3.A9rences_alimentaires). Une fois que vous avez obtenu une monture, allez dans Menu > Montures et sélectionnez la pour l'équiper.\n\nVous pouvez aussi gagner des œufs pour les Quêtes de Familiers en accomplissant certaines Quêtes. (Voir ci-dessous pour en apprendre plus sur les Quêtes.)",
+ "webFaqAnswer6": "A partir du niveau 3, vous débloquerez le Système de Butin. Chaque fois que vous réaliserez une tâche,, vous aurez une chance aléatoire de recevoir un œuf, une potion d'éclosion, ou un morceau de nourriture. Ils seront stockés dans Inventaire > Marché.\n
\nPour faire éclore un Familier, vous aurez besoin d'un œuf et d'une potion d'éclosion. Cliquez l’œuf pour déterminer l'animal que vous voulez faire éclore, et puis cliquez la potion d'éclosion pour déterminer sa couleur ! Allez dans Inventaire > Familiers pour équiper votre nouveau Familier en cliquant dessus.\n
\nVous pouvez aussi faire grandir vos Familiers en Montures en les nourrissant dans Inventaire > Familiers. Cliquez sur un Familier, puis cliquez sur un morceau de nourriture sur le menu de droite ! Vous devrez nourrir un familier plusieurs fois avant qu'il ne devienne une Monture, mais si vous devinez sa nourriture préférée, il grandira plus vite. Faites des essais, ou [lisez les spoilers ici](http://fr.habitica.wikia.com/wiki/Nourriture#Pr.C3.A9f.C3.A9rences_alimentaires). Une fois que vous avez obtenu une monture, allez dans Inventaire > Montures et sélectionnez la pour l'équiper.\n
\nVous pouvez aussi gagner des œufs pour les Quêtes de Familiers en accomplissant certaines Quêtes. (Voir ci-dessous pour en apprendre plus sur les Quêtes.)",
"faqQuestion7": "Comment devenir Guerrier, Mage, Voleur ou Guérisseur ?",
"iosFaqAnswer7": "Arrivé au niveau 10, vous pourrez choisir de devenir un Guerrier, un Mage, un Voleur ou un Guérisseur. (Tous les joueurs commencent comme Guerrier par défaut.) Chaque classe a différents options d'équipement, différentes Compétences qu'ils peuvent utiliser après le niveau 11, et différents avantages. Les Guerriers peuvent facilement infliger des dégâts aux Boss, supportent plus de dégâts de leurs tâches et rendent leur équipe plus forte. Les Mages peuvent aussi facilement infliger des dégâts aux Boss, ainsi que gagner rapidement des niveaux et rendre du Mana à leur équipe. Les Voleurs gagnent le plus d'or et trouvent le plus de butin, et peuvent aider leur équipe à faire pareil. Enfin, les Guérisseurs peuvent se soigner et soigner les membres de leur équipe.\n\nSi vous ne voulez pas immédiatement choisir une classe -- par exemple, si vous faites en sorte de d'abord recueillir tout l'équipement de votre classe actuelle -- vous pouvez cliquer \"Décider plus tard\" et choisir plus tard dans Menu > Choisir une Classe.",
"webFaqAnswer7": "Arrivé au niveau 10, vous pourrez choisir de devenir un Guerrier, un Mage, un Voleur ou un Guérisseur. (Tous les joueurs commencent comme Guerrier par défaut.) Chaque classe a différents options d'équipement, différentes Compétences qu'ils peuvent utiliser après le niveau 11, et différents avantages. Les Guerriers peuvent facilement infliger des dégâts aux Boss, supportent plus de dégâts de leurs tâches et rendent leur équipe plus forte. Les Mages peuvent aussi facilement infliger des dégâts aux Boss, ainsi que gagner rapidement des niveaux et rendre du Mana à leur équipe. Les Voleurs gagnent le plus d'or et trouvent le plus de butin, et ils peuvent aider leur équipe à faire pareil. Enfin, les Guérisseurs peuvent se soigner et soigner les membres de leur équipe.\n
\nSi vous ne voulez pas immédiatement choisir une classe -- par exemple, si vous faites en sorte de d'abord recueillir tout l'équipement de votre classe actuelle -- vous pouvez cliquer \"Ne pas choisir\" et reprendre le choix plus tard dans Utilisateur > Caractéristiques.",
@@ -31,14 +31,14 @@
"iosFaqAnswer9": "Il vous faudra tout d'abord rejoindre une Équipe (Social > Équipe). Bien que vous puissiez combattre des monstres en solo, nous vous recommandons de jouer en groupe ; vos Quêtes n'en seront que plus faciles. Il est aussi très motivant d'avoir des amis à vos côtés pour vous encourager à accomplir toutes vos tâches ! \n\n Vous aurez ensuite besoin d'un Parchemin de Quête, que vous retrouverez dans Inventaire > Quêtes. Il y a trois façons d'obtenir un parchemin : \n- Au niveau 15, vous recevrez une série de Quêtes, c'est-à-dire trois quêtes liées. D'autres séries seront débloquées aux niveaux 30, 40 et 60. \n- Quand vous invitez des membres à rejoindre votre équipe, vous serez récompensé(e) avec le parchemin du Basi-list !\n - Vous pouvez acheter des Quêtes depuis la page Quêtes de votre inventaire [Inventaire > Quêtes](https://habitica.com/#/options/inventory/quests) pour de l'Or et des Gemmes. (Ces fonctionnalités seront ajoutées à l'application lors d'une mise à jour ultérieure.) \n\nPour combattre un Boss ou réunir des objets lors d'une quête de collection, accomplissez simplement vos tâches comme vous le faites d'habitude, et elles seront automatiquement converties en dégâts pendant la nuit. (Il vous faudra peut-être recharger la page pour voir descendre la barre de vie du Boss.) Si vous combattez un Boss et ratez une de vos tâches Quotidiennes, le Boss infligera des dégâts à votre équipe au moment où vous l'attaquerez. \n\nAprès le niveau 11, les Mages et les Guerriers gagnent des compétences leur permettant d'infliger des dégâts additionnels au Boss. Ce sont donc les deux classes à privilégier au niveau 10 si vous souhaitez gagner en force de frappe.",
"webFaqAnswer9": "D'abord, vous devez rejoindre une Équipe (dans Social > Équipe). Bien que vous puissiez combattre des monstres seul, nous recommandons de jouer dans un groupe car cela rendra vos Quêtes plus facile. En plus, avoir des amis qui vous encouragent à accomplir vos tâches est très motivant !\n
\nEnsuite, vous avez besoin d'un Parchemin de Quête, qui sont stockés dans Inventaire > Quêtes. Il y a trois façons d'obtenir un parchemin :\n
\n* Quand vous invitez des membres dans votre équipe, vous serez récompensés avec le parchemin du Basi-list !\n* Au niveau 15, vous recevrez une série de Quêtes, c'est à dire trois quêtes liées. D'autres séries seront débloquées aux niveau 30, 40 et 60.\n* Vous pouvez acheter des Quêtes depuis la page Quêtes (Inventaire > Quêtes) pour de l'Or et des Gemmes.\n
\nPour combattre un Boss ou réunir des objets pour une quête de collection, réalisez simplement vos tâches normalement, et elle seront transformées en dégâts pendant la nuit. (Recharger peut être nécessaire pour voir la barre de vie du Boss descendre). Si vous combattez un Boss et ratez une de vos tâches Quotidiennes, le Boss infligera des dégâts à votre équipe au moment où vous lui infligerez des dégâts.\n
\nAprès le niveau 11, les Mages et les Guerriers gagneront des compétences qui leurs permettent d'infliger des dégâts additionnels au Boss, en faisant ainsi d'excellentes classes après le niveau 10 si vous souhaitez taper dur.",
"faqQuestion10": "Que sont les Gemmes, et comment puis-je en obtenir ?",
- "iosFaqAnswer10": "Les Gemmes sont achetées avec de l'argent en sélectionnant l'icône de gemme dans l'en-tête. Lorsque des gens achètent des gemmes, ils nous aident à maintenir le site. Nous les remercions pour leur soutien ! En plus d'acheter des gemmes directement, il y a trois façons pour les joueurs de gagner des gemmes : * Gagnez un Défi sur le [site](https://habitica.com) qui a été ouvert par un autre joueur dans Social > Défis. (Nous ajouterons cette fonctionnalité dans l'application dans une mise à jour ultérieure !) * Abonnez-vous sur le [site](https://habitica.com/#/options/settings/subscription) et débloquez la capacité d'acheter un certain nombre de gemmes par mois. * Contribuez au projet Habitica avec vos compétences. Lisez cette page du Wiki pour plus de détails : [Contribuer à Habitica](http://habitica.wikia.com/wiki/Contributing_to_Habitica). Gardez à l'esprit que les objets achetés avec des gemmes n'offrent aucun avantage statistique, donc les joueurs peuvent très bien utiliser l'application sans elles !",
- "webFaqAnswer10": "Les Gemmes sont [achetées avec de l'argent](https://habitica.com/#/options/settings/subscription) bien que les [abonnés](https://habitica.com/#/options/settings/subscription) puissent en acheter avec de l'Or. Lorsque des gens achètent des gemmes, il nous aident à maintenir le site. Nous les remercions pour leur soutient !\n
\nEn plus d'acheter des gemmes directement, il y a deux façons pour les joueurs de gagner des gemmes :\n
\n* Gagnez un Défi qui a été ouvert par un autre joueur dans Social > Défis.\n* Contribuez au projet Habitica avec vos compétences. Lisez cette page du Wiki pour plus de détail : [Contribuer à Habitica](http://habitica.wikia.com/wiki/Contributing_to_Habitica).\n
\nGardez à l'esprit que les objets achetés avec des gemmes n'offrent aucun avantage statistiques, donc les joueurs peuvent très bien utiliser l'application sans elles !",
+ "iosFaqAnswer10": "Les Gemmes sont achetées avec de l'argent en sélectionnant l'icône de gemme dans l'en-tête. Lorsque des gens achètent des gemmes, ils nous aident à maintenir le site. Nous les remercions pour leur soutien ! En plus d'acheter des gemmes directement, il y a trois façons pour les joueurs de gagner des gemmes : * Gagnez un Défi sur le [site](https://habitica.com) qui a été ouvert par un autre joueur dans Social > Défis. (Nous ajouterons cette fonctionnalité dans l'application dans une mise à jour ultérieure !) * Abonnez-vous sur le [site](https://habitica.com/#/options/settings/subscription) et débloquez la capacité d'acheter un certain nombre de gemmes par mois. * Contribuez au projet Habitica avec vos compétences. Lisez cette page du Wiki pour plus de détails : [Contribuer à Habitica](http://fr.habitica.wikia.com/wiki/Contribuer_%C3%A0_Habitica). Gardez à l'esprit que les objets achetés avec des gemmes n'offrent aucun avantage statistique, donc les joueurs peuvent très bien utiliser l'application sans elles !",
+ "webFaqAnswer10": "Les Gemmes sont [achetées avec de l'argent](https://habitica.com/#/options/settings/subscription) bien que les [abonnés](https://habitica.com/#/options/settings/subscription) puissent en acheter avec de l'Or. Lorsque des gens achètent des gemmes, il nous aident à maintenir le site. Nous les remercions pour leur soutient !\n
\nEn plus d'acheter des gemmes directement, il y a deux façons pour les joueurs de gagner des gemmes :\n
\n* Gagnez un Défi qui a été ouvert par un autre joueur dans Social > Défis.\n* Contribuez au projet Habitica avec vos compétences. Lisez cette page du Wiki pour plus de détail : [Contribuer à Habitica](http://fr.habitica.wikia.com/wiki/Contribuer_%C3%A0_Habitica).\n
\nGardez à l'esprit que les objets achetés avec des gemmes n'offrent aucun avantage statistiques, donc les joueurs peuvent très bien utiliser l'application sans elles !",
"faqQuestion11": "Comment signaler un bug ou demander une fonctionnalité ?",
"iosFaqAnswer11": "Vous pouvez signaler une erreur, demander une fonctionnalité ou envoyer un avis depuis Menu > Signaler une erreur et Menu > Envoyer un avis ! Nous ferons notre possible pour vous aider.",
"webFaqAnswer11": "Les rapports d'erreur sont rassemblés sur GitHub. Aller dans [Aide > Signaler un Bug](https://github.com/HabitRPG/habitrpg/issues/2760) et suivez les instructions. Ne vous en faites pas, nous corrigerons ça rapidement !\n
\nLes demandes de fonctionnalités sont rassemblées sur Trello. Allez dans [Aide > Demander une Fonctionnalité](https://trello.com/c/odmhIqyW/440-read-first-table-of-contents) et suivez les instructions. Ta-Da!",
"faqQuestion12": "Comment combat-on un Boss Mondial?",
- "iosFaqAnswer12": "Les Boss Mondiaux sont des monstres spéciaux qui apparaissent dans la Taverne. Tous les membres actifs combattent automatiquement le boss : leurs tâches et habiletés blesseront le Boss comme à l'habitude.\n\nVous pouvez également participer à une quête normale en même temps. Vos tâches et habiletés seront pris en compte pour les deux boss : le Boss Mondial et le Boss/Quête de collection de votre équipe.\n\nUn Boss Mondial ne blessera jamais, vous et votre compte, de quelque manière que ce soit. Il a plutôt une Barre de colère qui se remplit lorsque les membres manquent les Quotidiennes. Si la Barre se remplit, il attaque l'un des Personnages non-joueurs du site et son image changera.\n\nPour en savoir plus, visitez la page [Boss Mondiaux précédents](http://habitica.wikia.com/wiki/World_Bosses) du wiki.",
- "webFaqAnswer12": "Les Boss Mondiaux sont des monstres spéciaux qui apparaissent dans la Taverne. Tous les membres actifs combattent automatiquement le boss : leurs tâches et habiletés blesseront le Boss comme à l'habitude.\n
\nVous pouvez également participer à une quête normale en même temps. Vos tâches et habiletés seront pris en compte pour les deux boss : le Boss Mondial et le Boss/Quête de collection de votre équipe.\n
\nUn Boss Mondial ne blessera jamais, vous et votre compte, de quelque manière que ce soit. Il a plutôt une Barre de colère qui se remplit lorsque les membres manquent les Quotidiennes. Si la Barre se remplit, il attaque l'un des Personnages non-joueurs du site et son image changera.\n
\nPour en savoir plus, visitez la page [Boss Mondiaux précédents](http://habitica.wikia.com/wiki/World_Bosses) du wiki.",
+ "iosFaqAnswer12": "Les Boss Mondiaux sont des monstres spéciaux qui apparaissent dans la Taverne. Tous les membres actifs combattent automatiquement le boss : leurs tâches et habiletés blesseront le Boss comme à l'habitude.\n\nVous pouvez également participer à une quête normale en même temps. Vos tâches et habiletés seront pris en compte pour les deux boss : le Boss Mondial et le Boss/Quête de collection de votre équipe.\n\nUn Boss Mondial ne blessera jamais, vous et votre compte, de quelque manière que ce soit. Il a plutôt une Barre de colère qui se remplit lorsque les membres manquent les Quotidiennes. Si la Barre se remplit, il attaque l'un des Personnages non-joueurs du site et son image changera.\n\nPour en savoir plus, visitez la page [Boss Mondiaux précédents](http://fr.habitica.wikia.com/wiki/Boss_Mondiaux) du wiki.",
+ "webFaqAnswer12": "Les Boss Mondiaux sont des monstres spéciaux qui apparaissent dans la Taverne. Tous les membres actifs combattent automatiquement le boss : leurs tâches et habiletés blesseront le Boss comme à l'habitude.\n
\nVous pouvez également participer à une quête normale en même temps. Vos tâches et habiletés seront pris en compte pour les deux boss : le Boss Mondial et le Boss/Quête de collection de votre équipe.\n
\nUn Boss Mondial ne blessera jamais, vous et votre compte, de quelque manière que ce soit. Il a plutôt une Barre de colère qui se remplit lorsque les membres manquent les Quotidiennes. Si la Barre se remplit, il attaque l'un des Personnages non-joueurs du site et son image changera.\n
\nPour en savoir plus, visitez la page [Boss Mondiaux précédents](http://fr.habitica.wikia.com/wiki/Boss_Mondiaux) du wiki.",
"iosFaqStillNeedHelp": "Si vous avez une question qui n'est pas dans cette liste ou dans la [FAQ du Wiki](http://fr.habitica.wikia.com/wiki/FAQ), venez la poser dans la Taverne depuis Menu > Taverne ! Nous serions ravis de vous aider.",
"webFaqStillNeedHelp": "Si vous avez une question qui n'est pas dans cette liste ou qui n'est pas dans la [FAQ du Wiki](http://fr.habitica.wikia.com/wiki/FAQ), venez la poser dans la [Guilde des Nouveaux Arrivants](https://habitica.com/#/options/groups/guilds/5481ccf3-5d2d-48a9-a871-70a7380cee5a) ! Nous serons heureux de vous aider."
}
\ No newline at end of file
diff --git a/common/locales/fr/front.json b/common/locales/fr/front.json
index e2b317a149..e07c058643 100644
--- a/common/locales/fr/front.json
+++ b/common/locales/fr/front.json
@@ -28,6 +28,7 @@
"communityReddit": "Reddit",
"companyAbout": "Comment ça marche",
"companyBlog": "Blog",
+ "devBlog": "Blog des développeurs",
"companyDonate": "Faire un don",
"companyExtensions": "Extensions",
"companyPrivacy": "Confidentialité",
@@ -51,6 +52,7 @@
"featureSocialHeading": "Jeu social",
"featuredIn": "Présenté dans",
"featuresHeading": "Nous proposons également...",
+ "footerDevs": "Développeurs",
"footerCommunity": "Communauté",
"footerCompany": "Compagnie",
"footerMobile": "Mobile",
@@ -182,6 +184,7 @@
"zelahQuote": "Avec [Habitica], je me suis persuadé d'aller au lit à l'heure avec la simple idée de gagner des points en me couchant tôt ou de perdre de la santé en me couchant tard !",
"reportAccountProblems": "Signaler un problème sur votre compte",
"reportCommunityIssues": "Signaler un problème au niveau de la Communauté",
+ "subscriptionPaymentIssues": "Problèmes d'abonnement et de paiement",
"generalQuestionsSite": "Questions générales sur le Site",
"businessInquiries": "Demandes pour les entreprises",
"merchandiseInquiries": "Demandes commerciales",
@@ -222,5 +225,32 @@
"altAttrWebstorm": "WebStorm",
"altAttrGithub": "GitHub",
"altAttrTrello": "Trello",
- "altAttrSlack": "Slack"
+ "altAttrSlack": "Slack",
+ "missingAuthHeaders": "En-têtes d'authentification manquants.",
+ "missingAuthParams": "Paramètres d'authentification manquants.",
+ "missingUsernameEmail": "Nom d'utilisateur ou courriel manquant.",
+ "missingEmail": "Courriel manquant.",
+ "missingUsername": "Nom d'utilisateur manquant.",
+ "missingPassword": "Mot de passe manquant.",
+ "missingNewPassword": "Nouveau mot de passe manquant.",
+ "wrongPassword": "Mauvais mot de passe.",
+ "notAnEmail": "Adresse courriel invalide.",
+ "emailTaken": "Adresse courriel déjà utilisée par un utilisateur.",
+ "newEmailRequired": "Nouvelle adresse courriel manquante.",
+ "usernameTaken": "Nom d'utilisateur déjà pris.",
+ "passwordConfirmationMatch": "La confirmation du mot de passe ne correspond pas au mot de passe.",
+ "invalidLoginCredentials": "Nom d'utilisateur, courriel ou mot de passe incorrect.",
+ "passwordReset": "Si nous avons votre courriel dans nos fichiers, un lien pour réinitialiser votre mot de passe vous a été envoyé.",
+ "passwordResetEmailSubject": "Mot de passe réinitialisé pour Habitica",
+ "passwordResetEmailText": "Le mot de passe de <%= username %> a été réinitialisé pour <%= newPassword %> . Important ! Le nom d'utilisateur et le mot de passe sont sensibles à la casse -- vous devez les écrire exactement comme ils sont indiqués ici. Nous vous recommandons de les copier-coller plutôt que de les écrire. Connectez-vous sur <%= baseUrl %>. Après que vous vous soyez connecté, rendez-vous à <%= baseUrl %>/#/options/settings/settings pour modifier votre mot de passe.",
+ "passwordResetEmailHtml": "Le mot de passe de <%= username %> a été changé pour <%= newPassword %>.
Important ! Le nom d'utilisateur et le mot de passe sont sensibles à la casse -- vous devez les écrire exactement comme ils sont indiqués ici. Nous vous recommandons de les copier-coller plutôt que de les écrire.
Connectez-vous sur <%= baseUrl %>. Après que vous vous soyez connecté, rendez-vous à <%= baseUrl %>/#/options/settings/settings pour modifier votre mot de passe.",
+ "invalidLoginCredentialsLong": "\nHoulà ! Votre nom d'utilisateur ou votre mot de passe est incorrect.\n- Assurez-vous que votre nom d'utilisateur ou votre courriel est écrit correctement.\n- Vous vous êtes peut-être enregistré avec Facebook, et pas votre courriel. Vérifiez en essayant de vous connecter avec Facebook.\n- Si vous avez oublié votre mot de passe, cliquez sur « Mot de passe oublié ».",
+ "invalidCredentials": "Aucun compte n'utilise cet identifiant.",
+ "accountSuspended": "Votre compte a été suspendu, veuillez contacter leslie@habitica.com en indiquant votre ID d'utilisateur \"<%= userId %>\" pour obtenir de l'aide.",
+ "onlyFbSupported": "Seul Facebook est actuellement supporté.",
+ "cantDetachFb": "Votre compte n'a pas d'autres méthodes d'authentification, vous ne pouvez pas détacher Facebook.",
+ "onlySocialAttachLocal": "L'authentification locale ne peut être ajoutée que sur un compte social.",
+ "invalidReqParams": "Paramètres de la requête invalides.",
+ "memberIdRequired": "\"member\" doit être un UUID valide.",
+ "heroIdRequired": "\"heroId\" doit être un UUID valide."
}
\ No newline at end of file
diff --git a/common/locales/fr/gear.json b/common/locales/fr/gear.json
index e0a16cc80b..444d3dc980 100644
--- a/common/locales/fr/gear.json
+++ b/common/locales/fr/gear.json
@@ -180,15 +180,17 @@
"weaponArmoireBlueLongbowText": "Arc Long Bleu",
"weaponArmoireBlueLongbowNotes": "Présentez armes... En joue... Feu ! Cet arc à une très grande portée. Augmente la Perception de <%= per %> points, la Constitution de <%= con %> points et la Force de <%= str %> points. Armoire Enchantée : Objet Indépendant.",
"weaponArmoireGlowingSpearText": "Lance Rayonnante",
- "weaponArmoireGlowingSpearNotes": "Cette lance hypnothise les tâches sauvages afin que vous les attaquiez. Augmente la Force de <%= str %> points. Armoire Enchantée : Objet Indépendant.",
+ "weaponArmoireGlowingSpearNotes": "Cette lance hypnotise les tâches sauvages afin que vous les attaquiez. Augmente la Force de <%= str %> points. Armoire Enchantée : Objet Indépendant.",
"weaponArmoireBarristerGavelText": "Marteau d'Avocat",
"weaponArmoireBarristerGavelNotes": "De l'ordre ! Augmente la Force et la Constitution de <%= attrs %> points chacune. Armoire Enchantée : Ensemble de l'Avocat (Objet 3 sur 3).",
"weaponArmoireJesterBatonText": "Bâton de Bouffon",
"weaponArmoireJesterBatonNotes": "Avec un geste de votre bâton et une répartie saillante, même les situations les plus compliquées deviennent simples. Augmente l'Intelligence et la Perception de <%= attrs %> points chacune. Armoire Enchantée : Ensemble du Bouffon (Objet 3 sur 3).",
"weaponArmoireMiningPickaxText": "Pioche de Minage",
"weaponArmoireMiningPickaxNotes": "Minez la quantité maximale d'or de vos tâches ! Augmente la Perception de <%= per %> points. Armoire Enchantée : Ensemble du Mineur (Objet 3 sur 3).",
- "weaponArmoireBasicLongbowText": "Basic Longbow",
- "weaponArmoireBasicLongbowNotes": "A serviceable hand-me-down bow. Increases Strength by <%= str %>. Enchanted Armoire: Basic Archer Set (Item 1 of 3).",
+ "weaponArmoireBasicLongbowText": "Arc long de base",
+ "weaponArmoireBasicLongbowNotes": "Un arc facile à prendre en main. Augmente la Force de <%= str %>. Armoire enchantée : Ensemble de l'Archer de base (Objet 1 sur 3).",
+ "weaponArmoireHabiticanDiplomaText": "Diplôme d'Habiticien·ne",
+ "weaponArmoireHabiticanDiplomaNotes": "Le certificat d'un succès significatif. Bravo ! Augmente l'intelligence de <%= int %>. Armoire enchantée : Ensemble du diplomé (objet 1 de 3)",
"armor": "armure",
"armorBase0Text": "Habit simple",
"armorBase0Notes": "Un vêtement ordinaire. N'apporte aucun avantage.",
@@ -250,8 +252,8 @@
"armorSpecialSnowflakeNotes": "Une robe qui vous maintient au chaud, même en pleine tempête. Augmente la Constitution de <%= con %> points. Équipement en édition limitée de l'hiver 2013-2014.",
"armorSpecialBirthdayText": "Robes de Fête Absurdes",
"armorSpecialBirthdayNotes": "Joyeux Anniversaire Habitica ! Portez ces Robes de Fête Absurdes pour célébrer cette journée magnifique ! N'apporte aucun bonus.",
- "armorSpecialBirthday2015Text": "Robes de Fête Ridicules",
- "armorSpecialBirthday2015Notes": "Joyeux Anniversaire Habitica ! Portez ces Robes de Fête Ridicules pour célébrer cette journée magnifique ! N'apporte aucun bonus.",
+ "armorSpecialBirthday2015Text": "Robes de Fête rigolotes",
+ "armorSpecialBirthday2015Notes": "Joyeux Anniversaire Habitica ! Portez ces Robes de Fête rigolotes pour célébrer cette journée magnifique ! N'apporte aucun bonus.",
"armorSpecialBirthday2016Text": "Ridicule Tunique de Fête",
"armorSpecialBirthday2016Notes": "Joyeux Anniversaire, Habitica ! Revêtez cette Ridicule Tunique de Fête afin de célébrer cette magnifique journée. N'apporte aucun bonus.",
"armorSpecialGaymerxText": "Armure de Guerrier Arc-en-Ciel",
@@ -364,6 +366,10 @@
"armorMystery201512Notes": "Invoquez la flamme glacée de l'hiver ! N'apporte aucun bonus. Équipement d'abonné·e de décembre 2015.",
"armorMystery201603Text": "Costume Chanceux",
"armorMystery201603Notes": "Ce costume est cousu avec des milliers de trèfles à quatre feuilles ! N'apporte aucun bonus. Équipement d'abonné de mars 2016.",
+ "armorMystery201604Text": "Armure de feuilles",
+ "armorMystery201604Notes": "Vous aussi, vous pouvez être un petit mais effroyable bouquet de feuilles. Ne confère aucun bonus. Équipement d'abonné·e d'Avril 2016.",
+ "armorMystery201605Text": "Marching Bard Uniform",
+ "armorMystery201605Notes": "Unlike the traditional bards who join adventuring parties, bards who join Habitican marching bands are known for grand parades, not dungeon raids. Confers no benefit. May 2016 Subscriber Item.",
"armorMystery301404Text": "Tenue Steampunk",
"armorMystery301404Notes": "Pimpant et fringuant ! N'apporte aucun bonus. Équipement d'abonné·e de février 3015.",
"armorArmoireLunarArmorText": "Armure Lunaire Apaisante",
@@ -392,8 +398,10 @@
"armorArmoireJesterCostumeNotes": "Tralala ! Malgré l'apparence de ce costume, vous n'êtes pas un bouffon. Augmente l'Intelligence de <%= int %> points. Armoire Enchantée : Ensemble du Bouffon (Objet 2 sur 3).",
"armorArmoireMinerOverallsText": "Salopette de Mineur",
"armorArmoireMinerOverallsNotes": "Elle semble peut-être usée, mais elle contient un enchantement qui repousse la poussière. Augmente la Constitution de <%= con %> points. Armoire Enchantée : Ensemble du Mineur (Objet 2 sur 3).",
- "armorArmoireBasicArcherArmorText": "Basic Archer Armor",
- "armorArmoireBasicArcherArmorNotes": "This camouflaged vest lets you slip unnoticed through the forests. Increases Perception by <%= per %>. Enchanted Armoire: Basic Archer Set (Item 2 of 3).",
+ "armorArmoireBasicArcherArmorText": "Armure d'Archer de base",
+ "armorArmoireBasicArcherArmorNotes": "Cette veste de camouflage vous permet de vous faufiler inaperçu dans les forêts. Augmente la Perception de <%= per %>. Armoire enchantée : Ensemble de l'Archer de base (Objet 2 sur 3).",
+ "armorArmoireGraduateRobeText": "Toge de diplômé",
+ "armorArmoireGraduateRobeNotes": "Félicitations ! Cette lourde toge porte toutes les connaissances que vous avez acquises. Augmente l'Intelligence de <%= int %>. Armoire enchantée : Ensemble du diplômé (objet 2 de 3).",
"headgear": "coiffe",
"headBase0Text": "Pas de casque",
"headBase0Notes": "Pas de couvre-chef.",
@@ -479,8 +487,8 @@
"headSpecialFallMageNotes": "La magie imprègne chaque fil de ce chapeau. Augmente la Perception de <%= per %>. Équipement en édition limitée de l'automne 2014.",
"headSpecialFallHealerText": "Bandages de Tête",
"headSpecialFallHealerNotes": "Hautement hygiénique tout en restant à la mode. Augmente l'Intelligence de <%= int %> points. Équipement en édition limitée de l'automne 2014.",
- "headSpecialNye2014Text": "Chapeau de Fête Ridicule",
- "headSpecialNye2014Notes": "Vous avez reçu un Chapeau de Fête Ridicule ! Portez-le avec fierté en célébrant le Nouvel An ! N'apporte aucun bonus.",
+ "headSpecialNye2014Text": "Chapeau de Fête rigolo",
+ "headSpecialNye2014Notes": "Vous avez reçu un Chapeau de Fête rigolo ! Portez-le avec fierté en célébrant le Nouvel An ! N'apporte aucun bonus.",
"headSpecialWinter2015RogueText": "Masque de Drakôn Stalactite",
"headSpecialWinter2015RogueNotes": "Vous êtes vraiment, sérieusement, absolument un authentique Drakôn Stalactite. Vous ? Un imposteur qui infiltre leurs rangs dans l'espoir de soutirer quelque trésor gisant - prétendument - dans leurs galeries de glace ? Jamais ! Roaar ! Augmente la Perception de <%= per %> points. Équipement en édition limitée de l'hiver 2014-2015.",
"headSpecialWinter2015WarriorText": "Casque en Pain d’Épice",
@@ -565,6 +573,10 @@
"headMystery201602Notes": "Protéger votre identité de tous vos admirateurs. N'apporte aucun bonus. Équipement d'abonné·e de février 2016.",
"headMystery201603Text": "Chapeau Chanceux",
"headMystery201603Notes": "Ce haut-de-forme est un porte-bonheur magique. N'apporte aucun bonus. Équipement d'abonné de mars 2016.",
+ "headMystery201604Text": "Couronne de fleurs",
+ "headMystery201604Notes": "Ces fleurs tressées font un casque étonnamment résistant ! Ne confère aucun bonus. Équipement d'abonné·e d'Avril 2016.",
+ "headMystery201605Text": "Marching Bard Hat",
+ "headMystery201605Notes": "Seventy-six dragons led the big parade, with a hundred and ten gryphons close at hand! Confers no benefit. May 2016 Subscriber Item.",
"headMystery301404Text": "Haut-de-forme Fantaisiste",
"headMystery301404Notes": "Un couvre-chef fantaisiste pour les gens de bonne famille les plus élégants ! N'apporte aucun bonus. Équipement d'abonné·e de janvier 3015.",
"headMystery301405Text": "Haut-de-forme Classique",
@@ -611,8 +623,10 @@
"headArmoireJesterCapNotes": "Les cloches de ce chapeau pourraient bien distraire vos adversaires mais elles vous aident juste à vous concentrer. Augmente la Perception de <%= per %> points. Armoire Enchantée : Ensemble du Bouffon (Objet 1 sur 3).",
"headArmoireMinerHelmetText": "Casque de Mineur",
"headArmoireMinerHelmetNotes": "Protégez votre tête des chutes de tâches ! Augmente l'Intelligence de <%= int %> points. Armoire Enchantée : Ensemble du Mineur (Objet 1 sur 3).",
- "headArmoireBasicArcherCapText": "Basic Archer Cap",
- "headArmoireBasicArcherCapNotes": "No archer would be complete without a jaunty cap! Increases Perception by <%= per %>. Enchanted Armoire: Basic Archer Set (Item 3 of 3).",
+ "headArmoireBasicArcherCapText": "Casquette d'Archer de base",
+ "headArmoireBasicArcherCapNotes": "Tout archer digne de ce nom possède un chapeau désinvolte ! Augmente la Perception de <%= per %>. Armoire enchantée : Ensemble de l'Archer de base (Objet 3 sur 3).",
+ "headArmoireGraduateCapText": "Coiffe de diplômé",
+ "headArmoireGraduateCapNotes": "Félicitations ! Vos pensées profondes vous ont récompensé de cette coiffe pensante. Augmente l'Intelligence de <%= int %>. Armoire enchantée : Ensemble du diplômé (objet 3 de 3).",
"offhand": "objet de main de bouclier",
"shieldBase0Text": "Pas d'Équipement de Main de Bouclier",
"shieldBase0Notes": "Pas de bouclier ni de deuxième arme.",
@@ -673,7 +687,7 @@
"shieldSpecialWinter2015HealerText": "Bouclier Apaisant",
"shieldSpecialWinter2015HealerNotes": "Ce bouclier dévie le vent glacial. Augmente la Constitution de <%= con %> points. Équipement en édition limitée de l'hiver 2014-2015.",
"shieldSpecialSpring2015RogueText": "Couinement explosif",
- "shieldSpecialSpring2015RogueNotes": "Ne vous laissez pas duper par le son - ces explosifs font mal. Augmente la Force de <%= str %> points. Équipement en édition limitée du printemps 2015.",
+ "shieldSpecialSpring2015RogueNotes": "Ne vous laissez pas duper par le son – ces explosifs font mal. Augmente la Force de <%= str %> points. Équipement en édition limitée du printemps 2015.",
"shieldSpecialSpring2015WarriorText": "Disque gamelle",
"shieldSpecialSpring2015WarriorNotes": "Lancez-le vers vos ennemis... ou ne faites que le tenir, parce qu'il va se remplir de délicieuse nourriture pour chien à l'heure du repas. Augmente la Constitution de <%= con %> points. Équipement en édition limitée du printemps 2015.",
"shieldSpecialSpring2015HealerText": "Coussin décoré",
@@ -716,6 +730,8 @@
"shieldArmoireDragonTamerShieldNotes": "Distrayez les ennemis grâce à ce bouclier en forme de Dragon. Augmente la Perception de <%= per %> points. Armoire Enchantée : Ensemble de Dresseur de Dragon (Objet 2 sur 3).",
"shieldArmoireMysticLampText": "Lampe Mystique",
"shieldArmoireMysticLampNotes": "Illuminez les cavernes les plus sombres grâce à cette lampe mystique ! Augmente la Perception de <%= per %> points. Armoire Enchantée : Objet Indépendant.",
+ "shieldArmoireFloralBouquetText": "Bouquet de fleurs",
+ "shieldArmoireFloralBouquetNotes": "Ne sont pas très utiles dans une bataille, mais ne sont-elles pas belles ? Augmente la Constitution de <%= con %> points. Armoire Enchantée : Objet Indépendant.",
"back": "Accessoire de Dos",
"backBase0Text": "Pas d’accessoire dorsal",
"backBase0Notes": "Pas d’accessoire dorsal.",
@@ -815,11 +831,25 @@
"headAccessoryMystery201510Notes": "Terrifiantes et un peu visqueuses. N'apportent aucun bonus. Équipement d'abonné·e d'octobre 2015.",
"headAccessoryMystery301405Text": "Lunettes Frontales",
"headAccessoryMystery301405Notes": "\"Les lunettes c'est pour les yeux,\" disaient-ils. \"Personne ne voudrait de lunettes qu'on ne peut porter que sur la tête\" disaient-ils. Ha ! Vous leur avez bien montré ! N'apporte aucun bonus. Équipement d'abonné·e d'août 3015.",
- "headAccessoryArmoireComicalArrowText": "Comical Arrow",
- "headAccessoryArmoireComicalArrowNotes": "This whimsical item doesn't provide a stat boost, but it sure is good for a laugh! Confers no benefit. Enchanted Armoire: Independent Item.",
+ "headAccessoryArmoireComicalArrowText": "Fléche Comique",
+ "headAccessoryArmoireComicalArrowNotes": "Cet objet saugrenu n'apporte aucune amélioration, mais au moins il fait rire ! Ne confère aucun bonus. Armoire enchantée : Objet indépendant.",
"eyewear": "Lunettes",
"eyewearBase0Text": "Pas de Lunettes",
"eyewearBase0Notes": "Pas de Lunettes.",
+ "eyewearSpecialBlackTopFrameText": "Lunettes normales noires",
+ "eyewearSpecialBlackTopFrameNotes": "Des lunettes à monture noire. Ne confère aucun bonus.",
+ "eyewearSpecialBlueTopFrameText": "Lunettes normales bleues",
+ "eyewearSpecialBlueTopFrameNotes": "Des lunettes à monture bleue. Ne confère aucun bonus.",
+ "eyewearSpecialGreenTopFrameText": "Lunettes normales vertes",
+ "eyewearSpecialGreenTopFrameNotes": "Des lunettes à monture verte. Ne confère aucun bonus.",
+ "eyewearSpecialPinkTopFrameText": "Lunettes normales roses",
+ "eyewearSpecialPinkTopFrameNotes": "Des lunettes à monture rose. Ne confère aucune bonus.",
+ "eyewearSpecialRedTopFrameText": "Lunettes normales rouges",
+ "eyewearSpecialRedTopFrameNotes": "Des lunettes cerclées de rouge. Ne confère aucun bonus.",
+ "eyewearSpecialWhiteTopFrameText": "Lunettes normales blanches",
+ "eyewearSpecialWhiteTopFrameNotes": "Des lunettes à monture blanche. Ne confère aucun bonus.",
+ "eyewearSpecialYellowTopFrameText": "Lunettes normales jaunes",
+ "eyewearSpecialYellowTopFrameNotes": "Des lunettes à monture jaune. Ne confère aucun bonus.",
"eyewearSpecialSummerRogueText": "Cache-œil Espiègle",
"eyewearSpecialSummerRogueNotes": "Même un voyou peut voir à quel point c'est élégant ! N'apporte aucun bonus. Équipement en édition limitée de l’été 2014.",
"eyewearSpecialSummerWarriorText": "Cache-œil Fringuant",
@@ -837,7 +867,7 @@
"eyewearMystery301404Text": "Lunettes",
"eyewearMystery301404Notes": "Il n'y a pas plus raffiné qu'une paire de lunettes - à l'exception, peut-être, d'un monocle. N'apporte aucun bonus. Équipement d'abonné·e d'avril 3015.",
"eyewearMystery301405Text": "Monocle",
- "eyewearMystery301405Notes": "Il n'y a pas plus raffiné qu'un monocle - à l'exception, peut-être, d'une paire de lunettes. N'apporte aucun bonus. Équipement d'abonné·e de Juillet 3015.",
+ "eyewearMystery301405Notes": "Il n'y a pas plus raffiné qu'un monocle - à l'exception, peut-être, d'une paire de lunettes. N'apporte aucun bonus. Équipement d'abonné·e de juillet 3015.",
"eyewearArmoirePlagueDoctorMaskText": "Masque de Médecin de la Peste",
"eyewearArmoirePlagueDoctorMaskNotes": "Un authentique masque porté par les médecins qui ont combattu la Peste de Procrastination ! N'apporte aucun bonus. Armoire Enchantée, Ensemble de Médecin de la Peste (Objet 2 sur 3)."
}
\ No newline at end of file
diff --git a/common/locales/fr/generic.json b/common/locales/fr/generic.json
index 24d685f819..0e4bcc3b14 100644
--- a/common/locales/fr/generic.json
+++ b/common/locales/fr/generic.json
@@ -25,7 +25,7 @@
"titleSettings": "Paramètres",
"expandToolbar": "Montrer la barre d'outlis",
"collapseToolbar": "Cacher la barre d'outils",
- "markdownBlurb": "Habitica utilise le balisage markdown dans les espaces de discussion. Voir le Mémo de Markdown pour de plus amples informations.",
+ "markdownBlurb": "Habitica utilise le balisage markdown dans les espaces de discussion. Voir le Mémo de Markdown pour de plus amples informations.",
"showFormattingHelp": "Afficher l'aide pour l'utilisation des balises.",
"hideFormattingHelp": "Masquer l'aide pour l'utilisation des balises.",
"youType": "Lorsque vous tapez:",
@@ -137,8 +137,8 @@
"achievementStressbeastText": "A contribué à vaincre l'Abominable Homme du Stress pendant l’Événement Pays des Merveilles de l'Hiver 2014 !",
"achievementBurnout": "Sauveur des Champs Florissants",
"achievementBurnoutText": "A contribué à vaincre Burnout et à revivifier les Esprits d'Échappement au cours du l'Événement Festival de l'Automne 2015 !",
- "achievementBewilder": "Savior of Mistiflying",
- "achievementBewilderText": "Helped defeat the Be-Wilder during the 2016 Spring Fling Event!",
+ "achievementBewilder": "Sauveur de Mistivolant",
+ "achievementBewilderText": "A contribué à vaincre l'Être Déchaîné au cours de l'événement Ménage de Printemps 2016 !",
"checkOutProgress": "Regardez mes progrès sur Habitica !",
"cardReceived": "A reçu une carte !",
"cardReceivedFrom": "<%= cardType %> de <%= userName %>",
diff --git a/common/locales/fr/groups.json b/common/locales/fr/groups.json
index 6fd93d0f65..f00d472438 100644
--- a/common/locales/fr/groups.json
+++ b/common/locales/fr/groups.json
@@ -92,6 +92,7 @@
"send": "Envoyer",
"messageSentAlert": "Message envoyé",
"pmHeading": "Envoyer un message privé à <%= name %>",
+ "pmsMarkedRead": "Vos messages privés ont été marqués comme lu",
"clearAll": "Effacer tous les messages",
"confirmDeleteAllMessages": "Êtes-vous certain de vouloir effacer tous les messages de votre boîte ? Les autres utilisateurs continueront de voir les messages que vous avez expédiés.",
"optOutPopover": "Vous n'aimez pas les messages privés ? Cliquez ici pour désactiver l'option",
@@ -99,6 +100,15 @@
"unblock": "Ne plus bloquer",
"pm-reply": "Répondre",
"inbox": "Boîte de Réception",
+ "messageRequired": "Un message est requis.",
+ "toUserIDRequired": "Un ID d'utilisateur est requis",
+ "gemAmountRequired": "Un nombre de gemmes est requis",
+ "notAuthorizedToSendMessageToThisUser": "Impossible d'envoyer un message à cet utilisateur.",
+ "privateMessageGiftIntro": "Bonjour <%= receiverName %>, <%= senderName %> vous a envoyé",
+ "privateMessageGiftGemsMessage": "<%= gemAmount %> gemmes!",
+ "privateMessageGiftSubscriptionMessage": "<%= numberOfMonths %> mois d'abonnement !",
+ "cannotSendGemsToYourself": "Vous ne pouvez pas vous envoyer de gemmes. Essayez un abonnement à la place.",
+ "badAmountOfGemsToSend": "Le total doit être compris entre 1 et votre nombre actuel de gemmes.",
"abuseFlag": "Signaler une infraction aux Règles de la Communauté",
"abuseFlagModalHeading": "Signaler <%= name %> pour infraction ?",
"abuseFlagModalBody": "Êtes vous sûr de vouloir signaler ce post ? Vous ne devez reporter UNIQUEMENT les posts qui sont contraîre les <%= firstLinkStart %>Règles de la Communauté<%= linkEnd %> et/ou les <%= secondLinkStart %>Conditions Générales d'Utilisation <%= linkEnd%>. Signaler inutilement un post est une violation des Règles de la Communauté et peux vous faire recevoir un avertissement. Les raisons qui justifient le signalement d'un post incluent mais ne se limitent pas :
aux insultes ou la diffamation
à l'intolérance ou propos à caractères religieux
aux sujets 'réservés aux adultes'
à la violence, même sous forme de plaisanterie
au spamming et aux messages incompréhensibles ou volontairement sans aucun sens
",
@@ -118,7 +128,7 @@
"inviteByEmailExplanation": "Si un ami rejoint Habitica via votre courriel, il sera automatiquement invité dans votre équipe !",
"inviteFriendsNow": "Inviter des amis maintenant",
"inviteFriendsLater": "Inviter des amis plus tard",
- "inviteAlertInfo": "Si vous avez des amis qui utilisent déjà Habitica, invitez-les avec leur ID d'Utilisateur ici.",
+ "inviteAlertInfo": "Si vous avez des amis qui utilisent déjà Habitica, invitez-les avec leur ID d'Utilisateur ici.",
"inviteExistUser": "Inviter des membres existants",
"byColon": "Invité par :",
"inviteNewUsers": "Inviter de nouveaux membres",
@@ -151,5 +161,29 @@
"partyUpName": "Une joyeuse équipe",
"partyOnName": "Une équipe festive",
"partyUpAchievement": "A rejoint une équipe avec une autre personne ! Amusez vous à combattre des monstres et à vous encourager.",
- "partyOnAchievement": "A rejoint une équipe d'au moins quatre personnes ! Profitez de votre plus grande responsabilité en vous regroupant avec vos amis pour vaincre vos ennemis !"
+ "partyOnAchievement": "A rejoint une équipe d'au moins quatre personnes ! Profitez de votre plus grande responsabilité en vous regroupant avec vos amis pour vaincre vos ennemis !",
+ "groupIdRequired": "\"groupId\" doit être un UUID valide",
+ "groupNotFound": "Groupe non trouvé.",
+ "groupTypesRequired": "Vous devez fournir une requête \"type\" valide.",
+ "questLeaderCannotLeaveGroup": "Vous ne pouvez pas quitter une équipe si vous avez démarré une quête. Abandonnez la quête d'abord.",
+ "cannotLeaveWhileActiveQuest": "Vous ne pouvez pas quitter une équipe pendant une quête active. Veuillez d'abord quitter la quête.",
+ "onlyLeaderCanRemoveMember": "Seul le responsable d'équipe peut supprimer un membre !",
+ "memberCannotRemoveYourself": "Vous ne pouvez pas vous supprimer vous-même !",
+ "groupMemberNotFound": "Utilisateur non trouvé parmi les membres du groupe",
+ "mustBeGroupMember": "Doit être un membre du groupe.",
+ "keepOrRemoveAll": "req.query.keep doit être soit \"keep-all\" soit \"remove-all\"",
+ "keepOrRemove": "req.query.keep doit être soit \"keep\" soit \"remove\"",
+ "canOnlyInviteEmailUuid": "Vous ne pouvez inviter qu'avec des UUIDs ou des courriels.",
+ "inviteMissingEmail": "L'adresse courriel est manquante dans l'invitation.",
+ "partyMustbePrivate": "Les équipes doivent être privées",
+ "userAlreadyInGroup": "Utilisateur déjà dans cette équipe.",
+ "userAlreadyInvitedToGroup": "Utilisateur déjà invité à joindre cette équipe.",
+ "userAlreadyPendingInvitation": "Utilisateur déjà en cours d'invitation.",
+ "userAlreadyInAParty": "Utilisateur déjà dans une équipe.",
+ "userWithIDNotFound": "Utilisateur avec l'ID \"<%= userId %>\" non trouvé.",
+ "userHasNoLocalRegistration": "L'utilisateur n'a pas un enregistrement local (nom d'utilisateur, courriel, mot de passe).",
+ "uuidsMustBeAnArray": "Les ID d'utilisateurs des invitations doivent être un tableau.",
+ "emailsMustBeAnArray": "Les emails des invitations doivent être un tableau.",
+ "canOnlyInviteMaxInvites": "Vous ne pouvez envoyer que \"<%= maxInvites %>\" invitations à la fois",
+ "onlyCreatorOrAdminCanDeleteChat": "Vous n'êtes pas autorisé à supprimer ce message !"
}
\ No newline at end of file
diff --git a/common/locales/fr/limited.json b/common/locales/fr/limited.json
index 002f00a123..e7712af012 100644
--- a/common/locales/fr/limited.json
+++ b/common/locales/fr/limited.json
@@ -5,7 +5,7 @@
"annoyingFriends": "Amis Pénibles",
"annoyingFriendsText": "A été touché·e <%= snowballs %> fois par des boules de neige lancées par un membre de l'équipe.",
"alarmingFriends": "Amis Inquiétants",
- "alarmingFriendsText": "A été transformé·e en fantôme <%= spookDust %> fois par des membres de l'équipe.",
+ "alarmingFriendsText": "A été effrayé·e <%= spookySparkles %> fois par des membres de l'équipe.",
"agriculturalFriends": "Amis Agricoles",
"agriculturalFriendsText": "A été transformé·e en fleur <%= seeds %> fois par un membre de son équipe.",
"aquaticFriends": "Amis Aquatiques",
@@ -27,11 +27,11 @@
"seasonalShop": "Boutique Saisonnière",
"seasonalShopClosedTitle": "<%= linkStart %>Leslie<%= linkEnd %>",
"seasonalShopTitle": "<%= linkStart %>Sorcière Saisonnière<%= linkEnd %>",
- "seasonalShopClosedText": "La Boutique Saisonnière est actuellement fermée !! Je ne sais pas où est passé la Sorcière Saisonnière, mais je parie qu'elle sera de retour lors du prochain Grand Gala !",
- "seasonalShopText": "Bienvenue à la Boutique Saisonnière !! Nous avons actuellement reçu les nouveautés Édition Saisonnière du Printemps. Tout l'équipement est disponible à l'achat pendant la Fête du Printemps chaque année, mais nous ne sommes ouverts que jusqu'au 30 avril, alors faites un stock dès maintenant, ou vous devrez attendre un an pour acheter à nouveau cet équipement !",
- "seasonalShopSummerText": "Bienvenue à la Boutique Saisonnière !! Nous avons reçu les nouveautés Édition Saisonnière de l'Été. Tout l'équipement sera disponible à l'achat pendant la Fête de l'Été chaque année, mais nous ne sommes ouverts que jusqu'au 31 juillet, alors faites un stock dès maintenant ou vous devrez attendre un an pour acheter à nouveau cet équipement !",
- "seasonalShopFallText": "Bienvenue à la Boutique Saisonnière !! Nous avons actuellement reçu les nouveautés Édition Saisonnière d'automne. Tout l'équipement est disponible à l'achat pendant le Festival d'Automne chaque année, mais nous ne sommes ouverts que jusqu'au 31 octobre, alors faites un stock dès maintenant, ou vous devrez attendre un an pour acheter à nouveau cet équipement !",
- "seasonalShopWinterText": "Bienvenue à la Boutique Saisonnière !! Nous avons actuellement reçu les nouveautés de l'Édition Saisonnière de l'Hiver. Tout l'équipement est disponible à l'achat pendant La Fantaisie Hivernale chaque année, mais nous ne sommes ouverts que jusqu'au 31 janvier, alors faites un stock dès maintenant, ou vous devrez attendre un an pour acheter à nouveau cet équipement !",
+ "seasonalShopClosedText": "La Boutique Saisonnière est actuellement fermée !! Je ne sais pas où est passé la Sorcière Saisonnière, mais je parie qu'elle sera de retour lors du prochain Grand Gala !",
+ "seasonalShopText": "Bienvenue à la Boutique Saisonnière !! Nous avons actuellement reçu les nouveautés Édition Saisonnière du Printemps. Tout l'équipement est disponible à l'achat pendant la Fête du Printemps chaque année, mais nous ne sommes ouverts que jusqu'au 30 avril, alors faites un stock dès maintenant, ou vous devrez attendre un an pour acheter à nouveau cet équipement !",
+ "seasonalShopSummerText": "Bienvenue à la Boutique Saisonnière !! Nous avons reçu les nouveautés Édition Saisonnière de l'Été. Tout l'équipement sera disponible à l'achat pendant la Fête de l'Été chaque année, mais nous ne sommes ouverts que jusqu'au 31 juillet, alors faites un stock dès maintenant ou vous devrez attendre un an pour acheter à nouveau cet équipement !",
+ "seasonalShopFallText": "Bienvenue à la Boutique Saisonnière !! Nous avons actuellement reçu les nouveautés Édition Saisonnière d'automne. Tout l'équipement est disponible à l'achat pendant le Festival d'Automne chaque année, mais nous ne sommes ouverts que jusqu'au 31 octobre, alors faites un stock dès maintenant, ou vous devrez attendre un an pour acheter à nouveau cet équipement !",
+ "seasonalShopWinterText": "Bienvenue à la Boutique Saisonnière !! Nous avons actuellement reçu les nouveautés de l'Édition Saisonnière de l'Hiver. Tout l'équipement est disponible à l'achat pendant La Fantaisie Hivernale chaque année, mais nous ne sommes ouverts que jusqu'au 31 janvier, alors faites un stock dès maintenant, ou vous devrez attendre un an pour acheter à nouveau cet équipement !",
"seasonalShopFallTextBroken": "Oh... Bienvenue à la Boutique Saisonnière... Nous avons actuellement reçu les nouveautés Édition Saisonnière d'automne, ou quelque chose du genre... Tout l'équipement est disponible à l'achat pendant le Festival d'Automne chaque année, mais nous ne sommes ouverts que jusqu'au 31 octobre... alors je suppose que vous devriez faire le stock dès maintenant, ou vous devrez attendre... et attendre... et attendre... *soupir*",
"seasonalShopRebirth": "Si vous avez utilisé l'Orbe de Renaissance, vous pouvez racheter cet équipement dans la colonne des Récompenses. Au début, vous pourrez seulement acheter les objets associés à votre classe actuelle (Guerrier par défaut), mais n'ayez crainte, les objets spécifiques à une classe deviendront disponibles si vous choisissez cette classe.",
"candycaneSet": "Sucre d'Orge (Mage)",
@@ -72,5 +72,6 @@
"comfortingKittySet": "Chaton rassurant (Guérisseur)",
"sneakySqueakerSet": "Rongeur sournois (Voleur)",
"fallEventAvailability": "Disponible jusqu'au 31 octobre",
- "winterEventAvailability": "Disponible jusqu'au 31 décembre"
+ "winterEventAvailability": "Disponible jusqu'au 31 décembre",
+ "springEventAvailability": "Disponible jusqu'au 31 mai"
}
\ No newline at end of file
diff --git a/common/locales/fr/maintenance.json b/common/locales/fr/maintenance.json
new file mode 100644
index 0000000000..7e391e2b47
--- /dev/null
+++ b/common/locales/fr/maintenance.json
@@ -0,0 +1,34 @@
+{
+ "habiticaBackSoon": "Pas de panique, Habitica revient vite !",
+ "importantMaintenance": "Nous sommes en train d'effectuer d'importantes tâches de maintenance qui devraient être terminées à <%= localDate %> dans votre fuseau horaire.",
+ "maintenance": "Maintenance",
+ "maintenanceMoreInfo": "Vous voulez plus d'information sur la maintenance ? <%= linkStart %>Allez voir notre page d'information<%= linkEnd %>.",
+ "noDamageKeepStreaks": "Vous ne prendrez AUCUN dommage et ne perdrez aucun combo !",
+ "thanksForPatience": "Merci pour votre patience !",
+ "twitterMaintenanceUpdates": "Pour vous tenir informé des mises à jours récentes, suivez notre compte Twitter, où nous posterons toutes les informations.",
+ "veteranPetAward": "À la fin, vous recevrez un familier Vétéran !",
+
+ "maintenanceInfoTitle": "Information à propos des maintenances à venir sur Habitica",
+ "maintenanceInfoWhat": "Que se passe-t-il ?",
+ "maintenanceInfoWhatText": "Le 21 mai, Habitica sera fermé pour maintenance durant la majorité de la journée. Vous ne prendrez aucun dommage et votre compte ne risque rien durant ce weekend, même si vous ne pouvez pas vous connecter pour valider vos Quotidiennes à temps. Nous travaillerons fort pour rendre la maintenance aussi courte que possible et posterons des mises à jour régulières sur notre compte Twitter. À la fin de la maintenance, pour vous remercier de votre patience, vous recevrez un familier rare !",
+ "maintenanceInfoWhy": "Pourquoi est-ce que cela arrive ?",
+ "maintenanceInfoWhyText": "Pendant les derniers mois, nous avons totalement repensé les coulisses d'Habitica. Nous avons notamment réécris l'API. Même si le site n'a pas l'air d'avoir bien changé en surface, c'est un tout nouveau monde en dessous. Ces modifications nous permettront d'être BIEN PLUS flexibles dans la création de nouvelles fonctionnalités et amélioreront les performances !",
+ "maintenanceInfoTechDetails": "Vous voulez plus de détails sur la partie technique du processus ? Visitez La Forge, notre blog de développement.",
+ "maintenanceInfoMore": "Informations supplémentaires",
+ "maintenanceInfoAccountChanges": "Quels changements vais-je trouver sur mon compte une fois que la réécriture sera terminée ?",
+ "maintenanceInfoAccountChangesText": "Au début, vous ne percevrez aucun changement notable, à l'exception de l'amélioration des performances de certaines fonctionnalités comme les Défis. Si vous remarquez un changement qui n'a pas lieu d'être, communiquez avec nous à admin@habitica.com pour que nous cherchions le problème pour vous !",
+ "maintenanceInfoAddFeatures": "Quel genre de fonctionnalités pourront alors être ajoutées à Habitica ?",
+ "maintenanceInfoAddFeaturesText": "Grâce à cette réécriture, nous pourrons commencer à créer des améliorations aux forum de discussion et aux Guildes, des forfaits pour les organisations et les familles ainsi que d'autres fonctionnalités de productivité comme les Mensuelles et la possibilité de valider les tâches de la veille ! Chaque fonctionnalité est aussi complexe que la suivante, alors les créer prendra du temps. Toutefois, nous ne pouvions tout simplement pas commencer sans cette réécriture.",
+ "maintenanceInfoHowLong": "Combien de temps durera la maintenance ?",
+ "maintenanceInfoHowLongText": "Nous devons migrer les tâches et les données des 1,3 millions d'utilisateurs d'Habitica et ce n'est pas une mince affaire ! Nous prévoyons que cette migration se déroulera environ de 13 h à 22 h, heure du Pacifique (soit de 20 h à 5 h UTC). Soyez assuré, nous faisons notre possible pour la terminer aussi vite que possible ! Vous pouvez suivre les mises à jour sur notre compte Twitter.",
+ "maintenanceInfoStatsAffected": "Comment seront affectées mes tâches Quotidiennes, mes combos, mes bonus et mes quêtes ?",
+ "maintenanceInfoStatsAffectedText1": "Vous ne prendrez AUCUN dégât ni ne perdrez de combo ce week-end. En dehors de ça, votre journée recommencera normalement ! Les Quotidiennes que vous avez validées seront à nouveau réalisables, les bonus seront réinitialisés, etc. Si vous participez à une quête de collection d'objets, vous trouverez toujours des objets. Si vous combattez un boss, vous lui infligerez toujours des dégâts, mais le boss ne vous en infligera pas. Même les monstres ont besoin de repos !",
+ "maintenanceInfoStatsAffectedText2": "Après mûre réflexion, notre équipe a conclu que c'était la façon la plus équitable de gérer le fait que beaucoup d'utilisateurs ne pourront pas valider leurs tâches Quotidiennes normalement pendant la maintenance. Nous sommes désolés pour la gêne occasionnée !",
+ "maintenanceInfoSeeTasks": "Que faire si j'ai besoin de voir ma liste de tâches ?",
+ "maintenanceInfoSeeTasksText": "Si vous savez déjà que vous aurez besoin de voir votre liste de tâches samedi pour vous souvenir de ce que vous avez à faire, nous vous recommandons de prendre une capture d'écran de vos tâches avant que la maintenance ne démarre, pour l'utiliser comme référence.",
+ "maintenanceInfoRarePet": "Quel type de familier rare vais-je recevoir ?",
+ "maintenanceInfoRarePetText": "Pour vous remercier de votre patience pendant cette indisponibilité, tout le monde recevra un rare familier vétéran. Si vous n'en avez jamais reçu précédemment, vous recevrez le loup vétéran. Si vous avez déjà le loup vétéran, vous recevrez le tigre vétéran. Et si vous avez déjà le tigre vétéran, vous recevrez un tout nouveau familier vétéran ! Votre familier vétéran pourrait apparaître plusieurs heures après la fin de la maintenance, mais ne craignez rien, tout le monde en aura un.",
+ "maintenanceInfoWho": "Qui a travaillé sur cet énorme projet ?",
+ "maintenanceInfoWhoText": "Nous sommes heureux que vous le demandiez ! Il a été dirigé par notre merveilleux contributeur paglias, aidé généreusement de Blade, de TheHollidayInn, de SabreCat, de Victor, de Pudeyevn, de TheUnknown et d'Alys.",
+ "maintenanceInfoTesting": "La nouvelle version a aussi été testée sans relâche par bon nombre d'incroyables volontaires open-source. Merci ! Nous n'aurions pas réussi sans vous."
+}
diff --git a/common/locales/fr/npc.json b/common/locales/fr/npc.json
index cbc8626352..4d4df6f2e9 100644
--- a/common/locales/fr/npc.json
+++ b/common/locales/fr/npc.json
@@ -5,7 +5,7 @@
"mattShall": "Dois-je préparer votre coursier, <%= name %> ? Lorsque vous avez assez nourri un animal pour qu'il devienne une fière monture, il apparaît ici. Cliquez sur une monture pour monter en selle !",
"mattBochText1": "Bienvenue à l'Écurie ! Je suis Matt, le Maître des Bêtes. Après avoir passé le niveau 3, vous pouvez faire éclore vos familiers grâce à vos œufs et potions. Lorsque vous faites éclore un animal au Marché, il apparaît ici! Cliquez sur l'image d'un animal pour le faire rejoindre votre avatar. Donnez à vos familiers la nourriture que vous trouvez dès la fin du niveau 3, et ils deviendront de puissantes montures.",
"daniel": "Daniel",
- "danielText": "Bienvenue à la Taverne ! Restez un moment et rencontrez les habitants. Si vous avez besoin de vous reposer (vacances ? maladie ?), je vous installerai à l'Auberge. Pendant votre séjour, vos tâches Quotidiennes ne vous infligeront pas de dommages à la fin de la journée, mais vous pourrez quand même les réaliser.",
+ "danielText": "Bienvenue à la Taverne ! Installez-vous et faites connaissance avec les autres personnes. Si vous avez besoin de vous reposer (vacances ? maladie ?), je vous installerai à l'Auberge. Pendant votre séjour, vos tâches Quotidiennes ne vous infligeront pas de dommages à la fin de la journée, mais vous pourrez quand même les réaliser.",
"danielText2": "Prenez garde : si vous êtes au milieu d'une quête contre un boss, celui-ci vous infligera tout de même des blessures en fonction des Quotidiennes manquées des membres de votre groupe ! De façon identique, vos propres dégâts au Boss (ou les objets récoltés) ne seront pas appliqués tant que vous ne quitterez pas l'Auberge.",
"danielTextBroken": "Bienvenue à la Taverne... je suppose... Si vous avez besoin de vous reposer, je vous installerai à l'Auberge... Pendant votre séjour, vos tâches Quotidiennes ne vous infligeront pas de dommages à la fin de la journée, mais vous pourrez quand même les réaliser... si vous en avez la force...",
"danielText2Broken": "Oh... si vous êtes au milieu d'une quête contre un boss, celui-ci vous infligera tout de même des blessures si les membres de votre groupe rate des Quotidiennes ! De plus, vos propres dégâts infligés au Boss (ou les objets récoltés) ne seront pas appliqués tant que vous n'aurez pas quitté l'Auberge.",
@@ -21,6 +21,26 @@
"ian": "Ian",
"ianText": "Bienvenue à la boutique des Quêtes ! Vous pouvez utiliser des parchemins de quête pour battre des monstres avec vos amis. Soyez sûr de vérifier notre ensemble de parchemins de Quêtes à l'achat sur votre droite !",
"ianBrokenText": "Bienvenue à la boutique des Quêtes... Ici, vous pouvez utiliser des Parchemins de quêtes pour combattre des monstres avec vos amis. Assurez-vous de regarder notre ensemble de Parchemins de quêtes en vente sur votre droite...",
+ "missingKeyParam": "\"req.params.key\" est requis.",
+ "itemNotFound": "Objet \"<%= key %>\" non trouvé.",
+ "cannotBuyItem": "Vous ne pouvez pas acheter cet objet.",
+ "missingTypeKeyEquip": "\"key\" et \"type\" sont des paramètres requis.",
+ "missingPetFoodFeed": "\"pet\" et \"food\" sont des paramètres requis.",
+ "invalidPetName": "Nom de familier invalide.",
+ "missingEggHatchingPotionHatch": "\"egg\" et \"hatchingPotion\" sont des paramètres requis.",
+ "invalidTypeEquip": "\"type\" doit faire partie de 'equipped', 'pet', 'mount', 'costume'.",
+ "mustPurchaseToSet": "Il faut acheter <%= val %> pour le mettre en <%= key %>.",
+ "typeRequired": "Type est requis",
+ "keyRequired": "La clé est requise",
+ "notAccteptedType": "Le Type doit faire partie de [eggs, hatchingPotions, food, quests, gear]",
+ "contentKeyNotFound": "Clé non trouvée pour le contenu <%= type %>",
+ "plusOneGem": "+1 Gemme",
+ "typeNotSellable": "Type n'est pas vendable. Il doit faire partie des types suivants <%= acceptedTypes %>",
+ "userItemsKeyNotFound": "Clé non trouvée pour user.items <%= type %>",
+ "pathRequired": "La chaîne de chemin d'accès est requise",
+ "unlocked": "Les objets ont été débloqués.",
+ "alreadyUnlocked": "Ensemble complet déjà débloqué.",
+ "alreadyUnlockedPart": "Ensemble déjà partiellement débloqué.",
"USD": "(USD)",
"newStuff": "Nouveauté",
"cool": "Rappelez-le moi plus tard",
@@ -64,6 +84,7 @@
"tourPetsPage": "Voici l'Écurie ! Après avoir passé le niveau 3, vous pouvez faire éclore vos familiers grâce à vos œufs et potions. Lorsque vous faites éclore un animal au Marché, il apparaît ici ! Cliquez sur l'image d'un animal pour le faire rejoindre votre avatar. Donnez à vos familiers la nourriture que vous trouvez dès la fin du niveau 3, et ils deviendront de puissantes montures.",
"tourMountsPage": "Lorsque vous avez assez nourri un animal pour qu'il devienne une fière monture, il apparaîtra ici. (les familiers, les montures et la nourriture sont accessibles à la fin du niveau 3.) Cliquez sur une monture pour monter en selle !",
"tourEquipmentPage": "C'est ici que vous rangez votre Équipement ! Votre Tenue de Combat influe sur vos stats. Si vous voulez que votre avatar arbore un équipement différent sans changer vos stats, cochez \"Utiliser un Costume\".",
+ "equipmentAlreadyOwned": "Vous avez déjà acheté cette pièce d'équipement.",
"tourOkay": "Cool !",
"tourAwesome": "Génial !",
"tourSplendid": "Splendide !",
diff --git a/common/locales/fr/pets.json b/common/locales/fr/pets.json
index 4c8c1fb047..42e3de96e1 100644
--- a/common/locales/fr/pets.json
+++ b/common/locales/fr/pets.json
@@ -12,6 +12,7 @@
"etherealLion": "Lion Éthéré",
"veteranWolf": "Loup de Vétéran",
"veteranTiger": "Tigre de Vétéran",
+ "veteranLion": "Lion vétéran",
"cerberusPup": "Chiot Cerbère",
"hydra": "Hydre",
"mantisShrimp": "Crevette-mante",
@@ -19,7 +20,7 @@
"orca": "Orque",
"royalPurpleGryphon": "Griffon Pourpre Royal",
"phoenix": "Phénix",
- "bumblebee": "Bumblebee",
+ "magicalBee": "Abeille féerique",
"rarePetPop1": "Cliquez sur l'empreinte dorée pour savoir comment obtenir cet animal rare en contribuant à Habitica !",
"rarePetPop2": "Comment obtenir ce Familier !",
"potion": "Potion <%= potionType %>",
@@ -35,7 +36,7 @@
"foodText": "nourriture",
"food": "Nourriture et Selles",
"noFood": "Vous n'avez ni nourriture ni selle.",
- "dropsExplanation": "Récupérez ces objets plus vite avec des Gemmes, si vous ne voulez pas attendre de les recevoir comme butin. Apprenez-en plus sur le système de butin.",
+ "dropsExplanation": "Récupérez ces objets plus vite avec des Gemmes, si vous ne voulez pas attendre de les recevoir comme butin. Apprenez-en plus sur le système de butin.",
"premiumPotionNoDropExplanation": "Les potions d'éclosion magiques ne peuvent pas être utilisées sur les œufs reçus dans les quêtes. Le seul moyen d'obtenir des potions d'éclosion magiques est de les acheter ci-dessous, pas de les recevoir des butins.",
"beastMasterProgress": "Progression Maître des Bêtes",
"stableBeastMasterProgress": "Progression du Maître des Bêtes : <%= number %> Familiers Trouvés",
@@ -62,6 +63,7 @@
"hatchedPet": "Vous avez fait éclore un <%= egg %><%= potion %>!",
"displayNow": "Afficher tout de suite",
"displayLater": "Afficher plus tard",
+ "petNotOwned": "Vous ne possédez pas ce familier.",
"earnedCompanion": "Grâce à votre productivité, vous avez gagné un nouveau compagnon. Nourrissez-le pour le faire grandir !",
"feedPet": "Donner <%= article %><%= text %> à <%= name %> ?",
"useSaddle": "Seller <%= pet %> ?",
@@ -83,5 +85,8 @@
"petKeyBoth": "Relâcher les Deux",
"confirmPetKey": "Êtes-vous sur·e ?",
"petKeyNeverMind": "Pas Encore",
+ "petsReleased": "Familiers libérés.",
+ "mountsAndPetsReleased": "Montures et familiers libérés",
+ "mountsReleased": "Montures libérées",
"gemsEach": "gemmes chacun"
}
\ No newline at end of file
diff --git a/common/locales/fr/quests.json b/common/locales/fr/quests.json
index 24a2eb3153..bffaf8c0fa 100644
--- a/common/locales/fr/quests.json
+++ b/common/locales/fr/quests.json
@@ -38,7 +38,7 @@
"bossDmg2": "Seuls les participants pourront combattre le boss et partager le butin de la quête.",
"bossDmg1Broken": "Chaque tâche Quotidienne ou À Faire et chaque Habitude positive effectuée inflige des dommages au boss... Frappez plus fort avec des tâches plus rouges ou avec les sorts Frappe Brutale et Explosion de Flammes... Le boss infligera des dommages à chacun des participants de la quête pour chaque Quotidienne manquée (multipliés par la Force du boss) en plus des dommages normaux... Alors protégez votre équipe en effectuant vos Quotidiennes... Tout dommage causé par vous et par le boss est pris en compte au cron (la réinitialisation de votre journée)...",
"bossDmg2Broken": "Seuls les participants pourront combattre le boss et partager le butin de la quête...",
- "tavernBossInfo": "Complete Dailies and To-Dos and score positive Habits to damage the World Boss! Incomplete Dailies fill the Rage Bar. When the Rage bar is full, the World Boss will attack an NPC. A World Boss will never damage individual players or accounts in any way. Only active accounts not resting in the Inn will have their tasks tallied.",
+ "tavernBossInfo": "Achève tes tâches Quotidiennes et complète des Habitudes positives pour faire des dégâts contre le Boss Mondial! Les tâches Quotidiennes non-faites remplissent sa barre de rage. Quand sa barre de rage est remplie, le Boss Mondial attaquera un PNJ. Un Boss Mondial n'attaquera en aucun cas un joueur ou un compte en ligne. Seuls les comptes actifs qui ne se reposent pas dans l'auberge auront leurs tâches comptées.",
"tavernBossInfoBroken": "Effectuez vos tâches Quotidiennes et À Faire et continuez vos Habitudes positives pour blesser le Boss Mondial... Chaque Quotidienne non effectuée remplit la barre d'Harassement... Lorsque la barre d'Harassement est pleine, le Boss Mondial attaquera un PNJ... Un Boss Mondial ne blessera jamais des joueurs individuels ou des comptes de quelque façon que ce soit... Seules les tâches des comptes actifs de joueurs qui ne se reposent pas à l'Auberge sont prises en compte...",
"bossColl1": "Pour obtenir des objets, accomplissez vos tâches positives. Vous trouvez les objets de quêtes de la même façon que vous trouvez les objets normaux mais vous ne les verrez que le jour suivant. À ce moment, tous les objets de quêtes que vous aurez trouvés seront comptabilisés et s'ajouteront à la pile.",
"bossColl2": "Seuls les participants peuvent collecter des objets et partager le butin de la quête.",
@@ -78,5 +78,24 @@
"whichQuestStart": "Quelle quête voulez-vous démarrer ?",
"getMoreQuests": "Obtenez plus de quêtes",
"unlockedAQuest": "Vous avez déverrouillé une quête !",
- "leveledUpReceivedQuest": "Vous avez atteint le niveau <%= level %> et reçu un parchemin de quête !"
+ "leveledUpReceivedQuest": "Vous avez atteint le niveau <%= level %> et reçu un parchemin de quête !",
+ "questInvitationDoesNotExist": "Aucune invitation à la quête n'a été envoyée pour l'instant.",
+ "questInviteNotFound": "Aucune invitation à la quête trouvée.",
+ "guildQuestsNotSupported": "Les guildes ne peuvent pas être invitées à joindre une quête.",
+ "questNotFound": "Quête \"<%= key %>\" introuvable.",
+ "questNotOwned": "Vous ne possédez pas ce parchemin de quête.",
+ "questNotGoldPurchasable": "La quête \"<%= key %>\" n'est pas achetable avec de l'or.",
+ "questLevelTooHigh": "Vous devez être au moins au niveau <%= level %> pour commencer cette quête.",
+ "questAlreadyUnderway": "Votre équipe est déjà en quête. Essayez à nouveau lorsque la quête actuelle sera terminée.",
+ "questAlreadyAccepted": "Vous avez déjà accepté l'invitation à la quête.",
+ "noActiveQuestToLeave": "Il n'y a pas de quête active à quitter",
+ "questLeaderCannotLeaveQuest": "Le responsable de la quête ne peut quitter la quête",
+ "notPartOfQuest": "Vous ne participez pas à la quête",
+ "noActiveQuestToAbort": "Il n'y a pas de quête active à interrompre.",
+ "onlyLeaderAbortQuest": "Seul le responsable du groupe ou de la quête peut interrompre une quête.",
+ "questAlreadyRejected": "Vous avez déjà rejeté l'invitation à la quête.",
+ "cantCancelActiveQuest": "Vous ne pouvez pas annuler une quête active, utilisez la fonction interrompre à la place.",
+ "onlyLeaderCancelQuest": "Seul le responsable du groupe ou de la quête peut annuler la quête.",
+ "questNotPending": "Il n'y a aucune quête à commencer.",
+ "questOrGroupLeaderOnlyStartQuest": "Seul le responsable du groupe ou de la quête peut forcer le début de la quête"
}
\ No newline at end of file
diff --git a/common/locales/fr/questscontent.json b/common/locales/fr/questscontent.json
index 94385d22d4..4a398042e8 100644
--- a/common/locales/fr/questscontent.json
+++ b/common/locales/fr/questscontent.json
@@ -298,15 +298,27 @@
"questSnailBoss": "Escargot de la Fange de Pénibilité",
"questSnailDropSnailEgg": "Escargot (Œuf)",
"questSnailUnlockText": "Débloque l'achat d’œuf d'Escargot au marché",
- "questBewilderText": "The Be-Wilder",
- "questBewilderNotes": "The party begins like any other.
The appetizers are excellent, the music is swinging, and even the dancing elephants have become routine. Habiticans laugh and frolic amid the overflowing floral centerpieces, happy to have a distraction from their least-favorite tasks, and the April Fool whirls among them, eagerly providing an amusing trick here and a witty twist there.
As the Mistiflying clock tower strikes midnight, the April Fool leaps onto the stage to give a speech.
“Friends! Enemies! Tolerant acquaintances! Lend me your ears.” The crowd chuckles as animal ears sprout from their heads, and they pose with their new accessories.
“As you know,” the Fool continues, “my confusing illusions usually only last a single day. But I’m pleased to announce that I’ve discovered a shortcut that will guarantee us non-stop fun, without having to deal with the pesky weight of our responsibilities. Charming Habiticans, meet my magical new friend... the Be-Wilder!”
Lemoness pales suddenly, dropping her hors d'oeuvres. “Wait! Don’t trust--”
But suddenly mists are pouring into the room, glittering and thick, and they swirl around the April Fool, coalescing into cloudy feathers and a stretching neck. The crowd is speechless as an monstrous bird unfolds before them, its wings shimmering with illusions. It lets out a horrible screeching laugh.
“Oh, it has been ages since a Habitican has been foolish enough to summon me! How wonderful it feels, to have a tangible form at last.”
Buzzing in terror, the magic bees of Mistiflying flee the floating city, which sags from the sky. One by one, the brilliant spring flowers wither up and wisp away.
“My dearest friends, why so alarmed?” crows the Be-Wilder, beating its wings. “There’s no need to toil for your rewards any more. I’ll just give you all the things that you desire!”
A rain of coins pours from the sky, hammering into the ground with brutal force, and the crowd screams and flees for cover. “Is this a joke?” Baconsaur shouts, as the gold smashes through windows and shatters roof shingles.
PainterProphet ducks as lightning bolts crackle overhead, and fog blots out the sun. “No! This time, I don’t think it is!”
Quickly, Habiticans, don’t let this World Boss distract us from our goals! Stay focused on the tasks that you need to complete so we can rescue Mistiflying -- and hopefully, ourselves.",
- "questBewilderCompletion": "The Be-Wilder is DEFEATED!
We've done it! The Be-Wilder lets out a ululating cry as it twists in the air, shedding feathers like falling rain. Slowly, gradually, it coils into a cloud of sparkling mist. As the newly-revealed sun pierces the fog, it burns away, revealing the coughing, mercifully human forms of Bailey, Matt, Alex.... and the April Fool himself.
Mistiflying is saved!
The April Fool has enough shame to look a bit sheepish. “Oh, hm,” he says. “Perhaps I got a little…. carried away.”
The crowd mutters. Sodden flowers wash up on sidewalks. Somewhere in the distance, a roof collapses with a spectacular splash.
“Er, yes,” the April Fool says. “That is. What I meant to say was, I’m dreadfully sorry.” He heaves a sigh. “I suppose it can’t all be fun and games, after all. It might not hurt to focus occasionally. Maybe I’ll get a head start on next year’s pranking.”
Redphoenix coughs meaningfully.
“I mean, get a head start on this year’s spring cleaning!” the April Fool says. “Nothing to fear, I’ll have Habit City in spit-shape soon. Luckily nobody is better than I at dual-wielding mops.”
Encouraged, the marching band starts up.
It isn’t long before all is back to normal in Habit City. Plus, now that the Be-Wilder has evaporated, the magical bees of Mistiflying bustle back to work, and soon the flowers are blooming and the city is floating once more.
As Habiticans cuddle the magical fuzzy bees, the April Fool’s eyes light up. “Oho, I’ve had a thought! Why don’t you all keep some of these fuzzy Bee Pets and Mounts? It’s a gift that perfectly symbolizes the balance between hard work and sweet rewards, if I’m going to get all boring and allegorical on you.” He winks. “Besides, they don’t have stingers! Fool’s honor.”",
- "questBewilderCompletionChat": "`The Be-Wilder is DEFEATED!`\n\nWe've done it! The Be-Wilder lets out a ululating cry as it twists in the air, shedding feathers like falling rain. Slowly, gradually, it coils into a cloud of sparkling mist. As the newly-revealed sun pierces the fog, it burns away, revealing the coughing, mercifully human forms of Bailey, Matt, Alex.... and the April Fool himself.\n\n`Mistiflying is saved!`\n\nThe April Fool has enough shame to look a bit sheepish. “Oh, hm,” he says. “Perhaps I got a little…. carried away.”\n\nThe crowd mutters. Sodden flowers wash up on sidewalks. Somewhere in the distance, a roof collapses with a spectacular splash.\n\n“Er, yes,” the April Fool says. “That is. What I meant to say was, I’m dreadfully sorry.” He heaves a sigh. “I suppose it can’t all be fun and games, after all. It might not hurt to focus occasionally. Maybe I’ll get a head start on next year’s pranking.”\n\nRedphoenix coughs meaningfully.\n\n“I mean, get a head start on this year’s spring cleaning!” the April Fool says. “Nothing to fear, I’ll have Habit City in spit-shape soon. Luckily nobody is better than I at dual-wielding mops.”\n\nEncouraged, the marching band starts up.\n\nIt isn’t long before all is back to normal in Habit City. Plus, now that the Be-Wilder has evaporated, the magical bees of Mistiflying bustle back to work, and soon the flowers are blooming and the city is floating once more.\n\nAs Habiticans cuddle the magical fuzzy bees, the April Fool’s eyes light up. “Oho, I’ve had a thought! Why don’t you all keep some of these fuzzy Bee Pets and Mounts? It’s a gift that perfectly symbolizes the balance between hard work and sweet rewards, if I’m going to get all boring and allegorical on you.” He winks. “Besides, they don’t have stingers! Fool’s honor.”",
- "questBewilderBossRageTitle": "Beguilement Strike",
- "questBewilderBossRageDescription": "When this gauge fills, The Be-Wilder will unleash its Beguilement Strike on Habitica!",
- "questBewilderDropBumblebeePet": "Bumblebee (Pet)",
- "questBewilderDropBumblebeeMount": "Bumblebee (Mount)",
- "questBewilderBossRageMarket": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nOh no! Despite our best efforts, we've gotten distracted by the Be-Wilder’s charming illusions and have forgotten to do some of our Dailies! With a cackling cry, the shining bird beats its wings, raising a swarm of mist around Alex the Merchant. When the fog clears, he has been possessed! “Have some free samples!” he shouts gleefully, and begins to hurl exploding eggs and potions at fleeing Habiticans. Not the most favorable of sales, to be sure.\n\nHurry! Let's stay focused on our Dailies to defeat this monster before it possesses someone else.",
- "questBewilderBossRageStables": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nAhh!!! Once again the Be-Wilder has dazzled us into neglecting our Dailies, and now it has attacked Matt the Beast Master! With a swirl of mist, Matt transforms into a terrifying winged creature, and all the pets and mounts howl sadly in their stables. Quickly, stay focused on your tasks to defeat this dastardly distraction!",
- "questBewilderBossRageBailey": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nLook out! In the middle of reporting the news, Bailey the Town Crier has been possessed by the Be-Wilder! She lets out an evil, uninformative screech as she rises into the air. Now how will we know what’s going on?\n\nDon't give up... we're so close to defeating this bothersome bird for once and for all!"
+ "questBewilderText": "L'Être Déchaîné",
+ "questBewilderNotes": "La fête commence comme d'habitude.
Les amuse-bouches sont excellents, la musique, entraînante, même les éléphants dansants font partie de la routine. Les Habiticien·ne·s rient et s'amusent entre les centres de table regorgeant de fleurs, heureux d'être distraits de leurs tâches les plus ingrates. Le Fou d'avril tournoyant parmi la foule, conte une blague marrante par-ci et joue un mauvais tour par-là.
Alors que l'horloge de Mistivolant sonne minuit, le Fou d'avril saute sur la scène pour donner un discours:
« Ami·e·s ! Ennemi·e·s ! Connaissances tolérantes ! Prêtez-moi vos oreilles ! » Les spectateurs gloussent alors que des oreilles d'animaux sortent de leur tête, puis ils prennent la pose, portant fièrement leur nouvel accessoire.
« Comme vous le savez tous, continue le Fou, mes illusions confuses ne durent normalement qu'une journée. Mais je suis heureux de vous annoncer que j'ai découvert un moyen de nous amuser perpétuellement, sans avoir à nous soucier du sale poids de nos responsabilités. Charmant·e·s Habiticien·ne·s, je vous présente mon nouvel ami magique... l'Être Déchaîné ! »
Lemoness pâlit soudainement, échappant ses hors-d'oeuvres. « Attendez ! Ne faites pas confiance... »
Tout à coup, une épaisse brume scintillante envahit la pièce et tourbillonne autour du Fou d'avril, l'enveloppant pour se transformer en plumes embrumées et en long cou. La foule est bouche bée devant ce monstrueux oiseau déployant ses ailes miroitantes d'illusions. Il pousse un horrible rire strident.
« Oh, il y a des siècles qu'un Habiticien avait été assez fou pour m'invoquer ! Comme c'est merveilleux d'avoir une forme physique à nouveau. »
Bourdonnant de terreur, les abeilles féeriques de Mistivolant s'enfuient hors de la ville flottante, qui s'affaisse dans le ciel. Une à une, les jolies fleurs de printemps se fanent et disparaissent.
« Mes très chers ami·e·s, pourquoi êtes-vous si affolé·e·s ?, s'écrit l'Être Déchaîné en battant des ailes. Vous n'avez plus à travailler fort pour obtenir des récompenses; je vous donnerai tout ce que vous désirez ! »
Des pièces d'or se mettent à pleuvoir du ciel, atteignant le sol avec une force brutale. La foule cherche refuge en hurlant. « C'est une blague ? », s'écrie Baconsaur, au moment même où des pièces fracassent les fenêtres et détruisent les bardeaux des toits.
PainterProphet se baisse pour éviter les éclairs qui tonnent là-haut. La brume voile le soleil. « Non ! Cette fois, je ne crois pas que c'en est une ! »
Vite, Habiticien·ne·s, ne laissons pas ce Boss Mondial nous distraire de nos objectifs ! Restons concentré·e·s sur les tâches que nous devons accomplir pour sauver Mistivolant et, espérons-le... nous-mêmes.",
+ "questBewilderCompletion": "L'Être Déchaîné est VAINCU !
Nous avons réussi ! L'Être Déchaîné lâche un hululement de désespoir en tournoyant dans les airs, ses plumes tombant comme de la pluie. Peu à peu, il se transforme en nuage de brume étincelante, qui, une fois percée par le soleil réapparu, se dissipe pour laisser place aux formes humaines, toussotantes et pardonantes, de Bailey, de Matt, d'Alex... et du Fou d'avril lui-même.
Mistivolant est sauvée !
Le Fou d'avril est si honteux qu'il a l'air penaud. «Oh, hum, dit-il. Je me suis peut-être laissé... emporter ! »
La foule marmonne. Des fleurs détrempées s'échouent sur le trottoir. Au loin, un toit s'écroule en un spectaculaire splash.
« Heu... oui, dit le Fou. C'est que... je voulais plutôt dire que je suis terriblement désolé. » Il soupire. « Je suppose qu'on ne peut pas toujours jouer et avoir du plaisir après tout. Ça ne fait pas de mal de travailler quelques fois. Je pense prendre une longueur d'avance sur mes tours de l'année prochaine. »
Redphoenix s'éclaircit la voix méchamment.
« Je veux dire... prendre une longueur d'avance sur ce ménage du printemps, se reprend le Fou d'avril. N'ayez rien à craindre : je remettrai Habitudiville en état en un rien de temps. Heureusement, personne n'égale mes talents de nettoyage à deux vadrouilles. »
Encouragée, la foule s'y met.
En peu de temps, Habitudiville revient à son état normal. Maintenant que l'Être Déchaîné s'est évaporé, les abeilles féeriques de Mistivolant se remettent au travail, et en un rien de temps, les fleurs éclosent et la ville flotte de nouveau.
Alors que les Habiticien·ne·s câlinent les abeilles féeriques touffues, les yeux du Fou d'avril s'illuminent : « Oh! j'ai une idée, s'exclame-t-il. Pourquoi ne garderiez-vous pas tous ces Abeilles touffues comme Familier et Monture? C'est un cadeau parfait pour symboliser l'équilibre entre dur labeur et récompense méritée, si je vous ennuie avec une allégorie. » Il vous fait un clin d'oeil. « En plus, elles ne piquent pas. Parole de Fou ! »",
+ "questBewilderCompletionChat": "`L'Être Déchaîné est VAINCU !`\n\nNous avons réussi ! L'Être Déchaîné lâche un hululement de désespoir en tournoyant dans les airs, ses plumes tombant comme de la pluie. Peu à peu, il se transforme en nuage de brume étincelante, qui, une fois percée par le soleil réapparu, se dissipe pour laisser place aux formes humaines, toussotantes et pardonnantes, de Bailey, de Matt, d'Alex... et du Fou d'avril lui-même.\n\n`Mistivolant est sauvée !`\n\nLe Fou d'avril est si honteux qu'il a l'air penaud. «Oh, hum, dit-il. Je me suis peut-être laissé... emporter ! »\n\nLa foule marmonne. Des fleurs détrempées atterrissent sur le trottoir. Au loin, un toit s'écroule en un spectaculaire splash.\n\n« Heu... oui, dit le Fou. C'est que... je voulais plutôt dire que je suis terriblement désolé. » Il soupire. « Je suppose qu'on ne peut pas toujours jouer et avoir du plaisir après tout. Ça ne fait pas de mal de travailler quelques fois. Je crois que je vais prendre une longueur d'avance sur mes tours de l'année prochaine... peut-être. »\n\nRedphoenix tousse méchamment.\n\n« Je veux dire... prendre une longueur d'avance sur ce ménage du printemps, se reprend le Fou d'avril. N'ayez rien à craindre: je remettrai Habitudiville en état en un rien de temps. Heureusement, personne n'égale mes talents de nettoyage à deux vadrouilles. »\n\nEncouragée, la foule s'y met.\n\nEn peu de temps, Habitudiville revient à son état normal. Maintenant que l'Être Déchaîné s'est évaporé, les abeilles féeriques de Mistivolant se remettent au travail, et en un rien de temps, les fleurs éclosent et la ville flotte de nouveau.\n\nAlors que les Habiticien·ne·s câlinent les abeilles féeriques touffues, les yeux du Fou d'avril s'illuminent : « Oh! j'ai une idée, s'exclame-t-il. Pourquoi ne garderiez-vous pas tous ces Abeilles touffues comme Familier et Monture? C'est un cadeau parfait pour symboliser l'équilibre entre dur labeur et récompense méritée, si je vous ennuie avec une allégorie. » Il vous fait un clin d'oeil. « En plus, elles ne piquent pas. Parole de Fou ! »",
+ "questBewilderBossRageTitle": "Frappe séductrice",
+ "questBewilderBossRageDescription": "Lorsque la jauge sera remplie, l'Être Déchaîné libèrera sa Frappe Séductrice sur Habitica !",
+ "questBewilderDropBumblebeePet": "Abeille féerique (familier)",
+ "questBewilderDropBumblebeeMount": "Abeille féerique (monture)",
+ "questBewilderBossRageMarket": "`L'Être Déchaîné libère sa FRAPPE SÉDUCTRICE !`\n\nOh non ! Malgré tous nos efforts, nous avons été distraits par les illusions charmantes de l'Être Déchaîné et nous avons oublié quelques Quotidiennes ! En poussant un cri moqueur, l'oiseau scintillant bat ses ailes pour lever un essaim de brume autour d'Alex le Marchand. Une fois la brume dispersée, il est possédé ! « Échantillons gratuits ! », crie-t-il joyeusement. Il se met à lancer des oeufs et des potions explosives aux Habiticien·ne·s en fuite. Pas la meilleure manière de vendre, ça c'est certain.\n\nDépêchez-vous ! Restons concentré·e·s sur nos Quotidiennes pour combattre ce monstre avant qu'il ne possède quelqu'un d'autre.",
+ "questBewilderBossRageStables": "`L'Être Déchaîné libère sa FRAPPE SÉDUCTRICE !`\n\nAhh !!! Une fois encore, l'Être Déchaîné nous a convaincu de négliger nos Quotidiennes et s'est maintenant attaqué à Matt le Maître des Bêtes ! D'un tourbillon de brume, Matt se transforme en une terrible créature ailée alors que tous les familiers et montures pleurent dans l'écurie. Vite ! Restez concentré·e·s sur vos tâches pour combattre cette ignoble distraction.",
+ "questBewilderBossRageBailey": "`L'Être Déchaîné libère sa FRAPPE SÉDUCTRICE !`\n\nRegardez ! Au beau milieu de ses nouvelles, Bailey la Crieuse Publique s'est fait posséder par l'Être Déchaîné ! Elle lâche un discours machiavélique et dépourvu d'informations, tout en s'élevant dans les airs. Comment allons-nous faire maintenant pour savoir ce qui se passe ?\n\nNe vous découragez pas... nous sommes sur le point de combattre cet oiseau distrayant une bonne fois pour toute !",
+ "questFalconText": "Les Oiseaux de la Proiecrastination",
+ "questFalconNotes": "Le mont Habitica est obscurci par une montagne menaçante de tâches à faire. C'était autrefois un lieu où pique-niquer et apprécier le sentiment de réussite, jusqu'à ce que les tâches négligées deviennent hors de contrôle. Aujourd'hui, il est habité par les redoutables Oiseaux de la Proiecrastination, des créatures ignobles qui empêchent les Habiticien·nes d'accomplir leurs tâches !
\"C'est trop dur ! \" croassent-ils à @JonArinbjorn and @Onheiron. \"Cela va prendre trop de temps à faire tout de suite ! Cela ne fera aucune différence si vous attendez jusqu'à demain ! Pourquoi ne pas faire quelque chose d'amusant à la place ?\"
Plus jamais, jurez vous. Vous escaladerez votre montagne personnelle de tâches à faire et vaincrez les Oiseaux de la Proiecrastination !",
+ "questFalconCompletion": "Ayant enfin triomphé des Oiseaux de la Proiecrastination, vous vous installez afin d'apprécier la vue et votre repos bien mérité.
\"Waouh ! \" dit @Trogdorina. \"Vous avez gagné !\"
ajoute @Squish. \"Tenez, prenez ces œufs que j'ai trouvés en récompense.\"",
+ "questFalconBoss": "Les Oiseaux de la Proiecrastination",
+ "questFalconDropFalconEgg": "Faucon (œuf)",
+ "questFalconUnlockText": "Déverrouille l'achat d’œufs de faucon au Marché",
+ "questTreelingText": "L'Arbre tortueux",
+ "questTreelingNotes": "C'est la Compétition annuelle de jardinage et tout le monde parle du mystérieux projet qu'@aurakami a promis de révélé. Vous vous joignez à la foule le jour de la grande annonce et vous émerveillez devant l'arbre animé. @fuzzytrees explique que cet arbre aidera à la maintenance des jardins, en montrant qu'il peut tondre la pelouse, couper la haie et tailler les rosiers en même temps... jusqu'à ce l'arbre devienne sauvage et retourne son sécateur contre son créateur ! La foule est en panique. Alors que tous essaient de s'enfuir, vous vous avancez d'un bond, sans peur et prêt au combat.",
+ "questTreelingCompletion": "Vous vous dépoussiérez alors que les toutes dernières feuilles tombent au sol. Malgré les mécontents, la Compétition de jardinage est maintenant hors de danger – quoique l'arbre que vous venez de réduire en un tas de copeaux ne gagnera pas le moindre prix ! « Il reste quelques petits défauts à travailler, dit @PainterProphet. Quelqu'un saura peut-être mieux entraîner ces arbrisseaux. Voulez-vous vous lancer ? »",
+ "questTreelingBoss": "Arbre tortueux",
+ "questTreelingDropTreelingEgg": "Arbrisseau (œuf)",
+ "questTreelingUnlockText": "Déverrouille l'achat d’œufs d'arbrisseau au Marché"
}
\ No newline at end of file
diff --git a/common/locales/fr/rebirth.json b/common/locales/fr/rebirth.json
index b3a847de58..f4a50c2221 100644
--- a/common/locales/fr/rebirth.json
+++ b/common/locales/fr/rebirth.json
@@ -5,7 +5,7 @@
"rebirthStartOver": "Renaissance fait recommencer votre personnage au Niveau 1.",
"rebirthAdvList1": "Vous repartez avec une Santé complète.",
"rebirthAdvList2": "Vous n'avez ni Expérience, ni Or, ni Équipement (à l'exception d'objets gratuits comme les objets Mystère).",
- "rebirthAdvList3": "Vos Habitudes, Quotidiennes et Tâches sont remises au jaune et les combos sont remis à zéro.",
+ "rebirthAdvList3": "Vos Habitudes, Quotidiennes et Tâches À Faire sont remises au jaune et les combos sont remis à zéro, à l'exception des tâches de Défis.",
"rebirthAdvList4": "Vous avez la classe de départ de Guerrier jusqu'à ce que vous obteniez une nouvelle classe.",
"rebirthInherit": "Votre nouveau personnage a hérité de quelques petites choses de son prédécesseur :",
"rebirthInList1": "Les tâches, l'historique et les réglages sont conservés.",
@@ -24,5 +24,6 @@
"rebirthPop": "Commencez un nouveau personnage au Niveau 1 en conservant les succès, les objets de collection et les tâches avec historique.",
"rebirthName": "Orbe de Renaissance",
"reborn": "Né de nouveau, niveau maximum <%= reLevel %>",
- "confirmReborn": "Êtes-vous sûr·e ?"
+ "confirmReborn": "Êtes-vous sûr·e ?",
+ "rebirthComplete": "Vous avez été ressuscité !"
}
\ No newline at end of file
diff --git a/common/locales/fr/settings.json b/common/locales/fr/settings.json
index bc3442435e..2b15a37a27 100644
--- a/common/locales/fr/settings.json
+++ b/common/locales/fr/settings.json
@@ -47,6 +47,7 @@
"customDayStart": "Heure personnalisée de début de journée",
"changeCustomDayStart": "Changer l'Heure personnalisée de début de journée ?",
"sureChangeCustomDayStart": "Êtes-vous sûr•e de vouloir changer votre heure personnalisée de début de journée ?",
+ "customDayStartHasChanged": "L'heure personnalisée du début de votre journée a changé.",
"nextCron": "Vos tâches Quotidiennes seront réinitialisées la prochaine fois que vous utiliserez Habitica après <%= time %>. Faites attention à compléter vos tâches Quotidiennes avant cette heure !",
"customDayStartInfo1": "Par défaut, Habitica vérifie et réinitialise vos tâches Quotidiennes à minuit dans votre fuseau horaire. Vous pouvez personnaliser cette heure ici.",
"misc": "Divers",
@@ -61,12 +62,23 @@
"newUsername": "Nouveau nom d'utilisateur",
"dangerZone": "Zone de Danger",
"resetText1": "ATTENTION ! Cette action va réinitialiser une grand partie de votre compte. Ceci est fortement déconseillé, mais certaines personnes y trouvent une utilité dans les premiers temps, après une courte utilisation de l'application.",
- "resetText2": "Vous perdrez tous vos niveaux, or et points d'expérience. Toutes vos tâches seront supprimées de façon permanente et vous perdrez tout l'historique associé aux tâches. Vous perdrez tout votre équipement mais il vous sera possible de l'acheter à nouveau, y compris les équipements en Edition Limitée et les Equipements Mystères d'abonné (vous devrez cependant être de la classe correspondante pour racheter les équipements de classe). Vous conserverez votre classe actuelle, ainsi que vos familiers et montures. Vous préférerez peut-être utiliser un Orbe de Renaissance à la place, une option bien plus sûre qui vous permettra de conserver toutes vos tâches.",
+ "resetText2": "Vous perdrez tous vos niveaux, or et points d'expérience. Toutes vos tâches, à l'exception des tâches de Défis, seront supprimées de façon permanente et vous perdrez tout l'historique associé aux tâches. Vous perdrez tout votre équipement, mais il vous sera possible de l'acheter à nouveau, y compris les équipements en Édition Limitée et les Équipements Mystères d'abonné. Vous devrez cependant être de la classe correspondante pour racheter les équipements de classe. Vous conserverez votre classe actuelle, ainsi que vos familiers et montures. Vous préférerez peut-être utiliser un Orbe de Renaissance à la place, une option bien plus sûre qui vous permettra de conserver toutes vos tâches.",
"deleteText": "Êtes-vous sûr•e ? Cela va supprimer votre compte Habitica définitivement et il ne pourra pas être restauré ! Vous serez obligé de créer un nouveau compte pour ré-utiliser Habitica. Les Gemmes sur votre compte ou celles dépensées ne seront pas remboursées. Si vous êtes absolument certain, tapez <%= deleteWord %> dans le champ de texte ci-dessous.",
"API": "API",
+ "APIv3": "API v3",
+ "APIv2": "API v2 - Obsolète",
"APIText": "Copiez ceci pour un usage dans des applications tierces. Considérez toutefois votre Jeton d'API comme l'équivalent d'un mot de passe, et ne le partagez pas publiquement. Votre ID d'utilisateur peut occasionnellement vous être demandé, mais ne publiez jamais votre Jeton d'API là où d'autres peuvent le voir, y compris sur Github.",
"APIToken": "Jeton d'API (ceci est un mot de passe - voir l'avertissement ci-dessus !)",
+ "thirdPartyApps": "Applications tierces",
+ "dataToolDesc": "Une page web qui vous montre des informations sur votre compte Habitica, comme des statistiques au sujet de vos tâches, de votre équipement et vos compétences.",
+ "beeminder": "Beeminder",
+ "beeminderDesc": "Laissez Beeminder contrôler automatiquement vos tâches à faire provenant d'Habitica. Vous pouvez vous engager à effectuer un certain nombre de tâches à faire par jour ou par semaine, ou vous pouvez vous engager à diminuer progressivement le nombre de tâches à faire que vous n'avez pas encore effectuées. (Par \"s'engager\" Beeminder vous menace d'avoir à payer de l'argent pour de vrai ! Mais vous pouvez aussi simplement apprécier les graphiques sophistiqués de Beeminder.)",
+ "chromeChatExtension": "L'extension Habitica Chat Client pour Chrome",
+ "chromeChatExtensionDesc": "L'extension Habitica Chat Client pour Chrome ajoute une fenêtre de discussion intuitive à l'ensemble du site habitica.com. Elle permet aux utilisateurs de discuter dans la Taverne, avec leur équipe et dans les guildes dont ils sont membres.",
+ "otherExtensions": "Autres extensions",
+ "otherDesc": "Trouvez d'autres applications, extensions et outils sur le wiki d'Habitica.",
"resetDo": "Allez-y, réinitialisez mon compte !",
+ "resetComplete": "Réinitialisation terminée !",
"fixValues": "Régler les Valeurs",
"fixValuesText1": "Si vous avez rencontré un bug ou avez fait une erreur qui a modifié de manière injuste votre personnage (dégâts que n'auriez pas du prendre, Or que vous n'auriez pas du vraiment gagner, etc.), vous pouvez modifier manuellement vos valeurs ici. Oui, ceci permet de tricher : utilisez cette fonctionnalité avec sagesse ou vous saboterez vos propres bonnes habitudes !",
"fixValuesText2": "Notez que vous ne pouvez pas restaurer les combos sur des tâches individuelles ici. Pour faire cela, modifiez la Quotidienne et allez dans les Options Avancées où vous trouverez un champ \"Restaurer les Combos\".",
@@ -96,6 +108,7 @@
"emailNotifications": "Notifications par Mail",
"wonChallenge": "Vous avez gagné un Défi !",
"newPM": "Message Privé Reçu",
+ "sentGems": "Gemmes envoyées !",
"giftedGems": "Gemmes Offertes",
"giftedGemsInfo": "<%= amount %> Gemmes - de <%= name %>",
"giftedSubscription": "Abonnement Offert",
@@ -135,6 +148,11 @@
"webhooks": "Webhooks",
"enabled": "Activé",
"webhookURL": "URL du webhook",
+ "invalidUrl": "URL invalide",
+ "invalidEnabled": "le paramètre \"enabled' doit être booléen",
+ "regIdRequired": "RegId est requis",
+ "pushDeviceAdded": "Appareil push ajouté avec succès",
+ "pushDeviceAlreadyAdded": "L'utilisateur a déjà l'appareil push",
"add": "Ajouter",
"buyGemsGoldCap": "Limite augmentée à <%= amount %>",
"mysticHourglass": "<%= amount %> Sablier Mystique ",
@@ -149,5 +167,5 @@
"amazonPayments": "Paiments Amazon",
"timezone": "Fuseau horaire",
"timezoneUTC": "Habitica utilise le fuseau horaire réglé sur votre PC, qui est le suivant : <%= utc %>",
- "timezoneInfo": "Si ce n'est pas le bon fuseau horaire, commencez par actualiser la page en utilisant le bouton Actualiser de votre navigateur pour vous assurer qu'Habitica est à jour. Si ce n'est toujours pas le bon, réglez le fuseau horaire sur votre PC et actualisez cette page à nouveau.
Le fuseau horaire doit être le même sur tous les autres PC ou applications mobiles que vous utilisez. Si vos Quotidiennes continuent d'être réinitialisées à la mauvaise heure, répétez cette vérification sur tous les PC que vous utilisez et sur le navigateur de vos appareils mobiles."
+ "timezoneInfo": "Si ce n'est pas le bon fuseau horaire, commencez par actualiser la page en utilisant le bouton Actualiser de votre navigateur pour vous assurer qu'Habitica est à jour. Si ce n'est toujours pas le bon, réglez le fuseau horaire sur votre PC et actualisez cette page à nouveau.
Si vous utilisez Habitica sur d'autres PCs ou appareils mobiles, le fuseau horaire doit être le même sur tous. Si vos Quotidiennes continuent d'être réinitialisées à la mauvaise heure, répétez cette vérification sur tous les PCs que vous utilisez et sur le navigateur de vos appareils mobiles."
}
\ No newline at end of file
diff --git a/common/locales/fr/spells.json b/common/locales/fr/spells.json
index 991abb8b12..757c86f89a 100644
--- a/common/locales/fr/spells.json
+++ b/common/locales/fr/spells.json
@@ -35,8 +35,8 @@
"spellSpecialSnowballAuraNotes": "Jetez une boule de neige à un membre de votre équipe ! Qu'est-ce qui pourrait mal tourner ? L'effet dure jusqu'à la fin du jour pour ce membre.",
"spellSpecialSaltText": "Sel",
"spellSpecialSaltNotes": "Quelqu'un vous a envoyé une boule de neige. Ha, ha, ha, très drôle. Enlevez-moi cette neige de là, maintenant !",
- "spellSpecialSpookDustText": "Poudre Poltergeist",
- "spellSpecialSpookDustNotes": "Change un ami en drap flottant avec des yeux !",
+ "spellSpecialSpookySparklesText": "Etincelles effrayantes",
+ "spellSpecialSpookySparklesNotes": "Changez un·e ami·e en couverture flottante avec des yeux !",
"spellSpecialOpaquePotionText": "Potion Opaque",
"spellSpecialOpaquePotionNotes": "Annule les effets de la Poudre Poltergeist.",
"spellSpecialShinySeedText": "Graine Brillante",
@@ -46,5 +46,11 @@
"spellSpecialSeafoamText": "Écume",
"spellSpecialSeafoamNotes": "Transformez un ami en une créature marine !",
"spellSpecialSandText": "Sable",
- "spellSpecialSandNotes": "Annule les effets de l'Écume."
+ "spellSpecialSandNotes": "Annule les effets de l'Écume.",
+ "spellNotFound": "Compétence \"<%= spellId %>\" introuvable.",
+ "partyNotFound": "Équipe non trouvée.",
+ "targetIdUUID": "\"targetId\" doit être un nom d'utilisateur valide.",
+ "challengeTasksNoCast": "Utiliser une compétence sur des tâches de défis est interdit.",
+ "spellNotOwned": "Vous ne possédez pas cette compétence.",
+ "spellLevelTooHigh": "Vous devez être au niveau <%= level %> pour utiliser cette compétence."
}
\ No newline at end of file
diff --git a/common/locales/fr/subscriber.json b/common/locales/fr/subscriber.json
index 2744519cc3..5109a67816 100644
--- a/common/locales/fr/subscriber.json
+++ b/common/locales/fr/subscriber.json
@@ -4,14 +4,16 @@
"subDescription": "Utilisez l'or pour acheter des gemmes, gagnez des objets-mystères chaque mois, conservez l'historique de vos progrès, doublez le butin quotidien, soutenez les développeurs. Cliquez pour plus d'informations.",
"buyGemsGold": "Acheter des Gemmes avec de l'Or",
"buyGemsGoldText": "Alexander le Marchand vous vendra les gemmes pour <%= gemCost%> or par gemme. Ses envois mensuels sont initialement plafonnés à <%= gemLimit%> gemmes par mois, mais cette limite augmente de 5 gemmes tous les trois mois d'abonnement consécutifs, jusqu'à un maximum de 50 gemmes par mois !",
+ "mustSubscribeToPurchaseGems": "Vous devez être abonné pour acheter des gemmes avec de l'or.",
+ "reachedGoldToGemCap": "Vous avez atteint la limite de conversion Or => Gemme, <%= convCap %> pour ce mois. Elle existe pour limiter l'abus / le farming. La limite sera remise à zéro dans les 3 premiers jours du mois suivant.",
"retainHistory": "Conservation de plus d'entrées de l'historique",
"retainHistoryText": "Rend les Tâches à Faire effectuées et l'historique des tâches disponibles plus longtemps.",
"doubleDrops": "Limite journalière de butin doublée",
"doubleDropsText": "Complétez votre écurie encore plus vite !",
"mysteryItem": "Des objets uniques tous les mois",
- "mysteryItemText": "Chaque mois, vous recevrez un objet cosmétique unique pour votre avatar ! En plus, pour chaque période de trois mois consécutifs d'inscription, les Mystérieux Voyageurs Temporels vous donneront accès aux objets cosmétiques historiques (et futurs !)",
+ "mysteryItemText": "Chaque mois, vous recevrez un objet cosmétique unique pour votre avatar ! En plus, pour chaque période de trois mois consécutifs d'abonnement, les Mystérieux Voyageurs Temporels vous donneront accès aux objets cosmétiques historiques (et futurs !)",
"supportDevs": "Soutenez les développeurs",
- "supportDevsText": "Votre inscription aide à garder Habitica prospère et aide à financer le développement de nouvelles fonctionnalités. Merci de vie générosité !",
+ "supportDevsText": "Votre abonnement aide à garder Habitica prospère et aide à financer le développement de nouvelles fonctionnalités. Merci de vie générosité !",
"monthUSD": "USD / Mois",
"organization": "Organisation",
"groupPlans": "Offres d'Entreprise",
@@ -29,6 +31,7 @@
"manageSub": "Cliquez ici pour gérer votre abonnement",
"cancelSub": "Annuler l'abonnement",
"canceledSubscription": "Abonnement annulé",
+ "cancelingSubscription": "Annulation de l'abonnement",
"adminSub": "Abonnements Administrateur",
"morePlans": "Plus d'offres À venir",
"organizationSub": "Organisation Privée",
@@ -64,15 +67,18 @@
"buyGemsAllow1": "Vous pouvez acheter",
"buyGemsAllow2": "plus de Gemmes ce mois-ci",
"purchaseGemsSeparately": "Acheter des Gemmes supplémentaires",
- "subFreeGemsHow": "Les joueurs d'Habitica peuvent obtenir des gemmes gratuitement, soit en remportant des défis récompensés par des gemmes, soit en tant que contributeur ayant aidé au développement de Habitica.",
+ "subFreeGemsHow": "Les joueurs d'Habitica peuvent obtenir des gemmes gratuitement, soit en remportant des défis récompensés par des gemmes, soit en tant que contributeur ayant aidé au développement de Habitica.",
"seeSubscriptionDetails": "Rendez-vous sur Paramètres > Abonnement pour visualiser les détails de votre abonnement !",
"timeTravelers": "Voyageurs Temporels",
"timeTravelersTitleNoSub": "<%= linkStartTyler %>Tyler<%= linkEnd %> et <%= linkStartVicky %>Vicky<%= linkEnd %>",
"timeTravelersTitle": "Mystérieux Voyageurs Temporels",
"timeTravelersPopoverNoSub": "Vous aurez besoin d'un Sablier Mystique pour invoquer les mystérieux Voyageurs Temporels ! <%= linkStart %>Les abonné·e·s<%= linkEnd %> reçoivent un Sablier Mystique par tranche de trois mois consécutifs d'abonnement. Revenez lorsque vous aurez un Sablier Mystique, et les Voyageurs Temporels vous fourniront un Familier ou une Monture rare, un Ensemble d'Équipement d'abonné·e du passé... ou peut-être même du futur.",
- "timeTravelersPopover": "Vous avez un Sablier Mystique! C'est une joie de voyager dans le temps pour vous! Veuillez choisir le familier, la monture ou Set d'Objets Mystère que vous désirez. Vous trouverez une liste d'objets passés ici! Si ceux-ci ne vous satisfont pas, peut-être seriez vous intéressés par l'un de nos convoités Set d'objets Steampunk?",
+ "timeTravelersPopover": "Vous avez un Sablier Mystique! C'est une joie de voyager dans le temps pour vous ! Veuillez choisir le familier, la monture ou Set d'Objets Mystère que vous désirez. Vous trouverez une liste d'objets passés ici! Si ceux-ci ne vous satisfont pas, peut-être seriez vous intéressés par l'un de nos convoités Set d'objets Steampunk ?",
"timeTravelersAlreadyOwned": "Félicitations ! Vous possédez déjà tout ce que les Voyageurs Temporels ont à offrir. Merci de soutenir le site !",
"mysticHourglassPopover": "Un Sablier Mystique vous permet d'acheter certains objets dont l'achat est limité dans le temps, comme les ensembles d'objets Mystère des mois précédents et des récompenses passées de Boss mondiaux.",
+ "mysterySetNotFound": "Ensemble mystère non trouvé, ou ensemble déjà possédé.",
+ "mysteryItemIsEmpty": "Les objets mystère sont vides",
+ "mysteryItemOpened": "Objet mystère ouvert.",
"mysterySet201402": "Ensemble du Messager Ailé",
"mysterySet201403": "Ensemble du Marcheur Sylvain",
"mysterySet201404": "Ensemble du Papillon Crépusculaire",
@@ -98,7 +104,9 @@
"mysterySet201512": "Ensemble de la Flamme Hivernale",
"mysterySet201601": "Ensemble du Champion de la Résolution",
"mysterySet201602": "Ensemble du Bourreau des Cœurs ",
- "mysterySet201603": "Ensemble Chanceux du Trèfle à Quatre Feuilles",
+ "mysterySet201603": "Ensemble du Trèfle à Quatre Feuilles",
+ "mysterySet201604": "Ensemble du Guerrier Feuille",
+ "mysterySet201605": "Marching Bard Set",
"mysterySet301404": "Ensemble Steampunk de Base",
"mysterySet301405": "Ensemble D'accessoire Steampunk",
"mysterySetwondercon": "Wondercon",
@@ -110,9 +118,25 @@
"hourglassBuyItemConfirm": "Acheter cet objet pour 1 Sablier Mystique ?",
"petsAlreadyOwned": "Familier déjà possédé.",
"mountsAlreadyOwned": "Monture déjà possédée.",
- "typeNotAllowedHourglass": "Type d'Objet non supporté pour un achat avec un Sablier Mystique. Types autorisés :",
+ "typeNotAllowedHourglass": "Type d'Objet non supporté pour un achat avec un Sablier Mystique. Types autorisés : <%= allowedTypes %>",
"petsNotAllowedHourglass": "Familier non disponible pour l'achat avec un Sablier Mystique.",
"mountsNotAllowedHourglass": "Monture non disponible pour l'achat avec un Sablier Mystique.",
"hourglassPurchase": "A acheté un objet avec un Sablier Mystique !",
- "hourglassPurchaseSet": "A acheté un ensemble d'objets avec un Sablier Mystique !"
+ "hourglassPurchaseSet": "A acheté un ensemble d'objets avec un Sablier Mystique !",
+ "missingUnsubscriptionCode": "Il manque le code de désabonnement.",
+ "missingSubscription": "L'utilisateur n'a pas de forfait d'abonnement",
+ "missingSubscriptionCode": "Il manque le code d'abonnement. Valeurs possibles : basic_earned, basic_3mo, basic_6mo, google_6mo, basic_12mo.",
+ "cannotDeleteActiveAccount": "Vous avez un abonnement actif, annulez le forfait avant de supprimer votre compte.",
+ "paymentNotSuccessful": "Le paiement n'a pas abouti",
+ "planNotActive": "Le plan n'a pas encore été activé (à cause d'un bug PayPal). Il commencera le <%= nextBillingDate %>, après quoi vous pourrez annuler pour en garder tous les bénéfices.",
+ "notAllowedHourglass": "Familier/monture non disponible pour l'achat avec un Sablier Mystique.",
+ "readCard": "<%= cardType %> a été lue",
+ "cardTypeRequired": "Type de carte requis",
+ "cardTypeNotAllowed": "Type de carte inconnu.",
+ "invalidCoupon": "Code du coupon invalide.",
+ "couponUsed": "Code du coupon déjà utilisé.",
+ "noSudoAccess": "Vous n'avez pas d'accès sudo.",
+ "couponCodeRequired": "Le code du coupon est requis.",
+ "eventRequired": "\"req.params.event\" est requis.",
+ "countRequired": "\"req.query.count\" est requis."
}
\ No newline at end of file
diff --git a/common/locales/fr/tasks.json b/common/locales/fr/tasks.json
index 1458576d6d..48230d7179 100644
--- a/common/locales/fr/tasks.json
+++ b/common/locales/fr/tasks.json
@@ -73,6 +73,7 @@
"clearTags": "Nettoyer",
"hideTags": "Masquer",
"showTags": "Afficher",
+ "toRequired": "Vous devez fournir une valeur to",
"startDate": "Date de début",
"startDateHelpTitle": "Quand cette tâche devrait-elle démarrer ?",
"startDateHelp": "Définissez la date à laquelle cette tâche prendra effet. Elle ne sera pas active avant cette date.",
@@ -88,8 +89,9 @@
"fortifyName": "Potion de Fortification",
"fortifyPop": "Fait revenir toutes les tâches à une valeur neutre (couleur jaune) et restaure tous les points de santé que vous aviez perdus.",
"fortify": "Fortification",
- "fortifyText": "La potion de fortification ramènera toutes vos tâches à un niveau neutre (jaune), comme si vous veniez de les ajouter, et remplira votre barre de santé. C'est utile si vos tâches rouges rendent le jeu trop dur, ou si vos tâches bleues le rendent trop facile. Si cela vous motive de retrouver des bases saines, dépensez vos gemmes et accordez vous un sursis !",
+ "fortifyText": "La potion de fortification ramènera toutes vos tâches, à l'exception des tâches de Défis, à un niveau neutre (jaune), comme si vous veniez de les ajouter, et remplira votre barre de santé. C'est utile si vos tâches rouges rendent le jeu trop dur, ou si vos tâches bleues le rendent trop facile. Si cela vous motive de retrouver des bases saines, dépensez vos gemmes et accordez-vous un sursis !",
"confirmFortify": "Êtes-vous sûr•e ?",
+ "fortifyComplete": "Fortification terminée !",
"sureDelete": "Êtes-vous sûr•e de vouloir effacer cette <%= taskType %> avec le texte \"<%= taskText %>\"?",
"streakCoins": "Bonus de combo !",
"pushTaskToTop": "Déplace la tâche en tête de liste. Maintenez ctrl ou cmd enfoncé pour la déplacer en fin de liste. ",
@@ -103,7 +105,7 @@
"dailyHelp2": "Si vous n'accomplissez pas vos tâches Quotidiennes, vous perdez de la Santé lors du Cron (votre passage d'un jour à l'autre).",
"dailyHelp3": "Les tâches Quotidiennes deviennent <%= emphasisStart %>plus rouges<%= emphasisEnd %> lorsque vous les ratez, et <%= emphasisStart %>plus bleues<%= emphasisEnd %> lorsque vous les accomplissez. Plus une tâche Quotidienne est rouge, plus elle vous récompensera ... ou vous infligera des dégâts.",
"dailyHelp4": "Pour changer votre Cron, c'est-à-dire le moment du passage d'un jour à l'autre, allez sur <%= linkStart %> Paramètres > Site<%= linkEnd %> > Heure personnalisée de début de journée.",
- "dailyHelp5": "Vous pouvez vous inspirer de ces exemples de Quotidiennes !",
+ "dailyHelp5": "Vous pouvez vous inspirer de ces exemples de Quotidiennes !",
"toDoHelp1": "Les tâches À Faire sont jaunes par défaut, et rougissent (en prenant de la valeur) au fil du temps que vous mettez à les accomplir.",
"toDoHelp2": "Les tâches À Faire ne vous blessent jamais ! Elles octroient seulement de l'Or et de l'Expérience.",
"toDoHelp3": "En fractionnant votre tâche À Faire en une liste faite de plus petits éléments, vous la rendrez moins effrayante et augmenterez vos points !",
@@ -112,5 +114,18 @@
"rewardHelp2": "L’équipement affecte vos statistiques (<%= linkStart %>Utilisateur > Caractéristiques<%= linkEnd %>).",
"rewardHelp3": "De l'équipement spécial apparaitra ici pendant les Évènements Mondiaux.",
"rewardHelp4": "Ne soyez pas effrayé de créer des récompenses personnalisées ! Regardez quelques exemples ici.",
- "clickForHelp": "Cliquez pour obtenir de l'aide."
+ "clickForHelp": "Cliquez pour obtenir de l'aide.",
+ "taskIdRequired": "\"taskId\" doit être un UUID valide.",
+ "taskNotFound": "Tâche non trouvée.",
+ "invalidTaskType": "Task type doit être l'un des suivants : \"habit\", \"daily\", \"todo\", \"reward\".",
+ "cantDeleteChallengeTasks": "Une tâche de Défi ne peut être supprimée.",
+ "checklistOnlyDailyTodo": "Les listes de vérification ne sont disponibles que sur les tâches Quotidiennes et À faire",
+ "checklistItemNotFound": "Aucune liste de vérification n'a été trouvée pour l'ID fourni.",
+ "itemIdRequired": "\"itemId\" doit être un UUID valide.",
+ "tagNotFound": "Aucune étiquette n'a été trouvée pour l'ID fourni.",
+ "tagIdRequired": "\"tagId\" doit être un UUID valide correspondant à une étiquette appartenant à l'utilisateur.",
+ "positionRequired": "\"position\" est requis et doit être un nombre.",
+ "cantMoveCompletedTodo": "Impossible de déplacer une tâche À Faire complétée.",
+ "directionUpDown": "\"direction\" est requis et doit être 'up' ou 'down'",
+ "alreadyTagged": "La tâche est déjà étiquetée avec l'étiquette utilisée."
}
\ No newline at end of file
diff --git a/common/locales/he/backgrounds.json b/common/locales/he/backgrounds.json
index 9119d1f48e..68ee1fe3da 100644
--- a/common/locales/he/backgrounds.json
+++ b/common/locales/he/backgrounds.json
@@ -154,11 +154,18 @@
"backgroundRainforestNotes": "צאו להרפתקאה ביער הגשם.",
"backgroundStoneCircleText": "מעגל אבנים",
"backgroundStoneCircleNotes": "הטילו כשפים במעגל אבנים.",
- "backgrounds042016": "SET 23: Released April 2016",
- "backgroundArcheryRangeText": "Archery Range",
- "backgroundArcheryRangeNotes": "Practice on the Archery Range.",
- "backgroundGiantFlowersText": "Giant Flowers",
- "backgroundGiantFlowersNotes": "Frolic atop Giant Flowers.",
- "backgroundRainbowsEndText": "End of the Rainbow",
- "backgroundRainbowsEndNotes": "Discover gold at the End of the Rainbow."
+ "backgrounds042016": "סט 23: פורסם באפריל 2016",
+ "backgroundArcheryRangeText": "מטווח קשתות",
+ "backgroundArcheryRangeNotes": "התאמנו במטווח הקשתות.",
+ "backgroundGiantFlowersText": "פרחי ענק",
+ "backgroundGiantFlowersNotes": "השתובבו על פרחי ענק.",
+ "backgroundRainbowsEndText": "קצה הקשת",
+ "backgroundRainbowsEndNotes": "גלו זהב בקצה הקשת.",
+ "backgrounds052016": "סט 24: פורסם במאי 2016",
+ "backgroundBeehiveText": "כוורת דבורים",
+ "backgroundBeehiveNotes": "זמזמ/י ורקוד/ריקדי בכוורת דבורים.",
+ "backgroundGazeboText": "גזיבו",
+ "backgroundGazeboNotes": "הילחמ/י נגד גזיבו.",
+ "backgroundTreeRootsText": "שורשי עץ",
+ "backgroundTreeRootsNotes": "סקור/סיקרי את שורשי העץ."
}
\ No newline at end of file
diff --git a/common/locales/he/challenge.json b/common/locales/he/challenge.json
index b235d929fc..f6f994b912 100644
--- a/common/locales/he/challenge.json
+++ b/common/locales/he/challenge.json
@@ -63,5 +63,21 @@
"congratulations": "ברכות!",
"hurray": "יש!!",
"noChallengeOwner": "אין בעלים",
- "noChallengeOwnerPopover": "אין לאתגר זה בעלים כיוון שמי שיצר את האתגר מחק את החשבון שלו."
+ "noChallengeOwnerPopover": "אין לאתגר זה בעלים כיוון שמי שיצר את האתגר מחק את החשבון שלו.",
+ "challengeMemberNotFound": "המשתמש לא נמצא בין חברי האתגר",
+ "onlyGroupLeaderChal": "רק מנהיג/ת הקבוצה יכול/ה ליצור אתגרים",
+ "tavChalsMinPrize": "הפרס חייב להיות לפחות אבן חן 1 עבור אתגרי הפונדק.",
+ "cantAfford": "הפרס הזה יקר מידי עבורך. יש לרכוש עוד אבני חן, או להנמיך את הכמות בפרס.",
+ "challengeIdRequired": "\"challengeId\" חייב להיות UUID - זהות משתמש ייחודי - תקף.",
+ "winnerIdRequired": "\"winnerId\" חייב להיות UUID - זהות משתמש ייחודי - תקף.",
+ "challengeNotFound": "האתגר לא נמצא.",
+ "onlyLeaderDeleteChal": "רק מנהיג/ת האתגר יכול/ה למחוק אותו.",
+ "onlyLeaderUpdateChal": "רק מנהיג/ת האתגר יכול/ה לעדכן אותו.",
+ "winnerNotFound": "מנצח/ת עם זהות משתמש \"<%= userId %>\" לא נמצא או לא חלק מהאתגר.",
+ "noCompletedTodosChallenge": "\"includeComepletedTodos\" is not supported when fetching challenge tasks.",
+ "userTasksNoChallengeId": "כאשר \"tasksOwner\" הוא/היא \"user\" \"challengeId\" איננו יכול לעבור.",
+ "onlyChalLeaderEditTasks": "מטלות השייכות לאתגר יכולות לעבור עריכה רק על ידי המנהיג/ה.",
+ "userAlreadyInChallenge": "המשתמש/ת כבר משתתפ/ת באתגר הזה.",
+ "cantOnlyUnlinkChalTask": "רק מטלות אתגרים שבורים יכולות להיות לא מקושרות.",
+ "shortNameTooShort": "Tag Name must have at least 3 characters."
}
\ No newline at end of file
diff --git a/common/locales/he/character.json b/common/locales/he/character.json
index 4549c4585b..68ff904398 100644
--- a/common/locales/he/character.json
+++ b/common/locales/he/character.json
@@ -1,4 +1,5 @@
{
+ "communityGuidelinesWarning": "שימו לב ששם התצוגה, תמונת הפרופיל, וה״חרטוט״, חייבים לעמוד בהנחיות הקהילה (למשל: איסור על גסויות, נושאים שהם למבוגרים-בלבד, העלבות, וכד׳). אם יש לכם ספק בנוגע לעמידה בכללים, הרגישו חופשיים לשלוח מייל לleslie@habitica.com!",
"statsAch": "תכונות והישגים",
"profile": "פרופיל",
"avatar": "התאם דמות",
@@ -34,7 +35,7 @@
"beard": "זקן",
"mustache": "שפם",
"flower": "פרח",
- "wheelchair": "Wheelchair",
+ "wheelchair": "כיסא גלגלים",
"basicSkins": "עורות בסיסיים",
"rainbowSkins": "עורות בצבעי הקשת",
"pastelSkins": "עורות בצבעי פסטל",
@@ -109,6 +110,7 @@
"mage": "מכשף",
"mystery": "מסתורין",
"changeClass": "שינוי מקצוע, החזרת נקודות תכונה",
+ "lvl10ChangeClass": "כדי לשנות מקצוע עליך להיות לפחות ברמה 10.",
"levelPopover": "כל דרגה מעניקה לך נקודה אחת להקצות לתכונה לפי בחירתך. ניתן לעשות זאת באופן ידני, או לתת למשחק להחליט בשבילך באמצעות אחת האופציות להקצאה אוטומטית.",
"unallocated": "נקודות תכונה שטרם הוקצו",
"haveUnallocated": "יש לך <%= points %> נקודות תכונה שטרם הקצית.",
@@ -164,5 +166,7 @@
"int": "תבונה",
"showQuickAllocation": "הצגת הקצאת תכונות",
"hideQuickAllocation": "הסתרת הקצאת תכונות",
- "quickAllocationLevelPopover": "כל דרגה מקנה לכם נקודה אחת לטובת תכונה כרצונכם. תוכלו לעשות זאת ידנית, או לתת למשחק להחליט בשבילכם באמצעות אופציית הקצאת הנקודות האוטומטית שניתן למצוא תחת משתמש -> תכונות דמות."
+ "quickAllocationLevelPopover": "כל דרגה מקנה לכם נקודה אחת לטובת תכונה כרצונכם. תוכלו לעשות זאת ידנית, או לתת למשחק להחליט בשבילכם באמצעות אופציית הקצאת הנקודות האוטומטית שניתן למצוא תחת משתמש -> תכונות דמות.",
+ "invalidAttribute": "\"<%= attr %>\" אינה תכונה תקפה.",
+ "notEnoughAttrPoints": "אין לך מספיק נקודות תכונה."
}
\ No newline at end of file
diff --git a/common/locales/he/content.json b/common/locales/he/content.json
index ae6e0da566..2170a68257 100644
--- a/common/locales/he/content.json
+++ b/common/locales/he/content.json
@@ -113,6 +113,12 @@
"questEggSnailText": "חילזון",
"questEggSnailMountText": "חילזון",
"questEggSnailAdjective": "איטי אבל יציב",
+ "questEggFalconText": "פאלקון",
+ "questEggFalconMountText": "פאלקון",
+ "questEggFalconAdjective": "מהיר",
+ "questEggTreelingText": "Treeling",
+ "questEggTreelingMountText": "Treeling",
+ "questEggTreelingAdjective": "a leafy",
"eggNotes": "מצא שיקוי הבקעה לשפוך על ביצה זו, והיא תהפוך ל<%= eggText(locale) %> <%= eggAdjective(locale) %>.",
"hatchingPotionBase": "רגיל",
"hatchingPotionWhite": "לבן",
@@ -126,6 +132,7 @@
"hatchingPotionGolden": "זהוב",
"hatchingPotionSpooky": "מפחיד",
"hatchingPotionPeppermint": "מנטה",
+ "hatchingPotionFloral": "Floral",
"hatchingPotionNotes": "מזוג שיקוי זה על ביצה, והיא תבקע כ: <%= potText(locale) %>.",
"premiumPotionAddlNotes": "לא ניתן לשימוש על ביצי הרפתקאות.",
"foodMeat": "בשר",
diff --git a/common/locales/he/contrib.json b/common/locales/he/contrib.json
index f6ea181fb3..3ce26f2995 100644
--- a/common/locales/he/contrib.json
+++ b/common/locales/he/contrib.json
@@ -35,8 +35,12 @@
"hallContributors": "היכל התורמים",
"hallPatrons": "היכל נותני החסות",
"rewardUser": "הענק פרס למשתמש",
- "UUID": "UUID",
+ "UUID": "מזהה משתמש",
"loadUser": "טען משתמש",
+ "noAdminAccess": "אין לכם הרשאות מנהל.",
+ "pageMustBeNumber": "req.query.page חייב להיות מספר",
+ "userNotFound": "משתמש לא נמצא.",
+ "invalidUUID": "UUID חייב להיות בתוקף",
"title": "כותרת",
"moreDetails": "פרטים נוספים (1-7)",
"moreDetails2": "פרטים נוספים (8-9)",
@@ -52,8 +56,8 @@
"visitHeroes": "בקר בהיכל הגיבורים (תורמים ונותני גיבוי)",
"conLearn": "למד עוד אודות פרסי תורמים",
"conLearnHow": "למד כיצד לתרום ל-Habitica",
- "surveysSingle": "עזר ל- Habitica לגדול ע״י מילוי שאלון. אין שאלונים פעילים.",
- "surveysMultiple": "עזר ל- Habitica לגדול ע״י מילוי <%= surveys %> שאלונים. אין שאלונים פעילים.",
+ "surveysSingle": "Helped Habitica grow, either by filling out a survey or helping with a major testing effort. Thank you!",
+ "surveysMultiple": "Helped Habitica grow on <%= surveys %> occasions, either by filling out a survey or helping with a major testing effort. Thank you!",
"currentSurvey": "שאלון נוכחי",
"surveyWhen": "התג ינתן כפרס לכל ממלאי השאלון בסוף מרץ, לאחר שעיבוד נתוני השאלונים יסתיים.",
"blurbInbox": "כאן נשמרות הודעותיכם הפרטיות! תוכלו לשלוח למישהו הודעה על ידי לחיצה על תמונת המעטפה ליד שמם בפונדק, בחבורה, או בשיחת גילדה. אם קיבלתם הודעה פרטית שהיא אינה ראוייה, עליכם לשלוח תמונת מסך שלה ללמונס (leslie@habitica.com)",
diff --git a/common/locales/he/death.json b/common/locales/he/death.json
index a833285292..82dbf1f482 100644
--- a/common/locales/he/death.json
+++ b/common/locales/he/death.json
@@ -12,5 +12,6 @@
"losingHealthQuickly": "מאבדים בריאות במהירות?",
"lowHealthTips3": "מטלות יומיות אשר לא הושלמו יפגעו בכם במשך הלילה, הזהרו שלא להתחיל עם יותר מידי!",
"lowHealthTips4": "אם משימה יומית לא בתוקף ביום מסויים, תוכלו להשביתה על ידי לחיצה על העפרון.",
- "goodLuck": "בהצלחה!"
+ "goodLuck": "בהצלחה!",
+ "cannotRevive": "לא ניתן להחיות מבלי שיהיו מתים"
}
\ No newline at end of file
diff --git a/common/locales/he/front.json b/common/locales/he/front.json
index a212e85da1..75097bb5cf 100644
--- a/common/locales/he/front.json
+++ b/common/locales/he/front.json
@@ -28,6 +28,7 @@
"communityReddit": "רדיט",
"companyAbout": "איך זה עובד",
"companyBlog": "בלוג",
+ "devBlog": "Developer Blog",
"companyDonate": "תרום",
"companyExtensions": "הרחבות",
"companyPrivacy": "פרטיות",
@@ -51,6 +52,7 @@
"featureSocialHeading": "משחק חברתי",
"featuredIn": "מוצגים נוספים",
"featuresHeading": "מאפיינים נוספים:",
+ "footerDevs": "Developers",
"footerCommunity": "קהילה",
"footerCompany": "חברה",
"footerMobile": "נייד",
@@ -182,6 +184,7 @@
"zelahQuote": "עם [האביטיקה], אפשר לשכנע אותי ללכת לישון בזמן באמצעות המחשבה של זכייה בנקודות על ללכת לישון מוקדם או הפסד בריאות על איחור!",
"reportAccountProblems": "דווח על בעיות בחשבון משתמש",
"reportCommunityIssues": "דווח על בעיות בקהילה",
+ "subscriptionPaymentIssues": "Subscription and Payment Issues",
"generalQuestionsSite": "שאלות כלליות בנוגע לאתר",
"businessInquiries": "שאלות ובירורים עסקיים",
"merchandiseInquiries": "שאלות ובירורים בנוגע לרכישות",
@@ -222,5 +225,32 @@
"altAttrWebstorm": "WebStorm",
"altAttrGithub": "גיטהאב",
"altAttrTrello": "Trello",
- "altAttrSlack": "Slack"
+ "altAttrSlack": "Slack",
+ "missingAuthHeaders": "Missing authentication headers.",
+ "missingAuthParams": "Missing authentication parameters.",
+ "missingUsernameEmail": "Missing username or email.",
+ "missingEmail": "Missing email.",
+ "missingUsername": "Missing username.",
+ "missingPassword": "Missing password.",
+ "missingNewPassword": "Missing new password.",
+ "wrongPassword": "Wrong password.",
+ "notAnEmail": "Invalid email address.",
+ "emailTaken": "Email address is already used in an account.",
+ "newEmailRequired": "Missing new email address.",
+ "usernameTaken": "Username already taken.",
+ "passwordConfirmationMatch": "Password confirmation doesn't match password.",
+ "invalidLoginCredentials": "Incorrect username and/or email and/or password.",
+ "passwordReset": "If we have your email on file, your password reset link has been sent to your email.",
+ "passwordResetEmailSubject": "Password Reset for Habitica",
+ "passwordResetEmailText": "Password for <%= username %> has been reset to <%= newPassword %> . Important! Both username and password are case-sensitive -- you must enter both exactly as shown here. We recommend copying and pasting both instead of typing them. Log in at <%= baseUrl %>. After you have logged in, head to <%= baseUrl %>/#/options/settings/settings and change your password.",
+ "passwordResetEmailHtml": "Password for <%= username %> has been reset to <%= newPassword %>.
Important! Both username and password are case-sensitive -- you must enter both exactly as shown here. We recommend copying and pasting both instead of typing them.
Log in at <%= baseUrl %>. After you have logged in, head to <%= baseUrl %>/#/options/settings/settings and change your password.",
+ "invalidLoginCredentialsLong": "Uh-oh - your username or password is incorrect.\n- Make sure your username or email is typed correctly.\n- You may have signed up with Facebook, not email. Double-check by trying Facebook login.\n- If you forgot your password, click \"Forgot Password\".",
+ "invalidCredentials": "There is no account that uses those credentials.",
+ "accountSuspended": "Account has been suspended, please contact leslie@habitica.com with your User ID \"<%= userId %>\" for assistance.",
+ "onlyFbSupported": "Only Facebook is supported currently.",
+ "cantDetachFb": "Account lacks another authentication method, can't detach Facebook.",
+ "onlySocialAttachLocal": "Local authentication can be added to only a social account.",
+ "invalidReqParams": "Invalid request parameters.",
+ "memberIdRequired": "\"member\" must be a valid UUID.",
+ "heroIdRequired": "\"heroId\" must be a valid UUID."
}
\ No newline at end of file
diff --git a/common/locales/he/gear.json b/common/locales/he/gear.json
index 2cdd0cf2d4..2c2e748282 100644
--- a/common/locales/he/gear.json
+++ b/common/locales/he/gear.json
@@ -187,8 +187,10 @@
"weaponArmoireJesterBatonNotes": "בהינף שרביטכם וכמה תשובות שנונות, גם המצבים הסבוכים ביותר נעשים ברורים. מגביר תבונה ותפיסה ב <%= attrs %> כל אחד. ארמואר קסום: סט לץ (חפץ 3 מתוך 3).",
"weaponArmoireMiningPickaxText": "מעדר כרייה",
"weaponArmoireMiningPickaxNotes": "כרו כמות מקסימלית של זהב ממשימותיכם! מגביר תפיסה ב <%= per %>. ארמואר קסום: סט כורים (חפץ 3 מתוך 3).",
- "weaponArmoireBasicLongbowText": "Basic Longbow",
- "weaponArmoireBasicLongbowNotes": "A serviceable hand-me-down bow. Increases Strength by <%= str %>. Enchanted Armoire: Basic Archer Set (Item 1 of 3).",
+ "weaponArmoireBasicLongbowText": "קשת-ארוכה בסיסית",
+ "weaponArmoireBasicLongbowNotes": "קשת יד שנייה שניתן להשתמש בה. מגבירה כוח ב <%= str %>. ארמואר קסום: סט קשת בסיסי (חפץ 1 מתוך 3).",
+ "weaponArmoireHabiticanDiplomaText": "Habitican Diploma",
+ "weaponArmoireHabiticanDiplomaNotes": "A certificate of significant achievement -- well done! Increases Intelligence by <%= int %>. Enchanted Armoire: Graduate Set (Item 1 of 3).",
"armor": "שריון",
"armorBase0Text": "בגדים פשוטים",
"armorBase0Notes": "אלה סתם בגדים, הם לא נותנים בונוס.",
@@ -364,6 +366,10 @@
"armorMystery201512Notes": "זמנו את הלהבות הקרחיות של החורף! לא מקנה ייתרון כלשהו. דצמבר 2015, חפץ מנויים.",
"armorMystery201603Text": "חליפת מזל",
"armorMystery201603Notes": "חליפה זו ארוגה מאלפי תלתנים בעלי ארבע עלים! לא מקנה ייתרון. מרץ 2016, חפץ מנויים.",
+ "armorMystery201604Text": "Armor o' Leaves",
+ "armorMystery201604Notes": "You, too, can be a small but fearsome leaf puff. Confers no benefit. April 2016 Subscriber Item.",
+ "armorMystery201605Text": "Marching Bard Uniform",
+ "armorMystery201605Notes": "Unlike the traditional bards who join adventuring parties, bards who join Habitican marching bands are known for grand parades, not dungeon raids. Confers no benefit. May 2016 Subscriber Item.",
"armorMystery301404Text": "חליפת סטימפאנק",
"armorMystery301404Notes": "נאה ונמרץ, אה! לא מקנה ייתרון. פברואר 3015, חפץ מנויים.",
"armorArmoireLunarArmorText": "שריון ירח מרגיע",
@@ -392,8 +398,10 @@
"armorArmoireJesterCostumeNotes": "טרה-לה-לה! למרות המראה של התחפושת הזו, אינכם שוטים. מגבירה תבונה ב <%= int %>. ארמואר קסום: סט לץ (חפץ 2 מתוך 3).",
"armorArmoireMinerOverallsText": "אוברול כורים",
"armorArmoireMinerOverallsNotes": "ייתכן והם נראים שחוקים, אך הם מכושפים לדחות עפר. מגבירים חוסן ב <%= con %>. ארמואר קסום: סט כורים (חפץ 2 מתוך 3).",
- "armorArmoireBasicArcherArmorText": "Basic Archer Armor",
- "armorArmoireBasicArcherArmorNotes": "This camouflaged vest lets you slip unnoticed through the forests. Increases Perception by <%= per %>. Enchanted Armoire: Basic Archer Set (Item 2 of 3).",
+ "armorArmoireBasicArcherArmorText": "שריון קשת בסיסי",
+ "armorArmoireBasicArcherArmorNotes": "ווסט הסוואה זה מאפשר לכם להתגנב בלתי מורגשים דרך היער. מגביר תפיסה ב <%= per %>. ארמואר קסום: סט קשת בסיסי (חפץ 2 מתוך 3).",
+ "armorArmoireGraduateRobeText": "Graduate Robe",
+ "armorArmoireGraduateRobeNotes": "Congratulations! This weighty robe hangs heavy with all the knowledge you have accrued. Increases Intelligence by <%= int %>. Enchanted Armoire: Graduate Set (Item 2 of 3).",
"headgear": "ציוד ראש",
"headBase0Text": "ללא קסדה",
"headBase0Notes": "בלי שום כיסוי ראש בכלל.",
@@ -565,6 +573,10 @@
"headMystery201602Notes": "שמרו על זהותכם מפני מעריצים. לא מקנה ייתרון. פברואר 2016, חפץ מנויים.",
"headMystery201603Text": "כובע מזל",
"headMystery201603Notes": "כובע זה הוא קמע מזל קסום. לא מקנה ייתרון. מרץ 2016, חפץ מנויים.",
+ "headMystery201604Text": "Crown o' Flowers",
+ "headMystery201604Notes": "These woven flowers make a surprisingly strong helm! Confers no benefit. April 2016 Subscriber Item.",
+ "headMystery201605Text": "Marching Bard Hat",
+ "headMystery201605Notes": "Seventy-six dragons led the big parade, with a hundred and ten gryphons close at hand! Confers no benefit. May 2016 Subscriber Item.",
"headMystery301404Text": "כובע ראש מפואר",
"headMystery301404Notes": "כובע ראש מפואר למכובד שבג׳נטלמנים! ינואר 3015, חפץ מנויים. לא מקנה ייתרון.",
"headMystery301405Text": "כובע ראש בסיסי",
@@ -611,8 +623,10 @@
"headArmoireJesterCapNotes": "הפעמונים על הכובע הזה עלולים להסיח את דעתם של אויבכם, אך לכם הם עוזרים להתרכז. מגבירים תפיסה ב <%= per %>. ארמואר קסום: סט לץ (חפץ 1 מתוך 3).",
"headArmoireMinerHelmetText": "קסדת כרייה",
"headArmoireMinerHelmetNotes": "הגנו על ראשכם ממשימות נופלות! מגביר תבונה ב <%= int %>. ארמואר קסום: סט כורים (חפץ 1 מתוך 3).",
- "headArmoireBasicArcherCapText": "Basic Archer Cap",
- "headArmoireBasicArcherCapNotes": "No archer would be complete without a jaunty cap! Increases Perception by <%= per %>. Enchanted Armoire: Basic Archer Set (Item 3 of 3).",
+ "headArmoireBasicArcherCapText": "כובע קשת בסיסי",
+ "headArmoireBasicArcherCapNotes": "אף קשת לא יהיה מושלם בלי כובע קליל! מגביר תפיסה ב <%= per %>. ארמואר קסום: סט קשת בסיסי (חפץ 3 מתוך 3).",
+ "headArmoireGraduateCapText": "Graduate Cap",
+ "headArmoireGraduateCapNotes": "Congratulations! Your deep thoughts have earned you this thinking cap. Increases Intelligence by <%= int %>. Enchanted Armoire: Graduate Set (Item 3 of 3).",
"offhand": "חפץ יד-מגן",
"shieldBase0Text": "אין חפץ יד-מגן",
"shieldBase0Notes": "לא הצטיידת באף מגן או נשק ליד החדשה.",
@@ -673,7 +687,7 @@
"shieldSpecialWinter2015HealerText": "מגן מרגיע",
"shieldSpecialWinter2015HealerNotes": "המגן מסיט את הרוח הקפואה. מגביר חוסן ב <%= con %>. מהדורה מוגבלת 2014-2015, ציוד חורף.",
"shieldSpecialSpring2015RogueText": "צפצוף מתפוצץ",
- "shieldSpecialSpring2015RogueNotes": "אל תיתנו לקול להטעות אתכם - חומרי נפץ אלו אוגרים בקרבם סנוקרת. מגבירים כוח ב <%= str %>. מהדורה מוגבלת 2015, ציוד אביב.",
+ "shieldSpecialSpring2015RogueNotes": "Don't let the sound fool you - these explosives pack a punch. Increases Strength by <%= str %>. Limited Edition 2015 Spring Gear.",
"shieldSpecialSpring2015WarriorText": "גועל כלים",
"shieldSpecialSpring2015WarriorNotes": "שלחו זאת על אויבייכם... או פשוט החזיקו זאת, כיוון שהוא יתמלא במזון כלבים טעים בארוחת הערב. מגביר חוסן ב <%= con %>. מהדורה מוגבלת 2015, ציוד אביב.",
"shieldSpecialSpring2015HealerText": "כרית עם דוגמה",
@@ -716,6 +730,8 @@
"shieldArmoireDragonTamerShieldNotes": "הסיחו את דעתם של אויבים עם מגן בצורת דרקון זה. מגביר תפיסה ב <%= per %>. ארמואר קסום: סט מאלף דרקונים (חפץ 2 מתוך 3).",
"shieldArmoireMysticLampText": "מנורה מיסטית",
"shieldArmoireMysticLampNotes": "האירו את המערות החשוכות ביותר עם מנורה מיסטית זו! מגביר תפיסה ב <%= per %>. ארמואר קסום: חפץ בלתי תלוי.",
+ "shieldArmoireFloralBouquetText": "Bouquet o' Flowers",
+ "shieldArmoireFloralBouquetNotes": "Not much help in battle, but aren't they beautiful? Increases Constitution by <%= con %>. Enchanted Armoire: Independent Item.",
"back": "אביזר גב",
"backBase0Text": "אביזר ללא גב",
"backBase0Notes": "אביזר ללא גב.",
@@ -815,11 +831,25 @@
"headAccessoryMystery201510Notes": "הקרניים המפחידות האלו הן חלקלקות במקצת. לא נותנות ייתרון כלשהו. חפץ מנויים אוקטובר 2015.",
"headAccessoryMystery301405Text": "משקפי ראש",
"headAccessoryMystery301405Notes": "״משקפיים הן לעייניים,״ הם אמרו. ״אף אחד לא ירצה משקפיים שאפשר לחבוש רק על הראש,״ הם אמרו. הא! בהחלט הראיתם להם! לא מקנות ייתרון. אוגוסט 3015, חפץ מנויים.",
- "headAccessoryArmoireComicalArrowText": "Comical Arrow",
- "headAccessoryArmoireComicalArrowNotes": "This whimsical item doesn't provide a stat boost, but it sure is good for a laugh! Confers no benefit. Enchanted Armoire: Independent Item.",
+ "headAccessoryArmoireComicalArrowText": "חץ קומי",
+ "headAccessoryArmoireComicalArrowNotes": "חפץ גחמתי זה לא משפר תכונות, אבל הוא בהחלט מצחיק! לא מקנה ייתרון. ארמואר קסום: חפץ בלתי תלוי.",
"eyewear": "לבוש לעיניים",
"eyewearBase0Text": "ללא לבוש לעיניים",
"eyewearBase0Notes": "ללא לבוש לעיניים.",
+ "eyewearSpecialBlackTopFrameText": "Black Standard Eyeglasses",
+ "eyewearSpecialBlackTopFrameNotes": "Glasses with a black frame above the lenses. Confers no benefit.",
+ "eyewearSpecialBlueTopFrameText": "Blue Standard Eyeglasses",
+ "eyewearSpecialBlueTopFrameNotes": "Glasses with a blue frame above the lenses. Confers no benefit.",
+ "eyewearSpecialGreenTopFrameText": "Green Standard Eyeglasses",
+ "eyewearSpecialGreenTopFrameNotes": "Glasses with a green frame above the lenses. Confers no benefit.",
+ "eyewearSpecialPinkTopFrameText": "Pink Standard Eyeglasses",
+ "eyewearSpecialPinkTopFrameNotes": "Glasses with a pink frame above the lenses. Confers no benefit.",
+ "eyewearSpecialRedTopFrameText": "Red Standard Eyeglasses",
+ "eyewearSpecialRedTopFrameNotes": "Glasses with a red frame above the lenses. Confers no benefit.",
+ "eyewearSpecialWhiteTopFrameText": "White Standard Eyeglasses",
+ "eyewearSpecialWhiteTopFrameNotes": "Glasses with a white frame above the lenses. Confers no benefit.",
+ "eyewearSpecialYellowTopFrameText": "Yellow Standard Eyeglasses",
+ "eyewearSpecialYellowTopFrameNotes": "Glasses with a yellow frame above the lenses. Confers no benefit.",
"eyewearSpecialSummerRogueText": "רטייה שובבית",
"eyewearSpecialSummerRogueNotes": "לא צריך להיות נבזה כדי לראות עד כמה זה אופנתי! לא מקנה ייתרון. מהדורה מוגבלת 2014, ציוד קיץ.",
"eyewearSpecialSummerWarriorText": "רטייה נמרצת",
diff --git a/common/locales/he/generic.json b/common/locales/he/generic.json
index d46901f868..f7ea121a9c 100644
--- a/common/locales/he/generic.json
+++ b/common/locales/he/generic.json
@@ -33,7 +33,7 @@
"italics": "*נטוי*",
"bold": "**מודגש**",
"strikethrough": "~~מחוק~~",
- "emojiExample": ":חיוך:",
+ "emojiExample": ":smile:",
"markdownLinkEx": "[הביטיקה נפלאה!](https://habitica.com)",
"markdownImageEx": "",
"unorderedListHTML": "+ חפץ ראשון + חפץ שני + חפץ שלישי",
@@ -95,7 +95,7 @@
"memberSince": "- חבר/ה מאז",
"lastLoggedIn": "- התחבר/ה לאחרונה",
"notPorted": "התכונה הזו עוד לא יובאה מהאתר המקורי",
- "buyThis": "לרכוש את <%= text %> באמצעות <%= price %> מאבני החן שלך?",
+ "buyThis": "האם לקנות <%= text %> זה עם <%= price %> מתוך <%= gems %> אבני החן שלכם?",
"noReachServer": "השרת אינו זמין כרגע, אנא נסו שוב מאוחר יותר",
"errorUpCase": "שגיאה:",
"newPassSent": "סיסמא חדשה נשלחה",
@@ -137,8 +137,8 @@
"achievementStressbeastText": "סייע בהבסת המפלחץ המתועב במהלך חורף הפלאות של 2014!",
"achievementBurnout": "מציל השדות הפורחים",
"achievementBurnoutText": "סייע להביס ״שחיקה״ ולהשיב את ״הרוחות המותשות״ במהלך אירוע פסטיבל השלכת 2015!",
- "achievementBewilder": "Savior of Mistiflying",
- "achievementBewilderText": "Helped defeat the Be-Wilder during the 2016 Spring Fling Event!",
+ "achievementBewilder": "מציל של מיסטי",
+ "achievementBewilderText": "סייעתם להביס את המש-תומם במהלך ארוע הפלינג האביבי 2016!",
"checkOutProgress": "תראו את ההתקדמות שלי בהביטיקה!",
"cardReceived": "קיבלתם כרטיס!",
"cardReceivedFrom": "<%= cardType %> מ <%= userName %>",
diff --git a/common/locales/he/groups.json b/common/locales/he/groups.json
index 7de42b45ca..6a45cab186 100644
--- a/common/locales/he/groups.json
+++ b/common/locales/he/groups.json
@@ -92,6 +92,7 @@
"send": "שליחה",
"messageSentAlert": "הודעה נשלחה",
"pmHeading": "הודעה פרטית ל <%= name %>",
+ "pmsMarkedRead": "Your private messages have been marked as read",
"clearAll": "מחק את כל ההודעות",
"confirmDeleteAllMessages": "האם אתם בטוחים שאתם רוצים למחוק את כל ההודעות בתיבת הדואר שלכם? משתמשים אחרים עדיין יראו הודעות ששלחתם אליהם.",
"optOutPopover": "לא בקטע של הודעות פרטיות? לחצ/י כדי לוותר על זה",
@@ -99,6 +100,15 @@
"unblock": "ביטול חסימה",
"pm-reply": "שליחת תגובה",
"inbox": "תיבת דואר",
+ "messageRequired": "A message is required.",
+ "toUserIDRequired": "A User ID is required",
+ "gemAmountRequired": "A number of gems is required",
+ "notAuthorizedToSendMessageToThisUser": "Can't send message to this user.",
+ "privateMessageGiftIntro": "Hello <%= receiverName %>, <%= senderName %> has sent you",
+ "privateMessageGiftGemsMessage": "<%= gemAmount %> gems!",
+ "privateMessageGiftSubscriptionMessage": "<%= numberOfMonths %> months of subscription!",
+ "cannotSendGemsToYourself": "Cannot send gems to yourself. Try a subscription instead.",
+ "badAmountOfGemsToSend": "Amount must be within 1 and your current number of gems.",
"abuseFlag": "דווח/י על הפרת חוקי הקהילה",
"abuseFlagModalHeading": "לדווח על <%= name %> באשמת הפרת חוקים?",
"abuseFlagModalBody": "האם אתם בטוחים שאתם רוצים לדווח על ההודעה הזו? אתם אמורים לדווח אך ורק על הודעות שמפרות את <%= firstLinkStart %>הנחיות הקהילה<%= linkEnd %> ו/או <%= secondLinkStart %>תנאי השירות<%= linkEnd %>. דיווח לא מוצדק הוא הפרה של הנחיות הקהילה ויחשב כעבירה. סיבות נאותות לדיווח על הודעה כוללות, אך לא מוגבלות ל:
קללות, שבועות דתיות
קנאות, השמצות
נושאים למבוגרים
אלימות, כולל בדיחות
דואר זבל, והודעות לא הגיוניות
",
@@ -151,5 +161,29 @@
"partyUpName": "חגיגה",
"partyOnName": "מסיבה",
"partyUpAchievement": "הצטרפתם לחבורה עם אדם נוסף! תהנו להלחם במפלצות ולתמוך אחד בשני.",
- "partyOnAchievement": "הצטרפתם לחבורה עם לפחות ארבעה אנשים נוספים! תהנו ממחוייבות מוגברת כשאתם מצטרפים לחבריכם כדי להלחם באוייבים!"
+ "partyOnAchievement": "הצטרפתם לחבורה עם לפחות ארבעה אנשים נוספים! תהנו ממחוייבות מוגברת כשאתם מצטרפים לחבריכם כדי להלחם באוייבים!",
+ "groupIdRequired": "\"groupId\" must be a valid UUID",
+ "groupNotFound": "Group not found.",
+ "groupTypesRequired": "You must supply a valid \"type\" query string.",
+ "questLeaderCannotLeaveGroup": "You cannot leave your party when you have started a quest. Abort the quest first.",
+ "cannotLeaveWhileActiveQuest": "You cannot leave party during an active quest. Please leave the quest first.",
+ "onlyLeaderCanRemoveMember": "Only group leader can remove a member!",
+ "memberCannotRemoveYourself": "You cannot remove yourself!",
+ "groupMemberNotFound": "User not found among group's members",
+ "mustBeGroupMember": "Must be member of the group.",
+ "keepOrRemoveAll": "req.query.keep must be either \"keep-all\" or \"remove-all\"",
+ "keepOrRemove": "req.query.keep must be either \"keep\" or \"remove\"",
+ "canOnlyInviteEmailUuid": "Can only invite using uuids or emails.",
+ "inviteMissingEmail": "Missing email address in invite.",
+ "partyMustbePrivate": "Parties must be private",
+ "userAlreadyInGroup": "User already in that group.",
+ "userAlreadyInvitedToGroup": "User already invited to that group.",
+ "userAlreadyPendingInvitation": "User already pending invitation.",
+ "userAlreadyInAParty": "User already in a party.",
+ "userWithIDNotFound": "User with id \"<%= userId %>\" not found.",
+ "userHasNoLocalRegistration": "User does not have a local registration (username, email, password).",
+ "uuidsMustBeAnArray": "User ID invites must be an array.",
+ "emailsMustBeAnArray": "Email address invites must be an array.",
+ "canOnlyInviteMaxInvites": "You can only invite \"<%= maxInvites %>\" at a time",
+ "onlyCreatorOrAdminCanDeleteChat": "Not authorized to delete this message!"
}
\ No newline at end of file
diff --git a/common/locales/he/limited.json b/common/locales/he/limited.json
index f58a20cfc4..f8e0b66e0e 100644
--- a/common/locales/he/limited.json
+++ b/common/locales/he/limited.json
@@ -5,7 +5,7 @@
"annoyingFriends": "חברים מרגיזים",
"annoyingFriendsText": "חטף <%= snowballs %> כדורי שלג לפרצוף משאר החבורה.",
"alarmingFriends": "חברים מפחידים",
- "alarmingFriendsText": "הופחד/ה <%= spookDust %> פעמים ע\"י שאר החבורה שלו/ה.",
+ "alarmingFriendsText": "חברי חבורה הבהילו אתכם <%= spookySparkles %> פעמים.",
"agriculturalFriends": "חברים חקלאיים",
"agriculturalFriendsText": "הוחלף לפרח <%= seeds %> פעמים על-ידי חברי חבורה.",
"aquaticFriends": "חברים ימיים",
@@ -45,7 +45,7 @@
"toAndFromCard": "אל: <%= toName %>, מאת: <%= fromName %>",
"nyeCard": "כרטיס לשנה החדשה",
"nyeCardExplanation": "על חגיגות השנה החדשה יחד, שניכם מקבלים את תג ״המכרים הוותיקים״!",
- "nyeCardNotes": "שליחת כרטיס שנה טובה לחבר/ת חבורה.",
+ "nyeCardNotes": "שליחת כרטיס שנה טובה לחברי חבורה.",
"seasonalItems": "חפצים עונתיים",
"nyeCardAchievementTitle": "מכרים וותיקים",
"nyeCardAchievementText": "שנה טובה! שלחתם או קיבלתם <%= cards %> כרטיסי שנה טובה.",
@@ -68,9 +68,10 @@
"mummyMedicSet": "מומיה רופאה (מרפא)",
"vampireSmiterSet": "ערפד מייסר (נוכל)",
"bewareDogSet": "כלב זהירות (לוחם)",
- "magicianBunnySet": "ארנב קוסים (מכשף)",
+ "magicianBunnySet": "ארנב קוסמים (מכשף)",
"comfortingKittySet": "חתלתול מנחם (מרפא)",
"sneakySqueakerSet": "חורק מתגנב (נוכל)",
"fallEventAvailability": "זמין עד ה 31 באוקטובר",
- "winterEventAvailability": "זמין עד ה 31 בדצמבר"
+ "winterEventAvailability": "זמין עד ה 31 בדצמבר",
+ "springEventAvailability": "זמין עד ה 31 במאי"
}
\ No newline at end of file
diff --git a/common/locales/he/maintenance.json b/common/locales/he/maintenance.json
new file mode 100644
index 0000000000..e1afe60a76
--- /dev/null
+++ b/common/locales/he/maintenance.json
@@ -0,0 +1,34 @@
+{
+ "habiticaBackSoon": "אל דאגה, Habitica תחזור בקרוב!",
+ "importantMaintenance": "אנו עוסקים בתחזוקה חשובה שאנו מעריכים כי תימשך עד <%= localDate %> באזור הזמן שלך.",
+ "maintenance": "תחזוקה",
+ "maintenanceMoreInfo": "רוצה עוד מידע בנוגע לתחזוקה? <%= linkStart %>תנ/י מבט בדף המידע שלנו<%= linkEnd %>.",
+ "noDamageKeepStreaks": "לא יהיה לך שום אובדן של רצפים או פגיעה בנקודות החיים שלך!",
+ "thanksForPatience": "תודה לך על סבלנותך!",
+ "twitterMaintenanceUpdates": "לעדכונים החדשים ביותר, צפה/י בחשבון הטוויטר שלנו, שבו נפרסם מידע על המצב.",
+ "veteranPetAward": "בסוף התחזוקה, את/ה תקבל/י חיית מחמד ותיקה!",
+
+ "maintenanceInfoTitle": "מידע בנוגע לתחזוקה צפויה בHabitica",
+ "maintenanceInfoWhat": "מה קורה?",
+ "maintenanceInfoWhatText": "ב21 במאי, Habitica תרד לתחזוקה למשך רוב היום. לא תהיה שום פגיעה בנקודות החיים שלך או כל נזק לחשבון שלך במהלך אותו סוף השבוע, אפילו אם לא תוכל/י להיכנס לחשבון כדי לסמן את היומיות שלך בזמן! אנחנו נעבוד קשה מאוד כדי לעשות את פרק הזמן הזה קצר ככל האפשר, ונפרסם עדכונים שוטפים בחשבון הטוויטר שלנו. בתום תקופת התחזוקה כולכם/ן תקבלו חיית מחמד נדירה!",
+ "maintenanceInfoWhy": "מדוע זה קורה?",
+ "maintenanceInfoWhyText": "במהלך מספר החודשים האחרונים, אנחנו הכנסנו אויר מחודש מאחורי הקלעים של Habitica. למשל, כתבנו מחדש את הAPI. למרות שלמראית עין על פני השטח לא התרחש כמעט שינוי, יש עולם חדש שלם מתחת. זה יאפשר לנו הרבה יותר גמישות כאשר נרצה לבנות מאפיינים בעתיד, ויוביל לביצועים משופרים!",
+ "maintenanceInfoTechDetails": "Want more details on the technical side of the process? Visit The Forge, our dev blog.",
+ "maintenanceInfoMore": "More Information",
+ "maintenanceInfoAccountChanges": "What changes will I see to my account after the rewrite is complete?",
+ "maintenanceInfoAccountChangesText": "At first, there won’t be any notable changes aside from performance improvements for features such as Challenges. If you notice any changes that shouldn’t be there, email us at admin@habitica.com and we will investigate them for you!",
+ "maintenanceInfoAddFeatures": "What kind of features will this allow Habitica to add?",
+ "maintenanceInfoAddFeaturesText": "Completing this rewrite will allow us to start building out improved chat and Guilds, plans for organizations and families, and additional productivity features like Monthlies and the ability to record yesterday’s activity! Those are all involved features on their own, so it will take time to build them, but until we were finished with this rewrite, there was no way we could start them.",
+ "maintenanceInfoHowLong": "How long will the maintenance take?",
+ "maintenanceInfoHowLongText": "We have to migrate tasks and data for all 1.3 million Habitica users -- not an easy task! We anticipate that it will take place between approximately 1pm Pacific Time (8pm UTC) and 10pm Pacific Time (5am UTC). Rest assured that we’re doing everything we can to make it go as quickly as possible! You can follow updates on our Twitter.",
+ "maintenanceInfoStatsAffected": "How will my Dailies, Streaks, Buffs, and Quests be affected?",
+ "maintenanceInfoStatsAffectedText1": "You will NOT take any damage or lose any streaks that weekend, but otherwise, your day will reset normally! Dailies that you checked will become unchecked, buffs will reset, etc. If you are in a Collection Quest, you will still find items. If you are in a Boss Battle, you will still deal damage to the Boss, but the Boss will not deal damage to you. (Even monsters need a break!)",
+ "maintenanceInfoStatsAffectedText2": "After a lot of thought, our team concluded that this was the most fair way to handle the fact that many users will not be able to check off their Dailies normally during the maintenance. We’re sorry for any inconvenience this causes!",
+ "maintenanceInfoSeeTasks": "What if I need to see my task list?",
+ "maintenanceInfoSeeTasksText": "If you know that you will need to see your task list on Saturday to remind yourself what you have to do, we recommend that before the maintenance begins, you take a screenshot of your tasks so that you can use it as a reference.",
+ "maintenanceInfoRarePet": "What kind of rare pet will I receive?",
+ "maintenanceInfoRarePetText": "To thank you for your patience during the downtime, everyone will get a rare Veteran Pet. If you’ve never received a Veteran Pet before, you will receive a Veteran Wolf. If you already have a Veteran Wolf, you will receive a Veteran Tiger. And if you already have a Veteran Wolf and a Veteran Tiger, you will receive a never-before-seen Veteran pet! After the migration is completed, it may take several hours for your pet to show up, but never fear, everyone will get one.",
+ "maintenanceInfoWho": "Who worked on this massive project?",
+ "maintenanceInfoWhoText": "We’re glad you asked! It was spearheaded by our amazing contributor paglias, with lots of help from Blade, TheHollidayInn, SabreCat, Victor Pudeyev, TheUnknown, and Alys.",
+ "maintenanceInfoTesting": "The new version was also tirelessly tested by a bunch of our amazing open-source volunteers. Thank you -- we couldn't have done this without you."
+}
diff --git a/common/locales/he/npc.json b/common/locales/he/npc.json
index df6fc5868d..faf756a3da 100644
--- a/common/locales/he/npc.json
+++ b/common/locales/he/npc.json
@@ -21,6 +21,26 @@
"ian": "איאן",
"ianText": "ברוכים הבאים לחנות ההרפתקאות! כאן תוכלו להשתמש במגילות הרפתקאות כדי להלחם במפלצות עם חבריכם. שימו לב לבדוק את מבחר מגילות ההרפתקאות שיש לנו למכירה כאן בצד ימין!",
"ianBrokenText": "ברוכים הבאים לחנות ההרפתקאות... כאן תוכלו למצוא מגילות הרפתקאות כדי להלחם במפלצות עם חבריכם... שימו לב לבדוק את מבחר מגילות ההרפתקאות שיש לנו למכירה פה בצד ימין...",
+ "missingKeyParam": "\"req.params.key\" is required.",
+ "itemNotFound": "Item \"<%= key %>\" not found.",
+ "cannotBuyItem": "You can't buy this item.",
+ "missingTypeKeyEquip": "\"key\" and \"type\" are required parameters.",
+ "missingPetFoodFeed": "\"pet\" and \"food\" are required parameters.",
+ "invalidPetName": "Invalid pet name supplied.",
+ "missingEggHatchingPotionHatch": "\"egg\" and \"hatchingPotion\" are required parameters.",
+ "invalidTypeEquip": "\"type\" must be one of 'equipped', 'pet', 'mount', 'costume'.",
+ "mustPurchaseToSet": "Must purchase <%= val %> to set it on <%= key %>.",
+ "typeRequired": "Type is required",
+ "keyRequired": "Key is required",
+ "notAccteptedType": "Type must be in [eggs, hatchingPotions, food, quests, gear]",
+ "contentKeyNotFound": "Key not found for Content <%= type %>",
+ "plusOneGem": "+1 Gem",
+ "typeNotSellable": "Type is not sellable. Must be one of the following <%= acceptedTypes %>",
+ "userItemsKeyNotFound": "Key not found for user.items <%= type %>",
+ "pathRequired": "Path string is required",
+ "unlocked": "Items have been unlocked",
+ "alreadyUnlocked": "Full set already unlocked.",
+ "alreadyUnlockedPart": "Full set already partially unlocked.",
"USD": "(דולר)",
"newStuff": "דברים חדשים",
"cool": "ספר לי מאוחר יותר",
@@ -64,6 +84,7 @@
"tourPetsPage": "זוהי האורווה! אחרי דרה 3, תוכלו להבקיע חיות מחמד באמצעות ביצים ושיקויים. כאשר אתם מבקיעים חיית מחמד בשוק, היא תופיע כאן! לחצו על תמונת חיית המחמד כדי להוסיף אותה לתמונת הדמות שלכם. האכילו אותן עם אוכל שאתם מוצאים אחרי שלב 3, והן תצמחנה להיות חיות רכיבה עוצמתיות.",
"tourMountsPage": "Once you've fed a pet enough food to turn it into a mount, it will appear here. (Pets, mounts, and food are available after level 4.) Click a mount to saddle up!",
"tourEquipmentPage": "זה המקום שבו הציוד שלכם מאוכסן! ציוד הלחימה שלכם משפיע על התכונות שלכם. אם תרצו להציג ציוד אחר על הדמות שלכם מבלי להשפיע על התכונות, לחצו על ״אפשרו תחפושת.״",
+ "equipmentAlreadyOwned": "You already own that piece of equipment",
"tourOkay": "אוקיי!",
"tourAwesome": "מדהים!",
"tourSplendid": "נפלא!",
diff --git a/common/locales/he/pets.json b/common/locales/he/pets.json
index 7e57b58a47..a9bd7ce5b7 100644
--- a/common/locales/he/pets.json
+++ b/common/locales/he/pets.json
@@ -12,6 +12,7 @@
"etherealLion": "אריה אתרי",
"veteranWolf": "זאב ותיק",
"veteranTiger": "נמר ותיק",
+ "veteranLion": "Veteran Lion",
"cerberusPup": "גור קרברוס",
"hydra": "הידרה",
"mantisShrimp": "חסילון-גמל-שלמה",
@@ -19,7 +20,7 @@
"orca": "אורקה",
"royalPurpleGryphon": "גריפון סגול מלכותי",
"phoenix": "פיניקס",
- "bumblebee": "Bumblebee",
+ "magicalBee": "דבורה קסומה",
"rarePetPop1": "לחצו על הכפה הזהובה ותוכלו ללמוד כיצד להשיג חיה נדירה זו - דרך תרומה להאביטיקה!",
"rarePetPop2": "איך לקבל את חיית המחמד הזו!",
"potion": "שיקוי <%= potionType %>",
@@ -62,6 +63,7 @@
"hatchedPet": "הבקעת <%= potion %> <%= egg %>!",
"displayNow": "הצג עכשיו",
"displayLater": "הצג מאוחר יותר",
+ "petNotOwned": "You do not own this pet.",
"earnedCompanion": "עם כל הפרודוקטיביות שלך, זכית במלווה חדש. האכל אותו כדי לגרום לו לגדול!",
"feedPet": "להאכיל את ה<%= name %> שלך ב <%= text %>?",
"useSaddle": "לשים אוכף על ה<%= pet %> שלך?",
@@ -83,5 +85,8 @@
"petKeyBoth": "שחרר את שניהם",
"confirmPetKey": "האם אתה בטוח?",
"petKeyNeverMind": "עוד לא",
+ "petsReleased": "Pets released.",
+ "mountsAndPetsReleased": "Mounts and pets released",
+ "mountsReleased": "Mounts released",
"gemsEach": "אבני חן לכל אחד"
}
\ No newline at end of file
diff --git a/common/locales/he/quests.json b/common/locales/he/quests.json
index 90891622c4..6deb1ed7cd 100644
--- a/common/locales/he/quests.json
+++ b/common/locales/he/quests.json
@@ -38,7 +38,7 @@
"bossDmg2": "רק משתתפים שנלחמים באוייב חולקים בביזת ההרפתקאה.",
"bossDmg1Broken": "כל מטלה יומית, משימה, והרגל חיובי - פוגעים באוייב... פגעו בו יותר עם משימות אדומו, או ״חבטה אכזרית״ או ״פרץ להבות״... האוייב יגרום נזק לכל משתתף בחבורה בעבור כל מטלה יומית שתחמיצו (מוכפלת בעוצמת האוייב), בנוסף לנזק הרגיל, אז שמרו על החבורה בריאה על ידי השלמת מטלות יומיות... כל הנזק מהאוייב נסכם ב״כרון״ (גלגול היום שלכם)...",
"bossDmg2Broken": "רק משתתפים ילחמו בבוס ויחלקו בביזה מההרפתקאה",
- "tavernBossInfo": "Complete Dailies and To-Dos and score positive Habits to damage the World Boss! Incomplete Dailies fill the Rage Bar. When the Rage bar is full, the World Boss will attack an NPC. A World Boss will never damage individual players or accounts in any way. Only active accounts not resting in the Inn will have their tasks tallied.",
+ "tavernBossInfo": "השלימו מטלות יומיות, משימות, והרגלים חיוביים כדי לפגוע באוייב העולמי! מטלות יומיות לא מושלמות ימלאו את מד הזעם. כאשר מד הזעם מלא, האוייב העולמי יתקוף דמות-בלי-שחקן. אוייב עולמי לעולם לא יפגע בשחקן פרטי או בחשבון כלשהו בשום צורה. רק פעולות של חשבונות פעילים שאינם נחים באכסנייה ייחשבו.",
"tavernBossInfoBroken": "השלימו מטלות יומיות, ומשימות, והקנו לעצמכם הרגלים חיוביים כדי לפגוע באוייב העולמי... משימות יומיות שלא הושלמו ימלאו את ״מד רצף ההתשה״... כאשר צד ״רצף ההתשה״ מלא, האוייב העולמי יתקיף דב״ש... אוייב עולמי לעולם לא יפגע בשחקנים פרטיים ולא ייחשב בשום מקרה... רק חשבונות פעילים שאינם נחים באכסנייה יישתתפו בחישוב...",
"bossColl1": "כדי לאסוף חפצים, בצעו מטלות חיוביות. חפצי הרפתקאות נופלים ממש כמו חפצים רגילים, אם כי, אתם לא תראו אותם עד היום הבא, בו כל מה שמצאה החבורה ייאסף לערימת ההרפתקאה.",
"bossColl2": "רק משתתפים בהרפתקאה יכולים לאסוף חפצים ולהתחלק בשלל ההרפתקאה.",
@@ -78,5 +78,24 @@
"whichQuestStart": "איזו הרפתקאה תרצו להתחיל?",
"getMoreQuests": "קבלו הרפתקאות נוספות",
"unlockedAQuest": "שחררתם הרפתקאה!",
- "leveledUpReceivedQuest": "עליתם לרמה <%= level %> וקיבלתם מגילת הרפתקאה!"
+ "leveledUpReceivedQuest": "עליתם לרמה <%= level %> וקיבלתם מגילת הרפתקאה!",
+ "questInvitationDoesNotExist": "No quest invitation has been sent out yet.",
+ "questInviteNotFound": "No quest invitation found.",
+ "guildQuestsNotSupported": "Guilds cannot be invited on quests.",
+ "questNotFound": "Quest \"<%= key %>\" not found.",
+ "questNotOwned": "You don't own that quest scroll.",
+ "questNotGoldPurchasable": "Quest \"<%= key %>\" is not a Gold-purchasable quest.",
+ "questLevelTooHigh": "You must be level <%= level %> to begin this quest.",
+ "questAlreadyUnderway": "Your party is already on a quest. Try again when the current quest has ended.",
+ "questAlreadyAccepted": "You already accepted the quest invitation.",
+ "noActiveQuestToLeave": "No active quest to leave",
+ "questLeaderCannotLeaveQuest": "Quest leader cannot leave quest",
+ "notPartOfQuest": "You are not part of the quest",
+ "noActiveQuestToAbort": "There is no active quest to abort.",
+ "onlyLeaderAbortQuest": "Only the group or quest leader can abort a quest.",
+ "questAlreadyRejected": "You already rejected the quest invitation.",
+ "cantCancelActiveQuest": "You can not cancel an active quest, use the abort functionality.",
+ "onlyLeaderCancelQuest": "Only the group or quest leader can cancel the quest.",
+ "questNotPending": "There is no quest to start.",
+ "questOrGroupLeaderOnlyStartQuest": "Only the quest leader or group leader can force start the quest"
}
\ No newline at end of file
diff --git a/common/locales/he/questscontent.json b/common/locales/he/questscontent.json
index 3ec9a01afd..840037dfcc 100644
--- a/common/locales/he/questscontent.json
+++ b/common/locales/he/questscontent.json
@@ -298,15 +298,27 @@
"questSnailBoss": "חילזון בוצת עבודת פרך",
"questSnailDropSnailEgg": "חילזון (ביצה)",
"questSnailUnlockText": "משחרר ביצי חלזונות לרכישה בשוק",
- "questBewilderText": "The Be-Wilder",
- "questBewilderNotes": "The party begins like any other.
The appetizers are excellent, the music is swinging, and even the dancing elephants have become routine. Habiticans laugh and frolic amid the overflowing floral centerpieces, happy to have a distraction from their least-favorite tasks, and the April Fool whirls among them, eagerly providing an amusing trick here and a witty twist there.
As the Mistiflying clock tower strikes midnight, the April Fool leaps onto the stage to give a speech.
“Friends! Enemies! Tolerant acquaintances! Lend me your ears.” The crowd chuckles as animal ears sprout from their heads, and they pose with their new accessories.
“As you know,” the Fool continues, “my confusing illusions usually only last a single day. But I’m pleased to announce that I’ve discovered a shortcut that will guarantee us non-stop fun, without having to deal with the pesky weight of our responsibilities. Charming Habiticans, meet my magical new friend... the Be-Wilder!”
Lemoness pales suddenly, dropping her hors d'oeuvres. “Wait! Don’t trust--”
But suddenly mists are pouring into the room, glittering and thick, and they swirl around the April Fool, coalescing into cloudy feathers and a stretching neck. The crowd is speechless as an monstrous bird unfolds before them, its wings shimmering with illusions. It lets out a horrible screeching laugh.
“Oh, it has been ages since a Habitican has been foolish enough to summon me! How wonderful it feels, to have a tangible form at last.”
Buzzing in terror, the magic bees of Mistiflying flee the floating city, which sags from the sky. One by one, the brilliant spring flowers wither up and wisp away.
“My dearest friends, why so alarmed?” crows the Be-Wilder, beating its wings. “There’s no need to toil for your rewards any more. I’ll just give you all the things that you desire!”
A rain of coins pours from the sky, hammering into the ground with brutal force, and the crowd screams and flees for cover. “Is this a joke?” Baconsaur shouts, as the gold smashes through windows and shatters roof shingles.
PainterProphet ducks as lightning bolts crackle overhead, and fog blots out the sun. “No! This time, I don’t think it is!”
Quickly, Habiticans, don’t let this World Boss distract us from our goals! Stay focused on the tasks that you need to complete so we can rescue Mistiflying -- and hopefully, ourselves.",
- "questBewilderCompletion": "The Be-Wilder is DEFEATED!
We've done it! The Be-Wilder lets out a ululating cry as it twists in the air, shedding feathers like falling rain. Slowly, gradually, it coils into a cloud of sparkling mist. As the newly-revealed sun pierces the fog, it burns away, revealing the coughing, mercifully human forms of Bailey, Matt, Alex.... and the April Fool himself.
Mistiflying is saved!
The April Fool has enough shame to look a bit sheepish. “Oh, hm,” he says. “Perhaps I got a little…. carried away.”
The crowd mutters. Sodden flowers wash up on sidewalks. Somewhere in the distance, a roof collapses with a spectacular splash.
“Er, yes,” the April Fool says. “That is. What I meant to say was, I’m dreadfully sorry.” He heaves a sigh. “I suppose it can’t all be fun and games, after all. It might not hurt to focus occasionally. Maybe I’ll get a head start on next year’s pranking.”
Redphoenix coughs meaningfully.
“I mean, get a head start on this year’s spring cleaning!” the April Fool says. “Nothing to fear, I’ll have Habit City in spit-shape soon. Luckily nobody is better than I at dual-wielding mops.”
Encouraged, the marching band starts up.
It isn’t long before all is back to normal in Habit City. Plus, now that the Be-Wilder has evaporated, the magical bees of Mistiflying bustle back to work, and soon the flowers are blooming and the city is floating once more.
As Habiticans cuddle the magical fuzzy bees, the April Fool’s eyes light up. “Oho, I’ve had a thought! Why don’t you all keep some of these fuzzy Bee Pets and Mounts? It’s a gift that perfectly symbolizes the balance between hard work and sweet rewards, if I’m going to get all boring and allegorical on you.” He winks. “Besides, they don’t have stingers! Fool’s honor.”",
- "questBewilderCompletionChat": "`The Be-Wilder is DEFEATED!`\n\nWe've done it! The Be-Wilder lets out a ululating cry as it twists in the air, shedding feathers like falling rain. Slowly, gradually, it coils into a cloud of sparkling mist. As the newly-revealed sun pierces the fog, it burns away, revealing the coughing, mercifully human forms of Bailey, Matt, Alex.... and the April Fool himself.\n\n`Mistiflying is saved!`\n\nThe April Fool has enough shame to look a bit sheepish. “Oh, hm,” he says. “Perhaps I got a little…. carried away.”\n\nThe crowd mutters. Sodden flowers wash up on sidewalks. Somewhere in the distance, a roof collapses with a spectacular splash.\n\n“Er, yes,” the April Fool says. “That is. What I meant to say was, I’m dreadfully sorry.” He heaves a sigh. “I suppose it can’t all be fun and games, after all. It might not hurt to focus occasionally. Maybe I’ll get a head start on next year’s pranking.”\n\nRedphoenix coughs meaningfully.\n\n“I mean, get a head start on this year’s spring cleaning!” the April Fool says. “Nothing to fear, I’ll have Habit City in spit-shape soon. Luckily nobody is better than I at dual-wielding mops.”\n\nEncouraged, the marching band starts up.\n\nIt isn’t long before all is back to normal in Habit City. Plus, now that the Be-Wilder has evaporated, the magical bees of Mistiflying bustle back to work, and soon the flowers are blooming and the city is floating once more.\n\nAs Habiticans cuddle the magical fuzzy bees, the April Fool’s eyes light up. “Oho, I’ve had a thought! Why don’t you all keep some of these fuzzy Bee Pets and Mounts? It’s a gift that perfectly symbolizes the balance between hard work and sweet rewards, if I’m going to get all boring and allegorical on you.” He winks. “Besides, they don’t have stingers! Fool’s honor.”",
- "questBewilderBossRageTitle": "Beguilement Strike",
- "questBewilderBossRageDescription": "When this gauge fills, The Be-Wilder will unleash its Beguilement Strike on Habitica!",
- "questBewilderDropBumblebeePet": "Bumblebee (Pet)",
- "questBewilderDropBumblebeeMount": "Bumblebee (Mount)",
- "questBewilderBossRageMarket": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nOh no! Despite our best efforts, we've gotten distracted by the Be-Wilder’s charming illusions and have forgotten to do some of our Dailies! With a cackling cry, the shining bird beats its wings, raising a swarm of mist around Alex the Merchant. When the fog clears, he has been possessed! “Have some free samples!” he shouts gleefully, and begins to hurl exploding eggs and potions at fleeing Habiticans. Not the most favorable of sales, to be sure.\n\nHurry! Let's stay focused on our Dailies to defeat this monster before it possesses someone else.",
- "questBewilderBossRageStables": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nAhh!!! Once again the Be-Wilder has dazzled us into neglecting our Dailies, and now it has attacked Matt the Beast Master! With a swirl of mist, Matt transforms into a terrifying winged creature, and all the pets and mounts howl sadly in their stables. Quickly, stay focused on your tasks to defeat this dastardly distraction!",
- "questBewilderBossRageBailey": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nLook out! In the middle of reporting the news, Bailey the Town Crier has been possessed by the Be-Wilder! She lets out an evil, uninformative screech as she rises into the air. Now how will we know what’s going on?\n\nDon't give up... we're so close to defeating this bothersome bird for once and for all!"
+ "questBewilderText": "המש-תומם",
+ "questBewilderNotes": "המסיבה מתחילה כמו כל אחת אחרת.
המתאבנים מצוינים, המוזיקה מניעה, ואפילו פילי הריקוד הפכו לעניין שבשגרה. האביטיקנים צוחקים ומשתובבים בין סידורי הפרחים שעולים על גדותיהם, שמחים להסחת דעת מן המשימות הפחות אהובות עליהם, והשוטה של אפריל מתערבל מביניהם, ובשקיקה עושה טריק משעשע פה וטוויסט הומוריסטי שם.
בזמן ששעון החול המיסטי מצביע על חצות, השוטה של אפריל מזנק לבמה כדי לשאת נאום.
״חברים! אויבים! מכרים סובלניים! השאילו לי את אוזניכם.\" הקהל מגחך, בזמן שאוזניים של בעלי חיים מזדקרות מתוך ראשיהם, והם מדגמנים את אביזריהם החדשים.
\"כפי שאתם יודעים,\" השוטה ממשיך, \"האשליות המבלבלות שלי בדרך כלל אורכות רק יום אחד. אבל אני שמח לבשר כי גיליתי קיצור דרך שיבטיח לנו כיף ללא הפסקה, מבלי שנצטרך להתמודד עם האחריות המציקה שלנו. האביטיקנים מקסימים, הכירו את החבר החדש הקסום שלי... המש-תובב!\"
למונס מחווירה לפתע, שומטת את המתאבנים שלה. \"חכו! אל תסמכו--\"
אבל פתאום ערפילים נשפכים לתוך החדר, נוצצים ועבים, והם מתערבלים סביב השוטה של אפריל, מתגבשים לנוצות מעוננות וצוואר שנמתח. הקהל נותר ללא מילים בזמן שציפור מפלצתית מופיעה בפניהם, כנפיה מנצנצות עם אשליות. היא פולטת צחוק חריקת בלמים נורא.
\"הא, זה כבר דורות מאז שהאביטיקנים היו טיפשים מספיק כדי להזמין אותי! כמה נפלא זה, להיות מוחשית שוב.
מזמזמות בטרור, דבורי הקסם של מיסטיפיינג בורחות מהעיר המרחפת, אשר שוקעת מהשמים. בזה אחר זה, פרחי האביב המבריקים קומלים להם.
\"ידידיי היקרים, מדוע אתם כל כך מזועזעים?\" קורא המש-תובב בקול עורבני, מכה בכנפיו. \"אין צורך לעמול עבור התגמולים שלכם יותר. אני פשוט אתן לכם את כל הדברים שאתם רוצים!\"
גשם של מטבעות נשפך מהשמים, בתנועת פטישים לתוך האדמה ובעוצמה ברוטלית, והקהל צורח ובורח למחסה. \"האם זו בדיחה?\" באקונזאור צועק, כשהזהב מתנפץ דרך חלונות ושובר לרסיסים רעפי גגות.
PainterProphet כורע בזמן שברקים מתפצחים מלמעלה, וכתמי ערפל מכסים את השמש. \"לא! הפעם, אני לא חושב שזו בדיחה!\"
במהירות, האביטיקנים, אל תתנו לאוייב העולמי הזה להסיח את דעתנו מן המטרות שלנו! הישארו ממוקדים על המשימות שאתם צריכים להשלים כדי שנוכל להציל את מיסטיפיינג - ובתקווה, גם את עצמנו.",
+ "questBewilderCompletion": "המש-תובב הובס!
עשינו זאת! המש-תובב פולט צעקה מייבבת בזמן שהוא מתפתל באוויר, משיל נוצות כמו גשם. לאט, בהדרגה, הוא מתפתל אל תוך ענן של ערפל נוצץ. כשהשמש שנחשפת שוב, מפלחת את הערפל שנשרף, חושפת את הדמויות האנושיות המשתעלות של ביילי, מאט, אלכס.... והשוטה של אפריל בכבודו ובעצמו. מיסטיפיינג ניצלה!
השוטה של אפריל מסמיק מבושה ונראה קצת נבוך. \"אה, אה,\" הוא אומר. \"אולי קצת.... נסחפתי.\"
הקהל ממלמל. פרחים רטובים שוטפים את המדרכות. אי שם במרחק, גג מתמוטט בקול נפץ.
\"ארר, כן,\" אומר השוטה של אפריל. \"זה. מה שהתכוונתי לומר הוא, שאני נורא מצטער.\" הוא נאנח אנחה. \"אני מניח שלא הכל יכול להיות רק כיף ומשחקים, אחרי הכל. אולי זה לא נורא להתמקד מדי פעם. אולי אני אתכונן מראש קצת למתיחה של שנה הבאה.\"
רד-פניקס משתעל בקול.
\"אני מתכוון, אתקדם עם ניקיון האביב של השנה!״ השוטה של אפריל אומר. \"אין מה לחשוש, אני אביא את עיר ההרגלים לניקיון מבריק בקרוב. למרבה המזל אף אחד לא יודע טוב ממני להשתמש במגב כפול.\"
מעודדת, להקת המארש מתחילה.
לא לוקח הרבה זמן לדברים לחזור לקדמותם בעיר ההרגלים. בנוסף, עתה כשהמש-תובב התאדה, הדבורים הקסומות של המולת מיסטיפיינג חוזרות לעבודה, ועד מהרה הפרחים פורחים והעיר צפה שוב.
כשהאביטיקנים מחבקים את הדבורים הקסומות, עיניו של השוטה של אפריל מתחילות להאיר. \"אוהו, יש לי רעיון! למה שלא תשמרו כמה חיות מחמד וחיות רכיבה מהדבורים המזמזמות האלה? זו מתנה שמסמלת בצורה יוצאת מן הכלל את האיזון בין עבודה קשה ותגמולים מתוקים, אם להיות משעמם ואלגורי.\" הוא קורץ. \"מלבד זאת, אין להן עוקצים! נשבע בכבודו של השוטה.\"",
+ "questBewilderCompletionChat": "`המש-תובב הובס!`\n\nעשינו זאת! המש-תובב פולט צעקה מייבבת בזמן שהוא מתפתל באוויר, משיל נוצות כמו גשם. לאט, בהדרגה, הוא מתפתל אל תוך ענן של ערפל נוצץ. כשהשמש שנחשפת שוב, מפלחת את הערפל שנשרף, חושפת את הדמויות האנושיות המשתעלות של ביילי, מאט, אלכס.... והשוטה של אפריל בכבודו ובעצמו.\n\nמיסטיפיינג ניצלה!\n\nהשוטה של אפריל מסמיק מבושה ונראה קצת נבוך. \"אה, אה,\" הוא אומר. \"אולי קצת.... נסחפתי.\"\n\nהקהל ממלמל. פרחים רטובים שוטפים את המדרכות. אי שם במרחק, גג מתמוטט בקול נפץ.\n\n\"ארר, כן,\" אומר השוטה של אפריל. \"זה. מה שהתכוונתי לומר הוא, שאני נורא מצטער.\" הוא נאנח אנחה. \"אני מניח שלא הכל יכול להיות רק כיף ומשחקים, אחרי הכל. אולי זה לא נורא להתמקד מדי פעם. אולי אני אתכונן מראש קצת למתיחה של שנה הבאה.\"\n\nרד-פניקס משתעל בקול.\n\n\"אני מתכוון, אתקדם עם ניקיון האביב של השנה!״ השוטה של אפריל אומר. \"אין מה לחשוש, אני אביא את עיר ההרגלים לניקיון מבריק בקרוב. למרבה המזל אף אחד לא יודע טוב ממני להשתמש במגב כפול.\"\n\nמעודדת, להקת המארש מתחילה.\n\nלא לוקח הרבה זמן לדברים לחזור לקדמותם בעיר ההרגלים. בנוסף, עתה כשהמש-תובב התאדה, הדבורים הקסומות של המולת מיסטיפיינג חוזרות לעבודה, ועד מהרה הפרחים פורחים והעיר צפה שוב.\n\nכשהאביטיקנים מחבקים את הדבורים הקסומות, עיניו של השוטה של אפריל מתחילות להאיר. \"אוהו, יש לי רעיון! למה שלא תשמרו כמה חיות מחמד וחיות רכיבה מהדבורים המזמזמות האלה? זו מתנה שמסמלת בצורה יוצאת מן הכלל את האיזון בין עבודה קשה ותגמולים מתוקים, אם להיות משעמם ואלגורי.\" הוא קורץ. \"מלבד זאת, אין להן עוקצים! נשבע בכבודו של השוטה.\"",
+ "questBewilderBossRageTitle": "מכת משתערמומיות",
+ "questBewilderBossRageDescription": "כאשר מתמלא המד, המש-תומם ישחרר את מכת המשתערמומיות על האביטיקה!",
+ "questBewilderDropBumblebeePet": "דבורה קסומה (חיית מחמד)",
+ "questBewilderDropBumblebeeMount": "דבורה קסומה (חיית רכיבה)",
+ "questBewilderBossRageMarket": "`המש-תומם משתמש במכת המשתערמומיות!`\n\nהו לא! למרות מיטב מאמצינו, דעתנו הוסחה על-ידי האשליות המקסימות של המש-תומם ושכחנו לעשות חלק מהמטלות היומיות שלנו! בצעקה וקרקורים, הציפור הזורחת מכה בכנפיה, מעלה נחיל של ערפל סביב אלכס הסוחר. כאשר הערפל מתפזר, הוא כבר אחוז דיבוק! \"הנה כמה דוגמיות בחינם!\" הוא צועק בחדווה, ומתחיל להטיח ביצים ושיקויים מתפוצצים לעבר האביטיקנים שנסים על נפשם. לא הדרך הכי טובה למכור, זה בטוח.\n\nמהר! בואו נישאר ממוקדים על המטלות היומיות שלנו כדי להביס את המפלצת הזאת לפני שהיא משתלטת על עוד מישהו.",
+ "questBewilderBossRageStables": "אהה!!! שוב המש-תומם סנוור אותנו להזניח את המטלות היומיות שלנו, ועכשיו הוא תקף את מאט אדון החיות! עם מערבולת של ערפל, מאט הופך ליצור מכונף ומפחיד, וכל חיות המחמד וחיות הרכיבה מייללות בעצב באורוות שלהן. בזריזות, הישארו ממוקדים במשימות שלכם כדי להביס את הסחת הדעת השפלה הזו!",
+ "questBewilderBossRageBailey": "`המשתובב משתמש במכת משתערמומיות!`\n\nתזהרי! באמצע הדיווח על החדשות, ביילי כרוזת העיר כבר נאחזה דיבוק על ידי המש-תובב! היא פולטת קול חריקה רע ולא אינפורמטיבי בזמן שהיא עולה לאוויר. עכשיו איך נדע מה קורה?\n\nאל תוותרו... אנחנו כל כך קרובים להביס את הציפור הטורדנית הזו פעם אחת ולתמיד!",
+ "questFalconText": "ציפורי הרצחיינות",
+ "questFalconNotes": "ערמת מטלות שעולה על גדותיה מטילה צל על הר האביטיקה. זה היה אמור להיות מקום לפיקניק ולהנאה מתחושה של הישג, עד שהמשימות המוזנחות יצאו מכלל שליטה. עכשיו זהו ביתן של ציפורי הרצחיינות האימתניות, יצורים רעים אשר מונעים מהאביטיקנים מלהשלים את המשימות שלהם!
\"זה קשה מדי!\" הן מקרקרות לעבר @JonArinbjorn ו @Onheiron. \"זה ייקח זמן רב מדי לעשות זאת! זה לא ישנה כלום אם תחכו עד מחר! למה אתם לא עושים משהו כיפי במקום?\"
לא עוד, אתם נשבעים. אתם תטפסו על הר המטלות האישי שלכם ותביסו את ציפורי הרצחיינות!",
+ "questFalconCompletion": "מרוצים מכך שסוף סוף ניצחתם את ציפורי הרצחיינות, אתם מתמקמים כדי להנות קצת מהנוף ומהמנוחה שכל כך מגיעה לכם.
״וואו!״ אומרת @Trogdorina. ״ניצחתם!״
@Squish מוסיף, ״הנה, קחו את הביצים שמצאתי בתור פרס.״",
+ "questFalconBoss": "ציפורי רצחיינות",
+ "questFalconDropFalconEgg": "פאלקון (ביצה)",
+ "questFalconUnlockText": "משחרר ביצי פאלקון לרכישה בשוק",
+ "questTreelingText": "עץ ההסתבכות",
+ "questTreelingNotes": "זוהי תחרות הגן השנתית, וכולם מדברים על הפרוייקט המסתורי ש @aurakami הבטיח לחשוף. אתם מצטרפים לקהל ביום ההכרזה, ומשתאים למראה עץ מתנועע. @fuzzytrees מסביר כיצד העץ יסייע בתחזוקת הגן, ומראה איך הוא יכול לכסח את הדשא, לקצץ את הגדר ולגזום את הורדים בעת ובעונה אחת - עד שלפתע העץ משתולל, ומפנה את מזמרותיו לעבר היוצר שלו! הקהל נכנס לפאניקה כשכולם בורחים מהמקום, אבל אתם לא מפחדים - מזנקים קדימה, ומכנים אלי קרב.",
+ "questTreelingCompletion": "You dust yourself off as the last few leaves drift to the floor. In spite of the upset, the Garden Competition is now safe – although the tree you just reduced to a heap of wood chips won't be winning any prizes! \"Still a few kinks to work out there,\" @PainterProphet says. \"Perhaps someone else would do a better job of training the saplings. Do you fancy a go?\"",
+ "questTreelingBoss": "Tangle Tree",
+ "questTreelingDropTreelingEgg": "Treeling (Egg)",
+ "questTreelingUnlockText": "Unlocks purchasable Treeling eggs in the Market"
}
\ No newline at end of file
diff --git a/common/locales/he/rebirth.json b/common/locales/he/rebirth.json
index 83ccf118e3..90bc61d293 100644
--- a/common/locales/he/rebirth.json
+++ b/common/locales/he/rebirth.json
@@ -5,7 +5,7 @@
"rebirthStartOver": "לידה מחדש מתחילה את הדמות שלכם מחדש ברמה 1.",
"rebirthAdvList1": "אתה חוזר לרפואה שלמה.",
"rebirthAdvList2": "אין לכם ניסיון, זהב, או ציוד (למעט חפצים חופשיים כמו חפצים מיסתוריים).",
- "rebirthAdvList3": "ההרגלים, המטלות היומיות והמשימות ארוכות הטווח שלך והפכו לצהובות, והרצפים שלך יתאפסו.",
+ "rebirthAdvList3": "Your Habits, Dailies, and To-Dos reset to yellow, and streaks reset, except for challenge tasks.",
"rebirthAdvList4": "המקצוע שלך יהיה לוחם/ת עד שתרוויח/י מקצוע חדש.",
"rebirthInherit": "הדמות החדשה שלך יורשת מספר דברים מהקודמת:",
"rebirthInList1": "מטלות, היסטוריה, והגדרות נשארים כשהיו.",
@@ -24,5 +24,6 @@
"rebirthPop": "התחיל/י דמות חדשה ברמה 1 עם אותם הישגים, חפצי אספנות, משימות והיסטוריה.",
"rebirthName": "כדור הלידה מחדש",
"reborn": "היוולדו מחדש. רמה מירבית: <%= reLevel %>",
- "confirmReborn": "האם אתם בטוחים?"
+ "confirmReborn": "האם אתם בטוחים?",
+ "rebirthComplete": "You have been reborn!"
}
\ No newline at end of file
diff --git a/common/locales/he/settings.json b/common/locales/he/settings.json
index 3185d6aed0..06ca87a73f 100644
--- a/common/locales/he/settings.json
+++ b/common/locales/he/settings.json
@@ -47,6 +47,7 @@
"customDayStart": "התחלת יום מותאמת אישית",
"changeCustomDayStart": "שנה את מועד תחילת היום?",
"sureChangeCustomDayStart": "האם אתם בטוחים שאתם מעוניינים לשנות את מועד תחילת היום?",
+ "customDayStartHasChanged": "Your custom day start has changed.",
"nextCron": "שמור מועד תחילת יום מותאם-אישית",
"customDayStartInfo1": "האביטיקה מכוונת לבדוק ולאפס את המטלות היומיות בחצות של אזור הזמן שלכם, מידי יום. תוכלו לשנות זאת כאן.",
"misc": "שונות",
@@ -61,12 +62,23 @@
"newUsername": "שם כניסה חדש",
"dangerZone": "אזור מסוכן",
"resetText1": "אזהרה! פעולה זו תמחוק חלקים רבים מהמשתמש שלך. זה ממש לא מומלץ כי תאבד/י מידע היסטורי, השימושי למעקב אחר ההתקדמות שלך לאורך זמן, אם כי, ישנם האנשים שזה שימושי עבורם אחרי שהם משחקים באתר זמן מה.",
- "resetText2": "את/ה תאבד/י את כל הדרגות, הזהב, ונקודות הניסיון שלך. כל המשימות שלך ימחקו לצמיתות יחד עם ההיסטוריה שלהן. את/ה תאבד/י את כל הציוד שלך אך תוכלי לקנות את כולו בחזרה, כולל את כל הציוד ממהדורה מוגבלת או חפצי תורם שכבר בבעלותך (תיאלצ/י להיות במקצוע הנכון בשביל לקנות מחדש ציוד מקצועי מתאים). את/ה תשמור/י על המקצוע שלך, חיות המחמד וחיות הרכיבה. אולי תעדיפ/י להשתמש בכדור הלידה מחדש במקום זאת, שהוא אפשרות הרבה יותר בטוחה שתשמר את המשימות שלך.",
+ "resetText2": "You will lose all your levels, gold, and experience points. All your tasks (except those from challenges) will be deleted permanently and you will lose all of their historical data. You will lose all your equipment but you will be able to buy it all back, including all limited edition equipment or subscriber Mystery items that you already own (you will need to be in the correct class to re-buy class-specific gear). You will keep your current class and your pets and mounts. You might prefer to use an Orb of Rebirth instead, which is a much safer option and which will preserve your tasks.",
"deleteText": "האם את/ה בטוח/ה? זה ימחק את המשתמש שלך לנצח, ולעולם לא תוכל/י להחזיר אותו! תיאלצ/י להיאשם בחשבון חדש לhabitRPG. אבני חן שנאגרו או בוזבזו לא יוחזרו לך. אם את/ה בטוח/ה ב100%, הקלד/י <%= deleteWord %> לתיבת הטקסט למטה.",
"API": "ממשק",
+ "APIv3": "API v3",
+ "APIv2": "API v2 - Deprecated",
"APIText": "העתיקו את השדות הללו לשימוש באפליקציות חיצוניות. עם זאת, חשבו על אסימון הממשק שלכם כמו על סיסמא, ואל תחלקו אותו בציבור. ייתכן שיבקשו מכם את זהות המשתמש שלכם במקום ציבורי, אבל לעולם לא את אסימון הממשק. כולל ב-github.",
"APIToken": "אסימון ממשק (זו סיסמא של ממש! ראו אזהרה למעלה!)",
+ "thirdPartyApps": "אפליקציות צד שלישי",
+ "dataToolDesc": "דף אינטרנט שמראה לכם מידע מסויים מחשבון ההאביטיקה שלכם, כגון סטטיסטיקות לגבי המשימות שלכם, ציוד, ותכונות.",
+ "beeminder": "דבוריזכורת",
+ "beeminderDesc": "הרשו לדבוריזכורת לנטר את המשימות שלכם בהאביטיקה. אתם יכולים להתחייב לסיים מספר מסויים של משימות ביום או בשבוע, או שאתם יכולים להתחייב להוריד בהדרגה את מספר המשימות שנותרו לכם. (על ידי התחייבות, הדבוריזכורת מתכוונת לאיום בתשלום של כסף אמיתי! אך אתם יכולים גם ״סתם״ לאהוב את הגרפים המיופיפים של הדבוריזכורת).",
+ "chromeChatExtension": "תוסף שיחה לכרום",
+ "chromeChatExtensionDesc": "תוסף השיחה של כרום להאביטיקה מוסיף חלון שיחה אינטואיטיבי לכל habitica.com. הוא מאפשר למשתמשים לשוחח בפונדק, בחבורה שלכם, ובכל גילדה אליה הם רשומים.",
+ "otherExtensions": "תוספים אחרים",
+ "otherDesc": "מצאו אפליקציות, הרחבות, וכלים נוספים בוויקי של האביטיקה.",
"resetDo": "עשה זאת! אתחל את החשבון שלי!",
+ "resetComplete": "Reset complete!",
"fixValues": "תקן ערכים",
"fixValuesText1": "אם נתקלת בבאג או עשית טעות ששינתה את דמותך באופן לא הוגן (נזק שלא היית צריך לקבל או זהב שלא באמת הרווחת וכיוב') ניתן לתקן את המספרים הללו באופן ידני כאן. כן, זה אומר שאפשר לרמות: השתמש/י בתכונה הזו בחכמה או שתחבל/י בבניית ההרגלים של עצמך!",
"fixValuesText2": "שימ/י לב שלא ניתן לשחזר רצפים על מטלות יחידות כאן. כדי לעשות זאת ערוך/ערכי את המטלה ולכ/י לאפשרויות מתקדמות, שם יהיה שדה המאפשר לשחזר רצף.",
@@ -96,6 +108,7 @@
"emailNotifications": "הודעות",
"wonChallenge": "זכית באתגר!",
"newPM": "קיבלת הודעה פרטית חדשה",
+ "sentGems": "Sent gems!",
"giftedGems": "אבני חן שזכיתם בהן",
"giftedGemsInfo": "<%= amount %> אבני חן - מאת <%= name %>",
"giftedSubscription": "מנוי שניתן במתנה",
@@ -135,6 +148,11 @@
"webhooks": "Webhooks",
"enabled": "מאופשר",
"webhookURL": "Webhook URL",
+ "invalidUrl": "invalid url",
+ "invalidEnabled": "the \"enabled\" parameter should be a boolean",
+ "regIdRequired": "RegId is required",
+ "pushDeviceAdded": "Push device added successfully",
+ "pushDeviceAlreadyAdded": "The user already has the push device",
"add": "הוסף",
"buyGemsGoldCap": "סף הועלה ל<%= amount %>",
"mysticHourglass": "<%= amount %> שעוני-חול מיסטיים",
@@ -149,5 +167,5 @@
"amazonPayments": "Amazon Payments",
"timezone": "אזור זמן",
"timezoneUTC": "האביטיקה משתמשת באזור הזמן של המחשב שלכם, שהוא: <%= utc %> ",
- "timezoneInfo": "אם אזור הזמן הזה לא נכון, ראשית - טענו מחדש את העמוד הזה באמצעות כפתור הטעינה מחדש של הדפדפן שלכם - כדי לוודא שהביטיקה עולה עם המידע העדכני ביותר. אם הוא עדיין שגוי, כוונו את אזור הזמן במחשב שלכם ונסו לטעון את הדף שוב.
אם אתם משתמשים בהביטיקה על מחשבים או מכשירים ניידים אחרים, אזור הזמן חייב להיות אותו הדבר על כולם. אם המטלות היומיות שלכם מתאפסות בזמן הלא נכון, חזרו על הפעולות האלו בכל המכשירים והדפדפנים בהם אתם משתמשים כדי לשחק בהביטיקה."
+ "timezoneInfo": "If that time zone is wrong, first reload this page using your browser's reload or refresh button to ensure that Habitica has the most recent information. If it is still wrong, adjust the time zone on your PC and then reload this page again.
If you use Habitica on other PCs or mobile devices, the time zone must be the same on them all. If your Dailies have been resetting at the wrong time, repeat this check on all other PCs and on a browser on your mobile devices."
}
\ No newline at end of file
diff --git a/common/locales/he/spells.json b/common/locales/he/spells.json
index 2a5d48c7d2..f855704a1e 100644
--- a/common/locales/he/spells.json
+++ b/common/locales/he/spells.json
@@ -35,16 +35,22 @@
"spellSpecialSnowballAuraNotes": "השליכו כדור שלג על החבר שלכם, מה כבר יקרה? נמשך עד היום הבא של החבר הפגוע.",
"spellSpecialSaltText": "מלח",
"spellSpecialSaltNotes": "מישהו/י זרק/ה עלייך כדור שלג. פפחחחחחח, זה קורע, משהו היסטרי. טוב, נו, בוא נוריד את השלג הזה ממך!",
- "spellSpecialSpookDustText": "נצנצים מלחיצים",
- "spellSpecialSpookDustNotes": "הפכו חבר/ה לשמיכה מעופפת עם עיניים!",
- "spellSpecialOpaquePotionText": "תרופה שקופה",
+ "spellSpecialSpookySparklesText": "Spooky Sparkles",
+ "spellSpecialSpookySparklesNotes": "Turn a friend into a floating blanket with eyes!",
+ "spellSpecialOpaquePotionText": "שיקוי עכור",
"spellSpecialOpaquePotionNotes": "מבטלת את השפעת הנצנצים המלחיצים.",
"spellSpecialShinySeedText": "זרע מנצנץ",
"spellSpecialShinySeedNotes": "הפכו חבר/ה לפרח מאושר!",
- "spellSpecialPetalFreePotionText": "תרופה ללא כותרת",
+ "spellSpecialPetalFreePotionText": "שיקוי ללא עלי כותרת",
"spellSpecialPetalFreePotionNotes": "מבטלת את השפעת הזרעים המנצנצים",
"spellSpecialSeafoamText": "קצף ים",
"spellSpecialSeafoamNotes": "הפוך חבר ליצור ים!",
"spellSpecialSandText": "חול",
- "spellSpecialSandNotes": "בטל את ההשפעה של קצף הים."
+ "spellSpecialSandNotes": "בטל את ההשפעה של קצף הים.",
+ "spellNotFound": "Skill \"<%= spellId %>\" not found.",
+ "partyNotFound": "Party not found",
+ "targetIdUUID": "\"targetId\" must be a valid User ID.",
+ "challengeTasksNoCast": "Casting a skill on challenge tasks is not allowed.",
+ "spellNotOwned": "You don't own this skill.",
+ "spellLevelTooHigh": "You must be level <%= level %> to use this skill."
}
\ No newline at end of file
diff --git a/common/locales/he/subscriber.json b/common/locales/he/subscriber.json
index 96bff0af20..9314b41574 100644
--- a/common/locales/he/subscriber.json
+++ b/common/locales/he/subscriber.json
@@ -4,7 +4,9 @@
"subDescription": "רכשו אבני חן עם זהב, קבלו חפצים חודשיים מסתוריים, שימרו על היסטוריית התקדמות, הכפילו את גבולות הנפילות היומיות, תמכו במפתחים. לחצו כאן למידע נוסף.",
"buyGemsGold": "קנה אבני חן עם זהב",
"buyGemsGoldText": "אלכסנדר הסוחר ימכור לכם אנבי חן תמורת <%= gemCost %> זהב עבור כל אבן חן. המשלוחים החודשיים שלו מוגבלים בתחילה ל <%= gemLimit %> אבני חן לחודש, אך גבול זה גדל ב 5 אבני חן בעבור כל 3 חודשי מנוי רצופים, עד למקסימום של 50 אבני חן בחודש!",
- "retainHistory": "שמר רשומות היסטוריה נוספות",
+ "mustSubscribeToPurchaseGems": "Must subscribe to purchase gems with GP",
+ "reachedGoldToGemCap": "You've reached the Gold=>Gem conversion cap <%= convCap %> for this month. We have this to prevent abuse / farming. The cap will reset within the first three days of next month.",
+ "retainHistory": "שמור רשומות היסטוריה נוספות",
"retainHistoryText": "גורם למטרות שהושלמו והיסטוריית משימות להיות זמינות זמן רב יותר.",
"doubleDrops": "הגבלת הביזה היומית מוכפלת",
"doubleDropsText": "כך תוכלו למלא את האורווה שלכם מהר יותר!",
@@ -15,7 +17,7 @@
"monthUSD": "דולר / חודש",
"organization": "ארגון",
"groupPlans": "תכניות עסקיות",
- "indivPlan1": "לאנשים פרטיים, habitRPG הוא משחק חינמי. אפילו עבור קבוצות מצומצמות הוא חימני (או זול)",
+ "indivPlan1": "לאנשים פרטיים, האביטיקה היא משחק חינמי. אפילו עבור קבוצות מצומצמות היא חימנית (או זולה)",
"indivPlan2": "ניתן לשימוש כדי לתת מוטיבציה לשינוי התנהגותי. חשבו על קבוצות כתיבה, אתגרי אומנות ועוד.",
"groupText1": "אבל מנהלי קבוצות מסויימים ירצו שליטה נוספת, פרטיות, אבטחה ותמיכה. למשל כאשר מדובר במשפחות, קבוצות ייעוץ בריאותי, ועדי עובדים ועוד. התכניות הללו מספקות גרסא פרטית של habitRPG עבור הקבוצה או הארגון שלכם, שהיא בטוחה ונפרדת",
"groupText2": "בדוק למטה עבור תכונות נוספות של תכניות, וצרו איתנו קשר למידע נוסף!",
@@ -29,10 +31,11 @@
"manageSub": "לחצ/י לניהול המינוי",
"cancelSub": "ביטול תרומה",
"canceledSubscription": "מנויים שבוטלו",
+ "cancelingSubscription": "Canceling the subscription",
"adminSub": "תרומת מנהלים",
"morePlans": "תוכניות נוספות בקרוב",
"organizationSub": "ארגון פרטי",
- "organizationSubText": "חברי הארגון משחקים מחוץ לhabitRPG עצמו, כדי לספק להם מיקוד.",
+ "organizationSubText": "חברי הארגון משחקים מחוץ להאביטיקה עצמה, כדי לספק להם מיקוד.",
"hostingType": "סוג אחסון",
"hostingTypeText": "אחסון משותף אומר שהחברה שלך משתמשת באותו מסד נתונים כמו habitRPG למרות שאין לכם אינטראקציה עם האביטיקה. אחסון ייעודי פירושו שיש לכם מסד נתונים ושרת משלכם. ניתן לבחור לאחסן את habitRPG על השרת או מסד הנתונים שלך, או שאנו נתקין אותו על השרתים שלנו.",
"dedicated": "ייעודי",
@@ -56,7 +59,7 @@
"sureCancelSub": "האם את/ה בטוח/ה שאת/ה רוצה לבטל את המינוי שלך?",
"subCanceled": "התרומה תפסיק לתפקד ב",
"buyGemsGoldTitle": "לקנות אבני חן בזהב",
- "becomeSubscriber": "הפוך למנוי",
+ "becomeSubscriber": "היפכו למנויים",
"subGemPop": "מכיוון שאתה תורם ל-Habitica, אתה יכול לקנות מספר אבני חן בכל חודש באמצעות זהב. אתה יכול לראות כמה אבני חן זמינים לך לקנייה בפינה של סמל אבן החן.",
"subGemName": "אבני חן של מנוי",
"freeGemsTitle": "קבל אבני חן בחינם",
@@ -73,6 +76,9 @@
"timeTravelersPopover": "אנחנו רואים שיש לכם שעון חול מיסטי, אז בשמחה נחזור בשבילכם בזמן! אנא בחרו חיית מחמד, חיית רכיבה, או סט עצמים מיסתורי שתחפצו בהם. תוכלו לראות רשימה של סטים מהעבר כאן! אם אלו לא מספקים אתכם, אולי תתעניינו באחד מהסטים העתידניים בסיגנון סטימפאנק?",
"timeTravelersAlreadyOwned": "ברכותינו! כבר יש ברשותך את כל מה שהנוסעים בזמן יכולים להציע כעת. רוב תודות על התמיכה באתר!",
"mysticHourglassPopover": "שעון חול מיסטי מאפשר לכם לרכוש עצמים מסויימים שמוגבלים בזמן, כגון סט עצמים מסטוריים ופרסים לאוייבי העולם, מהעבר!",
+ "mysterySetNotFound": "Mystery set not found, or set already owned.",
+ "mysteryItemIsEmpty": "Mystery items are empty",
+ "mysteryItemOpened": "Mystery item opened.",
"mysterySet201402": "סט שליח מכונף",
"mysterySet201403": "סט המהלך ביער",
"mysterySet201404": "סט פרפר הדמדומים",
@@ -99,6 +105,8 @@
"mysterySet201601": "סט אלוף של החלטיות",
"mysterySet201602": "סט שוברי לבבות",
"mysterySet201603": "סט תלתן מזל",
+ "mysterySet201604": "Leaf Warrior Set",
+ "mysterySet201605": "Marching Bard Set",
"mysterySet301404": "סט סטימפאנק רגיל",
"mysterySet301405": "סט סטימפאנק אקססוריז",
"mysterySetwondercon": "וונדרקון",
@@ -110,9 +118,25 @@
"hourglassBuyItemConfirm": "קנה חפץ זה תמורת 1 שעון חול מיסטי?",
"petsAlreadyOwned": "חיית המחמד כבר ברשותך.",
"mountsAlreadyOwned": "חיית הרכיבה כבר ברשותך.",
- "typeNotAllowedHourglass": "סוג החפץ לא ניתן לרכישה באמצעות שעון חול מיסטי. סוגים מותרים:",
+ "typeNotAllowedHourglass": "Item type not supported for purchase with Mystic Hourglass. Allowed types: <%= allowedTypes %>",
"petsNotAllowedHourglass": "חיית המחמד אינה זמינה לרכישה באמצעות שעון החול המיסטי.",
"mountsNotAllowedHourglass": "חיית הרכיבה אינה זמינה לרכישה באמצעות שעון החול המיסטי.",
"hourglassPurchase": "רכשת חפץ באמצעות שעון החול המיסטי!",
- "hourglassPurchaseSet": "רכשתם סט חפצים באמצעות שעון חול מיסטי!"
+ "hourglassPurchaseSet": "רכשתם סט חפצים באמצעות שעון חול מיסטי!",
+ "missingUnsubscriptionCode": "Missing unsubscription code.",
+ "missingSubscription": "User does not have a plan subscription",
+ "missingSubscriptionCode": "Missing subscription code. Possible values: basic_earned, basic_3mo, basic_6mo, google_6mo, basic_12mo.",
+ "cannotDeleteActiveAccount": "You have an active subscription, cancel your plan before deleting your account.",
+ "paymentNotSuccessful": "The payment was not successful",
+ "planNotActive": "The plan hasn't activated yet (due to a PayPal bug). It will begin <%= nextBillingDate %>, after which you can cancel to retain your full benefits",
+ "notAllowedHourglass": "Pet/Mount not available for purchase with Mystic Hourglass.",
+ "readCard": "<%= cardType %> has been read",
+ "cardTypeRequired": "Card type required",
+ "cardTypeNotAllowed": "Unknown card type.",
+ "invalidCoupon": "Invalid coupon code.",
+ "couponUsed": "Coupon code already used.",
+ "noSudoAccess": "You don't have sudo access.",
+ "couponCodeRequired": "The coupon code is required.",
+ "eventRequired": "\"req.params.event\" is required.",
+ "countRequired": "\"req.query.count\" is required."
}
\ No newline at end of file
diff --git a/common/locales/he/tasks.json b/common/locales/he/tasks.json
index b037512ce4..4c248259e9 100644
--- a/common/locales/he/tasks.json
+++ b/common/locales/he/tasks.json
@@ -73,6 +73,7 @@
"clearTags": "נקה",
"hideTags": "הסתר",
"showTags": "הראה",
+ "toRequired": "You must supply a to value",
"startDate": "תאריך התחלה",
"startDateHelpTitle": "מתי כדאי שמשימה תתחיל?",
"startDateHelp": "קבעו תאריך שבו משימה זו נכנסת לתוקף. משימה זו לא תחול בימים מוקדמים יותר.",
@@ -88,8 +89,9 @@
"fortifyName": "שיקוי תגבור",
"fortifyPop": "מחזיר את כל המשימות לערכן ההתחלתי (צבע צהוב) ומחזיר את כל הבריאות שאיבדת.",
"fortify": "תגבור",
- "fortifyText": "״ביצור״ יחזיר את כל המשימות שלכם למצב ניטראלי (צהובות), כאילו רק הוספתם אותן, וימלא את הבריאות שלכם עד הסוף. זה נהדר אם כל המשימות האדומות שלכם הופכות את המשחק לקשה מידי, או כל המשימות הכחולות הופכות אותו לקל מידי. אם להתחיל מחדש נשמע הרבה יותר טוב, השתמשו באבני החן ותפסו אוויר לנשימה!",
+ "fortifyText": "Fortify will return all your tasks, except challenge tasks, to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!",
"confirmFortify": "האם אתה בטוח?",
+ "fortifyComplete": "Fortify complete!",
"sureDelete": "האם אתם בטוחים שאתם מעוניינים למחוק את ה<%= taskType %> עם הטקסט ״<%= taskText %>״?",
"streakCoins": "בונוס התמדה!",
"pushTaskToTop": "העלו משימות לראש הרשימה. החזיקו את כפתור ה ctrl או cmd כדי להוריד אותן לתחתית.",
@@ -112,5 +114,18 @@
"rewardHelp2": "ציוד משפיע על התכונות שלכם (<%= linkStart %>משתמש > תכונות והישגים<%= linkEnd %>).",
"rewardHelp3": "ציוד מיוחד יופיע כאן בזמן ״אירועים עולמיים״.",
"rewardHelp4": "אל תחשושו להכין פרסים מותאמים אישית! ראו כמה דוגמאות כאן.",
- "clickForHelp": "לחצו לעזרה"
+ "clickForHelp": "לחצו לעזרה",
+ "taskIdRequired": "\"taskId\" must be a valid UUID.",
+ "taskNotFound": "Task not found.",
+ "invalidTaskType": "Task type must be one of \"habit\", \"daily\", \"todo\", \"reward\".",
+ "cantDeleteChallengeTasks": "A task belonging to a challenge can't be deleted.",
+ "checklistOnlyDailyTodo": "Checklists are supported only on dailies and todos",
+ "checklistItemNotFound": "No checklist item was found with given id.",
+ "itemIdRequired": "\"itemId\" must be a valid UUID.",
+ "tagNotFound": "No tag item was found with given id.",
+ "tagIdRequired": "\"tagId\" must be a valid UUID corresponding to a tag belonging to the user.",
+ "positionRequired": "\"position\" is required and must be a number.",
+ "cantMoveCompletedTodo": "Can't move a completed todo.",
+ "directionUpDown": "\"direction\" is required and must be 'up' or 'down'",
+ "alreadyTagged": "The task is already tagged with given tag."
}
\ No newline at end of file
diff --git a/common/locales/hu/backgrounds.json b/common/locales/hu/backgrounds.json
index 794c32ae70..b5ad4ab01c 100644
--- a/common/locales/hu/backgrounds.json
+++ b/common/locales/hu/backgrounds.json
@@ -160,5 +160,12 @@
"backgroundGiantFlowersText": "Giant Flowers",
"backgroundGiantFlowersNotes": "Frolic atop Giant Flowers.",
"backgroundRainbowsEndText": "End of the Rainbow",
- "backgroundRainbowsEndNotes": "Discover gold at the End of the Rainbow."
+ "backgroundRainbowsEndNotes": "Discover gold at the End of the Rainbow.",
+ "backgrounds052016": "SET 24: Released May 2016",
+ "backgroundBeehiveText": "Beehive",
+ "backgroundBeehiveNotes": "Buzz and dance in a Beehive.",
+ "backgroundGazeboText": "Gazebo",
+ "backgroundGazeboNotes": "Battle a Gazebo.",
+ "backgroundTreeRootsText": "Tree Roots",
+ "backgroundTreeRootsNotes": "Explore the Tree Roots."
}
\ No newline at end of file
diff --git a/common/locales/hu/challenge.json b/common/locales/hu/challenge.json
index 8de4ec9329..f7b3d3ef93 100644
--- a/common/locales/hu/challenge.json
+++ b/common/locales/hu/challenge.json
@@ -63,5 +63,20 @@
"congratulations": "Gratulálok!",
"hurray": "Hurrá!",
"noChallengeOwner": "nincs tulajdonosa",
- "noChallengeOwnerPopover": "Ennek a kihívásnak nincs tulajdonosa, mert a személy aki létrehozta a kihívást, törölte fiókját."
+ "noChallengeOwnerPopover": "Ennek a kihívásnak nincs tulajdonosa, mert a személy aki létrehozta a kihívást, törölte fiókját.",
+ "challengeMemberNotFound": "User not found among challenge's members",
+ "onlyGroupLeaderChal": "Only the group leader can create challenges",
+ "tavChalsMinPrize": "Prize must be at least 1 Gem for Tavern challenges.",
+ "cantAfford": "You can't afford this prize. Purchase more gems or lower the prize amount.",
+ "challengeIdRequired": "\"challengeId\" must be a valid UUID.",
+ "winnerIdRequired": "\"winnerId\" must be a valid UUID.",
+ "challengeNotFound": "Challenge not found.",
+ "onlyLeaderDeleteChal": "Only the challenge leader can delete it.",
+ "onlyLeaderUpdateChal": "Only the challenge leader can update it.",
+ "winnerNotFound": "Winner with id \"<%= userId %>\" not found or not part of the challenge.",
+ "noCompletedTodosChallenge": "\"includeComepletedTodos\" is not supported when fetching a challenge tasks.",
+ "userTasksNoChallengeId": "When \"tasksOwner\" is \"user\" \"challengeId\" can't be passed.",
+ "onlyChalLeaderEditTasks": "Tasks belonging to a challenge can only be edited by the leader.",
+ "userAlreadyInChallenge": "User is already participating in this challenge.",
+ "cantOnlyUnlinkChalTask": "Only broken challenges tasks can be unlinked."
}
\ No newline at end of file
diff --git a/common/locales/hu/character.json b/common/locales/hu/character.json
index 744796bb9a..931b7644c4 100644
--- a/common/locales/hu/character.json
+++ b/common/locales/hu/character.json
@@ -1,4 +1,5 @@
{
+ "communityGuidelinesWarning": "Please keep in mind that your Display Name, profile photo, and blurb must comply with the Community Guidelines (e.g. no profanity, no adult topics, no insults, etc). If you have any questions about whether or not something is appropriate, feel free to email leslie@habitica.com!",
"statsAch": "Karakterlap és kitűntetések",
"profile": "Profil",
"avatar": "Customize Avatar",
@@ -109,6 +110,7 @@
"mage": "Mágus",
"mystery": "Rejtély",
"changeClass": "Kaszt megváltoztatása, tulajdonság pontok újraosztása",
+ "lvl10ChangeClass": "To change class you must be at least level 10.",
"levelPopover": "Minden szinttel kapsz egy tulajdonság pontot, amit kioszthatsz. Ezt megteheted kézzel, vagy hagyhatod, hogy a játék eldöntse helyetted, ha az egyik Automatikus Elosztás opciót használod.",
"unallocated": "Kiosztatlan tulajdonság pontok",
"haveUnallocated": "<%= points %> elkölthető tulajdonság pontod maradt",
@@ -164,5 +166,7 @@
"int": "INT",
"showQuickAllocation": "Show stat allocation",
"hideQuickAllocation": "Hide stat allocation",
- "quickAllocationLevelPopover": "Each level earns you one point to assign to an attribute of your choice. You can do so manually, or let the game decide for you using one of the Automatic Allocation options found in User -> Stats."
+ "quickAllocationLevelPopover": "Each level earns you one point to assign to an attribute of your choice. You can do so manually, or let the game decide for you using one of the Automatic Allocation options found in User -> Stats.",
+ "invalidAttribute": "\"<%= attr %>\" is not a valid attribute.",
+ "notEnoughAttrPoints": "You don't have enough attribute points."
}
\ No newline at end of file
diff --git a/common/locales/hu/content.json b/common/locales/hu/content.json
index bdf692aa35..faebe1458f 100644
--- a/common/locales/hu/content.json
+++ b/common/locales/hu/content.json
@@ -113,6 +113,12 @@
"questEggSnailText": "Snail",
"questEggSnailMountText": "Snail",
"questEggSnailAdjective": "a slow but steady",
+ "questEggFalconText": "Falcon",
+ "questEggFalconMountText": "Falcon",
+ "questEggFalconAdjective": "a swift",
+ "questEggTreelingText": "Treeling",
+ "questEggTreelingMountText": "Treeling",
+ "questEggTreelingAdjective": "a leafy",
"eggNotes": "Find a hatching potion to pour on this egg, and it will hatch into <%= eggAdjective(locale) %> <%= eggText(locale) %>.",
"hatchingPotionBase": "Alap",
"hatchingPotionWhite": "Fehér",
@@ -126,6 +132,7 @@
"hatchingPotionGolden": "Arany",
"hatchingPotionSpooky": "Spooky",
"hatchingPotionPeppermint": "Borsmenta",
+ "hatchingPotionFloral": "Floral",
"hatchingPotionNotes": "Öntsd ezt egy tojásra, és egy <%= potText(locale) %> háziállat fog belőle kikelni.",
"premiumPotionAddlNotes": "Not usable on quest pet eggs.",
"foodMeat": "Hús",
diff --git a/common/locales/hu/contrib.json b/common/locales/hu/contrib.json
index 9d4408f7ed..b7e63a83ff 100644
--- a/common/locales/hu/contrib.json
+++ b/common/locales/hu/contrib.json
@@ -35,8 +35,12 @@
"hallContributors": "Hall of Contributors",
"hallPatrons": "Mecénások Csarnoka",
"rewardUser": "Felhasználói jutalmak",
- "UUID": "Egyedi azonosító",
+ "UUID": "User ID",
"loadUser": "Felhasználó betöltése",
+ "noAdminAccess": "You don't have admin access.",
+ "pageMustBeNumber": "req.query.page must be a number",
+ "userNotFound": "User not found.",
+ "invalidUUID": "UUID must be valid",
"title": "Rang",
"moreDetails": "További részletek(1-7)",
"moreDetails2": "További részletek(8-9)",
@@ -52,8 +56,8 @@
"visitHeroes": "Látogasd meg a Hősök Csarnokát (közreműködők és támogatók)",
"conLearn": "Tudj meg többet a támogatói jutalmakról",
"conLearnHow": "Ismerd meg hogyan tudod támogatni a Habitica-t",
- "surveysSingle": "Egy kérdőív kitöltésével segítette a Habitica-t. Jelenleg nincs aktív kérdőív.",
- "surveysMultiple": "A <%= surveys %> kérdőív kitöltésével segítette a Habitica-t. Jelenleg nincs aktív kérdőív.",
+ "surveysSingle": "Helped Habitica grow, either by filling out a survey or helping with a major testing effort. Thank you!",
+ "surveysMultiple": "Helped Habitica grow on <%= surveys %> occasions, either by filling out a survey or helping with a major testing effort. Thank you!",
"currentSurvey": "Jelenlegi kérdőív",
"surveyWhen": "A kitűzőt minden résztvevő megkapja, amint a kérdőívek feldolgozásra kerültek; késő márciusban.",
"blurbInbox": "This is where your private messages are stored! You can send someone a message by clicking on the envelope icon next to their name in Tavern, Party, or Guild Chat. If you've received an inappropriate PM, you should email a screenshot of it to Lemoness (leslie@habitica.com)",
diff --git a/common/locales/hu/death.json b/common/locales/hu/death.json
index 1cbbccf7c1..ff7b70dc7d 100644
--- a/common/locales/hu/death.json
+++ b/common/locales/hu/death.json
@@ -12,5 +12,6 @@
"losingHealthQuickly": "Gyorsan fogy az életerőd?",
"lowHealthTips3": "A kihagyott napi feladatok az éjszaka folyamán megsebeznek téged, vigyázz, hogy ne adj túl sok feladatot hozzá az elején!",
"lowHealthTips4": "Ha egy napi feladatot nem kell bizonyos napokon elvégezned, kikapcsolhatod őket, a ceruza ikonra kattintva.",
- "goodLuck": "Sok szerencsét!"
+ "goodLuck": "Sok szerencsét!",
+ "cannotRevive": "Cannot revive if not dead"
}
\ No newline at end of file
diff --git a/common/locales/hu/front.json b/common/locales/hu/front.json
index 6da9f4bb78..222acf1337 100644
--- a/common/locales/hu/front.json
+++ b/common/locales/hu/front.json
@@ -28,6 +28,7 @@
"communityReddit": "Reddit",
"companyAbout": "Hogyan működik",
"companyBlog": "Blog",
+ "devBlog": "Developer Blog",
"companyDonate": "Adományozás",
"companyExtensions": "Kiegészítők",
"companyPrivacy": "Titoktartás",
@@ -51,6 +52,7 @@
"featureSocialHeading": "Közösségi élmény",
"featuredIn": "Megjelent",
"featuresHeading": "További lehetőségek...",
+ "footerDevs": "Developers",
"footerCommunity": "Közösség",
"footerCompany": "Cég",
"footerMobile": "Mobil",
@@ -182,6 +184,7 @@
"zelahQuote": "A [Habiticával] elértem, hogy időben feküdjek le, mert jutalmat kapok a korai lefekvésért és bosszant a gondolat, hogy ha későn fekszem le, életet vesztek!",
"reportAccountProblems": "Fiók problémák jelentése",
"reportCommunityIssues": "Közösségi problémák jelentése",
+ "subscriptionPaymentIssues": "Subscription and Payment Issues",
"generalQuestionsSite": "Általános kérdések az oldalról",
"businessInquiries": "Üzleti információk",
"merchandiseInquiries": "Termék információk",
@@ -222,5 +225,32 @@
"altAttrWebstorm": "WebStorm",
"altAttrGithub": "GitHub",
"altAttrTrello": "Trello",
- "altAttrSlack": "Slack"
+ "altAttrSlack": "Slack",
+ "missingAuthHeaders": "Missing authentication headers.",
+ "missingAuthParams": "Missing authentication parameters.",
+ "missingUsernameEmail": "Missing username or email.",
+ "missingEmail": "Missing email.",
+ "missingUsername": "Missing username.",
+ "missingPassword": "Missing password.",
+ "missingNewPassword": "Missing new password.",
+ "wrongPassword": "Wrong password.",
+ "notAnEmail": "Invalid email address.",
+ "emailTaken": "Email address is already used in an account.",
+ "newEmailRequired": "Missing new email address.",
+ "usernameTaken": "Username already taken.",
+ "passwordConfirmationMatch": "Password confirmation doesn't match password.",
+ "invalidLoginCredentials": "Incorrect username and/or email and/or password.",
+ "passwordReset": "If we have your email on file, your password reset link has been sent to your email.",
+ "passwordResetEmailSubject": "Password Reset for Habitica",
+ "passwordResetEmailText": "Password for <%= username %> has been reset to <%= newPassword %> . Important! Both username and password are case-sensitive -- you must enter both exactly as shown here. We recommend copying and pasting both instead of typing them. Log in at <%= baseUrl %>. After you have logged in, head to <%= baseUrl %>/#/options/settings/settings and change your password.",
+ "passwordResetEmailHtml": "Password for <%= username %> has been reset to <%= newPassword %>.
Important! Both username and password are case-sensitive -- you must enter both exactly as shown here. We recommend copying and pasting both instead of typing them.
Log in at <%= baseUrl %>. After you have logged in, head to <%= baseUrl %>/#/options/settings/settings and change your password.",
+ "invalidLoginCredentialsLong": "Uh-oh - your username or password is incorrect.\n- Make sure your username or email is typed correctly.\n- You may have signed up with Facebook, not email. Double-check by trying Facebook login.\n- If you forgot your password, click \"Forgot Password\".",
+ "invalidCredentials": "There is no account that uses those credentials.",
+ "accountSuspended": "Account has been suspended, please contact leslie@habitica.com with your User ID \"<%= userId %>\" for assistance.",
+ "onlyFbSupported": "Only Facebook is supported currently.",
+ "cantDetachFb": "Account lacks another authentication method, can't detach Facebook.",
+ "onlySocialAttachLocal": "Local authentication can be added to only a social account.",
+ "invalidReqParams": "Invalid request parameters.",
+ "memberIdRequired": "\"member\" must be a valid UUID.",
+ "heroIdRequired": "\"heroId\" must be a valid UUID."
}
\ No newline at end of file
diff --git a/common/locales/hu/gear.json b/common/locales/hu/gear.json
index 9931cb83cf..f3a25506f1 100644
--- a/common/locales/hu/gear.json
+++ b/common/locales/hu/gear.json
@@ -189,6 +189,8 @@
"weaponArmoireMiningPickaxNotes": "Mine the maximum amount of gold from your tasks! Increases Perception by <%= per %>. Enchanted Armoire: Miner Set (Item 3 of 3).",
"weaponArmoireBasicLongbowText": "Basic Longbow",
"weaponArmoireBasicLongbowNotes": "A serviceable hand-me-down bow. Increases Strength by <%= str %>. Enchanted Armoire: Basic Archer Set (Item 1 of 3).",
+ "weaponArmoireHabiticanDiplomaText": "Habitican Diploma",
+ "weaponArmoireHabiticanDiplomaNotes": "A certificate of significant achievement -- well done! Increases Intelligence by <%= int %>. Enchanted Armoire: Graduate Set (Item 1 of 3).",
"armor": "páncél",
"armorBase0Text": "Egyszerű ruházat",
"armorBase0Notes": "Átlagos ruházat. Nem ad semmi előnyt.",
@@ -364,6 +366,10 @@
"armorMystery201512Notes": "Summon the icy flames of winter! Confers no benefit. December 2015 Subscriber Item.",
"armorMystery201603Text": "Lucky Suit",
"armorMystery201603Notes": "This suit is sewn from thousands of four-leafed clovers! Confers no benefit. March 2016 Subscriber Item.",
+ "armorMystery201604Text": "Armor o' Leaves",
+ "armorMystery201604Notes": "You, too, can be a small but fearsome leaf puff. Confers no benefit. April 2016 Subscriber Item.",
+ "armorMystery201605Text": "Marching Bard Uniform",
+ "armorMystery201605Notes": "Unlike the traditional bards who join adventuring parties, bards who join Habitican marching bands are known for grand parades, not dungeon raids. Confers no benefit. May 2016 Subscriber Item.",
"armorMystery301404Text": "Steampunk öltözet",
"armorMystery301404Notes": "Jól vasalt és lenyűgöző, mi! Nem ad semmi előnyt. 3015 februári előfizetői tárgy.",
"armorArmoireLunarArmorText": "Soothing Lunar Armor",
@@ -394,6 +400,8 @@
"armorArmoireMinerOverallsNotes": "They may seem worn, but they are enchanted to repel dirt. Increases Constitution by <%= con %>. Enchanted Armoire: Miner Set (Item 2 of 3).",
"armorArmoireBasicArcherArmorText": "Basic Archer Armor",
"armorArmoireBasicArcherArmorNotes": "This camouflaged vest lets you slip unnoticed through the forests. Increases Perception by <%= per %>. Enchanted Armoire: Basic Archer Set (Item 2 of 3).",
+ "armorArmoireGraduateRobeText": "Graduate Robe",
+ "armorArmoireGraduateRobeNotes": "Congratulations! This weighty robe hangs heavy with all the knowledge you have accrued. Increases Intelligence by <%= int %>. Enchanted Armoire: Graduate Set (Item 2 of 3).",
"headgear": "Fejviselet",
"headBase0Text": "Nincs sisak",
"headBase0Notes": "Nincs fejfedő",
@@ -565,6 +573,10 @@
"headMystery201602Notes": "Shield your identity from all your admirers. Confers no benefit. February 2016 Subscriber Item.",
"headMystery201603Text": "Lucky Hat",
"headMystery201603Notes": "This top hat is a magical good-luck charm. Confers no benefit. March 2016 Subscriber Item.",
+ "headMystery201604Text": "Crown o' Flowers",
+ "headMystery201604Notes": "These woven flowers make a surprisingly strong helm! Confers no benefit. April 2016 Subscriber Item.",
+ "headMystery201605Text": "Marching Bard Hat",
+ "headMystery201605Notes": "Seventy-six dragons led the big parade, with a hundred and ten gryphons close at hand! Confers no benefit. May 2016 Subscriber Item.",
"headMystery301404Text": "Elegáns Cilinder",
"headMystery301404Notes": "Egy elegáns cilinder a legnemesebb előkelőségeknek! 3015 januári előfizetői tárgy. Nem ad semmi előnyt.",
"headMystery301405Text": "Egyszerű Cilinder",
@@ -613,6 +625,8 @@
"headArmoireMinerHelmetNotes": "Protect your head from falling tasks! Increases Intelligence by <%= int %>. Enchanted Armoire: Miner Set (Item 1 of 3).",
"headArmoireBasicArcherCapText": "Basic Archer Cap",
"headArmoireBasicArcherCapNotes": "No archer would be complete without a jaunty cap! Increases Perception by <%= per %>. Enchanted Armoire: Basic Archer Set (Item 3 of 3).",
+ "headArmoireGraduateCapText": "Graduate Cap",
+ "headArmoireGraduateCapNotes": "Congratulations! Your deep thoughts have earned you this thinking cap. Increases Intelligence by <%= int %>. Enchanted Armoire: Graduate Set (Item 3 of 3).",
"offhand": "Balkezes tárgy",
"shieldBase0Text": "Nincs balkezes felszerelés",
"shieldBase0Notes": "Nincs pajzs vagy másodlagos fegyver.",
@@ -673,7 +687,7 @@
"shieldSpecialWinter2015HealerText": "Csillapító Pajzs",
"shieldSpecialWinter2015HealerNotes": "Ez a pajzs eltéríti a fagyos szeleket. Növeli a szervezettséget <%= con %> ponttal. Korlátozott Példányszámú 2014-2015-ös Téli felszerelés.",
"shieldSpecialSpring2015RogueText": "Exploding Squeak",
- "shieldSpecialSpring2015RogueNotes": "Don't let the sound fool you - these explosives pack a punch. Increases Strength <%= str %>. Limited Edition 2015 Spring Gear.",
+ "shieldSpecialSpring2015RogueNotes": "Don't let the sound fool you - these explosives pack a punch. Increases Strength by <%= str %>. Limited Edition 2015 Spring Gear.",
"shieldSpecialSpring2015WarriorText": "Dish Discus",
"shieldSpecialSpring2015WarriorNotes": "Hurl it at your enemies.... or just hold it, because it will fill up with yummy kibble at dinnertime. Increases Constitution by <%= con %>. Limited Edition 2015 Spring Gear.",
"shieldSpecialSpring2015HealerText": "Patterned Pillow",
@@ -716,6 +730,8 @@
"shieldArmoireDragonTamerShieldNotes": "Distract enemies with this dragon-shaped shield. Increases Perception by <%= per %>. Enchanted Armoire: Dragon Tamer Set (Item 2 of 3).",
"shieldArmoireMysticLampText": "Mystic Lamp",
"shieldArmoireMysticLampNotes": "Light the darkest caves with this mystic lamp! Increases Perception by <%= per %>. Enchanted Armoire: Independent Item.",
+ "shieldArmoireFloralBouquetText": "Bouquet o' Flowers",
+ "shieldArmoireFloralBouquetNotes": "Not much help in battle, but aren't they beautiful? Increases Constitution by <%= con %>. Enchanted Armoire: Independent Item.",
"back": "Back Accessory",
"backBase0Text": "Nincs háti kiegészítő",
"backBase0Notes": "Nincs háti kiegészítő.",
@@ -820,6 +836,20 @@
"eyewear": "Eyewear",
"eyewearBase0Text": "Nincs szemviselet.",
"eyewearBase0Notes": "Nincs szemviselet.",
+ "eyewearSpecialBlackTopFrameText": "Black Standard Eyeglasses",
+ "eyewearSpecialBlackTopFrameNotes": "Glasses with a black frame above the lenses. Confers no benefit.",
+ "eyewearSpecialBlueTopFrameText": "Blue Standard Eyeglasses",
+ "eyewearSpecialBlueTopFrameNotes": "Glasses with a blue frame above the lenses. Confers no benefit.",
+ "eyewearSpecialGreenTopFrameText": "Green Standard Eyeglasses",
+ "eyewearSpecialGreenTopFrameNotes": "Glasses with a green frame above the lenses. Confers no benefit.",
+ "eyewearSpecialPinkTopFrameText": "Pink Standard Eyeglasses",
+ "eyewearSpecialPinkTopFrameNotes": "Glasses with a pink frame above the lenses. Confers no benefit.",
+ "eyewearSpecialRedTopFrameText": "Red Standard Eyeglasses",
+ "eyewearSpecialRedTopFrameNotes": "Glasses with a red frame above the lenses. Confers no benefit.",
+ "eyewearSpecialWhiteTopFrameText": "White Standard Eyeglasses",
+ "eyewearSpecialWhiteTopFrameNotes": "Glasses with a white frame above the lenses. Confers no benefit.",
+ "eyewearSpecialYellowTopFrameText": "Yellow Standard Eyeglasses",
+ "eyewearSpecialYellowTopFrameNotes": "Glasses with a yellow frame above the lenses. Confers no benefit.",
"eyewearSpecialSummerRogueText": "Huncut szemkötő",
"eyewearSpecialSummerRogueNotes": "Nem kell csibésznek lenned, hogy meglásd ez mennyire stílusos. Nem ad semmi előnyt. Korlátozott Példányszámú 2014 Nyári Felszerelés.",
"eyewearSpecialSummerWarriorText": "Káprázatos Szemkötő",
diff --git a/common/locales/hu/groups.json b/common/locales/hu/groups.json
index e95a3c9a0d..0125fe3b98 100644
--- a/common/locales/hu/groups.json
+++ b/common/locales/hu/groups.json
@@ -92,6 +92,7 @@
"send": "Küldés",
"messageSentAlert": "Üzenet elküldve",
"pmHeading": "Privát üzenet neki: <%= name %>",
+ "pmsMarkedRead": "Your private messages have been marked as read",
"clearAll": "Összes üzenet törlése",
"confirmDeleteAllMessages": "Biztos, hogy ki akarod törölni az üzeneteidet? A többi felhasználó még mindig látni fogja az üzeneteket, amiket küldtél nekik.",
"optOutPopover": "Nem szereted a privát üzeneteket? Kattints, hogy teljesen kiszállj belőle",
@@ -99,6 +100,15 @@
"unblock": "Letiltás feloldása",
"pm-reply": "Válasz küldése",
"inbox": "Bejövő üzenetek",
+ "messageRequired": "A message is required.",
+ "toUserIDRequired": "A User ID is required",
+ "gemAmountRequired": "A number of gems is required",
+ "notAuthorizedToSendMessageToThisUser": "Can't send message to this user.",
+ "privateMessageGiftIntro": "Hello <%= receiverName %>, <%= senderName %> has sent you",
+ "privateMessageGiftGemsMessage": "<%= gemAmount %> gems!",
+ "privateMessageGiftSubscriptionMessage": "<%= numberOfMonths %> months of subscription!",
+ "cannotSendGemsToYourself": "Cannot send gems to yourself. Try a subscription instead.",
+ "badAmountOfGemsToSend": "Amount must be within 1 and your current number of gems.",
"abuseFlag": "Jelentsd a Közösségi Irányelvek megsértését",
"abuseFlagModalHeading": "Jelented <%= name %>-t a megsértésért?",
"abuseFlagModalBody": "Are you sure you want to report this post? You should ONLY report a post that violates the <%= firstLinkStart %>Community Guidelines<%= linkEnd %> and/or <%= secondLinkStart %>Terms of Service<%= linkEnd %>. Inappropriately reporting a post is a violation of the Community Guidelines and may give you an infraction. Appropriate reasons to flag a post include but are not limited to:
swearing, religous oaths
bigotry, slurs
adult topics
violence, including as a joke
spam, nonsensical messages
",
@@ -151,5 +161,29 @@
"partyUpName": "Party Up",
"partyOnName": "Party On",
"partyUpAchievement": "Joined a Party with another person! Have fun battling monsters and supporting each other.",
- "partyOnAchievement": "Joined a Party with at least four people! Enjoy your increased accountability as you unite with your friends to vanquish your foes!"
+ "partyOnAchievement": "Joined a Party with at least four people! Enjoy your increased accountability as you unite with your friends to vanquish your foes!",
+ "groupIdRequired": "\"groupId\" must be a valid UUID",
+ "groupNotFound": "Group not found.",
+ "groupTypesRequired": "You must supply a valid \"type\" query string.",
+ "questLeaderCannotLeaveGroup": "You cannot leave your party when you have started a quest. Abort the quest first.",
+ "cannotLeaveWhileActiveQuest": "You cannot leave party during an active quest. Please leave the quest first.",
+ "onlyLeaderCanRemoveMember": "Only group leader can remove a member!",
+ "memberCannotRemoveYourself": "You cannot remove yourself!",
+ "groupMemberNotFound": "User not found among group's members",
+ "mustBeGroupMember": "Must be member of the group.",
+ "keepOrRemoveAll": "req.query.keep must be either \"keep-all\" or \"remove-all\"",
+ "keepOrRemove": "req.query.keep must be either \"keep\" or \"remove\"",
+ "canOnlyInviteEmailUuid": "Can only invite using uuids or emails.",
+ "inviteMissingEmail": "Missing email address in invite.",
+ "partyMustbePrivate": "Parties must be private",
+ "userAlreadyInGroup": "User already in that group.",
+ "userAlreadyInvitedToGroup": "User already invited to that group.",
+ "userAlreadyPendingInvitation": "User already pending invitation.",
+ "userAlreadyInAParty": "User already in a party.",
+ "userWithIDNotFound": "User with id \"<%= userId %>\" not found.",
+ "userHasNoLocalRegistration": "User does not have a local registration (username, email, password).",
+ "uuidsMustBeAnArray": "User ID invites must be an array.",
+ "emailsMustBeAnArray": "Email address invites must be an array.",
+ "canOnlyInviteMaxInvites": "You can only invite \"<%= maxInvites %>\" at a time",
+ "onlyCreatorOrAdminCanDeleteChat": "Not authorized to delete this message!"
}
\ No newline at end of file
diff --git a/common/locales/hu/limited.json b/common/locales/hu/limited.json
index aac26fa890..cdb9f34015 100644
--- a/common/locales/hu/limited.json
+++ b/common/locales/hu/limited.json
@@ -5,7 +5,7 @@
"annoyingFriends": "Idegesítő barátok",
"annoyingFriendsText": "Eltaláltak <%= snowballs %> hógolyóval a csapattársaid.",
"alarmingFriends": "Ijesztő barátok",
- "alarmingFriendsText": "A csapattagjaid <%= spookDust %>-szor kísértettek.",
+ "alarmingFriendsText": "Got spooked <%= spookySparkles %> times by party members.",
"agriculturalFriends": "Mezőgazdasági Barátok",
"agriculturalFriendsText": "Csapattársaid <%= seeds %> alkalommal varázsoltak virággá.",
"aquaticFriends": "Vizi Barátok",
@@ -16,7 +16,7 @@
"valentine0": "\"A rózsák veresek\n\na Napijaim kékesek\n\nörülök, hogy veled együtt\n\negy csapatban lehetek.\"",
"valentine1": "\"A rózsák veresek\n\na violák szépek\n\nvessünk együtt\n\nA rossz szokásoknak véget.\"",
"valentine2": "\"A rózsák veresek\n\nEz a vers pedig régi\n\nremélem azért tetszett, mert \n\n10 Aranyat kellet érte fizetni.\"",
- "valentine3": "\"Roses are red\n\nIce Drakes are blue\n\nNo treasure is better\n\nThan time spent with you!\"",
+ "valentine3": "Vörös a rózsa,\nA Jégsárkány kék,\nAnnál nincs nagyobb kincs\nMintha veled lehetnék.",
"valentineCardAchievementTitle": "Cuki barátok",
"valentineCardAchievementText": "Óóó, a barátoddal nagyon fontosak lehettek egymásnak! <%= cards %> Valentin napi kártyát küldött/kapott.",
"polarBear": "Jegesmedve",
@@ -28,27 +28,27 @@
"seasonalShopClosedTitle": "<%= linkStart %>Leslie<%= linkEnd %>",
"seasonalShopTitle": "<%= linkStart %>Szezonális Varázslónő<%= linkEnd %>",
"seasonalShopClosedText": "A szezonális bolt jelenleg zárva van! Nem tudom hol van épp a szezonális varázslónő, de abban biztos vagyok, hogy a következő nagy gálára itt lesz!",
- "seasonalShopText": "Welcome to the Seasonal Shop!! We're stocking springtime Seasonal Edition goodies at the moment. Everything here will be available to purchase during the Spring Fling event each year, but we're only open until April 30th, so be sure to stock up now, or you'll have to wait a year to buy these items again!",
- "seasonalShopSummerText": "Welcome to the Seasonal Shop!! We're stocking summertime Seasonal Edition goodies at the moment. Everything here will be available to purchase during the Summer Splash event each year, but we're only open until July 31st, so be sure to stock up now, or you'll have to wait a year to buy these items again!",
- "seasonalShopFallText": "Welcome to the Seasonal Shop!! We're stocking autumn Seasonal Edition goodies at the moment. Everything here will be available to purchase during the Fall Festival event each year, but we're only open until October 31, so be sure to stock up now, or you'll have to wait a year to buy these items again!",
- "seasonalShopWinterText": "Welcome to the Seasonal Shop!! We're stocking winter Seasonal Edition goodies at the moment. Everything here will be available to purchase during the Winter Wonderland event each year, but we're only open until January 31, so be sure to stock up now, or you'll have to wait a year to buy these items again!",
- "seasonalShopFallTextBroken": "Oh.... Welcome to the Seasonal Shop... We're stocking autumn Seasonal Edition goodies, or something... Everything here will be available to purchase during the Fall Festival event each year, but we're only open until October 31... I guess you should to stock up now, or you'll have to wait... and wait... and wait... *sigh*",
- "seasonalShopRebirth": "If you've used the Orb of Rebirth, you can repurchase this equipment in the Rewards Column. Initially, you'll only be able to purchase the items for your current class (Warrior by default), but fear not, the other class-specific items will become available if you switch to that class.",
+ "seasonalShopText": "Üdvözöllek az Évszakok Boltjában!! Tavaszi szezonális áruink sorakoznak itt. Minden amit itt látsz megvásárolható a Tavaszi Kiszállásunk alatt. Mivel csupán április 30-ig vagyunk nyitva, menny biztosra és szerezd meg amire szükséged van, hogy ne kelljen egy újabb évet várnod a lehetőségre.",
+ "seasonalShopSummerText": "Üdvözöllek az Évszakok Boltjában!! Nyári szezonális áruink sorakoznak itt. Minden amit itt látsz megvásárolható a Nyári Kiszállásunk alatt. Mivel csupán július 31-ig vagyunk nyitva, menny biztosra és szerezd meg amire szükséged van, hogy ne kelljen egy újabb évet várnod a lehetőségre.",
+ "seasonalShopFallText": "Üdvözöllek az Évszakok Boltjában!! Őszi szezonális áruink sorakoznak itt. Minden amit itt látsz megvásárolható a Őszi Kiszállásunk alatt. Mivel csupán október 31-ig vagyunk nyitva, menny biztosra és szerezd meg amire szükséged van, hogy ne kelljen egy újabb évet várnod a lehetőségre.",
+ "seasonalShopWinterText": "Üdvözöllek az Évszakok Boltjában!! Téli szezonális áruink sorakoznak itt. Minden amit itt látsz megvásárolható a Téli Kiszállásunk alatt. Mivel csupán január 31-ig vagyunk nyitva, menny biztosra és szerezd meg amire szükséged van, hogy ne kelljen egy újabb évet várnod a lehetőségre.",
+ "seasonalShopFallTextBroken": "Oh... Üdvözöllek az Évszakok Boltjában... Nyári szezonális áruink sorakoznak itt, vagy mi... Minden amit itt látsz megvásárolható az Őszi Kiszállásunk alatt. de csupán július 31-ig vagyunk nyitva...Szerintem menny biztosra és szerezd meg amire szükséged van, vagy várnod kell még... várnod kell... és várnod... *huh*",
+ "seasonalShopRebirth": "Ha használtad az Újjászületés gömbjét, újra megveheted ezt a felszerelési tárgyat a Jutalmak Oszlopban. Alapvetően, cska jelenlegi osztályodba (be állítás nélkül a Harcos) tartozó tárgyakat veheted meg, de ne félj, más osztály-specifikus tárgyak is elérhetőek lesznek ha az adott osztályra váltasz.",
"candycaneSet": "Botcukor (Varázsló)",
"skiSet": "Orgyilkos (Tolvaj)",
"snowflakeSet": "Hópehely (Gyógyító)",
"yetiSet": "Yetiszelíditő (Harcos)",
- "northMageSet": "Mage of the North (Mage)",
- "icicleDrakeSet": "Icicle Drake (Rogue)",
- "soothingSkaterSet": "Soothing Skater (Healer)",
- "gingerbreadSet": "Gingerbread Warrior (Warrior)",
+ "northMageSet": "Észak Mágusa",
+ "icicleDrakeSet": "Jégtüskés Sárkány (Tolvaj)",
+ "soothingSkaterSet": "Enyhítő Korcsolya (Gyógyító)",
+ "gingerbreadSet": "Mézeskalács Harcos (Harcos)",
"toAndFromCard": "Fogadó: <%= toName %>, Küldő: <%= fromName %>",
"nyeCard": "Új évi üdvözlőkártya",
- "nyeCardExplanation": "For celebrating the new year together, you both receive the \"Auld Acquaintance\" badge!",
+ "nyeCardExplanation": "Az alkalomból, hogy együtt ünneplitek az újévet, mindketten megkapjátok a \"Régi Ismerős\" kitűzőt!",
"nyeCardNotes": "Küldj egy Új évi üdvözlőkártyát az egyik csapattagodnak.",
"seasonalItems": "Szezonális tárgyak",
- "nyeCardAchievementTitle": "Auld Acquaintance",
- "nyeCardAchievementText": "Happy New Year! Sent or received <%= cards %> New Year's cards.",
+ "nyeCardAchievementTitle": "Régi Ismerős",
+ "nyeCardAchievementText": "Boldog Újévet! Küldj vagy kapj <%= cards %> Újév üdvözlőkártyát.",
"nye0": "Happy New Year! May you slay many a bad Habit.",
"nye1": "Happy New Year! May you reap many Rewards.",
"nye2": "Happy New Year! May you earn many a Perfect Day.",
@@ -56,21 +56,22 @@
"nye4": "Happy New Year! May you not get attacked by a raging Hippogriff.",
"holidayCard": "Egy ünnepi üdvözlőkártyád érkezett.",
"mightyBunnySet": "Mighty Bunny (Warrior)",
- "magicMouseSet": "Magic Mouse (Mage)",
+ "magicMouseSet": "Varázsegér (Mágus)",
"lovingPupSet": "Loving Pup (Healer)",
- "stealthyKittySet": "Stealthy Kitty (Rogue)",
+ "stealthyKittySet": "Tolvaj Macska (Tolvaj)",
"daringSwashbucklerSet": "Daring Swashbuckler (Warrior)",
"emeraldMermageSet": "Emerald Mermage (Mage)",
"reefSeahealerSet": "Reef Seahealer (Healer)",
"roguishPirateSet": "Roguish Pirate (Rogue)",
- "monsterOfScienceSet": "Monster of Science (Warrior)",
+ "monsterOfScienceSet": "Tudomány szörnye (Harcos)",
"witchyWizardSet": "Witchy Wizard (Mage)",
"mummyMedicSet": "Mummy Medic (Healer)",
"vampireSmiterSet": "Vampire Smiter (Rogue)",
"bewareDogSet": "Beware Dog (Warrior)",
- "magicianBunnySet": "Magician's Bunny (Mage)",
+ "magicianBunnySet": "A bűvész nyula (Mágus)",
"comfortingKittySet": "Comforting Kitty (Healer)",
"sneakySqueakerSet": "Sneaky Squeaker (Rogue)",
- "fallEventAvailability": "Available until October 31",
- "winterEventAvailability": "Available until December 31"
+ "fallEventAvailability": "Elérhető október 31-ig ",
+ "winterEventAvailability": "Elérhető december 31-ig",
+ "springEventAvailability": "Available until May 31"
}
\ No newline at end of file
diff --git a/common/locales/hu/maintenance.json b/common/locales/hu/maintenance.json
new file mode 100644
index 0000000000..efdb524cd2
--- /dev/null
+++ b/common/locales/hu/maintenance.json
@@ -0,0 +1,34 @@
+{
+ "habiticaBackSoon": "Don't worry, Habitica will be back soon!",
+ "importantMaintenance": "We are doing important maintenance that we estimate will last until <%= localDate %> in your timezone.",
+ "maintenance": "Maintenance",
+ "maintenanceMoreInfo": "Want more information about the maintenance? <%= linkStart %>Check out our info page<%= linkEnd %>.",
+ "noDamageKeepStreaks": "You will NOT take damage or lose streaks!",
+ "thanksForPatience": "Thanks for your patience!",
+ "twitterMaintenanceUpdates": "For the most recent updates, watch our Twitter, where we will be posting status information.",
+ "veteranPetAward": "At the end, you will receive a Veteran pet!",
+
+ "maintenanceInfoTitle": "Information about Upcoming Maintenance to Habitica",
+ "maintenanceInfoWhat": "What is happening?",
+ "maintenanceInfoWhatText": "On May 21, Habitica will be down for maintenance for most of the day. You will not take any damage or have your account harmed during that weekend, even if you can’t log in to check off your Dailies in time! We will be working very hard to make the downtime as short as possible, and will be posting regular updates on our Twitter account. At the end of the downtime, to thank everyone for their patience, you will all receive a rare pet!",
+ "maintenanceInfoWhy": "Why is this happening?",
+ "maintenanceInfoWhyText": "For the past several months, we have been thoroughly revamping Habitica behind-the-scenes. Specifically, we have rewritten the API. While it may not look much different on the surface, it’s a whole new world underneath. This will allow us WAY more flexibility when we want to build features in the future, and lead to improved performance!",
+ "maintenanceInfoTechDetails": "Want more details on the technical side of the process? Visit The Forge, our dev blog.",
+ "maintenanceInfoMore": "More Information",
+ "maintenanceInfoAccountChanges": "What changes will I see to my account after the rewrite is complete?",
+ "maintenanceInfoAccountChangesText": "At first, there won’t be any notable changes aside from performance improvements for features such as Challenges. If you notice any changes that shouldn’t be there, email us at admin@habitica.com and we will investigate them for you!",
+ "maintenanceInfoAddFeatures": "What kind of features will this allow Habitica to add?",
+ "maintenanceInfoAddFeaturesText": "Completing this rewrite will allow us to start building out improved chat and Guilds, plans for organizations and families, and additional productivity features like Monthlies and the ability to record yesterday’s activity! Those are all involved features on their own, so it will take time to build them, but until we were finished with this rewrite, there was no way we could start them.",
+ "maintenanceInfoHowLong": "How long will the maintenance take?",
+ "maintenanceInfoHowLongText": "We have to migrate tasks and data for all 1.3 million Habitica users -- not an easy task! We anticipate that it will take place between approximately 1pm Pacific Time (8pm UTC) and 10pm Pacific Time (5am UTC). Rest assured that we’re doing everything we can to make it go as quickly as possible! You can follow updates on our Twitter.",
+ "maintenanceInfoStatsAffected": "How will my Dailies, Streaks, Buffs, and Quests be affected?",
+ "maintenanceInfoStatsAffectedText1": "You will NOT take any damage or lose any streaks that weekend, but otherwise, your day will reset normally! Dailies that you checked will become unchecked, buffs will reset, etc. If you are in a Collection Quest, you will still find items. If you are in a Boss Battle, you will still deal damage to the Boss, but the Boss will not deal damage to you. (Even monsters need a break!)",
+ "maintenanceInfoStatsAffectedText2": "After a lot of thought, our team concluded that this was the most fair way to handle the fact that many users will not be able to check off their Dailies normally during the maintenance. We’re sorry for any inconvenience this causes!",
+ "maintenanceInfoSeeTasks": "What if I need to see my task list?",
+ "maintenanceInfoSeeTasksText": "If you know that you will need to see your task list on Saturday to remind yourself what you have to do, we recommend that before the maintenance begins, you take a screenshot of your tasks so that you can use it as a reference.",
+ "maintenanceInfoRarePet": "What kind of rare pet will I receive?",
+ "maintenanceInfoRarePetText": "To thank you for your patience during the downtime, everyone will get a rare Veteran Pet. If you’ve never received a Veteran Pet before, you will receive a Veteran Wolf. If you already have a Veteran Wolf, you will receive a Veteran Tiger. And if you already have a Veteran Wolf and a Veteran Tiger, you will receive a never-before-seen Veteran pet! After the migration is completed, it may take several hours for your pet to show up, but never fear, everyone will get one.",
+ "maintenanceInfoWho": "Who worked on this massive project?",
+ "maintenanceInfoWhoText": "We’re glad you asked! It was spearheaded by our amazing contributor paglias, with lots of help from Blade, TheHollidayInn, SabreCat, Victor Pudeyev, TheUnknown, and Alys.",
+ "maintenanceInfoTesting": "The new version was also tirelessly tested by a bunch of our amazing open-source volunteers. Thank you -- we couldn't have done this without you."
+}
diff --git a/common/locales/hu/npc.json b/common/locales/hu/npc.json
index 8dbbaff7da..4b606fc0da 100644
--- a/common/locales/hu/npc.json
+++ b/common/locales/hu/npc.json
@@ -21,6 +21,26 @@
"ian": "Ian",
"ianText": "Üdv a Küldetés boltban! Itt használhatod a küldetés tekercseid, hogy a barátaiddal szörnyek ellen harcolhassatok. Ne felejtsd el megnézni eladó küldetés tekercseink széles választékát a jobb oldalon.",
"ianBrokenText": "Üdv a Küldetés boltban... Itt használhatod a küldetés tekercseid, hogy a barátaiddal szörnyek ellen harcolhassatok... Ne felejtsd el megnézni eladó küldetés tekercseink széles választékát a jobb oldalon...",
+ "missingKeyParam": "\"req.params.key\" is required.",
+ "itemNotFound": "Item \"<%= key %>\" not found.",
+ "cannotBuyItem": "You can't buy this item.",
+ "missingTypeKeyEquip": "\"key\" and \"type\" are required parameters.",
+ "missingPetFoodFeed": "\"pet\" and \"food\" are required parameters.",
+ "invalidPetName": "Invalid pet name supplied.",
+ "missingEggHatchingPotionHatch": "\"egg\" and \"hatchingPotion\" are required parameters.",
+ "invalidTypeEquip": "\"type\" must be one of 'equipped', 'pet', 'mount', 'costume'.",
+ "mustPurchaseToSet": "Must purchase <%= val %> to set it on <%= key %>.",
+ "typeRequired": "Type is required",
+ "keyRequired": "Key is required",
+ "notAccteptedType": "Type must be in [eggs, hatchingPotions, food, quests, gear]",
+ "contentKeyNotFound": "Key not found for Content <%= type %>",
+ "plusOneGem": "+1 Gem",
+ "typeNotSellable": "Type is not sellable. Must be one of the following <%= acceptedTypes %>",
+ "userItemsKeyNotFound": "Key not found for user.items <%= type %>",
+ "pathRequired": "Path string is required",
+ "unlocked": "Items have been unlocked",
+ "alreadyUnlocked": "Full set already unlocked.",
+ "alreadyUnlockedPart": "Full set already partially unlocked.",
"USD": "(USD)",
"newStuff": "Új cucc",
"cool": "Mondd el később",
@@ -64,6 +84,7 @@
"tourPetsPage": "This is the Stable! After level 4, you can hatch pets using eggs and potions. When you hatch a pet in the Market, it will appear here! Click a pet's image to add it to your avatar. Feed them with the food you find after level 4, and they'll grow into powerful mounts.",
"tourMountsPage": "Once you've fed a pet enough food to turn it into a mount, it will appear here. (Pets, mounts, and food are available after level 4.) Click a mount to saddle up!",
"tourEquipmentPage": "This is where your Equipment is stored! Your Battle Gear affects your stats. If you want to show different Equipment on your avatar without changing your stats, click \"Enable Costume.\"",
+ "equipmentAlreadyOwned": "You already own that piece of equipment",
"tourOkay": "Oké!",
"tourAwesome": "Fantasztikus!",
"tourSplendid": "Pompás!",
diff --git a/common/locales/hu/pets.json b/common/locales/hu/pets.json
index 871fde9233..ea192391e3 100644
--- a/common/locales/hu/pets.json
+++ b/common/locales/hu/pets.json
@@ -12,6 +12,7 @@
"etherealLion": "Földöntúli oroszlán",
"veteranWolf": "Veterán farkas",
"veteranTiger": "Veterán Tigris",
+ "veteranLion": "Veteran Lion",
"cerberusPup": "Kerberosz kölyök",
"hydra": "Hidra",
"mantisShrimp": "Sáskarák",
@@ -19,7 +20,7 @@
"orca": "Gyilkos bálna",
"royalPurpleGryphon": "Fenséges lila griffmadár",
"phoenix": "Főnix",
- "bumblebee": "Bumblebee",
+ "magicalBee": "Magical Bee",
"rarePetPop1": "Kattintas az arany mancsra, hogy megtudd hogyan tudod megszerezni ezt a ritka háziállatot úgy, hogy közreműködsz a Habitica-nek.",
"rarePetPop2": "Hogyan szerezheted meg ezt a háziállatot!",
"potion": "<%= potionType %> főzet",
@@ -62,6 +63,7 @@
"hatchedPet": "You hatched a <%= potion %> <%= egg %>!",
"displayNow": "Display Now",
"displayLater": "Display Later",
+ "petNotOwned": "You do not own this pet.",
"earnedCompanion": "With all your productivity, you've earned a new companion. Feed it to make it grow!",
"feedPet": "Megeteted a(z) <%= article %><%= text %>-t a <%= name %>-el?",
"useSaddle": "Felnyergeled a(z) <%= pet %>-t?",
@@ -83,5 +85,8 @@
"petKeyBoth": "Mindkettő elengedése",
"confirmPetKey": "Biztos vagy benne?",
"petKeyNeverMind": "Még Nem",
+ "petsReleased": "Pets released.",
+ "mountsAndPetsReleased": "Mounts and pets released",
+ "mountsReleased": "Mounts released",
"gemsEach": "drágakő egyenként"
}
\ No newline at end of file
diff --git a/common/locales/hu/quests.json b/common/locales/hu/quests.json
index 96e2a904af..9adc3572d0 100644
--- a/common/locales/hu/quests.json
+++ b/common/locales/hu/quests.json
@@ -78,5 +78,24 @@
"whichQuestStart": "Melyik küldetést szeretnéd elkezdeni?",
"getMoreQuests": "Szerezz több küldetést",
"unlockedAQuest": "Feltártál egy küldetést!",
- "leveledUpReceivedQuest": "You leveled up to Level <%= level %> and received a quest scroll!"
+ "leveledUpReceivedQuest": "You leveled up to Level <%= level %> and received a quest scroll!",
+ "questInvitationDoesNotExist": "No quest invitation has been sent out yet.",
+ "questInviteNotFound": "No quest invitation found.",
+ "guildQuestsNotSupported": "Guilds cannot be invited on quests.",
+ "questNotFound": "Quest \"<%= key %>\" not found.",
+ "questNotOwned": "You don't own that quest scroll.",
+ "questNotGoldPurchasable": "Quest \"<%= key %>\" is not a Gold-purchasable quest.",
+ "questLevelTooHigh": "You must be level <%= level %> to begin this quest.",
+ "questAlreadyUnderway": "Your party is already on a quest. Try again when the current quest has ended.",
+ "questAlreadyAccepted": "You already accepted the quest invitation.",
+ "noActiveQuestToLeave": "No active quest to leave",
+ "questLeaderCannotLeaveQuest": "Quest leader cannot leave quest",
+ "notPartOfQuest": "You are not part of the quest",
+ "noActiveQuestToAbort": "There is no active quest to abort.",
+ "onlyLeaderAbortQuest": "Only the group or quest leader can abort a quest.",
+ "questAlreadyRejected": "You already rejected the quest invitation.",
+ "cantCancelActiveQuest": "You can not cancel an active quest, use the abort functionality.",
+ "onlyLeaderCancelQuest": "Only the group or quest leader can cancel the quest.",
+ "questNotPending": "There is no quest to start.",
+ "questOrGroupLeaderOnlyStartQuest": "Only the quest leader or group leader can force start the quest"
}
\ No newline at end of file
diff --git a/common/locales/hu/questscontent.json b/common/locales/hu/questscontent.json
index 50a8aaabd3..013d28394c 100644
--- a/common/locales/hu/questscontent.json
+++ b/common/locales/hu/questscontent.json
@@ -304,9 +304,21 @@
"questBewilderCompletionChat": "`The Be-Wilder is DEFEATED!`\n\nWe've done it! The Be-Wilder lets out a ululating cry as it twists in the air, shedding feathers like falling rain. Slowly, gradually, it coils into a cloud of sparkling mist. As the newly-revealed sun pierces the fog, it burns away, revealing the coughing, mercifully human forms of Bailey, Matt, Alex.... and the April Fool himself.\n\n`Mistiflying is saved!`\n\nThe April Fool has enough shame to look a bit sheepish. “Oh, hm,” he says. “Perhaps I got a little…. carried away.”\n\nThe crowd mutters. Sodden flowers wash up on sidewalks. Somewhere in the distance, a roof collapses with a spectacular splash.\n\n“Er, yes,” the April Fool says. “That is. What I meant to say was, I’m dreadfully sorry.” He heaves a sigh. “I suppose it can’t all be fun and games, after all. It might not hurt to focus occasionally. Maybe I’ll get a head start on next year’s pranking.”\n\nRedphoenix coughs meaningfully.\n\n“I mean, get a head start on this year’s spring cleaning!” the April Fool says. “Nothing to fear, I’ll have Habit City in spit-shape soon. Luckily nobody is better than I at dual-wielding mops.”\n\nEncouraged, the marching band starts up.\n\nIt isn’t long before all is back to normal in Habit City. Plus, now that the Be-Wilder has evaporated, the magical bees of Mistiflying bustle back to work, and soon the flowers are blooming and the city is floating once more.\n\nAs Habiticans cuddle the magical fuzzy bees, the April Fool’s eyes light up. “Oho, I’ve had a thought! Why don’t you all keep some of these fuzzy Bee Pets and Mounts? It’s a gift that perfectly symbolizes the balance between hard work and sweet rewards, if I’m going to get all boring and allegorical on you.” He winks. “Besides, they don’t have stingers! Fool’s honor.”",
"questBewilderBossRageTitle": "Beguilement Strike",
"questBewilderBossRageDescription": "When this gauge fills, The Be-Wilder will unleash its Beguilement Strike on Habitica!",
- "questBewilderDropBumblebeePet": "Bumblebee (Pet)",
- "questBewilderDropBumblebeeMount": "Bumblebee (Mount)",
+ "questBewilderDropBumblebeePet": "Magical Bee (Pet)",
+ "questBewilderDropBumblebeeMount": "Magical Bee (Mount)",
"questBewilderBossRageMarket": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nOh no! Despite our best efforts, we've gotten distracted by the Be-Wilder’s charming illusions and have forgotten to do some of our Dailies! With a cackling cry, the shining bird beats its wings, raising a swarm of mist around Alex the Merchant. When the fog clears, he has been possessed! “Have some free samples!” he shouts gleefully, and begins to hurl exploding eggs and potions at fleeing Habiticans. Not the most favorable of sales, to be sure.\n\nHurry! Let's stay focused on our Dailies to defeat this monster before it possesses someone else.",
"questBewilderBossRageStables": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nAhh!!! Once again the Be-Wilder has dazzled us into neglecting our Dailies, and now it has attacked Matt the Beast Master! With a swirl of mist, Matt transforms into a terrifying winged creature, and all the pets and mounts howl sadly in their stables. Quickly, stay focused on your tasks to defeat this dastardly distraction!",
- "questBewilderBossRageBailey": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nLook out! In the middle of reporting the news, Bailey the Town Crier has been possessed by the Be-Wilder! She lets out an evil, uninformative screech as she rises into the air. Now how will we know what’s going on?\n\nDon't give up... we're so close to defeating this bothersome bird for once and for all!"
+ "questBewilderBossRageBailey": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nLook out! In the middle of reporting the news, Bailey the Town Crier has been possessed by the Be-Wilder! She lets out an evil, uninformative screech as she rises into the air. Now how will we know what’s going on?\n\nDon't give up... we're so close to defeating this bothersome bird for once and for all!",
+ "questFalconText": "The Birds of Preycrastination",
+ "questFalconNotes": "Mt. Habitica is being overshadowed by a looming mountain of To-Dos. It used to be a place to picnic and enjoy a sense of accomplishment, until the neglected tasks grew out of control. Now it's home to fearsome Birds of Preycrastination, foul creatures which stop Habiticans from completing their tasks!
\"It's too hard!\" they caw at @JonArinbjorn and @Onheiron. \"It'll take too long to do right now! It won't make any difference if you wait until tomorrow! Why don't you do something fun instead?\"
No more, you vow. You will climb your personal mountain of To-Dos and defeat the Birds of Preycrastination!",
+ "questFalconCompletion": "Having finally triumphed over the Birds of Preycrastination, you settle down to enjoy the view and your well-earned rest.
\"Wow!\" says @Trogdorina. \"You won!\"
@Squish adds, \"Here, take these eggs I found as a reward.\"",
+ "questFalconBoss": "Birds of Preycrastination",
+ "questFalconDropFalconEgg": "Falcon (Egg)",
+ "questFalconUnlockText": "Unlocks purchasable Falcon eggs in the Market",
+ "questTreelingText": "The Tangle Tree",
+ "questTreelingNotes": "It's the annual Garden Competition, and everyone is talking about the mysterious project which @aurakami has promised to unveil. You join the crowd on the day of the big announcement, and marvel at the introduction of a moving tree. @fuzzytrees explains that the tree will help with garden maintenance, showing how it can mow the lawn, trim the hedge and prune the roses all at the same time – until the tree suddenly goes wild, turning its secateurs on its creator! The crowd panics as everyone tries to flee, but you aren't afraid – you leap forward, ready to do battle.",
+ "questTreelingCompletion": "You dust yourself off as the last few leaves drift to the floor. In spite of the upset, the Garden Competition is now safe – although the tree you just reduced to a heap of wood chips won't be winning any prizes! \"Still a few kinks to work out there,\" @PainterProphet says. \"Perhaps someone else would do a better job of training the saplings. Do you fancy a go?\"",
+ "questTreelingBoss": "Tangle Tree",
+ "questTreelingDropTreelingEgg": "Treeling (Egg)",
+ "questTreelingUnlockText": "Unlocks purchasable Treeling eggs in the Market"
}
\ No newline at end of file
diff --git a/common/locales/hu/rebirth.json b/common/locales/hu/rebirth.json
index d40b0157a6..04d19bb36d 100644
--- a/common/locales/hu/rebirth.json
+++ b/common/locales/hu/rebirth.json
@@ -5,7 +5,7 @@
"rebirthStartOver": "Újjászületés elölről kezdi a karaktered 1. Szintről. ",
"rebirthAdvList1": "Az életerőd feltöltődik.",
"rebirthAdvList2": "Nincs Tapasztalatod, Aranyad vagy Felszerelésed (Ingyenes tárgyak kivételével, mint a Rejtélyes tárgyak.",
- "rebirthAdvList3": "A szokásaid, napi feladataid és a tennivalóid visszaállnak sárgára, és a szériák nullázódnak.",
+ "rebirthAdvList3": "Your Habits, Dailies, and To-Dos reset to yellow, and streaks reset, except for challenge tasks.",
"rebirthAdvList4": "A kezdő kasztod a Harcos, amíg nem válik elérhetővé más kaszt",
"rebirthInherit": "Az új karaktered örököl pár tárgyat az elődjétől:",
"rebirthInList1": "A feladatok, előzmények és beállítások megmaradnak.",
@@ -24,5 +24,6 @@
"rebirthPop": "Kezdj új karaktert 1-es Szinttől, megtartva a kitűntetéseidet, a gyűjteményeidet, a feladataidat és a történetedet.",
"rebirthName": "Az Újjászületés Gömbje",
"reborn": "Újjászülettél, maximális szint <%= reLevel %>",
- "confirmReborn": "Biztos vagy benne?"
+ "confirmReborn": "Biztos vagy benne?",
+ "rebirthComplete": "You have been reborn!"
}
\ No newline at end of file
diff --git a/common/locales/hu/settings.json b/common/locales/hu/settings.json
index a7de66be0c..f74545a0b7 100644
--- a/common/locales/hu/settings.json
+++ b/common/locales/hu/settings.json
@@ -47,6 +47,7 @@
"customDayStart": "Egyedi nap kezdet",
"changeCustomDayStart": "Megváltoztatod az egyedi nap kezdetet?",
"sureChangeCustomDayStart": "Biztosan meg akarod változtatni, mikor kezdődjön a nap?",
+ "customDayStartHasChanged": "Your custom day start has changed.",
"nextCron": "A napi feladataid legközelebb akkor indulnak újra, amikor a Habiticat <%= time %> után használod. Bizonyosodj meg róla, hogy ez előtt az idő előtt befejezed a napi feladataid.",
"customDayStartInfo1": "A Habitican a napi feladataid újraindítása a saját időzónádbeli éjfélre van alapértelmezve. Ezt az időt itt tudod tesreszabni",
"misc": "Egyéb",
@@ -61,12 +62,23 @@
"newUsername": "Új bejelentkezési név",
"dangerZone": "Veszélyzóna",
"resetText1": "VIGYÁZAT! Ez alapállapotra állítja a fiókod sok részét. Alapvetően nem ajánljuk, de pár embernek hasznos lehet az elején, miután még csak egy rövid ideig játszott.",
- "resetText2": "El fogod veszíteni az összes szintedet, aranyadat és tapasztalati pontod. Minden feladatod véglegesen törlődik az előzményekkel együtt. Minden tárgyadat elveszíted, de ezeket később visszavásárolhatod a korlátozott példányszámú tárgyak és a Rejtélyes tárgyak esetében is, amiket most birtokolsz (megfelelő kasztúnak kell lenned, hogy a kaszt-specifikus tárgyakat megvehesd). A jelenlegi kasztod, háziállataid és hátasaid megmaradnak. Esetleg jobban jársz egy Újjászületés gömbjével, mely biztonságosabb opció és megőrzi a feladataidat is.",
+ "resetText2": "You will lose all your levels, gold, and experience points. All your tasks (except those from challenges) will be deleted permanently and you will lose all of their historical data. You will lose all your equipment but you will be able to buy it all back, including all limited edition equipment or subscriber Mystery items that you already own (you will need to be in the correct class to re-buy class-specific gear). You will keep your current class and your pets and mounts. You might prefer to use an Orb of Rebirth instead, which is a much safer option and which will preserve your tasks.",
"deleteText": "Biztos vagy benne? Ez végleg kitörli a fiókodat és soha nem tudod visszahozni! Újra kell regisztrálnod, hogy a Habiticat újra használhasd. A birtokolt, vagy elköltött drágaköveket nem kapod vissza. Ha abszolút biztos vagy, akkor írd be a <%= deleteWord %> szót az alábbi szövegdobozba.",
"API": "API",
+ "APIv3": "API v3",
+ "APIv2": "API v2 - Deprecated",
"APIText": "Másold ki ezt, külső alkalmazások használatához. Egyébiránt úgy gondolj az API Kulcsodra, mint egy jelszóra és ne oszd meg senkivel nyilvánosan. Előfordulhat, hogy a felhasználói azonosítódat kérik, de soha ne írd be sehova az API Kulcsodat, ahol mások esetleg láthatják, még a Githubra se.",
"APIToken": "API Kulcs (ez egy jelszó - lásd a figyelmeztetést lejjebb)",
+ "thirdPartyApps": "Third Party Apps",
+ "dataToolDesc": "A webpage that shows you certain information from your Habitica account, such as statistics about your tasks, equipment, and skills.",
+ "beeminder": "Beeminder",
+ "beeminderDesc": "Let Beeminder automatically monitor your Habitica To-Dos. You can commit to maintaining a target number of To-Dos completed per day or per week, or you can commit to gradually reducing your remaining number of uncompleted To-Dos. (By \"commit\" Beeminder means under threat of paying actual money! But you may also just like Beeminder's fancy graphs.)",
+ "chromeChatExtension": "Chrome Chat Extension",
+ "chromeChatExtensionDesc": "The Chrome Chat Extension for Habitica adds an intuitive chat box to all of habitica.com. It allows users to chat in the Tavern, their party, and any guilds they are in.",
+ "otherExtensions": "Other Extensions",
+ "otherDesc": "Find other apps, extensions, and tools on the Habitica wiki.",
"resetDo": "Csináld, indítsd újra a fiókomat!",
+ "resetComplete": "Reset complete!",
"fixValues": "Értékek helyreállítása",
"fixValuesText1": "Ha egy hibával találkoztál, vagy te magad hibáztál, ami igazságtalanul megváltoztatta a karaktered értékeit (sebzés, amit nem kellett volna elszenvedned, arany, amiért nem dolgoztál meg, stb.), akkor a számokat kijavíthatod itt. Igen, ezzel csalhatsz is: használd ezt a funkciót bölcsen, különben szabotálod a saját fejlődésed.",
"fixValuesText2": "Tudd, hogy itt nem tudod visszaállítani a feladatok szériáit. Hogy ezt megtedd, lépj be az adott napi feladatba, menj a haladó beállításokra, ahol megtalálod a széria visszaállítása mezőt.",
@@ -96,6 +108,7 @@
"emailNotifications": "E-mail értesítések",
"wonChallenge": "Megnyertél egy kihívást!",
"newPM": "Kaptál egy privát üzenetet",
+ "sentGems": "Sent gems!",
"giftedGems": "Drágakövek ajándékozva",
"giftedGemsInfo": "<%= amount %> drágakő <%= name %>-tól/től",
"giftedSubscription": "Előfizetés ajándékozva",
@@ -135,6 +148,11 @@
"webhooks": "Webkampók",
"enabled": "Engedélyezve",
"webhookURL": "Webkampó URL",
+ "invalidUrl": "invalid url",
+ "invalidEnabled": "the \"enabled\" parameter should be a boolean",
+ "regIdRequired": "RegId is required",
+ "pushDeviceAdded": "Push device added successfully",
+ "pushDeviceAlreadyAdded": "The user already has the push device",
"add": "Hozzáadás",
"buyGemsGoldCap": "Határ megemelve <%= amount %>-ra/re",
"mysticHourglass": "<%= amount %> misztikus óraüveg",
@@ -149,5 +167,5 @@
"amazonPayments": "Amazon Payments",
"timezone": "Időzóna",
"timezoneUTC": "A Habitica a számítógépeden beállított időzónát használja, ami a következő: <%= utc %>",
- "timezoneInfo": "Ha az időzóna hibás, először frissítsd ezt a lapot a böngésződben, hogy megbizonyosodhass arról, hogy a Habitica a legújabb információk birtokában van. Ha még mindig hibás, változtasd meg a számítógéped időzónáját, majd töltsd újra ezt az oldalt.
Ha használod a Habiticát más számítógépeken, illetve mobilkészülékeken is, az időzónának minden készülénken egyeznie kell. Ha a napi feladataid rossz időben indulnak újra, ismételd meg ezt az ellenőrzést minden további számítógépen, illetve mobilkészüléken a böngészőben."
+ "timezoneInfo": "If that time zone is wrong, first reload this page using your browser's reload or refresh button to ensure that Habitica has the most recent information. If it is still wrong, adjust the time zone on your PC and then reload this page again.
If you use Habitica on other PCs or mobile devices, the time zone must be the same on them all. If your Dailies have been resetting at the wrong time, repeat this check on all other PCs and on a browser on your mobile devices."
}
\ No newline at end of file
diff --git a/common/locales/hu/spells.json b/common/locales/hu/spells.json
index bb4c5ef255..e4911b6dc6 100644
--- a/common/locales/hu/spells.json
+++ b/common/locales/hu/spells.json
@@ -35,8 +35,8 @@
"spellSpecialSnowballAuraNotes": "Megdobsz egy csapttagot hógolyóval, ugyan mi rossz történhet? A csapattag új napjáig marad érvényben.",
"spellSpecialSaltText": "Só",
"spellSpecialSaltNotes": "Valaki megdobott egy hógolyóval. Ha ha, nagyon vicces. Leszedné valaki rólam a havat?!",
- "spellSpecialSpookDustText": "Kísérteties sziporkák",
- "spellSpecialSpookDustNotes": "Turn a friend into a floating blanket with eyes!",
+ "spellSpecialSpookySparklesText": "Spooky Sparkles",
+ "spellSpecialSpookySparklesNotes": "Turn a friend into a floating blanket with eyes!",
"spellSpecialOpaquePotionText": "Opálos főzet",
"spellSpecialOpaquePotionNotes": "Szűntesd meg a Kísérteties sziporkák hatását.",
"spellSpecialShinySeedText": "Fényes mag",
@@ -46,5 +46,11 @@
"spellSpecialSeafoamText": "Tengerhab",
"spellSpecialSeafoamNotes": "Változtass át egy barátot tengeri lénnyé!",
"spellSpecialSandText": "Homok",
- "spellSpecialSandNotes": "Tengerhab hatásának megszüntetése."
+ "spellSpecialSandNotes": "Tengerhab hatásának megszüntetése.",
+ "spellNotFound": "Skill \"<%= spellId %>\" not found.",
+ "partyNotFound": "Party not found",
+ "targetIdUUID": "\"targetId\" must be a valid User ID.",
+ "challengeTasksNoCast": "Casting a skill on challenge tasks is not allowed.",
+ "spellNotOwned": "You don't own this skill.",
+ "spellLevelTooHigh": "You must be level <%= level %> to use this skill."
}
\ No newline at end of file
diff --git a/common/locales/hu/subscriber.json b/common/locales/hu/subscriber.json
index b26a814383..b8656c951a 100644
--- a/common/locales/hu/subscriber.json
+++ b/common/locales/hu/subscriber.json
@@ -4,6 +4,8 @@
"subDescription": "Vásárolj drágaköveket arany fejében, juss hozzá havonta rejtélyes tárgyakhoz, őrizd meg a haladásod előzményeit, duplázd meg a napi tárgyszerzési limited, támogasd a fejlesztőket! Kattints további információkért!",
"buyGemsGold": "Drágakő vásárlása aranyért",
"buyGemsGoldText": "Alexander, a kereskedő drágaköveket kínál, darabonként <%= gemCost %> arany fejében. A szállítmányai jelenleg maximum havi <%= gemLimit %> drágakövet biztosítanak számodra, de ez a limit minden egymás-utáni (megszakítás nélküli) három hónap előfizetés után 5 drágakővel nő, egészen havi maximális 50 drágakőig.",
+ "mustSubscribeToPurchaseGems": "Must subscribe to purchase gems with GP",
+ "reachedGoldToGemCap": "You've reached the Gold=>Gem conversion cap <%= convCap %> for this month. We have this to prevent abuse / farming. The cap will reset within the first three days of next month.",
"retainHistory": "Retain additional history entries",
"retainHistoryText": "Makes completed To-Dos and task history available for longer.",
"doubleDrops": "Napi tárgy találati korlátozás megduplázva",
@@ -29,6 +31,7 @@
"manageSub": "Az előfizetésed kezeléséért kattints ide",
"cancelSub": "Leíratkozás",
"canceledSubscription": "Megszüntetett előfizetés",
+ "cancelingSubscription": "Canceling the subscription",
"adminSub": "Adminisztrátor felíratkozások",
"morePlans": "More Plans Coming Soon",
"organizationSub": "Privát szervezet",
@@ -73,6 +76,9 @@
"timeTravelersPopover": "We see you have a Mystic Hourglass, so we will happily travel back in time for you! Please choose the pet, mount, or Mystery Item Set you would like. You can see a list of the past item sets here! If those don't satisfy you, perhaps you'd be interested in one of our fashionably futuristic Steampunk Item Sets?",
"timeTravelersAlreadyOwned": "Congratulations! You already own everything the Time Travelers currently offer. Thanks for supporting the site!",
"mysticHourglassPopover": "A Mystic Hourglass allows you to purchase certain limited-time items, such as monthly Mystery Item Sets and awards from world bosses, from the past!",
+ "mysterySetNotFound": "Mystery set not found, or set already owned.",
+ "mysteryItemIsEmpty": "Mystery items are empty",
+ "mysteryItemOpened": "Mystery item opened.",
"mysterySet201402": "Winged Messenger Set",
"mysterySet201403": "Forest Walker Set",
"mysterySet201404": "Twilight Butterfly Set",
@@ -99,6 +105,8 @@
"mysterySet201601": "Champion of Resolution Set",
"mysterySet201602": "Heartbreaker Set",
"mysterySet201603": "Lucky Clover Set",
+ "mysterySet201604": "Leaf Warrior Set",
+ "mysterySet201605": "Marching Bard Set",
"mysterySet301404": "Steampunk Standard Set",
"mysterySet301405": "Steampunk Accessories Set",
"mysterySetwondercon": "Wondercon",
@@ -110,9 +118,25 @@
"hourglassBuyItemConfirm": "Buy this item for 1 Mystic Hourglass?",
"petsAlreadyOwned": "A háziállat már megvan neked.",
"mountsAlreadyOwned": "Mount already owned.",
- "typeNotAllowedHourglass": "Item type not supported for purchase with Mystic Hourglass. Allowed types:",
+ "typeNotAllowedHourglass": "Item type not supported for purchase with Mystic Hourglass. Allowed types: <%= allowedTypes %>",
"petsNotAllowedHourglass": "Pet not available for purchase with Mystic Hourglass.",
"mountsNotAllowedHourglass": "Mount not available for purchase with Mystic Hourglass.",
"hourglassPurchase": "Purchased an item using a Mystic Hourglass!",
- "hourglassPurchaseSet": "Purchased an item set using a Mystic Hourglass!"
+ "hourglassPurchaseSet": "Purchased an item set using a Mystic Hourglass!",
+ "missingUnsubscriptionCode": "Missing unsubscription code.",
+ "missingSubscription": "User does not have a plan subscription",
+ "missingSubscriptionCode": "Missing subscription code. Possible values: basic_earned, basic_3mo, basic_6mo, google_6mo, basic_12mo.",
+ "cannotDeleteActiveAccount": "You have an active subscription, cancel your plan before deleting your account.",
+ "paymentNotSuccessful": "The payment was not successful",
+ "planNotActive": "The plan hasn't activated yet (due to a PayPal bug). It will begin <%= nextBillingDate %>, after which you can cancel to retain your full benefits",
+ "notAllowedHourglass": "Pet/Mount not available for purchase with Mystic Hourglass.",
+ "readCard": "<%= cardType %> has been read",
+ "cardTypeRequired": "Card type required",
+ "cardTypeNotAllowed": "Unknown card type.",
+ "invalidCoupon": "Invalid coupon code.",
+ "couponUsed": "Coupon code already used.",
+ "noSudoAccess": "You don't have sudo access.",
+ "couponCodeRequired": "The coupon code is required.",
+ "eventRequired": "\"req.params.event\" is required.",
+ "countRequired": "\"req.query.count\" is required."
}
\ No newline at end of file
diff --git a/common/locales/hu/tasks.json b/common/locales/hu/tasks.json
index e12d8b326c..e8ec2a51a5 100644
--- a/common/locales/hu/tasks.json
+++ b/common/locales/hu/tasks.json
@@ -73,6 +73,7 @@
"clearTags": "Törlés",
"hideTags": "Elrejt",
"showTags": "Mutat",
+ "toRequired": "You must supply a to value",
"startDate": "Kezdő dátum",
"startDateHelpTitle": "Mikortól induljon a feladat?",
"startDateHelp": "Állítsd be a feladat dátumát, amikortól aktív lesz. Előtte inaktív marad.",
@@ -88,8 +89,9 @@
"fortifyName": "Erősítőfőzet",
"fortifyPop": "Visszaállít minden feladatot semleges értékűre (sárga színűre), és feltölti az életerőt.",
"fortify": "Megerősít",
- "fortifyText": "Az erősítés visszaállítja minden feladatodat semleges (sárga) állapotba, mintha csak most adtad volna őket hozzá, ezenkívül feltölti az életerődet. Ez egy remek lehetősége, ha a sok piros feladat túl nehézzé teszi a játékot, vagy a sok kék túl könnyűvé. Ha a friss kezdés motiválóbbnak hangzik, szándd rá a drágaköveket és fújj egyet!",
+ "fortifyText": "Fortify will return all your tasks, except challenge tasks, to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!",
"confirmFortify": "Biztos vagy benne?",
+ "fortifyComplete": "Fortify complete!",
"sureDelete": "Biztos vagy benne, hogy törölni szeretnéd a <%= taskType %>-t az alábbi szöveggel \"<%= taskText %>\"?",
"streakCoins": "Széria bónusz!",
"pushTaskToTop": "A feladat legfelülre küldése a listában. Tartsd nyomva a Ctrl-t vagy a Cmd-t, hogy legalura küldd.",
@@ -112,5 +114,18 @@
"rewardHelp2": "A felszerelés hatással van a tulajdonságaidra (<%= linkStart %>Avatar > Stats<%= linkEnd %>).",
"rewardHelp3": "A különleges felszerelések itt jelennek meg a nagy eseményekkor.",
"rewardHelp4": "Ne légy rest saját jutalmakat beállítani! Nézz meg néhány példát jutalmakra.",
- "clickForHelp": "Kattints ide segítségért"
+ "clickForHelp": "Kattints ide segítségért",
+ "taskIdRequired": "\"taskId\" must be a valid UUID.",
+ "taskNotFound": "Task not found.",
+ "invalidTaskType": "Task type must be one of \"habit\", \"daily\", \"todo\", \"reward\".",
+ "cantDeleteChallengeTasks": "A task belonging to a challenge can't be deleted.",
+ "checklistOnlyDailyTodo": "Checklists are supported only on dailies and todos",
+ "checklistItemNotFound": "No checklist item was found with given id.",
+ "itemIdRequired": "\"itemId\" must be a valid UUID.",
+ "tagNotFound": "No tag item was found with given id.",
+ "tagIdRequired": "\"tagId\" must be a valid UUID corresponding to a tag belonging to the user.",
+ "positionRequired": "\"position\" is required and must be a number.",
+ "cantMoveCompletedTodo": "Can't move a completed todo.",
+ "directionUpDown": "\"direction\" is required and must be 'up' or 'down'",
+ "alreadyTagged": "The task is already tagged with given tag."
}
\ No newline at end of file
diff --git a/common/locales/it/backgrounds.json b/common/locales/it/backgrounds.json
index d7ebb864b3..3554fbb71d 100644
--- a/common/locales/it/backgrounds.json
+++ b/common/locales/it/backgrounds.json
@@ -154,11 +154,18 @@
"backgroundRainforestNotes": "Avventurati in una foresta pluviale.",
"backgroundStoneCircleText": "Cerchio di Pietre",
"backgroundStoneCircleNotes": "Lancia incantesimi all'interno di un Cerchio di Pietre.",
- "backgrounds042016": "SET 23: Released April 2016",
- "backgroundArcheryRangeText": "Archery Range",
- "backgroundArcheryRangeNotes": "Practice on the Archery Range.",
- "backgroundGiantFlowersText": "Giant Flowers",
- "backgroundGiantFlowersNotes": "Frolic atop Giant Flowers.",
- "backgroundRainbowsEndText": "End of the Rainbow",
- "backgroundRainbowsEndNotes": "Discover gold at the End of the Rainbow."
+ "backgrounds042016": "SERIE 23: Aprile 2016",
+ "backgroundArcheryRangeText": "Campo di tiro con l'arco",
+ "backgroundArcheryRangeNotes": "Fai pratica nel campo di tiro con l'arco.",
+ "backgroundGiantFlowersText": "Fiori Giganti",
+ "backgroundGiantFlowersNotes": "Divertiti in cima a dei fiori giganteschi.",
+ "backgroundRainbowsEndText": "Fine dell'Arcobaleno",
+ "backgroundRainbowsEndNotes": "Trova l'oro alla fine dell'arcobaleno!",
+ "backgrounds052016": "SERIE 24: Maggio 2016",
+ "backgroundBeehiveText": "Alveare",
+ "backgroundBeehiveNotes": "Ronza e danza in un alveare.",
+ "backgroundGazeboText": "Gazebo",
+ "backgroundGazeboNotes": "Combatti in un Gazebo.",
+ "backgroundTreeRootsText": "Radici dell'Albero",
+ "backgroundTreeRootsNotes": "Esplora le Radici dell'Albero."
}
\ No newline at end of file
diff --git a/common/locales/it/challenge.json b/common/locales/it/challenge.json
index 2b0982dcf2..b4b90e625e 100644
--- a/common/locales/it/challenge.json
+++ b/common/locales/it/challenge.json
@@ -63,5 +63,21 @@
"congratulations": "Congratulazioni!",
"hurray": "Evviva!",
"noChallengeOwner": "nessun proprietario",
- "noChallengeOwnerPopover": "Questa sfida non ha un proprietario perché la persona che ha creato la sfida ha eliminato il proprio account."
+ "noChallengeOwnerPopover": "Questa sfida non ha un proprietario perché la persona che ha creato la sfida ha eliminato il proprio account.",
+ "challengeMemberNotFound": "User not found among challenge's members",
+ "onlyGroupLeaderChal": "Only the group leader can create challenges",
+ "tavChalsMinPrize": "Prize must be at least 1 Gem for Tavern challenges.",
+ "cantAfford": "You can't afford this prize. Purchase more gems or lower the prize amount.",
+ "challengeIdRequired": "\"challengeId\" deve essere un UUID valido.",
+ "winnerIdRequired": "\"winnerId\" deve essere un UUID valido.",
+ "challengeNotFound": "Sfida non trovata.",
+ "onlyLeaderDeleteChal": "Only the challenge leader can delete it.",
+ "onlyLeaderUpdateChal": "Only the challenge leader can update it.",
+ "winnerNotFound": "Vincitore con id \"<%= userId %>\" non trovato o non ha partecipato a questa sfida.",
+ "noCompletedTodosChallenge": "\"includeComepletedTodos\" is not supported when fetching challenge tasks.",
+ "userTasksNoChallengeId": "When \"tasksOwner\" is \"user\" \"challengeId\" can't be passed.",
+ "onlyChalLeaderEditTasks": "Tasks belonging to a challenge can only be edited by the leader.",
+ "userAlreadyInChallenge": "L'utente sta già partecipando a questa sfida.",
+ "cantOnlyUnlinkChalTask": "Only broken challenges tasks can be unlinked.",
+ "shortNameTooShort": "Tag Name must have at least 3 characters."
}
\ No newline at end of file
diff --git a/common/locales/it/character.json b/common/locales/it/character.json
index a9b6e3819a..b0e4cfd5ae 100644
--- a/common/locales/it/character.json
+++ b/common/locales/it/character.json
@@ -1,10 +1,11 @@
{
+ "communityGuidelinesWarning": "Tieni presente che il tuo nome pubblico, la foto profilo e la sezione \"su di me\" devono rispettare le Linee guida della community (per esempio no parolacce, no argomenti per adulti, no insulti, ecc.). Se hai qualche dubbio se qualcosa sia appropriato o meno, puoi tranquillamente scrivere una e-mail a leslie@habitica.com!",
"statsAch": "Statistiche e medaglie",
"profile": "Profilo",
"avatar": "Personalizza Avatar",
"other": "Altro",
"fullName": "Nome completo",
- "displayName": "Nome mostrato",
+ "displayName": "Nome pubblico",
"displayPhoto": "Foto",
"displayBlurb": "Su di me",
"displayBlurbPlaceholder": "Scrivi una piccola presentazione",
@@ -34,16 +35,16 @@
"beard": "Barba",
"mustache": "Baffi",
"flower": "Fiore",
- "wheelchair": "Wheelchair",
+ "wheelchair": "Sedia a rotelle",
"basicSkins": "Skin base",
"rainbowSkins": "Skin arcobaleno",
"pastelSkins": "Skin pastello",
- "spookySkins": "Skin spettrale",
+ "spookySkins": "Skin spettrali",
"supernaturalSkins": "Skin Sovrannaturali",
"splashySkins": "Skin acquatiche",
"rainbowColors": "Colori arcobaleno",
"shimmerColors": "Colori luccicanti",
- "hauntedColors": "Colori Spettrali",
+ "hauntedColors": "Colori spettrali",
"winteryColors": "Colori invernali",
"equipment": "Equipaggiamento",
"equipmentBonus": "Equipaggiamento",
@@ -66,7 +67,7 @@
"ultimGearText": "Ha potenziato al massimo armi e armatura per le seguenti classi:",
"level": "Livello",
"levelUp": "Livello aumentato!",
- "gainedLevel": "Nuovo lìvello!",
+ "gainedLevel": "Nuovo livello!",
"leveledUp": "Raggiungendo i tuoi obiettivi nella vita reale, ora sei al livello <%= level %>!",
"fullyHealed": "Salute ripristinata!",
"huzzah": "Urrà!",
@@ -109,6 +110,7 @@
"mage": "Mago",
"mystery": "Mistero",
"changeClass": "Cambia classe, recupera Punti Attributo allocati",
+ "lvl10ChangeClass": "Per cambiare classe devi essere almeno al livello 10.",
"levelPopover": "Ogni volta che sali di livello ottieni un punto da assegnare ad un attributo a tua scelta. Puoi farlo manualmente, o lasciare che se ne occupi il gioco selezionando una delle opzioni di allocazione automatica.",
"unallocated": "Punti Attributo non allocati",
"haveUnallocated": "Hai <%= points %> Punto/i Attributo non allocato/i",
@@ -164,5 +166,7 @@
"int": "INT",
"showQuickAllocation": "Mostra allocazione delle statistiche",
"hideQuickAllocation": "Nascondi allocazione delle statistiche",
- "quickAllocationLevelPopover": "Ogni volta che sali di livello ottieni un punto da assegnare ad un attributo a tua scelta. Puoi farlo manualmente, o lasciare che se ne occupi il gioco selezionando una delle opzioni di allocazione automatica che trovi in Utente -> Statistiche."
+ "quickAllocationLevelPopover": "Ogni volta che sali di livello ottieni un punto da assegnare ad un attributo a tua scelta. Puoi farlo manualmente, o lasciare che se ne occupi il gioco selezionando una delle opzioni di allocazione automatica che trovi in Utente -> Statistiche.",
+ "invalidAttribute": "\"<%= attr %>\" non è un attributo valido.",
+ "notEnoughAttrPoints": "Non hai abbastanza punti attributo."
}
\ No newline at end of file
diff --git a/common/locales/it/content.json b/common/locales/it/content.json
index 70c07be561..e2e49133a8 100644
--- a/common/locales/it/content.json
+++ b/common/locales/it/content.json
@@ -112,7 +112,13 @@
"questEggMonkeyAdjective": "una dispettosa",
"questEggSnailText": "Chiocciola",
"questEggSnailMountText": "Chiocciola",
- "questEggSnailAdjective": "un lento ma costante",
+ "questEggSnailAdjective": "una lenta ma costante",
+ "questEggFalconText": "Falco",
+ "questEggFalconMountText": "Falco",
+ "questEggFalconAdjective": "un rapido",
+ "questEggTreelingText": "Treeling",
+ "questEggTreelingMountText": "Treeling",
+ "questEggTreelingAdjective": "un frondoso",
"eggNotes": "Trova una pozione per far schiudere questo uovo, e nascerà <%= eggAdjective(locale) %> <%= eggText(locale) %>.",
"hatchingPotionBase": "Base",
"hatchingPotionWhite": "Bianco",
@@ -126,6 +132,7 @@
"hatchingPotionGolden": "Oro",
"hatchingPotionSpooky": "Spettrale",
"hatchingPotionPeppermint": "Menta Piperita",
+ "hatchingPotionFloral": "Floreale",
"hatchingPotionNotes": "Versa questa pozione su un uovo, e nascerà un animale <%= potText(locale) %>.",
"premiumPotionAddlNotes": "Non utilizzabile su uova di animali ottenute dalle missioni.",
"foodMeat": "Carne",
diff --git a/common/locales/it/contrib.json b/common/locales/it/contrib.json
index b2069b4a6e..bcc1ed8c48 100644
--- a/common/locales/it/contrib.json
+++ b/common/locales/it/contrib.json
@@ -25,7 +25,7 @@
"readMore": "Maggiori informazioni",
"kickstartName": "Sostenitore Kickstarter - grado $<%= tier %>",
"kickstartText": "Ha sostenuto il progetto Kickstarter",
- "helped": "Ha aiutato Habit a crescere",
+ "helped": "Ha aiutato Habitica a crescere",
"helpedText1": "Ha aiutato Habitica a crescere, compilando",
"helpedText2": "questo sondaggio.",
"hall": "Salone degli Eroi",
@@ -35,8 +35,12 @@
"hallContributors": "Salone dei Collaboratori",
"hallPatrons": "Salone dei Mecenati",
"rewardUser": "Premia utente",
- "UUID": "UUID",
+ "UUID": "ID Utente",
"loadUser": "Carica utente",
+ "noAdminAccess": "Non puoi accedere come amministratore.",
+ "pageMustBeNumber": "req.query.page deve essere un numero",
+ "userNotFound": "Utente non trovato.",
+ "invalidUUID": "UUID deve essere valido",
"title": "Titolo",
"moreDetails": "Dettagli (1-7)",
"moreDetails2": "Dettagli (8-9)",
@@ -52,8 +56,8 @@
"visitHeroes": "Visita il Salone degli Eroi (sostenitori)",
"conLearn": "Maggiori informazioni sulle ricompense per i sostenitori.",
"conLearnHow": "Scopri come contribuire a migliorare Habitica",
- "surveysSingle": "Ha aiutato Habitica a crescere, compilando un questionario. Al momento non ci sono questionari attivi.",
- "surveysMultiple": "Ha aiutato Habitica a crescere, compilando <%= surveys %> questionari. Al momento non ci sono questionari attivi.",
+ "surveysSingle": "Helped Habitica grow, either by filling out a survey or helping with a major testing effort. Thank you!",
+ "surveysMultiple": "Helped Habitica grow on <%= surveys %> occasions, either by filling out a survey or helping with a major testing effort. Thank you!",
"currentSurvey": "Questionario in corso",
"surveyWhen": "Questa onoreficenza verrà conferita a tutti i partecipanti, non appena i risultati dei questionari saranno elaborati, verso fine marzo.",
"blurbInbox": "Qui vengono conservati i tuoi messaggi privati! Puoi inviare un messaggio a qualcuno cliccando sull'icona a forma di \"busta da lettere\" accanto al suo nome nella Taverna, nella Squadra o nella chat di una Gilda. Se hai ricevuto un messaggio privato dai contenuti inappropriati, dovresti inviare un'email a Lemoness (leslie@habitica.com) allegando uno screenshot.",
diff --git a/common/locales/it/death.json b/common/locales/it/death.json
index 5a1b2e03f4..366d1c2912 100644
--- a/common/locales/it/death.json
+++ b/common/locales/it/death.json
@@ -9,8 +9,9 @@
"toRegainHealth": "Per ripristinare la Salute:",
"lowHealthTips1": "Sali di livello per curarti completamente!",
"lowHealthTips2": "Compra una Pozione di Salute nella colonna delle Ricompense per ripristinare 15 punti Salute.",
- "losingHealthQuickly": "Perdi Salute rapidamente?",
- "lowHealthTips3": "Le Giornaliere incomplete ti danneggiano a fine giornata, quindi fai attenzione a non aggiungerne troppe all'inizio!",
+ "losingHealthQuickly": "Perdi punti Salute rapidamente?",
+ "lowHealthTips3": "Le Daily incomplete ti danneggiano a fine giornata, quindi fai attenzione a non aggiungerne troppe all'inizio!",
"lowHealthTips4": "Se una Giornaliera non va completata in un certo giorno, puoi disattivarla cliccando sull'icona a forma di matita.",
- "goodLuck": "Buona fortuna!"
+ "goodLuck": "Buona fortuna!",
+ "cannotRevive": "Impossibile rianimare se non sei morto"
}
\ No newline at end of file
diff --git a/common/locales/it/defaulttasks.json b/common/locales/it/defaulttasks.json
index 11806217d7..4d68e44378 100644
--- a/common/locales/it/defaulttasks.json
+++ b/common/locales/it/defaulttasks.json
@@ -1,7 +1,7 @@
{
"defaultHabit1Text": "Attività produttiva (Fai click sulla matita per modificare)",
"defaultHabit1Notes": "Esempi di buone abitudini: + Mangia della verdura + 15 minuti di attività produttiva",
- "defaultHabit2Text": "Mangia Cibo Spazzatura (Fai click sulla matita per modificare)",
+ "defaultHabit2Text": "Mangia cibo spazzatura (Fai click sulla matita per modificare)",
"defaultHabit2Notes": "Esempi di cattive abitudini: - Fumare - Procrastinare",
"defaultHabit3Text": "Usa le scale/l'ascensore (Fai click sulla matita per modificare)",
"defaultHabit3Notes": "Esempi di buone/cattive abitudini: +/- Ho usato le scale/ascensore ; +/- Bevuto acqua/bibita",
diff --git a/common/locales/it/front.json b/common/locales/it/front.json
index f1238c40aa..31493ed694 100644
--- a/common/locales/it/front.json
+++ b/common/locales/it/front.json
@@ -6,11 +6,11 @@
"althaireQuote": "Essere continuamente in missione mi motiva a completare tutte le mie daily e i miei to-do. La mia motivazione più grande è non deludere la mia squadra.",
"andeeliaoQuote": "Un sito fantastico, ho iniziato solo qualche giorno fa e sono già più produttiva e riesco a gestire meglio il mio tempo!",
"autumnesquirrelQuote": "Ora procrastino meno al lavoro e nelle faccende di casa, e pago le bollette in tempo.",
- "businessSample1": "Conferma 1 pagina dell'Inventario",
+ "businessSample1": "Conferma 1 pagina dell'inventario",
"businessSample2": "20 min di archiviazione",
"businessSample3": "Organizza le e-mail ricevute",
"businessSample4": "Prepara 1 documento per il cliente",
- "businessSample5": "Chiama Clienti/Rimanda le telefonate",
+ "businessSample5": "Chiama clienti/Rimanda le telefonate",
"businessText": "Usa Habitica per il tuo business",
"choreSample1": "Metti i panni sporchi nel cesto",
"choreSample2": "20 min di lavori di casa",
@@ -18,7 +18,7 @@
"choreSample4": "Riordina una stanza",
"choreSample5": "Lava e asciuga un carico di vestiti",
"chores": "Faccende di casa",
- "clearBrowserData": "Cancella i dati dal browser",
+ "clearBrowserData": "Pulizia dati del browser",
"communityBug": "Segnala un bug",
"communityExtensions": "Add-on ed estensioni",
"communityFacebook": "Facebook",
@@ -28,6 +28,7 @@
"communityReddit": "Reddit",
"companyAbout": "Come funziona",
"companyBlog": "Blog",
+ "devBlog": "Developer Blog",
"companyDonate": "Fai una donazione",
"companyExtensions": "Estensioni",
"companyPrivacy": "Privacy",
@@ -51,9 +52,10 @@
"featureSocialHeading": "Interazione sociale",
"featuredIn": "Apparso su",
"featuresHeading": "Altre caratteristiche...",
+ "footerDevs": "Sviluppatori",
"footerCommunity": "Community",
"footerCompany": "Compagnia",
- "footerMobile": "Cellulare",
+ "footerMobile": "App",
"footerSocial": "Social",
"forgotPass": "Password dimenticata",
"frabjabulousQuote": " [Habitica] è la ragione per cui ho ottenuto un fantastico lavoro ben retribuito... e ancor più miracolosamente, ora uso il filo interdentale ogni giorno!",
@@ -165,12 +167,12 @@
"teams": "Squadre",
"terms": "Termini e condizioni",
"testimonialHeading": "Cosa dicono gli utenti...",
- "localStorageTryFirst": "Se stai trovando problemi con Habitica, premi il bottone qui sotto per svuotare i dati locali in questo sito (gli altri non subiranno modifiche). Poi dovrai effettuare di nuovo l'accesso, perciò per prima cosa assicurati di conoscere in anticipo i dettagli del tuo log-in, che puoi trovare in Impostazioni-> <%= linkStart %>Site<%= linkEnd %>.",
- "localStorageTryNext": "Se il problema rimane, per favore <%= linkStart %>Riferite un Bug<%= linkEnd %> se non lo avete già fatto.",
- "localStorageClearing": "Vuotare la memoria locale",
- "localStorageClearingExplanation": "La memoria locale del browser è stata vuotata. Sarete disconnessi e inviati alla pagina iniziale. Per favore, attendete.",
- "localStorageClear": "Pulisci la memoria locale",
- "localStorageClearExplanation": "questo pulsante pulirà la memoria locale e vi disconnetterà",
+ "localStorageTryFirst": "Se stai avendo problemi con Habitica, premi il bottone qui sotto per pulire i dati locali di questo sito (gli altri non subiranno modifiche). Dovrai effettuare di nuovo l'accesso, perciò per prima cosa assicurati di conoscere in anticipo i tuoi dettagli per accedere al sito, che puoi trovare in Impostazioni -> <%= linkStart %>Sito<%= linkEnd %>.",
+ "localStorageTryNext": "Se il problema persiste, per favore <%= linkStart %>segnalate un bug<%= linkEnd %> se non lo avete già fatto.",
+ "localStorageClearing": "Pulizia memoria locale in corso",
+ "localStorageClearingExplanation": "Stiamo svuotando la memoria locale del tuo browser. Verrai disconnesso e inviato alla pagina principale. Per favore, attendi.",
+ "localStorageClear": "Pulisci memoria locale",
+ "localStorageClearExplanation": "Questo pulsante pulirà la memoria locale e vi disconnetterà",
"tutorials": "Guide",
"unlockByline1": "Raggiungi i tuoi obiettivi e sali di livello.",
"unlockByline2": "Sblocca nuovi metodi di motivazione, come collezionare animali, ricompense casuali, lanciare incantesimi e altro!",
@@ -181,14 +183,15 @@
"work": "Lavoro",
"zelahQuote": "Con [Habitica] riesco a persuadermi ad andare a letto in tempo con il pensiero di guadagnare punti per essere andata a dormire presto o perdere salute per esserci andata tardi!",
"reportAccountProblems": "Segnala un problema con l'account",
- "reportCommunityIssues": "Segnala un problema con la Community",
+ "reportCommunityIssues": "Segnala un problema con la community",
+ "subscriptionPaymentIssues": "Subscription and Payment Issues",
"generalQuestionsSite": "Domande generiche sul sito",
"businessInquiries": "Informazioni sul Business",
"merchandiseInquiries": "Informazioni sui Prodotti",
"marketingInquiries": "Informazioni su Marketing e Social Media",
"tweet": "Tweet",
"apps": "Applicazioni",
- "checkOutMobileApps": "Controlla le app sul tuo cellulare!",
+ "checkOutMobileApps": "Dai un'occhiata alle nostre app!",
"imagine1": "Immagina se migliorare la tua vita fosse divertente come un videogioco.",
"landingCopy1": "Avanza nel gioco completando attività nella vita reale.",
"landingCopy2": "Combatti mostri insieme ai tuoi amici per tenerti motivato e responsabile delle tue attività.",
@@ -197,7 +200,7 @@
"getStartedNow": "Comincia ora!",
"altAttrNavLogo": "Habitica Home",
"altAttrLifehacker": "Lifehacker",
- "altAttrNewYorkTimes": "Il New York Times",
+ "altAttrNewYorkTimes": "New York Times",
"altAttrMakeUseOf": "MakeUseOf",
"altAttrForbes": "Forbes",
"altAttrCnet": "CNet",
@@ -222,5 +225,32 @@
"altAttrWebstorm": "WebStorm",
"altAttrGithub": "GitHub",
"altAttrTrello": "Trello",
- "altAttrSlack": "Slack"
+ "altAttrSlack": "Slack",
+ "missingAuthHeaders": "Missing authentication headers.",
+ "missingAuthParams": "Missing authentication parameters.",
+ "missingUsernameEmail": "Missing username or email.",
+ "missingEmail": "Missing email.",
+ "missingUsername": "Missing username.",
+ "missingPassword": "Missing password.",
+ "missingNewPassword": "Missing new password.",
+ "wrongPassword": "Password errata.",
+ "notAnEmail": "Indirizzo e-mail non valido.",
+ "emailTaken": "Email address is already used in an account.",
+ "newEmailRequired": "Missing new email address.",
+ "usernameTaken": "Username already taken.",
+ "passwordConfirmationMatch": "Password confirmation doesn't match password.",
+ "invalidLoginCredentials": "Incorrect username and/or email and/or password.",
+ "passwordReset": "If we have your email on file, your password reset link has been sent to your email.",
+ "passwordResetEmailSubject": "Password Reset for Habitica",
+ "passwordResetEmailText": "Password for <%= username %> has been reset to <%= newPassword %> . Important! Both username and password are case-sensitive -- you must enter both exactly as shown here. We recommend copying and pasting both instead of typing them. Log in at <%= baseUrl %>. After you have logged in, head to <%= baseUrl %>/#/options/settings/settings and change your password.",
+ "passwordResetEmailHtml": "Password for <%= username %> has been reset to <%= newPassword %>.
Important! Both username and password are case-sensitive -- you must enter both exactly as shown here. We recommend copying and pasting both instead of typing them.
Log in at <%= baseUrl %>. After you have logged in, head to <%= baseUrl %>/#/options/settings/settings and change your password.",
+ "invalidLoginCredentialsLong": "Uh-oh - your username or password is incorrect.\n- Make sure your username or email is typed correctly.\n- You may have signed up with Facebook, not email. Double-check by trying Facebook login.\n- If you forgot your password, click \"Forgot Password\".",
+ "invalidCredentials": "There is no account that uses those credentials.",
+ "accountSuspended": "Account has been suspended, please contact leslie@habitica.com with your User ID \"<%= userId %>\" for assistance.",
+ "onlyFbSupported": "Only Facebook is supported currently.",
+ "cantDetachFb": "Account lacks another authentication method, can't detach Facebook.",
+ "onlySocialAttachLocal": "Local authentication can be added to only a social account.",
+ "invalidReqParams": "Invalid request parameters.",
+ "memberIdRequired": "\"member\" must be a valid UUID.",
+ "heroIdRequired": "\"heroId\" must be a valid UUID."
}
\ No newline at end of file
diff --git a/common/locales/it/gear.json b/common/locales/it/gear.json
index 64d8cd302c..76f379265f 100644
--- a/common/locales/it/gear.json
+++ b/common/locales/it/gear.json
@@ -187,8 +187,10 @@
"weaponArmoireJesterBatonNotes": "Con un movimento del tuo bastone e qualche replica arguta, anche le situazioni più complicate diventano chiare. Aumenta l'Intelligenza e la Percezione di <%= attrs %> ognuna. Baule Incantato: Set del Giullare (Oggetto 3 di 3).",
"weaponArmoireMiningPickaxText": "Piccone da Minatore",
"weaponArmoireMiningPickaxNotes": "Estrai quanto più oro possibile dalle tue attività! Aumenta la Percezione di <%= per %>. Baule Incantato: Set del Minatore (Oggetto 3 di 3).",
- "weaponArmoireBasicLongbowText": "Basic Longbow",
- "weaponArmoireBasicLongbowNotes": "A serviceable hand-me-down bow. Increases Strength by <%= str %>. Enchanted Armoire: Basic Archer Set (Item 1 of 3).",
+ "weaponArmoireBasicLongbowText": "Arco Lungo Base",
+ "weaponArmoireBasicLongbowNotes": "Un utile arco di seconda mano. Aumenta la Forza di <%= str %>. Baule Incantato: Set Base dell'Arciere (Oggetto 1 di 3).",
+ "weaponArmoireHabiticanDiplomaText": "Habitican Diploma",
+ "weaponArmoireHabiticanDiplomaNotes": "A certificate of significant achievement -- well done! Increases Intelligence by <%= int %>. Enchanted Armoire: Graduate Set (Item 1 of 3).",
"armor": "armatura",
"armorBase0Text": "Vestiti semplici",
"armorBase0Notes": "Vestiario comune. Non conferisce alcun bonus.",
@@ -364,6 +366,10 @@
"armorMystery201512Notes": "Evoca le gelide fiamme dell'inverno! Non conferisce alcun bonus. Oggetto per Abbonati, Dicembre 2015.",
"armorMystery201603Text": "Completo della Fortuna",
"armorMystery201603Notes": "Questo completo è cucito con migliaia di quadrifogli! Non conferisce benefici. Oggetto per abbonati, marzo 2016.",
+ "armorMystery201604Text": "Armatura di Foglie",
+ "armorMystery201604Notes": "You, too, can be a small but fearsome leaf puff. Confers no benefit. April 2016 Subscriber Item.",
+ "armorMystery201605Text": "Marching Bard Uniform",
+ "armorMystery201605Notes": "Unlike the traditional bards who join adventuring parties, bards who join Habitican marching bands are known for grand parades, not dungeon raids. Confers no benefit. May 2016 Subscriber Item.",
"armorMystery301404Text": "Completo Steampunk",
"armorMystery301404Notes": "Raffinato, a dir poco impeccabile! Non conferisce alcun bonus. Oggetto per abbonati, febbraio 3015.",
"armorArmoireLunarArmorText": "Armatura Lunare Lenitiva",
@@ -392,8 +398,10 @@
"armorArmoireJesterCostumeNotes": "Trallallà! A dispetto dell'aspetto, non sei affatto uno sciocco. Aumenta l'Intelligenza di <%= int %>. Baule Incantato: Set del Giullare (Oggetto 2 di 3).",
"armorArmoireMinerOverallsText": "Tuta da lavoro da Minatore",
"armorArmoireMinerOverallsNotes": "Può sembrare logora, ma è incantata per non sporcarsi. Aumenta la Costituzione di <%= con %>. Baule Incantato: Set del Minatore (Oggetto 2 di 3).",
- "armorArmoireBasicArcherArmorText": "Basic Archer Armor",
- "armorArmoireBasicArcherArmorNotes": "This camouflaged vest lets you slip unnoticed through the forests. Increases Perception by <%= per %>. Enchanted Armoire: Basic Archer Set (Item 2 of 3).",
+ "armorArmoireBasicArcherArmorText": "Armatura Base da Arciere",
+ "armorArmoireBasicArcherArmorNotes": "Questa veste mimetica ti permette di muoverti attraverso la foresta senza essere notato. Aumenta la Percezione di <%= per %>. Baule Incantato: Set Base dell'Arciere (Oggetto 2 di 3).",
+ "armorArmoireGraduateRobeText": "Veste da Laureato",
+ "armorArmoireGraduateRobeNotes": "Congratulations! This weighty robe hangs heavy with all the knowledge you have accrued. Increases Intelligence by <%= int %>. Enchanted Armoire: Graduate Set (Item 2 of 3).",
"headgear": "copricapo",
"headBase0Text": "Nessun elmo",
"headBase0Notes": "Non indossi nessun copricapo.",
@@ -402,7 +410,7 @@
"headWarrior2Text": "Cuffia di maglia",
"headWarrior2Notes": "Cappuccio formato da anelli di ferro intrecciati. Aumenta la Forza di <%= str %>.",
"headWarrior3Text": "Elmo a piastre",
- "headWarrior3Notes": "Elmo di spesso metallo, resistente contro qualsiasi colpo. Aumenta la Forza di <%= str %>.",
+ "headWarrior3Notes": "Elmo di metallo spesso, resistente contro qualsiasi colpo. Aumenta la Forza di <%= str %>.",
"headWarrior4Text": "Elmo Rosso",
"headWarrior4Notes": "I rubini incastonati ne aumentano il potere, e brillano quando chi lo indossa è infuriato. Aumenta la Forza di <%= str %>.",
"headWarrior5Text": "Elmo d'oro",
@@ -565,6 +573,10 @@
"headMystery201602Notes": "Nascondi la tua identità da tutti i tuoi ammiratori. Non conferisce alcun bonus. Oggetto per abbonati, febbraio 2016.",
"headMystery201603Text": "Cappello della Fortuna",
"headMystery201603Notes": "Questo cappello è un amuleto magico portafortuna. Non conferisce alcun bonus. Oggetto per abbonati, marzo 2016.",
+ "headMystery201604Text": "Corona di Fiori",
+ "headMystery201604Notes": "Questi fiori intrecciati formano un elmo sorprendentemente resistente! Non conferisce alcun bonus. Oggetto per abbonati, aprile 2016.",
+ "headMystery201605Text": "Marching Bard Hat",
+ "headMystery201605Notes": "Seventy-six dragons led the big parade, with a hundred and ten gryphons close at hand! Confers no benefit. May 2016 Subscriber Item.",
"headMystery301404Text": "Cilindro Elegante",
"headMystery301404Notes": "Un cilindro per i più fini gentiluomini! Oggetto per abbonati, gennaio 3015. Non conferisce alcun bonus.",
"headMystery301405Text": "Cilindro Base",
@@ -611,8 +623,10 @@
"headArmoireJesterCapNotes": "Le campanelle su questo cappello potrebbero distrarre i vostri avversari, ma aiutano voi a concentrarvi. Aumenta la Percezione di <%= per %>. Baule Incantato: Set del Giullare (Oggetto 1 di 3).",
"headArmoireMinerHelmetText": "Elmetto da Minatore",
"headArmoireMinerHelmetNotes": "Proteggi la tua testa dalle attività che cadono! Aumenta l'Intelligenza di<%= int %>. Baule Incantato: Set del Minatore (Oggetto 1 di 3).",
- "headArmoireBasicArcherCapText": "Basic Archer Cap",
- "headArmoireBasicArcherCapNotes": "No archer would be complete without a jaunty cap! Increases Perception by <%= per %>. Enchanted Armoire: Basic Archer Set (Item 3 of 3).",
+ "headArmoireBasicArcherCapText": "Cappello Base da Arciere",
+ "headArmoireBasicArcherCapNotes": "Un arciere non sarebbe tale senza un vivace cappello! Aumenta la Percezione di <%= per %>. Baule Incantato: Set Base dell'Arciere (Oggetto 3 di 3).",
+ "headArmoireGraduateCapText": "Cappello da Laureato",
+ "headArmoireGraduateCapNotes": "Congratulations! Your deep thoughts have earned you this thinking cap. Increases Intelligence by <%= int %>. Enchanted Armoire: Graduate Set (Item 3 of 3).",
"offhand": "oggetto per mano da scudo",
"shieldBase0Text": "Nessun equipaggiamento nella mano da scudo",
"shieldBase0Notes": "Nessuno scudo o arma secondaria.",
@@ -673,7 +687,7 @@
"shieldSpecialWinter2015HealerText": "Scudo Rilassante",
"shieldSpecialWinter2015HealerNotes": "Questo scudo respinge i venti gelidi. Aumenta la Costituzione di <%= con %>. Edizione limitata, inverno 2014-2015.",
"shieldSpecialSpring2015RogueText": "Squittio Esplosivo",
- "shieldSpecialSpring2015RogueNotes": "Non lasciarti ingannare dal suono - questi esplosivi hanno una carica potente! Aumenta la Forza di <%= str %>. Edizione limitata, primavera 2015.",
+ "shieldSpecialSpring2015RogueNotes": "Don't let the sound fool you - these explosives pack a punch. Increases Strength by <%= str %>. Limited Edition 2015 Spring Gear.",
"shieldSpecialSpring2015WarriorText": "Disco Piatto",
"shieldSpecialSpring2015WarriorNotes": "Tiralo ai tuoi nemici... Oppure tienilo, perchè si riempirà di gustose crocchette all'ora di cena. Aumenta la Costituzione di <%= con %>. Edizione limitata, primavera 2015.",
"shieldSpecialSpring2015HealerText": "Cuscino Decorato",
@@ -716,6 +730,8 @@
"shieldArmoireDragonTamerShieldNotes": "Distrai i nemici con questo scudo a forma di drago. Aumenta la Percezione di <%= per %>. Baule Incantato: Set del Domatore di Draghi (Oggetto 2 di 3).",
"shieldArmoireMysticLampText": "Lanterna Mistica",
"shieldArmoireMysticLampNotes": "Illumina le caverne più buie con questa lanterna mistica! Aumenta la Percezione di <%= per %>. Baule Incantato: Oggetto Indipendente.",
+ "shieldArmoireFloralBouquetText": "Mazzo di Fiori",
+ "shieldArmoireFloralBouquetNotes": "Non sono di grande aiuto in battaglia, ma non sono bellissimi? Aumenta la Costituzione di <%= con %>. Scrigno Incantato: Oggetto Indipendente.",
"back": "Accessorio da schiena.",
"backBase0Text": "Nessun accessorio da schiena",
"backBase0Notes": "Nessun accessorio da schiena.",
@@ -729,7 +745,7 @@
"backMystery201504Notes": "Bzz bzz bzz! Svolazza da un'attività all'altra. Non conferisce alcun bonus. Oggetto per abbonati, aprile 2015.",
"backMystery201507Text": "Tavola da surf da sballo",
"backMystery201507Notes": "Fai surf sui Moli Motivati e cavalca le onde sulla Baia Inkompleta! Non conferisce alcun bonus. Oggetto per abbonati, Luglio 2015.",
- "backMystery201510Text": "Coda di Goblin.",
+ "backMystery201510Text": "Coda di Goblin",
"backMystery201510Notes": "Prensile e potente! Non conferisce alcun bonus. Oggetto per abbonati, Ottobre 2015. ",
"backMystery201602Text": "Mantello del Rubacuori",
"backMystery201602Notes": "Con un fruscio del tuo mantello, i tuoi nemici cadono ai tuoi piedi. Non conferisce alcun beneficio. Oggetto per abbonati, febbraio 2016.",
@@ -815,11 +831,25 @@
"headAccessoryMystery201510Notes": "Queste terrificanti corna sono leggermente viscide. Non conferisce alcun bonus. Oggetto per abbonati, ottobre 2015.",
"headAccessoryMystery301405Text": "Occhiali da Testa",
"headAccessoryMystery301405Notes": "\"Gli occhiali sono per i tuoi occhi\", dicevano. \"Nessuno vuole degli occhiali solo per tenerli in testa\", dicevano. Hah! Ora mostra quanto si sbagliano! Non conferisce alcun bonus. Oggetto per abbonati, agosto 3015.",
- "headAccessoryArmoireComicalArrowText": "Comical Arrow",
- "headAccessoryArmoireComicalArrowNotes": "This whimsical item doesn't provide a stat boost, but it sure is good for a laugh! Confers no benefit. Enchanted Armoire: Independent Item.",
+ "headAccessoryArmoireComicalArrowText": "Freccia Comica",
+ "headAccessoryArmoireComicalArrowNotes": "Questo stravagante oggetto non aumenta alcuna statistica, ma è perfetta per farsi due risate! Non conferisce alcun bonus. Baule Incantato: Oggetto Indipendente.",
"eyewear": "Occhiali",
"eyewearBase0Text": "Nessuna benda",
"eyewearBase0Notes": "Nessun occhiale o benda.",
+ "eyewearSpecialBlackTopFrameText": "Occhiali Classici Neri",
+ "eyewearSpecialBlackTopFrameNotes": "Occhiali con una montatura nera sopra alle lenti. Non conferisce alcun bonus.",
+ "eyewearSpecialBlueTopFrameText": "Occhiali Classici Blu",
+ "eyewearSpecialBlueTopFrameNotes": "Occhiali con una montatura blu sopra alle lenti. Non conferisce alcun bonus.",
+ "eyewearSpecialGreenTopFrameText": "Occhiali Classici Verdi",
+ "eyewearSpecialGreenTopFrameNotes": "Occhiali con una montatura verde sopra alle lenti. Non conferisce alcun bonus.",
+ "eyewearSpecialPinkTopFrameText": "Occhiali Classici Rosa",
+ "eyewearSpecialPinkTopFrameNotes": "Occhiali con una montatura rosa sopra alle lenti. Non conferisce alcun bonus.",
+ "eyewearSpecialRedTopFrameText": "Occhiali Classici Rossi",
+ "eyewearSpecialRedTopFrameNotes": "Occhiali con una montatura rossa sopra alle lenti. Non conferisce alcun bonus.",
+ "eyewearSpecialWhiteTopFrameText": "Occhiali Classici Bianchi",
+ "eyewearSpecialWhiteTopFrameNotes": "Occhiali con una montatura bianca sopra alle lenti. Non conferisce alcun bonus.",
+ "eyewearSpecialYellowTopFrameText": "Occhiali Classici Gialli",
+ "eyewearSpecialYellowTopFrameNotes": "Occhiali con una montatura gialla sopra alle lenti. Non conferisce alcun bonus.",
"eyewearSpecialSummerRogueText": "Benda Furfantesca",
"eyewearSpecialSummerRogueNotes": "La benda per i furfanti con stile! Non conferisce alcun bonus. Edizione limitata, estate 2014.",
"eyewearSpecialSummerWarriorText": "Benda Affascinante",
diff --git a/common/locales/it/generic.json b/common/locales/it/generic.json
index 1cd9ed6db1..ba8c1cd38a 100644
--- a/common/locales/it/generic.json
+++ b/common/locales/it/generic.json
@@ -48,7 +48,7 @@
"market": "Mercato",
"subscriberItem": "Oggetto misterioso",
"newSubscriberItem": "Nuovo oggetto misterioso!",
- "subscriberItemText": "Ogni mese, gli abbonati ricevono un oggetto misterioso. Questo oggetto in genere viene rivelato circa una settimana prima della fine del mese. Leggi la pagina della wiki 'Mystery Item' per maggiori informazioni.",
+ "subscriberItemText": "Ogni mese, gli abbonati ricevono un oggetto misterioso. Questo oggetto in genere viene rivelato circa una settimana prima della fine del mese. Leggi la pagina 'Mystery Item' della wiki per maggiori informazioni.",
"all": "Tutto",
"none": "Nessuno",
"or": "Oppure",
@@ -106,7 +106,7 @@
"menu": "Menù",
"notifications": "Notifiche",
"noNotifications": "Non ci sono nuovi messaggi.",
- "clear": "Pulisci",
+ "clear": "Nascondi",
"endTour": "Fine giro",
"audioTheme": "Tema sonoro",
"audioTheme_off": "Off",
@@ -137,8 +137,8 @@
"achievementStressbeastText": "Ha contribuito alla sconfitta dell'Abominevole Mostro dello Stress durante l'evento Winter Wonderland 2014!",
"achievementBurnout": "Eroe dei Campi Fiorenti",
"achievementBurnoutText": "Ha contribuito alla sconfitta degli Spiriti dell'Esaurimento durante l'evento Festival d'Autunno 2015!",
- "achievementBewilder": "Savior of Mistiflying",
- "achievementBewilderText": "Helped defeat the Be-Wilder during the 2016 Spring Fling Event!",
+ "achievementBewilder": "Salvatore di Mistiflying",
+ "achievementBewilderText": "Ha contribuito alla sconfitta del Be-Wilder durante l'evento Spring Fling 2016!",
"checkOutProgress": "Guarda i miei progressi su Habitica!",
"cardReceived": "Hai ricevuto una cartolina!",
"cardReceivedFrom": "<%= cardType %> da <%= userName %>",
@@ -166,9 +166,9 @@
"birthday0": "Tanti auguri a te!",
"birthdayCardAchievementTitle": "Superbonus Compleanno",
"birthdayCardAchievementText": "Cento di questi giorni! Mandati o ricevuti <%= cards %> biglietti di compleanno",
- "streakAchievement": "Hai guadagnato una Medaglia Serie!",
+ "streakAchievement": "Hai ottenuto una Medaglia Serie!",
"firstStreakAchievement": "Serie di 21 giorni",
- "streakAchievementCount": "<%= streaks %> Serie di 21 giorni",
+ "streakAchievementCount": "<%= streaks %> serie di 21 giorni",
"twentyOneDays": "Hai completato la tua Daily per 21 giorni di fila!",
"dontBreakStreak": "Ottimo lavoro. Non interrompere la serie!",
"dontStop": "Non fermarti ora!",
@@ -177,6 +177,6 @@
"hatchPetShare": "Ho fatto nascere un nuovo animaletto completando le mie attività nella vita reale!",
"raisePetShare": "Ho fatto crescere un animaletto completando le mie attività nella vita reale!",
"wonChallengeShare": "Ho vinto una sfida in Habitica!",
- "achievementShare": "Ho guadagnato una nuova medaglia in Habitica!",
+ "achievementShare": "Ho ottenuto una nuova medaglia su Habitica!",
"orderBy": "Ordina per <%= item %>"
}
\ No newline at end of file
diff --git a/common/locales/it/groups.json b/common/locales/it/groups.json
index 1b5f6a57d4..0ca9f8aba1 100644
--- a/common/locales/it/groups.json
+++ b/common/locales/it/groups.json
@@ -13,7 +13,7 @@
"community": "Forum della Community",
"dataTool": "Visualizzazione dati utente (in inglese)",
"resources": "Risorse",
- "askQuestionNewbiesGuild": "Fai una domanda (Gilda dei Novellini)",
+ "askQuestionNewbiesGuild": "Fai una domanda (Gilda dei Novelli)",
"tavernTalk": "Chiacchiere in Taverna",
"tavernAlert1": "Nota: se stai segnalando un bug, gli sviluppatori non potranno aiutarti qui. In quel caso, per favore",
"tavernAlert2": "usa GitHub",
@@ -92,6 +92,7 @@
"send": "Invia",
"messageSentAlert": "Messaggio inviato!",
"pmHeading": "Messaggio privato a <%= name %>",
+ "pmsMarkedRead": "Il tuo messaggio stato segnato come letto",
"clearAll": "Cancella tutti i messaggi",
"confirmDeleteAllMessages": "Vuoi davvero cancellare tutti i messaggi ricevuti? Gli altri utenti potranno ancora vedere i messaggi che gli hai inviato.",
"optOutPopover": "Non ti piacciono i messaggi privati? Clicca per disattivare questa funzione.",
@@ -99,6 +100,15 @@
"unblock": "Sblocca",
"pm-reply": "Rispondi",
"inbox": "Messaggi",
+ "messageRequired": "Un messaggio è richiesto.",
+ "toUserIDRequired": "Un ID Utente è richesto",
+ "gemAmountRequired": "Un numero di gemme è richiesto",
+ "notAuthorizedToSendMessageToThisUser": "Non puoi inviare un messaggio a questo utente.",
+ "privateMessageGiftIntro": "Ciao <%= receiverName %>, <%= senderName %> ti ha inviato",
+ "privateMessageGiftGemsMessage": "<%= gemAmount %> gemme!",
+ "privateMessageGiftSubscriptionMessage": "<%= numberOfMonths %> mesi di abbonamento!",
+ "cannotSendGemsToYourself": "Non puoi inviare gemme a te stesso. Prova un abbonamento invece.",
+ "badAmountOfGemsToSend": "Amount must be within 1 and your current number of gems.",
"abuseFlag": "Segnala violazione delle linee guida della community",
"abuseFlagModalHeading": "Segnalare <%= name %> per violazione?",
"abuseFlagModalBody": "Sei sicuro di voler segnalare questo post? Dovresti segnalare SOLO i posti che violano le <%= firstLinkStart %>Linee guida della Community<%= linkEnd %> e/o i <%= secondLinkStart %>Termini di Servizio<%= linkEnd %>. Segnalare impropriamente un post viola le Linee guida della Community e può essere considerata come un'infrazione da parte tua. Motivazioni adeguate per segnalare un post possono essere:
imprecazioni, bestemmie
bigottismo, calunnie
argomenti per adulti
violenza, anche se per scherzo
spam, messaggi senza senso
",
@@ -151,5 +161,29 @@
"partyUpName": "Party Up",
"partyOnName": "Party On",
"partyUpAchievement": "Ti sei unito ad una Squadra con un'altra persona! Divertiti a combattere mostri e a sostenervi a vicenda.",
- "partyOnAchievement": "Ti sei unito ad una Squadra con almeno quattro persone! Goditi la tua maggiore responsabilità mentre ti unisci con i tuoi amici per sconfiggere i tuoi nemici!"
+ "partyOnAchievement": "Ti sei unito ad una Squadra con almeno quattro persone! Goditi la tua maggiore responsabilità mentre ti unisci con i tuoi amici per sconfiggere i tuoi nemici!",
+ "groupIdRequired": "\"groupId\" deve essere un UUID valido.",
+ "groupNotFound": "Gruppo non trovato.",
+ "groupTypesRequired": "You must supply a valid \"type\" query string.",
+ "questLeaderCannotLeaveGroup": "You cannot leave your party when you have started a quest. Abort the quest first.",
+ "cannotLeaveWhileActiveQuest": "Non puoi lasciare la squadra durante una missione. Per favore prima abbandona la missione.",
+ "onlyLeaderCanRemoveMember": "Only group leader can remove a member!",
+ "memberCannotRemoveYourself": "Non puoi rimuovere te stesso!",
+ "groupMemberNotFound": "Utente non trovato tra i membri del gruppo.",
+ "mustBeGroupMember": "Deve essere membro del gruppo.",
+ "keepOrRemoveAll": "req.query.keep must be either \"keep-all\" or \"remove-all\"",
+ "keepOrRemove": "req.query.keep must be either \"keep\" or \"remove\"",
+ "canOnlyInviteEmailUuid": "E' possibile invitare solo con uuid o e-mail.",
+ "inviteMissingEmail": "Indirizzo e-mail mancante nell'invito.",
+ "partyMustbePrivate": "Le squadre devono essere private",
+ "userAlreadyInGroup": "Utente già parte di quel gruppo.",
+ "userAlreadyInvitedToGroup": "Utente già invitato in quel gruppo.",
+ "userAlreadyPendingInvitation": "User already pending invitation.",
+ "userAlreadyInAParty": "Utente già in una squadra.",
+ "userWithIDNotFound": "Utente con id \"<%= userId %>\" non trovato.",
+ "userHasNoLocalRegistration": "L'utente non ha una registrazione locale (nome utente, e-mail, password).",
+ "uuidsMustBeAnArray": "User ID invites must be an array.",
+ "emailsMustBeAnArray": "Email address invites must be an array.",
+ "canOnlyInviteMaxInvites": "You can only invite \"<%= maxInvites %>\" at a time",
+ "onlyCreatorOrAdminCanDeleteChat": "Not authorized to delete this message!"
}
\ No newline at end of file
diff --git a/common/locales/it/limited.json b/common/locales/it/limited.json
index 3566e899b9..0e577356bb 100644
--- a/common/locales/it/limited.json
+++ b/common/locales/it/limited.json
@@ -5,7 +5,7 @@
"annoyingFriends": "Amici Fastidiosi",
"annoyingFriendsText": "I compagni di squadra gli hanno tirato <%= snowballs %> palle di neve.",
"alarmingFriends": "Amici Inquietanti",
- "alarmingFriendsText": "Sei stato spaventato <%= spookDust %> volte dai tuoi compagni di squadra.",
+ "alarmingFriendsText": "E' stato spaventato <%= spookySparkles %> volte dai compagni di squadra.",
"agriculturalFriends": "Amici Floreali",
"agriculturalFriendsText": "E' stato trasformato in un fiore <%= seeds %> volte dai compagni di squadra.",
"aquaticFriends": "Amici acquatici",
@@ -49,7 +49,7 @@
"seasonalItems": "Oggetti Stagionali",
"nyeCardAchievementTitle": "Vecchia conoscenza",
"nyeCardAchievementText": "Buon anno! Inviati o ricevuti <%= cards %> biglietti di auguri per il nuovo anno.",
- "nye0": "Buon anno! Che tu possa sconfiggere molte Habit cattive.",
+ "nye0": "Buon anno! Che tu possa sconfiggere molte cattive abitudini.",
"nye1": "Buon anno! Che tu possa ottenere un mare di Ricompense.",
"nye2": "Buon anno! Che tu possa trascorrere tanti Giorni Perfetti.",
"nye3": "Buon anno! Che la tua lista di To-Do resti corta e piacevole.",
@@ -72,5 +72,6 @@
"comfortingKittySet": "Gattino Confortante (Guaritore)",
"sneakySqueakerSet": "Roditore Furtivo (Assassino)",
"fallEventAvailability": "Disponibile fino al 31 ottobre",
- "winterEventAvailability": "Disponibili fino al 31 dicembre"
+ "winterEventAvailability": "Disponibili fino al 31 dicembre",
+ "springEventAvailability": "Disponibile fino al 31 maggio"
}
\ No newline at end of file
diff --git a/common/locales/it/maintenance.json b/common/locales/it/maintenance.json
new file mode 100644
index 0000000000..2eeb0c0d9c
--- /dev/null
+++ b/common/locales/it/maintenance.json
@@ -0,0 +1,34 @@
+{
+ "habiticaBackSoon": "Non ti preoccupare, Habitica tornerà presto!",
+ "importantMaintenance": "Stiamo effettuando delle importanti operazioni di manutenzione, stimiamo che dureranno fino al <%= localDate %> nel tuo fuso orario.",
+ "maintenance": "Manutenzione",
+ "maintenanceMoreInfo": "Want more information about the maintenance? <%= linkStart %>Check out our info page<%= linkEnd %>.",
+ "noDamageKeepStreaks": "NON riceverai alcun danno o perderai le tue Serie!",
+ "thanksForPatience": "Grazie per la tua pazienza!",
+ "twitterMaintenanceUpdates": "Per gli aggiornamenti più recenti, tieni d'occhio il nostro Twitter, dove pubblicheremo informazioni sullo stato della manutenzione.",
+ "veteranPetAward": "Alla fine, riceverai un raro animale veterano!",
+
+ "maintenanceInfoTitle": "Information about Upcoming Maintenance to Habitica",
+ "maintenanceInfoWhat": "Cosa sta succedendo?",
+ "maintenanceInfoWhatText": "On May 21, Habitica will be down for maintenance for most of the day. You will not take any damage or have your account harmed during that weekend, even if you can’t log in to check off your Dailies in time! We will be working very hard to make the downtime as short as possible, and will be posting regular updates on our Twitter account. At the end of the downtime, to thank everyone for their patience, you will all receive a rare pet!",
+ "maintenanceInfoWhy": "Perchè questo sta succedendo?",
+ "maintenanceInfoWhyText": "For the past several months, we have been thoroughly revamping Habitica behind-the-scenes. Specifically, we have rewritten the API. While it may not look much different on the surface, it’s a whole new world underneath. This will allow us WAY more flexibility when we want to build features in the future, and lead to improved performance!",
+ "maintenanceInfoTechDetails": "Want more details on the technical side of the process? Visit The Forge, our dev blog.",
+ "maintenanceInfoMore": "Maggiori informazioni",
+ "maintenanceInfoAccountChanges": "Che cambiamenti vedrò nel mio account dopo che la riscrittura sarà completa?",
+ "maintenanceInfoAccountChangesText": "At first, there won’t be any notable changes aside from performance improvements for features such as Challenges. If you notice any changes that shouldn’t be there, email us at admin@habitica.com and we will investigate them for you!",
+ "maintenanceInfoAddFeatures": "What kind of features will this allow Habitica to add?",
+ "maintenanceInfoAddFeaturesText": "Completing this rewrite will allow us to start building out improved chat and Guilds, plans for organizations and families, and additional productivity features like Monthlies and the ability to record yesterday’s activity! Those are all involved features on their own, so it will take time to build them, but until we were finished with this rewrite, there was no way we could start them.",
+ "maintenanceInfoHowLong": "Quanto durerà la manutenzione?",
+ "maintenanceInfoHowLongText": "We have to migrate tasks and data for all 1.3 million Habitica users -- not an easy task! We anticipate that it will take place between approximately 1pm Pacific Time (8pm UTC) and 10pm Pacific Time (5am UTC). Rest assured that we’re doing everything we can to make it go as quickly as possible! You can follow updates on our Twitter.",
+ "maintenanceInfoStatsAffected": "How will my Dailies, Streaks, Buffs, and Quests be affected?",
+ "maintenanceInfoStatsAffectedText1": "You will NOT take any damage or lose any streaks that weekend, but otherwise, your day will reset normally! Dailies that you checked will become unchecked, buffs will reset, etc. If you are in a Collection Quest, you will still find items. If you are in a Boss Battle, you will still deal damage to the Boss, but the Boss will not deal damage to you. (Even monsters need a break!)",
+ "maintenanceInfoStatsAffectedText2": "After a lot of thought, our team concluded that this was the most fair way to handle the fact that many users will not be able to check off their Dailies normally during the maintenance. We’re sorry for any inconvenience this causes!",
+ "maintenanceInfoSeeTasks": "What if I need to see my task list?",
+ "maintenanceInfoSeeTasksText": "If you know that you will need to see your task list on Saturday to remind yourself what you have to do, we recommend that before the maintenance begins, you take a screenshot of your tasks so that you can use it as a reference.",
+ "maintenanceInfoRarePet": "Che tipo di animale raro riceverò?",
+ "maintenanceInfoRarePetText": "To thank you for your patience during the downtime, everyone will get a rare Veteran Pet. If you’ve never received a Veteran Pet before, you will receive a Veteran Wolf. If you already have a Veteran Wolf, you will receive a Veteran Tiger. And if you already have a Veteran Wolf and a Veteran Tiger, you will receive a never-before-seen Veteran pet! After the migration is completed, it may take several hours for your pet to show up, but never fear, everyone will get one.",
+ "maintenanceInfoWho": "Chi ha lavorato su questo progetto immenso?",
+ "maintenanceInfoWhoText": "We’re glad you asked! It was spearheaded by our amazing contributor paglias, with lots of help from Blade, TheHollidayInn, SabreCat, Victor Pudeyev, TheUnknown, and Alys.",
+ "maintenanceInfoTesting": "The new version was also tirelessly tested by a bunch of our amazing open-source volunteers. Thank you -- we couldn't have done this without you."
+}
diff --git a/common/locales/it/noscript.json b/common/locales/it/noscript.json
index 2be7eaa71e..d9f385d329 100644
--- a/common/locales/it/noscript.json
+++ b/common/locales/it/noscript.json
@@ -1,6 +1,6 @@
{
"jsDisabledHeading": "Ahimé! Non hai abilitato JavaScript nel tuo browser",
- "jsDisabledHeadingFull": "Ahimé! Non hai abilitato JavaScript nel tuo browser, e senza, Habitica non può funzionare bene",
+ "jsDisabledHeadingFull": "Ahimé! Non hai abilitato JavaScript nel tuo browser e, senza di esso, Habitica non può funzionare bene",
"jsDisabledText": "Habitica non può funzionare correttamente senza di esso!",
"jsDisabledLink": "Per favore abilita JavaScript per continuare!"
}
\ No newline at end of file
diff --git a/common/locales/it/npc.json b/common/locales/it/npc.json
index 0f349e70a8..8898c6b5d0 100644
--- a/common/locales/it/npc.json
+++ b/common/locales/it/npc.json
@@ -7,7 +7,7 @@
"daniel": "Daniel",
"danielText": "Benvenuto nella Taverna! Resta per un po' e incontra la gente del posto. Se hai bisogno di riposare (vacanza? malattia?), ti sistemerò nella Locanda. Mentre riposi, le tue Daily non ti danneggeranno alla fine del giorno, ma potrai comunque spuntarle.",
"danielText2": "Fai attenzione: se stai partecipando ad una missione Boss, il boss ti danneggerà comunque per le Daily non completate dei tuoi compagni di squadra! Inoltre, il tuo danno al Boss (o la raccolta di oggetti) non avrà effetto finchè non lasci la Locanda.",
- "danielTextBroken": "Benvenuto nella Taverna... Credo... Se hai bisogno di riposare, ti sistemerò nella Locanda... Mentre riposi, le tue Giornaliere non ti danneggeranno alla fine del giorno, ma potrai comunque spuntarle... se ne hai le forze...",
+ "danielTextBroken": "Benvenuto nella Taverna... Credo... Se hai bisogno di riposare, ti sistemerò nella Locanda... Mentre riposi, le tue Daily non ti danneggeranno alla fine del giorno, ma potrai comunque spuntarle... se ne hai le forze...",
"danielText2Broken": "Oh... Se stai partecipando ad una missione Boss, il boss ti danneggerà comunque per le Daily non completate dei tuoi compagni di squadra... Inoltre, il tuo danno al Boss (o gli oggetti raccolti) non avrà effetto finché non lasci la Locanda...",
"alexander": "Alexander il Mercante",
"welcomeMarket": "Benvenuto nel Mercato! Compra uova rare e pozioni! Vendi la merce che ti avanza! Commissiona servizi utili! Vieni a vedere cosa abbiamo da offrire.",
@@ -21,6 +21,26 @@
"ian": "Ian",
"ianText": "Benvenuto nel Negozio delle Missioni! Qui puoi utilizzare le Pergamene delle missioni per combattere i mostri con i tuoi amici. Assicurati di controllare la nostra raffinata scelta di Pergamene delle missioni per l'acquisto a destra!",
"ianBrokenText": "Benvenuto nel Negozio delle Missioni... Qui puoi utilizzare le Pergamene delle missioni per combattere i mostri con i tuoi amici... Assicurati di controllare la nostra raffinata scelta di Pergamene delle missioni per l'acquisto a destra...",
+ "missingKeyParam": "\"req.params.key\" è richiesto.",
+ "itemNotFound": "Oggetto \"<%= key %>\" non trovato.",
+ "cannotBuyItem": "Non puoi comprare questo oggetto.",
+ "missingTypeKeyEquip": "\"key\" e \"type\" sono parametri richiesti.",
+ "missingPetFoodFeed": "\"pet\" e \"food\" sono parametri richiesti.",
+ "invalidPetName": "Nome dell'animale non valido",
+ "missingEggHatchingPotionHatch": "\"egg\" e \"hatchingPotion\" sono parametri richiesti.",
+ "invalidTypeEquip": "\"type\" deve essere uno tra 'equipped', 'pet', 'mount', 'costume'.",
+ "mustPurchaseToSet": "Devi acquistare <%= val %> per impostare <%= key %>.",
+ "typeRequired": "E necessario scrivere",
+ "keyRequired": "E' richiesta una chiave",
+ "notAccteptedType": "E' necessario scrivere [uova, Pozioni schiusura, cibo, missioni, equipaggiamento]",
+ "contentKeyNotFound": "Non trovata la chiave per il Contenuto <%= type %>",
+ "plusOneGem": "+1 Gemma",
+ "typeNotSellable": "L'oggetto non è in vendita. deve essere uno dei seguenti <%= acceptedTypes %>",
+ "userItemsKeyNotFound": "Non trovata la chiave per gli oggetti.utente <%= type %>",
+ "pathRequired": "E' richiesta la stringa guida",
+ "unlocked": "Sono disponibili nuovi oggetti",
+ "alreadyUnlocked": "Set completo già sbloccato.",
+ "alreadyUnlockedPart": "Set completo già parzialmente sbloccato.",
"USD": "(USD)",
"newStuff": "Novità",
"cool": "Ricordamelo più tardi",
@@ -64,6 +84,7 @@
"tourPetsPage": "Questa è la Scuderia! Dopo il livello 3, puoi far nascere degli animali utilizzando uova e pozioni. Quando fai schiudere un uovo nel Mercato, apparirà qui! Fai click sull'immagine di un animale per aggiungerlo al tuo avatar. Dagli da mangiare il cibo che troverai dopo il livello 3, e crescerà fino a diventare una potente cavalcatura.",
"tourMountsPage": "Una volta che avrai dato abbastanza cibo al tuo animale da trasformarlo in una cavalcatura, apparirà qui. (Animali, cavalcature e cibo sono disponibili dopo il livello 3.) Fai click su una cavalcatura per montare in sella!",
"tourEquipmentPage": "Qui è dove il tuo Equipaggiamento è immagazzinato! Il tuo Equipaggiamento da battaglia influenza le tue statistiche. Se desideri mostrare un Equipaggiamento diverso sul tuo avatar senza modificare le tue statistiche, clicca \"Abilita Costume.\"",
+ "equipmentAlreadyOwned": "Possiedi già quel pezzo di equipaggiamento",
"tourOkay": "Ok!",
"tourAwesome": "Fantastico!",
"tourSplendid": "Splendido!",
diff --git a/common/locales/it/pets.json b/common/locales/it/pets.json
index 30532bc78a..159b4bf145 100644
--- a/common/locales/it/pets.json
+++ b/common/locales/it/pets.json
@@ -12,6 +12,7 @@
"etherealLion": "Leone Etereo",
"veteranWolf": "Lupo Veterano",
"veteranTiger": "Tigre Veterana",
+ "veteranLion": "Leone Veterano",
"cerberusPup": "Cucciolo di Cerbero",
"hydra": "Idra",
"mantisShrimp": "Canocchia",
@@ -19,7 +20,7 @@
"orca": "Orca",
"royalPurpleGryphon": "Grifone Viola Reale",
"phoenix": "Fenice",
- "bumblebee": "Bumblebee",
+ "magicalBee": "Ape Magica",
"rarePetPop1": "Clicca sulla zampa d'oro per scoprire come ottenere questo raro animale contribuendo a migliorare Habitica!",
"rarePetPop2": "Come ottenere questo animale?",
"potion": "Pozione <%= potionType %>",
@@ -62,6 +63,7 @@
"hatchedPet": "E' nato un <%= egg %> <%= potion %>!",
"displayNow": "Mostra ora",
"displayLater": "Mostra più tardi",
+ "petNotOwned": "Non possiedi questo animale.",
"earnedCompanion": "Con tutta la tua produttività, ti sei guadagnato un nuovo compagno. Nutrilo per farlo crescere!",
"feedPet": "Dare da mangiare <%= article %><%= text %> al tuo <%= name %>?",
"useSaddle": "Mettere la sella a <%= pet %>?",
@@ -83,5 +85,8 @@
"petKeyBoth": "Libera entrambi",
"confirmPetKey": "Sei sicuro?",
"petKeyNeverMind": "Non ancora",
+ "petsReleased": "Animali liberati.",
+ "mountsAndPetsReleased": "Cavalcature e animali liberati",
+ "mountsReleased": "Cavalcature liberate",
"gemsEach": "gemme ciascuno"
}
\ No newline at end of file
diff --git a/common/locales/it/quests.json b/common/locales/it/quests.json
index 671c30e0b6..81612ac3ce 100644
--- a/common/locales/it/quests.json
+++ b/common/locales/it/quests.json
@@ -38,12 +38,12 @@
"bossDmg2": "Solo i partecipanti potranno combattere il boss e condividere il bottino della missione.",
"bossDmg1Broken": "Ogni Daily e To-Do completata e ogni Habit positiva danneggiano il boss... Puoi infliggere danni maggiori con le attività più rosse, con Attacco Brutale e con Fiammata... I boss danneggeranno ogni partecipante per le Daily mancate (moltiplicate per la Forza del boss) oltre al loro normale danno, quindi tieni in forze la tua squadra completando le tue Daily... I danni inflitti e ricevuti dal boss sono calcolati al cambio di giorno (all'ora da te impostata)...",
"bossDmg2Broken": "Solo i partecipanti potranno combattere il boss e dividersi il bottino della missione...",
- "tavernBossInfo": "Complete Dailies and To-Dos and score positive Habits to damage the World Boss! Incomplete Dailies fill the Rage Bar. When the Rage bar is full, the World Boss will attack an NPC. A World Boss will never damage individual players or accounts in any way. Only active accounts not resting in the Inn will have their tasks tallied.",
+ "tavernBossInfo": "Completa Daily e To-Do e completa Abitudini positive per danneggiare al World Boss! Le Daily non completate riempiono la barra della Furia. Quando la barra della Furia sarà piena, il World Boss attaccherà un NPC. Un World Boss non danneggerà mai i singoli giocatori o gli account in alcun modo. Solo le Daily dei giocatori attivi che non stanno riposando nella Locanda verranno tenute in conto.",
"tavernBossInfoBroken": "Competa Attività Giornaliere e To-Do e conquista Abitudini positive per danneggiare il Boss Mondiale! Attività Giornaliere non completate riempiono la Barra del Colpo Esaustivo. Quando la Barra del Colpo Esaustivo é piena, il Boss Mondiale attaccherà un PNG. Un Boss Mondiale non danneggerà mai giocatori individuali o account in nessuna maniera. Solo le Attivitá di account attivi che non stanno riposando nella Locanda verranno conteggiate.",
"bossColl1": "Per ottenere gli oggetti, completa delle attività positive. Gli oggetti delle missioni compaiono come quelli normali; non li vedrai però fino al giorno dopo, quando tutto quello che hai trovato verrà raccolto e aggiunto agli oggetti già trovati.",
- "bossColl2": "Solo i partecipanti potranno collezionare gli oggetti e condividere il bottino della missione.",
+ "bossColl2": "Solo i partecipanti possono raccogliere oggetti e condividere il bottino della missione.",
"bossColl1Broken": "Per raccogliere oggetti, completa le tue attività positive... Gli oggetti delle missioni compaiono come oggetti normali; tuttavia, non vedrai gli oggetti fino al giorno successivo, dopodiché tutto ciò che hai trovato verrà conteggiato e aggiunto ai tuoi oggetti.",
- "bossColl2Broken": "Solo i partecipanti potranno collezionare gli oggetti e condividere il bottino della missione.",
+ "bossColl2Broken": "Solo i partecipanti possono raccogliere oggetti e condividere il bottino della missione...",
"abort": "Annulla",
"leaveQuest": "Abbandona missione",
"sureLeave": "Vuoi davvero abbandonare la missione attiva? Tutti i tuoi progressi nella missione andranno persi.",
@@ -78,5 +78,24 @@
"whichQuestStart": "Che missione vuoi cominciare?",
"getMoreQuests": "Ottieni altre missioni",
"unlockedAQuest": "Hai sbloccato una missione!",
- "leveledUpReceivedQuest": "Sei salito al livello Livello <%= level %> e hai ricevuto una Pergamena!"
+ "leveledUpReceivedQuest": "Sei salito al livello Livello <%= level %> e hai ricevuto una Pergamena!",
+ "questInvitationDoesNotExist": "Non è stato ancora mandato alcun invito ad una missione.",
+ "questInviteNotFound": "Nessun invito ad una missione trovato.",
+ "guildQuestsNotSupported": "Le Gilde non possono essere invitate a partecipare ad una missione.",
+ "questNotFound": "Missione \"<%= key %>\" non trovata.",
+ "questNotOwned": "Non possiedi questa pergamena.",
+ "questNotGoldPurchasable": "La Missione \"<%= key %>\" non può essere comprata con Oro.",
+ "questLevelTooHigh": "Devi essere almeno di livello <%= level %> per iniziare questa missione.",
+ "questAlreadyUnderway": "La tua squadra è già in missione. Riprova quando la missione corrente è terminata.",
+ "questAlreadyAccepted": "Hai già accettato l'invito alla missione.",
+ "noActiveQuestToLeave": "Nessuna missione attiva da abbandonare",
+ "questLeaderCannotLeaveQuest": "Il capo della missione non la può abbandonare",
+ "notPartOfQuest": "Non fai parte della missione",
+ "noActiveQuestToAbort": "Non c'è una missione attiva da annullare.",
+ "onlyLeaderAbortQuest": "Solo il capo del gruppo o della missione può annullare una missione",
+ "questAlreadyRejected": "Hai già rifiutato l'invito alla missione",
+ "cantCancelActiveQuest": "Non è possibile annullare una ricerca attiva, utilizzare la funzionalità di interruzione ",
+ "onlyLeaderCancelQuest": "Solo il capo del gruppo o della missione può annullare una missione",
+ "questNotPending": "Non ci sono missioni da cominciare",
+ "questOrGroupLeaderOnlyStartQuest": "Solo il capo del gruppo o della missione può forzare l'inizio di una missione"
}
\ No newline at end of file
diff --git a/common/locales/it/questscontent.json b/common/locales/it/questscontent.json
index f6860edca0..b18d03e525 100644
--- a/common/locales/it/questscontent.json
+++ b/common/locales/it/questscontent.json
@@ -112,7 +112,7 @@
"questEggHuntCollectPlainEgg": "Uova Semplici",
"questEggHuntDropPlainEgg": "Uovo Semplice",
"questDilatoryText": "Il Drago Terrore dei Dilatori",
- "questDilatoryNotes": "We should have heeded the warnings.
Dark shining eyes. Ancient scales. Massive jaws, and flashing teeth. We've awoken something horrifying from the crevasse: the Dread Drag'on of Dilatory! Screaming Habiticans fled in all directions when it reared out of the sea, its terrifyingly long neck extending hundreds of feet out of the water as it shattered windows with its searing roar.
\"This must be what dragged Dilatory down!\" yells Lemoness. \"It wasn't the weight of the neglected tasks - the Dark Red Dailies just attracted its attention!\"
\"It's surging with magical energy!\" @Baconsaur cries. \"To have lived this long, it must be able to heal itself! How can we defeat it?\"
Why, the same way we defeat all beasts - with productivity! Quickly, Habitica, band together and strike through your tasks, and all of us will battle this monster together. (There's no need to abandon previous quests - we believe in your ability to double-strike!) It won't attack us individually, but the more Dailies we skip, the closer we get to triggering its Neglect Strike - and I don't like the way it's eyeing the Tavern....",
+ "questDilatoryNotes": "Dovremmo avere ascoltato gli avvertimenti.
Scuri occhi scintillanti. Antiche scale. Enormi mascelle, e denti lampeggianti. Abbiamo svegliato qualcosa di orribile dal crepaccio: Il terribile Trascinatore della Dilazione!Gli Habiticans fuggivano urlanti in ogni direzione quando egli emergeva dal mare,con il suo collo spaventosamente lungo che si estendeva per centinaia di piedi fuori dall'acqua, mentre mandava in frantumi le finestre con il suo rovente boato.
\"Questo deve essere quello che ha trascinato fin qui Dilatory!\" urla Lemoness. \"Non è stato il peso dei compiti trascurati - le Dailies rosso scuro hanno solo attirato la sua attenzione!\"
\"Si cura con l'energia magica!\" grida @Baconsaur. \"Per aver vissuto così a lungo, deve essere in grado di guarire se stesso! Come possiamo sconfiggerlo?
Beh, nello stesso modo in cui sconfiggiamo tutti gli animali - con la produttività! Presto, Habitica, uniamoci e colpiamo con le attività, e tutti insieme daremo battaglia a questo mostro. (Non c'è bisogno di abbandonare le missioni precedenti - noi crediamo nella vostra capacità di colpire doppio!) Non ci attaccherà individualmente, ma più Daily saltiamo, più ci avviciniamo a innescare il suo Colpo Negletto - e non mi piace il modo in cui sta fissando la Taverna....",
"questDilatoryBoss": "Il Drago Terrore dei Dilatori",
"questDilatoryBossRageTitle": "Colpo della Negligenza",
"questDilatoryBossRageDescription": "Quando questa barra sarà completamente piena, il Drago Terrore dei Dilatori scatenerà il caos sul terreno di Habitica",
@@ -121,7 +121,7 @@
"questDilatoryBossRageTavern": "Il Drago Terrore scaglia il COLPO DELLA NEGLIGENZA!\n\n\nOh no! Nonostante tutti i nostri sforzi, ci siamo lasciati scappare alcune Dailies, e il loro colore rosso scuro ha attirato la furia del Drago! Con il suo spaventoso Colpo della Negligenza, ha decimato la Taverna! Per fortuna, abbiamo aperto una Locanda in una città nei paraggi, e siete liberi di continuare a chiacchierare sulla riva... ma il povero Daniel il Barista ha appena visto il suo amato locale sbriciolarsi davanti ai suoi occhi!\n\n\nSpero che la bestia non attacchi di nuovo!",
"questDilatoryBossRageStables": "Il Drago Terrore scaglia il COLPO DELLA NEGLIGENZA!\n\nDiamine| Ancora una volta abbiamo lasciato troppe Dailies da parte. Il Drago ha scagliato il suo Colpo della Negligenza contro Matt e le stalle! Gli animali sono scappati in tutte le direzioni. Fortunatamente, sembrerebbe che i tuoi sono salvi!\n\nPovera Habitica! Spero che tutto questo non accada di nuovo. Sbrigati a svolgere tutte le tue attività!",
"questDilatoryBossRageMarket": "Il Drago Terrore scaglia il COLPO DELLA NEGLIGENZA!\n\nAhhh! Il negozio di Alex il mercante è stato ridotto in mille pezzi dal Colpo della Negligenza del Drago! Me sembra che stiamo davvero indebolendo questa bestia. Dubito che abbia abbastanza energia per scagliarne un altro.\n\nQuindi non esitare, Habitica! Scacciamo questa dannata bestia dai nostri lidi!",
- "questDilatoryCompletion": "La Sconfitta del Terrificante Disturbo-Drago di Dilatory`\n\nCe l'abbiamo fatta! Con un ultimo ruggito, il Terrificante Disturbo-Drago collassa e nuota via lontano, lontano. Una folla di esultanti abitanti di Habitica ricopre la costa! Abbiamo aiutato Matt, Daniel, e Alex a ricostruire i propri edifici. Ma che succede?\n\n`I cittadini ritornano!`\n\nOra che il Disturbo-Drago è scappato, migliaia di colori scintillanti colori stanno risalendo il mare. È un banco arcobaleno di Canocchie... E tra loro, centinaia di sirene!\n\n\"Siamo i cittadini perduti di Dilatory!\" spiega la loro leader, Manta. \"Quando Dilatory fu sommersa, le Canocchie che abitavano queste acque ci trasformarono in sirene con un incantesimo in modo che potessimo sopravvivere. Ma nella sua furia, il Terrificante Disturbo-Drago ci ha intrappolati tutti in quell'oscuro crepaccio. Siamo stati prigionieri lì per centinaia di anni - ma adesso finalmente siamo liberi di poter ricostruire la nostra città!\"\n\n\"Come ringraziamento,\" dice il suo amico @Ottl, \"Per favore accetta questo Animale Canocchia e questa Cavalcatura Canocchia, oltre a XP, oro e la nostra eterna gratitudine.\"\n\n`Ricompense`\n* Animale Canocchia\n* Cavalcatura Canocchia\n* Cioccolata, Zucchero Filato Blu, Zucchero Filato Rosa, Pesce, Miele, Carne, Latte, Patata, Carne Corrotta, Fragola",
+ "questDilatoryCompletion": "La Sconfitta del Terrificante Il Drago Terrore dei Dilatori`\n\nCe l'abbiamo fatta! Con un ultimo ruggito, il Terrificante Il Drago Terrore collassa e nuota via lontano, lontano. Una folla di esultanti abitanti di Habitica ricopre la costa! Abbiamo aiutato Matt, Daniel, e Alex a ricostruire i propri edifici. Ma che succede?\n\n`I cittadini ritornano!`\n\nOra che il Il Drago Terrore è scappato, migliaia di colori scintillanti colori stanno risalendo il mare. È un banco arcobaleno di Canocchie... E tra loro, centinaia di sirene!\n\n\"Siamo i cittadini perduti di Dilatory!\" spiega la loro leader, Manta. \"Quando Dilatory fu sommersa, le Canocchie che abitavano queste acque ci trasformarono in sirene con un incantesimo in modo che potessimo sopravvivere. Ma nella sua furia, il Terrificante Disturbo-Drago ci ha intrappolati tutti in quell'oscuro crepaccio. Siamo stati prigionieri lì per centinaia di anni - ma adesso finalmente siamo liberi di poter ricostruire la nostra città!\"\n\n\"Come ringraziamento,\" dice il suo amico @Ottl, \"Per favore accetta questo Animale Canocchia e questa Cavalcatura Canocchia, oltre a XP, oro e la nostra eterna gratitudine.\"\n\n`Ricompense`\n* Animale Canocchia\n* Cavalcatura Canocchia\n* Cioccolata, Zucchero Filato Blu, Zucchero Filato Rosa, Pesce, Miele, Carne, Latte, Patata, Carne Corrotta, Fragola",
"questSeahorseText": "Il Derby Dilatorio",
"questSeahorseNotes": "Il Giorno del Derby è arrivato, e abitanti di ogni parte di Habitica sono giunti a Dilatory per far gareggiare i loro cavallucci di mare! All'improvviso dal tracciato della gara senti un frastuono di ringhi e tonfi nell'acqua. Lo stalliere di cavallucci marini @Kiwibot grida al di sopra del fragore delle onde: \"Tutti questi cavallucci hanno attirato lo Stallone di Mare! Distrugerà le stalle e rovinerà l'antico tracciato! Qualcuno può fermarlo?\"",
"questSeahorseCompletion": "Lo stallone marino, finalmente domato, nuota docile accanto a te. \"Oh, guarda!\" esclama Kiwibot, \"Vuole che ci occupiamo dei suoi cuccioli\". Ti porge tre uova, dicendo: \"Crescili bene! E sappi che sarai sempre il benvenuto al Derby Dilatorio!\"",
@@ -142,7 +142,7 @@
"questAtom3Boss": "Il Bucatomante",
"questAtom3DropPotion": "Pozione di schiusura base",
"questOwlText": "Il Gufo Notturno",
- "questOwlNotes": "The Tavern light is lit 'til dawn Until one eve the glow is gone! How can we see for our all-nighters? @Twitching cries, \"I need some fighters! See that Night-Owl, starry foe? Fight with haste and do not slow! We'll drive its shadow from our door, And make the night shine bright once more!\"",
+ "questOwlNotes": "La luce della Taverna è accesa fino all'alba Finché una notte la luce è andata! Come possiamo vedere per tutti i nostri nottambuli? @Twitching grida: \"Ho bisogno di alcuni combattenti! Vedete quel nemico Gufo-Notturno? Lottate in fretta e non rallentate! guideremo la sua ombra via dalla nostra porta, e renderemo la notte di nuovo luminosa! \"",
"questOwlCompletion": "Ben prima dell'alba svanisce il Gufo ma comunque ormai tu sei stufo. Forse è tempo di riposare un po'? Poi sul giaciglio vedi un nido, ohibò! Il Gufo Notturno sa che può esser bello nella notte aspettar che canti il gallo ma cinguetterà gentile il tuo gufetto per dirti che è ora di andare a letto.",
"questOwlBoss": "Il Gufo Notturno",
"questOwlDropOwlEgg": "Gufo (uovo)",
@@ -291,22 +291,34 @@
"questMonkeyCompletion": "\nCe l'avete fatta! Niente banane per quei demoni oggi. Sopraffatte dalla vostra diligenza, le scimmie fuggono in preda al panico. \"Guarda \", dice @Misceo . \" Hanno lasciato indietro un paio di uova.
@Leephon sogghigna. \"Forse una scimmietta ben addestrata vi può aiutare tanto quanto quelle selvatiche vi ostacolano ! \"",
"questMonkeyBoss": "Mostruoso Mandrillo",
"questMonkeyDropMonkeyEgg": "Scimmia (uovo)",
- "questMonkeyUnlockText": "Permette di acquistare le uova di Scimmia nel Mercato",
+ "questMonkeyUnlockText": "Sblocca l'acquisto delle uova di scimmia nel Mercato",
"questSnailText": "La Chiocciola della Fogna Sgobbosa",
"questSnailNotes": "Siete entusiasti di iniziare ad esplorare le Segrete abbandonate di Sgobbosa, ma non appena entrati, sentite che il terreno sotto i vostri piedi sta iniziando a risucchiarvi gli stivali. Scrutando il percorso davanti a voi e vedete alcuni Habiticans impantanati nella melma. @Overomega urla, \"hanno troppe attività e daily di poco conto, e sono rimasti bloccati su cose poco importanti! Tirateli fuori!\"
\"Dovete trovare la fonte della melma,\" concorda @Pfeffernusse , \"o le attività che non possono compiere li trascineranno a fondo per sempre! \"
Tirando fuori la vostra arma, guadate attraverso il fango appiccicoso .... e incontrate la temibile Lumaca della Fogna Sgobbosa. ",
"questSnailCompletion": "Affondate la vostra arma sul gran guscio della Chiocciola spaccandolo in due e liberando un flusso d'acqua. La melma è lavata via, e gli Habiticans intorno a voi gioiscono. \"Guardate!\" dice @Misceo. \"C'è un piccolo gruppo di uova di lumaca nei resti del limo.\"",
"questSnailBoss": "Chiocciola della Fogna Sgobbosa",
"questSnailDropSnailEgg": "Chiocciola (Uovo)",
"questSnailUnlockText": "Sblocca l'acquisto delle uova di chiocciola nel Mercato",
- "questBewilderText": "The Be-Wilder",
- "questBewilderNotes": "The party begins like any other.
The appetizers are excellent, the music is swinging, and even the dancing elephants have become routine. Habiticans laugh and frolic amid the overflowing floral centerpieces, happy to have a distraction from their least-favorite tasks, and the April Fool whirls among them, eagerly providing an amusing trick here and a witty twist there.
As the Mistiflying clock tower strikes midnight, the April Fool leaps onto the stage to give a speech.
“Friends! Enemies! Tolerant acquaintances! Lend me your ears.” The crowd chuckles as animal ears sprout from their heads, and they pose with their new accessories.
“As you know,” the Fool continues, “my confusing illusions usually only last a single day. But I’m pleased to announce that I’ve discovered a shortcut that will guarantee us non-stop fun, without having to deal with the pesky weight of our responsibilities. Charming Habiticans, meet my magical new friend... the Be-Wilder!”
Lemoness pales suddenly, dropping her hors d'oeuvres. “Wait! Don’t trust--”
But suddenly mists are pouring into the room, glittering and thick, and they swirl around the April Fool, coalescing into cloudy feathers and a stretching neck. The crowd is speechless as an monstrous bird unfolds before them, its wings shimmering with illusions. It lets out a horrible screeching laugh.
“Oh, it has been ages since a Habitican has been foolish enough to summon me! How wonderful it feels, to have a tangible form at last.”
Buzzing in terror, the magic bees of Mistiflying flee the floating city, which sags from the sky. One by one, the brilliant spring flowers wither up and wisp away.
“My dearest friends, why so alarmed?” crows the Be-Wilder, beating its wings. “There’s no need to toil for your rewards any more. I’ll just give you all the things that you desire!”
A rain of coins pours from the sky, hammering into the ground with brutal force, and the crowd screams and flees for cover. “Is this a joke?” Baconsaur shouts, as the gold smashes through windows and shatters roof shingles.
PainterProphet ducks as lightning bolts crackle overhead, and fog blots out the sun. “No! This time, I don’t think it is!”
Quickly, Habiticans, don’t let this World Boss distract us from our goals! Stay focused on the tasks that you need to complete so we can rescue Mistiflying -- and hopefully, ourselves.",
- "questBewilderCompletion": "The Be-Wilder is DEFEATED!
We've done it! The Be-Wilder lets out a ululating cry as it twists in the air, shedding feathers like falling rain. Slowly, gradually, it coils into a cloud of sparkling mist. As the newly-revealed sun pierces the fog, it burns away, revealing the coughing, mercifully human forms of Bailey, Matt, Alex.... and the April Fool himself.
Mistiflying is saved!
The April Fool has enough shame to look a bit sheepish. “Oh, hm,” he says. “Perhaps I got a little…. carried away.”
The crowd mutters. Sodden flowers wash up on sidewalks. Somewhere in the distance, a roof collapses with a spectacular splash.
“Er, yes,” the April Fool says. “That is. What I meant to say was, I’m dreadfully sorry.” He heaves a sigh. “I suppose it can’t all be fun and games, after all. It might not hurt to focus occasionally. Maybe I’ll get a head start on next year’s pranking.”
Redphoenix coughs meaningfully.
“I mean, get a head start on this year’s spring cleaning!” the April Fool says. “Nothing to fear, I’ll have Habit City in spit-shape soon. Luckily nobody is better than I at dual-wielding mops.”
Encouraged, the marching band starts up.
It isn’t long before all is back to normal in Habit City. Plus, now that the Be-Wilder has evaporated, the magical bees of Mistiflying bustle back to work, and soon the flowers are blooming and the city is floating once more.
As Habiticans cuddle the magical fuzzy bees, the April Fool’s eyes light up. “Oho, I’ve had a thought! Why don’t you all keep some of these fuzzy Bee Pets and Mounts? It’s a gift that perfectly symbolizes the balance between hard work and sweet rewards, if I’m going to get all boring and allegorical on you.” He winks. “Besides, they don’t have stingers! Fool’s honor.”",
- "questBewilderCompletionChat": "`The Be-Wilder is DEFEATED!`\n\nWe've done it! The Be-Wilder lets out a ululating cry as it twists in the air, shedding feathers like falling rain. Slowly, gradually, it coils into a cloud of sparkling mist. As the newly-revealed sun pierces the fog, it burns away, revealing the coughing, mercifully human forms of Bailey, Matt, Alex.... and the April Fool himself.\n\n`Mistiflying is saved!`\n\nThe April Fool has enough shame to look a bit sheepish. “Oh, hm,” he says. “Perhaps I got a little…. carried away.”\n\nThe crowd mutters. Sodden flowers wash up on sidewalks. Somewhere in the distance, a roof collapses with a spectacular splash.\n\n“Er, yes,” the April Fool says. “That is. What I meant to say was, I’m dreadfully sorry.” He heaves a sigh. “I suppose it can’t all be fun and games, after all. It might not hurt to focus occasionally. Maybe I’ll get a head start on next year’s pranking.”\n\nRedphoenix coughs meaningfully.\n\n“I mean, get a head start on this year’s spring cleaning!” the April Fool says. “Nothing to fear, I’ll have Habit City in spit-shape soon. Luckily nobody is better than I at dual-wielding mops.”\n\nEncouraged, the marching band starts up.\n\nIt isn’t long before all is back to normal in Habit City. Plus, now that the Be-Wilder has evaporated, the magical bees of Mistiflying bustle back to work, and soon the flowers are blooming and the city is floating once more.\n\nAs Habiticans cuddle the magical fuzzy bees, the April Fool’s eyes light up. “Oho, I’ve had a thought! Why don’t you all keep some of these fuzzy Bee Pets and Mounts? It’s a gift that perfectly symbolizes the balance between hard work and sweet rewards, if I’m going to get all boring and allegorical on you.” He winks. “Besides, they don’t have stingers! Fool’s honor.”",
+ "questBewilderText": "Be-Wilder",
+ "questBewilderNotes": "La festa inizia come qualsiasi altra.
Gli antipasti sono eccellenti, la musica è allegra, e anche gli elefanti danzanti sono diventati di routine. Gli Habiticans ridono e giocano in mezzo ai centrotavola traboccanti di fiori, felici di avere una distrazione dai loro compiti meno amati, e il Pesce d'Aprile volteggia tra di loro, distribuendo con entusiasmo un trucco divertente qui e un tocco spiritoso lì.
Appena l'orologio della torre di Fantalata batte la mezzanotte, il Pesce d'Aprile balza sul palco per pronunciare un discorso.
\"Amici! Nemici! conoscenti tolleranti! Prestatemi le orecchie. \"La folla ridacchia mentre orecchie da animali spuntano sulle loro teste, ed essi si mettono in posa con i loro nuovi accessori
\"Come sapete,\" il Pesce continua, \"le mie frastornanti illusioni di solito durano solo un giorno. Ma sono lieto di annunciare che ho scoperto una scorciatoia che ci garantirà il divertimento non-stop, senza avere a che fare con il peso fastidioso delle nostre responsabilità. Fascinosi Habiticans, vi presento il mio nuovo amico magico ... Be-Wilder! \"
Lemoness impallidisce all'improvviso, lasciando cadere le tartine. \"Hey! Non fidat-- \"
Ma improvvisamente una nebbia spessa e baluginante si riversa nella stanza, e turbina intorno al Pesce d'Aprile, condensandosi in piume nuvolose e in un collo allungato. La folla è senza parole, mentre un uccello mostruoso spiega davanti a loro, le ali luccicanti di illusioni. Si lascia sfuggire un'orribile risata stridente .
\"Oh, sono passati secoli da quando un Habitican è stato così sciocco da evocarmi! Che meraviglia, avere una forma tangibile, finalmente. \"
Ronzando di terrore, le api magiche di Fantalata fuggono dalla città galleggiante, che incurva dal cielo. Uno per uno, i brillanti fiori primaverili appassiscono e WISP via.
\"Miei carissimi amici, perché siete così allarmati?\" gracchia Be-Wilder, battendo le ali. \"Non c'è più bisogno di faticare per i premi. Vi darò io tutte le cose che desiderate! \"
Una pioggia di monete si riversa dal cielo, martellando la terra con forza brutale, e la folla urla e fugge cercando riparo. \"È uno scherzo?\" grida Baconsaur, mentre l'oro rompe attraverso le finestre e frantuma le tegole del tetto.
PainterProphet si accuccia, mentre i fulmini gli crepitano in testa, e la nebbia oscura il sole. \"No! Questa volta, non credo accadrà! \"|
Presto, Habiticans, non lasciate che questo Boss mondiale ci distragga dai nostri obiettivi! Rimanete concentrati sui compiti che è necessario completare in modo che possiamo salvare Fantalata - e, si spera, noi stessi. ",
+ "questBewilderCompletion": "Il Be-Wilder è SCONFITTO!
Ce l'abbiamo fatta! Il Be-Wilder lancia un grido ululante mentre si contorce in aria, Lasciando cadere una pioggia di piume. Lentamente, a poco a poco, si raccoglie in una nuvola di nebbia baluginante. Mentre il sole appena rivelato penetra la nebbia, esso brucia via, rivelando, le forme miserevolmente umane e tossicchianti di Bailey, Matt, Alex .... e dello stesso Pesce d'Aprile.
Fantalata è salva! strong>
Il Pesce d'Aprile ha abbastanza vergogna da apparire un po 'imbarazzato. \"Oh, hm,\" dice. \"Forse mi sono lasciato un po'... trasportare. \"
La folla borbotta. Fiori fradici scorrono via dai marciapiedi. Da qualche parte, in lontananza, un tetto crolla in una spettacolare nuvola di polvere.
\"Ehm, sì\", dice il Pesce d'Aprile. \"Proprio così. Quello che intendevo dire era, che mi dispiace terribilmente. \"Emette un sospiro. \"Suppongo che non possa essere tutto divertimento e giochi, dopo tutto. Potrebbe non esser male concentrarsi di tanto in tanto. Forse avrò una nuova grande partenza sullo Scherzo del prossimo anno.\"
Redphoenix tossisce con intenzione.
\"Voglio dire, una nuova partenza sulla pulizia di primavera di quest'anno!\", Dice il Pesce d'Aprile. \"Non temete, avrò Habit City in grande spolvero al più presto. Fortunatamente nessuno è meglio di me a maneggiare il doppio mop. \"
Incoraggiata, la banda musicale si avvia.
Non passa molto tempo prima che tutto sia tornato alla normalità in Habit City. In più, ora che il Be-Wilder è evaporato, le api magiche di Fantalata tornano a tutta forza al lavoro, e presto i fiori sono in fiore e la città fluttua di nuovo.
mentre gli Habiticans coccolano le magiche api lanuginose, gli occhi del Pesce d'Aprile si illuminano. \"Oho, ho avuto un pensiero! Perché tutti voi non tenete alcune di queste Api lanuginose come animali e Cavalcature? E 'un dono che simboleggia perfettamente l'equilibrio tra il duro lavoro e dolci ricompense, per dirvela in modo noioso ed allegorico.\" Ammicca. \"Inoltre, non hanno pungiglione! Parola di Pesce \".",
+ "questBewilderCompletionChat": "Il Be-Wilder è SCONFITTO! \n\nCe l'abbiamo fatta! Il Be-Wilder lancia un grido ululante mentre si contorce in aria, Lasciando cadere una pioggia di piume. Lentamente, a poco a poco, si raccoglie in una nuvola di nebbia baluginante. Mentre il sole appena rivelato penetra la nebbia, esso brucia via, rivelando, le forme miserevolmente umane e tossicchianti di Bailey, Matt, Alex .... e dello stesso Pesce d'Aprile.\n\n Fantalata è salva! \n\n Il Pesce d'Aprile ha abbastanza vergogna da apparire un po 'imbarazzato. \"Oh, hm,\" dice. \"Forse mi sono lasciato un po'... trasportare. \"\n\n La folla borbotta. Fiori fradici scorrono via dai marciapiedi. Da qualche parte, in lontananza, un tetto crolla in una spettacolare nuvola di polvere. \n\n \"Ehm, sì\", dice il Pesce d'Aprile. \"Proprio così. Quello che intendevo dire era, che mi dispiace terribilmente. \"Emette un sospiro. \"Suppongo che non possa essere tutto divertimento e giochi, dopo tutto. Potrebbe non esser male concentrarsi di tanto in tanto. Forse avrò una nuova grande partenza sullo Scherzo del prossimo anno.\"\n\nRedphoenix tossisce con intenzione. \n\n \"Voglio dire, una nuova partenza sulla pulizia di primavera di quest'anno!\", Dice il Pesce d'Aprile. \"Non temete, avrò Habit City in grande spolvero al più presto. Fortunatamente nessuno è meglio di me a maneggiare il doppio mop. \" \n\n Incoraggiata, la banda musicale si avvia. \n\n Non passa molto tempo prima che tutto sia tornato alla normalità in Habit City. In più, ora che il Be-Wilder è evaporato, le api magiche di Fantalata tornano a tutta forza al lavoro, e presto i fiori sono in fiore e la città fluttua di nuovo. \n\nMentre gli Habiticans coccolano le magiche api lanuginose, gli occhi del Pesce d'Aprile si illuminano. \"Oho, ho avuto un pensiero! Perché tutti voi non tenete alcune di queste Api lanuginose come animali e Cavalcature? E 'un dono che simboleggia perfettamente l'equilibrio tra duro lavoro e dolci ricompense, per dirvela in modo noioso ed allegorico.\" Ammicca. \"Inoltre, non hanno pungiglione! Parola di Pesce \".",
"questBewilderBossRageTitle": "Beguilement Strike",
- "questBewilderBossRageDescription": "When this gauge fills, The Be-Wilder will unleash its Beguilement Strike on Habitica!",
- "questBewilderDropBumblebeePet": "Bumblebee (Pet)",
- "questBewilderDropBumblebeeMount": "Bumblebee (Mount)",
- "questBewilderBossRageMarket": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nOh no! Despite our best efforts, we've gotten distracted by the Be-Wilder’s charming illusions and have forgotten to do some of our Dailies! With a cackling cry, the shining bird beats its wings, raising a swarm of mist around Alex the Merchant. When the fog clears, he has been possessed! “Have some free samples!” he shouts gleefully, and begins to hurl exploding eggs and potions at fleeing Habiticans. Not the most favorable of sales, to be sure.\n\nHurry! Let's stay focused on our Dailies to defeat this monster before it possesses someone else.",
- "questBewilderBossRageStables": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nAhh!!! Once again the Be-Wilder has dazzled us into neglecting our Dailies, and now it has attacked Matt the Beast Master! With a swirl of mist, Matt transforms into a terrifying winged creature, and all the pets and mounts howl sadly in their stables. Quickly, stay focused on your tasks to defeat this dastardly distraction!",
- "questBewilderBossRageBailey": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nLook out! In the middle of reporting the news, Bailey the Town Crier has been possessed by the Be-Wilder! She lets out an evil, uninformative screech as she rises into the air. Now how will we know what’s going on?\n\nDon't give up... we're so close to defeating this bothersome bird for once and for all!"
+ "questBewilderBossRageDescription": "Quando questo indicatore sarà pieno, il Be-Wilder scatenerà il suo Colpo Beguilement su Habitica!",
+ "questBewilderDropBumblebeePet": "Ape Magica (animale)",
+ "questBewilderDropBumblebeeMount": "Ape Magica (cavalcatura)",
+ "questBewilderBossRageMarket": "`Il Be-Wilder utilizza beguilement STRIKE!` \n\nOh no! Nonostante i nostri migliori sforzi, siamo stati distratti dalle affascinanti illusioni del Be-Wilder e abbiamo dimenticato di compiere alcune delle nostre Daily! Con un grido schiamazzante, l'uccello lucente batte le ali, sollevando una nuvola di nebbia intorno ad Alex il commerciante. Quando la nebbia si dirada, è stato posseduto! \"Prendete alcuni campioni gratuiti!\" Grida allegramente, e comincia a scagliare uova che esplodono e pozioni contro gli Habiticans in fuga. Di certo non la più favorevole delle svendite.\n\nSvelti! Restiamo focalizzati sulle nostre Daily per sconfiggere questo mostro prima che possieda qualcun altro.",
+ "questBewilderBossRageStables": "`Il Be-Wilder utilizza beguilement STRIKE!` \n\nAhh !!! Ancora una volta il Bee-Wilder ci ha abbagliato perché trascurassimo le nostre Daily, e ora ha attaccato Matt il guardiano degli animali! Con un vortice di nebbia, Matt si trasforma in una terribile creatura alata, e tutti gli animali domestici e le cavalcature si lamentano tristemente nelle loro stalle. Presto, rimanete concentrati sui vostri compiti per sconfiggere questa vile distrazione!",
+ "questBewilderBossRageBailey": "`Il Be-Wilder utilizza beguilement STRIKE!` \n\nAttenzione! Nel mezzo della trasmissione di una notizia, Bailey la banditrice è stata posseduta dal Be-Wilder! Lei si lascia sfuggire un maligno, informe stridio mentre si alza in aria. Ora Come facciamo a sapere cosa sta succedendo? \n\nNon rinunciate ... siamo così vicini a sconfiggere questo uccello fastidioso una volta per tutte!",
+ "questFalconText": "Gli uccelli della preghiercrastinazione",
+ "questFalconNotes": "Il Monte Habitica è oscurato da un'incombente montagna di To-Do. Era un luogo per fare picnic e sentirsi realizzati, finché le attività trascurare sono sfuggite ad ogni controllo. Ora qui ci vivono dei terribili Uccelli della Procrastinazione, orride creature che impediscono agli abitanti di Habitica di completare i loro compiti!
\"È troppo difficile!\" gracchiano a @JonArinbjorn e @Onheiron. \"Ci vorrebbe troppo tempo per farlo ora! Non farà alcuna differenza se aspetti fino a domani! Perché invece non fai qualcosa di divertente?\"
Non più, prometti solennemente. Scalerai la tua personale montagna di To-Do e sconfiggerai gli Uccelli della Procrastinazione!",
+ "questFalconCompletion": "Dopo aver finalmente trionfato sugli Uccelli della Procrastinazione, ti sistemi per goderti la vista e il tuo meritato riposo.
\"Wow\" esclama @Trogdorina. \"Hai vinto\"
@Squish aggunge, \"Ecco, prendo queste uova che ho trovato come ricompensa.\"",
+ "questFalconBoss": "Uccelli della Procrastinazione",
+ "questFalconDropFalconEgg": "Falco (uovo)",
+ "questFalconUnlockText": "Sblocca l'acquisto delle uova di falco nel Mercato",
+ "questTreelingText": "L' Albero Groviglio",
+ "questTreelingNotes": "E' il concorso annuale dei giardini, e tutti parlano del progetto misterioso che @aurakami ha promesso di svelare. Vi unite alla folla il giorno del grande annuncio, e ammirate la presentazione di un albero che si muove. @fuzzytrees spiega che l'albero aiuterà con la manutenzione del giardino, mostrando come può tagliare il prato, regolare la siepe e potare le rose tutto allo stesso tempo - finché l'albero all'improvviso diventa selvaggio, rivolgendo le cesoie verso il suo creatore! La folla viene presa dal panico, mentre ognuno cerca di fuggire, ma voi non avete paura: balzate in avanti, pronti a dare battaglia.",
+ "questTreelingCompletion": "Vi date una spolverata, mentre le ultime foglie vanno alla deriva sul pavimento. Nonostante il trambusto, il Concorso dei Giardini è ora al sicuro - anche se l'albero che avete appena ridotto a un mucchio di trucioli non vincerà alcun premio! \"Ancora qualche dettaglio su cui lavorare\", dice @PainterProphet. \"Forse qualcun altro avrebbe educato meglio l'alberello. Volete provare voi?\"",
+ "questTreelingBoss": "Albero Groviglio",
+ "questTreelingDropTreelingEgg": "Alberello (Uovo)",
+ "questTreelingUnlockText": "Sblocca l'acquisto delle uova di Alberello nel Mercato"
}
\ No newline at end of file
diff --git a/common/locales/it/rebirth.json b/common/locales/it/rebirth.json
index b56135093c..75c9b6e428 100644
--- a/common/locales/it/rebirth.json
+++ b/common/locales/it/rebirth.json
@@ -5,7 +5,7 @@
"rebirthStartOver": "La rinascita riporta il tuo personaggio al livello 1.",
"rebirthAdvList1": "Torni ad avere tutti i punti vita.",
"rebirthAdvList2": "Non hai punti esperienza, oro o equipaggiamento (ad eccezione degli oggetti gratuiti come gli Oggetti Misteriosi)",
- "rebirthAdvList3": "Le tue Habit, le Daily e i To-Do si resettano tornando al colore giallo, e le serie si azzerano.",
+ "rebirthAdvList3": "Le tue Habit, le Daily e i To-Do si resettano tornando al colore giallo e le serie si azzerano, eccetto per le attività delle sfide.",
"rebirthAdvList4": "La tua classe iniziale sarà Guerriero fino a quando non otterrai una nuova classe.",
"rebirthInherit": "Il tuo nuovo personaggio eredita delle cose dal suo predecessore:",
"rebirthInList1": "Attività, cronologia e impostazioni rimangono.",
@@ -24,5 +24,6 @@
"rebirthPop": "Ricomincia con un nuovo personaggio al livello 1 mantenendo medaglie, oggetti, attività e cronologia.",
"rebirthName": "Sfera della Rinascita",
"reborn": "Rinasci, livello massimo <%= reLevel %>",
- "confirmReborn": "Sei sicuro?"
+ "confirmReborn": "Sei sicuro?",
+ "rebirthComplete": "Rinascita completata!"
}
\ No newline at end of file
diff --git a/common/locales/it/settings.json b/common/locales/it/settings.json
index 9a1f1341ec..a745a31fae 100644
--- a/common/locales/it/settings.json
+++ b/common/locales/it/settings.json
@@ -47,6 +47,7 @@
"customDayStart": "Data di inizio giorno personalizzata",
"changeCustomDayStart": "Cambiare l'ora di inizio della giornata?",
"sureChangeCustomDayStart": "Sei sicuro di voler cambiare l'inizio della giornata personalizzata?",
+ "customDayStartHasChanged": "La tua ora di inizio giorno è stata cambiata.",
"nextCron": "Le tue Daily verranno ripristinate la prima volta che usi Habitica dopo <%= time %>. Assicurati di aver completato le tue Daily prima di allora!",
"customDayStartInfo1": "Habitica è impostato per resettare le tue Daily a mezzanotte nel tuo fuso orario ogni giorno. Qui puoi modificare l'ora di \"reset\".",
"misc": "Altro",
@@ -61,12 +62,23 @@
"newUsername": "Nuovo nome utente",
"dangerZone": "Zona pericolosa",
"resetText1": "ATTENZIONE! Questo resetterà diversi aspetti del tuo account. E' altamente sconsigliato, ma qualcuno trova questa opzione utile all'inizio, dopo aver provato il sito per un po' di tempo.",
- "resetText2": "Perderai tutti i tuoi livelli, l'oro e i punti esperienza. Tutte le tue attività verranno cancellate permanentemente, insieme alla cronologia dei progressi. Perderai inoltre tutto il tuo equipaggiamento, ma potrai recuperare ogni cosa, compresi gli oggetti in edizione limitata e gli Oggetti Misteriosi per gli abbonati (tieni presente che alcuni oggetti potrebbero richiedere l'appartenenza ad una determinata classe per essere acquistati). Manterrai la tua classe, i tuoi animali e le cavalcature. Generalmente viene utilizzata la Sfera della Rinascita, un'alternativa molto più sicura che ti permette di mantenere le tue attività.",
+ "resetText2": "You will lose all your levels, gold, and experience points. All your tasks (except those from challenges) will be deleted permanently and you will lose all of their historical data. You will lose all your equipment but you will be able to buy it all back, including all limited edition equipment or subscriber Mystery items that you already own (you will need to be in the correct class to re-buy class-specific gear). You will keep your current class and your pets and mounts. You might prefer to use an Orb of Rebirth instead, which is a much safer option and which will preserve your tasks.",
"deleteText": "Vuoi davvero procedere? In questo modo eliminerai il tuo account per sempre, non potrà mai più essere recuperato! Dovrai registrare un nuovo account per continuare ad usare Habitica. Le gemme accumulate o spese non verranno risarcite. Se sei assolutamente sicuro, digita <%= deleteWord %> nella casella di testo qui sotto.",
"API": "API",
+ "APIv3": "API v3",
+ "APIv2": "API v2 - Deprecata",
"APIText": "Copia questi valori per utilizzarli in applicazioni di terze parti. Ad ogni modo, pensa al tuo API Token come a una password: tienilo segreto. Occasionalmente potrebbe venirti richiesto l'ID Utente, ma non scrivere mai il tuo API Token dove può essere visto da altre persone, nemmeno su GitHub.",
"APIToken": "API Token (questa è una password - leggi la nota sopra!)",
+ "thirdPartyApps": "Applicazioni di terze parti",
+ "dataToolDesc": "Una pagina web che ti mostra alcune informazioni sul tuo account Habitica, ad esempio statistiche sulle tue attività, equipaggiamento e abilità.",
+ "beeminder": "Beeminder",
+ "beeminderDesc": "Lascia che Beeminder monitori automaticamente i tuoi To-Do di Habitica. Puoi impegnarti a completare un certo numero di To-Do al giorno o alla settimana, oppure puoi impegnarti a ridurre progressivamente il numero di To-Do incompleti. (Per \"impegrarsi\" Beeminder intende di farlo sotto minaccia di dover pagare con soldi reali! Ma potrebbero semplicemente piacerti gli stravaganti graafici di Beeminder.)",
+ "chromeChatExtension": "Estensione chat per Chrome",
+ "chromeChatExtensionDesc": "L'estensione Chat di Chrome per Habitica aggiunge un'intuitiva finestra di chat ad habitica.com. Permette agli utenti di chiacchierare nella Taverna, nella loro squadra, e in qualsiasi gilda siano.",
+ "otherExtensions": "Altre estensioni",
+ "otherDesc": "Puoi trovare altre app, strumenti ed estensioni nella wiki di Habitica.",
"resetDo": "Sì, resetta il mio account!",
+ "resetComplete": "Reset completato!",
"fixValues": "Sistema valori",
"fixValuesText1": "Se hai avuto a che fare con un bug o commesso un errore che ha ingiustamente \"cambiato\" il tuo personaggio (danni che non avresti dovuto ricevere, Oro che non hai guadagnato, ecc.), qui puoi sistemare manualmente quei valori. Sì, questo rende possibile barare: usa questa funzionalità con saggezza, o finirai per rovinare la tua stessa costruzione di Habit!",
"fixValuesText2": "Tieni presente che qui non puoi modificare le serie delle singole attività. Per fare quello, modifica una Daily e vai nelle sue opzioni avanzate, troverai un campo \"Ripristina serie\".",
@@ -96,6 +108,7 @@
"emailNotifications": "Notifiche via email",
"wonChallenge": "Hai vinto una Sfida!",
"newPM": "Hai ricevuto un messaggio privato",
+ "sentGems": "Gemme inviate!",
"giftedGems": "Ti vengono regalate delle Gemme",
"giftedGemsInfo": "<%= amount %> Gemme - da <%= name %>",
"giftedSubscription": "Ti viene regalato un abbonamento",
@@ -135,6 +148,11 @@
"webhooks": "Webhooks",
"enabled": "Abilitato",
"webhookURL": "URL Webhook",
+ "invalidUrl": "url non valido",
+ "invalidEnabled": "Il parametro \"enabled\" dovrebbe essere un booleano",
+ "regIdRequired": "RegId è richiesto",
+ "pushDeviceAdded": "Dispositivo push aggiunto con successo.",
+ "pushDeviceAlreadyAdded": "L'utente ha già il dispositivo push",
"add": "Aggiungi",
"buyGemsGoldCap": "Limite alzato a <%= amount %>",
"mysticHourglass": "<%= amount %> Clessidra Mistica",
@@ -149,5 +167,5 @@
"amazonPayments": "Pagamenti su Amazon",
"timezone": "Fuso orario",
"timezoneUTC": "Habitica usa il fuso orario impostato sul tuo PC, ovvero: <%= utc %>",
- "timezoneInfo": "Se il fuso orario è sbagliato, ricarica questa pagina tramite il bottone di ricarica o aggiornamento della pagina del browser per assicurarti che Habitica contenga le informazioni più aggiornate. Se è ancora sbagliato, imposta il fuso orario sul tuo PC e poi ricarica di nuovo la pagina.
Se usi Habitica su altri PC o su altri dispositivi mobili, il fuso orario deve essere identico su ognuno di essi. Se le tue Daily sono state reimpostate ad un'ora sbagliata, ripeti questo controllo su tutti gli altri PC e su un browser sui tuoi dispositivi mobili."
+ "timezoneInfo": "Se il fuso orario è sbagliato, ricarica questa pagina tramite il bottone di ricarica o aggiornamento della pagina del browser, per assicurarti che Habitica contenga le informazioni più aggiornate. Se è ancora sbagliato, imposta il fuso orario corretto sul tuo PC e poi ricarica di nuovo questa pagina.
Se usi Habitica su altri PC o su altri dispositivi mobili, il fuso orario deve essere identico su ognuno di essi. Se le tue Daily sono state reimpostate ad un'ora sbagliata, ripeti questo controllo su tutti gli altri PC e su un browser sui tuoi dispositivi mobili."
}
\ No newline at end of file
diff --git a/common/locales/it/spells.json b/common/locales/it/spells.json
index 9c35bdf777..a81ae56d76 100644
--- a/common/locales/it/spells.json
+++ b/common/locales/it/spells.json
@@ -18,7 +18,7 @@
"spellRoguePickPocketText": "Borseggio",
"spellRoguePickPocketNotes": "Derubi un'attività nelle vicinanze. Ottieni oro! Fai click su un'attività per usare quest'abilità. (Dipende da: PER)",
"spellRogueBackStabText": "Pugnalata alle spalle",
- "spellRogueBackStabNotes": "Tradisci un'attività ingenua. Guadagni oro e XP! Fai click su un'attività per usare quest'abilità. (Dipende da: STR) ",
+ "spellRogueBackStabNotes": "Tradisci un'attività ingenua. Guadagni oro e XP! Fai click su un'attività per usare quest'abilità. (Dipende da: STR)",
"spellRogueToolsOfTradeText": "Attrezzi del Mestiere",
"spellRogueToolsOfTradeNotes": "Condividi i tuoi talenti con i tuoi amici. Tutta la squadra guadagna un bonus di Percezione! (Dipende da: PER senza bonus)",
"spellRogueStealthText": "Furtività",
@@ -35,8 +35,8 @@
"spellSpecialSnowballAuraNotes": "Tira una palla di neve a un compagno di squadra! Cosa potrebbe mai andare storto? L'effetto scomparirà quando inizierà la nuova giornata del tuo \"bersaglio\".",
"spellSpecialSaltText": "Sale",
"spellSpecialSaltNotes": "Qualcuno ti ha tirato una palla di neve. Ah ah, divertente. Ora però toglimi questa neve di dosso!",
- "spellSpecialSpookDustText": "Luci Sinistre",
- "spellSpecialSpookDustNotes": "Trasforma un amico in un lenzuolo fluttuante con due occhi!",
+ "spellSpecialSpookySparklesText": "Luci Sinistre",
+ "spellSpecialSpookySparklesNotes": "Trasforma un amico in un lenzuolo fluttuante!",
"spellSpecialOpaquePotionText": "Pozione Opaca",
"spellSpecialOpaquePotionNotes": "Annulla l'effetto di Luci Sinistre",
"spellSpecialShinySeedText": "Seme Brillante",
@@ -46,5 +46,11 @@
"spellSpecialSeafoamText": "Schiumarina",
"spellSpecialSeafoamNotes": "Trasforma un amico in una creatura del mare!",
"spellSpecialSandText": "Sabbia",
- "spellSpecialSandNotes": "Annulla gli effetti della Schiumarina."
-}
+ "spellSpecialSandNotes": "Annulla gli effetti della Schiumarina.",
+ "spellNotFound": "Abilità \"<%= spellId %>\" non trovata.",
+ "partyNotFound": "Squadra non trovata.",
+ "targetIdUUID": "\"targetId\" deve essere un ID Utente valido.",
+ "challengeTasksNoCast": "Usare un'abilità sulle attività delle sfide non è permesso.",
+ "spellNotOwned": "Non possiedi questa abilità.",
+ "spellLevelTooHigh": "Devi aver raggiunto il livello <%= level %> per usare questa abilità."
+}
\ No newline at end of file
diff --git a/common/locales/it/subscriber.json b/common/locales/it/subscriber.json
index 1ff4d3b9db..7f2683cf90 100644
--- a/common/locales/it/subscriber.json
+++ b/common/locales/it/subscriber.json
@@ -4,6 +4,8 @@
"subDescription": "Compra le Gemme con l'oro, ottieni oggetti misteriosi ogni mese, mantieni i dati sui progressi delle attività, raddoppia il limite di drop giornaliero di oggetti, supporta gli sviluppatori. Clicca per avere maggiori informazioni.",
"buyGemsGold": "Acquista Gemme usando l'oro",
"buyGemsGoldText": "Alexander il Mercante ti venderà Gemme al prezzo di <%= gemCost %> Oro per ogni gemma. Le sue consegne mensili saranno inizialmente limitate a <%= gemLimit %> gemme al mese, ma questo limite aumenta di 5 gemme per ogni tre mesi di abbonamento consecutivi, fino a un massimo di 50 gemme al mese!",
+ "mustSubscribeToPurchaseGems": "Must subscribe to purchase gems with GP",
+ "reachedGoldToGemCap": "You've reached the Gold=>Gem conversion cap <%= convCap %> for this month. We have this to prevent abuse / farming. The cap will reset within the first three days of next month.",
"retainHistory": "Conserva più dati nella cronologia",
"retainHistoryText": "Rende la cronologia delle To-Do completate e delle altre attività disponibili più a lungo.",
"doubleDrops": "Numero massimo di drop giornalieri raddoppiato",
@@ -29,8 +31,9 @@
"manageSub": "Clicca per gestire l'abbonamento",
"cancelSub": "Annulla abbonamento",
"canceledSubscription": "Abbonamento annullato",
+ "cancelingSubscription": "Annullamento dell'abbonamento",
"adminSub": "Abbonamento per amministratori",
- "morePlans": "Altri Piani In Arrivo",
+ "morePlans": "Altri piani In arrivo",
"organizationSub": "Organizzazione privata",
"organizationSubText": "I membri dell'organizzazione partecipano separatamente dall'Habitica pubblico, permettendo maggiore concentrazione ai tuoi partecipanti.",
"hostingType": "Tipo di hosting",
@@ -73,6 +76,9 @@
"timeTravelersPopover": "Abbiamo notato che possiedi una Clessidra Mistica, quindi saremo felici di tornare indietro nel tempo per te! Scegli l'animale, la cavalcatura o il set di Oggetti Misteriosi che desideri. Puoi vedere una lista degli oggetti dei set passati qui! Se questi non ti soddisfano, forse preferiresti uno dei nostri raffinati e futuristici set di oggetti steampunk?",
"timeTravelersAlreadyOwned": "Congratulazioni! Possiedi già tutto ciò che i Viaggiatori del Tempo possono attualmente offrire. Grazie per il tuo supporto al sito!",
"mysticHourglassPopover": "La Clessidra Mistica ti permette di acquistare alcuni oggetti a disponibilità limitata, come i set mensili di Oggetti Misteriosi le ricompense degli eventi mondiali, dal passato!",
+ "mysterySetNotFound": "Mystery set not found, or set already owned.",
+ "mysteryItemIsEmpty": "Mystery items are empty",
+ "mysteryItemOpened": "Oggetto misterioso aperto.",
"mysterySet201402": "Set messaggero alato",
"mysterySet201403": "Set proteggiforeste",
"mysterySet201404": "Set farfalla d'alba",
@@ -99,6 +105,8 @@
"mysterySet201601": "Set Campione della Risoluzione",
"mysterySet201602": "Set del Rubacuori",
"mysterySet201603": "Set Quadrifoglio",
+ "mysterySet201604": "Set guerriero foglia",
+ "mysterySet201605": "Marching Bard Set",
"mysterySet301404": "Set steampunk standard",
"mysterySet301405": "Set accessori steampunk",
"mysterySetwondercon": "Wondercon",
@@ -110,9 +118,25 @@
"hourglassBuyItemConfirm": "Acquistare questo oggetto per 1 Clessidra Mistica?",
"petsAlreadyOwned": "Possiedi già questo animale.",
"mountsAlreadyOwned": "Possiedi già questa cavalcatura.",
- "typeNotAllowedHourglass": "Tipo di oggetto non acquistabile con una Clessidra Mistica. Tipi permessi:",
+ "typeNotAllowedHourglass": "Item type not supported for purchase with Mystic Hourglass. Allowed types: <%= allowedTypes %>",
"petsNotAllowedHourglass": "Animale non acquistabile con una Clessidra Mistica.",
"mountsNotAllowedHourglass": "Cavalcatura non acquistabile con una Clessidra Mistica.",
"hourglassPurchase": "Acquistato un oggetto usando una Clessidra Mistica!",
- "hourglassPurchaseSet": "Acquistato un set di oggetti usando una Clessidra Mistica!"
+ "hourglassPurchaseSet": "Acquistato un set di oggetti usando una Clessidra Mistica!",
+ "missingUnsubscriptionCode": "Missing unsubscription code.",
+ "missingSubscription": "User does not have a plan subscription",
+ "missingSubscriptionCode": "Missing subscription code. Possible values: basic_earned, basic_3mo, basic_6mo, google_6mo, basic_12mo.",
+ "cannotDeleteActiveAccount": "Hai un abbonamento attivo, termina il tuo piano prima di eliminare il tuo account.",
+ "paymentNotSuccessful": "Il pagamento non è andato a buon fine",
+ "planNotActive": "The plan hasn't activated yet (due to a PayPal bug). It will begin <%= nextBillingDate %>, after which you can cancel to retain your full benefits",
+ "notAllowedHourglass": "Animale/Cavalcatura non acquistabile con la Clessidra Mistica.",
+ "readCard": "<%= cardType %> è stato letto",
+ "cardTypeRequired": "Tipo di carta richiesto",
+ "cardTypeNotAllowed": "Tipo di carta sconosciuto.",
+ "invalidCoupon": "Codice coupon non valido.",
+ "couponUsed": "Codice coupon già utilizzato.",
+ "noSudoAccess": "Non hai accesso sudo.",
+ "couponCodeRequired": "Il codice coupon è richiesto.",
+ "eventRequired": "\"req.params.event\" è richiesto.",
+ "countRequired": "\"req.query.count\" è richiesto."
}
\ No newline at end of file
diff --git a/common/locales/it/tasks.json b/common/locales/it/tasks.json
index 7ccd6c6b5d..c4a18bcada 100644
--- a/common/locales/it/tasks.json
+++ b/common/locales/it/tasks.json
@@ -73,6 +73,7 @@
"clearTags": "Deseleziona",
"hideTags": "Nascondi",
"showTags": "Mostra",
+ "toRequired": "You must supply a to value",
"startDate": "Data di inizio",
"startDateHelpTitle": "Quando dovrebbe cominciare questa attività?",
"startDateHelp": "Imposta la data in cui l'attività sarà \"attiva\". Non sarà necessario completarla prima di quel giorno.",
@@ -83,34 +84,48 @@
"perfectName": "Giorni Perfetti",
"perfectText": "Ha completato tutte le Daily attive <%= perfects %> volte. Grazie a questa medaglia ottieni un bonus a tutti gli attributi pari a +livello/2 per il giorno successivo. Per i livelli superiori a 100 non c'è alcun effetto aggiuntivo oltre al bonus.",
"perfectSingular": "Giorno Perfetto",
- "perfectSingularText": "Completate tutte le Daily attive in un giorno. Grazie a questa medaglia ottieni un bonus a tutti gli attributi pari a livello/2 per il giorno successivo. Per i livelli superiori al 100 non ci sono effetti aggiuntivi.",
+ "perfectSingularText": "Completate tutte le Daily attive in un giorno. Grazie a questa medaglia ottieni un bonus per tutti gli attributi (pari a +livello/2) per il giorno successivo. Per i livelli superiori al 100 non ci sono effetti aggiuntivi oltre al bonus.",
"streakerAchievement": "Hai ottenuto la medaglia \"Perfezionista\"! I 21 giorni consecutivi sono un importante traguardo per formare abitudini. Continua ad ottenere questa medaglia per ogni altri 21 giorni addizionali, su questa Daily o su qualunque altra!",
"fortifyName": "Pozione di Fortificazione",
"fortifyPop": "Fa tornare tutte le attività al valore neutro (colore giallo) e ripristina tutti i punti vita persi.",
"fortify": "Fortifica",
- "fortifyText": "La fortificazione farà tornare tutte le attività allo stato neutro (colore giallo), proprio come se le avessi appena create, e riempirà la tua barra della salute. Questo è un ottimo aiuto se le tue attività rosse stanno rendendo il gioco troppo difficile, o se quelle blu stanno rendendo il gioco troppo semplice. Se ripartire con calma suona come una buona idea, spendi qualche gemma e fai vedere alle tue attività chi comanda!",
+ "fortifyText": "La fortificazione farà tornare tutte le attività, eccetto quelle delle sfide, allo stato neutro (colore giallo), proprio come se le avessi appena create, e riempirà la tua barra della Salute. Questo è un ottimo aiuto se le tue attività rosse stanno rendendo il gioco troppo difficile, o se quelle blu stanno rendendo il gioco troppo semplice. Se ripartire con calma suona come una buona idea, spendi qualche gemma e fai vedere alle tue attività chi comanda!",
"confirmFortify": "Sei sicuro?",
+ "fortifyComplete": "Fortificazione completa!",
"sureDelete": "Vuoi davvero eliminare la <%= taskType %> con il testo \"<%= taskText %>\"?",
"streakCoins": "Bonus serie!",
"pushTaskToTop": "Metti in cima alla lista. Tieni premuto CTRL o cmd per mettere in fondo.",
"emptyTask": "Inserisci prima il titolo dell'attività",
"dailiesRestingInInn": "Stai riposando nella Locanda! Le tue Daily NON ti danneggeranno stanotte, PERÒ si resetteranno comunque ogni giorno. Se stai partecipando ad una missione, non infliggerai danni/raccoglierai oggetti finchè non lasci la Locanda, ma puoi comunque essere danneggiato dal Boss se i tuoi compagni di squadra non completano le proprie Daily.",
- "habitHelp1": "Gli Habit Positivi sono quelli che completi spesso. Ti premiano con Oro ed Esperienza ogni volta che clicchi <%= plusIcon %>.",
- "habitHelp2": "Gli Habit Cattivi sono quelli che vuoi evitare. Ti tolgono Saluta ogni volta che clicchi <%= minusIcon %>.",
- "habitHelp3": "Per farti ispirare dai un'occhiata a questi Habits d'esempio!",
+ "habitHelp1": "Le buone abitudini sono quelle che completi spesso. Ti premiano con Oro ed Esperienza ogni volta che clicchi <%= plusIcon %>.",
+ "habitHelp2": "Le cattive abitudini sono cose che vuoi evitare di fare. Ti fanno perdere Salute ogni volta che clicchi <%= minusIcon %>.",
+ "habitHelp3": "Per farti ispirare, dai un'occhiata a questi esempi di Habit! (in inglese)",
"newbieGuild": "Hai altre domande? Chiedile nella <%= linkStart %>Gilda dei Newbies<%= linkEnd %>!",
"dailyHelp1": "Le Daily si ripetono <%= emphasisStart %>ogni giorno<%= emphasisEnd %> in cui sono attive. Clicca <%= pencilIcon %> per cambiare i giorni in cui sono attive.",
- "dailyHelp2": "Se non completi le Daily attive perdi Salute quando la giornata finisce.",
+ "dailyHelp2": "Se non completi le Daily attive, perdi Salute quando la giornata finisce.",
"dailyHelp3": "Le attività giornaliere diventano <%= emphasisStart %>rosso scuro<%= emphasisEnd %> quando non le porti a termine, e <%= emphasisStart %>blu scuro<%= emphasisEnd %> quando le completi. Più l'attività giornaliera è rossa, più questa ti premierà... o danneggerà.",
- "dailyHelp4": "Per cambiare quando il tuo giorno finisce, vai su <%= linkStart %> Impostazioni > Sito <%= linkEnd %> > Inizio Giorno Personalizzato.",
- "dailyHelp5": "Per cercare ispirazione, prova a guardare questi esempi di Daily! (in inglese)",
+ "dailyHelp4": "Per cambiare l'ora in cui la tua giornata finisce, vai su <%= linkStart %> Impostazioni > Sito <%= linkEnd %> > Inizio Giorno Personalizzato.",
+ "dailyHelp5": "Per farti ispirare, dai un'occhiata a questi esempi di Daily! (in inglese)",
"toDoHelp1": "Le To-Do appena create sono gialle, e più tempo ci metti a completarle più diventano rosse (aumentano di valore).",
- "toDoHelp2": "I To-Do non ti fanno del male! Ti premiamo solo con Oro ed Esperienza.",
- "toDoHelp3": "Suddividere una To-Do in una lista di attività più piccole (una checklist) la renderà meno spaventosa, a aumenterà i tuoi punti!",
- "toDoHelp4": "Per trovare inspirazione, dai un'occhiata a queste attività d'esempio!",
+ "toDoHelp2": "Le To-Do non ti fanno del male! Possono solo premiarti con Oro ed Esperienza.",
+ "toDoHelp3": "Suddividere una To-Do in una lista di attività più piccole (una checklist) la renderà meno spaventosa, e aumenterà i tuoi punti!",
+ "toDoHelp4": "Per farti ispirare, dai un'occhiata a questi esempi di To-Do! (in inglese)",
"rewardHelp1": "L'equipaggiamento che compri per il tuo avatar viene messo in <%= linkStart %>Inventario > Equipaggiamento<%= linkEnd %>.",
"rewardHelp2": "L'equipaggiamento condiziona le tue statistiche (<%= linkStart %>Utente > Statistiche<%= linkEnd %>).",
"rewardHelp3": "Gli equipaggiamenti speciali compariranno qui durante gli Eventi Globali.",
"rewardHelp4": "Non aver paura di creare le tue ricompense! Guarda qualche esempio qui.",
- "clickForHelp": "Fai click per suggerimenti"
+ "clickForHelp": "Fai click per suggerimenti",
+ "taskIdRequired": "\"taskId\" deve essere un UUID valido.",
+ "taskNotFound": "Attività non trovata.",
+ "invalidTaskType": "Il tipo di attività deve essere uno tra \"habit\", \"daily\", \"todo\", \"reward\".",
+ "cantDeleteChallengeTasks": "Un'attività che appartiene ad una sfida non può essere eliminata.",
+ "checklistOnlyDailyTodo": "Le checklist sono supportate solo su Daily e To-Do.",
+ "checklistItemNotFound": "Non è stato trovato alcun elemento checklist con l'id specificato.",
+ "itemIdRequired": "\"itemId\" deve essere un UUID valido.",
+ "tagNotFound": "Non è stata trovata alcuna etichetta con l'id specificato.",
+ "tagIdRequired": "\"tagId\" deve essere un UUID valido e deve corrispondere a un'etichetta appartenente all'utente.",
+ "positionRequired": "\"position\" è richiesto e deve essere un numero.",
+ "cantMoveCompletedTodo": "Non è possibile spostare una To-Do completata.",
+ "directionUpDown": "\"direction\" è richiesto è deve essere 'up' oppure 'down'.",
+ "alreadyTagged": "L'attività ha già l'etichetta specificata."
}
\ No newline at end of file
diff --git a/common/locales/ja/backgrounds.json b/common/locales/ja/backgrounds.json
index 11657ed5ec..18343f7c85 100644
--- a/common/locales/ja/backgrounds.json
+++ b/common/locales/ja/backgrounds.json
@@ -109,7 +109,7 @@
"backgroundMarketText": "Habitica マーケット",
"backgroundMarketNotes": "Habitica マーケットで買い物しよう。",
"backgroundStableText": "Habitica の動物小屋",
- "backgroundStableNotes": "Habiticaの動物小屋で、乗用獣の世話をしましょう。",
+ "backgroundStableNotes": "Habitica の動物小屋で、騎獣の世話をしましょう。",
"backgroundTavernText": "Habirica の居酒屋",
"backgroundTavernNotes": "Habitica の居酒屋に行ってみよう。",
"backgrounds102015": "セット17:2015年10月リリース",
@@ -154,11 +154,18 @@
"backgroundRainforestNotes": "熱帯雨林を探検しましょう。",
"backgroundStoneCircleText": "石の円陣",
"backgroundStoneCircleNotes": "石の円陣で呪文を唱えましょう。",
- "backgrounds042016": "SET 23: Released April 2016",
- "backgroundArcheryRangeText": "Archery Range",
- "backgroundArcheryRangeNotes": "Practice on the Archery Range.",
- "backgroundGiantFlowersText": "Giant Flowers",
- "backgroundGiantFlowersNotes": "Frolic atop Giant Flowers.",
- "backgroundRainbowsEndText": "End of the Rainbow",
- "backgroundRainbowsEndNotes": "Discover gold at the End of the Rainbow."
+ "backgrounds042016": "セット 23 : 2016年4月リリース",
+ "backgroundArcheryRangeText": "アーチェリー場",
+ "backgroundArcheryRangeNotes": "アーチェリー場で弓の練習をしましょう。",
+ "backgroundGiantFlowersText": "大きな花",
+ "backgroundGiantFlowersNotes": "大きな花のてっぺんで遊びましょう。",
+ "backgroundRainbowsEndText": "にじの終わり",
+ "backgroundRainbowsEndNotes": "にじの終わりでゴールドを見つけましょう。",
+ "backgrounds052016": "セット 24 : 2016年5月リリース",
+ "backgroundBeehiveText": "ハチの巣",
+ "backgroundBeehiveNotes": "ハチの巣で羽をふるわせて踊りましょう。",
+ "backgroundGazeboText": "あずま屋",
+ "backgroundGazeboNotes": "あずま屋での戦い",
+ "backgroundTreeRootsText": "木の根",
+ "backgroundTreeRootsNotes": "木の根を探索しましょう。"
}
\ No newline at end of file
diff --git a/common/locales/ja/challenge.json b/common/locales/ja/challenge.json
index 916fb6e665..dd74982556 100644
--- a/common/locales/ja/challenge.json
+++ b/common/locales/ja/challenge.json
@@ -16,7 +16,7 @@
"selectWinner": "優勝者を選び、チャレンジを終了する",
"deleteOrSelect": "消すか優勝者を選ぶ",
"endChallenge": "チャレンジを終了する",
- "challengeDiscription": "これはチャレンジのタスクです。ユーザーが参加すると色が変わり、グループの進行過程を示すグラフが提示されます。",
+ "challengeDiscription": "こちらはチャレンジタスクです。チャレンジに参加するとダッシュボードに加わります。下に表示されているサンプルのチャレンジタスクは、グループ全体としての進行に合わせて、色が変わったり、グラフが増えたりします。",
"hows": "みんな元気かい?",
"filter": "フィルター",
"groups": "グループ",
@@ -38,20 +38,20 @@
"publicChallenges": "公共のチャレンジは最小でジェムが1個必要です(スパムを減らすために助かる)。",
"officialChallenge": "Habiticaの公式チャレンジ",
"by": "で",
- "participants": "参加者<%= membercount %>人",
+ "participants": "<%= membercount %> 人の参加者",
"join": "参加する",
"exportChallengeCSV": "CSVに送出",
"selectGroup": "グループを選択して下さい",
"challengeCreated": "チャレンジ作成終了",
"sureDelCha": "チャレンジを消しますか?",
- "sureDelChaTavern": "本当にこのチャレンジを削除しますか? 使ったジェムは返金されません。",
+ "sureDelChaTavern": "チャレンジを削除します。よろしいですか? 使ったジェムは返金されません。",
"removeTasks": "タスクを消す",
"keepTasks": "タスクを残す",
"closeCha": "チャレンジを終了して・・・",
"leaveCha": "チャレンジを出て・・・",
"challengedOwnedFilterHeader": "所有",
- "challengedOwnedFilter": "所有した",
- "challengedNotOwnedFilter": "所有してません",
+ "challengedOwnedFilter": "所有",
+ "challengedNotOwnedFilter": "所有していない",
"challengedEitherOwnedFilter": "どちらも",
"backToChallenges": "すべてのチャレンジへ戻る",
"prizeValue": "<%= gemcount %> <%= gemicon %>賞",
@@ -60,8 +60,23 @@
"noPermissionEditChallenge": "このチャレンジを編集する権限がありません。",
"noPermissionDeleteChallenge": "このチャレンジを削除する権限がありません。",
"noPermissionCloseChallenge": "このチャレンジを閉じる権限がありません。",
- "congratulations": "おめでとう!",
- "hurray": "やった!",
+ "congratulations": "おめでとう!",
+ "hurray": "やった!",
"noChallengeOwner": "所有者なし",
- "noChallengeOwnerPopover": "このチャレンジを作成した方のアカウントが削除されたため、このチャレンジには所有者がいない状態です。"
+ "noChallengeOwnerPopover": "このチャレンジを作成したメンバーのアカウントが削除されたため、このチャレンジには所有者がいない状態です。",
+ "challengeMemberNotFound": "チャレンジのメンバーの中にユーザーが見つかりません。",
+ "onlyGroupLeaderChal": "グループのリーダーだけが、チャレンジをつくることができます。",
+ "tavChalsMinPrize": "キャンプ場チャレンジの賞品は、最低 1 ジェムです。",
+ "cantAfford": "賞品分のジェムがありません。ジェムを追加購入するか、より安い賞品にしてください。",
+ "challengeIdRequired": "「チャレンジID」の UUID が無効です。",
+ "winnerIdRequired": "「優勝者ID」が無効です。",
+ "challengeNotFound": "チャレンジが見つかりません。",
+ "onlyLeaderDeleteChal": "削除できるのは、チャレンジのリーダーだけです。",
+ "onlyLeaderUpdateChal": "更新できるのは、チャレンジのリーダーだけです。",
+ "winnerNotFound": "「<%= userId %>」の優勝者が見つからないか、チャレンジのメンバーではありません。",
+ "noCompletedTodosChallenge": "「完了した To Do をふくむ」は、チャレンジ タスクには対応していません。",
+ "userTasksNoChallengeId": "「タスクのオーナー」が「ユーザー」「チャレンジID」の場合は先に進めません。",
+ "onlyChalLeaderEditTasks": "チャレンジのタスクは、リーダーだけが編集できます。",
+ "userAlreadyInChallenge": "ユーザーはすでにこのチャレンジに参加しています。",
+ "cantOnlyUnlinkChalTask": "チャレンジとの連関が切れたタスクのみ、リンクを解除できます。"
}
\ No newline at end of file
diff --git a/common/locales/ja/character.json b/common/locales/ja/character.json
index ae34376928..694c21a87c 100644
--- a/common/locales/ja/character.json
+++ b/common/locales/ja/character.json
@@ -1,4 +1,5 @@
{
+ "communityGuidelinesWarning": "あなたの表示名、プロフィル写真、自己紹介文は、コミュニティー ガイドラインに従わなくてはならないことを、心に刻んでおいてください(例 : 冒涜・不敬の禁止、成人向けの話題の禁止、侮辱行為の禁止など)。適切な事象なのかどうかについてのご質問は、お気軽に leslie@habitica.com へメールをお寄せください!",
"statsAch": "ステータスと実績",
"profile": "プロフィール",
"avatar": "アバターのカスタマイズ",
@@ -13,7 +14,7 @@
"inventory": "所持品",
"social": "ソーシャル",
"lvl": "Lv.",
- "buffed": "バフ",
+ "buffed": "バフあり",
"bodyBody": "体",
"bodySize": "サイズ",
"bodySlim": "小さい",
@@ -34,7 +35,7 @@
"beard": "あごひげ",
"mustache": "くちひげ",
"flower": "花",
- "wheelchair": "Wheelchair",
+ "wheelchair": "車いす",
"basicSkins": "基本的な肌の色",
"rainbowSkins": "虹の肌の色",
"pastelSkins": "パステルスキン",
@@ -67,11 +68,11 @@
"level": "レベル",
"levelUp": "レベルアップ!",
"gainedLevel": "レベルが上がりました!",
- "leveledUp": "実生活での目標達成で、レベル <%= level %>になりました!",
- "fullyHealed": "全部直りました!",
+ "leveledUp": "実生活での目標達成で、レベル <%= level %> になりました!",
+ "fullyHealed": "体力が完全に回復しました。",
"huzzah": "やった!",
"mana": "マナ",
- "hp": "HP",
+ "hp": "体力",
"mp": "マナ",
"xp": "経験",
"health": "体力",
@@ -88,19 +89,19 @@
"strength": "力",
"strengthText": "力は「会心の一撃」が出る確率と、ゴールド、経験値、そして何かが落ちるチャンスを上げます。さらに、ボスモンスターに与えるダメージも増えます。",
"constitution": "体質",
- "conText": "性格は、悪い習慣を行ったり、日課を果たせなかったときに受けるダメージを減らします。",
+ "conText": "性格の値が高いと、悪い習慣を行ったり、日課を果たせなかったときに受けるダメージを減らします。",
"perception": "知覚",
- "perText": "感覚は得られるゴールドを増やします。またマーケットをアンロックした後では、タスク達成時のアイテムを見つける確率が上がります。",
+ "perText": "知覚は得られるゴールドを増やし、マーケットのアンロック後は、タスク達成時のアイテムを見つける確率が上がります。",
"intelligence": "知能",
"intText": "知能は得られる経験値を増やします。またクラスをアンロックした後は、クラスの能力によって有効なマナの最大値を決定します。",
"levelBonus": "レベルボーナス",
- "levelBonusText": "それぞれの能力値に、(現在のレベル - 1 ) ÷2 のボーナスが得られます。",
+ "levelBonusText": "それぞれの能力値に、(現在のレベル - 1 ) ÷2 のボーナスがつきます。",
"allocatedPoints": "割りあてたポイント",
- "allocatedPointsText": "獲得した能力値は割り当てることができます。キャラクタービルドの項目からポイントの割り当てを行えます。",
+ "allocatedPointsText": "獲得した能力値は割り当てることができます。キャラクターの育成の項目からポイントを割り当てます。",
"allocated": "割り当て済み",
- "buffs": "バフ(補助魔法)",
- "buffsText": "一時的な属性ボーナスは、能力や実績から得られます。これらは一日の終わりに消失します。アンロックしたアビリティについてはタスクページの業績リストから確認できます。",
- "characterBuild": "キャラクタービルド",
+ "buffs": "勢い( 能力値ボーナス )",
+ "buffsText": "一時的な能力値ボーナスは、特殊能力や実績から得られます。これらは一日の終わりに消失します。アンロックした特殊能力はタスクページのごほうびリストから確認できます。",
+ "characterBuild": "キャラクターの育成",
"class": "クラス",
"experience": "経験値",
"warrior": "戦士",
@@ -109,6 +110,7 @@
"mage": "魔道士",
"mystery": "ミステリー",
"changeClass": "クラスの変更、能力値ポイントの調整",
+ "lvl10ChangeClass": "クラスを変えるには、レベル10以上にならないといけません。",
"levelPopover": "レベルが上がるたびに、能力値のどれかに割りあてできる1ポイントを得ることができます。手動で好きなように割りあてることもできますし、「自動割りあて」設定でシステムに任せることもできます。",
"unallocated": "未割りあての能力値ポイント",
"haveUnallocated": "<%= points %> ポイントが割りあてできます。",
@@ -119,16 +121,16 @@
"classAllocation": "クラス基準でポイントを割りあてる",
"classAllocationPop": "あなたのクラスにとって重要な能力値に、多めにポイントを割りあてます。",
"taskAllocation": "タスクの実践基準でポイントを割りあてる",
- "taskAllocationPop": "達成するタスクに関連して、肉体的 (力)、知的(知能) 、社会的(性格)、そして、その他(感覚)にポイントを割りあてます。",
+ "taskAllocationPop": "達成するタスクに関連して、肉体的 (力)、知的(知能) 、社会的(性格)、そして、その他(知覚)にポイントを割りあてます。",
"distributePoints": "未割りあてのポイントをふりわける",
- "distributePointsPop": "選択したの割りあて方法にもとづいて、すべての未割りあてポイントをふりわけます。",
+ "distributePointsPop": "選択した方法にもとづいて、すべての未割りあてポイントをふりわけます。",
"warriorText": "戦士はタスクを完了したときに、「会心の一撃」が出やすく、その効果も高い。「会心の一撃」が出ると、ゴールド、経験値、アイテムドロップの確率にボーナスがつきます。また、戦士はボスに大きなダメージを与えます。予測できない一攫千金タイプの報酬でやる気が出る、もしくはボス クエストで活躍したいなら、戦士でプレーしましょう!",
"mageText": "魔道士は、すぐに身に着ける学習能力をもっています。経験値の取得とレベルアップが他のクラスより速いのです。また、魔道士は、特殊能力を使つためのマナをうまく集めます。Habitica の戦術的な面を楽しみたい、レベルアップや拡張機能のアンロックにすごくやる気が出るなら、魔道士でプレーしましょう!",
"rogueText": "盗賊は富を集めることを愛するのです。ほかのどのクラスよりもゴールドを稼ぎ、アイテムを見つける確率が高いのです。盗賊の特徴、忍びの術をもってすれば、日課をやらなかったとしても、性格的に傷つかない。戦利品や勲章――Habitica では、ごほうびと実績に強く心動かされるなら、盗賊でプレーしましょう!",
"healerText": "治療師は痛みに耐え、他人を守るのです。やらなかった日課や悪い習慣にも治療師は動揺せず、失敗から体力を回復させる能力を持っています。パーティーの他のメンバーを助けることに喜びを感じる、困難な仕事による死をも恐れぬ理想があるのなら、治療師でプレーしましょう!",
"optOutOfClasses": "やめる",
"optOutOfPMs": "やめる",
- "optOutOfClassesText": "クラスなんてめんどくさい? 後で選びたい? 選ばなくても構いません。特殊能力のない戦士になります。クラスのしくみについてwiki を参照し、いつでも ユーザー -> ステータス で有効にすることができます。",
+ "optOutOfClassesText": "クラスなんてめんどくさい? 後で選びたい? 選ばなくても構いません。特殊能力のない戦士になります。クラスのしくみについてwiki を参照し、いつでも ユーザー -> ステータス で有効にできます。",
"select": "選択",
"stealth": "ステルス",
"stealthNewDay": "日があらたまったとき、前日にやり残した日課によるダメージを避けられます。",
@@ -164,5 +166,7 @@
"int": "知能",
"showQuickAllocation": "割りあてを表示",
"hideQuickAllocation": "割りあてを非表示",
- "quickAllocationLevelPopover": "レベルが上がるたびに、能力値のどれかに割りあてできる1ポイントを得ることができます。手動で好きなように割りあてることもできますし、「自動割りあて」設定でシステムに任せることもできます。"
+ "quickAllocationLevelPopover": "レベルが上がるたびに、能力値のどれかに割りあてできる1ポイントを得ることができます。手動で好きなように割りあてることもできますし、「自動割りあて」設定でシステムに任せることもできます。",
+ "invalidAttribute": "<%= attr %> は無効な能力値です。",
+ "notEnoughAttrPoints": "能力値ポイントが足りません。"
}
\ No newline at end of file
diff --git a/common/locales/ja/content.json b/common/locales/ja/content.json
index 241efcf0a7..8148df1fa4 100644
--- a/common/locales/ja/content.json
+++ b/common/locales/ja/content.json
@@ -16,10 +16,10 @@
"dropEggPandaCubAdjective": "優しい",
"dropEggLionCubText": "ライオンの子",
"dropEggLionCubMountText": "ライオン",
- "dropEggLionCubAdjective": "豪奢な",
+ "dropEggLionCubAdjective": "堂々とした",
"dropEggFoxText": "狐",
- "dropEggFoxMountText": "キツネ",
- "dropEggFoxAdjective": "ずる賢い",
+ "dropEggFoxMountText": "狐",
+ "dropEggFoxAdjective": "したたかな",
"dropEggFlyingPigText": "空飛ぶ豚",
"dropEggFlyingPigMountText": "空飛ぶ豚",
"dropEggFlyingPigAdjective": "気まぐれな",
@@ -29,8 +29,8 @@
"dropEggCactusText": "サボテン",
"dropEggCactusMountText": "サボテン",
"dropEggCactusAdjective": "チクチクする",
- "dropEggBearCubText": "熊の子",
- "dropEggBearCubMountText": "熊",
+ "dropEggBearCubText": "子ぐま",
+ "dropEggBearCubMountText": "くま",
"dropEggBearCubAdjective": "勇敢な",
"questEggGryphonText": "グリフォン",
"questEggGryphonMountText": "グリフォン",
@@ -42,7 +42,7 @@
"questEggDeerMountText": "鹿",
"questEggDeerAdjective": "上品な",
"questEggEggText": "たまご",
- "questEggEggMountText": "たまご入れ",
+ "questEggEggMountText": "たまごカゴ",
"questEggEggAdjective": "鮮やかな",
"questEggRatText": "ネズミ",
"questEggRatMountText": "ネズミ",
@@ -71,8 +71,8 @@
"questEggTRexText": "ティラノサウルス",
"questEggTRexMountText": "ティラノサウルス",
"questEggTRexAdjective": "腕が超小さい",
- "questEggRockText": "ロック",
- "questEggRockMountText": "石",
+ "questEggRockText": "岩",
+ "questEggRockMountText": "岩",
"questEggRockAdjective": "陽気な",
"questEggBunnyText": "ウサギ",
"questEggBunnyMountText": "ウサギ",
@@ -109,24 +109,31 @@
"questEggSabretoothAdjective": "凶暴な",
"questEggMonkeyText": "さる",
"questEggMonkeyMountText": "さる",
- "questEggMonkeyAdjective": "いたずら",
+ "questEggMonkeyAdjective": "いたずらな",
"questEggSnailText": "カタツムリ",
"questEggSnailMountText": "カタツムリ",
- "questEggSnailAdjective": "遅いが着実",
+ "questEggSnailAdjective": "遅いが着実な",
+ "questEggFalconText": "たか",
+ "questEggFalconMountText": "たか",
+ "questEggFalconAdjective": "敏速な",
+ "questEggTreelingText": "トリーリング",
+ "questEggTreelingMountText": "トリーリング",
+ "questEggTreelingAdjective": "リーフリ",
"eggNotes": "たまごがえしの薬を見つけて、たまごにかけると、<%= eggAdjective(locale) %> <%= eggText(locale) %>が生まれます。",
"hatchingPotionBase": "普通の",
"hatchingPotionWhite": "白い",
"hatchingPotionDesert": "砂漠の",
"hatchingPotionRed": "赤い",
- "hatchingPotionShade": "闇の",
- "hatchingPotionSkeleton": "スケルトン",
- "hatchingPotionZombie": "ゾンビ",
- "hatchingPotionCottonCandyPink": "ピンクの綿菓子",
- "hatchingPotionCottonCandyBlue": "青い綿菓子",
+ "hatchingPotionShade": "影の",
+ "hatchingPotionSkeleton": "骨の",
+ "hatchingPotionZombie": "ゾンビの",
+ "hatchingPotionCottonCandyPink": "ピンクの",
+ "hatchingPotionCottonCandyBlue": "水色の",
"hatchingPotionGolden": "黄金の",
"hatchingPotionSpooky": "不気味な",
- "hatchingPotionPeppermint": "ペパーミント",
- "hatchingPotionNotes": "これをたまごにかけると、<%= potText(locale) %> のペットが生まれます。",
+ "hatchingPotionPeppermint": "薄緑色の",
+ "hatchingPotionFloral": "フローラル",
+ "hatchingPotionNotes": "これをたまごにかけると、<%= potText(locale) %> ペットが生まれます。",
"premiumPotionAddlNotes": "ペットのたまごはクエストに使えません。",
"foodMeat": "肉",
"foodMilk": "ミルク",
@@ -140,8 +147,8 @@
"foodHoney": "はちみつ",
"foodCakeSkeleton": "骨子だけのケーキ",
"foodCakeBase": "基本のケーキ",
- "foodCakeCottonCandyBlue": "キャンディの青いケーキ",
- "foodCakeCottonCandyPink": "キャンディのピンク色のケーキ",
+ "foodCakeCottonCandyBlue": "水色のケーキ",
+ "foodCakeCottonCandyPink": "ピンク色のケーキ",
"foodCakeShade": "チョコレートケーキ",
"foodCakeWhite": "クリームケーキ",
"foodCakeGolden": "はちみつケーキ",
@@ -150,15 +157,15 @@
"foodCakeRed": "いちごケーキ",
"foodCandySkeleton": "骨子だけのキャンディ",
"foodCandyBase": "ベーシックキャンディ",
- "foodCandyCottonCandyBlue": "サワーブルーキャンディ",
- "foodCandyCottonCandyPink": "サワーピンクキャンディ",
+ "foodCandyCottonCandyBlue": "水色のキャンディ",
+ "foodCandyCottonCandyPink": "ピンクのキャンディ",
"foodCandyShade": "チョコレートキャンディ",
"foodCandyWhite": "バニラキャンディ",
- "foodCandyGolden": "ハニーキャンディ",
+ "foodCandyGolden": "はちみつキャンディ",
"foodCandyZombie": "腐ったキャンディ",
- "foodCandyDesert": "サンドキャンディ",
+ "foodCandyDesert": "砂のキャンディ",
"foodCandyRed": "シナモンキャンディ",
- "foodSaddleText": "鞍",
- "foodSaddleNotes": "ペットの 1 匹をすぐに乗用獣に成長させます。",
- "foodNotes": "ペットが丈夫に育つように、これを与えましょう。"
+ "foodSaddleText": "くら",
+ "foodSaddleNotes": "ペットの 1 匹をすぐに騎獣に成長させます。",
+ "foodNotes": "これをペットにあげて、丈夫に育てましょう。"
}
\ No newline at end of file
diff --git a/common/locales/ja/contrib.json b/common/locales/ja/contrib.json
index 352ea9766d..813ddd6b51 100644
--- a/common/locales/ja/contrib.json
+++ b/common/locales/ja/contrib.json
@@ -25,18 +25,22 @@
"readMore": "続きを読む",
"kickstartName": "Kickstarter での資金協力 - $<%= tier %> 段",
"kickstartText": "Kickstarter プロジェクトでの資金提供",
- "helped": "Habitの成長を支援しました。",
+ "helped": "Habit の発展を支援しました。",
"helpedText1": "このアンケートへ回答し、",
"helpedText2": "Habitica の成長を支援しました。",
"hall": "英雄記念館",
"contribTitle": "貢献者タイトル (例 \"Blacksmith\")",
"contribLevel": "貢献段位",
- "contribHallText": "1 - 7 段は一般の貢献者、8 段はモデレーター、9 段はスタッフです。この段位によって、利用可能なアイテム、ペット、乗用獣が決まります。また名札の色も決まります。8 段および 9 段には、管理ステータスが自動的に与えられます。",
+ "contribHallText": "1 - 7 段は一般の貢献者、8 段はモデレーター、9 段はスタッフです。この段位によって、利用可能なアイテム、ペット、騎獣が決まります。また名札の色も決まります。8 段および 9 段には、管理ステータスが自動的に与えられます。",
"hallContributors": "貢献者記念館",
"hallPatrons": "後援者の記念館",
"rewardUser": "ユーザーを報奨",
- "UUID": "UUID",
+ "UUID": "ユーザーID",
"loadUser": "ユーザーをロード",
+ "noAdminAccess": "管理者権限が必要です。",
+ "pageMustBeNumber": "req.query.page は数字でなくてはなりません。",
+ "userNotFound": "ユーザーが見つかりませんでした。",
+ "invalidUUID": "UUID が無効です。",
"title": "タイトル",
"moreDetails": "詳細(1-7)",
"moreDetails2": "詳細(8-9)",
@@ -52,8 +56,8 @@
"visitHeroes": "英雄(貢献者・資金提供者)記念館を訪ねる",
"conLearn": "貢献者の報酬について学ぶ",
"conLearnHow": "Habiticaの進展における参加",
- "surveysSingle": "アンケート回答によるHabiticaの成長を支援しました。現在アンケートは開催されていません。",
- "surveysMultiple": "<%= surveys %>個のアンケート回答によるHabiticaの成長を支援しました。現在アンケートは開催されていません。",
+ "surveysSingle": "Helped Habitica grow, either by filling out a survey or helping with a major testing effort. Thank you!",
+ "surveysMultiple": "Helped Habitica grow on <%= surveys %> occasions, either by filling out a survey or helping with a major testing effort. Thank you!",
"currentSurvey": "開催中のアンケート",
"surveyWhen": "バッジは3月下旬アンケートが処理されたときに参加者全員に授与されます。",
"blurbInbox": "ここはあなたの個人的なメッセージが保存される場所です!あなたは酒場、パーティー、またはギルドチャットで名前の隣にある封筒のアイコンをクリックすることで、メッセージを送信することができます。もしあなたが不適切なメッセージを受け取った場合は、Lemoness (leslie@habitica.com)にそのスクリーンショットを電子メールで送信してください。",
diff --git a/common/locales/ja/death.json b/common/locales/ja/death.json
index 28a3e68347..63cac95d2c 100644
--- a/common/locales/ja/death.json
+++ b/common/locales/ja/death.json
@@ -12,5 +12,6 @@
"losingHealthQuickly": "体力が減るのが速すぎる?",
"lowHealthTips3": "日課をやらないで夜を越すとダメージを受けます。はじめにたくさんの日課を追加しすぎないように!",
"lowHealthTips4": "特定の日には日課をやらないでいいのなら、鉛筆アイコンをクリックして無効にします。",
- "goodLuck": "がんばって!"
+ "goodLuck": "がんばって!",
+ "cannotRevive": "生きている間は復活できません。"
}
\ No newline at end of file
diff --git a/common/locales/ja/faq.json b/common/locales/ja/faq.json
index 05820dabc0..1507b6d4d5 100644
--- a/common/locales/ja/faq.json
+++ b/common/locales/ja/faq.json
@@ -1,6 +1,6 @@
{
"frequentlyAskedQuestions": "よくある質問",
- "faqQuestion0": "わからないことがあって困っています。どこで一覧を見つけられますか?",
+ "faqQuestion0": "わからないことがあって困っています。どこで概要を確認できますか?",
"iosFaqAnswer0": "まず、あなたの毎日の生活の中でやりたいタスクを設定します。そして実生活でそのタスクを完了したらチェックを入れます。すると、ゴールドと経験値を手に入ります。ゴールドでアバターの装備やお好みの「ごほうび」といったアイテムを買えます。経験値によってアバターがレベルアップし、新しいペット、スキルやクエストといった新しい機能をアンロックしていきます! アバターはメニュー >アバターのカスタマイズで設定できます。\n\nいくつかの基本操作 : 右上の (+) をクリックすると、新しいタスクの作成。すでにあるタスクをタップすると編集、左にスワイプすると削除できます。左上でタスクにタグをつけることで並べ替えができます。丸いチェックリストをクリックすることで、表示をたたんだり展開したりできます。",
"webFaqAnswer0": "まず、あなたの毎日の生活の中でやりたいタスクを設定します。そして実生活でそのタスクを完了したらチェックを入れます。すると、ゴールドと経験値を手に入ります。ゴールドでアバターの装備やお好みの「ごほうび」といったアイテムを買えます。経験値によってアバターがレベルアップし、新しいペット、スキルやクエストといった新しい機能をアンロックしていきます! 詳しくは [ヘルプ -> 新規ユーザーのための概要](https://habitica.com/static/overview) を読んでください。",
"faqQuestion1": "どのようにタスクをセットすればいいのですか?",
@@ -12,33 +12,33 @@
"faqQuestion3": "なぜタスクの色が変わるんですか ?",
"iosFaqAnswer3": "タスクの色は、最近あなたがどれだけタスクをこなしたかによって変化します! 新しいタスクは中間的な黄色でスタートします。たくさん日課をこなしたり、いい習慣をこなすと青に近づいていきます。日課をやりそこねたり、悪い習慣を行うと赤に近づいていきます。赤くなったタスクは、完了するとより多くの経験値やゴールドなどの報酬が得られますが、それが日課や悪い習慣であれば、よりたくさんのダメージを受けます! あなたにとって面倒なタスクほど、やる気を出すのに役立つことでしょう。",
"webFaqAnswer3": "タスクの色は、最近あなたがどれだけタスクをこなしたかによって変化します! 新しいタスクは中間的な黄色でスタートします。たくさん日課やいい習慣をこなすと青に近づいていきます。日課をやりそこねたり、悪い習慣を行うと赤に近づいていきます。赤くなったタスクは、完了するとより多くの経験値やゴールドなどの報酬が得られますが、それが日課や悪い習慣であれば、よりたくさんのダメージを受けます! あなたにとって面倒なタスクほど、やる気を出すのに役立つことでしょう。",
- "faqQuestion4": "なぜ私のアバターの体力 (HP) が減ったの? 回復する方法は?",
+ "faqQuestion4": "なぜ私のアバターの体力が減ったの? 回復する方法は?",
"iosFaqAnswer4": "ダメージを受け、体力が減るのにはいくつかの原因があります。1 つ目、日課をやらないまま夜を明かせば、ダメージを受けます。2 つ目、悪い習慣をチェックすれば、ダメージを受けます。最後に、パーティーでのボス戦の途中で、パーティーの仲間のだれかが日課をやり残した場合、ボスがあなたを攻撃します。\n\n主な回復方法はレベルを上げることで、レベルが上がると体力は全回復します。また、「ごほうび」欄の「体力回復の薬」をゴールドで買っても回復できます。そして、レベル10以上になると、治療師になることができ、回復の特殊能力を覚えます。もしパーティーの仲間に治療師がいれば、回復してもらうことがもきます。",
"webFaqAnswer4": "ダメージを受け、体力が減るのにはいくつかの原因があります。1 つ目、日課をやらないまま夜を明かせば、ダメージを受けます。2 つ目、悪い習慣をチェックすれば、ダメージを受けます。最後に、パーティーでのボス戦の途中で、パーティーの仲間のだれかが日課をやり残した場合、ボスがあなたを攻撃します。\n
\nすぐにクラスを選びたくなければ――たとえば、いまのクラスの装備を買い集めている最中――「後で決める」をクリックし、後から選ぶときは メニュー > クラスを選ぶ で行います。",
"faqQuestion8": "レベル10以降、ヘッダーに表示される青いバーは何ですか?",
- "iosFaqAnswer8": "レベル10になってクラスを選択すると表示される青いバーは、マナ バーです。レベルアップを続けると、マナを使う特殊能力の機能がアンロックされます。それぞれのクラスは異なった特殊能力をもっており、レベル11以降、メニュー > 特殊能力を使う に表示されます。体力バーと違って、マナ バーはレベルを上げてもリセットされません。マナは、いい習慣、日課、To-Do を達成することで増え、悪い週間を行うと減ります。夜が明けたときにも少し回復しますが、それはより多くの日課を完了しただけ、より回復します。",
+ "iosFaqAnswer8": "レベル10になってクラスを選択すると表示される青いバーは、マナ バーです。レベルアップを続けると、マナを使う特殊能力の機能がアンロックされます。それぞれのクラスは異なった特殊能力をもっており、レベル11以降、メニュー > 特殊能力を使う に表示されます。体力バーと違って、マナ バーはレベルを上げてもリセットされません。マナは、いい習慣、日課、To-Do を達成することで増え、悪い習慣を行うと減ります。夜が明けたときにも少し回復しますが、それはより多くの日課を完了すると、より回復します。",
"webFaqAnswer8": "レベル10になってクラスを選択すると表示される青いバーは、マナ バーです。レベルアップを続けると、マナを使う特殊能力の機能がアンロックされます。それぞれのクラスは異なった特殊能力をもっており、レベル11以降、「ごほうび」欄の特別な枠に表示されます。体力バーと違って、マナ バーはレベルを上げてもリセットされません。マナは、いい習慣、日課、To-Do を達成することで増え、悪い週間を行うと減ります。夜が明けたときにも少し回復しますが、それはより多くの日課を完了しただけ、より回復します。",
"faqQuestion9": "モンスターと戦ったり、クエストを始めるにはどうしたらいいですか?",
- "iosFaqAnswer9": "First, you need to join or start a Party (see above). Although you can battle monsters alone, we recommend playing in a group, because this will make Quests much easier. Plus, having a friend to cheer you on as you accomplish your tasks is very motivating!\n\n Next, you need a Quest Scroll, which are stored under Menu > Items. There are three ways to get a scroll:\n\n - At level 15, you get a Quest-line, aka three linked quests. More Quest-lines unlock at levels 30, 40, and 60 respectively. \n - When you invite people to your Party, you'll be rewarded with the Basi-List Scroll!\n - You can buy Quests from the Quests Page on the [website](https://habitica.com/#/options/inventory/quests) for Gold and Gems. (We will add this feature to the app in a future update.)\n\n To battle the Boss or collect items for a Collection Quest, simply complete your tasks normally, and they will be tallied into damage overnight. (Reloading by pulling down on the screen may be required to see the Boss's health bar go down.) If you are fighting a Boss and you missed any Dailies, the Boss will damage your Party at the same time that you damage the Boss. \n\n After level 11 Mages and Warriors will gain Skills that allow them to deal additional damage to the Boss, so these are excellent classes to choose at level 10 if you want to be a heavy hitter.",
- "webFaqAnswer9": "First, you need to join or start a party (under Social > Party). Although you can battle monsters alone, we recommend playing in a group, because this will make quests much easier. Plus, having a friend to cheer you on as you accomplish your tasks is very motivating!\n
\n Next, you need a Quest Scroll, which are stored under Inventory > Quests. There are three ways to get a scroll:\n
\n * When you invite people to your party, you’ll be rewarded with the Basi-List Scroll!\n * At level 15, you get a Quest-line, i.e., three linked quests. More Quest-lines unlock at levels 30, 40, and 60 respectively.\n * You can buy Quests from the Quests Page (Inventory > Quests) for Gold and Gems.\n
\n To battle the Boss or collect items for a Collection Quest, simply complete your tasks normally, and they will be tallied into damage overnight. (Reloading may be required to see the Boss's Health bar go down.) If you are fighting a Boss and you missed any Dailies, the Boss will damage your party at the same time that you damage the Boss.\n
\n After level 11 Mages and Warriors will gain Skills that allow them to deal additional damage to the Boss, so these are excellent classes to choose at level 10 if you want to be a heavy hitter.",
- "faqQuestion10": "ジェムってなに?どこで手に入れるの?",
- "iosFaqAnswer10": "Gems are purchased with real money by tapping on the gem icon in the header. When people buy gems, they are helping us to keep the site running. We're very grateful for their support!\n\n In addition to buying gems directly, there are three other ways players can gain gems:\n\n * Win a Challenge on the [website](https://habitica.com) that has been set up by another player under Social > Challenges. (We will be adding Challenges to the app in a future update!)\n * Subscribe on the [website](https://habitica.com/#/options/settings/subscription) and unlock the ability to buy a certain number of gems per month.\n * Contribute your skills to the Habitica project. See this wiki page for more details: [Contributing to Habitica](http://habitica.wikia.com/wiki/Contributing_to_Habitica).\n\n Keep in mind that items purchased with gems do not offer any statistical advantages, so players can still make use of the app without them!",
- "webFaqAnswer10": "Gems are [purchased with real money](https://habitica.com/#/options/settings/subscription), although [subscribers](https://habitica.com/#/options/settings/subscription) can purchase them with Gold. When people subscribe or buy Gems, they are helping us to keep the site running. We're very grateful for their support!\n
\n In addition to buying Gems directly or becoming a subscriber, there are two other ways players can gain Gems:\n
\n * Win a Challenge that has been set up by another player under Social > Challenges.\n * Contribute your skills to the Habitica project. See this wiki page for more details: [Contributing to Habitica](http://habitica.wikia.com/wiki/Contributing_to_Habitica)\n
\n Keep in mind that items purchased with Gems do not offer any statistical advantages, so players can still make use of the site without them!",
- "faqQuestion11": "バグとかリクエストってどこで報告すればいいの?",
- "iosFaqAnswer11": "You can report a bug, request a feature, or send feedback under Menu > Report a Bug and Menu > Send Feedback! We'll do everything we can to assist you.",
- "webFaqAnswer11": "バグの報告はGitHubで受け付けています。[ヘルプ > バグを報告する](https://github.com/HabitRPG/habitrpg/issues/2760)にアクセスし、表示される指示に従ってください。大丈夫、私たちがすぐに修正します!\n
\n機能のリクエストはTrelloで受け付けています。[ヘルプ > 仕様をお願いする](https://trello.com/c/odmhIqyW/440-read-first-table-of-contents)にアクセスし、表示される指示に従ってください。ジャジャーン!",
- "faqQuestion12": "ワールドボスと戦うには?",
- "iosFaqAnswer12": "ワールドボスは酒場に現れる特別なモンスターです。全てのアクティブユーザーは自動的にこのボスと戦うことになります。プレイヤーが消化した日課や使用したスキルは、常にこのボスにダメージを与えます。\n\nあなたは通常のクエストボスと同時にワールドボスと戦うことが出来ます。あなたが消化したタスク、使用したスキルは通常のボス/もしくは物集めクエストとワールドボスの両方に効力を発揮するでしょう。\n\nワールドボスはあなたのアカウントには一切ダメージを与えません。その代わり、あなたが消化しそこねた日課に応じて怒りゲージが貯まります。このゲージがもし一杯になると、ボスはこのサイトに属するNPCに攻撃します。NPCのグラフィックは変わってしまうでしょう。\n\n [過去のワールドボス](http://habitica.wikia.com/wiki/World_Bosses) についての記事はこちらです。",
- "webFaqAnswer12": "World Bosses are special monsters that appear in the Tavern. All active users are automatically battling the Boss, and their tasks and skills will damage the Boss as usual.\n
\n You can also be in a normal Quest at the same time. Your tasks and skills will count towards both the World Boss and the Boss/Collection Quest in your party.\n
\n A World Boss will never hurt you or your account in any way. Instead, it has a Rage Bar that fills when users skip Dailies. If its Rage bar fills, it will attack one of the Non-Player Characters around the site and their image will change.\n
\n You can read more about [past World Bosses](http://habitica.wikia.com/wiki/World_Bosses) on the wiki.",
- "iosFaqStillNeedHelp": "If you have a question that isn't on this list or on the [Wiki FAQ](http://habitica.wikia.com/wiki/FAQ), come ask in the Tavern chat under Menu > Tavern! We're happy to help.",
- "webFaqStillNeedHelp": "If you have a question that isn't on this list or on the [Wiki FAQ](http://habitica.wikia.com/wiki/FAQ), come ask in the [Newbies Guild](https://habitica.com/#/options/groups/guilds/5481ccf3-5d2d-48a9-a871-70a7380cee5a)! We're happy to help."
+ "iosFaqAnswer9": "まず、パーティーに加わるか、新しいパーティーを作るか(上述) する必要があります。一人でモンスターと戦うこともできますが、クエストをずっと簡単にしてくれるので、グループでプレーすることをお勧めします。加えて、タスクを達成するようあなたを応援してくれる友達がいることで、とてもやる気になるからです!\n\n\n次に、クエストの巻物が必要です。メニュー > 所持品 に保管されています。巻物を入手するには3通りの方法があります : \n\n- レベル15 で、3 リンク クエスト として知られる、シリーズ クエストにたどり着きます。その後、レベル 30、40、そして60 のそれぞれで、シリーズ クエストがアンロックされます。\n- だれかをあなたのパーティーに招待すると、バシ・リストの巻物が手に入ります。\n- [website](https://habitica.com/#/options/inventory/quests) のクエストのページで、ゴールドまたはジェムと引き換えにクエストを購入できます。( アプリ版では、この機能は将来の更新で追加されます。 )\n\nボスと戦ったり、コレクション クエストでアイテムを集めたりするには、タスクを通常通り完了するだけです。日付が改められるたびに、ダメージとして計算されます。( ボスの体力バーが減るのを見るには、スクリーンをプル ダウンして、リロードする必要があるでしょう。 ) ボスと戦っている間に日課をやり残すと、ボスへのダメージが発生するタイミングで、ボスからあなた方パーティーへの攻撃によるダメージが発生します。\n\nレベル11以降、魔道士と戦士は、ボスへの追加的なダメージを発生する特殊能力があらわれるので、もし、ボスに対する破壊的な攻撃力を身に着けたいなら、この2つはすばらしいクラスです。レベル10でいずれかを選びましょう。",
+ "webFaqAnswer9": "まず、パーティーに加わるか、新しいパーティーを作るか( ソーシャル > パーティー ) する必要があります。一人でモンスターと戦うこともできますが、クエストをずっと簡単にしてくれるので、グループでプレーすることをお勧めします。加えて、タスクを達成するようあなたを応援してくれる友達がいることで、とてもやる気になるからです!\n
\n詳しくは wiki の [過去の世界のボス](http://habitica.wikia.com/wiki/World_Bosses) をお読みください。",
+ "iosFaqStillNeedHelp": "この中や [Wiki FAQ] (http://habitica.wikia.com/wiki/FAQ) にない質問は、ソーシャル > キャンプ場チャット で聞いてみてください。喜んで手助けします。",
+ "webFaqStillNeedHelp": "この中や [Wiki FAQ] (http://habitica.wikia.com/wiki/FAQ) にない質問は、[Newbies Guild](https://habitica.com/#/options/groups/guilds/5481ccf3-5d2d-48a9-a871-70a7380cee5a) で聞いてみてください。喜んで手助けします。"
}
\ No newline at end of file
diff --git a/common/locales/ja/front.json b/common/locales/ja/front.json
index 0393a57f5c..62ee3d411b 100644
--- a/common/locales/ja/front.json
+++ b/common/locales/ja/front.json
@@ -22,12 +22,13 @@
"communityBug": "バグを登録する",
"communityExtensions": "アドオン & 拡張機能",
"communityFacebook": "Facebook",
- "communityFeature": "機能をリクエストする",
+ "communityFeature": "機能を要望する",
"communityForum": "フォーラム",
"communityKickstarter": "Kickstarter",
"communityReddit": "Reddit",
"companyAbout": "機能説明",
"companyBlog": "ブログ",
+ "devBlog": "開発者ブログ",
"companyDonate": "寄付",
"companyExtensions": "拡張機能",
"companyPrivacy": "プライバシー",
@@ -45,12 +46,13 @@
"featureAchievementHeading": "実績バッジ",
"featureEquipByline": "タスクをこなしたごほうびで、限定アイテム、薬、その他のゲーム内グッズを市場で買いましょう!",
"featureEquipHeading": "アイテムその他",
- "featurePetByline": "タスクを終えたときに、たまごとアイテムが手に入ります。できるだけ生産的につとめ、ペットと乗用獣を集めましょう!",
- "featurePetHeading": "ペットと乗用獣",
+ "featurePetByline": "タスクを終えたときに、たまごとアイテムが手に入ります。できるだけ生産的につとめ、ペットと騎獣を集めましょう!",
+ "featurePetHeading": "ペットと騎獣",
"featureSocialByline": "共通の趣味をもつ人たちが集まるグループに参加してみましょう。チャレンジを作成して、ほかのユーザーたちと競争しましょう。",
"featureSocialHeading": "グループ プレー",
"featuredIn": "~で取り上げてます",
"featuresHeading": "こんな点もおすすめです...",
+ "footerDevs": "開発者",
"footerCommunity": "コミュニティ",
"footerCompany": "会社",
"footerMobile": "モバイル",
@@ -65,7 +67,7 @@
"goalSample4": "Duolingo で英語を勉強する",
"goalSample5": "役に立つ記事を読む",
"goals": "目標",
- "health": "健康",
+ "health": "体力",
"healthSample1": "水を飲む・炭酸飲料を飲む",
"healthSample2": "ガムを噛む・タバコを吸う",
"healthSample3": "階段で行く・エレベーターに乗る",
@@ -179,9 +181,10 @@
"username": "ユーザー名",
"watchVideos": "動画を見る",
"work": "仕事",
- "zelahQuote": "[Habitica] は、早く寝てポイントを増やすか夜ふかしして体力を失うかと考えさせて、ぼくを定刻にベッドに行くよう言い聞かせてくれたよ。",
+ "zelahQuote": "[Habitica] は、早く寝てポイントを増やすか、夜ふかしして体力を減らすかと考えさせることで、ぼくを定刻にベッドに行くよう説得してくれたよ。",
"reportAccountProblems": "アカウントの問題を報告する",
"reportCommunityIssues": "コミュニティの問題を報告する",
+ "subscriptionPaymentIssues": "寄付や支払いの問題",
"generalQuestionsSite": "サイトについての一般的なご質問",
"businessInquiries": "ビジネスのお問い合わせ",
"merchandiseInquiries": "商品化についてのお問い合わせ",
@@ -222,5 +225,32 @@
"altAttrWebstorm": "WebStorm",
"altAttrGithub": "GitHub",
"altAttrTrello": "Trello",
- "altAttrSlack": "Slack"
+ "altAttrSlack": "Slack",
+ "missingAuthHeaders": "認証ヘッダーが見つかりません。",
+ "missingAuthParams": "認証パラメーターが見つかりません。",
+ "missingUsernameEmail": "ユーザー名またはメールアドレスが見つかりません。",
+ "missingEmail": "Missing email.",
+ "missingUsername": "Missing username.",
+ "missingPassword": "Missing password.",
+ "missingNewPassword": "Missing new password.",
+ "wrongPassword": "Wrong password.",
+ "notAnEmail": "Invalid email address.",
+ "emailTaken": "Email address is already used in an account.",
+ "newEmailRequired": "Missing new email address.",
+ "usernameTaken": "Username already taken.",
+ "passwordConfirmationMatch": "Password confirmation doesn't match password.",
+ "invalidLoginCredentials": "Incorrect username and/or email and/or password.",
+ "passwordReset": "If we have your email on file, your password reset link has been sent to your email.",
+ "passwordResetEmailSubject": "Password Reset for Habitica",
+ "passwordResetEmailText": "Password for <%= username %> has been reset to <%= newPassword %> . Important! Both username and password are case-sensitive -- you must enter both exactly as shown here. We recommend copying and pasting both instead of typing them. Log in at <%= baseUrl %>. After you have logged in, head to <%= baseUrl %>/#/options/settings/settings and change your password.",
+ "passwordResetEmailHtml": "Password for <%= username %> has been reset to <%= newPassword %>.
Important! Both username and password are case-sensitive -- you must enter both exactly as shown here. We recommend copying and pasting both instead of typing them.
Log in at <%= baseUrl %>. After you have logged in, head to <%= baseUrl %>/#/options/settings/settings and change your password.",
+ "invalidLoginCredentialsLong": "Uh-oh - your username or password is incorrect.\n- Make sure your username or email is typed correctly.\n- You may have signed up with Facebook, not email. Double-check by trying Facebook login.\n- If you forgot your password, click \"Forgot Password\".",
+ "invalidCredentials": "There is no account that uses those credentials.",
+ "accountSuspended": "Account has been suspended, please contact leslie@habitica.com with your User ID \"<%= userId %>\" for assistance.",
+ "onlyFbSupported": "Only Facebook is supported currently.",
+ "cantDetachFb": "Account lacks another authentication method, can't detach Facebook.",
+ "onlySocialAttachLocal": "Local authentication can be added to only a social account.",
+ "invalidReqParams": "Invalid request parameters.",
+ "memberIdRequired": "\"member\" must be a valid UUID.",
+ "heroIdRequired": "\"heroId\" must be a valid UUID."
}
\ No newline at end of file
diff --git a/common/locales/ja/gear.json b/common/locales/ja/gear.json
index baf5070352..0c8fc8bc7f 100644
--- a/common/locales/ja/gear.json
+++ b/common/locales/ja/gear.json
@@ -14,7 +14,7 @@
"weaponWarrior4Text": "サファイアの刃",
"weaponWarrior4Notes": "北風のごとく身を切る刃を持つ剣。力が<%= str %>上がる。",
"weaponWarrior5Text": "ルビーの剣",
- "weaponWarrior5Notes": "赤めたときの熱りがずっと消えない武器。力が <%= str %> 上がる。",
+ "weaponWarrior5Notes": "赤めたときの熱がずっと消えない武器。力が <%= str %> 上がります。",
"weaponWarrior6Text": "黄金の剣",
"weaponWarrior6Notes": "暗黒の魔物が嫌う剣。力が<%= str %>上がる。",
"weaponRogue0Text": "ダガー",
@@ -34,300 +34,302 @@
"weaponWizard0Text": "見習いの杖",
"weaponWizard0Notes": "稽古用の杖。効果なし。",
"weaponWizard1Text": "木の杖",
- "weaponWizard1Notes": "刻んだ木の基本的な道具。知能が<%= int %>、知覚が<%= per %>上がる。",
+ "weaponWizard1Notes": "木を削った基本的な装備。知能が<%= int %>、知覚が<%= per %> 上がります。",
"weaponWizard2Text": "ジュエルスタッフ",
"weaponWizard2Notes": "宝石を原動力としている。知能が<%= int %>、知覚が<%= per %>上がる。",
"weaponWizard3Text": "鉄の杖",
- "weaponWizard3Notes": "メッキは熱気、寒気と雷を導く。知能が <%= int %>、知覚が <%= per %> 上がる。",
+ "weaponWizard3Notes": "金属板が熱、冷気、そして雷をほとばしらせます。知能が <%= int %>、知覚が <%= per %> 上がります。",
"weaponWizard4Text": "真鍮の杖",
"weaponWizard4Notes": "その重さに見合う威力を誇る。知能が<%= int %>、知覚が<%= per %>上がる。",
"weaponWizard5Text": "大魔道士の杖",
"weaponWizard5Notes": "最も複雑な呪文が織り込まれている。知能が<%= int %>、知覚が<%= per %>上がる。",
"weaponWizard6Text": "黄金の杖",
- "weaponWizard6Notes": "強力で貴重な金で錬金されたオリハルコンによって作られている。知能が<%= int %>、知覚が<%= per %>上がる。",
+ "weaponWizard6Notes": "オリハルコン、錬金術で生み出された金できたえられており、強く貴重。知能が<%= int %>、知覚が<%= per %>上がります。",
"weaponHealer0Text": "初心者のロッド",
- "weaponHealer0Notes": "訓練中の治療師用のつえ。効果なし。",
- "weaponHealer1Text": "侍祭の杖",
- "weaponHealer1Notes": "治療者の入会式中制作された。知能が<%= int %>上がる。",
- "weaponHealer2Text": "クォーツロッド",
- "weaponHealer2Notes": "治療特性を持つ宝石で飾られている。知能が<%= int %>上がる。",
- "weaponHealer3Text": "アメシストのロッド",
- "weaponHealer3Notes": "触れるだけで解毒する。知能が<%= int %>上がる。",
- "weaponHealer4Text": "医師のロッド",
- "weaponHealer4Notes": "ヒーリングツールとしての診療室のバッジ。知能が <%= int %>上がる。",
- "weaponHealer5Text": "ロイヤルセプター",
- "weaponHealer5Notes": "君主または君主の右手に立っている人の手を飾るのにふさわしい。知能が <%= int %> 上がる。",
- "weaponHealer6Text": "黄金セプター",
- "weaponHealer6Notes": "それを目にする全ての者の痛みを癒やす。知能が<%= int %>上がる。",
- "weaponSpecial0Text": "ダークソウルブレード",
- "weaponSpecial0Notes": "敵の生命の本質的な喜びが、その邪悪な一撃の力を増す。力が <%= str %> 上がる。",
- "weaponSpecial1Text": "クリスタルブレード",
- "weaponSpecial1Notes": "そのきらめく彫刻が勇者の伝説を物語っている。全ての能力値が<%= attrs %>上がる。",
- "weaponSpecial2Text": "スティーヴン・ウェーバの竜のシャフト",
- "weaponSpecial2Notes": "内なるドラゴンサージの力を感じる。力と知覚がそれぞれ<%= attrs %>上がる。",
- "weaponSpecial3Text": "マストエネイのマイルストーンを揉消しているモーニングスター",
- "weaponSpecial3Notes": "対戦、モンスター、倦怠感: 処理した!すりつぶせ!力、知能、そして体質がそれぞれ <%= attrs %> 上がる。",
- "weaponSpecialCriticalText": "バグクラッシャーのクリティカルハンマー",
- "weaponSpecialCriticalNotes": "多くの戦士が倒れたところ、このチャンピオンはクリティカルなGitHubの敵を殺した。バグの骨から作られたこのハンマーは、強力な会心の一撃を与える。力と知覚がそれぞれ <%= attrs %> 上がる。",
- "weaponSpecialTridentOfCrashingTidesText": "ざざっと波の三叉",
- "weaponSpecialTridentOfCrashingTidesNotes": "Gives you the ability to command fish, and also deliver some mighty stabs to your tasks. Increases Intelligence by <%= int %>.",
- "weaponSpecialYetiText": "雪男テイマースピア",
- "weaponSpecialYetiNotes": "この槍の使い手はどんな雪男にも命令できる。力が <%= str %> 上がる。2013年-2014年冬季限定版装備。",
- "weaponSpecialSkiText": "スキーサシンポール",
- "weaponSpecialSkiNotes": "敵の大群を破壊することができる武器!使い手が非常に素晴らしいパラレルターンをするのにも役立つ。力が <%= str %> 上がる。2013年-2014年冬季限定版装備。",
- "weaponSpecialCandycaneText": "キャンディケインスタッフ",
- "weaponSpecialCandycaneNotes": "強力な魔道士のスタッフ。そして、強力にうまい!両手の武器。知能が <%= int %>、知覚が <%= per %> 上がる。2013年-2014年冬季限定版装備。",
- "weaponSpecialSnowflakeText": "雪の杖",
- "weaponSpecialSnowflakeNotes": "この杖は無限の癒しの力で輝いている。知能が<%= int %>上がる。2013年-2014年冬季限定版装備。",
- "weaponSpecialSpringRogueText": "鉤爪",
- "weaponSpecialSpringRogueNotes": "高層ビルを登るのに使えるし、絨毯を切り裂くのにも使える。力が<%= str %>上がる。2014年春季限定版装備。",
- "weaponSpecialSpringWarriorText": "人参刀",
- "weaponSpecialSpringWarriorNotes": "この強大な剣は敵を簡単に切ることができる。また、おいしいミッドバトルスナックを作ることができる。力が<%= str %>上がる。2014年春季限定版装備。",
- "weaponSpecialSpringMageText": "スイスチーズの杖",
- "weaponSpecialSpringMageNotes": "強力な齧歯類だけが空腹に立ち向かうためにこの杖を振るうことができる。知能が <%= int %>、知覚が<%= per %> 上がる。2014年春季限定版装備。",
- "weaponSpecialSpringHealerText": "ラブリーボーン",
- "weaponSpecialSpringHealerNotes": "獲得しました!知能が<%= int %> 上がる。2014年春季限定版装備。",
+ "weaponHealer0Notes": "修行中の治療師用のつえ。効果なし。",
+ "weaponHealer1Text": "侍者のロッド",
+ "weaponHealer1Notes": "治療師の入門中につくったもの。知能が<%= int %>上がります。",
+ "weaponHealer2Text": "水晶の棒",
+ "weaponHealer2Notes": "治療能力のある宝玉が先端にうめこまれています。知能が<%= int %>上がります。",
+ "weaponHealer3Text": "紫水晶の棒",
+ "weaponHealer3Notes": "触れるだけで解毒します。知能が<%= int %>上がります。",
+ "weaponHealer4Text": "医者の棒",
+ "weaponHealer4Notes": "診療室のバッジほどによく使われる治療用の道具。知能が <%= int %> 上がります。",
+ "weaponHealer5Text": "王室のしゃく",
+ "weaponHealer5Notes": "君主または君主の右腕の人物の手にふさわしいものです。知能が <%= int %> 上がります。",
+ "weaponHealer6Text": "黄金のしゃく",
+ "weaponHealer6Notes": "目にする者すべての痛みを癒やします。知能が <%= int %> 上がります。",
+ "weaponSpecial0Text": "邪悪な魂の刃",
+ "weaponSpecial0Notes": "敵の生命をえさにした力で、邪悪な一撃とします。力が <%= str %> 上がります。",
+ "weaponSpecial1Text": "水晶の刃",
+ "weaponSpecial1Notes": "きらめくカッティングが勇者の伝説を物語ります。すべての能力値が <%= attrs %> 上がります。",
+ "weaponSpecial2Text": "ステファン・ウェバーの竜のシャフト",
+ "weaponSpecial2Notes": "内なるドラゴンの波動の潜在力を感じさせます。力と知覚がそれぞれ <%= attrs %> 上がります。",
+ "weaponSpecial3Text": "ムステインの道標をマッシュするモーニングスター",
+ "weaponSpecial3Notes": "会議、モンスター、倦怠感 : 処理した! すりつぶせ! 力、知能、そして性格がそれぞれ <%= attrs %> 上がります。",
+ "weaponSpecialCriticalText": "バグつぶしの重大ハンマー",
+ "weaponSpecialCriticalNotes": "このチャンピオンは、多くの戦士が倒れた GitHub 上の重大バグを叩きのめしました。バグの骨から作られたこのハンマーは、強力な会心の一撃を与えます。力と知覚がそれぞれ <%= attrs %> 上がります。",
+ "weaponSpecialTridentOfCrashingTidesText": "破壊的大波のやす",
+ "weaponSpecialTridentOfCrashingTidesNotes": "魚に命令する能力がつきます。また、タスクに深い傷を与えます。知能が <%= int %> 上がります。",
+ "weaponSpecialYetiText": "雪男使いのやり",
+ "weaponSpecialYetiNotes": "このやりを使えば、どんな雪男にも命令できます。力が <%= str %> 上がります。2013年-2014年冬の限定装備。",
+ "weaponSpecialSkiText": "スノアイパー のポール",
+ "weaponSpecialSkiNotes": "敵の大群を破壊することができる武器! また、とても素晴らしいパラレルターンをするのにも役立ちます。力が <%= str %> 上がります。2013年-2014年冬の限定装備。",
+ "weaponSpecialCandycaneText": "キャンディー棒のつえ",
+ "weaponSpecialCandycaneNotes": "パワフルな魔道士のつえ。パワフルにおいしい! 両手もちの武器。知能が <%= int %>、知覚が <%= per %> 上がります。2013年-2014年冬の限定装備。",
+ "weaponSpecialSnowflakeText": "雪の結晶のつえ",
+ "weaponSpecialSnowflakeNotes": "このつえは無限のいやしの力で輝いています。知能が <%= int %> 上がります。2013年-2014年冬の限定装備。",
+ "weaponSpecialSpringRogueText": "かぎづめ",
+ "weaponSpecialSpringRogueNotes": "高いビルを登るのによし、またカーペットを切り裂くのにもよし。力が <%= str %> 上がります。2014年春の限定装備。",
+ "weaponSpecialSpringWarriorText": "ニンジンの剣",
+ "weaponSpecialSpringWarriorNotes": "この力強い剣は簡単にスライスできます! また、戦いの間に食べるおいしいおかしを作ることもできます。力が <%= str %> 上がります。2014年春の限定装備。",
+ "weaponSpecialSpringMageText": "スイスチーズのつえ",
+ "weaponSpecialSpringMageNotes": "パワフルなげっし類だけが空腹に立ち向かうために、この強力なつえを振るうことができる。知能が <%= int %>、知覚が<%= per %> 上がります。2014年春の限定装備。",
+ "weaponSpecialSpringHealerText": "愛すべき骨",
+ "weaponSpecialSpringHealerNotes": "獲得しました! 知能が <%= int %> 上がります。2014年春の限定装備。",
"weaponSpecialSummerRogueText": "海賊の剣",
- "weaponSpecialSummerRogueNotes": "Avast! You'll make those Dailies walk the plank! Increases Strength by <%= str %>. Limited Edition 2014 Summer Gear.",
- "weaponSpecialSummerWarriorText": "海賊のスライサー",
- "weaponSpecialSummerWarriorNotes": "この危険なナイフとからませたいToDoリストのタスクはありません!力が <%= str %> 上がる。2014年夏季限定版装備。",
+ "weaponSpecialSummerRogueNotes": "止めろ! この短剣を使えば、日課に甲板の上を歩かせることができます! 力が <%= str %> 上がります。2014年夏の限定装備。",
+ "weaponSpecialSummerWarriorText": "船乗りのスライサー",
+ "weaponSpecialSummerWarriorNotes": "この危険なナイフとかかわりになりたい To-Do タスクはありません! 力が <%= str %> 上がります。2014年夏の限定装備。",
"weaponSpecialSummerMageText": "昆布キャッチャー",
- "weaponSpecialSummerMageNotes": "この三つ叉は、効果的に海藻を突き刺すために使用し、追加生産の昆布を収穫する!知能が <%= int %>、知覚が <%= per %> 上がる。2014年夏季限定版装備。",
- "weaponSpecialSummerHealerText": "浅瀬のワンド",
- "weaponSpecialSummerHealerNotes": "アクアマリンと生きた珊瑚で作られたこの杖は、非常に魅力的な魚の学校です。知能が <%= int %> 上がる。2014年夏季限定版装備。",
- "weaponSpecialFallRogueText": "銀の火刑",
- "weaponSpecialFallRogueNotes": "ゾンビを処刑します。あなたも注意することができないので、狼男に対してもボーナスが与えられます。力が<%= str %>上がる。2014年秋季限定版装備。",
- "weaponSpecialFallWarriorText": "理工の掴んでる爪",
- "weaponSpecialFallWarriorNotes": "この魅力的な爪は、非常に最先端の技術でできている。力が<%= str %>上がる。2014年秋季限定版装備。",
+ "weaponSpecialSummerMageNotes": "このやすは、海藻を突き刺すのに効果的で、昆布の収穫でさらなる生産性をもたらします! 知能が <%= int %>、知覚が <%= per %> 上がります。2014年夏の限定装備。",
+ "weaponSpecialSummerHealerText": "浅瀬のつえ",
+ "weaponSpecialSummerHealerNotes": "このつえはアクアマリンと生きたサンゴで作られており、魚の学校にとても魅力的です。知能が <%= int %> 上がります。2014年夏の限定装備。",
+ "weaponSpecialFallRogueText": "銀のくい",
+ "weaponSpecialFallRogueNotes": "ゾンビを処刑します。そんなに注意深くもしていられないので、オオカミ男に対してもボーナスが与えられます。力が <%= str %> 上がる。2014年秋の限定装備。",
+ "weaponSpecialFallWarriorText": "魅力的な科学のつめ",
+ "weaponSpecialFallWarriorNotes": "この魅力的なつめは、テクノロジーの最先端です。力が <%= str %> 上がります。2014年秋の限定装備。",
"weaponSpecialFallMageText": "魔法のほうき",
- "weaponSpecialFallMageNotes": "この魅惑のほうきは、ドラゴンよりも速く飛ぶ!知能が<%= int %>、知覚が<%= per %> 上がる。2014年秋季限定版装備。",
- "weaponSpecialFallHealerText": "黄金虫の杖",
- "weaponSpecialFallHealerNotes": "この杖の上の黄金虫が、使う者を保護し癒す。知能が<%= int %>上がる。2014年秋季限定版装備。",
+ "weaponSpecialFallMageNotes": "この魔法のほうきは、ドラゴンよりも速く飛びます! 知能が <%= int %>、知覚が <%= per %> 上がります。2014年秋の限定装備。",
+ "weaponSpecialFallHealerText": "黄金虫のつえ",
+ "weaponSpecialFallHealerNotes": "つえに止まる黄金虫が使う者を保護し、いやします。知能が <%= int %> 上がります。2014年秋の限定装備。",
"weaponSpecialWinter2015RogueText": "氷のスパイク",
- "weaponSpecialWinter2015RogueNotes": "あなたは本当に、確かに、完全にただ地面から拾ったのでしょう。力が<%= str %>上がる。2014年-2015年冬季限定版装備。",
+ "weaponSpecialWinter2015RogueNotes": "本当に、確かに、完全に、道路の脇で拾っただけのもの。力が <%= str %> 上がります。2014年-2015年冬の限定装備。",
"weaponSpecialWinter2015WarriorText": "ガムドロップの剣",
- "weaponSpecialWinter2015WarriorNotes": "このおいしい剣はおそらく怪物に攻撃する。しかし挑戦する準備はできている!力が<%= str %>上がる。2014年-2015年冬季限定版装備。",
- "weaponSpecialWinter2015MageText": "冬の光の杖",
- "weaponSpecialWinter2015MageNotes": "この水晶の杖の光は、心を元気で満たす。知能が<%= int %>、知覚が<%= per %>上がる。2014年-2015年冬季限定版装備。",
- "weaponSpecialWinter2015HealerText": "やわらげる王笏",
- "weaponSpecialWinter2015HealerNotes": "この笏は痛む筋肉を暖めて、ストレスを落ち着かせる。知能が<%= int %>上がる。2014年-2015年冬季限定版装備。",
- "weaponSpecialSpring2015RogueText": "鳴き声の爆発",
- "weaponSpecialSpring2015RogueNotes": "この音にだまされないで!この爆発は強打を与える。力を<%= str %>点上げる。2015年春季限定装備。",
- "weaponSpecialSpring2015WarriorText": "骨こん棒",
- "weaponSpecialSpring2015WarriorNotes": "それは本物の恐ろしい犬の本物の骨のこん棒で、確かに噛むおもちゃではなく、誰かさん?が良い犬なので季節限定魔女があなたに与えました。。誰が良い犬でしょう??あなたです!!!あなたが良い犬なんです!!!力が <%= str %>上がる。2015年春季限定版装備。",
- "weaponSpecialSpring2015MageText": "魔術師の杖",
- "weaponSpecialSpring2015MageNotes": "Conjure up a carrot for yourself with this fancy wand. Increases Intelligence by <%= int %> and Perception by <%= per %>. Limited Edition 2015 Spring Gear.",
+ "weaponSpecialWinter2015WarriorNotes": "このおいしい剣はきっとモンスターをひきつけます…しかしあなたも挑む準備はできています! 力が <%= str %> 上がります。2014年-2015年冬の限定装備。",
+ "weaponSpecialWinter2015MageText": "冬の光のつえ",
+ "weaponSpecialWinter2015MageNotes": "この水晶の杖の光は、心を元気で満たします。知能が <%= int %>、知覚が <%= per %>上がります。2014年-2015年冬の限定装備。",
+ "weaponSpecialWinter2015HealerText": "なぐさめの王しゃく",
+ "weaponSpecialWinter2015HealerNotes": "このしゃくは痛む筋肉を暖め、ストレスをとりのぞき、なぐさめます。知能が <%= int %> 上がります。2014年-2015年冬の限定装備。",
+ "weaponSpecialSpring2015RogueText": "キーキー声の爆発",
+ "weaponSpecialSpring2015RogueNotes": "この音にだまされないで。この爆発は強打を与えます。力が <%= str %> 上がります。2015年春の限定装備。",
+ "weaponSpecialSpring2015WarriorText": "骨のこん棒",
+ "weaponSpecialSpring2015WarriorNotes": "本物の骨でできたこん棒であり、犬たちを本当にこわがらせ、犬がかんで喜ぶおもちゃではありません。期間限定の魔女が「だれがいいコちゃん? だ~れ~が~いいコ・な・の?………お前よ!!!」って、あなたにくれたものです。力が <%= str %> 上がります。2015年春の限定装備。",
+ "weaponSpecialSpring2015MageText": "手品師のつえ",
+ "weaponSpecialSpring2015MageNotes": "このかわいいつえで、自分のためにニンジンを召喚しましょう。知能が <%= int %> 、知覚が <%= per %> 上がります。2015年春の限定装備。",
"weaponSpecialSpring2015HealerText": "猫のがらがら",
"weaponSpecialSpring2015HealerNotes": "振れば魅惑的なカチカチ音がして誰もが何時間でも楽しむ事ができる。知能が<%= int %>上がる。2015年春季限定版装備。",
- "weaponSpecialSummer2015RogueText": "ファイヤーリング・コーラル",
- "weaponSpecialSummer2015RogueNotes": "このファイヤーコーラルの親戚は毒液を水中に突き抜く能力を持つ。力を<%= str %>点上げる。2015年夏季限定装備。",
+ "weaponSpecialSummer2015RogueText": "火炎放射サンゴ",
+ "weaponSpecialSummer2015RogueNotes": "このファイヤーコーラル(赤サンゴ)の親戚は、水中で毒液を射る能力を持っています。力を <%= str %> 上げます。2015年夏の限定装備。",
"weaponSpecialSummer2015WarriorText": "太陽のカジキ",
- "weaponSpecialSummer2015WarriorNotes": "蠢動を抑えさせれば、太陽のカジキは凄い武器だ。力を<%= str %>点で上げる。2015年夏季限定装備。",
+ "weaponSpecialSummer2015WarriorNotes": "太陽のカジキはおそろしい武器です。ただし、ジタバタと逃げようとする動きを抑えられれば、ですが。力が <%= str %> 上がります。2015年夏の限定装備。",
"weaponSpecialSummer2015MageText": "占い師の棒",
- "weaponSpecialSummer2015MageNotes": "この棒の宝石に隠然が輝く。知性を<%= per %>点、知覚を<%= per %>点上げる。2015年夏季限定装備。",
- "weaponSpecialSummer2015HealerText": "波のワンド",
- "weaponSpecialSummer2015HealerNotes": "船酔いと海の病気を治す!知性を<%= int %>点上げる。2015年夏季限定装備。",
- "weaponSpecialFall2015RogueText": "戦斧",
- "weaponSpecialFall2015RogueNotes": "Fearsome To-Dos cower before the flapping of this ax. Increases Strength by <%= str %>. Limited Edition 2015 Autumn Gear.",
+ "weaponSpecialSummer2015MageNotes": "このつえの宝石のなかで秘めた力が輝きます。知能が <%= per %> 、知覚が<%= per %> 上がります。2015年夏の限定装備。",
+ "weaponSpecialSummer2015HealerText": "うちよせる波のつえ",
+ "weaponSpecialSummer2015HealerNotes": "船酔いと海の病気を治します! 知能が <%= int %> 上がります。2015年夏の限定装備。",
+ "weaponSpecialFall2015RogueText": "バット・ル アックス",
+ "weaponSpecialFall2015RogueNotes": "おそろしい To-Do も、この羽ばたく「おの」の前では縮み上がるでしょう。力が <%= str %> 上がります。2015年秋の限定装備。",
"weaponSpecialFall2015WarriorText": "木の板",
- "weaponSpecialFall2015WarriorNotes": "Great for elevating things in cornfields and/or smacking tasks. Increases Strength by <%= str %>. Limited Edition 2015 Autumn Gear.",
- "weaponSpecialFall2015MageText": "Enchanted Thread",
- "weaponSpecialFall2015MageNotes": "A powerful Stitch Witch can control this enchanted thread without even touching it! Increases Intelligence by <%= int %> and Perception by <%= per %>. Limited Edition 2015 Autumn Gear.",
- "weaponSpecialFall2015HealerText": "Swamp-Slime Potion",
- "weaponSpecialFall2015HealerNotes": "Brewed to perfection! Now you just have to convince yourself to drink it. Increases Intelligence by <%= int %>. Limited Edition 2015 Autumn Gear.",
+ "weaponSpecialFall2015WarriorNotes": "とうもろこし畑で何かをもちあげるのに、そしてタスクをぶったたくのに役立ちます。力が <%= str %> 上がります。2015年秋の限定装備。",
+ "weaponSpecialFall2015MageText": "魔法の糸",
+ "weaponSpecialFall2015MageNotes": "力のある、ぬい物の魔物は、この魔法の糸を手を触れずして操ります! 知能が <%= int %>、知覚が <%= per %> 上がります。2015年秋の限定装備。",
+ "weaponSpecialFall2015HealerText": "沼のスライムの毒薬",
+ "weaponSpecialFall2015HealerNotes": "完璧に抽出されました! さあ、覚悟を決めて飲み干しましょう。知能が <%= int %> 上がります。2015年秋の限定アイテム。",
"weaponSpecialWinter2016RogueText": "ココアのマグカップ",
- "weaponSpecialWinter2016RogueNotes": "Warming drink, or boiling projectile? You decide... Increases Strength by <%= str %>. Limited Edition 2015-2016 Winter Gear.",
+ "weaponSpecialWinter2016RogueNotes": "飲み物を温めますか、それともプロジェクトを沸騰させますか? ご自分で決めてください… 力が <%= str %> 上がります。2015-2016年冬の限定アイテム。",
"weaponSpecialWinter2016WarriorText": "丈夫なシャベル",
- "weaponSpecialWinter2016WarriorNotes": "Shovel overdue tasks out of the way! Increases Strength by <%= str %>. Limited Edition 2015-2016 Winter Gear.",
- "weaponSpecialWinter2016MageText": "呪術的なスノーボードー",
- "weaponSpecialWinter2016MageNotes": "Your moves are so sick, they must be magic! Increases Intelligence by <%= int %> and Perception by <%= per %>. Limited Edition 2015-2016 Winter Gear.",
+ "weaponSpecialWinter2016WarriorNotes": "目標に向かう道から、しめきりをすぎたタスクをどけましょう! 力が <%= str %> 上がります。2015-2016年冬の限定装備。",
+ "weaponSpecialWinter2016MageText": "呪術的なスノーボード",
+ "weaponSpecialWinter2016MageNotes": "動きがイカレてて、魔法のようです! 知能が <%= int %>、知覚が <%= per %> 上がります。2015-2016冬の限定装備。",
"weaponSpecialWinter2016HealerText": "紙吹雪大砲",
- "weaponSpecialWinter2016HealerNotes": "WHEEEEEEEEEE!!!!!!! HAPPY WINTER WONDERLAND!!!!!!!! Increases Intelligence by <%= int %>. Limited Edition 2015-2016 Winter Gear.",
- "weaponSpecialSpring2016RogueText": "Fire Bolas",
- "weaponSpecialSpring2016RogueNotes": "You've mastered the ball, the club, and the knife. Now you advance to juggling fire! Awoo! Increases Strength by <%= str %>. Limited Edition 2016 Spring Gear.",
- "weaponSpecialSpring2016WarriorText": "チーズの杵",
- "weaponSpecialSpring2016WarriorNotes": "No one has as many friends as the mouse with tender cheeses. Increases Strength by <%= str %>. Limited Edition 2016 Spring Gear.",
- "weaponSpecialSpring2016MageText": "鈴の杖",
- "weaponSpecialSpring2016MageNotes": "Abra-cat-abra! So dazzling, you might mesmerize yourself! Ooh... it jingles... Increases Intelligence by <%= int %> and Perception by <%= per %>. Limited Edition 2016 Spring Gear.",
- "weaponSpecialSpring2016HealerText": "春花の杖",
- "weaponSpecialSpring2016HealerNotes": "With a wave and a wink, you bring the fields and forests into bloom! Or bop troublesome mice on the head. Increases Intelligence by <%= int %>. Limited Edition 2016 Spring Gear.",
+ "weaponSpecialWinter2016HealerNotes": "イエェェェェェーーーー!!!!!!!! 冬のワンダーランド、おめでとう!!!!!!!! 知能が <%= int %> 上がります。2015-2016冬の限定装備。",
+ "weaponSpecialSpring2016RogueText": "火の玉投げなわ",
+ "weaponSpecialSpring2016RogueNotes": "あなたはボールとこん棒とナイフをマスターしました。つづいて炎のジャグリングに進みます! フォーッ! 力が <%= str %> 上がります。2016年春の限定装備。",
+ "weaponSpecialSpring2016WarriorText": "チーズのつえ",
+ "weaponSpecialSpring2016WarriorNotes": "やわらかいチーズをもったネズミほどたくさんの友人をもつことは不可能です。力が <%= str %> 上がります。2016年春の限定装備。",
+ "weaponSpecialSpring2016MageText": "ベルのつえ",
+ "weaponSpecialSpring2016MageNotes": "アブラカタブラ! なんて魅惑的…あなたは自分に催眠術をかけます! おー…ベルが鳴る…知能が <%= int %>、知覚が <%= per %> 上がります。2016年春の限定装備。",
+ "weaponSpecialSpring2016HealerText": "春花のつえ",
+ "weaponSpecialSpring2016HealerNotes": "手を振り、ウインクして、野原と森を花でいっぱいにします! もしくは、やっかいなネズミの頭をぶんなぐることもできます。知能が <%= int %> 上がります。2016年春の限定装備。",
"weaponMystery201411Text": "ごちそうの熊手",
- "weaponMystery201411Notes": "敵を突き刺したり、あなたの好きな食べ物のために掘る - この何にでも使える熊手がそれをすべてを行う!効果なし。2014年11月寄付会員アイテム。",
- "weaponMystery201502Text": "愛ならびに真実の輝く羽の杖",
- "weaponMystery201502Notes": "羽のために!愛のために!ならびに真実のために!効果なし。2015年2月購読者アイテム。",
- "weaponMystery201505Text": "グリーンナイトランス",
- "weaponMystery201505Notes": "この緑と銀色のランスには多くの相手をマウントから倒した記録を持つ。効果なし。2015年5月購読者アイテム。",
- "weaponMystery301404Text": "スチームパンク杖",
- "weaponMystery301404Notes": "町でターンをするには最適だ。効果なし。3015年3月購読者アイテム。",
+ "weaponMystery201411Notes": "敵を突き刺したり、あなたの好きな食べ物のために掘る - この何にでも使える熊手は全部できます! 効果なし。2014年11月寄付会員アイテム。",
+ "weaponMystery201502Text": "キラキラ輝く羽のついた愛と真実つえ",
+ "weaponMystery201502Notes": "羽のために! 愛のために! そして真実のために! 効果なし。2015年2月寄付会員アイテム。",
+ "weaponMystery201505Text": "緑の騎士のやり",
+ "weaponMystery201505Notes": "この緑と銀色のやりは、多くの敵を騎獣から引きずり下ろしました。効果なし。2015年5月寄付会員アイテム。",
+ "weaponMystery301404Text": "スチームパンクなつえ",
+ "weaponMystery301404Notes": "町を散歩するのに最適です。効果なし。3015年3月寄付会員アイテム。",
"weaponArmoireBasicCrossbowText": "基本的なクロスボウ",
- "weaponArmoireBasicCrossbowNotes": "このクロスボウはタスクの鎧を遠くから射抜くことができます。力を<%= str %>、知覚を<%= per %>、体質を<%= con %>上げる。魔法の戸棚: 独立したアイテム。",
- "weaponArmoireLunarSceptreText": "Soothing Lunar Sceptre",
- "weaponArmoireLunarSceptreNotes": "The healing power of this wand waxes and wanes. Increases Constitution by <%= con %> and Intelligence by <%= int %>. Enchanted Armoire: Soothing Lunar Set (Item 3 of 3).",
- "weaponArmoireRancherLassoText": "牧童の投げ縄",
- "weaponArmoireRancherLassoNotes": "Lassos: the ideal tool for rounding up and wrangling. Increases Strength by <%= str %>, Perception by <%= per %>, and Intelligence by <%= int %>. Enchanted Armoire: Rancher Set (Item 3 of 3).",
- "weaponArmoireMythmakerSwordText": "Mythmaker Sword",
- "weaponArmoireMythmakerSwordNotes": "Though it may seem humble, this sword has made many mythic heroes. Increases Perception and Strength by <%= attrs %> each. Enchanted Armoire: Golden Toga Set (Item 3 of 3).",
- "weaponArmoireIronCrookText": "Iron Crook",
- "weaponArmoireIronCrookNotes": "Fiercely hammered from iron, this iron crook is good at herding sheep. Increases Perception and Strength by <%= attrs %> each. Enchanted Armoire: Horned Iron Set (Item 3 of 3).",
- "weaponArmoireGoldWingStaffText": "黄金な羽の杖",
- "weaponArmoireGoldWingStaffNotes": "The wings on this staff constantly flutter and twist. Increases all attributes by <%= attrs %> each. Enchanted Armoire: Independent Item.",
- "weaponArmoireBatWandText": "コウモリの杖",
- "weaponArmoireBatWandNotes": "This wand can turn any task into a bat! Wave it about and watch them fly away. Increases Intelligence by <%= int %> and Perception by <%= per %>. Enchanted Armoire: Independent Item.",
- "weaponArmoireShepherdsCrookText": "Shepherd's Crook",
- "weaponArmoireShepherdsCrookNotes": "Useful for herding gryphons. Increases Constitution by <%= con %>. Enchanted Armoire: Shepherd Set (Item 1 of 3).",
- "weaponArmoireCrystalCrescentStaffText": "Crystal Crescent Staff",
- "weaponArmoireCrystalCrescentStaffNotes": "Summon the power of the crescent moon with this shining staff! Increases Intelligence and Strength by <%= attrs %> each. Enchanted Armoire: Crystal Crescent Set (Item 3 of 3).",
- "weaponArmoireBlueLongbowText": "Blue Longbow",
- "weaponArmoireBlueLongbowNotes": "Ready... Aim... Fire! This bow has great range. Increases Perception by <%= per %>, Constitution by <%= con %>, and Strength by <%= str %>. Enchanted Armoire: Independent Item.",
- "weaponArmoireGlowingSpearText": "輝く槍",
- "weaponArmoireGlowingSpearNotes": "This spear hypnotizes wild tasks so you can attack them. Increases Strength by <%= str %>. Enchanted Armoire: Independent Item.",
- "weaponArmoireBarristerGavelText": "Barrister Gavel",
- "weaponArmoireBarristerGavelNotes": "Order! Increases Strength and Constitution by <%= attrs %> each. Enchanted Armoire: Barrister Set (Item 3 of 3).",
- "weaponArmoireJesterBatonText": "Jester Baton",
- "weaponArmoireJesterBatonNotes": "With a wave of your baton and some witty repartee, even the most complicated situations become clear. Increases Intelligence and Perception by <%= attrs %> each. Enchanted Armoire: Jester Set (Item 3 of 3).",
- "weaponArmoireMiningPickaxText": "Mining Pickax",
- "weaponArmoireMiningPickaxNotes": "Mine the maximum amount of gold from your tasks! Increases Perception by <%= per %>. Enchanted Armoire: Miner Set (Item 3 of 3).",
- "weaponArmoireBasicLongbowText": "Basic Longbow",
- "weaponArmoireBasicLongbowNotes": "A serviceable hand-me-down bow. Increases Strength by <%= str %>. Enchanted Armoire: Basic Archer Set (Item 1 of 3).",
- "armor": "鎧",
+ "weaponArmoireBasicCrossbowNotes": "このクロスボウはタスクのよろいを遠くから射ぬくことができます。力が <%= str %>、知覚が <%= per %>、性格が <%= con %> 上がります。魔法の戸棚 : 個別のアイテム。",
+ "weaponArmoireLunarSceptreText": "なぐさめの月の王しゃく",
+ "weaponArmoireLunarSceptreNotes": "月の満ち欠けのいやしパワーをもっています。性格が <%= con %> 、知能が <%= int %> 上がります。魔法の戸棚 : なぐさめの月セット ( 3 個中 3 つめのアイテム)。",
+ "weaponArmoireRancherLassoText": "牧場の投げなわ",
+ "weaponArmoireRancherLassoNotes": "投げなわ : しばりあげ、世話をするための典型的な道具。力が <%= str %>、知覚が <%= per %>、そして知能が <%= int %> 上がります。 魔法の戸棚 : 牧場セット ( 3 個中 3 つめのアイテム)。",
+ "weaponArmoireMythmakerSwordText": "神話の主人公のつるぎ",
+ "weaponArmoireMythmakerSwordNotes": "見た目は粗末ですが、このつるぎはたくさんの神話のヒーローを育てました。知覚と力が <%= attrs %> ずつ上がります。魔法の戸棚 : 黄金のトーガ セット ( 3 個中 3 つめのアイテム)。",
+ "weaponArmoireIronCrookText": "鉄製の羊飼いのつえ",
+ "weaponArmoireIronCrookNotes": "鉄から激しく「つち」できたえられた、この羊飼いのつえは、羊を集めるのにぴったりです。知覚と力が <%= attrs %> ずつ上がります。魔法の戸棚 : 角の生えた鉄製品 セット ( 3 個中 3 つめのアイテム)。",
+ "weaponArmoireGoldWingStaffText": "金の羽のつえ",
+ "weaponArmoireGoldWingStaffNotes": "このつえに生えている翼はいつも羽ばたき、ふわふわとしています。すべての能力値が <%= attrs %> ずつ上がります。魔法の戸棚 : 個別のアイテム。",
+ "weaponArmoireBatWandText": "コウモリのつえ",
+ "weaponArmoireBatWandNotes": "このつえは、どんなタスクでもコウモリに変えることができます! ふりかざして、タスクが飛んでいくのを見届けましょう。知能が <%= int %> 、知覚が <%= per %> 上がります。魔法の戸棚 : 個別のアイテム。",
+ "weaponArmoireShepherdsCrookText": "牧童のかぎづえ",
+ "weaponArmoireShepherdsCrookNotes": "グリフォンたちを追いこむのに便利。性格が <%= con %> 上がります。魔法の戸棚 : 牧場セット ( 3 個中 1 つめのアイテム )。",
+ "weaponArmoireCrystalCrescentStaffText": "クリスタルな三日月のつえ",
+ "weaponArmoireCrystalCrescentStaffNotes": "輝くつえで、三日月の力を呼びよせましょう! 知能と力が <%= attrs %> ずつ上がります。魔法の戸棚 : クリスタルな三日月セット ( 3 個中 3 つめのアイテム ) 。",
+ "weaponArmoireBlueLongbowText": "青い長弓",
+ "weaponArmoireBlueLongbowNotes": "打ち方用意…射て! この弓は射程距離が広いのです。知覚が <%= per %>、性格が<%= con %>、そして力が <%= str %> 上がります。魔法の戸棚:個別のアイテム。",
+ "weaponArmoireGlowingSpearText": "輝くやり",
+ "weaponArmoireGlowingSpearNotes": "このやりは、ワイルドなタスクに催眠術をかけるので、攻撃できるようになります。力が <%= str %> 上がります。魔法の戸棚 : 個別のアイテム。",
+ "weaponArmoireBarristerGavelText": "裁判官の小づち",
+ "weaponArmoireBarristerGavelNotes": "命ずる! 力と性格が <%= attrs %> ずつ上がります。魔法の戸棚 : 裁判官セット ( 3 個中 3 つめのアイテム ) 。",
+ "weaponArmoireJesterBatonText": "ピエロのバトン",
+ "weaponArmoireJesterBatonNotes": "バトンを振り、軽妙な語りで、そうとう複雑な状況を打開します。知能と知覚が <%= attrs %> ずつ上がります。魔法の戸棚 : ピエロ セット ( 3 個中 3 つめのアイテム ) 。",
+ "weaponArmoireMiningPickaxText": "炭鉱のつるはし",
+ "weaponArmoireMiningPickaxNotes": "タスクから、最大限のゴールドを掘り出しましょう! 知覚が <%= per %> 上がります。魔法の戸棚 : 炭鉱夫セット ( 3 個中 3 個目のアイテム)。",
+ "weaponArmoireBasicLongbowText": "基本の長弓",
+ "weaponArmoireBasicLongbowNotes": "便利なお下がりの弓。力が <%= str %> 上がります。魔法の戸棚 : 基本の弓兵セット ( 3 個中 1 つめのアイテム)。",
+ "weaponArmoireHabiticanDiplomaText": "Habitican Diploma",
+ "weaponArmoireHabiticanDiplomaNotes": "A certificate of significant achievement -- well done! Increases Intelligence by <%= int %>. Enchanted Armoire: Graduate Set (Item 1 of 3).",
+ "armor": "よろい",
"armorBase0Text": "無地の服",
"armorBase0Notes": "普通の服。効果なし。",
"armorWarrior1Text": "レザーアーマー",
- "armorWarrior1Notes": "頑丈な革のジャーキン。体質が<%= con %>上がる。",
- "armorWarrior2Text": "チェインメイル",
- "armorWarrior2Notes": "金属リングを組み合わされたアーマー。体質が<%= con %>上がる。",
+ "armorWarrior1Notes": "頑丈な革のジャーキン。性格が <%= con %> 上がります。",
+ "armorWarrior2Text": "チェーンメイル",
+ "armorWarrior2Notes": "金属リングを組み合わされたアーマー。性格が <%= con %> 上がります。",
"armorWarrior3Text": "プレートアーマー",
- "armorWarrior3Notes": "全てが鋼で覆われた騎士の誇りのスーツ。体質が<%= con %>上がる。",
- "armorWarrior4Text": "レッドアーマー",
- "armorWarrior4Notes": "守りの魔法で輝いてるヘビープレート。体質が<%= con %>上がる。",
- "armorWarrior5Text": "ゴールデンアーマー",
- "armorWarrior5Notes": "儀式のように見えるが、既知の刃では、それを貫通することができない。 体質が<%= con %> 上がる。",
+ "armorWarrior3Notes": "全てが鋼で覆われた騎士の誇りのスーツ。性格が <%= con %> 上がります。",
+ "armorWarrior4Text": "赤いよろい",
+ "armorWarrior4Notes": "守りの魔法で輝く重い板。体質が <%= con %> 上がります。",
+ "armorWarrior5Text": "黄金のよろい",
+ "armorWarrior5Notes": "儀式用に見えますが、知られているかぎりの刃で貫通できません。 性格が <%= con %> 上がります。",
"armorRogue1Text": "オイルレザー",
- "armorRogue1Notes": "雑音を減少させるために処理された革の鎧。知覚が<%= per %>上がる。",
- "armorRogue2Text": "ブラックレザー",
- "armorRogue2Notes": "暗い染料で着色され、影に溶け込む。知覚が<%= per %>上がる。",
+ "armorRogue1Notes": "雑音が減るように処理された革のよろい。知覚が <%= per %> 上がります。",
+ "armorRogue2Text": "黒い革",
+ "armorRogue2Notes": "暗い染料で着色され、影に溶けこみます。知覚が <%= per %> 上がります。",
"armorRogue3Text": "迷彩ベスト",
- "armorRogue3Notes": "地下牢や荒野の中で等しく目立たない。知覚が<%= per %>上がる。",
- "armorRogue4Text": "半影の鎧",
- "armorRogue4Notes": "装備した者は黄昏のベールに包まれる。知覚が<%= per %>上がる。",
- "armorRogue5Text": "本影の鎧",
- "armorRogue5Notes": "白昼のオープンな中で隠れることができる。知覚が<%= per %>上がる。",
+ "armorRogue3Notes": "ダンジョンでも荒野でも等しく目立たない。知覚が <%= per %> 上がります。",
+ "armorRogue4Text": "半影のよろい",
+ "armorRogue4Notes": "装備した者は黄昏のベールに包まれます。知覚が <%= per %> 上がります。",
+ "armorRogue5Text": "本影のよろい",
+ "armorRogue5Notes": "白昼のオープンな中で隠れることができます。知覚が <%= per %> 上がります。",
"armorWizard1Text": "魔術師のローブ",
- "armorWizard1Notes": "垣魔道士の衣装。知能が<%= int %>上がる。",
+ "armorWizard1Notes": "垣魔道士の衣装。知能が <%= int %> 上がります。",
"armorWizard2Text": "ウィザードローブ",
- "armorWizard2Notes": "放浪する不思議な労働者の服。知能が<%= int %>上がる。",
+ "armorWizard2Notes": "旅する (Wandering)、不思議な (Wonder) 労働者 (Worker) の服。知能が <%= int %> 上がります。",
"armorWizard3Text": "神秘のローブ",
- "armorWizard3Notes": "選り抜きの秘密への加入を意味します。知能が<%= int %>上がる。",
+ "armorWizard3Notes": "精鋭たちの秘伝の世界へ足を踏み入れることを意味します。知能が <%= int %> 上がります。",
"armorWizard4Text": "アークメイジのローブ",
- "armorWizard4Notes": "それを着た者は魂と精霊を支配できます。知能が<%= int %>上がる。",
+ "armorWizard4Notes": "これを着る者には魂と精霊がひざまずきます。知能が <%= int %> 上がります。",
"armorWizard5Text": "王宮魔術師のローブ",
- "armorWizard5Notes": "王位の権力の象徴。知能が<%= int %>上がる。",
+ "armorWizard5Notes": "王位の権力の象徴。知能が <%= int %> 上がります。",
"armorHealer1Text": "侍祭のローブ",
"armorHealer1Notes": "謙虚さと決意を示す衣服。体質が<%= con %>上がる。",
"armorHealer2Text": "医術士のローブ",
- "armorHealer2Notes": "戦闘で負傷した者の手当をする時に着用します。体質が<%= con %>上がる。",
+ "armorHealer2Notes": "戦闘で負傷した者の手当をする時に着用します。性格が <%= con %> 上がります。",
"armorHealer3Text": "守護者のマント",
- "armorHealer3Notes": "攻撃を避けるため、治療師の魔法が自分にかかるよう切り替わります。性格が<%= con %>上がる。",
+ "armorHealer3Notes": "攻撃を避けるため、治療師の魔法が自分にかかるよう切り替わります。性格が<%= con %>上がります。",
"armorHealer4Text": "医師のマント",
- "armorHealer4Notes": "権威を表し、呪いを解く。体質が<%= con %>上がる。",
+ "armorHealer4Notes": "権威を表し、呪いを解きます。性格が <%= con %> 上がります。",
"armorHealer5Text": "王家のマント",
- "armorHealer5Notes": "王の命を救った人々の服装。体質が<%= con %>上がる。",
- "armorSpecial0Text": "影の鎧",
- "armorSpecial0Notes": "打撃を受けた時、打たれた場所の痛みで悲鳴を上げるでしょう。体質が<%= con %>上がる。",
- "armorSpecial1Text": "クリスタルアーマー",
- "armorSpecial1Notes": "ありふれた不快感に漬かれた者に着用させれば、たゆまぬ力を与える。全ての能力値が<%= attrs %>上がる。",
- "armorSpecial2Text": "貴族のチュニック",
- "armorSpecial2Notes": "あなたを格別にふわふわにする!体質と知能がそれぞれ<%= attrs %>上がる。",
- "armorSpecialFinnedOceanicArmorText": "Finned Oceanic Armor",
- "armorSpecialFinnedOceanicArmorNotes": "Although delicate, this armor makes your skin as harmful to the touch as a fire coral. Increases Strength by <%= str %>.",
+ "armorHealer5Notes": "王の命を救った人々の服装。性格が <%= con %> 上がります。",
+ "armorSpecial0Text": "影のよろい",
+ "armorSpecial0Notes": "打撃を受けた時、打たれた場所の痛みで悲鳴を上げるでしょう。性格が <%= con %> 上がります。",
+ "armorSpecial1Text": "クリスタルのよろい",
+ "armorSpecial1Notes": "よくある疲れを感じている者が着ると、たゆまぬ力を与える。すべての能力値が <%= attrs %> 上がります。",
+ "armorSpecial2Text": "ジーン・カラルドの聖なるチュニック",
+ "armorSpecial2Notes": "すんごい、ふわふわになります! 性格と知能が <%= attrs %> ずつ上がります。",
+ "armorSpecialFinnedOceanicArmorText": "ひれがついた大海のよろい",
+ "armorSpecialFinnedOceanicArmorNotes": "デリケートではありますが、このよろいの表面は、赤サンゴのように敵にダメージを与えるます。力が <%= str %> 上がります。",
"armorSpecialYetiText": "イエティ調教師のローブ",
- "armorSpecialYetiNotes": "ファジー、そして熾烈。体質が<%= con %>上がる。2013年-2014年冬季限定版装備。",
- "armorSpecialSkiText": "スキーアサシンのパーカー",
- "armorSpecialSkiNotes": "大量のシークレットダガーとスキートレイルマップ。知覚が<%= per %>上がる。2013年-2014年冬季限定版装備。",
- "armorSpecialCandycaneText": "キャンディケインローブ",
- "armorSpecialCandycaneNotes": "砂糖とシルクから紡糸されたもの。知能が<%= int %>上がる。2013年-2014年冬季限定版装備。",
- "armorSpecialSnowflakeText": "雪のローブ",
- "armorSpecialSnowflakeNotes": "吹雪の中でも暖かさを保つローブ。体質が<%= con %>上がる。2013年-2014年冬季限定版装備。",
- "armorSpecialBirthdayText": "パーティーローブ",
- "armorSpecialBirthdayNotes": "Habiticaの誕生日おめでとう!この素晴らしい日を祝うために、このおかしなパーティーローブを着てください。効果なし。",
- "armorSpecialBirthday2015Text": "ばかげたパーティーローブ",
- "armorSpecialBirthday2015Notes": "Habiticaの誕生日おめでとう!この素晴らしい日を祝うために、このばかげたパーティーローブを着てください。効果なし。",
- "armorSpecialBirthday2016Text": "Ridiculous Party Robes",
- "armorSpecialBirthday2016Notes": "Happy Birthday, Habitica! Wear these Ridiculous Party Robes to celebrate this wonderful day. Confers no benefit.",
- "armorSpecialGaymerxText": "虹色の戦士の鎧",
- "armorSpecialGaymerxNotes": "In celebration of the GaymerX Conference, this special armor is decorated with a radiant, colorful rainbow pattern! GaymerX is a game convention celebrating LGTBQ and gaming and is open to everyone.",
- "armorSpecialSpringRogueText": "キャットスーツ",
- "armorSpecialSpringRogueNotes": "完璧に手入れを施されている。知覚が<%= per %>上がる。2014年春季限定版装備。",
- "armorSpecialSpringWarriorText": "クローバーの鎧",
- "armorSpecialSpringWarriorNotes": "クローバーのように軟質で、鋼のように強い!体質が<%= con %>上がる。2014年春季限定版装備。",
+ "armorSpecialYetiNotes": "けば立っており、すさまじい。性格が <%= con %> 上がります。2013年-2014年冬の限定装備。",
+ "armorSpecialSkiText": "スキー暗殺者のパーカー",
+ "armorSpecialSkiNotes": "隠したダガーとスキールートマップでいっぱいです。知覚が <%= per %> 上がります。2013年-2014年冬の限定装備。",
+ "armorSpecialCandycaneText": "さとうきびのローブ",
+ "armorSpecialCandycaneNotes": "砂糖と絹でつむいだものです。知能が <%= int %> 上がります。2013年-2014年冬の限定装備。",
+ "armorSpecialSnowflakeText": "粉雪のローブ",
+ "armorSpecialSnowflakeNotes": "吹雪の中でも暖かさを保つローブ。性格が <%= con %> 上がります。2013年-2014年冬の限定装備。",
+ "armorSpecialBirthdayText": "ちょっとおかしなパーティー ローブ",
+ "armorSpecialBirthdayNotes": "誕生日おめでとう、Habitica! このすばらしい日を祝うために、このちょっとおかしなパーティー ローブを着てください。効果なし。",
+ "armorSpecialBirthday2015Text": "おバカなパーティー ローブ",
+ "armorSpecialBirthday2015Notes": "誕生日おめでとう、Habitica! このすばらしい日を祝うために、このおバカなパーティー ローブを着てください。効果なし。",
+ "armorSpecialBirthday2016Text": "イカれたパーティー ローブ",
+ "armorSpecialBirthday2016Notes": "誕生日おめでとう、Habitica! このすばらしい日を祝うために、このイカれたパーティー ローブを着てください。効果なし。",
+ "armorSpecialGaymerxText": "虹色の戦士のよろい",
+ "armorSpecialGaymerxNotes": "GaymerX カンファレンスを記念し、この特別なよろいは晴れやかでカラフルなレインボー柄で彩られています。GaymerX とは、LGTBQ (性的マイノリティー)とゲーム、そしてゲームがあらゆる人に開かれたものであることを祝うゲーム見本市です。",
+ "armorSpecialSpringRogueText": "なめらかなネコのスーツ",
+ "armorSpecialSpringRogueNotes": "完璧に毛づくろいされています。知覚が <%= per %> 上がります。2014年春の限定装備。",
+ "armorSpecialSpringWarriorText": "クローバー鋼のよろい",
+ "armorSpecialSpringWarriorNotes": "クローバーのようにやわらかで、鋼のように強い! 性格が <%= con %> 上がります。2014年春の限定装備。",
"armorSpecialSpringMageText": "ネズミのローブ",
- "armorSpecialSpringMageNotes": "ネズミが素敵!知能が<%= int %>上がる。2014年春季限定版装備。",
- "armorSpecialSpringHealerText": "子犬のローブ",
- "armorSpecialSpringHealerNotes": "フィットしてて暖かいが、着用者を危害から守る。体質が<%= con %>上がる。2014年春季限定版装備。",
+ "armorSpecialSpringMageNotes": "ネズミって素敵! 知能が <%= int %> 上がります。2014年春の限定装備。",
+ "armorSpecialSpringHealerText": "ふわ毛の子犬ローブ",
+ "armorSpecialSpringHealerNotes": "暖かくぴったりしていますが、着ている者を危害から守ります。性格が <%= con %> 上がります。2014年春の限定装備。",
"armorSpecialSummerRogueText": "海賊のローブ",
- "armorSpecialSummerRogueNotes": "これらのローブはとても快適だ!いえぇー!知覚が<%= per %>上がる。2014年夏季限定版装備。",
+ "armorSpecialSummerRogueNotes": "これらのローブはとても快適だぜ、イエェェェッ! 知覚が <%= per %> 上がります。2014年夏の限定装備。",
"armorSpecialSummerWarriorText": "剣士のローブ",
- "armorSpecialSummerWarriorNotes": "攻撃と防御を完結します。体質が<%= con %>上がる。2014年夏季限定版装備。",
- "armorSpecialSummerMageText": "エメラルドの尻尾",
- "armorSpecialSummerMageNotes": "かすかに光るうろこの付いた衣服は、その着用者を本当のマーメイジに変える!知能が<%= int %>上がる。2014年夏季限定版装備。",
- "armorSpecialSummerHealerText": "人魚のしっぽ",
- "armorSpecialSummerHealerNotes": "かすかに光るうろこの付いた衣服は、その着用者を本当のシーヒーラーに変える!体質が<%= int %>上がる。2014年夏季限定版装備。",
- "armorSpecialFallRogueText": "ブラッドレッドローブ",
- "armorSpecialFallRogueNotes": "ビビッド。ベルベット。バンパイアてき。知覚が<%= per %>上がる。2014年秋季限定版装備。",
- "armorSpecialFallWarriorText": "科学の白衣",
- "armorSpecialFallWarriorNotes": "幻怪のポーションの溢流から防ぐ。体質が<%= con %>上がる。2014年秋季限定版装備。",
+ "armorSpecialSummerWarriorNotes": "剣で切り、盾で受け、タスクをやっつけます。性格が <%= con %> 上がります。2014年夏の限定装備。",
+ "armorSpecialSummerMageText": "エメラルドのしっぽ",
+ "armorSpecialSummerMageNotes": "このうろこがかすかに光る衣服は、着る人を本物のマーメイジ(マーメイド + メイジ・魔法使い)に変えます! 知能が<%= int %>上がります。2014年夏の限定装備。",
+ "armorSpecialSummerHealerText": "海の治療師の尾",
+ "armorSpecialSummerHealerNotes": "このうろこがかすかに光る衣服は、着る人を本物のシーヒーラーに変えます! 性格が <%= con %> 上がります。2014年夏の限定装備。",
+ "armorSpecialFallRogueText": "血のように赤いローブ",
+ "armorSpecialFallRogueNotes": "ビビッド。ベルベット。バンパイアっぽい。知覚が <%= per %> 上がります。2014年秋の限定装備。",
+ "armorSpecialFallWarriorText": "科学の研究着",
+ "armorSpecialFallWarriorNotes": "魔法の薬がこぼれても防ぎます。性格が <%= con %> 上がります。2014年秋の限定装備。",
"armorSpecialFallMageText": "魔女のウィザードローブ",
- "armorSpecialFallMageNotes": "このローブは、イモリの目とカエルの舌の助けを得るために、たくさんのポケットを持っている。知能が<%= int %>上がる。2014年秋季限定版装備。",
- "armorSpecialFallHealerText": "ガウジーギア",
- "armorSpecialFallHealerNotes": "戦いを包帯を巻く前に変化させる!体質が<%= con %>上がる。2014年秋季限定装備。",
- "armorSpecialWinter2015RogueText": "氷柱のドレイクアーマー",
- "armorSpecialWinter2015RogueNotes": "この鎧は冷たく凍っているが、アイスドレイクの群れの真ん中で秘宝を発見したときに確かに価値があるでしょう。あなたがそのような秘宝を探していなくても、あなたは本当に確かに本物のアイスドレイクなのだから。いいか!?訊ねるのは止めてください!知覚が<%= per %>上がる。2014年-2015年冬季限定版装備。",
- "armorSpecialWinter2015WarriorText": "ジンジャーブレッドの鎧",
- "armorSpecialWinter2015WarriorNotes": "快適で暖かい、オーブンから一直線!体質が<%= con %>上がる。 2014-2015年冬季限定版装備。",
- "armorSpecialWinter2015MageText": "オーロラローブ",
- "armorSpecialWinter2015MageNotes": "このローブに北のかすかな光が見える。知能が<%= int %>上がる。2014-2015年冬季限定版装備。",
+ "armorSpecialFallMageNotes": "このローブには、イモリの目やカエルの舌を入れておくのにすごく便利な、たくさんのポケットが付いています。知能が <%= int %> 上がります。2014年秋の限定装備。",
+ "armorSpecialFallHealerText": "うすもやの衣",
+ "armorSpecialFallHealerNotes": "先に包帯を巻いて、攻めこむ! 性格が <%= con %> 上がります。2014年秋の限定装備。",
+ "armorSpecialWinter2015RogueText": "アイスドレイクアーマー",
+ "armorSpecialWinter2015RogueNotes": "このよろいは冷たく凍っていますが、つららのドラゴンの群れの中央に未発見の秘宝を見つけたときには確かな価値があるでしょう。秘宝を探していなくいとしても、あなたは間違いなく、絶対に、正真正銘のつららのドラゴンなんです、OK!? 質問は受け付けません! 知覚が <%= per %> 上がります。2014年-2015年冬の限定装備。",
+ "armorSpecialWinter2015WarriorText": "ジンジャーブレッドのよろい",
+ "armorSpecialWinter2015WarriorNotes": "快適で暖かい、オーブンから直送! 性格が <%= con %> 上がります。 2014-2015年冬の限定装備。",
+ "armorSpecialWinter2015MageText": "オーロラのローブ",
+ "armorSpecialWinter2015MageNotes": "このローブに北方のかすかな光が見えます。知能が <%= int %> 上がります。2014-2015年冬の限定装備。",
"armorSpecialWinter2015HealerText": "アイススケートの服装",
- "armorSpecialWinter2015HealerNotes": "アイススケートはとてもリラックスできますが、アイスドレイクから攻撃を受けるときに備えてこの保護装備なしでは試さない方がいいでしょう。体質が<%= con %>上がる。2014-2015年冬季限定版装備。",
+ "armorSpecialWinter2015HealerNotes": "アイススケートはとてもリラックスできますが、アイスドレイクから攻撃を受ける可能性を考えると、この保護装備なしでは試さない方がいいでしょう。性格が <%= con %> 上がります。2014-2015年冬の限定装備。",
"armorSpecialSpring2015RogueText": "チューチューローブ",
- "armorSpecialSpring2015RogueNotes": "毛布で覆われていて柔らかく決して燃えない。知覚が<%= per %>上がる。2015年春季限定版装備。",
- "armorSpecialSpring2015WarriorText": "用心の鎧",
- "armorSpecialSpring2015WarriorNotes": "最も獰猛な小犬だけは、こんなにふわふわであると認められる。体質が<%= con %>上がる。2015年春季限定版装備。",
- "armorSpecialSpring2015MageText": "魔術師のうさぎスーツ",
- "armorSpecialSpring2015MageNotes": "あなたの上着のすそはあなたのワタオウサギに似合っている!知能が<%= int %>上がる。2015年春期限定版装備。",
- "armorSpecialSpring2015HealerText": "慰める猫スーツ",
- "armorSpecialSpring2015HealerNotes": "この柔らかいキャットスーツは快適で、ミント茶と同じくらい元気づけられる。体質が <%= con %>上がる。2015年春季限定版装備。",
+ "armorSpecialSpring2015RogueNotes": "毛布で覆われていて柔らかく決して燃えない。知覚が <%= per %> 上がります。2015年春の限定装備。",
+ "armorSpecialSpring2015WarriorText": "用心のよろい",
+ "armorSpecialSpring2015WarriorNotes": "最も獰猛(どうもう)な小犬にだけ、このふわふわが許されます。性格が <%= con %> 上がります。2015年春の限定装備。",
+ "armorSpecialSpring2015MageText": "手品師のうさぎスーツ",
+ "armorSpecialSpring2015MageNotes": "コートのすそが、ふわふわしっぽにお似合いです! 知能が <%= int %> 上がります。2015年春の限定装備。",
+ "armorSpecialSpring2015HealerText": "くつろぎのキャットスーツ",
+ "armorSpecialSpring2015HealerNotes": "このやわらかいキャットスーツは快適で、ミントティーと同じくらいくつろげます。性格が <%= con %> 上がります。2015年春の限定装備。",
"armorSpecialSummer2015RogueText": "ルビーのしっぽ",
- "armorSpecialSummer2015RogueNotes": "This garment of shimmering scales transforms its wearer into a real Reef Renegade! Increases Perception by <%= per %>. Limited Edition 2015 Summer Gear.",
+ "armorSpecialSummer2015RogueNotes": "このうろこがかすかに光る衣服は、着る人を本物の岩礁の裏切り者に変えます! 知覚が<%= per %>上がります。2015年夏の限定装備。",
"armorSpecialSummer2015WarriorText": "黄金のしっぽ",
- "armorSpecialSummer2015WarriorNotes": "このきらめき鱗の衣類を身につけると本物の翻車魚戦士に変身する!体質は<%= con %>上がる。2015年夏限定装備。",
- "armorSpecialSummer2015MageText": "占い師の衣",
- "armorSpecialSummer2015MageNotes": "Hidden power resides in the puffs of these sleeves. Increases Intelligence by <%= int %>. Limited Edition 2015 Summer Gear.",
- "armorSpecialSummer2015HealerText": "船員の武具",
- "armorSpecialSummer2015HealerNotes": "This armor lets everyone know that you are an honest merchant sailor who would never dream of behaving like a scalawag. Increases Constitution by <%= con %>. Limited Edition 2015 Summer Gear.",
- "armorSpecialFall2015RogueText": "Bat-tle Armor",
- "armorSpecialFall2015RogueNotes": "Fly into bat-tle! Increases Perception by <%= per %>. Limited Edition 2015 Autumn Gear.",
- "armorSpecialFall2015WarriorText": "案山子の鎧",
- "armorSpecialFall2015WarriorNotes": "Despite being stuffed with straw, this armor is extremely hefty! Increases Constitution by <%= con %>. Limited Edition 2015 Autumn Gear.",
- "armorSpecialFall2015MageText": "Stitched Robes",
+ "armorSpecialSummer2015WarriorNotes": "このうろこがかすかに光る衣服は、着る人を本物のマンボウ戦士に変えます! 性格が<%= con %>上がります。2015年夏の限定装備。",
+ "armorSpecialSummer2015MageText": "占い師のローブ",
+ "armorSpecialSummer2015MageNotes": "ふくらんだ袖の中に、秘めた力が宿っています。知能が <%= int %> 上がります。2015年夏の限定装備。",
+ "armorSpecialSummer2015HealerText": "船員のよろい",
+ "armorSpecialSummer2015HealerNotes": "このよろいを着ていれば、この人は正直者の商船乗組員で、人でなしのようにふるまうことは想像できないと、だれもが思います。性格が <%= con %> 上がります。2015年夏の限定装備。",
+ "armorSpecialFall2015RogueText": "バット・ル アーマー",
+ "armorSpecialFall2015RogueNotes": "バット(こうもり)になって、バトルに飛び立ちましょう! 知覚が <%= per %> 上がります。2015年秋の限定装備。",
+ "armorSpecialFall2015WarriorText": "かかしのよろい",
+ "armorSpecialFall2015WarriorNotes": "このよろいは、麦わらで出来ているのにきわめて丈夫! \n性格が <%= con %> 上がります。2015年秋の限定装備。",
+ "armorSpecialFall2015MageText": "刺繍(ししゅう)付きのローブ",
"armorSpecialFall2015MageNotes": "Every stitch in this armor shimmers with enchantment. Increases Intelligence by <%= int %>. Limited Edition 2015 Autumn Gear.",
"armorSpecialFall2015HealerText": "Potioner Robes",
"armorSpecialFall2015HealerNotes": "What? Of course that was a potion of constitution. No, you are definitely not turning into a frog! Don't be ribbiticulous. Increases Constitution by <%= con %>. Limited Edition 2015 Autumn Gear.",
- "armorSpecialWinter2016RogueText": "Cocoa Armor",
+ "armorSpecialWinter2016RogueText": "ココアの鎧",
"armorSpecialWinter2016RogueNotes": "This leather armor keeps you nice and toasty. Is it actually made from cocoa? You'll never tell. Increases Perception by <%= per %>. Limited Edition 2015-2016 Winter Gear.",
- "armorSpecialWinter2016WarriorText": "Snowman Suit",
+ "armorSpecialWinter2016WarriorText": "雪だるまスーツ",
"armorSpecialWinter2016WarriorNotes": "Brr! This padded armor is truly powerful... until it melts. Increases Constitution by <%= con %>. Limited Edition 2015-2016 Winter Gear.",
"armorSpecialWinter2016MageText": "Snowboarder Parka",
"armorSpecialWinter2016MageNotes": "The wisest wizard keeps well-bundled in the winter wind. Increases Intelligence by <%= int %>. Limited Edition 2015-2016 Winter Gear.",
"armorSpecialWinter2016HealerText": "Festive Fairy Cloak",
"armorSpecialWinter2016HealerNotes": "Festive Fairies wrap their body wings around themselves for protection as they use their head wings to catch headwinds and fly around Habitica at speeds of up to 100 mph, delivering gifts and spraying everyone with confetti. How droll. Increases Constitution by <%= con %>. Limited Edition 2015-2016 Winter Gear.",
- "armorSpecialSpring2016RogueText": "Canine Camo Suit",
- "armorSpecialSpring2016RogueNotes": "A clever pup knows to choose a brighter guise for concealment when everything is green and vibrant. Increases Perception by <%= per %>. Limited Edition 2016 Spring Gear.",
- "armorSpecialSpring2016WarriorText": "Mighty Mail",
- "armorSpecialSpring2016WarriorNotes": "Though you be but little, you are fierce! Increases Constitution by <%= con %>. Limited Edition 2016 Spring Gear.",
- "armorSpecialSpring2016MageText": "Grand Malkin Robes",
- "armorSpecialSpring2016MageNotes": "Brightly colored, so you won't be mistaken for a necromouser. Increases Intelligence by <%= int %>. Limited Edition 2016 Spring Gear.",
- "armorSpecialSpring2016HealerText": "Fluffy Bunny Breeches",
- "armorSpecialSpring2016HealerNotes": "Hippity hop! Bound from hill to hill, healing those in need. Increases Constitution by <%= con %>. Limited Edition 2016 Spring Gear.",
+ "armorSpecialSpring2016RogueText": "犬用カモ柄スーツ",
+ "armorSpecialSpring2016RogueNotes": "かしこい子犬は知っています。すべてが活気あふれる緑色になる季節は、かくれるのにふさわしいのは明るい装いだということを。知覚が <%= per %> が上がります。2016年春の限定装備。",
+ "armorSpecialSpring2016WarriorText": "丈夫なくさりかたびら",
+ "armorSpecialSpring2016WarriorNotes": "あなたは、小ささくたって、どう猛です! 性格が <%= con %> 上がります。2016年春の限定装備。",
+ "armorSpecialSpring2016MageText": "マダム マルキンのローブ",
+ "armorSpecialSpring2016MageNotes": "明るく染め上げられ、けっしてネクロマンサーと間違えられることはなくなります。知能が <%= int %> 上がります。2016年春の限定装備。",
+ "armorSpecialSpring2016HealerText": "ふわふわウサギの半ズボン",
+ "armorSpecialSpring2016HealerNotes": "ピョーンピョン! 丘から丘へ、必要とあらば回復も。性格が <%= con %> 上がります。2016年春の限定装備。",
"armorMystery201402Text": "使者のローブ",
"armorMystery201402Notes": "かすかに光って、強くて、これらのローブは、手紙を運ぶために多くのポケットがある。効果なし。2014年2月購読者アイテム。",
"armorMystery201403Text": "フォレストウォーカーアーマー",
@@ -352,33 +354,37 @@
"armorMystery201503Notes": "この青い鉱物は、幸運、幸せと永遠の生産性を象徴する。効果なし。2015年3月購読者アイテム。",
"armorMystery201504Text": "忙しい蜂のローブ",
"armorMystery201504Notes": "このローブを獲得することで忙しい蜂のように生産的になれるでしょう!効果なし。2015年4月購読者アイテム。",
- "armorMystery201506Text": "Snorkel Suit",
- "armorMystery201506Notes": "Snorkel through a coral reef in this brightly-colored swim suit! Confers no benefit. June 2015 Subscriber Item.",
+ "armorMystery201506Text": "シュノーケルスーツ",
+ "armorMystery201506Notes": "この明るい色の潜水服で、サンゴ礁の海を泳ぎましょう!効果なし。2015年6月購読者アイテム。",
"armorMystery201508Text": "チータの衣装",
- "armorMystery201508Notes": "Run fast as a flash in the fluffy Cheetah Costume! Confers no benefit. August 2015 Subscriber Item.",
+ "armorMystery201508Notes": "このチーターのコスチュームを着て、閃光の如く走りましょう!効果なし。2015年8月購読者アイテム。",
"armorMystery201509Text": "人狼のコスチューム",
"armorMystery201509Notes": "これ本当に衣装だよね?効果なし。2015年9月購読者アイテム。",
"armorMystery201511Text": "木の鎧",
- "armorMystery201511Notes": "Considering this armor was carved directly from a magical log, it's surprisingly comfortable. Confers no benefit. November 2015 Subscriber Item.",
- "armorMystery201512Text": "冷やし炎の鎧",
- "armorMystery201512Notes": "Summon the icy flames of winter! Confers no benefit. December 2015 Subscriber Item.",
- "armorMystery201603Text": "Lucky Suit",
- "armorMystery201603Notes": "This suit is sewn from thousands of four-leafed clovers! Confers no benefit. March 2016 Subscriber Item.",
+ "armorMystery201511Notes": "魔法の丸太を直接削って造られた鎧…にしては着心地がびっくりするほどいい。効果なし。2015年11月購読者アイテム。",
+ "armorMystery201512Text": "冷たい炎の鎧",
+ "armorMystery201512Notes": "冷たい炎を呼び起こせ!効果なし。2015年12月購読者アイテム。",
+ "armorMystery201603Text": "幸運のスーツ",
+ "armorMystery201603Notes": "何千もの四つ葉のクローバーで綴られたスーツ!効果なし。2016年3月購読者アイテム。",
+ "armorMystery201604Text": "Armor o' Leaves",
+ "armorMystery201604Notes": "You, too, can be a small but fearsome leaf puff. Confers no benefit. April 2016 Subscriber Item.",
+ "armorMystery201605Text": "Marching Bard Uniform",
+ "armorMystery201605Notes": "Unlike the traditional bards who join adventuring parties, bards who join Habitican marching bands are known for grand parades, not dungeon raids. Confers no benefit. May 2016 Subscriber Item.",
"armorMystery301404Text": "スチームパンクスーツ",
- "armorMystery301404Notes": "小粋な、そして、颯爽とした!効果なし。3015年2月購読者アイテム。",
- "armorArmoireLunarArmorText": "Soothing Lunar Armor",
- "armorArmoireLunarArmorNotes": "The light of the moon will make you strong and savvy. Increases Strength by <%= str %> and Intelligence by <%= int %>. Enchanted Armoire: Soothing Lunar Set (Item 2 of 3).",
+ "armorMystery301404Notes": "なんて小粋で最先端!効果なし。3015年2月購読者アイテム。",
+ "armorArmoireLunarArmorText": "慰める月の鎧",
+ "armorArmoireLunarArmorNotes": "月の光があなたに力と守りを与えます。力が<%= str %> 、知能が<%= int %>上がります。魔法の戸棚 : なぐさめの月セット ( 3 個中 2 つめのアイテム)。",
"armorArmoireGladiatorArmorText": "剣闘士アーマー",
"armorArmoireGladiatorArmorNotes": "剣闘士になるには賢いだけではなく、強くなければならない。知覚を<%= per %>、力を<%= str %>上げる。魔法の戸棚:剣闘士セット (3アイテム中2番)。",
"armorArmoireRancherRobesText": "牧童のローブ",
"armorArmoireRancherRobesNotes": "Wrangle your mounts and round up your pets while wearing these magical Rancher Robes! Increases Strength by <%= str %>, Perception by <%= per %>, and Intelligence by <%= int %>. Enchanted Armoire: Rancher Set (Item 2 of 3).",
"armorArmoireGoldenTogaText": "黄金のトーガ",
"armorArmoireGoldenTogaNotes": "This glimmering toga is only worn by true heroes. Increases Strength and Constitution by <%= attrs %> each. Enchanted Armoire: Golden Toga Set (Item 1 of 3).",
- "armorArmoireHornedIronArmorText": "Horned Iron Armor",
+ "armorArmoireHornedIronArmorText": "尖った鉄の鎧",
"armorArmoireHornedIronArmorNotes": "Fiercely hammered from iron, this horned armor is nearly impossible to break. Increases Constitution by <%= con %> and Perception by <%= per %>. Enchanted Armoire: Horned Iron Set (Item 2 of 3).",
"armorArmoirePlagueDoctorOvercoatText": "Plague Doctor Overcoat",
"armorArmoirePlagueDoctorOvercoatNotes": "An authentic overcoat worn by the doctors who battle the Plague of Procrastination! Increases Intelligence by <%= int %>, Strength by <%= str %>, and Constitution by <%= con %>. Enchanted Armoire: Plague Doctor Set (Item 3 of 3).",
- "armorArmoireShepherdRobesText": "Shepherd Robes",
+ "armorArmoireShepherdRobesText": " シェパードの衣",
"armorArmoireShepherdRobesNotes": "The fabric is cool and breathable, perfect for a hot day herding gryphons in the desert. Increases Strength and Perception by <%= attrs %> each. Enchanted Armoire: Shepherd Set (Item 2 of 3).",
"armorArmoireRoyalRobesText": "王立の法服",
"armorArmoireRoyalRobesNotes": "Wonderful ruler, rule all day long! Increases Constitution, Intelligence, and Perception by <%= attrs %> each. Enchanted Armoire: Royal Set (Item 3 of 3).",
@@ -394,6 +400,8 @@
"armorArmoireMinerOverallsNotes": "They may seem worn, but they are enchanted to repel dirt. Increases Constitution by <%= con %>. Enchanted Armoire: Miner Set (Item 2 of 3).",
"armorArmoireBasicArcherArmorText": "Basic Archer Armor",
"armorArmoireBasicArcherArmorNotes": "This camouflaged vest lets you slip unnoticed through the forests. Increases Perception by <%= per %>. Enchanted Armoire: Basic Archer Set (Item 2 of 3).",
+ "armorArmoireGraduateRobeText": "Graduate Robe",
+ "armorArmoireGraduateRobeNotes": "Congratulations! This weighty robe hangs heavy with all the knowledge you have accrued. Increases Intelligence by <%= int %>. Enchanted Armoire: Graduate Set (Item 2 of 3).",
"headgear": "帽子・かぶと",
"headBase0Text": "兜なし",
"headBase0Notes": "帽子・かぶとなし",
@@ -443,8 +451,8 @@
"headSpecial1Notes": "手本となるような人の人気の冠。全ての能力値が<%= attrs %>増加します。",
"headSpecial2Text": "名無しヘルム",
"headSpecial2Notes": "見返りを求めない人たちに与えた証。知能と力がそれぞれ<%= attrs %>上がる。",
- "headSpecialFireCoralCircletText": "Fire Coral Circlet",
- "headSpecialFireCoralCircletNotes": "This circlet, designed by Habitica's greatest alchemists, allows you to breathe water and dive for treasure! Increases Perception by <%= per %>.",
+ "headSpecialFireCoralCircletText": "赤いサンゴのサークレット",
+ "headSpecialFireCoralCircletNotes": "このHabitica最高の錬金術師によって作られたサークレットは、あなたを水中で呼吸できるようにし宝物の為のダイビングを可能とします!知覚が<%= per %>上がる。",
"headSpecialNyeText": "変わったパーティハット",
"headSpecialNyeNotes": "変わったパーティハットをもらった!新年を祝いながら誇らしげにかぶれ!効果なし。",
"headSpecialYetiText": "イエティのテイマーヘルム",
@@ -513,24 +521,24 @@
"headSpecialFall2015MageNotes": "Every stitch in this hat augments its power. Increases Perception by <%= per %>. Limited Edition 2015 Autumn Gear.",
"headSpecialFall2015HealerText": "カエルの帽子",
"headSpecialFall2015HealerNotes": "This is an extremely serious hat that is worthy of only the most advanced potioners. Increases Intelligence by <%= int %>. Limited Edition 2015 Autumn Gear.",
- "headSpecialNye2015Text": "Ridiculous Party Hat",
+ "headSpecialNye2015Text": "おかしなパーティーの帽子",
"headSpecialNye2015Notes": "You've received a Ridiculous Party Hat! Wear it with pride while ringing in the New Year! Confers no benefit.",
- "headSpecialWinter2016RogueText": "Cocoa Helm",
+ "headSpecialWinter2016RogueText": "ココアのヘルメット",
"headSpecialWinter2016RogueNotes": "The protective scarf on this cozy helm is only removed to sip warm winter beverages. Increases Perception by <%= per %>. Limited Edition 2015-2016 Winter Gear.",
- "headSpecialWinter2016WarriorText": "Snowman Cap",
+ "headSpecialWinter2016WarriorText": "雪だるま帽子",
"headSpecialWinter2016WarriorNotes": "Brr! This mighty helm is truly powerful... until it melts. Increases Strength by <%= str %>. Limited Edition 2015-2016 Winter Gear.",
"headSpecialWinter2016MageText": "Snowboarder Hood",
"headSpecialWinter2016MageNotes": "Keeps the snow out of your eyes while you're casting spells. Increases Perception by <%= per %>. Limited Edition 2015-2016 Winter Gear.",
"headSpecialWinter2016HealerText": "Fairy Wing Helm",
"headSpecialWinter2016HealerNotes": "Thesewingsfluttersoquicklythattheyblur! Increases Intelligence by <%= int %>. Limited Edition 2015-2016 Winter Gear.",
- "headSpecialSpring2016RogueText": "Good Doggy Mask",
- "headSpecialSpring2016RogueNotes": "Aww, what a cute puppy! Come here and let me pet your head. ...Hey, where did all my Gold go? Increases Perception by <%= per %>. Limited Edition 2016 Spring Gear.",
- "headSpecialSpring2016WarriorText": "Mouse Guard Helm",
- "headSpecialSpring2016WarriorNotes": "Never again shall you be bopped on the head! Let them try! Increases Strength by <%= str %>. Limited Edition 2016 Spring Gear.",
- "headSpecialSpring2016MageText": "Grand Malkin Hat",
- "headSpecialSpring2016MageNotes": "Apparel to set you above the mere alley-mages of the world. Increases Perception by <%= per %>. Limited Edition 2016 Spring Gear.",
- "headSpecialSpring2016HealerText": "Blossom Diadem",
- "headSpecialSpring2016HealerNotes": "It glints with the potential of new life ready to burst forth. Increases Intelligence by <%= int %>. Limited Edition 2016 Spring Gear.",
+ "headSpecialSpring2016RogueText": "よくできた犬のマスク",
+ "headSpecialSpring2016RogueNotes": "わー、なんてかわいいワンちゃん! ここにきて頭をなでさせて。…あら? 私のゴールドはどこにいったのかしら? 知覚が <%= per %> 上がります。2016年春の限定装備。",
+ "headSpecialSpring2016WarriorText": "ネズミの防御ヘルメット",
+ "headSpecialSpring2016WarriorNotes": "頭をぶんなぐらせるのは止めさせて! アイツらに試してみましょう! 力が <%= str %> アップします。2016年春の限定装備。",
+ "headSpecialSpring2016MageText": "ミセス マルキンの帽子",
+ "headSpecialSpring2016MageNotes": "あなたを「世界中のそんじょそこらにいる魔法使いとは違うぞ」という感じにする服装。知覚が <%= per %> 上がります。2016年春の限定装備。",
+ "headSpecialSpring2016HealerText": "花の王冠",
+ "headSpecialSpring2016HealerNotes": "ぐっと前進する新しい生命のような潜在能力のような輝きを放ちます。知能が <%= int %> 上がります。2016年春の限定装備。",
"headSpecialGaymerxText": "虹色の戦士の兜",
"headSpecialGaymerxNotes": "In celebration of the GaymerX Conference, this special helmet is decorated with a radiant, colorful rainbow pattern! GaymerX is a game convention celebrating LGTBQ and gaming and is open to everyone.",
"headMystery201402Text": "羽かぶと",
@@ -565,6 +573,10 @@
"headMystery201602Notes": "Shield your identity from all your admirers. Confers no benefit. February 2016 Subscriber Item.",
"headMystery201603Text": "幸運の帽子",
"headMystery201603Notes": "This top hat is a magical good-luck charm. Confers no benefit. March 2016 Subscriber Item.",
+ "headMystery201604Text": "Crown o' Flowers",
+ "headMystery201604Notes": "These woven flowers make a surprisingly strong helm! Confers no benefit. April 2016 Subscriber Item.",
+ "headMystery201605Text": "Marching Bard Hat",
+ "headMystery201605Notes": "Seventy-six dragons led the big parade, with a hundred and ten gryphons close at hand! Confers no benefit. May 2016 Subscriber Item.",
"headMystery301404Text": "ファンシートップハット",
"headMystery301404Notes": "最上の良家の人々のためのファンシートップハット!3015年1月購読者アイテム。効果なし。",
"headMystery301405Text": "ベーシックトップハット",
@@ -582,25 +594,25 @@
"headArmoireBlueHairbowText": "青いリボン",
"headArmoireBlueHairbowNotes": "Become perceptive, tough, and smart while wearing this beautiful Blue Hairbow! Increases Perception by <%= per %>, Constitution by <%= con %>, and Intelligence by <%= int %>. Enchanted Armoire: Independent Item.",
"headArmoireRoyalCrownText": "王の冠",
- "headArmoireRoyalCrownNotes": "Hooray for the ruler, mighty and strong! Increases Strength by <%= str %>. Enchanted Armoire: Royal Set (Item 1 of 3).",
- "headArmoireGoldenLaurelsText": "Golden Laurels",
- "headArmoireGoldenLaurelsNotes": "These golden laurels reward those who have conquered bad habits. Increases Perception and Constitution by <%= attrs %> each. Enchanted Armoire: Golden Toga Set (Item 2 of 3).",
- "headArmoireHornedIronHelmText": "Horned Iron Helm",
- "headArmoireHornedIronHelmNotes": "Fiercely hammered from iron, this horned helmet is nearly impossible to break. Increases Constitution by <%= con %> and Strength by <%= str %>. Enchanted Armoire: Horned Iron Set (Item 1 of 3).",
+ "headArmoireRoyalCrownNotes": "偉大にして強大なる統治者万歳!力が<%= str %>上がる。魔法の戸棚:王者セット(3アイテム中の1つ目)",
+ "headArmoireGoldenLaurelsText": "黄金の月桂冠",
+ "headArmoireGoldenLaurelsNotes": "悪い習慣を克服した者を称える黄金の月桂冠。知覚と体質が <%= attrs %> 上がる。魔法の戸棚:黄金のトーガセット (3アイテム中の2番目)。",
+ "headArmoireHornedIronHelmText": "角ある鋼鉄の兜",
+ "headArmoireHornedIronHelmNotes": "鋼鉄を激しく打ち出して作られたこの角あるヘルメットを壊すことは至難でしょう。体力が<%= con %>、力が <%= str %>上昇します。魔法の戸棚:角ある鋼鉄セット(3アイテム中の1番目)",
"headArmoireYellowHairbowText": "黄色のリボン",
- "headArmoireYellowHairbowNotes": "Become perceptive, strong, and smart while wearing this beautiful Yellow Hairbow! Increases Perception, Strength, and Intelligence by <%= attrs %> each. Enchanted Armoire: Independent Item.",
- "headArmoireRedFloppyHatText": "Red Floppy Hat",
- "headArmoireRedFloppyHatNotes": "Many spells have been sewn into this simple hat, giving it a radiant red color. Increases Constitution, Intelligence, and Perception by <%= attrs %> each. Enchanted Armoire: Independent Item.",
- "headArmoirePlagueDoctorHatText": "Plague Doctor Hat",
- "headArmoirePlagueDoctorHatNotes": "An authentic hat worn by the doctors who battle the Plague of Procrastination! Increases Strength by <%= str %>, Intelligence by <%= int %>, and Constitution by <%= con %>. Enchanted Armoire: Plague Doctor Set (Item 1 of 3).",
- "headArmoireBlackCatText": "黒猫帽子",
- "headArmoireBlackCatNotes": "This black hat is... purring. And twitching its tail. And breathing? Yeah, you just have a sleeping cat on your head. Increases Intelligence and Perception by <%= attrs %> each. Enchanted Armoire: Independent Item.",
- "headArmoireOrangeCatText": "オレンジ色猫帽子",
- "headArmoireOrangeCatNotes": "This orange hat is... purring. And twitching its tail. And breathing? Yeah, you just have a sleeping cat on your head. Increases Strength and Constitution by <%= attrs %> each. Enchanted Armoire: Independent Item.",
- "headArmoireBlueFloppyHatText": "Blue Floppy Hat",
- "headArmoireBlueFloppyHatNotes": "Many spells have been sewn into this simple hat, giving it a brilliant blue color. Increases Constitution, Intelligence, and Perception by <%= attrs %> each. Enchanted Armoire: Independent Item.",
- "headArmoireShepherdHeaddressText": "Shepherd Headdress",
- "headArmoireShepherdHeaddressNotes": "Sometimes the gryphons that you herd like to chew on this headdress, but it makes you seem more intelligent nonetheless. Increases Intelligence by <%= int %>. Enchanted Armoire: Shepherd Set (Item 3 of 3).",
+ "headArmoireYellowHairbowNotes": "この美しい黄色のリボンをつければ敏感に、強く、そして賢くなることが出来ます!知覚、力、知力がそれぞれ<%= attrs %>上昇。魔法の戸棚:独立したアイテム。",
+ "headArmoireRedFloppyHatText": "柔らかな赤い帽子",
+ "headArmoireRedFloppyHatNotes": "このシンプルな帽子にはたくさんの呪文が縫い込まれており、それがこの晴れやかな赤をもたらしているのです。体力、知力、知覚がそれぞれ<%= attrs %>上がる。魔法の戸棚:独立したアイテム。",
+ "headArmoirePlagueDoctorHatText": "ペスト医師の帽子",
+ "headArmoirePlagueDoctorHatNotes": "怠慢の病と闘った博士が付けていた正真正銘の帽子。力が<%= str %>、知力が<%= int %>、体力が<%= con %>上昇する魔法の戸棚:ペスト医師セット(3アイテム中の1つ目)",
+ "headArmoireBlackCatText": "黒猫の帽子",
+ "headArmoireBlackCatNotes": "この黒い帽子…ごろごろ鳴いてる?あと尻尾振ってる?あと…息してませんかって?その通り。あなたの頭の上で寝ているのは猫です。知力と体質が<%= attrs %>上がる。魔法の戸棚:個別のアイテム。",
+ "headArmoireOrangeCatText": "橙色の猫の帽子",
+ "headArmoireOrangeCatNotes": "このオレンジ色の帽子…ごろごろ鳴いてる?あと尻尾振ってる?あと…息してませんかって?その通り。あなたの頭の上で寝ているのは猫です。知力と体質が<%= attrs %>上がる。魔法の戸棚:個別のアイテム。",
+ "headArmoireBlueFloppyHatText": "柔らかな青い帽子",
+ "headArmoireBlueFloppyHatNotes": "このシンプルな帽子にはたくさんの呪文が縫い込まれており、それがこの素晴らしい青をもたらしているのです。知力と知覚が<%= attrs %>上がる。魔法の戸棚:個別のアイテム。",
+ "headArmoireShepherdHeaddressText": "羊飼いのヘッドドレス",
+ "headArmoireShepherdHeaddressNotes": "もしかしたらあなたの飼いグリフォンはこの頭飾りを好んでかみかみするかもしれませんが、それにも関わらずあなたは賢く見えるでしょう。知力が<%= int %>上がる。魔法の戸棚:羊飼いセット(3アイテム中の3つめ)",
"headArmoireCrystalCrescentHatText": "Crystal Crescent Hat",
"headArmoireCrystalCrescentHatNotes": "The design on this hat waxes and wanes with the phases of the moon. Increases Intelligence and Perception by <%= attrs %> each. Enchanted Armoire: Crystal Crescent Set (Item 1 of 3).",
"headArmoireDragonTamerHelmText": "Dragon Tamer Helm",
@@ -613,6 +625,8 @@
"headArmoireMinerHelmetNotes": "Protect your head from falling tasks! Increases Intelligence by <%= int %>. Enchanted Armoire: Miner Set (Item 1 of 3).",
"headArmoireBasicArcherCapText": "Basic Archer Cap",
"headArmoireBasicArcherCapNotes": "No archer would be complete without a jaunty cap! Increases Perception by <%= per %>. Enchanted Armoire: Basic Archer Set (Item 3 of 3).",
+ "headArmoireGraduateCapText": "Graduate Cap",
+ "headArmoireGraduateCapNotes": "Congratulations! Your deep thoughts have earned you this thinking cap. Increases Intelligence by <%= int %>. Enchanted Armoire: Graduate Set (Item 3 of 3).",
"offhand": "利き手でないほうの手のアイテム",
"shieldBase0Text": "利き手でないほうの手の装備はありません",
"shieldBase0Notes": "盾、またはセカンドウェポンがありません。",
@@ -673,37 +687,37 @@
"shieldSpecialWinter2015HealerText": "やわらげる盾",
"shieldSpecialWinter2015HealerNotes": "この盾は凍るような風をそらせる。体質が<%= con %>上がる。2014年-2015年冬季限定版装備。",
"shieldSpecialSpring2015RogueText": "鳴き声の爆発",
- "shieldSpecialSpring2015RogueNotes": "その音に騙されないでください。これらの爆発物は強い効き目がある。力が<%= str %>上がる。2015年春季限定版装備。",
+ "shieldSpecialSpring2015RogueNotes": "Don't let the sound fool you - these explosives pack a punch. Increases Strength by <%= str %>. Limited Edition 2015 Spring Gear.",
"shieldSpecialSpring2015WarriorText": "ディッシュディスカス",
"shieldSpecialSpring2015WarriorNotes": "これを敵に投げつけろ... さもなければ持っておけ。なぜなら夕食時にはおいしい食べ物でいっぱいになっているでしょうから。体質が<%= con %>上がる。2015年春季限定版装備。",
"shieldSpecialSpring2015HealerText": "柄模様まくら",
"shieldSpecialSpring2015HealerNotes": "この柔らかい枕で頭を休めることができる。もしくはあなたの恐ろしい爪でこの枕と闘うこともできる。ガオー!体質が<%= con %>上がる。2015年春季限定版装備。",
"shieldSpecialSummer2015RogueText": "ファイヤーリング・コーラル",
- "shieldSpecialSummer2015RogueNotes": "このファイヤーコーラルの親戚は毒液を水中に突き抜く能力を持つ。力を<%= str %>点上げる。2015年夏季限定装備。",
+ "shieldSpecialSummer2015RogueNotes": "この種類の火傷サンゴは、毒を水中に発射する能力を持つ。力を<%= str %>点上げる。2015年夏季限定装備。",
"shieldSpecialSummer2015WarriorText": "マンボウの盾",
"shieldSpecialSummer2015WarriorNotes": "ディラトリーの職人に深海の金属で制作されたこの盾は砂と海のように輝く。体質を<%= str %>点上げる。2015年夏季限定装備。",
"shieldSpecialSummer2015HealerText": "硬派な盾",
- "shieldSpecialSummer2015HealerNotes": "この盾を使って船底ネズミをぶつけてやれ。体質を<%= con %>点で上げる。2015年夏限定装備。",
+ "shieldSpecialSummer2015HealerNotes": "この盾を使って船底ネズミをぶったたけ。体質を<%= con %>上げる。2015年夏限定装備。",
"shieldSpecialFall2015RogueText": "Bat-tle Ax",
"shieldSpecialFall2015RogueNotes": "Fearsome To-Dos cower before the flapping of this ax. Increases Strength by <%= str %>. Limited Edition 2015 Autumn Gear.",
"shieldSpecialFall2015WarriorText": "Birdseed Bag",
"shieldSpecialFall2015WarriorNotes": "It's true that you're supposed to be SCARING the crows, but there's nothing wrong with making friends! Increases Constitution by <%= con %>. Limited Edition 2015 Autumn Gear.",
- "shieldSpecialFall2015HealerText": "Stirring Stick",
+ "shieldSpecialFall2015HealerText": "かき混ぜ棒",
"shieldSpecialFall2015HealerNotes": "This stick can stir anything without melting, dissolving, or bursting into flame! It can also be used to fiercely poke enemy tasks. Increases Constitution by <%= con %>. Limited Edition 2015 Autumn Gear.",
"shieldSpecialWinter2016RogueText": "ココアのマグカップ",
- "shieldSpecialWinter2016RogueNotes": "Warming drink, or boiling projectile? You decide... Increases Strength by <%= str %>. Limited Edition 2015-2016 Winter Gear.",
- "shieldSpecialWinter2016WarriorText": "橇の盾",
- "shieldSpecialWinter2016WarriorNotes": "Use this sled to block attacks, or ride it triumphantly into battle! Increases Constitution by <%= con %>. Limited Edition 2015-2016 Winter Gear.",
- "shieldSpecialWinter2016HealerText": "Pixie Present",
- "shieldSpecialWinter2016HealerNotes": "Open it open it open it open it open it open it!!!!!!!!! Increases Constitution by <%= con %>. Limited Edition 2015-2016 Winter Gear.",
- "shieldSpecialSpring2016RogueText": "Fire Bolas",
- "shieldSpecialSpring2016RogueNotes": "You've mastered the ball, the club, and the knife. Now you advance to juggling fire! Awoo! Increases Strength <%= str %>. Limited Edition 2016 Spring Gear.",
- "shieldSpecialSpring2016WarriorText": "チェーズの輪",
- "shieldSpecialSpring2016WarriorNotes": "You braved fiendish traps to procure this defense-boosting food. Increases Constitution by <%= con %>. Limited Edition 2016 Spring Gear.",
- "shieldSpecialSpring2016HealerText": "Floral Buckler",
- "shieldSpecialSpring2016HealerNotes": "The April Fool claims this little shield will block Shiny Seeds. Don't believe him. Increases Constitution by <%= con %>. Limited Edition 2016 Spring Gear.",
+ "shieldSpecialWinter2016RogueNotes": "あったかい飲み物か、熱々の投擲武器か?それはあなたが決めること。力が<%= str %>上昇する。2015-2016年限定装備。",
+ "shieldSpecialWinter2016WarriorText": "ソリの盾",
+ "shieldSpecialWinter2016WarriorNotes": "このソリは攻撃を防ぐこともできるし、意気揚々と戦場を滑りまわることも出来ます!体力が<%= con %>上がる。2015-2016年冬限定装備。",
+ "shieldSpecialWinter2016HealerText": "妖精の贈り物",
+ "shieldSpecialWinter2016HealerNotes": "開けて開けて開けて開けてさあ開けて!!!!体質が<%= con %>上がる。2015-2016年冬限定装備。",
+ "shieldSpecialSpring2016RogueText": "火のついたボーラ",
+ "shieldSpecialSpring2016RogueNotes": "あなたはボールとこん棒とナイフをマスターしました。つづいて炎のジャグリングに進みます! フォーッ! 力が <%= str %> 上がります。2016年春の限定装備。",
+ "shieldSpecialSpring2016WarriorText": "チーズの輪",
+ "shieldSpecialSpring2016WarriorNotes": "この防御力アップ食で、凶悪なわなを見通し、立ちむかいます。性格が <%= con %> 上がります。2016年春の限定装備。",
+ "shieldSpecialSpring2016HealerText": "花のバックラー",
+ "shieldSpecialSpring2016HealerNotes": "この小さな盾は、キラキラした種を防いでしまうので、エイプリル・フールは文句をいっています。彼を信じてはいけません。性格が <%= con %> 上がります。2016年春の限定装備。",
"shieldMystery201601Text": "Resolution Slayer",
- "shieldMystery201601Notes": "This blade can be used to parry away all distractions. Confers no benefit. January 2016 Subscriber Item.",
+ "shieldMystery201601Notes": "この剣はすべての破壊を退けてくれるでしょう。効果なし。2016年購読者アイテム。",
"shieldMystery301405Text": "クロックシールド",
"shieldMystery301405Notes": "このそびえ立つクロックシールドとともに時はあなたの側にある!効果なし。3015年購読者アイテム。",
"shieldArmoireGladiatorShieldText": "剣闘士の盾",
@@ -716,6 +730,8 @@
"shieldArmoireDragonTamerShieldNotes": "Distract enemies with this dragon-shaped shield. Increases Perception by <%= per %>. Enchanted Armoire: Dragon Tamer Set (Item 2 of 3).",
"shieldArmoireMysticLampText": "Mystic Lamp",
"shieldArmoireMysticLampNotes": "Light the darkest caves with this mystic lamp! Increases Perception by <%= per %>. Enchanted Armoire: Independent Item.",
+ "shieldArmoireFloralBouquetText": "Bouquet o' Flowers",
+ "shieldArmoireFloralBouquetNotes": "Not much help in battle, but aren't they beautiful? Increases Constitution by <%= con %>. Enchanted Armoire: Independent Item.",
"back": "背中アクセサリー",
"backBase0Text": "バックアクセサリーがありません",
"backBase0Notes": "バックアクセサリーがありません。",
@@ -729,10 +745,10 @@
"backMystery201504Notes": "ブンブンブン!タスクからタスクへと飛び回ります。効果なし。2015年4月購読者アイテム。",
"backMystery201507Text": "素晴らしいサーフボード",
"backMystery201507Notes": "Surf off the Diligent Docks and ride the waves in Inkomplete Bay! Confers no benefit. July 2015 Subscriber Item.",
- "backMystery201510Text": "Goblin Tail",
+ "backMystery201510Text": "ゴブリンのしっぽ",
"backMystery201510Notes": "Prehensile and powerful! Confers no benefit. October 2015 Subscriber Item.",
"backMystery201602Text": "Heartbreaker Cape",
- "backMystery201602Notes": "With a swish of your cape, your enemies fall before you! Confers no benefit. February 2016 Subscriber Item.",
+ "backMystery201602Notes": "このケープが一振りされれば、あなたの敵はあなたのもとに下るでしょう!効果なし。2016年2月購読者限定アイテム。",
"backSpecialWonderconRedText": "マイティケープ",
"backSpecialWonderconRedNotes": "強さと美しさにヒュッと音がします。効果なし。コンベンション特別版アイテム。",
"backSpecialWonderconBlackText": "卑劣なケープ",
@@ -755,8 +771,8 @@
"bodySpecialSummer2015WarriorText": "海洋スパイク",
"bodySpecialSummer2015WarriorNotes": "各スパイクから、身につける者を守る海月の毒がぼたぼた垂れている。効果なし。2015年春季限定装備。",
"bodySpecialSummer2015MageText": "黄金バックル",
- "bodySpecialSummer2015MageNotes": "This buckle adds no power at all, but it's shiny. Confers no benefit. Limited Edition 2015 Summer Gear.",
- "bodySpecialSummer2015HealerText": "Sailor's Neckerchief",
+ "bodySpecialSummer2015MageNotes": "別に何の力も持っていないバックルだけれど、キラキラはしてる。効果なし。2015年夏季限定装備。",
+ "bodySpecialSummer2015HealerText": "船乗りのネッカチーフ",
"bodySpecialSummer2015HealerNotes": "Yo ho ho? No, no, no! Confers no benefit. Limited Edition 2015 Summer Gear.",
"headAccessory": "頭部につけるアクセサリー",
"accessories": "装飾品",
@@ -779,16 +795,16 @@
"headAccessorySpecialSpring2015MageNotes": "どこかで魔法使いが秘密を明かすような場合には、これらの耳は鋭敏に聴きとります。効果なし。2015年春季限定装備。",
"headAccessorySpecialSpring2015HealerText": "緑色の猫耳",
"headAccessorySpecialSpring2015HealerNotes": "これら可愛い猫耳は他者をとてもうらやましがらせます。効果なし。2015年春季限定装備。",
- "headAccessorySpecialSpring2016RogueText": "Green Dog Ears",
- "headAccessorySpecialSpring2016RogueNotes": "With these, you can keep track of tricky Mages even if they turn invisible! Confers no benefit. Limited Edition 2016 Spring Gear.",
+ "headAccessorySpecialSpring2016RogueText": "緑色の犬の耳",
+ "headAccessorySpecialSpring2016RogueNotes": "これを使うと、透明になったずるい魔法使いでも追いかけることができます! 効果なし。2016年春の限定装備。",
"headAccessorySpecialSpring2016WarriorText": "赤色のネズミ耳",
- "headAccessorySpecialSpring2016WarriorNotes": "騒がしい戦場を越えてテーマ曲を聞こえるように。効果なし。2016年春季限定版装備。",
+ "headAccessorySpecialSpring2016WarriorNotes": "騒がしい戦場でもあなたのテーマ曲がよく聞こえるように。効果なし。2016年春の限定装備。",
"headAccessorySpecialSpring2016MageText": "黄色の猫耳",
- "headAccessorySpecialSpring2016MageNotes": "These sharp ears can detect the minute hum of ambient Mana, or the muted footfalls of a Rogue. Confers no benefit. Limited Edition 2016 Spring Gear.",
- "headAccessorySpecialSpring2016HealerText": "Purple Bunny Ears",
- "headAccessorySpecialSpring2016HealerNotes": "They stand like flags above the fray, letting others know where to run for help. Confers no benefit. Limited Edition 2016 Spring Gear.",
+ "headAccessorySpecialSpring2016MageNotes": "このとがった耳は、マナが発するわずかな震えや、盗賊のわずかな足音も聞き取ります。効果なし。2016年春の限定装備。",
+ "headAccessorySpecialSpring2016HealerText": "むらさきウサギの耳",
+ "headAccessorySpecialSpring2016HealerNotes": "乱闘の中でも旗のように翻り、ほかの人が助太刀に駆けつけられるようにします。効果なし。2016年春の限定装備。",
"headAccessoryBearEarsText": "くま耳",
- "headAccessoryBearEarsNotes": "These ears make you look like a brave bear! Confers no benefit.",
+ "headAccessoryBearEarsNotes": "この耳はあなたを勇気ある熊さんに見せてくれます!効果なし。",
"headAccessoryCactusEarsText": "サボテン耳",
"headAccessoryCactusEarsNotes": "この耳があなたをとげだらけのサボテンのように見せる!効果なし。",
"headAccessoryFoxEarsText": "狐耳",
@@ -811,8 +827,8 @@
"headAccessoryMystery201409Notes": "この強力な鹿の角は葉っぱと共に色が変わる。効果なし。2014年9月購読者アイテム。",
"headAccessoryMystery201502Text": "思想の羽",
"headAccessoryMystery201502Notes": "あなたの想像を飛び立たせ!効果なし。2015年2月購読者限定アイテム。",
- "headAccessoryMystery201510Text": "Goblin Horns",
- "headAccessoryMystery201510Notes": "These fearsome horns are slightly slimy. Confers no benefit. October 2015 Subscriber Item.",
+ "headAccessoryMystery201510Text": "ゴブリンの角",
+ "headAccessoryMystery201510Notes": "少しぬめった恐ろしげな角。効果なし。2015年10月購読者アイテム。",
"headAccessoryMystery301405Text": "アイウェア",
"headAccessoryMystery301405Notes": "「ゴーグルはかけるものだ」、「かぶりものにしか使えないゴーグルなんてだれも要らないぞ」って何だ!ほら!違うだろう?効果なし。3015年8月購読者限定アイテム。",
"headAccessoryArmoireComicalArrowText": "Comical Arrow",
@@ -820,7 +836,21 @@
"eyewear": "眼鏡",
"eyewearBase0Text": "眼鏡がありません",
"eyewearBase0Notes": "眼鏡がありません。",
- "eyewearSpecialSummerRogueText": "悪党らしい眼帯",
+ "eyewearSpecialBlackTopFrameText": "Black Standard Eyeglasses",
+ "eyewearSpecialBlackTopFrameNotes": "Glasses with a black frame above the lenses. Confers no benefit.",
+ "eyewearSpecialBlueTopFrameText": "Blue Standard Eyeglasses",
+ "eyewearSpecialBlueTopFrameNotes": "Glasses with a blue frame above the lenses. Confers no benefit.",
+ "eyewearSpecialGreenTopFrameText": "Green Standard Eyeglasses",
+ "eyewearSpecialGreenTopFrameNotes": "Glasses with a green frame above the lenses. Confers no benefit.",
+ "eyewearSpecialPinkTopFrameText": "Pink Standard Eyeglasses",
+ "eyewearSpecialPinkTopFrameNotes": "Glasses with a pink frame above the lenses. Confers no benefit.",
+ "eyewearSpecialRedTopFrameText": "Red Standard Eyeglasses",
+ "eyewearSpecialRedTopFrameNotes": "Glasses with a red frame above the lenses. Confers no benefit.",
+ "eyewearSpecialWhiteTopFrameText": "White Standard Eyeglasses",
+ "eyewearSpecialWhiteTopFrameNotes": "Glasses with a white frame above the lenses. Confers no benefit.",
+ "eyewearSpecialYellowTopFrameText": "Yellow Standard Eyeglasses",
+ "eyewearSpecialYellowTopFrameNotes": "Glasses with a yellow frame above the lenses. Confers no benefit.",
+ "eyewearSpecialSummerRogueText": "ならず者の眼帯",
"eyewearSpecialSummerRogueNotes": "ならず者でなくてもこのオシャレが伝わるだろう!効果なし。2014年夏季限定装備。",
"eyewearSpecialSummerWarriorText": "威勢的な眼帯",
"eyewearSpecialSummerWarriorNotes": "ならず者でなくてもこのオシャレが伝わるだろう!効果なし。2014年夏季限定装備。",
@@ -838,6 +868,6 @@
"eyewearMystery301404Notes": "片眼鏡を別として、ゴーグルよりファンシーなアイウェアはない。効果なし。3015年7月購読者限定アイテム。",
"eyewearMystery301405Text": "片眼鏡",
"eyewearMystery301405Notes": "ゴーグルを別として、片眼鏡よりファンシーなアイウェアはない。効果なし。3015年7月購読者限定アイテム。",
- "eyewearArmoirePlagueDoctorMaskText": "Plague Doctor Mask",
- "eyewearArmoirePlagueDoctorMaskNotes": "An authentic mask worn by the doctors who battle the Plague of Procrastination. Confers no benefit. Enchanted Armoire: Plague Doctor Set (Item 2 of 3)."
+ "eyewearArmoirePlagueDoctorMaskText": "ペスト医師のマスク",
+ "eyewearArmoirePlagueDoctorMaskNotes": "怠惰の病と闘った博士が付けていた正真正銘のマスク。効果なし。魔法の戸棚:ペスト医師セット(3アイテム中の2つ目)"
}
\ No newline at end of file
diff --git a/common/locales/ja/generic.json b/common/locales/ja/generic.json
index c56c16dc67..37f5368c46 100644
--- a/common/locales/ja/generic.json
+++ b/common/locales/ja/generic.json
@@ -18,7 +18,7 @@
"titleDrops": "市場",
"titleQuests": "クエスト",
"titlePets": "ペット",
- "titleMounts": "乗用獣",
+ "titleMounts": "騎獣",
"titleEquipment": "装備",
"titleTimeTravelers": "タイムトラベラー",
"titleSeasonalShop": "期間限定ショップ",
@@ -36,8 +36,8 @@
"emojiExample": ":smile:",
"markdownLinkEx": "[Habitica最高!](https://habitica.com)",
"markdownImageEx": "",
- "unorderedListHTML": "+ アイテム① + アイテム② + アイテム③",
- "unorderedListMarkdown": "+ アイテム①\n+ アイテム②\n+ アイテム③",
+ "unorderedListHTML": "+ 1 つ目のアイテム + 2 つ目のアイテム + 3 つ目のアイテム",
+ "unorderedListMarkdown": "+ 1 つ目のアイテム\n+ 2 つ目のアイテム\n+ 3 つ目のアイテム",
"code": "`コード`",
"achievements": "実績",
"modalAchievement": "実績を解除!",
@@ -93,7 +93,7 @@
"costumeContestText": "Habitween (Habitica ハロウィン)の衣装コンテストに出場しました。一部のエントリーは、Habitica ブログでご覧いただけます!",
"costumeContestTextPlural": "第 <%= number %> 回Habitween (Habitica ハロウィン)の衣装コンテストに出場しました。一部のエントリーは、Habitica ブログでご覧いただけます!",
"memberSince": "メンバー登録日",
- "lastLoggedIn": "最近ログイン",
+ "lastLoggedIn": "- 最後のログイン",
"notPorted": "この機能はオリジナルサイトからまだ移植されていません",
"buyThis": "<%= price %> ジェムのこの<%= text %>を、手持ちの<%= gems %> ジェムから買いますか?",
"noReachServer": "現在、サーバーに到達できません。後ほどもう一度お試しください。",
@@ -112,33 +112,33 @@
"audioTheme_off": "オフ",
"audioTheme_danielTheBard": "吟遊詩人ダニエル",
"audioTheme_wattsTheme": "Wattのテーマ",
- "audioTheme_gokulTheme": "Gokulテーマ",
+ "audioTheme_gokulTheme": "Gokul のテーマ",
"audioTheme_luneFoxTheme": "LuneFoxのテーマ",
- "askQuestion": "質問をする",
+ "askQuestion": "質問する",
"reportBug": "バグを報告する",
"HabiticaWiki": "Habitica Wiki",
"HabiticaWikiFrontPage": "http://habitica.wikia.com/wiki/Habitica_Wiki",
- "contributeToHRPG": "Habiticaに貢献してください",
- "overview": "新規ユーザーのための概要",
- "January": "1月",
- "February": "2月",
- "March": "3月",
- "April": "4月",
- "May": "5月",
- "June": "6月",
- "July": "7月",
- "August": "8月",
- "September": "9月",
- "October": "10月",
- "November": "11月",
- "December": "12月",
+ "contributeToHRPG": "Habiticaに貢献する",
+ "overview": "新ユーザーのためのツアー",
+ "January": "1月",
+ "February": "2月",
+ "March": "3月",
+ "April": "4月",
+ "May": "5月",
+ "June": "6月",
+ "July": "7月",
+ "August": "8月",
+ "September": "9月",
+ "October": "10月",
+ "November": "11月",
+ "December": "12月",
"dateFormat": "日付の形式",
"achievementStressbeast": "Stoïkalmの救世主",
"achievementStressbeastText": "2014年冬のワンダーランドイベントで「忌まわしきストレスのけだもの」の打倒に協力しました!",
"achievementBurnout": "繁栄の地の救世主",
"achievementBurnoutText": "2015年秋の収穫祭イベントで「燃えつきと消耗の悪霊」の打倒に協力しました!",
- "achievementBewilder": "Savior of Mistiflying",
- "achievementBewilderText": "Helped defeat the Be-Wilder during the 2016 Spring Fling Event!",
+ "achievementBewilder": "霧がかりの救世主",
+ "achievementBewilderText": "2016年春の元気なダンス イベントで、「まごつかせのビ・ワイルダー」の打倒に協力しました!",
"checkOutProgress": "Habatica での進行状況を見てください!",
"cardReceived": "カードが届きました!",
"cardReceivedFrom": "<%= userName %> からの <%= cardType %>",
@@ -174,7 +174,7 @@
"dontStop": "つづけよう!",
"levelUpShare": "実生活での習慣が向上したので、Habitica でレベルアップしました!",
"questUnlockShare": "Habitica で新しいクエストをアンロックしたよ!",
- "hatchPetShare": "実生活でタスクを達成して、たまごから新しいペットがかえったよ!",
+ "hatchPetShare": "実生活でタスクを達成して、たまごから新しいペットが生まれました!",
"raisePetShare": "実生活でタスクを達成して、ペットが乗れるほど大きくなったよ!",
"wonChallengeShare": "Habitica のチャレンジに成功したよ!",
"achievementShare": "Habitica で新しい実績を解除したよ!",
diff --git a/common/locales/ja/groups.json b/common/locales/ja/groups.json
index ec6f4709d5..c902704316 100644
--- a/common/locales/ja/groups.json
+++ b/common/locales/ja/groups.json
@@ -4,12 +4,12 @@
"innCheckIn": "宿屋に泊まる",
"innText": "あなたは宿屋に泊まっています!チェックインしている間、一日の終わりに日課が未実施でもダメージを受けません、ですが日課は毎日リフレッシュされます。注意: もしあなたがボスクエストに参加しているなら、あなたのパーティの仲間が日課をし損ねたとき、その仲間も宿屋に泊まっていない限り、あなたはダメージを受けます!また、あなたのボスへのダメージ(または収集したアイテム)は宿屋をチェックアウトするまで適用されません。",
"innTextBroken": "あなたは宿屋で休んでいます! 宿屋にいる間は日課をやらずに日があらたまっても、ダメージを受けることはありませんが、日課は毎日更新されるでしょう。しかしボス クエストに参加している間は、パーティーの仲間のだれかが日課をサボった分のボスからのダメージは、宿屋にいても受けてしまいます...もし、そのパーティーの仲間も宿屋にいるなら話は別ですが。また、あなたが日課をやらなかった分のダメージ(もしくは集めたアイテム)は、宿屋をチェックアウトするまで無効です。",
- "lfgPosts": "グループポスト(メンバー募集)を探してください",
+ "lfgPosts": "グループ(パーティーメンバー募集)の投稿を探す",
"tutorial": "チュートリアル",
"glossary": "小辞典",
"wiki": "ウィキ",
"reportAP": "問題を報告する",
- "requestAF": "仕様をお願いする",
+ "requestAF": "機能を要望する",
"community": "コミュニティフォーラム",
"dataTool": "データ表示ツール",
"resources": "リソース",
@@ -92,6 +92,7 @@
"send": "送信する",
"messageSentAlert": "メッセージを送信しました",
"pmHeading": "<%= name %> 宛プライベートメッセージ",
+ "pmsMarkedRead": "Your private messages have been marked as read",
"clearAll": "すべてのメッセージを削除する",
"confirmDeleteAllMessages": "受信トレイの全てのメッセージを削除してよろしいですか? 既に送ったメッセージはまだ他のユーザに見られる状態です。",
"optOutPopover": "プライベートメッセージあまり好きじゃない?参加しない場合はクリックしてください。",
@@ -99,6 +100,15 @@
"unblock": "ブロックを解除する",
"pm-reply": "返信する",
"inbox": "受信トレイ",
+ "messageRequired": "A message is required.",
+ "toUserIDRequired": "A User ID is required",
+ "gemAmountRequired": "A number of gems is required",
+ "notAuthorizedToSendMessageToThisUser": "Can't send message to this user.",
+ "privateMessageGiftIntro": "Hello <%= receiverName %>, <%= senderName %> has sent you",
+ "privateMessageGiftGemsMessage": "<%= gemAmount %> gems!",
+ "privateMessageGiftSubscriptionMessage": "<%= numberOfMonths %> months of subscription!",
+ "cannotSendGemsToYourself": "Cannot send gems to yourself. Try a subscription instead.",
+ "badAmountOfGemsToSend": "Amount must be within 1 and your current number of gems.",
"abuseFlag": "コミュニティガイドライン違反を報告する",
"abuseFlagModalHeading": "<%= name %> を違反で報告しますか?",
"abuseFlagModalBody": "本当にこの投稿について報告しますか? 投稿の報告は、それが<%= firstLinkStart %>コミュニティー ガイドライン<%= linkEnd %>および<%= secondLinkStart %>利用規約<%= linkEnd %>のいずれかまたは両方に違反している場合に限定すべきです。不適切に報告することは、コミュニティ ー ガイドライン上の違反行為であり、あなたが違反者とみなされます。投稿を報告すべき適切な理由は以下の通りであり、またこれらに限定するものではありません :
ののしりや悪口、宗教的な冒涜
偏見、中傷
成人向きの話題
暴力。冗談としてであっても
スパム、無意味なメッセージ
",
@@ -151,5 +161,29 @@
"partyUpName": "パーティー立ち上げ",
"partyOnName": "パーティー参加",
"partyUpAchievement": "別の人のパーティーに参加しました! 楽しんで、モンスターと戦ってお互いを助け合いましょう。",
- "partyOnAchievement": "4人以上のパーティーに参加しました! 責任感が強くなったことを楽しんで、自分自身の敵と戦うために友達と協力しましょう!"
+ "partyOnAchievement": "4人以上のパーティーに参加しました! 責任感が強くなったことを楽しんで、自分自身の敵と戦うために友達と協力しましょう!",
+ "groupIdRequired": "\"groupId\" must be a valid UUID",
+ "groupNotFound": "Group not found.",
+ "groupTypesRequired": "You must supply a valid \"type\" query string.",
+ "questLeaderCannotLeaveGroup": "You cannot leave your party when you have started a quest. Abort the quest first.",
+ "cannotLeaveWhileActiveQuest": "You cannot leave party during an active quest. Please leave the quest first.",
+ "onlyLeaderCanRemoveMember": "Only group leader can remove a member!",
+ "memberCannotRemoveYourself": "You cannot remove yourself!",
+ "groupMemberNotFound": "User not found among group's members",
+ "mustBeGroupMember": "Must be member of the group.",
+ "keepOrRemoveAll": "req.query.keep must be either \"keep-all\" or \"remove-all\"",
+ "keepOrRemove": "req.query.keep must be either \"keep\" or \"remove\"",
+ "canOnlyInviteEmailUuid": "Can only invite using uuids or emails.",
+ "inviteMissingEmail": "Missing email address in invite.",
+ "partyMustbePrivate": "Parties must be private",
+ "userAlreadyInGroup": "User already in that group.",
+ "userAlreadyInvitedToGroup": "User already invited to that group.",
+ "userAlreadyPendingInvitation": "User already pending invitation.",
+ "userAlreadyInAParty": "User already in a party.",
+ "userWithIDNotFound": "User with id \"<%= userId %>\" not found.",
+ "userHasNoLocalRegistration": "User does not have a local registration (username, email, password).",
+ "uuidsMustBeAnArray": "User ID invites must be an array.",
+ "emailsMustBeAnArray": "Email address invites must be an array.",
+ "canOnlyInviteMaxInvites": "You can only invite \"<%= maxInvites %>\" at a time",
+ "onlyCreatorOrAdminCanDeleteChat": "Not authorized to delete this message!"
}
\ No newline at end of file
diff --git a/common/locales/ja/limited.json b/common/locales/ja/limited.json
index 2f4ea7122d..982229d95f 100644
--- a/common/locales/ja/limited.json
+++ b/common/locales/ja/limited.json
@@ -5,7 +5,7 @@
"annoyingFriends": "困った友達",
"annoyingFriendsText": "パーティーの仲間から <%= snowballs %> 回、雪玉を投げられました。",
"alarmingFriends": "要注意の友達",
- "alarmingFriendsText": "パーティーの仲間から <%= spookDust %> 回、オバケにされました。",
+ "alarmingFriendsText": "Got spooked <%= spookySparkles %> times by party members.",
"agriculturalFriends": "ぶざまな友達",
"agriculturalFriendsText": "パーティーの仲間から <%= seeds %> 回、花にされました。",
"aquaticFriends": "水の中の仲間たち",
@@ -28,7 +28,7 @@
"seasonalShopClosedTitle": "<%= linkStart %>レズリー<%= linkEnd %>",
"seasonalShopTitle": "<%= linkStart %>期間限定の魔女<%= linkEnd %>",
"seasonalShopClosedText": "期間限定マーケットは、現在閉店しています!! いまどこに期間限定の魔女がいるのかはわかりませんが、きっと、次の大祭の間には、戻ってくるでしょう!",
- "seasonalShopText": "季節限定ショップへようこそ!! ただいま春の季節限定版商品をとりそろえております。ここにあるものはすべて年に 1 度のスプリング・フリング イベント期間中にお買い上げいただけますが、4月30日までしかオープンしていませんのでこの期間を逃しますと、また 1 年お待ちいただくことになりますよ!",
+ "seasonalShopText": "季節限定ショップへようこそ!! ただいま春の季節限定版商品をとりそろえております。ここにあるものはすべて年に 1 度の春の元気なダンス イベント期間中にお買い上げいただけますが、4月30日までしかオープンしていませんのでこの期間を逃しますと、また 1 年お待ちいただくことになりますよ!",
"seasonalShopSummerText": "季節限定ショップへようこそ! 夏の 季節限定版グッズを揃えています。ここにあるものはすべて毎年サマースプラッシュイベント期間中に買う事ができますが、7月31日までしかオープンしていません。ですので今買い備えないと、買うのにまた一年またなければなりませんよ!",
"seasonalShopFallText": "いらっしゃいませ、ようこそ期間限定ショップへ!! ただいま秋の期間限定版グッズをとりそろえております。こちらはいずれも、毎年の秋まつりイベント期間のみの商品で、当店は10月31日までの限定オープンです。今すぐお買い上げいただかないと、来年までお待ちいただくことになりますよ!",
"seasonalShopWinterText": "いらっしゃいませ、期間限定ショップへようこそ!! ただいま冬の期間限定版グッズをとりそろえております。こちらはいずれも、毎年の冬のワンダーランド イベント期間のみの商品で、当店は1月31日までの限定オープンです。今すぐお買い上げいただかないと、来年までお待ちいただくことになりますよ!",
@@ -72,5 +72,6 @@
"comfortingKittySet": "くつろいだネコ (治療師)",
"sneakySqueakerSet": "こそこそしたピヨピヨ (盗賊)",
"fallEventAvailability": "10月31日まで有効",
- "winterEventAvailability": "12月31日まで有効"
+ "winterEventAvailability": "12月31日まで有効",
+ "springEventAvailability": "5月31日まで有効"
}
\ No newline at end of file
diff --git a/common/locales/ja/maintenance.json b/common/locales/ja/maintenance.json
new file mode 100644
index 0000000000..89354f6b66
--- /dev/null
+++ b/common/locales/ja/maintenance.json
@@ -0,0 +1,34 @@
+{
+ "habiticaBackSoon": "心配しないでください、ハビティカもすぐに換えてします!",
+ "importantMaintenance": "We are doing important maintenance that we estimate will last until <%= localDate %> in your timezone.",
+ "maintenance": "メンテナンス",
+ "maintenanceMoreInfo": "Want more information about the maintenance? <%= linkStart %>Check out our info page<%= linkEnd %>.",
+ "noDamageKeepStreaks": "You will NOT take damage or lose streaks!",
+ "thanksForPatience": "御辛抱いただいてありがとうございました。",
+ "twitterMaintenanceUpdates": "For the most recent updates, watch our Twitter, where we will be posting status information.",
+ "veteranPetAward": "At the end, you will receive a Veteran pet!",
+
+ "maintenanceInfoTitle": "Information about Upcoming Maintenance to Habitica",
+ "maintenanceInfoWhat": "What is happening?",
+ "maintenanceInfoWhatText": "On May 21, Habitica will be down for maintenance for most of the day. You will not take any damage or have your account harmed during that weekend, even if you can’t log in to check off your Dailies in time! We will be working very hard to make the downtime as short as possible, and will be posting regular updates on our Twitter account. At the end of the downtime, to thank everyone for their patience, you will all receive a rare pet!",
+ "maintenanceInfoWhy": "Why is this happening?",
+ "maintenanceInfoWhyText": "For the past several months, we have been thoroughly revamping Habitica behind-the-scenes. Specifically, we have rewritten the API. While it may not look much different on the surface, it’s a whole new world underneath. This will allow us WAY more flexibility when we want to build features in the future, and lead to improved performance!",
+ "maintenanceInfoTechDetails": "Want more details on the technical side of the process? Visit The Forge, our dev blog.",
+ "maintenanceInfoMore": "More Information",
+ "maintenanceInfoAccountChanges": "What changes will I see to my account after the rewrite is complete?",
+ "maintenanceInfoAccountChangesText": "At first, there won’t be any notable changes aside from performance improvements for features such as Challenges. If you notice any changes that shouldn’t be there, email us at admin@habitica.com and we will investigate them for you!",
+ "maintenanceInfoAddFeatures": "What kind of features will this allow Habitica to add?",
+ "maintenanceInfoAddFeaturesText": "Completing this rewrite will allow us to start building out improved chat and Guilds, plans for organizations and families, and additional productivity features like Monthlies and the ability to record yesterday’s activity! Those are all involved features on their own, so it will take time to build them, but until we were finished with this rewrite, there was no way we could start them.",
+ "maintenanceInfoHowLong": "How long will the maintenance take?",
+ "maintenanceInfoHowLongText": "We have to migrate tasks and data for all 1.3 million Habitica users -- not an easy task! We anticipate that it will take place between approximately 1pm Pacific Time (8pm UTC) and 10pm Pacific Time (5am UTC). Rest assured that we’re doing everything we can to make it go as quickly as possible! You can follow updates on our Twitter.",
+ "maintenanceInfoStatsAffected": "How will my Dailies, Streaks, Buffs, and Quests be affected?",
+ "maintenanceInfoStatsAffectedText1": "You will NOT take any damage or lose any streaks that weekend, but otherwise, your day will reset normally! Dailies that you checked will become unchecked, buffs will reset, etc. If you are in a Collection Quest, you will still find items. If you are in a Boss Battle, you will still deal damage to the Boss, but the Boss will not deal damage to you. (Even monsters need a break!)",
+ "maintenanceInfoStatsAffectedText2": "After a lot of thought, our team concluded that this was the most fair way to handle the fact that many users will not be able to check off their Dailies normally during the maintenance. We’re sorry for any inconvenience this causes!",
+ "maintenanceInfoSeeTasks": "What if I need to see my task list?",
+ "maintenanceInfoSeeTasksText": "If you know that you will need to see your task list on Saturday to remind yourself what you have to do, we recommend that before the maintenance begins, you take a screenshot of your tasks so that you can use it as a reference.",
+ "maintenanceInfoRarePet": "What kind of rare pet will I receive?",
+ "maintenanceInfoRarePetText": "To thank you for your patience during the downtime, everyone will get a rare Veteran Pet. If you’ve never received a Veteran Pet before, you will receive a Veteran Wolf. If you already have a Veteran Wolf, you will receive a Veteran Tiger. And if you already have a Veteran Wolf and a Veteran Tiger, you will receive a never-before-seen Veteran pet! After the migration is completed, it may take several hours for your pet to show up, but never fear, everyone will get one.",
+ "maintenanceInfoWho": "Who worked on this massive project?",
+ "maintenanceInfoWhoText": "We’re glad you asked! It was spearheaded by our amazing contributor paglias, with lots of help from Blade, TheHollidayInn, SabreCat, Victor Pudeyev, TheUnknown, and Alys.",
+ "maintenanceInfoTesting": "The new version was also tirelessly tested by a bunch of our amazing open-source volunteers. Thank you -- we couldn't have done this without you."
+}
diff --git a/common/locales/ja/npc.json b/common/locales/ja/npc.json
index be2c005171..bd562afe1d 100644
--- a/common/locales/ja/npc.json
+++ b/common/locales/ja/npc.json
@@ -2,8 +2,8 @@
"npc": "NPC",
"npcText": "Kickstarter プロジェクトへ最高レベルで資金提供!",
"mattBoch": "マット・バック",
- "mattShall": "<%= name %>、馬をお連れしましょうか? ペットに十分なエサを与えると、乗用獣となり、ここに現れます。乗用獣をクリックして、くらをおきましょう。",
- "mattBochText1": "動物小屋にようこそ! 私の名前はマット、猛獣使いだ。レベル3 から、「たまご」と「たまごがえしの薬」を使って、たまごからペットをかえすことができる。市場でペットをかえすと、ここに表示されるぞ! ペットの画像をクリックしてアバターに追加しよう。レベル 3 以降に見つかるえさをペットにやると、ペットはしっかりした乗用獣へと育っていくんだ。",
+ "mattShall": "<%= name %>、馬をお連れしましょうか? ペットに十分なエサを与えると、騎獣となり、ここに現れます。騎獣をクリックして、くらをおきましょう。",
+ "mattBochText1": "動物小屋にようこそ! 私の名前はマット、猛獣使いだ。レベル3 から、「たまご」と「たまごがえしの薬」を使って、たまごからペットをかえすことができる。市場でペットをかえすと、ここに表示されるぞ! ペットの画像をクリックしてアバターに追加しよう。レベル 3 以降に見つかるえさをペットにやると、ペットはしっかりした騎獣へと育っていくんだ。",
"daniel": "ダニエル",
"danielText": "キャンプ場へようこそ! しばらくゆっくりして、地元の人と会いましょう。冒険を休まなければいけないのなら ( 長期休暇? 病気? )、宿のご用意をいたします。宿にチェックインしている間は、日課をやらずに1日を終えてもダメージを受けません。もちろん日課をやっても、かまいません。",
"danielText2": "注意 : ボス クエストに参加している間は、パーティーの仲間が日課をサボると、ボスはあなたを攻撃しダメージを受けます! また、あなたがボスに与えるはずのダメージ ( そして集められたはずのアイテム ) は宿をチェックアウトするまで適用されません。",
@@ -21,6 +21,26 @@
"ian": "イアン",
"ianText": "クエスト ショップへようこそ! ここでは友達といっしょにモンスターと戦うクエストの巻物を開くことができます。この美しい品ぞろえを確認して、右側でお買い上げください!",
"ianBrokenText": "クエスト ショップへようこそ...ここでは友達といっしょにモンスターと戦うクエストの巻物を開くができる...クエストの巻物の美しい品ぞろえもよく見て、右側で買ってくれ...",
+ "missingKeyParam": "\"req.params.key\" is required.",
+ "itemNotFound": "Item \"<%= key %>\" not found.",
+ "cannotBuyItem": "You can't buy this item.",
+ "missingTypeKeyEquip": "\"key\" and \"type\" are required parameters.",
+ "missingPetFoodFeed": "\"pet\" and \"food\" are required parameters.",
+ "invalidPetName": "Invalid pet name supplied.",
+ "missingEggHatchingPotionHatch": "\"egg\" and \"hatchingPotion\" are required parameters.",
+ "invalidTypeEquip": "\"type\" must be one of 'equipped', 'pet', 'mount', 'costume'.",
+ "mustPurchaseToSet": "Must purchase <%= val %> to set it on <%= key %>.",
+ "typeRequired": "Type is required",
+ "keyRequired": "Key is required",
+ "notAccteptedType": "Type must be in [eggs, hatchingPotions, food, quests, gear]",
+ "contentKeyNotFound": "Key not found for Content <%= type %>",
+ "plusOneGem": "+1 Gem",
+ "typeNotSellable": "Type is not sellable. Must be one of the following <%= acceptedTypes %>",
+ "userItemsKeyNotFound": "Key not found for user.items <%= type %>",
+ "pathRequired": "Path string is required",
+ "unlocked": "Items have been unlocked",
+ "alreadyUnlocked": "Full set already unlocked.",
+ "alreadyUnlockedPart": "Full set already partially unlocked.",
"USD": "(米ドル)",
"newStuff": "新しいもの",
"cool": "後で教えて",
@@ -61,9 +81,10 @@
"tourChallengesPage": "チャレンジは、ユーザーが作成したテーマをもった一連のタスクです。チャレンジに加わると、あなたにそのタスクが加わります。賞金のジェムをかけて、他のユーザーと競争しましょう!",
"tourMarketPage": "レベル 3 以降、タスクをやりとげると、ときどき「たまご」や「たまごがえしの薬」が落ちています。ここに表示されるのでペットに命をふきこみましょう! 市場でアイテムとして買うこともできます。",
"tourHallPage": "英雄記念館へようこそ。こちらは Habitica をオープンソースの世界で貢献してくださった方々を称える場所です。プログラム、画像、音楽、執筆などでの貢献により、彼らはジェム、特別なアイテム、そして名誉ある肩書きを手にしました。あなたも Habitica に貢献できますよ! ",
- "tourPetsPage": "ここは動物小屋です! レベル 3 になると、「たまご」と「たまごがえしの薬」から、ペットをかえすことができます。ペットをクリックして、アバターに表示しましょう。レベル3以降手に入るえさをペットにやると、力強い乗用獣へと成長します。",
- "tourMountsPage": "ペットに十分なえさを与えると、乗用獣に成長します。乗用獣がここに表示されます。( ペット、乗用獣、えさはレベル 3 以降有効になります。) 乗用獣をクリックして、くらをセットしましょう。",
+ "tourPetsPage": "ここは動物小屋です! レベル 3 になると、「たまご」と「たまごがえしの薬」から、ペットをかえすことができます。ペットをクリックして、アバターに表示しましょう。レベル3以降手に入るえさをペットにやると、力強い騎獣へと成長します。",
+ "tourMountsPage": "ペットに十分なえさを与えると、騎獣に成長します。騎獣がここに表示されます。( ペット、騎獣、えさはレベル 3 以降有効になります。) 騎獣をクリックして、くらをセットしましょう。",
"tourEquipmentPage": "ここはアイテムの保管場所です! 武装はステータスに影響を与えます。ステータスを変えないでアバターに別の装備を表示したい場合は「衣装に使う」をクリックしてください。",
+ "equipmentAlreadyOwned": "You already own that piece of equipment",
"tourOkay": "OK!",
"tourAwesome": "すごい!",
"tourSplendid": "すばらしい!",
@@ -83,7 +104,7 @@
"welcome2notes": "実生活でのタスクのがんばりが、ゲームの進行を左右します!",
"welcome3": "実生活とゲームで前に進みましょう!",
"welcome3notes": "あなたが生活を改善するほどに、あなたのアバターもレベルアップし、ペットやクエスト、装備などの新しい機能がアンロックされていきます。",
- "welcome4": "健康 (体力・HP) を減らす悪い習慣を避けましょう。さもないとあなたのアバターは死んでしまいます!",
+ "welcome4": "アバターの体力を減らす悪い習慣を避けましょう。さもないとあなたのアバターは死んでしまいます!",
"welcome5": "それではアバターをカスタマイズしてタスクを設定しましょう…",
"imReady": "Habitica をはじめる"
}
\ No newline at end of file
diff --git a/common/locales/ja/pets.json b/common/locales/ja/pets.json
index 9ed5468f00..f500d5f049 100644
--- a/common/locales/ja/pets.json
+++ b/common/locales/ja/pets.json
@@ -4,29 +4,30 @@
"magicPets": "魔法の薬のペット",
"rarePets": "レアペット",
"questPets": "クエストペット",
- "mounts": "乗用獣",
- "mountsTamed": "よく慣れた乗用獣",
- "questMounts": "乗用獣のクエスト",
- "magicMounts": "魔法の薬の乗用獣",
- "rareMounts": "めずらしい乗用獣",
- "etherealLion": "エーテルライオン",
- "veteranWolf": "ベテラン狼",
- "veteranTiger": "ベテラントラ",
- "cerberusPup": "ケルベロスの幼体",
+ "mounts": "騎獣",
+ "mountsTamed": "よく慣れた騎獣",
+ "questMounts": "クエスト騎獣",
+ "magicMounts": "魔法の薬の騎獣",
+ "rareMounts": "貴重な騎獣",
+ "etherealLion": "天上のライオン",
+ "veteranWolf": "百戦錬磨のオオカミ",
+ "veteranTiger": "百戦錬磨のトラ",
+ "veteranLion": "Veteran Lion",
+ "cerberusPup": "子ケルベロス",
"hydra": "ヒドラ",
"mantisShrimp": "シャコ",
- "mammoth": "長毛マンモス",
+ "mammoth": "長い毛のマンモス",
"orca": "シャチ",
- "royalPurpleGryphon": "貝紫色のグリフォン",
- "phoenix": "フェニックス",
- "bumblebee": "Bumblebee",
- "rarePetPop1": "Habiticaに貢献することを通してどのようにこの珍しいペットを得ることができるかについて、より多くを学ぶために、金の足をクリックしてください!",
- "rarePetPop2": "このペットを得る方法!",
+ "royalPurpleGryphon": "青紫のグリフォン",
+ "phoenix": "不死鳥",
+ "magicalBee": "不思議なハチ",
+ "rarePetPop1": "金色の足跡をクリックすると、Habitica への貢献で、この貴重なペットを入手する詳しい方法がわかります!",
+ "rarePetPop2": "このペットを手に入れる方法!",
"potion": "<%= potionType %> ポーション",
"egg": "<%= eggType %>の卵",
"eggs": "たまご",
"eggSingular": "たまご",
- "noEggs": "たまごを持っていない。",
+ "noEggs": "たまごを持っていません。",
"hatchingPotions": "たまごがえしの薬",
"magicHatchingPotions": "魔法のたまごがえしの薬",
"hatchingPotion": "たまごがえしの薬",
@@ -43,45 +44,49 @@
"beastMasterName": "獣使い",
"beastMasterText": "は、90種すべてのペットを発見しました(信じられないほどの困難をなしとげました。このユーザーを称えましょう! )",
"beastMasterText2": "また、彼は計<%= count %>回、ペットを逃がしました",
- "mountMasterProgress": "乗用獣使いの進行状況",
- "stableMountMasterProgress": "乗用獣使いの進行状況: <%= number %>頭の乗用獣を飼いならした",
- "mountAchievement": "すべての乗用獣を飼いならしたので、「乗用獣使い」の実績を解除しました!",
- "mountMasterName": "乗用獣使い",
- "mountMasterText": "は、90頭の乗用獣すべてを飼いならした(さらにずっと困難なことをなしとげました。このユーザーを祝福しましょう! )",
- "mountMasterText2": "90匹すべての乗用獣を、計<%= count %> 回逃がしました",
- "beastMountMasterName": "獣使いと乗用獣使い",
+ "mountMasterProgress": "騎獣使いの進行状況",
+ "stableMountMasterProgress": "騎獣獣使いの進行状況 : <%= number %>頭の騎獣を飼いならした",
+ "mountAchievement": "すべての騎獣を飼いならしたので、「騎獣使い」の実績を解除しました!",
+ "mountMasterName": "騎獣使い",
+ "mountMasterText": "は、90頭の騎獣すべてを飼いならした(さらにずっと困難なことをなしとげました。このユーザーを祝福しましょう! )",
+ "mountMasterText2": "90匹すべての騎獣を、計<%= count %> 回逃がしました",
+ "beastMountMasterName": "獣使いと騎獣使い",
"triadBingoName": "ペット完全制覇",
- "triadBingoText": "ペット90種すべてと乗用獣90種すべてを発見し、そしてもう一度90匹すべてのペットを発見しました(どうやってやったの!)",
+ "triadBingoText": "ペット90種すべてと騎獣90種すべてを発見し、そしてもう一度90匹すべてのペットを発見しました(どうやってやったの!)",
"triadBingoText2": "そしていっぱいの動物小屋を計<%= count %>回解放しました。",
- "triadBingoAchievement": "すべてのペットを発見し、すべての乗用獣を飼いならし、さらに再びすべてのペットを発見したので「ペット完全制覇」の実績を手に入れた!",
+ "triadBingoAchievement": "すべてのペットを発見し、すべての騎獣を飼いならし、さらに再びすべてのペットを発見したので「ペット完全制覇」の実績を手に入れた!",
"dropsEnabled": "落とし物が手に入るようになった!",
"itemDrop": "アイテムが落ちました!",
"firstDrop": "落とし物のしくみがアンロックされました! 今後タスクを達成すると、たまごなどのアイテムや、薬、そしてエサの落とし物を発見するちょっとしたチャンスが訪れます。あなたは今<%= eggText %>たまごを見つけました! <%= eggNotes %>",
"useGems": "もしあなたがペットをじっと見守りつづけていて、これ以上ドロップを待てないのであれば、ジェムを使って所持品 > 市場 で買ってください!",
- "hatchAPot": "<%= potion %>の<%= egg %>をかえしますか?",
- "hatchedPet": "<%= potion %>で<%= egg %> をかえした!",
+ "hatchAPot": "<%= potion %><%= egg %>をかえしますか?",
+ "hatchedPet": "<%= potion %><%= egg %> が生まれました!",
"displayNow": "いますぐ表示",
"displayLater": "あとで表示",
- "earnedCompanion": "あなたの生産性のすべてによって、新しい仲間が生まれました。えさをやって成長させましょう!",
+ "petNotOwned": "You do not own this pet.",
+ "earnedCompanion": "あなたの前向きな行動の一つひとつから、新しい仲間が生まれました。えさをやって成長させましょう!",
"feedPet": "<%= name %>に<%= article %><%= text %>をやりますか?",
"useSaddle": "<%= pet %>にくらをおきますか?",
"raisedPet": "<%= pet %>を育てた!",
- "earnedSteed": "タスクを達成したことにより、忠実な乗用獣へと成長した!",
+ "earnedSteed": "多くのタスクを達成して、忠実な騎獣を手に入れた!",
"rideNow": "いますぐ乗る",
"rideLater": "あとで乗る",
- "petName": "<%= potion %> <%= egg %>",
- "mountName": "<%= potion %> <%= mount %>",
- "petKeyName": "犬小屋のカギ",
+ "petName": "<%= potion %><%= egg %>",
+ "mountName": "<%= potion %><%= mount %>",
+ "petKeyName": "動物小屋のカギ",
"petKeyPop": "ペットを自由に歩き回れるようにして、彼らが自分で冒険をはじめられるように解き放とう、そして自分自身、もう一度獣使いとなる興奮を味わおう!",
- "petKeyBegin": "犬小屋のカギ : 経験値 <%= title %> もう一度!",
+ "petKeyBegin": "動物小屋のカギ : <%= title %> をもう一度経験しましょう!",
"petKeyInfo": "ペットを集める興奮を失いましたか? それなら、彼らを解き放ち、落とし物を意味あるものにすることができます!",
- "petKeyInfo2": "犬小屋のカギを使うと、クエスト以外で集められるペットや乗用獣をゼロにリセットします。(クエストだけやめずらしいペットや乗用獣は影響を受けません。)",
- "petKeyInfo3": "動物小屋へのカギは3種類あります: ペットだけを逃がす(4ジェム)か、乗用獣だけを逃がす(4ジェム)、ペットも乗用獣も逃がす(6ジェム)。カギを使う事で獣使いと乗用獣使いの実績を積み重ねることができます。「ペット完全制覇」の実績は、「ペットも乗用獣も逃がす」カギを使って90匹すべてのペットを2回目に集めたときに手にすることができます。あなたがどれだけのペットや乗用獣を集めた飼い主なのかを世界に示しましょう! しかし慎重に選んでください。一度カギを使って動物小屋の扉を開けたら、最初から集めなおす以外に、彼らを再び手に入れることはできないのですから...。",
- "petKeyInfo4": "3つの動物小屋へのカギがあります: ペットだけを逃がす(4ジェム)か、乗用獣だけを逃がす(4ジェム)か、ペットも乗用獣も逃がすか。カギを使う事で「獣使い」と「乗用獣使い」の実績が解除されます。「ペット完全制覇」の実績はあなたが「ペットも乗用獣も逃がす」カギを使って90匹すべてのペットを2回目に集めたときに解除されます。あなたの飼い主としての集めぶりを世界に示しましょう!でも慎重に選んでください。一度カギを使って動物小屋の扉を開けたら、もう一度すべて集め直すしか取り戻す方法はないですから…。",
+ "petKeyInfo2": "動物小屋のカギを使うと、クエスト以外で集められるペットや騎獣をゼロにリセットします。(クエストだけやめずらしいペットや騎獣は影響を受けません。)",
+ "petKeyInfo3": "動物小屋へのカギは3種類あります: ペットだけを逃がす(4ジェム)か、騎獣だけを逃がす(4ジェム)、ペットも騎獣も逃がす(6ジェム)。カギを使う事で獣使いと騎獣使いの実績を積み重ねることができます。「ペット完全制覇」の実績は、「ペットも騎獣も逃がす」カギを使って90匹すべてのペットを2回目に集めたときに手にすることができます。あなたがどれだけのペットや騎獣を集めた飼い主なのかを世界に示しましょう! しかし慎重に選んでください。一度カギを使って動物小屋の扉を開けたら、最初から集めなおす以外に、彼らを再び手に入れることはできないのですから...。",
+ "petKeyInfo4": "3つの動物小屋へのカギがあります: ペットだけを逃がす(4ジェム)か、騎獣だけを逃がす(4ジェム)か、ペットも騎獣も逃がすか。カギを使う事で「獣使い」と「騎獣使い」の実績が解除されます。「ペット完全制覇」の実績はあなたが「ペットも騎獣も逃がす」カギを使って90匹すべてのペットを2回目に集めたときに解除されます。あなたの飼い主としての集めぶりを世界に示しましょう!でも慎重に選んでください。一度カギを使って動物小屋の扉を開けたら、もう一度すべて集め直すしか取り戻す方法はないですから…。",
"petKeyPets": "ペットを逃がす",
- "petKeyMounts": "乗用獣を逃がす",
+ "petKeyMounts": "騎獣を逃がす",
"petKeyBoth": "両方逃がす",
"confirmPetKey": "本当によろしいですか?",
- "petKeyNeverMind": "まだ!",
- "gemsEach": "それぞれのジェム"
+ "petKeyNeverMind": "まだ",
+ "petsReleased": "Pets released.",
+ "mountsAndPetsReleased": "Mounts and pets released",
+ "mountsReleased": "Mounts released",
+ "gemsEach": "ジェム(毎)"
}
\ No newline at end of file
diff --git a/common/locales/ja/quests.json b/common/locales/ja/quests.json
index 7cadc0bbd7..e093db77bb 100644
--- a/common/locales/ja/quests.json
+++ b/common/locales/ja/quests.json
@@ -1,10 +1,10 @@
{
"quests": "クエスト",
"quest": "クエスト",
- "whereAreMyQuests": "クエストのページができました! アイテム -> クエストからご覧ください。",
+ "whereAreMyQuests": "クエストのページができました! 所持品 -> クエストからご覧ください。",
"yourQuests": "あなたのクエスト",
"questsForSale": "クエストの販売",
- "petQuests": "ペットと乗用獣のクエスト",
+ "petQuests": "ペットと騎獣のクエスト",
"unlockableQuests": "アンロックできるクエスト",
"goldQuests": "ゴールドで購入できるクエスト",
"questDetails": "クエストの詳細",
@@ -24,7 +24,7 @@
"accepted": "承認しました",
"rejected": "拒否しました",
"pending": "未決",
- "questStart": "一旦、すべてのメンバーが受け入れまたは拒否のどちらかを選択すると、クエストが開始されます。「受け入れる」をクリックしたものだけがクエストに参加して、ドロップを受け取ることができるようになります。メンバーが長い間保留 (非アクティブ?) している場合は、クエストオーナーは「開始」をクリックして彼ら抜きでクエストを開始することができます。また、クエストオーナーは、「キャンセル」をクリックして、クエストをキャンセルし、クエストスクロールを取り戻すことができます。",
+ "questStart": "すべてのメンバーが承認または拒否のどちらかを選択すると、クエストがはじまります。「承認する」をクリックしたものだけがクエストに参加して、落とし物を受け取れます。メンバーが長い間保留 (アクティブでないユーザー?) している場合は、クエストのオーナーは「はじめる」をクリックして、返事をしなかったメンバー抜きでクエストをはじめることもできます。また、クエストのオーナーは、「キャンセル」をクリックして、クエストをキャンセルし、クエストの巻物を取り戻すことができます。",
"questStartBroken": "一度すべてのメンバーが承認または拒否するとクエストがはじまります...「承認」をクリックしたメンバーだけがクエストに参加して、アイテムを手に入れることができます...もし長期にメンバーが保留しつづけた場合 (アクティブじゃない?)、クエストの所有者は[はじめる]をクリックすることでそのメンバーたちを除いてクエストをはじめることができます...また、クエストの所有者は[中止する]をクリックすることで、クエストを中止しクエストの巻物を取り返すことができます...",
"begin": "はじめる",
"bossHP": "ボスの体力",
@@ -34,11 +34,11 @@
"collected": "獲得済み",
"collectionItems": "<%= number %> <%= items %>",
"itemsToCollect": "獲得アイテム",
- "bossDmg1": "日課やTo-Do、いい習慣を完了するたびにボスにダメージを与えます。赤くなったタスク行う、または「強烈なスマッシュ」や「火炎爆破」のスキルをつかうとさらに大きなダメージを与えることができます。あなたが日課をサボると、通常の日課のダメージにくわえて、ボスがクエストに参加している全員に与えるダメージ ( × ボスの強さ) を受けることになります。ですから、パーティーの仲間はみんなで日課を実行しなくてはいけません! ボスに与えるダメージ、およびボスから受けるダメージは、いずれも cron (あなたが設定した日付更新の時間) にまとめて計算されます。",
+ "bossDmg1": "日課やTo-Do、いい習慣を完了するたびにボスにダメージを与えます。赤くなったタスクを行う、または「強烈なスマッシュ」や「火炎爆破」の特殊能力をつかうとさらに大きなダメージを与えることができます。あなたが日課をサボると、通常の日課のダメージにくわえて、ボスがクエストに参加している全員に与えるダメージ ( × ボスの強さ) を受けることになります。ですから、パーティーの仲間はみんなで日課を実行しなくてはいけません! ボスに与えるダメージ、およびボスから受けるダメージは、いずれも cron (あなたが設定した日付更新の時間) にまとめて計算されます。",
"bossDmg2": "参加者だけがボスと戦い、クエストの賞品を分け合うことができます。",
- "bossDmg1Broken": "日課やTo-Do、いい習慣を行うたびにボスにダメージを与える...赤くなったタスクを行う、または「強烈なスマッシュ」や「火炎爆破」のスキルをつかうとさらに大きなダメージを与えられる...あなたが日課をサボると、通常の日課のダメージにくわえて、ボスがクエストに参加している全員に与えるダメージ ( × ボスの強さ) を受けることになる。だから、パーティーの仲間はみんなで日課を実行しなくてはいけない...ボスに与えるダメージ、およびボスから受けるダメージは、いずれも cron (あなたが設定した日付更新の時間) にまとめて計算される...",
+ "bossDmg1Broken": "日課やTo-Do、いい習慣を行うたびにボスにダメージを与える...赤くなったタスクを行う、または「強烈なスマッシュ」や「火炎爆破」の特殊能力をつかうとさらに大きなダメージを与えられる...あなたが日課をサボると、通常の日課のダメージにくわえて、ボスがクエストに参加している全員に与えるダメージ ( × ボスの強さ) を受けることになる。だから、パーティーの仲間はみんなで日課を実行しなくてはいけない...ボスに与えるダメージ、およびボスから受けるダメージは、いずれも cron (あなたが設定した日付更新の時間) にまとめて計算される...",
"bossDmg2Broken": "参加者だけがボスと戦い、クエストの賞品を分け合うことができる...",
- "tavernBossInfo": "Complete Dailies and To-Dos and score positive Habits to damage the World Boss! Incomplete Dailies fill the Rage Bar. When the Rage bar is full, the World Boss will attack an NPC. A World Boss will never damage individual players or accounts in any way. Only active accounts not resting in the Inn will have their tasks tallied.",
+ "tavernBossInfo": "日課やTo-Doを完了したり、いい習慣を行うたびに、世界のボスにダメージを与えます! 日課をサボると「消耗の一撃」バーがたまっていきます。「消耗の一撃」バーがいっぱいになると、世界のボスはNPCを攻撃します。世界のボスは一人ひとりのプレーヤーやアカウントを攻撃することはありません。宿で休んでいるメンバーを除いた活動中のアカウントのタスクだけが、ボスへのとして計算されます。",
"tavernBossInfoBroken": "日課やTo-Do、いい習慣を行うたびに世界のボスにダメージを与える...日課をサボると「消耗の一撃」バーがたまっていく...「消耗の一撃」バーがいっぱいになると、世界のボスはNPCを攻撃する...世界のボスは一人ひとりのプレーヤーやアカウントを攻撃することはない...宿で休んでいるメンバーを除いた活動中のアカウントのタスクのみ計算される...",
"bossColl1": "アイテムを収集するには、前向きのタスクを実行します。クエスト アイテムは通常のアイテムと同様に手に入ります。ただし、翌日までアイテムを確認しないと、あなたが見つけたアイテムは山に集められてしまいます。",
"bossColl2": "参加者だけがアイテムを集めることができ、クエストの賞品を分け合うことができる...",
@@ -78,5 +78,24 @@
"whichQuestStart": "どのクエストをはじめますか?",
"getMoreQuests": "もっとたくさんのクエストを手に入れる",
"unlockedAQuest": "クエストをアンロックしました!",
- "leveledUpReceivedQuest": "レベルアップして、レベル <%= level %> になったので、クエストの巻物を受けとりました!"
+ "leveledUpReceivedQuest": "レベルアップして、レベル <%= level %> になったので、クエストの巻物を受けとりました!",
+ "questInvitationDoesNotExist": "No quest invitation has been sent out yet.",
+ "questInviteNotFound": "No quest invitation found.",
+ "guildQuestsNotSupported": "Guilds cannot be invited on quests.",
+ "questNotFound": "Quest \"<%= key %>\" not found.",
+ "questNotOwned": "You don't own that quest scroll.",
+ "questNotGoldPurchasable": "Quest \"<%= key %>\" is not a Gold-purchasable quest.",
+ "questLevelTooHigh": "You must be level <%= level %> to begin this quest.",
+ "questAlreadyUnderway": "Your party is already on a quest. Try again when the current quest has ended.",
+ "questAlreadyAccepted": "You already accepted the quest invitation.",
+ "noActiveQuestToLeave": "No active quest to leave",
+ "questLeaderCannotLeaveQuest": "Quest leader cannot leave quest",
+ "notPartOfQuest": "You are not part of the quest",
+ "noActiveQuestToAbort": "There is no active quest to abort.",
+ "onlyLeaderAbortQuest": "Only the group or quest leader can abort a quest.",
+ "questAlreadyRejected": "You already rejected the quest invitation.",
+ "cantCancelActiveQuest": "You can not cancel an active quest, use the abort functionality.",
+ "onlyLeaderCancelQuest": "Only the group or quest leader can cancel the quest.",
+ "questNotPending": "There is no quest to start.",
+ "questOrGroupLeaderOnlyStartQuest": "Only the quest leader or group leader can force start the quest"
}
\ No newline at end of file
diff --git a/common/locales/ja/questscontent.json b/common/locales/ja/questscontent.json
index c74bf17dcd..0953701c09 100644
--- a/common/locales/ja/questscontent.json
+++ b/common/locales/ja/questscontent.json
@@ -1,63 +1,63 @@
{
"questEvilSantaText": "猟師のサンタ",
- "questEvilSantaNotes": "氷原の彼方から、なげきの遠吠えが聞こえる。あなたは、甲高い笑い声にさえぎられながらも聞こえてくるうなり声を追いかけて森の中の広場にたどり着いた。そこには、大きなメスのホッキョクグマがいた。ホッキョクグマは足かせをされ、おりの中に閉じこめられている。それでも命がけで戦っているのだ。打ち捨てられた衣装を着た、意地の悪い小悪魔がおりの上で踊っている。わなをしかけたサンタを倒して、ホッキョクグマを救え!",
- "questEvilSantaCompletion": "Trapper Santa squeals in anger, and bounces off into the night. The grateful she-bear, through roars and growls, tries to tell you something. You take her back to the stables, where Matt Boch the Beast Master listens to her tale with a gasp of horror. She has a cub! He ran off into the icefields when mama bear was captured.",
+ "questEvilSantaNotes": "氷原の彼方から、なげきの遠吠えが聞こえます。甲高い笑い声にさえぎられながらも聞こえてくるうなり声を追いかけて、ついに森の中の広場にたどり着きました。そこにいたのは、大きなメスのホッキョクグマです。ホッキョクグマは足かせをされ、おりの中に閉じこめられているのに、それでも命がけで戦っています。どこからか拾ってきた衣装を着た、意地の悪い小悪魔がおりの上で踊っています。わなをしかけたサンタを倒して、ホッキョクグマを救うのです!",
+ "questEvilSantaCompletion": "猟師のサンタがあげた怒りの悲鳴は、夜の中にこだましました。ホッキョクグマは感謝の気持ちをなんとか伝えようと、ほえ、うなります。あなたが動物小屋に連れて帰ると、猛獣使いのマット・バックは、恐怖のため息がまじるホッキョクグマの話を聞いてやりました。なんとホッキョクグマには子グマがいたのです! 母グマが捕まったとき、氷原に逃げていったというのです。",
"questEvilSantaBoss": "猟師のサンタ",
- "questEvilSantaDropBearCubPolarMount": "ホッキョクグマ(乗用獣)",
- "questEvilSanta2Text": "幼獣を見つけましょう!",
- "questEvilSanta2Notes": "When Trapper Santa captured the polar bear mount, her cub ran off into the icefields. You hear twig-snaps and snow crunch through the crystalline sound of the forest. Paw prints! You start racing to follow the trail. Find all the prints and broken twigs, and retrieve the cub!",
- "questEvilSanta2Completion": "You've found the cub! It will keep you company forever.",
+ "questEvilSantaDropBearCubPolarMount": "ホッキョクグマ (騎獣)",
+ "questEvilSanta2Text": "子グマの捜索",
+ "questEvilSanta2Notes": "猟師のサンタが騎獣のホッキョクグマを捕まえたとき、彼女の子グマは、氷原に逃げていきました。たしかに森の中の氷の結晶の音に交じって、小枝をふむ音や雪がくだける音が聞こえます。足あとだ! それを追いかけようと走り出します。足あとと折れた小枝を見のがしてはいけません。子グマを見つけ出しましょう!",
+ "questEvilSanta2Completion": "子グマを見つけました! ずっとあなたから離れないでしょう。",
"questEvilSanta2CollectTracks": "足跡",
"questEvilSanta2CollectBranches": "折れた小枝",
- "questEvilSanta2DropBearCubPolarPet": "愛白熊(ペット)",
+ "questEvilSanta2DropBearCubPolarPet": "ホッキョクグマ ( ペット )",
"questGryphonText": "炎のグリフォン",
- "questGryphonNotes": "グランドビーストマスターであるbaconsaurがあなたのパーティーに助けを求めてきました。「どうか、冒険者よ、あなたは私を助けなければならない!私の大切なグリフォンが解き放たれてHabit Cityに恐怖を与えている。もし止めることができたら、グリフォンの卵を授けよう!」",
- "questGryphonCompletion": "強大な獣は敗れて恥ずかしそうにこそこそと主人のもとに戻りました。「驚いた!よくやった冒険者よ!」baconsaur は声を上げました。 「どうかこれらグリフォンの卵を持って行きなさい。あなたならうまく育てると信じています!」",
+ "questGryphonNotes": "偉大な猛獣使い、baconsaurがあなたのパーティーに助けを求めてきました。「冒険者よ、どうか私を助けてください! 大切なグリフォンが逃げてしまい、Habit シティーを自由気ままに飛び回り、人びと に恐怖を与えているのです。もしグリフォンを止められたら、お礼にグリフォンのたまごを差し上げます!」",
+ "questGryphonCompletion": "やりました! 強い獣はこそこそとはずかしそうに主人の元に帰りました。「驚いた! 冒険者よ、よくやってくれましたね!」 baconsaur は声を上げました。 「どうかこのグリフォンのたまごを受けとってください。あなたならきっとうまく育てられるでしょう!」",
"questGryphonBoss": "炎のグリフォン",
- "questGryphonDropGryphonEgg": "グリフォン(たまご)",
- "questGryphonUnlockText": "市場でのグリフォンのたまご購入のアンロック",
+ "questGryphonDropGryphonEgg": "グリフォン ( たまご )",
+ "questGryphonUnlockText": "市場でのグリフォンのたまご購入をアンロック",
"questHedgehogText": "巨大なハリネズミ",
"questHedgehogNotes": "ハリネズミは面白い動物です。Habitの住民には最も愛いされているペットです。しかし、噂では、真夜中にミルクを与えた場合、ハリネズミはとても凶暴になります。しかも、50倍の大きさに巨大化します。 Inventrixがちょうど今それをやってしまいました。このままではまずいです。",
"questHedgehogCompletion": "あなたのパーティは強大化したハリネズミを鎮静させることに成功しました!ハリネズミは元の大きさに戻り、よろよろと卵のところに行きました。そしてキーキー鳴きながらいくつかの卵をあなたのパーティに向かって押して持ってこようとしています。孵化したハリネズミ達がもっとミルクを好きになるといいですね!",
"questHedgehogBoss": "巨大なハリネズミ",
- "questHedgehogDropHedgehogEgg": "ハリネズミ(たまご)",
- "questHedgehogUnlockText": "市場でのハリネズミのたまご購入のアンロック",
+ "questHedgehogDropHedgehogEgg": "ハリネズミ ( たまご ) ",
+ "questHedgehogUnlockText": "市場でのハリネズミのたまご購入をアンロック",
"questGhostStagText": "春の神",
"questGhostStagNotes": "あぁ、春だ。今年は風景がまた彩られていきます。冬の雪溜まりもなくなりました。いきいきとした植物は霜をはらい落としています。甘美な緑の葉が木々を満たし、草原は彩りを取り戻し、様々な花は平野に沿って生えていき、白い神秘的な霧が大地を覆っています!。。。おや、神秘的な霧?”これはいけません、”Inventrixは不安げに言います、”これはなにかの精霊による仕業です。あ、あなたに向かって来ています。”",
"questGhostStagCompletion": "負傷はしてないように見える精霊は、鼻を地面に向けました。心を落ち着かせる声があなたのパーティーを包み込みます。”私が行った行動について謝罪します。私はちょうど眠りから目覚めたところです。私の知恵はまだ完全に戻っていません。お詫びとしてこれらを受け取ってください。”一塊の卵が精霊の前の芝生の上に出現しました。そして精霊は無言で森林に逃げていき、通ったところの花びらは散っていきました。",
"questGhostStagBoss": "妖怪のしか",
"questGhostStagDropDeerEgg": "鹿(卵)",
- "questGhostStagUnlockText": "市場での鹿の卵購入のアンロック",
+ "questGhostStagUnlockText": "市場でのシカのたまご購入をアンロック",
"questRatText": "ネズミ王",
"questRatNotes": "ゴミ! 大量の未チェックの日課の山がHabitica中に分布しています。ラットの大群がいまやどこでも見られるほど問題は深刻になっています。あなたは@Pandahが獣のうちの一匹を愛情を込めてかわいがっているのに気づきます。彼女はラットは未チェックの日課を餌にしている優しい生き物であると説明します。本当の問題は、日課が下水道に嵌まり込み、クリアしなければならない危険な穴を作っている事です。あなたが下水道に降りたら、血で赤い目と切り苛まれた黄色い歯をもった大きなラットが、あなたを攻撃し群れを守ります。あなたは伝説的なラットキングに対して、恐怖で身がすくむでしょうか、毅然と立ち向かうでしょうか?",
"questRatCompletion": "あなたの最後の一撃が巨大なラットの力を奪い、ラットの目がだんだん暗い灰色に変わっていきます。その獣はたくさんの小さなラットに分裂し、恐怖で逃げ去りました。あなたは@Pandahが背後から、かつて巨大な生き物だったものを見ている事に気づきます。Habiticaの市民はあなたの勇気に触発され急いで未処理の日課を終えていると彼女が説明しました。我々は警戒しなければならない、油断したらラットキングが戻ってくるだろうと彼女は警告します。報酬として、@Pandahはあなたにラットの卵を提供します。彼女はあなたの不安な表情に気づき「すてきなペットになるでしょう」と微笑みます。",
"questRatBoss": "ネズミ王",
"questRatDropRatEgg": "ネズミ(卵)",
- "questRatUnlockText": "市場でのラットの卵購入のアンロック",
+ "questRatUnlockText": "市場でのネズミのたまご購入をアンロック",
"questOctopusText": "オクトフルの叫び",
"questOctopusNotes": "@Urse、ワイルドな目をした若い筆記者が、あなたに海岸の神秘的な洞窟の探索の手助けの求めています。夕暮れに、潮だまりの中で鍾乳石や石筍の巨大なゲートを立っています。あなたがゲートに近づくと、暗い渦がそこで回転し始めました。あなたは畏れてじっと見つめますと、渦の中からイカのようなドラゴンが浮き上がってきました。「星のスティッキーの子が目覚めた」と狂ったように@Urseが唸り声を上げました。「何億兆年も眠って、再び自由になって興奮して獲物を探している!」",
"questOctopusCompletion": "With a final blow, the creature slips away into the whirlpool from which it came. You cannot tell if @Urse is happy with your victory or saddened to see the beast go. Wordlessly, your companion points to three slimy, gargantuan eggs in a nearby tidepool, set in a nest of gold coins. \"Probably just octopus eggs,\" you say nervously. As you return home, @Urse frantically scribbles in a journal and you suspect this is not the last time you will hear of the great Octothulu.",
"questOctopusBoss": "オクトフル",
"questOctopusDropOctopusEgg": "たこ(卵)",
- "questOctopusUnlockText": "市場でのタコの卵購入のアンロック",
+ "questOctopusUnlockText": "市場でのタコのたまご購入をアンロック",
"questHarpyText": "助けて!ハーピィだ!",
"questHarpyNotes": "勇敢な冒険者@UncommonCriminalは数日前に、目撃した翼の生えたモンスターを追跡中に森の中で失踪しました。あなたが捜索しようとした時、怪我をしている一匹のオウムがあなたの肩に止まりました。美しい羽に痛々しい傷跡があります。オウムの脚には走り書きされたメモが付けられています。@UncommonCriminalは凶悪なハーピィに捕まって、脱出するために必死にあなたに助けを求めています。あなたはこの鳥と共にハーピィを倒し、@UncommonCriminalを救出しますか?",
"questHarpyCompletion": "最後の一撃がハーピーをうちおとして、羽があらゆる方向へと飛んでいきます。すばやく巣に登ると、オウムの卵に囲まれた@UncommonCriminalを見つけます。チームとして、あなたは素早く卵を近くの巣に戻します。あなたを見つけた傷付いたオウムは騒がしく鳴いて、あなたの腕の中に卵を落とします。「ハーピーの攻撃は保護が必要な卵を残している」@UncommonCriminalは説明します。「あなたは名誉のオウムになったようだ」",
"questHarpyBoss": "ハーピィ",
"questHarpyDropParrotEgg": "オウム(卵)",
- "questHarpyUnlockText": "市場でのオウムの卵購入のアンロック",
+ "questHarpyUnlockText": "市場でのオウムのたまご購入をアンロック",
"questRoosterText": "雄鶏の大暴れ",
"questRoosterNotes": "長年にわたり、農家の@extrajordanaryは目覚まし時計として雄鶏を飼っています。しかし、巨大な雄鶏が出現して、どの雄鶏よりも大きな声で鳴くため、Habiticaの住民みんなが目を覚ましました。そのせいで睡眠不足になり、日課をこなすのが困難になりました。 @Pandoroはこれに終止符を打つことに決めました。 「雄鶏の鳴き声を静められる人がいたらお願いできませんか?」あなたはボランティア活動として、朝いち早く雄鶏に近づきました。しかし、雄鶏は巨大な翼を羽ばたき、鋭い爪をみせて、戦いを挑む鳴き声を上げた。",
"questRoosterCompletion": "策略と力により、あなたは野獣を飼いならした。かつて羽根と半忘れのタスクで詰まっていたその耳は今や火を見るより明らかです。それは大人しく鳴いてくちばしをあなたの肩にすり寄せます。翌日あなたが出発しようとすると、@EmeraldOx が蓋付きバスケットを持って走り寄ります。「待って!今朝農家に行ったら、雄鶏がこれをあなたが寝ている扉に押しやっていました。彼はあなたにこれを持っていってほしいのだと思います」あなたがバスケットの蓋をあけると三つの繊細な卵を見つけました。",
"questRoosterBoss": "雄鶏",
"questRoosterDropRoosterEgg": "雄鶏(卵)",
- "questRoosterUnlockText": "市場での雄鶏の卵購入のアンロック",
+ "questRoosterUnlockText": "市場でのニワトリのたまご購入をアンロック",
"questSpiderText": "氷のクモ",
"questSpiderNotes": "天気の冷え込みとともに、優雅な霜がレースの織物のようにHabitican達の家の窓に張り付き始めました。@Arcosineを除いて.... 彼の家では、フロストスパーダーによって窓が凍って開かなくなってしまいました。なんてことだ。",
"questSpiderCompletion": "フロストスパイダーは敗れて、霜の小さな杭と魅了な卵嚢をいくつか残して崩れました。@Arcosineはすぐに報酬としてあなたに上げました。あなたならもしかして無害なクモをペットとして育てられるかも?",
"questSpiderBoss": "クモ",
"questSpiderDropSpiderEgg": "クモ(卵)",
- "questSpiderUnlockText": "市場でのクモの卵購入のアンロック",
+ "questSpiderUnlockText": "市場でのクモのたまご購入をアンロック",
"questVice1Text": "悪い習慣、パート1: ドラゴンの影響からあなたを開放する",
"questVice1Notes": "
They say there lies a terrible evil in the caverns of Mt. Habitica. A monster whose presence twists the wills of the strong heroes of the land, turning them towards bad habits and laziness! The beast is a grand dragon of immense power and comprised of the shadows themselves: Vice, the treacherous Shadow Wyrm. Brave Habiteers, stand up and defeat this foul beast once and for all, but only if you believe you can stand against its immense power.
Vice Part 1:
How can you expect to fight the beast if it already has control over you? Don't fall victim to laziness and vice! Work hard to fight against the dragon's dark influence and dispel his hold on you!
",
"questVice1Boss": "悪魔の影",
@@ -73,15 +73,15 @@
"questVice3DropWeaponSpecial2": "Stephen Weberのドラゴンの矢",
"questVice3DropDragonEgg": "ドラゴン(卵)",
"questVice3DropShadeHatchingPotion": "暗い水薬",
- "questMoonstone1Text": "The Moonstone Chain, Part 1: The Moonstone Chain",
- "questMoonstone1Notes": "A terrible affliction has struck Habiticans. Bad Habits thought long-dead are rising back up with a vengeance. Dishes lie unwashed, textbooks linger unread, and procrastination runs rampant!
You track some of your own returning Bad Habits to the Swamps of Stagnation and discover the culprit: the ghostly Necromancer, Recidivate. You rush in, weapons swinging, but they slide through her specter uselessly.
\"Don’t bother,\" she hisses with a dry rasp. \"Without a chain of moonstones, nothing can harm me – and master jeweler @aurakami scattered all the moonstones across Habitica long ago!\" Panting, you retreat... but you know what you must do.",
+ "questMoonstone1Text": "ムーンストーンの章, Part 1: ムーンストーンの鎖",
+ "questMoonstone1Notes": "酷い苦痛がHabiticianを襲いました。長い間死んだと思われていた悪い習慣が復讐のため蘇ったのです。汚れた皿は積み重なり、教科書は読まれずに放置され、先延ばしが横行しています!
「邪魔をするな。」彼女は耳障りに乾き、かすれた声で言います。「ムーンストーンの鎖がなければ、誰も私を傷つけられない…だからあの筆頭宝石商の@aurakamlは、遥か昔全てのムーンストーンをHabitica中にばらまいたのさ!」息切れし、あなたは退却します…が、どうやらすべきことはわかったようです。",
"questMoonstone1CollectMoonstone": "ムーンストーン",
"questMoonstone1DropMoonstone2Quest": "ムーンストーンチェーン パート2:魔術師レシディベイト(スクロール)",
- "questMoonstone2Text": "The Moonstone Chain, Part 2: Recidivate The Necromancer",
- "questMoonstone2Notes": "The brave weaponsmith @Inventrix helps you fashion the enchanted moonstones into a chain. You’re ready to confront Recidivate at last, but as you enter the Swamps of Stagnation, a terrible chill sweeps over you.
Rotting breath whispers in your ear. \"Back again? How delightful...\" You spin and lunge, and under the light of the moonstone chain, your weapon strikes solid flesh. \"You may have bound me to the world once more,\" Recidivate snarls, \"but now it is time for you to leave it!\"",
+ "questMoonstone2Text": "ムーンストーンの章, Part 2: 死霊術師Recidivate",
+ "questMoonstone2Notes": "勇気ある武器鍛冶 @Inventrix があなたが魔法のムーンストーンの鎖を作るのを手伝ってくれました。そしてあなたはRecidivateに相対しようとします。しかし、あなたが不振の沼に踏み入った途端、恐ろしい凍気があなたに吹き付け吹き飛ばしました。
腐った息があなたの耳にささやきます。「帰ってきたのか?なんと面白い…」あなたは回転をつけ突進します。あなたの武器はムーンストーンの光の下で実体をもった相手の肉を打ち付けました。「お前は、今一度現世に私をつなぎ留めたのかもしれないねぇ」Recidivateは唸ります。「けど…逃げるなら今のうちだよ!」",
"questMoonstone2Boss": "魔術師",
"questMoonstone2DropMoonstone3Quest": "ムーンストーンチェーン パート3:レシディベイトの変身(スクロール)",
- "questMoonstone3Text": "The Moonstone Chain, Part 3: Recidivate Transformed",
+ "questMoonstone3Text": "ムーンストーンの章, Part 3: Recidivate、変身",
"questMoonstone3Notes": "Recidivate crumples to the ground, and you strike at her with the moonstone chain. To your horror, Recidivate seizes the gems, eyes burning with triumph.
\"Foolish creature of flesh!\" she shouts. \"These moonstones will restore me to a physical form, true, but not as you imagined. As the full moon waxes from the dark, so too does my power flourish, and from the shadows I summon the specter of your most feared foe!\"
A sickly green fog rises from the swamp, and Recidivate’s body writhes and contorts into a shape that fills you with dread – the undead body of Vice, horribly reborn.",
"questMoonstone3Completion": "Your breath comes hard and sweat stings your eyes as the undead Wyrm collapses. The remains of Recidivate dissipate into a thin grey mist that clears quickly under the onslaught of a refreshing breeze, and you hear the distant, rallying cries of Habiticans defeating their Bad Habits for once and for all.
@Baconsaur the beast master swoops down on a gryphon. \"I saw the end of your battle from the sky, and I was greatly moved. Please, take this enchanted tunic – your bravery speaks of a noble heart, and I believe you were meant to have it.\"",
"questMoonstone3Boss": "ネクロ-バイス",
@@ -100,24 +100,24 @@
"questGoldenknight3Completion": "With a satisfying clang, the Iron Knight falls to his knees and slumps over. \"You are quite strong,\" he pants. \"I have been humbled, today.\" The Golden Knight approaches you and says, \"Thank you. I believe we have gained some humility from our encounter with you. I will speak with my father and explain the complaints against us. Perhaps, we should begin apologizing to the other Habiticans.\" She mulls over in thought before turning back to you. \"Here: as our gift to you, I want you to keep my morningstar. It is yours now.\"",
"questGoldenknight3Boss": "アイアンナイト",
"questGoldenknight3DropHoney": "蜂蜜(食べ物)",
- "questGoldenknight3DropGoldenPotion": "ゴールデンハッチングポーション",
+ "questGoldenknight3DropGoldenPotion": "金のたまごがえしの薬",
"questGoldenknight3DropWeapon": "ムステインのマイルストーンマッシュモーニングスター(利き手でないほうの手に装備する武器)",
"questBasilistText": "バシ・リスト",
"questBasilistNotes": "市場は抜け出したいほどのにぎわいです。勇敢な冒険者になるには、あえてそこに向かい、バシ・リスト――倒すことのできなかった To-Do の群れが合体した大蛇を発見しなくてはなりません。すぐそばにいる Habitica の民は、最初はおののいていたバシ・リストの長さに最近は慣れ、タスクをこなすことができなくなっているのです。近くのどこかから、@Arcosine が「急げ! To-Doと日課をこなして、モンスターを無力化するんだ。さもないとだれかが切り離してしまうぞ」と叫んでいるのが聞こえるでしょう。すぐに攻撃を! 冒険者よ。そして、気をつけましょう! もしあなたが1つでも日課をやり残すと、バシ・リストはあなたとパーティーの仲間に攻撃を加えてきます!",
- "questBasilistCompletion": "バジリストは紙くずに散らばっていて、虹色に揺らめいています。「やれやれ!」と@Arcosineが言います。「君たちがここにいて良かった!」以前より手慣れて感じて、あなたは紙のあいだから落ちたゴールドを集めます。",
+ "questBasilistCompletion": "バシ・リストは、紙くずとなって散らばり、虹色におだやかな輝きを放っています。「ふう!」と @Arcosine 。「君たちがここにいて良かった!」 以前よりも成長したこと感じながら、パーティーは紙をかきわけ、落ちているゴールドを集るのです。",
"questBasilistBoss": "バシ・リスト",
- "questEggHuntText": "卵狩り",
- "questEggHuntNotes": "奇妙な無地の卵は夜通しどこにでも現れました。マットの厩舎や酒場のカウンターの裏や市場のペット卵の間にさえも!なんと迷惑なんでしょう!「それらがどこから来たのか、卵がかえって何がうまれるのか、誰も知りません」とミーガンは言います。「しかしそこらで産まれるままにしておくわけにいきません。がんばって探してこの謎めいた卵を集めるのを手伝ってください。もし十分集めたら、たぶんあなたへの余分がでるでしょう…」",
- "questEggHuntCompletion": "やりました! ミーガンは感謝をこめて、10個のたまごをくれました。「きっと『たまごがえしの薬』がすばらしい色に染めてくれることでしょう! さらに乗用獣になるとき何が起こるか…」",
- "questEggHuntCollectPlainEgg": "プレン卵",
- "questEggHuntDropPlainEgg": "プレン卵",
+ "questEggHuntText": "たまご狩り",
+ "questEggHuntNotes": "一夜のうちに、奇妙な普通のたまごがいたるところに出現したのです。マットの動物小屋、キャンプ場のカウンターの裏、そして市場のペットのたまごの中にまぎれこんで! なんて迷惑なんでしょう! 「どこからやってきて、何が生まれるか、だれもわからないんだ」と、ミーガン。「だけど、そのまま放っておくわけにはいかない! この不思議なたまごを集めるために、力を貸してほしい。一生懸命働いて、一生懸命探してほしいんだ。たくさん集めたら、余分のいくつかはあげるよ…」",
+ "questEggHuntCompletion": "やりました! ミーガンは感謝をこめて、10個のたまごをくれました。「きっと『たまごがえしの薬』がすばらしい色に染めてくれることでしょう! さらに騎獣になるとき何が起こるでしょうか…」",
+ "questEggHuntCollectPlainEgg": "普通のたまご",
+ "questEggHuntDropPlainEgg": "普通のたまご",
"questDilatoryText": "ドレッドディラトリードラゴン",
"questDilatoryNotes": "We should have heeded the warnings.
Dark shining eyes. Ancient scales. Massive jaws, and flashing teeth. We've awoken something horrifying from the crevasse: the Dread Drag'on of Dilatory! Screaming Habiticans fled in all directions when it reared out of the sea, its terrifyingly long neck extending hundreds of feet out of the water as it shattered windows with its searing roar.
\"This must be what dragged Dilatory down!\" yells Lemoness. \"It wasn't the weight of the neglected tasks - the Dark Red Dailies just attracted its attention!\"
\"It's surging with magical energy!\" @Baconsaur cries. \"To have lived this long, it must be able to heal itself! How can we defeat it?\"
Why, the same way we defeat all beasts - with productivity! Quickly, Habitica, band together and strike through your tasks, and all of us will battle this monster together. (There's no need to abandon previous quests - we believe in your ability to double-strike!) It won't attack us individually, but the more Dailies we skip, the closer we get to triggering its Neglect Strike - and I don't like the way it's eyeing the Tavern....",
"questDilatoryBoss": "ドレッドディラトリードラゴン",
"questDilatoryBossRageTitle": "打撃を無視しなさい",
"questDilatoryBossRageDescription": "このバーがいっぱいになった時、ドレッドディラトリードラゴンはHabiticaの地形に大きな大破壊を浴びせます。",
"questDilatoryDropMantisShrimpPet": "シャコ(ペット)",
- "questDilatoryDropMantisShrimpMount": "カマキリエビ (乗用獣)",
+ "questDilatoryDropMantisShrimpMount": "カマキリエビ (騎獣)",
"questDilatoryBossRageTavern": "”ドレッドドラゴンはネグレクトストライクを放しました”\n\n私達は最善の努力を尽くしましたが、いくつかの日課を見落としてしまいました。 彼らの暗い赤色がドレッドドラゴンの怒りを掻きあげました。 恐ろしいネグレクトストライクで酒屋をめちゃくちゃにしました!幸運なことに私達は近くの街の宿にいたので安全でした。あなたたちはまだチャットを続けられます....しかし、貧しいバーテンダーのダニエルは最愛の建物が崩れていくのを見ているしかなかったです。!\n\nドレッドドラゴンがまた攻撃をして来ないことを祈ります!",
"questDilatoryBossRageStables": "”ドレッドドラゴンはネグレクトストライクを放しました”\n\nあぁ!私たちはまた日課をいっぱいやり残していました。ドレッドドラゴンはネグレクトストライクを解き放ちました!ペットたちは四方八方に逃げています。幸いなことに私たちは安全です。\n\n悲惨なHabitica!もう二度とこんなことが起こらないことを祈ります。はやくタスクをやらなければ!",
"questDilatoryBossRageMarket": "”ドレッドドラゴンはネグレクトストライクを放しました”\n\nああ!商人のアレックスはちょうどドレッドドラゴンのネグレクトストライクによって店を粉々に破壊されました。しかし、ドレッドドラゴンを追い詰めたようにも見えます。他の攻撃を繰り出す力があるかもしれません。\n\nだから動じないで、Habitica!この獣を私たちの海岸から追い出しましょう!",
@@ -126,40 +126,40 @@
"questSeahorseNotes": "今日はダービーの日です。大陸中からのHabitican人はディラトリーにやってきてペットのタツノオトシゴにレースさせます。突然大きな水しぶきと唸り声が競走場に聞こえ、あなたはタツノオトシゴの飼い主の@Kiwibotが波のうなりに叫ぶのを耳にします。「タツノオトシゴを集める事は凶暴なシースタリオンの注意をひきます!」彼女が叫びます。「彼は 厩舎を粉々にし古来の競走路を破壊している!誰か彼を鎮められませんか?」",
"questSeahorseCompletion": "今や飼いならされたシースタリオンはあなたの側を従順に泳いでいます。「ああ見て!」とKiwibotは言います。「彼は私たちに子供の世話をして欲しいのです。」彼女はあなたに3つの卵を与えます。「うまく育ててください」と彼女は言います。「あなたはディラトリーダービーではいつでも歓迎されます!」",
"questSeahorseBoss": "シースタリオン",
- "questSeahorseDropSeahorseEgg": "竜の落し子(卵)",
- "questSeahorseUnlockText": "市場でのタツノオトシゴの卵購入のアンロック",
- "questAtom1Text": "襲い来る日常、Part1:Dish Disaster!",
- "questAtom1Notes": "あなたは手洗いのできる池のほとりに着いてゆっくりしようとしましたが...しかしその池が洗っていない皿で汚れていました。なぜこんなことに?うーん、この状態の池を見過ごすわけにはいきません。あなたが唯一できることは:皿を洗って、あなたの休憩所を清潔にしなさい!石鹸を探して洗ったほうがいいでしょう。たくさんの石鹸を...",
- "questAtom1CollectSoapBars": "棒状の石鹸",
- "questAtom1Drop": "The SnackLess Monster (Quest Scroll)",
- "questAtom2Text": "Attack of the Mundane, Part 2: The SnackLess Monster",
- "questAtom2Notes": "やれやれ、ここはこの皿全てを洗うのにとてもいい場所のようです。あなたはたぶん最終的には楽しくなります。ああ、湖にピザの箱が浮いているのが見えます。ええと、本当に奇麗にするもうひとつのものは何でしょう?しかし悲しいかな、あれは単なるピザの箱ではありません!突然その箱が水面から持ち上がりモンスターの頭が正体を現します。ありえない!伝説のスナックレスモンスター!?有史以来、湖に密かに現存していると言われている、食べ残しや古代のHabitican人のゴミからうまれる生き物です。ゲーッ!",
+ "questSeahorseDropSeahorseEgg": "タツノオトシゴ ( たまご )",
+ "questSeahorseUnlockText": "市場でのタツノオトシゴのたまご購入をアンロック",
+ "questAtom1Text": "日常の攻撃 パート1 : 食器の惨事!",
+ "questAtom1Notes": "がんばったごほうびの骨休めに「洗い場の湖」のほとりに着きました。…しかし、湖は洗っていない食器で汚染されています! なぜこんなことに? うーん、この状態の湖を見過ごすわけにはいきません。できることは 1 つだけ : 皿を洗い、憩いの場所を取り戻すのです! この混乱を解決するには、洗剤を見つけた方がいいでしょう。たくさんの洗剤を…",
+ "questAtom1CollectSoapBars": "せっけんの棒",
+ "questAtom1Drop": "モンスター、「スナックナシ」 ( クエストの巻物 ) ",
+ "questAtom2Text": "日常の攻撃 パート2 : モンスター、「スナックナシ」",
+ "questAtom2Notes": "やれやれ、食器をすべて洗って、ぐっと素敵な場所になったようです。きっと、あなたはいまこの最後のシーンを楽しんでいることでしょう。ああ、湖にピザの箱が浮いているのが見えます。ええと、本当にもう 1 つ片づけないといけないの? いや待って、ただのピザの箱ではありません! 突然その箱が水面から浮かび上がるとその正体はモンスターの頭です。ありえない! 伝説のモンスター「スナックナシ」!? 有史以来、古代の Habitica の民の食べ残しやゴミから生まれた生き物が、湖にひそんでいると伝えられていました。ゲーッ!",
"questAtom2Boss": "スナックレスモンスター",
"questAtom2Drop": "The Laundromancer (Quest Scroll)",
"questAtom3Text": "Attack of the Mundane, Part 3: The Laundromancer",
"questAtom3Notes": "耳を突き破るような泣き声とともに、その口から五種類の美味しそうなチーズが吹き出し、スナックレスモンスターはバラバラに崩れ落ちた。「よくそんなことが言えるな!」水面の下から轟くような声がひびきます。水中から青いローブを纏った人影が現れ、魔法のトイレブラシを振り回しています。不潔な洗濯物は、湖の表面に泡立ち始めます。「私はランドロマンサーだ」と彼は怒ったように言います。「いい度胸をしてるな。俺の汚れた皿を楽しげに洗い、私のペットを壊し、そのような清潔な服で俺の領域に入る。俺のアンティランドリーマジックを喰らえ!」",
"questAtom3Completion": "物騒なラウンドロマンサーは倒されました!あなたの周りに積まれた洗濯物を洗濯しましょう。ここの様子は良くなりつつあります。あなたが新しく仕立てられた防具をかき分けて進み始めると、金属の光が目に入り、あなたの視線は光る兜に注がれます。この光るアイテムの元々の持ち主は知られていないようですが、あなたが身につけると惜しみない生命力の暖かい存在を感じます。 残念ながら彼らは名札を縫い付けませんでした。",
"questAtom3Boss": "ラウンドロマンサー",
- "questAtom3DropPotion": "Base Hatching Potion",
+ "questAtom3DropPotion": "普通のたまごがえしの薬",
"questOwlText": "闇夜のフクロウ",
"questOwlNotes": "The Tavern light is lit 'til dawn Until one eve the glow is gone! How can we see for our all-nighters? @Twitching cries, \"I need some fighters! See that Night-Owl, starry foe? Fight with haste and do not slow! We'll drive its shadow from our door, And make the night shine bright once more!\"",
"questOwlCompletion": "闇夜のフクロウは夜明け前に消えました。 あなたはあくびが出そうです。 たぶんすこし休憩をしたほうがいいのではないかな? その時、ベッドで巣を発見した! 闇夜のフクロウにとってそこは最高の場所でしょうか 仕事を終わらせるため徹夜をしました。 あなたの新しいペットが柔らかい声で鳴きます あなたにもう寝る時間だと教えてるのでしょう。",
"questOwlBoss": "闇夜のフクロウ",
"questOwlDropOwlEgg": "フクロウ(卵)",
- "questOwlUnlockText": "市場でのフクロウの卵購入のアンロック",
+ "questOwlUnlockText": "市場でのフクロウのたまご購入をアンロック",
"questPenguinText": "鳥霜",
"questPenguinNotes": "Habitica最南端の暑い夏の日だけれども、不自然な肌寒さがLively湖にやってきています。 強く寒い風が吹き荒れて岸が凍り始めます。氷が地面から突き出て草や土を押しのけます。@Melynnrose と @Breadstringsはあなたに走り寄ってきます。
「彼は怒って目にうつる全てに凍る息を使っています!」@Breadstringsが言います。「どうか私たちが氷に覆われてしまう前に彼を抑えてください!」このペンギンをクールダウンさせる必要がありそうです…。",
"questPenguinCompletion": "ペンギンを倒して氷が解け去りました。あの巨大なペンギンは日光に落ち着きを取り戻し、あなたがみつけた追加の魚をバケツから食べます。彼は湖をスケートで横切り、優しく息を吹いて、滑らかでキラキラ光る氷を作ります。なんと奇妙な鳥でしょう!「おまけに彼は卵を残していったようです」と@Painter de Clusterが言います。
\"早く!\" Lemoness があなたを呼びます。 \"出来る限り多くの触手とタスクを叩いて!次がやってこられないうちに!\"",
"questKrakenBoss": "The Kraken of Inkomplete",
"questKrakenCompletion": "巨大イカから逃れると、幾つかの卵が水面に浮いていました。それを調べたLemonessが、訝る顔をぱっと明るくしました。\n\"これはコウイカの卵よ!\"と彼女は言いました。\"あなたがやり遂げたことのご褒美として、これはもらっていきましょう。\"",
"questKrakenDropCuttlefishEgg": "コウイカ(卵)",
- "questKrakenUnlockText": "市場でのコウイカのたまご購入のアンロック",
+ "questKrakenUnlockText": "市場でのイカのたまご購入をアンロック",
"questWhaleText": "クジラの叫び",
- "questWhaleNotes": "You arrive at the Diligent Docks, hoping to take a submarine to watch the Dilatory Derby. Suddenly, a deafening bellow forces you to stop and cover your ears. \"Thar she blows!\" cries Captain @krazjega, pointing to a huge, wailing whale. \"It's not safe to send out the submarines while she's thrashing around!\"
\"Quick,\" calls @UncommonCriminal. \"Help me calm the poor creature so we can figure out why she's making all this noise!\"",
+ "questWhaleNotes": "あなたは潜水艦で”遅れがちレース”を見に行くために、勤勉波止場にやってきました。突然、耳をつんざく咆哮が響きます。あなたは足を止めて耳をふさがざるを得ませんでした。\"あそこで奴が潮を吹いた!\"そう叫ぶキャプテン @krazjegaの指す先には、泣き叫ぶ巨大なクジラの姿があります。\"奴があそこを壊しまわっている間は、安全に潜水艦にたどり着くことは無理だ!\"\n\n\"早く\" @UncommonCriminal が呼びます。”この可哀想な化物をおとなしくさせるのに手を貸して、そうすれば彼女が何故これほど騒ぎ立てるのか分かるでしょう!” ",
"questWhaleBoss": "叫ぶクジラ",
- "questWhaleCompletion": "After much hard work, the whale finally ceases her thunderous cry. \"Looks like she was drowning in waves of negative habits,\" @zoebeagle explains. \"Thanks to your consistent effort, we were able to turn the tides!\" As you step into the submarine, several whale eggs bob towards you, and you scoop them up.",
+ "questWhaleCompletion": "過酷な仕事の末、最終的にクジラは雷鳴のような叫びを止めました。\"彼女は悪い習慣の波に飲まれかかっていたようですが\"と@zoebeagle は言います。\"あなたの弛まぬ努力のおかげで、状況は一転したようです!\" あなたが潜水艦に乗り込むと、幾つかのクジラの卵があなたの方に漂ってきました。あなたはそれをすくい上げました。",
"questWhaleDropWhaleEgg": "クジラ(卵)",
- "questWhaleUnlockText": "市場でのクジラの卵購入のアンロック",
+ "questWhaleUnlockText": "市場でのクジラのたまご購入をアンロック",
"questDilatoryDistress1Text": "Dilatory Distress, Part 1: Message in a Bottle",
- "questDilatoryDistress1Notes": "A message in a bottle arrived from the newly rebuilt city of Dilatory! It reads: \"Dear Habiticans, we need your help once again. Our princess has disappeared and the city is under siege by some unknown watery demons! The mantis shrimps are holding the attackers at bay. Please aid us!\" To make the long journey to the sunken city, one must be able to breathe water. Fortunately, the alchemists @Benga and @hazel can make it all possible! You only have to find the proper ingredients.",
- "questDilatoryDistress1Completion": "You don the the finned armor and swim to Dilatory as quickly as you can. The merfolk and their mantis shrimp allies have managed to keep the monsters outside the city for the moment, but they are losing. No sooner are you within the castle walls than the horrifying siege descends!",
+ "questDilatoryDistress1Notes": "再建された都市Dilatoryから、メッセージボトルがとどきました!そこにはこう書いてあります。「親愛なるHabitica人よ、我々はあなた方の助けを再び必要としています。私達の姫君が行方不明となり、都市は正体不明の水の悪魔たちに包囲されました!今シャコ達が侵略者を入江で押しとどめています。どうか私達を助けて下さい!」 水中都市への長旅をするには、水中で呼吸できなければなりません。幸いなことに、錬金術士の@Bengaと@hazelがこれをしっかり可能にしてくれます!あなたが適切な材料を見つけてきさえすれば、ですが。",
+ "questDilatoryDistress1Completion": "あなたは水かき付きの鎧を装備し、できるだけ早くDilatoryに向かって泳ぎました。マーフォーク達と彼らのシャコ達は一時は何とか化け物たちが都市へ侵入するのを防いだようですが、今は劣勢です。あなたが城壁に入るやいなや、怖ろしい包囲軍が襲い掛かってきました!",
"questDilatoryDistress1CollectFireCoral": "火の珊瑚",
"questDilatoryDistress1CollectBlueFins": "青い鰭",
"questDilatoryDistress1DropArmor": "Finned Oceanic Armor (Armor)",
@@ -228,9 +228,9 @@
"questDilatoryDistress2RageTitle": "Swarm Respawn",
"questDilatoryDistress2RageDescription": "Swarm Respawn: This bar fills when you don't complete your Dailies. When it is full, the Water Skull Swarm will heal 30% of its remaining health!",
"questDilatoryDistress2RageEffect": "`Water Skull Swarm uses SWARM RESPAWN!`\n\nEmboldened by their victories, more skulls pour forth from the crevasse, bolstering the swarm!",
- "questDilatoryDistress2DropSkeletonPotion": "Skeleton Hatching Potion",
- "questDilatoryDistress2DropCottonCandyBluePotion": "Cotton Candy Blue Hatching Potion",
- "questDilatoryDistress2DropHeadgear": "Fire Coral Circlet (Headgear)",
+ "questDilatoryDistress2DropSkeletonPotion": "骨のたまごがえしの薬",
+ "questDilatoryDistress2DropCottonCandyBluePotion": "水色のたまごがえしの薬",
+ "questDilatoryDistress2DropHeadgear": "炎のサンゴのサークレット(頭装備)",
"questDilatoryDistress3Text": "Dilatory Distress, Part 3: Not a Mere Maid",
"questDilatoryDistress3Notes": "You follow the mantis shrimps deep into the Crevasse, and discover an underwater fortress. Princess Adva, escorted by more watery skulls, awaits you inside the main hall. \"My father has sent you, has he not? Tell him I refuse to return. I am content to stay here and practice my sorcery. Leave now, or you shall feel the wrath of the ocean's new queen!\" Adva seems very adamant, but as she speaks you notice a strange, ruby pendant on her neck glowing ominously... Perhaps her delusions would cease should you break it?",
"questDilatoryDistress3Completion": "Finally, you manage to pull the bewitched pendant from Adva's neck and throw it away. Adva clutches her head. \"Where am I? What happened here?\" After hearing your story, she frowns. \"This necklace was given to me by a strange ambassador - a lady called 'Tzina'. I don't remember anything after that!\"
Back at Dilatory, Manta is overjoyed by your success. \"Allow me to reward you with this trident and shield! I ordered them from @aiseant and @starsystemic as a gift for Adva, but... I'd rather not put weapons in her hands any time soon.\"",
@@ -243,13 +243,13 @@
"questCheetahCompletion": "The new Habitican is breathing heavily after the wild ride, but thanks you and your friends for your help. \"I'm glad that Cheetah won't be able to grab anyone else. It did leave some Cheetah eggs for us, so maybe we can raise them into more trustworthy pets!\"",
"questCheetahBoss": "チーター",
"questCheetahDropCheetahEgg": "チーター(たまご)",
- "questCheetahUnlockText": "Unlocks purchasable Cheetah eggs in the Market",
+ "questCheetahUnlockText": "市場でのチーターのたまご購入をアンロック",
"questHorseText": "Ride the Night-Mare",
"questHorseNotes": "While relaxing in the Tavern with @beffymaroo and @JessicaChase, the talk turns to good-natured boasting about your adventuring accomplishments. Proud of your deeds, and perhaps getting a bit carried away, you brag that you can tame any task around. A nearby stranger turns toward you and smiles. One eye twinkles as he invites you to prove your claim by riding his horse.\nAs you all head for the stables, @UncommonCriminal whispers, \"You may have bitten off more than you can chew. That's no horse - that's a Night-Mare!\" Looking at its stamping hooves, you begin to regret your words...",
"questHorseCompletion": "It takes all your skill, but finally the horse stamps a couple of hooves and nuzzles you in the shoulder before allowing you to mount. You ride briefly but proudly around the Tavern grounds while your friends cheer. The stranger breaks into a broad grin.\n\"I can see that was no idle boast! Your determination is truly impressive. Take these eggs to raise horses of your own, and perhaps we'll meet again one day.\" You take the eggs, the stranger tips his hat... and vanishes.",
"questHorseBoss": "Night-Mare",
"questHorseDropHorseEgg": "馬(たまご)",
- "questHorseUnlockText": "Unlocks purchasable Horse eggs in the Market",
+ "questHorseUnlockText": "市場での馬のたまご購入をアンロック",
"questBurnoutText": "Burnout and the Exhaust Spirits",
"questBurnoutNotes": "It is well past midnight, still and stiflingly hot, when Redphoenix and scout captain Kiwibot abruptly burst through the city gates. \"We need to evacuate all the wooden buildings!\" Redphoenix shouts. \"Hurry!\"
Kiwibot grips the wall as she catches her breath. \"It's draining people and turning them into Exhaust Spirits! That's why everything was delayed. That's where the missing people have gone. It's been stealing their energy!\"
\"'It'?'\" asks Lemoness.
And then the heat takes form.
It rises from the earth in a billowing, twisting mass, and the air chokes with the scent of smoke and sulphur. Flames lick across the molten ground and contort into limbs, writhing to horrific heights. Smoldering eyes snap open, and the creature lets out a deep and crackling cackle.
Kiwibot whispers a single word.
\"Burnout.\"",
"questBurnoutCompletion": "Burnout is DEFEATED!
With a great, soft sigh, Burnout slowly releases the ardent energy that was fueling its fire. As the monster curls quietly into ashes, its stolen energy shimmers through the air, rejuvenating the Exhaust Spirits and returning them to their true forms.
Ian, Daniel, and the Seasonal Sorceress cheer as Habiticans rush to greet them, and all the missing citizens of the Flourishing Fields embrace their friends and families. The final Exhaust Spirit transforms into the Joyful Reaper herself!
\"Look!\" whispers @Baconsaur, as the ashes begin to glitter. Slowly, they resolve into hundreds of shining phoenixes!
One of the glowing birds alights on the Joyful Reaper's skeletal arm, and she grins at it. \"It has been a long time since I've had the exquisite privilege to behold a phoenix in the Flourishing Fields,\" she says. \"Although given recent occurrences, I must say, this is highly thematically appropriate!\"
Her tone sobers, although (naturally) her grin remains. \"We're known for being hard-working here, but we are also known for our feasts and festivities. Rather ironic, I suppose, that as we strove to plan a spectacular party, we refused to permit ourselves any time for fun. We certainly won't make the same mistake twice!\"
She claps her hands. \"Now - let's celebrate!\"",
@@ -258,7 +258,7 @@
"questBurnoutBossRageTitle": "Exhaust Strike",
"questBurnoutBossRageDescription": "When this gauge fills, Burnout will unleash its Exhaust Strike on Habitica!",
"questBurnoutDropPhoenixPet": "不死鳥 (ペット)",
- "questBurnoutDropPhoenixMount": "不死鳥 (乗用獣)",
+ "questBurnoutDropPhoenixMount": "不死鳥 (騎獣)",
"questBurnoutBossRageQuests": "`Burnout uses EXHAUST STRIKE!`\n\nOh no! Despite our best efforts, we've let some Dailies get away from us, and now Burnout is inflamed with energy! With a crackling snarl, it engulfs Ian the Quest Master in a surge of spectral fire. As fallen quest scrolls smolder, the smoke clears, and you see that Ian has been drained of energy and turned into a drifting Exhaust Spirit!\n\nOnly defeating Burnout can break the spell and restore our beloved Quest Master. Let's keep our Dailies in check and defeat this monster before it attacks again!",
"questBurnoutBossRageSeasonalShop": "`Burnout uses EXHAUST STRIKE!`\n\nAhh!!! Our incomplete Dailies have fed the flames of Burnout, and now it has enough energy to strike again! It lets loose a gout of spectral flame that sears the Seasonal Shop. You're horrified to see that the cheery Seasonal Sorceress has been transformed into a drooping Exhaust Spirit.\n\nWe have to rescue our NPCs! Hurry, Habiticans, complete your tasks and defeat Burnout before it strikes for a third time!",
"questBurnoutBossRageTavern": "`Burnout uses EXHAUST STRIKE!`\n\nMany Habiticans have been hiding from Burnout in the Tavern, but no longer! With a screeching howl, Burnout rakes the Tavern with its white-hot hands. As the Tavern patrons flee, Daniel is caught in Burnout's grip, and transforms into an Exhaust Spirit right in front of you!\n\nThis hot-headed horror has gone on for too long. Don't give up... we're so close to vanquishing Burnout for once and for all!",
@@ -267,46 +267,58 @@
"questFrogCompletion": "The frog cowers back into the muck, defeated. As it slinks away, the blue slime fades, leaving the way ahead clear.
Sitting in the middle of the path are three pristine eggs. \"You can even see the tiny tadpoles and through the clear casing!\" @Breadstrings says. \"Here, you should take them.\"",
"questFrogBoss": "Clutter Frog",
"questFrogDropFrogEgg": "カエル(たまご)",
- "questFrogUnlockText": "Unlocks purchasable Frog eggs in the Market",
+ "questFrogUnlockText": "市場でのカエルのたまご購入をアンロック",
"questSnakeText": "The Serpent of Distraction",
"questSnakeNotes": "It takes a hardy soul to live in the Sand Dunes of Distraction. The arid desert is hardly a productive place, and the shimmering dunes have led many a traveler astray. However, something has even the locals spooked. The sands have been shifting and upturning entire villages. Residents claim a monster with an enormous serpentine body lies in wait under the sands, and they have all pooled together a reward for whomever will help them find and stop it. The much-lauded snake charmers @EmeraldOx and @PainterProphet have agreed to help you summon the beast. Can you stop the Serpent of Distraction?",
"questSnakeCompletion": "With assistance from the charmers, you banish the Serpent of Distraction. Though you were happy to help the inhabitants of the Dunes, you can't help but feel a little sad for your fallen foe. While you contemplate the sights, @LordDarkly approaches you. \"Thank you! It's not much, but I hope this can express our gratitude properly.\" He hands you some Gold and... some Snake eggs! You will see that majestic animal again after all.",
"questSnakeBoss": "Serpent of Distraction",
"questSnakeDropSnakeEgg": "蛇(たまご)",
- "questSnakeUnlockText": "Unlocks purchasable Snake eggs in the Market",
+ "questSnakeUnlockText": "市場でのヘビのたまご購入をアンロック",
"questUnicornText": "Convincing the Unicorn Queen",
"questUnicornNotes": "Conquest Creek has become muddied, destroying Habit City's fresh water supply! Luckily, @Lukreja knows an old legend that claims that a unicorn's horn can purify the foulest of waters. Together with your intrepid guide @UncommonCriminal, you hike through the frozen peaks of the Meandering Mountains. Finally, at the icy summit of Mount Habitica itself, you find the Unicorn Queen amid the glittering snows. \"Your pleas are compelling,\" she tells you. \"But first you must prove that you are worthy of my aid!\"",
"questUnicornCompletion": "Impressed by your diligence and strength, the Unicorn Queen at last agrees that your cause is worthy. She allows you to ride on her back as she soars to the source of Conquest Creek. As she lowers her golden horn to the befouled waters, a brilliant blue light rises from the water’s surface. It is so blinding that you are forced to close your eyes. When you open them a moment later, the unicorn is gone. However, @rosiesully lets out a cry of delight: the water is now clear, and three shining eggs rest at the creek’s edge.",
"questUnicornBoss": "The Unicorn Queen",
"questUnicornDropUnicornEgg": "Unicorn (Egg)",
- "questUnicornUnlockText": "Unlocks purchasable Unicorn eggs in the Market",
+ "questUnicornUnlockText": "市場でのユニコーンのたまご購入をアンロック",
"questSabretoothText": "The Sabre Cat",
"questSabretoothNotes": "A roaring monster is terrorizing Habitica! The creature stalks through the wilds and woods, then bursts forth to attack before vanishing again. It's been hunting innocent pandas and frightening the flying pigs into fleeing their pens to roost in the trees. @Inventrix and @icefelis explain that the Zombie Sabre Cat was set free while they were excavating in the ancient, untouched ice-fields of the Stoikalm Steppes. \"It was perfectly friendly at first – I don't know what happened. Please, you have to help us recapture it! Only a champion of Habitica can subdue this prehistoric beast!\"",
"questSabretoothCompletion": "After a long and tiring battle, you wrestle the Zombie Sabre Cat to the ground. As you are finally able to approach, you notice a nasty cavity in one of its sabre teeth. Realising the true cause of the cat's wrath, you're able to get the cavity filled by @Fandekasp, and advise everyone to avoid feeding their friend sweets in future. The Sabre Cat flourishes, and in gratitude, its tamers send you a generous reward – a clutch of sabretooth eggs!",
"questSabretoothBoss": "Zombie Sabre Cat",
"questSabretoothDropSabretoothEgg": "Sabretooth (Egg)",
- "questSabretoothUnlockText": "Unlocks purchasable Sabretooth eggs in the Market",
+ "questSabretoothUnlockText": "市場でのサーベルタイガーのたまご購入をアンロック",
"questMonkeyText": "Monstrous Mandrill and the Mischief Monkeys",
"questMonkeyNotes": "The Sloensteadi Savannah is being torn apart by the Monstrous Mandrill and his Mischief Monkeys! They shriek loudly enough to drown out the sound of approaching deadlines, encouraging everyone to avoid their duties and keep monkeying around. Alas, plenty of people ape this bad behavior. If no one stops these primates, soon everyone's tasks will be as red as the Monstrous Mandrill's face!
\"It will take a dedicated adventurer to resist them,\" says @yamato.
\"Quick, let's get this monkey off everyone's backs!\" @Oneironaut yells, and you charge into battle.",
"questMonkeyCompletion": "You did it! No bananas for those fiends today. Overwhelmed by your diligence, the monkeys flee in panic. \"Look,\" says @Misceo. \"They left a few eggs behind.\"
@Leephon grins. \"Maybe a well-trained pet monkey can help you as much as the wild ones hinder you!\"",
"questMonkeyBoss": "Monstrous Mandrill",
"questMonkeyDropMonkeyEgg": "サル(たまご)",
- "questMonkeyUnlockText": "Unlocks purchasable Monkey eggs in the Market",
+ "questMonkeyUnlockText": "市場でのサルのたまご購入をアンロック",
"questSnailText": "The Snail of Drudgery Sludge",
"questSnailNotes": "You're excited to begin questing in the abandoned Dungeons of Drudgery, but as soon as you enter, you feel the ground under your feet start to suck at your boots. You look up to the path ahead and see Habiticans mired in slime. @Overomega yells, \"They have too many unimportant tasks and dailies, and they're getting stuck on things that don't matter! Pull them out!\"
\"You need to find the source of the ooze,\" @Pfeffernusse agrees, \"or the tasks that they cannot accomplish will drag them down forever!\"
Pulling out your weapon, you wade through the gooey mud.... and encounter the fearsome Snail of Drudgery Sludge.",
"questSnailCompletion": "You bring your weapon down on the great Snail's shell, cracking it in two, releasing a flood of water. The slime is washed away, and the Habiticans around you rejoice. \"Look!\" says @Misceo. \"There's a small group of snail eggs in the remnants of the muck.\"",
"questSnailBoss": "Snail of Drudgery Sludge",
"questSnailDropSnailEgg": "カタツムリ(たまご)",
- "questSnailUnlockText": "Unlocks purchasable Snail eggs in the Market",
- "questBewilderText": "The Be-Wilder",
- "questBewilderNotes": "The party begins like any other.
The appetizers are excellent, the music is swinging, and even the dancing elephants have become routine. Habiticans laugh and frolic amid the overflowing floral centerpieces, happy to have a distraction from their least-favorite tasks, and the April Fool whirls among them, eagerly providing an amusing trick here and a witty twist there.
As the Mistiflying clock tower strikes midnight, the April Fool leaps onto the stage to give a speech.
“Friends! Enemies! Tolerant acquaintances! Lend me your ears.” The crowd chuckles as animal ears sprout from their heads, and they pose with their new accessories.
“As you know,” the Fool continues, “my confusing illusions usually only last a single day. But I’m pleased to announce that I’ve discovered a shortcut that will guarantee us non-stop fun, without having to deal with the pesky weight of our responsibilities. Charming Habiticans, meet my magical new friend... the Be-Wilder!”
Lemoness pales suddenly, dropping her hors d'oeuvres. “Wait! Don’t trust--”
But suddenly mists are pouring into the room, glittering and thick, and they swirl around the April Fool, coalescing into cloudy feathers and a stretching neck. The crowd is speechless as an monstrous bird unfolds before them, its wings shimmering with illusions. It lets out a horrible screeching laugh.
“Oh, it has been ages since a Habitican has been foolish enough to summon me! How wonderful it feels, to have a tangible form at last.”
Buzzing in terror, the magic bees of Mistiflying flee the floating city, which sags from the sky. One by one, the brilliant spring flowers wither up and wisp away.
“My dearest friends, why so alarmed?” crows the Be-Wilder, beating its wings. “There’s no need to toil for your rewards any more. I’ll just give you all the things that you desire!”
A rain of coins pours from the sky, hammering into the ground with brutal force, and the crowd screams and flees for cover. “Is this a joke?” Baconsaur shouts, as the gold smashes through windows and shatters roof shingles.
PainterProphet ducks as lightning bolts crackle overhead, and fog blots out the sun. “No! This time, I don’t think it is!”
Quickly, Habiticans, don’t let this World Boss distract us from our goals! Stay focused on the tasks that you need to complete so we can rescue Mistiflying -- and hopefully, ourselves.",
- "questBewilderCompletion": "The Be-Wilder is DEFEATED!
We've done it! The Be-Wilder lets out a ululating cry as it twists in the air, shedding feathers like falling rain. Slowly, gradually, it coils into a cloud of sparkling mist. As the newly-revealed sun pierces the fog, it burns away, revealing the coughing, mercifully human forms of Bailey, Matt, Alex.... and the April Fool himself.
Mistiflying is saved!
The April Fool has enough shame to look a bit sheepish. “Oh, hm,” he says. “Perhaps I got a little…. carried away.”
The crowd mutters. Sodden flowers wash up on sidewalks. Somewhere in the distance, a roof collapses with a spectacular splash.
“Er, yes,” the April Fool says. “That is. What I meant to say was, I’m dreadfully sorry.” He heaves a sigh. “I suppose it can’t all be fun and games, after all. It might not hurt to focus occasionally. Maybe I’ll get a head start on next year’s pranking.”
Redphoenix coughs meaningfully.
“I mean, get a head start on this year’s spring cleaning!” the April Fool says. “Nothing to fear, I’ll have Habit City in spit-shape soon. Luckily nobody is better than I at dual-wielding mops.”
Encouraged, the marching band starts up.
It isn’t long before all is back to normal in Habit City. Plus, now that the Be-Wilder has evaporated, the magical bees of Mistiflying bustle back to work, and soon the flowers are blooming and the city is floating once more.
As Habiticans cuddle the magical fuzzy bees, the April Fool’s eyes light up. “Oho, I’ve had a thought! Why don’t you all keep some of these fuzzy Bee Pets and Mounts? It’s a gift that perfectly symbolizes the balance between hard work and sweet rewards, if I’m going to get all boring and allegorical on you.” He winks. “Besides, they don’t have stingers! Fool’s honor.”",
- "questBewilderCompletionChat": "`The Be-Wilder is DEFEATED!`\n\nWe've done it! The Be-Wilder lets out a ululating cry as it twists in the air, shedding feathers like falling rain. Slowly, gradually, it coils into a cloud of sparkling mist. As the newly-revealed sun pierces the fog, it burns away, revealing the coughing, mercifully human forms of Bailey, Matt, Alex.... and the April Fool himself.\n\n`Mistiflying is saved!`\n\nThe April Fool has enough shame to look a bit sheepish. “Oh, hm,” he says. “Perhaps I got a little…. carried away.”\n\nThe crowd mutters. Sodden flowers wash up on sidewalks. Somewhere in the distance, a roof collapses with a spectacular splash.\n\n“Er, yes,” the April Fool says. “That is. What I meant to say was, I’m dreadfully sorry.” He heaves a sigh. “I suppose it can’t all be fun and games, after all. It might not hurt to focus occasionally. Maybe I’ll get a head start on next year’s pranking.”\n\nRedphoenix coughs meaningfully.\n\n“I mean, get a head start on this year’s spring cleaning!” the April Fool says. “Nothing to fear, I’ll have Habit City in spit-shape soon. Luckily nobody is better than I at dual-wielding mops.”\n\nEncouraged, the marching band starts up.\n\nIt isn’t long before all is back to normal in Habit City. Plus, now that the Be-Wilder has evaporated, the magical bees of Mistiflying bustle back to work, and soon the flowers are blooming and the city is floating once more.\n\nAs Habiticans cuddle the magical fuzzy bees, the April Fool’s eyes light up. “Oho, I’ve had a thought! Why don’t you all keep some of these fuzzy Bee Pets and Mounts? It’s a gift that perfectly symbolizes the balance between hard work and sweet rewards, if I’m going to get all boring and allegorical on you.” He winks. “Besides, they don’t have stingers! Fool’s honor.”",
- "questBewilderBossRageTitle": "Beguilement Strike",
- "questBewilderBossRageDescription": "When this gauge fills, The Be-Wilder will unleash its Beguilement Strike on Habitica!",
- "questBewilderDropBumblebeePet": "Bumblebee (Pet)",
- "questBewilderDropBumblebeeMount": "Bumblebee (Mount)",
- "questBewilderBossRageMarket": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nOh no! Despite our best efforts, we've gotten distracted by the Be-Wilder’s charming illusions and have forgotten to do some of our Dailies! With a cackling cry, the shining bird beats its wings, raising a swarm of mist around Alex the Merchant. When the fog clears, he has been possessed! “Have some free samples!” he shouts gleefully, and begins to hurl exploding eggs and potions at fleeing Habiticans. Not the most favorable of sales, to be sure.\n\nHurry! Let's stay focused on our Dailies to defeat this monster before it possesses someone else.",
- "questBewilderBossRageStables": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nAhh!!! Once again the Be-Wilder has dazzled us into neglecting our Dailies, and now it has attacked Matt the Beast Master! With a swirl of mist, Matt transforms into a terrifying winged creature, and all the pets and mounts howl sadly in their stables. Quickly, stay focused on your tasks to defeat this dastardly distraction!",
- "questBewilderBossRageBailey": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nLook out! In the middle of reporting the news, Bailey the Town Crier has been possessed by the Be-Wilder! She lets out an evil, uninformative screech as she rises into the air. Now how will we know what’s going on?\n\nDon't give up... we're so close to defeating this bothersome bird for once and for all!"
+ "questSnailUnlockText": "市場でのカタツムリのたまご購入をアンロック",
+ "questBewilderText": "まどわしのビ・ワイルダー",
+ "questBewilderNotes": "お祭りさわぎは、いつも同じようにはじまります。
二度としない…あなたは誓います。自分自身の To Do の山に登り、「グズグズ」の鳥をやっつけるのです!",
+ "questFalconCompletion": "ついに「グズグズ」の鳥に勝利し、あなたは腰をおろし、景色を楽しみながら疲れをいやします。
「ワオ!」と @Trogdorina 。「勝ちましたね!」と @Squish がつづきます。「私が見つけたたまごです。ほうびとして持っていってください」",
+ "questFalconBoss": "「グズグズ」の鳥",
+ "questFalconDropFalconEgg": "タカ ( たまご )",
+ "questFalconUnlockText": "市場でタカのたまごが購入できるようアンロック",
+ "questTreelingText": "The Tangle Tree",
+ "questTreelingNotes": "It's the annual Garden Competition, and everyone is talking about the mysterious project which @aurakami has promised to unveil. You join the crowd on the day of the big announcement, and marvel at the introduction of a moving tree. @fuzzytrees explains that the tree will help with garden maintenance, showing how it can mow the lawn, trim the hedge and prune the roses all at the same time – until the tree suddenly goes wild, turning its secateurs on its creator! The crowd panics as everyone tries to flee, but you aren't afraid – you leap forward, ready to do battle.",
+ "questTreelingCompletion": "You dust yourself off as the last few leaves drift to the floor. In spite of the upset, the Garden Competition is now safe – although the tree you just reduced to a heap of wood chips won't be winning any prizes! \"Still a few kinks to work out there,\" @PainterProphet says. \"Perhaps someone else would do a better job of training the saplings. Do you fancy a go?\"",
+ "questTreelingBoss": "Tangle Tree",
+ "questTreelingDropTreelingEgg": "Treeling (Egg)",
+ "questTreelingUnlockText": "Unlocks purchasable Treeling eggs in the Market"
}
\ No newline at end of file
diff --git a/common/locales/ja/rebirth.json b/common/locales/ja/rebirth.json
index 02a291352f..f934be6b5b 100644
--- a/common/locales/ja/rebirth.json
+++ b/common/locales/ja/rebirth.json
@@ -3,15 +3,15 @@
"rebirthUnlock": "「生まれかわり」の機能を解除しました! この特別なアイテムを市場で買うと、登録したタスクや実績、ペットその他はそのままにレベル1からやり直すことができます。Habitica はやりつくしたとか、はじめたばかりのキャラクターの新鮮な目で、新機能を体験したいなあと感じたら、この機能を使って、新しい命に息を吹きこんでください。",
"rebirthBegin": "転生: 新しい冒険をはじめる",
"rebirthStartOver": "生まれかわりでキャラクターをレベル 1 からやり直す。",
- "rebirthAdvList1": "あなたのHPは満タンになります。",
+ "rebirthAdvList1": "体力が満タンに回復します。",
"rebirthAdvList2": "経験値、ゴールド、所持品、すべて持っていません ( ミステリーアイテムのような無料のものを除く)。",
- "rebirthAdvList3": "習慣、日課、そして To-Do は黄色にリセットされ、連続実施記録もリセットされます。",
+ "rebirthAdvList3": "Your Habits, Dailies, and To-Dos reset to yellow, and streaks reset, except for challenge tasks.",
"rebirthAdvList4": "新しいクラスを獲得するまでは、あなたは初期の戦士クラスです。",
"rebirthInherit": "あなたの新しいキャラクターは前のキャラクターからいくつかを引き継ぎます:",
"rebirthInList1": "タスク、履歴および設定は残ります。",
"rebirthInList2": "チャレンジ、ギルド、そしてパーティー会員登録は残ります。",
"rebirthInList3": "ジェム、資金提供段位、および貢献段位は残ります。",
- "rebirthInList4": "ジェムでの購入または落し物で手に入れたアイテム (例えばペットや乗用獣など) は残りますが、再び機能を解除するまでアクセスできません。",
+ "rebirthInList4": "ジェムでの購入または落し物で手に入れたアイテム (例えばペットや騎獣など) は残りますが、再び機能を解除するまでアクセスできません。",
"rebirthInList5": "購入した限定版の装備は、該当するイベントが終了していても再購入できます。クラス特有のアイテムは、まず適切なクラスに変更しなくてはなりません。",
"rebirthEarnAchievement": "新しい冒険のはじまり の実績を解除できます!",
"beReborn": "転生する",
@@ -24,5 +24,6 @@
"rebirthPop": "実績、収集物、およびタスクと履歴を残して、レベル1 の新しいキャラクターではじめます。",
"rebirthName": "転生のオーブ",
"reborn": "転生しました、最大レベル<%= reLevel %>",
- "confirmReborn": "本当によろしいですか?"
+ "confirmReborn": "本当によろしいですか?",
+ "rebirthComplete": "You have been reborn!"
}
\ No newline at end of file
diff --git a/common/locales/ja/settings.json b/common/locales/ja/settings.json
index 9a612fe0ae..a3acacd9c5 100644
--- a/common/locales/ja/settings.json
+++ b/common/locales/ja/settings.json
@@ -18,14 +18,14 @@
"dontShowAgain": "次回、これを表示しない",
"suppressLevelUpModal": "レベルが上がったときにポップアップ表示しない",
"suppressHatchPetModal": "ペットがかえったときにポップアップ表示しない",
- "suppressRaisePetModal": "ペットが乗用獣になったときにポップアップ表示しない",
+ "suppressRaisePetModal": "ペットが騎獣になったときにポップアップ表示しない",
"suppressStreakModal": "連続実行 実績を達成したときにポップアップ表示しない",
"showTour": "ツアーを表示する。",
"restartTour": "Habitica 開始時に見たガイドを再度スタートする",
"showBailey": "ベイリーを表示する",
"showBaileyPop": "隠れている、広め屋・ベイリーを表示して、過去のニュースを見直す。",
"fixVal": "キャラクター設定値を直す",
- "fixValPop": "手動で健康、レベル、ゴールドのような値を変更します。",
+ "fixValPop": "手動で体力、レベル、ゴールドのような値を変更します。",
"enableClass": "クラスシステムを有効にする",
"enableClassPop": "初期設定では、クラスシステムは無効に設定されています。有効にしますか?",
"classTourPop": "クラス・システムのツアーを表示する",
@@ -47,6 +47,7 @@
"customDayStart": "日付更新の設定",
"changeCustomDayStart": "日付更新する時間を変更しますか?",
"sureChangeCustomDayStart": "日付更新する時間を変更します。よろしいですか?",
+ "customDayStartHasChanged": "Your custom day start has changed.",
"nextCron": "あなたの日課が次に更新されるのは <%= time %> です。この時間までに日課を片付けておきましょう!",
"customDayStartInfo1": "Habitica の標準設定では、日課は、毎日あなたのタイム ゾーンでの深夜に更新されます。その時間をカスタマイズします。",
"misc": "その他",
@@ -61,12 +62,23 @@
"newUsername": "新しいログイン名",
"dangerZone": "危険地帯",
"resetText1": "警告‼ この操作で、あなたのアカウントの多くの部分をリセットします。とてもがっかりすることです。しかし、お試しで短時間プレーした初心者ユーザーの一部には便利だと思われます。",
- "resetText2": "すべてのレベル、ゴールド、経験値を失います。すべてのタスクは完全に削除され、タスクの履歴データをすべて失います。すべての所持品を失うことになりますが、限定版の装備やミステリー アイテムをふくめすべて買い戻すことができます (クラス固有のギアを再購入するには適用クラスになる必要があります)。現在のクラス、ペットと乗用獣はそのままです。「生まれかわりのオーブ」を使う方が、ずっと安全でタスクも残りますので、ご検討ください。",
+ "resetText2": "You will lose all your levels, gold, and experience points. All your tasks (except those from challenges) will be deleted permanently and you will lose all of their historical data. You will lose all your equipment but you will be able to buy it all back, including all limited edition equipment or subscriber Mystery items that you already own (you will need to be in the correct class to re-buy class-specific gear). You will keep your current class and your pets and mounts. You might prefer to use an Orb of Rebirth instead, which is a much safer option and which will preserve your tasks.",
"deleteText": "本当にいいのですか? あなたのアカウントは削除され、復元はできません! もう一度 Habitica を使うには、新しいアカウントを登録しなくてはなりません。取得したジェム、使ったジェムは、返金できません。本当に間違いないのなら、下のテキストボックスに <%= deleteWord %> と入力してください。",
"API": "API",
+ "APIv3": "API v3",
+ "APIv2": "API v2 - Deprecated",
"APIText": "サードパーティのアプリケーションで使用する際にこちらをコピーしてください。しかし、APIトークンはパスワードのようなもので、公開しないでください。ユーザーIDを求められる場合はありますが、GitHub など他人から見える場所には、APIトークンは絶対に投稿しないでください。",
"APIToken": "API トークン ( これはあなたのパスワードです。 上記の注意をよく読んでください! )",
+ "thirdPartyApps": "サードパーティー アプリ",
+ "dataToolDesc": "あなたの Habitica のアカウントから、タスク、所持品、特殊能力の統計といった正確な情報を表示するWeb ページです。",
+ "beeminder": "Beeminder",
+ "beeminderDesc": "Beeminder にあなたの Habitica の To-Do を自動監視させます。日ごと、週ごとの To-Do の達成数目標を管理する契約 (Commit) をしたり、また徐々に未達成の To-Do を減らしていく契約ができます。( Beeminder における 「commit」は、現実のお金を支払わなければならない恐れを意味します! しかし、Beeminder の美しいグラフは、お気に召すことでしょう。)",
+ "chromeChatExtension": "チャット Chrome 拡張機能",
+ "chromeChatExtensionDesc": "Habitica チャット Chrome 拡張機能は、habitica.com ",
+ "otherExtensions": "Other Extensions",
+ "otherDesc": "Habitica wiki でその他のアプリや拡張機能、またはツールを見つけましょう。",
"resetDo": "はい。アカウントをリセットします!",
+ "resetComplete": "Reset complete!",
"fixValues": "設定値を直す",
"fixValuesText1": "バグに遭遇する、または(受けるべきでないダメージを受けた、実際には稼いでいないゴールドなど)不正にキャラクターを変更するようなミスを犯してしまった場合、ここでその数字を修正することができます。そう、これでズルをすることもできます : この機能は良心に従って使用してください。さもないと、あなたの実生活における習慣づけを放棄することになってしまいます。",
"fixValuesText2": "付記 : 個々のタスクの連続実行を復元することはできません。復元するには、日課の編集画面の拡張設定に、連続実行を復元する欄があります。",
@@ -96,6 +108,7 @@
"emailNotifications": "メール通知",
"wonChallenge": "あなたはチャレンジに成功しました!",
"newPM": "プライベートメッセージが届きました",
+ "sentGems": "Sent gems!",
"giftedGems": "贈られたジェム",
"giftedGemsInfo": "<%= name %> より <%= amount %> 個のジェム",
"giftedSubscription": "贈られた寄付(有料利用)",
@@ -135,6 +148,11 @@
"webhooks": "Webhook",
"enabled": "有効",
"webhookURL": "Webhook URL",
+ "invalidUrl": "invalid url",
+ "invalidEnabled": "the \"enabled\" parameter should be a boolean",
+ "regIdRequired": "RegId is required",
+ "pushDeviceAdded": "Push device added successfully",
+ "pushDeviceAlreadyAdded": "The user already has the push device",
"add": "追加する",
"buyGemsGoldCap": "購入可能数が <%= amount %> 個になりました",
"mysticHourglass": "<%= amount %> 個の「神秘の砂時計」",
@@ -149,5 +167,5 @@
"amazonPayments": "Amazon ペイメント",
"timezone": "タイム ゾーン",
"timezoneUTC": "Habitica はお使いのPCに設定されたタイム ゾーンを利用します。現在の設定 : <%= utc %>",
- "timezoneInfo": "タイム ゾーンが間違っているなら、まずブラウザの「リロード」もしくは「リフレッシュ」ボタンを使って Habitica の最新状態を取得してください。それでも間違っているなら、あなたのPC または 携帯端末のタイム ゾーンを調整し、再度ページをリロードしてください。
Habitica を別のPCや携帯端末でも使っているなら、すべて同じタイムゾーンを設定しなくてはなりません。 日課が間違った時間に更新されるなら、このチェックをすべてのPCや携帯端末でくり返してください。"
+ "timezoneInfo": "If that time zone is wrong, first reload this page using your browser's reload or refresh button to ensure that Habitica has the most recent information. If it is still wrong, adjust the time zone on your PC and then reload this page again.
If you use Habitica on other PCs or mobile devices, the time zone must be the same on them all. If your Dailies have been resetting at the wrong time, repeat this check on all other PCs and on a browser on your mobile devices."
}
\ No newline at end of file
diff --git a/common/locales/ja/spells.json b/common/locales/ja/spells.json
index d4fddd4b73..8ac178ea14 100644
--- a/common/locales/ja/spells.json
+++ b/common/locales/ja/spells.json
@@ -1,42 +1,42 @@
{
"spellWizardFireballText": "火炎爆破",
- "spellWizardFireballNotes": "炎が手から吹き出します。XPを手に入れ、ボスにさらにダメージを与えます!タスクをクリックすることで唱えます。(知性に基づく)",
+ "spellWizardFireballNotes": "炎が手から吹き出します。経験値を手に入れ、ボスに追加のダメージを与えます! 呪文をかけるタスクをクリックしてください。(基準 : 知性)",
"spellWizardMPHealText": "エーテル波動",
- "spellWizardMPHealNotes": "友人を助けるために、あなたのマナを犠牲にします。残りのパーティーのメンバー党は、MPを得ます!(知性に基づく)",
+ "spellWizardMPHealNotes": "あなたのマナを犠牲にして仲間を助けます。残りのパーティーのメンバーのマナが増えます! (基準 : 知能)",
"spellWizardEarthText": "地震",
- "spellWizardEarthNotes": "あなたの精神の力が地面を揺らします。パーティー全員が知性を得ます。(知性に基づく)",
+ "spellWizardEarthNotes": "精神の力で大地を揺らします。パーティー全員の知能に勢いボーナスがつきます! ( 基準 : 勢いなしの知能 )",
"spellWizardFrostText": "酷寒の霜",
- "spellWizardFrostNotes": "氷があなたのタスクを覆う。あなたの連続実行のいずれも明日ゼロにリセットされない! (一度唱えると全ての連続実行に影響する)",
+ "spellWizardFrostNotes": "氷があなたのタスクを覆う。明日はどの日課も連続実行がゼロにリセットされない! (1回唱えるとすべての連続実行タスクに効果を発する)",
"spellWarriorSmashText": "強烈なスマッシュ",
- "spellWarriorSmashNotes": "全力でタスクを攻撃する。タスクはより青く、もしくは赤が薄くなり、ボスに追加ダメージを与える! 唱える対象のタスクをクリックしてください。(力に基づく)",
+ "spellWarriorSmashNotes": "全力でタスクを攻撃する。タスクはより青く、もしくは赤が薄くなり、ボスに追加ダメージを与える! 唱える対象のタスクをクリックしてください。(基準 : 力)",
"spellWarriorDefensiveStanceText": "守勢の体勢",
- "spellWarriorDefensiveStanceNotes": "攻め寄るタスクの覚悟をする。体質のバフを獲得!(バフ抜き体質に基づく)",
+ "spellWarriorDefensiveStanceNotes": "攻め寄るタスクに覚悟を決めます。性格に勢いボーナスがつきます! ( 基準 : 勢いなしの性格値 )",
"spellWarriorValorousPresenceText": "勇烈な貫禄",
- "spellWarriorValorousPresenceNotes": "あなたの存在はパーティに自信を与える。パーティの全員は力のバフを獲得! (バフ抜き力に基づく)",
+ "spellWarriorValorousPresenceNotes": "あなたの存在はパーティに自信を与えます。パーティの全員の力に勢いボーナスがつきます! ( 基準 : 勢いなしの力値 )",
"spellWarriorIntimidateText": "威嚇的な視線",
- "spellWarriorIntimidateNotes": "視線で敵に恐怖を叩き込む。パーティ全員は体質のバフを獲得! (バフ抜き体質に基づく)",
+ "spellWarriorIntimidateNotes": "あなたのにらみが敵に恐怖をたたきこみます。パーティ全員の性格に勢いのボーナスがつきます! ( 基準 : 勢いなしの性格値 )",
"spellRoguePickPocketText": "スリ",
- "spellRoguePickPocketNotes": "近くのタスクをかすめる。ゴールドを獲得!唱える対象のタスクをクリックしてください。(知覚に基づく)",
- "spellRogueBackStabText": "バックスタッブ",
- "spellRogueBackStabNotes": "愚かなタスクを裏切る。ゴールドとEXPを獲得!唱える対象のタスクをクリックしてください。(力に基づく)",
+ "spellRoguePickPocketNotes": "近くのタスクにスリをはたらきます。ゴールドを獲得! 唱える対象のタスクをクリックしてください。(基準 : 知覚)",
+ "spellRogueBackStabText": "背後から一突き",
+ "spellRogueBackStabNotes": "愚かなタスクを裏切ります。ゴールドと経験値を獲得! 唱える対象のタスクをクリックしてください。(基準 : 力)",
"spellRogueToolsOfTradeText": "商売道具",
- "spellRogueToolsOfTradeNotes": "能力を仲間たちと共有する。パーティ全員は知覚のバフを獲得! (バフ抜き体質に基づく)",
+ "spellRogueToolsOfTradeNotes": "あなたの才能を仲間たちと共有します。パーティ全員の知覚に勢いボーナスがつきます! ( 基準 : 勢いなしの知覚値 )",
"spellRogueStealthText": "ステルス",
"spellRogueStealthNotes": "忍びすぎて見当たらない!未完成いくつかの日課は今夜ダメージを与えない上、連続実行・色も変わらない。(唱える回数による影響される日課が増える。)",
"spellHealerHealText": "ヒール",
- "spellHealerHealNotes": "体は怪我を癒す光に浴びる。元気を回復する! (体質と知性に基づく)",
- "spellHealerBrightnessText": "焼けるように輝度",
- "spellHealerBrightnessNotes": "あなたはすべてのタスクを隠す光のバーストを投げかけます。あなたのタスクの赤みが軽減されます。(知性に基づく)",
- "spellHealerProtectAuraText": "防護のオーラ",
- "spellHealerProtectAuraNotes": "ダメージからパーティを守る。パーティの全員は体質のバフを獲得! (バフ抜き体質に基づく)",
- "spellHealerHealAllText": "恵み",
- "spellHealerHealAllNotes": "癒すオーラに囲める。パーティ全員は元気を回復する! (体質と知性に基づく)",
+ "spellHealerHealNotes": "光があなたの体をつつみ、あなたの傷をいやします。体力を回復します! (基準 : 性格と知能)",
+ "spellHealerBrightnessText": "焼けるような輝き",
+ "spellHealerBrightnessNotes": "飛び出した光がタスクを隠します。あなたのタスクがより青く、または赤みが軽減されます。(基準 : 知能 )",
+ "spellHealerProtectAuraText": "守りのオーラ",
+ "spellHealerProtectAuraNotes": "ダメージからパーティを守ります。パーティの全員の性格に勢いのボーナスがつきます! ( 基準 : 勢いなしの性格値 )",
+ "spellHealerHealAllText": "おまじない",
+ "spellHealerHealAllNotes": "なだめるようなオーラがあなたのパーティーを包みこみます。パーティーのメンバーの体力を回復します。( 基準 : 性格と知能 )",
"spellSpecialSnowballAuraText": "雪玉",
- "spellSpecialSnowballAuraNotes": "パーティメンバーに雪玉を投げろ!何も問題ないだろう?メンバーの翌日まで効果が続く。",
+ "spellSpecialSnowballAuraNotes": "パーティーの仲間に雪玉を投げましょう! 何か問題でも? 翌日まで効果がつづきます。",
"spellSpecialSaltText": "塩",
- "spellSpecialSaltNotes": "雪玉に殴られた。ハハ、とても面白い。誰かこの雪を拭いてくれ!",
- "spellSpecialSpookDustText": "不気味なきらめき",
- "spellSpecialSpookDustNotes": "友達を目が付いた空飛ぶ毛布に変えよう!",
+ "spellSpecialSaltNotes": "だれかに雪玉を投げつけられた。ハハ、とてもおもしろい。だれかこの雪を拭いてくれ!",
+ "spellSpecialSpookySparklesText": "Spooky Sparkles",
+ "spellSpecialSpookySparklesNotes": "Turn a friend into a floating blanket with eyes!",
"spellSpecialOpaquePotionText": "不透明ポーション",
"spellSpecialOpaquePotionNotes": "不気味なきらめきの効果を取り消す。",
"spellSpecialShinySeedText": "輝く種",
@@ -46,5 +46,11 @@
"spellSpecialSeafoamText": "海の泡",
"spellSpecialSeafoamNotes": "友達を海の生き物に変えよう!",
"spellSpecialSandText": "砂",
- "spellSpecialSandNotes": "海の泡の効果を取り消す。"
+ "spellSpecialSandNotes": "海の泡の効果を取り消す。",
+ "spellNotFound": "Skill \"<%= spellId %>\" not found.",
+ "partyNotFound": "Party not found",
+ "targetIdUUID": "\"targetId\" must be a valid User ID.",
+ "challengeTasksNoCast": "Casting a skill on challenge tasks is not allowed.",
+ "spellNotOwned": "You don't own this skill.",
+ "spellLevelTooHigh": "You must be level <%= level %> to use this skill."
}
\ No newline at end of file
diff --git a/common/locales/ja/subscriber.json b/common/locales/ja/subscriber.json
index 9b5737e66b..53a3090d90 100644
--- a/common/locales/ja/subscriber.json
+++ b/common/locales/ja/subscriber.json
@@ -4,6 +4,8 @@
"subDescription": "ゴールドでジェムを買いましょう。毎月ミステリー アイテムが入手でき、進行の履歴が残り、毎日のアイテムが落ちる確率が倍になり、開発者を支援できます。詳細はクリックで。",
"buyGemsGold": "ゴールドでジェムを購入",
"buyGemsGoldText": "商人・アレクサンダーが、ジェム1個を<%= gemCost %>ゴールドで売ってくれます。アレクサンダーと1ヵ月の間に取り引きできるジェムの数は、最初は<%= gemLimit %>個に限定されています。しかし、寄付の継続3カ月ごとにその数が5個ずつ増えていき、最大で月に50個を買うことができます。",
+ "mustSubscribeToPurchaseGems": "Must subscribe to purchase gems with GP",
+ "reachedGoldToGemCap": "You've reached the Gold=>Gem conversion cap <%= convCap %> for this month. We have this to prevent abuse / farming. The cap will reset within the first three days of next month.",
"retainHistory": "詳細な履歴の保存",
"retainHistoryText": "完了した To-Do やタスクの履歴を、より長期間閲覧可能になります。",
"doubleDrops": "毎日のアイテムが落ちる確率が2倍",
@@ -29,6 +31,7 @@
"manageSub": "寄付を管理",
"cancelSub": "寄付の中止",
"canceledSubscription": "中止した寄付",
+ "cancelingSubscription": "Canceling the subscription",
"adminSub": "管理者の寄付",
"morePlans": "その他のプラン 近日中",
"organizationSub": "プライベートな組織",
@@ -69,10 +72,13 @@
"timeTravelers": "タイムトラベラー",
"timeTravelersTitleNoSub": "<%= linkStartTyler %>タイラー<%= linkEnd %>と<%= linkStartVicky %>ビッキー<%= linkEnd %>",
"timeTravelersTitle": "謎のタイムトラベラーズ",
- "timeTravelersPopoverNoSub": "謎のタイムトラベラーを呼び出すには、神秘の砂時計が必要です! <%= linkStart %>寄付会員<%= linkEnd %>には寄付の継続3ヵ月ごとに神秘の砂時計1つが与えられます。神秘の砂時計を手に入れたら戻ってきてください。タイム トラベラーが貴重なペットや乗用獣、寄付会員むけの過去の、そしてもしかしたら未来のアイテムを取ってきてくれるでしょう。",
- "timeTravelersPopover": "「神秘の砂時計」をお持ちだね? 私たちは喜んで、あなたを過去に連れていってあげよう! ペット、乗用獣、もしくはミステリー アイテムから好きなものを選ぶといい。過去のアイテム セットの一覧は ここで見られるぞ! もし気に入らないなら、私たちのイケてる未来的なスチームパンク セットは気に入るんじゃないか? ",
+ "timeTravelersPopoverNoSub": "謎のタイムトラベラーを呼び出すには、神秘の砂時計が必要です! <%= linkStart %>寄付会員<%= linkEnd %>には寄付の継続3ヵ月ごとに神秘の砂時計1つが与えられます。神秘の砂時計を手に入れたら戻ってきてください。タイム トラベラーが貴重なペットや騎獣、寄付会員むけの過去の、そしてもしかしたら未来のアイテムを取ってきてくれるでしょう。",
+ "timeTravelersPopover": "「神秘の砂時計」をお持ちだね? 私たちは喜んで、あなたを過去に連れていってあげよう! ペット、騎獣、もしくはミステリー アイテムから好きなものを選ぶといい。過去のアイテム セットの一覧は ここで見られるぞ! もし気に入らないなら、私たちのイケてる未来的なスチームパンク セットは気に入るんじゃないか? ",
"timeTravelersAlreadyOwned": "おめでとうございます! タイムトラベラーが現在用意しているすべてのアイテムを手に入れました。サイトへのご支援に感謝します!",
"mysticHourglassPopover": "「神秘の砂時計」は、毎月のミステリー アイテム セットや、世界のボスをやっつけたときの賞金といった特定の期間限定アイテムの過去の分を購入できます。",
+ "mysterySetNotFound": "Mystery set not found, or set already owned.",
+ "mysteryItemIsEmpty": "Mystery items are empty",
+ "mysteryItemOpened": "Mystery item opened.",
"mysterySet201402": "羽のあるメッセンジャー セット",
"mysterySet201403": "森の散策者 セット",
"mysterySet201404": "夕暮れのちょうちょ セット",
@@ -99,6 +105,8 @@
"mysterySet201601": "解決チャンピオン セット",
"mysterySet201602": "夢やぶれて セット",
"mysterySet201603": "幸運のクローバー セット",
+ "mysterySet201604": "Leaf Warrior Set",
+ "mysterySet201605": "Marching Bard Set",
"mysterySet301404": "スチームパンク標準 セット",
"mysterySet301405": "スチームパンク アクセサリー セット",
"mysterySetwondercon": "Wondercon イベント",
@@ -109,10 +117,26 @@
"hourglassBuyEquipSetConfirm": "神秘の砂時計 1 個で、このアイテム フルセットを買いますか?",
"hourglassBuyItemConfirm": "神秘の砂時計 1 個で、このアイテムを買いますか?",
"petsAlreadyOwned": "このペットはすでにもっています。",
- "mountsAlreadyOwned": "この乗用獣はすでにもっています。",
- "typeNotAllowedHourglass": "神秘の砂時計で買えない種類のアイテムです。買える種類 : ",
+ "mountsAlreadyOwned": "この騎獣はすでにもっています。",
+ "typeNotAllowedHourglass": "Item type not supported for purchase with Mystic Hourglass. Allowed types: <%= allowedTypes %>",
"petsNotAllowedHourglass": "ペットは神秘の砂時計では買えません。",
- "mountsNotAllowedHourglass": "神秘の砂時計で乗用獣は買えません。",
+ "mountsNotAllowedHourglass": "神秘の砂時計で騎獣は買えません。",
"hourglassPurchase": "神秘の砂時計で、アイテムを買いました!",
- "hourglassPurchaseSet": "神秘の砂時計で、アイテム セットを買いました!"
+ "hourglassPurchaseSet": "神秘の砂時計で、アイテム セットを買いました!",
+ "missingUnsubscriptionCode": "Missing unsubscription code.",
+ "missingSubscription": "User does not have a plan subscription",
+ "missingSubscriptionCode": "Missing subscription code. Possible values: basic_earned, basic_3mo, basic_6mo, google_6mo, basic_12mo.",
+ "cannotDeleteActiveAccount": "You have an active subscription, cancel your plan before deleting your account.",
+ "paymentNotSuccessful": "The payment was not successful",
+ "planNotActive": "The plan hasn't activated yet (due to a PayPal bug). It will begin <%= nextBillingDate %>, after which you can cancel to retain your full benefits",
+ "notAllowedHourglass": "Pet/Mount not available for purchase with Mystic Hourglass.",
+ "readCard": "<%= cardType %> has been read",
+ "cardTypeRequired": "Card type required",
+ "cardTypeNotAllowed": "Unknown card type.",
+ "invalidCoupon": "Invalid coupon code.",
+ "couponUsed": "Coupon code already used.",
+ "noSudoAccess": "You don't have sudo access.",
+ "couponCodeRequired": "The coupon code is required.",
+ "eventRequired": "\"req.params.event\" is required.",
+ "countRequired": "\"req.query.count\" is required."
}
\ No newline at end of file
diff --git a/common/locales/ja/tasks.json b/common/locales/ja/tasks.json
index 9ca615b71d..62cddd866f 100644
--- a/common/locales/ja/tasks.json
+++ b/common/locales/ja/tasks.json
@@ -73,6 +73,7 @@
"clearTags": "クリア",
"hideTags": "非表示にする",
"showTags": "表示する",
+ "toRequired": "You must supply a to value",
"startDate": "開始日",
"startDateHelpTitle": "このタスクはいつ始める?",
"startDateHelp": "タスクが効果を発する開始日を設定します。ただし、過去の日付を設定することができません。",
@@ -81,15 +82,16 @@
"streakSingular": "連続実行者",
"streakSingularText": "日課を21日連続で実行しました",
"perfectName": "パーフェクトな日",
- "perfectText": "やるべき日課すべてを<%=perfects %>日間、達成しました。この実績により、明日の能力値すべてに「レベル÷2」のボーナスがつきます(レベル100以上の場合はボーナスがつきません)。",
+ "perfectText": "<%=perfects %>日間連続で、やるべき日課のすべてを完了しています。この実績により次の日の全ての能力値に対して「レベル÷2」のボーナスが与えられます。レベルが100を超えると、ボーナスの効果はありません。",
"perfectSingular": "パーフェクトな日",
- "perfectSingularText": "やるべき日課をすべて完了しました。この実績により、明日の能力値すべてに「レベル÷2」のボーナスがつきます(レベル100以上の場合はボーナスがつきません)。",
+ "perfectSingularText": "今日はやるべき日課をすべて完了しました。この実績により次の日の全ての能力値に対して「レベル÷2」のボーナスが付与されます。レベルが100を超えると、それ以上バフの効果は上がりません。",
"streakerAchievement": "あなたは「連続実行」の実績を手に入れました! 21日間の連続記録は習慣形成における一つの節目です。この21日連続実行の実績を胸に、今日の日課およびほかのタスクにもとりみつづけましょう!",
"fortifyName": "防御の薬",
- "fortifyPop": "全てのタスクを通常の状態 (黄色)に戻します。加えて全ての体力を回復させます。",
+ "fortifyPop": "すべてのタスクを中間の状態 (黄色)に戻し、体力をすべて回復させます。",
"fortify": "防御",
- "fortifyText": "防御は、いままさに追加したようにタスクを通常(黄色)の状態に戻し、体力を満タンにします。赤いタスクがゲームを困難にしすぎていた場合、もしくは青いタスクがゲームを簡単にしすぎていた場合に、これはとても効果的です。はじまり、という響きにはやる気が出るものです。ジェムを使って、救済措置を手に入れましょう!",
+ "fortifyText": "Fortify will return all your tasks, except challenge tasks, to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!",
"confirmFortify": "本当によろしいですか?",
+ "fortifyComplete": "Fortify complete!",
"sureDelete": "「<%= taskText %>」の<%= taskType %>を削除して、本当によろしいですか?",
"streakCoins": "連続実行ボーナス!",
"pushTaskToTop": "タスクを一番上に表示します。[ctrl] もしくは [cmd]キーを押しながらクリックすると一番下へ。",
@@ -105,12 +107,25 @@
"dailyHelp4": "何時に日付が進むのかを変更するには、 「<%= linkStart %> 設定 > サイト<%= linkEnd %> > 日付更新の設定 」で操作してください。",
"dailyHelp5": "アイディアを得るために、 日課のサンプルをクリックして下さい!",
"toDoHelp1": "To-Doは黄色からはじまり、完了できない期間が長くなるとより赤く(色が濃く)なります。",
- "toDoHelp2": "To-DoはHPを減少させません。コインや経験値を獲るだけです。",
+ "toDoHelp2": "To-Do は体力を減らしません! 達成してゴールドや経験値を得るためのものです。",
"toDoHelp3": "To-Doは細かく分割してチェックリストにすることで、おそれは緩和され、獲られるポイントも増加します。",
"toDoHelp4": "参考のために、To-Do のサンプルをチェックしてみてください!",
"rewardHelp1": "アバターのために購入した装備は、<%= linkStart %>所持品 > 装備<%= linkEnd %>に保存されます。",
"rewardHelp2": "装備は、ステータスに効果を与えます。(<%= linkStart %>アバター > ステータス<%= linkEnd %>)",
"rewardHelp3": "世界的なイベントの期間中、スペシャルな装備が出現します。",
"rewardHelp4": "カスタム報酬を設定することを恐れないで下さい!サンプルはここにあるのでクリックして下さい。",
- "clickForHelp": "ヘルプを見るにはここをクリックして下さい"
+ "clickForHelp": "ヘルプを見るにはここをクリックして下さい",
+ "taskIdRequired": "\"taskId\" must be a valid UUID.",
+ "taskNotFound": "Task not found.",
+ "invalidTaskType": "Task type must be one of \"habit\", \"daily\", \"todo\", \"reward\".",
+ "cantDeleteChallengeTasks": "A task belonging to a challenge can't be deleted.",
+ "checklistOnlyDailyTodo": "Checklists are supported only on dailies and todos",
+ "checklistItemNotFound": "No checklist item was found with given id.",
+ "itemIdRequired": "\"itemId\" must be a valid UUID.",
+ "tagNotFound": "No tag item was found with given id.",
+ "tagIdRequired": "\"tagId\" must be a valid UUID corresponding to a tag belonging to the user.",
+ "positionRequired": "\"position\" is required and must be a number.",
+ "cantMoveCompletedTodo": "Can't move a completed todo.",
+ "directionUpDown": "\"direction\" is required and must be 'up' or 'down'",
+ "alreadyTagged": "The task is already tagged with given tag."
}
\ No newline at end of file
diff --git a/common/locales/nl/backgrounds.json b/common/locales/nl/backgrounds.json
index 08e7221dc8..33eaca699e 100644
--- a/common/locales/nl/backgrounds.json
+++ b/common/locales/nl/backgrounds.json
@@ -154,11 +154,18 @@
"backgroundRainforestNotes": "Trek een Regenwoud in.",
"backgroundStoneCircleText": "Cirkel van stenen",
"backgroundStoneCircleNotes": "Spreek betoveringen uit in een Cirkel van stenen",
- "backgrounds042016": "SET 23: Released April 2016",
- "backgroundArcheryRangeText": "Archery Range",
- "backgroundArcheryRangeNotes": "Practice on the Archery Range.",
- "backgroundGiantFlowersText": "Giant Flowers",
- "backgroundGiantFlowersNotes": "Frolic atop Giant Flowers.",
- "backgroundRainbowsEndText": "End of the Rainbow",
- "backgroundRainbowsEndNotes": "Discover gold at the End of the Rainbow."
+ "backgrounds042016": "SET 23: Uitgebracht in April 2016",
+ "backgroundArcheryRangeText": "Boogschietbaan",
+ "backgroundArcheryRangeNotes": "Oefen op de boogschietbaan",
+ "backgroundGiantFlowersText": "Reusachtige bloemen",
+ "backgroundGiantFlowersNotes": "Dartel op Reusachtige Bloemen",
+ "backgroundRainbowsEndText": "Einde van de regenboog",
+ "backgroundRainbowsEndNotes": "Vind goud aan het Einde van de Regenboog",
+ "backgrounds052016": "SET 24: Uitgebracht in februari 2016",
+ "backgroundBeehiveText": "Bijenkorf",
+ "backgroundBeehiveNotes": "Zoem en dans in een Bijenkorf",
+ "backgroundGazeboText": "Prieel",
+ "backgroundGazeboNotes": "Battle a Gazebo.",
+ "backgroundTreeRootsText": "Boomwortels",
+ "backgroundTreeRootsNotes": "Ontdek de Boomwortels"
}
\ No newline at end of file
diff --git a/common/locales/nl/challenge.json b/common/locales/nl/challenge.json
index 8d5046e7cc..32195e8346 100644
--- a/common/locales/nl/challenge.json
+++ b/common/locales/nl/challenge.json
@@ -63,5 +63,21 @@
"congratulations": "Gefeliciteerd!",
"hurray": "Hoera!",
"noChallengeOwner": "geen eigenaar",
- "noChallengeOwnerPopover": "Deze uitdaging heeft geen eigenaar omdat de eigenaar zijn of haar account heeft verwijderd."
+ "noChallengeOwnerPopover": "Deze uitdaging heeft geen eigenaar omdat de eigenaar zijn of haar account heeft verwijderd.",
+ "challengeMemberNotFound": "Gebruiker niet gevonden onder deelnemers aan de uitdaging",
+ "onlyGroupLeaderChal": "Alleen de groepsleider kan uitdagingen creëren",
+ "tavChalsMinPrize": "Prijs moet ten minste 1 Juweel zijn voor Herberg uitdagingen",
+ "cantAfford": "You can't afford this prize. Purchase more gems or lower the prize amount.",
+ "challengeIdRequired": "\"challengeId\" must be a valid UUID.",
+ "winnerIdRequired": "\"winnerId\" must be a valid UUID.",
+ "challengeNotFound": "Uitdaging niet gevonden",
+ "onlyLeaderDeleteChal": "Alleen de uitdagingsleider kan het verwijderen.",
+ "onlyLeaderUpdateChal": "Alleen de uitdagingsleider kan het updaten.",
+ "winnerNotFound": "Winner with id \"<%= userId %>\" not found or not part of the challenge.",
+ "noCompletedTodosChallenge": "\"includeComepletedTodos\" is not supported when fetching challenge tasks.",
+ "userTasksNoChallengeId": "When \"tasksOwner\" is \"user\" \"challengeId\" can't be passed.",
+ "onlyChalLeaderEditTasks": "Tasks belonging to a challenge can only be edited by the leader.",
+ "userAlreadyInChallenge": "User is already participating in this challenge.",
+ "cantOnlyUnlinkChalTask": "Only broken challenges tasks can be unlinked.",
+ "shortNameTooShort": "Tag Name must have at least 3 characters."
}
\ No newline at end of file
diff --git a/common/locales/nl/character.json b/common/locales/nl/character.json
index 72a0582cf0..fcb9c02e1a 100644
--- a/common/locales/nl/character.json
+++ b/common/locales/nl/character.json
@@ -1,4 +1,5 @@
{
+ "communityGuidelinesWarning": "Houd er rekening mee dat je Zichbare Naam, profielfoto en flaptekst overeen moeten komen met de Community Guidelines (bijvoorbeeld geen godslastering, geen volwassen onderwerpen, geen beledigingen, etc.). Als je vragen hebt over het wel of niet geschikt zijn van iets, voel je dan vrij om te emailen naar leslie@habitica.com!",
"statsAch": "Statistieken en prestaties",
"profile": "Profiel",
"avatar": "Avatar aanpassen",
@@ -34,7 +35,7 @@
"beard": "Baard",
"mustache": "Snor",
"flower": "Bloem",
- "wheelchair": "Wheelchair",
+ "wheelchair": "Rolstoel",
"basicSkins": "Basishuidskleuren",
"rainbowSkins": "Regenbooghuidskleuren",
"pastelSkins": "Pastelhuidskleuren",
@@ -109,6 +110,7 @@
"mage": "Magiër",
"mystery": "Verrassingsartikelen",
"changeClass": "Van klasse veranderen en eigenschapspunten terugkrijgen",
+ "lvl10ChangeClass": "Om van klasse te veranderen moet je ten minste niveau 10 zijn.",
"levelPopover": "Elk niveau geeft je één punt om toe te wijzen aan een eigenschap van jouw keuze. Je kunt dit handmatig doen of het spel voor jou laten beslissen door gebruik te maken van één van de automatische verdelingsopties.",
"unallocated": "nog niet toegewezen eigenschapspunten",
"haveUnallocated": "Je hebt <%= points %> eigenschapspunt(en) nog niet toegewezen.",
@@ -164,5 +166,7 @@
"int": "INT",
"showQuickAllocation": "Toon verdeling eigenschapspunten",
"hideQuickAllocation": "Verberg verdeling eigenschapspunten",
- "quickAllocationLevelPopover": "Met ieder level verdien je een punt om toe te wijzen aan een eigenschap van jouw keuze. Je kunt dit eigenhandig doen of het spel voor je laten bepalen door een van de automatische toewijzingsopties te kiezen in Gebruiker -> Statistieken gebruiker."
+ "quickAllocationLevelPopover": "Met ieder level verdien je een punt om toe te wijzen aan een eigenschap van jouw keuze. Je kunt dit eigenhandig doen of het spel voor je laten bepalen door een van de automatische toewijzingsopties te kiezen in Gebruiker -> Statistieken gebruiker.",
+ "invalidAttribute": "\"<%= attr %>\" is geen geldige eigenschap.",
+ "notEnoughAttrPoints": "Je hebt niet genoeg eigenschap punten"
}
\ No newline at end of file
diff --git a/common/locales/nl/content.json b/common/locales/nl/content.json
index 84344edba8..e4c5435166 100644
--- a/common/locales/nl/content.json
+++ b/common/locales/nl/content.json
@@ -20,8 +20,8 @@
"dropEggFoxText": "Vos",
"dropEggFoxMountText": "Vos",
"dropEggFoxAdjective": "sluwe",
- "dropEggFlyingPigText": "Vliegend Varken",
- "dropEggFlyingPigMountText": "Vliegend Varken",
+ "dropEggFlyingPigText": "Vliegende Big",
+ "dropEggFlyingPigMountText": "Vliegende Big",
"dropEggFlyingPigAdjective": "wispelturige",
"dropEggDragonText": "Draak",
"dropEggDragonMountText": "Draak",
@@ -113,6 +113,12 @@
"questEggSnailText": "Slak",
"questEggSnailMountText": "Slak",
"questEggSnailAdjective": "een langzame maar zekere",
+ "questEggFalconText": "Valk",
+ "questEggFalconMountText": "Valk",
+ "questEggFalconAdjective": "een snelle",
+ "questEggTreelingText": "Treeling",
+ "questEggTreelingMountText": "Treeling",
+ "questEggTreelingAdjective": "a leafy",
"eggNotes": "Vind een uitbroedtoverdrank om over dit ei te gieten en er zal een <%= eggAdjective(locale) %> <%= eggText(locale) %> uitkomen.",
"hatchingPotionBase": "Normale",
"hatchingPotionWhite": "Witte",
@@ -126,6 +132,7 @@
"hatchingPotionGolden": "Gouden",
"hatchingPotionSpooky": "spookachtige",
"hatchingPotionPeppermint": "Pepermunt",
+ "hatchingPotionFloral": "Floral",
"hatchingPotionNotes": "Giet dit over een ei, en er zal een <%= potText(locale) %> dierlijke metgezel uitkomen.",
"premiumPotionAddlNotes": "Niet te gebruiken op eieren van queeste-huisdieren.",
"foodMeat": "Vlees",
diff --git a/common/locales/nl/contrib.json b/common/locales/nl/contrib.json
index 68b28f8100..8e7cbbda99 100644
--- a/common/locales/nl/contrib.json
+++ b/common/locales/nl/contrib.json
@@ -35,8 +35,12 @@
"hallContributors": "Hal der Bijdragers",
"hallPatrons": "Hal der Weldoeners",
"rewardUser": "Gebruiker belonen",
- "UUID": "UUID",
+ "UUID": "Gebruikers ID",
"loadUser": "Gebruiker laden",
+ "noAdminAccess": "Je hebt geen beheerders toegang.",
+ "pageMustBeNumber": "req.query.page moet een nummer zijn",
+ "userNotFound": "Gebruiker niet gevonden.",
+ "invalidUUID": "UUID moet geldig zijn",
"title": "Titel",
"moreDetails": "Meer details (1-7)",
"moreDetails2": "meer details (8-9)",
@@ -52,8 +56,8 @@
"visitHeroes": "Bezoek de Hal der Helden (bijdragers en helpers)",
"conLearn": "Lees meer over beloningen voor bijdragers",
"conLearnHow": "Lees meer over hoe je kunt bijdragen aan Habitica",
- "surveysSingle": "Heeft Habitica geholpen te groeien door een enquête in te vullen. Er zijn op dit moment geen enquêtes.",
- "surveysMultiple": "Heeft Habitica geholpen te groeien door <%= surveys %> enquête(s) in te vullen. Er zijn op dit moment geen enquêtes.",
+ "surveysSingle": "Helped Habitica grow, either by filling out a survey or helping with a major testing effort. Thank you!",
+ "surveysMultiple": "Helped Habitica grow on <%= surveys %> occasions, either by filling out a survey or helping with a major testing effort. Thank you!",
"currentSurvey": "Huidige enquête",
"surveyWhen": "De badge zal eind maart worden uitgereikt, wanneer de enquêtes van alle deelnemers zijn verwerkt.",
"blurbInbox": "Hier worden je privéberichten opgeslagen! Je kan iemand een bericht sturen door op het envelop-icoon naast hun naam te klikken in de herberg-, groep- of gilde-chat. Als je een ongepast bericht hebt gekregen, zou er een screenshot van moeten mailen naar Lemoness (leslie@habitica.com)",
diff --git a/common/locales/nl/death.json b/common/locales/nl/death.json
index 4922f91224..328e7dee89 100644
--- a/common/locales/nl/death.json
+++ b/common/locales/nl/death.json
@@ -12,5 +12,6 @@
"losingHealthQuickly": "Snel gezondheid aan het verliezen?",
"lowHealthTips3": "Onvolledige dagelijkse taken doen je 's nachts pijn, dus wees voorzichtig en voeg in eerste instantie niet te veel toe!",
"lowHealthTips4": "Als een dagelijkse taak op een bepaalde dag niet gedaan hoeft te worden kun je die dag uitschakelen door op het potlood te klikken.",
- "goodLuck": "Veel plezier!"
+ "goodLuck": "Veel plezier!",
+ "cannotRevive": "Kan niet herrijzen als je niet dod bent"
}
\ No newline at end of file
diff --git a/common/locales/nl/front.json b/common/locales/nl/front.json
index 0ad4debcad..513427a4a0 100644
--- a/common/locales/nl/front.json
+++ b/common/locales/nl/front.json
@@ -28,6 +28,7 @@
"communityReddit": "Reddit",
"companyAbout": "Hoe het werkt",
"companyBlog": "Blog",
+ "devBlog": "Ontwikkelaars Blog",
"companyDonate": "Doneer",
"companyExtensions": "Extensies",
"companyPrivacy": "Privacy",
@@ -51,6 +52,7 @@
"featureSocialHeading": "Samenspel",
"featuredIn": "Vermeld in",
"featuresHeading": "We bieden ook...",
+ "footerDevs": "Ontwikkelaars",
"footerCommunity": "Gemeenschap",
"footerCompany": "Bedrijf",
"footerMobile": "Mobiel",
@@ -182,6 +184,7 @@
"zelahQuote": "Door [Habitica] kan ik overgehaald worden om op tijd naar bed te gaan, omdat ik eraan denk dat ik punten verdien als ik vroeg in bed kruip, en gezondheid verlies als ik lang opblijf!",
"reportAccountProblems": "Problemen met je account melden",
"reportCommunityIssues": "Problemen met de gemeenschap melden",
+ "subscriptionPaymentIssues": "Abonnements- en Betalingsproblemen",
"generalQuestionsSite": "Algemene vragen over de site",
"businessInquiries": "Zakelijke aanvragen",
"merchandiseInquiries": "Vragen over merchandise",
@@ -222,5 +225,32 @@
"altAttrWebstorm": "WebStorm",
"altAttrGithub": "GitHub",
"altAttrTrello": "Trello",
- "altAttrSlack": "Slack"
+ "altAttrSlack": "Slack",
+ "missingAuthHeaders": "Missing authentication headers.",
+ "missingAuthParams": "Missing authentication parameters.",
+ "missingUsernameEmail": "Ontbrekende gebruikersnaam of email.",
+ "missingEmail": "Ontbrekende e-mail.",
+ "missingUsername": "Ontbrekende gebruikersnaam.",
+ "missingPassword": "Ontbrekend wachtwoord.",
+ "missingNewPassword": "Ontbrekend nieuw wachtwoord.",
+ "wrongPassword": "Verkeerd wachtwoord.",
+ "notAnEmail": "Ongeldig e-mail adres.",
+ "emailTaken": "E-mail adres is al in gebruik door een account.",
+ "newEmailRequired": "Ontbrekend nieuw e-mail adres.",
+ "usernameTaken": "Gebruikersnaam al in gebruik.",
+ "passwordConfirmationMatch": "Wachtwoord confirmatie komt niet overeen met wachtwoord.",
+ "invalidLoginCredentials": "Incorrecte gebruikersnaam en/of e-mail en/of wachtwoord.",
+ "passwordReset": "If we have your email on file, your password reset link has been sent to your email.",
+ "passwordResetEmailSubject": "Password Reset for Habitica",
+ "passwordResetEmailText": "Password for <%= username %> has been reset to <%= newPassword %> . Important! Both username and password are case-sensitive -- you must enter both exactly as shown here. We recommend copying and pasting both instead of typing them. Log in at <%= baseUrl %>. After you have logged in, head to <%= baseUrl %>/#/options/settings/settings and change your password.",
+ "passwordResetEmailHtml": "Password for <%= username %> has been reset to <%= newPassword %>.
Important! Both username and password are case-sensitive -- you must enter both exactly as shown here. We recommend copying and pasting both instead of typing them.
Log in at <%= baseUrl %>. After you have logged in, head to <%= baseUrl %>/#/options/settings/settings and change your password.",
+ "invalidLoginCredentialsLong": "Uh-oh - your username or password is incorrect.\n- Make sure your username or email is typed correctly.\n- You may have signed up with Facebook, not email. Double-check by trying Facebook login.\n- If you forgot your password, click \"Forgot Password\".",
+ "invalidCredentials": "There is no account that uses those credentials.",
+ "accountSuspended": "Account has been suspended, please contact leslie@habitica.com with your User ID \"<%= userId %>\" for assistance.",
+ "onlyFbSupported": "Only Facebook is supported currently.",
+ "cantDetachFb": "Account lacks another authentication method, can't detach Facebook.",
+ "onlySocialAttachLocal": "Local authentication can be added to only a social account.",
+ "invalidReqParams": "Invalid request parameters.",
+ "memberIdRequired": "\"member\" must be a valid UUID.",
+ "heroIdRequired": "\"heroId\" must be a valid UUID."
}
\ No newline at end of file
diff --git a/common/locales/nl/gear.json b/common/locales/nl/gear.json
index b2e2866354..f17bf55009 100644
--- a/common/locales/nl/gear.json
+++ b/common/locales/nl/gear.json
@@ -187,8 +187,10 @@
"weaponArmoireJesterBatonNotes": "Met een zwaai van je staf en een gevatte persiflage worden zelfs de meest ingewikkelde situaties helder. Verhoogt intelligentie en perceptie elk met met <%= attrs %>. Betoverd Kabinet: Jokerset (Voorwerp 3 van 3).",
"weaponArmoireMiningPickaxText": "Mijnwerkershouweel",
"weaponArmoireMiningPickaxNotes": "Houw de maximale hoeveelheid goud uit je taken! Verhoogt perceptie met <%= per %>. Betoverd kabinet: Mijnwerkersset (Voorwerp 3 van 3).",
- "weaponArmoireBasicLongbowText": "Basic Longbow",
- "weaponArmoireBasicLongbowNotes": "A serviceable hand-me-down bow. Increases Strength by <%= str %>. Enchanted Armoire: Basic Archer Set (Item 1 of 3).",
+ "weaponArmoireBasicLongbowText": "Simpele handboog",
+ "weaponArmoireBasicLongbowNotes": "Een bruikbare tweedehandse boog. Vergroot de kracht met <%= str %>. Betoverd kabinet: Boogschuttersset (Voorwerp 1 van 3)",
+ "weaponArmoireHabiticanDiplomaText": "Habiticaan Diploma",
+ "weaponArmoireHabiticanDiplomaNotes": "A certificate of significant achievement -- well done! Increases Intelligence by <%= int %>. Enchanted Armoire: Graduate Set (Item 1 of 3).",
"armor": "wapenrusting",
"armorBase0Text": "Eenvoudige kleding",
"armorBase0Notes": "Normale kleding. Verleent geen voordelen.",
@@ -364,6 +366,10 @@
"armorMystery201512Notes": "Ontbied de ijzige vlammen van de winter! Verleent geen voordelen. Abonnee-uitrusting december 2015.",
"armorMystery201603Text": "Gelukzalig Pak",
"armorMystery201603Notes": "Dit pak is geweven uit duizenden klavertjes vier! Verleent geen voordelen. Abonnee-uitrusting maart 2016.",
+ "armorMystery201604Text": "Harnas van Bladeren",
+ "armorMystery201604Notes": "You, too, can be a small but fearsome leaf puff. Confers no benefit. April 2016 Subscriber Item.",
+ "armorMystery201605Text": "Marching Bard Uniform",
+ "armorMystery201605Notes": "Unlike the traditional bards who join adventuring parties, bards who join Habitican marching bands are known for grand parades, not dungeon raids. Confers no benefit. May 2016 Subscriber Item.",
"armorMystery301404Text": "Steampunkpak",
"armorMystery301404Notes": "Net en zwierig, niet? Verleent geen voordelen. Abonnee-uitrusting februari 3015.",
"armorArmoireLunarArmorText": "Kalmerend maanharnas",
@@ -392,8 +398,10 @@
"armorArmoireJesterCostumeNotes": "Tra-la-la! Anders dan dit kostuum doet vermoeden, ben je geen dwaas. Verhoogt intelligentie met <%= int %>. Betoverd kabinet: jokerset (voorwerp 2 van 3).",
"armorArmoireMinerOverallsText": "Mijnwerkersoveral",
"armorArmoireMinerOverallsNotes": "Ze zien er misschien afgedragen uit, maar je zijn betoverd om modder af te stoten. Verhoogt lichaam met <%= con %>. Betoverd kabinet: Mijnwerkersset (Voorwerp 2 van 3).",
- "armorArmoireBasicArcherArmorText": "Basic Archer Armor",
- "armorArmoireBasicArcherArmorNotes": "This camouflaged vest lets you slip unnoticed through the forests. Increases Perception by <%= per %>. Enchanted Armoire: Basic Archer Set (Item 2 of 3).",
+ "armorArmoireBasicArcherArmorText": "Simpel Boogschutters Harnas",
+ "armorArmoireBasicArcherArmorNotes": "Dit gecamoufleerde vest laat je onopgemerkt door de bossen glippen. Verhoogt perceptie met <%= per %>. Betoverd kabinet: Boogschutter set (Voorwerp 2 van 3).",
+ "armorArmoireGraduateRobeText": "Graduate Robe",
+ "armorArmoireGraduateRobeNotes": "Congratulations! This weighty robe hangs heavy with all the knowledge you have accrued. Increases Intelligence by <%= int %>. Enchanted Armoire: Graduate Set (Item 2 of 3).",
"headgear": "hoofdbescherming",
"headBase0Text": "Geen helm",
"headBase0Notes": "Geen hoofdbescherming.",
@@ -496,7 +504,7 @@
"headSpecialSpring2015MageText": "Hoed van de goochelaar",
"headSpecialSpring2015MageNotes": "Wat was er eerst: het konijn of de hoed? Verhoogt Perceptie met <%= per %>. Beperkte oplage lente-uitrusting 2015.",
"headSpecialSpring2015HealerText": "Troostende kroon",
- "headSpecialSpring2015HealerNotes": "De parel in het midden van de kroon kalmeert en troost iedereen in de buurt. Verhoogt Intelligentie met <%= per %>. Beperkte oplage lente-uitrusting 2015.",
+ "headSpecialSpring2015HealerNotes": "De parel in het midden van de kroon kalmeert en troost iedereen in de buurt. Verhoogt Intelligentie met <%= int %>. Beperkte oplage lente-uitrusting 2015.",
"headSpecialSummer2015RogueText": "Outlawhoed",
"headSpecialSummer2015RogueNotes": "Deze piratenhoed is overboord gevallen en is nu gedecoreerd met stukken vuurkoraal. Verhoogt Perceptie met <%= per %>. Beperkte oplage zomeruitrusting 2015.",
"headSpecialSummer2015WarriorText": "Oceaanhelm met juwelen",
@@ -565,6 +573,10 @@
"headMystery201602Notes": "Bescherm je identiteit voor al je aanbidders. Verleent geen voordelen. Abonnee-uitrusting februari 2016.",
"headMystery201603Text": "Gelukzalige Hoed",
"headMystery201603Notes": "Deze hoge hoed is een magische geluksbedel. Verleent geen voordelen. Abonnee-uitrusting maart 2016.",
+ "headMystery201604Text": "Kroon van Bloemen",
+ "headMystery201604Notes": "These woven flowers make a surprisingly strong helm! Confers no benefit. April 2016 Subscriber Item.",
+ "headMystery201605Text": "Marching Bard Hat",
+ "headMystery201605Notes": "Seventy-six dragons led the big parade, with a hundred and ten gryphons close at hand! Confers no benefit. May 2016 Subscriber Item.",
"headMystery301404Text": "Chique hoge hoed",
"headMystery301404Notes": "Een chique hoge hoed voor lieden van deftigen huize! Abonnee-uitrusting januari 3015. Verleent geen voordelen.",
"headMystery301405Text": "Standaard hoge hoed",
@@ -611,8 +623,10 @@
"headArmoireJesterCapNotes": "De belletjes op deze hoed leiden je tegenstanders misschien af, maar ze helpen jou om te focussen. Verhoogt perceptie met <%= per %>. Betoverd kabinet: Jokerset (Voorwerp 1 van 3). ",
"headArmoireMinerHelmetText": "Mijnwerkershelm",
"headArmoireMinerHelmetNotes": "Bescherm je hoofd tegen vallende taken! Verhoogt intelligentie met <%= int %>. Betoverd kabinet: Mijnwerker set (Voorwerp 1 van 3).",
- "headArmoireBasicArcherCapText": "Basic Archer Cap",
- "headArmoireBasicArcherCapNotes": "No archer would be complete without a jaunty cap! Increases Perception by <%= per %>. Enchanted Armoire: Basic Archer Set (Item 3 of 3).",
+ "headArmoireBasicArcherCapText": "Simpele Boogschutters Kap",
+ "headArmoireBasicArcherCapNotes": "Geen boogschutter zou complete zijn zonder een kwieke pet! Verhoogt perceptie met <%= per %>. Betoverd kabinet: Boogschutter set (Voorwerp 3 van 3).",
+ "headArmoireGraduateCapText": "Graduate Cap",
+ "headArmoireGraduateCapNotes": "Congratulations! Your deep thoughts have earned you this thinking cap. Increases Intelligence by <%= int %>. Enchanted Armoire: Graduate Set (Item 3 of 3).",
"offhand": "artikel voor schildhand",
"shieldBase0Text": "Geen uitrusting voor schildhand",
"shieldBase0Notes": "Geen schild of tweede wapen.",
@@ -673,7 +687,7 @@
"shieldSpecialWinter2015HealerText": "Kalmerend schild",
"shieldSpecialWinter2015HealerNotes": "Dit schild doet de ijskoude wind afbuigen. Verhoogt Lichaam met <%= con %>. Beperkte oplage winteruitrusting 2014-2015.",
"shieldSpecialSpring2015RogueText": "Ontploffende piep",
- "shieldSpecialSpring2015RogueNotes": "Laat je door het geluid niet voor de gek houden - deze explosieven leveren een krachtige klap. Verhoogt Kracht met <%= str %>. Beperkte oplage lente-uitrusting 2015.",
+ "shieldSpecialSpring2015RogueNotes": "Don't let the sound fool you - these explosives pack a punch. Increases Strength by <%= str %>. Limited Edition 2015 Spring Gear.",
"shieldSpecialSpring2015WarriorText": "Vliegende voerbak",
"shieldSpecialSpring2015WarriorNotes": "Smijt hem naar je vijanden... of houd hem gewoon beet, want rond etenstijd zal hij zich vullen met lekkere brokjes. Verhoogt Lichaam met <%= con %>. Beperkte oplage lente-uitrusting 2015.",
"shieldSpecialSpring2015HealerText": "Kussen met een leuk patroontje",
@@ -716,6 +730,8 @@
"shieldArmoireDragonTamerShieldNotes": "Leid vijanden af met dit schild in de vorm van een draak. Verhoogt Perceptie met <%= per %>. Betoverd kabinet: Drakentemmer set (Voorwerp 2 van 3).",
"shieldArmoireMysticLampText": "Mystieke Lamp",
"shieldArmoireMysticLampNotes": "Verlicht de donkerste grotten met deze Mystieke Lamp! Verhoogt perceptie met <%= per %>. Betoverd kabinet: Onafhankelijk voorwerp. ",
+ "shieldArmoireFloralBouquetText": "Boeket Bloemen",
+ "shieldArmoireFloralBouquetNotes": "Not much help in battle, but aren't they beautiful? Increases Constitution by <%= con %>. Enchanted Armoire: Independent Item.",
"back": "Lichaamsaccessoire",
"backBase0Text": "Geen rugaccessoire",
"backBase0Notes": "Geen rugaccessoire.",
@@ -815,11 +831,25 @@
"headAccessoryMystery201510Notes": "Deze angstaanjagende hoorns zijn een beetje slijmerig. Verleent geen voordelen. Abonnee-uitrusting oktober 2015.",
"headAccessoryMystery301405Text": "Veiligheidsbril voor op je hoofd",
"headAccessoryMystery301405Notes": "\"Veiligheidsbrillen zijn voor je ogen,\" zeiden ze. \"Niemand wil een veiligheidsbril die je alleen maar op je hoofd kunt dragen,\" zeiden ze. Ha! Jij hebt ze laten zien hoe het echt moet! Verleent geen voordelen. Abonnee-uitrusting augustus 3015.",
- "headAccessoryArmoireComicalArrowText": "Comical Arrow",
- "headAccessoryArmoireComicalArrowNotes": "This whimsical item doesn't provide a stat boost, but it sure is good for a laugh! Confers no benefit. Enchanted Armoire: Independent Item.",
+ "headAccessoryArmoireComicalArrowText": "Komische Pijl",
+ "headAccessoryArmoireComicalArrowNotes": "Dit grappige item zorgt niet voor een eigenschappen-boost, maar wel voor een lach! Verleent geen voordelen. Betoverd kabinet: Onafhankelijk voorwerp.",
"eyewear": "Oogaccessoire",
"eyewearBase0Text": "Geen oogaccessoire",
"eyewearBase0Notes": "Geen oogaccessoire.",
+ "eyewearSpecialBlackTopFrameText": "Black Standard Eyeglasses",
+ "eyewearSpecialBlackTopFrameNotes": "Glasses with a black frame above the lenses. Confers no benefit.",
+ "eyewearSpecialBlueTopFrameText": "Blue Standard Eyeglasses",
+ "eyewearSpecialBlueTopFrameNotes": "Glasses with a blue frame above the lenses. Confers no benefit.",
+ "eyewearSpecialGreenTopFrameText": "Green Standard Eyeglasses",
+ "eyewearSpecialGreenTopFrameNotes": "Glasses with a green frame above the lenses. Confers no benefit.",
+ "eyewearSpecialPinkTopFrameText": "Pink Standard Eyeglasses",
+ "eyewearSpecialPinkTopFrameNotes": "Glasses with a pink frame above the lenses. Confers no benefit.",
+ "eyewearSpecialRedTopFrameText": "Red Standard Eyeglasses",
+ "eyewearSpecialRedTopFrameNotes": "Glasses with a red frame above the lenses. Confers no benefit.",
+ "eyewearSpecialWhiteTopFrameText": "White Standard Eyeglasses",
+ "eyewearSpecialWhiteTopFrameNotes": "Glasses with a white frame above the lenses. Confers no benefit.",
+ "eyewearSpecialYellowTopFrameText": "Yellow Standard Eyeglasses",
+ "eyewearSpecialYellowTopFrameNotes": "Glasses with a yellow frame above the lenses. Confers no benefit.",
"eyewearSpecialSummerRogueText": "Snaaks ooglapje",
"eyewearSpecialSummerRogueNotes": "Je hoeft geen boefje te zijn om te zien hoe stijlvol dit is! Verleent geen voordelen. Beperkte oplage zomeruitrusting 2014.",
"eyewearSpecialSummerWarriorText": "Zwierig ooglapje",
diff --git a/common/locales/nl/generic.json b/common/locales/nl/generic.json
index f70a5d495e..10b7eb218a 100644
--- a/common/locales/nl/generic.json
+++ b/common/locales/nl/generic.json
@@ -137,8 +137,8 @@
"achievementStressbeastText": "Heeft geholpen het Verschrikkelijke Stressbeest te verslaan tijdens het Winter Wonderland-evenement van 2014!",
"achievementBurnout": "Verlosser van de Bloeiende Velden",
"achievementBurnoutText": "Heeft geholpen Burnout te verslaan tijdens het Najaarsvolksfeest van 2015!",
- "achievementBewilder": "Savior of Mistiflying",
- "achievementBewilderText": "Helped defeat the Be-Wilder during the 2016 Spring Fling Event!",
+ "achievementBewilder": "Redder van Mistiflying",
+ "achievementBewilderText": "Heeft geholpen om de Ver-Wilder te verslaan tijdens het lente-evenement in 2016.",
"checkOutProgress": "Moet je mijn vooruitgang in Habitica eens zien!",
"cardReceived": "Kaart ontvangen!",
"cardReceivedFrom": "<%= cardType %> van <%= userName %>",
diff --git a/common/locales/nl/groups.json b/common/locales/nl/groups.json
index a5c823d4be..abe7649e55 100644
--- a/common/locales/nl/groups.json
+++ b/common/locales/nl/groups.json
@@ -92,6 +92,7 @@
"send": "Verzenden",
"messageSentAlert": "Bericht verzonden",
"pmHeading": "Privébericht aan <%= name %>",
+ "pmsMarkedRead": "Your private messages have been marked as read",
"clearAll": "Alle berichten verwijderen",
"confirmDeleteAllMessages": "Weet je zeker dat je alle berichten in je inbox wilt verwijderen? Andere gebruikers kunnen de berichten die je ze hebt gestuurd blijven zien.",
"optOutPopover": "Vind je privéberichten maar niks? Klik hier om je helemaal af te melden.",
@@ -99,6 +100,15 @@
"unblock": "Deblokkeren",
"pm-reply": "Beantwoorden",
"inbox": "Inbox",
+ "messageRequired": "A message is required.",
+ "toUserIDRequired": "A User ID is required",
+ "gemAmountRequired": "A number of gems is required",
+ "notAuthorizedToSendMessageToThisUser": "Kan geen bericht versturen naar deze gebruiker.",
+ "privateMessageGiftIntro": "Hello <%= receiverName %>, <%= senderName %> has sent you",
+ "privateMessageGiftGemsMessage": "<%= gemAmount %> gems!",
+ "privateMessageGiftSubscriptionMessage": "<%= numberOfMonths %> months of subscription!",
+ "cannotSendGemsToYourself": "Cannot send gems to yourself. Try a subscription instead.",
+ "badAmountOfGemsToSend": "Amount must be within 1 and your current number of gems.",
"abuseFlag": "Overtreding van gemeenschapsrichtlijnen melden",
"abuseFlagModalHeading": "<%= name %> rapporteren wegens overtreding?",
"abuseFlagModalBody": "Weet je zeker dat je deze post wil rapporteren? Je zou een post ALLEEN moeten rapporteren als deze de <%= firstLinkStart %>gemeenschapsrichtlijnen<%= linkEnd %> en/of <%= secondLinkStart %>de algemene voorwaarden<%= linkEnd %> schendt. Ongepast een post rapporteren is een overtreding van de gemeenschapsrichtlijnen en kan je een overtreding opleveren. Goede redenen om een post aan te geven zijn, maar zijn niet beperkt tot:
schelden, religieuze verwensingen
vooroordelen, laster
onderwerpen voor voorwassenen
geweld, ook als grap
spam, onzinnige berichten
",
@@ -151,5 +161,29 @@
"partyUpName": "Groepsvorming",
"partyOnName": "Groepsfeestje",
"partyUpAchievement": "Is bij een groep gegaan met een andere persoon! Veel plezier met het vechten tegen monsters en het ondersteunen van elkaar.",
- "partyOnAchievement": "Is bij een groep gegaan met minstens vier mensen! Geniet van je verhoogde verantwoordelijkheid wanneer je je bij je vrienden voegt om je vijanden te verslaan!"
+ "partyOnAchievement": "Is bij een groep gegaan met minstens vier mensen! Geniet van je verhoogde verantwoordelijkheid wanneer je je bij je vrienden voegt om je vijanden te verslaan!",
+ "groupIdRequired": "\"groupId\" must be a valid UUID",
+ "groupNotFound": "Groep niet gevonden.",
+ "groupTypesRequired": "You must supply a valid \"type\" query string.",
+ "questLeaderCannotLeaveGroup": "You cannot leave your party when you have started a quest. Abort the quest first.",
+ "cannotLeaveWhileActiveQuest": "You cannot leave party during an active quest. Please leave the quest first.",
+ "onlyLeaderCanRemoveMember": "Only group leader can remove a member!",
+ "memberCannotRemoveYourself": "You cannot remove yourself!",
+ "groupMemberNotFound": "User not found among group's members",
+ "mustBeGroupMember": "Must be member of the group.",
+ "keepOrRemoveAll": "req.query.keep must be either \"keep-all\" or \"remove-all\"",
+ "keepOrRemove": "req.query.keep must be either \"keep\" or \"remove\"",
+ "canOnlyInviteEmailUuid": "Can only invite using uuids or emails.",
+ "inviteMissingEmail": "Missing email address in invite.",
+ "partyMustbePrivate": "Parties must be private",
+ "userAlreadyInGroup": "User already in that group.",
+ "userAlreadyInvitedToGroup": "User already invited to that group.",
+ "userAlreadyPendingInvitation": "User already pending invitation.",
+ "userAlreadyInAParty": "Gebruiker zit al in een groep.",
+ "userWithIDNotFound": "User with id \"<%= userId %>\" not found.",
+ "userHasNoLocalRegistration": "User does not have a local registration (username, email, password).",
+ "uuidsMustBeAnArray": "User ID invites must be an array.",
+ "emailsMustBeAnArray": "Email address invites must be an array.",
+ "canOnlyInviteMaxInvites": "You can only invite \"<%= maxInvites %>\" at a time",
+ "onlyCreatorOrAdminCanDeleteChat": "Not authorized to delete this message!"
}
\ No newline at end of file
diff --git a/common/locales/nl/limited.json b/common/locales/nl/limited.json
index 08c621b3f2..2bba166e67 100644
--- a/common/locales/nl/limited.json
+++ b/common/locales/nl/limited.json
@@ -5,7 +5,7 @@
"annoyingFriends": "Irritante vrienden",
"annoyingFriendsText": "Is <%= snowballs %> keer geraakt door een sneeuwbal door groepsleden.",
"alarmingFriends": "Verontrustende vrienden",
- "alarmingFriendsText": "Is <%= spookDust %> keer bespookt door groepsleden.",
+ "alarmingFriendsText": "Got spooked <%= spookySparkles %> times by party members.",
"agriculturalFriends": "Vrienden met groene vingers",
"agriculturalFriendsText": "Is <%= seeds %> keer door groepsgenoten in een bloem omgetoverd.",
"aquaticFriends": "Spetterende vrienden",
@@ -72,5 +72,6 @@
"comfortingKittySet": "Troostend Katje (Heler)",
"sneakySqueakerSet": "Sneaky Pieper (Dief)",
"fallEventAvailability": "Beschikbaar tot 31 oktober",
- "winterEventAvailability": "Beschikbaar tot 31 december"
+ "winterEventAvailability": "Beschikbaar tot 31 december",
+ "springEventAvailability": "Beschikbaar tot 31 mei"
}
\ No newline at end of file
diff --git a/common/locales/nl/maintenance.json b/common/locales/nl/maintenance.json
new file mode 100644
index 0000000000..7ec78446a7
--- /dev/null
+++ b/common/locales/nl/maintenance.json
@@ -0,0 +1,34 @@
+{
+ "habiticaBackSoon": "Maak je geen zorgen, Habitica is snel weer terug!",
+ "importantMaintenance": "We are doing important maintenance that we estimate will last until <%= localDate %> in your timezone.",
+ "maintenance": "Onderhoud",
+ "maintenanceMoreInfo": "Want more information about the maintenance? <%= linkStart %>Check out our info page<%= linkEnd %>.",
+ "noDamageKeepStreaks": "Je zal GEEN schade ontvangen of series verliezen!",
+ "thanksForPatience": "Bedankt voor je geduld!",
+ "twitterMaintenanceUpdates": "For the most recent updates, watch our Twitter, where we will be posting status information.",
+ "veteranPetAward": "At the end, you will receive a Veteran pet!",
+
+ "maintenanceInfoTitle": "Information about Upcoming Maintenance to Habitica",
+ "maintenanceInfoWhat": "Wat gebeurt er?",
+ "maintenanceInfoWhatText": "On May 21, Habitica will be down for maintenance for most of the day. You will not take any damage or have your account harmed during that weekend, even if you can’t log in to check off your Dailies in time! We will be working very hard to make the downtime as short as possible, and will be posting regular updates on our Twitter account. At the end of the downtime, to thank everyone for their patience, you will all receive a rare pet!",
+ "maintenanceInfoWhy": "Waarom gebeurt dit?",
+ "maintenanceInfoWhyText": "For the past several months, we have been thoroughly revamping Habitica behind-the-scenes. Specifically, we have rewritten the API. While it may not look much different on the surface, it’s a whole new world underneath. This will allow us WAY more flexibility when we want to build features in the future, and lead to improved performance!",
+ "maintenanceInfoTechDetails": "Want more details on the technical side of the process? Visit The Forge, our dev blog.",
+ "maintenanceInfoMore": "Meer informatie",
+ "maintenanceInfoAccountChanges": "What changes will I see to my account after the rewrite is complete?",
+ "maintenanceInfoAccountChangesText": "At first, there won’t be any notable changes aside from performance improvements for features such as Challenges. If you notice any changes that shouldn’t be there, email us at admin@habitica.com and we will investigate them for you!",
+ "maintenanceInfoAddFeatures": "What kind of features will this allow Habitica to add?",
+ "maintenanceInfoAddFeaturesText": "Completing this rewrite will allow us to start building out improved chat and Guilds, plans for organizations and families, and additional productivity features like Monthlies and the ability to record yesterday’s activity! Those are all involved features on their own, so it will take time to build them, but until we were finished with this rewrite, there was no way we could start them.",
+ "maintenanceInfoHowLong": "Hoe lang zal dit onderhoud duren?",
+ "maintenanceInfoHowLongText": "We have to migrate tasks and data for all 1.3 million Habitica users -- not an easy task! We anticipate that it will take place between approximately 1pm Pacific Time (8pm UTC) and 10pm Pacific Time (5am UTC). Rest assured that we’re doing everything we can to make it go as quickly as possible! You can follow updates on our Twitter.",
+ "maintenanceInfoStatsAffected": "How will my Dailies, Streaks, Buffs, and Quests be affected?",
+ "maintenanceInfoStatsAffectedText1": "You will NOT take any damage or lose any streaks that weekend, but otherwise, your day will reset normally! Dailies that you checked will become unchecked, buffs will reset, etc. If you are in a Collection Quest, you will still find items. If you are in a Boss Battle, you will still deal damage to the Boss, but the Boss will not deal damage to you. (Even monsters need a break!)",
+ "maintenanceInfoStatsAffectedText2": "After a lot of thought, our team concluded that this was the most fair way to handle the fact that many users will not be able to check off their Dailies normally during the maintenance. We’re sorry for any inconvenience this causes!",
+ "maintenanceInfoSeeTasks": "Wat als ik mijn taken lijst moet zien?",
+ "maintenanceInfoSeeTasksText": "If you know that you will need to see your task list on Saturday to remind yourself what you have to do, we recommend that before the maintenance begins, you take a screenshot of your tasks so that you can use it as a reference.",
+ "maintenanceInfoRarePet": "What kind of rare pet will I receive?",
+ "maintenanceInfoRarePetText": "To thank you for your patience during the downtime, everyone will get a rare Veteran Pet. If you’ve never received a Veteran Pet before, you will receive a Veteran Wolf. If you already have a Veteran Wolf, you will receive a Veteran Tiger. And if you already have a Veteran Wolf and a Veteran Tiger, you will receive a never-before-seen Veteran pet! After the migration is completed, it may take several hours for your pet to show up, but never fear, everyone will get one.",
+ "maintenanceInfoWho": "Who worked on this massive project?",
+ "maintenanceInfoWhoText": "We’re glad you asked! It was spearheaded by our amazing contributor paglias, with lots of help from Blade, TheHollidayInn, SabreCat, Victor Pudeyev, TheUnknown, and Alys.",
+ "maintenanceInfoTesting": "The new version was also tirelessly tested by a bunch of our amazing open-source volunteers. Thank you -- we couldn't have done this without you."
+}
diff --git a/common/locales/nl/npc.json b/common/locales/nl/npc.json
index c24a9864a0..26a04bd5df 100644
--- a/common/locales/nl/npc.json
+++ b/common/locales/nl/npc.json
@@ -21,6 +21,26 @@
"ian": "Ian",
"ianText": "Welkom in de queestenwinkel! Hier kun je queesten inzetten om samen met je vrienden monsters te verslaan. Neem een kijkje aan de rechterkant om te bekijken welke prachtige queesten te koop zijn!",
"ianBrokenText": "Welkom in de queestenwinkel... Hier kun je queeste-perkamentrollen gebruiken om samen met je vrienden monsters te verslaan... Neem een kijkje aan de rechterkant om te bekijken welke prachtige queesten er te koop zijn...",
+ "missingKeyParam": "\"req.params.key\" is required.",
+ "itemNotFound": "Item \"<%= key %>\" not found.",
+ "cannotBuyItem": "You can't buy this item.",
+ "missingTypeKeyEquip": "\"key\" and \"type\" are required parameters.",
+ "missingPetFoodFeed": "\"pet\" and \"food\" are required parameters.",
+ "invalidPetName": "Invalid pet name supplied.",
+ "missingEggHatchingPotionHatch": "\"egg\" and \"hatchingPotion\" are required parameters.",
+ "invalidTypeEquip": "\"type\" must be one of 'equipped', 'pet', 'mount', 'costume'.",
+ "mustPurchaseToSet": "Must purchase <%= val %> to set it on <%= key %>.",
+ "typeRequired": "Type is required",
+ "keyRequired": "Key is required",
+ "notAccteptedType": "Type must be in [eggs, hatchingPotions, food, quests, gear]",
+ "contentKeyNotFound": "Key not found for Content <%= type %>",
+ "plusOneGem": "+1 Gem",
+ "typeNotSellable": "Type is not sellable. Must be one of the following <%= acceptedTypes %>",
+ "userItemsKeyNotFound": "Key not found for user.items <%= type %>",
+ "pathRequired": "Path string is required",
+ "unlocked": "Items have been unlocked",
+ "alreadyUnlocked": "Full set already unlocked.",
+ "alreadyUnlockedPart": "Full set already partially unlocked.",
"USD": "(USD)",
"newStuff": "Nieuwe informatie",
"cool": "Een andere keer lezen",
@@ -64,6 +84,7 @@
"tourPetsPage": "Dit is de Stal! Na niveau 3 kan je huisdieren laten uitkomen door eieren en toverdranken te gebruiken. Als je een huisdier laat uitkomen in de markt verschijnt het hier! Klik op het plaatje van een huisdier om het aan je avatar toe te voegen. Voer je huisdieren met het voedsel wat je na niveau 3 vindt om ze te laten uitgroeien tot krachtige rijdieren.",
"tourMountsPage": "Als je een huisdier genoeg gevoerd hebt en het in een rijdier verandert, verschijnt het hier. (Huisdieren, rijdieren en voedsel zijn beschikbaar na niveau 3.) Klik op een rijdier om op te zadelen!",
"tourEquipmentPage": "Hier wordt je uitrusting opgeslagen. Je strijduitrusting heeft effect op je statistieken. Als je wilt dat je avatar er anders uitziet zonder dat de statistieken veranderen, zet dan \"Kostuum gebruiken\" aan.",
+ "equipmentAlreadyOwned": "You already own that piece of equipment",
"tourOkay": "Oké!",
"tourAwesome": "Super!",
"tourSplendid": "Prachtig!",
diff --git a/common/locales/nl/pets.json b/common/locales/nl/pets.json
index e70c9b0ad0..8b9d62023a 100644
--- a/common/locales/nl/pets.json
+++ b/common/locales/nl/pets.json
@@ -12,6 +12,7 @@
"etherealLion": "Etherische Leeuw",
"veteranWolf": "Veteranenwolf",
"veteranTiger": "Veteranentijger",
+ "veteranLion": "Veteran Lion",
"cerberusPup": "Cerberuspup",
"hydra": "Hydra",
"mantisShrimp": "Bidsprinkhaankreeft",
@@ -19,7 +20,7 @@
"orca": "Orka",
"royalPurpleGryphon": "Regaal Paarse Griffioen",
"phoenix": "Feniks",
- "bumblebee": "Bumblebee",
+ "magicalBee": "Magische Bij",
"rarePetPop1": "Klik op de gouden pootafdruk om meer te weten te komen over hoe je dit zeldzame dier kunt ontvangen door bij te dragen aan Habitica!",
"rarePetPop2": "Hoe krijg ik dit huisdier?",
"potion": "<%= potionType %> uitbroedtoverdrank",
@@ -62,6 +63,7 @@
"hatchedPet": "Je heb een <%= potion %> <%= egg %> uitgebroed!",
"displayNow": "Toon nu",
"displayLater": "Toon later",
+ "petNotOwned": "You do not own this pet.",
"earnedCompanion": "Door al jouw productiviteit heb je een nieuwe kameraad verdiend. Voer hem en laat hem groeien! ",
"feedPet": "<%= text %> aan je <%= name %> voeren?",
"useSaddle": "<%= pet %> zadelen?",
@@ -83,5 +85,8 @@
"petKeyBoth": "Beide vrijlaten",
"confirmPetKey": "Weet je het zeker?",
"petKeyNeverMind": "Nog niet",
+ "petsReleased": "Pets released.",
+ "mountsAndPetsReleased": "Mounts and pets released",
+ "mountsReleased": "Mounts released",
"gemsEach": "edelstenen elk"
}
\ No newline at end of file
diff --git a/common/locales/nl/quests.json b/common/locales/nl/quests.json
index 2bc629f7d6..4e7184187d 100644
--- a/common/locales/nl/quests.json
+++ b/common/locales/nl/quests.json
@@ -38,7 +38,7 @@
"bossDmg2": "Alleen deelnemers bevechten de eindbaas en delen de buit.",
"bossDmg1Broken": "Elke voltooide dagelijkse taak en to-do en elke positieve gewoonte brengt schade toe aan de eindbaas... Breng meer schade toe met rodere taken of Wrede Slag en Uiteenspatting van Vlammen... De eindbaas brengt iedereen die meedoet aan de queeste schade toe voor iedere dagelijkse taak die je gemist hebt (vermenigvuldigd met de kracht van de eindbaas), bovenop je normale schade, dus houd je groep gezond door je dagelijkse taken te doen... Alle schade van en aan een eindbaas wordt verrekend tijdens cron (je dagelijkse nieuwe start)...",
"bossDmg2Broken": "Alleen deelnemers bevechten de eindbaas en delen de buit...",
- "tavernBossInfo": "Complete Dailies and To-Dos and score positive Habits to damage the World Boss! Incomplete Dailies fill the Rage Bar. When the Rage bar is full, the World Boss will attack an NPC. A World Boss will never damage individual players or accounts in any way. Only active accounts not resting in the Inn will have their tasks tallied.",
+ "tavernBossInfo": "Voltooi Dagelijkse taken en To-Do's en score positieve Gewoontes om de Eindbaas schade aan te brengen! Onvoltooide Dagelijkse Taken vullen de Woede Balk. Wanneer de Woede Balk vol is, zal de Eindbaas een NPC aanvallen. Een Eindbaas zal nooit individuele spelers of aacounts schade aanbrengen op welke manier dan ook. Alleen van actieve accounts die niet in de Herberg verblijven zullen de taken meegeteld worden. ",
"tavernBossInfoBroken": "Voltooi dagelijkse taken en to-do's en vink positieve gewoontes af om de Wereldbaas schade toe te brengen... Onvoltooide dagelijkse taken vullen de 'Oncontroleerbare Furie'-balk... Als de 'Oncontroleerbare Furie'-balk vol is, valt de Wereldbaas een NPC aan... Een Wereldbaas zal nooit schade doen aan individuele spelers of accounts... Alleen de taken van actieve accounts, die niet verblijven in de herberg, worden meegerekend...",
"bossColl1": "Om vondsten te verzamelen moet je positieve taken doen. De vondsten van de queeste werken hetzelfde als normale vondsten; je zult de vondsten echter niet zien tot de volgende dag, wanneer alles wat je hebt gevonden wordt opgeteld en aan de buit wordt toegevoegd.",
"bossColl2": "Alleen deelnemers kunnen vondsten verzamelen en de buit delen.",
@@ -78,5 +78,24 @@
"whichQuestStart": "Welke queeste wil je beginnen?",
"getMoreQuests": "Verkrijg meer queesten",
"unlockedAQuest": "Je hebt een queeste vrijgespeeld!",
- "leveledUpReceivedQuest": "Je hebt niveau <%= level %> bereikt en hebt een queeste ontvangen!"
+ "leveledUpReceivedQuest": "Je hebt niveau <%= level %> bereikt en hebt een queeste ontvangen!",
+ "questInvitationDoesNotExist": "No quest invitation has been sent out yet.",
+ "questInviteNotFound": "No quest invitation found.",
+ "guildQuestsNotSupported": "Guilds cannot be invited on quests.",
+ "questNotFound": "Quest \"<%= key %>\" not found.",
+ "questNotOwned": "You don't own that quest scroll.",
+ "questNotGoldPurchasable": "Quest \"<%= key %>\" is not a Gold-purchasable quest.",
+ "questLevelTooHigh": "You must be level <%= level %> to begin this quest.",
+ "questAlreadyUnderway": "Your party is already on a quest. Try again when the current quest has ended.",
+ "questAlreadyAccepted": "You already accepted the quest invitation.",
+ "noActiveQuestToLeave": "No active quest to leave",
+ "questLeaderCannotLeaveQuest": "Quest leader cannot leave quest",
+ "notPartOfQuest": "You are not part of the quest",
+ "noActiveQuestToAbort": "There is no active quest to abort.",
+ "onlyLeaderAbortQuest": "Only the group or quest leader can abort a quest.",
+ "questAlreadyRejected": "You already rejected the quest invitation.",
+ "cantCancelActiveQuest": "You can not cancel an active quest, use the abort functionality.",
+ "onlyLeaderCancelQuest": "Only the group or quest leader can cancel the quest.",
+ "questNotPending": "There is no quest to start.",
+ "questOrGroupLeaderOnlyStartQuest": "Only the quest leader or group leader can force start the quest"
}
\ No newline at end of file
diff --git a/common/locales/nl/questscontent.json b/common/locales/nl/questscontent.json
index fa88268059..bbc9bacc6c 100644
--- a/common/locales/nl/questscontent.json
+++ b/common/locales/nl/questscontent.json
@@ -59,7 +59,7 @@
"questSpiderDropSpiderEgg": "Spin (ei)",
"questSpiderUnlockText": "Maakt het kopen van spinneneieren in de markt mogelijk",
"questVice1Text": "Ondeugd, deel 1: Bevrijd jezelf van de Invloed van de Draak",
- "questVice1Notes": "
They say there lies a terrible evil in the caverns of Mt. Habitica. A monster whose presence twists the wills of the strong heroes of the land, turning them towards bad habits and laziness! The beast is a grand dragon of immense power and comprised of the shadows themselves: Vice, the treacherous Shadow Wyrm. Brave Habiteers, stand up and defeat this foul beast once and for all, but only if you believe you can stand against its immense power.
Vice Part 1:
How can you expect to fight the beast if it already has control over you? Don't fall victim to laziness and vice! Work hard to fight against the dragon's dark influence and dispel his hold on you!
",
+ "questVice1Notes": "
Ze zeggen dat er een vreselijk kwaad schuilt in de grotten van Berg Habitica. Een monster wiens aanzwezigheid de wil van sterke helden van het land verdraait, hen naar slechte gewoonten en luiheid leidend! Het beest is een grootse draak met immense kracht, bestaande uit de schaduw zelf: Ondeugd, de veradelijke Schaduw Serpent. Dappere Habiteers, sta op en versla dit vuile beest voor eens en altijd, maar alleen als je gelooft dat je opkan tegen zijn immense kracht.
Ondeugd Deel 1:
Hoe kan je verwachten te vechten tegen zo'n beest wanneer het jou al in zijn macht heeft? Val niet ten slachtoffer aan luiheid en ondeugd! Werk hard om te vechten tegen de duistere invloed van de draak en verdrijf zijn greep op je!
",
"questVice1Boss": "De Schaduw van Ondeugd",
"questVice1DropVice2Quest": "Ondeugd deel 2 (perkamentrol)",
"questVice2Text": "Ondeugd, deel 2: Vind het Hol van de Draak",
@@ -78,17 +78,17 @@
"questMoonstone1CollectMoonstone": "Maanstenen",
"questMoonstone1DropMoonstone2Quest": "De Maanstenen Ketting deel 2: Recidive de Dodenbezweerder (perkamentrol)",
"questMoonstone2Text": "De Maanstenen Ketting, deel 2: Recidive de Dodenbezweerder",
- "questMoonstone2Notes": "The brave weaponsmith @Inventrix helps you fashion the enchanted moonstones into a chain. You’re ready to confront Recidivate at last, but as you enter the Swamps of Stagnation, a terrible chill sweeps over you.
Rotting breath whispers in your ear. \"Back again? How delightful...\" You spin and lunge, and under the light of the moonstone chain, your weapon strikes solid flesh. \"You may have bound me to the world once more,\" Recidivate snarls, \"but now it is time for you to leave it!\"",
+ "questMoonstone2Notes": "De dappere wapensmid @Inventrix helpt je om van de betoverde maanstenen een ketting te maken. Je bent eindelijk klaar om Herhaling te confronteren, maar zodra je de Moerassen van Stagnatie betreedt, komt er een verschrikkelijke kou over je heen.
Een rottende adem fluistert in je oor. \"Ben je alweer terug? Wat verrukkelijk...\" Je draait je om en haalt uit, en onder het licht van de maanstenen ketting raakt je wapen vast vlees. \"Je hebt mij misschien weer aan de wereld gebonden,\" snauwt Herhaling, \"maar nu is het tijd voor jou om die te verlaten!\"",
"questMoonstone2Boss": "De Dodenbezweerder",
"questMoonstone2DropMoonstone3Quest": "De Maanstenen Ketting deel 3: Recidive Getransformeerd (perkamentrol)",
"questMoonstone3Text": "De Maanstenen Ketting, deel 3: Recidive Getransformeerd",
- "questMoonstone3Notes": "Recidivate crumples to the ground, and you strike at her with the moonstone chain. To your horror, Recidivate seizes the gems, eyes burning with triumph.
\"Foolish creature of flesh!\" she shouts. \"These moonstones will restore me to a physical form, true, but not as you imagined. As the full moon waxes from the dark, so too does my power flourish, and from the shadows I summon the specter of your most feared foe!\"
A sickly green fog rises from the swamp, and Recidivate’s body writhes and contorts into a shape that fills you with dread – the undead body of Vice, horribly reborn.",
+ "questMoonstone3Notes": "Herhaling valt neer, en je haalt naar haar uit met de maanstenen ketting. Tot je afschuw grijpt Herhaling de stenen, haar ogen brandend van triomf.
\"Jij dwaas schepsel van vlees!\" roept ze. \"Deze maanstenen zullen me inderdaad tot een fysieke vorm herleiden, dat is waar, maar niet zoals jij wel had gedacht. Zodra de volle maan toeneemt vanuit het donker, zo zal ook mijn macht groeien, en van de schaduwen zal ik de geest van je meest gevreesde vijand oproepen!\"
Een ziekelijk groene mist stijgt op van het moeras, en het lichaam van Herhaling schokt en verwringt zich tot Ondeugd, herboren op de meest verschrikkelijke manier.",
"questMoonstone3Completion": "Your breath comes hard and sweat stings your eyes as the undead Wyrm collapses. The remains of Recidivate dissipate into a thin grey mist that clears quickly under the onslaught of a refreshing breeze, and you hear the distant, rallying cries of Habiticans defeating their Bad Habits for once and for all.
@Baconsaur the beast master swoops down on a gryphon. \"I saw the end of your battle from the sky, and I was greatly moved. Please, take this enchanted tunic – your bravery speaks of a noble heart, and I believe you were meant to have it.\"",
"questMoonstone3Boss": "Herrezen Ondeugd",
"questMoonstone3DropRottenMeat": "Bedorven vlees (voedsel)",
"questMoonstone3DropZombiePotion": "Zombie-uitbroedtoverdrankje",
"questGoldenknight1Text": "De Gouden Ridder, deel 1: Een Strenge Berisping",
- "questGoldenknight1Notes": "The Golden Knight has been getting on poor Habiticans' cases. Didn't do all of your Dailies? Checked off a negative Habit? She will use this as a reason to harass you about how you should follow her example. She is the shining example of a perfect Habitican, and you are naught but a failure. Well, that is not nice at all! Everyone makes mistakes. They should not have to be met with such negativity for it. Perhaps it is time you gather some testimonies from hurt Habiticans and give the Golden Knight a stern talking-to!",
+ "questGoldenknight1Notes": "De Gouden Ridder is arme Habiticanen lastig aan het vallen. Heb je niet al je Dagelijkse Taken gedaan? Negatieve Gewoonten afgevinkt? Ze zal dit als een reden gebruiken om je lastig te vallen over hoe je haar voorbeeld moet volgen. Zij is het schoolvoorbeeld van een perfecte Habiticaan, en jij ben niks anders dan een mislukking. Nou, dat is helemaal niet aardig! Iedereen maakt fouten. Daar hoef je niet zo'n negativiteit voor te krijgen. Misschien is het tijd dat je een aantal getuigenissen van gekwetste Habiticanen moet verzamelen om zo de Gouden Ridder een strenge berisping te geven!",
"questGoldenknight1CollectTestimony": "Getuigenissen",
"questGoldenknight1DropGoldenknight2Quest": "De Gouden Ridder-serie deel 2: Gouden Ridder (perkamentrol)",
"questGoldenknight2Text": "De Gouden Ridder, deel 2: Gouden Ridder",
@@ -292,21 +292,33 @@
"questMonkeyBoss": "Monsterlijke Mandril",
"questMonkeyDropMonkeyEgg": "Aap (ei)",
"questMonkeyUnlockText": "Maakt het kopen van apeneieren in de markt mogelijk",
- "questSnailText": "The Snail of Drudgery Sludge",
- "questSnailNotes": "You're excited to begin questing in the abandoned Dungeons of Drudgery, but as soon as you enter, you feel the ground under your feet start to suck at your boots. You look up to the path ahead and see Habiticans mired in slime. @Overomega yells, \"They have too many unimportant tasks and dailies, and they're getting stuck on things that don't matter! Pull them out!\"
\"You need to find the source of the ooze,\" @Pfeffernusse agrees, \"or the tasks that they cannot accomplish will drag them down forever!\"
Pulling out your weapon, you wade through the gooey mud.... and encounter the fearsome Snail of Drudgery Sludge.",
+ "questSnailText": "De Slak van de Geestdodende Drab",
+ "questSnailNotes": "Je kijkt ernaar uit om te beginnen queestes te doen in de verlaten Kerkers van Geestdodendheid, maar zodra je binnengaat voel je de grond onder je voeten aan je laarzen trekken. Je kijkt op naar het pad voor je en ziet Habiticanen gedrenkt in slijm. @Overomega roept: \"Ze hebben te veel onbelangrijke taken, dagelijks of niet, en ze raken vast in dingen die er niet toe doen! Trek ze eruit!\"
\"Je moet de bron van de drek vinden,\" voegt @Pfeffernusse eraan toe, \"of de taken die ze niet kunnen doen zullen hen voor altijd naar beneden trekken!\"
Je grijpt je wapen en waadt door de slijmerige modder... En ontmoet de schrikwekkende Slak van de Geestdodende Drab.",
"questSnailCompletion": "You bring your weapon down on the great Snail's shell, cracking it in two, releasing a flood of water. The slime is washed away, and the Habiticans around you rejoice. \"Look!\" says @Misceo. \"There's a small group of snail eggs in the remnants of the muck.\"",
- "questSnailBoss": "Snail of Drudgery Sludge",
+ "questSnailBoss": "Slak van de Geestdodende Drab",
"questSnailDropSnailEgg": "Slak (ei)",
"questSnailUnlockText": "Maakt het kopen van slakken-eieren in de markt mogelijk",
- "questBewilderText": "The Be-Wilder",
- "questBewilderNotes": "The party begins like any other.
The appetizers are excellent, the music is swinging, and even the dancing elephants have become routine. Habiticans laugh and frolic amid the overflowing floral centerpieces, happy to have a distraction from their least-favorite tasks, and the April Fool whirls among them, eagerly providing an amusing trick here and a witty twist there.
As the Mistiflying clock tower strikes midnight, the April Fool leaps onto the stage to give a speech.
“Friends! Enemies! Tolerant acquaintances! Lend me your ears.” The crowd chuckles as animal ears sprout from their heads, and they pose with their new accessories.
“As you know,” the Fool continues, “my confusing illusions usually only last a single day. But I’m pleased to announce that I’ve discovered a shortcut that will guarantee us non-stop fun, without having to deal with the pesky weight of our responsibilities. Charming Habiticans, meet my magical new friend... the Be-Wilder!”
Lemoness pales suddenly, dropping her hors d'oeuvres. “Wait! Don’t trust--”
But suddenly mists are pouring into the room, glittering and thick, and they swirl around the April Fool, coalescing into cloudy feathers and a stretching neck. The crowd is speechless as an monstrous bird unfolds before them, its wings shimmering with illusions. It lets out a horrible screeching laugh.
“Oh, it has been ages since a Habitican has been foolish enough to summon me! How wonderful it feels, to have a tangible form at last.”
Buzzing in terror, the magic bees of Mistiflying flee the floating city, which sags from the sky. One by one, the brilliant spring flowers wither up and wisp away.
“My dearest friends, why so alarmed?” crows the Be-Wilder, beating its wings. “There’s no need to toil for your rewards any more. I’ll just give you all the things that you desire!”
A rain of coins pours from the sky, hammering into the ground with brutal force, and the crowd screams and flees for cover. “Is this a joke?” Baconsaur shouts, as the gold smashes through windows and shatters roof shingles.
PainterProphet ducks as lightning bolts crackle overhead, and fog blots out the sun. “No! This time, I don’t think it is!”
Quickly, Habiticans, don’t let this World Boss distract us from our goals! Stay focused on the tasks that you need to complete so we can rescue Mistiflying -- and hopefully, ourselves.",
- "questBewilderCompletion": "The Be-Wilder is DEFEATED!
We've done it! The Be-Wilder lets out a ululating cry as it twists in the air, shedding feathers like falling rain. Slowly, gradually, it coils into a cloud of sparkling mist. As the newly-revealed sun pierces the fog, it burns away, revealing the coughing, mercifully human forms of Bailey, Matt, Alex.... and the April Fool himself.
Mistiflying is saved!
The April Fool has enough shame to look a bit sheepish. “Oh, hm,” he says. “Perhaps I got a little…. carried away.”
The crowd mutters. Sodden flowers wash up on sidewalks. Somewhere in the distance, a roof collapses with a spectacular splash.
“Er, yes,” the April Fool says. “That is. What I meant to say was, I’m dreadfully sorry.” He heaves a sigh. “I suppose it can’t all be fun and games, after all. It might not hurt to focus occasionally. Maybe I’ll get a head start on next year’s pranking.”
Redphoenix coughs meaningfully.
“I mean, get a head start on this year’s spring cleaning!” the April Fool says. “Nothing to fear, I’ll have Habit City in spit-shape soon. Luckily nobody is better than I at dual-wielding mops.”
Encouraged, the marching band starts up.
It isn’t long before all is back to normal in Habit City. Plus, now that the Be-Wilder has evaporated, the magical bees of Mistiflying bustle back to work, and soon the flowers are blooming and the city is floating once more.
As Habiticans cuddle the magical fuzzy bees, the April Fool’s eyes light up. “Oho, I’ve had a thought! Why don’t you all keep some of these fuzzy Bee Pets and Mounts? It’s a gift that perfectly symbolizes the balance between hard work and sweet rewards, if I’m going to get all boring and allegorical on you.” He winks. “Besides, they don’t have stingers! Fool’s honor.”",
+ "questBewilderText": "De Wees-Wilder",
+ "questBewilderNotes": "Het feest begint zoals elk feest.
De aperitieven zijn excellent, de muziek swingt de pan uit, en zelfs de dansende olifanten zijn een routine geworden. Habiticanen lachen en dartelen tussen de overvolle bloemrijke middenstukken, blij met de afleiding van hun minst favoriete taken, en de Aprilzot draait rond tussen hen in, gretig hier een amusant trukje en daar een slimme kronkel leverend.
Wanneer de klok van Mistiflying middernacht slaat, springt de Aprilzot op het podium om een speech te geven.
\"Vrienden! Vijanden! Tolerante kennissen! Leen mij uw oren.\" Het publiek grinnikt, dierenoren ontspruiten aan hun hoofd, en ze pronken met hun nieuwe accessoires.
\"Zoals jullie allemaal weten,\" ging de Zot verder, \"houden mijn verwarrende illusies het gewoonlijk maar een enkele dag uit. Maar ik ben verheugd om met jullie mee te delen dat ik een betere manier heb gevonden die ons non-stop plezier zal bezorgen, zonder dat irritante gewicht van onze verantwoordelijkheden. Lieve Habiticanen, ontmoet mijn nieuwe magische vriend... De Wees-Wilder!\"
Lemoness wordt plotseling lijkblijk, en laat haar voorgerechtjes vallen. \"Wacht! Vertrouw niet op--\"
Maar plotseling stromen er wolken mist de kamer binnen, schitterend en dik, en ze draaien rond de Aprilzot, samenkomend in wolkachtige veren en een uitgestrekte nek. De menigte staat met zijn mond vol tanden terwijl een monsterlijke vogel voor hen verschijnt, vleugels glinsterend met illusies. Het stoot een verschrikkelijke, krijsende lach uit.
\"Oh, het is al eeuwen geleden sinds een Habiticaan dom genoeg was om mij op te roepen! Hoe geweldig het voelt om eindelijk weer een tastbare vorm te hebben.\"
Paniekerig zoemend vluchten de bijen van Mistiflying de zwevende stad, die langzaam uit de lucht naar beneden zakt. Eén voor één verwelken de prachtige lentebloemen en verdwijnen ze.
\"Mijn liefste vrienden, waarom zo gealarmeerd?\" jubelt de Wees-Wilder, met zijn vleugels slaand. \"Je moet nu niet meer voor je beloningen werken. Ik zal je gewoon alles geven waarnaar je verlangt!\"
Een regen van munten valt neer uit de hemel en hamert neer op de grond met brute kracht, en de menigte gilt het uit en zoekt een veilig heenkomen. \"Is dit een grap?\" roept Baconsaur, terwijl het goud ramen vernield en dakpannen verpulvert.
PainterProphet duikt weg wanneer bliksemschichten boven zijn hoofd knetteren en mist de zon blokkeert. \"Nee! Deze keer denk ik van niet!\"
Snel, Habiticanen, laten we deze Wereldbaas ons niet afleiden van onze doelen! Blijf gefocust op de taken die je moet doen zodat we Mistiflying kunnen redden -- en hopelijk, onszelf.",
+ "questBewilderCompletion": "De Wees-Wilder is VERSLAGEN!
We hebben het gedaan! De Wees-Wilder stoot een huilende schreeuw uit wanneer hij in de lucht kronkelt, veren verliezend alsof het regen was. Langzaamaan verandert hij in een wolk glinsterende mist. Zodra de opnieuw tevoorschijn gekomen zon door de mist schijnt, brandt die weg, de menselijke hoestende vormen onthullend van Bailey, Matt, Alex... En de Aprilzot zelf. Mistiflying is gered! De Aprilzot schaamt zich genoeg om een beetje schaapachtig te kijken. \"Oh, hm,\" zegt hij, \"ik was misschien een beetje... te enthusiast.\"
De menigte mompelt. Doorweekte bloemen spoelen aan op de stoep. Ergens in de verte stort een dak in met een spectaculaire plons. \"Ehm, yep,\" zegt de Aprilzot. \"Dat ehm. Wat ik wilde zeggen was... Het spijt me verschrikkelijk.\" Hij slaakt een zucht. \"Ik neem aan dat het niet allemaal maar altijd leuk kan zijn. Het zal wel geen zeer doen om af en toe te focussen. Misschien zal ik zelfs een voorsprong krijgen bij de grappen van volgend jaar.\"
Redphoenix hoest veelbetekenend.
\"Ik bedoel, een voorsprong op de lenteschoonmaak dit jaar!\" zegt de Aprilzot. \"Vrees niet, ik zal Habit Stad in geen tijd in topvorm hebben. Gelukkig is er niemand beter in met twee dweilen tegelijk werken dan ik.\"
Aangemoedigd start de fanfare op.
Het duurt niet al te lang voordat alles weer normaal is in Habit Stad. Plus, nu dat de Wees-Wilder verdampt is, gaan de bijen van Mistiflying weer druk aan het werk, en weldra bloeien de bloemen en de stad zweeft opnieuw.
Terwijl Habiticanen met de zachte magische bijen knuffelen, lichten de ogen van de Aprilzot op. \"Oho, ik heb een idee! Waarom houden jullie niet allemaal enkele van deze zachte Bij Huisdieren en Rijdieren? Het is een geschenk dat perfect de balans tussen hard werk en zoete beloningen weet te symboliseren, als ik dan toch helemaal saai en allegorisch met jullie ga omgaan.\" Hij knipoogt. \"Daarnaast hebben ze geen angels! Op mijn eer als zot.\"",
"questBewilderCompletionChat": "`The Be-Wilder is DEFEATED!`\n\nWe've done it! The Be-Wilder lets out a ululating cry as it twists in the air, shedding feathers like falling rain. Slowly, gradually, it coils into a cloud of sparkling mist. As the newly-revealed sun pierces the fog, it burns away, revealing the coughing, mercifully human forms of Bailey, Matt, Alex.... and the April Fool himself.\n\n`Mistiflying is saved!`\n\nThe April Fool has enough shame to look a bit sheepish. “Oh, hm,” he says. “Perhaps I got a little…. carried away.”\n\nThe crowd mutters. Sodden flowers wash up on sidewalks. Somewhere in the distance, a roof collapses with a spectacular splash.\n\n“Er, yes,” the April Fool says. “That is. What I meant to say was, I’m dreadfully sorry.” He heaves a sigh. “I suppose it can’t all be fun and games, after all. It might not hurt to focus occasionally. Maybe I’ll get a head start on next year’s pranking.”\n\nRedphoenix coughs meaningfully.\n\n“I mean, get a head start on this year’s spring cleaning!” the April Fool says. “Nothing to fear, I’ll have Habit City in spit-shape soon. Luckily nobody is better than I at dual-wielding mops.”\n\nEncouraged, the marching band starts up.\n\nIt isn’t long before all is back to normal in Habit City. Plus, now that the Be-Wilder has evaporated, the magical bees of Mistiflying bustle back to work, and soon the flowers are blooming and the city is floating once more.\n\nAs Habiticans cuddle the magical fuzzy bees, the April Fool’s eyes light up. “Oho, I’ve had a thought! Why don’t you all keep some of these fuzzy Bee Pets and Mounts? It’s a gift that perfectly symbolizes the balance between hard work and sweet rewards, if I’m going to get all boring and allegorical on you.” He winks. “Besides, they don’t have stingers! Fool’s honor.”",
- "questBewilderBossRageTitle": "Beguilement Strike",
- "questBewilderBossRageDescription": "When this gauge fills, The Be-Wilder will unleash its Beguilement Strike on Habitica!",
- "questBewilderDropBumblebeePet": "Bumblebee (Pet)",
- "questBewilderDropBumblebeeMount": "Bumblebee (Mount)",
- "questBewilderBossRageMarket": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nOh no! Despite our best efforts, we've gotten distracted by the Be-Wilder’s charming illusions and have forgotten to do some of our Dailies! With a cackling cry, the shining bird beats its wings, raising a swarm of mist around Alex the Merchant. When the fog clears, he has been possessed! “Have some free samples!” he shouts gleefully, and begins to hurl exploding eggs and potions at fleeing Habiticans. Not the most favorable of sales, to be sure.\n\nHurry! Let's stay focused on our Dailies to defeat this monster before it possesses someone else.",
- "questBewilderBossRageStables": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nAhh!!! Once again the Be-Wilder has dazzled us into neglecting our Dailies, and now it has attacked Matt the Beast Master! With a swirl of mist, Matt transforms into a terrifying winged creature, and all the pets and mounts howl sadly in their stables. Quickly, stay focused on your tasks to defeat this dastardly distraction!",
- "questBewilderBossRageBailey": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nLook out! In the middle of reporting the news, Bailey the Town Crier has been possessed by the Be-Wilder! She lets out an evil, uninformative screech as she rises into the air. Now how will we know what’s going on?\n\nDon't give up... we're so close to defeating this bothersome bird for once and for all!"
+ "questBewilderBossRageTitle": "Betoverende Slag",
+ "questBewilderBossRageDescription": "Als deze meter vult, zal de Wees-Wilder zijn Betoverende Slag op Habitica loslaten!",
+ "questBewilderDropBumblebeePet": "Magische Bij (Huisdier)",
+ "questBewilderDropBumblebeeMount": "Magische Bij (Rijdier)",
+ "questBewilderBossRageMarket": "'De Wees-Wilder gebruikt BETOVEREND SLAG!'\n\nOh nee! Ondanks onze inspanningen, zijn we afgeleid door de charmante illusies van de Wees-Wilder en hebben vergeten een aantal van onze Dagelijkse Taken te doen! Met een kakelende kreet, slaat de stralende vogel zijn vleugels en brengt een zwerm van mist om Alex de Koopman. Wanneer de mist opklaart, is hij bezeten! \"Neem wat gratis monsters!\" roept hij opgewekt, en begint exploderende eieren en toverdranken te gooien naar vluchtende Habiticanen. Niet de meest gunstige manier van verkopen, om zeker te zijn.\n\nSchiet op! Laten we geconcentreerd blijven op onze Dagelijkse Taken om dit monster te verslaan voordat het iemand anders bezit. ",
+ "questBewilderBossRageStables": "'De Wees-Wilder gebruikt BETOVERENDE SLAG!'\n\nAhh!!! Eens te meer heeft de Wees-Wilder ons verblind in het verwaarlozen van onze Dagelijkse Taken, en nu heeft het Matt de Dierenmeester aangevallen! Met een werveling van mist, transformeert Matt in een angstaanjagend gevleugeld wezen, en al de huisdieren en rijdieren huilen jammerlijk in hun stallen. Snel, blijf geconcentreerd op je taken en versla deze lafhartige afleiding!\n",
+ "questBewilderBossRageBailey": "'De Wees-Wilder gebruikt BETOVERENDE SLAG!'\n\nKijk uit! In het midden van zijn nieuwrapportage, is Bailey de Stadsomroeper bezeten door de Wees-Wilder! Ze laat een duistere, niet-informatieve krijs vrij als ze de lucht in stijgt. Hoe weten we nu wat er aan de hand is?\n\nGeef niet op... we zijn zo dichtbij in het verslaan van deze lastige vogel voor eens en altijd!",
+ "questFalconText": "De Vogels van Uitstel",
+ "questFalconNotes": "Berg Habitica is klein naast een enorme berg met To-do's. Het was ooit een plek om te picknicken en te genieten van een gevoel van volmaaktheid, totdat de verwaarloosde taken uit de hand liepen. Nu wonen er angstaanjagende Vogels van Uitstel, weerzinwekkende beesten die Habiticanen tegenhouden om taken te voltooien!
\"Het is te moeilijk!\" klauwen ze naar @JonArinbjorn en @Onheiron. \"Het duurt te lang om nu te doen! Het maakt niet uit dat je tot morgen wacht! Waarom ga je niet iets leukers doen?\"
Dat stopt nu, neem je je voor. Je beklimt je persoonlijke berg met To-do's en verslaat de Vogels van Uitstel!",
+ "questFalconCompletion": "Eindelijk, na je overwinning op de Vogels van Uitstel, ga je zitten om van het uitzicht en van je welverdiende rust te genieten.
\"Wauw!\" zegt @Trogdorina. \"Je hebt gewonnen!\"
@Squish voegt eraan toe: \"Hier, neem deze eieren die ik gevonden heb als je beloning.\"",
+ "questFalconBoss": "Vogels van Uitstel",
+ "questFalconDropFalconEgg": "Valk (Ei)",
+ "questFalconUnlockText": "Ontgrendelt het kopen van Valk eieren in de Markt. ",
+ "questTreelingText": "The Tangle Tree",
+ "questTreelingNotes": "It's the annual Garden Competition, and everyone is talking about the mysterious project which @aurakami has promised to unveil. You join the crowd on the day of the big announcement, and marvel at the introduction of a moving tree. @fuzzytrees explains that the tree will help with garden maintenance, showing how it can mow the lawn, trim the hedge and prune the roses all at the same time – until the tree suddenly goes wild, turning its secateurs on its creator! The crowd panics as everyone tries to flee, but you aren't afraid – you leap forward, ready to do battle.",
+ "questTreelingCompletion": "You dust yourself off as the last few leaves drift to the floor. In spite of the upset, the Garden Competition is now safe – although the tree you just reduced to a heap of wood chips won't be winning any prizes! \"Still a few kinks to work out there,\" @PainterProphet says. \"Perhaps someone else would do a better job of training the saplings. Do you fancy a go?\"",
+ "questTreelingBoss": "Tangle Tree",
+ "questTreelingDropTreelingEgg": "Treeling (Egg)",
+ "questTreelingUnlockText": "Unlocks purchasable Treeling eggs in the Market"
}
\ No newline at end of file
diff --git a/common/locales/nl/rebirth.json b/common/locales/nl/rebirth.json
index 5e49679373..1b991589c0 100644
--- a/common/locales/nl/rebirth.json
+++ b/common/locales/nl/rebirth.json
@@ -5,7 +5,7 @@
"rebirthStartOver": "Hergeboorte start je personage opnieuw vanaf niveau 1.",
"rebirthAdvList1": "Je krijgt je volle gezondheid terug.",
"rebirthAdvList2": "Je hebt geen ervaring, goud, of uitrusting (met uitzondering van gratis items zoals verrassingsartikelen).",
- "rebirthAdvList3": "Je Gewoontes, Dagelijkse Taken en To-do's zijn op geel gezet, en series zijn teruggezet op nul.",
+ "rebirthAdvList3": "Your Habits, Dailies, and To-Dos reset to yellow, and streaks reset, except for challenge tasks.",
"rebirthAdvList4": "Je hebt de startklasse van Krijger tot je een nieuwe klasse verwerft.",
"rebirthInherit": "Je nieuwe personage erft een paar dingen van zijn voorganger:",
"rebirthInList1": "Taken, geschiedenis en instellingen blijven behouden.",
@@ -24,5 +24,6 @@
"rebirthPop": "Begin een nieuw personage vanaf niveau 1 terwijl je je prestaties, verzamelobjecten en taken met hun geschiedenis behoudt.",
"rebirthName": "Bol der Hergeboorte",
"reborn": "Herboren, maximale niveau <%= reLevel %>",
- "confirmReborn": "Weet je het zeker?"
+ "confirmReborn": "Weet je het zeker?",
+ "rebirthComplete": "Je bent herboren!"
}
\ No newline at end of file
diff --git a/common/locales/nl/settings.json b/common/locales/nl/settings.json
index 71db5872f3..ed28bcebd5 100644
--- a/common/locales/nl/settings.json
+++ b/common/locales/nl/settings.json
@@ -47,6 +47,7 @@
"customDayStart": "Begin van de dag aanpassen",
"changeCustomDayStart": "Begin van de dag aanpassen? ",
"sureChangeCustomDayStart": "Weet je zeker dat je het begin van de dag wilt aanpassen?",
+ "customDayStartHasChanged": "Your custom day start has changed.",
"nextCron": "Je dagelijkse taken worden gereset de eerste keer dat je Habitica gebruikt na <%= time %>. Zorg ervoor dat je je dagelijkse taken voor die tijd afrondt!",
"customDayStartInfo1": "Standaard controleert en reset Habitica je dagelijkse taken elke dag om middernacht. Je kunt dit hier aanpassen.",
"misc": "Diversen",
@@ -61,12 +62,23 @@
"newUsername": "Nieuwe inlognaam",
"dangerZone": "Gevarenzone",
"resetText1": "WAARSCHUWING! Deze functie reset veel onderdelen van je account. Hoewel we niet aanraden om dit te doen, vinden sommige mensen het handig na een tijdje geëxperimenteerd te hebben met de site.",
- "resetText2": "Je verliest al je niveaus, goud en ervaringspunten. Al je taken worden permanent verwijderd en je verliest de geschiedenisdata van je taken. Je verliest je hele uitrusting maar je kunt deze weer terugkopen in de winkel, ook alle uitrusting met beperkte oplage en de verrassingsartikelen voor abonnees (je moet wel de juiste klasse zijn om ze terug te kunnen kopen). Je houdt wel je huidige huis- en rijdieren. Het is misschien een goed idee om een Bol der Hergeboorte te gebruiken, want dat is een veel veiligere optie die ook je taken bewaart.",
+ "resetText2": "You will lose all your levels, gold, and experience points. All your tasks (except those from challenges) will be deleted permanently and you will lose all of their historical data. You will lose all your equipment but you will be able to buy it all back, including all limited edition equipment or subscriber Mystery items that you already own (you will need to be in the correct class to re-buy class-specific gear). You will keep your current class and your pets and mounts. You might prefer to use an Orb of Rebirth instead, which is a much safer option and which will preserve your tasks.",
"deleteText": "Weet je het zeker? Je account wordt voorgoed verwijderd en kan nooit meer hersteld worden! Je zult een nieuw account moeten aanmaken om Habitica opnieuw te gebruiken. Gespaarde of uitgegeven edelstenen worden niet vergoed. Als je het absoluut zeker weet, typ dan <%= deleteWord %> in het tekstvak hieronder.",
"API": "API",
+ "APIv3": "API v3",
+ "APIv2": "API v2 - Deprecated",
"APIText": "Kopieer deze codes om ze in applicaties van derde partijen te gebruiken. Beschouw de codes echter als een soort wachtwoord en deel ze niet met anderen. Je kunt soms gevraagd worden naar je gebruikers-ID maar we raden aan nooit je API-sleutel op een openbare plek te delen, ook niet op Github.",
"APIToken": "API-sleutel (dit is een persoonlijke code - zie waarschuwing hierboven!)",
+ "thirdPartyApps": "Applicaties van derden",
+ "dataToolDesc": "Een webpagina die je bepaalde informatie van je Habitica account laat zien, zoals statistieken over je taken, uitrusting en vaardigheden.",
+ "beeminder": "Beeminder",
+ "beeminderDesc": "Laat Beeminder automatisch je Habitica To-do's controleren. Je kan richten op het halen van een voorgenomen aantal voltooide To-do's per dag of per week, of je kan je wijden aan het langzaam verlagen van je resterende aantal onvoltooide To-do's. (Met \"toewijden\" bedoelt Beeminder onder dreiging van het betalen van echt geld! Maar je kan ook gewoon Beeminders fancy grafieken leuk vinden.)",
+ "chromeChatExtension": "Chrome Chat Extensie",
+ "chromeChatExtensionDesc": "De Chrome Chat Extensie voor Habitica voegt een intuïtieve chat box toe aan heel habitica.com. Het laat gebruikers chatten in de Herberg, hun groep en andere gildes waar ze inzitten.",
+ "otherExtensions": "Andere extensies",
+ "otherDesc": "Vind andere apps, extensies en hulpmiddelen op de Habitica wiki.",
"resetDo": "Ja, reset mijn account!",
+ "resetComplete": "Reset complete!",
"fixValues": "Waarden bijstellen",
"fixValuesText1": "Als je personage door een bug of een fout van jezelf oneerlijk veranderd is (schade opgelopen die je niet had moeten krijgen, goud gekregen dat je niet echt verdiend hebt, enz.), dan kun je je waardes hier handmatig aanpassen. Ja, dit maakt valsspelen mogelijk: gebruik deze functie verstandig, want anders saboteer je je gewoontes!",
"fixValuesText2": "Let wel dat je hier geen series op individuele taken kunt herstellen. Om dat te doen, moet je de Dagelijkse Taak openen om te bewerken. Onder Geavanceerde opties vind je Serie herstellen.",
@@ -96,6 +108,7 @@
"emailNotifications": "E-mailberichten",
"wonChallenge": "Je hebt een uitdaging gewonnen!",
"newPM": "Privébericht ontvangen",
+ "sentGems": "Sent gems!",
"giftedGems": "Geschonken edelstenen",
"giftedGemsInfo": "<%= amount %> edelstenen - van <%= name %>",
"giftedSubscription": "Geschonken abonnement",
@@ -135,6 +148,11 @@
"webhooks": "Webhooks",
"enabled": "Ingeschakeld",
"webhookURL": "Webhook-URL",
+ "invalidUrl": "ongeldige url",
+ "invalidEnabled": "the \"enabled\" parameter should be a boolean",
+ "regIdRequired": "RegId is required",
+ "pushDeviceAdded": "Push device added successfully",
+ "pushDeviceAlreadyAdded": "The user already has the push device",
"add": "Toevoegen",
"buyGemsGoldCap": "Capaciteit verhoogd naar <%= amount %>",
"mysticHourglass": "<%= amount %> mystieke zandloper(s)",
@@ -149,5 +167,5 @@
"amazonPayments": "Amazon-betalingen",
"timezone": "Tijdzone",
"timezoneUTC": "Habitica gebruikt de tijdzone die op je PC ingesteld is: <%=utc %>",
- "timezoneInfo": "Als die tijdzone verkeerd is, herlaad dan eerst deze pagina met je browsers \"herlaad of verversen\"-knop om er zeker van te zijn dat Habitica de nieuwste informatie heeft. Als het nu nog niet klopt, pas dan de tijdzone op je PC aan en laadt deze pagina opnieuw.
Als je Habitica op andere PC's of mobiele apparaten gebruikt, moet de tijdzone overal hetzelfde zijn. Als je dagelijkse taken gereset zijn op de verkeerde tijd, herhaal dan deze controle op alle andere PC's en in een browser op je mobiele apparaat."
+ "timezoneInfo": "If that time zone is wrong, first reload this page using your browser's reload or refresh button to ensure that Habitica has the most recent information. If it is still wrong, adjust the time zone on your PC and then reload this page again.
If you use Habitica on other PCs or mobile devices, the time zone must be the same on them all. If your Dailies have been resetting at the wrong time, repeat this check on all other PCs and on a browser on your mobile devices."
}
\ No newline at end of file
diff --git a/common/locales/nl/spells.json b/common/locales/nl/spells.json
index 25e0eaf3e0..c477664cd2 100644
--- a/common/locales/nl/spells.json
+++ b/common/locales/nl/spells.json
@@ -21,7 +21,7 @@
"spellRogueBackStabNotes": "Je verraadt een dwaze taak. Je ontvangt goud en ervaringspunten! Klik op een taak om de spreuk uit te spreken. (Gebaseerd op Kracht.)",
"spellRogueToolsOfTradeText": "Dievenkneepjes",
"spellRogueToolsOfTradeNotes": "Je deelt je kennis met de groep. De Perceptie van je groep wordt versterkt! (Gebaseerd op oorspronkelijke Perceptie zonder bonussen.)",
- "spellRogueStealthText": "Heimelijkheid ",
+ "spellRogueStealthText": "Heimelijkheid",
"spellRogueStealthNotes": "Je schuilt in de schaduw en bent onzichtbaar. Sommige onafgemaakte Dagelijkse Taken zullen je deze nacht niet kunnen vinden en hun roodheid en steries zullen niet veranderen. (Spreek meerdere keren uit om meer Dagelijkse Taken te beïnvloeden.)",
"spellHealerHealText": "Helend Licht",
"spellHealerHealNotes": "Licht bedekt je lichaam en heelt je wonden. Je krijgt er gezondheidspunten bij. (Gebaseerd op Lichaam en Intelligentie.)",
@@ -35,8 +35,8 @@
"spellSpecialSnowballAuraNotes": "Gooi een sneeuwbal naar een groepslid! Wat kan er nou misgaan? Duurt tot een nieuwe dag aanbreekt voor dit groepslid.",
"spellSpecialSaltText": "Zout",
"spellSpecialSaltNotes": "Iemand heeft een sneeuwbal naar je gegooid. Ha ha, heel grappig. Help me nu die sneeuw van me af te krijgen!",
- "spellSpecialSpookDustText": "Spookachtige Glitters",
- "spellSpecialSpookDustNotes": "Verander een vriend in een zwevend laken met ogen!",
+ "spellSpecialSpookySparklesText": "Spooky Sparkles",
+ "spellSpecialSpookySparklesNotes": "Turn a friend into a floating blanket with eyes!",
"spellSpecialOpaquePotionText": "Ondoorzichtig toverdrankje",
"spellSpecialOpaquePotionNotes": "Verwijder het effect van Spookachtige Glitters.",
"spellSpecialShinySeedText": "Glanzend zaadje",
@@ -46,5 +46,11 @@
"spellSpecialSeafoamText": "Zeeschuim",
"spellSpecialSeafoamNotes": "Verander een vriend in een zeedier!",
"spellSpecialSandText": "Zand",
- "spellSpecialSandNotes": "Verwijder de effecten van Zeeschuim."
-}
+ "spellSpecialSandNotes": "Verwijder de effecten van Zeeschuim.",
+ "spellNotFound": "Vaardigheid \"<%= spellId %>\" niet gevonden.",
+ "partyNotFound": "Groep niet gevonden",
+ "targetIdUUID": "\"targetId\" must be a valid User ID.",
+ "challengeTasksNoCast": "Casting a skill on challenge tasks is not allowed.",
+ "spellNotOwned": "You don't own this skill.",
+ "spellLevelTooHigh": "You must be level <%= level %> to use this skill."
+}
\ No newline at end of file
diff --git a/common/locales/nl/subscriber.json b/common/locales/nl/subscriber.json
index 18a8c700d0..ea2491c4c8 100644
--- a/common/locales/nl/subscriber.json
+++ b/common/locales/nl/subscriber.json
@@ -4,6 +4,8 @@
"subDescription": "Koop edelstenen met goud, ontvang maandelijkse verrassingsartikelen, behoud je voortgangsgeschiedenis, verdubbel je maximum aantal dagelijkse vondsten, ondersteun de ontwikkelaars. Klik voor meer informatie.",
"buyGemsGold": "Edelstenen kopen met goud",
"buyGemsGoldText": "Alexander de koopman verkoopt je edelstenen voor de prijs van <%= gemCost %> goud per edelsteen. Zijn maandelijkse leveringen zijn in eerste instantie gelimiteerd tot <%= gemLimit %> edelstenen per maand, maar deze limiet wordt met 5 edelstenen verhoogd voor elke drie maanden aaneengesloten abonnement, tot een maximum van 50 edelstenen per maand!",
+ "mustSubscribeToPurchaseGems": "Must subscribe to purchase gems with GP",
+ "reachedGoldToGemCap": "You've reached the Gold=>Gem conversion cap <%= convCap %> for this month. We have this to prevent abuse / farming. The cap will reset within the first three days of next month.",
"retainHistory": "Volledige extra geschiedenis behouden",
"retainHistoryText": "Maakt je afgeronde to-do's en taakgeschiedenis langer beschikbaar.",
"doubleDrops": "Dagelijkse vondstmaximum verdubbelen",
@@ -29,6 +31,7 @@
"manageSub": "Klik om je abonnement te beheren",
"cancelSub": "Abonnement stopzetten",
"canceledSubscription": "Beëindigd abonnement",
+ "cancelingSubscription": "Canceling the subscription",
"adminSub": "Beheerdersabonnementen",
"morePlans": "Meer plannen komen binnenkort",
"organizationSub": "Privéorganisatie",
@@ -73,6 +76,9 @@
"timeTravelersPopover": "We zien dat je een mystieke zandloper hebt, dus we reizen graag terug in de tijd voor je! Kies het huisdier, rijdier of de verrassingsvoorwerpset die je wilt. Je kunt hier een lijst zien van de voorwerpsets uit het verleden! Als deze je niet bevallen, ben je dan misschien meer geïnteresseerd in een van onze modebewuste futuristische Steampunk voorwerpsets?",
"timeTravelersAlreadyOwned": "Gefeliciteerd! Je bezit alles al dat de Tijdreizigers op dit moment aanbieden. Dankjewel voor het steunen van de site!",
"mysticHourglassPopover": "Met een mystieke zandloper kun je sommige voorwerpen met een beperkte oplage kopen, zoals maandelijkse verrassingsvoorwerpsets en beloningen van Wereldbazen uit het verleden!",
+ "mysterySetNotFound": "Mystery set not found, or set already owned.",
+ "mysteryItemIsEmpty": "Mystery items are empty",
+ "mysteryItemOpened": "Mystery item opened.",
"mysterySet201402": "Gevleugelde Boodschapperset",
"mysterySet201403": "Boswandelaarset",
"mysterySet201404": "Schemervlinderset",
@@ -99,6 +105,8 @@
"mysterySet201601": "Set van de Kampioen van Standvastigheid",
"mysterySet201602": "Hartenbrekersset",
"mysterySet201603": "Gelukigzalige Klaver Set",
+ "mysterySet201604": "Leaf Warrior Set",
+ "mysterySet201605": "Marching Bard Set",
"mysterySet301404": "Standaard Steampunkset",
"mysterySet301405": "Opgesmukte Steampunkset",
"mysterySetwondercon": "Wondercon",
@@ -110,9 +118,25 @@
"hourglassBuyItemConfirm": "Wil je dit voorwerp kopen voor 1 mystieke zandloper?",
"petsAlreadyOwned": "Je hebt dit huisdier al.",
"mountsAlreadyOwned": "Je hebt dit rijdier al.",
- "typeNotAllowedHourglass": "Je kunt dit soort voowerpen niet kopen met een mystieke zandloper. Toegestane soorten:",
+ "typeNotAllowedHourglass": "Item type not supported for purchase with Mystic Hourglass. Allowed types: <%= allowedTypes %>",
"petsNotAllowedHourglass": "Huisdier kan niet worden gekocht met mystieke zandloper.",
"mountsNotAllowedHourglass": "Rijdier kan niet worden gekocht met mystieke zandloper.",
"hourglassPurchase": "Je hebt een voorwerp gekocht met een mystieke zandloper!",
- "hourglassPurchaseSet": "Je hebt een set voorwerpen gekocht met een mystieke zandloper!"
+ "hourglassPurchaseSet": "Je hebt een set voorwerpen gekocht met een mystieke zandloper!",
+ "missingUnsubscriptionCode": "Missing unsubscription code.",
+ "missingSubscription": "User does not have a plan subscription",
+ "missingSubscriptionCode": "Missing subscription code. Possible values: basic_earned, basic_3mo, basic_6mo, google_6mo, basic_12mo.",
+ "cannotDeleteActiveAccount": "You have an active subscription, cancel your plan before deleting your account.",
+ "paymentNotSuccessful": "The payment was not successful",
+ "planNotActive": "The plan hasn't activated yet (due to a PayPal bug). It will begin <%= nextBillingDate %>, after which you can cancel to retain your full benefits",
+ "notAllowedHourglass": "Pet/Mount not available for purchase with Mystic Hourglass.",
+ "readCard": "<%= cardType %> has been read",
+ "cardTypeRequired": "Card type required",
+ "cardTypeNotAllowed": "Unknown card type.",
+ "invalidCoupon": "Ongeldige coupon code.",
+ "couponUsed": "Coupon code al in gebruik",
+ "noSudoAccess": "You don't have sudo access.",
+ "couponCodeRequired": "The coupon code is required.",
+ "eventRequired": "\"req.params.event\" is required.",
+ "countRequired": "\"req.query.count\" is required."
}
\ No newline at end of file
diff --git a/common/locales/nl/tasks.json b/common/locales/nl/tasks.json
index fb8fcc5011..4426b2126a 100644
--- a/common/locales/nl/tasks.json
+++ b/common/locales/nl/tasks.json
@@ -73,6 +73,7 @@
"clearTags": "Wissen",
"hideTags": "Verbergen",
"showTags": "Weergeven",
+ "toRequired": "You must supply a to value",
"startDate": "Begindatum",
"startDateHelpTitle": "Wanneer moet deze taak beginnen?",
"startDateHelp": "Selecteer de datum waarop deze taak actief wordt. Hij hoeft vóór deze datum niet voltooid te worden.",
@@ -88,8 +89,9 @@
"fortifyName": "Versterkingsdrankje",
"fortifyPop": "Geeft alle taken een neutrale waarde (gele kleur), en herstelt alle verloren gezondheidspunten.",
"fortify": "Versterk",
- "fortifyText": "Versterken brengt al je taken terug tot een neutrale (gele) staat, alsof je ze net toegevoegd hebt, en vult je gezondheidsbalk weer helemaal aan. Dit is geweldig wanneer al die rode taken het spel te moeilijk maken, of wanneer al je blauwe taken het te makkelijk maken. Als een frisse start bemoedigend klinkt, geef dan een paar edelstenen uit en haal opgelucht adem!",
+ "fortifyText": "Fortify will return all your tasks, except challenge tasks, to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!",
"confirmFortify": "Weet je het zeker?",
+ "fortifyComplete": "Fortify complete!",
"sureDelete": "Weet je zeker dat je de <%= taskType %> met de tekst \"<%= taskText %>\" wilt verwijderen?",
"streakCoins": "seriebonus!",
"pushTaskToTop": "Zet de taak bovenaan. Houd ctrl of cmd ingedrukt om hem onderaan te zetten.",
@@ -112,5 +114,18 @@
"rewardHelp2": "Uitrusting beïnvloedt je eigenschappen (<%= linkStart %>Avatar > Eigenschappen<%= linkEnd %>).",
"rewardHelp3": "Speciale uitrusting verschijnt hier tijdens wereldwijde evenementen.",
"rewardHelp4": "Wees niet bang om je eigen beloningen toe te voegen! Kijk ook eens naar de voorbeelden hier.",
- "clickForHelp": "Klik hier voor hulp"
+ "clickForHelp": "Klik hier voor hulp",
+ "taskIdRequired": "\"taskId\" must be a valid UUID.",
+ "taskNotFound": "Taak niet gevonden.",
+ "invalidTaskType": "Task type must be one of \"habit\", \"daily\", \"todo\", \"reward\".",
+ "cantDeleteChallengeTasks": "A task belonging to a challenge can't be deleted.",
+ "checklistOnlyDailyTodo": "Checklists are supported only on dailies and todos",
+ "checklistItemNotFound": "No checklist item was found with given id.",
+ "itemIdRequired": "\"itemId\" must be a valid UUID.",
+ "tagNotFound": "No tag item was found with given id.",
+ "tagIdRequired": "\"tagId\" must be a valid UUID corresponding to a tag belonging to the user.",
+ "positionRequired": "\"position\" is required and must be a number.",
+ "cantMoveCompletedTodo": "Can't move a completed todo.",
+ "directionUpDown": "\"direction\" is required and must be 'up' or 'down'",
+ "alreadyTagged": "The task is already tagged with given tag."
}
\ No newline at end of file
diff --git a/common/locales/pl/backgrounds.json b/common/locales/pl/backgrounds.json
index 904618ff1c..18b09bb5bf 100644
--- a/common/locales/pl/backgrounds.json
+++ b/common/locales/pl/backgrounds.json
@@ -154,11 +154,18 @@
"backgroundRainforestNotes": "Zapuść się w Las Deszczowy.",
"backgroundStoneCircleText": "Kamienny Krąg",
"backgroundStoneCircleNotes": "Rzucaj zaklęcia w Kamiennym Kręgu.",
- "backgrounds042016": "SET 23: Released April 2016",
- "backgroundArcheryRangeText": "Archery Range",
- "backgroundArcheryRangeNotes": "Practice on the Archery Range.",
- "backgroundGiantFlowersText": "Giant Flowers",
- "backgroundGiantFlowersNotes": "Frolic atop Giant Flowers.",
- "backgroundRainbowsEndText": "End of the Rainbow",
- "backgroundRainbowsEndNotes": "Discover gold at the End of the Rainbow."
+ "backgrounds042016": "ZESTAW 23: Opublikowany w kwietniu 2016",
+ "backgroundArcheryRangeText": "Strzelnica łuczników",
+ "backgroundArcheryRangeNotes": "Ćwicz łucznictwo na strzelnicy.",
+ "backgroundGiantFlowersText": "Gigantyczne kwiaty",
+ "backgroundGiantFlowersNotes": "Hasaj wśród gigantycznych kwiatów.",
+ "backgroundRainbowsEndText": "Koniec tęczy",
+ "backgroundRainbowsEndNotes": "Znajdź złoto na końcu tęczy.",
+ "backgrounds052016": "ZESTAW 24: Opublikowany w maju 2016",
+ "backgroundBeehiveText": "Ul",
+ "backgroundBeehiveNotes": "Bzycz i tańcz w ulu.",
+ "backgroundGazeboText": "Altana",
+ "backgroundGazeboNotes": "Battle a Gazebo.",
+ "backgroundTreeRootsText": "Korzenie drzewa",
+ "backgroundTreeRootsNotes": "Zbadaj korzenie drzewa"
}
\ No newline at end of file
diff --git a/common/locales/pl/challenge.json b/common/locales/pl/challenge.json
index de7998d9c4..0149a06b48 100644
--- a/common/locales/pl/challenge.json
+++ b/common/locales/pl/challenge.json
@@ -63,5 +63,21 @@
"congratulations": "Gratulacje!",
"hurray": "Hurra!",
"noChallengeOwner": "brak właściciela",
- "noChallengeOwnerPopover": "To wyzwanie nie posiada właściciela, ponieważ osoba zakładająca to wyzwanie usunęła swoje konto."
+ "noChallengeOwnerPopover": "To wyzwanie nie posiada właściciela, ponieważ osoba zakładająca to wyzwanie usunęła swoje konto.",
+ "challengeMemberNotFound": "Nie znaleziono użytkownika wśród uczestników wyzwania",
+ "onlyGroupLeaderChal": "Tylko przywódca grupy może tworzyć wyzwania",
+ "tavChalsMinPrize": "Nagroda za wyzwania karczmy musi wynosić co najmniej jeden klejnot.",
+ "cantAfford": "Nie stać cię na tę nagrodę. Kup więcej klejnotów lub obniż jej wysokość.",
+ "challengeIdRequired": "\"challengeId\" must be a valid UUID.",
+ "winnerIdRequired": "\"winnerId\" must be a valid UUID.",
+ "challengeNotFound": "Nie znaleziono wyzwania.",
+ "onlyLeaderDeleteChal": "Tylko przywódca wyzwania może je usunąć.",
+ "onlyLeaderUpdateChal": "Tylko przywódca wyzwania może je zaktualizować.",
+ "winnerNotFound": "Winner with id \"<%= userId %>\" not found or not part of the challenge.",
+ "noCompletedTodosChallenge": "\"includeComepletedTodos\" is not supported when fetching challenge tasks.",
+ "userTasksNoChallengeId": "When \"tasksOwner\" is \"user\" \"challengeId\" can't be passed.",
+ "onlyChalLeaderEditTasks": "Zadanie należące do wyzwania może być zmieniane jedynie przez przywódcę.",
+ "userAlreadyInChallenge": "Użytkownik bierze już udział w tym wyzwaniu.",
+ "cantOnlyUnlinkChalTask": "Only broken challenges tasks can be unlinked.",
+ "shortNameTooShort": "Tag Name must have at least 3 characters."
}
\ No newline at end of file
diff --git a/common/locales/pl/character.json b/common/locales/pl/character.json
index dd577b2058..0e114de957 100644
--- a/common/locales/pl/character.json
+++ b/common/locales/pl/character.json
@@ -1,4 +1,5 @@
{
+ "communityGuidelinesWarning": "Pamiętaj, że Twoja Nazwa Gracza, zdjęcie profilowe i krótki opis muszą być zgodne z Wytycznymi Społeczności (np. żadnej profanacji, tematów dla dorosłych, nic obraźliwego itp.). Jeśli masz pytania odnośnie tego, czy coś jest właściwe czy nie, pisz na e-mail leslie@habitica.com!",
"statsAch": "Statystyki i osiągnięcia",
"profile": "Profil",
"avatar": "Dostosuj Awatara",
@@ -34,7 +35,7 @@
"beard": "Broda",
"mustache": "Wąsy",
"flower": "Kwiatek",
- "wheelchair": "Wheelchair",
+ "wheelchair": "Wózek inwalidzki",
"basicSkins": "Podstawowe kolory",
"rainbowSkins": "Tęczowe kolory",
"pastelSkins": "Pastelowe kolory",
@@ -109,6 +110,7 @@
"mage": "Mag",
"mystery": "Tajemniczy",
"changeClass": "Zmień klasę i przywróć punkty atrybutów",
+ "lvl10ChangeClass": "Aby zmienić klasę musisz mieć co najmniej 10 level. ",
"levelPopover": "Każdy zdobyty poziom daje Ci jeden punkt, który możesz przydzielić do wybranego przez siebie atrybutu. Możesz zrobić to ręcznie lub pozwolić, by gra zdecydowała za Ciebie, używając jednej z opcji automatycznego przydzielania.",
"unallocated": "Nieprzydzielone punkty atrybutów",
"haveUnallocated": "Nieprzydzielone Punkty Atrybutów: <%= points %>",
@@ -164,5 +166,7 @@
"int": "INT",
"showQuickAllocation": "Pokaż przydział atrybutów",
"hideQuickAllocation": "Ukryj przydział atrybutów",
- "quickAllocationLevelPopover": "Z każdym poziomem otrzymujesz jeden punkt, który możesz przydzielić wybranemu atrybutowi. Możesz to zrobić ręcznie, lub zdać się na jedną z możliwych opcji Automatycznej Alokacji, dostępnych w Użytkownik -> Statystyki Awatara."
+ "quickAllocationLevelPopover": "Z każdym poziomem otrzymujesz jeden punkt, który możesz przydzielić wybranemu atrybutowi. Możesz to zrobić ręcznie, lub zdać się na jedną z możliwych opcji Automatycznej Alokacji, dostępnych w Użytkownik -> Statystyki Awatara.",
+ "invalidAttribute": "\"<%= attr %>\" nie jest poprawnym atrybutem.",
+ "notEnoughAttrPoints": "Nie masz wystarczająco punktów atrybutów."
}
\ No newline at end of file
diff --git a/common/locales/pl/content.json b/common/locales/pl/content.json
index bd168ececd..963104a9ac 100644
--- a/common/locales/pl/content.json
+++ b/common/locales/pl/content.json
@@ -113,6 +113,12 @@
"questEggSnailText": "Ślimak",
"questEggSnailMountText": "Ślimak",
"questEggSnailAdjective": "powolny ale wytrwały",
+ "questEggFalconText": "Sokół",
+ "questEggFalconMountText": "Sokół",
+ "questEggFalconAdjective": "szybki",
+ "questEggTreelingText": "Drzewo",
+ "questEggTreelingMountText": "Drzewo",
+ "questEggTreelingAdjective": "liściaste",
"eggNotes": "Znajdź eliksir wyklucia i wylej go na to jajo, a wykluje się z niego <%= eggAdjective(locale) %> <%= eggText(locale) %>. ",
"hatchingPotionBase": "Zwyczajny",
"hatchingPotionWhite": "Biały",
@@ -126,6 +132,7 @@
"hatchingPotionGolden": "Złoty",
"hatchingPotionSpooky": "Upiorny",
"hatchingPotionPeppermint": "Miętowy",
+ "hatchingPotionFloral": "Kwiecisty",
"hatchingPotionNotes": "Wylej go na jajko, a wykluje się z niego <%=potText(locale)%>.",
"premiumPotionAddlNotes": "Nie nadaje się do użytku na jajach otrzymanych za misje.",
"foodMeat": "Mięso",
diff --git a/common/locales/pl/contrib.json b/common/locales/pl/contrib.json
index 5610dbd7ab..d5354d5eda 100644
--- a/common/locales/pl/contrib.json
+++ b/common/locales/pl/contrib.json
@@ -35,8 +35,12 @@
"hallContributors": "Sala Współtwórców",
"hallPatrons": "Sala patronów",
"rewardUser": "Nagrodź użytkownika",
- "UUID": "UUID",
+ "UUID": "ID Użytkownika",
"loadUser": "Wczytaj użytkownika",
+ "noAdminAccess": "Nie masz dostępu administratora.",
+ "pageMustBeNumber": "req.query.page must be a number",
+ "userNotFound": "Nie znaleziono użytkownika.",
+ "invalidUUID": "UUID must be valid",
"title": "Tytuł",
"moreDetails": "Więcej szczegółów (1-7)",
"moreDetails2": "więcej szczegółów (8-9)",
@@ -52,8 +56,8 @@
"visitHeroes": "Odwiedź Salę bohaterów (osób wspierających projekt)",
"conLearn": "Dowiedz się więcej o nagrodach za wspieranie",
"conLearnHow": "Dowiedz się jak możesz dołożyć swój wkład do Habitica",
- "surveysSingle": "Pomógł rozwinąć Habitica poprzez wypełnienie ankiety. Brak aktywnych ankiet.",
- "surveysMultiple": "Pomógł rozwinąć Habitica poprzez wypełnienie <%= surveys %> ankiet. Brak aktywnych ankiet.",
+ "surveysSingle": "Helped Habitica grow, either by filling out a survey or helping with a major testing effort. Thank you!",
+ "surveysMultiple": "Helped Habitica grow on <%= surveys %> occasions, either by filling out a survey or helping with a major testing effort. Thank you!",
"currentSurvey": "Bieżąca ankieta.",
"surveyWhen": "Odznaki zostaną przyznane wszystkim biorącym udział, gdy zgłoszenia zostaną przetworzone pod koniec marca.",
"blurbInbox": "Tutaj trzymane są twoje prywatne wiadomości! Możesz wysłać komuś wiadomość klikając na ikonę koperty obok ich nazwy w Karczmie, Drużynie lub na czacie Gildii. Jeśli otrzymałeś nieodpowiednią wiadomość prywatną, powinieneś wysłać email zawierający zrzut ekranu do Lemoness (leslie@habitica.com).",
diff --git a/common/locales/pl/death.json b/common/locales/pl/death.json
index be8404013b..23a074e6d1 100644
--- a/common/locales/pl/death.json
+++ b/common/locales/pl/death.json
@@ -12,5 +12,6 @@
"losingHealthQuickly": "Szybko tracisz zdrowie?",
"lowHealthTips3": "Niewykonane Codzienne ranią ciebie w nocy, więc uważaj, by na początku nie dodać ich zbyt dużo!",
"lowHealthTips4": "Jeśli Codzienne nie jest zaplanowane na konkretny dzień, możesz je wyłączyć klikając na ikonę ołówka.",
- "goodLuck": "Powodzenia!"
+ "goodLuck": "Powodzenia!",
+ "cannotRevive": "Nie można ożywić kogoś, kto nie jest martwy"
}
\ No newline at end of file
diff --git a/common/locales/pl/front.json b/common/locales/pl/front.json
index 9bcf36cfed..79194b5730 100644
--- a/common/locales/pl/front.json
+++ b/common/locales/pl/front.json
@@ -28,6 +28,7 @@
"communityReddit": "Reddit",
"companyAbout": "Jak to działa",
"companyBlog": "Blog",
+ "devBlog": "Developer Blog",
"companyDonate": "Dotacja",
"companyExtensions": "Rozszerzenia",
"companyPrivacy": "Prywatność",
@@ -51,6 +52,7 @@
"featureSocialHeading": "Gra społecznościowa",
"featuredIn": "Dostępne w",
"featuresHeading": "Oferujemy również...",
+ "footerDevs": "Developers",
"footerCommunity": "Społeczność",
"footerCompany": "Firma",
"footerMobile": "Urządzenia przenośne",
@@ -182,6 +184,7 @@
"zelahQuote": "Z [Habitiką], myśl o zdobytych punktach (za dłuższy sen) lub straconym zdrowiu (za zarwanie nocy), skłania mnie do wcześniejszego pójścia do łóżka!",
"reportAccountProblems": "Zgłoś problem z kontem",
"reportCommunityIssues": "Zgłoś problem społecznościowy",
+ "subscriptionPaymentIssues": "Problemy z abonamentem i opłatami",
"generalQuestionsSite": "Ogólne pytania na temat strony",
"businessInquiries": "Zapytania biznesowe",
"merchandiseInquiries": "Zapytania handlowe",
@@ -222,5 +225,32 @@
"altAttrWebstorm": "WebStorm",
"altAttrGithub": "GitHub",
"altAttrTrello": "Trello",
- "altAttrSlack": "Slack"
+ "altAttrSlack": "Slack",
+ "missingAuthHeaders": "Missing authentication headers.",
+ "missingAuthParams": "Missing authentication parameters.",
+ "missingUsernameEmail": "Zapomniałem nazwy użytkownika lub maila.",
+ "missingEmail": "Zapomniałem maila.",
+ "missingUsername": "Zapomniałem nazwy użytkownika.",
+ "missingPassword": "Zapomniałem hasło.",
+ "missingNewPassword": "Missing new password.",
+ "wrongPassword": "Nieprawidłowe hasło.",
+ "notAnEmail": "Nieprawidłowy adres e-mail. ",
+ "emailTaken": "Adres e-mail jest już używany.",
+ "newEmailRequired": "Missing new email address.",
+ "usernameTaken": "Nazwa użytkownika jest już zajęta",
+ "passwordConfirmationMatch": "Potwierdzenie hasła nie jest identyczne z hasłem.",
+ "invalidLoginCredentials": "Błędna nazwa użytkownika i/lub e-mail i/lub hasło",
+ "passwordReset": "If we have your email on file, your password reset link has been sent to your email.",
+ "passwordResetEmailSubject": "Reset hasła do Habitiki",
+ "passwordResetEmailText": "Hasło użytkownika <%= username %> zostało zmienione na <%= newPassword %>. Uwaga! Wielkość liter ma znaczenie – zarówno nazwę użytkownika, jak i hasło musisz wprowadzić w dokładnie takiej samej formie jak tu. Radzimy skopiować je zamiast wpisywać samodzielnie. Zaloguj się na <%= baseUrl %>. Po zalogowaniu udaj się do <%= baseUrl %>/#/options/settings/settings i zmień hasło.",
+ "passwordResetEmailHtml": "Hasło użytkownika <%= username %> zostało zmienione na <%= newPassword %>.
Uwaga! Wielkość liter ma znaczenie – zarówno nazwę użytkownika, jak i hasło musisz wprowadzić w dokładnie takiej samej formie jak tu. Radzimy skopiować je zamiast wpisywać samodzielnie.
Zaloguj się na <%= baseUrl %>. Po zalogowaniu udaj się do <%= baseUrl %>/#/options/settings/settings i zmień hasło.",
+ "invalidLoginCredentialsLong": "Ojej, twoja nazwa użytkownika lub hasło są błędne.\n– Upewnij się, że poprawnie wpisałeś nazwę użytkownika lub e-mail.\n– Być może rejestrowałeś się za pomocą Facebooka, a nie e-maila. Upewnij się, że tak nie było, próbując logowania przez Facebook.\n– Jeśli zapomniałeś hasła, wybierz \"zapomniałem hasła\".",
+ "invalidCredentials": "There is no account that uses those credentials.",
+ "accountSuspended": "Konto zostało zawieszone. Aby uzyskać pomoc, napisz na leslie@habitica.com, zawierając w treści swoje ID użytkownika – „<%= userId %>”.",
+ "onlyFbSupported": "Only Facebook is supported currently.",
+ "cantDetachFb": "Account lacks another authentication method, can't detach Facebook.",
+ "onlySocialAttachLocal": "Local authentication can be added to only a social account.",
+ "invalidReqParams": "Invalid request parameters.",
+ "memberIdRequired": "\"member\" must be a valid UUID.",
+ "heroIdRequired": "\"heroId\" must be a valid UUID."
}
\ No newline at end of file
diff --git a/common/locales/pl/gear.json b/common/locales/pl/gear.json
index 2b4266b4d0..2d9fa1d1b8 100644
--- a/common/locales/pl/gear.json
+++ b/common/locales/pl/gear.json
@@ -187,8 +187,10 @@
"weaponArmoireJesterBatonNotes": "Po machnięciu berłem i ciętej ripoście nawet najtrudniejsze sytuacje stają się jasne. Zwiększa inteligencję i percepcję o <%= attrs %>. Zaczarowana Szafa: Zestaw Błazna (Przedmiot 3 z 3).",
"weaponArmoireMiningPickaxText": "Kilof",
"weaponArmoireMiningPickaxNotes": "Wydobądź maksimum złota ze swoich zadań! Zwiększa Percepcję o <%= per %>. Zaczarowana Szafa: Zestaw Górnika (Przedmiot 3 z 3).",
- "weaponArmoireBasicLongbowText": "Basic Longbow",
- "weaponArmoireBasicLongbowNotes": "A serviceable hand-me-down bow. Increases Strength by <%= str %>. Enchanted Armoire: Basic Archer Set (Item 1 of 3).",
+ "weaponArmoireBasicLongbowText": "Zwyczajny długi łuk",
+ "weaponArmoireBasicLongbowNotes": "Przechodzony ale użyteczny łuk. Zwiększa siłę o <%= str %>. Zaczarowana Szafa: Zwyczajny Zestaw Łucznika (przedmiot 1 z 3).",
+ "weaponArmoireHabiticanDiplomaText": "Habitican Diploma",
+ "weaponArmoireHabiticanDiplomaNotes": "A certificate of significant achievement -- well done! Increases Intelligence by <%= int %>. Enchanted Armoire: Graduate Set (Item 1 of 3).",
"armor": "zbroja",
"armorBase0Text": "Zwykłe ubranie",
"armorBase0Notes": "Zwyczajne ubranie. Nie ma na nic wpływu.",
@@ -364,6 +366,10 @@
"armorMystery201512Notes": "Przyzwij mrożące płomienie zimy! Brak dodatkowych korzyści. Przedmiot Abonencki, grudzień 2015.",
"armorMystery201603Text": "Szczęśliwy Garnitur",
"armorMystery201603Notes": "Ten garnitur został uszyty z tysięcy czterolistnych koniczyn! Brak dodatkowych korzyści. Przedmiot Abonencki, marzec 2016.",
+ "armorMystery201604Text": "Armor o' Leaves",
+ "armorMystery201604Notes": "You, too, can be a small but fearsome leaf puff. Confers no benefit. April 2016 Subscriber Item.",
+ "armorMystery201605Text": "Marching Bard Uniform",
+ "armorMystery201605Notes": "Unlike the traditional bards who join adventuring parties, bards who join Habitican marching bands are known for grand parades, not dungeon raids. Confers no benefit. May 2016 Subscriber Item.",
"armorMystery301404Text": "Steampunkowy garnitur",
"armorMystery301404Notes": "Elegancki i stylowy! Brak dodatkowych korzyści. Przedmiot Abonencki, luty 2015.",
"armorArmoireLunarArmorText": "Księżycowa Kojąca Zbroja",
@@ -392,8 +398,10 @@
"armorArmoireJesterCostumeNotes": "Tra-la-la! Wbrew wyglądowi tego kostiumu, nie jesteś głupcem. Zwiększa inteligencję o <%= int %>. Zaczarowana Szafa: Zestaw Błazna (Przedmiot 2 z 3).",
"armorArmoireMinerOverallsText": "Kombinezon Górnika",
"armorArmoireMinerOverallsNotes": "Zdaje się być znoszony, ale magicznie odpycha brud. Zwiększa Kondycję o <%= con %>. Zaczarowana Szafa: Zestaw Górnika (Przedmiot 2 z 3).",
- "armorArmoireBasicArcherArmorText": "Basic Archer Armor",
- "armorArmoireBasicArcherArmorNotes": "This camouflaged vest lets you slip unnoticed through the forests. Increases Perception by <%= per %>. Enchanted Armoire: Basic Archer Set (Item 2 of 3).",
+ "armorArmoireBasicArcherArmorText": "Zwyczajny pancerz łucznika",
+ "armorArmoireBasicArcherArmorNotes": "Ta maskująca kamizelka pozwala ci niezauważonym przemykać przez las. Zwiększa percepcję o <%= per %>. Zaczarowana Szafa: Zwyczajny Zestaw Łucznika (przedmiot 2 z 3).",
+ "armorArmoireGraduateRobeText": "Graduate Robe",
+ "armorArmoireGraduateRobeNotes": "Congratulations! This weighty robe hangs heavy with all the knowledge you have accrued. Increases Intelligence by <%= int %>. Enchanted Armoire: Graduate Set (Item 2 of 3).",
"headgear": "nakrycie głowy",
"headBase0Text": "Bez hełmu",
"headBase0Notes": "Bez nakrycia głowy.",
@@ -565,6 +573,10 @@
"headMystery201602Notes": "Ukryj swoje oblicze przed wielbicielami. Brak dodatkowych korzyści. Przedmiot Abonencki, luty 2016.",
"headMystery201603Text": "Szczęśliwy Kapelusz",
"headMystery201603Notes": "Ten cylinder to amulet przynoszący szczęście. Brak dodatkowych korzyści. Przedmiot Abonencki, marzec 2016.",
+ "headMystery201604Text": "Crown o' Flowers",
+ "headMystery201604Notes": "These woven flowers make a surprisingly strong helm! Confers no benefit. April 2016 Subscriber Item.",
+ "headMystery201605Text": "Marching Bard Hat",
+ "headMystery201605Notes": "Seventy-six dragons led the big parade, with a hundred and ten gryphons close at hand! Confers no benefit. May 2016 Subscriber Item.",
"headMystery301404Text": "Szykowny cylinder",
"headMystery301404Notes": "Fantazyjny cylinder dla najwyżej urodzonych. Przedmiot Abonencki, styczeń 2015. Brak dodatkowych korzyści.",
"headMystery301405Text": "Klasyczny cylinder",
@@ -611,8 +623,10 @@
"headArmoireJesterCapNotes": "Dzwoneczki na tej czapce mogłyby rozproszyć twoich przeciwników, ale po prostu pomagają ci się skupić. Zwiększa percepcję o <%= per %>. Zaczarowana Szafa: Zestaw Błazna (Przedmiot 1 z 3).",
"headArmoireMinerHelmetText": "Kask Górnika",
"headArmoireMinerHelmetNotes": "Chroń głowę przed spadającymi zadaniami! Zwiększa Inteligencję o <%= int %>. Zaczarowana Szafa: Zestaw Górnika (Przedmiot 1 z 3).",
- "headArmoireBasicArcherCapText": "Basic Archer Cap",
- "headArmoireBasicArcherCapNotes": "No archer would be complete without a jaunty cap! Increases Perception by <%= per %>. Enchanted Armoire: Basic Archer Set (Item 3 of 3).",
+ "headArmoireBasicArcherCapText": "Zwyczajna czapka łucznika",
+ "headArmoireBasicArcherCapNotes": "Strój łucznika nie jest kompletny bez zawadiackiej czapeczki! Zwiększa percepcję o <%= per %>. Zaczarowana Szafa: Zwyczajny Zestaw Łucznika (przedmiot 3 z 3).",
+ "headArmoireGraduateCapText": "Graduate Cap",
+ "headArmoireGraduateCapNotes": "Congratulations! Your deep thoughts have earned you this thinking cap. Increases Intelligence by <%= int %>. Enchanted Armoire: Graduate Set (Item 3 of 3).",
"offhand": "Tarcza",
"shieldBase0Text": "Brak tarczy w ekwipunku.",
"shieldBase0Notes": "Bez tarczy lub drugiej broni.",
@@ -673,7 +687,7 @@
"shieldSpecialWinter2015HealerText": "Kojąca tarcza",
"shieldSpecialWinter2015HealerNotes": "Ta tarcza odbija mroźny wiatr. Zwiększa kondycję o <%= con %>. Edycja Limitowana Zimowego Wyposażenia 2014-2015.",
"shieldSpecialSpring2015RogueText": "Explodujący pisk",
- "shieldSpecialSpring2015RogueNotes": "Niech nazwa Cię nie zmyli - to wybuchowy zestaw. Zwiększa Siłę o <%= str %>. Edycja Limitowana, wiosna 2015.",
+ "shieldSpecialSpring2015RogueNotes": "Don't let the sound fool you - these explosives pack a punch. Increases Strength by <%= str %>. Limited Edition 2015 Spring Gear.",
"shieldSpecialSpring2015WarriorText": "Dysk talerzowy",
"shieldSpecialSpring2015WarriorNotes": "Rzuć nim w swoich wrogów... lub po prostu go trzymaj, ponieważ w porze obiadu wypełnia się pyszną karmą. Zwiększa kondycję o <%= con %>. Edycja Limitowana, wiosna 2015.",
"shieldSpecialSpring2015HealerText": "Wzorzysta poduszka",
@@ -716,6 +730,8 @@
"shieldArmoireDragonTamerShieldNotes": "Ta tarcza w kształcie smoka rozproszy uwagę twoich wrogów. Zwiększa Percepcję o <%= per %>. Zaczarowana Szafa: Zestaw Poskramiacza Smoków (przedmiot 2 z 3).",
"shieldArmoireMysticLampText": "Mistyczna Lampa",
"shieldArmoireMysticLampNotes": "Oświetl najciemniejsze z jaskiń tą mistyczną lampą! Zwiększa Percepcję o <%= per %>. Zaczarowana Szafa: przedmiot niezależny.",
+ "shieldArmoireFloralBouquetText": "Bouquet o' Flowers",
+ "shieldArmoireFloralBouquetNotes": "Not much help in battle, but aren't they beautiful? Increases Constitution by <%= con %>. Enchanted Armoire: Independent Item.",
"back": "Osprzęt na plecy",
"backBase0Text": "Nic na plecach",
"backBase0Notes": "Nic na plecach.",
@@ -815,11 +831,25 @@
"headAccessoryMystery201510Notes": "Te straszne rogi są nieco oślizgłe. Brak dodatkowych korzyści. Przedmiot Abonencki, październik 2015.",
"headAccessoryMystery301405Text": "Gogle na głowę",
"headAccessoryMystery301405Notes": "\"Gogle nosi się na oczach\", mówili. \"Nikt nie chce gogli które można nosić tylko na głowie\", mówili. Ha! Niech spojrzą na Ciebie! Brak dodatkowych korzyści. Przedmiot Abonencki, sierpień 2015.",
- "headAccessoryArmoireComicalArrowText": "Comical Arrow",
- "headAccessoryArmoireComicalArrowNotes": "This whimsical item doesn't provide a stat boost, but it sure is good for a laugh! Confers no benefit. Enchanted Armoire: Independent Item.",
+ "headAccessoryArmoireComicalArrowText": "Zabawna Strzała",
+ "headAccessoryArmoireComicalArrowNotes": "Ten dziwaczny przedmiot nie podnosi statystyk ale na pewno jest bardzo śmieszny! Brak dodatkowych korzyści. Zaczarowana Szafa: Przedmiot Niezależny.",
"eyewear": "Okulary",
"eyewearBase0Text": "Brak okularów",
"eyewearBase0Notes": "Brak okularów.",
+ "eyewearSpecialBlackTopFrameText": "Black Standard Eyeglasses",
+ "eyewearSpecialBlackTopFrameNotes": "Glasses with a black frame above the lenses. Confers no benefit.",
+ "eyewearSpecialBlueTopFrameText": "Blue Standard Eyeglasses",
+ "eyewearSpecialBlueTopFrameNotes": "Glasses with a blue frame above the lenses. Confers no benefit.",
+ "eyewearSpecialGreenTopFrameText": "Green Standard Eyeglasses",
+ "eyewearSpecialGreenTopFrameNotes": "Glasses with a green frame above the lenses. Confers no benefit.",
+ "eyewearSpecialPinkTopFrameText": "Pink Standard Eyeglasses",
+ "eyewearSpecialPinkTopFrameNotes": "Glasses with a pink frame above the lenses. Confers no benefit.",
+ "eyewearSpecialRedTopFrameText": "Red Standard Eyeglasses",
+ "eyewearSpecialRedTopFrameNotes": "Glasses with a red frame above the lenses. Confers no benefit.",
+ "eyewearSpecialWhiteTopFrameText": "White Standard Eyeglasses",
+ "eyewearSpecialWhiteTopFrameNotes": "Glasses with a white frame above the lenses. Confers no benefit.",
+ "eyewearSpecialYellowTopFrameText": "Yellow Standard Eyeglasses",
+ "eyewearSpecialYellowTopFrameNotes": "Glasses with a yellow frame above the lenses. Confers no benefit.",
"eyewearSpecialSummerRogueText": "Zbójecka przepaska na oko",
"eyewearSpecialSummerRogueNotes": "Nie trzeba być piratem, by zauważyć, jak stylowa jest ta przepaska! Brak dodatkowych korzyści. Edycja Limitowana, lato 2014.",
"eyewearSpecialSummerWarriorText": "Dziarska przepaska na oko",
diff --git a/common/locales/pl/groups.json b/common/locales/pl/groups.json
index 64ed982ca2..e425b96132 100644
--- a/common/locales/pl/groups.json
+++ b/common/locales/pl/groups.json
@@ -92,6 +92,7 @@
"send": "Wyślij",
"messageSentAlert": "Wiadomość wysłana",
"pmHeading": "Wiadomość prywatna do <%= name %>",
+ "pmsMarkedRead": "Twoja prywatna wiadomość została oznaczona jako przeczytana",
"clearAll": "Usuń wszystkie wiadomości",
"confirmDeleteAllMessages": "Czy na pewno chcesz usunąć wszystkie wiadomości w skrzynce odbiorczej? Inni użytkownicy nadal będą widzieli wiadomości, które im przesłałeś.",
"optOutPopover": "Nie lubisz prywatnych wiadomości? Kliknij, aby całkiem z nich zrezygnować.",
@@ -99,6 +100,15 @@
"unblock": "Odblokuj",
"pm-reply": "Wyślij odpowiedź",
"inbox": "Skrzynka odbiorcza",
+ "messageRequired": "A message is required.",
+ "toUserIDRequired": "A User ID is required",
+ "gemAmountRequired": "Wymagana liczba klejnotów",
+ "notAuthorizedToSendMessageToThisUser": "Nie można wysłać wiadomości do tego użytkownika.",
+ "privateMessageGiftIntro": "Hej <%= receiverName %>, <%= senderName %> wysłał Tobie",
+ "privateMessageGiftGemsMessage": "<%= gemAmount %> klejnotów!",
+ "privateMessageGiftSubscriptionMessage": "Miesięcy abonamentu: <%= numberOfMonths %>!",
+ "cannotSendGemsToYourself": "Cannot send gems to yourself. Try a subscription instead.",
+ "badAmountOfGemsToSend": "Amount must be within 1 and your current number of gems.",
"abuseFlag": "Zgłoś naruszenie regulaminu społeczności.",
"abuseFlagModalHeading": "Zgłosić naruszenie zasad przez <%= name %>?",
"abuseFlagModalBody": "Czy na pewno chcesz zgłosić ten post? Powinieneś TYLKO zgłaszać posty, które naruszają <%= firstLinkStart %>Regulamin Społeczności<%= linkEnd %>oraz/lub <%= secondLinkStart %>Warunki<%= linkEnd %>. Niewłaściwe zgłaszanie postów jest naruszeniem Regulaminu Społeczności i może doprowadzić do zgłoszenia. Właściwymi powodami do oznaczania postów są, ale i nie tylko:
przeklinanie, slóbowania religijne
fanatyzm, bełkot
tematy dla dorosłych
przemoc, nawet w żartach
spam, iadomości bez sensu
",
@@ -151,5 +161,29 @@
"partyUpName": "Razem raźniej",
"partyOnName": "Jeden za wszystkich, wszyscy za jednego",
"partyUpAchievement": "Dołączył do drużyny z inną osobą! Dobrej zabawy przy walce z potworami i wspieraniu się nawzajem.",
- "partyOnAchievement": "Dołączył do co najmniej czteroosobowej drużyny! Ciesz się zwiększoną odpowiedzialnością po dołączeniu do przyjaciół w zwyciężaniu wrogów!"
+ "partyOnAchievement": "Dołączył do co najmniej czteroosobowej drużyny! Ciesz się zwiększoną odpowiedzialnością po dołączeniu do przyjaciół w zwyciężaniu wrogów!",
+ "groupIdRequired": "\"groupId\" must be a valid UUID",
+ "groupNotFound": "Grupa nie znaleziona.",
+ "groupTypesRequired": "You must supply a valid \"type\" query string.",
+ "questLeaderCannotLeaveGroup": "Nie możesz odejść z drużyny, jeśli zacząłeś misję – musisz ją najpierw przerwać.",
+ "cannotLeaveWhileActiveQuest": "Nie możesz odejść z drużyny w trakcie trwania misji. Najpierw powinieneś opuścić misję.",
+ "onlyLeaderCanRemoveMember": "Tylko przywódca grupy może usuwać jej członków!",
+ "memberCannotRemoveYourself": "Nie możesz usunąć siebie!",
+ "groupMemberNotFound": "Użytkownik nie znaleziony wśród członków grupy",
+ "mustBeGroupMember": "Musi być członkiem grupy.",
+ "keepOrRemoveAll": "req.query.keep must be either \"keep-all\" or \"remove-all\"",
+ "keepOrRemove": "req.query.keep must be either \"keep\" or \"remove\"",
+ "canOnlyInviteEmailUuid": "Can only invite using uuids or emails.",
+ "inviteMissingEmail": "Missing email address in invite.",
+ "partyMustbePrivate": "Drużyny muszą być prywatne.",
+ "userAlreadyInGroup": "Użytkownik jest już członkiem tej grupy.",
+ "userAlreadyInvitedToGroup": "Użytkownik został już zaproszony do tej grupy.",
+ "userAlreadyPendingInvitation": "User already pending invitation.",
+ "userAlreadyInAParty": "Użytkownik jest już w drużynie.",
+ "userWithIDNotFound": "Nie znaleziono użytkownika o numerze ID „<%= userId %>”.",
+ "userHasNoLocalRegistration": "User does not have a local registration (username, email, password).",
+ "uuidsMustBeAnArray": "User ID invites must be an array.",
+ "emailsMustBeAnArray": "Email address invites must be an array.",
+ "canOnlyInviteMaxInvites": "Możesz zaprosić jednocześnie nie więcej niż <%= maxInvites %> osób.",
+ "onlyCreatorOrAdminCanDeleteChat": "Nie masz uprawnień do usunięcia tej wiadomości!"
}
\ No newline at end of file
diff --git a/common/locales/pl/limited.json b/common/locales/pl/limited.json
index 4897760358..db038351dd 100644
--- a/common/locales/pl/limited.json
+++ b/common/locales/pl/limited.json
@@ -5,7 +5,7 @@
"annoyingFriends": "Złośliwi znajomi",
"annoyingFriendsText": "Dostał <%= snowballs %> razy śnieżką od członków Drużyny.",
"alarmingFriends": "Niepokojący przyjaciele",
- "alarmingFriendsText": "Przestraszony <%= spookDust %> razy przez członków drużyny.",
+ "alarmingFriendsText": "Got spooked <%= spookySparkles %> times by party members.",
"agriculturalFriends": "Rolniczy przyjaciele",
"agriculturalFriendsText": "Przemieniony w kwiat <%= seeds %> razy przez członków drużyny.",
"aquaticFriends": "Wodni przyjaciele",
@@ -72,5 +72,6 @@
"comfortingKittySet": "Wygodnicki Kotek (Uzdrowiciel)",
"sneakySqueakerSet": "Przebiegły Kwikacz (Łotrzyk)",
"fallEventAvailability": "Dostępne do 31 października",
- "winterEventAvailability": "Dostępne do 31 grudnia"
+ "winterEventAvailability": "Dostępne do 31 grudnia",
+ "springEventAvailability": "Dostępne do 31 maja"
}
\ No newline at end of file
diff --git a/common/locales/pl/maintenance.json b/common/locales/pl/maintenance.json
new file mode 100644
index 0000000000..e4dc02c3ee
--- /dev/null
+++ b/common/locales/pl/maintenance.json
@@ -0,0 +1,34 @@
+{
+ "habiticaBackSoon": "Nie martw się, Habitica niedługo wróci!",
+ "importantMaintenance": "Planujemy ważną przerwę techniczną, która potrwa do <%= localDate %> w twojej strefie czasowej.",
+ "maintenance": "Przerwa techniczna",
+ "maintenanceMoreInfo": "Chcesz dowiedzieć się więcej o zmianach? <%= linkStart%>Zajrzyj tutaj.<%= linkEnd %>",
+ "noDamageKeepStreaks": "NIE otrzymasz obrażeń ani nie stracisz serii!",
+ "thanksForPatience": "Dziękujemy za cierpliwość!",
+ "twitterMaintenanceUpdates": "Aby być na bieżąco ze zmianami, śledź naszego Twittera, na którym będą się pojawiać informacje o statusie.",
+ "veteranPetAward": "Na końcu otrzymasz chowańca weterana!",
+
+ "maintenanceInfoTitle": "Informacje o nadchodzącej przerwie technicznej",
+ "maintenanceInfoWhat": "Co się dzieje?",
+ "maintenanceInfoWhatText": "Wieczorem dnia 21 maja Habitica będzie niedostępna z powodu przerwy technicznej. Nie otrzymasz wtedy żadnych obrażeń, nawet jeśli nie zdążysz odhaczyć Codziennych na czas! Postaramy się, żeby przerwa trwała najkrócej jak to będzie możliwe, i będziemy umieszczać regularne aktualizacje na naszym Twitterze. W podzięce za waszą cierpliwość po powrocie strony każdy użytkownik dostanie rzadkiego chowańca!",
+ "maintenanceInfoWhy": "Dlaczego tak się dzieje?",
+ "maintenanceInfoWhyText": "Przez kilka poprzednich miesięcy zajmowaliśmy się przerabianiem Habitiki, a dokładniej przepisywaniem od nowa API. Chociaż na pierwszy rzut oka nie widać nic nowego, nowy kod wnosi naprawdę wiele zmian. Pozwoli on na dodanie wielu funkcji, które dotąd nie były możliwe, a także usprawni działanie strony!",
+ "maintenanceInfoTechDetails": "Chciałbyś poczytać więcej o technicznej stronie całego procesu? Zajrzyj na The Forge, bloga naszych developerów (po angielsku).",
+ "maintenanceInfoMore": "Dodatkowe informacje",
+ "maintenanceInfoAccountChanges": "Jakie zmiany zobaczę?",
+ "maintenanceInfoAccountChangesText": "Z początku nie będą widoczne żadne zmiany oprócz poprawy funkcjonowania niektórych elementów strony, takich jak wyzwania. Jeśli zauważysz zmiany, których nie powinno tam być, napisz do nas na admin@habitica.com – zajmiemy się tym!",
+ "maintenanceInfoAddFeatures": "Dodanie jakich funkcji umożliwią zmiany?",
+ "maintenanceInfoAddFeaturesText": "Przepisanie kodu pozwoli nam zająć się poprawkami w funkcjonowaniu czatu i gildii oraz budową planów dla organizacji i rodzin. Umożliwi także dodanie nowych funkcji, takich jak Comiesięczne zadania i odhaczanie wczorajszych Codziennych! Wszystko to są zupełnie nowe rzeczy, których stworzenie zajmie trochę czasu, ale dopóki nie skończyliśmy pisać nowego API, nie mieliśmy możliwości się tym zająć.",
+ "maintenanceInfoHowLong": "Ile czasu potrwa przerwa techniczna?",
+ "maintenanceInfoHowLongText": "Musimy przenieść zadania i dane ponad miliona użytkowników Habitiki – to niełatwe zadanie! Spodziewamy się, że przerwa będzie miała miejsce pomiędzy godziną 22, a 7 rano następnego dnia. Możecie być pewni, że dołożymy wszelkich starań, aby potrwała ona najkrócej jak to tylko możliwe! Aby śledzić zmiany na bieżąco, odwiedź naszego Twittera.",
+ "maintenanceInfoStatsAffected": "Jak to wpłynie na moje codzienne zadania, serie, wzmocnienia oraz misje?",
+ "maintenanceInfoStatsAffectedText1": "NIE otrzymasz żadnych obrażeń ani nie stracisz serii podczas przerwy, ale poza tym reset dnia przebiegnie normalnie. Odhaczone codzienne zadania odświeżą się, wzmocienia zresetują itd. Jeśli bierzesz udział w misji polegającej na zbieraniu przedmiotów, znajdziesz je, a jeśli walczysz z bossem, zadasz mu obrażenia, ale on tobie nie (nawet potwory czasem potrzebują przerwy!).",
+ "maintenanceInfoStatsAffectedText2": "Po długim namyśle zdecydowaliśmy, że to najuczciwszy sposób poradzenia sobie z tym, że wielu użytkowników nie będzie miało możliwości odhaczenia codziennych zadań podczas przerwy. Przepraszamy za niedogodności!",
+ "maintenanceInfoSeeTasks": "Co, jeśli będę chciał spojrzeć na swoje zadania?",
+ "maintenanceInfoSeeTasksText": "Jeśli wiesz, że w sobotę będzie ci potrzebna lista twoich zadań, abyś mógł sprawdzić co jest do zrobienia, radzimy zrobić dzień wcześniej zrzut ekranu, do którego będziesz mógł się zwrócić podczas przerwy.",
+ "maintenanceInfoRarePet": "Jakiego rzadkiego chowańca dostanę?",
+ "maintenanceInfoRarePetText": "W podzięce za waszą cierpliwość każdy użytkownik dostanie rzadkiego chowańca weterana. Jeśli to twój pierwszy taki chowaniec, otrzymasz Wilka Weterana. Jeśli posiadasz już Wilka, dostaniesz Tygrysa Weterana. A jeśli masz już oba, otrzymasz zupełnie nowego, niedostępnego wcześniej chowańca! Po powrocie strony pojawienie się chowańca może zająć kilka godzin, ale nie martw się, z pewnością go dostaniesz.",
+ "maintenanceInfoWho": "Kto pracował nad projektem?",
+ "maintenanceInfoWhoText": "Dobrze, że pytasz! Na czele grupy stał nasz wspaniały paglias, a dzielnie pomagali mu Blade, TheHollidayInn, SabreCat, Victor Pudeyev, TheUnknown, oraz Alys.",
+ "maintenanceInfoTesting": "Nowa wersja została również skrupulatnie przetestowana przez naszych wolontariuszy. Dziękujemy – bez was nie byłoby to możliwe."
+}
diff --git a/common/locales/pl/npc.json b/common/locales/pl/npc.json
index 4b08f16e6c..3e85c6e11e 100644
--- a/common/locales/pl/npc.json
+++ b/common/locales/pl/npc.json
@@ -21,6 +21,26 @@
"ian": "Ian",
"ianText": "Witaj w sklepie z misjami! Możesz tutaj wykorzystać zwoje misji, by wraz z przyjaciółmi toczyć boje z potworami. Już teraz koniecznie sprawdź naszą wspaniałą tablicę ze zwojami misji możliwymi do zakupu!",
"ianBrokenText": "Witaj w sklepie z misjami... Możesz tutaj wykorzystać zwoje misji, by wraz z przyjaciółmi toczyć boje z potworami... Już teraz koniecznie sprawdź naszą wspaniałą tablicę ze zwojami misji możliwymi do zakupu...",
+ "missingKeyParam": "\"req.params.key\" is required.",
+ "itemNotFound": "Item \"<%= key %>\" not found.",
+ "cannotBuyItem": "Nie możesz tego kupić.",
+ "missingTypeKeyEquip": "\"key\" and \"type\" are required parameters.",
+ "missingPetFoodFeed": "\"pet\" and \"food\" are required parameters.",
+ "invalidPetName": "Invalid pet name supplied.",
+ "missingEggHatchingPotionHatch": "\"egg\" and \"hatchingPotion\" are required parameters.",
+ "invalidTypeEquip": "\"type\" must be one of 'equipped', 'pet', 'mount', 'costume'.",
+ "mustPurchaseToSet": "Must purchase <%= val %> to set it on <%= key %>.",
+ "typeRequired": "Type is required",
+ "keyRequired": "Key is required",
+ "notAccteptedType": "Type must be in [eggs, hatchingPotions, food, quests, gear]",
+ "contentKeyNotFound": "Key not found for Content <%= type %>",
+ "plusOneGem": "+1 klejnot",
+ "typeNotSellable": "Type is not sellable. Must be one of the following <%= acceptedTypes %>",
+ "userItemsKeyNotFound": "Key not found for user.items <%= type %>",
+ "pathRequired": "Path string is required",
+ "unlocked": "Przedmioty zostały odblokowane",
+ "alreadyUnlocked": "Pełen zestaw jest już odblokowany.",
+ "alreadyUnlockedPart": "Zestaw jest już częściowo odblokowany.",
"USD": "(USD)",
"newStuff": "Nowości",
"cool": "Przypomnij mi później",
@@ -64,6 +84,7 @@
"tourPetsPage": "Oto i stajnia! Po uzyskaniu 3 poziomu można, przy użyciu jaj i eliksirów, wykluwać chowańce. Chowaniec wykluty na targu pojawi się tutaj! Kliknij na obrazku przedstawiającym chowańca, aby dołączył do Twojego awatara. Racz chowańce karmą, która będzie dostępna od poziomu 3, a wyrosną na potężne wierzchowce.",
"tourMountsPage": "Kiedy ofiarujesz chowańcowi wystarczającą ilość karmy, by zmienił się w wierzchowca, pojawi się tutaj. (Chowańce, wierzchowce i karmę odblokowuje uzyskanie poziomu 3.) Kliknij na wierzchowcu, aby go osiodłać!",
"tourEquipmentPage": "Tutaj znajduje się Twoje wyposażenie! Twój rynsztunek wpływa na statystyki. Jeśli chcesz, by Twój awatar posiadał inny rynsztunek bez zmiany statystyk, kliknij \"Dostosuj strój\".",
+ "equipmentAlreadyOwned": "Już posiadasz tę część ekwipunku.",
"tourOkay": "Oki!",
"tourAwesome": "Świetnie!",
"tourSplendid": "Wspaniale!",
diff --git a/common/locales/pl/pets.json b/common/locales/pl/pets.json
index f2f2212f5c..cab4bb2036 100644
--- a/common/locales/pl/pets.json
+++ b/common/locales/pl/pets.json
@@ -12,6 +12,7 @@
"etherealLion": "Eteryczny lew",
"veteranWolf": "Wilk weteran",
"veteranTiger": "Tygrys weteran",
+ "veteranLion": "Veteran Lion",
"cerberusPup": "Szczenię Cerbera",
"hydra": "Hydra",
"mantisShrimp": "Krewetka modliszkowa",
@@ -19,7 +20,7 @@
"orca": "Orka",
"royalPurpleGryphon": "Purpurowy królewski gryf",
"phoenix": "Feniks",
- "bumblebee": "Bumblebee",
+ "magicalBee": "Magiczna Pszczoła",
"rarePetPop1": "Kliknij na złotą łapę, aby dowiedzieć się, jak możesz zdobyć tego rzadkiego chowańca poprzez pomoc w tworzeniu Habitiki!",
"rarePetPop2": "Jak zdobyć tego Chowańca?",
"potion": "Eliksir <%= potionType %>",
@@ -62,6 +63,7 @@
"hatchedPet": "<%= potion %> <%= egg %> się wykluło!",
"displayNow": "Wyświetl teraz",
"displayLater": "Wyświetl później",
+ "petNotOwned": "Nie posiadasz tego chowańca.",
"earnedCompanion": "Dzięki swej produktywności masz nowego towarzysza. Nakarm go by urósł!",
"feedPet": "Nakarmić <%= text %> twojego <%= name %>?",
"useSaddle": "Osiodłać <%= pet %>?",
@@ -83,5 +85,8 @@
"petKeyBoth": "Wypuść wszystkich",
"confirmPetKey": "Na pewno?",
"petKeyNeverMind": "Jeszcze nie",
+ "petsReleased": "Pets released.",
+ "mountsAndPetsReleased": "Mounts and pets released",
+ "mountsReleased": "Mounts released",
"gemsEach": "klejnotów każdy"
}
\ No newline at end of file
diff --git a/common/locales/pl/quests.json b/common/locales/pl/quests.json
index 6ac9368b66..d7b14affb5 100644
--- a/common/locales/pl/quests.json
+++ b/common/locales/pl/quests.json
@@ -38,7 +38,7 @@
"bossDmg2": "Tylko uczestnicy będą mogli walczyć z bossem i brać udział w podziale łupów.",
"bossDmg1Broken": "Każde zakończone zadanie Codzienne i Do-Zrobienia i każdy pozytywny Nawyk zadają obrażenia bossowi... Zrań go bardziej czerwieńszymi zadaniami lub Brutalnym uderzeniem i Eksplozją płomieni... Boss zada obrażenia każdemu uczestnikowi misji za każde zadanie Codzienne, którego nie ukończono (pomnożone przez siłę bossa) oprócz standardowych obrażeń, więc pilnuj stanu zdrowia swojej drużyny, wypełniając codzienne zadania... Wszystkie obrażenia zadane bossowi lub przez niego będą podsumowane przez crona (rozpoczęcie nowego dnia)...",
"bossDmg2Broken": "Tylko uczestnicy będą mogli walczyć z bossem i brać udział w podziale łupów...",
- "tavernBossInfo": "Complete Dailies and To-Dos and score positive Habits to damage the World Boss! Incomplete Dailies fill the Rage Bar. When the Rage bar is full, the World Boss will attack an NPC. A World Boss will never damage individual players or accounts in any way. Only active accounts not resting in the Inn will have their tasks tallied.",
+ "tavernBossInfo": "Wypełniaj swoje Codzienne i Do-Zrobienia, aby zadawać obrażenia światowemu bossowi. Niewypełnione Codzienne powoują wzrastanie Furii. Kiedy Pasek Furii się zapełni, Świadowy Boss zaatakuje NPCa. Światowy Boss nigdy w żaden sposób nie skrzywdzi inywidualnych graczy. Liczą się wyłącznie zadania aktywnych graczy, którzy aktualnie nie wypoczywają w Gospodzie.",
"tavernBossInfoBroken": "Wypełnij zadania Codzienne i Do-Zrobienia i zdobądź punkty za pozytywne Nawyki, aby zadać obrażenia Globalnemu Bossowi... Niedokończone Codzienne wypełniają pasek wzrastania Furii... Kiedy pasek Furii się wypełni, Globalny Boss zaatakuje bohatera niezależnego... Globalny Boss nigdy nie zada obrażeń indywidualnym graczom, ani ich kontom... Liczą się wyłącznie zadania aktywnych graczy, którzy nie wypoczywają w Gospodzie...",
"bossColl1": "Aby zbierać przedmioty, wykonuj pozytywne zadania. Przedmioty misyjne znajdujesz tak samo jak zwykłe, jednakże nie zobaczysz łupów aż do końca dnia – wtedy to wszystkie przedmioty zostaną wyszczególnione i dodane do sterty.",
"bossColl2": "Tylko uczestnicy mogą zbierać przedmioty i brać udział w podziale łupów.",
@@ -78,5 +78,24 @@
"whichQuestStart": "Którą misję chcesz rozpocząć?",
"getMoreQuests": "Zdobądź więcej misji",
"unlockedAQuest": "Odblokowałeś misję!",
- "leveledUpReceivedQuest": "Zyskałeś nowy poziom - Poziom <%= level %> - i otrzymałeś zwój misji!"
+ "leveledUpReceivedQuest": "Zyskałeś nowy poziom - Poziom <%= level %> - i otrzymałeś zwój misji!",
+ "questInvitationDoesNotExist": "No quest invitation has been sent out yet.",
+ "questInviteNotFound": "No quest invitation found.",
+ "guildQuestsNotSupported": "Guilds cannot be invited on quests.",
+ "questNotFound": "Quest \"<%= key %>\" not found.",
+ "questNotOwned": "You don't own that quest scroll.",
+ "questNotGoldPurchasable": "Quest \"<%= key %>\" is not a Gold-purchasable quest.",
+ "questLevelTooHigh": "You must be level <%= level %> to begin this quest.",
+ "questAlreadyUnderway": "Your party is already on a quest. Try again when the current quest has ended.",
+ "questAlreadyAccepted": "You already accepted the quest invitation.",
+ "noActiveQuestToLeave": "No active quest to leave",
+ "questLeaderCannotLeaveQuest": "Quest leader cannot leave quest",
+ "notPartOfQuest": "You are not part of the quest",
+ "noActiveQuestToAbort": "There is no active quest to abort.",
+ "onlyLeaderAbortQuest": "Only the group or quest leader can abort a quest.",
+ "questAlreadyRejected": "You already rejected the quest invitation.",
+ "cantCancelActiveQuest": "You can not cancel an active quest, use the abort functionality.",
+ "onlyLeaderCancelQuest": "Only the group or quest leader can cancel the quest.",
+ "questNotPending": "There is no quest to start.",
+ "questOrGroupLeaderOnlyStartQuest": "Only the quest leader or group leader can force start the quest"
}
\ No newline at end of file
diff --git a/common/locales/pl/questscontent.json b/common/locales/pl/questscontent.json
index 98b1c0a9b6..3bdb9322f8 100644
--- a/common/locales/pl/questscontent.json
+++ b/common/locales/pl/questscontent.json
@@ -304,9 +304,21 @@
"questBewilderCompletionChat": "`The Be-Wilder is DEFEATED!`\n\nWe've done it! The Be-Wilder lets out a ululating cry as it twists in the air, shedding feathers like falling rain. Slowly, gradually, it coils into a cloud of sparkling mist. As the newly-revealed sun pierces the fog, it burns away, revealing the coughing, mercifully human forms of Bailey, Matt, Alex.... and the April Fool himself.\n\n`Mistiflying is saved!`\n\nThe April Fool has enough shame to look a bit sheepish. “Oh, hm,” he says. “Perhaps I got a little…. carried away.”\n\nThe crowd mutters. Sodden flowers wash up on sidewalks. Somewhere in the distance, a roof collapses with a spectacular splash.\n\n“Er, yes,” the April Fool says. “That is. What I meant to say was, I’m dreadfully sorry.” He heaves a sigh. “I suppose it can’t all be fun and games, after all. It might not hurt to focus occasionally. Maybe I’ll get a head start on next year’s pranking.”\n\nRedphoenix coughs meaningfully.\n\n“I mean, get a head start on this year’s spring cleaning!” the April Fool says. “Nothing to fear, I’ll have Habit City in spit-shape soon. Luckily nobody is better than I at dual-wielding mops.”\n\nEncouraged, the marching band starts up.\n\nIt isn’t long before all is back to normal in Habit City. Plus, now that the Be-Wilder has evaporated, the magical bees of Mistiflying bustle back to work, and soon the flowers are blooming and the city is floating once more.\n\nAs Habiticans cuddle the magical fuzzy bees, the April Fool’s eyes light up. “Oho, I’ve had a thought! Why don’t you all keep some of these fuzzy Bee Pets and Mounts? It’s a gift that perfectly symbolizes the balance between hard work and sweet rewards, if I’m going to get all boring and allegorical on you.” He winks. “Besides, they don’t have stingers! Fool’s honor.”",
"questBewilderBossRageTitle": "Beguilement Strike",
"questBewilderBossRageDescription": "When this gauge fills, The Be-Wilder will unleash its Beguilement Strike on Habitica!",
- "questBewilderDropBumblebeePet": "Bumblebee (Pet)",
- "questBewilderDropBumblebeeMount": "Bumblebee (Mount)",
+ "questBewilderDropBumblebeePet": "Magiczna Pszczoła (Chowaniec)",
+ "questBewilderDropBumblebeeMount": "Magiczna Pszczoła (Wierzchowiec)",
"questBewilderBossRageMarket": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nOh no! Despite our best efforts, we've gotten distracted by the Be-Wilder’s charming illusions and have forgotten to do some of our Dailies! With a cackling cry, the shining bird beats its wings, raising a swarm of mist around Alex the Merchant. When the fog clears, he has been possessed! “Have some free samples!” he shouts gleefully, and begins to hurl exploding eggs and potions at fleeing Habiticans. Not the most favorable of sales, to be sure.\n\nHurry! Let's stay focused on our Dailies to defeat this monster before it possesses someone else.",
"questBewilderBossRageStables": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nAhh!!! Once again the Be-Wilder has dazzled us into neglecting our Dailies, and now it has attacked Matt the Beast Master! With a swirl of mist, Matt transforms into a terrifying winged creature, and all the pets and mounts howl sadly in their stables. Quickly, stay focused on your tasks to defeat this dastardly distraction!",
- "questBewilderBossRageBailey": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nLook out! In the middle of reporting the news, Bailey the Town Crier has been possessed by the Be-Wilder! She lets out an evil, uninformative screech as she rises into the air. Now how will we know what’s going on?\n\nDon't give up... we're so close to defeating this bothersome bird for once and for all!"
+ "questBewilderBossRageBailey": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nLook out! In the middle of reporting the news, Bailey the Town Crier has been possessed by the Be-Wilder! She lets out an evil, uninformative screech as she rises into the air. Now how will we know what’s going on?\n\nDon't give up... we're so close to defeating this bothersome bird for once and for all!",
+ "questFalconText": "The Birds of Preycrastination",
+ "questFalconNotes": "Mt. Habitica is being overshadowed by a looming mountain of To-Dos. It used to be a place to picnic and enjoy a sense of accomplishment, until the neglected tasks grew out of control. Now it's home to fearsome Birds of Preycrastination, foul creatures which stop Habiticans from completing their tasks!
\"It's too hard!\" they caw at @JonArinbjorn and @Onheiron. \"It'll take too long to do right now! It won't make any difference if you wait until tomorrow! Why don't you do something fun instead?\"
No more, you vow. You will climb your personal mountain of To-Dos and defeat the Birds of Preycrastination!",
+ "questFalconCompletion": "Having finally triumphed over the Birds of Preycrastination, you settle down to enjoy the view and your well-earned rest.
\"Wow!\" says @Trogdorina. \"You won!\"
@Squish adds, \"Here, take these eggs I found as a reward.\"",
+ "questFalconBoss": "Birds of Preycrastination",
+ "questFalconDropFalconEgg": "Sokół (jajo)",
+ "questFalconUnlockText": "Odblokowuje dostęp do kupna jaj sokoła na Targu.",
+ "questTreelingText": "The Tangle Tree",
+ "questTreelingNotes": "It's the annual Garden Competition, and everyone is talking about the mysterious project which @aurakami has promised to unveil. You join the crowd on the day of the big announcement, and marvel at the introduction of a moving tree. @fuzzytrees explains that the tree will help with garden maintenance, showing how it can mow the lawn, trim the hedge and prune the roses all at the same time – until the tree suddenly goes wild, turning its secateurs on its creator! The crowd panics as everyone tries to flee, but you aren't afraid – you leap forward, ready to do battle.",
+ "questTreelingCompletion": "You dust yourself off as the last few leaves drift to the floor. In spite of the upset, the Garden Competition is now safe – although the tree you just reduced to a heap of wood chips won't be winning any prizes! \"Still a few kinks to work out there,\" @PainterProphet says. \"Perhaps someone else would do a better job of training the saplings. Do you fancy a go?\"",
+ "questTreelingBoss": "Tangle Tree",
+ "questTreelingDropTreelingEgg": "Treeling (Egg)",
+ "questTreelingUnlockText": "Unlocks purchasable Treeling eggs in the Market"
}
\ No newline at end of file
diff --git a/common/locales/pl/rebirth.json b/common/locales/pl/rebirth.json
index ca3085242d..e00b2b2cdb 100644
--- a/common/locales/pl/rebirth.json
+++ b/common/locales/pl/rebirth.json
@@ -5,7 +5,7 @@
"rebirthStartOver": "Po odrodzeniu twoja postać rozpocznie grę na poziomie 1.",
"rebirthAdvList1": "Odzyskujesz pełnię zdrowia.",
"rebirthAdvList2": "Nie posiadasz żadnych punktów doświadczenia, złota ani ekwipunku (z pominięciem darmowych przedmiotów takich jak tajemnicze przedmioty).",
- "rebirthAdvList3": "Twoje Nawyki, Codzienne, oraz Do-Zrobienia zostaną zresetowane do poziomu żółtego, a ich serie wyzerowane.",
+ "rebirthAdvList3": "Your Habits, Dailies, and To-Dos reset to yellow, and streaks reset, except for challenge tasks.",
"rebirthAdvList4": "Na początku jesteś w klasie Wojownika, dopóki nie zapracujesz sobie na zmianę klasy.",
"rebirthInherit": "Twoja postać dziedziczy pewne cechy ze swojego poprzedniego wcielenia.",
"rebirthInList1": "Zadania, historia oraz ustawienia konta pozostają bez zmian.",
@@ -24,5 +24,6 @@
"rebirthPop": "Stwórz nową postać na Poziomie 1 zachowując osiągnięcia, przedmioty kolekcjonerskie oraz zadania wraz z historią.",
"rebirthName": "Kula Odrodzenia",
"reborn": "Odrodzony, najwyższy poziom <%= reLevel %>",
- "confirmReborn": "Na pewno?"
+ "confirmReborn": "Na pewno?",
+ "rebirthComplete": "You have been reborn!"
}
\ No newline at end of file
diff --git a/common/locales/pl/settings.json b/common/locales/pl/settings.json
index 2b06b44ab7..4deebbbaec 100644
--- a/common/locales/pl/settings.json
+++ b/common/locales/pl/settings.json
@@ -47,6 +47,7 @@
"customDayStart": "Własny początek dnia",
"changeCustomDayStart": "Zmienić własny początek dnia?",
"sureChangeCustomDayStart": "Czy na pewno chcesz zmienić własny początek dnia?",
+ "customDayStartHasChanged": "Your custom day start has changed.",
"nextCron": "Twoje Codzienne zresetują się po pierwszym użyciu Habitiki po <%= time %>. Upewnij się, że ukończyłeś swoje Codzienne przed tym czasem!",
"customDayStartInfo1": "Habitica domyślnie sprawdza i resetuje twoje Codzienne o północy każdego dnia w twojej strefie czasowej. Tutaj możesz zmienić ten czas.",
"misc": "Pozostałe",
@@ -61,12 +62,23 @@
"newUsername": "Nowa nazwa użytkownika",
"dangerZone": "Strefa zagrożenia",
"resetText1": "UWAGA! Ta opcja zresetuje wiele części twojego konta. Odradzamy taki krok, jednak niektórzy gracze mogą uznać tę funkcję za przydatną po wstępnym wypróbowaniu strony.",
- "resetText2": "Stracisz swój poziom, całe złoto i punkty doświadczenia. Wszystkie Twoje zadania zostaną na zawsze usunięte, razem z historią ich wypełnienia. Stracisz także wyposażenie, choć będziesz mógł je kupić ponownie, włączając w to przedmioty limitowane i tylko dla subskrybentów, które już posiadasz (aby odkupić wyposażenie przeznaczone dla konkretnej klasy, musisz do niej przynależeć). Zachowasz swoją klasę, chowańce i wierzchowce. Być może lepiej będzie jeśli użyjesz Kuli Odrodzenia – to bezpieczniejsze i zachowa wszystkie Twoje zadania.",
+ "resetText2": "You will lose all your levels, gold, and experience points. All your tasks (except those from challenges) will be deleted permanently and you will lose all of their historical data. You will lose all your equipment but you will be able to buy it all back, including all limited edition equipment or subscriber Mystery items that you already own (you will need to be in the correct class to re-buy class-specific gear). You will keep your current class and your pets and mounts. You might prefer to use an Orb of Rebirth instead, which is a much safer option and which will preserve your tasks.",
"deleteText": "Jesteś pewien? Usunie to twoje konto na zawsze i nie będzie można go nigdy odzyskać! Będziesz musiał zarejestrować się ponownie na nowym koncie by używać Habitica ponownie. Posiadane lub wydane Klejnoty nie zostaną zwrócone. Jeśli jesteś absolutnie pewien swojej decyzji, wpisz <%= deleteWord %> w okno dialogowe poniżej.",
"API": "API",
+ "APIv3": "API v3",
+ "APIv2": "API v2 - Deprecated",
"APIText": "Skopiuj, by używać w innych aplikacjach. Traktuj jednak swój token API jak hasło i nie dziel się nim z innymi. Czasem możesz zostać poproszony o swoje ID użytkownika, ale nigdy nie publikuj swojego tokenu API tam, gdzie mogą go zobaczyć inne osoby, włączając w to Github.",
"APIToken": "Token API (to Twoje hasło – zobacz ostrzeżenie powyżej!)",
+ "thirdPartyApps": "Aplikacje osób trzecich",
+ "dataToolDesc": "Strona internetowa, która pokazuje ci niektóre informacje na temat twojego konta Habitica, takie jak statystyki twoich zadań, ekwipunku i umiejętności.",
+ "beeminder": "Beeminder",
+ "beeminderDesc": "Let Beeminder automatically monitor your Habitica To-Dos. You can commit to maintaining a target number of To-Dos completed per day or per week, or you can commit to gradually reducing your remaining number of uncompleted To-Dos. (By \"commit\" Beeminder means under threat of paying actual money! But you may also just like Beeminder's fancy graphs.)",
+ "chromeChatExtension": "Habitica chat - rozszerzenie dla Chrome ",
+ "chromeChatExtensionDesc": "Rozszerzenie z Czatem dla przeglądarki Chrome dodaje intuicyjny czas dla całego habitica.com. Pozwala użytkownikom rozmawiać w Tawernie, razem z drużyną i gildiami.",
+ "otherExtensions": "Inne Rozszerzenia",
+ "otherDesc": "Znajdź inne aplikacje, rozszerzenia i narzędzia na Habitica wiki.",
"resetDo": "Zrób to, zresetuj moje konto!",
+ "resetComplete": "Reset complete!",
"fixValues": "Napraw wartości",
"fixValuesText1": "Jeśli napotkałeś na błąd lub popełniłeś pomyłkę która niesłusznie zmieniła twoją postać (otrzymałeś niesłusznie obrażenia, Złoto na które nie zasłużyłeś, itp.), możesz tutaj ręcznie zmienić wartości liczbowe. Tak, pozwala to na oszukiwanie: używaj tej funkcji mądrze, lub będziesz sabotował własne wysiłki w budowaniu nawyków!",
"fixValuesText2": "Zauważ, że nie możesz przywrócić tutaj Serii w indywidualnych zadaniach. Aby to zrobić, edytuj Codzienne: w Zaawansowanych Opcjach znajdziesz pole do przywracania Serii.",
@@ -96,6 +108,7 @@
"emailNotifications": "Powiadomienia e-mail",
"wonChallenge": "Wygrałeś wyzwanie!",
"newPM": "Otrzymane wiadomości prywatne",
+ "sentGems": "Sent gems!",
"giftedGems": "Podarowane klejnoty",
"giftedGemsInfo": "<%= amount %> klejnotów - przez <%= name %>",
"giftedSubscription": "Podarowane abonamenty",
@@ -135,6 +148,11 @@
"webhooks": "Webhooki",
"enabled": "Włączone",
"webhookURL": "Link do webhooka",
+ "invalidUrl": "invalid url",
+ "invalidEnabled": "the \"enabled\" parameter should be a boolean",
+ "regIdRequired": "RegId is required",
+ "pushDeviceAdded": "Push device added successfully",
+ "pushDeviceAlreadyAdded": "The user already has the push device",
"add": "Dodaj",
"buyGemsGoldCap": "Limit podniesiony do <%= amount %>",
"mysticHourglass": "<%= amount %> Mistyczne Klepsydry",
@@ -149,5 +167,5 @@
"amazonPayments": "Płatności Amazon",
"timezone": "Strefa czasowa",
"timezoneUTC": "Habitica korzysta ze strefy czasowej ustawionej na twoim komputerze, to znaczy: <%= utc %>",
- "timezoneInfo": "Jeżeli wyświetlana strefa czasowa się nie zgadza, najpierw przeładuj stronę korzystając z przycisków \"odśwież\" lub \"przeładuj\" w twojej przeglądarce, aby wysłać do Habitica najnowsze dane. Jeśli nadal się nie zgadza, zmień ustawienia strefy czasowej na swoim komputerze i ponownie przeładuj stronę.
Jeśli korzystasz z Habitica na innym komputerze lub urządzeniach przenośnych, ustawiona strefa czasowa musi być identyczna na każdym z nich. Jeśli twoje Codzienne resetują się w niewłaściwym czasie, powtórz powyższy test na pozostałych komputerach i przeglądarkach na urządzeniach mobilnych."
+ "timezoneInfo": "Jeśli strefa czasowa jest błędna, spróbuj najpierw odświeżyć stronę przyciskiem odświeżania w przeglądarce, aby upewnić się, że Habitica ma dostęp do najnowszych danych. Jeśli strefa wciąż jest nieprawidłowa, ustaw ją na swoim komputerze i znów odśwież stronę.
Jeśli używasz Habitiki na innych komputerach lub urządzeniach mobilnych, strefa czasowa na każdym z nich musi być taka sama. Jeśli twoje codzienne zadania resetują się o niewłaściwej porze, sprawdź w ten sam sposób inne komputery oraz przeglądarki w twoich urządzeniach mobilnych."
}
\ No newline at end of file
diff --git a/common/locales/pl/spells.json b/common/locales/pl/spells.json
index bf6f116970..d3633eb829 100644
--- a/common/locales/pl/spells.json
+++ b/common/locales/pl/spells.json
@@ -35,8 +35,8 @@
"spellSpecialSnowballAuraNotes": "Rzuć w członka drużyny śnieżną pigułą! Co może pójść nie tak? Trwa aż do jego następnego dnia.",
"spellSpecialSaltText": "Sól",
"spellSpecialSaltNotes": "Ktoś rzucił w ciebie kulą śnieżną. Ha ha, bardzo śmieszne. A teraz otrzepcie mnie z tego śniegu!",
- "spellSpecialSpookDustText": "Straszne iskierki",
- "spellSpecialSpookDustNotes": "Zamień przyjaciela w latający koc z oczami!",
+ "spellSpecialSpookySparklesText": "Spooky Sparkles",
+ "spellSpecialSpookySparklesNotes": "Turn a friend into a floating blanket with eyes!",
"spellSpecialOpaquePotionText": "Nieprzejrzysty eliksir",
"spellSpecialOpaquePotionNotes": "Anuluje efekt strasznych iskierek.",
"spellSpecialShinySeedText": "Lśniące nasiono",
@@ -46,5 +46,11 @@
"spellSpecialSeafoamText": "Piana morska",
"spellSpecialSeafoamNotes": "Zamień przyjaciela w morskie stworzenie!",
"spellSpecialSandText": "Piasek",
- "spellSpecialSandNotes": "Anuluj skutki Piany Morskiej"
+ "spellSpecialSandNotes": "Anuluj skutki Piany Morskiej",
+ "spellNotFound": "Skill \"<%= spellId %>\" not found.",
+ "partyNotFound": "Party not found",
+ "targetIdUUID": "\"targetId\" must be a valid User ID.",
+ "challengeTasksNoCast": "Casting a skill on challenge tasks is not allowed.",
+ "spellNotOwned": "You don't own this skill.",
+ "spellLevelTooHigh": "You must be level <%= level %> to use this skill."
}
\ No newline at end of file
diff --git a/common/locales/pl/subscriber.json b/common/locales/pl/subscriber.json
index f217f57087..c388402e12 100644
--- a/common/locales/pl/subscriber.json
+++ b/common/locales/pl/subscriber.json
@@ -4,6 +4,8 @@
"subDescription": "Kupuj Klejnoty za złoto, otrzymuj co miesiąc tajemnicze przedmioty, zachowaj historię postępu, powiększ dwukrotnie limit znajdowanych przedmiotów, wspieraj twórców. Kliknij, żeby dowiedzieć się więcej.",
"buyGemsGold": "Kup Klejnoty za Złoto",
"buyGemsGoldText": "Kupiec Alexander sprzeda ci Klejnoty za <%= gemCost %> złota za klejnot. Jego comiesięczne dostawy są ograniczone do <%= gemLimit %> Klejnotów na miesiąc, ale ten limit rośnie o 5 Klejnotów na każde trzy miesiące ciągłego abonamentu, aż do maksymalnie 50 Klejnotów miesięcznie!",
+ "mustSubscribeToPurchaseGems": "Must subscribe to purchase gems with GP",
+ "reachedGoldToGemCap": "You've reached the Gold=>Gem conversion cap <%= convCap %> for this month. We have this to prevent abuse / farming. The cap will reset within the first three days of next month.",
"retainHistory": "Zachowuj dodatkowe wpisy w historii konta",
"retainHistoryText": "Historia ukończonych Do-Zrobienia i zadań jest dostępna dłużej.",
"doubleDrops": "Codzienny limit łupów podwojony",
@@ -29,6 +31,7 @@
"manageSub": "Kliknij by zarządzać abonamentem",
"cancelSub": "Anuluj abonament",
"canceledSubscription": "Anulowany Abonament",
+ "cancelingSubscription": "Canceling the subscription",
"adminSub": "Abonament administratora",
"morePlans": "Więcej możliwości wkrótce",
"organizationSub": "Organizacja prywatna",
@@ -73,6 +76,9 @@
"timeTravelersPopover": "Widzimy że zdobyłeś Mistyczną Klepsydrę, więc z chęcią zakrzywimy dla ciebie czas. Wybierz proszę chowańca, wierzchowca lub Tajemniczy Zestaw Przedmiotów, który ci się podoba. Listę z poprzednimi zestawami znajdziesz tutaj! Jeśli nie podoba ci się żaden z tych, może byłbyś zainteresowany jednym z naszych modnych futurystycznych Steampunkowych Zestawów Przedmiotów?",
"timeTravelersAlreadyOwned": "Gratulacje! Posiadasz już wszystko,co Podróżnicy w Czasie aktualnie oferują. Dziękujemy za wspieranie strony!",
"mysticHourglassPopover": "Mistyczna Klepsydra pozwala na kupienie pewnych przedmiotów o ograniczonej czasowo dostępności, takich jak comiesięczne Zestawy Tajemniczych Przedmiotów i nagrody od Światowych bossów z przeszłości.",
+ "mysterySetNotFound": "Mystery set not found, or set already owned.",
+ "mysteryItemIsEmpty": "Mystery items are empty",
+ "mysteryItemOpened": "Mystery item opened.",
"mysterySet201402": "Zestaw skrzydlatego posłańca",
"mysterySet201403": "Zestaw przemierzania lasów",
"mysterySet201404": "Zestaw motyla zmierzchu",
@@ -99,6 +105,8 @@
"mysterySet201601": "Zestaw czempiona postanowień",
"mysterySet201602": "Zestaw Łamacza Serc",
"mysterySet201603": "Zestaw Szczęśliwej Koniczyny",
+ "mysterySet201604": "Leaf Warrior Set",
+ "mysterySet201605": "Marching Bard Set",
"mysterySet301404": "Standardowy zestaw steampunkowy",
"mysterySet301405": "Zestaw steampunkowych akcesoriów",
"mysterySetwondercon": "Konwent Wspaniałości",
@@ -110,9 +118,25 @@
"hourglassBuyItemConfirm": "Kupić ten przedmiot za 1 Mistyczną Klepsydrę?",
"petsAlreadyOwned": "Masz już tego chowańca.",
"mountsAlreadyOwned": "Masz już tego wierzchowca.",
- "typeNotAllowedHourglass": "Tego przedmiotu nie można kupić za Mistyczne Klepsydry. Dostępne typy przedmiotów:",
+ "typeNotAllowedHourglass": "Item type not supported for purchase with Mystic Hourglass. Allowed types: <%= allowedTypes %>",
"petsNotAllowedHourglass": "Tego chowańca nie można kupić za Mistyczne Klepsydry.",
"mountsNotAllowedHourglass": "Tego wierzchowca nie można kupić za Mistyczne Klepsydry.",
"hourglassPurchase": "Kupiłeś przedmiot używając Mistycznej Klepsydry!",
- "hourglassPurchaseSet": "Kupiłeś zestaw przedmiotów używając Mistycznej Klepsydry!"
+ "hourglassPurchaseSet": "Kupiłeś zestaw przedmiotów używając Mistycznej Klepsydry!",
+ "missingUnsubscriptionCode": "Missing unsubscription code.",
+ "missingSubscription": "User does not have a plan subscription",
+ "missingSubscriptionCode": "Missing subscription code. Possible values: basic_earned, basic_3mo, basic_6mo, google_6mo, basic_12mo.",
+ "cannotDeleteActiveAccount": "You have an active subscription, cancel your plan before deleting your account.",
+ "paymentNotSuccessful": "The payment was not successful",
+ "planNotActive": "The plan hasn't activated yet (due to a PayPal bug). It will begin <%= nextBillingDate %>, after which you can cancel to retain your full benefits",
+ "notAllowedHourglass": "Pet/Mount not available for purchase with Mystic Hourglass.",
+ "readCard": "<%= cardType %> has been read",
+ "cardTypeRequired": "Card type required",
+ "cardTypeNotAllowed": "Unknown card type.",
+ "invalidCoupon": "Invalid coupon code.",
+ "couponUsed": "Coupon code already used.",
+ "noSudoAccess": "You don't have sudo access.",
+ "couponCodeRequired": "The coupon code is required.",
+ "eventRequired": "\"req.params.event\" is required.",
+ "countRequired": "\"req.query.count\" is required."
}
\ No newline at end of file
diff --git a/common/locales/pl/tasks.json b/common/locales/pl/tasks.json
index c35ee991dc..33394743db 100644
--- a/common/locales/pl/tasks.json
+++ b/common/locales/pl/tasks.json
@@ -73,6 +73,7 @@
"clearTags": "Wyczyść",
"hideTags": "Ukryj",
"showTags": "Pokaż",
+ "toRequired": "You must supply a to value",
"startDate": "Data rozpoczęcia",
"startDateHelpTitle": "Kiedy to zadanie powinno się rozpocząć?",
"startDateHelp": "Wyznacz termin wykonania tego zadania. Przed wybraną datą nie będą z nim związane żadne efekty.",
@@ -88,8 +89,9 @@
"fortifyName": "Mikstura wzmocnienia",
"fortifyPop": "Przywraca wszystkie zadania do neutralnej wartości (żółty kolor), oraz uzdrawia całe stracone Zdrowie.",
"fortify": "Wzmocnij",
- "fortifyText": "Wzmocnienie przywróci wszystkie zadania do wartości neutralnej (żółty kolor), tak jakbyś dopiero co je dodał, a w dodatku uzdrowi cię do pełna. Jest to świetne rozwiązanie, jeśli czerwone zadania sprawiają w grze zbyt wiele trudności lub z powodu niebieskich zadań gra jest zbyt łatwa. Jeśli rozpoczęcie od zera brzmi znacznie bardziej motywująco, to poświęć Klejnoty i poczuj ulgę!",
+ "fortifyText": "Fortify will return all your tasks, except challenge tasks, to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!",
"confirmFortify": "Na pewno?",
+ "fortifyComplete": "Fortify complete!",
"sureDelete": "Czy na pewno chcesz usunąć <%= taskType %> opisany \"<%= taskText %>\"?",
"streakCoins": "Premia za serię!",
"pushTaskToTop": "Umieść zadanie na górze listy. Przytrzymaj CTRL lub CMD, aby umieścić je na samym dole.",
@@ -112,5 +114,18 @@
"rewardHelp2": "Wyposażenie wpływa na Twoje statystyki (<%= linkStart %>Użytkownik > Statystyki<%= linkEnd %>).",
"rewardHelp3": "Specjalne elementy wyposażenia będą się pojawiały podczas wydarzeń o skali światowej.",
"rewardHelp4": "Nie bój się ustalać własnych nagród! Sprawdź przykłady tutaj.",
- "clickForHelp": "Kliknij po pomoc"
+ "clickForHelp": "Kliknij po pomoc",
+ "taskIdRequired": "\"taskId\" must be a valid UUID.",
+ "taskNotFound": "Task not found.",
+ "invalidTaskType": "Task type must be one of \"habit\", \"daily\", \"todo\", \"reward\".",
+ "cantDeleteChallengeTasks": "A task belonging to a challenge can't be deleted.",
+ "checklistOnlyDailyTodo": "Checklists are supported only on dailies and todos",
+ "checklistItemNotFound": "No checklist item was found with given id.",
+ "itemIdRequired": "\"itemId\" must be a valid UUID.",
+ "tagNotFound": "No tag item was found with given id.",
+ "tagIdRequired": "\"tagId\" must be a valid UUID corresponding to a tag belonging to the user.",
+ "positionRequired": "\"position\" is required and must be a number.",
+ "cantMoveCompletedTodo": "Can't move a completed todo.",
+ "directionUpDown": "\"direction\" is required and must be 'up' or 'down'",
+ "alreadyTagged": "The task is already tagged with given tag."
}
\ No newline at end of file
diff --git a/common/locales/pt/backgrounds.json b/common/locales/pt/backgrounds.json
index b95de39463..75757f890e 100644
--- a/common/locales/pt/backgrounds.json
+++ b/common/locales/pt/backgrounds.json
@@ -149,16 +149,23 @@
"backgroundGrandStaircaseNotes": "Descer pela Grande Escadaria",
"backgrounds032016": "Conjunto 22: Lançado em Março de 2016",
"backgroundDeepMineText": "Mina Profunda",
- "backgroundDeepMineNotes": "Encontre metais preciosos em uma Mina Profunda.",
+ "backgroundDeepMineNotes": "Encontre metais preciosos na Mina Profunda.",
"backgroundRainforestText": "Floresta Tropical",
"backgroundRainforestNotes": "Aventure-se em uma Floresta Tropical.",
"backgroundStoneCircleText": "Círculo de Pedras",
- "backgroundStoneCircleNotes": "Lance feitiços em um Círculo de Pedras.",
- "backgrounds042016": "SET 23: Released April 2016",
- "backgroundArcheryRangeText": "Archery Range",
- "backgroundArcheryRangeNotes": "Practice on the Archery Range.",
- "backgroundGiantFlowersText": "Giant Flowers",
- "backgroundGiantFlowersNotes": "Frolic atop Giant Flowers.",
- "backgroundRainbowsEndText": "End of the Rainbow",
- "backgroundRainbowsEndNotes": "Discover gold at the End of the Rainbow."
+ "backgroundStoneCircleNotes": "Lançar feitiços em um Círculo de Pedras.",
+ "backgrounds042016": "Conjunto 23: Lançado em Abril de 2016",
+ "backgroundArcheryRangeText": "Campo de tiro com arco",
+ "backgroundArcheryRangeNotes": "Praticar no campo de tiro com arco.",
+ "backgroundGiantFlowersText": "Flores gigantes",
+ "backgroundGiantFlowersNotes": "Divirta-se em cima de Flores Gigantes.",
+ "backgroundRainbowsEndText": "Final do arco-íris.",
+ "backgroundRainbowsEndNotes": "Descubra ouro no final do arco-íris",
+ "backgrounds052016": "Conjunto 24: Lançado em Maio de 2016.",
+ "backgroundBeehiveText": "Colmeia",
+ "backgroundBeehiveNotes": "Buzz and dance in a Beehive.",
+ "backgroundGazeboText": "Gazebo",
+ "backgroundGazeboNotes": "Battle a Gazebo.",
+ "backgroundTreeRootsText": "Raízes de árvore",
+ "backgroundTreeRootsNotes": "Explore as raízes de árvores."
}
\ No newline at end of file
diff --git a/common/locales/pt/challenge.json b/common/locales/pt/challenge.json
index 9c9d98966e..5ad710ea47 100644
--- a/common/locales/pt/challenge.json
+++ b/common/locales/pt/challenge.json
@@ -63,5 +63,21 @@
"congratulations": "Parabéns!",
"hurray": "Hurra!",
"noChallengeOwner": "sem dono",
- "noChallengeOwnerPopover": "Esse desafio não possui um dono porque quem criou o desafio deletou sua conta."
+ "noChallengeOwnerPopover": "Esse desafio não possui um dono porque quem criou o desafio deletou sua conta.",
+ "challengeMemberNotFound": "Usuário não encontrado entre os membros do desafio.",
+ "onlyGroupLeaderChal": "Somente o líder do grupo pode criar desafios",
+ "tavChalsMinPrize": "O prêmio precisa ser de no mínimo 1 gema para desafios da Taverna.",
+ "cantAfford": "Você não consegue pagar esse prêmio. Compre mais gemas ou reduza o tamanho do prêmio.",
+ "challengeIdRequired": "\"challengeId\" precisa ser um UUID válido.",
+ "winnerIdRequired": "\"winnerId\" precisa ser um UUID válido.",
+ "challengeNotFound": "Desafio não encontrado.",
+ "onlyLeaderDeleteChal": "Apenas o líder desafio pode excluí-lo.",
+ "onlyLeaderUpdateChal": "Apenas o líder desafio pode atualizá-lo.",
+ "winnerNotFound": "Vencedor com id \"<%= userId %>\" não encontrado ou não faz parte do desafio.",
+ "noCompletedTodosChallenge": "\"includeComepletedTodos\" is not supported when fetching challenge tasks.",
+ "userTasksNoChallengeId": "When \"tasksOwner\" is \"user\" \"challengeId\" can't be passed.",
+ "onlyChalLeaderEditTasks": "Tarefas pertencentes a um desafio só podem ser editadas pelo líder.",
+ "userAlreadyInChallenge": "Usuário já está participando desse desafio.",
+ "cantOnlyUnlinkChalTask": "Only broken challenges tasks can be unlinked.",
+ "shortNameTooShort": "Nome da etiqueta deve ter pelo menos 3 caracteres."
}
\ No newline at end of file
diff --git a/common/locales/pt/character.json b/common/locales/pt/character.json
index 724cd0fa1d..3cbf750480 100644
--- a/common/locales/pt/character.json
+++ b/common/locales/pt/character.json
@@ -1,4 +1,5 @@
{
+ "communityGuidelinesWarning": "Por favor, tenha em mente que seu Nome de exibição, foto de perfil, e sinopse devem estar de acordo com as Diretrizes da Comunidade (ou seja, sem profanidades, sem tópicos adultos, sem insultos, etc). Se você tem qualquer dúvida sobre se algo é ou não apropriado, entre em contato pelo email leslie@habitica.com!",
"statsAch": "Estatísticas & Conquistas",
"profile": "Perfil",
"avatar": "Personalizar Avatar",
@@ -34,7 +35,7 @@
"beard": "Barba",
"mustache": "Bigode",
"flower": "Flor",
- "wheelchair": "Wheelchair",
+ "wheelchair": "Cadeira de rodas",
"basicSkins": "Peles Básicas",
"rainbowSkins": "Peles de Arco-Íris",
"pastelSkins": "Peles em Tons Pastel",
@@ -109,6 +110,7 @@
"mage": "Mago",
"mystery": "Mistério",
"changeClass": "Alterar Classe, Restituir Pontos de Atributo",
+ "lvl10ChangeClass": "Para mudar de classe você precisa ser pelo menos nível 10.",
"levelPopover": "A cada nível que alcançar você terá um ponto para distribuir para um atributo de sua escolha. Você pode fazer isso manualmente, ou deixar o jogo decidir por você usando uma das opções de Distribuição Automática.",
"unallocated": "Pontos de Atributo não Distribuídos",
"haveUnallocated": "Você tem <%= points %> Ponto(s) de Atributo não usados",
@@ -164,5 +166,7 @@
"int": "INT",
"showQuickAllocation": "Mostrar distribuição de status",
"hideQuickAllocation": "Esconder distribuição de status",
- "quickAllocationLevelPopover": "Cada nível concederá a você um ponto para distribuir em um atributo de sua escolha. Você pode fazer isso manualmente, ou deixar o jogo decidir por você usando uma das opções de Distribuição Automática encontradas em Usuário -> Status do Avatar."
+ "quickAllocationLevelPopover": "Cada nível concederá à você um ponto para distribuir em um atributo de sua escolha. Você pode fazer isso manualmente, ou deixar o jogo decidir por você usando uma das opções de Distribuição Automática encontradas em Usuário -> Status do Avatar.",
+ "invalidAttribute": "\"<%= attr %>\" não é um atributo válido.",
+ "notEnoughAttrPoints": "Você não tem pontos de atributo suficientes."
}
\ No newline at end of file
diff --git a/common/locales/pt/content.json b/common/locales/pt/content.json
index 45068be301..c27340bd4f 100644
--- a/common/locales/pt/content.json
+++ b/common/locales/pt/content.json
@@ -31,7 +31,7 @@
"dropEggCactusAdjective": "um espinhoso",
"dropEggBearCubText": "Urso Filhote",
"dropEggBearCubMountText": "Urso",
- "dropEggBearCubAdjective": "um bravo",
+ "dropEggBearCubAdjective": "um corajoso",
"questEggGryphonText": "Grifo",
"questEggGryphonMountText": "Grifo",
"questEggGryphonAdjective": "um orgulhoso",
@@ -112,7 +112,13 @@
"questEggMonkeyAdjective": "um travesso",
"questEggSnailText": "Caracol",
"questEggSnailMountText": "Caracol",
- "questEggSnailAdjective": "um devagar mas estável",
+ "questEggSnailAdjective": "um devagar, mas estável",
+ "questEggFalconText": "Falcão",
+ "questEggFalconMountText": "Falcão",
+ "questEggFalconAdjective": "um rápido",
+ "questEggTreelingText": "Treeling",
+ "questEggTreelingMountText": "Treeling",
+ "questEggTreelingAdjective": "a leafy",
"eggNotes": "Ache uma poção de eclosão para usar nesse ovo e ele irá eclodir em um <%= eggAdjective(locale) %> <%= eggText(locale) %>.",
"hatchingPotionBase": "Básico",
"hatchingPotionWhite": "Branco",
@@ -126,6 +132,7 @@
"hatchingPotionGolden": "Dourado",
"hatchingPotionSpooky": "Assustador",
"hatchingPotionPeppermint": "Hortelã",
+ "hatchingPotionFloral": "Floral",
"hatchingPotionNotes": "Use-o em um ovo, e ele chocará como um mascote <%= potText(locale) %>.",
"premiumPotionAddlNotes": "Não utilizável em ovos de mascote de missão.",
"foodMeat": "Carne",
diff --git a/common/locales/pt/contrib.json b/common/locales/pt/contrib.json
index aa979de33c..e892c4e1ce 100644
--- a/common/locales/pt/contrib.json
+++ b/common/locales/pt/contrib.json
@@ -35,8 +35,12 @@
"hallContributors": "Salão dos Contribuidores",
"hallPatrons": "Salão dos Patrões",
"rewardUser": "Recompensar Usuário",
- "UUID": "UUID",
+ "UUID": "ID do Usuário",
"loadUser": "Carregar Usuário",
+ "noAdminAccess": "Você não possui acesso de administrador.",
+ "pageMustBeNumber": "req.query.page precisa ser um número",
+ "userNotFound": "Usuário não encontrado.",
+ "invalidUUID": "UUID precisa ser válido",
"title": "Título",
"moreDetails": "Mais detalhes (1-7)",
"moreDetails2": "mais detalhes (8-9)",
@@ -52,8 +56,8 @@
"visitHeroes": "Visite o Salão de Heróis (colaboradores e apoiadores)",
"conLearn": "Saiba mais sobre recompensas de colaboradores",
"conLearnHow": "Saiba como contribuir ao Habitica",
- "surveysSingle": "Ajudou Habitica a crescer participando de uma pesquisa. Não existem pesquisas ativas.",
- "surveysMultiple": "Ajudou Habitica a crescer participando de <%= surveys %> pesquisas. Não existem pesquisas ativas.",
+ "surveysSingle": "Helped Habitica grow, either by filling out a survey or helping with a major testing effort. Thank you!",
+ "surveysMultiple": "Helped Habitica grow on <%= surveys %> occasions, either by filling out a survey or helping with a major testing effort. Thank you!",
"currentSurvey": "Pesquisa Atual",
"surveyWhen": "A insígnia será ofertada a todos os participantes quando as pesquisas tiverem sido processadas, no final de março.",
"blurbInbox": "É aqui que suas mensagens particulares ficam guardadas! Você pode enviar uma mensagem à alguém clicando no ícone do envelope próximo a seu nome na Taverna, Equipe, ou nas Conversas da Guilda. Se você recebeu uma MP inapropriada, você deve enviar uma imagem da mensagem para Lemoness (leslie@habitica.com)",
diff --git a/common/locales/pt/death.json b/common/locales/pt/death.json
index e9ebb8f243..23331708eb 100644
--- a/common/locales/pt/death.json
+++ b/common/locales/pt/death.json
@@ -12,5 +12,6 @@
"losingHealthQuickly": "Perdendo vida rapidamente?",
"lowHealthTips3": "Tarefas diárias incompletas prejudicam você de um dia para o outro, tenha o cuidado de não adicionar muitas de uma só vez!",
"lowHealthTips4": "Se a tarefa diária não for cumprida no dia, você pode desabilitá-la clicando no ícone do lápis.",
- "goodLuck": "Boa Sorte!"
+ "goodLuck": "Boa Sorte!",
+ "cannotRevive": "Não pode reviver se não está morto"
}
\ No newline at end of file
diff --git a/common/locales/pt/front.json b/common/locales/pt/front.json
index 138fbe5b90..1046bc9ff6 100644
--- a/common/locales/pt/front.json
+++ b/common/locales/pt/front.json
@@ -28,6 +28,7 @@
"communityReddit": "Reddit",
"companyAbout": "Como Funciona",
"companyBlog": "Blog",
+ "devBlog": "Blog do desenvolvedor",
"companyDonate": "Doar",
"companyExtensions": "Extensões",
"companyPrivacy": "Privacidade",
@@ -51,6 +52,7 @@
"featureSocialHeading": "Jogo social",
"featuredIn": "Visto em",
"featuresHeading": "Nós também temos...",
+ "footerDevs": "Desenvolvedores",
"footerCommunity": "Comunidade",
"footerCompany": "Companhia",
"footerMobile": "Celular",
@@ -165,7 +167,7 @@
"teams": "Equipes",
"terms": "Termos e Condições",
"testimonialHeading": "O que as pessoas estão dizendo...",
- "localStorageTryFirst": "Se você estiver tendo problemas com o Habitica, clique no botão abaixo para limpar os dados armazenados para este site (outros sites não serão afetados). Você precisará logar novamente depois de fazer isso, então primeiro certifique-se de saber todos os seus dados de login, que podem ser encontrados em Configurações -> <%= linkStart %>Site<%= linkEnd %>.",
+ "localStorageTryFirst": "Se você estiver tendo problemas com o Habitica, clique no botão abaixo para limpar os dados locais armazenados para este site (outros sites não serão afetados). Você precisará logar novamente depois de fazer isso, então primeiro certifique-se que sabe todos os seus dados de login, que podem ser encontrados em Configurações -> <%= linkStart %>Site<%= linkEnd %>.",
"localStorageTryNext": "Se os problemas persistirem, por favor <%= linkStart %>Relate um Bug<%= linkEnd %> se ainda não o fez.",
"localStorageClearing": "Limpando dados locais",
"localStorageClearingExplanation": "Os dados do seu navegador estão sendo excluídos. Você será desconectado e redirecionado à página principal. Por favor, aguarde.",
@@ -182,6 +184,7 @@
"zelahQuote": "Com o [Habitica], eu sou persuadido a ir para a cama na hora pelos pontos que ganho por dormir cedo ou pela vida que perco dormindo tarde!",
"reportAccountProblems": "Reportar Problemas na Conta",
"reportCommunityIssues": "Reportar Problemas com a Comunidade",
+ "subscriptionPaymentIssues": "Assinatura e questões sobre pagamento.",
"generalQuestionsSite": "Perguntas Gerais sobre o Site",
"businessInquiries": "Consultas de Negócios",
"merchandiseInquiries": "Consultas de Merchandise",
@@ -222,5 +225,32 @@
"altAttrWebstorm": "WebStorm",
"altAttrGithub": "GitHub",
"altAttrTrello": "Trello",
- "altAttrSlack": "Slack"
+ "altAttrSlack": "Slack",
+ "missingAuthHeaders": "Faltando cabeçalhos de autenticação.",
+ "missingAuthParams": "Faltando parâmetros de autenticação.",
+ "missingUsernameEmail": "Faltando nome de usuário ou email.",
+ "missingEmail": "Faltando email.",
+ "missingUsername": "Faltando nome de usuário.",
+ "missingPassword": "Faltando senha.",
+ "missingNewPassword": "Faltando nova senha.",
+ "wrongPassword": "Senha incorreta.",
+ "notAnEmail": "Endereço de e-mail inválido.",
+ "emailTaken": "Endereço de email já está sendo usado em uma conta.",
+ "newEmailRequired": "Faltando novo endereço de email.",
+ "usernameTaken": "Nome de usuário já está sendo utilizado.",
+ "passwordConfirmationMatch": "A confirmação de senha não corresponde à senha.",
+ "invalidLoginCredentials": "Nome de usuário e/ou email e/ou senha incorretos.",
+ "passwordReset": "Se nós tivermos seu email em arquivo, o link para restauração da sua senha será enviado ao seu email.",
+ "passwordResetEmailSubject": "Reiniciar senha para o Habitica",
+ "passwordResetEmailText": "A senha para <%= username %> foi reiniciada para <%= newPassword %>. Importante! Tanto o nome de usuário quanto a senha diferenciam maiúsculas de minúsculas - você precisa escrever exatamente como mostrado aqui. Nós recomendamos que copie e cole ambas em vez de digitá-las. Entre em <%= baseUrl %>. Depois de entrar, vá para <%= baseUrl %>/#/options/settings/settings e mude sua senha.",
+ "passwordResetEmailHtml": "Password for <%= username %> has been reset to <%= newPassword %>.
Important! Both username and password are case-sensitive -- you must enter both exactly as shown here. We recommend copying and pasting both instead of typing them.
Log in at <%= baseUrl %>. After you have logged in, head to <%= baseUrl %>/#/options/settings/settings and change your password.",
+ "invalidLoginCredentialsLong": "Oh não - seu nome de usuário ou senha está incorreto.\n- Tenha certeza de que seu nome de usuário e senha estão digitados corretamente. \n- Você pode ter se cadastrado com o Facebook, não com o email. Cheque tentando login com o Facebook.\n- Se você esqueceu sua senha, clique \"Esqueci Senha\".",
+ "invalidCredentials": "Não há conta que use essas credenciais.",
+ "accountSuspended": "Account has been suspended, please contact leslie@habitica.com with your User ID \"<%= userId %>\" for assistance.",
+ "onlyFbSupported": "Somente Facebook é suportado atualmente.",
+ "cantDetachFb": "A conta não possui outro método de autenticação, não pode desvincular Facebook.",
+ "onlySocialAttachLocal": "Autenticação local só pode ser adicionada a uma conta social.",
+ "invalidReqParams": "Parâmetros requeridos inválidos.",
+ "memberIdRequired": "\"member\" must be a valid UUID.",
+ "heroIdRequired": "\"heroId\" must be a valid UUID."
}
\ No newline at end of file
diff --git a/common/locales/pt/gear.json b/common/locales/pt/gear.json
index 80bbef0a5b..e194465937 100644
--- a/common/locales/pt/gear.json
+++ b/common/locales/pt/gear.json
@@ -149,7 +149,7 @@
"weaponSpecialSpring2016WarriorNotes": "Ninguem tem tantos amigos quanto o rato com queijos tenros. Aumenta a força em <%= str %>. Edição Limitada 2016 Equipamento de Primavera.",
"weaponSpecialSpring2016MageText": "Cajado de Sinos",
"weaponSpecialSpring2016MageNotes": "Abra-Gata-Bra! Tão deslumbrante, você devia se hipnotizar! Ooh. Ele brilha... Aumenta Inteligência em <%= int %>. e percepção em <%= per %>. Edição Limitada 2016 Equipamento de Primavera ",
- "weaponSpecialSpring2016HealerText": "Varinha de Flor de Primavera",
+ "weaponSpecialSpring2016HealerText": "Varinha de Flor da Primavera",
"weaponSpecialSpring2016HealerNotes": "Com o aceno e uma piscada, você faz os campos e florestas florescerem! ou acerta ratos problemáticos na cabeça. Aumenta a inteligência em <%=int%> . Equipamento de Edição Limitada da Primavera 2016.",
"weaponMystery201411Text": "Forcado de Banquete",
"weaponMystery201411Notes": "Apunhale seus inimigos ou cave pelas suas comidas favoritas - esse garfo versátil faz de tudo! Não confere benefícios. Item de Assinante de Novembro 2014.",
@@ -187,8 +187,10 @@
"weaponArmoireJesterBatonNotes": "Com um balanço do seu bastão e algumas réplicas espirituosas, até mesmo a situação mais complicada fica simples. Aumenta Inteligência e Percepção em <%= attrs %> cada. Armário Encantado: Conjunto de Bobo da Corte (Item 3 de 3).",
"weaponArmoireMiningPickaxText": "Picareta de mineração",
"weaponArmoireMiningPickaxNotes": "Extraia a quantidade máxima de ouro de suas tarefas! Aumenta a Percepção em <%= per %>. Armadura Encantada: Conjunto \"Minerador\" (Item 3 de 3).",
- "weaponArmoireBasicLongbowText": "Basic Longbow",
- "weaponArmoireBasicLongbowNotes": "A serviceable hand-me-down bow. Increases Strength by <%= str %>. Enchanted Armoire: Basic Archer Set (Item 1 of 3).",
+ "weaponArmoireBasicLongbowText": "Arco Longo basico",
+ "weaponArmoireBasicLongbowNotes": "Um arco em segunda mão ainda em condição de uso. Aumenta Força em <%= str %> pontos. Armário Encantado: Conjunto básico de Arqueiro (item 1 de 3).",
+ "weaponArmoireHabiticanDiplomaText": "Diploma Habiticano",
+ "weaponArmoireHabiticanDiplomaNotes": "Um certificado de conquista significativa - parabéns! Aumenta a Inteligência em <%= int %>. Armário Encantado: Conjunto de Graduação (Item 1 de 3).",
"armor": "armadura",
"armorBase0Text": "Roupas Modestas",
"armorBase0Notes": "Vestimenta ordinária Não concede benefícios.",
@@ -364,6 +366,10 @@
"armorMystery201512Notes": "Invoca as chamas frias do inverno. Não confere benefícios. Item de Assinante de dezembro de 2015.",
"armorMystery201603Text": "Terno da sorte",
"armorMystery201603Notes": "Esse terno foi costurado com milhares de trevos de 4 folhas. Não concede benefícios. Item do Assinante de Março 2016",
+ "armorMystery201604Text": "Armor o' Leaves",
+ "armorMystery201604Notes": "You, too, can be a small but fearsome leaf puff. Confers no benefit. April 2016 Subscriber Item.",
+ "armorMystery201605Text": "Marching Bard Uniform",
+ "armorMystery201605Notes": "Unlike the traditional bards who join adventuring parties, bards who join Habitican marching bands are known for grand parades, not dungeon raids. Confers no benefit. May 2016 Subscriber Item.",
"armorMystery301404Text": "Fantasia Steampunk",
"armorMystery301404Notes": "Elegante e distinto. Não concede benefícios. Item de Assinante de Fevereiro 3015.",
"armorArmoireLunarArmorText": "Armadura Lunar Tranquilizadora",
@@ -392,8 +398,10 @@
"armorArmoireJesterCostumeNotes": "Tra-la-la! Apesar da aparência da sua fantasia, você não é bobo. Aumenta Inteligência em <%= int %>. Armário Encantado: Conjunto de Bobo da Corte (Item 2 de 3).",
"armorArmoireMinerOverallsText": "Macacão de Minerador",
"armorArmoireMinerOverallsNotes": "Eles podem parecer estragados, mas estão encantados para repelir sujeira. Aumenta Constituição em <%= con %>. Armário Encantado: Set de Minerador (Item 2 de 3).",
- "armorArmoireBasicArcherArmorText": "Basic Archer Armor",
- "armorArmoireBasicArcherArmorNotes": "This camouflaged vest lets you slip unnoticed through the forests. Increases Perception by <%= per %>. Enchanted Armoire: Basic Archer Set (Item 2 of 3).",
+ "armorArmoireBasicArcherArmorText": "Armadura de arqueiro básica",
+ "armorArmoireBasicArcherArmorNotes": "Este colete com camuflagem permite-te passar despercebido em florestas. Aumenta Percepção em <%= per %> pontos. Armário Encantado: Conjunto básico de Arqueiro (item 2 de 3).",
+ "armorArmoireGraduateRobeText": "Beca de Graduado",
+ "armorArmoireGraduateRobeNotes": "Parabéns! Essa beca pesada se pendura solidamente junto com todo o conhecimento que você acumulou. Aumenta Inteligência em <%= int %>. Armário Encantado: Conjunto de Graduação (Item 2 de 3).",
"headgear": "capacete",
"headBase0Text": "Sem Elmo",
"headBase0Notes": "Sem capacete.",
@@ -565,6 +573,10 @@
"headMystery201602Notes": "Proteja sua identidade de todos os seus admiradores. Não confere benefícios. Item de assinante de Fevereiro 2016",
"headMystery201603Text": "Chapéu da sorte",
"headMystery201603Notes": "Essa cartola é um amuleto da sorte mágico. Não concede benefícios. Item do Assinante de Março 2016 ",
+ "headMystery201604Text": "Coroa de Flores",
+ "headMystery201604Notes": "Essas flores entrelaçadas fazem um capacete surpreendentemente forte! Não confere benefício. Item de Assinante de Abril de 2016.",
+ "headMystery201605Text": "Marching Bard Hat",
+ "headMystery201605Notes": "Seventy-six dragons led the big parade, with a hundred and ten gryphons close at hand! Confers no benefit. May 2016 Subscriber Item.",
"headMystery301404Text": "Cartola Chique",
"headMystery301404Notes": "Uma cartola chique para as damas e cavalheiros mais finos! Item de Assinante de Janeiro 3015. Não concede benefícios.",
"headMystery301405Text": "Cartola Básica",
@@ -611,8 +623,10 @@
"headArmoireJesterCapNotes": "Os sinos nesse chapéu podem distrair seus oponentes, mas eles só ajudam no seu foco. Aumenta Percepção em <%= per %>. Armário Encantado: Conjunto de Bobo da Corte (Item 1 de 3).",
"headArmoireMinerHelmetText": "Elmo de Minerador",
"headArmoireMinerHelmetNotes": "Proteja sua cabeça de Tarefas caindo! Aumenta Inteligência em <%= int %>. Armário Encantado. Set de Minerador (Item 1 de 3).",
- "headArmoireBasicArcherCapText": "Basic Archer Cap",
- "headArmoireBasicArcherCapNotes": "No archer would be complete without a jaunty cap! Increases Perception by <%= per %>. Enchanted Armoire: Basic Archer Set (Item 3 of 3).",
+ "headArmoireBasicArcherCapText": "Chapéu de arqueiro básico",
+ "headArmoireBasicArcherCapNotes": "Nenhum arqueiro estaria completo sem um chapéu janota! Aumenta Percepção em <%= per %> pontos. Armário Encantado: Conjunto básico de Arqueiro (item 3 de 3).",
+ "headArmoireGraduateCapText": "Chapéu de Graduação",
+ "headArmoireGraduateCapNotes": "Parabéns! Seus pensamentos profundos te deram esse chapéu da compreensão. Aumenta Inteligência em <%= int %>. Armário Encantado: Conjunto de Graduação (Item 3 de 3).",
"offhand": "item da segunda mão",
"shieldBase0Text": "Sem Equipamento na Segunda Mão",
"shieldBase0Notes": "Sem escudo ou segundo armamento.",
@@ -673,7 +687,7 @@
"shieldSpecialWinter2015HealerText": "Escudo Alentador",
"shieldSpecialWinter2015HealerNotes": "Este escudo deflete o vento congelante. Aumenta Constituição em <%= con %>. Equipamento Edição Limitada de Inverno 2014-2015.",
"shieldSpecialSpring2015RogueText": "Guinchado Explosivo",
- "shieldSpecialSpring2015RogueNotes": "Não deixe o som te enganar - esses explosivos são poderosos. Aumenta Força em <%= str %>. Equipamento Edição Limitada de Primavera 2015.",
+ "shieldSpecialSpring2015RogueNotes": "Don't let the sound fool you - these explosives pack a punch. Increases Strength by <%= str %>. Limited Edition 2015 Spring Gear.",
"shieldSpecialSpring2015WarriorText": "Prato Pires",
"shieldSpecialSpring2015WarriorNotes": "Jogue nos seus inimigos... ou só segure, porque vai se encher de ração gostosa na hora da janta. Aumenta Constituição em <%= con %>. Equipamento Edição Limitada de Primavera 2015.",
"shieldSpecialSpring2015HealerText": "Travesseiro Estampado",
@@ -716,6 +730,8 @@
"shieldArmoireDragonTamerShieldNotes": "Distraia os inimigos com esse escudo em forma de dragão; Aumenta sua Percepção em <%= per %>. Armário Encantado: Conjunto do Domador de Dragões (Item 2 de 3).",
"shieldArmoireMysticLampText": "Lâmpada Mistíca",
"shieldArmoireMysticLampNotes": "Ilumine as cavernas mais sombrias com essa lâmpada mística! Aumenta percepção por <%= per %>. Armário Encantado: Item Independente",
+ "shieldArmoireFloralBouquetText": "Buquê de Flores",
+ "shieldArmoireFloralBouquetNotes": "Não ajudam muito em uma batalha, mas não são lindas? Aumenta Constituição em <%= con %>. Armário Encantado: Item Independente.",
"back": "Acessório de Costas",
"backBase0Text": "Sem Acessório de Fundo",
"backBase0Notes": "Sem Acessório de Fundo.",
@@ -815,11 +831,25 @@
"headAccessoryMystery201510Notes": "Esses chifres amedrontadores são pegajosos. Não concede benefícios. Item de Assinante de Outubro 2015.",
"headAccessoryMystery301405Text": "Óculos de Proteção para Cabeça",
"headAccessoryMystery301405Notes": "\"Óculos de proteção são para os olhos,\" eles disseram. \"Ninguém quer óculos que você só pode usar na cabeça,\" eles disseram. Ha! Você mostrou pra eles. Não concede benefícios. Item de Assinante de Agosto 3015.",
- "headAccessoryArmoireComicalArrowText": "Comical Arrow",
- "headAccessoryArmoireComicalArrowNotes": "This whimsical item doesn't provide a stat boost, but it sure is good for a laugh! Confers no benefit. Enchanted Armoire: Independent Item.",
+ "headAccessoryArmoireComicalArrowText": "Flecha cómica.",
+ "headAccessoryArmoireComicalArrowNotes": "Este objecto cómico não aumenta qualquer stat, mas é válido para uma boa gargalhada! Não confere qualquer benefício. Armário Encantado: Item independente.",
"eyewear": "Óculos",
"eyewearBase0Text": "Sem Acessório Para os Olhos",
"eyewearBase0Notes": "Sem Acessório Para os Olhos.",
+ "eyewearSpecialBlackTopFrameText": "Óculos Pretos Padrão",
+ "eyewearSpecialBlackTopFrameNotes": "Óculos com uma armação preta sobre as lentes. Não confere benefício.",
+ "eyewearSpecialBlueTopFrameText": "Blue Standard Eyeglasses",
+ "eyewearSpecialBlueTopFrameNotes": "Glasses with a blue frame above the lenses. Confers no benefit.",
+ "eyewearSpecialGreenTopFrameText": "Green Standard Eyeglasses",
+ "eyewearSpecialGreenTopFrameNotes": "Glasses with a green frame above the lenses. Confers no benefit.",
+ "eyewearSpecialPinkTopFrameText": "Pink Standard Eyeglasses",
+ "eyewearSpecialPinkTopFrameNotes": "Glasses with a pink frame above the lenses. Confers no benefit.",
+ "eyewearSpecialRedTopFrameText": "Red Standard Eyeglasses",
+ "eyewearSpecialRedTopFrameNotes": "Glasses with a red frame above the lenses. Confers no benefit.",
+ "eyewearSpecialWhiteTopFrameText": "White Standard Eyeglasses",
+ "eyewearSpecialWhiteTopFrameNotes": "Glasses with a white frame above the lenses. Confers no benefit.",
+ "eyewearSpecialYellowTopFrameText": "Yellow Standard Eyeglasses",
+ "eyewearSpecialYellowTopFrameNotes": "Glasses with a yellow frame above the lenses. Confers no benefit.",
"eyewearSpecialSummerRogueText": "Tapa-Olho Bandoleiro",
"eyewearSpecialSummerRogueNotes": "Não precisa de um biltre para ver como isso é estiloso! Não concede benefícios. Equipamento Edição Limitada de Verão 2014.",
"eyewearSpecialSummerWarriorText": "Tapa-Olho Elegalante",
diff --git a/common/locales/pt/generic.json b/common/locales/pt/generic.json
index 53d7476bda..8c2d56e0dd 100644
--- a/common/locales/pt/generic.json
+++ b/common/locales/pt/generic.json
@@ -137,8 +137,8 @@
"achievementStressbeastText": "Ajudou a derrotar a Abominável Besta do Estresse durante o Evento de Inverno do País das Maravilhas em 2014!",
"achievementBurnout": "Salvador dos Campos Prósperos",
"achievementBurnoutText": "Ajudou a derrotar o Desgaste e recuperar os Espíritos Exaustos durante o evento do Festival de Outono de 2015!",
- "achievementBewilder": "Savior of Mistiflying",
- "achievementBewilderText": "Helped defeat the Be-Wilder during the 2016 Spring Fling Event!",
+ "achievementBewilder": "Salvador de Mistiflying",
+ "achievementBewilderText": "Ajudou a derrotar o Be-Wilder durante o event do festival do Caso de Primavera de 2016!",
"checkOutProgress": "Veja o meu progresso em Habitica! ",
"cardReceived": "Recebeu um cartão!",
"cardReceivedFrom": "<%= cardType %> de <%= userName %>",
diff --git a/common/locales/pt/groups.json b/common/locales/pt/groups.json
index 833b45628b..bb069daf32 100644
--- a/common/locales/pt/groups.json
+++ b/common/locales/pt/groups.json
@@ -92,6 +92,7 @@
"send": "Enviar",
"messageSentAlert": "Mensagem enviada",
"pmHeading": "Mensagem privada para <%= name %>",
+ "pmsMarkedRead": "Suas mensagens privadas foram marcadas como lidas.",
"clearAll": "Deletar Todas as Mensagens",
"confirmDeleteAllMessages": "Tem certeza que desja deletar todas as mensagens na sua caixa de entrada? Outros usuários ainda irão ver as mensagens que você enviou para eles.",
"optOutPopover": "Não gosta de mensagens privadas? Clique para desativar completamente",
@@ -99,6 +100,15 @@
"unblock": "Desbloquear",
"pm-reply": "Enviar uma resposta",
"inbox": "Caixa de Entrada",
+ "messageRequired": "Uma mensagem é necessária.",
+ "toUserIDRequired": "Um ID de usuário é necessário",
+ "gemAmountRequired": "Um número de gemas é necessário",
+ "notAuthorizedToSendMessageToThisUser": "Não pode enviar mensagem para esse usuário.",
+ "privateMessageGiftIntro": "Olá <%= receiverName %>, <%= senderName %> te enviou",
+ "privateMessageGiftGemsMessage": "<%= gemAmount %> gemas!",
+ "privateMessageGiftSubscriptionMessage": "<%= numberOfMonths %> meses de assinatura!",
+ "cannotSendGemsToYourself": "Não é possível enviar gemas para si mesmo. Tente uma assinatura.",
+ "badAmountOfGemsToSend": "Quantidade precisa ser entre 1 e seu número atual de gemas.",
"abuseFlag": "Reportar violação das Diretrizes de Comunidade",
"abuseFlagModalHeading": "Reportar <%= name %> por violação?",
"abuseFlagModalBody": "Tem certeza que deseja denunciar essa publicação? Você deve denunciar APENAS uma publicação que viola as <%= firstLinkStart %>Diretrizes de Comunidade<%= linkEnd %> e/ou <%= secondLinkStart %>Termos de Serviço<%= linkEnd %>. Denunciar inapropriadamente uma publicação é uma violação das Diretrizes da Comunidade e pode resultar em uma infração. Razões apropriadas para reportar uma publicação incluem, mas não se limitam a:
xingamentos, blasfêmias religiosas
intolerância, insultos
tópicos adultos
violência, inclusive como brincadeira
spam, mensagens sem sentido
",
@@ -151,5 +161,29 @@
"partyUpName": "Junte-se",
"partyOnName": "Equipe online",
"partyUpAchievement": "Entrou em um grupo com outra pessoa! Divirtam-se batalhando contra monstros e ajudando um ao outro.",
- "partyOnAchievement": "Entrou em um grupo com pelo menos 4 pessoas! Divirta-se aumentando a sua produtividade com seus amigos para derrotar seus inimigos!"
+ "partyOnAchievement": "Entrou em um grupo com pelo menos 4 pessoas! Divirta-se aumentando a sua produtividade com seus amigos para derrotar seus inimigos!",
+ "groupIdRequired": "\"groupId\" precisa ser um UUID válido.",
+ "groupNotFound": "Grupo não encontrado.",
+ "groupTypesRequired": "You must supply a valid \"type\" query string.",
+ "questLeaderCannotLeaveGroup": "Você não pode sair de sua equipe quando você iniciou uma missão. Aborte a missão primeiro.",
+ "cannotLeaveWhileActiveQuest": "Você não pode sair de sua equipe durante uma missão ativa. Por favor, saia da missão primeiro.",
+ "onlyLeaderCanRemoveMember": "Somente o líder do grupo pode remover um membro!",
+ "memberCannotRemoveYourself": "Você não pode se remover!",
+ "groupMemberNotFound": "Usuário não encontrado entre os membros do grupo",
+ "mustBeGroupMember": "Deve ser membro do grupo.",
+ "keepOrRemoveAll": "req.query.keep precisa ser ou \"keep-all\" ou \"remove-all\"",
+ "keepOrRemove": "req.query.keep precisa ser ou \"keep\" ou \"remove\"",
+ "canOnlyInviteEmailUuid": "Só pode convidar usando uuids ou emails.",
+ "inviteMissingEmail": "Faltando o endereço de email no convite.",
+ "partyMustbePrivate": "Equipes precisam ser privadas.",
+ "userAlreadyInGroup": "Usuário já nesse grupo.",
+ "userAlreadyInvitedToGroup": "Usuário já convidado para esse grupo.",
+ "userAlreadyPendingInvitation": "Usuário ainda com convite pendente.",
+ "userAlreadyInAParty": "Usuário já está em uma equipe.",
+ "userWithIDNotFound": "Usuário com id \"<%= userId %>\" não encontrado.",
+ "userHasNoLocalRegistration": "Usuário não tem um registro local (usuário, email, senha).",
+ "uuidsMustBeAnArray": "User ID invites must be an array.",
+ "emailsMustBeAnArray": "Email address invites must be an array.",
+ "canOnlyInviteMaxInvites": "Você só pode convidar \"<%= maxInvites %>\" de cada vez",
+ "onlyCreatorOrAdminCanDeleteChat": "Não autorizado a deletar essa mensagem!"
}
\ No newline at end of file
diff --git a/common/locales/pt/limited.json b/common/locales/pt/limited.json
index 186e5ce184..071908cd05 100644
--- a/common/locales/pt/limited.json
+++ b/common/locales/pt/limited.json
@@ -5,7 +5,7 @@
"annoyingFriends": "Amigos Irritantes",
"annoyingFriendsText": "Membros da equipe jogaram bolas de neve em você <%= snowballs %> vezes.",
"alarmingFriends": "Amigos Alarmantes",
- "alarmingFriendsText": "Membros da equipe te assustaram <%= spookDust %> vezes.",
+ "alarmingFriendsText": "Got spooked <%= spookySparkles %> times by party members.",
"agriculturalFriends": "Amigos Agrícolas",
"agriculturalFriendsText": "Membros da equipe te transformaram em flor <%= seeds %> vezes.",
"aquaticFriends": "Amigos aquáticos",
@@ -68,9 +68,10 @@
"mummyMedicSet": "Médico Múmia (Curandeiro)",
"vampireSmiterSet": "Vampiro Violento (Ladino)",
"bewareDogSet": "Cuidado com o Cão (Guerreiro)",
- "magicianBunnySet": "Coelho dos Magos (Mago)",
+ "magicianBunnySet": "Coelho do Mago (Mago)",
"comfortingKittySet": "Gatinho Confortante (Curandeiro)",
"sneakySqueakerSet": "Ladrão Sorrateiro (Ladino)",
"fallEventAvailability": "Disponível até 31 de Outubro.",
- "winterEventAvailability": "Disponível até 31 de Dezembro"
+ "winterEventAvailability": "Disponível até 31 de Dezembro",
+ "springEventAvailability": "Disponível até 31 de Maio"
}
\ No newline at end of file
diff --git a/common/locales/pt/maintenance.json b/common/locales/pt/maintenance.json
new file mode 100644
index 0000000000..706d4cb5b4
--- /dev/null
+++ b/common/locales/pt/maintenance.json
@@ -0,0 +1,34 @@
+{
+ "habiticaBackSoon": "Não se preocupe. Habitica estará de volta em breve!",
+ "importantMaintenance": "Estamos a realizar manutenção importante que estimamos due até <%= localDate %> na sua zona local.",
+ "maintenance": "Manutenção",
+ "maintenanceMoreInfo": "Deseja mais informação acerca das operações de manutenção? <%= linkStart %>Veja a nossa página de informação<%= linkEnd %>.",
+ "noDamageKeepStreaks": "NÃO receberá dano ou perderá a sua sequência!",
+ "thanksForPatience": "Obrigado pela sua paciência!",
+ "twitterMaintenanceUpdates": "Para ler acerca das nossas actualizações recentes, siga-nos no Twitter onde colocamos informação acerca do estado corrente.",
+ "veteranPetAward": "No final, receberá uma mascote veterana!",
+
+ "maintenanceInfoTitle": "Informação acerca de Operações de Manutenção planeadas em Habitica",
+ "maintenanceInfoWhat": "Que está a acontecer?",
+ "maintenanceInfoWhatText": "A 21 de Maio, Habitica não estará disponível devido a operações de manutenção durante a maioria do dia. Não receberá dano ou terá a sua conta prejudicada durante o fim-de-semana, mesmo que não consiga aceder à sua lista de tarefas diárias a tempo! Estaremos a trabalhar arduamente para fazer com que o tempo de indisponibilidade seja o mais reduzido possível e colocaremos actualizações acerca das mesmas na nossa conta de Twitter. No final do tempo de indisponibilidade, para agradecer a todos pela vossa paciência, receberá uma mascote rara!",
+ "maintenanceInfoWhy": "Porque está isto a acontecer?",
+ "maintenanceInfoWhyText": "Nos passado meses, estivemos ocupados a re-estruturar Habitica por trás da cortina. Especificamente, estivemos a reescrever a API. Embora não pareça muito diferente à superfície, existe um mundo completamente novo por baixo da mesma. Isto permitirá que sejamos muito mais flexíveis quando queremos adicionar novas funcionalidades no futuro e leva a uma performance melhor!",
+ "maintenanceInfoTechDetails": "Quer mais detalhes acerca do lado técnico do processo? Visite The Forge, o nosso blog de desenvolvimento.",
+ "maintenanceInfoMore": "Mais informação",
+ "maintenanceInfoAccountChanges": "Que mudanças verei na minha conta depois de terminar a reescrita?",
+ "maintenanceInfoAccountChangesText": "Inicialmente não se notarão mudanças por demais aparte de melhorias de performance em funcionalidades como os Desafios. Se notar alguma mudança que não deveria estar presente, envie-nos um email para admin@habitica.com e realizaremos uma investigação!",
+ "maintenanceInfoAddFeatures": "Que tipo de funcionalidades serão possíveis de adicionar a Habitica com isto?",
+ "maintenanceInfoAddFeaturesText": "Completar esta re-escrita permitirá que possamos a construir funcionalidades de chat e Guildas melhor, planear para organizações e famílias, bem como criar novas funcionalidades de produtividade como Tarefas Mensais e a possibilidade de gravar a performance do dia anterior! todas estas funcionalidades são complexas por si só, pelo que demorará ainda algum tempo até que as completemos mas, até terminarmos esta reescrita, não nos era sequer possível começar tal trabalho.",
+ "maintenanceInfoHowLong": "Quanto tempo levaram as operações de manutenção?",
+ "maintenanceInfoHowLongText": "Teremos de migrar tarefas e dados para todos os 1.3 milhões de utilizadores de Habitica - não é uma tarefa fácil! Antecipamos que este bloco de trabalho ocorrerá aproximadamente entre as 1pm Pacific Time (8pm UTC) e as 10pm Pacific Time (5am UTC). Não se preocupe que faremos tudo para que tudo demore o menos tempo possível. Pode seguir as actualizações de estado na nossa conta de Twitter.",
+ "maintenanceInfoStatsAffected": "Como serão afectadas as minhas Tarefas Diárias, Combos, Buffs e Missões?",
+ "maintenanceInfoStatsAffectedText1": "NÃO receberá qualquer dano ou perderá qualquer combo nesse fim-de-semana mas, aparte disso, o seu dia será reposto de forma normal! Tarefas Diárias que complete ficarão por cumprir, buffs serão repostos, etc. Se se encontra numa Missão de Colecta, encontrará items. Se está a participar num Combate de Chefão, continuará a dar dano ao mesmo, mas o Chefão não lhe dará dano a si. (Até monstros precisam de uma pausa!)",
+ "maintenanceInfoStatsAffectedText2": "Depois de pensar bastante, nossa equipe concluiu que essa não seria a maneira mais justa de lidar com o fato de que muitos usuários não conseguirão marcar suas Tarefas Diárias normalmente durante a manutenção. Nós pedimos desculpas pela inconveniência que isso causa!",
+ "maintenanceInfoSeeTasks": "E se eu precisar ver minha lista de tarefas?",
+ "maintenanceInfoSeeTasksText": "Se você souber que precisará ver sua lista de tarefas no sábado para lembrar-se do que precisa fazer, nós recomendamos que, antes do início da manutenção, você dê um \"print screen\" nas suas tarefas para que você possa usar como referência.",
+ "maintenanceInfoRarePet": "Que tipo de mascote raro eu vou receber?",
+ "maintenanceInfoRarePetText": "Para te agradecer pela sua paciência durante o período fora do ar, todo mundo ganhará um Mascote Veterano raro. Se você nunca recebeu um Mascote Veterano antes, você receberá um Lobo Veterano. Se você já tem um Lobo Veterano, você receberá um Tigre Veterano. E se você já tem um Lobo Veterano e um Tigre Veterano, você receberá um mascote Veterano nunca antes visto! Depois que a migração estiver completa, pode levar várias horas para o seu mascote aparecer, mas não tema, todos ganharão um.",
+ "maintenanceInfoWho": "Quem trabalhou nesse enorme projeto?",
+ "maintenanceInfoWhoText": "Estamos felizes que você perguntou! Foi liderado pelo nosso incrível colaborador paglias, com muita ajuda de Blade, TheHollidayInn, SabreCat, Victor Pudeyev, TheUnknown, e Alys.",
+ "maintenanceInfoTesting": "A nova versão também foi incansavelmente testada por um grupo de nossos incríveis voluntários de código aberto. Obrigado -- nós não poderíamos ter feito isso sem vocês."
+}
diff --git a/common/locales/pt/npc.json b/common/locales/pt/npc.json
index 31094c893f..db1f3196e5 100644
--- a/common/locales/pt/npc.json
+++ b/common/locales/pt/npc.json
@@ -21,6 +21,26 @@
"ian": "Ian",
"ianText": "Bem-vindo à Loja de Missões! Aqui você pode usar os Pergaminhos de Missões para lutar contra monstros com seus amigos. Não deixe de verificar nossa refinada lista de Pergaminhos de Missões para comprar à direita.",
"ianBrokenText": "Bem-vindo à Loja de Missões... Aqui você pode usar Pergaminhos de Missões para enfrentar monstros com seus amigos... Não deixe de dar uma olhada na nossa refinada coleção de Pergaminhos de Missões a venda na direita...",
+ "missingKeyParam": "\"req.params.key\" é necessário.",
+ "itemNotFound": "Item \"<%= key %>\" não encontrado.",
+ "cannotBuyItem": "Você não pode comprar esse item.",
+ "missingTypeKeyEquip": "\"key\" e \"type\" são parâmetros necessários.",
+ "missingPetFoodFeed": "\"pet\" e \"food\" são parâmetros necessários.",
+ "invalidPetName": "Nome de mascote fornecido inválido.",
+ "missingEggHatchingPotionHatch": "\"egg\" e \"hatchingPotion\" são parâmetros necessários.",
+ "invalidTypeEquip": "\"type\" precisa ser um de 'equipped', 'pet', mount', 'costume'.",
+ "mustPurchaseToSet": "Must purchase <%= val %> to set it on <%= key %>.",
+ "typeRequired": "Tipo é necessário",
+ "keyRequired": "Chave é necessária",
+ "notAccteptedType": "Type must be in [eggs, hatchingPotions, food, quests, gear]",
+ "contentKeyNotFound": "Chave não encontrada para Conteúdo <%= type %>",
+ "plusOneGem": "+1 Gema",
+ "typeNotSellable": "Tipo não é vendível. Precisa ser um dos seguintes <%= acceptedTypes %>",
+ "userItemsKeyNotFound": "Key not found for user.items <%= type %>",
+ "pathRequired": "Path string is required",
+ "unlocked": "Itens foram destravados",
+ "alreadyUnlocked": "Conjunto completo já destravado.",
+ "alreadyUnlockedPart": "Conjunto completo já parcialmente destravado.",
"USD": "(US$) Dólar",
"newStuff": "Novidades",
"cool": "Me diga depois",
@@ -35,7 +55,7 @@
"amazonInstructions": "Aperte no botão para pagar usando Amazon Payments",
"paymentMethods": "Comprar usando",
"classGear": "Equipamento da Classe",
- "classGearText": "Primeiro: Não entre em pânico! Seu equipamento antigo está no seu inventário, e agora você está usando um equipamento de aprendiz da sua nova classe, Usar o equipamento de sua classe lhe concede um bonus de 50% de atributos. De qualquer modo, sinta-se livre para trocar de volta para o seu equipamento antigo.",
+ "classGearText": "Primeiro: Não entre em pânico! Seu equipamento antigo está no seu inventário, e agora você está usando um equipamento de aprendiz da sua nova classe. Usar o equipamento de sua classe lhe concede um bonus de 50% em atributos. De qualquer modo, sinta-se livre para mudar para o seu equipamento antigo.",
"classStats": "Esses são seus atributos da classe; eles afetam a jogabilidade. Cada vez que subir de nível, ganhará um ponto para distribuir em um atributo particular. Passe o mouse em cima de cada atributo para mais informações.",
"autoAllocate": "Distribuição Automática",
"autoAllocateText": "Se \"distribuição automática\" estiver marcado, seu avatar aumentará suas estatísticas automaticamente, baseado nos atributos das suas tarefas, que você pode encontrar em Tarefas > Editar > Avançado > Atributos. Ou seja, se você vai à academia frequentemente e sua tarefa diária \"Academia\" está marcada como \"Físico\", você ganhará Força automaticamente.",
@@ -45,7 +65,7 @@
"moreClass": "Para mais informações sobre o sistema de classes, veja",
"tourWelcome": "Bem vindo a Habitica! Essa é a sua lista de Afazeres. Complete uma tarefa para prosseguir!",
"tourExp": "Bom trabalho! Completar uma tarefa lhe dá Experiência e Ouro!",
- "tourDailies": "Essa coluna é para Tarefas Diárias. Para continuar, escreva uma tarefa que você deve fazer todos os dias! Exemplos de Tarefas Diárias:Arrumar a Cama, Usar Fio Dental, Checar E-mails de Trabalho",
+ "tourDailies": "Essa coluna é para Tarefas Diárias. Para continuar, crie uma tarefa que você deve fazer todos os dias! Exemplos de Tarefas Diárias:Arrumar a Cama, Usar Fio Dental, Checar E-mails de Trabalho",
"tourCron": "Esplêndido! Suas tarefas diárias vão resetar todo dia.",
"tourHP": "Cuidado! Se você não completar uma tarefa diária até a meia noite, ela vai te machucar!",
"tourHabits": "Essa coluna é para bons e maus Hábitos que você faz várias vezes por dia! Para proceder, clique no lápis para editar os nomes, depois clique no positivo (checkmark) para salvar.",
@@ -64,6 +84,7 @@
"tourPetsPage": "Este é o Estábulo! Depois do nível 4, você pode chocar mascotes usando ovos e poções. Quando você chocar um mascote no Mercado, ele aparecerá aqui. Clique na imagem de um mascote para adicioná-lo ao seu avatar. Alimente-os com comida que você encontrar depois do nível 4 e eles se transformarão em poderosas montarias.",
"tourMountsPage": "Depois que você alimentar um mascote o suficiente para transformá-lo em uma montaria, ele aparecerá aqui. (Mascotes, montarias e comida estão disponíveis depois do nível 4.) Clique em uma montaria para subir nela!",
"tourEquipmentPage": "É aqui que seu Equipamento fica guardado! Seu Equipamento de Batalha afeta seus atributos. Se você quiser mostrar um Equipamento diferente no seu avatar sem alterar seus atributos, clique em \"Usar Traje\".",
+ "equipmentAlreadyOwned": "Você já possui esse equipamento",
"tourOkay": "Okay!",
"tourAwesome": "Incrível!",
"tourSplendid": "Esplêndido!",
diff --git a/common/locales/pt/pets.json b/common/locales/pt/pets.json
index dd7cb70b5f..d5c3e814a8 100644
--- a/common/locales/pt/pets.json
+++ b/common/locales/pt/pets.json
@@ -12,6 +12,7 @@
"etherealLion": "Leão Etéreo",
"veteranWolf": "Lobo Veterano",
"veteranTiger": "Tigre Veterano",
+ "veteranLion": "Leão Veterano",
"cerberusPup": "Filhote de Cérbero",
"hydra": "Hidra",
"mantisShrimp": "Camarão Mantis",
@@ -19,7 +20,7 @@
"orca": "Orca",
"royalPurpleGryphon": "Grifo Real Roxo",
"phoenix": "Fênix",
- "bumblebee": "Bumblebee",
+ "magicalBee": "Abelha Mágica",
"rarePetPop1": "Clique na pata de ouro para saber mais em como obter esse mascote raro através de contribuições ao Habitica.",
"rarePetPop2": "Como Conseguir esse Mascote!",
"potion": "Poção <%= potionType %>",
@@ -62,6 +63,7 @@
"hatchedPet": "Você chocou um <%= potion %> <%= egg %>!",
"displayNow": "Mostrar Agora",
"displayLater": "Mostrar Depois",
+ "petNotOwned": "Você não possui esse mascote.",
"earnedCompanion": "Com toda sua produtividade, você conseguiu um novo companheiro. Lhe dê comida para fazê-lo crescer!",
"feedPet": "Alimentar <%= article %><%= text %> para o seu <%= name %>?",
"useSaddle": "Selar <%= pet %>?",
@@ -83,5 +85,8 @@
"petKeyBoth": "Solte Ambos",
"confirmPetKey": "Tem certeza?",
"petKeyNeverMind": "Não ainda",
+ "petsReleased": "Mascotes liberados.",
+ "mountsAndPetsReleased": "Montarias e mascotes liberados.",
+ "mountsReleased": "Montarias liberadas.",
"gemsEach": "gemas cada"
}
\ No newline at end of file
diff --git a/common/locales/pt/quests.json b/common/locales/pt/quests.json
index e5381ed5f1..9500e9a42b 100644
--- a/common/locales/pt/quests.json
+++ b/common/locales/pt/quests.json
@@ -38,7 +38,7 @@
"bossDmg2": "Apenas participantes lutarão contra o chefão e dividirão as recompensas da missão.",
"bossDmg1Broken": "Cada Tarefa Diária e Afazer completados e Hábitos positivos machucam o Chefão... Cause mais dano com tarefas mais vermelhas, com Destruição Brutal ou com Explosão de Chamas... O Chefão irá causar dano a todos os participantes da missão por cada Tarefa Diária perdida (multiplicado pela Força do Chefão) em adição ao seu dano normal, então mantenha sua equipe saudável completando suas Tarefas Diárias... Todo dano recebido ou causado ao Chefão será computado no seu cron (sua virada de dia)...",
"bossDmg2Broken": "Somente participantes lutarão contra o Chefão e dividirão as recompensas da missão...",
- "tavernBossInfo": "Complete Dailies and To-Dos and score positive Habits to damage the World Boss! Incomplete Dailies fill the Rage Bar. When the Rage bar is full, the World Boss will attack an NPC. A World Boss will never damage individual players or accounts in any way. Only active accounts not resting in the Inn will have their tasks tallied.",
+ "tavernBossInfo": "Completa Afazeres e Tarefas Diárias completadas e cada Hábito positivo causa dano no Chefão Global! Tarefas Diárias incompletas enchem a sua barra de Fúria. Quando a barra de Fúria enche, o Chefão Global atacará um NPC. Un Chefão Global nunca dará dano a jogadores ou contas indivíduais de qualquer forma. Apenas contas activas que não estejam a descansar na Taverna verão as suas Tarefas levadas em conta.",
"tavernBossInfoBroken": "Tarefas Diárias e Afazeres completos e Hábitos positivos machucarão o Chefão Global... Tarefas perdidas enchem a Barra do Ataque de Exaustão... Quando essa barra enche, o Chefão Global atacará um NPC... Um Chefão Global nunca machucará jogadores ou contas de qualquer forma... Apenas contas ativas não descansando na Pousada terão suas Tarefas Diárias computadas...",
"bossColl1": "Para coletar itens, complete suas tarefas positivas. Itens de missão aparecem assim como itens normais; entretanto, você não verá a coleta até o dia seguinte, então tudo que você encontrou será registrado e contribuído à pilha.",
"bossColl2": "Apenas participantes podem coletar itens e dividir as recompensas da missão.",
@@ -78,5 +78,24 @@
"whichQuestStart": "Que missão deseja iniciar?",
"getMoreQuests": "Conseguir mais missões",
"unlockedAQuest": "Você desbloqueou uma missão!",
- "leveledUpReceivedQuest": "Você subiu para o nível <%= level %> e recebeu um pergaminho de missão!"
+ "leveledUpReceivedQuest": "Você subiu para o nível <%= level %> e recebeu um pergaminho de missão!",
+ "questInvitationDoesNotExist": "Nenhum convite para missão foi enviado ainda.",
+ "questInviteNotFound": "Nenhum convite para missão encontrado.",
+ "guildQuestsNotSupported": "Guildas não podem ser convidadas para missão.",
+ "questNotFound": "Missão \"<%= key %>\" não encontrada.",
+ "questNotOwned": "Você não possui esse pergaminho de missão.",
+ "questNotGoldPurchasable": "Missão \"<%= key %>\" não é comprável com ouro.",
+ "questLevelTooHigh": "Você precisa ter nível <%= level %> para iniciar essa missão.",
+ "questAlreadyUnderway": "Sua equipe já está em uma missão. Tente novamente quando a missão atual tiver encerrado.",
+ "questAlreadyAccepted": "Você já aceitou o convite para a missão.",
+ "noActiveQuestToLeave": "Não há missão ativa para sair.",
+ "questLeaderCannotLeaveQuest": "O líder da missão não pode sair da missão.",
+ "notPartOfQuest": "Você não faz parte da missão.",
+ "noActiveQuestToAbort": "Não há missão ativa para abortar.",
+ "onlyLeaderAbortQuest": "Somente o líder do grupo ou da missão podem abortá-la.",
+ "questAlreadyRejected": "Você já rejeitou o convite para a missão.",
+ "cantCancelActiveQuest": "Você não pode cancelar uma missão ativa, use a funcionalidade de abortar.",
+ "onlyLeaderCancelQuest": "Somente o líder do grupo ou da missão podem cancelá-la.",
+ "questNotPending": "Não há missão para iniciar.",
+ "questOrGroupLeaderOnlyStartQuest": "Somente o líder da missão ou do grupo podem forçar o início da missão."
}
\ No newline at end of file
diff --git a/common/locales/pt/questscontent.json b/common/locales/pt/questscontent.json
index c8e424ddd0..98bf81fe66 100644
--- a/common/locales/pt/questscontent.json
+++ b/common/locales/pt/questscontent.json
@@ -298,15 +298,27 @@
"questSnailBoss": "trabalho fastidioso do caracol Sludge",
"questSnailDropSnailEgg": "Caracol (Ovo)",
"questSnailUnlockText": "Desbloqueia ovos de Caracol para compra no Mercado",
- "questBewilderText": "The Be-Wilder",
- "questBewilderNotes": "The party begins like any other.
The appetizers are excellent, the music is swinging, and even the dancing elephants have become routine. Habiticans laugh and frolic amid the overflowing floral centerpieces, happy to have a distraction from their least-favorite tasks, and the April Fool whirls among them, eagerly providing an amusing trick here and a witty twist there.
As the Mistiflying clock tower strikes midnight, the April Fool leaps onto the stage to give a speech.
“Friends! Enemies! Tolerant acquaintances! Lend me your ears.” The crowd chuckles as animal ears sprout from their heads, and they pose with their new accessories.
“As you know,” the Fool continues, “my confusing illusions usually only last a single day. But I’m pleased to announce that I’ve discovered a shortcut that will guarantee us non-stop fun, without having to deal with the pesky weight of our responsibilities. Charming Habiticans, meet my magical new friend... the Be-Wilder!”
Lemoness pales suddenly, dropping her hors d'oeuvres. “Wait! Don’t trust--”
But suddenly mists are pouring into the room, glittering and thick, and they swirl around the April Fool, coalescing into cloudy feathers and a stretching neck. The crowd is speechless as an monstrous bird unfolds before them, its wings shimmering with illusions. It lets out a horrible screeching laugh.
“Oh, it has been ages since a Habitican has been foolish enough to summon me! How wonderful it feels, to have a tangible form at last.”
Buzzing in terror, the magic bees of Mistiflying flee the floating city, which sags from the sky. One by one, the brilliant spring flowers wither up and wisp away.
“My dearest friends, why so alarmed?” crows the Be-Wilder, beating its wings. “There’s no need to toil for your rewards any more. I’ll just give you all the things that you desire!”
A rain of coins pours from the sky, hammering into the ground with brutal force, and the crowd screams and flees for cover. “Is this a joke?” Baconsaur shouts, as the gold smashes through windows and shatters roof shingles.
PainterProphet ducks as lightning bolts crackle overhead, and fog blots out the sun. “No! This time, I don’t think it is!”
Quickly, Habiticans, don’t let this World Boss distract us from our goals! Stay focused on the tasks that you need to complete so we can rescue Mistiflying -- and hopefully, ourselves.",
- "questBewilderCompletion": "The Be-Wilder is DEFEATED!
We've done it! The Be-Wilder lets out a ululating cry as it twists in the air, shedding feathers like falling rain. Slowly, gradually, it coils into a cloud of sparkling mist. As the newly-revealed sun pierces the fog, it burns away, revealing the coughing, mercifully human forms of Bailey, Matt, Alex.... and the April Fool himself.
Mistiflying is saved!
The April Fool has enough shame to look a bit sheepish. “Oh, hm,” he says. “Perhaps I got a little…. carried away.”
The crowd mutters. Sodden flowers wash up on sidewalks. Somewhere in the distance, a roof collapses with a spectacular splash.
“Er, yes,” the April Fool says. “That is. What I meant to say was, I’m dreadfully sorry.” He heaves a sigh. “I suppose it can’t all be fun and games, after all. It might not hurt to focus occasionally. Maybe I’ll get a head start on next year’s pranking.”
Redphoenix coughs meaningfully.
“I mean, get a head start on this year’s spring cleaning!” the April Fool says. “Nothing to fear, I’ll have Habit City in spit-shape soon. Luckily nobody is better than I at dual-wielding mops.”
Encouraged, the marching band starts up.
It isn’t long before all is back to normal in Habit City. Plus, now that the Be-Wilder has evaporated, the magical bees of Mistiflying bustle back to work, and soon the flowers are blooming and the city is floating once more.
As Habiticans cuddle the magical fuzzy bees, the April Fool’s eyes light up. “Oho, I’ve had a thought! Why don’t you all keep some of these fuzzy Bee Pets and Mounts? It’s a gift that perfectly symbolizes the balance between hard work and sweet rewards, if I’m going to get all boring and allegorical on you.” He winks. “Besides, they don’t have stingers! Fool’s honor.”",
- "questBewilderCompletionChat": "`The Be-Wilder is DEFEATED!`\n\nWe've done it! The Be-Wilder lets out a ululating cry as it twists in the air, shedding feathers like falling rain. Slowly, gradually, it coils into a cloud of sparkling mist. As the newly-revealed sun pierces the fog, it burns away, revealing the coughing, mercifully human forms of Bailey, Matt, Alex.... and the April Fool himself.\n\n`Mistiflying is saved!`\n\nThe April Fool has enough shame to look a bit sheepish. “Oh, hm,” he says. “Perhaps I got a little…. carried away.”\n\nThe crowd mutters. Sodden flowers wash up on sidewalks. Somewhere in the distance, a roof collapses with a spectacular splash.\n\n“Er, yes,” the April Fool says. “That is. What I meant to say was, I’m dreadfully sorry.” He heaves a sigh. “I suppose it can’t all be fun and games, after all. It might not hurt to focus occasionally. Maybe I’ll get a head start on next year’s pranking.”\n\nRedphoenix coughs meaningfully.\n\n“I mean, get a head start on this year’s spring cleaning!” the April Fool says. “Nothing to fear, I’ll have Habit City in spit-shape soon. Luckily nobody is better than I at dual-wielding mops.”\n\nEncouraged, the marching band starts up.\n\nIt isn’t long before all is back to normal in Habit City. Plus, now that the Be-Wilder has evaporated, the magical bees of Mistiflying bustle back to work, and soon the flowers are blooming and the city is floating once more.\n\nAs Habiticans cuddle the magical fuzzy bees, the April Fool’s eyes light up. “Oho, I’ve had a thought! Why don’t you all keep some of these fuzzy Bee Pets and Mounts? It’s a gift that perfectly symbolizes the balance between hard work and sweet rewards, if I’m going to get all boring and allegorical on you.” He winks. “Besides, they don’t have stingers! Fool’s honor.”",
- "questBewilderBossRageTitle": "Beguilement Strike",
- "questBewilderBossRageDescription": "When this gauge fills, The Be-Wilder will unleash its Beguilement Strike on Habitica!",
- "questBewilderDropBumblebeePet": "Bumblebee (Pet)",
- "questBewilderDropBumblebeeMount": "Bumblebee (Mount)",
- "questBewilderBossRageMarket": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nOh no! Despite our best efforts, we've gotten distracted by the Be-Wilder’s charming illusions and have forgotten to do some of our Dailies! With a cackling cry, the shining bird beats its wings, raising a swarm of mist around Alex the Merchant. When the fog clears, he has been possessed! “Have some free samples!” he shouts gleefully, and begins to hurl exploding eggs and potions at fleeing Habiticans. Not the most favorable of sales, to be sure.\n\nHurry! Let's stay focused on our Dailies to defeat this monster before it possesses someone else.",
- "questBewilderBossRageStables": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nAhh!!! Once again the Be-Wilder has dazzled us into neglecting our Dailies, and now it has attacked Matt the Beast Master! With a swirl of mist, Matt transforms into a terrifying winged creature, and all the pets and mounts howl sadly in their stables. Quickly, stay focused on your tasks to defeat this dastardly distraction!",
- "questBewilderBossRageBailey": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nLook out! In the middle of reporting the news, Bailey the Town Crier has been possessed by the Be-Wilder! She lets out an evil, uninformative screech as she rises into the air. Now how will we know what’s going on?\n\nDon't give up... we're so close to defeating this bothersome bird for once and for all!"
+ "questBewilderText": "O Be-Wilder",
+ "questBewilderNotes": "A festa começa como qualquer outra.
Os aperitivos estão excelentes, a música está a animar o ambiente e até os elefantes dançarinos já são parte da rotina. Os Habiticanos riem e conversam entre os centros de mesa florais abastados, contentes por se distrairem das suas tarefas mais fastidiosas e o Palhaço de Abril rodopia entre eles, ansiosamente executando truques e fazendo piadas.
Assim que a torre-relógio de Mistyflying bate à meia-noite, o Palhaço de Abril salta para o palco e começa um discurso.
\"Amigos! Inimigos! Conheçidos toleráveis! Dêem-me a vossa atenção.\" A multidão ri quando orelhas de animais aparecem nas suas cabeças e fazem poses com os seus novos acessórios.
\"Como sabem\", continua o Palhaço, \"as minhas ilusões confusas normalmente duram somente um dia. No entanto, é o meu prazer anunciar que descobri uma forma de nos garantir diversão sem fim sem o peso intrometido das nossas responsabilidades. Ilustres Habiticanos, conheçam o meu novo amigo mágico...O Be-Wilder!\"
Lemoness empalidece subitamente, deixando cair os seus aperitivos. \"Esperem! Não confiem--\"
Subitamente uma névoa espessa e brilhante preenche a sala, rodopiando à volta do Palhaço de Abril, formando penas e um pescoço alongado. A multidão fica sem palavras enquanto um pássaro monstruoso se materializa à sua frente, soltando um riso estridente.
\"Ah! Faz épocas desde que um Habiticano foi tolo o suficiente para me invocar! Que maravilhosa esta sensação de ter uma forma tangível finalmente.\"
Zumbindo de terror, as abelhas de Mistiflying fogem da cidade flutuante, que começa a descer do céu. Uma a uma, as flores brilhantes começam a secar e a flutuar ao vento.
\"Meus caros amigos, porquê este pânico?\", grita o Be-Wilder, batendo as suas asas. \"Não é necessário trabalhar pelas vossas recompensas. Eu simplesmente dar-vos-ei tudo o que desejam!\"
Moedas começam a chover do céu, batendo no solo com força e a multidão foge em busca de abrigo. \"Mas isto é uma piada?\" pergunta Baconsaur enquanto o ouro parte janelas e telhas.
PainterProphet agacha-se enquanto relâmpagos caem e um nevoeiro tapa o Sol. \"Não! Desta vez, não creio que seja!\"
Depressa Habiticanos, não deixem que este Chefão Global vos distraia dos vossos objectivos! Mantenham o foco nas Tarefas que precisam de completar para salvar Mistiflying -- e esperemos, vocês.",
+ "questBewilderCompletion": "O Be-Wilder foi DERROTADO!
Conseguimos! O Be-Wilder deixa sair um grito final enquanto se torce todo no ar, largando penas como se fosse uma chuva. Gradualmente, transforma-se numa nuvem de névoa. Com o Sol começando a furar o nevoeiro, este dissipa-se revelando as silhuetas de Bailey, Matt, Alex... e o Palhaço de Abril.
Mistiflying está salva!
O Palhaço de Abril tem um ar envergonhado. \"Ah, hmmm,\" diz ele. \"Talvez me tenha...entusiasmado um pouco.\"
A multidão murmura. Flores ensopadas enchem os passeios. Algures, um telhado cai, fazendo soar o som de água a salpicar.
\"Pois...\" diz o Palhaço de Abril, \"Bom. O que quero dizer é que quero pedir desculpa\". Ele solta um suspiro. \"Bom, parece que tudo não pode ser tudo diversão e jogos. Não magoa focar-nos de vez em quando. Acho que vou começar a preparar a partida do próximo ano.\"
Redphoenix tosse de forma ameaçadora.
\"Quero dizer, começar a limpeza de primavera deste ano!\" diz o Palhaço de Abril. \"Nada a temer! Vou deixar a Cidade de Habit toda arrumada em pouco tempo. Felizmente ninguém é melhor que eu a manusear duas esfregonas ao mesmo tempo.\"
Encorajada, a banda de cerimónias começa a tocar.
Não demora muito até que tudo esteja de volta ao normal na Cidade de Habit. Mais, agora que o Be-Wilder se evaporou, as abelhas mágicas de Mistiflying voltaram ao trabalho e em pouco tempo as flores estão de novo a despontar e a cidade flutua de novo.
Enquanto os Habiticanos dão festas às abelhas mágicas, os olhos do Palhaço de Abril iluminam-se. \"Oho! Tive um pensamento! Porque não mantemos algumas destas abelhas como Bichos de Estimação ou Montadas? É uma prenda que representa perfeitamente o balanço entre trabalho árduo e recompensas, isto se for para ser simbólico e aborrecido convosco.\" Ele pisca o olho. \"Além disso, não têm ferrões! Honra de Palhaço.\"",
+ "questBewilderCompletionChat": "`O Be-Wilder foi DERROTADO!`\n\nConseguimos! O Be-Wilder deixa sair um grito final enquanto se torce todo no ar, largando penas como se fosse uma chuva. Gradualmente, transforma-se numa nuvem de névoa. Com o Sol começando a furar o nevoeiro, este dissipa-se revelando as silhuetas de Bailey, Matt, Alex... e o Palhaço de Abril.\n\n`Mistiflying está salva!`\n\nO Palhaço de Abril tem um ar envergonhado. \"Ah, hmmm,\" diz ele. \"Talvez me tenha...entusiasmado um pouco.\"\n\nA multidão murmura. Flores ensopadas enchem os passeios. Algures, um telhado cai, fazendo soar o som de água a salpicar.\n\n\"Pois...\" diz o Palhaço de Abril, \"Bom. O que quero dizer é que quero pedir desculpa\". Ele solta um suspiro. \"Bom, parece que tudo não pode ser tudo diversão e jogos. Não magoa focar-nos de vez em quando. Acho que vou começar a preparar a partida do próximo ano.\"\n\nRedphoenix tosse de forma ameaçadora.\n\n\"Quero dizer, começar a limpeza de primavera deste ano!\" diz o Palhaço de Abril. \"Nada a temer! Vou deixar a Cidade de Habit toda arrumada em pouco tempo. Felizmente ninguém é melhor que eu a manusear duas esfregonas ao mesmo tempo.\"\n\nEncorajada, a banda de cerimónias começa a tocar.\n\nNão demora muito até que tudo esteja de volta ao normal na Cidade de Habit. Mais, agora que o Be-Wilder se evaporou, as abelhas mágicas de Mistiflying voltaram ao trabalho e em pouco tempo as flores estão de novo a despontar e a cidade flutua de novo.\n\nEnquanto os Habiticanos dão festas às abelhas mágicas, os olhos do Palhaço de Abril iluminam-se. \"Oho! Tive um pensamento! Porque não mantemos algumas destas abelhas como Bichos de Estimação ou Montadas? É uma prenda que representa perfeitamente o balanço entre trabalho árduo e recompensas, isto se for para ser simbólico e aborrecido convosco.\" Ele pisca o olho. \"Além disso, não têm ferrões! Honra de Palhaço.\"",
+ "questBewilderBossRageTitle": "Ataque de Decepção",
+ "questBewilderBossRageDescription": "Quando esta barra enche, o Be-Wilder libertará o seu Ataque de Decepção sobre Habitica!",
+ "questBewilderDropBumblebeePet": "Abelha Mágica (Mascote)",
+ "questBewilderDropBumblebeeMount": "Abelha Mágica (Montaria)",
+ "questBewilderBossRageMarket": "`O Be-Wilder usa o ATAQUE DE DECEPÇÃO!`\n\nOh não! Apesar do nosso melhor esforço, fomos distraídos pelas ilusões do Be-Wilder e esquecemo-nos de completar algumas das nossas Tarefas Diárias! Com um riso cacarejante, a besta brilhante bate as suas asas, levantando uma névoa à volta de Alex, o comerciante. Quando se dissipa, ele está possuido! \"Tomem algumas amostras grátis!\" grita com prazer, lançando ovos explosivos e poções aos Habiticanos em fuga. Não são os saldos mais favoráveis, sem dúvida.\n\nDepressa! Vamos manter-nos focados nas nossas Tarefas Diárias para derrotar este monstro antes que possua mais alguém.",
+ "questBewilderBossRageStables": "`O Be-Wilder usa o ATAQUE DE DECEPÇÃO!`\n\nAhhh!!! Uma vez mais, o Be-WIlder distraiu-nos o suficiente para negligenciarmos as nossas Tarefas Diárias, e agora atacou Matt, o Mestre das Bestas! Com um remoinho de névoa, Matt transforma-se numa criatura alada terrível e todos os animais e montadas uivam de tristeza nos seus estábulos. Depressa, mantenham-se focados nas vossas Tarefas para derrotar esta distração terrível!",
+ "questBewilderBossRageBailey": "`O Be-Wilder usa o ATAQUE DE DECEPÇÃO!`\n\nCuidado! Enquanto reporta as notícias, Bailey, a Arauto, é possuída pelo Be-Wilder! Ela solta um guincho maléfico e sem significado enquanto começa a flutuar no ar. Como é que vamos saber agora o que se está a passar?\n\nNão desistam...estamos tão perto de derrotar este pássaro incómodo de uma vez para todas!",
+ "questFalconText": "As Aves de Rapinrolação",
+ "questFalconNotes": "Montanha Habitica está sendo ofuscada por uma crescente montanha de Afazeres. Ela costumava ser um lugar ter piqueniques e aproveitar a sensação de dever cumprido, até que as tarefas negligenciadas cresceram fora de controle. Agora é a casa das temidas Aves de Rapinrolação, criaturas abomináveis que impedem os Habiticanos de completarem suas tarefas!
\"Está muito quente!\" elas grasnam para @JonArinbjorn e @Onheiron. \"Demorará muito para fazer agora. Não fará diferença alguma se você quiser esperar até amanhã! Por que você não faz algo divertido no lugar?\"
Basta, você jura. Você escalará sua montanha pessoal de Afazeres e derrotará as Aves de Rapinrolação!",
+ "questFalconCompletion": "Tendo finalmente triunfado sobre as Aves de Rapinrolação, você se acalma para aproveitar a vista e seu bem-merecido descanso.
\"Uau!\" diz @Trogdorina. \"Você venceu!\"
@Squish acrescenta, \"Aqui, pegue estes ovos que achei como recompensa.\"",
+ "questFalconBoss": "Aves de Rapinrolação",
+ "questFalconDropFalconEgg": "Falcão (Ovo)",
+ "questFalconUnlockText": "Desbloqueia ovos de Falcão para compra no Mercado",
+ "questTreelingText": "A Árvore Emaranhada",
+ "questTreelingNotes": "É a Competição de Jardinagem anual e todos estão falando sobre o projeto misterioso que @aurakami prometeu revelar. Você se junta à multidão naquele dia do grande anúncio e se admira com a entrada de uma árvore que se mexe. @fuzzytrees explica que a árvore ajudará com a manutenção do jardim, mostrando como ela pode cortar a grama, aparar os arbustos e podar as roseiras tudo ao mesmo tempo - até que de repente a árvore fica louca, virando suas tesouras de poda contra o seu criador! A multidão entra em pânico e todo mundo tenta fugir, mas você não tem medo - você se inclina para a frente, pronto para batalhar.",
+ "questTreelingCompletion": "Você tira seu pó quando as últimas folhas caem no chão. Em vez de triste, a Competição de Jardinagem agora está segura - apesar de que a árvore que você reduziu a uma pilha de lascas de madeira não ganhará nenhum prêmio! \"Ainda alguns nós para serem trabalhados aqui\", diz @PainterProphet. \"Talvez alguém mais faça um bom trabalho treinando os brotos. Você arrisca uma tentativa?\"",
+ "questTreelingBoss": "Árvore Emaranhada",
+ "questTreelingDropTreelingEgg": "Arvorezinha (Ovo)",
+ "questTreelingUnlockText": "Desbloqueia ovos de Arvorezinhas compráveis no Mercado"
}
\ No newline at end of file
diff --git a/common/locales/pt/rebirth.json b/common/locales/pt/rebirth.json
index 4e2ceeac36..116a697607 100644
--- a/common/locales/pt/rebirth.json
+++ b/common/locales/pt/rebirth.json
@@ -5,7 +5,7 @@
"rebirthStartOver": "Renascer reinicia seu personagem no nível 1.",
"rebirthAdvList1": "Você recupera a Vida toda.",
"rebirthAdvList2": "Você ficará sem Experiência, Ouro ou Equipamentos (com exceção dos itens grátis como os Itens Misteriosos). ",
- "rebirthAdvList3": "Seus Hábitos, Tarefas Diárias, e Afazeres reiniciam em amarelo, e seus combos resetam.",
+ "rebirthAdvList3": "Seus Hábitos, Tarefas Diárias e Afazeres reiniciaram para amarelo, seus combos reiniciaram, exceto as tarefas de desafios.",
"rebirthAdvList4": "Sua classe inicial será Guerreiro até liberar uma nova classe.",
"rebirthInherit": "Seu novo personagem herda algumas coisas de seu antecessor:",
"rebirthInList1": "Tarefas, histórico, e configurações permanecem.",
@@ -24,5 +24,6 @@
"rebirthPop": "Comece um novo personagem do Nível 1 mantendo conquistas, colecionáveis, e tarefas com histórico.",
"rebirthName": "Orbe do Renascimento",
"reborn": "Renascido, nível max <%= reLevel %>",
- "confirmReborn": "Tem certeza?"
+ "confirmReborn": "Tem certeza?",
+ "rebirthComplete": "Você renasceu!"
}
\ No newline at end of file
diff --git a/common/locales/pt/settings.json b/common/locales/pt/settings.json
index fb74efc854..b8e4b134b8 100644
--- a/common/locales/pt/settings.json
+++ b/common/locales/pt/settings.json
@@ -47,6 +47,7 @@
"customDayStart": "Início do Dia Personalizado",
"changeCustomDayStart": "Deseja mudar o Início de dia customizado?",
"sureChangeCustomDayStart": "Você tem certeza que quer mudar o início customizado de dia?",
+ "customDayStartHasChanged": "Seu início de dia personalizado mudou.",
"nextCron": "As suas tarefas diárias serão reiniciadas ao utilizar Habitica depois de <%= time %>. Certifique-se de que completou as suas tarefas diárias antes deste horário!",
"customDayStartInfo1": "Habitica está predefinido para verificar e reiniciar as suas tarefas diárias à meia noite de seu fuso horária a cada dia. Você pode personalizar esse horário aqui.",
"misc": "Variados",
@@ -61,12 +62,23 @@
"newUsername": "Novo nome de usuário",
"dangerZone": "Zona de Perigo",
"resetText1": "ATENÇÃO! Isso redefine várias partes da sua conta. Isso é altamente desencorajado, mas algumas pessoas acham útil no início, após brincarem com o site por um curto período de tempo.",
- "resetText2": "Você perderá todos os seus níveis, ouro e pontos de experiência. Todas as suas tarefas serão deletadas permanentemente e você perderá todo o histórico das suas tarefas. Você perderá todo seu equipamento, porém poderá comprá-lo novamente, incluindo todos os equipamentos de edição limitada ou Itens Misteriosos de assinante que você já possui (você precisará de estar na classe correta para comprar novamente os equipamentos específicos da classe). Você manterá sua classe atual, seus mascotes e montarias. Você pode preferir usar uma Orbe do Renascimento em vez disso, que é uma opção muito mais segura e manterá suas tarefas.",
+ "resetText2": "Você perderá todos os seus níveis, ouro, e pontos de experiência. Todas as suas tarefas (exceto aquelas de desafios) serão deletadas permanentemente e você perderá todo os dados históricos delas. Você perderá todo o seu equipamento, mas poderá comprá-lo de volta, incluindo todo o equipamento de edição limitada ou os itens misteriosos dos assinantes que você já possua (você precisará ter a classe correta para recomprar equipamento classe-específico). Você manterá sua classe atual e seus mascotes e montarias. Você pode preferir usar a Orbe do Renascimento em vez disso, que é uma opção muito mais segura e que preservará suas tarefas.",
"deleteText": "Tem certeza? Isso deletará sua conta para sempre, e nunca poderá ser recuperada! Você precisará registrar uma nova conta para usar Habitica de novo. Gemas gastas ou no banco não serão restituídas. Se tiver certeza absoluta, digite <%= deleteWord %> na caixa de texto abaixo.",
"API": "API",
+ "APIv3": "API v3",
+ "APIv2": "API v2 - Deprecated",
"APIText": "Copie isso para uso em aplicações de terceiros. Entretanto, pense no seu API Token como uma senha, não divulgue publicamente. Seu ID de Usuário pode ser, ocasionalmente, solicitado, mas nunca divulgue seu API Token em lugares onde outros possam ver, incluindo o Github.",
"APIToken": "API Token (isso é uma senha - veja o aviso acima!)",
+ "thirdPartyApps": "Aplicativos Terceirizados",
+ "dataToolDesc": "Uma página que mostra certas informações da sua conta de Habitica, como estatísticas sobre suas tarefas, equipamentos e habilidades.",
+ "beeminder": "Beeminder",
+ "beeminderDesc": "Deixe o Beeminder monitorar automaticamente suas tarefas do Habitica. Você pode se propor a manter um número alvo de tarefas completadas por dia ou por semana, ou você pode propor reduzir o seu numero de tarefas remanescentes incompletas gradualmente. (Por \"propor\" Beeminder quer dizer sobre aviso de pagar dinheiro real! Mas você também pode gostar dos gráficos chiques do Beeminder.)",
+ "chromeChatExtension": "Extensão de Chat do Chrome",
+ "chromeChatExtensionDesc": "A extensão de Chat em Habitica para o Chrome adiciona uma caixa de Chat intuitiva em habitica.com. Isso permite os usuários conversarem na Taverna, com sua equipe e qualquer guilda que esteja participando.",
+ "otherExtensions": "Outras Extensões",
+ "otherDesc": "Encontre outros aplicativos, extensões e ferramentas na Wiki de Habitica.",
"resetDo": "Faça, reinicie minha conta!",
+ "resetComplete": "Reset completo!",
"fixValues": "Corrigir Valores",
"fixValuesText1": "Se encontrou um bug ou cometeu algum erro que alterou seu personagem injustamente (dano que não deveria ter tomado, Ouro que você não mereceu, etc.), você pode manualmente corrigir seus números aqui. Sim, isso cria a possibilidade de trapacear: use essa ferramenta com sabedoria, ou sabotará sua própria construção de hábitos.",
"fixValuesText2": "Note que você não pode restaurar Combos de tarefas individuais aqui. Para fazer isso, edite a Tarefa Diária e vá nas Opções Avançadas, onde você encontrará o campo de Restauração de Combos.",
@@ -96,6 +108,7 @@
"emailNotifications": "Notificações de E-mail",
"wonChallenge": "Você venceu um desafio!",
"newPM": "Recebeu Mensagem Privada",
+ "sentGems": "Gemas enviadas!",
"giftedGems": "Gemas Presenteadas",
"giftedGemsInfo": "<%= amount %> Gemas - por <%= name %>",
"giftedSubscription": "Assinaturas Presenteadas",
@@ -135,6 +148,11 @@
"webhooks": "Webhooks",
"enabled": "Habilitado",
"webhookURL": "URL do Webhook",
+ "invalidUrl": "url inválida",
+ "invalidEnabled": "the \"enabled\" parameter should be a boolean",
+ "regIdRequired": "RegId is required",
+ "pushDeviceAdded": "Push device added successfully",
+ "pushDeviceAlreadyAdded": "The user already has the push device",
"add": "Adicionar",
"buyGemsGoldCap": "Máximo aumentado para <%= amount %>",
"mysticHourglass": "<%= amount %> Ampulhetas Místicas",
@@ -149,5 +167,5 @@
"amazonPayments": "Amazon Payments",
"timezone": "Fuso Horário",
"timezoneUTC": "O Habitica usa o fuso horário definido no seu computador, que é <%= utc %>",
- "timezoneInfo": "Se este fuso horário estiver incorreto, primeiro atualize esta página para garantir que Habitica tem as informações mais recentes. Caso continue errado, ajuste o fuso horário do seu computador e então atualize esta página novamente.
Se você utiliza Habitica em outros computadores ou aparelhos móveis, o fuso horário deve ser o mesmo em todos eles. Se suas Tarefas Diárias estiverem sendo redefinidas no horário errado, faça uma nova verificação em todos os outros computadores e navegadores de dispositivos móveis. "
+ "timezoneInfo": "If that time zone is wrong, first reload this page using your browser's reload or refresh button to ensure that Habitica has the most recent information. If it is still wrong, adjust the time zone on your PC and then reload this page again.
If you use Habitica on other PCs or mobile devices, the time zone must be the same on them all. If your Dailies have been resetting at the wrong time, repeat this check on all other PCs and on a browser on your mobile devices."
}
\ No newline at end of file
diff --git a/common/locales/pt/spells.json b/common/locales/pt/spells.json
index 94cb8579cb..b18e618b41 100644
--- a/common/locales/pt/spells.json
+++ b/common/locales/pt/spells.json
@@ -35,8 +35,8 @@
"spellSpecialSnowballAuraNotes": "Jogue uma bola de neve em um membro da equipe! O que poderia dar errado? Dura até o novo dia do membro.",
"spellSpecialSaltText": "Sal",
"spellSpecialSaltNotes": "Alguém jogou uma bola de neve em você. Ha ha, muito engraçado. Agora tire essa neve de mim!",
- "spellSpecialSpookDustText": "Brilhos Assustadores",
- "spellSpecialSpookDustNotes": "Transforme um amigo em um lençol flutuante com olhos!",
+ "spellSpecialSpookySparklesText": "Brilhos Assustadores",
+ "spellSpecialSpookySparklesNotes": "Transforme um amigo em um lençol voador com olhos!",
"spellSpecialOpaquePotionText": "Poção Opaca",
"spellSpecialOpaquePotionNotes": "Cancela os efeitos de Brilhos Assustadores.",
"spellSpecialShinySeedText": "Semente Cintilante",
@@ -46,5 +46,11 @@
"spellSpecialSeafoamText": "Espuma do mar",
"spellSpecialSeafoamNotes": "Transforma um amigo em uma criatura marinha!",
"spellSpecialSandText": "Areia",
- "spellSpecialSandNotes": "Cancela os efeitos da espuma do mar."
-}
+ "spellSpecialSandNotes": "Cancela os efeitos da espuma do mar.",
+ "spellNotFound": "Habilidade \"<%= spellId %>\" não encontrada.",
+ "partyNotFound": "Equipe não encontrada.",
+ "targetIdUUID": "\"targetId\" precisa ser um ID de usuário válido.",
+ "challengeTasksNoCast": "O uso de habilidade em tarefas de desafio não é permitido.",
+ "spellNotOwned": "Você não possui essa habilidade.",
+ "spellLevelTooHigh": "Você precisa ter nível <%= level %> para usar essa habilidade."
+}
\ No newline at end of file
diff --git a/common/locales/pt/subscriber.json b/common/locales/pt/subscriber.json
index 98f11c8e22..511da225f2 100644
--- a/common/locales/pt/subscriber.json
+++ b/common/locales/pt/subscriber.json
@@ -4,6 +4,8 @@
"subDescription": "Compre Gemas com ouro, ganhe itens misteriosos mensalmente, mantenha o histórico do progresso, dobre o limite de \"drop\" diário, ajude os desenvolvedores. Clique para mais informações.",
"buyGemsGold": "Comprar Gemas com Ouro",
"buyGemsGoldText": "Alexander, o Comerciante, venderá a você Gemas por <%= gemCost %> ouro cada gema. Suas entregas mensais são inicialmente limitadas a <%= gemLimit %> Gemas por mês, mas esse limite aumenta em 5 Gemas a cada três meses consecutivos de assinatura , até o máximo de 50 Gemas por mês!",
+ "mustSubscribeToPurchaseGems": "É necessário assinar para comprar gemas com ouro.",
+ "reachedGoldToGemCap": "Você atingiu o limite de conversão de ouro para gemas <%= convCap %> permitido para esse mês. Nós temos isso para prevenir abuso. O limite irá reiniciar dentro dos três primeiros dias do próximo mês.",
"retainHistory": "Guardar Itens adicionais no histórico",
"retainHistoryText": "Deixa os Afazeres completos e o histórico de tarefas disponíveis por mais tempo.",
"doubleDrops": "Capacidade de drop diário dobrado",
@@ -29,8 +31,9 @@
"manageSub": "Clique para gerenciar assinatura",
"cancelSub": "Cancelar Assinatura",
"canceledSubscription": "Assinatura Cancelada",
+ "cancelingSubscription": "Cancelando a assinatura.",
"adminSub": "Assinaturas Administrativas",
- "morePlans": "Mais Planos Vindo em Breve",
+ "morePlans": "Mais Planos em Breve",
"organizationSub": "Organização Privada",
"organizationSubText": "Membros da organização participam fora do Habitica, oferecendo foco para seus participantes.",
"hostingType": "Tipo de Hospedagem",
@@ -73,6 +76,9 @@
"timeTravelersPopover": "Notamos que você tem uma Ampulheta Mística, então estamos felizes viajar de volta no tempo por você! Por favor escolha um mascote, montaria ou Conjunto de Itens Misteriosos que deseja. Você pode ver a lista de conjuntos passados aqui! Se esses não te agradarem, talvez você esteja interessado em um de nossos elegantes novos conjuntos futurísticos Steampunk?",
"timeTravelersAlreadyOwned": "Parabéns! Você já possui todos os itens que os Viajantes do Tempo oferecem no momento. Obrigado por apoiar o site!",
"mysticHourglassPopover": "A Ampulheta Mística permite a compra de certos itens oferecidos por tempo limitado, como os Conjuntos de Itens Misteriosos Mensais e recompensas de Chefões do passado!",
+ "mysterySetNotFound": "Conjunto misterioso não encontrado, ou conjunto já possuído.",
+ "mysteryItemIsEmpty": "Itens misteriosos estão vazios.",
+ "mysteryItemOpened": "Item misterioso aberto.",
"mysterySet201402": "Conjunto \"Mensageiro Alado\"",
"mysterySet201403": "Conjunto \"Andarilho da Floresta\"",
"mysterySet201404": "Conjunto \"Borboleta Crepuscular\"",
@@ -99,6 +105,8 @@
"mysterySet201601": "Conjunto \"Campeão de Resolução\"",
"mysterySet201602": "Conjunto \"Destruidor de Corações\"",
"mysterySet201603": "Conjunto Trevo de 4 Folhas da Sorte",
+ "mysterySet201604": "Conjunto Guerreiro de Folha",
+ "mysterySet201605": "Marching Bard Set",
"mysterySet301404": "Conjunto \"Steampunk Padrão\"",
"mysterySet301405": "Conjunto \"Acessórios Steampunk\"",
"mysterySetwondercon": "WonderCon",
@@ -110,9 +118,25 @@
"hourglassBuyItemConfirm": "Comprar este item por 1 Ampulheta Mística?",
"petsAlreadyOwned": "Você já possui este mascote.",
"mountsAlreadyOwned": "Você já possui esta montaria.",
- "typeNotAllowedHourglass": "Tipo de item não disponível para compra usando Ampulhetas Místicas. Tipos permitidos:",
+ "typeNotAllowedHourglass": "Tipo de item não permitido para compra com a Ampulheta Mística. Tipos permitidos: <%= allowedTypes %>",
"petsNotAllowedHourglass": "Mascore não disponível para compra com Ampulheta Mística.",
"mountsNotAllowedHourglass": "Montaria não disponível para compra usando Ampulheta Mística.",
"hourglassPurchase": "Você comprou um item usando uma Ampulheta Mística!",
- "hourglassPurchaseSet": "Você comprou um conjunto de itens usando uma Ampulheta Mística!"
+ "hourglassPurchaseSet": "Você comprou um conjunto de itens usando uma Ampulheta Mística!",
+ "missingUnsubscriptionCode": "Código de cancelamento de assinatura faltando.",
+ "missingSubscription": "Usuário não possui um plano de assinatura.",
+ "missingSubscriptionCode": "Missing subscription code. Possible values: basic_earned, basic_3mo, basic_6mo, google_6mo, basic_12mo.",
+ "cannotDeleteActiveAccount": "Você possui um cadastro ativo, cancele seu plano antes de deletar sua conta.",
+ "paymentNotSuccessful": "O pagamento não foi bem-sucedido.",
+ "planNotActive": "O plano ainda não foi ativado (devido a um erro com o PayPal). Ele iniciará <%= nextBillingDate %>, depois disso você poderá cancelar para manter seus benefícios completos.",
+ "notAllowedHourglass": "Mascote/Montaria não disponíveis para compra com a Ampulheta Mística.",
+ "readCard": "<%= cardType %> foi lida",
+ "cardTypeRequired": "Tipo de cartão requerido.",
+ "cardTypeNotAllowed": "Tipo de carta desconhecido.",
+ "invalidCoupon": "Código de cupom inválido.",
+ "couponUsed": "Código de cupom já utilizado.",
+ "noSudoAccess": "Você não possui acesso de administrador.",
+ "couponCodeRequired": "O código do cupom é requerido.",
+ "eventRequired": "\"req.params.event\" é necessário.",
+ "countRequired": "\"req.query.count\" é necessário."
}
\ No newline at end of file
diff --git a/common/locales/pt/tasks.json b/common/locales/pt/tasks.json
index e07e14b2d5..51e877e7e2 100644
--- a/common/locales/pt/tasks.json
+++ b/common/locales/pt/tasks.json
@@ -73,6 +73,7 @@
"clearTags": "Limpar",
"hideTags": "Ocultar",
"showTags": "Mostrar",
+ "toRequired": "Você precisa fornecer um valor",
"startDate": "Data Inicial",
"startDateHelpTitle": "Quando esta tarefa deve começar?",
"startDateHelp": "Defina a data para a qual esta tarefa entra em vigor. Não será debitada nos dias anteriores.",
@@ -88,8 +89,9 @@
"fortifyName": "Poção de Fortificação",
"fortifyPop": "Reverte todas tarefas para o valor neutro (cor amarela), e recupera toda Vida perdida.",
"fortify": "Fortificar",
- "fortifyText": "Fortificar reverterá todas as suas tarefas para o valor neutro (amarelo), como se tivesse acabado de adicioná-las, e completará sua vida até estar cheia. Esta é uma ótima opção caso suas tarefas vermelhas estejam tornando o jogo muito difícil, ou suas tarefas azuis tornando-o muito fácil. Se começar de novo parece mais empolgante, gaste suas Gemas e aproveite!",
+ "fortifyText": "A poção fortificante retornará todas as suas tarefas, exceto as tarefas de desafios, para um estado neutro (amarelo), como se você tivesse acabado de adicioná-las, e aumentará sua Saúde para a barra cheia. Isso é ótimo se suas tarefas vermelhas estão deixando o jogo muito difícil, ou se suas tarefas azuis estão deixando o jogo fácil demais. Se começar do zero te parecer mais motivador, gaste suas gemas e ganhe uma moratória!",
"confirmFortify": "Tem certeza?",
+ "fortifyComplete": "Fortificação completa!",
"sureDelete": "Você tem certeza de que deseja deletar <%= taskType %> com o texto \"<%= taskText %>\"?",
"streakCoins": "Bônus de Combo!",
"pushTaskToTop": "Enviar tarefa para o topo. Segure CTRL ou CMD para enviar para baixo.",
@@ -112,5 +114,18 @@
"rewardHelp2": "Equipamentos afetam suas estatísticas (<%= linkStart %>Usuário > Estatísticas & Conquistas<%= linkEnd %>).",
"rewardHelp3": "Equipamentos Especiais irão aparecer durante os Eventos Mundiais.",
"rewardHelp4": "Não tenha medo de definir Recompensas Personalizadas! Confira algumas amostras aqui.",
- "clickForHelp": "Clique para ajuda"
+ "clickForHelp": "Clique para ajuda",
+ "taskIdRequired": "\"taskId\" precisa ser um UUID válido.",
+ "taskNotFound": "Tarefa não encontrada.",
+ "invalidTaskType": "O tipo de tarefa precisa ser um de \"habit\", \"daily\", \"todo\", \"reward\".",
+ "cantDeleteChallengeTasks": "Uma tarefa que pertença a um desafio não pode ser deletada.",
+ "checklistOnlyDailyTodo": "Listas só são suportadas em tarefas diárias e afazeres.",
+ "checklistItemNotFound": "Nenhum item de lista foi encontrado com o id dado.",
+ "itemIdRequired": "\"itemId\" precisa ser um UUID válido.",
+ "tagNotFound": "Nenhum item de etiqueta foi encontrado com esse id.",
+ "tagIdRequired": "\"tagId\" precisa ser um UUID válido correspondente a uma etiqueta pertencente ao usuário.",
+ "positionRequired": "\"position\" é necessária e precisa ser um número.",
+ "cantMoveCompletedTodo": "Não é possível mover um afazer concluído.",
+ "directionUpDown": "\"direction\" is required and must be 'up' or 'down'",
+ "alreadyTagged": "A tarefa fá foi etiquetada com essa etiqueta."
}
\ No newline at end of file
diff --git a/common/locales/ro/backgrounds.json b/common/locales/ro/backgrounds.json
index 2149f6f0c1..7f3d0801d9 100644
--- a/common/locales/ro/backgrounds.json
+++ b/common/locales/ro/backgrounds.json
@@ -160,5 +160,12 @@
"backgroundGiantFlowersText": "Giant Flowers",
"backgroundGiantFlowersNotes": "Frolic atop Giant Flowers.",
"backgroundRainbowsEndText": "End of the Rainbow",
- "backgroundRainbowsEndNotes": "Discover gold at the End of the Rainbow."
+ "backgroundRainbowsEndNotes": "Discover gold at the End of the Rainbow.",
+ "backgrounds052016": "SET 24: Released May 2016",
+ "backgroundBeehiveText": "Beehive",
+ "backgroundBeehiveNotes": "Buzz and dance in a Beehive.",
+ "backgroundGazeboText": "Gazebo",
+ "backgroundGazeboNotes": "Battle a Gazebo.",
+ "backgroundTreeRootsText": "Tree Roots",
+ "backgroundTreeRootsNotes": "Explore the Tree Roots."
}
\ No newline at end of file
diff --git a/common/locales/ro/challenge.json b/common/locales/ro/challenge.json
index cc7158e0cc..802f477975 100644
--- a/common/locales/ro/challenge.json
+++ b/common/locales/ro/challenge.json
@@ -63,5 +63,20 @@
"congratulations": "Congratulations!",
"hurray": "Hurray!",
"noChallengeOwner": "no owner",
- "noChallengeOwnerPopover": "This challenge does not have an owner because the person who created the challenge deleted their account."
+ "noChallengeOwnerPopover": "This challenge does not have an owner because the person who created the challenge deleted their account.",
+ "challengeMemberNotFound": "User not found among challenge's members",
+ "onlyGroupLeaderChal": "Only the group leader can create challenges",
+ "tavChalsMinPrize": "Prize must be at least 1 Gem for Tavern challenges.",
+ "cantAfford": "You can't afford this prize. Purchase more gems or lower the prize amount.",
+ "challengeIdRequired": "\"challengeId\" must be a valid UUID.",
+ "winnerIdRequired": "\"winnerId\" must be a valid UUID.",
+ "challengeNotFound": "Challenge not found.",
+ "onlyLeaderDeleteChal": "Only the challenge leader can delete it.",
+ "onlyLeaderUpdateChal": "Only the challenge leader can update it.",
+ "winnerNotFound": "Winner with id \"<%= userId %>\" not found or not part of the challenge.",
+ "noCompletedTodosChallenge": "\"includeComepletedTodos\" is not supported when fetching a challenge tasks.",
+ "userTasksNoChallengeId": "When \"tasksOwner\" is \"user\" \"challengeId\" can't be passed.",
+ "onlyChalLeaderEditTasks": "Tasks belonging to a challenge can only be edited by the leader.",
+ "userAlreadyInChallenge": "User is already participating in this challenge.",
+ "cantOnlyUnlinkChalTask": "Only broken challenges tasks can be unlinked."
}
\ No newline at end of file
diff --git a/common/locales/ro/character.json b/common/locales/ro/character.json
index ef3350480e..cd1bfe95b8 100644
--- a/common/locales/ro/character.json
+++ b/common/locales/ro/character.json
@@ -1,4 +1,5 @@
{
+ "communityGuidelinesWarning": "Please keep in mind that your Display Name, profile photo, and blurb must comply with the Community Guidelines (e.g. no profanity, no adult topics, no insults, etc). If you have any questions about whether or not something is appropriate, feel free to email leslie@habitica.com!",
"statsAch": "Atribute și Realizări",
"profile": "Profil",
"avatar": "Customize Avatar",
@@ -109,6 +110,7 @@
"mage": "Mag",
"mystery": "Mister",
"changeClass": "Schimbă-ți Clasa, Restituie Punctele de atribut",
+ "lvl10ChangeClass": "To change class you must be at least level 10.",
"levelPopover": "Fiecare nivel îți oferă un punct pe care-l poți repartiza unui atribut la alegerea ta. Poți face acest lucru manual, sau poți lăsa jocul să decidă pentru tine folosind una din opțiunile de Alocare Automată.",
"unallocated": "Puncte de Atribut Nealocate",
"haveUnallocated": "You have <%= points %> unallocated Attribute Point(s)",
@@ -164,5 +166,7 @@
"int": "INT",
"showQuickAllocation": "Show stat allocation",
"hideQuickAllocation": "Hide stat allocation",
- "quickAllocationLevelPopover": "Each level earns you one point to assign to an attribute of your choice. You can do so manually, or let the game decide for you using one of the Automatic Allocation options found in User -> Stats."
+ "quickAllocationLevelPopover": "Each level earns you one point to assign to an attribute of your choice. You can do so manually, or let the game decide for you using one of the Automatic Allocation options found in User -> Stats.",
+ "invalidAttribute": "\"<%= attr %>\" is not a valid attribute.",
+ "notEnoughAttrPoints": "You don't have enough attribute points."
}
\ No newline at end of file
diff --git a/common/locales/ro/content.json b/common/locales/ro/content.json
index 81eb783655..e7fbd62026 100644
--- a/common/locales/ro/content.json
+++ b/common/locales/ro/content.json
@@ -31,7 +31,7 @@
"dropEggCactusAdjective": "spinos",
"dropEggBearCubText": "Pui de urs",
"dropEggBearCubMountText": "Urs",
- "dropEggBearCubAdjective": "a brave",
+ "dropEggBearCubAdjective": "curajos",
"questEggGryphonText": "Grifon",
"questEggGryphonMountText": "Grifon",
"questEggGryphonAdjective": "mândru",
@@ -46,7 +46,7 @@
"questEggEggAdjective": "colorat",
"questEggRatText": "Șobolan",
"questEggRatMountText": "Șobolan",
- "questEggRatAdjective": "a sociable",
+ "questEggRatAdjective": "sociabil",
"questEggOctopusText": "Caracatiță",
"questEggOctopusMountText": "Caracatiță",
"questEggOctopusAdjective": "alunecoasă",
@@ -98,21 +98,27 @@
"questEggFrogText": "Broască",
"questEggFrogMountText": "Broască",
"questEggFrogAdjective": "princiară",
- "questEggSnakeText": "Snake",
- "questEggSnakeMountText": "Snake",
- "questEggSnakeAdjective": "a slithering",
+ "questEggSnakeText": "Șarpe",
+ "questEggSnakeMountText": "Șarpe",
+ "questEggSnakeAdjective": "alunecoasă",
"questEggUnicornText": "Unicorn",
- "questEggUnicornMountText": "Winged Unicorn",
- "questEggUnicornAdjective": "a magical",
- "questEggSabretoothText": "Sabretooth Tiger",
- "questEggSabretoothMountText": "Sabretooth Tiger",
- "questEggSabretoothAdjective": "a ferocious",
- "questEggMonkeyText": "Monkey",
- "questEggMonkeyMountText": "Monkey",
- "questEggMonkeyAdjective": "a mischievous",
- "questEggSnailText": "Snail",
- "questEggSnailMountText": "Snail",
- "questEggSnailAdjective": "a slow but steady",
+ "questEggUnicornMountText": "Unicorn Înaripat",
+ "questEggUnicornAdjective": "magic",
+ "questEggSabretoothText": "Tigru Preistoric",
+ "questEggSabretoothMountText": "Tigru Preistoric",
+ "questEggSabretoothAdjective": "feroce",
+ "questEggMonkeyText": "Maimuță",
+ "questEggMonkeyMountText": "Maimuță",
+ "questEggMonkeyAdjective": "neastâmpărat",
+ "questEggSnailText": "Melc",
+ "questEggSnailMountText": "Melc",
+ "questEggSnailAdjective": "încet dar sigur",
+ "questEggFalconText": "Șoim",
+ "questEggFalconMountText": "Șoim",
+ "questEggFalconAdjective": "iute",
+ "questEggTreelingText": "Treeling",
+ "questEggTreelingMountText": "Treeling",
+ "questEggTreelingAdjective": "a leafy",
"eggNotes": "Găsește o licoare de eclozat pentru a turna peste acest ou și va ecloza în <%= eggAdjective(locale) %> <%= eggText(locale) %>.",
"hatchingPotionBase": "de bază",
"hatchingPotionWhite": "Alb",
@@ -125,7 +131,8 @@
"hatchingPotionCottonCandyBlue": "Bleu",
"hatchingPotionGolden": "Auriu",
"hatchingPotionSpooky": "Înfricoșător",
- "hatchingPotionPeppermint": "Peppermint",
+ "hatchingPotionPeppermint": "Mentă",
+ "hatchingPotionFloral": "Floral",
"hatchingPotionNotes": "Toarnă aceasta pe un ou și va ecloza ca un companion <%= potText(locale) %>.",
"premiumPotionAddlNotes": "Neutilizabil pe ouă de companioni obținute din expediții.",
"foodMeat": "Carne",
diff --git a/common/locales/ro/contrib.json b/common/locales/ro/contrib.json
index 5b1211f0c1..b7a04f2018 100644
--- a/common/locales/ro/contrib.json
+++ b/common/locales/ro/contrib.json
@@ -35,8 +35,12 @@
"hallContributors": "Hall of Contributors",
"hallPatrons": "Hala Patronilor",
"rewardUser": "Răsplătește utilizatorul",
- "UUID": "UUID",
+ "UUID": "User ID",
"loadUser": "Încarcă utilizator",
+ "noAdminAccess": "You don't have admin access.",
+ "pageMustBeNumber": "req.query.page must be a number",
+ "userNotFound": "User not found.",
+ "invalidUUID": "UUID must be valid",
"title": "Titlu",
"moreDetails": "Mai multe detalii (1-7)",
"moreDetails2": "mai multe detalii (8-9)",
@@ -52,8 +56,8 @@
"visitHeroes": "Vizitează Hala Eroilor (contribuitori și susținători)",
"conLearn": "Află mai mult despre răsplățile de contribuitor",
"conLearnHow": "Află cum să contribui la Habitica",
- "surveysSingle": "Helped Habitica grow by filling out a survey. There are no active surveys.",
- "surveysMultiple": "Helped Habitica grow by filling out <%= surveys %> surveys. There are no active surveys.",
+ "surveysSingle": "Helped Habitica grow, either by filling out a survey or helping with a major testing effort. Thank you!",
+ "surveysMultiple": "Helped Habitica grow on <%= surveys %> occasions, either by filling out a survey or helping with a major testing effort. Thank you!",
"currentSurvey": "Current Survey",
"surveyWhen": "The badge will be awarded to all participants when surveys have been processed, in late March.",
"blurbInbox": "This is where your private messages are stored! You can send someone a message by clicking on the envelope icon next to their name in Tavern, Party, or Guild Chat. If you've received an inappropriate PM, you should email a screenshot of it to Lemoness (leslie@habitica.com)",
diff --git a/common/locales/ro/death.json b/common/locales/ro/death.json
index b67ccec897..f540eb6c98 100644
--- a/common/locales/ro/death.json
+++ b/common/locales/ro/death.json
@@ -12,5 +12,6 @@
"losingHealthQuickly": "Pierzi sănătate prea rapid?",
"lowHealthTips3": "Sarcini zilnice necompletate te rănesc peste noapte, așa că ai grijă să nu adaugi prea multe la început.",
"lowHealthTips4": "Dacă o sarcină zilnică nu trebuie realizată ziua respectivă, o poți dezactiva printr-un clic pe iconița cu creion.",
- "goodLuck": "Baftă!"
+ "goodLuck": "Baftă!",
+ "cannotRevive": "Cannot revive if not dead"
}
\ No newline at end of file
diff --git a/common/locales/ro/front.json b/common/locales/ro/front.json
index 6297a49584..8bec41aa35 100644
--- a/common/locales/ro/front.json
+++ b/common/locales/ro/front.json
@@ -28,6 +28,7 @@
"communityReddit": "Reddit",
"companyAbout": "How it Works",
"companyBlog": "Blog",
+ "devBlog": "Developer Blog",
"companyDonate": "Donează",
"companyExtensions": "Extensii",
"companyPrivacy": "Confidențialitate",
@@ -51,6 +52,7 @@
"featureSocialHeading": "Social play",
"featuredIn": "Featured in",
"featuresHeading": "We also feature...",
+ "footerDevs": "Developers",
"footerCommunity": "Comunitate",
"footerCompany": "Companie",
"footerMobile": "Mobil",
@@ -182,6 +184,7 @@
"zelahQuote": "With [Habitica], I can be persuaded to go to bed on time by the thought of gaining points for an early night or losing health for a late one!",
"reportAccountProblems": "Report Account Problems",
"reportCommunityIssues": "Report Community Issues",
+ "subscriptionPaymentIssues": "Subscription and Payment Issues",
"generalQuestionsSite": "General Questions about the Site",
"businessInquiries": "Business Inquiries",
"merchandiseInquiries": "Merchandise Inquiries",
@@ -222,5 +225,32 @@
"altAttrWebstorm": "WebStorm",
"altAttrGithub": "GitHub",
"altAttrTrello": "Trello",
- "altAttrSlack": "Slack"
+ "altAttrSlack": "Slack",
+ "missingAuthHeaders": "Missing authentication headers.",
+ "missingAuthParams": "Missing authentication parameters.",
+ "missingUsernameEmail": "Missing username or email.",
+ "missingEmail": "Missing email.",
+ "missingUsername": "Missing username.",
+ "missingPassword": "Missing password.",
+ "missingNewPassword": "Missing new password.",
+ "wrongPassword": "Wrong password.",
+ "notAnEmail": "Invalid email address.",
+ "emailTaken": "Email address is already used in an account.",
+ "newEmailRequired": "Missing new email address.",
+ "usernameTaken": "Username already taken.",
+ "passwordConfirmationMatch": "Password confirmation doesn't match password.",
+ "invalidLoginCredentials": "Incorrect username and/or email and/or password.",
+ "passwordReset": "If we have your email on file, your password reset link has been sent to your email.",
+ "passwordResetEmailSubject": "Password Reset for Habitica",
+ "passwordResetEmailText": "Password for <%= username %> has been reset to <%= newPassword %> . Important! Both username and password are case-sensitive -- you must enter both exactly as shown here. We recommend copying and pasting both instead of typing them. Log in at <%= baseUrl %>. After you have logged in, head to <%= baseUrl %>/#/options/settings/settings and change your password.",
+ "passwordResetEmailHtml": "Password for <%= username %> has been reset to <%= newPassword %>.
Important! Both username and password are case-sensitive -- you must enter both exactly as shown here. We recommend copying and pasting both instead of typing them.
Log in at <%= baseUrl %>. After you have logged in, head to <%= baseUrl %>/#/options/settings/settings and change your password.",
+ "invalidLoginCredentialsLong": "Uh-oh - your username or password is incorrect.\n- Make sure your username or email is typed correctly.\n- You may have signed up with Facebook, not email. Double-check by trying Facebook login.\n- If you forgot your password, click \"Forgot Password\".",
+ "invalidCredentials": "There is no account that uses those credentials.",
+ "accountSuspended": "Account has been suspended, please contact leslie@habitica.com with your User ID \"<%= userId %>\" for assistance.",
+ "onlyFbSupported": "Only Facebook is supported currently.",
+ "cantDetachFb": "Account lacks another authentication method, can't detach Facebook.",
+ "onlySocialAttachLocal": "Local authentication can be added to only a social account.",
+ "invalidReqParams": "Invalid request parameters.",
+ "memberIdRequired": "\"member\" must be a valid UUID.",
+ "heroIdRequired": "\"heroId\" must be a valid UUID."
}
\ No newline at end of file
diff --git a/common/locales/ro/gear.json b/common/locales/ro/gear.json
index ec334408c3..7f85401369 100644
--- a/common/locales/ro/gear.json
+++ b/common/locales/ro/gear.json
@@ -189,6 +189,8 @@
"weaponArmoireMiningPickaxNotes": "Mine the maximum amount of gold from your tasks! Increases Perception by <%= per %>. Enchanted Armoire: Miner Set (Item 3 of 3).",
"weaponArmoireBasicLongbowText": "Basic Longbow",
"weaponArmoireBasicLongbowNotes": "A serviceable hand-me-down bow. Increases Strength by <%= str %>. Enchanted Armoire: Basic Archer Set (Item 1 of 3).",
+ "weaponArmoireHabiticanDiplomaText": "Habitican Diploma",
+ "weaponArmoireHabiticanDiplomaNotes": "A certificate of significant achievement -- well done! Increases Intelligence by <%= int %>. Enchanted Armoire: Graduate Set (Item 1 of 3).",
"armor": "armură",
"armorBase0Text": "Îmbrăcăminte simplă",
"armorBase0Notes": "Haine obișnuite. Nu conferă niciun beneficiu.",
@@ -364,6 +366,10 @@
"armorMystery201512Notes": "Summon the icy flames of winter! Confers no benefit. December 2015 Subscriber Item.",
"armorMystery201603Text": "Lucky Suit",
"armorMystery201603Notes": "This suit is sewn from thousands of four-leafed clovers! Confers no benefit. March 2016 Subscriber Item.",
+ "armorMystery201604Text": "Armor o' Leaves",
+ "armorMystery201604Notes": "You, too, can be a small but fearsome leaf puff. Confers no benefit. April 2016 Subscriber Item.",
+ "armorMystery201605Text": "Marching Bard Uniform",
+ "armorMystery201605Notes": "Unlike the traditional bards who join adventuring parties, bards who join Habitican marching bands are known for grand parades, not dungeon raids. Confers no benefit. May 2016 Subscriber Item.",
"armorMystery301404Text": "Steampunk Suit",
"armorMystery301404Notes": "Dapper and dashing, wot! Confers no benefit. February 3015 Subscriber Item.",
"armorArmoireLunarArmorText": "Soothing Lunar Armor",
@@ -394,6 +400,8 @@
"armorArmoireMinerOverallsNotes": "They may seem worn, but they are enchanted to repel dirt. Increases Constitution by <%= con %>. Enchanted Armoire: Miner Set (Item 2 of 3).",
"armorArmoireBasicArcherArmorText": "Basic Archer Armor",
"armorArmoireBasicArcherArmorNotes": "This camouflaged vest lets you slip unnoticed through the forests. Increases Perception by <%= per %>. Enchanted Armoire: Basic Archer Set (Item 2 of 3).",
+ "armorArmoireGraduateRobeText": "Graduate Robe",
+ "armorArmoireGraduateRobeNotes": "Congratulations! This weighty robe hangs heavy with all the knowledge you have accrued. Increases Intelligence by <%= int %>. Enchanted Armoire: Graduate Set (Item 2 of 3).",
"headgear": "echipament pentru cap",
"headBase0Text": "Fără cască",
"headBase0Notes": "Fără echipament pentru cap",
@@ -565,6 +573,10 @@
"headMystery201602Notes": "Shield your identity from all your admirers. Confers no benefit. February 2016 Subscriber Item.",
"headMystery201603Text": "Lucky Hat",
"headMystery201603Notes": "This top hat is a magical good-luck charm. Confers no benefit. March 2016 Subscriber Item.",
+ "headMystery201604Text": "Crown o' Flowers",
+ "headMystery201604Notes": "These woven flowers make a surprisingly strong helm! Confers no benefit. April 2016 Subscriber Item.",
+ "headMystery201605Text": "Marching Bard Hat",
+ "headMystery201605Notes": "Seventy-six dragons led the big parade, with a hundred and ten gryphons close at hand! Confers no benefit. May 2016 Subscriber Item.",
"headMystery301404Text": "Fancy Top Hat",
"headMystery301404Notes": "A fancy top hat for the finest of gentlefolk! January 3015 Subscriber Item. Confers no benefit.",
"headMystery301405Text": "Basic Top Hat",
@@ -613,6 +625,8 @@
"headArmoireMinerHelmetNotes": "Protect your head from falling tasks! Increases Intelligence by <%= int %>. Enchanted Armoire: Miner Set (Item 1 of 3).",
"headArmoireBasicArcherCapText": "Basic Archer Cap",
"headArmoireBasicArcherCapNotes": "No archer would be complete without a jaunty cap! Increases Perception by <%= per %>. Enchanted Armoire: Basic Archer Set (Item 3 of 3).",
+ "headArmoireGraduateCapText": "Graduate Cap",
+ "headArmoireGraduateCapNotes": "Congratulations! Your deep thoughts have earned you this thinking cap. Increases Intelligence by <%= int %>. Enchanted Armoire: Graduate Set (Item 3 of 3).",
"offhand": "shield-hand item",
"shieldBase0Text": "No Shield-Hand Equipment",
"shieldBase0Notes": "Niciun scut sau a doua armă",
@@ -673,7 +687,7 @@
"shieldSpecialWinter2015HealerText": "Soothing Shield",
"shieldSpecialWinter2015HealerNotes": "This shield deflects the freezing wind. Increases Constitution by <%= con %>. Limited Edition 2014-2015 Winter Gear.",
"shieldSpecialSpring2015RogueText": "Exploding Squeak",
- "shieldSpecialSpring2015RogueNotes": "Don't let the sound fool you - these explosives pack a punch. Increases Strength <%= str %>. Limited Edition 2015 Spring Gear.",
+ "shieldSpecialSpring2015RogueNotes": "Don't let the sound fool you - these explosives pack a punch. Increases Strength by <%= str %>. Limited Edition 2015 Spring Gear.",
"shieldSpecialSpring2015WarriorText": "Dish Discus",
"shieldSpecialSpring2015WarriorNotes": "Hurl it at your enemies.... or just hold it, because it will fill up with yummy kibble at dinnertime. Increases Constitution by <%= con %>. Limited Edition 2015 Spring Gear.",
"shieldSpecialSpring2015HealerText": "Patterned Pillow",
@@ -716,6 +730,8 @@
"shieldArmoireDragonTamerShieldNotes": "Distract enemies with this dragon-shaped shield. Increases Perception by <%= per %>. Enchanted Armoire: Dragon Tamer Set (Item 2 of 3).",
"shieldArmoireMysticLampText": "Mystic Lamp",
"shieldArmoireMysticLampNotes": "Light the darkest caves with this mystic lamp! Increases Perception by <%= per %>. Enchanted Armoire: Independent Item.",
+ "shieldArmoireFloralBouquetText": "Bouquet o' Flowers",
+ "shieldArmoireFloralBouquetNotes": "Not much help in battle, but aren't they beautiful? Increases Constitution by <%= con %>. Enchanted Armoire: Independent Item.",
"back": "Back Accessory",
"backBase0Text": "Niciun accesoriu pentru spate",
"backBase0Notes": "Niciun accesoriu pentru spate",
@@ -820,6 +836,20 @@
"eyewear": "Eyewear",
"eyewearBase0Text": "No Eyewear",
"eyewearBase0Notes": "No Eyewear.",
+ "eyewearSpecialBlackTopFrameText": "Black Standard Eyeglasses",
+ "eyewearSpecialBlackTopFrameNotes": "Glasses with a black frame above the lenses. Confers no benefit.",
+ "eyewearSpecialBlueTopFrameText": "Blue Standard Eyeglasses",
+ "eyewearSpecialBlueTopFrameNotes": "Glasses with a blue frame above the lenses. Confers no benefit.",
+ "eyewearSpecialGreenTopFrameText": "Green Standard Eyeglasses",
+ "eyewearSpecialGreenTopFrameNotes": "Glasses with a green frame above the lenses. Confers no benefit.",
+ "eyewearSpecialPinkTopFrameText": "Pink Standard Eyeglasses",
+ "eyewearSpecialPinkTopFrameNotes": "Glasses with a pink frame above the lenses. Confers no benefit.",
+ "eyewearSpecialRedTopFrameText": "Red Standard Eyeglasses",
+ "eyewearSpecialRedTopFrameNotes": "Glasses with a red frame above the lenses. Confers no benefit.",
+ "eyewearSpecialWhiteTopFrameText": "White Standard Eyeglasses",
+ "eyewearSpecialWhiteTopFrameNotes": "Glasses with a white frame above the lenses. Confers no benefit.",
+ "eyewearSpecialYellowTopFrameText": "Yellow Standard Eyeglasses",
+ "eyewearSpecialYellowTopFrameNotes": "Glasses with a yellow frame above the lenses. Confers no benefit.",
"eyewearSpecialSummerRogueText": "Roguish Eyepatch",
"eyewearSpecialSummerRogueNotes": "It doesn't take a scallywag to see how stylish this is! Confers no benefit. Limited Edition 2014 Summer Gear.",
"eyewearSpecialSummerWarriorText": "Dashing Eyepatch",
diff --git a/common/locales/ro/groups.json b/common/locales/ro/groups.json
index 23d46f4996..89946fc398 100644
--- a/common/locales/ro/groups.json
+++ b/common/locales/ro/groups.json
@@ -92,6 +92,7 @@
"send": "Send",
"messageSentAlert": "Message sent",
"pmHeading": "Private message to <%= name %>",
+ "pmsMarkedRead": "Your private messages have been marked as read",
"clearAll": "Delete All Messages",
"confirmDeleteAllMessages": "Are you sure you want to delete all messages in your inbox? Other users will still see messages you have sent to them.",
"optOutPopover": "Don't like private messages? Click to completely opt out",
@@ -99,6 +100,15 @@
"unblock": "Un-block",
"pm-reply": "Send a reply",
"inbox": "Inbox",
+ "messageRequired": "A message is required.",
+ "toUserIDRequired": "A User ID is required",
+ "gemAmountRequired": "A number of gems is required",
+ "notAuthorizedToSendMessageToThisUser": "Can't send message to this user.",
+ "privateMessageGiftIntro": "Hello <%= receiverName %>, <%= senderName %> has sent you",
+ "privateMessageGiftGemsMessage": "<%= gemAmount %> gems!",
+ "privateMessageGiftSubscriptionMessage": "<%= numberOfMonths %> months of subscription!",
+ "cannotSendGemsToYourself": "Cannot send gems to yourself. Try a subscription instead.",
+ "badAmountOfGemsToSend": "Amount must be within 1 and your current number of gems.",
"abuseFlag": "Report violation of Community Guidelines",
"abuseFlagModalHeading": "Report <%= name %> for violation?",
"abuseFlagModalBody": "Are you sure you want to report this post? You should ONLY report a post that violates the <%= firstLinkStart %>Community Guidelines<%= linkEnd %> and/or <%= secondLinkStart %>Terms of Service<%= linkEnd %>. Inappropriately reporting a post is a violation of the Community Guidelines and may give you an infraction. Appropriate reasons to flag a post include but are not limited to:
swearing, religous oaths
bigotry, slurs
adult topics
violence, including as a joke
spam, nonsensical messages
",
@@ -151,5 +161,29 @@
"partyUpName": "Party Up",
"partyOnName": "Party On",
"partyUpAchievement": "Joined a Party with another person! Have fun battling monsters and supporting each other.",
- "partyOnAchievement": "Joined a Party with at least four people! Enjoy your increased accountability as you unite with your friends to vanquish your foes!"
+ "partyOnAchievement": "Joined a Party with at least four people! Enjoy your increased accountability as you unite with your friends to vanquish your foes!",
+ "groupIdRequired": "\"groupId\" must be a valid UUID",
+ "groupNotFound": "Group not found.",
+ "groupTypesRequired": "You must supply a valid \"type\" query string.",
+ "questLeaderCannotLeaveGroup": "You cannot leave your party when you have started a quest. Abort the quest first.",
+ "cannotLeaveWhileActiveQuest": "You cannot leave party during an active quest. Please leave the quest first.",
+ "onlyLeaderCanRemoveMember": "Only group leader can remove a member!",
+ "memberCannotRemoveYourself": "You cannot remove yourself!",
+ "groupMemberNotFound": "User not found among group's members",
+ "mustBeGroupMember": "Must be member of the group.",
+ "keepOrRemoveAll": "req.query.keep must be either \"keep-all\" or \"remove-all\"",
+ "keepOrRemove": "req.query.keep must be either \"keep\" or \"remove\"",
+ "canOnlyInviteEmailUuid": "Can only invite using uuids or emails.",
+ "inviteMissingEmail": "Missing email address in invite.",
+ "partyMustbePrivate": "Parties must be private",
+ "userAlreadyInGroup": "User already in that group.",
+ "userAlreadyInvitedToGroup": "User already invited to that group.",
+ "userAlreadyPendingInvitation": "User already pending invitation.",
+ "userAlreadyInAParty": "User already in a party.",
+ "userWithIDNotFound": "User with id \"<%= userId %>\" not found.",
+ "userHasNoLocalRegistration": "User does not have a local registration (username, email, password).",
+ "uuidsMustBeAnArray": "User ID invites must be an array.",
+ "emailsMustBeAnArray": "Email address invites must be an array.",
+ "canOnlyInviteMaxInvites": "You can only invite \"<%= maxInvites %>\" at a time",
+ "onlyCreatorOrAdminCanDeleteChat": "Not authorized to delete this message!"
}
\ No newline at end of file
diff --git a/common/locales/ro/limited.json b/common/locales/ro/limited.json
index dc21fa9fa7..279543e61f 100644
--- a/common/locales/ro/limited.json
+++ b/common/locales/ro/limited.json
@@ -5,7 +5,7 @@
"annoyingFriends": "Prieteni enervanți",
"annoyingFriendsText": "Lovit de <%= snowballs %> cu bulgări de zăpadă de coechipieri.",
"alarmingFriends": "Prieteni Alarmanți",
- "alarmingFriendsText": "Speriat de <%= spookDust %> ori de colegii din echipă",
+ "alarmingFriendsText": "Got spooked <%= spookySparkles %> times by party members.",
"agriculturalFriends": "Prieteni agricoli",
"agriculturalFriendsText": "Am fost transformat(ă) într-o floare de <%= seeds %> ori de coechipieri.",
"aquaticFriends": "Prieteni acvatici",
@@ -72,5 +72,6 @@
"comfortingKittySet": "Comforting Kitty (Healer)",
"sneakySqueakerSet": "Sneaky Squeaker (Rogue)",
"fallEventAvailability": "Available until October 31",
- "winterEventAvailability": "Available until December 31"
+ "winterEventAvailability": "Available until December 31",
+ "springEventAvailability": "Available until May 31"
}
\ No newline at end of file
diff --git a/common/locales/ro/maintenance.json b/common/locales/ro/maintenance.json
new file mode 100644
index 0000000000..efdb524cd2
--- /dev/null
+++ b/common/locales/ro/maintenance.json
@@ -0,0 +1,34 @@
+{
+ "habiticaBackSoon": "Don't worry, Habitica will be back soon!",
+ "importantMaintenance": "We are doing important maintenance that we estimate will last until <%= localDate %> in your timezone.",
+ "maintenance": "Maintenance",
+ "maintenanceMoreInfo": "Want more information about the maintenance? <%= linkStart %>Check out our info page<%= linkEnd %>.",
+ "noDamageKeepStreaks": "You will NOT take damage or lose streaks!",
+ "thanksForPatience": "Thanks for your patience!",
+ "twitterMaintenanceUpdates": "For the most recent updates, watch our Twitter, where we will be posting status information.",
+ "veteranPetAward": "At the end, you will receive a Veteran pet!",
+
+ "maintenanceInfoTitle": "Information about Upcoming Maintenance to Habitica",
+ "maintenanceInfoWhat": "What is happening?",
+ "maintenanceInfoWhatText": "On May 21, Habitica will be down for maintenance for most of the day. You will not take any damage or have your account harmed during that weekend, even if you can’t log in to check off your Dailies in time! We will be working very hard to make the downtime as short as possible, and will be posting regular updates on our Twitter account. At the end of the downtime, to thank everyone for their patience, you will all receive a rare pet!",
+ "maintenanceInfoWhy": "Why is this happening?",
+ "maintenanceInfoWhyText": "For the past several months, we have been thoroughly revamping Habitica behind-the-scenes. Specifically, we have rewritten the API. While it may not look much different on the surface, it’s a whole new world underneath. This will allow us WAY more flexibility when we want to build features in the future, and lead to improved performance!",
+ "maintenanceInfoTechDetails": "Want more details on the technical side of the process? Visit The Forge, our dev blog.",
+ "maintenanceInfoMore": "More Information",
+ "maintenanceInfoAccountChanges": "What changes will I see to my account after the rewrite is complete?",
+ "maintenanceInfoAccountChangesText": "At first, there won’t be any notable changes aside from performance improvements for features such as Challenges. If you notice any changes that shouldn’t be there, email us at admin@habitica.com and we will investigate them for you!",
+ "maintenanceInfoAddFeatures": "What kind of features will this allow Habitica to add?",
+ "maintenanceInfoAddFeaturesText": "Completing this rewrite will allow us to start building out improved chat and Guilds, plans for organizations and families, and additional productivity features like Monthlies and the ability to record yesterday’s activity! Those are all involved features on their own, so it will take time to build them, but until we were finished with this rewrite, there was no way we could start them.",
+ "maintenanceInfoHowLong": "How long will the maintenance take?",
+ "maintenanceInfoHowLongText": "We have to migrate tasks and data for all 1.3 million Habitica users -- not an easy task! We anticipate that it will take place between approximately 1pm Pacific Time (8pm UTC) and 10pm Pacific Time (5am UTC). Rest assured that we’re doing everything we can to make it go as quickly as possible! You can follow updates on our Twitter.",
+ "maintenanceInfoStatsAffected": "How will my Dailies, Streaks, Buffs, and Quests be affected?",
+ "maintenanceInfoStatsAffectedText1": "You will NOT take any damage or lose any streaks that weekend, but otherwise, your day will reset normally! Dailies that you checked will become unchecked, buffs will reset, etc. If you are in a Collection Quest, you will still find items. If you are in a Boss Battle, you will still deal damage to the Boss, but the Boss will not deal damage to you. (Even monsters need a break!)",
+ "maintenanceInfoStatsAffectedText2": "After a lot of thought, our team concluded that this was the most fair way to handle the fact that many users will not be able to check off their Dailies normally during the maintenance. We’re sorry for any inconvenience this causes!",
+ "maintenanceInfoSeeTasks": "What if I need to see my task list?",
+ "maintenanceInfoSeeTasksText": "If you know that you will need to see your task list on Saturday to remind yourself what you have to do, we recommend that before the maintenance begins, you take a screenshot of your tasks so that you can use it as a reference.",
+ "maintenanceInfoRarePet": "What kind of rare pet will I receive?",
+ "maintenanceInfoRarePetText": "To thank you for your patience during the downtime, everyone will get a rare Veteran Pet. If you’ve never received a Veteran Pet before, you will receive a Veteran Wolf. If you already have a Veteran Wolf, you will receive a Veteran Tiger. And if you already have a Veteran Wolf and a Veteran Tiger, you will receive a never-before-seen Veteran pet! After the migration is completed, it may take several hours for your pet to show up, but never fear, everyone will get one.",
+ "maintenanceInfoWho": "Who worked on this massive project?",
+ "maintenanceInfoWhoText": "We’re glad you asked! It was spearheaded by our amazing contributor paglias, with lots of help from Blade, TheHollidayInn, SabreCat, Victor Pudeyev, TheUnknown, and Alys.",
+ "maintenanceInfoTesting": "The new version was also tirelessly tested by a bunch of our amazing open-source volunteers. Thank you -- we couldn't have done this without you."
+}
diff --git a/common/locales/ro/npc.json b/common/locales/ro/npc.json
index b9100c7235..43c008c412 100644
--- a/common/locales/ro/npc.json
+++ b/common/locales/ro/npc.json
@@ -21,6 +21,26 @@
"ian": "Ian",
"ianText": "Welcome to the Quest Shop! Here you can use Quest Scrolls to battle monsters with your friends. Be sure to check out our fine array of Quest Scrolls for purchase on the right!",
"ianBrokenText": "Welcome to the Quest Shop... Here you can use Quest Scrolls to battle monsters with your friends... Be sure to check out our fine array of Quest Scrolls for purchase on the right...",
+ "missingKeyParam": "\"req.params.key\" is required.",
+ "itemNotFound": "Item \"<%= key %>\" not found.",
+ "cannotBuyItem": "You can't buy this item.",
+ "missingTypeKeyEquip": "\"key\" and \"type\" are required parameters.",
+ "missingPetFoodFeed": "\"pet\" and \"food\" are required parameters.",
+ "invalidPetName": "Invalid pet name supplied.",
+ "missingEggHatchingPotionHatch": "\"egg\" and \"hatchingPotion\" are required parameters.",
+ "invalidTypeEquip": "\"type\" must be one of 'equipped', 'pet', 'mount', 'costume'.",
+ "mustPurchaseToSet": "Must purchase <%= val %> to set it on <%= key %>.",
+ "typeRequired": "Type is required",
+ "keyRequired": "Key is required",
+ "notAccteptedType": "Type must be in [eggs, hatchingPotions, food, quests, gear]",
+ "contentKeyNotFound": "Key not found for Content <%= type %>",
+ "plusOneGem": "+1 Gem",
+ "typeNotSellable": "Type is not sellable. Must be one of the following <%= acceptedTypes %>",
+ "userItemsKeyNotFound": "Key not found for user.items <%= type %>",
+ "pathRequired": "Path string is required",
+ "unlocked": "Items have been unlocked",
+ "alreadyUnlocked": "Full set already unlocked.",
+ "alreadyUnlockedPart": "Full set already partially unlocked.",
"USD": "(USD)",
"newStuff": "Chestii noi",
"cool": "Amintește-mi mai târziu",
@@ -64,6 +84,7 @@
"tourPetsPage": "This is the Stable! After level 4, you can hatch pets using eggs and potions. When you hatch a pet in the Market, it will appear here! Click a pet's image to add it to your avatar. Feed them with the food you find after level 4, and they'll grow into powerful mounts.",
"tourMountsPage": "Once you've fed a pet enough food to turn it into a mount, it will appear here. (Pets, mounts, and food are available after level 4.) Click a mount to saddle up!",
"tourEquipmentPage": "This is where your Equipment is stored! Your Battle Gear affects your stats. If you want to show different Equipment on your avatar without changing your stats, click \"Enable Costume.\"",
+ "equipmentAlreadyOwned": "You already own that piece of equipment",
"tourOkay": "Okay!",
"tourAwesome": "Awesome!",
"tourSplendid": "Splendid!",
diff --git a/common/locales/ro/pets.json b/common/locales/ro/pets.json
index 2c66083a51..3d61dbc10b 100644
--- a/common/locales/ro/pets.json
+++ b/common/locales/ro/pets.json
@@ -12,6 +12,7 @@
"etherealLion": "Leu eteric",
"veteranWolf": "Lup veteran",
"veteranTiger": "Veteran Tiger",
+ "veteranLion": "Veteran Lion",
"cerberusPup": "Pui de Cerber",
"hydra": "Hidră",
"mantisShrimp": "Crevete călugăr",
@@ -19,7 +20,7 @@
"orca": "Orca",
"royalPurpleGryphon": "Royal Purple Gryphon",
"phoenix": "Phoenix",
- "bumblebee": "Bumblebee",
+ "magicalBee": "Magical Bee",
"rarePetPop1": "Apasă pe laba de aur pentru a afla mai multe despre cum poți obține acest companion rar contribuind la Habitica!",
"rarePetPop2": "Cum să primești acest companion",
"potion": "Poțiune <%= potionType %>",
@@ -62,6 +63,7 @@
"hatchedPet": "You hatched a <%= potion %> <%= egg %>!",
"displayNow": "Display Now",
"displayLater": "Display Later",
+ "petNotOwned": "You do not own this pet.",
"earnedCompanion": "With all your productivity, you've earned a new companion. Feed it to make it grow!",
"feedPet": "Hrănești <%= name %> cu <%= article %><%= text %>?",
"useSaddle": "Înșeuezi <%= pet %>?",
@@ -83,5 +85,8 @@
"petKeyBoth": "Release Both",
"confirmPetKey": "Are you sure?",
"petKeyNeverMind": "Încă nu",
+ "petsReleased": "Pets released.",
+ "mountsAndPetsReleased": "Mounts and pets released",
+ "mountsReleased": "Mounts released",
"gemsEach": "nestemate fiecare"
}
\ No newline at end of file
diff --git a/common/locales/ro/quests.json b/common/locales/ro/quests.json
index 1d7cb519a7..eb553d12bf 100644
--- a/common/locales/ro/quests.json
+++ b/common/locales/ro/quests.json
@@ -78,5 +78,24 @@
"whichQuestStart": "Which quest do you want to start?",
"getMoreQuests": "Get more quests",
"unlockedAQuest": "You unlocked a quest!",
- "leveledUpReceivedQuest": "You leveled up to Level <%= level %> and received a quest scroll!"
+ "leveledUpReceivedQuest": "You leveled up to Level <%= level %> and received a quest scroll!",
+ "questInvitationDoesNotExist": "No quest invitation has been sent out yet.",
+ "questInviteNotFound": "No quest invitation found.",
+ "guildQuestsNotSupported": "Guilds cannot be invited on quests.",
+ "questNotFound": "Quest \"<%= key %>\" not found.",
+ "questNotOwned": "You don't own that quest scroll.",
+ "questNotGoldPurchasable": "Quest \"<%= key %>\" is not a Gold-purchasable quest.",
+ "questLevelTooHigh": "You must be level <%= level %> to begin this quest.",
+ "questAlreadyUnderway": "Your party is already on a quest. Try again when the current quest has ended.",
+ "questAlreadyAccepted": "You already accepted the quest invitation.",
+ "noActiveQuestToLeave": "No active quest to leave",
+ "questLeaderCannotLeaveQuest": "Quest leader cannot leave quest",
+ "notPartOfQuest": "You are not part of the quest",
+ "noActiveQuestToAbort": "There is no active quest to abort.",
+ "onlyLeaderAbortQuest": "Only the group or quest leader can abort a quest.",
+ "questAlreadyRejected": "You already rejected the quest invitation.",
+ "cantCancelActiveQuest": "You can not cancel an active quest, use the abort functionality.",
+ "onlyLeaderCancelQuest": "Only the group or quest leader can cancel the quest.",
+ "questNotPending": "There is no quest to start.",
+ "questOrGroupLeaderOnlyStartQuest": "Only the quest leader or group leader can force start the quest"
}
\ No newline at end of file
diff --git a/common/locales/ro/questscontent.json b/common/locales/ro/questscontent.json
index c4d8a77f73..bc1231fcfc 100644
--- a/common/locales/ro/questscontent.json
+++ b/common/locales/ro/questscontent.json
@@ -304,9 +304,21 @@
"questBewilderCompletionChat": "`The Be-Wilder is DEFEATED!`\n\nWe've done it! The Be-Wilder lets out a ululating cry as it twists in the air, shedding feathers like falling rain. Slowly, gradually, it coils into a cloud of sparkling mist. As the newly-revealed sun pierces the fog, it burns away, revealing the coughing, mercifully human forms of Bailey, Matt, Alex.... and the April Fool himself.\n\n`Mistiflying is saved!`\n\nThe April Fool has enough shame to look a bit sheepish. “Oh, hm,” he says. “Perhaps I got a little…. carried away.”\n\nThe crowd mutters. Sodden flowers wash up on sidewalks. Somewhere in the distance, a roof collapses with a spectacular splash.\n\n“Er, yes,” the April Fool says. “That is. What I meant to say was, I’m dreadfully sorry.” He heaves a sigh. “I suppose it can’t all be fun and games, after all. It might not hurt to focus occasionally. Maybe I’ll get a head start on next year’s pranking.”\n\nRedphoenix coughs meaningfully.\n\n“I mean, get a head start on this year’s spring cleaning!” the April Fool says. “Nothing to fear, I’ll have Habit City in spit-shape soon. Luckily nobody is better than I at dual-wielding mops.”\n\nEncouraged, the marching band starts up.\n\nIt isn’t long before all is back to normal in Habit City. Plus, now that the Be-Wilder has evaporated, the magical bees of Mistiflying bustle back to work, and soon the flowers are blooming and the city is floating once more.\n\nAs Habiticans cuddle the magical fuzzy bees, the April Fool’s eyes light up. “Oho, I’ve had a thought! Why don’t you all keep some of these fuzzy Bee Pets and Mounts? It’s a gift that perfectly symbolizes the balance between hard work and sweet rewards, if I’m going to get all boring and allegorical on you.” He winks. “Besides, they don’t have stingers! Fool’s honor.”",
"questBewilderBossRageTitle": "Beguilement Strike",
"questBewilderBossRageDescription": "When this gauge fills, The Be-Wilder will unleash its Beguilement Strike on Habitica!",
- "questBewilderDropBumblebeePet": "Bumblebee (Pet)",
- "questBewilderDropBumblebeeMount": "Bumblebee (Mount)",
+ "questBewilderDropBumblebeePet": "Magical Bee (Pet)",
+ "questBewilderDropBumblebeeMount": "Magical Bee (Mount)",
"questBewilderBossRageMarket": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nOh no! Despite our best efforts, we've gotten distracted by the Be-Wilder’s charming illusions and have forgotten to do some of our Dailies! With a cackling cry, the shining bird beats its wings, raising a swarm of mist around Alex the Merchant. When the fog clears, he has been possessed! “Have some free samples!” he shouts gleefully, and begins to hurl exploding eggs and potions at fleeing Habiticans. Not the most favorable of sales, to be sure.\n\nHurry! Let's stay focused on our Dailies to defeat this monster before it possesses someone else.",
"questBewilderBossRageStables": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nAhh!!! Once again the Be-Wilder has dazzled us into neglecting our Dailies, and now it has attacked Matt the Beast Master! With a swirl of mist, Matt transforms into a terrifying winged creature, and all the pets and mounts howl sadly in their stables. Quickly, stay focused on your tasks to defeat this dastardly distraction!",
- "questBewilderBossRageBailey": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nLook out! In the middle of reporting the news, Bailey the Town Crier has been possessed by the Be-Wilder! She lets out an evil, uninformative screech as she rises into the air. Now how will we know what’s going on?\n\nDon't give up... we're so close to defeating this bothersome bird for once and for all!"
+ "questBewilderBossRageBailey": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nLook out! In the middle of reporting the news, Bailey the Town Crier has been possessed by the Be-Wilder! She lets out an evil, uninformative screech as she rises into the air. Now how will we know what’s going on?\n\nDon't give up... we're so close to defeating this bothersome bird for once and for all!",
+ "questFalconText": "The Birds of Preycrastination",
+ "questFalconNotes": "Mt. Habitica is being overshadowed by a looming mountain of To-Dos. It used to be a place to picnic and enjoy a sense of accomplishment, until the neglected tasks grew out of control. Now it's home to fearsome Birds of Preycrastination, foul creatures which stop Habiticans from completing their tasks!
\"It's too hard!\" they caw at @JonArinbjorn and @Onheiron. \"It'll take too long to do right now! It won't make any difference if you wait until tomorrow! Why don't you do something fun instead?\"
No more, you vow. You will climb your personal mountain of To-Dos and defeat the Birds of Preycrastination!",
+ "questFalconCompletion": "Having finally triumphed over the Birds of Preycrastination, you settle down to enjoy the view and your well-earned rest.
\"Wow!\" says @Trogdorina. \"You won!\"
@Squish adds, \"Here, take these eggs I found as a reward.\"",
+ "questFalconBoss": "Birds of Preycrastination",
+ "questFalconDropFalconEgg": "Falcon (Egg)",
+ "questFalconUnlockText": "Unlocks purchasable Falcon eggs in the Market",
+ "questTreelingText": "The Tangle Tree",
+ "questTreelingNotes": "It's the annual Garden Competition, and everyone is talking about the mysterious project which @aurakami has promised to unveil. You join the crowd on the day of the big announcement, and marvel at the introduction of a moving tree. @fuzzytrees explains that the tree will help with garden maintenance, showing how it can mow the lawn, trim the hedge and prune the roses all at the same time – until the tree suddenly goes wild, turning its secateurs on its creator! The crowd panics as everyone tries to flee, but you aren't afraid – you leap forward, ready to do battle.",
+ "questTreelingCompletion": "You dust yourself off as the last few leaves drift to the floor. In spite of the upset, the Garden Competition is now safe – although the tree you just reduced to a heap of wood chips won't be winning any prizes! \"Still a few kinks to work out there,\" @PainterProphet says. \"Perhaps someone else would do a better job of training the saplings. Do you fancy a go?\"",
+ "questTreelingBoss": "Tangle Tree",
+ "questTreelingDropTreelingEgg": "Treeling (Egg)",
+ "questTreelingUnlockText": "Unlocks purchasable Treeling eggs in the Market"
}
\ No newline at end of file
diff --git a/common/locales/ro/rebirth.json b/common/locales/ro/rebirth.json
index 43d4c22fdb..383f8a1533 100644
--- a/common/locales/ro/rebirth.json
+++ b/common/locales/ro/rebirth.json
@@ -5,7 +5,7 @@
"rebirthStartOver": "Renaștere repornește personajul de la Nivelul 1.",
"rebirthAdvList1": "Ai revenit la Sănătate deplină.",
"rebirthAdvList2": "You have no Experience, Gold, or Equipment (with the exception of free items like Mystery items).",
- "rebirthAdvList3": "Obiceiurile, Cotidienele și Sarcinile sunt resetate la galben, iar șirurile se resetează.",
+ "rebirthAdvList3": "Your Habits, Dailies, and To-Dos reset to yellow, and streaks reset, except for challenge tasks.",
"rebirthAdvList4": "Ai clasa de început a Războinicului până când vei câștiga o altă clasă.",
"rebirthInherit": "Noul tău personaj va moșteni unele lucruri de la predecesorul lui:",
"rebirthInList1": "Țelurile, istoricul și setările rămân.",
@@ -24,5 +24,6 @@
"rebirthPop": "Începe un nou personaj la Nivelul 1 păstrând realizările, obiectele de colecție și țelurile cu istoric.",
"rebirthName": "Globul Renașterii",
"reborn": "Renăscut, nivel maxim <%= reLevel %>",
- "confirmReborn": "Are you sure?"
+ "confirmReborn": "Are you sure?",
+ "rebirthComplete": "You have been reborn!"
}
\ No newline at end of file
diff --git a/common/locales/ro/settings.json b/common/locales/ro/settings.json
index 3d32a126f9..cad9e6df41 100644
--- a/common/locales/ro/settings.json
+++ b/common/locales/ro/settings.json
@@ -47,6 +47,7 @@
"customDayStart": "Când începe ziua",
"changeCustomDayStart": "Dorești schimbarea orei de început a zilei?",
"sureChangeCustomDayStart": "Sigur dorești schimbarea orei de început a zilei?",
+ "customDayStartHasChanged": "Your custom day start has changed.",
"nextCron": "Sarcinile tale zilnice se vor reseta prima dată când folosești Habitica după ora <%= time %>. Ai grijă să completezi sarcinile zilnice înainte de această oră!",
"customDayStartInfo1": "Habitica defaults to check and reset your Dailies at midnight in your own time zone each day. You can customize that time here.",
"misc": "Altele",
@@ -61,12 +62,23 @@
"newUsername": "New Login Name",
"dangerZone": "Zona de pericol",
"resetText1": "ATENȚIE! Aceasta îți va reseta multe setari ale contului. Este foarte nerecomandat dar totuși unii utilizatori găsesc aceasta resetare folositoare la început după ce s-au distrat cu site-ul pentru puțin timp.",
- "resetText2": "Vei pierde toate nivelurile, aurul și punctele de exepriență. Toate sarcinile vor fi șterse permanent și vei pierde și istoricul lor. Vei pierde tot echipamentul dar îl vei putea cumpăra din nou incluzând toate edițiile limitate say itemurile misterioase de abonat pe care deja le dețineai (va trebui să fi setat pe clasa respectivului echipament specific). Vei păstra clasa curentă și companionii împreună cu animalele de călărit. Poate preferi să utilizezi Globul Renașterii care este o opțiune mai sigură și care-ți păstrează sarcinile curente.",
+ "resetText2": "You will lose all your levels, gold, and experience points. All your tasks (except those from challenges) will be deleted permanently and you will lose all of their historical data. You will lose all your equipment but you will be able to buy it all back, including all limited edition equipment or subscriber Mystery items that you already own (you will need to be in the correct class to re-buy class-specific gear). You will keep your current class and your pets and mounts. You might prefer to use an Orb of Rebirth instead, which is a much safer option and which will preserve your tasks.",
"deleteText": "Ești sigur(ă)? Asta îți va șterge contul pentru totdeauna și nu va mai putea fi niciodată restaurat! Va trebui să înregistrezi un nou cont pentru a folosi Habitica din nou. Nestematele deținute sau cheltuite nu vor fi restituite. Dacă ești absolut sigur(ă), tastează <%= deleteWord %> în căsuța pentru text de mai jos.",
"API": "API",
+ "APIv3": "API v3",
+ "APIv2": "API v2 - Deprecated",
"APIText": "Folosește acestea la cererea unor aplicații externe. Totuși consideră Tokenul API ca o parolă și nu o afișa public. Ocazional ți se va cere ID-ul tău de utilizatăr dar nu afișa Token-ul API nicăieri unde ar putea fi văzut de ceilalți inclusiv în Github.",
"APIToken": "Tokern API (aceasta este ca o parolă - a se vedea atenționarea de mai sus!)",
+ "thirdPartyApps": "Third Party Apps",
+ "dataToolDesc": "A webpage that shows you certain information from your Habitica account, such as statistics about your tasks, equipment, and skills.",
+ "beeminder": "Beeminder",
+ "beeminderDesc": "Let Beeminder automatically monitor your Habitica To-Dos. You can commit to maintaining a target number of To-Dos completed per day or per week, or you can commit to gradually reducing your remaining number of uncompleted To-Dos. (By \"commit\" Beeminder means under threat of paying actual money! But you may also just like Beeminder's fancy graphs.)",
+ "chromeChatExtension": "Chrome Chat Extension",
+ "chromeChatExtensionDesc": "The Chrome Chat Extension for Habitica adds an intuitive chat box to all of habitica.com. It allows users to chat in the Tavern, their party, and any guilds they are in.",
+ "otherExtensions": "Other Extensions",
+ "otherDesc": "Find other apps, extensions, and tools on the Habitica wiki.",
"resetDo": "Fă-o, resetează-mi contul!",
+ "resetComplete": "Reset complete!",
"fixValues": "Aranjează valori",
"fixValuesText1": "Dacă ai găsit o eroare sau ai făcut o greșeală care ți-a schimbat în mod nedrept personajul (vătămări pe care nu trebuia să le fi primit, Aur pe care nu l-ai primit etc.), poți corecta manual numerele aici. Da, asta îți permite să trișezi: folosește facilitatea aceasta cu chibzuință sau îți vei sabota propria încercare de a deprinde obiceiuri!",
"fixValuesText2": "Atenție! Nu poți restabili Șirurile țelurilor individuale aici. Pentru a face asta, modifică Cotidiana și mergi la Opțiuni Avansate, unde vei găsi un câmp de Restabilire Șir.",
@@ -96,6 +108,7 @@
"emailNotifications": "Email Notifications",
"wonChallenge": "You won a Challenge!",
"newPM": "Received Private Message",
+ "sentGems": "Sent gems!",
"giftedGems": "Gifted Gems",
"giftedGemsInfo": "<%= amount %> Gems - by <%= name %>",
"giftedSubscription": "Gifted Subscription",
@@ -135,6 +148,11 @@
"webhooks": "Webhooks",
"enabled": "Enabled",
"webhookURL": "Webhook URL",
+ "invalidUrl": "invalid url",
+ "invalidEnabled": "the \"enabled\" parameter should be a boolean",
+ "regIdRequired": "RegId is required",
+ "pushDeviceAdded": "Push device added successfully",
+ "pushDeviceAlreadyAdded": "The user already has the push device",
"add": "Add",
"buyGemsGoldCap": "Cap raised to <%= amount %>",
"mysticHourglass": "<%= amount %> Mystic Hourglass",
@@ -149,5 +167,5 @@
"amazonPayments": "Amazon Payments",
"timezone": "Time Zone",
"timezoneUTC": "Habitica uses the time zone set on your PC, which is: <%= utc %>",
- "timezoneInfo": "If that time zone is wrong, first reload this page using your browser's reload or refresh button to ensure that Habitica has the most recent information. If it is still wrong, adjust the time zone on your PC and then reload this page again.
If you use Habitica on other PCs or mobile devices, the time zone must be the same on them all. If your Dailies have been reseting at the wrong time, repeat this check on all other PCs and on a browser on your mobile devices."
+ "timezoneInfo": "If that time zone is wrong, first reload this page using your browser's reload or refresh button to ensure that Habitica has the most recent information. If it is still wrong, adjust the time zone on your PC and then reload this page again.
If you use Habitica on other PCs or mobile devices, the time zone must be the same on them all. If your Dailies have been resetting at the wrong time, repeat this check on all other PCs and on a browser on your mobile devices."
}
\ No newline at end of file
diff --git a/common/locales/ro/spells.json b/common/locales/ro/spells.json
index 935d76c41b..a513e6f9cf 100644
--- a/common/locales/ro/spells.json
+++ b/common/locales/ro/spells.json
@@ -35,8 +35,8 @@
"spellSpecialSnowballAuraNotes": "Throw a snowball at a party member! What could possibly go wrong? Lasts until member's new day.",
"spellSpecialSaltText": "Sare",
"spellSpecialSaltNotes": "Cineva te-a lovit cu un bulgăre de zăpadă. Ha ha, foarte amuzant. Acum dă zăpada asta jos de pe mine!",
- "spellSpecialSpookDustText": "Spooky Sparkles",
- "spellSpecialSpookDustNotes": "Turn a friend into a floating blanket with eyes!",
+ "spellSpecialSpookySparklesText": "Spooky Sparkles",
+ "spellSpecialSpookySparklesNotes": "Turn a friend into a floating blanket with eyes!",
"spellSpecialOpaquePotionText": "Poțiune opacă",
"spellSpecialOpaquePotionNotes": "Cancel the effects of Spooky Sparkles.",
"spellSpecialShinySeedText": "Shiny Seed",
@@ -46,5 +46,11 @@
"spellSpecialSeafoamText": "Seafoam",
"spellSpecialSeafoamNotes": "Turn a friend into a sea creature!",
"spellSpecialSandText": "Sand",
- "spellSpecialSandNotes": "Cancel the effects of Seafoam."
+ "spellSpecialSandNotes": "Cancel the effects of Seafoam.",
+ "spellNotFound": "Skill \"<%= spellId %>\" not found.",
+ "partyNotFound": "Party not found",
+ "targetIdUUID": "\"targetId\" must be a valid User ID.",
+ "challengeTasksNoCast": "Casting a skill on challenge tasks is not allowed.",
+ "spellNotOwned": "You don't own this skill.",
+ "spellLevelTooHigh": "You must be level <%= level %> to use this skill."
}
\ No newline at end of file
diff --git a/common/locales/ro/subscriber.json b/common/locales/ro/subscriber.json
index b9bed50ba5..12ecc776df 100644
--- a/common/locales/ro/subscriber.json
+++ b/common/locales/ro/subscriber.json
@@ -4,6 +4,8 @@
"subDescription": "Cumpără nestemate cu aur, obține articole misterioase în fiecare lună, păstrează istoricul progresului, dublează limitele zilnice de picări, sprijină dezvoltatorii. Clic pentru mai multe informații.",
"buyGemsGold": "Cumpără Nestemate cu Aur",
"buyGemsGoldText": "Alexandru Negustoru îți va vinde nestematele la un preț de <%= gemCost %> aur pe nestemată. Livrările sale lunare sunt limitate inițial la <%= gemLimit %> nestemate pe lună, dar această limită crește cu câte 5 nestemate la fiecare trei luni de abonament consecuriv, până la un maxim de 50 de nestemate pe lună!",
+ "mustSubscribeToPurchaseGems": "Must subscribe to purchase gems with GP",
+ "reachedGoldToGemCap": "You've reached the Gold=>Gem conversion cap <%= convCap %> for this month. We have this to prevent abuse / farming. The cap will reset within the first three days of next month.",
"retainHistory": "Reține mai mult din istoric",
"retainHistoryText": "Makes completed To-Dos and task history available for longer.",
"doubleDrops": "Limita maximă de obiecte câștigate este dublată",
@@ -29,6 +31,7 @@
"manageSub": "Apasă ca să gestionezi abonamentul",
"cancelSub": "Anulează abonamentul",
"canceledSubscription": "Anulează abonamentul",
+ "cancelingSubscription": "Canceling the subscription",
"adminSub": "Abonamente de administrator",
"morePlans": "More Plans Coming Soon",
"organizationSub": "Organizație privată",
@@ -73,6 +76,9 @@
"timeTravelersPopover": "We see you have a Mystic Hourglass, so we will happily travel back in time for you! Please choose the pet, mount, or Mystery Item Set you would like. You can see a list of the past item sets here! If those don't satisfy you, perhaps you'd be interested in one of our fashionably futuristic Steampunk Item Sets?",
"timeTravelersAlreadyOwned": "Felicitări! Deja deții tot ce este oferit de călătorii în timp. Mulțumim că susții site-ul!",
"mysticHourglassPopover": "O clepsidră mistică îți permite să cumperi anumite articole disponibile o perioadă limitată, cum ar fi seturi misterioase de articole disponibile lunar și premii de la căpcăunii șef din trecut!",
+ "mysterySetNotFound": "Mystery set not found, or set already owned.",
+ "mysteryItemIsEmpty": "Mystery items are empty",
+ "mysteryItemOpened": "Mystery item opened.",
"mysterySet201402": "Winged Messenger Set",
"mysterySet201403": "Forest Walker Set",
"mysterySet201404": "Twilight Butterfly Set",
@@ -99,6 +105,8 @@
"mysterySet201601": "Champion of Resolution Set",
"mysterySet201602": "Heartbreaker Set",
"mysterySet201603": "Lucky Clover Set",
+ "mysterySet201604": "Leaf Warrior Set",
+ "mysterySet201605": "Marching Bard Set",
"mysterySet301404": "Steampunk Standard Set",
"mysterySet301405": "Steampunk Accessories Set",
"mysterySetwondercon": "Wondercon",
@@ -110,9 +118,25 @@
"hourglassBuyItemConfirm": "Cumperi acest articol pentru 1 clepsidră mistică?",
"petsAlreadyOwned": "Companionul este deja deținut.",
"mountsAlreadyOwned": "Bidiviul este deja deținut.",
- "typeNotAllowedHourglass": "Tipul de articol nu poate fi cumpărat cu clepsidra mistică. Tipurile permise:",
+ "typeNotAllowedHourglass": "Item type not supported for purchase with Mystic Hourglass. Allowed types: <%= allowedTypes %>",
"petsNotAllowedHourglass": "Companionul nu poate fi cumpărat cu clepsidra mistică.",
"mountsNotAllowedHourglass": "Bidiviul nu poate fi cumpărat cu clepsidra mistică.",
"hourglassPurchase": "Ai cumpărat un articol cu clepsidra mistică!",
- "hourglassPurchaseSet": "Ai cumpărat un set de articole cu clepsidra mistică!"
+ "hourglassPurchaseSet": "Ai cumpărat un set de articole cu clepsidra mistică!",
+ "missingUnsubscriptionCode": "Missing unsubscription code.",
+ "missingSubscription": "User does not have a plan subscription",
+ "missingSubscriptionCode": "Missing subscription code. Possible values: basic_earned, basic_3mo, basic_6mo, google_6mo, basic_12mo.",
+ "cannotDeleteActiveAccount": "You have an active subscription, cancel your plan before deleting your account.",
+ "paymentNotSuccessful": "The payment was not successful",
+ "planNotActive": "The plan hasn't activated yet (due to a PayPal bug). It will begin <%= nextBillingDate %>, after which you can cancel to retain your full benefits",
+ "notAllowedHourglass": "Pet/Mount not available for purchase with Mystic Hourglass.",
+ "readCard": "<%= cardType %> has been read",
+ "cardTypeRequired": "Card type required",
+ "cardTypeNotAllowed": "Unknown card type.",
+ "invalidCoupon": "Invalid coupon code.",
+ "couponUsed": "Coupon code already used.",
+ "noSudoAccess": "You don't have sudo access.",
+ "couponCodeRequired": "The coupon code is required.",
+ "eventRequired": "\"req.params.event\" is required.",
+ "countRequired": "\"req.query.count\" is required."
}
\ No newline at end of file
diff --git a/common/locales/ro/tasks.json b/common/locales/ro/tasks.json
index a943d9b118..ce7b1d6799 100644
--- a/common/locales/ro/tasks.json
+++ b/common/locales/ro/tasks.json
@@ -73,6 +73,7 @@
"clearTags": "Șterge",
"hideTags": "Ascunde",
"showTags": "Arată",
+ "toRequired": "You must supply a to value",
"startDate": "Start Date",
"startDateHelpTitle": "When should this task start?",
"startDateHelp": "Set the date for which this task takes effect. Will not be due on earlier days.",
@@ -88,8 +89,9 @@
"fortifyName": "Poțiune fortifiantă",
"fortifyPop": "Readu toate țelurile la valoarea neutră (culoarea galbenă) și refă toată Sănătatea pierdută.",
"fortify": "Fortifică",
- "fortifyText": "Fortify will return all your tasks to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!",
+ "fortifyText": "Fortify will return all your tasks, except challenge tasks, to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!",
"confirmFortify": "Are you sure?",
+ "fortifyComplete": "Fortify complete!",
"sureDelete": "Are you sure you want to delete the <%= taskType %> with the text \"<%= taskText %>\"?",
"streakCoins": "Bonus de realizare în șir!",
"pushTaskToTop": "Push task to top. Hold ctrl or cmd to push to bottom.",
@@ -112,5 +114,18 @@
"rewardHelp2": "Equipment affects your stats (<%= linkStart %>Avatar > Stats<%= linkEnd %>).",
"rewardHelp3": "Special equipment will appear here during World Events.",
"rewardHelp4": "Don't be afraid to set custom Rewards! Check out some samples here.",
- "clickForHelp": "Click for help"
+ "clickForHelp": "Click for help",
+ "taskIdRequired": "\"taskId\" must be a valid UUID.",
+ "taskNotFound": "Task not found.",
+ "invalidTaskType": "Task type must be one of \"habit\", \"daily\", \"todo\", \"reward\".",
+ "cantDeleteChallengeTasks": "A task belonging to a challenge can't be deleted.",
+ "checklistOnlyDailyTodo": "Checklists are supported only on dailies and todos",
+ "checklistItemNotFound": "No checklist item was found with given id.",
+ "itemIdRequired": "\"itemId\" must be a valid UUID.",
+ "tagNotFound": "No tag item was found with given id.",
+ "tagIdRequired": "\"tagId\" must be a valid UUID corresponding to a tag belonging to the user.",
+ "positionRequired": "\"position\" is required and must be a number.",
+ "cantMoveCompletedTodo": "Can't move a completed todo.",
+ "directionUpDown": "\"direction\" is required and must be 'up' or 'down'",
+ "alreadyTagged": "The task is already tagged with given tag."
}
\ No newline at end of file
diff --git a/common/locales/ru/backgrounds.json b/common/locales/ru/backgrounds.json
index 0f464dffc5..12cf313cc5 100644
--- a/common/locales/ru/backgrounds.json
+++ b/common/locales/ru/backgrounds.json
@@ -154,11 +154,18 @@
"backgroundRainforestNotes": "Отправьтесь в тропический лес.",
"backgroundStoneCircleText": "Круг Каменных глыб",
"backgroundStoneCircleNotes": "Творите заклинания в круге Каменных глыб!",
- "backgrounds042016": "SET 23: Released April 2016",
- "backgroundArcheryRangeText": "Archery Range",
- "backgroundArcheryRangeNotes": "Practice on the Archery Range.",
- "backgroundGiantFlowersText": "Giant Flowers",
- "backgroundGiantFlowersNotes": "Frolic atop Giant Flowers.",
- "backgroundRainbowsEndText": "End of the Rainbow",
- "backgroundRainbowsEndNotes": "Discover gold at the End of the Rainbow."
+ "backgrounds042016": "Набор 23: Выпущен в апреле 2016",
+ "backgroundArcheryRangeText": "Лучное стрельбище",
+ "backgroundArcheryRangeNotes": "Практикуйтесь на лучном стрельбище",
+ "backgroundGiantFlowersText": "Гигантские цветы",
+ "backgroundGiantFlowersNotes": "Веселитесь над Гигантскими Цветами",
+ "backgroundRainbowsEndText": "Там, где кончается радуга",
+ "backgroundRainbowsEndNotes": "Отыщите золото там, где кончается радуга",
+ "backgrounds052016": "SET 24: Released May 2016",
+ "backgroundBeehiveText": "Beehive",
+ "backgroundBeehiveNotes": "Buzz and dance in a Beehive.",
+ "backgroundGazeboText": "Gazebo",
+ "backgroundGazeboNotes": "Battle a Gazebo.",
+ "backgroundTreeRootsText": "Tree Roots",
+ "backgroundTreeRootsNotes": "Explore the Tree Roots."
}
\ No newline at end of file
diff --git a/common/locales/ru/challenge.json b/common/locales/ru/challenge.json
index d6d5f77814..3571b491d4 100644
--- a/common/locales/ru/challenge.json
+++ b/common/locales/ru/challenge.json
@@ -63,5 +63,20 @@
"congratulations": "Поздравляю!",
"hurray": "Ура!",
"noChallengeOwner": "нет владельца",
- "noChallengeOwnerPopover": "Это испытание не имеет владельца, потому что игрок, который его создал, удалил свой аккаунт."
+ "noChallengeOwnerPopover": "Это испытание не имеет владельца, потому что игрок, который его создал, удалил свой аккаунт.",
+ "challengeMemberNotFound": "User not found among challenge's members",
+ "onlyGroupLeaderChal": "Only the group leader can create challenges",
+ "tavChalsMinPrize": "Prize must be at least 1 Gem for Tavern challenges.",
+ "cantAfford": "You can't afford this prize. Purchase more gems or lower the prize amount.",
+ "challengeIdRequired": "\"challengeId\" must be a valid UUID.",
+ "winnerIdRequired": "\"winnerId\" must be a valid UUID.",
+ "challengeNotFound": "Challenge not found.",
+ "onlyLeaderDeleteChal": "Only the challenge leader can delete it.",
+ "onlyLeaderUpdateChal": "Only the challenge leader can update it.",
+ "winnerNotFound": "Winner with id \"<%= userId %>\" not found or not part of the challenge.",
+ "noCompletedTodosChallenge": "\"includeComepletedTodos\" is not supported when fetching a challenge tasks.",
+ "userTasksNoChallengeId": "When \"tasksOwner\" is \"user\" \"challengeId\" can't be passed.",
+ "onlyChalLeaderEditTasks": "Tasks belonging to a challenge can only be edited by the leader.",
+ "userAlreadyInChallenge": "User is already participating in this challenge.",
+ "cantOnlyUnlinkChalTask": "Only broken challenges tasks can be unlinked."
}
\ No newline at end of file
diff --git a/common/locales/ru/character.json b/common/locales/ru/character.json
index f16d5cc3a8..de1edb242e 100644
--- a/common/locales/ru/character.json
+++ b/common/locales/ru/character.json
@@ -1,4 +1,5 @@
{
+ "communityGuidelinesWarning": "Пожалуйста, помните: ваше имя, фото профиля, а также заметка о себе должны соответствовать Правилам сообщества (например, не содержать пошлости, материалы для взрослых, оскорбления, и тому подобное). Если у вас есть вопросы относительно того, что допустимо, а что нет, пишите нам на почту leslie@habitica.com!",
"statsAch": "Характеристики и достижения",
"profile": "Профиль",
"avatar": "Персонализировать аватар",
@@ -34,7 +35,7 @@
"beard": "Борода",
"mustache": "Усы",
"flower": "Цветок",
- "wheelchair": "Wheelchair",
+ "wheelchair": "Кресло-каталка",
"basicSkins": "Базовые цвета",
"rainbowSkins": "Радужные цвета",
"pastelSkins": "Пастельные цвета",
@@ -109,6 +110,7 @@
"mage": "Маг",
"mystery": "Таинственный",
"changeClass": "Сменить класс, перераспределить очки навыков",
+ "lvl10ChangeClass": "To change class you must be at least level 10.",
"levelPopover": "Каждый уровень приносит вам одно очко, которое можно направить на улучшение характеристик по вашему выбору. Это можно сделать вручную, а можно позволить игре решать за вас, выбрав один из вариантов автоматического распределения.",
"unallocated": "Осталось неиспользованных очков",
"haveUnallocated": "Вы не распределили <%= points %> очков",
@@ -164,5 +166,7 @@
"int": "ИНТ",
"showQuickAllocation": "Показать распределение характеристик",
"hideQuickAllocation": "Спрятать распределение характеристик",
- "quickAllocationLevelPopover": "Каждый уровень приносит вам одно очко для распределения характеристики по вашему выбору. Вы можете сделать это вручную, или позволить игре решить за вас, используя параметры Автоматического распределения, которые находятся на странице Пользователь -> Характеристики аватара "
+ "quickAllocationLevelPopover": "Каждый уровень приносит вам одно очко для распределения характеристики по вашему выбору. Вы можете сделать это вручную, или позволить игре решить за вас, используя параметры Автоматического распределения, которые находятся на странице Пользователь -> Характеристики аватара ",
+ "invalidAttribute": "\"<%= attr %>\" is not a valid attribute.",
+ "notEnoughAttrPoints": "You don't have enough attribute points."
}
\ No newline at end of file
diff --git a/common/locales/ru/content.json b/common/locales/ru/content.json
index 3af04206d5..aea58e33c6 100644
--- a/common/locales/ru/content.json
+++ b/common/locales/ru/content.json
@@ -113,6 +113,12 @@
"questEggSnailText": "Моллюск",
"questEggSnailMountText": "Моллюск",
"questEggSnailAdjective": "медленный, но непоколебимый",
+ "questEggFalconText": "Сокол",
+ "questEggFalconMountText": "Сокол",
+ "questEggFalconAdjective": "быстрый",
+ "questEggTreelingText": "Кустик",
+ "questEggTreelingMountText": "Куст",
+ "questEggTreelingAdjective": "ветвистый",
"eggNotes": "Найдите инкубационный эликсир, чтобы полить им это яйцо, и из него вылупится <%= eggAdjective(locale) %> <%= eggText(locale) %>.",
"hatchingPotionBase": "Обыкновенный",
"hatchingPotionWhite": "Белый",
@@ -126,6 +132,7 @@
"hatchingPotionGolden": "Золотой",
"hatchingPotionSpooky": "Зловещий",
"hatchingPotionPeppermint": "Мята",
+ "hatchingPotionFloral": "Floral",
"hatchingPotionNotes": "Полейте это на яйцо и из него вылупится <%= potText(locale) %> питомец.",
"premiumPotionAddlNotes": "Несовместим с яйцами квестовых питомцев.",
"foodMeat": "Мясо",
diff --git a/common/locales/ru/contrib.json b/common/locales/ru/contrib.json
index 3ce14640a6..7f5995e042 100644
--- a/common/locales/ru/contrib.json
+++ b/common/locales/ru/contrib.json
@@ -35,8 +35,12 @@
"hallContributors": "Зал участников",
"hallPatrons": "Зал покровителей",
"rewardUser": "Наградить пользователя",
- "UUID": "UUID",
+ "UUID": "ID пользователя",
"loadUser": "Загрузить пользователя",
+ "noAdminAccess": "You don't have admin access.",
+ "pageMustBeNumber": "req.query.page must be a number",
+ "userNotFound": "User not found.",
+ "invalidUUID": "UUID must be valid",
"title": "Звание",
"moreDetails": "Подробнее (1-7)",
"moreDetails2": "подробнее (8-9)",
@@ -52,8 +56,8 @@
"visitHeroes": "Посетите Зал героев (участники и спонсоры)",
"conLearn": "Подробнее о наградах для участников",
"conLearnHow": "Как внести свой вклад в Habitica",
- "surveysSingle": "Помог развитию Habitica, заполнив опрос. Сейчас нет опросов для заполнения.",
- "surveysMultiple": "Помог развитию HabitPRG, заполнив опросы: <%= surveys %>. Сейчас нет опросов для заполнения.",
+ "surveysSingle": "Helped Habitica grow, either by filling out a survey or helping with a major testing effort. Thank you!",
+ "surveysMultiple": "Helped Habitica grow on <%= surveys %> occasions, either by filling out a survey or helping with a major testing effort. Thank you!",
"currentSurvey": "Текущий опрос",
"surveyWhen": "Участники получат этот значок после обработки опросов, в конце марта.",
"blurbInbox": "Здесь хранятся ваши личные сообщения! Вы можете послать кому-нибудь сообщение, нажав на иконку конверта рядом с их именем в Таверне, Команде или в чате Гильдии. Если вы получили неуместное сообщение, вам следует послать скриншот этого сообщения Lemoness (leslie@habitica.com).",
diff --git a/common/locales/ru/death.json b/common/locales/ru/death.json
index 7a0d94b507..25c511fbbf 100644
--- a/common/locales/ru/death.json
+++ b/common/locales/ru/death.json
@@ -12,5 +12,6 @@
"losingHealthQuickly": "Слишком быстро теряете очки здоровья?",
"lowHealthTips3": "Невыполненные ежедневные задания наносят вам урон ночью, так что будьте осторожны и не создавайте слишком много заданий на первых порах!",
"lowHealthTips4": "Если нет необходимости в выполнении ежедневного задания в определенный день, вы можете отключить его, нажав значок карандаша.",
- "goodLuck": "Удачи!"
+ "goodLuck": "Удачи!",
+ "cannotRevive": "Cannot revive if not dead"
}
\ No newline at end of file
diff --git a/common/locales/ru/front.json b/common/locales/ru/front.json
index c23179217f..d307c764c8 100644
--- a/common/locales/ru/front.json
+++ b/common/locales/ru/front.json
@@ -28,6 +28,7 @@
"communityReddit": "Reddit",
"companyAbout": "Как это работает",
"companyBlog": "Блог",
+ "devBlog": "Блог разработчиков",
"companyDonate": "Пожертвования",
"companyExtensions": "Расширения",
"companyPrivacy": "Конфиденциальность",
@@ -51,6 +52,7 @@
"featureSocialHeading": "Коллективная игра",
"featuredIn": "О Habitica пишут",
"featuresHeading": "Мы также предлагаем...",
+ "footerDevs": "Разработчики",
"footerCommunity": "Сообщество",
"footerCompany": "Компания",
"footerMobile": "Мобильные приложения",
@@ -175,13 +177,14 @@
"unlockByline1": "Достигайте целей и повышайте свой уровень.",
"unlockByline2": "Откройте новые инструменты мотивации, включая коллекционирование питомцев, случайные награды, заклинания и другое!",
"unlockHeadline": "Пока вы остаетесь продуктивными, вы открываете что-то новое!",
- "useUUID": "Используйте UUID / API Token (для пользователей Facebook)",
+ "useUUID": "Используйте UUID / токен API (для пользователей Facebook)",
"username": "Имя",
"watchVideos": "Смотреть видео",
"work": "Работа",
"zelahQuote": "Благодаря [Habitica], я чаще стараюсь ложиться спать вовремя, потому что знаю, что получу очки, если лягу пораньше, и потеряю здоровье, если долго засижусь.",
"reportAccountProblems": "Сообщить о проблеме с учетной записью",
"reportCommunityIssues": "Сообщить о проблеме с сообществом",
+ "subscriptionPaymentIssues": "Subscription and Payment Issues",
"generalQuestionsSite": "Общие Вопросы по Сайту",
"businessInquiries": "Бизнес предложения",
"merchandiseInquiries": "Предложения по использованию торговой марки",
@@ -222,5 +225,32 @@
"altAttrWebstorm": "WebStorm",
"altAttrGithub": "GitHub",
"altAttrTrello": "Trello",
- "altAttrSlack": "Slack"
+ "altAttrSlack": "Slack",
+ "missingAuthHeaders": "Missing authentication headers.",
+ "missingAuthParams": "Missing authentication parameters.",
+ "missingUsernameEmail": "Missing username or email.",
+ "missingEmail": "Missing email.",
+ "missingUsername": "Missing username.",
+ "missingPassword": "Missing password.",
+ "missingNewPassword": "Missing new password.",
+ "wrongPassword": "Wrong password.",
+ "notAnEmail": "Invalid email address.",
+ "emailTaken": "Email address is already used in an account.",
+ "newEmailRequired": "Missing new email address.",
+ "usernameTaken": "Username already taken.",
+ "passwordConfirmationMatch": "Password confirmation doesn't match password.",
+ "invalidLoginCredentials": "Incorrect username and/or email and/or password.",
+ "passwordReset": "If we have your email on file, your password reset link has been sent to your email.",
+ "passwordResetEmailSubject": "Password Reset for Habitica",
+ "passwordResetEmailText": "Password for <%= username %> has been reset to <%= newPassword %> . Important! Both username and password are case-sensitive -- you must enter both exactly as shown here. We recommend copying and pasting both instead of typing them. Log in at <%= baseUrl %>. After you have logged in, head to <%= baseUrl %>/#/options/settings/settings and change your password.",
+ "passwordResetEmailHtml": "Password for <%= username %> has been reset to <%= newPassword %>.
Important! Both username and password are case-sensitive -- you must enter both exactly as shown here. We recommend copying and pasting both instead of typing them.
Log in at <%= baseUrl %>. After you have logged in, head to <%= baseUrl %>/#/options/settings/settings and change your password.",
+ "invalidLoginCredentialsLong": "Uh-oh - your username or password is incorrect.\n- Make sure your username or email is typed correctly.\n- You may have signed up with Facebook, not email. Double-check by trying Facebook login.\n- If you forgot your password, click \"Forgot Password\".",
+ "invalidCredentials": "There is no account that uses those credentials.",
+ "accountSuspended": "Account has been suspended, please contact leslie@habitica.com with your User ID \"<%= userId %>\" for assistance.",
+ "onlyFbSupported": "Only Facebook is supported currently.",
+ "cantDetachFb": "Account lacks another authentication method, can't detach Facebook.",
+ "onlySocialAttachLocal": "Local authentication can be added to only a social account.",
+ "invalidReqParams": "Invalid request parameters.",
+ "memberIdRequired": "\"member\" must be a valid UUID.",
+ "heroIdRequired": "\"heroId\" must be a valid UUID."
}
\ No newline at end of file
diff --git a/common/locales/ru/gear.json b/common/locales/ru/gear.json
index 879dd22009..b61f9243be 100644
--- a/common/locales/ru/gear.json
+++ b/common/locales/ru/gear.json
@@ -187,8 +187,10 @@
"weaponArmoireJesterBatonNotes": "Один взмах вашей дубинки и капелька шутовской находчивости - и даже самая сложная ситуация немедленно прояснится. Увеличивает интеллект и восприятие на <%= attrs %>. Зачарованный сундук: Набор Шута (предмет 3 из 3).",
"weaponArmoireMiningPickaxText": "Кирка",
"weaponArmoireMiningPickaxNotes": "Добудьте как можно больше золота из ваших задач! Увеличивает восприятие на <%= per %>. Зачарованный сундук: Набор Горняка (предмет 3 из 3).",
- "weaponArmoireBasicLongbowText": "Basic Longbow",
- "weaponArmoireBasicLongbowNotes": "A serviceable hand-me-down bow. Increases Strength by <%= str %>. Enchanted Armoire: Basic Archer Set (Item 1 of 3).",
+ "weaponArmoireBasicLongbowText": "Базовый лук",
+ "weaponArmoireBasicLongbowNotes": "Прочный и недорогой лук. Увеличивает силу на <%= str %>. Зачарованный сундук: Базовый набор Лучника (предмет 1 из 3).",
+ "weaponArmoireHabiticanDiplomaText": "Habitican Diploma",
+ "weaponArmoireHabiticanDiplomaNotes": "A certificate of significant achievement -- well done! Increases Intelligence by <%= int %>. Enchanted Armoire: Graduate Set (Item 1 of 3).",
"armor": "Броня",
"armorBase0Text": "Обычная одежда",
"armorBase0Notes": "Обычная одежда. Бонусов не дает.",
@@ -364,6 +366,10 @@
"armorMystery201512Notes": "Вызови ледяное пламя зимы! Преимуществ не дает. Предмет для подписчиков декабря 2015.",
"armorMystery201603Text": "Счастливый костюм",
"armorMystery201603Notes": "Этот костюм пошит из тысяч четырехлистных клеверов! Бонусов не дают. Подарок подписчикам марта 2016.",
+ "armorMystery201604Text": "Armor o' Leaves",
+ "armorMystery201604Notes": "You, too, can be a small but fearsome leaf puff. Confers no benefit. April 2016 Subscriber Item.",
+ "armorMystery201605Text": "Marching Bard Uniform",
+ "armorMystery201605Notes": "Unlike the traditional bards who join adventuring parties, bards who join Habitican marching bands are known for grand parades, not dungeon raids. Confers no benefit. May 2016 Subscriber Item.",
"armorMystery301404Text": "Стимпанковский костюм",
"armorMystery301404Notes": "Чудной и лихой! Бонусов не дает. Подарок подписчикам февраля 3015.",
"armorArmoireLunarArmorText": "Лунные доспехи успокоения",
@@ -392,8 +398,10 @@
"armorArmoireJesterCostumeNotes": "Тра-ля-ля! По этому костюму так сразу и не скажешь, но вы вовсе не дурак. Увеличивает интеллект на <%= int %>. Зачарованный сундук: Набор Шута (предмет 2 из 3).",
"armorArmoireMinerOverallsText": "Комбинезон шахтера",
"armorArmoireMinerOverallsNotes": "Да, он выглядит поношенным, но специальное заклятие отталкивает от него грязь. Увеличивает телосложение на <%= con %>. Зачарованный сундук: Набор Горняка (предмет 2 из 3).",
- "armorArmoireBasicArcherArmorText": "Basic Archer Armor",
- "armorArmoireBasicArcherArmorNotes": "This camouflaged vest lets you slip unnoticed through the forests. Increases Perception by <%= per %>. Enchanted Armoire: Basic Archer Set (Item 2 of 3).",
+ "armorArmoireBasicArcherArmorText": "Базовые доспехи лучника",
+ "armorArmoireBasicArcherArmorNotes": "Эта камуфляжная безрукавка позволит вам ускользать незамеченным через леса. Увеличивает восприятие на <%= per %>. Зачарованный сундук: Базовый набор Лучника (предмет 2 из 3).",
+ "armorArmoireGraduateRobeText": "Graduate Robe",
+ "armorArmoireGraduateRobeNotes": "Congratulations! This weighty robe hangs heavy with all the knowledge you have accrued. Increases Intelligence by <%= int %>. Enchanted Armoire: Graduate Set (Item 2 of 3).",
"headgear": "Головной убор",
"headBase0Text": "Нет шлема",
"headBase0Notes": "Нет головного убора.",
@@ -565,6 +573,10 @@
"headMystery201602Notes": "Оградите свою индивидуальность от всех своих поклонников. Бонусов не дает. Подарок подписчикам февраля 2016.",
"headMystery201603Text": "Счастливая шляпа",
"headMystery201603Notes": "Эта шляпа - магический амулет, приносящий удачу. Бонусов не дает. Подарок подписчикам марта 2016.",
+ "headMystery201604Text": "Crown o' Flowers",
+ "headMystery201604Notes": "These woven flowers make a surprisingly strong helm! Confers no benefit. April 2016 Subscriber Item.",
+ "headMystery201605Text": "Marching Bard Hat",
+ "headMystery201605Notes": "Seventy-six dragons led the big parade, with a hundred and ten gryphons close at hand! Confers no benefit. May 2016 Subscriber Item.",
"headMystery301404Text": "Модный цилиндр",
"headMystery301404Notes": "Модный цилиндр для самых уважаемых господ! Подарок подписчикам января 3015. Бонусов не дает.",
"headMystery301405Text": "Обычный цилиндр",
@@ -611,8 +623,10 @@
"headArmoireJesterCapNotes": "Колокольчики на шляпе отвлекают врагов, а вам помогают сосредоточиться. Увеличивает восприятие на <%= per %>. Зачарованный сундук: Набор Шута (предмет 1 из 3).",
"headArmoireMinerHelmetText": "Каска шахтера",
"headArmoireMinerHelmetNotes": "Защитите свою голову от падающих задач! Увеличивает интеллект на <%= int %>. Зачарованный сундук: Набор Шахтера (предмет 1 из 3).",
- "headArmoireBasicArcherCapText": "Basic Archer Cap",
- "headArmoireBasicArcherCapNotes": "No archer would be complete without a jaunty cap! Increases Perception by <%= per %>. Enchanted Armoire: Basic Archer Set (Item 3 of 3).",
+ "headArmoireBasicArcherCapText": "Базовая шляпа лучника",
+ "headArmoireBasicArcherCapNotes": "Ни один лучник не обходиться без своей веселой шляпы! Увеличивает восприятие на <%= per %>. Зачарованный сундук: Базовый набор Лучника (предмет 3 из 3).",
+ "headArmoireGraduateCapText": "Graduate Cap",
+ "headArmoireGraduateCapNotes": "Congratulations! Your deep thoughts have earned you this thinking cap. Increases Intelligence by <%= int %>. Enchanted Armoire: Graduate Set (Item 3 of 3).",
"offhand": "предмет для защитной руки",
"shieldBase0Text": "Нет снаряжения для защитной руки",
"shieldBase0Notes": "Нет щита или второго оружия.",
@@ -673,7 +687,7 @@
"shieldSpecialWinter2015HealerText": "Щит Успокоения",
"shieldSpecialWinter2015HealerNotes": "Этот щит отражает леденящий ветер. Увеличивает телосложение на <%= con %>. Экипировка ограниченного выпуска зимы 2014-2015.",
"shieldSpecialSpring2015RogueText": "Взрывной Писк",
- "shieldSpecialSpring2015RogueNotes": "Не дайте звуку обмануть вас – эта взрывчатка просто сбивает с ног. Увеличивает силу на <%= str %>. Экипировка ограниченного выпуска весны 2015.",
+ "shieldSpecialSpring2015RogueNotes": "Don't let the sound fool you - these explosives pack a punch. Increases Strength by <%= str %>. Limited Edition 2015 Spring Gear.",
"shieldSpecialSpring2015WarriorText": "Метательная Тарелка",
"shieldSpecialSpring2015WarriorNotes": "Швырните ей во врага! Ну или оставьте себе, потому что к обеду в ней будет полно вкусного корма. Увеличивает телосложение на <%= con %>. Экипировка ограниченного выпуска весны 2015.",
"shieldSpecialSpring2015HealerText": "Узорчатая Подушка",
@@ -716,6 +730,8 @@
"shieldArmoireDragonTamerShieldNotes": "Отвлеките врагов этим щитом в форме дракона. Увеличивает восприятие на <%= per %>. Зачарованный сундук: Набор Укротителя драконов (предмет 2 из 3).",
"shieldArmoireMysticLampText": "Таинственная лампа",
"shieldArmoireMysticLampNotes": "Осветите темные пещеры с помощью этой таинственной лампы! Увеличивает восприятие на <%= per %>. Зачарованный сундук: Независимый предмет.",
+ "shieldArmoireFloralBouquetText": "Bouquet o' Flowers",
+ "shieldArmoireFloralBouquetNotes": "Not much help in battle, but aren't they beautiful? Increases Constitution by <%= con %>. Enchanted Armoire: Independent Item.",
"back": "Аксессуар на спину",
"backBase0Text": "Нет аксессуаров на спине",
"backBase0Notes": "Нет аксессуаров на спине.",
@@ -815,11 +831,25 @@
"headAccessoryMystery201510Notes": "Эти грозные рога немного склизкие. Бонусов не даёт. Подарок подписчикам октября 2015.",
"headAccessoryMystery301405Text": "Очки на голове",
"headAccessoryMystery301405Notes": "\"Защищать очками надо глаза,\" говорили они. \"Кому сдались защитные очки на макушке,\" говорили они. Ха! Вы им показали! Подарок подписчикам августа 3015. Бонусов не дает.",
- "headAccessoryArmoireComicalArrowText": "Comical Arrow",
- "headAccessoryArmoireComicalArrowNotes": "This whimsical item doesn't provide a stat boost, but it sure is good for a laugh! Confers no benefit. Enchanted Armoire: Independent Item.",
+ "headAccessoryArmoireComicalArrowText": "Забавная Стрела",
+ "headAccessoryArmoireComicalArrowNotes": "Причудливый предмет не дает усиление характеристикам, но определенно подходит для потехи! Бонусов не дает. Зачарованный сундук: Независимый предмет.",
"eyewear": "Очки",
"eyewearBase0Text": "Нет очков или маски",
"eyewearBase0Notes": "Нет очков или маски.",
+ "eyewearSpecialBlackTopFrameText": "Black Standard Eyeglasses",
+ "eyewearSpecialBlackTopFrameNotes": "Glasses with a black frame above the lenses. Confers no benefit.",
+ "eyewearSpecialBlueTopFrameText": "Blue Standard Eyeglasses",
+ "eyewearSpecialBlueTopFrameNotes": "Glasses with a blue frame above the lenses. Confers no benefit.",
+ "eyewearSpecialGreenTopFrameText": "Green Standard Eyeglasses",
+ "eyewearSpecialGreenTopFrameNotes": "Glasses with a green frame above the lenses. Confers no benefit.",
+ "eyewearSpecialPinkTopFrameText": "Pink Standard Eyeglasses",
+ "eyewearSpecialPinkTopFrameNotes": "Glasses with a pink frame above the lenses. Confers no benefit.",
+ "eyewearSpecialRedTopFrameText": "Red Standard Eyeglasses",
+ "eyewearSpecialRedTopFrameNotes": "Glasses with a red frame above the lenses. Confers no benefit.",
+ "eyewearSpecialWhiteTopFrameText": "White Standard Eyeglasses",
+ "eyewearSpecialWhiteTopFrameNotes": "Glasses with a white frame above the lenses. Confers no benefit.",
+ "eyewearSpecialYellowTopFrameText": "Yellow Standard Eyeglasses",
+ "eyewearSpecialYellowTopFrameNotes": "Glasses with a yellow frame above the lenses. Confers no benefit.",
"eyewearSpecialSummerRogueText": "Разбойничья повязка",
"eyewearSpecialSummerRogueNotes": "Не нужно быть негодяем, чтобы понять, насколько она стильная! Бонусов не дает. Экипировка ограниченного выпуска лета 2014.",
"eyewearSpecialSummerWarriorText": "Пиратская повязка",
diff --git a/common/locales/ru/generic.json b/common/locales/ru/generic.json
index 3995fe288a..6caa6a045d 100644
--- a/common/locales/ru/generic.json
+++ b/common/locales/ru/generic.json
@@ -137,9 +137,9 @@
"achievementStressbeastText": "Помог победить Отвратительного Стрессозверя в 2014 во время Зимнего События Чудостраны!",
"achievementBurnout": "Спаситель Процветающих Полей",
"achievementBurnoutText": "Помог победить Перегорание и восстановить Истощённых Духов во время Осеннего Фестиваля 2015 года!",
- "achievementBewilder": "Savior of Mistiflying",
- "achievementBewilderText": "Helped defeat the Be-Wilder during the 2016 Spring Fling Event!",
- "checkOutProgress": "Отметить мои достижения в Habitica!",
+ "achievementBewilder": "Спаситель Летящейдымки",
+ "achievementBewilderText": "Помог(ла) победить С-толку-сбивателя во время события Весенней Веселухи 2016!",
+ "checkOutProgress": "Оцените мои достижения в Habitica!",
"cardReceived": "Получено письмо!",
"cardReceivedFrom": "<%= cardType %> от <%= userName %>",
"greetingCard": "Приветственное письмо",
diff --git a/common/locales/ru/groups.json b/common/locales/ru/groups.json
index 68f9085afa..c74a8d6d6a 100644
--- a/common/locales/ru/groups.json
+++ b/common/locales/ru/groups.json
@@ -72,11 +72,11 @@
"optionalMessage": "Необязательное сообщение",
"yesRemove": "Да, удалить",
"foreverAlone": "Вы не можете лайкать свое собственное сообщение. Не будьте таким человеком.",
- "sortLevel": "Отсортировать по уровню",
+ "sortLevel": "Сортировать по уровню",
"sortRandom": "В случайном порядке",
"sortPets": "Сортировать по количеству питомцев",
- "sortJoined": "Отсортировать по дате вступления",
- "sortName": "Отсортировать в алфавитном порядке",
+ "sortJoined": "Сортировать по дате присоединения к команде",
+ "sortName": "Сортировать по имени",
"sortBackgrounds": "Сортировать по фону",
"sortHabitrpgJoined": "Сортировать по дате присоединения к Habitica",
"sortHabitrpgLastLoggedIn": "Сортировать по времени последнего входа",
@@ -92,6 +92,7 @@
"send": "Отправить",
"messageSentAlert": "Сообщение отправлено",
"pmHeading": "Личное сообщение для <%= name %>",
+ "pmsMarkedRead": "Your private messages have been marked as read",
"clearAll": "Удалить все сообщения",
"confirmDeleteAllMessages": "Вы действительно хотите удалить входящие сообщения? Сообщения, отправленные вами другим пользователям, все еще будут им доступны.",
"optOutPopover": "Не любите личные сообщения? Нажмите, чтобы отказаться от использования",
@@ -99,6 +100,15 @@
"unblock": "Разблокировать",
"pm-reply": "Ответить",
"inbox": "Почта",
+ "messageRequired": "A message is required.",
+ "toUserIDRequired": "A User ID is required",
+ "gemAmountRequired": "A number of gems is required",
+ "notAuthorizedToSendMessageToThisUser": "Can't send message to this user.",
+ "privateMessageGiftIntro": "Hello <%= receiverName %>, <%= senderName %> has sent you",
+ "privateMessageGiftGemsMessage": "<%= gemAmount %> gems!",
+ "privateMessageGiftSubscriptionMessage": "<%= numberOfMonths %> months of subscription!",
+ "cannotSendGemsToYourself": "Cannot send gems to yourself. Try a subscription instead.",
+ "badAmountOfGemsToSend": "Amount must be within 1 and your current number of gems.",
"abuseFlag": "Сообщить о нарушении Правил сообщества",
"abuseFlagModalHeading": "Сообщить о нарушении со стороны <%= name %>?",
"abuseFlagModalBody": "Вы уверены, что хотите пожаловаться на этот пост? На пост следует жаловаться ИСКЛЮЧИТЕЛЬНО в случае, если в нем нарушаются <%= firstLinkStart %>Правила сообщества<%= linkEnd %> и/или <%= secondLinkStart %>Условия использования<%= linkEnd %>. Неуместная жалоба на пост является нарушением Правил сообщества и может послужить поводом для принятия соответствующих мер. Допустимые поводы подачи жалобы на пост включают, но не ограничены следующим:
ругань, религиозные проклятья
оскорбления, нецензурные выражения
\"взрослые\" темы
насилие, включая \"шуточное\"
спам, бессмысленные сообщения
",
@@ -151,5 +161,29 @@
"partyUpName": "Зажигаем",
"partyOnName": "Время вечеринок",
"partyUpAchievement": "Вы объединились в команду с другим человеком! Веселитесь сражаясь с монстрами и поддерживая друг друга!",
- "partyOnAchievement": "Вы объединились в команду как минимум из четырех человек! Получайте удовольствие от возросшей ответственности, так как вы объединились со своими друзьями, чтобы победить своих врагов!"
+ "partyOnAchievement": "Вы объединились в команду как минимум из четырех человек! Получайте удовольствие от возросшей ответственности, так как вы объединились со своими друзьями, чтобы победить своих врагов!",
+ "groupIdRequired": "\"groupId\" must be a valid UUID",
+ "groupNotFound": "Group not found.",
+ "groupTypesRequired": "You must supply a valid \"type\" query string.",
+ "questLeaderCannotLeaveGroup": "You cannot leave your party when you have started a quest. Abort the quest first.",
+ "cannotLeaveWhileActiveQuest": "You cannot leave party during an active quest. Please leave the quest first.",
+ "onlyLeaderCanRemoveMember": "Only group leader can remove a member!",
+ "memberCannotRemoveYourself": "You cannot remove yourself!",
+ "groupMemberNotFound": "User not found among group's members",
+ "mustBeGroupMember": "Must be member of the group.",
+ "keepOrRemoveAll": "req.query.keep must be either \"keep-all\" or \"remove-all\"",
+ "keepOrRemove": "req.query.keep must be either \"keep\" or \"remove\"",
+ "canOnlyInviteEmailUuid": "Can only invite using uuids or emails.",
+ "inviteMissingEmail": "Missing email address in invite.",
+ "partyMustbePrivate": "Parties must be private",
+ "userAlreadyInGroup": "User already in that group.",
+ "userAlreadyInvitedToGroup": "User already invited to that group.",
+ "userAlreadyPendingInvitation": "User already pending invitation.",
+ "userAlreadyInAParty": "User already in a party.",
+ "userWithIDNotFound": "User with id \"<%= userId %>\" not found.",
+ "userHasNoLocalRegistration": "User does not have a local registration (username, email, password).",
+ "uuidsMustBeAnArray": "User ID invites must be an array.",
+ "emailsMustBeAnArray": "Email address invites must be an array.",
+ "canOnlyInviteMaxInvites": "You can only invite \"<%= maxInvites %>\" at a time",
+ "onlyCreatorOrAdminCanDeleteChat": "Not authorized to delete this message!"
}
\ No newline at end of file
diff --git a/common/locales/ru/limited.json b/common/locales/ru/limited.json
index d4d3e6fafe..6261836ec3 100644
--- a/common/locales/ru/limited.json
+++ b/common/locales/ru/limited.json
@@ -5,7 +5,7 @@
"annoyingFriends": "Вредные друзья",
"annoyingFriendsText": "Прилетело снежков от членов команды: <%= snowballs %>.",
"alarmingFriends": "Пугающие друзья",
- "alarmingFriendsText": "Напуган членами команды <%= spookDust %> раз(а).",
+ "alarmingFriendsText": "Got spooked <%= spookySparkles %> times by party members.",
"agriculturalFriends": "Сельскохозяйственные друзья",
"agriculturalFriendsText": "Превращен в цветок членами команды <%= seeds %> раз(а).",
"aquaticFriends": "Водяные друзья",
@@ -72,5 +72,6 @@
"comfortingKittySet": "Успокаивающий котенок (Целитель)",
"sneakySqueakerSet": "Скрытный пискун (Разбойник)",
"fallEventAvailability": "Доступно до 31 октября",
- "winterEventAvailability": "Доступен до 31 декабря"
+ "winterEventAvailability": "Доступен до 31 декабря",
+ "springEventAvailability": "Available until May 31"
}
\ No newline at end of file
diff --git a/common/locales/ru/maintenance.json b/common/locales/ru/maintenance.json
new file mode 100644
index 0000000000..dbae0c703f
--- /dev/null
+++ b/common/locales/ru/maintenance.json
@@ -0,0 +1,34 @@
+{
+ "habiticaBackSoon": "Не волнуйтесь, Habitica скоро вернется!",
+ "importantMaintenance": "Мы проводим важное техническое обслуживание, которое продлится, по нашим оценкам, до <%= localDate %> в вашем часовом поясе.",
+ "maintenance": "Техническое обслуживание",
+ "maintenanceMoreInfo": "Хотите узнать больше о проводимых работах? <%= linkStart %>Зайдите на информационную страницу<%= linkEnd %>",
+ "noDamageKeepStreaks": "Вы НЕ получите урона, а ежедневные задачи сохранят серии!",
+ "thanksForPatience": "Благодарим за терпение!",
+ "twitterMaintenanceUpdates": "Актуальные новости смотрите в нашем Twitter, где мы будем размещать информацию о текущей ситуации.",
+ "veteranPetAward": "В конце вы получите питомца-ветерана!",
+
+ "maintenanceInfoTitle": "Информация о грядущем техническом обслуживании Habitica",
+ "maintenanceInfoWhat": "Что происходит?",
+ "maintenanceInfoWhatText": "21 мая Habitica будет недоступна из-за технического обслуживания большую часть дня. Но в эти выходные вы не получите урона, даже если вы не сможете зайти, чтобы отметить ежедневные задания. Ваша учетная запись никак не пострадает! Мы постараемся свести время обслуживания к минимуму и будем регулярно сообщать о ходе работ на нашей странице в Twitter. После завершения технического обслуживания мы отблагодарим всех за терпение: вы получите редкого питомца!",
+ "maintenanceInfoWhy": "Для чего это?",
+ "maintenanceInfoWhyText": "На протяжении нескольких месяцев мы основательно перестраивали закулисье Habitica. В частности, мы переписали API. Мало что может быть заметно на поверхности, но в глубине это новый мир. Все это обеспечит гораздо большую гибкость при внедрении новых функций в будущем и приведет к улучшению производительности! ",
+ "maintenanceInfoTechDetails": "Хотите узнать больше о технической стороне? Посетите наш блог разработчиков The Forge.",
+ "maintenanceInfoMore": "Подробности",
+ "maintenanceInfoAccountChanges": "Какие изменения будут заметны мне после завершения работ?",
+ "maintenanceInfoAccountChangesText": "Сперва никаких существенных изменений не будет, кроме улучшения производительности некоторых функций, как например, испытаний. Если вы заметите какие-либо изменения, которых быть не должно, сообщите нам на почту admin@habitica.com и мы изучим их!",
+ "maintenanceInfoAddFeatures": "Внедрение каких новых функций станет возможно?",
+ "maintenanceInfoAddFeaturesText": "Завершение этого проекта позволит нам начать работу над улучшенным чатом и гильдиями, корпоративным и семейным обслуживанием, а также новыми функциями, такими как ежемесячные задания и возможность записывать вчерашние события! Реализовать сами эти функции будет не очень просто, и это займет какое-то время - но раньше начать их разработку было просто невозможно.",
+ "maintenanceInfoHowLong": "Как долго займет техническое обслуживание?",
+ "maintenanceInfoHowLongText": "Мы должны перенести задачи и данные всех пользователей Habitica – а их 1,3 миллиона – нелегкая задача! Мы ожидаем, что ее выполнение займет несколько часов: с 13 часов Тихоокеанского времени (20 часов по UTC) до 22 часов Тихоокеанского времени (5 часов утра следующего дня по UTC). Мы делаем все возможное, чтобы перенос завершился максимально быстро! Вы сможете следить за новостями в нашем Twitter. ",
+ "maintenanceInfoStatsAffected": "Как обслуживание повлияет на мои ежедневные задания, серии, баффы и квесты?",
+ "maintenanceInfoStatsAffectedText1": "Вы НЕ получите урона, а серии ежедневных заданий не будут потеряны в эти выходные, но во всем остальном смена дня пройдет как обычно! Отметки о выполнении ежедневных заданий будут сброшены, как и баффы и пр. Если вы участвуете в квесте ваш прогресс будет учтен: вы найдете предметы или нанесете урон боссу, но босс не причинит вреда вам (даже монстрам иногда нужен перерыв!) ",
+ "maintenanceInfoStatsAffectedText2": "После долгих раздумий мы пришли к выводу, что это самый честный путь решения проблемы, что многие пользователи во время технического обслуживания не смогут отметить свои ежедневные задания, как обычно. Мы приносим извинения за неудобства!",
+ "maintenanceInfoSeeTasks": "Что, если мне будет нужно посмотреть мой список дел?",
+ "maintenanceInfoSeeTasksText": "Если вы знаете, что список дел вам понадобится в Субботу, мы рекомендуем сделать скриншот до начала технического обслуживания и распечатать его. Так вы сможете иметь под рукой список заданий, если понадобится напомнить себе о том, что надо сделать.",
+ "maintenanceInfoRarePet": "Какого редкого питомца я получу?",
+ "maintenanceInfoRarePetText": "В благодарность за ваше терпение в то время, что Habitica будет недоступна, каждый пользователь получит редкого питомца-ветерана. Если вы никогда прежде не получали питомца-ветерана, у вас появится волк-ветеран. Если он у вас уже есть, тогда к нему добавится тигр-ветеран. Если же и он у вас уже есть, вы получите не-виданного-доселе питомца-ветерана! После завершения переноса данных может пройти несколько часов, прежде чем ваш новый питомец появится в Стойлах, но не вздумайте волноваться – у всех будет по новому питомцу!",
+ "maintenanceInfoWho": "Кто работал над таким масштабным проектом?",
+ "maintenanceInfoWhoText": "Мы рады, что вы спросили! Его возглавлял наш великолепный разработчик paglias, а неоценимую помощь ему оказали Blade, TheHollidayInn, SabreCat, Victor Pudeyev, TheUnknown и Alys.",
+ "maintenanceInfoTesting": "Новую версию также без устали тестировали активисты из числа наших великолепных участников разработки open-source. Спасибо! Мы бы не справились без вас."
+}
diff --git a/common/locales/ru/npc.json b/common/locales/ru/npc.json
index 23ca91aa37..c8022c54dd 100644
--- a/common/locales/ru/npc.json
+++ b/common/locales/ru/npc.json
@@ -21,6 +21,26 @@
"ian": "Ян",
"ianText": "Добро пожаловать в Лавку квестов! Здесь вы можете использовать Свитки Квестов, чтобы сразиться с монстрами вместе с друзьями. Не забудьте оценить справа стройные ряды Свитков Квестов на продажу!",
"ianBrokenText": "Добро пожаловать в Лавку квестов... Здесь вы можете использовать Свитки Квестов, чтобы сразиться с монстрами вместе с друзьями... Оцените стройные ряды Свитков Квестов на продажу справа от вас...",
+ "missingKeyParam": "\"req.params.key\" обязателен.",
+ "itemNotFound": "Предмет \"<%= key %>\" не найден.",
+ "cannotBuyItem": "You can't buy this item.",
+ "missingTypeKeyEquip": "\"key\" and \"type\" are required parameters.",
+ "missingPetFoodFeed": "\"pet\" and \"food\" are required parameters.",
+ "invalidPetName": "Invalid pet name supplied.",
+ "missingEggHatchingPotionHatch": "\"egg\" and \"hatchingPotion\" are required parameters.",
+ "invalidTypeEquip": "\"type\" must be one of 'equipped', 'pet', 'mount', 'costume'.",
+ "mustPurchaseToSet": "Must purchase <%= val %> to set it on <%= key %>.",
+ "typeRequired": "Type is required",
+ "keyRequired": "Key is required",
+ "notAccteptedType": "Type must be in [eggs, hatchingPotions, food, quests, gear]",
+ "contentKeyNotFound": "Key not found for Content <%= type %>",
+ "plusOneGem": "+1 самоцвет",
+ "typeNotSellable": "Type is not sellable. Must be one of the following <%= acceptedTypes %>",
+ "userItemsKeyNotFound": "Key not found for user.items <%= type %>",
+ "pathRequired": "Path string is required",
+ "unlocked": "Items have been unlocked",
+ "alreadyUnlocked": "Full set already unlocked.",
+ "alreadyUnlockedPart": "Full set already partially unlocked.",
"USD": "(долл. США)",
"newStuff": "Что-то новенькое",
"cool": "Напомнить позже",
@@ -64,6 +84,7 @@
"tourPetsPage": "Это стойла! Начиная с 4-го уровня, вы можете выращивать питомцев из яиц с помощью эликсиров. После того, как питомец вылупится на Рынке, он появится здесь. Кликните на изображение питомца, чтобы добавить его на свой аватар. Кормите питомцев едой, которую будете находить после 4-го уровня, и они вырастут в выносливых скакунов.",
"tourMountsPage": "Когда питомец получит достаточно еды, он станет скакуном и будет отображаться здесь. (Питомцы, скакуны и еда доступны после 4-го уровня). Выберите скакуна, чтобы оседлать его!",
"tourEquipmentPage": "Здесь хранится ваше снаряжение! Боевая экипировка влияет на ваши характеристики. Если вы хотите, чтобы на аватаре отображалось иное снаряжение, без изменения характеристик, нажмите «Использовать костюм».",
+ "equipmentAlreadyOwned": "You already own that piece of equipment",
"tourOkay": "Хорошо!",
"tourAwesome": "Потрясающе!",
"tourSplendid": "Отлично!",
diff --git a/common/locales/ru/pets.json b/common/locales/ru/pets.json
index 6670f8601e..3ddf405ca0 100644
--- a/common/locales/ru/pets.json
+++ b/common/locales/ru/pets.json
@@ -12,6 +12,7 @@
"etherealLion": "Бесплотный лев",
"veteranWolf": "Волк-ветеран",
"veteranTiger": "Тигр-ветеран",
+ "veteranLion": "Veteran Lion",
"cerberusPup": "Щенок цербера",
"hydra": "Гидра",
"mantisShrimp": "Рак-богомол",
@@ -19,7 +20,7 @@
"orca": "Косатка",
"royalPurpleGryphon": "Королевский пурпурный грифон",
"phoenix": "Феникс",
- "bumblebee": "Bumblebee",
+ "magicalBee": "Волшебная пчелка",
"rarePetPop1": "Нажмите золотую лапу, чтобы подробнее узнать, как получить этого редкого питомца, приняв участие в проекте Habitica!",
"rarePetPop2": "Как получить этого питомца?",
"potion": "<%= potionType %> эликсир",
@@ -62,6 +63,7 @@
"hatchedPet": "У вас вылупился <%= potion %> <%= egg %>!",
"displayNow": "Показать сейчас",
"displayLater": "Показать позже",
+ "petNotOwned": "You do not own this pet.",
"earnedCompanion": "Вы были так продуктивны, что заработали нового спутника. Кормите его, чтобы он вырос!",
"feedPet": "Скормить <%= article %><%= text %> вашему питомцу: <%= name %>?",
"useSaddle": "Оседлать <%= pet %>?",
@@ -83,5 +85,8 @@
"petKeyBoth": "Освободить всех",
"confirmPetKey": "Вы уверены?",
"petKeyNeverMind": "Еще нет",
+ "petsReleased": "Pets released.",
+ "mountsAndPetsReleased": "Mounts and pets released",
+ "mountsReleased": "Mounts released",
"gemsEach": "самоцветов за каждый"
}
\ No newline at end of file
diff --git a/common/locales/ru/quests.json b/common/locales/ru/quests.json
index 954e25f5e2..f9400eaa8f 100644
--- a/common/locales/ru/quests.json
+++ b/common/locales/ru/quests.json
@@ -38,7 +38,7 @@
"bossDmg2": "Только участники будут сражаться с боссом и разделят квестовые трофеи.",
"bossDmg1Broken": "Босс получает урон от каждого завершенного ежедневного задания, каждой задачи и каждой полезной привычки... Вы можете нанести больше урона, выполняя красные задания и используя Мощный удар или Всплеск пламени... Босс нанесет урон каждому участнику квеста за каждое пропущенное ежедневное задание (ущерб от задания, умноженный на силу босса) в дополнение к обычному урону... Старайтесь поддерживать здоровье команды, выполняя ежедневные задания... Весь урон, нанесенный команде и боссу, засчитывается в момент смены суток (по окончании вашего дня)...",
"bossDmg2Broken": "Только участники будут сражаться с боссом и разделят квестовые трофеи.",
- "tavernBossInfo": "Complete Dailies and To-Dos and score positive Habits to damage the World Boss! Incomplete Dailies fill the Rage Bar. When the Rage bar is full, the World Boss will attack an NPC. A World Boss will never damage individual players or accounts in any way. Only active accounts not resting in the Inn will have their tasks tallied.",
+ "tavernBossInfo": "Завершайте Ежедневные Задания и Задачи и отмечайте положительные Привычки, чтобы нанести урон Мировому Боссу! Незавершённые Ежедневные Задания заполнят Шкалу Изнуряющего Удара. Когда Шкала Изнуряющего Удара заполнена, Мировой Босс нанесёт атаку неигровому персонажу. Мировой Босс никак и никогда не нанесёт урона вашему личному персонажу или вашему аккаунту. Задание будет засчитано только активным аккаунтам, которые не отдыхают в Гостинице.",
"tavernBossInfoBroken": "Завершайте Ежедневные Задания и отмечайте положительные Привычки, чтобы нанести урон Мировому Боссу... Незавершённые Ежедневные Задания заполнят Шкалу Изнуряющего Удара... Когда Шкала Изнуряющего Удара заполнена, Мировой Босс нанесёт атаку неигровому персонажу... Мировой Босс никак и никогда не нанесёт урона вашему личному персонажу или вашему аккаунту... Задание будет засчитанно только активным аккаунтам, которые не отдыхают в Гостинице...",
"bossColl1": "Чтобы собирать предметы, выполняйте положительные задания. Квестовые предметы падают точно так же, как обычные; однако, вы не увидите, что упало, до начала следующего дня — тогда всё, что вы нашли, будет посчитано и сложено в общую стопку.",
"bossColl2": "Только участники могут собрать предметы и разделять квестовые трофеи.",
@@ -78,5 +78,24 @@
"whichQuestStart": "Какой квест вы хотите начать?",
"getMoreQuests": "Получить больше квестов",
"unlockedAQuest": "Вы разблокировали квест!",
- "leveledUpReceivedQuest": "Вы повысили свой уровень до <%= level %> и получили свиток квеста!"
+ "leveledUpReceivedQuest": "Вы повысили свой уровень до <%= level %> и получили свиток квеста!",
+ "questInvitationDoesNotExist": "No quest invitation has been sent out yet.",
+ "questInviteNotFound": "No quest invitation found.",
+ "guildQuestsNotSupported": "Guilds cannot be invited on quests.",
+ "questNotFound": "Quest \"<%= key %>\" not found.",
+ "questNotOwned": "You don't own that quest scroll.",
+ "questNotGoldPurchasable": "Quest \"<%= key %>\" is not a Gold-purchasable quest.",
+ "questLevelTooHigh": "You must be level <%= level %> to begin this quest.",
+ "questAlreadyUnderway": "Your party is already on a quest. Try again when the current quest has ended.",
+ "questAlreadyAccepted": "You already accepted the quest invitation.",
+ "noActiveQuestToLeave": "No active quest to leave",
+ "questLeaderCannotLeaveQuest": "Quest leader cannot leave quest",
+ "notPartOfQuest": "You are not part of the quest",
+ "noActiveQuestToAbort": "There is no active quest to abort.",
+ "onlyLeaderAbortQuest": "Only the group or quest leader can abort a quest.",
+ "questAlreadyRejected": "You already rejected the quest invitation.",
+ "cantCancelActiveQuest": "You can not cancel an active quest, use the abort functionality.",
+ "onlyLeaderCancelQuest": "Only the group or quest leader can cancel the quest.",
+ "questNotPending": "There is no quest to start.",
+ "questOrGroupLeaderOnlyStartQuest": "Only the quest leader or group leader can force start the quest"
}
\ No newline at end of file
diff --git a/common/locales/ru/questscontent.json b/common/locales/ru/questscontent.json
index 5824f97637..ce20680285 100644
--- a/common/locales/ru/questscontent.json
+++ b/common/locales/ru/questscontent.json
@@ -298,15 +298,27 @@
"questSnailBoss": "Улитка из подземелья Тяжелого труда",
"questSnailDropSnailEgg": "Моллюск (яйцо)",
"questSnailUnlockText": "Позволяет покупать на рынке моллюска в яйце",
- "questBewilderText": "The Be-Wilder",
- "questBewilderNotes": "The party begins like any other.
The appetizers are excellent, the music is swinging, and even the dancing elephants have become routine. Habiticans laugh and frolic amid the overflowing floral centerpieces, happy to have a distraction from their least-favorite tasks, and the April Fool whirls among them, eagerly providing an amusing trick here and a witty twist there.
As the Mistiflying clock tower strikes midnight, the April Fool leaps onto the stage to give a speech.
“Friends! Enemies! Tolerant acquaintances! Lend me your ears.” The crowd chuckles as animal ears sprout from their heads, and they pose with their new accessories.
“As you know,” the Fool continues, “my confusing illusions usually only last a single day. But I’m pleased to announce that I’ve discovered a shortcut that will guarantee us non-stop fun, without having to deal with the pesky weight of our responsibilities. Charming Habiticans, meet my magical new friend... the Be-Wilder!”
Lemoness pales suddenly, dropping her hors d'oeuvres. “Wait! Don’t trust--”
But suddenly mists are pouring into the room, glittering and thick, and they swirl around the April Fool, coalescing into cloudy feathers and a stretching neck. The crowd is speechless as an monstrous bird unfolds before them, its wings shimmering with illusions. It lets out a horrible screeching laugh.
“Oh, it has been ages since a Habitican has been foolish enough to summon me! How wonderful it feels, to have a tangible form at last.”
Buzzing in terror, the magic bees of Mistiflying flee the floating city, which sags from the sky. One by one, the brilliant spring flowers wither up and wisp away.
“My dearest friends, why so alarmed?” crows the Be-Wilder, beating its wings. “There’s no need to toil for your rewards any more. I’ll just give you all the things that you desire!”
A rain of coins pours from the sky, hammering into the ground with brutal force, and the crowd screams and flees for cover. “Is this a joke?” Baconsaur shouts, as the gold smashes through windows and shatters roof shingles.
PainterProphet ducks as lightning bolts crackle overhead, and fog blots out the sun. “No! This time, I don’t think it is!”
Quickly, Habiticans, don’t let this World Boss distract us from our goals! Stay focused on the tasks that you need to complete so we can rescue Mistiflying -- and hopefully, ourselves.",
+ "questBewilderText": "С-толку-сбиватель",
+ "questBewilderNotes": "Вечеринка начинается, как обычно.
Отличные закуски, задорная музыка, и даже танцующие слоны уже не вызывают недоумения. Жители страны Habitica смеются и кружатся среди переполненных цветами корзин, с готовностью исполняя ловкие трюки и элегантные повороты.
Часы на Таинственной башне бьют полночь, и Апрельский Дурак запрыгивает на сцену, чтобы произнести речь.
\"Друзья! Враги! Терпящие меня знакомые! Послушайте меня\". В толпе посмеиваются при виде звериных ушей, вылезающих из голов, и жители позируют со своими новыми аксессуарами.
\"Как вы знаете\", - продолжает Дурак, - \"обычно мои путающие иллюзии длятся только один день. Но я с радостью сообщаю, что я нашел простой способ сделать веселье бесконечным и не иметь дела с нашими неприятными повседневными обязанностями. Дорогие жители страны Habitica, встречайте, мой новый волшебный друг - Раз О'зли!\"
",
"questBewilderCompletion": "The Be-Wilder is DEFEATED!
We've done it! The Be-Wilder lets out a ululating cry as it twists in the air, shedding feathers like falling rain. Slowly, gradually, it coils into a cloud of sparkling mist. As the newly-revealed sun pierces the fog, it burns away, revealing the coughing, mercifully human forms of Bailey, Matt, Alex.... and the April Fool himself.
Mistiflying is saved!
The April Fool has enough shame to look a bit sheepish. “Oh, hm,” he says. “Perhaps I got a little…. carried away.”
The crowd mutters. Sodden flowers wash up on sidewalks. Somewhere in the distance, a roof collapses with a spectacular splash.
“Er, yes,” the April Fool says. “That is. What I meant to say was, I’m dreadfully sorry.” He heaves a sigh. “I suppose it can’t all be fun and games, after all. It might not hurt to focus occasionally. Maybe I’ll get a head start on next year’s pranking.”
Redphoenix coughs meaningfully.
“I mean, get a head start on this year’s spring cleaning!” the April Fool says. “Nothing to fear, I’ll have Habit City in spit-shape soon. Luckily nobody is better than I at dual-wielding mops.”
Encouraged, the marching band starts up.
It isn’t long before all is back to normal in Habit City. Plus, now that the Be-Wilder has evaporated, the magical bees of Mistiflying bustle back to work, and soon the flowers are blooming and the city is floating once more.
As Habiticans cuddle the magical fuzzy bees, the April Fool’s eyes light up. “Oho, I’ve had a thought! Why don’t you all keep some of these fuzzy Bee Pets and Mounts? It’s a gift that perfectly symbolizes the balance between hard work and sweet rewards, if I’m going to get all boring and allegorical on you.” He winks. “Besides, they don’t have stingers! Fool’s honor.”",
"questBewilderCompletionChat": "`The Be-Wilder is DEFEATED!`\n\nWe've done it! The Be-Wilder lets out a ululating cry as it twists in the air, shedding feathers like falling rain. Slowly, gradually, it coils into a cloud of sparkling mist. As the newly-revealed sun pierces the fog, it burns away, revealing the coughing, mercifully human forms of Bailey, Matt, Alex.... and the April Fool himself.\n\n`Mistiflying is saved!`\n\nThe April Fool has enough shame to look a bit sheepish. “Oh, hm,” he says. “Perhaps I got a little…. carried away.”\n\nThe crowd mutters. Sodden flowers wash up on sidewalks. Somewhere in the distance, a roof collapses with a spectacular splash.\n\n“Er, yes,” the April Fool says. “That is. What I meant to say was, I’m dreadfully sorry.” He heaves a sigh. “I suppose it can’t all be fun and games, after all. It might not hurt to focus occasionally. Maybe I’ll get a head start on next year’s pranking.”\n\nRedphoenix coughs meaningfully.\n\n“I mean, get a head start on this year’s spring cleaning!” the April Fool says. “Nothing to fear, I’ll have Habit City in spit-shape soon. Luckily nobody is better than I at dual-wielding mops.”\n\nEncouraged, the marching band starts up.\n\nIt isn’t long before all is back to normal in Habit City. Plus, now that the Be-Wilder has evaporated, the magical bees of Mistiflying bustle back to work, and soon the flowers are blooming and the city is floating once more.\n\nAs Habiticans cuddle the magical fuzzy bees, the April Fool’s eyes light up. “Oho, I’ve had a thought! Why don’t you all keep some of these fuzzy Bee Pets and Mounts? It’s a gift that perfectly symbolizes the balance between hard work and sweet rewards, if I’m going to get all boring and allegorical on you.” He winks. “Besides, they don’t have stingers! Fool’s honor.”",
- "questBewilderBossRageTitle": "Beguilement Strike",
+ "questBewilderBossRageTitle": "Отвлекающий удар",
"questBewilderBossRageDescription": "When this gauge fills, The Be-Wilder will unleash its Beguilement Strike on Habitica!",
- "questBewilderDropBumblebeePet": "Bumblebee (Pet)",
- "questBewilderDropBumblebeeMount": "Bumblebee (Mount)",
+ "questBewilderDropBumblebeePet": "Волшебная пчелка (Питомец)",
+ "questBewilderDropBumblebeeMount": "Волшебная пчелка (Скакун)",
"questBewilderBossRageMarket": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nOh no! Despite our best efforts, we've gotten distracted by the Be-Wilder’s charming illusions and have forgotten to do some of our Dailies! With a cackling cry, the shining bird beats its wings, raising a swarm of mist around Alex the Merchant. When the fog clears, he has been possessed! “Have some free samples!” he shouts gleefully, and begins to hurl exploding eggs and potions at fleeing Habiticans. Not the most favorable of sales, to be sure.\n\nHurry! Let's stay focused on our Dailies to defeat this monster before it possesses someone else.",
"questBewilderBossRageStables": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nAhh!!! Once again the Be-Wilder has dazzled us into neglecting our Dailies, and now it has attacked Matt the Beast Master! With a swirl of mist, Matt transforms into a terrifying winged creature, and all the pets and mounts howl sadly in their stables. Quickly, stay focused on your tasks to defeat this dastardly distraction!",
- "questBewilderBossRageBailey": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nLook out! In the middle of reporting the news, Bailey the Town Crier has been possessed by the Be-Wilder! She lets out an evil, uninformative screech as she rises into the air. Now how will we know what’s going on?\n\nDon't give up... we're so close to defeating this bothersome bird for once and for all!"
+ "questBewilderBossRageBailey": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nLook out! In the middle of reporting the news, Bailey the Town Crier has been possessed by the Be-Wilder! She lets out an evil, uninformative screech as she rises into the air. Now how will we know what’s going on?\n\nDon't give up... we're so close to defeating this bothersome bird for once and for all!",
+ "questFalconText": "Птицы прокрастинации",
+ "questFalconNotes": "Mt. Habitica is being overshadowed by a looming mountain of To-Dos. It used to be a place to picnic and enjoy a sense of accomplishment, until the neglected tasks grew out of control. Now it's home to fearsome Birds of Preycrastination, foul creatures which stop Habiticans from completing their tasks!
\"It's too hard!\" they caw at @JonArinbjorn and @Onheiron. \"It'll take too long to do right now! It won't make any difference if you wait until tomorrow! Why don't you do something fun instead?\"
No more, you vow. You will climb your personal mountain of To-Dos and defeat the Birds of Preycrastination!",
+ "questFalconCompletion": "Одержав окончательную победу над Птицами Прокрастинации вы присели чтобы, насладится видом и заслуженным отдыхом.
\"Вау!\" - сказал @Trogdorina. \"Ты выйграл!\"
@Squish добавил: \"Вот, возьми эти яйца, которые я нашел, в качестве награды.\"",
+ "questFalconBoss": "Птицы прокрастинации",
+ "questFalconDropFalconEgg": "Сокол (яйцо)",
+ "questFalconUnlockText": "Позволяет покупать на рынке сокола в яйце.",
+ "questTreelingText": "The Tangle Tree",
+ "questTreelingNotes": "It's the annual Garden Competition, and everyone is talking about the mysterious project which @aurakami has promised to unveil. You join the crowd on the day of the big announcement, and marvel at the introduction of a moving tree. @fuzzytrees explains that the tree will help with garden maintenance, showing how it can mow the lawn, trim the hedge and prune the roses all at the same time – until the tree suddenly goes wild, turning its secateurs on its creator! The crowd panics as everyone tries to flee, but you aren't afraid – you leap forward, ready to do battle.",
+ "questTreelingCompletion": "You dust yourself off as the last few leaves drift to the floor. In spite of the upset, the Garden Competition is now safe – although the tree you just reduced to a heap of wood chips won't be winning any prizes! \"Still a few kinks to work out there,\" @PainterProphet says. \"Perhaps someone else would do a better job of training the saplings. Do you fancy a go?\"",
+ "questTreelingBoss": "Tangle Tree",
+ "questTreelingDropTreelingEgg": "Treeling (Egg)",
+ "questTreelingUnlockText": "Unlocks purchasable Treeling eggs in the Market"
}
\ No newline at end of file
diff --git a/common/locales/ru/rebirth.json b/common/locales/ru/rebirth.json
index 507a0ba69a..a987c202ab 100644
--- a/common/locales/ru/rebirth.json
+++ b/common/locales/ru/rebirth.json
@@ -5,7 +5,7 @@
"rebirthStartOver": "Перерождение позволит вашему персонажу начать всё сначала с 1 уровня.",
"rebirthAdvList1": "Ваше здоровье полностью восстанавливается.",
"rebirthAdvList2": "У вас нет ни опыта, ни золота, ни предметов (за исключением бесплатных, например мистических).",
- "rebirthAdvList3": "Ваши привычки, ежедневные задачи и список дел станут вновь желтыми, а счетчики серий выполненных подряд заданий обнулятся.",
+ "rebirthAdvList3": "Your Habits, Dailies, and To-Dos reset to yellow, and streaks reset, except for challenge tasks.",
"rebirthAdvList4": "У вас будет начальный класс воина, пока вы не заработаете новый класс.",
"rebirthInherit": "Ваш новый персонаж унаследует несколько вещей от своего предшественника:",
"rebirthInList1": "Задания, история и настройки останутся.",
@@ -24,5 +24,6 @@
"rebirthPop": "Начните заново с персонажем 1 уровня, сохранив достижения, коллекционные предметы и задания с историей.",
"rebirthName": "Шар возрождения",
"reborn": "Возрождение, макс. уровень <%= reLevel %>",
- "confirmReborn": "Вы уверены?"
+ "confirmReborn": "Вы уверены?",
+ "rebirthComplete": "You have been reborn!"
}
\ No newline at end of file
diff --git a/common/locales/ru/settings.json b/common/locales/ru/settings.json
index 36164edc36..24abe566a5 100644
--- a/common/locales/ru/settings.json
+++ b/common/locales/ru/settings.json
@@ -47,6 +47,7 @@
"customDayStart": "Персональное начало суток",
"changeCustomDayStart": "Изменить персональное начало суток?",
"sureChangeCustomDayStart": "Вы уверены, что хотите изменить персональное начало суток?",
+ "customDayStartHasChanged": "Your custom day start has changed.",
"nextCron": "Ваши ежедневные задания будут обнулены при первом же посещении сайта после <%= time %>. Убедитесь, что вы выполнили ежедневные задания до этого времени!",
"customDayStartInfo1": "Habitica по умолчанию проверяет и сбрасывает ваши ежедневные задачи каждый день в полночь по вашему часовому поясу. Вы можете настроить это время здесь.",
"misc": "Разное",
@@ -61,12 +62,23 @@
"newUsername": "Новое имя пользователя",
"dangerZone": "Опасная зона",
"resetText1": "Осторожно! Это обнулит многое в вашем аккаунте. Использовать эту функцию крайне не рекомендуется, однако некоторым игрокам она может пригодится в начале — после того, как они попробуют приложение.",
- "resetText2": "Вы потеряете уровень, всё свое золото и очки опыта. Все ваши задачи будут удалены навсегда и вы навсегда потеряете историю выполнения заданий. Вы также потеряете всё снаряжение, но сможете купить его снова, включая снаряжение ограниченного выпуска и таинственные предметы подписчиков, которые у вас уже есть (если снаряжение, которое вы хотите выкупить, предназначено для определенного класса, ваш класс должен соответствовать ему). Вы сохраните свой класс, питомцев и скакунов. Возможно, вам лучше воспользоваться шаром возрождения — он намного безопаснее и вы сохраните все свои задачи.",
+ "resetText2": "You will lose all your levels, gold, and experience points. All your tasks (except those from challenges) will be deleted permanently and you will lose all of their historical data. You will lose all your equipment but you will be able to buy it all back, including all limited edition equipment or subscriber Mystery items that you already own (you will need to be in the correct class to re-buy class-specific gear). You will keep your current class and your pets and mounts. You might prefer to use an Orb of Rebirth instead, which is a much safer option and which will preserve your tasks.",
"deleteText": "Вы уверены? Ваш аккаунт будет удален навсегда без возможности восстановления! Вам понадобится зарегистрировать новый аккаунт, если вы захотите пользоваться Habitica снова. Стоимость потраченных и находящихся в банке самоцветов не будет компенсирована. Если вы абсолютно уверены, напишите <%= deleteWord %> в текстовом поле ниже.",
"API": "API",
+ "APIv3": "API v3",
+ "APIv2": "API v2 - Deprecated",
"APIText": "Скопируйте, чтобы использовать в сторонних приложениях. Однако, относитесь к токену API, как к паролю — не выкладывайте его в открытый доступ. Иногда у вас могут попросить ваш ID пользователя, но никогда не публикуйте токен API там, где его могут увидеть другие, включая Github.",
"APIToken": "Токен API (это пароль — смотри предупреждение выше!)",
+ "thirdPartyApps": "Сторонние приложения",
+ "dataToolDesc": "Веб-страница, которая отображает определенную информацию из вашего аккаунта в Habitica, такую как статистика по задачам, снаряжение и умения.",
+ "beeminder": "Beeminder",
+ "beeminderDesc": "Позвольте Пчелиному Напоминателю автоматически отслеживать прогресс по списку задач в стране Habitica. Вы можете дать обещание выполнять определённое количество задач в день или в неделю, или же можно просто пообещать постепенно выполнять задачи из списка. (Под \"обещанием\" Пчелиный Напоминатель понимает угрозу денежного штрафа! Но может быть вам просто понравится, как он выглядит.)",
+ "chromeChatExtension": "Чат дополнение для Chrome",
+ "chromeChatExtensionDesc": "Чат дополнение для Chrome добавит область с чатом на все страницы habitica.com. Позволит общаться пользователям где бы они ни были: в таверне, команде или в любой гильдии.",
+ "otherExtensions": "Другие расширения",
+ "otherDesc": "Ищите другие приложения, дополнения и инструменты на Habitica wiki.",
"resetDo": "Да, сделать сброс учетной записи!",
+ "resetComplete": "Reset complete!",
"fixValues": "Исправить данные",
"fixValuesText1": "Если в результате программной ошибки или непреднамеренных действий изменились параметры персонажа (урон, который не должен был быть причинен; золото, которое вы на самом деле не заработали), вы можете вручную исправить значения. Да, таким образом возможно жульничать, поэтому мудро используйте эту функцию, если не хотите подорвать весь процесс выработки привычек!",
"fixValuesText2": "Обратите внимание, что здесь невозможно восстановить серии выполненных подряд отдельных заданий. Для этого войдите в редактирование ежедневного задания и воспользуйтесь полем – «Восстановить серию» в разделе «Дополнительные параметры».",
@@ -96,6 +108,7 @@
"emailNotifications": "Уведомления по электронной почте",
"wonChallenge": "Вы выиграли испытание",
"newPM": "Получено личное сообщение",
+ "sentGems": "Sent gems!",
"giftedGems": "Подаренные самоцветы",
"giftedGemsInfo": "<%= amount %> самоцветов от <%= name %>",
"giftedSubscription": "Подписка в подарок",
@@ -135,6 +148,11 @@
"webhooks": "Веб-хуки",
"enabled": "Включено",
"webhookURL": "Ссылка на веб-хук",
+ "invalidUrl": "invalid url",
+ "invalidEnabled": "the \"enabled\" parameter should be a boolean",
+ "regIdRequired": "RegId is required",
+ "pushDeviceAdded": "Push device added successfully",
+ "pushDeviceAlreadyAdded": "The user already has the push device",
"add": "Добавить",
"buyGemsGoldCap": "Предел повышен до <%= amount %>",
"mysticHourglass": "Кол-во мистических песочных часов: <%= amount %>",
@@ -149,5 +167,5 @@
"amazonPayments": "Платеж с помощью Amazon",
"timezone": "Часовой пояс",
"timezoneUTC": "Habitica использует часовой пояс, установленный на вашем компьютере. В данный момент это: <%= utc %>",
- "timezoneInfo": "Если часовой пояс определен неверно, сначала следует перезагрузить страницу, нажав на кнопку \"обновить\" в вашем браузере, чтобы убедиться, что используется текущее время. Если время по-прежнему определено неверно, поправьте установки времени на вашем компьютере и снова перезагрузите эту страницу.
Если вы используете Habitica на нескольких компьютерах или мобильных устройствах, необходимо установить одинаковый часовой пояс на всех используемых устройствах. Если ваши Ежедневные задачи сбрасываются в неправильное время, повторите эту проверку на всех остальных компьютерах и в браузерах на мобильных устройствах."
+ "timezoneInfo": "If that time zone is wrong, first reload this page using your browser's reload or refresh button to ensure that Habitica has the most recent information. If it is still wrong, adjust the time zone on your PC and then reload this page again.
If you use Habitica on other PCs or mobile devices, the time zone must be the same on them all. If your Dailies have been resetting at the wrong time, repeat this check on all other PCs and on a browser on your mobile devices."
}
\ No newline at end of file
diff --git a/common/locales/ru/spells.json b/common/locales/ru/spells.json
index de7809cd94..9a6ec57186 100644
--- a/common/locales/ru/spells.json
+++ b/common/locales/ru/spells.json
@@ -35,8 +35,8 @@
"spellSpecialSnowballAuraNotes": "Бросьте снежок в товарища по команде! Что может пойти не так? Эффект длится до наступления нового дня компаньона.",
"spellSpecialSaltText": "Соль",
"spellSpecialSaltNotes": "Кто-то кинул в вас снежок. Ха-ха, очень смешно. А теперь стряхните с меня снег!",
- "spellSpecialSpookDustText": "Зловещие искры",
- "spellSpecialSpookDustNotes": "Превратите друга в парящую простыню с глазами!",
+ "spellSpecialSpookySparklesText": "Spooky Sparkles",
+ "spellSpecialSpookySparklesNotes": "Turn a friend into a floating blanket with eyes!",
"spellSpecialOpaquePotionText": "Зелье Непроницаемости",
"spellSpecialOpaquePotionNotes": "Отменить эффект Зловещих искр",
"spellSpecialShinySeedText": "Солнечное семя",
@@ -46,5 +46,11 @@
"spellSpecialSeafoamText": "Морская пена",
"spellSpecialSeafoamNotes": "Превратите друга в морское создание!",
"spellSpecialSandText": "Песок",
- "spellSpecialSandNotes": "Отменить эффект морской пены."
+ "spellSpecialSandNotes": "Отменить эффект морской пены.",
+ "spellNotFound": "Skill \"<%= spellId %>\" not found.",
+ "partyNotFound": "Party not found",
+ "targetIdUUID": "\"targetId\" must be a valid User ID.",
+ "challengeTasksNoCast": "Casting a skill on challenge tasks is not allowed.",
+ "spellNotOwned": "You don't own this skill.",
+ "spellLevelTooHigh": "You must be level <%= level %> to use this skill."
}
\ No newline at end of file
diff --git a/common/locales/ru/subscriber.json b/common/locales/ru/subscriber.json
index ad3a8fcbd3..d961c5e74d 100644
--- a/common/locales/ru/subscriber.json
+++ b/common/locales/ru/subscriber.json
@@ -4,6 +4,8 @@
"subDescription": "Покупайте самоцветы за золото, получайте таинственные предметы каждый месяц, сохраняйте историю своего прогресса, удвойте ежедневный предел выпадения трофеев, поддерживайте разработчиков. Нажмите, чтобы узнать больше.",
"buyGemsGold": "Возможность покупки самоцветов за золото",
"buyGemsGoldText": "Торговец Александр будет продавать вам Самоцветы по цене <%= gemCost %> золота за каждый. Его ежемесячные поставки изначально ограничены до <%= gemLimit %> Самоцветов в месяц. Но это ограничение увеличивается на 5 Самоцветов каждые три месяца последовательной подписки вплоть до максимума в 50 Самоцветов в месяц!",
+ "mustSubscribeToPurchaseGems": "Must subscribe to purchase gems with GP",
+ "reachedGoldToGemCap": "You've reached the Gold=>Gem conversion cap <%= convCap %> for this month. We have this to prevent abuse / farming. The cap will reset within the first three days of next month.",
"retainHistory": "Увеличенный список выполненных задач",
"retainHistoryText": "Завершенные задачи дольше дольше не архивируются и доступны для просмотра.",
"doubleDrops": "Увеличение дневного предела выпадения предметов в 2 раза",
@@ -29,6 +31,7 @@
"manageSub": "Нажмите для управления подпиской",
"cancelSub": "Отмена подписки",
"canceledSubscription": "Подписка отменена",
+ "cancelingSubscription": "Canceling the subscription",
"adminSub": "Подписки администратора",
"morePlans": "Больше тарифов Скоро",
"organizationSub": "Частная организация",
@@ -73,6 +76,9 @@
"timeTravelersPopover": "Видно, что вы с Мистическими песочными часами, так что мы с радостью отправимся для вас в прошлое! Пожалуйста, выберите себе питомца, скакуна, или Комплект Таинственных предметов. Вы можете увидеть список прошлых комплектов здесь! Если они вам не подходят, возможно, вы будете заинтересованы в одном из наших модных футуристических Комплектов в стиле Стимпанк?",
"timeTravelersAlreadyOwned": "Поздравляем! У вас уже есть все предметы, которые могут предложить путешественники во времени. Благодарим за поддержку сайта!",
"mysticHourglassPopover": "Загадочные песочные часы позволят вам приобрести определённые предметы, которые были лишь временно доступны когда-то. Например, наборы таинственных предметов и награды за мировых боссов. Прямо из прошлого!",
+ "mysterySetNotFound": "Mystery set not found, or set already owned.",
+ "mysteryItemIsEmpty": "Mystery items are empty",
+ "mysteryItemOpened": "Mystery item opened.",
"mysterySet201402": "Набор Крылатого посланника",
"mysterySet201403": "Набор Лесничего",
"mysterySet201404": "Набор Сумеречной бабочки",
@@ -99,6 +105,8 @@
"mysterySet201601": "Набор Чемпиона Решительности",
"mysterySet201602": "Набор Сердцееда",
"mysterySet201603": "Набор Счастливого Клевера",
+ "mysterySet201604": "Leaf Warrior Set",
+ "mysterySet201605": "Marching Bard Set",
"mysterySet301404": "Стандартный Стимпанковый набор",
"mysterySet301405": "Набор аксессуаров в стиле Стимпанка",
"mysterySetwondercon": "Вандер-Кон",
@@ -110,9 +118,25 @@
"hourglassBuyItemConfirm": "Купить этот предмет за 1 мистические песочные часы?",
"petsAlreadyOwned": "У вас уже есть этот питомец.",
"mountsAlreadyOwned": "У вас уже есть этот скакун.",
- "typeNotAllowedHourglass": "Покупка предметов этого типа с помощью мистических песочных часов не поддерживается. Допустимые типы:",
+ "typeNotAllowedHourglass": "Item type not supported for purchase with Mystic Hourglass. Allowed types: <%= allowedTypes %>",
"petsNotAllowedHourglass": "Этого питомца нельзя купить за мистические песочные часы.",
"mountsNotAllowedHourglass": "Этого скакуна нельзя купить за мистические песочные часы.",
"hourglassPurchase": "Приобретён предмет за мистические песочные часы!",
- "hourglassPurchaseSet": "Приобретён набор предметов за мистические песочные часы!"
+ "hourglassPurchaseSet": "Приобретён набор предметов за мистические песочные часы!",
+ "missingUnsubscriptionCode": "Missing unsubscription code.",
+ "missingSubscription": "User does not have a plan subscription",
+ "missingSubscriptionCode": "Missing subscription code. Possible values: basic_earned, basic_3mo, basic_6mo, google_6mo, basic_12mo.",
+ "cannotDeleteActiveAccount": "You have an active subscription, cancel your plan before deleting your account.",
+ "paymentNotSuccessful": "The payment was not successful",
+ "planNotActive": "The plan hasn't activated yet (due to a PayPal bug). It will begin <%= nextBillingDate %>, after which you can cancel to retain your full benefits",
+ "notAllowedHourglass": "Pet/Mount not available for purchase with Mystic Hourglass.",
+ "readCard": "<%= cardType %> has been read",
+ "cardTypeRequired": "Card type required",
+ "cardTypeNotAllowed": "Unknown card type.",
+ "invalidCoupon": "Invalid coupon code.",
+ "couponUsed": "Coupon code already used.",
+ "noSudoAccess": "You don't have sudo access.",
+ "couponCodeRequired": "The coupon code is required.",
+ "eventRequired": "\"req.params.event\" is required.",
+ "countRequired": "\"req.query.count\" is required."
}
\ No newline at end of file
diff --git a/common/locales/ru/tasks.json b/common/locales/ru/tasks.json
index 3d23c20de7..01d64edd52 100644
--- a/common/locales/ru/tasks.json
+++ b/common/locales/ru/tasks.json
@@ -32,7 +32,7 @@
"mental": "Умственное",
"otherExamples": "Например, профессиональные цели, хобби, финансы и т. д.",
"progress": "Прогресс",
- "daily": "Ежедневное задание",
+ "daily": "Ежедневные задания",
"dailies": "Ежедневное",
"newDaily": "Новое ежедневное задание",
"newDailyBulk": "Новые ежедневные задания (по одному на строку)",
@@ -73,6 +73,7 @@
"clearTags": "Очистить",
"hideTags": "Скрыть",
"showTags": "Показать",
+ "toRequired": "You must supply a to value",
"startDate": "Дата начала",
"startDateHelpTitle": "Когда это задание должно начаться?",
"startDateHelp": "Задайте дату, в этот день задание вступит в силу. До этого оно будет необязательным.",
@@ -88,8 +89,9 @@
"fortifyName": "Эликсир укрепления",
"fortifyPop": "Вернуть все задания в нейтральное состояние (желтый цвет) и восстановить всё здоровье.",
"fortify": "Укрепление",
- "fortifyText": "Укрепление вернёт все ваши задания в нейтральный (жёлтый) цвет, как будто бы вы их только что добавили, и восстановит всё здоровье до максимума. Это пригодится, если все ваши красные задания слишком усложняют, а синие — слишком упрощают игру. Если новое, свежее начало даст вам больше сил и мотивации, не пожалейте самоцветов и получите временное облегчение!",
+ "fortifyText": "Fortify will return all your tasks, except challenge tasks, to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!",
"confirmFortify": "Вы уверены?",
+ "fortifyComplete": "Fortify complete!",
"sureDelete": "Вы уверены, что хотите удалить <%= taskType %> с текстом \"<%= taskText %>\"?",
"streakCoins": "Бонус за серию!",
"pushTaskToTop": "Нажмите, чтобы переместить в начало списка. Удерживайте ctrl или cmd при нажатии, чтобы переместить в конец.",
@@ -112,5 +114,18 @@
"rewardHelp2": "Снаряжение влияет на ваши характеристики (<%= linkStart %>Пользователь > Характеристики<%= linkEnd %>).",
"rewardHelp3": "Здесь появится особое снаряжение во время Мировых Событий.",
"rewardHelp4": "Не бойтесь назначать себе персональные награды! Посмотрите примеры наград здесь.",
- "clickForHelp": "Помощь"
+ "clickForHelp": "Помощь",
+ "taskIdRequired": "\"taskId\" must be a valid UUID.",
+ "taskNotFound": "Task not found.",
+ "invalidTaskType": "Task type must be one of \"habit\", \"daily\", \"todo\", \"reward\".",
+ "cantDeleteChallengeTasks": "A task belonging to a challenge can't be deleted.",
+ "checklistOnlyDailyTodo": "Checklists are supported only on dailies and todos",
+ "checklistItemNotFound": "No checklist item was found with given id.",
+ "itemIdRequired": "\"itemId\" must be a valid UUID.",
+ "tagNotFound": "No tag item was found with given id.",
+ "tagIdRequired": "\"tagId\" must be a valid UUID corresponding to a tag belonging to the user.",
+ "positionRequired": "\"position\" is required and must be a number.",
+ "cantMoveCompletedTodo": "Can't move a completed todo.",
+ "directionUpDown": "\"direction\" is required and must be 'up' or 'down'",
+ "alreadyTagged": "The task is already tagged with given tag."
}
\ No newline at end of file
diff --git a/common/locales/sk/backgrounds.json b/common/locales/sk/backgrounds.json
index a87d08ee0a..8bfd63bfac 100644
--- a/common/locales/sk/backgrounds.json
+++ b/common/locales/sk/backgrounds.json
@@ -98,67 +98,74 @@
"backgroundGiantWaveNotes": "Surfuj na obrovskej vlne!",
"backgroundSunkenShipText": "Potopená loď",
"backgroundSunkenShipNotes": "Preskúmaj potopenú loď.",
- "backgrounds082015": "SET 15: Released August 2015",
- "backgroundPyramidsText": "Pyramids",
- "backgroundPyramidsNotes": "Admire the Pyramids.",
- "backgroundSunsetSavannahText": "Sunset Savannah",
- "backgroundSunsetSavannahNotes": "Stalk across the Sunset Savannah.",
- "backgroundTwinklyPartyLightsText": "Twinkly Party Lights",
- "backgroundTwinklyPartyLightsNotes": "Dance under Twinkly Party Lights!",
- "backgrounds092015": "SET 16: Released September 2015",
- "backgroundMarketText": "Habitica Market",
- "backgroundMarketNotes": "Shop in the Habitica Market.",
- "backgroundStableText": "Habitica Stable",
- "backgroundStableNotes": "Tend mounts in the Habitica Stable.",
- "backgroundTavernText": "Habitica Tavern",
- "backgroundTavernNotes": "Visit the Habitica Tavern.",
- "backgrounds102015": "SET 17: Released October 2015",
- "backgroundHarvestMoonText": "Harvest Moon",
- "backgroundHarvestMoonNotes": "Cackle under the Harvest Moon.",
- "backgroundSlimySwampText": "Slimy Swamp",
- "backgroundSlimySwampNotes": "Slog through a Slimy Swamp.",
- "backgroundSwarmingDarknessText": "Swarming Darkness",
- "backgroundSwarmingDarknessNotes": "Shiver in the Swarming Darkness.",
- "backgrounds112015": "SET 18: Released November 2015",
- "backgroundFloatingIslandsText": "Floating Islands",
- "backgroundFloatingIslandsNotes": "Hop across the Floating Islands.",
- "backgroundNightDunesText": "Night Dunes",
- "backgroundNightDunesNotes": "Walk peacefully through the Night Dunes.",
- "backgroundSunsetOasisText": "Sunset Oasis",
- "backgroundSunsetOasisNotes": "Bask in the Sunset Oasis.",
- "backgrounds122015": "SET 19: Released December 2015",
- "backgroundAlpineSlopesText": "Alpine Slopes",
- "backgroundAlpineSlopesNotes": "Ski on the Alpine Slopes.",
- "backgroundSnowySunriseText": "Snowy Sunrise",
- "backgroundSnowySunriseNotes": "Gaze at the Snowy Sunrise.",
- "backgroundWinterTownText": "Winter Town",
- "backgroundWinterTownNotes": "Bustle through a Winter Town.",
- "backgrounds012016": "SET 20: Released January 2016",
- "backgroundFrozenLakeText": "Frozen Lake",
- "backgroundFrozenLakeNotes": "Skate on a Frozen Lake.",
- "backgroundSnowmanArmyText": "Snowman Army",
- "backgroundSnowmanArmyNotes": "Lead a Snowman Army.",
- "backgroundWinterNightText": "Winter Night",
- "backgroundWinterNightNotes": "Look at the stars of a Winter Night.",
- "backgrounds022016": "SET 21: Released February 2016",
- "backgroundBambooForestText": "Bamboo Forest",
- "backgroundBambooForestNotes": "Stroll through the Bamboo Forest.",
- "backgroundCozyLibraryText": "Cozy Library",
- "backgroundCozyLibraryNotes": "Read in the Cozy Library.",
- "backgroundGrandStaircaseText": "Grand Staircase",
- "backgroundGrandStaircaseNotes": "Stride down the Grand Staircase.",
- "backgrounds032016": "SET 22: Released March 2016",
- "backgroundDeepMineText": "Deep Mine",
- "backgroundDeepMineNotes": "Find precious metals in a Deep Mine.",
- "backgroundRainforestText": "Rainforest",
- "backgroundRainforestNotes": "Venture into a Rainforest.",
- "backgroundStoneCircleText": "Circle of Stones",
- "backgroundStoneCircleNotes": "Cast spells in a Circle of Stones.",
- "backgrounds042016": "SET 23: Released April 2016",
- "backgroundArcheryRangeText": "Archery Range",
- "backgroundArcheryRangeNotes": "Practice on the Archery Range.",
- "backgroundGiantFlowersText": "Giant Flowers",
- "backgroundGiantFlowersNotes": "Frolic atop Giant Flowers.",
- "backgroundRainbowsEndText": "End of the Rainbow",
- "backgroundRainbowsEndNotes": "Discover gold at the End of the Rainbow."
+ "backgrounds082015": "15. sada: Vydaná v auguste 2015",
+ "backgroundPyramidsText": "Pyramídy",
+ "backgroundPyramidsNotes": "Obdivuj pyramídy. ",
+ "backgroundSunsetSavannahText": "Západ slnka v Savane",
+ "backgroundSunsetSavannahNotes": "Kráčaj oproti Západu slnka v Savane.",
+ "backgroundTwinklyPartyLightsText": "Žiariace Párty Svetlá",
+ "backgroundTwinklyPartyLightsNotes": "Tancuj pod Žiariacimi Párty Svetlami!",
+ "backgrounds092015": "16. sada: Vydaná v septembri 2015",
+ "backgroundMarketText": "Trh Habitiky",
+ "backgroundMarketNotes": "Obchoduj v Trhu Habitiky.",
+ "backgroundStableText": "Stajňa Habitiky",
+ "backgroundStableNotes": "Staraj sa o svoje tátoše v Stajni Habitiky.",
+ "backgroundTavernText": "Hostinec Habitiky",
+ "backgroundTavernNotes": "Navštív Hostinec Habitiky.",
+ "backgrounds102015": "17. sada: Vydaná v októbri 2015",
+ "backgroundHarvestMoonText": "Jesenná rovnodennosť",
+ "backgroundHarvestMoonNotes": "Prechádzka počas jesennej rovnodennosti.",
+ "backgroundSlimySwampText": "Slizký močiar",
+ "backgroundSlimySwampNotes": "Prebroď sa slizkým močiarom.",
+ "backgroundSwarmingDarknessText": "Roj temnoty",
+ "backgroundSwarmingDarknessNotes": "Chvej sa v roji temnoty.",
+ "backgrounds112015": "18. sada: Vydaná v novembri 2015",
+ "backgroundFloatingIslandsText": "Plávajúce ostrovy",
+ "backgroundFloatingIslandsNotes": "Preskakuj naprieč plávajúcimi ostrovmi.",
+ "backgroundNightDunesText": "Nočné duny",
+ "backgroundNightDunesNotes": "Mierumilovne sa prechádzaj nočnými dunami.",
+ "backgroundSunsetOasisText": "Oáza zapadajúceho slnka",
+ "backgroundSunsetOasisNotes": "Vyhrievaj sa pri oáze zapadajúceho slnka.",
+ "backgrounds122015": "19. sada: vydaná v decembri 2015",
+ "backgroundAlpineSlopesText": "Alpské svahy",
+ "backgroundAlpineSlopesNotes": "Lyžuj sa na alpských svahoch",
+ "backgroundSnowySunriseText": "Zasnežený východ slnka",
+ "backgroundSnowySunriseNotes": "Zadívaj sa na zasnežený východ slnka.",
+ "backgroundWinterTownText": "Zimné mestečko",
+ "backgroundWinterTownNotes": "Pobehuj zasneženým mestečkom.",
+ "backgrounds012016": "20. sada: Vydaná v Januári 2016",
+ "backgroundFrozenLakeText": "Zmrznuté jazero",
+ "backgroundFrozenLakeNotes": "Korčuľuj sa na zmrznutom jazere.",
+ "backgroundSnowmanArmyText": "Armáda snehuliakov",
+ "backgroundSnowmanArmyNotes": "Veď armádu snehuliakov.",
+ "backgroundWinterNightText": "Zimná noc",
+ "backgroundWinterNightNotes": "Pozoruj hviezdy zimnej noci.",
+ "backgrounds022016": "21. sada: Vydaná vo februári 2016",
+ "backgroundBambooForestText": "Bambusový les",
+ "backgroundBambooForestNotes": "Prechádzaj sa bambusovým lesom.",
+ "backgroundCozyLibraryText": "Útulná knižnica",
+ "backgroundCozyLibraryNotes": "Čítaj si v útulnej knižnici.",
+ "backgroundGrandStaircaseText": "Veľké schodisko",
+ "backgroundGrandStaircaseNotes": "Kráčaj dolu veľkým schodiskom.",
+ "backgrounds032016": "22. sada: Vydaná v marci 2016",
+ "backgroundDeepMineText": "Hlboká baňa",
+ "backgroundDeepMineNotes": "Nájdi vzácne kovy v hlbokej bani.",
+ "backgroundRainforestText": "Dažďový prales",
+ "backgroundRainforestNotes": "Odváž sa vstúpiť do dažďového pralesa.",
+ "backgroundStoneCircleText": "Kamenný kruh",
+ "backgroundStoneCircleNotes": "Čaruj v kamennom kruhu.",
+ "backgrounds042016": "23. sada: Vydaná v apríli 2016",
+ "backgroundArcheryRangeText": "Lukostrelnica",
+ "backgroundArcheryRangeNotes": "Trénuj v lukostrelnici.",
+ "backgroundGiantFlowersText": "Obrovské kvety",
+ "backgroundGiantFlowersNotes": "Šanti navrchu obrovských kvetov.",
+ "backgroundRainbowsEndText": "Koniec dúhy",
+ "backgroundRainbowsEndNotes": "Nájdi zlato na konci dúhy",
+ "backgrounds052016": "24. sada: Vydaná v máji 2016",
+ "backgroundBeehiveText": "Včelí úľ",
+ "backgroundBeehiveNotes": "Bzuč a tancuj vo včeľom úli. ",
+ "backgroundGazeboText": "Terasa",
+ "backgroundGazeboNotes": "Bojuj na terase.",
+ "backgroundTreeRootsText": "Korene stromov",
+ "backgroundTreeRootsNotes": "Preskúmaj korene stromov."
}
\ No newline at end of file
diff --git a/common/locales/sk/challenge.json b/common/locales/sk/challenge.json
index b499fd1143..72fe392b39 100644
--- a/common/locales/sk/challenge.json
+++ b/common/locales/sk/challenge.json
@@ -33,7 +33,7 @@
"challengeTagPop": "Výzvy sa zobrazujú v zozname štítkov a v popise úlohy. Takže oficiálny názov výzvy môže byť dlhší a výstižný, no potrebuješ aj krátku verziu. Napr výzva s názvom \"Schudnúť 10 kíl za 3 mesiace\" môže používať štítok \"-10kg\" (Pre viac informácií klikni na \"?\").",
"challengeDescr": "Popis",
"prize": "Odmena",
- "prizePop": "If someone can 'win' your challenge, you can optionally award that winner a Gem prize. The maximum number you can award is the number of gems you own (plus the number of guild gems, if you created this challenge's guild). Note: This prize can't be changed later.",
+ "prizePop": "Ak niekto \"vyhrá\" tvoju výzvu, môžeš odmeniť víťaza drahokamami. Maximálny počet drahokamov, ktorými môžeš odmeniť je počet drahokamov ktoré vlastníš (+ počet drahokamov cechu, ak si vytvoril výzvu v rámci cechu). Poznámka: Túto odmenu už nemôžeš neskôr zmeniť.",
"prizePopTavern": "Ak niekto môže 'vyhrať' tvoju výzvu, môžeš toho víťaza odmeniť drahokamami. Maximum = počet tvojich drahokamov. Poznámka: Túto odmenu neskôr už nemôžeš zmeniť a drahokamy sa ti nevrátia ak výzvu zrušíš.",
"publicChallenges": "Minimum je 1 drahokam pre verejné výzvy (pomáha predísť spamu, naozaj).",
"officialChallenge": "Oficiálna výzva Habiticy",
@@ -43,8 +43,8 @@
"exportChallengeCSV": "Exportovať do CSV",
"selectGroup": "Prosím, vyber skupinu",
"challengeCreated": "Výzva bola úspešne vytvorená",
- "sureDelCha": "Are you sure you want to delete this challenge?",
- "sureDelChaTavern": "Are you sure you want to delete this challenge? Your gems will not be refunded.",
+ "sureDelCha": "Si si istý, že chceš zmazať túto výzvu?",
+ "sureDelChaTavern": "Si si istý, že chceš zmazať túto výzvu? Drahokamy sa ti nevrátia.",
"removeTasks": "Odstrániť úlohy",
"keepTasks": "Ponechať úlohy",
"closeCha": "Uzavrieť výzvu a...",
@@ -57,11 +57,26 @@
"prizeValue": "<%= gemcount %> <%= gemicon %> výhra",
"clone": "Klonovať",
"challengeNotEnoughGems": "Na vytvorenie výzvy nemáš dosť drahokamov.",
- "noPermissionEditChallenge": "You don't have permissions to edit this challenge",
- "noPermissionDeleteChallenge": "You don't have permissions to delete this challenge",
- "noPermissionCloseChallenge": "You don't have permissions to close this challenge",
- "congratulations": "Congratulations!",
- "hurray": "Hurray!",
- "noChallengeOwner": "no owner",
- "noChallengeOwnerPopover": "This challenge does not have an owner because the person who created the challenge deleted their account."
+ "noPermissionEditChallenge": "Nemáš povolenie upravovať túto výzvu ",
+ "noPermissionDeleteChallenge": "Nemáš povolenie zmazať túto výzvu",
+ "noPermissionCloseChallenge": "Nemáš povolenie uzavrieť túto výzvu",
+ "congratulations": "Gratulujeme!",
+ "hurray": "Hurá!",
+ "noChallengeOwner": "žiadny vlastník",
+ "noChallengeOwnerPopover": "Táto výzva nemá vlastníka, pretože osoba, ktorá túto výzvu vytvorila, zmazala svoj účet.",
+ "challengeMemberNotFound": "User not found among challenge's members",
+ "onlyGroupLeaderChal": "Only the group leader can create challenges",
+ "tavChalsMinPrize": "Prize must be at least 1 Gem for Tavern challenges.",
+ "cantAfford": "You can't afford this prize. Purchase more gems or lower the prize amount.",
+ "challengeIdRequired": "\"challengeId\" must be a valid UUID.",
+ "winnerIdRequired": "\"winnerId\" must be a valid UUID.",
+ "challengeNotFound": "Challenge not found.",
+ "onlyLeaderDeleteChal": "Only the challenge leader can delete it.",
+ "onlyLeaderUpdateChal": "Only the challenge leader can update it.",
+ "winnerNotFound": "Winner with id \"<%= userId %>\" not found or not part of the challenge.",
+ "noCompletedTodosChallenge": "\"includeComepletedTodos\" is not supported when fetching a challenge tasks.",
+ "userTasksNoChallengeId": "When \"tasksOwner\" is \"user\" \"challengeId\" can't be passed.",
+ "onlyChalLeaderEditTasks": "Tasks belonging to a challenge can only be edited by the leader.",
+ "userAlreadyInChallenge": "User is already participating in this challenge.",
+ "cantOnlyUnlinkChalTask": "Only broken challenges tasks can be unlinked."
}
\ No newline at end of file
diff --git a/common/locales/sk/character.json b/common/locales/sk/character.json
index 90a65df1e9..c00fecd300 100644
--- a/common/locales/sk/character.json
+++ b/common/locales/sk/character.json
@@ -1,7 +1,8 @@
{
+ "communityGuidelinesWarning": "Maj na pamäti, že tvoja zobrazované meno, profilová fotka a informácie o tebe musia vyhovovať Komunitných pravidiel (napr. žiadne vulgarizmy, témy len pre dospelých, urážky a pod.). Ak máš nejaké otázky ohľadom toho, či je niečo vhodné alebo nevhodné, napíš na leslie@habitica.com!",
"statsAch": "Štatistiky a odznaky",
"profile": "Profil",
- "avatar": "Customize Avatar",
+ "avatar": "Prispôsobiť avatara",
"other": "Ostatné",
"fullName": "Celé meno",
"displayName": "Zobrazené meno",
@@ -34,7 +35,7 @@
"beard": "Brada",
"mustache": "Fúzy",
"flower": "Kvet",
- "wheelchair": "Wheelchair",
+ "wheelchair": "Stolička",
"basicSkins": "Základné pokožky",
"rainbowSkins": "Dúhové pokožky",
"pastelSkins": "Pastelové pokožky",
@@ -53,22 +54,22 @@
"classEquipBonus": "Bonus za povolanie",
"battleGear": "Bojová výzbroj",
"battleGearText": "Toto je výzbroj, ktorú nosíš do boja; ovplyvňuje čísla pri interakcii s úlohami.",
- "autoEquipBattleGear": "Auto-equip new gear",
+ "autoEquipBattleGear": "Automaticky sa vybaviť novým výstrojom",
"costume": "Kostým",
"costumeText": "Ak chceš radšej vyzerať inak, než vyzeráš so svojou aktuálnou bojovou výzbrojou, zaškrtni \"použiť kostým\", aby si si mohol zvoliť oblečenie, ktoré sa ti páči, no zároveň získavať bonusy za svoju najlepšiu bojovú výzbroj.",
"useCostume": "Použiť kostým",
"useCostumeInfo1": "Klikni na \"použiť kostým\" ak si chceš obliecť výstroj bez toho, aby ovplyvnila tvoje štatistiky bojového výstroja! To znamená, že si môžeš vybrať predmety pre získanie najlepších bonusov vľavo a obliecť svoj avatar vpravo.",
- "useCostumeInfo2": "Once you click \"Use Costume\" your avatar will look pretty basic... but don't worry! If you look on the left, you'll see that your Battle Gear is still equipped. Next, you can make things fancy! Anything you equip on the right won't affect your stats, but can make you look super awesome. Try out different combos, mixing sets, and coordinating your Costume with your pets, mounts, and backgrounds.
Got more questions? Check out the Costume page on the wiki. Find the perfect ensemble? Show it off in the Costume Carnival guild or brag in the Tavern!",
- "gearAchievement": "You have earned the \"Ultimate Gear\" Achievement for upgrading to the maximum gear set for a class! You have attained the following complete sets:",
- "moreGearAchievements": "To attain more Ultimate Gear badges, change classes on your stats page and buy up your new class's gear!",
- "armoireUnlocked": "You've also unlocked the Enchanted Armoire! Click on the Enchanted Armoire Reward for a random chance at special Equipment! It may also give you random XP or food items.",
+ "useCostumeInfo2": "Keď klikneš na voľbu \"Použiť kostým\", tvoj avatar bude vyzerať veľmi jednoducho ... ale neboj sa! Ak sa pozrieš naľavo, uvidíš, že tvoj bojový výstroj sa stále používa. Teraz môžeš spraviť veci podľa svojho vkusu! Hocaké vybavenie, ktoré teraz použiješ napravo, nebude mať žiadny efekt na tvoje štatistiky, ale môžeš vďaka nemu vyzerať super úžasne. Vyskúšaj rozdielne kombinácie, miešanie sád, tátošov a pozadí. .
Stále máš otázky? Prečítaj si stránku kostýmov na wiki. Našiel si perfektný celok? Ukáž ho na kostýmovom karnevale alebo sa bež pochváliť do Hostinca!",
+ "gearAchievement": "Získal si odznak \"Najúžasnejší výstroj\" za maximálne vylepšenie sady výstroja pre svoje povolanie! Získal si nasledovné kompletné sady:",
+ "moreGearAchievements": "Na získanie viacerých odznakov najúžasnejšieho výstroja, zmeň si svoje povolanie na tvojej stránke štatistík a nakúp výstroj pre svoje nové povolanie!",
+ "armoireUnlocked": "Tiež si odomkol Kúzelnú skrinku! Klikni na Odmenu kúzelnej skrinky pre možnosť náhodného získania špeciálnej výbavy! Môže ti tiež dať ľubovoľne veľa XP alebo jedla.",
"ultimGearName": "Najúžasnejší výstroj",
- "ultimGearText": "Has upgraded to the maximum weapon and armor set for the following classes:",
+ "ultimGearText": "Vylepšil na maximum zbraň a brnenie pre nasledujúce povolania:",
"level": "Level",
"levelUp": "Získal si nový level!",
- "gainedLevel": "You gained a level!",
- "leveledUp": "By accomplishing your real-life goals, you've grown to Level <%= level %>!",
- "fullyHealed": "You have been fully healed!",
+ "gainedLevel": "Získal si level!",
+ "leveledUp": "Plnením cieľov v reálnom živote si získal Level <%= level %>!",
+ "fullyHealed": "Bol si plne uzdravený!",
"huzzah": "Huzzah!",
"mana": "Mana",
"hp": "HP",
@@ -83,8 +84,8 @@
"allocatePerPop": "Pridať bod do postrehu",
"allocateInt": "Body pridelené do Inteligencie:",
"allocateIntPop": "Pridať bod do inteligencie",
- "noMoreAllocate": "Now that you've hit level 100, you won't gain any more Attribute Points. You can continue leveling up, or start a new adventure at level 1 by using the Orb of Rebirth, now available for free in the Market.",
- "stats": "Avatar Stats",
+ "noMoreAllocate": "Teraz, keď si dosiahol level 100, nezískaš žiadne ďaľšie body atribútov. Môžeš pokračovať v levelovaní, alebo začať nové dobrodružstvo na úrovni 1 pomocou použitia Orbu znovuzrodenia, ktorý je teraz dostupný zdarma na trhu.",
+ "stats": "Štatistiky avatara",
"strength": "Sila",
"strengthText": "Sila zvyšuje šancu náhodných \"kritických zásahov\" a tým aj možnosť získať z nich viac zlata, skúseností a predmetov. Taktiež pomáha zasadiť silnejšiu ranu boss monštrám.",
"constitution": "Odolnosť",
@@ -109,6 +110,7 @@
"mage": "Mág",
"mystery": "Záhada",
"changeClass": "Zmeniť povolanie, vrátiť pridelené body atribútov",
+ "lvl10ChangeClass": "Aby si mohol zmeniť povolanie, musíš byť aspoň level 10.",
"levelPopover": "S každým levelom získaš jeden bod, ktorý môžeš priradiť do atribútu. Môžeš tak robiť ručne alebo nechať hru, aby rozhodla za teba, použitím možnosti \"automatické pridelenie\".",
"unallocated": "Nepridelené body atribútov",
"haveUnallocated": "Máš <%= points %> nepriradených bodov atribútov",
@@ -126,9 +128,9 @@
"mageText": "Mágovia sa rýchlo učia, získavajú skúsenosti a levely rýchlejšie ako iné triedy. Taktiež majú veľké množstvo many na používanie svojich schopností. Hraj za mága, ak ťa baví taktické hranie Habitu, alebo ak ťa silne motivuje levelovanie a odomykanie rôznych funkcií.",
"rogueText": "Zlodeji milujú hromadenie bohatstva, získavajú viac zlata ako ktokoľvek iný a sú odborníci na získavanie náhodných predmetov. Ich výborná schopnosť zakrádania im dáva možnosť vyhnúť sa následkom nesplnených denných úloh. Hraj za zlodeja, ak ťa silne motivujú odmeny a odznaky a chceš nahromadiť lup!",
"healerText": "Liečitelia sú chránení pred zraneniami a svoju ochranu rozširujú aj na ostatných. Z nestihnutých denných úloh a zlých návykov si veľa nerobia a majú prostriedky ako si obnoviť zdravie v prípade zlyhania. Hraj za liečiteľa, ak ťa baví pomáhať ostatným v družine, alebo ak rád utekáš zubatej z lopaty!",
- "optOutOfClasses": "Opt Out",
- "optOutOfPMs": "Opt Out",
- "optOutOfClassesText": "Can't be bothered with classes? Want to choose later? Opt out - you'll be a warrior with no special abilities. You can read about the class system later on the wiki and enable classes at any time under User -> Stats.",
+ "optOutOfClasses": "Zrušiť výber povolania",
+ "optOutOfPMs": "Zrušiť",
+ "optOutOfClassesText": "Nezaujímajú ťa povolania? Chceš si vybrať neskôr? Zruš ich - budeš bojovník bez špeciálnych vlastností. O systéme povolaní si môžeš prečítať neskôr na wiki môžeš ich povoliť aj neskôr v položke Užívateľ -> Štatistiky.",
"select": "Vybrať",
"stealth": "Zakrádanie",
"stealthNewDay": "Na začiatku nového dňa sa vyhneš zraneniam z nesplnených denných úloh.",
@@ -137,7 +139,7 @@
"respawn": "Znovuzrodenie!",
"youDied": "Si mŕtvy!",
"dieText": "Stratil si level, všetko zlato a náhodný kus výstroja. Povstaň, Habitier, a skús to znovu! Drž na uzde svoje zlé návyky, buď dôsledný v plnení svojich denných úloh a, ak ochabneš, drž sa od smrti ďaleko pomocou elixíru zdravia.",
- "sureReset": "Are you sure? This will reset your character's class and allocated points (you'll get them all back to re-allocate), and costs 3 gems.",
+ "sureReset": "Si si istý? Tento krok zruší povolanie tvojej postavy a získané body (získaš ich naspäť na opätovné pridelenie). Tiež to bude stáť 3 drahokamy.",
"purchaseFor": "Kúpiť za <%= cost %> drahokamov?",
"notEnoughMana": "Nedostatok many.",
"invalidTarget": "Neplatný cieľ",
@@ -162,7 +164,9 @@
"con": "ODO",
"per": "POS",
"int": "INT",
- "showQuickAllocation": "Show stat allocation",
- "hideQuickAllocation": "Hide stat allocation",
- "quickAllocationLevelPopover": "Each level earns you one point to assign to an attribute of your choice. You can do so manually, or let the game decide for you using one of the Automatic Allocation options found in User -> Stats."
+ "showQuickAllocation": "Zobraziť rozdelenie štatistík",
+ "hideQuickAllocation": "Skryť rozdelenie štatistík",
+ "quickAllocationLevelPopover": "S každým levelom získaš jeden bod, ktorý môžeš priradiť do atribútu podľa svojho výberu. Môžeš tak robiť ručne alebo nechať hru, aby rozhodla za teba, použitím možnosti \"automatické pridelenie\", ktorú môžeš nájsť v časti Užívateľ -> Štatistiky.",
+ "invalidAttribute": "\"<%= attr %>\" nie je platný atribút.",
+ "notEnoughAttrPoints": "You don't have enough attribute points."
}
\ No newline at end of file
diff --git a/common/locales/sk/communityguidelines.json b/common/locales/sk/communityguidelines.json
index ab05eeb950..87fa5b9d57 100644
--- a/common/locales/sk/communityguidelines.json
+++ b/common/locales/sk/communityguidelines.json
@@ -7,11 +7,11 @@
"commGuidePara003": "Tieto pravidlá sa týkajú všetkých spoločenských priestorov, ktoré používame, v rátane (no nie nutne len) Trello, GitHub, Transifex a Wikia (tiež známa ako wiki). Občas nastane nepredvídateľná udalosť, napríklad nový zdroj konfliktu alebo zlotrilý nekromancer. Keď sa toto stane, modovia môžu zareagovať upravením týchto pravidiel aby ochránili Komunitu pred novými hrozbami. Ale neboj sa: Dáme ti vedieť pomocou oznámenia od Baileyho, ak sa pravidlá zmenia. ",
"commGuidePara004": "Teraz si priprav svoje brká a zvitky na písanie poznámok a začnime! ",
"commGuideHeadingBeing": "Byť Habitičanom",
- "commGuidePara005": "Habitica is first and foremost a website devoted to improvement. As a result, we've been lucky to attract one of the warmest, kindest, most courteous and supportive communities on the internet. There are many traits that make up Habiticans. Some of the most common and most notable are:",
- "commGuideList01A": "A Helpful Spirit. Many people devote time and energy helping out new members of the community and guiding them. The Newbies Guild, for example, is a guild devoted just to answering people's questions. If you think you can help, don't be shy!",
- "commGuideList01B": "A Diligent Attitude. Habiticans work hard to improve their lives, but also help build the site and improve it constantly. We're an open-source project, so we are all constantly working to make the site the best place it can be.",
- "commGuideList01C": "A Supportive Demeanor. Habiticans cheer for each other's victories, and comfort each other during hard times. We lend strength to each other and lean on each other and learn from each other. In parties, we do this with our spells; in chat rooms, we do this with kind and supportive words.",
- "commGuideList01D": "A Respectful Manner. We all have different backgrounds, different skill sets, and different opinions. That's part of what makes our community so wonderful! Habiticans respect these differences and celebrate them. Stick around, and soon you will have friends from all walks of life.",
+ "commGuidePara005": "Habitica je prvá a hlavná web stránka, ktorá sa aktívne zlepšuje. Ako výsledok máme šťastie na jednu z najvrelejších, najmilších, najzdvorilejších a podporujúcich komunít na internete. Existuje veľa rýs, ktoré robia Habiticanov Habiticanmi. Niektoré z najbežnejších a najpozoruhodnejších sú:",
+ "commGuideList01A": "Pomocný duch Veľa ľudí venuje čas a energiu pomáhaniu novým členom komunity a ich vedeniu. The Newbies Guild, napríklad, je cech zameraný na odpovedanie otázok. Ak si myslíš, že môžeš pomôcť, nehanbi sa!",
+ "commGuideList01B": "Usilovný postoj. Habiticania usilovne pracujú, aby zlepšili svoje životy, ale tiež pomáhajú budovať stránku a stále ju zlepšujú. Sme projekt s otvoreným zdrojom, takže všetci neustále pracujeme na tom, aby sme zo stránky urobili to najlepšie miesto, akým môže byť.",
+ "commGuideList01C": "Podporujúce správanie. Habiticania oslavujú víťazstvá ostatných a pomáhajú si počas ťažkých chvíľ. Dodávajú si navzájom silu, spoliehajú sa na seba a učia sa od seba navzájom. V skupinách to dosiahneme s našimi kúzlami; v chatovacích miestnostiach s milými a podporujúcimi slovami. ",
+ "commGuideList01D": "Zdvorilé spôsoby. Všetci máme rozdielne pôvody, rôzne balíčky schopností, a rôzne názory. To je súčasť toho, čo robí našu komunitu takú úžasnú! Habiticania rešpektujú tieto rozdiely a oslavujú ich. Zostaň a čoskoro budeš mať priateľov z každého kúta spoločnosti. ",
"commGuideHeadingMeet": "Zoznám sa s Modmi!",
"commGuidePara006": "Habitica has some tireless knight-errants who join forces with the staff members to keep the community calm, contented, and free of trolls. Each has a specific domain, but will sometimes be called to serve in other social spheres. Staff and Mods will often precede official statements with the words \"Mod Talk\" or \"Mod Hat On\".",
"commGuidePara007": "Staff have purple tags marked with crowns. Their title is \"Heroic\".",
@@ -19,19 +19,19 @@
"commGuidePara009": "Súčasní zamestnanci sú (zľava doprava):",
"commGuidePara009a": "v Trelle",
"commGuidePara009b": "na GitHube",
- "commGuidePara010": "There are also several Moderators who assist the staff members. They were selected carefully, so please give them your respect and listen to their suggestions.",
- "commGuidePara011": "The current Moderators are (from left to right):",
+ "commGuidePara010": "Je tu tiež niekoľko Moderátorov, ktorý pomáhajú personálu. Sú starostlivo vybraný, tak im, prosím, prejavte svoj rešpekt a vypočujte si ich návrhy. ",
+ "commGuidePara011": "Súčasní moderátori sú (zľava doprava): ",
"commGuidePara011a": "v Hostincovom chate",
"commGuidePara011b": "na GitHub/Wikia",
"commGuidePara011c": "na Wikia",
"commGuidePara011d": "na GitHub",
"commGuidePara012": "If you have an issue or concern about a particular Mod, please send an email to Lemoness (leslie@habitica.com).",
- "commGuidePara013": "In a community as big as Habitica, users come and go, and sometimes a moderator needs to lay down their noble mantle and relax. The following are Moderators Emeritus. They no longer act with the power of a Moderator, but we would still like to honor their work!",
- "commGuidePara014": "Moderators Emeritus:",
+ "commGuidePara013": "V komunite tak veľkej, ako je Habitica, užívatelia prichádzajú a odchádzajú, a niekedy aj moderátor potrebuje odložiť svoj vznešený plášť a oddýchnuť si. Tu sú vyslúžilí moderátori. Už nemajú moc moderátora, ale aj tak by sme radi poctili ich prácu!",
+ "commGuidePara014": "Vyslúžilí moderátori",
"commGuideHeadingPublicSpaces": "Verejné priestory v Habitica",
- "commGuidePara015": "Habitica has two kinds of social spaces: public, and private. Public spaces include the Tavern, Public Guilds, GitHub, Trello, and the Wiki. Private spaces are Private Guilds, party chat, and Private Messages. All Display Names must comply with the public space guidelines. To change your Display Name, go on the website to User > Profile and click on the \"Edit\" button.",
- "commGuidePara016": "When navigating the public spaces in Habitica, there are some general rules to keep everyone safe and happy. These should be easy for adventurers like you!",
- "commGuidePara017": "Respect each other. Be courteous, kind, friendly, and helpful. Remember: Habiticans come from all backgrounds and have had wildly divergent experiences. This is part of what makes Habitica so cool! Building a community means respecting and celebrating our differences as well as our similarities. Here are some easy ways to respect each other:",
+ "commGuidePara015": "Habitica má dva druhy sociálnych priestorov: verejné a súkromné. Verejné priestory zahrňujú hostinec, verejné cechy, GitHub, Trello a Wiki. Súkromné priestory sú súkromné cechy, chat družiny a súkromné správy. Všetky viditeľné názvy sa musia prispôsobiť pravidlám o verejných priestoroch. Ak chceš zmeniť názov, navštív web stránku Užívateľ > Profil a klikni na \"Upraviť\".",
+ "commGuidePara016": "Ak vedieš verejné priestory v Habitice, mal by si vedieť, že existujú nejaké všeobecné pravidlá, ktoré udržujú ostatných v bezpečí a šťastných. Mali by byť ľahké pre dobrodruha ako si ty!",
+ "commGuidePara017": "Rešpektovanie sa navzájom. Buď zdvorilý, milý, priateľský a nápomocný. Pamätaj: Habiticania majú rôzny pôvod a divoko rozdielne skúsenosti. Toto je časť toho, čo robí Habiticu tak úžasnú! Budovanie komunity znamená rešpektovať a oslavovať naše rozdiely rovnako ako naše podobnosti. Tu je pár jednoduchých spôsobov ako rešpektovať jeden druhého. ",
"commGuideList02A": "Riaď sa všetkými Podmienkami a Pravidlami Užívania.",
"commGuideList02B": "Do not post images or text that are violent, threatening, or sexually explicit/suggestive, or that promote discrimination, bigotry, racism, sexism, hatred, harassment or harm against any individual or group. Not even as a joke. This includes slurs as well as statements. Not everyone has the same sense of humor, and so something that you consider a joke may be hurtful to another. Attack your Dailies, not each other.",
"commGuideList02C": "Keep discussions appropriate for all ages. We have many young Habiticans who use the site! Let's not tarnish any innocents or hinder any Habiticans in their goals.",
@@ -75,8 +75,8 @@
"commGuidePara046": "The Habitica wiki can be considered to be a database of all things Habitica. It provides information about site features, guides to play the game, tips on how you can contribute to Habitica and also provides a place for you to advertise your guild or party and vote on topics.",
"commGuidePara047": "Keďže wiki je hostená Wikia, pravidlá a podmienky Wikia platia dodatočne k pravidlám stanoveným Habiticou a Habitčanskou wiki stránkou. ",
"commGuidePara048": "The wiki is solely a collaboration between all of its editors so some additional guidelines include:",
- "commGuideList04A": "Requesting new pages or major changes on the Wiki Trello board",
- "commGuideList04B": "Being open to other peoples' suggestion about your edit",
+ "commGuideList04A": "Žiadanie nových stránok alebo hlavné zmeny na Wiki Trello paneli",
+ "commGuideList04B": "Byť otvorený návrhom ostatných ľudí k tvojej úprave textu",
"commGuideList04C": "Discussing any conflict of edits within the page's talk page",
"commGuideList04D": "Bringing any unresolved conflict to the attention of wiki admins",
"commGuideList04E": "Not spamming or sabotaging pages for personal gain",
diff --git a/common/locales/sk/content.json b/common/locales/sk/content.json
index 29d9b0353f..f1eec3a594 100644
--- a/common/locales/sk/content.json
+++ b/common/locales/sk/content.json
@@ -1,119 +1,125 @@
{
"potionText": "Elixír zdravia",
"potionNotes": "Vylieči 15 bodov zdravia (okamžité použitie)",
- "armoireText": "Enchanted Armoire",
- "armoireNotesFull": "Open the Armoire to randomly receive special Equipment, Experience, or food! Equipment pieces remaining:",
- "armoireLastItem": "You've found the last piece of rare Equipment in the Enchanted Armoire.",
- "armoireNotesEmpty": "The Armoire will have new Equipment in the first week of every month. Until then, keep clicking for Experience and Food!",
+ "armoireText": "Kúzelná skrinka",
+ "armoireNotesFull": "Otvor skrinku aby si získal ľubovoľnú špeciálnu výbavu, skúsenosti alebo jedlo! Zostávajúce časti výbavy:",
+ "armoireLastItem": "Našiel si poslednú časť vzácnej výbavy v Kúzelnej skrinke.",
+ "armoireNotesEmpty": "Skrinka bude mat novú výbavu vždy v prvom týždni každého mesiaca. Dovtedy môžeš klikať na skúsenosti a jedlo!",
"dropEggWolfText": "Vlk",
- "dropEggWolfMountText": "Wolf",
- "dropEggWolfAdjective": "a loyal",
+ "dropEggWolfMountText": "Vlk",
+ "dropEggWolfAdjective": "verný",
"dropEggTigerCubText": "Tigríček",
"dropEggTigerCubMountText": "Tiger",
- "dropEggTigerCubAdjective": "a fierce",
+ "dropEggTigerCubAdjective": "divoký",
"dropEggPandaCubText": "Pandiačik",
"dropEggPandaCubMountText": "Pandiak",
- "dropEggPandaCubAdjective": "a gentle",
+ "dropEggPandaCubAdjective": "krotký",
"dropEggLionCubText": "Levík",
"dropEggLionCubMountText": "Lev",
- "dropEggLionCubAdjective": "a regal",
+ "dropEggLionCubAdjective": "veľkolepý",
"dropEggFoxText": "Lišiak",
- "dropEggFoxMountText": "Fox",
- "dropEggFoxAdjective": "a wily",
+ "dropEggFoxMountText": "Lišiak",
+ "dropEggFoxAdjective": "prefíkaný",
"dropEggFlyingPigText": "Lietajúci brav",
- "dropEggFlyingPigMountText": "Flying Pig",
- "dropEggFlyingPigAdjective": "a whimsical",
+ "dropEggFlyingPigMountText": "Lietajúci brav",
+ "dropEggFlyingPigAdjective": "vrtošivý",
"dropEggDragonText": "Drak",
- "dropEggDragonMountText": "Dragon",
- "dropEggDragonAdjective": "a mighty",
+ "dropEggDragonMountText": "Drak",
+ "dropEggDragonAdjective": "mocný",
"dropEggCactusText": "Kaktus",
- "dropEggCactusMountText": "Cactus",
- "dropEggCactusAdjective": "a prickly",
+ "dropEggCactusMountText": "Kaktus",
+ "dropEggCactusAdjective": "pichľavý",
"dropEggBearCubText": "Medvedík",
"dropEggBearCubMountText": "Medveď",
- "dropEggBearCubAdjective": "a brave",
+ "dropEggBearCubAdjective": "odvážny",
"questEggGryphonText": "Gryf",
- "questEggGryphonMountText": "Gryphon",
- "questEggGryphonAdjective": "a proud",
+ "questEggGryphonMountText": "Gryf",
+ "questEggGryphonAdjective": "hrdý",
"questEggHedgehogText": "Ježko",
- "questEggHedgehogMountText": "Hedgehog",
- "questEggHedgehogAdjective": "a spiky",
+ "questEggHedgehogMountText": "Ježko",
+ "questEggHedgehogAdjective": "ostnatý",
"questEggDeerText": "Jeleň",
- "questEggDeerMountText": "Deer",
- "questEggDeerAdjective": "an elegant",
+ "questEggDeerMountText": "Jeleň",
+ "questEggDeerAdjective": "elegantný",
"questEggEggText": "Vajco",
- "questEggEggMountText": "Egg Basket",
- "questEggEggAdjective": "a colorful",
+ "questEggEggMountText": "Košík s vajíčkami",
+ "questEggEggAdjective": "pestrofarebné",
"questEggRatText": "Potkan",
- "questEggRatMountText": "Rat",
- "questEggRatAdjective": "a sociable",
+ "questEggRatMountText": "Potkan",
+ "questEggRatAdjective": "kamarátsky",
"questEggOctopusText": "Chobotnica",
- "questEggOctopusMountText": "Octopus",
- "questEggOctopusAdjective": "a slippery",
+ "questEggOctopusMountText": "Chobotnica",
+ "questEggOctopusAdjective": "klzká",
"questEggSeahorseText": "Morský koník",
- "questEggSeahorseMountText": "Seahorse",
- "questEggSeahorseAdjective": "a prize",
+ "questEggSeahorseMountText": "Morský koník",
+ "questEggSeahorseAdjective": "prvotriedny",
"questEggParrotText": "Papagáj",
- "questEggParrotMountText": "Parrot",
- "questEggParrotAdjective": "a vibrant",
+ "questEggParrotMountText": "Papagáj",
+ "questEggParrotAdjective": "kmitajúci",
"questEggRoosterText": "Kohút",
- "questEggRoosterMountText": "Rooster",
- "questEggRoosterAdjective": "a strutting",
+ "questEggRoosterMountText": "Kohút",
+ "questEggRoosterAdjective": "pyšný",
"questEggSpiderText": "Pavúk",
- "questEggSpiderMountText": "Spider",
- "questEggSpiderAdjective": "a creepy",
+ "questEggSpiderMountText": "Pavúk",
+ "questEggSpiderAdjective": "strašidelný",
"questEggOwlText": "Sova",
- "questEggOwlMountText": "Owl",
- "questEggOwlAdjective": "a wise",
+ "questEggOwlMountText": "Sova",
+ "questEggOwlAdjective": "múdra",
"questEggPenguinText": "Tučniak",
- "questEggPenguinMountText": "Penguin",
- "questEggPenguinAdjective": "a perspicacious",
+ "questEggPenguinMountText": "Tučniak",
+ "questEggPenguinAdjective": "bystrý",
"questEggTRexText": "Tyrannosaurus",
- "questEggTRexMountText": "Tyrannosaur",
- "questEggTRexAdjective": "a tiny-armed",
- "questEggRockText": "Rock",
- "questEggRockMountText": "Rock",
- "questEggRockAdjective": "a lively",
- "questEggBunnyText": "Bunny",
- "questEggBunnyMountText": "Bunny",
- "questEggBunnyAdjective": "a snuggly",
- "questEggSlimeText": "Marshmallow Slime",
- "questEggSlimeMountText": "Marshmallow Slime",
- "questEggSlimeAdjective": "a sweet",
- "questEggSheepText": "Sheep",
- "questEggSheepMountText": "Sheep",
- "questEggSheepAdjective": "a woolly",
- "questEggCuttlefishText": "Cuttlefish",
- "questEggCuttlefishMountText": "Cuttlefish",
- "questEggCuttlefishAdjective": "a cuddly",
- "questEggWhaleText": "Whale",
- "questEggWhaleMountText": "Whale",
- "questEggWhaleAdjective": "a splashy",
- "questEggCheetahText": "Cheetah",
- "questEggCheetahMountText": "Cheetah",
- "questEggCheetahAdjective": "an honest",
- "questEggHorseText": "Horse",
- "questEggHorseMountText": "Horse",
- "questEggHorseAdjective": "a galloping",
- "questEggFrogText": "Frog",
- "questEggFrogMountText": "Frog",
- "questEggFrogAdjective": "a princely",
- "questEggSnakeText": "Snake",
- "questEggSnakeMountText": "Snake",
- "questEggSnakeAdjective": "a slithering",
- "questEggUnicornText": "Unicorn",
- "questEggUnicornMountText": "Winged Unicorn",
- "questEggUnicornAdjective": "a magical",
- "questEggSabretoothText": "Sabretooth Tiger",
- "questEggSabretoothMountText": "Sabretooth Tiger",
- "questEggSabretoothAdjective": "a ferocious",
- "questEggMonkeyText": "Monkey",
- "questEggMonkeyMountText": "Monkey",
- "questEggMonkeyAdjective": "a mischievous",
- "questEggSnailText": "Snail",
- "questEggSnailMountText": "Snail",
- "questEggSnailAdjective": "a slow but steady",
- "eggNotes": "Find a hatching potion to pour on this egg, and it will hatch into <%= eggAdjective(locale) %> <%= eggText(locale) %>.",
+ "questEggTRexMountText": "Tyrannosaurus",
+ "questEggTRexAdjective": "drobnoruký",
+ "questEggRockText": "Kameň",
+ "questEggRockMountText": "Kameň",
+ "questEggRockAdjective": "živý",
+ "questEggBunnyText": "Králik",
+ "questEggBunnyMountText": "Králik",
+ "questEggBunnyAdjective": "prítulný",
+ "questEggSlimeText": "Sladký sliz",
+ "questEggSlimeMountText": "Sladký sliz",
+ "questEggSlimeAdjective": "sladký",
+ "questEggSheepText": "Ovca",
+ "questEggSheepMountText": "Ovca",
+ "questEggSheepAdjective": "huňatý",
+ "questEggCuttlefishText": "Sépia",
+ "questEggCuttlefishMountText": "Sépia",
+ "questEggCuttlefishAdjective": "maznavá",
+ "questEggWhaleText": "Veľryba",
+ "questEggWhaleMountText": "Veľryba",
+ "questEggWhaleAdjective": "špliechajúca",
+ "questEggCheetahText": "Gepard",
+ "questEggCheetahMountText": "Gepard",
+ "questEggCheetahAdjective": "čestný",
+ "questEggHorseText": "Kôň",
+ "questEggHorseMountText": "Kôň",
+ "questEggHorseAdjective": "cválajúci",
+ "questEggFrogText": "Žaba",
+ "questEggFrogMountText": "Žaba",
+ "questEggFrogAdjective": "kniežací",
+ "questEggSnakeText": "Had",
+ "questEggSnakeMountText": "Had",
+ "questEggSnakeAdjective": "plazí",
+ "questEggUnicornText": "Jednorožec",
+ "questEggUnicornMountText": "Okrídlený jednorožec",
+ "questEggUnicornAdjective": "čarovné",
+ "questEggSabretoothText": "Šablozubý tiger",
+ "questEggSabretoothMountText": "Šablozubý tiger",
+ "questEggSabretoothAdjective": "zúrivý",
+ "questEggMonkeyText": "Opica",
+ "questEggMonkeyMountText": "Opica",
+ "questEggMonkeyAdjective": "roztopašná",
+ "questEggSnailText": "Slimák",
+ "questEggSnailMountText": "Slimák",
+ "questEggSnailAdjective": "pomalý ale nezlomný",
+ "questEggFalconText": "Sokol",
+ "questEggFalconMountText": "Sokol",
+ "questEggFalconAdjective": "rýchly",
+ "questEggTreelingText": "Treeling",
+ "questEggTreelingMountText": "Treeling",
+ "questEggTreelingAdjective": "a leafy",
+ "eggNotes": "Nájdi liahoxír a vylej ho na toto vajíčko, aby sa z neho vyliahlo zvieratko: <%= eggAdjective(locale) %> <%= eggText(locale) %>.",
"hatchingPotionBase": "Základný",
"hatchingPotionWhite": "Biely",
"hatchingPotionDesert": "Púštny",
@@ -124,10 +130,11 @@
"hatchingPotionCottonCandyPink": "Cukrovo ružový",
"hatchingPotionCottonCandyBlue": "Cukrovomodrý",
"hatchingPotionGolden": "Zlatý",
- "hatchingPotionSpooky": "Spooky",
- "hatchingPotionPeppermint": "Peppermint",
+ "hatchingPotionSpooky": "Strašidelný",
+ "hatchingPotionPeppermint": "Mätový",
+ "hatchingPotionFloral": "Floral",
"hatchingPotionNotes": "Vylej tento elixír na vajíčko a vyliahne sa z neho <%= potText(locale) %> zvieratko.",
- "premiumPotionAddlNotes": "Not usable on quest pet eggs.",
+ "premiumPotionAddlNotes": "Nedá sa použiť na vajíčka zvieratiek z výprav.",
"foodMeat": "Mäso",
"foodMilk": "Mlieko",
"foodPotatoe": "Zemiak",
diff --git a/common/locales/sk/contrib.json b/common/locales/sk/contrib.json
index 29591238be..1dd10c6382 100644
--- a/common/locales/sk/contrib.json
+++ b/common/locales/sk/contrib.json
@@ -1,24 +1,24 @@
{
"friend": "Priateľ",
- "friendFirst": "When your first set of submissions is deployed, you will receive the Habitica Contributor's badge. Your name in Tavern chat will proudly display that you are a contributor. As a bounty for your work, you will also receive 3 Gems.",
- "friendSecond": "When your second set of submissions is deployed, the Crystal Armor will be available for purchase in the Rewards shop. As a bounty for your continued work, you will also receive 3 Gems.",
+ "friendFirst": "Po nasadení tvojej prvej sady príspevkov získaš odznak prispievateľa HabitRPG . V diskusii v hostinci bude tvoje meno hrdo zobrazovať, že si prispievateľ. Ako odmenu za svoju prácu dostaneš ešte 3 drahokamy.",
+ "friendSecond": "Po nasadení tvojej druhej sady príspevkov si budeš môcť v obchode s odmenami kúpiť kryštálové brnenie. Ako odmenu za tvoju neprestajnú prácu dostaneš 3 drahokamy.",
"elite": "Elitný",
- "eliteThird": "When your third set of submissions is deployed, the Crystal Helmet will be available for purchase in the Rewards shop. As a bounty for your continued work, you will also receive 3 Gems.",
- "eliteFourth": "When your fourth set of submissions is deployed, the Crystal Sword will be available for purchase in the Rewards shop. As a bounty for your continued work, you will also receive 4 Gems.",
+ "eliteThird": "Po nasadení tvojej tretej sady príspevkov si budeš môcť v obchode s odmenami kúpiť kryštálovú helmu. Ako odmenu za tvoju neprestajnú prácu dostaneš 3 drahokamy.",
+ "eliteFourth": "Po nasadení tvojej štvrtej sady príspevkov si budeš môcť v obchode s odmenami kúpiť kryštálový meč. Ako odmenu za tvoju neprestajnú prácu dostaneš 4 drahokamy.",
"champion": "Šampión",
- "championFifth": "When your fifth set of submissions is deployed, the Crystal Shield will be available for purchase in the Rewards shop. As a bounty for your continued work, you will also receive 4 Gems.",
- "championSixth": "When your sixth set of submissions is deployed, you will receive a Hydra Pet. You will also receive 4 Gems.",
+ "championFifth": "Po nasadení tvojej piatej sady príspevkov, si budeš môcť v obchode s odmenami kúpiť kryštálový štít. Ako odmenu za tvoju neprestajnú prácu dostaneš 4 drahokamy.",
+ "championSixth": "Po nasadení tvojej šiestej sady príspevkov dostaneš domáce zvieratko - hydru. Dostaneš aj 4 drahokamy.",
"legendary": "Legenda",
- "legSeventh": "When your seventh set of submissions is deployed, you will receive 4 Gems and become a member of the honored Contributor's Guild and be privy to the behind-the-scenes details of Habitica! Further contributions do not increase your tier, but you may continue to earn Gem bounties and titles.",
+ "legSeventh": "Pri nasadení tvojej siedmej sady príspevkov, dostaneš 4 drahokamy, staneš sa členom cteného spolku prispievateľov a budeš zasvätený do zákulisných detailov Habitiky! Ďalšie príspevky ti už nezvýšia hodnosť, ale môžeš pokračovať a naďalej získavať drahokamy a tituly.",
"moderator": "Moderátor",
"guardian": "Strážca",
- "guardianText": "Moderators were selected carefully from high tier contributors, so please give them your respect and listen to their suggestions.",
+ "guardianText": "Moderátori sú starostlivo zvolení z prispievateľov vysokého levelu, tak pred nimi, prosím, majte rešpekt a počúvajte ich rady.",
"staff": "Personál",
"heroic": "Hrdina",
- "heroicText": "The Heroic tier contains Habitica staff and staff-level contributors. If you have this title, you were appointed to it (or hired!).",
+ "heroicText": "Hodnosť hrdinu majú zamestnanci Habitiky a prispievatelia na úrovni zamestnancov. Ak máš tento titul, znamená to, že ti bol udelený (alebo si u nás zamestnaný!).",
"npcText": "NPC sú ľudia, ktorí podporili Habitica na Kickstarteri na najvyššej úrovni. Ich avatary dohliadajú na funkcie stránky!",
"modalContribAchievement": "Odznak prispievateľa!",
- "contribModal": "<%= name %>, you awesome person! You're now a tier <%= level %> contributor for helping Habitica. See",
+ "contribModal": "<%= name %>, si úžasný! Za pomoc Habitce si na <%= level %>. leveli prispievateľa. Pozri si",
"contribLink": "aké ceny si získal za svoj prínos.",
"contribName": "Prispievateľ",
"contribText": "Prispel do Habitica (kód, dizajn, pixelová grafika, právna rada, dokumenty, atď.). Chceš tento odznak?",
@@ -28,21 +28,25 @@
"helped": "Milovník ankiet",
"helpedText1": "Pomohol k zlepšeniu Habitica vyplnením",
"helpedText2": "tejto ankety.",
- "hall": "Hall of Heroes",
+ "hall": "Sieň hrdinov",
"contribTitle": "Titul prispievateľa (napr. \"kováč\")",
"contribLevel": "Úroveň prispievateľa",
"contribHallText": "1-7 sú bežní prispievatelia, 8 sú moderátori, 9 je personál. Toto určuje, ktoré predmety, zvieratká a tátoše sú dostupné. Taktiež určuje farbu štítku s menom. Úrovni 8 a 9 je automaticky priradený status admina.",
- "hallContributors": "Hall of Contributors",
+ "hallContributors": "Sieň prispievateľov",
"hallPatrons": "Sieň patrónov",
"rewardUser": "Odmeniť používateľa",
- "UUID": "UUID",
+ "UUID": "User ID",
"loadUser": "Nahrať používateľa",
+ "noAdminAccess": "You don't have admin access.",
+ "pageMustBeNumber": "req.query.page must be a number",
+ "userNotFound": "User not found.",
+ "invalidUUID": "UUID must be valid",
"title": "Titul",
"moreDetails": "Viac podrobností (1-7)",
"moreDetails2": "viac podrobností (8-9)",
"contributions": "Príspevky",
"admin": "Administrátor",
- "notGems": "is in USD, not in Gems. Aka, if this number is 1, it means 4 gems. Only use this option when manually granting gems to players, don't use it when granting contributor tiers. Contrib tiers will automatically add gems.",
+ "notGems": "je v USD, nie v drahokamoch. Teda, ak je číslo 1, znamená 4 drahokamy. Použi túto možnosť len vtedy, ak hráčom manuálne pridávaš drahokamy, nepoužívaj ho pri schvaľovaní prispievateľských hodností. Prispievateľské hodnosti automaticky pridajú drahokamy.",
"gamemaster": "Pán jaskyne (personál/moderátor)",
"backerTier": "Úroveň podporovateľa",
"balance": "Stav",
@@ -52,13 +56,13 @@
"visitHeroes": "Navštív Sieň hrdinov (prispievatelia a podporovatelia)",
"conLearn": "Zisti viac o prispievateľských odmenách",
"conLearnHow": "Zisti ako prispieť do Habitica",
- "surveysSingle": "Helped Habitica grow by filling out a survey. There are no active surveys.",
- "surveysMultiple": "Helped Habitica grow by filling out <%= surveys %> surveys. There are no active surveys.",
- "currentSurvey": "Current Survey",
- "surveyWhen": "The badge will be awarded to all participants when surveys have been processed, in late March.",
- "blurbInbox": "This is where your private messages are stored! You can send someone a message by clicking on the envelope icon next to their name in Tavern, Party, or Guild Chat. If you've received an inappropriate PM, you should email a screenshot of it to Lemoness (leslie@habitica.com)",
- "blurbGuildsPage": "Guilds are common-interest chat groups created by the players, for players. Browse through the list and join the Guilds that interest you!",
- "blurbChallenges": "Challenges are created by your fellow players. Joining a Challenge will add its tasks to your task dashboard, and winning a Challenge will give you an achievement and often a gem prize!",
- "blurbHallPatrons": "This is the Hall of Patrons, where we honor the noble adventurers who backed Habitica's original Kickstarter. We thank them for helping us bring Habitica to life!",
- "blurbHallContributors": "This is the Hall of Contributors, where open-source contributors to Habitica are honored. Whether through code, art, music, writing, or even just helpfulness, they have earned gems, exclusive equipment, and prestigious titles. You can contribute to Habitica, too! Find out more here. "
+ "surveysSingle": "Helped Habitica grow, either by filling out a survey or helping with a major testing effort. Thank you!",
+ "surveysMultiple": "Helped Habitica grow on <%= surveys %> occasions, either by filling out a survey or helping with a major testing effort. Thank you!",
+ "currentSurvey": "Aktuálny dotazník",
+ "surveyWhen": "Odznak dostanú všetci účastníci najneskôr v marci po spracovaní dotazníkov.",
+ "blurbInbox": "Tu sú uchované tvoje súkromné správy! Môžeš niekomu poslať správu kliknutím na ikonu obálky pri jeho mene v chate v hostinci, družine alebo cechu. Ak si dostal nevhodnú súkromnú správu, mal by si mailom odoslať screenshot tejto správy hráčovi Lemoness (leslie@habitica.com)",
+ "blurbGuildsPage": "Cechy sú chatovacie skupiny so spoločnými záujmami vytvorené hráčmi pre hráčov. Preštuduj si ich zoznam a pripoj sa k tým, ktoré ťa zaujímajú.",
+ "blurbChallenges": "Výzvy vytvárajú tvoji spoluhráči. Keď sa pripojíš k výzve, úlohy z nej sa objavia na tvojom paneli a ak výzvu vyhráš, dostaneš odznak a často aj nejaké drahokamy. ",
+ "blurbHallPatrons": "Toto je Sieň Patrónov, kde si uctievame ušľachtilých dobrodruhov, ktorí podporili pôvodnú Habitiku na Kickstarteri. Ďakujeme im za to, že nám pomohli priviesť Habitiku k životu!",
+ "blurbHallContributors": "Toto je Sieň Prispievateľov, kde sú uctievaní open-source prispievatelia Habitiky. Či už kódom, ilustráciami, hudbou, písaním alebo len pomáhaním, získali drahokamy, exkluzívne vybavenie, a prestížne tituly. Aj ty môžeš prispieť do Habitiky! Tu zistíš viac. "
}
\ No newline at end of file
diff --git a/common/locales/sk/death.json b/common/locales/sk/death.json
index 600c6164d5..cb71a616d6 100644
--- a/common/locales/sk/death.json
+++ b/common/locales/sk/death.json
@@ -12,5 +12,6 @@
"losingHealthQuickly": "Strácaš život rýchlo?",
"lowHealthTips3": "Nedokončené denné úlohy ťa ublížia cez noc, takže buď opatrní, aby si ich nepridal príliš veľa na začiatku.",
"lowHealthTips4": "Ak denná úloha nieje splatná na určitý deň, môžete to vypnúť kliknutím na ikonu ceruzky.",
- "goodLuck": "Veľa šťastia!"
+ "goodLuck": "Veľa šťastia!",
+ "cannotRevive": "Cannot revive if not dead"
}
\ No newline at end of file
diff --git a/common/locales/sk/defaulttasks.json b/common/locales/sk/defaulttasks.json
index b7ffa68d11..61b198380a 100644
--- a/common/locales/sk/defaulttasks.json
+++ b/common/locales/sk/defaulttasks.json
@@ -1,5 +1,5 @@
{
- "defaultHabit1Text": "Productive Work (Click the pencil to edit)",
+ "defaultHabit1Text": "Produktívna práca (Pre úpravu klikni na ceruzku)",
"defaultHabit1Notes": "Sample Good Habits: + Eat a vegetable + 15 minutes productive work",
"defaultHabit2Text": "Eat Junk Food (Click the pencil to edit)",
"defaultHabit2Notes": "Sample Bad Habits: - Smoke - Procrastinate",
@@ -7,7 +7,7 @@
"defaultHabit3Notes": "Sample Good or Bad Habits: +/- Took Stairs/Elevator ; +/- Drank Water/Soda",
"defaultTodoNotes": "You can either complete this To-Do, edit it, or remove it.",
"defaultTodo1Text": "Join Habitica (Check me off!)",
- "defaultReward1Text": "15 minute break",
+ "defaultReward1Text": "15 minútová prestávka",
"defaultReward1Notes": "Vlastné odmeny môžu mať rôzne formy. Niektorí ľudia odkladajú sledovanie svojho obľúbeného seriálu, kým nemajú dosť zlata, aby si to mohli dovoliť.",
"defaultTag1": "ráno",
"defaultTag2": "poobede",
diff --git a/common/locales/sk/faq.json b/common/locales/sk/faq.json
index 000a91cab8..0bb604e345 100644
--- a/common/locales/sk/faq.json
+++ b/common/locales/sk/faq.json
@@ -1,39 +1,39 @@
{
- "frequentlyAskedQuestions": "Frequently Asked Questions",
- "faqQuestion0": "I'm confused. Where do I get an overview?",
+ "frequentlyAskedQuestions": "Časté otázky",
+ "faqQuestion0": "Som zmätený/á. Kde nájdem súhrn?",
"iosFaqAnswer0": "First, you'll set up tasks that you want to do in your everyday life. Then, as you complete the tasks in real life and check them off, you'll earn experience and gold. Gold is used to buy equipment and some items, as well as custom rewards. Experience causes your character to level up and unlock content such as Pets, Skills, and Quests! You can customize your character under Menu > Customize Avatar.\n\n Some basic ways to interact: click the (+) in the upper-right-hand corner to add a new task. Tap on an existing task to edit it, and swipe left on a task to delete it. You can sort tasks using Tags in the upper-left-hand corner, and expand and contract checklists by clicking on the checklist bubble.",
"webFaqAnswer0": "First, you'll set up tasks that you want to do in your everyday life. Then, as you complete the tasks in real life and check them off, you'll earn Experience and Gold. Gold is used to buy equipment and some items, as well as custom rewards. Experience causes your character to level up and unlock content such as pets, skills, and quests! For more detail, check out a step-by-step overview of the game at [Help -> Overview for New Users](https://habitica.com/static/overview).",
- "faqQuestion1": "How do I set up my tasks?",
+ "faqQuestion1": "Ako si vytvorím moje úlohy?",
"iosFaqAnswer1": "Good Habits (the ones with a +) are tasks that you can do many times a day, such as eating vegetables. Bad Habits (the ones with a -) are tasks that you should avoid, like biting nails. Habits with a + and a - have a good choice and a bad choice, like taking the stairs vs. taking the elevator. Good Habits award experience and gold. Bad Habits subtract health.\n\n Dailies are tasks that you have to do every day, like brushing your teeth or checking your email. You can adjust the days that a Daily is due by tapping to edit it. If you skip a Daily that is due, your avatar will take damage overnight. Be careful not to add too many Dailies at once!\n\n To-Dos are your To-Do list. Completing a To-Do earns you gold and experience. You never lose health from To-Dos. You can add a due date to a To-Do by tapping to edit.",
"webFaqAnswer1": "Good Habits (the ones with a ) are tasks that you can do many times a day, such as eating vegetables. Bad Habits (the ones with a ) are tasks that you should avoid, like biting nails. Habits with a and a have a good choice and a bad choice, like taking the stairs vs. taking the elevator. Good Habits award Experience and Gold. Bad Habits subtract Health.\n
\n Dailies are tasks that you have to do every day, like brushing your teeth or checking your email. You can adjust the days that a Daily is due by clicking the pencil item to edit it. If you skip a Daily that is due, your avatar will take damage overnight. Be careful not to add too many Dailies at once!\n
\n To-Dos are your To-Do list. Completing a To-Do earns you Gold and Experience. You never lose Health from To-Dos. You can add a due date to a To-Do by clicking the pencil icon to edit.",
- "faqQuestion2": "What are some sample tasks?",
+ "faqQuestion2": "Čo sú to ukážkové úlohy?",
"iosFaqAnswer2": "The wiki has four lists of sample tasks to use as inspiration:\n
\n * [Sample Habits](http://habitica.wikia.com/wiki/Sample_Habits)\n * [Sample Dailies](http://habitica.wikia.com/wiki/Sample_Dailies)\n * [Sample To-Dos](http://habitica.wikia.com/wiki/Sample_To-Dos)\n * [Sample Custom Rewards](http://habitica.wikia.com/wiki/Sample_Custom_Rewards)",
"webFaqAnswer2": "The wiki has four lists of sample tasks to use as inspiration:\n * [Sample Habits](http://habitica.wikia.com/wiki/Sample_Habits)\n * [Sample Dailies](http://habitica.wikia.com/wiki/Sample_Dailies)\n * [Sample To-Dos](http://habitica.wikia.com/wiki/Sample_To-Dos)\n * [Sample Custom Rewards](http://habitica.wikia.com/wiki/Sample_Custom_Rewards)",
- "faqQuestion3": "Why do my tasks change color?",
+ "faqQuestion3": "Prečo moje úlohy menia farbu?",
"iosFaqAnswer3": "Your tasks change color based on how well you are currently accomplishing them! Each new task starts out as a neutral yellow. Perform Dailies or positive Habits more frequently and they move toward blue. Miss a Daily or give in to a bad Habit and the task moves toward red. The redder a task, the more rewards it will give you, but if it's a Daily or bad Habit, the more it will hurt you! This helps motivate you to complete the tasks that are giving you trouble.",
"webFaqAnswer3": "Your tasks change color based on how well you are currently accomplishing them! Each new task starts out as a neutral yellow. Perform Dailies or positive Habits more frequently and they move toward blue. Miss a Daily or give in to a bad Habit and the task moves toward red. The redder a task, the more rewards it will give you, but if it’s a Daily or bad Habit, the more it will hurt you! This helps motivate you to complete the tasks that are giving you trouble.",
- "faqQuestion4": "Why did my avatar lose health, and how do I regain it?",
+ "faqQuestion4": "Prečo môj avatar stráca zdravie a ako ho získam späť?",
"iosFaqAnswer4": "There are several things that can cause you to take damage. First, if you left Dailies incomplete overnight, they will damage you. Second, if you tap a bad Habit, it will damage you. Finally, if you are in a Boss Battle with your Party and one of your Party mates did not complete all their Dailies, the Boss will attack you.\n\n The main way to heal is to gain a level, which restores all your health. You can also buy a Health Potion with gold from the Rewards column. Plus, at level 10 or above, you can choose to become a Healer, and then you will learn healing skills. If you are in a Party with a Healer, they can heal you as well.",
"webFaqAnswer4": "There are several things that can cause you to take damage. First, if you left Dailies incomplete overnight, they will damage you. Second, if you click a bad Habit, it will damage you. Finally, if you are in a Boss Battle with your party and one of your party mates did not complete all their Dailies, the Boss will attack you.\n
\n The main way to heal is to gain a level, which restores all your Health. You can also buy a Health Potion with Gold from the Rewards column. Plus, at level 10 or above, you can choose to become a Healer, and then you will learn healing skills. If you are in a party (under Social > Party) with a Healer, they can heal you as well.",
"faqQuestion5": "How do I play Habitica with my friends?",
"iosFaqAnswer5": "The best way is to invite them to a Party with you! Parties can go on quests, battle monsters, and cast skills to support each other. Go to Menu > Party and click \"Create New Party\" if you don't already have a Party. Then tap on the Members list, and tap Invite in the upper right-hand corner to invite your friends by entering their User ID (a string of numbers and letters that they can find under Settings > Account Details on the app, and Settings > API on the website). On the website, you can also invite friends via email, which we will add to the app in a future update.\n\nOn the website, you and your friends can also join Guilds, which are public chat rooms. Guilds will be added to the app in a future update!",
"webFaqAnswer5": "The best way is to invite them to a party with you, under Social > Party! Parties can go on quests, battle monsters, and cast skills to support each other. You can also join guilds together (Social > Guilds). Guilds are chat rooms focusing on a shared interest or the pursuit of a common goal, and can be public or private. You can join as many guilds as you'd like, but only one party.\n
\n For more detailed info, check out the wiki pages on [Parties](http://habitrpg.wikia.com/wiki/Party) and [Guilds](http://habitrpg.wikia.com/wiki/Guilds).",
- "faqQuestion6": "How do I get a Pet or Mount?",
+ "faqQuestion6": "Ako získam zvieratko alebo tátoša?",
"iosFaqAnswer6": "At level 3, you will unlock the Drop System. Every time you complete a task, you'll have a random chance at receiving an egg, a hatching potion, or a piece of food. They will be stored in Menu > Items.\n\n To hatch a Pet, you'll need an egg and a hatching potion. Tap on the egg to determine the species you want to hatch, and select \"Hatch Egg.\" Then choose a hatching potion to determine its color! Go to Menu > Pets to equip your new Pet to your avatar by clicking on it. \n\n You can also grow your Pets into Mounts by feeding them under Menu > Pets. Tap on a Pet, and then select \"Feed Pet\"! You'll have to feed a pet many times before it becomes a Mount, but if you can figure out its favorite food, it will grow more quickly. Use trial and error, or [see the spoilers here](http://habitica.wikia.com/wiki/Food#Food_Preferences). Once you have a Mount, go to Menu > Mounts and tap on it to equip it to your avatar.\n\n You can also get eggs for Quest Pets by completing certain Quests. (See below to learn more about Quests.)",
"webFaqAnswer6": "At level 3, you will unlock the Drop System. Every time you complete a task, you'll have a random chance at receiving an egg, a hatching potion, or a piece of food. They will be stored in Inventory > Market.\n
\n To hatch a Pet, you'll need an egg and a hatching potion. Click on the egg to determine the species you want to hatch, and then click on the hatching potion to determine its color! Go to Inventory > Pets to equip it to your avatar by clicking on it.\n
\n You can also grow your Pets into Mounts by feeding them under Inventory > Pets. Click on a Pet, and then click on a piece of food from the right-hand menu to feed it! You'll have to feed a pet many times before it becomes a Mount, but if you can figure out its favorite food, it will grow more quickly. Use trial and error, or [see the spoilers here](http://habitica.wikia.com/wiki/Food#Food_Preferences). Once you have a Mount, go to Inventory > Mounts and click on it to equip it to your avatar.\n
\n You can also get eggs for Quest Pets by completing certain Quests. (See below to learn more about Quests.)",
- "faqQuestion7": "How do I become a Warrior, Mage, Rogue, or Healer?",
+ "faqQuestion7": "Ako sa stanem bojovníkom, mágom, zlodejom alebo liečiteľom?",
"iosFaqAnswer7": "At level 10, you can choose to become a Warrior, Mage, Rogue, or Healer. (All players start as Warriors by default.) Each Class has different equipment options, different Skills that they can cast after level 11, and different advantages. Warriors can easily damage Bosses, withstand more damage from their tasks, and help make their Party tougher. Mages can also easily damage Bosses, as well as level up quickly and restore Mana for their party. Rogues earn the most gold and find the most item drops, and they can help their Party do the same. Finally, Healers can heal themselves and their Party members.\n\n If you don't want to choose a Class immediately -- for example, if you are still working to buy all the gear of your current class -- you can click “Decide Later” and choose later under Menu > Choose Class.",
"webFaqAnswer7": "At level 10, you can choose to become a Warrior, Mage, Rogue, or Healer. (All players start as Warriors by default.) Each Class has different equipment options, different Skills that they can cast after level 11, and different advantages. Warriors can easily damage Bosses, withstand more damage from their tasks, and help make their party tougher. Mages can also easily damage Bosses, as well as level up quickly and restore Mana for their party. Rogues earn the most Gold and find the most item drops, and they can help their party do the same. Finally, Healers can heal themselves and their party members.\n
\n If you don't want to choose a Class immediately -- for example, if you are still working to buy all the gear of your current class -- you can click \"Opt Out\" and re-enable it later under User > Stats.",
"faqQuestion8": "What is the blue stat bar that appears in the Header after level 10?",
"iosFaqAnswer8": "The blue bar that appeared when you hit level 10 and chose a Class is your Mana bar. As you continue to level up, you will unlock special Skills that cost Mana to use. Each Class has different Skills, which appear after level 11 under Menu > Use Skills. Unlike your health bar, your Mana bar does not reset when you gain a level. Instead, Mana is gained when you complete Good Habits, Dailies, and To-Dos, and lost when you indulge bad Habits. You'll also regain some Mana overnight -- the more Dailies you completed, the more you will gain.",
"webFaqAnswer8": "The blue bar that appeared when you hit level 10 and chose a Class is your Mana bar. As you continue to level up, you will unlock special Skills that cost Mana to use. Each Class has different Skills, which appear after level 11 in a special section in the Rewards Column. Unlike your Health bar, your Mana bar does not reset when you gain a level. Instead, Mana is gained when you complete Good Habits, Dailies, and To-Dos, and lost when you indulge bad Habits. You’ll also regain some Mana overnight -- the more Dailies you completed, the more you will gain.",
- "faqQuestion9": "How do I fight monsters and go on Quests?",
+ "faqQuestion9": "Ako môžem bojovať s príšerami a ísť na výpravy?",
"iosFaqAnswer9": "First, you need to join or start a Party (see above). Although you can battle monsters alone, we recommend playing in a group, because this will make Quests much easier. Plus, having a friend to cheer you on as you accomplish your tasks is very motivating!\n\n Next, you need a Quest Scroll, which are stored under Menu > Items. There are three ways to get a scroll:\n\n - At level 15, you get a Quest-line, aka three linked quests. More Quest-lines unlock at levels 30, 40, and 60 respectively. \n - When you invite people to your Party, you'll be rewarded with the Basi-List Scroll!\n - You can buy Quests from the Quests Page on the [website](https://habitica.com/#/options/inventory/quests) for Gold and Gems. (We will add this feature to the app in a future update.)\n\n To battle the Boss or collect items for a Collection Quest, simply complete your tasks normally, and they will be tallied into damage overnight. (Reloading by pulling down on the screen may be required to see the Boss's health bar go down.) If you are fighting a Boss and you missed any Dailies, the Boss will damage your Party at the same time that you damage the Boss. \n\n After level 11 Mages and Warriors will gain Skills that allow them to deal additional damage to the Boss, so these are excellent classes to choose at level 10 if you want to be a heavy hitter.",
"webFaqAnswer9": "First, you need to join or start a party (under Social > Party). Although you can battle monsters alone, we recommend playing in a group, because this will make quests much easier. Plus, having a friend to cheer you on as you accomplish your tasks is very motivating!\n
\n Next, you need a Quest Scroll, which are stored under Inventory > Quests. There are three ways to get a scroll:\n
\n * When you invite people to your party, you’ll be rewarded with the Basi-List Scroll!\n * At level 15, you get a Quest-line, i.e., three linked quests. More Quest-lines unlock at levels 30, 40, and 60 respectively.\n * You can buy Quests from the Quests Page (Inventory > Quests) for Gold and Gems.\n
\n To battle the Boss or collect items for a Collection Quest, simply complete your tasks normally, and they will be tallied into damage overnight. (Reloading may be required to see the Boss's Health bar go down.) If you are fighting a Boss and you missed any Dailies, the Boss will damage your party at the same time that you damage the Boss.\n
\n After level 11 Mages and Warriors will gain Skills that allow them to deal additional damage to the Boss, so these are excellent classes to choose at level 10 if you want to be a heavy hitter.",
- "faqQuestion10": "What are Gems, and how do I get them?",
+ "faqQuestion10": "Čo sú to drahokamy a ako ich získam?",
"iosFaqAnswer10": "Gems are purchased with real money by tapping on the gem icon in the header. When people buy gems, they are helping us to keep the site running. We're very grateful for their support!\n\n In addition to buying gems directly, there are three other ways players can gain gems:\n\n * Win a Challenge on the [website](https://habitica.com) that has been set up by another player under Social > Challenges. (We will be adding Challenges to the app in a future update!)\n * Subscribe on the [website](https://habitica.com/#/options/settings/subscription) and unlock the ability to buy a certain number of gems per month.\n * Contribute your skills to the Habitica project. See this wiki page for more details: [Contributing to Habitica](http://habitica.wikia.com/wiki/Contributing_to_Habitica).\n\n Keep in mind that items purchased with gems do not offer any statistical advantages, so players can still make use of the app without them!",
"webFaqAnswer10": "Gems are [purchased with real money](https://habitica.com/#/options/settings/subscription), although [subscribers](https://habitica.com/#/options/settings/subscription) can purchase them with Gold. When people subscribe or buy Gems, they are helping us to keep the site running. We're very grateful for their support!\n
\n In addition to buying Gems directly or becoming a subscriber, there are two other ways players can gain Gems:\n
\n * Win a Challenge that has been set up by another player under Social > Challenges.\n * Contribute your skills to the Habitica project. See this wiki page for more details: [Contributing to Habitica](http://habitica.wikia.com/wiki/Contributing_to_Habitica)\n
\n Keep in mind that items purchased with Gems do not offer any statistical advantages, so players can still make use of the site without them!",
- "faqQuestion11": "How do I report a bug or request a feature?",
+ "faqQuestion11": "Ako nahlásim chybu alebo požiadam o novú funkciu?",
"iosFaqAnswer11": "You can report a bug, request a feature, or send feedback under Menu > Report a Bug and Menu > Send Feedback! We'll do everything we can to assist you.",
"webFaqAnswer11": "Bug reports are collected on GitHub. Go to [Help > Report a Bug](https://github.com/HabitRPG/habitrpg/issues/2760) and follow the instructions. Don't worry, we'll get you fixed up soon!\n
\n Feature requests are collected on Trello. Go to [Help > Request a Feature](https://trello.com/c/odmhIqyW/440-read-first-table-of-contents) and follow the instructions. Ta-da!",
"faqQuestion12": "How do I battle a World Boss?",
diff --git a/common/locales/sk/front.json b/common/locales/sk/front.json
index 75e7cf4f04..7a571e82b4 100644
--- a/common/locales/sk/front.json
+++ b/common/locales/sk/front.json
@@ -28,6 +28,7 @@
"communityReddit": "Reddit",
"companyAbout": "Ako to funguje",
"companyBlog": "Blog",
+ "devBlog": "Developer Blog",
"companyDonate": "Darovať",
"companyExtensions": "Rozšírenia",
"companyPrivacy": "Súkromie",
@@ -51,6 +52,7 @@
"featureSocialHeading": "Social play",
"featuredIn": "Featured in",
"featuresHeading": "We also feature...",
+ "footerDevs": "Developers",
"footerCommunity": "Komunita",
"footerCompany": "Naša spoločnosť",
"footerMobile": "Mobilné",
@@ -84,7 +86,7 @@
"landingend4": ", ktoré sú ideálne pre rodiny, učiteľov, podporné skupiny, a firmy.",
"landingfeatureslink": "našich funkcií",
"landingp1": "The problem with most productivity apps on the market is that they provide no incentive to continue using them. Habitica fixes this by making habit building fun! By rewarding you for your successes and penalizing you for slip-ups, Habitica provides external motivation for completing your day-to-day activities.",
- "landingp2": "Kedykoľvek posilníš svoj dobrý návyk, dokončíš dennú úlohu, alebo sa postaráš o staré to-do, Habitica ťa ihneď odmení bodmi skúseností a zlatom. Ako získavaš skúsenosti, zvyšuje sa ti level, zlepšuješ si štatistky a odomykáš nové funkcie, ako tnapríklad povolania a zvieratká. Zlato môžeš míňať na herné predmety, ktoré zmenia tvoj zážitok z hry, alebo za také odmeny, ktoré si vytvoríš v reálnom svete tak, aby ťa motivovali. Aj ten najmenší úspech ťa ihneď odmení a teda znižuje náchylnosť k prokrastinácii.",
+ "landingp2": "Kedykoľvek posilníš svoj dobrý návyk, dokončíš dennú úlohu, alebo sa postaráš o staré plánované úlohy, Habitica ťa ihneď odmení bodmi skúseností a zlatom. Ako získavaš skúsenosti, zvyšuje sa ti level, zlepšuješ si štatistky a odomykáš nové funkcie, ako tnapríklad povolania a zvieratká. Zlato môžeš míňať na herné predmety, ktoré zmenia tvoj zážitok z hry, alebo za také odmeny, ktoré si vytvoríš v reálnom svete tak, aby ťa motivovali. Aj ten najmenší úspech ťa ihneď odmení a teda znižuje náchylnosť k prokrastinácii.",
"landingp2header": "Okamžitá odmena",
"landingp3": "Whenever you indulge in a bad habit or fail to complete one of your daily tasks, you lose health. If your health drops too low, you lose some of the progress you've made. By providing immediate consequences, Habitica can help break bad habits and procrastination cycles before they cause real-world problems.",
"landingp3header": "Dôsledky",
@@ -96,7 +98,7 @@
"loginFacebookAlt": "Prihlásiť / Registrovať pomocou Facebook účtu",
"logout": "Odhlásiť",
"marketing1Header": "Hraním hry si zlepši návyky",
- "marketing1Lead1": "Habitica je počítačová hra, ktorá ti pomôže zlepšiť sa v skutočných návykoch. Gamifikuje tvoj život, tým že úlohy (návyky, denné úlohy a úlohy) pretvára na príšerky, ktoré potrebuješ poraziť. Čím lepšie ti to ide, tým lepšie v hre postupuješ. Ak pochybíš v reálnom živote, negatívne to ovplyvní aj tvoju postavu v hre.",
+ "marketing1Lead1": "Habitica je počítačová hra, ktorá ti pomôže zlepšiť sa v skutočných návykoch. Gamifikuje tvoj život, tým že úlohy (návyky, denné úlohy a plánované úlohy) pretvára na príšerky, ktoré potrebuješ poraziť. Čím lepšie ti to ide, tým lepšie v hre postupuješ. Ak pochybíš v reálnom živote, negatívne to ovplyvní aj tvoju postavu v hre.",
"marketing1Lead2": "Získaj hustú výzbroj. Zlepšuj si návyky a vylepši si postavu. Ukáž akú drsnú výzbroj máš.",
"marketing1Lead2Title": "Získaj hustú výzbroj",
"marketing1Lead3": "Hľadaj náhodné odmeny. Niektorých motivuje hazard, systém nazývaný \"stochastické odmeňovanie\". Habitica poskytuje všetky typy podpory: pozitívna, negatívna, predvídateľná aj náhodná.",
@@ -146,7 +148,7 @@
"rewardHeading": "Complete a task to earn gold!",
"sampleDailies": "Vzorové denné úlohy",
"sampleHabits": "Vzorové návyky",
- "sampleToDo": "Vzorové To-Dos",
+ "sampleToDo": "Vzorové plánované úlohy",
"school": "Škola",
"schoolSample1": "Finish 1 Assignment",
"schoolSample2": "Study 1 hour",
@@ -182,6 +184,7 @@
"zelahQuote": "With [Habitica], I can be persuaded to go to bed on time by the thought of gaining points for an early night or losing health for a late one!",
"reportAccountProblems": "Report Account Problems",
"reportCommunityIssues": "Report Community Issues",
+ "subscriptionPaymentIssues": "Subscription and Payment Issues",
"generalQuestionsSite": "General Questions about the Site",
"businessInquiries": "Business Inquiries",
"merchandiseInquiries": "Merchandise Inquiries",
@@ -222,5 +225,32 @@
"altAttrWebstorm": "WebStorm",
"altAttrGithub": "GitHub",
"altAttrTrello": "Trello",
- "altAttrSlack": "Slack"
+ "altAttrSlack": "Slack",
+ "missingAuthHeaders": "Missing authentication headers.",
+ "missingAuthParams": "Missing authentication parameters.",
+ "missingUsernameEmail": "Missing username or email.",
+ "missingEmail": "Missing email.",
+ "missingUsername": "Missing username.",
+ "missingPassword": "Missing password.",
+ "missingNewPassword": "Missing new password.",
+ "wrongPassword": "Wrong password.",
+ "notAnEmail": "Invalid email address.",
+ "emailTaken": "Email address is already used in an account.",
+ "newEmailRequired": "Missing new email address.",
+ "usernameTaken": "Username already taken.",
+ "passwordConfirmationMatch": "Password confirmation doesn't match password.",
+ "invalidLoginCredentials": "Incorrect username and/or email and/or password.",
+ "passwordReset": "If we have your email on file, your password reset link has been sent to your email.",
+ "passwordResetEmailSubject": "Password Reset for Habitica",
+ "passwordResetEmailText": "Password for <%= username %> has been reset to <%= newPassword %> . Important! Both username and password are case-sensitive -- you must enter both exactly as shown here. We recommend copying and pasting both instead of typing them. Log in at <%= baseUrl %>. After you have logged in, head to <%= baseUrl %>/#/options/settings/settings and change your password.",
+ "passwordResetEmailHtml": "Password for <%= username %> has been reset to <%= newPassword %>.
Important! Both username and password are case-sensitive -- you must enter both exactly as shown here. We recommend copying and pasting both instead of typing them.
Log in at <%= baseUrl %>. After you have logged in, head to <%= baseUrl %>/#/options/settings/settings and change your password.",
+ "invalidLoginCredentialsLong": "Uh-oh - your username or password is incorrect.\n- Make sure your username or email is typed correctly.\n- You may have signed up with Facebook, not email. Double-check by trying Facebook login.\n- If you forgot your password, click \"Forgot Password\".",
+ "invalidCredentials": "There is no account that uses those credentials.",
+ "accountSuspended": "Account has been suspended, please contact leslie@habitica.com with your User ID \"<%= userId %>\" for assistance.",
+ "onlyFbSupported": "Only Facebook is supported currently.",
+ "cantDetachFb": "Account lacks another authentication method, can't detach Facebook.",
+ "onlySocialAttachLocal": "Local authentication can be added to only a social account.",
+ "invalidReqParams": "Invalid request parameters.",
+ "memberIdRequired": "\"member\" must be a valid UUID.",
+ "heroIdRequired": "\"heroId\" must be a valid UUID."
}
\ No newline at end of file
diff --git a/common/locales/sk/gear.json b/common/locales/sk/gear.json
index 0c397d6d71..9a00998023 100644
--- a/common/locales/sk/gear.json
+++ b/common/locales/sk/gear.json
@@ -1,5 +1,5 @@
{
- "set": "Set",
+ "set": "Séria",
"weapon": "zbraň",
"weaponBase0Text": "Bez zbrane",
"weaponBase0Notes": "Bez zbrane.",
@@ -58,7 +58,7 @@
"weaponHealer5Text": "Kráľovské žezlo",
"weaponHealer5Notes": "Hodná monarchovej ruky alebo pre niekoho, kto je monarchovou pravou rukou. Zvyšuje inteligenciu o <%= int %>.",
"weaponHealer6Text": "Zlaté žezlo",
- "weaponHealer6Notes": "Utišuje bolesť všetkých, čo na ňupozrú. Zvyšuje inteligenciu o <%= int %>.",
+ "weaponHealer6Notes": "Utišuje bolesť všetkých, ktorý na ňu pozrú. Zvyšuje inteligenciu o <%= int %>.",
"weaponSpecial0Text": "Meč temných duší",
"weaponSpecial0Notes": "Vysáva z nepriateľa život, čím dodáva silu svojim nehoráznym úderom. Zvyšuje silu o <%= str %>.",
"weaponSpecial1Text": "Kryštálový meč",
@@ -66,11 +66,11 @@
"weaponSpecial2Text": "Dračia palica Stephena Webera",
"weaponSpecial2Notes": "Pocíť dračiu silu prúdiacu z vnútra! Zvyšuje oboje silu a postreh o <%= attrs %>.",
"weaponSpecial3Text": "Drtiaci kropáč Mustaina Milstonea",
- "weaponSpecial3Notes": "Potýčky, príšery, pochyby: poriešené! Pogniaviť! Zvyšuje všetky silu, inteligenciu a odolnosť o <%= attrs %>.",
+ "weaponSpecial3Notes": "Potýčky, príšery, pochyby: poriešené! Pogniaviť! Zvyšuje silu, inteligenciu aj odolnosť o <%= attrs %>.",
"weaponSpecialCriticalText": "Kritické kladivo Bugobijec",
"weaponSpecialCriticalNotes": "Tam kde mnoho bojovníkov padlo, tento šampión skolil problematického zloducha z Githubu. Vyrobené z kostí Bugov, toto kladivo udeľuje mocné kritické zásahy. Zvyšuje silu aj postreh o <%= attrs %>.",
- "weaponSpecialTridentOfCrashingTidesText": "Trident of Crashing Tides",
- "weaponSpecialTridentOfCrashingTidesNotes": "Gives you the ability to command fish, and also deliver some mighty stabs to your tasks. Increases Intelligence by <%= int %>.",
+ "weaponSpecialTridentOfCrashingTidesText": "Trojzubec lámajúci príliv",
+ "weaponSpecialTridentOfCrashingTidesNotes": "Dáva ti schopnosť veliť rybám a tiež zoslať pár mocných úderov na tvoje úlohy. Zvyšuje inteligenciu o <%= int %>.",
"weaponSpecialYetiText": "Kopija krotiteľa yetiov",
"weaponSpecialYetiNotes": "Táto kopija umožňuje rozkazovať hocijakému yetimu. Zvyšuje silu o <%= str %>. Limitovaná Edícia 2013-2014 Zimná Výbava.",
"weaponSpecialSkiText": "Lyžossassinská palica",
@@ -93,29 +93,29 @@
"weaponSpecialSummerWarriorNotes": "There isn't a task in any To-Do list willing to tangle with this gnarly knife! Increases Strength by <%= str %>. Limited Edition 2014 Summer Gear.",
"weaponSpecialSummerMageText": "Kelp Catcher",
"weaponSpecialSummerMageNotes": "This trident is used to spear seaweed effectively, for extra-productive kelp harvesting! Increases Intelligence by <%= int %> and Perception by <%= per %>. Limited Edition 2014 Summer Gear.",
- "weaponSpecialSummerHealerText": "Wand of the Shallows",
+ "weaponSpecialSummerHealerText": "Prútik z plytčín",
"weaponSpecialSummerHealerNotes": "This wand, made of aquamarine and live coral, is very attractive to schools of fish. Increases Intelligence by <%= int %>. Limited Edition 2014 Summer Gear.",
- "weaponSpecialFallRogueText": "Silver Stake",
+ "weaponSpecialFallRogueText": "Strieborný kolík",
"weaponSpecialFallRogueNotes": "Dispatches undead. Also grants a bonus against werewolves, because you can never be too careful. Increases Strength by <%= str %>. Limited Edition 2014 Autumn Gear.",
"weaponSpecialFallWarriorText": "Grabby Claw of Science",
"weaponSpecialFallWarriorNotes": "This grabby claw is at the very cutting edge of technology. Increases Strength by <%= str %>. Limited Edition 2014 Autumn Gear.",
- "weaponSpecialFallMageText": "Magic Broom",
+ "weaponSpecialFallMageText": "Čarovná metla",
"weaponSpecialFallMageNotes": "This enchanted broom flies faster than a dragon! Increases Intelligence by <%= int %> and Perception by <%= per %>. Limited Edition 2014 Autumn Gear.",
- "weaponSpecialFallHealerText": "Scarab Wand",
+ "weaponSpecialFallHealerText": "Skarabeový prútik",
"weaponSpecialFallHealerNotes": "The scarab on this wand protects and heals its wielder. Increases Intelligence by <%= int %>. Limited Edition 2014 Autumn Gear.",
- "weaponSpecialWinter2015RogueText": "Ice Spike",
+ "weaponSpecialWinter2015RogueText": "Ľadový osteň",
"weaponSpecialWinter2015RogueNotes": "You truly, definitely, absolutely just picked these up off of the ground. Increases Strength by <%= str %>. Limited Edition 2014-2015 Winter Gear.",
"weaponSpecialWinter2015WarriorText": "Gumdrop Sword",
"weaponSpecialWinter2015WarriorNotes": "This delicious sword probably attracts monsters... but you're up for the challenge! Increases Strength by <%= str %>. Limited Edition 2014-2015 Winter Gear.",
"weaponSpecialWinter2015MageText": "Winter-lit Staff",
"weaponSpecialWinter2015MageNotes": "The light of this crystal staff fills hearts with cheer. Increases Intelligence by <%= int %> and Perception by <%= per %>. Limited Edition 2014-2015 Winter Gear.",
- "weaponSpecialWinter2015HealerText": "Soothing Scepter",
+ "weaponSpecialWinter2015HealerText": "Chlácholivé žezlo",
"weaponSpecialWinter2015HealerNotes": "This scepter warms sore muscles and soothes away stress. Increases Intelligence by <%= int %>. Limited Edition 2014-2015 Winter Gear.",
"weaponSpecialSpring2015RogueText": "Exploding Squeak",
"weaponSpecialSpring2015RogueNotes": "Don't let the sound fool you - these explosives pack a punch. Increases Strength by <%= str %>. Limited Edition 2015 Spring Gear.",
"weaponSpecialSpring2015WarriorText": "Bone Club",
"weaponSpecialSpring2015WarriorNotes": "It is a real bone club for real fierce doggies and is definitely not a chew toy that the Seasonal Sorceress gave you because who's a good doggy? Whoooo's a good doggy?? It's you!!! You're a good doggy!!! Increases Strength by <%= str %>. Limited Edition 2015 Spring Gear.",
- "weaponSpecialSpring2015MageText": "Magician's Wand",
+ "weaponSpecialSpring2015MageText": "Čarodejníkov prútik",
"weaponSpecialSpring2015MageNotes": "Conjure up a carrot for yourself with this fancy wand. Increases Intelligence by <%= int %> and Perception by <%= per %>. Limited Edition 2015 Spring Gear.",
"weaponSpecialSpring2015HealerText": "Cat Rattle",
"weaponSpecialSpring2015HealerNotes": "When you wave it, it makes a fascinating clickety noise that would keep ANYONE entertained for hours. Increases Intelligence by <%= int %>. Limited Edition 2015 Spring Gear.",
@@ -125,7 +125,7 @@
"weaponSpecialSummer2015WarriorNotes": "The Sun Swordfish is a fearsome weapon, provided that it can be induced to stop wriggling. Increases Strength by <%= str %>. Limited Edition 2015 Summer Gear.",
"weaponSpecialSummer2015MageText": "Soothsayer Staff",
"weaponSpecialSummer2015MageNotes": "Hidden power glimmers in the jewels of this staff. Increases Intelligence by <%= int %> and Perception by <%= per %>. Limited Edition 2015 Summer Gear.",
- "weaponSpecialSummer2015HealerText": "Wand of the Waves",
+ "weaponSpecialSummer2015HealerText": "Prútik vĺn",
"weaponSpecialSummer2015HealerNotes": "Cures seasickness and sea sickness! Increases Intelligence by <%= int %>. Limited Edition 2015 Summer Gear.",
"weaponSpecialFall2015RogueText": "Bat-tle Ax",
"weaponSpecialFall2015RogueNotes": "Fearsome To-Dos cower before the flapping of this ax. Increases Strength by <%= str %>. Limited Edition 2015 Autumn Gear.",
@@ -165,21 +165,21 @@
"weaponArmoireLunarSceptreNotes": "The healing power of this wand waxes and wanes. Increases Constitution by <%= con %> and Intelligence by <%= int %>. Enchanted Armoire: Soothing Lunar Set (Item 3 of 3).",
"weaponArmoireRancherLassoText": "Rancher Lasso",
"weaponArmoireRancherLassoNotes": "Lassos: the ideal tool for rounding up and wrangling. Increases Strength by <%= str %>, Perception by <%= per %>, and Intelligence by <%= int %>. Enchanted Armoire: Rancher Set (Item 3 of 3).",
- "weaponArmoireMythmakerSwordText": "Mythmaker Sword",
+ "weaponArmoireMythmakerSwordText": "Meč Mýtotvorcu",
"weaponArmoireMythmakerSwordNotes": "Though it may seem humble, this sword has made many mythic heroes. Increases Perception and Strength by <%= attrs %> each. Enchanted Armoire: Golden Toga Set (Item 3 of 3).",
- "weaponArmoireIronCrookText": "Iron Crook",
+ "weaponArmoireIronCrookText": "Železný Hák",
"weaponArmoireIronCrookNotes": "Fiercely hammered from iron, this iron crook is good at herding sheep. Increases Perception and Strength by <%= attrs %> each. Enchanted Armoire: Horned Iron Set (Item 3 of 3).",
- "weaponArmoireGoldWingStaffText": "Gold Wing Staff",
+ "weaponArmoireGoldWingStaffText": "Zlatá Okrídlená Palica",
"weaponArmoireGoldWingStaffNotes": "The wings on this staff constantly flutter and twist. Increases all attributes by <%= attrs %> each. Enchanted Armoire: Independent Item.",
- "weaponArmoireBatWandText": "Bat Wand",
+ "weaponArmoireBatWandText": "Netopierí Prútik",
"weaponArmoireBatWandNotes": "This wand can turn any task into a bat! Wave it about and watch them fly away. Increases Intelligence by <%= int %> and Perception by <%= per %>. Enchanted Armoire: Independent Item.",
- "weaponArmoireShepherdsCrookText": "Shepherd's Crook",
+ "weaponArmoireShepherdsCrookText": "Pastierova Palica",
"weaponArmoireShepherdsCrookNotes": "Useful for herding gryphons. Increases Constitution by <%= con %>. Enchanted Armoire: Shepherd Set (Item 1 of 3).",
- "weaponArmoireCrystalCrescentStaffText": "Crystal Crescent Staff",
+ "weaponArmoireCrystalCrescentStaffText": "Kryštálová Polmesiacová Palica",
"weaponArmoireCrystalCrescentStaffNotes": "Summon the power of the crescent moon with this shining staff! Increases Intelligence and Strength by <%= attrs %> each. Enchanted Armoire: Crystal Crescent Set (Item 3 of 3).",
- "weaponArmoireBlueLongbowText": "Blue Longbow",
+ "weaponArmoireBlueLongbowText": "Modrý Luk",
"weaponArmoireBlueLongbowNotes": "Ready... Aim... Fire! This bow has great range. Increases Perception by <%= per %>, Constitution by <%= con %>, and Strength by <%= str %>. Enchanted Armoire: Independent Item.",
- "weaponArmoireGlowingSpearText": "Glowing Spear",
+ "weaponArmoireGlowingSpearText": "Žiarivý Oštep",
"weaponArmoireGlowingSpearNotes": "This spear hypnotizes wild tasks so you can attack them. Increases Strength by <%= str %>. Enchanted Armoire: Independent Item.",
"weaponArmoireBarristerGavelText": "Barrister Gavel",
"weaponArmoireBarristerGavelNotes": "Order! Increases Strength and Constitution by <%= attrs %> each. Enchanted Armoire: Barrister Set (Item 3 of 3).",
@@ -187,9 +187,11 @@
"weaponArmoireJesterBatonNotes": "With a wave of your baton and some witty repartee, even the most complicated situations become clear. Increases Intelligence and Perception by <%= attrs %> each. Enchanted Armoire: Jester Set (Item 3 of 3).",
"weaponArmoireMiningPickaxText": "Mining Pickax",
"weaponArmoireMiningPickaxNotes": "Mine the maximum amount of gold from your tasks! Increases Perception by <%= per %>. Enchanted Armoire: Miner Set (Item 3 of 3).",
- "weaponArmoireBasicLongbowText": "Basic Longbow",
+ "weaponArmoireBasicLongbowText": "Základný Luk",
"weaponArmoireBasicLongbowNotes": "A serviceable hand-me-down bow. Increases Strength by <%= str %>. Enchanted Armoire: Basic Archer Set (Item 1 of 3).",
- "armor": "armor",
+ "weaponArmoireHabiticanDiplomaText": "Habitican Diploma",
+ "weaponArmoireHabiticanDiplomaNotes": "A certificate of significant achievement -- well done! Increases Intelligence by <%= int %>. Enchanted Armoire: Graduate Set (Item 1 of 3).",
+ "armor": "zbroj",
"armorBase0Text": "Prosté ošatenie",
"armorBase0Notes": "Bežné ošatenie. Neposkytuje žiadne výhody.",
"armorWarrior1Text": "Kožená zbroj",
@@ -254,7 +256,7 @@
"armorSpecialBirthday2015Notes": "Happy Birthday, Habitica! Wear these Silly Party Robes to celebrate this wonderful day. Confers no benefit.",
"armorSpecialBirthday2016Text": "Ridiculous Party Robes",
"armorSpecialBirthday2016Notes": "Happy Birthday, Habitica! Wear these Ridiculous Party Robes to celebrate this wonderful day. Confers no benefit.",
- "armorSpecialGaymerxText": "Rainbow Warrior Armor",
+ "armorSpecialGaymerxText": "Dúhová Bojová Zbroj",
"armorSpecialGaymerxNotes": "In celebration of the GaymerX Conference, this special armor is decorated with a radiant, colorful rainbow pattern! GaymerX is a game convention celebrating LGTBQ and gaming and is open to everyone.",
"armorSpecialSpringRogueText": "Priliehavý mačací oblek",
"armorSpecialSpringRogueNotes": "Impeccably groomed. Increases Perception by <%= per %>. Limited Edition 2014 Spring Gear.",
@@ -364,6 +366,10 @@
"armorMystery201512Notes": "Summon the icy flames of winter! Confers no benefit. December 2015 Subscriber Item.",
"armorMystery201603Text": "Lucky Suit",
"armorMystery201603Notes": "This suit is sewn from thousands of four-leafed clovers! Confers no benefit. March 2016 Subscriber Item.",
+ "armorMystery201604Text": "Armor o' Leaves",
+ "armorMystery201604Notes": "You, too, can be a small but fearsome leaf puff. Confers no benefit. April 2016 Subscriber Item.",
+ "armorMystery201605Text": "Marching Bard Uniform",
+ "armorMystery201605Notes": "Unlike the traditional bards who join adventuring parties, bards who join Habitican marching bands are known for grand parades, not dungeon raids. Confers no benefit. May 2016 Subscriber Item.",
"armorMystery301404Text": "Steampunk Suit",
"armorMystery301404Notes": "Dapper and dashing, wot! Confers no benefit. February 3015 Subscriber Item.",
"armorArmoireLunarArmorText": "Soothing Lunar Armor",
@@ -394,6 +400,8 @@
"armorArmoireMinerOverallsNotes": "They may seem worn, but they are enchanted to repel dirt. Increases Constitution by <%= con %>. Enchanted Armoire: Miner Set (Item 2 of 3).",
"armorArmoireBasicArcherArmorText": "Basic Archer Armor",
"armorArmoireBasicArcherArmorNotes": "This camouflaged vest lets you slip unnoticed through the forests. Increases Perception by <%= per %>. Enchanted Armoire: Basic Archer Set (Item 2 of 3).",
+ "armorArmoireGraduateRobeText": "Graduate Robe",
+ "armorArmoireGraduateRobeNotes": "Congratulations! This weighty robe hangs heavy with all the knowledge you have accrued. Increases Intelligence by <%= int %>. Enchanted Armoire: Graduate Set (Item 2 of 3).",
"headgear": "headgear",
"headBase0Text": "Bez helmy",
"headBase0Notes": "Bez pokrývky hlavy.",
@@ -442,7 +450,7 @@
"headSpecial1Text": "Kryštálová helma",
"headSpecial1Notes": "Helma, ktorú obľubujú tí, čo sú radi pre iných vzorom. Zvyšuje všetky vlastnosti o <%= attrs %>.",
"headSpecial2Text": "Bezmenná helma",
- "headSpecial2Notes": "Svedectvo pre tých, čo sa obedovali bez toho, že by čosi žiadali na oplátku. Zvyšuje inteligenciu aj silu o <%= attrs %> .",
+ "headSpecial2Notes": "Svedectvo pre tých, čo sa obetovali bez toho, že by čosi žiadali na oplátku. Zvyšuje inteligenciu aj silu o <%= attrs %> .",
"headSpecialFireCoralCircletText": "Fire Coral Circlet",
"headSpecialFireCoralCircletNotes": "This circlet, designed by Habitica's greatest alchemists, allows you to breathe water and dive for treasure! Increases Perception by <%= per %>.",
"headSpecialNyeText": "Absurdný párty klobúk",
@@ -565,6 +573,10 @@
"headMystery201602Notes": "Shield your identity from all your admirers. Confers no benefit. February 2016 Subscriber Item.",
"headMystery201603Text": "Lucky Hat",
"headMystery201603Notes": "This top hat is a magical good-luck charm. Confers no benefit. March 2016 Subscriber Item.",
+ "headMystery201604Text": "Crown o' Flowers",
+ "headMystery201604Notes": "These woven flowers make a surprisingly strong helm! Confers no benefit. April 2016 Subscriber Item.",
+ "headMystery201605Text": "Marching Bard Hat",
+ "headMystery201605Notes": "Seventy-six dragons led the big parade, with a hundred and ten gryphons close at hand! Confers no benefit. May 2016 Subscriber Item.",
"headMystery301404Text": "Fancy Top Hat",
"headMystery301404Notes": "A fancy top hat for the finest of gentlefolk! January 3015 Subscriber Item. Confers no benefit.",
"headMystery301405Text": "Basic Top Hat",
@@ -613,6 +625,8 @@
"headArmoireMinerHelmetNotes": "Protect your head from falling tasks! Increases Intelligence by <%= int %>. Enchanted Armoire: Miner Set (Item 1 of 3).",
"headArmoireBasicArcherCapText": "Basic Archer Cap",
"headArmoireBasicArcherCapNotes": "No archer would be complete without a jaunty cap! Increases Perception by <%= per %>. Enchanted Armoire: Basic Archer Set (Item 3 of 3).",
+ "headArmoireGraduateCapText": "Graduate Cap",
+ "headArmoireGraduateCapNotes": "Congratulations! Your deep thoughts have earned you this thinking cap. Increases Intelligence by <%= int %>. Enchanted Armoire: Graduate Set (Item 3 of 3).",
"offhand": "shield-hand item",
"shieldBase0Text": "No Shield-Hand Equipment",
"shieldBase0Notes": "Bez štítu alebo druhej zbrane.",
@@ -673,7 +687,7 @@
"shieldSpecialWinter2015HealerText": "Soothing Shield",
"shieldSpecialWinter2015HealerNotes": "This shield deflects the freezing wind. Increases Constitution by <%= con %>. Limited Edition 2014-2015 Winter Gear.",
"shieldSpecialSpring2015RogueText": "Exploding Squeak",
- "shieldSpecialSpring2015RogueNotes": "Don't let the sound fool you - these explosives pack a punch. Increases Strength <%= str %>. Limited Edition 2015 Spring Gear.",
+ "shieldSpecialSpring2015RogueNotes": "Don't let the sound fool you - these explosives pack a punch. Increases Strength by <%= str %>. Limited Edition 2015 Spring Gear.",
"shieldSpecialSpring2015WarriorText": "Dish Discus",
"shieldSpecialSpring2015WarriorNotes": "Hurl it at your enemies.... or just hold it, because it will fill up with yummy kibble at dinnertime. Increases Constitution by <%= con %>. Limited Edition 2015 Spring Gear.",
"shieldSpecialSpring2015HealerText": "Patterned Pillow",
@@ -716,6 +730,8 @@
"shieldArmoireDragonTamerShieldNotes": "Distract enemies with this dragon-shaped shield. Increases Perception by <%= per %>. Enchanted Armoire: Dragon Tamer Set (Item 2 of 3).",
"shieldArmoireMysticLampText": "Mystic Lamp",
"shieldArmoireMysticLampNotes": "Light the darkest caves with this mystic lamp! Increases Perception by <%= per %>. Enchanted Armoire: Independent Item.",
+ "shieldArmoireFloralBouquetText": "Bouquet o' Flowers",
+ "shieldArmoireFloralBouquetNotes": "Not much help in battle, but aren't they beautiful? Increases Constitution by <%= con %>. Enchanted Armoire: Independent Item.",
"back": "Back Accessory",
"backBase0Text": "No Back Accessory",
"backBase0Notes": "No Back Accessory.",
@@ -820,6 +836,20 @@
"eyewear": "Eyewear",
"eyewearBase0Text": "No Eyewear",
"eyewearBase0Notes": "No Eyewear.",
+ "eyewearSpecialBlackTopFrameText": "Black Standard Eyeglasses",
+ "eyewearSpecialBlackTopFrameNotes": "Glasses with a black frame above the lenses. Confers no benefit.",
+ "eyewearSpecialBlueTopFrameText": "Blue Standard Eyeglasses",
+ "eyewearSpecialBlueTopFrameNotes": "Glasses with a blue frame above the lenses. Confers no benefit.",
+ "eyewearSpecialGreenTopFrameText": "Green Standard Eyeglasses",
+ "eyewearSpecialGreenTopFrameNotes": "Glasses with a green frame above the lenses. Confers no benefit.",
+ "eyewearSpecialPinkTopFrameText": "Pink Standard Eyeglasses",
+ "eyewearSpecialPinkTopFrameNotes": "Glasses with a pink frame above the lenses. Confers no benefit.",
+ "eyewearSpecialRedTopFrameText": "Red Standard Eyeglasses",
+ "eyewearSpecialRedTopFrameNotes": "Glasses with a red frame above the lenses. Confers no benefit.",
+ "eyewearSpecialWhiteTopFrameText": "White Standard Eyeglasses",
+ "eyewearSpecialWhiteTopFrameNotes": "Glasses with a white frame above the lenses. Confers no benefit.",
+ "eyewearSpecialYellowTopFrameText": "Yellow Standard Eyeglasses",
+ "eyewearSpecialYellowTopFrameNotes": "Glasses with a yellow frame above the lenses. Confers no benefit.",
"eyewearSpecialSummerRogueText": "Roguish Eyepatch",
"eyewearSpecialSummerRogueNotes": "It doesn't take a scallywag to see how stylish this is! Confers no benefit. Limited Edition 2014 Summer Gear.",
"eyewearSpecialSummerWarriorText": "Dashing Eyepatch",
diff --git a/common/locales/sk/generic.json b/common/locales/sk/generic.json
index 469610cb4b..5d423bc44f 100644
--- a/common/locales/sk/generic.json
+++ b/common/locales/sk/generic.json
@@ -3,41 +3,41 @@
"stringNotFound": "Reťazec '<%= string %>' sa nenašiel.",
"titleIndex": "Habitica | Tvoj Život - RPG",
"habitica": "Habitika",
- "titleTasks": "Tasks",
+ "titleTasks": "Úlohy",
"titleAvatar": "Avatar",
- "titleBackgrounds": "Backgrounds",
- "titleStats": "Stats & Achievements",
- "titleProfile": "Profile",
- "titleInbox": "Inbox",
- "titleTavern": "Tavern",
- "titleParty": "Party",
- "titleHeroes": "Hall of Heroes",
- "titlePatrons": "Hall of Patrons",
- "titleGuilds": "Guilds",
- "titleChallenges": "Challenges",
- "titleDrops": "Market",
- "titleQuests": "Quests",
- "titlePets": "Pets",
- "titleMounts": "Mounts",
- "titleEquipment": "Equipment",
- "titleTimeTravelers": "Time Travelers",
- "titleSeasonalShop": "Seasonal Shop",
- "titleSettings": "Settings",
+ "titleBackgrounds": "Pozadie",
+ "titleStats": "Štatistiky a Odznaky",
+ "titleProfile": "Profil",
+ "titleInbox": "Poštová schránka",
+ "titleTavern": "Hostinec",
+ "titleParty": "Družina",
+ "titleHeroes": "Sieň hrdinov",
+ "titlePatrons": "Sieň patrónov",
+ "titleGuilds": "Cechy",
+ "titleChallenges": "Výzvy",
+ "titleDrops": "Trh",
+ "titleQuests": "Výpravy",
+ "titlePets": "Zvieratká",
+ "titleMounts": "Tátoše",
+ "titleEquipment": "Výstroj",
+ "titleTimeTravelers": "Cestovatelia časom",
+ "titleSeasonalShop": "Sezónny obchod",
+ "titleSettings": "Nastavenia",
"expandToolbar": "Rozbaliť lištu",
"collapseToolbar": "Zrolovať lištu",
"markdownBlurb": "Habitika používa markdown na formátovanie správ. Pre viac info pozri na Markdown Cheat Sheet",
"showFormattingHelp": "Zobraziť pomôcku pre formátovanie",
"hideFormattingHelp": "Skryť pomôcku pre formátovanie",
- "youType": "You type:",
- "youSee": "You see:",
- "italics": "*Italics*",
- "bold": "**Bold**",
- "strikethrough": "~~Strikethrough~~",
+ "youType": "Píšeš",
+ "youSee": "Vidíš",
+ "italics": "*Šikmé*",
+ "bold": "**Hrubé**",
+ "strikethrough": "~~Prečiarknuté~~",
"emojiExample": ":smile:",
- "markdownLinkEx": "[Habitica is great!](https://habitica.com)",
- "markdownImageEx": "",
- "unorderedListHTML": "+ First item + Second item + Third item",
- "unorderedListMarkdown": "+ First item\n+ Second item\n+ Third item",
+ "markdownLinkEx": "[Habitika je skvelá!](https://habitica.com)",
+ "markdownImageEx": "",
+ "unorderedListHTML": "+ Prvá vec + Druhá vec + Tretia vec",
+ "unorderedListMarkdown": "+ Prvá vec\n+ Druhá vec\n+ Tretia vec",
"code": "`code`",
"achievements": "Odznaky",
"modalAchievement": "Odznak!",
@@ -48,7 +48,7 @@
"market": "Trh",
"subscriberItem": "Záhadný predmet",
"newSubscriberItem": "Nový tajomný predmet",
- "subscriberItemText": "Each month, subscribers will receive a mystery item. This is usually released about one week before the end of the month. See the wiki's 'Mystery Item' page for more information.",
+ "subscriberItemText": "Predplatitelia dostanú každý mesiac záhadný predmet. Obvykle sa dostáva vždy týždeň pred koncom mesiaca. Pozri článok na wiki s názvom ,,Mystery item\" pre viac informácii.",
"all": "Všetko",
"none": "Nič",
"or": "Alebo",
@@ -68,7 +68,7 @@
"neverMind": "Radšej nie",
"buyMoreGems": "Kúpiť viac drahokamov",
"notEnoughGems": "Nedostatok drahokamov",
- "alreadyHave": "Whoops! You already have this item. No need to buy it again!",
+ "alreadyHave": "Ups! Tento predmet už máš. Nepotrebuješ ho kupovať znovu!",
"delete": "Zmazať",
"gemsPopoverTitle": "Drahokamy",
"gems": "Drahokamy",
@@ -76,22 +76,22 @@
"moreInfo": "Viac informácií",
"showMoreMore": "(ukázať viac)",
"showMoreLess": "(ukázať menej)",
- "gemsWhatFor": "Click to buy Gems! Gems let you purchase special items like Quests, avatar customizations, and seasonal equipment.",
+ "gemsWhatFor": "Klinki a kúpiš drahokamy! Za drahokamy môžeš kúpiť špeciálne veci ako zvitky s výpravami, úpravy avatara a sezónnu výbavu.",
"veteran": "Veterán",
"veteranText": "Je ošľahaný Habitom Šedým (naša stránka pred Angularom), a utŕžil veľa jaziev z boja s krvilačnými bugmi.",
"originalUser": "Pôvodný používateľ!",
"originalUserText": "Jeden z prapôvodných používateľov. Nebojácny alfa tester!",
- "habitBirthday": "Habitica Birthday Bash",
- "habitBirthdayText": "Celebrated the Habitica Birthday Bash!",
- "habitBirthdayPluralText": "Celebrated <%= number %> Habitica Birthday Bashes!",
- "habiticaDay": "Habitica Naming Day",
- "habiticaDaySingularText": "Celebrated Habitica's Naming Day! Thanks for being a fantastic user.",
- "habiticaDayPluralText": "Celebrated <%= number %> Naming Days! Thanks for being a fantastic user.",
- "achievementDilatory": "Savior of Dilatory",
- "achievementDilatoryText": "Helped defeat the Dread Drag'on of Dilatory during the 2014 Summer Splash Event!",
- "costumeContest": "Costume Contestant",
- "costumeContestText": "Participated in the Habitoween Costume Contest. See some of the entries on the Habitica blog!",
- "costumeContestTextPlural": "Participated in <%= number %> Habitoween Costume Contests. See some of the entries on the Habitica blog!",
+ "habitBirthday": "Narodeninová oslava Habitiky",
+ "habitBirthdayText": "Slávil na Narodeninovej oslave Habitiky!",
+ "habitBirthdayPluralText": "Slávil na <%= number %> Narodeninových oslavách Habitiky!",
+ "habiticaDay": "Menovací deň Habitiky",
+ "habiticaDaySingularText": "Oslavoval Menovací deň Habitiky! Ďakujeme za to, že je fantastický užívateľ.",
+ "habiticaDayPluralText": "Oslavoval <%= number %> Menovacích dní Habitiky! Ďakujeme za to, že je fantastický užívateľ.",
+ "achievementDilatory": "Záchranca váhavosti",
+ "achievementDilatoryText": "Pomohol poraziť Desivého Dra'ka váhavosti počas Udalosti letného špliechania 2014!",
+ "costumeContest": "Súťaž kostýmov",
+ "costumeContestText": "Zúčastnil sa Habitoweenskej súťaže kostýmov. O vstupe do súťaže sa viac dozvieš na blogu Habitiky!",
+ "costumeContestTextPlural": "Zúčastnil sa <%= number %> Habitoweenskych súťaží kostýmov. O vstupe do súťaže sa viac dozvieš na blogu Habitiky!",
"memberSince": "- Členom od",
"lastLoggedIn": "- Posledné prihlásenie",
"notPorted": "Táto funkcia ešte nie je prenesená z pôvodnej stránky.",
@@ -100,26 +100,26 @@
"errorUpCase": "CHYBA:",
"newPassSent": "Nové heslo odoslané.",
"serverUnreach": "Server je dočasne nedostupný.",
- "requestError": "Yikes, an error occurred! Please reload the page, your last action may not have been saved correctly.",
- "seeConsole": "If the error persists, please report it at Help > Report a Bug. If you're familiar with your browser's console, please include any error messages.",
+ "requestError": "Och, nastala chyba! Prosím, znova načítaj stránku, tvoja posledná činnosť nemusela byť správne uložená.",
+ "seeConsole": "Ak chyba pretrváva, prosím nahláste ju v sekcií Pomoc > Nahlásiť chybu. Ak si rozumieš s konzolou prehliadača, prosím pripoj aj chybové správy.",
"error": "Chyba",
"menu": "Menu",
"notifications": "Notifikácie",
"noNotifications": "Žiadne nové správy",
"clear": "Čisté",
"endTour": "Ukončiť prehliadku",
- "audioTheme": "Audio Theme",
+ "audioTheme": "Zvuková téma",
"audioTheme_off": "Vypnuté",
- "audioTheme_danielTheBard": "Daniel The Bard",
- "audioTheme_wattsTheme": "Watts' Theme",
- "audioTheme_gokulTheme": "Gokul Theme",
- "audioTheme_luneFoxTheme": "LuneFox's Theme",
+ "audioTheme_danielTheBard": "Daniel, bard",
+ "audioTheme_wattsTheme": "Wattsova téma",
+ "audioTheme_gokulTheme": "Téma Gokul",
+ "audioTheme_luneFoxTheme": "Téma od LuneFox",
"askQuestion": "Spýtaj sa otázku",
"reportBug": "Nahlás problém",
- "HabiticaWiki": "The Habitica Wiki",
+ "HabiticaWiki": "Wiki Habitiky",
"HabiticaWikiFrontPage": "http://habitica.wikia.com/wiki/Habitica_Wiki",
- "contributeToHRPG": "Contribute to Habitica",
- "overview": "Overview for New Users",
+ "contributeToHRPG": "Prispej Habitice",
+ "overview": "Súhrn pre nových užívateľov",
"January": "Január",
"February": "Február",
"March": "Marec",
@@ -132,51 +132,51 @@
"October": "Október",
"November": "November",
"December": "December",
- "dateFormat": "Date Format",
- "achievementStressbeast": "Savior of Stoïkalm",
- "achievementStressbeastText": "Helped defeat the Abominable Stressbeast during the 2014 Winter Wonderland Event!",
- "achievementBurnout": "Savior of the Flourishing Fields",
- "achievementBurnoutText": "Helped defeat Burnout and restore the Exhaust Spirits during the 2015 Fall Festival Event!",
- "achievementBewilder": "Savior of Mistiflying",
- "achievementBewilderText": "Helped defeat the Be-Wilder during the 2016 Spring Fling Event!",
- "checkOutProgress": "Check out my progress in Habitica!",
- "cardReceived": "Received a card!",
- "cardReceivedFrom": "<%= cardType %> from <%= userName %>",
- "greetingCard": "Greeting Card",
- "greetingCardExplanation": "You both receive the Cheery Chum achievement!",
- "greetingCardNotes": "Send a greeting card to a party member.",
- "greeting0": "Hi there!",
- "greeting1": "Just saying hello :)",
- "greeting2": "`waves frantically`",
- "greeting3": "Hey you!",
- "greetingCardAchievementTitle": "Cheery Chum",
- "greetingCardAchievementText": "Hey! Hi! Hello! Sent or received <%= cards %> greeting cards.",
- "thankyouCard": "Thank-You Card",
- "thankyouCardExplanation": "You both receive the Greatly Grateful achievement!",
- "thankyouCardNotes": "Send a Thank-You card to a party member.",
- "thankyou0": "Thank you very much!",
- "thankyou1": "Thank you, thank you, thank you!",
- "thankyou2": "Sending you a thousand thanks.",
- "thankyou3": "I'm very grateful - thank you!",
- "thankyouCardAchievementTitle": "Greatly Grateful",
- "thankyouCardAchievementText": "Thanks for being thankful! Sent or received <%= cards %> Thank-You cards.",
- "birthdayCard": "Birthday Card",
- "birthdayCardExplanation": "You both receive the Birthday Bonanza achievement!",
- "birthdayCardNotes": "Send a birthday card to a party member.",
- "birthday0": "Happy birthday to you!",
- "birthdayCardAchievementTitle": "Birthday Bonanza",
- "birthdayCardAchievementText": "Many happy returns! Sent or received <%= cards %> birthday cards.",
- "streakAchievement": "You earned a streak achievement!",
- "firstStreakAchievement": "21-Day Streak",
- "streakAchievementCount": "<%= streaks %> 21-Day Streaks",
- "twentyOneDays": "You've completed your Daily for 21 days in a row!",
- "dontBreakStreak": "Amazing job. Don't break the streak!",
- "dontStop": "Don't Stop Now!",
- "levelUpShare": "I leveled up in Habitica by improving my real-life habits!",
- "questUnlockShare": "I unlocked a new quest in Habitica!",
- "hatchPetShare": "I hatched a new pet by completing my real-life tasks!",
- "raisePetShare": "I raised a pet into a mount by completing my real-life tasks!",
- "wonChallengeShare": "I won a challenge in Habitica!",
- "achievementShare": "I earned a new achievement in Habitica!",
- "orderBy": "Order By <%= item %>"
+ "dateFormat": "Formát dátumu",
+ "achievementStressbeast": "Záchranca Stoïkalmu",
+ "achievementStressbeastText": "Pomohol poraziť Odporného stresozvera počas Udalosti zimnej krajiny zázrakov 2014!",
+ "achievementBurnout": "Záchranca kvitnúcich polí",
+ "achievementBurnoutText": "Pomohol poraziť Vypaľača a zachránil Vyčerpané duše počas Jesenného festivalu 2015!",
+ "achievementBewilder": "Záchranca zakliatych v hmle",
+ "achievementBewilderText": "Pomohol poraziť Be-Wildera počas Udalosti jarnej rozcvičky 2016!",
+ "checkOutProgress": "Pozrieť si môj postup v Habitike!",
+ "cardReceived": "Získal si kartu!",
+ "cardReceivedFrom": "<%= cardType %> od <%= userName %>",
+ "greetingCard": "Pozdrav",
+ "greetingCardExplanation": "Obaja získavate odznak Čerešňový kamarát!",
+ "greetingCardNotes": "Pošli pozdrav členovi družiny.",
+ "greeting0": "Ahoj!",
+ "greeting1": "Len som ťa prišiel pozdraviť :)",
+ "greeting2": "´horúčkovito máva´",
+ "greeting3": "Hej ty!",
+ "greetingCardAchievementTitle": "Veselý kamarát",
+ "greetingCardAchievementText": "Hej! Ahoj! Nazdar! Poslal si alebo získal <%= cards %> pozdravov.",
+ "thankyouCard": "Ďakovná pohľadnica",
+ "thankyouCardExplanation": "Obaja získavate odznak Veľmi vďačný!",
+ "thankyouCardNotes": "Pošli ďakovnú pohľadnicu členovi družiny.",
+ "thankyou0": "Ďakujem ti veľmi pekne!",
+ "thankyou1": "Ďakujem, ďakujem, ďakujem!",
+ "thankyou2": "Tisíceré díky!",
+ "thankyou3": "Som veľmi vďačný - ďakujem ti!",
+ "thankyouCardAchievementTitle": "Veľmi vďačný",
+ "thankyouCardAchievementText": "Ďakujeme za to, že si vďačný! Odoslaných a prijatých <%= cards %> ďakovných pohľadníc.",
+ "birthdayCard": "Narodeninové prianie",
+ "birthdayCardExplanation": "Obaja získavate odznak Narodeninový zisk!",
+ "birthdayCardNotes": "Pošli narodeninové prianie členovi družiny.",
+ "birthday0": "Všetko najlepšie!",
+ "birthdayCardAchievementTitle": "Narodeninový zisk",
+ "birthdayCardAchievementText": "Veľa šťastných návratov! Poslal alebo prijal <%= cards %> narodeninových oznámení.",
+ "streakAchievement": "Získal si odznak série!",
+ "firstStreakAchievement": "21-dňová séria",
+ "streakAchievementCount": "<%= streaks %> 21-dňových sérii",
+ "twentyOneDays": "Splnil si svoju dennú úlohu 21 dní za sebou!",
+ "dontBreakStreak": "Výborná práca. Neporuš reťazec!",
+ "dontStop": "Neprestávaj!",
+ "levelUpShare": "V Habitike som zvýšil svoju úroveň zlepšovaním mojich návykov v reálnom živote!",
+ "questUnlockShare": "Odomkol som novú výpravu v Habitike!",
+ "hatchPetShare": "Vďaka plneniu úloh v reálnom živote sa mi vyliahlo nové zvieratko!",
+ "raisePetShare": "Moje zvieratko vyrástlo v tátoše vďaka plneniu úloh v reálnom živote!",
+ "wonChallengeShare": "Vyhral som výzvu v Habitike!",
+ "achievementShare": "Získal som nový odznak v Habitike!",
+ "orderBy": "Zoradiť podľa <%= item %>"
}
\ No newline at end of file
diff --git a/common/locales/sk/groups.json b/common/locales/sk/groups.json
index f7e80c3182..c9815caa36 100644
--- a/common/locales/sk/groups.json
+++ b/common/locales/sk/groups.json
@@ -1,9 +1,9 @@
{
- "tavern": "Tavern Chat",
+ "tavern": "Hostinec",
"innCheckOut": "Odíď z hostinca",
"innCheckIn": "Odpočiň si v hostinci",
- "innText": "You're resting in the Inn! While checked-in, your Dailies won't hurt you at the day's end, but they will still refresh every day. Be warned: If you are participating in a Boss Quest, the Boss will still damage you for your party mates' missed Dailies unless they are also in the Inn! Also, your own damage to the Boss (or items collected) will not be applied until you check out of the Inn.",
- "innTextBroken": "You're resting in the Inn, I guess... While checked-in, your Dailies won't hurt you at the day's end, but they will still refresh every day... If you are participating in a Boss Quest, the Boss will still damage you for your party mates' missed Dailies... unless they are also in the Inn... Also, your own damage to the Boss (or items collected) will not be applied until you check out of the Inn... so tired...",
+ "innText": "Odpočívaš v hostinci! Zatiaľ, čo odpočívaš, tvoje denné úlohy ťa nezrania na konci dňa, ale stále sa budú obnovovať každý deň. Daj si pozor: ak sa účastníš lovu na bossa, boss ťa aj tak bude zraňovať za nesplnené úlohy ostatných členov tvojej družiny okrem prípadov, keď aj oni odpočívajú v hostinci! Taktiež tvoje vlastné poškodenie bossa (alebo zozberané veci) nebudú potvrdené, až pokým sa neodhlásiš z krčmy.",
+ "innTextBroken": "Odpočívaš v hostinci, pokiaľ sa nemýlim... Zatiaľ, čo odpočívaš, tvoje denné úlohy ťa nezrania na konci dňa, ale stále sa budú obnovovať každý deň... Ak sa účastníš lovu na bossa, boss ťa aj tak bude zraňovať za nesplnené úlohy ostatných členov tvojej družiny... okrem prípadov, keď aj oni odpočívajú v hostinci... Taktiež tvoje vlastné poškodenie bossa (alebo zozberané veci) nebudú potvrdené, až pokým sa neodhlásiš z krčmy... taký unavený...",
"lfgPosts": "Príspevky ľudí hľadajúcich skupiny (\"Zháňam družinu\")",
"tutorial": "Návod",
"glossary": "Glosár",
@@ -13,30 +13,30 @@
"community": "Komunitné fórum",
"dataTool": "Nástroj na zobrazenie dát",
"resources": "Zdroje",
- "askQuestionNewbiesGuild": "Ask a Question (Newbies Guild)",
+ "askQuestionNewbiesGuild": "Spýtaj sa otázku (Newbies Guild)",
"tavernTalk": "Pokec v hostinci",
"tavernAlert1": "Poznámka: ak nahlasuješ bug, tu si ho vývojári nevšimnú. Prosím",
- "tavernAlert2": "use GitHub instead",
+ "tavernAlert2": "miesto toho použi GitHub",
"moderatorIntro1": "Moderátori hostinca a cechu sú:",
- "communityGuidelines": "Community Guidelines",
- "communityGuidelinesRead1": "Please read our",
- "communityGuidelinesRead2": "before chatting.",
+ "communityGuidelines": "Komunitné Pravidlá",
+ "communityGuidelinesRead1": "Prosím, prečítaj si naše",
+ "communityGuidelinesRead2": "pred chatovaním.",
"party": "Družina",
"createAParty": "Vytvoriť družinu",
- "updatedParty": "Party settings updated.",
- "noPartyText": "You are either not in a party or your party is taking a while to load. You can either create one and invite friends, or if you want to join an existing party, have them enter your Unique User ID below and then come back here to look for the invitation:",
- "LFG": "To advertise your new party or find one to join, go to the <%= linkStart %>Party Wanted (Looking for Group)<%= linkEnd %> Guild.",
- "wantExistingParty": "Want to join an existing party? Go to the <%= linkStart %>Party Wanted Guild<%= linkEnd %> and post this User ID:",
- "joinExistingParty": "Join Someone Else's Party",
+ "updatedParty": "Nastavenia družiny boli aktualizované.",
+ "noPartyText": "Buď nie si v družine, alebo tvojej družine chvíľu trvá, dokedy sa načíta. Môžeš si vytvoriť družinu a pozvať priateľov, alebo ak sa chceš pripojiť k existujúcej družine, nechaj jej členov, aby napísali tvoje unikátne ID užívateľa nižšie a neskôr sa vráť aby si skontroloval pozvánku:",
+ "LFG": "Na prezentáciu svojej družiny alebo na hľadanie nejakej, ku ktorej sa môžeš pripojiť, navštív cech <%= linkStart %>Party Wanted (Looking for Group)<%= linkEnd %>.",
+ "wantExistingParty": "Chceš sa pripojiť k existujúcej družine? Navštív cech <%= linkStart %>Party Wanted<%= linkEnd %> a napíš svoje užívateľské ID:",
+ "joinExistingParty": "Pripoj sa k družine",
"create": "Vytvoriť",
"userId": "ID používateľa",
"invite": "Pozvať",
"leave": "Opustiť",
"invitedTo": "Pozvaný do <%= name %>",
- "invitedToNewParty": "You were invited to join a party! Do you want to leave this party and join <%= partyName %>?",
- "joinNewParty": "Join New Party",
- "declineInvitation": "Decline Invitation",
- "loadingNewParty": "Your new party is loading. Please wait...",
+ "invitedToNewParty": "Bol si pozvaný do družiny! Chceš opustiť túto družinu a pridať sa k <%= partyName %>?",
+ "joinNewParty": "Pridať sa k novej družine",
+ "declineInvitation": "Odmietnuť pozvánku",
+ "loadingNewParty": "Tvoja nová družina sa načítava. Prosím počkaj...",
"newMsg": "Nová správa v \"<%= name %>\"",
"chat": "Chat",
"sendChat": "Odoslať",
@@ -48,12 +48,12 @@
"newGroupName": "<%= groupType %> meno",
"groupName": "Názov družiny",
"groupLeader": "Vodca družiny",
- "groupID": "Group ID",
+ "groupID": "Skupinové ID",
"groupDescr": "Popis zobrazujúci sa vo verejnom zozname cechov (Markdown je OK)",
"logoUrl": "URL loga",
"assignLeader": "Vodca družiny",
- "members": "Members",
- "partyList": "Order for party members in header",
+ "members": "Členovia",
+ "partyList": "Zoradenie členov družiny v hlavičke stránky",
"banTip": "Vykopnúť člena",
"moreMembers": "viac členov",
"invited": "Pozvaný",
@@ -68,22 +68,22 @@
"createGuild": "Vytvoriť cech",
"guild": "Cech",
"guilds": "Cechy",
- "sureKick": "Do you really want to remove this member from the party/guild?",
+ "sureKick": "Naozaj chceš odstrániť tohto člena z družiny/cechu?",
"optionalMessage": "Voliteľná správa",
- "yesRemove": "Yes, remove them",
+ "yesRemove": "Áno, odstráň ho",
"foreverAlone": "Nemôžeš si lajkovať vlastnú správu. Nechceš hádam byť jeden z tých ľudí.",
"sortLevel": "Zoradiť podľa levelu",
"sortRandom": "Zoradiť náhodne",
"sortPets": "Zoradiť podľa počtu zvieratiek",
- "sortJoined": "Sort by date joined the party",
- "sortName": "Sort by avatar name",
+ "sortJoined": "Zoradiť podľa dátumu prijatia do družiny",
+ "sortName": "Zoradiť podľa mien avatarov",
"sortBackgrounds": "Zoradiť podľa pozadia",
- "sortHabitrpgJoined": "Sort by Habitica date joined",
- "sortHabitrpgLastLoggedIn": "Sort by last time user logged in",
+ "sortHabitrpgJoined": "Zoradiť podľa dátumu pridania sa k Habitice",
+ "sortHabitrpgLastLoggedIn": "Zoradiť podľa posledného prihlásenia užívateľa",
"ascendingSort": "Zoradiť vzostupne",
"descendingSort": "Zoradiť zostupne",
"confirmGuild": "Vytvoriť cech za 4 drahokamy?",
- "leaveGroupCha": "Leave Guild challenges and...",
+ "leaveGroupCha": "Opustiť cechové výzvy a...",
"confirm": "Potvrdiť",
"leaveGroup": "Opustiť cech?",
"leavePartyCha": "Opustiť výzvy družiny a...",
@@ -92,64 +92,98 @@
"send": "Odoslať",
"messageSentAlert": "Správa bola odoslaná",
"pmHeading": "Súkromná správa pre <%= name %>",
+ "pmsMarkedRead": "Your private messages have been marked as read",
"clearAll": "Vymazať všetky správy",
- "confirmDeleteAllMessages": "Are you sure you want to delete all messages in your inbox? Other users will still see messages you have sent to them.",
- "optOutPopover": "Don't like private messages? Click to completely opt out",
+ "confirmDeleteAllMessages": "Si si istý, že chceš vymazať všetky svoje správy v poštovej schránke? Ostatní užívatelia uvidia správy, ktoré si im poslal.",
+ "optOutPopover": "Nemáš rád súkromné správy? Klikni sem aby si sa kompletne odhlásil z ich prijímania",
"block": "Zablokovať",
"unblock": "Odblokovať",
"pm-reply": "Odpovedať",
- "inbox": "Inbox",
- "abuseFlag": "Report violation of Community Guidelines",
+ "inbox": "Poštová schránka",
+ "messageRequired": "A message is required.",
+ "toUserIDRequired": "A User ID is required",
+ "gemAmountRequired": "A number of gems is required",
+ "notAuthorizedToSendMessageToThisUser": "Can't send message to this user.",
+ "privateMessageGiftIntro": "Hello <%= receiverName %>, <%= senderName %> has sent you",
+ "privateMessageGiftGemsMessage": "<%= gemAmount %> gems!",
+ "privateMessageGiftSubscriptionMessage": "<%= numberOfMonths %> months of subscription!",
+ "cannotSendGemsToYourself": "Cannot send gems to yourself. Try a subscription instead.",
+ "badAmountOfGemsToSend": "Amount must be within 1 and your current number of gems.",
+ "abuseFlag": "Nahlás porušenie Komunitných Pravidiel",
"abuseFlagModalHeading": "Nahlásiť <%= name %> za priestupok?",
- "abuseFlagModalBody": "Are you sure you want to report this post? You should ONLY report a post that violates the <%= firstLinkStart %>Community Guidelines<%= linkEnd %> and/or <%= secondLinkStart %>Terms of Service<%= linkEnd %>. Inappropriately reporting a post is a violation of the Community Guidelines and may give you an infraction. Appropriate reasons to flag a post include but are not limited to:
swearing, religous oaths
bigotry, slurs
adult topics
violence, including as a joke
spam, nonsensical messages
",
+ "abuseFlagModalBody": "Si si istý, že chceš nahlásiť tento príspevok? Mal by si nahlasovať LEN príspevky porušujúce <%= firstLinkStart %>Komunitné Pravidlá<%= linkEnd %> a/alebo <%= secondLinkStart %>Pravidlá Používania<%= linkEnd %>. Nevhodné nahlasovanie príspevkov je považované za porušivanie Komunitných Pravidiel a bude sa považovať za priestupok z tvojej strany. Vhodné dôvody na označenie príspevku obsahujú ale nie sú limitované nasledovnými:
náboženské prísahy, sľuby
bigotnosť, urážky
témy pre dospelých
násilie, aj keď je myslené ako vtip
spam, nezmyselné správy
",
"abuseFlagModalButton": "Nahlásiť priestupok",
- "abuseReported": "Thank you for reporting this violation. The moderators have been notified.",
- "abuseAlreadyReported": "You have already reported this message.",
- "needsText": "Please type a message.",
- "needsTextPlaceholder": "Type your message here.",
+ "abuseReported": "Ďakujeme ti za nahlásenie porušenia pravidiel. Moderátori už boli upovedomení.",
+ "abuseAlreadyReported": "Túto správu si už nahlásil.",
+ "needsText": "Prosím, napíš správu.",
+ "needsTextPlaceholder": "Napíš sem svoju správu.",
"copyMessageAsToDo": "Kopírovať správu do to-do",
"messageAddedAsToDo": "Správa bola skopírovaná do to-do.",
- "messageWroteIn": "<%= user %> wrote in <%= group %>",
- "msgPreviewHeading": "Message Preview",
- "leaderOnlyChallenges": "Only group leader can create challenges",
+ "messageWroteIn": "<%= user %> napísal v <%= group %>",
+ "msgPreviewHeading": "Prehľad správy",
+ "leaderOnlyChallenges": "Len vodca družiny môže vytvárať výzvy",
"sendGift": "Poslať darček",
"inviteFriends": "Pozvať priateľov",
- "inviteByEmail": "Invite by Email",
- "inviteByEmailExplanation": "If a friend joins Habitica via your email, they'll automatically be invited to your party!",
- "inviteFriendsNow": "Invite Friends Now",
- "inviteFriendsLater": "Invite Friends Later",
- "inviteAlertInfo": "If you have friends already using Habitica, invite them by User ID here.",
- "inviteExistUser": "Invite Existing Users",
- "byColon": "By:",
- "inviteNewUsers": "Invite New Users",
- "sendInvitations": "Send Invitations",
- "invitationsSent": "Invitations sent!",
- "inviteAlertInfo2": "Or share this link (copy/paste):",
+ "inviteByEmail": "Pozvať e-mailom",
+ "inviteByEmailExplanation": "Ak sa tvoj priateľ pripojí k Habitike cez e-mail, automaticky bude pozvaný do tvojej družiny!",
+ "inviteFriendsNow": "Pozvi priateľov teraz",
+ "inviteFriendsLater": "Pozvi priateľov neskôr",
+ "inviteAlertInfo": "Ak máš priateľov, ktorí už používajú Habitiku, pozvi ich pomocou užívateľského ID.",
+ "inviteExistUser": "Pozvi existujúcich užívateľov",
+ "byColon": "Od:",
+ "inviteNewUsers": "Pozvi nových užívateľov",
+ "sendInvitations": "Pošli pozvánky",
+ "invitationsSent": "Pozvánky odoslané!",
+ "inviteAlertInfo2": "Alebo zdieľaj tento odkaz (kopíruj/vlož):",
"sendGiftHeading": "Poslať darček pre <%= name %>",
- "sendGiftGemsBalance": "From <%= number %> Gems",
- "sendGiftCost": "Total: $<%= cost %> USD",
- "sendGiftFromBalance": "From Balance",
- "sendGiftPurchase": "Purchase",
- "sendGiftMessagePlaceholder": "Personal message (optional)",
- "sendGiftSubscription": "<%= months %> Month(s): $<%= price %> USD",
- "battleWithFriends": "Battle Monsters With Friends",
- "startPartyWithFriends": "Start a Party with your friends!",
+ "sendGiftGemsBalance": "Od <%= number %> Drahokamov",
+ "sendGiftCost": "Celkovo: $<%= cost %> USD",
+ "sendGiftFromBalance": "Zo zostatku",
+ "sendGiftPurchase": "Nákup",
+ "sendGiftMessagePlaceholder": "Súkromná správa (dobrovoľné)",
+ "sendGiftSubscription": "<%= months %> mesiac(ov): $<%= price %> USD",
+ "battleWithFriends": "Bojuj s príšerami za pomoci svojich kamarátov",
+ "startPartyWithFriends": "Založ družinu so svojimi kamarátmi!",
"startAParty": "Založiť družinu",
- "addToParty": "Add someone to your party",
- "likePost": "Click if you like this post!",
- "partyExplanation1": "Play Habitica with friends to stay accountable!",
- "partyExplanation2": "Battle monsters and create Challenges!",
- "partyExplanation3": "Invite friends now to earn a Quest Scroll!",
- "wantToStartParty": "Do you want to start a party?",
- "exclusiveQuestScroll": "Inviting a friend to your party will grant you an exclusive Quest Scroll to battle the Basi-List together!",
- "nameYourParty": "Name your new party!",
- "partyEmpty": "You're the only one in your party. Invite your friends!",
- "partyChatEmpty": "Your party chat is empty! Type a message in the box above to start chatting.",
- "guildChatEmpty": "This guild's chat is empty! Type a message in the box above to start chatting.",
- "possessiveParty": "<%= name %>'s Party",
- "requestAcceptGuidelines": "If you would like to post messages in the Tavern or any party or guild chat, please first read our <%= linkStart %>Community Guidelines<%= linkEnd %> and then click the button below to indicate that you accept them.",
+ "addToParty": "Pridaj niekoho do svojej družiny",
+ "likePost": "Klikni, ak máš rád tento príspevok!",
+ "partyExplanation1": "Hraj Habitiku zo svojimi priateľmi a buď zodpovedný!",
+ "partyExplanation2": "Bojuj s príšerami a vytváraj výzvy!",
+ "partyExplanation3": "Pozvi teraz svojich priateľov a získal zvitok s výpravou!",
+ "wantToStartParty": "Chceš založiť družinu?",
+ "exclusiveQuestScroll": "Keď pozveš priateľa do svojej družiny, získaš exkluzívny zvitok s výpravou a môžete spolu začať výpravu Basi-List!",
+ "nameYourParty": "Pomenuj svoju novú družinu!",
+ "partyEmpty": "Si jediný člen svojej družiny. Pozvi priateľov!",
+ "partyChatEmpty": "Chat tvojej družiny je prázdny! Napíš správu do poľa nižšie aby si začal chatovať.",
+ "guildChatEmpty": "Chat tvojho cechu je prázdny! Napíš správu do poľa nižšie aby si začal chatovať.",
+ "possessiveParty": "Družina <%= name %>",
+ "requestAcceptGuidelines": "Ak chceš uverejňovať správy v hostinci alebo družine či chate cechu, prečítaj si najskôr, prosím, <%= linkStart %>Komunitné Pravidlá<%= linkEnd %> a potom klikni na tlačidlo nižšie aby si označil, že ich prijímaš.",
"partyUpName": "Party Up",
"partyOnName": "Party On",
- "partyUpAchievement": "Joined a Party with another person! Have fun battling monsters and supporting each other.",
- "partyOnAchievement": "Joined a Party with at least four people! Enjoy your increased accountability as you unite with your friends to vanquish your foes!"
+ "partyUpAchievement": "Pripojil sa k družine s iným hráčom. Zabávaj sa bojovaním s príšerami a podporovaním sa navzájom.",
+ "partyOnAchievement": "Pripojil sa k družine aspoň so štyrmi hráčmi! Užívaj si svoju zvýšenú zodpovednosť keď sa spájaš s tvojimi priateľmi aby si porazil svojich protivníkov!",
+ "groupIdRequired": "\"groupId\" must be a valid UUID",
+ "groupNotFound": "Group not found.",
+ "groupTypesRequired": "You must supply a valid \"type\" query string.",
+ "questLeaderCannotLeaveGroup": "You cannot leave your party when you have started a quest. Abort the quest first.",
+ "cannotLeaveWhileActiveQuest": "You cannot leave party during an active quest. Please leave the quest first.",
+ "onlyLeaderCanRemoveMember": "Only group leader can remove a member!",
+ "memberCannotRemoveYourself": "You cannot remove yourself!",
+ "groupMemberNotFound": "User not found among group's members",
+ "mustBeGroupMember": "Must be member of the group.",
+ "keepOrRemoveAll": "req.query.keep must be either \"keep-all\" or \"remove-all\"",
+ "keepOrRemove": "req.query.keep must be either \"keep\" or \"remove\"",
+ "canOnlyInviteEmailUuid": "Can only invite using uuids or emails.",
+ "inviteMissingEmail": "Missing email address in invite.",
+ "partyMustbePrivate": "Parties must be private",
+ "userAlreadyInGroup": "User already in that group.",
+ "userAlreadyInvitedToGroup": "User already invited to that group.",
+ "userAlreadyPendingInvitation": "User already pending invitation.",
+ "userAlreadyInAParty": "User already in a party.",
+ "userWithIDNotFound": "User with id \"<%= userId %>\" not found.",
+ "userHasNoLocalRegistration": "User does not have a local registration (username, email, password).",
+ "uuidsMustBeAnArray": "User ID invites must be an array.",
+ "emailsMustBeAnArray": "Email address invites must be an array.",
+ "canOnlyInviteMaxInvites": "You can only invite \"<%= maxInvites %>\" at a time",
+ "onlyCreatorOrAdminCanDeleteChat": "Not authorized to delete this message!"
}
\ No newline at end of file
diff --git a/common/locales/sk/limited.json b/common/locales/sk/limited.json
index ba20d601d5..19fb8f9d59 100644
--- a/common/locales/sk/limited.json
+++ b/common/locales/sk/limited.json
@@ -1,18 +1,18 @@
{
"limitedEdition": "Limitovaná edícia",
- "seasonalEdition": "Seasonal Edition",
+ "seasonalEdition": "Sezónna edícia",
"winterColors": "Zimné farby",
"annoyingFriends": "Otravní kamaráti",
"annoyingFriendsText": "<%= snowballs %>-krát schytal snehovou guľou od člena družiny.",
- "alarmingFriends": "Alarming Friends",
- "alarmingFriendsText": "Got spooked <%= spookDust %> times by party members.",
- "agriculturalFriends": "Agricultural Friends",
- "agriculturalFriendsText": "Got transformed into a flower <%= seeds %> times by party members.",
- "aquaticFriends": "Aquatic Friends",
- "aquaticFriendsText": "Got splashed <%= seafoam %> times by party members.",
+ "alarmingFriends": "Znepokojujúci priatelia",
+ "alarmingFriendsText": "Got spooked <%= spookySparkles %> times by party members.",
+ "agriculturalFriends": "Poľnohospodárski priatelia",
+ "agriculturalFriendsText": "Členovia družiny ťa premenili na kvetinu <%= seeds %> krát.",
+ "aquaticFriends": "Vodní priatelia",
+ "aquaticFriendsText": "Členovia družiny ťa ošpliechali <%= seafoam %> krát.",
"valentineCard": "Valentínka",
"valentineCardExplanation": "For enduring such a saccharine poem, you both receive the \"Adoring Friends\" badge!",
- "valentineCardNotes": "Send a Valentine's Day card to a party member.",
+ "valentineCardNotes": "Pošli Valentínku členovi tvojej družiny.",
"valentine0": "\"Roses are red\n\nMy Dailies are blue\n\nI'm happy that I'm\n\nIn a Party with you!\"",
"valentine1": "\"Roses are red\n\nViolets are nice\n\nLet's get together\n\nAnd fight against Vice!\"",
"valentine2": "\"Roses are red\n\nThis poem style is old\n\nI hope that you like this\n\n'Cause it cost ten Gold.\"",
@@ -21,19 +21,19 @@
"valentineCardAchievementText": "Aww, you and your friend must really care about each other! Sent or received <%= cards %> Valentine's Day cards.",
"polarBear": "Polárny medveď",
"turkey": "Moriak",
- "gildedTurkey": "Gilded Turkey",
+ "gildedTurkey": "Pozlátený moriak",
"polarBearPup": "Polárne medvieďa",
"jackolantern": "Jack-O-Lantern",
"seasonalShop": "Sezónny obchod",
"seasonalShopClosedTitle": "<%= linkStart %>Leslie<%= linkEnd %>",
- "seasonalShopTitle": "<%= linkStart %>Seasonal Sorceress<%= linkEnd %>",
- "seasonalShopClosedText": "The Seasonal Shop is currently closed!! I don't know where the Seasonal Sorceress is now, but I bet she'll be back during the next Grand Gala!",
+ "seasonalShopTitle": "<%= linkStart %>Sezónna kúzelníčka<%= linkEnd %>",
+ "seasonalShopClosedText": "Sezónny obchod je momentálne zatvorený!! Neviem,kde je práve Sezónna kúzelníčka, ale stavím sa, že sa tu objaví počas následujúceho Grand Gala!",
"seasonalShopText": "Welcome to the Seasonal Shop!! We're stocking springtime Seasonal Edition goodies at the moment. Everything here will be available to purchase during the Spring Fling event each year, but we're only open until April 30th, so be sure to stock up now, or you'll have to wait a year to buy these items again!",
"seasonalShopSummerText": "Welcome to the Seasonal Shop!! We're stocking summertime Seasonal Edition goodies at the moment. Everything here will be available to purchase during the Summer Splash event each year, but we're only open until July 31st, so be sure to stock up now, or you'll have to wait a year to buy these items again!",
"seasonalShopFallText": "Welcome to the Seasonal Shop!! We're stocking autumn Seasonal Edition goodies at the moment. Everything here will be available to purchase during the Fall Festival event each year, but we're only open until October 31, so be sure to stock up now, or you'll have to wait a year to buy these items again!",
"seasonalShopWinterText": "Welcome to the Seasonal Shop!! We're stocking winter Seasonal Edition goodies at the moment. Everything here will be available to purchase during the Winter Wonderland event each year, but we're only open until January 31, so be sure to stock up now, or you'll have to wait a year to buy these items again!",
- "seasonalShopFallTextBroken": "Oh.... Welcome to the Seasonal Shop... We're stocking autumn Seasonal Edition goodies, or something... Everything here will be available to purchase during the Fall Festival event each year, but we're only open until October 31... I guess you should to stock up now, or you'll have to wait... and wait... and wait... *sigh*",
- "seasonalShopRebirth": "If you've used the Orb of Rebirth, you can repurchase this equipment in the Rewards Column. Initially, you'll only be able to purchase the items for your current class (Warrior by default), but fear not, the other class-specific items will become available if you switch to that class.",
+ "seasonalShopFallTextBroken": "Oh... Vitaj v Sezónnom obchode... Máme tu jesennú Sezónnu edíciu vecičiek, alebo také niečo... Všetko čo tu vidíš si môžeš kúpiť počas Fall Festivalu každý rok, ale máme otvorené len do 31. Októbra... Myslím, že by si si mal niečo kúpiť teraz, inak budeš musieť čakať... a čakať... a čakať... *povzdych*",
+ "seasonalShopRebirth": "Ak použiješ Orb znovuzrodenia, môžeš si kúpiť výstroj v stĺpci odmien. Spočiatku si budeš môcť kúpiť len predmety k tvojmu súčasnému povolaniu (k bojovníkovi, s ktorým začínaš), ale neboj sa, ostatné špecifické predmety pre povolania budú dostupné, keď zmeníš povolanie.",
"candycaneSet": "Candy Cane (Mage)",
"skiSet": "Ski-sassin (Rogue)",
"snowflakeSet": "Snowflake (Healer)",
@@ -42,11 +42,11 @@
"icicleDrakeSet": "Icicle Drake (Rogue)",
"soothingSkaterSet": "Soothing Skater (Healer)",
"gingerbreadSet": "Gingerbread Warrior (Warrior)",
- "toAndFromCard": "To: <%= toName %>, From: <%= fromName %>",
+ "toAndFromCard": "Pre: <%= toName %>, Od: <%= fromName %>",
"nyeCard": "New Year's Card",
"nyeCardExplanation": "For celebrating the new year together, you both receive the \"Auld Acquaintance\" badge!",
"nyeCardNotes": "Send a New Year's card to a party member.",
- "seasonalItems": "Seasonal Items",
+ "seasonalItems": "Sezónne predmety",
"nyeCardAchievementTitle": "Auld Acquaintance",
"nyeCardAchievementText": "Happy New Year! Sent or received <%= cards %> New Year's cards.",
"nye0": "Happy New Year! May you slay many a bad Habit.",
@@ -71,6 +71,7 @@
"magicianBunnySet": "Magician's Bunny (Mage)",
"comfortingKittySet": "Comforting Kitty (Healer)",
"sneakySqueakerSet": "Sneaky Squeaker (Rogue)",
- "fallEventAvailability": "Available until October 31",
- "winterEventAvailability": "Available until December 31"
+ "fallEventAvailability": "K dispozícii do 31. Októbra",
+ "winterEventAvailability": "K dispozícii do 31. Decembra",
+ "springEventAvailability": "Available until May 31"
}
\ No newline at end of file
diff --git a/common/locales/sk/maintenance.json b/common/locales/sk/maintenance.json
new file mode 100644
index 0000000000..cf554b51b7
--- /dev/null
+++ b/common/locales/sk/maintenance.json
@@ -0,0 +1,34 @@
+{
+ "habiticaBackSoon": "Nerob paniku, Habitica bude čoskoro späť!",
+ "importantMaintenance": "Robíme dôležitú údržbu, ktorá sa bude konať pravdepodobne do <%= localDate %> tvojej časovej zóny.",
+ "maintenance": "Údržba",
+ "maintenanceMoreInfo": "Chceš viac informácií o údržbe? <%= linkStart %>Pozri si našu info stránku<%= linkEnd %>.",
+ "noDamageKeepStreaks": "Neuberie sa ti zo života, ani nestratíš série!",
+ "thanksForPatience": "Vďaka za Vašu trpezlivosť!",
+ "twitterMaintenanceUpdates": "Pre najnovšie informácie sledujte náš Twitter, kde budeme zverejňovať informácie o stave údržby.",
+ "veteranPetAward": "Na konci dostaneš zvieratko veterán!",
+
+ "maintenanceInfoTitle": "Informácie o blížiacej sa údržbe",
+ "maintenanceInfoWhat": "Čo sa to deje?",
+ "maintenanceInfoWhatText": "Habitica nebude 21. mája spustená kvôli údržbe väčšinu dňa. Počas tohto víkendu ti nebude spôsobené žiadne poškodenie ani nebude tvoj účet zranený napriek tomu, že sa nebudeš môcť prihlásiť a skontrolovať svoje denné úlohy včas! Budeme sa veľmi snažiť, aby doba údržby trvala čo najkratšie a budeme zverejňovať pravidelné aktualizácie na našom Twitterovom účte. Aby sme všetkým poďakovali za vašu trpezlivosť, na konci doby údržby všetci dostanú nezvyčajné zvieratko!",
+ "maintenanceInfoWhy": "Prečo sa to robí?",
+ "maintenanceInfoWhyText": "For the past several months, we have been thoroughly revamping Habitica behind-the-scenes. Specifically, we have rewritten the API. While it may not look much different on the surface, it’s a whole new world underneath. This will allow us WAY more flexibility when we want to build features in the future, and lead to improved performance!",
+ "maintenanceInfoTechDetails": "Want more details on the technical side of the process? Visit The Forge, our dev blog.",
+ "maintenanceInfoMore": "Viac Informácií",
+ "maintenanceInfoAccountChanges": "Aké zmeny uvidím na mojom účte po skončení prepracovania?",
+ "maintenanceInfoAccountChangesText": "At first, there won’t be any notable changes aside from performance improvements for features such as Challenges. If you notice any changes that shouldn’t be there, email us at admin@habitica.com and we will investigate them for you!",
+ "maintenanceInfoAddFeatures": "What kind of features will this allow Habitica to add?",
+ "maintenanceInfoAddFeaturesText": "Completing this rewrite will allow us to start building out improved chat and Guilds, plans for organizations and families, and additional productivity features like Monthlies and the ability to record yesterday’s activity! Those are all involved features on their own, so it will take time to build them, but until we were finished with this rewrite, there was no way we could start them.",
+ "maintenanceInfoHowLong": "Ako dlho bude prebiehať údržba?",
+ "maintenanceInfoHowLongText": "We have to migrate tasks and data for all 1.3 million Habitica users -- not an easy task! We anticipate that it will take place between approximately 1pm Pacific Time (8pm UTC) and 10pm Pacific Time (5am UTC). Rest assured that we’re doing everything we can to make it go as quickly as possible! You can follow updates on our Twitter.",
+ "maintenanceInfoStatsAffected": "How will my Dailies, Streaks, Buffs, and Quests be affected?",
+ "maintenanceInfoStatsAffectedText1": "You will NOT take any damage or lose any streaks that weekend, but otherwise, your day will reset normally! Dailies that you checked will become unchecked, buffs will reset, etc. If you are in a Collection Quest, you will still find items. If you are in a Boss Battle, you will still deal damage to the Boss, but the Boss will not deal damage to you. (Even monsters need a break!)",
+ "maintenanceInfoStatsAffectedText2": "After a lot of thought, our team concluded that this was the most fair way to handle the fact that many users will not be able to check off their Dailies normally during the maintenance. We’re sorry for any inconvenience this causes!",
+ "maintenanceInfoSeeTasks": "What if I need to see my task list?",
+ "maintenanceInfoSeeTasksText": "If you know that you will need to see your task list on Saturday to remind yourself what you have to do, we recommend that before the maintenance begins, you take a screenshot of your tasks so that you can use it as a reference.",
+ "maintenanceInfoRarePet": "Aký druh vzácneho zvieratka dostanem?",
+ "maintenanceInfoRarePetText": "To thank you for your patience during the downtime, everyone will get a rare Veteran Pet. If you’ve never received a Veteran Pet before, you will receive a Veteran Wolf. If you already have a Veteran Wolf, you will receive a Veteran Tiger. And if you already have a Veteran Wolf and a Veteran Tiger, you will receive a never-before-seen Veteran pet! After the migration is completed, it may take several hours for your pet to show up, but never fear, everyone will get one.",
+ "maintenanceInfoWho": "Kto pracuje na tomto obrovskom projekte?",
+ "maintenanceInfoWhoText": "We’re glad you asked! It was spearheaded by our amazing contributor paglias, with lots of help from Blade, TheHollidayInn, SabreCat, Victor Pudeyev, TheUnknown, and Alys.",
+ "maintenanceInfoTesting": "The new version was also tirelessly tested by a bunch of our amazing open-source volunteers. Thank you -- we couldn't have done this without you."
+}
diff --git a/common/locales/sk/messages.json b/common/locales/sk/messages.json
index 8c45cf0390..6495f66c47 100644
--- a/common/locales/sk/messages.json
+++ b/common/locales/sk/messages.json
@@ -1,11 +1,11 @@
{
"messageLostItem": "Pokazil sa ti predmet - <%= itemText %>.",
"messageTaskNotFound": "Úloha sa nenašla.",
- "messageDuplicateTaskID": "A task with that ID already exists.",
+ "messageDuplicateTaskID": "Úloha s rovnakým ID už existuje.",
"messageTagNotFound": "Štítok sa nenašiel.",
"messagePetNotFound": ":pet sa nenašlo medzi user.items.pets",
"messageFoodNotFound": ":food sa nenašlo v user.items.food",
- "messageNotAvailable": "This item is not currently available for purchase.",
+ "messageNotAvailable": "Tento predmet si momentálne nemôžeš kúpiť. ",
"messageCannotFeedPet": "Toto zvieratko nemôžeš nakŕmiť.",
"messageAlreadyMount": "Takého tátoša už máš. Skús nakŕmiť iné zvieratko.",
"messageEvolve": "Skrotil si si <%= egg %>, vezmi ho na jazdu!",
@@ -13,44 +13,44 @@
"messageDontEnjoyFood": "<%= egg %> zjedlo <%= foodText %>, ale veľmi mu to nechutí.",
"messageBought": "Kúpené: <%= itemText %>",
"messageEquipped": "<%= itemText %> - vo výbave.",
- "messageUnEquipped": "<%= itemText %> unequipped.",
+ "messageUnEquipped": "<%= itemText %> odložený/á/é.",
"messageMissingEggPotion": "Na túto kombináciu ti chýba buď vajíčko alebo liahoxír",
- "messageInvalidEggPotionCombo": "You can't hatch Quest Pet Eggs with Magic Hatching Potions! Try a different egg.",
+ "messageInvalidEggPotionCombo": "Nemôžeš vyliahnuť vajíčko zvieratka z výpravy s čarovným liahoxírom! Skús iné vajíčko.",
"messageAlreadyPet": "Toto zvieratko už máš. Skús vyliahnuť inú kombináciu!",
"messageHatched": "Z vajíčka sa ti vyliahlo zvieratko! Zájdi do stajne a zober si ho k sebe.",
"messageNotEnoughGold": "Nemáš dosť zlata",
- "messageTwoHandedEquip": "Wielding <%= twoHandedText %> takes two hands, so <%= offHandedText %> has been unequipped.",
- "messageTwoHandedUnequip": "Wielding <%= twoHandedText %> takes two hands, so it was unequipped when you armed yourself with <%= offHandedText %>.",
+ "messageTwoHandedEquip": "Používanie <%= twoHandedText %> si vyžeduje obe ruky, takže <%= offHandedText %> bol odložený.",
+ "messageTwoHandedUnequip": "Používanie <%= twoHandedText %> si vyžaduje obe ruky, takže bol odložený, keď si sa vybavil predmetom <%= offHandedText %>.",
"messageDropFood": "Našiel si: <%= dropText %>! <%= dropNotes %>",
"messageDropEgg": "Našiel si vajíčko: <%= dropText %>! <%= dropNotes %>",
"messageDropPotion": "Našiel si liahoxír: <%= dropText %>! <%= dropNotes %>",
- "messageDropQuest": "You've found a quest!",
- "messageDropMysteryItem": "You open the box and find <%= dropText %>!",
+ "messageDropQuest": "Našiel si výpravu!",
+ "messageDropMysteryItem": "Otvoril si krabicu a našiel si <%= dropText %>!",
"messageFoundQuest": "Našiel si zvitok s popisom výpravy: \"<%= questText %>\"!",
- "messageAlreadyPurchasedGear": "You purchased this gear in the past, but do not currently own it. You can buy it again in the rewards column on the tasks page.",
- "messageAlreadyOwnGear": "You already own this item. Equip it by going to the equipment page.",
- "armoireEquipment": "<%= image %> You found a piece of rare Equipment in the Armoire: <%= dropText %>! Awesome!",
- "armoireFood": "<%= image %> You rummage in the Armoire and find <%= dropArticle %><%= dropText %>. What's that doing in here?",
- "armoireExp": "You wrestle with the Armoire and gain Experience. Take that!",
- "messageInsufficientGems": "Not enough gems!",
- "messageAuthPasswordMustMatch": ":password and :confirmPassword don't match",
- "messageAuthCredentialsRequired": ":username, :email, :password, :confirmPassword required",
- "messageAuthUsernameTaken": "Username already taken",
- "messageAuthEmailTaken": "Email already taken",
- "messageAuthNoUserFound": "No user found.",
- "messageAuthMustBeLoggedIn": "You must be logged in.",
- "messageAuthMustIncludeTokens": "You must include a token and uid (user id) in your request",
- "messageGroupNotFound": "Group not found or you don't have access.",
- "messageGroupAlreadyInParty": "Already in a party, try refreshing.",
- "messageGroupOnlyLeaderCanUpdate": "Only the group leader can update the group!",
- "messageGroupRequiresInvite": "Can't join a group you're not invited to.",
- "messageGroupCannotRemoveSelf": "You cannot remove yourself!",
- "messageGroupChatBlankMessage": "You cannot send a blank message",
- "messageGroupChatLikeOwnMessage": "Can't like your own message. Don't be that person.",
- "messageGroupChatFlagOwnMessage": "Can't report your own message.",
- "messageGroupChatFlagAlreadyReported": "You have already reported this message",
- "messageGroupChatNotFound": "Message not found!",
- "messageGroupChatAdminClearFlagCount": "Only an admin can clear the flag count!",
- "messageUserOperationProtected": "path `<%= operation %>` was not saved, as it's a protected path.",
- "messageUserOperationNotFound": "<%= operation %> operation not found"
+ "messageAlreadyPurchasedGear": "Tento výstroj si si kúpil v minulosti, ale momentálne ho nevlastníš. Môžeš si ho kúpiť znovu v stĺpci s odmenami na stránke s úlohami.",
+ "messageAlreadyOwnGear": "Tento predmet už vlastníš. Vyzbroj sa ním na stránke s výstrojom. ",
+ "armoireEquipment": "<%= image %> Našiel si časť vzácneho výstroja v skrini: <%= dropText %>! Úžasné!",
+ "armoireFood": "<%= image %> Prehrabával si sa v skrini a našiel si <%= dropArticle %><%= dropText %>. Čo to tam robí?",
+ "armoireExp": "Zápasil si so skriňou a získal si skúsenosti. Tu máš!",
+ "messageInsufficientGems": "Nemáš dostatok drahokamov!",
+ "messageAuthPasswordMustMatch": ":heslo a :potvrďHeslo sa nezhodujú",
+ "messageAuthCredentialsRequired": ":používateľskéMeno, :email, :heslo, :potvrďHeslo sú požadované",
+ "messageAuthUsernameTaken": "Používateľské meno je už obsadené",
+ "messageAuthEmailTaken": "Email je už obsadený",
+ "messageAuthNoUserFound": "Používateľ sa nenašiel.",
+ "messageAuthMustBeLoggedIn": "Musíš byť prihlásený/á.",
+ "messageAuthMustIncludeTokens": "Tvoja požiadavka musí obsahovať token a uid (id užívateľa)",
+ "messageGroupNotFound": "Skupina sa nenašla alebo k nej nemáš prístup.",
+ "messageGroupAlreadyInParty": "Už si v družine. Skús obnoviť stránku.",
+ "messageGroupOnlyLeaderCanUpdate": "Len vedúci skupiny môže aktualizovať skupinu!",
+ "messageGroupRequiresInvite": "Nemôžeš sa pridať ku skupine, do ktorej nie si pozvaný.",
+ "messageGroupCannotRemoveSelf": "Nemôžeš odstrániť sám seba!",
+ "messageGroupChatBlankMessage": "Nemôžeš poslať prázdnu správu",
+ "messageGroupChatLikeOwnMessage": "Nemôžeš označiť, že sa ti páči vlastná správa. Nebuď ten druh človeka.",
+ "messageGroupChatFlagOwnMessage": "Nemôžeš nahlásiť tvoju vlastnú správu.",
+ "messageGroupChatFlagAlreadyReported": "Túto správu si už nahlásil",
+ "messageGroupChatNotFound": "Správa sa nenašla!",
+ "messageGroupChatAdminClearFlagCount": "Len správca môže vynulovať počet označení.",
+ "messageUserOperationProtected": "cesta `<%= operation %>` nebola uložená, keďže je to chránená cesta.",
+ "messageUserOperationNotFound": "<%= operation %> operácia sa nenašla"
}
\ No newline at end of file
diff --git a/common/locales/sk/noscript.json b/common/locales/sk/noscript.json
index 6057268ef2..ae59acde7b 100644
--- a/common/locales/sk/noscript.json
+++ b/common/locales/sk/noscript.json
@@ -1,6 +1,6 @@
{
- "jsDisabledHeading": "Alas! Your browser doesn't have JavaScript enabled",
- "jsDisabledHeadingFull": "Alas! Your browser doesn't have JavaScript enabled and without it, Habitica can't work properly",
- "jsDisabledText": "Habitica can't properly display the site without it!",
- "jsDisabledLink": "Please enable JavaScript to continue!"
+ "jsDisabledHeading": "Ajéj! Tvoj prehliadač nemá povolený JavaScript",
+ "jsDisabledHeadingFull": "Ajéj! Tvoj prehliadač nemá povolený JavaScript a bez neho nemôže Habitica fungovať správne",
+ "jsDisabledText": "Habitica sa bez neho nezobrazí správne!",
+ "jsDisabledLink": "Prosím, povoľ JavaScript, aby si mohol pokračovať!"
}
\ No newline at end of file
diff --git a/common/locales/sk/npc.json b/common/locales/sk/npc.json
index 58c6401169..be1eed6245 100644
--- a/common/locales/sk/npc.json
+++ b/common/locales/sk/npc.json
@@ -21,6 +21,26 @@
"ian": "Ian",
"ianText": "Welcome to the Quest Shop! Here you can use Quest Scrolls to battle monsters with your friends. Be sure to check out our fine array of Quest Scrolls for purchase on the right!",
"ianBrokenText": "Welcome to the Quest Shop... Here you can use Quest Scrolls to battle monsters with your friends... Be sure to check out our fine array of Quest Scrolls for purchase on the right...",
+ "missingKeyParam": "\"req.params.key\" is required.",
+ "itemNotFound": "Item \"<%= key %>\" not found.",
+ "cannotBuyItem": "You can't buy this item.",
+ "missingTypeKeyEquip": "\"key\" and \"type\" are required parameters.",
+ "missingPetFoodFeed": "\"pet\" and \"food\" are required parameters.",
+ "invalidPetName": "Invalid pet name supplied.",
+ "missingEggHatchingPotionHatch": "\"egg\" and \"hatchingPotion\" are required parameters.",
+ "invalidTypeEquip": "\"type\" must be one of 'equipped', 'pet', 'mount', 'costume'.",
+ "mustPurchaseToSet": "Must purchase <%= val %> to set it on <%= key %>.",
+ "typeRequired": "Type is required",
+ "keyRequired": "Key is required",
+ "notAccteptedType": "Type must be in [eggs, hatchingPotions, food, quests, gear]",
+ "contentKeyNotFound": "Key not found for Content <%= type %>",
+ "plusOneGem": "+1 Gem",
+ "typeNotSellable": "Type is not sellable. Must be one of the following <%= acceptedTypes %>",
+ "userItemsKeyNotFound": "Key not found for user.items <%= type %>",
+ "pathRequired": "Path string is required",
+ "unlocked": "Items have been unlocked",
+ "alreadyUnlocked": "Full set already unlocked.",
+ "alreadyUnlockedPart": "Full set already partially unlocked.",
"USD": "(USD)",
"newStuff": "Novoty",
"cool": "Upozorni ma neskôr",
@@ -64,6 +84,7 @@
"tourPetsPage": "This is the Stable! After level 3, you can hatch pets using eggs and potions. When you hatch a pet in the Market, it will appear here! Click a pet's image to add it to your avatar. Feed them with the food you find after level 3, and they'll grow into powerful mounts.",
"tourMountsPage": "Once you've fed a pet enough food to turn it into a mount, it will appear here. (Pets, mounts, and food are available after level 3.) Click a mount to saddle up!",
"tourEquipmentPage": "This is where your Equipment is stored! Your Battle Gear affects your stats. If you want to show different Equipment on your avatar without changing your stats, click \"Enable Costume.\"",
+ "equipmentAlreadyOwned": "You already own that piece of equipment",
"tourOkay": "OK!",
"tourAwesome": "Úžasne!",
"tourSplendid": "Skvelé!",
diff --git a/common/locales/sk/pets.json b/common/locales/sk/pets.json
index aec6fd605f..a43eedf660 100644
--- a/common/locales/sk/pets.json
+++ b/common/locales/sk/pets.json
@@ -1,25 +1,26 @@
{
"pets": "Zvieratká",
"petsFound": "nájdených zvieratiek",
- "magicPets": "Magic Potion Pets",
+ "magicPets": "Zvieratká z čarovných elixírov",
"rarePets": "Vzácne zvieratká",
"questPets": "Zvieratká z výprav",
"mounts": "Tátoše",
"mountsTamed": "tátošov skrotených",
"questMounts": "Tátoše z výprav",
- "magicMounts": "Magic Potion Mounts",
+ "magicMounts": "Tátoše z čarovných elixírov",
"rareMounts": "Vzácni tátoši",
"etherealLion": "Éterický lev",
"veteranWolf": "Vlk veterán",
"veteranTiger": "Tiger Veterán",
+ "veteranLion": "Veteran Lion",
"cerberusPup": "Cerberusove šteniatko",
"hydra": "Hydra",
"mantisShrimp": "Garnát",
"mammoth": "Huňatý Mamut",
"orca": "Kosatka",
- "royalPurpleGryphon": "Royal Purple Gryphon",
- "phoenix": "Phoenix",
- "bumblebee": "Bumblebee",
+ "royalPurpleGryphon": "Kráľovský fialový Gryf",
+ "phoenix": "Fénix",
+ "magicalBee": "Čarovná včela",
"rarePetPop1": "Ak chceš vedieť, ako môžeš získať vzácne zvieratko vďaka prispevaniu do Habitica, klikni na zlatú packu.",
"rarePetPop2": "Ako získam toto zvieratko?",
"potion": "<%= potionType %> liahoxír",
@@ -28,60 +29,64 @@
"eggSingular": "vajíčko",
"noEggs": "Nemáš žiadne vajíčka.",
"hatchingPotions": "Liahoxíry",
- "magicHatchingPotions": "Magic Hatching Potions",
+ "magicHatchingPotions": "Čarovný Liahoxír",
"hatchingPotion": "liahoxír",
"noHatchingPotions": "Nemáš žiadne liahoxíry.",
"inventoryText": "Po kliknutí na vajíčko sa na zeleno vysvietia elixír, ktoré naň môžeš použiť. Po kliknutí na vysvietený elixír sa zvieratko vyliahne. Ak nie sú vysvietené žiadne elixíry, klikni na vajíčko, aby si zrušil výber a namiesto toho klikni na elixír, aby si videl na ktoré vajíčko ho môžeš použiť, použiteľné vajíčka sa vysvietia. Nepotrebné predmety môžeš predať kupcovi Alexandrovi.",
"foodText": "jedlo",
"food": "Krmivo a sedlá",
"noFood": "Nemáš žiadne krmivo ani sedlá.",
- "dropsExplanation": "Get these items faster with Gems if you don't want to wait for them to drop when completing a task. Learn more about the drop system.",
- "premiumPotionNoDropExplanation": "Magic Hatching Potions cannot be used on eggs received from Quests. The only way to get Magic Hatching Potions is by buying them below, not from random drops.",
+ "dropsExplanation": "Získaj tieto veci rýchlejšie pomocou drahokamov, ak nechceš čakať pokým ich získaš za plnenie úloh. Zisti viac o systéme odmeňovania.",
+ "premiumPotionNoDropExplanation": "Čarovné liahoxíry nemôžeš použiť na vajíčka, ktoré si dostal z Úloh. Jediný spôsob ako dostať čarovné liahoxíry je kúpiť si ich nižšie. Nepadnú ti náhodne. ",
"beastMasterProgress": "Postup k oceneniu \"Pán šeliem\"",
- "stableBeastMasterProgress": "Beast Master Progress: <%= number %> Pets Found",
+ "stableBeastMasterProgress": "Postup k oceneniu \"Pán šeliem\": <%= number %> nájdených zvieratiek",
"beastAchievement": "Za zozbieranie všetkých zvieratiek si získal odznak \"Pán šeliem\"!",
"beastMasterName": "Pán šeliem",
- "beastMasterText": "Has found all 90 pets (incredibly difficult, congratulate this user!)",
- "beastMasterText2": "and has released their pets a total of <%= count %> times",
- "mountMasterProgress": "Mount Master Progress",
- "stableMountMasterProgress": "Mount Master Progress: <%= number %> Mounts Tamed",
- "mountAchievement": "You have earned the \"Mount Master\" achievement for taming all the mounts!",
- "mountMasterName": "Mount Master",
- "mountMasterText": "Has tamed all 90 mounts (even more difficult, congratulate this user!)",
- "mountMasterText2": "and has released all 90 of their mounts a total of <%= count %> times",
- "beastMountMasterName": "Beast Master and Mount Master",
- "triadBingoName": "Triad Bingo",
- "triadBingoText": "Has found all 90 pets, all 90 mounts, and found all 90 pets AGAIN (HOW DID YOU DO THAT!)",
- "triadBingoText2": "and has released a full stable a total of <%= count %> times",
- "triadBingoAchievement": "You have earned the \"Triad Bingo\" achievement for finding all the pets, taming all the mounts, and finding all the pets again!",
+ "beastMasterText": "Našiel všetkých 90 zvieratiek (šialene náročné, zaslúži si uznanie!)",
+ "beastMasterText2": "a vypustil svoje zvieratká dokopy <%= count %> krát",
+ "mountMasterProgress": "Postup k oceneniu \"Pán zvierat\"",
+ "stableMountMasterProgress": "Postup k oceneniu \"Pán zvierat\": <%= number %> skrotených tátošov",
+ "mountAchievement": "Získal si odznak \"Pán zvierat\" za skrotenie všetkých tátošov!",
+ "mountMasterName": "Pán zvierat",
+ "mountMasterText": "Skrotil všetkých 90 tátošov (ešte šialenejšie, zaslúži si uznanie!)",
+ "mountMasterText2": "a vypustil všetkých 90 tátošov dokopy <%= count %> krát",
+ "beastMountMasterName": "Pán šeliem a Pán zvierat",
+ "triadBingoName": "Triáda bingo",
+ "triadBingoText": "Našiel všetkých 90 zvieratiek, skrotil všetkých 90 tátošov a našiel všetkých 90 zvieratiek ZNOVU (AKO SI TO UROBIL!)",
+ "triadBingoText2": "a vypustil plnú stajňu dokopy <%= count %> krát",
+ "triadBingoAchievement": "Získal si odznak \"Triáda bingo\" za nájdenie všetkých zvieratiek, skrotenie všetkých tátošov a nájdenie všetkých zvieratiek znovu!",
"dropsEnabled": "Padanie predmetov povolené!",
"itemDrop": "Vypadol predmet!",
- "firstDrop": "You've unlocked the Drop System! Now when you complete tasks, you have a small chance of finding an item, including eggs, potions, and food! You just found a <%= eggText %> Egg! <%= eggNotes %>",
- "useGems": "If you've got your eye on a pet, but can't wait any longer for it to drop, use Gems in Inventory > Market to buy one!",
+ "firstDrop": "Odomkol si padanie predmetov! Odteraz, keď dokončíš úlohu, máš malú šancu nájsť predmet vrátane vajíčok, elixírov a jedla! Práve si našiel <%= eggText %> Vajíčko! <%= eggNotes %>",
+ "useGems": "Ak máš zálusk na nejaké zvieratko a nemáš chuť čakať, kým ti padne, v menu Inventár > Trh si ho môžeš kúpiť za drahokamy!",
"hatchAPot": "Chceš vyliahnuť takéto zvieratko: <%= potion %> <%= egg %>?",
- "hatchedPet": "You hatched a <%= potion %> <%= egg %>!",
- "displayNow": "Display Now",
- "displayLater": "Display Later",
- "earnedCompanion": "With all your productivity, you've earned a new companion. Feed it to make it grow!",
+ "hatchedPet": "Vyliahol si <%= potion %> <%= egg %>!",
+ "displayNow": "Ukázať teraz",
+ "displayLater": "Ukázať neskôr",
+ "petNotOwned": "Nevlastníš toto zvieratko.",
+ "earnedCompanion": "Vďaka tvojej produktivite si získal nového spoločníka. Nakŕm ho, aby vyrástol!",
"feedPet": "Chceš nakŕmiť toto zvieratko: <%= name %> týmto: <%= article %><%= text %>?",
"useSaddle": "Chceš osedlať toto zvieratko: <%= pet %>?",
- "raisedPet": "You grew a <%= pet %>!",
- "earnedSteed": "By completing your tasks, you've earned a faithful steed!",
- "rideNow": "Ride Now",
- "rideLater": "Ride Later",
+ "raisedPet": "Vykŕmil si <%= pet %>!",
+ "earnedSteed": "Plnením tvojich úloh si získal verného tátoša!",
+ "rideNow": "Jazdiť teraz",
+ "rideLater": "Jazdiť neskôr",
"petName": "<%= potion %> <%= egg %>",
"mountName": "<%= potion %> <%= mount %>",
"petKeyName": "Kľúč ku klietkam",
- "petKeyPop": "Let your pets roam free, release them to start their own adventure, and give yourself the thrill of Beast Master once more!",
- "petKeyBegin": "Key to the Kennels: Experience <%= title %> Once More!",
- "petKeyInfo": "Miss the thrill of collecting pets? Now you can let them go, and have those drops be meaningful again!",
- "petKeyInfo2": "Use the Key to the Kennels to reset your non-quest collectible pets and/or mounts to zero. (Quest-only and Rare pets and mounts are not affected.)",
- "petKeyInfo3": "There are three Keys to the Kennels: Release Pets Only (4 Gems), Release Mounts Only (4 Gems), or Release Both Pets and Mounts (6 Gems). Using a Key lets you stack the Beast Master and Mount Master achievements. The Triad Bingo achievement will only stack if you use the \"Release Both Pets and Mounts\" key and have collected all 90 pets a second time. Show the world just how much of collection master you are! But choose wisely, because once you use a Key and open the kennel or stable doors, you won't be able to get them back without collecting them all again...",
- "petKeyInfo4": "There are three Keys to the Kennels: Release Pets Only (4 Gems), Release Mounts Only (4 Gems), or Release Both Pets and Mounts. Using a Key lets you stack the Beast Master and Mount Master achievements. The Triad Bingo achievement will only stack if you use the \"Release Both Pets and Mounts\" key and have collected all 90 pets a second time. Show the world just how much of collection master you are! But choose wisely, because once you use a Key and open the kennel or stable doors, you won't be able to get them back without collecting them all again...",
+ "petKeyPop": "Nechaj svoje zvieratká túlať sa svetom, vypusť ich, aby začali svoje vlastné dobrodružstvo a získaj nadšenie z odznaku \"Pána šeliem\" ešte raz!",
+ "petKeyBegin": "Kľúč ku klietkam: Staň sa <%= title %> ešte raz!",
+ "petKeyInfo": "Chýba ti nadšenie zo zbierania zvieratiek? Teraz ich môžeš pustiť na slobodu a padanie predmetov bude mať pre teba znovu zmysel. ",
+ "petKeyInfo2": "Použi kľúč ku klietkam aby si zresetoval počet svojich nevýpravných zvieratiek a/alebo tátošov na nulu. (Vzácne zvieratká a tátoše a tiež tie z questov týmto nebudú ovplyvnené.)",
+ "petKeyInfo3": "Sú tri druhy kľúčov ku klietkam: len na vypustenie zvieratiek (za 4 drahokamy), len na vypustenie tátošov (4 drahokamy), alebo na vypustenie zvieratiek aj tátošov (6 drahokamov). Použitie kľúča pomáha získať odznaky Pán šeliem a Pán zvierat. Odznak Triáda bingo získaš, len ak vypustíš všetky svoje zvieratká a tátoše a znova všetkých 90 získaš po druhý raz. Ukáž celému svetu, aký veľký majster zberania skutočne si! Avšak vyberaj rozvážne, pretože keď raz pužiješ kľúč a otvoríš dvere klietky alebo stajní, nebudeš môcť svoje zvieratká a tátoše získať späť inak ako ich opätovným zozberaním...",
+ "petKeyInfo4": "Sú tri druhy kľúčov ku klietkam: len na vypustenie zvieratiek (za 4 drahokamy), len na vypustenie tátošov (4 drahokamy), alebo na vypustenie zvieratiek aj tátošov. Použitie kľúča pomáha získať odznaky Pán šeliem a Pán zvierat. Odznak Triáda bingo získaš, len ak vypustíš všetky svoje zvieratká a tátoše a znova všetkých 90 získaš po druhý raz. Ukáž celému svetu, aký veľký majster zberania skutočne si! Avšak vyberaj rozvážne, pretože keď raz pužiješ kľúč a otvoríš dvere klietky alebo stajní, nebudeš môcť svoje zvieratká a tátoše získať späť inak ako ich opätovným zozberaním...",
"petKeyPets": "Vypusti moje zvieratká",
"petKeyMounts": "Vypusti mojich tátošov",
"petKeyBoth": "Vypusti oboje",
- "confirmPetKey": "Are you sure?",
+ "confirmPetKey": "Si si istý?",
"petKeyNeverMind": "Ešte nie",
+ "petsReleased": "Zvieratka vypustené.",
+ "mountsAndPetsReleased": "Tátoše a zvieratká vypustené",
+ "mountsReleased": "Tátoše vypustené",
"gemsEach": "drahokamy za každý"
}
\ No newline at end of file
diff --git a/common/locales/sk/quests.json b/common/locales/sk/quests.json
index cdf0494665..13ccb8ab12 100644
--- a/common/locales/sk/quests.json
+++ b/common/locales/sk/quests.json
@@ -12,41 +12,41 @@
"completed": "Dokončná!",
"youReceived": "Získal si",
"dropQuestCongrats": "Gratulujeme ti k získaniu zvitku výpravy! Teraz môžeš pozvať svoju družinu na výpravu alebo sa môžeš kedykoľvek vrátiť cez Inventár -> Výpravy.",
- "questSend": "Clicking \"Invite\" will send an invitation to your party members. When all members have accepted or denied, the quest begins. See status under Social > Party.",
- "questSendBroken": "Clicking \"Invite\" will send an invitation to your party members... When all members have accepted or denied, the quest begins... See status under Social > Party...",
- "inviteParty": "Invite Party to Quest",
+ "questSend": "Kliknutím na \"Pozvať\" pošleš pozvánku členom svojej družiny. Keď všetci členovia prijmú alebo odmietnu, začne sa výprava. Stav výpravy môžeš sledovať v Spoločnosť > Družina.",
+ "questSendBroken": "Kliknutím na \"Pozvať\" pošleš pozvánku členom svojej družiny... Keď všetci členovia prijmú alebo odmietnu, začne sa výprava... Stav výpravy môžeš sledovať v Spoločnosť > Družina.",
+ "inviteParty": "Pozvi družinu na výpravu",
"questInvitation": "Pozvánka na výpravu:",
"questInvitationTitle": "Pozvánka na výpravu",
"questInvitationInfo": "Pozvánka na výpravu <%= quest %>",
"askLater": "Spýtať sa neškôr",
- "questLater": "Quest Later",
+ "questLater": "Vodca výpravy",
"buyQuest": "Kúpiť výpravu",
"accepted": "Prijatá",
"rejected": "Odmietnutá",
"pending": "Čáká sa",
"questStart": "V momente, keď všetci členovia prijmú alebo odmietnu, výprava sa začne. Len tí, ktorí klikli na \"prijať\" sa zúčastnia výpravy a po jej dokončení dostanú odmenu. Ak sa pridlho čaká na niektorých členov družiny (sú neaktívni?), vlastník výpravy môže začať aj bez nich kliknutím na \"začať\". Vlastník výpravy môže tiež zrušiť výpravu klinutím na \"zrušiť\" a tým sa mu vráti zvitok do inventára.",
- "questStartBroken": "Once all members have either accepted or rejected, the quest begins... Only those that clicked \"accept\" will be able to participate in the quest and receive the drops... If members are pending too long (inactive?), the quest owner can start the quest without them by clicking \"Begin\"... The quest owner can also cancel the quest and regain the quest scroll by clicking \"Cancel\"...",
+ "questStartBroken": "V momente, keď všetci členovia prijmú alebo odmietnu, výprava sa začne... Len tí, ktorí klikli na \"prijať\" sa zúčastnia výpravy a po jej dokončení dostanú odmenu... Ak sa pridlho čaká na niektorých členov družiny (sú neaktívni?), vlastník výpravy môže začať aj bez nich kliknutím na \"začať\"... Vlastník výpravy môže tiež zrušiť výpravu kliknutím na \"zrušiť\" a tým sa mu vráti zvitok výpravy do inventára.",
"begin": "Začať",
"bossHP": "Bossove zdravie",
- "bossStrength": "Boss Strength",
- "rage": "Rage",
- "collect": "Collect",
+ "bossStrength": "Sila bossa",
+ "rage": "Zúrivosť",
+ "collect": "Zbierať",
"collected": "Nazbieraných",
"collectionItems": "<%= number %> <%= items %>",
- "itemsToCollect": "Items to Collect",
- "bossDmg1": "Each completed Daily and To-Do and each positive Habit hurts the boss. Hurt it more with redder tasks or Brutal Smash and Burst of Flames. The boss will deal damage to every quest participant for every Daily you've missed (multiplied by the boss's Strength) in addition to your regular damage, so keep your party healthy by completing your Dailies! All damage to and from a boss is tallied on cron (your day roll-over).",
+ "itemsToCollect": "Zozbierateľné predmety",
+ "bossDmg1": "Každá splnená denná úloha a plánovaná úloha a každý pozitívny návyk zraňuje bossa. Zraň ho viac pomocou plnenia červených úloh alebo Brutálnou ranou a Výbuchom ohňa. Boss navyše k tvojmu bežnému poškodeniu zraní každého člena výpravy za každú tvoju nesplnenú dennú úlohu (znásobenú bossovou silou), takže udržuj členov družiny zdravých vďaka plneniu svojich denných úloh! Všetko poškodenie bossovi a od bossa je vyhodnotené na konci (tvojho) dňa",
"bossDmg2": "Len tí, čo sú na výprave môžu bojovať s bossom a podeliť sa o korisť.",
- "bossDmg1Broken": "Each completed Daily and To-Do and each positive Habit hurts the boss... Hurt it more with redder tasks or Brutal Smash and Burst of Flames... The boss will deal damage to every quest participant for every Daily you've missed (multiplied by the boss's Strength) in addition to your regular damage, so keep your party healthy by completing your Dailies... All damage to and from a boss is tallied on cron (your day roll-over)...",
- "bossDmg2Broken": "Only participants will fight the boss and share in the quest loot...",
- "tavernBossInfo": "Complete Dailies and To-Dos and score positive Habits to damage the World Boss! Incomplete Dailies fill the Rage Bar. When the Rage bar is full, the World Boss will attack an NPC. A World Boss will never damage individual players or accounts in any way. Only active accounts not resting in the Inn will have their tasks tallied.",
- "tavernBossInfoBroken": "Complete Dailies and To-Dos and score positive Habits to damage the World Boss... Incomplete Dailies fill the Exhaust Strike Bar... When the Exhaust Strike bar is full, the World Boss will attack an NPC... A World Boss will never damage individual players or accounts in any way... Only active accounts not resting in the Inn will have their tasks tallied...",
+ "bossDmg1Broken": "Každá splnená denná úloha a plánovaná úloha a každý pozitívny návyk zraňuje bossa... Zraň ho viac pomocou plnenia červených úloh alebo Brutálnou ranou a Výbuchom ohňa... Boss navyše k tvojmu bežnému poškodeniu zraní každého člena výpravy za každú tvoju nesplnenú dennú úlohu (znásobenú bossovou silou), takže udržuj členov družiny zdravých vďaka plneniu svojich denných úloh... Všetko poškodenie bossovi a od bossa je vyhodnotené na konci (tvojho) dňa...",
+ "bossDmg2Broken": "Len účastníci budú bojovať s bossom a zdieľať korisť z výpravy...",
+ "tavernBossInfo": "Plň denné úlohy a plánované úlohy rovnako ako aj pozitívne návyky aby si zranil svetového bossa! Nesplnené denné úlohy napĺňajú ukazovateľ hnevu. Keď je ukazovateľ hnevu plný, svetový boss zaútočí na NPC. Svetový boss nikdy nezraní individuálnych hráčov a žiadnym spôsobom nepoškodí ich účty. Iba úlohy aktívnych účtov, ktoré práve neodpočívajú v hostinci, budú započítané.",
+ "tavernBossInfoBroken": "Plň denné úlohy a plánované úlohy rovnako ako aj pozitívne návyky aby si zranil svetového bossa... Nesplnené denné úlohy napĺňajú ukazovateľ omračujúceho útoku... Keď je ukazovateľ hnevu plný, svetový boss zaútočí na NPC... Svetový boss nikdy nezraní individuálnych hráčov a žiadnym spôsobom nepoškodí ich účty... Iba úlohy aktívnych účtov, ktoré práve neodpočívajú v hostinci, budú započítané...",
"bossColl1": "Pre získanie predmetov musíš plniť svoje dobré návyky. Predmety za ukončenie výpravy padajú ako normálne predmety, ale uvidíš ich až nasledujúci deň. Vtedy sa všetko sčíta a pridá na hromadu.",
"bossColl2": "Len tí, čo sú na výprave môžu zbierať predmety a podeliť sa o korisť.",
- "bossColl1Broken": "To collect items, do your positive tasks... Quest items drop just like normal items; however, you won't see the drops until the next day, then everything you've found will be tallied up and contributed to the pile...",
- "bossColl2Broken": "Only participants can collect items and share in the quest loot...",
+ "bossColl1Broken": "Pre získanie predmetov musíš plniť svoje dobré návyky... Predmety za ukončenie výpravy padajú ako normálne predmety; uvidíš ich však až nasledujúci deň, keď sa všetko sčíta a pridá na hromadu.",
+ "bossColl2Broken": "Len účastníci môžu zbierať predmety a zdieľať korisť z výpravy...",
"abort": "Ukončiť",
- "leaveQuest": "Leave Quest",
- "sureLeave": "Are you sure you want to leave the active quest? All your quest progress will be lost.",
+ "leaveQuest": "Opustiť výpravu",
+ "sureLeave": "Si si istý, že chceš opustiť aktívnu výpravu? Celý tvoj postup vo výprave bude stratený.",
"questOwner": "Vlastník výpravy",
"questOwnerNotInPendingQuest": "Vlastník výpravy opustil výpravu a teda ju už nemôže začať. Odporúčame aby si ju teraz ukončil. Vlastník výpravy získa zvitok naspäť.",
"questOwnerNotInRunningQuest": "Vlastník výpavy ju opustil. Môžes ju opustiť aj ty, ak potrebuješ. Ale môžeš aj ďalej pokračovať a všetci účastníci na konci dostanú odmeny za danú výpravu.",
@@ -57,26 +57,45 @@
"noScrolls": "Nemáš žiadne zvitky s mapami výprav.",
"scrollsText1": "Ak chceš ísť na výpravu, potrebuješ družinu. Ak chceš ísť sólo,",
"scrollsText2": "založ si prázdnu družinu",
- "scrollsPre": "You haven't unlocked this quest yet!",
- "alreadyEarnedQuestLevel": "You already earned this quest by attaining Level <%= level %>.",
- "alreadyEarnedQuestReward": "You already earned this quest by completing <%= priorQuest %>.",
+ "scrollsPre": "Túto výpravu si ešte neodomkol!",
+ "alreadyEarnedQuestLevel": "Už si získal túto výpravu dosiahnutím levela <%= level %>.",
+ "alreadyEarnedQuestReward": "Už si získal túto výpravu splnením <%= priorQuest %>.",
"completedQuests": "Dokončil nasledujúce výpravy",
"mustComplete": "Najskôr dokonči túto výpravu: <%= quest %>.",
- "mustLevel": "You must be level <%= level %> to begin this quest.",
+ "mustLevel": "Ak sa chceš začať túto výpravu, musíš mať aspoň level <%= level %> .",
"mustLvlQuest": "Ak si chceš kúpiť tento quest, musíš mať aspoň <%= level %>. level.",
- "mustInviteFriend": "To earn this quest, invite a friend to your Party. Invite someone now?",
- "unlockByQuesting": "To earn this quest, complete <%= title %>.",
+ "mustInviteFriend": "Aby si získal túto výpravu, pozvi priateľa do tvojej družiny. Chceš niekoho pozvať hneď?",
+ "unlockByQuesting": "Aby si získal túto výpravu, dokonči <%= title %>.",
"sureCancel": "Si si istý, že chceš zrušiť túto výpravu? Všetky prijaté pozvania sa stratia. Vlastník výpravy získa naspäť zvitok.",
"sureAbort": "Si si istý, že chceš ukončiť misiu? Zruší sa všetkým v družine a stratíte všetky svoje pokroky. Zvitok s výpravou sa vráti naspäť jeho vlastníkovi.",
"doubleSureAbort": "Si si naozaj istý? Uisti sa, že ťa potom nebudú naveky neznášať!",
"questWarning": "Ak sa nový hráči pridajú do družiny pred začiatkom výpravy, dostanú pozvánku aj oni. Ale ak sa už výprava začala, noví hráči sa nebudú môcť na ňu pridať.",
- "questWarningBroken": "If new players join the party before the quest starts, they will also receive an invitation... However once the quest has started, no new party members can join the quest...",
+ "questWarningBroken": "Ak sa do družiny pridá nový hráč pred začatím výpravy, tiež dostane pozvánku na výpravu... Ale keď už výprava začne, žiadny nový člen družiny sa nemôže pridať k výprave.",
"bossRageTitle": "Zúrivosť",
- "bossRageDescription": "When this bar fills, the boss will unleash a special attack!",
- "startAQuest": "START A QUEST",
- "startQuest": "Start Quest",
- "whichQuestStart": "Which quest do you want to start?",
- "getMoreQuests": "Get more quests",
- "unlockedAQuest": "You unlocked a quest!",
- "leveledUpReceivedQuest": "You leveled up to Level <%= level %> and received a quest scroll!"
+ "bossRageDescription": "Keď sa naplní ukazovateľ, boss rozpúta špeciálny útok!",
+ "startAQuest": "ZAČNI VÝPRAVU",
+ "startQuest": "Začni výpravu",
+ "whichQuestStart": "Ktorú výpravu chceš začať?",
+ "getMoreQuests": "Získaj ďalšie výpravy",
+ "unlockedAQuest": "Odomkol si výpravu!",
+ "leveledUpReceivedQuest": "Získal si level <%= level %> a dostal si zvitok s výpravou!",
+ "questInvitationDoesNotExist": "No quest invitation has been sent out yet.",
+ "questInviteNotFound": "No quest invitation found.",
+ "guildQuestsNotSupported": "Guilds cannot be invited on quests.",
+ "questNotFound": "Quest \"<%= key %>\" not found.",
+ "questNotOwned": "You don't own that quest scroll.",
+ "questNotGoldPurchasable": "Quest \"<%= key %>\" is not a Gold-purchasable quest.",
+ "questLevelTooHigh": "You must be level <%= level %> to begin this quest.",
+ "questAlreadyUnderway": "Your party is already on a quest. Try again when the current quest has ended.",
+ "questAlreadyAccepted": "You already accepted the quest invitation.",
+ "noActiveQuestToLeave": "No active quest to leave",
+ "questLeaderCannotLeaveQuest": "Quest leader cannot leave quest",
+ "notPartOfQuest": "You are not part of the quest",
+ "noActiveQuestToAbort": "There is no active quest to abort.",
+ "onlyLeaderAbortQuest": "Only the group or quest leader can abort a quest.",
+ "questAlreadyRejected": "You already rejected the quest invitation.",
+ "cantCancelActiveQuest": "You can not cancel an active quest, use the abort functionality.",
+ "onlyLeaderCancelQuest": "Only the group or quest leader can cancel the quest.",
+ "questNotPending": "There is no quest to start.",
+ "questOrGroupLeaderOnlyStartQuest": "Only the quest leader or group leader can force start the quest"
}
\ No newline at end of file
diff --git a/common/locales/sk/questscontent.json b/common/locales/sk/questscontent.json
index e2c58f57ba..5a4c9c7a44 100644
--- a/common/locales/sk/questscontent.json
+++ b/common/locales/sk/questscontent.json
@@ -1,72 +1,72 @@
{
"questEvilSantaText": "Lovec Santa",
- "questEvilSantaNotes": "You hear agonized roars deep in the icefields. You follow the growls - punctuated by the sound of cackling - to a clearing in the woods, where you see a fully-grown polar bear. She's caged and shackled, fighting for her life. Dancing atop the cage is a malicious little imp wearing a castaway costume. Vanquish Trapper Santa, and save the beast!",
- "questEvilSantaCompletion": "Trapper Santa squeals in anger, and bounces off into the night. The grateful she-bear, through roars and growls, tries to tell you something. You take her back to the stables, where Matt Boch the Beast Master listens to her tale with a gasp of horror. She has a cub! He ran off into the icefields when mama bear was captured.",
+ "questEvilSantaNotes": "Hlboko v ľadových pláňach počuješ hlasný rev. Nasleduješ vrčanie - prerušované chichotom - na čistinu v lese, kde zbadáš dospelú polárnu medvedicu. Je spútaná v klietke a reve o život. Na klietke tancuje zlomyseľný malý škriatok v odhodenom dotrhanom kostýme. Premôž Lovca Santu a zachráň medvedicu!",
+ "questEvilSantaCompletion": "Lovec Santa rozzúrene zvrieskne a zmizne v diaľke. Vďačná medvedica sa ti revom a vrčaním snaží niečo povedať. Vezmeš ju späť do stajní, kde si šepkár Matt Boch s napätím vypočuje jej príbeh. Ona má medvieďa! Ušlo od nej do ľadových plání, keď jeho mamu medvedicu zajali. Pomôž jej nájsť jej mláďa!",
"questEvilSantaBoss": "Lovec Santa",
"questEvilSantaDropBearCubPolarMount": "Polárny medveď (tátoš)",
"questEvilSanta2Text": "Nájdi mláďa",
- "questEvilSanta2Notes": "When Trapper Santa captured the polar bear mount, her cub ran off into the icefields. You hear twig-snaps and snow crunch through the crystalline sound of the forest. Paw prints! You start racing to follow the trail. Find all the prints and broken twigs, and retrieve the cub!",
- "questEvilSanta2Completion": "You've found the cub! It will keep you company forever.",
+ "questEvilSanta2Notes": "Keď Lovec Santa chytil polárnu medvedicu, jej mláďa ušlo do ľadových plání. Počuješ, ako sa tichým lesom šíri zvuk praskania vetvičiek a vŕzgania snehu. Stopy labiek! Vyštartuješ po ich stopách. Nájdi všetky stopy a zlomené vetvičky, a priveď mláďa!",
+ "questEvilSanta2Completion": "Našiel si mláďa! Bude ťa sprevádzať až do konca dní.",
"questEvilSanta2CollectTracks": "Stopy",
"questEvilSanta2CollectBranches": "Polámané vetvičky",
"questEvilSanta2DropBearCubPolarPet": "Polárny medveď (zvieratko)",
"questGryphonText": "Ohnivý Gryf",
- "questGryphonNotes": "The grand beast master, baconsaur, has come to your party seeking help. \"Please, adventurers, you must help me! My prized gryphon has broken free and is terrorizing Habit City! If you can stop her, I could reward you with some of her eggs!\"",
- "questGryphonCompletion": "Defeated, the mighty beast ashamedly slinks back to its master. \"My word! Well done, adventurers!\" baconsaur exclaims, \"Please, have some of the gryphon's eggs. I am sure you will raise these young ones well!\"",
+ "questGryphonNotes": "Veľký pán šeliem baconsaur prišiel k tvojej družine hľadajúc pomoc. \"Prosím, dobrodruhovia, musíte mi pomôcť!\" Moja drahocenná gryfka ušla a terorizuje mesto Habit! Ak sa vám ju podarí zastaviť, za odmenu vám dám pár jej vajíčok!\"",
+ "questGryphonCompletion": "Porazené, mocné zviera sa zahanbene zakráda naspäť k svojmu pánovi. \"Pánabeka! Dobrá práca, dobrodruhovia!\" baconsaur zvolá: \"Prosím, vezmite si zopár z týchto gryfích vajec. Som si istý, že ich dobre vychováte!\"",
"questGryphonBoss": "Ohnivý Gryf",
"questGryphonDropGryphonEgg": "Gryf (vajce)",
- "questGryphonUnlockText": "Unlocks purchasable gryphon eggs in the Market",
+ "questGryphonUnlockText": "Odomkne predajné gryfie vajíčka na trhu",
"questHedgehogText": "Ježolak",
"questHedgehogNotes": "Ježkovia sú vtipná skupinka zvierat. Sú jednou z najláskyplnejších zvieratiek, ktoré by mohol Habitier vlastniť. Ale povráva sa, že ak ich po polnoci nakŕmiš mliekom, tak ich to dosť rozdráždi. A narastú päťdesiatnásobne. A Inventrix to práve spravil. Ups.",
"questHedgehogCompletion": "Tvoja družina úspešne upokojila ježa! Po scvrknutí sa na bežnú veľkosť pokrivkáva ku svojim vajíčkam. Vracia sa spokojne si ňufkajúc a poštuchávajúc nejaké zo svojich vajíčok vašim smerom. Dúfajme, že títo ježkovia znášajú mlieko lepšie!",
"questHedgehogBoss": "Ježolak",
"questHedgehogDropHedgehogEgg": "Ježko (vajce)",
- "questHedgehogUnlockText": "Unlocks purchasable hedgehog eggs in the Market",
+ "questHedgehogUnlockText": "Odomkne predajné vajíčka ježkov na trhu",
"questGhostStagText": "Duch jari",
"questGhostStagNotes": "Aaa, jar. Čas roka, keď farby opäť začnú vypĺňať krajinu. Chladné hromady snehu sú preč. Kde bola námraza, tam teraz prekypuje rastlinný život. Zvodné zelené listy zapĺňajú koruny stromov, tráve sa navracia jej živý odtieň a na plániach vyrastajú dúhy kvetov a biela tajuplná hmla pokrýva krajinu! ... Počkať. Tajuplná hmla? \"Och nie,\" vraví Inventrix s obavami,, \"zdá sa, že túto hmlu spôsobuje nejaký duch. A zjavne útočí rovno na teba.\"",
"questGhostStagCompletion": "Duch, napohľad nezranený, skláňa svoj nos k zemi. A tvoju družinu obklopí upokojujúci hlas. \"Ospravedlňte moje chovanie. Práve som sa prebral a zdá sa, že sa mi ešte úplne nenavrátil rozum. Prosím, vezmite si tieto vajíčka, ako dôkaz mojej vďaky.\" Na tráve pred duchom sa zhmotní zhluk vajíčok. Duch odbehne bez slov do lesa, pričom v jeho stopách ostávajú kvety.",
"questGhostStagBoss": "Jelení duch",
"questGhostStagDropDeerEgg": "Jeleň (vajce)",
- "questGhostStagUnlockText": "Unlocks purchasable deer eggs in the Market",
+ "questGhostStagUnlockText": "Odomkne predajné jelenie vajíčka na trhu",
"questRatText": "Potkaní kráľ",
- "questRatNotes": "Garbage! Massive piles of unchecked Dailies are lying all across Habitica. The problem has become so serious that hordes of rats are now seen everywhere. You notice @Pandah petting one of the beasts lovingly. She explains that rats are gentle creatures that feed on unchecked Dailies. The real problem is that the Dailies have fallen into the sewer, creating a dangerous pit that must be cleared. As you descend into the sewers, a massive rat, with blood red eyes and mangled yellow teeth, attacks you, defending its horde. Will you cower in fear or face the fabled Rat King?",
- "questRatCompletion": "Your final strike saps the gargantuan rat's strength, his eyes fading to a dull grey. The beast splits into many tiny rats, which scurry off in fright. You notice @Pandah standing behind you, looking at the once mighty creature. She explains that the citizens of Habitica have been inspired by your courage and are quickly completing all their unchecked Dailies. She warns you that we must be vigilant, for should we let down our guard, the Rat King will return. As payment, @Pandah offers you several rat eggs. Noticing your uneasy expression, she smiles, \"They make wonderful pets.\"",
+ "questRatNotes": "Odpad! Masívne kopy neskontrolovaných denných úloh ležia po celej Habitice. Problém začal byť tak vážny, že hordy potkanov sú vidieť už úplne všade. Všimol si si, že @Pandah láskyplne hladká jedného z tvorov. Vysvetľuje, že potkany sú jemné stvorenia, ktoré sa živia neskontrolovanými dennými úlohami. Vážny problém je to, že denné úlohy padajú do kanálov, čím vytvorili nebezpečnú jamu, ktorá musí byť vyčistená. Ako zostupuješ do kanálov, obrovský potkan s krvavo červenými očami a skazenými žltými zubami na teba zaútočí obraňujúc svoju hordu. Ukryješ sa od strachu alebo sa postavíš legendárnemu Potkaniemu kráľovi?",
+ "questRatCompletion": "Tvoj finálny útok podlomil potkaniu silu, jeho oči chradnú do kalenej šedej. Tvor sa roztrhol na milión malých potkanov, ktorí mazali preč v panike. Všimneš si, že @Pandah stojí za tebou a pozerá sa na kedysi mocné stvorenie. Vysvetľuje, že občania Habitici sa inšpirovali tvojou odvahou a rýchlo plnia všetky svoje nesplnené denné úlohy. Varuje ťa, že musíme byť ostražitý, lebo keď opustíme našu stráž, Potkaní kráľ sa vráti. Ako odmenu, @Pandah ti ponúkne pár potkaních vajíčok. Všimnúc si tvoj neľahký výraz sa usmeje: \"Sú z nich úžasné zvieratká.\"",
"questRatBoss": "Potkaní kráľ",
"questRatDropRatEgg": "Potkan (vajce)",
- "questRatUnlockText": "Unlocks purchasable rat eggs in the Market",
+ "questRatUnlockText": "Odomkne predajné potkanie vajíčka na trhu",
"questOctopusText": "Volanie Oktothulu",
- "questOctopusNotes": "@Urse, podivný pisár, požiadal ťa, aby si mu pomohol preskúmať záhadnú jaskyňu pri pobreží. Medzi jazierkami po súmračnom prílive stoji mohutná brána so stalaktitov a stalagmitov. Ako sa blížiš k bráne, začína sa pri jej základe vytvárať vodný vír. S úžasom sleduješ ako z bahna vystupuje chobotnicoidný drak. \"Lepkavý plod hviez sa prebudil,\" kričí šialene @Urse. \"Po vigintilióne rokov je veľký Oktothulu opäť voľný a prahne po potešní!\"",
+ "questOctopusNotes": "@Urse, podivný mladý pisár, ťa požiadal, aby si mu pomohol preskúmať záhadnú jaskyňu pri pobreží. Medzi súmračnými jazierkami stojí mohutná brána zo stalaktitov a stalagmitov. Ako sa blížiš k bráne, v základni sa začína točiť temný vodný vír. S úžasom sleduješ ako z bahna vystupuje chobotnicoidný drak. \"Lepkavý plod hviezd sa prebudil,\" kričí šialene @Urse. \"Po vigintilióne rokov je veľký Oktothulu opäť voľný a prahne po potešení!\"",
"questOctopusCompletion": "With a final blow, the creature slips away into the whirlpool from which it came. You cannot tell if @Urse is happy with your victory or saddened to see the beast go. Wordlessly, your companion points to three slimy, gargantuan eggs in a nearby tidepool, set in a nest of gold coins. \"Probably just octopus eggs,\" you say nervously. As you return home, @Urse frantically scribbles in a journal and you suspect this is not the last time you will hear of the great Octothulu.",
"questOctopusBoss": "Oktothulu",
- "questOctopusDropOctopusEgg": "Oktothulu (vajíčko)",
- "questOctopusUnlockText": "Unlocks purchasable octopus eggs in the Market",
- "questHarpyText": "Help! Harpy!",
+ "questOctopusDropOctopusEgg": "Chobotnica (vajíčko)",
+ "questOctopusUnlockText": "Odomkne predajné vajíčka chobotníc na trhu",
+ "questHarpyText": "Pomóc! Harpya! ",
"questHarpyNotes": "The brave adventurer @UncommonCriminal has disappeared into the forest, following the trail of a winged monster that was sighted several days ago. You are about to begin a search when a wounded parrot lands on your arm, an ugly scar marring its beautiful plumage. Attached to its leg is a scrawled note explaining that while defending the parrots, @UncommonCriminal was captured by a vicious Harpy, and desperately needs your help to escape. Will you follow the bird, defeat the Harpy, and save @UncommonCriminal?",
"questHarpyCompletion": "A final blow to the Harpy brings it down, feathers flying in all directions. After a quick climb to its nest you find @UncommonCriminal, surrounded by parrot eggs. As a team, you quickly place the eggs back in the nearby nests. The scarred parrot who found you caws loudly, dropping several eggs in your arms. \"The Harpy attack has left some eggs in need of protection,\" explains @UncommonCriminal. \"It seems you have been made an honorary parrot.\"",
- "questHarpyBoss": "Harpy",
+ "questHarpyBoss": "Harpya",
"questHarpyDropParrotEgg": "Papagáj (vajce)",
- "questHarpyUnlockText": "Unlocks purchasable parrot eggs in the Market",
- "questRoosterText": "Rooster Rampage",
+ "questHarpyUnlockText": "Odomkne predajné vajíčka papagájov na trhu",
+ "questRoosterText": "Kohútie vystrájanie",
"questRoosterNotes": "For years the farmer @extrajordanary has used Roosters as an alarm clock. But now a giant Rooster has appeared, crowing louder than any before – and waking up everyone in Habitica! The sleep-deprived Habiticans struggle through their daily tasks. @Pandoro decides the time has come to put a stop to this. \"Please, is there anyone who can teach that Rooster to crow quietly?\" You volunteer, approaching the Rooster early one morning – but it turns, flapping its giant wings and showing its sharp claws, and crows a battle cry.",
"questRoosterCompletion": "With finesse and strength, you have tamed the wild beast. Its ears, once filled with feathers and half-remembered tasks, are now clear as day. It crows at you quietly, snuggling its beak into your shoulder. The next day you’re set to take your leave, but @EmeraldOx runs up to you with a covered basket. “Wait! When I went into the farmhouse this morning, the Rooster had pushed these against the door where you slept. I think he wants you to have them.” You uncover the basket to see three delicate eggs.",
"questRoosterBoss": "Kohút",
"questRoosterDropRoosterEgg": "Kohút (vajce)",
- "questRoosterUnlockText": "Unlocks purchasable rooster eggs in the Market",
- "questSpiderText": "The Icy Arachnid",
+ "questRoosterUnlockText": "Odomkne predajné vajíčka kohútov na trhu",
+ "questSpiderText": "Ľadový pavúkovec",
"questSpiderNotes": "As the weather starts cooling down, delicate frost begins appearing on Habiticans' windowpanes in lacy webs... except for @Arcosine, whose windows are frozen completely shut by the Frost Spider currently taking up residence in his home. Oh dear.",
"questSpiderCompletion": "The Frost Spider collapses, leaving behind a small pile of frost and a few of her enchanted egg sacs. @Arcosine rather hurriedly offers them to you as a reward--perhaps you could raise some non-threatening spiders as pets of your own?",
"questSpiderBoss": "Pavúk",
"questSpiderDropSpiderEgg": "Pavúk (vajce)",
- "questSpiderUnlockText": "Unlocks purchasable spider eggs in the Market",
- "questVice1Text": "Vice, Part 1: Free Yourself of the Dragon's Influence",
+ "questSpiderUnlockText": "Odomkne predajné pavúčie vajíčka na trhu",
+ "questVice1Text": "Neresť, časť 1: Osloboď sa z dračieho vplyvu. ",
"questVice1Notes": "
They say there lies a terrible evil in the caverns of Mt. Habitica. A monster whose presence twists the wills of the strong heroes of the land, turning them towards bad habits and laziness! The beast is a grand dragon of immense power and comprised of the shadows themselves: Vice, the treacherous Shadow Wyrm. Brave Habiteers, stand up and defeat this foul beast once and for all, but only if you believe you can stand against its immense power.
Vice Part 1:
How can you expect to fight the beast if it already has control over you? Don't fall victim to laziness and vice! Work hard to fight against the dragon's dark influence and dispel his hold on you!
",
"questVice1Boss": "Tieň neresti",
"questVice1DropVice2Quest": "Neresť, časť 2 (zvitok)",
- "questVice2Text": "Vice, Part 2: Find the Lair of the Wyrm",
+ "questVice2Text": "Neresť, časť 2: Nájdi dračí brloh",
"questVice2Notes": "With Vice's influence over you dispelled, you feel a surge of strength you didn't know you had return to you. Confident in yourselves and your ability to withstand the wyrm's influence, your party makes its way to Mt. Habitica. You approach the entrance to the mountain's caverns and pause. Swells of shadows, almost like fog, wisp out from the opening. It is near impossible to see anything in front of you. The light from your lanterns seem to end abruptly where the shadows begin. It is said that only magical light can pierce the dragon's infernal haze. If you can find enough light crystals, you could make your way to the dragon.",
"questVice2CollectLightCrystal": "Svetelné kryštály",
"questVice2DropVice3Quest": "Neresť, časť 3 (zvitok)",
- "questVice3Text": "Vice, Part 3: Vice Awakens",
+ "questVice3Text": "Neresť, časť 3: Neresť sa prebúdza",
"questVice3Notes": "Po dlhých snahách sa tvojej družine podarilo nájsť brloh Neresti. Mohutné oči monštra si prezerajú tvoju družinu s opovrhnutím. Ako jej tieň zakrúži okolo teba, v hlave ti šepká hlas: \"Ďalší pobláznení občania Habitiky, čo ma prišli zastaviť? Aké milé. Bolo by rozumnejšie nechodiť sem.\" Šupinatý titán cúvne a pripraví sa na útok. Teraz je tvoja šanca! Daj do toho všetko a poraz Neresť raz a navždy!",
"questVice3Completion": "The shadows dissipate from the cavern and a steely silence falls. My word, you've done it! You have defeated Vice! You and your party may finally breath a sigh of relief. Enjoy your victory, brave Habiteers, but take the lessons you've learned from battling Vice and move forward. There are still Habits to be done and potentially worse evils to conquer!",
"questVice3Boss": "Neresť, Tieňová saňa",
@@ -86,21 +86,21 @@
"questMoonstone3Completion": "Your breath comes hard and sweat stings your eyes as the undead Wyrm collapses. The remains of Recidivate dissipate into a thin grey mist that clears quickly under the onslaught of a refreshing breeze, and you hear the distant, rallying cries of Habiticans defeating their Bad Habits for once and for all.
@Baconsaur the beast master swoops down on a gryphon. \"I saw the end of your battle from the sky, and I was greatly moved. Please, take this enchanted tunic – your bravery speaks of a noble heart, and I believe you were meant to have it.\"",
"questMoonstone3Boss": "Necro-Vice",
"questMoonstone3DropRottenMeat": "Zhnité mäso (Jedlo)",
- "questMoonstone3DropZombiePotion": "Zombie Hatching Potion",
+ "questMoonstone3DropZombiePotion": "Zombie liahoxír",
"questGoldenknight1Text": "The Golden Knight, Part 1: A Stern Talking-To",
"questGoldenknight1Notes": "The Golden Knight has been getting on poor Habiticans' cases. Didn't do all of your Dailies? Checked off a negative Habit? She will use this as a reason to harass you about how you should follow her example. She is the shining example of a perfect Habitican, and you are naught but a failure. Well, that is not nice at all! Everyone makes mistakes. They should not have to be met with such negativity for it. Perhaps it is time you gather some testimonies from hurt Habiticans and give the Golden Knight a stern talking-to!",
"questGoldenknight1CollectTestimony": "Testimonies",
"questGoldenknight1DropGoldenknight2Quest": "The Golden Knight Chain Part 2: Gold Knight (Scroll)",
"questGoldenknight2Text": "The Golden Knight, Part 2: Gold Knight",
"questGoldenknight2Notes": "Armed with hundreds of Habitican's testimonies, you finally confront the Golden Knight. You begin to recite the Habitcan's complaints to her, one by one. \"And @Pfeffernusse says that your constant bragging-\" The knight raises her hand to silence you and scoffs, \"Please, these people are merely jealous of my success. Instead of complaining, they should simply work as hard as I! Perhaps I shall show you the power you can attain through diligence such as mine!\" She raises her morningstar and prepares to attack you!",
- "questGoldenknight2Boss": "Gold Knight",
+ "questGoldenknight2Boss": "Zlatá rytierka",
"questGoldenknight2DropGoldenknight3Quest": "The Golden Knight Chain Part 3: The Iron Knight (Scroll)",
"questGoldenknight3Text": "The Golden Knight, Part 3: The Iron Knight",
"questGoldenknight3Notes": "@Jon Arinbjorn cries out to you to get your attention. In the aftermath of your battle, a new figure has appeared. A knight coated in stained-black iron slowly approaches you with sword in hand. The Golden Knight shouts to the figure, \"Father, no!\" but the knight shows no signs of stopping. She turns to you and says, \"I am sorry. I have been a fool, with a head too big to see how cruel I have been. But my father is crueler than I could ever be. If he isn't stopped he'll destroy us all. Here, use my morningstar and halt the Iron Knight!\"",
"questGoldenknight3Completion": "With a satisfying clang, the Iron Knight falls to his knees and slumps over. \"You are quite strong,\" he pants. \"I have been humbled, today.\" The Golden Knight approaches you and says, \"Thank you. I believe we have gained some humility from our encounter with you. I will speak with my father and explain the complaints against us. Perhaps, we should begin apologizing to the other Habiticans.\" She mulls over in thought before turning back to you. \"Here: as our gift to you, I want you to keep my morningstar. It is yours now.\"",
- "questGoldenknight3Boss": "The Iron Knight",
- "questGoldenknight3DropHoney": "Honey (Food)",
- "questGoldenknight3DropGoldenPotion": "Golden Hatching Potion",
+ "questGoldenknight3Boss": "Železný rytier",
+ "questGoldenknight3DropHoney": "Med (Jedlo)",
+ "questGoldenknight3DropGoldenPotion": "Zlatý liahoxír",
"questGoldenknight3DropWeapon": "Mustaine's Milestone Mashing Morning Star (Shield-hand Weapon)",
"questBasilistText": "The Basi-List",
"questBasilistNotes": "There's a commotion in the marketplace--the kind that should make you run away. Being a courageous adventurer, you run towards it instead, and discover a Basi-list, coalescing from a clump of incomplete To-Dos! Nearby Habiticans are paralyzed with fear at the length of the Basi-list, unable to start working. From somewhere in the vicinity, you hear @Arcosine shout: \"Quick! Complete your To-Dos and Dailies to defang the monster, before someone gets a paper cut!\" Strike fast, adventurer, and check something off - but beware! If you leave any Dailies undone, the Basi-list will attack you and your party!",
@@ -125,9 +125,9 @@
"questSeahorseText": "The Dilatory Derby",
"questSeahorseNotes": "It's Derby Day, and Habiticans from all over the continent have traveled to Dilatory to race their pet seahorses! Suddenly, a great splashing and snarling breaks out at the racetrack, and you hear Seahorse Keeper @Kiwibot shouting above the roar of the waves. \"The gathering of seahorses has attracted a fierce Sea Stallion!\" she cries. \"He's smashing through the stables and destroying the ancient track! Can anyone calm him down?\"",
"questSeahorseCompletion": "The now-tame Sea Stallion swims docilely to your side. \"Oh, look!\" Kiwibot says. \"He wants us to take care of his children.\" She gives you three eggs. \"Raise them well,\" she says. \"You're welcome at the Dilatory Derby any day!\"",
- "questSeahorseBoss": "Sea Stallion",
+ "questSeahorseBoss": "Morský žrebec",
"questSeahorseDropSeahorseEgg": "Morský koník (vajce)",
- "questSeahorseUnlockText": "Unlocks purchasable seahorse eggs in the Market",
+ "questSeahorseUnlockText": "Odomkne predajné vajíčka morských koníkov na trhu",
"questAtom1Text": "Attack of the Mundane, Part 1: Dish Disaster!",
"questAtom1Notes": "You reach the shores of Washed-Up Lake for some well-earned relaxation... But the lake is polluted with unwashed dishes! How did this happen? Well, you simply cannot allow the lake to be in this state. There is only one thing you can do: clean the dishes and save your vacation spot! Better find some soap to clean up this mess. A lot of soap...",
"questAtom1CollectSoapBars": "Bars of Soap",
@@ -140,33 +140,33 @@
"questAtom3Notes": "With a deafening cry, and five delicious types of cheese bursting from its mouth, the SnackLess Monster falls to pieces. \"HOW DARE YOU!\" booms a voice from beneath the water's surface. A robed, blue figure emerges from the water, wielding a magic toilet brush. Filthy laundry begins to bubble up to the surface of the lake. \"I am the Laundromancer!\" he angrily announces. \"You have some nerve - washing my delightfully dirty dishes, destroying my pet, and entering my domain with such clean clothes. Prepare to feel the soggy wrath of my anti-laundry magic!\"",
"questAtom3Completion": "The wicked Laundromancer has been defeated! Clean laundry falls in piles all around you. Things are looking much better around here. As you begin to wade through the freshly pressed armor, a glint of metal catches your eye, and your gaze falls upon a gleaming helm. The original owner of this shining item may be unknown, but as you put it on, you feel the warming presence of a generous spirit. Too bad they didn't sew on a nametag.",
"questAtom3Boss": "The Laundromancer",
- "questAtom3DropPotion": "Base Hatching Potion",
- "questOwlText": "The Night-Owl",
+ "questAtom3DropPotion": "Základný liahoxír",
+ "questOwlText": "Nočná sova",
"questOwlNotes": "The Tavern light is lit 'til dawn Until one eve the glow is gone! How can we see for our all-nighters? @Twitching cries, \"I need some fighters! See that Night-Owl, starry foe? Fight with haste and do not slow! We'll drive its shadow from our door, And make the night shine bright once more!\"",
"questOwlCompletion": "The Night-Owl fades before the dawn, But even so, you feel a yawn. Perhaps it's time to get some rest? Then on your bed, you see a nest! A Night-Owl knows it can be great To finish work and stay up late, But your new pets will softly peep To tell you when it's time to sleep.",
- "questOwlBoss": "The Night-Owl",
- "questOwlDropOwlEgg": "Owl (Egg)",
- "questOwlUnlockText": "Unlocks purchasable owl eggs in the Market",
- "questPenguinText": "The Fowl Frost",
+ "questOwlBoss": "Nočná sova",
+ "questOwlDropOwlEgg": "Sova (Vajíčko)",
+ "questOwlUnlockText": "Odomkne predajné sovie vajíčka na trhu",
+ "questPenguinText": "Vtáčí mráz",
"questPenguinNotes": "Although it's a hot summer day in the southernmost tip of Habitica, an unnatural chill has fallen upon Lively Lake. Strong, frigid winds rush around as the shore begins to freeze over. Ice spikes jut up from the ground, pushing grass and dirt away. @Melynnrose and @Breadstrings run up to you.
\"Help!\" says @Melynnrose. \"We brought a giant penguin in to freeze the lake so we could all go ice skating, but we ran out of fish to feed him!\"
\"He got angry and is using his freeze breath on everything he sees!\" says @Breadstrings. \"Please, you have to subdue him before all of us are covered in ice!\" Looks like you need this penguin to... cool down.",
"questPenguinCompletion": "Upon the penguin's defeat, the ice melts away. The giant penguin settles down in the sunshine, slurping up an extra bucket of fish you found. He skates off across the lake, blowing gently downwards to create smooth, sparkling ice. What an odd bird! \"It appears he left behind a few eggs, as well,\" says @Painter de Cluster.
@Rattify laughs. \"Maybe these penguins will be a little more... chill?\"",
- "questPenguinBoss": "Frost Penguin",
- "questPenguinDropPenguinEgg": "Penguin (Egg)",
- "questPenguinUnlockText": "Unlocks purchasable penguin eggs in the Market",
+ "questPenguinBoss": "Zamrznutý tučniak",
+ "questPenguinDropPenguinEgg": "Tučniak (Vajíčko)",
+ "questPenguinUnlockText": "Odomkne predajné vajíčka tučniakov na trhu",
"questStressbeastText": "The Abominable Stressbeast of the Stoïkalm Steppes",
"questStressbeastNotes": "Complete Dailies and To-Dos to damage the World Boss! Incomplete Dailies fill the Stress Strike Bar. When the Stress Strike bar is full, the World Boss will attack an NPC. A World Boss will never damage individual players or accounts in any way. Only active accounts who are not resting in the inn will have their incomplete Dailies tallied.
~*~
The first thing we hear are the footsteps, slower and more thundering than the stampede. One by one, Habiticans look outside their doors, and words fail us.
We've all seen Stressbeasts before, of course - tiny vicious creatures that attack during difficult times. But this? This towers taller than the buildings, with paws that could crush a dragon with ease. Frost swings from its stinking fur, and as it roars, the icy blast rips the roofs off our houses. A monster of this magnitude has never been mentioned outside of distant legend.
\"Beware, Habiticans!\" SabreCat cries. \"Barricade yourselves indoors - this is the Abominable Stressbeast itself!\"
\"That thing must be made of centuries of stress!\" Kiwibot says, locking the Tavern door tightly and shuttering the windows.
\"The Stoïkalm Steppes,\" Lemoness says, face grim. \"All this time, we thought they were placid and untroubled, but they must have been secretly hiding their stress somewhere. Over generations, it grew into this, and now it's broken free and attacked them - and us!\"
There's only one way to drive away a Stressbeast, Abominable or otherwise, and that's to attack it with completed Dailies and To-Dos! Let's all band together and fight off this fearsome foe - but be sure not to slack on your tasks, or our undone Dailies may enrage it so much that it lashes out...",
"questStressbeastBoss": "The Abominable Stressbeast",
"questStressbeastBossRageTitle": "Stress Strike",
"questStressbeastBossRageDescription": "When this gauge fills, the Abominable Stressbeast will unleash its Stress Strike on Habitica!",
- "questStressbeastDropMammothPet": "Mammoth (Pet)",
- "questStressbeastDropMammothMount": "Mammoth (Mount)",
+ "questStressbeastDropMammothPet": "Mamut (Zvieratko)",
+ "questStressbeastDropMammothMount": "Mamut (Tátoš)",
"questStressbeastBossRageStables": "`Abominable Stressbeast uses STRESS STRIKE!`\n\nThe surge of stress heals Abominable Stressbeast!\n\nOh no! Despite our best efforts, we've let some Dailies get away from us, and their dark-red color has infuriated the Abominable Stressbeast and caused it to regain some of its health! The horrible creature lunges for the Stables, but Matt the Beast Master heroically leaps into the fray to protect the pets and mounts. The Stressbeast has seized Matt in its vicious grip, but at least it's distracted for the moment. Hurry! Let's keep our Dailies in check and defeat this monster before it attacks again!",
"questStressbeastBossRageBailey": "`Abominable Stressbeast uses STRESS STRIKE!`\n\nThe surge of stress heals Abominable Stressbeast!\n\nAhh!!! Our incomplete Dailies caused the Abominable Stressbeast to become madder than ever and regain some of its health! Bailey the Town Crier was shouting for citizens to get to safety, and now it has seized her in its other hand! Look at her, valiantly reporting on the news as the Stressbeast swings her around viciously... Let's be worthy of her bravery by being as productive as we can to save our NPCs!",
"questStressbeastBossRageGuide": "`Abominable Stressbeast uses STRESS STRIKE!`\n\nThe surge of stress heals Abominable Stressbeast!\n\nLook out! Justin the Guide is trying to distract the Stressbeast by running around its ankles, yelling productivity tips! The Abominable Stressbeast is stomping madly, but it seems like we're really wearing this beast down. I doubt it has enough energy for another strike. Don't give up... we're so close to finishing it off!",
"questStressbeastDesperation": "`Abominable Stressbeast reaches 500K health! Abominable Stressbeast uses Desperate Defense!`\n\nWe're almost there, Habiticans! With diligence and Dailies, we've whittled the Stressbeast's health down to only 500K! The creature roars and flails in desperation, rage building faster than ever. Bailey and Matt yell in terror as it begins to swing them around at a terrifying pace, raising a blinding snowstorm that makes it harder to hit.\n\nWe'll have to redouble our efforts, but take heart - this is a sign that the Stressbeast knows it is about to be defeated. Don't give up now!",
"questStressbeastCompletion": "The Abominable Stressbeast is DEFEATED!
We've done it! With a final bellow, the Abominable Stressbeast dissipates into a cloud of snow. The flakes twinkle down through the air as cheering Habiticans embrace their pets and mounts. Our animals and our NPCs are safe once more!
Stoïkalm is Saved!
SabreCat speaks gently to a small sabertooth. \"Please find the citizens of the Stoïkalm Steppes and bring them to us,\" he says. Several hours later, the sabertooth returns, with a herd of mammoth riders following slowly behind. You recognize the head rider as Lady Glaciate, the leader of Stoïkalm.
\"Mighty Habiticans,\" she says, \"My citizens and I owe you the deepest thanks, and the deepest apologies. In an effort to protect our Steppes from turmoil, we began to secretly banish all of our stress into the icy mountains. We had no idea that it would build up over generations into the Stressbeast that you saw! When it broke loose, it trapped all of us in the mountains in its stead and went on a rampage against our beloved animals.\" Her sad gaze follows the falling snow. \"We put everyone at risk with our foolishness. Rest assured that in the future, we will come to you with our problems before our problems come to you.\"
She turns to where @Baconsaur is snuggling with some of the baby mammoths. \"We have brought your animals an offering of food to apologize for frightening them, and as a symbol of trust, we will leave some of our pets and mounts with you. We know that you will all take care good care of them.\"",
"questStressbeastCompletionChat": "`The Abominable Stressbeast is DEFEATED!`\n\nWe've done it! With a final bellow, the Abominable Stressbeast dissipates into a cloud of snow. The flakes twinkle down through the air as cheering Habiticans embrace their pets and mounts. Our animals and our NPCs are safe once more!\n\n`Stoïkalm is Saved!`\n\nSabreCat speaks gently to a small sabertooth. \"Please find the citizens of the Stoïkalm Steppes and bring them to us,\" he says. Several hours later, the sabertooth returns, with a herd of mammoth riders following slowly behind. You recognize the head rider as Lady Glaciate, the leader of Stoïkalm.\n\n\"Mighty Habiticans,\" she says, \"My citizens and I owe you the deepest thanks, and the deepest apologies. In an effort to protect our Steppes from turmoil, we began to secretly banish all of our stress into the icy mountains. We had no idea that it would build up over generations into the Stressbeast that you saw! When it broke loose, it trapped all of us in the mountains in its stead and went on a rampage against our beloved animals.\" Her sad gaze follows the falling snow. \"We put everyone at risk with our foolishness. Rest assured that in the future, we will come to you with our problems before our problems come to you.\"\n\nShe turns to where @Baconsaur is snuggling with some of the baby mammoths. \"We have brought your animals an offering of food to apologize for frightening them, and as a symbol of trust, we will leave some of our pets and mounts with you. We know that you will all take care good care of them.\"",
- "questTRexText": "King of the Dinosaurs",
+ "questTRexText": "Kráľ dinosaurov",
"questTRexNotes": "Now that ancient creatures from the Stoïkalm Steppes are roaming throughout all of Habitica, @Urse has decided to adopt a full-grown Tyrannosaur. What could go wrong?
Everything.",
"questTRexCompletion": "The wild dinosaur finally stops its rampage and settles down to make friends with the giant roosters. @Urse beams down at it. \"They're not such terrible pets, after all! They just need a little discipline. Here, take some Tyrannosaur eggs for yourself.\"",
"questTRexBoss": "Flesh Tyrannosaur",
@@ -177,19 +177,19 @@
"questTRexUndeadRageTitle": "Skeleton Healing",
"questTRexUndeadRageDescription": "This bar fills when you don't complete your Dailies. When it is full, the Skeletal Tyrannosaur will heal 30% of its remaining health!",
"questTRexUndeadRageEffect": "`Skeletal Tyrannosaur uses SKELETON HEALING!`\n\nThe monster lets forth an unearthly roar, and some of its damaged bones knit back together!",
- "questTRexDropTRexEgg": "Tyrannosaur (Egg)",
+ "questTRexDropTRexEgg": "Tyrannosaur (Vajičko)",
"questTRexUnlockText": "Unlocks purchasable tyrannosaur eggs in the Market",
- "questRockText": "Escape the Cave Creature",
+ "questRockText": "Uteč jaskynnej príšere ",
"questRockNotes": "Crossing Habitica's Meandering Mountains with some friends, you make camp one night in a beautiful cave laced with shining minerals. But when you wake up the next morning, the entrance has disappeared, and the floor of the cave is shifting underneath you.
\"The mountain's alive!\" shouts your companion @pfeffernusse. \"These aren't crystals - these are teeth!\"
@Painter de Cluster grabs your hand. \"We'll have to find another way out - stay with me and don't get distracted, or we could be trapped in here forever!\"",
"questRockBoss": "Crystal Colossus",
"questRockCompletion": "Your diligence has allowed you to find a safe path through the living mountain. Standing in the sunshine, your friend @intune notices something glinting on the ground by the cave's exit. You stoop to pick it up, and see that it's a small rock with a vein of gold running through it. Beside it are a number of other rocks with rather peculiar shapes. They almost look like... eggs?",
- "questRockDropRockEgg": "Rock (Egg)",
- "questRockUnlockText": "Unlocks purchasable rock eggs in the Market",
- "questBunnyText": "The Killer Bunny",
+ "questRockDropRockEgg": "Kameň (Vajíčko)",
+ "questRockUnlockText": "Odomkne predajné vajíčka kameňov na trhu",
+ "questBunnyText": "Zabijak králik",
"questBunnyNotes": "After many difficult days, you reach the peak of Mount Procrastination and stand before the imposing doors of the Fortress of Neglect. You read the inscription in the stone. \"Inside resides the creature that embodies your greatest fears, the reason for your inaction. Knock and face your demon!\" You tremble, imagining the horror within and feel the urge to flee as you have done so many times before. @Draayder holds you back. \"Steady, my friend! The time has come at last. You must do this!\"
You knock and the doors swing inward. From within the gloom you hear a deafening roar, and you draw your weapon.",
- "questBunnyBoss": "Killer Bunny",
+ "questBunnyBoss": "Zabijak králik",
"questBunnyCompletion": "With one final blow the killer rabbit sinks to the ground. A sparkly mist rises from her body as she shrinks down into a tiny bunny... nothing like the cruel beast you faced a moment before. Her nose twitches adorably and she hops away, leaving some eggs behind. @Gully laughs. \"Mount Procrastination has a way of making even the smallest challenges seem insurmountable. Let's gather these eggs and head for home.\"",
- "questBunnyDropBunnyEgg": "Bunny (Egg)",
+ "questBunnyDropBunnyEgg": "Králik (Vajíčko)",
"questBunnyUnlockText": "Unlocks purchasable bunny eggs in the Market",
"questSlimeText": "The Jelly Regent",
"questSlimeNotes": "As you work on your tasks, you notice you are moving slower and slower. \"It's like walking through molasses,\" @Leephon grumbles. \"No, like walking through jelly!\" @starsystemic says. \"That slimy Jelly Regent has slathered his stuff all over Habitica. It's gumming up the works. Everybody is slowing down.\" You look around. The streets are slowly filling with clear, colorful ooze, and Habiticans are struggling to get anything done. As others flee the area, you grab a mop and prepare for battle!",
@@ -201,7 +201,7 @@
"questSheepNotes": "As you wander the rural Taskan countryside with friends, taking a \"quick break\" from your obligations, you find a cozy yarn shop. You are so absorbed in your procrastination that you hardly notice the ominous clouds creep over the horizon. \"I've got a ba-a-a-ad feeling about this weather,\" mutters @Misceo, and you look up. The stormy clouds are swirling together, and they look a lot like a... \"We don't have time for cloud-gazing!\" @starsystemic shouts. \"It's attacking!\" The Thunder Ram hurtles forward, slinging bolts of lightning right at you!",
"questSheepBoss": "Thunder Ram",
"questSheepCompletion": "Impressed by your diligence, the Thunder Ram is drained of its fury. It launches three huge hailstones in your direction, and then fades away with a low rumble. Upon closer inspection, you discover that the hailstones are actually three fluffy eggs. You gather them up, and then stroll home under a blue sky.",
- "questSheepDropSheepEgg": "Sheep (Egg)",
+ "questSheepDropSheepEgg": "Ovca (Vajíčko)",
"questSheepUnlockText": "Unlocks purchasable sheep eggs in the Market",
"questKrakenText": "The Kraken of Inkomplete",
"questKrakenNotes": "It's a warm, sunny day as you sail across the Inkomplete Bay, but your thoughts are clouded with worries about everything that you still need to do. It seems that as soon as you finish one task, another crops up, and then another...
Suddenly, the boat gives a horrible jolt, and slimy tentacles burst out of the water on all sides! \"We're being attacked by the Kraken of Inkomplete!\" Wolvenhalo cries.
\"Quickly!\" Lemoness calls to you. \"Strike down as many tentacles and tasks as you can, before new ones can rise up to take their place!\"",
@@ -213,12 +213,12 @@
"questWhaleNotes": "You arrive at the Diligent Docks, hoping to take a submarine to watch the Dilatory Derby. Suddenly, a deafening bellow forces you to stop and cover your ears. \"Thar she blows!\" cries Captain @krazjega, pointing to a huge, wailing whale. \"It's not safe to send out the submarines while she's thrashing around!\"
\"Quick,\" calls @UncommonCriminal. \"Help me calm the poor creature so we can figure out why she's making all this noise!\"",
"questWhaleBoss": "Wailing Whale",
"questWhaleCompletion": "After much hard work, the whale finally ceases her thunderous cry. \"Looks like she was drowning in waves of negative habits,\" @zoebeagle explains. \"Thanks to your consistent effort, we were able to turn the tides!\" As you step into the submarine, several whale eggs bob towards you, and you scoop them up.",
- "questWhaleDropWhaleEgg": "Whale (Egg)",
- "questWhaleUnlockText": "Unlocks purchasable whale eggs in the Market",
+ "questWhaleDropWhaleEgg": "Veľryba (vajíčko)",
+ "questWhaleUnlockText": "Odomkne predajné vajíčka veľrýb na trhu",
"questDilatoryDistress1Text": "Dilatory Distress, Part 1: Message in a Bottle",
"questDilatoryDistress1Notes": "A message in a bottle arrived from the newly rebuilt city of Dilatory! It reads: \"Dear Habiticans, we need your help once again. Our princess has disappeared and the city is under siege by some unknown watery demons! The mantis shrimps are holding the attackers at bay. Please aid us!\" To make the long journey to the sunken city, one must be able to breathe water. Fortunately, the alchemists @Benga and @hazel can make it all possible! You only have to find the proper ingredients.",
"questDilatoryDistress1Completion": "You don the the finned armor and swim to Dilatory as quickly as you can. The merfolk and their mantis shrimp allies have managed to keep the monsters outside the city for the moment, but they are losing. No sooner are you within the castle walls than the horrifying siege descends!",
- "questDilatoryDistress1CollectFireCoral": "Fire Coral",
+ "questDilatoryDistress1CollectFireCoral": "Ohnivý korál",
"questDilatoryDistress1CollectBlueFins": "Blue Fins",
"questDilatoryDistress1DropArmor": "Finned Oceanic Armor (Armor)",
"questDilatoryDistress2Text": "Dilatory Distress, Part 2: Creatures of the Crevasse",
@@ -228,14 +228,14 @@
"questDilatoryDistress2RageTitle": "Swarm Respawn",
"questDilatoryDistress2RageDescription": "Swarm Respawn: This bar fills when you don't complete your Dailies. When it is full, the Water Skull Swarm will heal 30% of its remaining health!",
"questDilatoryDistress2RageEffect": "`Water Skull Swarm uses SWARM RESPAWN!`\n\nEmboldened by their victories, more skulls pour forth from the crevasse, bolstering the swarm!",
- "questDilatoryDistress2DropSkeletonPotion": "Skeleton Hatching Potion",
- "questDilatoryDistress2DropCottonCandyBluePotion": "Cotton Candy Blue Hatching Potion",
+ "questDilatoryDistress2DropSkeletonPotion": "Kostlivý liahoxír",
+ "questDilatoryDistress2DropCottonCandyBluePotion": "Cukrovo modrý liahoxír",
"questDilatoryDistress2DropHeadgear": "Fire Coral Circlet (Headgear)",
"questDilatoryDistress3Text": "Dilatory Distress, Part 3: Not a Mere Maid",
"questDilatoryDistress3Notes": "You follow the mantis shrimps deep into the Crevasse, and discover an underwater fortress. Princess Adva, escorted by more watery skulls, awaits you inside the main hall. \"My father has sent you, has he not? Tell him I refuse to return. I am content to stay here and practice my sorcery. Leave now, or you shall feel the wrath of the ocean's new queen!\" Adva seems very adamant, but as she speaks you notice a strange, ruby pendant on her neck glowing ominously... Perhaps her delusions would cease should you break it?",
"questDilatoryDistress3Completion": "Finally, you manage to pull the bewitched pendant from Adva's neck and throw it away. Adva clutches her head. \"Where am I? What happened here?\" After hearing your story, she frowns. \"This necklace was given to me by a strange ambassador - a lady called 'Tzina'. I don't remember anything after that!\"
Back at Dilatory, Manta is overjoyed by your success. \"Allow me to reward you with this trident and shield! I ordered them from @aiseant and @starsystemic as a gift for Adva, but... I'd rather not put weapons in her hands any time soon.\"",
"questDilatoryDistress3Boss": "Adva, the Usurping Mermaid",
- "questDilatoryDistress3DropFish": "Fish (Food)",
+ "questDilatoryDistress3DropFish": "Ryba (Jedlo)",
"questDilatoryDistress3DropWeapon": "Trident of Crashing Tides (Weapon)",
"questDilatoryDistress3DropShield": "Moonpearl Shield (Shield-Hand Item)",
"questCheetahText": "Such a Cheetah",
@@ -248,8 +248,8 @@
"questHorseNotes": "While relaxing in the Tavern with @beffymaroo and @JessicaChase, the talk turns to good-natured boasting about your adventuring accomplishments. Proud of your deeds, and perhaps getting a bit carried away, you brag that you can tame any task around. A nearby stranger turns toward you and smiles. One eye twinkles as he invites you to prove your claim by riding his horse.\nAs you all head for the stables, @UncommonCriminal whispers, \"You may have bitten off more than you can chew. That's no horse - that's a Night-Mare!\" Looking at its stamping hooves, you begin to regret your words...",
"questHorseCompletion": "It takes all your skill, but finally the horse stamps a couple of hooves and nuzzles you in the shoulder before allowing you to mount. You ride briefly but proudly around the Tavern grounds while your friends cheer. The stranger breaks into a broad grin.\n\"I can see that was no idle boast! Your determination is truly impressive. Take these eggs to raise horses of your own, and perhaps we'll meet again one day.\" You take the eggs, the stranger tips his hat... and vanishes.",
"questHorseBoss": "Night-Mare",
- "questHorseDropHorseEgg": "Horse (Egg)",
- "questHorseUnlockText": "Unlocks purchasable Horse eggs in the Market",
+ "questHorseDropHorseEgg": "Kôň (Vajíčko)",
+ "questHorseUnlockText": "Odomkne predajné vajíčka koní na trhu",
"questBurnoutText": "Burnout and the Exhaust Spirits",
"questBurnoutNotes": "It is well past midnight, still and stiflingly hot, when Redphoenix and scout captain Kiwibot abruptly burst through the city gates. \"We need to evacuate all the wooden buildings!\" Redphoenix shouts. \"Hurry!\"
Kiwibot grips the wall as she catches her breath. \"It's draining people and turning them into Exhaust Spirits! That's why everything was delayed. That's where the missing people have gone. It's been stealing their energy!\"
\"'It'?'\" asks Lemoness.
And then the heat takes form.
It rises from the earth in a billowing, twisting mass, and the air chokes with the scent of smoke and sulphur. Flames lick across the molten ground and contort into limbs, writhing to horrific heights. Smoldering eyes snap open, and the creature lets out a deep and crackling cackle.
Kiwibot whispers a single word.
\"Burnout.\"",
"questBurnoutCompletion": "Burnout is DEFEATED!
With a great, soft sigh, Burnout slowly releases the ardent energy that was fueling its fire. As the monster curls quietly into ashes, its stolen energy shimmers through the air, rejuvenating the Exhaust Spirits and returning them to their true forms.
Ian, Daniel, and the Seasonal Sorceress cheer as Habiticans rush to greet them, and all the missing citizens of the Flourishing Fields embrace their friends and families. The final Exhaust Spirit transforms into the Joyful Reaper herself!
\"Look!\" whispers @Baconsaur, as the ashes begin to glitter. Slowly, they resolve into hundreds of shining phoenixes!
One of the glowing birds alights on the Joyful Reaper's skeletal arm, and she grins at it. \"It has been a long time since I've had the exquisite privilege to behold a phoenix in the Flourishing Fields,\" she says. \"Although given recent occurrences, I must say, this is highly thematically appropriate!\"
Her tone sobers, although (naturally) her grin remains. \"We're known for being hard-working here, but we are also known for our feasts and festivities. Rather ironic, I suppose, that as we strove to plan a spectacular party, we refused to permit ourselves any time for fun. We certainly won't make the same mistake twice!\"
She claps her hands. \"Now - let's celebrate!\"",
@@ -266,20 +266,20 @@
"questFrogNotes": "As you and your friends are slogging through the Swamps of Stagnation, @starsystemic points at a large sign. \"Stay on the path -- if you can.\"
\"Surely that isn't hard!\" @RosemonkeyCT says. \"It's broad and clear.\"
But as you continue, you notice that path is gradually overtaken by the muck of the swamp, laced with bits of strange blue debris and clutter, until it's impossible to proceed.
As you look around, wondering how it got this messy, @Jon Arjinborn shouts, \"Look out!\" An angry frog leaps from the sludge, clad in dirty laundry and lit by blue fire. You will have to overcome this poisonous Clutter Frog to progress!",
"questFrogCompletion": "The frog cowers back into the muck, defeated. As it slinks away, the blue slime fades, leaving the way ahead clear.
Sitting in the middle of the path are three pristine eggs. \"You can even see the tiny tadpoles and through the clear casing!\" @Breadstrings says. \"Here, you should take them.\"",
"questFrogBoss": "Clutter Frog",
- "questFrogDropFrogEgg": "Frog (Egg)",
- "questFrogUnlockText": "Unlocks purchasable Frog eggs in the Market",
+ "questFrogDropFrogEgg": "Žaba (Vajíčko)",
+ "questFrogUnlockText": "Odomkne predajné žabie vajíčka na trhu",
"questSnakeText": "The Serpent of Distraction",
"questSnakeNotes": "It takes a hardy soul to live in the Sand Dunes of Distraction. The arid desert is hardly a productive place, and the shimmering dunes have led many a traveler astray. However, something has even the locals spooked. The sands have been shifting and upturning entire villages. Residents claim a monster with an enormous serpentine body lies in wait under the sands, and they have all pooled together a reward for whomever will help them find and stop it. The much-lauded snake charmers @EmeraldOx and @PainterProphet have agreed to help you summon the beast. Can you stop the Serpent of Distraction?",
"questSnakeCompletion": "With assistance from the charmers, you banish the Serpent of Distraction. Though you were happy to help the inhabitants of the Dunes, you can't help but feel a little sad for your fallen foe. While you contemplate the sights, @LordDarkly approaches you. \"Thank you! It's not much, but I hope this can express our gratitude properly.\" He hands you some Gold and... some Snake eggs! You will see that majestic animal again after all.",
"questSnakeBoss": "Serpent of Distraction",
- "questSnakeDropSnakeEgg": "Snake (Egg)",
- "questSnakeUnlockText": "Unlocks purchasable Snake eggs in the Market",
- "questUnicornText": "Convincing the Unicorn Queen",
+ "questSnakeDropSnakeEgg": "Had (Vajíčko)",
+ "questSnakeUnlockText": "Odomkne predajné hadie vajíčka na trhu",
+ "questUnicornText": "Presvedčivá kráľovná jednorožcov",
"questUnicornNotes": "Conquest Creek has become muddied, destroying Habit City's fresh water supply! Luckily, @Lukreja knows an old legend that claims that a unicorn's horn can purify the foulest of waters. Together with your intrepid guide @UncommonCriminal, you hike through the frozen peaks of the Meandering Mountains. Finally, at the icy summit of Mount Habitica itself, you find the Unicorn Queen amid the glittering snows. \"Your pleas are compelling,\" she tells you. \"But first you must prove that you are worthy of my aid!\"",
"questUnicornCompletion": "Impressed by your diligence and strength, the Unicorn Queen at last agrees that your cause is worthy. She allows you to ride on her back as she soars to the source of Conquest Creek. As she lowers her golden horn to the befouled waters, a brilliant blue light rises from the water’s surface. It is so blinding that you are forced to close your eyes. When you open them a moment later, the unicorn is gone. However, @rosiesully lets out a cry of delight: the water is now clear, and three shining eggs rest at the creek’s edge.",
- "questUnicornBoss": "The Unicorn Queen",
- "questUnicornDropUnicornEgg": "Unicorn (Egg)",
- "questUnicornUnlockText": "Unlocks purchasable Unicorn eggs in the Market",
+ "questUnicornBoss": "Kráľovná jednorožcov",
+ "questUnicornDropUnicornEgg": "Jednorožec (Vajíčko)",
+ "questUnicornUnlockText": "Odomkne predajné vajíčka jednorožcov na trhu",
"questSabretoothText": "The Sabre Cat",
"questSabretoothNotes": "A roaring monster is terrorizing Habitica! The creature stalks through the wilds and woods, then bursts forth to attack before vanishing again. It's been hunting innocent pandas and frightening the flying pigs into fleeing their pens to roost in the trees. @Inventrix and @icefelis explain that the Zombie Sabre Cat was set free while they were excavating in the ancient, untouched ice-fields of the Stoikalm Steppes. \"It was perfectly friendly at first – I don't know what happened. Please, you have to help us recapture it! Only a champion of Habitica can subdue this prehistoric beast!\"",
"questSabretoothCompletion": "After a long and tiring battle, you wrestle the Zombie Sabre Cat to the ground. As you are finally able to approach, you notice a nasty cavity in one of its sabre teeth. Realising the true cause of the cat's wrath, you're able to get the cavity filled by @Fandekasp, and advise everyone to avoid feeding their friend sweets in future. The Sabre Cat flourishes, and in gratitude, its tamers send you a generous reward – a clutch of sabretooth eggs!",
@@ -290,23 +290,35 @@
"questMonkeyNotes": "The Sloensteadi Savannah is being torn apart by the Monstrous Mandrill and his Mischief Monkeys! They shriek loudly enough to drown out the sound of approaching deadlines, encouraging everyone to avoid their duties and keep monkeying around. Alas, plenty of people ape this bad behavior. If no one stops these primates, soon everyone's tasks will be as red as the Monstrous Mandrill's face!
\"It will take a dedicated adventurer to resist them,\" says @yamato.
\"Quick, let's get this monkey off everyone's backs!\" @Oneironaut yells, and you charge into battle.",
"questMonkeyCompletion": "You did it! No bananas for those fiends today. Overwhelmed by your diligence, the monkeys flee in panic. \"Look,\" says @Misceo. \"They left a few eggs behind.\"
@Leephon grins. \"Maybe a well-trained pet monkey can help you as much as the wild ones hinder you!\"",
"questMonkeyBoss": "Monstrous Mandrill",
- "questMonkeyDropMonkeyEgg": "Monkey (Egg)",
- "questMonkeyUnlockText": "Unlocks purchasable Monkey eggs in the Market",
+ "questMonkeyDropMonkeyEgg": "Opica (Vajíčko)",
+ "questMonkeyUnlockText": "Odomkne predajné vajíčka opíc na trhu",
"questSnailText": "The Snail of Drudgery Sludge",
"questSnailNotes": "You're excited to begin questing in the abandoned Dungeons of Drudgery, but as soon as you enter, you feel the ground under your feet start to suck at your boots. You look up to the path ahead and see Habiticans mired in slime. @Overomega yells, \"They have too many unimportant tasks and dailies, and they're getting stuck on things that don't matter! Pull them out!\"
\"You need to find the source of the ooze,\" @Pfeffernusse agrees, \"or the tasks that they cannot accomplish will drag them down forever!\"
Pulling out your weapon, you wade through the gooey mud.... and encounter the fearsome Snail of Drudgery Sludge.",
"questSnailCompletion": "You bring your weapon down on the great Snail's shell, cracking it in two, releasing a flood of water. The slime is washed away, and the Habiticans around you rejoice. \"Look!\" says @Misceo. \"There's a small group of snail eggs in the remnants of the muck.\"",
"questSnailBoss": "Snail of Drudgery Sludge",
- "questSnailDropSnailEgg": "Snail (Egg)",
- "questSnailUnlockText": "Unlocks purchasable Snail eggs in the Market",
+ "questSnailDropSnailEgg": "Slimák (Vajíčko)",
+ "questSnailUnlockText": "Odomkne predajné vajíčka slimákov na trhu",
"questBewilderText": "The Be-Wilder",
"questBewilderNotes": "The party begins like any other.
The appetizers are excellent, the music is swinging, and even the dancing elephants have become routine. Habiticans laugh and frolic amid the overflowing floral centerpieces, happy to have a distraction from their least-favorite tasks, and the April Fool whirls among them, eagerly providing an amusing trick here and a witty twist there.
As the Mistiflying clock tower strikes midnight, the April Fool leaps onto the stage to give a speech.
“Friends! Enemies! Tolerant acquaintances! Lend me your ears.” The crowd chuckles as animal ears sprout from their heads, and they pose with their new accessories.
“As you know,” the Fool continues, “my confusing illusions usually only last a single day. But I’m pleased to announce that I’ve discovered a shortcut that will guarantee us non-stop fun, without having to deal with the pesky weight of our responsibilities. Charming Habiticans, meet my magical new friend... the Be-Wilder!”
Lemoness pales suddenly, dropping her hors d'oeuvres. “Wait! Don’t trust--”
But suddenly mists are pouring into the room, glittering and thick, and they swirl around the April Fool, coalescing into cloudy feathers and a stretching neck. The crowd is speechless as an monstrous bird unfolds before them, its wings shimmering with illusions. It lets out a horrible screeching laugh.
“Oh, it has been ages since a Habitican has been foolish enough to summon me! How wonderful it feels, to have a tangible form at last.”
Buzzing in terror, the magic bees of Mistiflying flee the floating city, which sags from the sky. One by one, the brilliant spring flowers wither up and wisp away.
“My dearest friends, why so alarmed?” crows the Be-Wilder, beating its wings. “There’s no need to toil for your rewards any more. I’ll just give you all the things that you desire!”
A rain of coins pours from the sky, hammering into the ground with brutal force, and the crowd screams and flees for cover. “Is this a joke?” Baconsaur shouts, as the gold smashes through windows and shatters roof shingles.
PainterProphet ducks as lightning bolts crackle overhead, and fog blots out the sun. “No! This time, I don’t think it is!”
Quickly, Habiticans, don’t let this World Boss distract us from our goals! Stay focused on the tasks that you need to complete so we can rescue Mistiflying -- and hopefully, ourselves.",
"questBewilderCompletion": "The Be-Wilder is DEFEATED!
We've done it! The Be-Wilder lets out a ululating cry as it twists in the air, shedding feathers like falling rain. Slowly, gradually, it coils into a cloud of sparkling mist. As the newly-revealed sun pierces the fog, it burns away, revealing the coughing, mercifully human forms of Bailey, Matt, Alex.... and the April Fool himself.
Mistiflying is saved!
The April Fool has enough shame to look a bit sheepish. “Oh, hm,” he says. “Perhaps I got a little…. carried away.”
The crowd mutters. Sodden flowers wash up on sidewalks. Somewhere in the distance, a roof collapses with a spectacular splash.
“Er, yes,” the April Fool says. “That is. What I meant to say was, I’m dreadfully sorry.” He heaves a sigh. “I suppose it can’t all be fun and games, after all. It might not hurt to focus occasionally. Maybe I’ll get a head start on next year’s pranking.”
Redphoenix coughs meaningfully.
“I mean, get a head start on this year’s spring cleaning!” the April Fool says. “Nothing to fear, I’ll have Habit City in spit-shape soon. Luckily nobody is better than I at dual-wielding mops.”
Encouraged, the marching band starts up.
It isn’t long before all is back to normal in Habit City. Plus, now that the Be-Wilder has evaporated, the magical bees of Mistiflying bustle back to work, and soon the flowers are blooming and the city is floating once more.
As Habiticans cuddle the magical fuzzy bees, the April Fool’s eyes light up. “Oho, I’ve had a thought! Why don’t you all keep some of these fuzzy Bee Pets and Mounts? It’s a gift that perfectly symbolizes the balance between hard work and sweet rewards, if I’m going to get all boring and allegorical on you.” He winks. “Besides, they don’t have stingers! Fool’s honor.”",
"questBewilderCompletionChat": "`The Be-Wilder is DEFEATED!`\n\nWe've done it! The Be-Wilder lets out a ululating cry as it twists in the air, shedding feathers like falling rain. Slowly, gradually, it coils into a cloud of sparkling mist. As the newly-revealed sun pierces the fog, it burns away, revealing the coughing, mercifully human forms of Bailey, Matt, Alex.... and the April Fool himself.\n\n`Mistiflying is saved!`\n\nThe April Fool has enough shame to look a bit sheepish. “Oh, hm,” he says. “Perhaps I got a little…. carried away.”\n\nThe crowd mutters. Sodden flowers wash up on sidewalks. Somewhere in the distance, a roof collapses with a spectacular splash.\n\n“Er, yes,” the April Fool says. “That is. What I meant to say was, I’m dreadfully sorry.” He heaves a sigh. “I suppose it can’t all be fun and games, after all. It might not hurt to focus occasionally. Maybe I’ll get a head start on next year’s pranking.”\n\nRedphoenix coughs meaningfully.\n\n“I mean, get a head start on this year’s spring cleaning!” the April Fool says. “Nothing to fear, I’ll have Habit City in spit-shape soon. Luckily nobody is better than I at dual-wielding mops.”\n\nEncouraged, the marching band starts up.\n\nIt isn’t long before all is back to normal in Habit City. Plus, now that the Be-Wilder has evaporated, the magical bees of Mistiflying bustle back to work, and soon the flowers are blooming and the city is floating once more.\n\nAs Habiticans cuddle the magical fuzzy bees, the April Fool’s eyes light up. “Oho, I’ve had a thought! Why don’t you all keep some of these fuzzy Bee Pets and Mounts? It’s a gift that perfectly symbolizes the balance between hard work and sweet rewards, if I’m going to get all boring and allegorical on you.” He winks. “Besides, they don’t have stingers! Fool’s honor.”",
"questBewilderBossRageTitle": "Beguilement Strike",
"questBewilderBossRageDescription": "When this gauge fills, The Be-Wilder will unleash its Beguilement Strike on Habitica!",
- "questBewilderDropBumblebeePet": "Bumblebee (Pet)",
- "questBewilderDropBumblebeeMount": "Bumblebee (Mount)",
+ "questBewilderDropBumblebeePet": "Magical Bee (Pet)",
+ "questBewilderDropBumblebeeMount": "Magical Bee (Mount)",
"questBewilderBossRageMarket": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nOh no! Despite our best efforts, we've gotten distracted by the Be-Wilder’s charming illusions and have forgotten to do some of our Dailies! With a cackling cry, the shining bird beats its wings, raising a swarm of mist around Alex the Merchant. When the fog clears, he has been possessed! “Have some free samples!” he shouts gleefully, and begins to hurl exploding eggs and potions at fleeing Habiticans. Not the most favorable of sales, to be sure.\n\nHurry! Let's stay focused on our Dailies to defeat this monster before it possesses someone else.",
"questBewilderBossRageStables": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nAhh!!! Once again the Be-Wilder has dazzled us into neglecting our Dailies, and now it has attacked Matt the Beast Master! With a swirl of mist, Matt transforms into a terrifying winged creature, and all the pets and mounts howl sadly in their stables. Quickly, stay focused on your tasks to defeat this dastardly distraction!",
- "questBewilderBossRageBailey": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nLook out! In the middle of reporting the news, Bailey the Town Crier has been possessed by the Be-Wilder! She lets out an evil, uninformative screech as she rises into the air. Now how will we know what’s going on?\n\nDon't give up... we're so close to defeating this bothersome bird for once and for all!"
+ "questBewilderBossRageBailey": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nLook out! In the middle of reporting the news, Bailey the Town Crier has been possessed by the Be-Wilder! She lets out an evil, uninformative screech as she rises into the air. Now how will we know what’s going on?\n\nDon't give up... we're so close to defeating this bothersome bird for once and for all!",
+ "questFalconText": "The Birds of Preycrastination",
+ "questFalconNotes": "Mt. Habitica is being overshadowed by a looming mountain of To-Dos. It used to be a place to picnic and enjoy a sense of accomplishment, until the neglected tasks grew out of control. Now it's home to fearsome Birds of Preycrastination, foul creatures which stop Habiticans from completing their tasks!
\"It's too hard!\" they caw at @JonArinbjorn and @Onheiron. \"It'll take too long to do right now! It won't make any difference if you wait until tomorrow! Why don't you do something fun instead?\"
No more, you vow. You will climb your personal mountain of To-Dos and defeat the Birds of Preycrastination!",
+ "questFalconCompletion": "Having finally triumphed over the Birds of Preycrastination, you settle down to enjoy the view and your well-earned rest.
\"Wow!\" says @Trogdorina. \"You won!\"
@Squish adds, \"Here, take these eggs I found as a reward.\"",
+ "questFalconBoss": "Birds of Preycrastination",
+ "questFalconDropFalconEgg": "Sokol (Vajíčko)",
+ "questFalconUnlockText": "Odomkne predajné sokolie vajíčka na trhu",
+ "questTreelingText": "The Tangle Tree",
+ "questTreelingNotes": "It's the annual Garden Competition, and everyone is talking about the mysterious project which @aurakami has promised to unveil. You join the crowd on the day of the big announcement, and marvel at the introduction of a moving tree. @fuzzytrees explains that the tree will help with garden maintenance, showing how it can mow the lawn, trim the hedge and prune the roses all at the same time – until the tree suddenly goes wild, turning its secateurs on its creator! The crowd panics as everyone tries to flee, but you aren't afraid – you leap forward, ready to do battle.",
+ "questTreelingCompletion": "You dust yourself off as the last few leaves drift to the floor. In spite of the upset, the Garden Competition is now safe – although the tree you just reduced to a heap of wood chips won't be winning any prizes! \"Still a few kinks to work out there,\" @PainterProphet says. \"Perhaps someone else would do a better job of training the saplings. Do you fancy a go?\"",
+ "questTreelingBoss": "Tangle Tree",
+ "questTreelingDropTreelingEgg": "Treeling (Egg)",
+ "questTreelingUnlockText": "Unlocks purchasable Treeling eggs in the Market"
}
\ No newline at end of file
diff --git a/common/locales/sk/rebirth.json b/common/locales/sk/rebirth.json
index 1636ea5dc5..2030547460 100644
--- a/common/locales/sk/rebirth.json
+++ b/common/locales/sk/rebirth.json
@@ -2,27 +2,28 @@
"rebirthNew": "Znovuzrodenie: Čas na nové dobrodružstvo!",
"rebirthUnlock": "Odomkol si znovuzrodenie! Toto je špeciálna položka na trhu, ktorá ti umožňuje začať hru od 1. levelu, pričom ti zachová úlohy, odznaky, zvieratká, a ďalšie. Použi ho, ak máš pocit, že si už všetko dosiahol, a chceš do Habitica vdýchnuť nový život, alebo chceš zažiť nové funkcie očami nováčika.",
"rebirthBegin": "Znovuzrodenie: Začni nové dobrodružstvo",
- "rebirthStartOver": "Rebirth starts your character over from Level 1.",
+ "rebirthStartOver": "Znovuzrodením začneš znova hrať s postavou od 1. levelu.",
"rebirthAdvList1": "Vráti sa ti plné zdravie.",
- "rebirthAdvList2": "You have no Experience, Gold, or Equipment (with the exception of free items like Mystery items).",
- "rebirthAdvList3": "Tvoje návyky, denné úlohy a úlohy zmenia farbu na žltú a série sa vynulujú.",
+ "rebirthAdvList2": "Nemáš žiadne skúsenosti, zlato či výstroj (s výnimkou vecí, ktoré sú zdarma, ako sú napríklad tajomné predmety)",
+ "rebirthAdvList3": "Your Habits, Dailies, and To-Dos reset to yellow, and streaks reset, except for challenge tasks.",
"rebirthAdvList4": "Až kým nezískaš iné povolanie, budeš bojovník.",
"rebirthInherit": "Tvoja nová postava získa zopár vecí od svojho predchodcu:",
"rebirthInList1": "Úlohy, história a nastavenia sa nezmenia.",
"rebirthInList2": "Členstvo vo výzvách, cechoch, a družine ostáva.",
"rebirthInList3": "Drahokamy, úroveň podporovateľa na Kickstarteri a prispievateľské levely ostávajú.",
"rebirthInList4": "Predmety, ktoré si si kúpil za drahokamy alebo ti padli (ako napríklad zvieratká a tátoši) ostávajú, ale nebudeš ich mať dostupné, kým ich neodomkneš.",
- "rebirthInList5": "Limited edition equipment you've purchased can be repurchased, even if its event has ended. To repurchase class-specific equipment, you must first change to the correct class.",
+ "rebirthInList5": "Výstroj z limitovanej edície, ktorý si si kúpil, môže byť znova zakúpený, dokonca aj po skončení akcie. Pre opätovné zakúpenie špecifického výstroja pre dané povolanie si musíš najskôr zmeniť svoje povolanie.",
"rebirthEarnAchievement": "Za začatie nového dobrodružstva získavaš aj špeciálny odznak!",
"beReborn": "Znovu sa zrodiť",
"rebirthAchievement": "Začal si nové dobrodružstvo! Toto je tvoje znovuzrodenie čislo <%= number %> a najvušší level, ktorý si dosiahol je <%= level %>. Ak chceš tento odznak vylepšiť, začni nové dobrodružstvo po dosiahnutí ešte vyššieho levelu!",
- "rebirthAchievement100": "You've begun a new adventure! This is Rebirth <%= number %> for you, and the highest Level you've attained is 100 or higher. To stack this Achievement, begin your next new adventure when you've reached at least 100!",
+ "rebirthAchievement100": "Začal si nové dobrodružstvo! Toto je tvoje znovuzrodenie číslo <%= number %> a najvyšší level, ktorý si získal je 100 alebo vyšší. Ak chceš tento odznak vylepšiť, začni nové dobrodružstvo po dosiahnutí aspoň levelu 100!",
"rebirthBegan": "Začal nové dobrodružstvo",
"rebirthText": "Začal niekoľko nových dobrodružstiev: <%= rebirths %>",
"rebirthOrb": "Použil Orb znovuzrodenia, aby začal odznovu, po dosiahnutí levelu",
- "rebirthOrb100": "Used an Orb of Rebirth to start over after attaining Level 100 or higher",
+ "rebirthOrb100": "Použil Orb znovuzrodenia, aby začal odznovu, po dosiahnutí levelu 100 alebo vyššieho",
"rebirthPop": "Začni s novou postavou na leveli 1, pričom ti ostanú odznaky, zberateľské predmety, a úlohy.",
"rebirthName": "Orb znovuzrodenia",
"reborn": "Znovuzrodený, najvyšší dosiahnutý level: <%= reLevel %>",
- "confirmReborn": "Are you sure?"
+ "confirmReborn": "Si si istý?",
+ "rebirthComplete": "Bol si znovuzrodený!"
}
\ No newline at end of file
diff --git a/common/locales/sk/settings.json b/common/locales/sk/settings.json
index 5f25c5ba46..2436f3bd65 100644
--- a/common/locales/sk/settings.json
+++ b/common/locales/sk/settings.json
@@ -15,11 +15,11 @@
"startCollapsedPop": "Keď je zvolená táto možnosť, keď začneš upravovať úlohu, zoznam štítkov bude skrytý.",
"startAdvCollapsed": "Zrolované pokročilé možnosti v úlohách",
"startAdvCollapsedPop": "S nastavením tejto možnosti budú pokročilé možnosti v momente, keď prvý krát otvoríš úlohu na úpravy, skryté.",
- "dontShowAgain": "Don't show this again",
- "suppressLevelUpModal": "Don't show popup when gaining a level",
- "suppressHatchPetModal": "Don't show popup when hatching a pet",
- "suppressRaisePetModal": "Don't show popup when raising a pet into a mount",
- "suppressStreakModal": "Don't show popup when attaining a Streak achievement",
+ "dontShowAgain": "Už mi to nezobrazuj",
+ "suppressLevelUpModal": "Nezobrazuj popup, keď získam level",
+ "suppressHatchPetModal": "Nezobrazuj popup, keď vyliahnem zvieratko",
+ "suppressRaisePetModal": "Nezobrazuj popup, keď zo zvieratka vyrastie tátoš",
+ "suppressStreakModal": "Nezobrazuj popup, keď získam odznak za sériu",
"showTour": "Zobraziť prehliadku",
"restartTour": "Reštartovať úvodnú prehliadku prvej návštevy Habitica.",
"showBailey": "Zobraziť Bailey",
@@ -47,26 +47,38 @@
"customDayStart": "Vlastný začiatok dňa",
"changeCustomDayStart": "Change Custom Day Start?",
"sureChangeCustomDayStart": "Are you sure you want to change your custom day start?",
+ "customDayStartHasChanged": "Your custom day start has changed.",
"nextCron": "Your Dailies will next reset the first time you use Habitica after <%= time %>. Make sure you have completed your Dailies before this time!",
"customDayStartInfo1": "Habitica defaults to check and reset your Dailies at midnight in your own time zone each day. You can customize that time here.",
"misc": "Rôzne",
"showHeader": "Zobraziť záhlavie",
"changePass": "Zmeniť heslo",
"changeUsername": "Zmeniť používateľské meno",
- "changeEmail": "Change Email Address",
- "newEmail": "New Email Address",
+ "changeEmail": "Zmeniť Emailovú adresu",
+ "newEmail": "Nová Emailová adresa",
"oldPass": "Staré heslo",
"newPass": "Nové heslo",
"confirmPass": "Zopakuj nové heslo",
"newUsername": "Nové používateľské meno",
"dangerZone": "Nebezpečná zóna",
"resetText1": "POZOR! Toto vynuluje veľa častí tvojho konta. Táto možnosť sa neodporúča, ale niektorí ľudia to považujú za užitočné po krátkom hraní sa so stránkou.",
- "resetText2": "Stratíš svoju úroveň, zlato a body skúseností. Všetky tvoje úlohy sa nenávratne zmažú a prídeš aj o všetky historícké dáta. Prídeš o všetok výstroj, ale budeš si ho môcť kúpiť naspäť vrátane všetkých limitovaných alebo odberateľským záhadných predmetov, ktoré si už predtým vlastnil (budeš musieť ma správne povolanie, aby si mohol kúpiť výstroj špecifickú pre povolanie). Ostane ti súčasné povolanie a zvieratká a tátoše. Možno budeš viac preferovať Orb znovuzrodenia, ktorý je bezpečnejšou možnosťou a ktorý ti zachová úlohy.",
+ "resetText2": "You will lose all your levels, gold, and experience points. All your tasks (except those from challenges) will be deleted permanently and you will lose all of their historical data. You will lose all your equipment but you will be able to buy it all back, including all limited edition equipment or subscriber Mystery items that you already own (you will need to be in the correct class to re-buy class-specific gear). You will keep your current class and your pets and mounts. You might prefer to use an Orb of Rebirth instead, which is a much safer option and which will preserve your tasks.",
"deleteText": "Si si istý? Toto navždy zmaže tvoje konto a už sa nebude dať obnoviť! Ak budeš chcieť opäť používať Habitica, budeš sa musieť opäť registrovať s novým kontom. Odložené alebo použité drahokamy sa ti nepreplatia. Ak si si absolútne istý, napíš do textového poľa <%= deleteWord %>.",
"API": "API",
+ "APIv3": "API v3",
+ "APIv2": "API v2 - Zastarané",
"APIText": "Na použitie aplikácií tretích strán použi tento kľúč. API token považuj za niečo ako heslo, nezdieľaj ho verejne. Občas ťa môže niekto požiadať o User ID, ale nikomu nedávaj API Token, ani na Github.",
"APIToken": "API Token (toto je heslo - prečítaj si varovanie vyššie!)",
+ "thirdPartyApps": "Third Party Apps",
+ "dataToolDesc": "A webpage that shows you certain information from your Habitica account, such as statistics about your tasks, equipment, and skills.",
+ "beeminder": "Beeminder",
+ "beeminderDesc": "Let Beeminder automatically monitor your Habitica To-Dos. You can commit to maintaining a target number of To-Dos completed per day or per week, or you can commit to gradually reducing your remaining number of uncompleted To-Dos. (By \"commit\" Beeminder means under threat of paying actual money! But you may also just like Beeminder's fancy graphs.)",
+ "chromeChatExtension": "Chrome Chat Extension",
+ "chromeChatExtensionDesc": "The Chrome Chat Extension for Habitica adds an intuitive chat box to all of habitica.com. It allows users to chat in the Tavern, their party, and any guilds they are in.",
+ "otherExtensions": "Other Extensions",
+ "otherDesc": "Find other apps, extensions, and tools on the Habitica wiki.",
"resetDo": "Sprav to, zresetuj mi konto!",
+ "resetComplete": "Reset complete!",
"fixValues": "Upraviť hodnoty",
"fixValuesText1": "Ak si narazil na bug alebo spravil chybu, ktorá ti neprávom zmenila postavu (zranenie, ktoré si nemal dostať, zlato, ktoré si v skutočnosti nezískal, atď.), môžeš si manuálne upraviť hodnoty. Áno, dá sa pomocou toho podvádzať - používaj túto funkciu s rozmyslom, inak by mohla sabotovať budovanie tvojich vlastných návykov!",
"fixValuesText2": "Všimni si, že na jednotlivých úlohách si nemôžeš obnoviť série. To môžeš spraviť pri editácii denných úloh, keď si otvoríš pokročilé možnosti, kde nájdeš pole na obnovenie série.",
@@ -96,6 +108,7 @@
"emailNotifications": "Email notifikácie",
"wonChallenge": "You won a Challenge!",
"newPM": "Received Private Message",
+ "sentGems": "Poslať drahokamy!",
"giftedGems": "Gifted Gems",
"giftedGemsInfo": "<%= amount %> Gems - by <%= name %>",
"giftedSubscription": "Gifted Subscription",
@@ -127,15 +140,20 @@
"promoPlaceholder": "Enter Promotion Code",
"displayInviteToPartyWhenPartyIs1": "Display Invite To Party button when party has 1 member.",
"saveCustomDayStart": "Save Custom Day Start",
- "registration": "Registration",
+ "registration": "Registrácia",
"addLocalAuth": "Add local authentication:",
"generateCodes": "Generate Codes",
"generate": "Generate",
"getCodes": "Get Codes",
"webhooks": "Webhooks",
- "enabled": "Enabled",
+ "enabled": "Povolené",
"webhookURL": "Webhook URL",
- "add": "Add",
+ "invalidUrl": "invalid url",
+ "invalidEnabled": "the \"enabled\" parameter should be a boolean",
+ "regIdRequired": "RegId is required",
+ "pushDeviceAdded": "Push device added successfully",
+ "pushDeviceAlreadyAdded": "The user already has the push device",
+ "add": "Pridať",
"buyGemsGoldCap": "Cap raised to <%= amount %>",
"mysticHourglass": "<%= amount %> Mystic Hourglass",
"mysticHourglassText": "Mystic Hourglasses allow purchasing a previous month's Mystery Item set.",
@@ -147,7 +165,7 @@
"mysticHourglasses": "Mystic Hourglasses:",
"paypal": "PayPal",
"amazonPayments": "Amazon Payments",
- "timezone": "Time Zone",
+ "timezone": "Časová zóna",
"timezoneUTC": "Habitica uses the time zone set on your PC, which is: <%= utc %>",
- "timezoneInfo": "If that time zone is wrong, first reload this page using your browser's reload or refresh button to ensure that Habitica has the most recent information. If it is still wrong, adjust the time zone on your PC and then reload this page again.
If you use Habitica on other PCs or mobile devices, the time zone must be the same on them all. If your Dailies have been reseting at the wrong time, repeat this check on all other PCs and on a browser on your mobile devices."
+ "timezoneInfo": "If that time zone is wrong, first reload this page using your browser's reload or refresh button to ensure that Habitica has the most recent information. If it is still wrong, adjust the time zone on your PC and then reload this page again.
If you use Habitica on other PCs or mobile devices, the time zone must be the same on them all. If your Dailies have been resetting at the wrong time, repeat this check on all other PCs and on a browser on your mobile devices."
}
\ No newline at end of file
diff --git a/common/locales/sk/spells.json b/common/locales/sk/spells.json
index 7ee448f806..4071f7b165 100644
--- a/common/locales/sk/spells.json
+++ b/common/locales/sk/spells.json
@@ -1,50 +1,56 @@
{
"spellWizardFireballText": "Výbuch ohňa",
- "spellWizardFireballNotes": "Flames burst from your hands. You gain XP, and you deal extra damage to Bosses! Click on a task to cast. (Based on: INT)",
+ "spellWizardFireballNotes": "Plamene šľahajú z tvojich rúk. Získavaš skúsenosti a spôsobuješ extra poškodenie bossom! Klikni na úlohu aby si začal čarovať. (Založené na: INT)",
"spellWizardMPHealText": "Éterická vlna",
- "spellWizardMPHealNotes": "You sacrifice mana to help your friends. The rest of your party gains MP! (Based on: INT)",
+ "spellWizardMPHealNotes": "Obetuješ manu aby si pomohol svojim kamarátom. Zbytok tvojej družiny získava MP! (Založené na: INT)",
"spellWizardEarthText": "Zemetrasenie",
- "spellWizardEarthNotes": "Your mental power shakes the earth. Your whole party gains a buff to Intelligence! (Based on: Unbuffed INT)",
+ "spellWizardEarthNotes": "Tvoja mentálna sila trasie so zemou. Celá tvoja družina získava magický bonus k intelektu! (Založené na: INT bez bonusu)",
"spellWizardFrostText": "Mrazivá inovať",
- "spellWizardFrostNotes": "Ice covers your tasks. None of your streaks will reset to zero tomorrow! (One cast affects all streaks.)",
+ "spellWizardFrostNotes": "Ľad prekrýva tvoje úlohy. Žiadna z tvojich sérii sa do zajtra nezníži na nulu! (Jedno zakúzlenie platí pre všetky série.)",
"spellWarriorSmashText": "Brutálna rana",
- "spellWarriorSmashNotes": "You hit a task with all of your might. It gets more blue/less red, and you deal extra damage to Bosses! Click on a task to cast. (Based on: STR)",
+ "spellWarriorSmashNotes": "Vrazil si celou silou jednej úlohe. Získava modrejšiu / menej červenú farbu a tiež spôsobuješ väčšie poškodenie bossom! Klikni na úlohu na zakúzlenie. (Založené na: SIL)",
"spellWarriorDefensiveStanceText": "Obranný postoj",
- "spellWarriorDefensiveStanceNotes": "You prepare yourself for the onslaught of your tasks. You gain a buff to Constitution! (Based on: Unbuffed CON)",
+ "spellWarriorDefensiveStanceNotes": "Pripravíš sa na útok svojich úloh. Získavaš bonus k odolnosti! (Založené na: ODO bez bonusu)",
"spellWarriorValorousPresenceText": "Chrabrý výraz",
- "spellWarriorValorousPresenceNotes": "Your presence emboldens your party. Your whole party gains a buff to Strength! (Based on: Unbuffed STR)",
+ "spellWarriorValorousPresenceNotes": "Tvoja prítomnosť povzbudzuje družinu. Celá tvoja družina získava bonus k sile! (Založené na: SIL bez bonusu)",
"spellWarriorIntimidateText": "Zastrašujúci pohľad",
- "spellWarriorIntimidateNotes": "Your gaze strikes fear into your enemies. Your whole party gains a buff to Constitution! (Based on: Unbuffed CON)",
+ "spellWarriorIntimidateNotes": "Tvoj pohľad zosiela strach na tvojich nepriateľov. Celá tvoja družina získava bonus k odolnosti! (Založené na: ODO bez bonusu)",
"spellRoguePickPocketText": "Vreckárčiť",
- "spellRoguePickPocketNotes": "You rob a nearby task. You gain gold! Click on a task to cast. (Based on: PER)",
+ "spellRoguePickPocketNotes": "Okrádaš blízku úlohu a získavaš zlato! Klikni na úlohu pre zakúzlenie. (Založené na: POS)",
"spellRogueBackStabText": "Nôž do chrbta",
- "spellRogueBackStabNotes": "You betray a foolish task. You gain gold and XP! Click on a task to cast. (Based on: STR)",
+ "spellRogueBackStabNotes": "Podvádzaš bláznivú úlohu. Získavaš zlato a XP! Klikni na úlohu pre zakúzlenie. (Založené na: SIL)",
"spellRogueToolsOfTradeText": "Zlodejská výstroj",
- "spellRogueToolsOfTradeNotes": "You share your talents with friends. Your whole party gains a buff to Perception! (Based on: Unbuffed PER)",
+ "spellRogueToolsOfTradeNotes": "Zdieľaš svoj talent s kamarátmi. Celá tvoja družina získava bonus k postrehu! (Založené na: POS bez bonusu)",
"spellRogueStealthText": "Zakrádanie",
- "spellRogueStealthNotes": "You are too sneaky to spot. Some of your undone Dailies will not cause damage tonight, and their streaks/color will not change. (Cast multiple times to affect more Dailies)",
+ "spellRogueStealthNotes": "Si príliš prefíkaný, aby ťa bolo možné spozorovať. Niektoré tvoje nesplnené denné úlohy dnes v noci nespôsobia poškodenie a ich postup / farba sa nezmení. (Vyčaruj viackrát aby si ovplyvnil viac denných úloh)",
"spellHealerHealText": "Liečivá žiara",
- "spellHealerHealNotes": "Light covers your body, healing your wounds. You regain health! (Based on: CON and INT)",
+ "spellHealerHealNotes": "Svetlo pokrýva tvoje telo a lieči rany. Obnovuješ si svoje zdravie! (Založené na: ODO a INT)",
"spellHealerBrightnessText": "Spaľujúci jas",
- "spellHealerBrightnessNotes": "A burst of light dazzles your tasks. They become more blue and less red! (Based on: INT)",
+ "spellHealerBrightnessNotes": "Slnečný lúč oslňuje tvoje úlohy. Začínajú byť modrejšie a menej červené! (Založené na: INT)",
"spellHealerProtectAuraText": "Ochranná aura",
- "spellHealerProtectAuraNotes": "You shield your party from damage. Your whole party gains a buff to Constitution! (Based on: Unbuffed CON)",
+ "spellHealerProtectAuraNotes": "Ochraňuješ svoju družinu pred poškodením. Celá tvoja družina získava bonus k odolnosti! (Založené na: ODO bez bonusu)",
"spellHealerHealAllText": "Požehnanie",
- "spellHealerHealAllNotes": "A soothing aura surrounds you. Your whole party regains health! (Based on: CON and INT)",
+ "spellHealerHealAllNotes": "Obklopuje ťa upokojujúca aura. Celá tvoja družina si obnovuje zdravie! (Založené na: ODO a INT)",
"spellSpecialSnowballAuraText": "Snehová guľa",
"spellSpecialSnowballAuraNotes": "Hoď snehovou guľou po členovi svojej družiny! Čo zlé by sa mohlo stať? Trvá do konca jeho dňa.",
"spellSpecialSaltText": "Soľ",
"spellSpecialSaltNotes": "Niekto do teba hodil snehovú guľu. Ha ha, veľmi vtipné. Teraz zo mňa dostaň ten sneh!",
- "spellSpecialSpookDustText": "Strašidelné iskričky",
- "spellSpecialSpookDustNotes": "Turn a friend into a floating blanket with eyes!",
+ "spellSpecialSpookySparklesText": "Spooky Sparkles",
+ "spellSpecialSpookySparklesNotes": "Turn a friend into a floating blanket with eyes!",
"spellSpecialOpaquePotionText": "Elixír nepriehľadnosti",
"spellSpecialOpaquePotionNotes": "Zruší účinky strašidelných iskričiek.",
- "spellSpecialShinySeedText": "Shiny Seed",
- "spellSpecialShinySeedNotes": "Turn a friend into a joyous flower!",
- "spellSpecialPetalFreePotionText": "Petal-Free Potion",
- "spellSpecialPetalFreePotionNotes": "Cancel the effects of a Shiny Seed.",
- "spellSpecialSeafoamText": "Seafoam",
- "spellSpecialSeafoamNotes": "Turn a friend into a sea creature!",
+ "spellSpecialShinySeedText": "Lesklé semienko",
+ "spellSpecialShinySeedNotes": "Premení tvojho priateľa na veselý kvet!",
+ "spellSpecialPetalFreePotionText": "Bezlístkový elixír",
+ "spellSpecialPetalFreePotionNotes": "Ruší efekt lesklého semienka.",
+ "spellSpecialSeafoamText": "Morská pena",
+ "spellSpecialSeafoamNotes": "Premení tvojho priateľa na morského tvora!",
"spellSpecialSandText": "Piesok",
- "spellSpecialSandNotes": "Cancel the effects of Seafoam."
+ "spellSpecialSandNotes": "Ruší efekt morskej peny.",
+ "spellNotFound": "Skill \"<%= spellId %>\" not found.",
+ "partyNotFound": "Party not found",
+ "targetIdUUID": "\"targetId\" must be a valid User ID.",
+ "challengeTasksNoCast": "Casting a skill on challenge tasks is not allowed.",
+ "spellNotOwned": "You don't own this skill.",
+ "spellLevelTooHigh": "You must be level <%= level %> to use this skill."
}
\ No newline at end of file
diff --git a/common/locales/sk/subscriber.json b/common/locales/sk/subscriber.json
index bf3d16c38f..6411d937ea 100644
--- a/common/locales/sk/subscriber.json
+++ b/common/locales/sk/subscriber.json
@@ -1,14 +1,16 @@
{
"subscription": "Predplatné",
"subscriptions": "Predplatné",
- "subDescription": "Buy Gems with gold, get monthly mystery items, retain progress history, double daily drop-caps, support the devs. Click for more info.",
+ "subDescription": "Nakupuj drahokamy za peniaze, získaj záhadný predmet každý mesiac, uchovaj si históriu svojho pokroku, zdvojnásob denné padanie predmetov, podpor vývojárov. Klikni pre viac informácií. ",
"buyGemsGold": "Kúp si za zlato drahokamy.",
"buyGemsGoldText": "Alexander the Merchant will sell you Gems at a cost of <%= gemCost %> gold per gem. His monthly shipments are initially capped at <%= gemLimit %> Gems per month, but this cap increases by 5 Gems for every three months of consecutive subscription, up to a maximum of 50 Gems per month!",
+ "mustSubscribeToPurchaseGems": "Must subscribe to purchase gems with GP",
+ "reachedGoldToGemCap": "You've reached the Gold=>Gem conversion cap <%= convCap %> for this month. We have this to prevent abuse / farming. The cap will reset within the first three days of next month.",
"retainHistory": "Retain additional history entries",
"retainHistoryText": "Makes completed To-Dos and task history available for longer.",
"doubleDrops": "Zdvojnásob počet získateľných predmetov na deň",
"doubleDropsText": "Zaplň svoju stajňu rýchlejšie!",
- "mysteryItem": "Exclusive monthly items",
+ "mysteryItem": "Jedinečné predmety každý mesiac",
"mysteryItemText": "Each month you will receive a unique cosmetic item for your avatar! Plus, for every three months of consecutive subscription, the Mysterious Time Travelers will grant you access to historic (and futuristic!) cosmetic items.",
"supportDevs": "Podporuj vývojárov",
"supportDevsText": "Your subscription helps keep Habitica thriving and helps fund the development of new features. Thank you for your generosity!",
@@ -29,6 +31,7 @@
"manageSub": "Klikni pre zmenu predplatného",
"cancelSub": "Zrušiť predplatné",
"canceledSubscription": "Canceled Subscription",
+ "cancelingSubscription": "Canceling the subscription",
"adminSub": "Administrátorské predplatné",
"morePlans": "More Plans Coming Soon",
"organizationSub": "Súkromá organizácia",
@@ -73,6 +76,9 @@
"timeTravelersPopover": "We see you have a Mystic Hourglass, so we will happily travel back in time for you! Please choose the pet, mount, or Mystery Item Set you would like. You can see a list of the past item sets here! If those don't satisfy you, perhaps you'd be interested in one of our fashionably futuristic Steampunk Item Sets?",
"timeTravelersAlreadyOwned": "Congratulations! You already own everything the Time Travelers currently offer. Thanks for supporting the site!",
"mysticHourglassPopover": "A Mystic Hourglass allows you to purchase certain limited-time items, such as monthly Mystery Item Sets and awards from world bosses, from the past!",
+ "mysterySetNotFound": "Mystery set not found, or set already owned.",
+ "mysteryItemIsEmpty": "Mystery items are empty",
+ "mysteryItemOpened": "Mystery item opened.",
"mysterySet201402": "Winged Messenger Set",
"mysterySet201403": "Forest Walker Set",
"mysterySet201404": "Twilight Butterfly Set",
@@ -99,6 +105,8 @@
"mysterySet201601": "Champion of Resolution Set",
"mysterySet201602": "Heartbreaker Set",
"mysterySet201603": "Lucky Clover Set",
+ "mysterySet201604": "Leaf Warrior Set",
+ "mysterySet201605": "Marching Bard Set",
"mysterySet301404": "Steampunk Standard Set",
"mysterySet301405": "Steampunk Accessories Set",
"mysterySetwondercon": "Wondercon",
@@ -110,9 +118,25 @@
"hourglassBuyItemConfirm": "Buy this item for 1 Mystic Hourglass?",
"petsAlreadyOwned": "Pet already owned.",
"mountsAlreadyOwned": "Mount already owned.",
- "typeNotAllowedHourglass": "Item type not supported for purchase with Mystic Hourglass. Allowed types:",
+ "typeNotAllowedHourglass": "Item type not supported for purchase with Mystic Hourglass. Allowed types: <%= allowedTypes %>",
"petsNotAllowedHourglass": "Pet not available for purchase with Mystic Hourglass.",
"mountsNotAllowedHourglass": "Mount not available for purchase with Mystic Hourglass.",
"hourglassPurchase": "Purchased an item using a Mystic Hourglass!",
- "hourglassPurchaseSet": "Purchased an item set using a Mystic Hourglass!"
+ "hourglassPurchaseSet": "Purchased an item set using a Mystic Hourglass!",
+ "missingUnsubscriptionCode": "Missing unsubscription code.",
+ "missingSubscription": "User does not have a plan subscription",
+ "missingSubscriptionCode": "Missing subscription code. Possible values: basic_earned, basic_3mo, basic_6mo, google_6mo, basic_12mo.",
+ "cannotDeleteActiveAccount": "You have an active subscription, cancel your plan before deleting your account.",
+ "paymentNotSuccessful": "The payment was not successful",
+ "planNotActive": "The plan hasn't activated yet (due to a PayPal bug). It will begin <%= nextBillingDate %>, after which you can cancel to retain your full benefits",
+ "notAllowedHourglass": "Pet/Mount not available for purchase with Mystic Hourglass.",
+ "readCard": "<%= cardType %> has been read",
+ "cardTypeRequired": "Card type required",
+ "cardTypeNotAllowed": "Unknown card type.",
+ "invalidCoupon": "Invalid coupon code.",
+ "couponUsed": "Coupon code already used.",
+ "noSudoAccess": "You don't have sudo access.",
+ "couponCodeRequired": "The coupon code is required.",
+ "eventRequired": "\"req.params.event\" is required.",
+ "countRequired": "\"req.query.count\" is required."
}
\ No newline at end of file
diff --git a/common/locales/sk/tasks.json b/common/locales/sk/tasks.json
index 71729b7f3a..cf8c89b8c8 100644
--- a/common/locales/sk/tasks.json
+++ b/common/locales/sk/tasks.json
@@ -2,19 +2,19 @@
"clearCompleted": "Zmaž hotové",
"lotOfToDos": "Splnené úlohy sa po 3 dňoch automaticky archivujú. Ostanú prístupné cez Nastavenia > Export",
"deleteToDosExplanation": "Ak klikneš na tlačítko nižšie, všetky tvoje dokončené ToDo a archivované ToDo sa nenávratne zmažú. Ak si chceš uchovať históriu, najskôr použi export.",
- "addmultiple": "Add Multiple",
- "addsingle": "Add Single",
- "habit": "Habit",
+ "addmultiple": "Pridať viaceré",
+ "addsingle": "Pridať jeden",
+ "habit": "Návyk",
"habits": "Návyky",
"newHabit": "Nový návyk",
- "newHabitBulk": "New Habits (one per line)",
+ "newHabitBulk": "Nový návyk (jeden na riadok)",
"yellowred": "Slabé",
"greenblue": "Silné",
"edit": "Upraviť",
"save": "Uložiť",
"addChecklist": "Pridať kontrolný zoznam",
"checklist": "Kontrolný zoznam",
- "checklistText": "Break a task into smaller pieces! Checklists increase the Experience and Gold gained from a To-Do, and reduce the damage caused by a Daily.",
+ "checklistText": "Rozdeľ si úlohy na menšie časti! Kontrolný zoznam zvýši dosiahnuté skúsenosti a zlato získané z úlohy a zároveň zníži poškodenie spôsobené dennými úlohami. ",
"expandCollapse": "Rozbaliť/zbaliť",
"text": "Názov",
"extraNotes": "Poznámky",
@@ -22,7 +22,7 @@
"advancedOptions": "Pokročilé možnosti",
"difficulty": "Obtiažnosť",
"difficultyHelpTitle": "Ako náročná je úloha?",
- "difficultyHelpContent": "The harder a task, the more Experience and Gold it awards you when you check it off... but the more it damages you if it is a Daily or Bad Habit!",
+ "difficultyHelpContent": "Čím ťažšiu úlohu splníš, tým viac skúseností a zlata dostaneš... ale zároveň si spôsobíš viac poškodenia, ak nesplníš dennú úlohu alebo urobíš zlý návyk!",
"trivial": "Triviálne",
"easy": "Ľahké",
"medium": "Stredné",
@@ -32,40 +32,40 @@
"mental": "Mentálne",
"otherExamples": "Napr. profesné ciele, koníčky, finančné, atď.",
"progress": "Postup",
- "daily": "Daily",
+ "daily": "Denná úloha",
"dailies": "Denné úlohy",
"newDaily": "Nová denná úloha",
- "newDailyBulk": "New Dailies (one per line)",
+ "newDailyBulk": "Nové denné úlohy (jedna na riadok)",
"streakCounter": "Počítadlo série",
"repeat": "Opakovať",
"repeatEvery": "Opakovať každý",
"repeatHelpTitle": "Ako často by sa mala táto úloha opakovať?",
- "dailyRepeatHelpContent": "This task will be due every X days. You can set that value below.",
- "weeklyRepeatHelpContent": "This task will be due on the highlighted days below. Click on a day to activate/deactivate it.",
+ "dailyRepeatHelpContent": "Táto úloha bude povinná každých X dní. Hodnotu môžeš nastaviť nižšie.",
+ "weeklyRepeatHelpContent": "Táto úloha bude povinná počas vyznačených dní. Klikni na deň aby si ho aktivoval/deaktivoval.",
"repeatDays": "Každých X dní",
- "repeatWeek": "On Certain Days of the Week",
- "day": "Day",
- "days": "Days",
+ "repeatWeek": "Určité dni v týždni",
+ "day": "Deň",
+ "days": "Dni",
"restoreStreak": "Obnoviť sériu",
- "todo": "To-Do",
+ "todo": "Úloha",
"todos": "Úlohy",
"newTodo": "Nová úloha",
- "newTodoBulk": "New To-Dos (one per line)",
+ "newTodoBulk": "Nové úlohy (jedna na riadok)",
"dueDate": "Dokončiť do",
"remaining": "Aktívne",
"complete": "Hotové",
"dated": "S dátumom",
"due": "V pláne",
- "notDue": "Not Due",
+ "notDue": "Nie je presne určený čas",
"grey": "Šedé",
"score": "Skóre",
- "reward": "Reward",
+ "reward": "Odmena",
"rewards": "Odmeny",
"ingamerewards": "Výstroj a zručnosti",
"gold": "Zlato",
"silver": "Grajciar (100 grajciarov = 1 zlatka)",
"newReward": "Nová odmena",
- "newRewardBulk": "New Rewards (one per line)",
+ "newRewardBulk": "Nové odmeny (jedna na riadok)",
"price": "Cena",
"tags": "Štítky",
"editTags": "Upraviť",
@@ -73,44 +73,59 @@
"clearTags": "Vyčistiť",
"hideTags": "Skryť",
"showTags": "Zobraziť",
- "startDate": "Start Date",
+ "toRequired": "You must supply a to value",
+ "startDate": "Počiatočný dátum",
"startDateHelpTitle": "Kedy by mala táto úloha začať?",
- "startDateHelp": "Set the date for which this task takes effect. Will not be due on earlier days.",
+ "startDateHelp": "Nastav dátum, kedy bude táto úloha platná. Jej plnenie nebude povinné počas predchádzajúcich dní.",
"streakName": "- počet sérií",
"streakText": "Má na konte <%= streaks %> 21-dňové série na denných úlohách.",
"streakSingular": "Sériovač",
"streakSingularText": "Má na konte 21-dňovú sériu na denných úlohách.",
"perfectName": "x perfektný deň",
- "perfectText": "Completed all active Dailies on <%= perfects %> days. With this achievement you get a +level/2 buff to all attributes for the next day. Levels greater than 100 don't have any additional effects on buffs.",
+ "perfectText": "Zvládol všetky aktívne denné úlohy počas <%= perfects %> dní. S týmto odznakom získavaš bonus +level/2 pre všetky štatistiky na ďalší deň. Levely vyššie ako 100 nemajú žiadne ďalšie efekty bonusu.",
"perfectSingular": "Bezchybný deň",
- "perfectSingularText": "Completed all active Dailies in one day. With this achievement you get a +level/2 buff to all attributes for the next day. Levels greater than 100 don't have any additional effects on buffs.",
+ "perfectSingularText": "Zvládol všetky aktívne denné úlohy počas jedného dňa. S týmto odznakom získaš bonus +level/2 pre všetky štatistiky na ďalší deň. Levely vyššie ako 100 nemajú žiadne ďalšie efekty bonusu.",
"streakerAchievement": "Získal si odznak \"Sériovač\"! 21 dní je hranica na vytvorenie návyku. Môžeš pokračovať a získavať ďalšie odznaky za každých 21 dní na tejto alebo inej dennej úlohe!",
"fortifyName": "Siloxír",
"fortifyPop": "Vráť všetky úlohy do neutrálneho stavu (žltá farba) a navráť všetko stratené zdravie.",
"fortify": "Posilniť",
- "fortifyText": "Fortify will return all your tasks to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!",
- "confirmFortify": "Are you sure?",
- "sureDelete": "Are you sure you want to delete the <%= taskType %> with the text \"<%= taskText %>\"?",
+ "fortifyText": "Fortify will return all your tasks, except challenge tasks, to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!",
+ "confirmFortify": "Si si istý?",
+ "fortifyComplete": "Fortify complete!",
+ "sureDelete": "Si si istý, že chceš zmazať <%= taskType %> \"<%= taskText %>\"?",
"streakCoins": "Bonus za sériu!",
- "pushTaskToTop": "Push task to top. Hold ctrl or cmd to push to bottom.",
- "emptyTask": "Enter the task's title first.",
- "dailiesRestingInInn": "You're Resting in the Inn! Your Dailies will NOT hurt you tonight, but they WILL still refresh every day. If you're in a quest, you won't deal damage/collect items until you check out of the Inn, but you can still be injured by a Boss if your Party mates skip their own Dailies.",
- "habitHelp1": "Good Habits are things that you do often. They award Gold and Experience every time you click the <%= plusIcon %>.",
- "habitHelp2": "Bad Habits are things you want to avoid doing. They remove Health every time you click the <%= minusIcon %>.",
- "habitHelp3": "For inspiration, check out these sample Habits!",
- "newbieGuild": "More questions? Ask in the <%= linkStart %>Newbies Guild<%= linkEnd %>!",
- "dailyHelp1": "Dailies repeat <%= emphasisStart %>every day<%= emphasisEnd %> that they are active. Click the <%= pencilIcon %> to change the days a Daily is active.",
- "dailyHelp2": "If you don't complete active Dailies, you lose Health when your day rolls over.",
- "dailyHelp3": "Dailies turn <%= emphasisStart %>redder<%= emphasisEnd %> when you miss them, and <%= emphasisStart %>bluer<%= emphasisEnd %> when you complete them. The redder the Daily, the more it will reward you... or hurt you.",
- "dailyHelp4": "To change when your day rolls over, go to <%= linkStart %> Settings > Site<%= linkEnd %> > Custom Day Start.",
- "dailyHelp5": "For inspiration, check out these sample Dailies!",
- "toDoHelp1": "To-Dos start yellow, and get redder (more valuable) the longer it takes to complete them.",
- "toDoHelp2": "To-Dos never hurt you! They only award Gold and Experience.",
- "toDoHelp3": "Breaking a To-Do down into a checklist of smaller items will make it less scary, and will increase your points!",
- "toDoHelp4": "For inspiration, check out these sample To-Dos!",
- "rewardHelp1": "The Equipment you buy for your avatar is stored in <%= linkStart %>Inventory > Equipment<%= linkEnd %>.",
- "rewardHelp2": "Equipment affects your stats (<%= linkStart %>Avatar > Stats<%= linkEnd %>).",
- "rewardHelp3": "Special equipment will appear here during World Events.",
- "rewardHelp4": "Don't be afraid to set custom Rewards! Check out some samples here.",
- "clickForHelp": "Klikni pre pomoc"
+ "pushTaskToTop": "Presuň úlohu navrch. Drž ctrl alebo cmd aby si ju presunul naspodok.",
+ "emptyTask": "Najskôr vlož názov úlohy.",
+ "dailiesRestingInInn": "Odpočívaš v hostinci! Tvoje denné úlohy ťa túto noc nezrania, ale napriek tomu sa OBNOVIA ďalší deň. Ak si na výprave, nebudeš spôsobovať poškodenie/zberať predmety dokedy sa neodhlásiš z hostinca, ale stále môžeš byť zranený Bossom, ak člen tvojej družiny preskočí jeho dennú úlohu.",
+ "habitHelp1": "Dobré návyky sú veci, ktoré robíš často. Získaš za ne Zlato a Skúsenosti vždy, keď klikneš na <%= plusIcon %>.",
+ "habitHelp2": "Zlozvyky sú veci, ktorým sa chceš vyvarovať. Uberú ti Zdravie vždy, keď klikneš na <%= minusIcon %>.",
+ "habitHelp3": "Pre inšpiráciu pozri vzory úloh!",
+ "newbieGuild": "Máš ďalšie otázky? Spýtaj sa v <%= linkStart %>Newbies Guild<%= linkEnd %>!",
+ "dailyHelp1": "Denné úlohy sa opakujú <%= emphasisStart %>každý deň<%= emphasisEnd %>, počas ktorého sú aktívne. Klikni na <%= pencilIcon %>, aby si zmenil dni, kedy bude denná úloha aktívna.",
+ "dailyHelp2": "Ak nesplníš aktívne denné úlohy, stratíš zdravie na konci dňa.",
+ "dailyHelp3": "Denné úlohy <%= emphasisStart %>sčervenajú<%= emphasisEnd %>, keď ich nesplníš a <%= emphasisStart %>zmodrejú<%= emphasisEnd %>, keď ich splníš. Čím je denná úloha červenejšia, tým vyššiu odmenu získaš... alebo tým viac ti ublíži. ",
+ "dailyHelp4": "Ak chceš zmeniť koniec svojho dňa, klikni na <%= linkStart %> Nastavenia > Stránka<%= linkEnd %> > Vlastný začiatok dňa.",
+ "dailyHelp5": "Pre inšpiráciu pozri pár príkladov na denné úlohy!",
+ "toDoHelp1": "Úlohy zožltnú a následne sčervenajú (sú cennejšie), čím dlhšie ti trvá ich splniť. ",
+ "toDoHelp2": "Úlohy ti nikdy neublížia! Za ich splnenie získaš zlato a skúsenosti. ",
+ "toDoHelp3": "Rozložením úlohy na menšie časti bude úloha vyzerať splniteľnejšie a zvýši tvoje body!",
+ "toDoHelp4": "Pre inšpiráciu pozri pár príkladov na úlohy!",
+ "rewardHelp1": "Výstroj, ktorú si kúpiš pre svojho avatara je uložený v <%= linkStart %>Inventár > Výstroj<%= linkEnd %>.",
+ "rewardHelp2": "Výstroj ovplyvní tvoje štatistiky (<%= linkStart %>Avatar > Štatistiky<%= linkEnd %>).",
+ "rewardHelp3": "Špeciálny výstroj sa objaví tu počas Podujatí.",
+ "rewardHelp4": "Neboj sa vlastných odmien! Pozri si zopár ich ukážok.",
+ "clickForHelp": "Klikni pre pomoc",
+ "taskIdRequired": "\"taskId\" must be a valid UUID.",
+ "taskNotFound": "Úloha nebola nájdená.",
+ "invalidTaskType": "Task type must be one of \"habit\", \"daily\", \"todo\", \"reward\".",
+ "cantDeleteChallengeTasks": "Úloha, ktorá je súčasťou výzvy, nemôže byť vymazaná.",
+ "checklistOnlyDailyTodo": "Checklists are supported only on dailies and todos",
+ "checklistItemNotFound": "Žiadny zoznamový predmet nebol nájdený s daným id.",
+ "itemIdRequired": "\"itemId\" musí mať platné UUID.",
+ "tagNotFound": "No tag item was found with given id.",
+ "tagIdRequired": "\"tagId\" musí mať platné UUID zhodujúce sa so štítkom, ktorý patrí užívateľovi. ",
+ "positionRequired": "\"position\" je vyžadovaná a musí to byť číslo.",
+ "cantMoveCompletedTodo": "Hotový to-do sa nedá presunúť.",
+ "directionUpDown": "\"direction\" is required and must be 'up' or 'down'",
+ "alreadyTagged": "Úloha už má tento štítok."
}
\ No newline at end of file
diff --git a/common/locales/sr/backgrounds.json b/common/locales/sr/backgrounds.json
index 97e205a613..24a8edaac4 100644
--- a/common/locales/sr/backgrounds.json
+++ b/common/locales/sr/backgrounds.json
@@ -160,5 +160,12 @@
"backgroundGiantFlowersText": "Giant Flowers",
"backgroundGiantFlowersNotes": "Frolic atop Giant Flowers.",
"backgroundRainbowsEndText": "End of the Rainbow",
- "backgroundRainbowsEndNotes": "Discover gold at the End of the Rainbow."
+ "backgroundRainbowsEndNotes": "Discover gold at the End of the Rainbow.",
+ "backgrounds052016": "SET 24: Released May 2016",
+ "backgroundBeehiveText": "Beehive",
+ "backgroundBeehiveNotes": "Buzz and dance in a Beehive.",
+ "backgroundGazeboText": "Gazebo",
+ "backgroundGazeboNotes": "Battle a Gazebo.",
+ "backgroundTreeRootsText": "Tree Roots",
+ "backgroundTreeRootsNotes": "Explore the Tree Roots."
}
\ No newline at end of file
diff --git a/common/locales/sr/challenge.json b/common/locales/sr/challenge.json
index 79500b1f12..ea0344c605 100644
--- a/common/locales/sr/challenge.json
+++ b/common/locales/sr/challenge.json
@@ -63,5 +63,20 @@
"congratulations": "Congratulations!",
"hurray": "Hurray!",
"noChallengeOwner": "no owner",
- "noChallengeOwnerPopover": "This challenge does not have an owner because the person who created the challenge deleted their account."
+ "noChallengeOwnerPopover": "This challenge does not have an owner because the person who created the challenge deleted their account.",
+ "challengeMemberNotFound": "User not found among challenge's members",
+ "onlyGroupLeaderChal": "Only the group leader can create challenges",
+ "tavChalsMinPrize": "Prize must be at least 1 Gem for Tavern challenges.",
+ "cantAfford": "You can't afford this prize. Purchase more gems or lower the prize amount.",
+ "challengeIdRequired": "\"challengeId\" must be a valid UUID.",
+ "winnerIdRequired": "\"winnerId\" must be a valid UUID.",
+ "challengeNotFound": "Challenge not found.",
+ "onlyLeaderDeleteChal": "Only the challenge leader can delete it.",
+ "onlyLeaderUpdateChal": "Only the challenge leader can update it.",
+ "winnerNotFound": "Winner with id \"<%= userId %>\" not found or not part of the challenge.",
+ "noCompletedTodosChallenge": "\"includeComepletedTodos\" is not supported when fetching a challenge tasks.",
+ "userTasksNoChallengeId": "When \"tasksOwner\" is \"user\" \"challengeId\" can't be passed.",
+ "onlyChalLeaderEditTasks": "Tasks belonging to a challenge can only be edited by the leader.",
+ "userAlreadyInChallenge": "User is already participating in this challenge.",
+ "cantOnlyUnlinkChalTask": "Only broken challenges tasks can be unlinked."
}
\ No newline at end of file
diff --git a/common/locales/sr/character.json b/common/locales/sr/character.json
index 398339103c..247aeb329e 100644
--- a/common/locales/sr/character.json
+++ b/common/locales/sr/character.json
@@ -1,4 +1,5 @@
{
+ "communityGuidelinesWarning": "Please keep in mind that your Display Name, profile photo, and blurb must comply with the Community Guidelines (e.g. no profanity, no adult topics, no insults, etc). If you have any questions about whether or not something is appropriate, feel free to email leslie@habitica.com!",
"statsAch": "Karakteristike i odlikovanja",
"profile": "Profil",
"avatar": "Customize Avatar",
@@ -109,6 +110,7 @@
"mage": "Čarobnjak",
"mystery": "Tajna",
"changeClass": "Zamena klase i ponovna raspodela poena za osobine",
+ "lvl10ChangeClass": "To change class you must be at least level 10.",
"levelPopover": "Svaki nivo donosi Vam jedan poen koji možete dodeliti osobini kojoj želite. Možete to uraditi lično, ili možete prepustiti igri da to uradi umesto Vas.",
"unallocated": "Neupotrebljeni poeni",
"haveUnallocated": "Imate <%= points %> neupotrebljenih poena.",
@@ -164,5 +166,7 @@
"int": "INT",
"showQuickAllocation": "Show stat allocation",
"hideQuickAllocation": "Hide stat allocation",
- "quickAllocationLevelPopover": "Each level earns you one point to assign to an attribute of your choice. You can do so manually, or let the game decide for you using one of the Automatic Allocation options found in User -> Stats."
+ "quickAllocationLevelPopover": "Each level earns you one point to assign to an attribute of your choice. You can do so manually, or let the game decide for you using one of the Automatic Allocation options found in User -> Stats.",
+ "invalidAttribute": "\"<%= attr %>\" is not a valid attribute.",
+ "notEnoughAttrPoints": "You don't have enough attribute points."
}
\ No newline at end of file
diff --git a/common/locales/sr/content.json b/common/locales/sr/content.json
index 57075a2358..2690a74016 100644
--- a/common/locales/sr/content.json
+++ b/common/locales/sr/content.json
@@ -113,6 +113,12 @@
"questEggSnailText": "Snail",
"questEggSnailMountText": "Snail",
"questEggSnailAdjective": "a slow but steady",
+ "questEggFalconText": "Falcon",
+ "questEggFalconMountText": "Falcon",
+ "questEggFalconAdjective": "a swift",
+ "questEggTreelingText": "Treeling",
+ "questEggTreelingMountText": "Treeling",
+ "questEggTreelingAdjective": "a leafy",
"eggNotes": "Find a hatching potion to pour on this egg, and it will hatch into <%= eggAdjective(locale) %> <%= eggText(locale) %>.",
"hatchingPotionBase": "Običan",
"hatchingPotionWhite": "Beli",
@@ -126,6 +132,7 @@
"hatchingPotionGolden": "Zlatni",
"hatchingPotionSpooky": "Spooky",
"hatchingPotionPeppermint": "Peppermint",
+ "hatchingPotionFloral": "Floral",
"hatchingPotionNotes": "Pospite ovo po jajetu, i iz njega će se izleći <%= potText(locale) %> ljubimac.",
"premiumPotionAddlNotes": "Not usable on quest pet eggs.",
"foodMeat": "Meso",
diff --git a/common/locales/sr/contrib.json b/common/locales/sr/contrib.json
index 955ba23ebf..c0b6059364 100644
--- a/common/locales/sr/contrib.json
+++ b/common/locales/sr/contrib.json
@@ -35,8 +35,12 @@
"hallContributors": "Hall of Contributors",
"hallPatrons": "Dvorana pokrovitelja",
"rewardUser": "Dati nagradu korisniku",
- "UUID": "UUID",
+ "UUID": "User ID",
"loadUser": "Učitati korisnika",
+ "noAdminAccess": "You don't have admin access.",
+ "pageMustBeNumber": "req.query.page must be a number",
+ "userNotFound": "User not found.",
+ "invalidUUID": "UUID must be valid",
"title": "Titula",
"moreDetails": "Još detalja (1-7)",
"moreDetails2": "još detalja (8-9)",
@@ -52,8 +56,8 @@
"visitHeroes": "Posetiti Dvoranu Heroja (saradnici i sponzori)",
"conLearn": "Saznajte više o nagradima za saradnike",
"conLearnHow": "Saznajte kako da date svoj doprinos Habitica-u",
- "surveysSingle": "Pomogao Habitica-u popunjavanjem ankete. Trenutno nema anketa.",
- "surveysMultiple": "Pomogao Habitica-u popunjavanjem <%= surveys %> ankete/a. Trenutno nema anketa.",
+ "surveysSingle": "Helped Habitica grow, either by filling out a survey or helping with a major testing effort. Thank you!",
+ "surveysMultiple": "Helped Habitica grow on <%= surveys %> occasions, either by filling out a survey or helping with a major testing effort. Thank you!",
"currentSurvey": "Nova anketa",
"surveyWhen": "Odlikovanje se dodeljuje učesnicima nakon što se ankete obrade, krajem marta.",
"blurbInbox": "This is where your private messages are stored! You can send someone a message by clicking on the envelope icon next to their name in Tavern, Party, or Guild Chat. If you've received an inappropriate PM, you should email a screenshot of it to Lemoness (leslie@habitica.com)",
diff --git a/common/locales/sr/death.json b/common/locales/sr/death.json
index 0868ff41fc..fc64cb8022 100644
--- a/common/locales/sr/death.json
+++ b/common/locales/sr/death.json
@@ -12,5 +12,6 @@
"losingHealthQuickly": "Gubite zdravlje prebrzo?",
"lowHealthTips3": "Nezavršeni svakodnevni zadaci povređuju vas preko noći, budite pažljivi da ne dodate previše na početku.",
"lowHealthTips4": "Ako svakodnevni zadatak nije zadat za određen dan, možete ga isključiti klikom na sličicu olovke.",
- "goodLuck": "Srećno!"
+ "goodLuck": "Srećno!",
+ "cannotRevive": "Cannot revive if not dead"
}
\ No newline at end of file
diff --git a/common/locales/sr/front.json b/common/locales/sr/front.json
index c0e07d7c6c..85f87200f2 100644
--- a/common/locales/sr/front.json
+++ b/common/locales/sr/front.json
@@ -28,6 +28,7 @@
"communityReddit": "Reddit",
"companyAbout": "Kako radi",
"companyBlog": "blog",
+ "devBlog": "Developer Blog",
"companyDonate": "Donacije",
"companyExtensions": "Ekstenzije",
"companyPrivacy": "Privatnost",
@@ -51,6 +52,7 @@
"featureSocialHeading": "Društvena igra",
"featuredIn": "O Habiticau pisali su",
"featuresHeading": "Nudimo i...",
+ "footerDevs": "Developers",
"footerCommunity": "Zajednica",
"footerCompany": "Kompanija",
"footerMobile": "Aplikacije za mobilne telefone",
@@ -182,6 +184,7 @@
"zelahQuote": "With [Habitica], I can be persuaded to go to bed on time by the thought of gaining points for an early night or losing health for a late one!",
"reportAccountProblems": "Prijavite problem sa nalogom",
"reportCommunityIssues": "Prijavite problem u Zajednici",
+ "subscriptionPaymentIssues": "Subscription and Payment Issues",
"generalQuestionsSite": "Pitanja o sajtu",
"businessInquiries": "Business Inquiries",
"merchandiseInquiries": "Merchandise Inquiries",
@@ -222,5 +225,32 @@
"altAttrWebstorm": "WebStorm",
"altAttrGithub": "GitHub",
"altAttrTrello": "Trello",
- "altAttrSlack": "Slack"
+ "altAttrSlack": "Slack",
+ "missingAuthHeaders": "Missing authentication headers.",
+ "missingAuthParams": "Missing authentication parameters.",
+ "missingUsernameEmail": "Missing username or email.",
+ "missingEmail": "Missing email.",
+ "missingUsername": "Missing username.",
+ "missingPassword": "Missing password.",
+ "missingNewPassword": "Missing new password.",
+ "wrongPassword": "Wrong password.",
+ "notAnEmail": "Invalid email address.",
+ "emailTaken": "Email address is already used in an account.",
+ "newEmailRequired": "Missing new email address.",
+ "usernameTaken": "Username already taken.",
+ "passwordConfirmationMatch": "Password confirmation doesn't match password.",
+ "invalidLoginCredentials": "Incorrect username and/or email and/or password.",
+ "passwordReset": "If we have your email on file, your password reset link has been sent to your email.",
+ "passwordResetEmailSubject": "Password Reset for Habitica",
+ "passwordResetEmailText": "Password for <%= username %> has been reset to <%= newPassword %> . Important! Both username and password are case-sensitive -- you must enter both exactly as shown here. We recommend copying and pasting both instead of typing them. Log in at <%= baseUrl %>. After you have logged in, head to <%= baseUrl %>/#/options/settings/settings and change your password.",
+ "passwordResetEmailHtml": "Password for <%= username %> has been reset to <%= newPassword %>.
Important! Both username and password are case-sensitive -- you must enter both exactly as shown here. We recommend copying and pasting both instead of typing them.
Log in at <%= baseUrl %>. After you have logged in, head to <%= baseUrl %>/#/options/settings/settings and change your password.",
+ "invalidLoginCredentialsLong": "Uh-oh - your username or password is incorrect.\n- Make sure your username or email is typed correctly.\n- You may have signed up with Facebook, not email. Double-check by trying Facebook login.\n- If you forgot your password, click \"Forgot Password\".",
+ "invalidCredentials": "There is no account that uses those credentials.",
+ "accountSuspended": "Account has been suspended, please contact leslie@habitica.com with your User ID \"<%= userId %>\" for assistance.",
+ "onlyFbSupported": "Only Facebook is supported currently.",
+ "cantDetachFb": "Account lacks another authentication method, can't detach Facebook.",
+ "onlySocialAttachLocal": "Local authentication can be added to only a social account.",
+ "invalidReqParams": "Invalid request parameters.",
+ "memberIdRequired": "\"member\" must be a valid UUID.",
+ "heroIdRequired": "\"heroId\" must be a valid UUID."
}
\ No newline at end of file
diff --git a/common/locales/sr/gear.json b/common/locales/sr/gear.json
index 01912952d8..f9a420385f 100644
--- a/common/locales/sr/gear.json
+++ b/common/locales/sr/gear.json
@@ -189,6 +189,8 @@
"weaponArmoireMiningPickaxNotes": "Mine the maximum amount of gold from your tasks! Increases Perception by <%= per %>. Enchanted Armoire: Miner Set (Item 3 of 3).",
"weaponArmoireBasicLongbowText": "Basic Longbow",
"weaponArmoireBasicLongbowNotes": "A serviceable hand-me-down bow. Increases Strength by <%= str %>. Enchanted Armoire: Basic Archer Set (Item 1 of 3).",
+ "weaponArmoireHabiticanDiplomaText": "Habitican Diploma",
+ "weaponArmoireHabiticanDiplomaNotes": "A certificate of significant achievement -- well done! Increases Intelligence by <%= int %>. Enchanted Armoire: Graduate Set (Item 1 of 3).",
"armor": "oklop",
"armorBase0Text": "Obična odeća",
"armorBase0Notes": "Potpuno obična odeća. Ne daje nikakav bonus.",
@@ -364,6 +366,10 @@
"armorMystery201512Notes": "Summon the icy flames of winter! Confers no benefit. December 2015 Subscriber Item.",
"armorMystery201603Text": "Lucky Suit",
"armorMystery201603Notes": "This suit is sewn from thousands of four-leafed clovers! Confers no benefit. March 2016 Subscriber Item.",
+ "armorMystery201604Text": "Armor o' Leaves",
+ "armorMystery201604Notes": "You, too, can be a small but fearsome leaf puff. Confers no benefit. April 2016 Subscriber Item.",
+ "armorMystery201605Text": "Marching Bard Uniform",
+ "armorMystery201605Notes": "Unlike the traditional bards who join adventuring parties, bards who join Habitican marching bands are known for grand parades, not dungeon raids. Confers no benefit. May 2016 Subscriber Item.",
"armorMystery301404Text": "Stimpank odelo",
"armorMystery301404Notes": "Kicoško i zanosno! Ne daje nikakav bonus. Predmet za pretplatnike februar 3015..",
"armorArmoireLunarArmorText": "Soothing Lunar Armor",
@@ -394,6 +400,8 @@
"armorArmoireMinerOverallsNotes": "They may seem worn, but they are enchanted to repel dirt. Increases Constitution by <%= con %>. Enchanted Armoire: Miner Set (Item 2 of 3).",
"armorArmoireBasicArcherArmorText": "Basic Archer Armor",
"armorArmoireBasicArcherArmorNotes": "This camouflaged vest lets you slip unnoticed through the forests. Increases Perception by <%= per %>. Enchanted Armoire: Basic Archer Set (Item 2 of 3).",
+ "armorArmoireGraduateRobeText": "Graduate Robe",
+ "armorArmoireGraduateRobeNotes": "Congratulations! This weighty robe hangs heavy with all the knowledge you have accrued. Increases Intelligence by <%= int %>. Enchanted Armoire: Graduate Set (Item 2 of 3).",
"headgear": "šlemovi",
"headBase0Text": "Bez šlema",
"headBase0Notes": "Gola glava",
@@ -565,6 +573,10 @@
"headMystery201602Notes": "Shield your identity from all your admirers. Confers no benefit. February 2016 Subscriber Item.",
"headMystery201603Text": "Lucky Hat",
"headMystery201603Notes": "This top hat is a magical good-luck charm. Confers no benefit. March 2016 Subscriber Item.",
+ "headMystery201604Text": "Crown o' Flowers",
+ "headMystery201604Notes": "These woven flowers make a surprisingly strong helm! Confers no benefit. April 2016 Subscriber Item.",
+ "headMystery201605Text": "Marching Bard Hat",
+ "headMystery201605Notes": "Seventy-six dragons led the big parade, with a hundred and ten gryphons close at hand! Confers no benefit. May 2016 Subscriber Item.",
"headMystery301404Text": "Otmeni cilindar",
"headMystery301404Notes": "Otmeni cilindar za pripadnike visokog društva! Predmet za pretplatnike januar 3015. Ne daje nikakav bonus.",
"headMystery301405Text": "Jednostavni cilindar",
@@ -613,6 +625,8 @@
"headArmoireMinerHelmetNotes": "Protect your head from falling tasks! Increases Intelligence by <%= int %>. Enchanted Armoire: Miner Set (Item 1 of 3).",
"headArmoireBasicArcherCapText": "Basic Archer Cap",
"headArmoireBasicArcherCapNotes": "No archer would be complete without a jaunty cap! Increases Perception by <%= per %>. Enchanted Armoire: Basic Archer Set (Item 3 of 3).",
+ "headArmoireGraduateCapText": "Graduate Cap",
+ "headArmoireGraduateCapNotes": "Congratulations! Your deep thoughts have earned you this thinking cap. Increases Intelligence by <%= int %>. Enchanted Armoire: Graduate Set (Item 3 of 3).",
"offhand": "predmet za levu ruku",
"shieldBase0Text": "Nema opreme za levu ruku",
"shieldBase0Notes": "Nema štita ni drugog oružja",
@@ -673,7 +687,7 @@
"shieldSpecialWinter2015HealerText": "Umirujući štit",
"shieldSpecialWinter2015HealerNotes": "Ovaj štit odbija ledeni vetar. Povećava Vitalnost za <%= con %>. Oprema iz ograničene serije Zima 2014/15.",
"shieldSpecialSpring2015RogueText": "Eksplozivni pisak",
- "shieldSpecialSpring2015RogueNotes": "Ne dozvolite da Vas zvuk zavara - ovo je veoma moćan eksploziv. Povećava Snagu za <%= str %>. Oprema iz ograničene serije Proleće 2015.",
+ "shieldSpecialSpring2015RogueNotes": "Don't let the sound fool you - these explosives pack a punch. Increases Strength by <%= str %>. Limited Edition 2015 Spring Gear.",
"shieldSpecialSpring2015WarriorText": "Posuda za hranu",
"shieldSpecialSpring2015WarriorNotes": "Bacite ga na neprijatelje... ili ga jednostavno zadržite, jer će se napuniti ukusnom hranom kad dođe vreme za večeru. Povećava Vitalnost za <%= con %>. Oprema iz ograničene serije Proleće 2015.",
"shieldSpecialSpring2015HealerText": "Jastuk sa dezenom",
@@ -716,6 +730,8 @@
"shieldArmoireDragonTamerShieldNotes": "Distract enemies with this dragon-shaped shield. Increases Perception by <%= per %>. Enchanted Armoire: Dragon Tamer Set (Item 2 of 3).",
"shieldArmoireMysticLampText": "Mystic Lamp",
"shieldArmoireMysticLampNotes": "Light the darkest caves with this mystic lamp! Increases Perception by <%= per %>. Enchanted Armoire: Independent Item.",
+ "shieldArmoireFloralBouquetText": "Bouquet o' Flowers",
+ "shieldArmoireFloralBouquetNotes": "Not much help in battle, but aren't they beautiful? Increases Constitution by <%= con %>. Enchanted Armoire: Independent Item.",
"back": "modni detalj za leđa",
"backBase0Text": "Bez ukrasa na leđima",
"backBase0Notes": "Bez ukrasa na leđima.",
@@ -820,6 +836,20 @@
"eyewear": "naočare",
"eyewearBase0Text": "Bez naočara",
"eyewearBase0Notes": "Bez naočara.",
+ "eyewearSpecialBlackTopFrameText": "Black Standard Eyeglasses",
+ "eyewearSpecialBlackTopFrameNotes": "Glasses with a black frame above the lenses. Confers no benefit.",
+ "eyewearSpecialBlueTopFrameText": "Blue Standard Eyeglasses",
+ "eyewearSpecialBlueTopFrameNotes": "Glasses with a blue frame above the lenses. Confers no benefit.",
+ "eyewearSpecialGreenTopFrameText": "Green Standard Eyeglasses",
+ "eyewearSpecialGreenTopFrameNotes": "Glasses with a green frame above the lenses. Confers no benefit.",
+ "eyewearSpecialPinkTopFrameText": "Pink Standard Eyeglasses",
+ "eyewearSpecialPinkTopFrameNotes": "Glasses with a pink frame above the lenses. Confers no benefit.",
+ "eyewearSpecialRedTopFrameText": "Red Standard Eyeglasses",
+ "eyewearSpecialRedTopFrameNotes": "Glasses with a red frame above the lenses. Confers no benefit.",
+ "eyewearSpecialWhiteTopFrameText": "White Standard Eyeglasses",
+ "eyewearSpecialWhiteTopFrameNotes": "Glasses with a white frame above the lenses. Confers no benefit.",
+ "eyewearSpecialYellowTopFrameText": "Yellow Standard Eyeglasses",
+ "eyewearSpecialYellowTopFrameNotes": "Glasses with a yellow frame above the lenses. Confers no benefit.",
"eyewearSpecialSummerRogueText": "Odmetnička zakrpa za oko",
"eyewearSpecialSummerRogueNotes": "Ne morate biti gusar da biste razumeli da je ovo vrhunac stila. Ne daje nikakav bonus. Oprema iz ograničene serije Leto 2014.",
"eyewearSpecialSummerWarriorText": "Zanosna zakrpa za oko",
diff --git a/common/locales/sr/groups.json b/common/locales/sr/groups.json
index f2dfbe854d..682e2cc69f 100644
--- a/common/locales/sr/groups.json
+++ b/common/locales/sr/groups.json
@@ -92,6 +92,7 @@
"send": "Poslati",
"messageSentAlert": "Poruka poslata",
"pmHeading": "Privatna poruka za <%= name %>",
+ "pmsMarkedRead": "Your private messages have been marked as read",
"clearAll": "Obrisati sve poruke",
"confirmDeleteAllMessages": "Jeste li sigurni da želite da obrišete sve poruke u sandučetu? Drugi korisnici i dalje će moći da vide poruke koje ste im poslali.",
"optOutPopover": "Ne želite da koristite privatne poruke? Kliknite ovde da ih onemogućite",
@@ -99,6 +100,15 @@
"unblock": "Odblokirati",
"pm-reply": "Odgovor",
"inbox": "Sanduče",
+ "messageRequired": "A message is required.",
+ "toUserIDRequired": "A User ID is required",
+ "gemAmountRequired": "A number of gems is required",
+ "notAuthorizedToSendMessageToThisUser": "Can't send message to this user.",
+ "privateMessageGiftIntro": "Hello <%= receiverName %>, <%= senderName %> has sent you",
+ "privateMessageGiftGemsMessage": "<%= gemAmount %> gems!",
+ "privateMessageGiftSubscriptionMessage": "<%= numberOfMonths %> months of subscription!",
+ "cannotSendGemsToYourself": "Cannot send gems to yourself. Try a subscription instead.",
+ "badAmountOfGemsToSend": "Amount must be within 1 and your current number of gems.",
"abuseFlag": "Prijavite kršenje Pravila ponašanja",
"abuseFlagModalHeading": "Želite li da prijavite <%= name %> za kršenje pravila?",
"abuseFlagModalBody": "Are you sure you want to report this post? You should ONLY report a post that violates the <%= firstLinkStart %>Community Guidelines<%= linkEnd %> and/or <%= secondLinkStart %>Terms of Service<%= linkEnd %>. Inappropriately reporting a post is a violation of the Community Guidelines and may give you an infraction. Appropriate reasons to flag a post include but are not limited to:
swearing, religous oaths
bigotry, slurs
adult topics
violence, including as a joke
spam, nonsensical messages
",
@@ -151,5 +161,29 @@
"partyUpName": "Party Up",
"partyOnName": "Party On",
"partyUpAchievement": "Joined a Party with another person! Have fun battling monsters and supporting each other.",
- "partyOnAchievement": "Joined a Party with at least four people! Enjoy your increased accountability as you unite with your friends to vanquish your foes!"
+ "partyOnAchievement": "Joined a Party with at least four people! Enjoy your increased accountability as you unite with your friends to vanquish your foes!",
+ "groupIdRequired": "\"groupId\" must be a valid UUID",
+ "groupNotFound": "Group not found.",
+ "groupTypesRequired": "You must supply a valid \"type\" query string.",
+ "questLeaderCannotLeaveGroup": "You cannot leave your party when you have started a quest. Abort the quest first.",
+ "cannotLeaveWhileActiveQuest": "You cannot leave party during an active quest. Please leave the quest first.",
+ "onlyLeaderCanRemoveMember": "Only group leader can remove a member!",
+ "memberCannotRemoveYourself": "You cannot remove yourself!",
+ "groupMemberNotFound": "User not found among group's members",
+ "mustBeGroupMember": "Must be member of the group.",
+ "keepOrRemoveAll": "req.query.keep must be either \"keep-all\" or \"remove-all\"",
+ "keepOrRemove": "req.query.keep must be either \"keep\" or \"remove\"",
+ "canOnlyInviteEmailUuid": "Can only invite using uuids or emails.",
+ "inviteMissingEmail": "Missing email address in invite.",
+ "partyMustbePrivate": "Parties must be private",
+ "userAlreadyInGroup": "User already in that group.",
+ "userAlreadyInvitedToGroup": "User already invited to that group.",
+ "userAlreadyPendingInvitation": "User already pending invitation.",
+ "userAlreadyInAParty": "User already in a party.",
+ "userWithIDNotFound": "User with id \"<%= userId %>\" not found.",
+ "userHasNoLocalRegistration": "User does not have a local registration (username, email, password).",
+ "uuidsMustBeAnArray": "User ID invites must be an array.",
+ "emailsMustBeAnArray": "Email address invites must be an array.",
+ "canOnlyInviteMaxInvites": "You can only invite \"<%= maxInvites %>\" at a time",
+ "onlyCreatorOrAdminCanDeleteChat": "Not authorized to delete this message!"
}
\ No newline at end of file
diff --git a/common/locales/sr/limited.json b/common/locales/sr/limited.json
index 89269a92d6..af8afa5f74 100644
--- a/common/locales/sr/limited.json
+++ b/common/locales/sr/limited.json
@@ -5,7 +5,7 @@
"annoyingFriends": "Iritantni prijatelji",
"annoyingFriendsText": "Članovi družine gađali su Vas grudvama <%= snowballs %> puta.",
"alarmingFriends": "Zastrašujući prijatelji",
- "alarmingFriendsText": "Članovi družine uplašili su Vas <%= spookDust %> puta.",
+ "alarmingFriendsText": "Got spooked <%= spookySparkles %> times by party members.",
"agriculturalFriends": "Prijatelji zemljoradnici",
"agriculturalFriendsText": "Članovi družine pretvorili su Vas u cvet <%= seeds %> puta.",
"aquaticFriends": "Aquatic Friends",
@@ -72,5 +72,6 @@
"comfortingKittySet": "Comforting Kitty (Healer)",
"sneakySqueakerSet": "Sneaky Squeaker (Rogue)",
"fallEventAvailability": "Available until October 31",
- "winterEventAvailability": "Available until December 31"
+ "winterEventAvailability": "Available until December 31",
+ "springEventAvailability": "Available until May 31"
}
\ No newline at end of file
diff --git a/common/locales/sr/maintenance.json b/common/locales/sr/maintenance.json
new file mode 100644
index 0000000000..efdb524cd2
--- /dev/null
+++ b/common/locales/sr/maintenance.json
@@ -0,0 +1,34 @@
+{
+ "habiticaBackSoon": "Don't worry, Habitica will be back soon!",
+ "importantMaintenance": "We are doing important maintenance that we estimate will last until <%= localDate %> in your timezone.",
+ "maintenance": "Maintenance",
+ "maintenanceMoreInfo": "Want more information about the maintenance? <%= linkStart %>Check out our info page<%= linkEnd %>.",
+ "noDamageKeepStreaks": "You will NOT take damage or lose streaks!",
+ "thanksForPatience": "Thanks for your patience!",
+ "twitterMaintenanceUpdates": "For the most recent updates, watch our Twitter, where we will be posting status information.",
+ "veteranPetAward": "At the end, you will receive a Veteran pet!",
+
+ "maintenanceInfoTitle": "Information about Upcoming Maintenance to Habitica",
+ "maintenanceInfoWhat": "What is happening?",
+ "maintenanceInfoWhatText": "On May 21, Habitica will be down for maintenance for most of the day. You will not take any damage or have your account harmed during that weekend, even if you can’t log in to check off your Dailies in time! We will be working very hard to make the downtime as short as possible, and will be posting regular updates on our Twitter account. At the end of the downtime, to thank everyone for their patience, you will all receive a rare pet!",
+ "maintenanceInfoWhy": "Why is this happening?",
+ "maintenanceInfoWhyText": "For the past several months, we have been thoroughly revamping Habitica behind-the-scenes. Specifically, we have rewritten the API. While it may not look much different on the surface, it’s a whole new world underneath. This will allow us WAY more flexibility when we want to build features in the future, and lead to improved performance!",
+ "maintenanceInfoTechDetails": "Want more details on the technical side of the process? Visit The Forge, our dev blog.",
+ "maintenanceInfoMore": "More Information",
+ "maintenanceInfoAccountChanges": "What changes will I see to my account after the rewrite is complete?",
+ "maintenanceInfoAccountChangesText": "At first, there won’t be any notable changes aside from performance improvements for features such as Challenges. If you notice any changes that shouldn’t be there, email us at admin@habitica.com and we will investigate them for you!",
+ "maintenanceInfoAddFeatures": "What kind of features will this allow Habitica to add?",
+ "maintenanceInfoAddFeaturesText": "Completing this rewrite will allow us to start building out improved chat and Guilds, plans for organizations and families, and additional productivity features like Monthlies and the ability to record yesterday’s activity! Those are all involved features on their own, so it will take time to build them, but until we were finished with this rewrite, there was no way we could start them.",
+ "maintenanceInfoHowLong": "How long will the maintenance take?",
+ "maintenanceInfoHowLongText": "We have to migrate tasks and data for all 1.3 million Habitica users -- not an easy task! We anticipate that it will take place between approximately 1pm Pacific Time (8pm UTC) and 10pm Pacific Time (5am UTC). Rest assured that we’re doing everything we can to make it go as quickly as possible! You can follow updates on our Twitter.",
+ "maintenanceInfoStatsAffected": "How will my Dailies, Streaks, Buffs, and Quests be affected?",
+ "maintenanceInfoStatsAffectedText1": "You will NOT take any damage or lose any streaks that weekend, but otherwise, your day will reset normally! Dailies that you checked will become unchecked, buffs will reset, etc. If you are in a Collection Quest, you will still find items. If you are in a Boss Battle, you will still deal damage to the Boss, but the Boss will not deal damage to you. (Even monsters need a break!)",
+ "maintenanceInfoStatsAffectedText2": "After a lot of thought, our team concluded that this was the most fair way to handle the fact that many users will not be able to check off their Dailies normally during the maintenance. We’re sorry for any inconvenience this causes!",
+ "maintenanceInfoSeeTasks": "What if I need to see my task list?",
+ "maintenanceInfoSeeTasksText": "If you know that you will need to see your task list on Saturday to remind yourself what you have to do, we recommend that before the maintenance begins, you take a screenshot of your tasks so that you can use it as a reference.",
+ "maintenanceInfoRarePet": "What kind of rare pet will I receive?",
+ "maintenanceInfoRarePetText": "To thank you for your patience during the downtime, everyone will get a rare Veteran Pet. If you’ve never received a Veteran Pet before, you will receive a Veteran Wolf. If you already have a Veteran Wolf, you will receive a Veteran Tiger. And if you already have a Veteran Wolf and a Veteran Tiger, you will receive a never-before-seen Veteran pet! After the migration is completed, it may take several hours for your pet to show up, but never fear, everyone will get one.",
+ "maintenanceInfoWho": "Who worked on this massive project?",
+ "maintenanceInfoWhoText": "We’re glad you asked! It was spearheaded by our amazing contributor paglias, with lots of help from Blade, TheHollidayInn, SabreCat, Victor Pudeyev, TheUnknown, and Alys.",
+ "maintenanceInfoTesting": "The new version was also tirelessly tested by a bunch of our amazing open-source volunteers. Thank you -- we couldn't have done this without you."
+}
diff --git a/common/locales/sr/npc.json b/common/locales/sr/npc.json
index a7b09101de..3166db8029 100644
--- a/common/locales/sr/npc.json
+++ b/common/locales/sr/npc.json
@@ -21,6 +21,26 @@
"ian": "Ijan",
"ianText": "Welcome to the Quest Shop! Here you can use Quest Scrolls to battle monsters with your friends. Be sure to check out our fine array of Quest Scrolls for purchase on the right!",
"ianBrokenText": "Welcome to the Quest Shop... Here you can use Quest Scrolls to battle monsters with your friends... Be sure to check out our fine array of Quest Scrolls for purchase on the right...",
+ "missingKeyParam": "\"req.params.key\" is required.",
+ "itemNotFound": "Item \"<%= key %>\" not found.",
+ "cannotBuyItem": "You can't buy this item.",
+ "missingTypeKeyEquip": "\"key\" and \"type\" are required parameters.",
+ "missingPetFoodFeed": "\"pet\" and \"food\" are required parameters.",
+ "invalidPetName": "Invalid pet name supplied.",
+ "missingEggHatchingPotionHatch": "\"egg\" and \"hatchingPotion\" are required parameters.",
+ "invalidTypeEquip": "\"type\" must be one of 'equipped', 'pet', 'mount', 'costume'.",
+ "mustPurchaseToSet": "Must purchase <%= val %> to set it on <%= key %>.",
+ "typeRequired": "Type is required",
+ "keyRequired": "Key is required",
+ "notAccteptedType": "Type must be in [eggs, hatchingPotions, food, quests, gear]",
+ "contentKeyNotFound": "Key not found for Content <%= type %>",
+ "plusOneGem": "+1 Gem",
+ "typeNotSellable": "Type is not sellable. Must be one of the following <%= acceptedTypes %>",
+ "userItemsKeyNotFound": "Key not found for user.items <%= type %>",
+ "pathRequired": "Path string is required",
+ "unlocked": "Items have been unlocked",
+ "alreadyUnlocked": "Full set already unlocked.",
+ "alreadyUnlockedPart": "Full set already partially unlocked.",
"USD": "(USD)",
"newStuff": "Novosti",
"cool": "Pročitaću kasnije",
@@ -64,6 +84,7 @@
"tourPetsPage": "This is the Stable! After level 4, you can hatch pets using eggs and potions. When you hatch a pet in the Market, it will appear here! Click a pet's image to add it to your avatar. Feed them with the food you find after level 4, and they'll grow into powerful mounts.",
"tourMountsPage": "Once you've fed a pet enough food to turn it into a mount, it will appear here. (Pets, mounts, and food are available after level 4.) Click a mount to saddle up!",
"tourEquipmentPage": "This is where your Equipment is stored! Your Battle Gear affects your stats. If you want to show different Equipment on your avatar without changing your stats, click \"Enable Costume.\"",
+ "equipmentAlreadyOwned": "You already own that piece of equipment",
"tourOkay": "Okay!",
"tourAwesome": "Awesome!",
"tourSplendid": "Splendid!",
diff --git a/common/locales/sr/pets.json b/common/locales/sr/pets.json
index be5ba32b37..75974f58bf 100644
--- a/common/locales/sr/pets.json
+++ b/common/locales/sr/pets.json
@@ -12,6 +12,7 @@
"etherealLion": "Eterični lav",
"veteranWolf": "Vuk veteran",
"veteranTiger": "Veteran Tiger",
+ "veteranLion": "Veteran Lion",
"cerberusPup": "Kerberovo kuče",
"hydra": "Hidra",
"mantisShrimp": "Ustonožac",
@@ -19,7 +20,7 @@
"orca": "Orka",
"royalPurpleGryphon": "Royal Purple Gryphon",
"phoenix": "Feniks",
- "bumblebee": "Bumblebee",
+ "magicalBee": "Magical Bee",
"rarePetPop1": "Kliknite na zlatnu šapu da saznate kako da nabavite ovu retku zver!",
"rarePetPop2": "Kako nabaviti ovu zver!",
"potion": "<%= potionType %> napitak",
@@ -62,6 +63,7 @@
"hatchedPet": "You hatched a <%= potion %> <%= egg %>!",
"displayNow": "Display Now",
"displayLater": "Display Later",
+ "petNotOwned": "You do not own this pet.",
"earnedCompanion": "With all your productivity, you've earned a new companion. Feed it to make it grow!",
"feedPet": "Želite li da <%= name %> pojede <%= article %><%= text %>?",
"useSaddle": "Staviti sedlo na <%= pet %>?",
@@ -83,5 +85,8 @@
"petKeyBoth": "Oslobodi sve",
"confirmPetKey": "Jeste li sigurni?",
"petKeyNeverMind": "Otkaži",
+ "petsReleased": "Pets released.",
+ "mountsAndPetsReleased": "Mounts and pets released",
+ "mountsReleased": "Mounts released",
"gemsEach": "dragulja"
}
\ No newline at end of file
diff --git a/common/locales/sr/quests.json b/common/locales/sr/quests.json
index 80536728ab..4b3c63b12f 100644
--- a/common/locales/sr/quests.json
+++ b/common/locales/sr/quests.json
@@ -78,5 +78,24 @@
"whichQuestStart": "Which quest do you want to start?",
"getMoreQuests": "Get more quests",
"unlockedAQuest": "You unlocked a quest!",
- "leveledUpReceivedQuest": "You leveled up to Level <%= level %> and received a quest scroll!"
+ "leveledUpReceivedQuest": "You leveled up to Level <%= level %> and received a quest scroll!",
+ "questInvitationDoesNotExist": "No quest invitation has been sent out yet.",
+ "questInviteNotFound": "No quest invitation found.",
+ "guildQuestsNotSupported": "Guilds cannot be invited on quests.",
+ "questNotFound": "Quest \"<%= key %>\" not found.",
+ "questNotOwned": "You don't own that quest scroll.",
+ "questNotGoldPurchasable": "Quest \"<%= key %>\" is not a Gold-purchasable quest.",
+ "questLevelTooHigh": "You must be level <%= level %> to begin this quest.",
+ "questAlreadyUnderway": "Your party is already on a quest. Try again when the current quest has ended.",
+ "questAlreadyAccepted": "You already accepted the quest invitation.",
+ "noActiveQuestToLeave": "No active quest to leave",
+ "questLeaderCannotLeaveQuest": "Quest leader cannot leave quest",
+ "notPartOfQuest": "You are not part of the quest",
+ "noActiveQuestToAbort": "There is no active quest to abort.",
+ "onlyLeaderAbortQuest": "Only the group or quest leader can abort a quest.",
+ "questAlreadyRejected": "You already rejected the quest invitation.",
+ "cantCancelActiveQuest": "You can not cancel an active quest, use the abort functionality.",
+ "onlyLeaderCancelQuest": "Only the group or quest leader can cancel the quest.",
+ "questNotPending": "There is no quest to start.",
+ "questOrGroupLeaderOnlyStartQuest": "Only the quest leader or group leader can force start the quest"
}
\ No newline at end of file
diff --git a/common/locales/sr/questscontent.json b/common/locales/sr/questscontent.json
index dbe85848a6..c7b5fa1e0d 100644
--- a/common/locales/sr/questscontent.json
+++ b/common/locales/sr/questscontent.json
@@ -304,9 +304,21 @@
"questBewilderCompletionChat": "`The Be-Wilder is DEFEATED!`\n\nWe've done it! The Be-Wilder lets out a ululating cry as it twists in the air, shedding feathers like falling rain. Slowly, gradually, it coils into a cloud of sparkling mist. As the newly-revealed sun pierces the fog, it burns away, revealing the coughing, mercifully human forms of Bailey, Matt, Alex.... and the April Fool himself.\n\n`Mistiflying is saved!`\n\nThe April Fool has enough shame to look a bit sheepish. “Oh, hm,” he says. “Perhaps I got a little…. carried away.”\n\nThe crowd mutters. Sodden flowers wash up on sidewalks. Somewhere in the distance, a roof collapses with a spectacular splash.\n\n“Er, yes,” the April Fool says. “That is. What I meant to say was, I’m dreadfully sorry.” He heaves a sigh. “I suppose it can’t all be fun and games, after all. It might not hurt to focus occasionally. Maybe I’ll get a head start on next year’s pranking.”\n\nRedphoenix coughs meaningfully.\n\n“I mean, get a head start on this year’s spring cleaning!” the April Fool says. “Nothing to fear, I’ll have Habit City in spit-shape soon. Luckily nobody is better than I at dual-wielding mops.”\n\nEncouraged, the marching band starts up.\n\nIt isn’t long before all is back to normal in Habit City. Plus, now that the Be-Wilder has evaporated, the magical bees of Mistiflying bustle back to work, and soon the flowers are blooming and the city is floating once more.\n\nAs Habiticans cuddle the magical fuzzy bees, the April Fool’s eyes light up. “Oho, I’ve had a thought! Why don’t you all keep some of these fuzzy Bee Pets and Mounts? It’s a gift that perfectly symbolizes the balance between hard work and sweet rewards, if I’m going to get all boring and allegorical on you.” He winks. “Besides, they don’t have stingers! Fool’s honor.”",
"questBewilderBossRageTitle": "Beguilement Strike",
"questBewilderBossRageDescription": "When this gauge fills, The Be-Wilder will unleash its Beguilement Strike on Habitica!",
- "questBewilderDropBumblebeePet": "Bumblebee (Pet)",
- "questBewilderDropBumblebeeMount": "Bumblebee (Mount)",
+ "questBewilderDropBumblebeePet": "Magical Bee (Pet)",
+ "questBewilderDropBumblebeeMount": "Magical Bee (Mount)",
"questBewilderBossRageMarket": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nOh no! Despite our best efforts, we've gotten distracted by the Be-Wilder’s charming illusions and have forgotten to do some of our Dailies! With a cackling cry, the shining bird beats its wings, raising a swarm of mist around Alex the Merchant. When the fog clears, he has been possessed! “Have some free samples!” he shouts gleefully, and begins to hurl exploding eggs and potions at fleeing Habiticans. Not the most favorable of sales, to be sure.\n\nHurry! Let's stay focused on our Dailies to defeat this monster before it possesses someone else.",
"questBewilderBossRageStables": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nAhh!!! Once again the Be-Wilder has dazzled us into neglecting our Dailies, and now it has attacked Matt the Beast Master! With a swirl of mist, Matt transforms into a terrifying winged creature, and all the pets and mounts howl sadly in their stables. Quickly, stay focused on your tasks to defeat this dastardly distraction!",
- "questBewilderBossRageBailey": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nLook out! In the middle of reporting the news, Bailey the Town Crier has been possessed by the Be-Wilder! She lets out an evil, uninformative screech as she rises into the air. Now how will we know what’s going on?\n\nDon't give up... we're so close to defeating this bothersome bird for once and for all!"
+ "questBewilderBossRageBailey": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nLook out! In the middle of reporting the news, Bailey the Town Crier has been possessed by the Be-Wilder! She lets out an evil, uninformative screech as she rises into the air. Now how will we know what’s going on?\n\nDon't give up... we're so close to defeating this bothersome bird for once and for all!",
+ "questFalconText": "The Birds of Preycrastination",
+ "questFalconNotes": "Mt. Habitica is being overshadowed by a looming mountain of To-Dos. It used to be a place to picnic and enjoy a sense of accomplishment, until the neglected tasks grew out of control. Now it's home to fearsome Birds of Preycrastination, foul creatures which stop Habiticans from completing their tasks!
\"It's too hard!\" they caw at @JonArinbjorn and @Onheiron. \"It'll take too long to do right now! It won't make any difference if you wait until tomorrow! Why don't you do something fun instead?\"
No more, you vow. You will climb your personal mountain of To-Dos and defeat the Birds of Preycrastination!",
+ "questFalconCompletion": "Having finally triumphed over the Birds of Preycrastination, you settle down to enjoy the view and your well-earned rest.
\"Wow!\" says @Trogdorina. \"You won!\"
@Squish adds, \"Here, take these eggs I found as a reward.\"",
+ "questFalconBoss": "Birds of Preycrastination",
+ "questFalconDropFalconEgg": "Falcon (Egg)",
+ "questFalconUnlockText": "Unlocks purchasable Falcon eggs in the Market",
+ "questTreelingText": "The Tangle Tree",
+ "questTreelingNotes": "It's the annual Garden Competition, and everyone is talking about the mysterious project which @aurakami has promised to unveil. You join the crowd on the day of the big announcement, and marvel at the introduction of a moving tree. @fuzzytrees explains that the tree will help with garden maintenance, showing how it can mow the lawn, trim the hedge and prune the roses all at the same time – until the tree suddenly goes wild, turning its secateurs on its creator! The crowd panics as everyone tries to flee, but you aren't afraid – you leap forward, ready to do battle.",
+ "questTreelingCompletion": "You dust yourself off as the last few leaves drift to the floor. In spite of the upset, the Garden Competition is now safe – although the tree you just reduced to a heap of wood chips won't be winning any prizes! \"Still a few kinks to work out there,\" @PainterProphet says. \"Perhaps someone else would do a better job of training the saplings. Do you fancy a go?\"",
+ "questTreelingBoss": "Tangle Tree",
+ "questTreelingDropTreelingEgg": "Treeling (Egg)",
+ "questTreelingUnlockText": "Unlocks purchasable Treeling eggs in the Market"
}
\ No newline at end of file
diff --git a/common/locales/sr/rebirth.json b/common/locales/sr/rebirth.json
index 4bb399f070..72cc75d133 100644
--- a/common/locales/sr/rebirth.json
+++ b/common/locales/sr/rebirth.json
@@ -5,7 +5,7 @@
"rebirthStartOver": "Reinkarnacija vraća vašeg lika na nivo 1.",
"rebirthAdvList1": "Zdravlje će Vam biti napunjeno.",
"rebirthAdvList2": "Nemate iskustva,zlata ili opreme (sa izuzecima besplatnih predmeta kao što su Tajanstveni predmeti).",
- "rebirthAdvList3": "Svi podešeni zadaci biće vraćeni na žuto, i serije će biti prekinute.",
+ "rebirthAdvList3": "Your Habits, Dailies, and To-Dos reset to yellow, and streaks reset, except for challenge tasks.",
"rebirthAdvList4": "Bićete Ratnik dok ne otključate novu klasu.",
"rebirthInherit": "Novi lik će naslediti neke stvari od svog prethodnika:",
"rebirthInList1": "Zadaci, istorija, i postavke ostaće nepromenjeni.",
@@ -24,5 +24,6 @@
"rebirthPop": "Počnite iz početka s nivoa 1, i zadržite odlikovanja, kolekcionarske predmete, i zadatke, zajedno sa istorijom.",
"rebirthName": "Sfera za reinkarnaciju",
"reborn": "Reinkarniran, najviši nivo <%= reLevel %>",
- "confirmReborn": "Jeste li sigurni?"
+ "confirmReborn": "Jeste li sigurni?",
+ "rebirthComplete": "You have been reborn!"
}
\ No newline at end of file
diff --git a/common/locales/sr/settings.json b/common/locales/sr/settings.json
index 3f2e167be4..45e583a89f 100644
--- a/common/locales/sr/settings.json
+++ b/common/locales/sr/settings.json
@@ -47,6 +47,7 @@
"customDayStart": "Vreme početka dana",
"changeCustomDayStart": "Change Custom Day Start?",
"sureChangeCustomDayStart": "Are you sure you want to change your custom day start?",
+ "customDayStartHasChanged": "Your custom day start has changed.",
"nextCron": "Your Dailies will next reset the first time you use Habitica after <%= time %>. Make sure you have completed your Dailies before this time!",
"customDayStartInfo1": "Habitica defaults to check and reset your Dailies at midnight in your own time zone each day. You can customize that time here.",
"misc": "Ostalo",
@@ -61,12 +62,23 @@
"newUsername": "Novo korisničko ime",
"dangerZone": "Opasna zona",
"resetText1": "PAŽNjA! Ova funkcija nulira delove Vašeg naloga. Njena upotreba se ne preporučuje, ali neki korisnici je koriste nakon igranja s postavkama i upoznavanja sa sajtom.",
- "resetText2": "Izgubićete sve nivoe, zlato, i iskustvo. Svi Vaši zadaci biće izbrisani i izgubićete sve podatke o zadacima. Izgubićete svu opremu, ali moći ćete ponovo da je kupite, uključujući i opremu iz ograničenih serija i predmete za pretplatnike koje već posedujete (moraćete da imate odgovarajuću klasu da biste kupili određenu opremu). Zadržaćete svoju klasu, ljubimce, i životinje za jahanje. Ako upotebite sferu za reinkarnaciju, zadržaćete zadatke.",
+ "resetText2": "You will lose all your levels, gold, and experience points. All your tasks (except those from challenges) will be deleted permanently and you will lose all of their historical data. You will lose all your equipment but you will be able to buy it all back, including all limited edition equipment or subscriber Mystery items that you already own (you will need to be in the correct class to re-buy class-specific gear). You will keep your current class and your pets and mounts. You might prefer to use an Orb of Rebirth instead, which is a much safer option and which will preserve your tasks.",
"deleteText": "Jeste li sigurni? Ovim ćete zauvek obrisati svoj nalog, i nećete moći ponovo da ga aktivirate. Da biste ponovo igrali Habitica, moraćete da napravite novi nalog. Dragulji koje posedujete neće Vam biti vraćeni. Ako ste potpuno sigurni, ukucajte <%= deleteWord %> u polje za tekst.",
"API": "API",
+ "APIv3": "API v3",
+ "APIv2": "API v2 - Deprecated",
"APIText": "Kopirajte ove kodove u aplikacije razvijene za Habitica. Vaš API ključ ima funkciju lozinke, pazite kome ga šaljete i kome je dostupan. Povremeno od Vas može biti zatražen Lični Broj Korisnika (User ID, UID), ali nikada ne objavljujte svoj API ključ tamo gde ga bilo ko može videti, uključujući i Github.",
"APIToken": "API ključ (ovo je lozinka – pročitajte objašnjenje iznad)",
+ "thirdPartyApps": "Third Party Apps",
+ "dataToolDesc": "A webpage that shows you certain information from your Habitica account, such as statistics about your tasks, equipment, and skills.",
+ "beeminder": "Beeminder",
+ "beeminderDesc": "Let Beeminder automatically monitor your Habitica To-Dos. You can commit to maintaining a target number of To-Dos completed per day or per week, or you can commit to gradually reducing your remaining number of uncompleted To-Dos. (By \"commit\" Beeminder means under threat of paying actual money! But you may also just like Beeminder's fancy graphs.)",
+ "chromeChatExtension": "Chrome Chat Extension",
+ "chromeChatExtensionDesc": "The Chrome Chat Extension for Habitica adds an intuitive chat box to all of habitica.com. It allows users to chat in the Tavern, their party, and any guilds they are in.",
+ "otherExtensions": "Other Extensions",
+ "otherDesc": "Find other apps, extensions, and tools on the Habitica wiki.",
"resetDo": "Da, vrati nalog na početno stanje!",
+ "resetComplete": "Reset complete!",
"fixValues": "Ispravljanje vrednosti",
"fixValuesText1": "Ako ste naleteli na neku grešku u igri ili ste svojom greškom izmenili svoj nalog (šteta koja nije trebalo da Vam bude naneta, Zlato koje niste zaista zaradili, itd.), ovde možete da ispravite te vrednosti. Da, ovo Vam omogućava varanje: koristite ovu funkciju pažljivo, u protivnom, ometaćete samo svoju samodisciplinu.",
"fixValuesText2": "Odavde ne možete da vratite izgubljene serije na pojedinačnim zadacima. Vraćanje izgubljenih serija možete izvršiti u Naprednim opcijama u prozoru za izmenu Svakodnevnih zadataka, funkcijom Podešavanje brojača.",
@@ -96,6 +108,7 @@
"emailNotifications": "Obaveštenja imejlom",
"wonChallenge": "You won a Challenge!",
"newPM": "Primljena privatna poruka",
+ "sentGems": "Sent gems!",
"giftedGems": "Poklonjeni dragulji",
"giftedGemsInfo": "<%= amount %> Dragulja - od <%= name %>",
"giftedSubscription": "Poklonjena pretplata",
@@ -135,6 +148,11 @@
"webhooks": "Webhooks",
"enabled": "Enabled",
"webhookURL": "Webhook URL",
+ "invalidUrl": "invalid url",
+ "invalidEnabled": "the \"enabled\" parameter should be a boolean",
+ "regIdRequired": "RegId is required",
+ "pushDeviceAdded": "Push device added successfully",
+ "pushDeviceAlreadyAdded": "The user already has the push device",
"add": "Dodaj",
"buyGemsGoldCap": "Cap raised to <%= amount %>",
"mysticHourglass": "<%= amount %> Mystic Hourglass",
@@ -149,5 +167,5 @@
"amazonPayments": "Amazon Payments",
"timezone": "vremenska zona",
"timezoneUTC": "Habitica uses the time zone set on your PC, which is: <%= utc %>",
- "timezoneInfo": "If that time zone is wrong, first reload this page using your browser's reload or refresh button to ensure that Habitica has the most recent information. If it is still wrong, adjust the time zone on your PC and then reload this page again.
If you use Habitica on other PCs or mobile devices, the time zone must be the same on them all. If your Dailies have been reseting at the wrong time, repeat this check on all other PCs and on a browser on your mobile devices."
+ "timezoneInfo": "If that time zone is wrong, first reload this page using your browser's reload or refresh button to ensure that Habitica has the most recent information. If it is still wrong, adjust the time zone on your PC and then reload this page again.
If you use Habitica on other PCs or mobile devices, the time zone must be the same on them all. If your Dailies have been resetting at the wrong time, repeat this check on all other PCs and on a browser on your mobile devices."
}
\ No newline at end of file
diff --git a/common/locales/sr/spells.json b/common/locales/sr/spells.json
index d9c4473d3f..f90094e76a 100644
--- a/common/locales/sr/spells.json
+++ b/common/locales/sr/spells.json
@@ -35,8 +35,8 @@
"spellSpecialSnowballAuraNotes": "Bacite grudvu na člana družine! Šta može poći naopako? Efekat traje do kraja dana.",
"spellSpecialSaltText": "So",
"spellSpecialSaltNotes": "Neko Vas je gađao grudvom. Ha ha, jako smešno. A sad skidaj ovaj sneg s mene.",
- "spellSpecialSpookDustText": "Sablasne iskre",
- "spellSpecialSpookDustNotes": "Pretvorite prijatelja u lebdeće ćebe s prorezima za oči!",
+ "spellSpecialSpookySparklesText": "Spooky Sparkles",
+ "spellSpecialSpookySparklesNotes": "Turn a friend into a floating blanket with eyes!",
"spellSpecialOpaquePotionText": "Neprozirni napitak",
"spellSpecialOpaquePotionNotes": "Poništite efekat Sablasne iskre",
"spellSpecialShinySeedText": "Svetlucava semenka",
@@ -46,5 +46,11 @@
"spellSpecialSeafoamText": "Seafoam",
"spellSpecialSeafoamNotes": "Turn a friend into a sea creature!",
"spellSpecialSandText": "Sand",
- "spellSpecialSandNotes": "Cancel the effects of Seafoam."
+ "spellSpecialSandNotes": "Cancel the effects of Seafoam.",
+ "spellNotFound": "Skill \"<%= spellId %>\" not found.",
+ "partyNotFound": "Party not found",
+ "targetIdUUID": "\"targetId\" must be a valid User ID.",
+ "challengeTasksNoCast": "Casting a skill on challenge tasks is not allowed.",
+ "spellNotOwned": "You don't own this skill.",
+ "spellLevelTooHigh": "You must be level <%= level %> to use this skill."
}
\ No newline at end of file
diff --git a/common/locales/sr/subscriber.json b/common/locales/sr/subscriber.json
index 59656cb28d..c4f8acd3e4 100644
--- a/common/locales/sr/subscriber.json
+++ b/common/locales/sr/subscriber.json
@@ -4,6 +4,8 @@
"subDescription": "Buy Gems with gold, get monthly mystery items, retain progress history, double daily drop-caps, support the devs. Click for more info.",
"buyGemsGold": "Kupovina dragulja zlatom",
"buyGemsGoldText": "Alexander the Merchant will sell you Gems at a cost of <%= gemCost %> gold per gem. His monthly shipments are initially capped at <%= gemLimit %> Gems per month, but this cap increases by 5 Gems for every three months of consecutive subscription, up to a maximum of 50 Gems per month!",
+ "mustSubscribeToPurchaseGems": "Must subscribe to purchase gems with GP",
+ "reachedGoldToGemCap": "You've reached the Gold=>Gem conversion cap <%= convCap %> for this month. We have this to prevent abuse / farming. The cap will reset within the first three days of next month.",
"retainHistory": "Retain additional history entries",
"retainHistoryText": "Makes completed To-Dos and task history available for longer.",
"doubleDrops": "Udvostručenje maksimalnog dnevnog broja predmeta",
@@ -29,6 +31,7 @@
"manageSub": "Kliknite da podesite opcije u vezi s pretplatom",
"cancelSub": "Otkažite pretplatu",
"canceledSubscription": "Otkazana pretplata",
+ "cancelingSubscription": "Canceling the subscription",
"adminSub": "Pretplate administratora",
"morePlans": "More Plans Coming Soon",
"organizationSub": "Privatna organizacija",
@@ -73,6 +76,9 @@
"timeTravelersPopover": "We see you have a Mystic Hourglass, so we will happily travel back in time for you! Please choose the pet, mount, or Mystery Item Set you would like. You can see a list of the past item sets here! If those don't satisfy you, perhaps you'd be interested in one of our fashionably futuristic Steampunk Item Sets?",
"timeTravelersAlreadyOwned": "Congratulations! You already own everything the Time Travelers currently offer. Thanks for supporting the site!",
"mysticHourglassPopover": "A Mystic Hourglass allows you to purchase certain limited-time items, such as monthly Mystery Item Sets and awards from world bosses, from the past!",
+ "mysterySetNotFound": "Mystery set not found, or set already owned.",
+ "mysteryItemIsEmpty": "Mystery items are empty",
+ "mysteryItemOpened": "Mystery item opened.",
"mysterySet201402": "Winged Messenger Set",
"mysterySet201403": "Forest Walker Set",
"mysterySet201404": "Twilight Butterfly Set",
@@ -99,6 +105,8 @@
"mysterySet201601": "Champion of Resolution Set",
"mysterySet201602": "Heartbreaker Set",
"mysterySet201603": "Lucky Clover Set",
+ "mysterySet201604": "Leaf Warrior Set",
+ "mysterySet201605": "Marching Bard Set",
"mysterySet301404": "Steampunk Standard Set",
"mysterySet301405": "Steampunk Accessories Set",
"mysterySetwondercon": "Wondercon",
@@ -110,9 +118,25 @@
"hourglassBuyItemConfirm": "Buy this item for 1 Mystic Hourglass?",
"petsAlreadyOwned": "Pet already owned.",
"mountsAlreadyOwned": "Mount already owned.",
- "typeNotAllowedHourglass": "Item type not supported for purchase with Mystic Hourglass. Allowed types:",
+ "typeNotAllowedHourglass": "Item type not supported for purchase with Mystic Hourglass. Allowed types: <%= allowedTypes %>",
"petsNotAllowedHourglass": "Pet not available for purchase with Mystic Hourglass.",
"mountsNotAllowedHourglass": "Mount not available for purchase with Mystic Hourglass.",
"hourglassPurchase": "Purchased an item using a Mystic Hourglass!",
- "hourglassPurchaseSet": "Purchased an item set using a Mystic Hourglass!"
+ "hourglassPurchaseSet": "Purchased an item set using a Mystic Hourglass!",
+ "missingUnsubscriptionCode": "Missing unsubscription code.",
+ "missingSubscription": "User does not have a plan subscription",
+ "missingSubscriptionCode": "Missing subscription code. Possible values: basic_earned, basic_3mo, basic_6mo, google_6mo, basic_12mo.",
+ "cannotDeleteActiveAccount": "You have an active subscription, cancel your plan before deleting your account.",
+ "paymentNotSuccessful": "The payment was not successful",
+ "planNotActive": "The plan hasn't activated yet (due to a PayPal bug). It will begin <%= nextBillingDate %>, after which you can cancel to retain your full benefits",
+ "notAllowedHourglass": "Pet/Mount not available for purchase with Mystic Hourglass.",
+ "readCard": "<%= cardType %> has been read",
+ "cardTypeRequired": "Card type required",
+ "cardTypeNotAllowed": "Unknown card type.",
+ "invalidCoupon": "Invalid coupon code.",
+ "couponUsed": "Coupon code already used.",
+ "noSudoAccess": "You don't have sudo access.",
+ "couponCodeRequired": "The coupon code is required.",
+ "eventRequired": "\"req.params.event\" is required.",
+ "countRequired": "\"req.query.count\" is required."
}
\ No newline at end of file
diff --git a/common/locales/sr/tasks.json b/common/locales/sr/tasks.json
index 79468a4f28..03c3c51cac 100644
--- a/common/locales/sr/tasks.json
+++ b/common/locales/sr/tasks.json
@@ -73,6 +73,7 @@
"clearTags": "Očistiti",
"hideTags": "Sakriti",
"showTags": "Prikazati",
+ "toRequired": "You must supply a to value",
"startDate": "Start Date",
"startDateHelpTitle": "When should this task start?",
"startDateHelp": "Set the date for which this task takes effect. Will not be due on earlier days.",
@@ -88,8 +89,9 @@
"fortifyName": "Napitak okrepljenja",
"fortifyPop": "Vraća sve zadatke na početnu vrednost (žuta boja), i obnavlja izgubljeno zdravlje.",
"fortify": "Okrepljenje",
- "fortifyText": "Fortify will return all your tasks to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!",
+ "fortifyText": "Fortify will return all your tasks, except challenge tasks, to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!",
"confirmFortify": "Jeste li sigurni?",
+ "fortifyComplete": "Fortify complete!",
"sureDelete": "Are you sure you want to delete the <%= taskType %> with the text \"<%= taskText %>\"?",
"streakCoins": "Bonus zbog redovnog izvršavanja zadataka!",
"pushTaskToTop": "Push task to top. Hold ctrl or cmd to push to bottom.",
@@ -112,5 +114,18 @@
"rewardHelp2": "Equipment affects your stats (<%= linkStart %>Avatar > Stats<%= linkEnd %>).",
"rewardHelp3": "Special equipment will appear here during World Events.",
"rewardHelp4": "Don't be afraid to set custom Rewards! Check out some samples here.",
- "clickForHelp": "Click for help"
+ "clickForHelp": "Click for help",
+ "taskIdRequired": "\"taskId\" must be a valid UUID.",
+ "taskNotFound": "Task not found.",
+ "invalidTaskType": "Task type must be one of \"habit\", \"daily\", \"todo\", \"reward\".",
+ "cantDeleteChallengeTasks": "A task belonging to a challenge can't be deleted.",
+ "checklistOnlyDailyTodo": "Checklists are supported only on dailies and todos",
+ "checklistItemNotFound": "No checklist item was found with given id.",
+ "itemIdRequired": "\"itemId\" must be a valid UUID.",
+ "tagNotFound": "No tag item was found with given id.",
+ "tagIdRequired": "\"tagId\" must be a valid UUID corresponding to a tag belonging to the user.",
+ "positionRequired": "\"position\" is required and must be a number.",
+ "cantMoveCompletedTodo": "Can't move a completed todo.",
+ "directionUpDown": "\"direction\" is required and must be 'up' or 'down'",
+ "alreadyTagged": "The task is already tagged with given tag."
}
\ No newline at end of file
diff --git a/common/locales/sv/backgrounds.json b/common/locales/sv/backgrounds.json
index 322a73d6a3..1ee282a699 100644
--- a/common/locales/sv/backgrounds.json
+++ b/common/locales/sv/backgrounds.json
@@ -10,7 +10,7 @@
"backgrounds072014": "SET 2: Släpptes Juli 2014",
"backgroundCoralReefText": "Korallrev",
"backgroundCoralReefNotes": "Simma i ett korallrev.",
- "backgroundOpenWatersText": "Öppna Vatten",
+ "backgroundOpenWatersText": "Öppet Vatten",
"backgroundOpenWatersNotes": "Njut av de öppna vattnen.",
"backgroundSeafarerShipText": "Havsfartyg",
"backgroundSeafarerShipNotes": "Segla ombord ett Havsfartyg.",
@@ -140,25 +140,32 @@
"backgroundSnowmanArmyNotes": "Led en Snögubbearme.",
"backgroundWinterNightText": "Vinternatt",
"backgroundWinterNightNotes": "Se på stjärnorna en Vinternatt.",
- "backgrounds022016": "UPPSÄTTNING 21: Utgiven februari 2016",
+ "backgrounds022016": "SET 21: Släpptes februari 2016",
"backgroundBambooForestText": "Bambu Skog",
"backgroundBambooForestNotes": "Promenera genom Bambu Skogen.",
"backgroundCozyLibraryText": "Mysigt Bibliotek",
"backgroundCozyLibraryNotes": "Läs i det Mysiga Biblioteket.",
"backgroundGrandStaircaseText": "Storslagen Trappa",
"backgroundGrandStaircaseNotes": "Kliv nedför den Storslagna Trappan.",
- "backgrounds032016": "UPPSÄTTNING 22: Utgiven mars 2016",
+ "backgrounds032016": "SET 22: Släpptes mars 2016",
"backgroundDeepMineText": "Djup Gruva",
"backgroundDeepMineNotes": "Hitta dyrbara metaller i en Djup Gruva.",
"backgroundRainforestText": "Regnskog",
"backgroundRainforestNotes": "Bege in i en Regnskog.",
"backgroundStoneCircleText": "Cirkel av Stenar",
"backgroundStoneCircleNotes": "Kasta trollformler i en Cirkel av Stenar.",
- "backgrounds042016": "SET 23: Released April 2016",
- "backgroundArcheryRangeText": "Archery Range",
- "backgroundArcheryRangeNotes": "Practice on the Archery Range.",
- "backgroundGiantFlowersText": "Giant Flowers",
- "backgroundGiantFlowersNotes": "Frolic atop Giant Flowers.",
- "backgroundRainbowsEndText": "End of the Rainbow",
- "backgroundRainbowsEndNotes": "Discover gold at the End of the Rainbow."
+ "backgrounds042016": "SET 23: Släpptes april 2016",
+ "backgroundArcheryRangeText": "Bågskyttebana",
+ "backgroundArcheryRangeNotes": "Öva på bågskyttebanan",
+ "backgroundGiantFlowersText": "Jätteblommor",
+ "backgroundGiantFlowersNotes": "Roa dig ovan jätteblommor",
+ "backgroundRainbowsEndText": "Regnbågens slut",
+ "backgroundRainbowsEndNotes": "Upptäck guld vid regnbågens slut. ",
+ "backgrounds052016": "SET 24: Släpptes maj 2016",
+ "backgroundBeehiveText": "Bikupa",
+ "backgroundBeehiveNotes": "Sorl och dans i en bikupa.",
+ "backgroundGazeboText": "Lusthus",
+ "backgroundGazeboNotes": "Slåss mot ett lusthus.",
+ "backgroundTreeRootsText": "Trärötter",
+ "backgroundTreeRootsNotes": "Utforska trärötterna."
}
\ No newline at end of file
diff --git a/common/locales/sv/challenge.json b/common/locales/sv/challenge.json
index 78c3c72001..bd4ede5307 100644
--- a/common/locales/sv/challenge.json
+++ b/common/locales/sv/challenge.json
@@ -63,5 +63,20 @@
"congratulations": "Grattis!",
"hurray": "Hurra!",
"noChallengeOwner": "ingen ägare",
- "noChallengeOwnerPopover": "Den här utmaningen ägs inte på grund av att skaparen har tagit bort sitt konto. "
+ "noChallengeOwnerPopover": "Den här utmaningen ägs inte på grund av att skaparen har tagit bort sitt konto. ",
+ "challengeMemberNotFound": "User not found among challenge's members",
+ "onlyGroupLeaderChal": "Only the group leader can create challenges",
+ "tavChalsMinPrize": "Prize must be at least 1 Gem for Tavern challenges.",
+ "cantAfford": "You can't afford this prize. Purchase more gems or lower the prize amount.",
+ "challengeIdRequired": "\"challengeId\" must be a valid UUID.",
+ "winnerIdRequired": "\"winnerId\" must be a valid UUID.",
+ "challengeNotFound": "Challenge not found.",
+ "onlyLeaderDeleteChal": "Only the challenge leader can delete it.",
+ "onlyLeaderUpdateChal": "Only the challenge leader can update it.",
+ "winnerNotFound": "Winner with id \"<%= userId %>\" not found or not part of the challenge.",
+ "noCompletedTodosChallenge": "\"includeComepletedTodos\" is not supported when fetching a challenge tasks.",
+ "userTasksNoChallengeId": "When \"tasksOwner\" is \"user\" \"challengeId\" can't be passed.",
+ "onlyChalLeaderEditTasks": "Tasks belonging to a challenge can only be edited by the leader.",
+ "userAlreadyInChallenge": "User is already participating in this challenge.",
+ "cantOnlyUnlinkChalTask": "Only broken challenges tasks can be unlinked."
}
\ No newline at end of file
diff --git a/common/locales/sv/character.json b/common/locales/sv/character.json
index 71aaa1c918..ce76c97e50 100644
--- a/common/locales/sv/character.json
+++ b/common/locales/sv/character.json
@@ -1,4 +1,5 @@
{
+ "communityGuidelinesWarning": "Kom ihåg att ditt Användarnamn, Profilbild och dina ord om dig måste följa de Gemensamma Riktlinjerna (t.ex. inga hädelser, inga vuxna ämnen, inga förolämpningar, osv.) Om du har några frågor om huruvida något är lämpligt, var god maila leslie@habitica.com!",
"statsAch": "Statistik & Prestationer",
"profile": "Profil",
"avatar": "Skräddarsy Avatar",
@@ -34,7 +35,7 @@
"beard": "Skägg",
"mustache": "Mustasch",
"flower": "Blomma",
- "wheelchair": "Wheelchair",
+ "wheelchair": "Rullstol",
"basicSkins": "Grundläggande hudtyper",
"rainbowSkins": "Regnbågshud",
"pastelSkins": "Pastellfärgade hudtyper",
@@ -109,6 +110,7 @@
"mage": "Magiker",
"mystery": "Mysterium",
"changeClass": "Byt Klass, Återbetala Egenskapspoäng",
+ "lvl10ChangeClass": "To change class you must be at least level 10.",
"levelPopover": "Varje nivå ger dig en poäng att dela ut till valfri egenskap. Du kan antingen göra det manuellt eller låta spelet bestämma åt dig genom att använda ett av alternativen under automatisk utdelning.",
"unallocated": "Outdelade Egenskapspoäng",
"haveUnallocated": "Du har <%= points %> outdela(t/de) egenskapspoäng",
@@ -159,10 +161,12 @@
"healerWiki": "Helare",
"chooseClassLearn": "Lär dig mer om klasser",
"str": "STY",
- "con": "TÅL",
- "per": "UPP",
+ "con": "FYS",
+ "per": "KAR",
"int": "INT",
- "showQuickAllocation": "Show stat allocation",
- "hideQuickAllocation": "Hide stat allocation",
- "quickAllocationLevelPopover": "Each level earns you one point to assign to an attribute of your choice. You can do so manually, or let the game decide for you using one of the Automatic Allocation options found in User -> Stats."
+ "showQuickAllocation": "Visa poängfördelning",
+ "hideQuickAllocation": "Göm poängfördelning",
+ "quickAllocationLevelPopover": "Varje nivå ger dig en poäng att lägga ut på ett valfritt attribut. Du kan gör så själv eller låta spelet bestämma åt dig genom en av inställningarna under Användare -> Statistik & Prestationer",
+ "invalidAttribute": "\"<%= attr %>\" is not a valid attribute.",
+ "notEnoughAttrPoints": "You don't have enough attribute points."
}
\ No newline at end of file
diff --git a/common/locales/sv/communityguidelines.json b/common/locales/sv/communityguidelines.json
index 07ab6c0a29..810ea0eed0 100644
--- a/common/locales/sv/communityguidelines.json
+++ b/common/locales/sv/communityguidelines.json
@@ -103,12 +103,12 @@
"commGuidePara055": "Följande är ett par exempel på måttliga överträdelser. Detta är inte en omfattande lista.",
"commGuideList06A": "Att ignorera eller vara respektlös mot en Moderator. Detta inkluderar att offentligt klaga på moderatorer eller andra användare/glorifiera eller försvara bannade användare. Om du är bekymrad över någon regel eller Moderator, vänligen kontakta Lemoness via e-post (leslie@habitica.com).",
"commGuideList06B": "Backseat Modding. To quickly clarify a relevant point: A friendly mention of the rules is fine. Backseat modding consists of telling, demanding, and/or strongly implying that someone must take an action that you describe to correct a mistake. You can alert someone to the fact that they have committed a transgression, but please do not demand an action-for example, saying, \"Just so you know, profanity is discouraged in the Tavern, so you may want to delete that,\" would be better than saying, \"I'm going to have to ask you to delete that post.\"",
- "commGuideList06C": "Repeated Violation of Public Space Guidelines",
+ "commGuideList06C": "Upprepade Överträdelser mot de Allmäna Riktlinjerna",
"commGuideList06D": "Upprepande av smärre överträdelser",
"commGuideHeadingMinorInfractions": "Smärre Överträdelser",
"commGuidePara056": "Mindre överträdelser, trots att de inte uppmuntras, har ändå små konsekvenser. Om de fortsätter att inträffa kan de leda till allvarligare konsekvenser över tid.",
"commGuidePara057": "Följande är några exempel på mindre regelbrott. Detta är inte en omfattande lista.",
- "commGuideList07A": "First-time violation of Public Space Guidelines",
+ "commGuideList07A": "Förstagångsöverträdelser mot de Allmäna Riktlinjerna",
"commGuideList07B": "Any statements or actions that trigger a \"Please Don't\". When a Mod has to say \"Please Don't do this\" to a user, it can count as a very minor infraction for that user. An example might be \"Mod Talk: Please Don't keep arguing in favor of this feature idea after we've told you several times that it isn't feasible.\" In many cases, the Please Don't will be the minor consequence as well, but if Mods have to say \"Please Don't\" to the same user enough times, the triggering Minor Infractions will start to count as Moderate Infractions.",
"commGuideHeadingConsequences": "Konsekvenser",
"commGuidePara058": "In Habitica -- as in real life -- every action has a consequence, whether it is getting fit because you've been running, getting cavities because you've been eating too much sugar, or passing a class because you've been studying.",
@@ -148,7 +148,7 @@
"commGuideList12F": "Medarbetar-Husdjur, plus 4 Juveler",
"commGuideList12G": "Inbjudan till Medarbetarnas Gille, plus 4 Juveler.",
"commGuidePara065": "Mods are chosen from among Seventh Tier contributors by the Staff and preexisting Moderators. Note that while Seventh Tier Contributors have worked hard on behalf of the site, not all of them speak with the authority of a Mod.",
- "commGuidePara066": "There are some important things to note about the Contributor Tiers:",
+ "commGuidePara066": "Här är några viktiga saker angående medarbetarbrickor:",
"commGuideList13A": "Tiers are discretionary. They are assigned at the discretion of Moderators, based on many factors, including our perception of the work you are doing and its value in the community. We reserve the right to change the specific levels, titles and rewards at our discretion.",
"commGuideList13B": "Tiers get harder as you progress. If you made one monster, or fixed a small bug, that may be enough to give you your first contributor level, but not enough to get you the next. Like in any good RPG, with increased level comes increased challenge!",
"commGuideList13C": "Tiers don't \"start over\" in each field. When scaling the difficulty, we look at all your contributions, so that people who do a little bit of art, then fix a small bug, then dabble a bit in the wiki, do not proceed faster than people who are working hard at a single task. This helps keep things fair!",
@@ -165,7 +165,7 @@
"commGuideLink03": "Wikin",
"commGuideLink03description": "den största samlingen av information om Habitica.",
"commGuideLink04": "Github",
- "commGuideLink04description": "for bug reports or helping code programs!",
+ "commGuideLink04description": "för buggrapportering eller hjälp att koda program!",
"commGuideLink05": "The Main Trello",
"commGuideLink05description": "för förfrågningar av sidfunktioner.",
"commGuideLink06": "Det mobila Trello",
diff --git a/common/locales/sv/content.json b/common/locales/sv/content.json
index 708d0d3a5e..fde8e916a6 100644
--- a/common/locales/sv/content.json
+++ b/common/locales/sv/content.json
@@ -113,6 +113,12 @@
"questEggSnailText": "Snigel",
"questEggSnailMountText": "Snigel",
"questEggSnailAdjective": "en långsam men stadig",
+ "questEggFalconText": "Falk",
+ "questEggFalconMountText": "Falk",
+ "questEggFalconAdjective": "snabbt",
+ "questEggTreelingText": "Treeling",
+ "questEggTreelingMountText": "Treeling",
+ "questEggTreelingAdjective": "a leafy",
"eggNotes": "Hitta en kläckningsdryck och häll på det här ägget så kommer det kläckas till <%= eggAdjective(locale) %> <%= eggText(locale) %>.",
"hatchingPotionBase": "Standard",
"hatchingPotionWhite": "Vit",
@@ -126,6 +132,7 @@
"hatchingPotionGolden": "Guld",
"hatchingPotionSpooky": "läskig",
"hatchingPotionPeppermint": "Pepparmint",
+ "hatchingPotionFloral": "Floral",
"hatchingPotionNotes": "Häll den här på ett ägg, så kläcks det som ett <%= potText(locale) %> husdjur.",
"premiumPotionAddlNotes": "Kan ej användas på ägg till uppdragshusdjur",
"foodMeat": "Kött",
diff --git a/common/locales/sv/contrib.json b/common/locales/sv/contrib.json
index b3ca5bc796..f381542488 100644
--- a/common/locales/sv/contrib.json
+++ b/common/locales/sv/contrib.json
@@ -1,6 +1,6 @@
{
"friend": "Vän",
- "friendFirst": "When your first set of submissions is deployed, you will receive the Habitica Contributor's badge. Your name in Tavern chat will proudly display that you are a contributor. As a bounty for your work, you will also receive 3 Gems.",
+ "friendFirst": "När ditt första bidrag är placerat kommer du få Habiticas Medarbetarmedalj. Ditt namn i Värdshuschatten kommer stolt visa att du är en medarbetare. Som en gåva för ditt arbete får du även ta emot 3 Juveler.",
"friendSecond": "When your second set of submissions is deployed, the Crystal Armor will be available for purchase in the Rewards shop. As a bounty for your continued work, you will also receive 3 Gems.",
"elite": "Elit",
"eliteThird": "When your third set of submissions is deployed, the Crystal Helmet will be available for purchase in the Rewards shop. As a bounty for your continued work, you will also receive 3 Gems.",
@@ -28,15 +28,19 @@
"helped": "Hjälpte Habit Växa",
"helpedText1": "Hjälpte Habitica växa genom att fylla i",
"helpedText2": "denna undersökning.",
- "hall": "Hall of Heroes",
+ "hall": "Hjältarnas sal ",
"contribTitle": "Medarbetare-titel (t.ex. \"Smed\")",
"contribLevel": "Medarbetarnivå",
"contribHallText": "1-7 för normala medhjälpare, 8 för moderatorer, 9 för personal. Detta bestämmer även vilka föremål, husdjur samt riddjur som finns tillgängliga. Bestämmer även färg på namntagg. Nivåerna 8 och 9 blir automatiskt givna admin status.",
"hallContributors": "Hall of Contributors",
"hallPatrons": "Beskyddarnas Hall",
"rewardUser": "Belöna Användare",
- "UUID": "UUID",
+ "UUID": "User ID",
"loadUser": "Ladda Användare",
+ "noAdminAccess": "You don't have admin access.",
+ "pageMustBeNumber": "req.query.page must be a number",
+ "userNotFound": "User not found.",
+ "invalidUUID": "UUID must be valid",
"title": "Titel",
"moreDetails": "Fler detaljer (1-7)",
"moreDetails2": "fler detaljer (8-9)",
@@ -52,8 +56,8 @@
"visitHeroes": "Besök Hjältarnas Hall (medarbetare och hjälpare)",
"conLearn": "Lär dig mer om belöningar för medarbetare",
"conLearnHow": "Lär dig hur du kan bidra till Habitica",
- "surveysSingle": "Hjälpte Habitica växa genom att delta i en undersökning. Det finns inga aktiva undersökningar just nu.",
- "surveysMultiple": "Hjälpte Habitica växa genom att delta i <%= surveys %> undersökningar. Det finns inga aktiva undersökningar just nu.",
+ "surveysSingle": "Helped Habitica grow, either by filling out a survey or helping with a major testing effort. Thank you!",
+ "surveysMultiple": "Helped Habitica grow on <%= surveys %> occasions, either by filling out a survey or helping with a major testing effort. Thank you!",
"currentSurvey": "Nuvarande undersökning",
"surveyWhen": "Detta emblem kommer delas ut till alla deltagare när undersökningarna har bearbetats, sent i Mars.",
"blurbInbox": "This is where your private messages are stored! You can send someone a message by clicking on the envelope icon next to their name in Tavern, Party, or Guild Chat. If you've received an inappropriate PM, you should email a screenshot of it to Lemoness (leslie@habitica.com)",
diff --git a/common/locales/sv/death.json b/common/locales/sv/death.json
index b32bc97d1d..caba599286 100644
--- a/common/locales/sv/death.json
+++ b/common/locales/sv/death.json
@@ -12,5 +12,6 @@
"losingHealthQuickly": "Tappar du hälsa snabbt?",
"lowHealthTips3": "Oavklarade Dagliga Uppgifter skadar dig under natten, så var försiktig och inte lägga till för många i början!",
"lowHealthTips4": "Om en daglig uppgift inte behöver göras på en viss veckodag, så kan du avaktivera det genom att klicka på penn-symbolen.",
- "goodLuck": "Lycka till!"
+ "goodLuck": "Lycka till!",
+ "cannotRevive": "Cannot revive if not dead"
}
\ No newline at end of file
diff --git a/common/locales/sv/faq.json b/common/locales/sv/faq.json
index ed41164fb2..902e6879d6 100644
--- a/common/locales/sv/faq.json
+++ b/common/locales/sv/faq.json
@@ -2,15 +2,15 @@
"frequentlyAskedQuestions": "Frekvent Ställda Frågor",
"faqQuestion0": "Jag är förvirrad. Var kan jag få en överblick?",
"iosFaqAnswer0": "Först börjar du med att göra i ordning uppgifter som du vill göra dagligen. När du klarat av uppgifterna i verkliga livet och prickat av dem kommer du att tjäna erfarenhet och guld. Guld används till att köpa utrustning, några artiklar, samt skräddarsydda belöningar. Med erfarenhet levlar din karaktär upp och låser upp innehåll såsom Husdjur, Färdigheter och Uppdrag! Du kan skräddarsy din karaktär genom att gå till Meny > Skräddarsy Avatar.\n\nNågra grundläggande sätt att interagera: klicka på (+) i övre högra hörnet för att lägga till en ny uppgift. Klicka på en befintlig uppgift för att ändra den, och dra till vänster på en uppgift för att radera den. Du kan sortera dina uppgifter genom att använda Taggar i övre vänstra hörnet, och expandera samt minska checklistor genom att klicka på checklistans ikon.",
- "webFaqAnswer0": "First, you'll set up tasks that you want to do in your everyday life. Then, as you complete the tasks in real life and check them off, you'll earn Experience and Gold. Gold is used to buy equipment and some items, as well as custom rewards. Experience causes your character to level up and unlock content such as pets, skills, and quests! For more detail, check out a step-by-step overview of the game at [Help -> Overview for New Users](https://habitica.com/static/overview).",
+ "webFaqAnswer0": "Till att börja med ställer du in de uppgifter som du vill göra i din vardag. Eftersom du utför uppgifterna i verkliga livet och checkar av dem, så får du erfarenhet och guld. Gulf används för att köpa utrustning och en del saker, så väl som anpassade belöningar. Erfarenhet får din karaktär att höja sin level och låser upp innehåll som husdjur, förmågor och uppdrag! För mer detaljer, kolla in steg-för-steg översikten av spelet på [Help -> Overview for New Users](https://habitica.com/static/overview).",
"faqQuestion1": "Hur ställer jag in mina uppgifter?",
- "iosFaqAnswer1": "Good Habits (the ones with a +) are tasks that you can do many times a day, such as eating vegetables. Bad Habits (the ones with a -) are tasks that you should avoid, like biting nails. Habits with a + and a - have a good choice and a bad choice, like taking the stairs vs. taking the elevator. Good Habits award experience and gold. Bad Habits subtract health.\n\n Dailies are tasks that you have to do every day, like brushing your teeth or checking your email. You can adjust the days that a Daily is due by tapping to edit it. If you skip a Daily that is due, your avatar will take damage overnight. Be careful not to add too many Dailies at once!\n\n To-Dos are your To-Do list. Completing a To-Do earns you gold and experience. You never lose health from To-Dos. You can add a due date to a To-Do by tapping to edit.",
+ "iosFaqAnswer1": "Goda vanor (de som har ett +) är uppgifter du kan göra flera gånger om dagen, som att äta grönsaker. Dåliga vanor (de som har ett -) är vanor du borde undvika, som att bita på naglarna. Vanor med ett + och ett - är vanor med ett bra val och ett dåligt val, som att ta trapporna mot att ta hissen. Goda vanor belönas med erfarenhet och guld. Dåliga vanor straffar sig med förlorad hälsa. \n\nDagliga uppgifter är uppgifter som du bör göra varje dag, som att borsta tänderna eller kolla din email. Du kan anpassa vilken dag som din Dagliga uppgift ska göras genom att klicka för att anpassa den. Om du hoppar över en Daglig uppgift som skulle gjorts, kommer din avatar att ta skada under natten. Var försiktig och lägg inte till för många Dagliga uppgifter på en gång!\n\nAtt-göra är din att-göra-lista. Att fullfölja en Att-göra ger dig guld och erfarenhet. Du kan aldrig förlora hälsa från en Att-göra. Du kan lägga till ett datum då det ska vara färdigt genom att klicka för att anpassa den. ",
"webFaqAnswer1": "Good Habits (the ones with a ) are tasks that you can do many times a day, such as eating vegetables. Bad Habits (the ones with a ) are tasks that you should avoid, like biting nails. Habits with a and a have a good choice and a bad choice, like taking the stairs vs. taking the elevator. Good Habits award Experience and Gold. Bad Habits subtract Health.\n
\n Dailies are tasks that you have to do every day, like brushing your teeth or checking your email. You can adjust the days that a Daily is due by clicking the pencil item to edit it. If you skip a Daily that is due, your avatar will take damage overnight. Be careful not to add too many Dailies at once!\n
\n To-Dos are your To-Do list. Completing a To-Do earns you Gold and Experience. You never lose Health from To-Dos. You can add a due date to a To-Do by clicking the pencil icon to edit.",
- "faqQuestion2": "Finns det några enkla uppgifter?",
+ "faqQuestion2": "Finns det några exempel uppgifter?",
"iosFaqAnswer2": "The wiki has four lists of sample tasks to use as inspiration:\n
\n * [Sample Habits](http://habitica.wikia.com/wiki/Sample_Habits)\n * [Sample Dailies](http://habitica.wikia.com/wiki/Sample_Dailies)\n * [Sample To-Dos](http://habitica.wikia.com/wiki/Sample_To-Dos)\n * [Sample Custom Rewards](http://habitica.wikia.com/wiki/Sample_Custom_Rewards)",
"webFaqAnswer2": "The wiki has four lists of sample tasks to use as inspiration:\n * [Sample Habits](http://habitica.wikia.com/wiki/Sample_Habits)\n * [Sample Dailies](http://habitica.wikia.com/wiki/Sample_Dailies)\n * [Sample To-Dos](http://habitica.wikia.com/wiki/Sample_To-Dos)\n * [Sample Custom Rewards](http://habitica.wikia.com/wiki/Sample_Custom_Rewards)",
"faqQuestion3": "Varför ändrar mina uppgifter färg?",
- "iosFaqAnswer3": "Your tasks change color based on how well you are currently accomplishing them! Each new task starts out as a neutral yellow. Perform Dailies or positive Habits more frequently and they move toward blue. Miss a Daily or give in to a bad Habit and the task moves toward red. The redder a task, the more rewards it will give you, but if it's a Daily or bad Habit, the more it will hurt you! This helps motivate you to complete the tasks that are giving you trouble.",
+ "iosFaqAnswer3": "Dina uppgifter ändrar färg beroende på hur väl du utför dem i nuläget! Varje ny uppgift börjar som neutral i gult. Utför Dagliga Uppgifter eller positiva Vanor mer ofta så kommer de flyttas över mot blått. Missa en Daglig Uppgift eller ge efter för en dålig Vana och uppgiften kommer fluttas över mot rött. Ju rödare en uppgift, desto mer belöning kommer den ge dig, men om det är en Daglig Uppgift eller en dålig Vana, desto mer kommer den också att skada dig! Detta hjälper till att motivera dig till att utföra uppgifter innan de börjar ge dig besvär.",
"webFaqAnswer3": "Your tasks change color based on how well you are currently accomplishing them! Each new task starts out as a neutral yellow. Perform Dailies or positive Habits more frequently and they move toward blue. Miss a Daily or give in to a bad Habit and the task moves toward red. The redder a task, the more rewards it will give you, but if it’s a Daily or bad Habit, the more it will hurt you! This helps motivate you to complete the tasks that are giving you trouble.",
"faqQuestion4": "Varför förlorade min avatar hälsan och hur får jag tillbaka den?",
"iosFaqAnswer4": "There are several things that can cause you to take damage. First, if you left Dailies incomplete overnight, they will damage you. Second, if you tap a bad Habit, it will damage you. Finally, if you are in a Boss Battle with your Party and one of your Party mates did not complete all their Dailies, the Boss will attack you.\n\n The main way to heal is to gain a level, which restores all your health. You can also buy a Health Potion with gold from the Rewards column. Plus, at level 10 or above, you can choose to become a Healer, and then you will learn healing skills. If you are in a Party with a Healer, they can heal you as well.",
@@ -24,7 +24,7 @@
"faqQuestion7": "Hur blir jag en Krigare, Magiker, Smygare eller Helare?",
"iosFaqAnswer7": "At level 10, you can choose to become a Warrior, Mage, Rogue, or Healer. (All players start as Warriors by default.) Each Class has different equipment options, different Skills that they can cast after level 11, and different advantages. Warriors can easily damage Bosses, withstand more damage from their tasks, and help make their Party tougher. Mages can also easily damage Bosses, as well as level up quickly and restore Mana for their party. Rogues earn the most gold and find the most item drops, and they can help their Party do the same. Finally, Healers can heal themselves and their Party members.\n\n If you don't want to choose a Class immediately -- for example, if you are still working to buy all the gear of your current class -- you can click “Decide Later” and choose later under Menu > Choose Class.",
"webFaqAnswer7": "At level 10, you can choose to become a Warrior, Mage, Rogue, or Healer. (All players start as Warriors by default.) Each Class has different equipment options, different Skills that they can cast after level 11, and different advantages. Warriors can easily damage Bosses, withstand more damage from their tasks, and help make their party tougher. Mages can also easily damage Bosses, as well as level up quickly and restore Mana for their party. Rogues earn the most Gold and find the most item drops, and they can help their party do the same. Finally, Healers can heal themselves and their party members.\n
\n If you don't want to choose a Class immediately -- for example, if you are still working to buy all the gear of your current class -- you can click \"Opt Out\" and re-enable it later under User > Stats.",
- "faqQuestion8": "What is the blue stat bar that appears in the Header after level 10?",
+ "faqQuestion8": "Vad är det för blå bar som finns i menyn vid nivå 10?",
"iosFaqAnswer8": "The blue bar that appeared when you hit level 10 and chose a Class is your Mana bar. As you continue to level up, you will unlock special Skills that cost Mana to use. Each Class has different Skills, which appear after level 11 under Menu > Use Skills. Unlike your health bar, your Mana bar does not reset when you gain a level. Instead, Mana is gained when you complete Good Habits, Dailies, and To-Dos, and lost when you indulge bad Habits. You'll also regain some Mana overnight -- the more Dailies you completed, the more you will gain.",
"webFaqAnswer8": "The blue bar that appeared when you hit level 10 and chose a Class is your Mana bar. As you continue to level up, you will unlock special Skills that cost Mana to use. Each Class has different Skills, which appear after level 11 in a special section in the Rewards Column. Unlike your Health bar, your Mana bar does not reset when you gain a level. Instead, Mana is gained when you complete Good Habits, Dailies, and To-Dos, and lost when you indulge bad Habits. You’ll also regain some Mana overnight -- the more Dailies you completed, the more you will gain.",
"faqQuestion9": "Hur strider jag mot monster och går på äventyr?",
@@ -37,8 +37,8 @@
"iosFaqAnswer11": "You can report a bug, request a feature, or send feedback under Menu > Report a Bug and Menu > Send Feedback! We'll do everything we can to assist you.",
"webFaqAnswer11": "Bug reports are collected on GitHub. Go to [Help > Report a Bug](https://github.com/HabitRPG/habitrpg/issues/2760) and follow the instructions. Don't worry, we'll get you fixed up soon!\n
\n Feature requests are collected on Trello. Go to [Help > Request a Feature](https://trello.com/c/odmhIqyW/440-read-first-table-of-contents) and follow the instructions. Ta-da!",
"faqQuestion12": "Hur gör jag för att strida mot en Världsboss?",
- "iosFaqAnswer12": "World Bosses are special monsters that appear in the Tavern. All active users are automatically battling the Boss, and their tasks and skills will damage the Boss as usual.\n\n You can also be in a normal Quest at the same time. Your tasks and skills will count towards both the World Boss and the Boss/Collection Quest in your party.\n\n A World Boss will never hurt you or your account in any way. Instead, it has a Rage Bar that fills when users skip Dailies. If its Rage bar fills, it will attack one of the Non-Player Characters around the site and their image will change.\n\n You can read more about [past World Bosses](http://habitica.wikia.com/wiki/World_Bosses) on the wiki.",
+ "iosFaqAnswer12": "Världsbossar är speciella monster som dyker upp i Värdshuset. Alla aktiva användare kommer automatiskt att hamna i strid med Bossen och deras uppgifter och förmågor kommer skada Bossen som vanligt.\n\nDu kan också vara på ett vanligt Äventyr samtidigt. Dina uppgifter ofh förmågor kommer röknas mot både Världsbossen och Bossen/Samlingsäventyrer i din grupp.\n\nEn Världsboss skadar aldrig dig eller ditt konto på något sätt. Istället har den en Raserimätare som fylls upp när en användare hoppar över sina Dagsuppdrag. Om Raserimätaren fulls helt kommer Världsbossen att attackera en Icke-spelare Person som finns på sidan och deras bild kommer då att ändras.",
"webFaqAnswer12": "World Bosses are special monsters that appear in the Tavern. All active users are automatically battling the Boss, and their tasks and skills will damage the Boss as usual.\n
\n You can also be in a normal Quest at the same time. Your tasks and skills will count towards both the World Boss and the Boss/Collection Quest in your party.\n
\n A World Boss will never hurt you or your account in any way. Instead, it has a Rage Bar that fills when users skip Dailies. If its Rage bar fills, it will attack one of the Non-Player Characters around the site and their image will change.\n
\n You can read more about [past World Bosses](http://habitica.wikia.com/wiki/World_Bosses) on the wiki.",
- "iosFaqStillNeedHelp": "If you have a question that isn't on this list or on the [Wiki FAQ](http://habitica.wikia.com/wiki/FAQ), come ask in the Tavern chat under Menu > Tavern! We're happy to help.",
+ "iosFaqStillNeedHelp": "Om du har en fråga som inte finns på denna listan eller på [Wiki FAQ](http://habitica.wikia.com/wiki/FAQ), så kan du ställa den i Värdshus-chatten under Meny > Värdshus! Vi hjälper gärna till.",
"webFaqStillNeedHelp": "If you have a question that isn't on this list or on the [Wiki FAQ](http://habitica.wikia.com/wiki/FAQ), come ask in the [Newbies Guild](https://habitica.com/#/options/groups/guilds/5481ccf3-5d2d-48a9-a871-70a7380cee5a)! We're happy to help."
}
\ No newline at end of file
diff --git a/common/locales/sv/front.json b/common/locales/sv/front.json
index b4601f8f6f..00b8b011a8 100644
--- a/common/locales/sv/front.json
+++ b/common/locales/sv/front.json
@@ -3,7 +3,7 @@
"accept1Terms": "Genom att klicka på knappen nedan godkänner jag",
"accept2Terms": "och",
"alexandraQuote": "Kunde inte INTE prata om [Habitica] i mitt tal i Madrid. Ett måste för frilansare som fortfarande behöver en boss. ",
- "althaireQuote": "Att alltid ha igång ett uppdrag motiverar mig verkligen att göra alla mina dagliga utmaningar och att-göra. Min största motivation är att inte göra mitt sällskap besviket.",
+ "althaireQuote": "Att alltid ha ett äventyr igång motiverar mig verkligen att göra alla mina dagliga utmaningar och att-göra. Min största motivation är att inte svika mitt sällskap.",
"andeeliaoQuote": "Jättebra produkt, jag började bara för några dagar sedan och är redan mer medveten och produktiv med min tid!",
"autumnesquirrelQuote": "Jag har slutat skjuta upp arbete och hushållssysslor och betalar mina räkningar i tid.",
"businessSample1": "Bekräfta 1 sida av inventeringen",
@@ -28,13 +28,14 @@
"communityReddit": "Reddit",
"companyAbout": "Hur det fungerar",
"companyBlog": "Blogg",
+ "devBlog": "Developer Blog",
"companyDonate": "Donera",
"companyExtensions": "Tillägg",
"companyPrivacy": "Integritet",
"companyTerms": "Villkor",
"companyVideos": "Videor",
"contribUse": "Habiticas medhjälpare använder",
- "dragonsilverQuote": "I can't tell you how many time and task tracking systems I've tried over the decades... [Habitica] is the only thing I've used that actually helps me get things done rather than just list them.",
+ "dragonsilverQuote": "Jag kan inte ens berätta hur många tid- och planeringssystem jag har provat över årtionedena. [Habitica] är det enda jag provat som verkligen hjälpt mig få saker gjorda istället för att bara lägga in dem i listor.",
"dreimQuote": "När jag upptäckte [Habitica] förra sommaren hade jag precis blivit underkänt i ungefär hälften av mina prov. Tack vare de Dagliga Utmaningarna... kunde jag organisera och disciplinera mig själv, och jag klarade faktiskt av alla mina prov med riktigt bra betyg en månad sen.",
"elmiQuote": "Varje morgon ser jag fram emot att stiga upp så att jag kan tjäna lite guld!",
"email": "E-post",
@@ -42,21 +43,22 @@
"evagantzQuote": "Mitt första tandläkarbesök där tandhygienisten faktiskt var entusiastisk över tandtrådsvanor. Tack [Habitica]!",
"examplesHeading": "Spelare använder Habitica för att ...",
"featureAchievementByline": "Gjort någonting helt fantastiskt? Få ett märke och visa det upp!",
- "featureAchievementHeading": "Achievement Badges",
- "featureEquipByline": "Buy limited edition equipment, potions, and other virtual goodies in our Market with your task rewards!",
+ "featureAchievementHeading": "Bedrift-brickor",
+ "featureEquipByline": "Använd dina belöningar från uppgifter till att köpa trolldrycker, utrustning gjord i begränsad utgåva, och andra virtuella godsaker i vår Marknad.",
"featureEquipHeading": "Utrustning och extra saker",
- "featurePetByline": "Eggs and items drop when you complete your tasks. Be as productive as possible to collect pets and mounts!",
+ "featurePetByline": "Ägg och föremål hittas när du slutför uppgifter. Var så produktiv som möjligt och samla på dig Husdjur och Riddjur.",
"featurePetHeading": "Husdjur och Riddjur",
- "featureSocialByline": "Join common-interest groups with like-minded people. Create Challenges to compete against other users.",
+ "featureSocialByline": "Gå med i grupper med likasinnade människor med liknande intressen. Skapa utmaningar och tävla mot andra medlemmar.",
"featureSocialHeading": "Socialt spel",
"featuredIn": "Syns i",
"featuresHeading": "Vi har även ...",
+ "footerDevs": "Developers",
"footerCommunity": "Gemenskap",
"footerCompany": "Företag",
"footerMobile": "Mobil",
"footerSocial": "Socialt",
"forgotPass": "Glömt lösenord",
- "frabjabulousQuote": "[Habitica] is the reason I got a killer, high-paying job... and even more miraculous, I'm now a daily flosser!",
+ "frabjabulousQuote": "[Habitica] är anledningen till att jag fick ett suveränt och välbetalt jobb... Vad som är ännu mer mirakulöst är att jag nu använder tandtråd varje dag!",
"free": "Gå med gratis",
"gamifyButton": "Gör ditt liv till ett spel idag!",
"goalSample1": "Spela piano i 1 timme",
@@ -72,25 +74,25 @@
"healthSample4": "Ät hälsosamt/Skräpmat",
"healthSample5": "Svettas i 1 timme",
"history": "Historik",
- "infhQuote": "[Habitica] has really helped me impart structure to my life in graduate school.",
+ "infhQuote": "[Habitica] har verkligen hjälpt mig att få mer struktur i mitt liv gymnasieliv.",
"invalidEmail": "En giltig e-postadress krävs för att kunna utföra en lösenordsåterställning.",
- "irishfeet123Quote": "I've had horrible habits with clearing my place completely after meals and leaving cups all over the place. [Habitica] has cured that!",
- "joinOthers": "Join <%= userCount %> people making it fun to achieve goals!",
- "kazuiQuote": "Before [Habitica], I was stuck with my thesis, as well as dissatisfied with my personal discipline regarding housework and things like learning vocabulary and studying Go theory. It turns out breaking down these tasks into smaller manageable checklists is quite the thing to keep me motivated and constantly working.",
+ "irishfeet123Quote": "Jag har haft den hemska ovanan att alltid lämna koppar överallt när jag städar. [Habitica] har botat det!",
+ "joinOthers": "Anslut dig till de <%= userCount %> personer som gör det roligt att nå sina mål!",
+ "kazuiQuote": "Innan Habitica så hade jag fastnat på min avhandling och var missnöjd med min självdiciplin gällande hushållsarbete och saker i stil med att öka mittordförråd och studera Go-teori. Det visade sig att bryta ner dessa uppgifter till mindre, mer hanterbara checklistor var precis vad jag behövde för att hålla mig motiverad och i konstant arbete.",
"landingadminlink": "adminpaket",
"landingend": "Inte övertalad än?",
"landingend2": "Se en mer detaljerad lista med",
"landingend3": ". Letar du efter ett mer privat lösning? Ta en titt på våra",
"landingend4": "som är perfekta för familjer, lärare, stödgrupper och företag.",
"landingfeatureslink": "våra funktioner",
- "landingp1": "The problem with most productivity apps on the market is that they provide no incentive to continue using them. Habitica fixes this by making habit building fun! By rewarding you for your successes and penalizing you for slip-ups, Habitica provides external motivation for completing your day-to-day activities.",
+ "landingp1": "Problemet med de flesta produktivitetsappar på marknaden är att de inte ger en något incitament för att fortsätta använda dem. Habitica fixar detta genom att göra det roligt att bygga vanor! Genom att belöna dig när du lyckas och straffa dig när du gör misstag, ger Habitica dig extern motivation för att klara av dina dagliga aktiviteter.",
"landingp2": "Direkt när du förstärker en positiv vana, fullbordar en daglig uppgift eller tar itu med en gammal att-göra-uppgift så belönar Habitica dig med erfarenhetspoäng och guld. När du får erfarenhet kan du gå upp i level, vilket bygger upp dina egenskaper och låser upp fler funktioner som klasser och husdjur. Guld kan spenderas på föremål i spelet som kan ändra din upplevelse, eller personliga belöningar du har skapat för motivation. När även den minsta framgången ger dig omedelbar belöning är det mindre troligt att du skjuter upp det du behöver göra.",
"landingp2header": "Omedelbar tillfredställelse",
- "landingp3": "Whenever you indulge in a bad habit or fail to complete one of your daily tasks, you lose health. If your health drops too low, you lose some of the progress you've made. By providing immediate consequences, Habitica can help break bad habits and procrastination cycles before they cause real-world problems.",
+ "landingp3": "När du ger efter för en dålig vana eller misslyckas med att göra klart någon av dina dagliga uppgifter så förlorar du hälsa. Om din hälsa sjunker för lågt så förlorar du en del av de framsteg du gjort. Genom att ge omedelbara konsekvenser kan Habitica hjälpa till att bryta dåliga vanor och uppskjutningsmönster innan de blir till problem i verkligheten.",
"landingp3header": "Konsekvenser",
- "landingp4": "With an active community, Habitica provides the accountability you need to stay on task. With the party system, you can bring in a group of your closest friends to cheer you on. The guild system allows you to find people with similar interests or obstacles, so you can share your goals and swap tips on how to tackle your problems. In Habitica, the community means that you have both the support and the accountability you need to succeed.",
+ "landingp4": "Genom en aktiv gemenskap tillhandahåller Habitica den ansvarsskyldighet du behöver för att fortsätta med dina uppgifter. Med Sällskapssystemet kan du samla en grupp av dina närmaste vänner för att peppa dig. Gillen hjälper dig hitta människor med liknande intressen eller svårigheter, så att ni kan dela med er av era mål och tips på hur ni kan handskas med era problem. Hos Habitica så ger gemenskapen dig både det stöd och den ansvarsskyldighet du behöver för att lyckas.",
"landingp4header": "Ansvarsskyldighet",
- "leadText": "Habitica is a free habit building and productivity app that treats your real life like a game. With in-game rewards and punishments to motivate you and a strong social network to inspire you, Habitica can help you achieve your goals to become healthy, hard-working, and happy.",
+ "leadText": "Habitica är en gratis vane- och produktivitetsalp som behandlar ditt riktiga liv som ett spel. Med inbyggda belöningar och bestraffningar som motiverar dig och ett starkt socialt nätverk som inspirerar dig kan Habitica hjälpa dig att nå dina mål att bli hälsosam, hårt arbetande och lycklig.",
"login": "Logga in",
"loginAndReg": "Logga in / registrera",
"loginFacebookAlt": "Logga in / registrera med Facebook",
@@ -107,12 +109,12 @@
"marketing2Lead2Title": "Bossar",
"marketing2Lead3": "Utmaningar gör det möjligt att tävla med vänner och främligar. Den som klarat sig bäst mot slutet av utmaningen vinner speciella priser.",
"marketing3Header": "Appar",
- "marketing3Lead1": "The iPhone & Android apps let you take care of business on the go. We realize that logging into the website to click buttons can be a drag.",
- "marketing3Lead2": "Other 3rd Party Tools tie Habitica into various aspects of your life. Our API provides easy integration for things like the Chrome Extension, for which you lose points when browsing unproductive websites, and gain points when on productive ones. See more here",
+ "marketing3Lead1": "Våran iPhone & Android app gör det möjligt att ta hand om dina ärenden ''on the go''. Vi förstår att det kan vara jobbigt att ständigt logga in på hemsidan för att klicka på knappar.",
+ "marketing3Lead2": "Andra tredjeparts-verktyg knyter HabitRPG till olika delar av ditt liv. Vårat API ger enkel integrering till samer som Chrome Extensions där du tappar poäng varje gång du surfar till improduktiva hemsidor och får poäng när du besöker produktiva. Se mer här (på engelska)",
"marketing4Header": "Organisationsanvändning",
- "marketing4Lead1": "Education is one of the best sectors for gamification. We all know how glued to phones and games students are these days; harness that power! Pit your students against eachother in friendly competition. Reward good behavior with rare prizes. Watch their grades and behavior soar.",
+ "marketing4Lead1": "Utbildningssektorn är en av de bästa sektorerna för spelifiering. Vi vet alla hur elever idag sitter fastnaglade vid sina spel och telefoner; utnyttja det! Låt dem tävla mot varandra i vänskapliga tävlingar. Belöna gott uppförande med särskilda priser. Se sedan hur deras betyg och beteende skjuter i höjden.",
"marketing4Lead1Title": "Spelifiering inom utbildning",
- "marketing4Lead2": "Health care costs are on the rise, and something's gotta give. Hundreds of programs are built to reduce costs and improve wellness. We believe Habitica can pave a substantial path towards healthy lifestyles.",
+ "marketing4Lead2": "Kostnaden för sjukvården ökar och någonting måste göras. Det finns hundratals program vars mål är att minska ens utgifter och öka ens hälsa. Vi tror att Habitica kan leda en på vägen mot hälsosammare livsstilar.",
"marketing4Lead2Title": "Spelifiering inom hälsa och välmående",
"marketing4Lead3-1": "Vill du göra ditt liv till ett spel?",
"marketing4Lead3-2": "Intresserad av att leda en grupp inom utbildning, hälsa och mer?",
@@ -124,13 +126,13 @@
"motivate1": "Motivera dig själv till att göra vad som helst.",
"motivate2": "Få organisation. Få motivation. Få guld.",
"passConfirm": "Bekräfta lösenord",
- "passMan": "In case you are using a password manager (like 1Password) and have problems logging in, try typing your username and password manually.",
+ "passMan": "Om du använder ett program som automatiskt fyller i ditt lösenord (som 1Password) och har problem att logga in, försök att skriva in ditt användarnamn och lösenord manuellt.",
"password": "Lösenord",
"playButton": "Spela",
"playButtonFull": "Spela Habitica",
"presskit": "Presskit",
"presskitDownload": "Ladda ner alla bilder:",
- "presskitText": "Thanks for your interest in Habitica! The following images can be used for articles or videos about Habitica. For more information, please contact Siena Leslie at leslie@habitica.com.",
+ "presskitText": "Tack för ditt intresse för Habitica! Följande bilder kan användas för artiklar eller videos om Habitica. För mer information, var vänlig att kontakta Siena Leslie på leslie@habitica.com.",
"privacy": "integritetspolicy",
"psst": "Psst",
"punishByline": "Bryt dåliga vanor och uppskjutningscykler med omedelbara konsekvenser.",
@@ -153,24 +155,24 @@
"schoolSample3": "Möt med studiecirkel",
"schoolSample4": "Anteckningar för 1 kapitel",
"schoolSample5": "Läs 1 kapitel",
- "sixteenBitFilQuote": "I'm getting my jobs and tasks done in record time thanks to [Habitica]. I'm just always so eager to reach my next level-up!",
+ "sixteenBitFilQuote": "Jag blir klar med mina arbets- och dagliga uppgifter på rekordtid tack vare [Habitica]. Jag är alltid så sugen på att komma till nästa level!",
"skysailorQuote": "Mitt sällskap och våra uppdrag håller mig engagerad i spelet, vilket håller mig motiverad till att få saker gjorda och göra positiva förändringar i mitt liv.",
"socialTitle": "Habitica | Gör ditt liv till ett spel",
"supermouse35Quote": "Jag tränar mer och jag har inte glömt att ta mina mediciner på en månad. Tack, Habit. :D",
"sync": "Synkronisera",
"tasks": "Uppgifter",
"teamSample1": "Dagordning till tisdag",
- "teamSample2": "Brainstorm Growth Hacking",
- "teamSample3": "Discuss this week's KPIs",
+ "teamSample2": "''Brainstorm Growth Hacking''",
+ "teamSample3": "Diskutera den här veckans KPIs",
"teams": "Lag",
"terms": "allmänna villkor",
"testimonialHeading": "Vad andra säger ...",
- "localStorageTryFirst": "If you are experiencing problems with Habitica, click the button below to clear local storage for this website (other websites will not be affected). You will need to log in again after doing this, so first be sure that you know your log-in details, which can be found at Settings -> <%= linkStart %>Site<%= linkEnd %>.",
- "localStorageTryNext": "If the problem persists, please <%= linkStart %>Report a Bug<%= linkEnd %> if you haven't already.",
+ "localStorageTryFirst": "Om du stöter på problem på Habitica, klicka på knappen nedan för att rensa lagringsutrymmet för den här webbplatsen (andra webbplatser kommer inte att påverkas). Du kommer behöva logga in på nytt efter detta, så se först till att du har koll på dina inloggningsuppgifter, som du hittar under Inställningar -> <%= linkStart %>Hemsidas<%= linkEnd %>.",
+ "localStorageTryNext": "Om problemet kvarstår, var snäll och <%= linkStart %>Rapportera en bugg<%= linkEnd %> om du inte redan gjort det.",
"localStorageClearing": "Rensar Lokal Dataförvaring",
- "localStorageClearingExplanation": "Your browser's local storage is being cleared. You will be logged out and redirected to the home page. Please wait.",
+ "localStorageClearingExplanation": "Din webläsares lokala datasparning kommer nu rensas. Du blir utloggad och omdirigeras till hemsidan. Vänligen vänta.",
"localStorageClear": "Rensa Lokal Dataförvaring",
- "localStorageClearExplanation": "This button will clear local storage and log you out",
+ "localStorageClearExplanation": "Den här knappen kommer rensa all lokal datasparning när du loggar ut.",
"tutorials": "Handledningar",
"unlockByline1": "Uppnå dina mål och gå upp i nivå.",
"unlockByline2": "Lås upp nya motiveringsverktyg, som att samla husdjur, få slumpmässiga belöningar, kasta förtrollningar och mer!",
@@ -179,9 +181,10 @@
"username": "Användarnamn",
"watchVideos": "Se videor",
"work": "Arbete",
- "zelahQuote": "With [Habitica], I can be persuaded to go to bed on time by the thought of gaining points for an early night or losing health for a late one!",
+ "zelahQuote": "Med [Habitica] kan jag få mig själv i säng i tid tack vare motivationen att få poäng för en tidig natt och rädslan att förlora poäng för en sen!",
"reportAccountProblems": "Anmäl kontoproblem",
"reportCommunityIssues": "Anmäl gemenskapsproblem",
+ "subscriptionPaymentIssues": "Subscription and Payment Issues",
"generalQuestionsSite": "Allmäna frågor om sidan",
"businessInquiries": "Företagsfrågor",
"merchandiseInquiries": "Varufrågor",
@@ -190,9 +193,9 @@
"apps": "Appar",
"checkOutMobileApps": "Ta en titt på våra appar!",
"imagine1": "Föreställ dig att förbättrandet av ditt liv var lika roligt som att spela ett spel.",
- "landingCopy1": "Advance in the game by completing your real-life tasks.",
- "landingCopy2": "Battle monsters with friends to stay accountable to your goals.",
- "landingCopy3": "Join over <%= userCount %> people having fun as they improve their lives.",
+ "landingCopy1": "Gör framsteg i spelet genom att slutföra uppgifter i ditt vanliga liv.",
+ "landingCopy2": "Slåss mot monster tillsammans med dina vänner för att hålla dig ansvarig för dina mål.",
+ "landingCopy3": "Gå med de <%= userCount %> andra spelare som redan har roligt medan de förbättrar sina liv.",
"alreadyHaveAccount": "Jag har redan ett konto!",
"getStartedNow": "Börja Direkt!",
"altAttrNavLogo": "Habitica hem",
@@ -222,5 +225,32 @@
"altAttrWebstorm": "WebStorm",
"altAttrGithub": "GitHub",
"altAttrTrello": "Trello",
- "altAttrSlack": "Slack"
+ "altAttrSlack": "Slack",
+ "missingAuthHeaders": "Missing authentication headers.",
+ "missingAuthParams": "Missing authentication parameters.",
+ "missingUsernameEmail": "Missing username or email.",
+ "missingEmail": "Missing email.",
+ "missingUsername": "Missing username.",
+ "missingPassword": "Missing password.",
+ "missingNewPassword": "Missing new password.",
+ "wrongPassword": "Wrong password.",
+ "notAnEmail": "Invalid email address.",
+ "emailTaken": "Email address is already used in an account.",
+ "newEmailRequired": "Missing new email address.",
+ "usernameTaken": "Username already taken.",
+ "passwordConfirmationMatch": "Password confirmation doesn't match password.",
+ "invalidLoginCredentials": "Incorrect username and/or email and/or password.",
+ "passwordReset": "If we have your email on file, your password reset link has been sent to your email.",
+ "passwordResetEmailSubject": "Password Reset for Habitica",
+ "passwordResetEmailText": "Password for <%= username %> has been reset to <%= newPassword %> . Important! Both username and password are case-sensitive -- you must enter both exactly as shown here. We recommend copying and pasting both instead of typing them. Log in at <%= baseUrl %>. After you have logged in, head to <%= baseUrl %>/#/options/settings/settings and change your password.",
+ "passwordResetEmailHtml": "Password for <%= username %> has been reset to <%= newPassword %>.
Important! Both username and password are case-sensitive -- you must enter both exactly as shown here. We recommend copying and pasting both instead of typing them.
Log in at <%= baseUrl %>. After you have logged in, head to <%= baseUrl %>/#/options/settings/settings and change your password.",
+ "invalidLoginCredentialsLong": "Uh-oh - your username or password is incorrect.\n- Make sure your username or email is typed correctly.\n- You may have signed up with Facebook, not email. Double-check by trying Facebook login.\n- If you forgot your password, click \"Forgot Password\".",
+ "invalidCredentials": "There is no account that uses those credentials.",
+ "accountSuspended": "Account has been suspended, please contact leslie@habitica.com with your User ID \"<%= userId %>\" for assistance.",
+ "onlyFbSupported": "Only Facebook is supported currently.",
+ "cantDetachFb": "Account lacks another authentication method, can't detach Facebook.",
+ "onlySocialAttachLocal": "Local authentication can be added to only a social account.",
+ "invalidReqParams": "Invalid request parameters.",
+ "memberIdRequired": "\"member\" must be a valid UUID.",
+ "heroIdRequired": "\"heroId\" must be a valid UUID."
}
\ No newline at end of file
diff --git a/common/locales/sv/gear.json b/common/locales/sv/gear.json
index 420e729d48..4c2a5a8338 100644
--- a/common/locales/sv/gear.json
+++ b/common/locales/sv/gear.json
@@ -24,7 +24,7 @@
"weaponRogue2Text": "Kroksabel",
"weaponRogue2Notes": "Skarpt svärd, snabbt med ett dödande slag. Ökar Styrka med <%= str %>.",
"weaponRogue3Text": "Kukri",
- "weaponRogue3Notes": "Distinctive bush knife, both survival tool and weapon. Increases Strength by <%= str %>.",
+ "weaponRogue3Notes": "Typisk vildmarkskniv, både ett överlevnadsverktyg och ett vapen. Ökar styrka med <%= str %>.",
"weaponRogue4Text": "Nunchaku",
"weaponRogue4Notes": "Tunga batonger som svängs runt på en kedja. Ökar Styrka med <%= str %>.",
"weaponRogue5Text": "Ninja-to",
@@ -59,7 +59,7 @@
"weaponHealer5Notes": "Passar att pryda en monarks hand, eller handen på den som står på monarkens högra sida. Ökar Intelligens med <%= int %>.",
"weaponHealer6Text": "Guldspira",
"weaponHealer6Notes": "Stillar smärta hos alla som ser den. Ökar Intelligens med <%= int %>.",
- "weaponSpecial0Text": "Dark Souls Blade",
+ "weaponSpecial0Text": "Mörka själars klinga",
"weaponSpecial0Notes": "Konsumerar fiendens livsvilja för att förstärka sina slag. Ökar Styrka med <%= str %>.",
"weaponSpecial1Text": "Kristallsvärd",
"weaponSpecial1Notes": "Dess glittrande facetter förtäljer en hjältesaga. Ökar alla egenskaper med <%= attrs %>.",
@@ -69,12 +69,12 @@
"weaponSpecial3Notes": "Möten, monster, missnöje: avklarat! Mos! Ökar Styrka, Intelligens och Tålighet med <%= attrs %> vardera.",
"weaponSpecialCriticalText": "Kritisk Hammare för Kryp-Krossning",
"weaponSpecialCriticalNotes": "This champion slew a critical Github foe where many warriors fell. Fashioned from the bones of Bug, this hammer deals a mighty critical hit. Increases Strength and Perception by <%= attrs %> each.",
- "weaponSpecialTridentOfCrashingTidesText": "Trident of Crashing Tides",
+ "weaponSpecialTridentOfCrashingTidesText": "Tidvattenvågornas treudd",
"weaponSpecialTridentOfCrashingTidesNotes": "Ger dig förmågan att beordra fiskar och dessutom utdela mäktiga hugg till dina uppgifter. Ökar intelligens med <%= int %>.",
"weaponSpecialYetiText": "Yetitämjarspjut",
"weaponSpecialYetiNotes": "Detta spjut tillåter dess användare att kontrollera valfri yeti. Ökar Styrka med <%= str %>. Begränsad utgåva 2013-2014 Vinterutrustning.",
"weaponSpecialSkiText": "Ski-sassin Pole",
- "weaponSpecialSkiNotes": "A weapon capable of destroying hordes of enemies! It also helps the user make very nice parallel turns. Increases Strength by <%= str %>. Limited Edition 2013-2014 Winter Gear.",
+ "weaponSpecialSkiNotes": "Ett vapen som är kapabelt till att förinta horder med fiender! Det hjälper också dess användare att göra väldigt schyssta parallella vändningar. Ökar styrka med <%= str %>. Begränsad utgåva 2013-2014 Vinterutrustning",
"weaponSpecialCandycaneText": "Polkagrisstav",
"weaponSpecialCandycaneNotes": "A powerful mage's staff. Powerfully DELICIOUS, we mean! Two-handed weapon. Increases Intelligence by <%= int %> and Perception by <%= per %>. Limited Edition 2013-2014 Winter Gear.",
"weaponSpecialSnowflakeText": "Snöflingsstav",
@@ -82,9 +82,9 @@
"weaponSpecialSpringRogueText": "Krokklor",
"weaponSpecialSpringRogueNotes": "Utmärkt för att klättra uppför höga byggnader och även för att strimla mattor. Ökar Styrka med <%= str %>. Begränsad utgåva 2014 Vårutrustning.",
"weaponSpecialSpringWarriorText": "Morotssvärd",
- "weaponSpecialSpringWarriorNotes": "This mighty sword can slice foes with ease! It also makes a delicious mid-battle snack. Increases Strength by <%= str %>. Limited Edition 2014 Spring Gear.",
+ "weaponSpecialSpringWarriorNotes": "Det här mäktiga svärdet skivar fiender med lätthet! Det är också ett läckert mellanmål i striden. Ökar styrka med <%= str %>. Begränsad utgåva Vårutrustning 2014.",
"weaponSpecialSpringMageText": "Schweizerostsstav",
- "weaponSpecialSpringMageNotes": "Only the most powerful rodents can brave their hunger to wield this potent staff. Increases Intelligence by <%= int %> and Perception by <%= per %>. Limited Edition 2014 Spring Gear.",
+ "weaponSpecialSpringMageNotes": "Endast de mäktigaste gnagarna kan stå emot sin hunger och svinga den här kraftfulla staven. Ökar Intelligens med <%= int %> och Uppmärksamhet med <%= per %>. Begränsad utgåva Vårutrustning 2014. ",
"weaponSpecialSpringHealerText": "Underbart ben",
"weaponSpecialSpringHealerNotes": "APPORT! Ökar intelligens med <%= int %>. Begränsad utgåva 2014 vårutrustning.",
"weaponSpecialSummerRogueText": "Piratens kortsvärd",
@@ -92,7 +92,7 @@
"weaponSpecialSummerWarriorText": "Sjöfarande förskärare",
"weaponSpecialSummerWarriorNotes": "There isn't a task in any To-Do list willing to tangle with this gnarly knife! Increases Strength by <%= str %>. Limited Edition 2014 Summer Gear.",
"weaponSpecialSummerMageText": "Kelpfångare",
- "weaponSpecialSummerMageNotes": "This trident is used to spear seaweed effectively, for extra-productive kelp harvesting! Increases Intelligence by <%= int %> and Perception by <%= per %>. Limited Edition 2014 Summer Gear.",
+ "weaponSpecialSummerMageNotes": "Den här treudden används för att spetsa sjögräs effektivt, för en extra produktiv tångskörd! Ökar Intelligens med <%= int %> och Uppmärksamhet med <%= per %>. Begränsad utgåva Vårutrustning. 2014.",
"weaponSpecialSummerHealerText": "Skuggornas trollstav",
"weaponSpecialSummerHealerNotes": "This wand, made of aquamarine and live coral, is very attractive to schools of fish. Increases Intelligence by <%= int %>. Limited Edition 2014 Summer Gear.",
"weaponSpecialFallRogueText": "Silverpåle",
@@ -104,7 +104,7 @@
"weaponSpecialFallHealerText": "Skarabéstav",
"weaponSpecialFallHealerNotes": "Skarabén på denna stav skyddar och helar dess bärare. Ökar intelligens med <%= int %>. Begränsad utgåva 2014 höstutrustning.",
"weaponSpecialWinter2015RogueText": "Ispik",
- "weaponSpecialWinter2015RogueNotes": "You truly, definitely, absolutely just picked these up off of the ground. Increases Strength by <%= str %>. Limited Edition 2014-2015 Winter Gear.",
+ "weaponSpecialWinter2015RogueNotes": "Do plockade verkligen, absolut precis upp dessa från marken. Ökar styrka med <%=str %> Begränsad utgåva 2014-2015 Vinterutrustning.",
"weaponSpecialWinter2015WarriorText": "Geléhallonsvärd",
"weaponSpecialWinter2015WarriorNotes": "This delicious sword probably attracts monsters... but you're up for the challenge! Increases Strength by <%= str %>. Limited Edition 2014-2015 Winter Gear.",
"weaponSpecialWinter2015MageText": "Vinterupplyst stav",
@@ -112,34 +112,34 @@
"weaponSpecialWinter2015HealerText": "Lugnande spira",
"weaponSpecialWinter2015HealerNotes": "Den här spiran värmer ömma muskler och minskar stress. Ökar intelligens med <%= int %>. Begränsad utgåva 2014-2015 Vinterutrustning.",
"weaponSpecialSpring2015RogueText": "Exploderande pipleksak",
- "weaponSpecialSpring2015RogueNotes": "Don't let the sound fool you - these explosives pack a punch. Increases Strength by <%= str %>. Limited Edition 2015 Spring Gear.",
+ "weaponSpecialSpring2015RogueNotes": "Låt inte ljudet lura dig - dessa bomber är starkare än man tror. Ökar styrka med <%=str %>. Begränsad utgåva 2015 Vårutrustning",
"weaponSpecialSpring2015WarriorText": "Benklubba",
"weaponSpecialSpring2015WarriorNotes": "It is a real bone club for real fierce doggies and is definitely not a chew toy that the Seasonal Sorceress gave you because who's a good doggy? Whoooo's a good doggy?? It's you!!! You're a good doggy!!! Increases Strength by <%= str %>. Limited Edition 2015 Spring Gear.",
"weaponSpecialSpring2015MageText": "Magikerns stav",
"weaponSpecialSpring2015MageNotes": "Conjure up a carrot for yourself with this fancy wand. Increases Intelligence by <%= int %> and Perception by <%= per %>. Limited Edition 2015 Spring Gear.",
"weaponSpecialSpring2015HealerText": "Kattskallra",
"weaponSpecialSpring2015HealerNotes": "Når du skakar den har den ett fascinerande clickande ljud som skulle kunna hålla VEM SOM HELST underhållen i timtal. Ökar intelligens med <%= int %>. Begränsad vår utgåva 2015 av vår utrustning.",
- "weaponSpecialSummer2015RogueText": "Firing Coral",
+ "weaponSpecialSummer2015RogueText": "Brinnande Korall",
"weaponSpecialSummer2015RogueNotes": "This relative of fire coral has the ability to propel its venom through the water. Increases Strength by <%= str %>. Limited Edition 2015 Summer Gear.",
- "weaponSpecialSummer2015WarriorText": "Sun Swordfish",
+ "weaponSpecialSummer2015WarriorText": "Solens Svärdfisk",
"weaponSpecialSummer2015WarriorNotes": "The Sun Swordfish is a fearsome weapon, provided that it can be induced to stop wriggling. Increases Strength by <%= str %>. Limited Edition 2015 Summer Gear.",
"weaponSpecialSummer2015MageText": "Soothsayer Staff",
"weaponSpecialSummer2015MageNotes": "Hidden power glimmers in the jewels of this staff. Increases Intelligence by <%= int %> and Perception by <%= per %>. Limited Edition 2015 Summer Gear.",
- "weaponSpecialSummer2015HealerText": "Wand of the Waves",
+ "weaponSpecialSummer2015HealerText": "Vågornas trollstav",
"weaponSpecialSummer2015HealerNotes": "Cures seasickness and sea sickness! Increases Intelligence by <%= int %>. Limited Edition 2015 Summer Gear.",
- "weaponSpecialFall2015RogueText": "Bat-tle Ax",
+ "weaponSpecialFall2015RogueText": "Fladdermyxa",
"weaponSpecialFall2015RogueNotes": "Fearsome To-Dos cower before the flapping of this ax. Increases Strength by <%= str %>. Limited Edition 2015 Autumn Gear.",
"weaponSpecialFall2015WarriorText": "Träplanka",
"weaponSpecialFall2015WarriorNotes": "Great for elevating things in cornfields and/or smacking tasks. Increases Strength by <%= str %>. Limited Edition 2015 Autumn Gear.",
"weaponSpecialFall2015MageText": "Förtrollad tråd",
"weaponSpecialFall2015MageNotes": "A powerful Stitch Witch can control this enchanted thread without even touching it! Increases Intelligence by <%= int %> and Perception by <%= per %>. Limited Edition 2015 Autumn Gear.",
- "weaponSpecialFall2015HealerText": "Swamp-Slime Potion",
+ "weaponSpecialFall2015HealerText": "Träskslems-dryck",
"weaponSpecialFall2015HealerNotes": "Brewed to perfection! Now you just have to convince yourself to drink it. Increases Intelligence by <%= int %>. Limited Edition 2015 Autumn Gear.",
- "weaponSpecialWinter2016RogueText": "Cocoa Mug",
+ "weaponSpecialWinter2016RogueText": "En kopp varm choklad",
"weaponSpecialWinter2016RogueNotes": "Warming drink, or boiling projectile? You decide... Increases Strength by <%= str %>. Limited Edition 2015-2016 Winter Gear.",
- "weaponSpecialWinter2016WarriorText": "Sturdy Shovel",
+ "weaponSpecialWinter2016WarriorText": "Stadig Spade",
"weaponSpecialWinter2016WarriorNotes": "Shovel overdue tasks out of the way! Increases Strength by <%= str %>. Limited Edition 2015-2016 Winter Gear.",
- "weaponSpecialWinter2016MageText": "Sorcerous Snowboard",
+ "weaponSpecialWinter2016MageText": "Magisk Snowboard",
"weaponSpecialWinter2016MageNotes": "Your moves are so sick, they must be magic! Increases Intelligence by <%= int %> and Perception by <%= per %>. Limited Edition 2015-2016 Winter Gear.",
"weaponSpecialWinter2016HealerText": "Konfettikanon",
"weaponSpecialWinter2016HealerNotes": "WHEEEEEEEEEE!!!!!!! HAPPY WINTER WONDERLAND!!!!!!!! Increases Intelligence by <%= int %>. Limited Edition 2015-2016 Winter Gear.",
@@ -147,9 +147,9 @@
"weaponSpecialSpring2016RogueNotes": "You've mastered the ball, the club, and the knife. Now you advance to juggling fire! Awoo! Increases Strength by <%= str %>. Limited Edition 2016 Spring Gear.",
"weaponSpecialSpring2016WarriorText": "Cheese Mallet",
"weaponSpecialSpring2016WarriorNotes": "No one has as many friends as the mouse with tender cheeses. Increases Strength by <%= str %>. Limited Edition 2016 Spring Gear.",
- "weaponSpecialSpring2016MageText": "Staff of Bells",
+ "weaponSpecialSpring2016MageText": "Klangerstav",
"weaponSpecialSpring2016MageNotes": "Abra-cat-abra! So dazzling, you might mesmerize yourself! Ooh... it jingles... Increases Intelligence by <%= int %> and Perception by <%= per %>. Limited Edition 2016 Spring Gear.",
- "weaponSpecialSpring2016HealerText": "Spring Flower Wand",
+ "weaponSpecialSpring2016HealerText": "Vårblommetrollstav ",
"weaponSpecialSpring2016HealerNotes": "With a wave and a wink, you bring the fields and forests into bloom! Or bop troublesome mice on the head. Increases Intelligence by <%= int %>. Limited Edition 2016 Spring Gear.",
"weaponMystery201411Text": "Måltidernas högaffel",
"weaponMystery201411Notes": "Stab your enemies or dig in to your favorite foods - this versatile pitchfork does it all! Confers no benefit. November 2014 Subscriber Item.",
@@ -159,7 +159,7 @@
"weaponMystery201505Notes": "This green and silver lance has unseated many opponents from their mounts. Confers no benefit. May 2015 Subscriber Item.",
"weaponMystery301404Text": "Steampunk-käpp.",
"weaponMystery301404Notes": "Excellent for taking a turn about town. March 3015 Subscriber Item. Confers no benefit.",
- "weaponArmoireBasicCrossbowText": "Basic Crossbow",
+ "weaponArmoireBasicCrossbowText": "Grundläggande armborst",
"weaponArmoireBasicCrossbowNotes": "This crossbow can pierce a task's armor from very far away! Increases Strength by <%= str %>, Perception by <%= per %>, and Constitution by <%= con %>. Enchanted Armoire: Independent Item.",
"weaponArmoireLunarSceptreText": "Soothing Lunar Sceptre",
"weaponArmoireLunarSceptreNotes": "The healing power of this wand waxes and wanes. Increases Constitution by <%= con %> and Intelligence by <%= int %>. Enchanted Armoire: Soothing Lunar Set (Item 3 of 3).",
@@ -171,15 +171,15 @@
"weaponArmoireIronCrookNotes": "Fiercely hammered from iron, this iron crook is good at herding sheep. Increases Perception and Strength by <%= attrs %> each. Enchanted Armoire: Horned Iron Set (Item 3 of 3).",
"weaponArmoireGoldWingStaffText": "Gold Wing Staff",
"weaponArmoireGoldWingStaffNotes": "The wings on this staff constantly flutter and twist. Increases all attributes by <%= attrs %> each. Enchanted Armoire: Independent Item.",
- "weaponArmoireBatWandText": "Bat Wand",
+ "weaponArmoireBatWandText": "Fladdermus-spö",
"weaponArmoireBatWandNotes": "This wand can turn any task into a bat! Wave it about and watch them fly away. Increases Intelligence by <%= int %> and Perception by <%= per %>. Enchanted Armoire: Independent Item.",
"weaponArmoireShepherdsCrookText": "Shepherd's Crook",
"weaponArmoireShepherdsCrookNotes": "Useful for herding gryphons. Increases Constitution by <%= con %>. Enchanted Armoire: Shepherd Set (Item 1 of 3).",
- "weaponArmoireCrystalCrescentStaffText": "Crystal Crescent Staff",
+ "weaponArmoireCrystalCrescentStaffText": "Kristallhalvmånens stav ",
"weaponArmoireCrystalCrescentStaffNotes": "Summon the power of the crescent moon with this shining staff! Increases Intelligence and Strength by <%= attrs %> each. Enchanted Armoire: Crystal Crescent Set (Item 3 of 3).",
- "weaponArmoireBlueLongbowText": "Blue Longbow",
+ "weaponArmoireBlueLongbowText": "Blå långbåge",
"weaponArmoireBlueLongbowNotes": "Ready... Aim... Fire! This bow has great range. Increases Perception by <%= per %>, Constitution by <%= con %>, and Strength by <%= str %>. Enchanted Armoire: Independent Item.",
- "weaponArmoireGlowingSpearText": "Glowing Spear",
+ "weaponArmoireGlowingSpearText": "Lysande Spjut",
"weaponArmoireGlowingSpearNotes": "This spear hypnotizes wild tasks so you can attack them. Increases Strength by <%= str %>. Enchanted Armoire: Independent Item.",
"weaponArmoireBarristerGavelText": "Barrister Gavel",
"weaponArmoireBarristerGavelNotes": "Order! Increases Strength and Constitution by <%= attrs %> each. Enchanted Armoire: Barrister Set (Item 3 of 3).",
@@ -187,8 +187,10 @@
"weaponArmoireJesterBatonNotes": "With a wave of your baton and some witty repartee, even the most complicated situations become clear. Increases Intelligence and Perception by <%= attrs %> each. Enchanted Armoire: Jester Set (Item 3 of 3).",
"weaponArmoireMiningPickaxText": "Mining Pickax",
"weaponArmoireMiningPickaxNotes": "Mine the maximum amount of gold from your tasks! Increases Perception by <%= per %>. Enchanted Armoire: Miner Set (Item 3 of 3).",
- "weaponArmoireBasicLongbowText": "Basic Longbow",
+ "weaponArmoireBasicLongbowText": "Enkel långbåge ",
"weaponArmoireBasicLongbowNotes": "A serviceable hand-me-down bow. Increases Strength by <%= str %>. Enchanted Armoire: Basic Archer Set (Item 1 of 3).",
+ "weaponArmoireHabiticanDiplomaText": "Habitican Diploma",
+ "weaponArmoireHabiticanDiplomaNotes": "A certificate of significant achievement -- well done! Increases Intelligence by <%= int %>. Enchanted Armoire: Graduate Set (Item 1 of 3).",
"armor": "utrustning",
"armorBase0Text": "Vanliga kläder",
"armorBase0Notes": "Vanliga kläder. Har ingen effekt.",
@@ -252,7 +254,7 @@
"armorSpecialBirthdayNotes": "Grattis på födelsedagen, Habitica! Bär dessa Absurda Festdräkter för att fira denna underbara dag. Ger ingen fördel.",
"armorSpecialBirthday2015Text": "Löjliga partyrockar",
"armorSpecialBirthday2015Notes": "Grattis på födelsedagen, Habitica! Bär dessa Fåniga Festdräkter för att fira denna underbara dag. Ger ingen fördel.",
- "armorSpecialBirthday2016Text": "Ridiculous Party Robes",
+ "armorSpecialBirthday2016Text": "Fåniga Partydräkter",
"armorSpecialBirthday2016Notes": "Happy Birthday, Habitica! Wear these Ridiculous Party Robes to celebrate this wonderful day. Confers no benefit.",
"armorSpecialGaymerxText": "Regnbågsfärgad krigarutrustning",
"armorSpecialGaymerxNotes": "In celebration of the GaymerX Conference, this special armor is decorated with a radiant, colorful rainbow pattern! GaymerX is a game convention celebrating LGTBQ and gaming and is open to everyone.",
@@ -312,9 +314,9 @@
"armorSpecialFall2015MageNotes": "Every stitch in this armor shimmers with enchantment. Increases Intelligence by <%= int %>. Limited Edition 2015 Autumn Gear.",
"armorSpecialFall2015HealerText": "Potioner Robes",
"armorSpecialFall2015HealerNotes": "What? Of course that was a potion of constitution. No, you are definitely not turning into a frog! Don't be ribbiticulous. Increases Constitution by <%= con %>. Limited Edition 2015 Autumn Gear.",
- "armorSpecialWinter2016RogueText": "Cocoa Armor",
+ "armorSpecialWinter2016RogueText": "Kakaorustning",
"armorSpecialWinter2016RogueNotes": "This leather armor keeps you nice and toasty. Is it actually made from cocoa? You'll never tell. Increases Perception by <%= per %>. Limited Edition 2015-2016 Winter Gear.",
- "armorSpecialWinter2016WarriorText": "Snowman Suit",
+ "armorSpecialWinter2016WarriorText": "Snögubbedräkt",
"armorSpecialWinter2016WarriorNotes": "Brr! This padded armor is truly powerful... until it melts. Increases Constitution by <%= con %>. Limited Edition 2015-2016 Winter Gear.",
"armorSpecialWinter2016MageText": "Snowboarder Parka",
"armorSpecialWinter2016MageNotes": "The wisest wizard keeps well-bundled in the winter wind. Increases Intelligence by <%= int %>. Limited Edition 2015-2016 Winter Gear.",
@@ -341,7 +343,7 @@
"armorMystery201408Text": "Solrock",
"armorMystery201408Notes": "These robes are woven with sunlight and gold. Confers no benefit. August 2014 Subscriber Item.",
"armorMystery201409Text": "Strider Vest",
- "armorMystery201409Notes": "A leaf-covered vest that camouflages the wearer. Confers no benefit. September 2014 Subscriber Item.",
+ "armorMystery201409Notes": "En lövtäckt väst som kamouflerar sin bärare. Ger ingen fördel. September 2014 Prenumerantsartikel.",
"armorMystery201410Text": "Trollutrustning",
"armorMystery201410Notes": "Scaly, slimy, and strong! Confers no benefit. October 2014 Subscriber Item.",
"armorMystery201412Text": "Pingvindräkt",
@@ -352,7 +354,7 @@
"armorMystery201503Notes": "This blue mineral symbolizes good luck, happiness, and eternal productivity. Confers no benefit. March 2015 Subscriber Item.",
"armorMystery201504Text": "Busy Bee Robe",
"armorMystery201504Notes": "You'll be productive as a busy bee in this fetching robe! Confers no benefit. April 2015 Subscriber Item.",
- "armorMystery201506Text": "Snorkel Suit",
+ "armorMystery201506Text": "Snorkeldräkt",
"armorMystery201506Notes": "Snorkel through a coral reef in this brightly-colored swim suit! Confers no benefit. June 2015 Subscriber Item.",
"armorMystery201508Text": "Cheetah Costume",
"armorMystery201508Notes": "Run fast as a flash in the fluffy Cheetah Costume! Confers no benefit. August 2015 Subscriber Item.",
@@ -360,10 +362,14 @@
"armorMystery201509Notes": "This IS a costume, right? Confers no benefit. September 2015 Subscriber Item.",
"armorMystery201511Text": "Wooden Armor",
"armorMystery201511Notes": "Considering this armor was carved directly from a magical log, it's surprisingly comfortable. Confers no benefit. November 2015 Subscriber Item.",
- "armorMystery201512Text": "Cold Fire Armor",
+ "armorMystery201512Text": "Kall eld-rustning",
"armorMystery201512Notes": "Summon the icy flames of winter! Confers no benefit. December 2015 Subscriber Item.",
- "armorMystery201603Text": "Lucky Suit",
+ "armorMystery201603Text": "Turdräkt",
"armorMystery201603Notes": "This suit is sewn from thousands of four-leafed clovers! Confers no benefit. March 2016 Subscriber Item.",
+ "armorMystery201604Text": "Armor o' Leaves",
+ "armorMystery201604Notes": "You, too, can be a small but fearsome leaf puff. Confers no benefit. April 2016 Subscriber Item.",
+ "armorMystery201605Text": "Marching Bard Uniform",
+ "armorMystery201605Notes": "Unlike the traditional bards who join adventuring parties, bards who join Habitican marching bands are known for grand parades, not dungeon raids. Confers no benefit. May 2016 Subscriber Item.",
"armorMystery301404Text": "Steampunk-dräkt",
"armorMystery301404Notes": "Dapper and dashing, wot! Confers no benefit. February 3015 Subscriber Item.",
"armorArmoireLunarArmorText": "Soothing Lunar Armor",
@@ -394,6 +400,8 @@
"armorArmoireMinerOverallsNotes": "They may seem worn, but they are enchanted to repel dirt. Increases Constitution by <%= con %>. Enchanted Armoire: Miner Set (Item 2 of 3).",
"armorArmoireBasicArcherArmorText": "Basic Archer Armor",
"armorArmoireBasicArcherArmorNotes": "This camouflaged vest lets you slip unnoticed through the forests. Increases Perception by <%= per %>. Enchanted Armoire: Basic Archer Set (Item 2 of 3).",
+ "armorArmoireGraduateRobeText": "Graduate Robe",
+ "armorArmoireGraduateRobeNotes": "Congratulations! This weighty robe hangs heavy with all the knowledge you have accrued. Increases Intelligence by <%= int %>. Enchanted Armoire: Graduate Set (Item 2 of 3).",
"headgear": "huvudbonader",
"headBase0Text": "Ingen hjälm",
"headBase0Notes": "Ingen huvudbonad.",
@@ -447,7 +455,7 @@
"headSpecialFireCoralCircletNotes": "This circlet, designed by Habitica's greatest alchemists, allows you to breathe water and dive for treasure! Increases Perception by <%= per %>.",
"headSpecialNyeText": "Absurd partyhatt",
"headSpecialNyeNotes": "You've received an Absurd Party Hat! Wear it with pride while ringing in the New Year! Confers no benefit.",
- "headSpecialYetiText": "Yeti-Tamer Helm",
+ "headSpecialYetiText": "Yetitämjarens hjälm",
"headSpecialYetiNotes": "An adorably fearsome hat. Increases Strength by <%= str %>. Limited Edition 2013-2014 Winter Gear.",
"headSpecialSkiText": "Ski-sassin Helm",
"headSpecialSkiNotes": "Keeps the wearer's identity secret... and their face toasty. Increases Perception by <%= per %>. Limited Edition 2013-2014 Winter Gear.",
@@ -515,7 +523,7 @@
"headSpecialFall2015HealerNotes": "This is an extremely serious hat that is worthy of only the most advanced potioners. Increases Intelligence by <%= int %>. Limited Edition 2015 Autumn Gear.",
"headSpecialNye2015Text": "Ridiculous Party Hat",
"headSpecialNye2015Notes": "You've received a Ridiculous Party Hat! Wear it with pride while ringing in the New Year! Confers no benefit.",
- "headSpecialWinter2016RogueText": "Cocoa Helm",
+ "headSpecialWinter2016RogueText": "Kakaohjälm",
"headSpecialWinter2016RogueNotes": "The protective scarf on this cozy helm is only removed to sip warm winter beverages. Increases Perception by <%= per %>. Limited Edition 2015-2016 Winter Gear.",
"headSpecialWinter2016WarriorText": "Snowman Cap",
"headSpecialWinter2016WarriorNotes": "Brr! This mighty helm is truly powerful... until it melts. Increases Strength by <%= str %>. Limited Edition 2015-2016 Winter Gear.",
@@ -546,7 +554,7 @@
"headMystery201411Text": "Steel Helm of Sporting",
"headMystery201411Notes": "This is the traditional helmet worn in the beloved Habitican sport of Balance Ball, which consists of covering yourself with heavy protective gear and then committing to a healthy work-life balance..... WHILE PURSUED BY HIPPOGRIFFS. Confers no benefit. November 2014 Subscriber Item.",
"headMystery201412Text": "Pingvinhatt",
- "headMystery201412Notes": "Who's a penguin? Confers no benefit. December 2014 Subscriber Item.",
+ "headMystery201412Notes": "Vem är en pingvin? Ger ingen fördel. December 2014 prenumerantobjekt.",
"headMystery201501Text": "Tindrande hjälm",
"headMystery201501Notes": "The constellations flicker and swirl in this helm, guiding the wearer's thoughts towards focus. Confers no benefit. January 2015 Subscriber Item.",
"headMystery201505Text": "Grön Riddar Hjälm",
@@ -565,6 +573,10 @@
"headMystery201602Notes": "Shield your identity from all your admirers. Confers no benefit. February 2016 Subscriber Item.",
"headMystery201603Text": "Lucky Hat",
"headMystery201603Notes": "This top hat is a magical good-luck charm. Confers no benefit. March 2016 Subscriber Item.",
+ "headMystery201604Text": "Crown o' Flowers",
+ "headMystery201604Notes": "These woven flowers make a surprisingly strong helm! Confers no benefit. April 2016 Subscriber Item.",
+ "headMystery201605Text": "Marching Bard Hat",
+ "headMystery201605Notes": "Seventy-six dragons led the big parade, with a hundred and ten gryphons close at hand! Confers no benefit. May 2016 Subscriber Item.",
"headMystery301404Text": "Stilig cylinderhatt",
"headMystery301404Notes": "A fancy top hat for the finest of gentlefolk! January 3015 Subscriber Item. Confers no benefit.",
"headMystery301405Text": "Vanlig cylinderhatt",
@@ -585,13 +597,13 @@
"headArmoireRoyalCrownNotes": "Hooray for the ruler, mighty and strong! Increases Strength by <%= str %>. Enchanted Armoire: Royal Set (Item 1 of 3).",
"headArmoireGoldenLaurelsText": "Golden Laurels",
"headArmoireGoldenLaurelsNotes": "These golden laurels reward those who have conquered bad habits. Increases Perception and Constitution by <%= attrs %> each. Enchanted Armoire: Golden Toga Set (Item 2 of 3).",
- "headArmoireHornedIronHelmText": "Horned Iron Helm",
+ "headArmoireHornedIronHelmText": "Hornklädd Järnhjälm",
"headArmoireHornedIronHelmNotes": "Fiercely hammered from iron, this horned helmet is nearly impossible to break. Increases Constitution by <%= con %> and Strength by <%= str %>. Enchanted Armoire: Horned Iron Set (Item 1 of 3).",
"headArmoireYellowHairbowText": "Yellow Hairbow",
"headArmoireYellowHairbowNotes": "Become perceptive, strong, and smart while wearing this beautiful Yellow Hairbow! Increases Perception, Strength, and Intelligence by <%= attrs %> each. Enchanted Armoire: Independent Item.",
"headArmoireRedFloppyHatText": "Red Floppy Hat",
"headArmoireRedFloppyHatNotes": "Many spells have been sewn into this simple hat, giving it a radiant red color. Increases Constitution, Intelligence, and Perception by <%= attrs %> each. Enchanted Armoire: Independent Item.",
- "headArmoirePlagueDoctorHatText": "Plague Doctor Hat",
+ "headArmoirePlagueDoctorHatText": "Pestdoktorns Hatt",
"headArmoirePlagueDoctorHatNotes": "An authentic hat worn by the doctors who battle the Plague of Procrastination! Increases Strength by <%= str %>, Intelligence by <%= int %>, and Constitution by <%= con %>. Enchanted Armoire: Plague Doctor Set (Item 1 of 3).",
"headArmoireBlackCatText": "Black Cat Hat",
"headArmoireBlackCatNotes": "This black hat is... purring. And twitching its tail. And breathing? Yeah, you just have a sleeping cat on your head. Increases Intelligence and Perception by <%= attrs %> each. Enchanted Armoire: Independent Item.",
@@ -613,6 +625,8 @@
"headArmoireMinerHelmetNotes": "Protect your head from falling tasks! Increases Intelligence by <%= int %>. Enchanted Armoire: Miner Set (Item 1 of 3).",
"headArmoireBasicArcherCapText": "Basic Archer Cap",
"headArmoireBasicArcherCapNotes": "No archer would be complete without a jaunty cap! Increases Perception by <%= per %>. Enchanted Armoire: Basic Archer Set (Item 3 of 3).",
+ "headArmoireGraduateCapText": "Graduate Cap",
+ "headArmoireGraduateCapNotes": "Congratulations! Your deep thoughts have earned you this thinking cap. Increases Intelligence by <%= int %>. Enchanted Armoire: Graduate Set (Item 3 of 3).",
"offhand": "shield-hand item",
"shieldBase0Text": "No Shield-Hand Equipment",
"shieldBase0Notes": "Ingen sköld eller sekundärt vapen.",
@@ -658,7 +672,7 @@
"shieldSpecialSummerRogueNotes": "Avast! You'll make those Dailies walk the plank! Increases Strength by <%= str %>. Limited Edition 2014 Summer Gear.",
"shieldSpecialSummerWarriorText": "Drivvedssköld",
"shieldSpecialSummerWarriorNotes": "This shield, made from the wood of wrecked ships, can deter even the stormiest Dailies. Increases Constitution by <%= con %>. Limited Edition 2014 Summer Gear.",
- "shieldSpecialSummerHealerText": "Shield of the Shallows",
+ "shieldSpecialSummerHealerText": "Skuggornas Sköld",
"shieldSpecialSummerHealerNotes": "No one will dare to attack the coral reef when faced with this shiny shield! Increases Constitution by <%= con %>. Limited Edition 2014 Summer Gear.",
"shieldSpecialFallRogueText": "Silverpål",
"shieldSpecialFallRogueNotes": "Dispatches undead. Also grants a bonus against werewolves, because you can never be too careful. Increases Strength by <%= str %>. Limited Edition 2014 Autumn Gear.",
@@ -673,7 +687,7 @@
"shieldSpecialWinter2015HealerText": "Soothing Shield",
"shieldSpecialWinter2015HealerNotes": "This shield deflects the freezing wind. Increases Constitution by <%= con %>. Limited Edition 2014-2015 Winter Gear.",
"shieldSpecialSpring2015RogueText": "Exploderande pipleksak",
- "shieldSpecialSpring2015RogueNotes": "Don't let the sound fool you - these explosives pack a punch. Increases Strength <%= str %>. Limited Edition 2015 Spring Gear.",
+ "shieldSpecialSpring2015RogueNotes": "Don't let the sound fool you - these explosives pack a punch. Increases Strength by <%= str %>. Limited Edition 2015 Spring Gear.",
"shieldSpecialSpring2015WarriorText": "Dish Discus",
"shieldSpecialSpring2015WarriorNotes": "Kasta den mot dina fiender... eller håll bara i den, för den kommer att fyllas med mumsigt torrfoder vid middagstid. Ökar Tålighet med <%= con %>. Begränsad utgåva 2015 vårutrustning.",
"shieldSpecialSpring2015HealerText": "Mönstrad kudde",
@@ -690,7 +704,7 @@
"shieldSpecialFall2015WarriorNotes": "It's true that you're supposed to be SCARING the crows, but there's nothing wrong with making friends! Increases Constitution by <%= con %>. Limited Edition 2015 Autumn Gear.",
"shieldSpecialFall2015HealerText": "Stirring Stick",
"shieldSpecialFall2015HealerNotes": "This stick can stir anything without melting, dissolving, or bursting into flame! It can also be used to fiercely poke enemy tasks. Increases Constitution by <%= con %>. Limited Edition 2015 Autumn Gear.",
- "shieldSpecialWinter2016RogueText": "Cocoa Mug",
+ "shieldSpecialWinter2016RogueText": "Kakaomugg",
"shieldSpecialWinter2016RogueNotes": "Warming drink, or boiling projectile? You decide... Increases Strength by <%= str %>. Limited Edition 2015-2016 Winter Gear.",
"shieldSpecialWinter2016WarriorText": "Sled Shield",
"shieldSpecialWinter2016WarriorNotes": "Use this sled to block attacks, or ride it triumphantly into battle! Increases Constitution by <%= con %>. Limited Edition 2015-2016 Winter Gear.",
@@ -698,7 +712,7 @@
"shieldSpecialWinter2016HealerNotes": "Open it open it open it open it open it open it!!!!!!!!! Increases Constitution by <%= con %>. Limited Edition 2015-2016 Winter Gear.",
"shieldSpecialSpring2016RogueText": "Fire Bolas",
"shieldSpecialSpring2016RogueNotes": "You've mastered the ball, the club, and the knife. Now you advance to juggling fire! Awoo! Increases Strength <%= str %>. Limited Edition 2016 Spring Gear.",
- "shieldSpecialSpring2016WarriorText": "Cheese Wheel",
+ "shieldSpecialSpring2016WarriorText": "Osthjul",
"shieldSpecialSpring2016WarriorNotes": "You braved fiendish traps to procure this defense-boosting food. Increases Constitution by <%= con %>. Limited Edition 2016 Spring Gear.",
"shieldSpecialSpring2016HealerText": "Floral Buckler",
"shieldSpecialSpring2016HealerNotes": "The April Fool claims this little shield will block Shiny Seeds. Don't believe him. Increases Constitution by <%= con %>. Limited Edition 2016 Spring Gear.",
@@ -706,16 +720,18 @@
"shieldMystery201601Notes": "This blade can be used to parry away all distractions. Confers no benefit. January 2016 Subscriber Item.",
"shieldMystery301405Text": "Klocksköld",
"shieldMystery301405Notes": "Time is on your side with this towering clock shield! Confers no benefit. June 3015 Subscriber Item.",
- "shieldArmoireGladiatorShieldText": "Gladiator Shield",
+ "shieldArmoireGladiatorShieldText": "Gladiatorsköld",
"shieldArmoireGladiatorShieldNotes": "To be a gladiator you must.... eh, whatever, just bash them with your shield. Increases Constitution by <%= con %> and Strength by <%= str %>. Enchanted Armoire: Gladiator Set (Item 3 of 3).",
"shieldArmoireMidnightShieldText": "Midnight Shield",
"shieldArmoireMidnightShieldNotes": "This shield is most powerful at the stroke of midnight! Increases Constitution by <%= con %> and Strength by <%= str %>. Enchanted Armoire: Independent Item.",
"shieldArmoireRoyalCaneText": "Royal Cane",
"shieldArmoireRoyalCaneNotes": "Hooray for the ruler, worthy of song! Increases Constitution, Intelligence, and Perception by <%= attrs %> each. Enchanted Armoire: Royal Set (Item 2 of 3).",
- "shieldArmoireDragonTamerShieldText": "Dragon Tamer Shield",
+ "shieldArmoireDragonTamerShieldText": "Draktämjarsköld",
"shieldArmoireDragonTamerShieldNotes": "Distract enemies with this dragon-shaped shield. Increases Perception by <%= per %>. Enchanted Armoire: Dragon Tamer Set (Item 2 of 3).",
- "shieldArmoireMysticLampText": "Mystic Lamp",
+ "shieldArmoireMysticLampText": "Mystisk Lampa",
"shieldArmoireMysticLampNotes": "Light the darkest caves with this mystic lamp! Increases Perception by <%= per %>. Enchanted Armoire: Independent Item.",
+ "shieldArmoireFloralBouquetText": "Bouquet o' Flowers",
+ "shieldArmoireFloralBouquetNotes": "Not much help in battle, but aren't they beautiful? Increases Constitution by <%= con %>. Enchanted Armoire: Independent Item.",
"back": "Back Accessory",
"backBase0Text": "Inget ryggtillbehör",
"backBase0Notes": "Inget ryggtillbehör.",
@@ -725,9 +741,9 @@
"backMystery201404Notes": "Var en fjäril och fladdra förbi! Ger ingen fördel. April 2014 prenumerantobjekt.",
"backMystery201410Text": "Trollvingar",
"backMystery201410Notes": "Swoop through the night on these strong wings. Confers no benefit. October 2014 Subscriber Item.",
- "backMystery201504Text": "Busy Bee Wings",
+ "backMystery201504Text": "Hektiska Bivingar",
"backMystery201504Notes": "Buzz buzz buzz! Flit from task to task. Confers no benefit. April 2015 Subscriber Item.",
- "backMystery201507Text": "Rad Surfboard",
+ "backMystery201507Text": "Tuff Surfbräda",
"backMystery201507Notes": "Surf off the Diligent Docks and ride the waves in Inkomplete Bay! Confers no benefit. July 2015 Subscriber Item.",
"backMystery201510Text": "Goblin Tail",
"backMystery201510Notes": "Prehensile and powerful! Confers no benefit. October 2015 Subscriber Item.",
@@ -737,9 +753,9 @@
"backSpecialWonderconRedNotes": "Swishes with strength and beauty. Confers no benefit. Special Edition Convention Item.",
"backSpecialWonderconBlackText": "Lömsk mantel",
"backSpecialWonderconBlackNotes": "Spun of shadows and whispers. Confers no benefit. Special Edition Convention Item.",
- "body": "Body Accessory",
- "bodyBase0Text": "No Body Accessory",
- "bodyBase0Notes": "No Body Accessory.",
+ "body": "Kroppstillbehör",
+ "bodyBase0Text": "Inget kroppstillbehör",
+ "bodyBase0Notes": "Inget kroppstillbehör.",
"bodySpecialWonderconRedText": "Rubinkrage",
"bodySpecialWonderconRedNotes": "An attractive ruby collar! Confers no benefit. Special Edition Convention Item.",
"bodySpecialWonderconGoldText": "Guldkrage",
@@ -779,16 +795,16 @@
"headAccessorySpecialSpring2015MageNotes": "De här öronen lyssnar ivrigt ifall någon magiker skulle avslöjar hemligheter någonstans. Ger ingen fördel. Begränsad utgåva 2015 vår utrustning.",
"headAccessorySpecialSpring2015HealerText": "Gröna kattöron",
"headAccessorySpecialSpring2015HealerNotes": "De här kattunge-öronen kommer göra andra gröna av avund! Ger ingen fördel. Begränsad utgåva 2015 vår utrustning.",
- "headAccessorySpecialSpring2016RogueText": "Green Dog Ears",
+ "headAccessorySpecialSpring2016RogueText": "Gröna Hundöron",
"headAccessorySpecialSpring2016RogueNotes": "With these, you can keep track of tricky Mages even if they turn invisible! Confers no benefit. Limited Edition 2016 Spring Gear.",
- "headAccessorySpecialSpring2016WarriorText": "Red Mouse Ears",
+ "headAccessorySpecialSpring2016WarriorText": "Röda Musöron",
"headAccessorySpecialSpring2016WarriorNotes": "To better hear your theme song across clamorous battlefields. Confers no benefit. Limited Edition 2016 Spring Gear.",
- "headAccessorySpecialSpring2016MageText": "Yellow Cat Ears",
+ "headAccessorySpecialSpring2016MageText": "Gula Kattöron",
"headAccessorySpecialSpring2016MageNotes": "These sharp ears can detect the minute hum of ambient Mana, or the muted footfalls of a Rogue. Confers no benefit. Limited Edition 2016 Spring Gear.",
- "headAccessorySpecialSpring2016HealerText": "Purple Bunny Ears",
+ "headAccessorySpecialSpring2016HealerText": "Lila Kaninöron",
"headAccessorySpecialSpring2016HealerNotes": "They stand like flags above the fray, letting others know where to run for help. Confers no benefit. Limited Edition 2016 Spring Gear.",
"headAccessoryBearEarsText": "Björn öron",
- "headAccessoryBearEarsNotes": "These ears make you look like a brave bear! Confers no benefit.",
+ "headAccessoryBearEarsNotes": "De här öronen får dig att se ut som en modig björn! Ger ingen fördel.",
"headAccessoryCactusEarsText": "Kaktus öron",
"headAccessoryCactusEarsNotes": "De här öronen får dig att se ut som en stickig kaktus! Ger ingen fördel.",
"headAccessoryFoxEarsText": "Räv öron",
@@ -815,14 +831,28 @@
"headAccessoryMystery201510Notes": "These fearsome horns are slightly slimy. Confers no benefit. October 2015 Subscriber Item.",
"headAccessoryMystery301405Text": "Headwear Goggles",
"headAccessoryMystery301405Notes": "\"Goggles are for your eyes,\" they said. \"Nobody wants goggles that you can only wear on your head,\" they said. Hah! You sure showed them! Confers no benefit. August 3015 Subscriber Item.",
- "headAccessoryArmoireComicalArrowText": "Comical Arrow",
+ "headAccessoryArmoireComicalArrowText": "Komisk Pil",
"headAccessoryArmoireComicalArrowNotes": "This whimsical item doesn't provide a stat boost, but it sure is good for a laugh! Confers no benefit. Enchanted Armoire: Independent Item.",
- "eyewear": "Eyewear",
+ "eyewear": "Ögonskydd",
"eyewearBase0Text": "Inget ögonskydd",
"eyewearBase0Notes": "Inget ögonskydd.",
+ "eyewearSpecialBlackTopFrameText": "Black Standard Eyeglasses",
+ "eyewearSpecialBlackTopFrameNotes": "Glasses with a black frame above the lenses. Confers no benefit.",
+ "eyewearSpecialBlueTopFrameText": "Blue Standard Eyeglasses",
+ "eyewearSpecialBlueTopFrameNotes": "Glasses with a blue frame above the lenses. Confers no benefit.",
+ "eyewearSpecialGreenTopFrameText": "Green Standard Eyeglasses",
+ "eyewearSpecialGreenTopFrameNotes": "Glasses with a green frame above the lenses. Confers no benefit.",
+ "eyewearSpecialPinkTopFrameText": "Pink Standard Eyeglasses",
+ "eyewearSpecialPinkTopFrameNotes": "Glasses with a pink frame above the lenses. Confers no benefit.",
+ "eyewearSpecialRedTopFrameText": "Red Standard Eyeglasses",
+ "eyewearSpecialRedTopFrameNotes": "Glasses with a red frame above the lenses. Confers no benefit.",
+ "eyewearSpecialWhiteTopFrameText": "White Standard Eyeglasses",
+ "eyewearSpecialWhiteTopFrameNotes": "Glasses with a white frame above the lenses. Confers no benefit.",
+ "eyewearSpecialYellowTopFrameText": "Yellow Standard Eyeglasses",
+ "eyewearSpecialYellowTopFrameNotes": "Glasses with a yellow frame above the lenses. Confers no benefit.",
"eyewearSpecialSummerRogueText": "Roguish Eyepatch",
"eyewearSpecialSummerRogueNotes": "It doesn't take a scallywag to see how stylish this is! Confers no benefit. Limited Edition 2014 Summer Gear.",
- "eyewearSpecialSummerWarriorText": "Dashing Eyepatch",
+ "eyewearSpecialSummerWarriorText": "Snitsig Ögonlapp",
"eyewearSpecialSummerWarriorNotes": "It doesn't take a rapscallion to see how stylish this is! Confers no benefit. Limited Edition 2014 Summer Gear.",
"eyewearSpecialWonderconRedText": "Mäktig mask",
"eyewearSpecialWonderconRedNotes": "Vilket kraftfullt ansikts tillbehör! Ger ingen fördel. ???",
@@ -830,14 +860,14 @@
"eyewearSpecialWonderconBlackNotes": "Your motives are definitely legitimate. Confers no benefit. Special Edition Convention Item.",
"eyewearMystery201503Text": "Aquamarine Eyewear",
"eyewearMystery201503Notes": "Bli inte petad i ögat av de här shimrande juvelerna! Ger ingen fördel. Mars 2015 prenumerantobjekt.",
- "eyewearMystery201506Text": "Neon Snorkel",
- "eyewearMystery201506Notes": "This neon snorkel lets its wearer see underwater. Confers no benefit. June 2015 Subscriber Item.",
- "eyewearMystery201507Text": "Rad Sunglasses",
+ "eyewearMystery201506Text": "Neonsnorkel",
+ "eyewearMystery201506Notes": "Neonsnorkeln låter sin bärare se under vatten. Ger ingen fördel. Juni 2015 prenumerantobjekt.",
+ "eyewearMystery201507Text": "Tuffa Solglasögon",
"eyewearMystery201507Notes": "These sunglasses let you stay cool even when the weather is hot. Confers no benefit. July 2015 Subscriber Item.",
"eyewearMystery301404Text": "Eyewear Goggles",
"eyewearMystery301404Notes": "Inget ögonskydd skulle kunna vara stiligare än ett par glasögon - förutom, möjligtvis, en monokel. Ger inga fördelar. April 2015 prenumerationsföremål.",
"eyewearMystery301405Text": "Monokel",
"eyewearMystery301405Notes": "Inget ögonskydd skulle kunna vara stiligare än ett par glasögon - förutom, möjligtvis, en monokel. Ger inga fördelar. Juli 2015 prenumerationsföremål.",
- "eyewearArmoirePlagueDoctorMaskText": "Plague Doctor Mask",
+ "eyewearArmoirePlagueDoctorMaskText": "Pestdoktorns Mask",
"eyewearArmoirePlagueDoctorMaskNotes": "An authentic mask worn by the doctors who battle the Plague of Procrastination. Confers no benefit. Enchanted Armoire: Plague Doctor Set (Item 2 of 3)."
}
\ No newline at end of file
diff --git a/common/locales/sv/generic.json b/common/locales/sv/generic.json
index 35ddf94e2a..3020787216 100644
--- a/common/locales/sv/generic.json
+++ b/common/locales/sv/generic.json
@@ -10,10 +10,10 @@
"titleProfile": "Profil",
"titleInbox": "Inkorg",
"titleTavern": "Värdshus",
- "titleParty": "Party",
- "titleHeroes": "Hall of Heroes",
- "titlePatrons": "Hall of Patrons",
- "titleGuilds": "Guilds",
+ "titleParty": "Grupp",
+ "titleHeroes": "Hjältarnas Sal",
+ "titlePatrons": "Skyddsherrarnas Sal",
+ "titleGuilds": "Gille",
"titleChallenges": "Utmaningar",
"titleDrops": "Marknad",
"titleQuests": "Uppdrag",
@@ -25,15 +25,15 @@
"titleSettings": "Inställningar",
"expandToolbar": "Expandera Verktygsfält",
"collapseToolbar": "Fäll ihop Verktygsfält",
- "markdownBlurb": "Habitica uses markdown for message formatting. See the Markdown Cheat Sheet for more info.",
+ "markdownBlurb": "HabitRPG har olika koder för att formatera meddelanden. För mer information (på engelska), se Markdown Cheat Sheet.",
"showFormattingHelp": "Visa formateringshjälp",
- "hideFormattingHelp": "Dölja formateringshjälp",
- "youType": "Du skriva:",
+ "hideFormattingHelp": "Dölj formateringshjälp",
+ "youType": "Du skriver:",
"youSee": "Du ser:",
"italics": "*Kursivering*",
"bold": "**Fetstilt**",
"strikethrough": "~~Strykning~~",
- "emojiExample": ":Leende:",
+ "emojiExample": ":smile:",
"markdownLinkEx": "[Habitica är jättebra!](https://habitica.com)",
"markdownImageEx": "",
"unorderedListHTML": "+ Första artikeln + Andra artikeln + Tredje artikeln",
@@ -84,14 +84,14 @@
"habitBirthday": "Habiticas födelsedagsparty",
"habitBirthdayText": "Firade Habiticas födelsedagsparty!",
"habitBirthdayPluralText": "Har firat <%= number %> HabitPRG födelsedagspartyn!",
- "habiticaDay": "Habitica Naming Day",
- "habiticaDaySingularText": "Celebrated Habitica's Naming Day! Thanks for being a fantastic user.",
- "habiticaDayPluralText": "Celebrated <%= number %> Naming Days! Thanks for being a fantastic user.",
+ "habiticaDay": "HabitRPG Namnsdag",
+ "habiticaDaySingularText": "Firade HabitRPG:s namnsdag! Tack för att du är en så fantastisk användare.",
+ "habiticaDayPluralText": "Firade <%= number %> namnsdagar! Tack för att du är en så fantastisk användare.",
"achievementDilatory": "Dilatorys Befriare",
- "achievementDilatoryText": "Helped defeat the Dread Drag'on of Dilatory during the 2014 Summer Splash Event!",
- "costumeContest": "Costume Contestant",
- "costumeContestText": "Participated in the Habitoween Costume Contest. See some of the entries on the Habitica blog!",
- "costumeContestTextPlural": "Participated in <%= number %> Habitoween Costume Contests. See some of the entries on the Habitica blog!",
+ "achievementDilatoryText": "Hjälpte till att besegra Dilatorys Fruktade Drake under Sommaren 2014. ",
+ "costumeContest": "Maskeraddeltagare",
+ "costumeContestText": "Deltog i Habitoweens Maskeradtävling. Se en del av bidragen på HabitRPG-bloggen!",
+ "costumeContestTextPlural": "Deltagit i <%= number %> maskeradtävlingar under Habitoween. Se en del av bidragen på HabitRPG-bloggen!",
"memberSince": "- Medlem sedan",
"lastLoggedIn": "- Senast inloggad",
"notPorted": "Denna funktion är inte flyttad från orginalhemsidan ännu.",
@@ -100,8 +100,8 @@
"errorUpCase": "FEL:",
"newPassSent": "Nytt lösenord skickat.",
"serverUnreach": "Servern är otillgänglig för tillfället.",
- "requestError": "Yikes, an error occurred! Please reload the page, your last action may not have been saved correctly.",
- "seeConsole": "If the error persists, please report it at Help > Report a Bug. If you're familiar with your browser's console, please include any error messages.",
+ "requestError": "Ojsan, ett fel har skett! Vänligen ladda om sidan, din senaste handling kanske inte sparats ordentligt.",
+ "seeConsole": "Om felet fortsätter, rapportera in det under Gemenskap > Rapportera bugg. Om du känner till din webläsare ordentligt, inkludera gärna felmeddelanden därifrån.",
"error": "Fel",
"menu": "Meny",
"notifications": "Tillkännagivanden",
@@ -113,7 +113,7 @@
"audioTheme_danielTheBard": "Barden Daniel",
"audioTheme_wattsTheme": "Watts Tema",
"audioTheme_gokulTheme": "Gokul Tema",
- "audioTheme_luneFoxTheme": "LuneFox's Theme",
+ "audioTheme_luneFoxTheme": "LuneFoxs tema",
"askQuestion": "Ställ en fråga",
"reportBug": "Rapportera ett fel",
"HabiticaWiki": "Habitica Wikin",
@@ -134,49 +134,49 @@
"December": "December",
"dateFormat": "Datumformat",
"achievementStressbeast": "Stoïkalms Räddare",
- "achievementStressbeastText": "Helped defeat the Abominable Stressbeast during the 2014 Winter Wonderland Event!",
- "achievementBurnout": "Savior of the Flourishing Fields",
- "achievementBurnoutText": "Helped defeat Burnout and restore the Exhaust Spirits during the 2015 Fall Festival Event!",
- "achievementBewilder": "Savior of Mistiflying",
- "achievementBewilderText": "Helped defeat the Be-Wilder during the 2016 Spring Fling Event!",
+ "achievementStressbeastText": "Hjälpte till att besegra den Fruktansvärda Stressbesten under vintern 2014.",
+ "achievementBurnout": "Blomstrande Fältens Räddare",
+ "achievementBurnoutText": "Hjälpte till att besegra Utbrännaren och återställa Förbruknings-andarna under våren 2015.",
+ "achievementBewilder": "Mistiflyings Räddare",
+ "achievementBewilderText": "Hjälpte till att besegra Förvildaren under våren 2016.",
"checkOutProgress": "Kolla mina framsteg i Habitica!",
"cardReceived": "Tagit emot ett kort!",
"cardReceivedFrom": "<%= cardType %> från <%= userName %>",
"greetingCard": "Hälsningskort",
- "greetingCardExplanation": "You both receive the Cheery Chum achievement!",
- "greetingCardNotes": "Send a greeting card to a party member.",
+ "greetingCardExplanation": "Ni får båda en Lycklig Lirare-bedrift!",
+ "greetingCardNotes": "Skicka ett hälsningskort till en gruppmedlem.",
"greeting0": "Hallå där!",
- "greeting1": "Just saying hello :)",
+ "greeting1": "Bara hälsar. :)",
"greeting2": "`vinkar intensivt`",
"greeting3": "Hej du!",
- "greetingCardAchievementTitle": "Cheery Chum",
- "greetingCardAchievementText": "Hey! Hi! Hello! Sent or received <%= cards %> greeting cards.",
- "thankyouCard": "Tack kort",
- "thankyouCardExplanation": "You both receive the Greatly Grateful achievement!",
- "thankyouCardNotes": "Send a Thank-You card to a party member.",
+ "greetingCardAchievementTitle": "Lycklig Lirare",
+ "greetingCardAchievementText": "Hej! Tjena! Hallå! Skickar eller tagit emot <%= cards %> hälsningskort.",
+ "thankyouCard": "Tack-kort",
+ "thankyouCardExplanation": "Ni får båda en Mycket Tacksam-bedrift!",
+ "thankyouCardNotes": "Skicka ett tack-kort till en Gruppmedlem.",
"thankyou0": "Tack så mycket!",
"thankyou1": "Tack, tack, tack!",
- "thankyou2": "Sending you a thousand thanks.",
+ "thankyou2": "Skickar tusen tack till dig.",
"thankyou3": "Jag är väldigt tacksam - tack!",
"thankyouCardAchievementTitle": "Oerhört Tacksam",
"thankyouCardAchievementText": "Tack för att du är tacksam! Skickat eller tagit emot <%= cards %> Tack kort.",
"birthdayCard": "Födelsedagskort",
- "birthdayCardExplanation": "You both receive the Birthday Bonanza achievement!",
+ "birthdayCardExplanation": "Ni får båda en Födelsedagstjolabalo-bedrift!",
"birthdayCardNotes": "Sänd ett födelsedagskort ti",
"birthday0": "Grattis på dagen!",
- "birthdayCardAchievementTitle": "Birthday Bonanza",
- "birthdayCardAchievementText": "Many happy returns! Sent or received <%= cards %> birthday cards.",
- "streakAchievement": "You earned a streak achievement!",
- "firstStreakAchievement": "21-Day Streak",
- "streakAchievementCount": "<%= streaks %> 21-Day Streaks",
- "twentyOneDays": "You've completed your Daily for 21 days in a row!",
- "dontBreakStreak": "Amazing job. Don't break the streak!",
+ "birthdayCardAchievementTitle": "Födelsedagstjolabalo",
+ "birthdayCardAchievementText": "Hjärtliga gratulationer. Skickat eller tagit emot <%= cards %> födelsedagskort.",
+ "streakAchievement": "Du har tjänat in en Följd-bedrift!",
+ "firstStreakAchievement": "21-dagars följd",
+ "streakAchievementCount": "<%= streaks %> 21-dagars följder",
+ "twentyOneDays": "Du har slutfört dina Dagliga Uppgifter 21 dar i rad!",
+ "dontBreakStreak": "Fantastiskt jobb. Sluta inte när du har flytet uppe!",
"dontStop": "Sluta inte nu!",
- "levelUpShare": "I leveled up in Habitica by improving my real-life habits!",
- "questUnlockShare": "I unlocked a new quest in Habitica!",
- "hatchPetShare": "I hatched a new pet by completing my real-life tasks!",
- "raisePetShare": "I raised a pet into a mount by completing my real-life tasks!",
- "wonChallengeShare": "I won a challenge in Habitica!",
- "achievementShare": "I earned a new achievement in Habitica!",
- "orderBy": "Order By <%= item %>"
+ "levelUpShare": "Jag har gått upp i Level i HabitRPG genom att förbättra mina vanor!",
+ "questUnlockShare": "Jag låste upp ett nytt äventyr i HabitRPG!",
+ "hatchPetShare": "Jag har kläckt ett nytt husdjur genom att slutföra mina uppgifter!",
+ "raisePetShare": "Jag uppfostrade mitt husdjur till ett riddjur genom att slutföra mina uppgifter!",
+ "wonChallengeShare": "Jag van en utmaning i HabitRPG!",
+ "achievementShare": "Jag har klarat av en ny bedrift i HabitRPG!",
+ "orderBy": "Sortera enligt <%= item %>"
}
\ No newline at end of file
diff --git a/common/locales/sv/groups.json b/common/locales/sv/groups.json
index b4231a8dc9..3b41fe6501 100644
--- a/common/locales/sv/groups.json
+++ b/common/locales/sv/groups.json
@@ -13,7 +13,7 @@
"community": "Community-forum",
"dataTool": "Datavisningsverktyg",
"resources": "Resurser",
- "askQuestionNewbiesGuild": "Ask a Question (Newbies Guild)",
+ "askQuestionNewbiesGuild": "Ställ en fråga (Nybörjaregille)",
"tavernTalk": "Krogsnack",
"tavernAlert1": "Notera: om du rapporterar en bugg här kommer inte utvecklarna se det. Var god",
"tavernAlert2": "använd GitHub istället",
@@ -27,16 +27,16 @@
"noPartyText": "You are either not in a party or your party is taking a while to load. You can either create one and invite friends, or if you want to join an existing party, have them enter your Unique User ID below and then come back here to look for the invitation:",
"LFG": "To advertise your new party or find one to join, go to the <%= linkStart %>Party Wanted (Looking for Group)<%= linkEnd %> Guild.",
"wantExistingParty": "Want to join an existing party? Go to the <%= linkStart %>Party Wanted Guild<%= linkEnd %> and post this User ID:",
- "joinExistingParty": "Join Someone Else's Party",
+ "joinExistingParty": "Gå med i någon annans sällskap",
"create": "Skapa",
"userId": "Användar-ID",
"invite": "Bjud in",
"leave": "Lämna",
"invitedTo": "Inbjuden till <%= name %>",
"invitedToNewParty": "You were invited to join a party! Do you want to leave this party and join <%= partyName %>?",
- "joinNewParty": "Join New Party",
+ "joinNewParty": "Gå med i ett nytt sällskap",
"declineInvitation": "Avböj Inbjudan",
- "loadingNewParty": "Your new party is loading. Please wait...",
+ "loadingNewParty": "Ditt nya sällskap laddar. Vänta...",
"newMsg": "Nytt meddelande i \"<%= name %>\"",
"chat": "Chatt",
"sendChat": "Skicka Chatt",
@@ -92,6 +92,7 @@
"send": "Skicka",
"messageSentAlert": "Meddelande skickat",
"pmHeading": "Privatmeddelande till <%= name %>",
+ "pmsMarkedRead": "Your private messages have been marked as read",
"clearAll": "Radera alla meddelanden",
"confirmDeleteAllMessages": "Är du säker på att du vill ta bort alla meddelanden i din inkorg? Andra användare kommer fortfarande se meddelanden som du har skickat till dem.",
"optOutPopover": "Gillar du inte privatmeddelanden? Klicka för att stänga av funktionen",
@@ -99,10 +100,19 @@
"unblock": "Avblockera",
"pm-reply": "Skicka ett svar",
"inbox": "Inkorgen",
+ "messageRequired": "A message is required.",
+ "toUserIDRequired": "A User ID is required",
+ "gemAmountRequired": "A number of gems is required",
+ "notAuthorizedToSendMessageToThisUser": "Can't send message to this user.",
+ "privateMessageGiftIntro": "Hello <%= receiverName %>, <%= senderName %> has sent you",
+ "privateMessageGiftGemsMessage": "<%= gemAmount %> gems!",
+ "privateMessageGiftSubscriptionMessage": "<%= numberOfMonths %> months of subscription!",
+ "cannotSendGemsToYourself": "Cannot send gems to yourself. Try a subscription instead.",
+ "badAmountOfGemsToSend": "Amount must be within 1 and your current number of gems.",
"abuseFlag": "Anmäl brott mot gemenskapens riktlinjer.",
"abuseFlagModalHeading": "Rapportera <%= name %> för överträdelse?",
"abuseFlagModalBody": "Are you sure you want to report this post? You should ONLY report a post that violates the <%= firstLinkStart %>Community Guidelines<%= linkEnd %> and/or <%= secondLinkStart %>Terms of Service<%= linkEnd %>. Inappropriately reporting a post is a violation of the Community Guidelines and may give you an infraction. Appropriate reasons to flag a post include but are not limited to:
swearing, religous oaths
bigotry, slurs
adult topics
violence, including as a joke
spam, nonsensical messages
",
- "abuseFlagModalButton": "Report Violation",
+ "abuseFlagModalButton": "Rapportera missbruk",
"abuseReported": "Thank you for reporting this violation. The moderators have been notified.",
"abuseAlreadyReported": "Du har redan anmält det här meddelandet.",
"needsText": "Var god skriv ett meddelande.",
@@ -114,42 +124,66 @@
"leaderOnlyChallenges": "Only group leader can create challenges",
"sendGift": "Skicka Gåva",
"inviteFriends": "Bjud in Vänner",
- "inviteByEmail": "Invite by Email",
+ "inviteByEmail": "Bjud in via e-post",
"inviteByEmailExplanation": "If a friend joins Habitica via your email, they'll automatically be invited to your party!",
- "inviteFriendsNow": "Invite Friends Now",
- "inviteFriendsLater": "Invite Friends Later",
+ "inviteFriendsNow": "Bjud in vänner nu",
+ "inviteFriendsLater": "Bjud in vänner senare",
"inviteAlertInfo": "If you have friends already using Habitica, invite them by User ID here.",
- "inviteExistUser": "Invite Existing Users",
+ "inviteExistUser": "Bjud in existerande användare",
"byColon": "AV:",
- "inviteNewUsers": "Invite New Users",
- "sendInvitations": "Send Invitations",
- "invitationsSent": "Invitations sent!",
+ "inviteNewUsers": "Bjud in nya användare",
+ "sendInvitations": "Skicka inbjudningar",
+ "invitationsSent": "Inbjudningarna skickades!",
"inviteAlertInfo2": "Eller dela den här länken (kopiera/klistra in):",
"sendGiftHeading": "Skicka Gåva till <%= name %>",
"sendGiftGemsBalance": "Från <%= number %> Juveler",
"sendGiftCost": "Totalt: $<%= cost %> USD",
"sendGiftFromBalance": "From Balance",
- "sendGiftPurchase": "Purchase",
+ "sendGiftPurchase": "Köp",
"sendGiftMessagePlaceholder": "Personal message (optional)",
"sendGiftSubscription": "<%= months %> Month(s): $<%= price %> USD",
"battleWithFriends": "Strid mot Monster med Vänner",
- "startPartyWithFriends": "Start a Party with your friends!",
+ "startPartyWithFriends": "Starta ett sällskap med dina vänner!",
"startAParty": "Starta ett Sällskap",
"addToParty": "Lägg Till Någon i Ditt Sällskap",
"likePost": "Klicka för att gilla ",
"partyExplanation1": "Play Habitica with friends to stay accountable!",
- "partyExplanation2": "Battle monsters and create Challenges!",
+ "partyExplanation2": "Slåss mot monster och skapa Utmaningar!",
"partyExplanation3": "Bjud in vänner nu för att få en Uppdragsskriftrulle!",
- "wantToStartParty": "Do you want to start a party?",
+ "wantToStartParty": "Vill du starta ett nytt sällskap?",
"exclusiveQuestScroll": "Inviting a friend to your party will grant you an exclusive Quest Scroll to battle the Basi-List together!",
- "nameYourParty": "Name your new party!",
- "partyEmpty": "You're the only one in your party. Invite your friends!",
+ "nameYourParty": "Namnge ditt nya sällskap!",
+ "partyEmpty": "Du är den enda medlemmen i ditt sällskap. Bjud in dina vänner!",
"partyChatEmpty": "Your party chat is empty! Type a message in the box above to start chatting.",
"guildChatEmpty": "This guild's chat is empty! Type a message in the box above to start chatting.",
- "possessiveParty": "<%= name %>'s Party",
+ "possessiveParty": "<%= name %>'s Sällskap",
"requestAcceptGuidelines": "If you would like to post messages in the Tavern or any party or guild chat, please first read our <%= linkStart %>Community Guidelines<%= linkEnd %> and then click the button below to indicate that you accept them.",
- "partyUpName": "Party Up",
- "partyOnName": "Party On",
- "partyUpAchievement": "Joined a Party with another person! Have fun battling monsters and supporting each other.",
- "partyOnAchievement": "Joined a Party with at least four people! Enjoy your increased accountability as you unite with your friends to vanquish your foes!"
+ "partyUpName": "Sällskapa",
+ "partyOnName": "Sällskapa vidare",
+ "partyUpAchievement": "Gick med i ett sällskap med en annan person! Ha kul med att slåss mot monster och stötta varandra.",
+ "partyOnAchievement": "Joined a Party with at least four people! Enjoy your increased accountability as you unite with your friends to vanquish your foes!",
+ "groupIdRequired": "\"groupId\" must be a valid UUID",
+ "groupNotFound": "Group not found.",
+ "groupTypesRequired": "You must supply a valid \"type\" query string.",
+ "questLeaderCannotLeaveGroup": "You cannot leave your party when you have started a quest. Abort the quest first.",
+ "cannotLeaveWhileActiveQuest": "You cannot leave party during an active quest. Please leave the quest first.",
+ "onlyLeaderCanRemoveMember": "Only group leader can remove a member!",
+ "memberCannotRemoveYourself": "You cannot remove yourself!",
+ "groupMemberNotFound": "User not found among group's members",
+ "mustBeGroupMember": "Must be member of the group.",
+ "keepOrRemoveAll": "req.query.keep must be either \"keep-all\" or \"remove-all\"",
+ "keepOrRemove": "req.query.keep must be either \"keep\" or \"remove\"",
+ "canOnlyInviteEmailUuid": "Can only invite using uuids or emails.",
+ "inviteMissingEmail": "Missing email address in invite.",
+ "partyMustbePrivate": "Parties must be private",
+ "userAlreadyInGroup": "User already in that group.",
+ "userAlreadyInvitedToGroup": "User already invited to that group.",
+ "userAlreadyPendingInvitation": "User already pending invitation.",
+ "userAlreadyInAParty": "User already in a party.",
+ "userWithIDNotFound": "User with id \"<%= userId %>\" not found.",
+ "userHasNoLocalRegistration": "User does not have a local registration (username, email, password).",
+ "uuidsMustBeAnArray": "User ID invites must be an array.",
+ "emailsMustBeAnArray": "Email address invites must be an array.",
+ "canOnlyInviteMaxInvites": "You can only invite \"<%= maxInvites %>\" at a time",
+ "onlyCreatorOrAdminCanDeleteChat": "Not authorized to delete this message!"
}
\ No newline at end of file
diff --git a/common/locales/sv/limited.json b/common/locales/sv/limited.json
index edc645540f..689cf20e60 100644
--- a/common/locales/sv/limited.json
+++ b/common/locales/sv/limited.json
@@ -5,11 +5,11 @@
"annoyingFriends": "Jobbiga Vänner",
"annoyingFriendsText": "Fick <%= snowballs %> snöbollar kastad på sig av sällskapsmedlemmar.",
"alarmingFriends": "Oroväckande Vänner",
- "alarmingFriendsText": "Blev skrämd <%= spookDust %> gånger av sällskapsmedlemmar.",
+ "alarmingFriendsText": "Got spooked <%= spookySparkles %> times by party members.",
"agriculturalFriends": "Jordbruksvänner",
"agriculturalFriendsText": "Transformerades till en blomma <%= seeds %> gånger av sällskapsmedlemmar.",
"aquaticFriends": "Akvatiska Vänner",
- "aquaticFriendsText": "Got splashed <%= seafoam %> times by party members.",
+ "aquaticFriendsText": "Blev nedstänkt <%= seafoam %> gånger av sällskapsdeltagare.",
"valentineCard": "Alla Hjärtans Dag-kort",
"valentineCardExplanation": "For enduring such a saccharine poem, you both receive the \"Adoring Friends\" badge!",
"valentineCardNotes": "Skicka ett Alla Hjärtans Dag-kort till en sällskapsdeltagare.",
@@ -72,5 +72,6 @@
"comfortingKittySet": "Comforting Kitty (Healer)",
"sneakySqueakerSet": "Sneaky Squeaker (Rogue)",
"fallEventAvailability": "Tillgänglig till 31:e oktober",
- "winterEventAvailability": "Tillgänglig till 31:e december"
+ "winterEventAvailability": "Tillgänglig till 31:e december",
+ "springEventAvailability": "Available until May 31"
}
\ No newline at end of file
diff --git a/common/locales/sv/maintenance.json b/common/locales/sv/maintenance.json
new file mode 100644
index 0000000000..efdb524cd2
--- /dev/null
+++ b/common/locales/sv/maintenance.json
@@ -0,0 +1,34 @@
+{
+ "habiticaBackSoon": "Don't worry, Habitica will be back soon!",
+ "importantMaintenance": "We are doing important maintenance that we estimate will last until <%= localDate %> in your timezone.",
+ "maintenance": "Maintenance",
+ "maintenanceMoreInfo": "Want more information about the maintenance? <%= linkStart %>Check out our info page<%= linkEnd %>.",
+ "noDamageKeepStreaks": "You will NOT take damage or lose streaks!",
+ "thanksForPatience": "Thanks for your patience!",
+ "twitterMaintenanceUpdates": "For the most recent updates, watch our Twitter, where we will be posting status information.",
+ "veteranPetAward": "At the end, you will receive a Veteran pet!",
+
+ "maintenanceInfoTitle": "Information about Upcoming Maintenance to Habitica",
+ "maintenanceInfoWhat": "What is happening?",
+ "maintenanceInfoWhatText": "On May 21, Habitica will be down for maintenance for most of the day. You will not take any damage or have your account harmed during that weekend, even if you can’t log in to check off your Dailies in time! We will be working very hard to make the downtime as short as possible, and will be posting regular updates on our Twitter account. At the end of the downtime, to thank everyone for their patience, you will all receive a rare pet!",
+ "maintenanceInfoWhy": "Why is this happening?",
+ "maintenanceInfoWhyText": "For the past several months, we have been thoroughly revamping Habitica behind-the-scenes. Specifically, we have rewritten the API. While it may not look much different on the surface, it’s a whole new world underneath. This will allow us WAY more flexibility when we want to build features in the future, and lead to improved performance!",
+ "maintenanceInfoTechDetails": "Want more details on the technical side of the process? Visit The Forge, our dev blog.",
+ "maintenanceInfoMore": "More Information",
+ "maintenanceInfoAccountChanges": "What changes will I see to my account after the rewrite is complete?",
+ "maintenanceInfoAccountChangesText": "At first, there won’t be any notable changes aside from performance improvements for features such as Challenges. If you notice any changes that shouldn’t be there, email us at admin@habitica.com and we will investigate them for you!",
+ "maintenanceInfoAddFeatures": "What kind of features will this allow Habitica to add?",
+ "maintenanceInfoAddFeaturesText": "Completing this rewrite will allow us to start building out improved chat and Guilds, plans for organizations and families, and additional productivity features like Monthlies and the ability to record yesterday’s activity! Those are all involved features on their own, so it will take time to build them, but until we were finished with this rewrite, there was no way we could start them.",
+ "maintenanceInfoHowLong": "How long will the maintenance take?",
+ "maintenanceInfoHowLongText": "We have to migrate tasks and data for all 1.3 million Habitica users -- not an easy task! We anticipate that it will take place between approximately 1pm Pacific Time (8pm UTC) and 10pm Pacific Time (5am UTC). Rest assured that we’re doing everything we can to make it go as quickly as possible! You can follow updates on our Twitter.",
+ "maintenanceInfoStatsAffected": "How will my Dailies, Streaks, Buffs, and Quests be affected?",
+ "maintenanceInfoStatsAffectedText1": "You will NOT take any damage or lose any streaks that weekend, but otherwise, your day will reset normally! Dailies that you checked will become unchecked, buffs will reset, etc. If you are in a Collection Quest, you will still find items. If you are in a Boss Battle, you will still deal damage to the Boss, but the Boss will not deal damage to you. (Even monsters need a break!)",
+ "maintenanceInfoStatsAffectedText2": "After a lot of thought, our team concluded that this was the most fair way to handle the fact that many users will not be able to check off their Dailies normally during the maintenance. We’re sorry for any inconvenience this causes!",
+ "maintenanceInfoSeeTasks": "What if I need to see my task list?",
+ "maintenanceInfoSeeTasksText": "If you know that you will need to see your task list on Saturday to remind yourself what you have to do, we recommend that before the maintenance begins, you take a screenshot of your tasks so that you can use it as a reference.",
+ "maintenanceInfoRarePet": "What kind of rare pet will I receive?",
+ "maintenanceInfoRarePetText": "To thank you for your patience during the downtime, everyone will get a rare Veteran Pet. If you’ve never received a Veteran Pet before, you will receive a Veteran Wolf. If you already have a Veteran Wolf, you will receive a Veteran Tiger. And if you already have a Veteran Wolf and a Veteran Tiger, you will receive a never-before-seen Veteran pet! After the migration is completed, it may take several hours for your pet to show up, but never fear, everyone will get one.",
+ "maintenanceInfoWho": "Who worked on this massive project?",
+ "maintenanceInfoWhoText": "We’re glad you asked! It was spearheaded by our amazing contributor paglias, with lots of help from Blade, TheHollidayInn, SabreCat, Victor Pudeyev, TheUnknown, and Alys.",
+ "maintenanceInfoTesting": "The new version was also tirelessly tested by a bunch of our amazing open-source volunteers. Thank you -- we couldn't have done this without you."
+}
diff --git a/common/locales/sv/npc.json b/common/locales/sv/npc.json
index ccd49fd5ce..0ee1d4045a 100644
--- a/common/locales/sv/npc.json
+++ b/common/locales/sv/npc.json
@@ -21,6 +21,26 @@
"ian": "Ian",
"ianText": "Welcome to the Quest Shop! Here you can use Quest Scrolls to battle monsters with your friends. Be sure to check out our fine array of Quest Scrolls for purchase on the right!",
"ianBrokenText": "Welcome to the Quest Shop... Here you can use Quest Scrolls to battle monsters with your friends... Be sure to check out our fine array of Quest Scrolls for purchase on the right...",
+ "missingKeyParam": "\"req.params.key\" is required.",
+ "itemNotFound": "Item \"<%= key %>\" not found.",
+ "cannotBuyItem": "You can't buy this item.",
+ "missingTypeKeyEquip": "\"key\" and \"type\" are required parameters.",
+ "missingPetFoodFeed": "\"pet\" and \"food\" are required parameters.",
+ "invalidPetName": "Invalid pet name supplied.",
+ "missingEggHatchingPotionHatch": "\"egg\" and \"hatchingPotion\" are required parameters.",
+ "invalidTypeEquip": "\"type\" must be one of 'equipped', 'pet', 'mount', 'costume'.",
+ "mustPurchaseToSet": "Must purchase <%= val %> to set it on <%= key %>.",
+ "typeRequired": "Type is required",
+ "keyRequired": "Key is required",
+ "notAccteptedType": "Type must be in [eggs, hatchingPotions, food, quests, gear]",
+ "contentKeyNotFound": "Key not found for Content <%= type %>",
+ "plusOneGem": "+1 Gem",
+ "typeNotSellable": "Type is not sellable. Must be one of the following <%= acceptedTypes %>",
+ "userItemsKeyNotFound": "Key not found for user.items <%= type %>",
+ "pathRequired": "Path string is required",
+ "unlocked": "Items have been unlocked",
+ "alreadyUnlocked": "Full set already unlocked.",
+ "alreadyUnlockedPart": "Full set already partially unlocked.",
"USD": "(USD)",
"newStuff": "Nya Prylar",
"cool": "Påminn Mig Senare",
@@ -64,6 +84,7 @@
"tourPetsPage": "This is the Stable! After level 4, you can hatch pets using eggs and potions. When you hatch a pet in the Market, it will appear here! Click a pet's image to add it to your avatar. Feed them with the food you find after level 4, and they'll grow into powerful mounts.",
"tourMountsPage": "Once you've fed a pet enough food to turn it into a mount, it will appear here. (Pets, mounts, and food are available after level 4.) Click a mount to saddle up!",
"tourEquipmentPage": "This is where your Equipment is stored! Your Battle Gear affects your stats. If you want to show different Equipment on your avatar without changing your stats, click \"Enable Costume.\"",
+ "equipmentAlreadyOwned": "You already own that piece of equipment",
"tourOkay": "Okej!",
"tourAwesome": "Utmärkt!",
"tourSplendid": "Jättebra!",
@@ -71,19 +92,19 @@
"tourAvatarProceed": "Visa mig mina uppgifter!",
"tourToDosBrief": "To-Do List
Check off To-Dos to earn Gold & Experience!
To-Dos never make your avatar lose Health.
",
"tourDailiesBrief": "Daily Tasks
Dailies repeat every day.
You lose Health if you skip Dailies.
",
- "tourDailiesProceed": "I'll be careful!",
+ "tourDailiesProceed": "Jag skall vara försiktig!",
"tourHabitsBrief": "Good & Bad Habits
Purchase Equipment for your avatar, or set custom Rewards.
",
"tourRewardsProceed": "Det är allt!",
"welcomeToHabit": "Välkommen till Habitica!",
- "welcome1": "Create a basic avatar.",
+ "welcome1": "Skapa en grundläggare avatar.",
"welcome1notes": "This avatar will represent you as you progress.",
"welcome2": "Set up your tasks.",
"welcome2notes": "Hur bra du gör ifrån dig i verkliga uppgifter styr hur bra du gör ifrån dig i spelet!",
- "welcome3": "Progress in life and the game!",
+ "welcome3": "Gör framgångar i både verkligheten och spelet!",
"welcome3notes": "As you improve your life, your avatar will level up and unlock pets, quests, equipment, and more!",
"welcome4": "Avoid bad habits that drain Health (HP), or your avatar will die!",
- "welcome5": "Now you'll customize your avatar and set up your tasks...",
- "imReady": "Enter Habitica"
+ "welcome5": "Nu kan du skräddarsy din avatar och sätta uppgifter till dig själv...",
+ "imReady": "Logga in på Habitica"
}
\ No newline at end of file
diff --git a/common/locales/sv/pets.json b/common/locales/sv/pets.json
index cec657c00a..fcb2998fd5 100644
--- a/common/locales/sv/pets.json
+++ b/common/locales/sv/pets.json
@@ -1,17 +1,18 @@
{
"pets": "Husdjur",
"petsFound": "Husdjur Funna",
- "magicPets": "Magic Potion Pets",
+ "magicPets": "Husdjur från magiska drycker",
"rarePets": "Sällsynta Husdjur",
"questPets": "Uppdrags-husdjur",
"mounts": "Riddjur",
"mountsTamed": "Tämjda Riddjur",
"questMounts": "Uppdragsriddjur",
- "magicMounts": "Magic Potion Mounts",
+ "magicMounts": "Riddjur från magiska drycker",
"rareMounts": "Sällsynta Riddjur",
"etherealLion": "Himmelskt Lejon",
"veteranWolf": "Veteranvarg",
"veteranTiger": "Veterantiger",
+ "veteranLion": "Veteran Lion",
"cerberusPup": "Cerberusvalp",
"hydra": "Hydra",
"mantisShrimp": "Mantisräka",
@@ -19,7 +20,7 @@
"orca": "Späckhuggare",
"royalPurpleGryphon": "Kunglig Lila Grip",
"phoenix": "Fenix",
- "bumblebee": "Bumblebee",
+ "magicalBee": "Magiskt Bi",
"rarePetPop1": "Tryck på den gyllene tassen för att lära dig mer om hur du kan erhålla detta sällsynta husdjur genom att bidra till Habitica.",
"rarePetPop2": "Hur du får detta husdjur!",
"potion": "<%= potionType %> dryck",
@@ -44,7 +45,7 @@
"beastMasterText": "Har hittat alla 90 husdjur (extremt svårt, gratulera den här användaren!)",
"beastMasterText2": "och har släppt sina husdjur totalt <%= count %> gånger.",
"mountMasterProgress": "Framsteg mot Riddjursmästare",
- "stableMountMasterProgress": "Mount Master Progress: <%= number %> Mounts Tamed",
+ "stableMountMasterProgress": "Stallmästar-framsteg: <%= number %> Riddjur Tämjda",
"mountAchievement": "Du har erhållit bedriften \"Riddjursmästare\" genom att samla alla riddjur!",
"mountMasterName": "Riddjursmästare",
"mountMasterText": "Har tämjt alla 90 riddjur (ännu svårare, gratulera denna användare!)",
@@ -57,23 +58,24 @@
"dropsEnabled": "Dropsystem Aktiverat!",
"itemDrop": "Ett föremål har droppat!",
"firstDrop": "You've unlocked the Drop System! Now when you complete tasks, you have a small chance of finding an item, including eggs, potions, and food! You just found a <%= eggText %> Egg! <%= eggNotes %>",
- "useGems": "If you've got your eye on a pet, but can't wait any longer for it to drop, use Gems in Inventory > Market to buy one!",
+ "useGems": "Om du är intresserad av ett husdjur men inte klarar av att vänta på att det skall dyka upp som fynd kan du använda juveler från Inventory > Markanden för att köpa ett!",
"hatchAPot": "Kläck ett <%= potion %> <%= egg %>?",
- "hatchedPet": "You hatched a <%= potion %> <%= egg %>!",
+ "hatchedPet": "Du kläckte en <%= potion %> <%= egg %>!",
"displayNow": "Visa nu",
"displayLater": "Visa senare",
- "earnedCompanion": "With all your productivity, you've earned a new companion. Feed it to make it grow!",
+ "petNotOwned": "You do not own this pet.",
+ "earnedCompanion": "All din produktivitet har gett dig en ny kompanjon. Mata den så att den växer!",
"feedPet": "Mata <%= article %><%= text %> till din <%= name %>?",
"useSaddle": "Sadla <%= pet %>?",
- "raisedPet": "You grew a <%= pet %>!",
- "earnedSteed": "By completing your tasks, you've earned a faithful steed!",
+ "raisedPet": "Du födde upp en <%= pet %>!",
+ "earnedSteed": "Genom att genomföra dina uppgifter har du fått en trogen springare!",
"rideNow": "Rid Nu",
"rideLater": "Rid Senare",
"petName": "Kläck ett <%= potion %> <%= egg %>?",
"mountName": "<%= potion %> <%= mount %>",
"petKeyName": "Nyckel till Hundgårdarna",
"petKeyPop": "Låt dina husdjur ströva fritt, släpp lös dom för att starta sina egna äventyr, och ge dig själv spänningen av Djurmästare åter igen!",
- "petKeyBegin": "Key to the Kennels: Experience <%= title %> Once More!",
+ "petKeyBegin": "Nyckel till hundgårdarna: Upplev <%=title %> återigen!",
"petKeyInfo": "Saknar du spänningen i att samla husdjur? Nu kan du släppa dem fria, och göra samlandet meningsfullt igen!",
"petKeyInfo2": "Use the Key to the Kennels to reset your non-quest collectible pets and/or mounts to zero. (Quest-only and Rare pets and mounts are not affected.)",
"petKeyInfo3": "There are three Keys to the Kennels: Release Pets Only (4 Gems), Release Mounts Only (4 Gems), or Release Both Pets and Mounts (6 Gems). Using a Key lets you stack the Beast Master and Mount Master achievements. The Triad Bingo achievement will only stack if you use the \"Release Both Pets and Mounts\" key and have collected all 90 pets a second time. Show the world just how much of collection master you are! But choose wisely, because once you use a Key and open the kennel or stable doors, you won't be able to get them back without collecting them all again...",
@@ -83,5 +85,8 @@
"petKeyBoth": "Släpp båda",
"confirmPetKey": "Är du säker?",
"petKeyNeverMind": "Inte Än",
+ "petsReleased": "Pets released.",
+ "mountsAndPetsReleased": "Mounts and pets released",
+ "mountsReleased": "Mounts released",
"gemsEach": "juveler vardera"
}
\ No newline at end of file
diff --git a/common/locales/sv/quests.json b/common/locales/sv/quests.json
index b42d4dc05f..8152d37e9d 100644
--- a/common/locales/sv/quests.json
+++ b/common/locales/sv/quests.json
@@ -78,5 +78,24 @@
"whichQuestStart": "Vilket uppdrag vill du påbörja?",
"getMoreQuests": "Skaffa fler uppdrag",
"unlockedAQuest": "Du har låst upp ett uppdrag!",
- "leveledUpReceivedQuest": "You leveled up to Level <%= level %> and received a quest scroll!"
+ "leveledUpReceivedQuest": "You leveled up to Level <%= level %> and received a quest scroll!",
+ "questInvitationDoesNotExist": "No quest invitation has been sent out yet.",
+ "questInviteNotFound": "No quest invitation found.",
+ "guildQuestsNotSupported": "Guilds cannot be invited on quests.",
+ "questNotFound": "Quest \"<%= key %>\" not found.",
+ "questNotOwned": "You don't own that quest scroll.",
+ "questNotGoldPurchasable": "Quest \"<%= key %>\" is not a Gold-purchasable quest.",
+ "questLevelTooHigh": "You must be level <%= level %> to begin this quest.",
+ "questAlreadyUnderway": "Your party is already on a quest. Try again when the current quest has ended.",
+ "questAlreadyAccepted": "You already accepted the quest invitation.",
+ "noActiveQuestToLeave": "No active quest to leave",
+ "questLeaderCannotLeaveQuest": "Quest leader cannot leave quest",
+ "notPartOfQuest": "You are not part of the quest",
+ "noActiveQuestToAbort": "There is no active quest to abort.",
+ "onlyLeaderAbortQuest": "Only the group or quest leader can abort a quest.",
+ "questAlreadyRejected": "You already rejected the quest invitation.",
+ "cantCancelActiveQuest": "You can not cancel an active quest, use the abort functionality.",
+ "onlyLeaderCancelQuest": "Only the group or quest leader can cancel the quest.",
+ "questNotPending": "There is no quest to start.",
+ "questOrGroupLeaderOnlyStartQuest": "Only the quest leader or group leader can force start the quest"
}
\ No newline at end of file
diff --git a/common/locales/sv/questscontent.json b/common/locales/sv/questscontent.json
index 0d65148f48..0256fe358d 100644
--- a/common/locales/sv/questscontent.json
+++ b/common/locales/sv/questscontent.json
@@ -66,7 +66,7 @@
"questVice2Notes": "När Vices inflytande över dig borta kan du känna en våg av styrka du inte visste att du hade återvända. Säkra på er själva och er förmåga att motstå Skuggormens påverkan påbörjar ditt sällskap sin resa mot Mount Habitica. Ni närmar er ingången till bergets grottor och stannar upp. Skuggor sipprar från öppningen likt dimma och det är nästan omöjligt att se något alls. Lyktornas ljus verkar försvinna helt där skuggorna tar vid. Det sägs att bara magiskt ljus kan tränga igenom drakens infernaliska dis. Om ni kan hitta tillräckligt många ljuskristaller ska det nog gå att ta sig fram till draken.",
"questVice2CollectLightCrystal": "Ljuskristaller",
"questVice2DropVice3Quest": "Vice Del 3 (Skriftrulle)",
- "questVice3Text": "Vice, Part 3: Vice Awakens",
+ "questVice3Text": "Vice, Del 3: Vice Vaknar",
"questVice3Notes": "Med mycket möda har ditt sällskap till sist hittat Vices näste. Det enorma monstret synar er med uppenbart missnöje. Skuggor slingrar sig runt dig och du hör en röst viska i ditt huvud: \"Fler dåraktiga Habiticaner här för att stoppa mig? Så sött. Ni skulle ha stannat hemma.\" Den fjälliga titanen höjer huvudet och förbereder en attack. Här är er chans! Ge ert yttersta och besegre Vice en gång för alla!",
"questVice3Completion": "The shadows dissipate from the cavern and a steely silence falls. My word, you've done it! You have defeated Vice! You and your party may finally breath a sigh of relief. Enjoy your victory, brave Habiteers, but take the lessons you've learned from battling Vice and move forward. There are still Habits to be done and potentially worse evils to conquer!",
"questVice3Boss": "Vice, SkuggOrmen",
@@ -95,7 +95,7 @@
"questGoldenknight2Notes": "Armed with hundreds of Habitican's testimonies, you finally confront the Golden Knight. You begin to recite the Habitcan's complaints to her, one by one. \"And @Pfeffernusse says that your constant bragging-\" The knight raises her hand to silence you and scoffs, \"Please, these people are merely jealous of my success. Instead of complaining, they should simply work as hard as I! Perhaps I shall show you the power you can attain through diligence such as mine!\" She raises her morningstar and prepares to attack you!",
"questGoldenknight2Boss": "Gyllene Riddare",
"questGoldenknight2DropGoldenknight3Quest": "Den Gyllene Riddarens Kedja Del 3: JärnRiddaren (Skriftrulle)",
- "questGoldenknight3Text": "The Golden Knight, Part 3: The Iron Knight",
+ "questGoldenknight3Text": "Den Gyllene Riddaren, Del 3: Järnriddaren",
"questGoldenknight3Notes": "@Jon Arinbjorn cries out to you to get your attention. In the aftermath of your battle, a new figure has appeared. A knight coated in stained-black iron slowly approaches you with sword in hand. The Golden Knight shouts to the figure, \"Father, no!\" but the knight shows no signs of stopping. She turns to you and says, \"I am sorry. I have been a fool, with a head too big to see how cruel I have been. But my father is crueler than I could ever be. If he isn't stopped he'll destroy us all. Here, use my morningstar and halt the Iron Knight!\"",
"questGoldenknight3Completion": "With a satisfying clang, the Iron Knight falls to his knees and slumps over. \"You are quite strong,\" he pants. \"I have been humbled, today.\" The Golden Knight approaches you and says, \"Thank you. I believe we have gained some humility from our encounter with you. I will speak with my father and explain the complaints against us. Perhaps, we should begin apologizing to the other Habiticans.\" She mulls over in thought before turning back to you. \"Here: as our gift to you, I want you to keep my morningstar. It is yours now.\"",
"questGoldenknight3Boss": "Järnriddaren",
@@ -131,7 +131,7 @@
"questAtom1Text": "Attack of the Mundane, Part 1: Dish Disaster!",
"questAtom1Notes": "Du kommer till stränderna vid Sköljsjön för välförtjänt avkoppling.. Men sjön är förorenad med odiskad disk! Hur har det här gått till? Nå, du kan bara inte låta sjön se ut såhär. Det finns bara en sak att göra: diska all disk och rädda din semesterort! Bäst att leta upp diskmedel och röja upp den här röran. Mycket diskmedel...",
"questAtom1CollectSoapBars": "Tvålar",
- "questAtom1Drop": "The SnackLess Monster (Quest Scroll)",
+ "questAtom1Drop": "MindreGodis-Monstret (uppdragsskriftrulle)",
"questAtom2Text": "Attack of the Mundane, Part 2: The SnackLess Monster",
"questAtom2Notes": "Puh! Det börjar se mycket trevligare ut här nu när all disk är undanröjd. Kanske kan du äntligen få roa dig lite nu. Åh, det verkar flyta en pizzakartong i sjön. Nåja, vad är väl ännu en sak att städa upp? Men nej, det är ingen enkel pizzakartong! Plötsligt lyfts lådan från vattnet och visar sig vara ett monsters huvud. Det är inte möjligt! Det mytomspunna Sköljsjöodjuret?! Det sägs ha funnits gömt i sjön sedan urminnes tider: en varelse sprungen ur matrester och sopor från urålderns Habiticaner. Blä!",
"questAtom2Boss": "Sköljsjöodjuret",
@@ -174,7 +174,7 @@
"questTRexUndeadNotes": "As the ancient dinosaurs from the Stoïkalm Steppes roam through Habit City, a cry of terror emanates from the Grand Museum. @Baconsaur shouts, \"The Tyrannosaur skeleton in the museum is stirring! It must have sensed its kin!\" The bony beast bares its teeth and clatters towards you. How can you defeat a creature that is already dead? You'll have to strike fast before it heals itself!",
"questTRexUndeadCompletion": "The Tyrannosaur's glowing eyes grow dark, and it settles back onto its familiar pedestal. Everyone sighs with relief. \"Look!\" @Baconsaur says. \"Some of the fossilized eggs are shiny and new! Maybe they'll hatch for you.\"",
"questTRexUndeadBoss": "Skeletal Tyrannosaur",
- "questTRexUndeadRageTitle": "Skeleton Healing",
+ "questTRexUndeadRageTitle": "Skelettläkning ",
"questTRexUndeadRageDescription": "This bar fills when you don't complete your Dailies. When it is full, the Skeletal Tyrannosaur will heal 30% of its remaining health!",
"questTRexUndeadRageEffect": "`Skeletal Tyrannosaur uses SKELETON HEALING!`\n\nThe monster lets forth an unearthly roar, and some of its damaged bones knit back together!",
"questTRexDropTRexEgg": "Tyrannosaurus (ägg)",
@@ -201,7 +201,7 @@
"questSheepNotes": "As you wander the rural Taskan countryside with friends, taking a \"quick break\" from your obligations, you find a cozy yarn shop. You are so absorbed in your procrastination that you hardly notice the ominous clouds creep over the horizon. \"I've got a ba-a-a-ad feeling about this weather,\" mutters @Misceo, and you look up. The stormy clouds are swirling together, and they look a lot like a... \"We don't have time for cloud-gazing!\" @starsystemic shouts. \"It's attacking!\" The Thunder Ram hurtles forward, slinging bolts of lightning right at you!",
"questSheepBoss": "Thunder Ram",
"questSheepCompletion": "Impressed by your diligence, the Thunder Ram is drained of its fury. It launches three huge hailstones in your direction, and then fades away with a low rumble. Upon closer inspection, you discover that the hailstones are actually three fluffy eggs. You gather them up, and then stroll home under a blue sky.",
- "questSheepDropSheepEgg": "Sheep (Egg)",
+ "questSheepDropSheepEgg": "Får (Ägg)",
"questSheepUnlockText": "Unlocks purchasable sheep eggs in the Market",
"questKrakenText": "The Kraken of Inkomplete",
"questKrakenNotes": "It's a warm, sunny day as you sail across the Inkomplete Bay, but your thoughts are clouded with worries about everything that you still need to do. It seems that as soon as you finish one task, another crops up, and then another...
Suddenly, the boat gives a horrible jolt, and slimy tentacles burst out of the water on all sides! \"We're being attacked by the Kraken of Inkomplete!\" Wolvenhalo cries.
\"Quickly!\" Lemoness calls to you. \"Strike down as many tentacles and tasks as you can, before new ones can rise up to take their place!\"",
@@ -218,9 +218,9 @@
"questDilatoryDistress1Text": "Dilatory Distress, Part 1: Message in a Bottle",
"questDilatoryDistress1Notes": "A message in a bottle arrived from the newly rebuilt city of Dilatory! It reads: \"Dear Habiticans, we need your help once again. Our princess has disappeared and the city is under siege by some unknown watery demons! The mantis shrimps are holding the attackers at bay. Please aid us!\" To make the long journey to the sunken city, one must be able to breathe water. Fortunately, the alchemists @Benga and @hazel can make it all possible! You only have to find the proper ingredients.",
"questDilatoryDistress1Completion": "You don the the finned armor and swim to Dilatory as quickly as you can. The merfolk and their mantis shrimp allies have managed to keep the monsters outside the city for the moment, but they are losing. No sooner are you within the castle walls than the horrifying siege descends!",
- "questDilatoryDistress1CollectFireCoral": "Fire Coral",
- "questDilatoryDistress1CollectBlueFins": "Blue Fins",
- "questDilatoryDistress1DropArmor": "Finned Oceanic Armor (Armor)",
+ "questDilatoryDistress1CollectFireCoral": "Eldkorall",
+ "questDilatoryDistress1CollectBlueFins": "Blåa Fenor",
+ "questDilatoryDistress1DropArmor": "Fjällig Havsrustning (Rustning)",
"questDilatoryDistress2Text": "Dilatory Distress, Part 2: Creatures of the Crevasse",
"questDilatoryDistress2Notes": "The siege can be seen from miles away: thousands of disembodied skulls rushing through a portal in the crevasse walls and making their way towards Dilatory.
When you meet King Manta in his war room, his eyes seem sunken, and his face is worried. \"My daughter Adva disappeared into the Dark Crevasse just before this siege began. Please find her and bring her back home safely! I will lend you my Fire Coral Circlet to aid you. If you succeed, it is yours.\"",
"questDilatoryDistress2Completion": "You vanquish the nightmarish horde of skulls, but you feel no closer to finding Adva. You speak to @Kiwibot, the royal tracker, to see if she has any ideas. \"The mantis shrimps that defend the city must have seen Adva escape,\" @Kiwibot says. \"Try following them into the Dark Crevasse.\"",
@@ -228,26 +228,26 @@
"questDilatoryDistress2RageTitle": "Swarm Respawn",
"questDilatoryDistress2RageDescription": "Swarm Respawn: This bar fills when you don't complete your Dailies. When it is full, the Water Skull Swarm will heal 30% of its remaining health!",
"questDilatoryDistress2RageEffect": "`Water Skull Swarm uses SWARM RESPAWN!`\n\nEmboldened by their victories, more skulls pour forth from the crevasse, bolstering the swarm!",
- "questDilatoryDistress2DropSkeletonPotion": "Skeleton Hatching Potion",
- "questDilatoryDistress2DropCottonCandyBluePotion": "Cotton Candy Blue Hatching Potion",
+ "questDilatoryDistress2DropSkeletonPotion": "Skelett kläckningsdryck",
+ "questDilatoryDistress2DropCottonCandyBluePotion": "Blå sockervadd kläckningsdryck",
"questDilatoryDistress2DropHeadgear": "Fire Coral Circlet (Headgear)",
"questDilatoryDistress3Text": "Dilatory Distress, Part 3: Not a Mere Maid",
"questDilatoryDistress3Notes": "You follow the mantis shrimps deep into the Crevasse, and discover an underwater fortress. Princess Adva, escorted by more watery skulls, awaits you inside the main hall. \"My father has sent you, has he not? Tell him I refuse to return. I am content to stay here and practice my sorcery. Leave now, or you shall feel the wrath of the ocean's new queen!\" Adva seems very adamant, but as she speaks you notice a strange, ruby pendant on her neck glowing ominously... Perhaps her delusions would cease should you break it?",
"questDilatoryDistress3Completion": "Finally, you manage to pull the bewitched pendant from Adva's neck and throw it away. Adva clutches her head. \"Where am I? What happened here?\" After hearing your story, she frowns. \"This necklace was given to me by a strange ambassador - a lady called 'Tzina'. I don't remember anything after that!\"
Back at Dilatory, Manta is overjoyed by your success. \"Allow me to reward you with this trident and shield! I ordered them from @aiseant and @starsystemic as a gift for Adva, but... I'd rather not put weapons in her hands any time soon.\"",
"questDilatoryDistress3Boss": "Adva, the Usurping Mermaid",
- "questDilatoryDistress3DropFish": "Fish (Food)",
+ "questDilatoryDistress3DropFish": "Fisk (Mat)",
"questDilatoryDistress3DropWeapon": "Trident of Crashing Tides (Weapon)",
"questDilatoryDistress3DropShield": "Moonpearl Shield (Shield-Hand Item)",
- "questCheetahText": "Such a Cheetah",
+ "questCheetahText": "En sån Puma",
"questCheetahNotes": "As you hike across the Sloensteadi Savannah with your friends @PainterProphet, @tivaquinn, @Unruly Hyena, and @Crawford, you're startled to see a Cheetah screeching past with a new Habitican clamped in its jaws. Under the Cheetah's scorching paws, tasks burn away as though complete -- before anyone has the chance to actually finish them! The Habitican sees you and yells, \"Please help me! This Cheetah is making me level too quickly, but I'm not getting anything done. I want to slow down and enjoy the game. Make it stop!\" You fondly remember your own fledgling days, and know that you have to help the newbie by stopping the Cheetah!",
"questCheetahCompletion": "The new Habitican is breathing heavily after the wild ride, but thanks you and your friends for your help. \"I'm glad that Cheetah won't be able to grab anyone else. It did leave some Cheetah eggs for us, so maybe we can raise them into more trustworthy pets!\"",
"questCheetahBoss": "Gepard",
"questCheetahDropCheetahEgg": "Gepard (Ägg)",
- "questCheetahUnlockText": "Unlocks purchasable Cheetah eggs in the Market",
+ "questCheetahUnlockText": "Låser upp köpbara Puma-ägg i Marknaden",
"questHorseText": "Rid på Mardrömmen",
"questHorseNotes": "While relaxing in the Tavern with @beffymaroo and @JessicaChase, the talk turns to good-natured boasting about your adventuring accomplishments. Proud of your deeds, and perhaps getting a bit carried away, you brag that you can tame any task around. A nearby stranger turns toward you and smiles. One eye twinkles as he invites you to prove your claim by riding his horse.\nAs you all head for the stables, @UncommonCriminal whispers, \"You may have bitten off more than you can chew. That's no horse - that's a Night-Mare!\" Looking at its stamping hooves, you begin to regret your words...",
"questHorseCompletion": "It takes all your skill, but finally the horse stamps a couple of hooves and nuzzles you in the shoulder before allowing you to mount. You ride briefly but proudly around the Tavern grounds while your friends cheer. The stranger breaks into a broad grin.\n\"I can see that was no idle boast! Your determination is truly impressive. Take these eggs to raise horses of your own, and perhaps we'll meet again one day.\" You take the eggs, the stranger tips his hat... and vanishes.",
- "questHorseBoss": "Night-Mare",
+ "questHorseBoss": "Mardröm",
"questHorseDropHorseEgg": "Häst (ägg)",
"questHorseUnlockText": "Unlocks purchasable Horse eggs in the Market",
"questBurnoutText": "Burnout and the Exhaust Spirits",
@@ -257,7 +257,7 @@
"questBurnoutBoss": "Burnout",
"questBurnoutBossRageTitle": "Exhaust Strike",
"questBurnoutBossRageDescription": "When this gauge fills, Burnout will unleash its Exhaust Strike on Habitica!",
- "questBurnoutDropPhoenixPet": "Phoenix (Pet)",
+ "questBurnoutDropPhoenixPet": "Phoenix (Husdjur)",
"questBurnoutDropPhoenixMount": "Phoenix (Mount)",
"questBurnoutBossRageQuests": "`Burnout uses EXHAUST STRIKE!`\n\nOh no! Despite our best efforts, we've let some Dailies get away from us, and now Burnout is inflamed with energy! With a crackling snarl, it engulfs Ian the Quest Master in a surge of spectral fire. As fallen quest scrolls smolder, the smoke clears, and you see that Ian has been drained of energy and turned into a drifting Exhaust Spirit!\n\nOnly defeating Burnout can break the spell and restore our beloved Quest Master. Let's keep our Dailies in check and defeat this monster before it attacks again!",
"questBurnoutBossRageSeasonalShop": "`Burnout uses EXHAUST STRIKE!`\n\nAhh!!! Our incomplete Dailies have fed the flames of Burnout, and now it has enough energy to strike again! It lets loose a gout of spectral flame that sears the Seasonal Shop. You're horrified to see that the cheery Seasonal Sorceress has been transformed into a drooping Exhaust Spirit.\n\nWe have to rescue our NPCs! Hurry, Habiticans, complete your tasks and defeat Burnout before it strikes for a third time!",
@@ -272,41 +272,53 @@
"questSnakeNotes": "It takes a hardy soul to live in the Sand Dunes of Distraction. The arid desert is hardly a productive place, and the shimmering dunes have led many a traveler astray. However, something has even the locals spooked. The sands have been shifting and upturning entire villages. Residents claim a monster with an enormous serpentine body lies in wait under the sands, and they have all pooled together a reward for whomever will help them find and stop it. The much-lauded snake charmers @EmeraldOx and @PainterProphet have agreed to help you summon the beast. Can you stop the Serpent of Distraction?",
"questSnakeCompletion": "With assistance from the charmers, you banish the Serpent of Distraction. Though you were happy to help the inhabitants of the Dunes, you can't help but feel a little sad for your fallen foe. While you contemplate the sights, @LordDarkly approaches you. \"Thank you! It's not much, but I hope this can express our gratitude properly.\" He hands you some Gold and... some Snake eggs! You will see that majestic animal again after all.",
"questSnakeBoss": "Serpent of Distraction",
- "questSnakeDropSnakeEgg": "Snake (Egg)",
+ "questSnakeDropSnakeEgg": "Orm (Ägg)",
"questSnakeUnlockText": "Unlocks purchasable Snake eggs in the Market",
- "questUnicornText": "Convincing the Unicorn Queen",
+ "questUnicornText": "Att övertyga Enhörningsdrottningen",
"questUnicornNotes": "Conquest Creek has become muddied, destroying Habit City's fresh water supply! Luckily, @Lukreja knows an old legend that claims that a unicorn's horn can purify the foulest of waters. Together with your intrepid guide @UncommonCriminal, you hike through the frozen peaks of the Meandering Mountains. Finally, at the icy summit of Mount Habitica itself, you find the Unicorn Queen amid the glittering snows. \"Your pleas are compelling,\" she tells you. \"But first you must prove that you are worthy of my aid!\"",
"questUnicornCompletion": "Impressed by your diligence and strength, the Unicorn Queen at last agrees that your cause is worthy. She allows you to ride on her back as she soars to the source of Conquest Creek. As she lowers her golden horn to the befouled waters, a brilliant blue light rises from the water’s surface. It is so blinding that you are forced to close your eyes. When you open them a moment later, the unicorn is gone. However, @rosiesully lets out a cry of delight: the water is now clear, and three shining eggs rest at the creek’s edge.",
- "questUnicornBoss": "The Unicorn Queen",
- "questUnicornDropUnicornEgg": "Unicorn (Egg)",
+ "questUnicornBoss": "Enhörningsdrottningen ",
+ "questUnicornDropUnicornEgg": "Enhörning (Ägg)",
"questUnicornUnlockText": "Unlocks purchasable Unicorn eggs in the Market",
- "questSabretoothText": "The Sabre Cat",
+ "questSabretoothText": "Den Sabeltandade Katt",
"questSabretoothNotes": "A roaring monster is terrorizing Habitica! The creature stalks through the wilds and woods, then bursts forth to attack before vanishing again. It's been hunting innocent pandas and frightening the flying pigs into fleeing their pens to roost in the trees. @Inventrix and @icefelis explain that the Zombie Sabre Cat was set free while they were excavating in the ancient, untouched ice-fields of the Stoikalm Steppes. \"It was perfectly friendly at first – I don't know what happened. Please, you have to help us recapture it! Only a champion of Habitica can subdue this prehistoric beast!\"",
"questSabretoothCompletion": "After a long and tiring battle, you wrestle the Zombie Sabre Cat to the ground. As you are finally able to approach, you notice a nasty cavity in one of its sabre teeth. Realising the true cause of the cat's wrath, you're able to get the cavity filled by @Fandekasp, and advise everyone to avoid feeding their friend sweets in future. The Sabre Cat flourishes, and in gratitude, its tamers send you a generous reward – a clutch of sabretooth eggs!",
- "questSabretoothBoss": "Zombie Sabre Cat",
+ "questSabretoothBoss": "Zombie Sabelkatt ",
"questSabretoothDropSabretoothEgg": "Sabretooth (Egg)",
"questSabretoothUnlockText": "Unlocks purchasable Sabretooth eggs in the Market",
"questMonkeyText": "Monstrous Mandrill and the Mischief Monkeys",
"questMonkeyNotes": "The Sloensteadi Savannah is being torn apart by the Monstrous Mandrill and his Mischief Monkeys! They shriek loudly enough to drown out the sound of approaching deadlines, encouraging everyone to avoid their duties and keep monkeying around. Alas, plenty of people ape this bad behavior. If no one stops these primates, soon everyone's tasks will be as red as the Monstrous Mandrill's face!
\"It will take a dedicated adventurer to resist them,\" says @yamato.
\"Quick, let's get this monkey off everyone's backs!\" @Oneironaut yells, and you charge into battle.",
"questMonkeyCompletion": "You did it! No bananas for those fiends today. Overwhelmed by your diligence, the monkeys flee in panic. \"Look,\" says @Misceo. \"They left a few eggs behind.\"
@Leephon grins. \"Maybe a well-trained pet monkey can help you as much as the wild ones hinder you!\"",
"questMonkeyBoss": "Monstrous Mandrill",
- "questMonkeyDropMonkeyEgg": "Monkey (Egg)",
+ "questMonkeyDropMonkeyEgg": "Apa (Ägg)",
"questMonkeyUnlockText": "Unlocks purchasable Monkey eggs in the Market",
"questSnailText": "The Snail of Drudgery Sludge",
"questSnailNotes": "You're excited to begin questing in the abandoned Dungeons of Drudgery, but as soon as you enter, you feel the ground under your feet start to suck at your boots. You look up to the path ahead and see Habiticans mired in slime. @Overomega yells, \"They have too many unimportant tasks and dailies, and they're getting stuck on things that don't matter! Pull them out!\"
\"You need to find the source of the ooze,\" @Pfeffernusse agrees, \"or the tasks that they cannot accomplish will drag them down forever!\"
Pulling out your weapon, you wade through the gooey mud.... and encounter the fearsome Snail of Drudgery Sludge.",
"questSnailCompletion": "You bring your weapon down on the great Snail's shell, cracking it in two, releasing a flood of water. The slime is washed away, and the Habiticans around you rejoice. \"Look!\" says @Misceo. \"There's a small group of snail eggs in the remnants of the muck.\"",
"questSnailBoss": "Snail of Drudgery Sludge",
- "questSnailDropSnailEgg": "Snail (Egg)",
+ "questSnailDropSnailEgg": "Snigel (Ägg)",
"questSnailUnlockText": "Unlocks purchasable Snail eggs in the Market",
- "questBewilderText": "The Be-Wilder",
+ "questBewilderText": "Förvildaren",
"questBewilderNotes": "The party begins like any other.
The appetizers are excellent, the music is swinging, and even the dancing elephants have become routine. Habiticans laugh and frolic amid the overflowing floral centerpieces, happy to have a distraction from their least-favorite tasks, and the April Fool whirls among them, eagerly providing an amusing trick here and a witty twist there.
As the Mistiflying clock tower strikes midnight, the April Fool leaps onto the stage to give a speech.
“Friends! Enemies! Tolerant acquaintances! Lend me your ears.” The crowd chuckles as animal ears sprout from their heads, and they pose with their new accessories.
“As you know,” the Fool continues, “my confusing illusions usually only last a single day. But I’m pleased to announce that I’ve discovered a shortcut that will guarantee us non-stop fun, without having to deal with the pesky weight of our responsibilities. Charming Habiticans, meet my magical new friend... the Be-Wilder!”
Lemoness pales suddenly, dropping her hors d'oeuvres. “Wait! Don’t trust--”
But suddenly mists are pouring into the room, glittering and thick, and they swirl around the April Fool, coalescing into cloudy feathers and a stretching neck. The crowd is speechless as an monstrous bird unfolds before them, its wings shimmering with illusions. It lets out a horrible screeching laugh.
“Oh, it has been ages since a Habitican has been foolish enough to summon me! How wonderful it feels, to have a tangible form at last.”
Buzzing in terror, the magic bees of Mistiflying flee the floating city, which sags from the sky. One by one, the brilliant spring flowers wither up and wisp away.
“My dearest friends, why so alarmed?” crows the Be-Wilder, beating its wings. “There’s no need to toil for your rewards any more. I’ll just give you all the things that you desire!”
A rain of coins pours from the sky, hammering into the ground with brutal force, and the crowd screams and flees for cover. “Is this a joke?” Baconsaur shouts, as the gold smashes through windows and shatters roof shingles.
PainterProphet ducks as lightning bolts crackle overhead, and fog blots out the sun. “No! This time, I don’t think it is!”
Quickly, Habiticans, don’t let this World Boss distract us from our goals! Stay focused on the tasks that you need to complete so we can rescue Mistiflying -- and hopefully, ourselves.",
"questBewilderCompletion": "The Be-Wilder is DEFEATED!
We've done it! The Be-Wilder lets out a ululating cry as it twists in the air, shedding feathers like falling rain. Slowly, gradually, it coils into a cloud of sparkling mist. As the newly-revealed sun pierces the fog, it burns away, revealing the coughing, mercifully human forms of Bailey, Matt, Alex.... and the April Fool himself.
Mistiflying is saved!
The April Fool has enough shame to look a bit sheepish. “Oh, hm,” he says. “Perhaps I got a little…. carried away.”
The crowd mutters. Sodden flowers wash up on sidewalks. Somewhere in the distance, a roof collapses with a spectacular splash.
“Er, yes,” the April Fool says. “That is. What I meant to say was, I’m dreadfully sorry.” He heaves a sigh. “I suppose it can’t all be fun and games, after all. It might not hurt to focus occasionally. Maybe I’ll get a head start on next year’s pranking.”
Redphoenix coughs meaningfully.
“I mean, get a head start on this year’s spring cleaning!” the April Fool says. “Nothing to fear, I’ll have Habit City in spit-shape soon. Luckily nobody is better than I at dual-wielding mops.”
Encouraged, the marching band starts up.
It isn’t long before all is back to normal in Habit City. Plus, now that the Be-Wilder has evaporated, the magical bees of Mistiflying bustle back to work, and soon the flowers are blooming and the city is floating once more.
As Habiticans cuddle the magical fuzzy bees, the April Fool’s eyes light up. “Oho, I’ve had a thought! Why don’t you all keep some of these fuzzy Bee Pets and Mounts? It’s a gift that perfectly symbolizes the balance between hard work and sweet rewards, if I’m going to get all boring and allegorical on you.” He winks. “Besides, they don’t have stingers! Fool’s honor.”",
"questBewilderCompletionChat": "`The Be-Wilder is DEFEATED!`\n\nWe've done it! The Be-Wilder lets out a ululating cry as it twists in the air, shedding feathers like falling rain. Slowly, gradually, it coils into a cloud of sparkling mist. As the newly-revealed sun pierces the fog, it burns away, revealing the coughing, mercifully human forms of Bailey, Matt, Alex.... and the April Fool himself.\n\n`Mistiflying is saved!`\n\nThe April Fool has enough shame to look a bit sheepish. “Oh, hm,” he says. “Perhaps I got a little…. carried away.”\n\nThe crowd mutters. Sodden flowers wash up on sidewalks. Somewhere in the distance, a roof collapses with a spectacular splash.\n\n“Er, yes,” the April Fool says. “That is. What I meant to say was, I’m dreadfully sorry.” He heaves a sigh. “I suppose it can’t all be fun and games, after all. It might not hurt to focus occasionally. Maybe I’ll get a head start on next year’s pranking.”\n\nRedphoenix coughs meaningfully.\n\n“I mean, get a head start on this year’s spring cleaning!” the April Fool says. “Nothing to fear, I’ll have Habit City in spit-shape soon. Luckily nobody is better than I at dual-wielding mops.”\n\nEncouraged, the marching band starts up.\n\nIt isn’t long before all is back to normal in Habit City. Plus, now that the Be-Wilder has evaporated, the magical bees of Mistiflying bustle back to work, and soon the flowers are blooming and the city is floating once more.\n\nAs Habiticans cuddle the magical fuzzy bees, the April Fool’s eyes light up. “Oho, I’ve had a thought! Why don’t you all keep some of these fuzzy Bee Pets and Mounts? It’s a gift that perfectly symbolizes the balance between hard work and sweet rewards, if I’m going to get all boring and allegorical on you.” He winks. “Besides, they don’t have stingers! Fool’s honor.”",
"questBewilderBossRageTitle": "Beguilement Strike",
"questBewilderBossRageDescription": "When this gauge fills, The Be-Wilder will unleash its Beguilement Strike on Habitica!",
- "questBewilderDropBumblebeePet": "Bumblebee (Pet)",
- "questBewilderDropBumblebeeMount": "Bumblebee (Mount)",
+ "questBewilderDropBumblebeePet": "Magiskt Bi (Husdjur)",
+ "questBewilderDropBumblebeeMount": "Magiskt Bi (Riddjur)",
"questBewilderBossRageMarket": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nOh no! Despite our best efforts, we've gotten distracted by the Be-Wilder’s charming illusions and have forgotten to do some of our Dailies! With a cackling cry, the shining bird beats its wings, raising a swarm of mist around Alex the Merchant. When the fog clears, he has been possessed! “Have some free samples!” he shouts gleefully, and begins to hurl exploding eggs and potions at fleeing Habiticans. Not the most favorable of sales, to be sure.\n\nHurry! Let's stay focused on our Dailies to defeat this monster before it possesses someone else.",
"questBewilderBossRageStables": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nAhh!!! Once again the Be-Wilder has dazzled us into neglecting our Dailies, and now it has attacked Matt the Beast Master! With a swirl of mist, Matt transforms into a terrifying winged creature, and all the pets and mounts howl sadly in their stables. Quickly, stay focused on your tasks to defeat this dastardly distraction!",
- "questBewilderBossRageBailey": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nLook out! In the middle of reporting the news, Bailey the Town Crier has been possessed by the Be-Wilder! She lets out an evil, uninformative screech as she rises into the air. Now how will we know what’s going on?\n\nDon't give up... we're so close to defeating this bothersome bird for once and for all!"
+ "questBewilderBossRageBailey": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nLook out! In the middle of reporting the news, Bailey the Town Crier has been possessed by the Be-Wilder! She lets out an evil, uninformative screech as she rises into the air. Now how will we know what’s going on?\n\nDon't give up... we're so close to defeating this bothersome bird for once and for all!",
+ "questFalconText": "The Birds of Preycrastination",
+ "questFalconNotes": "Mt. Habitica is being overshadowed by a looming mountain of To-Dos. It used to be a place to picnic and enjoy a sense of accomplishment, until the neglected tasks grew out of control. Now it's home to fearsome Birds of Preycrastination, foul creatures which stop Habiticans from completing their tasks!
\"It's too hard!\" they caw at @JonArinbjorn and @Onheiron. \"It'll take too long to do right now! It won't make any difference if you wait until tomorrow! Why don't you do something fun instead?\"
No more, you vow. You will climb your personal mountain of To-Dos and defeat the Birds of Preycrastination!",
+ "questFalconCompletion": "Having finally triumphed over the Birds of Preycrastination, you settle down to enjoy the view and your well-earned rest.
\"Wow!\" says @Trogdorina. \"You won!\"
@Squish adds, \"Here, take these eggs I found as a reward.\"",
+ "questFalconBoss": "Birds of Preycrastination",
+ "questFalconDropFalconEgg": "Falk (Ägg)",
+ "questFalconUnlockText": "Låser upp köpbara Falk-ägg i marknaden",
+ "questTreelingText": "The Tangle Tree",
+ "questTreelingNotes": "It's the annual Garden Competition, and everyone is talking about the mysterious project which @aurakami has promised to unveil. You join the crowd on the day of the big announcement, and marvel at the introduction of a moving tree. @fuzzytrees explains that the tree will help with garden maintenance, showing how it can mow the lawn, trim the hedge and prune the roses all at the same time – until the tree suddenly goes wild, turning its secateurs on its creator! The crowd panics as everyone tries to flee, but you aren't afraid – you leap forward, ready to do battle.",
+ "questTreelingCompletion": "You dust yourself off as the last few leaves drift to the floor. In spite of the upset, the Garden Competition is now safe – although the tree you just reduced to a heap of wood chips won't be winning any prizes! \"Still a few kinks to work out there,\" @PainterProphet says. \"Perhaps someone else would do a better job of training the saplings. Do you fancy a go?\"",
+ "questTreelingBoss": "Tangle Tree",
+ "questTreelingDropTreelingEgg": "Treeling (Egg)",
+ "questTreelingUnlockText": "Unlocks purchasable Treeling eggs in the Market"
}
\ No newline at end of file
diff --git a/common/locales/sv/rebirth.json b/common/locales/sv/rebirth.json
index ba1fd047a5..e24f463085 100644
--- a/common/locales/sv/rebirth.json
+++ b/common/locales/sv/rebirth.json
@@ -5,14 +5,14 @@
"rebirthStartOver": "Återfödelse startar om din karaktär från nivå 1.",
"rebirthAdvList1": "Du återvänder till full Hälsa.",
"rebirthAdvList2": "Du har ingen erfarenhet, guld eller utrustning (utan gratis föremål t.ex mysterieföremål) ",
- "rebirthAdvList3": "Dina Vanor, Dagliga- och Att-Göra-Uppgifter återställs till gult, och följder återställs.",
+ "rebirthAdvList3": "Your Habits, Dailies, and To-Dos reset to yellow, and streaks reset, except for challenge tasks.",
"rebirthAdvList4": "Du har startklassen Krigare tills du låser upp klass-systemet.",
"rebirthInherit": "Din nya karaktär ärver vissa saker från dess föregångare:",
"rebirthInList1": "Uppgifter, historik och inställningar är kvar.",
"rebirthInList2": "Utmaning-, Gille- och Sällskapsmedlemskap är kvar.",
"rebirthInList3": "Juveler, Stödjarnivå och Medarbetarnivå är kvar.",
"rebirthInList4": "Föremål erhållna från Juveler eller drops (såsom husdjur eller riddjur) är kvar, men du kan inte komma åt dem innan du låser upp dem igen.",
- "rebirthInList5": "Limited edition equipment you've purchased can be repurchased, even if its event has ended. To repurchase class-specific equipment, you must first change to the correct class.",
+ "rebirthInList5": "Köpt utrustning som är av begränsad utgåva kan återköpas, även om dess event har slutat. För att köpa klass-specifik utrustning måste du först byta till den korrekta klassen. ",
"rebirthEarnAchievement": "Du får även en Bedrift för att du börjar på ett nytt äventyr!",
"beReborn": "Bli pånyttfödd",
"rebirthAchievement": "Du har börjat på ett nytt äventyr! Det här är Pånyttfödelse nummer <%= number %> för dig, och den högsta Leveln du har nått är <%= level %>. För att stapla denna Bedrift, börja ditt nästa nya äventyr när du har nått en ännu högre Level!",
@@ -24,5 +24,6 @@
"rebirthPop": "Starta en ny karaktär från Level 1 medan du behåller bedrifter, samlarobjekt och uppgifter med dess historik.",
"rebirthName": "Pånyttfödelsesfär",
"reborn": "Pånyttfödd, max level <%= reLevel %>",
- "confirmReborn": "Är du säker?"
+ "confirmReborn": "Är du säker?",
+ "rebirthComplete": "You have been reborn!"
}
\ No newline at end of file
diff --git a/common/locales/sv/settings.json b/common/locales/sv/settings.json
index d89419444f..9cbca16b1c 100644
--- a/common/locales/sv/settings.json
+++ b/common/locales/sv/settings.json
@@ -1,7 +1,7 @@
{
"settings": "Inställningar",
"language": "Språk",
- "americanEnglishGovern": "Ifall att det uppstår en oförenlighet i översättningarna kommer den amerikansk engelska versionen framstå.",
+ "americanEnglishGovern": "I den händelse att det uppstår en oförenlighet i översättningarna, så är den amerikansk engelska versionen styrande.",
"helpWithTranslation": "Skulle du vilja hjälpa till med översättningen av Habitica? Toppen! Besök då detta Trello kort.",
"showHeaderPop": "Visa din avatar, Hälsa-/Erfarenhetsmätare, och sällskap.",
"stickyHeader": "Fastklistrat sidhuvud",
@@ -17,9 +17,9 @@
"startAdvCollapsedPop": "Med den här inställningen är Avancerade Inställningar dolda när du öppnar en uppgift för redigering.",
"dontShowAgain": "Visa inte det här igen",
"suppressLevelUpModal": "Visa inte popup när jag levlar upp",
- "suppressHatchPetModal": "Don't show popup when hatching a pet",
- "suppressRaisePetModal": "Don't show popup when raising a pet into a mount",
- "suppressStreakModal": "Don't show popup when attaining a Streak achievement",
+ "suppressHatchPetModal": "Visa inte en popup när ett husdjur kläcks",
+ "suppressRaisePetModal": "Visa inte en popup när ett husdjur blir ett riddjur",
+ "suppressStreakModal": "Visa inte en popup när jag når en följd av bedrifter",
"showTour": "Visa Rundtur",
"restartTour": "Starta om den introducerande rundturen från när du började med Habitica.",
"showBailey": "Visa Bailey",
@@ -47,8 +47,9 @@
"customDayStart": "Skräddarsydd Dagsstart",
"changeCustomDayStart": "Ändra skräddarsydd dagsstarten?",
"sureChangeCustomDayStart": "Är du säker på att du vill ändrar skräddarsydd dagsstarten?",
- "nextCron": "Your Dailies will next reset the first time you use Habitica after <%= time %>. Make sure you have completed your Dailies before this time!",
- "customDayStartInfo1": "Habitica defaults to check and reset your Dailies at midnight in your own time zone each day. You can customize that time here.",
+ "customDayStartHasChanged": "Your custom day start has changed.",
+ "nextCron": "Dina dagliga utmaningar kommer återställas den första gången du använder Habitica efter <%= time %>. Se till att ha avslutat dina dagliga utmaningar innan det händer!",
+ "customDayStartInfo1": "Habitica återställer alltid dina dagliga utmaningar vid midnatt i din tidszon varje dag. Du kan ställa in den tiden här.",
"misc": "Blandat",
"showHeader": "Visa sidhuvud",
"changePass": "Ändra lösenord",
@@ -61,12 +62,23 @@
"newUsername": "Nytt Inloggningsnamn",
"dangerZone": "Riskområde",
"resetText1": "VARNING! Detta kommer återställa många delar av ditt konto. Vi rekommenderar det inte, men somliga anser det praktiskt i början efter att ha provat spelet en kort stund.",
- "resetText2": "You will lose all your levels, gold, and experience points. All your tasks will be deleted permanently and you will lose all of your task's historical data. You will lose all your equipment but you will be able to buy it all back, including all limited edition equipment or subscriber Mystery items that you already own (you will need to be in the correct class to re-buy class-specific gear). You will keep your current class and your pets and mounts. You might prefer to use an Orb of Rebirth instead, which is a much safer option and which will preserve your tasks.",
+ "resetText2": "You will lose all your levels, gold, and experience points. All your tasks (except those from challenges) will be deleted permanently and you will lose all of their historical data. You will lose all your equipment but you will be able to buy it all back, including all limited edition equipment or subscriber Mystery items that you already own (you will need to be in the correct class to re-buy class-specific gear). You will keep your current class and your pets and mounts. You might prefer to use an Orb of Rebirth instead, which is a much safer option and which will preserve your tasks.",
"deleteText": "Are you sure? This will delete your account forever, and it can never be restored! You will need to register a new account to use Habitica again. Banked or spent Gems will not be refunded. If you're absolutely certain, type <%= deleteWord %> into the text box below.",
"API": "API",
+ "APIv3": "API v3",
+ "APIv2": "API v2 - Deprecated",
"APIText": "Kopiera dessa för användning i tredjepartsapplikationer. Du bör behandla ditt API-token som ett lösenord och inte dela med dig av det publikt. Du kan få frågan att ange ditt User ID, men lägg inte ut ditt API-token där andra kan se det, inklusive på Github.",
"APIToken": "API Token (detta är ett lösenord - se varningen ovanför)",
+ "thirdPartyApps": "Tredjehandsappar",
+ "dataToolDesc": "En hemsida som visar dig särskilt information från ditt Habitica-konto, så som statistik om dina uppgifter, din utrustning och dina färdigheter.",
+ "beeminder": "Beeminder",
+ "beeminderDesc": "Let Beeminder automatically monitor your Habitica To-Dos. You can commit to maintaining a target number of To-Dos completed per day or per week, or you can commit to gradually reducing your remaining number of uncompleted To-Dos. (By \"commit\" Beeminder means under threat of paying actual money! But you may also just like Beeminder's fancy graphs.)",
+ "chromeChatExtension": "Google Chromes Chatt-Extension ",
+ "chromeChatExtensionDesc": "The Chrome Chat Extension for Habitica adds an intuitive chat box to all of habitica.com. It allows users to chat in the Tavern, their party, and any guilds they are in.",
+ "otherExtensions": "Other Extensions",
+ "otherDesc": "Hitta andra appar, tillägg och verktyg på Habitica-wikin.",
"resetDo": "Gör det, återställ mitt konto!",
+ "resetComplete": "Reset complete!",
"fixValues": "Ändra Värden",
"fixValuesText1": "Om du har stött på en bugg eller gjort ett misstag som orättvist ändrat din karaktär (skada du inte borde ha fått, Guld du inte egentligen förtjänat, osv.) så kan du manuellt rätta dina nummer här. Ja, det här gör det möjligt att fuska: använd denna funktion klokt, annars saboterar du din egen vanebildning!",
"fixValuesText2": "Notera att du inte kan återställa Följder för individuella uppgifter här. För att göra det, redigera den specifika Dagliga Uppgiften och gå in under Avancerade Alternativ där du hittar fältet Återställ Följd.",
@@ -80,7 +92,7 @@
"usernameSuccess": "Inloggningsnamn framgångsrikt ändrat",
"emailSuccess": "E-postadress framgångsrikt ändrad",
"detachFacebook": "Avregistrera Facebook",
- "detachedFacebook": "Successfully removed Facebook from your account",
+ "detachedFacebook": "Facebook är no borttaget från ditt konto",
"addedLocalAuth": "Successfully added local authentication",
"data": "Data",
"exportData": "Exportera data",
@@ -96,6 +108,7 @@
"emailNotifications": "E-postnotiser",
"wonChallenge": "Du vann en utmaning!",
"newPM": "Mottagit privatmeddelande",
+ "sentGems": "Sent gems!",
"giftedGems": "Skänkta juveler",
"giftedGemsInfo": "<%= amount %> juveler - av <%= name %>",
"giftedSubscription": "Skänkt prenumeration",
@@ -107,7 +120,7 @@
"invitedQuest": "Inbjuden till uppdrag",
"kickedGroup": "Avfärdad från grupp",
"remindersToLogin": "Påminnelser att titta in i Habitica",
- "subscribeUsing": "Subscribe using",
+ "subscribeUsing": "Prenumerera genom",
"unsubscribedSuccessfully": "Avprenumerationen lyckades!",
"unsubscribedTextUsers": "You have successfully unsubscribed from all Habitica emails. You can enable only the emails you want to receive from the settings (requires login).",
"unsubscribedTextOthers": "Du kommer inte få fler e-post från Habitica.",
@@ -121,9 +134,9 @@
"couponPlaceholder": "Skriv in kupongkod",
"couponText": "We sometimes have events and give out coupon codes for special gear. (eg, those who stop by our Wondercon booth)",
"apply": "Anmäl",
- "resubscribe": "Resubscribe",
+ "resubscribe": "Återprenumerera",
"promoCode": "Rabattkod",
- "promoCodeApplied": "Promo Code Applied! Check your inventory",
+ "promoCodeApplied": "Promo-Koden lades till! Kolla ditt lager",
"promoPlaceholder": "Lägg till rabbatkod",
"displayInviteToPartyWhenPartyIs1": "Display Invite To Party button when party has 1 member.",
"saveCustomDayStart": "Spara skräddarsydd dagsstart",
@@ -135,19 +148,24 @@
"webhooks": "Webhooks",
"enabled": "Aktiverad",
"webhookURL": "Webhook URL",
+ "invalidUrl": "invalid url",
+ "invalidEnabled": "the \"enabled\" parameter should be a boolean",
+ "regIdRequired": "RegId is required",
+ "pushDeviceAdded": "Push device added successfully",
+ "pushDeviceAlreadyAdded": "The user already has the push device",
"add": "Lägg till",
"buyGemsGoldCap": "Cap raised to <%= amount %>",
- "mysticHourglass": "<%= amount %> Mystic Hourglass",
- "mysticHourglassText": "Mystic Hourglasses allow purchasing a previous month's Mystery Item set.",
+ "mysticHourglass": "<%= amount %> Mystiskt timglas",
+ "mysticHourglassText": "Mystiskt timglas låter dig köpa föregående månadens uppsättning mystiska föremål.",
"purchasedPlanId": "Recurring $<%= price %> USD each <%= months %> Month(s) (<%= plan %>)",
- "purchasedPlanExtraMonths": "You have <%= months %> months of extra subscription credit.",
- "consecutiveSubscription": "Consecutive Subscription",
- "consecutiveMonths": "Consecutive Months:",
+ "purchasedPlanExtraMonths": "Du har <%= months %> månader kvar av din prenumerationskredit.",
+ "consecutiveSubscription": "Återkommande Prenumeration",
+ "consecutiveMonths": "På rad följande månader:",
"gemCapExtra": "Juvel gräns extra:",
"mysticHourglasses": "Mystiskt timglas:",
"paypal": "PayPal",
"amazonPayments": "Amazon Payments",
"timezone": "Tidszon",
- "timezoneUTC": "Habitica uses the time zone set on your PC, which is: <%= utc %>",
- "timezoneInfo": "If that time zone is wrong, first reload this page using your browser's reload or refresh button to ensure that Habitica has the most recent information. If it is still wrong, adjust the time zone on your PC and then reload this page again.
If you use Habitica on other PCs or mobile devices, the time zone must be the same on them all. If your Dailies have been reseting at the wrong time, repeat this check on all other PCs and on a browser on your mobile devices."
+ "timezoneUTC": "Habitica använder den tidszon som är inställd på din dator, vilket är: <%= utc %>",
+ "timezoneInfo": "If that time zone is wrong, first reload this page using your browser's reload or refresh button to ensure that Habitica has the most recent information. If it is still wrong, adjust the time zone on your PC and then reload this page again.
If you use Habitica on other PCs or mobile devices, the time zone must be the same on them all. If your Dailies have been resetting at the wrong time, repeat this check on all other PCs and on a browser on your mobile devices."
}
\ No newline at end of file
diff --git a/common/locales/sv/spells.json b/common/locales/sv/spells.json
index 06c4050127..613792045b 100644
--- a/common/locales/sv/spells.json
+++ b/common/locales/sv/spells.json
@@ -35,8 +35,8 @@
"spellSpecialSnowballAuraNotes": "Kasta en snöboll på en sällskapsdeltagare! Vad kan gå fel? Varar fram till deltagarens nästa dag.",
"spellSpecialSaltText": "Salt",
"spellSpecialSaltNotes": "Någon kastade en snöboll på dig. Ha ha, väldigt roligt. Se nu till att få bort den här snön från mig!",
- "spellSpecialSpookDustText": "Spöklik hud",
- "spellSpecialSpookDustNotes": "Transformera en vän till en flytande filt med ögon.",
+ "spellSpecialSpookySparklesText": "Spooky Sparkles",
+ "spellSpecialSpookySparklesNotes": "Turn a friend into a floating blanket with eyes!",
"spellSpecialOpaquePotionText": "Ogenomskinlig Dryck",
"spellSpecialOpaquePotionNotes": "Avbryt effekterna av spöklik hud.",
"spellSpecialShinySeedText": "Glänsande Frö",
@@ -46,5 +46,11 @@
"spellSpecialSeafoamText": "Sjöskum",
"spellSpecialSeafoamNotes": "Förvandla en vän till ett sjödjur!",
"spellSpecialSandText": "Sand",
- "spellSpecialSandNotes": "Avbryt effekten av Sjöskum."
+ "spellSpecialSandNotes": "Avbryt effekten av Sjöskum.",
+ "spellNotFound": "Skill \"<%= spellId %>\" not found.",
+ "partyNotFound": "Party not found",
+ "targetIdUUID": "\"targetId\" must be a valid User ID.",
+ "challengeTasksNoCast": "Casting a skill on challenge tasks is not allowed.",
+ "spellNotOwned": "You don't own this skill.",
+ "spellLevelTooHigh": "You must be level <%= level %> to use this skill."
}
\ No newline at end of file
diff --git a/common/locales/sv/subscriber.json b/common/locales/sv/subscriber.json
index d37afb18d2..2658b0109b 100644
--- a/common/locales/sv/subscriber.json
+++ b/common/locales/sv/subscriber.json
@@ -4,6 +4,8 @@
"subDescription": "Köp Juveler med guld, få mystiska saker varje månad, behåll historiken över framsteg, begränsningen på fynd ökas till det dubbla och stöd utvecklarna. Klicka här för mer information.",
"buyGemsGold": "Köp Juveler med Guld",
"buyGemsGoldText": "Köpmannen Alexander säljer dig Juveler mot en kostnad av <%= gemCost %> guld per juvel. Hans månatliga transporter är från början begränsad till <%= gemLimit %> Juveler per månad, men begränsningen ökar med 5 Juveler för var tredje månad av konsekutiv prenumeration, upp till maximalt 50 Juveler per månad!",
+ "mustSubscribeToPurchaseGems": "Must subscribe to purchase gems with GP",
+ "reachedGoldToGemCap": "You've reached the Gold=>Gem conversion cap <%= convCap %> for this month. We have this to prevent abuse / farming. The cap will reset within the first three days of next month.",
"retainHistory": "Behåll ytterligare historik över inlägg",
"retainHistoryText": "Gör att färdiga uppgifter och historik över uppgifter är tillgängliga under en längre tid.",
"doubleDrops": "Fördubblad daglig drop-gräns",
@@ -11,7 +13,7 @@
"mysteryItem": "Exklusiva föremål varje månad",
"mysteryItemText": "Each month you will receive a unique cosmetic item for your avatar! Plus, for every three months of consecutive subscription, the Mysterious Time Travelers will grant you access to historic (and futuristic!) cosmetic items.",
"supportDevs": "Stödjer utvecklarna",
- "supportDevsText": "Your subscription helps keep Habitica thriving and helps fund the development of new features. Thank you for your generosity!",
+ "supportDevsText": "Din prenumeration hjälper Habitica att växa och stödjer utvecklingen av nya tillägg. Tack för din generositet!",
"monthUSD": "USD / Månad",
"organization": "Organisation",
"groupPlans": "Affärsplaner",
@@ -29,8 +31,9 @@
"manageSub": "Klicka för att hantera abonnemang",
"cancelSub": "Avbryt Abonnemang",
"canceledSubscription": "Avbruten Prenumeration",
+ "cancelingSubscription": "Canceling the subscription",
"adminSub": "Administratör-abonnemang.",
- "morePlans": "More Plans Coming Soon",
+ "morePlans": "Mer Planer Kommer Snart",
"organizationSub": "Privat Organisation",
"organizationSubText": "Medlemmar i organisaitonen deltar utanför det vanliga Habitica, och fokusen hålls kvar bland dina deltagare.",
"hostingType": "Servertyp",
@@ -47,7 +50,7 @@
"timeSupportText": "Vi kommer ge support för utbildning, buggar, installation och funktionsförfrågningar.",
"gameFeatures": "Spelfunktioner",
"gold2Gem": "Juveler kan köpas med Guld",
- "gold2GemText": "Members will be able to purchase Gems with gold, meaning none of your participants need to buy anything with real money.",
+ "gold2GemText": "Medlemmar kan köpa Juveler med guld vilket innebär att tingen av dina deltagare behöver köpa någonting med riktiga pengar.",
"infiniteGem": "Obegränsade juveler för ledaren",
"infiniteGemText": "We will provide the organization leaders with as many Gems as they need, for things like challenge prizes, guild-creation, etc.",
"notYetPlan": "Plan ej tillgänglig än, men klicka för att kontakta oss så håller vi dig uppdaterad.",
@@ -57,7 +60,7 @@
"subCanceled": "Prenumerationen kommer bli inaktiv den",
"buyGemsGoldTitle": "Att köpa Juveler med Guld",
"becomeSubscriber": "Bli en Prenumerant",
- "subGemPop": "Because you subscribe to Habitica, you can purchase a number of Gems each month using Gold. You can see how many Gems are available to buy at the corner of the Gem icon.",
+ "subGemPop": "Eftersom du prenumererar på Habitica kan du köpa ett antal Juveler varje månad med Guld. Du kan se hur många Juveler du kan köpa i hörnet av Juvelikonen.",
"subGemName": "Prenumereringsjuveler",
"freeGemsTitle": "Få Juveler gratis",
"maxBuyGems": "Du har köpt alla de Juveler du kan för den här månaden. Mer går att köpa inom de tre första dagarna av nästa månad. Tack för att du prenumererar!",
@@ -65,14 +68,17 @@
"buyGemsAllow2": "fler Juveler den här månaden",
"purchaseGemsSeparately": "Köp ytterligare Juveler",
"subFreeGemsHow": "Habitica players can earn Gems for free by winning challenges that award Gems as a prize, or as a contributor reward by helping the development of Habitica.",
- "seeSubscriptionDetails": "Go to Settings > Subscription to see your subscription details!",
+ "seeSubscriptionDetails": "Gå till Settings > Subscription för att se detaljer om din prenumeration!",
"timeTravelers": "Tidsresenärer",
"timeTravelersTitleNoSub": "<%= linkStartTyler %>Tyler<%= linkEnd %> och <%= linkStartVicky %>Vicky<%= linkEnd %>",
"timeTravelersTitle": "Mystiska Tidsresenärer",
"timeTravelersPopoverNoSub": "You'll need a Mystic Hourglass to summon the mysterious Time Travelers! <%= linkStart %>Subscribers<%= linkEnd %> earn one Mystic Hourglass for every three months of consecutive subscribing. Come back when you have a Mystic Hourglass, and the Time Travelers will fetch you a rare pet, mount, or Subscriber Item Set from the past... or maybe even the future.",
"timeTravelersPopover": "We see you have a Mystic Hourglass, so we will happily travel back in time for you! Please choose the pet, mount, or Mystery Item Set you would like. You can see a list of the past item sets here! If those don't satisfy you, perhaps you'd be interested in one of our fashionably futuristic Steampunk Item Sets?",
- "timeTravelersAlreadyOwned": "Congratulations! You already own everything the Time Travelers currently offer. Thanks for supporting the site!",
+ "timeTravelersAlreadyOwned": "Grattis! Du äger nu allting som Tidsresenärerna erbjuder för tillfället. Tack för att du stödjer hemsidan!",
"mysticHourglassPopover": "A Mystic Hourglass allows you to purchase certain limited-time items, such as monthly Mystery Item Sets and awards from world bosses, from the past!",
+ "mysterySetNotFound": "Mystery set not found, or set already owned.",
+ "mysteryItemIsEmpty": "Mystery items are empty",
+ "mysteryItemOpened": "Mystery item opened.",
"mysterySet201402": "Winged Messenger Set",
"mysterySet201403": "Forest Walker Set",
"mysterySet201404": "Twilight Butterfly Set",
@@ -99,6 +105,8 @@
"mysterySet201601": "Champion of Resolution Set",
"mysterySet201602": "Heartbreaker Set",
"mysterySet201603": "Lucky Clover Set",
+ "mysterySet201604": "Leaf Warrior Set",
+ "mysterySet201605": "Marching Bard Set",
"mysterySet301404": "Steampunk Standard Set",
"mysterySet301405": "Steampunk Accessories Set",
"mysterySetwondercon": "Wondercon",
@@ -106,13 +114,29 @@
"subUpdateTitle": "Uppdatera",
"subUpdateDescription": "Uppdatera Ditt Kort att Bli Laddat",
"notEnoughHourglasses": "Du har inte tillräckligt med Mystiska Timglas.",
- "hourglassBuyEquipSetConfirm": "Buy this full set of items for 1 Mystic Hourglass?",
+ "hourglassBuyEquipSetConfirm": "Vill du köpa en full uppsättning av dessa föremål för 1 Mystiskt Timglas?",
"hourglassBuyItemConfirm": "Köp den här artikeln för 1 Mystiskt Timglas?",
"petsAlreadyOwned": "Du äger redan husdjuret.",
"mountsAlreadyOwned": "Du äger redan riddjuret.",
- "typeNotAllowedHourglass": "Föremålstypen är inte tillgänglig att köpas med Mystiskt Timglas. Tillåtna typer:",
+ "typeNotAllowedHourglass": "Item type not supported for purchase with Mystic Hourglass. Allowed types: <%= allowedTypes %>",
"petsNotAllowedHourglass": "Husdjuret inte tillgängligt att köpas med Mystiskt Timglas.",
"mountsNotAllowedHourglass": "Riddjuret är inte tillgängligt att köpas med Mystiskt Timglas.",
"hourglassPurchase": "Köpte ett föremål med Mystiskt Timglas!",
- "hourglassPurchaseSet": "Köpte ett föremålsset med Mystiskt Timglas!"
+ "hourglassPurchaseSet": "Köpte ett föremålsset med Mystiskt Timglas!",
+ "missingUnsubscriptionCode": "Missing unsubscription code.",
+ "missingSubscription": "User does not have a plan subscription",
+ "missingSubscriptionCode": "Missing subscription code. Possible values: basic_earned, basic_3mo, basic_6mo, google_6mo, basic_12mo.",
+ "cannotDeleteActiveAccount": "You have an active subscription, cancel your plan before deleting your account.",
+ "paymentNotSuccessful": "The payment was not successful",
+ "planNotActive": "The plan hasn't activated yet (due to a PayPal bug). It will begin <%= nextBillingDate %>, after which you can cancel to retain your full benefits",
+ "notAllowedHourglass": "Pet/Mount not available for purchase with Mystic Hourglass.",
+ "readCard": "<%= cardType %> has been read",
+ "cardTypeRequired": "Card type required",
+ "cardTypeNotAllowed": "Unknown card type.",
+ "invalidCoupon": "Invalid coupon code.",
+ "couponUsed": "Coupon code already used.",
+ "noSudoAccess": "You don't have sudo access.",
+ "couponCodeRequired": "The coupon code is required.",
+ "eventRequired": "\"req.params.event\" is required.",
+ "countRequired": "\"req.query.count\" is required."
}
\ No newline at end of file
diff --git a/common/locales/sv/tasks.json b/common/locales/sv/tasks.json
index 65c16b829c..c2e09bd715 100644
--- a/common/locales/sv/tasks.json
+++ b/common/locales/sv/tasks.json
@@ -73,6 +73,7 @@
"clearTags": "Återställ",
"hideTags": "Förminska",
"showTags": "Visa",
+ "toRequired": "You must supply a to value",
"startDate": "Startdatum",
"startDateHelpTitle": "När Borde Detta Uppdraget Börja?",
"startDateHelp": "Ställ in det datum för vilket den här uppgiften börjar. Kommer inte infalla på föreliggande dagar.",
@@ -81,36 +82,50 @@
"streakSingular": "Bedriftaren",
"streakSingularText": "Har utfört en 21-dagars-följd på en daglig utmaning.",
"perfectName": "Perfekta Dagar",
- "perfectText": "Completed all active Dailies on <%= perfects %> days. With this achievement you get a +level/2 buff to all attributes for the next day. Levels greater than 100 don't have any additional effects on buffs.",
+ "perfectText": "Slutfört alla dagliga utmaningar under en <%= perfekt %> dag. Med denna bedrift får du en +level/2 buff till alla egenskaper för nästa dag. Nivåer över 100 får ingen effekt av buffs.",
"perfectSingular": "Perfekt Dag",
- "perfectSingularText": "Completed all active Dailies in one day. With this achievement you get a +level/2 buff to all attributes for the next day. Levels greater than 100 don't have any additional effects on buffs.",
+ "perfectSingularText": "Slutfört alla dagliga utmaningar under en dag. Med denna bedrift får du en +level/2 buff till alla egenskaper för nästa dag. Nivåer över 100 får ingen effekt av buffs.",
"streakerAchievement": "Du har uppnått \"Bedriftaren\" bedriften! 21-dagars-gränsen är en milstolpe för att forma vanor. Du kan fortsätta att lagra denna bedrift för varje 21-dagars period, på denna dagliga utmaning eller någon annan!",
"fortifyName": "Stärkande Dryck",
"fortifyPop": "Återställ alla uppgifter till neutralt värde (gul färg) och återställ all förlorad Hälsa.",
"fortify": "Stärk",
- "fortifyText": "Fortify will return all your tasks to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!",
+ "fortifyText": "Fortify will return all your tasks, except challenge tasks, to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!",
"confirmFortify": "Är du säker?",
+ "fortifyComplete": "Fortify complete!",
"sureDelete": "Är du säker på att du vill radera <%= taskType %> med texten \"<%= taskText %>\"?",
"streakCoins": "Följdbonus!",
- "pushTaskToTop": "Push task to top. Hold ctrl or cmd to push to bottom.",
+ "pushTaskToTop": "Flytta uppgifterna till toppen. Håll ned ctrl eller cmd för att flytta dem nedåt.",
"emptyTask": "Skriv in uppgiftens titel först.",
"dailiesRestingInInn": "Du vilar på värdshuset! Dina Dagliga Uppgifter kommer inte skada dig i natt, men de kommer återställas varje dag. Om du är i ett uppdrag kommer du inte utdela skada eller få föremål förrän du checkar ut från värdshuset, men du kan fortfarande bli skadad av en boss om ditt sällskapskamrater missar sina Dagliga Uppgifter.",
"habitHelp1": "Goda vanor är saker du gör ofta. De belönar dig med Guld och Erfarenhet varje gång du klickar på <%= plusIcon %>.",
"habitHelp2": "Dåliga vanor är saker du vill undvika att göra. De sänker din hälsa varje gång du klickar på <%= minusIcon %>.",
- "habitHelp3": "For inspiration, check out these sample Habits!",
- "newbieGuild": "More questions? Ask in the <%= linkStart %>Newbies Guild<%= linkEnd %>!",
- "dailyHelp1": "Dailies repeat <%= emphasisStart %>every day<%= emphasisEnd %> that they are active. Click the <%= pencilIcon %> to change the days a Daily is active.",
+ "habitHelp3": "För inspiration, kolla på dessa href='http://habitica.wikia.com/wiki/Sample_Habits' target='_blank'>exempel Uppgifter!",
+ "newbieGuild": "Fler frågor? Ställ dom i <%= linkStart %>Newbies Guild<%= linkEnd %>!",
+ "dailyHelp1": "Dagliga utmaningar upprepas <%= emphasisStart %>varje dag<%= emphasisEnd %> som de är aktiva. Klicka på <%= pencilIcon %> För att välja vilka dagar en daglig uppgift skall vara aktiv.",
"dailyHelp2": "Om du inte gör klart dina aktiva Dagliga Utmaningar förlorar du Hälsa när dagen är slut.",
- "dailyHelp3": "Dailies turn <%= emphasisStart %>redder<%= emphasisEnd %> when you miss them, and <%= emphasisStart %>bluer<%= emphasisEnd %> when you complete them. The redder the Daily, the more it will reward you... or hurt you.",
+ "dailyHelp3": "Dagliga utmaningar blir <%= emphasisStart %>rödare<%= emphasisEnd %> när du missar dom och <%= emphasisStart %>blåare<%= emphasisEnd %> när du gör dem. Ju rödare en daglig utmaning är, desto mer kommer den belöna dig... eller skada dig.",
"dailyHelp4": "För att ändra när dagen tar slut gå till <%= linkStart %> Inställningar > Webbsida<%= linkEnd %> > Skräddarsydd Dagsstart.",
- "dailyHelp5": "For inspiration, check out these sample Dailies!",
+ "dailyHelp5": "För inspiration, spana in dessa förslag på dagliga utmaningar!",
"toDoHelp1": "Uppgifter (att göra) börjar gula, sedan blir de mer röda (mer värdefulla) ju längre det tar att göra klart dem.",
"toDoHelp2": "Att-Göra-Uppgifter skadar dig aldrig! De bara belönar dig med Guld och Erfarenhet.",
- "toDoHelp3": "Breaking a To-Do down into a checklist of smaller items will make it less scary, and will increase your points!",
- "toDoHelp4": "For inspiration, check out these sample To-Dos!",
- "rewardHelp1": "The Equipment you buy for your avatar is stored in <%= linkStart %>Inventory > Equipment<%= linkEnd %>.",
+ "toDoHelp3": "Att bryta ned en Att-Göra lista till en checklista bestående av mindre föremål kommer göra det mindre läskigt, och kommer öka dina poäng!",
+ "toDoHelp4": "För inspiration, spana in dessa förslag på dagliga att-göra!",
+ "rewardHelp1": "Utrustningen du köper till din avatar förvaras i <%= linkStart %>Förråd > Utrustning<%= linkEnd %>.",
"rewardHelp2": "Utrustning påverkar din statistik (<%= linkStart %>Avatar > Statistik<%= linkEnd %>).",
"rewardHelp3": "Speciell utrustning kommer finnas här under globala evenemang.",
- "rewardHelp4": "Don't be afraid to set custom Rewards! Check out some samples here.",
- "clickForHelp": "Klicka För Hjälp"
+ "rewardHelp4": "Var inte rädd för att sätta personliga belöningar! Spana in några exempel här.",
+ "clickForHelp": "Klicka För Hjälp",
+ "taskIdRequired": "\"taskId\" must be a valid UUID.",
+ "taskNotFound": "Task not found.",
+ "invalidTaskType": "Task type must be one of \"habit\", \"daily\", \"todo\", \"reward\".",
+ "cantDeleteChallengeTasks": "A task belonging to a challenge can't be deleted.",
+ "checklistOnlyDailyTodo": "Checklists are supported only on dailies and todos",
+ "checklistItemNotFound": "No checklist item was found with given id.",
+ "itemIdRequired": "\"itemId\" must be a valid UUID.",
+ "tagNotFound": "No tag item was found with given id.",
+ "tagIdRequired": "\"tagId\" must be a valid UUID corresponding to a tag belonging to the user.",
+ "positionRequired": "\"position\" is required and must be a number.",
+ "cantMoveCompletedTodo": "Can't move a completed todo.",
+ "directionUpDown": "\"direction\" is required and must be 'up' or 'down'",
+ "alreadyTagged": "The task is already tagged with given tag."
}
\ No newline at end of file
diff --git a/common/locales/uk/backgrounds.json b/common/locales/uk/backgrounds.json
index 734a1668b1..cd3c54c603 100644
--- a/common/locales/uk/backgrounds.json
+++ b/common/locales/uk/backgrounds.json
@@ -133,32 +133,39 @@
"backgroundSnowySunriseNotes": "Споглядайте Зимовий Ранок.",
"backgroundWinterTownText": "Зимове Місто",
"backgroundWinterTownNotes": "Прошвирніться по Зимовому Місту.",
- "backgrounds012016": "SET 20: Released January 2016",
- "backgroundFrozenLakeText": "Frozen Lake",
- "backgroundFrozenLakeNotes": "Skate on a Frozen Lake.",
- "backgroundSnowmanArmyText": "Snowman Army",
- "backgroundSnowmanArmyNotes": "Lead a Snowman Army.",
- "backgroundWinterNightText": "Winter Night",
- "backgroundWinterNightNotes": "Look at the stars of a Winter Night.",
- "backgrounds022016": "SET 21: Released February 2016",
- "backgroundBambooForestText": "Bamboo Forest",
- "backgroundBambooForestNotes": "Stroll through the Bamboo Forest.",
- "backgroundCozyLibraryText": "Cozy Library",
- "backgroundCozyLibraryNotes": "Read in the Cozy Library.",
- "backgroundGrandStaircaseText": "Grand Staircase",
- "backgroundGrandStaircaseNotes": "Stride down the Grand Staircase.",
- "backgrounds032016": "SET 22: Released March 2016",
- "backgroundDeepMineText": "Deep Mine",
- "backgroundDeepMineNotes": "Find precious metals in a Deep Mine.",
- "backgroundRainforestText": "Rainforest",
- "backgroundRainforestNotes": "Venture into a Rainforest.",
- "backgroundStoneCircleText": "Circle of Stones",
+ "backgrounds012016": "Набір 20: випущений в січні 2016",
+ "backgroundFrozenLakeText": "Замерзле Озеро",
+ "backgroundFrozenLakeNotes": "Прокатайтесь по Замерзлому Озері.",
+ "backgroundSnowmanArmyText": "Армія Сніговиків",
+ "backgroundSnowmanArmyNotes": "Лідер Армії Сніговиків",
+ "backgroundWinterNightText": "Зимова Ніч",
+ "backgroundWinterNightNotes": "Дивитись на зорі Зимової Ночі.",
+ "backgrounds022016": "Набір 21: випущений в лютому 2016",
+ "backgroundBambooForestText": "Бамбуковий ліс",
+ "backgroundBambooForestNotes": "Прогуляйтеся по бамбуковому лісі",
+ "backgroundCozyLibraryText": "Затишна Бібліотека",
+ "backgroundCozyLibraryNotes": "Читати в Затишній Бібліотеці.",
+ "backgroundGrandStaircaseText": "Парадні Сходи.",
+ "backgroundGrandStaircaseNotes": "Спустіться Парадними Сходами.",
+ "backgrounds032016": "Набір 22: випущений в березні 2016",
+ "backgroundDeepMineText": "Глибока Шахта",
+ "backgroundDeepMineNotes": "Шукати дорогоцінні метали в Глибокій Шахті",
+ "backgroundRainforestText": "Дощовий Ліс",
+ "backgroundRainforestNotes": "Відправтесь в Тропічний Ліс.",
+ "backgroundStoneCircleText": "Коло з Каменів",
"backgroundStoneCircleNotes": "Cast spells in a Circle of Stones.",
- "backgrounds042016": "SET 23: Released April 2016",
- "backgroundArcheryRangeText": "Archery Range",
- "backgroundArcheryRangeNotes": "Practice on the Archery Range.",
- "backgroundGiantFlowersText": "Giant Flowers",
- "backgroundGiantFlowersNotes": "Frolic atop Giant Flowers.",
- "backgroundRainbowsEndText": "End of the Rainbow",
- "backgroundRainbowsEndNotes": "Discover gold at the End of the Rainbow."
+ "backgrounds042016": "Набір 23: випущений в квітні 2016",
+ "backgroundArcheryRangeText": "Лучне Стрільбище",
+ "backgroundArcheryRangeNotes": "Попрактикуйтеся на Лучному Стрільбищі.",
+ "backgroundGiantFlowersText": "Гігантські Квіти",
+ "backgroundGiantFlowersNotes": "Повеселіться на Гігантських Квітках.",
+ "backgroundRainbowsEndText": "Кінець Радуги.",
+ "backgroundRainbowsEndNotes": "Знайдіть золото на Кінці Радуги.",
+ "backgrounds052016": "SET 24: Released May 2016",
+ "backgroundBeehiveText": "Beehive",
+ "backgroundBeehiveNotes": "Buzz and dance in a Beehive.",
+ "backgroundGazeboText": "Gazebo",
+ "backgroundGazeboNotes": "Battle a Gazebo.",
+ "backgroundTreeRootsText": "Tree Roots",
+ "backgroundTreeRootsNotes": "Explore the Tree Roots."
}
\ No newline at end of file
diff --git a/common/locales/uk/challenge.json b/common/locales/uk/challenge.json
index cdd6063b55..0b1ffe2242 100644
--- a/common/locales/uk/challenge.json
+++ b/common/locales/uk/challenge.json
@@ -63,5 +63,20 @@
"congratulations": "Вітаємо!",
"hurray": "Ура!",
"noChallengeOwner": "немає власника",
- "noChallengeOwnerPopover": "У цього випробування немає власника, тому що людина, що створила його, вилучила свій акаунт."
+ "noChallengeOwnerPopover": "У цього випробування немає власника, тому що людина, що створила його, вилучила свій акаунт.",
+ "challengeMemberNotFound": "User not found among challenge's members",
+ "onlyGroupLeaderChal": "Only the group leader can create challenges",
+ "tavChalsMinPrize": "Prize must be at least 1 Gem for Tavern challenges.",
+ "cantAfford": "You can't afford this prize. Purchase more gems or lower the prize amount.",
+ "challengeIdRequired": "\"challengeId\" must be a valid UUID.",
+ "winnerIdRequired": "\"winnerId\" must be a valid UUID.",
+ "challengeNotFound": "Challenge not found.",
+ "onlyLeaderDeleteChal": "Only the challenge leader can delete it.",
+ "onlyLeaderUpdateChal": "Only the challenge leader can update it.",
+ "winnerNotFound": "Winner with id \"<%= userId %>\" not found or not part of the challenge.",
+ "noCompletedTodosChallenge": "\"includeComepletedTodos\" is not supported when fetching a challenge tasks.",
+ "userTasksNoChallengeId": "When \"tasksOwner\" is \"user\" \"challengeId\" can't be passed.",
+ "onlyChalLeaderEditTasks": "Tasks belonging to a challenge can only be edited by the leader.",
+ "userAlreadyInChallenge": "User is already participating in this challenge.",
+ "cantOnlyUnlinkChalTask": "Only broken challenges tasks can be unlinked."
}
\ No newline at end of file
diff --git a/common/locales/uk/character.json b/common/locales/uk/character.json
index eaaa7ac5d3..35a5f1c779 100644
--- a/common/locales/uk/character.json
+++ b/common/locales/uk/character.json
@@ -1,7 +1,8 @@
{
+ "communityGuidelinesWarning": "Please keep in mind that your Display Name, profile photo, and blurb must comply with the Community Guidelines (e.g. no profanity, no adult topics, no insults, etc). If you have any questions about whether or not something is appropriate, feel free to email leslie@habitica.com!",
"statsAch": "Герой і здобутки",
"profile": "Профіль",
- "avatar": "Customize Avatar",
+ "avatar": "Редагувати Аватар",
"other": "Інше",
"fullName": "Повне ім'я",
"displayName": "Ім’я на показ",
@@ -34,7 +35,7 @@
"beard": "Борода",
"mustache": "Вуса",
"flower": "Квітка",
- "wheelchair": "Wheelchair",
+ "wheelchair": "Крісло на коліщатках",
"basicSkins": "Прості кольори",
"rainbowSkins": "Кольори веселки",
"pastelSkins": "Пастельні кольори",
@@ -84,7 +85,7 @@
"allocateInt": "Призначено очок інтелекту:",
"allocateIntPop": "Додати очко інтелекту",
"noMoreAllocate": "Ви досягли 100-го рівня, тому ви більше не будете отримувати Очки Характеристик. Ви можете продовжувати підвищувати свій рівень або почати ваші пригоди з першого рівня, скористувавшись Кулею Переродження, яку можно бескоштовно отримати на Ринку.",
- "stats": "Avatar Stats",
+ "stats": "Cтатистика персонажа",
"strength": "Сила",
"strengthText": "Сила зменшує ступінь небезпеки завдань (почервоніння), збільшує приріст від випадкових \"критичних ударів\" та допомагає завдавати ушкодження монстрам-босам.",
"constitution": "Комплекція",
@@ -109,6 +110,7 @@
"mage": "Чародій",
"mystery": "Таємниця",
"changeClass": "Змінити клас, перерозподілити пункти характеристик",
+ "lvl10ChangeClass": "To change class you must be at least level 10.",
"levelPopover": "Кожен рівень надає Вам одне очко, яке можна призначити на характеристику на Ваш вибір. Ви можете це зробити самотужки, а можна дозволити грі вирішувати за Вас, обравши один з варіянтів автоматичного розподілу.",
"unallocated": "Нерозподілені очки характеристик",
"haveUnallocated": "Ви маєте <%= points %> нерозподілених очок характеристик",
@@ -126,9 +128,9 @@
"mageText": "Чародії швидко вчаться, отримуючи досвід та рівні швидше, аніж інші класи. Також вигідно використовують ману на особливі здібності. Грайте чародієм, якщо Вам подобаються тактичні особливості Habitica, або Вас сильно мотивує отримання нових рівнів та відкривання додаткових можливостей.",
"rogueText": "Шибайголови обожнюють збагачуватися, отримуючи більше Золота, ніж інші класи. Також вони експерти зі знайдення випадкових предметів. Їхня неперевершена потайливість дозволяє їм ухилитися від наслідків невиконаних щоденних завдань. Грайте Шибайголовою, якщо Вас мотивують нагороди, досягнення та жага до награбованого та значків!",
"healerText": "Цілителі невразливі до ушкоджень та поширюють захист на інших. Невиконані щоденні завдання та шкідливі звички ледь турбують їх, у них є шляхи підняти своє Здоров'я після поразки. Грайте цілителем, якщо Вам подобається допомагати іншим членам гурту, або Вас надихає ідея через старанну працю обманути Смерть!",
- "optOutOfClasses": "Opt Out",
- "optOutOfPMs": "Opt Out",
- "optOutOfClassesText": "Can't be bothered with classes? Want to choose later? Opt out - you'll be a warrior with no special abilities. You can read about the class system later on the wiki and enable classes at any time under User -> Stats.",
+ "optOutOfClasses": "Відмовитися від вибору класу",
+ "optOutOfPMs": "Відмовитися від вибору класу",
+ "optOutOfClassesText": "Не хочете займати себе вибором класу? Хочете обрати потім? Відмовтеся - ви залишитеся воїном без особливих класових навиків. Ви завжди можете прочитати про класову систему пізніше на вікі та ввімкнути класи коли завгодно в меню Користувач -> Характеристики.",
"select": "Обрати",
"stealth": "Потайливість",
"stealthNewDay": "Коли почнеться новий день, ви ухилитеся від ушкоджень за невиконані щоденні завдання у такому числі.",
@@ -137,7 +139,7 @@
"respawn": "Відродження!",
"youDied": "Ви загинули!",
"dieText": "Ви втратили один рівень, усе Ваше золото та випадковий предмет спорядження. Підводьтеся та спробуйте ще раз! Приборкайте шкідливі звички, будьте старанними у виконанні щоденних завдань і тримайтеся від Смерті подалі, використовуючи еліксир здоров'я, якщо відчуваєте слабкість!",
- "sureReset": "Are you sure? This will reset your character's class and allocated points (you'll get them all back to re-allocate), and costs 3 gems.",
+ "sureReset": "Ви впевнені? Це коштує 3 самцвіти і відмінить Ваш вибір класу та розподілення очків характеристик (Ви зможете розподілити їх по-новому).",
"purchaseFor": "Придбати за <%= cost %> самоцвітів?",
"notEnoughMana": "Недостатньо мани.",
"invalidTarget": "Хибна ціль",
@@ -162,7 +164,9 @@
"con": "КОМ",
"per": "СПР",
"int": "ІНТ",
- "showQuickAllocation": "Show stat allocation",
- "hideQuickAllocation": "Hide stat allocation",
- "quickAllocationLevelPopover": "Each level earns you one point to assign to an attribute of your choice. You can do so manually, or let the game decide for you using one of the Automatic Allocation options found in User -> Stats."
+ "showQuickAllocation": "Показати розподілення характеристик",
+ "hideQuickAllocation": "Сховати розподілення характеристик",
+ "quickAllocationLevelPopover": "Кожного рівню Ви отримуєте одне очко, яке можна призначити на характеристику на Ваш вибір. Ви можете зробити це самотужки, або ж дозволити грі зробити вибір за Вас, обравши один з варіантів автоматичного розподілу.",
+ "invalidAttribute": "\"<%= attr %>\" is not a valid attribute.",
+ "notEnoughAttrPoints": "You don't have enough attribute points."
}
\ No newline at end of file
diff --git a/common/locales/uk/communityguidelines.json b/common/locales/uk/communityguidelines.json
index 539bd45755..a82d33d247 100644
--- a/common/locales/uk/communityguidelines.json
+++ b/common/locales/uk/communityguidelines.json
@@ -25,7 +25,7 @@
"commGuidePara011b": "на GitHub/Wikia",
"commGuidePara011c": "на Wikia",
"commGuidePara011d": "на GitHub",
- "commGuidePara012": "If you have an issue or concern about a particular Mod, please send an email to Lemoness (leslie@habitica.com).",
+ "commGuidePara012": "Якщо у Вас є якісь питання щодо певного Модератора, будь ласка відправте повідомлення на почту Lemoness (leslie@habitica.com).",
"commGuidePara013": "У такій великій спільноті, як Habitica, користувачі прибувають і відбувають, але трапляється так, що модератори мають потребу скласти свої повноваження та відпочити. Пройти шляхом Модератора Емерітуса. Вони більше не мають влади Модератора, але ми поважаємо і шануємо їх особистий внесок!",
"commGuidePara014": "Поважний Модератор:",
"commGuideHeadingPublicSpaces": "Громадські місця у Habitica",
@@ -103,12 +103,12 @@
"commGuidePara055": "The following are some examples of Moderate Infractions. This is not a comprehensive list.",
"commGuideList06A": "Ignoring or Disrespecting a Mod. This includes publicly complaining about moderators or other users/publicly glorifying or defending banned users. If you are concerned about one of the rules or Mods, please contact Lemoness via email (leslie@habitica.com).",
"commGuideList06B": "Backseat Modding. To quickly clarify a relevant point: A friendly mention of the rules is fine. Backseat modding consists of telling, demanding, and/or strongly implying that someone must take an action that you describe to correct a mistake. You can alert someone to the fact that they have committed a transgression, but please do not demand an action-for example, saying, \"Just so you know, profanity is discouraged in the Tavern, so you may want to delete that,\" would be better than saying, \"I'm going to have to ask you to delete that post.\"",
- "commGuideList06C": "Repeated Violation of Public Space Guidelines",
+ "commGuideList06C": "Багатократне порушення Правил та Умов поведінки у Громадських місцях ",
"commGuideList06D": "Repeated Minor Infractions",
"commGuideHeadingMinorInfractions": "Незначні порушення",
"commGuidePara056": "Minor Infractions, while discouraged, still have minor consequences. If they continue to occur, they can lead to more severe consequences over time.",
"commGuidePara057": "The following are some examples of Minor Infractions. This is not a comprehensive list.",
- "commGuideList07A": "First-time violation of Public Space Guidelines",
+ "commGuideList07A": "Перше порушення Правил та Умов поведінки у Громадських місцях ",
"commGuideList07B": "Any statements or actions that trigger a \"Please Don't\". When a Mod has to say \"Please Don't do this\" to a user, it can count as a very minor infraction for that user. An example might be \"Mod Talk: Please Don't keep arguing in favor of this feature idea after we've told you several times that it isn't feasible.\" In many cases, the Please Don't will be the minor consequence as well, but if Mods have to say \"Please Don't\" to the same user enough times, the triggering Minor Infractions will start to count as Moderate Infractions.",
"commGuideHeadingConsequences": "Наслідки",
"commGuidePara058": "In Habitica -- as in real life -- every action has a consequence, whether it is getting fit because you've been running, getting cavities because you've been eating too much sugar, or passing a class because you've been studying.",
@@ -130,11 +130,11 @@
"commGuideList10F": "Putting users on \"Probation\"",
"commGuideHeadingMinorConsequences": "Examples of Minor Consequences",
"commGuideList11A": "Reminders of Public Space Guidelines",
- "commGuideList11B": "Warnings",
+ "commGuideList11B": "Застереження",
"commGuideList11C": "Requests",
"commGuideList11D": "Deletions (Mods/Staff may delete problematic content)",
"commGuideList11E": "Edits (Mods/Staff may edit problematic content)",
- "commGuideHeadingRestoration": "Restoration",
+ "commGuideHeadingRestoration": "Відновлення",
"commGuidePara061": "Habitica is a land devoted to self-improvement, and we believe in second chances. If you commit an infraction and receive a consequence, view it as a chance to evaluate your actions and strive to be a better member of the community.",
"commGuidePara062": "The email that you receive explaining the consequences of your actions (or, in the case of minor consequences, the Mod/Staff announcement) is a good source of information. Cooperate with any restrictions which have been imposed, and endeavor to meet the requirements to have any penalties lifted.",
"commGuidePara063": "If you do not understand your consequences, or the nature of your infraction, ask the Staff/Moderators for help so you can avoid committing infractions in the future.",
diff --git a/common/locales/uk/content.json b/common/locales/uk/content.json
index 304553c04a..b036897507 100644
--- a/common/locales/uk/content.json
+++ b/common/locales/uk/content.json
@@ -113,6 +113,12 @@
"questEggSnailText": "Snail",
"questEggSnailMountText": "Snail",
"questEggSnailAdjective": "a slow but steady",
+ "questEggFalconText": "Falcon",
+ "questEggFalconMountText": "Falcon",
+ "questEggFalconAdjective": "a swift",
+ "questEggTreelingText": "Treeling",
+ "questEggTreelingMountText": "Treeling",
+ "questEggTreelingAdjective": "a leafy",
"eggNotes": "Знайдіть зілля вилуплення, щоби вилити на це яйце і з нього вилупиться <%= eggAdjective(locale) %> <%= eggText(locale) %>.",
"hatchingPotionBase": "Простий",
"hatchingPotionWhite": "Білий",
@@ -126,6 +132,7 @@
"hatchingPotionGolden": "Золотий",
"hatchingPotionSpooky": "Моторошний",
"hatchingPotionPeppermint": "Peppermint",
+ "hatchingPotionFloral": "Floral",
"hatchingPotionNotes": "Вилийте це на яйце і з нього вилупиться <%= potText(locale) %> улюбленець.",
"premiumPotionAddlNotes": "Не можна використати на квестових яйцях улюбленців",
"foodMeat": "М'ясо",
diff --git a/common/locales/uk/contrib.json b/common/locales/uk/contrib.json
index fb85944299..369a989a9c 100644
--- a/common/locales/uk/contrib.json
+++ b/common/locales/uk/contrib.json
@@ -35,8 +35,12 @@
"hallContributors": "Hall of Contributors",
"hallPatrons": "Зала Благодійників",
"rewardUser": "Нагородити гравця",
- "UUID": "UUID",
+ "UUID": "User ID",
"loadUser": "Завантажити користувача",
+ "noAdminAccess": "You don't have admin access.",
+ "pageMustBeNumber": "req.query.page must be a number",
+ "userNotFound": "User not found.",
+ "invalidUUID": "UUID must be valid",
"title": "Титул",
"moreDetails": "Детальніше (1-7)",
"moreDetails2": "детальніше (8-9)",
@@ -52,8 +56,8 @@
"visitHeroes": "Відвідати Залу Героїв (вкладники й меценати)",
"conLearn": "Довідатися більше про нагороди вкладникам",
"conLearnHow": "Довідатися, як стати співавтором Habitica",
- "surveysSingle": "Helped Habitica grow by filling out a survey. There are no active surveys.",
- "surveysMultiple": "Допомагає Habitica зростати, заповнивши <%= surveys %> опитувань. Наразі активних опитувань немає.",
+ "surveysSingle": "Helped Habitica grow, either by filling out a survey or helping with a major testing effort. Thank you!",
+ "surveysMultiple": "Helped Habitica grow on <%= surveys %> occasions, either by filling out a survey or helping with a major testing effort. Thank you!",
"currentSurvey": "Поточне опитування",
"surveyWhen": "Усіх учасників буде нагороджено відзнакою, коли опитування буде оброблено, наприкінці березня",
"blurbInbox": "This is where your private messages are stored! You can send someone a message by clicking on the envelope icon next to their name in Tavern, Party, or Guild Chat. If you've received an inappropriate PM, you should email a screenshot of it to Lemoness (leslie@habitica.com)",
diff --git a/common/locales/uk/death.json b/common/locales/uk/death.json
index 3cc675b2c9..8476f5a2c1 100644
--- a/common/locales/uk/death.json
+++ b/common/locales/uk/death.json
@@ -12,5 +12,6 @@
"losingHealthQuickly": "Швидко втрачаєте здоров'я?",
"lowHealthTips3": "Невиконані щоденні задачі приносять вам шкоду уночі, тож не додавайте забагато спочатку!",
"lowHealthTips4": "If a Daily isn't due on a certain day, you can disable it by clicking the pencil icon.",
- "goodLuck": "Удачі!"
+ "goodLuck": "Удачі!",
+ "cannotRevive": "Cannot revive if not dead"
}
\ No newline at end of file
diff --git a/common/locales/uk/faq.json b/common/locales/uk/faq.json
index 000a91cab8..db716cec2c 100644
--- a/common/locales/uk/faq.json
+++ b/common/locales/uk/faq.json
@@ -1,17 +1,17 @@
{
- "frequentlyAskedQuestions": "Frequently Asked Questions",
- "faqQuestion0": "I'm confused. Where do I get an overview?",
- "iosFaqAnswer0": "First, you'll set up tasks that you want to do in your everyday life. Then, as you complete the tasks in real life and check them off, you'll earn experience and gold. Gold is used to buy equipment and some items, as well as custom rewards. Experience causes your character to level up and unlock content such as Pets, Skills, and Quests! You can customize your character under Menu > Customize Avatar.\n\n Some basic ways to interact: click the (+) in the upper-right-hand corner to add a new task. Tap on an existing task to edit it, and swipe left on a task to delete it. You can sort tasks using Tags in the upper-left-hand corner, and expand and contract checklists by clicking on the checklist bubble.",
- "webFaqAnswer0": "First, you'll set up tasks that you want to do in your everyday life. Then, as you complete the tasks in real life and check them off, you'll earn Experience and Gold. Gold is used to buy equipment and some items, as well as custom rewards. Experience causes your character to level up and unlock content such as pets, skills, and quests! For more detail, check out a step-by-step overview of the game at [Help -> Overview for New Users](https://habitica.com/static/overview).",
- "faqQuestion1": "How do I set up my tasks?",
- "iosFaqAnswer1": "Good Habits (the ones with a +) are tasks that you can do many times a day, such as eating vegetables. Bad Habits (the ones with a -) are tasks that you should avoid, like biting nails. Habits with a + and a - have a good choice and a bad choice, like taking the stairs vs. taking the elevator. Good Habits award experience and gold. Bad Habits subtract health.\n\n Dailies are tasks that you have to do every day, like brushing your teeth or checking your email. You can adjust the days that a Daily is due by tapping to edit it. If you skip a Daily that is due, your avatar will take damage overnight. Be careful not to add too many Dailies at once!\n\n To-Dos are your To-Do list. Completing a To-Do earns you gold and experience. You never lose health from To-Dos. You can add a due date to a To-Do by tapping to edit.",
- "webFaqAnswer1": "Good Habits (the ones with a ) are tasks that you can do many times a day, such as eating vegetables. Bad Habits (the ones with a ) are tasks that you should avoid, like biting nails. Habits with a and a have a good choice and a bad choice, like taking the stairs vs. taking the elevator. Good Habits award Experience and Gold. Bad Habits subtract Health.\n
\n Dailies are tasks that you have to do every day, like brushing your teeth or checking your email. You can adjust the days that a Daily is due by clicking the pencil item to edit it. If you skip a Daily that is due, your avatar will take damage overnight. Be careful not to add too many Dailies at once!\n
\n To-Dos are your To-Do list. Completing a To-Do earns you Gold and Experience. You never lose Health from To-Dos. You can add a due date to a To-Do by clicking the pencil icon to edit.",
- "faqQuestion2": "What are some sample tasks?",
- "iosFaqAnswer2": "The wiki has four lists of sample tasks to use as inspiration:\n
\n * [Sample Habits](http://habitica.wikia.com/wiki/Sample_Habits)\n * [Sample Dailies](http://habitica.wikia.com/wiki/Sample_Dailies)\n * [Sample To-Dos](http://habitica.wikia.com/wiki/Sample_To-Dos)\n * [Sample Custom Rewards](http://habitica.wikia.com/wiki/Sample_Custom_Rewards)",
- "webFaqAnswer2": "The wiki has four lists of sample tasks to use as inspiration:\n * [Sample Habits](http://habitica.wikia.com/wiki/Sample_Habits)\n * [Sample Dailies](http://habitica.wikia.com/wiki/Sample_Dailies)\n * [Sample To-Dos](http://habitica.wikia.com/wiki/Sample_To-Dos)\n * [Sample Custom Rewards](http://habitica.wikia.com/wiki/Sample_Custom_Rewards)",
- "faqQuestion3": "Why do my tasks change color?",
- "iosFaqAnswer3": "Your tasks change color based on how well you are currently accomplishing them! Each new task starts out as a neutral yellow. Perform Dailies or positive Habits more frequently and they move toward blue. Miss a Daily or give in to a bad Habit and the task moves toward red. The redder a task, the more rewards it will give you, but if it's a Daily or bad Habit, the more it will hurt you! This helps motivate you to complete the tasks that are giving you trouble.",
- "webFaqAnswer3": "Your tasks change color based on how well you are currently accomplishing them! Each new task starts out as a neutral yellow. Perform Dailies or positive Habits more frequently and they move toward blue. Miss a Daily or give in to a bad Habit and the task moves toward red. The redder a task, the more rewards it will give you, but if it’s a Daily or bad Habit, the more it will hurt you! This helps motivate you to complete the tasks that are giving you trouble.",
+ "frequentlyAskedQuestions": "Поширені запитання",
+ "faqQuestion0": "Я в розгубленості. Що робити?",
+ "iosFaqAnswer0": "Спочатку Ви ставите перед собою завдання, котрі Вам необхідно виконати в своєму повсякденному житті. По мірі виконання цих завдань в реальному житті, Ви відмічаєте їх галочками та заробляєте досвід і золото. Золото використовується для придбання спорядження та інших речей, а також нагород, що створюєте Ви самі. Завдяки досвіду, Ваш персонаж набирає рівні і відкриває новий контент, наприклад улюбленців, навички і квести! Ви можете налаштувати свій аватар в Меню->Налаштування аватару.\n\nДеякі основні способи взаємодії: натисніть (+) у верхньому правому куті, щоб додати нове завдання. Натисніть на на вже існуюче завдання, щоб його змінити; потягніть завдання вліво для видалення. Ви можете сортувати завдання, використовуючи Теги в лівому верхньому куті, розгортати та згортати списки підзавдань, натиснувши на позначку списку.",
+ "webFaqAnswer0": "Спочатку Ви ставите перед собою завдання, котрі Вам необхідно виконати в своєму повсякденному житті. По мірі виконання цих завдань в реальному житті, Ви відмічаєте їх галочками та заробляєте Досвід і Золото. Золото використовується для придбання спорядження та інших речей, а також нагород, що створюєте Ви самі. Завдяки Досвіду, Ваш персонаж набирає рівні і відкриває новий контент, наприклад улюбленців, навички і квести! Подробиці можна дізнатися в нашому покроковому гіді на сторінці [Допомога -> Інформація для новачків] (https://habitica.com/static/overview).",
+ "faqQuestion1": "Як я можу створити завдання?",
+ "iosFaqAnswer1": "Корисні звички (ті, що позначені знаком +) - це завдання, які Ви можете виконувати багато разів за день: наприклад, вживати овочі. Шкідиві звички (зі знаком -) - це ті дії, від яких Вам варто відмовитися: наприклад, гризти нігті. Звички, навпроти яких стоять і +, і -, припускають двоїстий вибір - або в хороший, або в поганий бік: наприклад підйом по сходах пішки проти користування ліфтом. Корисні звички винагороджуються досвідом та золотом. Погані звички віднімають здоров'я.\n\nЩоденні завдання - це завдання, котрі ви маєте виконувати кожен день, наприклад чистити зуби чи перевіряти електронну пошту. Ви можете вказати дні, в які щоденне завдання обов'язкове або необов'язкове до виконання; для цього варто лише натиснути на нього для редагування. Якщо ви пропустите обов'язкове завдання, ваш персонаж втратить здоров'я наступної ночі. Будьте уважні і не додавайте забагато щоденних завдань відразу!\n\nЗадачі - це список одноразових справ, котрі Вам необхідно виконати. Виконання задач приносить Вам золото та досвід. Ви не втрачатимете здоров'я, якщо не виконаєте задачу. Ви можете вказати обов'язковий термін виконання задачі, натиснувши на неї для редагування.",
+ "webFaqAnswer1": "Корисні звички (ті, що позначені знаком ) - це завдання, які Ви можете виконувати багато разів за день: наприклад, вживати овочі. Шкідиві звички (зі знаком ) - це ті дії, від яких Вам варто відмовитися: наприклад, гризти нігті. Звички, навпроти яких стоять і , і , припускають двоїстий вибір - або в хороший, або в поганий бік: наприклад підйом по сходах пішки проти користування ліфтом. Корисні звички винагороджуються досвідом та золотом. Погані звички віднімають здоров'я.\n
\nЩоденні завдання - це завдання, котрі ви маєте виконувати кожен день, наприклад чистити зуби чи перевіряти електронну пошту. Ви можете вказати дні, в які щоденне завдання обов'язкове або необов'язкове до виконання; для цього варто лише натиснути на піктограму олівця для редагування. Якщо ви пропустите обов'язкове завдання, ваш персонаж втратить здоров'я наступної ночі. Будьте уважні і не додавайте забагато щоденних завдань відразу!\n
\nЗадачі - це список одноразових справ, котрі Вам необхідно виконати. Виконання задач приносить Вам Золото та Досвід. Ви не втрачатимете здоров'я, якщо не виконаєте задачу. Ви можете вказати обов'язковий термін виконання задачі, натиснувши на піктограму олівця для редагування.",
+ "faqQuestion2": "Де можна подивитися приклади завдань?",
+ "iosFaqAnswer2": "На Вікі є чотири списки з прикладами завдань, які можна використати для натхнення:\n
\n* [Приклади звичок](http://habitica.wikia.com/wiki/Sample_Habits)\n* [Приклади щоденних завдань](http://habitica.wikia.com/wiki/Sample_Dailies)\n* [Приклади задач](http://habitica.wikia.com/wiki/Sample_To-Dos)\n* [Приклади нагород](http://habitica.wikia.com/wiki/Sample_Custom_Rewards)",
+ "webFaqAnswer2": "На Вікі є чотири списки з прикладами завдань, які можна використати для натхнення:\n\n* [Приклади звичок](http://habitica.wikia.com/wiki/Sample_Habits)\n* [Приклади щоденних завдань](http://habitica.wikia.com/wiki/Sample_Dailies)\n* [Приклади задач](http://habitica.wikia.com/wiki/Sample_To-Dos)\n* [Приклади нагород](http://habitica.wikia.com/wiki/Sample_Custom_Rewards)",
+ "faqQuestion3": "Чому завдання змінюють колір?",
+ "iosFaqAnswer3": "Ваші завдання змінюють колір в залежності від того, наскільки добре Ви в даний момент справляєтесь з їх виконаням! Кожне нове завдання забарвлене в нейтральний жовтий колір. Виконуйте щоденні завдання або корисні звички, і тоді вони почнуть змінювати колір у бік синього. Якщо Ви будете пропускати щоденні завдання або піддастеся шкідливим звичкам, завдання почнуть потроху червоніти. Чим більш червоне завдання, тим більшу винагороду Ви отримаєте за його виконання, та тим часом, червоні щоденні завдання і шкідливі звички нанесуть вам більше ушкодження за пропуск, ніж зазвичай! Це послужить для Вас мотивацією справлятися із завданнями, які доставляють Вам найбільше клопоту.",
+ "webFaqAnswer3": "Ваші завдання змінюють колір в залежності від того, наскільки добре Ви в даний момент справляєтесь з їх виконаням! Кожне нове завдання забарвлене в нейтральний жовтий колір. Виконуйте щоденні завдання або корисні звички, і тоді вони почнуть змінювати колір у бік синього. Якщо Ви будете пропускати щоденні завдання або піддастеся шкідливим звичкам, завдання почнуть потроху червоніти. Чим більш червоне завдання, тим більшу винагороду Ви отримаєте за його виконання, та тим часом, червоні щоденні завдання і шкідливі звички нанесуть вам більше ушкодження за пропуск, ніж зазвичай! Це послужить для Вас мотивацією справлятися із завданнями, які доставляють Вам найбільше клопоту.",
"faqQuestion4": "Why did my avatar lose health, and how do I regain it?",
"iosFaqAnswer4": "There are several things that can cause you to take damage. First, if you left Dailies incomplete overnight, they will damage you. Second, if you tap a bad Habit, it will damage you. Finally, if you are in a Boss Battle with your Party and one of your Party mates did not complete all their Dailies, the Boss will attack you.\n\n The main way to heal is to gain a level, which restores all your health. You can also buy a Health Potion with gold from the Rewards column. Plus, at level 10 or above, you can choose to become a Healer, and then you will learn healing skills. If you are in a Party with a Healer, they can heal you as well.",
"webFaqAnswer4": "There are several things that can cause you to take damage. First, if you left Dailies incomplete overnight, they will damage you. Second, if you click a bad Habit, it will damage you. Finally, if you are in a Boss Battle with your party and one of your party mates did not complete all their Dailies, the Boss will attack you.\n
\n The main way to heal is to gain a level, which restores all your Health. You can also buy a Health Potion with Gold from the Rewards column. Plus, at level 10 or above, you can choose to become a Healer, and then you will learn healing skills. If you are in a party (under Social > Party) with a Healer, they can heal you as well.",
diff --git a/common/locales/uk/front.json b/common/locales/uk/front.json
index c192183b6e..8b651bdefa 100644
--- a/common/locales/uk/front.json
+++ b/common/locales/uk/front.json
@@ -28,6 +28,7 @@
"communityReddit": "Reddit",
"companyAbout": "Як це працює",
"companyBlog": "Блоґ",
+ "devBlog": "Developer Blog",
"companyDonate": "Пожертвувати",
"companyExtensions": "Розширення",
"companyPrivacy": "Політика конфіденційности",
@@ -51,6 +52,7 @@
"featureSocialHeading": "Social play",
"featuredIn": "Featured in",
"featuresHeading": "We also feature...",
+ "footerDevs": "Developers",
"footerCommunity": "Спільнота",
"footerCompany": "Компанія",
"footerMobile": "Мобільник",
@@ -182,6 +184,7 @@
"zelahQuote": "With [Habitica], I can be persuaded to go to bed on time by the thought of gaining points for an early night or losing health for a late one!",
"reportAccountProblems": "Report Account Problems",
"reportCommunityIssues": "Report Community Issues",
+ "subscriptionPaymentIssues": "Subscription and Payment Issues",
"generalQuestionsSite": "General Questions about the Site",
"businessInquiries": "Business Inquiries",
"merchandiseInquiries": "Merchandise Inquiries",
@@ -222,5 +225,32 @@
"altAttrWebstorm": "WebStorm",
"altAttrGithub": "GitHub",
"altAttrTrello": "Trello",
- "altAttrSlack": "Slack"
+ "altAttrSlack": "Slack",
+ "missingAuthHeaders": "Missing authentication headers.",
+ "missingAuthParams": "Missing authentication parameters.",
+ "missingUsernameEmail": "Missing username or email.",
+ "missingEmail": "Missing email.",
+ "missingUsername": "Missing username.",
+ "missingPassword": "Missing password.",
+ "missingNewPassword": "Missing new password.",
+ "wrongPassword": "Wrong password.",
+ "notAnEmail": "Invalid email address.",
+ "emailTaken": "Email address is already used in an account.",
+ "newEmailRequired": "Missing new email address.",
+ "usernameTaken": "Username already taken.",
+ "passwordConfirmationMatch": "Password confirmation doesn't match password.",
+ "invalidLoginCredentials": "Incorrect username and/or email and/or password.",
+ "passwordReset": "If we have your email on file, your password reset link has been sent to your email.",
+ "passwordResetEmailSubject": "Password Reset for Habitica",
+ "passwordResetEmailText": "Password for <%= username %> has been reset to <%= newPassword %> . Important! Both username and password are case-sensitive -- you must enter both exactly as shown here. We recommend copying and pasting both instead of typing them. Log in at <%= baseUrl %>. After you have logged in, head to <%= baseUrl %>/#/options/settings/settings and change your password.",
+ "passwordResetEmailHtml": "Password for <%= username %> has been reset to <%= newPassword %>.
Important! Both username and password are case-sensitive -- you must enter both exactly as shown here. We recommend copying and pasting both instead of typing them.
Log in at <%= baseUrl %>. After you have logged in, head to <%= baseUrl %>/#/options/settings/settings and change your password.",
+ "invalidLoginCredentialsLong": "Uh-oh - your username or password is incorrect.\n- Make sure your username or email is typed correctly.\n- You may have signed up with Facebook, not email. Double-check by trying Facebook login.\n- If you forgot your password, click \"Forgot Password\".",
+ "invalidCredentials": "There is no account that uses those credentials.",
+ "accountSuspended": "Account has been suspended, please contact leslie@habitica.com with your User ID \"<%= userId %>\" for assistance.",
+ "onlyFbSupported": "Only Facebook is supported currently.",
+ "cantDetachFb": "Account lacks another authentication method, can't detach Facebook.",
+ "onlySocialAttachLocal": "Local authentication can be added to only a social account.",
+ "invalidReqParams": "Invalid request parameters.",
+ "memberIdRequired": "\"member\" must be a valid UUID.",
+ "heroIdRequired": "\"heroId\" must be a valid UUID."
}
\ No newline at end of file
diff --git a/common/locales/uk/gear.json b/common/locales/uk/gear.json
index f7c0ef6e4c..3c81def29f 100644
--- a/common/locales/uk/gear.json
+++ b/common/locales/uk/gear.json
@@ -189,6 +189,8 @@
"weaponArmoireMiningPickaxNotes": "Mine the maximum amount of gold from your tasks! Increases Perception by <%= per %>. Enchanted Armoire: Miner Set (Item 3 of 3).",
"weaponArmoireBasicLongbowText": "Basic Longbow",
"weaponArmoireBasicLongbowNotes": "A serviceable hand-me-down bow. Increases Strength by <%= str %>. Enchanted Armoire: Basic Archer Set (Item 1 of 3).",
+ "weaponArmoireHabiticanDiplomaText": "Habitican Diploma",
+ "weaponArmoireHabiticanDiplomaNotes": "A certificate of significant achievement -- well done! Increases Intelligence by <%= int %>. Enchanted Armoire: Graduate Set (Item 1 of 3).",
"armor": "броня",
"armorBase0Text": "Звичайний одяг",
"armorBase0Notes": "Звичайнісінький одяг. Так собі.",
@@ -364,6 +366,10 @@
"armorMystery201512Notes": "Summon the icy flames of winter! Confers no benefit. December 2015 Subscriber Item.",
"armorMystery201603Text": "Lucky Suit",
"armorMystery201603Notes": "This suit is sewn from thousands of four-leafed clovers! Confers no benefit. March 2016 Subscriber Item.",
+ "armorMystery201604Text": "Armor o' Leaves",
+ "armorMystery201604Notes": "You, too, can be a small but fearsome leaf puff. Confers no benefit. April 2016 Subscriber Item.",
+ "armorMystery201605Text": "Marching Bard Uniform",
+ "armorMystery201605Notes": "Unlike the traditional bards who join adventuring parties, bards who join Habitican marching bands are known for grand parades, not dungeon raids. Confers no benefit. May 2016 Subscriber Item.",
"armorMystery301404Text": "Steampunk Suit",
"armorMystery301404Notes": "Dapper and dashing, wot! Confers no benefit. February 3015 Subscriber Item.",
"armorArmoireLunarArmorText": "Soothing Lunar Armor",
@@ -394,6 +400,8 @@
"armorArmoireMinerOverallsNotes": "They may seem worn, but they are enchanted to repel dirt. Increases Constitution by <%= con %>. Enchanted Armoire: Miner Set (Item 2 of 3).",
"armorArmoireBasicArcherArmorText": "Basic Archer Armor",
"armorArmoireBasicArcherArmorNotes": "This camouflaged vest lets you slip unnoticed through the forests. Increases Perception by <%= per %>. Enchanted Armoire: Basic Archer Set (Item 2 of 3).",
+ "armorArmoireGraduateRobeText": "Graduate Robe",
+ "armorArmoireGraduateRobeNotes": "Congratulations! This weighty robe hangs heavy with all the knowledge you have accrued. Increases Intelligence by <%= int %>. Enchanted Armoire: Graduate Set (Item 2 of 3).",
"headgear": "headgear",
"headBase0Text": "Без шолома",
"headBase0Notes": "Без головного убору",
@@ -565,6 +573,10 @@
"headMystery201602Notes": "Shield your identity from all your admirers. Confers no benefit. February 2016 Subscriber Item.",
"headMystery201603Text": "Lucky Hat",
"headMystery201603Notes": "This top hat is a magical good-luck charm. Confers no benefit. March 2016 Subscriber Item.",
+ "headMystery201604Text": "Crown o' Flowers",
+ "headMystery201604Notes": "These woven flowers make a surprisingly strong helm! Confers no benefit. April 2016 Subscriber Item.",
+ "headMystery201605Text": "Marching Bard Hat",
+ "headMystery201605Notes": "Seventy-six dragons led the big parade, with a hundred and ten gryphons close at hand! Confers no benefit. May 2016 Subscriber Item.",
"headMystery301404Text": "Fancy Top Hat",
"headMystery301404Notes": "A fancy top hat for the finest of gentlefolk! January 3015 Subscriber Item. Confers no benefit.",
"headMystery301405Text": "Basic Top Hat",
@@ -613,6 +625,8 @@
"headArmoireMinerHelmetNotes": "Protect your head from falling tasks! Increases Intelligence by <%= int %>. Enchanted Armoire: Miner Set (Item 1 of 3).",
"headArmoireBasicArcherCapText": "Basic Archer Cap",
"headArmoireBasicArcherCapNotes": "No archer would be complete without a jaunty cap! Increases Perception by <%= per %>. Enchanted Armoire: Basic Archer Set (Item 3 of 3).",
+ "headArmoireGraduateCapText": "Graduate Cap",
+ "headArmoireGraduateCapNotes": "Congratulations! Your deep thoughts have earned you this thinking cap. Increases Intelligence by <%= int %>. Enchanted Armoire: Graduate Set (Item 3 of 3).",
"offhand": "shield-hand item",
"shieldBase0Text": "No Shield-Hand Equipment",
"shieldBase0Notes": "Без щита чи другої зброї",
@@ -673,7 +687,7 @@
"shieldSpecialWinter2015HealerText": "Soothing Shield",
"shieldSpecialWinter2015HealerNotes": "This shield deflects the freezing wind. Increases Constitution by <%= con %>. Limited Edition 2014-2015 Winter Gear.",
"shieldSpecialSpring2015RogueText": "Exploding Squeak",
- "shieldSpecialSpring2015RogueNotes": "Don't let the sound fool you - these explosives pack a punch. Increases Strength <%= str %>. Limited Edition 2015 Spring Gear.",
+ "shieldSpecialSpring2015RogueNotes": "Don't let the sound fool you - these explosives pack a punch. Increases Strength by <%= str %>. Limited Edition 2015 Spring Gear.",
"shieldSpecialSpring2015WarriorText": "Dish Discus",
"shieldSpecialSpring2015WarriorNotes": "Hurl it at your enemies.... or just hold it, because it will fill up with yummy kibble at dinnertime. Increases Constitution by <%= con %>. Limited Edition 2015 Spring Gear.",
"shieldSpecialSpring2015HealerText": "Patterned Pillow",
@@ -716,6 +730,8 @@
"shieldArmoireDragonTamerShieldNotes": "Distract enemies with this dragon-shaped shield. Increases Perception by <%= per %>. Enchanted Armoire: Dragon Tamer Set (Item 2 of 3).",
"shieldArmoireMysticLampText": "Mystic Lamp",
"shieldArmoireMysticLampNotes": "Light the darkest caves with this mystic lamp! Increases Perception by <%= per %>. Enchanted Armoire: Independent Item.",
+ "shieldArmoireFloralBouquetText": "Bouquet o' Flowers",
+ "shieldArmoireFloralBouquetNotes": "Not much help in battle, but aren't they beautiful? Increases Constitution by <%= con %>. Enchanted Armoire: Independent Item.",
"back": "Back Accessory",
"backBase0Text": "No Back Accessory",
"backBase0Notes": "No Back Accessory.",
@@ -820,6 +836,20 @@
"eyewear": "Eyewear",
"eyewearBase0Text": "No Eyewear",
"eyewearBase0Notes": "No Eyewear.",
+ "eyewearSpecialBlackTopFrameText": "Black Standard Eyeglasses",
+ "eyewearSpecialBlackTopFrameNotes": "Glasses with a black frame above the lenses. Confers no benefit.",
+ "eyewearSpecialBlueTopFrameText": "Blue Standard Eyeglasses",
+ "eyewearSpecialBlueTopFrameNotes": "Glasses with a blue frame above the lenses. Confers no benefit.",
+ "eyewearSpecialGreenTopFrameText": "Green Standard Eyeglasses",
+ "eyewearSpecialGreenTopFrameNotes": "Glasses with a green frame above the lenses. Confers no benefit.",
+ "eyewearSpecialPinkTopFrameText": "Pink Standard Eyeglasses",
+ "eyewearSpecialPinkTopFrameNotes": "Glasses with a pink frame above the lenses. Confers no benefit.",
+ "eyewearSpecialRedTopFrameText": "Red Standard Eyeglasses",
+ "eyewearSpecialRedTopFrameNotes": "Glasses with a red frame above the lenses. Confers no benefit.",
+ "eyewearSpecialWhiteTopFrameText": "White Standard Eyeglasses",
+ "eyewearSpecialWhiteTopFrameNotes": "Glasses with a white frame above the lenses. Confers no benefit.",
+ "eyewearSpecialYellowTopFrameText": "Yellow Standard Eyeglasses",
+ "eyewearSpecialYellowTopFrameNotes": "Glasses with a yellow frame above the lenses. Confers no benefit.",
"eyewearSpecialSummerRogueText": "Roguish Eyepatch",
"eyewearSpecialSummerRogueNotes": "It doesn't take a scallywag to see how stylish this is! Confers no benefit. Limited Edition 2014 Summer Gear.",
"eyewearSpecialSummerWarriorText": "Dashing Eyepatch",
diff --git a/common/locales/uk/generic.json b/common/locales/uk/generic.json
index 24f8f21acd..0bea0a3c58 100644
--- a/common/locales/uk/generic.json
+++ b/common/locales/uk/generic.json
@@ -33,7 +33,7 @@
"italics": "*курсив*",
"bold": "**Жирний**",
"strikethrough": "~~Закреслено~~",
- "emojiExample": ":смайлик:",
+ "emojiExample": ":smile:",
"markdownLinkEx": "[Habitica - це круто!](https://habitica.com)",
"markdownImageEx": "",
"unorderedListHTML": "+ First item + Second item + Third item",
diff --git a/common/locales/uk/groups.json b/common/locales/uk/groups.json
index 0ca7617c99..50cbb2dc83 100644
--- a/common/locales/uk/groups.json
+++ b/common/locales/uk/groups.json
@@ -92,6 +92,7 @@
"send": "Надіслати",
"messageSentAlert": "Повідомлення відправлено",
"pmHeading": "Приватне повідомлення до <%= name %>",
+ "pmsMarkedRead": "Your private messages have been marked as read",
"clearAll": "Delete All Messages",
"confirmDeleteAllMessages": "Are you sure you want to delete all messages in your inbox? Other users will still see messages you have sent to them.",
"optOutPopover": "Не подобається приватні повідомлення? Натисніть, щоб повністю відмовитися від них",
@@ -99,6 +100,15 @@
"unblock": "Розблокувати",
"pm-reply": "Надіслати відповідь",
"inbox": "Inbox",
+ "messageRequired": "A message is required.",
+ "toUserIDRequired": "A User ID is required",
+ "gemAmountRequired": "A number of gems is required",
+ "notAuthorizedToSendMessageToThisUser": "Can't send message to this user.",
+ "privateMessageGiftIntro": "Hello <%= receiverName %>, <%= senderName %> has sent you",
+ "privateMessageGiftGemsMessage": "<%= gemAmount %> gems!",
+ "privateMessageGiftSubscriptionMessage": "<%= numberOfMonths %> months of subscription!",
+ "cannotSendGemsToYourself": "Cannot send gems to yourself. Try a subscription instead.",
+ "badAmountOfGemsToSend": "Amount must be within 1 and your current number of gems.",
"abuseFlag": "Report violation of Community Guidelines",
"abuseFlagModalHeading": "Report <%= name %> for violation?",
"abuseFlagModalBody": "Are you sure you want to report this post? You should ONLY report a post that violates the <%= firstLinkStart %>Community Guidelines<%= linkEnd %> and/or <%= secondLinkStart %>Terms of Service<%= linkEnd %>. Inappropriately reporting a post is a violation of the Community Guidelines and may give you an infraction. Appropriate reasons to flag a post include but are not limited to:
swearing, religous oaths
bigotry, slurs
adult topics
violence, including as a joke
spam, nonsensical messages
",
@@ -151,5 +161,29 @@
"partyUpName": "Party Up",
"partyOnName": "Party On",
"partyUpAchievement": "Joined a Party with another person! Have fun battling monsters and supporting each other.",
- "partyOnAchievement": "Joined a Party with at least four people! Enjoy your increased accountability as you unite with your friends to vanquish your foes!"
+ "partyOnAchievement": "Joined a Party with at least four people! Enjoy your increased accountability as you unite with your friends to vanquish your foes!",
+ "groupIdRequired": "\"groupId\" must be a valid UUID",
+ "groupNotFound": "Group not found.",
+ "groupTypesRequired": "You must supply a valid \"type\" query string.",
+ "questLeaderCannotLeaveGroup": "You cannot leave your party when you have started a quest. Abort the quest first.",
+ "cannotLeaveWhileActiveQuest": "You cannot leave party during an active quest. Please leave the quest first.",
+ "onlyLeaderCanRemoveMember": "Only group leader can remove a member!",
+ "memberCannotRemoveYourself": "You cannot remove yourself!",
+ "groupMemberNotFound": "User not found among group's members",
+ "mustBeGroupMember": "Must be member of the group.",
+ "keepOrRemoveAll": "req.query.keep must be either \"keep-all\" or \"remove-all\"",
+ "keepOrRemove": "req.query.keep must be either \"keep\" or \"remove\"",
+ "canOnlyInviteEmailUuid": "Can only invite using uuids or emails.",
+ "inviteMissingEmail": "Missing email address in invite.",
+ "partyMustbePrivate": "Parties must be private",
+ "userAlreadyInGroup": "User already in that group.",
+ "userAlreadyInvitedToGroup": "User already invited to that group.",
+ "userAlreadyPendingInvitation": "User already pending invitation.",
+ "userAlreadyInAParty": "User already in a party.",
+ "userWithIDNotFound": "User with id \"<%= userId %>\" not found.",
+ "userHasNoLocalRegistration": "User does not have a local registration (username, email, password).",
+ "uuidsMustBeAnArray": "User ID invites must be an array.",
+ "emailsMustBeAnArray": "Email address invites must be an array.",
+ "canOnlyInviteMaxInvites": "You can only invite \"<%= maxInvites %>\" at a time",
+ "onlyCreatorOrAdminCanDeleteChat": "Not authorized to delete this message!"
}
\ No newline at end of file
diff --git a/common/locales/uk/limited.json b/common/locales/uk/limited.json
index 2ea7e87834..408ba88a73 100644
--- a/common/locales/uk/limited.json
+++ b/common/locales/uk/limited.json
@@ -5,7 +5,7 @@
"annoyingFriends": "Набридливі друзі",
"annoyingFriendsText": "Ухопив сніжку <%= snowballs %> разів від спільників гурту.",
"alarmingFriends": "Лякаючi друзi",
- "alarmingFriendsText": "Був наляканий членами групи <%= spookDust %> разiв.",
+ "alarmingFriendsText": "Got spooked <%= spookySparkles %> times by party members.",
"agriculturalFriends": "Друзі-Садівники",
"agriculturalFriendsText": "Був <%= seeds %> разів перетворений на квітку товаришами по групі.",
"aquaticFriends": "Aquatic Friends",
@@ -23,7 +23,7 @@
"turkey": "Індичка",
"gildedTurkey": "Gilded Turkey",
"polarBearPup": "Біле ведмежа",
- "jackolantern": "Світильник Джека",
+ "jackolantern": "Джек-ліхтар",
"seasonalShop": "Сезонна крамниця",
"seasonalShopClosedTitle": "<%= linkStart %>Leslie<%= linkEnd %>",
"seasonalShopTitle": "<%= linkStart %>Seasonal Sorceress<%= linkEnd %>",
@@ -72,5 +72,6 @@
"comfortingKittySet": "Comforting Kitty (Healer)",
"sneakySqueakerSet": "Sneaky Squeaker (Rogue)",
"fallEventAvailability": "Available until October 31",
- "winterEventAvailability": "Available until December 31"
+ "winterEventAvailability": "Available until December 31",
+ "springEventAvailability": "Available until May 31"
}
\ No newline at end of file
diff --git a/common/locales/uk/maintenance.json b/common/locales/uk/maintenance.json
new file mode 100644
index 0000000000..efdb524cd2
--- /dev/null
+++ b/common/locales/uk/maintenance.json
@@ -0,0 +1,34 @@
+{
+ "habiticaBackSoon": "Don't worry, Habitica will be back soon!",
+ "importantMaintenance": "We are doing important maintenance that we estimate will last until <%= localDate %> in your timezone.",
+ "maintenance": "Maintenance",
+ "maintenanceMoreInfo": "Want more information about the maintenance? <%= linkStart %>Check out our info page<%= linkEnd %>.",
+ "noDamageKeepStreaks": "You will NOT take damage or lose streaks!",
+ "thanksForPatience": "Thanks for your patience!",
+ "twitterMaintenanceUpdates": "For the most recent updates, watch our Twitter, where we will be posting status information.",
+ "veteranPetAward": "At the end, you will receive a Veteran pet!",
+
+ "maintenanceInfoTitle": "Information about Upcoming Maintenance to Habitica",
+ "maintenanceInfoWhat": "What is happening?",
+ "maintenanceInfoWhatText": "On May 21, Habitica will be down for maintenance for most of the day. You will not take any damage or have your account harmed during that weekend, even if you can’t log in to check off your Dailies in time! We will be working very hard to make the downtime as short as possible, and will be posting regular updates on our Twitter account. At the end of the downtime, to thank everyone for their patience, you will all receive a rare pet!",
+ "maintenanceInfoWhy": "Why is this happening?",
+ "maintenanceInfoWhyText": "For the past several months, we have been thoroughly revamping Habitica behind-the-scenes. Specifically, we have rewritten the API. While it may not look much different on the surface, it’s a whole new world underneath. This will allow us WAY more flexibility when we want to build features in the future, and lead to improved performance!",
+ "maintenanceInfoTechDetails": "Want more details on the technical side of the process? Visit The Forge, our dev blog.",
+ "maintenanceInfoMore": "More Information",
+ "maintenanceInfoAccountChanges": "What changes will I see to my account after the rewrite is complete?",
+ "maintenanceInfoAccountChangesText": "At first, there won’t be any notable changes aside from performance improvements for features such as Challenges. If you notice any changes that shouldn’t be there, email us at admin@habitica.com and we will investigate them for you!",
+ "maintenanceInfoAddFeatures": "What kind of features will this allow Habitica to add?",
+ "maintenanceInfoAddFeaturesText": "Completing this rewrite will allow us to start building out improved chat and Guilds, plans for organizations and families, and additional productivity features like Monthlies and the ability to record yesterday’s activity! Those are all involved features on their own, so it will take time to build them, but until we were finished with this rewrite, there was no way we could start them.",
+ "maintenanceInfoHowLong": "How long will the maintenance take?",
+ "maintenanceInfoHowLongText": "We have to migrate tasks and data for all 1.3 million Habitica users -- not an easy task! We anticipate that it will take place between approximately 1pm Pacific Time (8pm UTC) and 10pm Pacific Time (5am UTC). Rest assured that we’re doing everything we can to make it go as quickly as possible! You can follow updates on our Twitter.",
+ "maintenanceInfoStatsAffected": "How will my Dailies, Streaks, Buffs, and Quests be affected?",
+ "maintenanceInfoStatsAffectedText1": "You will NOT take any damage or lose any streaks that weekend, but otherwise, your day will reset normally! Dailies that you checked will become unchecked, buffs will reset, etc. If you are in a Collection Quest, you will still find items. If you are in a Boss Battle, you will still deal damage to the Boss, but the Boss will not deal damage to you. (Even monsters need a break!)",
+ "maintenanceInfoStatsAffectedText2": "After a lot of thought, our team concluded that this was the most fair way to handle the fact that many users will not be able to check off their Dailies normally during the maintenance. We’re sorry for any inconvenience this causes!",
+ "maintenanceInfoSeeTasks": "What if I need to see my task list?",
+ "maintenanceInfoSeeTasksText": "If you know that you will need to see your task list on Saturday to remind yourself what you have to do, we recommend that before the maintenance begins, you take a screenshot of your tasks so that you can use it as a reference.",
+ "maintenanceInfoRarePet": "What kind of rare pet will I receive?",
+ "maintenanceInfoRarePetText": "To thank you for your patience during the downtime, everyone will get a rare Veteran Pet. If you’ve never received a Veteran Pet before, you will receive a Veteran Wolf. If you already have a Veteran Wolf, you will receive a Veteran Tiger. And if you already have a Veteran Wolf and a Veteran Tiger, you will receive a never-before-seen Veteran pet! After the migration is completed, it may take several hours for your pet to show up, but never fear, everyone will get one.",
+ "maintenanceInfoWho": "Who worked on this massive project?",
+ "maintenanceInfoWhoText": "We’re glad you asked! It was spearheaded by our amazing contributor paglias, with lots of help from Blade, TheHollidayInn, SabreCat, Victor Pudeyev, TheUnknown, and Alys.",
+ "maintenanceInfoTesting": "The new version was also tirelessly tested by a bunch of our amazing open-source volunteers. Thank you -- we couldn't have done this without you."
+}
diff --git a/common/locales/uk/noscript.json b/common/locales/uk/noscript.json
index 673966bc84..f5e9cd7f62 100644
--- a/common/locales/uk/noscript.json
+++ b/common/locales/uk/noscript.json
@@ -1,6 +1,6 @@
{
"jsDisabledHeading": "Упс! На вашому браузері вимкнено JavaScript",
- "jsDisabledHeadingFull": "Упс! На вашому браузері вимкнено JavaScript, без нього Habitica не може повноцінно функціювати",
+ "jsDisabledHeadingFull": "Упс! На вашому браузері вимкнено JavaScript, без нього Habitica не може повноцінно функціонувати",
"jsDisabledText": "Habitica не може повноцінно працювати без цього!",
"jsDisabledLink": "Щоб продовжити, будь ласка, увімкніть JavaScript!"
}
\ No newline at end of file
diff --git a/common/locales/uk/npc.json b/common/locales/uk/npc.json
index ea2b639473..2f828851a4 100644
--- a/common/locales/uk/npc.json
+++ b/common/locales/uk/npc.json
@@ -21,6 +21,26 @@
"ian": "Ian",
"ianText": "Welcome to the Quest Shop! Here you can use Quest Scrolls to battle monsters with your friends. Be sure to check out our fine array of Quest Scrolls for purchase on the right!",
"ianBrokenText": "Welcome to the Quest Shop... Here you can use Quest Scrolls to battle monsters with your friends... Be sure to check out our fine array of Quest Scrolls for purchase on the right...",
+ "missingKeyParam": "\"req.params.key\" is required.",
+ "itemNotFound": "Item \"<%= key %>\" not found.",
+ "cannotBuyItem": "You can't buy this item.",
+ "missingTypeKeyEquip": "\"key\" and \"type\" are required parameters.",
+ "missingPetFoodFeed": "\"pet\" and \"food\" are required parameters.",
+ "invalidPetName": "Invalid pet name supplied.",
+ "missingEggHatchingPotionHatch": "\"egg\" and \"hatchingPotion\" are required parameters.",
+ "invalidTypeEquip": "\"type\" must be one of 'equipped', 'pet', 'mount', 'costume'.",
+ "mustPurchaseToSet": "Must purchase <%= val %> to set it on <%= key %>.",
+ "typeRequired": "Type is required",
+ "keyRequired": "Key is required",
+ "notAccteptedType": "Type must be in [eggs, hatchingPotions, food, quests, gear]",
+ "contentKeyNotFound": "Key not found for Content <%= type %>",
+ "plusOneGem": "+1 Gem",
+ "typeNotSellable": "Type is not sellable. Must be one of the following <%= acceptedTypes %>",
+ "userItemsKeyNotFound": "Key not found for user.items <%= type %>",
+ "pathRequired": "Path string is required",
+ "unlocked": "Items have been unlocked",
+ "alreadyUnlocked": "Full set already unlocked.",
+ "alreadyUnlockedPart": "Full set already partially unlocked.",
"USD": "(USD)",
"newStuff": "Щось новеньке",
"cool": "Розкажіть потім",
@@ -64,6 +84,7 @@
"tourPetsPage": "This is the Stable! After level 3, you can hatch pets using eggs and potions. When you hatch a pet in the Market, it will appear here! Click a pet's image to add it to your avatar. Feed them with the food you find after level 3, and they'll grow into powerful mounts.",
"tourMountsPage": "Once you've fed a pet enough food to turn it into a mount, it will appear here. (Pets, mounts, and food are available after level 4.) Click a mount to saddle up!",
"tourEquipmentPage": "This is where your Equipment is stored! Your Battle Gear affects your stats. If you want to show different Equipment on your avatar without changing your stats, click \"Enable Costume.\"",
+ "equipmentAlreadyOwned": "You already own that piece of equipment",
"tourOkay": "Okay!",
"tourAwesome": "Awesome!",
"tourSplendid": "Splendid!",
diff --git a/common/locales/uk/pets.json b/common/locales/uk/pets.json
index ace3897ad0..954802eefb 100644
--- a/common/locales/uk/pets.json
+++ b/common/locales/uk/pets.json
@@ -11,7 +11,8 @@
"rareMounts": "Рідкісні скакуни",
"etherealLion": "Етерний лев",
"veteranWolf": "Лютий вовк",
- "veteranTiger": "Veteran Tiger",
+ "veteranTiger": "Лютий тигр",
+ "veteranLion": "Veteran Lion",
"cerberusPup": "Церберятко",
"hydra": "Гідра",
"mantisShrimp": "Рак-богомол",
@@ -19,8 +20,8 @@
"orca": "Orca",
"royalPurpleGryphon": "Royal Purple Gryphon",
"phoenix": "Phoenix",
- "bumblebee": "Bumblebee",
- "rarePetPop1": "Клацніть на золоту лапку, аби довідатися, як отримати цього улюбленця, завдяки підтримуванню Habitica!",
+ "magicalBee": "Magical Bee",
+ "rarePetPop1": "Клацніть на золоту лапку, аби довідатися, як отримати цього улюбленця, підтримавши проект Habitica!",
"rarePetPop2": "Як отримати цього улюбленця!",
"potion": "<%= potionType %> Зілля",
"egg": "<%= eggType %> Яйце",
@@ -32,7 +33,7 @@
"hatchingPotion": "зілля вилуплення",
"noHatchingPotions": "Ви не маєте жодного зілля дозрівання.",
"inventoryText": "Клацніть на яйці, аби побачити доступні зілля, підсвічені зеленим, та оберіть одне з них, щоб яйце дозріло для вилуплення. Якщо не підсвічено жодного зілля, ще раз клацніть на яйці, щоб скасувати вибір. Натомість клацніть на зіллі, аби виявити яйця, з якими його можна використати. Непотрібні предмети можна також продати купцеві Александру.",
- "foodText": "food",
+ "foodText": "їжа",
"food": "Їжа та сідла",
"noFood": "У Вас немає жодної їжі чи сідла.",
"dropsExplanation": "Get these items faster with Gems if you don't want to wait for them to drop when completing a task. Learn more about the drop system.",
@@ -41,7 +42,7 @@
"stableBeastMasterProgress": "Beast Master Progress: <%= number %> Pets Found",
"beastAchievement": "Ви здобули досягнення „Звіролов“ за збір усіх улюбленців!",
"beastMasterName": "Володар Звірів",
- "beastMasterText": "Has found all 90 pets (incredibly difficult, congratulate this user!)",
+ "beastMasterText": "Зібрав усіх 90 улюбленців (шалено важко, привітайте цього гравця!)",
"beastMasterText2": "and has released their pets a total of <%= count %> times",
"mountMasterProgress": "Прогрес Майстра Скакунів",
"stableMountMasterProgress": "Mount Master Progress: <%= number %> Mounts Tamed",
@@ -60,8 +61,9 @@
"useGems": "If you've got your eye on a pet, but can't wait any longer for it to drop, use Gems in Inventory > Market to buy one!",
"hatchAPot": "Пролупити <%= egg %>, використавши <%= potion %>?",
"hatchedPet": "You hatched a <%= potion %> <%= egg %>!",
- "displayNow": "Display Now",
- "displayLater": "Display Later",
+ "displayNow": "Показати зараз",
+ "displayLater": "Показати пізніше",
+ "petNotOwned": "You do not own this pet.",
"earnedCompanion": "With all your productivity, you've earned a new companion. Feed it to make it grow!",
"feedPet": "Згодувати <%= article %><%= text %> Вашому <%= name %>?",
"useSaddle": "Осідлати <%= pet %>?",
@@ -81,7 +83,10 @@
"petKeyPets": "Відпустити моїх улюбленців",
"petKeyMounts": "Відпустити моїх скакунів",
"petKeyBoth": "Release Both",
- "confirmPetKey": "Are you sure?",
+ "confirmPetKey": "Ви впевнені?",
"petKeyNeverMind": "Ще ні",
+ "petsReleased": "Pets released.",
+ "mountsAndPetsReleased": "Mounts and pets released",
+ "mountsReleased": "Mounts released",
"gemsEach": "gems each"
}
\ No newline at end of file
diff --git a/common/locales/uk/quests.json b/common/locales/uk/quests.json
index 025008a5ce..178949b774 100644
--- a/common/locales/uk/quests.json
+++ b/common/locales/uk/quests.json
@@ -78,5 +78,24 @@
"whichQuestStart": "Which quest do you want to start?",
"getMoreQuests": "Get more quests",
"unlockedAQuest": "You unlocked a quest!",
- "leveledUpReceivedQuest": "You leveled up to Level <%= level %> and received a quest scroll!"
+ "leveledUpReceivedQuest": "You leveled up to Level <%= level %> and received a quest scroll!",
+ "questInvitationDoesNotExist": "No quest invitation has been sent out yet.",
+ "questInviteNotFound": "No quest invitation found.",
+ "guildQuestsNotSupported": "Guilds cannot be invited on quests.",
+ "questNotFound": "Quest \"<%= key %>\" not found.",
+ "questNotOwned": "You don't own that quest scroll.",
+ "questNotGoldPurchasable": "Quest \"<%= key %>\" is not a Gold-purchasable quest.",
+ "questLevelTooHigh": "You must be level <%= level %> to begin this quest.",
+ "questAlreadyUnderway": "Your party is already on a quest. Try again when the current quest has ended.",
+ "questAlreadyAccepted": "You already accepted the quest invitation.",
+ "noActiveQuestToLeave": "No active quest to leave",
+ "questLeaderCannotLeaveQuest": "Quest leader cannot leave quest",
+ "notPartOfQuest": "You are not part of the quest",
+ "noActiveQuestToAbort": "There is no active quest to abort.",
+ "onlyLeaderAbortQuest": "Only the group or quest leader can abort a quest.",
+ "questAlreadyRejected": "You already rejected the quest invitation.",
+ "cantCancelActiveQuest": "You can not cancel an active quest, use the abort functionality.",
+ "onlyLeaderCancelQuest": "Only the group or quest leader can cancel the quest.",
+ "questNotPending": "There is no quest to start.",
+ "questOrGroupLeaderOnlyStartQuest": "Only the quest leader or group leader can force start the quest"
}
\ No newline at end of file
diff --git a/common/locales/uk/questscontent.json b/common/locales/uk/questscontent.json
index 3f25bc93c2..0e34d8c0a6 100644
--- a/common/locales/uk/questscontent.json
+++ b/common/locales/uk/questscontent.json
@@ -304,9 +304,21 @@
"questBewilderCompletionChat": "`The Be-Wilder is DEFEATED!`\n\nWe've done it! The Be-Wilder lets out a ululating cry as it twists in the air, shedding feathers like falling rain. Slowly, gradually, it coils into a cloud of sparkling mist. As the newly-revealed sun pierces the fog, it burns away, revealing the coughing, mercifully human forms of Bailey, Matt, Alex.... and the April Fool himself.\n\n`Mistiflying is saved!`\n\nThe April Fool has enough shame to look a bit sheepish. “Oh, hm,” he says. “Perhaps I got a little…. carried away.”\n\nThe crowd mutters. Sodden flowers wash up on sidewalks. Somewhere in the distance, a roof collapses with a spectacular splash.\n\n“Er, yes,” the April Fool says. “That is. What I meant to say was, I’m dreadfully sorry.” He heaves a sigh. “I suppose it can’t all be fun and games, after all. It might not hurt to focus occasionally. Maybe I’ll get a head start on next year’s pranking.”\n\nRedphoenix coughs meaningfully.\n\n“I mean, get a head start on this year’s spring cleaning!” the April Fool says. “Nothing to fear, I’ll have Habit City in spit-shape soon. Luckily nobody is better than I at dual-wielding mops.”\n\nEncouraged, the marching band starts up.\n\nIt isn’t long before all is back to normal in Habit City. Plus, now that the Be-Wilder has evaporated, the magical bees of Mistiflying bustle back to work, and soon the flowers are blooming and the city is floating once more.\n\nAs Habiticans cuddle the magical fuzzy bees, the April Fool’s eyes light up. “Oho, I’ve had a thought! Why don’t you all keep some of these fuzzy Bee Pets and Mounts? It’s a gift that perfectly symbolizes the balance between hard work and sweet rewards, if I’m going to get all boring and allegorical on you.” He winks. “Besides, they don’t have stingers! Fool’s honor.”",
"questBewilderBossRageTitle": "Beguilement Strike",
"questBewilderBossRageDescription": "When this gauge fills, The Be-Wilder will unleash its Beguilement Strike on Habitica!",
- "questBewilderDropBumblebeePet": "Bumblebee (Pet)",
- "questBewilderDropBumblebeeMount": "Bumblebee (Mount)",
+ "questBewilderDropBumblebeePet": "Magical Bee (Pet)",
+ "questBewilderDropBumblebeeMount": "Magical Bee (Mount)",
"questBewilderBossRageMarket": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nOh no! Despite our best efforts, we've gotten distracted by the Be-Wilder’s charming illusions and have forgotten to do some of our Dailies! With a cackling cry, the shining bird beats its wings, raising a swarm of mist around Alex the Merchant. When the fog clears, he has been possessed! “Have some free samples!” he shouts gleefully, and begins to hurl exploding eggs and potions at fleeing Habiticans. Not the most favorable of sales, to be sure.\n\nHurry! Let's stay focused on our Dailies to defeat this monster before it possesses someone else.",
"questBewilderBossRageStables": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nAhh!!! Once again the Be-Wilder has dazzled us into neglecting our Dailies, and now it has attacked Matt the Beast Master! With a swirl of mist, Matt transforms into a terrifying winged creature, and all the pets and mounts howl sadly in their stables. Quickly, stay focused on your tasks to defeat this dastardly distraction!",
- "questBewilderBossRageBailey": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nLook out! In the middle of reporting the news, Bailey the Town Crier has been possessed by the Be-Wilder! She lets out an evil, uninformative screech as she rises into the air. Now how will we know what’s going on?\n\nDon't give up... we're so close to defeating this bothersome bird for once and for all!"
+ "questBewilderBossRageBailey": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nLook out! In the middle of reporting the news, Bailey the Town Crier has been possessed by the Be-Wilder! She lets out an evil, uninformative screech as she rises into the air. Now how will we know what’s going on?\n\nDon't give up... we're so close to defeating this bothersome bird for once and for all!",
+ "questFalconText": "The Birds of Preycrastination",
+ "questFalconNotes": "Mt. Habitica is being overshadowed by a looming mountain of To-Dos. It used to be a place to picnic and enjoy a sense of accomplishment, until the neglected tasks grew out of control. Now it's home to fearsome Birds of Preycrastination, foul creatures which stop Habiticans from completing their tasks!
\"It's too hard!\" they caw at @JonArinbjorn and @Onheiron. \"It'll take too long to do right now! It won't make any difference if you wait until tomorrow! Why don't you do something fun instead?\"
No more, you vow. You will climb your personal mountain of To-Dos and defeat the Birds of Preycrastination!",
+ "questFalconCompletion": "Having finally triumphed over the Birds of Preycrastination, you settle down to enjoy the view and your well-earned rest.
\"Wow!\" says @Trogdorina. \"You won!\"
@Squish adds, \"Here, take these eggs I found as a reward.\"",
+ "questFalconBoss": "Birds of Preycrastination",
+ "questFalconDropFalconEgg": "Falcon (Egg)",
+ "questFalconUnlockText": "Unlocks purchasable Falcon eggs in the Market",
+ "questTreelingText": "The Tangle Tree",
+ "questTreelingNotes": "It's the annual Garden Competition, and everyone is talking about the mysterious project which @aurakami has promised to unveil. You join the crowd on the day of the big announcement, and marvel at the introduction of a moving tree. @fuzzytrees explains that the tree will help with garden maintenance, showing how it can mow the lawn, trim the hedge and prune the roses all at the same time – until the tree suddenly goes wild, turning its secateurs on its creator! The crowd panics as everyone tries to flee, but you aren't afraid – you leap forward, ready to do battle.",
+ "questTreelingCompletion": "You dust yourself off as the last few leaves drift to the floor. In spite of the upset, the Garden Competition is now safe – although the tree you just reduced to a heap of wood chips won't be winning any prizes! \"Still a few kinks to work out there,\" @PainterProphet says. \"Perhaps someone else would do a better job of training the saplings. Do you fancy a go?\"",
+ "questTreelingBoss": "Tangle Tree",
+ "questTreelingDropTreelingEgg": "Treeling (Egg)",
+ "questTreelingUnlockText": "Unlocks purchasable Treeling eggs in the Market"
}
\ No newline at end of file
diff --git a/common/locales/uk/rebirth.json b/common/locales/uk/rebirth.json
index 4dfdf32880..524d13ee08 100644
--- a/common/locales/uk/rebirth.json
+++ b/common/locales/uk/rebirth.json
@@ -5,14 +5,14 @@
"rebirthStartOver": "Переродження запускає вашого персонажа заново з 1го рівня.",
"rebirthAdvList1": "Ви повністю оздоровилися.",
"rebirthAdvList2": "У вас немає досвіду, золота, чи спорядження (крім безкоштовних предметів, наприклад, таємничих предметів).",
- "rebirthAdvList3": "Ваші „Звички“, „Щоденні“ та „Зробити“ відкотилися до жовтого, серії також відкотилися.",
+ "rebirthAdvList3": "Your Habits, Dailies, and To-Dos reset to yellow, and streaks reset, except for challenge tasks.",
"rebirthAdvList4": "Ви починаєте класом воїна, допоки не заслужите новий клас.",
"rebirthInherit": "Ваш персонаж успадкує декілька речей від попередника:",
"rebirthInList1": "Завдання, історія та налаштування збереглися.",
"rebirthInList2": "Випробування, ґільдії та гурти, до яких Ви належали, збереглися.",
"rebirthInList3": "Самоцвіти, рівень співавтора та мецената збереглися.",
"rebirthInList4": "Предмети придбано, ті, що випали (улюбленці чи скакуни) збереглися, але Ви не можете їх використовувати, допоки не розблокуєте їх знову.",
- "rebirthInList5": "Limited edition equipment you've purchased can be repurchased, even if its event has ended. To repurchase class-specific equipment, you must first change to the correct class.",
+ "rebirthInList5": "Спорядження обмеженого видання, яке Ви придбали, може бути придбане знову, незважаючи на те, що час того заходу вже вичерпався. Щоб знову придбати спорядження для конкретного класу, спершу Ви маєте змінити клас на потрібний.",
"rebirthEarnAchievement": "Ви отримали досягнення за початок нової пригоди!",
"beReborn": "Переродись-но!",
"rebirthAchievement": "Ви почали нову пригоду! Це Ваше <%= number %> переродження, Ви досягали найбільше <%= level %> рівня. Аби збільшити це досягнення, почніть свою нову пригоду після того, як досягнете вищого рівня!",
@@ -24,5 +24,6 @@
"rebirthPop": "Розпочніть заново з персонажем 1 рівня, зберігши досягнення, колекційні предмети та завдання з історією.",
"rebirthName": "Куля переродження",
"reborn": "Переродження, максимальний рівень <%= reLevel %>",
- "confirmReborn": "Are you sure?"
+ "confirmReborn": "Ви впевнені?",
+ "rebirthComplete": "You have been reborn!"
}
\ No newline at end of file
diff --git a/common/locales/uk/settings.json b/common/locales/uk/settings.json
index f3d6bccc06..479b909644 100644
--- a/common/locales/uk/settings.json
+++ b/common/locales/uk/settings.json
@@ -47,6 +47,7 @@
"customDayStart": "Обрати початок доби",
"changeCustomDayStart": "Change Custom Day Start?",
"sureChangeCustomDayStart": "Are you sure you want to change your custom day start?",
+ "customDayStartHasChanged": "Your custom day start has changed.",
"nextCron": "Your Dailies will next reset the first time you use Habitica after <%= time %>. Make sure you have completed your Dailies before this time!",
"customDayStartInfo1": "Habitica defaults to check and reset your Dailies at midnight in your own time zone each day. You can customize that time here.",
"misc": "Усяке",
@@ -61,12 +62,23 @@
"newUsername": "New Login Name",
"dangerZone": "Небезпечна зона",
"resetText1": "УВАГА! Це призведе до скидання багатьох частин вашого акаунта. Це вкрай небажано, але деякі люди вважають це корисним після гри з сайтом протягом короткого часу.",
- "resetText2": "Ви втратите всі рівні, золото та очки досвіду. Всі ваші завдання будуть видалені і ви втратите всі історичні дані завдань. Ви втратите увесь ваш обладунок, але матимете можливість придбати їх знову, включаючи всі обмежені тиражем обладунки або Загадкові предмети за підписку, які у вас вже є (ви повинні бути в правильному класі, щоб докуповувати Класові обладунки). Ви збережете поточний клас, улюбленців і їздових тварин. Ви можете віддати перевагу Зіллю Переродження, яке набагато безпечніший варіант і який збереже ваші завдання.",
+ "resetText2": "You will lose all your levels, gold, and experience points. All your tasks (except those from challenges) will be deleted permanently and you will lose all of their historical data. You will lose all your equipment but you will be able to buy it all back, including all limited edition equipment or subscriber Mystery items that you already own (you will need to be in the correct class to re-buy class-specific gear). You will keep your current class and your pets and mounts. You might prefer to use an Orb of Rebirth instead, which is a much safer option and which will preserve your tasks.",
"deleteText": "Ви впевнені? Акаунт буде повністю вилучено назавжди, і його неможливо буде відновити! Вам треба буде знову реєструватися, аби грати у Habitica. Накопичені й витрачені самоцвіти не буде повернено. Якщо Ви цілковито впевнені, надрукуйте <%= deleteWord %> у текстовому полі нижче.",
"API": "API",
+ "APIv3": "API v3",
+ "APIv2": "API v2 - Deprecated",
"APIText": "Скопіюйте це для використання в додатках сторонніх розробників. Але пам'ятайте, що API Token використовується як пароль, тож не поширюйте його публічно. Інколи вас питатимуть про ID користувача, але не публікуйте свій API Token там, де інші можуть побачити його, у тому числі на GitHub.",
"APIToken": "Отримати API (це пароль - прочитайте застереження вище!)",
+ "thirdPartyApps": "Third Party Apps",
+ "dataToolDesc": "A webpage that shows you certain information from your Habitica account, such as statistics about your tasks, equipment, and skills.",
+ "beeminder": "Beeminder",
+ "beeminderDesc": "Let Beeminder automatically monitor your Habitica To-Dos. You can commit to maintaining a target number of To-Dos completed per day or per week, or you can commit to gradually reducing your remaining number of uncompleted To-Dos. (By \"commit\" Beeminder means under threat of paying actual money! But you may also just like Beeminder's fancy graphs.)",
+ "chromeChatExtension": "Chrome Chat Extension",
+ "chromeChatExtensionDesc": "The Chrome Chat Extension for Habitica adds an intuitive chat box to all of habitica.com. It allows users to chat in the Tavern, their party, and any guilds they are in.",
+ "otherExtensions": "Other Extensions",
+ "otherDesc": "Find other apps, extensions, and tools on the Habitica wiki.",
"resetDo": "Так, зробіть повне скидання акаунту!",
+ "resetComplete": "Reset complete!",
"fixValues": "Виправити дані",
"fixValuesText1": "Якщо проґрамна помилка, або ненавмисні дії спричинили зміну параметрів персонажа (ушкодження, яке Ви не мали отримати, золото, яке Ви не заробили тощо), Ви можете самотужки відредагувати числа тут. Так, авжеж цим можна шахраювати: користуйтеся цією функцією мудро, аби не підривати процес вироблення звички!",
"fixValuesText2": "Зверніть увагу, що тут неможливо відновити лічильник низки завдань. Аби це зробити, зайдіть у редагування щоденного завдання до секції „Додатково“, там Ви знайдете поле „Відновити серію“.",
@@ -96,6 +108,7 @@
"emailNotifications": "Сповіщення по електронній пошті ",
"wonChallenge": "Ви виграли випробування! ",
"newPM": "Received Private Message",
+ "sentGems": "Sent gems!",
"giftedGems": "Gifted Gems",
"giftedGemsInfo": "<%= amount %> Gems - by <%= name %>",
"giftedSubscription": "Gifted Subscription",
@@ -135,6 +148,11 @@
"webhooks": "Webhooks",
"enabled": "Enabled",
"webhookURL": "Webhook URL",
+ "invalidUrl": "invalid url",
+ "invalidEnabled": "the \"enabled\" parameter should be a boolean",
+ "regIdRequired": "RegId is required",
+ "pushDeviceAdded": "Push device added successfully",
+ "pushDeviceAlreadyAdded": "The user already has the push device",
"add": "Add",
"buyGemsGoldCap": "Cap raised to <%= amount %>",
"mysticHourglass": "<%= amount %> Mystic Hourglass",
@@ -149,5 +167,5 @@
"amazonPayments": "Amazon Payments",
"timezone": "Time Zone",
"timezoneUTC": "Habitica uses the time zone set on your PC, which is: <%= utc %>",
- "timezoneInfo": "If that time zone is wrong, first reload this page using your browser's reload or refresh button to ensure that Habitica has the most recent information. If it is still wrong, adjust the time zone on your PC and then reload this page again.
If you use Habitica on other PCs or mobile devices, the time zone must be the same on them all. If your Dailies have been reseting at the wrong time, repeat this check on all other PCs and on a browser on your mobile devices."
+ "timezoneInfo": "If that time zone is wrong, first reload this page using your browser's reload or refresh button to ensure that Habitica has the most recent information. If it is still wrong, adjust the time zone on your PC and then reload this page again.
If you use Habitica on other PCs or mobile devices, the time zone must be the same on them all. If your Dailies have been resetting at the wrong time, repeat this check on all other PCs and on a browser on your mobile devices."
}
\ No newline at end of file
diff --git a/common/locales/uk/spells.json b/common/locales/uk/spells.json
index 12a6a23237..75ed2d220e 100644
--- a/common/locales/uk/spells.json
+++ b/common/locales/uk/spells.json
@@ -35,8 +35,8 @@
"spellSpecialSnowballAuraNotes": "Жбурніть сніжок в членів групи! Що може статися? Триває, до наступного ігрового дня.",
"spellSpecialSaltText": "Сіль",
"spellSpecialSaltNotes": "Хтось жбурнув у Вас сніжкою. Ха-ха, дуже смішно. Ану ж струсіть із мене цей сніг!",
- "spellSpecialSpookDustText": "Моторошні Іскри",
- "spellSpecialSpookDustNotes": "Перетворіть друга у літаючу ковдру з очима! ",
+ "spellSpecialSpookySparklesText": "Spooky Sparkles",
+ "spellSpecialSpookySparklesNotes": "Turn a friend into a floating blanket with eyes!",
"spellSpecialOpaquePotionText": "Непрозоре Зілля",
"spellSpecialOpaquePotionNotes": "Відмінити ефект Моторошних Іскор.",
"spellSpecialShinySeedText": "Shiny Seed",
@@ -46,5 +46,11 @@
"spellSpecialSeafoamText": "Морська Піна",
"spellSpecialSeafoamNotes": "Перетворіть друга на морське створіння! ",
"spellSpecialSandText": "Пісок",
- "spellSpecialSandNotes": "Відмінити ефект Морської Піни"
+ "spellSpecialSandNotes": "Відмінити ефект Морської Піни",
+ "spellNotFound": "Skill \"<%= spellId %>\" not found.",
+ "partyNotFound": "Party not found",
+ "targetIdUUID": "\"targetId\" must be a valid User ID.",
+ "challengeTasksNoCast": "Casting a skill on challenge tasks is not allowed.",
+ "spellNotOwned": "You don't own this skill.",
+ "spellLevelTooHigh": "You must be level <%= level %> to use this skill."
}
\ No newline at end of file
diff --git a/common/locales/uk/subscriber.json b/common/locales/uk/subscriber.json
index 082edcc0ca..da10322f29 100644
--- a/common/locales/uk/subscriber.json
+++ b/common/locales/uk/subscriber.json
@@ -4,6 +4,8 @@
"subDescription": "Buy Gems with gold, get monthly mystery items, retain progress history, double daily drop-caps, support the devs. Click for more info.",
"buyGemsGold": "Придбати самоцвіти за золото",
"buyGemsGoldText": "Alexander the Merchant will sell you Gems at a cost of <%= gemCost %> gold per gem. His monthly shipments are initially capped at <%= gemLimit %> Gems per month, but this cap increases by 5 Gems for every three months of consecutive subscription, up to a maximum of 50 Gems per month!",
+ "mustSubscribeToPurchaseGems": "Must subscribe to purchase gems with GP",
+ "reachedGoldToGemCap": "You've reached the Gold=>Gem conversion cap <%= convCap %> for this month. We have this to prevent abuse / farming. The cap will reset within the first three days of next month.",
"retainHistory": "Retain additional history entries",
"retainHistoryText": "Makes completed To-Dos and task history available for longer.",
"doubleDrops": "Щоденне випадання предметів подвоїлося",
@@ -29,6 +31,7 @@
"manageSub": "Клацніть, аби редагувати підписку",
"cancelSub": "Скасувати підписку",
"canceledSubscription": "Canceled Subscription",
+ "cancelingSubscription": "Canceling the subscription",
"adminSub": "Підписка адміністратора",
"morePlans": "More Plans Coming Soon",
"organizationSub": "Приватна орґанізація",
@@ -73,6 +76,9 @@
"timeTravelersPopover": "We see you have a Mystic Hourglass, so we will happily travel back in time for you! Please choose the pet, mount, or Mystery Item Set you would like. You can see a list of the past item sets here! If those don't satisfy you, perhaps you'd be interested in one of our fashionably futuristic Steampunk Item Sets?",
"timeTravelersAlreadyOwned": "Congratulations! You already own everything the Time Travelers currently offer. Thanks for supporting the site!",
"mysticHourglassPopover": "A Mystic Hourglass allows you to purchase certain limited-time items, such as monthly Mystery Item Sets and awards from world bosses, from the past!",
+ "mysterySetNotFound": "Mystery set not found, or set already owned.",
+ "mysteryItemIsEmpty": "Mystery items are empty",
+ "mysteryItemOpened": "Mystery item opened.",
"mysterySet201402": "Winged Messenger Set",
"mysterySet201403": "Forest Walker Set",
"mysterySet201404": "Twilight Butterfly Set",
@@ -99,6 +105,8 @@
"mysterySet201601": "Champion of Resolution Set",
"mysterySet201602": "Heartbreaker Set",
"mysterySet201603": "Lucky Clover Set",
+ "mysterySet201604": "Leaf Warrior Set",
+ "mysterySet201605": "Marching Bard Set",
"mysterySet301404": "Steampunk Standard Set",
"mysterySet301405": "Steampunk Accessories Set",
"mysterySetwondercon": "Wondercon",
@@ -110,9 +118,25 @@
"hourglassBuyItemConfirm": "Buy this item for 1 Mystic Hourglass?",
"petsAlreadyOwned": "Pet already owned.",
"mountsAlreadyOwned": "Mount already owned.",
- "typeNotAllowedHourglass": "Item type not supported for purchase with Mystic Hourglass. Allowed types:",
+ "typeNotAllowedHourglass": "Item type not supported for purchase with Mystic Hourglass. Allowed types: <%= allowedTypes %>",
"petsNotAllowedHourglass": "Pet not available for purchase with Mystic Hourglass.",
"mountsNotAllowedHourglass": "Mount not available for purchase with Mystic Hourglass.",
"hourglassPurchase": "Purchased an item using a Mystic Hourglass!",
- "hourglassPurchaseSet": "Purchased an item set using a Mystic Hourglass!"
+ "hourglassPurchaseSet": "Purchased an item set using a Mystic Hourglass!",
+ "missingUnsubscriptionCode": "Missing unsubscription code.",
+ "missingSubscription": "User does not have a plan subscription",
+ "missingSubscriptionCode": "Missing subscription code. Possible values: basic_earned, basic_3mo, basic_6mo, google_6mo, basic_12mo.",
+ "cannotDeleteActiveAccount": "You have an active subscription, cancel your plan before deleting your account.",
+ "paymentNotSuccessful": "The payment was not successful",
+ "planNotActive": "The plan hasn't activated yet (due to a PayPal bug). It will begin <%= nextBillingDate %>, after which you can cancel to retain your full benefits",
+ "notAllowedHourglass": "Pet/Mount not available for purchase with Mystic Hourglass.",
+ "readCard": "<%= cardType %> has been read",
+ "cardTypeRequired": "Card type required",
+ "cardTypeNotAllowed": "Unknown card type.",
+ "invalidCoupon": "Invalid coupon code.",
+ "couponUsed": "Coupon code already used.",
+ "noSudoAccess": "You don't have sudo access.",
+ "couponCodeRequired": "The coupon code is required.",
+ "eventRequired": "\"req.params.event\" is required.",
+ "countRequired": "\"req.query.count\" is required."
}
\ No newline at end of file
diff --git a/common/locales/uk/tasks.json b/common/locales/uk/tasks.json
index 0b5847cbe1..d1a49842e5 100644
--- a/common/locales/uk/tasks.json
+++ b/common/locales/uk/tasks.json
@@ -4,17 +4,17 @@
"deleteToDosExplanation": "Якщо натиснути на кнопку внизу, то всі з виконаних Зробити та архівних Зробити будуть остаточно вилучені. Якщо хочете зберегти запис про них, спершу експортуйте їх.",
"addmultiple": "Додати декілька",
"addsingle": "Додати одне",
- "habit": "Habit",
+ "habit": "Звичка",
"habits": "Звички",
"newHabit": "Додати звичку",
"newHabitBulk": "Нові звички (одна на рядок)",
- "yellowred": "Шкідливі",
- "greenblue": "Корисні",
+ "yellowred": "Слабкі",
+ "greenblue": "Сильні",
"edit": "Редагувати",
"save": "Зберегти",
"addChecklist": "Додати перелік",
"checklist": "Перелік",
- "checklistText": "Розбийте задачі на меньші шматки! Переліки збільшують кількість досвіду та золота, які ви отримаєте за виконання задачі, та зменьшують ушкодження від щоденніх задач.",
+ "checklistText": "Розбивайте великі задачі на менші! Коли Ви розбиваете задачу на підзавдання, Ви збільшуєте досвід та золото, які отримаєте за його виконання, а також зменшуєте ушкодження від невиконання щоденних завдань.",
"expandCollapse": "Показати/Приховати",
"text": "Назва",
"extraNotes": "Додаткові примітки",
@@ -32,7 +32,7 @@
"mental": "Розумове",
"otherExamples": "До прикладу, фахова мета, захоплення, фінанси тощо.",
"progress": "Поступ",
- "daily": "Daily",
+ "daily": "Щоденне завдання",
"dailies": "Щоденні",
"newDaily": "Нове щоденне",
"newDailyBulk": "Нові щоденні (одне на рядок)",
@@ -47,7 +47,7 @@
"day": "День",
"days": "Днів",
"restoreStreak": "Відновити серію",
- "todo": "To-Do",
+ "todo": "Завдання ",
"todos": "Зробити",
"newTodo": "Додати завдання",
"newTodoBulk": "Нові завдання (одна на рядок)",
@@ -55,11 +55,11 @@
"remaining": "Активні",
"complete": "Виконано",
"dated": "З датою",
- "due": "Повинен зробити",
+ "due": "Не виконані",
"notDue": "Не обов'язково",
"grey": "Сірі",
"score": "Рахунок",
- "reward": "Reward",
+ "reward": "Нагорода",
"rewards": "Нагороди",
"ingamerewards": "Обладунки та Уміння",
"gold": "Золото",
@@ -73,6 +73,7 @@
"clearTags": "Очистити",
"hideTags": "Приховати",
"showTags": "Показати",
+ "toRequired": "You must supply a to value",
"startDate": "Дата початку",
"startDateHelpTitle": "Коли має починатися це завдання?",
"startDateHelp": "Вкажіть дату, коли це завдання набуде чинності. До цієї дати воно буде не обов'язковим.",
@@ -88,9 +89,10 @@
"fortifyName": "Зілля підсилення",
"fortifyPop": "Повернути завдання до нейтрального стану (жовтого кольору) та відновити все здоров'я.",
"fortify": "Підсилитися",
- "fortifyText": "Підсилення поверне всі ваші завдання у нейтральний (жовтий) колір, немов ви щойно їх додали, та відновить все ваше здоров’я. Це дуже корисно, якщо ваші червоні задачі роблять гру занадто складною, а сині — занадто простою. Якщо новий початок з чистої сторінки вас мотивує, не шкодуйте самоцвіти та отримайте тимчасове полегшення!",
- "confirmFortify": "Are you sure?",
- "sureDelete": "Are you sure you want to delete the <%= taskType %> with the text \"<%= taskText %>\"?",
+ "fortifyText": "Fortify will return all your tasks, except challenge tasks, to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!",
+ "confirmFortify": "Ви впевнені?",
+ "fortifyComplete": "Fortify complete!",
+ "sureDelete": "Ви впевнені, що хочете видалити <%= taskType %> під назвою \"<%= taskText %>\"?",
"streakCoins": "Бонус за серію!",
"pushTaskToTop": "Перемістити завдання нагору. Натисніть CTRL або CMD, щоб перемістити його донизу",
"emptyTask": "Спочатку введіть назву завдання",
@@ -112,5 +114,18 @@
"rewardHelp2": "Спорядження впливає на ваші характеристики (<%= linkStart %>Персонаж > Характеристики<%= linkEnd %>).",
"rewardHelp3": "Спеціальне спорядження з'являтиметься тут під час Світових подій",
"rewardHelp4": "Не бійтеся встановлювати власні Нагороди! Огляньте кілька прикладів тут.",
- "clickForHelp": "Клацніть для довідки"
+ "clickForHelp": "Клацніть для довідки",
+ "taskIdRequired": "\"taskId\" must be a valid UUID.",
+ "taskNotFound": "Task not found.",
+ "invalidTaskType": "Task type must be one of \"habit\", \"daily\", \"todo\", \"reward\".",
+ "cantDeleteChallengeTasks": "A task belonging to a challenge can't be deleted.",
+ "checklistOnlyDailyTodo": "Checklists are supported only on dailies and todos",
+ "checklistItemNotFound": "No checklist item was found with given id.",
+ "itemIdRequired": "\"itemId\" must be a valid UUID.",
+ "tagNotFound": "No tag item was found with given id.",
+ "tagIdRequired": "\"tagId\" must be a valid UUID corresponding to a tag belonging to the user.",
+ "positionRequired": "\"position\" is required and must be a number.",
+ "cantMoveCompletedTodo": "Can't move a completed todo.",
+ "directionUpDown": "\"direction\" is required and must be 'up' or 'down'",
+ "alreadyTagged": "The task is already tagged with given tag."
}
\ No newline at end of file
diff --git a/common/locales/zh/backgrounds.json b/common/locales/zh/backgrounds.json
index 70f62726ca..f29670cd3a 100644
--- a/common/locales/zh/backgrounds.json
+++ b/common/locales/zh/backgrounds.json
@@ -154,11 +154,18 @@
"backgroundRainforestNotes": "到雨林中冒险。",
"backgroundStoneCircleText": "巨石阵",
"backgroundStoneCircleNotes": "在巨石阵中释放魔法",
- "backgrounds042016": "SET 23: Released April 2016",
- "backgroundArcheryRangeText": "Archery Range",
- "backgroundArcheryRangeNotes": "Practice on the Archery Range.",
- "backgroundGiantFlowersText": "Giant Flowers",
- "backgroundGiantFlowersNotes": "Frolic atop Giant Flowers.",
- "backgroundRainbowsEndText": "End of the Rainbow",
- "backgroundRainbowsEndNotes": "Discover gold at the End of the Rainbow."
+ "backgrounds042016": "第23组:2016年4月推出",
+ "backgroundArcheryRangeText": "射箭场",
+ "backgroundArcheryRangeNotes": "在射箭场练习。",
+ "backgroundGiantFlowersText": "巨大的花",
+ "backgroundGiantFlowersNotes": "在巨大的花顶嬉戏。",
+ "backgroundRainbowsEndText": "彩虹的尽头",
+ "backgroundRainbowsEndNotes": "在彩虹的尽头发现金子。",
+ "backgrounds052016": "第24组:2016年5月推出",
+ "backgroundBeehiveText": "蜂窝",
+ "backgroundBeehiveNotes": "在蜂窝里嗡嗡嗡地跳舞",
+ "backgroundGazeboText": "亭子",
+ "backgroundGazeboNotes": "攻击一个亭子",
+ "backgroundTreeRootsText": "树根",
+ "backgroundTreeRootsNotes": "探索茂密的树根"
}
\ No newline at end of file
diff --git a/common/locales/zh/challenge.json b/common/locales/zh/challenge.json
index b407f9a0f8..b26a01f029 100644
--- a/common/locales/zh/challenge.json
+++ b/common/locales/zh/challenge.json
@@ -57,11 +57,27 @@
"prizeValue": "<%= gemcount %> <%= gemicon %> 奖励",
"clone": "复制",
"challengeNotEnoughGems": "你的宝石数不足以发起这一挑战。",
- "noPermissionEditChallenge": "你没有权限修改这个挑战。",
+ "noPermissionEditChallenge": "你没有编辑这个挑战的权限",
"noPermissionDeleteChallenge": "你没有权限删除这个挑战。",
"noPermissionCloseChallenge": "你没有权限关闭这个挑战。",
"congratulations": "恭喜!",
"hurray": "万岁!",
"noChallengeOwner": "没有主持人",
- "noChallengeOwnerPopover": "这项挑战没有主持人,因为创建该挑战的用户已删除帐号"
+ "noChallengeOwnerPopover": "这项挑战没有主持人,因为创建该挑战的用户已删除帐号",
+ "challengeMemberNotFound": "挑战成员中找不到用户",
+ "onlyGroupLeaderChal": "仅有队长能创建挑战",
+ "tavChalsMinPrize": "酒馆挑战的奖励至少1颗宝石。",
+ "cantAfford": "你不能支付这个奖励。请购买更多宝石或减少奖励数量。",
+ "challengeIdRequired": "\"challengeId\"必须是一个有效的UUID。",
+ "winnerIdRequired": "\"winnerId\"必须是一个有效的UUID。",
+ "challengeNotFound": "找不到挑战。",
+ "onlyLeaderDeleteChal": "仅有挑战发起者能删除它。",
+ "onlyLeaderUpdateChal": "仅有挑战发起者能更新它。",
+ "winnerNotFound": "获胜者ID \"<%= userId %>\"找不到或者不是挑战的一部分。",
+ "noCompletedTodosChallenge": "\"includeComepletedTodos\" is not supported when fetching challenge tasks.",
+ "userTasksNoChallengeId": "当\"tasksOwner\"是\"user\" \"challengeId\",则不会通过。",
+ "onlyChalLeaderEditTasks": "属于一个挑战的任务仅能被发起者编辑。",
+ "userAlreadyInChallenge": "用户已经加入此挑战。",
+ "cantOnlyUnlinkChalTask": "仅有破碎的挑战任务能被拆开。",
+ "shortNameTooShort": "Tag Name must have at least 3 characters."
}
\ No newline at end of file
diff --git a/common/locales/zh/character.json b/common/locales/zh/character.json
index f22f558968..52ef6637dc 100644
--- a/common/locales/zh/character.json
+++ b/common/locales/zh/character.json
@@ -1,4 +1,5 @@
{
+ "communityGuidelinesWarning": "请注意您的显示名称、照片和简介,它们必须遵从社区指南 (例如,没有亵渎言论,没有成人话题,没有侮辱言论)。如果您对是否适当有任何问题,欢迎发送电子邮件给leslie@habitica.com!",
"statsAch": "角色属性及成就",
"profile": "角色信息",
"avatar": "角色形象",
@@ -34,7 +35,7 @@
"beard": "落腮胡",
"mustache": "八字胡",
"flower": "花",
- "wheelchair": "Wheelchair",
+ "wheelchair": " 轮椅",
"basicSkins": "基本肤色",
"rainbowSkins": "彩虹肤色",
"pastelSkins": "柔和肤色",
@@ -109,6 +110,7 @@
"mage": "法师",
"mystery": "神秘",
"changeClass": "更改职业,重新分配属性点",
+ "lvl10ChangeClass": "您必须达到等级10才可以更换职业。",
"levelPopover": "每一级你可以获得一个可自由分配的属性点。你可以手动分配,或者让系统为你自动分配。",
"unallocated": "未分配的属性点",
"haveUnallocated": "你有 <%= points %> 点未分配的属性点",
@@ -164,5 +166,7 @@
"int": "智力",
"showQuickAllocation": "显示属性分配",
"hideQuickAllocation": "隐藏属性分配",
- "quickAllocationLevelPopover": "每个等级获得一点属性点供你分配。你可以手工分配或者用一种自动分配选项让游戏帮你决定,在 玩家 -> 角色属性及成就 中选择。"
+ "quickAllocationLevelPopover": "每个等级获得一点属性点供你分配。你可以手工分配或者用一种自动分配选项让游戏帮你决定,在 玩家 -> 角色属性及成就 中选择。",
+ "invalidAttribute": "\"<%= attr %>\" 不是一个有效的属性点分配数字。",
+ "notEnoughAttrPoints": "您没有足够的属性点点数。"
}
\ No newline at end of file
diff --git a/common/locales/zh/content.json b/common/locales/zh/content.json
index f126b8d003..637e98cd66 100644
--- a/common/locales/zh/content.json
+++ b/common/locales/zh/content.json
@@ -113,6 +113,12 @@
"questEggSnailText": "蜗牛",
"questEggSnailMountText": "蜗牛",
"questEggSnailAdjective": "一只缓慢但沉着的",
+ "questEggFalconText": "猎鹰",
+ "questEggFalconMountText": "猎鹰",
+ "questEggFalconAdjective": "一只迅捷的",
+ "questEggTreelingText": "小树芽",
+ "questEggTreelingMountText": "小树芽",
+ "questEggTreelingAdjective": "一只叶子般的",
"eggNotes": "将一瓶孵化药水倒在这个宠物蛋上,你就能孵化出一只<%= eggAdjective(locale) %><%= eggText(locale) %>。",
"hatchingPotionBase": "普通",
"hatchingPotionWhite": "白色",
@@ -126,6 +132,7 @@
"hatchingPotionGolden": "金色",
"hatchingPotionSpooky": "阴森森的",
"hatchingPotionPeppermint": "薄荷",
+ "hatchingPotionFloral": "草花",
"hatchingPotionNotes": "把它倒在宠物蛋上可以孵化出一只<%= potText(locale) %>宠物。",
"premiumPotionAddlNotes": "无法在任务奖励宠物蛋上使用",
"foodMeat": "肉",
diff --git a/common/locales/zh/contrib.json b/common/locales/zh/contrib.json
index 31bf4b6fd6..ea00e8056a 100644
--- a/common/locales/zh/contrib.json
+++ b/common/locales/zh/contrib.json
@@ -35,8 +35,12 @@
"hallContributors": "贡献者殿堂",
"hallPatrons": "赞助人殿堂",
"rewardUser": "奖励玩家",
- "UUID": "帐号",
+ "UUID": "用户ID",
"loadUser": "载入玩家",
+ "noAdminAccess": "你没有管理员权限。",
+ "pageMustBeNumber": "req.query.page必须是一个数字",
+ "userNotFound": "找不到用户",
+ "invalidUUID": "UUID 必须有效",
"title": "头衔",
"moreDetails": "更多详情(1-7)",
"moreDetails2": "更多详情(8-9)",
@@ -52,8 +56,8 @@
"visitHeroes": "前往英雄殿堂 (贡献者和支持者之殿)",
"conLearn": "查看更多关于贡献奖励的信息",
"conLearnHow": "如何为Habitica作出贡献",
- "surveysSingle": "为了帮助Habitica成长而填写了一份问卷。现在没有任何可填的问卷",
- "surveysMultiple": "为了帮助Habitica成长而填写了 <%= surveys %> 份问卷。现在没有任何可填的问卷",
+ "surveysSingle": "Helped Habitica grow, either by filling out a survey or helping with a major testing effort. Thank you!",
+ "surveysMultiple": "Helped Habitica grow on <%= surveys %> occasions, either by filling out a survey or helping with a major testing effort. Thank you!",
"currentSurvey": "现在的问卷",
"surveyWhen": "那个章将会在三约尾给所有参与过问卷的玩家",
"blurbInbox": "这是你的私人邮件箱!你能在酒馆、队伍或者工会聊天中通过点击其他人名字边上的邮件图标给他们发送信息。如果你收到了一封不合适的私人消息,请截图发给柠檬小姐(leslie@habitica.com)。",
diff --git a/common/locales/zh/death.json b/common/locales/zh/death.json
index 1345337761..787da98b2f 100644
--- a/common/locales/zh/death.json
+++ b/common/locales/zh/death.json
@@ -12,5 +12,6 @@
"losingHealthQuickly": "生命值丢失很快?",
"lowHealthTips3": "前一晚仍未完成的每日任务会使你收到伤害,所以请小心,初期别添加太多每日任务!",
"lowHealthTips4": "如果每日任务在某一天仍未到期,你可以点击铅笔图标来取消它。",
- "goodLuck": "祝你好运!"
+ "goodLuck": "祝你好运!",
+ "cannotRevive": "如果没有死亡就不能复活"
}
\ No newline at end of file
diff --git a/common/locales/zh/front.json b/common/locales/zh/front.json
index a7c388bd05..fce8280728 100644
--- a/common/locales/zh/front.json
+++ b/common/locales/zh/front.json
@@ -28,6 +28,7 @@
"communityReddit": "Reddit",
"companyAbout": "如何使用",
"companyBlog": "博客",
+ "devBlog": "开发者部落格",
"companyDonate": "捐款",
"companyExtensions": "插件",
"companyPrivacy": "隐私",
@@ -51,6 +52,7 @@
"featureSocialHeading": "多人游戏",
"featuredIn": "特别提供",
"featuresHeading": "我们还有这些特别的……",
+ "footerDevs": "开发者",
"footerCommunity": "社区",
"footerCompany": "公司",
"footerMobile": "手机版",
@@ -182,6 +184,7 @@
"zelahQuote": "因为 [Habitica] 的帮助,我能够准时上床休息了,因为我老想着早睡能挣经验,晚睡会掉血!",
"reportAccountProblems": "报告账户问题",
"reportCommunityIssues": "报告社区问题",
+ "subscriptionPaymentIssues": "Subscription and Payment Issues",
"generalQuestionsSite": "关于本站的常见问题",
"businessInquiries": "业务咨询",
"merchandiseInquiries": "商业咨询",
@@ -222,5 +225,32 @@
"altAttrWebstorm": "WebStorm",
"altAttrGithub": "GitHub",
"altAttrTrello": "Trello",
- "altAttrSlack": "Slack"
+ "altAttrSlack": "Slack",
+ "missingAuthHeaders": "Missing authentication headers.",
+ "missingAuthParams": "Missing authentication parameters.",
+ "missingUsernameEmail": "缺少用户名或邮箱。",
+ "missingEmail": "缺少电子邮件地址。",
+ "missingUsername": "缺少用户名。",
+ "missingPassword": "缺少密码。",
+ "missingNewPassword": "缺少新密码。",
+ "wrongPassword": "密码错误。",
+ "notAnEmail": "无效的电子邮件地址。",
+ "emailTaken": "邮件地址已经在现有账号中存在",
+ "newEmailRequired": "缺少新的邮件地址",
+ "usernameTaken": "用户名已被使用",
+ "passwordConfirmationMatch": "密码不匹配",
+ "invalidLoginCredentials": "Incorrect username and/or email and/or password.",
+ "passwordReset": "If we have your email on file, your password reset link has been sent to your email.",
+ "passwordResetEmailSubject": "Password Reset for Habitica",
+ "passwordResetEmailText": "Password for <%= username %> has been reset to <%= newPassword %> . Important! Both username and password are case-sensitive -- you must enter both exactly as shown here. We recommend copying and pasting both instead of typing them. Log in at <%= baseUrl %>. After you have logged in, head to <%= baseUrl %>/#/options/settings/settings and change your password.",
+ "passwordResetEmailHtml": "Password for <%= username %> has been reset to <%= newPassword %>.
Important! Both username and password are case-sensitive -- you must enter both exactly as shown here. We recommend copying and pasting both instead of typing them.
Log in at <%= baseUrl %>. After you have logged in, head to <%= baseUrl %>/#/options/settings/settings and change your password.",
+ "invalidLoginCredentialsLong": "Uh-oh - your username or password is incorrect.\n- Make sure your username or email is typed correctly.\n- You may have signed up with Facebook, not email. Double-check by trying Facebook login.\n- If you forgot your password, click \"Forgot Password\".",
+ "invalidCredentials": "There is no account that uses those credentials.",
+ "accountSuspended": "Account has been suspended, please contact leslie@habitica.com with your User ID \"<%= userId %>\" for assistance.",
+ "onlyFbSupported": "Only Facebook is supported currently.",
+ "cantDetachFb": "Account lacks another authentication method, can't detach Facebook.",
+ "onlySocialAttachLocal": "Local authentication can be added to only a social account.",
+ "invalidReqParams": "Invalid request parameters.",
+ "memberIdRequired": "\"member\" must be a valid UUID.",
+ "heroIdRequired": "\"heroId\" must be a valid UUID."
}
\ No newline at end of file
diff --git a/common/locales/zh/gear.json b/common/locales/zh/gear.json
index 3129c36ed0..d833f4512c 100644
--- a/common/locales/zh/gear.json
+++ b/common/locales/zh/gear.json
@@ -1,5 +1,5 @@
{
- "set": "组",
+ "set": "套装",
"weapon": "武器",
"weaponBase0Text": "没有武器",
"weaponBase0Notes": "没有武器。",
@@ -66,7 +66,7 @@
"weaponSpecial2Text": "史蒂芬韦伯的巨龙长矛",
"weaponSpecial2Notes": "感受喷薄而出的巨龙神力吧!增加力量和感知各<%= attrs %>点。",
"weaponSpecial3Text": "马斯泰恩的碎石流星锤",
- "weaponSpecial3Notes": "怪物统统捣烂!增加力量,智力,体质各<%= attrs %>点。",
+ "weaponSpecial3Notes": "怪物统统捣烂!增加体质和感知各<%= attrs %>点。",
"weaponSpecialCriticalText": "碾碎臭虫的批判战锤",
"weaponSpecialCriticalNotes": "这位勇士杀死了一个使无数战士陨落的吹毛求疵的 Github 敌人。这把战锤由臭虫的骨头打造,能造成强大的致命一击。增加力量和感知各<%= attrs %>点。",
"weaponSpecialTridentOfCrashingTidesText": "怒潮三叉戟",
@@ -187,8 +187,10 @@
"weaponArmoireJesterBatonNotes": "挥舞着手杖,妙语连珠,最复杂的情况都会变得清晰起来。增加智力和感知各 <%= attrs %> 点。魔法衣橱:小丑套装(3件中的第3件)",
"weaponArmoireMiningPickaxText": "采矿鹤嘴锄",
"weaponArmoireMiningPickaxNotes": "从你的任务中挖取最大量的金币!增加感知<%= per %>点。魔法衣橱:采矿者套装(3件中的第3件)。",
- "weaponArmoireBasicLongbowText": "Basic Longbow",
- "weaponArmoireBasicLongbowNotes": "A serviceable hand-me-down bow. Increases Strength by <%= str %>. Enchanted Armoire: Basic Archer Set (Item 1 of 3).",
+ "weaponArmoireBasicLongbowText": "基础长弓",
+ "weaponArmoireBasicLongbowNotes": "一把有用的传下来的弓。增加<%= str %>点力量。魔法衣橱:基础射手套装(3件中的第1件)。",
+ "weaponArmoireHabiticanDiplomaText": "Habitican Diploma",
+ "weaponArmoireHabiticanDiplomaNotes": "A certificate of significant achievement -- well done! Increases Intelligence by <%= int %>. Enchanted Armoire: Graduate Set (Item 1 of 3).",
"armor": "护甲",
"armorBase0Text": "普通服装",
"armorBase0Notes": "普通的衣服。 没有属性加成。",
@@ -364,6 +366,10 @@
"armorMystery201512Notes": "召唤冰冷的冬之焰!没有属性加成。2015年12月订阅者物品。",
"armorMystery201603Text": "幸运衣",
"armorMystery201603Notes": "这件衣服是成千上万的四叶草缝成的!没有属性加成。2016年3月捐赠者物品。",
+ "armorMystery201604Text": "Armor o' Leaves",
+ "armorMystery201604Notes": "You, too, can be a small but fearsome leaf puff. Confers no benefit. April 2016 Subscriber Item.",
+ "armorMystery201605Text": "Marching Bard Uniform",
+ "armorMystery201605Notes": "Unlike the traditional bards who join adventuring parties, bards who join Habitican marching bands are known for grand parades, not dungeon raids. Confers no benefit. May 2016 Subscriber Item.",
"armorMystery301404Text": "蒸汽朋克套装",
"armorMystery301404Notes": "整洁又精神,真聪明!没有属性加成。3015年2月订阅者物品",
"armorArmoireLunarArmorText": "静月护甲",
@@ -377,7 +383,7 @@
"armorArmoireHornedIronArmorText": "铁角护甲",
"armorArmoireHornedIronArmorNotes": "纯钢打造,坚不可摧。这件铁角护甲几乎无法被打破。增加<%= con %>点体质和<%= per %>点感知。魔法衣橱:铁角套装(3件套之2)。",
"armorArmoirePlagueDoctorOvercoatText": "瘟疫医生外套",
- "armorArmoirePlagueDoctorOvercoatNotes": "瘟疫拖延症主治医生穿的正式大褂。增加<%= int %>点智力, <%= str %>点力量,还有<%= con %>点体质。魔法衣橱: 瘟疫医生系列 (3件的第2件)。",
+ "armorArmoirePlagueDoctorOvercoatNotes": "瘟疫拖延症主治医生穿的正式大褂。增加<%= int %>点智力, <%= str %>点力量,还有<%= con %>点体质。魔法衣橱: 瘟疫医生系列 (3件的第3件)。",
"armorArmoireShepherdRobesText": "牧羊人长袍",
"armorArmoireShepherdRobesNotes": "轻薄凉爽透气,在沙漠中放牧狮鹫的时候穿上最完美了。增加力量和感知各<%= attrs %>点。魔法衣橱:牧羊人套装(3件中的第2件)",
"armorArmoireRoyalRobesText": "皇家长袍",
@@ -392,8 +398,10 @@
"armorArmoireJesterCostumeNotes": "的啦啦啦!尽管衣服看起来呵呵,你不是一个傻瓜。增加智力 <%= int %> 点。魔法衣橱:小丑套装(3件中的第2件)",
"armorArmoireMinerOverallsText": "采矿者工作裤",
"armorArmoireMinerOverallsNotes": "它们看起来破旧,但他们被施了魔法抵御灰尘。增加体质<%= con %>点。魔法衣橱:采矿者套装(3件中的第2件)。",
- "armorArmoireBasicArcherArmorText": "Basic Archer Armor",
- "armorArmoireBasicArcherArmorNotes": "This camouflaged vest lets you slip unnoticed through the forests. Increases Perception by <%= per %>. Enchanted Armoire: Basic Archer Set (Item 2 of 3).",
+ "armorArmoireBasicArcherArmorText": "基础射手皮甲",
+ "armorArmoireBasicArcherArmorNotes": "这件伪装的背心使你在通过森林式不被注意。增加<%= per %>点感知。魔法衣橱:基础射手套装(3件中的第2件)。",
+ "armorArmoireGraduateRobeText": "Graduate Robe",
+ "armorArmoireGraduateRobeNotes": "Congratulations! This weighty robe hangs heavy with all the knowledge you have accrued. Increases Intelligence by <%= int %>. Enchanted Armoire: Graduate Set (Item 2 of 3).",
"headgear": "头饰",
"headBase0Text": "没有头盔",
"headBase0Notes": "没有头饰",
@@ -565,6 +573,10 @@
"headMystery201602Notes": "对所有你的爱慕者隐藏你的身份。没有属性加成。2016年2月捐赠者物品。",
"headMystery201603Text": "幸运帽",
"headMystery201603Notes": "这顶大礼帽有着神奇的好运魔力。没有属性加成。2016年3月捐赠者物品。",
+ "headMystery201604Text": "Crown o' Flowers",
+ "headMystery201604Notes": "These woven flowers make a surprisingly strong helm! Confers no benefit. April 2016 Subscriber Item.",
+ "headMystery201605Text": "Marching Bard Hat",
+ "headMystery201605Notes": "Seventy-six dragons led the big parade, with a hundred and ten gryphons close at hand! Confers no benefit. May 2016 Subscriber Item.",
"headMystery301404Text": "华丽礼帽",
"headMystery301404Notes": "上流社会佼佼者的华丽礼帽!3015年1月捐赠者物品。没有属性加成。",
"headMystery301405Text": "基础礼帽",
@@ -611,8 +623,10 @@
"headArmoireJesterCapNotes": "帽子上的铃铛让对手分心,它仅帮你集中注意力。增加感知 <%= per %> 点。魔法衣橱:小丑套装(3件中的第1件)",
"headArmoireMinerHelmetText": "采矿者头盔",
"headArmoireMinerHelmetNotes": "保护你的头不被掉下的任务砸伤!增加智力 <%= int %>点。魔法衣橱:采矿者套装(3件中的第1件)。",
- "headArmoireBasicArcherCapText": "Basic Archer Cap",
- "headArmoireBasicArcherCapNotes": "No archer would be complete without a jaunty cap! Increases Perception by <%= per %>. Enchanted Armoire: Basic Archer Set (Item 3 of 3).",
+ "headArmoireBasicArcherCapText": "基础射手帽",
+ "headArmoireBasicArcherCapNotes": "只有有了一个活泼的帽子,一个射手才是完整的!增加<%= per %>点感知。魔法衣橱:基础射手套装(3件中的第3件)。",
+ "headArmoireGraduateCapText": "Graduate Cap",
+ "headArmoireGraduateCapNotes": "Congratulations! Your deep thoughts have earned you this thinking cap. Increases Intelligence by <%= int %>. Enchanted Armoire: Graduate Set (Item 3 of 3).",
"offhand": "副手物品",
"shieldBase0Text": "没有副手装备",
"shieldBase0Notes": "没有盾牌或副手武器。",
@@ -673,7 +687,7 @@
"shieldSpecialWinter2015HealerText": "抚慰之盾",
"shieldSpecialWinter2015HealerNotes": "这块盾牌偏转了刺骨的寒风。增加<%= con %>点体质。2014-2015限定版装备。",
"shieldSpecialSpring2015RogueText": "霹雳爆破管",
- "shieldSpecialSpring2015RogueNotes": "别被它的噼里啪啦声糊弄了——爆炸起来简直厉害。提高 <%= str %>点力量。2015春季限定版装备。",
+ "shieldSpecialSpring2015RogueNotes": "别让这些声音愚弄你 - 这些炸药威力巨大。 增加力量 <%= str %> 点。2015年限量版弹簧装置套装。",
"shieldSpecialSpring2015WarriorText": "菜盘子铁饼",
"shieldSpecialSpring2015WarriorNotes": "向你的敌人掷去……或者只是单纯地拿着它,因为到了晚饭时间它就会装满好吃的粗磨粉狗粮啦。提高<%= con %>点体质。2015春季限定版装备。",
"shieldSpecialSpring2015HealerText": "印花枕头",
@@ -716,6 +730,8 @@
"shieldArmoireDragonTamerShieldNotes": "用这件龙形的盾牌扰乱敌人吧。增加<%= per %>点感知。魔法衣橱:驯龙套装(3件套之2)。",
"shieldArmoireMysticLampText": "神秘的灯",
"shieldArmoireMysticLampNotes": "用神秘的灯照亮黑暗的洞穴!增加感知<%= per %>点。魔法衣橱:独立装备。",
+ "shieldArmoireFloralBouquetText": "Bouquet o' Flowers",
+ "shieldArmoireFloralBouquetNotes": "在战斗中没有什么用处。但是它们很漂亮,不是吗?提升<%= con %>点体质。魔法衣橱:独立装备。",
"back": "背部挂件",
"backBase0Text": "没有背部挂件",
"backBase0Notes": "没有背部挂件。",
@@ -815,11 +831,25 @@
"headAccessoryMystery201510Notes": "这些吓人的角滑溜溜的。没有属性加成。2015年10月捐赠者物品。",
"headAccessoryMystery301405Text": "头戴护目镜",
"headAccessoryMystery301405Notes": "“护目镜是戴在眼睛上的,”人们说。“没有人会想要一副只能戴在头上的护目镜。”人们说。哈!你果然让他们长见识了!没有增益效果。3015年8月捐赠者物品。",
- "headAccessoryArmoireComicalArrowText": "Comical Arrow",
- "headAccessoryArmoireComicalArrowNotes": "This whimsical item doesn't provide a stat boost, but it sure is good for a laugh! Confers no benefit. Enchanted Armoire: Independent Item.",
+ "headAccessoryArmoireComicalArrowText": "滑稽的箭",
+ "headAccessoryArmoireComicalArrowNotes": "这个古怪的东西不会增加属性,但它保证好笑!没有属性加成。魔法衣橱:独立装备。",
"eyewear": "眼镜",
"eyewearBase0Text": "没有眼镜",
"eyewearBase0Notes": "没有眼镜。",
+ "eyewearSpecialBlackTopFrameText": "黑色标准眼睛",
+ "eyewearSpecialBlackTopFrameNotes": "有着黑色眼镜框的眼睛。没有属性加成。",
+ "eyewearSpecialBlueTopFrameText": "蓝色标准眼睛",
+ "eyewearSpecialBlueTopFrameNotes": "有着蓝色眼镜框的眼睛。没有属性加成。",
+ "eyewearSpecialGreenTopFrameText": "绿色标准眼睛",
+ "eyewearSpecialGreenTopFrameNotes": "有着绿色眼镜框的眼睛。没有属性加成。",
+ "eyewearSpecialPinkTopFrameText": "粉色标准眼睛",
+ "eyewearSpecialPinkTopFrameNotes": "有着粉色眼镜框的眼睛。没有属性加成。",
+ "eyewearSpecialRedTopFrameText": "红色标准眼睛",
+ "eyewearSpecialRedTopFrameNotes": "有着红色眼镜框的眼睛。没有属性加成。",
+ "eyewearSpecialWhiteTopFrameText": "白色标准眼睛",
+ "eyewearSpecialWhiteTopFrameNotes": "有着白色眼镜框的眼睛。没有属性加成。",
+ "eyewearSpecialYellowTopFrameText": "黄色标准眼睛",
+ "eyewearSpecialYellowTopFrameNotes": "有着黄色眼镜框的眼睛。没有属性加成。",
"eyewearSpecialSummerRogueText": "流氓眼罩",
"eyewearSpecialSummerRogueNotes": "即使是无赖也能看出这个眼罩有多时髦!没有增益效果。限定版2014夏季装备。",
"eyewearSpecialSummerWarriorText": "时髦眼罩",
diff --git a/common/locales/zh/generic.json b/common/locales/zh/generic.json
index 4a438815b8..c1e25a0901 100644
--- a/common/locales/zh/generic.json
+++ b/common/locales/zh/generic.json
@@ -27,18 +27,18 @@
"collapseToolbar": "隐藏列表",
"markdownBlurb": "Habitica使用markdown标记语言作为信息传递格式。点击 Markdown Cheat Sheet 查看更多信息。",
"showFormattingHelp": "查看格式相关帮助",
- "hideFormattingHelp": "缩小格式相关帮助",
+ "hideFormattingHelp": "隐藏格式相关帮助",
"youType": "你输入了:",
"youSee": "你会看到:",
- "italics": "斜体",
- "bold": "粗体",
- "strikethrough": "~~继续前进~~",
- "emojiExample": ":笑:",
+ "italics": "*斜体*",
+ "bold": "**粗体**",
+ "strikethrough": "~~删除线~~",
+ "emojiExample": ":smile:",
"markdownLinkEx": "[Habitica 真好!](https://habitica.com)",
"markdownImageEx": "",
"unorderedListHTML": "+ 第一个道具 + 第二个道具 + 第三个道具",
"unorderedListMarkdown": "+ 第一个道具\n+ 第二个道具\n+ 第三个道具",
- "code": "代码",
+ "code": "`代码`",
"achievements": "成就",
"modalAchievement": "获得成就!",
"special": "特殊",
@@ -137,8 +137,8 @@
"achievementStressbeastText": "在2014冬季仙境事件中协助战胜可恶的压力野兽!",
"achievementBurnout": "全盛田野的救护者",
"achievementBurnoutText": "在2015秋季节事件中打败了疲惫魔,复原了锻炼精魂!",
- "achievementBewilder": "Savior of Mistiflying",
- "achievementBewilderText": "Helped defeat the Be-Wilder during the 2016 Spring Fling Event!",
+ "achievementBewilder": "Mistiflying城的拯救者",
+ "achievementBewilderText": "在2016年春季嘉年华事件中协助战胜迷失怪!",
"checkOutProgress": "看一下我在Habitica的进步!",
"cardReceived": "收到一个卡片!",
"cardReceivedFrom": "来自 <%= userName %> 的 <%= cardType %> ",
diff --git a/common/locales/zh/groups.json b/common/locales/zh/groups.json
index 2bcee8cb47..0f28f8abf2 100644
--- a/common/locales/zh/groups.json
+++ b/common/locales/zh/groups.json
@@ -92,6 +92,7 @@
"send": "发送",
"messageSentAlert": "已发送消息",
"pmHeading": "向<%= name %>发私信",
+ "pmsMarkedRead": "Your private messages have been marked as read",
"clearAll": "删除所有消息",
"confirmDeleteAllMessages": "确定要删除收件箱里的所有消息吗?其他用户依然会看到你已发送给他们的消息。",
"optOutPopover": "不喜欢私信?点击不再接收",
@@ -99,6 +100,15 @@
"unblock": "解锁",
"pm-reply": "发送回复",
"inbox": "收件箱",
+ "messageRequired": "请填写信息",
+ "toUserIDRequired": "需要用户ID",
+ "gemAmountRequired": "需要指明宝石数量",
+ "notAuthorizedToSendMessageToThisUser": "无法向该用户发送消息",
+ "privateMessageGiftIntro": "<%= receiverName %> 您好!<%= senderName %> 给了您",
+ "privateMessageGiftGemsMessage": "<%= gemAmount %> 宝石!",
+ "privateMessageGiftSubscriptionMessage": "<%= numberOfMonths %> 个月的捐助!",
+ "cannotSendGemsToYourself": "无法向自己发送宝石。使用捐助功能试试看",
+ "badAmountOfGemsToSend": "Amount must be within 1 and your current number of gems.",
"abuseFlag": "举报违反社区准则的用户",
"abuseFlagModalHeading": "举报 <%= name %> 违规",
"abuseFlagModalBody": "你确定要举报这个帖子?你 只能 举报违反了<%= firstLinkStart %>社区准则<%= linkEnd %>或者 <%= secondLinkStart %>服务条款<%= linkEnd %> 的帖子。不当的举报是违反社区准则的,并且你会因此而违规。适当的举报理由包括但不限于:
咒骂 、宗教性的起誓
偏见、诋毁
成人话题
暴力内容,即便是玩笑性质的
垃圾邮件,荒谬的消息
",
@@ -151,5 +161,29 @@
"partyUpName": "举行派对",
"partyOnName": "欢乐派对",
"partyUpAchievement": "和另一个人一起加入一个队伍!愉快的去打怪兽并互相支持,玩的愉快。",
- "partyOnAchievement": "加入一个队伍,至少4人!享受你肩上扛着的不断增加的对伙伴的承诺,和你的朋友一起打败你的敌人!"
+ "partyOnAchievement": "加入一个队伍,至少4人!享受你肩上扛着的不断增加的对伙伴的承诺,和你的朋友一起打败你的敌人!",
+ "groupIdRequired": "\"groupId\" must be a valid UUID",
+ "groupNotFound": "找不到小组",
+ "groupTypesRequired": "You must supply a valid \"type\" query string.",
+ "questLeaderCannotLeaveGroup": "You cannot leave your party when you have started a quest. Abort the quest first.",
+ "cannotLeaveWhileActiveQuest": "You cannot leave party during an active quest. Please leave the quest first.",
+ "onlyLeaderCanRemoveMember": "Only group leader can remove a member!",
+ "memberCannotRemoveYourself": "你不能移除自己!",
+ "groupMemberNotFound": "User not found among group's members",
+ "mustBeGroupMember": "Must be member of the group.",
+ "keepOrRemoveAll": "req.query.keep 必须是 \"keep-all\" 或者 \"remove-all\"",
+ "keepOrRemove": "req.query.keep must be either \"keep\" or \"remove\"",
+ "canOnlyInviteEmailUuid": "Can only invite using uuids or emails.",
+ "inviteMissingEmail": "邀请中缺失电子邮箱地址。",
+ "partyMustbePrivate": "队伍必须是私有的",
+ "userAlreadyInGroup": "用户已经在这个小组中。",
+ "userAlreadyInvitedToGroup": "用户已经被邀请进入这个小组。",
+ "userAlreadyPendingInvitation": "用户已经收到邀请等待接受。",
+ "userAlreadyInAParty": "用户已经在一个队伍中。",
+ "userWithIDNotFound": "这个ID \"<%= userId %>\"的用户没有找到。",
+ "userHasNoLocalRegistration": "User does not have a local registration (username, email, password).",
+ "uuidsMustBeAnArray": "User ID invites must be an array.",
+ "emailsMustBeAnArray": "Email address invites must be an array.",
+ "canOnlyInviteMaxInvites": "You can only invite \"<%= maxInvites %>\" at a time",
+ "onlyCreatorOrAdminCanDeleteChat": "Not authorized to delete this message!"
}
\ No newline at end of file
diff --git a/common/locales/zh/limited.json b/common/locales/zh/limited.json
index ace24d70f3..f31d87c1cf 100644
--- a/common/locales/zh/limited.json
+++ b/common/locales/zh/limited.json
@@ -5,7 +5,7 @@
"annoyingFriends": "吵闹的朋友",
"annoyingFriendsText": "被队员的雪球砸中<%= snowballs %>次。",
"alarmingFriends": "惊人的朋友",
- "alarmingFriendsText": "被队友吓到了 <%= spookDust %> 次",
+ "alarmingFriendsText": "受到<%= spookySparkles %>次来自队友的惊吓。",
"agriculturalFriends": "农民同志",
"agriculturalFriendsText": "被队友变成花朵<%= seeds %>次。",
"aquaticFriends": "水族朋友",
@@ -72,5 +72,6 @@
"comfortingKittySet": "抚慰小猫(医者)",
"sneakySqueakerSet": "霹雳鼠(盗贼)",
"fallEventAvailability": "有效期至十月31日",
- "winterEventAvailability": "有效期至十二月31日"
+ "winterEventAvailability": "有效期至十二月31日",
+ "springEventAvailability": "5月31日前可用"
}
\ No newline at end of file
diff --git a/common/locales/zh/maintenance.json b/common/locales/zh/maintenance.json
new file mode 100644
index 0000000000..19ba27aaaa
--- /dev/null
+++ b/common/locales/zh/maintenance.json
@@ -0,0 +1,34 @@
+{
+ "habiticaBackSoon": "不要担心。Habitica将会很快回来!",
+ "importantMaintenance": "我们正在进行重要维护,估计将持续到您时区中的<%= localDate %>。",
+ "maintenance": "维护",
+ "maintenanceMoreInfo": "想知道更多的维护信息?<%= linkStart %>查看我们的信息页面<%= linkEnd %>。",
+ "noDamageKeepStreaks": "您将不会受到伤害或失去连击数!",
+ "thanksForPatience": "谢谢您的耐心!",
+ "twitterMaintenanceUpdates": "对于最近的更新,查看我们的our Twitter,我们将在这里发布状态信息。",
+ "veteranPetAward": "最终,您将获得一只老兵宠物!",
+
+ "maintenanceInfoTitle": "关于即将来临的Habitica维护信息",
+ "maintenanceInfoWhat": "发生了什么?",
+ "maintenanceInfoWhatText": "在5月21日,Habitica将在1天中的大部分时间维护。你的账号将不会受到任何伤害在这个周末,虽然你不能及时登陆打卡你的每日任务!我们将会努力工作尽可能缩短维护时间,发布定期更新在我们的 Twitter账号。在维护结束,为感谢大家的耐心,你们所有人将收到一只稀有宠物!",
+ "maintenanceInfoWhy": "为什么这会发生?",
+ "maintenanceInfoWhyText": "再过去的几个月,我们已经在幕后彻底的改造了Habitica。特别地是,我们重写了API。它在表面看起来可能不会很不同,但它底层是一个完全不同的世界。这将允许我们的方式更灵活当我们想要去在以后构建特性,也导致了性能的提升!",
+ "maintenanceInfoTechDetails": "想要更多技术上的处理过程?访问 铁匠铺,我们的开发部落格。",
+ "maintenanceInfoMore": "更多信息",
+ "maintenanceInfoAccountChanges": "在这次改写完成后,我的账户将能看到什么样的改变?",
+ "maintenanceInfoAccountChangesText": "首先,不会有显著的变化除了为一些特性的性能上的提高,例如挑战。如果你注意到任何例外的变化,请通过邮件告知我们 admin@habitica.com,我们将会调查它们!",
+ "maintenanceInfoAddFeatures": "这将允许Habitica添加哪种特性?",
+ "maintenanceInfoAddFeaturesText": "完成这次重写将允许我们开始建立改进的聊天和工会,为家庭和组织的计划,和附加的产能特性例如每月的和记录昨天活动的能力!那些是他们自己所有涉及到的功能,所以会消耗时间建立它们,但是直到我们完成这次重写,在之前没有方式允许我们去开始这些。",
+ "maintenanceInfoHowLong": "维护持续多久?",
+ "maintenanceInfoHowLongText": "我们必须移动所有一百三十万Habitica用户的任务和数据 -- 这不是一个容易的任务!我们预期将发生在大约太平洋时间下午1点 (8pm UTC)到太平洋时间晚10点 (5am UTC)。请放心,我们会竭尽所能使维护更快!你能关注我们Twitter上的更新。",
+ "maintenanceInfoStatsAffected": "我的每日任务,连击数,增益魔法和探索任务将受到怎样的影响?",
+ "maintenanceInfoStatsAffectedText1": "你将 不会 在这个周末受到任何伤害或丢失任何连击数,但是另外的,你的天数会正常复位!你打卡的每日任务将会变成未打卡状态,增益魔法将重置,等等。如果你在一个采集任务中,你仍然会找到物品。如果你在BOSS战中,你仍然对BOSS造成伤害,但BOSS不能伤害你。(甚至怪物也需要打个小盹!)",
+ "maintenanceInfoStatsAffectedText2": "在思索了很多之后,我们团队得出在维护期间,由于很多用户将不能正常打卡每日任务,这是最公平的方式来处理。我们非常抱歉这带来的不便!",
+ "maintenanceInfoSeeTasks": "在当前状况下我是否需要去看我的任务列表?",
+ "maintenanceInfoSeeTasksText": "你是否知道你需要在周六检视你的任务列表来提醒自己要做什么,我们建议在维护开始前这样做,对你的任务截图以便于能用来做参考。",
+ "maintenanceInfoRarePet": "我将会得到哪种稀有宠物?",
+ "maintenanceInfoRarePetText": "为了感谢你们在维护期间耐心等待,每个人都将得到一只稀有老兵宠物。如果你之前从未得到一个老兵宠物,你将得到一直老兵狼。如果你已经有一只老兵狼,你将会得到一只老兵老虎。如果你两只都有,你将会得到一只之前从未见过的老兵宠物!在迁移完成后,会有几小时才显示你的宠物,但不要担心,每人都会得到一只。",
+ "maintenanceInfoWho": "谁工作于这个浩大的项目?",
+ "maintenanceInfoWhoText": "我们很高兴你提到了这个!它有我们厉害的贡献者团体带领,从Blade,TheHollidayInn,SabreCat,Victor Pudeyev,TheUnknown,和 Alys得到了很多帮助。",
+ "maintenanceInfoTesting": "新版本也会被我们厉害的开源志愿者不辞幸劳地测试。谢谢你们 -- 没有你们我们根本完成不了。"
+}
diff --git a/common/locales/zh/npc.json b/common/locales/zh/npc.json
index accec7fac6..53a96a1df0 100644
--- a/common/locales/zh/npc.json
+++ b/common/locales/zh/npc.json
@@ -21,6 +21,26 @@
"ian": "岚",
"ianText": "欢迎来到任务商店!这里你可以使用任务卷轴来同你的朋友一起与怪物战斗。不要选购一个在右边为您精心排列的任务卷轴吗?",
"ianBrokenText": "欢迎来到任务商店……这里你可以使用任务卷轴来同你的朋友一起与怪物战斗……请一定要到右边栏来看看我们为您精心准备的任务卷轴……",
+ "missingKeyParam": "需要\"req.params.key\" 。",
+ "itemNotFound": "找不到物品\"<%= key %>\" 。",
+ "cannotBuyItem": "你不能购买这个物品。",
+ "missingTypeKeyEquip": "\"key\"和\"type\"是必须的参数。",
+ "missingPetFoodFeed": "\"pet\"和\"food\"是必须的参数。",
+ "invalidPetName": "无效的宠物名提供。",
+ "missingEggHatchingPotionHatch": "\"egg\"和\"hatchingPotion\"是必须的参数。",
+ "invalidTypeEquip": "\"type\"必须是'equipped',,'pet', 'mount', 'costume'中的一个。",
+ "mustPurchaseToSet": "必须购买 <%= val %>来把它使用在<%= key %>上。",
+ "typeRequired": "需要Type",
+ "keyRequired": "需要Key",
+ "notAccteptedType": "Type必须在 [eggs, hatchingPotions, food, quests, gear] 中",
+ "contentKeyNotFound": "内容<%= type %>找不到Key",
+ "plusOneGem": "+1 宝石",
+ "typeNotSellable": "Type不出售。必须是<%= acceptedTypes %>其中之一。",
+ "userItemsKeyNotFound": "user.items <%= type %>找不到Key ",
+ "pathRequired": "需要Path string",
+ "unlocked": "物品已经解锁",
+ "alreadyUnlocked": "全套已经解锁",
+ "alreadyUnlockedPart": "全套已经部分解锁",
"USD": "(美元)",
"newStuff": "新品",
"cool": "稍后再跟我说",
@@ -64,6 +84,7 @@
"tourPetsPage": "This is the Stable! After level 4, you can hatch pets using eggs and potions. When you hatch a pet in the Market, it will appear here! Click a pet's image to add it to your avatar. Feed them with the food you find after level 4, and they'll grow into powerful mounts.",
"tourMountsPage": "Once you've fed a pet enough food to turn it into a mount, it will appear here. (Pets, mounts, and food are available after level 4.) Click a mount to saddle up!",
"tourEquipmentPage": "这里是你的装备库!你的战斗工具影响你的属性点。如果你想要在不影响属性点的情况下在你的角色形象上展示不同的装备,点击“使用服装”。",
+ "equipmentAlreadyOwned": "你已经拥有那件装备",
"tourOkay": "好的!",
"tourAwesome": "太好了!",
"tourSplendid": "太棒了!",
diff --git a/common/locales/zh/pets.json b/common/locales/zh/pets.json
index afbf38368c..5a72b09dbc 100644
--- a/common/locales/zh/pets.json
+++ b/common/locales/zh/pets.json
@@ -12,6 +12,7 @@
"etherealLion": "灵狮",
"veteranWolf": "退伍军狼",
"veteranTiger": "退伍老虎",
+ "veteranLion": "老兵狮子",
"cerberusPup": "地狱小狗",
"hydra": "三头蛇",
"mantisShrimp": "虾蛄",
@@ -19,7 +20,7 @@
"orca": "兽人",
"royalPurpleGryphon": "紫御狮鹫",
"phoenix": "凤凰",
- "bumblebee": "Bumblebee",
+ "magicalBee": "魔法蜜蜂",
"rarePetPop1": "按按金色的爪印查看怎么通过为Habitica贡献来获得这只稀有宠物!",
"rarePetPop2": "得到这个宠物的方法!",
"potion": "<%= potionType %> 药水",
@@ -62,6 +63,7 @@
"hatchedPet": "你孵化了 <%= potion %> <%= egg %>!",
"displayNow": "现在显示",
"displayLater": "稍后显示",
+ "petNotOwned": "你没有拥有这个宠物。",
"earnedCompanion": "因为你的不懈努力,你又获得了一个新的伙伴,好好喂养它,让它成长!",
"feedPet": "喂你的<%= name %>一个<%= article %><%= text %>?",
"useSaddle": "把鞍用在<%= pet %>上?",
@@ -83,5 +85,8 @@
"petKeyBoth": "全部释放",
"confirmPetKey": "你确定吗?",
"petKeyNeverMind": "还没有",
+ "petsReleased": "宠物发布。",
+ "mountsAndPetsReleased": "坐骑和宠物发布",
+ "mountsReleased": "坐骑发布",
"gemsEach": "每颗宝石"
}
\ No newline at end of file
diff --git a/common/locales/zh/quests.json b/common/locales/zh/quests.json
index 2947862629..1b61891dea 100644
--- a/common/locales/zh/quests.json
+++ b/common/locales/zh/quests.json
@@ -38,7 +38,7 @@
"bossDmg2": "只有参与者才能与boss战斗并共享任务掉落。",
"bossDmg1Broken": "每一个完成的每日任务和待办事项,或是每培养一次好习惯都能攻击到BOSS。完成深红色的任务或使用碎裂一击及火球术能给BOSS造成更多伤害。除了你自己的日常任务外,一旦队伍中的任何一人未完成自己的每日任务,你都会从BOSS那里受到额外的伤害值 (按BOSS的力量值成倍增加)! 所有对BOSS和来自BOSS的伤害会在重置时间进行结算 (你的日常重置时间)",
"bossDmg2Broken": "只有参与者才能跟boss战斗并共享任务掉落。",
- "tavernBossInfo": "Complete Dailies and To-Dos and score positive Habits to damage the World Boss! Incomplete Dailies fill the Rage Bar. When the Rage bar is full, the World Boss will attack an NPC. A World Boss will never damage individual players or accounts in any way. Only active accounts not resting in the Inn will have their tasks tallied.",
+ "tavernBossInfo": "通过完成每日任务、待办事项或点击好习惯来伤害世界Boss!未完成的每日任务会填充BOSS的怒气条。当它怒气条满的时候会攻击1个NPC。世界Boss不会攻击个人或账户。只有不在客栈的玩家才能做这个任务。",
"tavernBossInfoBroken": "通过完成每日任务、待办事项或记录好习惯来打败世界Boss!未完成的每日任务会填充BOSS的颓废值,当它颓废值满的时候会攻击1个NPC。世界Boss不会攻击个人或账户。另外,只有不在客栈的玩家才能做这个任务。",
"bossColl1": "为了收集物品,做那些正的任务。任务物品会像正常物品一样掉落;但是你直到第二天才能看得见这些掉落,然后找到的所有物品会被结算然后堆叠到一起。",
"bossColl2": "只有参与者才能收集物品并共享任务掉落。",
@@ -78,5 +78,24 @@
"whichQuestStart": "你想要开始哪个探索任务?",
"getMoreQuests": "获取更多探索任务",
"unlockedAQuest": "你解锁了一个任务!",
- "leveledUpReceivedQuest": "你达到了 第 <%= level %> 级 ,并且收到了一个任务卷轴!"
+ "leveledUpReceivedQuest": "你达到了 第 <%= level %> 级 ,并且收到了一个任务卷轴!",
+ "questInvitationDoesNotExist": "还没有发出任何探索任务邀请。",
+ "questInviteNotFound": "找不到探索任务邀请。",
+ "guildQuestsNotSupported": "工会不能被邀请参加探索任务。",
+ "questNotFound": "找不到探索任务\"<%= key %>\"。",
+ "questNotOwned": "你还没拥有那个探索任务卷轴。",
+ "questNotGoldPurchasable": "探索任务\"<%= key %>\"不能用金币购买。",
+ "questLevelTooHigh": "你必须达到等级<%= level %>来开始这个探索任务。",
+ "questAlreadyUnderway": "你的队伍已经开始一个探索任务。当前探索任务结束后请再试一次。",
+ "questAlreadyAccepted": "你已经接收探索任务邀请。",
+ "noActiveQuestToLeave": "没有正进行的探索任务来退出",
+ "questLeaderCannotLeaveQuest": "任务业主不能放弃任务",
+ "notPartOfQuest": "您不是任务的一部分",
+ "noActiveQuestToAbort": "There is no active quest to abort.",
+ "onlyLeaderAbortQuest": "Only the group or quest leader can abort a quest.",
+ "questAlreadyRejected": "您已经拒绝了任务邀请。",
+ "cantCancelActiveQuest": "You can not cancel an active quest, use the abort functionality.",
+ "onlyLeaderCancelQuest": "Only the group or quest leader can cancel the quest.",
+ "questNotPending": "There is no quest to start.",
+ "questOrGroupLeaderOnlyStartQuest": "Only the quest leader or group leader can force start the quest"
}
\ No newline at end of file
diff --git a/common/locales/zh/questscontent.json b/common/locales/zh/questscontent.json
index 9813fe06ab..6fd06f039a 100644
--- a/common/locales/zh/questscontent.json
+++ b/common/locales/zh/questscontent.json
@@ -298,15 +298,27 @@
"questSnailBoss": "苦差事淤泥蜗牛",
"questSnailDropSnailEgg": "蜗牛(蛋)",
"questSnailUnlockText": "解锁蜗牛蛋购买功能",
- "questBewilderText": "The Be-Wilder",
- "questBewilderNotes": "The party begins like any other.
The appetizers are excellent, the music is swinging, and even the dancing elephants have become routine. Habiticans laugh and frolic amid the overflowing floral centerpieces, happy to have a distraction from their least-favorite tasks, and the April Fool whirls among them, eagerly providing an amusing trick here and a witty twist there.
As the Mistiflying clock tower strikes midnight, the April Fool leaps onto the stage to give a speech.
“Friends! Enemies! Tolerant acquaintances! Lend me your ears.” The crowd chuckles as animal ears sprout from their heads, and they pose with their new accessories.
“As you know,” the Fool continues, “my confusing illusions usually only last a single day. But I’m pleased to announce that I’ve discovered a shortcut that will guarantee us non-stop fun, without having to deal with the pesky weight of our responsibilities. Charming Habiticans, meet my magical new friend... the Be-Wilder!”
Lemoness pales suddenly, dropping her hors d'oeuvres. “Wait! Don’t trust--”
But suddenly mists are pouring into the room, glittering and thick, and they swirl around the April Fool, coalescing into cloudy feathers and a stretching neck. The crowd is speechless as an monstrous bird unfolds before them, its wings shimmering with illusions. It lets out a horrible screeching laugh.
“Oh, it has been ages since a Habitican has been foolish enough to summon me! How wonderful it feels, to have a tangible form at last.”
Buzzing in terror, the magic bees of Mistiflying flee the floating city, which sags from the sky. One by one, the brilliant spring flowers wither up and wisp away.
“My dearest friends, why so alarmed?” crows the Be-Wilder, beating its wings. “There’s no need to toil for your rewards any more. I’ll just give you all the things that you desire!”
A rain of coins pours from the sky, hammering into the ground with brutal force, and the crowd screams and flees for cover. “Is this a joke?” Baconsaur shouts, as the gold smashes through windows and shatters roof shingles.
PainterProphet ducks as lightning bolts crackle overhead, and fog blots out the sun. “No! This time, I don’t think it is!”
Quickly, Habiticans, don’t let this World Boss distract us from our goals! Stay focused on the tasks that you need to complete so we can rescue Mistiflying -- and hopefully, ourselves.",
- "questBewilderCompletion": "The Be-Wilder is DEFEATED!
We've done it! The Be-Wilder lets out a ululating cry as it twists in the air, shedding feathers like falling rain. Slowly, gradually, it coils into a cloud of sparkling mist. As the newly-revealed sun pierces the fog, it burns away, revealing the coughing, mercifully human forms of Bailey, Matt, Alex.... and the April Fool himself.
Mistiflying is saved!
The April Fool has enough shame to look a bit sheepish. “Oh, hm,” he says. “Perhaps I got a little…. carried away.”
The crowd mutters. Sodden flowers wash up on sidewalks. Somewhere in the distance, a roof collapses with a spectacular splash.
“Er, yes,” the April Fool says. “That is. What I meant to say was, I’m dreadfully sorry.” He heaves a sigh. “I suppose it can’t all be fun and games, after all. It might not hurt to focus occasionally. Maybe I’ll get a head start on next year’s pranking.”
Redphoenix coughs meaningfully.
“I mean, get a head start on this year’s spring cleaning!” the April Fool says. “Nothing to fear, I’ll have Habit City in spit-shape soon. Luckily nobody is better than I at dual-wielding mops.”
Encouraged, the marching band starts up.
It isn’t long before all is back to normal in Habit City. Plus, now that the Be-Wilder has evaporated, the magical bees of Mistiflying bustle back to work, and soon the flowers are blooming and the city is floating once more.
As Habiticans cuddle the magical fuzzy bees, the April Fool’s eyes light up. “Oho, I’ve had a thought! Why don’t you all keep some of these fuzzy Bee Pets and Mounts? It’s a gift that perfectly symbolizes the balance between hard work and sweet rewards, if I’m going to get all boring and allegorical on you.” He winks. “Besides, they don’t have stingers! Fool’s honor.”",
- "questBewilderCompletionChat": "`The Be-Wilder is DEFEATED!`\n\nWe've done it! The Be-Wilder lets out a ululating cry as it twists in the air, shedding feathers like falling rain. Slowly, gradually, it coils into a cloud of sparkling mist. As the newly-revealed sun pierces the fog, it burns away, revealing the coughing, mercifully human forms of Bailey, Matt, Alex.... and the April Fool himself.\n\n`Mistiflying is saved!`\n\nThe April Fool has enough shame to look a bit sheepish. “Oh, hm,” he says. “Perhaps I got a little…. carried away.”\n\nThe crowd mutters. Sodden flowers wash up on sidewalks. Somewhere in the distance, a roof collapses with a spectacular splash.\n\n“Er, yes,” the April Fool says. “That is. What I meant to say was, I’m dreadfully sorry.” He heaves a sigh. “I suppose it can’t all be fun and games, after all. It might not hurt to focus occasionally. Maybe I’ll get a head start on next year’s pranking.”\n\nRedphoenix coughs meaningfully.\n\n“I mean, get a head start on this year’s spring cleaning!” the April Fool says. “Nothing to fear, I’ll have Habit City in spit-shape soon. Luckily nobody is better than I at dual-wielding mops.”\n\nEncouraged, the marching band starts up.\n\nIt isn’t long before all is back to normal in Habit City. Plus, now that the Be-Wilder has evaporated, the magical bees of Mistiflying bustle back to work, and soon the flowers are blooming and the city is floating once more.\n\nAs Habiticans cuddle the magical fuzzy bees, the April Fool’s eyes light up. “Oho, I’ve had a thought! Why don’t you all keep some of these fuzzy Bee Pets and Mounts? It’s a gift that perfectly symbolizes the balance between hard work and sweet rewards, if I’m going to get all boring and allegorical on you.” He winks. “Besides, they don’t have stingers! Fool’s honor.”",
- "questBewilderBossRageTitle": "Beguilement Strike",
- "questBewilderBossRageDescription": "When this gauge fills, The Be-Wilder will unleash its Beguilement Strike on Habitica!",
- "questBewilderDropBumblebeePet": "Bumblebee (Pet)",
- "questBewilderDropBumblebeeMount": "Bumblebee (Mount)",
- "questBewilderBossRageMarket": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nOh no! Despite our best efforts, we've gotten distracted by the Be-Wilder’s charming illusions and have forgotten to do some of our Dailies! With a cackling cry, the shining bird beats its wings, raising a swarm of mist around Alex the Merchant. When the fog clears, he has been possessed! “Have some free samples!” he shouts gleefully, and begins to hurl exploding eggs and potions at fleeing Habiticans. Not the most favorable of sales, to be sure.\n\nHurry! Let's stay focused on our Dailies to defeat this monster before it possesses someone else.",
- "questBewilderBossRageStables": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nAhh!!! Once again the Be-Wilder has dazzled us into neglecting our Dailies, and now it has attacked Matt the Beast Master! With a swirl of mist, Matt transforms into a terrifying winged creature, and all the pets and mounts howl sadly in their stables. Quickly, stay focused on your tasks to defeat this dastardly distraction!",
- "questBewilderBossRageBailey": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nLook out! In the middle of reporting the news, Bailey the Town Crier has been possessed by the Be-Wilder! She lets out an evil, uninformative screech as she rises into the air. Now how will we know what’s going on?\n\nDon't give up... we're so close to defeating this bothersome bird for once and for all!"
+ "questBewilderText": "迷失怪",
+ "questBewilderNotes": "派对开始和其他一样。
@Squish接着说道,“.嘿,拿上这些我找到的蛋作为你的奖励。”",
+ "questFalconBoss": "掠食明天之鸟",
+ "questFalconDropFalconEgg": "猎鹰(宠物蛋)",
+ "questFalconUnlockText": "解锁猎鹰蛋购买功能",
+ "questTreelingText": "The Tangle Tree",
+ "questTreelingNotes": "It's the annual Garden Competition, and everyone is talking about the mysterious project which @aurakami has promised to unveil. You join the crowd on the day of the big announcement, and marvel at the introduction of a moving tree. @fuzzytrees explains that the tree will help with garden maintenance, showing how it can mow the lawn, trim the hedge and prune the roses all at the same time – until the tree suddenly goes wild, turning its secateurs on its creator! The crowd panics as everyone tries to flee, but you aren't afraid – you leap forward, ready to do battle.",
+ "questTreelingCompletion": "You dust yourself off as the last few leaves drift to the floor. In spite of the upset, the Garden Competition is now safe – although the tree you just reduced to a heap of wood chips won't be winning any prizes! \"Still a few kinks to work out there,\" @PainterProphet says. \"Perhaps someone else would do a better job of training the saplings. Do you fancy a go?\"",
+ "questTreelingBoss": "Tangle Tree",
+ "questTreelingDropTreelingEgg": "Treeling (Egg)",
+ "questTreelingUnlockText": "Unlocks purchasable Treeling eggs in the Market"
}
\ No newline at end of file
diff --git a/common/locales/zh/rebirth.json b/common/locales/zh/rebirth.json
index 816e48a6b7..5f853e107f 100644
--- a/common/locales/zh/rebirth.json
+++ b/common/locales/zh/rebirth.json
@@ -5,7 +5,7 @@
"rebirthStartOver": "复活后你的角色从1级开始。",
"rebirthAdvList1": "生命值完全复原",
"rebirthAdvList2": "你没有经验,金币,装备(免费物品例外如神秘物品)。",
- "rebirthAdvList3": "你的习惯,每日任务,待办事项会被重置为黄色,连击数也会被重置。",
+ "rebirthAdvList3": "你的习惯,每日任务,和待办事项重置到黄色,并且连击数也重置,除了挑战任务。",
"rebirthAdvList4": "你的初始职业是战士,但你以后可以解锁新的职业。",
"rebirthInherit": "你的新角色继承了一些前身的东西:",
"rebirthInList1": "任务,历史记录和设置保留了下来。",
@@ -24,5 +24,6 @@
"rebirthPop": "开始一个1级的新角色并保留成就,物品和任务历史。",
"rebirthName": "重生球",
"reborn": "重生, 最高级别 <%= reLevel %>",
- "confirmReborn": "你确定吗?"
+ "confirmReborn": "你确定吗?",
+ "rebirthComplete": "你已经重生了!"
}
\ No newline at end of file
diff --git a/common/locales/zh/settings.json b/common/locales/zh/settings.json
index fd7becbb80..d8c734165c 100644
--- a/common/locales/zh/settings.json
+++ b/common/locales/zh/settings.json
@@ -47,6 +47,7 @@
"customDayStart": "自定义起始日期",
"changeCustomDayStart": "变更自定义起始日期?",
"sureChangeCustomDayStart": "你确定要变更自定义起始日期吗?",
+ "customDayStartHasChanged": "你自定义的每日开始时间已经改变。",
"nextCron": "从你第一次使用Habitica<%= time %>之后,你的每日任务将会重置。请确保你在这之前已经完成了所有每日任务!",
"customDayStartInfo1": "在你所处地区时间每天凌晨12点,Habitica默认核对并重置你的每日任务。你可以在此变更任务重置时间。",
"misc": "其他",
@@ -61,12 +62,23 @@
"newUsername": "新的登录名",
"dangerZone": "危险区域",
"resetText1": "警告!这会重置你角色的许多数值。强烈不建议你这样做。不过,在短暂的试玩一段时间后,进行重置或许会有所帮助。",
- "resetText2": "你会失去你所有的等级,金币和经验。你所有的任务会被永久删除,你会失去你所有的任务历史数据。你会失去所有的装备,但是你能够把他们买回来。包括你曾经拥有过的所有限量版装备和订阅者的神秘物品(你需要编程合适的职业才能重新购买职业限定的装备)。你会保留你的当前职业和你的宠物与坐骑。相比之下,用道具重生球会更安全,你的任务会被保留下来。",
+ "resetText2": "你将会失去你所有等级,金币和经验。你的所有任务(除了那些来自挑战的任务)将会被永久删除并且你将失去所有它们的历史数据。你将会丢失所有的装备但你能够再次购买回来,包括所有你已拥有的限定版装备或捐赠者神秘物品(你将需要是恰当的职业才能重新购买职业限定装备)。你将会保留你当前职业和你的宠物和坐骑。你可能更愿意去使用 重生球 来替代,这是一个更安全的选择,它能保存你所有的任务。",
"deleteText": "你确定吗?这会永久地删除你的帐号,并且永远也无法恢复!如果希望再次使用Habitica需要注册一个新的帐号。已有的金钱和花掉的宝石无法被退费。如果你非常确定,在下面的文本框中输入<%= deleteWord %>。",
"API": "API / 应用程序接口",
+ "APIv3": "API v3",
+ "APIv2": "API v2 - 弃用",
"APIText": "这个是用来复制到第三方应用的。但是,你的API令牌相当于密码,不要公开它。有时候别人会问你要用户ID,但是永远不要把你的API令牌分享给其他人,包括Github。",
"APIToken": "API 令牌 (这相当于密码——看上面的警告!)",
+ "thirdPartyApps": "第三方应用",
+ "dataToolDesc": "一个网页,这个网页展示你的Habitica账户的特定信息,例如你的任务、装备和技能的统计信息。",
+ "beeminder": "蜜蜂看守",
+ "beeminderDesc": "让蜜蜂看守自动地监视你的Habitica待办事项。你能交付来维持一个目标数量的待办事项每周或每天被完成,或者你能交付来渐渐减少维持的未完成待办事项数量。(通过“交付”蜜蜂看守意味着有支付真实金钱的风险!但你也可能仅喜欢蜜蜂看守的华丽的图形)。",
+ "chromeChatExtension": "Chrome聊天扩展",
+ "chromeChatExtensionDesc": "Habitica的Chrome聊天扩展增加了一个直观的聊天窗口。它让用户能在它们所属的旅馆、队伍和工会中闲聊。",
+ "otherExtensions": "其他扩展",
+ "otherDesc": "其他应用、扩展和工具请关注Habitica维基。",
"resetDo": "去吧,重置我的账号吧!",
+ "resetComplete": "重置完成!",
"fixValues": "修复数值",
"fixValuesText1": "如果你遇到了一个bug或者错误地改变了你的角色 (不应该受到的伤害,获得了不属于你的金币等等),你可以手动的改正你的数值。是的,这使得用户有能力作弊:请正确地使用这个功能,否则你会破坏你自己的习惯养成系统!",
"fixValuesText2": "注意你不可以在这里重置单个任务的连击数。如果希望那样做,编辑日常标签,进入高级选项,在那里你可以修复连击数。",
@@ -96,6 +108,7 @@
"emailNotifications": "电子邮件通知",
"wonChallenge": "你赢得了一项挑战!",
"newPM": "收到悄悄话",
+ "sentGems": "发送宝石!",
"giftedGems": "自然宝石",
"giftedGemsInfo": "<%= amount %> 宝石 - 来自 <%= name %>",
"giftedSubscription": "捐助有礼",
@@ -135,6 +148,11 @@
"webhooks": "Webhooks",
"enabled": "已启用",
"webhookURL": "Webhook链接",
+ "invalidUrl": "无效的url",
+ "invalidEnabled": "\"enabled\"参数应该为一个布尔值",
+ "regIdRequired": "需要RegId",
+ "pushDeviceAdded": "成功添加推送设备",
+ "pushDeviceAlreadyAdded": "用户已经有推送设备了",
"add": "添加",
"buyGemsGoldCap": "上限提升至 <%= amount %>",
"mysticHourglass": "<%= amount %> 个神秘沙漏",
@@ -149,5 +167,5 @@
"amazonPayments": "亚马逊支付",
"timezone": "时区",
"timezoneUTC": "Habitica直接引用你电脑上的时区设置:<%= utc %>",
- "timezoneInfo": "如果这个时区显示错误,首先让你的网页浏览器刷新页面,保证Habitica有最新的信息。如果依旧错误,在你的电脑上调节时区设置,并再次刷新页面。
如果你在其他电脑或移动设备上使用Habitica,它们上的时区必须是一样的才行。如果你的每日任务已经在错误的时间被重置,在你所有其他的电脑和你移动设备的一个浏览器上重复这个检查。"
}
\ No newline at end of file
diff --git a/common/locales/zh/spells.json b/common/locales/zh/spells.json
index e9cd79fc86..cd9382e948 100644
--- a/common/locales/zh/spells.json
+++ b/common/locales/zh/spells.json
@@ -1,50 +1,56 @@
{
"spellWizardFireballText": "火焰爆轰",
- "spellWizardFireballNotes": "你的手中释放出强大的烈焰。你得到经验值,并且对怪物造成额外伤害!点击一个目标以使用 (法术强度与智力值有关)",
+ "spellWizardFireballNotes": "火焰在你手中迸发。你将获得经验值,并且对怪物们造成额外伤害!点击一项任务来释放(法术强度与智力值有关)",
"spellWizardMPHealText": "澎湃灵泉",
"spellWizardMPHealNotes": "你消耗魔法值来帮助你的队友。你队伍中的其他成员获得魔法值!(法术强度与智力值有关)",
"spellWizardEarthText": "地震",
- "spellWizardEarthNotes": "你的精神力量晃动着大地。你的整个队伍获得智力上的增益!(法术强度与未增益的智力有关)",
+ "spellWizardEarthNotes": "你的精神力量撼动了大地。所有队员获得智力上的增益!(法术强度与未增益的智力值有关)",
"spellWizardFrostText": "极寒霜冻",
- "spellWizardFrostNotes": "冰霜冻结了你的任务。你的任务连击数在明天不会重置!(一次施放影响所有连击数)",
+ "spellWizardFrostNotes": "冰霜冻结了你的任务。你所有任务的连击数不会在今天之后归零!(释放一次即对所有任务生效)",
"spellWarriorSmashText": "致命一击",
- "spellWarriorSmashNotes": "你全部力量都集中于一个任务。该任务获得更多蓝色值、更少红色值,而且你能对王造成额外伤害!点击一个任务来施放法术。(法术强度与力量值有关)",
+ "spellWarriorSmashNotes": "你全力击中了一项任务。该任务的蓝色值将更深/红色值将更浅,与此同时你对怪物们造成额外伤害!点击一项任务来释放法术。(法术强度与力量值有关)",
"spellWarriorDefensiveStanceText": "防御姿态",
- "spellWarriorDefensiveStanceNotes": "你准备对你的任务发起一次猛攻。你获得体质上的增益!(法术强度与未增益的体质值有关)",
+ "spellWarriorDefensiveStanceNotes": "你准备好接受来自自己任务的猛攻了。你获得体质上的增益!(法术强度与未增益的体质值有关)",
"spellWarriorValorousPresenceText": "悍勇现身",
- "spellWarriorValorousPresenceNotes": "你的出现鼓舞了你的队伍。你的整个队伍获得了力量上的增益!(法术强度与未增益的力量值有关)",
+ "spellWarriorValorousPresenceNotes": "你的出现鼓舞了你的队伍。所有队员获得了力量上的增益!(法术强度与未增益的力量值有关)",
"spellWarriorIntimidateText": "威慑凝视",
- "spellWarriorIntimidateNotes": "你的凝视将恐惧钉入敌人内心。你的整个队伍获得体质上的增益!(法术强度与未增益的体质值有关)",
+ "spellWarriorIntimidateNotes": "你的凝视使敌人吓破了胆。你的整个队伍获得体质上的增益!(法术强度与未增益的体质值有关)",
"spellRoguePickPocketText": "飞龙探云手",
- "spellRoguePickPocketNotes": "你窃取了一个附近的任务的财富。你获得了一些金币!点击一个任务来施放法术。(法术强度与感知值有关)",
+ "spellRoguePickPocketNotes": "你偷走了附近一个任务的财富。你获得了一些金币!点击一项任务来释放。(法术强度与感知值有关)",
"spellRogueBackStabText": "背刺",
- "spellRogueBackStabNotes": "你背叛了一个愚蠢的任务。你获得了一些金币与经验!点击一个任务来施放法术。(法术强度与力量值有关)",
- "spellRogueToolsOfTradeText": "职业用具",
- "spellRogueToolsOfTradeNotes": "你与你的队友分享了你的天赋。你的整个队伍获得了感知上的增益!(法术强度与未增益的感知值有关)",
+ "spellRogueBackStabNotes": "你背叛了一个愚蠢的任务。你获得了一些金币与经验值!点击一项任务来释放。(法术强度与力量值有关)",
+ "spellRogueToolsOfTradeText": "行业工具",
+ "spellRogueToolsOfTradeNotes": "你与朋友们分享了你的天赋。所有队员获得了感知上的增益!(法术强度与未增益的感知值有关)",
"spellRogueStealthText": "潜行",
- "spellRogueStealthNotes": "你是如此鬼祟以至于难以被找出。一些你的未完成的日常任务在今晚不会对你造成伤害,而且它们的连击数和颜色不会发生改变。(需要多次施放以对多个日常任务生效)",
- "spellHealerHealText": "圣光术",
- "spellHealerHealNotes": "圣光笼罩着你,治愈了你的伤口。你获得了生命值!(法术强度与体质值以及智力值有关)",
- "spellHealerBrightnessText": "灼热光矢",
- "spellHealerBrightnessNotes": "你爆发出一道光闪瞎了你的任务。你的任务的蓝色值增加、红色值降低了。(法术强度与智力值有关)",
- "spellHealerProtectAuraText": "守护光环",
+ "spellRogueStealthNotes": "你隐秘地在黑夜中行进。今晚,你未完成的一些日常任务将不会对你造成伤害,它们的连击数和颜色也保持不变。(需要多次施放以对多个日常任务生效)",
+ "spellHealerHealText": "治愈之光",
+ "spellHealerHealNotes": "圣光笼罩着你,治愈了你的伤口。你的生命值恢复了!(法术强度与体质值以及智力值有关)",
+ "spellHealerBrightnessText": "目眩神迷",
+ "spellHealerBrightnessNotes": "你爆发出一道光闪瞎了你的任务。所有任务的蓝色值增加、红色值降低。(法术强度与智力值有关)",
+ "spellHealerProtectAuraText": "灵光护佑",
"spellHealerProtectAuraNotes": "你保护你的队伍免受伤害。你的整个队伍获得体质上的增益!(法术强度与未增益的体质值有关)",
"spellHealerHealAllText": "祝福",
- "spellHealerHealAllNotes": "抚慰之光围绕着你。你的整个队伍恢复生命值!(法术强度与体质值以及智力值有关)",
+ "spellHealerHealAllNotes": "治愈的光环围绕着你。所有队员恢复生命值!(法术强度与体质值以及智力值有关)",
"spellSpecialSnowballAuraText": "雪球",
"spellSpecialSnowballAuraNotes": "向队友丢了一个雪球!会发生什么事情呢?所产生的效果会持续到第二天早上.",
"spellSpecialSaltText": "盐",
"spellSpecialSaltNotes": "有人向你扔了雪球。哈哈,很好玩。现在把这些雪从我身上弄下来!",
- "spellSpecialSpookDustText": "鬼火",
- "spellSpecialSpookDustNotes": "把一个好友变成长眼睛的魔毯。",
+ "spellSpecialSpookySparklesText": "幽灵般的闪光",
+ "spellSpecialSpookySparklesNotes": "把一个好友变成长眼睛的魔毯!",
"spellSpecialOpaquePotionText": "晦暗的药水",
"spellSpecialOpaquePotionNotes": "取消鬼火的效果",
"spellSpecialShinySeedText": "闪光种子",
"spellSpecialShinySeedNotes": "把一个好友变成一朵笑笑花。",
"spellSpecialPetalFreePotionText": "除花剂",
"spellSpecialPetalFreePotionNotes": "解除闪光种子效果。",
- "spellSpecialSeafoamText": "浪花",
- "spellSpecialSeafoamNotes": "把好友变成海洋生物",
+ "spellSpecialSeafoamText": "海泡石",
+ "spellSpecialSeafoamNotes": "把一位好友变成海洋生物",
"spellSpecialSandText": "沙子",
- "spellSpecialSandNotes": "取消浪花效果"
+ "spellSpecialSandNotes": "抵消海泡石的效果",
+ "spellNotFound": "找不到技能\"<%= spellId %>\"。",
+ "partyNotFound": "找不到队伍",
+ "targetIdUUID": "\"targetId\" 必须是有效ID。",
+ "challengeTasksNoCast": "不允许在挑战任务上使用技能。",
+ "spellNotOwned": "你没有拥有这个技能。",
+ "spellLevelTooHigh": "你必须达到等级<%= level %>来运用这个技能。"
}
\ No newline at end of file
diff --git a/common/locales/zh/subscriber.json b/common/locales/zh/subscriber.json
index e1f3aa5cfa..736a16e65e 100644
--- a/common/locales/zh/subscriber.json
+++ b/common/locales/zh/subscriber.json
@@ -4,6 +4,8 @@
"subDescription": "可以使用金币购买宝石,获得每月神秘装备,保留已完成项目的历史,双倍日常掉落率,支持开发者。点击获取更多信息。",
"buyGemsGold": "用金币购买宝石",
"buyGemsGoldText": "商人亚历山大可以按每个宝石 <%= gemCost %>金币的价格向你出售宝石,他每个月的初始进货量是 <%= gemLimit %> 个宝石,如果可以在接下来的时间里坚持捐助,每三个月宝石上限就会增加5,直到最大值1个月50颗宝石!",
+ "mustSubscribeToPurchaseGems": "必须捐赠来用GP购买宝石",
+ "reachedGoldToGemCap": "你已经达到了本月的 金币=>宝石 上限<%= convCap %>。我们用这个来阻止 滥用/买卖。这个上限将在每个月前三天重置。",
"retainHistory": "保留额外的历史条目",
"retainHistoryText": "使完成的待办事项和任务的历史纪录更长时间保留。",
"doubleDrops": "日常掉率上限翻倍",
@@ -29,6 +31,7 @@
"manageSub": "按这里管理捐助",
"cancelSub": "取消捐助",
"canceledSubscription": "取消捐助",
+ "cancelingSubscription": "取消捐助",
"adminSub": "管理员捐助",
"morePlans": "更多计划 即将推出",
"organizationSub": "私人组织",
@@ -73,6 +76,9 @@
"timeTravelersPopover": "我们看到你有1个神秘的沙漏,所以我们愉快地决定穿梭回来找你!请选择你喜欢的宠物,坐骑或是神秘物品。你还可以看到一系列已不再开放的物品列表在 这里!如果这些都不能满足你,也许你会对我们富有时尚未来感的蒸汽朋克物品套装感兴趣?",
"timeTravelersAlreadyOwned": "恭喜!你已经拥有了时光穿梭者目前能够提供的所有道具,感谢支持本站!",
"mysticHourglassPopover": "神秘沙漏可以让你购买一些特别的限定道具,比如每月神秘道具组合,世界BOSS甚至过去已经发布完的奖励!",
+ "mysterySetNotFound": "神秘套装找不到,或者已经拥有套装。",
+ "mysteryItemIsEmpty": "神秘物品是空的",
+ "mysteryItemOpened": "神秘物品打开。",
"mysterySet201402": "有翼使者长袍套装",
"mysterySet201403": "森林行者套装",
"mysterySet201404": "薄暮蝴蝶套装",
@@ -99,6 +105,8 @@
"mysterySet201601": "决心组冠军",
"mysterySet201602": "负心人套装",
"mysterySet201603": "幸运草套装",
+ "mysterySet201604": "Leaf Warrior Set",
+ "mysterySet201605": "Marching Bard Set",
"mysterySet301404": "蒸汽朋克标准套装",
"mysterySet301405": "蒸汽朋克配饰套装",
"mysterySetwondercon": "Wondercon漫展",
@@ -110,9 +118,25 @@
"hourglassBuyItemConfirm": "花费1个神秘沙漏购买这个道具?",
"petsAlreadyOwned": "你已经拥有这只宠物",
"mountsAlreadyOwned": "你已经拥有这只坐骑",
- "typeNotAllowedHourglass": "本道具无法使用神秘沙漏购买,可购买种类有:",
+ "typeNotAllowedHourglass": "Item type not supported for purchase with Mystic Hourglass. Allowed types: <%= allowedTypes %>",
"petsNotAllowedHourglass": "本宠物无法使用神秘沙漏购买,可购买种类有:",
"mountsNotAllowedHourglass": "本坐骑无法使用神秘沙漏购买,可购买种类有:",
"hourglassPurchase": "你使用了一个神秘沙漏购买了一件道具!",
- "hourglassPurchaseSet": "你使用了一个神秘沙漏购买了一套道具组合!"
+ "hourglassPurchaseSet": "你使用了一个神秘沙漏购买了一套道具组合!",
+ "missingUnsubscriptionCode": "Missing unsubscription code.",
+ "missingSubscription": "User does not have a plan subscription",
+ "missingSubscriptionCode": "Missing subscription code. Possible values: basic_earned, basic_3mo, basic_6mo, google_6mo, basic_12mo.",
+ "cannotDeleteActiveAccount": "You have an active subscription, cancel your plan before deleting your account.",
+ "paymentNotSuccessful": "支付失败",
+ "planNotActive": "The plan hasn't activated yet (due to a PayPal bug). It will begin <%= nextBillingDate %>, after which you can cancel to retain your full benefits",
+ "notAllowedHourglass": "Pet/Mount not available for purchase with Mystic Hourglass.",
+ "readCard": "<%= cardType %> has been read",
+ "cardTypeRequired": "Card type required",
+ "cardTypeNotAllowed": "Unknown card type.",
+ "invalidCoupon": "无效的优惠劵代码",
+ "couponUsed": "优惠劵已被使用",
+ "noSudoAccess": "您没有sudo权限",
+ "couponCodeRequired": "请填写优惠劵代码",
+ "eventRequired": "需要 \"req.params.event\"",
+ "countRequired": "需要 \"req.query.count\""
}
\ No newline at end of file
diff --git a/common/locales/zh/tasks.json b/common/locales/zh/tasks.json
index 8f08b0ce84..25fda3751f 100644
--- a/common/locales/zh/tasks.json
+++ b/common/locales/zh/tasks.json
@@ -73,6 +73,7 @@
"clearTags": "清除",
"hideTags": "隐藏",
"showTags": "显示",
+ "toRequired": "You must supply a to value",
"startDate": "开始日期",
"startDateHelpTitle": "这个 任务 什么时候开始",
"startDateHelp": "设置任务生效的日期。在这之前将不会到期。",
@@ -88,8 +89,9 @@
"fortifyName": "稳固药剂",
"fortifyPop": "将所有任务恢复至中性状态(黄色),并补充所有损失的生命值。",
"fortify": "稳固",
- "fortifyText": "稳固作用会将所有的任务重置到初始(黄色)状态,就像刚刚添加时一样,还会回满你的生命值。如果你变红的任务太多,让你感到游戏太难进行,而蓝色的任务又太容易,这会非常有帮助。如果觉得重获新生非常激励人,那就花几块宝石来给自己缓解一下吧!",
+ "fortifyText": "Fortify will return all your tasks, except challenge tasks, to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!",
"confirmFortify": "你确定吗?",
+ "fortifyComplete": "Fortify complete!",
"sureDelete": "你确定要删除这些 <%= taskType %>文本\"<%= taskText %>\"吗?",
"streakCoins": "连击奖励!",
"pushTaskToTop": "将任务置于顶部.按住ctrl或者cmd可拖至底部。",
@@ -112,5 +114,18 @@
"rewardHelp2": "装备能够影响你的属性点数 (<%= linkStart %>头像 > 属性点<%= linkEnd %>)。",
"rewardHelp3": "特殊装备会在世界性事件中出现。",
"rewardHelp4": "别害怕,大胆地为自己设置自定义奖励!来这里看看 一些简单例子。",
- "clickForHelp": "点击获取帮助"
+ "clickForHelp": "点击获取帮助",
+ "taskIdRequired": "\"taskId\" must be a valid UUID.",
+ "taskNotFound": "找不到任务。",
+ "invalidTaskType": "Task type must be one of \"habit\", \"daily\", \"todo\", \"reward\".",
+ "cantDeleteChallengeTasks": "A task belonging to a challenge can't be deleted.",
+ "checklistOnlyDailyTodo": "Checklists are supported only on dailies and todos",
+ "checklistItemNotFound": "No checklist item was found with given id.",
+ "itemIdRequired": "\"itemId\" must be a valid UUID.",
+ "tagNotFound": "No tag item was found with given id.",
+ "tagIdRequired": "\"tagId\" must be a valid UUID corresponding to a tag belonging to the user.",
+ "positionRequired": "\"position\" is required and must be a number.",
+ "cantMoveCompletedTodo": "Can't move a completed todo.",
+ "directionUpDown": "\"direction\" is required and must be 'up' or 'down'",
+ "alreadyTagged": "The task is already tagged with given tag."
}
\ No newline at end of file
diff --git a/common/locales/zh_TW/backgrounds.json b/common/locales/zh_TW/backgrounds.json
index 4c4b09de6d..45f6ea4cb1 100644
--- a/common/locales/zh_TW/backgrounds.json
+++ b/common/locales/zh_TW/backgrounds.json
@@ -154,11 +154,18 @@
"backgroundRainforestNotes": "冒險進入一個熱帶雨林。",
"backgroundStoneCircleText": "石圈",
"backgroundStoneCircleNotes": "在石圈裡施法。",
- "backgrounds042016": "SET 23: Released April 2016",
- "backgroundArcheryRangeText": "Archery Range",
- "backgroundArcheryRangeNotes": "Practice on the Archery Range.",
- "backgroundGiantFlowersText": "Giant Flowers",
+ "backgrounds042016": "第 23 組:2016 年 4 月推出",
+ "backgroundArcheryRangeText": "弓箭射程",
+ "backgroundArcheryRangeNotes": "練習弓箭射程",
+ "backgroundGiantFlowersText": "巨花",
"backgroundGiantFlowersNotes": "Frolic atop Giant Flowers.",
- "backgroundRainbowsEndText": "End of the Rainbow",
- "backgroundRainbowsEndNotes": "Discover gold at the End of the Rainbow."
+ "backgroundRainbowsEndText": "彩虹的盡頭",
+ "backgroundRainbowsEndNotes": "在彩虹的盡頭尋找金幣",
+ "backgrounds052016": "SET 24: Released May 2016",
+ "backgroundBeehiveText": "Beehive",
+ "backgroundBeehiveNotes": "Buzz and dance in a Beehive.",
+ "backgroundGazeboText": "Gazebo",
+ "backgroundGazeboNotes": "Battle a Gazebo.",
+ "backgroundTreeRootsText": "Tree Roots",
+ "backgroundTreeRootsNotes": "Explore the Tree Roots."
}
\ No newline at end of file
diff --git a/common/locales/zh_TW/challenge.json b/common/locales/zh_TW/challenge.json
index c463153958..a1c3da1d91 100644
--- a/common/locales/zh_TW/challenge.json
+++ b/common/locales/zh_TW/challenge.json
@@ -63,5 +63,20 @@
"congratulations": "恭喜!",
"hurray": "太好了!",
"noChallengeOwner": "無人擁有",
- "noChallengeOwnerPopover": "由於開創這個挑戰的人已經刪除他們的帳號,所以這個挑戰無人擁有。"
+ "noChallengeOwnerPopover": "由於開創這個挑戰的人已經刪除他們的帳號,所以這個挑戰無人擁有。",
+ "challengeMemberNotFound": "User not found among challenge's members",
+ "onlyGroupLeaderChal": "Only the group leader can create challenges",
+ "tavChalsMinPrize": "Prize must be at least 1 Gem for Tavern challenges.",
+ "cantAfford": "You can't afford this prize. Purchase more gems or lower the prize amount.",
+ "challengeIdRequired": "\"challengeId\" must be a valid UUID.",
+ "winnerIdRequired": "\"winnerId\" must be a valid UUID.",
+ "challengeNotFound": "Challenge not found.",
+ "onlyLeaderDeleteChal": "Only the challenge leader can delete it.",
+ "onlyLeaderUpdateChal": "Only the challenge leader can update it.",
+ "winnerNotFound": "Winner with id \"<%= userId %>\" not found or not part of the challenge.",
+ "noCompletedTodosChallenge": "\"includeComepletedTodos\" is not supported when fetching a challenge tasks.",
+ "userTasksNoChallengeId": "When \"tasksOwner\" is \"user\" \"challengeId\" can't be passed.",
+ "onlyChalLeaderEditTasks": "Tasks belonging to a challenge can only be edited by the leader.",
+ "userAlreadyInChallenge": "User is already participating in this challenge.",
+ "cantOnlyUnlinkChalTask": "Only broken challenges tasks can be unlinked."
}
\ No newline at end of file
diff --git a/common/locales/zh_TW/character.json b/common/locales/zh_TW/character.json
index 5cab47dd36..ea16eac7ac 100644
--- a/common/locales/zh_TW/character.json
+++ b/common/locales/zh_TW/character.json
@@ -1,4 +1,5 @@
{
+ "communityGuidelinesWarning": "Please keep in mind that your Display Name, profile photo, and blurb must comply with the Community Guidelines (e.g. no profanity, no adult topics, no insults, etc). If you have any questions about whether or not something is appropriate, feel free to email leslie@habitica.com!",
"statsAch": "屬性及成就",
"profile": "基本資料",
"avatar": "客制化角色圖像",
@@ -34,7 +35,7 @@
"beard": "落腮鬍",
"mustache": "八字鬍",
"flower": "花朵",
- "wheelchair": "Wheelchair",
+ "wheelchair": "輪椅",
"basicSkins": "基本膚色",
"rainbowSkins": "彩色膚色",
"pastelSkins": "柔和膚色",
@@ -109,6 +110,7 @@
"mage": "法師",
"mystery": "神秘",
"changeClass": "變更職業並重新分配屬性點",
+ "lvl10ChangeClass": "To change class you must be at least level 10.",
"levelPopover": "每升一級你都能得到一點並分配到你想要的屬性。你可以手動分配,或讓系統幫你自動完成。",
"unallocated": "未分配的屬性點",
"haveUnallocated": "你有( <%= points %> )點未分配的屬性點",
@@ -164,5 +166,7 @@
"int": "智力",
"showQuickAllocation": "開啟分配狀態",
"hideQuickAllocation": "關閉分配狀態",
- "quickAllocationLevelPopover": "每升一級你都能得到一點並分配到你想要的屬性。你可以手動分配,或讓系統在使用者->屬性下幫你自動完成。"
+ "quickAllocationLevelPopover": "每升一級你都能得到一點並分配到你想要的屬性。你可以手動分配,或讓系統在使用者->屬性下幫你自動完成。",
+ "invalidAttribute": "\"<%= attr %>\" is not a valid attribute.",
+ "notEnoughAttrPoints": "You don't have enough attribute points."
}
\ No newline at end of file
diff --git a/common/locales/zh_TW/content.json b/common/locales/zh_TW/content.json
index add68c446a..b06c3007c3 100644
--- a/common/locales/zh_TW/content.json
+++ b/common/locales/zh_TW/content.json
@@ -113,6 +113,12 @@
"questEggSnailText": "蛇",
"questEggSnailMountText": "蛇",
"questEggSnailAdjective": "緩慢但穩定的",
+ "questEggFalconText": "Falcon",
+ "questEggFalconMountText": "Falcon",
+ "questEggFalconAdjective": "a swift",
+ "questEggTreelingText": "Treeling",
+ "questEggTreelingMountText": "Treeling",
+ "questEggTreelingAdjective": "a leafy",
"eggNotes": "把孵化藥水倒在寵物蛋上會把它孵化成一隻<%= eggAdjective(locale) %> <%= eggText(locale) %>。",
"hatchingPotionBase": "普通",
"hatchingPotionWhite": "白色",
@@ -126,6 +132,7 @@
"hatchingPotionGolden": "金色",
"hatchingPotionSpooky": "怪異",
"hatchingPotionPeppermint": "薄荷",
+ "hatchingPotionFloral": "Floral",
"hatchingPotionNotes": "把它倒在寵物蛋上可以孵化出一隻<%= potText(locale) %>寵物。",
"premiumPotionAddlNotes": "不能夠在任務寵物蛋上使用。",
"foodMeat": "肉",
diff --git a/common/locales/zh_TW/contrib.json b/common/locales/zh_TW/contrib.json
index ad68845c75..2037c395a6 100644
--- a/common/locales/zh_TW/contrib.json
+++ b/common/locales/zh_TW/contrib.json
@@ -35,8 +35,12 @@
"hallContributors": "贊助人殿堂",
"hallPatrons": "贊助人殿堂",
"rewardUser": "獎勵玩家",
- "UUID": "帳號",
+ "UUID": "User ID",
"loadUser": "載入玩家",
+ "noAdminAccess": "You don't have admin access.",
+ "pageMustBeNumber": "req.query.page must be a number",
+ "userNotFound": "User not found.",
+ "invalidUUID": "UUID must be valid",
"title": "頭銜",
"moreDetails": "更多細節 (1-7)",
"moreDetails2": "更多細節 (8-9)",
@@ -52,8 +56,8 @@
"visitHeroes": "前往英雄殿堂(貢獻者和支持者之殿)",
"conLearn": "查看更多關於貢獻獎勵的信息",
"conLearnHow": "如何為Habitica作出貢獻",
- "surveysSingle": "填寫調查表來幫助Habitica成長。沒有進行中的調查表。",
- "surveysMultiple": "填寫<%= surveys %>調查表來幫助Habitica成長。沒有進行中的調查表。",
+ "surveysSingle": "Helped Habitica grow, either by filling out a survey or helping with a major testing effort. Thank you!",
+ "surveysMultiple": "Helped Habitica grow on <%= surveys %> occasions, either by filling out a survey or helping with a major testing effort. Thank you!",
"currentSurvey": "目前的調查表",
"surveyWhen": "在三月下旬調查表將處理完畢,徽章將被授予給的所有參與者。",
"blurbInbox": "這裡放著你的私人訊息,你可以透過在公會、酒館、隊伍裡發聲的使用者後面的信件圖示來送私人訊息給那個人。如收到不雅文字請截圖寄給Lemoness (leslie@habitica.com)。",
diff --git a/common/locales/zh_TW/death.json b/common/locales/zh_TW/death.json
index e12f2941ab..3650bcb84e 100644
--- a/common/locales/zh_TW/death.json
+++ b/common/locales/zh_TW/death.json
@@ -12,5 +12,6 @@
"losingHealthQuickly": "太容易失去生命值嗎?",
"lowHealthTips3": "未完成的每日任務會在隔天造成損害,所以要小心,不要一開始就增加太多每日任務!",
"lowHealthTips4": "如果你的每日任務在某些日子不用完成的話,你可以點選鉛筆按鈕去更改它。",
- "goodLuck": "祝你好運!"
+ "goodLuck": "祝你好運!",
+ "cannotRevive": "Cannot revive if not dead"
}
\ No newline at end of file
diff --git a/common/locales/zh_TW/front.json b/common/locales/zh_TW/front.json
index 7b9adedb62..611aee7f7d 100644
--- a/common/locales/zh_TW/front.json
+++ b/common/locales/zh_TW/front.json
@@ -18,7 +18,7 @@
"choreSample4": "整理一間房間",
"choreSample5": "清洗並晾乾成堆的衣服",
"chores": "家務事",
- "clearBrowserData": "Clear Browser Data",
+ "clearBrowserData": "清除瀏覽器資料",
"communityBug": "錯誤 (Bug) 回報",
"communityExtensions": "附加元件及擴充套件",
"communityFacebook": "Facebook",
@@ -28,6 +28,7 @@
"communityReddit": "Reddit",
"companyAbout": "如何執行",
"companyBlog": "部落格",
+ "devBlog": "Developer Blog",
"companyDonate": "贊助",
"companyExtensions": "擴充套件",
"companyPrivacy": "隱私權政策",
@@ -51,6 +52,7 @@
"featureSocialHeading": "社交遊戲",
"featuredIn": "特色在於",
"featuresHeading": "我們還有其他功能",
+ "footerDevs": "Developers",
"footerCommunity": "社群",
"footerCompany": "公司",
"footerMobile": "手機版本",
@@ -75,7 +77,7 @@
"infhQuote": "在我讀研究所的時候,[Habitica]幫我重建了生活重心!",
"invalidEmail": "需要有效的電子郵件地址,以便進行密碼重置。",
"irishfeet123Quote": "我曾經有個很糟的習慣,就是吃完飯後不去整理,而把杯子放得到處都是不放回原位。[Habitica] 救了我!",
- "joinOthers": "Join <%= userCount %> people making it fun to achieve goals!",
+ "joinOthers": "與 <%= userCount %> 人一起讓達成目標變得有趣!",
"kazuiQuote": "還沒有遇到[Habitica]之前,我被論文給卡住了,而且我要求自己要做到的事也都進度緩慢,像是做家事、背單字,學圍棋等。至從遇到它之後我才發現,原來我把事情分散開來會更好管理,現在做這些每日任務成了我的動力來源!",
"landingadminlink": "管理方案",
"landingend": "還沒被說服嗎?",
@@ -182,6 +184,7 @@
"zelahQuote": "有了 [Habitica],我可以為了獲得點數(早睡)或損失生命值(晚睡)而提早上床睡覺。",
"reportAccountProblems": "回報帳戶問題",
"reportCommunityIssues": "檢舉社群的問題",
+ "subscriptionPaymentIssues": "Subscription and Payment Issues",
"generalQuestionsSite": "關於本網站的一般問題",
"businessInquiries": "商業調查",
"merchandiseInquiries": "商品調查",
@@ -222,5 +225,32 @@
"altAttrWebstorm": "WebStorm",
"altAttrGithub": "GitHub",
"altAttrTrello": "Trello",
- "altAttrSlack": "Slack"
+ "altAttrSlack": "Slack",
+ "missingAuthHeaders": "Missing authentication headers.",
+ "missingAuthParams": "Missing authentication parameters.",
+ "missingUsernameEmail": "Missing username or email.",
+ "missingEmail": "Missing email.",
+ "missingUsername": "Missing username.",
+ "missingPassword": "Missing password.",
+ "missingNewPassword": "Missing new password.",
+ "wrongPassword": "Wrong password.",
+ "notAnEmail": "Invalid email address.",
+ "emailTaken": "Email address is already used in an account.",
+ "newEmailRequired": "Missing new email address.",
+ "usernameTaken": "Username already taken.",
+ "passwordConfirmationMatch": "Password confirmation doesn't match password.",
+ "invalidLoginCredentials": "Incorrect username and/or email and/or password.",
+ "passwordReset": "If we have your email on file, your password reset link has been sent to your email.",
+ "passwordResetEmailSubject": "Password Reset for Habitica",
+ "passwordResetEmailText": "Password for <%= username %> has been reset to <%= newPassword %> . Important! Both username and password are case-sensitive -- you must enter both exactly as shown here. We recommend copying and pasting both instead of typing them. Log in at <%= baseUrl %>. After you have logged in, head to <%= baseUrl %>/#/options/settings/settings and change your password.",
+ "passwordResetEmailHtml": "Password for <%= username %> has been reset to <%= newPassword %>.
Important! Both username and password are case-sensitive -- you must enter both exactly as shown here. We recommend copying and pasting both instead of typing them.
Log in at <%= baseUrl %>. After you have logged in, head to <%= baseUrl %>/#/options/settings/settings and change your password.",
+ "invalidLoginCredentialsLong": "Uh-oh - your username or password is incorrect.\n- Make sure your username or email is typed correctly.\n- You may have signed up with Facebook, not email. Double-check by trying Facebook login.\n- If you forgot your password, click \"Forgot Password\".",
+ "invalidCredentials": "There is no account that uses those credentials.",
+ "accountSuspended": "Account has been suspended, please contact leslie@habitica.com with your User ID \"<%= userId %>\" for assistance.",
+ "onlyFbSupported": "Only Facebook is supported currently.",
+ "cantDetachFb": "Account lacks another authentication method, can't detach Facebook.",
+ "onlySocialAttachLocal": "Local authentication can be added to only a social account.",
+ "invalidReqParams": "Invalid request parameters.",
+ "memberIdRequired": "\"member\" must be a valid UUID.",
+ "heroIdRequired": "\"heroId\" must be a valid UUID."
}
\ No newline at end of file
diff --git a/common/locales/zh_TW/gear.json b/common/locales/zh_TW/gear.json
index d84ef1272c..dd5a8756f3 100644
--- a/common/locales/zh_TW/gear.json
+++ b/common/locales/zh_TW/gear.json
@@ -146,11 +146,11 @@
"weaponSpecialSpring2016RogueText": "烈焰流星錘",
"weaponSpecialSpring2016RogueNotes": "你精通了錘球、棍棒和小刀,現在更要駕馭火焰了!哇!增加 <%= str %> 點力量。2016 春季限量裝備。",
"weaponSpecialSpring2016WarriorText": "奶酪錘",
- "weaponSpecialSpring2016WarriorNotes": "No one has as many friends as the mouse with tender cheeses. Increases Strength by <%= str %>. Limited Edition 2016 Spring Gear.",
- "weaponSpecialSpring2016MageText": "Staff of Bells",
- "weaponSpecialSpring2016MageNotes": "Abra-cat-abra! So dazzling, you might mesmerize yourself! Ooh... it jingles... Increases Intelligence by <%= int %> and Perception by <%= per %>. Limited Edition 2016 Spring Gear.",
- "weaponSpecialSpring2016HealerText": "Spring Flower Wand",
- "weaponSpecialSpring2016HealerNotes": "With a wave and a wink, you bring the fields and forests into bloom! Or bop troublesome mice on the head. Increases Intelligence by <%= int %>. Limited Edition 2016 Spring Gear.",
+ "weaponSpecialSpring2016WarriorNotes": "擁有柔軟起司的老鼠擁有最多的朋友。增加 <%=str %> 點力量。2016 春季限量裝備。",
+ "weaponSpecialSpring2016MageText": "鈴杖",
+ "weaponSpecialSpring2016MageNotes": "阿布拉卡特阿布拉!多麼炫目啊,你可能對自己施放了催眠術!喔... 他叮噹響,增加 <%= int %> 點智力和 <%= per %> 點感知。2016 春季限量裝備。",
+ "weaponSpecialSpring2016HealerText": "春花魔杖",
+ "weaponSpecialSpring2016HealerNotes": "只要一個揮舞並施法,你就可以讓整座森林與田野上花朵盛開!討厭的老鼠也都被驅趕走了。增加 <%= int %> 點智力。2016 春季限量裝備。",
"weaponMystery201411Text": "盛宴之叉",
"weaponMystery201411Notes": "刺傷你的仇敵或是插進你最愛的食物——這把多才多藝的叉子可是無所不能!沒有屬性加成。2014年11月訂閱者物品",
"weaponMystery201502Text": "愛與真理之微光翅膀法杖",
@@ -166,9 +166,9 @@
"weaponArmoireRancherLassoText": "牛仔套索",
"weaponArmoireRancherLassoNotes": "套索:為圍補和牽繩的理想工具。增加<%= str %>點力量, <%= per %>點感知和<%= int %>點智力。神祕寶箱:牛仔系列(3/3)。",
"weaponArmoireMythmakerSwordText": "神話英雄之劍",
- "weaponArmoireMythmakerSwordNotes": "Though it may seem humble, this sword has made many mythic heroes. Increases Perception and Strength by <%= attrs %> each. Enchanted Armoire: Golden Toga Set (Item 3 of 3).",
+ "weaponArmoireMythmakerSwordNotes": "重劍無鋒大巧不工,這把寶劍其實締造了許多神話英雄。增加 <%= attrs %> 點感知和力量。神祕寶箱:黃金戰袍套裝 (3 件中的第 3 件)",
"weaponArmoireIronCrookText": "鐵彎杖",
- "weaponArmoireIronCrookNotes": "Fiercely hammered from iron, this iron crook is good at herding sheep. Increases Perception and Strength by <%= attrs %> each. Enchanted Armoire: Horned Iron Set (Item 3 of 3).",
+ "weaponArmoireIronCrookNotes": "純鋼打造,力透杖柄。這柄區柄用來放牧效果極好。增加感知與力量各 <%=attrs %> 點。神秘寶箱:鐵角套裝 (3 件中的第 3 件)",
"weaponArmoireGoldWingStaffText": "金翼魔杖",
"weaponArmoireGoldWingStaffNotes": "法杖上的風不斷地旋轉扭動,增加所有屬性各<%= attrs %>點。神祕寶箱:獨立物品。",
"weaponArmoireBatWandText": "蝙蝠魔杖",
@@ -189,6 +189,8 @@
"weaponArmoireMiningPickaxNotes": "Mine the maximum amount of gold from your tasks! Increases Perception by <%= per %>. Enchanted Armoire: Miner Set (Item 3 of 3).",
"weaponArmoireBasicLongbowText": "Basic Longbow",
"weaponArmoireBasicLongbowNotes": "A serviceable hand-me-down bow. Increases Strength by <%= str %>. Enchanted Armoire: Basic Archer Set (Item 1 of 3).",
+ "weaponArmoireHabiticanDiplomaText": "Habitican Diploma",
+ "weaponArmoireHabiticanDiplomaNotes": "A certificate of significant achievement -- well done! Increases Intelligence by <%= int %>. Enchanted Armoire: Graduate Set (Item 1 of 3).",
"armor": "盔甲",
"armorBase0Text": "正常服裝",
"armorBase0Notes": "普通的衣服。 沒有屬性加成。",
@@ -364,6 +366,10 @@
"armorMystery201512Notes": "Summon the icy flames of winter! Confers no benefit. December 2015 Subscriber Item.",
"armorMystery201603Text": "幸運服裝",
"armorMystery201603Notes": "This suit is sewn from thousands of four-leafed clovers! Confers no benefit. March 2016 Subscriber Item.",
+ "armorMystery201604Text": "Armor o' Leaves",
+ "armorMystery201604Notes": "You, too, can be a small but fearsome leaf puff. Confers no benefit. April 2016 Subscriber Item.",
+ "armorMystery201605Text": "Marching Bard Uniform",
+ "armorMystery201605Notes": "Unlike the traditional bards who join adventuring parties, bards who join Habitican marching bands are known for grand parades, not dungeon raids. Confers no benefit. May 2016 Subscriber Item.",
"armorMystery301404Text": "蒸汽龐克套裝",
"armorMystery301404Notes": "精巧又瀟灑,哇嗚!沒有屬性加成。3015年1月訂閱者物品。",
"armorArmoireLunarArmorText": "潤月護甲",
@@ -394,6 +400,8 @@
"armorArmoireMinerOverallsNotes": "They may seem worn, but they are enchanted to repel dirt. Increases Constitution by <%= con %>. Enchanted Armoire: Miner Set (Item 2 of 3).",
"armorArmoireBasicArcherArmorText": "Basic Archer Armor",
"armorArmoireBasicArcherArmorNotes": "This camouflaged vest lets you slip unnoticed through the forests. Increases Perception by <%= per %>. Enchanted Armoire: Basic Archer Set (Item 2 of 3).",
+ "armorArmoireGraduateRobeText": "Graduate Robe",
+ "armorArmoireGraduateRobeNotes": "Congratulations! This weighty robe hangs heavy with all the knowledge you have accrued. Increases Intelligence by <%= int %>. Enchanted Armoire: Graduate Set (Item 2 of 3).",
"headgear": "頭飾",
"headBase0Text": "沒有頭盔",
"headBase0Notes": "沒有頭飾",
@@ -565,6 +573,10 @@
"headMystery201602Notes": "Shield your identity from all your admirers. Confers no benefit. February 2016 Subscriber Item.",
"headMystery201603Text": "幸運帽",
"headMystery201603Notes": "This top hat is a magical good-luck charm. Confers no benefit. March 2016 Subscriber Item.",
+ "headMystery201604Text": "Crown o' Flowers",
+ "headMystery201604Notes": "These woven flowers make a surprisingly strong helm! Confers no benefit. April 2016 Subscriber Item.",
+ "headMystery201605Text": "Marching Bard Hat",
+ "headMystery201605Notes": "Seventy-six dragons led the big parade, with a hundred and ten gryphons close at hand! Confers no benefit. May 2016 Subscriber Item.",
"headMystery301404Text": "華麗禮帽",
"headMystery301404Notes": "上流社會佼佼者的華麗禮帽!3015年1月訂閱者物品。沒有屬性加成。",
"headMystery301405Text": "基礎禮帽",
@@ -601,8 +613,8 @@
"headArmoireBlueFloppyHatNotes": "許多咒術被縫進這頂帽子裡,最後再給它一抹燦爛的藍色。增加感知、智力和體質各<%= attrs %>點。神祕寶箱:獨立物品。",
"headArmoireShepherdHeaddressText": "牧羊人頭飾",
"headArmoireShepherdHeaddressNotes": "你戴上這頂頭飾顯得智力非凡,不過有時候,你放養的獅鷲們喜歡咀嚼它。增加<%= int %>點智力。神秘寶箱:牧羊人系列(3/3)",
- "headArmoireCrystalCrescentHatText": "Crystal Crescent Hat",
- "headArmoireCrystalCrescentHatNotes": "The design on this hat waxes and wanes with the phases of the moon. Increases Intelligence and Perception by <%= attrs %> each. Enchanted Armoire: Crystal Crescent Set (Item 1 of 3).",
+ "headArmoireCrystalCrescentHatText": "水晶弦月帽",
+ "headArmoireCrystalCrescentHatNotes": "月之盈虧會反映這頂帽子的力量。增加智力和感知各 <%= attrs %> 點。神秘寶箱:水晶弦月套裝 (三件中的第一件)",
"headArmoireDragonTamerHelmText": "Dragon Tamer Helm",
"headArmoireDragonTamerHelmNotes": "You look exactly like a dragon. The perfect camouflage... Increases Intelligence by <%= int %>. Enchanted Armoire: Dragon Tamer Set (Item 1 of 3).",
"headArmoireBarristerWigText": "Barrister Wig",
@@ -613,6 +625,8 @@
"headArmoireMinerHelmetNotes": "Protect your head from falling tasks! Increases Intelligence by <%= int %>. Enchanted Armoire: Miner Set (Item 1 of 3).",
"headArmoireBasicArcherCapText": "Basic Archer Cap",
"headArmoireBasicArcherCapNotes": "No archer would be complete without a jaunty cap! Increases Perception by <%= per %>. Enchanted Armoire: Basic Archer Set (Item 3 of 3).",
+ "headArmoireGraduateCapText": "Graduate Cap",
+ "headArmoireGraduateCapNotes": "Congratulations! Your deep thoughts have earned you this thinking cap. Increases Intelligence by <%= int %>. Enchanted Armoire: Graduate Set (Item 3 of 3).",
"offhand": "副手物品",
"shieldBase0Text": "沒有副手裝備",
"shieldBase0Notes": "沒有盾牌或副武器。",
@@ -673,7 +687,7 @@
"shieldSpecialWinter2015HealerText": "寬慰之盾",
"shieldSpecialWinter2015HealerNotes": "這塊盾牌抵擋了刺骨的寒風。增加<%= con %>點體質。2014-2015冬季限量裝備。",
"shieldSpecialSpring2015RogueText": "霹靂爆破管",
- "shieldSpecialSpring2015RogueNotes": "別被它的劈哩啪啦聲糊弄了—爆炸起來非常厲害。提高<%= str %>點力量。2015春季限量版裝備。",
+ "shieldSpecialSpring2015RogueNotes": "Don't let the sound fool you - these explosives pack a punch. Increases Strength by <%= str %>. Limited Edition 2015 Spring Gear.",
"shieldSpecialSpring2015WarriorText": "盤子鐵餅",
"shieldSpecialSpring2015WarriorNotes": "向你的敵人擲去……或者只是單純地拿著它,因為到了晚飯時間它就會裝滿美味的狗食。提高<%= con %>點體質。2015春季限量版裝備。",
"shieldSpecialSpring2015HealerText": "印花枕頭",
@@ -716,6 +730,8 @@
"shieldArmoireDragonTamerShieldNotes": "Distract enemies with this dragon-shaped shield. Increases Perception by <%= per %>. Enchanted Armoire: Dragon Tamer Set (Item 2 of 3).",
"shieldArmoireMysticLampText": "神秘之燈",
"shieldArmoireMysticLampNotes": "Light the darkest caves with this mystic lamp! Increases Perception by <%= per %>. Enchanted Armoire: Independent Item.",
+ "shieldArmoireFloralBouquetText": "Bouquet o' Flowers",
+ "shieldArmoireFloralBouquetNotes": "Not much help in battle, but aren't they beautiful? Increases Constitution by <%= con %>. Enchanted Armoire: Independent Item.",
"back": "後背附件",
"backBase0Text": "沒有後背附件",
"backBase0Notes": "沒有後背附件。",
@@ -769,17 +785,17 @@
"headAccessorySpecialSpringWarriorNotes": "敏銳的兔耳偵察著每一捆胡蘿蔔。沒有增益效果。2014春季限量版裝備。",
"headAccessorySpecialSpringMageText": "藍色的老鼠耳朵",
"headAccessorySpecialSpringMageNotes": "這對圓圓的鼠耳如絲綢般柔軟。沒有屬性加成。2014春季限量版裝備。",
- "headAccessorySpecialSpringHealerText": "黃色的狗耳朵",
+ "headAccessorySpecialSpringHealerText": "黃犬順風耳",
"headAccessorySpecialSpringHealerNotes": "又軟又可愛。要不要一起玩呀?沒有屬性加成。2014春季限量版裝備。",
"headAccessorySpecialSpring2015RogueText": "黃色鼠耳",
"headAccessorySpecialSpring2015RogueNotes": "這對耳朵的鋼板能使自己不受爆炸聲干擾。沒有屬性加成。2015春季限量版裝備。",
- "headAccessorySpecialSpring2015WarriorText": "紫色犬耳",
+ "headAccessorySpecialSpring2015WarriorText": "紫犬順風耳",
"headAccessorySpecialSpring2015WarriorNotes": "它們是紫色的。它們是狗耳朵。別讓進一步的愚蠢浪費你的時間。沒有屬性加成。2015春季限量版裝備。",
"headAccessorySpecialSpring2015MageText": "藍色兔耳",
"headAccessorySpecialSpring2015MageNotes": "這對耳朵聽力過人,以防有魔術師在隨處洩密。沒有屬性加成。2015春季限量版裝備。",
"headAccessorySpecialSpring2015HealerText": "綠色貓耳",
"headAccessorySpecialSpring2015HealerNotes": "這對可愛的貓耳會讓其他人嫉妒到眼睛發綠。沒有屬性加成。2015春季限量版裝備。",
- "headAccessorySpecialSpring2016RogueText": "綠犬耳",
+ "headAccessorySpecialSpring2016RogueText": "綠犬順風耳",
"headAccessorySpecialSpring2016RogueNotes": "使用這個,你將可以追蹤到隱形的神秘法師!沒有屬性加成,2016 限量春季裝備。",
"headAccessorySpecialSpring2016WarriorText": "赤鼠耳",
"headAccessorySpecialSpring2016WarriorNotes": "To better hear your theme song across clamorous battlefields. Confers no benefit. Limited Edition 2016 Spring Gear.",
@@ -820,6 +836,20 @@
"eyewear": "眼部配件",
"eyewearBase0Text": "沒有眼部配件",
"eyewearBase0Notes": "沒有眼部配件",
+ "eyewearSpecialBlackTopFrameText": "Black Standard Eyeglasses",
+ "eyewearSpecialBlackTopFrameNotes": "Glasses with a black frame above the lenses. Confers no benefit.",
+ "eyewearSpecialBlueTopFrameText": "Blue Standard Eyeglasses",
+ "eyewearSpecialBlueTopFrameNotes": "Glasses with a blue frame above the lenses. Confers no benefit.",
+ "eyewearSpecialGreenTopFrameText": "Green Standard Eyeglasses",
+ "eyewearSpecialGreenTopFrameNotes": "Glasses with a green frame above the lenses. Confers no benefit.",
+ "eyewearSpecialPinkTopFrameText": "Pink Standard Eyeglasses",
+ "eyewearSpecialPinkTopFrameNotes": "Glasses with a pink frame above the lenses. Confers no benefit.",
+ "eyewearSpecialRedTopFrameText": "Red Standard Eyeglasses",
+ "eyewearSpecialRedTopFrameNotes": "Glasses with a red frame above the lenses. Confers no benefit.",
+ "eyewearSpecialWhiteTopFrameText": "White Standard Eyeglasses",
+ "eyewearSpecialWhiteTopFrameNotes": "Glasses with a white frame above the lenses. Confers no benefit.",
+ "eyewearSpecialYellowTopFrameText": "Yellow Standard Eyeglasses",
+ "eyewearSpecialYellowTopFrameNotes": "Glasses with a yellow frame above the lenses. Confers no benefit.",
"eyewearSpecialSummerRogueText": "流氓眼罩",
"eyewearSpecialSummerRogueNotes": "即使是無賴也能看出這個眼罩有多時髦!沒有屬性加成。限定版2014夏季裝備。",
"eyewearSpecialSummerWarriorText": "時髦眼罩",
diff --git a/common/locales/zh_TW/groups.json b/common/locales/zh_TW/groups.json
index 6b72e3a014..693c83fb77 100644
--- a/common/locales/zh_TW/groups.json
+++ b/common/locales/zh_TW/groups.json
@@ -92,6 +92,7 @@
"send": "送出",
"messageSentAlert": "已寄出的留言",
"pmHeading": "給<%= name %>的私訊",
+ "pmsMarkedRead": "Your private messages have been marked as read",
"clearAll": "刪除所有郵件",
"confirmDeleteAllMessages": "你確定要刪除收件箱中的所有郵件?其他用戶仍然會看到你發給他們的郵件。",
"optOutPopover": "不喜歡私密留言嗎?點選以更改設定。",
@@ -99,6 +100,15 @@
"unblock": "解除鎖定",
"pm-reply": "寄出回覆",
"inbox": "收信匣",
+ "messageRequired": "A message is required.",
+ "toUserIDRequired": "A User ID is required",
+ "gemAmountRequired": "A number of gems is required",
+ "notAuthorizedToSendMessageToThisUser": "Can't send message to this user.",
+ "privateMessageGiftIntro": "Hello <%= receiverName %>, <%= senderName %> has sent you",
+ "privateMessageGiftGemsMessage": "<%= gemAmount %> gems!",
+ "privateMessageGiftSubscriptionMessage": "<%= numberOfMonths %> months of subscription!",
+ "cannotSendGemsToYourself": "Cannot send gems to yourself. Try a subscription instead.",
+ "badAmountOfGemsToSend": "Amount must be within 1 and your current number of gems.",
"abuseFlag": "舉報社群規範違規事件",
"abuseFlagModalHeading": "您確定要舉報 <%= name %> 的違規事件?",
"abuseFlagModalBody": "你確定要舉報這則貼文嗎?你只能舉報違反<%= firstLinkStart %>社群規範<%= linkEnd %> 或是 <%= secondLinkStart %>服務條款<%= linkEnd %> 的貼文。任意舉報符合規定的貼文是違反社群條款的,你可能受到懲罰。當貼文涉及以下內容時是適合舉報的:
髒話、宗教性詛咒
偏激、詆毀
成人話題
暴力或是過分的玩笑
廣告、無意義訊息
",
@@ -151,5 +161,29 @@
"partyUpName": "隊伍參與",
"partyOnName": "龐大隊伍參與",
"partyUpAchievement": "成功加入一個隊伍!快去和怪物戰鬥並支援彼此吧。",
- "partyOnAchievement": "成功加入一個四人以上的隊伍!享受團隊帶給你額外的責任感,征服所有敵人吧!"
+ "partyOnAchievement": "成功加入一個四人以上的隊伍!享受團隊帶給你額外的責任感,征服所有敵人吧!",
+ "groupIdRequired": "\"groupId\" must be a valid UUID",
+ "groupNotFound": "Group not found.",
+ "groupTypesRequired": "You must supply a valid \"type\" query string.",
+ "questLeaderCannotLeaveGroup": "You cannot leave your party when you have started a quest. Abort the quest first.",
+ "cannotLeaveWhileActiveQuest": "You cannot leave party during an active quest. Please leave the quest first.",
+ "onlyLeaderCanRemoveMember": "Only group leader can remove a member!",
+ "memberCannotRemoveYourself": "You cannot remove yourself!",
+ "groupMemberNotFound": "User not found among group's members",
+ "mustBeGroupMember": "Must be member of the group.",
+ "keepOrRemoveAll": "req.query.keep must be either \"keep-all\" or \"remove-all\"",
+ "keepOrRemove": "req.query.keep must be either \"keep\" or \"remove\"",
+ "canOnlyInviteEmailUuid": "Can only invite using uuids or emails.",
+ "inviteMissingEmail": "Missing email address in invite.",
+ "partyMustbePrivate": "Parties must be private",
+ "userAlreadyInGroup": "User already in that group.",
+ "userAlreadyInvitedToGroup": "User already invited to that group.",
+ "userAlreadyPendingInvitation": "User already pending invitation.",
+ "userAlreadyInAParty": "User already in a party.",
+ "userWithIDNotFound": "User with id \"<%= userId %>\" not found.",
+ "userHasNoLocalRegistration": "User does not have a local registration (username, email, password).",
+ "uuidsMustBeAnArray": "User ID invites must be an array.",
+ "emailsMustBeAnArray": "Email address invites must be an array.",
+ "canOnlyInviteMaxInvites": "You can only invite \"<%= maxInvites %>\" at a time",
+ "onlyCreatorOrAdminCanDeleteChat": "Not authorized to delete this message!"
}
\ No newline at end of file
diff --git a/common/locales/zh_TW/limited.json b/common/locales/zh_TW/limited.json
index ca0255f14e..520ee2a77c 100644
--- a/common/locales/zh_TW/limited.json
+++ b/common/locales/zh_TW/limited.json
@@ -5,7 +5,7 @@
"annoyingFriends": "吵鬧的朋友",
"annoyingFriendsText": "被隊員的雪球砸中<%= snowballs %>次。",
"alarmingFriends": "愛嚇人的好友",
- "alarmingFriendsText": "被隊員嚇到 <%= spookDust %> 次。",
+ "alarmingFriendsText": "Got spooked <%= spookySparkles %> times by party members.",
"agriculturalFriends": "園藝之友",
"agriculturalFriendsText": "隊伍成員變身成一朵花共計<%= seeds %>次",
"aquaticFriends": "水族夥伴",
@@ -72,5 +72,6 @@
"comfortingKittySet": "Comforting Kitty (Healer)",
"sneakySqueakerSet": "Sneaky Squeaker (Rogue)",
"fallEventAvailability": "即日起至10月31日",
- "winterEventAvailability": "Available until December 31"
+ "winterEventAvailability": "Available until December 31",
+ "springEventAvailability": "Available until May 31"
}
\ No newline at end of file
diff --git a/common/locales/zh_TW/maintenance.json b/common/locales/zh_TW/maintenance.json
new file mode 100644
index 0000000000..efdb524cd2
--- /dev/null
+++ b/common/locales/zh_TW/maintenance.json
@@ -0,0 +1,34 @@
+{
+ "habiticaBackSoon": "Don't worry, Habitica will be back soon!",
+ "importantMaintenance": "We are doing important maintenance that we estimate will last until <%= localDate %> in your timezone.",
+ "maintenance": "Maintenance",
+ "maintenanceMoreInfo": "Want more information about the maintenance? <%= linkStart %>Check out our info page<%= linkEnd %>.",
+ "noDamageKeepStreaks": "You will NOT take damage or lose streaks!",
+ "thanksForPatience": "Thanks for your patience!",
+ "twitterMaintenanceUpdates": "For the most recent updates, watch our Twitter, where we will be posting status information.",
+ "veteranPetAward": "At the end, you will receive a Veteran pet!",
+
+ "maintenanceInfoTitle": "Information about Upcoming Maintenance to Habitica",
+ "maintenanceInfoWhat": "What is happening?",
+ "maintenanceInfoWhatText": "On May 21, Habitica will be down for maintenance for most of the day. You will not take any damage or have your account harmed during that weekend, even if you can’t log in to check off your Dailies in time! We will be working very hard to make the downtime as short as possible, and will be posting regular updates on our Twitter account. At the end of the downtime, to thank everyone for their patience, you will all receive a rare pet!",
+ "maintenanceInfoWhy": "Why is this happening?",
+ "maintenanceInfoWhyText": "For the past several months, we have been thoroughly revamping Habitica behind-the-scenes. Specifically, we have rewritten the API. While it may not look much different on the surface, it’s a whole new world underneath. This will allow us WAY more flexibility when we want to build features in the future, and lead to improved performance!",
+ "maintenanceInfoTechDetails": "Want more details on the technical side of the process? Visit The Forge, our dev blog.",
+ "maintenanceInfoMore": "More Information",
+ "maintenanceInfoAccountChanges": "What changes will I see to my account after the rewrite is complete?",
+ "maintenanceInfoAccountChangesText": "At first, there won’t be any notable changes aside from performance improvements for features such as Challenges. If you notice any changes that shouldn’t be there, email us at admin@habitica.com and we will investigate them for you!",
+ "maintenanceInfoAddFeatures": "What kind of features will this allow Habitica to add?",
+ "maintenanceInfoAddFeaturesText": "Completing this rewrite will allow us to start building out improved chat and Guilds, plans for organizations and families, and additional productivity features like Monthlies and the ability to record yesterday’s activity! Those are all involved features on their own, so it will take time to build them, but until we were finished with this rewrite, there was no way we could start them.",
+ "maintenanceInfoHowLong": "How long will the maintenance take?",
+ "maintenanceInfoHowLongText": "We have to migrate tasks and data for all 1.3 million Habitica users -- not an easy task! We anticipate that it will take place between approximately 1pm Pacific Time (8pm UTC) and 10pm Pacific Time (5am UTC). Rest assured that we’re doing everything we can to make it go as quickly as possible! You can follow updates on our Twitter.",
+ "maintenanceInfoStatsAffected": "How will my Dailies, Streaks, Buffs, and Quests be affected?",
+ "maintenanceInfoStatsAffectedText1": "You will NOT take any damage or lose any streaks that weekend, but otherwise, your day will reset normally! Dailies that you checked will become unchecked, buffs will reset, etc. If you are in a Collection Quest, you will still find items. If you are in a Boss Battle, you will still deal damage to the Boss, but the Boss will not deal damage to you. (Even monsters need a break!)",
+ "maintenanceInfoStatsAffectedText2": "After a lot of thought, our team concluded that this was the most fair way to handle the fact that many users will not be able to check off their Dailies normally during the maintenance. We’re sorry for any inconvenience this causes!",
+ "maintenanceInfoSeeTasks": "What if I need to see my task list?",
+ "maintenanceInfoSeeTasksText": "If you know that you will need to see your task list on Saturday to remind yourself what you have to do, we recommend that before the maintenance begins, you take a screenshot of your tasks so that you can use it as a reference.",
+ "maintenanceInfoRarePet": "What kind of rare pet will I receive?",
+ "maintenanceInfoRarePetText": "To thank you for your patience during the downtime, everyone will get a rare Veteran Pet. If you’ve never received a Veteran Pet before, you will receive a Veteran Wolf. If you already have a Veteran Wolf, you will receive a Veteran Tiger. And if you already have a Veteran Wolf and a Veteran Tiger, you will receive a never-before-seen Veteran pet! After the migration is completed, it may take several hours for your pet to show up, but never fear, everyone will get one.",
+ "maintenanceInfoWho": "Who worked on this massive project?",
+ "maintenanceInfoWhoText": "We’re glad you asked! It was spearheaded by our amazing contributor paglias, with lots of help from Blade, TheHollidayInn, SabreCat, Victor Pudeyev, TheUnknown, and Alys.",
+ "maintenanceInfoTesting": "The new version was also tirelessly tested by a bunch of our amazing open-source volunteers. Thank you -- we couldn't have done this without you."
+}
diff --git a/common/locales/zh_TW/npc.json b/common/locales/zh_TW/npc.json
index 119f15ee96..f3d024dec1 100644
--- a/common/locales/zh_TW/npc.json
+++ b/common/locales/zh_TW/npc.json
@@ -21,6 +21,26 @@
"ian": "Ian",
"ianText": "歡迎來到任務商店! 這裡可以買任務卷軸,與朋友一起打怪。記得常來看看有什麼可以買!",
"ianBrokenText": "歡迎來到任務商店...你可以跟你朋友們在這裡使用任務卷軸打怪...一定要來看看我們右邊販售的精美任務卷軸哦...",
+ "missingKeyParam": "\"req.params.key\" is required.",
+ "itemNotFound": "Item \"<%= key %>\" not found.",
+ "cannotBuyItem": "You can't buy this item.",
+ "missingTypeKeyEquip": "\"key\" and \"type\" are required parameters.",
+ "missingPetFoodFeed": "\"pet\" and \"food\" are required parameters.",
+ "invalidPetName": "Invalid pet name supplied.",
+ "missingEggHatchingPotionHatch": "\"egg\" and \"hatchingPotion\" are required parameters.",
+ "invalidTypeEquip": "\"type\" must be one of 'equipped', 'pet', 'mount', 'costume'.",
+ "mustPurchaseToSet": "Must purchase <%= val %> to set it on <%= key %>.",
+ "typeRequired": "Type is required",
+ "keyRequired": "Key is required",
+ "notAccteptedType": "Type must be in [eggs, hatchingPotions, food, quests, gear]",
+ "contentKeyNotFound": "Key not found for Content <%= type %>",
+ "plusOneGem": "+1 Gem",
+ "typeNotSellable": "Type is not sellable. Must be one of the following <%= acceptedTypes %>",
+ "userItemsKeyNotFound": "Key not found for user.items <%= type %>",
+ "pathRequired": "Path string is required",
+ "unlocked": "Items have been unlocked",
+ "alreadyUnlocked": "Full set already unlocked.",
+ "alreadyUnlockedPart": "Full set already partially unlocked.",
"USD": "(美金)",
"newStuff": "新品",
"cool": "稍候再跟我說",
@@ -64,6 +84,7 @@
"tourPetsPage": "這裡是馬廄!在你滿3等級時,你可以用寵物蛋和藥水來孵化寵物。當你在市集孵化出寵物時,牠會自動出現在這裡!點選寵物的圖像來加入到你的角色中。你4等級時會找到食物,用食物餵飽寵物的話,牠們就會成長為精力充沛的坐騎哦。",
"tourMountsPage": "一旦你餵飽了一隻寵物,牠就會進化成坐騎。坐騎會出現在這裡(當你等級3的時候,就會開放寵物、坐騎和食物。)點選一隻坐騎裝備上鞍吧! ",
"tourEquipmentPage": "這裡是放置裝備的地方! 你的戰鬥裝備影響你的數值。如果你想要讓你的角色大頭貼有不一樣的感覺,請點選 \"使用服裝\"。",
+ "equipmentAlreadyOwned": "You already own that piece of equipment",
"tourOkay": "好!",
"tourAwesome": "太棒了!",
"tourSplendid": "精彩!",
diff --git a/common/locales/zh_TW/pets.json b/common/locales/zh_TW/pets.json
index e2270f30ef..13df27566b 100644
--- a/common/locales/zh_TW/pets.json
+++ b/common/locales/zh_TW/pets.json
@@ -12,6 +12,7 @@
"etherealLion": "靈獅",
"veteranWolf": "退伍軍狼",
"veteranTiger": "資深的老虎",
+ "veteranLion": "Veteran Lion",
"cerberusPup": "三頭地獄幼犬",
"hydra": "三頭蛇",
"mantisShrimp": "瀨尿蝦",
@@ -19,7 +20,7 @@
"orca": "逆戟鯨",
"royalPurpleGryphon": "紫禦獅鷲",
"phoenix": "鳳凰",
- "bumblebee": "Bumblebee",
+ "magicalBee": "Magical Bee",
"rarePetPop1": "按按金色的爪印,看看怎麼為Habitica貢獻而獲得這隻稀有寵物!",
"rarePetPop2": "得到這個寵物的方法!",
"potion": "<%= potionType %> 藥水",
@@ -62,6 +63,7 @@
"hatchedPet": "您孵出了一個 <%= potion %> <%= egg %>!",
"displayNow": "現在顯示",
"displayLater": "稍後顯示",
+ "petNotOwned": "You do not own this pet.",
"earnedCompanion": "因為你的效率,你已經獲得了一個新的同伴。餵養它,使其茁壯成長!",
"feedPet": "餵一個<%= article %><%= text %> 給 <%= name %>?",
"useSaddle": "把鞍用在<%= pet %>上?",
@@ -83,5 +85,8 @@
"petKeyBoth": "釋放所有寵物與座騎",
"confirmPetKey": "你確定嗎?",
"petKeyNeverMind": "還不是時候",
+ "petsReleased": "Pets released.",
+ "mountsAndPetsReleased": "Mounts and pets released",
+ "mountsReleased": "Mounts released",
"gemsEach": "寶石/次"
}
\ No newline at end of file
diff --git a/common/locales/zh_TW/quests.json b/common/locales/zh_TW/quests.json
index 16b5158b94..f99d486331 100644
--- a/common/locales/zh_TW/quests.json
+++ b/common/locales/zh_TW/quests.json
@@ -78,5 +78,24 @@
"whichQuestStart": "你要開始哪個任務?",
"getMoreQuests": "取得更多任務",
"unlockedAQuest": "你解鎖了一個任務!",
- "leveledUpReceivedQuest": "您升級到等級<%= 級別 %>並獲得一個任務卷軸!"
+ "leveledUpReceivedQuest": "您升級到等級<%= 級別 %>並獲得一個任務卷軸!",
+ "questInvitationDoesNotExist": "No quest invitation has been sent out yet.",
+ "questInviteNotFound": "No quest invitation found.",
+ "guildQuestsNotSupported": "Guilds cannot be invited on quests.",
+ "questNotFound": "Quest \"<%= key %>\" not found.",
+ "questNotOwned": "You don't own that quest scroll.",
+ "questNotGoldPurchasable": "Quest \"<%= key %>\" is not a Gold-purchasable quest.",
+ "questLevelTooHigh": "You must be level <%= level %> to begin this quest.",
+ "questAlreadyUnderway": "Your party is already on a quest. Try again when the current quest has ended.",
+ "questAlreadyAccepted": "You already accepted the quest invitation.",
+ "noActiveQuestToLeave": "No active quest to leave",
+ "questLeaderCannotLeaveQuest": "Quest leader cannot leave quest",
+ "notPartOfQuest": "You are not part of the quest",
+ "noActiveQuestToAbort": "There is no active quest to abort.",
+ "onlyLeaderAbortQuest": "Only the group or quest leader can abort a quest.",
+ "questAlreadyRejected": "You already rejected the quest invitation.",
+ "cantCancelActiveQuest": "You can not cancel an active quest, use the abort functionality.",
+ "onlyLeaderCancelQuest": "Only the group or quest leader can cancel the quest.",
+ "questNotPending": "There is no quest to start.",
+ "questOrGroupLeaderOnlyStartQuest": "Only the quest leader or group leader can force start the quest"
}
\ No newline at end of file
diff --git a/common/locales/zh_TW/questscontent.json b/common/locales/zh_TW/questscontent.json
index 05662c9e5e..98841857ce 100644
--- a/common/locales/zh_TW/questscontent.json
+++ b/common/locales/zh_TW/questscontent.json
@@ -1,12 +1,12 @@
{
"questEvilSantaText": "聖誕盜獵者",
- "questEvilSantaNotes": "You hear agonized roars deep in the icefields. You follow the growls - punctuated by the sound of cackling - to a clearing in the woods, where you see a fully-grown polar bear. She's caged and shackled, fighting for her life. Dancing atop the cage is a malicious little imp wearing a castaway costume. Vanquish Trapper Santa, and save the beast!",
- "questEvilSantaCompletion": "Trapper Santa squeals in anger, and bounces off into the night. The grateful she-bear, through roars and growls, tries to tell you something. You take her back to the stables, where Matt Boch the Beast Master listens to her tale with a gasp of horror. She has a cub! He ran off into the icefields when mama bear was captured.",
+ "questEvilSantaNotes": "從冰原的深處,你聽到了一陣痛苦的嘶吼.你跟著那吠叫-從中夾帶著些許的笑聲-到了一個森林中的空地.你在那看到了一個成長完全的北極熊.在籠中的她身上被束縛著鎖鏈,為了生存而反抗著.在籠子上,有個身穿著破爛的聖誕服裝並跳著舞的惡毒小妖靈.擊敗\n聖誕盜獵者,拯救那被監禁的猛獸!",
+ "questEvilSantaCompletion": "聖誕盜獵者憤怒地嚎叫,縱身躍入夜幕之中。一隻充滿感激的母熊咆哮著似乎在訴說著什麼。你把她帶回馬厩,馴獸大師Matt Boch聆聽了她帶著恐懼的喘息講述的故事。她有個寶寶!當母熊被陷阱抓住的時候寶寶跑入了冰原。",
"questEvilSantaBoss": "聖誕盜獵者",
"questEvilSantaDropBearCubPolarMount": "北極熊 (坐騎)",
"questEvilSanta2Text": "找小熊",
- "questEvilSanta2Notes": "When Trapper Santa captured the polar bear mount, her cub ran off into the icefields. You hear twig-snaps and snow crunch through the crystalline sound of the forest. Paw prints! You start racing to follow the trail. Find all the prints and broken twigs, and retrieve the cub!",
- "questEvilSanta2Completion": "You've found the cub! It will keep you company forever.",
+ "questEvilSanta2Notes": "母熊的寶寶在母熊被聖誕盜獵者抓住的時候跑進了冰原。你聽到樹枝咔嚓作響,雪地嘎吱嘎吱的聲音迴盪在整個樹林。爪印!你們開始追尋雪地上的踪跡。找到所有的爪印和破損的樹枝,找回她的寶寶!",
+ "questEvilSanta2Completion": "恭喜你找到了小熊,它將永遠陪伴著你,讓你不再孤單。",
"questEvilSanta2CollectTracks": "足跡",
"questEvilSanta2CollectBranches": "折斷的樹枝",
"questEvilSanta2DropBearCubPolarPet": "北極熊 (寵物)",
@@ -36,7 +36,7 @@
"questRatUnlockText": "解鎖-可在市集中購買老鼠蛋",
"questOctopusText": "章魚克蘇魯的呼喚",
"questOctopusNotes": "@Urse是位相當狂熱的抄寫員,他希望你幫忙探索海岸邊的一個神祕洞穴。在暮光下潮淵中矗立著一座由鐘乳石所構成的大門。當你走進大門,一道黑暗的漩渦自大門根基處開始打轉。你詫異的看著一隻章魚似的龍自漩渦中心升起。「黏稠的星之子醒來了,」@Urse激動地大喊「經過了千萬億年之後,偉大的克蘇魯又重獲自由了,又可以為了盡興而開始劫掠了。」",
- "questOctopusCompletion": "With a final blow, the creature slips away into the whirlpool from which it came. You cannot tell if @Urse is happy with your victory or saddened to see the beast go. Wordlessly, your companion points to three slimy, gargantuan eggs in a nearby tidepool, set in a nest of gold coins. \"Probably just octopus eggs,\" you say nervously. As you return home, @Urse frantically scribbles in a journal and you suspect this is not the last time you will hear of the great Octothulu.",
+ "questOctopusCompletion": "最後一擊,怪物滑進了它現身的漩渦中。你不知道@Urse 是會為你的勝利而高興還是會為怪物的離開而難過。在沉默中,你的同伴指著附近的潮池,裡面有三個的粘滑的,巨大的蛋,被放在一個用金幣堆成的窩中。 ”也許這只是章魚蛋,”你不安地說。在你們回家之後,@Urse狂熱潦草地寫著遊記,而你懷疑著你不會是最後一次聽到偉大的章魚克·蘇魯這個名字。",
"questOctopusBoss": "章魚克蘇魯",
"questOctopusDropOctopusEgg": "章魚 ( 蛋 )",
"questOctopusUnlockText": "解鎖-可在市集中購買章魚蛋",
@@ -59,7 +59,7 @@
"questSpiderDropSpiderEgg": "蜘蛛 ( 蛋 )",
"questSpiderUnlockText": "解鎖-可在市集中購買蜘蛛蛋",
"questVice1Text": "惡習,第1部:從龍的影響中解放你自己",
- "questVice1Notes": "
They say there lies a terrible evil in the caverns of Mt. Habitica. A monster whose presence twists the wills of the strong heroes of the land, turning them towards bad habits and laziness! The beast is a grand dragon of immense power and comprised of the shadows themselves: Vice, the treacherous Shadow Wyrm. Brave Habiteers, stand up and defeat this foul beast once and for all, but only if you believe you can stand against its immense power.
Vice Part 1:
How can you expect to fight the beast if it already has control over you? Don't fall victim to laziness and vice! Work hard to fight against the dragon's dark influence and dispel his hold on you!
",
"questVice1Boss": "惡習之龍的陰影",
"questVice1DropVice2Quest": "惡習之龍—第 2 部 ( 捲軸 )",
"questVice2Text": "惡習,第2部:尋覓巨龍之巢",
@@ -74,7 +74,7 @@
"questVice3DropDragonEgg": "龍 ( 蛋 )",
"questVice3DropShadeHatchingPotion": "深色孵化藥水",
"questMoonstone1Text": "月光石項鍊,第1部:月光石項鍊",
- "questMoonstone1Notes": "A terrible affliction has struck Habiticans. Bad Habits thought long-dead are rising back up with a vengeance. Dishes lie unwashed, textbooks linger unread, and procrastination runs rampant!
You track some of your own returning Bad Habits to the Swamps of Stagnation and discover the culprit: the ghostly Necromancer, Recidivate. You rush in, weapons swinging, but they slide through her specter uselessly.
\"Don’t bother,\" she hisses with a dry rasp. \"Without a chain of moonstones, nothing can harm me – and master jeweler @aurakami scattered all the moonstones across Habitica long ago!\" Panting, you retreat... but you know what you must do.",
+ "questMoonstone1Notes": "一個可怕的災難困擾著Habitica居民。已經消失很久的壞習慣回來復仇了。盤子沒有清洗,課本很長時間沒有看,拖延症開始猖獗!
“別費勁兒了,”她用粗糙刺耳的聲音嘶嘶得說。 “沒有月長石項鍊的話,沒有什麼可以傷害我--而且寶石大師@aurakami很久以前就將月長石分散到了Habitica的各處!”雖然你氣喘吁籲的撤退了... 但是你知道你需要做什麼。",
"questMoonstone1CollectMoonstone": "月之石",
"questMoonstone1DropMoonstone2Quest": "月光石項鍊第 2 部分:死靈法師 Recidivate (卷軸)",
"questMoonstone2Text": "月光石項鍊,第2部:死靈法師Recidivate",
@@ -304,9 +304,21 @@
"questBewilderCompletionChat": "`The Be-Wilder is DEFEATED!`\n\nWe've done it! The Be-Wilder lets out a ululating cry as it twists in the air, shedding feathers like falling rain. Slowly, gradually, it coils into a cloud of sparkling mist. As the newly-revealed sun pierces the fog, it burns away, revealing the coughing, mercifully human forms of Bailey, Matt, Alex.... and the April Fool himself.\n\n`Mistiflying is saved!`\n\nThe April Fool has enough shame to look a bit sheepish. “Oh, hm,” he says. “Perhaps I got a little…. carried away.”\n\nThe crowd mutters. Sodden flowers wash up on sidewalks. Somewhere in the distance, a roof collapses with a spectacular splash.\n\n“Er, yes,” the April Fool says. “That is. What I meant to say was, I’m dreadfully sorry.” He heaves a sigh. “I suppose it can’t all be fun and games, after all. It might not hurt to focus occasionally. Maybe I’ll get a head start on next year’s pranking.”\n\nRedphoenix coughs meaningfully.\n\n“I mean, get a head start on this year’s spring cleaning!” the April Fool says. “Nothing to fear, I’ll have Habit City in spit-shape soon. Luckily nobody is better than I at dual-wielding mops.”\n\nEncouraged, the marching band starts up.\n\nIt isn’t long before all is back to normal in Habit City. Plus, now that the Be-Wilder has evaporated, the magical bees of Mistiflying bustle back to work, and soon the flowers are blooming and the city is floating once more.\n\nAs Habiticans cuddle the magical fuzzy bees, the April Fool’s eyes light up. “Oho, I’ve had a thought! Why don’t you all keep some of these fuzzy Bee Pets and Mounts? It’s a gift that perfectly symbolizes the balance between hard work and sweet rewards, if I’m going to get all boring and allegorical on you.” He winks. “Besides, they don’t have stingers! Fool’s honor.”",
"questBewilderBossRageTitle": "Beguilement Strike",
"questBewilderBossRageDescription": "When this gauge fills, The Be-Wilder will unleash its Beguilement Strike on Habitica!",
- "questBewilderDropBumblebeePet": "Bumblebee (Pet)",
- "questBewilderDropBumblebeeMount": "Bumblebee (Mount)",
+ "questBewilderDropBumblebeePet": "Magical Bee (Pet)",
+ "questBewilderDropBumblebeeMount": "Magical Bee (Mount)",
"questBewilderBossRageMarket": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nOh no! Despite our best efforts, we've gotten distracted by the Be-Wilder’s charming illusions and have forgotten to do some of our Dailies! With a cackling cry, the shining bird beats its wings, raising a swarm of mist around Alex the Merchant. When the fog clears, he has been possessed! “Have some free samples!” he shouts gleefully, and begins to hurl exploding eggs and potions at fleeing Habiticans. Not the most favorable of sales, to be sure.\n\nHurry! Let's stay focused on our Dailies to defeat this monster before it possesses someone else.",
"questBewilderBossRageStables": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nAhh!!! Once again the Be-Wilder has dazzled us into neglecting our Dailies, and now it has attacked Matt the Beast Master! With a swirl of mist, Matt transforms into a terrifying winged creature, and all the pets and mounts howl sadly in their stables. Quickly, stay focused on your tasks to defeat this dastardly distraction!",
- "questBewilderBossRageBailey": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nLook out! In the middle of reporting the news, Bailey the Town Crier has been possessed by the Be-Wilder! She lets out an evil, uninformative screech as she rises into the air. Now how will we know what’s going on?\n\nDon't give up... we're so close to defeating this bothersome bird for once and for all!"
+ "questBewilderBossRageBailey": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nLook out! In the middle of reporting the news, Bailey the Town Crier has been possessed by the Be-Wilder! She lets out an evil, uninformative screech as she rises into the air. Now how will we know what’s going on?\n\nDon't give up... we're so close to defeating this bothersome bird for once and for all!",
+ "questFalconText": "The Birds of Preycrastination",
+ "questFalconNotes": "Mt. Habitica is being overshadowed by a looming mountain of To-Dos. It used to be a place to picnic and enjoy a sense of accomplishment, until the neglected tasks grew out of control. Now it's home to fearsome Birds of Preycrastination, foul creatures which stop Habiticans from completing their tasks!
\"It's too hard!\" they caw at @JonArinbjorn and @Onheiron. \"It'll take too long to do right now! It won't make any difference if you wait until tomorrow! Why don't you do something fun instead?\"
No more, you vow. You will climb your personal mountain of To-Dos and defeat the Birds of Preycrastination!",
+ "questFalconCompletion": "Having finally triumphed over the Birds of Preycrastination, you settle down to enjoy the view and your well-earned rest.
\"Wow!\" says @Trogdorina. \"You won!\"
@Squish adds, \"Here, take these eggs I found as a reward.\"",
+ "questFalconBoss": "Birds of Preycrastination",
+ "questFalconDropFalconEgg": "Falcon (Egg)",
+ "questFalconUnlockText": "Unlocks purchasable Falcon eggs in the Market",
+ "questTreelingText": "The Tangle Tree",
+ "questTreelingNotes": "It's the annual Garden Competition, and everyone is talking about the mysterious project which @aurakami has promised to unveil. You join the crowd on the day of the big announcement, and marvel at the introduction of a moving tree. @fuzzytrees explains that the tree will help with garden maintenance, showing how it can mow the lawn, trim the hedge and prune the roses all at the same time – until the tree suddenly goes wild, turning its secateurs on its creator! The crowd panics as everyone tries to flee, but you aren't afraid – you leap forward, ready to do battle.",
+ "questTreelingCompletion": "You dust yourself off as the last few leaves drift to the floor. In spite of the upset, the Garden Competition is now safe – although the tree you just reduced to a heap of wood chips won't be winning any prizes! \"Still a few kinks to work out there,\" @PainterProphet says. \"Perhaps someone else would do a better job of training the saplings. Do you fancy a go?\"",
+ "questTreelingBoss": "Tangle Tree",
+ "questTreelingDropTreelingEgg": "Treeling (Egg)",
+ "questTreelingUnlockText": "Unlocks purchasable Treeling eggs in the Market"
}
\ No newline at end of file
diff --git a/common/locales/zh_TW/rebirth.json b/common/locales/zh_TW/rebirth.json
index 081dd5f6cf..8d33046a4f 100644
--- a/common/locales/zh_TW/rebirth.json
+++ b/common/locales/zh_TW/rebirth.json
@@ -5,7 +5,7 @@
"rebirthStartOver": "「重生」讓你的角色重返等級 1 !",
"rebirthAdvList1": "生命值完全復原",
"rebirthAdvList2": "你沒有任何經驗值,黃金,或裝備。(除了一些免費的物品例如「神秘物品」)",
- "rebirthAdvList3": "你的習慣、日常任務、待辦事項會被重置為黃色,連擊數也會被重置。",
+ "rebirthAdvList3": "Your Habits, Dailies, and To-Dos reset to yellow, and streaks reset, except for challenge tasks.",
"rebirthAdvList4": "你會從戰士職業開始,直到你解鎖了新的職業。",
"rebirthInherit": "你的新角色繼承了一些前輩的東西:",
"rebirthInList1": "任務、歷史記錄和設置會保留下來。",
@@ -24,5 +24,6 @@
"rebirthPop": "開始一個等級 1 的新角色,並保留成就、物品和任務歷史。",
"rebirthName": "重生之球",
"reborn": "重生,最高級別 <%= reLevel %>",
- "confirmReborn": "你確定嗎?"
+ "confirmReborn": "你確定嗎?",
+ "rebirthComplete": "You have been reborn!"
}
\ No newline at end of file
diff --git a/common/locales/zh_TW/settings.json b/common/locales/zh_TW/settings.json
index a78e61a852..b5d5c99914 100644
--- a/common/locales/zh_TW/settings.json
+++ b/common/locales/zh_TW/settings.json
@@ -47,6 +47,7 @@
"customDayStart": "設定開始日期",
"changeCustomDayStart": "要更改設定日期嗎?",
"sureChangeCustomDayStart": "你確定你要更改設定日期嗎?",
+ "customDayStartHasChanged": "Your custom day start has changed.",
"nextCron": "在<%= time %>以後,重新登入 Habitica,你的每日任務將會重新設定。確保<%= time %>前完成你的每日任務。",
"customDayStartInfo1": "Habitica 預設每日午夜將會重新結算當日的每日任務於你所在的時區。你可以在這裡更改每日任務結算時間。",
"misc": "其他",
@@ -61,12 +62,23 @@
"newUsername": "新帳號",
"dangerZone": "危險區域",
"resetText1": "警告!此功能會重設你角色的多數資料。強烈不建議你這樣做,但是有些人短暫地玩這個網站後,希望能重新開始。",
- "resetText2": "你會失去你所有的等級、金幣和經驗值,所有的任務會被永久刪除。你會失去你所有的任務歷史資料。你會失去所有的裝備,包括你曾經擁有過的所有限量版裝備,和訂閱者的神秘物品(你需要變成對應的職業,才能重新購買職業限定的裝備)。但是能夠再把他們買回來。你會保留你的當前職業和你的寵物與坐騎。相比之下,用道具重生球會更安全,你的任務會被保留下來。",
+ "resetText2": "You will lose all your levels, gold, and experience points. All your tasks (except those from challenges) will be deleted permanently and you will lose all of their historical data. You will lose all your equipment but you will be able to buy it all back, including all limited edition equipment or subscriber Mystery items that you already own (you will need to be in the correct class to re-buy class-specific gear). You will keep your current class and your pets and mounts. You might prefer to use an Orb of Rebirth instead, which is a much safer option and which will preserve your tasks.",
"deleteText": "確定嗎?這會永久地刪除你的帳號,並且再也無法恢復!如果希望再次使用 Habitica,需要註冊一個全新的帳號。舊帳號內儲存的和花掉的寶石也無法被退費。如果你非常確定要刪除帳號,在下面的文字欄中輸入<%= deleteWord %>。",
"API": "API",
+ "APIv3": "API v3",
+ "APIv2": "API v2 - Deprecated",
"APIText": "這個是使用再第三方應用上面的。但是,你的 API Token 相當於密碼,請不要公開它。有時候別人可能會向你要UUID,但是永遠不要把你的 API token 分享給其他人,包括在 Github 上。",
"APIToken": "API Token(這相當於密碼——注意看上面的警告!)",
+ "thirdPartyApps": "Third Party Apps",
+ "dataToolDesc": "A webpage that shows you certain information from your Habitica account, such as statistics about your tasks, equipment, and skills.",
+ "beeminder": "Beeminder",
+ "beeminderDesc": "Let Beeminder automatically monitor your Habitica To-Dos. You can commit to maintaining a target number of To-Dos completed per day or per week, or you can commit to gradually reducing your remaining number of uncompleted To-Dos. (By \"commit\" Beeminder means under threat of paying actual money! But you may also just like Beeminder's fancy graphs.)",
+ "chromeChatExtension": "Chrome Chat Extension",
+ "chromeChatExtensionDesc": "The Chrome Chat Extension for Habitica adds an intuitive chat box to all of habitica.com. It allows users to chat in the Tavern, their party, and any guilds they are in.",
+ "otherExtensions": "Other Extensions",
+ "otherDesc": "Find other apps, extensions, and tools on the Habitica wiki.",
"resetDo": "好,重置我的帳號!",
+ "resetComplete": "Reset complete!",
"fixValues": "修復數值",
"fixValuesText1": "如果你遇到一個程式上的錯誤,或是自己操作錯誤而導致你的角色受到不公平的待遇(如:受了不應該遭受的攻擊,得到你不應該得到的金幣等等),你可以在這裡手動調整這些數值。沒錯,這讓你有機會能作弊:請謹慎地使用這個功能,否則你會破壞你自己的習慣養成計畫!",
"fixValuesText2": "注意,你無法在這裡重置單一任務的連擊數。你得編輯每日任務標籤,並進入進階選項,重置連擊數。",
@@ -96,6 +108,7 @@
"emailNotifications": "電子郵件通知",
"wonChallenge": "你贏得一個挑戰!",
"newPM": "收到的私密訊息",
+ "sentGems": "Sent gems!",
"giftedGems": "禮物用寶石",
"giftedGemsInfo": "<%= name %> 的 <%= amount %> 個寶石",
"giftedSubscription": "禮物用訂閱",
@@ -135,6 +148,11 @@
"webhooks": "Webhooks",
"enabled": "啟用",
"webhookURL": "Webhook URL",
+ "invalidUrl": "invalid url",
+ "invalidEnabled": "the \"enabled\" parameter should be a boolean",
+ "regIdRequired": "RegId is required",
+ "pushDeviceAdded": "Push device added successfully",
+ "pushDeviceAlreadyAdded": "The user already has the push device",
"add": "增加",
"buyGemsGoldCap": "上限提高到<%= amount %>",
"mysticHourglass": "<%= amount %>神秘沙漏",
@@ -149,5 +167,5 @@
"amazonPayments": "Amazon Payments",
"timezone": "時區",
"timezoneUTC": "Habitica 使用您電腦的時區,現在是:<%= utc %>",
- "timezoneInfo": "如果該時區是錯誤的,首先使用瀏覽器的重載或刷新按鈕,以確保Habitica具有最新的信息刷新頁面。如果它仍然是錯的,調整你的電腦上的時區,然後再次刷新頁面。
如果您在其他PC或移動設備上使用Habitica ,時區必須對他們都一樣如果您有日常任務在錯誤的時間被重設,重複此檢查在所有其他電腦和對您的移動設備瀏覽器。"
+ "timezoneInfo": "If that time zone is wrong, first reload this page using your browser's reload or refresh button to ensure that Habitica has the most recent information. If it is still wrong, adjust the time zone on your PC and then reload this page again.
If you use Habitica on other PCs or mobile devices, the time zone must be the same on them all. If your Dailies have been resetting at the wrong time, repeat this check on all other PCs and on a browser on your mobile devices."
}
\ No newline at end of file
diff --git a/common/locales/zh_TW/spells.json b/common/locales/zh_TW/spells.json
index 38f06d0112..d141c5e462 100644
--- a/common/locales/zh_TW/spells.json
+++ b/common/locales/zh_TW/spells.json
@@ -35,8 +35,8 @@
"spellSpecialSnowballAuraNotes": "向隊友發射一個雪球,怎麼可能會有什麼問題呢?持續到隊友的下一天。",
"spellSpecialSaltText": "鹽",
"spellSpecialSaltNotes": "有人向你扔了雪球。哈哈,很好玩。現在把這些雪從我身上弄下來!",
- "spellSpecialSpookDustText": "鬼火",
- "spellSpecialSpookDustNotes": "把你的朋友變成長著眼睛的漂浮魔毯。",
+ "spellSpecialSpookySparklesText": "Spooky Sparkles",
+ "spellSpecialSpookySparklesNotes": "Turn a friend into a floating blanket with eyes!",
"spellSpecialOpaquePotionText": "晦暗藥水",
"spellSpecialOpaquePotionNotes": "取消鬼火的效果",
"spellSpecialShinySeedText": "閃亮種子",
@@ -46,5 +46,11 @@
"spellSpecialSeafoamText": "水藍",
"spellSpecialSeafoamNotes": "把朋友變成海洋生物!",
"spellSpecialSandText": "沙子",
- "spellSpecialSandNotes": "取消水藍的效果。"
+ "spellSpecialSandNotes": "取消水藍的效果。",
+ "spellNotFound": "Skill \"<%= spellId %>\" not found.",
+ "partyNotFound": "Party not found",
+ "targetIdUUID": "\"targetId\" must be a valid User ID.",
+ "challengeTasksNoCast": "Casting a skill on challenge tasks is not allowed.",
+ "spellNotOwned": "You don't own this skill.",
+ "spellLevelTooHigh": "You must be level <%= level %> to use this skill."
}
\ No newline at end of file
diff --git a/common/locales/zh_TW/subscriber.json b/common/locales/zh_TW/subscriber.json
index 27405cf248..d7d1564b55 100644
--- a/common/locales/zh_TW/subscriber.json
+++ b/common/locales/zh_TW/subscriber.json
@@ -4,6 +4,8 @@
"subDescription": "用金幣購買寶石、獲得每月的神秘物品、保留歷史進度、日常掉寶率上限加倍、支持開發者。點擊這裡來獲取更多資訊。",
"buyGemsGold": "用金幣購買寶石",
"buyGemsGoldText": "商人 Alexander 會以 <%= gemCost %> 金幣販售你 1 單位的寶石。而他每個月可以販售的寶石量從最初的 <%= gemLimit %> 單位的寶石,持續訂閱三個月後將會增加 5 單位的寶石,最高不超過一個月 50 單位的寶石!",
+ "mustSubscribeToPurchaseGems": "Must subscribe to purchase gems with GP",
+ "reachedGoldToGemCap": "You've reached the Gold=>Gem conversion cap <%= convCap %> for this month. We have this to prevent abuse / farming. The cap will reset within the first three days of next month.",
"retainHistory": "保留額外的歷史記錄",
"retainHistoryText": "讓已完成的代辦事項以及任務的歷史記錄可以保留更久。",
"doubleDrops": "日常掉寶率上限加倍",
@@ -29,6 +31,7 @@
"manageSub": "按這裡管理訂閱",
"cancelSub": "取消訂閱",
"canceledSubscription": "已取消訂閱",
+ "cancelingSubscription": "Canceling the subscription",
"adminSub": "管理員訂閱",
"morePlans": "更多計畫 即將推出",
"organizationSub": "私人組織",
@@ -73,6 +76,9 @@
"timeTravelersPopover": "我們發現你擁有神秘沙漏,很高興能為你穿越時空!請選擇你想要的寵物、座騎或神秘物品。你可以在這裡觀看看到往昔物品的清單 !如果這些無法滿足你,也許你會對我們時髦的未來風蒸氣龐克物品有興趣?",
"timeTravelersAlreadyOwned": "恭喜!你已擁有時光旅行者目前所供應的所有物品。感謝你對這個網站的支持!",
"mysticHourglassPopover": "神秘沙漏能讓你購買特定的限期物品,像是每個月的神祕物品和世界 Boss 的獎品,取得過去的東西!",
+ "mysterySetNotFound": "Mystery set not found, or set already owned.",
+ "mysteryItemIsEmpty": "Mystery items are empty",
+ "mysteryItemOpened": "Mystery item opened.",
"mysterySet201402": "有翼使者長袍套裝",
"mysterySet201403": "森林行者套裝",
"mysterySet201404": "薄暮蝴蝶套裝",
@@ -99,6 +105,8 @@
"mysterySet201601": "決心冠軍套裝",
"mysterySet201602": "負心者套裝",
"mysterySet201603": "幸運手套套裝",
+ "mysterySet201604": "Leaf Warrior Set",
+ "mysterySet201605": "Marching Bard Set",
"mysterySet301404": "蒸氣龐克標準套裝",
"mysterySet301405": "蒸氣龐克配件套裝",
"mysterySetwondercon": "Wondercon 漫畫展",
@@ -110,9 +118,25 @@
"hourglassBuyItemConfirm": "使用 1 個神祕沙漏購買這個物品嗎?",
"petsAlreadyOwned": "已擁有此寵物。",
"mountsAlreadyOwned": "已擁有此座騎。",
- "typeNotAllowedHourglass": "此物品類型無法用神秘沙漏購買。適用類型:",
+ "typeNotAllowedHourglass": "Item type not supported for purchase with Mystic Hourglass. Allowed types: <%= allowedTypes %>",
"petsNotAllowedHourglass": "此寵物無法使用神秘沙漏購買。",
"mountsNotAllowedHourglass": "此座騎無法使用神秘沙漏購買。",
"hourglassPurchase": "使用神秘沙漏購買了一項物品!",
- "hourglassPurchaseSet": "使用神秘沙漏購買了一組物品!"
+ "hourglassPurchaseSet": "使用神秘沙漏購買了一組物品!",
+ "missingUnsubscriptionCode": "Missing unsubscription code.",
+ "missingSubscription": "User does not have a plan subscription",
+ "missingSubscriptionCode": "Missing subscription code. Possible values: basic_earned, basic_3mo, basic_6mo, google_6mo, basic_12mo.",
+ "cannotDeleteActiveAccount": "You have an active subscription, cancel your plan before deleting your account.",
+ "paymentNotSuccessful": "The payment was not successful",
+ "planNotActive": "The plan hasn't activated yet (due to a PayPal bug). It will begin <%= nextBillingDate %>, after which you can cancel to retain your full benefits",
+ "notAllowedHourglass": "Pet/Mount not available for purchase with Mystic Hourglass.",
+ "readCard": "<%= cardType %> has been read",
+ "cardTypeRequired": "Card type required",
+ "cardTypeNotAllowed": "Unknown card type.",
+ "invalidCoupon": "Invalid coupon code.",
+ "couponUsed": "Coupon code already used.",
+ "noSudoAccess": "You don't have sudo access.",
+ "couponCodeRequired": "The coupon code is required.",
+ "eventRequired": "\"req.params.event\" is required.",
+ "countRequired": "\"req.query.count\" is required."
}
\ No newline at end of file
diff --git a/common/locales/zh_TW/tasks.json b/common/locales/zh_TW/tasks.json
index dfbffbedff..5ff68283bc 100644
--- a/common/locales/zh_TW/tasks.json
+++ b/common/locales/zh_TW/tasks.json
@@ -73,6 +73,7 @@
"clearTags": "清除",
"hideTags": "隱藏標籤",
"showTags": "展開標籤",
+ "toRequired": "You must supply a to value",
"startDate": "開始日",
"startDateHelpTitle": "這個任務的開始日?",
"startDateHelp": "設定這個認識開始有效的日期,在那之前它不會出現在到期清單。",
@@ -88,8 +89,9 @@
"fortifyName": "穩固藥水",
"fortifyPop": "將所有任務調回中性 ( 黃色 ),並將 HP 補滿。",
"fortify": "穩固",
- "fortifyText": "鞏固會將你的任務回復到中性狀態 ( 黃色 ,如同剛新增時),並把你的生命值補滿。把它當成無計可施時的最後王牌!紅色會為進步提供良好的動力,但如果滿目紅色讓你難過,且每天開始時都對你致命,那麼花費寶石來喘口氣吧!",
+ "fortifyText": "Fortify will return all your tasks, except challenge tasks, to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!",
"confirmFortify": "你確定嗎?",
+ "fortifyComplete": "Fortify complete!",
"sureDelete": "你確定要收回 <%= taskType %> 的挑戰 \"<%= taskText %>\" 嗎?",
"streakCoins": "連擊獎勵",
"pushTaskToTop": "按著 ctrl 或 cmd 來置頂。",
@@ -112,5 +114,18 @@
"rewardHelp2": "裝備會影響你的屬性。 (<%= linkStart %>角色 > 屬性<%= linkEnd %>。)",
"rewardHelp3": "在世界事件發生時,特殊的裝備會出現。",
"rewardHelp4": "不知道要怎麼自訂獎勵嗎? 點擊這裡有一些參考範例。",
- "clickForHelp": "點選獲得協助"
+ "clickForHelp": "點選獲得協助",
+ "taskIdRequired": "\"taskId\" must be a valid UUID.",
+ "taskNotFound": "Task not found.",
+ "invalidTaskType": "Task type must be one of \"habit\", \"daily\", \"todo\", \"reward\".",
+ "cantDeleteChallengeTasks": "A task belonging to a challenge can't be deleted.",
+ "checklistOnlyDailyTodo": "Checklists are supported only on dailies and todos",
+ "checklistItemNotFound": "No checklist item was found with given id.",
+ "itemIdRequired": "\"itemId\" must be a valid UUID.",
+ "tagNotFound": "No tag item was found with given id.",
+ "tagIdRequired": "\"tagId\" must be a valid UUID corresponding to a tag belonging to the user.",
+ "positionRequired": "\"position\" is required and must be a number.",
+ "cantMoveCompletedTodo": "Can't move a completed todo.",
+ "directionUpDown": "\"direction\" is required and must be 'up' or 'down'",
+ "alreadyTagged": "The task is already tagged with given tag."
}
\ No newline at end of file
diff --git a/common/script/.eslintrc b/common/script/.eslintrc
new file mode 100644
index 0000000000..0b1303a736
--- /dev/null
+++ b/common/script/.eslintrc
@@ -0,0 +1,6 @@
+{
+ "extends": [
+ "habitrpg/browser",
+ "habitrpg/babel"
+ ]
+}
diff --git a/common/script/constants.js b/common/script/constants.js
index d6e0aa9384..040b968f8f 100644
--- a/common/script/constants.js
+++ b/common/script/constants.js
@@ -1,3 +1,5 @@
export const MAX_HEALTH = 50;
export const MAX_LEVEL = 100;
export const MAX_STAT_POINTS = MAX_LEVEL;
+export const ATTRIBUTES = ['str', 'int', 'per', 'con'];
+export const TAVERN_ID = '00000000-0000-4000-A000-000000000000';
diff --git a/common/script/content/appearance/backgrounds.js b/common/script/content/appearance/backgrounds.js
index cce821d36c..d3efc73fa3 100644
--- a/common/script/content/appearance/backgrounds.js
+++ b/common/script/content/appearance/backgrounds.js
@@ -325,6 +325,34 @@ let backgrounds = {
notes: t('backgroundRainbowsEndNotes'),
},
},
+ backgrounds052016: {
+ beehive: {
+ text: t('backgroundBeehiveText'),
+ notes: t('backgroundBeehiveNotes'),
+ },
+ gazebo: {
+ text: t('backgroundGazeboText'),
+ notes: t('backgroundGazeboNotes'),
+ },
+ tree_roots: {
+ text: t('backgroundTreeRootsText'),
+ notes: t('backgroundTreeRootsNotes'),
+ },
+ },
+ backgrounds062016: {
+ lighthouse_shore: {
+ text: t('backgroundLighthouseShoreText'),
+ notes: t('backgroundLighthouseShoreNotes'),
+ },
+ lilypad: {
+ text: t('backgroundLilypadText'),
+ notes: t('backgroundLilypadNotes'),
+ },
+ waterfall_rock: {
+ text: t('backgroundWaterfallRockText'),
+ notes: t('backgroundWaterfallRockNotes'),
+ },
+ },
};
/* eslint-enable quote-props */
diff --git a/common/script/content/appearance/chair.js b/common/script/content/appearance/chair.js
index ab51bbf6aa..ab52b334ea 100644
--- a/common/script/content/appearance/chair.js
+++ b/common/script/content/appearance/chair.js
@@ -3,4 +3,9 @@ import prefill from './prefill.js';
export default prefill({
none: {},
black: {},
+ blue: {},
+ green: {},
+ pink: {},
+ red: {},
+ yellow: {},
});
diff --git a/common/script/content/gear/sets/armoire.js b/common/script/content/gear/sets/armoire.js
index 5d342db308..d20c933b22 100644
--- a/common/script/content/gear/sets/armoire.js
+++ b/common/script/content/gear/sets/armoire.js
@@ -126,6 +126,22 @@ let armor = {
set: 'basicArcher',
canOwn: ownsItem('armor_armoire_basicArcherArmor'),
},
+ graduateRobe: {
+ text: t('armorArmoireGraduateRobeText'),
+ notes: t('armorArmoireGraduateRobeNotes', { int: 10 }),
+ value: 100,
+ int: 10,
+ set: 'graduate',
+ canOwn: ownsItem('armor_armoire_graduateRobe'),
+ },
+ stripedSwimsuit: {
+ text: t('armorArmoireStripedSwimsuitText'),
+ notes: t('armorArmoireStripedSwimsuitNotes', { con: 13 }),
+ value: 100,
+ con: 13,
+ set: 'seaside',
+ canOwn: ownsItem('armor_armoire_stripedSwimsuit'),
+ },
};
let eyewear = {
@@ -330,6 +346,23 @@ let head = {
set: 'basicArcher',
canOwn: ownsItem('head_armoire_basicArcherCap'),
},
+ graduateCap: {
+ text: t('headArmoireGraduateCapText'),
+ notes: t('headArmoireGraduateCapNotes', { int: 9 }),
+ value: 100,
+ int: 9,
+ set: 'graduate',
+ canOwn: ownsItem('head_armoire_graduateCap'),
+ },
+ greenFloppyHat: {
+ text: t('headArmoireGreenFloppyHatText'),
+ notes: t('headArmoireGreenFloppyHatNotes', { attrs: 8 }),
+ value: 100,
+ per: 8,
+ int: 8,
+ con: 8,
+ canOwn: ownsItem('head_armoire_greenFloppyHat'),
+ },
};
let shield = {
@@ -375,6 +408,21 @@ let shield = {
per: 15,
canOwn: ownsItem('shield_armoire_mysticLamp'),
},
+ floralBouquet: {
+ text: t('shieldArmoireFloralBouquetText'),
+ notes: t('shieldArmoireFloralBouquetNotes', { con: 3 }),
+ value: 100,
+ con: 3,
+ canOwn: ownsItem('shield_armoire_floralBouquet'),
+ },
+ sandyBucket: {
+ text: t('shieldArmoireSandyBucketText'),
+ notes: t('shieldArmoireSandyBucketNotes', { per: 10 }),
+ value: 100,
+ per: 10,
+ set: 'seaside',
+ canOwn: ownsItem('shield_armoire_sandyBucket'),
+ },
};
let headAccessory = {
@@ -521,6 +569,22 @@ let weapon = {
set: 'basicArcher',
canOwn: ownsItem('weapon_armoire_basicLongbow'),
},
+ habiticanDiploma: {
+ text: t('weaponArmoireHabiticanDiplomaText'),
+ notes: t('weaponArmoireHabiticanDiplomaNotes', { int: 11 }),
+ value: 100,
+ int: 11,
+ set: 'graduate',
+ canOwn: ownsItem('weapon_armoire_habiticanDiploma'),
+ },
+ sandySpade: {
+ text: t('weaponArmoireSandySpadeText'),
+ notes: t('weaponArmoireSandySpadeNotes', { str: 10 }),
+ value: 100,
+ str: 10,
+ set: 'seaside',
+ canOwn: ownsItem('weapon_armoire_sandySpade'),
+ },
};
let armoireSet = {
diff --git a/common/script/content/gear/sets/mystery.js b/common/script/content/gear/sets/mystery.js
index 33f40ae47c..de7a0a322f 100644
--- a/common/script/content/gear/sets/mystery.js
+++ b/common/script/content/gear/sets/mystery.js
@@ -109,6 +109,18 @@ let armor = {
mystery: '201603',
value: 0,
},
+ 201604: {
+ text: t('armorMystery201604Text'),
+ notes: t('armorMystery201604Notes'),
+ mystery: '201604',
+ value: 0,
+ },
+ 201605: {
+ text: t('armorMystery201605Text'),
+ notes: t('armorMystery201605Notes'),
+ mystery: '201605',
+ value: 0,
+ },
301404: {
text: t('armorMystery301404Text'),
notes: t('armorMystery301404Notes'),
@@ -292,6 +304,18 @@ let head = {
mystery: '201603',
value: 0,
},
+ 201604: {
+ text: t('headMystery201604Text'),
+ notes: t('headMystery201604Notes'),
+ mystery: '201604',
+ value: 0,
+ },
+ 201605: {
+ text: t('headMystery201605Text'),
+ notes: t('headMystery201605Notes'),
+ mystery: '201605',
+ value: 0,
+ },
301404: {
text: t('headMystery301404Text'),
notes: t('headMystery301404Notes'),
diff --git a/common/script/content/gear/sets/special/index.js b/common/script/content/gear/sets/special/index.js
index 5111eaa793..cad6ae6f97 100644
--- a/common/script/content/gear/sets/special/index.js
+++ b/common/script/content/gear/sets/special/index.js
@@ -62,9 +62,6 @@ let armor = {
notes: t('armorSpecialSpringRogueNotes', { per: 15 }),
value: 90,
per: 15,
- canBuy: () => {
- return true;
- },
},
springWarrior: {
event: EVENTS.spring,
@@ -73,9 +70,6 @@ let armor = {
notes: t('armorSpecialSpringWarriorNotes', { con: 9 }),
value: 90,
con: 9,
- canBuy: () => {
- return true;
- },
},
springMage: {
event: EVENTS.spring,
@@ -84,9 +78,6 @@ let armor = {
notes: t('armorSpecialSpringMageNotes', { int: 9 }),
value: 90,
int: 9,
- canBuy: () => {
- return true;
- },
},
springHealer: {
event: EVENTS.spring,
@@ -95,9 +86,6 @@ let armor = {
notes: t('armorSpecialSpringHealerNotes', { con: 15 }),
value: 90,
con: 15,
- canBuy: () => {
- return true;
- },
},
summerRogue: {
event: EVENTS.summer,
@@ -208,9 +196,6 @@ let armor = {
notes: t('armorSpecialSpring2015RogueNotes', { per: 15 }),
value: 90,
per: 15,
- canBuy: () => {
- return true;
- },
},
spring2015Warrior: {
event: EVENTS.spring2015,
@@ -219,9 +204,6 @@ let armor = {
notes: t('armorSpecialSpring2015WarriorNotes', { con: 9 }),
value: 90,
con: 9,
- canBuy: () => {
- return true;
- },
},
spring2015Mage: {
event: EVENTS.spring2015,
@@ -230,9 +212,6 @@ let armor = {
notes: t('armorSpecialSpring2015MageNotes', { int: 9 }),
value: 90,
int: 9,
- canBuy: () => {
- return true;
- },
},
spring2015Healer: {
event: EVENTS.spring2015,
@@ -241,9 +220,6 @@ let armor = {
notes: t('armorSpecialSpring2015HealerNotes', { con: 15 }),
value: 90,
con: 15,
- canBuy: () => {
- return true;
- },
},
summer2015Rogue: {
event: EVENTS.summer2015,
@@ -457,6 +433,48 @@ let eyewear = {
notes: t('eyewearSpecialSummerWarriorNotes'),
value: 20,
},
+ blackTopFrame: {
+ gearSet: 'glasses',
+ text: t('eyewearSpecialBlackTopFrameText'),
+ notes: t('eyewearSpecialBlackTopFrameNotes'),
+ value: 0,
+ },
+ blueTopFrame: {
+ gearSet: 'glasses',
+ text: t('eyewearSpecialBlueTopFrameText'),
+ notes: t('eyewearSpecialBlueTopFrameNotes'),
+ value: 0,
+ },
+ greenTopFrame: {
+ gearSet: 'glasses',
+ text: t('eyewearSpecialGreenTopFrameText'),
+ notes: t('eyewearSpecialGreenTopFrameNotes'),
+ value: 0,
+ },
+ pinkTopFrame: {
+ gearSet: 'glasses',
+ text: t('eyewearSpecialPinkTopFrameText'),
+ notes: t('eyewearSpecialPinkTopFrameNotes'),
+ value: 0,
+ },
+ redTopFrame: {
+ gearSet: 'glasses',
+ text: t('eyewearSpecialRedTopFrameText'),
+ notes: t('eyewearSpecialRedTopFrameNotes'),
+ value: 0,
+ },
+ whiteTopFrame: {
+ gearSet: 'glasses',
+ text: t('eyewearSpecialWhiteTopFrameText'),
+ notes: t('eyewearSpecialWhiteTopFrameNotes'),
+ value: 0,
+ },
+ yellowTopFrame: {
+ gearSet: 'glasses',
+ text: t('eyewearSpecialYellowTopFrameText'),
+ notes: t('eyewearSpecialYellowTopFrameNotes'),
+ value: 0,
+ },
};
let head = {
@@ -515,9 +533,6 @@ let head = {
notes: t('headSpecialSpringRogueNotes', { per: 9 }),
value: 60,
per: 9,
- canBuy: () => {
- return true;
- },
},
springWarrior: {
event: EVENTS.spring,
@@ -526,9 +541,6 @@ let head = {
notes: t('headSpecialSpringWarriorNotes', { str: 9 }),
value: 60,
str: 9,
- canBuy: () => {
- return true;
- },
},
springMage: {
event: EVENTS.spring,
@@ -537,9 +549,6 @@ let head = {
notes: t('headSpecialSpringMageNotes', { per: 7 }),
value: 60,
per: 7,
- canBuy: () => {
- return true;
- },
},
springHealer: {
event: EVENTS.spring,
@@ -548,9 +557,6 @@ let head = {
notes: t('headSpecialSpringHealerNotes', { int: 7 }),
value: 60,
int: 7,
- canBuy: () => {
- return true;
- },
},
summerRogue: {
event: EVENTS.summer,
@@ -661,9 +667,6 @@ let head = {
notes: t('headSpecialSpring2015RogueNotes', { per: 9 }),
value: 60,
per: 9,
- canBuy: () => {
- return true;
- },
},
spring2015Warrior: {
event: EVENTS.spring2015,
@@ -672,9 +675,6 @@ let head = {
notes: t('headSpecialSpring2015WarriorNotes', { str: 9 }),
value: 60,
str: 9,
- canBuy: () => {
- return true;
- },
},
spring2015Mage: {
event: EVENTS.spring2015,
@@ -683,9 +683,6 @@ let head = {
notes: t('headSpecialSpring2015MageNotes', { per: 7 }),
value: 60,
per: 7,
- canBuy: () => {
- return true;
- },
},
spring2015Healer: {
event: EVENTS.spring2015,
@@ -694,9 +691,6 @@ let head = {
notes: t('headSpecialSpring2015HealerNotes', { int: 7 }),
value: 60,
int: 7,
- canBuy: () => {
- return true;
- },
},
summer2015Rogue: {
event: EVENTS.summer2015,
@@ -847,9 +841,6 @@ let headAccessory = {
text: t('headAccessorySpecialSpringRogueText'),
notes: t('headAccessorySpecialSpringRogueNotes'),
value: 20,
- canBuy: () => {
- return true;
- },
},
springWarrior: {
event: EVENTS.spring,
@@ -857,9 +848,6 @@ let headAccessory = {
text: t('headAccessorySpecialSpringWarriorText'),
notes: t('headAccessorySpecialSpringWarriorNotes'),
value: 20,
- canBuy: () => {
- return true;
- },
},
springMage: {
event: EVENTS.spring,
@@ -867,9 +855,6 @@ let headAccessory = {
text: t('headAccessorySpecialSpringMageText'),
notes: t('headAccessorySpecialSpringMageNotes'),
value: 20,
- canBuy: () => {
- return true;
- },
},
springHealer: {
event: EVENTS.spring,
@@ -877,9 +862,6 @@ let headAccessory = {
text: t('headAccessorySpecialSpringHealerText'),
notes: t('headAccessorySpecialSpringHealerNotes'),
value: 20,
- canBuy: () => {
- return true;
- },
},
spring2015Rogue: {
event: EVENTS.spring2015,
@@ -887,9 +869,6 @@ let headAccessory = {
text: t('headAccessorySpecialSpring2015RogueText'),
notes: t('headAccessorySpecialSpring2015RogueNotes'),
value: 20,
- canBuy: () => {
- return true;
- },
},
spring2015Warrior: {
event: EVENTS.spring2015,
@@ -897,9 +876,6 @@ let headAccessory = {
text: t('headAccessorySpecialSpring2015WarriorText'),
notes: t('headAccessorySpecialSpring2015WarriorNotes'),
value: 20,
- canBuy: () => {
- return true;
- },
},
spring2015Mage: {
event: EVENTS.spring2015,
@@ -907,9 +883,6 @@ let headAccessory = {
text: t('headAccessorySpecialSpring2015MageText'),
notes: t('headAccessorySpecialSpring2015MageNotes'),
value: 20,
- canBuy: () => {
- return true;
- },
},
spring2015Healer: {
event: EVENTS.spring2015,
@@ -917,9 +890,6 @@ let headAccessory = {
text: t('headAccessorySpecialSpring2015HealerText'),
notes: t('headAccessorySpecialSpring2015HealerNotes'),
value: 20,
- canBuy: () => {
- return true;
- },
},
bearEars: {
gearSet: 'animal',
@@ -1080,9 +1050,6 @@ let shield = {
notes: t('shieldSpecialSpringRogueNotes', { str: 8 }),
value: 80,
str: 8,
- canBuy: () => {
- return true;
- },
},
springWarrior: {
event: EVENTS.spring,
@@ -1091,9 +1058,6 @@ let shield = {
notes: t('shieldSpecialSpringWarriorNotes', { con: 7 }),
value: 70,
con: 7,
- canBuy: () => {
- return true;
- },
},
springHealer: {
event: EVENTS.spring,
@@ -1102,9 +1066,6 @@ let shield = {
notes: t('shieldSpecialSpringHealerNotes', { con: 9 }),
value: 70,
con: 9,
- canBuy: () => {
- return true;
- },
},
summerRogue: {
event: EVENTS.summer,
@@ -1137,9 +1098,6 @@ let shield = {
notes: t('shieldSpecialFallRogueNotes', { str: 8 }),
value: 80,
str: 8,
- canBuy: () => {
- return true;
- },
},
fallWarrior: {
event: EVENTS.fall,
@@ -1148,9 +1106,6 @@ let shield = {
notes: t('shieldSpecialFallWarriorNotes', { con: 7 }),
value: 70,
con: 7,
- canBuy: () => {
- return true;
- },
},
fallHealer: {
event: EVENTS.fall,
@@ -1159,9 +1114,6 @@ let shield = {
notes: t('shieldSpecialFallHealerNotes', { con: 9 }),
value: 70,
con: 9,
- canBuy: () => {
- return true;
- },
},
winter2015Rogue: {
event: EVENTS.winter2015,
@@ -1194,9 +1146,6 @@ let shield = {
notes: t('shieldSpecialSpring2015RogueNotes', { str: 8 }),
value: 80,
str: 8,
- canBuy: () => {
- return true;
- },
},
spring2015Warrior: {
event: EVENTS.spring2015,
@@ -1205,9 +1154,6 @@ let shield = {
notes: t('shieldSpecialSpring2015WarriorNotes', { con: 7 }),
value: 70,
con: 7,
- canBuy: () => {
- return true;
- },
},
spring2015Healer: {
event: EVENTS.spring2015,
@@ -1216,9 +1162,6 @@ let shield = {
notes: t('shieldSpecialSpring2015HealerNotes', { con: 9 }),
value: 70,
con: 9,
- canBuy: () => {
- return true;
- },
},
summer2015Rogue: {
event: EVENTS.summer2015,
@@ -1227,9 +1170,6 @@ let shield = {
notes: t('shieldSpecialSummer2015RogueNotes', { str: 8 }),
value: 80,
str: 8,
- canBuy: () => {
- return true;
- },
},
summer2015Warrior: {
event: EVENTS.summer2015,
@@ -1375,9 +1315,6 @@ let weapon = {
notes: t('weaponSpecialSpringRogueNotes', { str: 8 }),
value: 80,
str: 8,
- canBuy: () => {
- return true;
- },
},
springWarrior: {
event: EVENTS.spring,
@@ -1386,9 +1323,6 @@ let weapon = {
notes: t('weaponSpecialSpringWarriorNotes', { str: 15 }),
value: 90,
str: 15,
- canBuy: () => {
- return true;
- },
},
springMage: {
event: EVENTS.spring,
@@ -1399,9 +1333,6 @@ let weapon = {
value: 160,
int: 15,
per: 7,
- canBuy: () => {
- return true;
- },
},
springHealer: {
event: EVENTS.spring,
@@ -1410,9 +1341,6 @@ let weapon = {
notes: t('weaponSpecialSpringHealerNotes', { int: 9 }),
value: 90,
int: 9,
- canBuy: () => {
- return true;
- },
},
summerRogue: {
event: EVENTS.summer,
@@ -1523,9 +1451,6 @@ let weapon = {
notes: t('weaponSpecialSpring2015RogueNotes', { str: 8 }),
value: 80,
str: 8,
- canBuy: () => {
- return true;
- },
},
spring2015Warrior: {
event: EVENTS.spring2015,
@@ -1534,9 +1459,6 @@ let weapon = {
notes: t('weaponSpecialSpring2015WarriorNotes', { str: 15 }),
value: 90,
str: 15,
- canBuy: () => {
- return true;
- },
},
spring2015Mage: {
event: EVENTS.spring2015,
@@ -1547,9 +1469,6 @@ let weapon = {
value: 160,
int: 15,
per: 7,
- canBuy: () => {
- return true;
- },
},
spring2015Healer: {
event: EVENTS.spring2015,
@@ -1558,9 +1477,6 @@ let weapon = {
notes: t('weaponSpecialSpring2015HealerNotes', { int: 9 }),
value: 90,
int: 9,
- canBuy: () => {
- return true;
- },
},
summer2015Rogue: {
event: EVENTS.summer2015,
diff --git a/common/script/content/index.js b/common/script/content/index.js
index fc283e9442..9cdc643023 100644
--- a/common/script/content/index.js
+++ b/common/script/content/index.js
@@ -378,7 +378,21 @@ api.questEggs = {
adjective: t('questEggSnailAdjective'),
canBuy: (function(u) {
return u.achievements.quests && (u.achievements.quests.snail != null) > 0;
- })
+ }),
+ },
+ Falcon: {
+ text: t('questEggFalconText'),
+ adjective: t('questEggFalconAdjective'),
+ canBuy: (function(u) {
+ return u.achievements.quests && (u.achievements.quests.falcon != null) > 0;
+ }),
+ },
+ Treeling: {
+ text: t('questEggTreelingText'),
+ adjective: t('questEggTreelingAdjective'),
+ canBuy: (function(u) {
+ return u.achievements.quests && (u.achievements.quests.treeling != null) > 0;
+ }),
},
};
@@ -411,7 +425,8 @@ api.specialPets = {
'Tiger-Veteran': 'veteranTiger',
'Phoenix-Base': 'phoenix',
'Turkey-Gilded': 'gildedTurkey',
- 'Bumblebee-Base': 'bumblebee',
+ 'MagicalBee-Base': 'magicalBee',
+ 'Lion-Veteran': 'veteranLion',
};
api.specialMounts = {
@@ -424,7 +439,7 @@ api.specialMounts = {
'Gryphon-RoyalPurple': 'royalPurpleGryphon',
'Phoenix-Base': 'phoenix',
'JackOLantern-Base': 'jackolantern',
- 'Bumblebee-Base': 'bumblebee',
+ 'MagicalBee-Base': 'magicalBee',
};
api.timeTravelStable = {
@@ -499,7 +514,15 @@ api.premiumHatchingPotions = {
canBuy: (function() {
return false;
})
- }
+ },
+ Floral: {
+ value: 2,
+ text: t('hatchingPotionFloral'),
+ limited: true,
+ canBuy: (function() {
+ return false;
+ }),
+ },
};
_.each(api.dropHatchingPotions, function(pot, key) {
@@ -579,6 +602,14 @@ api.questMounts = _.transform(api.questEggs, function(m, egg) {
}));
});
+api.premiumMounts = _.transform(api.dropEggs, function(m, egg) {
+ return _.defaults(m, _.transform(api.hatchingPotions, function(m2, pot) {
+ if (pot.premium) {
+ return m2[egg.key + "-" + pot.key] = true;
+ }
+ }));
+});
+
api.food = {
Meat: {
text: t('foodMeat'),
@@ -2557,11 +2588,11 @@ api.quests = {
items: [
{
type: 'pets',
- key: 'Bumblebee-Base',
+ key: 'MagicalBee-Base',
text: t('questBewilderDropBumblebeePet')
}, {
type: 'mounts',
- key: 'Bumblebee-Base',
+ key: 'MagicalBee-Base',
text: t('questBewilderDropBumblebeeMount')
}, {
type: 'food',
@@ -2609,6 +2640,70 @@ api.quests = {
exp: 0,
},
},
+ falcon: {
+ text: t('questFalconText'),
+ notes: t('questFalconNotes'),
+ completion: t('questFalconCompletion'),
+ value: 4,
+ category: 'pet',
+ boss: {
+ name: t('questFalconBoss'),
+ hp: 700,
+ str: 2,
+ },
+ drop: {
+ items: [
+ {
+ type: 'eggs',
+ key: 'Falcon',
+ text: t('questFalconDropFalconEgg'),
+ }, {
+ type: 'eggs',
+ key: 'Falcon',
+ text: t('questFalconDropFalconEgg'),
+ }, {
+ type: 'eggs',
+ key: 'Falcon',
+ text: t('questFalconDropFalconEgg'),
+ }
+ ],
+ gp: 49,
+ exp: 425,
+ unlock: t('questFalconUnlockText'),
+ },
+ },
+ treeling: {
+ text: t('questTreelingText'),
+ notes: t('questTreelingNotes'),
+ completion: t('questTreelingCompletion'),
+ value: 4,
+ category: 'pet',
+ boss: {
+ name: t('questTreelingBoss'),
+ hp: 600,
+ str: 1.5,
+ },
+ drop: {
+ items: [
+ {
+ type: 'eggs',
+ key: 'Treeling',
+ text: t('questTreelingDropTreelingEgg'),
+ }, {
+ type: 'eggs',
+ key: 'Treeling',
+ text: t('questTreelingDropTreelingEgg'),
+ }, {
+ type: 'eggs',
+ key: 'Treeling',
+ text: t('questTreelingDropTreelingEgg'),
+ }
+ ],
+ gp: 43,
+ exp: 350,
+ unlock: t('questTreelingUnlockText'),
+ },
+ },
};
_.each(api.quests, function(v, key) {
diff --git a/common/script/content/mystery-sets.js b/common/script/content/mystery-sets.js
index 841727a713..8eae5a9392 100644
--- a/common/script/content/mystery-sets.js
+++ b/common/script/content/mystery-sets.js
@@ -106,6 +106,14 @@ let mysterySets = {
start: '2016-03-24',
end: '2016-04-02',
},
+ 201604: {
+ start: '2016-04-25',
+ end: '2016-05-02',
+ },
+ 201605: {
+ start: '2016-05-25',
+ end: '2016-06-02',
+ },
301404: {
start: '3014-03-24',
end: '3014-04-02',
diff --git a/common/script/content/spells.js b/common/script/content/spells.js
index 3150bd0da9..9b0514974d 100644
--- a/common/script/content/spells.js
+++ b/common/script/content/spells.js
@@ -1,6 +1,6 @@
import t from './translation';
import _ from 'lodash';
-
+import { NotAuthorized } from '../libs/errors';
/*
---------------------------------------------------------------
Spells
@@ -15,7 +15,7 @@ import _ from 'lodash';
web, this function can be performed on the client and on the server. `user` param is self (needed for determining your
own stats for effectiveness of cast), and `target` param is one of [task, party, user]. In the case of `self` spells,
you act on `user` instead of `target`. You can trust these are the correct objects, as long as the `target` attr of the
- spell is correct. Take a look at habitrpg/src/models/user.js and habitrpg/src/models/task.js for what attributes are
+ spell is correct. Take a look at habitrpg/website/server/models/user.js and habitrpg/website/server/models/task.js for what attributes are
available on each model. Note `task.value` is its "redness". If party is passed in, it's an array of users,
so you'll want to iterate over them like: `_.each(target,function(member){...})`
@@ -40,14 +40,12 @@ spells.wizard = {
lvl: 11,
target: 'task',
notes: t('spellWizardFireballNotes'),
- cast (user, target) {
+ cast (user, target, req) {
let bonus = user._statsComputed.int * user.fns.crit('per');
bonus *= Math.ceil((target.value < 0 ? 1 : target.value + 1) * 0.075);
user.stats.exp += diminishingReturns(bonus, 75);
if (!user.party.quest.progress) user.party.quest.progress = 0;
user.party.quest.progress.up += Math.ceil(user._statsComputed.int * 0.1);
- // TODO change, pass req to spell?
- let req = {language: user.preferences.language};
user.fns.updateStats(user.stats, req);
},
},
@@ -166,12 +164,11 @@ spells.rogue = {
lvl: 12,
target: 'task',
notes: t('spellRogueBackStabNotes'),
- cast (user, target) {
+ cast (user, target, req) {
let _crit = user.fns.crit('str', 0.3);
let bonus = calculateBonus(target.value, user._statsComputed.str, _crit);
user.stats.exp += diminishingReturns(bonus, 75, 50);
user.stats.gp += diminishingReturns(bonus, 18, 75);
- let req = {language: user.preferences.language};
user.fns.updateStats(user.stats, req);
},
},
@@ -197,7 +194,7 @@ spells.rogue = {
notes: t('spellRogueStealthNotes'),
cast (user) {
if (!user.stats.buffs.stealth) user.stats.buffs.stealth = 0;
- user.stats.buffs.stealth += Math.ceil(diminishingReturns(user._statsComputed.per, user.dailys.length * 0.64, 55));
+ user.stats.buffs.stealth += Math.ceil(diminishingReturns(user._statsComputed.per, user.tasksOrder.dailys.length * 0.64, 55));
},
},
};
@@ -218,10 +215,10 @@ spells.healer = {
text: t('spellHealerBrightnessText'),
mana: 15,
lvl: 12,
- target: 'self',
+ target: 'tasks',
notes: t('spellHealerBrightnessNotes'),
- cast (user) {
- _.each(user.tasks, (task) => {
+ cast (user, tasks) {
+ _.each(tasks, (task) => {
if (task.type !== 'reward') {
task.value += 4 * (user._statsComputed.int / (user._statsComputed.int + 40));
}
@@ -242,7 +239,7 @@ spells.healer = {
});
},
},
- heallAll: { // Blessing
+ healAll: { // Blessing
text: t('spellHealerHealAllText'),
mana: 25,
lvl: 14,
@@ -262,11 +259,13 @@ spells.special = {
text: t('spellSpecialSnowballAuraText'),
mana: 0,
value: 15,
+ previousPurchase: true,
target: 'user',
notes: t('spellSpecialSnowballAuraNotes'),
- cast (user, target) {
+ cast (user, target, req) {
+ if (!user.items.special.snowball) throw new NotAuthorized(t('spellNotOwned')(req.language));
target.stats.buffs.snowball = true;
- target.stats.buffs.spookDust = false;
+ target.stats.buffs.spookySparkles = false;
target.stats.buffs.shinySeed = false;
target.stats.buffs.seafoam = false;
if (!target.achievements.snowball) target.achievements.snowball = 0;
@@ -286,20 +285,22 @@ spells.special = {
user.stats.gp -= 5;
},
},
- spookDust: {
- text: t('spellSpecialSpookDustText'),
+ spookySparkles: {
+ text: t('spellSpecialSpookySparklesText'),
mana: 0,
value: 15,
+ previousPurchase: true,
target: 'user',
- notes: t('spellSpecialSpookDustNotes'),
- cast (user, target) {
+ notes: t('spellSpecialSpookySparklesNotes'),
+ cast (user, target, req) {
+ if (!user.items.special.spookySparkles) throw new NotAuthorized(t('spellNotOwned')(req.language));
target.stats.buffs.snowball = false;
- target.stats.buffs.spookDust = true;
+ target.stats.buffs.spookySparkles = true;
target.stats.buffs.shinySeed = false;
target.stats.buffs.seafoam = false;
- if (!target.achievements.spookDust) target.achievements.spookDust = 0;
- target.achievements.spookDust++;
- user.items.special.spookDust--;
+ if (!target.achievements.spookySparkles) target.achievements.spookySparkles = 0;
+ target.achievements.spookySparkles++;
+ user.items.special.spookySparkles--;
},
},
opaquePotion: {
@@ -310,7 +311,7 @@ spells.special = {
target: 'self',
notes: t('spellSpecialOpaquePotionNotes'),
cast (user) {
- user.stats.buffs.spookDust = false;
+ user.stats.buffs.spookySparkles = false;
user.stats.gp -= 5;
},
},
@@ -318,11 +319,13 @@ spells.special = {
text: t('spellSpecialShinySeedText'),
mana: 0,
value: 15,
+ previousPurchase: true,
target: 'user',
notes: t('spellSpecialShinySeedNotes'),
- cast (user, target) {
+ cast (user, target, req) {
+ if (!user.items.special.shinySeed) throw new NotAuthorized(t('spellNotOwned')(req.language));
target.stats.buffs.snowball = false;
- target.stats.buffs.spookDust = false;
+ target.stats.buffs.spookySparkles = false;
target.stats.buffs.shinySeed = true;
target.stats.buffs.seafoam = false;
if (!target.achievements.shinySeed) target.achievements.shinySeed = 0;
@@ -346,11 +349,13 @@ spells.special = {
text: t('spellSpecialSeafoamText'),
mana: 0,
value: 15,
+ previousPurchase: true,
target: 'user',
notes: t('spellSpecialSeafoamNotes'),
- cast (user, target) {
+ cast (user, target, req) {
+ if (!user.items.special.seafoam) throw new NotAuthorized(t('spellNotOwned')(req.language));
target.stats.buffs.snowball = false;
- target.stats.buffs.spookDust = false;
+ target.stats.buffs.spookySparkles = false;
target.stats.buffs.shinySeed = false;
target.stats.buffs.seafoam = true;
if (!target.achievements.seafoam) target.achievements.seafoam = 0;
@@ -391,7 +396,10 @@ spells.special = {
if (!target.items.special.nyeReceived) target.items.special.nyeReceived = [];
target.items.special.nyeReceived.push(user.profile.name);
+
+ if (!target.flags) target.flags = {};
target.flags.cardReceived = true;
+
user.stats.gp -= 10;
},
},
@@ -416,7 +424,10 @@ spells.special = {
if (!target.items.special.valentineReceived) target.items.special.valentineReceived = [];
target.items.special.valentineReceived.push(user.profile.name);
+
+ if (!target.flags) target.flags = {};
target.flags.cardReceived = true;
+
user.stats.gp -= 10;
},
},
@@ -440,7 +451,10 @@ spells.special = {
if (!target.items.special.greetingReceived) target.items.special.greetingReceived = [];
target.items.special.greetingReceived.push(user.profile.name);
+
+ if (!target.flags) target.flags = {};
target.flags.cardReceived = true;
+
user.stats.gp -= 10;
},
},
@@ -465,7 +479,10 @@ spells.special = {
if (!target.items.special.thankyouReceived) target.items.special.thankyouReceived = [];
target.items.special.thankyouReceived.push(user.profile.name);
+
+ if (!target.flags) target.flags = {};
target.flags.cardReceived = true;
+
user.stats.gp -= 10;
},
},
@@ -487,9 +504,13 @@ spells.special = {
u.achievements.birthday++;
});
}
+
if (!target.items.special.birthdayReceived) target.items.special.birthdayReceived = [];
target.items.special.birthdayReceived.push(user.profile.name);
+
+ if (!target.flags) target.flags = {};
target.flags.cardReceived = true;
+
user.stats.gp -= 10;
},
},
@@ -499,8 +520,8 @@ _.each(spells, (spellClass) => {
_.each(spellClass, (spell, key) => {
spell.key = key;
let _cast = spell.cast;
- spell.cast = function castSpell (user, target) {
- _cast(user, target);
+ spell.cast = function castSpell (user, target, req) {
+ _cast(user, target, req);
user.stats.mp -= spell.mana;
};
});
diff --git a/common/script/count.js b/common/script/count.js
index 4572e29dd8..3b4c303b3b 100644
--- a/common/script/count.js
+++ b/common/script/count.js
@@ -8,7 +8,7 @@ import content from './content/index';
const DROP_ANIMALS = keys(content.pets);
-function beastMasterProgress (pets) {
+function beastMasterProgress (pets = {}) {
let count = 0;
each(DROP_ANIMALS, (animal) => {
@@ -19,7 +19,7 @@ function beastMasterProgress (pets) {
return count;
}
-function dropPetsCurrentlyOwned (pets) {
+function dropPetsCurrentlyOwned (pets = {}) {
let count = 0;
each(DROP_ANIMALS, (animal) => {
@@ -30,7 +30,7 @@ function dropPetsCurrentlyOwned (pets) {
return count;
}
-function mountMasterProgress (mounts) {
+function mountMasterProgress (mounts = {}) {
let count = 0;
each(DROP_ANIMALS, (animal) => {
@@ -41,7 +41,7 @@ function mountMasterProgress (mounts) {
return count;
}
-function remainingGearInSet (userGear, set) {
+function remainingGearInSet (userGear = {}, set) {
let gear = filter(content.gear.flat, (item) => {
let setMatches = item.klass === set;
let hasItem = userGear[item.key];
@@ -54,7 +54,7 @@ function remainingGearInSet (userGear, set) {
return count;
}
-function questsOfCategory (userQuests, category) {
+function questsOfCategory (userQuests = {}, category) {
let quests = filter(content.quests, (quest) => {
let categoryMatches = quest.category === category;
let hasQuest = userQuests[quest.key];
diff --git a/common/script/cron.js b/common/script/cron.js
index 2c0760fcc9..a0f28f9d5a 100644
--- a/common/script/cron.js
+++ b/common/script/cron.js
@@ -1,3 +1,4 @@
+// TODO what can be moved to /website/server?
/*
------------------------------------------------------
Cron and time / day functions
diff --git a/common/script/fns/autoAllocate.js b/common/script/fns/autoAllocate.js
index ab037e628b..71a4898031 100644
--- a/common/script/fns/autoAllocate.js
+++ b/common/script/fns/autoAllocate.js
@@ -7,50 +7,69 @@ import splitWhitespace from '../libs/splitWhitespace';
{update} if aggregated changes, pass in userObj as update. otherwise commits will be made immediately
*/
-module.exports = function(user) {
- return user.stats[(function() {
- var diff, ideal, lvlDiv7, preference, stats, suggested;
- switch (user.preferences.allocationMode) {
- case "flat":
- stats = _.pick(user.stats, splitWhitespace('con str per int'));
- return _.invert(stats)[_.min(stats)];
- case "classbased":
- lvlDiv7 = user.stats.lvl / 7;
- ideal = [lvlDiv7 * 3, lvlDiv7 * 2, lvlDiv7, lvlDiv7];
- preference = (function() {
- switch (user.stats["class"]) {
- case "wizard":
- return ["int", "per", "con", "str"];
- case "rogue":
- return ["per", "str", "int", "con"];
- case "healer":
- return ["con", "int", "str", "per"];
- default:
- return ["str", "con", "per", "int"];
- }
- })();
- diff = [user.stats[preference[0]] - ideal[0], user.stats[preference[1]] - ideal[1], user.stats[preference[2]] - ideal[2], user.stats[preference[3]] - ideal[3]];
- suggested = _.findIndex(diff, (function(val) {
- if (val === _.min(diff)) {
- return true;
- }
- }));
- if (~suggested) {
- return preference[suggested];
- } else {
- return "str";
- }
- case "taskbased":
- suggested = _.invert(user.stats.training)[_.max(user.stats.training)];
- _.merge(user.stats.training, {
- str: 0,
- int: 0,
- con: 0,
- per: 0
- });
- return suggested || "str";
- default:
- return "str";
+function getStatToAllocate (user) {
+ let suggested;
+
+ let statsObj = user.stats.toObject ? user.stats.toObject() : user.stats;
+
+ switch (user.preferences.allocationMode) {
+ case 'flat': {
+ let stats = _.pick(statsObj, splitWhitespace('con str per int'));
+ return _.invert(stats)[_.min(stats)];
}
- })()]++;
+ case 'classbased': {
+ let lvlDiv7 = statsObj.lvl / 7;
+ let ideal = [lvlDiv7 * 3, lvlDiv7 * 2, lvlDiv7, lvlDiv7];
+
+ let preference;
+ switch (statsObj.class) {
+ case 'wizard': {
+ preference = ['int', 'per', 'con', 'str'];
+ break;
+ }
+ case 'rogue': {
+ preference = ['per', 'str', 'int', 'con'];
+ break;
+ }
+ case 'healer': {
+ preference = ['con', 'int', 'str', 'per'];
+ break;
+ }
+ default: {
+ preference = ['str', 'con', 'per', 'int'];
+ }
+ }
+
+ let diff = [
+ statsObj[preference[0]] - ideal[0],
+ statsObj[preference[1]] - ideal[1],
+ statsObj[preference[2]] - ideal[2],
+ statsObj[preference[3]] - ideal[3],
+ ];
+
+ suggested = _.findIndex(diff, (val) => {
+ if (val === _.min(diff)) return true;
+ });
+
+ return suggested !== -1 ? preference[suggested] : 'str';
+ }
+ case 'taskbased': {
+ suggested = _.invert(statsObj.training)[_.max(statsObj.training)];
+
+ let training = statsObj.training;
+ training.str = 0;
+ training.int = 0;
+ training.con = 0;
+ training.per = 0;
+
+ return suggested || 'str';
+ }
+ default: {
+ return 'str';
+ }
+ }
+}
+
+module.exports = function autoAllocate (user) {
+ return user.stats[getStatToAllocate(user)]++;
};
diff --git a/common/script/fns/crit.js b/common/script/fns/crit.js
index 69ac9e5b93..65a0cbb062 100644
--- a/common/script/fns/crit.js
+++ b/common/script/fns/crit.js
@@ -1,13 +1,8 @@
-module.exports = function(user, stat, chance) {
- var s;
- if (stat == null) {
- stat = 'str';
- }
- if (chance == null) {
- chance = .03;
- }
- s = user._statsComputed[stat];
- if (user.fns.predictableRandom() <= chance * (1 + s / 100)) {
+import predictableRandom from './predictableRandom';
+
+module.exports = function crit (user, stat = 'str', chance = 0.03) {
+ let s = user._statsComputed[stat];
+ if (predictableRandom(user) <= chance * (1 + s / 100)) {
return 1.5 + 4 * s / (s + 200);
} else {
return 1;
diff --git a/common/script/fns/cron.js b/common/script/fns/cron.js
deleted file mode 100644
index 1e3470d7f9..0000000000
--- a/common/script/fns/cron.js
+++ /dev/null
@@ -1,358 +0,0 @@
-import moment from 'moment';
-import _ from 'lodash';
-import {
- daysSince,
- shouldDo,
-} from '../cron';
-import {
- capByLevel,
- toNextLevel,
-} from '../statHelpers';
-/*
- ------------------------------------------------------
- Cron
- ------------------------------------------------------
- */
-
-/*
- At end of day, add value to all incomplete Daily & Todo tasks (further incentive)
- For incomplete Dailys, deduct experience
- Make sure to run this function once in a while as server will not take care of overnight calculations.
- And you have to run it every time client connects.
- {user}
- */
-
-module.exports = function(user, options) {
- var _progress, analyticsData, base, base1, base2, base3, base4, clearBuffs, dailyChecked, dailyDueUnchecked, daysMissed, expTally, lvl, lvlDiv2, multiDaysCountAsOneDay, now, perfect, plan, progress, ref, ref1, ref2, ref3, todoTally, timezoneOffsetFromUserPrefs, timezoneOffsetFromBrowser, timezoneOffsetAtLastCron;
- if (options == null) {
- options = {};
- }
- now = +options.now || +(new Date);
-
- // If the user's timezone has changed (due to travel or daylight savings),
- // cron can be triggered twice in one day, so we check for that and use
- // both timezones to work out if cron should run.
- // CDS = Custom Day Start time.
- timezoneOffsetFromUserPrefs = user.preferences.timezoneOffset || 0;
- timezoneOffsetAtLastCron = (_.isFinite(user.preferences.timezoneOffsetAtLastCron)) ? user.preferences.timezoneOffsetAtLastCron : timezoneOffsetFromUserPrefs;
- timezoneOffsetFromBrowser = (_.isFinite(+options.timezoneOffset)) ? +options.timezoneOffset : timezoneOffsetFromUserPrefs;
- // NB: All timezone offsets can be 0, so can't use `... || ...` to apply non-zero defaults
-
- if (timezoneOffsetFromBrowser !== timezoneOffsetFromUserPrefs) {
- // The user's browser has just told Habitica that the user's timezone has
- // changed so store and use the new zone.
- user.preferences.timezoneOffset = timezoneOffsetFromBrowser;
- timezoneOffsetFromUserPrefs = timezoneOffsetFromBrowser;
- }
-
- // How many days have we missed using the user's current timezone:
- daysMissed = daysSince(user.lastCron, _.defaults({
- now: now
- }, user.preferences));
-
- if (timezoneOffsetAtLastCron != timezoneOffsetFromUserPrefs) {
- // Since cron last ran, the user's timezone has changed.
- // How many days have we missed using the old timezone:
- let daysMissedNewZone = daysMissed;
- let daysMissedOldZone = daysSince(user.lastCron, _.defaults({
- now: now,
- timezoneOffsetOverride: timezoneOffsetAtLastCron,
- }, user.preferences));
-
- if (timezoneOffsetAtLastCron < timezoneOffsetFromUserPrefs) {
- // The timezone change was in the unsafe direction.
- // E.g., timezone changes from UTC+1 (offset -60) to UTC+0 (offset 0).
- // or timezone changes from UTC-4 (offset 240) to UTC-5 (offset 300).
- // Local time changed from, for example, 03:00 to 02:00.
-
- if (daysMissedOldZone > 0 && daysMissedNewZone > 0) {
- // Both old and new timezones indicate that we SHOULD run cron, so
- // it is safe to do so immediately.
- daysMissed = Math.min(daysMissedOldZone, daysMissedNewZone);
- // use minimum value to be nice to user
- }
- else if (daysMissedOldZone > 0) {
- // The old timezone says that cron should run; the new timezone does not.
- // This should be impossible for this direction of timezone change, but
- // just in case I'm wrong...
- console.log("zone has changed - old zone says run cron, NEW zone says no - stop cron now only -- SHOULD NOT HAVE GOT TO HERE", timezoneOffsetAtLastCron, timezoneOffsetFromUserPrefs, now); // used in production for confirming this never happens
- }
- else if (daysMissedNewZone > 0) {
- // The old timezone says that cron should NOT run -- i.e., cron has
- // already run today, from the old timezone's point of view.
- // The new timezone says that cron SHOULD run, but this is almost
- // certainly incorrect.
- // This happens when cron occurred at a time soon after the CDS. When
- // you reinterpret that time in the new timezone, it looks like it
- // was before the CDS, because local time has stepped backwards.
- // To fix this, rewrite the cron time to a time that the new
- // timezone interprets as being in today.
-
- daysMissed = 0; // prevent cron running now
- let timezoneOffsetDiff = timezoneOffsetAtLastCron - timezoneOffsetFromUserPrefs;
- // e.g., for dangerous zone change: 240 - 300 = -60 or -660 - -600 = -60
-
- user.lastCron = moment(user.lastCron).subtract(timezoneOffsetDiff, 'minutes');
- // NB: We don't change user.auth.timestamps.loggedin so that will still record the time that the previous cron actually ran.
- // From now on we can ignore the old timezone:
- user.preferences.timezoneOffsetAtLastCron = timezoneOffsetFromUserPrefs;
- }
- else {
- // Both old and new timezones indicate that cron should
- // NOT run.
- daysMissed = 0; // prevent cron running now
- }
- }
- else if (timezoneOffsetAtLastCron > timezoneOffsetFromUserPrefs) {
- daysMissed = daysMissedNewZone;
- // TODO: Either confirm that there is nothing that could possibly go wrong here and remove the need for this else branch, or fix stuff. There are probably situations where the Dailies do not reset early enough for a user who was expecting the zone change and wants to use all their Dailies immediately in the new zone; if so, we should provide an option for easy reset of Dailies (can't be automatic because there will be other situations where the user was not prepared).
- }
- }
-
- if (!(daysMissed > 0)) {
- return;
- }
- user.auth.timestamps.loggedin = new Date();
- user.lastCron = now;
- user.preferences.timezoneOffsetAtLastCron = timezoneOffsetFromUserPrefs;
- if (user.items.lastDrop.count > 0) {
- user.items.lastDrop.count = 0;
- }
- perfect = true;
- clearBuffs = {
- str: 0,
- int: 0,
- per: 0,
- con: 0,
- stealth: 0,
- streaks: false
- };
- plan = (ref = user.purchased) != null ? ref.plan : void 0;
- if (plan != null ? plan.customerId : void 0) {
- if (typeof plan.dateUpdated === "undefined") {
- // partial compensation for bug in subscription creation - https://github.com/HabitRPG/habitrpg/issues/6682
- plan.dateUpdated = new Date();
- }
- if (moment(plan.dateUpdated).format('MMYYYY') !== moment().format('MMYYYY')) {
- plan.gemsBought = 0;
- plan.dateUpdated = new Date();
- _.defaults(plan.consecutive, {
- count: 0,
- offset: 0,
- trinkets: 0,
- gemCapExtra: 0
- });
- plan.consecutive.count++;
- if (plan.consecutive.offset > 0) {
- plan.consecutive.offset--;
- } else if (plan.consecutive.count % 3 === 0) {
- plan.consecutive.trinkets++;
- plan.consecutive.gemCapExtra += 5;
- if (plan.consecutive.gemCapExtra > 25) {
- plan.consecutive.gemCapExtra = 25;
- }
- }
- }
- if (plan.dateTerminated && moment(plan.dateTerminated).isBefore(+(new Date))) {
- _.merge(plan, {
- planId: null,
- customerId: null,
- paymentMethod: null
- });
- _.merge(plan.consecutive, {
- count: 0,
- offset: 0,
- gemCapExtra: 0
- });
- if (typeof user.markModified === "function") {
- user.markModified('purchased.plan');
- }
- }
- }
- if (user.preferences.sleep === true) {
- user.stats.buffs = clearBuffs;
- user.dailys.forEach(function(daily) {
- var completed, repeat, thatDay;
- completed = daily.completed, repeat = daily.repeat;
- thatDay = moment(now).subtract({
- days: 1
- });
- if (shouldDo(thatDay.toDate(), daily, user.preferences) || completed) {
- _.each(daily.checklist, (function(box) {
- box.completed = false;
- return true;
- }));
- }
- return daily.completed = false;
- });
- return;
- }
- multiDaysCountAsOneDay = true;
- todoTally = 0;
- user.todos.forEach(function(task) {
- var absVal, completed, delta, id;
- if (!task) {
- return;
- }
- id = task.id, completed = task.completed;
- delta = user.ops.score({
- params: {
- id: task.id,
- direction: 'down'
- },
- query: {
- times: multiDaysCountAsOneDay != null ? multiDaysCountAsOneDay : {
- 1: daysMissed
- },
- cron: true
- }
- });
- absVal = completed ? Math.abs(task.value) : task.value;
- return todoTally += absVal;
- });
- dailyChecked = 0;
- dailyDueUnchecked = 0;
- if ((base = user.party.quest.progress).down == null) {
- base.down = 0;
- }
- user.dailys.forEach(function(task) {
- var EvadeTask, completed, delta, fractionChecked, id, j, n, ref1, ref2, scheduleMisses, thatDay;
- if (!task) {
- return;
- }
- id = task.id, completed = task.completed;
- EvadeTask = 0;
- scheduleMisses = daysMissed;
- if (completed) {
- dailyChecked += 1;
- } else {
- scheduleMisses = 0;
- for (n = j = 0, ref1 = daysMissed; 0 <= ref1 ? j < ref1 : j > ref1; n = 0 <= ref1 ? ++j : --j) {
- thatDay = moment(now).subtract({
- days: n + 1
- });
- if (shouldDo(thatDay.toDate(), task, user.preferences)) {
- scheduleMisses++;
- if (user.stats.buffs.stealth) {
- user.stats.buffs.stealth--;
- EvadeTask++;
- }
- if (multiDaysCountAsOneDay) {
- break;
- }
- }
- }
- if (scheduleMisses > EvadeTask) {
- perfect = false;
- if (((ref2 = task.checklist) != null ? ref2.length : void 0) > 0) {
- fractionChecked = _.reduce(task.checklist, (function(m, i) {
- return m + (i.completed ? 1 : 0);
- }), 0) / task.checklist.length;
- dailyDueUnchecked += 1 - fractionChecked;
- dailyChecked += fractionChecked;
- } else {
- dailyDueUnchecked += 1;
- }
- delta = user.ops.score({
- params: {
- id: task.id,
- direction: 'down'
- },
- query: {
- times: multiDaysCountAsOneDay != null ? multiDaysCountAsOneDay : {
- 1: scheduleMisses - EvadeTask
- },
- cron: true
- }
- });
- user.party.quest.progress.down += delta * (task.priority < 1 ? task.priority : 1);
- }
- }
- (task.history != null ? task.history : task.history = []).push({
- date: +(new Date),
- value: task.value
- });
- task.completed = false;
- if (completed || (scheduleMisses > 0)) {
- return _.each(task.checklist, (function(i) {
- i.completed = false;
- return true;
- }));
- }
- });
- user.habits.forEach(function(task) {
- if (task.up === false || task.down === false) {
- if (Math.abs(task.value) < 0.1) {
- return task.value = 0;
- } else {
- return task.value = task.value / 2;
- }
- }
- });
- ((base1 = (user.history != null ? user.history : user.history = {})).todos != null ? base1.todos : base1.todos = []).push({
- date: now,
- value: todoTally
- });
- expTally = user.stats.exp;
- lvl = 0;
- while (lvl < (user.stats.lvl - 1)) {
- lvl++;
- expTally += toNextLevel(lvl);
- }
- ((base2 = user.history).exp != null ? base2.exp : base2.exp = []).push({
- date: now,
- value: expTally
- });
- if (!((ref1 = user.purchased) != null ? (ref2 = ref1.plan) != null ? ref2.customerId : void 0 : void 0)) {
- user.fns.preenUserHistory();
- if (typeof user.markModified === "function") {
- user.markModified('history');
- }
- if (typeof user.markModified === "function") {
- user.markModified('dailys');
- }
- }
- user.stats.buffs = perfect ? ((base3 = user.achievements).perfect != null ? base3.perfect : base3.perfect = 0, user.achievements.perfect++, lvlDiv2 = Math.ceil(capByLevel(user.stats.lvl) / 2), {
- str: lvlDiv2,
- int: lvlDiv2,
- per: lvlDiv2,
- con: lvlDiv2,
- stealth: 0,
- streaks: false
- }) : clearBuffs;
- if (dailyDueUnchecked === 0 && dailyChecked === 0) {
- dailyChecked = 1;
- }
- user.stats.mp += _.max([10, .1 * user._statsComputed.maxMP]) * dailyChecked / (dailyDueUnchecked + dailyChecked);
- if (user.stats.mp > user._statsComputed.maxMP) {
- user.stats.mp = user._statsComputed.maxMP;
- }
- progress = user.party.quest.progress;
- _progress = _.cloneDeep(progress);
- _.merge(progress, {
- down: 0,
- up: 0
- });
- progress.collect = _.transform(progress.collect, (function(m, v, k) {
- return m[k] = 0;
- }));
- if ((base4 = user.flags).cronCount == null) {
- base4.cronCount = 0;
- }
- user.flags.cronCount++;
- analyticsData = {
- category: 'behavior',
- gaLabel: 'Cron Count',
- gaValue: user.flags.cronCount,
- uuid: user._id,
- user: user,
- resting: user.preferences.sleep,
- cronCount: user.flags.cronCount,
- progressUp: _.min([_progress.up, 900]),
- progressDown: _progress.down
- };
- if ((ref3 = options.analytics) != null) {
- ref3.track('Cron', analyticsData);
- }
- return _progress;
-};
diff --git a/common/script/fns/dotGet.js b/common/script/fns/dotGet.js
index c95ba55656..3b45e54c71 100644
--- a/common/script/fns/dotGet.js
+++ b/common/script/fns/dotGet.js
@@ -1,5 +1,7 @@
-import dotGet from '../libs/dotGet';
+import _ from 'lodash';
-module.exports = function(user, path) {
- return dotGet(user, path);
+// TODO remove completely, use _.get, only used in client
+
+module.exports = function dotGet (user, path) {
+ return _.get(user, path);
};
diff --git a/common/script/fns/dotSet.js b/common/script/fns/dotSet.js
index 283e7a50d0..ceb21605af 100644
--- a/common/script/fns/dotSet.js
+++ b/common/script/fns/dotSet.js
@@ -1,12 +1,14 @@
-import dotSet from '../libs/dotSet';
+import _ from 'lodash';
/*
-This allows you to set object properties by dot-path. Eg, you can run pathSet('stats.hp',50,user) which is the same as
-user.stats.hp = 50. This is useful because in our habitrpg-shared functions we're returning changesets as {path:value},
-so that different consumers can implement setters their own way. Derby needs model.set(path, value) for example, where
-Angular sets object properties directly - in which case, this function will be used.
- */
+ This allows you to set object properties by dot-path. Eg, you can run pathSet('stats.hp',50,user) which is the same as
+ user.stats.hp = 50. This is useful because in our habitrpg-shared functions we're returning changesets as {path:value},
+ so that different consumers can implement setters their own way. Derby needs model.set(path, value) for example, where
+ Angular sets object properties directly - in which case, this function will be used.
+*/
-module.exports = function(user, path, val) {
- return dotSet(user, path, val);
+// TODO use directly _.set and remove this fn, only used in client
+
+module.exports = function dotSet (user, path, val) {
+ return _.set(user, path, val);
};
diff --git a/common/script/fns/getItem.js b/common/script/fns/getItem.js
deleted file mode 100644
index b73ecf9073..0000000000
--- a/common/script/fns/getItem.js
+++ /dev/null
@@ -1,11 +0,0 @@
-import content from '../content/index';
-import i18n from '../i18n';
-
-module.exports = function(user, type) {
- var item;
- item = content.gear.flat[user.items.gear.equipped[type]];
- if (!item) {
- return content.gear.flat[type + "_base_0"];
- }
- return item;
-};
diff --git a/common/script/fns/handleTwoHanded.js b/common/script/fns/handleTwoHanded.js
index c700346988..a861a10e68 100644
--- a/common/script/fns/handleTwoHanded.js
+++ b/common/script/fns/handleTwoHanded.js
@@ -1,24 +1,23 @@
import content from '../content/index';
import i18n from '../i18n';
-module.exports = function(user, item, type, req) {
- var message, currentWeapon, currentShield;
- if (type == null) {
- type = 'equipped';
- }
- currentShield = content.gear.flat[user.items.gear[type].shield];
- currentWeapon = content.gear.flat[user.items.gear[type].weapon];
+module.exports = function handleTwoHanded (user, item, type = 'equipped', req = {}) {
+ let currentShield = content.gear.flat[user.items.gear[type].shield];
+ let currentWeapon = content.gear.flat[user.items.gear[type].weapon];
- if (item.type === "shield" && (currentWeapon ? currentWeapon.twoHanded : false)) {
+ let message;
+
+ if (item.type === 'shield' && (currentWeapon ? currentWeapon.twoHanded : false)) {
user.items.gear[type].weapon = 'weapon_base_0';
message = i18n.t('messageTwoHandedUnequip', {
twoHandedText: currentWeapon.text(req.language), offHandedText: item.text(req.language),
}, req.language);
- } else if (item.twoHanded && (currentShield && user.items.gear[type].shield != "shield_base_0")) {
- user.items.gear[type].shield = "shield_base_0";
+ } else if (item.twoHanded && (currentShield && user.items.gear[type].shield !== 'shield_base_0')) {
+ user.items.gear[type].shield = 'shield_base_0';
message = i18n.t('messageTwoHandedEquip', {
twoHandedText: item.text(req.language), offHandedText: currentShield.text(req.language),
}, req.language);
}
+
return message;
};
diff --git a/common/script/fns/index.js b/common/script/fns/index.js
index 24f3ab604b..04fddb2d75 100644
--- a/common/script/fns/index.js
+++ b/common/script/fns/index.js
@@ -1,4 +1,3 @@
-import getItem from './getItem';
import handleTwoHanded from './handleTwoHanded';
import predictableRandom from './predictableRandom';
import crit from './crit';
@@ -8,13 +7,10 @@ import dotGet from './dotGet';
import randomDrop from './randomDrop';
import autoAllocate from './autoAllocate';
import updateStats from './updateStats';
-import cron from './cron';
-import preenUserHistory from './preenUserHistory';
import ultimateGear from './ultimateGear';
import nullify from './nullify';
module.exports = {
- getItem,
handleTwoHanded,
predictableRandom,
crit,
@@ -24,8 +20,6 @@ module.exports = {
randomDrop,
autoAllocate,
updateStats,
- cron,
- preenUserHistory,
ultimateGear,
nullify,
};
diff --git a/common/script/fns/nullify.js b/common/script/fns/nullify.js
index b6e30aa3b9..38753071fc 100644
--- a/common/script/fns/nullify.js
+++ b/common/script/fns/nullify.js
@@ -1,5 +1,7 @@
-module.exports = function(user) {
+// TODO remove once v2 is retired
+
+module.exports = function nullify (user) {
user.ops = null;
user.fns = null;
- return user = null;
+ user = null;
};
diff --git a/common/script/fns/predictableRandom.js b/common/script/fns/predictableRandom.js
index 64c8153746..e67daf3b62 100644
--- a/common/script/fns/predictableRandom.js
+++ b/common/script/fns/predictableRandom.js
@@ -1,20 +1,24 @@
import _ from 'lodash';
-/*
-Because the same op needs to be performed on the client and the server (critical hits, item drops, etc),
-we need things to be "random", but technically predictable so that they don't go out-of-sync
- */
-module.exports = function(user, seed) {
- var x;
+// Because the same op needs to be performed on the client and the server (critical hits, item drops, etc),
+// we need things to be "random", but technically predictable so that they don't go out-of-sync
+
+module.exports = function predictableRandom (user, seed) {
if (!seed || seed === Math.PI) {
- seed = _.reduce(user.stats, (function(m, v) {
- if (_.isNumber(v)) {
- return m + v;
+ let stats = user.stats.toObject ? user.stats.toObject() : user.stats;
+ // These items are not part of the stat object but exists on the server (see controllers/user#getUser)
+ // we remove them in order to use the same user.stats both on server and on client
+ stats = _.omit(stats, 'toNextLevel', 'maxHealth', 'maxMP');
+
+ seed = _.reduce(stats, (accumulator, val) => {
+ if (_.isNumber(val)) {
+ return accumulator + val;
} else {
- return m;
+ return accumulator;
}
- }), 0);
+ }, 0);
}
- x = Math.sin(seed++) * 10000;
+
+ let x = Math.sin(seed++) * 10000;
return x - Math.floor(x);
};
diff --git a/common/script/fns/preenUserHistory.js b/common/script/fns/preenUserHistory.js
deleted file mode 100644
index a45dd82719..0000000000
--- a/common/script/fns/preenUserHistory.js
+++ /dev/null
@@ -1,25 +0,0 @@
-import _ from 'lodash';
-import preenHistory from '../libs/preenHistory';
-
-module.exports = function(user, minHistLen) {
- if (minHistLen == null) {
- minHistLen = 7;
- }
- _.each(user.habits.concat(user.dailys), function(task) {
- var ref;
- if (((ref = task.history) != null ? ref.length : void 0) > minHistLen) {
- task.history = preenHistory(task.history);
- }
- return true;
- });
- _.defaults(user.history, {
- todos: [],
- exp: []
- });
- if (user.history.exp.length > minHistLen) {
- user.history.exp = preenHistory(user.history.exp);
- }
- if (user.history.todos.length > minHistLen) {
- return user.history.todos = preenHistory(user.history.todos);
- }
-};
diff --git a/common/script/fns/randomDrop.js b/common/script/fns/randomDrop.js
index b0121b157b..ec6f350d4c 100644
--- a/common/script/fns/randomDrop.js
+++ b/common/script/fns/randomDrop.js
@@ -3,80 +3,119 @@ import content from '../content/index';
import i18n from '../i18n';
import { daysSince } from '../cron';
import { diminishingReturns } from '../statHelpers';
+import _predictableRandom from './predictableRandom';
+import randomVal from './randomVal';
// Clone a drop object maintaining its functions so that we can change it without affecting the original item
function cloneDropItem (drop) {
- return _.cloneDeep(drop, function (val) {
+ return _.cloneDeep(drop, (val) => {
return _.isFunction(val) ? val : undefined; // undefined will be handled by lodash
});
}
-module.exports = function(user, modifiers, req) {
- var acceptableDrops, base, base1, base2, chance, drop, dropK, dropMultiplier, name, name1, name2, quest, rarity, ref, ref1, ref2, ref3, task;
- task = modifiers.task;
- chance = _.min([Math.abs(task.value - 21.27), 37.5]) / 150 + .02;
- chance *= task.priority * (1 + (task.streak / 100 || 0)) * (1 + (user._statsComputed.per / 100)) * (1 + (user.contributor.level / 40 || 0)) * (1 + (user.achievements.rebirths / 20 || 0)) * (1 + (user.achievements.streak / 200 || 0)) * (user._tmp.crit || 1) * (1 + .5 * (_.reduce(task.checklist, (function(m, i) {
- return m + (i.completed ? 1 : 0);
- }), 0) || 0));
+module.exports = function randomDrop (user, options, req = {}) {
+ let acceptableDrops;
+ let chance;
+ let drop;
+ let dropK;
+ let dropMultiplier;
+ let quest;
+ let rarity;
+ let task;
+
+ let predictableRandom = options.predictableRandom || _predictableRandom;
+ task = options.task;
+
+ chance = _.min([Math.abs(task.value - 21.27), 37.5]) / 150 + 0.02;
+ chance *= task.priority * // Task priority: +50% for Medium, +100% for Hard
+ (1 + (task.streak / 100 || 0)) * // Streak bonus: +1% per streak
+ (1 + user._statsComputed.per / 100) * // PERception: +1% per point
+ (1 + (user.contributor.level / 40 || 0)) * // Contrib levels: +2.5% per level
+ (1 + (user.achievements.rebirths / 20 || 0)) * // Rebirths: +5% per achievement
+ (1 + (user.achievements.streak / 200 || 0)) * // Streak achievements: +0.5% per achievement
+ (user._tmp.crit || 1) * (1 + 0.5 * (_.reduce(task.checklist, (m, i) => {
+ return m + (i.completed ? 1 : 0); // +50% per checklist item complete. TODO: make this into X individual drop chances instead
+ }, 0) || 0));
chance = diminishingReturns(chance, 0.75);
- quest = content.quests[(ref = user.party.quest) != null ? ref.key : void 0];
- if ((quest != null ? quest.collect : void 0) && user.fns.predictableRandom(user.stats.gp) < chance) {
- dropK = user.fns.randomVal(quest.collect, {
- key: true
+
+ if (user.party.quest.key)
+ quest = content.quests[user.party.quest.key];
+ if (quest && quest.collect && predictableRandom(user, user.stats.gp) < chance) {
+ dropK = randomVal(user, quest.collect, {
+ key: true,
});
+ if (!user.party.quest.progress.collect[dropK])
+ user.party.quest.progress.collect[dropK] = 0;
user.party.quest.progress.collect[dropK]++;
- if (typeof user.markModified === "function") {
- user.markModified('party.quest.progress');
- }
+ user.markModified('party.quest.progress');
}
- dropMultiplier = ((ref1 = user.purchased) != null ? (ref2 = ref1.plan) != null ? ref2.customerId : void 0 : void 0) ? 2 : 1;
- if ((daysSince(user.items.lastDrop.date, user.preferences) === 0) && (user.items.lastDrop.count >= dropMultiplier * (5 + Math.floor(user._statsComputed.per / 25) + (user.contributor.level || 0)))) {
+
+ if (user.purchased && user.purchased.plan && user.purchased.plan.customerId) {
+ dropMultiplier = 2;
+ } else {
+ dropMultiplier = 1;
+ }
+
+ if (daysSince(user.items.lastDrop.date, user.preferences) === 0 &&
+ user.items.lastDrop.count >= dropMultiplier * (5 + Math.floor(user._statsComputed.per / 25) + (user.contributor.level || 0))) {
return;
}
- if (((ref3 = user.flags) != null ? ref3.dropsEnabled : void 0) && user.fns.predictableRandom(user.stats.exp) < chance) {
- rarity = user.fns.predictableRandom(user.stats.gp);
- if (rarity > .6) {
- drop = cloneDropItem(user.fns.randomVal(_.where(content.food, {
- canDrop: true
+
+ if (user.flags && user.flags.dropsEnabled && predictableRandom(user, user.stats.exp) < chance) {
+ rarity = predictableRandom(user, user.stats.gp);
+
+ if (rarity > 0.6) { // food 40% chance
+ drop = cloneDropItem(randomVal(user, _.where(content.food, {
+ canDrop: true,
})));
- if ((base = user.items.food)[name = drop.key] == null) {
- base[name] = 0;
+
+ if (!user.items.food[drop.key]) {
+ user.items.food[drop.key] = 0;
}
user.items.food[drop.key] += 1;
drop.type = 'Food';
drop.dialog = i18n.t('messageDropFood', {
dropArticle: drop.article,
dropText: drop.text(req.language),
- dropNotes: drop.notes(req.language)
+ dropNotes: drop.notes(req.language),
}, req.language);
- } else if (rarity > .3) {
- drop = cloneDropItem(user.fns.randomVal(content.dropEggs));
- if ((base1 = user.items.eggs)[name1 = drop.key] == null) {
- base1[name1] = 0;
+ } else if (rarity > 0.3) { // eggs 30% chance
+ drop = cloneDropItem(randomVal(user, content.dropEggs));
+ if (!user.items.eggs[drop.key]) {
+ user.items.eggs[drop.key] = 0;
}
user.items.eggs[drop.key]++;
drop.type = 'Egg';
drop.dialog = i18n.t('messageDropEgg', {
dropText: drop.text(req.language),
- dropNotes: drop.notes(req.language)
+ dropNotes: drop.notes(req.language),
}, req.language);
- } else {
- acceptableDrops = rarity < .02 ? ['Golden'] : rarity < .09 ? ['Zombie', 'CottonCandyPink', 'CottonCandyBlue'] : rarity < .18 ? ['Red', 'Shade', 'Skeleton'] : ['Base', 'White', 'Desert'];
- drop = cloneDropItem(user.fns.randomVal(_.pick(content.hatchingPotions, (function(v, k) {
+ } else { // Hatching Potion, 30% chance - break down by rarity.
+ if (rarity < 0.02) { // Very Rare: 10% (of 30%)
+ acceptableDrops = ['Golden'];
+ } else if (rarity < 0.09) { // Rare: 20% of 30%
+ acceptableDrops = ['Zombie', 'CottonCandyPink', 'CottonCandyBlue'];
+ } else if (rarity < 0.18) { // uncommon: 30% of 30%
+ acceptableDrops = ['Red', 'Shade', 'Skeleton'];
+ } else { // common, 40% of 30%
+ acceptableDrops = ['Base', 'White', 'Desert'];
+ }
+ drop = cloneDropItem(randomVal(user, _.pick(content.hatchingPotions, (v, k) => {
return acceptableDrops.indexOf(k) >= 0;
- }))));
- if ((base2 = user.items.hatchingPotions)[name2 = drop.key] == null) {
- base2[name2] = 0;
+ })));
+ if (!user.items.hatchingPotions[drop.key]) {
+ user.items.hatchingPotions[drop.key] = 0;
}
user.items.hatchingPotions[drop.key]++;
drop.type = 'HatchingPotion';
drop.dialog = i18n.t('messageDropPotion', {
dropText: drop.text(req.language),
- dropNotes: drop.notes(req.language)
+ dropNotes: drop.notes(req.language),
}, req.language);
}
+
user._tmp.drop = drop;
- user.items.lastDrop.date = +(new Date);
- return user.items.lastDrop.count++;
+ user.items.lastDrop.date = Number(new Date());
+ user.items.lastDrop.count++;
}
};
diff --git a/common/script/fns/randomVal.js b/common/script/fns/randomVal.js
index 3c2b5b82e8..2244d04558 100644
--- a/common/script/fns/randomVal.js
+++ b/common/script/fns/randomVal.js
@@ -1,14 +1,12 @@
import _ from 'lodash';
+import predictableRandom from './predictableRandom';
-/*
- Get a random property from an object
- returns random property (the value)
- */
+// Get a random property from an object
+// returns random property (the value)
-module.exports = function(user, obj, options) {
- var array, rand;
- array = (options != null ? options.key : void 0) ? _.keys(obj) : _.values(obj);
- rand = user.fns.predictableRandom(options != null ? options.seed : void 0);
+module.exports = function randomVal (user, obj, options = {}) {
+ let array = options.key ? _.keys(obj) : _.values(obj);
+ let rand = predictableRandom(user, options.seed);
array.sort();
return array[Math.floor(rand * array.length)];
};
diff --git a/common/script/fns/resetGear.js b/common/script/fns/resetGear.js
new file mode 100644
index 0000000000..2625f5c9b8
--- /dev/null
+++ b/common/script/fns/resetGear.js
@@ -0,0 +1,25 @@
+import _ from 'lodash';
+import content from '../content/index';
+
+module.exports = function resetGear (user) {
+ let gear = user.items.gear;
+
+ _.each(['equipped', 'costume'], function resetUserGear (type) {
+ gear[type] = {};
+ gear[type].armor = 'armor_base_0';
+ gear[type].weapon = 'weapon_warrior_0';
+ gear[type].head = 'head_base_0';
+ gear[type].shield = 'shield_base_0';
+ });
+
+ // Gear.owned is a Mongo object so the _.each function iterates over hidden properties.
+ // The content.gear.flat[k] check should prevent this causing an error
+ _.each(gear.owned, function resetOwnedGear (v, k) {
+ if (gear.owned[k] && content.gear.flat[k] && content.gear.flat[k].value) {
+ gear.owned[k] = false;
+ }
+ });
+
+ gear.owned.weapon_warrior_0 = true; // eslint-disable-line camelcase
+ user.preferences.costume = false;
+};
diff --git a/common/script/fns/ultimateGear.js b/common/script/fns/ultimateGear.js
index 1333e8cc38..5bca8ff912 100644
--- a/common/script/fns/ultimateGear.js
+++ b/common/script/fns/ultimateGear.js
@@ -1,33 +1,32 @@
import content from '../content/index';
import _ from 'lodash';
-module.exports = function(user) {
- var base, owned;
- owned = typeof window !== "undefined" && window !== null ? user.items.gear.owned : user.items.gear.owned.toObject();
- if ((base = user.achievements).ultimateGearSets == null) {
- base.ultimateGearSets = {
- healer: false,
- wizard: false,
- rogue: false,
- warrior: false
- };
- }
- content.classes.forEach(function(klass) {
+module.exports = function ultimateGear (user) {
+ let owned = typeof window !== 'undefined' ? user.items.gear.owned : user.items.gear.owned.toObject();
+
+ content.classes.forEach((klass) => {
if (user.achievements.ultimateGearSets[klass] !== true) {
- return user.achievements.ultimateGearSets[klass] = _.reduce(['armor', 'shield', 'head', 'weapon'], function(soFarGood, type) {
- var found;
- found = _.find(content.gear.tree[type][klass], {
- last: true
+ user.achievements.ultimateGearSets[klass] = _.reduce(['armor', 'shield', 'head', 'weapon'], (soFarGood, type) => {
+ let found = _.find(content.gear.tree[type][klass], {
+ last: true,
});
return soFarGood && (!found || owned[found.key] === true);
}, true);
}
});
- if (typeof user.markModified === "function") {
- user.markModified('achievements.ultimateGearSets');
+
+ let ultimateGearSetValues;
+ if (user.achievements.ultimateGearSets.toObject) {
+ ultimateGearSetValues = Object.values(user.achievements.ultimateGearSets.toObject());
+ } else {
+ ultimateGearSetValues = Object.values(user.achievements.ultimateGearSets);
}
- if (_.contains(user.achievements.ultimateGearSets, true) && user.flags.armoireEnabled !== true) {
+
+ let hasFullSet = _.includes(ultimateGearSetValues, true);
+
+ if (hasFullSet && user.flags.armoireEnabled !== true) {
user.flags.armoireEnabled = true;
- return typeof user.markModified === "function" ? user.markModified('flags') : void 0;
}
+
+ return;
};
diff --git a/common/script/fns/updateStats.js b/common/script/fns/updateStats.js
index 3ce83ed664..6061f717ab 100644
--- a/common/script/fns/updateStats.js
+++ b/common/script/fns/updateStats.js
@@ -1,21 +1,19 @@
import _ from 'lodash';
import {
MAX_HEALTH,
- MAX_STAT_POINTS
+ MAX_STAT_POINTS,
} from '../constants';
import { toNextLevel } from '../statHelpers';
-module.exports = function (user, stats, req, analytics) {
+import autoAllocate from './autoAllocate';
+
+module.exports = function updateStats (user, stats, req = {}, analytics) {
let allocatedStatPoints;
let totalStatPoints;
let experienceToNextLevel;
- if (stats.hp <= 0) {
- user.stats.hp = 0;
- return user.stats.hp;
- }
-
- user.stats.hp = stats.hp;
- user.stats.gp = stats.gp >= 0 ? stats.gp : 0;
+ user.stats.hp = stats.hp > 0 ? stats.hp : 0;
+ user.stats.gp = stats.gp > 0 ? stats.gp : 0;
+ if (!user._tmp) user._tmp = {};
experienceToNextLevel = toNextLevel(user.stats.lvl);
@@ -35,7 +33,7 @@ module.exports = function (user, stats, req, analytics) {
continue; // eslint-disable-line no-continue
}
if (user.preferences.automaticAllocation) {
- user.fns.autoAllocate();
+ autoAllocate(user);
} else {
user.stats.points = user.stats.lvl - allocatedStatPoints;
totalStatPoints = user.stats.points + allocatedStatPoints;
@@ -52,7 +50,6 @@ module.exports = function (user, stats, req, analytics) {
}
user.stats.exp = stats.exp;
- user.flags = user.flags || {};
if (!user.flags.customizationsNotification && (user.stats.exp > 5 || user.stats.lvl > 1)) {
user.flags.customizationsNotification = true;
@@ -62,48 +59,39 @@ module.exports = function (user, stats, req, analytics) {
}
if (!user.flags.dropsEnabled && user.stats.lvl >= 3) {
user.flags.dropsEnabled = true;
- if (user.items.eggs["Wolf"] > 0) {
- user.items.eggs["Wolf"]++;
+ if (user.items.eggs.Wolf > 0) {
+ user.items.eggs.Wolf++;
} else {
- user.items.eggs["Wolf"] = 1;
+ user.items.eggs.Wolf = 1;
}
}
- if (!user.flags.classSelected && user.stats.lvl >= 10) {
- user.flags.classSelected;
- }
_.each({
vice1: 30,
atom1: 15,
moonstone1: 60,
- goldenknight1: 40
- }, function(lvl, k) {
- var analyticsData, base, base1, ref;
- if (!((ref = user.flags.levelDrops) != null ? ref[k] : void 0) && user.stats.lvl >= lvl) {
- if ((base = user.items.quests)[k] == null) {
- base[k] = 0;
- }
+ goldenknight1: 40,
+ }, (lvl, k) => {
+ if (user.stats.lvl >= lvl && !user.flags.levelDrops[k]) {
+ user.flags.levelDrops[k] = true;
+ if (!user.items.quests[k])
+ user.items.quests[k] = 0;
user.items.quests[k]++;
- ((base1 = user.flags).levelDrops != null ? base1.levelDrops : base1.levelDrops = {})[k] = true;
- if (typeof user.markModified === "function") {
- user.markModified('flags.levelDrops');
+ user.markModified('flags.levelDrops');
+ if (analytics) {
+ analytics.track('acquire item', {
+ uuid: user._id,
+ itemKey: k,
+ acquireMethod: 'Level Drop',
+ category: 'behavior',
+ });
}
- analyticsData = {
- uuid: user._id,
- itemKey: k,
- acquireMethod: 'Level Drop',
- category: 'behavior'
- };
- if (analytics != null) {
- analytics.track('acquire item', analyticsData);
- }
- if (!user._tmp) user._tmp = {}
- return user._tmp.drop = {
+ user._tmp.drop = {
type: 'Quest',
- key: k
+ key: k,
};
}
});
if (!user.flags.rebirthEnabled && (user.stats.lvl >= 50 || user.achievements.beastMaster)) {
- return user.flags.rebirthEnabled = true;
+ user.flags.rebirthEnabled = true;
}
};
diff --git a/common/script/index.js b/common/script/index.js
index ea502e9a7d..50b01bf762 100644
--- a/common/script/index.js
+++ b/common/script/index.js
@@ -1,85 +1,197 @@
-import moment from 'moment';
import _ from 'lodash';
-import {
- daysSince,
- shouldDo,
-} from './cron';
+// When using a common module from the website or the server NEVER import the module directly
+// but access it through `api` (the main common) module, otherwise you would require the non transpiled version of the file in production.
+let api = module.exports = {};
+
+import content from './content/index';
+api.content = content;
+
+import * as errors from './libs/errors';
+api.errors = errors;
+import i18n from './i18n';
+api.i18n = i18n;
+
+// TODO under api.libs.cron?
+import { shouldDo, daysSince } from './cron';
+api.shouldDo = shouldDo;
+api.daysSince = daysSince;
+
+// TODO under api.constants? and capitalize exported names too
import {
MAX_HEALTH,
MAX_LEVEL,
MAX_STAT_POINTS,
+ TAVERN_ID,
} from './constants';
-import * as statHelpers from './statHelpers';
-
-import importedLibs from './libs';
-
-var $w, preenHistory, sortOrder,
- indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
-
-import content from './content/index';
-import i18n from './i18n';
-
-let api = module.exports = {};
-
-api.i18n = i18n;
-api.shouldDo = shouldDo;
-
api.maxLevel = MAX_LEVEL;
-api.capByLevel = statHelpers.capByLevel;
api.maxHealth = MAX_HEALTH;
+api.maxStatPoints = MAX_STAT_POINTS;
+api.TAVERN_ID = TAVERN_ID;
+
+// TODO under api.libs.statHelpers?
+import * as statHelpers from './statHelpers';
+api.capByLevel = statHelpers.capByLevel;
api.tnl = statHelpers.toNextLevel;
api.diminishingReturns = statHelpers.diminishingReturns;
-$w = api.$w = importedLibs.splitWhitespace;
-api.dotSet = importedLibs.dotSet;
-api.dotGet = importedLibs.dotGet;
-api.refPush = importedLibs.refPush;
-api.planGemLimits = importedLibs.planGemLimits;
+import splitWhitespace from './libs/splitWhitespace';
+api.$w = splitWhitespace;
-preenHistory = importedLibs.preenHistory;
+import dotSet from './libs/dotSet';
+api.dotSet = dotSet;
-api.preenTodos = importedLibs.preenTodos;
-api.updateStore = importedLibs.updateStore;
+import dotGet from './libs/dotGet';
+api.dotGet = dotGet;
+import refPush from './libs/refPush';
+api.refPush = refPush;
-/*
-------------------------------------------------------
-Content
-------------------------------------------------------
- */
+import planGemLimits from './libs/planGemLimits';
+api.planGemLimits = planGemLimits;
-api.content = content;
+import preenTodos from './libs/preenTodos';
+api.preenTodos = preenTodos;
+import updateStore from './libs/updateStore';
+api.updateStore = updateStore;
-/*
-------------------------------------------------------
-Misc Helpers
-------------------------------------------------------
- */
+import uuid from './libs/uuid';
+api.uuid = uuid;
-api.uuid = importedLibs.uuid;
-api.countExists = importedLibs.countExists;
-api.taskDefaults = importedLibs.taskDefaults;
-api.percent = importedLibs.percent;
-api.removeWhitespace = importedLibs.removeWhitespace;
-api.encodeiCalLink = importedLibs.encodeiCalLink;
-api.gold = importedLibs.gold;
-api.silver = importedLibs.silver;
-api.taskClasses = importedLibs.taskClasses;
-api.friendlyTimestamp = importedLibs.friendlyTimestamp;
-api.newChatMessages = importedLibs.newChatMessages;
-api.noTags = importedLibs.noTags;
-api.appliedTags = importedLibs.appliedTags;
+import taskDefaults from './libs/taskDefaults';
+api.taskDefaults = taskDefaults;
+import percent from './libs/percent';
+api.percent = percent;
-/*
-Various counting functions
- */
+import gold from './libs/gold';
+api.gold = gold;
+
+import silver from './libs/silver';
+api.silver = silver;
+
+import taskClasses from './libs/taskClasses';
+api.taskClasses = taskClasses;
+
+import noTags from './libs/noTags';
+api.noTags = noTags;
+
+import appliedTags from './libs/appliedTags';
+api.appliedTags = appliedTags;
+
+import pickDeep from './libs/pickDeep';
+api.pickDeep = pickDeep;
import count from './count';
api.count = count;
+import statsComputed from './libs/statsComputed';
+api.statsComputed = statsComputed;
+
+import autoAllocate from './fns/autoAllocate';
+import crit from './fns/crit';
+import handleTwoHanded from './fns/handleTwoHanded';
+import predictableRandom from './fns/predictableRandom';
+import randomDrop from './fns/randomDrop';
+import randomVal from './fns/randomVal';
+import resetGear from './fns/resetGear';
+import ultimateGear from './fns/ultimateGear';
+import updateStats from './fns/updateStats';
+
+api.fns = {
+ autoAllocate,
+ crit,
+ handleTwoHanded,
+ predictableRandom,
+ randomDrop,
+ randomVal,
+ resetGear,
+ ultimateGear,
+ updateStats,
+};
+
+import scoreTask from './ops/scoreTask';
+import sleep from './ops/sleep';
+import allocate from './ops/allocate';
+import buy from './ops/buy';
+import buyGear from './ops/buyGear';
+import buyHealthPotion from './ops/buyHealthPotion';
+import buyArmoire from './ops/buyArmoire';
+import buyMysterySet from './ops/buyMysterySet';
+import buyQuest from './ops/buyQuest';
+import buySpecialSpell from './ops/buySpecialSpell';
+import allocateNow from './ops/allocateNow';
+import hatch from './ops/hatch';
+import feed from './ops/feed';
+import equip from './ops/equip';
+import changeClass from './ops/changeClass';
+import disableClasses from './ops/disableClasses';
+import purchase from './ops/purchase';
+import purchaseHourglass from './ops/hourglassPurchase';
+import readCard from './ops/readCard';
+import openMysteryItem from './ops/openMysteryItem';
+import addWebhook from './ops/addWebhook';
+import updateWebhook from './ops/updateWebhook';
+import deleteWebhook from './ops/deleteWebhook';
+import releasePets from './ops/releasePets';
+import releaseBoth from './ops/releaseBoth';
+import releaseMounts from './ops/releaseMounts';
+import updateTask from './ops/updateTask';
+import clearCompleted from './ops/clearCompleted';
+import sell from './ops/sell';
+import unlock from './ops/unlock';
+import revive from './ops/revive';
+import rebirth from './ops/rebirth';
+import blockUser from './ops/blockUser';
+import clearPMs from './ops/clearPMs';
+import deletePM from './ops/deletePM';
+import reroll from './ops/reroll';
+import addPushDevice from './ops/addPushDevice';
+import reset from './ops/reset';
+import markPmsRead from './ops/markPMSRead';
+
+api.ops = {
+ scoreTask,
+ sleep,
+ allocate,
+ buy,
+ buyGear,
+ buyHealthPotion,
+ buyArmoire,
+ buyMysterySet,
+ buySpecialSpell,
+ buyQuest,
+ allocateNow,
+ hatch,
+ feed,
+ equip,
+ changeClass,
+ disableClasses,
+ purchase,
+ purchaseHourglass,
+ readCard,
+ openMysteryItem,
+ addWebhook,
+ updateWebhook,
+ deleteWebhook,
+ releasePets,
+ releaseBoth,
+ releaseMounts,
+ updateTask,
+ clearCompleted,
+ sell,
+ unlock,
+ revive,
+ rebirth,
+ blockUser,
+ clearPMs,
+ deletePM,
+ reroll,
+ addPushDevice,
+ reset,
+ markPmsRead,
+};
/*
------------------------------------------------------
@@ -87,10 +199,9 @@ User (prototype wrapper to give it ops, helper funcs, and virtuals
------------------------------------------------------
*/
-
/*
User is now wrapped (both on client and server), adding a few new properties:
- * getters (_statsComputed, tasks, etc)
+ * getters (_statsComputed)
* user.fns, which is a bunch of helper functions
These were originally up above, but they make more sense belonging to the user object so we don't have to pass
the user object all over the place. In fact, we should pull in more functions such as cron(), updateStats(), etc.
@@ -121,14 +232,16 @@ TODO
import importedOps from './ops';
import importedFns from './fns';
-api.wrap = function(user, main) {
- if (main == null) {
- main = true;
- }
- if (user._wrapped) {
- return;
- }
+// TODO Kept for the client side
+api.wrap = function wrapUser (user, main = true) {
+ if (user._wrapped) return;
user._wrapped = true;
+
+ // Make markModified available on the client side as a noop function
+ if (!user.markModified) {
+ user.markModified = function noopMarkModified () {};
+ }
+
if (main) {
user.ops = {
update: _.partial(importedOps.update, user),
@@ -163,6 +276,9 @@ api.wrap = function(user, main) {
releaseMounts: _.partial(importedOps.releaseMounts, user),
releaseBoth: _.partial(importedOps.releaseBoth, user),
buy: _.partial(importedOps.buy, user),
+ buyHealthPotion: _.partial(importedOps.buyHealthPotion, user),
+ buyArmoire: _.partial(importedOps.buyArmoire, user),
+ buyGear: _.partial(importedOps.buyGear, user),
buyQuest: _.partial(importedOps.buyQuest, user),
buyMysterySet: _.partial(importedOps.buyMysterySet, user),
hourglassPurchase: _.partial(importedOps.hourglassPurchase, user),
@@ -175,11 +291,12 @@ api.wrap = function(user, main) {
allocate: _.partial(importedOps.allocate, user),
readCard: _.partial(importedOps.readCard, user),
openMysteryItem: _.partial(importedOps.openMysteryItem, user),
- score: _.partial(importedOps.score, user),
+ score: _.partial(importedOps.scoreTask, user),
+ markPmsRead: _.partial(importedOps.markPmsRead, user),
};
}
+
user.fns = {
- getItem: _.partial(importedFns.getItem, user),
handleTwoHanded: _.partial(importedFns.handleTwoHanded, user),
predictableRandom: _.partial(importedFns.predictableRandom, user),
crit: _.partial(importedFns.crit, user),
@@ -189,34 +306,14 @@ api.wrap = function(user, main) {
randomDrop: _.partial(importedFns.randomDrop, user),
autoAllocate: _.partial(importedFns.autoAllocate, user),
updateStats: _.partial(importedFns.updateStats, user),
- cron: _.partial(importedFns.cron, user),
- preenUserHistory: _.partial(importedFns.preenUserHistory, user),
+ statsComputed: _.partial(statsComputed, user),
ultimateGear: _.partial(importedFns.ultimateGear, user),
nullify: _.partial(importedFns.nullify, user),
};
+
Object.defineProperty(user, '_statsComputed', {
- get: function() {
- var computed;
- computed = _.reduce(['per', 'con', 'str', 'int'], (function(_this) {
- return function(m, stat) {
- m[stat] = _.reduce($w('stats stats.buffs items.gear.equipped.weapon items.gear.equipped.armor items.gear.equipped.head items.gear.equipped.shield'), function(m2, path) {
- var item, val;
- val = user.fns.dotGet(path);
- return m2 + (~path.indexOf('items.gear') ? (item = content.gear.flat[val], (+(item != null ? item[stat] : void 0) || 0) * ((item != null ? item.klass : void 0) === user.stats["class"] || (item != null ? item.specialClass : void 0) === user.stats["class"] ? 1.5 : 1)) : +val[stat] || 0);
- }, 0);
- m[stat] += Math.floor(api.capByLevel(user.stats.lvl) / 2);
- return m;
- };
- })(this), {});
- computed.maxMP = computed.int * 2 + 30;
- return computed;
- }
- });
- return Object.defineProperty(user, 'tasks', {
- get: function() {
- var tasks;
- tasks = user.habits.concat(user.dailys).concat(user.todos).concat(user.rewards);
- return _.object(_.pluck(tasks, "id"), tasks);
- }
+ get () {
+ return statsComputed(user);
+ },
});
};
diff --git a/common/script/libs/appliedTags.js b/common/script/libs/appliedTags.js
index 31302fbe54..ded2a9de23 100644
--- a/common/script/libs/appliedTags.js
+++ b/common/script/libs/appliedTags.js
@@ -1,19 +1,14 @@
-import _ from 'lodash';
-
/*
Are there tags applied?
*/
-module.exports = function(userTags, taskTags) {
- var arr;
- arr = [];
- _.each(userTags, function(t) {
- if (t == null) {
- return;
- }
- if (taskTags != null ? taskTags[t.id] : void 0) {
- return arr.push(t.name);
- }
+// TODO move to client
+
+module.exports = function appliedTags (userTags, taskTags = []) {
+ let arr = userTags.filter(tag => {
+ return taskTags.indexOf(tag.id) !== -1;
+ }).map(tag => {
+ return tag.name;
});
return arr.join(', ');
};
diff --git a/common/script/libs/countExists.js b/common/script/libs/countExists.js
deleted file mode 100644
index 964e4e286f..0000000000
--- a/common/script/libs/countExists.js
+++ /dev/null
@@ -1,7 +0,0 @@
-import _ from 'lodash';
-
-module.exports = function(items) {
- return _.reduce(items, (function(m, v) {
- return m + (v ? 1 : 0);
- }), 0);
-};
diff --git a/common/script/libs/dotGet.js b/common/script/libs/dotGet.js
index 4585d8fd53..d8ce026b82 100644
--- a/common/script/libs/dotGet.js
+++ b/common/script/libs/dotGet.js
@@ -1,9 +1,5 @@
import _ from 'lodash';
-module.exports = function(obj, path) {
- return _.reduce(path.split('.'), ((function(_this) {
- return function(curr, next) {
- return curr != null ? curr[next] : void 0;
- };
- })(this)), obj);
-};
+// TODO remove completely, only used in client
+
+module.exports = _.get;
diff --git a/common/script/libs/dotSet.js b/common/script/libs/dotSet.js
index 40165078aa..b664c54d05 100644
--- a/common/script/libs/dotSet.js
+++ b/common/script/libs/dotSet.js
@@ -1,14 +1,5 @@
import _ from 'lodash';
-module.exports = function(obj, path, val) {
- var arr;
- arr = path.split('.');
- return _.reduce(arr, (function(_this) {
- return function(curr, next, index) {
- if ((arr.length - 1) === index) {
- curr[next] = val;
- }
- return curr[next] != null ? curr[next] : curr[next] = {};
- };
- })(this), obj);
-};
+// TODO remove completely, only used in client
+
+module.exports = _.set;
diff --git a/common/script/libs/encodeiCalLink.js b/common/script/libs/encodeiCalLink.js
deleted file mode 100644
index 4a85badd59..0000000000
--- a/common/script/libs/encodeiCalLink.js
+++ /dev/null
@@ -1,9 +0,0 @@
-/*
-Encode the download link for .ics iCal file
- */
-
-module.exports = function(uid, apiToken) {
- var loc, ref;
- loc = (typeof window !== "undefined" && window !== null ? window.location.host : void 0) || (typeof process !== "undefined" && process !== null ? (ref = process.env) != null ? ref.BASE_URL : void 0 : void 0) || '';
- return encodeURIComponent("http://" + loc + "/v1/users/" + uid + "/calendar.ics?apiToken=" + apiToken);
-};
diff --git a/common/script/libs/errors.js b/common/script/libs/errors.js
new file mode 100644
index 0000000000..cb780d215b
--- /dev/null
+++ b/common/script/libs/errors.js
@@ -0,0 +1,42 @@
+import extendableBuiltin from './extendableBuiltin';
+
+// Base class for custom application errors
+// It extends Error and capture the stack trace
+export class CustomError extends extendableBuiltin(Error) {
+ constructor () {
+ super();
+
+ if (Error.captureStackTrace) {
+ Error.captureStackTrace(this, this.constructor);
+ }
+ }
+}
+
+// We specify an httpCode for all errors so that they can be used in the API too
+
+export class NotAuthorized extends CustomError {
+ constructor (customMessage) {
+ super();
+ this.name = this.constructor.name;
+ this.httpCode = 401;
+ this.message = customMessage || 'Not authorized.';
+ }
+}
+
+export class BadRequest extends CustomError {
+ constructor (customMessage) {
+ super();
+ this.name = this.constructor.name;
+ this.httpCode = 400;
+ this.message = customMessage || 'Bad request.';
+ }
+}
+
+export class NotFound extends CustomError {
+ constructor (customMessage) {
+ super();
+ this.name = this.constructor.name;
+ this.httpCode = 404;
+ this.message = customMessage || 'Not found.';
+ }
+}
diff --git a/common/script/libs/extendableBuiltin.js b/common/script/libs/extendableBuiltin.js
new file mode 100644
index 0000000000..56186301c4
--- /dev/null
+++ b/common/script/libs/extendableBuiltin.js
@@ -0,0 +1,11 @@
+// Babel 6 doesn't support extending native class (Error, Array, ...)
+// This function makes it possible to extend native classes with the same results as Babel 5
+module.exports = function extendableBuiltin (klass) {
+ function ExtendableBuiltin () {
+ klass.apply(this, arguments);
+ }
+ ExtendableBuiltin.prototype = Object.create(klass.prototype);
+ Object.setPrototypeOf(ExtendableBuiltin, klass);
+
+ return ExtendableBuiltin;
+};
diff --git a/common/script/libs/friendlyTimestamp.js b/common/script/libs/friendlyTimestamp.js
deleted file mode 100644
index dfda6fbed7..0000000000
--- a/common/script/libs/friendlyTimestamp.js
+++ /dev/null
@@ -1,9 +0,0 @@
-import moment from 'moment';
-
-/*
-Friendly timestamp
- */
-
-module.exports = function(timestamp) {
- return moment(timestamp).format('MM/DD h:mm:ss a');
-};
diff --git a/common/script/libs/gold.js b/common/script/libs/gold.js
index 8016e2cff9..83d9531d5e 100644
--- a/common/script/libs/gold.js
+++ b/common/script/libs/gold.js
@@ -1,7 +1,9 @@
-module.exports = function(num) {
+// TODO move to client
+
+module.exports = function gold (num) {
if (num) {
return Math.floor(num);
} else {
- return "0";
+ return '0';
}
};
diff --git a/common/script/libs/index.js b/common/script/libs/index.js
deleted file mode 100644
index dd0c420d8d..0000000000
--- a/common/script/libs/index.js
+++ /dev/null
@@ -1,46 +0,0 @@
-import uuid from './uuid';
-import taskDefaults from './taskDefaults';
-import refPush from './refPush';
-import splitWhitespace from './splitWhitespace';
-import planGemLimits from './planGemLimits';
-import preenTodos from './preenTodos';
-import dotSet from './dotSet';
-import dotGet from './dotGet';
-import preenHistory from './preenHistory';
-import countExists from './countExists';
-import updateStore from './updateStore';
-
-import appliedTags from './appliedTags';
-import encodeiCalLink from './encodeiCalLink';
-import friendlyTimestamp from './friendlyTimestamp';
-import gold from './gold';
-import newChatMessages from './newChatMessages';
-import noTags from './noTags';
-import percent from './percent';
-import removeWhitespace from './removeWhitespace';
-import silver from './silver';
-import taskClasses from './taskClasses';
-
-module.exports = {
- uuid,
- taskDefaults,
- refPush,
- splitWhitespace,
- planGemLimits,
- preenTodos,
- dotSet,
- dotGet,
- preenHistory,
- countExists,
- updateStore,
- appliedTags,
- encodeiCalLink,
- friendlyTimestamp,
- gold,
- newChatMessages,
- noTags,
- percent,
- removeWhitespace,
- silver,
- taskClasses,
-};
diff --git a/common/script/libs/newChatMessages.js b/common/script/libs/newChatMessages.js
deleted file mode 100644
index abe7680edf..0000000000
--- a/common/script/libs/newChatMessages.js
+++ /dev/null
@@ -1,10 +0,0 @@
-/*
-Does user have new chat messages?
- */
-
-module.exports = function(messages, lastMessageSeen) {
- if (!((messages != null ? messages.length : void 0) > 0)) {
- return false;
- }
- return (messages != null ? messages[0] : void 0) && (messages[0].id !== lastMessageSeen);
-};
diff --git a/common/script/libs/noTags.js b/common/script/libs/noTags.js
index c3abb9054e..16a59d1d31 100644
--- a/common/script/libs/noTags.js
+++ b/common/script/libs/noTags.js
@@ -4,8 +4,10 @@ import _ from 'lodash';
are any tags active?
*/
-module.exports = function(tags) {
- return _.isEmpty(tags) || _.isEmpty(_.filter(tags, function(t) {
+// TODO move to client
+
+module.exports = function noTags (tags) {
+ return _.isEmpty(tags) || _.isEmpty(_.filter(tags, (t) => {
return t;
}));
};
diff --git a/common/script/libs/percent.js b/common/script/libs/percent.js
index 7439b22285..d7622474dd 100644
--- a/common/script/libs/percent.js
+++ b/common/script/libs/percent.js
@@ -1,10 +1,12 @@
-module.exports = function(x, y, dir) {
- var roundFn;
+// TODO move to client
+
+module.exports = function percent (x, y, dir) {
+ let roundFn;
switch (dir) {
- case "up":
+ case 'up':
roundFn = Math.ceil;
break;
- case "down":
+ case 'down':
roundFn = Math.floor;
break;
default:
diff --git a/common/script/libs/pickDeep.js b/common/script/libs/pickDeep.js
new file mode 100644
index 0000000000..919d926854
--- /dev/null
+++ b/common/script/libs/pickDeep.js
@@ -0,0 +1,13 @@
+// An utility to pick deep properties from an object.
+// Works like _.pick but supports nested props (ie pickDeep(obj, ['deep.property']))
+
+import _ from 'lodash';
+
+module.exports = function pickDeep (obj, properties) {
+ if (!_.isArray(properties)) throw new Error('"properties" must be an array');
+
+ let result = {};
+ _.each(properties, (prop) => _.set(result, prop, _.get(obj, prop)));
+
+ return result;
+};
diff --git a/common/script/libs/preenHistory.js b/common/script/libs/preenHistory.js
deleted file mode 100644
index 0d354c238c..0000000000
--- a/common/script/libs/preenHistory.js
+++ /dev/null
@@ -1,43 +0,0 @@
-import moment from 'moment';
-import _ from 'lodash';
-
-/*
-Preen history for users with > 7 history entries
-This takes an infinite array of single day entries [day day day day day...], and turns it into a condensed array
-of averages, condensing more the further back in time we go. Eg, 7 entries each for last 7 days; 1 entry each week
-of this month; 1 entry for each month of this year; 1 entry per previous year: [day*7 week*4 month*12 year*infinite]
- */
-
-module.exports = function(history) {
- var newHistory, preen, thisMonth;
- history = _.filter(history, function(h) {
- return !!h;
- });
- newHistory = [];
- preen = function(amount, groupBy) {
- var groups;
- groups = _.chain(history).groupBy(function(h) {
- return moment(h.date).format(groupBy);
- }).sortBy(function(h, k) {
- return k;
- }).value();
- groups = groups.slice(-amount);
- groups.pop();
- return _.each(groups, function(group) {
- newHistory.push({
- date: moment(group[0].date).toDate(),
- value: _.reduce(group, (function(m, obj) {
- return m + obj.value;
- }), 0) / group.length
- });
- return true;
- });
- };
- preen(50, "YYYY");
- preen(moment().format('MM'), "YYYYMM");
- thisMonth = moment().format('YYYYMM');
- newHistory = newHistory.concat(_.filter(history, function(h) {
- return moment(h.date).format('YYYYMM') === thisMonth;
- }));
- return newHistory;
-};
diff --git a/common/script/libs/preenTodos.js b/common/script/libs/preenTodos.js
index f07a49c9d0..fcf8775d5f 100644
--- a/common/script/libs/preenTodos.js
+++ b/common/script/libs/preenTodos.js
@@ -1,14 +1,12 @@
import moment from 'moment';
import _ from 'lodash';
-/*
- Preen 3-day past-completed To-Dos from Angular & mobile app
- */
+// TODO used only in v2
-module.exports = function(tasks) {
- return _.filter(tasks, function(t) {
- return !t.completed || (t.challenge && t.challenge.id) || moment(t.dateCompleted).isAfter(moment().subtract({
- days: 3
+module.exports = function preenTodos (tasks) {
+ return _.filter(tasks, (t) => {
+ return !t.completed || t.challenge && t.challenge.id || moment(t.dateCompleted).isAfter(moment().subtract({
+ days: 3,
}));
});
};
diff --git a/common/script/libs/refPush.js b/common/script/libs/refPush.js
index 06b3617014..1bdddc9edf 100644
--- a/common/script/libs/refPush.js
+++ b/common/script/libs/refPush.js
@@ -7,13 +7,14 @@ import uuid from './uuid';
no problem. To maintain sorting, we use these helper functions:
*/
-module.exports = function(reflist, item, prune) {
- if (prune == null) {
- prune = 0;
- }
+module.exports = function refPush (reflist, item) {
item.sort = _.isEmpty(reflist) ? 0 : _.max(reflist, 'sort').sort + 1;
+
if (!(item.id && !reflist[item.id])) {
item.id = uuid();
}
- return reflist[item.id] = item;
+
+ reflist[item.id] = item;
+
+ return reflist[item.id];
};
diff --git a/common/script/libs/removeWhitespace.js b/common/script/libs/removeWhitespace.js
deleted file mode 100644
index 1015beda54..0000000000
--- a/common/script/libs/removeWhitespace.js
+++ /dev/null
@@ -1,10 +0,0 @@
-/*
-Remove whitespace #FIXME are we using this anywwhere? Should we be?
- */
-
-module.exports = function(str) {
- if (!str) {
- return '';
- }
- return str.replace(/\s/g, '');
-};
diff --git a/common/script/libs/silver.js b/common/script/libs/silver.js
index 0dbae97b05..1d3620f602 100644
--- a/common/script/libs/silver.js
+++ b/common/script/libs/silver.js
@@ -2,10 +2,13 @@
Silver amount from their money
*/
-module.exports = function(num) {
+// TODO move to client
+
+module.exports = function silver (num) {
if (num) {
- return ("0" + Math.floor((num - Math.floor(num)) * 100)).slice(-2);
+ let centCount = Math.floor((num - Math.floor(num)) * 100);
+ return `0${centCount}`.slice(-2);
} else {
- return "00";
+ return '00';
}
};
diff --git a/common/script/libs/splitWhitespace.js b/common/script/libs/splitWhitespace.js
index 1ef3d513aa..2f8276bcb3 100644
--- a/common/script/libs/splitWhitespace.js
+++ b/common/script/libs/splitWhitespace.js
@@ -1,3 +1,4 @@
-module.exports = function(s) {
+
+module.exports = function splitWhitespace (s) {
return s.split(' ');
};
diff --git a/common/script/libs/statsComputed.js b/common/script/libs/statsComputed.js
new file mode 100644
index 0000000000..a239b2039c
--- /dev/null
+++ b/common/script/libs/statsComputed.js
@@ -0,0 +1,28 @@
+import _ from 'lodash';
+import content from '../content/index';
+import * as statHelpers from '../statHelpers';
+
+module.exports = function statsComputed (user) {
+ let paths = ['stats', 'stats.buffs', 'items.gear.equipped.weapon', 'items.gear.equipped.armor',
+ 'items.gear.equipped.head', 'items.gear.equipped.shield'];
+ let computed = _.reduce(['per', 'con', 'str', 'int'], (m, stat) => {
+ m[stat] = _.reduce(paths, (m2, path) => {
+ let val = _.get(user, path);
+ let item = content.gear.flat[val];
+ if (!item) item = {};
+ if (!item[stat]) {
+ item[stat] = 0;
+ } else {
+ item[stat] = Number(item[stat]);
+ }
+ let thisMultiplier = item.klass === user.stats.class || item.specialClass === user.stats.class ? 1.5 : 1;
+ let thisReturn = path.indexOf('items.gear') !== -1 ? item[stat] * thisMultiplier : Number(val[stat]);
+ return m2 + thisReturn || 0;
+ }, 0);
+ m[stat] += Math.floor(statHelpers.capByLevel(user.stats.lvl) / 2);
+ return m;
+ }, {});
+
+ computed.maxMP = computed.int * 2 + 30;
+ return computed;
+};
diff --git a/common/script/libs/taskClasses.js b/common/script/libs/taskClasses.js
index 21bed1ad63..9ef223d9f7 100644
--- a/common/script/libs/taskClasses.js
+++ b/common/script/libs/taskClasses.js
@@ -1,51 +1,45 @@
import {
- shouldDo
+ shouldDo,
} from '../cron';
+
/*
Task classes given everything about the class
*/
-module.exports = function(task, filters, dayStart, lastCron, showCompleted, main) {
- var classes, completed, enabled, filter, priority, ref, repeat, type, value;
- if (filters == null) {
- filters = [];
- }
- if (dayStart == null) {
- dayStart = 0;
- }
- if (lastCron == null) {
- lastCron = +(new Date);
- }
- if (showCompleted == null) {
- showCompleted = false;
- }
- if (main == null) {
- main = false;
- }
+
+// TODO move to the client
+
+module.exports = function taskClasses (task, filters = [], dayStart = 0, lastCron = Number(new Date()), showCompleted = false, main = false) {
if (!task) {
- return;
+ return '';
}
- type = task.type, completed = task.completed, value = task.value, repeat = task.repeat, priority = task.priority;
- if (main) {
- if (!task._editing) {
- for (filter in filters) {
- enabled = filters[filter];
- if (enabled && !((ref = task.tags) != null ? ref[filter] : void 0)) {
- return 'hidden';
- }
+ let type = task.type;
+ let classes = task.type;
+ let completed = task.completed;
+ let value = task.value;
+ let priority = task.priority;
+
+ if (main && !task._editing) {
+ for (let filter in filters) {
+ let enabled = filters[filter];
+ if (!task.tags) task.tags = [];
+ if (enabled && task.tags.indexOf(filter) === -1) {
+ return 'hidden';
}
}
}
- classes = type;
+
+ classes = task.type;
if (task._editing) {
- classes += " beingEdited";
+ classes += ' beingEdited';
}
+
if (type === 'todo' || type === 'daily') {
- if (completed || (type === 'daily' && !shouldDo(+(new Date), task, {
- dayStart: dayStart
+ if (completed || (type === 'daily' && !shouldDo(Number(new Date()), task, { // eslint-disable-line no-extra-parens
+ dayStart,
}))) {
- classes += " completed";
+ classes += ' completed';
} else {
- classes += " uncompleted";
+ classes += ' uncompleted';
}
} else if (type === 'habit') {
if (task.down && task.up) {
@@ -55,6 +49,7 @@ module.exports = function(task, filters, dayStart, lastCron, showCompleted, main
classes += ' habit-narrow';
}
}
+
if (priority === 0.1) {
classes += ' difficulty-trivial';
} else if (priority === 1) {
@@ -64,6 +59,7 @@ module.exports = function(task, filters, dayStart, lastCron, showCompleted, main
} else if (priority === 2) {
classes += ' difficulty-hard';
}
+
if (value < -20) {
classes += ' color-worst';
} else if (value < -10) {
@@ -79,5 +75,6 @@ module.exports = function(task, filters, dayStart, lastCron, showCompleted, main
} else {
classes += ' color-best';
}
+
return classes;
};
diff --git a/common/script/libs/taskDefaults.js b/common/script/libs/taskDefaults.js
index 69c815e4fd..e6bdba3def 100644
--- a/common/script/libs/taskDefaults.js
+++ b/common/script/libs/taskDefaults.js
@@ -1,71 +1,74 @@
-import uuid from './uuid';
+import { v4 as uuid } from 'uuid';
import _ from 'lodash';
+import moment from 'moment';
-/*
-Even though Mongoose handles task defaults, we want to make sure defaults are set on the client-side before
-sending up to the server for performance
- */
+// Even though Mongoose handles task defaults, we want to make sure defaults are set on the client-side before
+// sending up to the server for performance
-// TODO revisit
+// TODO move to client code?
-module.exports = function(task) {
- var defaults, ref, ref1, ref2;
- if (task == null) {
- task = {};
- }
- if (!(task.type && ((ref = task.type) === 'habit' || ref === 'daily' || ref === 'todo' || ref === 'reward'))) {
+const tasksTypes = ['habit', 'daily', 'todo', 'reward'];
+
+module.exports = function taskDefaults (task = {}) {
+ if (!task.type || tasksTypes.indexOf(task.type) === -1) {
task.type = 'habit';
}
- defaults = {
- id: uuid(),
- text: task.id != null ? task.id : '',
+
+ let defaultId = uuid();
+ let defaults = {
+ _id: defaultId,
+ text: task._id || defaultId,
notes: '',
+ tags: [],
+ value: task.type === 'reward' ? 10 : 0,
priority: 1,
challenge: {},
+ reminders: [],
attribute: 'str',
- dateCreated: new Date()
+ createdAt: new Date(), // TODO these are going to be overwritten by the server...
+ updatedAt: new Date(),
};
+
_.defaults(task, defaults);
+
+ if (task.type === 'habit' || task.type === 'daily') {
+ _.defaults(task, {
+ history: [],
+ });
+ }
+
+ if (task.type === 'todo' || task.type === 'daily') {
+ _.defaults(task, {
+ completed: false,
+ collapseChecklist: false,
+ checklist: [],
+ });
+ }
+
if (task.type === 'habit') {
_.defaults(task, {
up: true,
- down: true
- });
- }
- if ((ref1 = task.type) === 'habit' || ref1 === 'daily') {
- _.defaults(task, {
- history: []
- });
- }
- if ((ref2 = task.type) === 'daily' || ref2 === 'todo') {
- _.defaults(task, {
- completed: false
+ down: true,
});
}
+
if (task.type === 'daily') {
_.defaults(task, {
streak: 0,
repeat: {
- su: true,
m: true,
t: true,
w: true,
th: true,
f: true,
- s: true
- }
- }, {
- startDate: new Date(),
+ s: true,
+ su: true,
+ },
+ startDate: moment().startOf('day').toDate(),
everyX: 1,
- frequency: 'weekly'
+ frequency: 'weekly',
});
}
- task._id = task.id;
- if (task.value == null) {
- task.value = task.type === 'reward' ? 10 : 0;
- }
- if (!_.isNumber(task.priority)) {
- task.priority = 1;
- }
+
return task;
};
diff --git a/common/script/libs/updateStore.js b/common/script/libs/updateStore.js
index 05d009fd9d..f6593de8fd 100644
--- a/common/script/libs/updateStore.js
+++ b/common/script/libs/updateStore.js
@@ -1,36 +1,31 @@
import _ from 'lodash';
import content from '../content/index';
-/*
- Update the in-browser store with new gear. FIXME this was in user.fns, but it was causing strange issues there
- */
+// Return the list of gear items available for purchase
-var sortOrder = _.reduce(content.gearTypes, (function(m, v, k) {
- m[v] = k;
- return m;
-}), {});
+let sortOrder = _.reduce(content.gearTypes, (accumulator, val, key) => {
+ accumulator[val] = key;
+ return accumulator;
+}, {});
-module.exports = function(user) {
- var changes;
- if (!user) {
- return;
- }
- changes = [];
- _.each(content.gearTypes, function(type) {
- var found;
- found = _.find(content.gear.tree[type][user.stats["class"]], function(item) {
+module.exports = function updateStore (user) {
+ let changes = [];
+
+ _.each(content.gearTypes, (type) => {
+ let found = _.find(content.gear.tree[type][user.stats.class], (item) => {
return !user.items.gear.owned[item.key];
});
- if (found) {
- changes.push(found);
+
+ if (found) changes.push(found);
+ });
+
+ changes = changes.concat(_.filter(content.gear.flat, (val) => {
+ if (['special', 'mystery', 'armoire'].indexOf(val.klass) !== -1 && !user.items.gear.owned[val.key] && (val.canOwn ? val.canOwn(user) : false)) {
+ return true;
+ } else {
+ return false;
}
- return true;
- });
- changes = changes.concat(_.filter(content.gear.flat, function(v) {
- var ref;
- return ((ref = v.klass) === 'special' || ref === 'mystery' || ref === 'armoire') && !user.items.gear.owned[v.key] && (typeof v.canOwn === "function" ? v.canOwn(user) : void 0);
}));
- return _.sortBy(changes, function(c) {
- return sortOrder[c.type];
- });
+
+ return _.sortBy(changes, (change) => sortOrder[change.type]);
};
diff --git a/common/script/libs/uuid.js b/common/script/libs/uuid.js
index 4a26440d7d..63f75cf398 100644
--- a/common/script/libs/uuid.js
+++ b/common/script/libs/uuid.js
@@ -1,9 +1,4 @@
-// TODO use node-uuid module
-module.exports = function() {
- return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) {
- var r, v;
- r = Math.random() * 16 | 0;
- v = (c === "x" ? r : r & 0x3 | 0x8);
- return v.toString(16);
- });
-};
+import uuid from 'uuid';
+
+// TODO remove this file completely
+module.exports = uuid.v4;
diff --git a/common/script/ops/addPushDevice.js b/common/script/ops/addPushDevice.js
index d96cf249cf..a909fe9feb 100644
--- a/common/script/ops/addPushDevice.js
+++ b/common/script/ops/addPushDevice.js
@@ -1,20 +1,41 @@
import _ from 'lodash';
+import i18n from '../i18n';
+import {
+ BadRequest,
+ NotAuthorized,
+} from '../libs/errors';
+
+// TODO move to server code
+module.exports = function addPushDevice (user, req = {}) {
+ let regId = _.get(req, 'body.regId');
+ if (!regId) throw new BadRequest(i18n.t('regIdRequired', req.language));
+
+ let type = _.get(req, 'body.type');
+ if (!type) throw new BadRequest(i18n.t('typeRequired', req.language));
-module.exports = function(user, req, cb) {
- var i, item, pd;
if (!user.pushDevices) {
user.pushDevices = [];
}
- pd = user.pushDevices;
- item = {
- regId: req.body.regId,
- type: req.body.type
+
+ let pushDevices = user.pushDevices;
+
+ let item = {
+ regId,
+ type,
};
- i = _.findIndex(pd, {
- regId: item.regId
+
+ let indexOfPushDevice = _.findIndex(pushDevices, {
+ regId: item.regId,
});
- if (i === -1) {
- pd.push(item);
+
+ if (indexOfPushDevice !== -1) {
+ throw new NotAuthorized(i18n.t('pushDeviceAlreadyAdded', req.language));
}
- return typeof cb === "function" ? cb(null, user.pushDevices) : void 0;
+
+ pushDevices.push(item);
+
+ return [
+ user.pushDevices,
+ i18n.t('pushDeviceAdded', req.language),
+ ];
};
diff --git a/common/script/ops/addTag.js b/common/script/ops/addTag.js
index a020a5fbaf..b44ead8d2e 100644
--- a/common/script/ops/addTag.js
+++ b/common/script/ops/addTag.js
@@ -1,12 +1,17 @@
import uuid from '../libs/uuid';
+import _ from 'lodash';
-module.exports = function(user, req, cb) {
- if (user.tags == null) {
+// TODO used only in client, move there?
+
+module.exports = function addTag (user, req = {}) {
+ if (!user.tags) {
user.tags = [];
}
+
user.tags.push({
name: req.body.name,
- id: req.body.id || uuid()
+ id: _.get(req, 'body.id') || uuid(),
});
- return typeof cb === "function" ? cb(null, user.tags) : void 0;
+
+ return user.tags;
};
diff --git a/common/script/ops/addTask.js b/common/script/ops/addTask.js
index a2d1895e1e..2c4ea159e2 100644
--- a/common/script/ops/addTask.js
+++ b/common/script/ops/addTask.js
@@ -1,27 +1,15 @@
import taskDefaults from '../libs/taskDefaults';
-import i18n from '../i18n';
-module.exports = function(user, req, cb) {
- var task;
- task = taskDefaults(req.body);
- if (user.tasks[task.id] != null) {
- return typeof cb === "function" ? cb({
- code: 409,
- message: i18n.t('messageDuplicateTaskID', req.language)
- }) : void 0;
- }
- user[task.type + "s"].unshift(task);
- if (user.preferences.newTaskEdit) {
- task._editing = true;
- }
- if (user.preferences.tagsCollapsed) {
- task._tags = true;
- }
- if (!user.preferences.advancedCollapsed) {
- task._advanced = true;
- }
- if (typeof cb === "function") {
- cb(null, task);
- }
+// TODO move to client since it's only used there?
+
+module.exports = function addTask (user, req = {body: {}}) {
+ let task = taskDefaults(req.body);
+ user.tasksOrder[`${task.type}s`].unshift(task._id);
+ user[`${task.type}s`].unshift(task);
+
+ task._editing = user.preferences.newTaskEdit;
+ task._tags = !user.preferences.tagsCollapsed;
+ task._advanced = !user.preferences.advancedCollapsed;
+
return task;
};
diff --git a/common/script/ops/addWebhook.js b/common/script/ops/addWebhook.js
index 99eaf49bba..c308d1b9e5 100644
--- a/common/script/ops/addWebhook.js
+++ b/common/script/ops/addWebhook.js
@@ -1,15 +1,27 @@
import refPush from '../libs/refPush';
+import validator from 'validator';
+import i18n from '../i18n';
+import {
+ BadRequest,
+} from '../libs/errors';
+import _ from 'lodash';
-module.exports = function(user, req, cb) {
- var wh;
- wh = user.preferences.webhooks;
- refPush(wh, {
- url: req.body.url,
- enabled: req.body.enabled || true,
- id: req.body.id
- });
- if (typeof user.markModified === "function") {
- user.markModified('preferences.webhooks');
+module.exports = function addWebhook (user, req = {}) {
+ let wh = user.preferences.webhooks;
+
+ if (!validator.isURL(_.get(req, 'body.url'))) throw new BadRequest(i18n.t('invalidUrl', req.language));
+ if (!validator.isBoolean(_.get(req, 'body.enabled'))) throw new BadRequest(i18n.t('invalidEnabled', req.language));
+
+ user.markModified('preferences.webhooks');
+
+ if (req.v2 === true) {
+ return user.preferences.webhooks;
+ } else {
+ return [
+ refPush(wh, {
+ url: req.body.url,
+ enabled: req.body.enabled,
+ }),
+ ];
}
- return typeof cb === "function" ? cb(null, user.preferences.webhooks) : void 0;
};
diff --git a/common/script/ops/allocate.js b/common/script/ops/allocate.js
index 92b5ae53fa..8e07e09589 100644
--- a/common/script/ops/allocate.js
+++ b/common/script/ops/allocate.js
@@ -1,15 +1,31 @@
import _ from 'lodash';
-import splitWhitespace from '../libs/splitWhitespace';
+import {
+ ATTRIBUTES,
+} from '../constants';
+import {
+ BadRequest,
+ NotAuthorized,
+} from '../libs/errors';
+import i18n from '../i18n';
+
+module.exports = function allocate (user, req = {}) {
+ let stat = _.get(req, 'query.stat', 'str');
+
+ if (ATTRIBUTES.indexOf(stat) === -1) {
+ throw new BadRequest(i18n.t('invalidAttribute', {attr: stat}, req.language));
+ }
-module.exports = function(user, req, cb) {
- var stat;
- stat = req.query.stat || 'str';
if (user.stats.points > 0) {
user.stats[stat]++;
user.stats.points--;
if (stat === 'int') {
user.stats.mp++;
}
+ } else {
+ throw new NotAuthorized(i18n.t('notEnoughAttrPoints', req.language));
}
- return typeof cb === "function" ? cb(null, _.pick(user, splitWhitespace('stats'))) : void 0;
+
+ return [
+ user.stats,
+ ];
};
diff --git a/common/script/ops/allocateNow.js b/common/script/ops/allocateNow.js
index 815c0b8959..e8ae5d249c 100644
--- a/common/script/ops/allocateNow.js
+++ b/common/script/ops/allocateNow.js
@@ -1,10 +1,15 @@
import _ from 'lodash';
+import autoAllocate from '../fns/autoAllocate';
-module.exports = function(user, req, cb) {
- _.times(user.stats.points, user.fns.autoAllocate);
+module.exports = function allocateNow (user, req = {}) {
+ _.times(user.stats.points, () => autoAllocate(user));
user.stats.points = 0;
- if (typeof user.markModified === "function") {
- user.markModified('stats');
+
+ if (req.v2 === true) {
+ return _.pick(user, 'stats');
+ } else {
+ return [
+ user.stats,
+ ];
}
- return typeof cb === "function" ? cb(null, user.stats) : void 0;
};
diff --git a/common/script/ops/blockUser.js b/common/script/ops/blockUser.js
index dd08925640..5c123735ed 100644
--- a/common/script/ops/blockUser.js
+++ b/common/script/ops/blockUser.js
@@ -1,13 +1,21 @@
-module.exports = function(user, req, cb) {
- var i;
- i = user.inbox.blocks.indexOf(req.params.uuid);
- if (~i) {
- user.inbox.blocks.splice(i, 1);
- } else {
+import validator from 'validator';
+import i18n from '../i18n';
+import {
+ BadRequest,
+} from '../libs/errors';
+
+module.exports = function blockUser (user, req = {}) {
+ if (!validator.isUUID(req.params.uuid)) throw new BadRequest(i18n.t('invalidUUID', req.language));
+
+ let i = user.inbox.blocks.indexOf(req.params.uuid);
+ if (i === -1) {
user.inbox.blocks.push(req.params.uuid);
+ } else {
+ user.inbox.blocks.splice(i, 1);
}
- if (typeof user.markModified === "function") {
- user.markModified('inbox.blocks');
- }
- return typeof cb === "function" ? cb(null, user.inbox.blocks) : void 0;
+
+ user.markModified('inbox.blocks');
+ return [
+ user.inbox.blocks,
+ ];
};
diff --git a/common/script/ops/buy.js b/common/script/ops/buy.js
index 1686316264..ded5b034d2 100644
--- a/common/script/ops/buy.js
+++ b/common/script/ops/buy.js
@@ -1,119 +1,24 @@
-import content from '../content/index';
import i18n from '../i18n';
import _ from 'lodash';
-import count from '../count';
-import splitWhitespace from '../libs/splitWhitespace';
+import {
+ BadRequest,
+} from '../libs/errors';
+import buyHealthPotion from './buyHealthPotion';
+import buyArmoire from './buyArmoire';
+import buyGear from './buyGear';
-module.exports = function(user, req, cb, analytics) {
- var analyticsData, armoireExp, armoireResp, armoireResult, base, buyResp, drop, eligibleEquipment, item, key, message, name;
- key = req.params.key;
- item = key === 'potion' ? content.potion : key === 'armoire' ? content.armoire : content.gear.flat[key];
- if (!item) {
- return typeof cb === "function" ? cb({
- code: 404,
- message: "Item '" + key + " not found (see https://github.com/HabitRPG/habitrpg/blob/develop/common/script/content/index.js)"
- }) : void 0;
- }
- if (user.stats.gp < item.value) {
- return typeof cb === "function" ? cb({
- code: 401,
- message: i18n.t('messageNotEnoughGold', req.language)
- }) : void 0;
- }
- if ((item.canOwn != null) && !item.canOwn(user)) {
- return typeof cb === "function" ? cb({
- code: 401,
- message: "You can't buy this item"
- }) : void 0;
- }
- armoireResp = void 0;
- if (item.key === 'potion') {
- user.stats.hp += 15;
- if (user.stats.hp > 50) {
- user.stats.hp = 50;
- }
- } else if (item.key === 'armoire') {
- armoireResult = user.fns.predictableRandom(user.stats.gp);
- eligibleEquipment = _.filter(content.gear.flat, (function(i) {
- return i.klass === 'armoire' && !user.items.gear.owned[i.key];
- }));
- if (!_.isEmpty(eligibleEquipment) && (armoireResult < .6 || !user.flags.armoireOpened)) {
- eligibleEquipment.sort();
- drop = user.fns.randomVal(eligibleEquipment);
- user.items.gear.owned[drop.key] = true;
- user.flags.armoireOpened = true;
- message = i18n.t('armoireEquipment', {
- image: '',
- dropText: drop.text(req.language)
- }, req.language);
- if (count.remainingGearInSet(user.items.gear.owned, 'armoire') === 0) {
- user.flags.armoireEmpty = true;
- }
- armoireResp = {
- type: "gear",
- dropKey: drop.key,
- dropText: drop.text(req.language)
- };
- } else if ((!_.isEmpty(eligibleEquipment) && armoireResult < .8) || armoireResult < .5) {
- drop = user.fns.randomVal(_.where(content.food, {
- canDrop: true
- }));
- if ((base = user.items.food)[name = drop.key] == null) {
- base[name] = 0;
- }
- user.items.food[drop.key] += 1;
- message = i18n.t('armoireFood', {
- image: '',
- dropArticle: drop.article,
- dropText: drop.text(req.language)
- }, req.language);
- armoireResp = {
- type: "food",
- dropKey: drop.key,
- dropArticle: drop.article,
- dropText: drop.text(req.language)
- };
- } else {
- armoireExp = Math.floor(user.fns.predictableRandom(user.stats.exp) * 40 + 10);
- user.stats.exp += armoireExp;
- message = i18n.t('armoireExp', req.language);
- armoireResp = {
- "type": "experience",
- "value": armoireExp
- };
- }
+module.exports = function buy (user, req = {}, analytics) {
+ let key = _.get(req, 'params.key');
+ if (!key) throw new BadRequest(i18n.t('missingKeyParam', req.language));
+
+ let buyRes;
+ if (key === 'potion') {
+ buyRes = buyHealthPotion(user, req, analytics);
+ } else if (key === 'armoire') {
+ buyRes = buyArmoire(user, req, analytics);
} else {
- if (user.preferences.autoEquip) {
- user.items.gear.equipped[item.type] = item.key;
- message = user.fns.handleTwoHanded(item, null, req);
- }
- user.items.gear.owned[item.key] = true;
- if (message == null) {
- message = i18n.t('messageBought', {
- itemText: item.text(req.language)
- }, req.language);
- }
- if (item.last) {
- user.fns.ultimateGear();
- }
+ buyRes = buyGear(user, req, analytics);
}
- user.stats.gp -= item.value;
- analyticsData = {
- uuid: user._id,
- itemKey: key,
- acquireMethod: 'Gold',
- goldCost: item.value,
- category: 'behavior'
- };
- if (analytics != null) {
- analytics.track('acquire item', analyticsData);
- }
- buyResp = _.pick(user, splitWhitespace('items achievements stats flags'));
- if (armoireResp) {
- buyResp["armoire"] = armoireResp;
- }
- return typeof cb === "function" ? cb({
- code: 200,
- message: message
- }, buyResp) : void 0;
+
+ return buyRes;
};
diff --git a/common/script/ops/buyArmoire.js b/common/script/ops/buyArmoire.js
new file mode 100644
index 0000000000..e183c06984
--- /dev/null
+++ b/common/script/ops/buyArmoire.js
@@ -0,0 +1,115 @@
+import content from '../content/index';
+import i18n from '../i18n';
+import _ from 'lodash';
+import count from '../count';
+import splitWhitespace from '../libs/splitWhitespace';
+import {
+ NotAuthorized,
+} from '../libs/errors';
+import predictableRandom from '../fns/predictableRandom';
+import randomVal from '../fns/randomVal';
+
+module.exports = function buyArmoire (user, req = {}, analytics) {
+ let item = content.armoire;
+
+ if (user.stats.gp < item.value) {
+ throw new NotAuthorized(i18n.t('messageNotEnoughGold', req.language));
+ }
+
+ if (item.canOwn && !item.canOwn(user)) {
+ throw new NotAuthorized(i18n.t('cannotBuyItem', req.language));
+ }
+
+ let armoireResp;
+ let armoireResult;
+ let eligibleEquipment;
+ let drop;
+ let message;
+
+ armoireResult = predictableRandom(user, user.stats.gp);
+ eligibleEquipment = _.filter(content.gear.flat, (eligible) => {
+ return eligible.klass === 'armoire' && !user.items.gear.owned[eligible.key];
+ });
+
+ if (!_.isEmpty(eligibleEquipment) && (armoireResult < 0.6 || !user.flags.armoireOpened)) {
+ eligibleEquipment.sort();
+ drop = randomVal(user, eligibleEquipment);
+
+ if (user.items.gear.owned[drop.key]) {
+ throw new NotAuthorized(i18n.t('equipmentAlradyOwned', req.language));
+ }
+
+ user.items.gear.owned[drop.key] = true;
+ user.flags.armoireOpened = true;
+ message = i18n.t('armoireEquipment', {
+ image: ``,
+ dropText: drop.text(req.language),
+ }, req.language);
+
+ if (count.remainingGearInSet(user.items.gear.owned, 'armoire') === 0) {
+ user.flags.armoireEmpty = true;
+ }
+
+ armoireResp = {
+ type: 'gear',
+ dropKey: drop.key,
+ dropText: drop.text(req.language),
+ };
+ } else if ((!_.isEmpty(eligibleEquipment) && armoireResult < 0.8) || armoireResult < 0.5) { // eslint-disable-line no-extra-parens
+ drop = randomVal(user, _.where(content.food, {
+ canDrop: true,
+ }));
+ user.items.food[drop.key] = user.items.food[drop.key] || 0;
+ user.items.food[drop.key] += 1;
+
+ message = i18n.t('armoireFood', {
+ image: ``,
+ dropArticle: drop.article,
+ dropText: drop.text(req.language),
+ }, req.language);
+ armoireResp = {
+ type: 'food',
+ dropKey: drop.key,
+ dropArticle: drop.article,
+ dropText: drop.text(req.language),
+ };
+ } else {
+ let armoireExp = Math.floor(predictableRandom(user, user.stats.exp) * 40 + 10);
+ user.stats.exp += armoireExp;
+ message = i18n.t('armoireExp', req.language);
+ armoireResp = {
+ type: 'experience',
+ value: armoireExp,
+ };
+ }
+
+ user.stats.gp -= item.value;
+
+ if (!message) {
+ message = i18n.t('messageBought', {
+ itemText: item.text(req.language),
+ }, req.language);
+ }
+
+ if (analytics) {
+ analytics.track('acquire item', {
+ uuid: user._id,
+ itemKey: 'Armoire',
+ acquireMethod: 'Gold',
+ goldCost: item.value,
+ category: 'behavior',
+ });
+ }
+
+ let resData = _.pick(user, splitWhitespace('items flags'));
+ if (armoireResp) resData.armoire = armoireResp;
+
+ if (req.v2 === true) {
+ return resData;
+ } else {
+ return [
+ resData,
+ message,
+ ];
+ }
+};
diff --git a/common/script/ops/buyGear.js b/common/script/ops/buyGear.js
new file mode 100644
index 0000000000..e4f4eb3b68
--- /dev/null
+++ b/common/script/ops/buyGear.js
@@ -0,0 +1,70 @@
+import content from '../content/index';
+import i18n from '../i18n';
+import _ from 'lodash';
+import splitWhitespace from '../libs/splitWhitespace';
+import {
+ BadRequest,
+ NotAuthorized,
+ NotFound,
+} from '../libs/errors';
+import handleTwoHanded from '../fns/handleTwoHanded';
+import ultimateGear from '../fns/ultimateGear';
+
+module.exports = function buyGear (user, req = {}, analytics) {
+ let key = _.get(req, 'params.key');
+ if (!key) throw new BadRequest(i18n.t('missingKeyParam', req.language));
+
+ let item = content.gear.flat[key];
+
+ if (!item) throw new NotFound(i18n.t('itemNotFound', {key}, req.language));
+
+ if (user.stats.gp < item.value) {
+ throw new NotAuthorized(i18n.t('messageNotEnoughGold', req.language));
+ }
+
+ if (item.canOwn && !item.canOwn(user)) {
+ throw new NotAuthorized(i18n.t('cannotBuyItem', req.language));
+ }
+
+ let message;
+
+ if (user.items.gear.owned[item.key]) {
+ throw new NotAuthorized(i18n.t('equipmentAlreadyOwned', req.language));
+ }
+
+ if (user.preferences.autoEquip) {
+ user.items.gear.equipped[item.type] = item.key;
+ message = handleTwoHanded(user, item, undefined, req);
+ }
+
+ user.items.gear.owned[item.key] = true;
+
+ if (item.last) ultimateGear(user);
+
+ user.stats.gp -= item.value;
+
+ if (!message) {
+ message = i18n.t('messageBought', {
+ itemText: item.text(req.language),
+ }, req.language);
+ }
+
+ if (analytics) {
+ analytics.track('acquire item', {
+ uuid: user._id,
+ itemKey: key,
+ acquireMethod: 'Gold',
+ goldCost: item.value,
+ category: 'behavior',
+ });
+ }
+
+ if (req.v2 === true) {
+ return _.pick(user, splitWhitespace('items achievements stats flags'));
+ } else {
+ return [
+ _.pick(user, splitWhitespace('items achievements stats flags')),
+ message,
+ ];
+ }
+};
diff --git a/common/script/ops/buyHealthPotion.js b/common/script/ops/buyHealthPotion.js
new file mode 100644
index 0000000000..1a6c8b0e18
--- /dev/null
+++ b/common/script/ops/buyHealthPotion.js
@@ -0,0 +1,48 @@
+import content from '../content/index';
+import i18n from '../i18n';
+import {
+ NotAuthorized,
+} from '../libs/errors';
+
+module.exports = function buyHealthPotion (user, req = {}, analytics) {
+ let item = content.potion;
+
+ if (user.stats.gp < item.value) {
+ throw new NotAuthorized(i18n.t('messageNotEnoughGold', req.language));
+ }
+
+ if (item.canOwn && !item.canOwn(user)) {
+ throw new NotAuthorized(i18n.t('cannotBuyItem', req.language));
+ }
+
+ user.stats.hp += 15;
+ if (user.stats.hp > 50) {
+ user.stats.hp = 50;
+ }
+
+ user.stats.gp -= item.value;
+
+ let message = i18n.t('messageBought', {
+ itemText: item.text(req.language),
+ }, req.language);
+
+
+ if (analytics) {
+ analytics.track('acquire item', {
+ uuid: user._id,
+ itemKey: 'Potion',
+ acquireMethod: 'Gold',
+ goldCost: item.value,
+ category: 'behavior',
+ });
+ }
+
+ if (req.v2 === true) {
+ return user.stats;
+ } else {
+ return [
+ user.stats,
+ message,
+ ];
+ }
+};
diff --git a/common/script/ops/buyMysterySet.js b/common/script/ops/buyMysterySet.js
index 44ccdb9aaf..acf0014279 100644
--- a/common/script/ops/buyMysterySet.js
+++ b/common/script/ops/buyMysterySet.js
@@ -2,42 +2,54 @@ import i18n from '../i18n';
import content from '../content/index';
import _ from 'lodash';
import splitWhitespace from '../libs/splitWhitespace';
+import pickDeep from '../libs/pickDeep';
+import {
+ BadRequest,
+ NotAuthorized,
+ NotFound,
+} from '../libs/errors';
+
+module.exports = function buyMysterySet (user, req = {}, analytics) {
+ let key = _.get(req, 'params.key');
+ if (!key) throw new BadRequest(i18n.t('missingKeyParam', req.language));
-module.exports = function(user, req, cb, analytics) {
- var mysterySet, ref;
if (!(user.purchased.plan.consecutive.trinkets > 0)) {
- return typeof cb === "function" ? cb({
- code: 401,
- message: i18n.t('notEnoughHourglasses', req.language)
- }) : void 0;
- }
- mysterySet = (ref = content.timeTravelerStore(user.items.gear.owned)) != null ? ref[req.params.key] : void 0;
- if ((typeof window !== "undefined" && window !== null ? window.confirm : void 0) != null) {
- if (!window.confirm(i18n.t('hourglassBuyEquipSetConfirm'))) {
- return;
- }
+ throw new NotAuthorized(i18n.t('notEnoughHourglasses', req.language));
}
+
+ let ref = content.timeTravelerStore(user.items.gear.owned);
+ let mysterySet = ref ? ref[key] : undefined;
+
if (!mysterySet) {
- return typeof cb === "function" ? cb({
- code: 404,
- message: "Mystery set not found, or set already owned"
- }) : void 0;
+ throw new NotFound(i18n.t('mysterySetNotFound', req.language));
}
- _.each(mysterySet.items, function(i) {
- var analyticsData;
- user.items.gear.owned[i.key] = true;
- analyticsData = {
- uuid: user._id,
- itemKey: i.key,
- itemType: 'Subscriber Gear',
- acquireMethod: 'Hourglass',
- category: 'behavior'
- };
- return analytics != null ? analytics.track('acquire item', analyticsData) : void 0;
+
+ if (typeof window !== 'undefined' && window.confirm) { // TODO move to client
+ if (!window.confirm(i18n.t('hourglassBuyEquipSetConfirm'))) return;
+ }
+
+ _.each(mysterySet.items, item => {
+ user.items.gear.owned[item.key] = true;
+ if (analytics) {
+ analytics.track('acquire item', {
+ uuid: user._id,
+ itemKey: item.key,
+ itemType: 'Subscriber Gear',
+ acquireMethod: 'Hourglass',
+ category: 'behavior',
+ });
+ }
});
+
user.purchased.plan.consecutive.trinkets--;
- return typeof cb === "function" ? cb({
- code: 200,
- message: i18n.t('hourglassPurchaseSet', req.language)
- }, _.pick(user, splitWhitespace('items purchased.plan.consecutive'))) : void 0;
+
+
+ if (req.v2 === true) {
+ return pickDeep(user, splitWhitespace('items purchased.plan.consecutive'));
+ } else {
+ return [
+ { items: user.items, purchasedPlanConsecutive: user.purchased.plan.consecutive },
+ i18n.t('hourglassPurchaseSet', req.language),
+ ];
+ }
};
diff --git a/common/script/ops/buyQuest.js b/common/script/ops/buyQuest.js
index b7653d43ce..af7c384419 100644
--- a/common/script/ops/buyQuest.js
+++ b/common/script/ops/buyQuest.js
@@ -1,49 +1,50 @@
import i18n from '../i18n';
import content from '../content/index';
+import {
+ BadRequest,
+ NotAuthorized,
+ NotFound,
+} from '../libs/errors';
+import _ from 'lodash';
+
+// buy a quest with gold
+module.exports = function buyQuest (user, req = {}, analytics) {
+ let key = _.get(req, 'params.key');
+ if (!key) throw new BadRequest(i18n.t('missingKeyParam', req.language));
+
+ let item = content.quests[key];
+ if (!item) throw new NotFound(i18n.t('questNotFound', {key}, req.language));
-module.exports = function(user, req, cb, analytics) {
- var analyticsData, base, item, key, message, name;
- key = req.params.key;
- item = content.quests[key];
- if (!item) {
- return typeof cb === "function" ? cb({
- code: 404,
- message: "Quest '" + key + " not found (see https://github.com/HabitRPG/habitrpg/blob/develop/common/script/content/index.js)"
- }) : void 0;
- }
if (!(item.category === 'gold' && item.goldValue)) {
- return typeof cb === "function" ? cb({
- code: 404,
- message: "Quest '" + key + " is not a Gold-purchasable quest (see https://github.com/HabitRPG/habitrpg/blob/develop/common/script/content/index.js)"
- }) : void 0;
+ throw new NotAuthorized(i18n.t('questNotGoldPurchasable', {key}, req.language));
}
if (user.stats.gp < item.goldValue) {
- return typeof cb === "function" ? cb({
- code: 401,
- message: i18n.t('messageNotEnoughGold', req.language)
- }) : void 0;
+ throw new NotAuthorized(i18n.t('messageNotEnoughGold', req.language));
}
- message = i18n.t('messageBought', {
- itemText: item.text(req.language)
- }, req.language);
- if ((base = user.items.quests)[name = item.key] == null) {
- base[name] = 0;
- }
- user.items.quests[item.key] += 1;
+
+ user.items.quests[item.key] = user.items.quests[item.key] || 0;
+ user.items.quests[item.key]++;
user.stats.gp -= item.goldValue;
- analyticsData = {
- uuid: user._id,
- itemKey: item.key,
- itemType: 'Market',
- goldCost: item.goldValue,
- acquireMethod: 'Gold',
- category: 'behavior'
- };
- if (analytics != null) {
- analytics.track('acquire item', analyticsData);
+
+ if (analytics) {
+ analytics.track('acquire item', {
+ uuid: user._id,
+ itemKey: item.key,
+ itemType: 'Market',
+ goldCost: item.goldValue,
+ acquireMethod: 'Gold',
+ category: 'behavior',
+ });
+ }
+
+ if (req.v2 === true) {
+ return user.items.quests;
+ } else {
+ return [
+ user.items.quests,
+ i18n.t('messageBought', {
+ itemText: item.text(req.language),
+ }, req.language),
+ ];
}
- return typeof cb === "function" ? cb({
- code: 200,
- message: message
- }, user.items.quests) : void 0;
};
diff --git a/common/script/ops/buySpecialSpell.js b/common/script/ops/buySpecialSpell.js
index e2a9dc8deb..20ea0251aa 100644
--- a/common/script/ops/buySpecialSpell.js
+++ b/common/script/ops/buySpecialSpell.js
@@ -2,30 +2,34 @@ import i18n from '../i18n';
import content from '../content/index';
import _ from 'lodash';
import splitWhitespace from '../libs/splitWhitespace';
+import {
+ BadRequest,
+ NotAuthorized,
+ NotFound,
+} from '../libs/errors';
+
+module.exports = function buySpecialSpell (user, req = {}) {
+ let key = _.get(req, 'params.key');
+ if (!key) throw new BadRequest(i18n.t('missingKeyParam', req.language));
+
+ let item = content.special[key];
+ if (!item) throw new NotFound(i18n.t('spellNotFound', {spellId: key}, req.language));
-module.exports = function(user, req, cb) {
- var base, item, key, message;
- key = req.params.key;
- item = content.special[key];
if (user.stats.gp < item.value) {
- return typeof cb === "function" ? cb({
- code: 401,
- message: i18n.t('messageNotEnoughGold', req.language)
- }) : void 0;
+ throw new NotAuthorized(i18n.t('messageNotEnoughGold', req.language));
}
user.stats.gp -= item.value;
- if ((base = user.items.special)[key] == null) {
- base[key] = 0;
- }
+
user.items.special[key]++;
- if (typeof user.markModified === "function") {
- user.markModified('items.special');
+
+ if (req.v2 === true) {
+ return _.pick(user, splitWhitespace('items stats'));
+ } else {
+ return [
+ _.pick(user, splitWhitespace('items stats')),
+ i18n.t('messageBought', {
+ itemText: item.text(req.language),
+ }, req.language),
+ ];
}
- message = i18n.t('messageBought', {
- itemText: item.text(req.language)
- }, req.language);
- return typeof cb === "function" ? cb({
- code: 200,
- message: message
- }, _.pick(user, splitWhitespace('items stats'))) : void 0;
};
diff --git a/common/script/ops/changeClass.js b/common/script/ops/changeClass.js
index 98fd8fd58d..4b3eb6b289 100644
--- a/common/script/ops/changeClass.js
+++ b/common/script/ops/changeClass.js
@@ -2,58 +2,77 @@ import i18n from '../i18n';
import _ from 'lodash';
import splitWhitespace from '../libs/splitWhitespace';
import { capByLevel } from '../statHelpers';
+import {
+ NotAuthorized,
+} from '../libs/errors';
-module.exports = function(user, req, cb, analytics) {
- var analyticsData, klass, ref;
- klass = (ref = req.query) != null ? ref["class"] : void 0;
- if (klass === 'warrior' || klass === 'rogue' || klass === 'wizard' || klass === 'healer') {
- analyticsData = {
- uuid: user._id,
- "class": klass,
- acquireMethod: 'Gems',
- gemCost: 3,
- category: 'behavior'
- };
- if (analytics != null) {
- analytics.track('change class', analyticsData);
- }
- user.stats["class"] = klass;
+module.exports = function changeClass (user, req = {}, analytics) {
+ let klass = _.get(req, 'query.class');
+
+ // user.flags.classSelected is set to false after the user paid the 3 gems
+ if (user.stats.lvl < 10) {
+ throw new NotAuthorized(i18n.t('lvl10ChangeClass', req.language));
+ } else if (!user.flags.classSelected && (klass === 'warrior' || klass === 'rogue' || klass === 'wizard' || klass === 'healer')) {
+ user.stats.class = klass;
user.flags.classSelected = true;
- _.each(["weapon", "armor", "shield", "head"], function(type) {
- var foundKey;
- foundKey = false;
- _.findLast(user.items.gear.owned, function(v, k) {
- if (~k.indexOf(type + "_" + klass) && v === true) {
- return foundKey = k;
+
+ _.each(['weapon', 'armor', 'shield', 'head'], (type) => {
+ let foundKey = false;
+ _.findLast(user.items.gear.owned, (val, key) => {
+ if (key.indexOf(`${type}_${klass}`) !== -1 && val === true) {
+ foundKey = key;
+ return true;
}
});
- user.items.gear.equipped[type] = foundKey ? foundKey : type === "weapon" ? "weapon_" + klass + "_0" : type === "shield" && klass === "rogue" ? "shield_rogue_0" : type + "_base_0";
- if (type === "weapon" || (type === "shield" && klass === "rogue")) {
- user.items.gear.owned[type + "_" + klass + "_0"] = true;
+
+ if (!foundKey) {
+ if (type === 'weapon') {
+ foundKey = `weapon_${klass}_0`;
+ } else if (type === 'shield' && klass === 'rogue') {
+ foundKey = 'shield_rogue_0';
+ } else {
+ foundKey = `${type}_base_0`;
+ }
+ }
+
+ user.items.gear.equipped[type] = foundKey;
+
+ if (type === 'weapon' || (type === 'shield' && klass === 'rogue')) { // eslint-disable-line no-extra-parens
+ user.items.gear.owned[`${type}_${klass}_0`] = true;
}
- return true;
});
+
+ if (analytics) {
+ analytics.track('change class', {
+ uuid: user._id,
+ class: klass,
+ acquireMethod: 'Gems',
+ gemCost: 3,
+ category: 'behavior',
+ });
+ }
} else {
if (user.preferences.disableClasses) {
user.preferences.disableClasses = false;
user.preferences.autoAllocate = false;
} else {
- if (!(user.balance >= .75)) {
- return typeof cb === "function" ? cb({
- code: 401,
- message: i18n.t('notEnoughGems', req.language)
- }) : void 0;
- }
- user.balance -= .75;
+ if (user.balance < 0.75) throw new NotAuthorized(i18n.t('notEnoughGems', req.language));
+ user.balance -= 0.75;
}
- _.merge(user.stats, {
- str: 0,
- con: 0,
- per: 0,
- int: 0,
- points: capByLevel(user.stats.lvl)
- });
+
+ user.stats.str = 0;
+ user.stats.con = 0;
+ user.stats.per = 0;
+ user.stats.int = 0;
+ user.stats.points = capByLevel(user.stats.lvl);
user.flags.classSelected = false;
}
- return typeof cb === "function" ? cb(null, _.pick(user, splitWhitespace('stats flags items preferences'))) : void 0;
+
+ if (req.v2 === true) {
+ return _.pick(user, splitWhitespace('stats flags items preferences'));
+ } else {
+ return [
+ _.pick(user, splitWhitespace('stats flags items preferences')),
+ ];
+ }
};
diff --git a/common/script/ops/clearCompleted.js b/common/script/ops/clearCompleted.js
index d60f12704f..26fb1727d9 100644
--- a/common/script/ops/clearCompleted.js
+++ b/common/script/ops/clearCompleted.js
@@ -1,12 +1,10 @@
import _ from 'lodash';
-module.exports = function(user, req, cb) {
- _.remove(user.todos, function(t) {
- var ref;
- return t.completed && !((ref = t.challenge) != null ? ref.id : void 0);
+// TODO move to client since it's only used there?
+// TODO rename file to clearCompletedTodos
+
+module.exports = function clearCompletedTodos (todos) {
+ _.remove(todos, todo => {
+ return todo.completed && (!todo.challenge || !todo.challenge.id || todo.challenge.broken);
});
- if (typeof user.markModified === "function") {
- user.markModified('todos');
- }
- return typeof cb === "function" ? cb(null, user.todos) : void 0;
};
diff --git a/common/script/ops/clearPMs.js b/common/script/ops/clearPMs.js
index 47e0a6ebc6..765ecc3b56 100644
--- a/common/script/ops/clearPMs.js
+++ b/common/script/ops/clearPMs.js
@@ -1,7 +1,7 @@
-module.exports = function(user, req, cb) {
+module.exports = function clearPMs (user) {
user.inbox.messages = {};
- if (typeof user.markModified === "function") {
- user.markModified('inbox.messages');
- }
- return typeof cb === "function" ? cb(null, user.inbox.messages) : void 0;
+ user.markModified('inbox.messages');
+ return [
+ user.inbox.messages,
+ ];
};
diff --git a/common/script/ops/deletePM.js b/common/script/ops/deletePM.js
index ad95bc9ae0..84bb7ee33a 100644
--- a/common/script/ops/deletePM.js
+++ b/common/script/ops/deletePM.js
@@ -1,7 +1,9 @@
-module.exports = function(user, req, cb) {
- delete user.inbox.messages[req.params.id];
- if (typeof user.markModified === "function") {
- user.markModified('inbox.messages.' + req.params.id);
- }
- return typeof cb === "function" ? cb(null, user.inbox.messages) : void 0;
+import _ from 'lodash';
+
+module.exports = function deletePM (user, req = {}) {
+ delete user.inbox.messages[_.get(req, 'params.id')];
+ user.markModified(`inbox.messages.${req.params.id}`);
+ return [
+ user.inbox.messages,
+ ];
};
diff --git a/common/script/ops/deleteTag.js b/common/script/ops/deleteTag.js
index a82af59e86..c40fe79ba5 100644
--- a/common/script/ops/deleteTag.js
+++ b/common/script/ops/deleteTag.js
@@ -1,26 +1,32 @@
import i18n from '../i18n';
import _ from 'lodash';
+import { NotFound } from '../libs/errors';
-module.exports = function(user, req, cb) {
- var i, tag, tid;
- tid = req.params.id;
- i = _.findIndex(user.tags, {
- id: tid
+// TODO used only in client, move there?
+
+module.exports = function deleteTag (user, req = {}) {
+ let tid = _.get(req, 'params.id');
+
+ let index = _.findIndex(user.tags, {
+ id: tid,
});
- if (!~i) {
- return typeof cb === "function" ? cb({
- code: 404,
- message: i18n.t('messageTagNotFound', req.language)
- }) : void 0;
+
+ if (index === -1) {
+ throw new NotFound(i18n.t('messageTagNotFound', req.language));
}
- tag = user.tags[i];
+
+ let tag = user.tags[index];
delete user.filters[tag.id];
- user.tags.splice(i, 1);
- _.each(user.tasks, function(task) {
+
+ user.tags.splice(index, 1);
+
+ _.each(user.tasks, (task) => {
return delete task.tags[tag.id];
});
- _.each(['habits', 'dailys', 'todos', 'rewards'], function(type) {
- return typeof user.markModified === "function" ? user.markModified(type) : void 0;
+
+ _.each(['habits', 'dailys', 'todos', 'rewards'], (type) => {
+ user.markModified(type);
});
- return typeof cb === "function" ? cb(null, user.tags) : void 0;
+
+ return user.tags;
};
diff --git a/common/script/ops/deleteTask.js b/common/script/ops/deleteTask.js
index 715b102241..a641763de5 100644
--- a/common/script/ops/deleteTask.js
+++ b/common/script/ops/deleteTask.js
@@ -1,17 +1,22 @@
import i18n from '../i18n';
+import { NotFound } from '../libs/errors';
+import _ from 'lodash';
-module.exports = function(user, req, cb) {
- var i, ref, task;
- task = user.tasks[(ref = req.params) != null ? ref.id : void 0];
- if (!task) {
- return typeof cb === "function" ? cb({
- code: 404,
- message: i18n.t('messageTaskNotFound', req.language)
- }) : void 0;
+// TODO used only in client, move there?
+
+module.exports = function deleteTask (user, req = {}) {
+ let tid = _.get(req, 'params.id');
+ let taskType = _.get(req, 'params.taskType');
+
+ let index = _.findIndex(user[`${taskType}s`], function findById (task) {
+ return task._id === tid;
+ });
+
+ if (index === -1) {
+ throw new NotFound(i18n.t('messageTaskNotFound', req.language));
}
- i = user[task.type + "s"].indexOf(task);
- if (~i) {
- user[task.type + "s"].splice(i, 1);
- }
- return typeof cb === "function" ? cb(null, {}) : void 0;
+
+ user[`${taskType}s`].splice(index, 1);
+
+ return {};
};
diff --git a/common/script/ops/deleteWebhook.js b/common/script/ops/deleteWebhook.js
index a187f08770..9c4f67eba8 100644
--- a/common/script/ops/deleteWebhook.js
+++ b/common/script/ops/deleteWebhook.js
@@ -1,7 +1,10 @@
-module.exports = function(user, req, cb) {
- delete user.preferences.webhooks[req.params.id];
- if (typeof user.markModified === "function") {
- user.markModified('preferences.webhooks');
- }
- return typeof cb === "function" ? cb(null, user.preferences.webhooks) : void 0;
+import _ from 'lodash';
+
+module.exports = function deleteWebhook (user, req) {
+ delete user.preferences.webhooks[_.get(req, 'params.id')];
+ user.markModified('preferences.webhooks');
+
+ return [
+ user.preferences.webhooks,
+ ];
};
diff --git a/common/script/ops/disableClasses.js b/common/script/ops/disableClasses.js
index 89636ed52d..e611bb0872 100644
--- a/common/script/ops/disableClasses.js
+++ b/common/script/ops/disableClasses.js
@@ -2,12 +2,19 @@ import splitWhitespace from '../libs/splitWhitespace';
import { capByLevel } from '../statHelpers';
import _ from 'lodash';
-module.exports = function(user, req, cb) {
- user.stats["class"] = 'warrior';
+module.exports = function disableClasses (user, req = {}) {
+ user.stats.class = 'warrior';
user.flags.classSelected = true;
user.preferences.disableClasses = true;
user.preferences.autoAllocate = true;
user.stats.str = capByLevel(user.stats.lvl);
user.stats.points = 0;
- return typeof cb === "function" ? cb(null, _.pick(user, splitWhitespace('stats flags preferences'))) : void 0;
+
+ if (req.v2 === true) {
+ return _.pick(user, splitWhitespace('stats flags preferences'));
+ } else {
+ return [
+ _.pick(user, splitWhitespace('stats flags preferences')),
+ ];
+ }
};
diff --git a/common/script/ops/equip.js b/common/script/ops/equip.js
index 8c07a364b1..9c614915a7 100644
--- a/common/script/ops/equip.js
+++ b/common/script/ops/equip.js
@@ -1,52 +1,68 @@
import content from '../content/index';
import i18n from '../i18n';
+import handleTwoHanded from '../fns/handleTwoHanded';
+import {
+ NotFound,
+ BadRequest,
+} from '../libs/errors';
+import _ from 'lodash';
+
+module.exports = function equip (user, req = {}) {
+ // Being type a parameter followed by another parameter
+ // when using the API it must be passes specifically in the URL, it's won't default to equipped
+ let type = _.get(req, 'params.type', 'equipped');
+ let key = _.get(req, 'params.key');
+
+ if (!key || !type) throw new BadRequest(i18n.t('missingTypeKeyEquip', req.language));
+ if (['mount', 'pet', 'costume', 'equipped'].indexOf(type) === -1) {
+ throw new BadRequest(i18n.t('invalidTypeEquip', req.language));
+ }
+
+ let message;
-module.exports = function(user, req, cb) {
- var item, key, message, ref, type;
- ref = [req.params.type || 'equipped', req.params.key], type = ref[0], key = ref[1];
switch (type) {
- case 'mount':
+ case 'mount': {
if (!user.items.mounts[key]) {
- return typeof cb === "function" ? cb({
- code: 404,
- message: ":You do not own this mount."
- }) : void 0;
+ throw new NotFound(i18n.t('mountNotOwned', req.language));
}
+
user.items.currentMount = user.items.currentMount === key ? '' : key;
break;
- case 'pet':
+ }
+ case 'pet': {
if (!user.items.pets[key]) {
- return typeof cb === "function" ? cb({
- code: 404,
- message: ":You do not own this pet."
- }) : void 0;
+ throw new NotFound(i18n.t('petNotOwned', req.language));
}
+
user.items.currentPet = user.items.currentPet === key ? '' : key;
break;
+ }
case 'costume':
- case 'equipped':
- item = content.gear.flat[key];
+ case 'equipped': {
if (!user.items.gear.owned[key]) {
- return typeof cb === "function" ? cb({
- code: 404,
- message: ":You do not own this gear."
- }) : void 0;
+ throw new NotFound(i18n.t('gearNotOwned', req.language));
}
+
+ let item = content.gear.flat[key];
+
if (user.items.gear[type][item.type] === key) {
- user.items.gear[type][item.type] = item.type + "_base_0";
+ user.items.gear[type][item.type] = `${item.type}_base_0`;
message = i18n.t('messageUnEquipped', {
- itemText: item.text(req.language)
+ itemText: item.text(req.language),
}, req.language);
} else {
user.items.gear[type][item.type] = item.key;
- message = user.fns.handleTwoHanded(item, type, req);
- }
- if (typeof user.markModified === "function") {
- user.markModified("items.gear." + type);
+ message = handleTwoHanded(user, item, type, req);
}
+ break;
+ }
+ }
+
+ if (req.v2 === true) {
+ return user.items;
+ } else {
+ let res = [user.items];
+ if (message) res.push(message);
+ return res;
}
- return typeof cb === "function" ? cb((message ? {
- code: 200,
- message: message
- } : null), user.items) : void 0;
};
diff --git a/common/script/ops/feed.js b/common/script/ops/feed.js
index 3c9b0f87fd..637d9b3e1d 100644
--- a/common/script/ops/feed.js
+++ b/common/script/ops/feed.js
@@ -1,78 +1,102 @@
import content from '../content/index';
import i18n from '../i18n';
+import _ from 'lodash';
+import {
+ BadRequest,
+ NotAuthorized,
+ NotFound,
+} from '../libs/errors';
+
+function evolve (user, pet, petDisplayName, req) {
+ user.items.pets[pet] = -1;
+ user.items.mounts[pet] = true;
+
+ if (pet === user.items.currentPet) {
+ user.items.currentPet = '';
+ }
+
+ return i18n.t('messageEvolve', {
+ egg: petDisplayName,
+ }, req.language);
+}
+
+module.exports = function feed (user, req = {}) {
+ let pet = _.get(req, 'params.pet');
+ let foodK = _.get(req, 'params.food');
+
+ if (!pet || !foodK) throw new BadRequest(i18n.t('missingPetFoodFeed', req.language));
+
+ if (pet.indexOf('-') === -1) {
+ throw new BadRequest(i18n.t('invalidPetName', req.language));
+ }
+
+ let food = content.food[foodK];
+ if (!food) {
+ throw new NotFound(i18n.t('messageFoodNotFound', req.language));
+ }
+
+ let userPets = user.items.pets;
-module.exports = function(user, req, cb) {
- var egg, eggText, evolve, food, message, pet, petDisplayName, potion, potionText, ref, ref1, ref2, userPets;
- ref = req.params, pet = ref.pet, food = ref.food;
- food = content.food[food];
- ref1 = pet.split('-'), egg = ref1[0], potion = ref1[1];
- userPets = user.items.pets;
- potionText = content.hatchingPotions[potion] ? content.hatchingPotions[potion].text() : potion;
- eggText = content.eggs[egg] ? content.eggs[egg].text() : egg;
- petDisplayName = i18n.t('petName', {
- potion: potionText,
- egg: eggText
- });
if (!userPets[pet]) {
- return typeof cb === "function" ? cb({
- code: 404,
- message: i18n.t('messagePetNotFound', req.language)
- }) : void 0;
+ throw new NotFound(i18n.t('messagePetNotFound', req.language));
}
- if (!((ref2 = user.items.food) != null ? ref2[food.key] : void 0)) {
- return typeof cb === "function" ? cb({
- code: 404,
- message: i18n.t('messageFoodNotFound', req.language)
- }) : void 0;
+
+ let [egg, potion] = pet.split('-');
+
+ let potionText = content.hatchingPotions[potion] ? content.hatchingPotions[potion].text(req.language) : potion;
+ let eggText = content.eggs[egg] ? content.eggs[egg].text(req.language) : egg;
+
+ let petDisplayName = i18n.t('petName', {
+ potion: potionText,
+ egg: eggText,
+ }, req.language);
+
+ if (!user.items.food[food.key]) {
+ throw new NotFound(i18n.t('messageFoodNotFound', req.language));
}
+
if (content.specialPets[pet]) {
- return typeof cb === "function" ? cb({
- code: 401,
- message: i18n.t('messageCannotFeedPet', req.language)
- }) : void 0;
+ throw new NotAuthorized(i18n.t('messageCannotFeedPet', req.language));
}
+
if (user.items.mounts[pet]) {
- return typeof cb === "function" ? cb({
- code: 401,
- message: i18n.t('messageAlreadyMount', req.language)
- }) : void 0;
+ throw new NotAuthorized(i18n.t('messageAlreadyMount', req.language));
}
- message = '';
- evolve = function() {
- userPets[pet] = -1;
- user.items.mounts[pet] = true;
- if (pet === user.items.currentPet) {
- user.items.currentPet = "";
- }
- return message = i18n.t('messageEvolve', {
- egg: petDisplayName
- }, req.language);
- };
+
+ let message;
+
if (food.key === 'Saddle') {
- evolve();
+ message = evolve(user, pet, petDisplayName, req);
} else {
if (food.target === potion || content.hatchingPotions[potion].premium) {
userPets[pet] += 5;
message = i18n.t('messageLikesFood', {
egg: petDisplayName,
- foodText: food.text(req.language)
+ foodText: food.text(req.language),
}, req.language);
} else {
userPets[pet] += 2;
message = i18n.t('messageDontEnjoyFood', {
egg: petDisplayName,
- foodText: food.text(req.language)
+ foodText: food.text(req.language),
}, req.language);
}
+
if (userPets[pet] >= 50 && !user.items.mounts[pet]) {
- evolve();
+ message = evolve(user, pet, petDisplayName, req);
}
}
+
user.items.food[food.key]--;
- return typeof cb === "function" ? cb({
- code: 200,
- message: message
- }, {
- value: userPets[pet]
- }) : void 0;
+
+ if (req.v2 === true) {
+ return {
+ value: userPets[pet],
+ };
+ } else {
+ return [
+ userPets[pet],
+ message,
+ ];
+ }
};
diff --git a/common/script/ops/getTag.js b/common/script/ops/getTag.js
index 06f3f24110..4a7db63128 100644
--- a/common/script/ops/getTag.js
+++ b/common/script/ops/getTag.js
@@ -1,17 +1,18 @@
import _ from 'lodash';
import i18n from '../i18n';
+import { NotFound } from '../libs/errors';
-module.exports = function(user, req, cb) {
- var i, tid;
- tid = req.params.id;
- i = _.findIndex(user.tags, {
- id: tid
+// TODO used only in client, move there?
+
+module.exports = function getTag (user, req = {}) {
+ let tid = _.get(req, 'params.id');
+
+ let index = _.findIndex(user.tags, {
+ id: tid,
});
- if (!~i) {
- return typeof cb === "function" ? cb({
- code: 404,
- message: i18n.t('messageTagNotFound', req.language)
- }) : void 0;
+ if (index === -1) {
+ throw new NotFound(i18n.t('messageTagNotFound', req.language));
}
- return typeof cb === "function" ? cb(null, user.tags[i]) : void 0;
+
+ return user.tags[index];
};
diff --git a/common/script/ops/getTags.js b/common/script/ops/getTags.js
index af9419b050..a96589a831 100644
--- a/common/script/ops/getTags.js
+++ b/common/script/ops/getTags.js
@@ -1,3 +1,5 @@
-module.exports = function(user, req, cb) {
- return typeof cb === "function" ? cb(null, user.tags) : void 0;
+// TODO used only in client, move there?
+
+module.exports = function getTags (user) {
+ return user.tags;
};
diff --git a/common/script/ops/hatch.js b/common/script/ops/hatch.js
index 6292e24bcb..87adbf3276 100644
--- a/common/script/ops/hatch.js
+++ b/common/script/ops/hatch.js
@@ -1,39 +1,44 @@
import content from '../content/index';
import i18n from '../i18n';
+import _ from 'lodash';
+import {
+ BadRequest,
+ NotAuthorized,
+ NotFound,
+} from '../libs/errors';
+
+module.exports = function hatch (user, req = {}) {
+ let egg = _.get(req, 'params.egg');
+ let hatchingPotion = _.get(req, 'params.hatchingPotion');
-module.exports = function(user, req, cb) {
- var egg, hatchingPotion, pet, ref;
- ref = req.params, egg = ref.egg, hatchingPotion = ref.hatchingPotion;
if (!(egg && hatchingPotion)) {
- return typeof cb === "function" ? cb({
- code: 400,
- message: "Please specify query.egg & query.hatchingPotion"
- }) : void 0;
+ throw new BadRequest(i18n.t('missingEggHatchingPotionHatch', req.language));
}
+
if (!(user.items.eggs[egg] > 0 && user.items.hatchingPotions[hatchingPotion] > 0)) {
- return typeof cb === "function" ? cb({
- code: 403,
- message: i18n.t('messageMissingEggPotion', req.language)
- }) : void 0;
+ throw new NotFound(i18n.t('messageMissingEggPotion', req.language));
}
+
if (content.hatchingPotions[hatchingPotion].premium && !content.dropEggs[egg]) {
- return typeof cb === "function" ? cb({
- code: 403,
- message: i18n.t('messageInvalidEggPotionCombo', req.language)
- }) : void 0;
+ throw new BadRequest(i18n.t('messageInvalidEggPotionCombo', req.language));
}
- pet = egg + "-" + hatchingPotion;
+
+ let pet = `${egg}-${hatchingPotion}`;
+
if (user.items.pets[pet] && user.items.pets[pet] > 0) {
- return typeof cb === "function" ? cb({
- code: 403,
- message: i18n.t('messageAlreadyPet', req.language)
- }) : void 0;
+ throw new NotAuthorized(i18n.t('messageAlreadyPet', req.language));
}
+
user.items.pets[pet] = 5;
user.items.eggs[egg]--;
user.items.hatchingPotions[hatchingPotion]--;
- return typeof cb === "function" ? cb({
- code: 200,
- message: i18n.t('messageHatched', req.language)
- }, user.items) : void 0;
+
+ if (req.v2 === true) {
+ return user.items;
+ } else {
+ return [
+ user.items,
+ i18n.t('messageHatched', req.language),
+ ];
+ }
};
diff --git a/common/script/ops/hourglassPurchase.js b/common/script/ops/hourglassPurchase.js
index 4955898f0b..e1d07bb482 100644
--- a/common/script/ops/hourglassPurchase.js
+++ b/common/script/ops/hourglassPurchase.js
@@ -1,54 +1,61 @@
import content from '../content/index';
import i18n from '../i18n';
import _ from 'lodash';
+import {
+ BadRequest,
+ NotAuthorized,
+} from '../libs/errors';
import splitWhitespace from '../libs/splitWhitespace';
-module.exports = function(user, req, cb, analytics) {
- var analyticsData, key, ref, type;
- ref = req.params, type = ref.type, key = ref.key;
+module.exports = function purchaseHourglass (user, req = {}, analytics) {
+ let key = _.get(req, 'params.key');
+ if (!key) throw new BadRequest(i18n.t('missingKeyParam', req.language));
+
+ let type = _.get(req, 'params.type');
+ if (!type) throw new BadRequest(i18n.t('missingTypeParam', req.language));
+
if (!content.timeTravelStable[type]) {
- return typeof cb === "function" ? cb({
- code: 403,
- message: i18n.t('typeNotAllowedHourglass', req.language) + JSON.stringify(_.keys(content.timeTravelStable))
- }) : void 0;
+ throw new NotAuthorized(i18n.t('typeNotAllowedHourglass', {allowedTypes: _.keys(content.timeTravelStable).toString()}, req.language));
}
+
if (!_.contains(_.keys(content.timeTravelStable[type]), key)) {
- return typeof cb === "function" ? cb({
- code: 403,
- message: i18n.t(type + 'NotAllowedHourglass', req.language)
- }) : void 0;
+ throw new NotAuthorized(i18n.t('notAllowedHourglass', req.language));
}
+
if (user.items[type][key]) {
- return typeof cb === "function" ? cb({
- code: 403,
- message: i18n.t(type + 'AlreadyOwned', req.language)
- }) : void 0;
+ throw new NotAuthorized(i18n.t(`${type}AlreadyOwned`, req.language));
}
- if (!(user.purchased.plan.consecutive.trinkets > 0)) {
- return typeof cb === "function" ? cb({
- code: 403,
- message: i18n.t('notEnoughHourglasses', req.language)
- }) : void 0;
+
+ if (user.purchased.plan.consecutive.trinkets <= 0) {
+ throw new NotAuthorized(i18n.t('notEnoughHourglasses', req.language));
}
+
user.purchased.plan.consecutive.trinkets--;
+
if (type === 'pets') {
user.items.pets[key] = 5;
}
+
if (type === 'mounts') {
user.items.mounts[key] = true;
}
- analyticsData = {
- uuid: user._id,
- itemKey: key,
- itemType: type,
- acquireMethod: 'Hourglass',
- category: 'behavior'
- };
- if (analytics != null) {
- analytics.track('acquire item', analyticsData);
+
+ if (analytics) {
+ analytics.track('acquire item', {
+ uuid: user._id,
+ itemKey: key,
+ itemType: type,
+ acquireMethod: 'Hourglass',
+ category: 'behavior',
+ });
+ }
+
+ if (req.v2 === true) {
+ return _.pick(user, splitWhitespace('items purchased.plan.consecutive'));
+ } else {
+ return [
+ { items: user.items, purchasedPlanConsecutive: user.purchased.plan.consecutive },
+ i18n.t('hourglassPurchase', req.language),
+ ];
}
- return typeof cb === "function" ? cb({
- code: 200,
- message: i18n.t('hourglassPurchase', req.language)
- }, _.pick(user, splitWhitespace('items purchased.plan.consecutive'))) : void 0;
};
diff --git a/common/script/ops/index.js b/common/script/ops/index.js
index 1bfd3d55f2..2e8bca246d 100644
--- a/common/script/ops/index.js
+++ b/common/script/ops/index.js
@@ -30,6 +30,9 @@ import releasePets from './releasePets';
import releaseMounts from './releaseMounts';
import releaseBoth from './releaseBoth';
import buy from './buy';
+import buyGear from './buyGear';
+import buyHealthPotion from './buyHealthPotion';
+import buyArmoire from './buyArmoire';
import buyQuest from './buyQuest';
import buyMysterySet from './buyMysterySet';
import hourglassPurchase from './hourglassPurchase';
@@ -42,7 +45,9 @@ import disableClasses from './disableClasses';
import allocate from './allocate';
import readCard from './readCard';
import openMysteryItem from './openMysteryItem';
-import score from './score';
+import scoreTask from './scoreTask';
+import markPmsRead from './markPMSRead';
+
module.exports = {
update,
@@ -77,6 +82,9 @@ module.exports = {
releaseMounts,
releaseBoth,
buy,
+ buyGear,
+ buyHealthPotion,
+ buyArmoire,
buyQuest,
buyMysterySet,
hourglassPurchase,
@@ -89,5 +97,6 @@ module.exports = {
allocate,
readCard,
openMysteryItem,
- score,
+ scoreTask,
+ markPmsRead,
};
diff --git a/common/script/ops/markPMSRead.js b/common/script/ops/markPMSRead.js
new file mode 100644
index 0000000000..add9f49de5
--- /dev/null
+++ b/common/script/ops/markPMSRead.js
@@ -0,0 +1,14 @@
+import i18n from '../i18n';
+
+module.exports = function markPmsRead (user, req = {}) {
+ user.inbox.newMessages = 0;
+
+ if (req.v2 === true) {
+ return user;
+ } else {
+ return [
+ user.inbox.newMessages,
+ i18n.t('pmsMarkedRead'),
+ ];
+ }
+};
diff --git a/common/script/ops/openMysteryItem.js b/common/script/ops/openMysteryItem.js
index eb28c605e0..9728bc9eb9 100644
--- a/common/script/ops/openMysteryItem.js
+++ b/common/script/ops/openMysteryItem.js
@@ -1,32 +1,38 @@
import content from '../content/index';
+import i18n from '../i18n';
+import {
+ BadRequest,
+} from '../libs/errors';
+import _ from 'lodash';
+
+module.exports = function openMysteryItem (user, req = {}, analytics) {
+ let item = user.purchased.plan.mysteryItems.shift();
-module.exports = function(user, req, cb, analytics) {
- var analyticsData, item, ref, ref1;
- item = (ref = user.purchased.plan) != null ? (ref1 = ref.mysteryItems) != null ? ref1.shift() : void 0 : void 0;
if (!item) {
- return typeof cb === "function" ? cb({
- code: 400,
- message: "Empty"
- }) : void 0;
+ throw new BadRequest(i18n.t('mysteryItemIsEmpty', req.language));
}
- item = content.gear.flat[item];
+
+ item = _.cloneDeep(content.gear.flat[item]);
user.items.gear.owned[item.key] = true;
- if (typeof user.markModified === "function") {
- user.markModified('purchased.plan.mysteryItems');
+
+ user.markModified('purchased.plan.mysteryItems');
+
+ if (analytics) {
+ analytics.track('open mystery item', {
+ uuid: user._id,
+ itemKey: item,
+ itemType: 'Subscriber Gear',
+ acquireMethod: 'Subscriber',
+ category: 'behavior',
+ });
}
- item.notificationType = 'Mystery';
- analyticsData = {
- uuid: user._id,
- itemKey: item,
- itemType: 'Subscriber Gear',
- acquireMethod: 'Subscriber',
- category: 'behavior'
- };
- if (analytics != null) {
- analytics.track('open mystery item', analyticsData);
+
+ if (req.v2 === true) {
+ return user.items.gear.owned;
+ } else {
+ return [
+ item,
+ i18n.t('mysteryItemOpened', req.language),
+ ];
}
- if (typeof window !== 'undefined') {
- (user._tmp != null ? user._tmp : user._tmp = {}).drop = item;
- }
- return typeof cb === "function" ? cb(null, user.items.gear.owned) : void 0;
};
diff --git a/common/script/ops/purchase.js b/common/script/ops/purchase.js
index d4d0f78816..79eb0475d4 100644
--- a/common/script/ops/purchase.js
+++ b/common/script/ops/purchase.js
@@ -3,105 +3,125 @@ import i18n from '../i18n';
import _ from 'lodash';
import splitWhitespace from '../libs/splitWhitespace';
import planGemLimits from '../libs/planGemLimits';
+import {
+ NotFound,
+ NotAuthorized,
+ BadRequest,
+} from '../libs/errors';
+
+module.exports = function purchase (user, req = {}, analytics) {
+ let type = _.get(req.params, 'type');
+ let key = _.get(req.params, 'key');
+ let item;
+ let price;
+
+ if (!type) {
+ throw new BadRequest(i18n.t('typeRequired', req.language));
+ }
+
+ if (!key) {
+ throw new BadRequest(i18n.t('keyRequired', req.language));
+ }
-module.exports = function(user, req, cb, analytics) {
- var analyticsData, convCap, convRate, item, key, price, ref, ref1, ref2, ref3, type;
- ref = req.params, type = ref.type, key = ref.key;
if (type === 'gems' && key === 'gem') {
- ref1 = planGemLimits, convRate = ref1.convRate, convCap = ref1.convCap;
+ let convRate = planGemLimits.convRate;
+ let convCap = planGemLimits.convCap;
convCap += user.purchased.plan.consecutive.gemCapExtra;
- if (!((ref2 = user.purchased) != null ? (ref3 = ref2.plan) != null ? ref3.customerId : void 0 : void 0)) {
- return typeof cb === "function" ? cb({
- code: 401,
- message: "Must subscribe to purchase gems with GP"
- }, req) : void 0;
+
+ if (!user.purchased || !user.purchased.plan || !user.purchased.plan.customerId) {
+ throw new NotAuthorized(i18n.t('mustSubscribeToPurchaseGems', req.language));
}
- if (!(user.stats.gp >= convRate)) {
- return typeof cb === "function" ? cb({
- code: 401,
- message: "Not enough Gold"
- }) : void 0;
+
+ if (user.stats.gp < convRate) {
+ throw new NotAuthorized(i18n.t('messageNotEnoughGold', req.language));
}
+
if (user.purchased.plan.gemsBought >= convCap) {
- return typeof cb === "function" ? cb({
- code: 401,
- message: "You've reached the Gold=>Gem conversion cap (" + convCap + ") for this month. We have this to prevent abuse / farming. The cap will reset within the first three days of next month."
- }) : void 0;
+ throw new NotAuthorized(i18n.t('reachedGoldToGemCap', {convCap}, req.language));
}
- user.balance += .25;
+
+ user.balance += 0.25;
user.purchased.plan.gemsBought++;
user.stats.gp -= convRate;
- analyticsData = {
- uuid: user._id,
- itemKey: key,
- acquireMethod: 'Gold',
- goldCost: convRate,
- category: 'behavior'
- };
- if (analytics != null) {
- analytics.track('purchase gems', analyticsData);
+
+ if (analytics) {
+ analytics.track('purchase gems', {
+ uuid: user._id,
+ itemKey: key,
+ acquireMethod: 'Gold',
+ goldCost: convRate,
+ category: 'behavior',
+ });
}
- return typeof cb === "function" ? cb({
- code: 200,
- message: "+1 Gem"
- }, _.pick(user, splitWhitespace('stats balance'))) : void 0;
+
+ return [
+ _.pick(user, splitWhitespace('stats balance')),
+ i18n.t('plusOneGem'),
+ ];
}
- if (type !== 'eggs' && type !== 'hatchingPotions' && type !== 'food' && type !== 'quests' && type !== 'gear') {
- return typeof cb === "function" ? cb({
- code: 404,
- message: ":type must be in [eggs,hatchingPotions,food,quests,gear]"
- }, req) : void 0;
+
+ let acceptedTypes = ['eggs', 'hatchingPotions', 'food', 'quests', 'gear'];
+ if (acceptedTypes.indexOf(type) === -1) {
+ throw new NotFound(i18n.t('notAccteptedType', req.language));
}
+
if (type === 'gear') {
item = content.gear.flat[key];
- if (user.items.gear.owned[key]) {
- return typeof cb === "function" ? cb({
- code: 401,
- message: i18n.t('alreadyHave', req.language)
- }) : void 0;
+
+ if (!item) {
+ throw new NotFound(i18n.t('contentKeyNotFound', {type}, req.language));
}
+
+ if (user.items.gear.owned[key]) {
+ throw new NotAuthorized(i18n.t('alreadyHave', req.language));
+ }
+
price = (item.twoHanded || item.gearSet === 'animal' ? 2 : 1) / 4;
} else {
item = content[type][key];
+
+ if (!item) {
+ throw new NotFound(i18n.t('contentKeyNotFound', {type}, req.language));
+ }
+
price = item.value / 4;
}
- if (!item) {
- return typeof cb === "function" ? cb({
- code: 404,
- message: ":key not found for Content." + type
- }, req) : void 0;
- }
+
if (!item.canBuy(user)) {
- return typeof cb === "function" ? cb({
- code: 403,
- message: i18n.t('messageNotAvailable', req.language)
- }) : void 0;
+ throw new NotAuthorized(i18n.t('messageNotAvailable', req.language));
}
- if ((user.balance < price) || !user.balance) {
- return typeof cb === "function" ? cb({
- code: 403,
- message: i18n.t('notEnoughGems', req.language)
- }) : void 0;
+
+ if (!user.balance || user.balance < price) {
+ throw new NotAuthorized(i18n.t('notEnoughGems', req.language));
}
+
user.balance -= price;
+
if (type === 'gear') {
user.items.gear.owned[key] = true;
} else {
- if (!(user.items[type][key] > 0)) {
+ if (!user.items[type][key] || user.items[type][key] < 0) {
user.items[type][key] = 0;
}
user.items[type][key]++;
}
- analyticsData = {
- uuid: user._id,
- itemKey: key,
- itemType: 'Market',
- acquireMethod: 'Gems',
- gemCost: item.value,
- category: 'behavior'
- };
- if (analytics != null) {
- analytics.track('acquire item', analyticsData);
+
+ if (analytics) {
+ analytics.track('acquire item', {
+ uuid: user._id,
+ itemKey: key,
+ itemType: 'Market',
+ acquireMethod: 'Gems',
+ gemCost: item.value,
+ category: 'behavior',
+ });
+ }
+
+ if (req.v2 === true) {
+ return _.pick(user, splitWhitespace('items balance'));
+ } else {
+ return [
+ _.pick(user, splitWhitespace('items balance')),
+ ];
}
- return typeof cb === "function" ? cb(null, _.pick(user, splitWhitespace('items balance'))) : void 0;
};
diff --git a/common/script/ops/readCard.js b/common/script/ops/readCard.js
index a6eb5c05f1..57b0da4b00 100644
--- a/common/script/ops/readCard.js
+++ b/common/script/ops/readCard.js
@@ -1,10 +1,32 @@
-module.exports = function(user, req, cb) {
- var cardType;
- cardType = req.params.cardType;
- user.items.special[cardType + "Received"].shift();
- if (typeof user.markModified === "function") {
- user.markModified("items.special." + cardType + "Received");
+import splitWhitespace from '../libs/splitWhitespace';
+import _ from 'lodash';
+import i18n from '../i18n';
+import {
+ BadRequest,
+ NotAuthorized,
+} from '../libs/errors';
+import content from '../content/index';
+
+module.exports = function readCard (user, req = {}) {
+ let cardType = _.get(req.params, 'cardType');
+
+ if (!cardType) {
+ throw new BadRequest(i18n.t('cardTypeRequired', req.language));
}
+
+ if (_.keys(content.cardTypes).indexOf(cardType) === -1) {
+ throw new NotAuthorized(i18n.t('cardTypeNotAllowed', req.language));
+ }
+
+ user.items.special[`${cardType}Received`].shift();
user.flags.cardReceived = false;
- return typeof cb === "function" ? cb(null, 'items.special flags.cardReceived') : void 0;
+
+ if (req.v2 === true) {
+ return _.pick(user, splitWhitespace('items.special flags.cardReceived'));
+ } else {
+ return [
+ { specialItems: user.items.special, cardReceived: user.flags.cardReceived },
+ i18n.t('readCard', {cardType}, req.language),
+ ];
+ }
};
diff --git a/common/script/ops/rebirth.js b/common/script/ops/rebirth.js
index 1ddb75aba1..54f9533fc5 100644
--- a/common/script/ops/rebirth.js
+++ b/common/script/ops/rebirth.js
@@ -1,21 +1,25 @@
-import content from '../content/index';
import i18n from '../i18n';
import _ from 'lodash';
import { capByLevel } from '../statHelpers';
import { MAX_LEVEL } from '../constants';
+import {
+ NotAuthorized,
+} from '../libs/errors';
+import resetGear from '../fns/resetGear';
+import equip from './equip';
-module.exports = function(user, req, cb, analytics) {
- var analyticsData, flags, gear, lvl, stats;
+const USERSTATSLIST = ['per', 'int', 'con', 'str', 'points', 'gp', 'exp', 'mp'];
+
+module.exports = function rebirth (user, tasks = [], req = {}, analytics) {
if (user.balance < 2 && user.stats.lvl < MAX_LEVEL) {
- return typeof cb === "function" ? cb({
- code: 401,
- message: i18n.t('notEnoughGems', req.language)
- }) : void 0;
+ throw new NotAuthorized(i18n.t('notEnoughGems', req.language));
}
- analyticsData = {
+
+ let analyticsData = {
uuid: user._id,
- category: 'behavior'
+ category: 'behavior',
};
+
if (user.stats.lvl < MAX_LEVEL) {
user.balance -= 2;
analyticsData.acquireMethod = 'Gems';
@@ -24,63 +28,55 @@ module.exports = function(user, req, cb, analytics) {
analyticsData.gemCost = 0;
analyticsData.acquireMethod = '> 100';
}
- if (analytics != null) {
+
+ if (analytics) {
analytics.track('Rebirth', analyticsData);
}
- lvl = capByLevel(user.stats.lvl);
- _.each(user.tasks, function(task) {
- if (task.type !== 'reward') {
- task.value = 0;
- }
- if (task.type === 'daily') {
- return task.streak = 0;
+
+ let lvl = capByLevel(user.stats.lvl);
+
+ _.each(tasks, function resetTasks (task) {
+ if (!task.challenge || !task.challenge.id || task.challenge.broken) {
+ if (task.type !== 'reward') {
+ task.value = 0;
+ }
+ if (task.type === 'daily') {
+ task.streak = 0;
+ }
}
});
- stats = user.stats;
+
+ let stats = user.stats;
stats.buffs = {};
stats.hp = 50;
stats.lvl = 1;
- stats["class"] = 'warrior';
- _.each(['per', 'int', 'con', 'str', 'points', 'gp', 'exp', 'mp'], function(value) {
- return stats[value] = 0;
- });
- // TODO during refactoring: move all gear code from rebirth() to its own function and then call it in reset() as well
- gear = user.items.gear;
- _.each(['equipped', 'costume'], function(type) {
- gear[type] = {};
- gear[type].armor = 'armor_base_0';
- gear[type].weapon = 'weapon_warrior_0';
- gear[type].head = 'head_base_0';
- return gear[type].shield = 'shield_base_0';
+ stats.class = 'warrior';
+
+ _.each(USERSTATSLIST, function resetStats (value) {
+ stats[value] = 0;
});
+
+ resetGear(user);
+
if (user.items.currentPet) {
- user.ops.equip({
+ equip(user, {
params: {
type: 'pet',
- key: user.items.currentPet
- }
+ key: user.items.currentPet,
+ },
});
}
+
if (user.items.currentMount) {
- user.ops.equip({
+ equip(user, {
params: {
type: 'mount',
- key: user.items.currentMount
- }
+ key: user.items.currentMount,
+ },
});
}
- _.each(gear.owned, function(v, k) {
- if (gear.owned[k] && content.gear.flat[k].value) {
- gear.owned[k] = false;
- return true;
- }
- });
- gear.owned.weapon_warrior_0 = true;
- if (typeof user.markModified === "function") {
- user.markModified('items.gear.owned');
- }
- user.preferences.costume = false;
- flags = user.flags;
+
+ let flags = user.flags;
if (!user.achievements.beastMaster) {
flags.rebirthEnabled = false;
}
@@ -88,13 +84,23 @@ module.exports = function(user, req, cb, analytics) {
flags.dropsEnabled = false;
flags.classSelected = false;
flags.levelDrops = {};
+
if (!user.achievements.rebirths) {
user.achievements.rebirths = 1;
user.achievements.rebirthLevel = lvl;
- } else if (lvl > user.achievements.rebirthLevel || lvl === 100) {
+ } else if (lvl > user.achievements.rebirthLevel || lvl === MAX_LEVEL) {
user.achievements.rebirths++;
user.achievements.rebirthLevel = lvl;
}
+
user.stats.buffs = {};
- return typeof cb === "function" ? cb(null, user) : void 0;
+
+ if (req.v2 === true) {
+ return user;
+ } else {
+ return [
+ {user, tasks},
+ i18n.t('rebirthComplete'),
+ ];
+ }
};
diff --git a/common/script/ops/releaseBoth.js b/common/script/ops/releaseBoth.js
index a782581f85..cf7d2267ca 100644
--- a/common/script/ops/releaseBoth.js
+++ b/common/script/ops/releaseBoth.js
@@ -1,50 +1,68 @@
import content from '../content/index';
import i18n from '../i18n';
+import {
+ NotAuthorized,
+} from '../libs/errors';
+import splitWhitespace from '../libs/splitWhitespace';
+import _ from 'lodash';
+
+module.exports = function releaseBoth (user, req = {}, analytics) {
+ let animal;
-module.exports = function(user, req, cb, analytics) {
- var analyticsData, animal, giveTriadBingo;
if (user.balance < 1.5 && !user.achievements.triadBingo) {
- return typeof cb === "function" ? cb({
- code: 401,
- message: i18n.t('notEnoughGems', req.language)
- }) : void 0;
- } else {
- giveTriadBingo = true;
- if (!user.achievements.triadBingo) {
- analyticsData = {
+ throw new NotAuthorized(i18n.t('notEnoughGems', req.language));
+ }
+
+ let giveTriadBingo = true;
+
+ if (!user.achievements.triadBingo) {
+ if (analytics) {
+ analytics.track('release pets & mounts', {
uuid: user._id,
acquireMethod: 'Gems',
gemCost: 6,
- category: 'behavior'
- };
- if (typeof analytics !== "undefined" && analytics !== null) {
- analytics.track('release pets & mounts', analyticsData);
- }
- user.balance -= 1.5;
- }
- user.items.currentMount = "";
- user.items.currentPet = "";
- for (animal in content.pets) {
- if (user.items.pets[animal] === -1) {
- giveTriadBingo = false;
- }
- user.items.pets[animal] = 0;
- user.items.mounts[animal] = null;
- }
- if (!user.achievements.beastMasterCount) {
- user.achievements.beastMasterCount = 0;
- }
- user.achievements.beastMasterCount++;
- if (!user.achievements.mountMasterCount) {
- user.achievements.mountMasterCount = 0;
- }
- user.achievements.mountMasterCount++;
- if (giveTriadBingo) {
- if (!user.achievements.triadBingoCount) {
- user.achievements.triadBingoCount = 0;
- }
- user.achievements.triadBingoCount++;
+ category: 'behavior',
+ });
}
+
+ user.balance -= 1.5;
+ }
+
+ user.items.currentMount = '';
+ user.items.currentPet = '';
+
+ for (animal in content.pets) {
+ if (user.items.pets[animal] === -1) {
+ giveTriadBingo = false;
+ }
+
+ user.items.pets[animal] = 0;
+ user.items.mounts[animal] = null;
+ }
+
+ if (!user.achievements.beastMasterCount) {
+ user.achievements.beastMasterCount = 0;
+ }
+ user.achievements.beastMasterCount++;
+
+ if (!user.achievements.mountMasterCount) {
+ user.achievements.mountMasterCount = 0;
+ }
+ user.achievements.mountMasterCount++;
+
+ if (giveTriadBingo) {
+ if (!user.achievements.triadBingoCount) {
+ user.achievements.triadBingoCount = 0;
+ }
+ user.achievements.triadBingoCount++;
+ }
+
+ if (req.v2 === true) {
+ return user;
+ } else {
+ return [
+ _.pick(user, splitWhitespace('achievements items balance')),
+ i18n.t('mountsAndPetsReleased'),
+ ];
}
- return typeof cb === "function" ? cb(null, user) : void 0;
};
diff --git a/common/script/ops/releaseMounts.js b/common/script/ops/releaseMounts.js
index 4aefab6b40..d8b8dde659 100644
--- a/common/script/ops/releaseMounts.js
+++ b/common/script/ops/releaseMounts.js
@@ -1,32 +1,43 @@
import content from '../content/index';
import i18n from '../i18n';
+import {
+ NotAuthorized,
+} from '../libs/errors';
+
+module.exports = function releaseMounts (user, req = {}, analytics) {
+ let mount;
-module.exports = function(user, req, cb, analytics) {
- var analyticsData, mount;
if (user.balance < 1) {
- return typeof cb === "function" ? cb({
- code: 401,
- message: i18n.t('notEnoughGems', req.language)
- }) : void 0;
+ throw new NotAuthorized(i18n.t('notEnoughGems', req.language));
+ }
+
+ user.balance -= 1;
+ user.items.currentMount = '';
+
+ for (mount in content.pets) {
+ user.items.mounts[mount] = null;
+ }
+
+ if (!user.achievements.mountMasterCount) {
+ user.achievements.mountMasterCount = 0;
+ }
+ user.achievements.mountMasterCount++;
+
+ if (analytics) {
+ analytics.track('release mounts', {
+ uuid: user._id,
+ acquireMethod: 'Gems',
+ gemCost: 4,
+ category: 'behavior',
+ });
+ }
+
+ if (req.v2 === true) {
+ return user;
} else {
- user.balance -= 1;
- user.items.currentMount = "";
- for (mount in content.pets) {
- user.items.mounts[mount] = null;
- }
- if (!user.achievements.mountMasterCount) {
- user.achievements.mountMasterCount = 0;
- }
- user.achievements.mountMasterCount++;
+ return [
+ user.items.mounts,
+ i18n.t('mountsReleased'),
+ ];
}
- analyticsData = {
- uuid: user._id,
- acquireMethod: 'Gems',
- gemCost: 4,
- category: 'behavior'
- };
- if (analytics != null) {
- analytics.track('release mounts', analyticsData);
- }
- return typeof cb === "function" ? cb(null, user) : void 0;
};
diff --git a/common/script/ops/releasePets.js b/common/script/ops/releasePets.js
index a4b452cd86..9466e1ccda 100644
--- a/common/script/ops/releasePets.js
+++ b/common/script/ops/releasePets.js
@@ -1,32 +1,41 @@
import content from '../content/index';
import i18n from '../i18n';
+import {
+ NotAuthorized,
+} from '../libs/errors';
-module.exports = function(user, req, cb, analytics) {
- var analyticsData, pet;
+module.exports = function releasePets (user, req = {}, analytics) {
if (user.balance < 1) {
- return typeof cb === "function" ? cb({
- code: 401,
- message: i18n.t('notEnoughGems', req.language)
- }) : void 0;
+ throw new NotAuthorized(i18n.t('notEnoughGems', req.language));
+ }
+
+ user.balance -= 1;
+ user.items.currentPet = '';
+
+ for (let pet in content.pets) {
+ user.items.pets[pet] = 0;
+ }
+
+ if (!user.achievements.beastMasterCount) {
+ user.achievements.beastMasterCount = 0;
+ }
+ user.achievements.beastMasterCount++;
+
+ if (analytics) {
+ analytics.track('release pets', {
+ uuid: user._id,
+ acquireMethod: 'Gems',
+ gemCost: 4,
+ category: 'behavior',
+ });
+ }
+
+ if (req.v2 === true) {
+ return user;
} else {
- user.balance -= 1;
- for (pet in content.pets) {
- user.items.pets[pet] = 0;
- }
- if (!user.achievements.beastMasterCount) {
- user.achievements.beastMasterCount = 0;
- }
- user.achievements.beastMasterCount++;
- user.items.currentPet = "";
+ return [
+ user.items.pets,
+ i18n.t('petsReleased'),
+ ];
}
- analyticsData = {
- uuid: user._id,
- acquireMethod: 'Gems',
- gemCost: 4,
- category: 'behavior'
- };
- if (analytics != null) {
- analytics.track('release pets', analyticsData);
- }
- return typeof cb === "function" ? cb(null, user) : void 0;
};
diff --git a/common/script/ops/reroll.js b/common/script/ops/reroll.js
index f6a0862f1d..3087845509 100644
--- a/common/script/ops/reroll.js
+++ b/common/script/ops/reroll.js
@@ -1,29 +1,40 @@
import i18n from '../i18n';
import _ from 'lodash';
+import {
+ NotAuthorized,
+} from '../libs/errors';
-module.exports = function(user, req, cb, analytics) {
- var analyticsData;
+module.exports = function reroll (user, tasks = [], req = {}, analytics) {
if (user.balance < 1) {
- return typeof cb === "function" ? cb({
- code: 401,
- message: i18n.t('notEnoughGems', req.language)
- }) : void 0;
+ throw new NotAuthorized(i18n.t('notEnoughGems', req.language));
}
+
user.balance--;
- _.each(user.tasks, function(task) {
- if (task.type !== 'reward') {
- return task.value = 0;
+ user.stats.hp = 50;
+
+ _.each(tasks, function resetTaskValues (task) {
+ if (!task.challenge || !task.challenge.id || task.challenge.broken) {
+ if (task.type !== 'reward') {
+ task.value = 0;
+ }
}
});
- user.stats.hp = 50;
- analyticsData = {
- uuid: user._id,
- acquireMethod: 'Gems',
- gemCost: 4,
- category: 'behavior'
- };
- if (analytics != null) {
- analytics.track('Fortify Potion', analyticsData);
+
+ if (analytics) {
+ analytics.track('Fortify Potion', {
+ uuid: user._id,
+ acquireMethod: 'Gems',
+ gemCost: 4,
+ category: 'behavior',
+ });
+ }
+
+ if (req.v2 === true) {
+ return user;
+ } else {
+ return [
+ {user, tasks},
+ i18n.t('fortifyComplete'),
+ ];
}
- return typeof cb === "function" ? cb(null, user) : void 0;
};
diff --git a/common/script/ops/reset.js b/common/script/ops/reset.js
index fbe87729e3..3e48fa4f2d 100644
--- a/common/script/ops/reset.js
+++ b/common/script/ops/reset.js
@@ -1,35 +1,29 @@
-import _ from 'lodash';
+import resetGear from '../fns/resetGear';
+import i18n from '../i18n';
-module.exports = function(user, req, cb) {
- var gear;
- user.habits = [];
- user.dailys = [];
- user.todos = [];
- user.rewards = [];
+module.exports = function reset (user, tasks = [], req = {}) {
user.stats.hp = 50;
user.stats.lvl = 1;
user.stats.gp = 0;
user.stats.exp = 0;
- gear = user.items.gear;
- _.each(['equipped', 'costume'], function(type) {
- gear[type].armor = 'armor_base_0';
- gear[type].weapon = 'weapon_base_0';
- gear[type].head = 'head_base_0';
- return gear[type].shield = 'shield_base_0';
- });
- if (typeof gear.owned === 'undefined') {
- gear.owned = {};
- }
- _.each(gear.owned, function(v, k) {
- if (gear.owned[k]) {
- gear.owned[k] = false;
+
+ let tasksToRemove = [];
+ tasks.forEach(task => {
+ if (!task.challenge || !task.challenge.id || task.challenge.broken) {
+ tasksToRemove.push(task._id);
+ let i = user.tasksOrder[`${task.type}s`].indexOf(task._id);
+ if (i !== -1) user.tasksOrder[`${task.type}s`].splice(i, 1);
}
- return true;
});
- gear.owned.weapon_warrior_0 = true;
- if (typeof user.markModified === "function") {
- user.markModified('items.gear.owned');
+
+ resetGear(user);
+
+ if (req.v2 === true) {
+ return user;
+ } else {
+ return [
+ {user, tasksToRemove},
+ i18n.t('resetComplete'),
+ ];
}
- user.preferences.costume = false;
- return typeof cb === "function" ? cb(null, user) : void 0;
};
diff --git a/common/script/ops/revive.js b/common/script/ops/revive.js
index 7b6fe8445f..30b29a78fc 100644
--- a/common/script/ops/revive.js
+++ b/common/script/ops/revive.js
@@ -1,72 +1,107 @@
import content from '../content/index';
import i18n from '../i18n';
import _ from 'lodash';
+import {
+ NotAuthorized,
+} from '../libs/errors';
+import randomVal from '../fns/randomVal';
-module.exports = function(user, req, cb, analytics) {
- var analyticsData, base, cl, gearOwned, item, losableItems, lostItem, lostStat;
- if (!(user.stats.hp <= 0)) {
- return typeof cb === "function" ? cb({
- code: 400,
- message: "Cannot revive if not dead"
- }) : void 0;
+module.exports = function revive (user, req = {}, analytics) {
+ if (user.stats.hp > 0) {
+ throw new NotAuthorized(i18n.t('cannotRevive', req.language));
}
+
_.merge(user.stats, {
hp: 50,
exp: 0,
- gp: 0
+ gp: 0,
});
+
if (user.stats.lvl > 1) {
user.stats.lvl--;
}
- lostStat = user.fns.randomVal(_.reduce(['str', 'con', 'per', 'int'], (function(m, k) {
+
+ let lostStat = randomVal(user, _.reduce(['str', 'con', 'per', 'int'], function findRandomStat (m, k) {
if (user.stats[k]) {
m[k] = k;
}
return m;
- }), {}));
+ }, {}));
+
if (lostStat) {
user.stats[lostStat]--;
}
- cl = user.stats["class"];
- gearOwned = (typeof (base = user.items.gear.owned).toObject === "function" ? base.toObject() : void 0) || user.items.gear.owned;
- losableItems = {};
- _.each(gearOwned, function(v, k) {
- var itm;
- if (v) {
- itm = content.gear.flat['' + k];
+
+ let base = user.items.gear.owned;
+ let gearOwned;
+
+ if (typeof base.toObject === 'function') {
+ gearOwned = base.toObject();
+ } else {
+ gearOwned = user.items.gear.owned;
+ }
+
+ let losableItems = {};
+ let userClass = user.stats.class;
+
+ _.each(gearOwned, function findLosableItems (value, key) {
+ let itm;
+ if (value) {
+ itm = content.gear.flat[key];
+
if (itm) {
- if ((itm.value > 0 || k === 'weapon_warrior_0') && (itm.klass === cl || (itm.klass === 'special' && (!itm.specialClass || itm.specialClass === cl)) || itm.klass === 'armoire')) {
- return losableItems['' + k] = '' + k;
+ let itemHasValueOrWarrior0 = itm.value > 0 || key === 'weapon_warrior_0';
+
+ let itemClassEqualsUserClass = itm.klass === userClass;
+
+ let itemClassSpecial = itm.klass === 'special';
+ let itemNotSpecialOrUserClassIsSpecial = !itm.specialClass || itm.specialClass === userClass;
+ let itemIsSpecial = itemNotSpecialOrUserClassIsSpecial && itemClassSpecial;
+
+ let itemIsArmoire = itm.klass === 'armoire';
+
+ if (itemHasValueOrWarrior0 && (itemClassEqualsUserClass || itemIsSpecial || itemIsArmoire)) {
+ losableItems[key] = key;
+ return losableItems[key];
}
}
}
});
- lostItem = user.fns.randomVal(losableItems);
- if (item = content.gear.flat[lostItem]) {
+
+ let lostItem = randomVal(user, losableItems);
+
+ let message = '';
+ let item = content.gear.flat[lostItem];
+
+ if (item) {
user.items.gear.owned[lostItem] = false;
+
if (user.items.gear.equipped[item.type] === lostItem) {
- user.items.gear.equipped[item.type] = item.type + "_base_0";
+ user.items.gear.equipped[item.type] = `${item.type}_base_0`;
}
+
if (user.items.gear.costume[item.type] === lostItem) {
- user.items.gear.costume[item.type] = item.type + "_base_0";
+ user.items.gear.costume[item.type] = `${item.type}_base_0`;
}
+
+ message = i18n.t('messageLostItem', { itemText: item.text(req.language)}, req.language);
}
- if (typeof user.markModified === "function") {
- user.markModified('items.gear');
+
+ if (analytics) {
+ analytics.track('Death', {
+ uuid: user._id,
+ lostItem,
+ gaLabel: lostItem,
+ category: 'behavior',
+ });
}
- analyticsData = {
- uuid: user._id,
- lostItem: lostItem,
- gaLabel: lostItem,
- category: 'behavior'
- };
- if (analytics != null) {
- analytics.track('Death', analyticsData);
+
+ if (req.v2 === true) {
+ return user;
+ } else {
+ return [
+ user.items,
+ message,
+ ];
}
- return typeof cb === "function" ? cb((item ? {
- code: 200,
- message: i18n.t('messageLostItem', {
- itemText: item.text(req.language)
- }, req.language)
- } : null), user) : void 0;
};
diff --git a/common/script/ops/score.js b/common/script/ops/score.js
deleted file mode 100644
index ec0d2a70e2..0000000000
--- a/common/script/ops/score.js
+++ /dev/null
@@ -1,221 +0,0 @@
-import moment from 'moment';
-import _ from 'lodash';
-import i18n from '../i18n';
-
-module.exports = function(user, req, cb) {
- var addPoints, calculateDelta, calculateReverseDelta, changeTaskValue, delta, direction, gainMP, id, multiplier, num, options, ref, stats, subtractPoints, task, th;
- ref = req.params, id = ref.id, direction = ref.direction;
- task = user.tasks[id];
- options = req.query || {};
- _.defaults(options, {
- times: 1,
- cron: false
- });
- user._tmp = {};
- stats = {
- gp: +user.stats.gp,
- hp: +user.stats.hp,
- exp: +user.stats.exp
- };
- task.value = +task.value;
- task.streak = ~~task.streak;
- if (task.priority == null) {
- task.priority = 1;
- }
- if (task.value > stats.gp && task.type === 'reward') {
- return typeof cb === "function" ? cb({
- code: 401,
- message: i18n.t('messageNotEnoughGold', req.language)
- }) : void 0;
- }
- delta = 0;
- calculateDelta = function() {
- var currVal, nextDelta, ref1;
- currVal = task.value < -47.27 ? -47.27 : task.value > 21.27 ? 21.27 : task.value;
- nextDelta = Math.pow(0.9747, currVal) * (direction === 'down' ? -1 : 1);
- if (((ref1 = task.checklist) != null ? ref1.length : void 0) > 0) {
- if (direction === 'down' && task.type === 'daily' && options.cron) {
- nextDelta *= 1 - _.reduce(task.checklist, (function(m, i) {
- return m + (i.completed ? 1 : 0);
- }), 0) / task.checklist.length;
- }
- if (task.type === 'todo') {
- nextDelta *= 1 + _.reduce(task.checklist, (function(m, i) {
- return m + (i.completed ? 1 : 0);
- }), 0);
- }
- }
- return nextDelta;
- };
- calculateReverseDelta = function() {
- var calc, closeEnough, currVal, diff, nextDelta, ref1, testVal;
- currVal = task.value < -47.27 ? -47.27 : task.value > 21.27 ? 21.27 : task.value;
- testVal = currVal + Math.pow(0.9747, currVal) * (direction === 'down' ? -1 : 1);
- closeEnough = 0.00001;
- while (true) {
- calc = testVal + Math.pow(0.9747, testVal);
- diff = currVal - calc;
- if (Math.abs(diff) < closeEnough) {
- break;
- }
- if (diff > 0) {
- testVal -= diff;
- } else {
- testVal += diff;
- }
- }
- nextDelta = testVal - currVal;
- if (((ref1 = task.checklist) != null ? ref1.length : void 0) > 0) {
- if (task.type === 'todo') {
- nextDelta *= 1 + _.reduce(task.checklist, (function(m, i) {
- return m + (i.completed ? 1 : 0);
- }), 0);
- }
- }
- return nextDelta;
- };
- changeTaskValue = function() {
- return _.times(options.times, function() {
- var nextDelta, ref1;
- nextDelta = !options.cron && direction === 'down' ? calculateReverseDelta() : calculateDelta();
- if (task.type !== 'reward') {
- if (user.preferences.automaticAllocation === true && user.preferences.allocationMode === 'taskbased' && !(task.type === 'todo' && direction === 'down')) {
- user.stats.training[task.attribute] += nextDelta;
- }
- if (direction === 'up') {
- user.party.quest.progress.up = user.party.quest.progress.up || 0;
- if ((ref1 = task.type) === 'daily' || ref1 === 'todo') {
- user.party.quest.progress.up += nextDelta * (1 + (user._statsComputed.str / 200));
- }
- if (task.type === 'habit') {
- user.party.quest.progress.up += nextDelta * (0.5 + (user._statsComputed.str / 400));
- }
- }
- task.value += nextDelta;
- }
- return delta += nextDelta;
- });
- };
- addPoints = function() {
- var _crit, afterStreak, currStreak, gpMod, intBonus, perBonus, streakBonus;
- _crit = (delta > 0 ? user.fns.crit() : 1);
- if (_crit > 1) {
- user._tmp.crit = _crit;
- }
- intBonus = 1 + (user._statsComputed.int * .025);
- stats.exp += Math.round(delta * intBonus * task.priority * _crit * 6);
- perBonus = 1 + user._statsComputed.per * .02;
- gpMod = delta * task.priority * _crit * perBonus;
- return stats.gp += task.streak ? (currStreak = direction === 'down' ? task.streak - 1 : task.streak, streakBonus = currStreak / 100 + 1, afterStreak = gpMod * streakBonus, currStreak > 0 ? gpMod > 0 ? user._tmp.streakBonus = afterStreak - gpMod : void 0 : void 0, afterStreak) : gpMod;
- };
- subtractPoints = function() {
- var conBonus, hpMod;
- conBonus = 1 - (user._statsComputed.con / 250);
- if (conBonus < .1) {
- conBonus = 0.1;
- }
- hpMod = delta * conBonus * task.priority * 2;
- return stats.hp += Math.round(hpMod * 10) / 10;
- };
- gainMP = function(delta) {
- delta *= user._tmp.crit || 1;
- user.stats.mp += delta;
- if (user.stats.mp >= user._statsComputed.maxMP) {
- user.stats.mp = user._statsComputed.maxMP;
- }
- if (user.stats.mp < 0) {
- return user.stats.mp = 0;
- }
- };
- switch (task.type) {
- case 'habit':
- changeTaskValue();
- if (delta > 0) {
- addPoints();
- } else {
- subtractPoints();
- }
- gainMP(_.max([0.25, .0025 * user._statsComputed.maxMP]) * (direction === 'down' ? -1 : 1));
- th = (task.history != null ? task.history : task.history = []);
- if (th[th.length - 1] && moment(th[th.length - 1].date).isSame(new Date, 'day')) {
- th[th.length - 1].value = task.value;
- } else {
- th.push({
- date: +(new Date),
- value: task.value
- });
- }
- if (typeof user.markModified === "function") {
- user.markModified("habits." + (_.findIndex(user.habits, {
- id: task.id
- })) + ".history");
- }
- break;
- case 'daily':
- if (options.cron) {
- changeTaskValue();
- subtractPoints();
- if (!user.stats.buffs.streaks) {
- task.streak = 0;
- }
- } else {
- changeTaskValue();
- if (direction === 'down') {
- delta = calculateDelta();
- }
- addPoints();
- gainMP(_.max([1, .01 * user._statsComputed.maxMP]) * (direction === 'down' ? -1 : 1));
- if (direction === 'up') {
- task.streak = task.streak ? task.streak + 1 : 1;
- if ((task.streak % 21) === 0) {
- user.achievements.streak = user.achievements.streak ? user.achievements.streak + 1 : 1;
- }
- } else {
- if ((task.streak % 21) === 0) {
- user.achievements.streak = user.achievements.streak ? user.achievements.streak - 1 : 0;
- }
- task.streak = task.streak ? task.streak - 1 : 0;
- }
- }
- break;
- case 'todo':
- if (options.cron) {
- changeTaskValue();
- } else {
- task.dateCompleted = direction === 'up' ? new Date : void 0;
- changeTaskValue();
- if (direction === 'down') {
- delta = calculateDelta();
- }
- addPoints();
- multiplier = _.max([
- _.reduce(task.checklist, (function(m, i) {
- return m + (i.completed ? 1 : 0);
- }), 1), 1
- ]);
- gainMP(_.max([multiplier, .01 * user._statsComputed.maxMP * multiplier]) * (direction === 'down' ? -1 : 1));
- }
- break;
- case 'reward':
- changeTaskValue();
- stats.gp -= Math.abs(task.value);
- num = parseFloat(task.value).toFixed(2);
- if (stats.gp < 0) {
- stats.hp += stats.gp;
- stats.gp = 0;
- }
- }
- user.fns.updateStats(stats, req);
- if (typeof window === 'undefined') {
- if (direction === 'up') {
- user.fns.randomDrop({
- task: task,
- delta: delta
- }, req);
- }
- }
- if (typeof cb === "function") {
- cb(null, user);
- }
- return delta;
-};
diff --git a/common/script/ops/scoreTask.js b/common/script/ops/scoreTask.js
new file mode 100644
index 0000000000..c366b84624
--- /dev/null
+++ b/common/script/ops/scoreTask.js
@@ -0,0 +1,260 @@
+import _ from 'lodash';
+import {
+ NotAuthorized,
+} from '../libs/errors';
+import i18n from '../i18n';
+import updateStats from '../fns/updateStats';
+import crit from '../fns/crit';
+
+const MAX_TASK_VALUE = 21.27;
+const MIN_TASK_VALUE = -47.27;
+const CLOSE_ENOUGH = 0.00001;
+
+function _getTaskValue (taskValue) {
+ if (taskValue < MIN_TASK_VALUE) {
+ return MIN_TASK_VALUE;
+ } else if (taskValue > MAX_TASK_VALUE) {
+ return MAX_TASK_VALUE;
+ } else {
+ return taskValue;
+ }
+}
+
+// Calculates the next task.value based on direction
+// Uses a capped inverse log y=.95^x, y>= -5
+function _calculateDelta (task, direction, cron) {
+ // Min/max on task redness
+ let currVal = _getTaskValue(task.value);
+ let nextDelta = Math.pow(0.9747, currVal) * (direction === 'down' ? -1 : 1);
+
+ // Checklists
+ if (task.checklist && task.checklist.length > 0) {
+ // If the Daily, only dock them a portion based on their checklist completion
+ if (direction === 'down' && task.type === 'daily' && cron) {
+ nextDelta *= 1 - _.reduce(task.checklist, (m, i) => m + (i.completed ? 1 : 0), 0) / task.checklist.length;
+ }
+
+ // If To-Do, point-match the TD per checklist item completed
+ if (task.type === 'todo') {
+ nextDelta *= 1 + _.reduce(task.checklist, (m, i) => m + (i.completed ? 1 : 0), 0);
+ }
+ }
+
+ return nextDelta;
+}
+
+// Approximates the reverse delta for the task value
+// This is meant to return the task value to its original value when unchecking a task.
+// First, calculate the value using the normal way for our first guess although
+// it will be a bit off
+function _calculateReverseDelta (task, direction) {
+ let currVal = _getTaskValue(task.value);
+ let testVal = currVal + Math.pow(0.9747, currVal) * (direction === 'down' ? -1 : 1);
+
+ // Now keep moving closer to the original value until we get "close enough"
+ // Check how close we are to the original value by computing the delta off our guess
+ // and looking at the difference between that and our current value.
+ while (true) { // eslint-disable-line no-constant-condition
+ let calc = testVal + Math.pow(0.9747, testVal);
+ let diff = currVal - calc;
+
+ if (Math.abs(diff) < CLOSE_ENOUGH) break;
+
+ if (diff > 0) {
+ testVal -= diff;
+ } else {
+ testVal += diff;
+ }
+ }
+
+ // When we get close enough, return the difference between our approximated value
+ // and the current value. This will be the delta calculated from the original value
+ // before the task was checked.
+ let nextDelta = testVal - currVal;
+
+ // Checklists - If To-Do, point-match the TD per checklist item completed
+ if (task.checklist && task.checklist.length > 0 && task.type === 'todo') {
+ nextDelta *= 1 + _.reduce(task.checklist, (m, i) => m + (i.completed ? 1 : 0), 0);
+ }
+
+ return nextDelta;
+}
+
+function _gainMP (user, val) {
+ val *= user._tmp.crit || 1;
+ user.stats.mp += val;
+
+ if (user.stats.mp >= user._statsComputed.maxMP) user.stats.mp = user._statsComputed.maxMP;
+ if (user.stats.mp < 0) {
+ user.stats.mp = 0;
+ }
+}
+
+// HP modifier
+// ===== CONSTITUTION =====
+// TODO Decreases HP loss from bad habits / missed dailies by 0.5% per point.
+function _subtractPoints (user, task, stats, delta) {
+ let conBonus = 1 - user._statsComputed.con / 250;
+ if (conBonus < 0.1) conBonus = 0.1;
+
+ let hpMod = delta * conBonus * task.priority * 2; // constant 2 multiplier for better results
+ stats.hp += Math.round(hpMod * 10) / 10; // round to 1dp
+ return stats.hp;
+}
+
+function _addPoints (user, task, stats, direction, delta) {
+ // ===== CRITICAL HITS =====
+ // allow critical hit only when checking off a task, not when unchecking it:
+ let _crit = delta > 0 ? crit(user) : 1;
+ // if there was a crit, alert the user via notification
+ if (_crit > 1) user._tmp.crit = _crit;
+
+ // Exp Modifier
+ // ===== Intelligence =====
+ // TODO Increases Experience gain by .2% per point.
+ let intBonus = 1 + user._statsComputed.int * 0.025;
+ stats.exp += Math.round(delta * intBonus * task.priority * _crit * 6);
+
+ // GP modifier
+ // ===== PERCEPTION =====
+ // TODO Increases Gold gained from tasks by .3% per point.
+ let perBonus = 1 + user._statsComputed.per * 0.02;
+ let gpMod = delta * task.priority * _crit * perBonus;
+
+ if (task.streak) {
+ let currStreak = direction === 'down' ? task.streak - 1 : task.streak;
+ let streakBonus = currStreak / 100 + 1; // eg, 1-day streak is 1.01, 2-day is 1.02, etc
+ let afterStreak = gpMod * streakBonus;
+ if (currStreak > 0 && gpMod > 0) {
+ user._tmp.streakBonus = afterStreak - gpMod; // keep this on-hand for later, so we can notify streak-bonus
+ }
+
+ stats.gp += afterStreak;
+ } else {
+ stats.gp += gpMod;
+ }
+}
+
+function _changeTaskValue (user, task, direction, times, cron) {
+ let addToDelta = 0;
+
+ // If multiple days have passed, multiply times days missed
+ _.times(times, () => {
+ // Each iteration calculate the nextDelta, which is then accumulated in the total delta.
+ let nextDelta = !cron && direction === 'down' ? _calculateReverseDelta(task, direction) : _calculateDelta(task, direction, cron);
+
+ if (task.type !== 'reward') {
+ if (user.preferences.automaticAllocation === true && user.preferences.allocationMode === 'taskbased' && !(task.type === 'todo' && direction === 'down')) {
+ user.stats.training[task.attribute] += nextDelta;
+ }
+
+ if (direction === 'up') { // Make progress on quest based on STR
+ user.party.quest.progress.up = user.party.quest.progress.up || 0;
+
+ if (task.type === 'todo' || task.type === 'daily') {
+ user.party.quest.progress.up += nextDelta * (1 + user._statsComputed.str / 200);
+ } else if (task.type === 'habit') {
+ user.party.quest.progress.up += nextDelta * (0.5 + user._statsComputed.str / 400);
+ }
+ }
+
+ task.value += nextDelta;
+ }
+
+ addToDelta += nextDelta;
+ });
+
+ return addToDelta;
+}
+
+module.exports = function scoreTask (options = {}, req = {}) {
+ let {user, task, direction, times = 1, cron = false} = options;
+ let delta = 0;
+ let stats = {
+ gp: user.stats.gp,
+ hp: user.stats.hp,
+ exp: user.stats.exp,
+ };
+
+ // This is for setting one-time temporary flags, such as streakBonus or itemDropped. Useful for notifying
+ // the API consumer, then cleared afterwards
+ user._tmp = {};
+
+ // If they're trying to purchase a too-expensive reward, don't allow them to do that.
+ if (task.value > user.stats.gp && task.type === 'reward') throw new NotAuthorized(i18n.t('messageNotEnoughGold', req.language));
+
+ if (task.type === 'habit') {
+ delta += _changeTaskValue(user, task, direction, times, cron);
+
+ // Add habit value to habit-history (if different)
+ if (delta > 0) {
+ _addPoints(user, task, stats, direction, delta);
+ } else {
+ _subtractPoints(user, task, stats, delta);
+ }
+ _gainMP(user, _.max([0.25, 0.0025 * user._statsComputed.maxMP]) * (direction === 'down' ? -1 : 1));
+
+ task.history = task.history || [];
+ // Add history entry, even more than 1 per day
+ task.history.push({
+ date: Number(new Date()),
+ value: task.value,
+ });
+ } else if (task.type === 'daily') {
+ if (cron) {
+ delta += _changeTaskValue(user, task, direction, times, cron);
+ _subtractPoints(user, task, stats, delta);
+ if (!user.stats.buffs.streaks) task.streak = 0;
+ } else {
+ delta += _changeTaskValue(user, task, direction, times, cron);
+ if (direction === 'down') delta = _calculateDelta(task, direction, cron); // recalculate delta for unchecking so the gp and exp come out correctly
+ _addPoints(user, task, stats, direction, delta); // obviously for delta>0, but also a trick to undo accidental checkboxes
+ _gainMP(user, _.max([1, 0.01 * user._statsComputed.maxMP]) * (direction === 'down' ? -1 : 1));
+
+ if (direction === 'up') {
+ task.streak += 1;
+ // Give a streak achievement when the streak is a multiple of 21
+ if (task.streak % 21 === 0) user.achievements.streak = user.achievements.streak ? user.achievements.streak + 1 : 1;
+ task.completed = true;
+ } else if (direction === 'down') {
+ // Remove a streak achievement if streak was a multiple of 21 and the daily was undone
+ if (task.streak % 21 === 0) user.achievements.streak = user.achievements.streak ? user.achievements.streak - 1 : 0;
+ task.streak -= 1;
+ task.completed = false;
+ }
+ }
+ } else if (task.type === 'todo') {
+ if (cron) { // don't touch stats on cron
+ delta += _changeTaskValue(user, task, direction, times, cron);
+ } else {
+ if (direction === 'up') {
+ task.dateCompleted = new Date();
+ task.completed = true;
+ } else if (direction === 'down') {
+ task.completed = false;
+ task.dateCompleted = undefined;
+ }
+
+ delta += _changeTaskValue(user, task, direction, times, cron);
+ if (direction === 'down') delta = _calculateDelta(task, direction, cron); // recalculate delta for unchecking so the gp and exp come out correctly
+ _addPoints(user, task, stats, direction, delta);
+
+ // MP++ per checklist item in ToDo, bonus per CLI
+ let multiplier = _.max([_.reduce(task.checklist, (m, i) => m + (i.completed ? 1 : 0), 1), 1]);
+ _gainMP(user, _.max([multiplier, 0.01 * user._statsComputed.maxMP * multiplier]) * (direction === 'down' ? -1 : 1));
+ }
+ } else if (task.type === 'reward') {
+ // Don't adjust values for rewards
+ delta += _changeTaskValue(user, task, direction, times, cron);
+ // purchase item
+ stats.gp -= Math.abs(task.value);
+ // hp - gp difference
+ if (stats.gp < 0) {
+ stats.hp += stats.gp;
+ stats.gp = 0;
+ }
+ }
+
+ updateStats(user, stats, req);
+ return [delta];
+};
diff --git a/common/script/ops/sell.js b/common/script/ops/sell.js
index 33e5f7d673..10412da222 100644
--- a/common/script/ops/sell.js
+++ b/common/script/ops/sell.js
@@ -1,23 +1,43 @@
import content from '../content/index';
+import i18n from '../i18n';
import _ from 'lodash';
import splitWhitespace from '../libs/splitWhitespace';
+import {
+ NotFound,
+ NotAuthorized,
+ BadRequest,
+} from '../libs/errors';
-module.exports = function(user, req, cb) {
- var key, ref, type;
- ref = req.params, key = ref.key, type = ref.type;
- if (type !== 'eggs' && type !== 'hatchingPotions' && type !== 'food') {
- return typeof cb === "function" ? cb({
- code: 404,
- message: ":type not found. Must bes in [eggs, hatchingPotions, food]"
- }) : void 0;
+const ACCEPTEDTYPES = ['eggs', 'hatchingPotions', 'food'];
+
+module.exports = function sell (user, req = {}) {
+ let key = _.get(req.params, 'key');
+ let type = _.get(req.params, 'type');
+
+ if (!type) {
+ throw new BadRequest(i18n.t('typeRequired', req.language));
}
+
+ if (!key) {
+ throw new BadRequest(i18n.t('keyRequired', req.language));
+ }
+
+ if (ACCEPTEDTYPES.indexOf(type) === -1) {
+ throw new NotAuthorized(i18n.t('typeNotSellable', {acceptedTypes: ACCEPTEDTYPES.join(', ')}, req.language));
+ }
+
if (!user.items[type][key]) {
- return typeof cb === "function" ? cb({
- code: 404,
- message: ":key not found for user.items." + type
- }) : void 0;
+ throw new NotFound(i18n.t('userItemsKeyNotFound', {type}, req.language));
}
+
user.items[type][key]--;
user.stats.gp += content[type][key].value;
- return typeof cb === "function" ? cb(null, _.pick(user, splitWhitespace('stats items'))) : void 0;
+
+ if (req.v2 === true) {
+ return _.pick(user, splitWhitespace('stats items'));
+ } else {
+ return [
+ _.pick(user, splitWhitespace('stats items')),
+ ];
+ }
};
diff --git a/common/script/ops/sleep.js b/common/script/ops/sleep.js
index dec8095ad3..1a531ecb88 100644
--- a/common/script/ops/sleep.js
+++ b/common/script/ops/sleep.js
@@ -1,4 +1,9 @@
-module.exports = function(user, req, cb) {
+module.exports = function sleep (user, req = {}) {
user.preferences.sleep = !user.preferences.sleep;
- return typeof cb === "function" ? cb(null, {}) : void 0;
+
+ if (req.v2 === true) {
+ return {};
+ } else {
+ return [user.preferences.sleep];
+ }
};
diff --git a/common/script/ops/sortTag.js b/common/script/ops/sortTag.js
index 85dcda169f..c1fc42f330 100644
--- a/common/script/ops/sortTag.js
+++ b/common/script/ops/sortTag.js
@@ -1,9 +1,19 @@
-module.exports = function(user, req, cb) {
- var from, ref, to;
- ref = req.query, to = ref.to, from = ref.from;
- if (!((to != null) && (from != null))) {
- return typeof cb === "function" ? cb('?to=__&from=__ are required') : void 0;
+import { BadRequest } from '../libs/errors';
+import _ from 'lodash';
+
+// TODO used only in client, move there?
+
+module.exports = function sortTag (user, req = {}) {
+ let to = _.get(req, 'query.to');
+ let fromParam = _.get(req, 'query.from');
+
+ let invalidTo = !to && to !== 0;
+ let invalidFrom = !fromParam && fromParam !== 0;
+
+ if (invalidTo || invalidFrom) {
+ throw new BadRequest('?to=__&from=__ are required');
}
- user.tags.splice(to, 0, user.tags.splice(from, 1)[0]);
- return typeof cb === "function" ? cb(null, user.tags) : void 0;
+
+ user.tags.splice(to, 0, user.tags.splice(fromParam, 1)[0]);
+ return user.tags;
};
diff --git a/common/script/ops/sortTask.js b/common/script/ops/sortTask.js
index b903b692dc..07db8289cf 100644
--- a/common/script/ops/sortTask.js
+++ b/common/script/ops/sortTask.js
@@ -1,39 +1,49 @@
import i18n from '../i18n';
import preenTodos from '../libs/preenTodos';
+import {
+ NotFound,
+ BadRequest,
+} from '../libs/errors';
+import _ from 'lodash';
-module.exports = function(user, req, cb) {
- var from, id, movedTask, preenedTasks, ref, task, tasks, to;
- id = req.params.id;
- ref = req.query, to = ref.to, from = ref.from;
- task = user.tasks[id];
- if (!task) {
- return typeof cb === "function" ? cb({
- code: 404,
- message: i18n.t('messageTaskNotFound', req.language)
- }) : void 0;
+// TODO used only in client, move there?
+
+module.exports = function sortTask (user, req = {}) {
+ let id = _.get(req, 'params.id');
+ let to = _.get(req, 'query.to');
+ let fromParam = _.get(req, 'query.from');
+ let taskType = _.get(req, 'params.taskType');
+
+ let index = _.findIndex(user[`${taskType}s`], function findById (task) {
+ return task._id === id;
+ });
+
+ if (index === -1) {
+ throw new NotFound(i18n.t('messageTaskNotFound', req.language));
}
- if (!((to != null) && (from != null))) {
- return typeof cb === "function" ? cb('?to=__&from=__ are required') : void 0;
+ if (to == null && fromParam == null) { // eslint-disable-line eqeqeq
+ throw new BadRequest('?to=__&from=__ are required');
}
- tasks = user[task.type + "s"];
- if (task.type === 'todo' && tasks[from] !== task) {
- preenedTasks = preenTodos(tasks);
+
+ let tasks = user[`${taskType}s`];
+
+ if (taskType === 'todo') {
+ let preenedTasks = preenTodos(tasks);
+
if (to !== -1) {
to = tasks.indexOf(preenedTasks[to]);
}
- from = tasks.indexOf(preenedTasks[from]);
+
+ fromParam = tasks.indexOf(preenedTasks[fromParam]);
}
- if (tasks[from] !== task) {
- return typeof cb === "function" ? cb({
- code: 404,
- message: i18n.t('messageTaskNotFound', req.language)
- }) : void 0;
- }
- movedTask = tasks.splice(from, 1)[0];
+
+ let movedTask = tasks.splice(fromParam, 1)[0];
+
if (to === -1) {
tasks.push(movedTask);
} else {
tasks.splice(to, 0, movedTask);
}
- return typeof cb === "function" ? cb(null, tasks) : void 0;
+
+ return tasks;
};
diff --git a/common/script/ops/unlock.js b/common/script/ops/unlock.js
index c53a5997ec..62f1420e2a 100644
--- a/common/script/ops/unlock.js
+++ b/common/script/ops/unlock.js
@@ -1,63 +1,118 @@
import i18n from '../i18n';
import _ from 'lodash';
import splitWhitespace from '../libs/splitWhitespace';
+import {
+ NotAuthorized,
+ BadRequest,
+} from '../libs/errors';
+import setWith from 'lodash.setwith'; // Not available in lodash 3
-module.exports = function(user, req, cb, analytics) {
- var alreadyOwns, analyticsData, cost, fullSet, k, path, split, v;
- path = req.query.path;
- fullSet = ~path.indexOf(",");
- cost = ~path.indexOf('background.') ? fullSet ? 3.75 : 1.75 : fullSet ? 1.25 : 0.5;
- alreadyOwns = !fullSet && user.fns.dotGet("purchased." + path) === true;
- if ((user.balance < cost || !user.balance) && !alreadyOwns) {
- return typeof cb === "function" ? cb({
- code: 401,
- message: i18n.t('notEnoughGems', req.language)
- }) : void 0;
+// If item is already purchased -> equip it
+// Otherwise unlock it
+module.exports = function unlock (user, req = {}, analytics) {
+ let path = _.get(req.query, 'path');
+
+ if (!path) {
+ throw new BadRequest(i18n.t('pathRequired', req.language));
}
- if (fullSet) {
- _.each(path.split(","), function(p) {
- if (~path.indexOf('gear.')) {
- user.fns.dotSet("" + p, true);
- true;
- } else {
+ let isFullSet = path.indexOf(',') !== -1;
+ let isBackground = path.indexOf('background.') !== -1;
+
+ let cost;
+ if (isBackground && isFullSet) {
+ cost = 3.75;
+ } else if (isBackground) {
+ cost = 1.75;
+ } else if (isFullSet) {
+ cost = 1.25;
+ } else {
+ cost = 0.5;
+ }
+
+ let setPaths;
+ let alreadyOwns;
+
+ if (isFullSet) {
+ setPaths = path.split(',');
+ let alreadyOwnedItems = 0;
+
+ _.each(setPaths, singlePath => {
+ if (_.get(user, `purchased.${singlePath}`) === true) {
+ alreadyOwnedItems++;
}
- user.fns.dotSet("purchased." + p, true);
- return true;
+ });
+
+ if (alreadyOwnedItems === setPaths.length) {
+ throw new NotAuthorized(i18n.t('alreadyUnlocked', req.language));
+ // TODO write math formula to check if buying the full set is cheaper than the items individually
+ // (item cost * number of remaining items) < setCost`
+ } /* else if (alreadyOwnedItems > 0) {
+ throw new NotAuthorized(i18n.t('alreadyUnlockedPart', req.language));
+ } */
+ } else {
+ alreadyOwns = _.get(user, `purchased.${path}`) === true;
+ }
+
+ if ((!user.balance || user.balance < cost) && !alreadyOwns) {
+ throw new NotAuthorized(i18n.t('notEnoughGems', req.language));
+ }
+
+ if (isFullSet) {
+ _.each(setPaths, function markItemsAsPurchased (pathPart) {
+ if (path.indexOf('gear.') !== -1) {
+ // Using Object so path[1] won't create an array but an object {path: {1: value}}
+ setWith(user, pathPart, true, Object);
+ }
+
+ // Using Object so path[1] won't create an array but an object {path: {1: value}}
+ setWith(user, `purchased.${pathPart}`, true, Object);
});
} else {
- if (alreadyOwns) {
- split = path.split('.');
- v = split.pop();
- k = split.join('.');
- if (k === 'background' && v === user.preferences.background) {
- v = '';
+ if (alreadyOwns) { // eslint-disable-line no-lonely-if
+ let split = path.split('.');
+ let value = split.pop();
+ let key = split.join('.');
+ if (key === 'background' && value === user.preferences.background) {
+ value = '';
}
- user.fns.dotSet("preferences." + k, v);
- return typeof cb === "function" ? cb(null, req) : void 0;
+
+ // Using Object so path[1] won't create an array but an object {path: {1: value}}
+ setWith(user, `preferences.${key}`, value, Object);
+ } else {
+ // Using Object so path[1] won't create an array but an object {path: {1: value}}
+ setWith(user, `purchased.${path}`, true, Object);
}
- user.fns.dotSet("purchased." + path, true);
}
- user.balance -= cost;
- if (~path.indexOf('gear.')) {
- if (typeof user.markModified === "function") {
- user.markModified('gear.owned');
- }
- } else {
- if (typeof user.markModified === "function") {
+
+ if (!alreadyOwns) {
+ if (path.indexOf('gear.') === -1) {
user.markModified('purchased');
}
+
+ user.balance -= cost;
+
+ if (analytics) {
+ analytics.track('acquire item', {
+ uuid: user._id,
+ itemKey: path,
+ itemType: 'customization',
+ acquireMethod: 'Gems',
+ gemCost: cost / 0.25,
+ category: 'behavior',
+ });
+ }
}
- analyticsData = {
- uuid: user._id,
- itemKey: path,
- itemType: 'customization',
- acquireMethod: 'Gems',
- gemCost: cost / .25,
- category: 'behavior'
- };
- if (analytics != null) {
- analytics.track('acquire item', analyticsData);
+
+ let response = [
+ _.pick(user, splitWhitespace('purchased preferences items')),
+ ];
+
+ if (!alreadyOwns) response.push(i18n.t('unlocked', req.language));
+
+ if (req.v2 === true) {
+ return response[0];
+ } else {
+ return response;
}
- return typeof cb === "function" ? cb(null, _.pick(user, splitWhitespace('purchased preferences items'))) : void 0;
};
diff --git a/common/script/ops/update.js b/common/script/ops/update.js
index 12a100e372..c41e74180e 100644
--- a/common/script/ops/update.js
+++ b/common/script/ops/update.js
@@ -1,9 +1,11 @@
import _ from 'lodash';
-module.exports = function(user, req, cb) {
- _.each(req.body, function(v, k) {
- user.fns.dotSet(k, v);
- return true;
+// TODO used only in client, move there?
+
+module.exports = function updateUser (user, req = {}) {
+ _.each(req.body, (val, key) => {
+ _.set(user, key, val);
});
- return typeof cb === "function" ? cb(null, user) : void 0;
+
+ return user;
};
diff --git a/common/script/ops/updateTag.js b/common/script/ops/updateTag.js
index 8e4019fa51..ade87f916d 100644
--- a/common/script/ops/updateTag.js
+++ b/common/script/ops/updateTag.js
@@ -1,18 +1,20 @@
import i18n from '../i18n';
import _ from 'lodash';
+import { NotFound } from '../libs/errors';
-module.exports = function(user, req, cb) {
- var i, tid;
- tid = req.params.id;
- i = _.findIndex(user.tags, {
- id: tid
+// TODO used only in client, move there?
+
+module.exports = function updateTag (user, req = {}) {
+ let tid = _.get(req, 'params.id');
+
+ let index = _.findIndex(user.tags, {
+ id: tid,
});
- if (!~i) {
- return typeof cb === "function" ? cb({
- code: 404,
- message: i18n.t('messageTagNotFound', req.language)
- }) : void 0;
+
+ if (index === -1) {
+ throw new NotFound(i18n.t('messageTagNotFound', req.language));
}
- user.tags[i].name = req.body.name;
- return typeof cb === "function" ? cb(null, user.tags[i]) : void 0;
+
+ user.tags[index].name = _.get(req, 'body.name');
+ return user.tags[index];
};
diff --git a/common/script/ops/updateTask.js b/common/script/ops/updateTask.js
index 427104f15e..2a2b1edba2 100644
--- a/common/script/ops/updateTask.js
+++ b/common/script/ops/updateTask.js
@@ -1,23 +1,25 @@
-import i18n from '../i18n';
import _ from 'lodash';
-module.exports = function(user, req, cb) {
- var ref, task;
- if (!(task = user.tasks[(ref = req.params) != null ? ref.id : void 0])) {
- return typeof cb === "function" ? cb({
- code: 404,
- message: i18n.t('messageTaskNotFound', req.language)
- }) : void 0;
+// From server pass task.toObject() not the task document directly
+module.exports = function updateTask (task, req = {}) {
+ let body = req.body || {};
+
+ // If reminders are updated -> replace the original ones
+ if (body.reminders) {
+ task.reminders = body.reminders;
}
- _.merge(task, _.omit(req.body, ['checklist', 'reminders', 'id', 'type']));
- if (req.body.checklist) {
- task.checklist = req.body.checklist;
+
+ // If checklist is updated -> replace the original one
+ if (body.checklist) {
+ task.checklist = body.checklist;
}
- if (req.body.reminders) {
- task.reminders = req.body.reminders;
+
+ // If tags are updated -> replace the original ones
+ if (body.tags) {
+ task.tags = body.tags;
}
- if (typeof task.markModified === "function") {
- task.markModified('tags');
- }
- return typeof cb === "function" ? cb(null, task) : void 0;
+
+ _.merge(task, _.omit(body, ['_id', 'id', 'type', 'reminders', 'checklist', 'tags']));
+
+ return [task];
};
diff --git a/common/script/ops/updateWebhook.js b/common/script/ops/updateWebhook.js
index e2775a40b4..63fed89b17 100644
--- a/common/script/ops/updateWebhook.js
+++ b/common/script/ops/updateWebhook.js
@@ -1,9 +1,20 @@
-import _ from 'lodash';
+import validator from 'validator';
+import i18n from '../i18n';
+import {
+ BadRequest,
+} from '../libs/errors';
-module.exports = function(user, req, cb) {
- _.merge(user.preferences.webhooks[req.params.id], req.body);
- if (typeof user.markModified === "function") {
- user.markModified('preferences.webhooks');
+module.exports = function updateWebhook (user, req) {
+ if (!validator.isURL(req.body.url)) throw new BadRequest(i18n.t('invalidUrl', req.language));
+ if (!validator.isBoolean(req.body.enabled)) throw new BadRequest(i18n.t('invalidEnabled', req.language));
+
+ user.markModified('preferences.webhooks');
+ user.preferences.webhooks[req.params.id].url = req.body.url;
+ user.preferences.webhooks[req.params.id].enabled = req.body.enabled;
+
+ if (req.v2 === true) {
+ return user.preferences.webhooks;
+ } else {
+ return [user.preferences.webhooks[req.params.id]];
}
- return typeof cb === "function" ? cb(null, user.preferences.webhooks) : void 0;
};
diff --git a/common/script/public/config.js b/common/script/public/config.js
index bb78e244bd..4456909ef4 100644
--- a/common/script/public/config.js
+++ b/common/script/public/config.js
@@ -1,8 +1,48 @@
'use strict';
-angular.module('habitrpg').config(['$httpProvider', function($httpProvider){
+
+angular.module('habitrpg')
+.config(['$httpProvider', function($httpProvider){
$httpProvider.interceptors.push(['$q', '$rootScope', function($q, $rootScope){
+ var resyncNumber = 0;
+ var lastResync = 0;
+
+ // Verify that the user was not updated from another browser/app/client
+ // If it was, sync
+ function verifyUserUpdated (response) {
+ var isApiCall = response.config.url.indexOf('api/v3') !== -1;
+ var isUserAvailable = $rootScope.User && $rootScope.User.user && $rootScope.User.user._wrapped === true;
+ var hasUserV = response.data && response.data.userV;
+ var isNotSync = response.config.url.indexOf('/api/v3/user') !== 0;
+
+ if (isApiCall && isUserAvailable && hasUserV) {
+ var oldUserV = $rootScope.User.user._v;
+ $rootScope.User.user._v = response.data.userV;
+
+ // Something has changed on the user object that was not tracked here, sync the user
+ if (isNotSync && ($rootScope.User.user._v - oldUserV) > 1) {
+ $rootScope.User.sync();
+ }
+ }
+ }
+
return {
+ request: function (config) {
+ var url = config.url;
+
+ if (url.indexOf('api/v3') !== -1) {
+ if ($rootScope.User && $rootScope.User.user) {
+ if (url.indexOf('?') !== -1) {
+ config.url += '&userV=' + $rootScope.User.user._v;
+ } else {
+ config.url += '?userV=' + $rootScope.User.user._v;
+ }
+ }
+ }
+
+ return config;
+ },
response: function(response) {
+ verifyUserUpdated(response);
return response;
},
responseError: function(response) {
@@ -21,25 +61,43 @@ angular.module('habitrpg').config(['$httpProvider', function($httpProvider){
if (!mobileApp) // skip mobile for now
$rootScope.$broadcast('responseError', "The site has been updated and the page needs to refresh. The last action has not been recorded, please refresh and try again.");
- } else if (response.data.code && response.data.code === 'ACCOUNT_SUSPENDED') {
+ } else if (response.data && response.data.code && response.data.code === 'ACCOUNT_SUSPENDED') {
confirm(response.data.err);
localStorage.clear();
window.location.href = mobileApp ? '/app/login' : '/logout'; //location.reload()
- // 400 range?
+ // 400 range
+ } else if (response.status < 400) {
+ // never triggered because we're in responseError
+ $rootScope.$broadcast('responseText', response.data && response.data.message);
} else if (response.status < 500) {
- $rootScope.$broadcast('responseText', response.data.err || response.data);
- // Need to reject the prompse so the error is handled correctly
- if (response.status === 401)
- return $q.reject(response);
+ if (response.status === 400 && response.data && response.data.errors && _.isArray(response.data.errors)) { // bad requests with more info
+ response.data.errors.forEach(function (err) {
+ $rootScope.$broadcast('responseError', err.message);
+ });
+ } else {
+ $rootScope.$broadcast('responseError', response.data && response.data.message);
+ }
+ if ($rootScope.User && $rootScope.User.sync) {
+ if (resyncNumber < 100 && (Date.now() - lastResync) > 500) { // avoid thousands of requests when user is not found
+ $rootScope.User.sync();
+ resyncNumber++;
+ lastResync = Date.now();
+ }
+ }
+
+ // Need to reject the prompse so the error is handled correctly
+ if (response.status === 401) {
+ return $q.reject(response);
+ }
// Error
} else {
- var error = window.env.t('requestError') + '
' + window.env.t('seeConsole');
if (mobileApp) error = 'Error contacting the server. Please try again in a few minutes.';
- $rootScope.$broadcast('responseError', error);
+ $rootScope.$broadcast('responseError500', error);
console.error(response);
}
@@ -47,4 +105,4 @@ angular.module('habitrpg').config(['$httpProvider', function($httpProvider){
}
};
}]);
-}]);
\ No newline at end of file
+}]);
diff --git a/common/script/public/directives.js b/common/script/public/directives.js
index 5662014e39..cd43ccb67c 100644
--- a/common/script/public/directives.js
+++ b/common/script/public/directives.js
@@ -2,90 +2,26 @@
/**
* Markdown
- * See http://www.heikura.me/#!/angularjs-markdown-directive
*/
(function(){
var md = function () {
- marked.setOptions({
- gfm:true,
- pedantic:false,
- sanitize:true
- // callback for code highlighter
- // Uncomment this (and htljs.tabReplace below) if we add in highlight.js (http://www.heikura.me/#!/angularjs-markdown-directive)
-// highlight:function (code, lang) {
-// if (lang != undefined)
-// return hljs.highlight(lang, code).value;
-//
-// return hljs.highlightAuto(code).value;
-// }
- });
-
- emoji.img_path = 'common/img/emoji/unicode/';
+ var mdown = window.habiticaMarkdown;
var toHtml = function (markdown) {
if (markdown == undefined)
return '';
- markdown = marked(markdown);
- markdown = emoji.replace_colons(markdown);
- markdown = emoji.replace_unified(markdown);
+ markdown = mdown.render(markdown);
return markdown;
};
- // [nickgordon20131123] this hacky override wraps images with a link to the image in a new window, and also adds some classes in case we want to style
- marked.InlineLexer.prototype.outputLink = function(cap, link) {
- var escape = function(html, encode) {
- return html
- .replace(!encode ? /&(?!#?\w+;)/g : /&/g, '&')
- .replace(//g, '>')
- .replace(/"/g, '"')
- .replace(/'/g, ''');
- };
- if (cap[0].charAt(0) !== '!') {
- return ''
- + this.output(cap[1])
- + '';
- } else {
- return '';
- }
- }
-
- //hljs.tabReplace = ' ';
-
return {
toHtml:toHtml
};
}();
- habitrpg.directive('markdown', ['$timeout','MOBILE_APP', function($timeout, MOBILE_APP) {
+ habitrpg.directive('markdown', ['$timeout', function($timeout) {
return {
restrict: 'E',
link: function(scope, element, attrs) {
@@ -104,36 +40,16 @@
html = html.replace(userHighlight, "@"+userName+"");
- html = html.replace(' href',' target="'+linktarget+'" href');
element.html(html);
- if(MOBILE_APP) {
- var elements = element.find("a");
- _.forEach(elements, function(link){
- if(link.href) {
-
- link.onclick = function (e) {
- scope.externalLink(this.href);
-
- e.preventDefault();
- e.stopPropagation();
- };
- }
- });
- }
-
- if(removeWatch)
- {
+ if (removeWatch) {
doRemoveWatch();
}
};
- if(useTimeout)
- {
+ if(useTimeout) {
$timeout(replaceMarkdown, timeoutTime);
- }
- else
- {
+ } else {
replaceMarkdown();
}
});
@@ -145,8 +61,6 @@
return function(input){
var html = md.toHtml(input);
- html = html.replace(' href',' target="_blank" href');
-
return html;
};
});
diff --git a/common/script/public/userServices.js b/common/script/public/userServices.js
deleted file mode 100644
index e5b1790086..0000000000
--- a/common/script/public/userServices.js
+++ /dev/null
@@ -1,268 +0,0 @@
-'use strict';
-
-angular.module('habitrpg')
- .service('ApiUrl', ['API_URL', function(currentApiUrl){
- this.setApiUrl = function(newUrl){
- currentApiUrl = newUrl;
- };
-
- this.get = function(){
- return currentApiUrl;
- };
- }])
-
-/**
- * Services that persists and retrieves user from localStorage.
- */
- .factory('User', ['$rootScope', '$http', '$location', '$window', 'STORAGE_USER_ID', 'STORAGE_SETTINGS_ID', 'MOBILE_APP', 'Notification', 'ApiUrl',
- function($rootScope, $http, $location, $window, STORAGE_USER_ID, STORAGE_SETTINGS_ID, MOBILE_APP, Notification, ApiUrl) {
- var authenticated = false;
- var defaultSettings = {
- auth: { apiId: '', apiToken: ''},
- sync: {
- queue: [], //here OT will be queued up, this is NOT call-back queue!
- sent: [] //here will be OT which have been sent, but we have not got reply from server yet.
- },
- fetching: false, // whether fetch() was called or no. this is to avoid race conditions
- online: false
- };
- var settings = {}; //habit mobile settings (like auth etc.) to be stored here
- var user = {}; // this is stored as a reference accessible to all controllers, that way updates propagate
-
- var userNotifications = {
- // "party.order" : env.t("updatedParty"),
- // "party.orderAscending" : env.t("updatedParty")
- // party.order notifications are not currently needed because the party avatars are resorted immediately now
- }; // this is a list of notifications to send to the user when changes are made, along with the message.
-
- //first we populate user with schema
- user.apiToken = user._id = ''; // we use id / apitoken to determine if registered
-
- //than we try to load localStorage
- if (localStorage.getItem(STORAGE_USER_ID)) {
- _.extend(user, JSON.parse(localStorage.getItem(STORAGE_USER_ID)));
- }
- user._wrapped = false;
-
- var syncQueue = function (cb) {
- if (!authenticated) {
- $window.alert("Not authenticated, can't sync, go to settings first.");
- return;
- }
-
- var queue = settings.sync.queue;
- var sent = settings.sync.sent;
- if (queue.length === 0) {
- // Sync: Queue is empty
- return;
- }
- if (settings.fetching) {
- // Sync: Already fetching
- return;
- }
- if (settings.online!==true) {
- // Sync: Not online
- return;
- }
-
- settings.fetching = true;
- // move all actions from queue array to sent array
- _.times(queue.length, function () {
- sent.push(queue.shift());
- });
-
- // Save the current filters
- var current_filters = user.filters;
-
- $http.post(ApiUrl.get() + '/api/v2/user/batch-update', sent, {params: {data:+new Date, _v:user._v, siteVersion: $window.env && $window.env.siteVersion}})
- .success(function (data, status, heacreatingders, config) {
- //make sure there are no pending actions to sync. If there are any it is not safe to apply model from server as we may overwrite user data.
- if (!queue.length) {
- //we can't do user=data as it will not update user references in all other angular controllers.
-
- // the user has been modified from another application, sync up
- if(data && data.wasModified) {
- delete data.wasModified;
- $rootScope.$emit('userUpdated', user);
- }
-
- // Update user
- _.extend(user, data);
- // Preserve filter selections between syncs
- _.extend(user.filters,current_filters);
- if (!user._wrapped){
-
- // This wraps user with `ops`, which are functions shared both on client and mobile. When performed on client,
- // they update the user in the browser and then send the request to the server, where the same operation is
- // replicated. We need to wrap each op to provide a callback to send that operation
- $window.habitrpgShared.wrap(user);
- _.each(user.ops, function(op,k){
- user.ops[k] = function(req,cb){
- if (cb) return op(req,cb);
- op(req,function(err,response) {
- for(var updatedItem in req.body) {
- var itemUpdateResponse = userNotifications[updatedItem];
- if(itemUpdateResponse) Notification.text(itemUpdateResponse);
- }
- if (err) {
- var message = err.code ? err.message : err;
- if (MOBILE_APP) Notification.push({type:'text',text:message});
- else Notification.text(message);
- // In the case of 200s, they're friendly alert messages like "Your pet has hatched!" - still send the op
- if ((err.code && err.code >= 400) || !err.code) return;
- }
- userServices.log({op:k, params: req.params, query:req.query, body:req.body});
- });
- }
- });
- }
-
- // Emit event when user is synced
- $rootScope.$emit('userSynced');
- }
- sent.length = 0;
- settings.fetching = false;
- save();
- if (cb) {
- cb(false)
- }
-
- syncQueue(); // call syncQueue to check if anyone pushed more actions to the queue while we were talking to server.
- })
- .error(function (data, status, headers, config) {
- // (Notifications handled in app.js)
-
- // If we're offline, queue up offline actions so we can send when we're back online
- if (status === 0) {
- //move sent actions back to queue
- _.times(sent.length, function () {
- queue.push(sent.shift())
- });
- settings.fetching = false;
- // In the case of errors, discard the corrupt queue
- } else {
- // Clear the queue. Better if we can hunt down the problem op, but this is the easiest solution
- settings.sync.queue = settings.sync.sent = [];
- save();
- }
- });
- }
-
-
- var save = function () {
- localStorage.setItem(STORAGE_USER_ID, JSON.stringify(user));
- localStorage.setItem(STORAGE_SETTINGS_ID, JSON.stringify(settings));
- };
- var userServices = {
- user: user,
- set: function(updates) {
- user.ops.update({body:updates});
- },
-
- online: function (status) {
- if (status===true) {
- settings.online = true;
- syncQueue();
- } else {
- settings.online = false;
- };
- },
-
- authenticate: function (uuid, token, cb) {
- if (!!uuid && !!token) {
- var offset = moment().zone(); // eg, 240 - this will be converted on server as -(offset/60)
- $http.defaults.headers.common['x-api-user'] = uuid;
- $http.defaults.headers.common['x-api-key'] = token;
- $http.defaults.headers.common['x-user-timezoneOffset'] = offset;
- authenticated = true;
- settings.auth.apiId = uuid;
- settings.auth.apiToken = token;
- settings.online = true;
- if (user && user._v) user._v--; // shortcut to always fetch new updates on page reload
- userServices.log({}, function(){
- // If they don't have timezone, set it
- if (user.preferences.timezoneOffset !== offset)
- userServices.set({'preferences.timezoneOffset': offset});
- cb && cb();
- });
- } else {
- alert('Please enter your ID and Token in settings.')
- }
- },
-
- authenticated: function(){
- return this.settings.auth.apiId !== "";
- },
-
- getBalanceInGems: function() {
- var balance = user.balance || 0;
- return balance * 4;
- },
-
- log: function (action, cb) {
- //push by one buy one if an array passed in.
- if (_.isArray(action)) {
- action.forEach(function (a) {
- settings.sync.queue.push(a);
- });
- } else {
- settings.sync.queue.push(action);
- }
-
- save();
- syncQueue(cb);
- },
-
- sync: function(){
- user._v--;
- userServices.log({});
- },
-
- save: save,
-
- settings: settings
- };
-
-
- //load settings if we have them
- if (localStorage.getItem(STORAGE_SETTINGS_ID)) {
- //use extend here to make sure we keep object reference in other angular controllers
- _.extend(settings, JSON.parse(localStorage.getItem(STORAGE_SETTINGS_ID)));
-
- //if settings were saved while fetch was in process reset the flag.
- settings.fetching = false;
- //create and load if not
- } else {
- localStorage.setItem(STORAGE_SETTINGS_ID, JSON.stringify(defaultSettings));
- _.extend(settings, defaultSettings);
- }
-
- //If user does not have ApiID that forward him to settings.
- if (!settings.auth.apiId || !settings.auth.apiToken) {
-
- if (MOBILE_APP) {
- $location.path("/login");
- } else {
- //var search = $location.search(); // FIXME this should be working, but it's returning an empty object when at a root url /?_id=...
- var search = $location.search($window.location.search.substring(1)).$$search; // so we use this fugly hack instead
- if (search.err) return alert(search.err);
- if (search._id && search.apiToken) {
- userServices.authenticate(search._id, search.apiToken, function(){
- $window.location.href='/';
- });
- } else {
- var isStaticOrSocial = $window.location.pathname.match(/^\/(static|social)/);
- if (!isStaticOrSocial){
- localStorage.clear();
- $window.location.href = '/logout';
- }
- }
- }
-
- } else {
- userServices.authenticate(settings.auth.apiId, settings.auth.apiToken)
- }
-
- return userServices;
- }
-]);
diff --git a/config.json.example b/config.json.example
index 6aeb8ac74a..c7f801326c 100644
--- a/config.json.example
+++ b/config.json.example
@@ -1,5 +1,6 @@
{
"PORT":3000,
+ "ENABLE_CONSOLE_LOGS_IN_PROD":"false",
"IP":"0.0.0.0",
"CORES":1,
"BASE_URL":"http://localhost:3000",
@@ -8,6 +9,10 @@
"NODE_DB_URI":"mongodb://localhost/habitrpg",
"TEST_DB_URI":"mongodb://localhost/habitrpg_test",
"NODE_ENV":"development",
+ "ENABLE_CONSOLE_LOGS_IN_TEST": false,
+ "CRON_SAFE_MODE":"false",
+ "CRON_SEMI_SAFE_MODE":"false",
+ "MAINTENANCE_MODE": "false",
"SESSION_SECRET":"YOUR SECRET HERE",
"ADMIN_EMAIL": "you@example.com",
"SMTP_USER":"user@example.com",
@@ -19,6 +24,7 @@
"STRIPE_API_KEY":"aaaabbbbccccddddeeeeffff00001111",
"STRIPE_PUB_KEY":"22223333444455556666777788889999",
"NEW_RELIC_LICENSE_KEY":"NEW_RELIC_LICENSE_KEY",
+ "NEW_RELIC_NO_CONFIG_FILE":"true",
"NEW_RELIC_APPLICATION_ID":"NEW_RELIC_APPLICATION_ID",
"NEW_RELIC_API_KEY":"NEW_RELIC_API_KEY",
"GA_ID": "GA_ID",
@@ -33,7 +39,7 @@
"EMAIL_SERVER": {
"url": "http://example.com",
"authUser": "user",
- "authPassword": "password"
+ "authPassword": "password"
},
"S3":{
"bucket":"bucket",
@@ -55,13 +61,8 @@
"client_secret":"client_secret"
},
"IAP_GOOGLE_KEYDIR": "/path/to/google/public/key/dir/",
- "LOGGLY": {
- "enabled": false,
- "subdomain": "subdomain",
- "token": "token",
- "username": "username",
- "password": "password"
- },
+ "LOGGLY_TOKEN": "token",
+ "LOGGLY_ACCOUNT": "account",
"PUSH_CONFIGS": {
"GCM_SERVER_API_KEY": "",
"APN_PEM_FILES": {
diff --git a/gulpfile.js b/gulpfile.js
index 0c399ce7fa..d7cd0d79c2 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -9,6 +9,7 @@
require('babel-register');
if (process.env.NODE_ENV === 'production') {
+ require('./tasks/gulp-apidoc');
require('./tasks/gulp-newstuff');
require('./tasks/gulp-build');
require('./tasks/gulp-babelify');
diff --git a/karma.conf.js b/karma.conf.js
index b888c19ac1..d0ceffd5c6 100644
--- a/karma.conf.js
+++ b/karma.conf.js
@@ -1,7 +1,7 @@
// Karma configuration
// http://karma-runner.github.io/0.10/config/configuration-file.html
-module.exports = function(config) {
+module.exports = function karmaConfig (config) {
config.set({
// base path, that will be used to resolve files and exclude
basePath: '',
@@ -11,45 +11,43 @@ module.exports = function(config) {
// list of files / patterns to load in the browser
files: [
- 'website/public/bower_components/jquery/dist/jquery.js',
- 'website/public/bower_components/pnotify/jquery.pnotify.js',
- 'website/public/bower_components/angular/angular.js',
- 'website/public/bower_components/angular-loading-bar/build/loading-bar.min.js',
- 'website/public/bower_components/angular-resource/angular-resource.min.js',
- 'website/public/bower_components/hello/dist/hello.all.min.js',
- 'website/public/bower_components/angular-sanitize/angular-sanitize.js',
- 'website/public/bower_components/bootstrap/dist/js/bootstrap.js',
- 'website/public/bower_components/angular-bootstrap/ui-bootstrap.js',
- 'website/public/bower_components/angular-bootstrap/ui-bootstrap-tpls.js',
- 'website/public/bower_components/angular-ui-router/release/angular-ui-router.js',
- 'website/public/bower_components/angular-filter/dist/angular-filter.js',
- 'website/public/bower_components/angular-ui/build/angular-ui.js',
- 'website/public/bower_components/angular-ui-utils/ui-utils.min.js',
- 'website/public/bower_components/Angular-At-Directive/src/at.js',
- 'website/public/bower_components/Angular-At-Directive/src/caret.js',
- 'website/public/bower_components/angular-mocks/angular-mocks.js',
- 'website/public/bower_components/ngInfiniteScroll/build/ng-infinite-scroll.js',
- "website/public/bower_components/select2/select2.js",
- "website/public/bower_components/angular-ui-select2/src/select2.js",
- 'website/public/bower_components/marked/lib/marked.js',
- 'website/public/bower_components/js-emoji/emoji.js',
+ 'website/client/bower_components/jquery/dist/jquery.js',
+ 'website/client/bower_components/pnotify/jquery.pnotify.js',
+ 'website/client/bower_components/angular/angular.js',
+ 'website/client/bower_components/angular-loading-bar/build/loading-bar.min.js',
+ 'website/client/bower_components/angular-resource/angular-resource.min.js',
+ 'website/client/bower_components/hello/dist/hello.all.min.js',
+ 'website/client/bower_components/angular-sanitize/angular-sanitize.js',
+ 'website/client/bower_components/bootstrap/dist/js/bootstrap.js',
+ 'website/client/bower_components/angular-bootstrap/ui-bootstrap.js',
+ 'website/client/bower_components/angular-bootstrap/ui-bootstrap-tpls.js',
+ 'website/client/bower_components/angular-ui-router/release/angular-ui-router.js',
+ 'website/client/bower_components/angular-filter/dist/angular-filter.js',
+ 'website/client/bower_components/angular-ui/build/angular-ui.js',
+ 'website/client/bower_components/angular-ui-utils/ui-utils.min.js',
+ 'website/client/bower_components/Angular-At-Directive/src/at.js',
+ 'website/client/bower_components/Angular-At-Directive/src/caret.js',
+ 'website/client/bower_components/angular-mocks/angular-mocks.js',
+ 'website/client/bower_components/ngInfiniteScroll/build/ng-infinite-scroll.js',
+ 'website/client/bower_components/select2/select2.js',
+ 'website/client/bower_components/angular-ui-select2/src/select2.js',
+ 'website/client/bower_components/habitica-markdown/dist/habitica-markdown.min.js',
'common/dist/scripts/habitrpg-shared.js',
'test/spec/mocks/**/*.js',
- "website/public/js/env.js",
- "website/public/js/app.js",
- "common/script/public/config.js",
- "common/script/public/userServices.js",
- "common/script/public/directives.js",
+ 'website/client/js/env.js',
+ 'website/client/js/app.js',
+ 'common/script/public/config.js',
+ 'common/script/public/directives.js',
- "website/public/js/services/**/*.js",
- "website/public/js/filters/**/*.js",
- "website/public/js/directives/**/*.js",
- "website/public/js/controllers/**/*.js",
+ 'website/client/js/services/**/*.js',
+ 'website/client/js/filters/**/*.js',
+ 'website/client/js/directives/**/*.js',
+ 'website/client/js/controllers/**/*.js',
'test/spec/specHelper.js',
- 'test/spec/**/*.js'
+ 'test/spec/**/*.js',
],
// list of files / patterns to exclude
@@ -78,20 +76,20 @@ module.exports = function(config) {
browsers: ['PhantomJS'],
preprocessors: {
- 'website/public/js/**/*.js': ['coverage'],
+ 'website/client/js/**/*.js': ['coverage'],
'test/**/*.js': ['babel'],
},
coverageReporter: {
type: 'lcov',
- dir: 'coverage/karma'
+ dir: 'coverage/karma',
},
// Enable mocha-style reporting, for better test visibility
- reporters: ['mocha', 'coverage'],
+ reporters: ['mocha', 'coverage'],
// Continuous Integration mode
// if true, it capture browsers, run tests and exit
- singleRun: false
+ singleRun: false,
});
};
diff --git a/migrations/20160521_veteran_ladder.js b/migrations/20160521_veteran_ladder.js
new file mode 100644
index 0000000000..cf92ff5375
--- /dev/null
+++ b/migrations/20160521_veteran_ladder.js
@@ -0,0 +1,76 @@
+var migrationName = '20160521_veteran_ladder.js';
+var authorName = 'Sabe'; // in case script author needs to know when their ...
+var authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; //... own data is done
+
+/*
+ * Award Gilded Turkey pet to Turkey mount owners, Turkey Mount if they only have Turkey Pet,
+ * and Turkey Pet otherwise
+ */
+
+var dbserver = 'localhost:27017'; // FOR TEST DATABASE
+// var dbserver = 'username:password@ds031379-a0.mongolab.com:31379'; // FOR PRODUCTION DATABASE
+var dbname = 'habitrpg';
+
+var mongo = require('mongoskin');
+var _ = require('lodash');
+
+var dbUsers = mongo.db(dbserver + '/' + dbname + '?auto_reconnect').collection('users');
+
+// specify a query to limit the affected users (empty for all users):
+var query = {
+ 'auth.timestamps.loggedin':{$gt:new Date('2016-05-01')} // remove when running migration a second time
+};
+
+// specify fields we are interested in to limit retrieved data (empty if we're not reading data):
+var fields = {
+ 'migration': 1,
+ 'items.pets.Wolf-Veteran': 1,
+ 'items.pets.Tiger-Veteran': 1
+};
+
+console.warn('Updating users...');
+var progressCount = 1000;
+var count = 0;
+dbUsers.findEach(query, fields, {batchSize:250}, function(err, user) {
+ if (err) { return exiting(1, 'ERROR! ' + err); }
+ if (!user) {
+ console.warn('All appropriate users found and modified.');
+ return displayData();
+ }
+ count++;
+
+ // specify user data to change:
+ var set = {};
+ if (user.migration !== migrationName) {
+ if (user.items.pets['Tiger-Veteran']) {
+ set = {'migration':migrationName, 'items.pets.Lion-Veteran':5};
+ } else if (user.items.pets['Wolf-Veteran']) {
+ set = {'migration':migrationName, 'items.pets.Tiger-Veteran':5};
+ } else {
+ set = {'migration':migrationName, 'items.pets.Wolf-Veteran':5};
+ }
+ }
+
+ dbUsers.update({_id:user._id}, {$set:set});
+
+ if (count%progressCount == 0) console.warn(count + ' ' + user._id);
+ if (user._id == authorUuid) console.warn(authorName + ' processed');
+});
+
+
+function displayData() {
+ console.warn('\n' + count + ' users processed\n');
+ return exiting(0);
+}
+
+
+function exiting(code, msg) {
+ code = code || 0; // 0 = success
+ if (code && !msg) { msg = 'ERROR!'; }
+ if (msg) {
+ if (code) { console.error(msg); }
+ else { console.log( msg); }
+ }
+ process.exit(code);
+}
+
diff --git a/migrations/20160527_fix_empty_checklist_id.js b/migrations/20160527_fix_empty_checklist_id.js
new file mode 100644
index 0000000000..0aaa36d1ac
--- /dev/null
+++ b/migrations/20160527_fix_empty_checklist_id.js
@@ -0,0 +1,78 @@
+var uuid = require('uuid').v4;
+var mongo = require('mongodb').MongoClient;
+var _ = require('lodash');
+
+var taskIds = require('checklists-no-id.json').map(function (obj) {
+ return obj._id;
+});
+
+// Fix empty task.checklistt.id
+
+var progressCount = 100;
+var count = 0;
+
+function displayData() {
+ console.warn('\n' + count + ' tasks processed\n');
+ return exiting(0);
+}
+
+function exiting(code, msg) {
+ code = code || 0; // 0 = success
+
+ if (code && !msg) { msg = 'ERROR!'; }
+ if (msg) {
+ if (code) { console.error(msg); }
+ else { console.log( msg); }
+ }
+}
+
+mongo.connect('db url')
+.then(function (db) {
+ var dbTasks = db.collection('tasks');
+
+ // specify a query to limit the affected tasks (empty for all tasks):
+ var query = {
+ '_id':{ $in: taskIds },
+ };
+
+ // specify fields we are interested in to limit retrieved data (empty if we're not reading data):
+ var fields = {
+ 'checklist': 1,
+ };
+
+ console.warn('Updating tasks...');
+
+ dbTasks.find(query, fields, {batchSize: 250}).toArray(function(err, tasks) {
+ if (err) { return exiting(1, 'ERROR! ' + err); }
+
+ tasks.forEach(function (task) {
+ var checklist = task.checklist || [];
+ checklist.forEach(function (item) {
+ if (!item.id || item.id === "") {
+ item.id = uuid();
+ }
+ });
+
+ // specify user data to change:
+ var set = {
+ checklist: checklist,
+ };
+ //console.log(set);
+
+ dbTasks.update({_id: task._id}, {$set: set}, function (err, res) {
+ if (err) console.error('Error while updating', err);
+ });
+
+ count++;
+ if (count % progressCount == 0) console.warn(count + ' ' + task._id);
+ });
+
+ if (count === tasks.length) {
+ console.warn('All appropriate tasks found and modified.');
+ return displayData();
+ }
+ });
+})
+.catch(function (err) {
+ throw err;
+});
\ No newline at end of file
diff --git a/migrations/20160529_fix_challenges.js b/migrations/20160529_fix_challenges.js
new file mode 100644
index 0000000000..51c31ee0c9
--- /dev/null
+++ b/migrations/20160529_fix_challenges.js
@@ -0,0 +1,264 @@
+'use strict';
+
+/****************************************
+ * Reason: After the api v3 maintenance migration, some challenge tasks
+ * became unlinked from their challenges. We're still not sure why,
+ * but this re-links them
+ *
+ * Note: We ran this on a local backup of the DB, and from that, grabbed
+ * the ids of the tasks that could be fixed and the updates that would
+ * be applied to them. We only ran the `updateTasks` promise task.
+ *
+ * IMPORTANT - Setting challenge.broken to null caused issues
+ * see https://github.com/HabitRPG/habitrpg/issues/7546
+ ***************************************/
+
+const authorName = 'Blade';
+const authorUuid = '75f270e8-c5db-4722-a5e6-a83f1b23f76b';
+
+global.Promise = require('bluebird');
+const MongoClient = require('mongodb').MongoClient;
+const TaskQueue = require('cwait').TaskQueue;
+const logger = require('./utils/logger');
+
+// PROD: Enable prod db
+// const NODE_DB_URI = 'mongodb://username:password@dsXXXXXX-a0.mlab.com:XXXXX,dsXXXXXX-a1.mlab.com:XXXXX/habitica?replicaSet=rs-dsXXXXXX';
+const NODE_DB_URI = 'mongodb://localhost/new-prod-copy';
+
+// Cached ids from running the findBrokenChallengeTasks query on a local copy of the db
+// These are all the ids that _are_ fixable
+const TASK_IDS = require('../fixable_task_ids.json');
+const TASK_UPDATE_DATA = require('../challenge_fixes.json');
+
+let db;
+let count = 0;
+
+var timer = setInterval(function(){
+ count++;
+ if (count % 30 === 0) {
+ logger.warn('Process has been running for', count / 60, 'minutes');
+ }
+}, 1000);
+
+connectToDb()
+ // .then(findBrokenChallengeTasks)
+ // .then(getDataFromTasks)
+ // .then(getUserChallenges)
+ // .then(getChallengeTasks)
+ // .then(correctUserTasks)
+ .then(updateTasks)
+ .then(closeDb)
+ .catch(reportError)
+
+function connectToDb () {
+ return new Promise((resolve, reject) => {
+ MongoClient.connect(NODE_DB_URI, (err, database) => {
+ if (err) {
+ logger.error('Uh oh... Problem connecting to the new database');
+ return reject(err);
+ }
+
+ logger.success('Connected to the database');
+
+ db = database;
+
+ resolve(db);
+ });
+ });
+}
+
+function reportError (err) {
+ logger.error('Uh oh, an error occurred');
+ closeDb();
+ throw err;
+}
+
+function unique (array) {
+ return Array.from(new Set(array));
+}
+
+function findBrokenChallengeTasks () {
+ logger.info('Looking for broken tasks...');
+
+ // return db.collection('tasks').find({'challenge.broken': 'CHALLENGE_TASK_NOT_FOUND'}).toArray()
+ return db.collection('tasks').find({'_id': { '$in': TASK_IDS }}).toArray()
+ .then((tasks) => {
+ logger.success('Found', tasks.length, 'broken tasks.');
+ return Promise.resolve(tasks);
+ });
+}
+
+function getDataFromTasks (tasks) {
+ logger.info('Collecting data about the tasks...');
+
+ let userTasks = {};
+
+ tasks.forEach((task) => {
+ let userId = task.userId;
+
+ if (!userTasks[userId]) {
+ userTasks[userId] = [];
+ }
+ userTasks[userId].push(task);
+ });
+
+ let users = unique(tasks.map(task => task.userId));
+
+ return Promise.resolve({
+ users,
+ userTasks,
+ tasks,
+ });
+}
+
+function getUserChallenges (data) {
+ logger.info('Collecting user challenges...');
+
+ return db.collection('users').find({_id: { '$in': data.users }}, {challenges: 1}).toArray().then((docs) => {
+ logger.success('Found', docs.length, 'users from broken challenge tasks.');
+
+ let challenges = [];
+ docs.forEach((user) => {
+ challenges.push.apply(challenges, user.challenges);
+ });
+
+ challenges = unique(challenges);
+
+ let userChallenges = {};
+
+ docs.forEach((user) => {
+ let userId = user._id;
+ if (!userChallenges[userId]) {
+ userChallenges[userId] = [];
+ }
+ userChallenges[userId].push.apply(userChallenges[userId], user.challenges);
+ });
+
+ data.userChallenges = userChallenges;
+ data.challenges = challenges;
+
+ logger.success('Found', challenges.length, 'unique challenges.');
+
+ return Promise.resolve(data);
+ });
+}
+
+function getChallengeTasks (data) {
+ logger.info('Looking up original challenge tasks...');
+
+ return db.collection('tasks').find({'userId': null, 'challenge.id': { '$in': data.challenges }}, [ 'text', 'type', 'challenge', '_legacyId' ]).toArray().then((docs) => {
+ logger.success('Found', docs.length, 'challenge tasks.');
+
+ let challengeTasks = {};
+
+ docs.forEach((task) => {
+ let chalId = task.challenge.id;
+ if (!challengeTasks[chalId]) {
+ challengeTasks[chalId] = [];
+ }
+ challengeTasks[chalId].push(task);
+ });
+ data.challengeTasks = challengeTasks;
+
+ return Promise.resolve(data);
+ });
+}
+
+function correctUserTasks (data) {
+ logger.info('Correcting user tasks...');
+
+ let tasksToUpdate = {};
+ let duplicateTasks = {};
+
+ for (let user in data.userChallenges) {
+ if (user === authorUuid) {
+ logger.success('Processing data for', authorName);
+ }
+ if (data.userChallenges.hasOwnProperty(user)) {
+ let challenges = data.userChallenges[user];
+
+ challenges.forEach((chal) => {
+ let challengeTasks = data.challengeTasks[chal];
+ let userTasks = data.userTasks[user];
+
+ if (challengeTasks) {
+ challengeTasks.forEach((challengeTask) => {
+ let text = challengeTask.text;
+ let type = challengeTask.type;
+ let taskId = challengeTask._id;
+ let legacyId = challengeTask._legacyId;
+
+ let foundTask = userTasks.find((task) => {
+ return TASK_IDS.indexOf(task._id) > -1 && task._legacyId === legacyId && task.type === type && task.text === text;
+ })
+
+ if (foundTask && !tasksToUpdate[foundTask._id]) {
+ tasksToUpdate[foundTask._id] = {
+ id: chal,
+ broken: null, // NOTE: this caused a lot of problems
+ taskId,
+ }
+ } else if (foundTask && taskId !== tasksToUpdate[foundTask._id].taskId) {
+ logger.error('Duplicate task found, id:', foundTask._id);
+ duplicateTasks[foundTask._id] = duplicateTasks[foundTask._id] || [tasksToUpdate[foundTask._id].taskId];
+ duplicateTasks[foundTask._id].push(taskId);
+ }
+ });
+ }
+ });
+ }
+ }
+
+ let numberOfDuplicateTasksFound = Object.keys(duplicateTasks).length;
+
+ if (numberOfDuplicateTasksFound > 0) {
+ logger.error('Found', numberOfDuplicateTasksFound, 'duplicate taks');
+ }
+
+
+ data.tasksToUpdate = tasksToUpdate;
+
+ return Promise.resolve(data);
+}
+
+function updateTasks (data) {
+ let tasksToUpdate = TASK_UPDATE_DATA;
+ let taskIdsToUpdate = Object.keys(tasksToUpdate);
+ let queue = new TaskQueue(Promise, 300);
+ let promiseCount = 0;
+
+ logger.info('About to update', taskIdsToUpdate.length, 'user tasks');
+
+ function updateTaskById (taskId) {
+ promiseCount++;
+
+ if (promiseCount % 500 === 0) {
+ logger.info(promiseCount, 'updates started');
+ }
+
+ return db.collection('tasks').findOneAndUpdate({_id: taskId, 'challenge.broken': 'CHALLENGE_TASK_NOT_FOUND'}, {$set: {challenge: tasksToUpdate[taskId]}}, {returnOriginal: false})
+ }
+
+ return Promise.map(taskIdsToUpdate, queue.wrap(updateTaskById)).then((result) => {
+ let updates = result.filter(res => res.lastErrorObject.updatedExisting)
+ let failures = result.filter(res => !res.lastErrorObject.updatedExisting);
+
+ logger.success(updates.length, 'tasks have been fixed');
+
+ if (failures.length > 0) {
+ logger.error(failures.length, 'tasks could not be updated');
+ logger.error('Manually check these results');
+ logger.error(failures);
+ }
+
+ return Promise.resolve(data);
+ });
+}
+
+function closeDb (data) {
+ logger.success('The process took ' + count + ' seconds');
+
+ clearInterval(timer)
+
+ db.close();
+}
diff --git a/migrations/20160530_fix_tasks_from_null_value_in_challenges_broken.js b/migrations/20160530_fix_tasks_from_null_value_in_challenges_broken.js
new file mode 100644
index 0000000000..f5ae6c9925
--- /dev/null
+++ b/migrations/20160530_fix_tasks_from_null_value_in_challenges_broken.js
@@ -0,0 +1,229 @@
+'use strict';
+
+/****************************************
+ * Reason: After running the 20160529_fix_challenges.js migration
+ * challenge.broken was set to null, which is not a valid value
+ * which caused cron to fail and run many times, messing up daily values,
+ * history and streaks
+ *
+ * Note: Part of this code does calculation to look up users that
+ * were affected. After the user ids were found, @crookedneighbor
+ * pm'ed each user asking if they would like their tasks reset to the previous day
+ ***************************************/
+
+global.Promise = require('bluebird');
+const MongoClient = require('mongodb').MongoClient;
+const TaskQueue = require('cwait').TaskQueue;
+const logger = require('./utils/logger');
+
+const TASK_IDS = require('../task_ids.json');
+// cached call to getAffectedUsers minus the users I've helped manually
+const POSSIBLY_AFFECTED_USERS = require('../users.json');
+const AFFECTED_USERS = require('../users_with_bad_history.json');
+
+// PROD: Enable prod db
+const OLD_DB_URI = 'mongodb://localhost/new-prod-copy';
+const NEW_DB_URI = 'mongodb://username:password@dsXXXXXX-a0.mlab.com:XXXXX,dsXXXXXX-a1.mlab.com:XXXXX/habitica?replicaSet=rs-dsXXXXXX';
+
+let oldDb, newDb, OldTasks, NewTasks, OldUsers, NewUsers;
+let count = 0;
+
+var timer = setInterval(function(){
+ count++;
+ if (count % 30 === 0) {
+ logger.warn('Process has been running for', count / 60, 'minutes');
+ }
+}, 1000);
+
+Promise.all([
+ connectToDb(OLD_DB_URI, 'backup'),
+ connectToDb(NEW_DB_URI, 'prod'),
+])
+ .then(assignDbVariables)
+ // .then(getAffectedUsersEmail)
+ // .then(findAffectedUsers)
+ // .then(determineIfTasksNeedAdjusting)
+ .then(getValuesOfOldTasksFromBackup)
+ .then(updateNewTasks)
+ .then(closeDb)
+ .catch(reportError)
+
+function connectToDb (dbUri, type) {
+ return new Promise((resolve, reject) => {
+ MongoClient.connect(dbUri, (err, database) => {
+ if (err) {
+ logger.error(`Uh oh... Problem connecting to the ${type} database`);
+ return reject(err);
+ }
+
+ logger.success(`Connected to the ${type} database`);
+
+ resolve(database);
+ });
+ });
+}
+
+function reportError (err) {
+ logger.error('Uh oh, an error occurred');
+ // closeDb();
+ throw err;
+}
+
+function assignDbVariables (results) {
+ oldDb = results[0];
+ OldTasks = oldDb.collection('tasks_backup');
+ OldUsers = oldDb.collection('users');
+
+ newDb = results[1];
+ NewTasks = newDb.collection('tasks');
+ NewUsers = newDb.collection('users');
+
+ return Promise.resolve();
+}
+
+function getAffectedUsersEmail () {
+ logger.info('Looking up emails and pm-ability for affected users');
+
+ let pmsWithEmail = [];
+ let emails = [];
+ let missing = [];
+
+ return NewUsers.find({_id: {$in: AFFECTED_USERS}}, ['preferences', 'inbox', 'auth.facebook.email', 'auth.local.email']).toArray().then((users) => {
+ users.forEach((user) => {
+ if (user.preferences.emailNotifications.newPM && user.inbox.optOut !== true) {
+ pmsWithEmail.push(user._id);
+ } else {
+ if (user.auth && user.auth.local && user.auth.local.email) {
+ emails.push(user.auth.local.email);
+ } else if (user.auth && user.auth.facebook && user.auth.facebook.email) {
+ emails.push(user.auth.facebook.email);
+ } else {
+ missing.push(user._id);
+ }
+ }
+ });
+
+ logger.log('PMable users with email notification');
+ logger.log(pmsWithEmail);
+
+ logger.log('Emailable users');
+ logger.log(emails);
+
+ logger.log('Unreachable users');
+ logger.log(missing);
+
+ return Promise.resolve();
+ });
+}
+
+function findAffectedUsers () {
+ logger.info('finding affected users');
+ return NewTasks.find({_id: {$in: TASK_IDS}}).toArray().then((tasks) => {
+ let users = unique(tasks.map(task => task.userId));
+
+ logger.success('Found', users.length, 'users that may have been affected');
+
+ return Promise.resolve(users);
+ });
+}
+
+function determineIfTasksNeedAdjusting (tasks) {
+ return Promise.all([
+ OldTasks.find({userId: {$in: POSSIBLY_AFFECTED_USERS}, type: 'daily'}, ['value', 'history', 'userId']).toArray(),
+ NewTasks.find({userId: {$in: POSSIBLY_AFFECTED_USERS}, type: 'daily'}, ['value', 'history', 'userId']).toArray(),
+ ]).then((results) => {
+ let backupTasks = results[0];
+ let prodTasks = results[1];
+
+ logger.success(backupTasks.length, 'dailys found in backup db');
+ logger.success(prodTasks.length, 'dailys found in prod db');
+
+ let backupTasksAsObject = backupTasks.reduce((object, task) => {
+ object[task._id] = task;
+ return object;
+ }, {});
+ let prodTasksGroupedByUser = prodTasks.reduce((object, task) => {
+ if (!object[task.userId]) object[task.userId] = [];
+ object[task.userId].push(task);
+ return object;
+ }, {});
+
+ let usersWithBadHistory = POSSIBLY_AFFECTED_USERS.filter((user) => {
+ let tasks = prodTasksGroupedByUser[user];
+ if (!tasks) return false;
+
+ for (let i = 0; i < tasks.length; i++) {
+ let prodTask = tasks[i];
+ let backupTask = backupTasksAsObject[prodTask._id];
+
+ if (!backupTask) { continue; }
+
+ let historyDifference = prodTask.history.length - backupTask.history.length;
+
+ if (historyDifference > 4) {
+ return true;
+ }
+ }
+
+ return false;
+ });
+
+ logger.info('Total possibly affected users:', POSSIBLY_AFFECTED_USERS.length);
+ logger.info('Users with wonky task data:', usersWithBadHistory.length);
+
+ return Promise.resolve(usersWithBadHistory);
+ });
+}
+
+let usersToRun = [
+ // Since this is happening a little at a time as users get back to us, just fill this in with user ids
+];
+
+function getValuesOfOldTasksFromBackup () {
+ logger.info('looking for tasks');
+
+ return OldTasks.find({userId: {$in: usersToRun}}, ['value', 'streak', 'history']).toArray();
+}
+
+function updateNewTasks (oldTasks) {
+ let queue = new TaskQueue(Promise, 300);
+ let promiseCount = 0;
+
+ logger.info('About to update', oldTasks.length, 'user tasks');
+
+ function updateTaskById (task) {
+ promiseCount++;
+
+ if (promiseCount % 100=== 0) {
+ logger.info(promiseCount, 'updates started');
+ }
+
+ return NewTasks.findOneAndUpdate({_id: task._id}, {$set: {value: task.value, streak: task.streak, history: task.history}}, {returnOriginal: false})
+ }
+
+ return Promise.map(oldTasks, queue.wrap(updateTaskById)).then((result) => {
+ let updates = result.filter(res => res.lastErrorObject && res.lastErrorObject.updatedExisting)
+ let failures = result.filter(res => !(res.lastErrorObject && res.lastErrorObject.updatedExisting));
+
+ logger.success(updates.length, 'tasks have been fixed');
+
+ if (failures.length > 0) {
+ logger.error(failures.length, 'tasks could not be found');
+ }
+
+ return Promise.resolve();
+ });
+}
+
+function unique (array) {
+ return Array.from(new Set(array));
+}
+
+function closeDb () {
+ logger.success('The process took ' + count + ' seconds');
+
+ clearInterval(timer)
+
+ oldDb.close();
+ newDb.close();
+}
diff --git a/migrations/api_v3/challenges.js b/migrations/api_v3/challenges.js
new file mode 100644
index 0000000000..009c205e33
--- /dev/null
+++ b/migrations/api_v3/challenges.js
@@ -0,0 +1,212 @@
+// Migrate challenges collection to new schema (except for members)
+
+// The console-stamp module must be installed (not included in package.json)
+
+// It requires two environment variables: MONGODB_OLD and MONGODB_NEW
+
+// Due to some big user profiles it needs more RAM than is allowed by default by v8 (arounf 1.7GB).
+// Run the script with --max-old-space-size=4096 to allow up to 4GB of RAM
+console.log('Starting migrations/api_v3/challenges.js.');
+
+require('babel-register');
+require('babel-polyfill');
+
+var Bluebird = require('bluebird');
+var MongoDB = require('mongodb');
+var nconf = require('nconf');
+var mongoose = require('mongoose');
+var _ = require('lodash');
+var uuid = require('uuid');
+var consoleStamp = require('console-stamp');
+var fs = require('fs');
+
+// Add timestamps to console messages
+consoleStamp(console);
+
+// Initialize configuration
+require('../../website/server/libs/api-v3/setupNconf')();
+
+var MONGODB_OLD = nconf.get('MONGODB_OLD');
+var MONGODB_NEW = nconf.get('MONGODB_NEW');
+
+var MongoClient = MongoDB.MongoClient;
+
+mongoose.Promise = Bluebird; // otherwise mongoose models won't work
+
+// Load new models
+var NewChallenge = require('../../website/server/models/challenge').model;
+var Tasks = require('../../website/server/models/task');
+
+// To be defined later when MongoClient connects
+var mongoDbOldInstance;
+var oldChallengeCollection;
+
+var mongoDbNewInstance;
+var newChallengeCollection;
+var newTaskCollection;
+
+var BATCH_SIZE = 1000;
+
+var processedChallenges = 0;
+var totoalProcessedTasks = 0;
+
+var newTasksIds = {}; // a map of old id -> [new id, challengeId]
+
+// Only process challenges that fall in a interval ie -> up to 0000-4000-0000-0000
+var AFTER_CHALLENGE_ID = nconf.get('AFTER_CHALLENGE_ID');
+var BEFORE_CHALLENGE_ID = nconf.get('BEFORE_CHALLENGE_ID');
+
+function processChallenges (afterId) {
+ var processedTasks = 0;
+ var lastChallenge = null;
+ var oldChallenges;
+
+ var query = {};
+
+ if (BEFORE_CHALLENGE_ID) {
+ query._id = {$lte: BEFORE_CHALLENGE_ID};
+ }
+
+ if ((afterId || AFTER_CHALLENGE_ID) && !query._id) {
+ query._id = {};
+ }
+
+ if (afterId) {
+ query._id.$gt = afterId;
+ } else if (AFTER_CHALLENGE_ID) {
+ query._id.$gt = AFTER_CHALLENGE_ID;
+ }
+
+ var batchInsertTasks = newTaskCollection.initializeUnorderedBulkOp();
+ var batchInsertChallenges = newChallengeCollection.initializeUnorderedBulkOp();
+
+ console.log(`Executing challenges query.\nMatching challenges after ${afterId ? afterId : AFTER_CHALLENGE_ID} and before ${BEFORE_CHALLENGE_ID} (included).`);
+
+ return oldChallengeCollection
+ .find(query)
+ .sort({_id: 1})
+ .limit(BATCH_SIZE)
+ .toArray()
+ .then(function (oldChallengesR) {
+ oldChallenges = oldChallengesR;
+
+ console.log(`Processing ${oldChallenges.length} challenges. Already processed ${processedChallenges} challenges and ${totoalProcessedTasks} tasks.`);
+
+ if (oldChallenges.length === BATCH_SIZE) {
+ lastChallenge = oldChallenges[oldChallenges.length - 1]._id;
+ }
+
+ oldChallenges.forEach(function (oldChallenge) {
+ var oldTasks = oldChallenge.habits.concat(oldChallenge.dailys).concat(oldChallenge.rewards).concat(oldChallenge.todos);
+ delete oldChallenge.habits;
+ delete oldChallenge.dailys;
+ delete oldChallenge.rewards;
+ delete oldChallenge.todos;
+
+ var createdAt = oldChallenge.timestamp;
+
+ oldChallenge.memberCount = oldChallenge.members.length;
+ if (oldChallenge.prize <= 0) oldChallenge.prize = 0;
+ if (!oldChallenge.name) oldChallenge.name = 'challenge name';
+ if (!oldChallenge.shortName) oldChallenge.name = 'challenge-name';
+
+ if (!oldChallenge.group) throw new Error('challenge.group is required');
+ if (!oldChallenge.leader) throw new Error('challenge.leader is required');
+
+
+ if (oldChallenge.leader === '9') {
+ oldChallenge.leader = '00000000-0000-4000-9000-000000000000';
+ }
+
+ if (oldChallenge.group === 'habitrpg') {
+ oldChallenge.group = '00000000-0000-4000-A000-000000000000';
+ }
+
+ delete oldChallenge.id;
+
+ var newChallenge = new NewChallenge(oldChallenge);
+
+ newChallenge.createdAt = createdAt;
+
+ oldTasks.forEach(function (oldTask) {
+ oldTask._id = uuid.v4();
+ oldTask._legacyId = oldTask.id; // store the old task id
+ delete oldTask.id;
+
+ oldTask.challenge = oldTask.challenge || {};
+ oldTask.challenge.id = newChallenge._id;
+
+ if (newTasksIds[oldTask._legacyId + '-' + newChallenge._id]) {
+ throw new Error('duplicate :(');
+ } else {
+ newTasksIds[oldTask._legacyId + '-' + newChallenge._id] = oldTask._id;
+ }
+
+ oldTask.tags = _.map(oldTask.tags || {}, function (tagPresent, tagId) {
+ return tagPresent && tagId;
+ }).filter(function (tag) {
+ return tag !== false;
+ });
+
+ if (!oldTask.text) oldTask.text = 'task text'; // required
+
+ oldTask.createdAt = oldTask.dateCreated;
+
+ newChallenge.tasksOrder[`${oldTask.type}s`].push(oldTask._id);
+ if (oldTask.completed) oldTask.completed = false;
+
+ var newTask = new Tasks[oldTask.type](oldTask);
+
+ batchInsertTasks.insert(newTask.toObject());
+ processedTasks++;
+ });
+
+ batchInsertChallenges.insert(newChallenge.toObject());
+ });
+
+ console.log(`Saving ${oldChallenges.length} challenges and ${processedTasks} tasks.`);
+
+ return Bluebird.all([
+ batchInsertChallenges.execute(),
+ batchInsertTasks.execute(),
+ ]);
+ })
+ .then(function () {
+ totoalProcessedTasks += processedTasks;
+ processedChallenges += oldChallenges.length;
+
+ console.log(`Saved ${oldChallenges.length} challenges and their tasks.`);
+
+ if (lastChallenge) {
+ return processChallenges(lastChallenge);
+ } else {
+ console.log('Writing newTasksIds.json...')
+ fs.writeFileSync('newTasksIds.json', JSON.stringify(newTasksIds, null, 4), 'utf8');
+ return console.log('Done!');
+ }
+ });
+}
+
+// Connect to the databases
+Bluebird.all([
+ MongoClient.connect(MONGODB_OLD),
+ MongoClient.connect(MONGODB_NEW),
+])
+.then(function (result) {
+ var oldInstance = result[0];
+ var newInstance = result[1];
+
+ mongoDbOldInstance = oldInstance;
+ oldChallengeCollection = mongoDbOldInstance.collection('challenges');
+
+ mongoDbNewInstance = newInstance;
+ newChallengeCollection = mongoDbNewInstance.collection('challenges');
+ newTaskCollection = mongoDbNewInstance.collection('tasks');
+
+ console.log(`Connected with MongoClient to ${MONGODB_OLD} and ${MONGODB_NEW}.`);
+
+ return processChallenges();
+})
+.catch(function (err) {
+ console.error(err.stack || err);
+});
diff --git a/migrations/api_v3/challengesMembers.js b/migrations/api_v3/challengesMembers.js
new file mode 100644
index 0000000000..971119831a
--- /dev/null
+++ b/migrations/api_v3/challengesMembers.js
@@ -0,0 +1,143 @@
+// Migrate challenges members
+// Run AFTER users migration
+
+// The console-stamp module must be installed (not included in package.json)
+
+// It requires two environment variables: MONGODB_OLD and MONGODB_NEW
+
+// Due to some big user profiles it needs more RAM than is allowed by default by v8 (arounf 1.7GB).
+// Run the script with --max-old-space-size=4096 to allow up to 4GB of RAM
+console.log('Starting migrations/api_v3/challengesMembers.js.');
+
+require('babel-register');
+require('babel-polyfill');
+
+var Bluebird = require('bluebird');
+var MongoDB = require('mongodb');
+var nconf = require('nconf');
+var mongoose = require('mongoose');
+var _ = require('lodash');
+var uuid = require('uuid');
+var consoleStamp = require('console-stamp');
+
+// Add timestamps to console messages
+consoleStamp(console);
+
+// Initialize configuration
+require('../../website/server/libs/api-v3/setupNconf')();
+
+var MONGODB_OLD = nconf.get('MONGODB_OLD');
+var MONGODB_NEW = nconf.get('MONGODB_NEW');
+
+var MongoClient = MongoDB.MongoClient;
+
+mongoose.Promise = Bluebird; // otherwise mongoose models won't work
+
+// To be defined later when MongoClient connects
+var mongoDbOldInstance;
+var oldChallengeCollection;
+
+var mongoDbNewInstance;
+var newUserCollection;
+
+var BATCH_SIZE = 1000;
+
+var processedChallenges = 0;
+
+// Only process challenges that fall in a interval ie -> up to 0000-4000-0000-0000
+var AFTER_CHALLENGE_ID = nconf.get('AFTER_CHALLENGE_ID');
+var BEFORE_CHALLENGE_ID = nconf.get('BEFORE_CHALLENGE_ID');
+
+function processChallenges (afterId) {
+ var processedTasks = 0;
+ var lastChallenge = null;
+ var oldChallenges;
+
+ var query = {};
+
+ if (BEFORE_CHALLENGE_ID) {
+ query._id = {$lte: BEFORE_CHALLENGE_ID};
+ }
+
+ if ((afterId || AFTER_CHALLENGE_ID) && !query._id) {
+ query._id = {};
+ }
+
+ if (afterId) {
+ query._id.$gt = afterId;
+ } else if (AFTER_CHALLENGE_ID) {
+ query._id.$gt = AFTER_CHALLENGE_ID;
+ }
+
+ console.log(`Executing challenges query.\nMatching challenges after ${afterId ? afterId : AFTER_CHALLENGE_ID} and before ${BEFORE_CHALLENGE_ID} (included).`);
+
+ return oldChallengeCollection
+ .find(query)
+ .sort({_id: 1})
+ .limit(BATCH_SIZE)
+ .toArray()
+ .then(function (oldChallengesR) {
+ oldChallenges = oldChallengesR;
+
+ var promises = [];
+
+ console.log(`Processing ${oldChallenges.length} challenges. Already processed ${processedChallenges} challenges.`);
+
+ if (oldChallenges.length === BATCH_SIZE) {
+ lastChallenge = oldChallenges[oldChallenges.length - 1]._id;
+ }
+
+ oldChallenges.forEach(function (oldChallenge) {
+ // Tyler Renelle
+ oldChallenge.members.forEach(function (id, index) {
+ if (id === '9') {
+ oldChallenge.members[index] = '00000000-0000-4000-9000-000000000000';
+ }
+ });
+
+ promises.push(newUserCollection.updateMany({
+ _id: {$in: oldChallenge.members || []},
+ }, {
+ $push: {challenges: oldChallenge._id},
+ }, {multi: true}));
+ });
+
+ console.log(`Migrating members of ${oldChallenges.length} challenges.`);
+
+ return Bluebird.all(promises);
+ })
+ .then(function () {
+ processedChallenges += oldChallenges.length;
+
+ console.log(`Migrated members of ${oldChallenges.length} challenges.`);
+
+ if (lastChallenge) {
+ return processChallenges(lastChallenge);
+ } else {
+ return console.log('Done!');
+ }
+ });
+}
+
+// Connect to the databases
+Bluebird.all([
+ MongoClient.connect(MONGODB_OLD),
+ MongoClient.connect(MONGODB_NEW),
+])
+.then(function (result) {
+ var oldInstance = result[0];
+ var newInstance = result[1];
+
+ mongoDbOldInstance = oldInstance;
+ oldChallengeCollection = mongoDbOldInstance.collection('challenges');
+
+ mongoDbNewInstance = newInstance;
+ newUserCollection = mongoDbNewInstance.collection('users');
+
+ console.log(`Connected with MongoClient to ${MONGODB_OLD} and ${MONGODB_NEW}.`);
+
+ return processChallenges();
+})
+.catch(function (err) {
+ console.error(err.stack || err);
+});
diff --git a/migrations/api_v3/coupons.js b/migrations/api_v3/coupons.js
new file mode 100644
index 0000000000..64071faffe
--- /dev/null
+++ b/migrations/api_v3/coupons.js
@@ -0,0 +1,136 @@
+// Migrate coupons collection to new schema
+
+// The console-stamp module must be installed (not included in package.json)
+
+// It requires two environment variables: MONGODB_OLD and MONGODB_NEW
+
+// Due to some big user profiles it needs more RAM than is allowed by default by v8 (arounf 1.7GB).
+// Run the script with --max-old-space-size=4096 to allow up to 4GB of RAM
+console.log('Starting migrations/api_v3/coupons.js.');
+
+require('babel-register');
+require('babel-polyfill');
+
+var Bluebird = require('bluebird');
+var MongoDB = require('mongodb');
+var nconf = require('nconf');
+var mongoose = require('mongoose');
+var _ = require('lodash');
+var uuid = require('uuid');
+var consoleStamp = require('console-stamp');
+
+// Add timestamps to console messages
+consoleStamp(console);
+
+// Initialize configuration
+require('../../website/server/libs/api-v3/setupNconf')();
+
+var MONGODB_OLD = nconf.get('MONGODB_OLD');
+var MONGODB_NEW = nconf.get('MONGODB_NEW');
+
+var MongoClient = MongoDB.MongoClient;
+
+mongoose.Promise = Bluebird; // otherwise mongoose models won't work
+
+// Load new models
+var Coupon = require('../../website/server/models/coupon').model;
+
+// To be defined later when MongoClient connects
+var mongoDbOldInstance;
+var oldCouponCollection;
+
+var mongoDbNewInstance;
+var newCouponCollection;
+
+var BATCH_SIZE = 1000;
+
+var processedCoupons = 0;
+
+// Only process coupons that fall in a interval ie -> up to 0000-4000-0000-0000
+var AFTER_COUPON_ID = nconf.get('AFTER_COUPON_ID');
+var BEFORE_COUPON_ID = nconf.get('BEFORE_COUPON_ID');
+
+function processCoupons (afterId) {
+ var processedTasks = 0;
+ var lastCoupon = null;
+ var oldCoupons;
+
+ var query = {};
+
+ if (BEFORE_COUPON_ID) {
+ query._id = {$lte: BEFORE_COUPON_ID};
+ }
+
+ if ((afterId || AFTER_COUPON_ID) && !query._id) {
+ query._id = {};
+ }
+
+ if (afterId) {
+ query._id.$gt = afterId;
+ } else if (AFTER_COUPON_ID) {
+ query._id.$gt = AFTER_COUPON_ID;
+ }
+
+ var batchInsertCoupons = newCouponCollection.initializeUnorderedBulkOp();
+
+ console.log(`Executing coupons query.\nMatching coupons after ${afterId ? afterId : AFTER_COUPON_ID} and before ${BEFORE_COUPON_ID} (included).`);
+
+ return oldCouponCollection
+ .find(query)
+ .sort({_id: 1})
+ .limit(BATCH_SIZE)
+ .toArray()
+ .then(function (oldCouponsR) {
+ oldCoupons = oldCouponsR;
+
+ console.log(`Processing ${oldCoupons.length} coupons. Already processed ${processedCoupons} coupons.`);
+
+ if (oldCoupons.length === BATCH_SIZE) {
+ lastCoupon = oldCoupons[oldCoupons.length - 1]._id;
+ }
+
+ oldCoupons.forEach(function (oldCoupon) {
+ var newCoupon = new Coupon(oldCoupon);
+
+ batchInsertCoupons.insert(newCoupon.toObject());
+ });
+
+ console.log(`Saving ${oldCoupons.length} coupons.`);
+
+ return batchInsertCoupons.execute();
+ })
+ .then(function () {
+ processedCoupons += oldCoupons.length;
+
+ console.log(`Saved ${oldCoupons.length} coupons.`);
+
+ if (lastCoupon) {
+ return processCoupons(lastCoupon);
+ } else {
+ return console.log('Done!');
+ }
+ });
+}
+
+// Connect to the databases
+Bluebird.all([
+ MongoClient.connect(MONGODB_OLD),
+ MongoClient.connect(MONGODB_NEW),
+])
+.then(function (result) {
+ var oldInstance = result[0];
+ var newInstance = result[1];
+
+ mongoDbOldInstance = oldInstance;
+ oldCouponCollection = mongoDbOldInstance.collection('coupons');
+
+ mongoDbNewInstance = newInstance;
+ newCouponCollection = mongoDbNewInstance.collection('coupons');
+
+ console.log(`Connected with MongoClient to ${MONGODB_OLD} and ${MONGODB_NEW}.`);
+
+ return processCoupons();
+})
+.catch(function (err) {
+ console.error(err.stack || err);
+});
diff --git a/migrations/api_v3/emailUnsubscriptions.js b/migrations/api_v3/emailUnsubscriptions.js
new file mode 100644
index 0000000000..d90525db16
--- /dev/null
+++ b/migrations/api_v3/emailUnsubscriptions.js
@@ -0,0 +1,137 @@
+// Migrate unsubscriptions collection to new schema
+
+// The console-stamp module must be installed (not included in package.json)
+
+// It requires two environment variables: MONGODB_OLD and MONGODB_NEW
+
+// Due to some big user profiles it needs more RAM than is allowed by default by v8 (arounf 1.7GB).
+// Run the script with --max-old-space-size=4096 to allow up to 4GB of RAM
+console.log('Starting migrations/api_v3/unsubscriptions.js.');
+
+require('babel-register');
+require('babel-polyfill');
+
+var Bluebird = require('bluebird');
+var MongoDB = require('mongodb');
+var nconf = require('nconf');
+var mongoose = require('mongoose');
+var _ = require('lodash');
+var uuid = require('uuid');
+var consoleStamp = require('console-stamp');
+
+// Add timestamps to console messages
+consoleStamp(console);
+
+// Initialize configuration
+require('../../website/server/libs/api-v3/setupNconf')();
+
+var MONGODB_OLD = nconf.get('MONGODB_OLD');
+var MONGODB_NEW = nconf.get('MONGODB_NEW');
+
+var MongoClient = MongoDB.MongoClient;
+
+mongoose.Promise = Bluebird; // otherwise mongoose models won't work
+
+// Load new models
+var EmailUnsubscription = require('../../website/server/models/emailUnsubscription').model;
+
+// To be defined later when MongoClient connects
+var mongoDbOldInstance;
+var oldUnsubscriptionCollection;
+
+var mongoDbNewInstance;
+var newUnsubscriptionCollection;
+
+var BATCH_SIZE = 1000;
+
+var processedUnsubscriptions = 0;
+
+// Only process unsubscriptions that fall in a interval ie -> up to 0000-4000-0000-0000
+var AFTER_UNSUBSCRIPTION_ID = nconf.get('AFTER_UNSUBSCRIPTION_ID');
+var BEFORE_UNSUBSCRIPTION_ID = nconf.get('BEFORE_UNSUBSCRIPTION_ID');
+
+function processUnsubscriptions (afterId) {
+ var processedTasks = 0;
+ var lastUnsubscription = null;
+ var oldUnsubscriptions;
+
+ var query = {};
+
+ if (BEFORE_UNSUBSCRIPTION_ID) {
+ query._id = {$lte: BEFORE_UNSUBSCRIPTION_ID};
+ }
+
+ if ((afterId || AFTER_UNSUBSCRIPTION_ID) && !query._id) {
+ query._id = {};
+ }
+
+ if (afterId) {
+ query._id.$gt = afterId;
+ } else if (AFTER_UNSUBSCRIPTION_ID) {
+ query._id.$gt = AFTER_UNSUBSCRIPTION_ID;
+ }
+
+ var batchInsertUnsubscriptions = newUnsubscriptionCollection.initializeUnorderedBulkOp();
+
+ console.log(`Executing unsubscriptions query.\nMatching unsubscriptions after ${afterId ? afterId : AFTER_UNSUBSCRIPTION_ID} and before ${BEFORE_UNSUBSCRIPTION_ID} (included).`);
+
+ return oldUnsubscriptionCollection
+ .find(query)
+ .sort({_id: 1})
+ .limit(BATCH_SIZE)
+ .toArray()
+ .then(function (oldUnsubscriptionsR) {
+ oldUnsubscriptions = oldUnsubscriptionsR;
+
+ console.log(`Processing ${oldUnsubscriptions.length} unsubscriptions. Already processed ${processedUnsubscriptions} unsubscriptions.`);
+
+ if (oldUnsubscriptions.length === BATCH_SIZE) {
+ lastUnsubscription = oldUnsubscriptions[oldUnsubscriptions.length - 1]._id;
+ }
+
+ oldUnsubscriptions.forEach(function (oldUnsubscription) {
+ oldUnsubscription.email = oldUnsubscription.email.toLowerCase();
+ var newUnsubscription = new EmailUnsubscription(oldUnsubscription);
+
+ batchInsertUnsubscriptions.insert(newUnsubscription.toObject());
+ });
+
+ console.log(`Saving ${oldUnsubscriptions.length} unsubscriptions.`);
+
+ return batchInsertUnsubscriptions.execute();
+ })
+ .then(function () {
+ processedUnsubscriptions += oldUnsubscriptions.length;
+
+ console.log(`Saved ${oldUnsubscriptions.length} unsubscriptions.`);
+
+ if (lastUnsubscription) {
+ return processUnsubscriptions(lastUnsubscription);
+ } else {
+ return console.log('Done!');
+ }
+ });
+}
+
+// Connect to the databases
+Bluebird.all([
+ MongoClient.connect(MONGODB_OLD),
+ MongoClient.connect(MONGODB_NEW),
+])
+.then(function (result) {
+ var oldInstance = result[0];
+ var newInstance = result[1];
+
+ mongoDbOldInstance = oldInstance;
+ oldUnsubscriptionCollection = mongoDbOldInstance.collection('emailunsubscriptions');
+
+ mongoDbNewInstance = newInstance;
+ newUnsubscriptionCollection = mongoDbNewInstance.collection('emailunsubscriptions');
+
+ console.log(`Connected with MongoClient to ${MONGODB_OLD} and ${MONGODB_NEW}.`);
+
+ return processUnsubscriptions();
+})
+.catch(function (err) {
+ console.error(err.stack || err);
+});
diff --git a/migrations/api_v3/groups.js b/migrations/api_v3/groups.js
new file mode 100644
index 0000000000..c6582e6b68
--- /dev/null
+++ b/migrations/api_v3/groups.js
@@ -0,0 +1,211 @@
+/*
+ members are not stored anymore
+ invites are not stored anymore
+
+ tavern id and leader must be updated
+*/
+
+// Migrate groups collection to new schema
+// Run AFTER users migration
+
+// The console-stamp module must be installed (not included in package.json)
+
+// It requires two environment variables: MONGODB_OLD and MONGODB_NEW
+
+// Due to some big user profiles it needs more RAM than is allowed by default by v8 (arounf 1.7GB).
+// Run the script with --max-old-space-size=4096 to allow up to 4GB of RAM
+console.log('Starting migrations/api_v3/groups.js.');
+
+require('babel-register');
+require('babel-polyfill');
+
+var Bluebird = require('bluebird');
+var MongoDB = require('mongodb');
+var nconf = require('nconf');
+var mongoose = require('mongoose');
+var _ = require('lodash');
+var uuid = require('uuid');
+var consoleStamp = require('console-stamp');
+
+// Add timestamps to console messages
+consoleStamp(console);
+
+// Initialize configuration
+require('../../website/server/libs/api-v3/setupNconf')();
+
+var MONGODB_OLD = nconf.get('MONGODB_OLD');
+var MONGODB_NEW = nconf.get('MONGODB_NEW');
+
+var MongoClient = MongoDB.MongoClient;
+
+mongoose.Promise = Bluebird; // otherwise mongoose models won't work
+
+// Load new models
+var NewGroup = require('../../website/server/models/group').model;
+
+var TAVERN_ID = require('../../website/server/models/group').TAVERN_ID;
+
+// To be defined later when MongoClient connects
+var mongoDbOldInstance;
+var oldGroupCollection;
+
+var mongoDbNewInstance;
+var newGroupCollection;
+var newUserCollection;
+
+var BATCH_SIZE = 1000;
+
+var processedGroups = 0;
+
+// Only process groups that fall in a interval ie -> up to 0000-4000-0000-0000
+var AFTER_GROUP_ID = nconf.get('AFTER_GROUP_ID');
+var BEFORE_GROUP_ID = nconf.get('BEFORE_GROUP_ID');
+
+function processGroups (afterId) {
+ var processedTasks = 0;
+ var lastGroup = null;
+ var oldGroups;
+
+ var query = {};
+
+ if (BEFORE_GROUP_ID) {
+ query._id = {$lte: BEFORE_GROUP_ID};
+ }
+
+ if ((afterId || AFTER_GROUP_ID) && !query._id) {
+ query._id = {};
+ }
+
+ if (afterId) {
+ query._id.$gt = afterId;
+ } else if (AFTER_GROUP_ID) {
+ query._id.$gt = AFTER_GROUP_ID;
+ }
+
+ var batchInsertGroups = newGroupCollection.initializeUnorderedBulkOp();
+
+ console.log(`Executing groups query.\nMatching groups after ${afterId ? afterId : AFTER_GROUP_ID} and before ${BEFORE_GROUP_ID} (included).`);
+
+ return oldGroupCollection
+ .find(query)
+ .sort({_id: 1})
+ .limit(BATCH_SIZE)
+ .toArray()
+ .then(function (oldGroupsR) {
+ oldGroups = oldGroupsR;
+
+ var promises = [];
+
+ console.log(`Processing ${oldGroups.length} groups. Already processed ${processedGroups} groups.`);
+
+ if (oldGroups.length === BATCH_SIZE) {
+ lastGroup = oldGroups[oldGroups.length - 1]._id;
+ }
+
+ oldGroups.forEach(function (oldGroup) {
+ if ((!oldGroup.privacy || oldGroup.privacy === 'private') && (!oldGroup.members || oldGroup.members.length === 0)) return; // delete empty private groups TODO must also delete challenges or this won't work
+
+ oldGroup.members = oldGroup.members || [];
+ oldGroup.memberCount = oldGroup.members ? oldGroup.members.length : 0;
+ oldGroup.challengeCount = oldGroup.challenges ? oldGroup.challenges.length : 0;
+
+ if (oldGroup.balance <= 0) oldGroup.balance = 0;
+ if (!oldGroup.name) oldGroup.name = 'group name';
+ if (!oldGroup.leaderOnly) oldGroup.leaderOnly = {};
+ if (!oldGroup.leaderOnly.challenges) oldGroup.leaderOnly.challenges = false;
+
+ // Tavern
+ if (oldGroup._id === 'habitrpg') {
+ oldGroup._id = TAVERN_ID;
+ oldGroup.leader = '7bde7864-ebc5-4ee2-a4b7-1070d464cdb0'; // Siena Leslie
+ }
+
+ if (!oldGroup.type) {
+ // throw new Error('group.type is required');
+ oldGroup.type = 'guild';
+ }
+
+ if (!oldGroup.leader) {
+ if (oldGroup.members && oldGroup.members.length > 0) {
+ oldGroup.leader = oldGroup.members[0];
+ } else {
+ throw new Error('group.leader is required and no member available!');
+ }
+ }
+
+ if (!oldGroup.privacy) {
+ // throw new Error('group.privacy is required');
+ oldGroup.privacy = 'private';
+ }
+
+ var updateMembers = {};
+
+ if (oldGroup.type === 'guild') {
+ updateMembers.$push = {guilds: oldGroup._id};
+ } else if (oldGroup.type === 'party') {
+ updateMembers.$set = {'party._id': oldGroup._id};
+ }
+
+ if (oldGroup.members) {
+ // Tyler Renelle
+ oldGroup.members.forEach(function (id, index) {
+ if (id === '9') {
+ oldGroup.members[index] = '00000000-0000-4000-9000-000000000000';
+ }
+ });
+
+ promises.push(newUserCollection.updateMany({
+ _id: {$in: oldGroup.members},
+ }, updateMembers, {multi: true}));
+ }
+
+ var newGroup = new NewGroup(oldGroup);
+
+ batchInsertGroups.insert(newGroup.toObject());
+ });
+
+ console.log(`Saving ${oldGroups.length} groups and migrating members to users collection.`);
+
+ promises.push(batchInsertGroups.execute());
+ return Bluebird.all(promises);
+ })
+ .then(function () {
+ processedGroups += oldGroups.length;
+
+ console.log(`Saved ${oldGroups.length} groups and migrated their members to the user collection.`);
+
+ if (lastGroup) {
+ return processGroups(lastGroup);
+ } else {
+ return console.log('Done!');
+ }
+ });
+}
+
+// Connect to the databases
+Bluebird.all([
+ MongoClient.connect(MONGODB_OLD),
+ MongoClient.connect(MONGODB_NEW),
+])
+.then(function (result) {
+ var oldInstance = result[0];
+ var newInstance = result[1];
+
+ mongoDbOldInstance = oldInstance;
+ oldGroupCollection = mongoDbOldInstance.collection('groups');
+
+ mongoDbNewInstance = newInstance;
+ newGroupCollection = mongoDbNewInstance.collection('groups');
+ newUserCollection = mongoDbNewInstance.collection('users');
+
+ console.log(`Connected with MongoClient to ${MONGODB_OLD} and ${MONGODB_NEW}.`);
+
+ // First delete the tavern group created by having required the group model
+ return newGroupCollection.deleteOne({_id: TAVERN_ID});
+})
+.then(function () {
+ return processGroups();
+})
+.catch(function (err) {
+ console.error(err.stack || err);
+});
diff --git a/migrations/api_v3/indexes.js b/migrations/api_v3/indexes.js
new file mode 100644
index 0000000000..07aaa21db8
--- /dev/null
+++ b/migrations/api_v3/indexes.js
@@ -0,0 +1,52 @@
+/*
+ DEFINE BEFORE MIGRATING
+
+ tasks: userId OK (sparse?), challenge.id OK (sparse?), challenge.taskId OK (sparse?), type? completed?
+ users:
+ id & apiToken, OK
+ auth.facebook.emails.value OK -> unique and sparse?,
+ auth.facebook.id - unique and sparse, OK
+ auth.local.email - unique and sparse, OK
+ auth.local.lowerCaseUsername, OK
+ auth.local.username - unique OK
+ auth.local.username & auth.local.hashed_password?,
+ auth.timestamps.created?, OK
+ auth.timestamps.loggedin?, OK
+ backer.tier -1 OK
+ { "contributor.admin" : 1 , "contributor.level" : -1 , "backer.npc" : -1 , "profile.name" : 1}
+ { "contributor.admin" : 1.0} NO, see ^
+ { "contributor.level" : 1.0} OK
+ { "contributor.level" : 1.0 , "purchased.plan.customerId" : 1.0} ?
+ NO { "flags.lastWeeklyRecap" : 1 , "_id" : 1 , "preferences.emailNotifications.unsubscribeFromAll" : 1 , "preferences.emailNotifications.weeklyRecaps" : 1}
+ { "invitations.guilds.id" : 1} OK
+ { "invitations.party.id" : 1} OK
+ OK { "preferences.sleep" : 1 , "_id" : 1 , "flags.lastWeeklyRecap" : 1 , "preferences.emailNotifications.unsubscribeFromAll" : 1 , "preferences.emailNotifications.weeklyRecaps" : 1}
+ OK { "preferences.sleep" : 1 , "_id" : 1 , "lastCron" : 1 , "preferences.emailNotifications.importantAnnouncements" : 1 , "preferences.emailNotifications.unsubscribeFromAll" : 1 , "flags.recaptureEmailsPhase" : 1}
+ profile.name ? OK
+ { "purchased.plan.customerId" : 1.0} OK
+ { "purchased.plan.paymentMethod" : 1.0} OK
+
+ guilds OK
+ party.id OK
+ challenges OK
+ challenges:
+ { "_id" : 1.0 , "__v" : 1.0} ? NO
+ { "_id" : 1.0 , "official" : -1.0 , "timestamp" : -1.0}
+ { "group" : 1.0 , "official" : -1.0 , "timestamp" : -1.0} OK
+ { "leader" : 1.0 , "official" : -1.0 , "timestamp" : -1.0} OK
+ { "members" : 1.0 , "official" : -1.0 , "timestamp" : -1.0} ? NO
+ { "official" : -1 , "timestamp" : -1} ?
+ { "official" : -1 , "timestamp" : -1, "_id": 1} ?
+ groups:
+ { "_id" : 1 , "quest.key" : 1} ?
+ { "_id" : 1.0 , "__v" : 1.0} ?
+ { "_id" : 1.0 , "privacy" : 1.0 , "members" : 1.0} ? NO
+ { "members" : 1.0 , "type" : 1.0 , "memberCount" : -1.0} ? NO
+ { "members" : 1} ? NO
+ { "privacy" : 1.0 , "memberCount" : -1.0} ?
+ { "privacy" : 1.0} OK
+ { "type" : 1 , "privacy" : 1} ?
+ { "type" : 1.0 , "members" : 1.0} ? NO
+ { "type" : 1} ? OK
+ emailUnsubscriptions: email unique OK
+*/
diff --git a/migrations/api_v3/users.js b/migrations/api_v3/users.js
new file mode 100644
index 0000000000..656b8f498a
--- /dev/null
+++ b/migrations/api_v3/users.js
@@ -0,0 +1,262 @@
+// Migrate users collection to new schema
+// This should run AFTER challenges migration
+
+// The console-stamp module must be installed (not included in package.json)
+
+// It requires two environment variables: MONGODB_OLD and MONGODB_NEW
+
+// Due to some big user profiles it needs more RAM than is allowed by default by v8 (arounf 1.7GB).
+// Run the script with --max-old-space-size=4096 to allow up to 4GB of RAM
+console.log('Starting migrations/api_v3/users.js.');
+
+require('babel-register');
+require('babel-polyfill');
+
+var Bluebird = require('bluebird');
+var MongoDB = require('mongodb');
+var nconf = require('nconf');
+var mongoose = require('mongoose');
+var _ = require('lodash');
+var uuid = require('uuid');
+var consoleStamp = require('console-stamp');
+var common = require('../../common');
+var moment = require('moment');
+
+// Add timestamps to console messages
+consoleStamp(console);
+
+// Initialize configuration
+require('../../website/server/libs/api-v3/setupNconf')();
+
+var MONGODB_OLD = nconf.get('MONGODB_OLD');
+var MONGODB_NEW = nconf.get('MONGODB_NEW');
+
+var taskDefaults = common.taskDefaults;
+var MongoClient = MongoDB.MongoClient;
+
+mongoose.Promise = Bluebird; // otherwise mongoose models won't work
+
+// Load new models
+var NewUser = require('../../website/server/models/user').model;
+var NewTasks = require('../../website/server/models/task');
+
+// To be defined later when MongoClient connects
+var mongoDbOldInstance;
+var oldUserCollection;
+
+var mongoDbNewInstance;
+var newUserCollection;
+var newTaskCollection;
+
+var BATCH_SIZE = 1000;
+
+var processedUsers = 0;
+var totoalProcessedTasks = 0;
+
+var challengeTaskWithMatchingId = 0;
+var challengeTaskNoMatchingId = 0;
+
+// Load the new tasks ids for challenges tasks
+var newTasksIds = require('./newTasksIds.json');
+
+// Only process users that fall in a interval ie up to -> 0000-4000-0000-0000
+var AFTER_USER_ID = nconf.get('AFTER_USER_ID');
+var BEFORE_USER_ID = nconf.get('BEFORE_USER_ID');
+
+function processUsers (afterId) {
+ var processedTasks = 0;
+ var lastUser = null;
+ var oldUsers;
+
+ var now = new Date();
+
+ var query = {};
+
+ if (BEFORE_USER_ID) {
+ query._id = {$lte: BEFORE_USER_ID};
+ }
+
+ if ((afterId || AFTER_USER_ID) && !query._id) {
+ query._id = {};
+ }
+
+ if (afterId) {
+ query._id.$gt = afterId;
+ } else if (AFTER_USER_ID) {
+ query._id.$gt = AFTER_USER_ID;
+ }
+
+ var batchInsertTasks = newTaskCollection.initializeUnorderedBulkOp();
+ var batchInsertUsers = newUserCollection.initializeUnorderedBulkOp();
+
+ console.log(`Executing users query.\nMatching users after ${afterId ? afterId : AFTER_USER_ID} and before ${BEFORE_USER_ID} (included).`);
+
+ return oldUserCollection
+ .find(query)
+ .sort({_id: 1})
+ .limit(BATCH_SIZE)
+ .toArray()
+ .then(function (oldUsersR) {
+ oldUsers = oldUsersR;
+
+ console.log(`Processing ${oldUsers.length} users. Already processed ${processedUsers} users and ${totoalProcessedTasks} tasks.`);
+
+ if (oldUsers.length === BATCH_SIZE) {
+ lastUser = oldUsers[oldUsers.length - 1]._id;
+ }
+
+ oldUsers.forEach(function (oldUser) {
+ var oldTasks = oldUser.habits.concat(oldUser.dailys).concat(oldUser.rewards).concat(oldUser.todos);
+ delete oldUser.habits;
+ delete oldUser.dailys;
+ delete oldUser.rewards;
+ delete oldUser.todos;
+
+ delete oldUser.id;
+
+ // spookDust -> spookySparkles
+
+ if (oldUser.achievements && oldUser.achievements.spookDust) {
+ oldUser.achievements.spookySparkles = oldUser.achievements.spookDust;
+ delete oldUser.achievements.spookDust;
+ }
+
+ if (oldUser.items && oldUser.items.special && oldUser.items.special.spookDust) {
+ oldUser.items.special.spookySparkles = oldUser.items.special.spookDust;
+ delete oldUser.items.special.spookDust;
+ }
+
+ if (oldUser.stats && oldUser.stats.buffs && oldUser.stats.buffs.spookySparkles) {
+ oldUser.stats.buffs.spookySparkles = oldUser.stats.buffs.spookDust;
+ delete oldUser.stats.buffs.spookDust;
+ }
+
+ // end spookDust -> spookySparkles
+
+ oldUser.tags = oldUser.tags.map(function (tag) {
+ return {
+ id: tag.id,
+ name: tag.name || 'tag name',
+ challenge: tag.challenge,
+ };
+ });
+
+ if (oldUser._id === '9') { // Tyler Renelle
+ oldUser._id = '00000000-0000-4000-9000-000000000000';
+ }
+
+ var newUser = new NewUser(oldUser);
+ var isSubscribed = newUser.isSubscribed();
+
+ oldTasks.forEach(function (oldTask) {
+ oldTask._id = uuid.v4(); // create a new unique uuid
+ oldTask.userId = newUser._id;
+ oldTask._legacyId = oldTask.id; // store the old task id
+ delete oldTask.id;
+
+ oldTask.challenge = oldTask.challenge || {};
+ if (oldTask.challenge.id) {
+ if (oldTask.challenge.broken) {
+ oldTask.challenge.taskId = oldTask._legacyId;
+ } else {
+ var newId = newTasksIds[oldTask._legacyId + '-' + oldTask.challenge.id];
+
+ // Challenges' tasks ids changed
+ if (!newId && !oldTask.challenge.broken) {
+ challengeTaskNoMatchingId++;
+ oldTask.challenge.taskId = oldTask._legacyId;
+ oldTask.challenge.broken = 'CHALLENGE_TASK_NOT_FOUND';
+ } else {
+ challengeTaskWithMatchingId++;
+ oldTask.challenge.taskId = newId;
+ }
+ }
+ }
+
+ // Delete old completed todos
+ if (oldTask.type === 'todo' && oldTask.completed && (!oldTask.challenge.id || oldTask.challenge.broken)) {
+ if (moment(now).subtract(isSubscribed ? 90 : 30, 'days').toDate() > moment(oldTask.dateCompleted).toDate()) {
+ return;
+ }
+ }
+
+ oldTask.createdAt = oldTask.dateCreated;
+
+ if (!oldTask.text) oldTask.text = 'task text'; // required
+ oldTask.tags = _.map(oldTask.tags, function (tagPresent, tagId) {
+ return tagPresent && tagId;
+ }).filter(function (tag) {
+ return tag !== false;
+ });
+
+ if (oldTask.type !== 'todo' || (oldTask.type === 'todo' && !oldTask.completed)) {
+ newUser.tasksOrder[`${oldTask.type}s`].push(oldTask._id);
+ }
+
+ var allTasksFields = ['_id', 'type', 'text', 'notes', 'tags', 'value', 'priority', 'attribute', 'challenge', 'reminders', 'userId', '_legacyId', 'createdAt'];
+ // using mongoose models is too slow
+ if (oldTask.type === 'habit') {
+ oldTask = _.pick(oldTask, allTasksFields.concat(['history', 'up', 'down']));
+ } else if (oldTask.type === 'daily') {
+ oldTask = _.pick(oldTask, allTasksFields.concat(['completed', 'collapseChecklist', 'checklist', 'history', 'frequency', 'everyX', 'startDate', 'repeat', 'streak']));
+ } else if (oldTask.type === 'todo') {
+ oldTask = _.pick(oldTask, allTasksFields.concat(['completed', 'collapseChecklist', 'checklist', 'date', 'dateCompleted']));
+ } else if (oldTask.type === 'reward') {
+ oldTask = _.pick(oldTask, allTasksFields);
+ } else {
+ throw new Error('Task with no or invalid type!');
+ }
+
+ batchInsertTasks.insert(taskDefaults(oldTask));
+ processedTasks++;
+ });
+
+ batchInsertUsers.insert(newUser.toObject());
+ });
+
+ console.log(`Saving ${oldUsers.length} users and ${processedTasks} tasks.`);
+
+ return Bluebird.all([
+ batchInsertUsers.execute(),
+ batchInsertTasks.execute(),
+ ]);
+ })
+ .then(function () {
+ totoalProcessedTasks += processedTasks;
+ processedUsers += oldUsers.length;
+
+ console.log(`Saved ${oldUsers.length} users and their tasks.`);
+ console.log('Challenges\' tasks no matching id: ', challengeTaskNoMatchingId);
+ console.log('Challenges\' tasks with matching id: ', challengeTaskWithMatchingId);
+
+ if (lastUser) {
+ return processUsers(lastUser);
+ } else {
+ return console.log('Done!');
+ }
+ });
+}
+
+// Connect to the databases
+Bluebird.all([
+ MongoClient.connect(MONGODB_OLD),
+ MongoClient.connect(MONGODB_NEW),
+])
+.then(function (result) {
+ var oldInstance = result[0];
+ var newInstance = result[1];
+
+ mongoDbOldInstance = oldInstance;
+ oldUserCollection = mongoDbOldInstance.collection('users');
+
+ mongoDbNewInstance = newInstance;
+ newUserCollection = mongoDbNewInstance.collection('users');
+ newTaskCollection = mongoDbNewInstance.collection('tasks');
+
+ console.log(`Connected with MongoClient to ${MONGODB_OLD} and ${MONGODB_NEW}.`);
+
+ return processUsers();
+})
+.catch(function (err) {
+ console.error(err.stack || err);
+});
diff --git a/migrations/manual_password_reset.js b/migrations/manual_password_reset.js
index 68b69cbbbe..622e16913b 100644
--- a/migrations/manual_password_reset.js
+++ b/migrations/manual_password_reset.js
@@ -7,7 +7,7 @@ nconf.argv().env().file('user', path.join(path.resolve(__dirname, '../config.jso
var Users = require('mongoskin').db(nconf.get("PRODUCTION_DB:URL"), nconf.get("PRODUCTION_DB").CREDS).collection('users'),
async = require('async'),
- utils = require('../website/src/utils'),
+ utils = require('../website/server/utils'),
salt = utils.makeSalt(),
newPassword = utils.makeSalt(), // use a salt as the new password too (they'll change it later)
hashed_password = utils.encryptPassword(newPassword, salt);
diff --git a/migrations/mystery_items.js b/migrations/mystery_items.js
index 1e385a0480..c858c277e3 100644
--- a/migrations/mystery_items.js
+++ b/migrations/mystery_items.js
@@ -2,7 +2,7 @@ var _id = '';
var update = {
$addToSet: {
'purchased.plan.mysteryItems':{
- $each:['head_mystery_201603','armor_mystery_201603']
+ $each:['head_mystery_201605','armor_mystery_201605']
}
}
};
diff --git a/migrations/utils/connect.js b/migrations/utils/connect.js
new file mode 100644
index 0000000000..0c1b104892
--- /dev/null
+++ b/migrations/utils/connect.js
@@ -0,0 +1,35 @@
+'use strict';
+
+const MongoClient = require('mongodb').MongoClient;
+const logger = require('./logger');
+
+let db;
+
+function connectToDb (dbUri) {
+ return new Promise((resolve, reject) => {
+ MongoClient.connect(dbUri, (err, database) => {
+ if (err) {
+ logger.error(`Uh oh... Problem connecting to the database at ${dbUri}`);
+ return reject(err);
+ }
+
+ db = database;
+
+ logger.success(`Connected to ${dbUri}`);
+
+ resolve(database);
+ });
+ });
+}
+
+function closeDb () {
+ if (db) db.close();
+
+ logger.success('CLosed connection to the database');
+ return Promise.resolve();
+}
+
+module.exports = {
+ connectToDb,
+ closeDb,
+}
diff --git a/migrations/utils/logger.js b/migrations/utils/logger.js
new file mode 100644
index 0000000000..a2ddc96fe2
--- /dev/null
+++ b/migrations/utils/logger.js
@@ -0,0 +1,20 @@
+'use strict';
+
+const chalk = require('chalk');
+
+const logger = {
+ info: loggerGenerator('info', 'cyan'),
+ success: loggerGenerator('info', 'green'),
+ error: loggerGenerator('error', 'red'),
+ log: loggerGenerator('log', 'white'),
+ warn: loggerGenerator('warn', 'yellow'),
+};
+
+function loggerGenerator (type, color) {
+ return function () {
+ let args = Array.from(arguments).map(arg => chalk[color](arg));
+ console[type].apply(null, args);
+ }
+}
+
+module.exports = logger;
diff --git a/migrations/utils/timer.js b/migrations/utils/timer.js
new file mode 100644
index 0000000000..37a75830b1
--- /dev/null
+++ b/migrations/utils/timer.js
@@ -0,0 +1,44 @@
+'use strict';
+
+let logger = require('./logger');
+
+class Timer {
+ constructor (options) {
+ options = options || {};
+ let warningThreshold = options.minutesWarningThreshold || 10;
+
+ this.count = 0;
+ this._minutesWarningThreshold = warningThreshold * 60;
+
+ if (!options.disableAutoStart) this.start();
+ }
+ start () {
+ this._internalTimer = setInterval(() =>{
+ this.count++;
+
+ let shouldWarn = this._minutesWarningThreshold < this.count;
+ let logStyle = shouldWarn ? 'error' : 'warn';
+ let dangerMessage = shouldWarn ? 'DANGER: ' : '';
+
+ if (this.count % 30 === 0) {
+ logger[logStyle](`${dangerMessage}Process has been running for`, this.count / 60, 'minutes');
+ }
+ }, 1000);
+ }
+ stop () {
+ if (!this._internalTimer) {
+ throw new Error('Timer has not started');
+ }
+ clearInterval(this._internalTimer);
+ }
+
+ get seconds () {
+ return this.count;
+ }
+
+ get minutes () {
+ return this.count / 60;
+ }
+}
+
+module.exports = Timer;
diff --git a/migrations/utils/unique.js b/migrations/utils/unique.js
new file mode 100644
index 0000000000..0d222145c3
--- /dev/null
+++ b/migrations/utils/unique.js
@@ -0,0 +1,7 @@
+'use strict';
+
+function unique (array) {
+ return Array.from(new Set(array));
+}
+
+module.exports = unique;
diff --git a/newrelic.js b/newrelic.js
deleted file mode 100644
index 0e8a550af7..0000000000
--- a/newrelic.js
+++ /dev/null
@@ -1,27 +0,0 @@
-var nconf = require('nconf');
-
-/**
- * New Relic agent configuration.
- *
- * See lib/config.defaults.js in the agent distribution for a more complete
- * description of configuration variables and their potential values.
- */
-exports.config = {
- /**
- * Array of application names.
- */
- app_name: nconf.get('NEW_RELIC_APP_NAME'),
- /**
- * Your New Relic license key.
- */
- license_key: nconf.get('NEW_RELIC_LICENSE_KEY'),
- ssl: false,
- logging: {
- /**
- * Level at which to log. 'trace' is most useful to New Relic when diagnosing
- * issues with the agent, 'info' and higher will impose the least overhead on
- * production applications.
- */
- level: 'info'
- }
-}
diff --git a/package.json b/package.json
index 529a754d99..3161ea3616 100644
--- a/package.json
+++ b/package.json
@@ -1,32 +1,39 @@
{
- "name": "habitrpg",
+ "name": "habitica",
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
- "version": "0.0.0-152",
- "main": "./website/src/server.js",
+ "version": "3.6.0",
+ "main": "./website/server/index.js",
"dependencies": {
+ "accepts": "^1.3.2",
"amazon-payments": "0.0.4",
"amplitude": "^2.0.3",
+ "apidoc": "^0.16.0",
"async": "^1.5.0",
"aws-sdk": "^2.0.25",
- "babel-plugin-syntax-async-functions": "^6.5.0",
- "babel-plugin-transform-regenerator": "^6.6.0",
+ "babel-plugin-transform-async-to-module-method": "^6.8.0",
"babel-polyfill": "^6.6.1",
"babel-preset-es2015": "^6.6.0",
"babel-register": "^6.6.0",
"babelify": "^7.2.0",
+ "bluebird": "^3.3.5",
"body-parser": "^1.15.0",
"bower": "~1.3.12",
"browserify": "~12.0.1",
"compression": "^1.6.1",
"connect-ratelimit": "0.0.7",
"cookie-session": "^1.2.0",
- "coupon-code": "~0.3.0",
+ "coupon-code": "^0.4.3",
"csv-stringify": "^1.0.2",
+ "cwait": "^1.0.0",
"domain-middleware": "~0.1.0",
- "express": "^4.13.4",
+ "estraverse": "^4.1.1",
+ "express": "~4.13.3",
+ "express-csv": "~0.6.0",
+ "express-validator": "^2.18.0",
"firebase": "^2.2.9",
"firebase-token-generator": "^2.0.0",
"glob": "^4.3.5",
+ "got": "^6.1.1",
"grunt": "~0.4.1",
"grunt-cli": "~0.1.9",
"grunt-contrib-clean": "~0.6.0",
@@ -39,7 +46,6 @@
"grunt-karma": "~0.12.1",
"gulp": "^3.9.0",
"gulp-babel": "^6.1.2",
- "gulp-eslint": "^1.0.0",
"gulp-grunt": "^0.5.2",
"gulp-imagemin": "^2.4.0",
"gulp-nodemon": "^2.0.4",
@@ -52,35 +58,38 @@
"jade": "~1.11.0",
"js2xmlparser": "~1.0.0",
"lodash": "^3.10.1",
- "loggly": "~1.0.8",
- "marked": "^0.3.5",
+ "lodash.setwith": "^4.2.0",
+ "markdown-it": "^6.0.1",
"merge-stream": "^1.0.0",
"method-override": "^2.3.5",
- "moment": "~2.10.6",
- "mongoose": "~3.8.23",
+ "moment": "^2.13.0",
+ "mongoose": "^4.4.16",
"mongoose-id-autoinc": "~2013.7.14-4",
"morgan": "^1.7.0",
"nconf": "~0.8.2",
- "newrelic": "~1.26.1",
- "nib": "~1.0.1",
- "nodemailer": "^1.9.0",
+ "newrelic": "^1.27.2",
+ "nib": "^1.1.0",
+ "nodemailer": "^2.3.2",
+ "object-path": "^0.9.2",
"pageres": "^4.1.1",
"passport": "~0.2.1",
"passport-facebook": "2.0.0",
- "paypal-ipn": "2.1.0",
+ "paypal-ipn": "3.0.0",
"paypal-rest-sdk": "^1.2.1",
"pretty-data": "^0.40.0",
"ps-tree": "^1.0.0",
"push-notify": "^1.1.1",
- "q": "^1.4.1",
- "request": "~2.44.0",
+ "request": "~2.72.0",
+ "rimraf": "^2.4.3",
+ "run-sequence": "^1.1.4",
"s3-upload-stream": "^1.0.6",
"serve-favicon": "^2.3.0",
"stripe": "^4.2.0",
- "superagent": "~1.4.0",
+ "superagent": "^1.8.3",
"swagger-node-express": "lefnire/swagger-node-express#habitrpg",
"universal-analytics": "~0.3.2",
- "validator": "~4.2.1",
+ "uuid": "^2.0.1",
+ "validator": "^4.9.0",
"vinyl-buffer": "^1.0.0",
"vinyl-source-stream": "^1.1.0",
"winston": "^2.1.0"
@@ -88,15 +97,20 @@
"private": true,
"engines": {
"node": "^4.3.1",
- "npm": "^2.14.9"
+ "npm": "^3.8.9"
},
"scripts": {
- "test": "gulp test",
+ "lint": "eslint .",
+ "test": "npm run lint && gulp test",
"test:api-v2:unit": "mocha test/server_side",
"test:api-v2:integration": "mocha test/api/v2 --recursive",
- "test:api-legacy": "istanbul cover -i \"website/src/**\" --dir coverage/api ./node_modules/mocha/bin/_mocha test/api-legacy",
- "test:common": "mocha test/common",
- "test:content": "mocha test/content",
+ "test:api-v3": "gulp test:api-v3",
+ "test:api-v3:unit": "gulp test:api-v3:unit",
+ "test:api-v3:integration": "gulp test:api-v3:integration",
+ "test:api-v3:integration:separate-server": "gulp test:api-v3:integration:separate-server",
+ "test:api-legacy": "istanbul cover -i \"website/server/**\" --dir coverage/api ./node_modules/mocha/bin/_mocha test/api-legacy",
+ "test:common": "mocha test/common --recursive",
+ "test:content": "mocha test/content --recursive",
"test:karma": "karma start --single-run",
"test:karma:watch": "karma start",
"test:prepare:webdriver": "webdriver-manager update",
@@ -109,15 +123,17 @@
"coverage": "COVERAGE=true mocha --require register-handlers.js --reporter html-cov > coverage.html; open coverage.html"
},
"devDependencies": {
- "babel-eslint": "^5.0.0",
+ "babel-eslint": "^6.0.0",
"chai": "^3.4.0",
"chai-as-promised": "^5.1.0",
+ "chalk": "^1.1.3",
"coveralls": "^2.11.2",
"csv": "~0.3.6",
"deep-diff": "~0.1.4",
- "eslint": "^1.9.0",
+ "eslint": "^2.10.1",
+ "eslint-config-habitrpg": "^1.0.0",
"eslint-plugin-babel": "^3.0.0",
- "eslint-plugin-mocha": "^1.1.0",
+ "eslint-plugin-mocha": "^2.1.0",
"event-stream": "^3.2.2",
"expect.js": "~0.2.0",
"istanbul": "^0.3.14",
@@ -132,17 +148,17 @@
"mocha": "^2.3.3",
"mongodb": "^2.0.46",
"mongoskin": "~0.6.1",
+ "nock": "^2.17.0",
"phantomjs": "^1.9",
"protractor": "^3.1.1",
+ "require-again": "^1.0.1",
"rewire": "^2.3.3",
- "rimraf": "^2.4.3",
- "run-sequence": "^1.1.4",
- "shelljs": "^0.4.0",
+ "shelljs": "^0.7.0",
"sinon": "^1.17.2",
"sinon-chai": "^2.8.0",
"superagent-defaults": "^0.1.13",
- "uuid": "^2.0.1",
"vinyl-source-stream": "^1.0.0",
- "vinyl-transform": "^1.0.0"
+ "vinyl-transform": "^1.0.0",
+ "xml2js": "^0.4.16"
}
}
diff --git a/website/src/controllers/payments/paypalBillingSetup.js b/scripts/paypalBillingSetup.js
similarity index 99%
rename from website/src/controllers/payments/paypalBillingSetup.js
rename to scripts/paypalBillingSetup.js
index 2effcbd81d..d21cd80c1c 100644
--- a/website/src/controllers/payments/paypalBillingSetup.js
+++ b/scripts/paypalBillingSetup.js
@@ -2,14 +2,16 @@
// payment plan definitions, instead you have to create it via their REST SDK and keep it updated the same way. So this
// file will be used once for initing your billing plan (then you get the resultant plan.id to store in config.json),
// and once for any time you need to edit the plan thereafter
+
var path = require('path');
var nconf = require('nconf');
-_ = require('lodash');
-nconf.argv().env().file('user', path.join(path.resolve(__dirname, '../../../config.json')));
+var _ = require('lodash');
var paypal = require('paypal-rest-sdk');
var blocks = require('../../../../common').content.subscriptionBlocks;
var live = nconf.get('PAYPAL:mode')=='live';
+nconf.argv().env().file('user', path.join(path.resolve(__dirname, '../../../config.json')));
+
var OP = 'create'; // list create update remove
paypal.configure({
diff --git a/tasks/gulp-apidoc.js b/tasks/gulp-apidoc.js
new file mode 100644
index 0000000000..b8f65d2abd
--- /dev/null
+++ b/tasks/gulp-apidoc.js
@@ -0,0 +1,22 @@
+import gulp from 'gulp';
+import clean from 'rimraf';
+import apidoc from 'apidoc';
+
+const APIDOC_DEST_PATH = './website/build/apidoc';
+const APIDOC_SRC_PATH = './website/server';
+gulp.task('apidoc:clean', (done) => {
+ clean(APIDOC_DEST_PATH, done);
+});
+
+gulp.task('apidoc', ['apidoc:clean'], (done) => {
+ let result = apidoc.createDoc({
+ src: APIDOC_SRC_PATH,
+ dest: APIDOC_DEST_PATH,
+ });
+
+ if (result === false) {
+ done(new Error('There was a problem generating apiDoc documentation.'))
+ } else {
+ done();
+ }
+});
diff --git a/tasks/gulp-build.js b/tasks/gulp-build.js
index 2fd34d0121..e660145d92 100644
--- a/tasks/gulp-build.js
+++ b/tasks/gulp-build.js
@@ -1,4 +1,5 @@
import gulp from 'gulp';
+import runSequence from 'run-sequence';
import babel from 'gulp-babel';
require('gulp-grunt')(gulp);
@@ -11,7 +12,7 @@ gulp.task('build', () => {
});
gulp.task('build:src', () => {
- return gulp.src('website/src/**/*.js')
+ return gulp.src('website/server/**/*.js')
.pipe(babel())
.pipe(gulp.dest('website/transpiled-babel/'));
});
@@ -29,9 +30,13 @@ gulp.task('build:dev', ['browserify', 'prepare:staticNewStuff'], (done) => {
});
gulp.task('build:dev:watch', ['build:dev'], () => {
- gulp.watch(['website/public/**/*.styl', 'common/script/*']);
+ gulp.watch(['website/client/**/*.styl', 'common/script/*']);
});
gulp.task('build:prod', ['browserify', 'build:server', 'prepare:staticNewStuff'], (done) => {
- gulp.start('grunt-build:prod', done);
+ runSequence(
+ 'grunt-build:prod',
+ 'apidoc',
+ done
+ );
});
diff --git a/tasks/gulp-console.js b/tasks/gulp-console.js
index 07512c6357..026d646cee 100644
--- a/tasks/gulp-console.js
+++ b/tasks/gulp-console.js
@@ -1,8 +1,7 @@
import mongoose from 'mongoose';
import autoinc from 'mongoose-id-autoinc';
-import logging from '../website/src/libs/logging';
+import logger from '../website/server/libs/api-v3/logger';
import nconf from 'nconf';
-import utils from '../website/src/libs/utils';
import repl from 'repl';
import gulp from 'gulp';
@@ -19,11 +18,9 @@ let improveRepl = (context) => {
process.stdout.write('\u001B[2J\u001B[0;0f');
}});
- utils.setupConfig();
-
- context.Challenge = require('../website/src/models/challenge').model;
- context.Group = require('../website/src/models/group').model;
- context.User = require('../website/src/models/user').model;
+ context.Challenge = require('../website/server/models/challenge').model;
+ context.Group = require('../website/server/models/group').model;
+ context.User = require('../website/server/models/user').model;
var isProd = nconf.get('NODE_ENV') === 'production';
var mongooseOptions = !isProd ? {} : {
@@ -36,7 +33,7 @@ let improveRepl = (context) => {
mongooseOptions,
function(err) {
if (err) throw err;
- logging.info('Connected with Mongoose');
+ logger.info('Connected with Mongoose');
}
)
);
diff --git a/tasks/gulp-eslint.js b/tasks/gulp-eslint.js
deleted file mode 100644
index 735845beb7..0000000000
--- a/tasks/gulp-eslint.js
+++ /dev/null
@@ -1,63 +0,0 @@
-import gulp from 'gulp';
-import eslint from 'gulp-eslint';
-
-const SERVER_FILES = [
- './website/src/**/api-v3/**/*.js',
- // Comment these out in develop, uncomment them in api-v3
- // './website/src/models/user.js',
- // './website/src/server.js'
-];
-const COMMON_FILES = [
- './common/script/**/*.js',
- // @TODO remove these negations as the files are converted over.
- '!./common/script/index.js',
- '!./common/script/content/index.js',
- '!./common/script/ops/**/*.js',
- '!./common/script/fns/**/*.js',
- '!./common/script/libs/**/*.js',
- '!./common/script/public/**/*.js',
-];
-const TEST_FILES = [
- './test/**/*.js',
- // @TODO remove these negations as the test files are cleaned up.
- '!./test/api-legacy/**/*',
- '!./test/common/simulations/**/*',
- '!./test/content/**/*',
- '!./test/server_side/**/*',
- '!./test/spec/**/*',
-];
-
-let linter = (src, options) => {
- return gulp
- .src(src)
- .pipe(eslint(options))
- .pipe(eslint.format())
- .pipe(eslint.failAfterError());
-};
-
-// TODO lint client
-// TDOO separate linting cong between
-// TODO lint gulp tasks, tests, ...?
-// TODO what about prefer-const rule?
-// TODO remove estraverse dependency once https://github.com/adametry/gulp-eslint/issues/117 sorted out
-gulp.task('lint:server', () => {
- return linter(SERVER_FILES);
-});
-
-gulp.task('lint:common', () => {
- return linter(COMMON_FILES);
-});
-
-gulp.task('lint:tests', () => {
- return linter(TEST_FILES);
-});
-
-gulp.task('lint', ['lint:server', 'lint:common', 'lint:tests']);
-
-gulp.task('lint:watch', () => {
- gulp.watch([
- SERVER_FILES,
- COMMON_FILES,
- TEST_FILES,
- ], ['lint']);
-});
diff --git a/tasks/gulp-newstuff.js b/tasks/gulp-newstuff.js
index 16085e5c1c..b6d8093ee5 100644
--- a/tasks/gulp-newstuff.js
+++ b/tasks/gulp-newstuff.js
@@ -4,7 +4,7 @@ import {writeFileSync} from 'fs';
gulp.task('prepare:staticNewStuff', () => {
writeFileSync(
- './website/public/new-stuff.html',
+ './website/client/new-stuff.html',
jade.compileFile('./website/views/shared/new-stuff.jade')()
);
});
diff --git a/tasks/gulp-sprites.js b/tasks/gulp-sprites.js
index 55464fcc6c..4c89871fc4 100644
--- a/tasks/gulp-sprites.js
+++ b/tasks/gulp-sprites.js
@@ -127,7 +127,7 @@ function calculateImgDimensions(img, addPadding) {
}
function checkForSpecialTreatment(name) {
- let regex = /^hair|skin|beard|mustach|shirt|flower|^headAccessory_special_\w+Ears/;
+ let regex = /^hair|skin|beard|mustach|shirt|flower|^headAccessory_special_\w+Ears|^eyewear_special_\w+TopFrame/;
return name.match(regex) || name === 'head_0';
}
diff --git a/tasks/gulp-start.js b/tasks/gulp-start.js
index 7cb842af00..51825f71ea 100644
--- a/tasks/gulp-start.js
+++ b/tasks/gulp-start.js
@@ -9,7 +9,7 @@ gulp.task('nodemon', () => {
nodemon({
script: pkg.main,
ignore: [
- 'website/public/*',
+ 'website/client/*',
'website/views/*',
'common/dist/script/content/*',
]
diff --git a/tasks/gulp-tests.js b/tasks/gulp-tests.js
index bada857d36..7db299a5c4 100644
--- a/tasks/gulp-tests.js
+++ b/tasks/gulp-tests.js
@@ -9,17 +9,20 @@ import mongoose from 'mongoose';
import { exec } from 'child_process';
import psTree from 'ps-tree';
import gulp from 'gulp';
-import Q from 'q';
+import Bluebird from 'bluebird';
import runSequence from 'run-sequence';
import os from 'os';
import nconf from 'nconf';
+// TODO rewrite
+
const TEST_SERVER_PORT = 3003
let server;
const TEST_DB_URI = nconf.get('TEST_DB_URI');
const API_V2_TEST_COMMAND = 'npm run test:api-v2:integration';
+const API_V3_TEST_COMMAND = 'npm run test:api-v3';
const LEGACY_API_TEST_COMMAND = 'npm run test:api-legacy';
const COMMON_TEST_COMMAND = 'npm run test:common';
const CONTENT_TEST_COMMAND = 'npm run test:content';
@@ -41,9 +44,9 @@ let testBin = (string, additionalEnvVariables = '') => {
additionalEnvVariables = additionalEnvVariables.split(' ').join('&&set ');
additionalEnvVariables = 'set ' + additionalEnvVariables + '&&';
}
- return `set NODE_ENV=testing&&${additionalEnvVariables}${string}`;
+ return `set NODE_ENV=test&&${additionalEnvVariables}${string}`;
} else {
- return `NODE_ENV=testing ${additionalEnvVariables} ${string}`;
+ return `NODE_ENV=test ${additionalEnvVariables} ${string}`;
}
};
@@ -65,7 +68,7 @@ gulp.task('test:prepare:mongo', (cb) => {
gulp.task('test:prepare:server', ['test:prepare:mongo'], () => {
if (!server) {
- server = exec(testBin('node ./website/src/server.js', `NODE_DB_URI=${TEST_DB_URI} PORT=${TEST_SERVER_PORT} `), (error, stdout, stderr) => {
+ server = exec(testBin(`node ./website/server/index.js`, `NODE_DB_URI=${TEST_DB_URI} PORT=${TEST_SERVER_PORT}`), (error, stdout, stderr) => {
if (error) { throw `Problem with the server: ${error}`; }
if (stderr) { console.error(stderr); }
});
@@ -101,7 +104,7 @@ gulp.task('test:common:clean', (cb) => {
});
gulp.task('test:common:watch', ['test:common:clean'], () => {
- gulp.watch(['common/script/**', 'test/common/**'], ['test:common:clean']);
+ gulp.watch(['common/script/**/*', 'test/common/**/*'], ['test:common:clean']);
});
gulp.task('test:common:safe', ['test:prepare:build'], (cb) => {
@@ -216,7 +219,7 @@ gulp.task('test:api-legacy:watch', [
'test:prepare:mongo',
'test:api-legacy:clean'
], () => {
- gulp.watch(['website/src/**', 'test/api-legacy/**'], ['test:api-legacy:clean']);
+ gulp.watch(['website/server/**', 'test/api-legacy/**'], ['test:api-legacy:clean']);
});
gulp.task('test:karma', ['test:prepare:build'], (cb) => {
@@ -262,7 +265,7 @@ gulp.task('test:e2e', ['test:prepare', 'test:prepare:server'], (cb) => {
].map(exec);
support.push(server);
- Q.all([
+ Bluebird.all([
awaitPort(TEST_SERVER_PORT),
awaitPort(4444)
]).then(() => {
@@ -283,7 +286,7 @@ gulp.task('test:e2e:safe', ['test:prepare', 'test:prepare:server'], (cb) => {
'npm run test:e2e:webdriver',
].map(exec);
- Q.all([
+ Bluebird.all([
awaitPort(TEST_SERVER_PORT),
awaitPort(4444)
]).then(() => {
@@ -306,16 +309,16 @@ gulp.task('test:e2e:safe', ['test:prepare', 'test:prepare:server'], (cb) => {
});
});
-gulp.task('test:api-v2', ['test:prepare:server'], (done) => {
-
+/*gulp.task('test:api-v2', ['test:prepare:server'], (done) => {
+ process.env.API_VERSION = 'v2';
awaitPort(TEST_SERVER_PORT).then(() => {
- runMochaTests('./test/api/v2/**/*.js', server, done)
+ runMochaTests('./test/api/v2/**//*.js', server, done)
});
});
gulp.task('test:api-v2:watch', ['test:prepare:server'], () => {
process.env.RUN_INTEGRATION_TEST_FOREVER = true;
- gulp.watch(['website/src/**', 'test/api/v2/**'], ['test:api-v2']);
+ gulp.watch(['website/server/**', 'test/api/v2/**'], ['test:api-v2']);
});
gulp.task('test:api-v2:safe', ['test:prepare:server'], (done) => {
@@ -324,7 +327,118 @@ gulp.task('test:api-v2:safe', ['test:prepare:server'], (done) => {
testBin(API_V2_TEST_COMMAND),
(err, stdout, stderr) => {
testResults.push({
- suite: 'API Specs\t',
+ suite: 'API V2 Specs\t',
+ pass: testCount(stdout, /(\d+) passing/),
+ fail: testCount(stderr, /(\d+) failing/),
+ pend: testCount(stdout, /(\d+) pending/)
+ });
+ done();
+ }
+ );
+ pipe(runner);
+ });
+});*/
+
+gulp.task('test:api-v2:integration', (done) => {
+ let runner = exec(
+ testBin('mocha test/api/v2 --recursive'),
+ {maxBuffer: 500*1024},
+ (err, stdout, stderr) => done(err)
+ )
+
+ pipe(runner);
+});
+
+gulp.task('test:api-v3:unit', (done) => {
+ let runner = exec(
+ testBin('mocha test/api/v3/unit --recursive'),
+ (err, stdout, stderr) => done(err)
+ )
+
+ pipe(runner);
+});
+
+gulp.task('test:api-v3:unit:watch', () => {
+ gulp.watch(['website/server/libs/api-v3/*', 'test/api/v3/unit/**/*', 'website/server/controllers/**/*'], ['test:api-v3:unit']);
+});
+
+gulp.task('test:api-v3:integration', (done) => {
+ let runner = exec(
+ testBin('mocha test/api/v3/integration --recursive'),
+ {maxBuffer: 500*1024},
+ (err, stdout, stderr) => done(err)
+ )
+
+ pipe(runner);
+});
+
+gulp.task('test:api-v3:integration:watch', () => {
+ gulp.watch(['website/server/controllers/api-v3/**/*', 'common/script/ops/*', 'website/server/libs/api-v3/*.js',
+ 'test/api/v3/integration/**/*'], ['test:api-v3:integration']);
+});
+
+gulp.task('test:api-v3:integration:separate-server', (done) => {
+ let runner = exec(
+ testBin('mocha test/api/v3/integration --recursive', 'LOAD_SERVER=0'),
+ {maxBuffer: 500*1024},
+ (err, stdout, stderr) => done(err)
+ )
+
+ pipe(runner);
+});
+
+gulp.task('test', (done) => {
+ runSequence(
+ 'test:common',
+ 'test:karma',
+ 'test:api-v3:unit',
+ 'test:api-v3:integration',
+ 'test:api-v2:integration',
+ done
+ );
+});
+
+gulp.task('test:api-v3', (done) => {
+ runSequence(
+ 'test:api-v3:unit',
+ 'test:api-v3:integration',
+ done
+ );
+});
+
+// Old tests tasks
+/*
+gulp.task('test:api-v3', ['test:api-v3:unit', 'test:api-v3:integration']);
+
+gulp.task('test:api-v3:watch', ['test:api-v3:unit:watch', 'test:api-v3:integration:watch']);
+
+gulp.task('test:api-v3:unit', (done) => {*/
+// runMochaTests('./test/api/v3/unit/**/*.js', null, done)
+/*});
+
+gulp.task('test:api-v3:unit:watch', () => {
+ gulp.watch(['website/server/**', 'test/api/v3/unit/**'], ['test:api-v3:unit']);
+});
+
+gulp.task('test:api-v3:integration', ['test:prepare:server'], (done) => {
+ process.env.API_VERSION = 'v3';
+ awaitPort(TEST_SERVER_PORT).then(() => {*/
+// runMochaTests('./test/api/v3/integration/**/*.js', server, done)
+/* });
+});
+
+gulp.task('test:api-v3:integration:watch', ['test:prepare:server'], () => {
+ process.env.RUN_INTEGRATION_TEST_FOREVER = true;
+ gulp.watch(['website/server/**', 'test/api/v3/integration/**'], ['test:api-v3:integration']);
+});
+
+gulp.task('test:api-v3:safe', ['test:prepare:server'], (done) => {
+ awaitPort(TEST_SERVER_PORT).then(() => {
+ let runner = exec(
+ testBin(API_V3_TEST_COMMAND),
+ (err, stdout, stderr) => {
+ testResults.push({
+ suite: 'API V3 Specs\t',
pass: testCount(stdout, /(\d+) passing/),
fail: testCount(stdout, /(\d+) failing/),
pend: testCount(stdout, /(\d+) pending/)
@@ -338,14 +452,14 @@ gulp.task('test:api-v2:safe', ['test:prepare:server'], (done) => {
gulp.task('test:all', (done) => {
runSequence(
- 'lint',
- 'test:e2e:safe',
- 'test:common:safe',
- 'test:content:safe',
+ //'test:e2e:safe',
+ //'test:common:safe',
+ //'test:content:safe',
// 'test:server_side:safe',
- 'test:karma:safe',
- 'test:api-legacy:safe',
- 'test:api-v2:safe',
+ //'test:karma:safe',
+ //'test:api-legacy:safe',
+ //'test:api-v2:safe',
+ 'test:api-v3:safe',
done);
});
@@ -386,4 +500,4 @@ gulp.task('test', ['test:all'], () => {
console.log('\n\x1b[36mThanks for helping keep Habitica clean!\x1b[0m');
process.exit();
}
-});
+});*/
diff --git a/tasks/taskHelper.js b/tasks/taskHelper.js
index 408978efd4..b83faf6af2 100644
--- a/tasks/taskHelper.js
+++ b/tasks/taskHelper.js
@@ -1,9 +1,9 @@
-import { exec } from 'child_process';
-import psTree from 'ps-tree';
-import nconf from 'nconf';
-import net from 'net';
-import Q from 'q';
-import { post } from 'superagent';
+import { exec } from 'child_process';
+import psTree from 'ps-tree';
+import nconf from 'nconf';
+import net from 'net';
+import Bluebird from 'bluebird';
+import { post } from 'superagent';
import { sync as glob } from 'glob';
import Mocha from 'mocha';
import { resolve } from 'path';
@@ -43,25 +43,24 @@ export function kill(proc) {
* has fully spun up. Optionally provide a maximum number of seconds to wait
* before failing.
*/
-export function awaitPort(port, max=60) {
- let socket, timeout, interval;
- let deferred = Q.defer();
+export function awaitPort (port, max=60) {
+ return new Bluebird((reject, resolve) => {
+ let socket, timeout, interval;
- timeout = setTimeout(() => {
- clearInterval(interval);
- deferred.reject(`Timed out after ${max} seconds`);
- }, max * 1000);
-
- interval = setInterval(() => {
- socket = net.connect({port: port}, () => {
+ timeout = setTimeout(() => {
clearInterval(interval);
- clearTimeout(timeout);
- socket.destroy();
- deferred.resolve();
- }).on('error', () => { socket.destroy });
- }, 1000);
+ reject(`Timed out after ${max} seconds`);
+ }, max * 1000);
- return deferred.promise
+ interval = setInterval(() => {
+ socket = net.connect({port: port}, () => {
+ clearInterval(interval);
+ clearTimeout(timeout);
+ socket.destroy();
+ resolve();
+ }).on('error', () => { socket.destroy });
+ }, 1000);
+ });
};
/*
diff --git a/test/.eslintrc b/test/.eslintrc
index 9ee7ce9e28..a56eb09d64 100644
--- a/test/.eslintrc
+++ b/test/.eslintrc
@@ -1,18 +1,10 @@
{
- "rules": {
- "one-var": 0,
- "func-names": 0,
- "max-nested-callbacks": 0,
- "no-unused-expressions": 0,
- "mocha/no-exclusive-tests": 2,
- "mocha/no-global-tests": 2,
- "mocha/handle-done-callback": 2
- },
- "globals": {
- "expect": true,
- "_": true,
- "sandbox": true,
- "sinon": true
- },
- "plugins": [ "mocha" ]
+ "extends": [
+ "habitrpg/mocha",
+ "habitrpg/babel"
+ ],
+ "globals": {
+ "_": true,
+ "Promise": true
+ }
}
diff --git a/test/README.md b/test/README.md
deleted file mode 100644
index 8115753727..0000000000
--- a/test/README.md
+++ /dev/null
@@ -1,5 +0,0 @@
-We need to clean up this directory. The *real* tests are in spec/ mock/ e2e/ and api.mocha.coffee. We want to:
-
-1. Move all old / deprecated tests from casper, test2, etc into spec, mock, e2e
-1. Remove dependency of api.mocha.coffee on Derby, port it to Mongoose
-1. Add better test-coverage
diff --git a/test/api-legacy/api-helper.js b/test/api-legacy/api-helper.js
index 9e61773211..1b994a80a8 100644
--- a/test/api-legacy/api-helper.js
+++ b/test/api-legacy/api-helper.js
@@ -1,3 +1,4 @@
+require('babel-core/register');
var path, superagentDefaults;
superagentDefaults = require("superagent-defaults");
@@ -5,6 +6,8 @@ superagentDefaults = require("superagent-defaults");
global.request = superagentDefaults();
global.mongoose = require("mongoose");
+var Bluebird = require('bluebird');
+mongoose.Promise = Bluebird;
global.moment = require("moment");
@@ -14,7 +17,7 @@ global._ = require("lodash");
global.shared = require("../../common");
-global.User = require("../../website/src/models/user").model;
+global.User = require("../../website/server/models/user").model;
global.chai = require("chai");
diff --git a/test/api-legacy/challenges.js b/test/api-legacy/challenges.js
index 2f264655ef..b09754bc5c 100644
--- a/test/api-legacy/challenges.js
+++ b/test/api-legacy/challenges.js
@@ -1,10 +1,10 @@
var Challenge, Group, app;
-app = require("../../website/src/server");
+app = require("../../website/server/server");
-Group = require("../../website/src/models/group").model;
+Group = require("../../website/server/models/group").model;
-Challenge = require("../../website/src/models/challenge").model;
+Challenge = require("../../website/server/models/challenge").model;
describe("Challenges", function() {
var challenge, group, updateTodo;
diff --git a/test/api-legacy/chat.js b/test/api-legacy/chat.js
index 89b32b0c54..1f2dbab487 100644
--- a/test/api-legacy/chat.js
+++ b/test/api-legacy/chat.js
@@ -2,9 +2,9 @@ var Group, app, diff;
diff = require("deep-diff");
-Group = require("../../website/src/models/group").model;
+Group = require("../../website/server/models/group").model;
-app = require("../../website/src/server");
+app = require("../../website/server/server");
describe("Chat", function() {
var chat, group;
diff --git a/test/api-legacy/coupons.js b/test/api-legacy/coupons.js
index 31d840de61..4d4e366473 100644
--- a/test/api-legacy/coupons.js
+++ b/test/api-legacy/coupons.js
@@ -1,8 +1,8 @@
var Coupon, app, makeSudoUser;
-app = require("../../website/src/server");
+app = require("../../website/server/server");
-Coupon = require("../../website/src/models/coupon").model;
+Coupon = require("../../website/server/models/coupon").model;
makeSudoUser = function(usr, cb) {
return registerNewUser(function() {
diff --git a/test/api-legacy/inAppPurchases.js b/test/api-legacy/inAppPurchases.js
index d4182e437c..96809a7717 100644
--- a/test/api-legacy/inAppPurchases.js
+++ b/test/api-legacy/inAppPurchases.js
@@ -1,12 +1,12 @@
var app, iapMock, inApp, rewire, sinon;
-app = require('../../website/src/server');
+app = require('../../website/server/server');
rewire = require('rewire');
sinon = require('sinon');
-inApp = rewire('../../website/src/controllers/payments/iap');
+inApp = rewire('../../website/server/controllers/payments/iap');
iapMock = {};
diff --git a/test/api-legacy/party.js b/test/api-legacy/party.js
index 8ea8188713..2f98b67bde 100644
--- a/test/api-legacy/party.js
+++ b/test/api-legacy/party.js
@@ -2,9 +2,9 @@ var Group, app, diff;
diff = require("deep-diff");
-Group = require("../../website/src/models/group").model;
+Group = require("../../website/server/models/group").model;
-app = require("../../website/src/server");
+app = require("../../website/server/server");
describe("Party", function() {
return context("Quests", function() {
diff --git a/test/api-legacy/pushNotifications.js b/test/api-legacy/pushNotifications.js
index ce3d9672f8..7f98ddccfa 100644
--- a/test/api-legacy/pushNotifications.js
+++ b/test/api-legacy/pushNotifications.js
@@ -1,6 +1,6 @@
var app, rewire, sinon;
-app = require("../../website/src/server");
+app = require("../../website/server/server");
rewire = require('rewire');
@@ -21,7 +21,7 @@ describe("Push-Notifications", function() {
});
context("Challenges", function() {
var challengeMock, challenges, userMock;
- challenges = rewire("../../website/src/controllers/api-v2/challenges");
+ challenges = rewire("../../website/server/controllers/api-v2/challenges");
challenges.__set__('pushNotify', pushSpy);
challengeMock = {
findById: function(arg, cb) {
@@ -76,7 +76,7 @@ describe("Push-Notifications", function() {
context("Groups", function() {
var groups, recipient;
recipient = null;
- groups = rewire("../../website/src/controllers/api-v2/groups");
+ groups = rewire("../../website/server/controllers/api-v2/groups");
groups.__set__('pushNotify', pushSpy);
before(function(done) {
return registerNewUser(function(err, _user) {
@@ -304,7 +304,7 @@ describe("Push-Notifications", function() {
});
context("sending gems from balance", function() {
var members;
- members = rewire("../../website/src/controllers/api-v2/members");
+ members = rewire("../../website/server/controllers/api-v2/members");
members.sendMessage = function() {
return true;
};
@@ -342,7 +342,7 @@ describe("Push-Notifications", function() {
});
return describe("Purchases", function() {
var membersMock, payments;
- payments = rewire("../../website/src/controllers/payments");
+ payments = rewire("../../website/server/controllers/payments");
payments.__set__('pushNotify', pushSpy);
membersMock = {
sendMessage: function() {
diff --git a/test/api-legacy/score.js b/test/api-legacy/score.js
index 8c6906acfb..af31a4f334 100644
--- a/test/api-legacy/score.js
+++ b/test/api-legacy/score.js
@@ -1,4 +1,4 @@
-require("../../website/src/server");
+require("../../website/server/server");
describe("Score", function() {
before(function(done) {
diff --git a/test/api-legacy/subscriptions.js b/test/api-legacy/subscriptions.js
index 9d8624cb73..73b55039df 100644
--- a/test/api-legacy/subscriptions.js
+++ b/test/api-legacy/subscriptions.js
@@ -1,8 +1,8 @@
var app, payments;
-payments = require("../../website/src/controllers/payments");
+payments = require("../../website/server/controllers/payments");
-app = require("../../website/src/server");
+app = require("../../website/server/server");
describe("Subscriptions", function() {
before(function(done) {
diff --git a/test/api-legacy/todos.js b/test/api-legacy/todos.js
index 5285847fd1..b72ea57223 100644
--- a/test/api-legacy/todos.js
+++ b/test/api-legacy/todos.js
@@ -1,4 +1,4 @@
-require("../../website/src/server");
+require("../../website/server/server");
describe("Todos", function() {
before(function(done) {
diff --git a/test/api/README.md b/test/api/README.md
index 81e7c2d54b..1f5c98abb3 100644
--- a/test/api/README.md
+++ b/test/api/README.md
@@ -1,5 +1,7 @@
# So you want to write API integration tests?
+@TODO rewrite
+
That's great! This README will serve as a quick primer for style conventions and practices for these tests.
## What is this?
@@ -73,7 +75,7 @@ POST-groups_id_leave.test.js
To mitigate [callback hell](http://callbackhell.com/) :imp:, we've written a helper method to generate a user object that can make http requests that [return promises](https://babeljs.io/docs/learn-es2015/#promises). This makes it very easy to chain together commands. All you need to do to make a subsequent request is return another promise and then call `.then((result) => {})` on the surrounding block, like so:
```js
-it('does something', () => {
+it('does something', () => {
let user;
return generateUser().then((_user) => { // We return the initial promise so this test can be run asyncronously
@@ -97,7 +99,7 @@ it('does something', () => {
If the test is simple, you can use the [chai-as-promised](http://chaijs.com/plugins/chai-as-promised) `return expect(somePromise).to.eventually` syntax to make your assertion.
```js
-it('makes the party creator the leader automatically', () => {
+it('makes the party creator the leader automatically', () => {
return expect(user.post('/groups', {
type: 'party',
})).to.eventually.have.deep.property('leader._id', user._id);
@@ -107,7 +109,7 @@ it('makes the party creator the leader automatically', () => {
If the test is checking that the request returns an error, use the `.eventually.be.rejected.and.eql` syntax.
```js
-it('returns an error', () => {
+it('returns an error', () => {
return expect(user.get('/groups/id-of-a-party-that-user-does-not-belong-to'))
.to.eventually.be.rejected.and.eql({
code: 404,
diff --git a/test/api/v2/groups/GET-groups.test.js b/test/api/v2/groups/GET-groups.test.js
index 203fdd0acc..d941b2533e 100644
--- a/test/api/v2/groups/GET-groups.test.js
+++ b/test/api/v2/groups/GET-groups.test.js
@@ -3,12 +3,15 @@ import {
generateUser,
resetHabiticaDB,
} from '../../../helpers/api-integration/v2';
+import {
+ TAVERN_ID,
+} from '../../../../website/server/models/group';
describe('GET /groups', () => {
const NUMBER_OF_PUBLIC_GUILDS = 3;
- const NUMBER_OF_USERS_GUILDS = 2;
let user;
+ let leader;
before(async () => {
// Set up a world with a mixture of public and private guilds
@@ -16,7 +19,7 @@ describe('GET /groups', () => {
await resetHabiticaDB();
user = await generateUser();
- let leader = await generateUser({ balance: 10 });
+ leader = await generateUser({ balance: 10 });
await generateGroup(leader, {
name: 'public guild - is member',
@@ -68,7 +71,7 @@ describe('GET /groups', () => {
await expect(user.get('/groups', null, {type: 'tavern'}))
.to.eventually.have.a.lengthOf(1)
.and.to.have.deep.property('[0]')
- .and.to.have.property('_id', 'habitrpg');
+ .and.to.have.property('_id', TAVERN_ID);
});
});
@@ -90,8 +93,8 @@ describe('GET /groups', () => {
context('guilds passed in as query', () => {
it('returns all guilds user is a part of ', async () => {
- await expect(user.get('/groups', null, {type: 'guilds'}))
- .to.eventually.have.a.lengthOf(NUMBER_OF_USERS_GUILDS);
+ await expect(leader.get('/groups', null, {type: 'guilds'}))
+ .to.eventually.have.a.lengthOf(4);
});
});
});
diff --git a/test/api/v2/groups/POST-groups_id_invite.test.js b/test/api/v2/groups/POST-groups_id_invite.test.js
index 5acf6e637a..ef768dbf7e 100644
--- a/test/api/v2/groups/POST-groups_id_invite.test.js
+++ b/test/api/v2/groups/POST-groups_id_invite.test.js
@@ -27,8 +27,8 @@ describe('POST /groups/:id/invite', () => {
await inviter.post(`/groups/${group._id}/invite`, {
uuids: [invitee._id],
});
- await group.sync();
- expect(group.invites).to.include(invitee._id);
+ group = await inviter.get(`/groups/${group._id}`);
+ expect(_.find(group.invites, {_id: invitee._id})._id).to.exists;
});
});
});
@@ -53,8 +53,8 @@ describe('POST /groups/:id/invite', () => {
await inviter.post(`/groups/${group._id}/invite`, {
uuids: [invitee._id],
});
- await group.sync();
- expect(group.invites).to.include(invitee._id);
+ group = await inviter.get(`/groups/${group._id}`);
+ expect(_.find(group.invites, {_id: invitee._id})._id).to.exists;
});
});
});
diff --git a/test/api/v2/groups/POST-groups_id_join.test.js b/test/api/v2/groups/POST-groups_id_join.test.js
index 250fd4bbf2..cf216354a0 100644
--- a/test/api/v2/groups/POST-groups_id_join.test.js
+++ b/test/api/v2/groups/POST-groups_id_join.test.js
@@ -30,9 +30,8 @@ describe('POST /groups/:id/join', () => {
it(`allows user to join a ${groupType}`, async () => {
await invitee.post(`/groups/${group._id}/join`);
- await group.sync();
-
- expect(group.members).to.include(invitee._id);
+ group = await invitee.get(`/groups/${group._id}`);
+ expect(_.find(group.members, {_id: invitee._id})._id).to.exists;
});
});
});
@@ -78,9 +77,9 @@ describe('POST /groups/:id/join', () => {
it('allows user to join a public guild', async () => {
await user.post(`/groups/${group._id}/join`);
- await group.sync();
+ group = await user.get(`/groups/${group._id}`);
- expect(group.members).to.include(user._id);
+ expect(_.find(group.members, {_id: user._id})._id).to.exists;
});
});
@@ -103,9 +102,9 @@ describe('POST /groups/:id/join', () => {
it('makes the joining user the leader', async () => {
await user.post(`/groups/${group._id}/join`);
- await group.sync();
+ group = await user.get(`/groups/${group._id}`);
- await expect(group.leader).to.eql(user._id);
+ expect(group.leader._id).to.eql(user._id);
});
});
});
diff --git a/test/api/v2/groups/POST-groups_id_leave.test.js b/test/api/v2/groups/POST-groups_id_leave.test.js
index f7e3e0e9e6..f6511e941c 100644
--- a/test/api/v2/groups/POST-groups_id_leave.test.js
+++ b/test/api/v2/groups/POST-groups_id_leave.test.js
@@ -28,9 +28,9 @@ describe('POST /groups/:id/leave', () => {
it('leaves the group', async () => {
await user.post(`/groups/${group._id}/leave`);
- await group.sync();
+ await user.sync();
- expect(group.members).to.not.include(user._id);
+ expect(user.guilds).to.not.include(group._id);
});
});
@@ -102,8 +102,8 @@ describe('POST /groups/:id/leave', () => {
it('deletes the group invitations from users', async () => {
await user.post(`/groups/${group._id}/leave`);
- await expect(invitee1.get(`/user`)).to.eventually.have.deep.property('invitations.guilds').and.to.be.empty;
- await expect(invitee2.get(`/user`)).to.eventually.have.deep.property('invitations.guilds').and.to.be.empty;
+ await expect(invitee1.get('/user')).to.eventually.have.deep.property('invitations.guilds').and.to.be.empty;
+ await expect(invitee2.get('/user')).to.eventually.have.deep.property('invitations.guilds').and.to.be.empty;
});
});
@@ -129,8 +129,8 @@ describe('POST /groups/:id/leave', () => {
it('deletes the group invitations from users', async () => {
await user.post(`/groups/${group._id}/leave`);
- await expect(invitee1.get(`/user`)).to.eventually.have.deep.property('invitations.party').and.to.be.empty;
- await expect(invitee2.get(`/user`)).to.eventually.have.deep.property('invitations.party').and.to.be.empty;
+ await expect(invitee1.get('/user')).to.eventually.have.deep.property('invitations.party').and.to.be.empty;
+ await expect(invitee2.get('/user')).to.eventually.have.deep.property('invitations.party').and.to.be.empty;
});
});
});
diff --git a/test/api/v2/user/DELETE-user.test.js b/test/api/v2/user/DELETE-user.test.js
index db0eeca1c8..8d28a07b78 100644
--- a/test/api/v2/user/DELETE-user.test.js
+++ b/test/api/v2/user/DELETE-user.test.js
@@ -4,7 +4,11 @@ import {
generateGroup,
generateUser,
} from '../../../helpers/api-integration/v2';
-import { find } from 'lodash';
+import {
+ find,
+ map,
+} from 'lodash';
+import Bluebird from 'bluebird';
describe('DELETE /user', () => {
let user;
@@ -19,6 +23,18 @@ describe('DELETE /user', () => {
})).to.eventually.eql(false);
});
+ it('deletes the user\'s tasks', async () => {
+ // gets the user's todos ids
+ let ids = user.todos.map(todo => todo._id);
+ expect(ids.length).to.be.above(0); // make sure the user has some task to delete
+
+ await user.del('/user');
+
+ await Bluebird.all(map(ids, id => {
+ return expect(checkExistence('tasks', id)).to.eventually.eql(false);
+ }));
+ });
+
context('user has active subscription', () => {
it('does not delete account');
});
diff --git a/test/api/v2/user/batch-update/POST-user_batch-update.test.js b/test/api/v2/user/batch-update/POST-user_batch-update.test.js
index 0b1f244ff7..f64cbc7f7b 100644
--- a/test/api/v2/user/batch-update/POST-user_batch-update.test.js
+++ b/test/api/v2/user/batch-update/POST-user_batch-update.test.js
@@ -31,7 +31,7 @@ describe('POST /user/batch-update', () => {
});
});
- context('development only operations', () => { // These tests will fail if your NODE_ENV is set to 'development' instead of 'testing'
+ xcontext('development only operations', () => { // These tests will fail if your NODE_ENV is set to 'development' instead of 'testing'
let protectedOperations = {
'Add Ten Gems': 'addTenGems',
'Add Hourglass': 'addHourglass',
diff --git a/test/api/v2/user/pushDevice/POST-pushDevice.test.js b/test/api/v2/user/pushDevice/POST-pushDevice.test.js
index 97cfc4dbb9..c0b5e9be72 100644
--- a/test/api/v2/user/pushDevice/POST-pushDevice.test.js
+++ b/test/api/v2/user/pushDevice/POST-pushDevice.test.js
@@ -2,7 +2,7 @@ import {
generateUser,
} from '../../../../helpers/api-integration/v2';
-describe('POST /user/pushDevice', () => {
+xdescribe('POST /user/pushDevice', () => {
let user;
beforeEach(async () => {
diff --git a/test/api/v2/user/tasks/GET-tasks.test.js b/test/api/v2/user/tasks/GET-tasks.test.js
index 5cca2f2696..1506f4c2f0 100644
--- a/test/api/v2/user/tasks/GET-tasks.test.js
+++ b/test/api/v2/user/tasks/GET-tasks.test.js
@@ -6,22 +6,15 @@ describe('GET /user/tasks/', () => {
let user;
beforeEach(async () => {
- return generateUser({
- dailys: [
- {text: 'daily', type: 'daily'},
- {text: 'daily', type: 'daily'},
- {text: 'daily', type: 'daily'},
- {text: 'daily', type: 'daily'},
- ],
- }).then((_user) => {
+ return generateUser().then((_user) => {
user = _user;
});
});
it('gets all tasks', async () => {
- return user.get(`/user/tasks/`).then((tasks) => {
+ return user.get('/user/tasks/').then((tasks) => {
expect(tasks).to.be.an('array');
- expect(tasks.length).to.be.greaterThan(3);
+ expect(tasks.length).to.equal(1);
let task = tasks[0];
expect(task.id).to.exist;
diff --git a/test/api/v2/user/tasks/POST-clear-completed.test.js b/test/api/v2/user/tasks/POST-clear-completed.test.js
new file mode 100644
index 0000000000..40e6cbd5cf
--- /dev/null
+++ b/test/api/v2/user/tasks/POST-clear-completed.test.js
@@ -0,0 +1,26 @@
+import {
+ generateUser,
+} from '../../../../helpers/api-integration/v2';
+
+describe('POST /user/tasks/clear-completed', () => {
+ let user;
+
+ beforeEach(async () => {
+ return generateUser().then((_user) => {
+ user = _user;
+ });
+ });
+
+ it('removes all completed todos', async () => {
+ let toComplete = await user.post('/user/tasks', {
+ type: 'todo',
+ text: 'done',
+ });
+
+ await user.post(`/user/tasks/${toComplete._id}/up`);
+
+ let todos = await user.get('/user/tasks?type=todo');
+ let uncomplete = await user.post('/user/tasks/clear-completed');
+ expect(todos.length).to.equal(uncomplete.length + 1);
+ });
+});
diff --git a/test/api/v2/user/tasks/POST-tasks.test.js b/test/api/v2/user/tasks/POST-tasks.test.js
index 5f43224803..4fe4c9f5ea 100644
--- a/test/api/v2/user/tasks/POST-tasks.test.js
+++ b/test/api/v2/user/tasks/POST-tasks.test.js
@@ -35,7 +35,7 @@ describe('POST /user/tasks', () => {
});
});
- it('does not create a task with an id that already exists', async () => {
+ xit('does not create a task with an id that already exists', async () => {
let todo = user.todos[0];
return expect(user.post('/user/tasks', {
diff --git a/test/api/v2/user/tasks/PUT-tasks_id.test.js b/test/api/v2/user/tasks/PUT-tasks_id.test.js
index 037322a6b7..ee41a4fe68 100644
--- a/test/api/v2/user/tasks/PUT-tasks_id.test.js
+++ b/test/api/v2/user/tasks/PUT-tasks_id.test.js
@@ -28,18 +28,16 @@ describe('PUT /user/tasks/:id', () => {
});
});
- it('updates text, attribute, priority, value and notes', async () => {
+ it('updates text, attribute, priority and notes', async () => {
return user.put(`/user/tasks/${task.id}`, {
text: 'new text',
notes: 'new notes',
- value: 10000,
- priority: 0.5,
+ priority: 0.1,
attribute: 'str',
}).then((updatedTask) => {
expect(updatedTask.text).to.eql('new text');
expect(updatedTask.notes).to.eql('new notes');
- expect(updatedTask.value).to.eql(10000);
- expect(updatedTask.priority).to.eql(0.5);
+ expect(updatedTask.priority).to.eql(0.1);
expect(updatedTask.attribute).to.eql('str');
});
});
diff --git a/test/api/v3/README.md b/test/api/v3/README.md
new file mode 100644
index 0000000000..ad9b55a32c
--- /dev/null
+++ b/test/api/v3/README.md
@@ -0,0 +1,4 @@
+# How to run tests:
+
+1. `npm test` is equivalent to `gulp test:api-v3` which will run, in order, `gulp lint`, `gulp test:api-v3:unit` and `gulp test:api-v3:integration`. If one of these fails, the whole `npm test` command blocks and fails. Each of these commands can also be run as a standalone command.
+2. To run the server and the integrations tests in two different terminals (to better inspect the output in the server) run `npm start` in one and `npm test:api-v3:integration:separate-server` in the other
diff --git a/test/api/v3/integration/challenges/DELETE-challenges_challengeId.test.js b/test/api/v3/integration/challenges/DELETE-challenges_challengeId.test.js
new file mode 100644
index 0000000000..45dc91758a
--- /dev/null
+++ b/test/api/v3/integration/challenges/DELETE-challenges_challengeId.test.js
@@ -0,0 +1,94 @@
+import {
+ generateUser,
+ generateChallenge,
+ createAndPopulateGroup,
+ sleep,
+ checkExistence,
+ translate as t,
+} from '../../../../helpers/api-v3-integration.helper';
+import { v4 as generateUUID } from 'uuid';
+
+describe('DELETE /challenges/:challengeId', () => {
+ it('returns error when challengeId is not a valid UUID', async () => {
+ let user = await generateUser();
+ await expect(user.del('/challenges/test')).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('invalidReqParams'),
+ });
+ });
+
+ it('returns error when challengeId is not for a valid challenge', async () => {
+ let user = await generateUser();
+
+ await expect(user.del(`/challenges/${generateUUID()}`)).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('challengeNotFound'),
+ });
+ });
+
+ context('Deleting a valid challenge', () => {
+ let groupLeader;
+ let group;
+ let challenge;
+ let taskText = 'A challenge task text';
+
+ beforeEach(async () => {
+ let populatedGroup = await createAndPopulateGroup();
+
+ groupLeader = populatedGroup.groupLeader;
+ group = populatedGroup.group;
+
+ challenge = await generateChallenge(groupLeader, group);
+
+ await groupLeader.post(`/tasks/challenge/${challenge._id}`, [
+ {type: 'habit', text: taskText},
+ ]);
+
+ await challenge.sync();
+ });
+
+ it('returns an error when user doesn\'t have permissions to delete the challenge', async () => {
+ let user = await generateUser();
+
+ await expect(user.del(`/challenges/${challenge._id}`)).to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('onlyLeaderDeleteChal'),
+ });
+ });
+
+ it('deletes challenge', async () => {
+ await groupLeader.del(`/challenges/${challenge._id}`);
+
+ await sleep(0.5);
+
+ await expect(checkExistence('challenges', challenge._id)).to.eventually.equal(false);
+ });
+
+ it('refunds gems to group leader', async () => {
+ let oldBalance = (await groupLeader.sync()).balance;
+
+ await groupLeader.del(`/challenges/${challenge._id}`);
+
+ await sleep(0.5);
+
+ await expect(groupLeader.sync()).to.eventually.have.property('balance', oldBalance + challenge.prize / 4);
+ });
+
+ it('sets broken and doesn\'t set winner flags for user\'s challenge tasks', async () => {
+ await groupLeader.del(`/challenges/${challenge._id}`);
+
+ await sleep(0.5);
+
+ let tasks = await groupLeader.get('/tasks/user');
+ let testTask = _.find(tasks, (task) => {
+ return task.text === taskText;
+ });
+
+ expect(testTask.challenge.broken).to.eql('CHALLENGE_DELETED');
+ expect(testTask.challenge.winner).to.be.null;
+ });
+ });
+});
diff --git a/test/api/v3/integration/challenges/GET-challenges_challengeId.test.js b/test/api/v3/integration/challenges/GET-challenges_challengeId.test.js
new file mode 100644
index 0000000000..33b329300a
--- /dev/null
+++ b/test/api/v3/integration/challenges/GET-challenges_challengeId.test.js
@@ -0,0 +1,142 @@
+import {
+ generateUser,
+ createAndPopulateGroup,
+ generateChallenge,
+ translate as t,
+} from '../../../../helpers/api-v3-integration.helper';
+import { v4 as generateUUID } from 'uuid';
+
+describe('GET /challenges/:challengeId', () => {
+ it('fails if challenge doesn\'t exists', async () => {
+ let user = await generateUser();
+ await expect(user.get(`/challenges/${generateUUID()}`)).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('challengeNotFound'),
+ });
+ });
+
+ context('public guild', () => {
+ let groupLeader;
+ let group;
+ let challenge;
+ let user;
+
+ beforeEach(async () => {
+ user = await generateUser();
+
+ let populatedGroup = await createAndPopulateGroup({
+ groupDetails: {type: 'guild', privacy: 'public'},
+ });
+
+ groupLeader = populatedGroup.groupLeader;
+ group = populatedGroup.group;
+
+ challenge = await generateChallenge(groupLeader, group);
+ });
+
+ it('should return challenge data', async () => {
+ let chal = await user.get(`/challenges/${challenge._id}`);
+ expect(chal.memberCount).to.equal(challenge.memberCount);
+ expect(chal.name).to.equal(challenge.name);
+ expect(chal._id).to.equal(challenge._id);
+
+ expect(chal.leader).to.eql({
+ _id: groupLeader._id,
+ id: groupLeader._id,
+ profile: {name: groupLeader.profile.name},
+ });
+ expect(chal.group).to.eql(_.pick(group, ['_id', 'id', 'name', 'type', 'privacy']));
+ });
+ });
+
+ context('private guild', () => {
+ let groupLeader;
+ let group;
+ let challenge;
+ let members;
+ let user;
+
+ beforeEach(async () => {
+ user = await generateUser();
+
+ let populatedGroup = await createAndPopulateGroup({
+ groupDetails: {type: 'guild', privacy: 'private'},
+ members: 1,
+ });
+
+ groupLeader = populatedGroup.groupLeader;
+ group = populatedGroup.group;
+ members = populatedGroup.members;
+
+ challenge = await generateChallenge(groupLeader, group);
+ await members[0].post(`/challenges/${challenge._id}/join`);
+ });
+
+ it('fails if user doesn\'t have access to the challenge', async () => {
+ await expect(user.get(`/challenges/${challenge._id}`)).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('challengeNotFound'),
+ });
+ });
+
+ it('should return challenge data', async () => {
+ let chal = await members[0].get(`/challenges/${challenge._id}`);
+ expect(chal.name).to.equal(challenge.name);
+ expect(chal._id).to.equal(challenge._id);
+
+ expect(chal.leader).to.eql({
+ _id: groupLeader._id,
+ id: groupLeader._id,
+ profile: {name: groupLeader.profile.name},
+ });
+ expect(chal.group).to.eql(_.pick(group, ['_id', 'id', 'name', 'type', 'privacy']));
+ });
+ });
+
+ context('party', () => {
+ let groupLeader;
+ let group;
+ let challenge;
+ let members;
+ let user;
+
+ beforeEach(async () => {
+ user = await generateUser();
+
+ let populatedGroup = await createAndPopulateGroup({
+ groupDetails: {type: 'party'},
+ members: 1,
+ });
+
+ groupLeader = populatedGroup.groupLeader;
+ group = populatedGroup.group;
+ members = populatedGroup.members;
+
+ challenge = await generateChallenge(groupLeader, group);
+ await members[0].post(`/challenges/${challenge._id}/join`);
+ });
+
+ it('fails if user doesn\'t have access to the challenge', async () => {
+ await expect(user.get(`/challenges/${challenge._id}`)).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('challengeNotFound'),
+ });
+ });
+
+ it('should return challenge data', async () => {
+ let chal = await members[0].get(`/challenges/${challenge._id}`);
+ expect(chal.name).to.equal(challenge.name);
+ expect(chal._id).to.equal(challenge._id);
+
+ expect(chal.leader).to.eql({
+ _id: groupLeader._id,
+ id: groupLeader.id,
+ profile: {name: groupLeader.profile.name},
+ });
+ expect(chal.group).to.eql(_.pick(group, ['_id', 'id', 'name', 'type', 'privacy']));
+ });
+ });
+});
diff --git a/test/api/v3/integration/challenges/GET-challenges_challengeId_export_csv.test.js b/test/api/v3/integration/challenges/GET-challenges_challengeId_export_csv.test.js
new file mode 100644
index 0000000000..2b98af9579
--- /dev/null
+++ b/test/api/v3/integration/challenges/GET-challenges_challengeId_export_csv.test.js
@@ -0,0 +1,74 @@
+import {
+ generateUser,
+ createAndPopulateGroup,
+ generateChallenge,
+ translate as t,
+ sleep,
+} from '../../../../helpers/api-v3-integration.helper';
+import { v4 as generateUUID } from 'uuid';
+
+describe('GET /challenges/:challengeId/export/csv', () => {
+ let groupLeader;
+ let group;
+ let challenge;
+ let members;
+ let user;
+
+ beforeEach(async () => {
+ let populatedGroup = await createAndPopulateGroup({
+ members: 3,
+ });
+
+ groupLeader = populatedGroup.groupLeader;
+ group = populatedGroup.group;
+ members = populatedGroup.members;
+
+ challenge = await generateChallenge(groupLeader, group);
+ await members[0].post(`/challenges/${challenge._id}/join`);
+ await members[1].post(`/challenges/${challenge._id}/join`);
+ await members[2].post(`/challenges/${challenge._id}/join`);
+
+ await groupLeader.post(`/tasks/challenge/${challenge._id}`, [
+ {type: 'habit', text: 'Task 1'},
+ {type: 'todo', text: 'Task 2'},
+ ]);
+ await sleep(0.5); // Make sure tasks are synced to the users
+ await members[0].sync();
+ await members[1].sync();
+ await members[2].sync();
+ });
+
+ it('fails if challenge doesn\'t exists', async () => {
+ user = await generateUser();
+ user.get('/user');
+ await expect(user.get(`/challenges/${generateUUID()}/export/csv`)).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('challengeNotFound'),
+ });
+ });
+
+ it('fails if user doesn\'t have access to the challenge', async () => {
+ user = await generateUser();
+ user.get('/user');
+
+ await expect(user.get(`/challenges/${challenge._id}/export/csv`)).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('challengeNotFound'),
+ });
+ });
+
+ it('should return a valid CSV file with export data', async () => {
+ let res = await members[0].get(`/challenges/${challenge._id}/export/csv`);
+ let sortedMembers = _.sortBy([members[0], members[1], members[2], groupLeader], '_id');
+ let splitRes = res.split('\n');
+
+ expect(splitRes[0]).to.equal('UUID,name,Task,Value,Notes,Task,Value,Notes');
+ expect(splitRes[1]).to.equal(`${sortedMembers[0]._id},${sortedMembers[0].profile.name},habit:Task 1,0,,todo:Task 2,0,`);
+ expect(splitRes[2]).to.equal(`${sortedMembers[1]._id},${sortedMembers[1].profile.name},habit:Task 1,0,,todo:Task 2,0,`);
+ expect(splitRes[3]).to.equal(`${sortedMembers[2]._id},${sortedMembers[2].profile.name},habit:Task 1,0,,todo:Task 2,0,`);
+ expect(splitRes[4]).to.equal(`${sortedMembers[3]._id},${sortedMembers[3].profile.name},habit:Task 1,0,,todo:Task 2,0,`);
+ expect(splitRes[5]).to.equal('');
+ });
+});
diff --git a/test/api/v3/integration/challenges/GET-challenges_challengeId_members.test.js b/test/api/v3/integration/challenges/GET-challenges_challengeId_members.test.js
new file mode 100644
index 0000000000..58b6583626
--- /dev/null
+++ b/test/api/v3/integration/challenges/GET-challenges_challengeId_members.test.js
@@ -0,0 +1,145 @@
+import {
+ generateUser,
+ generateGroup,
+ generateChallenge,
+ translate as t,
+} from '../../../../helpers/api-v3-integration.helper';
+import { v4 as generateUUID } from 'uuid';
+
+describe('GET /challenges/:challengeId/members', () => {
+ let user;
+
+ beforeEach(async () => {
+ user = await generateUser();
+ });
+
+ it('validates optional req.query.lastId to be an UUID', async () => {
+ await expect(user.get(`/challenges/${generateUUID()}/members?lastId=invalidUUID`)).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('invalidReqParams'),
+ });
+ });
+
+ it('fails if challenge doesn\'t exists', async () => {
+ await expect(user.get(`/challenges/${generateUUID()}/members`)).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('challengeNotFound'),
+ });
+ });
+
+ it('fails if user doesn\'t have access to the challenge', async () => {
+ let group = await generateGroup(user);
+ let challenge = await generateChallenge(user, group);
+ let anotherUser = await generateUser();
+
+ await expect(anotherUser.get(`/challenges/${challenge._id}/members`)).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('challengeNotFound'),
+ });
+ });
+
+ it('works with challenges belonging to public guild', async () => {
+ let leader = await generateUser({balance: 4});
+ let group = await generateGroup(leader, {type: 'guild', privacy: 'public', name: generateUUID()});
+ let challenge = await generateChallenge(leader, group);
+ let res = await user.get(`/challenges/${challenge._id}/members`);
+ expect(res[0]).to.eql({
+ _id: leader._id,
+ id: leader._id,
+ profile: {name: leader.profile.name},
+ });
+ expect(res[0]).to.have.all.keys(['_id', 'id', 'profile']);
+ expect(res[0].profile).to.have.all.keys(['name']);
+ });
+
+ it('populates only some fields', async () => {
+ let anotherUser = await generateUser({balance: 3});
+ let group = await generateGroup(anotherUser, {type: 'guild', privacy: 'public', name: generateUUID()});
+ let challenge = await generateChallenge(anotherUser, group);
+ let res = await user.get(`/challenges/${challenge._id}/members`);
+ expect(res[0]).to.eql({
+ _id: anotherUser._id,
+ id: anotherUser._id,
+ profile: {name: anotherUser.profile.name},
+ });
+ expect(res[0]).to.have.all.keys(['_id', 'id', 'profile']);
+ expect(res[0].profile).to.have.all.keys(['name']);
+ });
+
+ it('returns only first 30 members if req.query.includeAllMembers is not true', async () => {
+ let group = await generateGroup(user, {type: 'party', name: generateUUID()});
+ let challenge = await generateChallenge(user, group);
+
+ let usersToGenerate = [];
+ for (let i = 0; i < 31; i++) {
+ usersToGenerate.push(generateUser({challenges: [challenge._id]}));
+ }
+ await Promise.all(usersToGenerate);
+
+ let res = await user.get(`/challenges/${challenge._id}/members?includeAllMembers=not-true`);
+ expect(res.length).to.equal(30);
+ res.forEach(member => {
+ expect(member).to.have.all.keys(['_id', 'id', 'profile']);
+ expect(member.profile).to.have.all.keys(['name']);
+ });
+ });
+
+ it('returns only first 30 members if req.query.includeAllMembers is not defined', async () => {
+ let group = await generateGroup(user, {type: 'party', name: generateUUID()});
+ let challenge = await generateChallenge(user, group);
+
+ let usersToGenerate = [];
+ for (let i = 0; i < 31; i++) {
+ usersToGenerate.push(generateUser({challenges: [challenge._id]}));
+ }
+ await Promise.all(usersToGenerate);
+
+ let res = await user.get(`/challenges/${challenge._id}/members`);
+ expect(res.length).to.equal(30);
+ res.forEach(member => {
+ expect(member).to.have.all.keys(['_id', 'id', 'profile']);
+ expect(member.profile).to.have.all.keys(['name']);
+ });
+ });
+
+ it('returns all members if req.query.includeAllMembers is true', async () => {
+ let group = await generateGroup(user, {type: 'party', name: generateUUID()});
+ let challenge = await generateChallenge(user, group);
+
+ let usersToGenerate = [];
+ for (let i = 0; i < 31; i++) {
+ usersToGenerate.push(generateUser({challenges: [challenge._id]}));
+ }
+ await Promise.all(usersToGenerate);
+
+ let res = await user.get(`/challenges/${challenge._id}/members?includeAllMembers=true`);
+ expect(res.length).to.equal(32);
+ res.forEach(member => {
+ expect(member).to.have.all.keys(['_id', 'id', 'profile']);
+ expect(member.profile).to.have.all.keys(['name']);
+ });
+ });
+
+ it('supports using req.query.lastId to get more members', async () => {
+ let group = await generateGroup(user, {type: 'party', name: generateUUID()});
+ let challenge = await generateChallenge(user, group);
+
+ let usersToGenerate = [];
+ for (let i = 0; i < 57; i++) {
+ usersToGenerate.push(generateUser({challenges: [challenge._id]}));
+ }
+ let generatedUsers = await Promise.all(usersToGenerate); // Group has 59 members (1 is the leader)
+ let expectedIds = [user._id].concat(generatedUsers.map(generatedUser => generatedUser._id));
+
+ let res = await user.get(`/challenges/${challenge._id}/members`);
+ expect(res.length).to.equal(30);
+ let res2 = await user.get(`/challenges/${challenge._id}/members?lastId=${res[res.length - 1]._id}`);
+ expect(res2.length).to.equal(28);
+
+ let resIds = res.concat(res2).map(member => member._id);
+ expect(resIds).to.eql(expectedIds.sort());
+ });
+});
diff --git a/test/api/v3/integration/challenges/GET-challenges_challengeId_members_memberId.test.js b/test/api/v3/integration/challenges/GET-challenges_challengeId_members_memberId.test.js
new file mode 100644
index 0000000000..c47e1d4ad8
--- /dev/null
+++ b/test/api/v3/integration/challenges/GET-challenges_challengeId_members_memberId.test.js
@@ -0,0 +1,107 @@
+import {
+ generateUser,
+ generateChallenge,
+ generateGroup,
+ translate as t,
+} from '../../../../helpers/api-v3-integration.helper';
+import { v4 as generateUUID } from 'uuid';
+
+describe('GET /challenges/:challengeId/members/:memberId', () => {
+ let user;
+
+ beforeEach(async () => {
+ user = await generateUser();
+ });
+
+ it('validates req.params.memberId to be an UUID', async () => {
+ await expect(user.get(`/challenges/invalidUUID/members/${generateUUID()}`)).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('invalidReqParams'),
+ });
+ });
+
+ it('validates req.params.memberId to be an UUID', async () => {
+ await expect(user.get(`/challenges/${generateUUID()}/members/invalidUUID`)).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('invalidReqParams'),
+ });
+ });
+
+ it('fails if member doesn\'t exists', async () => {
+ let userId = generateUUID();
+ await expect(user.get(`/challenges/${generateUUID()}/members/${userId}`)).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('userWithIDNotFound', {userId}),
+ });
+ });
+
+ it('fails if challenge doesn\'t exists', async () => {
+ let member = await generateUser();
+ await expect(user.get(`/challenges/${generateUUID()}/members/${member._id}`)).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('challengeNotFound'),
+ });
+ });
+
+ it('fails if user doesn\'t have access to the challenge', async () => {
+ let group = await generateGroup(user, {type: 'party', name: generateUUID()});
+ let challenge = await generateChallenge(user, group);
+ let anotherUser = await generateUser();
+ let member = await generateUser();
+ await expect(anotherUser.get(`/challenges/${challenge._id}/members/${member._id}`)).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('challengeNotFound'),
+ });
+ });
+
+ it('fails if member is not part of the challenge', async () => {
+ let group = await generateGroup(user, {type: 'party', name: generateUUID()});
+ let challenge = await generateChallenge(user, group);
+ let member = await generateUser();
+ await expect(user.get(`/challenges/${challenge._id}/members/${member._id}`)).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('challengeMemberNotFound'),
+ });
+ });
+
+ it('works with challenges belonging to a public guild', async () => {
+ let groupLeader = await generateUser({balance: 4});
+ let group = await generateGroup(groupLeader, {type: 'guild', privacy: 'public', name: generateUUID()});
+ let challenge = await generateChallenge(groupLeader, group);
+ let taskText = 'Test Text';
+ await groupLeader.post(`/tasks/challenge/${challenge._id}`, [{type: 'habit', text: taskText}]);
+
+ let memberProgress = await user.get(`/challenges/${challenge._id}/members/${groupLeader._id}`);
+ expect(memberProgress).to.have.all.keys(['_id', 'id', 'profile', 'tasks']);
+ expect(memberProgress.profile).to.have.all.keys(['name']);
+ expect(memberProgress.tasks.length).to.equal(1);
+ });
+
+ it('returns the member tasks for the challenges', async () => {
+ let group = await generateGroup(user, {type: 'party', name: generateUUID()});
+ let challenge = await generateChallenge(user, group);
+ await user.post(`/tasks/challenge/${challenge._id}`, [{type: 'habit', text: 'Test Text'}]);
+
+ let memberProgress = await user.get(`/challenges/${challenge._id}/members/${user._id}`);
+ let chalTasks = await user.get(`/tasks/challenge/${challenge._id}`);
+ expect(memberProgress.tasks.length).to.equal(chalTasks.length);
+ expect(memberProgress.tasks[0].challenge.id).to.equal(challenge._id);
+ expect(memberProgress.tasks[0].challenge.taskId).to.equal(chalTasks[0]._id);
+ });
+
+ it('returns the tasks without the tags', async () => {
+ let group = await generateGroup(user, {type: 'party', name: generateUUID()});
+ let challenge = await generateChallenge(user, group);
+ let taskText = 'Test Text';
+ await user.post(`/tasks/challenge/${challenge._id}`, [{type: 'habit', text: taskText}]);
+
+ let memberProgress = await user.get(`/challenges/${challenge._id}/members/${user._id}`);
+ expect(memberProgress.tasks[0]).not.to.have.key('tags');
+ });
+});
diff --git a/test/api/v3/integration/challenges/GET-challenges_group_groupid.test.js b/test/api/v3/integration/challenges/GET-challenges_group_groupid.test.js
new file mode 100644
index 0000000000..c507d0a4b9
--- /dev/null
+++ b/test/api/v3/integration/challenges/GET-challenges_group_groupid.test.js
@@ -0,0 +1,184 @@
+import {
+ generateUser,
+ generateChallenge,
+ createAndPopulateGroup,
+ translate as t,
+} from '../../../../helpers/api-v3-integration.helper';
+
+describe('GET challenges/group/:groupId', () => {
+ context('Public Guild', () => {
+ let publicGuild, user, nonMember, challenge, challenge2;
+
+ before(async () => {
+ let { group, groupLeader } = await createAndPopulateGroup({
+ groupDetails: {
+ name: 'TestGuild',
+ type: 'guild',
+ privacy: 'public',
+ },
+ });
+
+ publicGuild = group;
+ user = groupLeader;
+
+ nonMember = await generateUser();
+
+ challenge = await generateChallenge(user, group);
+ challenge2 = await generateChallenge(user, group);
+ });
+
+ it('should return group challenges for non member with populated leader', async () => {
+ let challenges = await nonMember.get(`/challenges/groups/${publicGuild._id}`);
+
+ let foundChallenge1 = _.find(challenges, { _id: challenge._id });
+ expect(foundChallenge1).to.exist;
+ expect(foundChallenge1.leader).to.eql({
+ _id: publicGuild.leader._id,
+ id: publicGuild.leader._id,
+ profile: {name: user.profile.name},
+ });
+ let foundChallenge2 = _.find(challenges, { _id: challenge2._id });
+ expect(foundChallenge2).to.exist;
+ expect(foundChallenge2.leader).to.eql({
+ _id: publicGuild.leader._id,
+ id: publicGuild.leader._id,
+ profile: {name: user.profile.name},
+ });
+ });
+
+ it('should return group challenges for member with populated leader', async () => {
+ let challenges = await user.get(`/challenges/groups/${publicGuild._id}`);
+
+ let foundChallenge1 = _.find(challenges, { _id: challenge._id });
+ expect(foundChallenge1).to.exist;
+ expect(foundChallenge1.leader).to.eql({
+ _id: publicGuild.leader._id,
+ id: publicGuild.leader._id,
+ profile: {name: user.profile.name},
+ });
+ let foundChallenge2 = _.find(challenges, { _id: challenge2._id });
+ expect(foundChallenge2).to.exist;
+ expect(foundChallenge2.leader).to.eql({
+ _id: publicGuild.leader._id,
+ id: publicGuild.leader._id,
+ profile: {name: user.profile.name},
+ });
+ });
+
+ it('should return newest challenges first', async () => {
+ let challenges = await user.get(`/challenges/groups/${publicGuild._id}`);
+
+ let foundChallengeIndex = _.findIndex(challenges, { _id: challenge2._id });
+ expect(foundChallengeIndex).to.eql(0);
+
+ let newChallenge = await generateChallenge(user, publicGuild);
+
+ challenges = await user.get(`/challenges/groups/${publicGuild._id}`);
+
+ foundChallengeIndex = _.findIndex(challenges, { _id: newChallenge._id });
+ expect(foundChallengeIndex).to.eql(0);
+ });
+ });
+
+ context('Private Guild', () => {
+ let privateGuild, user, nonMember, challenge, challenge2;
+
+ before(async () => {
+ let { group, groupLeader } = await createAndPopulateGroup({
+ groupDetails: {
+ name: 'TestPrivateGuild',
+ type: 'guild',
+ privacy: 'private',
+ },
+ });
+
+ privateGuild = group;
+ user = groupLeader;
+
+ nonMember = await generateUser();
+
+ challenge = await generateChallenge(user, group);
+ challenge2 = await generateChallenge(user, group);
+ });
+
+ it('should prevent non-member from seeing challenges', async () => {
+ await expect(nonMember.get(`/challenges/groups/${privateGuild._id}`))
+ .to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('groupNotFound'),
+ });
+ });
+
+ it('should return group challenges for member with populated leader', async () => {
+ let challenges = await user.get(`/challenges/groups/${privateGuild._id}`);
+
+ let foundChallenge1 = _.find(challenges, { _id: challenge._id });
+ expect(foundChallenge1).to.exist;
+ expect(foundChallenge1.leader).to.eql({
+ _id: privateGuild.leader._id,
+ id: privateGuild.leader._id,
+ profile: {name: user.profile.name},
+ });
+ let foundChallenge2 = _.find(challenges, { _id: challenge2._id });
+ expect(foundChallenge2).to.exist;
+ expect(foundChallenge2.leader).to.eql({
+ _id: privateGuild.leader._id,
+ id: privateGuild.leader._id,
+ profile: {name: user.profile.name},
+ });
+ });
+ });
+
+ context('official challenge is present', () => {
+ let publicGuild, user, officialChallenge, challenge, challenge2;
+
+ before(async () => {
+ let { group, groupLeader } = await createAndPopulateGroup({
+ groupDetails: {
+ name: 'TestGuild',
+ type: 'guild',
+ privacy: 'public',
+ },
+ });
+
+ user = groupLeader;
+ publicGuild = group;
+
+ await user.update({
+ 'contributor.admin': true,
+ });
+
+ officialChallenge = await generateChallenge(user, group, {
+ official: true,
+ });
+
+ challenge = await generateChallenge(user, group);
+ challenge2 = await generateChallenge(user, group);
+ });
+
+ it('should return official challenges first', async () => {
+ let challenges = await user.get(`/challenges/groups/${publicGuild._id}`);
+
+ let foundChallengeIndex = _.findIndex(challenges, { _id: officialChallenge._id });
+ expect(foundChallengeIndex).to.eql(0);
+ });
+
+ it('should return newest challenges first, after official ones', async () => {
+ let challenges = await user.get(`/challenges/groups/${publicGuild._id}`);
+
+ let foundChallengeIndex = _.findIndex(challenges, { _id: challenge._id });
+ expect(foundChallengeIndex).to.eql(2);
+
+ foundChallengeIndex = _.findIndex(challenges, { _id: challenge2._id });
+ expect(foundChallengeIndex).to.eql(1);
+
+ let newChallenge = await generateChallenge(user, publicGuild);
+
+ challenges = await user.get(`/challenges/groups/${publicGuild._id}`);
+
+ foundChallengeIndex = _.findIndex(challenges, { _id: newChallenge._id });
+ expect(foundChallengeIndex).to.eql(1);
+ });
+ });
+});
diff --git a/test/api/v3/integration/challenges/GET-challenges_user.test.js b/test/api/v3/integration/challenges/GET-challenges_user.test.js
new file mode 100644
index 0000000000..b46367f46f
--- /dev/null
+++ b/test/api/v3/integration/challenges/GET-challenges_user.test.js
@@ -0,0 +1,200 @@
+import {
+ generateUser,
+ generateChallenge,
+ createAndPopulateGroup,
+} from '../../../../helpers/api-v3-integration.helper';
+
+describe('GET challenges/user', () => {
+ context('no official challenges', () => {
+ let user, member, nonMember, challenge, challenge2, publicGuild;
+
+ before(async () => {
+ let { group, groupLeader, members } = await createAndPopulateGroup({
+ groupDetails: {
+ name: 'TestGuild',
+ type: 'guild',
+ privacy: 'public',
+ },
+ members: 1,
+ });
+
+ user = groupLeader;
+ publicGuild = group;
+ member = members[0];
+ nonMember = await generateUser();
+
+ challenge = await generateChallenge(user, group);
+ challenge2 = await generateChallenge(user, group);
+ });
+
+ it('should return challenges user has joined', async () => {
+ await nonMember.post(`/challenges/${challenge._id}/join`);
+
+ let challenges = await nonMember.get('/challenges/user');
+
+ let foundChallenge = _.find(challenges, { _id: challenge._id });
+ expect(foundChallenge).to.exist;
+ expect(foundChallenge.leader).to.eql({
+ _id: publicGuild.leader._id,
+ id: publicGuild.leader._id,
+ profile: {name: user.profile.name},
+ });
+ expect(foundChallenge.group).to.eql({
+ _id: publicGuild._id,
+ id: publicGuild._id,
+ type: publicGuild.type,
+ privacy: publicGuild.privacy,
+ name: publicGuild.name,
+ });
+ });
+
+ it('should return challenges user has created', async () => {
+ let challenges = await user.get('/challenges/user');
+
+ let foundChallenge1 = _.find(challenges, { _id: challenge._id });
+ expect(foundChallenge1).to.exist;
+ expect(foundChallenge1.leader).to.eql({
+ _id: publicGuild.leader._id,
+ id: publicGuild.leader._id,
+ profile: {name: user.profile.name},
+ });
+ expect(foundChallenge1.group).to.eql({
+ _id: publicGuild._id,
+ id: publicGuild._id,
+ type: publicGuild.type,
+ privacy: publicGuild.privacy,
+ name: publicGuild.name,
+ });
+ let foundChallenge2 = _.find(challenges, { _id: challenge2._id });
+ expect(foundChallenge2).to.exist;
+ expect(foundChallenge2.leader).to.eql({
+ _id: publicGuild.leader._id,
+ id: publicGuild.leader._id,
+ profile: {name: user.profile.name},
+ });
+ expect(foundChallenge2.group).to.eql({
+ _id: publicGuild._id,
+ id: publicGuild._id,
+ type: publicGuild.type,
+ privacy: publicGuild.privacy,
+ name: publicGuild.name,
+ });
+ });
+
+ it('should return challenges in user\'s group', async () => {
+ let challenges = await member.get('/challenges/user');
+
+ let foundChallenge1 = _.find(challenges, { _id: challenge._id });
+ expect(foundChallenge1).to.exist;
+ expect(foundChallenge1.leader).to.eql({
+ _id: publicGuild.leader._id,
+ id: publicGuild.leader._id,
+ profile: {name: user.profile.name},
+ });
+ expect(foundChallenge1.group).to.eql({
+ _id: publicGuild._id,
+ id: publicGuild._id,
+ type: publicGuild.type,
+ privacy: publicGuild.privacy,
+ name: publicGuild.name,
+ });
+ let foundChallenge2 = _.find(challenges, { _id: challenge2._id });
+ expect(foundChallenge2).to.exist;
+ expect(foundChallenge2.leader).to.eql({
+ _id: publicGuild.leader._id,
+ id: publicGuild.leader._id,
+ profile: {name: user.profile.name},
+ });
+ expect(foundChallenge2.group).to.eql({
+ _id: publicGuild._id,
+ id: publicGuild._id,
+ type: publicGuild.type,
+ privacy: publicGuild.privacy,
+ name: publicGuild.name,
+ });
+ });
+
+ it('should return newest challenges first', async () => {
+ let challenges = await user.get('/challenges/user');
+
+ let foundChallengeIndex = _.findIndex(challenges, { _id: challenge2._id });
+ expect(foundChallengeIndex).to.eql(0);
+
+ let newChallenge = await generateChallenge(user, publicGuild);
+
+ challenges = await user.get('/challenges/user');
+
+ foundChallengeIndex = _.findIndex(challenges, { _id: newChallenge._id });
+ expect(foundChallengeIndex).to.eql(0);
+ });
+
+ it('should not return challenges user doesn\'t have access to', async () => {
+ let { group, groupLeader } = await createAndPopulateGroup({
+ groupDetails: {
+ name: 'TestPrivateGuild',
+ type: 'guild',
+ privacy: 'private',
+ },
+ });
+
+ let privateChallenge = await generateChallenge(groupLeader, group);
+
+ let challenges = await nonMember.get('/challenges/user');
+
+ let foundChallenge = _.find(challenges, { _id: privateChallenge._id });
+ expect(foundChallenge).to.not.exist;
+ });
+ });
+
+ context('official challenge is present', () => {
+ let user, officialChallenge, challenge, challenge2, publicGuild;
+
+ before(async () => {
+ let { group, groupLeader } = await createAndPopulateGroup({
+ groupDetails: {
+ name: 'TestGuild',
+ type: 'guild',
+ privacy: 'public',
+ },
+ });
+
+ user = groupLeader;
+ publicGuild = group;
+
+ await user.update({
+ 'contributor.admin': true,
+ });
+
+ officialChallenge = await generateChallenge(user, group, {
+ official: true,
+ });
+
+ challenge = await generateChallenge(user, group);
+ challenge2 = await generateChallenge(user, group);
+ });
+
+ it('should return official challenges first', async () => {
+ let challenges = await user.get('/challenges/user');
+
+ let foundChallengeIndex = _.findIndex(challenges, { _id: officialChallenge._id });
+ expect(foundChallengeIndex).to.eql(0);
+ });
+
+ it('should return newest challenges first, after official ones', async () => {
+ let challenges = await user.get('/challenges/user');
+
+ let foundChallengeIndex = _.findIndex(challenges, { _id: challenge._id });
+ expect(foundChallengeIndex).to.eql(2);
+
+ foundChallengeIndex = _.findIndex(challenges, { _id: challenge2._id });
+ expect(foundChallengeIndex).to.eql(1);
+
+ let newChallenge = await generateChallenge(user, publicGuild);
+
+ challenges = await user.get('/challenges/user');
+
+ foundChallengeIndex = _.findIndex(challenges, { _id: newChallenge._id });
+ expect(foundChallengeIndex).to.eql(1);
+ });
+ });
+});
diff --git a/test/api/v3/integration/challenges/POST-challenges.test.js b/test/api/v3/integration/challenges/POST-challenges.test.js
new file mode 100644
index 0000000000..a97578eef5
--- /dev/null
+++ b/test/api/v3/integration/challenges/POST-challenges.test.js
@@ -0,0 +1,308 @@
+import {
+ generateUser,
+ createAndPopulateGroup,
+ translate as t,
+} from '../../../../helpers/api-v3-integration.helper';
+import { v4 as generateUUID } from 'uuid';
+
+describe('POST /challenges', () => {
+ it('returns error when group is empty', async () => {
+ let user = await generateUser();
+
+ await expect(user.post('/challenges')).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('invalidReqParams'),
+ });
+ });
+
+ it('returns error when groupId is not for a valid group', async () => {
+ let user = await generateUser();
+
+ await expect(user.post('/challenges', {
+ group: generateUUID(),
+ })).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('groupNotFound'),
+ });
+ });
+
+ it('returns error when creating a challenge in the tavern with no prize', async () => {
+ let user = await generateUser();
+
+ await expect(user.post('/challenges', {
+ group: 'habitrpg',
+ prize: 0,
+ })).to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('tavChalsMinPrize'),
+ });
+ });
+
+ it('returns error when creating a challenge in a public guild and you are not a member of it', async () => {
+ let user = await generateUser();
+ let { group } = await createAndPopulateGroup({
+ groupDetails: {
+ type: 'guild',
+ privacy: 'public',
+ },
+ });
+
+ await expect(user.post('/challenges', {
+ group: group._id,
+ prize: 4,
+ })).to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('mustBeGroupMember'),
+ });
+ });
+
+ context('Creating a challenge for a valid group', () => {
+ let groupLeader;
+ let group;
+ let groupMember;
+
+ beforeEach(async () => {
+ let populatedGroup = await createAndPopulateGroup({
+ members: 1,
+ leaderDetails: {
+ balance: 3,
+ },
+ groupDetails: {
+ type: 'guild',
+ leaderOnly: {
+ challenges: true,
+ },
+ },
+ });
+
+ groupLeader = await populatedGroup.groupLeader.sync();
+ group = populatedGroup.group;
+ groupMember = populatedGroup.members[0];
+ });
+
+ it('returns an error when non-leader member creates a challenge in leaderOnly group', async () => {
+ await expect(groupMember.post('/challenges', {
+ group: group._id,
+ })).to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('onlyGroupLeaderChal'),
+ });
+ });
+
+ it('returns an error when non-leader member creates a challenge in leaderOnly group', async () => {
+ await expect(groupMember.post('/challenges', {
+ group: group._id,
+ })).to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('onlyGroupLeaderChal'),
+ });
+ });
+
+ it('allows non-leader member to create a challenge', async () => {
+ let populatedGroup = await createAndPopulateGroup({
+ members: 1,
+ });
+
+ group = populatedGroup.group;
+ groupMember = populatedGroup.members[0];
+
+ let chal = await groupMember.post('/challenges', {
+ group: group._id,
+ name: 'Test Challenge',
+ shortName: 'TC Label',
+ });
+
+ expect(chal.leader).to.eql({
+ _id: groupMember._id,
+ profile: {name: groupMember.profile.name},
+ });
+ });
+
+ it('doesn\'t take gems from user or group when challenge has no prize', async () => {
+ let oldUserBalance = groupLeader.balance;
+ let oldGroupBalance = group.balance;
+
+ await groupLeader.post('/challenges', {
+ group: group._id,
+ name: 'Test Challenge',
+ shortName: 'TC Label',
+ prize: 0,
+ });
+
+ await expect(groupLeader.sync()).to.eventually.have.property('balance', oldUserBalance);
+ await expect(group.sync()).to.eventually.have.property('balance', oldGroupBalance);
+ });
+
+ it('returns error when user and group can\'t pay prize', async () => {
+ await expect(groupLeader.post('/challenges', {
+ group: group._id,
+ name: 'Test Challenge',
+ shortName: 'TC Label',
+ prize: 20,
+ })).to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('cantAfford'),
+ });
+ });
+
+ it('takes prize out of group if it has sufficient funds', async () => {
+ let oldUserBalance = groupLeader.balance;
+ let oldGroupBalance = group.balance;
+ let prize = 4;
+
+ await groupLeader.post('/challenges', {
+ group: group._id,
+ name: 'Test Challenge',
+ shortName: 'TC Label',
+ prize,
+ });
+
+ await expect(group.sync()).to.eventually.have.property('balance', oldGroupBalance - prize / 4);
+ await expect(groupLeader.sync()).to.eventually.have.property('balance', oldUserBalance);
+ });
+
+ it('takes prize out of both group and user if group doesn\'t have enough', async () => {
+ let oldUserBalance = groupLeader.balance;
+ let prize = 8;
+
+ await groupLeader.post('/challenges', {
+ group: group._id,
+ name: 'Test Challenge',
+ shortName: 'TC Label',
+ prize,
+ });
+
+ await expect(group.sync()).to.eventually.have.property('balance', 0);
+ await expect(groupLeader.sync()).to.eventually.have.property('balance', oldUserBalance - (prize / 4 - 1));
+ });
+
+ it('takes prize out of user if group has no balance', async () => {
+ let oldUserBalance = groupLeader.balance;
+ let prize = 8;
+
+ await group.update({ balance: 0});
+ await groupLeader.post('/challenges', {
+ group: group._id,
+ name: 'Test Challenge',
+ shortName: 'TC Label',
+ prize,
+ });
+
+ await expect(group.sync()).to.eventually.have.property('balance', 0);
+ await expect(groupLeader.sync()).to.eventually.have.property('balance', oldUserBalance - prize / 4);
+ });
+
+ it('increases challenge count of group', async () => {
+ let oldChallengeCount = group.challengeCount;
+
+ await groupLeader.post('/challenges', {
+ group: group._id,
+ name: 'Test Challenge',
+ shortName: 'TC Label',
+ });
+
+ await expect(group.sync()).to.eventually.have.property('challengeCount', oldChallengeCount + 1);
+ });
+
+ it('sets challenge as official if created by admin and official flag is set', async () => {
+ await groupLeader.update({
+ contributor: {
+ admin: true,
+ },
+ });
+
+ let challenge = await groupLeader.post('/challenges', {
+ group: group._id,
+ name: 'Test Challenge',
+ shortName: 'TC Label',
+ official: true,
+ });
+
+ expect(challenge.official).to.eql(true);
+ });
+
+ it('doesn\'t set challenge as official if official flag is set by non-admin', async () => {
+ let challenge = await groupLeader.post('/challenges', {
+ group: group._id,
+ name: 'Test Challenge',
+ shortName: 'TC Label',
+ official: true,
+ });
+
+ expect(challenge.official).to.eql(false);
+ });
+
+ it('returns an error when challenge validation fails; doesn\'s save user or group', async () => {
+ let oldChallengeCount = group.challengeCount;
+ let oldUserBalance = groupLeader.balance;
+ let oldUserChallenges = groupLeader.challenges;
+ let oldGroupBalance = group.balance;
+
+ await expect(groupLeader.post('/challenges', {
+ group: group._id,
+ prize: 8,
+ })).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: 'Challenge validation failed',
+ });
+
+ group = await group.sync();
+ groupLeader = await groupLeader.sync();
+
+ expect(group.challengeCount).to.eql(oldChallengeCount);
+ expect(group.balance).to.eql(oldGroupBalance);
+ expect(groupLeader.balance).to.eql(oldUserBalance);
+ expect(groupLeader.challenges).to.eql(oldUserChallenges);
+ });
+
+ it('sets all properites of the challenge as passed', async () => {
+ let name = 'Test Challenge';
+ let shortName = 'TC Label';
+ let description = 'Test Description';
+ let prize = 4;
+
+ let challenge = await groupLeader.post('/challenges', {
+ group: group._id,
+ name,
+ shortName,
+ description,
+ prize,
+ });
+
+ expect(challenge.leader).to.eql({
+ _id: groupLeader._id,
+ profile: {name: groupLeader.profile.name},
+ });
+ expect(challenge.name).to.eql(name);
+ expect(challenge.shortName).to.eql(shortName);
+ expect(challenge.description).to.eql(description);
+ expect(challenge.official).to.eql(false);
+ expect(challenge.group).to.eql({
+ _id: group._id,
+ privacy: group.privacy,
+ name: group.name,
+ type: group.type,
+ });
+ expect(challenge.memberCount).to.eql(1);
+ expect(challenge.prize).to.eql(prize);
+ });
+
+ it('adds challenge to creator\'s challenges', async () => {
+ let challenge = await groupLeader.post('/challenges', {
+ group: group._id,
+ name: 'Test Challenge',
+ shortName: 'TC Label',
+ });
+
+ await expect(groupLeader.sync()).to.eventually.have.property('challenges').to.include(challenge._id);
+ });
+ });
+});
diff --git a/test/api/v3/integration/challenges/POST-challenges_challengeId_join.test.js b/test/api/v3/integration/challenges/POST-challenges_challengeId_join.test.js
new file mode 100644
index 0000000000..c55c0e0b49
--- /dev/null
+++ b/test/api/v3/integration/challenges/POST-challenges_challengeId_join.test.js
@@ -0,0 +1,127 @@
+import {
+ generateUser,
+ generateChallenge,
+ createAndPopulateGroup,
+ translate as t,
+} from '../../../../helpers/api-v3-integration.helper';
+import { v4 as generateUUID } from 'uuid';
+
+describe('POST /challenges/:challengeId/join', () => {
+ it('returns error when challengeId is not a valid UUID', async () => {
+ let user = await generateUser({ balance: 1});
+
+ await expect(user.post('/challenges/test/join')).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('invalidReqParams'),
+ });
+ });
+
+ it('returns error when challengeId is not for a valid challenge', async () => {
+ let user = await generateUser({ balance: 1});
+
+ await expect(user.post(`/challenges/${generateUUID()}/join`)).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('challengeNotFound'),
+ });
+ });
+
+ context('Joining a valid challenge', () => {
+ let groupLeader;
+ let group;
+ let challenge;
+ let authorizedUser;
+
+ beforeEach(async () => {
+ let populatedGroup = await createAndPopulateGroup({
+ members: 1,
+ });
+
+ groupLeader = populatedGroup.groupLeader;
+ group = populatedGroup.group;
+ authorizedUser = populatedGroup.members[0];
+
+ challenge = await generateChallenge(groupLeader, group);
+ });
+
+ it('returns an error when user doesn\'t have permissions to access the challenge', async () => {
+ let unauthorizedUser = await generateUser();
+
+ await expect(unauthorizedUser.post(`/challenges/${challenge._id}/join`)).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('challengeNotFound'),
+ });
+ });
+
+ it('returns challenge data', async () => {
+ let res = await authorizedUser.post(`/challenges/${challenge._id}/join`);
+
+ expect(res.group).to.eql({
+ _id: group._id,
+ privacy: group.privacy,
+ name: group.name,
+ type: group.type,
+ });
+ expect(res.leader).to.eql({
+ _id: groupLeader._id,
+ id: groupLeader._id,
+ profile: {name: groupLeader.profile.name},
+ });
+ expect(res.name).to.equal(challenge.name);
+ });
+
+ it('adds challenge to user challenges', async () => {
+ await authorizedUser.post(`/challenges/${challenge._id}/join`);
+
+ await authorizedUser.sync();
+
+ expect(authorizedUser).to.have.property('challenges').to.include(challenge._id);
+ });
+
+ it('returns error when user has already joined the challenge', async () => {
+ await authorizedUser.post(`/challenges/${challenge._id}/join`);
+
+ await expect(authorizedUser.post(`/challenges/${challenge._id}/join`)).to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('userAlreadyInChallenge'),
+ });
+ });
+
+ it('increases memberCount of challenge', async () => {
+ let oldMemberCount = challenge.memberCount;
+
+ await authorizedUser.post(`/challenges/${challenge._id}/join`);
+
+ await challenge.sync();
+
+ expect(challenge).to.have.property('memberCount', oldMemberCount + 1);
+ });
+
+ it('syncs challenge tasks to joining user', async () => {
+ let taskText = 'A challenge task text';
+
+ await groupLeader.post(`/tasks/challenge/${challenge._id}`, [
+ {type: 'habit', text: taskText},
+ ]);
+
+ await authorizedUser.post(`/challenges/${challenge._id}/join`);
+ let tasks = await authorizedUser.get('/tasks/user');
+ let tasksTexts = tasks.map((task) => {
+ return task.text;
+ });
+
+ expect(tasksTexts).to.include(taskText);
+ });
+
+ it('adds challenge tag to user tags', async () => {
+ let userTagsLength = (await authorizedUser.get('/tags')).length;
+
+ await authorizedUser.post(`/challenges/${challenge._id}/join`);
+
+ await expect(authorizedUser.get('/tags')).to.eventually.have.length(userTagsLength + 1);
+ });
+ });
+});
diff --git a/test/api/v3/integration/challenges/POST-challenges_challengeId_leave.test.js b/test/api/v3/integration/challenges/POST-challenges_challengeId_leave.test.js
new file mode 100644
index 0000000000..0502be654c
--- /dev/null
+++ b/test/api/v3/integration/challenges/POST-challenges_challengeId_leave.test.js
@@ -0,0 +1,123 @@
+import {
+ generateUser,
+ generateChallenge,
+ createAndPopulateGroup,
+ translate as t,
+} from '../../../../helpers/api-v3-integration.helper';
+import { v4 as generateUUID } from 'uuid';
+
+describe('POST /challenges/:challengeId/leave', () => {
+ it('returns error when challengeId is not a valid UUID', async () => {
+ let user = await generateUser();
+
+ await expect(user.post('/challenges/test/leave')).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('invalidReqParams'),
+ });
+ });
+
+ it('returns error when challengeId is not for a valid challenge', async () => {
+ let user = await generateUser();
+
+ await expect(user.post(`/challenges/${generateUUID()}/leave`)).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('challengeNotFound'),
+ });
+ });
+
+ context('Leaving a valid challenge', () => {
+ let groupLeader;
+ let group;
+ let challenge;
+ let notInChallengeUser;
+ let leavingUser;
+ let taskText;
+
+ beforeEach(async () => {
+ let populatedGroup = await createAndPopulateGroup({
+ members: 2,
+ });
+
+ groupLeader = populatedGroup.groupLeader;
+ group = populatedGroup.group;
+ leavingUser = populatedGroup.members[0];
+ notInChallengeUser = populatedGroup.members[1];
+
+ challenge = await generateChallenge(groupLeader, group);
+
+ taskText = 'A challenge task text';
+
+ await groupLeader.post(`/tasks/challenge/${challenge._id}`, [
+ {type: 'habit', text: taskText},
+ ]);
+
+ await leavingUser.post(`/challenges/${challenge._id}/join`);
+
+ await challenge.sync();
+ });
+
+ it('returns an error when user doesn\'t have permissions to view the challenge', async () => {
+ let unauthorizedUser = await generateUser();
+
+ await expect(unauthorizedUser.post(`/challenges/${challenge._id}/leave`)).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('challengeNotFound'),
+ });
+ });
+
+ it('returns an error when user isn\'t a member of the challenge', async () => {
+ await expect(notInChallengeUser.post(`/challenges/${challenge._id}/leave`)).to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('challengeMemberNotFound'),
+ });
+ });
+
+ it('removes challenge from user challenges', async () => {
+ await leavingUser.post(`/challenges/${challenge._id}/leave`);
+
+ await leavingUser.sync();
+
+ expect(leavingUser).to.have.property('challenges').to.not.include(challenge._id);
+ });
+
+ it('decreases memberCount of challenge', async () => {
+ let oldMemberCount = challenge.memberCount;
+
+ await leavingUser.post(`/challenges/${challenge._id}/leave`);
+
+ await challenge.sync();
+
+ expect(challenge).to.have.property('memberCount', oldMemberCount - 1);
+ });
+
+ it('unlinks challenge tasks from leaving user when remove-all is passed', async () => {
+ await leavingUser.post(`/challenges/${challenge._id}/leave`, {
+ keep: 'remove-all',
+ });
+ let tasks = await leavingUser.get('/tasks/user');
+ let tasksTexts = tasks.map((task) => {
+ return task.text;
+ });
+
+ expect(tasksTexts).to.not.include(taskText);
+ });
+
+ it('doesn\'t unlink challenge tasks from leaving user when remove-all isn\'t passed', async () => {
+ await leavingUser.post(`/challenges/${challenge._id}/leave`, {
+ keep: 'test',
+ });
+
+ let tasks = await leavingUser.get('/tasks/user');
+ let testTask = _.find(tasks, (task) => {
+ return task.text === taskText;
+ });
+
+ expect(testTask).to.not.be.undefined;
+ expect(testTask.challenge).to.eql({});
+ });
+ });
+});
diff --git a/test/api/v3/integration/challenges/POST-challenges_challengeId_winner_winnerId.test.js b/test/api/v3/integration/challenges/POST-challenges_challengeId_winner_winnerId.test.js
new file mode 100644
index 0000000000..98dfc20926
--- /dev/null
+++ b/test/api/v3/integration/challenges/POST-challenges_challengeId_winner_winnerId.test.js
@@ -0,0 +1,139 @@
+import {
+ generateUser,
+ generateChallenge,
+ createAndPopulateGroup,
+ sleep,
+ checkExistence,
+ translate as t,
+} from '../../../../helpers/api-v3-integration.helper';
+import { v4 as generateUUID } from 'uuid';
+
+describe('POST /challenges/:challengeId/winner/:winnerId', () => {
+ it('returns error when challengeId is not a valid UUID', async () => {
+ let user = await generateUser();
+
+ await expect(user.post(`/challenges/test/selectWinner/${user._id}`)).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('invalidReqParams'),
+ });
+ });
+
+ it('returns error when winnerId is not a valid UUID', async () => {
+ let user = await generateUser();
+
+ await expect(user.post(`/challenges/${generateUUID()}/selectWinner/test`)).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('invalidReqParams'),
+ });
+ });
+
+ it('returns error when challengeId is not for a valid challenge', async () => {
+ let user = await generateUser();
+
+ await expect(user.post(`/challenges/${generateUUID()}/selectWinner/${user._id}`)).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('challengeNotFound'),
+ });
+ });
+
+ context('Selecting winner for a valid challenge', () => {
+ let groupLeader;
+ let group;
+ let challenge;
+ let winningUser;
+ let taskText = 'A challenge task text';
+
+ beforeEach(async () => {
+ let populatedGroup = await createAndPopulateGroup({
+ members: 1,
+ });
+
+ groupLeader = populatedGroup.groupLeader;
+ group = populatedGroup.group;
+ winningUser = populatedGroup.members[0];
+
+ challenge = await generateChallenge(groupLeader, group, {
+ prize: 1,
+ });
+
+ await groupLeader.post(`/tasks/challenge/${challenge._id}`, [
+ {type: 'habit', text: taskText},
+ ]);
+
+ await winningUser.post(`/challenges/${challenge._id}/join`);
+
+ await challenge.sync();
+ });
+
+ it('returns an error when user doesn\'t have permissions to select winner', async () => {
+ await expect(winningUser.post(`/challenges/${challenge._id}/selectWinner/${winningUser._id}`)).to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('onlyLeaderDeleteChal'),
+ });
+ });
+
+ it('returns an error when winning user isn\'t part of the challenge', async () => {
+ let notInChallengeUser = await generateUser();
+
+ await expect(groupLeader.post(`/challenges/${challenge._id}/selectWinner/${notInChallengeUser._id}`)).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('winnerNotFound', {userId: notInChallengeUser._id}),
+ });
+ });
+
+ it('deletes challenge after winner is selected', async () => {
+ await groupLeader.post(`/challenges/${challenge._id}/selectWinner/${winningUser._id}`);
+
+ await sleep(0.5);
+
+ await expect(checkExistence('challenges', challenge._id)).to.eventually.equal(false);
+ });
+
+ it('adds challenge to winner\'s achievements', async () => {
+ await groupLeader.post(`/challenges/${challenge._id}/selectWinner/${winningUser._id}`);
+
+ await sleep(0.5);
+
+ await expect(winningUser.sync()).to.eventually.have.deep.property('achievements.challenges').to.include(challenge.name);
+ });
+
+ it('gives winner gems as reward', async () => {
+ let oldBalance = winningUser.balance;
+
+ await groupLeader.post(`/challenges/${challenge._id}/selectWinner/${winningUser._id}`);
+
+ await sleep(0.5);
+
+ await expect(winningUser.sync()).to.eventually.have.property('balance', oldBalance + challenge.prize / 4);
+ });
+
+ it('doesn\'t refund gems to group leader', async () => {
+ let oldBalance = (await groupLeader.sync()).balance;
+
+ await groupLeader.post(`/challenges/${challenge._id}/selectWinner/${winningUser._id}`);
+
+ await sleep(0.5);
+
+ await expect(groupLeader.sync()).to.eventually.have.property('balance', oldBalance);
+ });
+
+ it('sets broken and winner flags for user\'s challenge tasks', async () => {
+ await groupLeader.post(`/challenges/${challenge._id}/selectWinner/${winningUser._id}`);
+
+ await sleep(0.5);
+
+ let tasks = await winningUser.get('/tasks/user');
+ let testTask = _.find(tasks, (task) => {
+ return task.text === taskText;
+ });
+
+ expect(testTask.challenge.broken).to.eql('CHALLENGE_CLOSED');
+ expect(testTask.challenge.winner).to.eql(winningUser.profile.name);
+ });
+ });
+});
diff --git a/test/api/v3/integration/challenges/PUT-challenges_challengeId.test.js b/test/api/v3/integration/challenges/PUT-challenges_challengeId.test.js
new file mode 100644
index 0000000000..1fdcc7a5a2
--- /dev/null
+++ b/test/api/v3/integration/challenges/PUT-challenges_challengeId.test.js
@@ -0,0 +1,85 @@
+import {
+ generateUser,
+ generateChallenge,
+ createAndPopulateGroup,
+ translate as t,
+} from '../../../../helpers/api-v3-integration.helper';
+
+describe('PUT /challenges/:challengeId', () => {
+ let privateGuild, user, nonMember, challenge, member;
+
+ beforeEach(async () => {
+ let { group, groupLeader, members } = await createAndPopulateGroup({
+ groupDetails: {
+ name: 'TestPrivateGuild',
+ type: 'guild',
+ privacy: 'private',
+ },
+ members: 1,
+ });
+
+ privateGuild = group;
+ user = groupLeader;
+
+ nonMember = await generateUser();
+ member = members[0];
+
+ challenge = await generateChallenge(user, group);
+ await member.post(`/challenges/${challenge._id}/join`);
+ });
+
+ it('fails if the user can\'t view the challenge', async () => {
+ await expect(nonMember.put(`/challenges/${challenge._id}`))
+ .to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('challengeNotFound'),
+ });
+ });
+
+ it('should only allow the leader or an admin to update the challenge', async () => {
+ await expect(member.put(`/challenges/${challenge._id}`))
+ .to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('onlyLeaderUpdateChal'),
+ });
+ });
+
+ it('only updates allowed fields', async () => {
+ let res = await user.put(`/challenges/${challenge._id}`, {
+ // ignored
+ prize: 33,
+ group: 'blabla',
+ memberCount: 33,
+ tasksOrder: 'new order',
+ official: true,
+ shortName: 'new short name',
+
+ // applied
+ name: 'New Challenge Name',
+ description: 'New challenge description.',
+ leader: member._id,
+ });
+
+ expect(res.prize).to.equal(0);
+ expect(res.group).to.eql({
+ _id: privateGuild._id,
+ privacy: privateGuild.privacy,
+ name: privateGuild.name,
+ type: privateGuild.type,
+ });
+ expect(res.memberCount).to.equal(2);
+ expect(res.tasksOrder).not.to.equal('new order');
+ expect(res.official).to.equal(false);
+ expect(res.shortName).not.to.equal('new short name');
+
+ expect(res.leader).to.eql({
+ _id: member._id,
+ id: member._id,
+ profile: {name: member.profile.name},
+ });
+ expect(res.name).to.equal('New Challenge Name');
+ expect(res.description).to.equal('New challenge description.');
+ });
+});
diff --git a/test/api/v3/integration/chat/DELETE-chat_id.test.js b/test/api/v3/integration/chat/DELETE-chat_id.test.js
new file mode 100644
index 0000000000..5d1f73f867
--- /dev/null
+++ b/test/api/v3/integration/chat/DELETE-chat_id.test.js
@@ -0,0 +1,81 @@
+import {
+ createAndPopulateGroup,
+ generateUser,
+ translate as t,
+} from '../../../../helpers/api-v3-integration.helper';
+import { v4 as generateUUID } from 'uuid';
+
+describe('DELETE /groups/:groupId/chat/:chatId', () => {
+ let groupWithChat, message, user, userThatDidNotCreateChat, admin;
+
+ before(async () => {
+ let { group, groupLeader } = await createAndPopulateGroup({
+ groupDetails: {
+ type: 'guild',
+ privacy: 'public',
+ },
+ });
+
+ groupWithChat = group;
+ user = groupLeader;
+ message = await user.post(`/groups/${groupWithChat._id}/chat`, { message: 'Some message' });
+ message = message.message;
+ userThatDidNotCreateChat = await generateUser();
+ admin = await generateUser({'contributor.admin': true});
+ });
+
+ context('Chat errors', () => {
+ it('returns an error is message does not exist', async () => {
+ let fakeChatId = generateUUID();
+ await expect(user.del(`/groups/${groupWithChat._id}/chat/${fakeChatId}`)).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('messageGroupChatNotFound'),
+ });
+ });
+
+ it('returns an error when user does not have permission to delete', async () => {
+ await expect(userThatDidNotCreateChat.del(`/groups/${groupWithChat._id}/chat/${message.id}`)).to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('onlyCreatorOrAdminCanDeleteChat'),
+ });
+ });
+ });
+
+ context('Chat success', () => {
+ let nextMessage;
+
+ beforeEach(async () => {
+ nextMessage = await user.post(`/groups/${groupWithChat._id}/chat`, { message: 'Some new message' });
+ nextMessage = nextMessage.message;
+ });
+
+ it('allows creator to delete a their message', async () => {
+ await user.del(`/groups/${groupWithChat._id}/chat/${nextMessage.id}`);
+ let messages = await user.get(`/groups/${groupWithChat._id}/chat/`);
+ expect(messages).is.an('array');
+ expect(messages).to.not.include(nextMessage);
+ });
+
+ it('allows admin to delete another user\'s message', async () => {
+ await admin.del(`/groups/${groupWithChat._id}/chat/${nextMessage.id}`);
+ let messages = await user.get(`/groups/${groupWithChat._id}/chat/`);
+ expect(messages).is.an('array');
+ expect(messages).to.not.include(nextMessage);
+ });
+
+ it('returns empty when previous message parameter is passed and the last message was deleted', async () => {
+ await expect(user.del(`/groups/${groupWithChat._id}/chat/${nextMessage.id}?previousMsg=${nextMessage.id}`))
+ .to.eventually.be.empty;
+ });
+
+ it('returns the update chat when previous message parameter is passed and the chat is updated', async () => {
+ await expect(user.del(`/groups/${groupWithChat._id}/chat/${nextMessage.id}?previousMsg=${message.id}`))
+ .eventually
+ .is.an('array')
+ .to.include(message)
+ .to.be.lengthOf(1);
+ });
+ });
+});
diff --git a/test/api/v3/integration/chat/GET-chat.test.js b/test/api/v3/integration/chat/GET-chat.test.js
new file mode 100644
index 0000000000..91424d655e
--- /dev/null
+++ b/test/api/v3/integration/chat/GET-chat.test.js
@@ -0,0 +1,65 @@
+import {
+ generateUser,
+ generateGroup,
+ translate as t,
+} from '../../../../helpers/api-v3-integration.helper';
+
+describe('GET /groups/:groupId/chat', () => {
+ let user;
+
+ before(async () => {
+ user = await generateUser();
+ });
+
+ context('public Guild', () => {
+ let group;
+
+ before(async () => {
+ let leader = await generateUser({balance: 2});
+
+ group = await generateGroup(leader, {
+ name: 'test group',
+ type: 'guild',
+ privacy: 'public',
+ }, {
+ chat: [
+ {text: 'Hello', flags: {}},
+ {text: 'Welcome to the Guild', flags: {}},
+ ],
+ });
+ });
+
+ it('returns Guild chat', async () => {
+ let chat = await user.get(`/groups/${group._id}/chat`);
+
+ expect(chat).to.eql(group.chat);
+ });
+ });
+
+ context('private Guild', () => {
+ let group;
+
+ before(async () => {
+ let leader = await generateUser({balance: 2});
+
+ group = await generateGroup(leader, {
+ name: 'test group',
+ type: 'guild',
+ privacy: 'private',
+ }, {
+ chat: [
+ 'Hello',
+ 'Welcome to the Guild',
+ ],
+ });
+ });
+
+ it('returns error if user is not member of requested private group', async () => {
+ await expect(user.get(`/groups/${group._id}/chat`)).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('groupNotFound'),
+ });
+ });
+ });
+});
diff --git a/test/api/v3/integration/chat/POST-chat.flag.test.js b/test/api/v3/integration/chat/POST-chat.flag.test.js
new file mode 100644
index 0000000000..51a3abb164
--- /dev/null
+++ b/test/api/v3/integration/chat/POST-chat.flag.test.js
@@ -0,0 +1,84 @@
+import {
+ generateUser,
+ translate as t,
+} from '../../../../helpers/api-v3-integration.helper';
+import { find } from 'lodash';
+
+describe('POST /chat/:chatId/flag', () => {
+ let user, admin, anotherUser, group;
+ const TEST_MESSAGE = 'Test Message';
+
+ before(async () => {
+ user = await generateUser({balance: 1});
+ admin = await generateUser({balance: 1, 'contributor.admin': true});
+ anotherUser = await generateUser();
+
+ group = await user.post('/groups', {
+ name: 'Test Guild',
+ type: 'guild',
+ privacy: 'public',
+ });
+ });
+
+ it('Returns an error when chat message is not found', async () => {
+ await expect(user.post(`/groups/${group._id}/chat/incorrectMessage/flag`))
+ .to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('messageGroupChatNotFound'),
+ });
+ });
+
+ it('Returns an error when user tries to flag their own message', async () => {
+ let message = await user.post(`/groups/${group._id}/chat`, { message: TEST_MESSAGE });
+ await expect(user.post(`/groups/${group._id}/chat/${message.message.id}/flag`))
+ .to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('messageGroupChatFlagOwnMessage'),
+ });
+ });
+
+ it('Flags a chat', async () => {
+ let message = await anotherUser.post(`/groups/${group._id}/chat`, { message: TEST_MESSAGE});
+ message = message.message;
+
+ let flagResult = await user.post(`/groups/${group._id}/chat/${message.id}/flag`);
+ expect(flagResult.flags[user._id]).to.equal(true);
+ expect(flagResult.flagCount).to.equal(1);
+
+ let groupWithFlags = await admin.get(`/groups/${group._id}`);
+
+ let messageToCheck = find(groupWithFlags.chat, {id: message.id});
+ expect(messageToCheck.flags[user._id]).to.equal(true);
+ });
+
+ it('Flags a chat with a higher flag acount when an admin flags the message', async () => {
+ let message = await user.post(`/groups/${group._id}/chat`, { message: TEST_MESSAGE});
+ message = message.message;
+
+ let flagResult = await admin.post(`/groups/${group._id}/chat/${message.id}/flag`);
+ expect(flagResult.flags[admin._id]).to.equal(true);
+ expect(flagResult.flagCount).to.equal(5);
+
+ let groupWithFlags = await admin.get(`/groups/${group._id}`);
+
+ let messageToCheck = find(groupWithFlags.chat, {id: message.id});
+ expect(messageToCheck.flags[admin._id]).to.equal(true);
+ expect(messageToCheck.flagCount).to.equal(5);
+ });
+
+ it('Returns an error when user tries to flag a message that is already flagged', async () => {
+ let message = await anotherUser.post(`/groups/${group._id}/chat`, { message: TEST_MESSAGE});
+ message = message.message;
+
+ await user.post(`/groups/${group._id}/chat/${message.id}/flag`);
+
+ await expect(user.post(`/groups/${group._id}/chat/${message.id}/flag`))
+ .to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('messageGroupChatFlagAlreadyReported'),
+ });
+ });
+});
diff --git a/test/api/v3/integration/chat/POST-chat.like.test.js b/test/api/v3/integration/chat/POST-chat.like.test.js
new file mode 100644
index 0000000000..d7ae6047df
--- /dev/null
+++ b/test/api/v3/integration/chat/POST-chat.like.test.js
@@ -0,0 +1,75 @@
+import {
+ createAndPopulateGroup,
+ translate as t,
+} from '../../../../helpers/api-v3-integration.helper';
+import { find } from 'lodash';
+
+describe('POST /chat/:chatId/like', () => {
+ let user;
+ let groupWithChat;
+ let testMessage = 'Test Message';
+ let anotherUser;
+
+ before(async () => {
+ let { group, groupLeader, members } = await createAndPopulateGroup({
+ groupDetails: {
+ name: 'Test Guild',
+ type: 'guild',
+ privacy: 'public',
+ },
+ members: 1,
+ });
+
+ user = groupLeader;
+ groupWithChat = group;
+ anotherUser = members[0];
+ });
+
+ it('Returns an error when chat message is not found', async () => {
+ await expect(user.post(`/groups/${groupWithChat._id}/chat/incorrectMessage/like`))
+ .to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('messageGroupChatNotFound'),
+ });
+ });
+
+ it('Returns an error when user tries to like their own message', async () => {
+ let message = await user.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage});
+
+ await expect(user.post(`/groups/${groupWithChat._id}/chat/${message.message.id}/like`))
+ .to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('messageGroupChatLikeOwnMessage'),
+ });
+ });
+
+ it('Likes a chat', async () => {
+ let message = await anotherUser.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage});
+
+ let likeResult = await user.post(`/groups/${groupWithChat._id}/chat/${message.message.id}/like`);
+
+ expect(likeResult.likes[user._id]).to.equal(true);
+
+ let groupWithChatLikes = await user.get(`/groups/${groupWithChat._id}`);
+
+ let messageToCheck = find(groupWithChatLikes.chat, {id: message.message.id});
+ expect(messageToCheck.likes[user._id]).to.equal(true);
+ });
+
+ it('Unlikes a chat', async () => {
+ let message = await anotherUser.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage});
+
+ let likeResult = await user.post(`/groups/${groupWithChat._id}/chat/${message.message.id}/like`);
+ expect(likeResult.likes[user._id]).to.equal(true);
+
+ let unlikeResult = await user.post(`/groups/${groupWithChat._id}/chat/${message.message.id}/like`);
+ expect(unlikeResult.likes[user._id]).to.equal(false);
+
+ let groupWithoutChatLikes = await user.get(`/groups/${groupWithChat._id}`);
+
+ let messageToCheck = find(groupWithoutChatLikes.chat, {id: message.message.id});
+ expect(messageToCheck.likes[user._id]).to.equal(false);
+ });
+});
diff --git a/test/api/v3/integration/chat/POST-chat.test.js b/test/api/v3/integration/chat/POST-chat.test.js
new file mode 100644
index 0000000000..99d1469af8
--- /dev/null
+++ b/test/api/v3/integration/chat/POST-chat.test.js
@@ -0,0 +1,81 @@
+import {
+ createAndPopulateGroup,
+ translate as t,
+} from '../../../../helpers/api-v3-integration.helper';
+
+describe('POST /chat', () => {
+ let user, groupWithChat, userWithChatRevoked, member;
+ let testMessage = 'Test Message';
+
+ before(async () => {
+ let { group, groupLeader, members } = await createAndPopulateGroup({
+ groupDetails: {
+ name: 'Test Guild',
+ type: 'guild',
+ privacy: 'public',
+ },
+ members: 2,
+ });
+
+ user = groupLeader;
+ groupWithChat = group;
+ userWithChatRevoked = await members[0].update({'flags.chatRevoked': true});
+ member = members[0];
+ });
+
+ it('Returns an error when no message is provided', async () => {
+ await expect(user.post(`/groups/${groupWithChat._id}/chat`, { message: ''}))
+ .to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('invalidReqParams'),
+ });
+ });
+
+ it('Returns an error when group is not found', async () => {
+ await expect(user.post('/groups/invalidID/chat', { message: testMessage})).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('groupNotFound'),
+ });
+ });
+
+ it('Returns an error when chat privileges are revoked', async () => {
+ await expect(userWithChatRevoked.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage})).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: 'Your chat privileges have been revoked.',
+ });
+ });
+
+ it('creates a chat', async () => {
+ let message = await user.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage});
+
+ expect(message.message.id).to.exist;
+ });
+
+ it('notifies other users of new messages for a guild', async () => {
+ let message = await user.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage});
+ let memberWithNotification = await member.get('/user');
+
+ expect(message.message.id).to.exist;
+ expect(memberWithNotification.newMessages[`${groupWithChat._id}`]).to.exist;
+ });
+
+ it('notifies other users of new messages for a party', async () => {
+ let { group, groupLeader, members } = await createAndPopulateGroup({
+ groupDetails: {
+ name: 'Test Party',
+ type: 'party',
+ privacy: 'private',
+ },
+ members: 1,
+ });
+
+ let message = await groupLeader.post(`/groups/${group._id}/chat`, { message: testMessage});
+ let memberWithNotification = await members[0].get('/user');
+
+ expect(message.message.id).to.exist;
+ expect(memberWithNotification.newMessages[`${group._id}`]).to.exist;
+ });
+});
diff --git a/test/api/v3/integration/chat/POST-chat_seen.test.js b/test/api/v3/integration/chat/POST-chat_seen.test.js
new file mode 100644
index 0000000000..8b22461a04
--- /dev/null
+++ b/test/api/v3/integration/chat/POST-chat_seen.test.js
@@ -0,0 +1,63 @@
+import {
+ createAndPopulateGroup,
+} from '../../../../helpers/api-v3-integration.helper';
+
+describe('POST /groups/:id/chat/seen', () => {
+ context('Guild', () => {
+ let guild, guildLeader, guildMember, guildMessage;
+
+ before(async () => {
+ let { group, groupLeader, members } = await createAndPopulateGroup({
+ groupDetails: {
+ type: 'guild',
+ privacy: 'public',
+ },
+ members: 1,
+ });
+
+ guild = group;
+ guildLeader = groupLeader;
+ guildMember = members[0];
+
+ guildMessage = await guildLeader.post(`/groups/${guild._id}/chat`, { message: 'Some guild message' });
+ guildMessage = guildMessage.message;
+ });
+
+ it('clears new messages for a guild', async () => {
+ await guildMember.post(`/groups/${guild._id}/chat/seen`);
+
+ let guildThatHasSeenChat = await guildMember.get('/user');
+
+ expect(guildThatHasSeenChat.newMessages).to.be.empty;
+ });
+ });
+
+ context('Party', () => {
+ let party, partyLeader, partyMember, partyMessage;
+
+ before(async () => {
+ let { group, groupLeader, members } = await createAndPopulateGroup({
+ groupDetails: {
+ type: 'party',
+ privacy: 'private',
+ },
+ members: 1,
+ });
+
+ party = group;
+ partyLeader = groupLeader;
+ partyMember = members[0];
+
+ partyMessage = await partyLeader.post(`/groups/${party._id}/chat`, { message: 'Some party message' });
+ partyMessage = partyMessage.message;
+ });
+
+ it('clears new messages for a party', async () => {
+ await partyMember.post(`/groups/${party._id}/chat/seen`);
+
+ let partyMemberThatHasSeenChat = await partyMember.get('/user');
+
+ expect(partyMemberThatHasSeenChat.newMessages).to.be.empty;
+ });
+ });
+});
diff --git a/test/api/v3/integration/chat/POST-groups_id_chat_id_clear_flags.test.js b/test/api/v3/integration/chat/POST-groups_id_chat_id_clear_flags.test.js
new file mode 100644
index 0000000000..87cad5d6f0
--- /dev/null
+++ b/test/api/v3/integration/chat/POST-groups_id_chat_id_clear_flags.test.js
@@ -0,0 +1,101 @@
+import {
+ createAndPopulateGroup,
+ generateUser,
+ translate as t,
+} from '../../../../helpers/api-v3-integration.helper';
+import { v4 as generateUUID } from 'uuid';
+
+describe('POST /groups/:id/chat/:id/clearflags', () => {
+ let groupWithChat, message, author, nonAdmin, admin;
+
+ before(async () => {
+ let { group, groupLeader, members } = await createAndPopulateGroup({
+ groupDetails: {
+ type: 'guild',
+ privacy: 'public',
+ },
+ members: 1,
+ });
+
+ groupWithChat = group;
+ author = groupLeader;
+ nonAdmin = members[0];
+ admin = await generateUser({'contributor.admin': true});
+
+ message = await author.post(`/groups/${groupWithChat._id}/chat`, { message: 'Some message' });
+ message = message.message;
+ admin.post(`/groups/${groupWithChat._id}/chat/${message.id}/flag`);
+ });
+
+ context('Single Message', () => {
+ it('returns error when non-admin attempts to clear flags', async () => {
+ return expect(nonAdmin.post(`/groups/${groupWithChat._id}/chat/${message.id}/clearflags`))
+ .to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('messageGroupChatAdminClearFlagCount'),
+ });
+ });
+
+ it('returns error if message does not exist', async () => {
+ let fakeMessageID = generateUUID();
+
+ await expect(admin.post(`/groups/${groupWithChat._id}/chat/${fakeMessageID}/clearflags`))
+ .to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('messageGroupChatNotFound'),
+ });
+ });
+
+ it('clears flags and leaves old flags on the flag object', async () => {
+ await admin.post(`/groups/${groupWithChat._id}/chat/${message.id}/clearflags`);
+ let messages = await admin.get(`/groups/${groupWithChat._id}/chat`);
+ expect(messages[0].flagCount).to.eql(0);
+ expect(messages[0].flags).to.have.property(admin._id, true);
+ });
+ });
+
+ context('admin user, group with multiple messages', () => {
+ let message2, message3, message4;
+
+ before(async () => {
+ message2 = await author.post(`/groups/${groupWithChat._id}/chat`, { message: 'Some message 2' });
+ message2 = message2.message;
+ await admin.post(`/groups/${groupWithChat._id}/chat/${message2.id}/flag`);
+
+ message3 = await author.post(`/groups/${groupWithChat._id}/chat`, { message: 'Some message 3' });
+ message3 = message3.message;
+ await admin.post(`/groups/${groupWithChat._id}/chat/${message3.id}/flag`);
+ await nonAdmin.post(`/groups/${groupWithChat._id}/chat/${message3.id}/flag`);
+
+ message4 = await author.post(`/groups/${groupWithChat._id}/chat`, { message: 'Some message 4' });
+ message4 = message4.message;
+ });
+
+ it('changes only the message that is flagged', async () => {
+ await admin.post(`/groups/${groupWithChat._id}/chat/${message.id}/clearflags`);
+ let messages = await admin.get(`/groups/${groupWithChat._id}/chat`);
+
+ expect(messages).to.have.lengthOf(4);
+
+ let messageThatWasUnflagged = messages[3];
+ let messageWith1Flag = messages[2];
+ let messageWith2Flag = messages[1];
+ let messageWithoutFlags = messages[0];
+
+ expect(messageThatWasUnflagged.flagCount).to.eql(0);
+ expect(messageThatWasUnflagged.flags).to.have.property(admin._id, true);
+
+ expect(messageWith1Flag.flagCount).to.eql(5);
+ expect(messageWith1Flag.flags).to.have.property(admin._id, true);
+
+ expect(messageWith2Flag.flagCount).to.eql(6);
+ expect(messageWith2Flag.flags).to.have.property(admin._id, true);
+ expect(messageWith2Flag.flags).to.have.property(nonAdmin._id, true);
+
+ expect(messageWithoutFlags.flagCount).to.eql(0);
+ expect(messageWithoutFlags.flags).to.eql({});
+ });
+ });
+});
diff --git a/test/api/v3/integration/content/GET-content.test.js b/test/api/v3/integration/content/GET-content.test.js
new file mode 100644
index 0000000000..9324fce398
--- /dev/null
+++ b/test/api/v3/integration/content/GET-content.test.js
@@ -0,0 +1,25 @@
+import {
+ requester,
+ translate as t,
+} from '../../../../helpers/api-v3-integration.helper';
+import i18n from '../../../../../common/script/i18n';
+
+describe('GET /content', () => {
+ it('returns content (and does not require authentication)', async () => {
+ let res = await requester().get('/content');
+ expect(res).to.have.deep.property('backgrounds.backgrounds062014.beach');
+ expect(res.backgrounds.backgrounds062014.beach.text).to.equal(t('backgroundBeachText'));
+ });
+
+ it('returns content not in English', async () => {
+ let res = await requester().get('/content?language=de');
+ expect(res).to.have.deep.property('backgrounds.backgrounds062014.beach');
+ expect(res.backgrounds.backgrounds062014.beach.text).to.equal(i18n.t('backgroundBeachText', 'de'));
+ });
+
+ it('falls back to English if the desired language is not found', async () => {
+ let res = await requester().get('/content?language=wrong');
+ expect(res).to.have.deep.property('backgrounds.backgrounds062014.beach');
+ expect(res.backgrounds.backgrounds062014.beach.text).to.equal(t('backgroundBeachText'));
+ });
+});
diff --git a/test/api/v3/integration/coupons/GET-coupons.test.js b/test/api/v3/integration/coupons/GET-coupons.test.js
new file mode 100644
index 0000000000..6008d2b1df
--- /dev/null
+++ b/test/api/v3/integration/coupons/GET-coupons.test.js
@@ -0,0 +1,39 @@
+import {
+ generateUser,
+ translate as t,
+ resetHabiticaDB,
+} from '../../../../helpers/api-v3-integration.helper';
+
+describe('GET /coupons/', () => {
+ let user;
+ before(async () => {
+ await resetHabiticaDB();
+ });
+
+ beforeEach(async () => {
+ user = await generateUser();
+ });
+
+ it('returns an error if user has no sudo permission', async () => {
+ await user.get('/user'); // needed so the request after this will authenticate with the correct cookie session
+ await expect(user.get('/coupons')).to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('noSudoAccess'),
+ });
+ });
+
+ it('should return the coupons in CSV format ordered by creation date', async () => {
+ await user.update({
+ 'contributor.sudo': true,
+ });
+
+ let coupons = await user.post('/coupons/generate/wondercon?count=11');
+ let res = await user.get('/coupons');
+ let splitRes = res.split('\n');
+
+ expect(splitRes.length).to.equal(13);
+ expect(splitRes[0]).to.equal('code,event,date,user');
+ expect(splitRes[6].split(',')[1]).to.equal(coupons[5].event);
+ });
+});
diff --git a/test/api/v3/integration/coupons/POST-coupons_enter_code.test.js b/test/api/v3/integration/coupons/POST-coupons_enter_code.test.js
new file mode 100644
index 0000000000..e1f9db3583
--- /dev/null
+++ b/test/api/v3/integration/coupons/POST-coupons_enter_code.test.js
@@ -0,0 +1,62 @@
+import {
+ generateUser,
+ translate as t,
+ resetHabiticaDB,
+} from '../../../../helpers/api-v3-integration.helper';
+
+describe('POST /coupons/enter/:code', () => {
+ let user;
+ let sudoUser;
+
+ before(async () => {
+ await resetHabiticaDB();
+ });
+
+ beforeEach(async () => {
+ user = await generateUser();
+ sudoUser = await generateUser({
+ 'contributor.sudo': true,
+ });
+ });
+
+ it('returns an error if code is missing', async () => {
+ await expect(user.post('/coupons/enter')).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: 'Not found.',
+ });
+ });
+
+ it('returns an error if code is invalid', async () => {
+ await expect(user.post('/coupons/enter/notValid')).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('invalidCoupon'),
+ });
+ });
+
+ it('returns an error if coupon has been used', async () => {
+ let [coupon] = await sudoUser.post('/coupons/generate/wondercon?count=1');
+ await user.post(`/coupons/enter/${coupon._id}`); // use coupon
+
+ await expect(user.post(`/coupons/enter/${coupon._id}`)).to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('couponUsed'),
+ });
+ });
+
+ it('should apply the coupon to the user', async () => {
+ let [coupon] = await sudoUser.post('/coupons/generate/wondercon?count=1');
+ let userRes = await user.post(`/coupons/enter/${coupon._id}`);
+ expect(userRes._id).to.equal(user._id);
+ expect(userRes.items.gear.owned.eyewear_special_wondercon_red).to.be.true;
+ expect(userRes.items.gear.owned.eyewear_special_wondercon_black).to.be.true;
+ expect(userRes.items.gear.owned.back_special_wondercon_black).to.be.true;
+ expect(userRes.items.gear.owned.back_special_wondercon_red).to.be.true;
+ expect(userRes.items.gear.owned.body_special_wondercon_red).to.be.true;
+ expect(userRes.items.gear.owned.body_special_wondercon_black).to.be.true;
+ expect(userRes.items.gear.owned.body_special_wondercon_gold).to.be.true;
+ expect(userRes.extra).to.eql({signupEvent: 'wondercon'});
+ });
+});
diff --git a/test/api/v3/integration/coupons/POST-coupons_generate_event.test.js b/test/api/v3/integration/coupons/POST-coupons_generate_event.test.js
new file mode 100644
index 0000000000..27bbc5c4f7
--- /dev/null
+++ b/test/api/v3/integration/coupons/POST-coupons_generate_event.test.js
@@ -0,0 +1,66 @@
+import {
+ generateUser,
+ translate as t,
+ resetHabiticaDB,
+} from '../../../../helpers/api-v3-integration.helper';
+import couponCode from 'coupon-code';
+
+describe('POST /coupons/generate/:event', () => {
+ let user;
+ before(async () => {
+ await resetHabiticaDB();
+ });
+
+ beforeEach(async () => {
+ user = await generateUser({
+ 'contributor.sudo': true,
+ });
+ });
+
+ it('returns an error if user has no sudo permission', async () => {
+ await user.update({
+ 'contributor.sudo': false,
+ });
+
+ await expect(user.post('/coupons/generate/aaa')).to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('noSudoAccess'),
+ });
+ });
+
+ it('returns an error if event is missing', async () => {
+ await expect(user.post('/coupons/generate')).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: 'Not found.',
+ });
+ });
+
+ it('returns an error if event is invalid', async () => {
+ await expect(user.post('/coupons/generate/notValid?count=1')).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: 'Coupon validation failed',
+ });
+ });
+
+ it('returns an error if count is missing', async () => {
+ await expect(user.post('/coupons/generate/notValid')).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('invalidReqParams'),
+ });
+ });
+
+ it('should generate coupons', async () => {
+ await user.update({
+ 'contributor.sudo': true,
+ });
+
+ let coupons = await user.post('/coupons/generate/wondercon?count=2');
+ expect(coupons.length).to.equal(2);
+ expect(coupons[0].event).to.equal('wondercon');
+ expect(couponCode.validate(coupons[1]._id)).to.not.equal(''); // '' means invalid
+ });
+});
diff --git a/test/api/v3/integration/coupons/POST-coupons_validate_code.test.js b/test/api/v3/integration/coupons/POST-coupons_validate_code.test.js
new file mode 100644
index 0000000000..9d433813ea
--- /dev/null
+++ b/test/api/v3/integration/coupons/POST-coupons_validate_code.test.js
@@ -0,0 +1,36 @@
+import {
+ generateUser,
+ requester,
+ resetHabiticaDB,
+} from '../../../../helpers/api-v3-integration.helper';
+
+describe('POST /coupons/validate/:code', () => {
+ let api = requester();
+
+ before(async () => {
+ await resetHabiticaDB();
+ });
+
+ it('returns an error if code is missing', async () => {
+ await expect(api.post('/coupons/validate')).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: 'Not found.',
+ });
+ });
+
+ it('returns true if coupon code is valid', async () => {
+ let sudoUser = await generateUser({
+ 'contributor.sudo': true,
+ });
+
+ let [coupon] = await sudoUser.post('/coupons/generate/wondercon?count=1');
+ let res = await api.post(`/coupons/validate/${coupon._id}`);
+ expect(res).to.eql({valid: true});
+ });
+
+ it('returns false if coupon code is valid', async () => {
+ let res = await api.post('/coupons/validate/notValid');
+ expect(res).to.eql({valid: false});
+ });
+});
diff --git a/test/api/v3/integration/dataexport/GET-export_avatar-memberId.html.test.js b/test/api/v3/integration/dataexport/GET-export_avatar-memberId.html.test.js
new file mode 100644
index 0000000000..a7b97390b6
--- /dev/null
+++ b/test/api/v3/integration/dataexport/GET-export_avatar-memberId.html.test.js
@@ -0,0 +1,35 @@
+import {
+ generateUser,
+ translate as t,
+} from '../../../../helpers/api-v3-integration.helper';
+import { v4 as generateUUID } from 'uuid';
+
+describe('GET /export/avatar-:memberId.html', () => {
+ let user;
+
+ before(async () => {
+ user = await generateUser();
+ });
+
+ it('validates req.params.memberId', async () => {
+ await expect(user.get('/export/avatar-:memberId.html')).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('invalidReqParams'),
+ });
+ });
+
+ it('handles non-existing members', async () => {
+ let dummyId = generateUUID();
+ await expect(user.get(`/export/avatar-${dummyId}.html`)).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('userWithIDNotFound', {userId: dummyId}),
+ });
+ });
+
+ it('returns an html page', async () => {
+ let res = await user.get(`/export/avatar-${user._id}.html`);
+ expect(res.substring(0, 100).indexOf('')).to.equal(0);
+ });
+});
diff --git a/test/api/v3/integration/dataexport/GET-export_avatar-memberId.png.test.js b/test/api/v3/integration/dataexport/GET-export_avatar-memberId.png.test.js
new file mode 100644
index 0000000000..a46ed695c6
--- /dev/null
+++ b/test/api/v3/integration/dataexport/GET-export_avatar-memberId.png.test.js
@@ -0,0 +1,3 @@
+// TODO how to test this route since it points to a file on AWS s3?
+
+describe('GET /export/avatar-:memberId.png', () => {});
diff --git a/test/api/v3/integration/dataexport/GET-export_history.csv.test.js b/test/api/v3/integration/dataexport/GET-export_history.csv.test.js
new file mode 100644
index 0000000000..2dbc80c49d
--- /dev/null
+++ b/test/api/v3/integration/dataexport/GET-export_history.csv.test.js
@@ -0,0 +1,47 @@
+import {
+ generateUser,
+} from '../../../../helpers/api-v3-integration.helper';
+import {
+ updateDocument,
+} from '../../../../helpers/mongo';
+import moment from 'moment';
+
+describe('GET /export/history.csv', () => {
+ it('should return a valid CSV file with tasks history data', async () => {
+ let user = await generateUser();
+ let tasks = await user.post('/tasks/user', [
+ {type: 'habit', text: 'habit 1'},
+ {type: 'daily', text: 'daily 1'},
+ {type: 'habit', text: 'habit 2'},
+ {type: 'todo', text: 'todo 1'},
+ ]);
+
+ // score all the tasks twice
+ await Promise.all(tasks.map(task => {
+ return user.post(`/tasks/${task._id}/score/up`);
+ }));
+ await Promise.all(tasks.map(task => {
+ return user.post(`/tasks/${task._id}/score/up`);
+ }));
+
+ // adding an history entry to daily 1 manually because cron didn't run yet
+ await updateDocument('tasks', tasks[1], {
+ history: {value: 3.2, date: Number(new Date())},
+ });
+
+ // get updated tasks
+ tasks = await Promise.all(tasks.map(task => {
+ return user.get(`/tasks/${task._id}`);
+ }));
+
+ let res = await user.get('/export/history.csv');
+ let splitRes = res.split('\n');
+ expect(splitRes[0]).to.equal('Task Name,Task ID,Task Type,Date,Value');
+ expect(splitRes[1]).to.equal(`habit 1,${tasks[0]._id},habit,${moment(tasks[0].history[0].date).format('YYYY-MM-DD HH:mm:ss')},${tasks[0].history[0].value}`);
+ expect(splitRes[2]).to.equal(`habit 1,${tasks[0]._id},habit,${moment(tasks[0].history[1].date).format('YYYY-MM-DD HH:mm:ss')},${tasks[0].history[1].value}`);
+ expect(splitRes[3]).to.equal(`daily 1,${tasks[1]._id},daily,${moment(tasks[1].history[0].date).format('YYYY-MM-DD HH:mm:ss')},${tasks[1].history[0].value}`);
+ expect(splitRes[4]).to.equal(`habit 2,${tasks[2]._id},habit,${moment(tasks[2].history[0].date).format('YYYY-MM-DD HH:mm:ss')},${tasks[2].history[0].value}`);
+ expect(splitRes[5]).to.equal(`habit 2,${tasks[2]._id},habit,${moment(tasks[2].history[1].date).format('YYYY-MM-DD HH:mm:ss')},${tasks[2].history[1].value}`);
+ expect(splitRes[6]).to.equal('');
+ });
+});
diff --git a/test/api/v3/integration/dataexport/GET-export_userdata.json.test.js b/test/api/v3/integration/dataexport/GET-export_userdata.json.test.js
new file mode 100644
index 0000000000..d8152f1209
--- /dev/null
+++ b/test/api/v3/integration/dataexport/GET-export_userdata.json.test.js
@@ -0,0 +1,29 @@
+import {
+ generateUser,
+} from '../../../../helpers/api-v3-integration.helper';
+
+describe('GET /export/userdata.json', () => {
+ it('should return a valid JSON file with user data', async () => {
+ let user = await generateUser();
+ let tasks = await user.post('/tasks/user', [
+ {type: 'habit', text: 'habit 1'},
+ {type: 'daily', text: 'daily 1'},
+ {type: 'reward', text: 'reward 1'},
+ {type: 'todo', text: 'todo 1'},
+ ]);
+
+ let res = await user.get('/export/userdata.json');
+ expect(res._id).to.equal(user._id);
+ expect(res).to.contain.all.keys(['tasks', 'flags', 'tasksOrder', 'auth']);
+ expect(res.auth.local).not.to.have.keys(['salt', 'hashed_password']);
+ expect(res.tasks).to.have.all.keys(['dailys', 'habits', 'todos', 'rewards']);
+ expect(res.tasks.habits.length).to.equal(1);
+ expect(res.tasks.habits[0]._id).to.equal(tasks[0]._id);
+ expect(res.tasks.dailys.length).to.equal(1);
+ expect(res.tasks.dailys[0]._id).to.equal(tasks[1]._id);
+ expect(res.tasks.rewards.length).to.equal(1);
+ expect(res.tasks.rewards[0]._id).to.equal(tasks[2]._id);
+ expect(res.tasks.todos.length).to.equal(2);
+ expect(res.tasks.todos[1]._id).to.equal(tasks[3]._id);
+ });
+});
diff --git a/test/api/v3/integration/dataexport/GET-export_userdata.xml.test.js b/test/api/v3/integration/dataexport/GET-export_userdata.xml.test.js
new file mode 100644
index 0000000000..58bf4e6135
--- /dev/null
+++ b/test/api/v3/integration/dataexport/GET-export_userdata.xml.test.js
@@ -0,0 +1,42 @@
+import {
+ generateUser,
+} from '../../../../helpers/api-v3-integration.helper';
+import xml2js from 'xml2js';
+import Bluebird from 'bluebird';
+
+let parseStringAsync = Bluebird.promisify(xml2js.parseString, {context: xml2js});
+
+describe('GET /export/userdata.xml', () => {
+ it('should return a valid XML file with user data', async () => {
+ let user = await generateUser();
+ let tasks = await user.post('/tasks/user', [
+ {type: 'habit', text: 'habit 1'},
+ {type: 'daily', text: 'daily 1'},
+ {type: 'reward', text: 'reward 1'},
+ {type: 'todo', text: 'todo 1'},
+ // due to how the xml parser works an array is returned only if there's more than one children
+ // so we create two tasks for each type
+ {type: 'habit', text: 'habit 2'},
+ {type: 'daily', text: 'daily 2'},
+ {type: 'reward', text: 'reward 2'},
+ {type: 'todo', text: 'todo 2'},
+
+ ]);
+
+ let response = await user.get('/export/userdata.xml');
+ let {user: res} = await parseStringAsync(response, {explicitArray: false});
+
+ expect(res._id).to.equal(user._id);
+ expect(res).to.contain.all.keys(['tasks', 'flags', 'tasksOrder', 'auth']);
+ expect(res.auth.local).not.to.have.keys(['salt', 'hashed_password']);
+ expect(res.tasks).to.have.all.keys(['dailys', 'habits', 'todos', 'rewards']);
+ expect(res.tasks.habits.length).to.equal(2);
+ expect(res.tasks.habits[0]._id).to.equal(tasks[0]._id);
+ expect(res.tasks.dailys.length).to.equal(2);
+ expect(res.tasks.dailys[0]._id).to.equal(tasks[1]._id);
+ expect(res.tasks.rewards.length).to.equal(2);
+ expect(res.tasks.rewards[0]._id).to.equal(tasks[2]._id);
+ expect(res.tasks.todos.length).to.equal(3);
+ expect(res.tasks.todos[1]._id).to.equal(tasks[3]._id);
+ });
+});
diff --git a/test/api/v3/integration/debug/POST-debug_addHourglass.test.js b/test/api/v3/integration/debug/POST-debug_addHourglass.test.js
new file mode 100644
index 0000000000..767fa840f2
--- /dev/null
+++ b/test/api/v3/integration/debug/POST-debug_addHourglass.test.js
@@ -0,0 +1,35 @@
+import nconf from 'nconf';
+import {
+ generateUser,
+} from '../../../../helpers/api-v3-integration.helper';
+
+describe('POST /debug/add-hourglass', () => {
+ let userToGetHourGlass;
+
+ before(async () => {
+ userToGetHourGlass = await generateUser();
+ });
+
+ after(() => {
+ nconf.set('IS_PROD', false);
+ });
+
+ it('adds Hourglass to the current user', async () => {
+ await userToGetHourGlass.post('/debug/add-hourglass');
+
+ let userWithHourGlass = await userToGetHourGlass.get('/user');
+
+ expect(userWithHourGlass.purchased.plan.consecutive.trinkets).to.equal(1);
+ });
+
+ it('returns error when not in production mode', async () => {
+ nconf.set('IS_PROD', true);
+
+ await expect(userToGetHourGlass.post('/debug/add-hourglass'))
+ .eventually.be.rejected.and.to.deep.equal({
+ code: 404,
+ error: 'NotFound',
+ message: 'Not found.',
+ });
+ });
+});
diff --git a/test/api/v3/integration/debug/POST-debug_addTenGems.test.js b/test/api/v3/integration/debug/POST-debug_addTenGems.test.js
new file mode 100644
index 0000000000..fd01aea5d3
--- /dev/null
+++ b/test/api/v3/integration/debug/POST-debug_addTenGems.test.js
@@ -0,0 +1,35 @@
+import nconf from 'nconf';
+import {
+ generateUser,
+} from '../../../../helpers/api-v3-integration.helper';
+
+describe('POST /debug/add-ten-gems', () => {
+ let userToGainTenGems;
+
+ before(async () => {
+ userToGainTenGems = await generateUser();
+ });
+
+ after(() => {
+ nconf.set('IS_PROD', false);
+ });
+
+ it('adds ten gems to the current user', async () => {
+ await userToGainTenGems.post('/debug/add-ten-gems');
+
+ let userWithTenGems = await userToGainTenGems.get('/user');
+
+ expect(userWithTenGems.balance).to.equal(2.5);
+ });
+
+ it('returns error when not in production mode', async () => {
+ nconf.set('IS_PROD', true);
+
+ await expect(userToGainTenGems.post('/debug/add-ten-gems'))
+ .eventually.be.rejected.and.to.deep.equal({
+ code: 404,
+ error: 'NotFound',
+ message: 'Not found.',
+ });
+ });
+});
diff --git a/test/api/v3/integration/debug/POST-debug_make-admin.test.js b/test/api/v3/integration/debug/POST-debug_make-admin.test.js
new file mode 100644
index 0000000000..98818fb07e
--- /dev/null
+++ b/test/api/v3/integration/debug/POST-debug_make-admin.test.js
@@ -0,0 +1,35 @@
+import nconf from 'nconf';
+import {
+ generateUser,
+} from '../../../../helpers/api-v3-integration.helper';
+
+describe('POST /debug/make-admin (pended for v3 prod testing)', () => {
+ let user;
+
+ before(async () => {
+ user = await generateUser();
+ });
+
+ afterEach(() => {
+ nconf.set('IS_PROD', false);
+ });
+
+ it('makes user an admine', async () => {
+ await user.post('/debug/make-admin');
+
+ await user.sync();
+
+ expect(user.contributor.admin).to.eql(true);
+ });
+
+ it('returns error when not in production mode', async () => {
+ nconf.set('IS_PROD', true);
+
+ await expect(user.post('/debug/make-admin'))
+ .eventually.be.rejected.and.to.deep.equal({
+ code: 404,
+ error: 'NotFound',
+ message: 'Not found.',
+ });
+ });
+});
diff --git a/test/api/v3/integration/debug/POST-debug_modify-inventory.test.js b/test/api/v3/integration/debug/POST-debug_modify-inventory.test.js
new file mode 100644
index 0000000000..93f9081492
--- /dev/null
+++ b/test/api/v3/integration/debug/POST-debug_modify-inventory.test.js
@@ -0,0 +1,160 @@
+/* eslint-disable camelcase */
+
+import nconf from 'nconf';
+import {
+ generateUser,
+} from '../../../../helpers/api-v3-integration.helper';
+
+describe('POST /debug/modify-inventory', () => {
+ let user, originalItems;
+
+ before(async () => {
+ originalItems = {
+ gear: { owned: { armor_base_0: true } },
+ special: {
+ snowball: 1,
+ },
+ pets: {
+ 'Wolf-Desert': 5,
+ },
+ mounts: {
+ 'Wolf-Desert': true,
+ },
+ eggs: {
+ Wolf: 5,
+ },
+ hatchingPotions: {
+ Desert: 5,
+ },
+ food: {
+ Watermelon: 5,
+ },
+ quests: {
+ gryphon: 5,
+ },
+ };
+ user = await generateUser({
+ items: originalItems,
+ });
+ });
+
+ afterEach(() => {
+ nconf.set('IS_PROD', false);
+ });
+
+ it('sets equipment', async () => {
+ let gear = {
+ weapon_healer_2: true,
+ weapon_wizard_1: true,
+ weapon_special_critical: true,
+ };
+
+ await user.post('/debug/modify-inventory', {
+ gear,
+ });
+
+ await user.sync();
+
+ expect(user.items.gear.owned).to.eql(gear);
+ });
+
+ it('sets special spells', async () => {
+ let special = {
+ shinySeed: 3,
+ };
+
+ await user.post('/debug/modify-inventory', {
+ special,
+ });
+
+ await user.sync();
+
+ expect(user.items.special).to.eql(special);
+ });
+
+ it('sets mounts', async () => {
+ let mounts = {
+ 'Orca-Base': true,
+ 'Mammoth-Base': true,
+ };
+
+ await user.post('/debug/modify-inventory', {
+ mounts,
+ });
+
+ await user.sync();
+
+ expect(user.items.mounts).to.eql(mounts);
+ });
+
+ it('sets eggs', async () => {
+ let eggs = {
+ Gryphon: 3,
+ Hedgehog: 7,
+ };
+
+ await user.post('/debug/modify-inventory', {
+ eggs,
+ });
+
+ await user.sync();
+
+ expect(user.items.eggs).to.eql(eggs);
+ });
+
+ it('sets hatching potions', async () => {
+ let hatchingPotions = {
+ White: 7,
+ Spooky: 2,
+ };
+
+ await user.post('/debug/modify-inventory', {
+ hatchingPotions,
+ });
+
+ await user.sync();
+
+ expect(user.items.hatchingPotions).to.eql(hatchingPotions);
+ });
+
+ it('sets food', async () => {
+ let food = {
+ Meat: 5,
+ Candy_Red: 7,
+ };
+
+ await user.post('/debug/modify-inventory', {
+ food,
+ });
+
+ await user.sync();
+
+ expect(user.items.food).to.eql(food);
+ });
+
+ it('sets quests', async () => {
+ let quests = {
+ whale: 5,
+ cheetah: 10,
+ };
+
+ await user.post('/debug/modify-inventory', {
+ quests,
+ });
+
+ await user.sync();
+
+ expect(user.items.quests).to.eql(quests);
+ });
+
+ it('returns error when not in production mode', async () => {
+ nconf.set('IS_PROD', true);
+
+ await expect(user.post('/debug/modify-inventory'))
+ .eventually.be.rejected.and.to.deep.equal({
+ code: 404,
+ error: 'NotFound',
+ message: 'Not found.',
+ });
+ });
+});
diff --git a/test/api/v3/integration/debug/POST-debug_quest-progress.test.js b/test/api/v3/integration/debug/POST-debug_quest-progress.test.js
new file mode 100644
index 0000000000..3ae3d48882
--- /dev/null
+++ b/test/api/v3/integration/debug/POST-debug_quest-progress.test.js
@@ -0,0 +1,63 @@
+import nconf from 'nconf';
+import {
+ generateUser,
+} from '../../../../helpers/api-v3-integration.helper';
+
+describe('POST /debug/quest-progress', () => {
+ let user;
+
+ beforeEach(async () => {
+ user = await generateUser();
+ });
+
+ afterEach(() => {
+ nconf.set('IS_PROD', false);
+ });
+
+ it('errors if user is not on a quest', async () => {
+ await expect(user.post('/debug/quest-progress'))
+ .to.eventually.be.rejected.and.to.deep.equal({
+ code: 400,
+ error: 'BadRequest',
+ message: 'User is not on a valid quest.',
+ });
+ });
+
+ it('increases boss quest progress by 1000', async () => {
+ await user.update({
+ 'party.quest.key': 'whale',
+ });
+
+ await user.post('/debug/quest-progress');
+
+ await user.sync();
+
+ expect(user.party.quest.progress.up).to.eql(1000);
+ });
+
+ it('increases collection quest progress by 300 items', async () => {
+ await user.update({
+ 'party.quest.key': 'evilsanta2',
+ });
+
+ await user.post('/debug/quest-progress');
+
+ await user.sync();
+
+ expect(user.party.quest.progress.collect).to.eql({
+ tracks: 300,
+ branches: 300,
+ });
+ });
+
+ it('returns error when not in production mode', async () => {
+ nconf.set('IS_PROD', true);
+
+ await expect(user.post('/debug/quest-progress'))
+ .eventually.be.rejected.and.to.deep.equal({
+ code: 404,
+ error: 'NotFound',
+ message: 'Not found.',
+ });
+ });
+});
diff --git a/test/api/v3/integration/debug/POST-debug_set-cron.test.js b/test/api/v3/integration/debug/POST-debug_set-cron.test.js
new file mode 100644
index 0000000000..c737831d95
--- /dev/null
+++ b/test/api/v3/integration/debug/POST-debug_set-cron.test.js
@@ -0,0 +1,39 @@
+import nconf from 'nconf';
+import {
+ generateUser,
+} from '../../../../helpers/api-v3-integration.helper';
+
+describe('POST /debug/set-cron', () => {
+ let user;
+
+ before(async () => {
+ user = await generateUser();
+ });
+
+ afterEach(() => {
+ nconf.set('IS_PROD', false);
+ });
+
+ it('sets last cron', async () => {
+ let newCron = new Date(2015, 11, 20);
+
+ await user.post('/debug/set-cron', {
+ lastCron: newCron,
+ });
+
+ await user.sync();
+
+ expect(user.lastCron).to.eql(newCron);
+ });
+
+ it('returns error when not in production mode', async () => {
+ nconf.set('IS_PROD', true);
+
+ await expect(user.post('/debug/set-cron'))
+ .eventually.be.rejected.and.to.deep.equal({
+ code: 404,
+ error: 'NotFound',
+ message: 'Not found.',
+ });
+ });
+});
diff --git a/test/api/v3/integration/emails/GET-email-unsubscribe.test.js b/test/api/v3/integration/emails/GET-email-unsubscribe.test.js
new file mode 100644
index 0000000000..1bd3a532fa
--- /dev/null
+++ b/test/api/v3/integration/emails/GET-email-unsubscribe.test.js
@@ -0,0 +1,68 @@
+import {
+ generateUser,
+ translate as t,
+} from '../../../../helpers/api-v3-integration.helper';
+import { encrypt } from '../../../../../website/server/libs/api-v3/encryption';
+import { v4 as generateUUID } from 'uuid';
+
+describe('GET /email/unsubscribe', () => {
+ let user;
+ let testEmail = 'test@habitica.com';
+
+ beforeEach(async () => {
+ user = await generateUser();
+ });
+
+ it('return error when code is not provided', async () => {
+ await expect(user.get('/email/unsubscribe')).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: 'Invalid request parameters.',
+ });
+ });
+
+ it('return error when user is not found', async () => {
+ let code = encrypt(JSON.stringify({
+ _id: generateUUID(),
+ }));
+
+ await expect(user.get(`/email/unsubscribe?code=${code}`)).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('userNotFound'),
+ });
+ });
+
+ it('unsubscribes a user from email notifications', async () => {
+ let code = encrypt(JSON.stringify({
+ _id: user._id,
+ email: user.email,
+ }));
+
+ await user.get(`/email/unsubscribe?code=${code}`);
+
+ let unsubscribedUser = await user.get('/user');
+
+ expect(unsubscribedUser.preferences.emailNotifications.unsubscribeFromAll).to.be.true;
+ });
+
+ it('unsubscribes an email from notifications', async () => {
+ let code = encrypt(JSON.stringify({
+ email: testEmail,
+ }));
+
+ let unsubscribedMessage = await user.get(`/email/unsubscribe?code=${code}`);
+
+ expect(unsubscribedMessage).to.equal('
Unsubscribed successfully!
You won\'t receive any other email from Habitica.');
+ });
+
+ it('returns okay when email is already unsubscribed', async () => {
+ let code = encrypt(JSON.stringify({
+ email: testEmail,
+ }));
+
+ let unsubscribedMessage = await user.get(`/email/unsubscribe?code=${code}`);
+
+ expect(unsubscribedMessage).to.equal('
Unsubscribed successfully!
You won\'t receive any other email from Habitica.');
+ });
+});
diff --git a/test/api/v3/integration/groups/GET-groups.test.js b/test/api/v3/integration/groups/GET-groups.test.js
new file mode 100644
index 0000000000..8e1e9dfbfc
--- /dev/null
+++ b/test/api/v3/integration/groups/GET-groups.test.js
@@ -0,0 +1,115 @@
+import {
+ generateUser,
+ resetHabiticaDB,
+ generateGroup,
+ translate as t,
+} from '../../../../helpers/api-v3-integration.helper';
+import {
+ TAVERN_ID,
+} from '../../../../../website/server/models/group';
+
+describe('GET /groups', () => {
+ let user;
+ const NUMBER_OF_PUBLIC_GUILDS = 3; // 2 + the tavern
+ const NUMBER_OF_PUBLIC_GUILDS_USER_IS_MEMBER = 1;
+ const NUMBER_OF_USERS_PRIVATE_GUILDS = 1;
+ const NUMBER_OF_GROUPS_USER_CAN_VIEW = 5;
+
+ before(async () => {
+ await resetHabiticaDB();
+
+ let leader = await generateUser({ balance: 10 });
+ user = await generateUser({balance: 4});
+
+ let publicGuildUserIsMemberOf = await generateGroup(leader, {
+ name: 'public guild - is member',
+ type: 'guild',
+ privacy: 'public',
+ });
+ await leader.post(`/groups/${publicGuildUserIsMemberOf._id}/invite`, { uuids: [user._id]});
+ await user.post(`/groups/${publicGuildUserIsMemberOf._id}/join`);
+
+ await generateGroup(leader, {
+ name: 'public guild - is not member',
+ type: 'guild',
+ privacy: 'public',
+ });
+
+ let privateGuildUserIsMemberOf = await generateGroup(leader, {
+ name: 'private guild - is member',
+ type: 'guild',
+ privacy: 'private',
+ });
+ await leader.post(`/groups/${privateGuildUserIsMemberOf._id}/invite`, { uuids: [user._id]});
+ await user.post(`/groups/${privateGuildUserIsMemberOf._id}/join`);
+
+ await generateGroup(leader, {
+ name: 'private guild - is not member',
+ type: 'guild',
+ privacy: 'private',
+ });
+
+ await generateGroup(leader, {
+ name: 'party - is not member',
+ type: 'party',
+ privacy: 'private',
+ });
+
+ await user.post('/groups', {
+ name: 'party - is member',
+ type: 'party',
+ privacy: 'private',
+ });
+ });
+
+ it('returns error when no query passed in', async () => {
+ await expect(user.get('/groups'))
+ .to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: 'Invalid request parameters.',
+ });
+ });
+
+ it('returns error when an invalid ?type query is passed', async () => {
+ await expect(user.get('/groups?type=invalid'))
+ .to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('groupTypesRequired'),
+ });
+ });
+
+ it('returns only the tavern when tavern passed in as query', async () => {
+ await expect(user.get('/groups?type=tavern'))
+ .to.eventually.have.a.lengthOf(1)
+ .and.to.have.deep.property('[0]')
+ .and.to.have.property('_id', TAVERN_ID);
+ });
+
+ it('returns only the user\'s party when party passed in as query', async () => {
+ await expect(user.get('/groups?type=party'))
+ .to.eventually.have.a.lengthOf(1)
+ .and.to.have.deep.property('[0]');
+ });
+
+ it('returns all public guilds when publicGuilds passed in as query', async () => {
+ await expect(user.get('/groups?type=publicGuilds'))
+ .to.eventually.have.a.lengthOf(NUMBER_OF_PUBLIC_GUILDS);
+ });
+
+ it('returns all the user\'s guilds when guilds passed in as query', async () => {
+ await expect(user.get('/groups?type=guilds'))
+ .to.eventually.have.a.lengthOf(NUMBER_OF_PUBLIC_GUILDS_USER_IS_MEMBER + NUMBER_OF_USERS_PRIVATE_GUILDS);
+ });
+
+ it('returns all private guilds user is a part of when privateGuilds passed in as query', async () => {
+ await expect(user.get('/groups?type=privateGuilds'))
+ .to.eventually.have.a.lengthOf(NUMBER_OF_USERS_PRIVATE_GUILDS);
+ });
+
+ it('returns a list of groups user has access to', async () => {
+ await expect(user.get('/groups?type=privateGuilds,publicGuilds,party,tavern'))
+ .to.eventually.have.lengthOf(NUMBER_OF_GROUPS_USER_CAN_VIEW);
+ });
+});
diff --git a/test/api/v3/integration/groups/GET-groups_groupId_invites.test.js b/test/api/v3/integration/groups/GET-groups_groupId_invites.test.js
new file mode 100644
index 0000000000..1b7c172d94
--- /dev/null
+++ b/test/api/v3/integration/groups/GET-groups_groupId_invites.test.js
@@ -0,0 +1,102 @@
+import {
+ generateUser,
+ generateGroup,
+ translate as t,
+} from '../../../../helpers/api-v3-integration.helper';
+import { v4 as generateUUID } from 'uuid';
+
+describe('GET /groups/:groupId/invites', () => {
+ let user;
+
+ beforeEach(async () => {
+ user = await generateUser();
+ });
+
+ it('validates optional req.query.lastId to be an UUID', async () => {
+ await expect(user.get('/groups/groupId/invites?lastId=invalidUUID')).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('invalidReqParams'),
+ });
+ });
+
+ it('fails if group doesn\'t exists', async () => {
+ await expect(user.get(`/groups/${generateUUID()}/invites`)).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('groupNotFound'),
+ });
+ });
+
+ it('fails if user doesn\'t have access to the group', async () => {
+ let group = await generateGroup(user, {type: 'party', name: generateUUID()});
+ let anotherUser = await generateUser();
+ await expect(anotherUser.get(`/groups/${group._id}/invites`)).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('groupNotFound'),
+ });
+ });
+
+ it('works when passing party as req.params.groupId', async () => {
+ let group = await generateGroup(user, {type: 'party', name: generateUUID()});
+ let invited = await generateUser();
+ await user.post(`/groups/${group._id}/invite`, {uuids: [invited._id]});
+ let res = await user.get('/groups/party/invites');
+
+ expect(res).to.be.an('array');
+ expect(res.length).to.equal(1);
+ expect(res[0]).to.eql({
+ _id: invited._id,
+ id: invited._id,
+ profile: {name: invited.profile.name},
+ });
+ });
+
+ it('populates only some fields', async () => {
+ let group = await generateGroup(user, {type: 'party', name: generateUUID()});
+ let invited = await generateUser();
+ await user.post(`/groups/${group._id}/invite`, {uuids: [invited._id]});
+ let res = await user.get('/groups/party/invites');
+ expect(res[0]).to.have.all.keys(['_id', 'id', 'profile']);
+ expect(res[0].profile).to.have.all.keys(['name']);
+ });
+
+ it('returns only first 30 invites', async () => {
+ let group = await generateGroup(user, {type: 'party', name: generateUUID()});
+ let invitesToGenerate = [];
+ for (let i = 0; i < 31; i++) {
+ invitesToGenerate.push(generateUser());
+ }
+ let generatedInvites = await Promise.all(invitesToGenerate);
+ await user.post(`/groups/${group._id}/invite`, {uuids: generatedInvites.map(invite => invite._id)});
+
+ let res = await user.get('/groups/party/invites');
+ expect(res.length).to.equal(30);
+ res.forEach(member => {
+ expect(member).to.have.all.keys(['_id', 'id', 'profile']);
+ expect(member.profile).to.have.all.keys(['name']);
+ });
+ });
+
+ it('supports using req.query.lastId to get more invites', async () => {
+ let leader = await generateUser({balance: 4});
+ let group = await generateGroup(leader, {type: 'guild', privacy: 'public', name: generateUUID()});
+
+ let invitesToGenerate = [];
+ for (let i = 0; i < 32; i++) {
+ invitesToGenerate.push(generateUser());
+ }
+ let generatedInvites = await Promise.all(invitesToGenerate); // Group has 32 invites
+ let expectedIds = generatedInvites.map(generatedInvite => generatedInvite._id);
+ await user.post(`/groups/${group._id}/invite`, {uuids: expectedIds});
+
+ let res = await user.get(`/groups/${group._id}/invites`);
+ expect(res.length).to.equal(30);
+ let res2 = await user.get(`/groups/${group._id}/invites?lastId=${res[res.length - 1]._id}`);
+ expect(res2.length).to.equal(2);
+
+ let resIds = res.concat(res2).map(invite => invite._id);
+ expect(resIds).to.eql(expectedIds.sort());
+ });
+});
diff --git a/test/api/v3/integration/groups/GET-groups_groupId_members.test.js b/test/api/v3/integration/groups/GET-groups_groupId_members.test.js
new file mode 100644
index 0000000000..5827de782d
--- /dev/null
+++ b/test/api/v3/integration/groups/GET-groups_groupId_members.test.js
@@ -0,0 +1,113 @@
+import {
+ generateUser,
+ generateGroup,
+ translate as t,
+} from '../../../../helpers/api-v3-integration.helper';
+import { v4 as generateUUID } from 'uuid';
+
+describe('GET /groups/:groupId/members', () => {
+ let user;
+
+ beforeEach(async () => {
+ user = await generateUser();
+ });
+
+ it('validates optional req.query.lastId to be an UUID', async () => {
+ await expect(user.get('/groups/groupId/members?lastId=invalidUUID')).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('invalidReqParams'),
+ });
+ });
+
+ it('fails if group doesn\'t exists', async () => {
+ await expect(user.get(`/groups/${generateUUID()}/members`)).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('groupNotFound'),
+ });
+ });
+
+ it('fails if user doesn\'t have access to the group', async () => {
+ let group = await generateGroup(user, {type: 'party', name: generateUUID()});
+ let anotherUser = await generateUser();
+ await expect(anotherUser.get(`/groups/${group._id}/members`)).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('groupNotFound'),
+ });
+ });
+
+ it('works when passing party as req.params.groupId', async () => {
+ await generateGroup(user, {type: 'party', name: generateUUID()});
+ let res = await user.get('/groups/party/members');
+ expect(res).to.be.an('array');
+ expect(res.length).to.equal(1);
+ expect(res[0]).to.eql({
+ _id: user._id,
+ id: user._id,
+ profile: {name: user.profile.name},
+ });
+ });
+
+ it('populates only some fields', async () => {
+ await generateGroup(user, {type: 'party', name: generateUUID()});
+ let res = await user.get('/groups/party/members');
+ expect(res[0]).to.have.all.keys(['_id', 'id', 'profile']);
+ expect(res[0].profile).to.have.all.keys(['name']);
+ });
+
+ it('returns only first 30 members', async () => {
+ let group = await generateGroup(user, {type: 'party', name: generateUUID()});
+
+ let usersToGenerate = [];
+ for (let i = 0; i < 31; i++) {
+ usersToGenerate.push(generateUser({party: {_id: group._id}}));
+ }
+ await Promise.all(usersToGenerate);
+
+ let res = await user.get('/groups/party/members');
+ expect(res.length).to.equal(30);
+ res.forEach(member => {
+ expect(member).to.have.all.keys(['_id', 'id', 'profile']);
+ expect(member.profile).to.have.all.keys(['name']);
+ });
+ });
+
+ it('returns only first 30 members even when ?includeAllMembers=true', async () => {
+ let group = await generateGroup(user, {type: 'party', name: generateUUID()});
+
+ let usersToGenerate = [];
+ for (let i = 0; i < 31; i++) {
+ usersToGenerate.push(generateUser({party: {_id: group._id}}));
+ }
+ await Promise.all(usersToGenerate);
+
+ let res = await user.get('/groups/party/members?includeAllMembers=true');
+ expect(res.length).to.equal(30);
+ res.forEach(member => {
+ expect(member).to.have.all.keys(['_id', 'id', 'profile']);
+ expect(member.profile).to.have.all.keys(['name']);
+ });
+ });
+
+ it('supports using req.query.lastId to get more members', async () => {
+ let leader = await generateUser({balance: 4});
+ let group = await generateGroup(leader, {type: 'guild', privacy: 'public', name: generateUUID()});
+
+ let usersToGenerate = [];
+ for (let i = 0; i < 57; i++) {
+ usersToGenerate.push(generateUser({guilds: [group._id]}));
+ }
+ let generatedUsers = await Promise.all(usersToGenerate); // Group has 59 members (1 is the leader)
+ let expectedIds = [leader._id].concat(generatedUsers.map(generatedUser => generatedUser._id));
+
+ let res = await user.get(`/groups/${group._id}/members`);
+ expect(res.length).to.equal(30);
+ let res2 = await user.get(`/groups/${group._id}/members?lastId=${res[res.length - 1]._id}`);
+ expect(res2.length).to.equal(28);
+
+ let resIds = res.concat(res2).map(member => member._id);
+ expect(resIds).to.eql(expectedIds.sort());
+ });
+});
diff --git a/test/api/v3/integration/groups/GET-groups_id.test.js b/test/api/v3/integration/groups/GET-groups_id.test.js
new file mode 100644
index 0000000000..2ecb53a33f
--- /dev/null
+++ b/test/api/v3/integration/groups/GET-groups_id.test.js
@@ -0,0 +1,298 @@
+import {
+ generateUser,
+ createAndPopulateGroup,
+ translate as t,
+} from '../../../../helpers/api-v3-integration.helper';
+
+import {
+ each,
+} from 'lodash';
+
+describe('GET /groups/:id', () => {
+ let typesOfGroups = {};
+ typesOfGroups['public guild'] = { type: 'guild', privacy: 'public' };
+ typesOfGroups['private guild'] = { type: 'guild', privacy: 'private' };
+ typesOfGroups.party = { type: 'party', privacy: 'private' };
+
+ each(typesOfGroups, (groupDetails, groupType) => {
+ context(`Member of a ${groupType}`, () => {
+ let leader, member, createdGroup;
+
+ before(async () => {
+ let groupData = await createAndPopulateGroup({
+ members: 30,
+ groupDetails,
+ });
+
+ leader = groupData.groupLeader;
+ member = groupData.members[0];
+ createdGroup = groupData.group;
+ });
+
+ it('returns the group object', async () => {
+ let group = await member.get(`/groups/${createdGroup._id}`);
+
+ expect(group._id).to.eql(createdGroup._id);
+ expect(group.name).to.eql(createdGroup.name);
+ expect(group.type).to.eql(createdGroup.type);
+ expect(group.privacy).to.eql(createdGroup.privacy);
+ });
+
+ it('transforms leader id to leader object', async () => {
+ let group = await member.get(`/groups/${createdGroup._id}`);
+
+ expect(group.leader._id).to.eql(leader._id);
+ expect(group.leader.profile.name).to.eql(leader.profile.name);
+ });
+ });
+ });
+
+ context('Non-member of a public guild', () => {
+ let nonMember, createdGroup;
+
+ before(async () => {
+ let groupData = await createAndPopulateGroup({
+ members: 1,
+ groupDetails: {
+ name: 'test guild',
+ type: 'guild',
+ privacy: 'public',
+ },
+ });
+
+ createdGroup = groupData.group;
+ nonMember = await generateUser();
+ });
+
+ it('returns the group object for a non-member', async () => {
+ let group = await nonMember.get(`/groups/${createdGroup._id}`);
+
+ expect(group._id).to.eql(createdGroup._id);
+ expect(group.name).to.eql(createdGroup.name);
+ expect(group.type).to.eql(createdGroup.type);
+ expect(group.privacy).to.eql(createdGroup.privacy);
+ });
+ });
+
+ context('Non-member of a private guild', () => {
+ let nonMember, createdGroup;
+
+ before(async () => {
+ let groupData = await createAndPopulateGroup({
+ members: 1,
+ groupDetails: {
+ name: 'test guild',
+ type: 'guild',
+ privacy: 'private',
+ },
+ });
+
+ createdGroup = groupData.group;
+ nonMember = await generateUser();
+ });
+
+ it('does not return the group object for a non-member', async () => {
+ await expect(nonMember.get(`/groups/${createdGroup._id}`))
+ .to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('groupNotFound'),
+ });
+ });
+ });
+
+ context('Non-member of a party', () => {
+ let nonMember, createdGroup;
+
+ before(async () => {
+ let groupData = await createAndPopulateGroup({
+ members: 1,
+ groupDetails: {
+ name: 'test party',
+ type: 'party',
+ privacy: 'private',
+ },
+ });
+
+ createdGroup = groupData.group;
+ nonMember = await generateUser();
+ });
+
+ it('does not return the group object for a non-member', async () => {
+ await expect(nonMember.get(`/groups/${createdGroup._id}`))
+ .to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('groupNotFound'),
+ });
+ });
+ });
+
+ context('Member of a party', () => {
+ let member, createdGroup;
+
+ before(async () => {
+ let groupData = await createAndPopulateGroup({
+ members: 1,
+ groupDetails: {
+ name: 'test party',
+ type: 'party',
+ privacy: 'private',
+ },
+ });
+
+ createdGroup = groupData.group;
+ member = groupData.members[0];
+ });
+
+ it('returns the user\'s party if an id of "party" is passed in', async () => {
+ let group = await member.get('/groups/party');
+
+ expect(group._id).to.eql(createdGroup._id);
+ expect(group.name).to.eql(createdGroup.name);
+ expect(group.type).to.eql(createdGroup.type);
+ expect(group.privacy).to.eql(createdGroup.privacy);
+ });
+ });
+
+ context('Non-existent group', () => {
+ let user;
+
+ beforeEach(async () => {
+ user = await generateUser();
+ });
+
+ it('returns error if group does not exist', async () => {
+ await expect(user.get('/groups/group-that-does-not-exist'))
+ .to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('groupNotFound'),
+ });
+ });
+ });
+
+ context('Flagged messages', () => {
+ let group;
+
+ let chat1 = {
+ id: 'chat1',
+ text: 'chat 1',
+ flags: {},
+ };
+
+ let chat2 = {
+ id: 'chat2',
+ text: 'chat 2',
+ flags: {},
+ flagCount: 0,
+ };
+
+ let chat3 = {
+ id: 'chat3',
+ text: 'chat 3',
+ flags: {
+ 'user-id': true,
+ },
+ flagCount: 1,
+ };
+
+ let chat4 = {
+ id: 'chat4',
+ text: 'chat 4',
+ flags: {
+ 'user-id': true,
+ 'other-user-id': true,
+ },
+ flagCount: 2,
+ };
+
+ let chat5 = {
+ id: 'chat5',
+ text: 'chat 5',
+ flags: {
+ 'user-id': true,
+ 'other-user-id': true,
+ 'yet-another-user-id': true,
+ },
+ flagCount: 3,
+ };
+
+ beforeEach(async () => {
+ let groupData = await createAndPopulateGroup({
+ groupDetails: {
+ name: 'test guild',
+ type: 'guild',
+ privacy: 'public',
+ chat: [
+ chat1,
+ chat2,
+ chat3,
+ chat4,
+ chat5,
+ ],
+ },
+ });
+
+ group = groupData.group;
+
+ await group.addChat([chat1, chat2, chat3, chat4, chat5]);
+ });
+
+ context('non-admin', () => {
+ let nonAdmin;
+
+ beforeEach(async () => {
+ nonAdmin = await generateUser();
+ });
+
+ it('does not include messages with a flag count of 2 or greater', async () => {
+ let fetchedGroup = await nonAdmin.get(`/groups/${group._id}`);
+
+ expect(fetchedGroup.chat).to.have.lengthOf(3);
+ expect(fetchedGroup.chat[0].id).to.eql(chat1.id);
+ expect(fetchedGroup.chat[1].id).to.eql(chat2.id);
+ expect(fetchedGroup.chat[2].id).to.eql(chat3.id);
+ });
+
+ it('does not include user ids in flags object', async () => {
+ let fetchedGroup = await nonAdmin.get(`/groups/${group._id}`);
+ let chatWithOneFlag = fetchedGroup.chat[2];
+
+ expect(chatWithOneFlag.id).to.eql(chat3.id);
+ expect(chat3.flags).to.eql({ 'user-id': true });
+ expect(chatWithOneFlag.flags).to.eql({});
+ });
+ });
+
+ context('admin', () => {
+ let admin;
+
+ beforeEach(async () => {
+ admin = await generateUser({
+ 'contributor.admin': true,
+ });
+ });
+
+ it('includes all messages', async () => {
+ let fetchedGroup = await admin.get(`/groups/${group._id}`);
+
+ expect(fetchedGroup.chat).to.have.lengthOf(5);
+ expect(fetchedGroup.chat[0].id).to.eql(chat1.id);
+ expect(fetchedGroup.chat[1].id).to.eql(chat2.id);
+ expect(fetchedGroup.chat[2].id).to.eql(chat3.id);
+ expect(fetchedGroup.chat[3].id).to.eql(chat4.id);
+ expect(fetchedGroup.chat[4].id).to.eql(chat5.id);
+ });
+
+ it('includes user ids in flags object', async () => {
+ let fetchedGroup = await admin.get(`/groups/${group._id}`);
+ let chatWithOneFlag = fetchedGroup.chat[2];
+
+ expect(chatWithOneFlag.id).to.eql(chat3.id);
+ expect(chat3.flags).to.eql({ 'user-id': true });
+ expect(chatWithOneFlag.flags).to.eql(chat3.flags);
+ });
+ });
+ });
+});
diff --git a/test/api/v3/integration/groups/POST-groups.test.js b/test/api/v3/integration/groups/POST-groups.test.js
new file mode 100644
index 0000000000..ad8f7a6c92
--- /dev/null
+++ b/test/api/v3/integration/groups/POST-groups.test.js
@@ -0,0 +1,228 @@
+import {
+ generateUser,
+ translate as t,
+} from '../../../../helpers/api-v3-integration.helper';
+
+describe('POST /group', () => {
+ let user;
+
+ beforeEach(async () => {
+ user = await generateUser({ balance: 10 });
+ });
+
+ context('All Groups', () => {
+ it('it returns validation error when type is not provided', async () => {
+ await expect(
+ user.post('/groups', { name: 'Test Group Without Type' })
+ ).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: 'Group validation failed',
+ });
+ });
+
+ it('it returns validation error when type is not supported', async () => {
+ await expect(
+ user.post('/groups', { name: 'Group with unsupported type', type: 'foo' })
+ ).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: 'Group validation failed',
+ });
+ });
+
+ it('sets the group leader to the user who created the group', async () => {
+ let group = await user.post('/groups', {
+ name: 'Test Public Guild',
+ type: 'guild',
+ });
+
+ expect(group.leader).to.eql({
+ _id: user._id,
+ profile: {
+ name: user.profile.name,
+ },
+ });
+ });
+ });
+
+ context('Guilds', () => {
+ it('returns an error when a user with insufficient funds attempts to create a guild', async () => {
+ await user.update({ balance: 0 });
+
+ await expect(
+ user.post('/groups', {
+ name: 'Test Public Guild',
+ type: 'guild',
+ })
+ ).to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('messageInsufficientGems'),
+ });
+ });
+
+ it('adds guild to user\'s list of guilds', async () => {
+ let guild = await user.post('/groups', {
+ name: 'some guild',
+ type: 'guild',
+ privacy: 'public',
+ });
+
+ let updatedUser = await user.get('/user');
+
+ expect(updatedUser.guilds).to.include(guild._id);
+ });
+
+ context('public guild', () => {
+ it('creates a group', async () => {
+ let groupName = 'Test Public Guild';
+ let groupType = 'guild';
+ let groupPrivacy = 'public';
+
+ let publicGuild = await user.post('/groups', {
+ name: groupName,
+ type: groupType,
+ privacy: groupPrivacy,
+ });
+
+ expect(publicGuild._id).to.exist;
+ expect(publicGuild.name).to.equal(groupName);
+ expect(publicGuild.type).to.equal(groupType);
+ expect(publicGuild.memberCount).to.equal(1);
+ expect(publicGuild.privacy).to.equal(groupPrivacy);
+ expect(publicGuild.leader).to.eql({
+ _id: user._id,
+ profile: {
+ name: user.profile.name,
+ },
+ });
+ });
+ });
+
+ context('private guild', () => {
+ let groupName = 'Test Private Guild';
+ let groupType = 'guild';
+ let groupPrivacy = 'private';
+
+ it('creates a group', async () => {
+ let privateGuild = await user.post('/groups', {
+ name: groupName,
+ type: groupType,
+ privacy: groupPrivacy,
+ });
+
+ expect(privateGuild._id).to.exist;
+ expect(privateGuild.name).to.equal(groupName);
+ expect(privateGuild.type).to.equal(groupType);
+ expect(privateGuild.memberCount).to.equal(1);
+ expect(privateGuild.privacy).to.equal(groupPrivacy);
+ expect(privateGuild.leader).to.eql({
+ _id: user._id,
+ profile: {
+ name: user.profile.name,
+ },
+ });
+ });
+
+ it('deducts gems from user and adds them to guild bank', async () => {
+ let privateGuild = await user.post('/groups', {
+ name: groupName,
+ type: groupType,
+ privacy: groupPrivacy,
+ });
+
+ expect(privateGuild.balance).to.eql(1);
+
+ let updatedUser = await user.get('/user');
+
+ expect(updatedUser.balance).to.eql(user.balance - 1);
+ });
+ });
+ });
+
+ context('Parties', () => {
+ let partyName = 'Test Party';
+ let partyType = 'party';
+
+ it('creates a party', async () => {
+ let party = await user.post('/groups', {
+ name: partyName,
+ type: partyType,
+ });
+
+ expect(party._id).to.exist;
+ expect(party.name).to.equal(partyName);
+ expect(party.type).to.equal(partyType);
+ expect(party.memberCount).to.equal(1);
+ expect(party.leader).to.eql({
+ _id: user._id,
+ profile: {
+ name: user.profile.name,
+ },
+ });
+ });
+
+ it('does not require gems to create a party', async () => {
+ await user.update({ balance: 0 });
+
+ let party = await user.post('/groups', {
+ name: partyName,
+ type: partyType,
+ });
+
+ expect(party._id).to.exist;
+
+ let updatedUser = await user.get('/user');
+
+ expect(updatedUser.balance).to.eql(user.balance);
+ });
+
+ it('sets party id on user object', async () => {
+ let party = await user.post('/groups', {
+ name: partyName,
+ type: partyType,
+ });
+
+ let updatedUser = await user.get('/user');
+
+ expect(updatedUser.party._id).to.eql(party._id);
+ });
+
+ it('does not award Party Up achievement to solo partier', async () => {
+ await user.post('/groups', {
+ name: partyName,
+ type: partyType,
+ });
+
+ let updatedUser = await user.get('/user');
+
+ expect(updatedUser.achievements.partyUp).to.not.eql(true);
+ });
+
+ it('prevents user in a party from creating another party', async () => {
+ await user.post('/groups', {
+ name: partyName,
+ type: partyType,
+ });
+
+ await expect(user.post('/groups')).to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('messageGroupAlreadyInParty'),
+ });
+ });
+
+ it('prevents creating a public party', async () => {
+ await expect(user.post('/groups', {
+ name: partyName,
+ type: partyType,
+ privacy: 'public',
+ })).to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('partyMustbePrivate'),
+ });
+ });
+ });
+});
diff --git a/test/api/v3/integration/groups/POST-groups_groupId_join.test.js b/test/api/v3/integration/groups/POST-groups_groupId_join.test.js
new file mode 100644
index 0000000000..f47a734e89
--- /dev/null
+++ b/test/api/v3/integration/groups/POST-groups_groupId_join.test.js
@@ -0,0 +1,286 @@
+import {
+ generateUser,
+ createAndPopulateGroup,
+ checkExistence,
+ translate as t,
+} from '../../../../helpers/api-v3-integration.helper';
+import { v4 as generateUUID } from 'uuid';
+
+describe('POST /group/:groupId/join', () => {
+ const PET_QUEST = 'whale';
+
+ it('returns error when groupId is not for a valid group', async () => {
+ let joiningUser = await generateUser();
+
+ await expect(joiningUser.post(`/groups/${generateUUID()}/join`)).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('groupNotFound'),
+ });
+ });
+
+ context('Joining a public guild', () => {
+ let user, joiningUser, publicGuild;
+
+ beforeEach(async () => {
+ let {group, groupLeader} = await createAndPopulateGroup({
+ groupDetails: {
+ name: 'Test Guild',
+ type: 'guild',
+ privacy: 'public',
+ },
+ });
+
+ publicGuild = group;
+ user = groupLeader;
+ joiningUser = await generateUser();
+ });
+
+ it('allows non-invited users to join public guilds', async () => {
+ let res = await joiningUser.post(`/groups/${publicGuild._id}/join`);
+
+ await expect(joiningUser.get('/user')).to.eventually.have.property('guilds').to.include(publicGuild._id);
+ expect(res.leader._id).to.eql(user._id);
+ expect(res.leader.profile.name).to.eql(user.profile.name);
+ });
+
+ it('returns an error is user was already a member', async () => {
+ await joiningUser.post(`/groups/${publicGuild._id}/join`);
+ await expect(joiningUser.post(`/groups/${publicGuild._id}/join`)).to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('userAlreadyInGroup'),
+ });
+ });
+
+ it('promotes joining member in a public empty guild to leader', async () => {
+ await user.post(`/groups/${publicGuild._id}/leave`);
+
+ await joiningUser.post(`/groups/${publicGuild._id}/join`);
+
+ await expect(joiningUser.get(`/groups/${publicGuild._id}`)).to.eventually.have.deep.property('leader._id', joiningUser._id);
+ });
+
+ it('increments memberCount when joining guilds', async () => {
+ let oldMemberCount = publicGuild.memberCount;
+
+ await joiningUser.post(`/groups/${publicGuild._id}/join`);
+
+ await expect(joiningUser.get(`/groups/${publicGuild._id}`)).to.eventually.have.property('memberCount', oldMemberCount + 1);
+ });
+ });
+
+ context('Joining a private guild', () => {
+ let user, invitedUser, guild;
+
+ beforeEach(async () => {
+ let { group, groupLeader, invitees } = await createAndPopulateGroup({
+ groupDetails: {
+ name: 'Test Guild',
+ type: 'guild',
+ privacy: 'private',
+ },
+ invites: 1,
+ });
+
+ guild = group;
+ user = groupLeader;
+ invitedUser = invitees[0];
+ });
+
+ it('returns error when user is not invited to private guild', async () => {
+ let userWithoutInvite = await generateUser();
+
+ await expect(userWithoutInvite.post(`/groups/${guild._id}/join`)).to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('messageGroupRequiresInvite'),
+ });
+ });
+
+ context('User is invited', () => {
+ it('allows invited user to join private guilds', async () => {
+ await invitedUser.post(`/groups/${guild._id}/join`);
+
+ await expect(invitedUser.get('/user')).to.eventually.have.property('guilds').to.include(guild._id);
+ });
+
+ it('clears invitation from user when joining guilds', async () => {
+ await invitedUser.post(`/groups/${guild._id}/join`);
+
+ await expect(invitedUser.get('/user'))
+ .to.eventually.have.deep.property('invitations.guilds')
+ .to.not.include({id: guild._id});
+ });
+
+ it('increments memberCount when joining guilds', async () => {
+ let oldMemberCount = guild.memberCount;
+
+ await invitedUser.post(`/groups/${guild._id}/join`);
+
+ await expect(invitedUser.get(`/groups/${guild._id}`)).to.eventually.have.property('memberCount', oldMemberCount + 1);
+ });
+
+ it('does not give basilist quest to inviter when joining a guild', async () => {
+ await invitedUser.post(`/groups/${guild._id}/join`);
+
+ await expect(user.get('/user')).to.eventually.not.have.deep.property('items.quests.basilist');
+ });
+
+ it('does not increment basilist quest count to inviter with basilist when joining a guild', async () => {
+ await user.update({ 'items.quests.basilist': 1 });
+
+ await invitedUser.post(`/groups/${guild._id}/join`);
+
+ await expect(user.get('/user')).to.eventually.have.deep.property('items.quests.basilist', 1);
+ });
+ });
+ });
+
+ context('Joining a party', () => {
+ let user, invitedUser, party;
+
+ beforeEach(async () => {
+ let { group, groupLeader, invitees } = await createAndPopulateGroup({
+ groupDetails: {
+ name: 'Test Party',
+ type: 'party',
+ },
+ members: 2,
+ invites: 1,
+ });
+
+ party = group;
+ user = groupLeader;
+ invitedUser = invitees[0];
+ });
+
+ it('returns error when user is not invited to party', async () => {
+ let userWithoutInvite = await generateUser();
+
+ await expect(userWithoutInvite.post(`/groups/${party._id}/join`)).to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('messageGroupRequiresInvite'),
+ });
+ });
+
+ context('User is invited', () => {
+ it('allows invited user to join party', async () => {
+ await invitedUser.post(`/groups/${party._id}/join`);
+
+ await expect(invitedUser.get('/user')).to.eventually.have.deep.property('party._id', party._id);
+ });
+
+ it('clears invitation from user when joining party', async () => {
+ await invitedUser.post(`/groups/${party._id}/join`);
+
+ await expect(invitedUser.get('/user')).to.eventually.not.have.deep.property('invitations.party.id');
+ });
+
+ it('increments memberCount when joining party', async () => {
+ let oldMemberCount = party.memberCount;
+
+ await invitedUser.post(`/groups/${party._id}/join`);
+
+ await expect(invitedUser.get(`/groups/${party._id}`)).to.eventually.have.property('memberCount', oldMemberCount + 1);
+ });
+
+ it('gives basilist quest item to the inviter when joining a party', async () => {
+ await invitedUser.post(`/groups/${party._id}/join`);
+
+ await expect(user.get('/user')).to.eventually.have.deep.property('items.quests.basilist', 1);
+ });
+
+ it('increments basilist quest item count to inviter when joining a party', async () => {
+ await user.update({'items.quests.basilist': 1 });
+
+ await invitedUser.post(`/groups/${party._id}/join`);
+
+ await expect(user.get('/user')).to.eventually.have.deep.property('items.quests.basilist', 2);
+ });
+
+ it('deletes previous party where the user was the only member', async () => {
+ let userToInvite = await generateUser();
+ let oldParty = await userToInvite.post('/groups', { // add user to a party
+ name: 'Another Test Party',
+ type: 'party',
+ });
+
+ await expect(checkExistence('groups', oldParty._id)).to.eventually.equal(true);
+ await user.post(`/groups/${party._id}/invite`, {
+ uuids: [userToInvite._id],
+ });
+ await userToInvite.post(`/groups/${party._id}/join`);
+
+ await expect(user.get('/user')).to.eventually.have.deep.property('party._id', party._id);
+ await expect(checkExistence('groups', oldParty._id)).to.eventually.equal(false);
+ });
+
+ it('invites joining member to active quest', async () => {
+ await user.update({
+ [`items.quests.${PET_QUEST}`]: 1,
+ });
+ await user.post(`/groups/${party._id}/quests/invite/${PET_QUEST}`);
+
+ await invitedUser.post(`/groups/${party._id}/join`);
+
+ await invitedUser.sync();
+ await party.sync();
+
+ expect(invitedUser).to.have.deep.property('party.quest.RSVPNeeded', true);
+ expect(invitedUser).to.have.deep.property('party.quest.key', party.quest.key);
+ expect(party.quest.members[invitedUser._id]).to.be.null;
+ });
+ });
+ });
+
+ context('Party incentive achievements', () => {
+ let leader, member, party;
+
+ beforeEach(async () => {
+ leader = await generateUser();
+ member = await generateUser();
+ party = await leader.post('/groups', {
+ name: 'Testing Party',
+ type: 'party',
+ });
+ await leader.post(`/groups/${party._id}/invite`, {
+ uuids: [member._id],
+ });
+ await member.post(`/groups/${party._id}/join`);
+ });
+
+ it('awards Party Up achievement to party of size 2', async () => {
+ await member.sync();
+ await leader.sync();
+
+ expect(member).to.have.deep.property('achievements.partyUp', true);
+ expect(leader).to.have.deep.property('achievements.partyUp', true);
+ });
+
+ it('does not award Party On achievement to party of size 2', async () => {
+ await member.sync();
+ await leader.sync();
+
+ expect(member).to.not.have.deep.property('achievements.partyOn');
+ expect(leader).to.not.have.deep.property('achievements.partyOn');
+ });
+
+ it('awards Party On achievement to party of size 4', async () => {
+ let addlMemberOne = await generateUser();
+ let addlMemberTwo = await generateUser();
+ await leader.post(`/groups/${party._id}/invite`, {
+ uuids: [addlMemberOne._id, addlMemberTwo._id],
+ });
+ await addlMemberOne.post(`/groups/${party._id}/join`);
+ await addlMemberTwo.post(`/groups/${party._id}/join`);
+
+ await member.sync();
+ await leader.sync();
+
+ expect(member).to.have.deep.property('achievements.partyOn', true);
+ expect(leader).to.have.deep.property('achievements.partyOn', true);
+ });
+ });
+});
diff --git a/test/api/v3/integration/groups/POST-groups_groupId_leave.js b/test/api/v3/integration/groups/POST-groups_groupId_leave.js
new file mode 100644
index 0000000000..3e13634bd8
--- /dev/null
+++ b/test/api/v3/integration/groups/POST-groups_groupId_leave.js
@@ -0,0 +1,210 @@
+import {
+ generateChallenge,
+ checkExistence,
+ createAndPopulateGroup,
+ sleep,
+ generateUser,
+ translate as t,
+} from '../../../../helpers/api-v3-integration.helper';
+import {
+ each,
+} from 'lodash';
+
+describe('POST /groups/:groupId/leave', () => {
+ let typesOfGroups = {
+ 'public guild': { type: 'guild', privacy: 'public' },
+ 'private guild': { type: 'guild', privacy: 'private' },
+ party: { type: 'party', privacy: 'private' },
+ };
+
+ each(typesOfGroups, (groupDetails, groupType) => {
+ context(`Leaving a ${groupType}`, () => {
+ let groupToLeave;
+ let leader;
+ let member;
+ let memberCount;
+
+ beforeEach(async () => {
+ let { group, groupLeader, members } = await createAndPopulateGroup({
+ groupDetails,
+ members: 1,
+ });
+
+ groupToLeave = group;
+ leader = groupLeader;
+ member = members[0];
+ memberCount = group.memberCount;
+ });
+
+ it('prevents non members from leaving', async () => {
+ let user = await generateUser();
+ await expect(user.post(`/groups/${groupToLeave._id}/leave`)).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('groupNotFound'),
+ });
+ });
+
+ it(`lets user leave a ${groupType}`, async () => {
+ await member.post(`/groups/${groupToLeave._id}/leave`);
+
+ let userThatLeftGroup = await member.get('/user');
+
+ expect(userThatLeftGroup.guilds).to.be.empty;
+ expect(userThatLeftGroup.party._id).to.not.exist;
+ await groupToLeave.sync();
+ expect(groupToLeave.memberCount).to.equal(memberCount - 1);
+ });
+
+ it(`sets a new group leader when leader leaves a ${groupType}`, async () => {
+ await leader.post(`/groups/${groupToLeave._id}/leave`);
+
+ await groupToLeave.sync();
+ expect(groupToLeave.memberCount).to.equal(memberCount - 1);
+ expect(groupToLeave.leader).to.equal(member._id);
+ });
+
+ context('With challenges', () => {
+ let challenge;
+
+ beforeEach(async () => {
+ challenge = await generateChallenge(leader, groupToLeave);
+
+ await leader.post(`/tasks/challenge/${challenge._id}`, {
+ text: 'test habit',
+ type: 'habit',
+ });
+
+ await sleep(0.5);
+ });
+
+ it('removes all challenge tasks when keep parameter is set to remove', async () => {
+ await leader.post(`/groups/${groupToLeave._id}/leave?keep=remove-all`);
+
+ let userWithoutChallengeTasks = await leader.get('/user');
+
+ expect(userWithoutChallengeTasks.challenges).to.not.include(challenge._id);
+ expect(userWithoutChallengeTasks.tasksOrder.habits).to.be.empty;
+ });
+
+ it('keeps all challenge tasks when keep parameter is not set', async () => {
+ await leader.post(`/groups/${groupToLeave._id}/leave`);
+
+ let userWithChallengeTasks = await leader.get('/user');
+
+ expect(userWithChallengeTasks.challenges).to.not.include(challenge._id);
+ // @TODO find elegant way to assert against the task existing
+ expect(userWithChallengeTasks.tasksOrder.habits).to.not.be.empty;
+ });
+ });
+
+ it('prevents quest leader from leaving a groupToLeave');
+ it('prevents a user from leaving during an active quest');
+ });
+ });
+
+ context('Leaving a group as the last member', () => {
+ context('private guild', () => {
+ let privateGuild;
+ let leader;
+ let invitedUser;
+
+ beforeEach(async () => {
+ let { group, groupLeader, invitees } = await createAndPopulateGroup({
+ groupDetails: {
+ name: 'Test Private Guild',
+ type: 'guild',
+ },
+ invites: 1,
+ });
+
+ privateGuild = group;
+ leader = groupLeader;
+ invitedUser = invitees[0];
+ });
+
+ it('removes a group when the last member leaves', async () => {
+ await leader.post(`/groups/${privateGuild._id}/leave`);
+
+ await expect(checkExistence('groups', privateGuild._id)).to.eventually.equal(false);
+ });
+
+ it('removes invitations when the last member leaves', async () => {
+ await leader.post(`/groups/${privateGuild._id}/leave`);
+
+ let userWithoutInvitation = await invitedUser.get('/user');
+
+ expect(userWithoutInvitation.invitations.guilds).to.be.empty;
+ });
+ });
+
+ context('public guild', () => {
+ let publicGuild;
+ let leader;
+ let invitedUser;
+
+ beforeEach(async () => {
+ let { group, groupLeader, invitees } = await createAndPopulateGroup({
+ groupDetails: {
+ name: 'Test Public Guild',
+ type: 'guild',
+ privacy: 'public',
+ },
+ invites: 1,
+ });
+
+ publicGuild = group;
+ leader = groupLeader;
+ invitedUser = invitees[0];
+ });
+
+ it('keeps the group when the last member leaves', async () => {
+ await leader.post(`/groups/${publicGuild._id}/leave`);
+
+ await expect(checkExistence('groups', publicGuild._id)).to.eventually.equal(true);
+ });
+
+ it('keeps the invitations when the last member leaves a public guild', async () => {
+ await leader.post(`/groups/${publicGuild._id}/leave`);
+
+ let userWithoutInvitation = await invitedUser.get('/user');
+
+ expect(userWithoutInvitation.invitations.guilds).to.not.be.empty;
+ });
+ });
+
+ context('party', () => {
+ let party;
+ let leader;
+ let invitedUser;
+
+ beforeEach(async () => {
+ let { group, groupLeader, invitees } = await createAndPopulateGroup({
+ groupDetails: {
+ name: 'Test Party',
+ type: 'party',
+ },
+ invites: 1,
+ });
+
+ party = group;
+ leader = groupLeader;
+ invitedUser = invitees[0];
+ });
+
+ it('removes a group when the last member leaves a party', async () => {
+ await leader.post(`/groups/${party._id}/leave`);
+
+ await expect(checkExistence('party', party._id)).to.eventually.equal(false);
+ });
+
+ it('removes invitations when the last member leaves a party', async () => {
+ await leader.post(`/groups/${party._id}/leave`);
+
+ let userWithoutInvitation = await invitedUser.get('/user');
+
+ expect(userWithoutInvitation.invitations.party).to.be.empty;
+ });
+ });
+ });
+});
diff --git a/test/api/v3/integration/groups/POST-groups_groupId_reject.test.js b/test/api/v3/integration/groups/POST-groups_groupId_reject.test.js
new file mode 100644
index 0000000000..18f83fe9c9
--- /dev/null
+++ b/test/api/v3/integration/groups/POST-groups_groupId_reject.test.js
@@ -0,0 +1,113 @@
+import {
+ generateUser,
+ createAndPopulateGroup,
+ translate as t,
+} from '../../../../helpers/api-v3-integration.helper';
+
+describe('POST /group/:groupId/reject-invite', () => {
+ context('Rejecting a public guild invite', () => {
+ let publicGuild, invitedUser;
+
+ beforeEach(async () => {
+ let {group, invitees} = await createAndPopulateGroup({
+ groupDetails: {
+ name: 'Test Guild',
+ type: 'guild',
+ privacy: 'public',
+ },
+ invites: 1,
+ });
+
+ publicGuild = group;
+ invitedUser = invitees[0];
+ });
+
+ it('returns error when user is not invited', async () => {
+ let userWithoutInvite = await generateUser();
+
+ await expect(userWithoutInvite.post(`/groups/${publicGuild._id}/reject-invite`)).to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('messageGroupRequiresInvite'),
+ });
+ });
+
+ it('clears invitation from user', async () => {
+ await invitedUser.post(`/groups/${publicGuild._id}/reject-invite`);
+
+ await expect(invitedUser.get('/user'))
+ .to.eventually.have.deep.property('invitations.guilds')
+ .to.not.include({id: publicGuild._id});
+ });
+ });
+
+ context('Rejecting a private guild invite', () => {
+ let invitedUser, guild;
+
+ beforeEach(async () => {
+ let { group, invitees } = await createAndPopulateGroup({
+ groupDetails: {
+ name: 'Test Guild',
+ type: 'guild',
+ privacy: 'private',
+ },
+ invites: 1,
+ });
+
+ guild = group;
+ invitedUser = invitees[0];
+ });
+
+ it('returns error when user is not invited', async () => {
+ let userWithoutInvite = await generateUser();
+
+ await expect(userWithoutInvite.post(`/groups/${guild._id}/reject-invite`)).to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('messageGroupRequiresInvite'),
+ });
+ });
+
+ it('clears invitation from user', async () => {
+ await invitedUser.post(`/groups/${guild._id}/reject-invite`);
+
+ await expect(invitedUser.get('/user'))
+ .to.eventually.have.deep.property('invitations.guilds')
+ .to.not.include({id: guild._id});
+ });
+ });
+
+ context('Rejecting a party invite', () => {
+ let invitedUser, party;
+
+ beforeEach(async () => {
+ let { group, invitees } = await createAndPopulateGroup({
+ groupDetails: {
+ name: 'Test Party',
+ type: 'party',
+ },
+ members: 2,
+ invites: 1,
+ });
+
+ party = group;
+ invitedUser = invitees[0];
+ });
+
+ it('returns error when user is not invited', async () => {
+ let userWithoutInvite = await generateUser();
+
+ await expect(userWithoutInvite.post(`/groups/${party._id}/reject-invite`)).to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('messageGroupRequiresInvite'),
+ });
+ });
+
+ it('clears invitation from user', async () => {
+ await invitedUser.post(`/groups/${party._id}/reject-invite`);
+
+ await expect(invitedUser.get('/user')).to.eventually.not.have.deep.property('invitations.party.id');
+ });
+ });
+});
diff --git a/test/api/v3/integration/groups/POST-groups_id_removeMember.test.js b/test/api/v3/integration/groups/POST-groups_id_removeMember.test.js
new file mode 100644
index 0000000000..0ebde39361
--- /dev/null
+++ b/test/api/v3/integration/groups/POST-groups_id_removeMember.test.js
@@ -0,0 +1,130 @@
+import {
+ generateUser,
+ createAndPopulateGroup,
+ translate as t,
+} from '../../../../helpers/api-v3-integration.helper';
+
+describe('POST /groups/:groupId/removeMember/:memberId', () => {
+ let leader;
+ let invitedUser;
+ let guild;
+ let member;
+ let member2;
+
+ beforeEach(async () => {
+ let { group, groupLeader, invitees, members } = await createAndPopulateGroup({
+ groupDetails: {
+ name: 'Test Guild',
+ type: 'guild',
+ privacy: 'private',
+ },
+ invites: 1,
+ members: 2,
+ });
+
+ guild = group;
+ leader = groupLeader;
+ invitedUser = invitees[0];
+ member = members[0];
+ member2 = members[1];
+ });
+
+ context('All Groups', () => {
+ it('returns an error when user is not member of the group', async () => {
+ let nonMember = await generateUser();
+
+ expect(nonMember.post(`/groups/${guild._id}/removeMember/${member._id}`))
+ .to.eventually.be.rejected.and.eql({
+ code: 401,
+ type: 'NotAuthorized',
+ message: t('onlyLeaderCanRemoveMember'),
+ });
+ });
+
+ it('returns an error when user is a non-leader member of a group', async () => {
+ expect(member2.post(`/groups/${guild._id}/removeMember/${member._id}`))
+ .to.eventually.be.rejected.and.eql({
+ code: 401,
+ type: 'NotAuthorized',
+ message: t('onlyLeaderCanRemoveMember'),
+ });
+ });
+
+ it('does not allow leader to remove themselves', async () => {
+ expect(leader.post(`/groups/${guild._id}/removeMember/${leader._id}`))
+ .to.eventually.be.rejected.and.eql({
+ code: 401,
+ text: t('messageGroupCannotRemoveSelf'),
+ });
+ });
+ });
+
+ context('Guilds', () => {
+ it('can remove other members', async () => {
+ await leader.post(`/groups/${guild._id}/removeMember/${member._id}`);
+ let memberRemoved = await member.get('/user');
+
+ expect(memberRemoved.guilds.indexOf(guild._id)).eql(-1);
+ });
+
+ it('updates memberCount', async () => {
+ let oldMemberCount = guild.memberCount;
+ await leader.post(`/groups/${guild._id}/removeMember/${member._id}`);
+ await expect(leader.get(`/groups/${guild._id}`)).to.eventually.have.property('memberCount', oldMemberCount - 1);
+ });
+
+ it('can remove other invites', async () => {
+ await leader.post(`/groups/${guild._id}/removeMember/${invitedUser._id}`);
+
+ let invitedUserWithoutInvite = await invitedUser.get('/user');
+
+ expect(_.findIndex(invitedUserWithoutInvite.invitations.guilds, {id: guild._id})).eql(-1);
+ });
+ });
+
+ context('Party', () => {
+ let party;
+ let partyleader;
+ let partyInvitedUser;
+ let partyMember;
+
+ beforeEach(async () => {
+ let { group, groupLeader, invitees, members } = await createAndPopulateGroup({
+ groupDetails: {
+ name: 'Test Party',
+ type: 'party',
+ privacy: 'private',
+ },
+ invites: 1,
+ members: 1,
+ });
+
+ party = group;
+ partyleader = groupLeader;
+ partyInvitedUser = invitees[0];
+ partyMember = members[0];
+ });
+
+ it('can remove other members', async () => {
+ await partyleader.post(`/groups/${party._id}/removeMember/${partyMember._id}`);
+
+ let memberRemoved = await partyMember.get('/user');
+
+ expect(memberRemoved.party._id).eql(undefined);
+ });
+
+ it('updates memberCount', async () => {
+ let oldMemberCount = party.memberCount;
+ await partyleader.post(`/groups/${party._id}/removeMember/${partyMember._id}`);
+ await expect(partyleader.get(`/groups/${party._id}`)).to.eventually.have.property('memberCount', oldMemberCount - 1);
+ });
+
+ it('can remove other invites', async () => {
+ await partyleader.post(`/groups/${party._id}/removeMember/${partyInvitedUser._id}`);
+
+ let invitedUserWithoutInvite = await partyInvitedUser.get('/user');
+
+ expect(_.findIndex(invitedUserWithoutInvite.invitations.party, {id: party._id})).eql(-1);
+ });
+ });
+});
diff --git a/test/api/v3/integration/groups/POST-groups_invite.test.js b/test/api/v3/integration/groups/POST-groups_invite.test.js
new file mode 100644
index 0000000000..3e6ed0be30
--- /dev/null
+++ b/test/api/v3/integration/groups/POST-groups_invite.test.js
@@ -0,0 +1,343 @@
+import {
+ generateUser,
+ translate as t,
+} from '../../../../helpers/api-v3-integration.helper';
+import { v4 as generateUUID } from 'uuid';
+
+const INVITES_LIMIT = 100;
+
+describe('Post /groups/:groupId/invite', () => {
+ let inviter;
+ let group;
+ let groupName = 'Test Public Guild';
+
+ beforeEach(async () => {
+ inviter = await generateUser({balance: 1});
+ group = await inviter.post('/groups', {
+ name: groupName,
+ type: 'guild',
+ });
+ });
+
+ describe('user id invites', () => {
+ it('returns an error when invited user is not found', async () => {
+ let fakeID = generateUUID();
+
+ await expect(inviter.post(`/groups/${group._id}/invite`, {
+ uuids: [fakeID],
+ }))
+ .to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('userWithIDNotFound', {userId: fakeID}),
+ });
+ });
+
+ it('returns an error when inviting yourself to a group', async () => {
+ await expect(inviter.post(`/groups/${group._id}/invite`, {
+ uuids: [inviter._id],
+ }))
+ .to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('cannotInviteSelfToGroup'),
+ });
+ });
+
+ it('returns an error when uuids is not an array', async () => {
+ let fakeID = generateUUID();
+
+ await expect(inviter.post(`/groups/${group._id}/invite`, {
+ uuids: {fakeID},
+ }))
+ .to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('uuidsMustBeAnArray'),
+ });
+ });
+
+ it('returns empty when uuids is empty', async () => {
+ await expect(inviter.post(`/groups/${group._id}/invite`, {
+ uuids: [],
+ }))
+ .to.eventually.be.empty;
+ });
+
+ it('returns an error when there are more than INVITES_LIMIT uuids', async () => {
+ let uuids = [];
+
+ for (let i = 0; i < 101; i += 1) {
+ uuids.push(generateUUID());
+ }
+
+ await expect(inviter.post(`/groups/${group._id}/invite`, {
+ uuids,
+ }))
+ .to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('canOnlyInviteMaxInvites', {maxInvites: INVITES_LIMIT}),
+ });
+ });
+
+ it('invites a user to a group by uuid', async () => {
+ let userToInvite = await generateUser();
+
+ await expect(inviter.post(`/groups/${group._id}/invite`, {
+ uuids: [userToInvite._id],
+ })).to.eventually.deep.equal([{
+ id: group._id,
+ name: groupName,
+ inviter: inviter._id,
+ }]);
+
+ await expect(userToInvite.get('/user'))
+ .to.eventually.have.deep.property('invitations.guilds[0].id', group._id);
+ });
+
+ it('invites multiple users to a group by uuid', async () => {
+ let userToInvite = await generateUser();
+ let userToInvite2 = await generateUser();
+
+ await expect(inviter.post(`/groups/${group._id}/invite`, {
+ uuids: [userToInvite._id, userToInvite2._id],
+ })).to.eventually.deep.equal([
+ {
+ id: group._id,
+ name: groupName,
+ inviter: inviter._id,
+ },
+ {
+ id: group._id,
+ name: groupName,
+ inviter: inviter._id,
+ },
+ ]);
+
+ await expect(userToInvite.get('/user')).to.eventually.have.deep.property('invitations.guilds[0].id', group._id);
+ await expect(userToInvite2.get('/user')).to.eventually.have.deep.property('invitations.guilds[0].id', group._id);
+ });
+
+ it('returns an error when inviting multiple users and a user is not found', async () => {
+ let userToInvite = await generateUser();
+ let fakeID = generateUUID();
+
+ await expect(inviter.post(`/groups/${group._id}/invite`, {
+ uuids: [userToInvite._id, fakeID],
+ }))
+ .to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('userWithIDNotFound', {userId: fakeID}),
+ });
+ });
+ });
+
+ describe('email invites', () => {
+ let testInvite = {name: 'test', email: 'test@habitica.com'};
+
+ it('returns an error when invite is missing an email', async () => {
+ await expect(inviter.post(`/groups/${group._id}/invite`, {
+ emails: [{name: 'test'}],
+ }))
+ .to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('inviteMissingEmail'),
+ });
+ });
+
+ it('returns an error when emails is not an array', async () => {
+ await expect(inviter.post(`/groups/${group._id}/invite`, {
+ emails: {testInvite},
+ }))
+ .to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('emailsMustBeAnArray'),
+ });
+ });
+
+ it('returns empty when emails is an empty array', async () => {
+ await expect(inviter.post(`/groups/${group._id}/invite`, {
+ emails: [],
+ }))
+ .to.eventually.be.empty;
+ });
+
+ it('returns an error when there are more than INVITES_LIMIT emails', async () => {
+ let emails = [];
+
+ for (let i = 0; i < 101; i += 1) {
+ emails.push(`${generateUUID()}@habitica.com`);
+ }
+
+ await expect(inviter.post(`/groups/${group._id}/invite`, {
+ emails,
+ }))
+ .to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('canOnlyInviteMaxInvites', {maxInvites: INVITES_LIMIT}),
+ });
+ });
+
+ it('invites a user to a group by email', async () => {
+ let res = await inviter.post(`/groups/${group._id}/invite`, {
+ emails: [testInvite],
+ inviter: 'inviter name',
+ });
+
+ expect(res).to.exist;
+ });
+
+ it('invites multiple users to a group by email', async () => {
+ let res = await inviter.post(`/groups/${group._id}/invite`, {
+ emails: [testInvite, {name: 'test2', email: 'test2@habitica.com'}],
+ });
+
+ expect(res).to.exist;
+ });
+ });
+
+ describe('user and email invites', () => {
+ it('returns an error when emails and uuids are not provided', async () => {
+ await expect(inviter.post(`/groups/${group._id}/invite`))
+ .to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('canOnlyInviteEmailUuid'),
+ });
+ });
+
+ it('returns an error when there are more than INVITES_LIMIT uuids and emails', async () => {
+ let emails = [];
+ let uuids = [];
+
+ for (let i = 0; i < 50; i += 1) {
+ emails.push(`${generateUUID()}@habitica.com`);
+ }
+
+ for (let i = 0; i < 51; i += 1) {
+ uuids.push(generateUUID());
+ }
+
+ await expect(inviter.post(`/groups/${group._id}/invite`, {
+ emails,
+ uuids,
+ }))
+ .to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('canOnlyInviteMaxInvites', {maxInvites: INVITES_LIMIT}),
+ });
+ });
+
+ it('invites users to a group by uuid and email', async () => {
+ let newUser = await generateUser();
+ let invite = await inviter.post(`/groups/${group._id}/invite`, {
+ uuids: [newUser._id],
+ emails: [{name: 'test', email: 'test@habitica.com'}],
+ });
+ let invitedUser = await newUser.get('/user');
+
+ expect(invitedUser.invitations.guilds[0].id).to.equal(group._id);
+ expect(invite).to.exist;
+ });
+ });
+
+ describe('guild invites', () => {
+ it('returns an error when invited user is already invited to the group', async () => {
+ let userToInivite = await generateUser();
+ await inviter.post(`/groups/${group._id}/invite`, {
+ uuids: [userToInivite._id],
+ });
+
+ await expect(inviter.post(`/groups/${group._id}/invite`, {
+ uuids: [userToInivite._id],
+ }))
+ .to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('userAlreadyInvitedToGroup'),
+ });
+ });
+
+ it('returns an error when invited user is already in the group', async () => {
+ let userToInvite = await generateUser();
+ await inviter.post(`/groups/${group._id}/invite`, {
+ uuids: [userToInvite._id],
+ });
+ await userToInvite.post(`/groups/${group._id}/join`);
+
+ await expect(inviter.post(`/groups/${group._id}/invite`, {
+ uuids: [userToInvite._id],
+ }))
+ .to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('userAlreadyInGroup'),
+ });
+ });
+ });
+
+ describe('party invites', () => {
+ let party;
+
+ beforeEach(async () => {
+ party = await inviter.post('/groups', {
+ name: 'Test Party',
+ type: 'party',
+ });
+ });
+
+ it('returns an error when invited user has a pending invitation to the party', async () => {
+ let userToInvite = await generateUser();
+ await inviter.post(`/groups/${party._id}/invite`, {
+ uuids: [userToInvite._id],
+ });
+
+ await expect(inviter.post(`/groups/${party._id}/invite`, {
+ uuids: [userToInvite._id],
+ }))
+ .to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('userAlreadyPendingInvitation'),
+ });
+ });
+
+ it('returns an error when invited user is already in a party of more than 1 member', async () => {
+ let userToInvite = await generateUser();
+ let userToInvite2 = await generateUser();
+ await inviter.post(`/groups/${party._id}/invite`, {
+ uuids: [userToInvite._id, userToInvite2._id],
+ });
+ await userToInvite.post(`/groups/${party._id}/join`);
+ await userToInvite2.post(`/groups/${party._id}/join`);
+
+ await expect(inviter.post(`/groups/${party._id}/invite`, {
+ uuids: [userToInvite._id],
+ }))
+ .to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('userAlreadyInAParty'),
+ });
+ });
+
+ it('allow inviting a user to a party if he\'s partying solo', async () => {
+ let userToInvite = await generateUser();
+ await userToInvite.post('/groups', { // add user to a party
+ name: 'Another Test Party',
+ type: 'party',
+ });
+
+ await inviter.post(`/groups/${party._id}/invite`, {
+ uuids: [userToInvite._id],
+ });
+ expect((await userToInvite.get('/user')).invitations.party.id).to.equal(party._id);
+ });
+ });
+});
diff --git a/test/api/v3/integration/groups/PUT-groups.test.js b/test/api/v3/integration/groups/PUT-groups.test.js
new file mode 100644
index 0000000000..8d581d56ca
--- /dev/null
+++ b/test/api/v3/integration/groups/PUT-groups.test.js
@@ -0,0 +1,46 @@
+import {
+ createAndPopulateGroup,
+ translate as t,
+} from '../../../../helpers/api-v3-integration.helper';
+
+describe('PUT /group', () => {
+ let leader, nonLeader, groupToUpdate;
+ let groupName = 'Test Public Guild';
+ let groupType = 'guild';
+ let groupUpdatedName = 'Test Public Guild Updated';
+
+ beforeEach(async () => {
+ let { group, groupLeader, members } = await createAndPopulateGroup({
+ groupDetails: {
+ name: groupName,
+ type: groupType,
+ privacy: 'public',
+ },
+ members: 1,
+ });
+
+ groupToUpdate = group;
+ leader = groupLeader;
+ nonLeader = members[0];
+ });
+
+ it('returns an error when a non group leader tries to update', async () => {
+ await expect(nonLeader.put(`/groups/${groupToUpdate._id}`, {
+ name: groupUpdatedName,
+ })).to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('messageGroupOnlyLeaderCanUpdate'),
+ });
+ });
+
+ it('updates a group', async () => {
+ let updatedGroup = await leader.put(`/groups/${groupToUpdate._id}`, {
+ name: groupUpdatedName,
+ });
+
+ expect(updatedGroup.leader._id).to.eql(leader._id);
+ expect(updatedGroup.leader.profile.name).to.eql(leader.profile.name);
+ expect(updatedGroup.name).to.equal(groupUpdatedName);
+ });
+});
diff --git a/test/api/v3/integration/hall/GET-hall_heroes.test.js b/test/api/v3/integration/hall/GET-hall_heroes.test.js
new file mode 100644
index 0000000000..745bc7739c
--- /dev/null
+++ b/test/api/v3/integration/hall/GET-hall_heroes.test.js
@@ -0,0 +1,29 @@
+import {
+ generateUser,
+} from '../../../../helpers/api-v3-integration.helper';
+
+describe('GET /hall/heroes', () => {
+ it('returns all heroes sorted by -contributor.level and with correct fields', async () => {
+ let nonHero = await generateUser();
+ let hero1 = await generateUser({
+ contributor: {level: 1},
+ });
+ let hero2 = await generateUser({
+ contributor: {level: 3},
+ });
+
+ let heroes = await nonHero.get('/hall/heroes');
+ expect(heroes.length).to.equal(2);
+ expect(heroes[0]._id).to.equal(hero2._id);
+ expect(heroes[1]._id).to.equal(hero1._id);
+
+ expect(heroes[0]).to.have.all.keys(['_id', 'contributor', 'backer', 'profile']);
+ expect(heroes[1]).to.have.all.keys(['_id', 'contributor', 'backer', 'profile']);
+
+ expect(heroes[0].profile).to.have.all.keys(['name']);
+ expect(heroes[1].profile).to.have.all.keys(['name']);
+
+ expect(heroes[0].profile.name).to.equal(hero2.profile.name);
+ expect(heroes[1].profile.name).to.equal(hero1.profile.name);
+ });
+});
diff --git a/test/api/v3/integration/hall/GET-hall_heroes_heroId.test.js b/test/api/v3/integration/hall/GET-hall_heroes_heroId.test.js
new file mode 100644
index 0000000000..2bf1a0a017
--- /dev/null
+++ b/test/api/v3/integration/hall/GET-hall_heroes_heroId.test.js
@@ -0,0 +1,56 @@
+import {
+ generateUser,
+ translate as t,
+} from '../../../../helpers/api-v3-integration.helper';
+import { v4 as generateUUID } from 'uuid';
+
+describe('GET /heroes/:heroId', () => {
+ let user;
+
+ before(async () => {
+ user = await generateUser({
+ contributor: {admin: true},
+ });
+ });
+
+ it('requires the caller to be an admin', async () => {
+ let nonAdmin = await generateUser();
+
+ await expect(nonAdmin.get(`/hall/heroes/${user._id}`)).to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('noAdminAccess'),
+ });
+ });
+
+ it('validates req.params.heroId', async () => {
+ await expect(user.get('/hall/heroes/invalidUUID')).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('invalidReqParams'),
+ });
+ });
+
+ it('handles non-existing heroes', async () => {
+ let dummyId = generateUUID();
+ await expect(user.get(`/hall/heroes/${dummyId}`)).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('userWithIDNotFound', {userId: dummyId}),
+ });
+ });
+
+ it('returns only necessary hero data', async () => {
+ let hero = await generateUser({
+ contributor: {tier: 23},
+ });
+ let heroRes = await user.get(`/hall/heroes/${hero._id}`);
+
+ expect(heroRes).to.have.all.keys([ // works as: object has all and only these keys
+ '_id', 'id', 'balance', 'profile', 'purchased',
+ 'contributor', 'auth', 'items',
+ ]);
+ expect(heroRes.auth.local).not.to.have.keys(['salt', 'hashed_password']);
+ expect(heroRes.profile).to.have.all.keys(['name']);
+ });
+});
diff --git a/test/api/v3/integration/hall/GET-hall_patrons.test.js b/test/api/v3/integration/hall/GET-hall_patrons.test.js
new file mode 100644
index 0000000000..9599daef89
--- /dev/null
+++ b/test/api/v3/integration/hall/GET-hall_patrons.test.js
@@ -0,0 +1,60 @@
+import {
+ generateUser,
+ translate as t,
+ resetHabiticaDB,
+} from '../../../../helpers/api-v3-integration.helper';
+import { times } from 'lodash';
+
+describe('GET /hall/patrons', () => {
+ let user;
+
+ beforeEach(async () => {
+ await resetHabiticaDB();
+ user = await generateUser();
+ });
+
+ it('fails if req.query.page is not numeric', async () => {
+ await expect(user.get('/hall/patrons?page=notNumber')).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('invalidReqParams'),
+ });
+ });
+
+ it('returns all patrons sorted by -backer.tier and with correct fields', async () => {
+ let patron1 = await generateUser({
+ backer: {tier: 1},
+ });
+ let patron2 = await generateUser({
+ backer: {tier: 3},
+ });
+
+ let patrons = await user.get('/hall/patrons');
+ expect(patrons.length).to.equal(2);
+ expect(patrons[0]._id).to.equal(patron2._id);
+ expect(patrons[1]._id).to.equal(patron1._id);
+
+ expect(patrons[0]).to.have.all.keys(['_id', 'contributor', 'backer', 'profile']);
+ expect(patrons[1]).to.have.all.keys(['_id', 'contributor', 'backer', 'profile']);
+
+ expect(patrons[0].profile).to.have.all.keys(['name']);
+ expect(patrons[1].profile).to.have.all.keys(['name']);
+
+ expect(patrons[0].profile.name).to.equal(patron2.profile.name);
+ expect(patrons[1].profile.name).to.equal(patron1.profile.name);
+ });
+
+ it('returns only first 50 patrons per request, more if req.query.page is passed', async () => {
+ await Promise.all(times(53, n => {
+ return generateUser({backer: {tier: n}});
+ }));
+
+ let patrons = await user.get('/hall/patrons');
+ expect(patrons.length).to.equal(50);
+
+ let morePatrons = await user.get('/hall/patrons?page=1');
+ expect(morePatrons.length).to.equal(2);
+ expect(morePatrons[0].backer.tier).to.equal(2);
+ expect(morePatrons[1].backer.tier).to.equal(1);
+ });
+});
diff --git a/test/api/v3/integration/hall/PUT-hall_heores_heroId.test.js b/test/api/v3/integration/hall/PUT-hall_heores_heroId.test.js
new file mode 100644
index 0000000000..bb724a51b4
--- /dev/null
+++ b/test/api/v3/integration/hall/PUT-hall_heores_heroId.test.js
@@ -0,0 +1,146 @@
+import {
+ generateUser,
+ translate as t,
+} from '../../../../helpers/api-v3-integration.helper';
+import { v4 as generateUUID } from 'uuid';
+
+describe('PUT /heroes/:heroId', () => {
+ let user;
+
+ before(async () => {
+ user = await generateUser({
+ contributor: {admin: true},
+ });
+ });
+
+ it('requires the caller to be an admin', async () => {
+ let nonAdmin = await generateUser();
+
+ await expect(nonAdmin.put(`/hall/heroes/${user._id}`)).to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('noAdminAccess'),
+ });
+ });
+
+ it('validates req.params.heroId', async () => {
+ await expect(user.put('/hall/heroes/invalidUUID')).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('invalidReqParams'),
+ });
+ });
+
+ it('handles non-existing heroes', async () => {
+ let dummyId = generateUUID();
+ await expect(user.put(`/hall/heroes/${dummyId}`)).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('userWithIDNotFound', {userId: dummyId}),
+ });
+ });
+
+ it('updates contributor level, balance, ads, blocked', async () => {
+ let hero = await generateUser();
+ let heroRes = await user.put(`/hall/heroes/${hero._id}`, {
+ balance: 3,
+ contributor: {level: 1},
+ purchased: {ads: true},
+ auth: {blocked: true},
+ });
+
+ // test response
+ expect(heroRes).to.have.all.keys([ // works as: object has all and only these keys
+ '_id', 'balance', 'profile', 'purchased',
+ 'contributor', 'auth', 'items',
+ ]);
+ expect(heroRes.auth.local).not.to.have.keys(['salt', 'hashed_password']);
+ expect(heroRes.profile).to.have.all.keys(['name']);
+
+ // test response values
+ expect(heroRes.balance).to.equal(3 + 0.75); // 3+0.75 for first contrib level
+ expect(heroRes.contributor.level).to.equal(1);
+ expect(heroRes.purchased.ads).to.equal(true);
+ expect(heroRes.auth.blocked).to.equal(true);
+ // test hero values
+ await hero.sync();
+ expect(hero.balance).to.equal(3 + 0.75); // 3+0.75 for first contrib level
+ expect(hero.contributor.level).to.equal(1);
+ expect(hero.purchased.ads).to.equal(true);
+ expect(hero.auth.blocked).to.equal(true);
+ });
+
+ it('updates contributor level', async () => {
+ let hero = await generateUser({
+ contributor: {level: 5},
+ });
+ let heroRes = await user.put(`/hall/heroes/${hero._id}`, {
+ contributor: {level: 6},
+ });
+
+ // test response
+ expect(heroRes).to.have.all.keys([ // works as: object has all and only these keys
+ '_id', 'balance', 'profile', 'purchased',
+ 'contributor', 'auth', 'items',
+ ]);
+ expect(heroRes.auth.local).not.to.have.keys(['salt', 'hashed_password']);
+ expect(heroRes.profile).to.have.all.keys(['name']);
+
+ // test response values
+ expect(heroRes.balance).to.equal(1); // 0+1 for sixth contrib level
+ expect(heroRes.contributor.level).to.equal(6);
+ expect(heroRes.items.pets['Dragon-Hydra']).to.equal(5);
+ // test hero values
+ await hero.sync();
+ expect(hero.balance).to.equal(1); // 0+1 for sixth contrib level
+ expect(hero.contributor.level).to.equal(6);
+ expect(hero.items.pets['Dragon-Hydra']).to.equal(5);
+ });
+
+ it('updates contributor data', async () => {
+ let hero = await generateUser({
+ contributor: {level: 5},
+ });
+ let heroRes = await user.put(`/hall/heroes/${hero._id}`, {
+ contributor: {text: 'Astronaut'},
+ });
+
+ // test response
+ expect(heroRes).to.have.all.keys([ // works as: object has all and only these keys
+ '_id', 'balance', 'profile', 'purchased',
+ 'contributor', 'auth', 'items',
+ ]);
+ expect(heroRes.auth.local).not.to.have.keys(['salt', 'hashed_password']);
+ expect(heroRes.profile).to.have.all.keys(['name']);
+
+ // test response values
+ expect(heroRes.contributor.level).to.equal(5); // doesn't modify previous values
+ expect(heroRes.contributor.text).to.equal('Astronaut');
+ // test hero values
+ await hero.sync();
+ expect(hero.contributor.level).to.equal(5); // doesn't modify previous values
+ expect(hero.contributor.text).to.equal('Astronaut');
+ });
+
+ it('updates items', async () => {
+ let hero = await generateUser();
+ let heroRes = await user.put(`/hall/heroes/${hero._id}`, {
+ itemPath: 'items.special.snowball',
+ itemVal: 5,
+ });
+
+ // test response
+ expect(heroRes).to.have.all.keys([ // works as: object has all and only these keys
+ '_id', 'balance', 'profile', 'purchased',
+ 'contributor', 'auth', 'items',
+ ]);
+ expect(heroRes.auth.local).not.to.have.keys(['salt', 'hashed_password']);
+ expect(heroRes.profile).to.have.all.keys(['name']);
+
+ // test response values
+ expect(heroRes.items.special.snowball).to.equal(5);
+ // test hero values
+ await hero.sync();
+ expect(hero.items.special.snowball).to.equal(5);
+ });
+});
diff --git a/test/api/v3/integration/members/GET-members_id.test.js b/test/api/v3/integration/members/GET-members_id.test.js
new file mode 100644
index 0000000000..a802a1e8a7
--- /dev/null
+++ b/test/api/v3/integration/members/GET-members_id.test.js
@@ -0,0 +1,49 @@
+import {
+ generateUser,
+ translate as t,
+} from '../../../../helpers/api-v3-integration.helper';
+import { v4 as generateUUID } from 'uuid';
+
+describe('GET /members/:memberId', () => {
+ let user;
+
+ before(async () => {
+ user = await generateUser();
+ });
+
+ it('validates req.params.memberId', async () => {
+ await expect(user.get('/members/invalidUUID')).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('invalidReqParams'),
+ });
+ });
+
+ it('returns a member public data only', async () => {
+ let member = await generateUser({ // make sure user has all the fields that can be returned by the getMember call
+ contributor: {level: 1},
+ backer: {tier: 3},
+ preferences: {
+ costume: false,
+ background: 'volcano',
+ },
+ });
+ let memberRes = await user.get(`/members/${member._id}`);
+ expect(memberRes).to.have.all.keys([ // works as: object has all and only these keys
+ '_id', 'id', 'preferences', 'profile', 'stats', 'achievements', 'party',
+ 'backer', 'contributor', 'auth', 'items',
+ ]);
+ expect(Object.keys(memberRes.auth)).to.eql(['timestamps']);
+ expect(Object.keys(memberRes.preferences).sort()).to.eql(['size', 'hair', 'skin', 'shirt',
+ 'chair', 'costume', 'sleep', 'background'].sort());
+ });
+
+ it('handles non-existing members', async () => {
+ let dummyId = generateUUID();
+ await expect(user.get(`/members/${dummyId}`)).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('userWithIDNotFound', {userId: dummyId}),
+ });
+ });
+});
diff --git a/test/api/v3/integration/members/POST-send_private_message.test.js b/test/api/v3/integration/members/POST-send_private_message.test.js
new file mode 100644
index 0000000000..3bd3380437
--- /dev/null
+++ b/test/api/v3/integration/members/POST-send_private_message.test.js
@@ -0,0 +1,107 @@
+import {
+ generateUser,
+ translate as t,
+} from '../../../../helpers/api-v3-integration.helper';
+import { v4 as generateUUID } from 'uuid';
+
+describe('POST /members/send-private-message', () => {
+ let userToSendMessage;
+ let messageToSend = 'Test Private Message';
+
+ beforeEach(async () => {
+ userToSendMessage = await generateUser();
+ });
+
+ it('returns error when message is not provided', async () => {
+ await expect(userToSendMessage.post('/members/send-private-message'))
+ .to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: 'Invalid request parameters.',
+ });
+ });
+
+ it('returns error when toUserId is not provided', async () => {
+ await expect(userToSendMessage.post('/members/send-private-message', {
+ message: messageToSend,
+ })).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: 'Invalid request parameters.',
+ });
+ });
+
+ it('returns error when to user is not found', async () => {
+ await expect(userToSendMessage.post('/members/send-private-message', {
+ message: messageToSend,
+ toUserId: generateUUID(),
+ })).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('userNotFound'),
+ });
+ });
+
+ it('returns error when to user has blocked the sender', async () => {
+ let receiver = await generateUser({'inbox.blocks': [userToSendMessage._id]});
+
+ await expect(userToSendMessage.post('/members/send-private-message', {
+ message: messageToSend,
+ toUserId: receiver._id,
+ })).to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('notAuthorizedToSendMessageToThisUser'),
+ });
+ });
+
+ it('returns error when sender has blocked to user', async () => {
+ let receiver = await generateUser();
+ let sender = await generateUser({'inbox.blocks': [receiver._id]});
+
+ await expect(sender.post('/members/send-private-message', {
+ message: messageToSend,
+ toUserId: receiver._id,
+ })).to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('notAuthorizedToSendMessageToThisUser'),
+ });
+ });
+
+ it('returns error when to user has opted out of messaging', async () => {
+ let receiver = await generateUser({'inbox.optOut': true});
+
+ await expect(userToSendMessage.post('/members/send-private-message', {
+ message: messageToSend,
+ toUserId: receiver._id,
+ })).to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('notAuthorizedToSendMessageToThisUser'),
+ });
+ });
+
+ it('sends a private message to a user', async () => {
+ let receiver = await generateUser();
+
+ await userToSendMessage.post('/members/send-private-message', {
+ message: messageToSend,
+ toUserId: receiver._id,
+ });
+
+ let updatedReceiver = await receiver.get('/user');
+ let updatedSender = await userToSendMessage.get('/user');
+
+ let sendersMessageInReceiversInbox = _.find(updatedReceiver.inbox.messages, (message) => {
+ return message.uuid === userToSendMessage._id && message.text === messageToSend;
+ });
+
+ let sendersMessageInSendersInbox = _.find(updatedSender.inbox.messages, (message) => {
+ return message.uuid === receiver._id && message.text === messageToSend;
+ });
+
+ expect(sendersMessageInReceiversInbox).to.exist;
+ expect(sendersMessageInSendersInbox).to.exist;
+ });
+});
diff --git a/test/api/v3/integration/members/POST-transfer_gems.test.js b/test/api/v3/integration/members/POST-transfer_gems.test.js
new file mode 100644
index 0000000000..96644a3e88
--- /dev/null
+++ b/test/api/v3/integration/members/POST-transfer_gems.test.js
@@ -0,0 +1,174 @@
+import {
+ generateUser,
+ translate as t,
+} from '../../../../helpers/api-v3-integration.helper';
+import { v4 as generateUUID } from 'uuid';
+
+describe('POST /members/transfer-gems', () => {
+ let userToSendMessage;
+ let receiver;
+ let message = 'Test Private Message';
+ let gemAmount = 20;
+
+ beforeEach(async () => {
+ userToSendMessage = await generateUser({balance: 5});
+ receiver = await generateUser();
+ });
+
+ it('returns error when no parameters are provided', async () => {
+ await expect(userToSendMessage.post('/members/transfer-gems'))
+ .to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: 'Invalid request parameters.',
+ });
+ });
+
+ it('returns error when toUserId is not provided', async () => {
+ await expect(userToSendMessage.post('/members/transfer-gems', {
+ message,
+ gemAmount,
+ })).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: 'Invalid request parameters.',
+ });
+ });
+
+ it('returns error when to user is not found', async () => {
+ await expect(userToSendMessage.post('/members/transfer-gems', {
+ message,
+ gemAmount,
+ toUserId: generateUUID(),
+ })).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('userNotFound'),
+ });
+ });
+
+ it('returns error when to user attempts to send gems to themselves', async () => {
+ await expect(userToSendMessage.post('/members/transfer-gems', {
+ message,
+ gemAmount,
+ toUserId: userToSendMessage._id,
+ })).to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('cannotSendGemsToYourself'),
+ });
+ });
+
+ it('returns error when there is no gemAmount', async () => {
+ await expect(userToSendMessage.post('/members/transfer-gems', {
+ message,
+ toUserId: receiver._id,
+ })).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: 'Invalid request parameters.',
+ });
+ });
+
+ it('returns error when gemAmount is not an integer', async () => {
+ await expect(userToSendMessage.post('/members/transfer-gems', {
+ message,
+ gemAmount: 1.5,
+ toUserId: receiver._id,
+ })).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: 'Invalid request parameters.',
+ });
+ });
+
+ it('returns error when gemAmount is negative', async () => {
+ await expect(userToSendMessage.post('/members/transfer-gems', {
+ message,
+ gemAmount: -5,
+ toUserId: receiver._id,
+ })).to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('badAmountOfGemsToSend'),
+ });
+ });
+
+ it('returns error when gemAmount is more than the sender\'s balance', async () => {
+ await expect(userToSendMessage.post('/members/transfer-gems', {
+ message,
+ gemAmount: gemAmount + 4,
+ toUserId: receiver._id,
+ })).to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('badAmountOfGemsToSend'),
+ });
+ });
+
+ it('sends a private message about gems to a user', async () => {
+ await userToSendMessage.post('/members/transfer-gems', {
+ message,
+ gemAmount,
+ toUserId: receiver._id,
+ });
+
+ let updatedReceiver = await receiver.get('/user');
+ let updatedSender = await userToSendMessage.get('/user');
+
+ let sendersMessageInReceiversInbox = _.find(updatedReceiver.inbox.messages, (inboxMessage) => {
+ return inboxMessage.uuid === userToSendMessage._id;
+ });
+
+ let sendersMessageInSendersInbox = _.find(updatedSender.inbox.messages, (inboxMessage) => {
+ return inboxMessage.uuid === receiver._id;
+ });
+
+ let messageSentContent = t('privateMessageGiftIntro', {
+ receiverName: receiver.profile.name,
+ senderName: userToSendMessage.profile.name,
+ });
+ messageSentContent += t('privateMessageGiftGemsMessage', {gemAmount});
+ messageSentContent += message;
+
+ expect(sendersMessageInReceiversInbox).to.exist;
+ expect(sendersMessageInReceiversInbox.text).to.equal(messageSentContent);
+ expect(updatedReceiver.balance).to.equal(gemAmount / 4);
+
+ expect(sendersMessageInSendersInbox).to.exist;
+ expect(sendersMessageInSendersInbox.text).to.equal(messageSentContent);
+ expect(updatedSender.balance).to.equal(0);
+ });
+
+ it('does not requrie a message', async () => {
+ await userToSendMessage.post('/members/transfer-gems', {
+ gemAmount,
+ toUserId: receiver._id,
+ });
+
+ let updatedReceiver = await receiver.get('/user');
+ let updatedSender = await userToSendMessage.get('/user');
+
+ let sendersMessageInReceiversInbox = _.find(updatedReceiver.inbox.messages, (inboxMessage) => {
+ return inboxMessage.uuid === userToSendMessage._id;
+ });
+
+ let sendersMessageInSendersInbox = _.find(updatedSender.inbox.messages, (inboxMessage) => {
+ return inboxMessage.uuid === receiver._id;
+ });
+
+ let messageSentContent = t('privateMessageGiftIntro', {
+ receiverName: receiver.profile.name,
+ senderName: userToSendMessage.profile.name,
+ });
+ messageSentContent += t('privateMessageGiftGemsMessage', {gemAmount});
+
+ expect(sendersMessageInReceiversInbox).to.exist;
+ expect(sendersMessageInReceiversInbox.text).to.equal(messageSentContent);
+ expect(updatedReceiver.balance).to.equal(gemAmount / 4);
+
+ expect(sendersMessageInSendersInbox).to.exist;
+ expect(sendersMessageInSendersInbox.text).to.equal(messageSentContent);
+ expect(updatedSender.balance).to.equal(0);
+ });
+});
diff --git a/test/api/v3/integration/models/GET-model_paths.test.js b/test/api/v3/integration/models/GET-model_paths.test.js
new file mode 100644
index 0000000000..0a9a94451a
--- /dev/null
+++ b/test/api/v3/integration/models/GET-model_paths.test.js
@@ -0,0 +1,32 @@
+import {
+ generateUser,
+ translate as t,
+} from '../../../../helpers/api-integration/v3';
+
+describe('GET /models/:model/paths', () => {
+ let user;
+
+ before(async () => {
+ user = await generateUser();
+ });
+
+ it('returns an error when model is not accessible or doesn\'t exists', async () => {
+ await expect(user.get('/models/1234/paths')).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('invalidReqParams'),
+ });
+ });
+
+ let models = ['habit', 'daily', 'todo', 'reward', 'user', 'tag', 'challenge', 'group'];
+ models.forEach(model => {
+ it(`returns the model paths for ${model}`, async () => {
+ let res = await user.get(`/models/${model}/paths`);
+
+ if (model !== 'tag') expect(res._id).to.equal('String');
+ if (model === 'tag') expect(res.id).to.equal('String');
+
+ expect(res).to.not.have.keys('__v');
+ });
+ });
+});
diff --git a/test/api/v3/integration/notFound.test.js b/test/api/v3/integration/notFound.test.js
new file mode 100644
index 0000000000..747b370af9
--- /dev/null
+++ b/test/api/v3/integration/notFound.test.js
@@ -0,0 +1,13 @@
+import { requester } from '../../../helpers/api-integration/v3';
+
+describe('notFound Middleware', () => {
+ it('returns a 404 error when the resource is not found', async () => {
+ let request = requester().get('/api/v3/dummy-url');
+
+ await expect(request).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: 'Not found.',
+ });
+ });
+});
diff --git a/test/api/v3/integration/payments/GET-payments_amazon_subscribe_cancel.test.js b/test/api/v3/integration/payments/GET-payments_amazon_subscribe_cancel.test.js
new file mode 100644
index 0000000000..37588d1f18
--- /dev/null
+++ b/test/api/v3/integration/payments/GET-payments_amazon_subscribe_cancel.test.js
@@ -0,0 +1,21 @@
+import {
+ generateUser,
+ translate as t,
+} from '../../../../helpers/api-integration/v3';
+
+describe('payments : amazon #subscribeCancel', () => {
+ let endpoint = '/amazon/subscribe/cancel';
+ let user;
+
+ beforeEach(async () => {
+ user = await generateUser();
+ });
+
+ it('verifies subscription', async () => {
+ await expect(user.get(endpoint)).to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('missingSubscription'),
+ });
+ });
+});
diff --git a/test/api/v3/integration/payments/GET-payments_paypal_checkout.test.js b/test/api/v3/integration/payments/GET-payments_paypal_checkout.test.js
new file mode 100644
index 0000000000..7c692f31d1
--- /dev/null
+++ b/test/api/v3/integration/payments/GET-payments_paypal_checkout.test.js
@@ -0,0 +1,21 @@
+import {
+ generateUser,
+ translate as t,
+} from '../../../../helpers/api-integration/v3';
+
+xdescribe('payments : paypal #checkout', () => {
+ let endpoint = '/paypal/checkout';
+ let user;
+
+ beforeEach(async () => {
+ user = await generateUser();
+ });
+
+ it('verifies subscription', async () => {
+ await expect(user.get(endpoint)).to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('missingSubscription'),
+ });
+ });
+});
diff --git a/test/api/v3/integration/payments/GET-payments_paypal_checkout_success.test.js b/test/api/v3/integration/payments/GET-payments_paypal_checkout_success.test.js
new file mode 100644
index 0000000000..6de04c8848
--- /dev/null
+++ b/test/api/v3/integration/payments/GET-payments_paypal_checkout_success.test.js
@@ -0,0 +1,21 @@
+import {
+ generateUser,
+ translate as t,
+} from '../../../../helpers/api-integration/v3';
+
+xdescribe('payments : paypal #checkoutSuccess', () => {
+ let endpoint = '/paypal/checkout/success';
+ let user;
+
+ beforeEach(async () => {
+ user = await generateUser();
+ });
+
+ it('verifies subscription', async () => {
+ await expect(user.get(endpoint)).to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('missingSubscription'),
+ });
+ });
+});
diff --git a/test/api/v3/integration/payments/GET-payments_paypal_subscribe.test.js b/test/api/v3/integration/payments/GET-payments_paypal_subscribe.test.js
new file mode 100644
index 0000000000..54c540ee39
--- /dev/null
+++ b/test/api/v3/integration/payments/GET-payments_paypal_subscribe.test.js
@@ -0,0 +1,21 @@
+import {
+ generateUser,
+ translate as t,
+} from '../../../../helpers/api-integration/v3';
+
+xdescribe('payments : paypal #subscribe', () => {
+ let endpoint = '/paypal/subscribe';
+ let user;
+
+ beforeEach(async () => {
+ user = await generateUser();
+ });
+
+ it('verifies credentials', async () => {
+ await expect(user.get(endpoint)).to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('missingSubscription'),
+ });
+ });
+});
diff --git a/test/api/v3/integration/payments/GET-payments_paypal_subscribe_cancel.test.js b/test/api/v3/integration/payments/GET-payments_paypal_subscribe_cancel.test.js
new file mode 100644
index 0000000000..1ba8b7af16
--- /dev/null
+++ b/test/api/v3/integration/payments/GET-payments_paypal_subscribe_cancel.test.js
@@ -0,0 +1,21 @@
+import {
+ generateUser,
+ translate as t,
+} from '../../../../helpers/api-integration/v3';
+
+describe('payments : paypal #subscribeCancel', () => {
+ let endpoint = '/paypal/subscribe/cancel';
+ let user;
+
+ beforeEach(async () => {
+ user = await generateUser();
+ });
+
+ it('verifies credentials', async () => {
+ await expect(user.get(endpoint)).to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('missingSubscription'),
+ });
+ });
+});
diff --git a/test/api/v3/integration/payments/GET-payments_paypal_subscribe_success.test.js b/test/api/v3/integration/payments/GET-payments_paypal_subscribe_success.test.js
new file mode 100644
index 0000000000..1a38342e9c
--- /dev/null
+++ b/test/api/v3/integration/payments/GET-payments_paypal_subscribe_success.test.js
@@ -0,0 +1,21 @@
+import {
+ generateUser,
+ translate as t,
+} from '../../../../helpers/api-integration/v3';
+
+xdescribe('payments : paypal #subscribeSuccess', () => {
+ let endpoint = '/paypal/subscribe/success';
+ let user;
+
+ beforeEach(async () => {
+ user = await generateUser();
+ });
+
+ it('verifies credentials', async () => {
+ await expect(user.get(endpoint)).to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('missingSubscription'),
+ });
+ });
+});
diff --git a/test/api/v3/integration/payments/GET-payments_stripe_subscribe_cancel.test.js b/test/api/v3/integration/payments/GET-payments_stripe_subscribe_cancel.test.js
new file mode 100644
index 0000000000..6d7ac87d0f
--- /dev/null
+++ b/test/api/v3/integration/payments/GET-payments_stripe_subscribe_cancel.test.js
@@ -0,0 +1,21 @@
+import {
+ generateUser,
+ translate as t,
+} from '../../../../helpers/api-integration/v3';
+
+describe('payments - stripe - #subscribeCancel', () => {
+ let endpoint = '/stripe/subscribe/cancel';
+ let user;
+
+ beforeEach(async () => {
+ user = await generateUser();
+ });
+
+ it('verifies credentials', async () => {
+ await expect(user.get(endpoint)).to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('missingSubscription'),
+ });
+ });
+});
diff --git a/test/api/v3/integration/payments/POST-payments_amazon_checkout.test.js b/test/api/v3/integration/payments/POST-payments_amazon_checkout.test.js
new file mode 100644
index 0000000000..8745a74e85
--- /dev/null
+++ b/test/api/v3/integration/payments/POST-payments_amazon_checkout.test.js
@@ -0,0 +1,20 @@
+import {
+ generateUser,
+} from '../../../../helpers/api-integration/v3';
+
+describe('payments - amazon - #checkout', () => {
+ let endpoint = '/amazon/checkout';
+ let user;
+
+ beforeEach(async () => {
+ user = await generateUser();
+ });
+
+ it('verifies credentials', async () => {
+ await expect(user.post(endpoint)).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: 'Missing req.body.orderReferenceId',
+ });
+ });
+});
diff --git a/test/api/v3/integration/payments/POST-payments_amazon_createOrderReferenceId.test.js b/test/api/v3/integration/payments/POST-payments_amazon_createOrderReferenceId.test.js
new file mode 100644
index 0000000000..17a50520eb
--- /dev/null
+++ b/test/api/v3/integration/payments/POST-payments_amazon_createOrderReferenceId.test.js
@@ -0,0 +1,22 @@
+import {
+ generateUser,
+} from '../../../../helpers/api-integration/v3';
+
+describe('payments - amazon - #createOrderReferenceId', () => {
+ let endpoint = '/amazon/createOrderReferenceId';
+ let user;
+
+ beforeEach(async () => {
+ user = await generateUser();
+ });
+
+ it('verifies billingAgreementId', async (done) => {
+ try {
+ await user.post(endpoint);
+ } catch (e) {
+ // Parameter AWSAccessKeyId cannot be empty.
+ expect(e.error).to.eql('BadRequest');
+ done();
+ }
+ });
+});
diff --git a/test/api/v3/integration/payments/POST-payments_amazon_subscribe.test.js b/test/api/v3/integration/payments/POST-payments_amazon_subscribe.test.js
new file mode 100644
index 0000000000..5c3b98ad87
--- /dev/null
+++ b/test/api/v3/integration/payments/POST-payments_amazon_subscribe.test.js
@@ -0,0 +1,21 @@
+import {
+ generateUser,
+ translate as t,
+} from '../../../../helpers/api-integration/v3';
+
+describe('payments - amazon - #subscribe', () => {
+ let endpoint = '/amazon/subscribe';
+ let user;
+
+ beforeEach(async () => {
+ user = await generateUser();
+ });
+
+ it('verifies subscription code', async () => {
+ await expect(user.post(endpoint)).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('missingSubscriptionCode'),
+ });
+ });
+});
diff --git a/test/api/v3/integration/payments/POST-payments_amazon_verifyAccessToken.test.js b/test/api/v3/integration/payments/POST-payments_amazon_verifyAccessToken.test.js
new file mode 100644
index 0000000000..51ccf8c41c
--- /dev/null
+++ b/test/api/v3/integration/payments/POST-payments_amazon_verifyAccessToken.test.js
@@ -0,0 +1,20 @@
+import {
+ generateUser,
+} from '../../../../helpers/api-integration/v3';
+
+describe('payments : amazon', () => {
+ let endpoint = '/amazon/verifyAccessToken';
+ let user;
+
+ beforeEach(async () => {
+ user = await generateUser();
+ });
+
+ it('verifies access token', async () => {
+ await expect(user.post(endpoint)).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: 'Missing req.body.access_token',
+ });
+ });
+});
diff --git a/test/api/v3/integration/payments/POST-payments_paypal_ipn.test.js b/test/api/v3/integration/payments/POST-payments_paypal_ipn.test.js
new file mode 100644
index 0000000000..219e9ce35b
--- /dev/null
+++ b/test/api/v3/integration/payments/POST-payments_paypal_ipn.test.js
@@ -0,0 +1,17 @@
+import {
+ generateUser,
+} from '../../../../helpers/api-integration/v3';
+
+describe('payments - paypal - #ipn', () => {
+ let endpoint = '/paypal/ipn';
+ let user;
+
+ beforeEach(async () => {
+ user = await generateUser();
+ });
+
+ it('verifies credentials', async () => {
+ let result = await user.post(endpoint);
+ expect(result).to.eql('OK');
+ });
+});
diff --git a/test/api/v3/integration/payments/POST-payments_stripe_checkout.test.js b/test/api/v3/integration/payments/POST-payments_stripe_checkout.test.js
new file mode 100644
index 0000000000..1443a3af74
--- /dev/null
+++ b/test/api/v3/integration/payments/POST-payments_stripe_checkout.test.js
@@ -0,0 +1,20 @@
+import {
+ generateUser,
+} from '../../../../helpers/api-integration/v3';
+
+describe('payments - stripe - #checkout', () => {
+ let endpoint = '/stripe/checkout';
+ let user;
+
+ beforeEach(async () => {
+ user = await generateUser();
+ });
+
+ it('verifies credentials', async () => {
+ await expect(user.post(endpoint, {id: 123})).to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'Error',
+ message: 'Invalid API Key provided: ****************************1111',
+ });
+ });
+});
diff --git a/test/api/v3/integration/payments/POST-payments_stripe_subscribe_edit.test.js b/test/api/v3/integration/payments/POST-payments_stripe_subscribe_edit.test.js
new file mode 100644
index 0000000000..d6d568ace4
--- /dev/null
+++ b/test/api/v3/integration/payments/POST-payments_stripe_subscribe_edit.test.js
@@ -0,0 +1,21 @@
+import {
+ generateUser,
+ translate as t,
+} from '../../../../helpers/api-integration/v3';
+
+describe('payments - stripe - #subscribeEdit', () => {
+ let endpoint = '/stripe/subscribe/edit';
+ let user;
+
+ beforeEach(async () => {
+ user = await generateUser();
+ });
+
+ it('verifies credentials', async () => {
+ await expect(user.post(endpoint)).to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('missingSubscription'),
+ });
+ });
+});
diff --git a/test/api/v3/integration/quests/POST-groups_groupId_quests_accept.test.js b/test/api/v3/integration/quests/POST-groups_groupId_quests_accept.test.js
new file mode 100644
index 0000000000..665a185279
--- /dev/null
+++ b/test/api/v3/integration/quests/POST-groups_groupId_quests_accept.test.js
@@ -0,0 +1,119 @@
+import {
+ createAndPopulateGroup,
+ translate as t,
+ generateUser,
+} from '../../../../helpers/api-v3-integration.helper';
+
+describe('POST /groups/:groupId/quests/accept', () => {
+ const PET_QUEST = 'whale';
+
+ let questingGroup;
+ let leader;
+ let partyMembers;
+ let user;
+
+ beforeEach(async () => {
+ user = await generateUser();
+
+ let { group, groupLeader, members } = await createAndPopulateGroup({
+ groupDetails: { type: 'party', privacy: 'private' },
+ members: 2,
+ });
+
+ questingGroup = group;
+ leader = groupLeader;
+ partyMembers = members;
+
+ await leader.update({
+ [`items.quests.${PET_QUEST}`]: 1,
+ });
+ });
+
+ context('failure conditions', () => {
+ it('does not accept quest without an invite', async () => {
+ await expect(leader.post(`/groups/${questingGroup._id}/quests/accept`))
+ .to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('questInviteNotFound'),
+ });
+ });
+
+ it('does not accept quest for a group in which user is not a member', async () => {
+ await expect(user.post(`/groups/${questingGroup._id}/quests/accept`))
+ .to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('groupNotFound'),
+ });
+ });
+
+ it('does not accept quest for a guild', async () => {
+ let { group: guild, groupLeader: guildLeader } = await createAndPopulateGroup({
+ groupDetails: { type: 'guild', privacy: 'private' },
+ });
+
+ await expect(guildLeader.post(`/groups/${guild._id}/quests/accept`))
+ .to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('guildQuestsNotSupported'),
+ });
+ });
+
+ it('does not accept invite twice', async () => {
+ await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`);
+ await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`);
+
+ await expect(partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`))
+ .to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('questAlreadyAccepted'),
+ });
+ });
+
+ it('does not accept invite for a quest already underway', async () => {
+ await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`);
+ await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`);
+ // quest will start after everyone has accepted
+ await partyMembers[1].post(`/groups/${questingGroup._id}/quests/accept`);
+
+ await expect(partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`))
+ .to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('questAlreadyUnderway'),
+ });
+ });
+ });
+
+ context('successfully accepting a quest invitation', () => {
+ it('joins a quest from an invitation', async () => {
+ await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`);
+ await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`);
+
+ await Promise.all([partyMembers[0].sync(), questingGroup.sync()]);
+ expect(leader.party.quest.RSVPNeeded).to.equal(false);
+ expect(questingGroup.quest.members[partyMembers[0]._id]);
+ });
+
+ it('does not begin the quest if pending invitations remain', async () => {
+ await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`);
+ await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`);
+
+ await questingGroup.sync();
+ expect(questingGroup.quest.active).to.equal(false);
+ });
+
+ it('begins the quest if accepting the last pending invite', async () => {
+ await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`);
+ await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`);
+ // quest will start after everyone has accepted
+ await partyMembers[1].post(`/groups/${questingGroup._id}/quests/accept`);
+
+ await questingGroup.sync();
+ expect(questingGroup.quest.active).to.equal(true);
+ });
+ });
+});
diff --git a/test/api/v3/integration/quests/POST-groups_groupId_quests_force-start.test.js b/test/api/v3/integration/quests/POST-groups_groupId_quests_force-start.test.js
new file mode 100644
index 0000000000..b6d43f826b
--- /dev/null
+++ b/test/api/v3/integration/quests/POST-groups_groupId_quests_force-start.test.js
@@ -0,0 +1,126 @@
+import {
+ createAndPopulateGroup,
+ translate as t,
+ generateUser,
+} from '../../../../helpers/api-v3-integration.helper';
+
+describe('POST /groups/:groupId/quests/force-start', () => {
+ const PET_QUEST = 'whale';
+
+ let questingGroup;
+ let leader;
+ let partyMembers;
+
+ beforeEach(async () => {
+ let { group, groupLeader, members } = await createAndPopulateGroup({
+ groupDetails: { type: 'party', privacy: 'private' },
+ members: 2,
+ });
+
+ questingGroup = group;
+ leader = groupLeader;
+ partyMembers = members;
+
+ await leader.update({
+ [`items.quests.${PET_QUEST}`]: 1,
+ });
+ });
+
+ context('failure conditions', () => {
+ it('does not force start a quest for a group in which user is not a member', async () => {
+ let nonMember = await generateUser();
+
+ await expect(nonMember.post(`/groups/${questingGroup._id}/quests/force-start`))
+ .to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('groupNotFound'),
+ });
+ });
+
+ it('does not force start quest for a guild', async () => {
+ let { group: guild, groupLeader: guildLeader } = await createAndPopulateGroup({
+ groupDetails: { type: 'guild', privacy: 'private' },
+ });
+
+ await expect(guildLeader.post(`/groups/${guild._id}/quests/force-start`))
+ .to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('guildQuestsNotSupported'),
+ });
+ });
+
+ it('does not force start for a party without a pending quest', async () => {
+ await expect(leader.post(`/groups/${questingGroup._id}/quests/force-start`))
+ .to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('questNotPending'),
+ });
+ });
+
+ it('does not force start for a quest already underway', async () => {
+ await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`);
+ await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`);
+ // quest will start after everyone has accepted
+ await partyMembers[1].post(`/groups/${questingGroup._id}/quests/accept`);
+
+ await expect(leader.post(`/groups/${questingGroup._id}/quests/force-start`))
+ .to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('questAlreadyUnderway'),
+ });
+ });
+
+ it('does not allow non-quest leader or non-group leader to force start a quest', async () => {
+ await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`);
+
+ await expect(partyMembers[0].post(`/groups/${questingGroup._id}/quests/force-start`))
+ .to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('questOrGroupLeaderOnlyStartQuest'),
+ });
+ });
+ });
+
+ context('successfully force starting a quest', () => {
+ it('allows quest leader to force start quest', async () => {
+ let questLeader = partyMembers[0];
+ await questLeader.update({[`items.quests.${PET_QUEST}`]: 1});
+ await questLeader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`);
+
+ await questLeader.post(`/groups/${questingGroup._id}/quests/force-start`);
+
+ await questingGroup.sync();
+
+ expect(questingGroup.quest.active).to.eql(true);
+ });
+
+ it('allows group leader to force start quest', async () => {
+ let questLeader = partyMembers[0];
+ await questLeader.update({[`items.quests.${PET_QUEST}`]: 1});
+ await questLeader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`);
+
+ await leader.post(`/groups/${questingGroup._id}/quests/force-start`);
+
+ await questingGroup.sync();
+
+ expect(questingGroup.quest.active).to.eql(true);
+ });
+
+ it('sends back the quest object', async () => {
+ await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`);
+
+ let quest = await leader.post(`/groups/${questingGroup._id}/quests/force-start`);
+
+ expect(quest.active).to.eql(true);
+ expect(quest.key).to.eql(PET_QUEST);
+ expect(quest.members).to.eql({
+ [`${leader._id}`]: true,
+ });
+ });
+ });
+});
diff --git a/test/api/v3/integration/quests/POST-groups_groupId_quests_invite.test.js b/test/api/v3/integration/quests/POST-groups_groupId_quests_invite.test.js
new file mode 100644
index 0000000000..973a51a1a5
--- /dev/null
+++ b/test/api/v3/integration/quests/POST-groups_groupId_quests_invite.test.js
@@ -0,0 +1,192 @@
+import {
+ createAndPopulateGroup,
+ translate as t,
+ sleep,
+} from '../../../../helpers/api-v3-integration.helper';
+import { v4 as generateUUID } from 'uuid';
+import { quests as questScrolls } from '../../../../../common/script/content';
+
+describe('POST /groups/:groupId/quests/invite/:questKey', () => {
+ let questingGroup;
+ let leader;
+ let member;
+ const PET_QUEST = 'whale';
+
+ beforeEach(async () => {
+ let { group, groupLeader, members } = await createAndPopulateGroup({
+ groupDetails: { type: 'party', privacy: 'private' },
+ members: 1,
+ });
+
+ questingGroup = group;
+ leader = groupLeader;
+ member = members[0];
+ });
+
+ context('failure conditions', () => {
+ it('does not issue invites with an invalid group ID', async () => {
+ await expect(leader.post(`/groups/${generateUUID()}/quests/invite/${PET_QUEST}`)).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('groupNotFound'),
+ });
+ });
+
+ it('does not issue invites for a group in which user is not a member', async () => {
+ let { group } = await createAndPopulateGroup({
+ groupDetails: { type: 'party', privacy: 'private' },
+ members: 1,
+ });
+
+ let alternateGroup = group;
+
+ await expect(leader.post(`/groups/${alternateGroup._id}/quests/invite/${PET_QUEST}`)).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('groupNotFound'),
+ });
+ });
+
+ it('does not issue invites for Guilds', async () => {
+ let { group } = await createAndPopulateGroup({
+ groupDetails: { type: 'guild', privacy: 'public' },
+ members: 1,
+ });
+
+ let alternateGroup = group;
+
+ await expect(leader.post(`/groups/${alternateGroup._id}/quests/invite/${PET_QUEST}`)).to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('guildQuestsNotSupported'),
+ });
+ });
+
+ it('does not issue invites with an invalid quest key', async () => {
+ const FAKE_QUEST = 'herkimer';
+
+ await expect(leader.post(`/groups/${questingGroup._id}/quests/invite/${FAKE_QUEST}`)).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('questNotFound', {key: FAKE_QUEST}),
+ });
+ });
+
+ it('does not issue invites for a quest the user does not own', async () => {
+ await expect(leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`)).to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('questNotOwned'),
+ });
+ });
+
+ it('does not issue invites if the user is of insufficient Level', async () => {
+ const LEVELED_QUEST = 'atom1';
+ const LEVELED_QUEST_REQ = questScrolls[LEVELED_QUEST].lvl;
+ const leaderUpdate = {};
+ leaderUpdate[`items.quests.${LEVELED_QUEST}`] = 1;
+ leaderUpdate['stats.lvl'] = LEVELED_QUEST_REQ - 1;
+
+ await leader.update(leaderUpdate);
+
+ await expect(leader.post(`/groups/${questingGroup._id}/quests/invite/${LEVELED_QUEST}`)).to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('questLevelTooHigh', {level: LEVELED_QUEST_REQ}),
+ });
+ });
+
+ it('does not issue invites if a quest is already underway', async () => {
+ const QUEST_IN_PROGRESS = 'atom1';
+ const leaderUpdate = {};
+ leaderUpdate[`items.quests.${PET_QUEST}`] = 1;
+
+ await leader.update(leaderUpdate);
+ await questingGroup.update({ 'quest.key': QUEST_IN_PROGRESS });
+
+ await expect(leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`)).to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('questAlreadyUnderway'),
+ });
+ });
+ });
+
+ context('successfully issuing a quest invitation', () => {
+ beforeEach(async () => {
+ const memberUpdate = {};
+ memberUpdate[`items.quests.${PET_QUEST}`] = 1;
+
+ await Promise.all([
+ leader.update(memberUpdate),
+ member.update(memberUpdate),
+ ]);
+ });
+
+ it('adds quest details to group object', async () => {
+ await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`);
+
+ await questingGroup.sync();
+
+ let quest = questingGroup.quest;
+
+ expect(quest.key).to.eql(PET_QUEST);
+ expect(quest.active).to.eql(false);
+ expect(quest.leader).to.eql(leader._id);
+ expect(quest.members).to.have.property(leader._id, true);
+ expect(quest.members).to.have.property(member._id, null);
+ expect(quest).to.have.property('progress');
+ });
+
+ it('adds quest details to user objects', async () => {
+ await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`);
+
+ await sleep(0.1); // member updates happen in the background
+
+ await Promise.all([
+ leader.sync(),
+ member.sync(),
+ ]);
+
+ expect(leader.party.quest.key).to.eql(PET_QUEST);
+ expect(member.party.quest.key).to.eql(PET_QUEST);
+ expect(leader.party.quest.RSVPNeeded).to.eql(false);
+ expect(member.party.quest.RSVPNeeded).to.eql(true);
+ });
+
+ it('sends back the quest object', async () => {
+ let inviteResponse = await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`);
+
+ expect(inviteResponse.key).to.eql(PET_QUEST);
+ expect(inviteResponse.active).to.eql(false);
+ expect(inviteResponse.leader).to.eql(leader._id);
+ expect(inviteResponse.members).to.have.property(leader._id, true);
+ expect(inviteResponse.members).to.have.property(member._id, null);
+ expect(inviteResponse).to.have.property('progress');
+ });
+
+ it('allows non-party-leader party members to send invites', async () => {
+ let inviteResponse = await member.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`);
+
+ await questingGroup.sync();
+
+ expect(inviteResponse.key).to.eql(PET_QUEST);
+ expect(questingGroup.quest.key).to.eql(PET_QUEST);
+ });
+
+ it('starts quest automatically if user is in a solo party', async () => {
+ let leaderDetails = { balance: 10 };
+ leaderDetails[`items.quests.${PET_QUEST}`] = 1;
+ let { group, groupLeader } = await createAndPopulateGroup({
+ groupDetails: { type: 'party', privacy: 'private' },
+ leaderDetails,
+ });
+
+ await groupLeader.post(`/groups/${group._id}/quests/invite/${PET_QUEST}`);
+
+ await group.sync();
+
+ expect(group.quest.active).to.eql(true);
+ });
+ });
+});
diff --git a/test/api/v3/integration/quests/POST-groups_groupid_quests_abort.test.js b/test/api/v3/integration/quests/POST-groups_groupid_quests_abort.test.js
new file mode 100644
index 0000000000..850cf53646
--- /dev/null
+++ b/test/api/v3/integration/quests/POST-groups_groupid_quests_abort.test.js
@@ -0,0 +1,126 @@
+import {
+ createAndPopulateGroup,
+ translate as t,
+ generateUser,
+} from '../../../../helpers/api-v3-integration.helper';
+import { v4 as generateUUID } from 'uuid';
+
+describe('POST /groups/:groupId/quests/abort', () => {
+ let questingGroup;
+ let partyMembers;
+ let user;
+ let leader;
+
+ const PET_QUEST = 'whale';
+
+ beforeEach(async () => {
+ let { group, groupLeader, members } = await createAndPopulateGroup({
+ groupDetails: { type: 'party', privacy: 'private' },
+ members: 2,
+ });
+
+ questingGroup = group;
+ leader = groupLeader;
+ partyMembers = members;
+
+ await leader.update({
+ [`items.quests.${PET_QUEST}`]: 1,
+ });
+ user = await generateUser();
+ });
+
+ context('failure conditions', () => {
+ it('returns an error when group is not found', async () => {
+ await expect(partyMembers[0].post(`/groups/${generateUUID()}/quests/abort`))
+ .to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('groupNotFound'),
+ });
+ });
+
+ it('returns an error for a group in which user is not a member', async () => {
+ await expect(user.post(`/groups/${questingGroup._id}/quests/abort`))
+ .to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('groupNotFound'),
+ });
+ });
+
+ it('returns an error when group is a guild', async () => {
+ let { group: guild, groupLeader: guildLeader } = await createAndPopulateGroup({
+ groupDetails: { type: 'guild', privacy: 'private' },
+ });
+
+ await expect(guildLeader.post(`/groups/${guild._id}/quests/abort`))
+ .to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('guildQuestsNotSupported'),
+ });
+ });
+
+ it('returns an error when quest is not active', async () => {
+ await expect(partyMembers[0].post(`/groups/${questingGroup._id}/quests/abort`))
+ .to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('noActiveQuestToAbort'),
+ });
+ });
+
+ it('returns an error when non quest leader attempts to abort', async () => {
+ await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`);
+ await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`);
+ await partyMembers[1].post(`/groups/${questingGroup._id}/quests/accept`);
+
+ await expect(partyMembers[0].post(`/groups/${questingGroup._id}/quests/abort`))
+ .to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('onlyLeaderAbortQuest'),
+ });
+ });
+ });
+
+ it('aborts a quest', async () => {
+ await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`);
+ await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`);
+ await partyMembers[1].post(`/groups/${questingGroup._id}/quests/accept`);
+
+ let res = await leader.post(`/groups/${questingGroup._id}/quests/abort`);
+ await Promise.all([
+ leader.sync(),
+ questingGroup.sync(),
+ partyMembers[0].sync(),
+ partyMembers[1].sync(),
+ ]);
+
+ let cleanUserQuestObj = {
+ key: null,
+ progress: {
+ up: 0,
+ down: 0,
+ collect: {},
+ },
+ completed: null,
+ RSVPNeeded: false,
+ };
+
+ expect(leader.party.quest).to.eql(cleanUserQuestObj);
+ expect(partyMembers[0].party.quest).to.eql(cleanUserQuestObj);
+ expect(partyMembers[1].party.quest).to.eql(cleanUserQuestObj);
+ expect(leader.items.quests[PET_QUEST]).to.equal(1);
+ expect(questingGroup.quest).to.deep.equal(res);
+ expect(questingGroup.quest).to.eql({
+ key: null,
+ active: false,
+ leader: null,
+ progress: {
+ collect: {},
+ },
+ members: {},
+ });
+ });
+});
diff --git a/test/api/v3/integration/quests/POST-groups_groupid_quests_cancel.test.js b/test/api/v3/integration/quests/POST-groups_groupid_quests_cancel.test.js
new file mode 100644
index 0000000000..f3bd03a180
--- /dev/null
+++ b/test/api/v3/integration/quests/POST-groups_groupid_quests_cancel.test.js
@@ -0,0 +1,138 @@
+import {
+ createAndPopulateGroup,
+ translate as t,
+ generateUser,
+} from '../../../../helpers/api-v3-integration.helper';
+import { v4 as generateUUID } from 'uuid';
+
+describe('POST /groups/:groupId/quests/cancel', () => {
+ let questingGroup;
+ let partyMembers;
+ let user;
+ let leader;
+
+ const PET_QUEST = 'whale';
+
+ beforeEach(async () => {
+ let { group, groupLeader, members } = await createAndPopulateGroup({
+ groupDetails: { type: 'party', privacy: 'private' },
+ members: 2,
+ });
+
+ questingGroup = group;
+ leader = groupLeader;
+ partyMembers = members;
+
+ await leader.update({
+ [`items.quests.${PET_QUEST}`]: 1,
+ });
+ user = await generateUser();
+ });
+
+ context('failure conditions', () => {
+ it('returns an error when group is not found', async () => {
+ await expect(partyMembers[0].post(`/groups/${generateUUID()}/quests/cancel`))
+ .to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('groupNotFound'),
+ });
+ });
+
+ it('does not reject quest for a group in which user is not a member', async () => {
+ await expect(user.post(`/groups/${questingGroup._id}/quests/cancel`))
+ .to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('groupNotFound'),
+ });
+ });
+
+ it('returns an error when group is a guild', async () => {
+ let { group: guild, groupLeader: guildLeader } = await createAndPopulateGroup({
+ groupDetails: { type: 'guild', privacy: 'private' },
+ });
+
+ await expect(guildLeader.post(`/groups/${guild._id}/quests/cancel`))
+ .to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('guildQuestsNotSupported'),
+ });
+ });
+
+ it('returns an error when group is not on a quest', async () => {
+ await expect(partyMembers[0].post(`/groups/${questingGroup._id}/quests/cancel`))
+ .to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('questInvitationDoesNotExist'),
+ });
+ });
+
+ it('only the leader can cancel the quest', async () => {
+ await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`);
+
+ await expect(partyMembers[0].post(`/groups/${questingGroup._id}/quests/cancel`))
+ .to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('onlyLeaderCancelQuest'),
+ });
+ });
+
+ it('does not cancel a quest already underway', async () => {
+ await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`);
+ await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`);
+ // quest will start after everyone has accepted
+ await partyMembers[1].post(`/groups/${questingGroup._id}/quests/accept`);
+
+ await expect(leader.post(`/groups/${questingGroup._id}/quests/cancel`))
+ .to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('cantCancelActiveQuest'),
+ });
+ });
+ });
+
+ it('cancels a quest', async () => {
+ await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`);
+ await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`);
+
+ let res = await leader.post(`/groups/${questingGroup._id}/quests/cancel`);
+
+ await Promise.all([
+ leader.sync(),
+ partyMembers[0].sync(),
+ partyMembers[1].sync(),
+ questingGroup.sync(),
+ ]);
+
+ let clean = {
+ key: null,
+ progress: {
+ up: 0,
+ down: 0,
+ collect: {},
+ },
+ completed: null,
+ RSVPNeeded: false,
+ };
+
+ expect(leader.party.quest).to.eql(clean);
+ expect(partyMembers[1].party.quest).to.eql(clean);
+ expect(partyMembers[0].party.quest).to.eql(clean);
+
+ expect(res).to.eql(questingGroup.quest);
+ expect(questingGroup.quest).to.eql({
+ key: null,
+ active: false,
+ leader: null,
+ progress: {
+ collect: {},
+ },
+ members: {},
+ });
+ });
+});
diff --git a/test/api/v3/integration/quests/POST-groups_groupid_quests_leave.test.js b/test/api/v3/integration/quests/POST-groups_groupid_quests_leave.test.js
new file mode 100644
index 0000000000..65d781c163
--- /dev/null
+++ b/test/api/v3/integration/quests/POST-groups_groupid_quests_leave.test.js
@@ -0,0 +1,124 @@
+import {
+ createAndPopulateGroup,
+ translate as t,
+ generateUser,
+} from '../../../../helpers/api-v3-integration.helper';
+import { v4 as generateUUID } from 'uuid';
+
+describe('POST /groups/:groupId/quests/leave', () => {
+ let questingGroup;
+ let partyMembers;
+ let user;
+ let leader;
+
+ const PET_QUEST = 'whale';
+
+ beforeEach(async () => {
+ let { group, groupLeader, members } = await createAndPopulateGroup({
+ groupDetails: { type: 'party', privacy: 'private' },
+ members: 2,
+ });
+
+ questingGroup = group;
+ leader = groupLeader;
+ partyMembers = members;
+
+ await leader.update({
+ [`items.quests.${PET_QUEST}`]: 1,
+ });
+ user = await generateUser();
+ });
+
+ context('failure conditions', () => {
+ it('returns an error when group is not found', async () => {
+ await expect(partyMembers[0].post(`/groups/${generateUUID()}/quests/leave`))
+ .to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('groupNotFound'),
+ });
+ });
+
+ it('returns an error for a group in which user is not a member', async () => {
+ await expect(user.post(`/groups/${questingGroup._id}/quests/leave`))
+ .to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('groupNotFound'),
+ });
+ });
+
+ it('returns an error when group is a guild', async () => {
+ let { group: guild, groupLeader: guildLeader } = await createAndPopulateGroup({
+ groupDetails: { type: 'guild', privacy: 'private' },
+ });
+
+ await expect(guildLeader.post(`/groups/${guild._id}/quests/leave`))
+ .to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('guildQuestsNotSupported'),
+ });
+ });
+
+ it('returns an error when quest is not active', async () => {
+ await expect(partyMembers[0].post(`/groups/${questingGroup._id}/quests/leave`))
+ .to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('noActiveQuestToLeave'),
+ });
+ });
+
+ it('returns an error when quest leader attempts to leave', async () => {
+ await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`);
+ await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`);
+ await partyMembers[1].post(`/groups/${questingGroup._id}/quests/accept`);
+
+ await expect(leader.post(`/groups/${questingGroup._id}/quests/leave`))
+ .to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('questLeaderCannotLeaveQuest'),
+ });
+ });
+
+ it('returns an error when non quest member attempts to leave', async () => {
+ await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`);
+ await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`);
+ await partyMembers[1].post(`/groups/${questingGroup._id}/quests/reject`);
+
+ await expect(partyMembers[1].post(`/groups/${questingGroup._id}/quests/leave`))
+ .to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('notPartOfQuest'),
+ });
+ });
+ });
+
+ it('leaves a quest', async () => {
+ await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`);
+ await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`);
+ await partyMembers[1].post(`/groups/${questingGroup._id}/quests/accept`);
+
+ let leaveResult = await partyMembers[0].post(`/groups/${questingGroup._id}/quests/leave`);
+ await Promise.all([
+ partyMembers[0].sync(),
+ questingGroup.sync(),
+ ]);
+
+ expect(partyMembers[0].party.quest).to.eql({
+ key: null,
+ progress: {
+ up: 0,
+ down: 0,
+ collect: {},
+ },
+ completed: null,
+ RSVPNeeded: false,
+ });
+ expect(questingGroup.quest).to.deep.equal(leaveResult);
+ expect(questingGroup.quest.members[partyMembers[0]._id]).to.be.false;
+ });
+});
diff --git a/test/api/v3/integration/quests/POST-groups_groupid_quests_reject.test.js b/test/api/v3/integration/quests/POST-groups_groupid_quests_reject.test.js
new file mode 100644
index 0000000000..2dcddfe727
--- /dev/null
+++ b/test/api/v3/integration/quests/POST-groups_groupid_quests_reject.test.js
@@ -0,0 +1,146 @@
+import {
+ createAndPopulateGroup,
+ translate as t,
+ generateUser,
+} from '../../../../helpers/api-v3-integration.helper';
+import { v4 as generateUUID } from 'uuid';
+
+describe('POST /groups/:groupId/quests/reject', () => {
+ let questingGroup;
+ let partyMembers;
+ let user;
+ let leader;
+
+ const PET_QUEST = 'whale';
+
+ beforeEach(async () => {
+ let { group, groupLeader, members } = await createAndPopulateGroup({
+ groupDetails: { type: 'party', privacy: 'private' },
+ members: 2,
+ });
+
+ questingGroup = group;
+ leader = groupLeader;
+ partyMembers = members;
+
+ await leader.update({
+ [`items.quests.${PET_QUEST}`]: 1,
+ });
+ user = await generateUser();
+ });
+
+ context('failure conditions', () => {
+ it('returns an error when group is not found', async () => {
+ await expect(partyMembers[0].post(`/groups/${generateUUID()}/quests/reject`))
+ .to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('groupNotFound'),
+ });
+ });
+
+ it('does not accept quest for a group in which user is not a member', async () => {
+ await expect(user.post(`/groups/${questingGroup._id}/quests/accept`))
+ .to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('groupNotFound'),
+ });
+ });
+
+ it('returns an error when group is a guild', async () => {
+ let { group: guild, groupLeader: guildLeader } = await createAndPopulateGroup({
+ groupDetails: { type: 'guild', privacy: 'private' },
+ });
+
+ await expect(guildLeader.post(`/groups/${guild._id}/quests/reject`))
+ .to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('guildQuestsNotSupported'),
+ });
+ });
+
+ it('returns an error when group is not on a quest', async () => {
+ await expect(partyMembers[0].post(`/groups/${questingGroup._id}/quests/reject`))
+ .to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('questInvitationDoesNotExist'),
+ });
+ });
+
+ it('return an error when a user rejects an invite twice', async () => {
+ await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`);
+ await partyMembers[0].post(`/groups/${questingGroup._id}/quests/reject`);
+
+ await expect(partyMembers[0].post(`/groups/${questingGroup._id}/quests/reject`))
+ .to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('questAlreadyRejected'),
+ });
+ });
+
+ it('return an error when a user rejects an invite already accepted', async () => {
+ await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`);
+ await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`);
+
+ await expect(partyMembers[0].post(`/groups/${questingGroup._id}/quests/reject`))
+ .to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('questAlreadyAccepted'),
+ });
+ });
+
+ it('does not reject invite for a quest already underway', async () => {
+ await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`);
+ await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`);
+ // quest will start after everyone has accepted
+ await partyMembers[1].post(`/groups/${questingGroup._id}/quests/accept`);
+
+ await expect(partyMembers[0].post(`/groups/${questingGroup._id}/quests/reject`))
+ .to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('questAlreadyUnderway'),
+ });
+ });
+ });
+
+ context('successfully quest rejection', () => {
+ let cleanUserQuestObj = {
+ key: null,
+ progress: {
+ up: 0,
+ down: 0,
+ collect: {},
+ },
+ completed: null,
+ RSVPNeeded: false,
+ };
+
+ it('rejects a quest invitation', async () => {
+ await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`);
+
+ let res = await partyMembers[0].post(`/groups/${questingGroup._id}/quests/reject`);
+ await partyMembers[0].sync();
+ await questingGroup.sync();
+
+ expect(partyMembers[0].party.quest).to.eql(cleanUserQuestObj);
+ expect(questingGroup.quest.members[partyMembers[0]._id]).to.be.false;
+ expect(questingGroup.quest.active).to.be.false;
+ expect(res).to.eql(questingGroup.quest);
+ });
+
+ it('starts the quest when the last user reject', async () => {
+ await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`);
+ await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`);
+ await partyMembers[1].post(`/groups/${questingGroup._id}/quests/reject`);
+ await questingGroup.sync();
+
+ expect(questingGroup.quest.active).to.be.true;
+ });
+ });
+});
diff --git a/test/api/v3/integration/status/GET-status.test.js b/test/api/v3/integration/status/GET-status.test.js
new file mode 100644
index 0000000000..1d4d33a7d7
--- /dev/null
+++ b/test/api/v3/integration/status/GET-status.test.js
@@ -0,0 +1,12 @@
+import {
+ requester,
+} from '../../../../helpers/api-v3-integration.helper';
+
+describe('GET /status', () => {
+ it('returns status: up', async () => {
+ let res = await requester().get('/status');
+ expect(res).to.eql({
+ status: 'up',
+ });
+ });
+});
diff --git a/test/api/v3/integration/tags/DELETE-tags_id.test.js b/test/api/v3/integration/tags/DELETE-tags_id.test.js
new file mode 100644
index 0000000000..c03e8bb9e0
--- /dev/null
+++ b/test/api/v3/integration/tags/DELETE-tags_id.test.js
@@ -0,0 +1,27 @@
+import {
+ generateUser,
+} from '../../../../helpers/api-integration/v3';
+
+describe('DELETE /tags/:tagId', () => {
+ let user;
+
+ before(async () => {
+ user = await generateUser();
+ });
+
+ it('deletes a tag given it\'s id', async () => {
+ let tagName = 'Tag 1';
+ let tag = await user.post('/tags', {name: tagName});
+ let numberOfTags = (await user.get('/tags')).length;
+
+ await user.del(`/tags/${tag.id}`);
+
+ let tags = await user.get('/tags');
+ let tagNames = tags.map((t) => {
+ return t.name;
+ });
+
+ expect(tags.length).to.equal(numberOfTags - 1);
+ expect(tagNames).to.not.include(tagName);
+ });
+});
diff --git a/test/api/v3/integration/tags/GET-tags.test.js b/test/api/v3/integration/tags/GET-tags.test.js
new file mode 100644
index 0000000000..7a24963474
--- /dev/null
+++ b/test/api/v3/integration/tags/GET-tags.test.js
@@ -0,0 +1,22 @@
+import {
+ generateUser,
+} from '../../../../helpers/api-integration/v3';
+
+describe('GET /tags', () => {
+ let user;
+
+ before(async () => {
+ user = await generateUser();
+ });
+
+ it('returns all user\'s tags', async () => {
+ let tag1 = await user.post('/tags', {name: 'Tag 1'});
+ let tag2 = await user.post('/tags', {name: 'Tag 2'});
+
+ let tags = await user.get('/tags');
+
+ expect(tags.length).to.equal(2 + 3); // + 3 because 1 is a default task
+ expect(tags[tags.length - 2].name).to.equal(tag1.name);
+ expect(tags[tags.length - 1].name).to.equal(tag2.name);
+ });
+});
diff --git a/test/api/v3/integration/tags/GET-tags_id.test.js b/test/api/v3/integration/tags/GET-tags_id.test.js
new file mode 100644
index 0000000000..4ab818593d
--- /dev/null
+++ b/test/api/v3/integration/tags/GET-tags_id.test.js
@@ -0,0 +1,20 @@
+import {
+ generateUser,
+} from '../../../../helpers/api-integration/v3';
+
+describe('GET /tags/:tagId', () => {
+ let user;
+
+ before(async () => {
+ user = await generateUser();
+ });
+
+ it('returns a tag given it\'s id', async () => {
+ let createdTag = await user.post('/tags', {name: 'Tag 1'});
+ let tag = await user.get(`/tags/${createdTag.id}`);
+
+ expect(tag).to.deep.equal(createdTag);
+ });
+
+ it('handles non-existing tags');
+});
diff --git a/test/api/v3/integration/tags/POST-tag-reorder.test.js b/test/api/v3/integration/tags/POST-tag-reorder.test.js
new file mode 100644
index 0000000000..0710cecf3e
--- /dev/null
+++ b/test/api/v3/integration/tags/POST-tag-reorder.test.js
@@ -0,0 +1,44 @@
+import {
+ generateUser,
+ translate as t,
+} from '../../../../helpers/api-integration/v3';
+
+describe('POST /reorder-tags', () => {
+ let user;
+
+ before(async () => {
+ user = await generateUser();
+ });
+
+ it('returns error when no parameters are provided', async () => {
+ await expect(user.post('/reorder-tags'))
+ .to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: 'Invalid request parameters.',
+ });
+ });
+
+ it('returns error when tag is not found', async () => {
+ await expect(user.post('/reorder-tags', {tagId: 'fake-id', to: 3}))
+ .to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('tagNotFound'),
+ });
+ });
+
+ it('updates tags', async () => {
+ let tag1Name = 'Tag 1';
+ let tag2Name = 'Tag 2';
+ await user.post('/tags', {name: tag1Name});
+ await user.post('/tags', {name: tag2Name});
+ await user.sync();
+
+ await user.post('/reorder-tags', {tagId: user.tags[4].id, to: 3});
+ await user.sync();
+
+ expect(user.tags[3].name).to.equal(tag2Name);
+ expect(user.tags[4].name).to.equal(tag1Name);
+ });
+});
diff --git a/test/api/v3/integration/tags/POST-tags.test.js b/test/api/v3/integration/tags/POST-tags.test.js
new file mode 100644
index 0000000000..93f2dfdb60
--- /dev/null
+++ b/test/api/v3/integration/tags/POST-tags.test.js
@@ -0,0 +1,25 @@
+import {
+ generateUser,
+} from '../../../../helpers/api-integration/v3';
+
+describe('POST /tags', () => {
+ let user;
+
+ beforeEach(async () => {
+ user = await generateUser();
+ });
+
+ it('creates a tag correctly', async () => {
+ let tagName = 'Tag 1';
+ let createdTag = await user.post('/tags', {
+ name: tagName,
+ ignored: false,
+ });
+
+ let tag = await user.get(`/tags/${createdTag.id}`);
+
+ expect(tag.name).to.equal(tagName);
+ expect(tag.ignored).to.not.exist;
+ expect(tag).to.deep.equal(createdTag);
+ });
+});
diff --git a/test/api/v3/integration/tags/PUT-tags_id.test.js b/test/api/v3/integration/tags/PUT-tags_id.test.js
new file mode 100644
index 0000000000..4c16453ac3
--- /dev/null
+++ b/test/api/v3/integration/tags/PUT-tags_id.test.js
@@ -0,0 +1,28 @@
+import {
+ generateUser,
+} from '../../../../helpers/api-integration/v3';
+
+describe('PUT /tags/:tagId', () => {
+ let user;
+
+ before(async () => {
+ user = await generateUser();
+ });
+
+ it('updates a tag given it\'s id', async () => {
+ let updatedTagName = 'Tag updated';
+ let createdTag = await user.post('/tags', {name: 'Tag 1'});
+ let updatedTag = await user.put(`/tags/${createdTag.id}`, {
+ name: updatedTagName,
+ ignored: true,
+ });
+
+ createdTag = await user.get(`/tags/${updatedTag.id}`);
+
+ expect(updatedTag.name).to.equal(updatedTagName);
+ expect(updatedTag.ignored).to.not.exist;
+
+ expect(createdTag.name).to.equal(updatedTagName);
+ expect(createdTag.ignored).to.not.exist;
+ });
+});
diff --git a/test/api/v3/integration/tasks/DELETE-tasks_id.test.js b/test/api/v3/integration/tasks/DELETE-tasks_id.test.js
new file mode 100644
index 0000000000..bb92e4759f
--- /dev/null
+++ b/test/api/v3/integration/tasks/DELETE-tasks_id.test.js
@@ -0,0 +1,59 @@
+import {
+ generateUser,
+ translate as t,
+} from '../../../../helpers/api-integration/v3';
+
+describe('DELETE /tasks/:id', () => {
+ let user;
+
+ before(async () => {
+ user = await generateUser();
+ });
+
+ context('task can be deleted', () => {
+ let task;
+
+ beforeEach(async () => {
+ task = await user.post('/tasks/user', {
+ text: 'test habit',
+ type: 'habit',
+ });
+ });
+
+ it('deletes a user\'s task', async () => {
+ await user.del(`/tasks/${task._id}`);
+
+ await expect(user.get(`/tasks/${task._id}`)).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('taskNotFound'),
+ });
+ });
+ });
+
+ context('task cannot be deleted', () => {
+ it('cannot delete a non-existant task', async () => {
+ await expect(user.del('/tasks/550e8400-e29b-41d4-a716-446655440000')).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('taskNotFound'),
+ });
+ });
+
+ it('cannot delete a task owned by someone else', async () => {
+ let anotherUser = await generateUser();
+ let anotherUsersTask = await anotherUser.post('/tasks/user', {
+ text: 'test habit',
+ type: 'habit',
+ });
+
+ await expect(user.del(`/tasks/${anotherUsersTask._id}`)).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('taskNotFound'),
+ });
+ });
+
+ it('removes a task from user.tasksOrder'); // TODO
+ });
+});
diff --git a/test/api/v3/integration/tasks/GET-tasks_challenge_challengeId.test.js b/test/api/v3/integration/tasks/GET-tasks_challenge_challengeId.test.js
new file mode 100644
index 0000000000..3fed4cfaed
--- /dev/null
+++ b/test/api/v3/integration/tasks/GET-tasks_challenge_challengeId.test.js
@@ -0,0 +1,75 @@
+import {
+ generateUser,
+ generateGroup,
+ generateChallenge,
+ translate as t,
+} from '../../../../helpers/api-integration/v3';
+import { each } from 'lodash';
+import { v4 as generateUUID } from 'uuid';
+
+describe('GET /tasks/:taskId', () => {
+ let user;
+ let guild;
+ let challenge;
+ let task;
+ let tasksToTest = {
+ habit: {
+ text: 'test habit',
+ type: 'habit',
+ up: false,
+ down: true,
+ },
+ todo: {
+ text: 'test todo',
+ type: 'todo',
+ },
+ daily: {
+ text: 'test daily',
+ type: 'daily',
+ frequency: 'daily',
+ everyX: 5,
+ startDate: new Date(),
+ },
+ reward: {
+ text: 'test reward',
+ type: 'reward',
+ },
+ };
+
+ before(async () => {
+ user = await generateUser();
+ guild = await generateGroup(user);
+ challenge = await generateChallenge(user, guild);
+ });
+
+ it('returns error when incorrect id is passed', async () => {
+ await expect(user.get(`/tasks/${generateUUID()}`)).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('taskNotFound'),
+ });
+ });
+
+ each(tasksToTest, (taskValue, taskType) => {
+ context(`${taskType}`, () => {
+ before(async () => {
+ task = await user.post(`/tasks/challenge/${challenge._id}`, taskValue);
+ });
+
+ it('gets challenge task', async () => {
+ let getTask = await user.get(`/tasks/${task._id}`);
+ expect(getTask).to.eql(task);
+ });
+
+ it('returns error when user is not a member of the challenge', async () => {
+ let anotherUser = await generateUser();
+
+ await expect(anotherUser.get(`/tasks/${task._id}`)).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('taskNotFound'),
+ });
+ });
+ });
+ });
+});
diff --git a/test/api/v3/integration/tasks/GET-tasks_id.test.js b/test/api/v3/integration/tasks/GET-tasks_id.test.js
new file mode 100644
index 0000000000..99f2d769f4
--- /dev/null
+++ b/test/api/v3/integration/tasks/GET-tasks_id.test.js
@@ -0,0 +1,58 @@
+import {
+ generateUser,
+ translate as t,
+} from '../../../../helpers/api-integration/v3';
+import { v4 as generateUUID } from 'uuid';
+
+describe('GET /tasks/:id', () => {
+ let user;
+
+ before(async () => {
+ user = await generateUser();
+ });
+
+ context('task can be accessed', async () => {
+ let task;
+
+ beforeEach(async () => {
+ task = await user.post('/tasks/user', {
+ text: 'test habit',
+ type: 'habit',
+ });
+ });
+
+ it('gets specified task', async () => {
+ let getTask = await user.get(`/tasks/${task._id}`);
+ expect(getTask).to.eql(task);
+ });
+
+ // TODO after challenges are implemented
+ it('can get active challenge task that user does not own'); // Yes?
+ });
+
+ context('task cannot be accessed', () => {
+ it('cannot get a non-existant task', async () => {
+ let dummyId = generateUUID();
+
+ await expect(user.get(`/tasks/${dummyId}`)).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('taskNotFound'),
+ });
+ });
+
+ it('cannot get a task owned by someone else', async () => {
+ let anotherUser = await generateUser();
+ let task = await user.post('/tasks/user', {
+ text: 'test habit',
+ type: 'habit',
+ });
+
+ await expect(anotherUser.get(`/tasks/${task._id}`)).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('taskNotFound'),
+ });
+ });
+ });
+});
diff --git a/test/api/v3/integration/tasks/GET-tasks_user.test.js b/test/api/v3/integration/tasks/GET-tasks_user.test.js
new file mode 100644
index 0000000000..406c0d43fb
--- /dev/null
+++ b/test/api/v3/integration/tasks/GET-tasks_user.test.js
@@ -0,0 +1,84 @@
+import {
+ generateUser,
+} from '../../../../helpers/api-integration/v3';
+
+describe('GET /tasks/user', () => {
+ let user;
+
+ beforeEach(async () => {
+ user = await generateUser();
+ });
+
+ it('returns all user\'s tasks', async () => {
+ let createdTasks = await user.post('/tasks/user', [{text: 'test habit', type: 'habit'}, {text: 'test todo', type: 'todo'}]);
+ let tasks = await user.get('/tasks/user');
+ expect(tasks.length).to.equal(createdTasks.length + 1); // + 1 because 1 is a default task
+ });
+
+ it('returns only a type of user\'s tasks if req.query.type is specified', async () => {
+ let createdTasks = await user.post('/tasks/user', [
+ {text: 'test habit', type: 'habit'},
+ {text: 'test daily', type: 'daily'},
+ {text: 'test reward', type: 'reward'},
+ {text: 'test todo', type: 'todo'},
+ ]);
+ let habits = await user.get('/tasks/user?type=habits');
+ let dailys = await user.get('/tasks/user?type=dailys');
+ let rewards = await user.get('/tasks/user?type=rewards');
+
+ expect(habits.length).to.be.at.least(1);
+ expect(habits[0]._id).to.equal(createdTasks[0]._id);
+ expect(dailys.length).to.be.at.least(1);
+ expect(dailys[0]._id).to.equal(createdTasks[1]._id);
+ expect(rewards.length).to.be.at.least(1);
+ expect(rewards[0]._id).to.equal(createdTasks[2]._id);
+ });
+
+ it('returns uncompleted todos if req.query.type is "todos"', async () => {
+ let existingTodos = await user.get('/tasks/user?type=todos');
+
+ // populate user with other task types
+ await user.post('/tasks/user', [
+ {text: 'daily', type: 'daily'},
+ {text: 'reward', type: 'reward'},
+ {text: 'habit', type: 'habit'},
+ ]);
+
+ let newUncompletedTodos = await user.post('/tasks/user', [
+ {text: 'test todo 1', type: 'todo'},
+ {text: 'test todo 2', type: 'todo'},
+ ]);
+ let todoToBeCompleted = await user.post('/tasks/user', {
+ text: 'wll be completed todo', type: 'todo',
+ });
+
+ await user.post(`/tasks/${todoToBeCompleted._id}/score/up`);
+
+ let uncompletedTodos = [...existingTodos, ...newUncompletedTodos];
+
+ let todos = await user.get('/tasks/user?type=todos');
+
+ expect(todos.length).to.be.gte(2);
+ expect(todos.length).to.eql(uncompletedTodos.length);
+ expect(todos.every(task => task.type === 'todo'));
+ expect(todos.every(task => task.completed === false));
+ });
+
+ it('returns completed todos sorted by reverse completion date if req.query.type is "completeTodos"', async () => {
+ let todo1 = await user.post('/tasks/user', {text: 'todo to complete 1', type: 'todo'});
+ let todo2 = await user.post('/tasks/user', {text: 'todo to complete 2', type: 'todo'});
+
+ await user.sync();
+ let initialTodoCount = user.tasksOrder.todos.length;
+
+ await user.post(`/tasks/${todo2._id}/score/up`);
+ await user.post(`/tasks/${todo1._id}/score/up`);
+ await user.sync();
+
+ expect(user.tasksOrder.todos.length).to.equal(initialTodoCount - 2);
+
+ let completedTodos = await user.get('/tasks/user?type=completedTodos');
+ expect(completedTodos.length).to.equal(2);
+ expect(completedTodos[completedTodos.length - 1].text).to.equal('todo to complete 2'); // last is the todo that was completed most recently
+ });
+});
diff --git a/test/api/v3/integration/tasks/POST-tasks_clearCompletedTodos.test.js b/test/api/v3/integration/tasks/POST-tasks_clearCompletedTodos.test.js
new file mode 100644
index 0000000000..d6cdf21749
--- /dev/null
+++ b/test/api/v3/integration/tasks/POST-tasks_clearCompletedTodos.test.js
@@ -0,0 +1,43 @@
+import {
+ generateUser,
+ generateGroup,
+ generateChallenge,
+} from '../../../../helpers/api-integration/v3';
+
+describe('POST /tasks/clearCompletedTodos', () => {
+ it('deletes all completed todos except the ones from a challenge', async () => {
+ let user = await generateUser({balance: 1});
+ let guild = await generateGroup(user);
+ let challenge = await generateChallenge(user, guild);
+
+ let initialTodoCount = user.tasksOrder.todos.length;
+ await user.post('/tasks/user', [
+ {text: 'todo 1', type: 'todo'},
+ {text: 'todo 2', type: 'todo'},
+ {text: 'todo 3', type: 'todo'},
+ {text: 'todo 4', type: 'todo'},
+ {text: 'todo 5', type: 'todo'},
+ ]);
+
+ await user.post(`/tasks/challenge/${challenge._id}`, {
+ text: 'todo 6',
+ type: 'todo',
+ });
+
+ let tasks = await user.get('/tasks/user?type=todos');
+ expect(tasks.length).to.equal(initialTodoCount + 6);
+
+ for (let task of tasks) {
+ if (['todo 2', 'todo 3', 'todo 6'].indexOf(task.text) !== -1) {
+ await user.post(`/tasks/${task._id}/score/up`); // eslint-disable-line babel/no-await-in-loop
+ }
+ }
+
+ await user.post('/tasks/clearCompletedTodos');
+ let completedTodos = await user.get('/tasks/user?type=completedTodos');
+ let todos = await user.get('/tasks/user?type=todos');
+ let allTodos = todos.concat(completedTodos);
+ expect(allTodos.length).to.equal(initialTodoCount + 4); // + 6 - 3 completed (but one is from challenge)
+ expect(allTodos[allTodos.length - 1].text).to.equal('todo 6');
+ });
+});
diff --git a/test/api/v3/integration/tasks/POST-tasks_id_score_direction.test.js b/test/api/v3/integration/tasks/POST-tasks_id_score_direction.test.js
new file mode 100644
index 0000000000..86236fd110
--- /dev/null
+++ b/test/api/v3/integration/tasks/POST-tasks_id_score_direction.test.js
@@ -0,0 +1,298 @@
+import {
+ generateUser,
+ translate as t,
+} from '../../../../helpers/api-integration/v3';
+import { v4 as generateUUID } from 'uuid';
+
+describe('POST /tasks/:id/score/:direction', () => {
+ let user;
+
+ beforeEach(async () => {
+ user = await generateUser({
+ 'stats.gp': 100,
+ });
+ });
+
+ context('all', () => {
+ it('requires a task id', async () => {
+ await expect(user.post('/tasks/123/score/up')).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('invalidReqParams'),
+ });
+ });
+
+ it('requires a task direction', async () => {
+ await expect(user.post(`/tasks/${generateUUID()}/score/tt`)).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('invalidReqParams'),
+ });
+ });
+ });
+
+ context('todos', () => {
+ let todo;
+
+ beforeEach(async () => {
+ todo = await user.post('/tasks/user', {
+ text: 'test todo',
+ type: 'todo',
+ });
+ });
+
+ it('completes todo when direction is up', async () => {
+ await user.post(`/tasks/${todo._id}/score/up`);
+ let task = await user.get(`/tasks/${todo._id}`);
+
+ expect(task.completed).to.equal(true);
+ expect(task.dateCompleted).to.be.a('string'); // date gets converted to a string as json doesn't have a Date type
+ });
+
+ it('moves completed todos out of user.tasksOrder.todos', async () => {
+ let getUser = await user.get('/user');
+ expect(getUser.tasksOrder.todos.indexOf(todo._id)).to.not.equal(-1);
+
+ await user.post(`/tasks/${todo._id}/score/up`);
+ let updatedTask = await user.get(`/tasks/${todo._id}`);
+ expect(updatedTask.completed).to.equal(true);
+
+ let updatedUser = await user.get('/user');
+ expect(updatedUser.tasksOrder.todos.indexOf(todo._id)).to.equal(-1);
+ });
+
+ it('moves un-completed todos back into user.tasksOrder.todos', async () => {
+ let getUser = await user.get('/user');
+ expect(getUser.tasksOrder.todos.indexOf(todo._id)).to.not.equal(-1);
+
+ await user.post(`/tasks/${todo._id}/score/up`);
+ await user.post(`/tasks/${todo._id}/score/down`);
+
+ let updatedTask = await user.get(`/tasks/${todo._id}`);
+ expect(updatedTask.completed).to.equal(false);
+
+ let updatedUser = await user.get('/user');
+ let l = updatedUser.tasksOrder.todos.length;
+ expect(updatedUser.tasksOrder.todos.indexOf(todo._id)).not.to.equal(-1);
+ expect(updatedUser.tasksOrder.todos.indexOf(todo._id)).to.equal(l - 1); // Check that it was pushed at the bottom
+ });
+
+ it('uncompletes todo when direction is down', async () => {
+ await user.post(`/tasks/${todo._id}/score/down`);
+ let updatedTask = await user.get(`/tasks/${todo._id}`);
+
+ expect(updatedTask.completed).to.equal(false);
+ expect(updatedTask.dateCompleted).to.be.a('undefined');
+ });
+
+ it('scores up todo even if it is already completed'); // Yes?
+
+ it('scores down todo even if it is already uncompleted'); // Yes?
+
+ context('user stats when direction is up', () => {
+ let updatedUser;
+
+ beforeEach(async () => {
+ await user.post(`/tasks/${todo._id}/score/up`);
+ updatedUser = await user.get('/user');
+ });
+
+ it('increases user\'s mp', () => {
+ expect(updatedUser.stats.mp).to.be.greaterThan(user.stats.mp);
+ });
+
+ it('increases user\'s exp', () => {
+ expect(updatedUser.stats.exp).to.be.greaterThan(user.stats.exp);
+ });
+
+ it('increases user\'s gold', () => {
+ expect(updatedUser.stats.gp).to.be.greaterThan(user.stats.gp);
+ });
+ });
+
+ context('user stats when direction is down', () => {
+ let updatedUser;
+
+ beforeEach(async () => {
+ await user.post(`/tasks/${todo._id}/score/down`);
+ updatedUser = await user.get('/user');
+ });
+
+ it('decreases user\'s mp', () => {
+ expect(updatedUser.stats.mp).to.be.lessThan(user.stats.mp);
+ });
+
+ it('decreases user\'s exp', () => {
+ expect(updatedUser.stats.exp).to.be.lessThan(user.stats.exp);
+ });
+
+ it('decreases user\'s gold', () => {
+ expect(updatedUser.stats.gp).to.be.lessThan(user.stats.gp);
+ });
+ });
+ });
+
+ context('dailys', () => {
+ let daily;
+
+ beforeEach(async () => {
+ daily = await user.post('/tasks/user', {
+ text: 'test daily',
+ type: 'daily',
+ });
+ });
+
+ it('completes daily when direction is up', async () => {
+ await user.post(`/tasks/${daily._id}/score/up`);
+ let task = await user.get(`/tasks/${daily._id}`);
+
+ expect(task.completed).to.equal(true);
+ });
+
+ it('uncompletes daily when direction is down', async () => {
+ await user.post(`/tasks/${daily._id}/score/down`);
+ let task = await user.get(`/tasks/${daily._id}`);
+
+ expect(task.completed).to.equal(false);
+ });
+
+ it('scores up daily even if it is already completed'); // Yes?
+
+ it('scores down daily even if it is already uncompleted'); // Yes?
+
+ context('user stats when direction is up', () => {
+ let updatedUser;
+
+ beforeEach(async () => {
+ await user.post(`/tasks/${daily._id}/score/up`);
+ updatedUser = await user.get('/user');
+ });
+
+ it('increases user\'s mp', () => {
+ expect(updatedUser.stats.mp).to.be.greaterThan(user.stats.mp);
+ });
+
+ it('increases user\'s exp', () => {
+ expect(updatedUser.stats.exp).to.be.greaterThan(user.stats.exp);
+ });
+
+ it('increases user\'s gold', () => {
+ expect(updatedUser.stats.gp).to.be.greaterThan(user.stats.gp);
+ });
+ });
+
+ context('user stats when direction is down', () => {
+ let updatedUser;
+
+ beforeEach(async () => {
+ await user.post(`/tasks/${daily._id}/score/down`);
+ updatedUser = await user.get('/user');
+ });
+
+ it('decreases user\'s mp', () => {
+ expect(updatedUser.stats.mp).to.be.lessThan(user.stats.mp);
+ });
+
+ it('decreases user\'s exp', () => {
+ expect(updatedUser.stats.exp).to.be.lessThan(user.stats.exp);
+ });
+
+ it('decreases user\'s gold', () => {
+ expect(updatedUser.stats.gp).to.be.lessThan(user.stats.gp);
+ });
+ });
+ });
+
+ context('habits', () => {
+ let habit, minusHabit, plusHabit, neitherHabit; // eslint-disable-line no-unused-vars
+
+ beforeEach(async () => {
+ habit = await user.post('/tasks/user', {
+ text: 'test habit',
+ type: 'habit',
+ });
+
+ minusHabit = await user.post('/tasks/user', {
+ text: 'test min habit',
+ type: 'habit',
+ up: false,
+ });
+
+ plusHabit = await user.post('/tasks/user', {
+ text: 'test plus habit',
+ type: 'habit',
+ down: false,
+ });
+
+ neitherHabit = await user.post('/tasks/user', {
+ text: 'test neither habit',
+ type: 'habit',
+ up: false,
+ down: false,
+ });
+ });
+
+ it('prevents plus only habit from scoring down'); // Yes?
+
+ it('prevents minus only habit from scoring up'); // Yes?
+
+ it('increases user\'s mp when direction is up', async () => {
+ await user.post(`/tasks/${habit._id}/score/up`);
+ let updatedUser = await user.get('/user');
+
+ expect(updatedUser.stats.mp).to.be.greaterThan(user.stats.mp);
+ });
+
+ it('decreases user\'s mp when direction is down', async () => {
+ await user.post(`/tasks/${habit._id}/score/down`);
+ let updatedUser = await user.get('/user');
+
+ expect(updatedUser.stats.mp).to.be.lessThan(user.stats.mp);
+ });
+
+ it('increases user\'s exp when direction is up', async () => {
+ await user.post(`/tasks/${habit._id}/score/up`);
+ let updatedUser = await user.get('/user');
+
+ expect(updatedUser.stats.exp).to.be.greaterThan(user.stats.exp);
+ });
+
+ it('increases user\'s gold when direction is up', async () => {
+ await user.post(`/tasks/${habit._id}/score/up`);
+ let updatedUser = await user.get('/user');
+
+ expect(updatedUser.stats.gp).to.be.greaterThan(user.stats.gp);
+ });
+ });
+
+ context('reward', () => {
+ let reward, updatedUser;
+
+ beforeEach(async () => {
+ reward = await user.post('/tasks/user', {
+ text: 'test reward',
+ type: 'reward',
+ value: 5,
+ });
+
+ await user.post(`/tasks/${reward._id}/score/up`);
+ updatedUser = await user.get('/user');
+ });
+
+ it('purchases reward', () => {
+ expect(user.stats.gp).to.equal(updatedUser.stats.gp + 5);
+ });
+
+ it('does not change user\'s mp', () => {
+ expect(user.stats.mp).to.equal(updatedUser.stats.mp);
+ });
+
+ it('does not change user\'s exp', () => {
+ expect(user.stats.exp).to.equal(updatedUser.stats.exp);
+ });
+
+ it('does not allow a down direction', () => {
+ expect(user.stats.mp).to.equal(updatedUser.stats.mp);
+ });
+ });
+});
diff --git a/test/api/v3/integration/tasks/POST-tasks_move_taskId_to_position.test.js b/test/api/v3/integration/tasks/POST-tasks_move_taskId_to_position.test.js
new file mode 100644
index 0000000000..211c7ea975
--- /dev/null
+++ b/test/api/v3/integration/tasks/POST-tasks_move_taskId_to_position.test.js
@@ -0,0 +1,84 @@
+import {
+ generateUser,
+ translate as t,
+} from '../../../../helpers/api-integration/v3';
+import { v4 as generateUUID } from 'uuid';
+
+describe('POST /tasks/:taskId/move/to/:position', () => {
+ let user;
+
+ beforeEach(async () => {
+ user = await generateUser();
+ });
+
+ it('requires a valid taskId', async () => {
+ await expect(user.post('/tasks/123/move/to/1')).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('invalidReqParams'),
+ });
+ });
+
+ it('requires a numeric position parameter', async () => {
+ await expect(user.post(`/tasks/${generateUUID()}/move/to/notANumber`)).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('invalidReqParams'),
+ });
+ });
+
+ it('taskId must match a valid task', async () => {
+ await expect(user.post(`/tasks/${generateUUID()}/move/to/1`)).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('taskNotFound'),
+ });
+ });
+
+ it('can move task to new position', async () => {
+ let tasks = await user.post('/tasks/user', [
+ {type: 'habit', text: 'habit 1'},
+ {type: 'habit', text: 'habit 2'},
+ {type: 'daily', text: 'daily 1'},
+ {type: 'habit', text: 'habit 3'},
+ {type: 'habit', text: 'habit 4'},
+ {type: 'todo', text: 'todo 1'},
+ {type: 'habit', text: 'habit 5'},
+ ]);
+
+ let taskToMove = tasks[1];
+ expect(taskToMove.text).to.equal('habit 2');
+ let newOrder = await user.post(`/tasks/${tasks[1]._id}/move/to/3`);
+ expect(newOrder[3]).to.equal(taskToMove._id);
+ expect(newOrder.length).to.equal(5);
+ });
+
+ it('can\'t move completed todo', async () => {
+ let task = await user.post('/tasks/user', {type: 'todo', text: 'todo 1'});
+ await user.post(`/tasks/${task._id}/score/up`);
+
+ await expect(user.post(`/tasks/${task._id}/move/to/1`)).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('cantMoveCompletedTodo'),
+ });
+ });
+
+ it('can push to bottom', async () => {
+ let tasks = await user.post('/tasks/user', [
+ {type: 'habit', text: 'habit 1'},
+ {type: 'habit', text: 'habit 2'},
+ {type: 'daily', text: 'daily 1'},
+ {type: 'habit', text: 'habit 3'},
+ {type: 'habit', text: 'habit 4'},
+ {type: 'todo', text: 'todo 1'},
+ {type: 'habit', text: 'habit 5'},
+ ]);
+
+ let taskToMove = tasks[1];
+ expect(taskToMove.text).to.equal('habit 2');
+ let newOrder = await user.post(`/tasks/${tasks[1]._id}/move/to/-1`);
+ expect(newOrder[4]).to.equal(taskToMove._id);
+ expect(newOrder.length).to.equal(5);
+ });
+});
diff --git a/test/api/v3/integration/tasks/POST-tasks_user.test.js b/test/api/v3/integration/tasks/POST-tasks_user.test.js
new file mode 100644
index 0000000000..623d9bb4a4
--- /dev/null
+++ b/test/api/v3/integration/tasks/POST-tasks_user.test.js
@@ -0,0 +1,593 @@
+import {
+ generateUser,
+ translate as t,
+} from '../../../../helpers/api-v3-integration.helper';
+import { v4 as generateUUID } from 'uuid';
+
+describe('POST /tasks/user', () => {
+ let user;
+
+ before(async () => {
+ user = await generateUser();
+ });
+
+ context('validates params', async () => {
+ it('returns an error if req.body.type is absent', async () => {
+ await expect(user.post('/tasks/user', {
+ notType: 'habit',
+ })).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('invalidTaskType'),
+ });
+ });
+
+ it('returns an error if req.body.type is not valid', async () => {
+ await expect(user.post('/tasks/user', {
+ type: 'habitF',
+ })).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('invalidTaskType'),
+ });
+ });
+
+ it('returns an error if one object inside an array is invalid', async () => {
+ await expect(user.post('/tasks/user', [
+ {type: 'habitF'},
+ {type: 'habit'},
+ ])).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('invalidTaskType'),
+ });
+ });
+
+ it('returns an error if req.body.text is absent', async () => {
+ await expect(user.post('/tasks/user', {
+ type: 'habit',
+ })).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: 'habit validation failed',
+ });
+ });
+
+ it('does not update user.tasksOrder.{taskType} when the task is not saved because invalid', async () => {
+ let originalHabitsOrder = (await user.get('/user')).tasksOrder.habits;
+ await expect(user.post('/tasks/user', {
+ type: 'habit',
+ })).to.eventually.be.rejected.and.eql({ // this block is necessary
+ code: 400,
+ error: 'BadRequest',
+ message: 'habit validation failed',
+ });
+
+ let updatedHabitsOrder = (await user.get('/user')).tasksOrder.habits;
+ expect(updatedHabitsOrder).to.eql(originalHabitsOrder);
+ });
+
+ it('does not update user.tasksOrder.{taskType} when a task inside an array is not saved because invalid', async () => {
+ let originalHabitsOrder = (await user.get('/user')).tasksOrder.habits;
+ await expect(user.post('/tasks/user', [
+ {type: 'habit'}, // Missing text
+ {type: 'habit', text: 'valid'}, // Valid
+ ])).to.eventually.be.rejected.and.eql({ // this block is necessary
+ code: 400,
+ error: 'BadRequest',
+ message: 'habit validation failed',
+ });
+
+ let updatedHabitsOrder = (await user.get('/user')).tasksOrder.habits;
+ expect(updatedHabitsOrder).to.eql(originalHabitsOrder);
+ });
+
+ it('does not save any task sent in an array when 1 is invalid', async () => {
+ let originalTasks = await user.get('/tasks/user');
+ await expect(user.post('/tasks/user', [
+ {type: 'habit'}, // Missing text
+ {type: 'habit', text: 'valid'}, // Valid
+ ])).to.eventually.be.rejected.and.eql({ // this block is necessary
+ code: 400,
+ error: 'BadRequest',
+ message: 'habit validation failed',
+ }).then(async () => {
+ let updatedTasks = await user.get('/tasks/user');
+
+ expect(updatedTasks).to.eql(originalTasks);
+ });
+ });
+
+ it('automatically sets "task.userId" to user\'s uuid', async () => {
+ let task = await user.post('/tasks/user', {
+ text: 'test habit',
+ type: 'habit',
+ });
+
+ expect(task.userId).to.equal(user._id);
+ });
+
+ it(`ignores setting userId, history, createdAt,
+ updatedAt, challenge, completed,
+ dateCompleted fields`, async () => {
+ let task = await user.post('/tasks/user', {
+ text: 'test daily',
+ type: 'daily',
+ userId: 123,
+ history: [123],
+ createdAt: 'yesterday',
+ updatedAt: 'tomorrow',
+ challenge: 'no',
+ completed: true,
+ dateCompleted: 'never',
+ value: 324, // ignored because not a reward
+ });
+
+ expect(task.userId).to.equal(user._id);
+ expect(task.history).to.eql([]);
+ expect(task.createdAt).not.to.equal('yesterday');
+ expect(task.updatedAt).not.to.equal('tomorrow');
+ expect(task.challenge).not.to.equal('no');
+ expect(task.completed).to.equal(false);
+ expect(task.streak).not.to.equal('never');
+ expect(task.value).not.to.equal(324);
+ });
+
+ it('ignores invalid fields', async () => {
+ let task = await user.post('/tasks/user', {
+ text: 'test daily',
+ type: 'daily',
+ notValid: true,
+ });
+
+ expect(task).not.to.have.property('notValid');
+ });
+ });
+
+ context('all types', () => {
+ it('can create reminders', async () => {
+ let id1 = generateUUID();
+
+ let task = await user.post('/tasks/user', {
+ text: 'test habit',
+ type: 'habit',
+ reminders: [
+ {id: id1, startDate: new Date(), time: new Date()},
+ ],
+ });
+
+ expect(task.reminders).to.be.an('array');
+ expect(task.reminders.length).to.eql(1);
+ expect(task.reminders[0]).to.be.an('object');
+ expect(task.reminders[0].id).to.eql(id1);
+ expect(task.reminders[0].startDate).to.be.a('string'); // json doesn't have dates
+ expect(task.reminders[0].time).to.be.a('string');
+ });
+ });
+
+ context('habits', () => {
+ it('creates a habit', async () => {
+ let task = await user.post('/tasks/user', {
+ text: 'test habit',
+ type: 'habit',
+ up: false,
+ down: true,
+ notes: 1976,
+ });
+
+ expect(task.userId).to.equal(user._id);
+ expect(task.text).to.eql('test habit');
+ expect(task.notes).to.eql('1976');
+ expect(task.type).to.eql('habit');
+ expect(task.up).to.eql(false);
+ expect(task.down).to.eql(true);
+ });
+
+ it('updates user.tasksOrder.habits when a new habit is created', async () => {
+ let originalHabitsOrderLen = (await user.get('/user')).tasksOrder.habits.length;
+ let task = await user.post('/tasks/user', {
+ type: 'habit',
+ text: 'an habit',
+ });
+
+ let updatedUser = await user.get('/user');
+ expect(updatedUser.tasksOrder.habits[0]).to.eql(task._id);
+ expect(updatedUser.tasksOrder.habits.length).to.eql(originalHabitsOrderLen + 1);
+ });
+
+ it('updates user.tasksOrder.habits when multiple habits are created', async () => {
+ let originalHabitsOrderLen = (await user.get('/user')).tasksOrder.habits.length;
+ let [task, task2] = await user.post('/tasks/user', [{
+ type: 'habit',
+ text: 'an habit',
+ }, {
+ type: 'habit',
+ text: 'another habit',
+ }]);
+
+ let updatedUser = await user.get('/user');
+ expect(updatedUser.tasksOrder.habits[0]).to.eql(task2._id);
+ expect(updatedUser.tasksOrder.habits[1]).to.eql(task._id);
+ expect(updatedUser.tasksOrder.habits.length).to.eql(originalHabitsOrderLen + 2);
+ });
+
+ it('creates multiple habits', async () => {
+ let [task, task2] = await user.post('/tasks/user', [{
+ text: 'test habit',
+ type: 'habit',
+ up: false,
+ down: true,
+ notes: 1976,
+ }, {
+ text: 'test habit 2',
+ type: 'habit',
+ up: true,
+ down: false,
+ notes: 1977,
+ }]);
+
+ expect(task.userId).to.equal(user._id);
+ expect(task.text).to.eql('test habit');
+ expect(task.notes).to.eql('1976');
+ expect(task.type).to.eql('habit');
+ expect(task.up).to.eql(false);
+ expect(task.down).to.eql(true);
+
+ expect(task2.userId).to.equal(user._id);
+ expect(task2.text).to.eql('test habit 2');
+ expect(task2.notes).to.eql('1977');
+ expect(task2.type).to.eql('habit');
+ expect(task2.up).to.eql(true);
+ expect(task2.down).to.eql(false);
+ });
+
+ it('defaults to setting up and down to true', async () => {
+ let task = await user.post('/tasks/user', {
+ text: 'test habit',
+ type: 'habit',
+ notes: 1976,
+ });
+
+ expect(task.up).to.eql(true);
+ expect(task.down).to.eql(true);
+ });
+
+ it('cannot create checklists', async () => {
+ let task = await user.post('/tasks/user', {
+ text: 'test habit',
+ type: 'habit',
+ checklist: [
+ {_id: 123, completed: false, text: 'checklist'},
+ ],
+ });
+
+ expect(task).not.to.have.property('checklist');
+ });
+ });
+
+ context('todos', () => {
+ it('creates a todo', async () => {
+ let task = await user.post('/tasks/user', {
+ text: 'test todo',
+ type: 'todo',
+ notes: 1976,
+ });
+
+ expect(task.userId).to.equal(user._id);
+ expect(task.text).to.eql('test todo');
+ expect(task.notes).to.eql('1976');
+ expect(task.type).to.eql('todo');
+ });
+
+ it('creates multiple todos', async () => {
+ let [task, task2] = await user.post('/tasks/user', [{
+ text: 'test todo',
+ type: 'todo',
+ notes: 1976,
+ }, {
+ text: 'test todo 2',
+ type: 'todo',
+ notes: 1977,
+ }]);
+
+ expect(task.userId).to.equal(user._id);
+ expect(task.text).to.eql('test todo');
+ expect(task.notes).to.eql('1976');
+ expect(task.type).to.eql('todo');
+
+ expect(task2.userId).to.equal(user._id);
+ expect(task2.text).to.eql('test todo 2');
+ expect(task2.notes).to.eql('1977');
+ expect(task2.type).to.eql('todo');
+ });
+
+ it('updates user.tasksOrder.todos when a new todo is created', async () => {
+ let originalTodosOrderLen = (await user.get('/user')).tasksOrder.todos.length;
+ let task = await user.post('/tasks/user', {
+ type: 'todo',
+ text: 'a todo',
+ });
+
+ let updatedUser = await user.get('/user');
+ expect(updatedUser.tasksOrder.todos[0]).to.eql(task._id);
+ expect(updatedUser.tasksOrder.todos.length).to.eql(originalTodosOrderLen + 1);
+ });
+
+ it('updates user.tasksOrder.todos when multiple todos are created', async () => {
+ let originalTodosOrderLen = (await user.get('/user')).tasksOrder.todos.length;
+ let [task, task2] = await user.post('/tasks/user', [{
+ type: 'todo',
+ text: 'a todo',
+ }, {
+ type: 'todo',
+ text: 'another todo',
+ }]);
+
+ let updatedUser = await user.get('/user');
+ expect(updatedUser.tasksOrder.todos[0]).to.eql(task2._id);
+ expect(updatedUser.tasksOrder.todos[1]).to.eql(task._id);
+ expect(updatedUser.tasksOrder.todos.length).to.eql(originalTodosOrderLen + 2);
+ });
+
+ it('can create checklists', async () => {
+ let task = await user.post('/tasks/user', {
+ text: 'test todo',
+ type: 'todo',
+ checklist: [
+ {completed: false, text: 'checklist'},
+ ],
+ });
+
+ expect(task.checklist).to.be.an('array');
+ expect(task.checklist.length).to.eql(1);
+ expect(task.checklist[0]).to.be.an('object');
+ expect(task.checklist[0].text).to.eql('checklist');
+ expect(task.checklist[0].completed).to.eql(false);
+ expect(task.checklist[0].id).to.be.a('string');
+ });
+ });
+
+ context('dailys', () => {
+ it('creates a daily', async () => {
+ let now = new Date();
+
+ let task = await user.post('/tasks/user', {
+ text: 'test daily',
+ type: 'daily',
+ notes: 1976,
+ frequency: 'daily',
+ everyX: 5,
+ startDate: now,
+ });
+
+ expect(task.userId).to.equal(user._id);
+ expect(task.text).to.eql('test daily');
+ expect(task.notes).to.eql('1976');
+ expect(task.type).to.eql('daily');
+ expect(task.frequency).to.eql('daily');
+ expect(task.everyX).to.eql(5);
+ expect(new Date(task.startDate)).to.eql(now);
+ });
+
+ it('creates multiple dailys', async () => {
+ let [task, task2] = await user.post('/tasks/user', [{
+ text: 'test daily',
+ type: 'daily',
+ notes: 1976,
+ }, {
+ text: 'test daily 2',
+ type: 'daily',
+ notes: 1977,
+ }]);
+
+ expect(task.userId).to.equal(user._id);
+ expect(task.text).to.eql('test daily');
+ expect(task.notes).to.eql('1976');
+ expect(task.type).to.eql('daily');
+
+ expect(task2.userId).to.equal(user._id);
+ expect(task2.text).to.eql('test daily 2');
+ expect(task2.notes).to.eql('1977');
+ expect(task2.type).to.eql('daily');
+ });
+
+ it('updates user.tasksOrder.dailys when a new daily is created', async () => {
+ let originalDailysOrderLen = (await user.get('/user')).tasksOrder.dailys.length;
+ let task = await user.post('/tasks/user', {
+ type: 'daily',
+ text: 'a daily',
+ });
+
+ let updatedUser = await user.get('/user');
+ expect(updatedUser.tasksOrder.dailys[0]).to.eql(task._id);
+ expect(updatedUser.tasksOrder.dailys.length).to.eql(originalDailysOrderLen + 1);
+ });
+
+ it('updates user.tasksOrder.dailys when multiple dailys are created', async () => {
+ let originalDailysOrderLen = (await user.get('/user')).tasksOrder.dailys.length;
+ let [task, task2] = await user.post('/tasks/user', [{
+ type: 'daily',
+ text: 'a daily',
+ }, {
+ type: 'daily',
+ text: 'another daily',
+ }]);
+
+ let updatedUser = await user.get('/user');
+ expect(updatedUser.tasksOrder.dailys[0]).to.eql(task2._id);
+ expect(updatedUser.tasksOrder.dailys[1]).to.eql(task._id);
+ expect(updatedUser.tasksOrder.dailys.length).to.eql(originalDailysOrderLen + 2);
+ });
+
+ it('defaults to a weekly frequency, with every day set', async () => {
+ let task = await user.post('/tasks/user', {
+ text: 'test daily',
+ type: 'daily',
+ });
+
+ expect(task.frequency).to.eql('weekly');
+ expect(task.everyX).to.eql(1);
+ expect(task.repeat).to.eql({
+ m: true,
+ t: true,
+ w: true,
+ th: true,
+ f: true,
+ s: true,
+ su: true,
+ });
+ });
+
+ it('allows repeat field to be configured', async () => {
+ let task = await user.post('/tasks/user', {
+ text: 'test daily',
+ type: 'daily',
+ repeat: {
+ m: false,
+ w: false,
+ su: false,
+ },
+ });
+
+ expect(task.repeat).to.eql({
+ m: false,
+ t: true,
+ w: false,
+ th: true,
+ f: true,
+ s: true,
+ su: false,
+ });
+ });
+
+ it('defaults startDate to today', async () => {
+ let today = (new Date()).getDay();
+
+ let task = await user.post('/tasks/user', {
+ text: 'test daily',
+ type: 'daily',
+ });
+
+ expect((new Date(task.startDate)).getDay()).to.eql(today);
+ });
+
+ it('can create checklists', async () => {
+ let task = await user.post('/tasks/user', {
+ text: 'test daily',
+ type: 'daily',
+ checklist: [
+ {completed: false, text: 'checklist'},
+ ],
+ });
+
+ expect(task.checklist).to.be.an('array');
+ expect(task.checklist.length).to.eql(1);
+ expect(task.checklist[0]).to.be.an('object');
+ expect(task.checklist[0].text).to.eql('checklist');
+ expect(task.checklist[0].completed).to.eql(false);
+ expect(task.checklist[0].id).to.be.a('string');
+ });
+ });
+
+ context('rewards', () => {
+ it('creates a reward', async () => {
+ let task = await user.post('/tasks/user', {
+ text: 'test reward',
+ type: 'reward',
+ notes: 1976,
+ value: 10,
+ });
+
+ expect(task.userId).to.equal(user._id);
+ expect(task.text).to.eql('test reward');
+ expect(task.notes).to.eql('1976');
+ expect(task.type).to.eql('reward');
+ expect(task.value).to.eql(10);
+ });
+
+ it('creates multiple rewards', async () => {
+ let [task, task2] = await user.post('/tasks/user', [{
+ text: 'test reward',
+ type: 'reward',
+ notes: 1976,
+ value: 11,
+ }, {
+ text: 'test reward 2',
+ type: 'reward',
+ notes: 1977,
+ value: 12,
+ }]);
+
+ expect(task.userId).to.equal(user._id);
+ expect(task.text).to.eql('test reward');
+ expect(task.notes).to.eql('1976');
+ expect(task.type).to.eql('reward');
+ expect(task.value).to.eql(11);
+
+ expect(task2.userId).to.equal(user._id);
+ expect(task2.text).to.eql('test reward 2');
+ expect(task2.notes).to.eql('1977');
+ expect(task2.type).to.eql('reward');
+ expect(task2.value).to.eql(12);
+ });
+
+ it('updates user.tasksOrder.rewards when a new reward is created', async () => {
+ let originalRewardsOrderLen = (await user.get('/user')).tasksOrder.rewards.length;
+ let task = await user.post('/tasks/user', {
+ type: 'reward',
+ text: 'a reward',
+ });
+
+ let updatedUser = await user.get('/user');
+ expect(updatedUser.tasksOrder.rewards[0]).to.eql(task._id);
+ expect(updatedUser.tasksOrder.rewards.length).to.eql(originalRewardsOrderLen + 1);
+ });
+
+ it('updates user.tasksOrder.dreward when multiple rewards are created', async () => {
+ let originalRewardsOrderLen = (await user.get('/user')).tasksOrder.rewards.length;
+ let [task, task2] = await user.post('/tasks/user', [{
+ type: 'reward',
+ text: 'a reward',
+ }, {
+ type: 'reward',
+ text: 'another reward',
+ }]);
+
+ let updatedUser = await user.get('/user');
+ expect(updatedUser.tasksOrder.rewards[0]).to.eql(task2._id);
+ expect(updatedUser.tasksOrder.rewards[1]).to.eql(task._id);
+ expect(updatedUser.tasksOrder.rewards.length).to.eql(originalRewardsOrderLen + 2);
+ });
+
+ it('defaults to a 0 value', async () => {
+ let task = await user.post('/tasks/user', {
+ text: 'test reward',
+ type: 'reward',
+ });
+
+ expect(task.value).to.eql(0);
+ });
+
+ it('requires value to be coerced into a number', async () => {
+ let task = await user.post('/tasks/user', {
+ text: 'test reward',
+ type: 'reward',
+ value: '10',
+ });
+
+ expect(task.value).to.eql(10);
+ });
+
+ it('cannot create checklists', async () => {
+ let task = await user.post('/tasks/user', {
+ text: 'test reward',
+ type: 'reward',
+ checklist: [
+ {_id: 123, completed: false, text: 'checklist'},
+ ],
+ });
+
+ expect(task).not.to.have.property('checklist');
+ });
+ });
+});
diff --git a/test/api/v3/integration/tasks/PUT-tasks_challenge_challengeId.test.js b/test/api/v3/integration/tasks/PUT-tasks_challenge_challengeId.test.js
new file mode 100644
index 0000000000..88343cb47c
--- /dev/null
+++ b/test/api/v3/integration/tasks/PUT-tasks_challenge_challengeId.test.js
@@ -0,0 +1,323 @@
+import {
+ generateUser,
+ generateGroup,
+ generateChallenge,
+ translate as t,
+} from '../../../../helpers/api-integration/v3';
+import { v4 as generateUUID } from 'uuid';
+
+describe('PUT /tasks/:id', () => {
+ let user;
+ let guild;
+ let challenge;
+
+ before(async () => {
+ user = await generateUser();
+ guild = await generateGroup(user);
+ challenge = await generateChallenge(user, guild);
+ });
+
+ context('errors', () => {
+ let task;
+
+ beforeEach(async () => {
+ task = await user.post(`/tasks/challenge/${challenge._id}`, {
+ text: 'test habit',
+ type: 'habit',
+ });
+ });
+
+ it('returns error when incorrect id is passed', async () => {
+ await expect(user.put(`/tasks/${generateUUID()}`, {
+ text: 'some new text',
+ up: false,
+ down: false,
+ notes: 'some new notes',
+ })).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('taskNotFound'),
+ });
+ });
+
+ it('returns error when user is not a member of the challenge', async () => {
+ let anotherUser = await generateUser();
+
+ await expect(anotherUser.put(`/tasks/${task._id}`, {
+ text: 'some new text',
+ up: false,
+ down: false,
+ notes: 'some new notes',
+ })).to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('onlyChalLeaderEditTasks'),
+ });
+ });
+ });
+
+ context('validates params', () => {
+ let task;
+
+ beforeEach(async () => {
+ task = await user.post(`/tasks/challenge/${challenge._id}`, {
+ text: 'test habit',
+ type: 'habit',
+ });
+ });
+
+ it(`ignores setting _id, type, userId, history, createdAt,
+ updatedAt, challenge, completed, streak,
+ dateCompleted fields`, async () => {
+ let savedTask = await user.put(`/tasks/${task._id}`, {
+ _id: 123,
+ type: 'daily',
+ userId: 123,
+ history: [123],
+ createdAt: 'yesterday',
+ updatedAt: 'tomorrow',
+ challenge: 'no',
+ completed: true,
+ streak: 25,
+ dateCompleted: 'never',
+ value: 324, // ignored because not a reward
+ });
+
+ expect(savedTask._id).to.equal(task._id);
+ expect(savedTask.type).to.equal(task.type);
+ expect(savedTask.userId).to.equal(task.userId);
+ expect(savedTask.history).to.eql(task.history);
+ expect(savedTask.createdAt).to.equal(task.createdAt);
+ expect(savedTask.updatedAt).to.be.greaterThan(task.updatedAt);
+ expect(savedTask.challenge._id).to.equal(task.challenge._id);
+ expect(savedTask.completed).to.equal(task.completed);
+ expect(savedTask.streak).to.equal(task.streak);
+ expect(savedTask.dateCompleted).to.equal(task.dateCompleted);
+ expect(savedTask.value).to.equal(task.value);
+ });
+
+ it('ignores invalid fields', async () => {
+ let savedTask = await user.put(`/tasks/${task._id}`, {
+ notValid: true,
+ });
+
+ expect(savedTask.notValid).to.be.undefined;
+ });
+ });
+
+ context('habits', () => {
+ let habit;
+
+ beforeEach(async () => {
+ habit = await user.post(`/tasks/challenge/${challenge._id}`, {
+ text: 'test habit',
+ type: 'habit',
+ notes: 1976,
+ });
+ });
+
+ it('updates a habit', async () => {
+ let savedHabit = await user.put(`/tasks/${habit._id}`, {
+ text: 'some new text',
+ up: false,
+ down: false,
+ notes: 'some new notes',
+ });
+
+ expect(savedHabit.text).to.eql('some new text');
+ expect(savedHabit.notes).to.eql('some new notes');
+ expect(savedHabit.up).to.eql(false);
+ expect(savedHabit.down).to.eql(false);
+ });
+ });
+
+ context('todos', () => {
+ let todo;
+
+ beforeEach(async () => {
+ todo = await user.post(`/tasks/challenge/${challenge._id}`, {
+ text: 'test todo',
+ type: 'todo',
+ notes: 1976,
+ });
+ });
+
+ it('updates a todo', async () => {
+ let savedTodo = await user.put(`/tasks/${todo._id}`, {
+ text: 'some new text',
+ notes: 'some new notes',
+ });
+
+ expect(savedTodo.text).to.eql('some new text');
+ expect(savedTodo.notes).to.eql('some new notes');
+ });
+
+ it('can update checklists (replace it)', async () => {
+ await user.put(`/tasks/${todo._id}`, {
+ checklist: [
+ {text: 123, completed: false},
+ {text: 456, completed: true},
+ ],
+ });
+
+ let savedTodo = await user.put(`/tasks/${todo._id}`, {
+ checklist: [
+ {text: 789, completed: false},
+ ],
+ });
+
+ expect(savedTodo.checklist.length).to.equal(1);
+ expect(savedTodo.checklist[0].text).to.equal('789');
+ expect(savedTodo.checklist[0].completed).to.equal(false);
+ });
+
+ it('can update tags (replace them)', async () => {
+ let finalUUID = generateUUID();
+ await user.put(`/tasks/${todo._id}`, {
+ tags: [generateUUID(), generateUUID()],
+ });
+
+ let savedTodo = await user.put(`/tasks/${todo._id}`, {
+ tags: [finalUUID],
+ });
+
+ expect(savedTodo.tags.length).to.equal(1);
+ expect(savedTodo.tags[0]).to.equal(finalUUID);
+ });
+ });
+
+ context('dailys', () => {
+ let daily;
+
+ beforeEach(async () => {
+ daily = await user.post(`/tasks/challenge/${challenge._id}`, {
+ text: 'test daily',
+ type: 'daily',
+ notes: 1976,
+ });
+ });
+
+ it('updates a daily', async () => {
+ let savedDaily = await user.put(`/tasks/${daily._id}`, {
+ text: 'some new text',
+ notes: 'some new notes',
+ frequency: 'daily',
+ everyX: 5,
+ });
+
+ expect(savedDaily.text).to.eql('some new text');
+ expect(savedDaily.notes).to.eql('some new notes');
+ expect(savedDaily.frequency).to.eql('daily');
+ expect(savedDaily.everyX).to.eql(5);
+ });
+
+ it('can update checklists (replace it)', async () => {
+ await user.put(`/tasks/${daily._id}`, {
+ checklist: [
+ {text: 123, completed: false},
+ {text: 456, completed: true},
+ ],
+ });
+
+ let savedDaily = await user.put(`/tasks/${daily._id}`, {
+ checklist: [
+ {text: 789, completed: false},
+ ],
+ });
+
+ expect(savedDaily.checklist.length).to.equal(1);
+ expect(savedDaily.checklist[0].text).to.equal('789');
+ expect(savedDaily.checklist[0].completed).to.equal(false);
+ });
+
+ it('can update tags (replace them)', async () => {
+ let finalUUID = generateUUID();
+ await user.put(`/tasks/${daily._id}`, {
+ tags: [generateUUID(), generateUUID()],
+ });
+
+ let savedDaily = await user.put(`/tasks/${daily._id}`, {
+ tags: [finalUUID],
+ });
+
+ expect(savedDaily.tags.length).to.equal(1);
+ expect(savedDaily.tags[0]).to.equal(finalUUID);
+ });
+
+ it('updates repeat, even if frequency is set to daily', async () => {
+ await user.put(`/tasks/${daily._id}`, {
+ frequency: 'daily',
+ });
+
+ let savedDaily = await user.put(`/tasks/${daily._id}`, {
+ repeat: {
+ m: false,
+ su: false,
+ },
+ });
+
+ expect(savedDaily.repeat).to.eql({
+ m: false,
+ t: true,
+ w: true,
+ th: true,
+ f: true,
+ s: true,
+ su: false,
+ });
+ });
+
+ it('updates everyX, even if frequency is set to weekly', async () => {
+ await user.put(`/tasks/${daily._id}`, {
+ frequency: 'weekly',
+ });
+
+ let savedDaily = await user.put(`/tasks/${daily._id}`, {
+ everyX: 5,
+ });
+
+ expect(savedDaily.everyX).to.eql(5);
+ });
+
+ it('defaults startDate to today if none date object is passed in', async () => {
+ let savedDaily = await user.put(`/tasks/${daily._id}`, {
+ frequency: 'weekly',
+ });
+
+ expect((new Date(savedDaily.startDate)).getDay()).to.eql((new Date()).getDay());
+ });
+ });
+
+ context('rewards', () => {
+ let reward;
+
+ beforeEach(async () => {
+ reward = await user.post(`/tasks/challenge/${challenge._id}`, {
+ text: 'test reward',
+ type: 'reward',
+ notes: 1976,
+ value: 10,
+ });
+ });
+
+ it('updates a reward', async () => {
+ let savedReward = await user.put(`/tasks/${reward._id}`, {
+ text: 'some new text',
+ notes: 'some new notes',
+ value: 11,
+ });
+
+ expect(savedReward.text).to.eql('some new text');
+ expect(savedReward.notes).to.eql('some new notes');
+ expect(savedReward.value).to.eql(11);
+ });
+
+ it('requires value to be coerced into a number', async () => {
+ let savedReward = await user.put(`/tasks/${reward._id}`, {
+ value: '100',
+ });
+
+ expect(savedReward.value).to.eql(100);
+ });
+ });
+});
diff --git a/test/api/v3/integration/tasks/PUT-tasks_id.test.js b/test/api/v3/integration/tasks/PUT-tasks_id.test.js
new file mode 100644
index 0000000000..566c1ebe5e
--- /dev/null
+++ b/test/api/v3/integration/tasks/PUT-tasks_id.test.js
@@ -0,0 +1,396 @@
+import {
+ generateUser,
+ generateGroup,
+ sleep,
+ generateChallenge,
+} from '../../../../helpers/api-integration/v3';
+import { v4 as generateUUID } from 'uuid';
+
+describe('PUT /tasks/:id', () => {
+ let user;
+
+ before(async () => {
+ user = await generateUser();
+ });
+
+ context('validates params', () => {
+ let task;
+
+ beforeEach(async () => {
+ task = await user.post('/tasks/user', {
+ text: 'test habit',
+ type: 'habit',
+ });
+ });
+
+ it(`ignores setting _id, type, userId, history, createdAt,
+ updatedAt, challenge, completed, streak,
+ dateCompleted fields`, async () => {
+ let savedTask = await user.put(`/tasks/${task._id}`, {
+ _id: 123,
+ type: 'daily',
+ userId: 123,
+ history: [123],
+ createdAt: 'yesterday',
+ updatedAt: 'tomorrow',
+ challenge: 'no',
+ completed: true,
+ streak: 25,
+ dateCompleted: 'never',
+ });
+
+ expect(savedTask._id).to.equal(task._id);
+ expect(savedTask.type).to.equal(task.type);
+ expect(savedTask.userId).to.equal(task.userId);
+ expect(savedTask.history).to.eql(task.history);
+ expect(savedTask.createdAt).to.equal(task.createdAt);
+ expect(savedTask.updatedAt).to.be.greaterThan(task.updatedAt);
+ expect(savedTask.challenge).to.eql(task.challenge);
+ expect(savedTask.completed).to.eql(task.completed);
+ expect(savedTask.streak).to.equal(savedTask.streak); // it's an habit, dailies can change it
+ expect(savedTask.dateCompleted).to.equal(task.dateCompleted);
+ });
+
+ it('ignores invalid fields', async () => {
+ let savedTask = await user.put(`/tasks/${task._id}`, {
+ notValid: true,
+ });
+
+ expect(savedTask.notValid).to.be.undefined;
+ });
+
+ it(`only allows setting streak, reminders, checklist, notes, attribute, tags
+ fields for challenge tasks owned by a user`, async () => {
+ let guild = await generateGroup(user);
+ let challenge = await generateChallenge(user, guild);
+
+ let challengeTask = await user.post(`/tasks/challenge/${challenge._id}`, {
+ type: 'daily',
+ text: 'Daily in challenge',
+ reminders: [
+ {time: new Date(), startDate: new Date()},
+ ],
+ checklist: [
+ {text: 123, completed: false},
+ ],
+ });
+ await sleep(2);
+
+ await user.sync();
+
+ // Pick challenge task
+ let challengeUserTaskId = user.tasksOrder.dailys[user.tasksOrder.dailys.length - 1];
+
+ let challengeUserTask = await user.get(`/tasks/${challengeUserTaskId}`);
+
+ let savedChallengeUserTask = await user.put(`/tasks/${challengeUserTaskId}`, {
+ _id: 123,
+ type: 'daily',
+ userId: 123,
+ history: [123],
+ createdAt: 'yesterday',
+ updatedAt: 'tomorrow',
+ challenge: 'no',
+ completed: true,
+ streak: 25,
+ priority: 1.5,
+ repeat: {
+ m: false,
+ },
+ everyX: 15,
+ frequency: 'weekly',
+ text: 'new text',
+ dateCompleted: 'never',
+ reminders: [
+ {time: new Date(), startDate: new Date()},
+ {time: new Date(), startDate: new Date()},
+ ],
+ checklist: [
+ {text: 123, completed: false},
+ {text: 456, completed: true},
+ ],
+ notes: 'new notes',
+ attribute: 'per',
+ tags: [challengeUserTaskId],
+ });
+
+ // original task is not touched
+ let updatedChallengeTask = await user.get(`/tasks/${challengeTask._id}`);
+ expect(updatedChallengeTask).to.eql(challengeTask);
+
+ // ignored
+ expect(savedChallengeUserTask._id).to.equal(challengeUserTask._id);
+ expect(savedChallengeUserTask.type).to.equal(challengeUserTask.type);
+ expect(savedChallengeUserTask.repeat.m).to.equal(true);
+ expect(savedChallengeUserTask.priority).to.equal(challengeUserTask.priority);
+ expect(savedChallengeUserTask.frequency).to.equal(challengeUserTask.frequency);
+ expect(savedChallengeUserTask.userId).to.equal(challengeUserTask.userId);
+ expect(savedChallengeUserTask.text).to.equal(challengeUserTask.text);
+ expect(savedChallengeUserTask.history).to.eql(challengeUserTask.history);
+ expect(savedChallengeUserTask.createdAt).to.equal(challengeUserTask.createdAt);
+ expect(savedChallengeUserTask.updatedAt).to.be.greaterThan(challengeUserTask.updatedAt);
+ expect(savedChallengeUserTask.challenge).to.eql(challengeUserTask.challenge);
+ expect(savedChallengeUserTask.completed).to.equal(challengeUserTask.completed);
+ expect(savedChallengeUserTask.dateCompleted).to.equal(challengeUserTask.dateCompleted);
+ expect(savedChallengeUserTask.priority).to.equal(challengeUserTask.priority);
+
+ // changed
+ expect(savedChallengeUserTask.notes).to.equal('new notes');
+ expect(savedChallengeUserTask.attribute).to.equal('per');
+ expect(savedChallengeUserTask.tags).to.eql([challengeUserTaskId]);
+ expect(savedChallengeUserTask.streak).to.equal(25);
+ expect(savedChallengeUserTask.reminders.length).to.equal(2);
+ expect(savedChallengeUserTask.checklist.length).to.equal(2);
+ });
+ });
+
+ context('all types', () => {
+ let daily;
+
+ beforeEach(async () => {
+ daily = await user.post('/tasks/user', {
+ text: 'test daily',
+ type: 'daily',
+ notes: 1976,
+ });
+ });
+
+ it('can update reminders (replace them)', async () => {
+ await user.put(`/tasks/${daily._id}`, {
+ reminders: [
+ {time: new Date(), startDate: new Date()},
+ ],
+ });
+
+ let id1 = generateUUID();
+ let id2 = generateUUID();
+
+ let savedDaily = await user.put(`/tasks/${daily._id}`, {
+ reminders: [
+ {id: id1, time: new Date(), startDate: new Date()},
+ {id: id2, time: new Date(), startDate: new Date()},
+ ],
+ });
+
+ expect(savedDaily.reminders.length).to.equal(2);
+ expect(savedDaily.reminders[0].id).to.equal(id1);
+ expect(savedDaily.reminders[1].id).to.equal(id2);
+ });
+ });
+
+ context('habits', () => {
+ let habit;
+
+ beforeEach(async () => {
+ habit = await user.post('/tasks/user', {
+ text: 'test habit',
+ type: 'habit',
+ notes: 1976,
+ });
+ });
+
+ it('updates a habit', async () => {
+ let savedHabit = await user.put(`/tasks/${habit._id}`, {
+ text: 'some new text',
+ up: false,
+ down: false,
+ notes: 'some new notes',
+ });
+
+ expect(savedHabit.text).to.eql('some new text');
+ expect(savedHabit.notes).to.eql('some new notes');
+ expect(savedHabit.up).to.eql(false);
+ expect(savedHabit.down).to.eql(false);
+ });
+ });
+
+ context('todos', () => {
+ let todo;
+
+ beforeEach(async () => {
+ todo = await user.post('/tasks/user', {
+ text: 'test todo',
+ type: 'todo',
+ notes: 1976,
+ });
+ });
+
+ it('updates a todo', async () => {
+ let savedTodo = await user.put(`/tasks/${todo._id}`, {
+ text: 'some new text',
+ notes: 'some new notes',
+ });
+
+ expect(savedTodo.text).to.eql('some new text');
+ expect(savedTodo.notes).to.eql('some new notes');
+ });
+
+ it('can update checklists (replace it)', async () => {
+ await user.put(`/tasks/${todo._id}`, {
+ checklist: [
+ {text: 123, completed: false},
+ {text: 456, completed: true},
+ ],
+ });
+
+ let savedTodo = await user.put(`/tasks/${todo._id}`, {
+ checklist: [
+ {text: 789, completed: false},
+ ],
+ });
+
+ expect(savedTodo.checklist.length).to.equal(1);
+ expect(savedTodo.checklist[0].text).to.equal('789');
+ expect(savedTodo.checklist[0].completed).to.equal(false);
+ });
+
+ it('can update tags (replace them)', async () => {
+ let finalUUID = generateUUID();
+ await user.put(`/tasks/${todo._id}`, {
+ tags: [generateUUID(), generateUUID()],
+ });
+
+ let savedTodo = await user.put(`/tasks/${todo._id}`, {
+ tags: [finalUUID],
+ });
+
+ expect(savedTodo.tags.length).to.equal(1);
+ expect(savedTodo.tags[0]).to.equal(finalUUID);
+ });
+ });
+
+ context('dailys', () => {
+ let daily;
+
+ beforeEach(async () => {
+ daily = await user.post('/tasks/user', {
+ text: 'test daily',
+ type: 'daily',
+ notes: 1976,
+ });
+ });
+
+ it('updates a daily', async () => {
+ let savedDaily = await user.put(`/tasks/${daily._id}`, {
+ text: 'some new text',
+ notes: 'some new notes',
+ frequency: 'daily',
+ everyX: 5,
+ });
+
+ expect(savedDaily.text).to.eql('some new text');
+ expect(savedDaily.notes).to.eql('some new notes');
+ expect(savedDaily.frequency).to.eql('daily');
+ expect(savedDaily.everyX).to.eql(5);
+ });
+
+ it('can update checklists (replace it)', async () => {
+ await user.put(`/tasks/${daily._id}`, {
+ checklist: [
+ {text: 123, completed: false},
+ {text: 456, completed: true},
+ ],
+ });
+
+ let savedDaily = await user.put(`/tasks/${daily._id}`, {
+ checklist: [
+ {text: 789, completed: false},
+ ],
+ });
+
+ expect(savedDaily.checklist.length).to.equal(1);
+ expect(savedDaily.checklist[0].text).to.equal('789');
+ expect(savedDaily.checklist[0].completed).to.equal(false);
+ });
+
+ it('can update tags (replace them)', async () => {
+ let finalUUID = generateUUID();
+ await user.put(`/tasks/${daily._id}`, {
+ tags: [generateUUID(), generateUUID()],
+ });
+
+ let savedDaily = await user.put(`/tasks/${daily._id}`, {
+ tags: [finalUUID],
+ });
+
+ expect(savedDaily.tags.length).to.equal(1);
+ expect(savedDaily.tags[0]).to.equal(finalUUID);
+ });
+
+ it('updates repeat, even if frequency is set to daily', async () => {
+ await user.put(`/tasks/${daily._id}`, {
+ frequency: 'daily',
+ });
+
+ let savedDaily = await user.put(`/tasks/${daily._id}`, {
+ repeat: {
+ m: false,
+ su: false,
+ },
+ });
+
+ expect(savedDaily.repeat).to.eql({
+ m: false,
+ t: true,
+ w: true,
+ th: true,
+ f: true,
+ s: true,
+ su: false,
+ });
+ });
+
+ it('updates everyX, even if frequency is set to weekly', async () => {
+ await user.put(`/tasks/${daily._id}`, {
+ frequency: 'weekly',
+ });
+
+ let savedDaily = await user.put(`/tasks/${daily._id}`, {
+ everyX: 5,
+ });
+
+ expect(savedDaily.everyX).to.eql(5);
+ });
+
+ it('defaults startDate to today if none date object is passed in', async () => {
+ let savedDaily = await user.put(`/tasks/${daily._id}`, {
+ frequency: 'weekly',
+ });
+
+ expect((new Date(savedDaily.startDate)).getDay()).to.eql((new Date()).getDay());
+ });
+ });
+
+ context('rewards', () => {
+ let reward;
+
+ beforeEach(async () => {
+ reward = await user.post('/tasks/user', {
+ text: 'test reward',
+ type: 'reward',
+ notes: 1976,
+ value: 10,
+ });
+ });
+
+ it('updates a reward', async () => {
+ let savedReward = await user.put(`/tasks/${reward._id}`, {
+ text: 'some new text',
+ notes: 'some new notes',
+ value: 10,
+ });
+
+ expect(savedReward.text).to.eql('some new text');
+ expect(savedReward.notes).to.eql('some new notes');
+ expect(savedReward.value).to.eql(10);
+ });
+
+ it('requires value to be coerced into a number', async () => {
+ let savedReward = await user.put(`/tasks/${reward._id}`, {
+ value: '100',
+ });
+
+ expect(savedReward.value).to.eql(100);
+ });
+ });
+});
diff --git a/test/api/v3/integration/tasks/challenges/DELETE-tasks_challenge_challengeId_checklist_itemId.test.js b/test/api/v3/integration/tasks/challenges/DELETE-tasks_challenge_challengeId_checklist_itemId.test.js
new file mode 100644
index 0000000000..eac6d455ea
--- /dev/null
+++ b/test/api/v3/integration/tasks/challenges/DELETE-tasks_challenge_challengeId_checklist_itemId.test.js
@@ -0,0 +1,115 @@
+import {
+ generateUser,
+ generateGroup,
+ generateChallenge,
+ translate as t,
+} from '../../../../../helpers/api-integration/v3';
+import { v4 as generateUUID } from 'uuid';
+
+describe('DELETE /tasks/:taskId/checklist/:itemId', () => {
+ let user;
+ let guild;
+ let challenge;
+
+ before(async () => {
+ user = await generateUser();
+ guild = await generateGroup(user);
+ challenge = await generateChallenge(user, guild);
+ });
+
+ it('fails on task not found', async () => {
+ await expect(user.del(`/tasks/${generateUUID()}/checklist/${generateUUID()}`)).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('taskNotFound'),
+ });
+ });
+
+ it('fails on checklist item not found', async () => {
+ let createdTask = await user.post(`/tasks/challenge/${challenge._id}`, {
+ type: 'daily',
+ text: 'daily with checklist',
+ });
+
+ await expect(user.del(`/tasks/${createdTask._id}/checklist/${generateUUID()}`)).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('checklistItemNotFound'),
+ });
+ });
+
+ it('returns error when user is not a member of the challenge', async () => {
+ let task = await user.post(`/tasks/challenge/${challenge._id}`, {
+ type: 'daily',
+ text: 'Daily with checklist',
+ });
+
+ let savedTask = await user.post(`/tasks/${task._id}/checklist`, {
+ text: 'Checklist Item 1',
+ completed: false,
+ });
+
+ let anotherUser = await generateUser();
+
+ await expect(anotherUser.del(`/tasks/${task._id}/checklist/${savedTask.checklist[0].id}`))
+ .to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('onlyChalLeaderEditTasks'),
+ });
+ });
+
+ it('deletes a checklist item from a daily', async () => {
+ let task = await user.post(`/tasks/challenge/${challenge._id}`, {
+ type: 'daily',
+ text: 'Daily with checklist',
+ });
+
+ let savedTask = await user.post(`/tasks/${task._id}/checklist`, {text: 'Checklist Item 1', completed: false});
+
+ await user.del(`/tasks/${task._id}/checklist/${savedTask.checklist[0].id}`);
+ savedTask = await user.get(`/tasks/${task._id}`);
+
+ expect(savedTask.checklist.length).to.equal(0);
+ });
+
+ it('deletes a checklist item from a todo', async () => {
+ let task = await user.post(`/tasks/challenge/${challenge._id}`, {
+ type: 'todo',
+ text: 'Todo with checklist',
+ });
+
+ let savedTask = await user.post(`/tasks/${task._id}/checklist`, {text: 'Checklist Item 1', completed: false});
+
+ await user.del(`/tasks/${task._id}/checklist/${savedTask.checklist[0].id}`);
+ savedTask = await user.get(`/tasks/${task._id}`);
+
+ expect(savedTask.checklist.length).to.equal(0);
+ });
+
+ it('does not work with habits', async () => {
+ let habit = await user.post(`/tasks/challenge/${challenge._id}`, {
+ type: 'habit',
+ text: 'habit with checklist',
+ });
+
+ await expect(user.del(`/tasks/${habit._id}/checklist/${generateUUID()}`)).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('checklistOnlyDailyTodo'),
+ });
+ });
+
+ it('does not work with rewards', async () => {
+ let reward = await user.post(`/tasks/challenge/${challenge._id}`, {
+ type: 'reward',
+ text: 'reward with checklist',
+ });
+
+ await expect(user.del(`/tasks/${reward._id}/checklist/${generateUUID()}`)).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('checklistOnlyDailyTodo'),
+ });
+ });
+});
diff --git a/test/api/v3/integration/tasks/challenges/DELETE-tasks_id_challenge_challengeId.test.js b/test/api/v3/integration/tasks/challenges/DELETE-tasks_id_challenge_challengeId.test.js
new file mode 100644
index 0000000000..34c93c2f05
--- /dev/null
+++ b/test/api/v3/integration/tasks/challenges/DELETE-tasks_id_challenge_challengeId.test.js
@@ -0,0 +1,115 @@
+import {
+ generateUser,
+ generateGroup,
+ generateChallenge,
+ sleep,
+ translate as t,
+} from '../../../../../helpers/api-integration/v3';
+import { v4 as generateUUID } from 'uuid';
+
+describe('DELETE /tasks/:id', () => {
+ let user;
+ let guild;
+ let challenge;
+ let task;
+
+ before(async () => {
+ user = await generateUser();
+ guild = await generateGroup(user);
+ challenge = await generateChallenge(user, guild);
+ });
+
+ beforeEach(async () => {
+ task = await user.post(`/tasks/challenge/${challenge._id}`, {
+ text: 'test habit',
+ type: 'habit',
+ });
+ });
+
+ it('cannot delete a non-existant task', async () => {
+ await expect(user.del(`/tasks/${generateUUID()}`)).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('taskNotFound'),
+ });
+ });
+
+ it('returns error when user is not leader of the challenge', async () => {
+ let anotherUser = await generateUser();
+
+ await expect(anotherUser.del(`/tasks/${task._id}`)).to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('onlyChalLeaderEditTasks'),
+ });
+ });
+
+ it('deletes a user\'s task', async () => {
+ await user.del(`/tasks/${task._id}`);
+
+ await expect(user.get(`/tasks/${task._id}`)).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('taskNotFound'),
+ });
+ });
+
+ context('challenge member', () => {
+ let anotherUser;
+ let anotherUsersNewChallengeTaskID;
+ let newChallengeTask;
+
+ beforeEach(async () => {
+ anotherUser = await generateUser();
+ await user.post(`/groups/${guild._id}/invite`, { uuids: [anotherUser._id] });
+ await anotherUser.post(`/groups/${guild._id}/join`);
+ await anotherUser.post(`/challenges/${challenge._id}/join`);
+
+ newChallengeTask = await user.post(`/tasks/challenge/${challenge._id}`, {
+ text: 'test habit',
+ type: 'habit',
+ });
+
+ let anotherUserWithNewChallengeTask = await anotherUser.get('/user');
+ anotherUsersNewChallengeTaskID = anotherUserWithNewChallengeTask.tasksOrder.habits[0];
+ });
+
+ it('returns error when user attempts to delete an active challenge task', async () => {
+ await expect(anotherUser.del(`/tasks/${anotherUsersNewChallengeTaskID}`))
+ .to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('cantDeleteChallengeTasks'),
+ });
+ });
+
+ it('allows user to delete challenge task after user leaves challenge', async () => {
+ await anotherUser.post(`/challenges/${challenge._id}/leave`);
+
+ await anotherUser.del(`/tasks/${anotherUsersNewChallengeTaskID}`);
+
+ await expect(anotherUser.get(`/tasks/${task._id}`)).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('taskNotFound'),
+ });
+ });
+
+ // TODO for some reason this test fails on TravisCI, review after mongodb indexes have been added
+ xit('allows user to delete challenge task after challenge task is broken', async () => {
+ await expect(user.del(`/tasks/${newChallengeTask._id}`));
+
+ await sleep(2);
+
+ await expect(anotherUser.del(`/tasks/${anotherUsersNewChallengeTaskID}`));
+
+ await sleep(2);
+
+ await expect(anotherUser.get(`/tasks/${anotherUsersNewChallengeTaskID}`)).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('taskNotFound'),
+ });
+ });
+ });
+});
diff --git a/test/api/v3/integration/tasks/challenges/GET_tasks_challenge.id.test.js b/test/api/v3/integration/tasks/challenges/GET_tasks_challenge.id.test.js
new file mode 100644
index 0000000000..2399db04b6
--- /dev/null
+++ b/test/api/v3/integration/tasks/challenges/GET_tasks_challenge.id.test.js
@@ -0,0 +1,86 @@
+import {
+ generateUser,
+ generateGroup,
+ generateChallenge,
+ translate as t,
+} from '../../../../../helpers/api-integration/v3';
+import { v4 as generateUUID } from 'uuid';
+import { each } from 'lodash';
+
+describe('GET /tasks/challenge/:challengeId', () => {
+ let user;
+ let guild;
+ let challenge;
+ let task;
+ let tasks = [];
+ let challengeWithTask;
+ let tasksToTest = {
+ habit: {
+ text: 'test habit',
+ type: 'habit',
+ up: false,
+ down: true,
+ },
+ todo: {
+ text: 'test todo',
+ type: 'todo',
+ },
+ daily: {
+ text: 'test daily',
+ type: 'daily',
+ frequency: 'daily',
+ everyX: 5,
+ startDate: new Date(),
+ },
+ reward: {
+ text: 'test reward',
+ type: 'reward',
+ },
+ };
+
+ before(async () => {
+ user = await generateUser();
+ guild = await generateGroup(user);
+ challenge = await generateChallenge(user, guild);
+ });
+
+ it('returns error when challenge is not found', async () => {
+ let dummyId = generateUUID();
+
+ await expect(user.get(`/tasks/challenge/${dummyId}`)).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('challengeNotFound'),
+ });
+ });
+
+ each(tasksToTest, (taskValue, taskType) => {
+ context(`${taskType}`, () => {
+ before(async () => {
+ task = await user.post(`/tasks/challenge/${challenge._id}`, taskValue);
+ tasks.push(task);
+ challengeWithTask = await user.get(`/challenges/${challenge._id}`);
+ });
+
+ it('gets challenge tasks', async () => {
+ let getTask = await user.get(`/tasks/challenge/${challengeWithTask._id}`);
+ expect(getTask).to.eql(tasks);
+ });
+
+ it('gets challenge tasks filtered by type', async () => {
+ let challengeTasks = await user.get(`/tasks/challenge/${challengeWithTask._id}?type=${task.type}s`);
+ expect(challengeTasks).to.eql([task]);
+ });
+
+ it('cannot get a task owned by someone else', async () => {
+ let anotherUser = await generateUser();
+
+ await expect(anotherUser.get(`/tasks/challenge/${challengeWithTask._id}`)).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('challengeNotFound'),
+ });
+ });
+ });
+ });
+});
diff --git a/test/api/v3/integration/tasks/challenges/POST-tasks_challenge_challengeId_taskId_checklist.test.js b/test/api/v3/integration/tasks/challenges/POST-tasks_challenge_challengeId_taskId_checklist.test.js
new file mode 100644
index 0000000000..06bc7b68b2
--- /dev/null
+++ b/test/api/v3/integration/tasks/challenges/POST-tasks_challenge_challengeId_taskId_checklist.test.js
@@ -0,0 +1,119 @@
+import {
+ generateUser,
+ generateGroup,
+ generateChallenge,
+ translate as t,
+} from '../../../../../helpers/api-integration/v3';
+import { v4 as generateUUID } from 'uuid';
+
+describe('POST /tasks/:taskId/checklist/', () => {
+ let user;
+ let guild;
+ let challenge;
+
+ before(async () => {
+ user = await generateUser();
+ guild = await generateGroup(user);
+ challenge = await generateChallenge(user, guild);
+ });
+
+ it('fails on task not found', async () => {
+ await expect(user.post(`/tasks/${generateUUID()}/checklist`, {
+ text: 'Checklist Item 1',
+ })).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('taskNotFound'),
+ });
+ });
+
+ it('returns error when user is not a member of the challenge', async () => {
+ let task = await user.post(`/tasks/challenge/${challenge._id}`, {
+ type: 'daily',
+ text: 'Daily with checklist',
+ });
+
+ let anotherUser = await generateUser();
+
+ await expect(anotherUser.post(`/tasks/${task._id}/checklist`, {
+ text: 'Checklist Item 1',
+ ignored: false,
+ _id: 123,
+ }))
+ .to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('onlyChalLeaderEditTasks'),
+ });
+ });
+
+ it('adds a checklist item to a daily', async () => {
+ let task = await user.post(`/tasks/challenge/${challenge._id}`, {
+ type: 'daily',
+ text: 'Daily with checklist',
+ });
+
+ let savedTask = await user.post(`/tasks/${task._id}/checklist`, {
+ text: 'Checklist Item 1',
+ ignored: false,
+ _id: 123,
+ });
+
+ expect(savedTask.checklist.length).to.equal(1);
+ expect(savedTask.checklist[0].text).to.equal('Checklist Item 1');
+ expect(savedTask.checklist[0].completed).to.equal(false);
+ expect(savedTask.checklist[0].id).to.be.a('string');
+ expect(savedTask.checklist[0].id).to.not.equal('123');
+ expect(savedTask.checklist[0].ignored).to.be.an('undefined');
+ });
+
+ it('adds a checklist item to a todo', async () => {
+ let task = await user.post(`/tasks/challenge/${challenge._id}`, {
+ type: 'todo',
+ text: 'Todo with checklist',
+ });
+
+ let savedTask = await user.post(`/tasks/${task._id}/checklist`, {
+ text: 'Checklist Item 1',
+ ignored: false,
+ _id: 123,
+ });
+
+ expect(savedTask.checklist.length).to.equal(1);
+ expect(savedTask.checklist[0].text).to.equal('Checklist Item 1');
+ expect(savedTask.checklist[0].completed).to.equal(false);
+ expect(savedTask.checklist[0].id).to.be.a('string');
+ expect(savedTask.checklist[0].id).to.not.equal('123');
+ expect(savedTask.checklist[0].ignored).to.be.an('undefined');
+ });
+
+ it('does not add a checklist to habits', async () => {
+ let habit = await user.post(`/tasks/challenge/${challenge._id}`, {
+ type: 'habit',
+ text: 'habit with checklist',
+ });
+
+ await expect(user.post(`/tasks/${habit._id}/checklist`, {
+ text: 'Checklist Item 1',
+ })).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('checklistOnlyDailyTodo'),
+ });
+ });
+
+ it('does not add a checklist to rewards', async () => {
+ let reward = await user.post(`/tasks/challenge/${challenge._id}`, {
+ type: 'reward',
+ text: 'reward with checklist',
+ });
+
+ await expect(user.post(`/tasks/${reward._id}/checklist`, {
+ text: 'Checklist Item 1',
+ })).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('checklistOnlyDailyTodo'),
+ });
+ });
+});
diff --git a/test/api/v3/integration/tasks/challenges/POST-tasks_challenge_id.test.js b/test/api/v3/integration/tasks/challenges/POST-tasks_challenge_id.test.js
new file mode 100644
index 0000000000..62899ffb59
--- /dev/null
+++ b/test/api/v3/integration/tasks/challenges/POST-tasks_challenge_id.test.js
@@ -0,0 +1,125 @@
+import {
+ generateUser,
+ generateGroup,
+ generateChallenge,
+ translate as t,
+} from '../../../../../helpers/api-v3-integration.helper';
+import { v4 as generateUUID } from 'uuid';
+
+describe('POST /tasks/challenge/:challengeId', () => {
+ let user;
+ let guild;
+ let challenge;
+
+ beforeEach(async () => {
+ user = await generateUser({balance: 1});
+ guild = await generateGroup(user);
+ challenge = await generateChallenge(user, guild);
+ });
+
+ it('returns error when challenge is not found', async () => {
+ let fakeChallengeId = generateUUID();
+
+ await expect(user.post(`/tasks/challenge/${fakeChallengeId}`, {
+ text: 'test habit',
+ type: 'habit',
+ up: false,
+ down: true,
+ notes: 1976,
+ })).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('challengeNotFound'),
+ });
+ });
+
+ it('returns error when user does not have the challenge', async () => {
+ let userWithoutChallenge = await generateUser();
+
+ await expect(userWithoutChallenge.post(`/tasks/challenge/${challenge._id}`, {
+ text: 'test habit',
+ type: 'habit',
+ up: false,
+ down: true,
+ notes: 1976,
+ })).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('challengeNotFound'),
+ });
+ });
+
+ it('returns error when non leader tries to edit challenge', async () => {
+ let userThatIsNotLeaderOfChallenge = await generateUser({
+ challenges: [challenge._id],
+ });
+
+ await expect(userThatIsNotLeaderOfChallenge.post(`/tasks/challenge/${challenge._id}`, {
+ text: 'test habit',
+ type: 'habit',
+ up: false,
+ down: true,
+ notes: 1976,
+ })).to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('onlyChalLeaderEditTasks'),
+ });
+ });
+
+ it('creates a habit', async () => {
+ let task = await user.post(`/tasks/challenge/${challenge._id}`, {
+ text: 'test habit',
+ type: 'habit',
+ up: false,
+ down: true,
+ notes: 1976,
+ });
+ let challengeWithTask = await user.get(`/challenges/${challenge._id}`);
+
+ expect(challengeWithTask.tasksOrder.habits.indexOf(task._id)).to.be.above(-1);
+ expect(task.challenge.id).to.equal(challenge._id);
+ expect(task.text).to.eql('test habit');
+ expect(task.notes).to.eql('1976');
+ expect(task.type).to.eql('habit');
+ expect(task.up).to.eql(false);
+ expect(task.down).to.eql(true);
+ });
+
+ it('creates a todo', async () => {
+ let task = await user.post(`/tasks/challenge/${challenge._id}`, {
+ text: 'test todo',
+ type: 'todo',
+ notes: 1976,
+ });
+ let challengeWithTask = await user.get(`/challenges/${challenge._id}`);
+
+ expect(challengeWithTask.tasksOrder.todos.indexOf(task._id)).to.be.above(-1);
+ expect(task.challenge.id).to.equal(challenge._id);
+ expect(task.text).to.eql('test todo');
+ expect(task.notes).to.eql('1976');
+ expect(task.type).to.eql('todo');
+ });
+
+ it('creates a daily', async () => {
+ let now = new Date();
+ let task = await user.post(`/tasks/challenge/${challenge._id}`, {
+ text: 'test daily',
+ type: 'daily',
+ notes: 1976,
+ frequency: 'daily',
+ everyX: 5,
+ startDate: now,
+ });
+ let challengeWithTask = await user.get(`/challenges/${challenge._id}`);
+
+ expect(challengeWithTask.tasksOrder.dailys.indexOf(task._id)).to.be.above(-1);
+ expect(task.challenge.id).to.equal(challenge._id);
+ expect(task.text).to.eql('test daily');
+ expect(task.notes).to.eql('1976');
+ expect(task.type).to.eql('daily');
+ expect(task.frequency).to.eql('daily');
+ expect(task.everyX).to.eql(5);
+ expect(new Date(task.startDate)).to.eql(now);
+ });
+});
diff --git a/test/api/v3/integration/tasks/challenges/POST-tasks_challenges_challengeId_tasks_id_score_direction.test.js b/test/api/v3/integration/tasks/challenges/POST-tasks_challenges_challengeId_tasks_id_score_direction.test.js
new file mode 100644
index 0000000000..0fa8bafd30
--- /dev/null
+++ b/test/api/v3/integration/tasks/challenges/POST-tasks_challenges_challengeId_tasks_id_score_direction.test.js
@@ -0,0 +1,145 @@
+import {
+ generateUser,
+ generateGroup,
+ generateChallenge,
+} from '../../../../../helpers/api-integration/v3';
+import Bluebird from 'bluebird';
+import { find } from 'lodash';
+
+describe('POST /tasks/:id/score/:direction', () => {
+ let user;
+ let guild;
+ let challenge;
+
+ before(async () => {
+ user = await generateUser();
+ guild = await generateGroup(user);
+ challenge = await generateChallenge(user, guild);
+ });
+
+ context('habits', () => {
+ let habit;
+ let usersChallengeTaskId;
+ let previousTaskHistory;
+
+ before(async () => {
+ habit = await user.post(`/tasks/challenge/${challenge._id}`, {
+ text: 'test habit',
+ type: 'habit',
+ });
+ await Bluebird.delay(1000);
+ let updatedUser = await user.get('/user');
+ usersChallengeTaskId = updatedUser.tasksOrder.habits[0];
+ });
+
+ it('scores and adds history', async () => {
+ await user.post(`/tasks/${usersChallengeTaskId}/score/up`);
+
+ let tasks = await user.get(`/tasks/challenge/${challenge._id}`);
+ let task = find(tasks, {_id: habit._id});
+ previousTaskHistory = task.history[0];
+
+ expect(task.value).to.equal(1);
+ expect(task.history).to.have.lengthOf(1);
+ });
+
+ it('should update the history', async () => {
+ await user.post(`/tasks/${usersChallengeTaskId}/score/up`);
+
+ let tasks = await user.get(`/tasks/challenge/${challenge._id}`);
+ let task = find(tasks, {_id: habit._id});
+
+ expect(task.history).to.have.lengthOf(1);
+ expect(task.history[0].date).to.not.equal(previousTaskHistory.date);
+ expect(task.history[0].value).to.not.equal(previousTaskHistory.value);
+ });
+ });
+
+ context('dailies', () => {
+ let daily;
+ let usersChallengeTaskId;
+ let previousTaskHistory;
+
+ before(async () => {
+ daily = await user.post(`/tasks/challenge/${challenge._id}`, {
+ text: 'test daily',
+ type: 'daily',
+ });
+ await Bluebird.delay(1000);
+ let updatedUser = await user.get('/user');
+ usersChallengeTaskId = updatedUser.tasksOrder.dailys[0];
+ });
+
+ it('it scores and adds history', async () => {
+ await user.post(`/tasks/${usersChallengeTaskId}/score/up`);
+
+ let tasks = await user.get(`/tasks/challenge/${challenge._id}`);
+ let task = find(tasks, {_id: daily._id});
+ previousTaskHistory = task.history[0];
+
+ expect(task.history).to.have.lengthOf(1);
+ expect(task.value).to.equal(1);
+ });
+
+ it('should update the history', async () => {
+ await user.post(`/tasks/${usersChallengeTaskId}/score/up`);
+
+ let tasks = await user.get(`/tasks/challenge/${challenge._id}`);
+ let task = find(tasks, {_id: daily._id});
+
+ expect(task.history).to.have.lengthOf(1);
+ expect(task.history[0].date).to.not.equal(previousTaskHistory.date);
+ expect(task.history[0].value).to.not.equal(previousTaskHistory.value);
+ });
+ });
+
+ context('todos', () => {
+ let todo;
+ let usersChallengeTaskId;
+
+ before(async () => {
+ todo = await user.post(`/tasks/challenge/${challenge._id}`, {
+ text: 'test todo',
+ type: 'todo',
+ });
+ await Bluebird.delay(1000);
+ let updatedUser = await user.get('/user');
+ usersChallengeTaskId = updatedUser.tasksOrder.todos[0];
+ });
+
+ it('scores but does not add history', async () => {
+ await user.post(`/tasks/${usersChallengeTaskId}/score/up`);
+
+ let tasks = await user.get(`/tasks/challenge/${challenge._id}`);
+ let task = find(tasks, {_id: todo._id});
+
+ expect(task.history).to.not.exist;
+ expect(task.value).to.equal(1);
+ });
+ });
+
+ context('rewards', () => {
+ let reward;
+ let usersChallengeTaskId;
+
+ before(async () => {
+ reward = await user.post(`/tasks/challenge/${challenge._id}`, {
+ text: 'test reward',
+ type: 'reward',
+ });
+ await Bluebird.delay(1000);
+ let updatedUser = await user.get('/user');
+ usersChallengeTaskId = updatedUser.tasksOrder.todos[0];
+ });
+
+ it('does not score', async () => {
+ await user.post(`/tasks/${usersChallengeTaskId}/score/up`);
+
+ let tasks = await user.get(`/tasks/challenge/${challenge._id}`);
+ let task = find(tasks, {_id: reward._id});
+
+ expect(task.history).to.not.exist;
+ expect(task.value).to.equal(0);
+ });
+ });
+});
diff --git a/test/api/v3/integration/tasks/challenges/PUT-tasks_challenge_challengeId_tasksId_checklist_itemId.test.js b/test/api/v3/integration/tasks/challenges/PUT-tasks_challenge_challengeId_tasksId_checklist_itemId.test.js
new file mode 100644
index 0000000000..4dc2ceaa3e
--- /dev/null
+++ b/test/api/v3/integration/tasks/challenges/PUT-tasks_challenge_challengeId_tasksId_checklist_itemId.test.js
@@ -0,0 +1,155 @@
+import {
+ generateUser,
+ generateGroup,
+ generateChallenge,
+ translate as t,
+} from '../../../../../helpers/api-integration/v3';
+import { v4 as generateUUID } from 'uuid';
+
+describe('PUT /tasks/:taskId/checklist/:itemId', () => {
+ let user;
+ let guild;
+ let challenge;
+
+ before(async () => {
+ user = await generateUser();
+ guild = await generateGroup(user);
+ challenge = await generateChallenge(user, guild);
+ });
+
+ it('fails on task not found', async () => {
+ let task = await user.post(`/tasks/challenge/${challenge._id}`, {
+ type: 'todo',
+ text: 'Todo with checklist',
+ });
+
+ await expect(user.put(`/tasks/${task._id}/checklist/${generateUUID()}`, {
+ text: 'updated',
+ completed: true,
+ _id: 123, // ignored
+ }))
+ .to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('checklistItemNotFound'),
+ });
+ });
+
+ it('returns error when user is not a member of the challenge', async () => {
+ let task = await user.post(`/tasks/challenge/${challenge._id}`, {
+ type: 'todo',
+ text: 'Todo with checklist',
+ });
+
+ let savedTask = await user.post(`/tasks/${task._id}/checklist`, {
+ text: 'Checklist Item 1',
+ completed: false,
+ });
+
+ let anotherUser = await generateUser();
+
+ await expect(anotherUser.put(`/tasks/${task._id}/checklist/${savedTask.checklist[0].id}`, {
+ text: 'updated',
+ completed: true,
+ _id: 123, // ignored
+ }))
+ .to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('onlyChalLeaderEditTasks'),
+ });
+ });
+
+ it('updates a checklist item on dailies', async () => {
+ let task = await user.post(`/tasks/challenge/${challenge._id}`, {
+ type: 'daily',
+ text: 'Daily with checklist',
+ });
+
+ let savedTask = await user.post(`/tasks/${task._id}/checklist`, {
+ text: 'Checklist Item 1',
+ completed: false,
+ });
+
+ savedTask = await user.put(`/tasks/${task._id}/checklist/${savedTask.checklist[0].id}`, {
+ text: 'updated',
+ completed: true,
+ _id: 123, // ignored
+ });
+
+ expect(savedTask.checklist.length).to.equal(1);
+ expect(savedTask.checklist[0].text).to.equal('updated');
+ expect(savedTask.checklist[0].completed).to.equal(true);
+ expect(savedTask.checklist[0].id).to.not.equal('123');
+ });
+
+ it('updates a checklist item on todos', async () => {
+ let task = await user.post(`/tasks/challenge/${challenge._id}`, {
+ type: 'todo',
+ text: 'Todo with checklist',
+ });
+
+ let savedTask = await user.post(`/tasks/${task._id}/checklist`, {
+ text: 'Checklist Item 1',
+ completed: false,
+ });
+
+ savedTask = await user.put(`/tasks/${task._id}/checklist/${savedTask.checklist[0].id}`, {
+ text: 'updated',
+ completed: true,
+ _id: 123, // ignored
+ });
+
+ expect(savedTask.checklist.length).to.equal(1);
+ expect(savedTask.checklist[0].text).to.equal('updated');
+ expect(savedTask.checklist[0].completed).to.equal(true);
+ expect(savedTask.checklist[0].id).to.not.equal('123');
+ });
+
+ it('fails on habits', async () => {
+ let habit = await user.post('/tasks/user', {
+ type: 'habit',
+ text: 'habit with checklist',
+ });
+
+ await expect(user.put(`/tasks/${habit._id}/checklist/${generateUUID()}`)).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('checklistOnlyDailyTodo'),
+ });
+ });
+
+ it('fails on rewards', async () => {
+ let reward = await user.post('/tasks/user', {
+ type: 'reward',
+ text: 'reward with checklist',
+ });
+
+ await expect(user.put(`/tasks/${reward._id}/checklist/${generateUUID()}`)).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('checklistOnlyDailyTodo'),
+ });
+ });
+
+ it('fails on task not found', async () => {
+ await expect(user.put(`/tasks/${generateUUID()}/checklist/${generateUUID()}`)).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('taskNotFound'),
+ });
+ });
+
+ it('fails on checklist item not found', async () => {
+ let createdTask = await user.post('/tasks/user', {
+ type: 'daily',
+ text: 'daily with checklist',
+ });
+
+ await expect(user.put(`/tasks/${createdTask._id}/checklist/${generateUUID()}`)).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('checklistItemNotFound'),
+ });
+ });
+});
diff --git a/test/api/v3/integration/tasks/checklists/DELETE-tasks_taskId_checklist_itemId.test.js b/test/api/v3/integration/tasks/checklists/DELETE-tasks_taskId_checklist_itemId.test.js
new file mode 100644
index 0000000000..2cf08bbece
--- /dev/null
+++ b/test/api/v3/integration/tasks/checklists/DELETE-tasks_taskId_checklist_itemId.test.js
@@ -0,0 +1,74 @@
+import {
+ generateUser,
+ translate as t,
+} from '../../../../../helpers/api-integration/v3';
+import { v4 as generateUUID } from 'uuid';
+
+describe('DELETE /tasks/:taskId/checklist/:itemId', () => {
+ let user;
+
+ before(async () => {
+ user = await generateUser();
+ });
+
+ it('deletes a checklist item', async () => {
+ let task = await user.post('/tasks/user', {
+ type: 'daily',
+ text: 'Daily with checklist',
+ });
+
+ let savedTask = await user.post(`/tasks/${task._id}/checklist`, {text: 'Checklist Item 1', completed: false});
+
+ await user.del(`/tasks/${task._id}/checklist/${savedTask.checklist[0].id}`);
+ savedTask = await user.get(`/tasks/${task._id}`);
+
+ expect(savedTask.checklist.length).to.equal(0);
+ });
+
+ it('does not work with habits', async () => {
+ let habit = await user.post('/tasks/user', {
+ type: 'habit',
+ text: 'habit with checklist',
+ });
+
+ await expect(user.del(`/tasks/${habit._id}/checklist/${generateUUID()}`)).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('checklistOnlyDailyTodo'),
+ });
+ });
+
+ it('does not work with rewards', async () => {
+ let reward = await user.post('/tasks/user', {
+ type: 'reward',
+ text: 'reward with checklist',
+ });
+
+ await expect(user.del(`/tasks/${reward._id}/checklist/${generateUUID()}`)).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('checklistOnlyDailyTodo'),
+ });
+ });
+
+ it('fails on task not found', async () => {
+ await expect(user.del(`/tasks/${generateUUID()}/checklist/${generateUUID()}`)).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('taskNotFound'),
+ });
+ });
+
+ it('fails on checklist item not found', async () => {
+ let createdTask = await user.post('/tasks/user', {
+ type: 'daily',
+ text: 'daily with checklist',
+ });
+
+ await expect(user.del(`/tasks/${createdTask._id}/checklist/${generateUUID()}`)).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('checklistItemNotFound'),
+ });
+ });
+});
diff --git a/test/api/v3/integration/tasks/checklists/POST-tasks_taskId_checklist.test.js b/test/api/v3/integration/tasks/checklists/POST-tasks_taskId_checklist.test.js
new file mode 100644
index 0000000000..7166db02da
--- /dev/null
+++ b/test/api/v3/integration/tasks/checklists/POST-tasks_taskId_checklist.test.js
@@ -0,0 +1,73 @@
+import {
+ generateUser,
+ translate as t,
+} from '../../../../../helpers/api-integration/v3';
+import { v4 as generateUUID } from 'uuid';
+
+describe('POST /tasks/:taskId/checklist/', () => {
+ let user;
+
+ before(async () => {
+ user = await generateUser();
+ });
+
+ it('adds a checklist item to a task', async () => {
+ let task = await user.post('/tasks/user', {
+ type: 'daily',
+ text: 'Daily with checklist',
+ });
+
+ let savedTask = await user.post(`/tasks/${task._id}/checklist`, {
+ text: 'Checklist Item 1',
+ ignored: false,
+ _id: 123,
+ });
+
+ expect(savedTask.checklist.length).to.equal(1);
+ expect(savedTask.checklist[0].text).to.equal('Checklist Item 1');
+ expect(savedTask.checklist[0].completed).to.equal(false);
+ expect(savedTask.checklist[0].id).to.be.a('string');
+ expect(savedTask.checklist[0].id).to.not.equal('123');
+ expect(savedTask.checklist[0].ignored).to.be.an('undefined');
+ });
+
+ it('does not add a checklist to habits', async () => {
+ let habit = await user.post('/tasks/user', {
+ type: 'habit',
+ text: 'habit with checklist',
+ });
+
+ await expect(user.post(`/tasks/${habit._id}/checklist`, {
+ text: 'Checklist Item 1',
+ })).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('checklistOnlyDailyTodo'),
+ });
+ });
+
+ it('does not add a checklist to rewards', async () => {
+ let reward = await user.post('/tasks/user', {
+ type: 'reward',
+ text: 'reward with checklist',
+ });
+
+ await expect(user.post(`/tasks/${reward._id}/checklist`, {
+ text: 'Checklist Item 1',
+ })).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('checklistOnlyDailyTodo'),
+ });
+ });
+
+ it('fails on task not found', async () => {
+ await expect(user.post(`/tasks/${generateUUID()}/checklist`, {
+ text: 'Checklist Item 1',
+ })).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('taskNotFound'),
+ });
+ });
+});
diff --git a/test/api/v3/integration/tasks/checklists/POST-tasks_taskId_checklist_itemId_score.test.js b/test/api/v3/integration/tasks/checklists/POST-tasks_taskId_checklist_itemId_score.test.js
new file mode 100644
index 0000000000..edb65dfb65
--- /dev/null
+++ b/test/api/v3/integration/tasks/checklists/POST-tasks_taskId_checklist_itemId_score.test.js
@@ -0,0 +1,79 @@
+import {
+ generateUser,
+ translate as t,
+} from '../../../../../helpers/api-integration/v3';
+import { v4 as generateUUID } from 'uuid';
+
+describe('POST /tasks/:taskId/checklist/:itemId/score', () => {
+ let user;
+
+ before(async () => {
+ user = await generateUser();
+ });
+
+ it('scores a checklist item', async () => {
+ let task = await user.post('/tasks/user', {
+ type: 'daily',
+ text: 'Daily with checklist',
+ });
+
+ let savedTask = await user.post(`/tasks/${task._id}/checklist`, {
+ text: 'Checklist Item 1',
+ completed: false,
+ });
+
+ savedTask = await user.post(`/tasks/${task._id}/checklist/${savedTask.checklist[0].id}/score`);
+
+ expect(savedTask.checklist.length).to.equal(1);
+ expect(savedTask.checklist[0].completed).to.equal(true);
+ });
+
+ it('fails on habits', async () => {
+ let habit = await user.post('/tasks/user', {
+ type: 'habit',
+ text: 'habit with checklist',
+ });
+
+ await expect(user.post(`/tasks/${habit._id}/checklist/${generateUUID()}/score`, {
+ text: 'Checklist Item 1',
+ })).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('checklistOnlyDailyTodo'),
+ });
+ });
+
+ it('fails on rewards', async () => {
+ let reward = await user.post('/tasks/user', {
+ type: 'reward',
+ text: 'reward with checklist',
+ });
+
+ await expect(user.post(`/tasks/${reward._id}/checklist/${generateUUID()}/score`)).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('checklistOnlyDailyTodo'),
+ });
+ });
+
+ it('fails on task not found', async () => {
+ await expect(user.post(`/tasks/${generateUUID()}/checklist/${generateUUID()}/score`)).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('taskNotFound'),
+ });
+ });
+
+ it('fails on checklist item not found', async () => {
+ let createdTask = await user.post('/tasks/user', {
+ type: 'daily',
+ text: 'daily with checklist',
+ });
+
+ await expect(user.post(`/tasks/${createdTask._id}/checklist/${generateUUID()}/score`)).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('checklistItemNotFound'),
+ });
+ });
+});
diff --git a/test/api/v3/integration/tasks/checklists/PUT-tasks_taskId_checklist_itemId.test.js b/test/api/v3/integration/tasks/checklists/PUT-tasks_taskId_checklist_itemId.test.js
new file mode 100644
index 0000000000..003bcb2650
--- /dev/null
+++ b/test/api/v3/integration/tasks/checklists/PUT-tasks_taskId_checklist_itemId.test.js
@@ -0,0 +1,83 @@
+import {
+ generateUser,
+ translate as t,
+} from '../../../../../helpers/api-integration/v3';
+import { v4 as generateUUID } from 'uuid';
+
+describe('PUT /tasks/:taskId/checklist/:itemId', () => {
+ let user;
+
+ before(async () => {
+ user = await generateUser();
+ });
+
+ it('updates a checklist item', async () => {
+ let task = await user.post('/tasks/user', {
+ type: 'daily',
+ text: 'Daily with checklist',
+ });
+
+ let savedTask = await user.post(`/tasks/${task._id}/checklist`, {
+ text: 'Checklist Item 1',
+ completed: false,
+ });
+
+ savedTask = await user.put(`/tasks/${task._id}/checklist/${savedTask.checklist[0].id}`, {
+ text: 'updated',
+ completed: true,
+ _id: 123, // ignored
+ });
+
+ expect(savedTask.checklist.length).to.equal(1);
+ expect(savedTask.checklist[0].text).to.equal('updated');
+ expect(savedTask.checklist[0].completed).to.equal(true);
+ expect(savedTask.checklist[0].id).to.not.equal('123');
+ });
+
+ it('fails on habits', async () => {
+ let habit = await user.post('/tasks/user', {
+ type: 'habit',
+ text: 'habit with checklist',
+ });
+
+ await expect(user.put(`/tasks/${habit._id}/checklist/${generateUUID()}`)).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('checklistOnlyDailyTodo'),
+ });
+ });
+
+ it('fails on rewards', async () => {
+ let reward = await user.post('/tasks/user', {
+ type: 'reward',
+ text: 'reward with checklist',
+ });
+
+ await expect(user.put(`/tasks/${reward._id}/checklist/${generateUUID()}`)).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('checklistOnlyDailyTodo'),
+ });
+ });
+
+ it('fails on task not found', async () => {
+ await expect(user.put(`/tasks/${generateUUID()}/checklist/${generateUUID()}`)).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('taskNotFound'),
+ });
+ });
+
+ it('fails on checklist item not found', async () => {
+ let createdTask = await user.post('/tasks/user', {
+ type: 'daily',
+ text: 'daily with checklist',
+ });
+
+ await expect(user.put(`/tasks/${createdTask._id}/checklist/${generateUUID()}`)).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('checklistItemNotFound'),
+ });
+ });
+});
diff --git a/test/api/v3/integration/tasks/tags/DELETE-tasks_taskId_tags_tagId.test.js b/test/api/v3/integration/tasks/tags/DELETE-tasks_taskId_tags_tagId.test.js
new file mode 100644
index 0000000000..ebb1a3c9e8
--- /dev/null
+++ b/test/api/v3/integration/tasks/tags/DELETE-tasks_taskId_tags_tagId.test.js
@@ -0,0 +1,42 @@
+import {
+ generateUser,
+ translate as t,
+} from '../../../../../helpers/api-integration/v3';
+import { v4 as generateUUID } from 'uuid';
+
+describe('DELETE /tasks/:taskId/tags/:tagId', () => {
+ let user;
+
+ before(async () => {
+ user = await generateUser();
+ });
+
+ it('removes a tag from a task', async () => {
+ let task = await user.post('/tasks/user', {
+ type: 'habit',
+ text: 'Task with tag',
+ });
+
+ let tag = await user.post('/tags', {name: 'Tag 1'});
+
+ await user.post(`/tasks/${task._id}/tags/${tag.id}`);
+ await user.del(`/tasks/${task._id}/tags/${tag.id}`);
+
+ let updatedTask = await user.get(`/tasks/${task._id}`);
+
+ expect(updatedTask.tags.length).to.equal(0);
+ });
+
+ it('only deletes existing tags', async () => {
+ let createdTask = await user.post('/tasks/user', {
+ type: 'habit',
+ text: 'Task with tag',
+ });
+
+ await expect(user.del(`/tasks/${createdTask._id}/tags/${generateUUID()}`)).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('tagNotFound'),
+ });
+ });
+});
diff --git a/test/api/v3/integration/tasks/tags/POST-tasks_taskId_tags_tagId.test.js b/test/api/v3/integration/tasks/tags/POST-tasks_taskId_tags_tagId.test.js
new file mode 100644
index 0000000000..d6cea02036
--- /dev/null
+++ b/test/api/v3/integration/tasks/tags/POST-tasks_taskId_tags_tagId.test.js
@@ -0,0 +1,55 @@
+import {
+ generateUser,
+ translate as t,
+} from '../../../../../helpers/api-integration/v3';
+import { v4 as generateUUID } from 'uuid';
+
+describe('POST /tasks/:taskId/tags/:tagId', () => {
+ let user;
+
+ before(async () => {
+ user = await generateUser();
+ });
+
+ it('adds a tag to a task', async () => {
+ let task = await user.post('/tasks/user', {
+ type: 'habit',
+ text: 'Task with tag',
+ });
+
+ let tag = await user.post('/tags', {name: 'Tag 1'});
+ let savedTask = await user.post(`/tasks/${task._id}/tags/${tag.id}`);
+
+ expect(savedTask.tags[0]).to.equal(tag.id);
+ });
+
+ it('does not add a tag to a task twice', async () => {
+ let task = await user.post('/tasks/user', {
+ type: 'habit',
+ text: 'Task with tag',
+ });
+
+ let tag = await user.post('/tags', {name: 'Tag 1'});
+
+ await user.post(`/tasks/${task._id}/tags/${tag.id}`);
+
+ await expect(user.post(`/tasks/${task._id}/tags/${tag.id}`)).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('alreadyTagged'),
+ });
+ });
+
+ it('does not add a non existing tag to a task', async () => {
+ let task = await user.post('/tasks/user', {
+ type: 'habit',
+ text: 'Task with tag',
+ });
+
+ await expect(user.post(`/tasks/${task._id}/tags/${generateUUID()}`)).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('invalidReqParams'),
+ });
+ });
+});
diff --git a/test/api/v3/integration/user/DELETE-user.test.js b/test/api/v3/integration/user/DELETE-user.test.js
new file mode 100644
index 0000000000..2a284add53
--- /dev/null
+++ b/test/api/v3/integration/user/DELETE-user.test.js
@@ -0,0 +1,176 @@
+import {
+ checkExistence,
+ createAndPopulateGroup,
+ generateGroup,
+ generateUser,
+ translate as t,
+} from '../../../../helpers/api-integration/v3';
+import {
+ find,
+ each,
+ map,
+} from 'lodash';
+import Bluebird from 'bluebird';
+
+describe('DELETE /user', () => {
+ let user;
+ let password = 'password'; // from habitrpg/test/helpers/api-integration/v3/object-generators.js
+
+ beforeEach(async () => {
+ user = await generateUser({balance: 10});
+ });
+
+ it('returns an errors if password is wrong', async () => {
+ await expect(user.del('/user', {
+ password: 'wrong-password',
+ })).to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('wrongPassword'),
+ });
+ });
+
+ it('returns an error if user has active subscription', async () => {
+ let userWithSubscription = await generateUser({'purchased.plan.customerId': 'fake-customer-id'});
+
+ await expect(userWithSubscription.del('/user', {
+ password,
+ })).to.be.rejected.and.to.eventually.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('cannotDeleteActiveAccount'),
+ });
+ });
+
+ it('deletes the user\'s tasks', async () => {
+ // gets the user's tasks ids
+ let ids = [];
+ each(user.tasksOrder, (idsForOrder) => {
+ ids.push(...idsForOrder);
+ });
+
+ expect(ids.length).to.be.above(0); // make sure the user has some task to delete
+
+ await user.del('/user', {
+ password,
+ });
+
+ await Bluebird.all(map(ids, id => {
+ return expect(checkExistence('tasks', id)).to.eventually.eql(false);
+ }));
+ });
+
+ it('deletes the user', async () => {
+ await user.del('/user', {
+ password,
+ });
+ await expect(checkExistence('users', user._id)).to.eventually.eql(false);
+ });
+
+ context('last member of a party', () => {
+ let party;
+
+ beforeEach(async () => {
+ party = await generateGroup(user, {
+ type: 'party',
+ privacy: 'private',
+ });
+ });
+
+ it('deletes party when user is the only member', async () => {
+ await user.del('/user', {
+ password,
+ });
+ await expect(checkExistence('party', party._id)).to.eventually.eql(false);
+ });
+ });
+
+ context('last member of a private guild', () => {
+ let privateGuild;
+
+ beforeEach(async () => {
+ privateGuild = await generateGroup(user, {
+ type: 'guild',
+ privacy: 'private',
+ });
+ });
+
+ it('deletes guild when user is the only member', async () => {
+ await user.del('/user', {
+ password,
+ });
+ await expect(checkExistence('groups', privateGuild._id)).to.eventually.eql(false);
+ });
+ });
+
+ context('groups user is leader of', () => {
+ let guild, oldLeader, newLeader;
+
+ beforeEach(async () => {
+ let { group, groupLeader, members } = await createAndPopulateGroup({
+ groupDetails: {
+ type: 'guild',
+ privacy: 'public',
+ },
+ members: 1,
+ });
+
+ guild = group;
+ newLeader = members[0];
+ oldLeader = groupLeader;
+ });
+
+ it('chooses new group leader for any group user was the leader of', async () => {
+ await oldLeader.del('/user', {
+ password,
+ });
+
+ let updatedGuild = await newLeader.get(`/groups/${guild._id}`);
+
+ expect(updatedGuild.leader).to.exist;
+ expect(updatedGuild.leader._id).to.not.eql(oldLeader._id);
+ });
+ });
+
+ context('groups user is a part of', () => {
+ let group1, group2, userToDelete, otherUser;
+
+ beforeEach(async () => {
+ userToDelete = await generateUser({balance: 10});
+
+ group1 = await generateGroup(userToDelete, {
+ type: 'guild',
+ privacy: 'public',
+ });
+
+ let {group, members} = await createAndPopulateGroup({
+ groupDetails: {
+ type: 'guild',
+ privacy: 'public',
+ },
+ members: 3,
+ });
+
+ group2 = group;
+ otherUser = members[0];
+
+ await userToDelete.post(`/groups/${group2._id}/join`);
+ });
+
+ it('removes user from all groups user was a part of', async () => {
+ await userToDelete.del('/user', {
+ password,
+ });
+
+ let updatedGroup1Members = await otherUser.get(`/groups/${group1._id}/members`);
+ let updatedGroup2Members = await otherUser.get(`/groups/${group2._id}/members`);
+ let userInGroup = find(updatedGroup2Members, (member) => {
+ return member._id === userToDelete._id;
+ });
+
+ expect(updatedGroup1Members).to.be.empty;
+ expect(updatedGroup2Members).to.not.be.empty;
+ expect(userInGroup).to.not.exist;
+ });
+ });
+});
diff --git a/test/api/v3/integration/user/DELETE-user_delete_webhook.test.js b/test/api/v3/integration/user/DELETE-user_delete_webhook.test.js
new file mode 100644
index 0000000000..46844dd855
--- /dev/null
+++ b/test/api/v3/integration/user/DELETE-user_delete_webhook.test.js
@@ -0,0 +1,23 @@
+import {
+ generateUser,
+} from '../../../../helpers/api-integration/v3';
+
+let user;
+let endpoint = '/user/webhook';
+
+describe('DELETE /user/webhook', () => {
+ beforeEach(async () => {
+ user = await generateUser();
+ });
+
+ it('succeeds', async () => {
+ let id = 'some-id';
+ user.preferences.webhooks[id] = { url: 'http://some-url.com', enabled: true };
+ await user.sync();
+ expect(user.preferences.webhooks).to.eql({});
+ let response = await user.del(`${endpoint}/${id}`);
+ expect(response).to.eql({});
+ await user.sync();
+ expect(user.preferences.webhooks).to.eql({});
+ });
+});
diff --git a/test/api/v3/integration/user/DELETE-user_messages.test.js b/test/api/v3/integration/user/DELETE-user_messages.test.js
new file mode 100644
index 0000000000..98df8e0209
--- /dev/null
+++ b/test/api/v3/integration/user/DELETE-user_messages.test.js
@@ -0,0 +1,27 @@
+import {
+ generateUser,
+} from '../../../../helpers/api-integration/v3';
+
+describe('DELETE user message', () => {
+ let user;
+
+ beforeEach(async () => {
+ user = await generateUser({ inbox: { messages: { first: 'message', second: 'message' } } });
+ expect(user.inbox.messages.first).to.eql('message');
+ expect(user.inbox.messages.second).to.eql('message');
+ });
+
+ it('one message', async () => {
+ let result = await user.del('/user/messages/first');
+ await user.sync();
+ expect(result).to.eql({ second: 'message' });
+ expect(user.inbox.messages).to.eql({ second: 'message' });
+ });
+
+ it('clear all', async () => {
+ let result = await user.del('/user/messages');
+ await user.sync();
+ expect(user.inbox.messages).to.eql({});
+ expect(result).to.eql({});
+ });
+});
diff --git a/test/api/v3/integration/user/GET-user.test.js b/test/api/v3/integration/user/GET-user.test.js
new file mode 100644
index 0000000000..f4ed75f03f
--- /dev/null
+++ b/test/api/v3/integration/user/GET-user.test.js
@@ -0,0 +1,24 @@
+import {
+ generateUser,
+} from '../../../../helpers/api-integration/v3';
+
+describe('GET /user', () => {
+ let user;
+
+ before(async () => {
+ user = await generateUser();
+ });
+
+ it('returns the authenticated user', async () => {
+ let returnedUser = await user.get('/user');
+ expect(returnedUser._id).to.equal(user._id);
+ });
+
+ it('does not return private paths (and apiToken)', async () => {
+ let returnedUser = await user.get('/user');
+
+ expect(returnedUser.auth.local.hashed_password).to.not.exist;
+ expect(returnedUser.auth.local.salt).to.not.exist;
+ expect(returnedUser.apiToken).to.not.exist;
+ });
+});
diff --git a/test/api/v3/integration/user/GET-user_anonymized.test.js b/test/api/v3/integration/user/GET-user_anonymized.test.js
new file mode 100644
index 0000000000..136174a75e
--- /dev/null
+++ b/test/api/v3/integration/user/GET-user_anonymized.test.js
@@ -0,0 +1,90 @@
+import {
+ generateUser,
+ generateHabit,
+ generateDaily,
+ generateReward,
+} from '../../../../helpers/api-integration/v3';
+import common from '../../../../../common';
+import { v4 as generateUUID } from 'uuid';
+
+describe('GET /user/anonymized', () => {
+ let user;
+ let endpoint = '/user/anonymized';
+
+ before(async () => {
+ user = await generateUser();
+ await user.update({ newMessages: ['some', 'new', 'messages'], 'profile.name': 'profile', 'purchased.plan': 'purchased plan',
+ contributor: 'contributor', invitations: 'invitations', 'items.special.nyeReceived': 'some', 'items.special.valentineReceived': 'some',
+ webhooks: 'some', 'achievements.challenges': 'some',
+ 'inbox.messages': [{ text: 'some text' }],
+ tags: [{ name: 'some name', challenge: 'some challenge' }],
+ });
+
+ await generateHabit({ userId: user._id });
+ await generateHabit({ userId: user._id, text: generateUUID() });
+ let daily = await generateDaily({ userId: user._id, checklist: [{ completed: false, text: 'this-text' }] });
+ expect(daily.checklist[0].text.substr(0, 5)).to.not.eql('item ');
+ await generateReward({ userId: user._id, text: 'some text 4' });
+
+ expect(user.newMessages).to.exist;
+ expect(user.profile).to.exist;
+ expect(user.purchased.plan).to.exist;
+ expect(user.contributor).to.exist;
+ expect(user.invitations).to.exist;
+ expect(user.items.special.nyeReceived).to.exist;
+ expect(user.items.special.valentineReceived).to.exist;
+ expect(user.webhooks).to.exist;
+ expect(user.achievements.challenges).to.exist;
+ expect(user.inbox.messages[0].text).to.exist;
+ expect(user.inbox.messages[0].text).to.not.eql('inbox message text');
+ expect(user.tags[0].name).to.exist;
+ expect(user.tags[0].name).to.not.eql('tag');
+ expect(user.tags[0].challenge).to.not.eql('challenge');
+ });
+
+ it('returns the authenticated user', async () => {
+ let returnedUser = await user.get(endpoint);
+ returnedUser = returnedUser.user;
+ expect(returnedUser._id).to.equal(user._id);
+ });
+
+ it('does not return private paths (and apiToken)', async () => {
+ let returnedUser = await user.get(endpoint);
+ let tasks2 = returnedUser.tasks;
+ returnedUser = returnedUser.user;
+ expect(returnedUser.auth.local).to.not.exist;
+ expect(returnedUser.apiToken).to.not.exist;
+ expect(returnedUser.stats.maxHealth).to.eql(common.maxHealth);
+ expect(returnedUser.stats.toNextLevel).to.eql(common.tnl(user.stats.lvl));
+ expect(returnedUser.stats.maxMP).to.eql(30); // TODO why 30?
+ expect(returnedUser.newMessages).to.not.exist;
+ expect(returnedUser.profile).to.not.exist;
+ expect(returnedUser.purchased.plan).to.not.exist;
+ expect(returnedUser.contributor).to.not.exist;
+ expect(returnedUser.invitations).to.not.exist;
+ expect(returnedUser.items.special.nyeReceived).to.not.exist;
+ expect(returnedUser.items.special.valentineReceived).to.not.exist;
+ expect(returnedUser.webhooks).to.not.exist;
+ expect(returnedUser.achievements.challenges).to.not.exist;
+ _.forEach(returnedUser.inbox.messages, (msg) => {
+ expect(msg.text).to.eql('inbox message text');
+ });
+ _.forEach(returnedUser.tags, (tag) => {
+ expect(tag.name).to.eql('tag');
+ expect(tag.challenge).to.eql('challenge');
+ });
+ // tasks
+ expect(tasks2).to.exist;
+ expect(tasks2.length).to.eql(5); // +1 because generateUser() assigns one todo
+ expect(tasks2[0].checklist).to.exist;
+ _.forEach(tasks2, (task) => {
+ expect(task.text).to.eql('task text');
+ expect(task.notes).to.eql('task notes');
+ if (task.checklist) {
+ _.forEach(task.checklist, (c) => {
+ expect(c.text.substr(0, 5)).to.eql('item ');
+ });
+ }
+ });
+ });
+});
diff --git a/test/api/v3/integration/user/GET-user_inventory_buy.test.js b/test/api/v3/integration/user/GET-user_inventory_buy.test.js
new file mode 100644
index 0000000000..fd2a25b4ee
--- /dev/null
+++ b/test/api/v3/integration/user/GET-user_inventory_buy.test.js
@@ -0,0 +1,26 @@
+import {
+ generateUser,
+ translate as t,
+} from '../../../../helpers/api-integration/v3';
+
+describe('GET /user/inventory/buy', () => {
+ let user;
+
+ before(async () => {
+ user = await generateUser();
+ });
+
+ // More tests in common code unit tests
+
+ it('returns the gear items available for purchase', async () => {
+ let buyList = await user.get('/user/inventory/buy');
+
+ expect(_.find(buyList, item => {
+ return item.text === t('armorWarrior1Text');
+ })).to.exist;
+
+ expect(_.find(buyList, item => {
+ return item.text === t('armorWarrior2Text');
+ })).to.not.exist;
+ });
+});
diff --git a/test/api/v3/integration/user/POST-user_addPushDevice.test.js b/test/api/v3/integration/user/POST-user_addPushDevice.test.js
new file mode 100644
index 0000000000..1a3a5d4f03
--- /dev/null
+++ b/test/api/v3/integration/user/POST-user_addPushDevice.test.js
@@ -0,0 +1,35 @@
+import {
+ generateUser,
+ translate as t,
+} from '../../../../helpers/api-integration/v3';
+
+describe('POST /user/addPushDevice', () => {
+ let user;
+ let regId = '10';
+ let type = 'someRandomType';
+
+ beforeEach(async () => {
+ user = await generateUser();
+ });
+
+ it('returns an error if user already has the push device', async () => {
+ await user.post('/user/addPushDevice', {type, regId});
+ await expect(user.post('/user/addPushDevice', {type, regId}))
+ .to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('pushDeviceAlreadyAdded'),
+ });
+ });
+
+ // More tests in common code unit tests
+
+ it('adds a push device to the user', async () => {
+ let response = await user.post('/user/addPushDevice', {type, regId});
+ await user.sync();
+
+ expect(response.message).to.equal(t('pushDeviceAdded'));
+ expect(user.pushDevices[0].type).to.equal(type);
+ expect(user.pushDevices[0].regId).to.equal(regId);
+ });
+});
diff --git a/test/api/v3/integration/user/POST-user_add_webhook.test.js b/test/api/v3/integration/user/POST-user_add_webhook.test.js
new file mode 100644
index 0000000000..d13f15baa4
--- /dev/null
+++ b/test/api/v3/integration/user/POST-user_add_webhook.test.js
@@ -0,0 +1,29 @@
+import {
+ generateUser,
+ translate as t,
+} from '../../../../helpers/api-integration/v3';
+
+let user;
+let endpoint = '/user/webhook';
+
+describe('POST /user/webhook', () => {
+ beforeEach(async () => {
+ user = await generateUser();
+ });
+
+ it('validates', async () => {
+ await expect(user.post(endpoint, { enabled: true })).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('invalidUrl'),
+ });
+ });
+
+ it('successfully adds the webhook', async () => {
+ expect(user.preferences.webhooks).to.eql({});
+ let response = await user.post(endpoint, { enabled: true, url: 'http://some-url.com'});
+ expect(response.id).to.exist;
+ await user.sync();
+ expect(user.preferences.webhooks).to.not.eql({});
+ });
+});
diff --git a/test/api/v3/integration/user/POST-user_allocate.test.js b/test/api/v3/integration/user/POST-user_allocate.test.js
new file mode 100644
index 0000000000..02d4990092
--- /dev/null
+++ b/test/api/v3/integration/user/POST-user_allocate.test.js
@@ -0,0 +1,41 @@
+import {
+ generateUser,
+ translate as t,
+} from '../../../../helpers/api-integration/v3';
+
+describe('POST /user/allocate', () => {
+ let user;
+
+ beforeEach(async () => {
+ user = await generateUser();
+ });
+
+ // More tests in common code unit tests
+
+ it('returns an error if an invalid attribute is supplied', async () => {
+ await expect(user.post('/user/allocate?stat=invalid'))
+ .to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('invalidAttribute', {attr: 'invalid'}),
+ });
+ });
+
+ it('returns an error if the user doesn\'t have attribute points', async () => {
+ await expect(user.post('/user/allocate'))
+ .to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('notEnoughAttrPoints'),
+ });
+ });
+
+ it('allocates attribute points', async () => {
+ await user.update({'stats.points': 1});
+ let res = await user.post('/user/allocate?stat=con');
+ await user.sync();
+ expect(user.stats.con).to.equal(1);
+ expect(user.stats.points).to.equal(0);
+ expect(res.con).to.equal(1);
+ });
+});
diff --git a/test/api/v3/integration/user/POST-user_allocate_now.test.js b/test/api/v3/integration/user/POST-user_allocate_now.test.js
new file mode 100644
index 0000000000..b45f2156be
--- /dev/null
+++ b/test/api/v3/integration/user/POST-user_allocate_now.test.js
@@ -0,0 +1,28 @@
+import {
+ generateUser,
+} from '../../../../helpers/api-integration/v3';
+
+describe('POST /user/allocate-now', () => {
+ // More tests in common code unit tests
+
+ it('auto allocates all points', async () => {
+ let user = await generateUser({
+ 'stats.points': 5,
+ 'stats.int': 3,
+ 'stats.con': 9,
+ 'stats.per': 9,
+ 'stats.str': 9,
+ 'preferences.allocationMode': 'flat',
+ });
+
+ let res = await user.post('/user/allocate-now');
+ await user.sync();
+
+ expect(res).to.eql(user.stats);
+ expect(user.stats.points).to.equal(0);
+ expect(user.stats.con).to.equal(9);
+ expect(user.stats.int).to.equal(8);
+ expect(user.stats.per).to.equal(9);
+ expect(user.stats.str).to.equal(9);
+ });
+});
diff --git a/test/api/v3/integration/user/POST-user_block.test.js b/test/api/v3/integration/user/POST-user_block.test.js
new file mode 100644
index 0000000000..51766eb51e
--- /dev/null
+++ b/test/api/v3/integration/user/POST-user_block.test.js
@@ -0,0 +1,34 @@
+import {
+ generateUser,
+ translate as t,
+} from '../../../../helpers/api-integration/v3';
+
+describe('block user', () => {
+ let user;
+ let blockedUser;
+ let blockedUser2;
+
+ beforeEach(async () => {
+ blockedUser = await generateUser();
+ blockedUser2 = await generateUser();
+ user = await generateUser({ inbox: { blocks: [blockedUser._id] } });
+ expect(user.inbox.blocks.length).to.eql(1);
+ expect(user.inbox.blocks).to.eql([blockedUser._id]);
+ });
+
+ it('validates uuid', async () => {
+ await expect(user.post('/user/block/1')).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('invalidUUID'),
+ });
+ });
+
+ it('successfully', async () => {
+ let response = await user.post(`/user/block/${blockedUser2._id}`);
+ await user.sync();
+ expect(response).to.eql([blockedUser._id, blockedUser2._id]);
+ expect(user.inbox.blocks.length).to.eql(2);
+ expect(user.inbox.blocks).to.include(blockedUser2._id);
+ });
+});
diff --git a/test/api/v3/integration/user/POST-user_buy.test.js b/test/api/v3/integration/user/POST-user_buy.test.js
new file mode 100644
index 0000000000..ffad12f2a0
--- /dev/null
+++ b/test/api/v3/integration/user/POST-user_buy.test.js
@@ -0,0 +1,62 @@
+/* eslint-disable camelcase */
+
+import {
+ generateUser,
+ translate as t,
+} from '../../../../helpers/api-integration/v3';
+import shared from '../../../../../common/script';
+
+let content = shared.content;
+
+describe('POST /user/buy/:key', () => {
+ let user;
+
+ beforeEach(async () => {
+ user = await generateUser({
+ 'stats.gp': 400,
+ });
+ });
+
+ // More tests in common code unit tests
+
+ it('returns an error if the item is not found', async () => {
+ await expect(user.post('/user/buy/notExisting'))
+ .to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('itemNotFound', {key: 'notExisting'}),
+ });
+ });
+
+ it('buys a potion', async () => {
+ await user.update({
+ 'stats.gp': 400,
+ });
+
+ let potion = content.potion;
+ let res = await user.post('/user/buy/potion');
+ await user.sync();
+
+ expect(user.stats.hp).to.equal(50);
+ expect(res.data).to.eql(user.stats);
+ expect(res.message).to.equal(t('messageBought', {itemText: potion.text()}));
+ });
+
+ it('buys a piece of gear', async () => {
+ let key = 'armor_warrior_1';
+
+ await user.post(`/user/buy/${key}`);
+ await user.sync();
+
+ expect(user.items.gear.owned).to.eql({
+ armor_warrior_1: true,
+ eyewear_special_blackTopFrame: true,
+ eyewear_special_blueTopFrame: true,
+ eyewear_special_greenTopFrame: true,
+ eyewear_special_pinkTopFrame: true,
+ eyewear_special_redTopFrame: true,
+ eyewear_special_whiteTopFrame: true,
+ eyewear_special_yellowTopFrame: true,
+ });
+ });
+});
diff --git a/test/api/v3/integration/user/POST-user_buy_armoire.test.js b/test/api/v3/integration/user/POST-user_buy_armoire.test.js
new file mode 100644
index 0000000000..32b8134647
--- /dev/null
+++ b/test/api/v3/integration/user/POST-user_buy_armoire.test.js
@@ -0,0 +1,41 @@
+import {
+ generateUser,
+ translate as t,
+} from '../../../../helpers/api-integration/v3';
+
+describe('POST /user/buy-armoire', () => {
+ let user;
+
+ beforeEach(async () => {
+ user = await generateUser({
+ 'stats.gp': 400,
+ });
+ });
+
+ // More tests in common code unit tests
+
+ it('returns an error if user does not have enough gold', async () => {
+ await user.update({
+ 'stats.gp': 5,
+ });
+
+ await expect(user.post('/user/buy-armoire'))
+ .to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('messageNotEnoughGold'),
+ });
+ });
+
+ it('reduces gold when buying from the armoire', async () => {
+ await user.post('/user/buy-armoire');
+
+ await user.sync();
+
+ expect(user.stats.gp).to.equal(300);
+ });
+
+ xit('buys a piece of armoire', async () => {
+ // Skipped because can't stub predictableRandom correctly
+ });
+});
diff --git a/test/api/v3/integration/user/POST-user_buy_gear.test.js b/test/api/v3/integration/user/POST-user_buy_gear.test.js
new file mode 100644
index 0000000000..f577263d4a
--- /dev/null
+++ b/test/api/v3/integration/user/POST-user_buy_gear.test.js
@@ -0,0 +1,45 @@
+/* eslint-disable camelcase */
+
+import {
+ generateUser,
+ translate as t,
+} from '../../../../helpers/api-integration/v3';
+
+describe('POST /user/buy-gear/:key', () => {
+ let user;
+
+ beforeEach(async () => {
+ user = await generateUser({
+ 'stats.gp': 400,
+ });
+ });
+
+ // More tests in common code unit tests
+
+ it('returns an error if the item is not found', async () => {
+ await expect(user.post('/user/buy-gear/notExisting'))
+ .to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('itemNotFound', {key: 'notExisting'}),
+ });
+ });
+
+ it('buys a piece of gear', async () => {
+ let key = 'armor_warrior_1';
+
+ await user.post(`/user/buy-gear/${key}`);
+ await user.sync();
+
+ expect(user.items.gear.owned).to.eql({
+ armor_warrior_1: true,
+ eyewear_special_blackTopFrame: true,
+ eyewear_special_blueTopFrame: true,
+ eyewear_special_greenTopFrame: true,
+ eyewear_special_pinkTopFrame: true,
+ eyewear_special_redTopFrame: true,
+ eyewear_special_whiteTopFrame: true,
+ eyewear_special_yellowTopFrame: true,
+ });
+ });
+});
diff --git a/test/api/v3/integration/user/POST-user_buy_health_potion.test.js b/test/api/v3/integration/user/POST-user_buy_health_potion.test.js
new file mode 100644
index 0000000000..835e893bd7
--- /dev/null
+++ b/test/api/v3/integration/user/POST-user_buy_health_potion.test.js
@@ -0,0 +1,42 @@
+import {
+ generateUser,
+ translate as t,
+} from '../../../../helpers/api-integration/v3';
+import shared from '../../../../../common/script';
+
+let content = shared.content;
+
+describe('POST /user/buy-health-potion', () => {
+ let user;
+
+ beforeEach(async () => {
+ user = await generateUser({
+ 'stats.hp': 40,
+ });
+ });
+
+ // More tests in common code unit tests
+
+ it('returns an error if user does not have enough gold', async () => {
+ await expect(user.post('/user/buy-health-potion'))
+ .to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('messageNotEnoughGold'),
+ });
+ });
+
+ it('buys a potion', async () => {
+ await user.update({
+ 'stats.gp': 400,
+ });
+
+ let potion = content.potion;
+ let res = await user.post('/user/buy-health-potion');
+ await user.sync();
+
+ expect(user.stats.hp).to.equal(50);
+ expect(res.data).to.eql(user.stats);
+ expect(res.message).to.equal(t('messageBought', {itemText: potion.text()}));
+ });
+});
diff --git a/test/api/v3/integration/user/POST-user_buy_mystery_set.test.js b/test/api/v3/integration/user/POST-user_buy_mystery_set.test.js
new file mode 100644
index 0000000000..da7116d732
--- /dev/null
+++ b/test/api/v3/integration/user/POST-user_buy_mystery_set.test.js
@@ -0,0 +1,38 @@
+import {
+ generateUser,
+ translate as t,
+} from '../../../../helpers/api-integration/v3';
+
+describe('POST /user/buy-mystery-set/:key', () => {
+ let user;
+
+ beforeEach(async () => {
+ user = await generateUser({
+ 'purchased.plan.consecutive.trinkets': 1,
+ });
+ });
+
+ // More tests in common code unit tests
+
+ it('returns an error if the mystery set is not found', async () => {
+ await expect(user.post('/user/buy-mystery-set/notExisting'))
+ .to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('mysterySetNotFound'),
+ });
+ });
+
+ it('buys a mystery set', async () => {
+ let key = 301404;
+
+ let res = await user.post(`/user/buy-mystery-set/${key}`);
+ await user.sync();
+
+ expect(res.data).to.eql({
+ items: JSON.parse(JSON.stringify(user.items)), // otherwise dates can't be compared
+ purchasedPlanConsecutive: user.purchased.plan.consecutive,
+ });
+ expect(res.message).to.equal(t('hourglassPurchaseSet'));
+ });
+});
diff --git a/test/api/v3/integration/user/POST-user_buy_quest.test.js b/test/api/v3/integration/user/POST-user_buy_quest.test.js
new file mode 100644
index 0000000000..3330988537
--- /dev/null
+++ b/test/api/v3/integration/user/POST-user_buy_quest.test.js
@@ -0,0 +1,40 @@
+import {
+ generateUser,
+ translate as t,
+} from '../../../../helpers/api-integration/v3';
+import shared from '../../../../../common/script';
+
+let content = shared.content;
+
+describe('POST /user/buy-quest/:key', () => {
+ let user;
+
+ beforeEach(async () => {
+ user = await generateUser();
+ });
+
+ // More tests in common code unit tests
+
+ it('returns an error if the quest is not found', async () => {
+ await expect(user.post('/user/buy-quest/notExisting'))
+ .to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('questNotFound', {key: 'notExisting'}),
+ });
+ });
+
+ it('buys a quest', async () => {
+ let key = 'dilatoryDistress1';
+ let item = content.quests[key];
+
+ await user.update({'stats.gp': 250});
+ let res = await user.post(`/user/buy-quest/${key}`);
+ await user.sync();
+
+ expect(res.data).to.eql(user.items.quests);
+ expect(res.message).to.equal(t('messageBought', {
+ itemText: item.text(),
+ }));
+ });
+});
diff --git a/test/api/v3/integration/user/POST-user_buy_special_spell.test.js b/test/api/v3/integration/user/POST-user_buy_special_spell.test.js
new file mode 100644
index 0000000000..2ae16d1baf
--- /dev/null
+++ b/test/api/v3/integration/user/POST-user_buy_special_spell.test.js
@@ -0,0 +1,43 @@
+import {
+ generateUser,
+ translate as t,
+} from '../../../../helpers/api-integration/v3';
+import shared from '../../../../../common/script';
+
+let content = shared.content;
+
+describe('POST /user/buy-special-spell/:key', () => {
+ let user;
+
+ beforeEach(async () => {
+ user = await generateUser();
+ });
+
+ // More tests in common code unit tests
+
+ it('returns an error if the special spell is not found', async () => {
+ await expect(user.post('/user/buy-special-spell/notExisting'))
+ .to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('spellNotFound', {spellId: 'notExisting'}),
+ });
+ });
+
+ it('buys a special spell', async () => {
+ let key = 'thankyou';
+ let item = content.special[key];
+
+ await user.update({'stats.gp': 250});
+ let res = await user.post(`/user/buy-special-spell/${key}`);
+ await user.sync();
+
+ expect(res.data).to.eql({
+ items: JSON.parse(JSON.stringify(user.items)), // otherwise dates can't be compared
+ stats: user.stats,
+ });
+ expect(res.message).to.equal(t('messageBought', {
+ itemText: item.text(),
+ }));
+ });
+});
diff --git a/test/api/v3/integration/user/POST-user_change-class.test.js b/test/api/v3/integration/user/POST-user_change-class.test.js
new file mode 100644
index 0000000000..d4b4192f23
--- /dev/null
+++ b/test/api/v3/integration/user/POST-user_change-class.test.js
@@ -0,0 +1,30 @@
+import {
+ generateUser,
+} from '../../../../helpers/api-integration/v3';
+
+describe('POST /user/change-class', () => {
+ let user;
+
+ beforeEach(async () => {
+ user = await generateUser({
+ 'flags.classSelected': false,
+ 'stats.lvl': 10,
+ });
+ });
+
+ // More tests in common code unit tests
+
+ it('changes class', async () => {
+ let res = await user.post('/user/change-class?class=rogue');
+ await user.sync();
+
+ expect(res).to.eql(JSON.parse(
+ JSON.stringify({
+ preferences: user.preferences,
+ stats: user.stats,
+ flags: user.flags,
+ items: user.items,
+ })
+ ));
+ });
+});
diff --git a/test/api/v3/integration/user/POST-user_class_cast_spellId.test.js b/test/api/v3/integration/user/POST-user_class_cast_spellId.test.js
new file mode 100644
index 0000000000..8ada2ede7d
--- /dev/null
+++ b/test/api/v3/integration/user/POST-user_class_cast_spellId.test.js
@@ -0,0 +1,172 @@
+import {
+ generateUser,
+ translate as t,
+ createAndPopulateGroup,
+ generateChallenge,
+ sleep,
+} from '../../../../helpers/api-integration/v3';
+
+import { v4 as generateUUID } from 'uuid';
+
+describe('POST /user/class/cast/:spellId', () => {
+ let user;
+
+ beforeEach(async () => {
+ user = await generateUser();
+ });
+
+ it('returns an error if spell does not exist', async () => {
+ await user.update({'stats.class': 'rogue'});
+ let spellId = 'invalidSpell';
+ await expect(user.post(`/user/class/cast/${spellId}`))
+ .to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('spellNotFound', {spellId}),
+ });
+ });
+
+ it('returns an error if spell does not exist in user\'s class', async () => {
+ let spellId = 'pickPocket';
+ await expect(user.post(`/user/class/cast/${spellId}`))
+ .to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('spellNotFound', {spellId}),
+ });
+ });
+
+ it('returns an error if spell.mana > user.mana', async () => {
+ await user.update({'stats.class': 'rogue'});
+ await expect(user.post('/user/class/cast/backStab'))
+ .to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('notEnoughMana'),
+ });
+ });
+
+ it('returns an error if spell.value > user.gold', async () => {
+ await expect(user.post('/user/class/cast/birthday'))
+ .to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('messageNotEnoughGold'),
+ });
+ });
+
+ it('returns an error if spell.lvl > user.level', async () => {
+ await user.update({'stats.mp': 200, 'stats.class': 'wizard'});
+ await expect(user.post('/user/class/cast/earth'))
+ .to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('spellLevelTooHigh', {level: 13}),
+ });
+ });
+
+ it('returns an error if user doesn\'t own the spell', async () => {
+ await expect(user.post('/user/class/cast/snowball'))
+ .to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('spellNotOwned'),
+ });
+ });
+
+ it('returns an error if targetId is not an UUID', async () => {
+ await expect(user.post('/user/class/cast/spellId?targetId=notAnUUID'))
+ .to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('invalidReqParams'),
+ });
+ });
+
+ it('returns an error if targetId is required but missing', async () => {
+ await user.update({'stats.class': 'rogue', 'stats.lvl': 11});
+ await expect(user.post('/user/class/cast/pickPocket'))
+ .to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('targetIdUUID'),
+ });
+ });
+
+ it('returns an error if targeted task doesn\'t exist', async () => {
+ await user.update({'stats.class': 'rogue', 'stats.lvl': 11});
+ await expect(user.post(`/user/class/cast/pickPocket?targetId=${generateUUID()}`))
+ .to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('taskNotFound'),
+ });
+ });
+
+ it('returns an error if a challenge task was targeted', async () => {
+ let {group, groupLeader} = await createAndPopulateGroup();
+ let challenge = await generateChallenge(groupLeader, group);
+ await groupLeader.post(`/tasks/challenge/${challenge._id}`, [
+ {type: 'habit', text: 'task text'},
+ ]);
+ await groupLeader.update({'stats.class': 'rogue', 'stats.lvl': 11});
+ await sleep(0.5);
+ await groupLeader.sync();
+ await expect(groupLeader.post(`/user/class/cast/pickPocket?targetId=${groupLeader.tasksOrder.habits[0]}`))
+ .to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('challengeTasksNoCast'),
+ });
+ });
+
+ it('returns an error if targeted party member doesn\'t exist', async () => {
+ let {groupLeader} = await createAndPopulateGroup({
+ groupDetails: { type: 'party', privacy: 'private' },
+ members: 1,
+ });
+ await groupLeader.update({'items.special.snowball': 3});
+
+ let target = generateUUID();
+ await expect(groupLeader.post(`/user/class/cast/snowball?targetId=${target}`))
+ .to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('userWithIDNotFound', {userId: target}),
+ });
+ });
+
+ it('returns an error if party does not exists', async () => {
+ await user.update({'items.special.snowball': 3});
+
+ await expect(user.post(`/user/class/cast/snowball?targetId=${generateUUID()}`))
+ .to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('partyNotFound'),
+ });
+ });
+
+ it('send message in party chat if party && !spell.silent', async () => {
+ let { group, groupLeader } = await createAndPopulateGroup({
+ groupDetails: { type: 'party', privacy: 'private' },
+ members: 1,
+ });
+ await groupLeader.update({'stats.mp': 200, 'stats.class': 'wizard', 'stats.lvl': 13});
+ await groupLeader.post('/user/class/cast/earth');
+ await sleep(1);
+ await group.sync();
+ expect(group.chat[0]).to.exists;
+ expect(group.chat[0].uuid).to.equal('system');
+ });
+
+ // TODO find a way to have sinon working in integration tests
+ // it doesn't work when tests are running separately from server
+ it('passes correct target to spell when targetType === \'task\'');
+ it('passes correct target to spell when targetType === \'tasks\'');
+ it('passes correct target to spell when targetType === \'self\'');
+ it('passes correct target to spell when targetType === \'party\'');
+ it('passes correct target to spell when targetType === \'user\'');
+ it('passes correct target to spell when targetType === \'party\' and user is not in a party');
+ it('passes correct target to spell when targetType === \'user\' and user is not in a party');
+});
diff --git a/test/api/v3/integration/user/POST-user_custom-day-start.test.js b/test/api/v3/integration/user/POST-user_custom-day-start.test.js
new file mode 100644
index 0000000000..868b9ae91d
--- /dev/null
+++ b/test/api/v3/integration/user/POST-user_custom-day-start.test.js
@@ -0,0 +1,47 @@
+import moment from 'moment';
+import {
+ generateUser,
+ translate as t,
+} from '../../../../helpers/api-integration/v3';
+
+let user;
+let endpoint = '/user/custom-day-start';
+
+describe('POST /user/custom-day-start', () => {
+ beforeEach(async () => {
+ user = await generateUser();
+ });
+
+ it('updates user.preferences.dayStart', async () => {
+ expect(user.preferences.dayStart).to.eql(0);
+
+ await user.post(endpoint, { dayStart: 1 });
+ await user.sync();
+
+ expect(user.preferences.dayStart).to.eql(1);
+ });
+
+ it('sets lastCron to the current time to prevent an unexpected cron', async () => {
+ let oldCron = moment().subtract(7, 'hours');
+
+ await user.update({lastCron: oldCron});
+ await user.post(endpoint, { dayStart: 1 });
+ await user.sync();
+
+ expect(user.lastCron.valueOf()).to.be.gt(oldCron.valueOf());
+ });
+
+ it('returns a confirmation message', async () => {
+ let {message} = await user.post(endpoint, { dayStart: 1 });
+
+ expect(message).to.eql(t('customDayStartHasChanged'));
+ });
+
+ it('errors if invalid value is passed', async () => {
+ await expect(user.post(endpoint, { dayStart: 'foo' }))
+ .to.eventually.be.rejected;
+
+ await expect(user.post(endpoint, { dayStart: 24}))
+ .to.eventually.be.rejected;
+ });
+});
diff --git a/test/api/v3/integration/user/POST-user_disable-classes.test.js b/test/api/v3/integration/user/POST-user_disable-classes.test.js
new file mode 100644
index 0000000000..0632a8adc7
--- /dev/null
+++ b/test/api/v3/integration/user/POST-user_disable-classes.test.js
@@ -0,0 +1,26 @@
+import {
+ generateUser,
+} from '../../../../helpers/api-integration/v3';
+
+describe('POST /user/disable-classes', () => {
+ let user;
+
+ beforeEach(async () => {
+ user = await generateUser();
+ });
+
+ // More tests in common code unit tests
+
+ it('disable classes', async () => {
+ let res = await user.post('/user/disable-classes');
+ await user.sync();
+
+ expect(res).to.eql(JSON.parse(
+ JSON.stringify({
+ preferences: user.preferences,
+ stats: user.stats,
+ flags: user.flags,
+ })
+ ));
+ });
+});
diff --git a/test/api/v3/integration/user/POST-user_equip_type_key.test.js b/test/api/v3/integration/user/POST-user_equip_type_key.test.js
new file mode 100644
index 0000000000..c5cde777df
--- /dev/null
+++ b/test/api/v3/integration/user/POST-user_equip_type_key.test.js
@@ -0,0 +1,40 @@
+/* eslint-disable camelcase */
+
+import {
+ generateUser,
+} from '../../../../helpers/api-integration/v3';
+
+describe('POST /user/equip/:type/:key', () => {
+ let user;
+
+ beforeEach(async () => {
+ user = await generateUser();
+ });
+
+ // More tests in common code unit tests
+
+ it('equip an item', async () => {
+ await user.update({
+ 'items.gear.owned': {
+ weapon_warrior_0: true,
+ weapon_warrior_1: true,
+ weapon_warrior_2: true,
+ weapon_wizard_1: true,
+ weapon_wizard_2: true,
+ shield_base_0: true,
+ shield_warrior_1: true,
+ },
+ 'items.gear.equipped': {
+ weapon: 'weapon_warrior_0',
+ shield: 'shield_base_0',
+ },
+ 'stats.gp': 200,
+ });
+
+ await user.post('/user/equip/equipped/weapon_warrior_1');
+ let res = await user.post('/user/equip/equipped/weapon_warrior_2');
+ await user.sync();
+
+ expect(res).to.eql(JSON.parse(JSON.stringify(user.items)));
+ });
+});
diff --git a/test/api/v3/integration/user/POST-user_feed_pet_food.test.js b/test/api/v3/integration/user/POST-user_feed_pet_food.test.js
new file mode 100644
index 0000000000..7581c266ee
--- /dev/null
+++ b/test/api/v3/integration/user/POST-user_feed_pet_food.test.js
@@ -0,0 +1,45 @@
+/* eslint-disable camelcase */
+
+import {
+ generateUser,
+ translate as t,
+} from '../../../../helpers/api-integration/v3';
+import content from '../../../../../common/script/content';
+
+describe('POST /user/feed/:pet/:food', () => {
+ let user;
+
+ beforeEach(async () => {
+ user = await generateUser();
+ });
+
+ // More tests in common code unit tests
+
+ it('does not enjoy the food', async () => {
+ await user.update({
+ 'items.pets.Wolf-Base': 5,
+ 'items.food.Milk': 2,
+ });
+
+ let food = content.food.Milk;
+ let [egg, potion] = 'Wolf-Base'.split('-');
+ let potionText = content.hatchingPotions[potion] ? content.hatchingPotions[potion].text() : potion;
+ let eggText = content.eggs[egg] ? content.eggs[egg].text() : egg;
+
+ let res = await user.post('/user/feed/Wolf-Base/Milk');
+ await user.sync();
+ expect(res).to.eql({
+ data: user.items.pets['Wolf-Base'],
+ message: t('messageDontEnjoyFood', {
+ egg: t('petName', {
+ potion: potionText,
+ egg: eggText,
+ }),
+ foodText: food.text(),
+ }),
+ });
+
+ expect(user.items.food.Milk).to.equal(1);
+ expect(user.items.pets['Wolf-Base']).to.equal(7);
+ });
+});
diff --git a/test/api/v3/integration/user/POST-user_hatch_egg_hatchingPotion.test.js b/test/api/v3/integration/user/POST-user_hatch_egg_hatchingPotion.test.js
new file mode 100644
index 0000000000..9621377beb
--- /dev/null
+++ b/test/api/v3/integration/user/POST-user_hatch_egg_hatchingPotion.test.js
@@ -0,0 +1,31 @@
+import {
+ generateUser,
+ translate as t,
+} from '../../../../helpers/api-integration/v3';
+
+describe('POST /user/hatch/:egg/:hatchingPotion', () => {
+ let user;
+
+ beforeEach(async () => {
+ user = await generateUser();
+ });
+
+ // More tests in common code unit tests
+
+ it('hatch a new pet', async () => {
+ await user.update({
+ 'items.eggs.Wolf': 1,
+ 'items.hatchingPotions.Base': 1,
+ });
+ let res = await user.post('/user/hatch/Wolf/Base');
+ await user.sync();
+ expect(user.items.pets['Wolf-Base']).to.equal(5);
+ expect(user.items.eggs.Wolf).to.equal(0);
+ expect(user.items.hatchingPotions.Base).to.equal(0);
+
+ expect(res).to.eql({
+ message: t('messageHatched'),
+ data: JSON.parse(JSON.stringify(user.items)),
+ });
+ });
+});
diff --git a/test/api/v3/integration/user/POST-user_mark_pms_read.test.js b/test/api/v3/integration/user/POST-user_mark_pms_read.test.js
new file mode 100644
index 0000000000..50552359ef
--- /dev/null
+++ b/test/api/v3/integration/user/POST-user_mark_pms_read.test.js
@@ -0,0 +1,22 @@
+import {
+ generateUser,
+} from '../../../../helpers/api-integration/v3';
+
+describe('POST /user/mark-pms-read', () => {
+ let user;
+
+ beforeEach(async () => {
+ user = await generateUser();
+ });
+
+ // More tests in common code unit tests
+
+ it('marks user\'s private messages as read', async () => {
+ await user.update({
+ 'inbox.newMessages': 1,
+ });
+ await user.post('/user/mark-pms-read');
+ await user.sync();
+ expect(user.inbox.newMessages).to.equal(0);
+ });
+});
diff --git a/test/api/v3/integration/user/POST-user_open_mystery_item.test.js b/test/api/v3/integration/user/POST-user_open_mystery_item.test.js
new file mode 100644
index 0000000000..6cfc0fe254
--- /dev/null
+++ b/test/api/v3/integration/user/POST-user_open_mystery_item.test.js
@@ -0,0 +1,31 @@
+import {
+ generateUser,
+ translate as t,
+} from '../../../../helpers/api-integration/v3';
+import content from '../../../../../common/script/content/index';
+
+describe('POST /user/open-mystery-item', () => {
+ let user;
+ let mysteryItemKey = 'eyewear_special_summerRogue';
+ let mysteryItemIndex = content.gear.flat[mysteryItemKey].index;
+ let mysteryItemType = content.gear.flat[mysteryItemKey].type;
+
+ beforeEach(async () => {
+ user = await generateUser({
+ 'purchased.plan.mysteryItems': [mysteryItemKey],
+ });
+ });
+
+ // More tests in common code unit tests
+
+ it('opens a mystery item', async () => {
+ let response = await user.post('/user/open-mystery-item');
+ await user.sync();
+
+ expect(user.items.gear.owned[mysteryItemKey]).to.be.true;
+ expect(response.message).to.equal(t('mysteryItemOpened'));
+ expect(response.data.key).to.eql(mysteryItemKey);
+ expect(response.data.index).to.eql(mysteryItemIndex);
+ expect(response.data.type).to.eql(mysteryItemType);
+ });
+});
diff --git a/test/api/v3/integration/user/POST-user_purchase.test.js b/test/api/v3/integration/user/POST-user_purchase.test.js
new file mode 100644
index 0000000000..dff6d59c48
--- /dev/null
+++ b/test/api/v3/integration/user/POST-user_purchase.test.js
@@ -0,0 +1,34 @@
+import {
+ generateUser,
+ translate as t,
+} from '../../../../helpers/api-integration/v3';
+
+describe('POST /user/purchase/:type/:key', () => {
+ let user;
+ let type = 'hatchingPotions';
+ let key = 'Base';
+
+ beforeEach(async () => {
+ user = await generateUser({
+ balance: 40,
+ });
+ });
+
+ // More tests in common code unit tests
+
+ it('returns an error when key is not provided', async () => {
+ await expect(user.post('/user/purchase/gems/gem'))
+ .to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('mustSubscribeToPurchaseGems'),
+ });
+ });
+
+ it('purchases a gem item', async () => {
+ await user.post(`/user/purchase/${type}/${key}`);
+ await user.sync();
+
+ expect(user.items[type][key]).to.equal(1);
+ });
+});
diff --git a/test/api/v3/integration/user/POST-user_purchase_hourglass.test.js b/test/api/v3/integration/user/POST-user_purchase_hourglass.test.js
new file mode 100644
index 0000000000..cd43334d00
--- /dev/null
+++ b/test/api/v3/integration/user/POST-user_purchase_hourglass.test.js
@@ -0,0 +1,25 @@
+import {
+ generateUser,
+ translate as t,
+} from '../../../../helpers/api-integration/v3';
+
+describe('POST /user/purchase-hourglass/:type/:key', () => {
+ let user;
+
+ beforeEach(async () => {
+ user = await generateUser({
+ 'purchased.plan.consecutive.trinkets': 2,
+ });
+ });
+
+ // More tests in common code unit tests
+
+ it('buys a hourglass pet', async () => {
+ let response = await user.post('/user/purchase-hourglass/pets/MantisShrimp-Base');
+ await user.sync();
+
+ expect(response.message).to.eql(t('hourglassPurchase'));
+ expect(user.purchased.plan.consecutive.trinkets).to.eql(1);
+ expect(user.items.pets).to.eql({'MantisShrimp-Base': 5});
+ });
+});
diff --git a/test/api/v3/integration/user/POST-user_read_card.test.js b/test/api/v3/integration/user/POST-user_read_card.test.js
new file mode 100644
index 0000000000..3b3573b6cc
--- /dev/null
+++ b/test/api/v3/integration/user/POST-user_read_card.test.js
@@ -0,0 +1,38 @@
+import {
+ generateUser,
+ translate as t,
+} from '../../../../helpers/api-integration/v3';
+
+describe('POST /user/read-card/:cardType', () => {
+ let user;
+ let cardType = 'greeting';
+
+ beforeEach(async () => {
+ user = await generateUser();
+ });
+
+ it('returns an error when unknown cardType is provded', async () => {
+ await expect(user.post('/user/read-card/randomCardType'))
+ .to.eventually.be.rejected.and.to.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('cardTypeNotAllowed'),
+ });
+ });
+
+ // More tests in common code unit tests
+
+ it('reads a card', async () => {
+ await user.update({
+ 'items.special.greetingReceived': [true],
+ 'flags.cardReceived': true,
+ });
+
+ let response = await user.post(`/user/read-card/${cardType}`);
+ await user.sync();
+
+ expect(response.message).to.equal(t('readCard', {cardType}));
+ expect(user.items.special[`${cardType}Received`]).to.be.empty;
+ expect(user.flags.cardReceived).to.be.false;
+ });
+});
diff --git a/test/api/v3/integration/user/POST-user_rebirth.test.js b/test/api/v3/integration/user/POST-user_rebirth.test.js
new file mode 100644
index 0000000000..21fbed0b8d
--- /dev/null
+++ b/test/api/v3/integration/user/POST-user_rebirth.test.js
@@ -0,0 +1,57 @@
+import {
+ generateUser,
+ generateDaily,
+ generateReward,
+ translate as t,
+} from '../../../../helpers/api-integration/v3';
+
+describe('POST /user/rebirth', () => {
+ let user;
+
+ beforeEach(async () => {
+ user = await generateUser();
+ });
+
+ it('returns an error when user balance is too low', async () => {
+ await expect(user.post('/user/rebirth'))
+ .to.eventually.be.rejected.and.to.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('notEnoughGems'),
+ });
+ });
+
+ // More tests in common code unit tests
+
+ it('resets user\'s tasks', async () => {
+ await user.update({
+ balance: 2,
+ });
+
+ let daily = await generateDaily({
+ text: 'test habit',
+ type: 'daily',
+ value: 1,
+ streak: 1,
+ userId: user._id,
+ });
+
+ let reward = await generateReward({
+ text: 'test reward',
+ type: 'reward',
+ value: 1,
+ userId: user._id,
+ });
+
+ let response = await user.post('/user/rebirth');
+ await user.sync();
+
+ let updatedDaily = await user.get(`/tasks/${daily._id}`);
+ let updatedReward = await user.get(`/tasks/${reward._id}`);
+
+ expect(response.message).to.equal(t('rebirthComplete'));
+ expect(updatedDaily.streak).to.equal(0);
+ expect(updatedDaily.value).to.equal(0);
+ expect(updatedReward.value).to.equal(1);
+ });
+});
diff --git a/test/api/v3/integration/user/POST-user_release_both.test.js b/test/api/v3/integration/user/POST-user_release_both.test.js
new file mode 100644
index 0000000000..8c47d95dfe
--- /dev/null
+++ b/test/api/v3/integration/user/POST-user_release_both.test.js
@@ -0,0 +1,48 @@
+import {
+ generateUser,
+ translate as t,
+} from '../../../../helpers/api-integration/v3';
+
+describe('POST /user/release-both', () => {
+ let user;
+ let animal = 'Wolf-Base';
+
+ beforeEach(async () => {
+ user = await generateUser({
+ 'items.currentMount': animal,
+ 'items.currentPet': animal,
+ 'items.pets': {animal: 5},
+ 'items.mounts': {animal: true},
+ });
+ });
+
+ it('returns an error when user balance is too low and user does not have triadBingo', async () => {
+ await expect(user.post('/user/release-both'))
+ .to.eventually.be.rejected.and.to.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('notEnoughGems'),
+ });
+ });
+
+ // More tests in common code unit tests
+
+ it('grants triad bingo with gems', async () => {
+ await user.update({
+ balance: 1.5,
+ });
+
+ let response = await user.post('/user/release-both');
+ await user.sync();
+
+ expect(response.message).to.equal(t('mountsAndPetsReleased'));
+ expect(user.balance).to.equal(0);
+ expect(user.items.currentMount).to.be.empty;
+ expect(user.items.currentPet).to.be.empty;
+ expect(user.items.pets[animal]).to.be.empty;
+ expect(user.items.mounts[animal]).to.equal(null);
+ expect(user.achievements.beastMasterCount).to.equal(1);
+ expect(user.achievements.mountMasterCount).to.equal(1);
+ expect(user.achievements.triadBingoCount).to.equal(1);
+ });
+});
diff --git a/test/api/v3/integration/user/POST-user_release_mounts.test.js b/test/api/v3/integration/user/POST-user_release_mounts.test.js
new file mode 100644
index 0000000000..86391599f0
--- /dev/null
+++ b/test/api/v3/integration/user/POST-user_release_mounts.test.js
@@ -0,0 +1,42 @@
+import {
+ generateUser,
+ translate as t,
+} from '../../../../helpers/api-integration/v3';
+
+describe('POST /user/release-mounts', () => {
+ let user;
+ let animal = 'Wolf-Base';
+
+ beforeEach(async () => {
+ user = await generateUser({
+ 'items.currentMount': animal,
+ 'items.mounts': {animal: true},
+ });
+ });
+
+ it('returns an error when user balance is too low', async () => {
+ await expect(user.post('/user/release-mounts'))
+ .to.eventually.be.rejected.and.to.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('notEnoughGems'),
+ });
+ });
+
+ // More tests in common code unit tests
+
+ it('releases mounts', async () => {
+ await user.update({
+ balance: 1,
+ });
+
+ let response = await user.post('/user/release-mounts');
+ await user.sync();
+
+ expect(response.message).to.equal(t('mountsReleased'));
+ expect(user.balance).to.equal(0);
+ expect(user.items.currentMount).to.be.empty;
+ expect(user.items.mounts[animal]).to.equal(null);
+ expect(user.achievements.mountMasterCount).to.equal(1);
+ });
+});
diff --git a/test/api/v3/integration/user/POST-user_release_pets.test.js b/test/api/v3/integration/user/POST-user_release_pets.test.js
new file mode 100644
index 0000000000..a7f7b9b66f
--- /dev/null
+++ b/test/api/v3/integration/user/POST-user_release_pets.test.js
@@ -0,0 +1,42 @@
+import {
+ generateUser,
+ translate as t,
+} from '../../../../helpers/api-integration/v3';
+
+describe('POST /user/release-pets', () => {
+ let user;
+ let animal = 'Wolf-Base';
+
+ beforeEach(async () => {
+ user = await generateUser({
+ 'items.currentPet': animal,
+ 'items.pets': {animal: 5},
+ });
+ });
+
+ it('returns an error when user balance is too low', async () => {
+ await expect(user.post('/user/release-pets'))
+ .to.eventually.be.rejected.and.to.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('notEnoughGems'),
+ });
+ });
+
+ // More tests in common code unit tests
+
+ it('releases pets', async () => {
+ await user.update({
+ balance: 1,
+ });
+
+ let response = await user.post('/user/release-pets');
+ await user.sync();
+
+ expect(response.message).to.equal(t('petsReleased'));
+ expect(user.balance).to.equal(0);
+ expect(user.items.currentPet).to.be.empty;
+ expect(user.items.pets[animal]).to.equal(0);
+ expect(user.achievements.beastMasterCount).to.equal(1);
+ });
+});
diff --git a/test/api/v3/integration/user/POST-user_reroll.test.js b/test/api/v3/integration/user/POST-user_reroll.test.js
new file mode 100644
index 0000000000..29774d1239
--- /dev/null
+++ b/test/api/v3/integration/user/POST-user_reroll.test.js
@@ -0,0 +1,54 @@
+import {
+ generateUser,
+ generateDaily,
+ generateReward,
+ translate as t,
+} from '../../../../helpers/api-integration/v3';
+
+describe('POST /user/reroll', () => {
+ let user;
+
+ beforeEach(async () => {
+ user = await generateUser();
+ });
+
+ it('returns an error when user balance is too low', async () => {
+ await expect(user.post('/user/reroll'))
+ .to.eventually.be.rejected.and.to.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('notEnoughGems'),
+ });
+ });
+
+ // More tests in common code unit tests
+
+ it('resets user\'s tasks', async () => {
+ await user.update({
+ balance: 2,
+ });
+
+ let daily = await generateDaily({
+ text: 'test habit',
+ type: 'daily',
+ userId: user._id,
+ });
+
+ let reward = await generateReward({
+ text: 'test reward',
+ type: 'reward',
+ value: 1,
+ userId: user._id,
+ });
+
+ let response = await user.post('/user/reroll');
+ await user.sync();
+
+ let updatedDaily = await user.get(`/tasks/${daily._id}`);
+ let updatedReward = await user.get(`/tasks/${reward._id}`);
+
+ expect(response.message).to.equal(t('fortifyComplete'));
+ expect(updatedDaily.value).to.equal(0);
+ expect(updatedReward.value).to.equal(1);
+ });
+});
diff --git a/test/api/v3/integration/user/POST-user_reset.test.js b/test/api/v3/integration/user/POST-user_reset.test.js
new file mode 100644
index 0000000000..2baf7bd083
--- /dev/null
+++ b/test/api/v3/integration/user/POST-user_reset.test.js
@@ -0,0 +1,104 @@
+import {
+ generateUser,
+ generateGroup,
+ generateChallenge,
+ translate as t,
+} from '../../../../helpers/api-integration/v3';
+
+describe('POST /user/reset', () => {
+ let user;
+
+ beforeEach(async () => {
+ user = await generateUser();
+ });
+
+ // More tests in common code unit tests
+
+ it('resets user\'s habits', async () => {
+ let task = await user.post('/tasks/user', {
+ text: 'test habit',
+ type: 'habit',
+ });
+
+ await user.post('/user/reset');
+ await user.sync();
+
+ await expect(user.get(`/tasks/${task._id}`)).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('taskNotFound'),
+ });
+
+ expect(user.tasksOrder.habits).to.be.empty;
+ });
+
+ it('resets user\'s dailys', async () => {
+ let task = await user.post('/tasks/user', {
+ text: 'test daily',
+ type: 'daily',
+ });
+
+ await user.post('/user/reset');
+ await user.sync();
+
+ await expect(user.get(`/tasks/${task._id}`)).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('taskNotFound'),
+ });
+
+ expect(user.tasksOrder.dailys).to.be.empty;
+ });
+
+ it('resets user\'s todos', async () => {
+ let task = await user.post('/tasks/user', {
+ text: 'test todo',
+ type: 'todo',
+ });
+
+ await user.post('/user/reset');
+ await user.sync();
+
+ await expect(user.get(`/tasks/${task._id}`)).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('taskNotFound'),
+ });
+
+ expect(user.tasksOrder.todos).to.be.empty;
+ });
+
+ it('resets user\'s rewards', async () => {
+ let task = await user.post('/tasks/user', {
+ text: 'test reward',
+ type: 'reward',
+ });
+
+ await user.post('/user/reset');
+ await user.sync();
+
+ await expect(user.get(`/tasks/${task._id}`)).to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('taskNotFound'),
+ });
+
+ expect(user.tasksOrder.rewards).to.be.empty;
+ });
+
+ it('does not delete challenge tasks', async () => {
+ let guild = await generateGroup(user);
+ let challenge = await generateChallenge(user, guild);
+ let task = await user.post(`/tasks/challenge/${challenge._id}`, {
+ text: 'test challenge habit',
+ type: 'habit',
+ });
+
+ await user.post('/user/reset');
+ await user.sync();
+
+ let userChallengeTask = await user.get(`/tasks/${task._id}`);
+
+ expect(userChallengeTask).to.eql(task);
+ });
+});
diff --git a/test/api/v3/integration/user/POST-user_revive.test.js b/test/api/v3/integration/user/POST-user_revive.test.js
new file mode 100644
index 0000000000..6ba85ac87f
--- /dev/null
+++ b/test/api/v3/integration/user/POST-user_revive.test.js
@@ -0,0 +1,37 @@
+import {
+ generateUser,
+ translate as t,
+} from '../../../../helpers/api-integration/v3';
+
+describe('POST /user/revive', () => {
+ let user;
+
+ beforeEach(async () => {
+ user = await generateUser({
+ 'user.items.gear.owned': {weaponKey: true},
+ });
+ });
+
+ it('returns an error when user is not dead', async () => {
+ await expect(user.post('/user/revive'))
+ .to.eventually.be.rejected.and.to.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('cannotRevive'),
+ });
+ });
+
+ // More tests in common code unit tests
+
+ it('decreases a stat', async () => {
+ await user.update({
+ 'stats.str': 2,
+ 'stats.hp': 0,
+ });
+
+ await user.post('/user/revive');
+ await user.sync();
+
+ expect(user.stats.str).to.equal(1);
+ });
+});
diff --git a/test/api/v3/integration/user/POST-user_sell.test.js b/test/api/v3/integration/user/POST-user_sell.test.js
new file mode 100644
index 0000000000..1914336175
--- /dev/null
+++ b/test/api/v3/integration/user/POST-user_sell.test.js
@@ -0,0 +1,41 @@
+import {
+ generateUser,
+ translate as t,
+} from '../../../../helpers/api-integration/v3';
+import content from '../../../../../common/script/content';
+
+describe('POST /user/sell/:type/:key', () => {
+ let user;
+ let type = 'eggs';
+ let key = 'Wolf';
+
+ beforeEach(async () => {
+ user = await generateUser();
+ });
+
+ // More tests in common code unit tests
+
+ it('returns an error when user does not have item', async () => {
+ await expect(user.post(`/user/sell/${type}/${key}`))
+ .to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('userItemsKeyNotFound', {type}),
+ });
+ });
+
+ it('sells an item', async () => {
+ await user.update({
+ items: {
+ eggs: {
+ Wolf: 1,
+ },
+ },
+ });
+
+ await user.post(`/user/sell/${type}/${key}`);
+ await user.sync();
+
+ expect(user.stats.gp).to.equal(content[type][key].value);
+ });
+});
diff --git a/test/api/v3/integration/user/POST-user_sleep.test.js b/test/api/v3/integration/user/POST-user_sleep.test.js
new file mode 100644
index 0000000000..0e9773150e
--- /dev/null
+++ b/test/api/v3/integration/user/POST-user_sleep.test.js
@@ -0,0 +1,25 @@
+import {
+ generateUser,
+} from '../../../../helpers/api-integration/v3';
+
+describe('POST /user/sleep', () => {
+ let user;
+
+ beforeEach(async () => {
+ user = await generateUser();
+ });
+
+ // More tests in common code unit tests
+
+ it('toggles sleep status', async () => {
+ let res = await user.post('/user/sleep');
+ expect(res).to.eql(true);
+ await user.sync();
+ expect(user.preferences.sleep).to.be.true;
+
+ let res2 = await user.post('/user/sleep');
+ expect(res2).to.eql(false);
+ await user.sync();
+ expect(user.preferences.sleep).to.be.false;
+ });
+});
diff --git a/test/api/v3/integration/user/POST-user_unlock.js b/test/api/v3/integration/user/POST-user_unlock.js
new file mode 100644
index 0000000000..6dbdb3c1b1
--- /dev/null
+++ b/test/api/v3/integration/user/POST-user_unlock.js
@@ -0,0 +1,37 @@
+import {
+ generateUser,
+ translate as t,
+} from '../../../../helpers/api-integration/v3';
+
+describe('POST /user/unlock', () => {
+ let user;
+ let unlockPath = 'shirt.convict,shirt.cross,shirt.fire,shirt.horizon,shirt.ocean,shirt.purple,shirt.rainbow,shirt.redblue,shirt.thunder,shirt.tropical,shirt.zombie';
+ let unlockCost = 1.25;
+ let usersStartingGems = 5;
+
+ beforeEach(async () => {
+ user = await generateUser();
+ });
+
+ it('returns an error when user balance is too low', async () => {
+ await expect(user.post(`/user/unlock?path=${unlockPath}`))
+ .to.eventually.be.rejected.and.to.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('notEnoughGems'),
+ });
+ });
+
+ // More tests in common code unit tests
+
+ it('reduces a user\'s balance', async () => {
+ await user.update({
+ balance: usersStartingGems,
+ });
+ let response = await user.post(`/user/unlock?path=${unlockPath}`);
+ await user.sync();
+
+ expect(response.message).to.equal(t('unlocked'));
+ expect(user.balance).to.equal(usersStartingGems - unlockCost);
+ });
+});
diff --git a/test/api/v3/integration/user/PUT-user.test.js b/test/api/v3/integration/user/PUT-user.test.js
new file mode 100644
index 0000000000..f606c95c2c
--- /dev/null
+++ b/test/api/v3/integration/user/PUT-user.test.js
@@ -0,0 +1,201 @@
+import {
+ generateUser,
+ translate as t,
+} from '../../../../helpers/api-integration/v3';
+
+import { each, get } from 'lodash';
+
+describe('PUT /user', () => {
+ let user;
+
+ beforeEach(async () => {
+ user = await generateUser();
+ });
+
+ context('Allowed Operations', () => {
+ it('updates the user', async () => {
+ await user.put('/user', {
+ 'profile.name': 'Frodo',
+ 'preferences.costume': true,
+ 'stats.hp': 14,
+ });
+
+ await user.sync();
+
+ expect(user.profile.name).to.eql('Frodo');
+ expect(user.preferences.costume).to.eql(true);
+ expect(user.stats.hp).to.eql(14);
+ });
+ });
+
+ context('Top Level Protected Operations', () => {
+ let protectedOperations = {
+ 'gem balance': {balance: 100},
+ auth: {'auth.blocked': true, 'auth.timestamps.created': new Date()},
+ contributor: {'contributor.level': 9, 'contributor.admin': true, 'contributor.text': 'some text'},
+ backer: {'backer.tier': 10, 'backer.npc': 'Bilbo'},
+ subscriptions: {'purchased.plan.extraMonths': 500, 'purchased.plan.consecutive.trinkets': 1000},
+ 'customization gem purchases': {'purchased.background.tavern': true, 'purchased.skin.bear': true},
+ };
+
+ each(protectedOperations, (data, testName) => {
+ it(`does not allow updating ${testName}`, async () => {
+ let errorText = t('messageUserOperationProtected', { operation: Object.keys(data)[0] });
+
+ await expect(user.put('/user', data)).to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: errorText,
+ });
+ });
+ });
+ });
+
+ context('Sub-Level Protected Operations', () => {
+ let protectedOperations = {
+ 'class stat': {'stats.class': 'wizard'},
+ 'flags unless whitelisted': {'flags.dropsEnabled': true},
+ webhooks: {'preferences.webhooks': [1, 2, 3]},
+ sleep: {'preferences.sleep': true},
+ 'disable classes': {'preferences.disableClasses': true},
+ };
+
+ each(protectedOperations, (data, testName) => {
+ it(`does not allow updating ${testName}`, async () => {
+ let errorText = t('messageUserOperationProtected', { operation: Object.keys(data)[0] });
+
+ await expect(user.put('/user', data)).to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: errorText,
+ });
+ });
+ });
+ });
+
+ context('Default Appearance Preferences', () => {
+ let testCases = {
+ shirt: 'yellow',
+ skin: 'ddc994',
+ 'hair.color': 'blond',
+ 'hair.bangs': 2,
+ 'hair.base': 1,
+ 'hair.flower': 4,
+ size: 'broad',
+ };
+
+ each(testCases, (item, type) => {
+ const update = {};
+ update[`preferences.${type}`] = item;
+
+ it(`updates user with ${type} that is a default`, async () => {
+ let dbUpdate = {};
+ dbUpdate[`purchased.${type}.${item}`] = true;
+ await user.update(dbUpdate);
+
+ // Sanity checks to make sure user is not already equipped with item
+ expect(get(user.preferences, type)).to.not.eql(item);
+
+ let updatedUser = await user.put('/user', update);
+
+ expect(get(updatedUser.preferences, type)).to.eql(item);
+ });
+ });
+
+ it('returns an error if user tries to update body size with invalid type', async () => {
+ await expect(user.put('/user', {
+ 'preferences.size': 'round',
+ })).to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('mustPurchaseToSet', { val: 'round', key: 'preferences.size' }),
+ });
+ });
+
+ it('can set beard to default', async () => {
+ await user.update({
+ 'purchased.hair.beard': 3,
+ 'preferences.hair.beard': 3,
+ });
+
+ let updatedUser = await user.put('/user', {
+ 'preferences.hair.beard': 0,
+ });
+
+ expect(updatedUser.preferences.hair.beard).to.eql(0);
+ });
+
+ it('can set mustache to default', async () => {
+ await user.update({
+ 'purchased.hair.mustache': 2,
+ 'preferences.hair.mustache': 2,
+ });
+
+ let updatedUser = await user.put('/user', {
+ 'preferences.hair.mustache': 0,
+ });
+
+ expect(updatedUser.preferences.hair.mustache).to.eql(0);
+ });
+ });
+
+ context('Purchasable Appearance Preferences', () => {
+ let testCases = {
+ background: 'volcano',
+ shirt: 'convict',
+ skin: 'cactus',
+ 'hair.base': 7,
+ 'hair.beard': 2,
+ 'hair.color': 'rainbow',
+ 'hair.mustache': 2,
+ };
+
+ each(testCases, (item, type) => {
+ const update = {};
+ update[`preferences.${type}`] = item;
+
+ it(`returns an error if user tries to update ${type} with ${type} the user does not own`, async () => {
+ await expect(user.put('/user', update)).to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('mustPurchaseToSet', {val: item, key: `preferences.${type}`}),
+ });
+ });
+
+ it(`updates user with ${type} user does own`, async () => {
+ let dbUpdate = {};
+ dbUpdate[`purchased.${type}.${item}`] = true;
+ await user.update(dbUpdate);
+
+ // Sanity check to make sure user is not already equipped with item
+ expect(get(user.preferences, type)).to.not.eql(item);
+
+ let updatedUser = await user.put('/user', update);
+
+ expect(get(updatedUser.preferences, type)).to.eql(item);
+ });
+ });
+ });
+
+ context('Improvement Categories', () => {
+ it('sets valid categories', async () => {
+ await user.put('/user', {
+ 'preferences.improvementCategories': ['work', 'school'],
+ });
+
+ await user.sync();
+
+ expect(user.preferences.improvementCategories).to.eql(['work', 'school']);
+ });
+
+ it('discards invalid categories', async () => {
+ await expect(user.put('/user', {
+ 'preferences.improvementCategories': ['work', 'procrastination', 'school'],
+ })).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: 'User validation failed',
+ });
+ });
+ });
+});
diff --git a/test/api/v3/integration/user/PUT-user_update_webhook.test.js b/test/api/v3/integration/user/PUT-user_update_webhook.test.js
new file mode 100644
index 0000000000..13ca9ff00c
--- /dev/null
+++ b/test/api/v3/integration/user/PUT-user_update_webhook.test.js
@@ -0,0 +1,32 @@
+import {
+ generateUser,
+ translate as t,
+} from '../../../../helpers/api-integration/v3';
+
+let user;
+let url = 'http://new-url.com';
+let enabled = true;
+
+describe('PUT /user/webhook/:id', () => {
+ beforeEach(async () => {
+ user = await generateUser();
+ });
+
+ it('validation fails', async () => {
+ await expect(user.put('/user/webhook/some-id'), { enabled: true }).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('invalidUrl'),
+ });
+ });
+
+ it('succeeds', async () => {
+ let response = await user.post('/user/webhook', { enabled: true, url: 'http://some-url.com'});
+ await user.sync();
+ expect(user.preferences.webhooks[response.id].url).to.not.eql(url);
+ let response2 = await user.put(`/user/webhook/${response.id}`, {url, enabled});
+ expect(response2.url).to.eql(url);
+ await user.sync();
+ expect(user.preferences.webhooks[response.id].url).to.eql(url);
+ });
+});
diff --git a/test/api/v3/integration/user/auth/DELETE-user_auth_social_network.test.js b/test/api/v3/integration/user/auth/DELETE-user_auth_social_network.test.js
new file mode 100644
index 0000000000..cf1354b095
--- /dev/null
+++ b/test/api/v3/integration/user/auth/DELETE-user_auth_social_network.test.js
@@ -0,0 +1,40 @@
+import {
+ generateUser,
+ translate as t,
+} from '../../../../../helpers/api-integration/v3';
+
+describe('DELETE social registration', () => {
+ let user;
+ let endpoint = '/user/auth/social/facebook';
+ beforeEach(async () => {
+ user = await generateUser();
+ await user.update({ 'auth.facebook.id': 'some-fb-id' });
+ expect(user.auth.local.username).to.not.be.empty;
+ expect(user.auth.facebook).to.not.be.empty;
+ });
+ context('of NOT-FACEBOOK', () => {
+ it('is not supported', async () => {
+ await expect(user.del('/user/auth/social/SOME-OTHER-NETWORK')).to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('onlyFbSupported'),
+ });
+ });
+ });
+ context('of facebook', () => {
+ it('fails if local registration does not exist for this user', async () => {
+ await user.update({ 'auth.local': { ok: true } });
+ await expect(user.del(endpoint)).to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('cantDetachFb'),
+ });
+ });
+ it('succeeds', async () => {
+ let response = await user.del(endpoint);
+ expect(response).to.eql({});
+ await user.sync();
+ expect(user.auth.facebook).to.be.empty;
+ });
+ });
+});
diff --git a/test/api/v3/integration/user/auth/GET-logout.test.js b/test/api/v3/integration/user/auth/GET-logout.test.js
new file mode 100644
index 0000000000..731c523bde
--- /dev/null
+++ b/test/api/v3/integration/user/auth/GET-logout.test.js
@@ -0,0 +1,3 @@
+describe('GET /user/auth/logout', () => {
+ // TODO Test manually
+});
diff --git a/test/api/v3/integration/user/auth/POST-firebase.test.js b/test/api/v3/integration/user/auth/POST-firebase.test.js
new file mode 100644
index 0000000000..7ebd5a20cb
--- /dev/null
+++ b/test/api/v3/integration/user/auth/POST-firebase.test.js
@@ -0,0 +1,18 @@
+import {
+ generateUser,
+} from '../../../../../helpers/api-integration/v3';
+import moment from 'moment';
+
+describe('POST /user/auth/firebase', () => {
+ let user;
+
+ before(async () => {
+ user = await generateUser();
+ });
+
+ it('returns a Firebase token', async () => {
+ let {token, expires} = await user.post('/user/auth/firebase');
+ expect(moment(expires).isValid()).to.be.true;
+ expect(token).to.be.a('string');
+ });
+});
diff --git a/test/api/v3/integration/user/auth/POST-login-local.test.js b/test/api/v3/integration/user/auth/POST-login-local.test.js
new file mode 100644
index 0000000000..571b23c3ea
--- /dev/null
+++ b/test/api/v3/integration/user/auth/POST-login-local.test.js
@@ -0,0 +1,69 @@
+import {
+ generateUser,
+ requester,
+ translate as t,
+} from '../../../../../helpers/api-integration/v3';
+
+describe('POST /user/auth/local/login', () => {
+ let api;
+ let user;
+ let endpoint = '/user/auth/local/login';
+ let password = 'password';
+ beforeEach(async () => {
+ api = requester();
+ user = await generateUser();
+ });
+ it('success with username', async () => {
+ let response = await api.post(endpoint, {
+ username: user.auth.local.username,
+ password,
+ });
+ expect(response.apiToken).to.eql(user.apiToken);
+ });
+ it('success with email', async () => {
+ let response = await api.post(endpoint, {
+ username: user.auth.local.email,
+ password,
+ });
+ expect(response.apiToken).to.eql(user.apiToken);
+ });
+ it('user is blocked', async () => {
+ await user.update({ 'auth.blocked': 1 });
+ await expect(api.post(endpoint, {
+ username: user.auth.local.username,
+ password,
+ })).to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('accountSuspended', { userId: user._id }),
+ });
+ });
+ it('wrong password', async () => {
+ await expect(api.post(endpoint, {
+ username: user.auth.local.username,
+ password: 'wrong-password',
+ })).to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('invalidLoginCredentialsLong'),
+ });
+ });
+ it('missing username', async () => {
+ await expect(api.post(endpoint, {
+ password: 'wrong-password',
+ })).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('invalidReqParams'),
+ });
+ });
+ it('missing password', async () => {
+ await expect(api.post(endpoint, {
+ username: user.auth.local.username,
+ })).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('invalidReqParams'),
+ });
+ });
+});
diff --git a/test/api/v3/integration/user/auth/POST-register_local.test.js b/test/api/v3/integration/user/auth/POST-register_local.test.js
new file mode 100644
index 0000000000..63d8f755a5
--- /dev/null
+++ b/test/api/v3/integration/user/auth/POST-register_local.test.js
@@ -0,0 +1,349 @@
+import {
+ generateUser,
+ requester,
+ translate as t,
+ createAndPopulateGroup,
+} from '../../../../../helpers/api-integration/v3';
+import { v4 as generateRandomUserName } from 'uuid';
+import { each } from 'lodash';
+import { encrypt } from '../../../../../../website/server/libs/api-v3/encryption';
+
+describe('POST /user/auth/local/register', () => {
+ context('username and email are free', () => {
+ let api;
+
+ beforeEach(async () => {
+ api = requester();
+ });
+
+ it('registers a new user', async () => {
+ let username = generateRandomUserName();
+ let email = `${username}@example.com`;
+ let password = 'password';
+
+ let user = await api.post('/user/auth/local/register', {
+ username,
+ email,
+ password,
+ confirmPassword: password,
+ });
+
+ expect(user._id).to.exist;
+ expect(user.apiToken).to.exist;
+ expect(user.auth.local.username).to.eql(username);
+ });
+
+ it('requires password and confirmPassword to match', async () => {
+ let username = generateRandomUserName();
+ let email = `${username}@example.com`;
+ let password = 'password';
+ let confirmPassword = 'not password';
+
+ await expect(api.post('/user/auth/local/register', {
+ username,
+ email,
+ password,
+ confirmPassword,
+ })).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('invalidReqParams'),
+ });
+ });
+
+ it('requires a username', async () => {
+ let email = `${generateRandomUserName()}@example.com`;
+ let password = 'password';
+ let confirmPassword = 'password';
+
+ await expect(api.post('/user/auth/local/register', {
+ email,
+ password,
+ confirmPassword,
+ })).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('invalidReqParams'),
+ });
+ });
+
+ it('requires an email', async () => {
+ let username = generateRandomUserName();
+ let password = 'password';
+
+ await expect(api.post('/user/auth/local/register', {
+ username,
+ password,
+ confirmPassword: password,
+ })).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('invalidReqParams'),
+ });
+ });
+
+ it('requires a valid email', async () => {
+ let username = generateRandomUserName();
+ let email = 'notanemail@sdf';
+ let password = 'password';
+
+ await expect(api.post('/user/auth/local/register', {
+ username,
+ email,
+ password,
+ confirmPassword: password,
+ })).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('invalidReqParams'),
+ });
+ });
+
+ it('requires a password', async () => {
+ let username = generateRandomUserName();
+ let email = `${username}@example.com`;
+ let confirmPassword = 'password';
+
+ await expect(api.post('/user/auth/local/register', {
+ username,
+ email,
+ confirmPassword,
+ })).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('invalidReqParams'),
+ });
+ });
+ });
+
+ context('attach to facebook user', () => {
+ let user;
+ let email = 'some@email.net';
+ let username = 'some-username';
+ let password = 'some-password';
+ beforeEach(async () => {
+ user = await generateUser();
+ });
+ it('checks onlySocialAttachLocal', async () => {
+ await expect(user.post('/user/auth/local/register', {
+ email,
+ username,
+ password,
+ confirmPassword: password,
+ })).to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('onlySocialAttachLocal'),
+ });
+ });
+ it('succeeds', async () => {
+ await user.update({ 'auth.facebook.id': 'some-fb-id', 'auth.local': { ok: true } });
+ await user.post('/user/auth/local/register', {
+ username,
+ email,
+ password,
+ confirmPassword: password,
+ });
+ await user.sync();
+ expect(user.auth.local.username).to.eql(username);
+ expect(user.auth.local.email).to.eql(email);
+ });
+ });
+
+ context('login is already taken', () => {
+ let username, email, api;
+
+ beforeEach(async () => {
+ api = requester();
+ username = generateRandomUserName();
+ email = `${username}@example.com`;
+
+ return generateUser({
+ 'auth.local.username': username,
+ 'auth.local.lowerCaseUsername': username,
+ 'auth.local.email': email,
+ });
+ });
+
+ it('rejects if username is already taken', async () => {
+ let uniqueEmail = `${generateRandomUserName()}@exampe.com`;
+ let password = 'password';
+
+ await expect(api.post('/user/auth/local/register', {
+ username,
+ email: uniqueEmail,
+ password,
+ confirmPassword: password,
+ })).to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('usernameTaken'),
+ });
+ });
+
+ it('rejects if email is already taken', async () => {
+ let uniqueUsername = generateRandomUserName();
+ let password = 'password';
+
+ await expect(api.post('/user/auth/local/register', {
+ username: uniqueUsername,
+ email,
+ password,
+ confirmPassword: password,
+ })).to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('emailTaken'),
+ });
+ });
+ });
+
+ context('req.query.groupInvite', () => {
+ let api, username, email, password;
+
+ beforeEach(() => {
+ api = requester();
+ username = generateRandomUserName();
+ email = `${username}@example.com`;
+ password = 'password';
+ });
+
+ it('does not crash the signup process when it\'s invalid', async () => {
+ let user = await api.post('/user/auth/local/register?groupInvite=aaaaInvalid', {
+ username,
+ email,
+ password,
+ confirmPassword: password,
+ });
+
+ expect(user._id).to.be.a('string');
+ });
+
+ it('supports invite using req.query.groupInvite', async () => {
+ let { group, groupLeader } = await createAndPopulateGroup({
+ groupDetails: { type: 'party', privacy: 'private' },
+ });
+
+ let invite = encrypt(JSON.stringify({
+ id: group._id,
+ inviter: groupLeader._id,
+ sentAt: Date.now(), // so we can let it expire
+ }));
+
+ let user = await api.post(`/user/auth/local/register?groupInvite=${invite}`, {
+ username,
+ email,
+ password,
+ confirmPassword: password,
+ });
+
+ expect(user.invitations.party).to.eql({
+ id: group._id,
+ name: group.name,
+ inviter: groupLeader._id,
+ });
+ });
+ });
+
+ context('successful login via api', () => {
+ let api, username, email, password;
+
+ beforeEach(() => {
+ api = requester();
+ username = generateRandomUserName();
+ email = `${username}@example.com`;
+ password = 'password';
+ });
+
+ it('sets all site tour values to -2 (already seen)', async () => {
+ let user = await api.post('/user/auth/local/register', {
+ username,
+ email,
+ password,
+ confirmPassword: password,
+ });
+
+ expect(user.flags.tour).to.not.be.empty;
+
+ each(user.flags.tour, (value) => {
+ expect(value).to.eql(-2);
+ });
+ });
+
+ it('populates user with default todos, not no other task types', async () => {
+ let user = await api.post('/user/auth/local/register', {
+ username,
+ email,
+ password,
+ confirmPassword: password,
+ });
+
+ expect(user.tasksOrder.todos).to.not.be.empty;
+ expect(user.tasksOrder.dailys).to.be.empty;
+ expect(user.tasksOrder.habits).to.be.empty;
+ expect(user.tasksOrder.rewards).to.be.empty;
+ });
+
+ it('populates user with default tags', async () => {
+ let user = await api.post('/user/auth/local/register', {
+ username,
+ email,
+ password,
+ confirmPassword: password,
+ });
+
+ expect(user.tags).to.not.be.empty;
+ });
+ });
+
+ context('successful login with habitica-web header', () => {
+ let api, username, email, password;
+
+ beforeEach(() => {
+ api = requester({}, {'x-client': 'habitica-web'});
+ username = generateRandomUserName();
+ email = `${username}@example.com`;
+ password = 'password';
+ });
+
+ it('sets all common tutorial flags to true', async () => {
+ let user = await api.post('/user/auth/local/register', {
+ username,
+ email,
+ password,
+ confirmPassword: password,
+ });
+
+ expect(user.flags.tour).to.not.be.empty;
+
+ each(user.flags.tutorial.common, (value) => {
+ expect(value).to.eql(true);
+ });
+ });
+
+ it('populates user with default todos, habits, and rewards', async () => {
+ let user = await api.post('/user/auth/local/register', {
+ username,
+ email,
+ password,
+ confirmPassword: password,
+ });
+
+ expect(user.tasksOrder.todos).to.not.be.empty;
+ expect(user.tasksOrder.dailys).to.be.empty;
+ expect(user.tasksOrder.habits).to.not.be.empty;
+ expect(user.tasksOrder.rewards).to.not.be.empty;
+ });
+
+ it('populates user with default tags', async () => {
+ let user = await api.post('/user/auth/local/register', {
+ username,
+ email,
+ password,
+ confirmPassword: password,
+ });
+
+ expect(user.tags).to.not.be.empty;
+ });
+ });
+});
diff --git a/test/api/v3/integration/user/auth/POST-user_reset_password.test.js b/test/api/v3/integration/user/auth/POST-user_reset_password.test.js
new file mode 100644
index 0000000000..773d199db6
--- /dev/null
+++ b/test/api/v3/integration/user/auth/POST-user_reset_password.test.js
@@ -0,0 +1,38 @@
+import {
+ generateUser,
+ translate as t,
+} from '../../../../../helpers/api-integration/v3';
+
+describe('POST /user/reset-password', async () => {
+ let endpoint = '/user/reset-password';
+ let user;
+
+ beforeEach(async () => {
+ user = await generateUser();
+ });
+
+ it('resets password', async () => {
+ let previousPassword = user.auth.local.hashed_password;
+ let response = await user.post(endpoint, {
+ email: user.auth.local.email,
+ });
+ expect(response).to.eql({ data: {}, message: t('passwordReset') });
+ await user.sync();
+ expect(user.auth.local.hashed_password).to.not.eql(previousPassword);
+ });
+
+ it('same message on error as on success', async () => {
+ let response = await user.post(endpoint, {
+ email: 'nonExistent@email.com',
+ });
+ expect(response).to.eql({ data: {}, message: t('passwordReset') });
+ });
+
+ it('errors if email is not provided', async () => {
+ await expect(user.post(endpoint)).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('invalidReqParams'),
+ });
+ });
+});
diff --git a/test/api/v3/integration/user/auth/PUT-user_update_email.test.js b/test/api/v3/integration/user/auth/PUT-user_update_email.test.js
new file mode 100644
index 0000000000..47357d3c85
--- /dev/null
+++ b/test/api/v3/integration/user/auth/PUT-user_update_email.test.js
@@ -0,0 +1,79 @@
+import {
+ generateUser,
+ translate as t,
+} from '../../../../../helpers/api-v3-integration.helper';
+
+const ENDPOINT = '/user/auth/update-email';
+
+describe('PUT /user/auth/update-email', () => {
+ let newEmail = 'some-new-email_2@example.net';
+ let oldPassword = 'password'; // from habitrpg/test/helpers/api-integration/v3/object-generators.js
+
+ context('Local Authenticaion User', async () => {
+ let user;
+
+ beforeEach(async () => {
+ user = await generateUser();
+ });
+
+ it('does not change email if email is not provided', async () => {
+ await expect(user.put(ENDPOINT)).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('invalidReqParams'),
+ });
+ });
+
+ it('does not change email if password is not provided', async () => {
+ await expect(user.put(ENDPOINT, {
+ newEmail,
+ })).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('invalidReqParams'),
+ });
+ });
+
+ it('does not change email if wrong password is provided', async () => {
+ await expect(user.put(ENDPOINT, {
+ newEmail,
+ password: 'wrong password',
+ })).to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('wrongPassword'),
+ });
+ });
+
+ it('changes email if new email and existing password are provided', async () => {
+ let response = await user.put(ENDPOINT, {
+ newEmail,
+ password: oldPassword,
+ });
+ expect(response).to.eql({ email: 'some-new-email_2@example.net' });
+
+ await user.sync();
+ expect(user.auth.local.email).to.eql(newEmail);
+ });
+ });
+
+ context('Social Login User', async () => {
+ let socialUser;
+
+ beforeEach(async () => {
+ socialUser = await generateUser();
+ await socialUser.update({ 'auth.local': { ok: true } });
+ });
+
+ it('does not change email if user.auth.local.email does not exist for this user', async () => {
+ await expect(socialUser.put(ENDPOINT, {
+ newEmail,
+ password: oldPassword,
+ })).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('userHasNoLocalRegistration'),
+ });
+ });
+ });
+});
diff --git a/test/api/v3/integration/user/auth/PUT-user_update_password.test.js b/test/api/v3/integration/user/auth/PUT-user_update_password.test.js
new file mode 100644
index 0000000000..6821e18d3e
--- /dev/null
+++ b/test/api/v3/integration/user/auth/PUT-user_update_password.test.js
@@ -0,0 +1,92 @@
+import {
+ generateUser,
+ translate as t,
+} from '../../../../../helpers/api-v3-integration.helper';
+
+const ENDPOINT = '/user/auth/update-password';
+
+describe('PUT /user/auth/update-password', async () => {
+ let user;
+ let password = 'password'; // from habitrpg/test/helpers/api-integration/v3/object-generators.js
+ let wrongPassword = 'wrong-password';
+ let newPassword = 'new-password';
+
+ beforeEach(async () => {
+ user = await generateUser();
+ });
+
+ it('successfully changes the password', async () => {
+ let previousHashedPassword = user.auth.local.hashed_password;
+ let response = await user.put(ENDPOINT, {
+ password,
+ newPassword,
+ confirmPassword: newPassword,
+ });
+ expect(response).to.eql({});
+ await user.sync();
+ expect(user.auth.local.hashed_password).to.not.eql(previousHashedPassword);
+ });
+
+ it('returns an error when confirmPassword does not match newPassword', async () => {
+ await expect(user.put(ENDPOINT, {
+ password,
+ newPassword,
+ confirmPassword: `${newPassword}-wrong-confirmation`,
+ })).to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('passwordConfirmationMatch'),
+ });
+ });
+
+ it('returns an error when existing password is wrong', async () => {
+ await expect(user.put(ENDPOINT, {
+ password: wrongPassword,
+ newPassword,
+ confirmPassword: newPassword,
+ })).to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('wrongPassword'),
+ });
+ });
+
+ it('returns an error when password is missing', async () => {
+ let body = {
+ newPassword,
+ confirmPassword: newPassword,
+ };
+
+ await expect(user.put(ENDPOINT, body)).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('invalidReqParams'),
+ });
+ });
+
+ it('returns an error when newPassword is missing', async () => {
+ let body = {
+ password,
+ confirmPassword: newPassword,
+ };
+
+ await expect(user.put(ENDPOINT, body)).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('invalidReqParams'),
+ });
+ });
+
+ it('returns an error when confirmPassword is missing', async () => {
+ let body = {
+ password,
+ newPassword,
+ };
+
+ await expect(user.put(ENDPOINT, body)).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('invalidReqParams'),
+ });
+ });
+});
diff --git a/test/api/v3/integration/user/auth/PUT-user_update_username.test.js b/test/api/v3/integration/user/auth/PUT-user_update_username.test.js
new file mode 100644
index 0000000000..372248db84
--- /dev/null
+++ b/test/api/v3/integration/user/auth/PUT-user_update_username.test.js
@@ -0,0 +1,76 @@
+import {
+ generateUser,
+ translate as t,
+} from '../../../../../helpers/api-v3-integration.helper';
+
+const ENDPOINT = '/user/auth/update-username';
+
+describe('PUT /user/auth/update-username', async () => {
+ let user;
+ let newUsername = 'new-username';
+ let password = 'password'; // from habitrpg/test/helpers/api-integration/v3/object-generators.js
+
+ beforeEach(async () => {
+ user = await generateUser();
+ });
+
+ it('successfully changes username', async () => {
+ let response = await user.put(ENDPOINT, {
+ username: newUsername,
+ password,
+ });
+ expect(response).to.eql({ username: newUsername });
+ await user.sync();
+ expect(user.auth.local.username).to.eql(newUsername);
+ });
+
+ context('errors', async () => {
+ it('prevents username update if new username is already taken', async () => {
+ let existingUsername = 'existing-username';
+ await generateUser({'auth.local.username': existingUsername, 'auth.local.lowerCaseUsername': existingUsername });
+
+ await expect(user.put(ENDPOINT, {
+ username: existingUsername,
+ password,
+ })).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('usernameTaken'),
+ });
+ });
+
+ it('errors if password is wrong', async () => {
+ await expect(user.put(ENDPOINT, {
+ username: newUsername,
+ password: 'wrong-password',
+ })).to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('wrongPassword'),
+ });
+ });
+
+ it('prevents social-only user from changing username', async () => {
+ let socialUser = await generateUser({ 'auth.local': { ok: true } });
+
+ await expect(socialUser.put(ENDPOINT, {
+ username: newUsername,
+ password,
+ })).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('userHasNoLocalRegistration'),
+ });
+ });
+
+ it('errors if new username is not provided', async () => {
+ await expect(user.put(ENDPOINT, {
+ password,
+ })).to.eventually.be.rejected.and.eql({
+ code: 400,
+ error: 'BadRequest',
+ message: t('invalidReqParams'),
+ });
+ });
+ });
+});
diff --git a/test/api/v3/unit/libs/analyticsService.test.js b/test/api/v3/unit/libs/analyticsService.test.js
new file mode 100644
index 0000000000..553dfb676c
--- /dev/null
+++ b/test/api/v3/unit/libs/analyticsService.test.js
@@ -0,0 +1,312 @@
+// TODO These tests are pretty brittle
+// rewrite them to not depend on nock
+// Trust that the amplitude module works as intended and sends the requests
+import analyticsService from '../../../../../website/server/libs/api-v3/analyticsService';
+
+import nock from 'nock';
+
+describe('analyticsService', () => {
+ let amplitudeNock, gaNock;
+
+ beforeEach(() => {
+ amplitudeNock = nock('https://api.amplitude.com')
+ .filteringPath(/httpapi.*/g, '')
+ .post('/')
+ .reply(200, {status: 'OK'});
+
+ gaNock = nock('http://www.google-analytics.com');
+ });
+
+ describe('#track', () => {
+ let eventType, data;
+
+ beforeEach(() => {
+ eventType = 'Cron';
+ data = {
+ category: 'behavior',
+ uuid: 'unique-user-id',
+ resting: true,
+ cronCount: 5,
+ };
+ });
+
+ context('Amplitude', () => {
+ it('calls out to amplitude', () => {
+ return analyticsService.track(eventType, data)
+ .then(() => {
+ amplitudeNock.done();
+ });
+ });
+
+ it('uses a dummy user id if none is provided', () => {
+ delete data.uuid;
+
+ amplitudeNock
+ .filteringPath(/httpapi.*user_id.*no-user-id-was-provided.*/g, '');
+
+ return analyticsService.track(eventType, data)
+ .then(() => {
+ amplitudeNock.done();
+ });
+ });
+
+ it('sets platform as server', () => {
+ amplitudeNock
+ .filteringPath(/httpapi.*platform.*server.*/g, '');
+
+ return analyticsService.track(eventType, data)
+ .then(() => {
+ amplitudeNock.done();
+ });
+ });
+
+ it('sends details about event', () => {
+ amplitudeNock
+ .filteringPath(/httpapi.*event_properties%22%3A%7B%22category%22%3A%22behavior%22%2C%22resting%22%3Atrue%2C%22cronCount%22%3A5%7D%2C%22.*/g, '');
+
+ return analyticsService.track(eventType, data)
+ .then(() => {
+ amplitudeNock.done();
+ });
+ });
+
+ it('sends english item name for gear if itemKey is provided', () => {
+ data.itemKey = 'headAccessory_special_foxEars';
+
+ amplitudeNock
+ .filteringPath(/httpapi.*itemName.*Fox%20Ears.*/g, '');
+
+ return analyticsService.track(eventType, data)
+ .then(() => {
+ amplitudeNock.done();
+ });
+ });
+
+ it('sends english item name for egg if itemKey is provided', () => {
+ data.itemKey = 'Wolf';
+
+ amplitudeNock
+ .filteringPath(/httpapi.*itemName.*Wolf%20Egg.*/g, '');
+
+ return analyticsService.track(eventType, data)
+ .then(() => {
+ amplitudeNock.done();
+ });
+ });
+
+ it('sends english item name for food if itemKey is provided', () => {
+ data.itemKey = 'Cake_Skeleton';
+
+ amplitudeNock
+ .filteringPath(/httpapi.*itemName.*Bare%20Bones%20Cake.*/g, '');
+
+ return analyticsService.track(eventType, data)
+ .then(() => {
+ amplitudeNock.done();
+ });
+ });
+
+ it('sends english item name for hatching potion if itemKey is provided', () => {
+ data.itemKey = 'Golden';
+
+ amplitudeNock
+ .filteringPath(/httpapi.*itemName.*Golden%20Hatching%20Potion.*/g, '');
+
+ return analyticsService.track(eventType, data)
+ .then(() => {
+ amplitudeNock.done();
+ });
+ });
+
+ xit('sends english item name for quest if itemKey is provided', () => {
+ data.itemKey = 'atom1';
+
+ amplitudeNock
+ .filteringPath(/httpapi.*itemName.*Attack%20of%20the%20Mundane%2C%20Part%201%3A%20Dish%20Disaster!.*/g, '');
+
+ return analyticsService.track(eventType, data)
+ .then(() => {
+ amplitudeNock.done();
+ });
+ });
+
+ it('sends english item name for purchased spell if itemKey is provided', () => {
+ data.itemKey = 'seafoam';
+
+ amplitudeNock
+ .filteringPath(/httpapi.*itemName.*Seafoam.*/g, '');
+
+ return analyticsService.track(eventType, data)
+ .then(() => {
+ amplitudeNock.done();
+ });
+ });
+
+ it('sends user data if provided', () => {
+ let stats = { class: 'wizard', exp: 5, gp: 23, hp: 10, lvl: 4, mp: 30 };
+ let user = {
+ stats,
+ contributor: { level: 1 },
+ purchased: { plan: { planId: 'foo-plan' } },
+ flags: {tour: {intro: -2}},
+ habits: [{_id: 'habit'}],
+ dailys: [{_id: 'daily'}],
+ todos: [{_id: 'todo'}],
+ rewards: [{_id: 'reward'}],
+ };
+
+ data.user = user;
+
+ amplitudeNock
+ .filteringPath(/httpapi.*user_properties%22%3A%7B%22Class%22%3A%22wizard%22%2C%22Experience%22%3A5%2C%22Gold%22%3A23%2C%22Health%22%3A10%2C%22Level%22%3A4%2C%22Mana%22%3A30%2C%22tutorialComplete%22%3Atrue%2C%22Number%20Of%20Tasks%22%3A%7B%22habits%22%3A1%2C%22dailys%22%3A1%2C%22todos%22%3A1%2C%22rewards%22%3A1%7D%2C%22contributorLevel%22%3A1%2C%22subscription%22%3A%22foo-plan%22%7D%2C%22.*/g, '');
+
+ return analyticsService.track(eventType, data)
+ .then(() => {
+ amplitudeNock.done();
+ });
+ });
+ });
+
+ context('GA', () => {
+ it('calls out to GA', () => {
+ gaNock
+ .post('/collect')
+ .reply(200, {status: 'OK'});
+
+ return analyticsService.track(eventType, data)
+ .then(() => {
+ gaNock.done();
+ });
+ });
+
+ it('sends details about event', () => {
+ gaNock
+ .post('/collect', /ec=behavior&ea=Cron&v=1&tid=GA_ID&cid=.*&t=event/)
+ .reply(200, {status: 'OK'});
+
+ return analyticsService.track(eventType, data)
+ .then(() => {
+ gaNock.done();
+ });
+ });
+ });
+ });
+
+ describe('#trackPurchase', () => {
+ let data;
+
+ beforeEach(() => {
+ data = {
+ uuid: 'user-id',
+ sku: 'paypal-checkout',
+ paymentMethod: 'PayPal',
+ itemPurchased: 'Gems',
+ purchaseValue: 8,
+ purchaseType: 'checkout',
+ gift: false,
+ quantity: 1,
+ };
+ });
+
+ context('Amplitude', () => {
+ it('calls out to amplitude', () => {
+ return analyticsService.trackPurchase(data)
+ .then(() => {
+ amplitudeNock.done();
+ });
+ });
+
+ it('uses a dummy user id if none is provided', () => {
+ delete data.uuid;
+
+ amplitudeNock
+ .filteringPath(/httpapi.*user_id.*no-user-id-was-provided.*/g, '');
+
+ return analyticsService.trackPurchase(data)
+ .then(() => {
+ amplitudeNock.done();
+ });
+ });
+
+ it('sets platform as server', () => {
+ amplitudeNock
+ .filteringPath(/httpapi.*platform.*server.*/g, '');
+
+ return analyticsService.trackPurchase(data)
+ .then(() => {
+ amplitudeNock.done();
+ });
+ });
+
+ it('sends details about purchase', () => {
+ amplitudeNock
+ .filteringPath(/httpapi.*aypal-checkout%22%2C%22paymentMethod%22%3A%22PayPal%22%2C%22itemPurchased%22%3A%22Gems%22%2C%22purchaseType%22%3A%22checkout%22%2C%22gift%22%3Afalse%2C%22quantity%22%3A1%7D%2C%22event_type%22%3A%22purchase%22%2C%22revenue.*/g, '');
+
+ return analyticsService.trackPurchase(data)
+ .then(() => {
+ amplitudeNock.done();
+ });
+ });
+
+ it('sends user data if provided', () => {
+ let stats = { class: 'wizard', exp: 5, gp: 23, hp: 10, lvl: 4, mp: 30 };
+ let user = {
+ stats,
+ contributor: { level: 1 },
+ purchased: { plan: { planId: 'foo-plan' } },
+ flags: {tour: {intro: -2}},
+ habits: [{_id: 'habit'}],
+ dailys: [{_id: 'daily'}],
+ todos: [{_id: 'todo'}],
+ rewards: [{_id: 'reward'}],
+ };
+
+ data.user = user;
+
+ amplitudeNock
+ .filteringPath(/httpapi.*user_properties%22%3A%7B%22Class%22%3A%22wizard%22%2C%22Experience%22%3A5%2C%22Gold%22%3A23%2C%22Health%22%3A10%2C%22Level%22%3A4%2C%22Mana%22%3A30%2C%22tutorialComplete%22%3Atrue%2C%22Number%20Of%20Tasks%22%3A%7B%22habits%22%3A1%2C%22dailys%22%3A1%2C%22todos%22%3A1%2C%22rewards%22%3A1%7D%2C%22contributorLevel%22%3A1%2C%22subscription%22%3A%22foo-plan%22%7D%2C%22.*/g, '');
+
+ return analyticsService.trackPurchase(data)
+ .then(() => {
+ amplitudeNock.done();
+ });
+ });
+ });
+
+ context('GA', () => {
+ it('calls out to GA', () => {
+ gaNock
+ .post('/collect')
+ .reply(200, {status: 'OK'});
+
+ return analyticsService.trackPurchase(data)
+ .then(() => {
+ gaNock.done();
+ });
+ });
+
+ it('sends details about purchase', () => {
+ gaNock
+ .post('/collect', /ti=user-id&tr=8&v=1&tid=GA_ID&cid=.*&t=transaction/)
+ .reply(200, {status: 'OK'})
+ .post('/collect', /ec=commerce&ea=checkout&el=PayPal&ev=8&v=1&tid=GA_ID&cid=.*&t=event/)
+ .reply(200, {status: 'OK'});
+
+ return analyticsService.trackPurchase(data)
+ .then(() => {
+ gaNock.done();
+ });
+ });
+ });
+ });
+
+ describe('mockAnalyticsService', () => {
+ it('has stubbed track method', () => {
+ expect(analyticsService.mockAnalyticsService).to.respondTo('track');
+ });
+
+ it('has stubbed trackPurchase method', () => {
+ expect(analyticsService.mockAnalyticsService).to.respondTo('trackPurchase');
+ });
+ });
+});
diff --git a/test/api/v3/unit/libs/baseModel.test.js b/test/api/v3/unit/libs/baseModel.test.js
new file mode 100644
index 0000000000..39bf7df047
--- /dev/null
+++ b/test/api/v3/unit/libs/baseModel.test.js
@@ -0,0 +1,97 @@
+import baseModel from '../../../../../website/server/libs/api-v3/baseModel';
+import mongoose from 'mongoose';
+
+describe('Base model plugin', () => {
+ let schema;
+
+ beforeEach(() => {
+ schema = new mongoose.Schema();
+ sandbox.stub(schema, 'add');
+ });
+
+ it('adds a _id field to the schema', () => {
+ schema.plugin(baseModel);
+
+ expect(schema.add).to.be.calledWith(sinon.match({
+ _id: sinon.match.object,
+ }));
+ });
+
+ it('can add timestamps fields', () => {
+ schema.plugin(baseModel, {timestamps: true});
+
+ expect(schema.add).to.be.calledTwice;
+ });
+
+ it('can sanitize input objects', () => {
+ schema.plugin(baseModel, {
+ noSet: ['noUpdateForMe'],
+ });
+
+ expect(schema.statics.sanitize).to.exist;
+ let sanitized = schema.statics.sanitize({ok: true, noUpdateForMe: true});
+
+ expect(sanitized).to.have.property('ok');
+ expect(sanitized).not.to.have.property('noUpdateForMe');
+ expect(sanitized.noUpdateForMe).to.equal(undefined);
+ });
+
+ it('accepts an array of additional fields to sanitize at runtime', () => {
+ schema.plugin(baseModel, {
+ noSet: ['noUpdateForMe'],
+ });
+
+ expect(schema.statics.sanitize).to.exist;
+ let sanitized = schema.statics.sanitize({ok: true, noUpdateForMe: true, usuallySettable: true}, ['usuallySettable']);
+
+ expect(sanitized).to.have.property('ok');
+ expect(sanitized).not.to.have.property('noUpdateForMe');
+ expect(sanitized).not.to.have.property('usuallySettable');
+ });
+
+
+ it('can make fields private', () => {
+ schema.plugin(baseModel, {
+ private: ['amPrivate'],
+ });
+
+ expect(schema.options.toJSON.transform).to.exist;
+ let objToTransform = {ok: true, amPrivate: true};
+ let privatized = schema.options.toJSON.transform({}, objToTransform);
+
+ expect(privatized).to.have.property('ok');
+ expect(privatized).not.to.have.property('amPrivate');
+ });
+
+ it('accepts a further transform function for toJSON', () => {
+ let options = {
+ private: ['amPrivate'],
+ toJSONTransform: sandbox.stub().returns(true),
+ };
+
+ schema.plugin(baseModel, options);
+
+ let objToTransform = {ok: true, amPrivate: true};
+ let doc = {doc: true};
+ let privatized = schema.options.toJSON.transform(doc, objToTransform);
+
+ expect(privatized).to.equals(true);
+ expect(options.toJSONTransform).to.be.calledWith(objToTransform, doc);
+ });
+
+ it('accepts a transform function for sanitize', () => {
+ let options = {
+ private: ['amPrivate'],
+ sanitizeTransform: sandbox.stub().returns(true),
+ };
+
+ schema.plugin(baseModel, options);
+
+ expect(schema.options.toJSON.transform).to.exist;
+ let objToSanitize = {ok: true, noUpdateForMe: true};
+ let sanitized = schema.statics.sanitize(objToSanitize);
+
+ expect(sanitized).to.equals(true);
+ expect(options.sanitizeTransform).to.be.calledWith(objToSanitize);
+ });
+});
diff --git a/test/api/v3/unit/libs/buildManifest.test.js b/test/api/v3/unit/libs/buildManifest.test.js
new file mode 100644
index 0000000000..1444738f10
--- /dev/null
+++ b/test/api/v3/unit/libs/buildManifest.test.js
@@ -0,0 +1,19 @@
+import {
+ getManifestFiles,
+} from '../../../../../website/server/libs/api-v3/buildManifest';
+
+describe('Build Manifest', () => {
+ describe('getManifestFiles', () => {
+ it('returns an html string', () => {
+ let htmlCode = getManifestFiles('app');
+
+ expect(htmlCode.startsWith('';
+ }else{
+ _.each(files.css, function(file){
+ code += '';
+ });
+ _.each(files.js, function(file){
+ code += '';
+ });
+ }
+
+ return code;
+};
diff --git a/website/server/libs/api-v2/firebase.js b/website/server/libs/api-v2/firebase.js
new file mode 100644
index 0000000000..8a8d9f002c
--- /dev/null
+++ b/website/server/libs/api-v2/firebase.js
@@ -0,0 +1,83 @@
+var Firebase = require('firebase');
+var nconf = require('nconf');
+var isProd = nconf.get('NODE_ENV') === 'production';
+var firebaseConfig = nconf.get('FIREBASE');
+
+var firebaseRef;
+var isFirebaseEnabled = (nconf.get('NODE_ENV') === 'production') && (firebaseConfig.ENABLED === 'true');
+
+import { TAVERN_ID } from '../../models/group';
+
+// Setup
+if(isFirebaseEnabled){
+ firebaseRef = new Firebase('https://' + firebaseConfig.APP + '.firebaseio.com');
+
+ // TODO what happens if an op is sent before client is authenticated?
+ firebaseRef.authWithCustomToken(firebaseConfig.SECRET, function(err, authData){
+ // TODO it's ok to kill the server here? what if FB is offline?
+ if(err) throw new Error('Impossible to authenticate Firebase');
+ });
+}
+
+var api = module.exports = {};
+
+api.updateGroupData = function(group){
+ if(!isFirebaseEnabled) return;
+ // TODO is throw ok? we don't have callbacks
+ if(!group) throw new Error('group is required.');
+ // Return in case of tavern (comparison working because we use string for _id)
+ if(group._id === TAVERN_ID) return;
+
+ firebaseRef.child('rooms/' + group._id)
+ .set({
+ name: group.name
+ });
+};
+
+api.addUserToGroup = function(groupId, userId){
+ if(!isFirebaseEnabled) return;
+ if(!userId || !groupId) throw new Error('groupId, userId are required.');
+ if(groupId === TAVERN_ID) return;
+
+ firebaseRef.child('members/' + groupId + '/' + userId)
+ .set(true);
+
+ firebaseRef.child('users/' + userId + '/rooms/' + groupId)
+ .set(true);
+};
+
+api.removeUserFromGroup = function(groupId, userId){
+ if(!isFirebaseEnabled) return;
+ if(!userId || !groupId) throw new Error('groupId, userId are required.');
+ if(groupId === TAVERN_ID) return;
+
+ firebaseRef.child('members/' + groupId + '/' + userId)
+ .remove();
+
+ firebaseRef.child('users/' + userId + '/rooms/' + groupId)
+ .remove();
+};
+
+api.deleteGroup = function(groupId){
+ if(!isFirebaseEnabled) return;
+ if(!groupId) throw new Error('groupId is required.');
+ if(groupId === TAVERN_ID) return;
+
+ firebaseRef.child('rooms/' + groupId)
+ .remove();
+
+ // TODO not really necessary as long as we only store room data,
+ // as empty objects are automatically deleted (/members/... in future...)
+ firebaseRef.child('members/' + groupId)
+ .remove();
+};
+
+// TODO not really necessary as long as we only store room data,
+// as empty objects are automatically deleted
+api.deleteUser = function(userId){
+ if(!isFirebaseEnabled) return;
+ if(!userId) throw new Error('userId is required.');
+
+ firebaseRef.child('users/' + userId)
+ .remove();
+};
diff --git a/website/server/libs/api-v2/i18n.js b/website/server/libs/api-v2/i18n.js
new file mode 100644
index 0000000000..e8295deb03
--- /dev/null
+++ b/website/server/libs/api-v2/i18n.js
@@ -0,0 +1,180 @@
+var fs = require('fs'),
+ path = require('path'),
+ _ = require('lodash'),
+ User = require('../../models/user').model,
+ accepts = require('accepts'),
+ shared = require('../../../../common'),
+ translations = {};
+
+var localePath = path.join(__dirname, "/../../../../common/locales/")
+
+var loadTranslations = function(locale){
+ var files = fs.readdirSync(path.join(localePath, locale));
+ translations[locale] = {};
+ _.each(files, function(file){
+ if(path.extname(file) !== '.json') return;
+ _.merge(translations[locale], require(path.join(localePath, locale, file)));
+ });
+};
+
+// First fetch english so we can merge with missing strings in other languages
+loadTranslations('en');
+
+fs.readdirSync(localePath).forEach(function(file) {
+ if(file === 'en' || fs.statSync(path.join(localePath, file)).isDirectory() === false) return;
+ loadTranslations(file);
+ // Merge missing strings from english
+ _.defaults(translations[file], translations.en);
+});
+
+var langCodes = Object.keys(translations);
+
+var avalaibleLanguages = _.map(langCodes, function(langCode){
+ return {
+ code: langCode,
+ name: translations[langCode].languageName
+ }
+});
+
+// Load MomentJS localization files
+var momentLangs = {};
+
+// Handle different language codes from MomentJS and /locales
+var momentLangsMapping = {
+ 'en': 'en-gb',
+ 'en_GB': 'en-gb',
+ 'no': 'nn',
+ 'zh': 'zh-cn',
+ 'es_419': 'es'
+};
+
+var momentLangs = {};
+
+_.each(langCodes, function(code){
+ var lang = _.find(avalaibleLanguages, {code: code});
+ lang.momentLangCode = (momentLangsMapping[code] || code);
+ try{
+ // MomentJS lang files are JS files that has to be executed in the browser so we load them as plain text files
+ var f = fs.readFileSync(path.join(__dirname, '/../../node_modules/moment/locale/' + lang.momentLangCode + '.js'), 'utf8');
+ momentLangs[code] = f;
+ }catch (e){}
+});
+
+// Remove en_GB from langCodes checked by browser to avaoi it being
+// used in place of plain original 'en'
+var defaultLangCodes = _.without(langCodes, 'en_GB');
+
+// A list of languages that have different versions
+var multipleVersionsLanguages = ['es', 'zh'];
+
+var latinAmericanSpanishes = {
+ 'es-419': 'es_419',
+ 'es-mx': 'es_419',
+ 'es-gt': 'es_419',
+ 'es-cr': 'es_419',
+ 'es-pa': 'es_419',
+ 'es-do': 'es_419',
+ 'es-ve': 'es_419',
+ 'es-co': 'es_419',
+ 'es-pe': 'es_419',
+ 'es-ar': 'es_419',
+ 'es-ec': 'es_419',
+ 'es-cl': 'es_419',
+ 'es-uy': 'es_419',
+ 'es-py': 'es_419',
+ 'es-bo': 'es_419',
+ 'es-sv': 'es_419',
+ 'es-hn': 'es_419',
+ 'es-ni': 'es_419',
+ 'es-pr': 'es_419',
+};
+
+var chineseVersions = {
+ 'zh-tw': 'zh_TW',
+};
+
+var getUserLanguage = function(req, res, next){
+ var getFromBrowser = function(){
+ var acceptedLanguages = accepts(req).languages();
+
+ var acceptable = _(acceptedLanguages).map(function(lang){
+ return lang.slice(0, 2);
+ }).uniq().value();
+
+ var matches = _.intersection(acceptable, defaultLangCodes);
+
+ var iAcceptedCompleteLang = (matches.length > 0) ? multipleVersionsLanguages.indexOf(matches[0].toLowerCase()) : -1;
+
+ if(iAcceptedCompleteLang !== -1){
+ var acceptedCompleteLang = _.find(acceptedLanguages, function(accepted){
+ return accepted.slice(0, 2) == multipleVersionsLanguages[iAcceptedCompleteLang];
+ });
+
+ if(acceptedCompleteLang){
+ acceptedCompleteLang = acceptedCompleteLang.toLowerCase();
+ }else{
+ return 'en';
+ }
+
+ if(matches[0] === 'es'){
+ return latinAmericanSpanishes[acceptedCompleteLang] || 'es';
+ }else if(matches[0] === 'zh'){
+ return chineseVersions[acceptedCompleteLang] || 'zh';
+ }else{
+ return en;
+ }
+
+ }else if(matches.length > 0){
+ return matches[0].toLowerCase();
+ }else{
+ return 'en';
+ }
+ };
+
+ var getFromUser = function(user){
+ var lang;
+ if(user && user.preferences.language && translations[user.preferences.language]){
+ lang = user.preferences.language;
+ }else{
+ var preferred = getFromBrowser();
+ lang = translations[preferred] ? preferred : 'en';
+ }
+ req.language = lang;
+ next();
+ };
+
+ if(req.query.lang){
+ req.language = translations[req.query.lang] ? (req.query.lang) : 'en';
+ next();
+ }else if(req.locals && req.locals.user){
+ getFromUser(req.locals.user);
+ }else if(req.session && req.session.userId){
+ User.findOne({_id: req.session.userId}, 'preferences.language', function(err, user){
+ if(err) return next(err);
+ getFromUser(user);
+ });
+ }else{
+ getFromUser(null);
+ }
+};
+
+shared.i18n.translations = translations;
+
+module.exports = {
+ translations: translations,
+ avalaibleLanguages: avalaibleLanguages,
+ langCodes: langCodes,
+ getUserLanguage: getUserLanguage,
+ momentLangs: momentLangs
+};
+
+
+// Export en strings only, temporary solution for mobile
+// This is copied from middlewares/locals#t()
+module.exports.enTranslations = function(){ // stringName and vars are the allowed parameters
+ var language = _.find(avalaibleLanguages, {code: 'en'});
+ //language.momentLang = ((!isStaticPage && i18n.momentLangs[language.code]) || undefined);
+ var args = Array.prototype.slice.call(arguments, 0);
+ args.push(language.code);
+ return shared.i18n.t.apply(null, args);
+};
diff --git a/website/server/libs/api-v2/logging.js b/website/server/libs/api-v2/logging.js
new file mode 100644
index 0000000000..454b5a5aa8
--- /dev/null
+++ b/website/server/libs/api-v2/logging.js
@@ -0,0 +1,35 @@
+var nconf = require('nconf');
+var winston = require('winston');
+
+var logger;
+
+if (!logger) {
+ logger = new (winston.Logger)({});
+ logger.add(winston.transports.Console, {colorize:true}); // TODO remove
+
+ if (nconf.get('NODE_ENV') !== 'production') {
+ logger.add(winston.transports.File, {filename: 'habitrpg.log'});
+ }
+}
+
+// A custom log function that wraps Winston. Makes it easy to instrument code
+// and still possible to replace Winston in the future.
+module.exports.log = function(/* variable args */) {
+ if (logger)
+ logger.log.apply(logger, arguments);
+};
+
+module.exports.info = function(/* variable args */) {
+ if (logger)
+ logger.info.apply(logger, arguments);
+};
+
+module.exports.warn = function(/* variable args */) {
+ if (logger)
+ logger.warn.apply(logger, arguments);
+};
+
+module.exports.error = function(/* variable args */) {
+ if (logger)
+ logger.error.apply(logger, arguments);
+};
\ No newline at end of file
diff --git a/website/src/libs/utils.js b/website/server/libs/api-v2/utils.js
similarity index 90%
rename from website/src/libs/utils.js
rename to website/server/libs/api-v2/utils.js
index 6266843edf..8657bf6513 100644
--- a/website/src/libs/utils.js
+++ b/website/server/libs/api-v2/utils.js
@@ -4,8 +4,8 @@ var crypto = require('crypto');
var path = require("path");
var request = require('request');
-// Set when utils.setupConfig is run
-var isProd, baseUrl;
+const IS_PROD = nconf.get('IS_PROD');
+const BASE_URL = nconf.get('BASE_URL');
module.exports.sendEmail = function(mailData) {
var smtpTransport = nodemailer.createTransport({
@@ -17,7 +17,7 @@ module.exports.sendEmail = function(mailData) {
});
smtpTransport.sendMail(mailData, function(error, response){
- var logging = require('./logging');
+ var logging = require('./api-v2/logging');
if(error) logging.error(error);
else logging.info("Message sent: " + response.message);
smtpTransport.close(); // shut down the connection pool, no more messages
@@ -60,7 +60,7 @@ module.exports.txnEmail = function(mailingInfoArray, emailType, variables, perso
var mailingInfoArray = Array.isArray(mailingInfoArray) ? mailingInfoArray : [mailingInfoArray];
var variables = [
- {name: 'BASE_URL', content: baseUrl}
+ {name: 'BASE_URL', content: BASE_URL}
].concat(variables || []);
// It's important to pass at least a user with its `preferences` as we need to check if he unsubscribed
@@ -121,7 +121,7 @@ module.exports.txnEmail = function(mailingInfoArray, emailType, variables, perso
});
}
- if(isProd && mailingInfoArray.length > 0){
+ if(IS_PROD && mailingInfoArray.length > 0){
request({
url: nconf.get('EMAIL_SERVER:url') + '/job',
method: 'POST',
@@ -168,20 +168,12 @@ module.exports.analytics = { track: function() { }, trackPurchase: function() {
* Load nconf and define default configuration values if config.json or ENV vars are not found
*/
module.exports.setupConfig = function(){
- nconf.argv()
- .env()
- //.file('defaults', path.join(path.resolve(__dirname, '../config.json.example')))
- .file('user', path.join(path.resolve(__dirname, './../../../config.json')));
-
- if (nconf.get('NODE_ENV') === "development")
+ if (nconf.get('IS_DEV'))
Error.stackTraceLimit = Infinity;
- if (nconf.get('NODE_ENV') === 'production' && nconf.get('NEW_RELIC_ENABLED') === 'true')
+ if (IS_PROD && nconf.get('NEW_RELIC_ENABLED') === 'true')
require('newrelic');
- isProd = nconf.get('NODE_ENV') === 'production';
- baseUrl = nconf.get('BASE_URL');
-
- var analytics = isProd && require('./analytics');
+ var analytics = IS_PROD && require('./api-v2/analytics');
var analyticsTokens = {
amplitudeToken: nconf.get('AMPLITUDE_KEY'),
googleAnalytics: nconf.get('GA_ID')
diff --git a/website/src/libs/webhook.js b/website/server/libs/api-v2/webhook.js
similarity index 100%
rename from website/src/libs/webhook.js
rename to website/server/libs/api-v2/webhook.js
diff --git a/website/server/libs/api-v3/amazonPayments.js b/website/server/libs/api-v3/amazonPayments.js
new file mode 100644
index 0000000000..4d3a3756b8
--- /dev/null
+++ b/website/server/libs/api-v3/amazonPayments.js
@@ -0,0 +1,62 @@
+import amazonPayments from 'amazon-payments';
+import nconf from 'nconf';
+import common from '../../../../common';
+import Bluebird from 'bluebird';
+import {
+ BadRequest,
+} from './errors';
+
+// TODO better handling of errors
+
+const i18n = common.i18n;
+const IS_PROD = nconf.get('NODE_ENV') === 'production';
+
+let amzPayment = amazonPayments.connect({
+ environment: amazonPayments.Environment[IS_PROD ? 'Production' : 'Sandbox'],
+ sellerId: nconf.get('AMAZON_PAYMENTS:SELLER_ID'),
+ mwsAccessKey: nconf.get('AMAZON_PAYMENTS:MWS_KEY'),
+ mwsSecretKey: nconf.get('AMAZON_PAYMENTS:MWS_SECRET'),
+ clientId: nconf.get('AMAZON_PAYMENTS:CLIENT_ID'),
+});
+
+let getTokenInfo = Bluebird.promisify(amzPayment.api.getTokenInfo, {context: amzPayment.api});
+let createOrderReferenceId = Bluebird.promisify(amzPayment.offAmazonPayments.createOrderReferenceForId, {context: amzPayment.offAmazonPayments});
+let setOrderReferenceDetails = Bluebird.promisify(amzPayment.offAmazonPayments.setOrderReferenceDetails, {context: amzPayment.offAmazonPayments});
+let confirmOrderReference = Bluebird.promisify(amzPayment.offAmazonPayments.confirmOrderReference, {context: amzPayment.offAmazonPayments});
+let closeOrderReference = Bluebird.promisify(amzPayment.offAmazonPayments.closeOrderReference, {context: amzPayment.offAmazonPayments});
+let setBillingAgreementDetails = Bluebird.promisify(amzPayment.offAmazonPayments.setBillingAgreementDetails, {context: amzPayment.offAmazonPayments});
+let confirmBillingAgreement = Bluebird.promisify(amzPayment.offAmazonPayments.confirmBillingAgreement, {context: amzPayment.offAmazonPayments});
+let closeBillingAgreement = Bluebird.promisify(amzPayment.offAmazonPayments.closeBillingAgreement, {context: amzPayment.offAmazonPayments});
+
+let authorizeOnBillingAgreement = (inputSet) => {
+ return new Promise((resolve, reject) => {
+ amzPayment.offAmazonPayments.authorizeOnBillingAgreement(inputSet, (err, response) => {
+ if (err) return reject(err);
+ if (response.AuthorizationDetails.AuthorizationStatus.State === 'Declined') return reject(new BadRequest(i18n.t('paymentNotSuccessful')));
+ return resolve(response);
+ });
+ });
+};
+
+let authorize = (inputSet) => {
+ return new Promise((resolve, reject) => {
+ amzPayment.offAmazonPayments.authorize(inputSet, (err, response) => {
+ if (err) return reject(err);
+ if (response.AuthorizationDetails.AuthorizationStatus.State === 'Declined') return reject(new BadRequest(i18n.t('paymentNotSuccessful')));
+ return resolve(response);
+ });
+ });
+};
+
+module.exports = {
+ getTokenInfo,
+ createOrderReferenceId,
+ setOrderReferenceDetails,
+ confirmOrderReference,
+ closeOrderReference,
+ confirmBillingAgreement,
+ setBillingAgreementDetails,
+ closeBillingAgreement,
+ authorizeOnBillingAgreement,
+ authorize,
+};
diff --git a/website/server/libs/api-v3/analyticsService.js b/website/server/libs/api-v3/analyticsService.js
new file mode 100644
index 0000000000..b810ac1858
--- /dev/null
+++ b/website/server/libs/api-v3/analyticsService.js
@@ -0,0 +1,237 @@
+/* eslint-disable camelcase */
+import nconf from 'nconf';
+import Amplitude from 'amplitude';
+import Bluebird from 'bluebird';
+import googleAnalytics from 'universal-analytics';
+import {
+ each,
+ omit,
+} from 'lodash';
+import { content as Content } from '../../../../common';
+
+const AMPLIUDE_TOKEN = nconf.get('AMPLITUDE_KEY');
+const GA_TOKEN = nconf.get('GA_ID');
+const GA_POSSIBLE_LABELS = ['gaLabel', 'itemKey'];
+const GA_POSSIBLE_VALUES = ['gaValue', 'gemCost', 'goldCost'];
+const AMPLITUDE_PROPERTIES_TO_SCRUB = ['uuid', 'user', 'purchaseValue', 'gaLabel', 'gaValue'];
+
+let amplitude = new Amplitude(AMPLIUDE_TOKEN);
+let ga = googleAnalytics(GA_TOKEN);
+
+let _lookUpItemName = (itemKey) => {
+ if (!itemKey) return;
+
+ let gear = Content.gear.flat[itemKey];
+ let egg = Content.eggs[itemKey];
+ let food = Content.food[itemKey];
+ let hatchingPotion = Content.hatchingPotions[itemKey];
+ let quest = Content.quests[itemKey];
+ let spell = Content.special[itemKey];
+
+ let itemName;
+
+ if (gear) {
+ itemName = gear.text();
+ } else if (egg) {
+ itemName = `${egg.text()} Egg`;
+ } else if (food) {
+ itemName = food.text();
+ } else if (hatchingPotion) {
+ itemName = `${hatchingPotion.text()} Hatching Potion`;
+ } else if (quest) {
+ itemName = quest.text();
+ } else if (spell) {
+ itemName = spell.text();
+ }
+
+ return itemName;
+};
+
+let _formatUserData = (user) => {
+ let properties = {};
+
+ if (user.stats) {
+ properties.Class = user.stats.class;
+ properties.Experience = Math.floor(user.stats.exp);
+ properties.Gold = Math.floor(user.stats.gp);
+ properties.Health = Math.ceil(user.stats.hp);
+ properties.Level = user.stats.lvl;
+ properties.Mana = Math.floor(user.stats.mp);
+ }
+
+ properties.tutorialComplete = user.flags && user.flags.tour && user.flags.tour.intro === -2;
+
+ if (user.habits && user.dailys && user.todos && user.rewards) {
+ properties['Number Of Tasks'] = {
+ habits: user.habits.length,
+ dailys: user.dailys.length,
+ todos: user.todos.length,
+ rewards: user.rewards.length,
+ };
+ }
+
+ if (user.contributor && user.contributor.level) {
+ properties.contributorLevel = user.contributor.level;
+ }
+
+ if (user.purchased && user.purchased.plan.planId) {
+ properties.subscription = user.purchased.plan.planId;
+ }
+
+ return properties;
+};
+
+
+let _formatDataForAmplitude = (data) => {
+ let event_properties = omit(data, AMPLITUDE_PROPERTIES_TO_SCRUB);
+
+ let ampData = {
+ user_id: data.uuid || 'no-user-id-was-provided',
+ platform: 'server',
+ event_properties,
+ };
+
+ if (data.user) {
+ ampData.user_properties = _formatUserData(data.user);
+ }
+
+ let itemName = _lookUpItemName(data.itemKey);
+
+ if (itemName) {
+ event_properties.itemName = itemName;
+ }
+
+ return ampData;
+};
+
+let _sendDataToAmplitude = (eventType, data) => {
+ let amplitudeData = _formatDataForAmplitude(data);
+
+ amplitudeData.event_type = eventType;
+
+ return new Bluebird((resolve, reject) => {
+ amplitude.track(amplitudeData)
+ .then(resolve)
+ .catch(reject);
+ });
+};
+
+let _generateLabelForGoogleAnalytics = (data) => {
+ let label;
+
+ each(GA_POSSIBLE_LABELS, (key) => {
+ if (data[key]) {
+ label = data[key];
+ return false; // exit each early
+ }
+ });
+
+ return label;
+};
+
+let _generateValueForGoogleAnalytics = (data) => {
+ let value;
+
+ each(GA_POSSIBLE_VALUES, (key) => {
+ if (data[key]) {
+ value = data[key];
+ return false; // exit each early
+ }
+ });
+
+ return value;
+};
+
+let _sendDataToGoogle = (eventType, data) => {
+ let eventData = {
+ ec: data.category,
+ ea: eventType,
+ };
+
+ let label = _generateLabelForGoogleAnalytics(data);
+
+ if (label) {
+ eventData.el = label;
+ }
+
+ let value = _generateValueForGoogleAnalytics(data);
+
+ if (value) {
+ eventData.ev = value;
+ }
+
+ return new Bluebird((resolve, reject) => {
+ ga.event(eventData, (err) => {
+ if (err) return reject(err);
+ resolve();
+ });
+ });
+};
+
+let _sendPurchaseDataToAmplitude = (data) => {
+ let amplitudeData = _formatDataForAmplitude(data);
+
+ amplitudeData.event_type = 'purchase';
+ amplitudeData.revenue = data.purchaseValue;
+
+ return new Bluebird((resolve, reject) => {
+ amplitude.track(amplitudeData)
+ .then(resolve)
+ .catch(reject);
+ });
+};
+
+let _sendPurchaseDataToGoogle = (data) => {
+ let label = data.paymentMethod;
+ let type = data.purchaseType;
+ let price = data.purchaseValue;
+ let qty = data.quantity;
+ let sku = data.sku;
+ let itemKey = data.itemPurchased;
+ let variation = type;
+
+ if (data.gift) variation += ' - Gift';
+
+ let eventData = {
+ ec: 'commerce',
+ ea: type,
+ el: label,
+ ev: price,
+ };
+
+ return new Bluebird((resolve) => {
+ ga.event(eventData).send();
+
+ ga.transaction(data.uuid, price)
+ .item(price, qty, sku, itemKey, variation)
+ .send();
+
+ resolve();
+ });
+};
+
+function track (eventType, data) {
+ return Bluebird.all([
+ _sendDataToAmplitude(eventType, data),
+ _sendDataToGoogle(eventType, data),
+ ]);
+}
+
+function trackPurchase (data) {
+ return Bluebird.all([
+ _sendPurchaseDataToAmplitude(data),
+ _sendPurchaseDataToGoogle(data),
+ ]);
+}
+
+// Stub for non-prod environments
+let mockAnalyticsService = {
+ track: () => { },
+ trackPurchase: () => { },
+};
+
+module.exports = {
+ track,
+ trackPurchase,
+ mockAnalyticsService,
+};
diff --git a/website/server/libs/api-v3/baseModel.js b/website/server/libs/api-v3/baseModel.js
new file mode 100644
index 0000000000..009b735fa6
--- /dev/null
+++ b/website/server/libs/api-v3/baseModel.js
@@ -0,0 +1,79 @@
+import { v4 as uuid } from 'uuid';
+import validator from 'validator';
+import objectPath from 'object-path'; // TODO use lodash's unset once v4 is out
+import _ from 'lodash';
+
+module.exports = function baseModel (schema, options = {}) {
+ if (options._id !== false) {
+ schema.add({
+ _id: {
+ type: String,
+ default: uuid,
+ validate: [validator.isUUID, 'Invalid uuid.'],
+ },
+ });
+ }
+
+ if (options.timestamps) {
+ schema.add({
+ createdAt: {
+ type: Date,
+ default: Date.now,
+ },
+ updatedAt: {
+ type: Date,
+ default: Date.now,
+ },
+ });
+ }
+
+ if (options.timestamps) {
+ schema.pre('save', function updateUpdatedAt (next) {
+ if (!this.isNew) this.updatedAt = Date.now();
+ next();
+ });
+
+ schema.pre('update', function preUpdateModel () {
+ this.update({}, { $set: { updatedAt: new Date() } });
+ });
+ }
+
+ let noSetFields = ['createdAt', 'updatedAt'];
+ let privateFields = ['__v'];
+
+ if (Array.isArray(options.noSet)) noSetFields.push(...options.noSet);
+ // This method accepts an additional array of fields to be sanitized that can be passed at runtime
+ schema.statics.sanitize = function sanitize (objToSanitize = {}, additionalFields = []) {
+ noSetFields.concat(additionalFields).forEach((fieldPath) => {
+ objectPath.del(objToSanitize, fieldPath);
+ });
+
+ // Allow a sanitize transform function to be used
+ return options.sanitizeTransform ? options.sanitizeTransform(objToSanitize) : objToSanitize;
+ };
+
+ if (Array.isArray(options.private)) privateFields.push(...options.private);
+
+ if (!schema.options.toJSON) schema.options.toJSON = {};
+ schema.options.toJSON.transform = function transformToObject (doc, plainObj) {
+ privateFields.forEach((fieldPath) => {
+ objectPath.del(plainObj, fieldPath);
+ });
+
+ // Always return `id`
+ if (!plainObj.id && plainObj._id) plainObj.id = plainObj._id;
+
+ // Allow an additional toJSON transform function to be used
+ return options.toJSONTransform ? options.toJSONTransform(plainObj, doc) : plainObj;
+ };
+
+ schema.statics.getModelPaths = function getModelPaths () {
+ return _.reduce(this.schema.paths, (result, field, path) => {
+ if (privateFields.indexOf(path) === -1) {
+ result[path] = field.instance || 'Boolean';
+ }
+
+ return result;
+ }, {});
+ };
+};
diff --git a/website/server/libs/api-v3/buildManifest.js b/website/server/libs/api-v3/buildManifest.js
new file mode 100644
index 0000000000..55db474354
--- /dev/null
+++ b/website/server/libs/api-v3/buildManifest.js
@@ -0,0 +1,62 @@
+import fs from 'fs';
+import path from 'path';
+import nconf from 'nconf';
+
+const MANIFEST_FILE_PATH = path.join(__dirname, '/../../../client/manifest.json');
+const BUILD_FOLDER_PATH = path.join(__dirname, '/../../../build');
+let manifestFiles = require(MANIFEST_FILE_PATH);
+
+const IS_PROD = nconf.get('IS_PROD');
+let buildFiles = [];
+
+function _walk (folder) {
+ let files = fs.readdirSync(folder);
+
+ files.forEach((fileName) => {
+ let file = `${folder}/${fileName}`;
+
+ if (fs.statSync(file).isDirectory()) {
+ _walk(file);
+ } else {
+ let relFolder = path.relative(BUILD_FOLDER_PATH, folder);
+ let original = fileName.replace(/-.{8}(\.[\d\w]+)$/, '$1'); // Match the hash part of the filename
+
+ if (relFolder) {
+ original = `${relFolder}/${original}`;
+ fileName = `${relFolder}/${fileName}`;
+ }
+
+ buildFiles[original] = fileName;
+ }
+ });
+}
+
+// Walks through all the files in the build directory
+// and creates a map of original files names and hashed files names
+_walk(BUILD_FOLDER_PATH);
+
+export function getBuildUrl (url) {
+ return `/${buildFiles[url] || url}`;
+}
+
+export function getManifestFiles (page) {
+ let files = manifestFiles[page];
+
+ if (!files) throw new Error(`Page "${page}" not found!`);
+
+ let htmlCode = '';
+
+ if (IS_PROD) {
+ htmlCode += ``; // eslint-disable-line prefer-template
+ htmlCode += ``; // eslint-disable-line prefer-template
+ } else {
+ files.css.forEach((file) => {
+ htmlCode += ``;
+ });
+ files.js.forEach((file) => {
+ htmlCode += ``;
+ });
+ }
+
+ return htmlCode;
+}
diff --git a/website/server/libs/api-v3/collectionManipulators.js b/website/server/libs/api-v3/collectionManipulators.js
new file mode 100644
index 0000000000..95d3981601
--- /dev/null
+++ b/website/server/libs/api-v3/collectionManipulators.js
@@ -0,0 +1,22 @@
+import {
+ findIndex,
+ isPlainObject,
+} from 'lodash';
+
+export function removeFromArray (array, element) {
+ let elementIndex;
+
+ if (isPlainObject(element)) {
+ elementIndex = findIndex(array, element);
+ } else {
+ elementIndex = array.indexOf(element);
+ }
+
+ if (elementIndex !== -1) {
+ let removedElement = array[elementIndex];
+ array.splice(elementIndex, 1);
+ return removedElement;
+ }
+
+ return false;
+}
diff --git a/website/server/libs/api-v3/cron.js b/website/server/libs/api-v3/cron.js
new file mode 100644
index 0000000000..6b46a7a435
--- /dev/null
+++ b/website/server/libs/api-v3/cron.js
@@ -0,0 +1,310 @@
+import moment from 'moment';
+import Bluebird from 'bluebird';
+import { model as User } from '../../models/user';
+import common from '../../../../common/';
+import { preenUserHistory } from '../../libs/api-v3/preening';
+import _ from 'lodash';
+import nconf from 'nconf';
+
+const CRON_SAFE_MODE = nconf.get('CRON_SAFE_MODE') === 'true';
+const CRON_SEMI_SAFE_MODE = nconf.get('CRON_SEMI_SAFE_MODE') === 'true';
+const shouldDo = common.shouldDo;
+const scoreTask = common.ops.scoreTask;
+// const maxPMs = 200;
+
+export async function recoverCron (status, locals) {
+ let {user} = locals;
+
+ await Bluebird.delay(300);
+
+ let reloadedUser = await User.findOne({_id: user._id}).exec();
+
+ if (!reloadedUser) {
+ throw new Error(`User ${user._id} not found while recovering.`);
+ } else if (reloadedUser._cronSignature !== 'NOT_RUNNING') {
+ status.times++;
+
+ if (status.times < 5) {
+ await recoverCron(status, locals);
+ } else {
+ throw new Error(`Impossible to recover from cron for user ${user._id}.`);
+ }
+ } else {
+ locals.user = reloadedUser;
+ return null;
+ }
+}
+
+let CLEAR_BUFFS = {
+ str: 0,
+ int: 0,
+ per: 0,
+ con: 0,
+ stealth: 0,
+ streaks: false,
+};
+
+function grantEndOfTheMonthPerks (user, now) {
+ let plan = user.purchased.plan;
+
+ if (moment(plan.dateUpdated).format('MMYYYY') !== moment().format('MMYYYY')) {
+ plan.gemsBought = 0; // reset gem-cap
+ plan.dateUpdated = now;
+ // For every month, inc their "consecutive months" counter. Give perks based on consecutive blocks
+ // If they already got perks for those blocks (eg, 6mo subscription, subscription gifts, etc) - then dec the offset until it hits 0
+ // TODO use month diff instead of ++ / --? see https://github.com/HabitRPG/habitrpg/issues/4317
+ _.defaults(plan.consecutive, {count: 0, offset: 0, trinkets: 0, gemCapExtra: 0});
+
+ plan.consecutive.count++;
+
+ if (plan.consecutive.offset > 0) {
+ plan.consecutive.offset--;
+ } else if (plan.consecutive.count % 3 === 0) { // every 3 months
+ plan.consecutive.trinkets++;
+ plan.consecutive.gemCapExtra += 5;
+ if (plan.consecutive.gemCapExtra > 25) plan.consecutive.gemCapExtra = 25; // cap it at 50 (hard 25 limit + extra 25)
+ }
+ }
+}
+
+function removeTerminatedSubscription (user) {
+ // If subscription's termination date has arrived
+ let plan = user.purchased.plan;
+
+ if (plan.dateTerminated && moment(plan.dateTerminated).isBefore(new Date())) {
+ _.merge(plan, {
+ planId: null,
+ customerId: null,
+ paymentMethod: null,
+ });
+
+ _.merge(plan.consecutive, {
+ count: 0,
+ offset: 0,
+ gemCapExtra: 0,
+ });
+
+ user.markModified('purchased.plan');
+ }
+}
+
+function performSleepTasks (user, tasksByType, now) {
+ user.stats.buffs = _.cloneDeep(CLEAR_BUFFS);
+
+ tasksByType.dailys.forEach((daily) => {
+ let completed = daily.completed;
+ let thatDay = moment(now).subtract({days: 1});
+
+ if (shouldDo(thatDay.toDate(), daily, user.preferences) || completed) {
+ // TODO also untick checklists if the Daily was due on previous missed days, if two or more days were missed at once -- https://github.com/HabitRPG/habitrpg/pull/7218#issuecomment-219256016
+ if (daily.checklist) {
+ daily.checklist.forEach(box => box.completed = false);
+ }
+ }
+
+ daily.completed = false;
+ });
+}
+
+// Perform various beginning-of-day reset actions.
+export function cron (options = {}) {
+ let {user, tasksByType, analytics, now = new Date(), daysMissed, timezoneOffsetFromUserPrefs} = options;
+
+ user.preferences.timezoneOffsetAtLastCron = timezoneOffsetFromUserPrefs;
+ // User is only allowed a certain number of drops a day. This resets the count.
+ if (user.items.lastDrop.count > 0) user.items.lastDrop.count = 0;
+
+ // "Perfect Day" achievement for perfect-days
+ let perfect = true;
+
+ if (user.isSubscribed()) {
+ grantEndOfTheMonthPerks(user, now);
+ if (!CRON_SAFE_MODE) removeTerminatedSubscription(user);
+ }
+
+ // User is resting at the inn.
+ // On cron, buffs are cleared and all dailies are reset without performing damage
+ if (user.preferences.sleep === true) {
+ performSleepTasks(user, tasksByType, now);
+ return;
+ }
+
+ let multiDaysCountAsOneDay = true;
+ // If the user does not log in for two or more days, cron (mostly) acts as if it were only one day.
+ // When site-wide difficulty settings are introduced, this can be a user preference option.
+
+ // Tally each task
+ let todoTally = 0;
+
+ tasksByType.todos.forEach(task => { // make uncompleted To-Dos redder (further incentive to complete them)
+ scoreTask({
+ task,
+ user,
+ direction: 'down',
+ cron: true,
+ times: multiDaysCountAsOneDay ? 1 : daysMissed,
+ });
+
+ todoTally += task.value;
+ });
+
+ // For incomplete Dailys, add value (further incentive), deduct health, keep records for later decreasing the nightly mana gain
+ let dailyChecked = 0; // how many dailies were checked?
+ let dailyDueUnchecked = 0; // how many dailies were un-checked?
+ if (!user.party.quest.progress.down) user.party.quest.progress.down = 0;
+
+ tasksByType.dailys.forEach((task) => {
+ let completed = task.completed;
+ // Deduct points for missed Daily tasks
+ let EvadeTask = 0;
+ let scheduleMisses = daysMissed;
+
+ if (completed) {
+ dailyChecked += 1;
+ } else {
+ // dailys repeat, so need to calculate how many they've missed according to their own schedule
+ scheduleMisses = 0;
+
+ for (let i = 0; i < daysMissed; i++) {
+ let thatDay = moment(now).subtract({days: i + 1});
+
+ if (shouldDo(thatDay.toDate(), task, user.preferences)) {
+ scheduleMisses++;
+ if (user.stats.buffs.stealth) {
+ user.stats.buffs.stealth--;
+ EvadeTask++;
+ }
+ if (multiDaysCountAsOneDay) break;
+ }
+ }
+
+ if (scheduleMisses > EvadeTask) {
+ // The user did not complete this due Daily (but no penalty if cron is running in safe mode).
+ if (CRON_SAFE_MODE) {
+ dailyChecked += 1; // allows full allotment of mp to be gained
+ } else {
+ perfect = false;
+
+ if (task.checklist && task.checklist.length > 0) { // Partially completed checklists dock fewer mana points
+ let fractionChecked = _.reduce(task.checklist, (m, i) => m + (i.completed ? 1 : 0), 0) / task.checklist.length;
+ dailyDueUnchecked += 1 - fractionChecked;
+ dailyChecked += fractionChecked;
+ } else {
+ dailyDueUnchecked += 1;
+ }
+
+ let delta = scoreTask({
+ user,
+ task,
+ direction: 'down',
+ times: multiDaysCountAsOneDay ? 1 : scheduleMisses - EvadeTask,
+ cron: true,
+ });
+
+ if (!CRON_SEMI_SAFE_MODE) {
+ // Apply damage from a boss, less damage for Trivial priority (difficulty)
+ user.party.quest.progress.down += delta * (task.priority < 1 ? task.priority : 1);
+ // NB: Medium and Hard priorities do not increase damage from boss. This was by accident
+ // initially, and when we realised, we could not fix it because users are used to
+ // their Medium and Hard Dailies doing an Easy amount of damage from boss.
+ // Easy is task.priority = 1. Anything < 1 will be Trivial (0.1) or any future
+ // setting between Trivial and Easy.
+ }
+ }
+ }
+ }
+
+ task.history.push({
+ date: Number(new Date()),
+ value: task.value,
+ });
+ task.completed = false;
+
+ if (completed || scheduleMisses > 0) {
+ if (task.checklist) {
+ task.checklist.forEach(i => i.completed = false);
+ }
+ }
+ });
+
+ // move singleton Habits towards yellow.
+ tasksByType.habits.forEach((task) => { // slowly reset 'onlies' value to 0
+ if (task.up === false || task.down === false) {
+ task.value = Math.abs(task.value) < 0.1 ? 0 : task.value = task.value / 2;
+ }
+ });
+
+ // Finished tallying
+ user.history.todos.push({date: now, value: todoTally});
+
+ // tally experience
+ let expTally = user.stats.exp;
+ let lvl = 0; // iterator
+ while (lvl < user.stats.lvl - 1) {
+ lvl++;
+ expTally += common.tnl(lvl);
+ }
+
+ user.history.exp.push({date: now, value: expTally});
+
+ // preen user history so that it doesn't become a performance problem
+ // also for subscribed users but differently
+ // TODO also do while resting in the inn. Note that later we'll be allowing the value/color of tasks to change while sleeping (https://github.com/HabitRPG/habitrpg/issues/5232), so the code in performSleepTasks() might be best merged back into here for that. Perhaps wait until then to do preen history for sleeping users.
+ preenUserHistory(user, tasksByType, user.preferences.timezoneOffset);
+
+ if (perfect) {
+ user.achievements.perfect++;
+ let lvlDiv2 = Math.ceil(common.capByLevel(user.stats.lvl) / 2);
+ user.stats.buffs = {
+ str: lvlDiv2,
+ int: lvlDiv2,
+ per: lvlDiv2,
+ con: lvlDiv2,
+ stealth: 0,
+ streaks: false,
+ };
+ } else {
+ user.stats.buffs = _.cloneDeep(CLEAR_BUFFS);
+ }
+
+ // Add 10 MP, or 10% of max MP if that'd be more. Perform this after Perfect Day for maximum benefit
+ // Adjust for fraction of dailies completed
+ if (dailyDueUnchecked === 0 && dailyChecked === 0) dailyChecked = 1;
+ user.stats.mp += _.max([10, 0.1 * user._statsComputed.maxMP]) * dailyChecked / (dailyDueUnchecked + dailyChecked);
+ if (user.stats.mp > user._statsComputed.maxMP) user.stats.mp = user._statsComputed.maxMP;
+
+ // After all is said and done, progress up user's effect on quest, return those values & reset the user's
+ let progress = user.party.quest.progress;
+ let _progress = _.cloneDeep(progress);
+ _.merge(progress, {down: 0, up: 0});
+ progress.collect = _.transform(progress.collect, (m, v, k) => m[k] = 0);
+
+ // TODO: Clean PMs - keep 200 for subscribers and 50 for free users. Should also be done while resting in the inn
+ // let numberOfPMs = Object.keys(user.inbox.messages).length;
+ // if (numberOfPMs > maxPMs) {
+ // _(user.inbox.messages)
+ // .sortBy('timestamp')
+ // .takeRight(numberOfPMs - maxPMs)
+ // .each(pm => {
+ // delete user.inbox.messages[pm.id];
+ // }).value();
+ //
+ // user.markModified('inbox.messages');
+ // }
+
+ // Analytics
+ user.flags.cronCount++;
+ analytics.track('Cron', { // TODO also do while resting in the inn. https://github.com/HabitRPG/habitrpg/issues/7161#issuecomment-218214191
+ category: 'behavior',
+ gaLabel: 'Cron Count',
+ gaValue: user.flags.cronCount,
+ uuid: user._id,
+ user,
+ resting: user.preferences.sleep,
+ cronCount: user.flags.cronCount,
+ progressUp: _.min([_progress.up, 900]),
+ progressDown: _progress.down,
+ });
+
+ return _progress;
+}
diff --git a/website/server/libs/api-v3/csvStringify.js b/website/server/libs/api-v3/csvStringify.js
new file mode 100644
index 0000000000..39fb7c16c8
--- /dev/null
+++ b/website/server/libs/api-v3/csvStringify.js
@@ -0,0 +1,11 @@
+import csvStringify from 'csv-stringify';
+import Bluebird from 'bluebird';
+
+module.exports = (input) => {
+ return new Bluebird((resolve, reject) => {
+ csvStringify(input, (err, output) => {
+ if (err) return reject(err);
+ return resolve(output);
+ });
+ });
+};
diff --git a/website/server/libs/api-v3/email.js b/website/server/libs/api-v3/email.js
new file mode 100644
index 0000000000..fd2e166098
--- /dev/null
+++ b/website/server/libs/api-v3/email.js
@@ -0,0 +1,156 @@
+import { createTransport } from 'nodemailer';
+import nconf from 'nconf';
+import { encrypt } from './encryption';
+import request from 'request';
+import logger from './logger';
+
+const IS_PROD = nconf.get('IS_PROD');
+const EMAIL_SERVER = {
+ url: nconf.get('EMAIL_SERVER:url'),
+ auth: {
+ user: nconf.get('EMAIL_SERVER:authUser'),
+ password: nconf.get('EMAIL_SERVER:authPassword'),
+ },
+};
+const BASE_URL = nconf.get('BASE_URL');
+
+let smtpTransporter = createTransport({
+ service: nconf.get('SMTP_SERVICE'),
+ auth: {
+ user: nconf.get('SMTP_USER'),
+ pass: nconf.get('SMTP_PASS'),
+ },
+});
+
+// Send email directly from the server using the smtpTransporter,
+// used only to send password reset emails because users unsubscribed on Mandrill wouldn't get them
+export function send (mailData) {
+ return smtpTransporter.sendMail(mailData); // promise
+}
+
+export function getUserInfo (user, fields = []) {
+ let info = {};
+
+ if (fields.indexOf('name') !== -1) {
+ info.name = user.profile && user.profile.name;
+
+ if (!info.name) {
+ if (user.auth.local && user.auth.local.username) {
+ info.name = user.auth.local.username;
+ } else if (user.auth.facebook) {
+ info.name = user.auth.facebook.displayName || user.auth.facebook.username;
+ }
+ }
+ }
+
+ if (fields.indexOf('email') !== -1) {
+ if (user.auth.local && user.auth.local.email) {
+ info.email = user.auth.local.email;
+ } else if (user.auth.facebook && user.auth.facebook.emails && user.auth.facebook.emails[0] && user.auth.facebook.emails[0].value) {
+ info.email = user.auth.facebook.emails[0].value;
+ }
+ }
+
+ if (fields.indexOf('_id') !== -1) {
+ info._id = user._id;
+ }
+
+ if (fields.indexOf('canSend') !== -1) {
+ if (user.preferences && user.preferences.emailNotifications) {
+ info.canSend = user.preferences.emailNotifications.unsubscribeFromAll !== true;
+ }
+ }
+
+ return info;
+}
+
+// Send a transactional email using Mandrill through the external email server
+export function sendTxn (mailingInfoArray, emailType, variables, personalVariables) {
+ mailingInfoArray = Array.isArray(mailingInfoArray) ? mailingInfoArray : [mailingInfoArray];
+
+ variables = [
+ {name: 'BASE_URL', content: BASE_URL},
+ ].concat(variables || []);
+
+ // It's important to pass at least a user with its `preferences` as we need to check if he unsubscribed
+ mailingInfoArray = mailingInfoArray.map((mailingInfo) => {
+ return mailingInfo._id ? getUserInfo(mailingInfo, ['_id', 'email', 'name', 'canSend']) : mailingInfo;
+ }).filter((mailingInfo) => {
+ // Always send reset-password emails
+ // Don't check canSend for non registered users as already checked before
+ return mailingInfo.email && (!mailingInfo._id || mailingInfo.canSend || emailType === 'reset-password');
+ });
+
+ // Personal variables are personal to each email recipient, if they are missing
+ // we manually create a structure for them with RECIPIENT_NAME and RECIPIENT_UNSUB_URL
+ // otherwise we just add RECIPIENT_NAME and RECIPIENT_UNSUB_URL to the existing personal variables
+ if (!personalVariables || personalVariables.length === 0) {
+ personalVariables = mailingInfoArray.map((mailingInfo) => {
+ return {
+ rcpt: mailingInfo.email,
+ vars: [
+ {
+ name: 'RECIPIENT_NAME',
+ content: mailingInfo.name,
+ },
+ {
+ name: 'RECIPIENT_UNSUB_URL',
+ content: `/email/unsubscribe?code=${encrypt(JSON.stringify({
+ _id: mailingInfo._id,
+ email: mailingInfo.email,
+ }))}`,
+ },
+ ],
+ };
+ });
+ } else {
+ let temporaryPersonalVariables = {};
+
+ mailingInfoArray.forEach((mailingInfo) => {
+ temporaryPersonalVariables[mailingInfo.email] = {
+ name: mailingInfo.name,
+ _id: mailingInfo._id,
+ };
+ });
+
+ personalVariables.forEach((singlePersonalVariables) => {
+ singlePersonalVariables.vars.push(
+ {
+ name: 'RECIPIENT_NAME',
+ content: temporaryPersonalVariables[singlePersonalVariables.rcpt].name,
+ },
+ {
+ name: 'RECIPIENT_UNSUB_URL',
+ content: `/email/unsubscribe?code=${encrypt(JSON.stringify({
+ _id: temporaryPersonalVariables[singlePersonalVariables.rcpt]._id,
+ email: singlePersonalVariables.rcpt,
+ }))}`,
+ }
+ );
+ });
+ }
+
+ if (IS_PROD && mailingInfoArray.length > 0) {
+ request.post({
+ url: `${EMAIL_SERVER.url}/job`,
+ auth: {
+ user: EMAIL_SERVER.auth.user,
+ pass: EMAIL_SERVER.auth.password,
+ },
+ json: {
+ type: 'email',
+ data: {
+ emailType,
+ to: mailingInfoArray,
+ variables,
+ personalVariables,
+ },
+ options: {
+ priority: 'high',
+ attempts: 5,
+ backoff: {delay: 10 * 60 * 1000, type: 'fixed'},
+ },
+ },
+ }, (err) => logger.error(err));
+ }
+}
diff --git a/website/server/libs/api-v3/encryption.js b/website/server/libs/api-v3/encryption.js
new file mode 100644
index 0000000000..390c59234e
--- /dev/null
+++ b/website/server/libs/api-v3/encryption.js
@@ -0,0 +1,24 @@
+import {
+ createCipher,
+ createDecipher,
+} from 'crypto';
+import nconf from 'nconf';
+
+const algorithm = 'aes-256-ctr';
+const SESSION_SECRET = nconf.get('SESSION_SECRET');
+
+export function encrypt (text) {
+ let cipher = createCipher(algorithm, SESSION_SECRET);
+ let crypted = cipher.update(text, 'utf8', 'hex');
+
+ crypted += cipher.final('hex');
+ return crypted;
+}
+
+export function decrypt (text) {
+ let decipher = createDecipher(algorithm, SESSION_SECRET);
+ let dec = decipher.update(text, 'hex', 'utf8');
+
+ dec += decipher.final('utf8');
+ return dec;
+}
diff --git a/website/server/libs/api-v3/errors.js b/website/server/libs/api-v3/errors.js
new file mode 100644
index 0000000000..2b6d52bbe3
--- /dev/null
+++ b/website/server/libs/api-v3/errors.js
@@ -0,0 +1,62 @@
+import common from '../../../../common';
+
+export const CustomError = common.errors.CustomError;
+
+/**
+ * @apiDefine NotAuthorized
+ * @apiError NotAuthorized The client is not authorized to make this request.
+ *
+ * @apiErrorExample Error-Response:
+ * HTTP/1.1 401 Unauthorized
+ * {
+ * "error": "NotAuthorized",
+ * "message": "Not authorized."
+ * }
+ */
+export const NotAuthorized = common.errors.NotAuthorized;
+
+/**
+ * @apiDefine BadRequest
+ * @apiError BadRequest The request wasn't formatted correctly.
+ *
+ * @apiErrorExample Error-Response:
+ * HTTP/1.1 400 Bad Request
+ * {
+ * "error": "BadRequest",
+ * "message": "Bad request."
+ * }
+ */
+export const BadRequest = common.errors.BadRequest;
+
+/**
+ * @apiDefine NotFound
+ * @apiError NotFound The requested resource was not found.
+ *
+ * @apiErrorExample Error-Response:
+ * HTTP/1.1 404 Not Found
+ * {
+ * "error": "NotFound",
+ * "message": "Not found."
+ * }
+ */
+export const NotFound = common.errors.NotFound;
+
+/**
+ * @apiDefine InternalServerError
+ * @apiError InternalServerError An unexpected error occurred.
+ *
+ * @apiErrorExample Error-Response:
+ * HTTP/1.1 500 Internal Server Error
+ * {
+ * "error": "InternalServerError",
+ * "message": "An unexpected error occurred."
+ * }
+ */
+export class InternalServerError extends CustomError {
+ constructor (customMessage) {
+ super();
+ this.name = this.constructor.name;
+ this.httpCode = 500;
+ this.message = customMessage || 'An unexpected error occurred.';
+ }
+}
diff --git a/website/server/libs/api-v3/firebase.js b/website/server/libs/api-v3/firebase.js
new file mode 100644
index 0000000000..324183e85f
--- /dev/null
+++ b/website/server/libs/api-v3/firebase.js
@@ -0,0 +1,69 @@
+import Firebase from 'firebase';
+import nconf from 'nconf';
+import { TAVERN_ID } from '../../models/group';
+
+const FIREBASE_CONFIG = nconf.get('FIREBASE');
+const FIREBASE_ENABLED = FIREBASE_CONFIG.ENABLED === 'true';
+
+let firebaseRef;
+
+if (FIREBASE_ENABLED) {
+ firebaseRef = new Firebase(`https://${FIREBASE_CONFIG.APP}.firebaseio.com`);
+
+ // TODO what happens if an op is sent before client is authenticated?
+ firebaseRef.authWithCustomToken(FIREBASE_CONFIG.SECRET, (err) => {
+ // TODO it's ok to kill the server here? what if FB is offline?
+ if (err) throw new Error('Impossible to authenticate Firebase');
+ });
+}
+
+export function updateGroupData (group) {
+ if (!FIREBASE_ENABLED) return;
+ // TODO is throw ok? we don't have callbacks
+ if (!group) throw new Error('group obj is required.');
+ // Return in case of tavern (comparison working because we use string for _id)
+ if (group._id === TAVERN_ID) return;
+
+ firebaseRef.child(`rooms/${group._id}`)
+ .set({
+ name: group.name,
+ });
+}
+
+export function addUserToGroup (groupId, userId) {
+ if (!FIREBASE_ENABLED) return;
+ if (!userId || !groupId) throw new Error('groupId, userId are required.');
+ if (groupId === TAVERN_ID) return;
+
+ firebaseRef.child(`members/${groupId}/${userId}`).set(true);
+ firebaseRef.child(`users/${userId}/rooms/${groupId}`).set(true);
+}
+
+export function removeUserFromGroup (groupId, userId) {
+ if (!FIREBASE_ENABLED) return;
+ if (!userId || !groupId) throw new Error('groupId, userId are required.');
+ if (groupId === TAVERN_ID) return;
+
+ firebaseRef.child(`members/${groupId}/${userId}`).remove();
+ firebaseRef.child(`users/${userId}/rooms/${groupId}`).remove();
+}
+
+export function deleteGroup (groupId) {
+ if (!FIREBASE_ENABLED) return;
+ if (!groupId) throw new Error('groupId is required.');
+ if (groupId === TAVERN_ID) return;
+
+ firebaseRef.child(`members/${groupId}`).remove();
+ // TODO not really necessary as long as we only store room data,
+ // as empty objects are automatically deleted (/members/... in future...)
+ firebaseRef.child(`rooms/${groupId}`).remove();
+}
+
+// TODO not really necessary as long as we only store room data,
+// as empty objects are automatically deleted
+export function deleteUser (userId) {
+ if (!FIREBASE_ENABLED) return;
+ if (!userId) throw new Error('userId is required.');
+
+ firebaseRef.child(`users/${userId}`).remove();
+}
diff --git a/website/server/libs/api-v3/i18n.js b/website/server/libs/api-v3/i18n.js
new file mode 100644
index 0000000000..4c424ace39
--- /dev/null
+++ b/website/server/libs/api-v3/i18n.js
@@ -0,0 +1,105 @@
+import fs from 'fs';
+import path from 'path';
+import _ from 'lodash';
+import shared from '../../../../common';
+
+export const localePath = path.join(__dirname, '/../../../../common/locales/');
+
+// Store translations
+export let translations = {};
+// Store MomentJS localization files
+export let momentLangs = {};
+
+// Handle differencies in language codes between MomentJS and /locales
+let momentLangsMapping = {
+ en: 'en-gb',
+ en_GB: 'en-gb', // eslint-disable-line camelcase
+ no: 'nn',
+ zh: 'zh-cn',
+ es_419: 'es', // eslint-disable-line camelcase
+};
+
+function _loadTranslations (locale) {
+ let files = fs.readdirSync(path.join(localePath, locale));
+
+ translations[locale] = {};
+
+ files.forEach((file) => {
+ if (path.extname(file) !== '.json') return;
+
+ // We use require to load and parse a JSON file
+ _.merge(translations[locale], require(path.join(localePath, locale, file))); // eslint-disable-line global-require
+ });
+}
+
+// First fetch English strings so we can merge them with missing strings in other languages
+_loadTranslations('en');
+
+// Then load all other languages
+fs.readdirSync(localePath).forEach((file) => {
+ if (file === 'en' || fs.statSync(path.join(localePath, file)).isDirectory() === false) return;
+ _loadTranslations(file);
+
+ // Merge missing strings from english
+ _.defaults(translations[file], translations.en);
+});
+
+// Add translations to shared
+shared.i18n.translations = translations;
+
+export let langCodes = Object.keys(translations);
+
+export let availableLanguages = langCodes.map((langCode) => {
+ return {
+ code: langCode,
+ name: translations[langCode].languageName,
+ };
+});
+
+langCodes.forEach((code) => {
+ let lang = _.find(availableLanguages, {code});
+
+ lang.momentLangCode = momentLangsMapping[code] || code;
+
+ try {
+ // MomentJS lang files are JS files that has to be executed in the browser so we load them as plain text files
+ // We wrap everything in a try catch because the file might not exist
+ let f = fs.readFileSync(path.join(__dirname, `/../../../node_modules/moment/locale/${lang.momentLangCode}.js`), 'utf8');
+
+ momentLangs[code] = f;
+ } catch (e) { // eslint-disable-lint no-empty
+ // The catch block is mandatory so it won't crash the server
+ }
+});
+
+// Remove en_GB from langCodes checked by browser to avoid it being
+// used in place of plain original 'en' (it's an optional language that can be enabled only in setting)
+export let defaultLangCodes = _.without(langCodes, 'en_GB');
+
+// A map of languages that have different versions and the relative versions
+export let multipleVersionsLanguages = {
+ es: {
+ 'es-419': 'es_419',
+ 'es-mx': 'es_419',
+ 'es-gt': 'es_419',
+ 'es-cr': 'es_419',
+ 'es-pa': 'es_419',
+ 'es-do': 'es_419',
+ 'es-ve': 'es_419',
+ 'es-co': 'es_419',
+ 'es-pe': 'es_419',
+ 'es-ar': 'es_419',
+ 'es-ec': 'es_419',
+ 'es-cl': 'es_419',
+ 'es-uy': 'es_419',
+ 'es-py': 'es_419',
+ 'es-bo': 'es_419',
+ 'es-sv': 'es_419',
+ 'es-hn': 'es_419',
+ 'es-ni': 'es_419',
+ 'es-pr': 'es_419',
+ },
+ zh: {
+ 'zh-tw': 'zh_TW',
+ },
+};
diff --git a/website/server/libs/api-v3/logger.js b/website/server/libs/api-v3/logger.js
new file mode 100644
index 0000000000..21622d8634
--- /dev/null
+++ b/website/server/libs/api-v3/logger.js
@@ -0,0 +1,84 @@
+// Logger utility
+import winston from 'winston';
+import nconf from 'nconf';
+import _ from 'lodash';
+import {
+ CustomError,
+} from './errors';
+
+const IS_PROD = nconf.get('IS_PROD');
+const IS_TEST = nconf.get('IS_TEST');
+const ENABLE_LOGS_IN_TEST = nconf.get('ENABLE_CONSOLE_LOGS_IN_TEST') === 'true';
+const ENABLE_LOGS_IN_PROD = nconf.get('ENABLE_CONSOLE_LOGS_IN_PROD') === 'true';
+
+const logger = new winston.Logger();
+
+if (IS_PROD) {
+ if (ENABLE_LOGS_IN_PROD) {
+ logger.add(winston.transports.Console, {
+ timestamp: true,
+ colorize: false,
+ prettyPrint: false,
+ });
+ }
+} else if (!IS_TEST || IS_TEST && ENABLE_LOGS_IN_TEST) { // Do not log anything when testing unless specified
+ logger
+ .add(winston.transports.Console, {
+ timestamp: true,
+ colorize: true,
+ prettyPrint: true,
+ });
+}
+
+// exports a public interface insteaf of accessing directly the logger module
+let loggerInterface = {
+ info (...args) {
+ logger.info(...args);
+ },
+
+ // Accepts two argument,
+ // an Error object (required)
+ // and an object of additional data to log alongside the error
+ // If the first argument isn't an Error, it'll call logger.error with all the arguments supplied
+ error (...args) {
+ let [err, errorData = {}, ...otherArgs] = args;
+
+ if (err instanceof Error) {
+ // pass the error stack as the first parameter to logger.error
+ let stack = err.stack || err.message || err;
+
+ if (_.isPlainObject(errorData) && !errorData.fullError) {
+ // If the error object has interesting data (not only httpCode, message and name from the CustomError class)
+ // add it to the logs
+ if (err instanceof CustomError) {
+ let errWithoutCommonProps = _.omit(err, ['name', 'httpCode', 'message']);
+
+ if (Object.keys(errWithoutCommonProps).length > 0) {
+ errorData.fullError = errWithoutCommonProps;
+ }
+ } else {
+ errorData.fullError = err;
+ }
+ }
+
+ let loggerArgs = [stack, errorData, ...otherArgs];
+
+ // Treat 4xx errors that are handled as warnings, 5xx and uncaught errors as serious problems
+ if (!errorData || !errorData.isHandledError || errorData.httpCode >= 500) {
+ logger.error(...loggerArgs);
+ } else {
+ logger.warn(...loggerArgs);
+ }
+ } else {
+ logger.error(...args);
+ }
+ },
+};
+
+// Logs unhandled promises errors
+// when no catch is attached to a promise a unhandledRejection event will be triggered
+process.on('unhandledRejection', function handlePromiseRejection (reason) {
+ loggerInterface.error(reason);
+});
+
+module.exports = loggerInterface;
diff --git a/website/server/libs/api-v3/password.js b/website/server/libs/api-v3/password.js
new file mode 100644
index 0000000000..c825083936
--- /dev/null
+++ b/website/server/libs/api-v3/password.js
@@ -0,0 +1,18 @@
+// Utilities for working with passwords
+import crypto from 'crypto';
+
+// Return the encrypted version of a password (using sha1) given a salt
+export function encrypt (password, salt) {
+ return crypto
+ .createHmac('sha1', salt)
+ .update(password)
+ .digest('hex');
+}
+
+// Create a salt, default length is 10
+export function makeSalt (len = 10) {
+ return crypto
+ .randomBytes(Math.ceil(len / 2))
+ .toString('hex')
+ .substring(0, len);
+}
\ No newline at end of file
diff --git a/website/server/libs/api-v3/payments.js b/website/server/libs/api-v3/payments.js
new file mode 100644
index 0000000000..ae825abff3
--- /dev/null
+++ b/website/server/libs/api-v3/payments.js
@@ -0,0 +1,190 @@
+import _ from 'lodash' ;
+import analytics from './analyticsService';
+import {
+ getUserInfo,
+ sendTxn as txnEmail,
+} from './email';
+import moment from 'moment';
+import nconf from 'nconf';
+import sendPushNotification from './pushNotifications';
+import shared from '../../../../common' ;
+
+const IS_PROD = nconf.get('IS_PROD');
+
+let api = {};
+
+function revealMysteryItems (user) {
+ _.each(shared.content.gear.flat, function findMysteryItems (item) {
+ if (
+ item.klass === 'mystery' &&
+ moment().isAfter(shared.content.mystery[item.mystery].start) &&
+ moment().isBefore(shared.content.mystery[item.mystery].end) &&
+ !user.items.gear.owned[item.key] &&
+ user.purchased.plan.mysteryItems.indexOf(item.key) === -1
+ ) {
+ user.purchased.plan.mysteryItems.push(item.key);
+ }
+ });
+}
+
+api.createSubscription = async function createSubscription (data) {
+ let recipient = data.gift ? data.gift.member : data.user;
+ let plan = recipient.purchased.plan;
+ let block = shared.content.subscriptionBlocks[data.gift ? data.gift.subscription.key : data.sub.key];
+ let months = Number(block.months);
+
+ if (data.gift) {
+ if (plan.customerId && !plan.dateTerminated) { // User has active plan
+ plan.extraMonths += months;
+ } else {
+ plan.dateTerminated = moment(plan.dateTerminated).add({months}).toDate();
+ if (!plan.dateUpdated) plan.dateUpdated = new Date();
+ }
+
+ if (!plan.customerId) plan.customerId = 'Gift'; // don't override existing customer, but all sub need a customerId
+ } else {
+ _(plan).merge({ // override with these values
+ planId: block.key,
+ customerId: data.customerId,
+ dateUpdated: new Date(),
+ gemsBought: 0,
+ paymentMethod: data.paymentMethod,
+ extraMonths: Number(plan.extraMonths) +
+ Number(plan.dateTerminated ? moment(plan.dateTerminated).diff(new Date(), 'months', true) : 0),
+ dateTerminated: null,
+ // Specify a lastBillingDate just for Amazon Payments
+ // Resetted every time the subscription restarts
+ lastBillingDate: data.paymentMethod === 'Amazon Payments' ? new Date() : undefined,
+ }).defaults({ // allow non-override if a plan was previously used
+ dateCreated: new Date(),
+ mysteryItems: [],
+ }).value();
+ }
+
+ // Block sub perks
+ let perks = Math.floor(months / 3);
+ if (perks) {
+ plan.consecutive.offset += months;
+ plan.consecutive.gemCapExtra += perks * 5;
+ if (plan.consecutive.gemCapExtra > 25) plan.consecutive.gemCapExtra = 25;
+ plan.consecutive.trinkets += perks;
+ }
+
+ revealMysteryItems(recipient);
+
+ if (IS_PROD) {
+ if (!data.gift) txnEmail(data.user, 'subscription-begins');
+
+ analytics.trackPurchase({
+ uuid: data.user._id,
+ itemPurchased: 'Subscription',
+ sku: `${data.paymentMethod.toLowerCase()}-subscription`,
+ purchaseType: 'subscribe',
+ paymentMethod: data.paymentMethod,
+ quantity: 1,
+ gift: Boolean(data.gift),
+ purchaseValue: block.price,
+ });
+ }
+
+ data.user.purchased.txnCount++;
+
+ if (data.gift) {
+ let message = `\`Hello ${data.gift.member.profile.name}, ${data.user.profile.name} has sent you ${shared.content.subscriptionBlocks[data.gift.subscription.key].months} months of subscription!\``;
+ if (data.gift.message) message += ` ${data.gift.message}`;
+
+ data.user.sendMessage(data.gift.member, message);
+
+ let byUserName = getUserInfo(data.user, ['name']).name;
+
+ if (data.gift.member.preferences.emailNotifications.giftedSubscription !== false) {
+ txnEmail(data.gift.member, 'gifted-subscription', [
+ {name: 'GIFTER', content: byUserName},
+ {name: 'X_MONTHS_SUBSCRIPTION', content: months},
+ ]);
+ }
+
+ if (data.gift.member._id !== data.user._id) { // Only send push notifications if sending to a user other than yourself
+ sendPushNotification(data.gift.member, shared.i18n.t('giftedSubscription'), `${months} months - by ${byUserName}`);
+ }
+ }
+
+ await data.user.save();
+ if (data.gift) await data.gift.member.save();
+};
+
+// Sets their subscription to be cancelled later
+api.cancelSubscription = async function cancelSubscription (data) {
+ let plan = data.user.purchased.plan;
+ let now = moment();
+ let remaining = data.nextBill ? moment(data.nextBill).diff(new Date(), 'days') : 30;
+ let nowStr = `${now.format('MM')}/${moment(plan.dateUpdated).format('DD')}/${now.format('YYYY')}`;
+ let nowStrFormat = 'MM/DD/YYYY';
+
+ plan.dateTerminated =
+ moment(nowStr, nowStrFormat)
+ .add({days: remaining}) // end their subscription 1mo from their last payment
+ .add({days: Math.ceil(30 * plan.extraMonths)}) // plus any extra time (carry-over, gifted subscription, etc) they have.
+ .toDate();
+ plan.extraMonths = 0; // clear extra time. If they subscribe again, it'll be recalculated from p.dateTerminated
+
+ await data.user.save();
+
+ txnEmail(data.user, 'cancel-subscription');
+
+ analytics.track('unsubscribe', {
+ uuid: data.user._id,
+ gaCategory: 'commerce',
+ gaLabel: data.paymentMethod,
+ paymentMethod: data.paymentMethod,
+ });
+};
+
+api.buyGems = async function buyGems (data) {
+ let amt = data.amount || 5;
+ amt = data.gift ? data.gift.gems.amount / 4 : amt;
+
+ (data.gift ? data.gift.member : data.user).balance += amt;
+ data.user.purchased.txnCount++;
+
+ if (IS_PROD) {
+ if (!data.gift) txnEmail(data.user, 'donation');
+
+ analytics.trackPurchase({
+ uuid: data.user._id,
+ itemPurchased: 'Gems',
+ sku: `${data.paymentMethod.toLowerCase()}-checkout`,
+ purchaseType: 'checkout',
+ paymentMethod: data.paymentMethod,
+ quantity: 1,
+ gift: Boolean(data.gift),
+ purchaseValue: amt,
+ });
+ }
+
+ if (data.gift) {
+ let byUsername = getUserInfo(data.user, ['name']).name;
+ let gemAmount = data.gift.gems.amount || 20;
+
+ let message = `\`Hello ${data.gift.member.profile.name}, ${data.user.profile.name} has sent you ${gemAmount} gems!\``;
+ if (data.gift.message) message += ` ${data.gift.message}`;
+ data.user.sendMessage(data.gift.member, message);
+
+ if (data.gift.member.preferences.emailNotifications.giftedGems !== false) {
+ txnEmail(data.gift.member, 'gifted-gems', [
+ {name: 'GIFTER', content: byUsername},
+ {name: 'X_GEMS_GIFTED', content: gemAmount},
+ ]);
+ }
+
+ if (data.gift.member._id !== data.user._id) { // Only send push notifications if sending to a user other than yourself
+ sendPushNotification(data.gift.member, shared.i18n.t('giftedGems'), `${gemAmount} Gems - by ${byUsername}`);
+ }
+
+ await data.gift.member.save();
+ }
+
+ await data.user.save();
+};
+
+module.exports = api;
diff --git a/website/server/libs/api-v3/preening.js b/website/server/libs/api-v3/preening.js
new file mode 100644
index 0000000000..00be142299
--- /dev/null
+++ b/website/server/libs/api-v3/preening.js
@@ -0,0 +1,82 @@
+import _ from 'lodash';
+import moment from 'moment';
+
+// Aggregate entries
+function _aggregate (history, aggregateBy) {
+ return _.chain(history)
+ .groupBy(entry => { // group entries by aggregateBy
+ return moment(entry.date).format(aggregateBy);
+ })
+ .sortBy((entry, key) => key) // sort by date
+ .map(entries => {
+ return {
+ date: Number(entries[0].date),
+ value: _.reduce(entries, (previousValue, entry) => {
+ return previousValue + entry.value;
+ }, 0) / entries.length,
+ };
+ })
+ .value();
+}
+
+/* Preen an array of history entries
+Free users:
+- 1 value for each day of the past 60 days (no compression)
+- 1 value each month for the previous 10 months
+- 1 value each year for the previous years
+Subscribers and challenges:
+- 1 value for each day of the past 365 days (no compression)
+- 1 value each month for the previous 12 months
+- 1 value each year for the previous years
+ */
+export function preenHistory (history, isSubscribed, timezoneOffset) {
+ // history = _.filter(history, historyEntry => Boolean(historyEntry)); // Filter missing entries
+ let now = timezoneOffset ? moment().zone(timezoneOffset) : moment();
+ // Date after which to begin compressing data
+ let cutOff = now.subtract(isSubscribed ? 365 : 60, 'days').startOf('day');
+
+ // Keep uncompressed entries (modifies history and returns removed items)
+ let newHistory = _.remove(history, entry => {
+ let date = moment(entry.date);
+ return date.isSame(cutOff) || date.isAfter(cutOff);
+ });
+
+ // Date after which to begin compressing data by year
+ let monthsCutOff = cutOff.subtract(isSubscribed ? 12 : 10, 'months').startOf('day');
+ let aggregateByMonth = _.remove(history, entry => {
+ let date = moment(entry.date);
+ return date.isSame(monthsCutOff) || date.isAfter(monthsCutOff);
+ });
+ // Aggregate remaining entries by month and year
+ if (aggregateByMonth.length > 0) newHistory.unshift(..._aggregate(aggregateByMonth, 'YYYYMM'));
+ if (history.length > 0) newHistory.unshift(..._aggregate(history, 'YYYY'));
+
+ return newHistory;
+}
+
+// Preen history for users and tasks.
+export function preenUserHistory (user, tasksByType) {
+ let isSubscribed = user.isSubscribed();
+ let timezoneOffset = user.preferences.timezoneOffset;
+ let minHistoryLength = isSubscribed ? 365 : 60;
+
+ function _processTask (task) {
+ if (task.history && task.history.length > minHistoryLength) {
+ task.history = preenHistory(task.history, isSubscribed, timezoneOffset);
+ task.markModified('history');
+ }
+ }
+
+ tasksByType.habits.forEach(_processTask);
+ tasksByType.dailys.forEach(_processTask);
+
+ if (user.history.exp.length > minHistoryLength) {
+ user.history.exp = preenHistory(user.history.exp, isSubscribed, timezoneOffset);
+ user.markModified('history.exp');
+ }
+
+ if (user.history.todos.length > minHistoryLength) {
+ user.history.todos = preenHistory(user.history.todos, isSubscribed, timezoneOffset);
+ user.markModified('history.todos');
+ }
+}
diff --git a/website/server/libs/api-v3/pushNotifications.js b/website/server/libs/api-v3/pushNotifications.js
new file mode 100644
index 0000000000..ce5af61661
--- /dev/null
+++ b/website/server/libs/api-v3/pushNotifications.js
@@ -0,0 +1,58 @@
+/* eslint-disable */
+
+import _ from 'lodash';
+import nconf from 'nconf';
+import pushNotify from 'push-notify';
+
+const GCM_API_KEY = nconf.get('PUSH_CONFIGS:GCM_SERVER_API_KEY');
+
+let gcm = GCM_API_KEY ? pushNotify.gcm({
+ apiKey: GCM_API_KEY,
+ retries: 3,
+}) : undefined;
+
+// TODO review and test this file when push notifications are added back
+
+if (gcm) {
+ gcm.on('transmitted', (/* result, message, registrationId */) => {
+ // console.info("transmitted", result, message, registrationId);
+ });
+
+ gcm.on('transmissionError', (/* error, message, registrationId */) => {
+ // console.info("transmissionError", error, message, registrationId);
+ });
+
+ gcm.on('updated', (/* result, registrationId */) => {
+ // console.info("updated", result, registrationId);
+ });
+}
+
+module.exports = function sendNotification (user, title, message, timeToLive = 15) {
+ return; // TODO push notifications are not currently enabled
+
+ if (!user) return;
+ let pushDevices = user.pushDevices.toObject ? user.pushDevices.toObject() : user.pushDevices;
+
+ _.each(pushDevices, pushDevice => {
+ switch (pushDevice.type) {
+ case 'android':
+ if (gcm) {
+ gcm.send({
+ registrationId: pushDevice.regId,
+ // collapseKey: 'COLLAPSE_KEY',
+ delayWhileIdle: true,
+ timeToLive,
+ data: {
+ title,
+ message,
+ },
+ });
+ }
+
+ break;
+
+ case 'ios':
+ break;
+ }
+ });
+};
diff --git a/website/server/libs/api-v3/routes.js b/website/server/libs/api-v3/routes.js
new file mode 100644
index 0000000000..c0399fb73c
--- /dev/null
+++ b/website/server/libs/api-v3/routes.js
@@ -0,0 +1,61 @@
+import fs from 'fs';
+import _ from 'lodash';
+import {
+ getUserLanguage,
+} from '../../middlewares/api-v3/language';
+import cron from '../../middlewares/api-v3/cron';
+
+// Wrapper function to handler `async` route handlers that return promises
+// It takes the async function, execute it and pass any error to next (args[2])
+let _wrapAsyncFn = fn => (...args) => fn(...args).catch(args[2]);
+let noop = (req, res, next) => next();
+
+module.exports.readController = function readController (router, controller) {
+ _.each(controller, (action) => {
+ let {method, url, middlewares = [], handler, runCron} = action;
+
+ // If an authentication middleware is used run getUserLanguage after it, otherwise before
+ // for cron instead use it only if an authentication middleware is present
+ let authMiddlewareIndex = _.findIndex(middlewares, middleware => {
+ if (middleware.name.indexOf('authWith') === 0) { // authWith{Headers|Session|Url|...}
+ return true;
+ } else {
+ return false;
+ }
+ });
+
+ let middlewaresToAdd = [getUserLanguage];
+
+ if (authMiddlewareIndex !== -1) { // the user will be authenticated, getUserLanguage and cron after authentication
+ if (!(runCron === false)) { // eslint-disable-line no-extra-parens
+ middlewaresToAdd.push(cron);
+ }
+
+ if (authMiddlewareIndex === middlewares.length - 1) {
+ middlewares.push(...middlewaresToAdd);
+ } else {
+ middlewares.splice(authMiddlewareIndex + 1, 0, ...middlewaresToAdd);
+ }
+ } else { // no auth, getUserLanguage as the first middleware
+ middlewares.unshift(...middlewaresToAdd);
+ }
+
+ method = method.toLowerCase();
+ let fn = handler ? _wrapAsyncFn(handler) : noop;
+
+ router[method](url, ...middlewares, fn);
+ });
+};
+
+module.exports.walkControllers = function walkControllers (router, filePath) {
+ fs
+ .readdirSync(filePath)
+ .forEach(fileName => {
+ if (!fs.statSync(filePath + fileName).isFile()) {
+ walkControllers(router, `${filePath}${fileName}/`);
+ } else if (fileName.match(/\.js$/)) {
+ let controller = require(filePath + fileName); // eslint-disable-line global-require
+ module.exports.readController(router, controller);
+ }
+ });
+};
diff --git a/website/server/libs/api-v3/setupMongoose.js b/website/server/libs/api-v3/setupMongoose.js
new file mode 100644
index 0000000000..41ba9e5358
--- /dev/null
+++ b/website/server/libs/api-v3/setupMongoose.js
@@ -0,0 +1,28 @@
+import nconf from 'nconf';
+import logger from './logger';
+import autoinc from 'mongoose-id-autoinc';
+import mongoose from 'mongoose';
+import Bluebird from 'bluebird';
+
+const IS_PROD = nconf.get('IS_PROD');
+const MAINTENANCE_MODE = nconf.get('MAINTENANCE_MODE');
+
+// Use Q promises instead of mpromise in mongoose
+mongoose.Promise = Bluebird;
+
+// Do not connect to MongoDB when in maintenance mode
+if (MAINTENANCE_MODE !== 'true') {
+ let mongooseOptions = !IS_PROD ? {} : {
+ replset: { socketOptions: { keepAlive: 120, connectTimeoutMS: 30000 } },
+ server: { socketOptions: { keepAlive: 120, connectTimeoutMS: 30000 } },
+ };
+
+ const NODE_DB_URI = nconf.get('IS_TEST') ? nconf.get('TEST_DB_URI') : nconf.get('NODE_DB_URI');
+
+ let db = mongoose.connect(NODE_DB_URI, mongooseOptions, (err) => {
+ if (err) throw err;
+ logger.info('Connected with Mongoose.');
+ });
+
+ autoinc.init(db);
+}
\ No newline at end of file
diff --git a/website/server/libs/api-v3/setupNconf.js b/website/server/libs/api-v3/setupNconf.js
new file mode 100644
index 0000000000..d88b04014e
--- /dev/null
+++ b/website/server/libs/api-v3/setupNconf.js
@@ -0,0 +1,17 @@
+import nconf from 'nconf';
+import { join, resolve } from 'path';
+
+const PATH_TO_CONFIG = join(resolve(__dirname, '../../../../config.json'));
+
+module.exports = function setupNconf (file) {
+ let configFile = file || PATH_TO_CONFIG;
+
+ nconf
+ .argv()
+ .env()
+ .file('user', configFile);
+
+ nconf.set('IS_PROD', nconf.get('NODE_ENV') === 'production');
+ nconf.set('IS_DEV', nconf.get('NODE_ENV') === 'development');
+ nconf.set('IS_TEST', nconf.get('NODE_ENV') === 'test');
+};
diff --git a/website/server/libs/api-v3/setupPassport.js b/website/server/libs/api-v3/setupPassport.js
new file mode 100644
index 0000000000..dd9fdeaaa2
--- /dev/null
+++ b/website/server/libs/api-v3/setupPassport.js
@@ -0,0 +1,24 @@
+import passport from 'passport';
+import nconf from 'nconf';
+import passportFacebook from 'passport-facebook';
+
+const FacebookStrategy = passportFacebook.Strategy;
+
+// Passport session setup.
+// To support persistent login sessions, Passport needs to be able to
+// serialize users into and deserialize users out of the session. Typically,
+// this will be as simple as storing the user ID when serializing, and finding
+// the user by ID when deserializing. However, since this example does not
+// have a database of user records, the complete Facebook profile is serialized
+// and deserialized.
+passport.serializeUser((user, done) => done(null, user));
+passport.deserializeUser((obj, done) => done(null, obj));
+
+// TODO remove?
+// This auth strategy is no longer used. It's just kept around for auth.js#loginFacebook() (passport._strategies.facebook.userProfile)
+// The proper fix would be to move to a general OAuth module simply to verify accessTokens
+passport.use(new FacebookStrategy({
+ clientID: nconf.get('FACEBOOK_KEY'),
+ clientSecret: nconf.get('FACEBOOK_SECRET'),
+ // callbackURL: nconf.get("BASE_URL") + "/auth/facebook/callback"
+}, (accessToken, refreshToken, profile, done) => done(null, profile)));
diff --git a/website/server/libs/api-v3/webhook.js b/website/server/libs/api-v3/webhook.js
new file mode 100644
index 0000000000..e53eebf541
--- /dev/null
+++ b/website/server/libs/api-v3/webhook.js
@@ -0,0 +1,31 @@
+import { each } from 'lodash';
+import { post } from 'request';
+import { isURL } from 'validator';
+import logger from './logger';
+
+let _sendWebhook = (url, body) => {
+ post({
+ url,
+ body,
+ json: true,
+ }, (err) => logger.error(err));
+};
+
+let _isInvalidWebhook = (hook) => {
+ return !hook.enabled || !isURL(hook.url);
+};
+
+export function sendTaskWebhook (webhooks, data) {
+ each(webhooks, (hook) => {
+ if (_isInvalidWebhook(hook)) return;
+
+ let body = {
+ direction: data.task.direction,
+ task: data.task.details,
+ delta: data.task.delta,
+ user: data.user,
+ };
+
+ _sendWebhook(hook.url, body);
+ });
+}
diff --git a/website/server/middlewares/api-v2/domain.js b/website/server/middlewares/api-v2/domain.js
new file mode 100644
index 0000000000..db3e65df2e
--- /dev/null
+++ b/website/server/middlewares/api-v2/domain.js
@@ -0,0 +1,49 @@
+var nconf = require('nconf');
+var moment = require('moment');
+var domainMiddleware = require('domain-middleware');
+var os = require('os');
+var request = require('request');
+
+var IS_PROD = nconf.get('NODE_ENV') === 'production';
+
+module.exports = function(server,mongoose) {
+ /* if (IS_PROD) {
+ var mins = 3, // how often to run this check
+ useAvg = false, // use average over 3 minutes, or simply the last minute's report
+ url = 'https://api.newrelic.com/v2/applications/'+nconf.get('NEW_RELIC_APPLICATION_ID')+'/metrics/data.json?names[]=Apdex&values[]=score';
+ setInterval(function(){
+ // see https://docs.newrelic.com/docs/apm/apis/api-v2-examples/average-response-time-examples-api-v2, https://rpm.newrelic.com/api/explore/applications/data
+ request({
+ url: useAvg ? url+'&from='+moment().subtract({minutes:mins}).utc().format()+'&to='+moment().utc().format()+'&summarize=true' : url,
+ headers: {'X-Api-Key': nconf.get('NEW_RELIC_API_KEY')}
+ }, function(err, response, body){
+ var ts = JSON.parse(body).metric_data.metrics[0].timeslices,
+ score = ts[ts.length-1].values.score,
+ memory = os.freemem() / os.totalmem(),
+ memoryHigh = memory < 0.1;
+
+ if (memoryHigh) {
+ var newRelicMemoryLeakMessage = '[Memory Leak] Apdex='+score+' Memory='+parseFloat(memory).toFixed(3)+' Time='+moment().format();
+ throw newRelicMemoryLeakMessage;
+ }
+ });
+
+ var memory = os.freemem() / os.totalmem(),
+ memoryHigh = memory < 0.1;
+ if (memoryHigh) {
+ var memoryLeakMessage = '[Memory Leak] Memory='+parseFloat(memory).toFixed(3)+' Time='+moment().format();
+ throw memoryLeakMessage;
+ }
+ }, mins*60*1000);
+ } */
+
+ return domainMiddleware({
+ server: {
+ close:function(){
+ server.close();
+ mongoose.connection.close();
+ }
+ },
+ killTimeout: 10000
+ });
+};
diff --git a/website/server/middlewares/api-v2/errorHandler.js b/website/server/middlewares/api-v2/errorHandler.js
new file mode 100644
index 0000000000..fcea9d175c
--- /dev/null
+++ b/website/server/middlewares/api-v2/errorHandler.js
@@ -0,0 +1,17 @@
+var logging = require('../../libs/api-v2/logging');
+
+module.exports = function(err, req, res, next) {
+ //res.locals.domain.emit('error', err);
+ // when we hit an error, send it to admin as an email. If no ADMIN_EMAIL is present, just send it to yourself (SMTP_USER)
+ var stack = (err.stack ? err.stack : err.message ? err.message : err) +
+ "\n ----------------------------\n" +
+ "\n\noriginalUrl: " + req.originalUrl +
+ "\n\nauth: " + req.headers['x-api-user'] + ' | ' + req.headers['x-api-key'] +
+ "\n\nheaders: " + JSON.stringify(req.headers) +
+ "\n\nbody: " + JSON.stringify(req.body) +
+ (res.locals.ops ? "\n\ncompleted ops: " + JSON.stringify(res.locals.ops) : "");
+ logging.error(stack);
+ var message = err.message ? err.message : err;
+ message = (message.length < 200) ? message : message.substring(0,100) + message.substring(message.length-100,message.length);
+ res.status(500).json({err:message}); //res.end(err.message);
+};
diff --git a/website/server/middlewares/api-v2/locals.js b/website/server/middlewares/api-v2/locals.js
new file mode 100644
index 0000000000..223186a81a
--- /dev/null
+++ b/website/server/middlewares/api-v2/locals.js
@@ -0,0 +1,70 @@
+var nconf = require('nconf');
+var _ = require('lodash');
+var utils = require('../libs/api-v2/utils');
+var shared = require('../../../common');
+var i18n = require('../libs/api-v2/i18n');
+var buildManifest = require('../libs/api-v2/buildManifest');
+var shared = require('../../../common');
+var forceRefresh = require('./forceRefresh');
+var tavernQuest = require('../models/group').tavernQuest;
+var mods = require('../models/user').mods;
+
+// To avoid stringifying more data then we need,
+// items from `env` used on the client will have to be specified in this array
+var clientVars = ['language', 'isStaticPage', 'avalaibleLanguages', 'translations',
+ 'FACEBOOK_KEY', 'NODE_ENV', 'BASE_URL', 'GA_ID',
+ 'AMAZON_PAYMENTS', 'STRIPE_PUB_KEY', 'AMPLITUDE_KEY',
+ 'worldDmg', 'mods', 'IS_MOBILE'];
+
+var env = {
+ getManifestFiles: buildManifest.getManifestFiles,
+ getBuildUrl: buildManifest.getBuildUrl,
+ _: _,
+ clientVars: clientVars,
+ mods: mods,
+ Content: shared.content,
+ siteVersion: forceRefresh.siteVersion,
+ avalaibleLanguages: i18n.avalaibleLanguages,
+ AMAZON_PAYMENTS: {
+ SELLER_ID: nconf.get('AMAZON_PAYMENTS:SELLER_ID'),
+ CLIENT_ID: nconf.get('AMAZON_PAYMENTS:CLIENT_ID')
+ }
+};
+
+'NODE_ENV BASE_URL GA_ID STRIPE_PUB_KEY FACEBOOK_KEY AMPLITUDE_KEY'.split(' ').forEach(function(key){
+ env[key] = nconf.get(key);
+});
+
+module.exports = function(req, res, next) {
+ var language = _.find(i18n.avalaibleLanguages, {code: req.language});
+ var isStaticPage = req.url.split('/')[1] === 'static'; // If url contains '/static/'
+
+ // Load moment.js language file only when not on static pages
+ language.momentLang = ((!isStaticPage && i18n.momentLangs[language.code]) || undefined);
+
+ res.locals.habitrpg = _.assign(env, {
+ IS_MOBILE: /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(req.header('User-Agent')),
+ language: language,
+ isStaticPage: isStaticPage,
+ translations: i18n.translations[language.code],
+ t: function(){ // stringName and vars are the allowed parameters
+ var args = Array.prototype.slice.call(arguments, 0);
+ args.push(language.code);
+ return shared.i18n.t.apply(null, args);
+ },
+ // Defined here and not outside of the middleware because tavernQuest might be an
+ // empty object until the query to fetch it finishes
+ worldDmg: (tavernQuest && tavernQuest.extra && tavernQuest.extra.worldDmg) || {},
+ });
+
+ // Put query-string party (& guild but use partyInvite for backward compatibility)
+ // invitations into session to be handled later
+ // TODO once we have proper logging in place, log errors here
+ if(req.query.partyInvite){
+ try{
+ req.session.partyInvite = JSON.parse(utils.decrypt(req.query.partyInvite));
+ } catch(e){}
+ }
+
+ next();
+};
diff --git a/website/server/middlewares/api-v3/analytics.js b/website/server/middlewares/api-v3/analytics.js
new file mode 100644
index 0000000000..8512d16011
--- /dev/null
+++ b/website/server/middlewares/api-v3/analytics.js
@@ -0,0 +1,23 @@
+import nconf from 'nconf';
+import {
+ track,
+ trackPurchase,
+ mockAnalyticsService,
+} from '../../libs/api-v3/analyticsService';
+
+let service;
+
+if (nconf.get('IS_PROD')) {
+ service = {
+ track,
+ trackPurchase,
+ };
+} else {
+ service = mockAnalyticsService;
+}
+
+module.exports = function attachAnalytics (req, res, next) {
+ res.analytics = service;
+
+ next();
+};
diff --git a/website/server/middlewares/api-v3/auth.js b/website/server/middlewares/api-v3/auth.js
new file mode 100644
index 0000000000..0feb73a98d
--- /dev/null
+++ b/website/server/middlewares/api-v3/auth.js
@@ -0,0 +1,87 @@
+import {
+ NotAuthorized,
+} from '../../libs/api-v3/errors';
+import {
+ model as User,
+} from '../../models/user';
+
+// Strins won't be translated here because getUserLanguage has not run yet
+
+// Authenticate a request through the x-api-user and x-api key header
+// If optional is true, don't error on missing authentication
+export function authWithHeaders (optional = false) {
+ return function authWithHeadersHandler (req, res, next) {
+ let userId = req.header('x-api-user');
+ let apiToken = req.header('x-api-key');
+
+ if (!userId || !apiToken) {
+ if (optional) return next();
+ return next(new NotAuthorized(res.t('missingAuthHeaders')));
+ }
+
+ return User.findOne({
+ _id: userId,
+ apiToken,
+ })
+ .exec()
+ .then((user) => {
+ if (!user) throw new NotAuthorized(res.t('invalidCredentials'));
+ if (user.auth.blocked) throw new NotAuthorized(res.t('accountSuspended', {userId: user._id}));
+
+ res.locals.user = user;
+
+ req.session.userId = user._id;
+ return next();
+ })
+ .catch(next);
+ };
+}
+
+// Authenticate a request through a valid session
+export function authWithSession (req, res, next) {
+ let userId = req.session.userId;
+
+ // Always allow authentication with headers
+ if (!userId) {
+ if (!req.header('x-api-user') || !req.header('x-api-key')) {
+ return next(new NotAuthorized(res.t('invalidCredentials')));
+ } else {
+ return authWithHeaders()(req, res, next);
+ }
+ }
+
+ return User.findOne({
+ _id: userId,
+ })
+ .exec()
+ .then((user) => {
+ if (!user) throw new NotAuthorized(res.t('invalidCredentials'));
+
+ res.locals.user = user;
+ return next();
+ })
+ .catch(next);
+}
+
+export function authWithUrl (req, res, next) {
+ let userId = req.query._id;
+ let apiToken = req.query.apiToken;
+
+ // Always allow authentication with headers
+ if (!userId || !apiToken) {
+ if (!req.header('x-api-user') || !req.header('x-api-key')) {
+ return next(new NotAuthorized(res.t('missingAuthParams')));
+ } else {
+ return authWithHeaders()(req, res, next);
+ }
+ }
+
+ return User.findOne({ _id: userId, apiToken }).exec()
+ .then((user) => {
+ if (!user) throw new NotAuthorized(res.t('invalidCredentials'));
+
+ res.locals.user = user;
+ return next();
+ })
+ .catch(next);
+}
diff --git a/website/server/middlewares/api-v3/cors.js b/website/server/middlewares/api-v3/cors.js
new file mode 100644
index 0000000000..c249c183c6
--- /dev/null
+++ b/website/server/middlewares/api-v3/cors.js
@@ -0,0 +1,9 @@
+module.exports = function corsMiddleware (req, res, next) {
+ res.set({
+ 'Access-Control-Allow-Origin': req.header('origin') || '*',
+ 'Access-Control-Allow-Methods': 'OPTIONS,GET,POST,PUT,HEAD,DELETE',
+ 'Access-Control-Allow-Headers': 'Content-Type,Accept,Content-Encoding,X-Requested-With,x-api-user,x-api-key',
+ });
+ if (req.method === 'OPTIONS') return res.sendStatus(200);
+ return next();
+};
diff --git a/website/server/middlewares/api-v3/cron.js b/website/server/middlewares/api-v3/cron.js
new file mode 100644
index 0000000000..18d9907d0d
--- /dev/null
+++ b/website/server/middlewares/api-v3/cron.js
@@ -0,0 +1,212 @@
+import _ from 'lodash';
+import moment from 'moment';
+import common from '../../../../common';
+import * as Tasks from '../../models/task';
+import Bluebird from 'bluebird';
+import { model as Group } from '../../models/group';
+import { model as User } from '../../models/user';
+import { recoverCron, cron } from '../../libs/api-v3/cron';
+import { v4 as uuid } from 'uuid';
+
+const daysSince = common.daysSince;
+
+async function cronAsync (req, res) {
+ let user = res.locals.user;
+ if (!user) return null; // User might not be available when authentication is not mandatory
+
+ let analytics = res.analytics;
+ let now = new Date();
+
+ try {
+ // If the user's timezone has changed (due to travel or daylight savings),
+ // cron can be triggered twice in one day, so we check for that and use
+ // both timezones to work out if cron should run.
+ // CDS = Custom Day Start time.
+ let timezoneOffsetFromUserPrefs = user.preferences.timezoneOffset;
+ let timezoneOffsetAtLastCron = _.isFinite(user.preferences.timezoneOffsetAtLastCron) ? user.preferences.timezoneOffsetAtLastCron : timezoneOffsetFromUserPrefs;
+ let timezoneOffsetFromBrowser = Number(req.header('x-user-timezoneoffset'));
+ timezoneOffsetFromBrowser = _.isFinite(timezoneOffsetFromBrowser) ? timezoneOffsetFromBrowser : timezoneOffsetFromUserPrefs;
+ // NB: All timezone offsets can be 0, so can't use `... || ...` to apply non-zero defaults
+
+ if (timezoneOffsetFromBrowser !== timezoneOffsetFromUserPrefs) {
+ // The user's browser has just told Habitica that the user's timezone has
+ // changed so store and use the new zone.
+ user.preferences.timezoneOffset = timezoneOffsetFromBrowser;
+ timezoneOffsetFromUserPrefs = timezoneOffsetFromBrowser;
+ }
+
+ // How many days have we missed using the user's current timezone:
+ let daysMissed = daysSince(user.lastCron, _.defaults({now}, user.preferences));
+
+ if (timezoneOffsetAtLastCron !== timezoneOffsetFromUserPrefs) {
+ // Since cron last ran, the user's timezone has changed.
+ // How many days have we missed using the old timezone:
+ let daysMissedNewZone = daysMissed;
+ let daysMissedOldZone = daysSince(user.lastCron, _.defaults({
+ now,
+ timezoneOffsetOverride: timezoneOffsetAtLastCron,
+ }, user.preferences));
+
+ if (timezoneOffsetAtLastCron < timezoneOffsetFromUserPrefs) {
+ // The timezone change was in the unsafe direction.
+ // E.g., timezone changes from UTC+1 (offset -60) to UTC+0 (offset 0).
+ // or timezone changes from UTC-4 (offset 240) to UTC-5 (offset 300).
+ // Local time changed from, for example, 03:00 to 02:00.
+
+ if (daysMissedOldZone > 0 && daysMissedNewZone > 0) {
+ // Both old and new timezones indicate that we SHOULD run cron, so
+ // it is safe to do so immediately.
+ daysMissed = Math.min(daysMissedOldZone, daysMissedNewZone);
+ // use minimum value to be nice to user
+ } else if (daysMissedOldZone > 0) {
+ // The old timezone says that cron should run; the new timezone does not.
+ // This should be impossible for this direction of timezone change, but
+ // just in case I'm wrong...
+ // TODO
+ // console.log("zone has changed - old zone says run cron, NEW zone says no - stop cron now only -- SHOULD NOT HAVE GOT TO HERE", timezoneOffsetAtLastCron, timezoneOffsetFromUserPrefs, now); // used in production for confirming this never happens
+ } else if (daysMissedNewZone > 0) {
+ // The old timezone says that cron should NOT run -- i.e., cron has
+ // already run today, from the old timezone's point of view.
+ // The new timezone says that cron SHOULD run, but this is almost
+ // certainly incorrect.
+ // This happens when cron occurred at a time soon after the CDS. When
+ // you reinterpret that time in the new timezone, it looks like it
+ // was before the CDS, because local time has stepped backwards.
+ // To fix this, rewrite the cron time to a time that the new
+ // timezone interprets as being in today.
+
+ daysMissed = 0; // prevent cron running now
+ let timezoneOffsetDiff = timezoneOffsetAtLastCron - timezoneOffsetFromUserPrefs;
+ // e.g., for dangerous zone change: 240 - 300 = -60 or -660 - -600 = -60
+
+ user.lastCron = moment(user.lastCron).subtract(timezoneOffsetDiff, 'minutes');
+ // NB: We don't change user.auth.timestamps.loggedin so that will still record the time that the previous cron actually ran.
+ // From now on we can ignore the old timezone:
+ user.preferences.timezoneOffsetAtLastCron = timezoneOffsetFromUserPrefs;
+ } else {
+ // Both old and new timezones indicate that cron should
+ // NOT run.
+ daysMissed = 0; // prevent cron running now
+ }
+ } else if (timezoneOffsetAtLastCron > timezoneOffsetFromUserPrefs) {
+ daysMissed = daysMissedNewZone;
+ // TODO: Either confirm that there is nothing that could possibly go wrong here and remove the need for this else branch, or fix stuff.
+ // There are probably situations where the Dailies do not reset early enough for a user who was expecting the zone change and wants to use all their Dailies immediately in the new zone;
+ // if so, we should provide an option for easy reset of Dailies (can't be automatic because there will be other situations where the user was not prepared).
+ }
+ }
+
+ if (daysMissed <= 0) {
+ if (user.isModified()) await user.save();
+ return null;
+ }
+
+ let _cronSignature = uuid();
+
+ // To avoid double cron we first set _cronSignature and then check that it's not changed while processing
+ let userUpdateResult = await User.update({
+ _id: user._id,
+ _cronSignature: 'NOT_RUNNING', // Check that in the meantime another cron has not started
+ }, {
+ $set: {
+ _cronSignature,
+ lastCron: now, // setting lastCron now so we don't risk re-running parts of cron if it fails
+ 'auth.timestamps.loggedin': now,
+ },
+ }).exec();
+
+ // If the cron signature is already set, cron is running in another request
+ // throw an error and recover later,
+ if (userUpdateResult.nMatched === 0 || userUpdateResult.nModified === 0) {
+ throw new Error('CRON_ALREADY_RUNNING');
+ }
+
+ let tasks = await Tasks.Task.find({
+ userId: user._id,
+ $or: [ // Exclude completed todos
+ {type: 'todo', completed: false},
+ {type: {$in: ['habit', 'daily', 'reward']}},
+ ],
+ }).exec();
+
+ let tasksByType = {habits: [], dailys: [], todos: [], rewards: []};
+ tasks.forEach(task => tasksByType[`${task.type}s`].push(task));
+
+ // Run cron
+ let progress = cron({user, tasksByType, now, daysMissed, analytics, timezoneOffsetFromUserPrefs});
+
+ // Clear old completed todos - 30 days for free users, 90 for subscribers
+ // Do not delete challenges completed todos TODO unless the task is broken?
+ Tasks.Task.remove({
+ userId: user._id,
+ type: 'todo',
+ completed: true,
+ dateCompleted: {
+ $lt: moment(now).subtract(user.isSubscribed() ? 90 : 30, 'days').toDate(),
+ },
+ 'challenge.id': {$exists: false},
+ }).exec();
+
+ res.locals.wasModified = true; // TODO remove after v2 is retired
+
+ // Group.tavernBoss(user, progress);
+
+ // Save user and tasks
+ let toSave = [user.save()];
+ tasks.forEach(task => {
+ if (task.isModified()) toSave.push(task.save());
+ });
+ await Bluebird.all(toSave);
+
+ let quest = common.content.quests[user.party.quest.key];
+
+ if (quest) {
+ // If user is on a quest, roll for boss & player, or handle collections
+ let questType = quest.boss ? 'boss' : 'collect';
+ await Group[`${questType}Quest`](user, progress);
+ }
+
+ // Set _cronSignature, lastCron and auth.timestamps.loggedin to signal end of cron
+ await User.update({
+ _id: user._id,
+ }, {
+ $set: {
+ _cronSignature: 'NOT_RUNNING',
+ },
+ }).exec();
+
+ // Reload user
+ res.locals.user = await User.findOne({_id: user._id}).exec();
+ return null;
+ } catch (err) {
+ // If cron was aborted for a race condition try to recover from it
+ if (err.message === 'CRON_ALREADY_RUNNING') {
+ // Recovering after abort, wait 300ms and reload user
+ // do it for max 5 times then reset _cronSignature so that it doesn't prevent cron from running
+ // at the next request
+ let recoveryStatus = {
+ times: 0,
+ };
+
+ await recoverCron(recoveryStatus, res.locals);
+ } else {
+ // For any other error make sure to reset _cronSignature so that it doesn't prevent cron from running
+ // at the next request
+ await User.update({
+ _id: user._id,
+ }, {
+ _cronSignature: 'NOT_RUNNING',
+ }).exec();
+
+ throw err; // re-throw the original error
+ }
+ }
+}
+
+module.exports = function cronMiddleware (req, res, next) {
+ cronAsync(req, res)
+ .then(() => {
+ next();
+ })
+ .catch(next);
+};
diff --git a/website/server/middlewares/api-v3/domain.js b/website/server/middlewares/api-v3/domain.js
new file mode 100644
index 0000000000..fb5e28d352
--- /dev/null
+++ b/website/server/middlewares/api-v3/domain.js
@@ -0,0 +1,13 @@
+import domainMiddleware from 'domain-middleware';
+
+module.exports = function implementDomainMiddleware (server, mongoose) {
+ return domainMiddleware({
+ server: {
+ close () {
+ server.close();
+ mongoose.connection.close();
+ },
+ },
+ killTimeout: 10000,
+ });
+};
diff --git a/website/server/middlewares/api-v3/ensureAccessRight.js b/website/server/middlewares/api-v3/ensureAccessRight.js
new file mode 100644
index 0000000000..2fb64ed8af
--- /dev/null
+++ b/website/server/middlewares/api-v3/ensureAccessRight.js
@@ -0,0 +1,23 @@
+import {
+ NotAuthorized,
+} from '../../libs/api-v3/errors';
+
+export function ensureAdmin (req, res, next) {
+ let user = res.locals.user;
+
+ if (!user.contributor.admin) {
+ return next(new NotAuthorized(res.t('noAdminAccess')));
+ }
+
+ next();
+}
+
+export function ensureSudo (req, res, next) {
+ let user = res.locals.user;
+
+ if (!user.contributor.sudo) {
+ return next(new NotAuthorized(res.t('noSudoAccess')));
+ }
+
+ next();
+}
diff --git a/website/server/middlewares/api-v3/ensureDevelpmentMode.js b/website/server/middlewares/api-v3/ensureDevelpmentMode.js
new file mode 100644
index 0000000000..98f70d33f5
--- /dev/null
+++ b/website/server/middlewares/api-v3/ensureDevelpmentMode.js
@@ -0,0 +1,12 @@
+import nconf from 'nconf';
+import {
+ NotFound,
+} from '../../libs/api-v3/errors';
+
+module.exports = function ensureDevelpmentMode (req, res, next) {
+ if (nconf.get('IS_PROD')) {
+ next(new NotFound());
+ } else {
+ next();
+ }
+};
diff --git a/website/server/middlewares/api-v3/errorHandler.js b/website/server/middlewares/api-v3/errorHandler.js
new file mode 100644
index 0000000000..b71fb9666e
--- /dev/null
+++ b/website/server/middlewares/api-v3/errorHandler.js
@@ -0,0 +1,90 @@
+// The error handler middleware that handles all errors
+// and respond to the client
+import logger from '../../libs/api-v3/logger';
+import {
+ CustomError,
+ BadRequest,
+ InternalServerError,
+} from '../../libs/api-v3/errors';
+import {
+ map,
+ omit,
+} from 'lodash';
+
+module.exports = function errorHandler (err, req, res, next) { // eslint-disable-line no-unused-vars
+ // In case of a CustomError class, use it's data
+ // Otherwise try to identify the type of error (mongoose validation, mongodb unique, ...)
+ // If we can't identify it, respond with a generic 500 error
+ let responseErr = err instanceof CustomError ? err : null;
+
+ // Handle errors created with 'http-errors' or similar that have a status/statusCode property
+ if (err.statusCode && typeof err.statusCode === 'number') {
+ responseErr = new CustomError();
+ responseErr.httpCode = err.statusCode;
+ responseErr.name = err.name;
+ responseErr.message = err.message;
+ }
+
+ // Handle errors by express-validator
+ if (Array.isArray(err) && err[0].param && err[0].msg) {
+ responseErr = new BadRequest(res.t('invalidReqParams'));
+ responseErr.errors = err.map((paramErr) => {
+ return {
+ message: paramErr.msg,
+ param: paramErr.param,
+ value: paramErr.value,
+ };
+ });
+ }
+
+ // Handle mongoose validation errors
+ if (err.name === 'ValidationError') {
+ responseErr = new BadRequest(err.message); // TODO standard message? translate?
+ responseErr.errors = map(err.errors, (mongooseErr) => {
+ return {
+ message: mongooseErr.message,
+ path: mongooseErr.path,
+ value: mongooseErr.value,
+ };
+ });
+ }
+
+ // Handle Stripe Card errors errors (can be safely shown to the users)
+ // https://stripe.com/docs/api/node#errors
+ if (err.type === 'StripeCardError') {
+ responseErr = new BadRequest(err.message);
+ }
+
+ if (!responseErr || responseErr.httpCode >= 500) {
+ // Try to identify the error...
+ // ...
+ // Otherwise create an InternalServerError and use it
+ // we don't want to leak anything, just a generic error message
+ // Use it also in case of identified errors but with httpCode === 500
+ responseErr = new InternalServerError();
+ }
+
+ // log the error
+ logger.error(err, {
+ method: req.method,
+ originalUrl: req.originalUrl,
+ headers: omit(req.headers, ['x-api-key', 'cookie']), // don't send sensitive information that only adds noise
+ body: req.body,
+ httpCode: responseErr.httpCode,
+ isHandledError: responseErr.httpCode < 500,
+ });
+
+ let jsonRes = {
+ success: false,
+ error: responseErr.name,
+ message: responseErr.message,
+ };
+
+ if (responseErr.errors) {
+ jsonRes.errors = responseErr.errors;
+ }
+
+ // In some occasions like when invalid JSON is supplied `res.respond` might be not yet avalaible,
+ // in this case we use the standard res.status(...).json(...)
+ return res.status(responseErr.httpCode).json(jsonRes);
+};
diff --git a/website/server/middlewares/api-v3/index.js b/website/server/middlewares/api-v3/index.js
new file mode 100644
index 0000000000..694fdb6ea5
--- /dev/null
+++ b/website/server/middlewares/api-v3/index.js
@@ -0,0 +1,87 @@
+// This module is only used to attach middlewares to the express app
+import errorHandler from './errorHandler';
+import bodyParser from 'body-parser';
+import notFoundHandler from './notFound';
+import nconf from 'nconf';
+import morgan from 'morgan';
+import cookieSession from 'cookie-session';
+import cors from './cors';
+import staticMiddleware from './static';
+import domainMiddleware from './domain';
+import mongoose from 'mongoose';
+import compression from 'compression';
+import favicon from 'serve-favicon';
+import methodOverride from 'method-override';
+import passport from 'passport';
+import path from 'path';
+import maintenanceMode from './maintenanceMode';
+import {
+ forceSSL,
+ forceHabitica,
+} from './redirects';
+import v1 from './v1';
+import v2 from './v2';
+import v3 from './v3';
+import responseHandler from './response';
+import {
+ attachTranslateFunction,
+} from './language';
+
+const IS_PROD = nconf.get('IS_PROD');
+const DISABLE_LOGGING = nconf.get('DISABLE_REQUEST_LOGGING');
+const PUBLIC_DIR = path.join(__dirname, '/../../../client');
+
+const SESSION_SECRET = nconf.get('SESSION_SECRET');
+const TWO_WEEKS = 1000 * 60 * 60 * 24 * 14;
+
+module.exports = function attachMiddlewares (app, server) {
+ app.set('view engine', 'jade');
+ app.set('views', `${__dirname}/../views`);
+
+ app.use(domainMiddleware(server, mongoose));
+
+ if (!IS_PROD && !DISABLE_LOGGING) app.use(morgan('dev'));
+
+ // add res.respond and res.t
+ app.use(responseHandler);
+ app.use(attachTranslateFunction);
+
+ app.use(compression());
+ app.use(favicon(`${PUBLIC_DIR}/favicon.ico`));
+
+ app.use(maintenanceMode);
+
+ app.use(cors);
+ app.use(forceSSL);
+ app.use(forceHabitica);
+
+ app.use(bodyParser.urlencoded({
+ extended: true, // Uses 'qs' library as old connect middleware
+ }));
+ app.use(bodyParser.json());
+ app.use(methodOverride());
+
+ app.use(cookieSession({
+ name: 'connect:sess', // Used to keep backward compatibility with Express 3 cookies
+ secret: SESSION_SECRET,
+ httpOnly: true, // so cookies are not accessible with browser JS
+ // TODO what about https only (secure) ?
+ maxAge: TWO_WEEKS,
+ }));
+
+ // Initialize Passport! Also use passport.session() middleware, to support
+ // persistent login sessions (recommended).
+ app.use(passport.initialize());
+ app.use(passport.session());
+
+ app.use('/api/v2', v2);
+ app.use('/api/v1', v1);
+ app.use(v3); // the main app, also setup top-level routes
+ staticMiddleware(app);
+
+ app.use(notFoundHandler);
+
+ // Error handler middleware, define as the last one.
+ // Used for v3 and v1, v2 will keep using its own error handler
+ app.use(errorHandler);
+};
diff --git a/website/server/middlewares/api-v3/language.js b/website/server/middlewares/api-v3/language.js
new file mode 100644
index 0000000000..b83c9d5208
--- /dev/null
+++ b/website/server/middlewares/api-v3/language.js
@@ -0,0 +1,91 @@
+import { model as User } from '../../models/user';
+import accepts from 'accepts';
+import common from '../../../../common';
+import _ from 'lodash';
+import {
+ translations,
+ defaultLangCodes,
+ multipleVersionsLanguages,
+} from '../../libs/api-v3/i18n';
+
+const i18n = common.i18n;
+
+function _getUniqueListOfLanguages (languages) {
+ let acceptableLanguages = _(languages).map((lang) => {
+ return lang.slice(0, 2);
+ }).uniq().value();
+
+ let uniqueListOfLanguages = _.intersection(acceptableLanguages, defaultLangCodes);
+
+ return uniqueListOfLanguages;
+}
+
+function _checkForApplicableLanguageVariant (originalLanguageOptions) {
+ let languageVariant = _.find(originalLanguageOptions, (accepted) => {
+ let trimmedAccepted = accepted.slice(0, 2);
+
+ return multipleVersionsLanguages[trimmedAccepted];
+ });
+
+ return languageVariant;
+}
+
+function _getFromBrowser (req) {
+ let originalLanguageOptions = accepts(req).languages();
+ let uniqueListOfLanguages = _getUniqueListOfLanguages(originalLanguageOptions);
+ let baseLanguage = (uniqueListOfLanguages[0] || '').toLowerCase();
+ let languageMapping = multipleVersionsLanguages[baseLanguage];
+
+ if (languageMapping) {
+ let languageVariant = _checkForApplicableLanguageVariant(originalLanguageOptions);
+
+ if (languageVariant) {
+ languageVariant = languageVariant.toLowerCase();
+ } else {
+ return 'en';
+ }
+
+ return languageMapping[languageVariant] || baseLanguage;
+ } else {
+ return baseLanguage || 'en';
+ }
+}
+
+function _getFromUser (user, req) {
+ let preferredLang = user && user.preferences && user.preferences.language;
+ let lang = translations[preferredLang] ? preferredLang : _getFromBrowser(req);
+
+ return lang;
+}
+
+export function attachTranslateFunction (req, res, next) {
+ res.t = function reqTranslation () {
+ return i18n.t(...arguments, req.language);
+ };
+
+ next();
+}
+
+export function getUserLanguage (req, res, next) {
+ if (req.query.lang) { // In case the language is specified in the request url, use it
+ req.language = translations[req.query.lang] ? req.query.lang : 'en';
+ return next();
+ } else if (req.locals && req.locals.user) { // If the request is authenticated, use the user's preferred language
+ req.language = _getFromUser(req.locals.user, req);
+ return next();
+ } else if (req.session && req.session.userId) { // Same thing if the user has a valid session
+ return User.findOne({
+ _id: req.session.userId,
+ }, 'preferences.language')
+ .lean()
+ .exec()
+ .then((user) => {
+ req.language = _getFromUser(user, req);
+ return next();
+ })
+ .catch(next);
+ } else { // Otherwise get from browser
+ req.language = _getFromUser(null, req);
+ return next();
+ }
+}
diff --git a/website/server/middlewares/api-v3/locals.js b/website/server/middlewares/api-v3/locals.js
new file mode 100644
index 0000000000..e6e06e3b16
--- /dev/null
+++ b/website/server/middlewares/api-v3/locals.js
@@ -0,0 +1,61 @@
+import nconf from 'nconf';
+import _ from 'lodash';
+import shared from '../../../../common';
+import * as i18n from '../../libs/api-v3/i18n';
+import {
+ getBuildUrl,
+ getManifestFiles,
+} from '../../libs/api-v3/buildManifest';
+import forceRefresh from './../forceRefresh';
+import { tavernQuest } from '../../models/group';
+import { mods } from '../../models/user';
+
+// To avoid stringifying more data then we need,
+// items from `env` used on the client will have to be specified in this array
+const CLIENT_VARS = ['language', 'isStaticPage', 'availableLanguages', 'translations',
+ 'FACEBOOK_KEY', 'NODE_ENV', 'BASE_URL', 'GA_ID',
+ 'AMAZON_PAYMENTS', 'STRIPE_PUB_KEY', 'AMPLITUDE_KEY',
+ 'worldDmg', 'mods', 'IS_MOBILE'];
+
+let env = {
+ getManifestFiles,
+ getBuildUrl,
+ _,
+ clientVars: CLIENT_VARS,
+ mods,
+ Content: shared.content,
+ siteVersion: forceRefresh.siteVersion,
+ availableLanguages: i18n.availableLanguages,
+ AMAZON_PAYMENTS: {
+ SELLER_ID: nconf.get('AMAZON_PAYMENTS:SELLER_ID'),
+ CLIENT_ID: nconf.get('AMAZON_PAYMENTS:CLIENT_ID'),
+ },
+};
+
+'NODE_ENV BASE_URL GA_ID STRIPE_PUB_KEY FACEBOOK_KEY AMPLITUDE_KEY'.split(' ').forEach(key => {
+ env[key] = nconf.get(key);
+});
+
+module.exports = function locals (req, res, next) {
+ let language = _.find(i18n.availableLanguages, {code: req.language});
+ let isStaticPage = req.url.split('/')[1] === 'static'; // If url contains '/static/'
+
+ // Load moment.js language file only when not on static pages
+ language.momentLang = !isStaticPage && i18n.momentLangs[language.code] || undefined;
+
+ res.locals.habitrpg = _.assign(env, {
+ IS_MOBILE: /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(req.header('User-Agent')),
+ language,
+ isStaticPage,
+ translations: i18n.translations[language.code],
+ t (...args) { // stringName and vars are the allowed parameters
+ args.push(language.code);
+ return shared.i18n.t(...args);
+ },
+ // Defined here and not outside of the middleware because tavernQuest might be an
+ // empty object until the query to fetch it finishes
+ worldDmg: tavernQuest && tavernQuest.extra && tavernQuest.extra.worldDmg || {},
+ });
+
+ next();
+};
diff --git a/website/server/middlewares/api-v3/maintenanceMode.js b/website/server/middlewares/api-v3/maintenanceMode.js
new file mode 100644
index 0000000000..331663125a
--- /dev/null
+++ b/website/server/middlewares/api-v3/maintenanceMode.js
@@ -0,0 +1,31 @@
+import { getUserLanguage } from './language';
+import nconf from 'nconf';
+
+const MAINTENANCE_MODE = nconf.get('MAINTENANCE_MODE');
+
+module.exports = function maintenanceMode (req, res, next) {
+ if (MAINTENANCE_MODE !== 'true') return next();
+
+ getUserLanguage(req, res, (err) => {
+ if (err) return next(err);
+
+ let pageVariables = {
+ maintenanceStart: nconf.get('MAINTENANCE_START'),
+ maintenanceEnd: nconf.get('MAINTENANCE_END'),
+ translation: res.t,
+ };
+
+ if (req.headers && req.headers.accept && req.headers.accept.indexOf('text/html') !== -1) {
+ if (req.path === '/views/static/maintenance-info') {
+ return res.status(503).render('../../../views/static/maintenance-info', pageVariables);
+ } else {
+ return res.status(503).render('../../../views/static/maintenance', pageVariables);
+ }
+ } else {
+ return res.status(503).send({
+ error: 'Maintenance',
+ message: 'Server offline for maintenance.',
+ });
+ }
+ });
+};
diff --git a/website/server/middlewares/api-v3/notFound.js b/website/server/middlewares/api-v3/notFound.js
new file mode 100644
index 0000000000..6a71b5def4
--- /dev/null
+++ b/website/server/middlewares/api-v3/notFound.js
@@ -0,0 +1,7 @@
+import {
+ NotFound,
+} from '../../libs/api-v3/errors';
+
+module.exports = function NotFoundMiddleware (req, res, next) {
+ next(new NotFound());
+};
diff --git a/website/server/middlewares/api-v3/redirects.js b/website/server/middlewares/api-v3/redirects.js
new file mode 100644
index 0000000000..a907a89b14
--- /dev/null
+++ b/website/server/middlewares/api-v3/redirects.js
@@ -0,0 +1,43 @@
+import nconf from 'nconf';
+
+const IS_PROD = nconf.get('IS_PROD');
+const IGNORE_REDIRECT = nconf.get('IGNORE_REDIRECT');
+const BASE_URL = nconf.get('BASE_URL');
+
+function isHTTP (req) {
+ return ( // eslint-disable-line no-extra-parens
+ req.header('x-forwarded-proto') &&
+ req.header('x-forwarded-proto') !== 'https' &&
+ IS_PROD &&
+ BASE_URL.indexOf('https') === 0
+ );
+}
+
+function isProxied (req) {
+ return ( // eslint-disable-line no-extra-parens
+ req.header('x-habitica-lb') &&
+ req.header('x-habitica-lb') === 'Yes'
+ );
+}
+
+export function forceSSL (req, res, next) {
+ if (isHTTP(req) && !isProxied(req)) {
+ return res.redirect(BASE_URL + req.originalUrl);
+ }
+
+ next();
+}
+
+// Redirect to habitica for non-api urls
+
+function nonApiUrl (req) {
+ return req.originalUrl.search(/\/api\//) === -1;
+}
+
+export function forceHabitica (req, res, next) {
+ if (IS_PROD && !IGNORE_REDIRECT && !isProxied(req) && nonApiUrl(req)) {
+ return res.redirect(301, BASE_URL + req.url);
+ }
+
+ next();
+}
diff --git a/website/server/middlewares/api-v3/response.js b/website/server/middlewares/api-v3/response.js
new file mode 100644
index 0000000000..e00d691fb2
--- /dev/null
+++ b/website/server/middlewares/api-v3/response.js
@@ -0,0 +1,25 @@
+module.exports = function responseHandler (req, res, next) {
+ // Only used for successful responses
+ res.respond = function respond (status = 200, data = {}, message) {
+ let user = res.locals && res.locals.user;
+
+ let response = {
+ success: status < 400,
+ data,
+ };
+
+ if (message) response.message = message;
+
+ // When userV=Number (user version) query parameter is passed and a user is logged in,
+ // sends back the current user._v in the response so that the client
+ // can verify if it's the most up to date data.
+ // Considered part of the private API for now and not officially supported
+ if (user && req.query.userV) {
+ response.userV = user._v;
+ }
+
+ res.status(status).json(response);
+ };
+
+ next();
+};
diff --git a/website/server/middlewares/api-v3/setupBody.js b/website/server/middlewares/api-v3/setupBody.js
new file mode 100644
index 0000000000..b3309fb2da
--- /dev/null
+++ b/website/server/middlewares/api-v3/setupBody.js
@@ -0,0 +1,5 @@
+// TODO test this middleware
+module.exports = function setupBodyMiddleware (req, res, next) {
+ req.body = req.body || {};
+ next();
+};
diff --git a/website/server/middlewares/api-v3/static.js b/website/server/middlewares/api-v3/static.js
new file mode 100644
index 0000000000..19c1c9025c
--- /dev/null
+++ b/website/server/middlewares/api-v3/static.js
@@ -0,0 +1,18 @@
+import express from 'express';
+import nconf from 'nconf';
+import path from 'path';
+
+const IS_PROD = nconf.get('IS_PROD');
+const MAX_AGE = IS_PROD ? 31536000000 : 0;
+const PUBLIC_DIR = path.join(__dirname, '/../../../client');
+const BUILD_DIR = path.join(__dirname, '/../../../build');
+
+module.exports = function staticMiddleware (expressApp) {
+ // TODO move all static files to a single location (one for public and one for build)
+ expressApp.use(express.static(BUILD_DIR, { maxAge: MAX_AGE }));
+ expressApp.use('/common/dist', express.static(`${PUBLIC_DIR}/../../common/dist`, { maxAge: MAX_AGE }));
+ expressApp.use('/common/audio', express.static(`${PUBLIC_DIR}/../../common/audio`, { maxAge: MAX_AGE }));
+ expressApp.use('/common/script/public', express.static(`${PUBLIC_DIR}/../../common/script/public`, { maxAge: MAX_AGE }));
+ expressApp.use('/common/img', express.static(`${PUBLIC_DIR}/../../common/img`, { maxAge: MAX_AGE }));
+ expressApp.use(express.static(PUBLIC_DIR));
+};
diff --git a/website/server/middlewares/api-v3/v1.js b/website/server/middlewares/api-v3/v1.js
new file mode 100644
index 0000000000..abe26f42e6
--- /dev/null
+++ b/website/server/middlewares/api-v3/v1.js
@@ -0,0 +1,19 @@
+// API v1 middlewares and routes
+// DEPRECATED AND INACTIVE
+
+import express from 'express';
+import nconf from 'nconf';
+import {
+ NotFound,
+} from '../../libs/api-v3/errors';
+
+const router = express.Router(); // eslint-disable-line babel/new-cap
+
+const BASE_URL = nconf.get('BASE_URL');
+
+router.all('*', function deprecatedV1 (req, res, next) {
+ let error = new NotFound(`API v1 is no longer supported, please use API v3 instead (${BASE_URL}/static/api).`);
+ return next(error);
+});
+
+module.exports = router;
diff --git a/website/server/middlewares/api-v3/v2.js b/website/server/middlewares/api-v3/v2.js
new file mode 100644
index 0000000000..ec49e326a4
--- /dev/null
+++ b/website/server/middlewares/api-v3/v2.js
@@ -0,0 +1,27 @@
+// DEPRECATED BUT STILL ACTIVE
+
+// import path from 'path';
+import swagger from 'swagger-node-express';
+// import shared from '../../../../common';
+import express from 'express';
+import analytics from './analytics';
+import responseHandler from './response';
+
+const v2app = express();
+
+// re-set the view options because they are not inherited from the top level app
+v2app.set('view engine', 'jade');
+v2app.set('views', `${__dirname}/../../../views`);
+
+v2app.use(analytics);
+v2app.use(responseHandler);
+
+
+// Custom Directives
+v2app.use('/', require('../../routes/api-v2/auth'));
+
+require('../../routes/api-v2/swagger')(swagger, v2app);
+
+v2app.use(require('../api-v2/errorHandler'));
+
+module.exports = v2app;
diff --git a/website/server/middlewares/api-v3/v3.js b/website/server/middlewares/api-v3/v3.js
new file mode 100644
index 0000000000..f9aa637fa0
--- /dev/null
+++ b/website/server/middlewares/api-v3/v3.js
@@ -0,0 +1,30 @@
+import express from 'express';
+import expressValidator from 'express-validator';
+import analytics from './analytics';
+import setupBody from './setupBody';
+import routes from '../../libs/api-v3/routes';
+import path from 'path';
+
+const API_CONTROLLERS_PATH = path.join(__dirname, '/../../controllers/api-v3/');
+const TOP_LEVEL_CONTROLLERS_PATH = path.join(__dirname, '/../../controllers/top-level/');
+
+const v3app = express();
+
+// re-set the view options because they are not inherited from the top level app
+v3app.set('view engine', 'jade');
+v3app.set('views', `${__dirname}/../../../views`);
+
+v3app.use(expressValidator());
+v3app.use(analytics);
+v3app.use(setupBody);
+
+const topLevelRouter = express.Router(); // eslint-disable-line babel/new-cap
+
+routes.walkControllers(topLevelRouter, TOP_LEVEL_CONTROLLERS_PATH);
+v3app.use('/', topLevelRouter);
+
+const v3Router = express.Router(); // eslint-disable-line babel/new-cap
+routes.walkControllers(v3Router, API_CONTROLLERS_PATH);
+v3app.use('/api/v3', v3Router);
+
+module.exports = v3app;
diff --git a/website/server/middlewares/apiThrottle.js b/website/server/middlewares/apiThrottle.js
new file mode 100644
index 0000000000..b392cc777e
--- /dev/null
+++ b/website/server/middlewares/apiThrottle.js
@@ -0,0 +1,26 @@
+var nconf = require('nconf');
+var limiter = require('connect-ratelimit');
+
+var IS_PROD = nconf.get('NODE_ENV') === 'production';
+
+// TODO since Habitica runs on many different servers this module is pretty useless
+// as it will only block requests that go to the same server but anyway we should probably have a rate limiter in place
+
+module.exports = function(app) {
+ // disable the rate limiter middleware
+ if (/*!IS_PROD || */true) return;
+ app.use(limiter({
+ end:false,
+ categories:{
+ normal: {
+ // 2 req/s, but split as minutes
+ totalRequests: 80,
+ every: 60000
+ }
+ }
+ })).use(function(req,res,next){
+ //logging.info(res.ratelimit);
+ if (res.ratelimit.exceeded) return res.status(429).json({err:'Rate limit exceeded'});
+ next();
+ });
+};
diff --git a/website/src/middlewares/forceRefresh.js b/website/server/middlewares/forceRefresh.js
similarity index 83%
rename from website/src/middlewares/forceRefresh.js
rename to website/server/middlewares/forceRefresh.js
index 6d5b4fa8c9..f843694790 100644
--- a/website/src/middlewares/forceRefresh.js
+++ b/website/server/middlewares/forceRefresh.js
@@ -1,3 +1,5 @@
+// TODO do we need this module anymore in v3? No
+
module.exports.siteVersion = 1;
module.exports.middleware = function(req, res, next){
diff --git a/website/server/models/challenge.js b/website/server/models/challenge.js
new file mode 100644
index 0000000000..511c969429
--- /dev/null
+++ b/website/server/models/challenge.js
@@ -0,0 +1,426 @@
+import mongoose from 'mongoose';
+import Bluebird from 'bluebird';
+import validator from 'validator';
+import baseModel from '../libs/api-v3/baseModel';
+import _ from 'lodash';
+import * as Tasks from './task';
+import { model as User } from './user';
+import {
+ model as Group,
+ TAVERN_ID,
+} from './group';
+import { removeFromArray } from '../libs/api-v3/collectionManipulators';
+import shared from '../../../common';
+import { sendTxn as txnEmail } from '../libs/api-v3/email';
+import sendPushNotification from '../libs/api-v3/pushNotifications';
+import cwait from 'cwait';
+
+let Schema = mongoose.Schema;
+
+let schema = new Schema({
+ name: {type: String, required: true},
+ shortName: {type: String, required: true, minlength: 3},
+ description: String,
+ official: {type: Boolean, default: false},
+ tasksOrder: {
+ habits: [{type: String, ref: 'Task'}],
+ dailys: [{type: String, ref: 'Task'}],
+ todos: [{type: String, ref: 'Task'}],
+ rewards: [{type: String, ref: 'Task'}],
+ },
+ leader: {type: String, ref: 'User', validate: [validator.isUUID, 'Invalid uuid.'], required: true},
+ group: {type: String, ref: 'Group', validate: [validator.isUUID, 'Invalid uuid.'], required: true},
+ memberCount: {type: Number, default: 1},
+ prize: {type: Number, default: 0, min: 0},
+}, {
+ strict: true,
+ minimize: false, // So empty objects are returned
+});
+
+schema.plugin(baseModel, {
+ noSet: ['_id', 'memberCount', 'tasksOrder'],
+ timestamps: true,
+});
+
+// A list of additional fields that cannot be updated (but can be set on creation)
+let noUpdate = ['group', 'official', 'shortName', 'prize'];
+schema.statics.sanitizeUpdate = function sanitizeUpdate (updateObj) {
+ return this.sanitize(updateObj, noUpdate);
+};
+
+// Returns true if user is a member of the challenge
+schema.methods.isMember = function isChallengeMember (user) {
+ return user.challenges.indexOf(this._id) !== -1;
+};
+
+// Returns true if the user can modify (close, selectWinner, ...) the challenge
+schema.methods.canModify = function canModifyChallenge (user) {
+ return user.contributor.admin || this.leader === user._id;
+};
+
+// Returns true if user has access to the challenge (can join)
+schema.methods.hasAccess = function hasAccessToChallenge (user, group) {
+ if (group.type === 'guild' && group.privacy === 'public') return true;
+ return user.getGroups().indexOf(this.group) !== -1;
+};
+
+// Returns true if user can view the challenge
+// Different from hasAccess because you can see challenges of groups you've been removed from if you're partecipating in them
+schema.methods.canView = function canViewChallenge (user, group) {
+ if (this.isMember(user)) return true;
+ return this.hasAccess(user, group);
+};
+
+// Takes a Task document and return a plain object of attributes that can be synced to the user
+function _syncableAttrs (task) {
+ let t = task.toObject(); // lodash doesn't seem to like _.omit on Document
+ // only sync/compare important attrs
+ let omitAttrs = ['_id', 'userId', 'challenge', 'history', 'tags', 'completed', 'streak', 'notes', 'updatedAt'];
+ if (t.type !== 'reward') omitAttrs.push('value');
+ return _.omit(t, omitAttrs);
+}
+
+// Sync challenge to user, including tasks and tags.
+// Used when user joins the challenge or to force sync.
+schema.methods.syncToUser = async function syncChallengeToUser (user) {
+ let challenge = this;
+ challenge.shortName = challenge.shortName || challenge.name;
+
+ // Add challenge to user.challenges
+ if (!_.contains(user.challenges, challenge._id)) user.challenges.push(challenge._id);
+
+ // Sync tags
+ let userTags = user.tags;
+ let i = _.findIndex(userTags, {id: challenge._id});
+
+ if (i !== -1) {
+ if (userTags[i].name !== challenge.shortName) {
+ // update the name - it's been changed since
+ userTags[i].name = challenge.shortName;
+ }
+ } else {
+ userTags.push({
+ id: challenge._id,
+ name: challenge.shortName,
+ challenge: true,
+ });
+ }
+
+ let [challengeTasks, userTasks] = await Bluebird.all([
+ // Find original challenge tasks
+ Tasks.Task.find({
+ userId: {$exists: false},
+ 'challenge.id': challenge._id,
+ }).exec(),
+ // Find user's tasks linked to this challenge
+ Tasks.Task.find({
+ userId: user._id,
+ 'challenge.id': challenge._id,
+ }).exec(),
+ ]);
+
+ let toSave = []; // An array of things to save
+
+ challengeTasks.forEach(chalTask => {
+ let matchingTask = _.find(userTasks, userTask => userTask.challenge.taskId === chalTask._id);
+
+ if (!matchingTask) { // If the task is new, create it
+ matchingTask = new Tasks[chalTask.type](Tasks.Task.sanitize(_syncableAttrs(chalTask)));
+ matchingTask.challenge = {taskId: chalTask._id, id: challenge._id};
+ matchingTask.userId = user._id;
+ user.tasksOrder[`${chalTask.type}s`].push(matchingTask._id);
+ } else {
+ _.merge(matchingTask, _syncableAttrs(chalTask));
+ // Make sure the task is in user.tasksOrder
+ let orderList = user.tasksOrder[`${chalTask.type}s`];
+ if (orderList.indexOf(matchingTask._id) === -1 && (matchingTask.type !== 'todo' || !matchingTask.completed)) orderList.push(matchingTask._id);
+ }
+
+ if (!matchingTask.notes) matchingTask.notes = chalTask.notes; // don't override the notes, but provide it if not provided
+ if (matchingTask.tags.indexOf(challenge._id) === -1) matchingTask.tags.push(challenge._id); // add tag if missing
+ toSave.push(matchingTask.save());
+ });
+
+ // Flag deleted tasks as "broken"
+ userTasks.forEach(userTask => {
+ if (!_.find(challengeTasks, chalTask => chalTask._id === userTask.challenge.taskId)) {
+ userTask.challenge.broken = 'TASK_DELETED';
+ toSave.push(userTask.save());
+ }
+ });
+
+ toSave.push(user.save());
+ return Bluebird.all(toSave);
+};
+
+async function _fetchMembersIds (challengeId) {
+ return (await User.find({challenges: {$in: [challengeId]}}).select('_id').lean().exec()).map(member => member._id);
+}
+
+async function _addTaskFn (challenge, tasks, memberId) {
+ let updateTasksOrderQ = {$push: {}};
+ let toSave = [];
+
+ tasks.forEach(chalTask => {
+ let userTask = new Tasks[chalTask.type](Tasks.Task.sanitize(_syncableAttrs(chalTask)));
+ userTask.challenge = {taskId: chalTask._id, id: challenge._id};
+ userTask.userId = memberId;
+
+ let tasksOrderList = updateTasksOrderQ.$push[`tasksOrder.${chalTask.type}s`];
+ if (!tasksOrderList) {
+ updateTasksOrderQ.$push[`tasksOrder.${chalTask.type}s`] = {
+ $position: 0, // unshift
+ $each: [userTask._id],
+ };
+ } else {
+ tasksOrderList.$each.unshift(userTask._id);
+ }
+
+ toSave.push(userTask.save({
+ validateBeforeSave: false, // no user data supplied
+ }));
+ });
+
+ // Update the user
+ toSave.unshift(User.update({_id: memberId}, updateTasksOrderQ).exec());
+ return await Bluebird.all(toSave);
+}
+
+// Add a new task to challenge members
+schema.methods.addTasks = async function challengeAddTasks (tasks) {
+ let challenge = this;
+ let membersIds = await _fetchMembersIds(challenge._id);
+
+ let queue = new cwait.TaskQueue(Bluebird, 25); // process only 5 users concurrently
+
+ await Bluebird.map(membersIds, queue.wrap((memberId) => {
+ return _addTaskFn(challenge, tasks, memberId);
+ }));
+};
+
+// Sync updated task to challenge members
+schema.methods.updateTask = async function challengeUpdateTask (task) {
+ let challenge = this;
+
+ let updateCmd = {$set: {}};
+
+ let syncableAttrs = _syncableAttrs(task);
+ for (let key in syncableAttrs) {
+ updateCmd.$set[key] = syncableAttrs[key];
+ }
+
+ // Updating instead of loading and saving for performances, risks becoming a problem if we introduce more complexity in tasks
+ await Tasks.Task.update({
+ userId: {$exists: true},
+ 'challenge.id': challenge.id,
+ 'challenge.taskId': task._id,
+ }, updateCmd, {multi: true}).exec();
+};
+
+// Remove a task from challenge members
+schema.methods.removeTask = async function challengeRemoveTask (task) {
+ let challenge = this;
+
+ // Set the task as broken
+ await Tasks.Task.update({
+ userId: {$exists: true},
+ 'challenge.id': challenge.id,
+ 'challenge.taskId': task._id,
+ }, {
+ $set: {'challenge.broken': 'TASK_DELETED'},
+ }, {multi: true}).exec();
+};
+
+// Unlink challenges tasks (and the challenge itself) from user. TODO rename to 'leave'
+schema.methods.unlinkTasks = async function challengeUnlinkTasks (user, keep) {
+ let challengeId = this._id;
+ let findQuery = {
+ userId: user._id,
+ 'challenge.id': challengeId,
+ };
+
+ removeFromArray(user.challenges, challengeId);
+
+ if (keep === 'keep-all') {
+ await Tasks.Task.update(findQuery, {
+ $set: {challenge: {}},
+ }, {multi: true}).exec();
+
+ await user.save();
+ } else { // keep = 'remove-all'
+ let tasks = await Tasks.Task.find(findQuery).select('_id type completed').exec();
+ let taskPromises = tasks.map(task => {
+ // Remove task from user.tasksOrder and delete them
+ if (task.type !== 'todo' || !task.completed) {
+ removeFromArray(user.tasksOrder[`${task.type}s`], task._id);
+ }
+
+ return task.remove();
+ });
+ user.markModified('tasksOrder');
+ taskPromises.push(user.save());
+ return Bluebird.all(taskPromises);
+ }
+};
+
+// TODO everything here should be moved to a worker
+// actually even for a worker it's probably just too big and will kill mongo, figure out something else
+schema.methods.closeChal = async function closeChal (broken = {}) {
+ let challenge = this;
+
+ let winner = broken.winner;
+ let brokenReason = broken.broken;
+
+ // Delete the challenge
+ await this.model('Challenge').remove({_id: challenge._id}).exec();
+
+ // Refund the leader if the challenge is closed and the group not the tavern
+ if (challenge.group !== TAVERN_ID && brokenReason === 'CHALLENGE_DELETED') {
+ await User.update({_id: challenge.leader}, {$inc: {balance: challenge.prize / 4}}).exec();
+ }
+
+ // Update the challengeCount on the group
+ await Group.update({_id: challenge.group}, {$inc: {challengeCount: -1}}).exec();
+
+ // Award prize to winner and notify
+ if (winner) {
+ winner.achievements.challenges.push(challenge.name);
+ winner.balance += challenge.prize / 4;
+ let savedWinner = await winner.save();
+ if (savedWinner.preferences.emailNotifications.wonChallenge !== false) {
+ txnEmail(savedWinner, 'won-challenge', [
+ {name: 'CHALLENGE_NAME', content: challenge.name},
+ ]);
+ }
+
+ sendPushNotification(savedWinner, shared.i18n.t('wonChallenge'), challenge.name);
+ }
+
+ // Run some operations in the background withouth blocking the thread
+ let backgroundTasks = [
+ // And it's tasks
+ Tasks.Task.remove({'challenge.id': challenge._id, userId: {$exists: false}}).exec(),
+ // Set the challenge tag to non-challenge status and remove the challenge from the user's challenges
+ User.update({
+ challenges: challenge._id,
+ 'tags._id': challenge._id,
+ }, {
+ $set: {'tags.$.challenge': false},
+ $pull: {challenges: challenge._id},
+ }, {multi: true}).exec(),
+ // Break users' tasks
+ Tasks.Task.update({
+ 'challenge.id': challenge._id,
+ }, {
+ $set: {
+ 'challenge.broken': brokenReason,
+ 'challenge.winner': winner && winner.profile.name,
+ },
+ }, {multi: true}).exec(),
+ ];
+
+ Bluebird.all(backgroundTasks);
+};
+
+// Methods to adapt the new schema to API v2 responses (mostly tasks inside the challenge model)
+// These will be removed once API v2 is discontinued
+
+// Get all the tasks belonging to a challenge,
+schema.methods.getTasks = function getChallengeTasks () {
+ let args = Array.from(arguments);
+ let cb;
+ let type;
+
+ if (args.length === 1) {
+ cb = args[0];
+ } else if (args.length > 1) {
+ type = args[0];
+ cb = args[1];
+ } else {
+ cb = function noop () {};
+ }
+
+ let query = {
+ userId: {
+ $exists: false,
+ },
+
+ 'challenge.id': this._id,
+ };
+
+ if (type) query.type = type;
+
+ return Tasks.Task.find(query, cb); // so we can use it as a promise
+};
+
+// Given challenge and an array of tasks and one of members return an API compatible challenge + tasks obj + members
+schema.methods.addToChallenge = function addToChallenge (tasks, members) {
+ let obj = this.toJSON();
+ obj.members = members;
+
+ let tasksOrder = obj.tasksOrder; // Saving a reference because we won't return it
+
+ obj.habits = [];
+ obj.dailys = [];
+ obj.todos = [];
+ obj.rewards = [];
+
+ obj.tasksOrder = undefined;
+ let unordered = [];
+
+ tasks.forEach((task) => {
+ // We want to push the task at the same position where it's stored in tasksOrder
+ let pos = tasksOrder[`${task.type}s`].indexOf(task._id);
+ if (pos === -1) { // Should never happen, it means the lists got out of sync
+ unordered.push(task.toJSONV2());
+ } else {
+ obj[`${task.type}s`][pos] = task.toJSONV2();
+ }
+ });
+
+ // Reconcile unordered items
+ unordered.forEach((task) => {
+ obj[`${task.type}s`].push(task);
+ });
+
+ // Remove null values that can be created when inserting tasks at an index > length
+ ['habits', 'dailys', 'rewards', 'todos'].forEach((type) => {
+ obj[type] = _.compact(obj[type]);
+ });
+
+ return obj;
+};
+
+// Return the data maintaining backward compatibility
+schema.methods.getTransformedData = function getTransformedData (options) {
+ let self = this;
+
+ let cb = options.cb;
+ let populateMembers = options.populateMembers;
+
+ let queryMembers = {
+ challenges: self._id,
+ };
+
+ let selectDataMembers = '_id';
+
+ if (populateMembers) {
+ selectDataMembers += ` ${populateMembers}`;
+ }
+
+ let membersQuery = User.find(queryMembers).select(selectDataMembers);
+ if (options.limitPopulation) membersQuery.limit(15);
+
+ Bluebird.all([
+ membersQuery.exec(),
+ self.getTasks(),
+ ])
+ .then((results) => {
+ cb(null, self.addToChallenge(results[1], results[0]));
+ })
+ .catch(cb);
+};
+
+// END of API v2 methods
+
+export let model = mongoose.model('Challenge', schema);
diff --git a/website/server/models/coupon.js b/website/server/models/coupon.js
new file mode 100644
index 0000000000..af9953aba0
--- /dev/null
+++ b/website/server/models/coupon.js
@@ -0,0 +1,57 @@
+/* eslint-disable camelcase */
+
+import mongoose from 'mongoose';
+import _ from 'lodash';
+import shared from '../../../common';
+import couponCode from 'coupon-code';
+import baseModel from '../libs/api-v3/baseModel';
+import {
+ BadRequest,
+ NotAuthorized,
+} from '../libs/api-v3/errors';
+
+export let schema = new mongoose.Schema({
+ _id: {type: String, default: couponCode.generate},
+ event: {type: String, enum: ['wondercon', 'google_6mo']},
+ user: {type: String, ref: 'User'},
+}, {
+ strict: true,
+ minimize: false, // So empty objects are returned
+});
+
+schema.plugin(baseModel, {
+ timestamps: true,
+ _id: false,
+});
+
+schema.statics.generate = async function generateCoupons (event, count = 1) {
+ let coupons = _.times(count, () => {
+ return {event};
+ });
+
+ return await this.create(coupons);
+};
+
+schema.statics.apply = async function applyCoupon (user, req, code) {
+ let coupon = await this.findById(couponCode.validate(code)).exec();
+ if (!coupon) throw new BadRequest(shared.i18n.t('invalidCoupon', req.language));
+ if (coupon.user) throw new NotAuthorized(shared.i18n.t('couponUsed', req.language));
+
+ if (coupon.event === 'wondercon') {
+ user.items.gear.owned.eyewear_special_wondercon_red = true;
+ user.items.gear.owned.eyewear_special_wondercon_black = true;
+ user.items.gear.owned.back_special_wondercon_black = true;
+ user.items.gear.owned.back_special_wondercon_red = true;
+ user.items.gear.owned.body_special_wondercon_red = true;
+ user.items.gear.owned.body_special_wondercon_black = true;
+ user.items.gear.owned.body_special_wondercon_gold = true;
+ user.extra = {signupEvent: 'wondercon'};
+ }
+
+ await user.save();
+ coupon.user = user._id;
+ await coupon.save();
+};
+
+module.exports.schema = schema;
+export let model = mongoose.model('Coupon', schema);
diff --git a/website/server/models/emailUnsubscription.js b/website/server/models/emailUnsubscription.js
new file mode 100644
index 0000000000..fe30e5d608
--- /dev/null
+++ b/website/server/models/emailUnsubscription.js
@@ -0,0 +1,24 @@
+import mongoose from 'mongoose';
+import validator from 'validator';
+import baseModel from '../libs/api-v3/baseModel';
+
+// A collection used to store mailing list unsubscription for non registered email addresses
+export let schema = new mongoose.Schema({
+ email: {
+ type: String,
+ required: true,
+ trim: true,
+ lowercase: true,
+ validator: [validator.isEmail, 'Invalid email.'],
+ },
+}, {
+ strict: true,
+ minimize: false, // So empty objects are returned
+});
+
+schema.plugin(baseModel, {
+ noSet: ['_id'],
+ timestamps: true,
+});
+
+export let model = mongoose.model('EmailUnsubscription', schema);
diff --git a/website/server/models/group.js b/website/server/models/group.js
new file mode 100644
index 0000000000..cff731c034
--- /dev/null
+++ b/website/server/models/group.js
@@ -0,0 +1,764 @@
+import mongoose from 'mongoose';
+import {
+ model as User,
+ nameFields,
+} from './user';
+import shared from '../../../common';
+import _ from 'lodash';
+import { model as Challenge} from './challenge';
+import validator from 'validator';
+import { removeFromArray } from '../libs/api-v3/collectionManipulators';
+import {
+ InternalServerError,
+ BadRequest,
+} from '../libs/api-v3/errors';
+import * as firebase from '../libs/api-v2/firebase';
+import baseModel from '../libs/api-v3/baseModel';
+import { sendTxn as sendTxnEmail } from '../libs/api-v3/email';
+import Bluebird from 'bluebird';
+import nconf from 'nconf';
+import sendPushNotification from '../libs/api-v3/pushNotifications';
+
+const questScrolls = shared.content.quests;
+const Schema = mongoose.Schema;
+
+export const INVITES_LIMIT = 100;
+export const TAVERN_ID = shared.TAVERN_ID;
+
+const CRON_SAFE_MODE = nconf.get('CRON_SAFE_MODE') === 'true';
+const CRON_SEMI_SAFE_MODE = nconf.get('CRON_SEMI_SAFE_MODE') === 'true';
+
+// NOTE once Firebase is enabled any change to groups' members in MongoDB will have to be run through the API
+// changes made directly to the db will cause Firebase to get out of sync
+export let schema = new Schema({
+ name: {type: String, required: true},
+ description: String,
+ leader: {type: String, ref: 'User', validate: [validator.isUUID, 'Invalid uuid.'], required: true},
+ type: {type: String, enum: ['guild', 'party'], required: true},
+ privacy: {type: String, enum: ['private', 'public'], default: 'private', required: true},
+ chat: Array,
+ /*
+ # [{
+ # timestamp: Date
+ # user: String
+ # text: String
+ # contributor: String
+ # uuid: String
+ # id: String
+ # }]
+ */
+ leaderOnly: { // restrict group actions to leader (members can't do them)
+ challenges: {type: Boolean, default: false, required: true},
+ // invites: {type: Boolean, default: false, required: true},
+ },
+ memberCount: {type: Number, default: 1},
+ challengeCount: {type: Number, default: 0},
+ balance: {type: Number, default: 0},
+ logo: String,
+ leaderMessage: String,
+ quest: {
+ key: String,
+ active: {type: Boolean, default: false},
+ leader: {type: String, ref: 'User'},
+ progress: {
+ hp: Number,
+ collect: {type: Schema.Types.Mixed, default: () => {
+ return {};
+ }}, // {feather: 5, ingot: 3}
+ rage: Number, // limit break / "energy stored in shell", for explosion-attacks
+ },
+
+ // Shows boolean for each party-member who has accepted the quest. Eg {UUID: true, UUID: false}. Once all users click
+ // 'Accept', the quest begins. If a false user waits too long, probably a good sign to prod them or boot them.
+ // TODO when booting user, remove from .joined and check again if we can now start the quest
+ members: {type: Schema.Types.Mixed, default: () => {
+ return {};
+ }},
+ extra: {type: Schema.Types.Mixed, default: () => {
+ return {};
+ }},
+ },
+}, {
+ strict: true,
+ minimize: false, // So empty objects are returned
+});
+
+schema.plugin(baseModel, {
+ noSet: ['_id', 'balance', 'quest', 'memberCount', 'chat', 'challengeCount'],
+});
+
+// A list of additional fields that cannot be updated (but can be set on creation)
+let noUpdate = ['privacy', 'type'];
+schema.statics.sanitizeUpdate = function sanitizeUpdate (updateObj) {
+ return this.sanitize(updateObj, noUpdate);
+};
+
+// Basic fields to fetch for populating a group info
+export let basicFields = 'name type privacy';
+
+schema.pre('remove', true, async function preRemoveGroup (next, done) {
+ next();
+ try {
+ await this.removeGroupInvitations();
+ done();
+ } catch (err) {
+ done(err);
+ }
+});
+
+schema.post('remove', function postRemoveGroup (group) {
+ firebase.deleteGroup(group._id);
+});
+
+schema.statics.getGroup = async function getGroup (options = {}) {
+ let {user, groupId, fields, optionalMembership = false, populateLeader = false, requireMembership = false} = options;
+ let query;
+
+ let isUserParty = groupId === 'party' || user.party._id === groupId;
+ let isUserGuild = user.guilds.indexOf(groupId) !== -1;
+ let isTavern = ['habitrpg', TAVERN_ID].indexOf(groupId) !== -1;
+
+ // When requireMembership is true check that user is member even in public guild
+ if (requireMembership && !isUserParty && !isUserGuild && !isTavern) {
+ return null;
+ }
+
+ // When optionalMembership is true it's not required for the user to be a member of the group
+ if (isUserParty) {
+ query = {type: 'party', _id: user.party._id};
+ } else if (isTavern) {
+ query = {_id: TAVERN_ID};
+ } else if (optionalMembership === true) {
+ query = {_id: groupId};
+ } else if (isUserGuild) {
+ query = {type: 'guild', _id: groupId};
+ } else {
+ query = {type: 'guild', privacy: 'public', _id: groupId};
+ }
+
+ let mQuery = this.findOne(query);
+ if (fields) mQuery.select(fields);
+ if (populateLeader === true) mQuery.populate('leader', nameFields);
+ let group = await mQuery.exec();
+ return group;
+};
+
+export const VALID_QUERY_TYPES = ['party', 'guilds', 'privateGuilds', 'publicGuilds', 'tavern'];
+
+schema.statics.getGroups = async function getGroups (options = {}) {
+ let {user, types, groupFields = basicFields, sort = '-memberCount', populateLeader = false} = options;
+ let queries = [];
+
+ // Throw error if an invalid type is supplied
+ let areValidTypes = types.every(type => VALID_QUERY_TYPES.indexOf(type) !== -1);
+ if (!areValidTypes) throw new BadRequest(shared.i18n.t('groupTypesRequired'));
+
+ types.forEach(type => {
+ switch (type) {
+ case 'party': {
+ queries.push(this.getGroup({user, groupId: 'party', fields: groupFields, populateLeader}));
+ break;
+ }
+ case 'guilds': {
+ let userGuildsQuery = this.find({
+ type: 'guild',
+ _id: {$in: user.guilds},
+ }).select(groupFields);
+ if (populateLeader === true) userGuildsQuery.populate('leader', nameFields);
+ userGuildsQuery.sort(sort).exec();
+ queries.push(userGuildsQuery);
+ break;
+ }
+ case 'privateGuilds': {
+ let privateGuildsQuery = this.find({
+ type: 'guild',
+ privacy: 'private',
+ _id: {$in: user.guilds},
+ }).select(groupFields);
+ if (populateLeader === true) privateGuildsQuery.populate('leader', nameFields);
+ privateGuildsQuery.sort(sort).exec();
+ queries.push(privateGuildsQuery);
+ break;
+ }
+ // NOTE: when returning publicGuilds we use `.lean()` so all mongoose methods won't be available.
+ // Docs are going to be plain javascript objects
+ case 'publicGuilds': {
+ let publicGuildsQuery = this.find({
+ type: 'guild',
+ privacy: 'public',
+ }).select(groupFields);
+ if (populateLeader === true) publicGuildsQuery.populate('leader', nameFields);
+ publicGuildsQuery.sort(sort).lean().exec();
+ queries.push(publicGuildsQuery);
+ break;
+ }
+ case 'tavern': {
+ if (types.indexOf('publicGuilds') === -1) {
+ queries.push(this.getGroup({user, groupId: TAVERN_ID, fields: groupFields}));
+ }
+ break;
+ }
+ }
+ });
+
+ let groupsArray = _.reduce(await Bluebird.all(queries), (previousValue, currentValue) => {
+ if (_.isEmpty(currentValue)) return previousValue; // don't add anything to the results if the query returned null or an empty array
+ return previousValue.concat(Array.isArray(currentValue) ? currentValue : [currentValue]); // otherwise concat the new results to the previousValue
+ }, []);
+
+ return groupsArray;
+};
+
+// When converting to json remove chat messages with more than 1 flag and remove all flags info
+// unless the user is an admin
+// Not putting into toJSON because there we can't access user
+schema.statics.toJSONCleanChat = function groupToJSONCleanChat (group, user) {
+ let toJSON = group.toJSON();
+ if (!user.contributor.admin) {
+ _.remove(toJSON.chat, chatMsg => {
+ chatMsg.flags = {};
+ return chatMsg.flagCount >= 2;
+ });
+ }
+ return toJSON;
+};
+
+schema.methods.removeGroupInvitations = async function removeGroupInvitations () {
+ let group = this;
+
+ let usersToRemoveInvitationsFrom = await User.find({
+ [`invitations.${group.type}${group.type === 'guild' ? 's' : ''}.id`]: group._id,
+ }).exec();
+
+ let userUpdates = usersToRemoveInvitationsFrom.map(user => {
+ if (group.type === 'party') {
+ user.invitations.party = {};
+ this.markModified('invitations.party');
+ } else {
+ removeFromArray(user.invitations.guilds, { id: group._id });
+ }
+ return user.save();
+ });
+
+ return Bluebird.all(userUpdates);
+};
+
+// Return true if user is a member of the group
+schema.methods.isMember = function isGroupMember (user) {
+ if (this._id === TAVERN_ID) {
+ return true; // everyone is considered part of the tavern
+ } else if (this.type === 'party') {
+ return user.party._id === this._id ? true : false;
+ } else { // guilds
+ return user.guilds.indexOf(this._id) !== -1;
+ }
+};
+
+export function chatDefaults (msg, user) {
+ let message = {
+ id: shared.uuid(),
+ text: msg,
+ timestamp: Number(new Date()),
+ likes: {},
+ flags: {},
+ flagCount: 0,
+ };
+
+ if (user) {
+ _.defaults(message, {
+ uuid: user._id,
+ contributor: user.contributor && user.contributor.toObject(),
+ backer: user.backer && user.backer.toObject(),
+ user: user.profile.name,
+ });
+ } else {
+ message.uuid = 'system';
+ }
+
+ return message;
+}
+
+const NO_CHAT_NOTIFICATIONS = [TAVERN_ID];
+
+schema.methods.sendChat = function sendChat (message, user) {
+ this.chat.unshift(chatDefaults(message, user));
+ this.chat.splice(200);
+
+ // Kick off chat notifications in the background.
+ let lastSeenUpdate = {$set: {}};
+ lastSeenUpdate.$set[`newMessages.${this._id}`] = {name: this.name, value: true};
+
+ // do not send notifications for guilds with more than 5000 users and for the tavern
+ if (NO_CHAT_NOTIFICATIONS.indexOf(this._id) !== -1 || this.memberCount > 5000) {
+ // TODO For Tavern, only notify them if their name was mentioned
+ // var profileNames = [] // get usernames from regex of @xyz. how to handle space-delimited profile names?
+ // User.update({'profile.name':{$in:profileNames}},lastSeenUpdate,{multi:true}).exec();
+ } else {
+ let query = {};
+
+ if (this.type === 'party') {
+ query['party._id'] = this._id;
+ } else {
+ query.guilds = this._id;
+ }
+
+ query._id = { $ne: user ? user._id : ''};
+
+ User.update(query, lastSeenUpdate, {multi: true}).exec();
+ }
+};
+
+schema.methods.startQuest = async function startQuest (user) {
+ // not using i18n strings because these errors are meant for devs who forgot to pass some parameters
+ if (this.type !== 'party') throw new InternalServerError('Must be a party to use this method');
+ if (!this.quest.key) throw new InternalServerError('Party does not have a pending quest');
+ if (this.quest.active) throw new InternalServerError('Quest is already active');
+
+ let userIsParticipating = this.quest.members[user._id];
+ let quest = questScrolls[this.quest.key];
+ let collected = {};
+ if (quest.collect) {
+ collected = _.transform(quest.collect, (result, n, itemToCollect) => {
+ result[itemToCollect] = 0;
+ });
+ }
+
+ this.markModified('quest');
+ this.quest.active = true;
+ if (quest.boss) {
+ this.quest.progress.hp = quest.boss.hp;
+ if (quest.boss.rage) this.quest.progress.rage = 0;
+ } else if (quest.collect) {
+ this.quest.progress.collect = collected;
+ }
+
+ // Changes quest.members to only include participating members
+ // TODO: is that important? What does it matter if the non-participating members
+ // are still on the object?
+ // TODO: is it important to run clean quest progress on non-members like we did in v2?
+ this.quest.members = _.pick(this.quest.members, _.identity);
+ let nonUserQuestMembers = _.keys(this.quest.members);
+ removeFromArray(nonUserQuestMembers, user._id);
+
+ if (userIsParticipating) {
+ user.party.quest.key = this.quest.key;
+ user.party.quest.progress.down = 0;
+ user.party.quest.progress.collect = collected;
+ user.party.quest.completed = null;
+ user.markModified('party.quest');
+ }
+
+ // Remove the quest from the quest leader items (if they are the current user)
+ if (this.quest.leader === user._id) {
+ user.items.quests[this.quest.key] -= 1;
+ user.markModified('items.quests');
+ } else { // another user is starting the quest, update the leader separately
+ await User.update({_id: this.quest.leader}, {
+ $inc: {
+ [`items.quests.${this.quest.key}`]: -1,
+ },
+ }).exec();
+ }
+
+ // update the remaining users
+ await User.update({
+ _id: { $in: nonUserQuestMembers },
+ }, {
+ $set: {
+ 'party.quest.key': this.quest.key,
+ 'party.quest.progress.down': 0,
+ 'party.quest.progress.collect': collected,
+ 'party.quest.completed': null,
+ },
+ }, { multi: true }).exec();
+
+ // send notifications in the background without blocking
+ User.find(
+ { _id: { $in: nonUserQuestMembers } },
+ 'party.quest items.quests auth.facebook auth.local preferences.emailNotifications pushDevices profile.name'
+ ).exec().then((membersToNotify) => {
+ let membersToEmail = _.filter(membersToNotify, (member) => {
+ // send push notifications and filter users that disabled emails
+ sendPushNotification(member, 'HabitRPG', `${shared.i18n.t('questStarted')}: ${quest.text()}`);
+
+ return member.preferences.emailNotifications.questStarted !== false &&
+ member._id !== user._id;
+ });
+ sendTxnEmail(membersToEmail, 'quest-started', [
+ { name: 'PARTY_URL', content: '/#/options/groups/party' },
+ ]);
+ });
+};
+
+// return a clean object for user.quest
+function _cleanQuestProgress (merge) {
+ let clean = {
+ key: null,
+ progress: {
+ up: 0,
+ down: 0,
+ collect: {},
+ },
+ completed: null,
+ RSVPNeeded: false,
+ };
+
+ if (merge) {
+ _.merge(clean, _.omit(merge, 'progress'));
+ if (merge.progress) _.merge(clean.progress, merge.progress);
+ }
+
+ return clean;
+}
+
+schema.statics.cleanQuestProgress = _cleanQuestProgress;
+
+// returns a clean object for group.quest
+schema.statics.cleanGroupQuest = function cleanGroupQuest () {
+ return {
+ key: null,
+ active: false,
+ leader: null,
+ progress: {
+ collect: {},
+ },
+ members: {},
+ };
+};
+
+// Participants: Grant rewards & achievements, finish quest.
+// Changes the group object update members
+schema.methods.finishQuest = async function finishQuest (quest) {
+ let questK = quest.key;
+ let updates = {$inc: {}, $set: {}};
+
+ updates.$inc[`achievements.quests.${questK}`] = 1;
+ updates.$inc['stats.gp'] = Number(quest.drop.gp);
+ updates.$inc['stats.exp'] = Number(quest.drop.exp);
+
+ if (this._id === TAVERN_ID) {
+ updates.$set['party.quest.completed'] = questK; // Just show the notif
+ } else {
+ updates.$set['party.quest'] = _cleanQuestProgress({completed: questK}); // clear quest progress
+ }
+
+ _.each(quest.drop.items, (item) => {
+ let dropK = item.key;
+
+ switch (item.type) {
+ case 'gear': {
+ // TODO This means they can lose their new gear on death, is that what we want?
+ updates.$set[`items.gear.owned.${dropK}`] = true;
+ break;
+ }
+ case 'eggs':
+ case 'food':
+ case 'hatchingPotions':
+ case 'quests': {
+ updates.$inc[`items.${item.type}.${dropK}`] = _.where(quest.drop.items, {type: item.type, key: item.key}).length;
+ break;
+ }
+ case 'pets': {
+ updates.$set[`items.pets.${dropK}`] = 5;
+ break;
+ }
+ case 'mounts': {
+ updates.$set[`items.mounts.${dropK}`] = true;
+ break;
+ }
+ }
+ });
+
+ let q = this._id === TAVERN_ID ? {} : {_id: {$in: _.keys(this.quest.members)}};
+ this.quest = {};
+ this.markModified('quest');
+
+ return await User.update(q, updates, {multi: true}).exec();
+};
+
+function _isOnQuest (user, progress, group) {
+ return group && progress && group.quest && group.quest.active && group.quest.members[user._id] === true;
+}
+
+schema.statics.collectQuest = async function collectQuest (user, progress) {
+ let group = await this.getGroup({user, groupId: 'party'});
+ if (!_isOnQuest(user, progress, group)) return;
+ let quest = shared.content.quests[group.quest.key];
+
+ _.each(progress.collect, (v, k) => {
+ group.quest.progress.collect[k] += v;
+ });
+
+ let foundText = _.reduce(progress.collect, (m, v, k) => {
+ m.push(`${v} ${quest.collect[k].text('en')}`);
+ return m;
+ }, []);
+
+ foundText = foundText ? foundText.join(', ') : 'nothing';
+ group.sendChat(`\`${user.profile.name} found ${foundText}.\``);
+ group.markModified('quest.progress.collect');
+
+ // Still needs completing
+ if (_.find(shared.content.quests[group.quest.key].collect, (v, k) => {
+ return group.quest.progress.collect[k] < v.count;
+ })) return await group.save();
+
+ await group.finishQuest(quest);
+ group.sendChat('`All items found! Party has received their rewards.`');
+
+ return await group.save();
+};
+
+schema.statics.bossQuest = async function bossQuest (user, progress) {
+ let group = await this.getGroup({user, groupId: 'party'});
+ if (!_isOnQuest(user, progress, group)) return;
+
+ let quest = shared.content.quests[group.quest.key];
+ if (!progress || !quest) return; // TODO why is this ever happening, progress should be defined at this point, log?
+
+ let down = progress.down * quest.boss.str; // multiply by boss strength
+
+ group.quest.progress.hp -= progress.up;
+ // TODO Create a party preferred language option so emits like this can be localized. Suggestion: Always display the English version too. Or, if English is not displayed to the players, at least include it in a new field in the chat object that's visible in the database - essential for admins when troubleshooting quests!
+ let playerAttack = `${user.profile.name} attacks ${quest.boss.name('en')} for ${progress.up.toFixed(1)} damage.`;
+ let bossAttack = CRON_SAFE_MODE || CRON_SEMI_SAFE_MODE ? `${quest.boss.name('en')} does not attack, because it respects the fact that there are some bugs\` \`post-maintenance and it doesn't want to hurt anyone unfairly. It will continue its rampage soon!` : `${quest.boss.name('en')} attacks party for ${Math.abs(down).toFixed(1)} damage.`;
+ // TODO Consider putting the safe mode boss attack message in an ENV var
+ group.sendChat(`\`${playerAttack}\` \`${bossAttack}\``);
+
+ // If boss has Rage, increment Rage as well
+ if (quest.boss.rage) {
+ group.quest.progress.rage += Math.abs(down);
+ if (group.quest.progress.rage >= quest.boss.rage.value) {
+ group.sendChat(quest.boss.rage.effect('en'));
+ group.quest.progress.rage = 0;
+
+ // TODO To make Rage effects more expandable, let's turn these into functions in quest.boss.rage
+ if (quest.boss.rage.healing) group.quest.progress.hp += group.quest.progress.hp * quest.boss.rage.healing;
+ if (group.quest.progress.hp > quest.boss.hp) group.quest.progress.hp = quest.boss.hp;
+ }
+ }
+
+ // Everyone takes damage
+ await User.update({
+ _id: {$in: _.keys(group.quest.members)},
+ }, {
+ $inc: {'stats.hp': down},
+ }, {multi: true}).exec();
+ // Apply changes the currently cronning user locally so we don't have to reload it to get the updated state
+ // TODO how to mark not modified? https://github.com/Automattic/mongoose/pull/1167
+ // must be notModified or otherwise could overwrite future changes: if the user is saved it'll save
+ // the modified user.stats.hp but that must not happen as the hp value has already been updated by the User.update above
+ // if (down) user.stats.hp += down;
+
+ // Boss slain, finish quest
+ if (group.quest.progress.hp <= 0) {
+ group.sendChat(`\`You defeated ${quest.boss.name('en')}! Questing party members receive the rewards of victory.\``);
+
+ // Participants: Grant rewards & achievements, finish quest
+ await group.finishQuest(shared.content.quests[group.quest.key]);
+ }
+
+ return await group.save();
+};
+
+// to set a boss: `db.groups.update({_id:TAVERN_ID},{$set:{quest:{key:'dilatory',active:true,progress:{hp:1000,rage:1500}}}})`
+// we export an empty object that is then populated with the query-returned data
+export let tavernQuest = {};
+let tavernQ = {_id: TAVERN_ID, 'quest.key': {$ne: null}};
+
+// we use process.nextTick because at this point the model is not yet available
+process.nextTick(() => {
+ model // eslint-disable-line no-use-before-define
+ .findOne(tavernQ).exec()
+ .then(tavern => {
+ if (!tavern) return; // No tavern quest
+
+ // Using _assign so we don't lose the reference to the exported tavernQuest
+ _.assign(tavernQuest, tavern.quest.toObject());
+ })
+ .catch(err => {
+ throw err;
+ });
+});
+
+// returns a promise
+schema.statics.tavernBoss = async function tavernBoss (user, progress) {
+ if (!progress) return;
+
+ // hack: prevent crazy damage to world boss
+ let dmg = Math.min(900, Math.abs(progress.up || 0));
+ let rage = -Math.min(900, Math.abs(progress.down || 0));
+
+ let tavern = await this.findOne(tavernQ).exec();
+ if (!(tavern && tavern.quest && tavern.quest.key)) return;
+
+ let quest = shared.content.quests[tavern.quest.key];
+
+ if (tavern.quest.progress.hp <= 0) {
+ tavern.sendChat(quest.completionChat('en'));
+ await tavern.finishQuest(quest);
+ _.assign(tavernQuest, {extra: null});
+ return tavern.save();
+ } else {
+ // Deal damage. Note a couple things here, str & def are calculated. If str/def are defined in the database,
+ // use those first - which allows us to update the boss on the go if things are too easy/hard.
+ if (!tavern.quest.extra) tavern.quest.extra = {};
+ tavern.quest.progress.hp -= dmg / (tavern.quest.extra.def || quest.boss.def);
+ tavern.quest.progress.rage -= rage * (tavern.quest.extra.str || quest.boss.str);
+
+ if (tavern.quest.progress.rage >= quest.boss.rage.value) {
+ if (!tavern.quest.extra.worldDmg) tavern.quest.extra.worldDmg = {};
+
+ let wd = tavern.quest.extra.worldDmg;
+ // Burnout attacks Ian, Seasonal Sorceress, tavern
+ // Be-Wilder attacks Alex, Matt, Bailey
+ let scene = wd.market ? wd.stables ? wd.bailey ? false : 'bailey' : 'stables' : 'market'; // eslint-disable-line no-nested-ternary
+
+ if (!scene) {
+ tavern.sendChat(`\`${quest.boss.name('en')} tries to unleash ${quest.boss.rage.title('en')} but is too tired.\``);
+ tavern.quest.progress.rage = 0; // quest.boss.rage.value;
+ } else {
+ tavern.sendChat(quest.boss.rage[scene]('en'));
+ tavern.quest.extra.worldDmg[scene] = true;
+ tavern.quest.extra.worldDmg.recent = scene;
+ tavern.markModified('quest.extra.worldDmg');
+ tavern.quest.progress.rage = 0;
+ if (quest.boss.rage.healing) {
+ tavern.quest.progress.hp += quest.boss.rage.healing * tavern.quest.progress.hp;
+ }
+ }
+ }
+
+ if (quest.boss.desperation && tavern.quest.progress.hp < quest.boss.desperation.threshold && !tavern.quest.extra.desperate) {
+ tavern.sendChat(quest.boss.desperation.text('en'));
+ tavern.quest.extra.desperate = true;
+ tavern.quest.extra.def = quest.boss.desperation.def;
+ tavern.quest.extra.str = quest.boss.desperation.str;
+ tavern.markModified('quest.extra');
+ }
+
+ _.assign(tavernQuest, tavern.quest.toObject());
+ return tavern.save();
+ }
+};
+
+schema.methods.leave = async function leaveGroup (user, keep = 'keep-all') {
+ let group = this;
+
+ let challenges = await Challenge.find({
+ _id: {$in: user.challenges},
+ group: group._id,
+ });
+
+ let challengesToRemoveUserFrom = challenges.map(chal => {
+ return chal.unlinkTasks(user, keep);
+ });
+ await Bluebird.all(challengesToRemoveUserFrom);
+
+ let promises = [];
+
+ // remove the group from the user's groups
+ if (group.type === 'guild') {
+ promises.push(User.update({_id: user._id}, {$pull: {guilds: group._id}}).exec());
+ } else {
+ promises.push(User.update({_id: user._id}, {$set: {party: {}}}).exec());
+ }
+
+ // If user is the last one in group and group is private, delete it
+ if (group.memberCount <= 1 && group.privacy === 'private') {
+ promises.push(group.remove());
+ } else { // otherwise If the leader is leaving (or if the leader previously left, and this wasn't accounted for)
+ let update = {
+ $inc: {memberCount: -1},
+ };
+
+ if (group.leader === user._id) {
+ let query = group.type === 'party' ? {'party._id': group._id} : {guilds: group._id};
+ query._id = {$ne: user._id};
+ let seniorMember = await User.findOne(query).select('_id').exec();
+
+ // could be missing in case of public guild (that can have 0 members) with 1 member who is leaving
+ if (seniorMember) update.$set = {leader: seniorMember._id};
+ }
+ promises.push(group.update(update).exec());
+ }
+
+ firebase.removeUserFromGroup(group._id, user._id);
+
+ return await Bluebird.all(promises);
+};
+
+// API v2 compatibility methods
+schema.methods.getTransformedData = function getTransformedData (options) {
+ let cb = options.cb;
+ let populateMembers = options.populateMembers;
+ let populateInvites = options.populateInvites;
+ let populateChallenges = options.populateChallenges;
+
+ let obj = this.toJSON();
+
+ let queryMembers = {};
+ let queryInvites = {};
+
+ if (this.type === 'guild') {
+ queryInvites['invitations.guilds.id'] = this._id;
+ } else {
+ queryInvites['invitations.party.id'] = this._id;
+ }
+
+ if (this.type === 'guild') {
+ queryMembers.guilds = this._id;
+ } else {
+ queryMembers['party._id'] = this._id;
+ }
+
+ let selectDataMembers = '_id';
+ let selectDataInvites = '_id';
+ let selectDataChallenges = '_id';
+
+ if (populateMembers) {
+ selectDataMembers += ` ${populateMembers}`;
+ }
+ if (populateInvites) {
+ selectDataInvites += ` ${populateInvites}`;
+ }
+ if (populateChallenges) {
+ selectDataChallenges += ` ${populateChallenges}`;
+ }
+
+ let membersQuery = User.find(queryMembers).select(selectDataMembers);
+ if (options.limitPopulation) membersQuery.limit(15);
+
+ Bluebird.all([
+ membersQuery.exec(),
+ User.find(queryInvites).select(populateInvites).exec(),
+ Challenge.find({group: obj._id}).select(populateMembers).exec(),
+ ])
+ .then((results) => {
+ obj.members = results[0];
+ obj.invites = results[1];
+ obj.challenges = results[2];
+
+ cb(null, obj);
+ })
+ .catch(cb);
+};
+// END API v2 compatibility methods
+
+export let model = mongoose.model('Group', schema);
+
+// initialize tavern if !exists (fresh installs)
+// do not run when testing as it's handled by the tests and can easily cause a race condition
+if (!nconf.get('IS_TEST')) {
+ model.count({_id: TAVERN_ID}, (err, ct) => {
+ if (err) throw err;
+ if (ct > 0) return;
+ new model({ // eslint-disable-line babel/new-cap
+ _id: TAVERN_ID,
+ leader: '7bde7864-ebc5-4ee2-a4b7-1070d464cdb0', // Siena Leslie
+ name: 'Tavern',
+ type: 'guild',
+ privacy: 'public',
+ }).save();
+ });
+}
diff --git a/website/server/models/tag.js b/website/server/models/tag.js
new file mode 100644
index 0000000000..f201541540
--- /dev/null
+++ b/website/server/models/tag.js
@@ -0,0 +1,27 @@
+import mongoose from 'mongoose';
+import baseModel from '../libs/api-v3/baseModel';
+import { v4 as uuid } from 'uuid';
+import validator from 'validator';
+
+let Schema = mongoose.Schema;
+
+export let schema = new Schema({
+ id: {
+ type: String,
+ default: uuid,
+ validate: [validator.isUUID, 'Invalid uuid.'],
+ },
+ name: {type: String, required: true},
+ challenge: {type: String},
+}, {
+ strict: true,
+ minimize: false, // So empty objects are returned
+ _id: false, // use id instead of _id
+});
+
+schema.plugin(baseModel, {
+ noSet: ['_id', 'id', 'challenge'],
+ _id: false, // use id instead of _id
+});
+
+export let model = mongoose.model('Tag', schema);
diff --git a/website/server/models/task.js b/website/server/models/task.js
new file mode 100644
index 0000000000..0664fab855
--- /dev/null
+++ b/website/server/models/task.js
@@ -0,0 +1,225 @@
+import mongoose from 'mongoose';
+import shared from '../../../common';
+import validator from 'validator';
+import moment from 'moment';
+import baseModel from '../libs/api-v3/baseModel';
+import _ from 'lodash';
+import { preenHistory } from '../libs/api-v3/preening';
+
+let Schema = mongoose.Schema;
+let discriminatorOptions = {
+ discriminatorKey: 'type', // the key that distinguishes task types
+};
+let subDiscriminatorOptions = _.defaults(_.cloneDeep(discriminatorOptions), {
+ _id: false,
+ minimize: false, // So empty objects are returned
+});
+
+export let tasksTypes = ['habit', 'daily', 'todo', 'reward'];
+
+// Important
+// When something changes here remember to update the client side model at common/script/libs/taskDefaults
+export let TaskSchema = new Schema({
+ _legacyId: String, // TODO Remove when v2 is deprecated
+ type: {type: String, enum: tasksTypes, required: true, default: tasksTypes[0]},
+ text: {type: String, required: true},
+ notes: {type: String, default: ''},
+ tags: [{
+ type: String,
+ validate: [validator.isUUID, 'Invalid uuid.'],
+ }],
+ value: {type: Number, default: 0, required: true}, // redness or cost for rewards Required because it must be settable (for rewards)
+ priority: {
+ type: Number,
+ default: 1,
+ required: true,
+ validate: [
+ (val) => [0.1, 1, 1.5, 2].indexOf(val) !== -1,
+ 'Valid priority values are 0.1, 1, 1.5, 2.',
+ ],
+ },
+ attribute: {type: String, default: 'str', enum: ['str', 'con', 'int', 'per']},
+ userId: {type: String, ref: 'User', validate: [validator.isUUID, 'Invalid uuid.']}, // When not set it belongs to a challenge
+
+ challenge: {
+ id: {type: String, ref: 'Challenge', validate: [validator.isUUID, 'Invalid uuid.']}, // When set (and userId not set) it's the original task
+ taskId: {type: String, ref: 'Task', validate: [validator.isUUID, 'Invalid uuid.']}, // When not set but challenge.id defined it's the original task
+ broken: {type: String, enum: ['CHALLENGE_DELETED', 'TASK_DELETED', 'UNSUBSCRIBED', 'CHALLENGE_CLOSED', 'CHALLENGE_TASK_NOT_FOUND']}, // CHALLENGE_TASK_NOT_FOUND comes from v3 migration
+ winner: String, // user.profile.name of the winner
+ },
+
+ reminders: [{
+ _id: false,
+ id: {type: String, validate: [validator.isUUID, 'Invalid uuid.'], default: shared.uuid, required: true},
+ startDate: {type: Date},
+ time: {type: Date, required: true},
+ }],
+}, _.defaults({
+ minimize: false, // So empty objects are returned
+ strict: true,
+}, discriminatorOptions));
+
+TaskSchema.plugin(baseModel, {
+ noSet: ['challenge', 'userId', 'completed', 'history', 'dateCompleted', '_legacyId'],
+ sanitizeTransform (taskObj) {
+ if (taskObj.type && taskObj.type !== 'reward') { // value should be settable directly only for rewards
+ delete taskObj.value;
+ }
+
+ return taskObj;
+ },
+ private: [],
+ timestamps: true,
+});
+
+// Sanitize user tasks linked to a challenge
+// See http://habitica.wikia.com/wiki/Challenges#Challenge_Participant.27s_Permissions for more info
+TaskSchema.statics.sanitizeUserChallengeTask = function sanitizeUserChallengeTask (taskObj) {
+ let initialSanitization = this.sanitize(taskObj);
+
+ return _.pick(initialSanitization, ['streak', 'checklist', 'attribute', 'reminders', 'tags', 'notes']);
+};
+
+// Sanitize checklist objects (disallowing id)
+TaskSchema.statics.sanitizeChecklist = function sanitizeChecklist (checklistObj) {
+ delete checklistObj.id;
+ return checklistObj;
+};
+
+// Sanitize reminder objects (disallowing id)
+TaskSchema.statics.sanitizeReminder = function sanitizeReminder (reminderObj) {
+ delete reminderObj.id;
+ return reminderObj;
+};
+
+TaskSchema.methods.scoreChallengeTask = async function scoreChallengeTask (delta) {
+ let chalTask = this;
+
+ chalTask.value += delta;
+
+ if (chalTask.type === 'habit' || chalTask.type === 'daily') {
+ // Add only one history entry per day
+ let lastChallengHistoryIndex = chalTask.history.length - 1;
+
+ if (chalTask.history[lastChallengHistoryIndex] &&
+ moment(chalTask.history[lastChallengHistoryIndex].date).isSame(new Date(), 'day')) {
+ chalTask.history[lastChallengHistoryIndex] = {
+ date: Number(new Date()),
+ value: chalTask.value,
+ };
+ chalTask.markModified(`history.${lastChallengHistoryIndex}`);
+ } else {
+ chalTask.history.push({
+ date: Number(new Date()),
+ value: chalTask.value,
+ });
+
+ // Only preen task history once a day when the task is scored first
+ if (chalTask.history.length > 365) {
+ chalTask.history = preenHistory(chalTask.history, true); // true means the challenge will retain as much entries as a subscribed user
+ }
+ }
+ }
+
+ await chalTask.save();
+};
+
+
+// Methods to adapt the new schema to API v2 responses (mostly tasks inside the user model)
+// These will be removed once API v2 is discontinued
+
+// toJSON for API v2
+TaskSchema.methods.toJSONV2 = function toJSONV2 () {
+ let toJSON = this.toJSON();
+ if (toJSON._legacyId) {
+ toJSON.id = toJSON._legacyId;
+ } else {
+ toJSON.id = toJSON._id;
+ }
+
+ if (!toJSON.challenge) toJSON.challenge = {};
+
+ let v3Tags = this.tags;
+
+ toJSON.tags = {};
+ v3Tags.forEach(tag => {
+ toJSON.tags[tag] = true;
+ });
+
+ toJSON.dateCreated = this.createdAt;
+
+ return toJSON;
+};
+
+TaskSchema.statics.fromJSONV2 = function fromJSONV2 (taskObj) {
+ if (taskObj.id) taskObj._id = taskObj.id;
+
+ let v2Tags = taskObj.tags || {};
+
+ taskObj.tags = [];
+ taskObj.tags = _.map(v2Tags, (tag, key) => key);
+
+ return taskObj;
+};
+
+// END of API v2 methods
+
+export let Task = mongoose.model('Task', TaskSchema);
+
+// habits and dailies shared fields
+let habitDailySchema = () => {
+ return {history: Array}; // [{date:Date, value:Number}], // this causes major performance problems
+};
+
+// dailys and todos shared fields
+let dailyTodoSchema = () => {
+ return {
+ completed: {type: Boolean, default: false},
+ // Checklist fields (dailies and todos)
+ collapseChecklist: {type: Boolean, default: false},
+ checklist: [{
+ completed: {type: Boolean, default: false},
+ text: {type: String, required: false, default: ''}, // required:false because it can be empty on creation
+ _id: false,
+ id: {type: String, default: shared.uuid, required: true, validate: [validator.isUUID, 'Invalid uuid.']},
+ }],
+ };
+};
+
+export let HabitSchema = new Schema(_.defaults({
+ up: {type: Boolean, default: true},
+ down: {type: Boolean, default: true},
+}, habitDailySchema()), subDiscriminatorOptions);
+export let habit = Task.discriminator('habit', HabitSchema);
+
+export let DailySchema = new Schema(_.defaults({
+ frequency: {type: String, default: 'weekly', enum: ['daily', 'weekly']},
+ everyX: {type: Number, default: 1}, // e.g. once every X weeks
+ startDate: {
+ type: Date,
+ default () {
+ return moment().startOf('day').toDate();
+ },
+ },
+ repeat: { // used only for 'weekly' frequency,
+ m: {type: Boolean, default: true},
+ t: {type: Boolean, default: true},
+ w: {type: Boolean, default: true},
+ th: {type: Boolean, default: true},
+ f: {type: Boolean, default: true},
+ s: {type: Boolean, default: true},
+ su: {type: Boolean, default: true},
+ },
+ streak: {type: Number, default: 0},
+}, habitDailySchema(), dailyTodoSchema()), subDiscriminatorOptions);
+export let daily = Task.discriminator('daily', DailySchema);
+
+export let TodoSchema = new Schema(_.defaults({
+ dateCompleted: Date,
+ // TODO we're getting parse errors, people have stored as "today" and "3/13". Need to run a migration & put this back to type: Date see http://stackoverflow.com/questions/1353684/detecting-an-invalid-date-date-instance-in-javascript
+ date: String, // due date for todos
+}, dailyTodoSchema()), subDiscriminatorOptions);
+export let todo = Task.discriminator('todo', TodoSchema);
+
+export let RewardSchema = new Schema({}, subDiscriminatorOptions);
+export let reward = Task.discriminator('reward', RewardSchema);
diff --git a/website/server/models/user.js b/website/server/models/user.js
new file mode 100644
index 0000000000..db305e3330
--- /dev/null
+++ b/website/server/models/user.js
@@ -0,0 +1,824 @@
+import mongoose from 'mongoose';
+import shared from '../../../common';
+import _ from 'lodash';
+import validator from 'validator';
+import moment from 'moment';
+import * as Tasks from './task';
+import Bluebird from 'bluebird';
+import { schema as TagSchema } from './tag';
+import baseModel from '../libs/api-v3/baseModel';
+import {
+ chatDefaults,
+ TAVERN_ID,
+} from './group';
+import { defaults } from 'lodash';
+
+let Schema = mongoose.Schema;
+
+// User schema definition
+export let schema = new Schema({
+ apiToken: {
+ type: String,
+ default: shared.uuid,
+ },
+
+ auth: {
+ blocked: Boolean,
+ facebook: {type: Schema.Types.Mixed, default: () => {
+ return {};
+ }},
+ local: {
+ email: {
+ type: String,
+ validate: [validator.isEmail, shared.i18n.t('invalidEmail')],
+ },
+ username: {
+ type: String,
+ },
+ // Store a lowercase version of username to check for duplicates
+ lowerCaseUsername: String,
+ hashed_password: String, // eslint-disable-line camelcase
+ salt: String,
+ },
+ timestamps: {
+ created: {type: Date, default: Date.now},
+ loggedin: {type: Date, default: Date.now},
+ },
+ },
+ // We want to know *every* time an object updates. Mongoose uses __v to designate when an object contains arrays which
+ // have been updated (http://goo.gl/gQLz41), but we want *every* update
+ _v: { type: Number, default: 0 },
+ migration: String,
+ achievements: {
+ originalUser: Boolean,
+ habitSurveys: Number,
+ ultimateGearSets: {
+ healer: {type: Boolean, default: false},
+ wizard: {type: Boolean, default: false},
+ rogue: {type: Boolean, default: false},
+ warrior: {type: Boolean, default: false},
+ },
+ beastMaster: Boolean,
+ beastMasterCount: Number,
+ mountMaster: Boolean,
+ mountMasterCount: Number,
+ triadBingo: Boolean,
+ triadBingoCount: Number,
+ veteran: Boolean,
+ snowball: Number,
+ spookySparkles: Number,
+ shinySeed: Number,
+ seafoam: Number,
+ streak: Number,
+ challenges: Array,
+ quests: {type: Schema.Types.Mixed, default: () => {
+ return {};
+ }},
+ rebirths: Number,
+ rebirthLevel: Number,
+ perfect: {type: Number, default: 0},
+ habitBirthdays: Number,
+ valentine: Number,
+ costumeContest: Boolean, // Superseded by costumeContests
+ nye: Number,
+ habiticaDays: Number,
+ greeting: Number,
+ thankyou: Number,
+ costumeContests: Number,
+ birthday: Number,
+ partyUp: Boolean,
+ partyOn: Boolean,
+ },
+
+ backer: {
+ tier: Number,
+ npc: String,
+ tokensApplied: Boolean,
+ },
+
+ contributor: {
+ // 1-9, see https://trello.com/c/wkFzONhE/277-contributor-gear https://github.com/HabitRPG/habitrpg/issues/3801
+ level: {
+ type: Number,
+ min: 0,
+ max: 9,
+ },
+ admin: Boolean,
+ sudo: Boolean,
+ // Artisan, Friend, Blacksmith, etc
+ text: String,
+ // a markdown textarea to list their contributions + links
+ contributions: String,
+ critical: String,
+ },
+
+ balance: {type: Number, default: 0},
+ // Not saved on the user right now
+ filters: {type: Schema.Types.Mixed, default: () => {
+ return {};
+ }},
+
+ purchased: {
+ ads: {type: Boolean, default: false},
+ // eg, {skeleton: true, pumpkin: true, eb052b: true}
+ skin: {type: Schema.Types.Mixed, default: () => {
+ return {};
+ }},
+ hair: {type: Schema.Types.Mixed, default: () => {
+ return {};
+ }},
+ shirt: {type: Schema.Types.Mixed, default: () => {
+ return {};
+ }},
+ background: {type: Schema.Types.Mixed, default: () => {
+ return {};
+ }},
+ txnCount: {type: Number, default: 0},
+ mobileChat: Boolean,
+ plan: {
+ planId: String,
+ paymentMethod: String, // enum: ['Paypal','Stripe', 'Gift', 'Amazon Payments', '']}
+ customerId: String, // Billing Agreement Id in case of Amazon Payments
+ dateCreated: Date,
+ dateTerminated: Date,
+ dateUpdated: Date,
+ extraMonths: {type: Number, default: 0},
+ gemsBought: {type: Number, default: 0},
+ mysteryItems: {type: Array, default: () => []},
+ lastBillingDate: Date, // Used only for Amazon Payments to keep track of billing date
+ consecutive: {
+ count: {type: Number, default: 0},
+ offset: {type: Number, default: 0}, // when gifted subs, offset++ for each month. offset-- each new-month (cron). count doesn't ++ until offset==0
+ gemCapExtra: {type: Number, default: 0},
+ trinkets: {type: Number, default: 0},
+ },
+ },
+ },
+
+ flags: {
+ customizationsNotification: {type: Boolean, default: false},
+ showTour: {type: Boolean, default: true},
+ tour: {
+ // -1 indicates "uninitiated", -2 means "complete", any other number is the current tour step (0-index)
+ intro: {type: Number, default: -1},
+ classes: {type: Number, default: -1},
+ stats: {type: Number, default: -1},
+ tavern: {type: Number, default: -1},
+ party: {type: Number, default: -1},
+ guilds: {type: Number, default: -1},
+ challenges: {type: Number, default: -1},
+ market: {type: Number, default: -1},
+ pets: {type: Number, default: -1},
+ mounts: {type: Number, default: -1},
+ hall: {type: Number, default: -1},
+ equipment: {type: Number, default: -1},
+ },
+ tutorial: {
+ common: {
+ habits: {type: Boolean, default: false},
+ dailies: {type: Boolean, default: false},
+ todos: {type: Boolean, default: false},
+ rewards: {type: Boolean, default: false},
+ party: {type: Boolean, default: false},
+ pets: {type: Boolean, default: false},
+ gems: {type: Boolean, default: false},
+ skills: {type: Boolean, default: false},
+ classes: {type: Boolean, default: false},
+ tavern: {type: Boolean, default: false},
+ equipment: {type: Boolean, default: false},
+ items: {type: Boolean, default: false},
+ },
+ ios: {
+ addTask: {type: Boolean, default: false},
+ editTask: {type: Boolean, default: false},
+ deleteTask: {type: Boolean, default: false},
+ filterTask: {type: Boolean, default: false},
+ groupPets: {type: Boolean, default: false},
+ inviteParty: {type: Boolean, default: false},
+ },
+ },
+ dropsEnabled: {type: Boolean, default: false},
+ itemsEnabled: {type: Boolean, default: false},
+ newStuff: {type: Boolean, default: false},
+ rewrite: {type: Boolean, default: true},
+ classSelected: {type: Boolean, default: false},
+ mathUpdates: Boolean,
+ rebirthEnabled: {type: Boolean, default: false},
+ levelDrops: {type: Schema.Types.Mixed, default: () => {
+ return {};
+ }},
+ chatRevoked: Boolean,
+ // Used to track the status of recapture emails sent to each user,
+ // can be 0 - no email sent - 1, 2, 3 or 4 - 4 means no more email will be sent to the user
+ recaptureEmailsPhase: {type: Number, default: 0},
+ // Needed to track the tip to send inside the email
+ weeklyRecapEmailsPhase: {type: Number, default: 0},
+ // Used to track when the next weekly recap should be sent
+ lastWeeklyRecap: {type: Date, default: Date.now},
+ // Used to enable weekly recap emails as users login
+ lastWeeklyRecapDiscriminator: Boolean,
+ communityGuidelinesAccepted: {type: Boolean, default: false},
+ cronCount: {type: Number, default: 0},
+ welcomed: {type: Boolean, default: false},
+ armoireEnabled: {type: Boolean, default: false},
+ armoireOpened: {type: Boolean, default: false},
+ armoireEmpty: {type: Boolean, default: false},
+ cardReceived: {type: Boolean, default: false},
+ warnedLowHealth: {type: Boolean, default: false},
+ },
+
+ history: {
+ exp: Array, // [{date: Date, value: Number}], // big peformance issues if these are defined
+ todos: Array, // [{data: Date, value: Number}] // big peformance issues if these are defined
+ },
+
+ items: {
+ gear: {
+ owned: _.transform(shared.content.gear.flat, (m, v) => {
+ m[v.key] = {type: Boolean};
+ if (v.key.match(/[armor|head|shield]_warrior_0/) || v.gearSet === 'glasses') {
+ m[v.key].default = true;
+ }
+ }),
+
+ equipped: {
+ weapon: String,
+ armor: {type: String, default: 'armor_base_0'},
+ head: {type: String, default: 'head_base_0'},
+ shield: {type: String, default: 'shield_base_0'},
+ back: String,
+ headAccessory: String,
+ eyewear: String,
+ body: String,
+ },
+ costume: {
+ weapon: String,
+ armor: {type: String, default: 'armor_base_0'},
+ head: {type: String, default: 'head_base_0'},
+ shield: {type: String, default: 'shield_base_0'},
+ back: String,
+ headAccessory: String,
+ eyewear: String,
+ body: String,
+ },
+ },
+
+ special: {
+ snowball: {type: Number, default: 0},
+ spookySparkles: {type: Number, default: 0},
+ shinySeed: {type: Number, default: 0},
+ seafoam: {type: Number, default: 0},
+ valentine: {type: Number, default: 0},
+ valentineReceived: Array, // array of strings, by sender name
+ nye: {type: Number, default: 0},
+ nyeReceived: Array,
+ greeting: {type: Number, default: 0},
+ greetingReceived: Array,
+ thankyou: {type: Number, default: 0},
+ thankyouReceived: Array,
+ birthday: {type: Number, default: 0},
+ birthdayReceived: Array,
+ },
+
+ // -------------- Animals -------------------
+ // Complex bit here. The result looks like:
+ // pets: {
+ // 'Wolf-Desert': 0, // 0 means does not own
+ // 'PandaCub-Red': 10, // Number represents "Growth Points"
+ // etc...
+ // }
+ pets: _.defaults(
+ // First transform to a 1D eggs/potions mapping
+ _.transform(shared.content.pets, (m, v, k) => m[k] = Number),
+ // Then add additional pets (quest, backer, contributor, premium)
+ _.transform(shared.content.questPets, (m, v, k) => m[k] = Number),
+ _.transform(shared.content.specialPets, (m, v, k) => m[k] = Number),
+ _.transform(shared.content.premiumPets, (m, v, k) => m[k] = Number)
+ ),
+ currentPet: String, // Cactus-Desert
+
+ // eggs: {
+ // 'PandaCub': 0, // 0 indicates "doesn't own"
+ // 'Wolf': 5 // Number indicates "stacking"
+ // }
+ eggs: _.transform(shared.content.eggs, (m, v, k) => m[k] = Number),
+
+ // hatchingPotions: {
+ // 'Desert': 0, // 0 indicates "doesn't own"
+ // 'CottonCandyBlue': 5 // Number indicates "stacking"
+ // }
+ hatchingPotions: _.transform(shared.content.hatchingPotions, (m, v, k) => m[k] = Number),
+
+ // Food: {
+ // 'Watermelon': 0, // 0 indicates "doesn't own"
+ // 'RottenMeat': 5 // Number indicates "stacking"
+ // }
+ food: _.transform(shared.content.food, (m, v, k) => m[k] = Number),
+
+ // mounts: {
+ // 'Wolf-Desert': true,
+ // 'PandaCub-Red': false,
+ // etc...
+ // }
+ mounts: _.defaults(
+ // First transform to a 1D eggs/potions mapping
+ _.transform(shared.content.pets, (m, v, k) => m[k] = Boolean),
+ // Then add quest and premium pets
+ _.transform(shared.content.questPets, (m, v, k) => m[k] = Boolean),
+ _.transform(shared.content.premiumPets, (m, v, k) => m[k] = Boolean),
+ // Then add additional mounts (backer, contributor)
+ _.transform(shared.content.specialMounts, (m, v, k) => m[k] = Boolean)
+ ),
+ currentMount: String,
+
+ // Quests: {
+ // 'boss_0': 0, // 0 indicates "doesn't own"
+ // 'collection_honey': 5 // Number indicates "stacking"
+ // }
+ quests: _.transform(shared.content.quests, (m, v, k) => m[k] = Number),
+
+ lastDrop: {
+ date: {type: Date, default: Date.now},
+ count: {type: Number, default: 0},
+ },
+ },
+
+ lastCron: {type: Date, default: Date.now},
+ _cronSignature: {type: String, default: 'NOT_RUNNING'}, // Private property used to avoid double cron
+
+ // {GROUP_ID: Boolean}, represents whether they have unseen chat messages
+ newMessages: {type: Schema.Types.Mixed, default: () => {
+ return {};
+ }},
+
+ challenges: [{type: String, ref: 'Challenge', validate: [validator.isUUID, 'Invalid uuid.']}],
+
+ invitations: {
+ // Using an array without validation because otherwise mongoose treat this as a subdocument and applies _id by default
+ // Schema is (id, name, inviter)
+ // TODO one way to fix is http://mongoosejs.com/docs/guide.html#_id
+ guilds: {type: Array, default: () => []},
+ // Using a Mixed type because otherwise user.invitations.party = {} // to reset invitation, causes validation to fail TODO
+ // schema is the same as for guild invitations (id, name, inviter)
+ party: {type: Schema.Types.Mixed, default: () => {
+ return {};
+ }},
+ },
+
+ guilds: [{type: String, ref: 'Group', validate: [validator.isUUID, 'Invalid uuid.']}],
+
+ party: {
+ _id: {type: String, validate: [validator.isUUID, 'Invalid uuid.'], ref: 'Group'},
+ order: {type: String, default: 'level'},
+ orderAscending: {type: String, default: 'ascending'},
+ quest: {
+ key: String,
+ progress: {
+ up: {type: Number, default: 0},
+ down: {type: Number, default: 0},
+ collect: {type: Schema.Types.Mixed, default: () => {
+ return {};
+ }}, // {feather:1, ingot:2}
+ },
+ completed: String, // When quest is done, we move it from key => completed, and it's a one-time flag (for modal) that they unset by clicking "ok" in browser
+ RSVPNeeded: {type: Boolean, default: false}, // Set to true when invite is pending, set to false when quest invite is accepted or rejected, quest starts, or quest is cancelled
+ },
+ },
+ preferences: {
+ dayStart: {type: Number, default: 0, min: 0, max: 23},
+ size: {type: String, enum: ['broad', 'slim'], default: 'slim'},
+ hair: {
+ color: {type: String, default: 'red'},
+ base: {type: Number, default: 3},
+ bangs: {type: Number, default: 1},
+ beard: {type: Number, default: 0},
+ mustache: {type: Number, default: 0},
+ flower: {type: Number, default: 1},
+ },
+ hideHeader: {type: Boolean, default: false},
+ skin: {type: String, default: '915533'},
+ shirt: {type: String, default: 'blue'},
+ timezoneOffset: {type: Number, default: 0},
+ sound: {type: String, default: 'off', enum: ['off', 'danielTheBard', 'gokulTheme', 'luneFoxTheme', 'wattsTheme']},
+ chair: {type: String, default: 'none'},
+ timezoneOffsetAtLastCron: Number,
+ language: String,
+ automaticAllocation: Boolean,
+ allocationMode: {type: String, enum: ['flat', 'classbased', 'taskbased'], default: 'flat'},
+ autoEquip: {type: Boolean, default: true},
+ costume: Boolean,
+ dateFormat: {type: String, enum: ['MM/dd/yyyy', 'dd/MM/yyyy', 'yyyy/MM/dd'], default: 'MM/dd/yyyy'},
+ sleep: {type: Boolean, default: false},
+ stickyHeader: {type: Boolean, default: true},
+ disableClasses: {type: Boolean, default: false},
+ newTaskEdit: {type: Boolean, default: false},
+ dailyDueDefaultView: {type: Boolean, default: false},
+ tagsCollapsed: {type: Boolean, default: false},
+ advancedCollapsed: {type: Boolean, default: false},
+ toolbarCollapsed: {type: Boolean, default: false},
+ reverseChatOrder: {type: Boolean, default: false},
+ background: String,
+ displayInviteToPartyWhenPartyIs1: {type: Boolean, default: true},
+ webhooks: {type: Schema.Types.Mixed, default: () => {
+ return {};
+ }},
+ // For the following fields make sure to use strict comparison when searching for falsey values (=== false)
+ // As users who didn't login after these were introduced may have them undefined/null
+ emailNotifications: {
+ unsubscribeFromAll: {type: Boolean, default: false},
+ newPM: {type: Boolean, default: true},
+ kickedGroup: {type: Boolean, default: true},
+ wonChallenge: {type: Boolean, default: true},
+ giftedGems: {type: Boolean, default: true},
+ giftedSubscription: {type: Boolean, default: true},
+ invitedParty: {type: Boolean, default: true},
+ invitedGuild: {type: Boolean, default: true},
+ questStarted: {type: Boolean, default: true},
+ invitedQuest: {type: Boolean, default: true},
+ // remindersToLogin: {type: Boolean, default: true},
+ // importantAnnouncements are in fact the recapture emails
+ importantAnnouncements: {type: Boolean, default: true},
+ weeklyRecaps: {type: Boolean, default: true},
+ },
+ suppressModals: {
+ levelUp: {type: Boolean, default: false},
+ hatchPet: {type: Boolean, default: false},
+ raisePet: {type: Boolean, default: false},
+ streak: {type: Boolean, default: false},
+ },
+ improvementCategories: {
+ type: Array,
+ validate: (categories) => {
+ const validCategories = ['work', 'exercise', 'healthWellness', 'school', 'teams', 'chores', 'creativity'];
+ let isValidCategory = categories.every(category => validCategories.indexOf(category) !== -1);
+ return isValidCategory;
+ },
+ },
+ },
+ profile: {
+ blurb: String,
+ imageUrl: String,
+ name: String,
+ },
+ stats: {
+ hp: {type: Number, default: shared.maxHealth},
+ mp: {type: Number, default: 10},
+ exp: {type: Number, default: 0},
+ gp: {type: Number, default: 0},
+ lvl: {type: Number, default: 1},
+
+ // Class System
+ class: {type: String, enum: ['warrior', 'rogue', 'wizard', 'healer'], default: 'warrior', required: true},
+ points: {type: Number, default: 0},
+ str: {type: Number, default: 0},
+ con: {type: Number, default: 0},
+ int: {type: Number, default: 0},
+ per: {type: Number, default: 0},
+ buffs: {
+ str: {type: Number, default: 0},
+ int: {type: Number, default: 0},
+ per: {type: Number, default: 0},
+ con: {type: Number, default: 0},
+ stealth: {type: Number, default: 0},
+ streaks: {type: Boolean, default: false},
+ snowball: {type: Boolean, default: false},
+ spookySparkles: {type: Boolean, default: false},
+ shinySeed: {type: Boolean, default: false},
+ seafoam: {type: Boolean, default: false},
+ },
+ training: {
+ int: {type: Number, default: 0},
+ per: {type: Number, default: 0},
+ str: {type: Number, default: 0},
+ con: {type: Number, default: 0},
+ },
+ },
+
+ tags: [TagSchema],
+
+ inbox: {
+ newMessages: {type: Number, default: 0},
+ blocks: {type: Array, default: () => []},
+ messages: {type: Schema.Types.Mixed, default: () => {
+ return {};
+ }},
+ optOut: {type: Boolean, default: false},
+ },
+ tasksOrder: {
+ habits: [{type: String, ref: 'Task'}],
+ dailys: [{type: String, ref: 'Task'}],
+ todos: [{type: String, ref: 'Task'}],
+ rewards: [{type: String, ref: 'Task'}],
+ },
+ extra: {type: Schema.Types.Mixed, default: () => {
+ return {};
+ }},
+ pushDevices: {
+ type: [{
+ regId: {type: String},
+ type: {type: String},
+ }],
+ default: () => [],
+ },
+}, {
+ strict: true,
+ minimize: false, // So empty objects are returned
+});
+
+schema.plugin(baseModel, {
+ // noSet is not used as updating uses a whitelist and creating only accepts specific params (password, email, username, ...)
+ noSet: [],
+ private: ['auth.local.hashed_password', 'auth.local.salt', '_cronSignature'],
+ toJSONTransform: function userToJSON (plainObj, originalDoc) {
+ // plainObj.filters = {}; // TODO Not saved, remove?
+ plainObj._tmp = originalDoc._tmp; // be sure to send down drop notifs
+
+ return plainObj;
+ },
+});
+
+// A list of publicly accessible fields (not everything from preferences because there are also a lot of settings tha should remain private)
+export let publicFields = `preferences.size preferences.hair preferences.skin preferences.shirt
+ preferences.chair preferences.costume preferences.sleep preferences.background profile stats
+ achievements party backer contributor auth.timestamps items`;
+
+// The minimum amount of data needed when populating multiple users
+export let nameFields = 'profile.name';
+
+schema.post('init', function postInitUser (doc) {
+ shared.wrap(doc);
+});
+
+function _populateDefaultTasks (user, taskTypes) {
+ let tagsI = taskTypes.indexOf('tag');
+
+ if (tagsI !== -1) {
+ user.tags = _.map(shared.content.userDefaults.tags, (tag) => {
+ let newTag = _.cloneDeep(tag);
+
+ // tasks automatically get _id=helpers.uuid() from TaskSchema id.default, but tags are Schema.Types.Mixed - so we need to manually invoke here
+ newTag.id = shared.uuid();
+ // Render tag's name in user's language
+ newTag.name = newTag.name(user.preferences.language);
+ return newTag;
+ });
+ }
+
+ let tasksToCreate = [];
+
+ if (tagsI !== -1) {
+ taskTypes = _.clone(taskTypes);
+ taskTypes.splice(tagsI, 1);
+ }
+
+ _.each(taskTypes, (taskType) => {
+ let tasksOfType = _.map(shared.content.userDefaults[`${taskType}s`], (taskDefaults) => {
+ let newTask = new Tasks[taskType](taskDefaults);
+
+ newTask.userId = user._id;
+ newTask.text = taskDefaults.text(user.preferences.language);
+ if (newTask.notes) newTask.notes = taskDefaults.notes(user.preferences.language);
+ if (taskDefaults.checklist) {
+ newTask.checklist = _.map(taskDefaults.checklist, (checklistItem) => {
+ checklistItem.text = checklistItem.text(user.preferences.language);
+ return checklistItem;
+ });
+ }
+
+ return newTask.save();
+ });
+
+ tasksToCreate.push(...tasksOfType);
+ });
+
+ return Bluebird.all(tasksToCreate)
+ .then((tasksCreated) => {
+ _.each(tasksCreated, (task) => {
+ user.tasksOrder[`${task.type}s`].push(task._id);
+ });
+ });
+}
+
+function _populateDefaultsForNewUser (user) {
+ let taskTypes;
+ let iterableFlags = user.flags.toObject();
+
+ if (user.registeredThrough === 'habitica-web' || user.registeredThrough === 'habitica-android') {
+ taskTypes = ['habit', 'daily', 'todo', 'reward', 'tag'];
+
+ _.each(iterableFlags.tutorial.common, (val, section) => {
+ user.flags.tutorial.common[section] = true;
+ });
+ } else {
+ taskTypes = ['todo', 'tag'];
+ user.flags.showTour = false;
+
+ _.each(iterableFlags.tour, (val, section) => {
+ user.flags.tour[section] = -2;
+ });
+ }
+
+ return _populateDefaultTasks(user, taskTypes);
+}
+
+function _setProfileName (user) {
+ let fb = user.auth.facebook;
+
+ let localUsername = user.auth.local && user.auth.local.username;
+ let facebookUsername = fb && (fb.displayName || fb.name || fb.username || `${fb.first_name && fb.first_name} ${fb.last_name}`);
+ let anonymous = 'Anonymous';
+
+ return localUsername || facebookUsername || anonymous;
+}
+
+schema.pre('save', true, function preSaveUser (next, done) {
+ next();
+
+ if (_.isNaN(this.preferences.dayStart) || this.preferences.dayStart < 0 || this.preferences.dayStart > 23) {
+ this.preferences.dayStart = 0;
+ }
+
+ if (!this.profile.name) {
+ this.profile.name = _setProfileName(this);
+ }
+
+ // Determines if Beast Master should be awarded
+ let beastMasterProgress = shared.count.beastMasterProgress(this.items.pets);
+
+ if (beastMasterProgress >= 90 || this.achievements.beastMasterCount > 0) {
+ this.achievements.beastMaster = true;
+ }
+
+ // Determines if Mount Master should be awarded
+ let mountMasterProgress = shared.count.mountMasterProgress(this.items.mounts);
+
+ if (mountMasterProgress >= 90 || this.achievements.mountMasterCount > 0) {
+ this.achievements.mountMaster = true;
+ }
+
+ // Determines if Triad Bingo should be awarded
+
+ let dropPetCount = shared.count.dropPetsCurrentlyOwned(this.items.pets);
+ let qualifiesForTriad = dropPetCount >= 90 && mountMasterProgress >= 90;
+
+ if (qualifiesForTriad || this.achievements.triadBingoCount > 0) {
+ this.achievements.triadBingo = true;
+ }
+
+ // Enable weekly recap emails for old users who sign in
+ if (this.flags.lastWeeklyRecapDiscriminator) {
+ // Enable weekly recap emails in 24 hours
+ this.flags.lastWeeklyRecap = moment().subtract(6, 'days').toDate();
+ // Unset the field so this is run only once
+ this.flags.lastWeeklyRecapDiscriminator = undefined;
+ }
+
+ // EXAMPLE CODE for allowing all existing and new players to be
+ // automatically granted an item during a certain time period:
+ // if (!this.items.pets['JackOLantern-Base'] && moment().isBefore('2014-11-01'))
+ // this.items.pets['JackOLantern-Base'] = 5;
+
+ // our own version incrementer
+ if (_.isNaN(this._v) || !_.isNumber(this._v)) this._v = 0;
+ this._v++;
+
+ // Populate new users with default content
+ if (this.isNew) {
+ _populateDefaultsForNewUser(this)
+ .then(() => done())
+ .catch(done);
+ } else {
+ done();
+ }
+});
+
+schema.pre('update', function preUpdateUser () {
+ this.update({}, {$inc: {_v: 1}});
+});
+
+schema.methods.isSubscribed = function isSubscribed () {
+ return !!this.purchased.plan.customerId; // eslint-disable-line no-implicit-coercion
+};
+
+// Get an array of groups ids the user is member of
+schema.methods.getGroups = function getUserGroups () {
+ let userGroups = this.guilds.slice(0); // clone user.guilds so we don't modify the original
+ if (this.party._id) userGroups.push(this.party._id);
+ userGroups.push(TAVERN_ID);
+ return userGroups;
+};
+
+schema.methods.sendMessage = async function sendMessage (userToReceiveMessage, message) {
+ let sender = this;
+
+ shared.refPush(userToReceiveMessage.inbox.messages, chatDefaults(message, sender));
+ userToReceiveMessage.inbox.newMessages++;
+ userToReceiveMessage._v++;
+ userToReceiveMessage.markModified('inbox.messages');
+
+ shared.refPush(sender.inbox.messages, defaults({sent: true}, chatDefaults(message, userToReceiveMessage)));
+ sender.markModified('inbox.messages');
+
+ let promises = [userToReceiveMessage.save(), sender.save()];
+ await Bluebird.all(promises);
+};
+
+// Methods to adapt the new schema to API v2 responses (mostly tasks inside the user model)
+// These will be removed once API v2 is discontinued
+
+// Get all the tasks belonging to a user,
+schema.methods.getTasks = function getUserTasks () {
+ let args = Array.from(arguments);
+ let cb;
+ let type;
+
+ if (args.length === 1) {
+ cb = args[0];
+ } else {
+ type = args[0];
+ cb = args[1];
+ }
+
+ let query = {
+ userId: this._id,
+ };
+
+ if (type) query.type = type;
+
+ Tasks.Task.find(query, cb);
+};
+
+// Given user and an array of tasks, return an API compatible user + tasks obj
+schema.methods.addTasksToUser = function addTasksToUser (tasks) {
+ let obj = this.toJSON();
+
+ obj.id = obj._id;
+ obj.filters = {};
+
+ obj.tags = obj.tags.map(tag => {
+ return {
+ id: tag.id,
+ name: tag.name,
+ challenge: tag.challenge,
+ };
+ });
+
+ let tasksOrder = obj.tasksOrder; // Saving a reference because we won't return it
+
+ obj.habits = [];
+ obj.dailys = [];
+ obj.todos = [];
+ obj.rewards = [];
+
+ obj.tasksOrder = undefined;
+ let unordered = [];
+
+ tasks.forEach((task) => {
+ // We want to push the task at the same position where it's stored in tasksOrder
+ let pos = tasksOrder[`${task.type}s`].indexOf(task._id);
+ if (pos === -1) { // Should never happen, it means the lists got out of sync
+ unordered.push(task.toJSONV2());
+ } else {
+ obj[`${task.type}s`][pos] = task.toJSONV2();
+ }
+ });
+
+ // Reconcile unordered items
+ unordered.forEach((task) => {
+ obj[`${task.type}s`].push(task);
+ });
+
+ // Remove null values that can be created when inserting tasks at an index > length
+ ['habits', 'dailys', 'rewards', 'todos'].forEach((type) => {
+ obj[type] = _.compact(obj[type]);
+ });
+
+ return obj;
+};
+
+// Return the data maintaining backward compatibility
+schema.methods.getTransformedData = function getTransformedData (cb) {
+ let self = this;
+ this.getTasks((err, tasks) => {
+ if (err) return cb(err);
+ cb(null, self.addTasksToUser(tasks));
+ });
+};
+
+// END of API v2 methods
+export let model = mongoose.model('User', schema);
+
+// Initially export an empty object so external requires will get
+// the right object by reference when it's defined later
+// Otherwise it would remain undefined if requested before the query executes
+export let mods = [];
+
+mongoose.model('User')
+ .find({'contributor.admin': true})
+ .sort('-contributor.level -backer.npc profile.name')
+ .select('profile contributor backer')
+ .exec()
+ .then((foundMods) => {
+ // Using push to maintain the reference to mods
+ mods.push(...foundMods);
+ }); // In case of failure we don't want this to crash the whole server
diff --git a/website/server/routes/api-v2/auth.js b/website/server/routes/api-v2/auth.js
new file mode 100644
index 0000000000..f8af1b4338
--- /dev/null
+++ b/website/server/routes/api-v2/auth.js
@@ -0,0 +1,21 @@
+var auth = require('../../controllers/api-v2/auth');
+var express = require('express');
+var i18n = require('../../libs/api-v2/i18n');
+var router = express.Router();
+import {
+ getUserLanguage
+} from '../../middlewares/api-v3/language';
+
+/* auth.auth*/
+// auth.setupPassport(router); //TODO make this consistent with the others
+router.post('/register', getUserLanguage, auth.registerUser);
+router.post('/user/auth/local', getUserLanguage, auth.loginLocal);
+router.post('/user/auth/social', getUserLanguage, auth.loginSocial);
+router.delete('/user/auth/social', getUserLanguage, auth.auth, auth.deleteSocial);
+router.post('/user/reset-password', getUserLanguage, auth.resetPassword);
+router.post('/user/change-password', getUserLanguage, auth.auth, auth.changePassword);
+router.post('/user/change-username', getUserLanguage, auth.auth, auth.changeUsername);
+router.post('/user/change-email', getUserLanguage, auth.auth, auth.changeEmail);
+// router.post('/user/auth/firebase', i18n.getUserLanguage, auth.auth, auth.getFirebaseToken);
+
+module.exports = router;
diff --git a/website/server/routes/api-v2/coupon.js b/website/server/routes/api-v2/coupon.js
new file mode 100644
index 0000000000..7caee3f815
--- /dev/null
+++ b/website/server/routes/api-v2/coupon.js
@@ -0,0 +1,15 @@
+var nconf = require('nconf');
+var express = require('express');
+var router = express.Router();
+var auth = require('../../controllers/api-v2/auth');
+var coupon = require('../../controllers/api-v2/coupon');
+var i18n = require('../../libs/api-v2/i18n');
+import {
+ getUserLanguage
+} from '../../middlewares/api-v3/language';
+
+router.get('/coupons', auth.authWithUrl, getUserLanguage, coupon.ensureAdmin, coupon.getCoupons);
+router.post('/coupons/generate/:event', auth.auth, getUserLanguage, coupon.ensureAdmin, coupon.generateCoupons);
+router.post('/user/coupon/:code', auth.auth, getUserLanguage, coupon.enterCode);
+
+module.exports = router;
diff --git a/website/src/routes/api-v2/swagger.js b/website/server/routes/api-v2/swagger.js
similarity index 91%
rename from website/src/routes/api-v2/swagger.js
rename to website/server/routes/api-v2/swagger.js
index a1500ac39a..c60d8364b6 100644
--- a/website/src/routes/api-v2/swagger.js
+++ b/website/server/routes/api-v2/swagger.js
@@ -13,12 +13,15 @@ var members = require("../../controllers/api-v2/members");
var auth = require("../../controllers/api-v2/auth");
var hall = require("../../controllers/api-v2/hall");
var challenges = require("../../controllers/api-v2/challenges");
-var dataexport = require("../../controllers/dataexport");
+var dataexport = require("../../controllers/api-v2/dataexport");
var nconf = require("nconf");
var cron = user.cron;
var _ = require('lodash');
var content = require('../../../../common').content;
-var i18n = require('../../libs/i18n');
+var i18n = require('../../libs/api-v2/i18n');
+import {
+ getUserLanguage
+} from '../../middlewares/api-v3/language';
var forceRefresh = require('../../middlewares/forceRefresh').middleware;
module.exports = function(swagger, v2) {
@@ -60,7 +63,7 @@ module.exports = function(swagger, v2) {
description: "Export user history",
method: 'GET'
},
- middleware: [auth.auth, i18n.getUserLanguage],
+ middleware: [auth.auth, getUserLanguage],
action: dataexport.history
},
"/user/tasks/{id}/{direction}": {
@@ -134,7 +137,7 @@ module.exports = function(swagger, v2) {
description: 'Unlink a task from its challenge',
parameters: [path("id", "Task ID", "string"), query('keep', "When unlinking a challenge task, how to handle the orphans?", 'string', ['keep', 'keep-all', 'remove', 'remove-all'])]
},
- middleware: [auth.auth, i18n.getUserLanguage],
+ middleware: [auth.auth, getUserLanguage],
action: challenges.unlink
},
"/user/inventory/buy": {
@@ -235,7 +238,7 @@ module.exports = function(swagger, v2) {
method: 'DELETE',
description: "Delete a user object entirely, USE WITH CAUTION!"
},
- middleware: [auth.auth, i18n.getUserLanguage],
+ middleware: [auth.auth, getUserLanguage],
action: user["delete"]
},
"/user/revive": {
@@ -311,7 +314,7 @@ module.exports = function(swagger, v2) {
description: "This is an advanced route which is useful for apps which might for example need offline support. You can send a whole batch of user-based operations, which allows you to queue them up offline and send them all at once. The format is {op:'nameOfOperation',parameters:{},body:{},query:{}}",
parameters: [body('', 'The array of batch-operations to perform', 'object')]
},
- middleware: [forceRefresh, auth.auth, i18n.getUserLanguage, cron, user.sessionPartyInvite],
+ middleware: [forceRefresh, auth.auth, getUserLanguage, cron, user.sessionPartyInvite],
action: user.batchUpdate
},
"/user/tags/{id}:GET": {
@@ -406,7 +409,7 @@ module.exports = function(swagger, v2) {
description: "Get a list of groups",
parameters: [query('type', "Comma-separated types of groups to return, eg 'party,guilds,public,tavern'", 'string')]
},
- middleware: [auth.auth, i18n.getUserLanguage],
+ middleware: [auth.auth, getUserLanguage],
action: groups.list
},
"/groups:POST": {
@@ -416,7 +419,7 @@ module.exports = function(swagger, v2) {
description: 'Create a group',
parameters: [body('', 'Group object (see GroupSchema)', 'object')]
},
- middleware: [auth.auth, i18n.getUserLanguage],
+ middleware: [auth.auth, getUserLanguage],
action: groups.create
},
"/groups/{gid}:GET": {
@@ -425,7 +428,7 @@ module.exports = function(swagger, v2) {
description: "Get a group. The party the user currently is in can be accessed with the gid 'party'.",
parameters: [path('gid', 'Group ID', 'string')]
},
- middleware: [auth.auth, i18n.getUserLanguage],
+ middleware: [auth.auth, getUserLanguage],
action: groups.get
},
"/groups/{gid}:POST": {
@@ -435,7 +438,7 @@ module.exports = function(swagger, v2) {
description: "Edit a group",
parameters: [body('', 'Group object (see GroupSchema)', 'object')]
},
- middleware: [auth.auth, i18n.getUserLanguage, groups.attachGroup],
+ middleware: [auth.auth, getUserLanguage, groups.attachGroup],
action: groups.update
},
"/groups/{gid}/join": {
@@ -444,7 +447,7 @@ module.exports = function(swagger, v2) {
description: 'Join a group',
parameters: [path('gid', 'Id of the group to join', 'string')]
},
- middleware: [auth.auth, i18n.getUserLanguage, groups.attachGroup],
+ middleware: [auth.auth, getUserLanguage, groups.attachGroup],
action: groups.join
},
"/groups/{gid}/leave": {
@@ -453,7 +456,7 @@ module.exports = function(swagger, v2) {
description: 'Leave a group',
parameters: [path('gid', 'ID of the group to leave', 'string')]
},
- middleware: [auth.auth, i18n.getUserLanguage, groups.attachGroup],
+ middleware: [auth.auth, getUserLanguage, groups.attachGroup],
action: groups.leave
},
"/groups/{gid}/invite": {
@@ -462,7 +465,7 @@ module.exports = function(swagger, v2) {
description: "Invite a user to a group",
parameters: [path('gid', 'Group id', 'string'), body('', 'a payload of invites either under body.uuids or body.emails, only one of them!', 'object')]
},
- middleware: [auth.auth, i18n.getUserLanguage, groups.attachGroup],
+ middleware: [auth.auth, getUserLanguage, groups.attachGroup],
action: groups.invite
},
"/groups/{gid}/removeMember": {
@@ -471,7 +474,7 @@ module.exports = function(swagger, v2) {
description: "Remove / boot a member from a group",
parameters: [path('gid', 'Group id', 'string'), query('uuid', 'User id to boot', 'string')]
},
- middleware: [auth.auth, i18n.getUserLanguage, groups.attachGroup],
+ middleware: [auth.auth, getUserLanguage, groups.attachGroup],
action: groups.removeMember
},
"/groups/{gid}/questAccept": {
@@ -480,7 +483,7 @@ module.exports = function(swagger, v2) {
description: "Accept a quest invitation",
parameters: [path('gid', "Group id", 'string'), query('key', "optional. if provided, trigger new invite, if not, accept existing invite", 'string')]
},
- middleware: [auth.auth, i18n.getUserLanguage, groups.attachGroup],
+ middleware: [auth.auth, getUserLanguage, groups.attachGroup],
action: groups.questAccept
},
"/groups/{gid}/questReject": {
@@ -489,7 +492,7 @@ module.exports = function(swagger, v2) {
description: 'Reject quest invitation',
parameters: [path('gid', 'Group id', 'string')]
},
- middleware: [auth.auth, i18n.getUserLanguage, groups.attachGroup],
+ middleware: [auth.auth, getUserLanguage, groups.attachGroup],
action: groups.questReject
},
"/groups/{gid}/questCancel": {
@@ -498,7 +501,7 @@ module.exports = function(swagger, v2) {
description: 'Cancel quest before it starts (in invitation stage)',
parameters: [path('gid', 'Group to cancel quest in', 'string')]
},
- middleware: [auth.auth, i18n.getUserLanguage, groups.attachGroup],
+ middleware: [auth.auth, getUserLanguage, groups.attachGroup],
action: groups.questCancel
},
"/groups/{gid}/questAbort": {
@@ -507,7 +510,7 @@ module.exports = function(swagger, v2) {
description: 'Abort quest after it has started (all progress will be lost)',
parameters: [path('gid', 'Group to abort quest in', 'string')]
},
- middleware: [auth.auth, i18n.getUserLanguage, groups.attachGroup],
+ middleware: [auth.auth, getUserLanguage, groups.attachGroup],
action: groups.questAbort
},
"/groups/{gid}/questLeave": {
@@ -516,7 +519,7 @@ module.exports = function(swagger, v2) {
description: 'Leave an active quest (Quest leaders cannot leave active quests. They must abort the quest to leave)',
parameters: [path('gid', 'Group to leave quest in', 'string')]
},
- middleware: [auth.auth, i18n.getUserLanguage, groups.attachGroup],
+ middleware: [auth.auth, getUserLanguage, groups.attachGroup],
action: groups.questLeave
},
"/groups/{gid}/chat:GET": {
@@ -525,7 +528,7 @@ module.exports = function(swagger, v2) {
description: "Get all chat messages",
parameters: [path('gid', 'Group to return the chat from ', 'string')]
},
- middleware: [auth.auth, i18n.getUserLanguage, groups.attachGroup],
+ middleware: [auth.auth, getUserLanguage, groups.attachGroup],
action: groups.getChat
},
"/groups/{gid}/chat:POST": {
@@ -535,7 +538,7 @@ module.exports = function(swagger, v2) {
description: "Send a chat message",
parameters: [query('message', 'Chat message', 'string'), path('gid', 'Group id', 'string')]
},
- middleware: [auth.auth, i18n.getUserLanguage, groups.attachGroup],
+ middleware: [auth.auth, getUserLanguage, groups.attachGroup],
action: groups.postChat
},
"/groups/{gid}/chat/seen": {
@@ -552,7 +555,7 @@ module.exports = function(swagger, v2) {
description: 'Delete a chat message in a given group',
parameters: [path('gid', 'ID of the group containing the message to be deleted', 'string'), path('messageId', 'ID of message to be deleted', 'string')]
},
- middleware: [auth.auth, i18n.getUserLanguage, groups.attachGroup],
+ middleware: [auth.auth, getUserLanguage, groups.attachGroup],
action: groups.deleteChatMessage
},
"/groups/{gid}/chat/{mid}/like": {
@@ -561,7 +564,7 @@ module.exports = function(swagger, v2) {
description: "Like a chat message",
parameters: [path('gid', 'Group id', 'string'), path('mid', 'Message id', 'string')]
},
- middleware: [auth.auth, i18n.getUserLanguage, groups.attachGroup],
+ middleware: [auth.auth, getUserLanguage, groups.attachGroup],
action: groups.likeChatMessage
},
"/groups/{gid}/chat/{mid}/flag": {
@@ -570,7 +573,7 @@ module.exports = function(swagger, v2) {
description: "Flag a chat message",
parameters: [path('gid', 'Group id', 'string'), path('mid', 'Message id', 'string')]
},
- middleware: [auth.auth, i18n.getUserLanguage, groups.attachGroup],
+ middleware: [auth.auth, getUserLanguage, groups.attachGroup],
action: groups.flagChatMessage
},
"/groups/{gid}/chat/{mid}/clearflags": {
@@ -579,7 +582,7 @@ module.exports = function(swagger, v2) {
description: "Clear flag count from message and unhide it",
parameters: [path('gid', 'Group id', 'string'), path('mid', 'Message id', 'string')]
},
- middleware: [auth.auth, i18n.getUserLanguage, groups.attachGroup],
+ middleware: [auth.auth, getUserLanguage, groups.attachGroup],
action: groups.clearFlagCount
},
"/members/{uuid}:GET": {
@@ -588,7 +591,7 @@ module.exports = function(swagger, v2) {
description: "Get a member.",
parameters: [path('uuid', 'Member ID', 'string')]
},
- middleware: [i18n.getUserLanguage],
+ middleware: [getUserLanguage],
action: members.getMember
},
"/members/{uuid}/message": {
@@ -620,14 +623,14 @@ module.exports = function(swagger, v2) {
},
"/hall/heroes": {
spec: {},
- middleware: [auth.auth, i18n.getUserLanguage],
+ middleware: [auth.auth, getUserLanguage],
action: hall.getHeroes
},
"/hall/heroes/{uid}:GET": {
spec: {
path: "/hall/heroes/{uid}"
},
- middleware: [auth.auth, i18n.getUserLanguage, hall.ensureAdmin],
+ middleware: [auth.auth, getUserLanguage, hall.ensureAdmin],
action: hall.getHero
},
"/hall/heroes/{uid}:POST": {
@@ -635,14 +638,14 @@ module.exports = function(swagger, v2) {
method: 'POST',
path: "/hall/heroes/{uid}"
},
- middleware: [auth.auth, i18n.getUserLanguage, hall.ensureAdmin],
+ middleware: [auth.auth, getUserLanguage, hall.ensureAdmin],
action: hall.updateHero
},
"/hall/patrons": {
spec: {
parameters: [query('page', 'Page number to fetch (this list is long)', 'string')]
},
- middleware: [auth.auth, i18n.getUserLanguage],
+ middleware: [auth.auth, getUserLanguage],
action: hall.getPatrons
},
"/challenges:GET": {
@@ -650,7 +653,7 @@ module.exports = function(swagger, v2) {
path: '/challenges',
description: "Get a list of challenges"
},
- middleware: [auth.auth, i18n.getUserLanguage],
+ middleware: [auth.auth, getUserLanguage],
action: challenges.list
},
"/challenges:POST": {
@@ -660,7 +663,7 @@ module.exports = function(swagger, v2) {
description: "Create a challenge",
parameters: [body('', 'Challenge object (see ChallengeSchema)', 'object')]
},
- middleware: [auth.auth, i18n.getUserLanguage],
+ middleware: [auth.auth, getUserLanguage],
action: challenges.create
},
"/challenges/{cid}:GET": {
@@ -669,7 +672,7 @@ module.exports = function(swagger, v2) {
description: 'Get a challenge',
parameters: [path('cid', 'Challenge id', 'string')]
},
- middleware: [auth.auth, i18n.getUserLanguage],
+ middleware: [auth.auth, getUserLanguage],
action: challenges.get
},
"/challenges/{cid}/csv": {
@@ -686,7 +689,7 @@ module.exports = function(swagger, v2) {
description: "Update a challenge",
parameters: [path('cid', 'Challenge id', 'string'), body('', 'Challenge object (see ChallengeSchema)', 'object')]
},
- middleware: [auth.auth, i18n.getUserLanguage],
+ middleware: [auth.auth, getUserLanguage],
action: challenges.update
},
"/challenges/{cid}:DELETE": {
@@ -696,7 +699,7 @@ module.exports = function(swagger, v2) {
description: "Delete a challenge",
parameters: [path('cid', 'Challenge id', 'string')]
},
- middleware: [auth.auth, i18n.getUserLanguage],
+ middleware: [auth.auth, getUserLanguage],
action: challenges["delete"]
},
"/challenges/{cid}/close": {
@@ -705,7 +708,7 @@ module.exports = function(swagger, v2) {
description: 'Close a challenge',
parameters: [path('cid', 'Challenge id', 'string'), query('uid', 'User ID of the winner', 'string', true)]
},
- middleware: [auth.auth, i18n.getUserLanguage],
+ middleware: [auth.auth, getUserLanguage],
action: challenges.selectWinner
},
"/challenges/{cid}/join": {
@@ -714,7 +717,7 @@ module.exports = function(swagger, v2) {
description: "Join a challenge",
parameters: [path('cid', 'Challenge id', 'string')]
},
- middleware: [auth.auth, i18n.getUserLanguage],
+ middleware: [auth.auth, getUserLanguage],
action: challenges.join
},
"/challenges/{cid}/leave": {
@@ -723,7 +726,7 @@ module.exports = function(swagger, v2) {
description: 'Leave a challenge',
parameters: [path('cid', 'Challenge id', 'string')]
},
- middleware: [auth.auth, i18n.getUserLanguage],
+ middleware: [auth.auth, getUserLanguage],
action: challenges.leave
},
"/challenges/{cid}/member/{uid}": {
@@ -731,7 +734,7 @@ module.exports = function(swagger, v2) {
description: "Get a member's progress in a particular challenge",
parameters: [path('cid', 'Challenge id', 'string'), path('uid', 'User id', 'string')]
},
- middleware: [auth.auth, i18n.getUserLanguage],
+ middleware: [auth.auth, getUserLanguage],
action: challenges.getMember
}
};
@@ -765,7 +768,7 @@ module.exports = function(swagger, v2) {
method: 'GET'
});
if (route.middleware == null) {
- route.middleware = path.indexOf('/user') === 0 ? [auth.auth, i18n.getUserLanguage, cron] : [i18n.getUserLanguage];
+ route.middleware = path.indexOf('/user') === 0 ? [auth.auth, getUserLanguage, cron] : [i18n.getUserLanguage];
}
swagger["add" + route.spec.method](route);
return true;
diff --git a/website/server/routes/api-v2/unsubscription.js b/website/server/routes/api-v2/unsubscription.js
new file mode 100644
index 0000000000..cbd2e16554
--- /dev/null
+++ b/website/server/routes/api-v2/unsubscription.js
@@ -0,0 +1,11 @@
+var express = require('express');
+var router = express.Router();
+var i18n = require('../../libs/api-v2/i18n');
+var unsubscription = require('../../controllers/api-v2/unsubscription');
+import {
+ getUserLanguage
+} from '../../middlewares/api-v3/language';
+
+router.get('/unsubscribe', getUserLanguage, unsubscription.unsubscribe);
+
+module.exports = router;
diff --git a/website/server/routes/payments.js b/website/server/routes/payments.js
new file mode 100644
index 0000000000..13e9ec9163
--- /dev/null
+++ b/website/server/routes/payments.js
@@ -0,0 +1,34 @@
+var nconf = require('nconf');
+var express = require('express');
+var router = express.Router();
+var auth = require('../controllers/api-v2/auth');
+var payments = require('../controllers/payments');
+var i18n = require('../libs/api-v2/i18n');
+import {
+ getUserLanguage
+} from '../../middlewares/api-v3/language';
+
+router.get('/paypal/checkout', auth.authWithUrl, getUserLanguage, payments.paypalCheckout);
+router.get('/paypal/checkout/success', getUserLanguage, payments.paypalCheckoutSuccess);
+router.get('/paypal/subscribe', auth.authWithUrl, getUserLanguage, payments.paypalSubscribe);
+router.get('/paypal/subscribe/success', getUserLanguage, payments.paypalSubscribeSuccess);
+router.get('/paypal/subscribe/cancel', auth.authWithUrl, getUserLanguage, payments.paypalSubscribeCancel);
+router.post('/paypal/ipn', getUserLanguage, payments.paypalIPN); // misc ipn handling
+
+router.post('/stripe/checkout', auth.auth, getUserLanguage, payments.stripeCheckout);
+router.post('/stripe/subscribe/edit', auth.auth, getUserLanguage, payments.stripeSubscribeEdit);
+//router.get('/stripe/subscribe', auth.authWithUrl, getUserLanguage, payments.stripeSubscribe); // checkout route is used (above) with ?plan= instead
+router.get('/stripe/subscribe/cancel', auth.authWithUrl, getUserLanguage, payments.stripeSubscribeCancel);
+
+router.post('/amazon/verifyAccessToken', auth.auth, getUserLanguage, payments.amazonVerifyAccessToken);
+router.post('/amazon/createOrderReferenceId', auth.auth, getUserLanguage, payments.amazonCreateOrderReferenceId);
+router.post('/amazon/checkout', auth.auth, getUserLanguage, payments.amazonCheckout);
+router.post('/amazon/subscribe', auth.auth, getUserLanguage, payments.amazonSubscribe);
+router.get('/amazon/subscribe/cancel', auth.authWithUrl, getUserLanguage, payments.amazonSubscribeCancel);
+
+router.post('/iap/android/verify', auth.authWithUrl, /*getUserLanguage, */payments.iapAndroidVerify);
+router.post('/iap/ios/verify', auth.auth, /*getUserLanguage, */ payments.iapIosVerify);
+
+router.get('/api/v2/coupons/valid-discount/:code', /*auth.authWithUrl, getUserLanguage, */ payments.validCoupon);
+
+module.exports = router;
diff --git a/website/server/server.js b/website/server/server.js
new file mode 100644
index 0000000000..f86e6c63b7
--- /dev/null
+++ b/website/server/server.js
@@ -0,0 +1,35 @@
+import nconf from 'nconf';
+import logger from './libs/api-v3/logger';
+import express from 'express';
+import http from 'http';
+import attachMiddlewares from './middlewares/api-v3/index';
+import Bluebird from 'bluebird';
+
+global.Promise = Bluebird;
+
+const server = http.createServer();
+const app = express();
+
+app.set('port', nconf.get('PORT'));
+
+// Setup translations
+import './libs/api-v3/i18n';
+
+// Load config files
+import './libs/api-v3/setupMongoose';
+import './libs/api-v3/firebase';
+import './libs/api-v3/setupPassport';
+
+// Load some schemas & models
+import './models/challenge';
+import './models/group';
+import './models/user';
+
+attachMiddlewares(app, server);
+
+server.on('request', app);
+server.listen(app.get('port'), () => {
+ logger.info(`Express server listening on port ${app.get('port')}`);
+});
+
+module.exports = server;
diff --git a/website/src/controllers/api-v2/auth.js b/website/src/controllers/api-v2/auth.js
deleted file mode 100644
index f63c652814..0000000000
--- a/website/src/controllers/api-v2/auth.js
+++ /dev/null
@@ -1,383 +0,0 @@
-var _ = require('lodash');
-var validator = require('validator');
-var passport = require('passport');
-var shared = require('../../../../common');
-var async = require('async');
-var utils = require('../../libs/utils');
-var nconf = require('nconf');
-var request = require('request');
-var FirebaseTokenGenerator = require('firebase-token-generator');
-var User = require('../../models/user').model;
-var EmailUnsubscription = require('../../models/emailUnsubscription').model;
-var analytics = utils.analytics;
-var i18n = require('./../../libs/i18n');
-
-var isProd = nconf.get('NODE_ENV') === 'production';
-
-var api = module.exports;
-
-var NO_TOKEN_OR_UID = { err: shared.i18n.t('messageAuthMustIncludeTokens') };
-var NO_USER_FOUND = {err: shared.i18n.t('messageAuthNoUserFound') };
-var NO_SESSION_FOUND = { err: shared.i18n.t('messageAuthMustBeLoggedIn') };
-var accountSuspended = function(uuid){
- return {
- err: 'Account has been suspended, please contact leslie@habitica.com with your UUID ('+uuid+') for assistance.',
- code: 'ACCOUNT_SUSPENDED'
- };
-}
-
-api.auth = function(req, res, next) {
- var uid = req.headers['x-api-user'];
- var token = req.headers['x-api-key'];
- if (!(uid && token)) return res.status(401).json(NO_TOKEN_OR_UID);
- User.findOne({_id: uid, apiToken: token}, function(err, user) {
- if (err) return next(err);
- if (_.isEmpty(user)) return res.status(401).json(NO_USER_FOUND);
- if (user.auth.blocked) return res.status(401).json(accountSuspended(user._id));
-
- res.locals.wasModified = req.query._v ? +user._v !== +req.query._v : true;
- res.locals.user = user;
- req.session.userId = user._id;
- return next();
- });
-};
-
-api.authWithSession = function(req, res, next) { //[todo] there is probably a more elegant way of doing this...
- if (!(req.session && req.session.userId))
- return res.status(401).json(NO_SESSION_FOUND);
- User.findOne({_id: req.session.userId}, function(err, user) {
- if (err) return next(err);
- if (_.isEmpty(user)) return res.status(401).json(NO_USER_FOUND);
- res.locals.user = user;
- next();
- });
-};
-
-api.authWithUrl = function(req, res, next) {
- User.findOne({_id:req.query._id, apiToken:req.query.apiToken}, function(err,user){
- if (err) return next(err);
- if (_.isEmpty(user)) return res.status(401).json(NO_USER_FOUND);
- res.locals.user = user;
- next();
- });
-}
-
-api.registerUser = function(req, res, next) {
- var email = req.body.email && req.body.email.toLowerCase();
- var username = req.body.username;
- // Get the lowercase version of username to check that we do not have duplicates
- // So we can search for it in the database and then reject the choosen username if 1 or more results are found
- var lowerCaseUsername = username && username.toLowerCase();
-
- async.auto({
- validate: function(cb) {
- if (!(username && req.body.password && email))
- return cb({code:401, err: shared.i18n.t('messageAuthCredentialsRequired')});
- if (req.body.password !== req.body.confirmPassword)
- return cb({code:401, err: shared.i18n.t('messageAuthPasswordMustMatch')});
- if (!validator.isEmail(email))
- return cb({code:401, err: ":email invalid"});
- cb();
- },
- findReg: function(cb) {
- // Search for duplicates using lowercase version of username
- User.findOne({$or:[{'auth.local.email': email}, {'auth.local.lowerCaseUsername': lowerCaseUsername}]}, {'auth.local':1}, cb);
- },
- findFacebook: function(cb){
- User.findOne({_id: req.headers['x-api-user'], apiToken: req.headers['x-api-key']}, {auth:1}, cb);
- },
- register: ['validate', 'findReg', 'findFacebook', function(cb, data) {
- if (data.findReg) {
- if (email === data.findReg.auth.local.email) return cb({code:401, err:"Email already taken"});
- // Check that the lowercase username isn't already used
- if (lowerCaseUsername === data.findReg.auth.local.lowerCaseUsername) return cb({code:401, err: shared.i18n.t('messageAuthUsernameTaken')});
- }
- var salt = utils.makeSalt();
- var newUser = {
- auth: {
- local: {
- username: username,
- lowerCaseUsername: lowerCaseUsername, // Store the lowercase version of the username
- email: email, // Store email as lowercase
- salt: salt,
- hashed_password: utils.encryptPassword(req.body.password, salt)
- },
- timestamps: {created: +new Date(), loggedIn: +new Date()}
- }
- };
- // existing user, allow them to add local authentication
- if (data.findFacebook) {
- data.findFacebook.auth.local = newUser.auth.local;
- data.findFacebook.registeredThrough = newUser.registeredThrough;
- data.findFacebook.save(cb);
- // new user, register them
- } else {
- newUser.preferences = newUser.preferences || {};
- newUser.preferences.language = req.language; // User language detected from browser, not saved
- var user = new User(newUser);
-
- user.registeredThrough = req.headers['x-client'];
- var analyticsData = {
- category: 'acquisition',
- type: 'local',
- gaLabel: 'local',
- uuid: user._id,
- };
- analytics.track('register', analyticsData)
-
- user.save(function(err, savedUser){
- // Clean previous email preferences
- // TODO when emails added to EmailUnsubcription they should use lowercase version
- EmailUnsubscription.remove({email: savedUser.auth.local.email}, function(){
- utils.txnEmail(savedUser, 'welcome');
- });
- cb.apply(cb, arguments);
- });
- }
- }]
- }, function(err, data) {
- if (err) return err.code ? res.status(err.code).json(err) : next(err);
- res.status(200).json(data.register[0]);
- });
-};
-
-/*
- Register new user with uname / password
- */
-
-
-api.loginLocal = function(req, res, next) {
- var username = req.body.username;
- var password = req.body.password;
- if (!(username && password)) return res.status(401).json({err:'Missing :username or :password in request body, please provide both'});
- var login = validator.isEmail(username) ?
- {'auth.local.email':username.toLowerCase()} : // Emails are all lowercase
- {'auth.local.username':username}; // Use the username as the user typed it
-
- User.findOne(login, {auth:1}, function(err, user){
- if (err) return next(err);
- if (!user) return res.status(401).json({err:"Uh-oh - your username or password is incorrect.\n- Make sure your username or email is typed correctly.\n- You may have signed up with Facebook, not email. Double-check by trying Facebook login.\n- If you forgot your password, click \"Forgot Password\" on the habitica.com website's login form."});
- if (user.auth.blocked) return res.status(401).json(accountSuspended(user._id));
- // We needed the whole user object first so we can get his salt to encrypt password comparison
- User.findOne(
- {$and: [login, {'auth.local.hashed_password': utils.encryptPassword(password, user.auth.local.salt)}]}
- , {_id:1, apiToken:1}
- , function(err, user){
- if (err) return next(err);
- if (!user) return res.status(401).json({err:"Uh-oh - your username or password is incorrect.\n- Make sure your username or email is typed correctly.\n- You may have signed up with Facebook, not email. Double-check by trying Facebook login.\n- If you forgot your password, click \"Forgot Password\" on the habitica.com website's login form."});
- res.json({id: user._id,token: user.apiToken});
- password = null;
- });
- });
-};
-
-/*
- POST /user/auth/social
- */
-api.loginSocial = function(req, res, next) {
- var access_token = req.body.authResponse.access_token,
- network = req.body.network;
- if (network!=='facebook')
- return res.status(401).json({err:"Only Facebook supported currently."});
- async.auto({
- profile: function (cb) {
- passport._strategies[network].userProfile(access_token, cb);
- },
- user: ['profile', function (cb, results) {
- var q = {};
- q['auth.' + network + '.id'] = results.profile.id;
- User.findOne(q, {_id: 1, apiToken: 1, auth: 1}, cb);
- }],
- register: ['profile', 'user', function (cb, results) {
- if (results.user) return cb(null, results.user);
- // Create new user
- var prof = results.profile;
- var user = {
- preferences: {
- language: req.language // User language detected from browser, not saved
- },
- auth: {
- timestamps: {created: +new Date(), loggedIn: +new Date()}
- }
- };
- user.auth[network] = prof;
- user = new User(user);
- user.registeredThrough = req.headers['x-client'];
-
- user.save(function(err, savedUser){
- // Clean previous email preferences
- if(savedUser.auth.facebook.emails && savedUser.auth.facebook.emails[0] && savedUser.auth.facebook.emails[0].value){
- EmailUnsubscription.remove({email: savedUser.auth.facebook.emails[0].value}, function(){
- utils.txnEmail(savedUser, 'welcome');
- });
- }
- cb.apply(cb, arguments);
- });
-
- var analyticsData = {
- category: 'acquisition',
- type: network,
- gaLabel: network,
- uuid: user._id,
- };
- analytics.track('register', analyticsData)
- }]
- }, function(err, results){
- if (err) return res.status(401).json({err: err.toString ? err.toString() : err});
- var acct = results.register[0] ? results.register[0] : results.register;
- if (acct.auth.blocked) return res.status(401).json(accountSuspended(acct._id));
- return res.status(200).json({id:acct._id, token:acct.apiToken});
- })
-};
-
-/**
- * DELETE /user/auth/social
- */
-api.deleteSocial = function(req,res,next){
- if (!res.locals.user.auth.local.username)
- return res.status(401).json({err:"Account lacks another authentication method, can't detach Facebook"});
- //FIXME for some reason, the following gives https://gist.github.com/lefnire/f93eb306069b9089d123
- //res.locals.user.auth.facebook = null;
- //res.locals.user.auth.save(function(err, saved){
- User.update({_id:res.locals.user._id}, {$unset:{'auth.facebook':1}}, function(err){
- if (err) return next(err);
- res.sendStatus(200);
- })
-}
-
-api.resetPassword = function(req, res, next){
- var email = req.body.email && req.body.email.toLowerCase(), // Emails are all lowercase
- salt = utils.makeSalt(),
- newPassword = utils.makeSalt(), // use a salt as the new password too (they'll change it later)
- hashed_password = utils.encryptPassword(newPassword, salt);
-
- if(!email) return res.status(400).json({err: "Email not provided"});
-
- User.findOne({'auth.local.email': email}, function(err, user){
- if (err) return next(err);
- if (!user) return res.status(401).json({err:"Sorry, we can't find a user registered with email " + email + "\n- Make sure your email address is typed correctly.\n- You may have signed up with Facebook, not email. Double-check by trying Facebook login."});
- user.auth.local.salt = salt;
- user.auth.local.hashed_password = hashed_password;
- utils.sendEmail({
- from: "Habitica ",
- to: email,
- subject: "Password Reset for Habitica",
- text: "Password for " + user.auth.local.username + " has been reset to " + newPassword + " Important! Both username and password are case-sensitive -- you must enter both exactly as shown here. We recommend copying and pasting both instead of typing them. Log in at " + nconf.get('BASE_URL') + ". After you've logged in, head to " + nconf.get('BASE_URL') + "/#/options/settings/settings and change your password.",
- html: "Password for " + user.auth.local.username + " has been reset to " + newPassword + "
Important! Both username and password are case-sensitive -- you must enter both exactly as shown here. We recommend copying and pasting both instead of typing them.
Log in at " + nconf.get('BASE_URL') + ". After you've logged in, head to " + nconf.get('BASE_URL') + "/#/options/settings/settings and change your password."
- });
- user.save(function(err){
- if(err) return next(err);
- res.send('New password sent to '+ email);
- email = salt = newPassword = hashed_password = null;
- });
- });
-};
-
-var invalidPassword = function(user, password){
- var hashed_password = utils.encryptPassword(password, user.auth.local.salt);
- if (hashed_password !== user.auth.local.hashed_password)
- return {code:401, err:"Incorrect password"};
- return false;
-}
-
-api.changeUsername = function(req, res, next) {
- var user = res.locals.user;
- var username = req.body.username;
- var lowerCaseUsername = username && username.toLowerCase(); // we search for the lowercased version to intercept duplicates
-
- if(!username) return res.status(400).json({err: "Username not provided"});
- async.waterfall([
- function(cb){
- User.findOne({'auth.local.lowerCaseUsername': lowerCaseUsername}, {auth:1}, cb);
- },
- function(found, cb){
- if (found) return cb({code:401, err: "Username already taken"});
- if (invalidPassword(user, req.body.password)) return cb(invalidPassword(user, req.body.password));
- user.auth.local.username = username;
- user.auth.local.lowerCaseUsername = lowerCaseUsername;
-
- user.save(cb);
- }
- ], function(err){
- if (err) return err.code ? res.status(err.code).json(err) : next(err);
- res.sendStatus(200);
- })
-}
-
-api.changeEmail = function(req, res, next){
- var email = req.body.email && req.body.email.toLowerCase(); // emails are all lowercase
- if(!email) return res.status(400).json({err: "Email not provided"});
-
- async.waterfall([
- function(cb){
- User.findOne({'auth.local.email': email}, {auth:1}, cb);
- },
- function(found, cb){
- if(found) return cb({code:401, err: shared.i18n.t('messageAuthEmailTaken')});
- if (invalidPassword(res.locals.user, req.body.password)) return cb(invalidPassword(res.locals.user, req.body.password));
- res.locals.user.auth.local.email = email;
- res.locals.user.save(cb);
- }
- ], function(err){
- if (err) return err.code ? res.status(err.code).json(err) : next(err);
- res.sendStatus(200);
- })
-}
-
-api.changePassword = function(req, res, next) {
- var user = res.locals.user,
- oldPassword = req.body.oldPassword,
- newPassword = req.body.newPassword,
- confirmNewPassword = req.body.confirmNewPassword;
-
- if (newPassword != confirmNewPassword)
- return res.status(401).json({err: "Password & Confirm don't match"});
-
- var salt = user.auth.local.salt,
- hashed_old_password = utils.encryptPassword(oldPassword, salt),
- hashed_new_password = utils.encryptPassword(newPassword, salt);
-
- if (hashed_old_password !== user.auth.local.hashed_password)
- return res.status(401).json({err:"Old password doesn't match"});
-
- user.auth.local.hashed_password = hashed_new_password;
- user.save(function(err, saved){
- if (err) next(err);
- res.sendStatus(200);
- })
-};
-
-var firebaseTokenGeneratorInstance = new FirebaseTokenGenerator(nconf.get('FIREBASE:SECRET'));
-api.getFirebaseToken = function(req, res, next) {
- var user = res.locals.user;
- // Expires 24 hours after now (60*60*24*1000) (in milliseconds)
- var expires = new Date();
- expires.setTime(expires.getTime() + 86400000);
-
- var token = firebaseTokenGeneratorInstance
- .createToken({
- uid: user._id,
- isHabiticaUser: true
- }, {
- expires: expires
- });
-
- res.status(200).json({
- token: token,
- expires: expires
- });
-};
-
-/*
- Registers a new user. Only accepting username/password registrations, no Facebook
-*/
-
-api.setupPassport = function(router) {
-
- router.get('/logout', i18n.getUserLanguage, function(req, res) {
- req.logout();
- delete req.session.userId;
- res.redirect('/');
- })
-
-};
diff --git a/website/src/controllers/api-v2/challenges.js b/website/src/controllers/api-v2/challenges.js
deleted file mode 100644
index 967f175213..0000000000
--- a/website/src/controllers/api-v2/challenges.js
+++ /dev/null
@@ -1,453 +0,0 @@
-// @see ../routes for routing
-
-var _ = require('lodash');
-var nconf = require('nconf');
-var async = require('async');
-var shared = require('../../../../common');
-var User = require('./../../models/user').model;
-var Group = require('./../../models/group').model;
-var Challenge = require('./../../models/challenge').model;
-var logging = require('./../../libs/logging');
-var csvStringify = require('csv-stringify');
-var utils = require('../../libs/utils');
-var api = module.exports;
-var pushNotify = require('./../pushNotifications');
-
-/*
- ------------------------------------------------------------------------
- Challenges
- ------------------------------------------------------------------------
-*/
-
-api.list = function(req, res, next) {
- var user = res.locals.user;
- async.waterfall([
- function(cb){
- // Get all available groups I belong to
- Group.find({members: {$in: [user._id]}}).select('_id').exec(cb);
- },
- function(gids, cb){
- // and their challenges
- Challenge.find({
- $or:[
- {leader: user._id},
- {members:{$in:[user._id]}}, // all challenges I belong to (is this necessary? thought is a left a group, but not its challenge)
- {group:{$in:gids}}, // all challenges in my groups
- {group: 'habitrpg'} // public group
- ],
- _id:{$ne:'95533e05-1ff9-4e46-970b-d77219f199e9'} // remove the Spread the Word Challenge for now, will revisit when we fix the closing-challenge bug
- })
- .select('name leader description group memberCount prize official')
- .select({members:{$elemMatch:{$in:[user._id]}}})
- .sort('-official -timestamp')
- .populate('group', '_id name type')
- .populate('leader', 'profile.name')
- .exec(cb);
- }
- ], function(err, challenges){
- if (err) return next(err);
- _.each(challenges, function(c){
- c._isMember = c.members.length > 0;
- })
- res.json(challenges);
- user = null;
- });
-}
-
-// GET
-api.get = function(req, res, next) {
- var user = res.locals.user;
- // TODO use mapReduce() or aggregate() here to
- // 1) Find the sum of users.tasks.values within the challnege (eg, {'profile.name':'tyler', 'sum': 100})
- // 2) Sort by the sum
- // 3) Limit 30 (only show the 30 users currently in the lead)
- Challenge.findById(req.params.cid)
- .populate('members', 'profile.name _id')
- .populate('group', '_id name type')
- .populate('leader', 'profile.name')
- .exec(function(err, challenge){
- if(err) return next(err);
- if (!challenge) return res.status(404).json({err: 'Challenge ' + req.params.cid + ' not found'});
- challenge._isMember = !!(_.find(challenge.members, function(member) {
- return member._id === user._id;
- }));
- res.json(challenge);
- });
-}
-
-api.csv = function(req, res, next) {
- var cid = req.params.cid;
- var challenge;
- async.waterfall([
- function(cb){
- Challenge.findById(cid,cb)
- },
- function(_challenge,cb) {
- challenge = _challenge;
- if (!challenge) return cb('Challenge ' + cid + ' not found');
- User.aggregate([
- {$match:{'_id':{ '$in': challenge.members}}}, //yes, we want members
- {$project:{'profile.name':1,tasks:{$setUnion:["$habits","$dailys","$todos","$rewards"]}}},
- {$unwind:"$tasks"},
- {$match:{"tasks.challenge.id":cid}},
- {$sort:{'tasks.type':1,'tasks.id':1}},
- {$group:{_id:"$_id", "tasks":{$push:"$tasks"},"name":{$first:"$profile.name"}}}
- ], cb);
- }
- ],function(err,users){
- if(err) return next(err);
- var output = ['UUID','name'];
- _.each(challenge.tasks,function(t){
- //output.push(t.type+':'+t.text);
- //not the right order yet
- output.push('Task');
- output.push('Value');
- output.push('Notes');
- })
- output = [output];
- _.each(users, function(u){
- var uData = [u._id,u.name];
- _.each(u.tasks,function(t){
- uData = uData.concat([t.type+':'+t.text, t.value, t.notes]);
- })
- output.push(uData);
- });
-
- res.set({
- 'Content-Type': 'text/csv',
- 'Content-disposition': `attachment; filename=${cid}.csv`,
- });
-
- csvStringify(output, (err, csv) => {
- if (err) return next(err);
- res.status(200).send(csv);
- challenge = cid = null;
- });
- })
-}
-
-api.getMember = function(req, res, next) {
- var cid = req.params.cid;
- var uid = req.params.uid;
-
- // We need to start using the aggregation framework instead of in-app filtering, see http://docs.mongodb.org/manual/aggregation/
- // See code at 32c0e75 for unwind/group example
-
- //http://stackoverflow.com/questions/24027213/how-to-match-multiple-array-elements-without-using-unwind
- var proj = {'profile.name':'$profile.name'};
- _.each(['habits','dailys','todos','rewards'], function(type){
- proj[type] = {
- $setDifference: [{
- $map: {
- input: '$'+type,
- as: "el",
- in: {
- $cond: [{$eq: ["$$el.challenge.id", cid]}, '$$el', false]
- }
- }
- }, [false]]
- }
- });
- User.aggregate()
- .match({_id: uid})
- .project(proj)
- .exec(function(err, member){
- if (err) return next(err);
- if (!member) return res.status(404).json({err: 'Member '+uid+' for challenge '+cid+' not found'});
- res.json(member[0]);
- uid = cid = null;
- });
-}
-
-// CREATE
-api.create = function(req, res, next){
- var user = res.locals.user;
-
- async.auto({
- get_group: function(cb){
- var q = {_id:req.body.group};
- if (req.body.group!='habitrpg') q.members = {$in:[user._id]}; // make sure they're a member of the group
- Group.findOne(q, cb);
- },
- save_chal: ['get_group', function(cb, results){
- var group = results.get_group,
- prize = +req.body.prize;
- if (!group)
- return cb({code:404, err:"Group." + req.body.group + " not found"});
- if (group.leaderOnly && group.leaderOnly.challenges && group.leader !== user._id)
- return cb({code:401, err: "Only the group leader can create challenges"});
- // If they're adding a prize, do some validation
- if (prize < 0)
- return cb({code:401, err: 'Challenge prize must be >= 0'});
- if (req.body.group=='habitrpg' && prize < 1)
- return cb({code:401, err: 'Prize must be at least 1 Gem for public challenges.'});
- if (prize > 0) {
- var groupBalance = ((group.balance && group.leader==user._id) ? group.balance : 0);
- var prizeCost = prize/4; // I really should have stored user.balance as gems rather than dollars... stupid...
- if (prizeCost > user.balance + groupBalance)
- return cb("You can't afford this prize. Purchase more gems or lower the prize amount.")
-
- if (groupBalance >= prizeCost) {
- // Group pays for all of prize
- group.balance -= prizeCost;
- } else if (groupBalance > 0) {
- // User pays remainder of prize cost after group
- var remainder = prizeCost - group.balance;
- group.balance = 0;
- user.balance -= remainder;
- } else {
- // User pays for all of prize
- user.balance -= prizeCost;
- }
- }
- req.body.leader = user._id;
- req.body.official = user.contributor.admin && req.body.official;
- var chal = new Challenge(req.body); // FIXME sanitize
- chal.members.push(user._id);
- chal.save(cb);
- }],
- save_group: ['save_chal', function(cb, results){
- results.get_group.challenges.push(results.save_chal[0]._id);
- results.get_group.save(cb);
- }],
- sync_user: ['save_group', function(cb, results){
- // Auto-join creator to challenge (see members.push above)
- results.save_chal[0].syncToUser(user, cb);
- }]
- }, function(err, results){
- if (err) return err.code? res.status(err.code).json(err) : next(err);
- return res.json(results.save_chal[0]);
- user = null;
- })
-}
-
-// UPDATE
-api.update = function(req, res, next){
- var cid = req.params.cid;
- var user = res.locals.user;
- var before;
- async.waterfall([
- function(cb){
- // We first need the original challenge data, since we're going to compare against new & decide to sync users
- Challenge.findById(cid, cb);
- },
- function(_before, cb) {
- if (!_before) return cb('Challenge ' + cid + ' not found');
- if (_before.leader != user._id && !user.contributor.admin) return cb(shared.i18n.t('noPermissionEditChallenge', req.language));
- // Update the challenge, since syncing will need the updated challenge. But store `before` we're going to do some
- // before-save / after-save comparison to determine if we need to sync to users
- before = _before;
- var attrs = _.pick(req.body, 'name shortName description habits dailys todos rewards date'.split(' '));
- Challenge.findByIdAndUpdate(cid, {$set:attrs}, {new: true}, cb);
- },
- function(saved, cb) {
-
- // Compare whether any changes have been made to tasks. If so, we'll want to sync those changes to subscribers
- if (before.isOutdated(req.body)) {
- User.find({_id: {$in: saved.members}}, function(err, users){
- logging.info('Challenge updated, sync to subscribers');
- if (err) throw err;
- _.each(users, function(user){
- saved.syncToUser(user);
- })
- })
- }
-
- // after saving, we're done as far as the client's concerned. We kick off syncing (heavy task) in the background
- cb(null, saved);
- }
- ], function(err, saved){
- if(err) next(err);
- res.json(saved);
- cid = user = before = null;
- })
-}
-
-/**
- * Called by either delete() or selectWinner(). Will delete the challenge and set the "broken" property on all users' subscribed tasks
- * @param {cid} the challenge id
- * @param {broken} the object representing the broken status of the challenge. Eg:
- * {broken: 'CHALLENGE_DELETED', id: CHALLENGE_ID}
- * {broken: 'CHALLENGE_CLOSED', id: CHALLENGE_ID, winner: USER_NAME}
- */
-function closeChal(cid, broken, cb) {
- var removed;
- async.waterfall([
- function(cb2){
- Challenge.findOneAndRemove({_id:cid}, cb2)
- },
- function(_removed, cb2) {
- removed = _removed;
- var pull = {'$pull':{}}; pull['$pull'][_removed._id] = 1;
- Group.findByIdAndUpdate(_removed.group, {new: true}, pull);
- User.find({_id:{$in: removed.members}}, cb2);
- },
- function(users, cb2) {
- var parallel = [];
- _.each(users, function(user){
- var tag = _.find(user.tags, {id:cid});
- if (tag) tag.challenge = undefined;
- _.each(user.tasks, function(task){
- if (task.challenge && task.challenge.id == removed._id) {
- _.merge(task.challenge, broken);
- }
- })
- parallel.push(function(cb3){
- user.save(cb3);
- })
- })
- async.parallel(parallel, cb2);
- removed = null;
- }
- ], cb);
-}
-
-/**
- * Delete & close
- */
-api.delete = function(req, res, next){
- var user = res.locals.user;
- var cid = req.params.cid;
-
- async.waterfall([
- function(cb){
- Challenge.findById(cid, cb);
- },
- function(chal, cb){
- if (!chal) return cb('Challenge ' + cid + ' not found');
- if (chal.leader != user._id && !user.contributor.admin) return cb(shared.i18n.t('noPermissionDeleteChallenge', req.language));
- if (chal.group != 'habitrpg') user.balance += chal.prize/4; // Refund gems to user if a non-tavern challenge
- user.save(cb);
- },
- function(save, num, cb){
- closeChal(req.params.cid, {broken: 'CHALLENGE_DELETED'}, cb);
- }
- ], function(err){
- if (err) return next(err);
- res.sendStatus(200);
- user = cid = null;
- });
-}
-
-/**
- * Select Winner & Close
- */
-api.selectWinner = function(req, res, next) {
- if (!req.query.uid) return res.status(401).json({err: 'Must select a winner'});
- var user = res.locals.user;
- var cid = req.params.cid;
- var chal;
- async.waterfall([
- function(cb){
- Challenge.findById(cid, cb);
- },
- function(_chal, cb){
- chal = _chal;
- if (!chal) return cb('Challenge ' + cid + ' not found');
- if (chal.leader != user._id && !user.contributor.admin) return cb(shared.i18n.t('noPermissionCloseChallenge', req.language));
- User.findById(req.query.uid, cb)
- },
- function(winner, cb){
- if (!winner) return cb('Winner ' + req.query.uid + ' not found.');
- _.defaults(winner.achievements, {challenges: []});
- winner.achievements.challenges.push(chal.name);
- winner.balance += chal.prize/4;
- winner.save(cb);
- },
- function(saved, num, cb) {
- if(saved.preferences.emailNotifications.wonChallenge !== false){
- utils.txnEmail(saved, 'won-challenge', [
- {name: 'CHALLENGE_NAME', content: chal.name}
- ]);
- }
-
- pushNotify.sendNotify(saved, shared.i18n.t('wonChallenge'), chal.name);
-
- closeChal(cid, {broken: 'CHALLENGE_CLOSED', winner: saved.profile.name}, cb);
- }
- ], function(err){
- if (err) return next(err);
- res.sendStatus(200);
- user = cid = chal = null;
- })
-}
-
-api.join = function(req, res, next){
- var user = res.locals.user;
- var cid = req.params.cid;
-
- async.waterfall([
- function(cb) {
- Challenge.findByIdAndUpdate(cid, {$addToSet:{members:user._id}}, {new: true}, cb);
- },
- function(chal, cb) {
-
- // Trigger updating challenge member count in the background. We can't do it above because we don't have
- // _.size(challenge.members). We can't do it in pre(save) because we're calling findByIdAndUpdate above.
- Challenge.update({_id:cid}, {$set:{memberCount:_.size(chal.members)}}).exec();
-
- if (!~user.challenges.indexOf(cid))
- user.challenges.unshift(cid);
- // Add all challenge's tasks to user's tasks
- chal.syncToUser(user, function(err){
- if (err) return cb(err);
- cb(null, chal); // we want the saved challenge in the return results, due to ng-resource
- });
- }
- ], function(err, chal){
- if(err) return next(err);
- chal._isMember = true;
- res.json(chal);
- user = cid = null;
- });
-}
-
-
-api.leave = function(req, res, next){
- var user = res.locals.user;
- var cid = req.params.cid;
- // whether or not to keep challenge's tasks. strictly default to true if "keep-all" isn't provided
- var keep = (/^remove-all/i).test(req.query.keep) ? 'remove-all' : 'keep-all';
-
- async.waterfall([
- function(cb){
- Challenge.findByIdAndUpdate(cid, {$pull:{members:user._id}}, {new: true}, cb);
- },
- function(chal, cb){
-
- // Trigger updating challenge member count in the background. We can't do it above because we don't have
- // _.size(challenge.members). We can't do it in pre(save) because we're calling findByIdAndUpdate above.
- if (chal)
- Challenge.update({_id:cid}, {$set:{memberCount:_.size(chal.members)}}).exec();
-
- var i = user.challenges.indexOf(cid)
- if (~i) user.challenges.splice(i,1);
- user.unlink({cid:cid, keep:keep}, function(err){
- if (err) return cb(err);
- cb(null, chal);
- })
- }
- ], function(err, chal){
- if(err) return next(err);
- if (chal) chal._isMember = false;
- res.json(chal);
- user = cid = keep = null;
- });
-}
-
-api.unlink = function(req, res, next) {
- // they're scoring the task - commented out, we probably don't need it due to route ordering in api.js
- //var urlParts = req.originalUrl.split('/');
- //if (_.contains(['up','down'], urlParts[urlParts.length -1])) return next();
-
- var user = res.locals.user;
- var tid = req.params.id;
- var cid = user.tasks[tid].challenge.id;
- if (!req.query.keep)
- return res.status(400).json({err: 'Provide unlink method as ?keep=keep-all (keep, keep-all, remove, remove-all)'});
- user.unlink({cid:cid, keep:req.query.keep, tid:tid}, function(err, saved){
- if (err) return next(err);
- res.sendStatus(200);
- user = tid = cid = null;
- });
-}
diff --git a/website/src/controllers/api-v2/coupon.js b/website/src/controllers/api-v2/coupon.js
deleted file mode 100644
index a69c41e326..0000000000
--- a/website/src/controllers/api-v2/coupon.js
+++ /dev/null
@@ -1,45 +0,0 @@
-var _ = require('lodash');
-var Coupon = require('./../../models/coupon').model;
-var api = module.exports;
-var csvStringify = require('csv-stringify');
-var async = require('async');
-
-api.ensureAdmin = function(req, res, next) {
- if (!res.locals.user.contributor.sudo) return res.status(401).json({err:"You don't have admin access"});
- next();
-}
-
-api.generateCoupons = function(req,res,next) {
- let count = Number(req.query.count);
- Coupon.generate(req.params.event, count, function(err){
- if(err) return next(err);
- res.sendStatus(200);
- });
-}
-
-api.getCoupons = function(req,res,next) {
- var options = {sort:'seq'};
- if (req.query.limit) options.limit = req.query.limit;
- if (req.query.skip) options.skip = req.query.skip;
- Coupon.find({},{}, options, function(err,coupons){
- let output = [['code']].concat(_.map(coupons, function(c){
- return [c._id];
- }))
-
- res.set({
- 'Content-Type': 'text/csv',
- 'Content-disposition': `attachment; filename=habitica-coupons.csv`,
- });
- csvStringify(output, (err, csv) => {
- if (err) return next(err);
- res.status(200).send(csv);
- });
- });
-}
-
-api.enterCode = function(req,res,next) {
- Coupon.apply(res.locals.user,req.params.code,function(err,user){
- if (err) return res.status(400).json({err:err});
- res.json(user);
- });
-}
diff --git a/website/src/controllers/api-v2/groups.js b/website/src/controllers/api-v2/groups.js
deleted file mode 100644
index e117aa40ad..0000000000
--- a/website/src/controllers/api-v2/groups.js
+++ /dev/null
@@ -1,1106 +0,0 @@
-'use strict';
-// @see ../routes for routing
-
-function clone(a) {
- return JSON.parse(JSON.stringify(a));
-}
-
-var _ = require('lodash');
-var nconf = require('nconf');
-var async = require('async');
-var Q = require('q');
-var utils = require('./../../libs/utils');
-var shared = require('../../../../common');
-var User = require('./../../models/user').model;
-var Group = require('./../../models/group').model;
-var Challenge = require('./../../models/challenge').model;
-var EmailUnsubscription = require('./../../models/emailUnsubscription').model;
-var isProd = nconf.get('NODE_ENV') === 'production';
-var api = module.exports;
-var pushNotify = require('./../pushNotifications');
-var analytics = utils.analytics;
-var firebase = require('../../libs/firebase');
-
-/*
- ------------------------------------------------------------------------
- Groups
- ------------------------------------------------------------------------
-*/
-
-var partyFields = api.partyFields = 'profile preferences stats achievements party backer contributor auth.timestamps items';
-var nameFields = 'profile.name';
-var challengeFields = '_id name';
-var guildPopulate = {path: 'members', select: nameFields, options: {limit: 15} };
-const FLAG_REPORT_EMAILS = nconf.get('FLAG_REPORT_EMAIL').split(',').map(email => {return {email, canSend: true}});
-
-/**
- * For parties, we want a lot of member details so we can show their avatars in the header. For guilds, we want very
- * limited fields - and only a sampling of the members, beacuse they can be in the thousands
- * @param type: 'party' or otherwise
- * @param q: the Mongoose query we're building up
- * @param additionalFields: if we want to populate some additional field not fetched normally
- * pass it as a string, parties only
- */
-var populateQuery = function(type, q, additionalFields){
- if (type == 'party')
- q.populate('members', partyFields + (additionalFields ? (' ' + additionalFields) : ''));
- else
- q.populate(guildPopulate);
- q.populate('leader', nameFields);
- q.populate('invites', nameFields);
- q.populate({
- path: 'challenges',
- match: (type=='habitrpg') ? {_id:{$ne:'95533e05-1ff9-4e46-970b-d77219f199e9'}} : undefined, // remove the Spread the Word Challenge for now, will revisit when we fix the closing-challenge bug
- select: challengeFields,
- options: {sort: {official: -1, timestamp: -1}}
- });
- return q;
-}
-
-/**
- * Fetch groups list. This no longer returns party or tavern, as those can be requested indivdually
- * as /groups/party or /groups/tavern
- */
-api.list = function(req, res, next) {
- var user = res.locals.user;
- var groupFields = 'name description memberCount balance leader';
- var sort = '-memberCount';
- var type = req.query.type || 'party,guilds,public,tavern';
-
- async.parallel({
-
- // unecessary given our ui-router setup
- party: function(cb){
- if (!~type.indexOf('party')) return cb(null, {});
- Group.findOne({type: 'party', members: {'$in': [user._id]}})
- .select(groupFields).exec(function(err, party){
- if (err) return cb(err);
- cb(null, (party === null ? [] : [party])); // return as an array for consistent ngResource use
- });
- },
-
- guilds: function(cb) {
- if (!~type.indexOf('guilds')) return cb(null, []);
- Group.find({members: {'$in': [user._id]}, type:'guild'})
- .select(groupFields).sort(sort).exec(cb);
- },
-
- 'public': function(cb) {
- if (!~type.indexOf('public')) return cb(null, []);
- Group.find({privacy: 'public'})
- .select(groupFields + ' members')
- .sort(sort)
- .lean()
- .exec(function(err, groups){
- if (err) return cb(err);
- _.each(groups, function(g){
- // To save some client-side performance, don't send down the full members arr, just send down temp var _isMember
- if (~g.members.indexOf(user._id)) g._isMember = true;
- g.members = undefined;
- });
- cb(null, groups);
- });
- },
-
- // unecessary given our ui-router setup
- tavern: function(cb) {
- if (!~type.indexOf('tavern')) return cb(null, {});
- Group.findById('habitrpg').select(groupFields).exec(function(err, tavern){
- if (err) return cb(err);
- cb(null, [tavern]); // return as an array for consistent ngResource use
- });
- }
-
- }, function(err, results){
- if (err) return next(err);
- // ngResource expects everything as arrays. We used to send it down as a structured object: {public:[], party:{}, guilds:[], tavern:{}}
- // but unfortunately ngResource top-level attrs are considered the ngModels in the list, so we had to do weird stuff and multiple
- // requests to get it to work properly. Instead, we're not depending on the client to do filtering / organization, and we're
- // just sending down a merged array. Revisit
- var arr = _.reduce(results, function(m,v){
- if (_.isEmpty(v)) return m;
- return m.concat(_.isArray(v) ? v : [v]);
- }, [])
- res.json(arr);
-
- user = groupFields = sort = type = null;
- })
-};
-
-/**
- * Get group
- * TODO: implement requesting fields ?fields=chat,members
- */
-api.get = function(req, res, next) {
- var user = res.locals.user;
- var gid = req.params.gid;
-
- var q = (gid == 'party')
- ? Group.findOne({type: 'party', members: {'$in': [user._id]}})
- : Group.findOne({$or:[
- {_id:gid, privacy:'public'},
- {_id:gid, privacy:'private', members: {$in:[user._id]}} // if the group is private, only return if they have access
- ]});
- populateQuery(gid, q);
- q.exec(function(err, group){
- if (err) return next(err);
- if(!group){
- if(gid !== 'party') return res.status(404).json({err: shared.i18n.t('messageGroupNotFound')});
-
- // Don't send a 404 when querying for a party even if it doesn't exist
- // so that users with no party don't get a 404 on every access to the site
- return res.json(group);
- }
-
- if (!user.contributor.admin) {
- _purgeFlagInfoFromChat(group, user);
- }
-
- //Since we have a limit on how many members are populate to the group, we want to make sure the user is always in the group
- var userInGroup = _.find(group.members, function(member){ return member._id == user._id; });
- //If the group is private or the group is a party, then the user must be a member of the group based on access restrictions above
- if (group.privacy === 'private' || gid === 'party') {
- //If the user is not in the group query, remove a user and add the current user
- if (!userInGroup) {
- group.members.splice(0,1);
- group.members.push(user);
- }
- res.json(group);
- } else if ( group.privacy === "public" ) { //The group is public, we must do an extra check to see if the user is already in the group query
- //We must see how to check if a user is a member of a public group, so we requery
- var q2 = Group.findOne({ _id: group._id, privacy:'public', members: {$in:[user._id]} });
- q2.exec(function(err, group2){
- if (err) return next(err);
- if (group2 && !userInGroup) {
- group.members.splice(0,1);
- group.members.push(user);
- }
- res.json(group);
- });
- }
-
- gid = null;
- });
-};
-
-
-api.create = function(req, res, next) {
- var group = new Group(req.body);
- var user = res.locals.user;
- group.members = [user._id];
- group.leader = user._id;
-
- if(group.type === 'guild'){
- if(user.balance < 1) return res.status(401).json({err: shared.i18n.t('messageInsufficientGems')});
-
- group.balance = 1;
- user.balance--;
-
- async.waterfall([
- function(cb){user.save(cb)},
- function(saved,ct,cb){group.save(cb)},
- function(saved,ct,cb){
- firebase.updateGroupData(saved);
- firebase.addUserToGroup(saved._id, user._id);
- saved.populate('members', nameFields, cb);
- }
- ],function(err,saved){
- if (err) return next(err);
- res.json(saved);
- group = user = null;
- });
-
- } else{
- async.waterfall([
- function(cb){
- Group.findOne({type:'party',members:{$in:[user._id]}},cb);
- },
- function(found, cb){
- if (found) return cb(shared.i18n.t('messageGroupAlreadyInParty'));
- group.save(cb);
- },
- function(saved, count, cb){
- firebase.updateGroupData(saved);
- firebase.addUserToGroup(saved._id, user._id);
- saved.populate('members', nameFields, cb);
- }
- ], function(err, populated){
- if (err === shared.i18n.t('messageGroupAlreadyInParty')) return res.status(400).json({err:err});
- if (err) return next(err);
- group = user = null;
- return res.json(populated);
- })
- }
-}
-
-api.update = function(req, res, next) {
- var group = res.locals.group;
- var user = res.locals.user;
-
- if(group.leader !== user._id)
- return res.status(401).json({err: shared.i18n.t('messageGroupOnlyLeaderCanUpdate')});
-
- 'name description logo logo leaderMessage leader leaderOnly'.split(' ').forEach(function(attr){
- group[attr] = req.body[attr];
- });
-
- group.save(function(err, saved){
- if (err) return next(err);
-
- firebase.updateGroupData(saved);
- res.sendStatus(204);
- });
-}
-
-// TODO remove from api object?
-api.attachGroup = function(req, res, next) {
- var user = res.locals.user;
- var gid = req.params.gid;
- var q = (gid == 'party') ? Group.findOne({type: 'party', members: {'$in': [res.locals.user._id]}}) : Group.findById(gid);
- q.exec(function(err, group){
- if(err) return next(err);
- if(!group) return res.status(404).json({err: shared.i18n.t('messageGroupNotFound')});
-
- if (!user.contributor.admin) {
- _purgeFlagInfoFromChat(group, user);
- }
-
- res.locals.group = group;
- next();
- });
-}
-
-api.getChat = function(req, res, next) {
- // TODO: This code is duplicated from api.get - pull it out into a function to remove duplication.
- var user = res.locals.user;
- var gid = req.params.gid;
- var q = (gid == 'party')
- ? Group.findOne({type: 'party', members: {$in:[user._id]}})
- : Group.findOne({$or:[
- {_id:gid, privacy:'public'},
- {_id:gid, privacy:'private', members: {$in:[user._id]}}
- ]});
- populateQuery(gid, q);
- q.exec(function(err, group){
- if (err) return next(err);
- if (!group && gid!=='party') return res.status(404).json({err: shared.i18n.t('messageGroupNotFound')});
-
- res.json(res.locals.group.chat);
- gid = null;
- });
-};
-
-/**
- * TODO make this it's own ngResource so we don't have to send down group data with each chat post
- */
-api.postChat = function(req, res, next) {
- if(!req.query.message) {
- return res.status(400).json({err: shared.i18n.t('messageGroupChatBlankMessage')});
- } else {
- var user = res.locals.user
- var group = res.locals.group;
- if (group.type!='party' && user.flags.chatRevoked) return res.status(401).json({err:'Your chat privileges have been revoked.'});
- var lastClientMsg = req.query.previousMsg;
- var chatUpdated = (lastClientMsg && group.chat && group.chat[0] && group.chat[0].id !== lastClientMsg) ? true : false;
-
- group.sendChat(req.query.message, user); // FIXME this should be body, but ngResource is funky
-
- if (group.type === 'party') {
- user.party.lastMessageSeen = group.chat[0].id;
- user.save();
- }
-
- group.save(function(err, saved){
- if (err) return next(err);
- chatUpdated ? res.json({chat: group.chat}) : res.json({message: saved.chat[0]});
- group = chatUpdated = null;
- });
- }
-}
-
-api.deleteChatMessage = function(req, res, next){
- var user = res.locals.user
- var group = res.locals.group;
- var message = _.find(group.chat, {id: req.params.messageId});
-
- if(!message) return res.status(404).json({err: "Message not found!"});
-
- if(user._id !== message.uuid && !(user.backer && user.contributor.admin))
- return res.status(401).json({err: "Not authorized to delete this message!"})
-
- var lastClientMsg = req.query.previousMsg;
- var chatUpdated = (lastClientMsg && group.chat && group.chat[0] && group.chat[0].id !== lastClientMsg) ? true : false;
-
- Group.update({_id:group._id}, {$pull:{chat:{id: req.params.messageId}}}, function(err){
- if(err) return next(err);
- chatUpdated ? res.json({chat: group.chat}) : res.sendStatus(204);
- group = chatUpdated = null;
- });
-}
-
-api.flagChatMessage = function(req, res, next){
- var user = res.locals.user
- var group = res.locals.group;
- var message = _.find(group.chat, {id: req.params.mid});
-
- if(!message) return res.status(404).json({err: shared.i18n.t('messageGroupChatNotFound')});
- if(message.uuid == user._id) return res.status(401).json({err: shared.i18n.t('messageGroupChatFlagOwnMessage')});
-
- User.findOne({_id: message.uuid}, {auth: 1}, function(err, author){
- if(err) return next(err);
-
- // Log user ids that have flagged the message
- if(!message.flags) message.flags = {};
- if(message.flags[user._id] && !user.contributor.admin) return res.status(401).json({err: shared.i18n.t('messageGroupChatFlagAlreadyReported')});
- message.flags[user._id] = true;
-
- // Log total number of flags (publicly viewable)
- if(!message.flagCount) message.flagCount = 0;
- if(user.contributor.admin){
- // Arbitraty amount, higher than 2
- message.flagCount = 5;
- } else {
- message.flagCount++
- }
-
- Group.update({_id: group._id, 'chat.id': message.id}, {'$set': {
- 'chat.$.flags': message.flags,
- 'chat.$.flagCount': message.flagCount,
- }}, function(err) {
- if (err) return next(err);
-
- utils.txnEmail(FLAG_REPORT_EMAILS, 'flag-report-to-mods', [
- {name: "MESSAGE_TIME", content: (new Date(message.timestamp)).toString()},
- {name: "MESSAGE_TEXT", content: message.text},
-
- {name: "REPORTER_USERNAME", content: user.profile.name},
- {name: "REPORTER_UUID", content: user._id},
- {name: "REPORTER_EMAIL", content: user.auth.local ? user.auth.local.email : ((user.auth.facebook && user.auth.facebook.emails && user.auth.facebook.emails[0]) ? user.auth.facebook.emails[0].value : null)},
- {name: "REPORTER_MODAL_URL", content: "/static/front/#?memberId=" + user._id},
-
- {name: "AUTHOR_USERNAME", content: message.user},
- {name: "AUTHOR_UUID", content: message.uuid},
- {name: "AUTHOR_EMAIL", content: author.auth.local ? author.auth.local.email : ((author.auth.facebook && author.auth.facebook.emails && author.auth.facebook.emails[0]) ? author.auth.facebook.emails[0].value : null)},
- {name: "AUTHOR_MODAL_URL", content: "/static/front/#?memberId=" + message.uuid},
-
- {name: "GROUP_NAME", content: group.name},
- {name: "GROUP_TYPE", content: group.type},
- {name: "GROUP_ID", content: group._id},
- {name: "GROUP_URL", content: group._id == 'habitrpg' ? '/#/options/groups/tavern' : (group.type === 'guild' ? ('/#/options/groups/guilds/' + group._id) : 'party')},
- ]);
-
- return res.sendStatus(204);
- });
- });
-}
-
-api.clearFlagCount = function(req, res, next){
- var user = res.locals.user
- var group = res.locals.group;
- var message = _.find(group.chat, {id: req.params.mid});
-
- if(!message) return res.status(404).json({err: shared.i18n.t('messageGroupChatNotFound')});
-
- if(user.contributor.admin){
- message.flagCount = 0;
-
- Group.update({_id: group._id, 'chat.id': message.id}, {'$set': {
- 'chat.$.flagCount': message.flagCount,
- }}, function(err) {
- if(err) return next(err);
- return res.sendStatus(204);
- });
- } else {
- return res.status(401).json({err: shared.i18n.t('messageGroupChatAdminClearFlagCount')})
- }
-}
-
-api.seenMessage = function(req,res,next){
- // Skip the auth step, we want this to be fast. If !found with uuid/token, then it just doesn't save
- // Check for req.params.gid to exist
- if(req.params.gid){
- var update = {$unset:{}};
- update['$unset']['newMessages.'+req.params.gid] = '';
- User.update({_id:req.headers['x-api-user'], apiToken:req.headers['x-api-key']},update).exec();
- }
- res.sendStatus(200);
-}
-
-api.likeChatMessage = function(req, res, next) {
- var user = res.locals.user;
- var group = res.locals.group;
- var message = _.find(group.chat, {id: req.params.mid});
-
- if (!message) return res.status(404).json({err: shared.i18n.t('messageGroupChatNotFound')});
- if (message.uuid == user._id) return res.status(401).json({err: shared.i18n.t('messageGroupChatLikeOwnMessage')});
- if (!message.likes) message.likes = {};
- if (message.likes[user._id]) {
- delete message.likes[user._id];
- } else {
- message.likes[user._id] = true;
- }
-
- Group.update({_id: group._id, 'chat.id': message.id}, {'$set': {
- 'chat.$.likes': message.likes
- }}, function(err) {
- if (err) return next(err);
- return res.send(group.chat);
- });
-}
-
-api.join = function(req, res, next) {
- var user = res.locals.user,
- group = res.locals.group,
- isUserInvited = false;
-
- if (group.type == 'party' && group._id == (user.invitations && user.invitations.party && user.invitations.party.id)) {
- User.update({_id:user.invitations.party.inviter}, {$inc:{'items.quests.basilist':1}}).exec(); // Reward inviter
- user.invitations.party = undefined; // Clear invite
- user.save();
- // invite new user to pending quest
- if (group.quest.key && !group.quest.active) {
- User.update({_id:user._id},{$set: {'party.quest.RSVPNeeded': true, 'party.quest.key': group.quest.key}}).exec();
- group.quest.members[user._id] = undefined;
- group.markModified('quest.members');
- }
- isUserInvited = true;
- } else if (group.type == 'guild' && user.invitations && user.invitations.guilds) {
- var i = _.findIndex(user.invitations.guilds, {id:group._id});
- if (~i){
- isUserInvited = true;
- user.invitations.guilds.splice(i,1);
- user.save();
- }else{
- isUserInvited = group.privacy === 'private' ? false : true;
- }
- }
-
- if(!isUserInvited) return res.status(401).json({err: shared.i18n.t('messageGroupRequiresInvite')});
-
- if (!_.contains(group.members, user._id)){
- if (group.members.length === 0) {
- group.leader = user._id;
- }
-
- group.members.push(user._id);
-
- if (group.invites.length > 0) {
- group.invites.splice(_.indexOf(group.invites, user._id), 1);
- }
- }
-
- async.series([
- function(cb){
- group.save(cb);
- },
- function(cb){
- firebase.addUserToGroup(group._id, user._id);
- // TODO why query group once again?
- populateQuery(group.type, Group.findById(group._id)).exec(cb);
- }
- ], function(err, results){
- if (err) return next(err);
- // Return the group? Or not?
- res.json(results[1]);
- group = null;
- });
-}
-
-api.leave = function(req, res, next) {
- var user = res.locals.user;
- var group = res.locals.group;
-
- if (group.type === 'party') {
- if (group.quest && group.quest.leader === user._id) {
- return res.json(403, 'You cannot leave your party when you have started a quest. Abort the quest first.');
- }
-
- if (group.quest && group.quest.active && group.quest.members && group.quest.members[user._id]) {
- return res.json(403, 'You cannot leave party during an active quest. Please leave the quest first.');
- }
- }
-
- // When removing the user from challenges, should we keep the tasks?
- var keep = (/^remove-all/i).test(req.query.keep) ? 'remove-all' : 'keep-all';
-
- group.leave(user, keep, function(err){
- if (err) return next(err);
- user = group = keep = null;
-
- return res.sendStatus(204);
- });
-};
-
-var inviteByUUIDs = function(uuids, group, req, res, next){
- async.each(uuids, function(uuid, cb){
- User.findById(uuid, function(err,invite){
- if (err) return cb(err);
- if (!invite)
- return cb({code:400,err:'User with id "' + uuid + '" not found'});
- if (group.type == 'guild') {
- if (_.contains(group.members,uuid))
- return cb({code:400, err: "User already in that group"});
- if (invite.invitations && invite.invitations.guilds && _.find(invite.invitations.guilds, {id:group._id}))
- return cb({code:400, err:"User already invited to that group"});
- sendInvite();
- } else if (group.type == 'party') {
- if (invite.invitations && !_.isEmpty(invite.invitations.party))
- return cb({code: 400,err:"User already pending invitation."});
- Group.find({type: 'party', members: {$in: [uuid]}}, function(err, groups){
- if (err) return cb(err);
- if (!_.isEmpty(groups) && groups[0].members.length > 1) {
- return cb({code: 400, err: "User already in a party."})
- }
- sendInvite();
- });
- }
-
- function sendInvite (){
- if(group.type === 'guild'){
- invite.invitations.guilds.push({id: group._id, name: group.name, inviter:res.locals.user._id});
-
- pushNotify.sendNotify(invite, shared.i18n.t('invitedGuild'), group.name);
- }else{
- //req.body.type in 'guild', 'party'
- invite.invitations.party = {id: group._id, name: group.name, inviter:res.locals.user._id};
-
- pushNotify.sendNotify(invite, shared.i18n.t('invitedParty'), group.name);
- }
-
- group.invites.push(invite._id);
-
- async.series([
- function(cb){
- invite.save(cb);
- }
- ], function(err, results){
- if (err) return cb(err);
-
- if(invite.preferences.emailNotifications['invited' + (group.type == 'guild' ? 'Guild' : 'Party')] !== false){
- var inviterVars = utils.getUserInfo(res.locals.user, ['name', 'email']);
- var emailVars = [
- {name: 'INVITER', content: inviterVars.name}
- ];
-
- if(group.type == 'guild'){
- emailVars.push(
- {name: 'GUILD_NAME', content: group.name},
- {name: 'GUILD_URL', content: '/#/options/groups/guilds/public'}
- );
- }else{
- emailVars.push(
- {name: 'PARTY_NAME', content: group.name},
- {name: 'PARTY_URL', content: '/#/options/groups/party'}
- )
- }
-
- utils.txnEmail(invite, ('invited-' + (group.type == 'guild' ? 'guild' : 'party')), emailVars);
- }
-
- cb();
- });
- }
- });
- }, function(err){
- if(err) return err.code ? res.status(err.code).json({err: err.err}) : next(err);
-
- async.series([
- function(cb) {
- group.save(cb);
- },
- function(cb) {
- // TODO pass group from save above don't find it again, or you have to find it again in order to run populate?
- populateQuery(group.type, Group.findById(group._id)).exec(function(err, populatedGroup){
- if(err) return next(err);
-
- res.json(populatedGroup);
- });
- }
- ]);
- });
-};
-
-var inviteByEmails = function(invites, group, req, res, next){
- var usersAlreadyRegistered = [];
-
- async.each(invites, function(invite, cb){
- if (invite.email) {
- User.findOne({$or: [
- {'auth.local.email': invite.email},
- {'auth.facebook.emails.value': invite.email}
- ]}).select({_id: true, 'preferences.emailNotifications': true})
- .exec(function(err, userToContact){
- if(err) return next(err);
-
- if(userToContact){
- usersAlreadyRegistered.push(userToContact._id);
- return cb();
- }
-
- // yeah, it supports guild too but for backward compatibility we'll use partyInvite as query
-
- var link = '?partyInvite='+ utils.encrypt(JSON.stringify({id:group._id, inviter:res.locals.user._id, name:group.name}));
-
- var inviterVars = utils.getUserInfo(res.locals.user, ['name', 'email']);
- var variables = [
- {name: 'LINK', content: link},
- {name: 'INVITER', content: req.body.inviter || inviterVars.name}
- ];
-
- if(group.type == 'guild'){
- variables.push({name: 'GUILD_NAME', content: group.name});
- }
-
- // TODO implement "users can only be invited once"
- // Check for the email address not to be unsubscribed
- EmailUnsubscription.findOne({email: invite.email}, function(err, unsubscribed){
- if(err) return cb(err);
- if(unsubscribed) return cb();
-
- utils.txnEmail(invite, ('invite-friend' + (group.type == 'guild' ? '-guild' : '')), variables);
-
- cb();
- });
- });
- }else{
- cb();
- }
- }, function(err){
- if(err) return err.code ? res.status(err.code).json({err: err.err}) : next(err);
-
- if (usersAlreadyRegistered.length > 0){
- inviteByUUIDs(usersAlreadyRegistered, group, req, res, next);
- } else{
-
- // Send only status code down the line because it doesn't need
- // info on invited users since they are not yet registered
- res.status(200).json({});
- }
- });
-};
-
-api.invite = function(req, res, next){
- var group = res.locals.group;
-
- if (group.privacy === 'private' && !_.contains(group.members,res.locals.user._id)) {
- return res.status(401).json({err: "Only a member can invite new members!"});
- }
- if (req.body.uuids) {
- inviteByUUIDs(req.body.uuids, group, req, res, next);
- } else if (req.body.emails) {
- inviteByEmails(req.body.emails, group, req, res, next)
- } else {
- return res.status(400).json({err: "Can only invite by email or uuid"});
- }
-}
-
-api.removeMember = function(req, res, next){
- var group = res.locals.group;
- var uuid = req.query.uuid;
- var message = req.query.message;
- var user = res.locals.user;
-
- // Send an email to the removed user with an optional message from the leader
- var sendMessage = function(removedUser){
- if(removedUser.preferences.emailNotifications.kickedGroup !== false){
- utils.txnEmail(removedUser, ('kicked-from-' + group.type), [
- {name: 'GROUP_NAME', content: group.name},
- {name: 'MESSAGE', content: message},
- {name: 'GUILDS_LINK', content: '/#/options/groups/guilds/public'},
- {name: 'PARTY_WANTED_GUILD', content: '/#/options/groups/guilds/f2db2a7f-13c5-454d-b3ee-ea1f5089e601'}
- ]);
- }
- }
-
- if(group.leader !== user._id){
- return res.status(401).json({err: "Only group leader can remove a member!"});
- }
-
- if(user._id === uuid){
- return res.status(401).json({err: "You cannot remove yourself!"});
- }
-
- if(_.contains(group.members, uuid)){
- var update = {$pull:{members:uuid}};
- if (group.quest && group.quest.leader === uuid) {
- update['$set'] = {
- quest: { key: null, leader: null }
- };
- } else if(group.quest && group.quest.members){
- // remove member from quest
- update['$unset'] = {};
- update['$unset']['quest.members.' + uuid] = "";
- }
- update['$inc'] = {memberCount: -1};
- Group.update({_id:group._id},update, function(err, saved){
- if (err) return next(err);
-
- User.findById(uuid, function(err, removedUser){
- if(err) return next(err);
-
- sendMessage(removedUser);
-
- //Mark removed users messages as seen
- var update = {$unset:{}};
- update.$unset['newMessages.' + group._id] = '';
- if (group.quest && group.quest.active && group.quest.leader === uuid) {
- update['$inc'] = {};
- update['$inc']['items.quests.' + group.quest.key] = 1;
- }
- User.update({_id: removedUser._id, apiToken: removedUser.apiToken}, update).exec();
-
- // Sending an empty 204 because Group.update doesn't return the group
- // see http://mongoosejs.com/docs/api.html#model_Model.update
- group = uuid = null;
- return res.sendStatus(204);
- });
- });
- }else if(_.contains(group.invites, uuid)){
- User.findById(uuid, function(err,invited){
- if(err) return next(err);
-
- var invitations = invited.invitations;
- if(group.type === 'guild'){
- invitations.guilds.splice(_.indexOf(invitations.guilds, group._id), 1);
- }else{
- invitations.party = undefined;
- }
-
- async.series([
- function(cb){
- invited.save(cb);
- },
- function(cb){
- Group.update({_id:group._id},{$pull:{invites:uuid}}, cb);
- }
- ], function(err, results){
- if (err) return next(err);
-
- // Sending an empty 204 because Group.update doesn't return the group
- // see http://mongoosejs.com/docs/api.html#model_Model.update
- sendMessage(invited);
- group = uuid = null;
- return res.sendStatus(204);
- });
-
- });
- }else{
- group = uuid = null;
- return res.status(400).json({err: "User not found among group's members!"});
- }
-}
-
-// ------------------------------------
-// Quests
-// ------------------------------------
-
-function questStart(req, res, next) {
- var group = res.locals.group;
- var force = req.query.force;
-
- // if (group.quest.active) return res.status(400).json({err:'Quest already began.'});
- // temporarily send error email, until we know more about this issue (then remove below, uncomment above).
- if (group.quest.active) return next('Quest already began.');
-
- group.markModified('quest');
-
- // Not ready yet, wait till everyone's accepted, rejected, or we force-start
- var statuses = _.values(group.quest.members);
- if (!force && (~statuses.indexOf(undefined) || ~statuses.indexOf(null))) {
- return group.save(function(err,saved){
- if (err) return next(err);
- res.json(saved);
- })
- }
-
- var parallel = [],
- questMembers = {},
- key = group.quest.key,
- quest = shared.content.quests[key],
- collected = quest.collect ? _.transform(quest.collect, function(m,v,k){m[k]=0}) : {};
-
- _.each(group.members, function(m){
- var updates = {$set:{},$inc:{'_v':1}};
- if (m == group.quest.leader)
- updates['$inc']['items.quests.'+key] = -1;
- if (group.quest.members[m] == true) {
- // See https://github.com/HabitRPG/habitrpg/issues/2168#issuecomment-31556322 , we need to *not* reset party.quest.progress.up
- //updates['$set']['party.quest'] = Group.cleanQuestProgress({key:key,progress:{collect:collected}});
- updates['$set']['party.quest.key'] = key;
- updates['$set']['party.quest.progress.down'] = 0;
- updates['$set']['party.quest.progress.collect'] = collected;
- updates['$set']['party.quest.completed'] = null;
- questMembers[m] = true;
-
- User.findOne({_id: m}, {pushDevices: 1}, function(err, user){
- pushNotify.sendNotify(user, "HabitRPG", shared.i18n.t('questStarted') + ": "+ quest.text() );
- });
- } else {
- updates['$set']['party.quest'] = Group.cleanQuestProgress();
- }
-
- parallel.push(function(cb2){
- User.update({_id:m},updates,cb2);
- });
- });
-
- group.quest.active = true;
- if (quest.boss) {
- group.quest.progress.hp = quest.boss.hp;
- if (quest.boss.rage) group.quest.progress.rage = 0;
- } else {
- group.quest.progress.collect = collected;
- }
- group.quest.members = questMembers;
- group.markModified('quest'); // members & progress.collect are both Mixed types
- parallel.push(function(cb2){group.save(cb2)});
-
- parallel.push(function(cb){
- // Fetch user.auth to send email, then remove it from data sent to the client
- populateQuery(group.type, Group.findById(group._id), 'auth.facebook auth.local').exec(cb);
- });
-
- async.parallel(parallel,function(err, results){
- if (err) return next(err);
-
- var lastIndex = results.length -1;
- var groupClone = clone(group);
-
- groupClone.members = results[lastIndex].members;
-
- // Send quest started email
- var usersToEmail = groupClone.members.filter(function(user){
- return (
- user.preferences.emailNotifications.questStarted !== false &&
- user._id !== res.locals.user._id &&
- group.quest.members[user._id] == true
- )
- });
-
- utils.txnEmail(usersToEmail, 'quest-started', [
- {name: 'PARTY_URL', content: '/#/options/groups/party'}
- ]);
-
- _.each(groupClone.members, function(user){
- // Remove sensitive data from what is sent to the public
- // but after having sent emails as they are needed
- user.auth.facebook = undefined;
- user.auth.local = undefined;
- });
-
- group = null;
-
- return res.json(groupClone);
- });
-}
-
-api.questAccept = function(req, res, next) {
- var group = res.locals.group;
- var user = res.locals.user;
- var key = req.query.key;
-
- if (!group || group.type !== 'party') return res.status(400).json({err: "Must be in a party to start quests."});
-
- // If ?key=xxx is provided, we're starting a new quest and inviting the party. Otherwise, we're a party member accepting the invitation
- if (key) {
- var quest = shared.content.quests[key];
- if (!quest) return res.status(404).json({err:'Quest ' + key + ' not found'});
- if (quest.lvl && user.stats.lvl < quest.lvl) return res.status(400).json({err: "You must be level "+quest.lvl+" to begin this quest."});
- if (group.quest.key) return res.status(400).json({err: 'Your party is already on a quest. Try again when the current quest has ended.'});
- if (!user.items.quests[key]) return res.status(400).json({err: "You don't own that quest scroll"});
- group.quest.key = key;
- group.quest.members = {};
- // Invite everyone. true means "accepted", false="rejected", undefined="pending". Once we click "start quest"
- // or everyone has either accepted/rejected, then we store quest key in user object.
- _.each(group.members, function(m){
- if (m == user._id) {
- var analyticsData = {
- category: 'behavior',
- owner: true,
- response: 'accept',
- gaLabel: 'accept',
- questName: key,
- uuid: user._id,
- };
- analytics.track('quest',analyticsData);
- group.quest.members[m] = true;
- group.quest.leader = user._id;
- } else {
- User.update({_id:m},{$set: {'party.quest.RSVPNeeded': true, 'party.quest.key': group.quest.key}}).exec();
- group.quest.members[m] = undefined;
- }
- });
-
- User.find({
- _id: {
- $in: _.without(group.members, user._id)
- }
- }, {auth: 1, preferences: 1, profile: 1, pushDevices: 1}, function(err, members){
- if(err) return next(err);
-
- var inviterVars = utils.getUserInfo(user, ['name', 'email']);
-
- var membersToEmail = members.filter(function(member){
- return member.preferences.emailNotifications.invitedQuest !== false;
- });
-
- utils.txnEmail(membersToEmail, ('invite-' + (quest.boss ? 'boss' : 'collection') + '-quest'), [
- {name: 'QUEST_NAME', content: quest.text()},
- {name: 'INVITER', content: inviterVars.name},
- {name: 'PARTY_URL', content: '/#/options/groups/party'}
- ]);
-
- _.each(members, function(groupMember){
- pushNotify.sendNotify(groupMember, shared.i18n.t('questInvitationTitle'), shared.i18n.t('questInvitationInfo', { quest: quest.text() }));
- });
-
- questStart(req,res,next);
- });
-
- // Party member accepting the invitation
- } else {
- if (!group.quest.key) return res.status(400).json({err:'No quest invitation has been sent out yet.'});
- var analyticsData = {
- category: 'behavior',
- owner: false,
- response: 'accept',
- gaLabel: 'accept',
- questName: group.quest.key,
- uuid: user._id,
- };
- analytics.track('quest',analyticsData);
- group.quest.members[user._id] = true;
- User.update({_id:user._id}, {$set: {'party.quest.RSVPNeeded': false}}).exec();
- questStart(req,res,next);
- }
-}
-
-api.questReject = function(req, res, next) {
- var group = res.locals.group;
- var user = res.locals.user;
-
- if (!group.quest.key) return res.status(400).json({err:'No quest invitation has been sent out yet.'});
- var analyticsData = {
- category: 'behavior',
- owner: false,
- response: 'reject',
- gaLabel: 'reject',
- questName: group.quest.key,
- uuid: user._id,
- };
- analytics.track('quest',analyticsData);
- group.quest.members[user._id] = false;
- User.update({_id:user._id}, {$set: {'party.quest.RSVPNeeded': false, 'party.quest.key': null}}).exec();
- questStart(req,res,next);
-}
-
-api.questCancel = function(req, res, next){
- // Cancel a quest BEFORE it has begun (i.e., in the invitation stage)
- // Quest scroll has not yet left quest owner's inventory so no need to return it.
- // Do not wipe quest progress for members because they'll want it to be applied to the next quest that's started.
- var group = res.locals.group;
- async.parallel([
- function(cb){
- if (! group.quest.active) {
- // Do not cancel active quests because this function does
- // not do the clean-up required for that.
- // TODO: return an informative error when quest is active
- group.quest = {key:null,progress:{},leader:null};
- group.markModified('quest');
- group.save(cb);
- _.each(group.members, function(m){
- User.update({_id:m}, {$set: {'party.quest.RSVPNeeded': false, 'party.quest.key': null}}).exec();
- });
- }
- }
- ], function(err){
- if (err) return next(err);
- res.json(group);
- group = null;
- })
-}
-
-api.questAbort = function(req, res, next){
- // Abort a quest AFTER it has begun (see questCancel for BEFORE)
- var group = res.locals.group;
- async.parallel([
- function(cb){
- User.update(
- {_id:{$in: _.keys(group.quest.members)}},
- {
- $set: {'party.quest':Group.cleanQuestProgress()},
- $inc: {_v:1}
- },
- {multi:true},
- cb);
- },
- // Refund party leader quest scroll
- function(cb){
- if (group.quest.active) {
- var update = {$inc:{}};
- update['$inc']['items.quests.' + group.quest.key] = 1;
- User.update({_id:group.quest.leader}, update).exec();
- }
- group.quest = {key:null,progress:{},leader:null};
- group.markModified('quest');
- group.save(cb);
- }, function(cb){
- populateQuery(group.type, Group.findById(group._id)).exec(cb);
- }
- ], function(err, results){
- if (err) return next(err);
-
- var groupClone = clone(group);
-
- groupClone.members = results[2].members;
-
- res.json(groupClone);
- group = null;
- })
-}
-
-api.questLeave = function(req, res, next) {
- // Non-member leave quest while still in progress
- var group = res.locals.group;
- var user = res.locals.user;
-
- if (!(group.quest && group.quest.active)) {
- return res.status(404).json({ err: 'No active quest to leave' });
- }
-
- if (!(group.quest.members && group.quest.members[user._id])) {
- return res.status(403).json({ err: 'You are not part of the quest' });
- }
-
- if (group.quest.leader === user._id) {
- return res.status(403).json({ err: 'Quest leader cannot leave quest' });
- }
-
- delete group.quest.members[user._id];
- group.markModified('quest.members');
-
- user.party.quest = Group.cleanQuestProgress();
- user.markModified('party.quest');
-
- var groupSavePromise = Q.nbind(group.save, group);
- var userSavePromise = Q.nbind(user.save, user);
-
- Q.all([groupSavePromise(), userSavePromise()])
- .done(function(values) {
- return res.sendStatus(204);
- }, function(error) {
- return next(error);
- });
-}
-
-function _purgeFlagInfoFromChat(group, user) {
- group.chat = _.filter(group.chat, function(message) { return !message.flagCount || message.flagCount < 2; });
- _.each(group.chat, function (message) {
- if (message.flags) {
- var userHasFlagged = message.flags[user._id];
- message.flags = {};
-
- if (userHasFlagged) message.flags[user._id] = userHasFlagged;
- }
- });
-}
diff --git a/website/src/controllers/api-v2/hall.js b/website/src/controllers/api-v2/hall.js
deleted file mode 100644
index ec88894c62..0000000000
--- a/website/src/controllers/api-v2/hall.js
+++ /dev/null
@@ -1,85 +0,0 @@
-var _ = require('lodash');
-var nconf = require('nconf');
-var async = require('async');
-var shared = require('../../../../common');
-var User = require('./../../models/user').model;
-var Group = require('./../../models/group').model;
-var api = module.exports;
-
-api.ensureAdmin = function(req, res, next) {
- var user = res.locals.user;
- if (!(user.contributor && user.contributor.admin)) return res.status(401).json({err:"You don't have admin access"});
- next();
-}
-
-api.getHeroes = function(req,res,next) {
- User.find({'contributor.level':{$gt:0}})
- .select('contributor backer balance profile.name')
- .sort('-contributor.level')
- .exec(function(err, users){
- if (err) return next(err);
- res.json(users);
- });
-}
-
-api.getPatrons = function(req,res,next){
- var page = req.query.page || 0,
- perPage = 50;
- User.find({'backer.tier':{$gt:0}})
- .select('contributor backer profile.name')
- .sort('-backer.tier')
- .skip(page*perPage)
- .limit(perPage)
- .exec(function(err, users){
- if (err) return next(err);
- res.json(users);
- });
-}
-
-api.getHero = function(req,res,next) {
- User.findById(req.params.uid)
- .select('contributor balance profile.name purchased items')
- .select('auth.local.username auth.local.email auth.facebook auth.blocked')
- .exec(function(err, user){
- if (err) return next(err)
- if (!user) return res.status(400).json({err:'User not found'});
- res.json(user);
- });
-}
-
-api.updateHero = function(req,res,next) {
- async.waterfall([
- function(cb){
- User.findById(req.params.uid, cb);
- },
- function(member, cb){
- if (!member) return res.status(404).json({err: "User not found"});
- member.balance = req.body.balance || 0;
- var newTier = req.body.contributor.level; // tier = level in this context
- var oldTier = member.contributor && member.contributor.level || 0;
- if (newTier > oldTier) {
- member.flags.contributor = true;
- var gemsPerTier = {1:3, 2:3, 3:3, 4:4, 5:4, 6:4, 7:4, 8:0, 9:0}; // e.g., tier 5 gives 4 gems. Tier 8 = moderator. Tier 9 = staff
- var tierDiff = newTier - oldTier; // can be 2+ tier increases at once
- while (tierDiff) {
- member.balance += gemsPerTier[newTier] / 4; // balance is in $
- tierDiff--;
- newTier--; // give them gems for the next tier down if they weren't aready that tier
- }
- }
- member.contributor = req.body.contributor;
- member.purchased.ads = req.body.purchased.ads;
- if (member.contributor.level >= 6) member.items.pets['Dragon-Hydra'] = 5;
- if (req.body.itemPath && req.body.itemVal
- && req.body.itemPath.indexOf('items.') === 0
- && User.schema.paths[req.body.itemPath]) {
- shared.dotSet(member, req.body.itemPath, req.body.itemVal); // Sanitization at 5c30944 (deemed unnecessary)
- }
- if (_.isBoolean(req.body.auth.blocked)) member.auth.blocked = req.body.auth.blocked;
- member.save(cb);
- }
- ], function(err, saved){
- if (err) return next(err);
- res.status(204).json({});
- })
-}
diff --git a/website/src/controllers/api-v2/members.js b/website/src/controllers/api-v2/members.js
deleted file mode 100644
index 01c136c472..0000000000
--- a/website/src/controllers/api-v2/members.js
+++ /dev/null
@@ -1,134 +0,0 @@
-var User = require('mongoose').model('User');
-var groups = require('../../models/group');
-var partyFields = require('./groups').partyFields
-var api = module.exports;
-var async = require('async');
-var _ = require('lodash');
-var shared = require('../../../../common');
-var utils = require('../../libs/utils');
-var nconf = require('nconf');
-var pushNotify = require('./../pushNotifications');
-
-var fetchMember = function(uuid, restrict){
- return function(cb){
- var q = User.findById(uuid);
- if (restrict) q.select(partyFields);
- q.exec(function(err, member){
- if (err) return cb(err);
- if (!member) return cb({code:404, err: 'User not found'});
- return cb(null, member);
- })
- }
-}
-
-var sendErr = function(err, res, next){
- err.code ? res.status(err.code).json({err: err.err}) : next(err);
-}
-
-api.getMember = function(req, res, next) {
- fetchMember(req.params.uuid, true)(function(err, member){
- if (err) return sendErr(err, res, next);
- res.json(member);
- })
-}
-
-api.sendMessage = function(user, member, data){
- var msg;
- if (!data.type) {
- msg = data.message
- } else {
- msg = "`Hello " + member.profile.name + ", " + user.profile.name + " has sent you ";
- if (data.type == 'gems') {
- var gemAmount = data.gems.amount;
- var gemLabel = gemAmount > 1 ? "gems" : "gem";
- msg += gemAmount + " " + gemLabel + "!`";
- } else {
- var monthAmount = shared.content.subscriptionBlocks[data.subscription.key].months;
- var monthLabel = monthAmount > 1 ? "months" : "month";
- msg += monthAmount + " " + monthLabel + " of subscription!`";
- }
- msg += data.message ? data.message : '';
- }
- shared.refPush(member.inbox.messages, groups.chatDefaults(msg, user));
- member.inbox.newMessages++;
- member._v++;
- member.markModified('inbox.messages');
-
- shared.refPush(user.inbox.messages, _.defaults({sent:true}, groups.chatDefaults(msg, member)));
- user.markModified('inbox.messages');
-}
-
-api.sendPrivateMessage = function(req, res, next){
- var fetchedMember;
- async.waterfall([
- fetchMember(req.params.uuid),
- function(member, cb) {
- fetchedMember = member;
- if (~member.inbox.blocks.indexOf(res.locals.user._id) // can't send message if that user blocked me
- || ~res.locals.user.inbox.blocks.indexOf(member._id) // or if I blocked them
- || member.inbox.optOut) { // or if they've opted out of messaging
- return cb({code: 401, err: "Can't send message to this user."});
- }
- api.sendMessage(res.locals.user, member, {message:req.body.message});
- async.parallel([
- function (cb2) { member.save(cb2) },
- function (cb2) { res.locals.user.save(cb2) }
- ], cb);
- }
- ], function(err){
- if (err) return sendErr(err, res, next);
-
- if(fetchedMember.preferences.emailNotifications.newPM !== false){
- utils.txnEmail(fetchedMember, 'new-pm', [
- {name: 'SENDER', content: utils.getUserInfo(res.locals.user, ['name']).name},
- {name: 'PMS_INBOX_URL', content: '/#/options/groups/inbox'}
- ]);
- }
-
- res.sendStatus(200);
- })
-}
-
-api.sendGift = function(req, res, next){
- async.waterfall([
- fetchMember(req.params.uuid),
- function(member, cb) {
- // Gems
- switch (req.body.type) {
- case "gems":
- var amt = req.body.gems.amount / 4,
- user = res.locals.user;
- if (member.id == user.id)
- return cb({code: 401, err: "Cannot send gems to yourself. Try a subscription instead."});
- if (!amt || amt <=0 || user.balance < amt)
- return cb({code: 401, err: "Amount must be within 0 and your current number of gems."});
- member.balance += amt;
- user.balance -= amt;
- api.sendMessage(user, member, req.body);
-
- var byUsername = utils.getUserInfo(user, ['name']).name;
-
- if(member.preferences.emailNotifications.giftedGems !== false){
- utils.txnEmail(member, 'gifted-gems', [
- {name: 'GIFTER', content: byUsername},
- {name: 'X_GEMS_GIFTED', content: req.body.gems.amount}
- ]);
- }
-
- pushNotify.sendNotify(member, shared.i18n.t('giftedGems'), shared.i18n.t('giftedGemsInfo', { amount: req.body.gems.amount, name: byUsername }));
-
- return async.parallel([
- function (cb2) { member.save(cb2) },
- function (cb2) { user.save(cb2) }
- ], cb);
- case "subscription":
- return cb();
- default:
- return cb({code:400, err:"Body must contain a gems:{amount,fromBalance} or subscription:{months} object"});
- }
- }
- ], function(err) {
- if (err) return sendErr(err, res, next);
- res.sendStatus(200);
- });
-}
diff --git a/website/src/controllers/api-v2/unsubscription.js b/website/src/controllers/api-v2/unsubscription.js
deleted file mode 100644
index 2fecbef03f..0000000000
--- a/website/src/controllers/api-v2/unsubscription.js
+++ /dev/null
@@ -1,36 +0,0 @@
-var User = require('../../models/user').model;
-var EmailUnsubscription = require('../../models/emailUnsubscription').model;
-var utils = require('../../libs/utils');
-var i18n = require('../../../../common').i18n;
-
-var api = module.exports = {};
-
-api.unsubscribe = function(req, res, next){
- if(!req.query.code) return res.status(500).json({err: 'Missing unsubscription code.'});
-
- var data = JSON.parse(utils.decrypt(req.query.code));
-
- if(data._id){
- User.update({_id: data._id}, {
- $set: {'preferences.emailNotifications.unsubscribeFromAll': true}
- }, {multi: false}, function(err, updateRes){
- if(err) return next(err);
- if(updateRes !== 1) return res.status(404).json({err: 'User not found'});
-
- res.send('