diff --git a/habitica-images b/habitica-images index 351ca72bc4..7203f4633c 160000 --- a/habitica-images +++ b/habitica-images @@ -1 +1 @@ -Subproject commit 351ca72bc4cecce515aa94a1951e4b7803f5d3f3 +Subproject commit 7203f4633cc90ede5b2f6745393d54493c487ff6 diff --git a/migrations/archive/2023/20230123_habit_birthday.js b/migrations/archive/2023/20230123_habit_birthday.js new file mode 100644 index 0000000000..618664e0b8 --- /dev/null +++ b/migrations/archive/2023/20230123_habit_birthday.js @@ -0,0 +1,88 @@ +/* eslint-disable no-console */ +import { v4 as uuid } from 'uuid'; +import { model as User } from '../../../website/server/models/user'; + +const MIGRATION_NAME = '20230123_habit_birthday'; +const progressCount = 1000; +let count = 0; + +async function updateUser (user) { + count += 1; + + const inc = { 'balance': 5 }; + const set = {}; + const push = {}; + + set.migration = MIGRATION_NAME; + + if (typeof user.items.gear.owned.armor_special_birthday2022 !== 'undefined') { + set['items.gear.owned.armor_special_birthday2023'] = true; + } else if (typeof user.items.gear.owned.armor_special_birthday2021 !== 'undefined') { + set['items.gear.owned.armor_special_birthday2022'] = true; + } else if (typeof user.items.gear.owned.armor_special_birthday2020 !== 'undefined') { + set['items.gear.owned.armor_special_birthday2021'] = true; + } else if (typeof user.items.gear.owned.armor_special_birthday2019 !== 'undefined') { + set['items.gear.owned.armor_special_birthday2020'] = true; + } else if (typeof user.items.gear.owned.armor_special_birthday2018 !== 'undefined') { + set['items.gear.owned.armor_special_birthday2019'] = true; + } else if (typeof user.items.gear.owned.armor_special_birthday2017 !== 'undefined') { + set['items.gear.owned.armor_special_birthday2018'] = true; + } else if (typeof user.items.gear.owned.armor_special_birthday2016 !== 'undefined') { + set['items.gear.owned.armor_special_birthday2017'] = true; + } else if (typeof user.items.gear.owned.armor_special_birthday2015 !== 'undefined') { + set['items.gear.owned.armor_special_birthday2016'] = true; + } else if (typeof user.items.gear.owned.armor_special_birthday !== 'undefined') { + set['items.gear.owned.armor_special_birthday2015'] = true; + } else { + set['items.gear.owned.armor_special_birthday'] = true; + } + + push.notifications = { + type: 'ITEM_RECEIVED', + data: { + icon: 'notif_head_special_nye', + title: 'Birthday Bash Day 1!', + text: 'Enjoy your new Birthday Robe and 20 Gems on us!', + destination: 'equipment', + }, + seen: false, + }; + + if (count % progressCount === 0) console.warn(`${count} ${user._id}`); + + return await User.update({_id: user._id}, {$inc: inc, $set: set, $push: push}).exec(); +} + +export default async function processUsers () { + let query = { + migration: {$ne: MIGRATION_NAME}, + 'auth.timestamps.loggedin': {$gt: new Date('2022-12-23')}, + }; + + const fields = { + _id: 1, + items: 1, + }; + + while (true) { // eslint-disable-line no-constant-condition + const users = await User // eslint-disable-line no-await-in-loop + .find(query) + .limit(250) + .sort({_id: 1}) + .select(fields) + .lean() + .exec(); + + if (users.length === 0) { + console.warn('All appropriate users found and modified.'); + console.warn(`\n${count} users processed\n`); + break; + } else { + query._id = { + $gt: users[users.length - 1], + }; + } + + await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop + } +}; diff --git a/migrations/archive/2023/20230127_habit_birthday_day5.js b/migrations/archive/2023/20230127_habit_birthday_day5.js new file mode 100644 index 0000000000..a6ad5f49fd --- /dev/null +++ b/migrations/archive/2023/20230127_habit_birthday_day5.js @@ -0,0 +1,69 @@ +/* eslint-disable no-console */ +import { v4 as uuid } from 'uuid'; +import { model as User } from '../../../website/server/models/user'; + +const MIGRATION_NAME = '20230127_habit_birthday_day5'; +const progressCount = 1000; +let count = 0; + +async function updateUser (user) { + count += 1; + + const set = {}; + const push = {}; + + set.migration = MIGRATION_NAME; + + set['items.gear.owned.back_special_anniversary'] = true; + set['items.gear.owned.body_special_anniversary'] = true; + set['items.gear.owned.eyewear_special_anniversary'] = true; + + push.notifications = { + type: 'ITEM_RECEIVED', + data: { + icon: 'notif_head_special_nye', + title: 'Birthday Bash Day 5!', + text: 'Come celebrate by wearing your new Habitica Hero Cape, Collar, and Mask!', + destination: 'equipment', + }, + seen: false, + }; + + if (count % progressCount === 0) console.warn(`${count} ${user._id}`); + + return await User.update({_id: user._id}, {$set: set, $push: push}).exec(); +} + +export default async function processUsers () { + let query = { + migration: {$ne: MIGRATION_NAME}, + 'auth.timestamps.loggedin': {$gt: new Date('2022-12-23')}, + }; + + const fields = { + _id: 1, + items: 1, + }; + + while (true) { // eslint-disable-line no-constant-condition + const users = await User // eslint-disable-line no-await-in-loop + .find(query) + .limit(250) + .sort({_id: 1}) + .select(fields) + .lean() + .exec(); + + if (users.length === 0) { + console.warn('All appropriate users found and modified.'); + console.warn(`\n${count} users processed\n`); + break; + } else { + query._id = { + $gt: users[users.length - 1], + }; + } + + await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop + } +}; diff --git a/migrations/archive/2023/20230201_habit_birthday_day10.js b/migrations/archive/2023/20230201_habit_birthday_day10.js new file mode 100644 index 0000000000..9743c6b91f --- /dev/null +++ b/migrations/archive/2023/20230201_habit_birthday_day10.js @@ -0,0 +1,79 @@ +/* eslint-disable no-console */ +import { v4 as uuid } from 'uuid'; +import { model as User } from '../../../website/server/models/user'; + +const MIGRATION_NAME = '20230201_habit_birthday_day10'; +const progressCount = 1000; +let count = 0; + +async function updateUser (user) { + count += 1; + + const set = { + migration: MIGRATION_NAME, + 'purchased.background.birthday_bash': true, + }; + const push = { + notifications: { + type: 'ITEM_RECEIVED', + data: { + icon: 'notif_head_special_nye', + title: 'Birthday Bash Day 10!', + text: 'Join in for the end of our birthday celebrations with 10th Birthday background, Cake, and achievement!', + destination: 'backgrounds', + }, + seen: false, + }, + }; + const inc = { + 'items.food.Cake_Skeleton': 1, + 'items.food.Cake_Base': 1, + 'items.food.Cake_CottonCandyBlue': 1, + 'items.food.Cake_CottonCandyPink': 1, + 'items.food.Cake_Shade': 1, + 'items.food.Cake_White': 1, + 'items.food.Cake_Golden': 1, + 'items.food.Cake_Zombie': 1, + 'items.food.Cake_Desert': 1, + 'items.food.Cake_Red': 1, + 'achievements.habitBirthdays': 1, + }; + + if (count % progressCount === 0) console.warn(`${count} ${user._id}`); + + return await User.update({_id: user._id}, {$set: set, $push: push, $inc: inc }).exec(); +} + +export default async function processUsers () { + let query = { + migration: {$ne: MIGRATION_NAME}, + 'auth.timestamps.loggedin': {$gt: new Date('2022-12-23')}, + }; + + const fields = { + _id: 1, + items: 1, + }; + + while (true) { // eslint-disable-line no-constant-condition + const users = await User // eslint-disable-line no-await-in-loop + .find(query) + .limit(250) + .sort({_id: 1}) + .select(fields) + .lean() + .exec(); + + if (users.length === 0) { + console.warn('All appropriate users found and modified.'); + console.warn(`\n${count} users processed\n`); + break; + } else { + query._id = { + $gt: users[users.length - 1], + }; + } + + await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop + } +}; diff --git a/package-lock.json b/package-lock.json index a097f97ade..1f1a270bfa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "habitica", - "version": "4.255.2", + "version": "4.258.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 0d119e0022..1c7d06a247 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "habitica", "description": "A habit tracker app which treats your goals like a Role Playing Game.", - "version": "4.255.2", + "version": "4.258.0", "main": "./website/server/index.js", "dependencies": { "@babel/core": "^7.20.12", diff --git a/test/api/unit/libs/payments/amazon/checkout.test.js b/test/api/unit/libs/payments/amazon/checkout.test.js index 02402b14a9..12013e26a1 100644 --- a/test/api/unit/libs/payments/amazon/checkout.test.js +++ b/test/api/unit/libs/payments/amazon/checkout.test.js @@ -17,7 +17,7 @@ describe('Amazon Payments - Checkout', () => { let closeOrderReferenceSpy; let paymentBuyGemsStub; - let paymentCreateSubscritionStub; + let paymentCreateSubscriptionStub; let amount = gemsBlock.price / 100; function expectOrderReferenceSpy () { @@ -85,8 +85,8 @@ describe('Amazon Payments - Checkout', () => { paymentBuyGemsStub = sinon.stub(payments, 'buyGems'); paymentBuyGemsStub.resolves({}); - paymentCreateSubscritionStub = sinon.stub(payments, 'createSubscription'); - paymentCreateSubscritionStub.resolves({}); + paymentCreateSubscriptionStub = sinon.stub(payments, 'createSubscription'); + paymentCreateSubscriptionStub.resolves({}); sinon.stub(common, 'uuid').returns('uuid-generated'); sandbox.stub(gems, 'validateGiftMessage'); @@ -109,6 +109,7 @@ describe('Amazon Payments - Checkout', () => { user, paymentMethod, headers, + sku: undefined, }; if (gift) { expectedArgs.gift = gift; @@ -215,13 +216,14 @@ describe('Amazon Payments - Checkout', () => { }); gift.member = receivingUser; - expect(paymentCreateSubscritionStub).to.be.calledOnce; - expect(paymentCreateSubscritionStub).to.be.calledWith({ + expect(paymentCreateSubscriptionStub).to.be.calledOnce; + expect(paymentCreateSubscriptionStub).to.be.calledWith({ user, paymentMethod: amzLib.constants.PAYMENT_METHOD_GIFT, headers, gift, gemsBlock: undefined, + sku: undefined, }); expectAmazonStubs(); }); diff --git a/test/api/unit/libs/payments/apple.test.js b/test/api/unit/libs/payments/apple.test.js index 1aefe2cb51..217a307d0b 100644 --- a/test/api/unit/libs/payments/apple.test.js +++ b/test/api/unit/libs/payments/apple.test.js @@ -12,10 +12,10 @@ const { i18n } = common; describe('Apple Payments', () => { const subKey = 'basic_3mo'; - describe('verifyGemPurchase', () => { + describe('verifyPurchase', () => { let sku; let user; let token; let receipt; let headers; - let iapSetupStub; let iapValidateStub; let iapIsValidatedStub; let paymentBuyGemsStub; let + let iapSetupStub; let iapValidateStub; let iapIsValidatedStub; let paymentBuySkuStub; let iapGetPurchaseDataStub; let validateGiftMessageStub; beforeEach(() => { @@ -36,7 +36,7 @@ describe('Apple Payments', () => { productId: 'com.habitrpg.ios.Habitica.21gems', transactionId: token, }]); - paymentBuyGemsStub = sinon.stub(payments, 'buyGems').resolves({}); + paymentBuySkuStub = sinon.stub(payments, 'buySkuItem').resolves({}); validateGiftMessageStub = sinon.stub(gems, 'validateGiftMessage'); }); @@ -45,7 +45,7 @@ describe('Apple Payments', () => { iap.validate.restore(); iap.isValidated.restore(); iap.getPurchaseData.restore(); - payments.buyGems.restore(); + payments.buySkuItem.restore(); gems.validateGiftMessage.restore(); }); @@ -54,7 +54,7 @@ describe('Apple Payments', () => { iapIsValidatedStub = sinon.stub(iap, 'isValidated') .returns(false); - await expect(applePayments.verifyGemPurchase({ user, receipt, headers })) + await expect(applePayments.verifyPurchase({ user, receipt, headers })) .to.eventually.be.rejected.and.to.eql({ httpCode: 401, name: 'NotAuthorized', @@ -66,7 +66,7 @@ describe('Apple Payments', () => { iapGetPurchaseDataStub.restore(); iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData').returns([]); - await expect(applePayments.verifyGemPurchase({ user, receipt, headers })) + await expect(applePayments.verifyPurchase({ user, receipt, headers })) .to.eventually.be.rejected.and.to.eql({ httpCode: 401, name: 'NotAuthorized', @@ -76,7 +76,7 @@ describe('Apple Payments', () => { it('errors if the user cannot purchase gems', async () => { sinon.stub(user, 'canGetGems').resolves(false); - await expect(applePayments.verifyGemPurchase({ user, receipt, headers })) + await expect(applePayments.verifyPurchase({ user, receipt, headers })) .to.eventually.be.rejected.and.to.eql({ httpCode: 401, name: 'NotAuthorized', @@ -94,14 +94,16 @@ describe('Apple Payments', () => { productId: 'badProduct', transactionId: token, }]); + paymentBuySkuStub.restore(); - await expect(applePayments.verifyGemPurchase({ user, receipt, headers })) + await expect(applePayments.verifyPurchase({ user, receipt, headers })) .to.eventually.be.rejected.and.to.eql({ - httpCode: 401, - name: 'NotAuthorized', + httpCode: 400, + name: 'BadRequest', message: applePayments.constants.RESPONSE_INVALID_ITEM, }); + paymentBuySkuStub = sinon.stub(payments, 'buySkuItem').resolves({}); user.canGetGems.restore(); }); @@ -138,7 +140,7 @@ describe('Apple Payments', () => { }]); sinon.stub(user, 'canGetGems').resolves(true); - await applePayments.verifyGemPurchase({ user, receipt, headers }); + await applePayments.verifyPurchase({ user, receipt, headers }); expect(iapSetupStub).to.be.calledOnce; expect(iapValidateStub).to.be.calledOnce; @@ -148,13 +150,13 @@ describe('Apple Payments', () => { expect(iapGetPurchaseDataStub).to.be.calledOnce; expect(validateGiftMessageStub).to.not.be.called; - expect(paymentBuyGemsStub).to.be.calledOnce; - expect(paymentBuyGemsStub).to.be.calledWith({ + expect(paymentBuySkuStub).to.be.calledOnce; + expect(paymentBuySkuStub).to.be.calledWith({ user, - paymentMethod: applePayments.constants.PAYMENT_METHOD_APPLE, - gemsBlock: common.content.gems[gemTest.gemsBlock], - headers, gift: undefined, + paymentMethod: applePayments.constants.PAYMENT_METHOD_APPLE, + sku: gemTest.productId, + headers, }); expect(user.canGetGems).to.be.calledOnce; user.canGetGems.restore(); @@ -173,7 +175,7 @@ describe('Apple Payments', () => { }]); const gift = { uuid: receivingUser._id }; - await applePayments.verifyGemPurchase({ + await applePayments.verifyPurchase({ user, gift, receipt, headers, }); @@ -187,18 +189,16 @@ describe('Apple Payments', () => { expect(validateGiftMessageStub).to.be.calledOnce; expect(validateGiftMessageStub).to.be.calledWith(gift, user); - expect(paymentBuyGemsStub).to.be.calledOnce; - expect(paymentBuyGemsStub).to.be.calledWith({ + expect(paymentBuySkuStub).to.be.calledOnce; + expect(paymentBuySkuStub).to.be.calledWith({ user, - paymentMethod: applePayments.constants.PAYMENT_METHOD_APPLE, - headers, gift: { - type: 'gems', - gems: { amount: 4 }, - member: sinon.match({ _id: receivingUser._id }), uuid: receivingUser._id, + member: sinon.match({ _id: receivingUser._id }), }, - gemsBlock: common.content.gems['4gems'], + paymentMethod: applePayments.constants.PAYMENT_METHOD_APPLE, + sku: 'com.habitrpg.ios.Habitica.4gems', + headers, }); }); }); diff --git a/test/api/unit/libs/payments/google.test.js b/test/api/unit/libs/payments/google.test.js index 7df7f273ef..eb9b3574ba 100644 --- a/test/api/unit/libs/payments/google.test.js +++ b/test/api/unit/libs/payments/google.test.js @@ -12,11 +12,11 @@ const { i18n } = common; describe('Google Payments', () => { const subKey = 'basic_3mo'; - describe('verifyGemPurchase', () => { + describe('verifyPurchase', () => { let sku; let user; let token; let receipt; let signature; let - headers; const gemsBlock = common.content.gems['21gems']; + headers; let iapSetupStub; let iapValidateStub; let iapIsValidatedStub; let - paymentBuyGemsStub; let validateGiftMessageStub; + paymentBuySkuStub; let validateGiftMessageStub; beforeEach(() => { sku = 'com.habitrpg.android.habitica.iap.21gems'; @@ -27,11 +27,10 @@ describe('Google Payments', () => { iapSetupStub = sinon.stub(iap, 'setup') .resolves(); - iapValidateStub = sinon.stub(iap, 'validate') - .resolves({}); + iapValidateStub = sinon.stub(iap, 'validate').resolves({ productId: sku }); iapIsValidatedStub = sinon.stub(iap, 'isValidated') .returns(true); - paymentBuyGemsStub = sinon.stub(payments, 'buyGems').resolves({}); + paymentBuySkuStub = sinon.stub(payments, 'buySkuItem').resolves({}); validateGiftMessageStub = sinon.stub(gems, 'validateGiftMessage'); }); @@ -39,7 +38,7 @@ describe('Google Payments', () => { iap.setup.restore(); iap.validate.restore(); iap.isValidated.restore(); - payments.buyGems.restore(); + payments.buySkuItem.restore(); gems.validateGiftMessage.restore(); }); @@ -48,7 +47,7 @@ describe('Google Payments', () => { iapIsValidatedStub = sinon.stub(iap, 'isValidated') .returns(false); - await expect(googlePayments.verifyGemPurchase({ + await expect(googlePayments.verifyPurchase({ user, receipt, signature, headers, })) .to.eventually.be.rejected.and.to.eql({ @@ -60,21 +59,25 @@ describe('Google Payments', () => { it('should throw an error if productId is invalid', async () => { receipt = `{"token": "${token}", "productId": "invalid"}`; + iapValidateStub.restore(); + iapValidateStub = sinon.stub(iap, 'validate').resolves({}); - await expect(googlePayments.verifyGemPurchase({ + paymentBuySkuStub.restore(); + await expect(googlePayments.verifyPurchase({ user, receipt, signature, headers, })) .to.eventually.be.rejected.and.to.eql({ - httpCode: 401, - name: 'NotAuthorized', + httpCode: 400, + name: 'BadRequest', message: googlePayments.constants.RESPONSE_INVALID_ITEM, }); + paymentBuySkuStub = sinon.stub(payments, 'buySkuItem').resolves({}); }); it('should throw an error if user cannot purchase gems', async () => { sinon.stub(user, 'canGetGems').resolves(false); - await expect(googlePayments.verifyGemPurchase({ + await expect(googlePayments.verifyPurchase({ user, receipt, signature, headers, })) .to.eventually.be.rejected.and.to.eql({ @@ -88,7 +91,7 @@ describe('Google Payments', () => { it('purchases gems', async () => { sinon.stub(user, 'canGetGems').resolves(true); - await googlePayments.verifyGemPurchase({ + await googlePayments.verifyPurchase({ user, receipt, signature, headers, }); @@ -101,15 +104,17 @@ describe('Google Payments', () => { signature, }); expect(iapIsValidatedStub).to.be.calledOnce; - expect(iapIsValidatedStub).to.be.calledWith({}); + expect(iapIsValidatedStub).to.be.calledWith( + { productId: sku }, + ); - expect(paymentBuyGemsStub).to.be.calledOnce; - expect(paymentBuyGemsStub).to.be.calledWith({ + expect(paymentBuySkuStub).to.be.calledOnce; + expect(paymentBuySkuStub).to.be.calledWith({ user, - paymentMethod: googlePayments.constants.PAYMENT_METHOD_GOOGLE, - gemsBlock, - headers, gift: undefined, + paymentMethod: googlePayments.constants.PAYMENT_METHOD_GOOGLE, + sku, + headers, }); expect(user.canGetGems).to.be.calledOnce; user.canGetGems.restore(); @@ -120,7 +125,7 @@ describe('Google Payments', () => { await receivingUser.save(); const gift = { uuid: receivingUser._id }; - await googlePayments.verifyGemPurchase({ + await googlePayments.verifyPurchase({ user, gift, receipt, signature, headers, }); @@ -134,20 +139,20 @@ describe('Google Payments', () => { signature, }); expect(iapIsValidatedStub).to.be.calledOnce; - expect(iapIsValidatedStub).to.be.calledWith({}); + expect(iapIsValidatedStub).to.be.calledWith( + { productId: sku }, + ); - expect(paymentBuyGemsStub).to.be.calledOnce; - expect(paymentBuyGemsStub).to.be.calledWith({ + expect(paymentBuySkuStub).to.be.calledOnce; + expect(paymentBuySkuStub).to.be.calledWith({ user, - paymentMethod: googlePayments.constants.PAYMENT_METHOD_GOOGLE, - gemsBlock, - headers, gift: { - type: 'gems', - gems: { amount: 21 }, - member: sinon.match({ _id: receivingUser._id }), uuid: receivingUser._id, + member: sinon.match({ _id: receivingUser._id }), }, + paymentMethod: googlePayments.constants.PAYMENT_METHOD_GOOGLE, + sku, + headers, }); }); }); diff --git a/test/api/unit/libs/payments/skuItem.test.js b/test/api/unit/libs/payments/skuItem.test.js new file mode 100644 index 0000000000..bc4bbb5c94 --- /dev/null +++ b/test/api/unit/libs/payments/skuItem.test.js @@ -0,0 +1,40 @@ +import { + canBuySkuItem, +} from '../../../../../website/server/libs/payments/skuItem'; +import { model as User } from '../../../../../website/server/models/user'; + +describe('payments/skuItems', () => { + let user; + let clock; + + beforeEach(() => { + user = new User(); + clock = null; + }); + afterEach(() => { + if (clock !== null) clock.restore(); + }); + + describe('#canBuySkuItem', () => { + it('returns true for random sku', () => { + expect(canBuySkuItem('something', user)).to.be.true; + }); + + describe('#gryphatrice', () => { + const sku = 'Pet-Gryphatrice-Jubilant'; + it('returns true during birthday week', () => { + clock = sinon.useFakeTimers(new Date('2023-01-29')); + expect(canBuySkuItem(sku, user)).to.be.true; + }); + it('returns false outside of birthday week', () => { + clock = sinon.useFakeTimers(new Date('2023-01-20')); + expect(canBuySkuItem(sku, user)).to.be.false; + }); + it('returns false if user already owns it', () => { + clock = sinon.useFakeTimers(new Date('2023-02-01')); + user.items.pets['Gryphatrice-Jubilant'] = 5; + expect(canBuySkuItem(sku, user)).to.be.false; + }); + }); + }); +}); diff --git a/test/api/v3/integration/payments/apple/POST-payments_apple_verifyiap.js b/test/api/v3/integration/payments/apple/POST-payments_apple_verifyiap.js index d142fe2a24..0400eedcf3 100644 --- a/test/api/v3/integration/payments/apple/POST-payments_apple_verifyiap.js +++ b/test/api/v3/integration/payments/apple/POST-payments_apple_verifyiap.js @@ -21,11 +21,11 @@ describe('payments : apple #verify', () => { let verifyStub; beforeEach(async () => { - verifyStub = sinon.stub(applePayments, 'verifyGemPurchase').resolves({}); + verifyStub = sinon.stub(applePayments, 'verifyPurchase').resolves({}); }); afterEach(() => { - applePayments.verifyGemPurchase.restore(); + applePayments.verifyPurchase.restore(); }); it('makes a purchase', async () => { diff --git a/test/api/v3/integration/payments/google/POST-payments_google_verifyiap.js b/test/api/v3/integration/payments/google/POST-payments_google_verifyiap.js index ecdf17dfc0..3bfcb08b8a 100644 --- a/test/api/v3/integration/payments/google/POST-payments_google_verifyiap.js +++ b/test/api/v3/integration/payments/google/POST-payments_google_verifyiap.js @@ -21,11 +21,11 @@ describe('payments : google #verify', () => { let verifyStub; beforeEach(async () => { - verifyStub = sinon.stub(googlePayments, 'verifyGemPurchase').resolves({}); + verifyStub = sinon.stub(googlePayments, 'verifyPurchase').resolves({}); }); afterEach(() => { - googlePayments.verifyGemPurchase.restore(); + googlePayments.verifyPurchase.restore(); }); it('makes a purchase', async () => { diff --git a/website/client/src/app.vue b/website/client/src/app.vue index e9afbb2515..ede7eb8c9c 100644 --- a/website/client/src/app.vue +++ b/website/client/src/app.vue @@ -35,6 +35,7 @@ + + + + + diff --git a/website/client/src/components/header/notifications/itemReceived.vue b/website/client/src/components/header/notifications/itemReceived.vue new file mode 100644 index 0000000000..700f7b47a5 --- /dev/null +++ b/website/client/src/components/header/notifications/itemReceived.vue @@ -0,0 +1,58 @@ + + + diff --git a/website/client/src/components/header/notificationsDropdown.vue b/website/client/src/components/header/notificationsDropdown.vue index 3d83ec3e3e..4cd0bc438e 100644 --- a/website/client/src/components/header/notificationsDropdown.vue +++ b/website/client/src/components/header/notificationsDropdown.vue @@ -123,23 +123,24 @@ import successImage from '@/assets/svg/success.svg'; import starBadge from '@/assets/svg/star-badge.svg'; // Notifications -import NEW_STUFF from './notifications/newStuff'; -import GROUP_TASK_NEEDS_WORK from './notifications/groupTaskNeedsWork'; -import GUILD_INVITATION from './notifications/guildInvitation'; -import PARTY_INVITATION from './notifications/partyInvitation'; +import CARD_RECEIVED from './notifications/cardReceived'; import CHALLENGE_INVITATION from './notifications/challengeInvitation'; -import QUEST_INVITATION from './notifications/questInvitation'; +import GIFT_ONE_GET_ONE from './notifications/g1g1'; import GROUP_TASK_ASSIGNED from './notifications/groupTaskAssigned'; import GROUP_TASK_CLAIMED from './notifications/groupTaskClaimed'; -import UNALLOCATED_STATS_POINTS from './notifications/unallocatedStatsPoints'; -import NEW_MYSTERY_ITEMS from './notifications/newMysteryItems'; -import CARD_RECEIVED from './notifications/cardReceived'; -import NEW_INBOX_MESSAGE from './notifications/newPrivateMessage'; +import GROUP_TASK_NEEDS_WORK from './notifications/groupTaskNeedsWork'; +import GUILD_INVITATION from './notifications/guildInvitation'; +import ITEM_RECEIVED from './notifications/itemReceived'; import NEW_CHAT_MESSAGE from './notifications/newChatMessage'; -import WORLD_BOSS from './notifications/worldBoss'; -import VERIFY_USERNAME from './notifications/verifyUsername'; +import NEW_INBOX_MESSAGE from './notifications/newPrivateMessage'; +import NEW_MYSTERY_ITEMS from './notifications/newMysteryItems'; +import NEW_STUFF from './notifications/newStuff'; import ONBOARDING_COMPLETE from './notifications/onboardingComplete'; -import GIFT_ONE_GET_ONE from './notifications/g1g1'; +import PARTY_INVITATION from './notifications/partyInvitation'; +import QUEST_INVITATION from './notifications/questInvitation'; +import UNALLOCATED_STATS_POINTS from './notifications/unallocatedStatsPoints'; +import VERIFY_USERNAME from './notifications/verifyUsername'; +import WORLD_BOSS from './notifications/worldBoss'; import OnboardingGuide from './onboardingGuide'; export default { @@ -147,24 +148,25 @@ export default { MenuDropdown, MessageCount, // One component for each type - NEW_STUFF, - GROUP_TASK_NEEDS_WORK, - GUILD_INVITATION, - PARTY_INVITATION, + CARD_RECEIVED, CHALLENGE_INVITATION, - QUEST_INVITATION, + GIFT_ONE_GET_ONE, GROUP_TASK_ASSIGNED, GROUP_TASK_CLAIMED, - UNALLOCATED_STATS_POINTS, - NEW_MYSTERY_ITEMS, - CARD_RECEIVED, - NEW_INBOX_MESSAGE, + GROUP_TASK_NEEDS_WORK, + GUILD_INVITATION, + ITEM_RECEIVED, NEW_CHAT_MESSAGE, - WorldBoss: WORLD_BOSS, - VERIFY_USERNAME, - OnboardingGuide, + NEW_INBOX_MESSAGE, + NEW_MYSTERY_ITEMS, + NEW_STUFF, ONBOARDING_COMPLETE, - GIFT_ONE_GET_ONE, + PARTY_INVITATION, + QUEST_INVITATION, + UNALLOCATED_STATS_POINTS, + VERIFY_USERNAME, + WorldBoss: WORLD_BOSS, + OnboardingGuide, }, data () { return { @@ -185,6 +187,7 @@ export default { // NOTE: Those not listed here won't be shown in the notification panel! handledNotifications: [ 'NEW_STUFF', + 'ITEM_RECEIVED', 'GIFT_ONE_GET_ONE', 'GROUP_TASK_NEEDS_WORK', 'GUILD_INVITATION', diff --git a/website/client/src/components/news/birthdayModal.vue b/website/client/src/components/news/birthdayModal.vue new file mode 100644 index 0000000000..28f8db7aef --- /dev/null +++ b/website/client/src/components/news/birthdayModal.vue @@ -0,0 +1,877 @@ + + + + + + + + + diff --git a/website/client/src/components/payments/amazonModal.vue b/website/client/src/components/payments/amazonModal.vue index fcae0e01ec..fa332be149 100644 --- a/website/client/src/components/payments/amazonModal.vue +++ b/website/client/src/components/payments/amazonModal.vue @@ -78,6 +78,7 @@ export default { orderReferenceId: null, subscription: null, coupon: null, + sku: null, }, isAmazonSetup: false, amazonButtonEnabled: false, @@ -174,7 +175,10 @@ export default { storePaymentStatusAndReload (url) { let paymentType; - if (this.amazonPayments.type === 'single' && !this.amazonPayments.gift) paymentType = 'gems'; + if (this.amazonPayments.type === 'single') { + if (this.amazonPayments.sku) paymentType = 'sku'; + else if (!this.amazonPayments.gift) paymentType = 'gems'; + } if (this.amazonPayments.type === 'subscription') paymentType = 'subscription'; if (this.amazonPayments.groupId || this.amazonPayments.groupToCreate) paymentType = 'groupPlan'; if (this.amazonPayments.type === 'single' && this.amazonPayments.gift && this.amazonPayments.giftReceiver) { @@ -223,6 +227,7 @@ export default { const data = { orderReferenceId: this.amazonPayments.orderReferenceId, gift: this.amazonPayments.gift, + sku: this.amazonPayments.sku, }; if (this.amazonPayments.gemsBlock) { diff --git a/website/client/src/components/payments/successModal.vue b/website/client/src/components/payments/successModal.vue index 4ac16233bc..620158cd86 100644 --- a/website/client/src/components/payments/successModal.vue +++ b/website/client/src/components/payments/successModal.vue @@ -1,8 +1,8 @@ + + + + @@ -232,9 +269,8 @@ margin-bottom: 16px; } - .check { - width: 35.1px; - height: 28px; + .svg-check { + width: 45px; color: $white; } } @@ -293,6 +329,34 @@ .group-billing-date { width: 269px; } + + .words { + margin-bottom: 16px; + justify-content: center; + font-size: 0.875rem; + color: $gray-50; + line-height: 1.71; + } + + .jub-success { + margin-top: 0px; + margin-bottom: 0px; + } + + .gryph-bg { + width: 110px; + height: 104px; + align-items: center; + justify-content: center; + padding: 16px; + border-radius: 4px; + background-color: $gray-700; + } + .btn-jub { + margin-bottom: 8px; + margin-top: 24px; + } + } .modal-footer { background: $gray-700; @@ -430,6 +494,9 @@ export default { isNewGroup () { return this.paymentData.paymentType === 'groupPlan' && this.paymentData.newGroup; }, + ownsJubilantGryphatrice () { + return this.paymentData.paymentType === 'sku'; // will need to be revised when there are other discrete skus in system + }, }, mounted () { this.$root.$on('habitica:payment-success', data => { @@ -458,6 +525,12 @@ export default { this.sendingInProgress = false; this.$root.$emit('bv::hide::modal', 'payments-success-modal'); }, + closeAndRedirect () { + if (this.$router.history.current.name !== 'stable') { + this.$router.push('/inventory/stable'); + } + this.close(); + }, submit () { if (this.paymentData.group && !this.paymentData.newGroup) { Analytics.track({ diff --git a/website/client/src/mixins/foolPet.js b/website/client/src/mixins/foolPet.js index 6a2c74f9a2..371a28bd6f 100644 --- a/website/client/src/mixins/foolPet.js +++ b/website/client/src/mixins/foolPet.js @@ -26,6 +26,7 @@ export default { 'Fox-Veteran', 'JackOLantern-Glow', 'Gryphon-Gryphatrice', + 'Gryphatrice-Jubilant', 'JackOLantern-RoyalPurple', ]; const BASE_PETS = [ diff --git a/website/client/src/mixins/payments.js b/website/client/src/mixins/payments.js index f22c5ffc5f..65af65e5aa 100644 --- a/website/client/src/mixins/payments.js +++ b/website/client/src/mixins/payments.js @@ -9,7 +9,6 @@ import { CONSTANTS, setLocalSetting } from '@/libs/userlocalManager'; const { STRIPE_PUB_KEY } = process.env; -// const habiticaUrl = `${window.location.protocol}//${window.location.host}`; let stripeInstance = null; export default { @@ -70,6 +69,7 @@ export default { type, giftData, gemsBlock, + sku, } = data; let { url } = data; @@ -93,6 +93,11 @@ export default { url += `?gemsBlock=${gemsBlock.key}`; } + if (type === 'sku') { + appState.sku = sku; + url += `?sku=${sku}`; + } + setLocalSetting(CONSTANTS.savedAppStateValues.SAVED_APP_STATE, JSON.stringify(appState)); window.open(url, '_blank'); @@ -129,6 +134,7 @@ export default { if (data.group || data.groupToCreate) paymentType = 'groupPlan'; if (data.gift && data.gift.type === 'gems') paymentType = 'gift-gems'; if (data.gift && data.gift.type === 'subscription') paymentType = 'gift-subscription'; + if (data.sku) paymentType = 'sku'; let url = '/stripe/checkout-session'; const postData = {}; @@ -148,6 +154,7 @@ export default { if (data.coupon) postData.coupon = data.coupon; if (data.groupId) postData.groupId = data.groupId; if (data.demographics) postData.demographics = data.demographics; + if (data.sku) postData.sku = data.sku; const response = await axios.post(url, postData); @@ -250,6 +257,7 @@ export default { if (data.type === 'single') { this.amazonPayments.gemsBlock = data.gemsBlock; + this.amazonPayments.sku = data.sku; } if (data.gift) { diff --git a/website/common/locales/ar/achievements.json b/website/common/locales/ar/achievements.json index 3b843b0743..829054dee1 100755 --- a/website/common/locales/ar/achievements.json +++ b/website/common/locales/ar/achievements.json @@ -127,16 +127,22 @@ "achievementReptacularRumbleModalText": "لقد جمعت كل الزواحف الأليفة!", "achievementReptacularRumbleText": "لقد فقس جميع الألوان القياسية للحيوانات الأليفة الزواحف: التمساح، الزاحف المجنح، الأفعى، ترايسيراتوبس، السلحفاة، التيرانوصور ريكس وفيلوسيرابتور!", "achievementBirdsOfAFeather": "أصدقاء الطيران", - "achievementZodiacZookeeper": "حارس حديقة الحيوانات الفلكية", - "achievementShadyCustomerText": "جمع كل حيوانات الظل الأليفة.", - "achievementShadyCustomerModalText": "لقد جمعت كل حيوانات الظل الأليفة!", - "achievementZodiacZookeeperModalText": "لقد جمعت كل الحيوانات الأليفة الفلكية!", + "achievementZodiacZookeeper": "حارس البروج", + "achievementShadyCustomerText": "جمعت كل الحيوانات الأليفة السوداء.", + "achievementShadyCustomerModalText": "لقد جمعت كل الحيوانات الأليفة السوداء!", + "achievementZodiacZookeeperModalText": "لقد جمعت كل حيوانات البروج الأليفة!", "achievementBirdsOfAFeatherText": "لقد فقس جميع الألوان القياسية للحيوانات الأليفة الطائرة: الخنزير الطائر، البومة، الببغاء، الزاحف المجنح، الجرايفون، فالكون، الطاووس والديك!", - "achievementShadeOfItAllModalText": "قمت بترويض كل الحيوانات السوداء", - "achievementShadyCustomer": "عميل الظل", - "achievementShadeOfItAll": "الظل فوق كل شيء", - "achievementShadeOfItAllText": "روض كل حيوانات الظل السوداء.", + "achievementShadeOfItAllModalText": "لقد قمت بترويض كل الركوبات السوداء!", + "achievementShadyCustomer": "عميل مشبوه", + "achievementShadeOfItAll": "كل شيء في الظلام", + "achievementShadeOfItAllText": "قام بترويض كل الركوبات السوداء.", "achievementWoodlandWizard": "ساحر الغابة", "achievementWoodlandWizardText": "لقد فقس جميع الألوان القياسية لمخلوقات الغابة: الغرير، الدب، الغزال، الثعلب، الضفدع، القنفذ، البومة، الأفعى، السنجاب والشجيرة!", - "achievementWoodlandWizardModalText": "لقد جمعت كل حيوانات الغابة الأليفة!" + "achievementWoodlandWizardModalText": "لقد جمعت كل حيوانات الغابة الأليفة!", + "achievementPolarPro": "المحترف القطبي", + "achievementPolarProText": "فقست جميع الحيوانات الأليفة القطبية: الدب ، الثعلب ، البطريق ، الحوت ، والذئب!", + "achievementPolarProModalText": "لقد جمعت كل الحيوانات الأليفة القطبية!", + "achievementBoneToPick": "جامع العظام", + "achievementBoneToPickText": "فقست جميع الحيوانات الأليفة الهيكلية المغامرة والكلاسيكية!", + "achievementBoneToPickModalText": "لقد جمعت كل الحيوانات الأليفة الهيكلية المغامرة والكلاسيكية!" } diff --git a/website/common/locales/ar/backgrounds.json b/website/common/locales/ar/backgrounds.json index 07dce4ccfc..ce445413be 100755 --- a/website/common/locales/ar/backgrounds.json +++ b/website/common/locales/ar/backgrounds.json @@ -422,5 +422,31 @@ "backgroundDuckPondNotes": "أطعم الطيور المائية في بركة البط.", "backgroundValentinesDayFeastingHallNotes": "اشعر بالحب في قاعة احتفالات عيد الحب.", "hideLockedBackgrounds": "إخفاء الخلفيات المقفلة", - "backgrounds032019": "SET 58: تم إصداره في مارس 2019" + "backgrounds032019": "SET 58: تم إصداره في مارس 2019", + "backgroundFieldWithColoredEggsNotes": "ابحث عن كنز الربيع في حقل به بيض ملون.", + "backgroundFlowerMarketNotes": "اعثر على الزهور المثالية لباقة أو حديقة في سوق الزهور.", + "backgrounds052019": "المجموعة 60: صدرت في مايو 2019", + "backgroundHalflingsHouseNotes": "قم بزيارة منزل هافلينج الساحر.", + "backgroundBirchForestText": "غابة البتولا", + "backgroundBlossomingDesertNotes": "شاهد إزهارًا هائلًا نادرًا في الصحراء المزهرة.", + "backgroundDojoText": "دوجو", + "backgroundBlossomingDesertText": "الصحراء المزهرة", + "backgroundHalflingsHouseText": "منزل هافلينج", + "backgroundFlowerMarketText": "سوق الزهور", + "backgroundBirchForestNotes": "اقضِ بعض الوقت في غابة البتولا الهادئة.", + "backgroundFieldWithColoredEggsText": "حقل به بيض ملون", + "backgrounds042019": "المجموعة 59: صدرت في أبريل 2019", + "backgroundRainbowMeadowNotes": "ابحث عن وعاء الذهب حيث ينتهي قوس قزح في مرج.", + "backgrounds062019": "المجموعة 61: صدرت في يونيو 2019", + "backgroundUnderwaterVentsNotes": "غص في الأعماق حيث تكمن الفتحات المائية الحرارية.", + "backgroundRainbowMeadowText": "مرج قوس قزح", + "backgroundSeasideCliffsNotes": "قف على الشاطئ وسط جمال المنحدرات الساحلية المحيطة.", + "backgroundSchoolOfFishNotes": "اسبح بين سرب من الأسماك.", + "backgroundUnderwaterVentsText": "الفتحات الحرارية المائية", + "backgroundSeasideCliffsText": "المنحدرات الساحلية", + "backgroundParkWithStatueText": "حديقة بها تمثال", + "backgroundDojoNotes": "تعلم حركات جديدة في دوجو.", + "backgroundSchoolOfFishText": "سرب من الأسماك", + "backgrounds072019": "المجموعة 62: صدرت في يوليو 2019", + "backgroundParkWithStatueNotes": "اتبع مسارًا تصطف على جانبيه الأزهار عبر حديقة بها تمثال." } diff --git a/website/common/locales/ar/gear.json b/website/common/locales/ar/gear.json index 42643a0cad..68dedcde31 100755 --- a/website/common/locales/ar/gear.json +++ b/website/common/locales/ar/gear.json @@ -91,7 +91,7 @@ "weaponSpecialTakeThisText": "Take This Sword", "weaponSpecialTakeThisNotes": "This sword was earned by participating in a sponsored Challenge made by Take This. Congratulations! Increases all Stats by <%= attrs %>.", "weaponSpecialTridentOfCrashingTidesText": "ترايدنت الأمواج المتضاربة", - "weaponSpecialTridentOfCrashingTidesNotes": "يمنحك القدرة على التحكم بالأسماك، وكذلك القدرة على تقديم بعض الطعنات القوية لمهماتك. يزيد الذكاء بقدر <%= int %>.", + "weaponSpecialTridentOfCrashingTidesNotes": "يمنحك القدرة على التحكم بالأسماك، وكذلك القدرة على توجيه بعض الطعنات القوية لمهماتك. يزيد الذكاء بقدر <%= int %>.", "weaponSpecialTaskwoodsLanternText": "Taskwoods Lantern", "weaponSpecialTaskwoodsLanternNotes": "Given at the dawn of time to the guardian ghost of the Taskwood Orchards, this lantern can illuminate the deepest darkness and weave powerful spells. Increases Perception and Intelligence by <%= attrs %> each.", "weaponSpecialBardInstrumentText": "Bardic Lute", @@ -117,7 +117,7 @@ "weaponSpecialYetiText": "رمح مروض اليتي", "weaponSpecialYetiNotes": "هذا الرمح يسمح لمستخدمه الصيطرة على أي يتي. يزيد القوة بقدر <%= str %>. معدات الطبعة المحدودة لشتاء 2013-2014.", "weaponSpecialSkiText": "عمود المتزلج القاتل", - "weaponSpecialSkiNotes": "سلاح قادر على تدمير المجموعات الكبيرة من الأعداء. وأيضاً يساعد مستخدمه على الانعطافات المتوازية. يزيد القوة بقدر <%= str %>. معدات الطبعة المحدودة لشتاء 2013-2014.", + "weaponSpecialSkiNotes": "سلاح قادر على تدمير المجموعات الكبيرة من الأعداء. وأيضاً يساعد مستخدمه على الانعطافات المتوازية. يزيد القوة بقدر <%= str %>. معدات الطبعة المحدودة لشتاء 2013-2014.", "weaponSpecialCandycaneText": "قضيب حلوى القصب", "weaponSpecialCandycaneNotes": "A powerful mage's staff. Powerfully DELICIOUS, we mean! Increases Intelligence by <%= int %> and Perception by <%= per %>. Limited Edition 2013-2014 Winter Gear.", "weaponSpecialSnowflakeText": "العصا السحرية ذات ندفة الثلج", diff --git a/website/common/locales/ar/messages.json b/website/common/locales/ar/messages.json index 963d60b7c9..0656ddcebb 100755 --- a/website/common/locales/ar/messages.json +++ b/website/common/locales/ar/messages.json @@ -11,7 +11,7 @@ "messageLikesFood": "<%= egg %> يحب <%= foodText %> كثيراً!", "messageDontEnjoyFood": "<%= egg %> يأكل <%= foodText %> ولكنه لا يبدو مستمتعاً.", "messageBought": "لقد اشتريت <%= itemText %>", - "messageUnEquipped": "<%= itemText %> unequipped.", + "messageUnEquipped": "<%= itemText %> غير مجهز/ة.", "messageMissingEggPotion": "تفتقد أيًا من تلك البيضة أو جرعة الفقس.", "messageInvalidEggPotionCombo": "لا يمكنك فقس بيض حيوانات التنقيب مع جرع فقس سحرية! جرب بيضة أخرى.", "messageAlreadyPet": "لديك ذلك الحيوان الأليف. حاول فقس تركيبة مختلفة!", @@ -50,5 +50,7 @@ "unallocatedStatsPoints": "You have <%= points %> unallocated Stat Points", "beginningOfConversation": "هذه هي بداية محادثتك مع <%= userName %>. تذكر أن تكون لطيفاً محترماً، واتبع إرشادات المنتدى!", "messageDeletedUser": "عذراً، لقد حذف هذا المستخدم حسابه.", - "messageMissingDisplayName": "Missing display name." + "messageMissingDisplayName": "Missing display name.", + "messageBattleGearUnEquipped": "المعدات القتالية غير مجهزة.", + "messageCostumeUnEquipped": "الزي غير مجهز." } diff --git a/website/common/locales/bg/achievements.json b/website/common/locales/bg/achievements.json index a3b99d546a..a037bc1729 100644 --- a/website/common/locales/bg/achievements.json +++ b/website/common/locales/bg/achievements.json @@ -1,8 +1,8 @@ { "achievement": "Постижение", "onwards": "Напред!", - "levelup": "Изпълнявайки целите си в истинския живот, Вие качихте ниво и здравето Ви беше запълнено!", - "reachedLevel": "Достигнахте ниво <%= level %>", + "levelup": "Изпълнявайки целите си в истинския живот, Вие се качихте ниво и здравето Ви беше запълнено!", + "reachedLevel": "Достигнахте Ниво <%= level %>", "achievementLostMasterclasser": "Изпълнител на Мисии: Серия на Класовите Повелители", "achievementLostMasterclasserText": "Завършихте шестнадесетте мисии от Серията на Класовите Повелители и разрешихте загадката на Изгубената Класова Повелителка!", "achievementUndeadUndertaker": "Нежив Погребален Директор", @@ -59,13 +59,13 @@ "foundNewItemsExplanation": "Завършека на задачи ви дава шанс да намерите предмети като Яйца, Излюпващи Отвари и Животинска Храна.", "foundNewItems": "Намерихте нови предмети!", "hideAchievements": "Скрий <%= category %>", - "showAllAchievements": "Покажи Всички", - "onboardingCompleteDescSmall": "Ако желаете още повече, отидете в Постижения и започнете събирането!", + "showAllAchievements": "Покажи Всички <%=category %>", + "onboardingCompleteDescSmall": "Ако желаете още повече, отидете в Постижения и започнете колекционирането!", "onboardingCompleteDesc": "Получихте 5 Постижения и 100 Злато за завършения списък.", - "onboardingComplete": "Завършихте задачите си!", + "onboardingComplete": "Завършихте уводните си задачи!", "earnedAchievement": "Получихте постижение!", "yourProgress": "Вашият Напредък", - "gettingStartedDesc": "Изпълнете задачите и ще получите 5 Постижения и 100 Злато при завършване!", + "gettingStartedDesc": "Изпълнете тези уводни задачи и ще получите 5 Постижения и 100 Злато при завършване!", "achievementPrimedForPaintingModalText": "Събрахте всички Бели Животни!", "achievementPearlyProModalText": "Опитомихте всички Бели Оседлани Зверове!", "achievementPearlyProText": "Опетомили са всички Оседлани Зверове.", @@ -75,13 +75,13 @@ "achievementFedPet": "Нахрани Животно", "achievementHatchedPetModalText": "Отиди в инвентара си и смеси излюпваща отвара с Яйце", "achievementHatchedPet": "Излюпи Животно", - "viewAchievements": "Постижения", + "viewAchievements": "Прегледай Постижения", "letsGetStarted": "Нека да започнем!", "onboardingProgress": "<%= percentage %>% напредък", "achievementBareNecessitiesModalText": "Завършихте мисиите за Маймуна, Ленивец и Фиданка!", "achievementBareNecessitiesText": "Завършили са всички мисии за Маймуна, Ленивец и Фиданка.", "achievementBareNecessities": "От първа необходимост", - "achievementBoneCollectorText": "Събрали сте всички Скелети домашни любимци.", + "achievementBoneCollectorText": "Събрали са всички Скелетни Любимци.", "achievementBoneCollector": "Колекционер на кости", "achievementAllThatGlittersModalText": "Събрахте всички оседлани Златни животни!", "achievementAllThatGlittersText": "Събрали сте всички оседлани Златни животни.", @@ -92,5 +92,6 @@ "achievementFreshwaterFriendsModalText": "Завършихте мисиите за аксолотъла, жабата и хипопотама!", "achievementFreshwaterFriendsText": "Завършили сте мисиите за домашни любимци за аксолотъла, жабата и хипопотама.", "achievementFreshwaterFriends": "Сладководни приятели", - "yourRewards": "Вашите възнаграждения" + "yourRewards": "Вашите Награди", + "achievementBoneCollectorModalText": "Събрали сте всичките Скелетни Любимци!" } diff --git a/website/common/locales/en/achievements.json b/website/common/locales/en/achievements.json index ad43c35688..1432a49ef3 100644 --- a/website/common/locales/en/achievements.json +++ b/website/common/locales/en/achievements.json @@ -143,6 +143,6 @@ "achievementBoneToPickText": "Has hatched all the Classic and Quest Skeleton Pets!", "achievementBoneToPickModalText": "You collected all the Classic and Quest Skeleton Pets!", "achievementPolarPro": "Polar Pro", - "achievementPolarProText": "Has hatched all Polar pets: Bear, Fox, Penguin, Whale, and Wolf!", + "achievementPolarProText": "Has hatched all standard colors of Polar pets: Bear, Fox, Penguin, Whale, and Wolf!", "achievementPolarProModalText": "You collected all the Polar Pets!" } diff --git a/website/common/locales/en/backgrounds.json b/website/common/locales/en/backgrounds.json index 31352661db..5abbb901a2 100644 --- a/website/common/locales/en/backgrounds.json +++ b/website/common/locales/en/backgrounds.json @@ -857,5 +857,9 @@ "backgroundSteamworksText": "Steamworks", "backgroundSteamworksNotes": "Build mighty contraptions of vapor and steel in a Steamworks.", "backgroundClocktowerText": "Clock Tower", - "backgroundClocktowerNotes": "Situate your secret lair behind the face of a Clock Tower." + "backgroundClocktowerNotes": "Situate your secret lair behind the face of a Clock Tower.", + + "eventBackgrounds": "Event Backgrounds", + "backgroundBirthdayBashText": "Birthday Bash", + "backgroundBirthdayBashNotes": "Habitica's having a birthday party, and everyone's invited!" } diff --git a/website/common/locales/en/gear.json b/website/common/locales/en/gear.json index b5d12552ea..c9ee405725 100644 --- a/website/common/locales/en/gear.json +++ b/website/common/locales/en/gear.json @@ -438,8 +438,8 @@ "headSpecialNye2021Text": "Preposterous Party Hat", "headSpecialNye2021Notes": "You've received a Preposterous Party Hat! Wear it with pride while ringing in the New Year! Confers no benefit.", - "headSpecialNye2022Text": "Fantastic Party Hat", - "headSpecialNye2022Notes": "You've received a Fantastic Party Hat! Wear it with pride while ringing in the New Year! Confers no benefit.", + "headSpecialNye2022Text": "Fabulous Party Hat", + "headSpecialNye2022Notes": "You've received a Fabulous Party Hat! Wear it with pride while ringing in the New Year! Confers no benefit.", "weaponSpecialSpring2022RogueText": "Giant Earring Stud", "weaponSpecialSpring2022RogueNotes": "A shiny! It’s so shiny and gleaming and pretty and nice and all yours! Increases Strength by <%= str %>. Limited Edition 2022 Spring Gear.", @@ -801,7 +801,8 @@ "armorSpecialBirthday2021Notes": "Happy Birthday, Habitica! Wear these Extravagant Party Robes to celebrate this wonderful day. Confers no benefit.", "armorSpecialBirthday2022Text": "Preposterous Party Robes", "armorSpecialBirthday2022Notes": "Happy Birthday, Habitica! Wear these Proposterous Party Robes to celebrate this wonderful day. Confers no benefit.", - + "armorSpecialBirthday2023Text": "Fabulous Party Robes", + "armorSpecialBirthday2023Notes": "Happy Birthday, Habitica! Wear these Fabulous Party Robes to celebrate this wonderful day. Confers no benefit.", "armorSpecialGaymerxText": "Rainbow Warrior Armor", "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.", @@ -2662,7 +2663,9 @@ "backMystery202206Text": "Sea Sprite Wings", "backMystery202206Notes": "Whimsical wings made of water and waves! Confers no benefit. June 2022 Subscriber Item.", "backMystery202301Text": "Five Tails of Valor", - "backMystery202301Notes": "These fluffy tails contain ethereal power and also a high level of charm! Confers no benefit. January 2023 Subscriber Item.", + "backMystery202301Notes": "These fluffy tails contain ethereal power and also a high level of charm! Confers no benefit. January 2023 Subscriber Item.", + "backMystery202302Text": "Trickster Tabby Tail", + "backMystery202302Notes": "Anytime you wear this tail it's sure to be a frabjous day! Callooh! Callay! Confers no benefit. February 2023 Subscriber Item.", "backSpecialWonderconRedText": "Mighty Cape", "backSpecialWonderconRedNotes": "Swishes with strength and beauty. Confers no benefit. Special Edition Convention Item.", @@ -2680,6 +2683,9 @@ "backSpecialTurkeyTailGildedNotes": "Plumage fit for a parade! Confers no benefit.", "backSpecialNamingDay2020Text": "Royal Purple Gryphon Tail", "backSpecialNamingDay2020Notes": "Happy Naming Day! Swish this fiery, pixely tail about as you celebrate Habitica. Confers no benefit.", + "backSpecialAnniversaryText": "Habitica Hero Cape", + "backSpecialAnniversaryNotes": "Let this proud cape fly in the wind and tell everyone that you're a Habitica Hero. Confers no benefit. Special Edition 10th Birthday Bash Item.", + "backBearTailText": "Bear Tail", "backBearTailNotes": "This tail makes you look like a brave bear! Confers no benefit.", "backCactusTailText": "Cactus Tail", @@ -2711,6 +2717,8 @@ "bodySpecialTakeThisNotes": "These pauldrons were earned by participating in a sponsored Challenge made by Take This. Congratulations! Increases all Stats by <%= attrs %>.", "bodySpecialAetherAmuletText": "Aether Amulet", "bodySpecialAetherAmuletNotes": "This amulet has a mysterious history. Increases Constitution and Strength by <%= attrs %> each.", + "bodySpecialAnniversaryText": "Habitica Hero Collar", + "bodySpecialAnniversaryNotes": "Perfectly complement your royal purple ensemble! Confers no benefit. Special Edition 10th Birthday Bash Item.", "bodySpecialSummerMageText": "Shining Capelet", "bodySpecialSummerMageNotes": "Neither salt water nor fresh water can tarnish this metallic capelet. Confers no benefit. Limited Edition 2014 Summer Gear.", @@ -2867,6 +2875,8 @@ "headAccessoryMystery202205Notes": "These dazzling horns are as bright as a desert sunset. Confers no benefit. May 2022 Subscriber Item.", "headAccessoryMystery202212Text": "Glacial Tiara", "headAccessoryMystery202212Notes": "Magnify your warmth and friendship to new heights with this ornate golden tiara. Confers no benefit. December 2022 Subscriber Item.", + "headAccessoryMystery202302Text": "Trickster Tabby Ears", + "headAccessoryMystery202302Notes": "The purr-fect accessory to set off your enchanting grin. Confers no benefit. February 2023 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.", @@ -2913,6 +2923,8 @@ "eyewearSpecialAetherMaskNotes": "This mask has a mysterious history. Increases Intelligence by <%= int %>.", "eyewearSpecialKS2019Text": "Mythic Gryphon Visor", "eyewearSpecialKS2019Notes": "Bold as a gryphon's... hmm, gryphons don't have visors. It reminds you to... oh, who are we kidding, it just looks cool! Confers no benefit.", + "eyewearSpecialAnniversaryText": "Habitica Hero Mask", + "eyewearSpecialAnniversaryNotes": "Look through the eyes of a Habitica Hero - you! Confers no benefit. Special Edition 10th Birthday Bash Item.", "eyewearSpecialSummerRogueText": "Roguish Eyepatch", "eyewearSpecialSummerRogueNotes": "It doesn't take a scallywag to see how stylish this is! Confers no benefit. Limited Edition 2014 Summer Gear.", diff --git a/website/common/locales/en/limited.json b/website/common/locales/en/limited.json index 44b213e424..f2ee092832 100644 --- a/website/common/locales/en/limited.json +++ b/website/common/locales/en/limited.json @@ -209,6 +209,7 @@ "dateEndOctober": "October 31", "dateEndNovember": "November 30", "dateEndDecember": "December 31", + "dateStartFebruary": "February 8", "januaryYYYY": "January <%= year %>", "februaryYYYY": "February <%= year %>", "marchYYYY": "March <%= year %>", @@ -236,5 +237,33 @@ "g1g1Limitations": "This is a limited time event that starts on December 15th at 8:00 AM ET (13:00 UTC) and will end January 8th at 11:59 PM ET (January 9th 04:59 UTC). This promotion only applies when you gift to another Habitican. If you or your gift recipient already have a subscription, the gifted subscription will add months of credit that will only be used after the current subscription is canceled or expires.", "noLongerAvailable": "This item is no longer available.", "gemSaleHow": "Between <%= eventStartMonth %> <%= eventStartOrdinal %> and <%= eventEndOrdinal %>, simply purchase any Gem bundle like usual and your account will be credited with the promotional amount of Gems. More Gems to spend, share, or save for any future releases!", - "gemSaleLimitations": "This promotion only applies during the limited time event. This event starts on <%= eventStartMonth %> <%= eventStartOrdinal %> at 8:00 AM EDT (12:00 UTC) and will end <%= eventStartMonth %> <%= eventEndOrdinal %> at 8:00 PM EDT (00:00 UTC). The promo offer is only available when buying Gems for yourself." -} + "gemSaleLimitations": "This promotion only applies during the limited time event. This event starts on <%= eventStartMonth %> <%= eventStartOrdinal %> at 8:00 AM EDT (12:00 UTC) and will end <%= eventStartMonth %> <%= eventEndOrdinal %> at 8:00 PM EDT (00:00 UTC). The promo offer is only available when buying Gems for yourself.", + "anniversaryLimitations": "This is a limited time event that starts on January 30th at 8:00 AM ET (13:00 UTC) and will end February 8th at 11:59 PM ET (04:59 UTC). The Limited Edition Jubilant Gryphatrice and ten Magic Hatching Potions will be available to buy during this time. The other Gifts listed in the Four for Free section will be automatically delivered to all accounts that were active in the 30 days prior to day the gift is sent. Accounts created after the gifts are sent will not be able to claim them.", + "anniversaryLimitedDates": "January 30th to February 8th", + "limitedEvent": "Limited Event", + "celebrateAnniversary": "Celebrate Habitica's 10th Birthday with gifts and exclusive items below!", + "celebrateBirthday": "Celebrate Habitica's 10th Birthday with gifts and exclusive items!", + "jubilantGryphatricePromo": "Animated Jubilant Gryphatrice Pet", + "limitedEdition": "Limited Edition", + "anniversaryGryphatriceText": "The rare Jubilant Gryphatrice joins the birthday celebrations! Don't miss your chance to own this exclusive animated Pet.", + "anniversaryGryphatricePrice": "Own it today for $9.99 or 60 gems", + "buyNowMoneyButton": "Buy Now for $9.99", + "buyNowGemsButton": "Buy Now for 60 Gems", + "wantToPayWithGemsText": "Want to pay with Gems?", + "wantToPayWithMoneyText": "Want to pay with Stripe, Paypal, or Amazon?", + "ownJubilantGryphatrice": "You own the Jubilant Gryphatrice! Visit the Stable to equip!", + "jubilantSuccess": "You've successfully purchased the Jubilant Gryphatrice!", + "stableVisit": "Visit the Stable to equip!", + "takeMeToStable": "Take me to the Stable", + "plentyOfPotions": "Plenty of Potions", + "plentyOfPotionsText": "We're bringing back 10 of the community's favorite Magic Hatching potions. Head over to The Market to fill out your collection!", + "visitTheMarketButton": "Visit the Market", + "fourForFree": "Four for Free", + "fourForFreeText": "To keep the party going, we'll be giving away Party Robes, 20 Gems, and a limited edition birthday Background and item set that includes a Cape, Pauldrons, and an Eyemask.", + "dayOne": "Day 1", + "dayFive": "Day 5", + "dayTen": "Day 10", + "partyRobes": "Party Robes", + "twentyGems": "20 Gems", + "birthdaySet": "Birthday Set" +} \ No newline at end of file diff --git a/website/common/locales/en/pets.json b/website/common/locales/en/pets.json index 5b2cd13a1a..001ed2ad8c 100644 --- a/website/common/locales/en/pets.json +++ b/website/common/locales/en/pets.json @@ -32,6 +32,7 @@ "royalPurpleJackalope": "Royal Purple Jackalope", "invisibleAether": "Invisible Aether", "gryphatrice": "Gryphatrice", + "jubilantGryphatrice": "Jubilant Gryphatrice", "potion": "<%= potionType %> Potion", "egg": "<%= eggType %> Egg", "eggs": "Eggs", diff --git a/website/common/locales/en/questsContent.json b/website/common/locales/en/questsContent.json index 440a2910b1..63b09b9621 100644 --- a/website/common/locales/en/questsContent.json +++ b/website/common/locales/en/questsContent.json @@ -365,7 +365,7 @@ "questSnailUnlockText": "Unlocks Snail Eggs for purchase 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.", + "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 a 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", diff --git a/website/common/locales/en/subscriber.json b/website/common/locales/en/subscriber.json index 4a26167d82..287315df4f 100644 --- a/website/common/locales/en/subscriber.json +++ b/website/common/locales/en/subscriber.json @@ -146,6 +146,7 @@ "mysterySet202211": "Electromancer Set", "mysterySet202212": "Glacial Guardian Set", "mysterySet202301": "Valiant Vulpine Set", + "mysterySet202302": "Trickster Tabby Set", "mysterySet301404": "Steampunk Standard Set", "mysterySet301405": "Steampunk Accessories Set", "mysterySet301703": "Peacock Steampunk Set", diff --git a/website/common/locales/fil/content.json b/website/common/locales/fil/content.json index 2988acbfe3..1f854b1014 100755 --- a/website/common/locales/fil/content.json +++ b/website/common/locales/fil/content.json @@ -303,9 +303,9 @@ "foodCandyRed": "Cinnamon Candy", "foodCandyRedThe": "the Cinnamon Candy", "foodCandyRedA": "Cinnamon Candy", - "foodSaddleText": "Siya", - "foodSaddleNotes": "Agád na pinapalakí ang isa sa iyong mga alaga.", - "foodSaddleSellWarningNote": "Uy! Medyo kapaki-pakinabang! Pamilyar ka ba kung papaano gumamit ng Siya sa iyong mga Alagà?", + "foodSaddleText": "Upuán", + "foodSaddleNotes": "Agád na pinalálakí ang isa sa iyóng mga alaga.", + "foodSaddleSellWarningNote": "Uy! Kapakípakinabang! May alám ka ba sa paggamit ng mga Upuán sa mga Alagà?", "foodNotes": "Feed this to a pet and it may grow into a sturdy steed.", "foodPieRedA": "isang hiwa ng Pulang Cherry Pie", "foodPieRedThe": "ang Pulang Cherry Pie", diff --git a/website/common/locales/fil/front.json b/website/common/locales/fil/front.json index 4ccd8424b0..ddbc6bc224 100644 --- a/website/common/locales/fil/front.json +++ b/website/common/locales/fil/front.json @@ -1,5 +1,5 @@ { - "FAQ": "FAQ", + "FAQ": "MKN (Mga Kadalasang Naitátanong)", "marketing1Lead1Title": "Buhay Mo, ang Role Playing Game", "marketing1Header": "Pabutihin ang Iyong mga Gawi sa pamamagitan ng Paglalaro", "logout": "Log Out", @@ -23,10 +23,10 @@ "companyAbout": "Paano Gumagana", "communityInstagram": "Instagram", "communityFacebook": "Facebook", - "communityExtensions": "Add-ons & Extensions", - "clearBrowserData": "Alisin ang Browser Data", + "communityExtensions": "Mga Pandagdág-Gamit at Pámpalawig-Gamit", + "clearBrowserData": "Burahín ang Alaala ng Tagahanap", "chores": "Mga Gawaing-Bahay", - "termsAndAgreement": "Sa pagpindot ng button sa ibaba, ipinapahiwatig mong nabasa at sumasang-ayon ka sa Terms of Service at Privacy Policy.", + "termsAndAgreement": "Sa pagpindót sa ibabà, ipinápahiwatig n'yo na nabasa at sumasang-ayon kayó sa Mga Alituntúnin sa Paglílingkód at Pátakarán sa Paglilihim.", "marketing3Lead1": "Ang **iPhone & Android** apps ay nakatutulong sa'yo upang makatapos ng gawain kahit saan. Napagtanto namin na ang pag-login gamit ang website ay nakakapagod.", "marketing3Header": "Apps at Extensions", "marketing2Lead3": "Isang uri ng pakikipagkumpitensya sa iyong mga kaibigan at sa mga hindi kakilala ang mga Hamon. Kung sino ang pinakamagaling sa pagtapos ng bawat hamon ay makatatanggap ng mga espesyal na premyo.", diff --git a/website/common/locales/fil/loginincentives.json b/website/common/locales/fil/loginincentives.json index 5fcb019d1b..d1b562965c 100755 --- a/website/common/locales/fil/loginincentives.json +++ b/website/common/locales/fil/loginincentives.json @@ -15,8 +15,8 @@ "oneOfAllHatchingPotions": "tig-íisá ng bawat pangkaraniwang Mahiwagang Langís na Pampápapisâ", "threeOfEachFood": "tigtátatló ng bawat pángkaraniwang Pagkaing Pang-alagà", "fourOfEachFood": "tíg-aapat ng bawat pángkaraniwang Pagkaing Pang-alagà", - "twoSaddles": "two Saddles", - "threeSaddles": "three Saddles", + "twoSaddles": "dalawáng Upuán", + "threeSaddles": "tatlóng Upuán", "incentiveAchievement": "ang Kakaibang Kalakihan sa Katapatan na tagumpáy", "royallyLoyal": "Kakaibang Kalakihan sa Katapatan", "royallyLoyalText": "Limandaáng ulit ng nakapuntá ang tagagamit na itó dito, at napagkaloobán na ng bawat Gantimpalà ng maaaring matanggáp ukol sa dalás ng pagpuntá rito!", diff --git a/website/common/locales/fr/gear.json b/website/common/locales/fr/gear.json index 14b54261b1..018861c7f3 100644 --- a/website/common/locales/fr/gear.json +++ b/website/common/locales/fr/gear.json @@ -2782,5 +2782,7 @@ "headSpecialWinter2023HealerNotes": "Ce heaume cardinal est parfait pour siffler et chanter pour annoncer la nouvelle saison. Augmente l'intelligence de <%= int %>. Équipement en édition limitée de l'hiver 2022-2023.", "shieldSpecialWinter2023WarriorText": "Bouclier huitre", "shieldSpecialWinter2023HealerText": "Chansonnette fraiche", - "shieldSpecialWinter2023HealerNotes": "Votre chanson de givre et de neige apaisera les esprits de ceux qui l'entendent. Augmente la constitution de <%= con %>. Équipement en édition limitée de l'hiver 2022-2023." + "shieldSpecialWinter2023HealerNotes": "Votre chanson de givre et de neige apaisera les esprits de ceux qui l'entendent. Augmente la constitution de <%= con %>. Équipement en édition limitée de l'hiver 2022-2023.", + "headSpecialNye2022Text": "Chapeau de fête fantastique", + "headSpecialNye2022Notes": "Vous avez reçu un chapeau de soirée fantastique ! Portez-le avec fierté en levant votre verre à cette nouvelle année ! Ne confère aucun bonus." } diff --git a/website/common/locales/fr/limited.json b/website/common/locales/fr/limited.json index d10f0b7b46..7126967ef2 100644 --- a/website/common/locales/fr/limited.json +++ b/website/common/locales/fr/limited.json @@ -239,5 +239,6 @@ "winter2023WalrusWarriorSet": "Morse (Guerrier)", "winter2023FairyLightsMageSet": "Lumières féeriques (Mage)", "winter2023CardinalHealerSet": "Cardinal (Guérisseur)", - "spring2023RibbonRogueSet": "Ruban (Voleur)" + "spring2023RibbonRogueSet": "Ruban (Voleur)", + "winter2023RibbonRogueSet": "Ruban (Voleur)" } diff --git a/website/common/locales/he/achievements.json b/website/common/locales/he/achievements.json index b351a7244b..6c9d9d76f9 100644 --- a/website/common/locales/he/achievements.json +++ b/website/common/locales/he/achievements.json @@ -95,5 +95,9 @@ "achievementGoodAsGold": "זהב טהור", "achievementBugBonanzaModalText": "השלמת את ההרפתקאות של חיות המחמד של החיפושית, הפרפר, החילזון והעכביש!", "achievementBareNecessities": "רק את הטוב", - "achievementBoneCollector": "אספן עצמות" + "achievementBoneCollector": "אספן עצמות", + "achievementSkeletonCrew": "צוות השלדים", + "achievementBareNecessitiesModalText": "השלמת את המסעות של הקוף, העצלן והעצים הצעירים!", + "achievementBugBonanza": "שיגעון החרקים", + "achievementFreshwaterFriends": "חברים ממים מתוקים" } diff --git a/website/common/locales/he/quests.json b/website/common/locales/he/quests.json index 1e9849beee..c93cc6853a 100644 --- a/website/common/locales/he/quests.json +++ b/website/common/locales/he/quests.json @@ -3,28 +3,28 @@ "quest": "הרפתקה", "petQuests": "הרפתקאות של חיות מחמד וחיות רכיבה", "unlockableQuests": "הרפתקאות שאינן ניתנות לפתיחה", - "goldQuests": "", + "goldQuests": "הרפתקאות מסדרת הרב-אמן", "questDetails": "פרטי הרפתקה", "questDetailsTitle": "פרטי ההרפתקה", - "questDescription": "", + "questDescription": "הרפתקאות מאפשרות לשחקנים להתרכז במטרות ארוכות טווח בתוך המשחק עם חברי החבורה שלהם.", "invitations": "הזמנות", "completed": "הושלם!", - "rewardsAllParticipants": "", - "rewardsQuestOwner": "", + "rewardsAllParticipants": "מזכה את כל המשתתפים של ההרפתקה", + "rewardsQuestOwner": "פרסים נוספים לבעל ההרפתקה", "inviteParty": "הזמינו את החבורה להרפתקה", "questInvitation": "הזמנה להרפתקה:", "questInvitationInfo": "הזמנה להרפתקה <%= quest %> ", - "invitedToQuest": "", + "invitedToQuest": "הוזמנת להרפתקה <%= quest %>", "askLater": "שאלו אחר כך", "buyQuest": "קנו הרפתקה", "accepted": "מוסכם", - "declined": "", + "declined": "סירבו", "rejected": "נדחה", "pending": "ממתין לתשובה", - "questCollection": "", + "questCollection": "+ <%= val %> חפצ(ים) של ההרפתקה נמצאו", "questDamage": "+ <%= val %> damage to boss", "begin": "התחל", - "bossHP": "", + "bossHP": "נק\"פ של הבוס", "bossStrength": "עוצמת האויב", "rage": "זעם", "collect": "לאסוף", @@ -34,8 +34,8 @@ "sureLeave": "לעזוב את ההרפתקה? כל ההתקדמות תרד לטמיון.", "mustComplete": "קודם עליך להשלים את <%= quest %>", "mustLvlQuest": "עליכם להיות בדרגה <%= level %> כדי לקנות את ההרפתקה הזו!", - "unlockByQuesting": "", - "questConfirm": "Are you sure? Only <%= questmembers %> of your <%= totalmembers %> party members have joined this quest! Quests start automatically when all players have joined or rejected the invitation.", + "unlockByQuesting": "כדי לפתוח את ההרפתקה, תשלימו את <%= title %>.", + "questConfirm": "בטוחים שאתם רוצים להתחיל את ההרפתקה? לא כל חברי החבורה אישרו את ההזמנה. הרפתקאות מתחילות באופן אוטומטי אחרי שכל חברי החבורה השיבו להזמנה.", "sureCancel": "האם אתם בטוחים שברצונכם לבטל את ההרפתקה? ביטול ההרפתקה תבטל את כל ההזמנות שנשלחו, גם עבור אלה שהסכימו כבר להשתתף, והמגילה תחזור לרשותכם.", "sureAbort": "האם אתם בטוחים שברצונכם לבטל את ההרפתקה הזו? כל ההתקדמות שלכם תאבד. המגילה תשוב לידי בעליה.", "doubleSureAbort": "האם אתם באמת באמת בטוחים? כל החבורה שלכם עלולה לשנוא אתכם לנצח!", @@ -93,5 +93,10 @@ "selectQuestModal": "נא לבחור הרפתקה", "questAlreadyStartedFriendly": "ההרפתקה כבר החלה, אבל תמיד ניתן להצטרף לבאה אחריה!", "membersParticipating": "<%= accepted %> / <%= invited %> חברים משתתפים", - "chatQuestAborted": "<%= username %> הפסיק את ההרפתקה <%= questName %>." + "chatQuestAborted": "<%= username %> הפסיק את ההרפתקה <%= questName %>.", + "bossDamage": "פגעת בבוס!", + "questInvitationNotificationInfo": "הוזמנת להצטרף להרפתקה", + "questItemsPending": "<%= amount %> חפצים ממתינים", + "hatchingPotionQuests": "הרפתקאות של שיקויי הבקיעה הקסומים", + "sureLeaveInactive": "בטוחים שאתם רוצים לעזוב את ההרפתה? אתם לא תוכלו להשתתף." } diff --git a/website/common/locales/id/backgrounds.json b/website/common/locales/id/backgrounds.json index 2f66ab60a3..96de8b01cf 100644 --- a/website/common/locales/id/backgrounds.json +++ b/website/common/locales/id/backgrounds.json @@ -348,28 +348,28 @@ "backgroundFlyingOverAncientForestNotes": "Terbang di atas pepohonan Hutan Kuno.", "backgrounds052018": "SET 48: Dirilis Mei 2018", "backgroundTerracedRiceFieldText": "Sawah Padi Berteras", - "backgroundTerracedRiceFieldNotes": "Enjoy a Terraced Rice Field in the growing season.", + "backgroundTerracedRiceFieldNotes": "Nikmati Sengkedan Sawah di musim tanam.", "backgroundFantasticalShoeStoreText": "Toko Sepatu Fantastis", - "backgroundFantasticalShoeStoreNotes": "Look for fun new footwear in the Fantastical Shoe Store.", + "backgroundFantasticalShoeStoreNotes": "Cari alas kaki lucu di Toko Sepatu Fantastik.", "backgroundChampionsColosseumText": "Koloseum Jawara", "backgroundChampionsColosseumNotes": "Berjemur di bawah cahaya kejayaan Koloseum Jawara.", "backgrounds062018": "SET 49: Dirilis Juni 2018", "backgroundDocksText": "Dermaga", "backgroundDocksNotes": "Memancing dari atas Dermaga.", - "backgroundRowboatText": "Rowboat", - "backgroundRowboatNotes": "Sing rounds in a Rowboat.", + "backgroundRowboatText": "Perahu Dayung", + "backgroundRowboatNotes": "Bernyanyi di Perahu Dayung.", "backgroundPirateFlagText": "Bendera Bajak Laut", "backgroundPirateFlagNotes": "Kibarkan Bendera Bajak Laut yang menakutkan.", "backgrounds072018": "SET 50: Dirilis Juli 2018", - "backgroundDarkDeepText": "Dark Deep", - "backgroundDarkDeepNotes": "Swim in the Dark Deep among bioluminescent critters.", + "backgroundDarkDeepText": "Gelap Dalam", + "backgroundDarkDeepNotes": "Berenang di Gelap Dalam bersama makhluk-makhluk bercahaya.", "backgroundDilatoryCityText": "Kota Dilatory", - "backgroundDilatoryCityNotes": "Meander through the undersea City of Dilatory.", - "backgroundTidePoolText": "Tide Pool", - "backgroundTidePoolNotes": "Observe the ocean life near a Tide Pool.", + "backgroundDilatoryCityNotes": "Berkelana di kedalaman Kota bawah laut Dilatory.", + "backgroundTidePoolText": "Kolam Ombak", + "backgroundTidePoolNotes": "Mengamati kehidupan laut dari dekat Kolam Ombak.", "backgrounds082018": "SET 51: Dirilis Agustus 2018", - "backgroundTrainingGroundsText": "Training Grounds", - "backgroundTrainingGroundsNotes": "Spar on the Training Grounds.", + "backgroundTrainingGroundsText": "Lapangan Latih", + "backgroundTrainingGroundsNotes": "Bertanding di Lapangan Latih.", "backgroundFlyingOverRockyCanyonText": "Ngarai Berbatu", "backgroundFlyingOverRockyCanyonNotes": "Lihat pemandangan menakjubkan di bawahmu selagi kamu terbang di atas Ngarai Berbatu.", "backgroundBridgeText": "Jembatan", @@ -382,16 +382,16 @@ "backgroundCozyBarnText": "Lumbung Nyaman", "backgroundCozyBarnNotes": "Bersantai bersama peliharaan dan tungganganmu di Lumbung Nyaman mereka.", "backgrounds102018": "SET 53: Dirilis Oktober 2018", - "backgroundBayouText": "Bayou", - "backgroundBayouNotes": "Bask in the fireflies' glow on the misty Bayou.", + "backgroundBayouText": "Rawa", + "backgroundBayouNotes": "Disinari cahaya kunang-kunang di Rawa berkabut.", "backgroundCreepyCastleText": "Kastil Menyeramkan", "backgroundCreepyCastleNotes": "Berani mendekati Kastil Menyeramkan.", "backgroundDungeonText": "Penjara Bawah Tanah", "backgroundDungeonNotes": "Selamatkan para tahanan dari Penjara Bawah Tanah yang menyeramkan.", "backgrounds112018": "SET 54: Dirilis November 2018", - "backgroundBackAlleyText": "Back Alley", - "backgroundBackAlleyNotes": "Look shady loitering in a Back Alley.", - "backgroundGlowingMushroomCaveText": "Glowing Mushroom Cave", + "backgroundBackAlleyText": "Gang Belakang", + "backgroundBackAlleyNotes": "Terlihat mencurigakan berkeliaran di Gang Belakang.", + "backgroundGlowingMushroomCaveText": "Goa Jamur Bercahaya", "backgroundGlowingMushroomCaveNotes": "Stare in awe at a Glowing Mushroom Cave.", "backgroundCozyBedroomText": "Kamar Tidur Nyaman", "backgroundCozyBedroomNotes": "Curl up in a Cozy Bedroom.", @@ -620,7 +620,7 @@ "backgroundClotheslineNotes": "Bersantai sambil mengeringkan pakaian di Jemuran.", "backgroundClotheslineText": "Jemuran", "backgrounds062021": "SET 85: Dirilis Juni 2021", - "backgroundWindmillsNotes": ".Pasang pelana dan berjalan di Kincir Angin.", + "backgroundWindmillsNotes": "Pasang pelana dan berjalan di Kincir Angin.", "backgroundWindmillsText": "Kincir Angin", "backgroundDragonsLairNotes": "Mencoba untuk tidak mengusik Sarang Naga.", "backgroundDragonsLairText": "Sarang Naga", @@ -639,5 +639,17 @@ "backgroundVineyardNotes": "Telusuri Kebun Anggur yang berlimpah ruah.", "backgroundAutumnLakeshoreText": "Tepi Danau Musim Gugur", "backgrounds092021": "SET 88: Dirilis September 2021", - "backgroundVineyardText": "Kebun Anggur" + "backgroundVineyardText": "Kebun Anggur", + "backgroundWinterWaterfallText": "Air Terjun Musim Dingin", + "backgroundWinterWaterfallNotes": "Mengagumi Air Terjun Musim Dingin.", + "backgroundOrangeGroveText": "Belukar Jingga", + "backgroundOrangeGroveNotes": "Mengembara ke wangi Belukar Jingga.", + "backgrounds102021": "SET 89: Dirilis Oktober 2021", + "hideLockedBackgrounds": "Sembunyikan latar belakang yang masih terkunci", + "backgroundCrypticCandlesNotes": "Panggil pasukan sihir rahasia di antara Lilin-Lilin Gaib.", + "backgroundHauntedPhotoText": "Foto Berhantu", + "backgroundHauntedPhotoNotes": "Temukan dirimu terperangkap di dunia monokromik dalam Foto Berhantu.", + "backgroundUndeadHandsText": "Tangan Mayat Hidup", + "backgroundUndeadHandsNotes": "Mencoba kabur dari genggaman Tangan Mayat Hidup.", + "backgroundCrypticCandlesText": "Lilin Gaib" } diff --git a/website/common/locales/id/character.json b/website/common/locales/id/character.json index 2f4288220e..1fbfa98d82 100644 --- a/website/common/locales/id/character.json +++ b/website/common/locales/id/character.json @@ -1,5 +1,5 @@ { - "communityGuidelinesWarning": "Ingatlah bahwa Nama Tampilan, foto profil, dan celotehmu harus mengikuti Pedoman Komunitas (misalnya, tidak menggunakan bahasa senonoh, topik dewasa, penghinaan, dsb). Jika kamu mempunyai pertanyaan apapun mengenai pantasnya sesuatu hal, silahkan email <%= hrefBlankCommunityManagerEmail %>!", + "communityGuidelinesWarning": "Ingatlah bahwa Nama Tampilan, foto profil, dan celotehmu harus mengikuti Pedoman Komunitas (misalnya, tidak menggunakan bahasa tidak senonoh, topik dewasa, penghinaan, dsb). Jika kamu mempunyai pertanyaan apapun mengenai pantasnya sesuatu hal, silahkan email <%= hrefBlankCommunityManagerEmail %>!", "profile": "Profil", "avatar": "Ubah Tampilan Avatar", "editAvatar": "Sunting Avatar", @@ -15,7 +15,7 @@ "imageUrl": "URL Gambar", "inventory": "Inventori", "social": "Sosial", - "lvl": "Lvl", + "lvl": "Level", "buffed": "Mendapat Buff", "bodyBody": "Tubuh", "size": "Ukuran", @@ -37,7 +37,7 @@ "mustache": "Kumis", "flower": "Bunga", "accent": "Aksen", - "headband": "Headband", + "headband": "Ikat kepala", "wheelchair": "Kursi Roda", "extra": "Ekstra", "rainbowSkins": "Kulit Warna-Warni", @@ -62,10 +62,10 @@ "autoEquipPopoverText": "Pilih ini jika kamu ingin langsung memakai perlengkapan setelah membelinya.", "costumeDisabled": "Kamu telah menonaktifkan kostummu.", "gearAchievement": "Kamu mendapat lencana \"Ultimate Gear\" karena sudah mendapat semua perlengkapan sesuai dengan pekerjaanmu! Kamu sudah melengkapi perlengkapan berikut:", - "gearAchievementNotification": "You have earned the \"Ultimate Gear\" Achievement for upgrading to the maximum gear set for a class!", + "gearAchievementNotification": "Kamu telah memperoleh Pencapaian \"Perlengkapan Terakhir\" karena telah meng-upgrade semua perlengkapan sampai maksimal untuk suatu pekerjaan!", "moreGearAchievements": "Untuk mendapatkan lebih banyak lencana Ultimate Gear, ubah pekerjaanmu di Pengaturan > Laman Situs dan beli perlengkapan untuk pekerjaan barumu!", "armoireUnlocked": "Kalau ingin lebih banyak perlengkapan, coba cek Peti Ajaib! Klik Hadiah Peti Ajaib untuk kesempatan mendapatkan Perlengkapan spesial! Kamu juga bisa mendapatkan pengalaman atau makanan.", - "ultimGearName": "Ultimate Gear - <%= ultClass %>", + "ultimGearName": "Perlengkapan Terakhir - <%= ultClass %>", "ultimGearText": "Telah meng-upgrade set senjata dan pakaian sampai maksimal untuk pekerjaan <%= ultClass %>.", "level": "Level", "levelUp": "Naik Level!", @@ -85,7 +85,7 @@ "allocatePerPop": "Tambahkan satu poin kepada Persepsi", "allocateInt": "Poin yang diberikan untuk Kecerdasan:", "allocateIntPop": "Tambahkan satu poin kepada Kecerdasan", - "noMoreAllocate": "Sekarang kamu sudah mencapai level 100, kamu tidak akan mendapat poin atribut lagi. Kamu bisa terus menaikkan level, atau mulai petualangan baru dari level 1 dengan menggunakan Orb of Rebirth!", + "noMoreAllocate": "Sekarang kamu sudah mencapai level 100, kamu tidak akan mendapat poin atribut lagi. Kamu bisa terus menaikkan level, atau mulai petualangan baru dari level 1 dengan menggunakan Orb of Rebirth!", "stats": "Atribut", "achievs": "Pencapaian", "strength": "Kekuatan", @@ -164,7 +164,7 @@ "per": "PER", "int": "KEC", "notEnoughAttrPoints": "Kamu tidak memiliki cukup Poin Atribut.", - "classNotSelected": "You must select Class before you can assign Stat Points.", + "classNotSelected": "Kamu harus memilih Pekerjaan sebelum bisa menempatkan Poin Status.", "style": "Gaya", "facialhair": "Wajah", "photo": "Foto", @@ -174,7 +174,7 @@ "latestCheckin": "Check In Terakhir", "editProfile": "Edit Profil", "challengesWon": "Tantangan yang Dimenangi", - "questsCompleted": "Misi yang Diselesaikan", + "questsCompleted": "Misi yang diselesaikan", "headAccess": "Hiasan Kepala.", "backAccess": "Hiasan Punggung.", "bodyAccess": "Hiasan Tubuh.", diff --git a/website/common/locales/id/communityguidelines.json b/website/common/locales/id/communityguidelines.json index 49e142d3b2..d90b6ab0b6 100644 --- a/website/common/locales/id/communityguidelines.json +++ b/website/common/locales/id/communityguidelines.json @@ -8,13 +8,13 @@ "commGuideHeadingInteractions": "Interactions in Habitica", "commGuidePara015": "Habitica mempunyai dua jenis ruang sosial: umum, dan pribadi. Ruang umum meliputi Kedai Minuman, Guild Umum, GitHub, Trello, dan Wiki. Sedangkan ruang pribadi meliputi Guild Pribadi, obrolan Party, dan Pesan Pribadi. Semua Nama Tampilan dan @namapengguna harus menyesuaikan dengan pedoman ruang publik. Untuk mengganti Nama Tampilanmu dan/atau @namapengguna, pada app mobile pergi ke Menu > Pengaturan > Profil. Pada situs, pergi ke Pengguna > Pengaturan.", "commGuidePara016": "Ketika menjelajahi ruang publik di Habitica, terdapat beberapa peraturan umum untuk memastikan semua orang tetap bahagia dan nyaman.", - "commGuideList02A": "Hormati satu sama lain. Bersikaplah sopan, baik, ramah, dan suka membantu. Ingat: Para Habitican datang dari segala latar belakang dan punya pengalaman yang berbeda satu dengan yang lain. Inilah yang membuat Habitica sangat digemari! Membangun komunitas yang berarti saling menghormati dan merayakan segala perbedaan dan kesamaan kita semua.", + "commGuideList02A": "Hormati satu sama lain. Bersikaplah sopan, baik, ramah, dan suka membantu. Ingat: Para Habitican datang dari segala latar belakang dan punya pengalaman yang berbeda satu dengan yang lain. Inilah yang membuat Habitica sangat digemari! Membangun komunitas yang berarti saling menghormati dan merayakan segala perbedaan dan kesamaan kita semua.", "commGuideList02B": "Patuhi semuaSyarat dan Ketentuan baik di ruang publik maupun pribadi.", "commGuideList02C": "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.", "commGuideList02D": "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.", "commGuideList02E": "Avoid profanity. This includes milder, religious-based oaths that may be acceptable elsewhere. We have people from all religious and cultural backgrounds, and we want to make sure that all of them feel comfortable in public spaces. If a moderator or staff member tells you that a term is disallowed on Habitica, even if it is a term that you did not realize was problematic, that decision is final. Additionally, slurs will be dealt with very severely, as they are also a violation of the Terms of Service.", "commGuideList02F": "Avoid extended discussions of divisive topics in the Tavern and where it would be off-topic. If you feel that someone has said something rude or hurtful, do not engage them. If someone mentions something that is allowed by the guidelines but which is hurtful to you, it’s okay to politely let someone know that. If it is against the guidelines or the Terms of Service, you should flag it and let a mod respond. When in doubt, flag the post.", - "commGuideList02G": "Patuhi segera segala permintaan Moderator. Ini juga termasuk, tetapi tidak terbatas pada, memintamu untuk membatasi jumlah postinganmu di ruang tertentu, menghapus konten yang tidak sesuai dari profil, memindahkan diskusimu ke tempat yang lebih sesuai, dll. Jangan berdebat dengan moderator. Jika kamu mempunyai masalah atau pendapat terkait cara bersikap, kirimkan email pada admin@habitica.comuntuk ditindaklanjuti oleh manager komunitas kami.", + "commGuideList02G": "Patuhi segera segala permintaan Moderator. Ini juga termasuk, tetapi tidak terbatas pada, memintamu untuk membatasi jumlah postinganmu di ruang tertentu, menghapus konten yang tidak sesuai dari profil, memindahkan diskusimu ke tempat yang lebih sesuai, dll. Jangan berdebat dengan moderator. Jika kamu mempunyai masalah atau pendapat terkait cara bersikap, kirimkan email pada admin@habitica.comuntuk ditindaklanjuti oleh manager komunitas kami.", "commGuideList02J": "Do not spam. Spamming may include, but is not limited to: posting the same comment or query in multiple places, posting links without explanation or context, posting nonsensical messages, posting multiple promotional messages about a Guild, Party or Challenge, or posting many messages in a row. Asking for gems or a subscription in any of the chat spaces or via Private Message is also considered spamming. If people clicking on a link will result in any benefit to you, you need to disclose that in the text of your message or that will also be considered spam.

It is up to the mods to decide if something constitutes spam or might lead to spam, even if you don’t feel that you have been spamming. For example, advertising a Guild is acceptable once or twice, but multiple posts in one day would probably constitute spam, no matter how useful the Guild is!", "commGuideList02K": "Avoid posting large header text in the public chat spaces, particularly the Tavern. Much like ALL CAPS, it reads as if you were yelling, and interferes with the comfortable atmosphere.", "commGuideList02L": "Kami sangat tidak menyarankan pertukaran informasi pribadi -- khususnya informasi yang dapat digunakan untuk mengidentifikasimu -- di ruang obrolan umum. Informasi mengidentifikasi dapat termasuk, namun tidak terbatas pada: alamat kamu, alamat email kamu, dan token API/passwordmu. Ini untuk keamananmu! Staf ataupun moderator bisa menghapus postingan sesuai kebijakan mereka. Jika kamu diminta untuk memberi informasi pribadi di dalam sebuah Guild tertutup (?), Party, atau PM, kami sangat menyarankan kamu menolak dengan sopan kemudian menyiagakan staf dan moderator dengan 1) menandakan pesan jika di dalam sebuah Party atau Guild tertutup, atau 2) mengisi Kontak Form Moderator dengan tangkapan layar.", @@ -109,7 +109,7 @@ "commGuidePara013": "In a community as big as Habitica, users come and go, and sometimes a staff member or moderator needs to lay down their noble mantle and relax. The following are Staff and Moderators Emeritus. They no longer act with the power of a Staff member or Moderator, but we would still like to honor their work!", "commGuidePara014": "Staff and Moderators Emeritus:", "commGuideHeadingFinal": "Bagian Terakhir", - "commGuidePara067": "Maka terimalah ini, wahai Habitican pemberani -- Pedoman Komunitas! Hapus penat keringat di keningmu dan hadiahkan dirimu XP karna telah membaca ini semua. jika kamu masih memiliki pertanyaan mengenai Pedoman Komunitas ini, mohon beritahu kami melalui Formulir Kontak Moderatordan kami akan dengan senang hati membantumu.", + "commGuidePara067": "Maka terimalah ini, wahai Habitican pemberani -- Pedoman Komunitas! Hapus penat keringat di keningmu dan hadiahkan dirimu XP karna telah membaca ini semua. jika kamu masih memiliki pertanyaan mengenai Pedoman Komunitas ini, mohon beritahu kami melalui admin@habitica.com dan kami akan dengan senang hati membantumu.", "commGuidePara068": "Sekarang majulah, pengembara yang berani, dan kalahkan tugas-tugas itu!", "commGuideHeadingLinks": "Link yang Berguna", "commGuideLink01": "Habitica Help: Ask a Question: a Guild for users to ask questions!", diff --git a/website/common/locales/id/contrib.json b/website/common/locales/id/contrib.json index 463179e574..843157fdba 100644 --- a/website/common/locales/id/contrib.json +++ b/website/common/locales/id/contrib.json @@ -49,9 +49,10 @@ "balance": "Saldo", "playerTiers": "Tingkatan Pemain", "tier": "Tingkat", - "conRewardsURL": "http://habitica.fandom.com/wiki/Contributor_Rewards", + "conRewardsURL": "https://habitica.fandom.com/wiki/Contributor_Rewards", "surveysSingle": "Membantu Habitica berkembang, baik dengan mengisi survey atau membantu dalam pengujian utama. Terima kasih!", "surveysMultiple": "Membantu Habitica berkembang sebanyak <%= count %> kali, baik dengan mengisi survey atau membantu dalam pengujian utama. Terima kasih!", "blurbHallPatrons": "Ini adalah Aula Patron, di mana kami memberi penghormatan kepada para petualang pemberani yang telah membantu Kickstarter asli Habitica. Kami berterima kasih kepada mereka yang membantu kami mewujudkan Habitica menjadi kenyataan!", - "blurbHallContributors": "Ini adalah Aula para Kontributor, di mana kontributor open-source Habitica dicantumkan. Baik melalui kode, seni, musik, tulisan, atau bahkan hanya pertolongan kecil, mereka mendapatkan permata, perlengkapan eksklusif , dan titel kebanggaan . Kamu juga bisa berkontribusi untuk Habitica! Lihat Selengkapnya. " + "blurbHallContributors": "Ini adalah Aula para Kontributor, di mana kontributor open-source Habitica dicantumkan. Baik melalui kode, seni, musik, tulisan, atau bahkan hanya pertolongan kecil, mereka mendapatkan permata, perlengkapan eksklusif , dan titel kebanggaan . Kamu juga bisa berkontribusi untuk Habitica! Lihat Selengkapnya. ", + "noPrivAccess": "Kamu tidak memiliki hak yang diperlukan." } diff --git a/website/common/locales/id/settings.json b/website/common/locales/id/settings.json index 8805134ef9..d829fe679d 100644 --- a/website/common/locales/id/settings.json +++ b/website/common/locales/id/settings.json @@ -42,7 +42,7 @@ "sureChangeCustomDayStartTime": "Kamu yakin ingin mengatur waktu Awal Harimu? Keseharianmu akan diulang setiap kamu masuk Habitica pertama kali setelah jam <%= time %>. Pastikan kamu menyelesaikan Keseharian sebelum waktu tersebut!", "customDayStartHasChanged": "Awal harimu telah diubah.", "nextCron": "Tugas harian kamu akan diatur ulang saat pertama kalinya kamu menggunakan Habitica setelah <%= time %>. Pastikan kamu sudah menyelesaikan tugas harian kamu sebelum waktu tersebut!", - "customDayStartInfo1": "Habitica memeriksa dan mengatur ulang tugas harian kamu di tengah malam di zona waktumu setiap hari. Kamu bisa mengganti waktu tersebut disini.", + "customDayStartInfo1": "Habitica memeriksa dan mengatur ulang tugas harian kamu di tengah malam pada zona waktumu setiap hari. Kamu bisa menyesuaikan kalau itu terjadi melewati waktu default sini.", "misc": "Lain-lain", "showHeader": "Perlihatkan Header", "changePass": "Ubah Kata Sandi", @@ -55,7 +55,7 @@ "newUsername": "Nama Pengguna Baru", "dangerZone": "Zona Berbahaya", "resetText1": "PERINGATAN! Ini me-reset banyak hal dari akunmu. Hal ini sangat tidak disarankan, tetapi bagi beberapa orang ini berguna di awal setelah mencoba bermain di situs ini dalam waktu yang singkat.", - "resetText2": "Kamu akan kehilangan semua level, Koin Emasmu, dan poin Pengalamanmu. Semua tugasmu (kecuali tugas dari tantangan) akan dihapus selamanya dan kamu akan kehilangan data riwayat mereka. Kamu akan kehilangan semua perlengkapanmu tapi kamu masih bisa membelinya lagi, termasuk semua perlengkapan edisi terbatas atau item pelanggan Misteri yang sudah kamu miliki (kamu harus mengambil pekerjaan yang sesuai untuk membeli ulang perlengkapan khusus pekerjaan). Kamu akan tetap memiliki pekerjaanmu yang sekarang serta peliharaan dan tungganganmu. Kamu mungkin lebih memilih untuk menggunakan Batu Kelahiran, yang merupakan pilihan lebih aman yang akan mempertahankan tugas-tugas dan perlengkapanmu.", + "resetText2": "Kamu akan kehilangan semua level, Koin Emas, dan poin Pengalamanmu. Semua tugas (kecuali tugas dari tantangan) akan dihapus selamanya dan kamu akan kehilangan data riwayat mereka. Kamu akan kehilangan semua perlengkapanmu, kecuali Item Misteri Berlangganan dan item perayaan yang diberikan gratisan. Kamu masih bisa membeli item-item yang terhapus itu lagi, termasuk semua perlengkapan edisi terbatas atau item pelanggan Misteri yang sudah kamu miliki (kamu harus mengambil pekerjaan yang sesuai untuk membeli ulang perlengkapan khusus pekerjaan). Kamu akan tetap memiliki pekerjaanmu yang sekarang, pencapaianmu, peliharaan, serta tungganganmu. Kamu mungkin lebih memilih untuk menggunakan Batu Kelahiran, yang merupakan pilihan lebih aman yang akan mempertahankan tugas-tugas dan perlengkapanmu.", "deleteLocalAccountText": "Apakah kamu yakin? Pilihan ini akan menghapus akun selamanya, dan tidak akan dapat dikembalikan! Kamu harus mendaftar menggunakan akun yang baru untuk dapat kembali menggunakan Habitica. Permata yang telah dibeli atau telah disimpan tidak akan dikembalikan. Jika kamu benar-benar yakin, ketikkan kata sandi pada kotak teks di bawah ini.", "deleteSocialAccountText": "Apakah kamu yakin? Ini akan menghapus akunmu untuk selamanya, dan tidak akan bisa dikembalikan! Kamu perlu mendaftar akun baru untuk menggunakan Habitica lagi. Permata yang disimpan di Bank atau telah digunakan tidak akan dikembalikan. Jika kamu betul-betul yakin, ketik \"<%= magicWord %>\" ke dalam kotak teks di bawah.", "API": "API", @@ -71,7 +71,7 @@ "beeminderDesc": "Mengizinkan Beeminder memonitor To Do Habitica kamu secara otomatis. Kamu dapat memutuskan untuk mengatur jumlah target To Do yang selesai per hari atau per minggu, atau kamu dapat memutuskan untuk mengurangi secara berkala jumlah sisa To-Do yang belum selesai. (Arti \"memutuskan\" dalam Beeminder yakni dorongan untuk membayar sejumlah uang sungguhan! Tetapi mungkin saja kamu juga menyukai grafik Beeminder yang menarik.)", "chromeChatExtension": "Ekstensi Obrolan Chrome", "chromeChatExtensionDesc": "Ekstensi Obrolan Chrome untuk Habitica menambah kotak obrolan intuitif ke semua laman habitica.com. Ekstensi membuat pengguna dapat melakukan obrolan di Kedai Minum, party mereka, dan guild yang mereka ikuti.", - "otherExtensions": "Ekstensi Lainnya", + "otherExtensions": "Ekstensi Lainnya", "otherDesc": "Temukan aplikasi, ekstensi, dan peralatan lainnya dalam Habitica wiki.", "resetDo": "Lakukan, reset akun!", "resetComplete": "Reset selesai!", @@ -180,7 +180,7 @@ "subscriptionReminders": "Pengingat Berlangganan", "giftedSubscriptionWinterPromo": "Halo <%= username %>, kamu mendapatkan<%= monthCount %> bulan berlangganan sebagai hadiah dari promosi holiday gift-giving kami!", "newPMNotificationTitle": "Pesan Baru dari <%= name %>", - "chatExtensionDesc": "Chat Extension for Habitica menambahkan kotak percakapan intuitif di seluruh habitica.com. Dengan ini, pengguna dapat melakukan chat di Kedai Minum, party, dan di guild manapun mereka bergabung.", + "chatExtensionDesc": "Chat Extension for Habitica menambahkan kotak percakapan intuitif di seluruh habitica.com. Dengan ini, pengguna dapat melakukan chat di Kedai Minum, party, dan di guild manapun mereka bergabung.", "chatExtension": "Ekstensi Obrolan Chrome and Ekstensi Obrolan Firefox", "resetAccount": "Reset Akun", "bannedSlurUsedInProfile": "Kata pada Nama Penggunamu atau Tentang Diri mengandung bahasa kasar, dan hakmu untuk mengobrol telah dicabut.", @@ -213,5 +213,17 @@ "transaction_buy_money": "Dibeli dengan uang", "transaction_buy_gold": "Dibeli dengan koin emas", "transaction_gift_receive": "Diterima dari", - "transaction_admin_update_balance": "Admin diberikan" + "transaction_admin_update_balance": "Admin diberikan", + "passwordIssueLength": "Sandi harus terdiri dari 8 sampai 64 karakter.", + "timestamp": "Cap waktu", + "nextHourglassDescription": "Pelanggan menerima Jam Pasir Mistik pada\ntiga hari pertama dalam satu bulan.", + "transaction_change_class": "Perubahan Pekerjaan", + "transaction_create_challenge": "Telah membuat tantangan", + "transaction_create_bank_challenge": "Telah membuat bank tantangan", + "transaction_create_guild": "Telah membuat guild", + "transaction_rebirth": "Telah menggunakan Batu Kelahiran", + "transaction_release_pets": "Telah melepaskan peliharaan", + "transaction_release_mounts": "Telah melepaskan tunggangan", + "transaction_reroll": "Telah menggunakan Ramuan Penguat", + "transaction_subscription_perks": "Keuntungan berlangganan" } diff --git a/website/common/locales/it/gear.json b/website/common/locales/it/gear.json index 8e3d0bb49e..efd8760836 100644 --- a/website/common/locales/it/gear.json +++ b/website/common/locales/it/gear.json @@ -2782,5 +2782,7 @@ "headSpecialWinter2023HealerText": "Elmo del Cardinale Rosso", "headSpecialWinter2023HealerNotes": "Questo elmo da cardinale rosso è perfetto per fischiettare e cantare annunciando la stagione invernale. Aumenta l'Intelligenza di <%= int %>. Equipaggiamento in Edizione Limitata, Inverno 2022-2023.", "shieldSpecialWinter2023HealerText": "Fresche Composizioni Musicali", - "shieldSpecialWinter2023HealerNotes": "Il tuo canto di gelo e neve calmerà gli animi di tutti coloro che lo ascolteranno. Aumenta la Costituzione di <%= con %>. Equipaggiamento in Edizione Limitata, Inverno 2022-2023." + "shieldSpecialWinter2023HealerNotes": "Il tuo canto di gelo e neve calmerà gli animi di tutti coloro che lo ascolteranno. Aumenta la Costituzione di <%= con %>. Equipaggiamento in Edizione Limitata, Inverno 2022-2023.", + "headSpecialNye2022Notes": "Hai ricevuto un Cappello da Festa Fantastico! Indossalo con orgoglio mentre inauguri il Nuovo Anno! Non conferisce alcun bonus.", + "headSpecialNye2022Text": "Cappello da Festa Fantastico" } diff --git a/website/common/locales/it/limited.json b/website/common/locales/it/limited.json index f334cb6d1a..d13138e370 100644 --- a/website/common/locales/it/limited.json +++ b/website/common/locales/it/limited.json @@ -238,5 +238,6 @@ "winter2023WalrusWarriorSet": "Tricheco (Guerriero)", "winter2023FairyLightsMageSet": "Luci Fatate (Mago)", "winter2023CardinalHealerSet": "Cardinale Rosso (Guaritore)", - "spring2023RibbonRogueSet": "Fiocco (Ladro)" + "spring2023RibbonRogueSet": "Fiocco (Ladro)", + "winter2023RibbonRogueSet": "Fiocco (Ladro)" } diff --git a/website/common/locales/it/subscriber.json b/website/common/locales/it/subscriber.json index 6dedc0932e..71123beeb5 100644 --- a/website/common/locales/it/subscriber.json +++ b/website/common/locales/it/subscriber.json @@ -186,7 +186,7 @@ "dropCapReached": "Hai trovato tutti gli oggetti per oggi!", "mysterySet202011": "Set del Mago Foglioso", "mysterySet202012": "Set Fenice del Fuoco Ghiacciato", - "mysterySet202101": "Set sciccoso Leopardo delle Nevi", + "mysterySet202101": "Set del Leopardo delle Nevi Sciccoso", "mysterySet202102": "Set del campione ammaliante", "mysterySet202103": "Set dell'osservatore di fiori", "mysterySet202104": "Set del Guardiano cardo", diff --git a/website/common/locales/ja/achievements.json b/website/common/locales/ja/achievements.json index 7d097cde52..ba684db38b 100644 --- a/website/common/locales/ja/achievements.json +++ b/website/common/locales/ja/achievements.json @@ -140,6 +140,9 @@ "achievementWoodlandWizardModalText": "森のペットを全部集めました!", "achievementWoodlandWizardText": "森の生き物――アナグマ、クマ、鹿、狐、カエル、ハリネズミ、フクロウ、カタツムリ、リス、木人を、すべての基本色で孵化させました!", "achievementBoneToPick": "骨の髄まで", - "achievementBoneToPickText": "全ての基本のペットとクエストのペットを、骨の薬で孵化させました!", - "achievementBoneToPickModalText": "骨の薬で孵化した全ての基本のペットとクエストのペットを集めました!" + "achievementBoneToPickText": "全ての骨の基本のペットとクエストのペットを孵化させました!", + "achievementBoneToPickModalText": "全ての骨の基本のペットとクエストのペットを集めました!", + "achievementPolarPro": "極地のプロ", + "achievementPolarProText": "全ての極地のペットを孵化させました:くま・狐・ペンギン・クジラ・狼!", + "achievementPolarProModalText": "極地のペットをすべて集めました!" } diff --git a/website/common/locales/ja/backgrounds.json b/website/common/locales/ja/backgrounds.json index f42bdf200e..96e6a32575 100644 --- a/website/common/locales/ja/backgrounds.json +++ b/website/common/locales/ja/backgrounds.json @@ -742,5 +742,12 @@ "backgroundAmongGiantMushroomsNotes": "巨大なマッシュルームに驚きましょう。", "backgroundMistyAutumnForestText": "霧深い秋の森", "backgroundAutumnBridgeText": "秋の橋", - "backgroundAutumnBridgeNotes": "秋の橋の美しさに感服しましょう。" + "backgroundAutumnBridgeNotes": "秋の橋の美しさに感服しましょう。", + "backgrounds122022": "セット103:2022年12月リリース", + "backgroundBranchesOfAHolidayTreeText": "クリスマスツリーの枝", + "backgroundBranchesOfAHolidayTreeNotes": "クリスマスツリーの枝の上で跳んだり跳ねたりしましょう。", + "backgroundInsideACrystalText": "クリスタルのなか", + "backgroundInsideACrystalNotes": "クリスタルのなかから、外の景色を見てみましょう。", + "backgroundSnowyVillageText": "雪の村", + "backgroundSnowyVillageNotes": "雪の村を眺めましょう。" } diff --git a/website/common/locales/ja/gear.json b/website/common/locales/ja/gear.json index 50116176c9..bd35026a0d 100644 --- a/website/common/locales/ja/gear.json +++ b/website/common/locales/ja/gear.json @@ -2740,5 +2740,49 @@ "weaponArmoireMagicSpatulaNotes": "食べ物が空中で飛んだりひっくり返ったりするのをご覧あれ。魔法で食べ物が3 回ひっくり返ってからあなたのへらに戻ってきたら、その日はラッキー。知覚が<%= per %>上がります。ラッキー宝箱:台所用品セット(2個中1つ目のアイテム)。", "weaponArmoireMagicSpatulaText": "魔法のへら", "shieldArmoireBubblingCauldronText": "ぶくぶくの大釜", - "shieldArmoireBubblingCauldronNotes": "生産性の秘薬を煮詰めたり、辛いスープを作ったりするのにピッタリの釜です。じつは両者にはあまり違いがなかったりして!体質が<%= con %>上がります。ラッキー宝箱:台所用品セット(2個中1つ目のアイテム)。" + "shieldArmoireBubblingCauldronNotes": "生産性の秘薬を煮詰めたり、辛いスープを作ったりするのにピッタリの釜です。じつは両者にはあまり違いがなかったりして!体質が<%= con %>上がります。ラッキー宝箱:台所用品セット(2個中1つ目のアイテム)。", + "headSpecialNye2022Text": "すてきでたわけたパーティーハット", + "headSpecialNye2022Notes": "すてきでたわけたパーティハットをもらいました! 新年を告げる鐘を聞きながら、誇りをもってかぶりましょう! 効果なし。", + "weaponSpecialWinter2023RogueText": "緑色のサテンのサッシュ", + "weaponSpecialWinter2023RogueNotes": "盗賊には罠を張って敵の武器を奪い、そのアイテムをかわいくして返す習性があるという伝説があります。力が<%= str %>上がります。2022年-2023年冬の限定装備。", + "weaponSpecialWinter2023WarriorText": "牙の槍", + "weaponSpecialWinter2023WarriorNotes": "この槍についた2つのとんがりは形がセイウチの牙に似ていますが、二倍はパワフルです。疑いやお馬鹿な詩が逃げ出すまで突きつけましょう。力が<%= str %>上がります。2022年-2023年冬の限定装備。", + "weaponMystery202212Text": "氷のワンド", + "weaponMystery202212Notes": "このワンドについているまばゆい雪の結晶は極寒の冬の夜でも心を温める力を秘めています。効果なし。2022年12月の有料会員アイテム。", + "weaponSpecialWinter2023MageText": "狐火", + "weaponSpecialWinter2023MageNotes": "狐でも炎でもありません。でもとってもおめでたい!知能が<%= int %>、知覚が<%= per %>上がります。2022年-2023年冬の限定装備。", + "weaponSpecialWinter2023HealerText": "投げリース", + "weaponSpecialWinter2023HealerNotes": "このおめでたいのとげのあるリースが敵や障害物に向かって空中を回転し、次の一投のためにブーメランのように戻ってくるのを見届けましょう。知能が<%= int %>上がります。2022年-2023年冬の限定装備。", + "weaponArmoireFinelyCutGemText": "繊細なカットがほどこされた宝石", + "weaponArmoireFinelyCutGemNotes": "よく見つけ出しましたね!この見事で精密なカットの宝石は自慢のコレクションとなるでしょう。そして、あなたが待ち望んだ特別な魔法がかかっているかも。体質が<%= con %>上がります。2022年-2023年冬の限定装備。", + "armorSpecialWinter2023RogueNotes": "アイテムを入手。それをきれいな紙に包む。そして地元の盗賊に贈りましょう!知覚が<%= per %>上がります。2022年-2023年冬の限定装備。", + "armorSpecialWinter2023WarriorNotes": "このタフなセイウチのスーツは、真夜中のビーチを散歩するのにぴったりです。体質が<%= con %>上がります。2022年-2023年冬の限定装備。", + "armorSpecialWinter2023RogueText": "リボンラッピング", + "armorSpecialWinter2023WarriorText": "セイウチスーツ", + "armorMystery202212Notes": "宇宙は寒くても、このすてきなドレスがあれば、空を飛ぶときも心地よく過ごせます。効果なし。2022年12月の有料会員アイテム。", + "armorArmoireJewelersApronNotes": "この汚れたエプロンは創作意欲が湧いたときにおあつらえ向きです。なによりも、必要なものはすべて入れておける何ダースもの小さなポケットがついています。知能が<%= int %>上がります。ラッキー宝箱:宝石細工師(4個中1つ目のアイテム)。", + "armorSpecialWinter2023MageText": "フェアリーライトガウン", + "armorSpecialWinter2023MageNotes": "イルミネーションがついているからと言って、ツリーになれるわけではありません!……いつかはなれるかも。知能が<%= int %>上がります。2022年-2023年冬の限定装備。", + "armorSpecialWinter2023HealerText": "ショウジョウコウカンチョウのスーツ", + "armorSpecialWinter2023HealerNotes": "まばゆい深紅のスーツはあなたが抱える問題の上空を飛ぶのにピッタリです。体質が<%= con %>上がります。2022年-2023年冬の限定装備。", + "armorArmoireJewelersApronText": "宝石細工師のエプロン", + "armorMystery202212Text": "氷河のドレス", + "headSpecialWinter2023MageNotes": "私の瞳の星の輝きは、あなたが星降る夜の薬でたまごをかえしたからでしょうか?知覚が<%= per %>上がります。2022年-2023年冬の限定装備。", + "headSpecialWinter2023RogueText": "ギフト用ちょう結び", + "headSpecialWinter2023RogueNotes": "人々のあなたの髪の「包みを解き」たくなる誘惑はあなたの避けたりかわしたりの練習のチャンスになります。知覚が<%= per %>上がります。2022年-2023年冬の限定装備。", + "headSpecialWinter2023WarriorText": "セイウチヘルメット", + "headSpecialWinter2023WarriorNotes": "このセイウチヘルメットは友達とおしゃべりしたりおしゃれな食事を一緒に食べるのに最適です。力が<%= str %>上がります。2022年-2023年冬の限定装備。", + "headSpecialWinter2023MageText": "フェアリーライトティアラ", + "headSpecialWinter2023HealerText": "ショウジョウコウカンチョウヘルメット", + "headSpecialWinter2023HealerNotes": "このショウジョウコウカンチョウのヘルメットは、冬の季節を告げるためにさえずったり歌ったりするのにぴったりです。知能が<%= int %>上がります。2022年-2023年冬の限定装備。", + "shieldSpecialWinter2023WarriorText": "オイスターシールド", + "shieldSpecialWinter2023HealerNotes": "あなたの氷と雪の歌は聴く人の魂を癒やすでしょう。体質が<%= con %>上がります。2022年-2023年冬の限定装備。", + "shieldSpecialWinter2023WarriorNotes": "セイウチいわく『さあいろんなことを話し合うときがついにやってきた。オイスターシールドだの――誰かが歌っていた冬のベルの歌だの――あるいはこの盾の真珠の行方の謎――または新年がもたらすもの!』体質が<%= con %>上がります。2022年-2023年冬の限定装備。", + "shieldSpecialWinter2023HealerText": "魂をゆさぶる歌", + "shieldArmoireJewelersPliersNotes": "切って、ねじって、はさんで、それから…。この道具ならあなたがイメージできるものならどんなものでも作成可能です。力が<%= str %>上がります。ラッキー宝箱:宝石細工師(4個中3つ目のアイテム)。", + "shieldArmoireJewelersPliersText": "宝石細工師のペンチ", + "headAccessoryMystery202212Text": "氷のティアラ", + "eyewearArmoireJewelersEyeLoupeNotes": "このルーペは作業中のものを拡大し、細部まで完全に見ることができます。ラッキー宝箱:宝石細工師(4個中2つ目のアイテム)。", + "eyewearArmoireJewelersEyeLoupeText": "宝石鑑定用ルーペ", + "headAccessoryMystery202212Notes": "この金細工のティアラであなたの優しさと思いやりの心を新記録まで高めましょう。効果なし。2022年12月の有料会員アイテム。" } diff --git a/website/common/locales/ja/groups.json b/website/common/locales/ja/groups.json index 3b48e575f0..74ed4ce665 100644 --- a/website/common/locales/ja/groups.json +++ b/website/common/locales/ja/groups.json @@ -314,7 +314,7 @@ "groupManagementControls": "グループのマネジメント・コントロール", "groupManagementControlsDesc": "タスクが本当に完了されたかを確認するためにタスク承認機能を使いましょう。グループメンバーへ任務を共有するためのグループマネージャーを追加し、全てのチームメンバーのためのプライベートなグループチャットを楽しみましょう。", "inGameBenefits": "ゲーム中のメリット", - "inGameBenefitsDesc": "グループメンバーは、限定のツノウサギの乗騎だけでなく、毎月の特別な装備セットや、ゴールドでジェムを買う機能など、有料プランの特典を全て受けられます。", + "inGameBenefitsDesc": "グループメンバーは、限定のジャッカロープの乗騎だけでなく、毎月の特別な装備セットや、ゴールドでジェムを買う機能など、有料プランの特典を全て受けられます。", "inspireYourParty": "パーティーで刺激し合い、一緒に人生をゲーム化しましょう。", "letsMakeAccount": "まずはアカウントを作成しましょう", "nameYourGroup": "次に、あなたのグループの名前をつけましょう", @@ -324,7 +324,7 @@ "gettingStarted": "はじめよう", "congratsOnGroupPlan": "おめでとうございます! あなたの新しいグループが設立されました。こちらにいくつかのよくある質問と答えがあります。", "whatsIncludedGroup": "有料プランに含まれるもの", - "whatsIncludedGroupDesc": "グループのメンバー全員が有料プランの特典をすべて受けられます。毎月の有料会員アイテム、ゴールドでジェムを買う機能、そしてグループプランメンバーシップのユーザー限定のロイヤルパープルのツノウサギの乗騎などです。", + "whatsIncludedGroupDesc": "グループのメンバー全員が有料プランの特典をすべて受けられます。毎月の有料会員アイテム、ゴールドでジェムを買う機能、そしてグループプランメンバーシップのユーザー限定のロイヤルパープルのジャッカロープの乗騎などです。", "howDoesBillingWork": "どのように課金しますか?", "howDoesBillingWorkDesc": "グループリーダーは、月ごとの基準でグループメンバー数に基づいた請求をされます。この料金には、グループリーダーのための $9 (USD) がふくまれ、 さらに各グループメンバーごとに $3 USD が加算されます。 例えば、4人のユーザーのグループは、1人のグループリーダーと3人のグループメンバーで構成されるため、月ごとに $18 USD の費用がかかります。", "howToAssignTask": "どのようにタスクを割り当てますか?", diff --git a/website/common/locales/ja/limited.json b/website/common/locales/ja/limited.json index b97138feeb..44591be0aa 100644 --- a/website/common/locales/ja/limited.json +++ b/website/common/locales/ja/limited.json @@ -188,7 +188,7 @@ "fall2020WraithWarriorSet": "レイス(戦士)", "royalPurpleJackolantern": "ロイヤルパープルのジャック・オ・ランタン", "novemberYYYY": "<%= year %>年11月", - "g1g1Limitations": "このイベントは日本時間で12月16日の22:00時から1月6日10:00時までの期間限定です。プロモーションは他のプレイヤーにギフトを贈った際にのみ適用されます。もしギフトを受け取った人がすでに有料会員の場合、有料会員期間が延長されます。この延長は、現在の有料会員期間を満了もしくはキャンセルした後にのみ適用されます。", + "g1g1Limitations": "このイベントは日本時間で12月15日の22:00から1月9日13:59までの期間限定です。プロモーションは他のプレイヤーにギフトを贈った際にのみ適用されます。もしギフトを受け取った人がすでに有料会員の場合、有料会員期間が延長されます。この延長は、現在の有料会員期間を満了もしくはキャンセルした後にのみ適用されます。", "limitations": "制限事項", "g1g1HowItWorks": "「有料プランをプレゼントする」をタップして、贈る相手のユーザーネームを入力し、プレゼントする有料プランの期間を選択してください。自動的にあなたのアカウントにもプレゼントした分と同じ期間のプランが無料で適用されます。", "howItWorks": "機能説明", @@ -235,5 +235,9 @@ "fall2022KappaRogueSet": "カッパ(盗賊)", "fall2022OrcWarriorSet": "オーク(戦士)", "gemSaleHow": "<%= eventStartMonth %> <%= eventStartOrdinal %> から<%= eventEndOrdinal %>の間、通常どおりジェムを購入するだけで、あなたのアカウントはプロモーション分のジェムを受け取れます。買い物したり、分け合ったり、将来のため貯めておけるジェムが増えました!", - "gemSaleLimitations": "このプロモーションは期間限定のイベント中にのみ適用されます。イベントは米国東部標準時 <%= eventStartMonth %> <%= eventStartOrdinal %> の午前 8:00(12:00 UTC) に開始し、米国東部標準時 <%= eventStartMonth %> <%= eventEndOrdinal %> の午後 8:00 ( 00:00 UTC)に終了します。 割引はジェムを自分で購入する場合にのみ利用できます。" + "gemSaleLimitations": "このプロモーションは期間限定のイベント中にのみ適用されます。イベントは米国東部標準時 <%= eventStartMonth %> <%= eventStartOrdinal %> の午前 8:00(12:00 UTC) に開始し、米国東部標準時 <%= eventStartMonth %> <%= eventEndOrdinal %> の午後 8:00 ( 00:00 UTC)に終了します。 割引はジェムを自分で購入する場合にのみ利用できます。", + "winter2023WalrusWarriorSet": "セイウチ(戦士)", + "winter2023FairyLightsMageSet": "フェアリーライト(魔道士)", + "winter2023CardinalHealerSet": "ショウジョウコウカンチョウ(治療師)", + "winter2023RibbonRogueSet": "リボン(盗賊)" } diff --git a/website/common/locales/ja/pets.json b/website/common/locales/ja/pets.json index 67d89bb263..001e39c697 100644 --- a/website/common/locales/ja/pets.json +++ b/website/common/locales/ja/pets.json @@ -28,7 +28,7 @@ "magicalBee": "不思議なハチ", "hopefulHippogriffPet": "希望に満ちたヒッポグリフ", "hopefulHippogriffMount": "希望に満ちたヒッポグリフ", - "royalPurpleJackalope": "ロイヤルパープルのツノウサギ", + "royalPurpleJackalope": "ロイヤルパープルのジャッカロープ", "invisibleAether": "不可視のエーテル獣", "potion": "<%= potionType %> 薬", "egg": "<%= eggType %>のたまご", diff --git a/website/common/locales/ja/subscriber.json b/website/common/locales/ja/subscriber.json index 1294a5b68c..4f945cb7e6 100644 --- a/website/common/locales/ja/subscriber.json +++ b/website/common/locales/ja/subscriber.json @@ -129,7 +129,7 @@ "subscriptionBenefit1": "商人のAlexanderは、市場でジェムを1つにつき20ゴールドですぐ売ってくれます!", "subscriptionBenefit3": "毎日の落とし物上限を2倍にして、Habiticaでより多くのアイテムを見つけましょう。", "subscriptionBenefit4": "毎月、あなたのアバターを着飾るためのユニークでおしゃれなアイテムです。", - "subscriptionBenefit5": "初めて有料会員になったときは、ロイヤルパープルのツノウサギのペットを受け取れます。", + "subscriptionBenefit5": "初めて有料会員になったときは、ロイヤルパープルのジャッカロープのペットを受け取れます。", "subscriptionBenefit6": "タイムトラベラーの店でアイテムを買うために神秘の砂時計を手に入れましょう!", "purchaseAll": "セットを購入する", "gemsRemaining": "残りのジェム", @@ -213,5 +213,6 @@ "mysterySet202208": "はつらつポニーテールセット", "mysterySet202209": "魔法学者セット", "mysterySet202210": "くちなわセット", - "mysterySet202211": "エレクトロマンサーセット" + "mysterySet202211": "エレクトロマンサーセット", + "mysterySet202212": "氷のガーディアンセット" } diff --git a/website/common/locales/ms/achievements.json b/website/common/locales/ms/achievements.json index 64a0e48daa..0cfdb4abed 100755 --- a/website/common/locales/ms/achievements.json +++ b/website/common/locales/ms/achievements.json @@ -1,10 +1,10 @@ { "achievement": "Pencapaian", - "onwards": "Onwards!", - "levelup": "By accomplishing your real life goals, you leveled up and are now fully healed!", - "reachedLevel": "Anda Telah Mencapai Tahap <%= level %>", - "achievementLostMasterclasser": "Quest Completionist: Masterclasser Series", - "achievementLostMasterclasserText": "Completed all sixteen quests in the Masterclasser Quest Series and solved the mystery of the Lost Masterclasser!", + "onwards": "Seterusnya!", + "levelup": "Dengan mencapai matlamat hidup sebenar anda, kamu meratakan dan kini sembuh sepenuhnya!", + "reachedLevel": "Anda Telah Mencapai Tahap <%= tahap %>", + "achievementLostMasterclasser": "Pencari Penyelesaian: Siri Masterclasser", + "achievementLostMasterclasserText": "Menyelesaikan kesemua enam belas pencarian dalam Siri Pencarian Masterclasser dan menyelesaikan misteri Masterclasser yang Hilang!", "achievementBackToBasics": "Kembali kepada Asas", "viewAchievements": "Lihat Pencapaian", "letsGetStarted": "Mari kita mulakan!", @@ -16,23 +16,131 @@ "achievementBackToBasicsModalText": "Anda sudah mengumpul semua Haiwan Peliharaan Asas!", "hideAchievements": "Menyembui <%= category %>", "showAllAchievements": "Tunjukkan Semua<%= category %>", - "onboardingCompleteDesc": "Anda telah memperoleh 5 pencapaiandan100emas sebagai ganjaran untuk melengkapi senarai ini.", + "onboardingCompleteDesc": "Kamu telah mendapat 5 Pencapaian dan 100 Mata emas kerana melengkapkan senarai.", "earnedAchievement": "Anda telah memperoleh pencapaian!", "gettingStartedDesc": "Mari kita mencipta tugas, menyelesaikannya, dan kemudian memeriksa ganjaran anda. Anda akan mendapat 5 pencapaiandan100 emassetelah anda selesai dengan tugas anda!", "achievementPurchasedEquipmentText": "Sudah membeli peralatan pertama mereka.", - "achievementPurchasedEquipment": "Membeli Peralatan", + "achievementPurchasedEquipment": "Pembelian peralatan", "achievementFedPetText": "Sudah memberi makanan kepada Haiwan Peliharaan pertama mereka.", "achievementFedPet": "Memberi makanan kepada satu Haiwan Peliharaan", - "achievementHatchedPetModalText": "Pergi ke inventori anda dan cuba menggabungkan Potion Penetasan dengan satu telur", + "achievementHatchedPetModalText": "Pergi ke inventori anda dan cuba gabungkan Ramuan menetas dan Telur", "achievementHatchedPetText": "Sudah menetaskan Haiwan Peliharaan pertama mereka.", "achievementHatchedPet": "Menetaskan satu Haiwan Peliharaan", "achievementCompletedTaskModalText": "Semakkan mana-mana tugasan anda untuk mendapat ganjaran", "achievementCompletedTaskText": "Sudah menyelesaikan tugas pertama mereka.", - "achievementCompletedTask": "Selesaikan Satu Tugas", + "achievementCompletedTask": "Selesaikan tugasan", "achievementCreatedTaskModalText": "Tambahkan tugas untuk sesuatu yang anda ingin mencapai dalam minggu ini", "achievementCreatedTaskText": "Sudah mencipta tugas pertama mereka.", - "achievementCreatedTask": "Mencipta satu Tugas", + "achievementCreatedTask": "Buat tugas pertama anda", "achievementMonsterMagusModalText": "Anda sudah mengumpul semua Haiwan Peliharaan Zombie!", "achievementMonsterMagusText": "Sudah mengumpul semua Haiwan Peliharaan Zombie.", - "achievementPartyOn": "Kumpulan anda berkembang menjadi 4 orang ahli!" + "achievementPartyOn": "Kumpulan anda berkembang menjadi 4 orang ahli!", + "onboardingProgress": "<%= Peratusan %>% Kemajuan", + "yourRewards": "Ganjaran anda", + "achievementJustAddWaterModalText": "Telah selesai pencarian haiwan peliharaan seperti Sotong, Kuda Laut, Sotong Katak, Ikan Paus, Penyu, Siput Laut, Ular Laut dan Ikan Lumba-Lumba!", + "achievementJustAddWater": "Hanya Tambah Air", + "achievementMindOverMatterModalText": "Anda telah menyelesaikan pencarian haiwan peliharaan Batu, Lendir dan Benang!", + "foundNewItems": "Anda menemui item baharu!", + "achievementMindOverMatter": "Fikiran Daripada Perkara", + "yourProgress": "Kemajuan Anda", + "foundNewItemsCTA": "Pergi ke Inventori anda dan cuba gabungkan ramuan penetasan dan telur baharu anda!", + "achievementVioletsAreBlueModalText": "Anda mengumpul semua Haiwan Peliharaan Haiwan peliharaan gula-gula kapas biru!", + "achievementMindOverMatterText": "Telah menyelesaikan pencarian haiwan peliharaan Batu, Lendir dan Benang.", + "achievementLostMasterclasserModalText": "Anda menyelesaikan kesemua enam belas pencarian dalam Siri Pencarian Masterclasser dan menyelesaikan misteri Masterclasser yang Hilang!", + "onboardingComplete": "Anda telah menyelesaikan tugasan anda!", + "achievementJustAddWaterText": "Telah selesai pencarian haiwan peliharaan seperti Sotong, Kuda Laut, Sotong Katak, Ikan Paus, Penyu, Siput Laut, Ular Laut dan Ikan Lumba-Lumba.", + "achievementDomesticatedModalText": "Anda mengumpul semua haiwan peliharaan yang dijinakkan!", + "onboardingCompleteDescSmall": "Jika anda mahukan lebih banyak lagi, lihat Pencapaian dan mula mengumpul!", + "foundNewItemsExplanation": "Menyelesaikan tugas memberi anda peluang untuk mencari barang, seperti Telur, Ramuan Penetasan dan Makanan Haiwan.", + "achievementZodiacZookeeper": "Zodiak Penjaga Zoo", + "achievementShadyCustomer": "Pelanggan Rendang", + "achievementShadyCustomerText": "Telah mengumpul semua Haiwan Peliharaan Terendak.", + "achievementShadyCustomerModalText": "Anda mengumpul semua Haiwan Peliharaan Terendak!", + "achievementShadeOfItAll": "Terendak Semuanya", + "achievementShadeOfItAllText": "Telah menjinakkan semua Gunung Naungan.", + "achievementShadeOfItAllModalText": "Anda menjinakkan semua Gunung Naungan!", + "achievementAllYourBaseText": "Telah menjinakkan semua Lekapan Asas.", + "achievementKickstarter2019": "Pin Penyokong Kickstarter", + "achievementAridAuthority": "Kuasa gersang", + "achievementAridAuthorityModalText": "Anda menjinakkan semua Gunung Gurun!", + "achievementAllYourBaseModalText": "Anda menjinakkan semua Pelekap Pangkalan!", + "achievementUndeadUndertakerText": "Telah menjinakkan semua Zombi Mounts.", + "achievementBackToBasicsText": "Telah mengumpul semua Haiwan Peliharaan Asas.", + "achievementAridAuthorityText": "Telah menjinakkan semua Gunung Gurun.", + "achievementUndeadUndertakerModalText": "Anda menjinakkan semua Zombi Mounts!", + "achievementMonsterMagus": "Raksasa Magus", + "achievementKickstarter2019Text": "Pin Menyokong Projek Kickstarter 2019", + "achievementTickledPinkText": "Telah mengumpulkan seluruh permen kapas peliharaan Merah Jambu.", + "achievementPurchasedEquipmentModalText": "Peralatan adalah cara untuk mempersonalisasikan avatarmu dan meningkatkan sats", + "achievementPearlyProText": "Telah menjinakkan jumlah semua putih.", + "achievementRosyOutlookText": "Telah menjinakkan semua permen kapas jumlah Merah Jambu.", + "achievementBugBonanzaModalText": "Anda selesai pencarian dan bela kumbang, kupu-kupu, siput, dan laba-laba!", + "achievementRosyOutlook": "Pandangan Kemerahan", + "achievementTickledPink": "Kegelian Merah Jambu", + "achievementPearlyProModalText": "Anda menjinakkan semua jumlah putih!", + "achievementPrimedForPainting": "Siap untuk lukisan", + "achievementBugBonanza": "Tambang Serangga", + "achievementPearlyPro": "Mutiara pro", + "achievementBugBonanzaText": "Telah selesai pencarian dan bela kumbang, kupu, siput, dan laba-laba.", + "achievementPrimedForPaintingModalText": "Anda mengoleksi semua Peliharaan Putih!", + "achievementRosyOutlookModalText": "anda menjinakkan semua permen kapas jumlah Merah Jambu!", + "achievementFedPetModalText": "Ada berbagai jenis makanan, tapi peliharaan bisa memilih", + "achievementPrimedForPaintingText": "telah mengumpulkan seluruh Peliharaan Putih.", + "achievementTickledPinkModalText": "Anda mengoleksi semua permen kapas peliharaan Merah Jambu!", + "achievementUndeadUndertaker": "Makam Mayat Hidup", + "achievementGoodAsGoldText": "telah mengumpulkan seluruh Peliharaan Emas.", + "achievementGoodAsGoldModalText": "Anda mengoleksi semua peliharaan emas!", + "achievementFreshwaterFriendsModalText": "Anda menyelesaikan bela dan pencarian axolotl, katak, dan badak sungai!", + "achievementGoodAsGold": "Baik Sebagai Emas", + "achievementAllThatGlitters": "Semua Itu Bergemerlapan", + "achievementBareNecessitiesModalText": "Anda selesai bela dan pencarian monyet, kungkang, dan pokok pet quests!", + "achievementFreshwaterFriends": "Teman Air Tawar", + "achievementBareNecessities": "Kebutuhan yang Kosong", + "achievementBoneCollectorModalText": "Anda mengoleksi semua kerangka peliharaan!", + "achievementSkeletonCrewModalText": "Anda menjinakkan semua jumlah kerangka!", + "achievementSeeingRedModalText": "Anda mengoleksi semua peliharaan merah!", + "achievementRedLetterDayText": "Telah menjinakkan jumlah semua merah.", + "achievementRedLetterDayModalText": "Anda menjinakkan semua jumlah merah!", + "achievementSeeingRed": "Melihat merah", + "achievementSkeletonCrewText": "Jumlah kerangka semua telah dijinakkan.", + "achievementSkeletonCrew": "Kakitangan Kerangka", + "achievementSeeingRedText": "Telah mengumpulkan semua peliharaan merah.", + "achievementBoneCollectorText": "Telah mengumpulkan seluruh kerangka peliharaan.", + "achievementRedLetterDay": "Hari Huruf Merah", + "achievementAllThatGlittersModalText": "Anda menjinakkan semua jumlah emas!", + "achievementAllThatGlittersText": "Semua jumlah emas telah dijinakkan.", + "achievementSeasonalSpecialistText": "Telah menyelesaikan pencarian sepanjang musim semi dan musim dingin: Perburuan Telur, Trapper Santa dan Menemukan Anak Beruang Kutub!", + "achievementWildBlueYonderModalText": "Anda menjinakkan semua jumlah permen kapas biru!", + "achievementVioletsAreBlueText": "Telah mengumpulkan seluruh permen kapas peliharaan biru.", + "achievementDomesticated": "E-I-E-I-O", + "achievementWildBlueYonderText": "Telah menjinakkan semua jumlah permen kapas biru.", + "achievementVioletsAreBlue": "Bunga violet berwarna biru", + "achievementWildBlueYonder": "Liar biru gelap", + "achievementSeasonalSpecialistModalText": "Anda telah menyelesaikan semua pencarian musiman!", + "achievementSeasonalSpecialist": "Spesialis Musiman", + "achievementLegendaryBestiary": "Legendaris Bestiary", + "achievementZodiacZookeeperText": "Menetas semua warna standar haiwan zodiak: Tikus, lembu, arnab, ular, kuda, biri-biri, monyet, ayam jantan, serigala, harimau, babi terbang dan naga!", + "achievementZodiacZookeeperModalText": "Telah mengumpul semua Haiwan Zodiak!", + "achievementBirdsOfAFeather": "Burung Sejenis", + "achievementBirdsOfAFeatherText": "Menetas semua warna standar haiwan peliharaan terbang: Babi terbang, burung hantu, burung kakak tua, pterodactyl, elang, helang, burung merak dan ayam jantan!", + "achievementReptacularRumble": "Reptilia Berkeroncong", + "achievementBirdsOfAFeatherModalText": "Mengumpul semua haiwan peliharaan terbang!", + "achievementReptacularRumbleText": "Menetas semua warna reptilia standard: Buaya, Pterodactyl, Ular, Triceratops, Kura-kura, Tyrannosaurus Rex dan Velociraptor!", + "achievementReptacularRumbleModalText": "Mengumpul semua haiwan peliharaan reptilia!", + "achievementGroupsBeta2022": "Penguji beta interaktif", + "achievementGroupsBeta2022Text": "Anda dan pasukan anda memberikan maklum balas yang tidak ternilai untuk membantu menguji Habitica.", + "achievementGroupsBeta2022ModalText": "Anda dan pasukan anda membantu Habitica dengan menguji dan memberi maklum balas!", + "achievementWoodlandWizard": "Hutan Sihir", + "achievementWoodlandWizardText": "Anda telah menetas semua warna makhluk hutan standard: Badger, beruang, rusa, musang, katak, landak, burung hantu, siput, tupai dan kerdil!", + "achievementWoodlandWizardModalText": "Mengumpul semua haiwan peliharaan hutan!", + "achievementBoneToPick": "Tulang untuk memilih", + "achievementBoneToPickText": "Anda telah menetas semua Haiwan Kesayangan Klasik dan Haiwan Kerangka Quest!", + "achievementBoneToPickModalText": "Anda Mengumpul semua Haiwan Kesayangan Klasik dan Haiwan Kerangka!", + "achievementPolarPro": "kutub profesional", + "achievementPolarProText": "Menetas semua haiwan peliharaan Artik: Beruang, musang, penguin, ikan paus dan serigala!", + "achievementPolarProModalText": "Anda telah mengumpul semua haiwan peliharaan kutub!", + "achievementBareNecessitiesText": "Selesai pencarian haiwan peliharaan monyet, kemalasan dan kerdil.", + "achievementFreshwaterFriendsText": "Selesai pencarian haiwan peliharaan untuk Axolotl, Katak dan Badak Sungai.", + "achievementLegendaryBestiaryText": "Menetas semua warna haiwan peliharaan Mythic lalai: Naga, babi terbang, griffin, ular laut dan unicorn!", + "achievementBoneCollector": "Pengumpul Tulang" } diff --git a/website/common/locales/ms/front.json b/website/common/locales/ms/front.json index 9a3c03a2a5..607830f03b 100644 --- a/website/common/locales/ms/front.json +++ b/website/common/locales/ms/front.json @@ -5,5 +5,7 @@ "communityInstagram": "Instagram", "clearBrowserData": "Hapuskan Data Pelayar Web", "termsAndAgreement": "Dengan klik butang di bawah, anda diandaikan telah membaca serta setuju dengan Syarat Perkhidmatan dan Dasar Privasi.", - "communityExtensions": "Alat & Sambungan Tambahan" + "communityExtensions": "Alat tambah dan sambungan", + "companyAbout": "bagaimana ia berfungsi", + "companyBlog": "Blog" } diff --git a/website/common/locales/ms/subscriber.json b/website/common/locales/ms/subscriber.json index a0b2f35acd..dcd5259664 100755 --- a/website/common/locales/ms/subscriber.json +++ b/website/common/locales/ms/subscriber.json @@ -1,13 +1,13 @@ { "subscription": "Langganan", "subscriptions": "Langganan-langganan", - "sendGems": "Send Gems", - "buyGemsGold": "Beli permata dengan syiling emas", + "sendGems": "Hantar Permata", + "buyGemsGold": "Beli Permata dengan Mata Emas", "mustSubscribeToPurchaseGems": "Mesti melanggan untuk membeli permata dengan GP", "reachedGoldToGemCap": "You've reached the Gold=>Gem conversion cap <%= convCap %> for this month. We have this to prevent abuse / farming. The cap resets within the first three days of each month.", "reachedGoldToGemCapQuantity": "Your requested amount <%= quantity %> exceeds the Gold=>Gem conversion cap <%= convCap %> for this month. We have this to prevent abuse / farming. The cap resets within the first three days of each month.", "mysteryItem": "Barang bulanan eksklusif", - "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.", + "mysteryItemText": "Setiap bulan anda akan menerima item kosmetik yang unik untuk avatar anda! Selain itu, untuk setiap tiga bulan langganan berturut-turut, Pengembara Masa Misterius akan memberikan anda akses kepada barangan kosmetik bersejarah (dan futuristik!).", "exclusiveJackalopePet": "Exclusive pet", "giftSubscription": "Want to gift a subscription to someone?", "giftSubscriptionText4": "Thanks for supporting Habitica!", @@ -120,7 +120,7 @@ "choosePaymentMethod": "Choose your payment method", "buyGemsSupportsDevs": "Purchasing Gems supports the developers and helps keep Habitica running", "support": "SUPPORT", - "gemBenefitLeadin": "Gems allow you to buy fun extras for your account, including:", + "gemBenefitLeadin": "Apa yang anda boleh beli dengan Permata?", "gemBenefit1": "Unique and fashionable costumes for your avatar.", "gemBenefit2": "Backgrounds to immerse your avatar in the world of Habitica!", "gemBenefit3": "Exciting Quest chains that drop pet eggs.", diff --git a/website/common/locales/pt/generic.json b/website/common/locales/pt/generic.json index 68ad1a06ea..b2a233ce3f 100644 --- a/website/common/locales/pt/generic.json +++ b/website/common/locales/pt/generic.json @@ -125,7 +125,7 @@ "birthdayCardNotes": "Envie um cartão de aniversário para um membro da equipa.", "birthday0": "Parabéns pra você!", "birthdayCardAchievementTitle": "Aniversário Próspero", - "birthdayCardAchievementText": "Muitas respostas felizes! Enviou ou recebeu <%= cards %> cartões de aniversário.", + "birthdayCardAchievementText": "Muitas respostas felizes! Enviou ou recebeu <%= count %> cartões de aniversário.", "congratsCard": "Cartão de Parabéns", "congratsCardExplanation": "Ambos recebem a conquista de Companheiro Congrulatório!", "congratsCardNotes": "Envia um cartão de Parabéns a um membro da equipa.", diff --git a/website/common/locales/pt_BR/achievements.json b/website/common/locales/pt_BR/achievements.json index e5c29ab638..dfa036eebb 100644 --- a/website/common/locales/pt_BR/achievements.json +++ b/website/common/locales/pt_BR/achievements.json @@ -132,7 +132,7 @@ "achievementBirdsOfAFeatherModalText": "Você coletou todos os mascotes voadores!", "achievementGroupsBeta2022Text": "Você e seu grupo deram um feedback inestimável para ajudar a testar o Habitica.", "achievementGroupsBeta2022ModalText": "Você e seu grupo ajudaram o Habitica, testando e dando o seu feedback!", - "achievementReptacularRumbleModalText": "Você coletou todos os mascotes do tipo réptil!", + "achievementReptacularRumbleModalText": "Você coletou todos os mascotes répteis!", "achievementReptacularRumble": "Répteis Reptumbantes", "achievementReptacularRumbleText": "Coletou todos os mascotes comuns do tipo réptil: Cobra, Jacaré, Pterodáctilo, Tartaruga, Tiranossauro, Tricerátops e Velociraptor!", "achievementGroupsBeta2022": "Testador Beta Interativo", diff --git a/website/common/locales/pt_BR/front.json b/website/common/locales/pt_BR/front.json index b11020d568..39d5fc4863 100644 --- a/website/common/locales/pt_BR/front.json +++ b/website/common/locales/pt_BR/front.json @@ -1,6 +1,6 @@ { "FAQ": "FAQ", - "termsAndAgreement": "Ao clicar no botão abaixo, você afirma que leu e aceitou os Termos de Serviço e Política de Privacidade.", + "termsAndAgreement": "Ao clicar no botão abaixo, você afirma que leu e aceita os Termos de Serviço e Política de Privacidade.", "accept1Terms": "Ao clicar no botão abaixo, eu concordo com os", "accept2Terms": "e com a", "chores": "Afazeres", diff --git a/website/common/locales/pt_BR/gear.json b/website/common/locales/pt_BR/gear.json index 306137d72c..392c620e29 100644 --- a/website/common/locales/pt_BR/gear.json +++ b/website/common/locales/pt_BR/gear.json @@ -2782,5 +2782,7 @@ "shieldSpecialWinter2023WarriorText": "Escudo de Ostra", "shieldSpecialWinter2023WarriorNotes": "A hora chegou, a Morsa diz, de dizer muitas coisas: sobre conchas de ostras—e sinos do inverno—sobre canções que alguém canta—e sobre onde está a pérola deste escudo—ou o que mais o novo ano trouxer! Aumenta Constituição em <%= con %>. Edição Limitada do Equipamento de Inverno de 2022-2023.", "shieldSpecialWinter2023HealerText": "Geléias Legais", - "shieldSpecialWinter2023HealerNotes": "Sua canção de geada e neve acalmará os espíritos de todos que ouvirem. Aumenta Constituição em <%= con %>. Edição Limitada do Equipamento de Inverno de 2022-2023." + "shieldSpecialWinter2023HealerNotes": "Sua canção de geada e neve acalmará os espíritos de todos que ouvirem. Aumenta Constituição em <%= con %>. Edição Limitada do Equipamento de Inverno de 2022-2023.", + "headSpecialNye2022Text": "Chapéu de Festa Fantástico", + "headSpecialNye2022Notes": "Você recebeu um Chapéu de Festa Fantástico! Use com orgulho enquanto curte o Ano Novo! Não confere benefícios." } diff --git a/website/common/locales/pt_BR/limited.json b/website/common/locales/pt_BR/limited.json index c558563be7..fb6545f641 100644 --- a/website/common/locales/pt_BR/limited.json +++ b/website/common/locales/pt_BR/limited.json @@ -239,5 +239,6 @@ "winter2023FairyLightsMageSet": "Luzes de Natal (Mago)", "winter2023CardinalHealerSet": "Cardeal (Curandeiro)", "spring2023RibbonRogueSet": "Fita (Gatuno)", - "winter2023WalrusWarriorSet": "Morsa (Guerreiro)" + "winter2023WalrusWarriorSet": "Morsa (Guerreiro)", + "winter2023RibbonRogueSet": "Fita (Gatuno)" } diff --git a/website/common/locales/ru/backgrounds.json b/website/common/locales/ru/backgrounds.json index 897731dbc4..7b5005bcb2 100644 --- a/website/common/locales/ru/backgrounds.json +++ b/website/common/locales/ru/backgrounds.json @@ -748,5 +748,6 @@ "backgroundInsideACrystalText": "Внутри кристалла", "backgroundBranchesOfAHolidayTreeNotes": "Порезвитесь на ветвях праздничной елки.", "backgroundSnowyVillageText": "Снежная деревня", - "backgroundSnowyVillageNotes": "Полюбуйтесь заснеженной деревней." + "backgroundSnowyVillageNotes": "Полюбуйтесь заснеженной деревней.", + "backgroundInsideACrystalNotes": "Наблюдайте за внешним миром из кристалла." } diff --git a/website/common/locales/ru/gear.json b/website/common/locales/ru/gear.json index e07ee34c7a..8b96a6d60f 100644 --- a/website/common/locales/ru/gear.json +++ b/website/common/locales/ru/gear.json @@ -2742,5 +2742,33 @@ "weaponSpecialWinter2023RogueText": "Зеленый атласный пояс", "weaponSpecialWinter2023WarriorNotes": "Два зубца этого копья по своей форме напоминают бивни моржа, но в два раза мощнее. Подкалывайте сомнения до тех пор пока они не отступят! Увеличивает силу на <%= str %>. Ограниченный выпуск зимы 2022-2023.", "weaponSpecialWinter2023RogueNotes": "Легенды рассказывают, как разбойники заманивают своих противников в западню, обезоруживают их, а затем возвращают им их же оружие, просто чтобы быть милыми. Увеличивает Силу на <%= str %>. Ограниченный выпуск зимы 2022-2023.", - "weaponSpecialWinter2023WarriorText": "Бивневое копье" + "weaponSpecialWinter2023WarriorText": "Бивневое копье", + "headSpecialNye2022Text": "Фантастический праздничный колпак", + "headSpecialNye2022Notes": "Вы получили фантастический праздничный колпак! Носите его с гордостью, встречая Новый год! Бонусов не дает.", + "armorSpecialWinter2023HealerText": "Костюм кардинала", + "armorSpecialWinter2023HealerNotes": "Этот яркий костюм кардинала идеально подходит для того, чтобы летать высоко над своими проблемами. Увеличивает телосложение на <%= con %>. Ограниченный выпуск зимы 2022-2023.", + "armorSpecialWinter2023WarriorText": "Костюм моржа", + "armorSpecialWinter2023WarriorNotes": "Этот прочный костюм моржа идеально подходит для прогулки по пляжу посреди ночи. Увеличивает телосложение на <%= con %>. Ограниченный выпуск зимы 2022-2023.", + "armorMystery202212Text": "Ледяное платье", + "weaponMystery202212Notes": "Светящаяся снежинка в этом жезле способна согреть сердце даже в самую холодную зимнюю ночь! Бонусов не дает. Подарок подписчикам декабря 2022.", + "weaponArmoireFinelyCutGemText": "Идеальный драгоценный камень", + "armorArmoireJewelersApronText": "Фартук ювелира", + "shieldArmoireJewelersPliersText": "Ювелирные плоскогубцы", + "headAccessoryMystery202212Text": "Ледяная тиара", + "headSpecialWinter2023WarriorText": "Шлем моржа", + "headSpecialWinter2023HealerText": "Шлем кардинала", + "eyewearArmoireJewelersEyeLoupeText": "Глазная лупа ювелира", + "weaponSpecialWinter2023HealerText": "Метательный венок", + "weaponSpecialWinter2023MageText": "Лисий огонь, ложный огонь", + "weaponSpecialWinter2023HealerNotes": "Смотрите, как этот праздничный, колючий венок движется по воздуху прямо по направлению к вашему противнику или препятствиям на вашем пути, а затем возвращается к вам обратно, как бумеранг, ожидая нового броска. Увеличивает интеллект на <%= int %>. Ограниченный выпуск зимы 2022 - 2023.", + "weaponArmoireFinelyCutGemNotes": "Какая находка! Теперь этот потрясающий драгоценный камень будет украшать вашу коллекцию. Возможно, в нем заключена особая магия, которая только и ждет, чтобы вы ею воспользовались. Увеличивает телосложение на <%= con %>. Зачарованный сундук: Набор Ювелира (предмет 4 из 4 ).", + "weaponSpecialWinter2023MageNotes": "Ни лисы, ни огня, но очень празднично! Увеличивает интеллект на <%= int %> и восприятие на <%= per %>. Ограниченный выпуск зимы 2022 - 2023.", + "armorSpecialWinter2023RogueText": "Подарочная лента", + "armorSpecialWinter2023RogueNotes": "Приобретите предметы. Упакуйте их в красивую бумагу. И подарите их местному разбойнику! В этот сезон - это необходимость. Увеличивает восприятие на <%= per %>. Ограниченный выпуск зимы 2022 - 2023.", + "armorSpecialWinter2023MageText": "Платье волшебных огней", + "armorSpecialWinter2023MageNotes": "То, что на вас светятся огни, не делает вас елкой! ...может быть в следующем году. Увеличивает интеллект на <%= int %>. Ограниченный выпуск зимы 2022 - 2023.", + "armorMystery202212Notes": "Вселенная может быть холодна, но в этом очаровательном платье вам будет тепло и уютно во время полета. Бонусов не дает. Подарок подписчикам декабря 2022.", + "armorArmoireJewelersApronNotes": "Этот тяжелый фартук - то, что вам нужно, когда вы чувствуете себя творческим человеком. Самое главное, что в нём есть десятки маленьких карманов, для хранения всего того, что вам необходимо. Увеличивает интеллект на <%= int %>. Зачарованный сундук (предмет 1 из 4).", + "headSpecialWinter2023RogueText": "Подарочный бант", + "headSpecialWinter2023RogueNotes": "Соблазн людей \"погладить\" ваши волосы, дает вам возможность потренировать навыки уклонения и уворотов. Увеличивает восприятие на <%= per %>. Ограниченный выпуск зимы 2022 - 2023." } diff --git a/website/common/locales/ru/limited.json b/website/common/locales/ru/limited.json index d784f80d58..721bad3628 100644 --- a/website/common/locales/ru/limited.json +++ b/website/common/locales/ru/limited.json @@ -197,7 +197,7 @@ "winter2021IceFishingWarriorSet": "Рыбак на льду (Воин)", "g1g1Returning": "В честь праздничного сезона мы запускаем специальное предложение. Если вы подарите подписку своему другу, то получите точно такую же бесплатно!", "septemberYYYY": "Сентябрь <%= year %>", - "g1g1Limitations": "Это предложение начнет действовать 16 декабря в 8:00 (13:00 UTC) и завершится 6 января в 20:00 (1:00 UTC). Акция будет действовать только в том случае, если вы дарите подписку другому пользователю Habitica. Если у вас или у получателя подарка уже есть подписка, то подаренная подписка добавится к текущей и будет использоваться только после того, как текущая подписка будет отменена, или истечет ее срок действия.", + "g1g1Limitations": "Это предложение начнет действовать 15 декабря в 8:00 (13:00 UTC) и завершится 8 января в 23:59 ( 9 января 4:59 UTC). Акция будет действовать только в том случае, если вы дарите подписку другому пользователю Habitica. Если у вас или у получателя подарка уже есть подписка, то подаренная подписка добавится к текущей и будет использоваться только после того, как текущая подписка будет отменена, или истечет ее срок действия.", "g1g1HowItWorks": "Введите имя пользователя, которому вы хотите сделать подарок. Далее выберите подписку, которую вы хотите подарить, и оплатите заказ. Вы получите автоматически точно такую же подписку, которую вы только что подарили.", "spring2021TwinFlowerRogueSet": "Сдвоенный цветок (Разбойник)", "spring2021WillowHealerSet": "Ива (Целитель)", @@ -239,5 +239,6 @@ "winter2023FairyLightsMageSet": "Волшебные огни (Маг)", "winter2023CardinalHealerSet": "Кардинал (Целитель)", "winter2023WalrusWarriorSet": "Морж (Воин)", - "spring2023RibbonRogueSet": "Лента (Разбойник)" + "spring2023RibbonRogueSet": "Лента (Разбойник)", + "winter2023RibbonRogueSet": "Лента (Разбойник)" } diff --git a/website/common/locales/uk/achievements.json b/website/common/locales/uk/achievements.json index 2b17f9c544..8d71b59563 100644 --- a/website/common/locales/uk/achievements.json +++ b/website/common/locales/uk/achievements.json @@ -141,5 +141,8 @@ "achievementWoodlandWizardText": "Зібрали усіх лісових істот стандартних кольорів: борсука, ведмедя, оленя, лисицю, жабу, їжака, сову, равлика, білку та деревце!", "achievementBoneToPickModalText": "Ви зібрали всіх класичних та квестових кістяних улюбленців!", "achievementBoneToPickText": "Зібрали всіх класичних та квестових кістяних улюбленців!", - "achievementBoneToPick": "Могильник" + "achievementBoneToPick": "Могильник", + "achievementPolarPro": "Полярник", + "achievementPolarProText": "Зібрали всіх полярних улюбленців: ведмедя, лиса, пінгвіна, кита та вовка!", + "achievementPolarProModalText": "Ви зібрали всіх полярних тваринок!" } diff --git a/website/common/locales/uk/faq.json b/website/common/locales/uk/faq.json index 6599acf6a2..ce50e637d0 100644 --- a/website/common/locales/uk/faq.json +++ b/website/common/locales/uk/faq.json @@ -56,5 +56,7 @@ "androidFaqStillNeedHelp": "Якщо виникли питання, котрі відсутні в списку або в [Wiki FAQ](https://habitica.fandom.com/wiki/FAQ), задайте його у чаті Таверни у Меню > Таверна! Ми раді допомогти.", "webFaqStillNeedHelp": "Якщо виникли питання, котрі відсутні в списку або в [FAQ на Вікі](https://habitica.fandom.com/wiki/FAQ), то задайте його в [Ґільдії для початківці](https://habitica.com/groups/guild/5481ccf3-5d2d-48a9-a871-70a7380cee5a)! Ми з радістю допоможемо вам.", "faqQuestion13": "Що таке груповий план?", - "webFaqAnswer13": "## Як працюють групові плани?\n\n[Груповий план](/group-plans) надає вашій команді чи ґільдії доступ до спільної дошки завдань, подібної до вашої особистої дошки завдань! Це спільний простір в Habitica, де будь-хто в групі може створювати та виконувати завдання.\n\nТакож доступні такі функції, як ролі учасників, перегляд статусу та призначення завдань, які дають вам більше контролю. [Відвідайте нашу Wiki](https://habitica.fandom.com/wiki/Group_Plans), щоб дізнатися більше про функції наших групових планів!\n\n## Кому вигідний груповий план?\n\nГрупові плани працюють найкраще, коли у вас є невелика команда людей, які хочуть співпрацювати разом. Ми рекомендуємо 2-5 учасників.\n\nГрупові плани чудово підходять для сімей, будь то батьки з дитиною чи ви з партнером. Спільні цілі, справи чи обов’язки легко відстежувати на одній дошці.\n\nГрупові плани також можуть бути корисними для команд колег, які мають спільну мету, або керівників, які хочуть познайомити своїх співробітників з гейміфікацією.\n\n## Швидкі поради щодо використання груп\n\nОсь кілька коротких порад, які допоможуть вам почати роботу з новою групою. Ми надамо додаткові відомості в наступних розділах:\n\n* Зробіть учасника менеджером, щоб надати йому можливість створювати та редагувати завдання\n* Залишайте завдання непризначеними, якщо будь-хто може їх виконати, і їх потрібно виконати лише один раз\n* Призначте завдання одній людині, щоб переконатися, що ніхто інший не зможе виконати його завдання\n* Призначте завдання кільком людям, якщо їм усім потрібно його виконати\n* Увімкніть можливість відображати спільні завдання на вашій особистій дошці, щоб нічого не пропустити\n* Ви отримуєте винагороду за виконані вами завдання, навіть якщо ви якщо це завдання виконується кількома людьми\n* Винагороди за виконання завдань не розподіляються між членами команди\n* Використовуйте колір завдань на командній дошці, щоб оцінити середню швидкість виконання завдань\n* Регулярно переглядайте завдання на командній дошці, щоб переконатися, що вони все ще актуальні\n* Пропуск щоденки не завдасть шкоди ні вам, ні вашій команді, але колір завдання погіршиться\n\n## Як інші учасники групи можуть створювати завдання?\n\nСтворювати завдання можуть лише лідер групи та менеджери. Якщо ви хочете, щоб учасник групи міг створювати завдання, вам слід підвищити його до менеджера, перейшовши на вкладку «Інформація про групу», переглянувши список учасників і натиснувши піктограму з крапкою біля їх імені.\n\n## Як працює призначення завдання?\n\nГрупові плани дають вам унікальну можливість призначати завдання іншим учасникам групи. Призначення завдання чудово підходить для делегування. Якщо ви комусь доручили завдання, інші учасники не зможуть його виконати.\n\nВи також можете призначити завдання кільком людям, якщо його потрібно виконати більше ніж одному учаснику. Наприклад, якщо кожен має почистити зуби, створіть завдання та призначте його кожному учаснику групи. Усі вони зможуть перевірити це й отримати за це свої індивідуальні винагороди. Головне завдання буде відображено як виконане, коли всі його відмітять.\n\n## Як працюють непризначені завдання?\n\nНепризначене завдання може виконати будь-хто в групі, тому залиште завдання непризначеними, щоб дозволити будь-якому учаснику виконати його. Наприклад, виности сміття. Той, хто виносить сміття, може відмітити непризначене завдання, і воно відображатиметься як виконане для всіх.\n\n## Як працює скидання синхронізованого дня?\n\nСпільні завдання буде скинуто одночасно для всіх, щоб підтримувати синхронізацію спільної панелі завдань. Цей час відображається на спільній панелі завдань і визначається часом початку дня керівника групи. Оскільки спільні завдання скидаються автоматично, ви не матимете можливості завершити вчорашні невиконані спільні щоденники, коли увійдете наступного ранку.\n\nСпільні щоденники не завдадуть шкоди, якщо їх пропустити, однак їхній колір погіршиться, щоб допомогти візуалізувати прогрес. Ми не хочемо, щоб спільний досвід був негативним!\n\n## Як я можу використовувати свою групу в мобільних програмах?\n\nХоча мобільні програми ще не повністю підтримують усі функції групових планів, ви все одно можете виконувати спільні завдання з програм для iOS та Android, скопіювавши їх на особисту дошку завдань. Ви можете ввімкнути цей параметр у налаштуваннях мобільних програм або на панелі групових завдань у версії браузера. Тепер відкриті та призначені спільні завдання відображатимуться на вашій персональній панелі завдань на всіх платформах.\n\n## Яка різниця між спільними завданнями групи та випробуваннями?\n\nСпільні дошки завдань у груповому плані є більш динамічними, ніж випробування, оскільки їх можна постійно оновлювати та з ними можна взаємодіяти. Випробування чудові, якщо у вас є один набір завдань, які потрібно розіслати багатьом людям.\n\nГрупові плани також є платною функцією, тоді як виклики доступні для всіх безкоштовно.\n\nВи не можете призначати певні завдання у випробуваннях, а також випробування не мають спільного скидання. Загалом виклики пропонують менше контролю та прямої взаємодії." + "webFaqAnswer13": "## Як працюють групові плани?\n\n[Груповий план](/group-plans) надає вашій команді чи ґільдії доступ до спільної дошки завдань, подібної до вашої особистої дошки завдань! Це спільний простір в Habitica, де будь-хто в групі може створювати та виконувати завдання.\n\nТакож доступні такі функції, як ролі учасників, перегляд статусу та призначення завдань, які дають вам більше контролю. [Відвідайте нашу Wiki](https://habitica.fandom.com/wiki/Group_Plans), щоб дізнатися більше про функції наших групових планів!\n\n## Кому вигідний груповий план?\n\nГрупові плани працюють найкраще, коли у вас є невелика команда людей, які хочуть співпрацювати разом. Ми рекомендуємо 2-5 учасників.\n\nГрупові плани чудово підходять для сімей, будь то батьки з дитиною чи ви з партнером. Спільні цілі, справи чи обов’язки легко відстежувати на одній дошці.\n\nГрупові плани також можуть бути корисними для команд колег, які мають спільну мету, або керівників, які хочуть познайомити своїх співробітників з гейміфікацією.\n\n## Швидкі поради щодо використання груп\n\nОсь кілька коротких порад, які допоможуть вам почати роботу з новою групою. Ми надамо додаткові відомості в наступних розділах:\n\n* Зробіть учасника менеджером, щоб надати йому можливість створювати та редагувати завдання\n* Залишайте завдання непризначеними, якщо будь-хто може їх виконати, і їх потрібно виконати лише один раз\n* Призначте завдання одній людині, щоб переконатися, що ніхто інший не зможе виконати його завдання\n* Призначте завдання кільком людям, якщо їм усім потрібно його виконати\n* Увімкніть можливість відображати спільні завдання на вашій особистій дошці, щоб нічого не пропустити\n* Ви отримуєте винагороду за виконані вами завдання, навіть якщо ви якщо це завдання виконується кількома людьми\n* Винагороди за виконання завдань не розподіляються між членами команди\n* Використовуйте колір завдань на командній дошці, щоб оцінити середню швидкість виконання завдань\n* Регулярно переглядайте завдання на командній дошці, щоб переконатися, що вони все ще актуальні\n* Пропуск щоденки не завдасть шкоди ні вам, ні вашій команді, але колір завдання погіршиться\n\n## Як інші учасники групи можуть створювати завдання?\n\nСтворювати завдання можуть лише лідер групи та менеджери. Якщо ви хочете, щоб учасник групи міг створювати завдання, вам слід підвищити його до менеджера, перейшовши на вкладку «Інформація про групу», переглянувши список учасників і натиснувши піктограму з крапкою біля їх імені.\n\n## Як працює призначення завдання?\n\nГрупові плани дають вам унікальну можливість призначати завдання іншим учасникам групи. Призначення завдання чудово підходить для делегування. Якщо ви комусь доручили завдання, інші учасники не зможуть його виконати.\n\nВи також можете призначити завдання кільком людям, якщо його потрібно виконати більше ніж одному учаснику. Наприклад, якщо кожен має почистити зуби, створіть завдання та призначте його кожному учаснику групи. Усі вони зможуть перевірити це й отримати за це свої індивідуальні винагороди. Головне завдання буде відображено як виконане, коли всі його відмітять.\n\n## Як працюють непризначені завдання?\n\nНепризначене завдання може виконати будь-хто в групі, тому залиште завдання непризначеними, щоб дозволити будь-якому учаснику виконати його. Наприклад, виности сміття. Той, хто виносить сміття, може відмітити непризначене завдання, і воно відображатиметься як виконане для всіх.\n\n## Як працює скидання синхронізованого дня?\n\nСпільні завдання буде скинуто одночасно для всіх, щоб підтримувати синхронізацію спільної панелі завдань. Цей час відображається на спільній панелі завдань і визначається часом початку дня керівника групи. Оскільки спільні завдання скидаються автоматично, ви не матимете можливості завершити вчорашні невиконані спільні щоденники, коли увійдете наступного ранку.\n\nСпільні щоденники не завдадуть шкоди, якщо їх пропустити, однак їхній колір погіршиться, щоб допомогти візуалізувати прогрес. Ми не хочемо, щоб спільний досвід був негативним!\n\n## Як я можу використовувати свою групу в мобільних програмах?\n\nХоча мобільні програми ще не повністю підтримують усі функції групових планів, ви все одно можете виконувати спільні завдання з програм для iOS та Android, скопіювавши їх на особисту дошку завдань. Ви можете ввімкнути цей параметр у налаштуваннях мобільних програм або на панелі групових завдань у версії браузера. Тепер відкриті та призначені спільні завдання відображатимуться на вашій персональній панелі завдань на всіх платформах.\n\n## Яка різниця між спільними завданнями групи та випробуваннями?\n\nСпільні дошки завдань у груповому плані є більш динамічними, ніж випробування, оскільки їх можна постійно оновлювати та з ними можна взаємодіяти. Випробування чудові, якщо у вас є один набір завдань, які потрібно розіслати багатьом людям.\n\nГрупові плани також є платною функцією, тоді як виклики доступні для всіх безкоштовно.\n\nВи не можете призначати певні завдання у випробуваннях, а також випробування не мають спільного скидання. Загалом виклики пропонують менше контролю та прямої взаємодії.", + "iosFaqAnswer13": "## Як працюють групові плани?\n\n[Груповий план](/group-plans) надає вашій команді чи ґільдії доступ до спільної дошки завдань, подібної до вашої особистої дошки! Це спільний простір в Habitica, де будь-хто в групі може створювати та виконувати завдання.\n\nТакож доступні такі функції, як ролі учасників, перегляд статусу та призначення завдань, які дають вам більше контролю. [Відвідайте нашу Вікі](https://habitica.fandom.com/wiki/Group_Plans), щоб дізнатися більше про функції наших групових планів!\n\n## Кому вигідний груповий план?\n\nГрупові плани працюють найкраще, коли у вас є невелика команда людей, які хочуть співпрацювати разом. Ми рекомендуємо 2-5 учасників.\n\nГрупові плани чудово підходять для сімей, будь то батьки з дитиною чи ви з партнером. Спільні цілі, справи чи обов’язки легко відстежувати на одній дошці.\n\nГрупові плани також можуть бути корисними для команд колег, які мають спільну мету, або керівників, які хочуть залучити своїх співробітників за допомогою гейміфікації.\n\n## Швидкі поради щодо використання груп\n\nОсь кілька коротких порад, які допоможуть вам почати роботу з новою групою. Ми надамо додаткові відомості в наступних розділах:\n\n* Зробіть учасника менеджером, щоб надати йому можливість створювати та редагувати завдання\n* Залишайте завдання непризначеними, якщо будь-хто може їх виконати, і їх потрібно виконати лише один раз\n* Призначте завдання одній людині, щоб переконатися, що ніхто інший не зможе виконати це завдання\n* Призначте завдання кільком людям, якщо їм усім потрібно його виконати\n* Увімкніть можливість відображати спільні завдання на вашій особистій дошці, щоб нічого не пропустити\n* Ви отримуєте винагороду за виконані вами завдання, навіть якщо ви якщо це завдання виконується кількома людьми\n* Винагороди за виконання завдань не розподіляються між членами команди\n* Використовуйте колір завдань на командній дошці, щоб оцінити середню швидкість виконання завдань\n* Регулярно переглядайте завдання на командній дошці, щоб переконатися, що вони все ще актуальні\n* Пропуск щоденки не завдасть шкоди ні вам, ні вашій команді, але колір завдання погіршиться\n\n## Як інші учасники групи можуть створювати завдання?\n\nСтворювати завдання можуть лише лідер групи та менеджери. Якщо ви хочете, щоб учасник групи міг створювати завдання, вам слід підвищити його до менеджера, перейшовши на вкладку «Інформація про групу», переглянувши список учасників і натиснувши піктограму з крапкою біля їх імені.\n\n## Як працює призначення завдання?\n\nГрупові плани дають вам унікальну можливість призначати завдання іншим учасникам групи. Призначення завдання чудово підходить для делегування. Якщо ви комусь доручили завдання, інші учасники не зможуть його виконати.\n\nВи також можете призначити завдання кільком людям, якщо його потрібно виконати більше ніж одному учаснику. Наприклад, якщо кожен має почистити зуби, створіть завдання та призначте його кожному учаснику групи. Усі вони зможуть відзначити його й отримати за це свої індивідуальні винагороди. Головне завдання буде відображено як виконане, коли всі його відзначать як виконане.\n\n## Як працюють непризначені завдання?\n\nНепризначене завдання може виконати будь-хто в групі, тому залиште завдання непризначеними, щоб дозволити будь-якому учаснику виконати його. Наприклад, винести сміття. Той, хто виніс сміття, може відмітити непризначене завдання, і воно відображатиметься як виконане для всіх.\n\n## Як працює скидання синхронізованого дня?\n\nСпільні завдання буде скинуто одночасно для всіх, щоб підтримувати синхронізацію спільної панелі завдань. Цей час відображається на спільній панелі завдань і визначається часом початку дня керівника групи. Оскільки спільні завдання скидаються автоматично, ви не матимете можливості завершити вчорашні невиконані спільні щоденники, коли увійдете наступного ранку.\n\nСпільні щоденники не завдадуть шкоди, якщо їх пропустити, однак їхній колір погіршиться, щоб допомогти візуалізувати прогрес. Ми не хочемо, щоб спільний досвід був негативним!\n\n## Як я можу використовувати свою групу в мобільних програмах?\n\nХоча мобільні програми ще не повністю підтримують усі функції групових планів, ви все одно можете виконувати спільні завдання з програм для iOS та Android, скопіювавши їх на особисту дошку завдань. Ви можете ввімкнути цей параметр у налаштуваннях мобільних програм або на панелі групових завдань у версії браузера. Тепер відкриті та призначені спільні завдання відображатимуться на вашій персональній панелі завдань на всіх платформах.\n\n## Яка різниця між спільними завданнями групи та випробуваннями?\n\nСпільні дошки завдань у груповому плані є більш динамічними, ніж випробування, оскільки їх можна постійно оновлювати та з ними можна взаємодіяти. Випробування чудові, якщо у вас є один набір завдань, які потрібно розіслати багатьом людям.\n\nГрупові плани також є платною функцією, тоді як випробування доступні для всіх безкоштовно.\n\nВи не можете призначати певні завдання у випробуваннях, а також випробування не мають спільного часу скидання. Загалом випробування пропонують менше контролю та прямої взаємодії.", + "androidFaqAnswer13": "## Як працюють групові плани?\n\n[Груповий план](/group-plans) надає вашій команді чи ґільдії доступ до спільної дошки завдань, подібної до вашої особистої дошки! Це спільний простір в Habitica, де будь-хто в групі може створювати та виконувати завдання.\n\nТакож доступні такі функції, як ролі учасників, перегляд статусу та призначення завдань, які дають вам більше контролю. [Відвідайте нашу Вікі](https://habitica.fandom.com/wiki/Group_Plans), щоб дізнатися більше про функції наших групових планів!\n\n## Кому вигідний груповий план?\n\nГрупові плани працюють найкраще, коли у вас є невелика команда людей, які хочуть співпрацювати разом. Ми рекомендуємо 2-5 учасників.\n\nГрупові плани чудово підходять для сімей, будь то батьки з дитиною чи ви з партнером. Спільні цілі, справи чи обов’язки легко відстежувати на одній дошці.\n\nГрупові плани також можуть бути корисними для команд колег, які мають спільну мету, або керівників, які хочуть залучити своїх співробітників за допомогою гейміфікації.\n\n## Швидкі поради щодо використання груп\n\nОсь кілька коротких порад, які допоможуть вам почати роботу з новою групою. Ми надамо додаткові відомості в наступних розділах:\n\n* Зробіть учасника менеджером, щоб надати йому можливість створювати та редагувати завдання\n* Залишайте завдання непризначеними, якщо будь-хто може їх виконати, і їх потрібно виконати лише один раз\n* Призначте завдання одній людині, щоб переконатися, що ніхто інший не зможе виконати це завдання\n* Призначте завдання кільком людям, якщо їм усім потрібно його виконати\n* Увімкніть можливість відображати спільні завдання на вашій особистій дошці, щоб нічого не пропустити\n* Ви отримуєте винагороду за виконані вами завдання, навіть якщо ви якщо це завдання виконується кількома людьми\n* Винагороди за виконання завдань не розподіляються між членами команди\n* Використовуйте колір завдань на командній дошці, щоб оцінити середню швидкість виконання завдань\n* Регулярно переглядайте завдання на командній дошці, щоб переконатися, що вони все ще актуальні\n* Пропуск щоденки не завдасть шкоди ні вам, ні вашій команді, але колір завдання погіршиться\n\n## Як інші учасники групи можуть створювати завдання?\n\nСтворювати завдання можуть лише лідер групи та менеджери. Якщо ви хочете, щоб учасник групи міг створювати завдання, вам слід підвищити його до менеджера, перейшовши на вкладку «Інформація про групу», переглянувши список учасників і натиснувши піктограму з крапкою біля їх імені.\n\n## Як працює призначення завдання?\n\nГрупові плани дають вам унікальну можливість призначати завдання іншим учасникам групи. Призначення завдання чудово підходить для делегування. Якщо ви комусь доручили завдання, інші учасники не зможуть його виконати.\n\nВи також можете призначити завдання кільком людям, якщо його потрібно виконати більше ніж одному учаснику. Наприклад, якщо кожен має почистити зуби, створіть завдання та призначте його кожному учаснику групи. Усі вони зможуть відзначити його й отримати за це свої індивідуальні винагороди. Головне завдання буде відображено як виконане, коли всі його відзначать як виконане.\n\n## Як працюють непризначені завдання?\n\nНепризначене завдання може виконати будь-хто в групі, тому залиште завдання непризначеними, щоб дозволити будь-якому учаснику виконати його. Наприклад, винести сміття. Той, хто виніс сміття, може відмітити непризначене завдання, і воно відображатиметься як виконане для всіх.\n\n## Як працює скидання синхронізованого дня?\n\nСпільні завдання буде скинуто одночасно для всіх, щоб підтримувати синхронізацію спільної панелі завдань. Цей час відображається на спільній панелі завдань і визначається часом початку дня керівника групи. Оскільки спільні завдання скидаються автоматично, ви не матимете можливості завершити вчорашні невиконані спільні щоденники, коли увійдете наступного ранку.\n\nСпільні щоденники не завдадуть шкоди, якщо їх пропустити, однак їхній колір погіршиться, щоб допомогти візуалізувати прогрес. Ми не хочемо, щоб спільний досвід був негативним!\n\n## Як я можу використовувати свою групу в мобільних програмах?\n\nХоча мобільні програми ще не повністю підтримують усі функції групових планів, ви все одно можете виконувати спільні завдання з програм для iOS та Android, скопіювавши їх на особисту дошку завдань. Ви можете ввімкнути цей параметр у налаштуваннях мобільних програм або на панелі групових завдань у версії браузера. Тепер відкриті та призначені спільні завдання відображатимуться на вашій персональній панелі завдань на всіх платформах.\n\n## Яка різниця між спільними завданнями групи та випробуваннями?\n\nСпільні дошки завдань у груповому плані є більш динамічними, ніж випробування, оскільки їх можна постійно оновлювати та з ними можна взаємодіяти. Випробування чудові, якщо у вас є один набір завдань, які потрібно розіслати багатьом людям.\n\nГрупові плани також є платною функцією, тоді як випробування доступні для всіх безкоштовно.\n\nВи не можете призначати певні завдання у випробуваннях, а також випробування не мають спільного часу скидання. Загалом випробування пропонують менше контролю та прямої взаємодії." } diff --git a/website/common/locales/uk/gear.json b/website/common/locales/uk/gear.json index 7c688eaeab..46082fc7c7 100644 --- a/website/common/locales/uk/gear.json +++ b/website/common/locales/uk/gear.json @@ -149,32 +149,32 @@ "weaponSpecialWinter2015RogueText": "Крижаний шип", "weaponSpecialWinter2015RogueNotes": "Ви дійсно, точно, абсолютно щойно підняли це з землі. Збільшує силу на <%= str %>. Обмежене видання зимового спорядження 2014-2015.", "weaponSpecialWinter2015WarriorText": "Гумко-Меч", - "weaponSpecialWinter2015WarriorNotes": "Можливо, цей чудовий меч приваблює монстрів... але ви готові прийняти цей виклик! Збільшує силу на <%= str %>. Лімітована серія зимового спорядження 2014-2015.", + "weaponSpecialWinter2015WarriorNotes": "Можливо, цей чудовий меч приваблює монстрів... але ви готові прийняти цей виклик! Збільшує силу на <%= str %>. Лімітована серія зимового спорядження 2014-2015.", "weaponSpecialWinter2015MageText": "Жезл зимового сяйва", "weaponSpecialWinter2015MageNotes": "Світло цього кришталевого жезла наповнює серця бадьорістю. Збільшує інтелект на <%= int %> і сприйняття на <%= per %>. Лімітована серія зимового спорядження 2014-2015.", "weaponSpecialWinter2015HealerText": "Заспокійливий скіпетр", "weaponSpecialWinter2015HealerNotes": "Цей скіпетр зігріває хворі м’язи та знімає стрес. Збільшує інтелект на <%= int %>. Лімітована серія зимового спорядження 2014-2015.", "weaponSpecialSpring2015RogueText": "Вибуховий писк", "weaponSpecialSpring2015RogueNotes": "Нехай звук не вводить вас в оману – ця вибухівка справді потужна. Збільшує силу на <%= str %>. Обмежене видання весняного спорядження 2015.", - "weaponSpecialSpring2015WarriorText": "", + "weaponSpecialSpring2015WarriorText": "Кістяна бита", "weaponSpecialSpring2015WarriorNotes": "", - "weaponSpecialSpring2015MageText": "", + "weaponSpecialSpring2015MageText": "Чарівна паличка", "weaponSpecialSpring2015MageNotes": "", - "weaponSpecialSpring2015HealerText": "", + "weaponSpecialSpring2015HealerText": "Котяча брязкальце", "weaponSpecialSpring2015HealerNotes": "", - "weaponSpecialSummer2015RogueText": "", + "weaponSpecialSummer2015RogueText": "Корал-випалювач", "weaponSpecialSummer2015RogueNotes": "", - "weaponSpecialSummer2015WarriorText": "", + "weaponSpecialSummer2015WarriorText": "Сонячна риба-меч", "weaponSpecialSummer2015WarriorNotes": "", - "weaponSpecialSummer2015MageText": "", + "weaponSpecialSummer2015MageText": "Посох віщуна", "weaponSpecialSummer2015MageNotes": "", - "weaponSpecialSummer2015HealerText": "", + "weaponSpecialSummer2015HealerText": "Паличка хвиль", "weaponSpecialSummer2015HealerNotes": "", - "weaponSpecialFall2015RogueText": "", + "weaponSpecialFall2015RogueText": "Кажаняча сокира", "weaponSpecialFall2015RogueNotes": "Боягузливі завдання тремтять при виді цієї сокири!. Збільшує силу на <%= str %>. Лімітований випуск осені 2015.", - "weaponSpecialFall2015WarriorText": "", + "weaponSpecialFall2015WarriorText": "Дерев'яна дошка", "weaponSpecialFall2015WarriorNotes": "", - "weaponSpecialFall2015MageText": "", + "weaponSpecialFall2015MageText": "Зачарована нитка", "weaponSpecialFall2015MageNotes": "", "weaponSpecialFall2015HealerText": "", "weaponSpecialFall2015HealerNotes": "", diff --git a/website/common/locales/uk/generic.json b/website/common/locales/uk/generic.json index 2d72cd457d..7f1cb8a9c0 100644 --- a/website/common/locales/uk/generic.json +++ b/website/common/locales/uk/generic.json @@ -95,7 +95,7 @@ "achievementStressbeastText": "Допоміг перемогти огидного Стресозвіра під час події Winter Wonderland в 2014 році!", "achievementBurnout": "Рятівник Квітучих полів", "achievementBurnoutText": "Допоміг перемогти Вигорання та відновити Духів Виснаження під час Осіннього фестивалю 2015 року!", - "achievementBewilder": "Рятівник Містіфлаїнга", + "achievementBewilder": "Рятівник Хмарополя", "achievementBewilderText": "Допомігли перемогти Забудівника під час Весняного Летючого Івенту 2016!", "achievementDysheartener": "Рятівник розбитих серцем", "achievementDysheartenerText": "Допомогли перемогти Серцеїда під час подій до Дня святого Валентина у 2018 році!", diff --git a/website/common/locales/uk/limited.json b/website/common/locales/uk/limited.json index 2ef60ee177..88ab33d873 100644 --- a/website/common/locales/uk/limited.json +++ b/website/common/locales/uk/limited.json @@ -148,7 +148,7 @@ "discountBundle": "комплект", "g1g1Announcement": "Акція \"Подаруй підписку - отримай підписку\" діє прямо зараз!", "g1g1Details": "Подаруйте підписку другу, і Ви отримаєте таку ж підписку безкоштовно!", - "g1g1Limitations": "Це обмежена подія, яка розпочнеться 6 грудня о 8:00 за європейським часом (13:00 UTC) і завершиться 6 січня о 20:00 за європейським часом (1:00 UTC). Ця акція застосовується лише тоді, коли Ви даруєте іншому жителю Habitica. Якщо Ви або Ваш одержувач подарунка вже маєте підписку, подарована підписка додасть місяці кредиту, який буде використаний лише після скасування або закінчення терміну дії поточної підписки.", + "g1g1Limitations": "Це обмежена подія, яка розпочнеться 5 грудня о 8:00 за європейським часом (13:00 UTC) і завершиться 8 січня о 23:59 за європейським часом (9 січня 04:59 UTC). Ця акція застосовується лише тоді, коли ви даруєте іншому жителю Habitica. Якщо ви або ваш одержувач подарунка вже маєте підписку, подарована підписка додасть місяці кредиту, який буде використаний лише після скасування або закінчення терміну дії поточної підписки.", "limitations": "Обмеження", "g1g1HowItWorks": "Введіть ім’я користувача облікового запису, якому ви хочете подарувати. Звідти виберіть додаткову довжину, яку ви хочете подарувати, і відмітьте. Ваш рахунок автоматично буде винагороджений тим самим рівнем передплати, який ви щойно подарували.", "howItWorks": "Як це робиться", @@ -232,5 +232,9 @@ "fall2022WatcherHealerSet": "Підглядач (цілитель)", "fall2022KappaRogueSet": "Каппа (розбійник)", "gemSaleHow": "У період від <%= eventStartOrdinal %> <%= eventStartMonth %> до <%= eventEndOrdinal %> просто придбайте будь-який набір самоцвітів, як зазвичай, і на ваш рахунок буде також зараховано їх акційну кількість. Більше дорогоцінних каменів, які можна витратити, поділитися чи зберегти для будь-яких майбутніх покупок!", - "gemSaleLimitations": "Ця акція діє лише протягом обмеженого часу. Ця подія починається <%= eventStartOrdinal %> <%= eventStartMonth %> о 15:00 за Києвом (12:00 UTC) і закінчиться <%= eventEndOrdinal %> <%= eventStartMonth %> о 03:00 за Києвом ( 00:00 UTC). Промо-акція діє тільки при покупці самоцвітів для себе." + "gemSaleLimitations": "Ця акція діє лише протягом обмеженого часу. Ця подія починається <%= eventStartOrdinal %> <%= eventStartMonth %> о 15:00 за Києвом (12:00 UTC) і закінчиться <%= eventEndOrdinal %> <%= eventStartMonth %> о 03:00 за Києвом ( 00:00 UTC). Промо-акція діє тільки при покупці самоцвітів для себе.", + "winter2023WalrusWarriorSet": "Морж (воїн)", + "winter2023FairyLightsMageSet": "Казкові вогні (маг)", + "winter2023CardinalHealerSet": "Кардинал (маг)", + "winter2023RibbonRogueSet": "Стрічка (розбійник)" } diff --git a/website/common/locales/uk/questscontent.json b/website/common/locales/uk/questscontent.json index 28d654cdf4..56188d6eb8 100644 --- a/website/common/locales/uk/questscontent.json +++ b/website/common/locales/uk/questscontent.json @@ -1,7 +1,7 @@ { "questEvilSantaText": "Санта-Мисливець", - "questEvilSantaNotes": "Ви чуєте агонізований рев глибоко в крижаних полях. Ви слідуєте за гарчанням, перерваним гудінням, - до галявини в лісі, де ви бачите повністю дорослого білого ведмедя. Він перебуває в клітках і в кайданах, бореться за своє життя. Танцює на верхній частині клітини злісний маленький чорт, одягнений у порваний костюм. Переможіть Санта-звіролова та рятуйте звіра!

Примітка : „Санта-звіролов” нагороджує досягнення, яке можна скласти, але дає рідкісне кріплення, яке можна додати до вашої стайні лише один раз.", - "questEvilSantaCompletion": "Санта-звіролов сердито кричить і тікає в ніч. Вдячна ведмедиця крізь рев і гарчання намагається вам щось сказати. Ви повертаєте її до стайні, де Матвей, доглядач тварин, слухає її розповідь, зітхнувши від жаху. У неї є дитинча! Воно втекло на крижані поля, коли маму-ведмедицю схопили.", + "questEvilSantaNotes": "Ви чуєте агонізований рев глибоко в крижаних полях. Ви слідуєте за гарчанням, перерваним гудінням, - до галявини в лісі, де ви бачите повністю дорослого білого ведмедя. Він перебуває в клітках і в кайданах, бореться за своє життя. Танцює на верхній частині клітини злісний маленький чорт, одягнений у порваний костюм. Переможіть Санту-звіролова та врятуйте звіра!

Примітка : „Санта-звіролов” нагороджує досягнення, яке можна скласти, але дає рідкісне кріплення, яке можна додати до вашої стайні лише один раз.", + "questEvilSantaCompletion": "Санта-звіролов сердито кричить і тікає в ніч. Вдячна ведмедиця крізь рев і гарчання намагається вам щось сказати. Ви повертаєте її до хліву, де Матвей, доглядач тварин, слухає її розповідь, зітхнувши від жаху. У неї є дитинча! Воно втекло на крижані поля, коли маму-ведмедицю схопили.", "questEvilSantaBoss": "Санта Звіролов", "questEvilSantaDropBearCubPolarMount": "Білий ведмідь (скакун)", "questEvilSanta2Text": "Знайти дитинча", @@ -117,23 +117,23 @@ "questBasilistCompletion": "Спискозмій розсипався на папірці всіх кольорів веселки. \"Вау!\" каже @Arcosine. — Добре, що ви були тут!\" Відчуваючи себе більш досвідченим, ніж раніше, ви знаходите трохи золота серед паперів.", "questBasilistBoss": "Спискозмій", "questEggHuntText": "Яйцелови", - "questEggHuntNotes": "За ніч дивні яйця з’явилися скрізь: у стайні у Матвея, за прилавком у таверні і навіть серед яєць домашніх тварин на Ринку! Яка неприємність! \"Ніхто не знає, звідки вони з'явилися і що з них може вилупитися, - каже Меган, - але ми не можемо просто залишити їх валятися ось так! Наполегливо працюйте і шукайте, щоб допомогти мені зібрати ці таємничі яйця. Можливо, якщо Ви зберете достатньо, то щось знайдеться і для Вас...\"", + "questEggHuntNotes": "За ніч дивні яйця з’явилися скрізь: в хліву у Матвея, за прилавком у таверні і навіть серед яєць домашніх тварин на Ринку! Яка неприємність! \"Ніхто не знає, звідки вони з'явилися і що з них може вилупитися, - каже Меган, - але ми не можемо просто залишити їх валятися ось так! Наполегливо працюйте і шукайте, щоб допомогти мені зібрати ці таємничі яйця. Можливо, якщо Ви зберете достатньо, то щось знайдеться і для Вас...\"", "questEggHuntCompletion": "Ви зробили це! На знак подяки Меган дарує вам десять яєць. «Б’юся об заклад, що зілля вилуплення пофарбує їх у прекрасні кольори! І мені цікаво, що станеться, коли вони перетворяться на верхових тварин…»", "questEggHuntCollectPlainEgg": "Прості яйця", "questEggHuntDropPlainEgg": "Просте яйце", "questDilatoryText": "Жахливий Драк'он Некваптиди", - "questDilatoryNotes": "", + "questDilatoryNotes": "Нам слід було прислухатися до попереджень.

Темні блискучі очі. Древня луска. Масивні щелепи та блискучі зуби. Ми пробудили щось жахливе з тріщини: Жахливий Драк'он Некваптиди! Габітиканці з криком розбіглися навсібіч, коли він вирвався з моря, його жахливо довга шия висунулася на сотні метрів з води, розбиваючи шибки на вікнах одним тільки риком.

\"Мабуть, це те, що затягнуло Некваптиду!\" - кричить Lemoness. «Це не вага занедбаних завдань — червоні щоденки просто привернули його увагу!»

«Він сповнений магічної енергії!» @Baconsaur плаче. «Щоб прожити стільки часу, воно повинно мати можливість самовідновитися! Як ми можемо його перемогти?»

Так само, як ми перемагаємо всіх звірів — продуктивністю! Швидше, Габітико, об’єднуйся та виконуй свої завдання, і ми всі разом битимемося з цим монстром. (Немає потреби відмовлятися від попередніх квестів — ми віримо у вашу здатність завдавати подвійних ударів!) Він не атакуватиме нас окремо, але чим більше щоденок ми пропускаємо, тим ближче ми наближаємось до того, щоб спрацьовував його Удар Занехаяння — і мені анітрохи не подобається, як він дивиться на нашу Таверну....", "questDilatoryBoss": "Жахливий Драк'он Некваптиди", "questDilatoryBossRageTitle": "Удар Занехаяння", "questDilatoryBossRageDescription": "Коли ця смужка заповниться, Жахливий Драк'он Некваптиди розпочне в Габітиці великі руйнування", "questDilatoryDropMantisShrimpPet": "Рак-богомол (улюбленець)", "questDilatoryDropMantisShrimpMount": "Рак-богомол (скакун)", "questDilatoryBossRageTavern": "\"Жахливий Драк'он застосовує УДАР ЗАНЕХАЯННЯ!\"\n\nОй-ой! Незважаючи на всі наші зусилля, ми проґавили деякі щоденні справи і їхній темно-червоний колір накликав лють Драк'она! Своїм страшним Ударом Занехаяння він знищив Таверну! На щастя, у місті неподалік ми поставили Господу і завжди можна потеревенити на узбережжі... але бідолашний Бармен Даніель на власні очі побачив, як його улюблений будинок розвалився!\n\nСподіваюся, що звірюка не нападе знову!", - "questDilatoryBossRageStables": "\"Жахливий Драк'он застосовує УДАР ЗАНЕХАЯННЯ!\"\n\nОй, лишенько! Знову ми недоробили багато завдань. Драк'он спрямував свій УДАР ЗАНЕХАЯННЯ на Митька та його стайні! Тваринки-вихованці порозбігалися навсебіч. На щастя, усі ваші улюбленці, здається, у порядку!\n\nБідолашна Габітика! Сподіваюсь, такого більше не станеться. Покваптеся та виконайте усі свої завдання!", + "questDilatoryBossRageStables": "\"Жахливий Драк'он застосовує УДАР ЗАНЕХАЯННЯ!\"\n\nОй, лишенько! Знову ми недоробили багато завдань. Драк'он спрямував свій УДАР ЗАНЕХАЯННЯ на хліви Матвея! Тваринки-вихованці порозбігалися навсебіч. На щастя, з усіма вашими улюбленцями, здається, все гаразд!\n\nБідолашна Габітика! Сподіваюсь, такого більше не станеться. Покваптеся та виконайте усі свої завдання!", "questDilatoryBossRageMarket": "`Жахливий Драк'он застосовує УДАР ЗАНЕХАЯННЯ!`\n\nОоой!! Щойно Драк'он Ударом Занехаяння вщент розбив крамницю Торговця Алекса! Та, здається, ми добряче знесилили цю тварюку. Гадаю, він не має більше сил на ще один удар.\n\nТож не відступай, Габітико! Проженімо цього звіра геть з наших берегів!", "questDilatoryCompletion": "\"Подолання Жахливого Драк'она Некваптиди\"\n\nНам вдалося! Заревівши востаннє, Жахливий Драк'он падає та пливе ген далеко-далеко. На березі вишикувалися натовпи втішених габітиканців! Ми допомогли Митькові, Даніелю та Алексу відбудувати їхні будинки. Але що це?\n\n\"Мешканці повертаються!\"\n\nТепер, коли Драк'он утік, море замерехтіло кольорами. Це різнобарвний рій раків-богомолів... а серед них — сотні морських істот!\n\n\"Ми — забуті жителі Некваптиди!\", — пояснює їхній ватажок Манта. \"Коли Некваптида затонула, раки-богомоли, які жили у цих водах, з допомогою чарів перетворили нас на водяників, щоб ми змогли вижити. Але лютий Жахливий Драк'он усіх нас запроторив до темної ущелини. Ми сотні років сиділи у тому полоні, та тепер нарешті можемо відбудувати своє місто!\"\n\n\"Як подяку,\" — каже його друг @Ottl, — \"Прийміть, будь ласка, цього рака-богомола та рака-богомола скакуна, а також досвід, золото та нашу безмежну вдячність.\"\n\n\"Нагороди\"\n * Рак-богомол улюбленець\n * Рак-богомол скакун\n * Шоколад, блакитна цукрова вата, рожева цукрова вата, риба, мед, м'ясо, молоко, картопля, тухле м'ясо, полуниця", "questSeahorseText": "Перегони у Некваптиді", - "questSeahorseNotes": "Сьогодні - День перегонів. До Некваптиди прибули габітиканці з усього континенту, щоб влаштувати перегони на своїх морських кониках! Зненацька на біговій доріжці зчиняється шум та гамір і ви чуєте, як власниця морських коників @Kiwibot перекрикує рев хвиль. \"Зібрання морських коників привернуло увагу шаленого Морського жеребця!\" — гукає вона. \"Він поривається через стайні і нищить старовинну дорогу для бігу! Чи може хтось його вгамувати?\"", + "questSeahorseNotes": "Сьогодні - День перегонів. До Некваптиди прибули габітиканці з усього континенту, щоб влаштувати перегони на своїх морських кониках! Зненацька на біговій доріжці зчиняється шум та гамір і ви чуєте, як власниця морських коників @Kiwibot перекрикує рев хвиль. \"Зібрання морських коників привернуло увагу шаленого Морського жеребця!\" — гукає вона. \"Він поривається через хліви і нищить старовинну дорогу для бігу! Чи може хтось його вгамувати?\"", "questSeahorseCompletion": "Приборканий морський жеребець покірно підпливає до вас. \"Поглянь!\" — каже Kiwibot. \"Він хоче, щоб ми подбали про його діток.\" Він дає вам три яйця. \"Виростіть їх як слід,\" — каже Kiwibot. \"Приходьте на перегони коли забажаєте!\"", "questSeahorseBoss": "Морський жеребець", "questSeahorseDropSeahorseEgg": "Яйце морського коника", @@ -276,11 +276,11 @@ "questBurnoutBossRageQuests": "", "questBurnoutBossRageSeasonalShop": "", "questBurnoutBossRageTavern": "", - "questFrogText": "", + "questFrogText": "Болото Жабок Безладу", "questFrogNotes": "", "questFrogCompletion": "", - "questFrogBoss": "", - "questFrogDropFrogEgg": "", + "questFrogBoss": "Жабка Безладу", + "questFrogDropFrogEgg": "Яйце з жабеням", "questFrogUnlockText": "Розблоковує жаб’ячі яйця для придбання на ринку", "questSnakeText": "Змія зволікання", "questSnakeNotes": "", @@ -306,11 +306,11 @@ "questMonkeyBoss": "Жахливий мандрил", "questMonkeyDropMonkeyEgg": "Яйце мавпи", "questMonkeyUnlockText": "Розблоковує мавпячі яйця для придбання на ринку", - "questSnailText": "", + "questSnailText": "Равлик важкої роботи", "questSnailNotes": "", "questSnailCompletion": "", - "questSnailBoss": "", - "questSnailDropSnailEgg": "", + "questSnailBoss": "Равлик важкої роботи", + "questSnailDropSnailEgg": "Яйце з равликом", "questSnailUnlockText": "Відкриває яйця равликів для придбання на ринку", "questBewilderText": "", "questBewilderNotes": "", @@ -323,12 +323,12 @@ "questBewilderBossRageMarket": "", "questBewilderBossRageStables": "", "questBewilderBossRageBailey": "", - "questFalconText": "", - "questFalconNotes": "Гора Габітика затьмарюється нависаючою горою справ. Раніше це було місце для пікніка та насолоди почуттям досягнутого, поки занедбані завдання не вийшли з-під контролю. Зараз тут мешкають страшні Птахи Прокрастинації, нечисті істоти, які заважають жителям Габітану виконувати свої завдання!

\"Це занадто важко!\" вони переймаються @JonArinbjorn та @Onheiron. \"Це займе занадто багато часу зараз! Це не зробить ніякої різниці, якщо ви почекаєте до завтра! Чому б вам не зробити щось цікаве замість цього?\"

Більше, обітницю. Ви підніметеся на свою особисту гору завдань і переможете Птахів Прокрастинації!", - "questFalconCompletion": "", - "questFalconBoss": "", - "questFalconDropFalconEgg": "Яйце сокола", - "questFalconUnlockText": "Розблоковує купівлю сокола в яйці на ринку", + "questFalconText": "Птахи прокрастинації", + "questFalconNotes": "Гора Габітика затьмарюється нависаючою горою справ. Раніше це було місце для пікніка та насолоди почуттям досягнутого, поки занедбані завдання не вийшли з-під контролю. Зараз тут мешкають страшні Птахи Прокрастинації, нечисті істоти, які заважають жителям Габітики виконувати свої завдання!

\"Це занадто важко!\" вони переймаються @JonArinbjorn та @Onheiron. \"Це займе занадто багато часу зараз! Це не зробить ніякої різниці, якщо ви почекаєте до завтра! Чому б вам не зробити щось цікаве замість цього?\"

Більше, обітницю. Ви підніметеся на свою особисту гору завдань і переможете Птахів Прокрастинації!", + "questFalconCompletion": "Перемігши нарешті хижих птахів, ви сідаєте, щоб насолодитися краєвидом і заслуженим відпочинком.

\"Вау!\" каже @Trogdorina. «Ви виграли!»

@Squish додає: «Візьми в нагороду ці яйця, які я знайшов».", + "questFalconBoss": "Птахи прокрастинації", + "questFalconDropFalconEgg": "Яйце з соколом", + "questFalconUnlockText": "Розблоковує купівлю на ринку яєць з соколом", "questTreelingText": "Заплутане дерево", "questTreelingNotes": "", "questTreelingCompletion": "", @@ -424,10 +424,10 @@ "questSlothBoss": "Заколисливий лінивець", "questSlothDropSlothEgg": "Яйце з лінивцем", "questSlothUnlockText": "Розблоковує купівлю яєць з лінивцем на ринку", - "questTriceratopsText": "", + "questTriceratopsText": "Топчучий трицератопс", "questTriceratopsNotes": "", "questTriceratopsCompletion": "", - "questTriceratopsBoss": "", + "questTriceratopsBoss": "Топчучий трицератопс", "questTriceratopsDropTriceratopsEgg": "Яйце з трицератопсом", "questTriceratopsUnlockText": "Розблоковує купівлю на ринку яєць з трицератопсом", "questGroupStoikalmCalamity": "Лихо Залишспокою", @@ -454,16 +454,16 @@ "questStoikalmCalamity3DropShield": "", "questStoikalmCalamity3DropWeapon": "", "questGuineaPigText": "Банда морських свинок", - "questGuineaPigNotes": "", - "questGuineaPigCompletion": "", + "questGuineaPigNotes": "Ви бродите знаменитим ринком Габіт-сіті, як раптом @Pandah махає вам рукою. \"Гей, заціни-но це!\" Він вказує на коричнево-бежеве яйце, якого ви ніколи раніше не бачили.

Купець Alexander хмуриться на нього. «Я не пам’ятаю, щоб викладав таке тут. Цікаво, звідки воно взялося...» Маленька лапа перервала його роздуми.

«Жени все твоє золото, купець!» — пищить тоненький голосок, наповнений злом.

\"О ні, яйце було приманкою!\" — вигукує @mewrose. «Це жорстока і жадібна банда морських свинок! Вони ніколи не виконують щоденок, тому постійно крадуть золото, щоб купити лікувальні зілля».

«Пограбування ринку?» каже @emmavig. — Тільки не в мою зміну! Без додаткових підказок ви стрибаєте на допомогу купцю Alexander.", + "questGuineaPigCompletion": "«Ми здаємось!» Бос банди морських свинок підіймає лапки, а пухнаста голова опускається від сорому. З-під його капелюха випадає список, і @snazzyorange швидко підбирає його для доказів. «Почекай хвилинку», — кажете ви. «Це не дивно, що вам так важко розвиватись! У вас забагато щоденок. Вам не потрібні зілля для здоров’я — вам потрібна лише допомога в організації».

«Справді?» — пищить бос банди морських свинок. «Ми пограбували стільки людей через це! Будь ласка, прийміть наші яйця як вибачення за наші лихі справи».", "questGuineaPigBoss": "Банда морських свинок", - "questGuineaPigDropGuineaPigEgg": "Яйце морської свинки", + "questGuineaPigDropGuineaPigEgg": "Яйце з морською свинкою", "questGuineaPigUnlockText": "Розблоковує купівлю морської свинки в яйці на ринку", - "questPeacockText": "", + "questPeacockText": "Павич Тягни-Штовхай", "questPeacockNotes": "", "questPeacockCompletion": "", - "questPeacockBoss": "", - "questPeacockDropPeacockEgg": "", + "questPeacockBoss": "Павич Тягни-Штовхай", + "questPeacockDropPeacockEgg": "Яйце з павичем", "questPeacockUnlockText": "Розблоковує купівлю павича в яйці на ринку", "questButterflyText": "Бувай-бувай, метелику", "questButterflyNotes": "Ваша подруга-садівник @Megan надсилає вам запрошення: «Ці теплі дні — ідеальний час, щоб відвідати Сад метеликів Габітики в селищі Завданяївка. Приходьте подивитися на міграцію метеликів!» Коли ви приїжджаєте, то бачите, що сад у руїнах — зосталась тільки випалена трава та висохлі бур’яни. Було так спекотно, що габітиканці не вийшли полити квіти, а темно-червоні щоденки перетворили його на сухе, напечене сонцем і пожежонебезпечне місце. Там літає лише один метелик, і щось у ньому дивне...

«О ні! Це ідеальне місце для вилуплення Палаючого метелика», — кричить @Leephon.

«Якщо ми не спіймаємо його, він знищить усе!» задихається @Eevachu.

Час сказати \"Бувай\" цьому метелику!", @@ -471,31 +471,31 @@ "questButterflyBoss": "Палаючий метелик", "questButterflyDropButterflyEgg": "Яйце з гусінню", "questButterflyUnlockText": "Розблоковує купівлю яєць з гусінню на ринку", - "questGroupMayhemMistiflying": "", - "questMayhemMistiflying1Text": "", + "questGroupMayhemMistiflying": "Хаос у Хмарополі", + "questMayhemMistiflying1Text": "Хаос у Хмарополі (частина 1): В якій Хмаропіль переживає страшенні турботи", "questMayhemMistiflying1Notes": "", "questMayhemMistiflying1Completion": "", "questMayhemMistiflying1Boss": "Рій повітряних черепів", "questMayhemMistiflying1RageTitle": "Відродження рою", "questMayhemMistiflying1RageDescription": "Відродження рою: ця шкала росте, коли ви не завершуєте свої щоденні завдання. Коли вона заповнюється, рій повітряних черепів відновить 30% здоров’я до того, що залишилося!", "questMayhemMistiflying1RageEffect": "`Рій повітряних черепів використовує ВІДРОДЖЕННЯ РОЮ!`\n\nПідбадьорені своїми перемогами, ще більше черепів вилітає з хмар!", - "questMayhemMistiflying1DropSkeletonPotion": "", - "questMayhemMistiflying1DropWhitePotion": "", - "questMayhemMistiflying1DropArmor": "", - "questMayhemMistiflying2Text": "", + "questMayhemMistiflying1DropSkeletonPotion": "Кістяний еліксир вилуплення", + "questMayhemMistiflying1DropWhitePotion": "Білий еліксир вилуплення", + "questMayhemMistiflying1DropArmor": "Пустотливий веселковий халат посланця (броня)", + "questMayhemMistiflying2Text": "Хаос у Хмарополі (частина 2): В якій вітер посилюється", "questMayhemMistiflying2Notes": "", "questMayhemMistiflying2Completion": "", "questMayhemMistiflying2CollectRedMistiflies": "", "questMayhemMistiflying2CollectBlueMistiflies": "", "questMayhemMistiflying2CollectGreenMistiflies": "", - "questMayhemMistiflying2DropHeadgear": "", - "questMayhemMistiflying3Text": "", + "questMayhemMistiflying2DropHeadgear": "Пустотливий веселковий капюшон посланця (головний убір)", + "questMayhemMistiflying3Text": "Хаос у Хмарополі (частина 3): У якій листоноша надзвичайно грубий", "questMayhemMistiflying3Notes": "", "questMayhemMistiflying3Completion": "", "questMayhemMistiflying3Boss": "", "questMayhemMistiflying3DropPinkCottonCandy": "Рожева цукрова кулька(Food)", - "questMayhemMistiflying3DropShield": "", - "questMayhemMistiflying3DropWeapon": "", + "questMayhemMistiflying3DropShield": "Веселковий конверт (ліва рука)", + "questMayhemMistiflying3DropWeapon": "Веселковий конверт (права рука)", "featheredFriendsText": "Набір квестів \"Пернаті друзі\"", "featheredFriendsNotes": "", "questNudibranchText": "Зараження мотиваційними морськими молюсками", @@ -517,7 +517,7 @@ "witchyFamiliarsText": "Набір квестів \"Відьмині друзі\"", "witchyFamiliarsNotes": "", "questGroupLostMasterclasser": "", - "questUnlockLostMasterclasser": "", + "questUnlockLostMasterclasser": "Щоб розблокувати даний квест, виконайте останні квести із серій квестів: \"Біда у Некваптиді\", \"Хаос у Хмарополі\", \"Лихо Залишспокою\" та \"Жах у Завданялісі\".", "questLostMasterclasser1Text": "", "questLostMasterclasser1Notes": "", "questLostMasterclasser1Completion": "", @@ -552,24 +552,24 @@ "questLostMasterclasser4DropBackAccessory": "", "questLostMasterclasser4DropWeapon": "", "questLostMasterclasser4DropMount": "", - "questYarnText": "", + "questYarnText": "Заплутаний клубок", "questYarnNotes": "", "questYarnCompletion": "", - "questYarnBoss": "", - "questYarnDropYarnEgg": "", + "questYarnBoss": "Страшний Клубостр", + "questYarnDropYarnEgg": "Яйце з пряжею", "questYarnUnlockText": "Розблоковує купівлю пряжі в яйці на ринку", "winterQuestsText": "Зимовий набір квестів", "winterQuestsNotes": "", - "questPterodactylText": "", + "questPterodactylText": "П-терор-дактиль", "questPterodactylNotes": "", "questPterodactylCompletion": "", - "questPterodactylBoss": "", - "questPterodactylDropPterodactylEgg": "", + "questPterodactylBoss": "П-терор-дактиль", + "questPterodactylDropPterodactylEgg": "Яйце з птеродактилем", "questPterodactylUnlockText": "Розблоковує купівлю птеродактиля в яйці на ринку", "questBadgerText": "Не борси мені!", "questBadgerNotes": "", "questBadgerCompletion": "Ви нарешті відганяєте Борсука-Набриду й поспішаєте до його нори. У кінці тунелю ви знаходите його скарбницю феїних «сонливих» справ. Лігво виглядає покинуте, за винятком трьох яєць, які, здається, готові вилупитися.", - "questBadgerBoss": "", + "questBadgerBoss": "Борсук-набрида", "questBadgerDropBadgerEgg": "Яйце борсука", "questBadgerUnlockText": "Розблоковує купівлю борсука в яйці на ринку", "questDysheartenerText": "", @@ -595,11 +595,11 @@ "dysheartenerArtCredit": "Графічна робота @AnnDeLune", "hugabugText": "Набір квестів \"Обійми жука\"", "hugabugNotes": "", - "questSquirrelText": "", + "questSquirrelText": "Підступна білка", "questSquirrelNotes": "", "questSquirrelCompletion": "", - "questSquirrelBoss": "", - "questSquirrelDropSquirrelEgg": "", + "questSquirrelBoss": "Підступна білка", + "questSquirrelDropSquirrelEgg": "Яйце з білкою", "questSquirrelUnlockText": "Розблоковує купівлю білки в яйці на ринку", "cuddleBuddiesText": "Набір квестів \"Пухнасті друзі\"", "cuddleBuddiesNotes": "Містить «Кролик-вбивця», «Нечестивий тхір» і «Банда морських свинок». Доступний до 31 березня.", @@ -635,7 +635,7 @@ "questVelociraptorBoss": "Велоци-репер", "questVelociraptorDropVelociraptorEgg": "Яйце велоцираптора", "questVelociraptorUnlockText": "Розблоковує купівлю велоцераптора в яйці на ринку", - "evilSantaAddlNotes": "Зверніть увагу, що \"Санта-звіролов\" та «Знайди дитинча» мають досягнуті квестові досягнення, але дають рідкісного домашнього улюбленця та кріплення, який можна додати до вашої стайні лише один раз.", + "evilSantaAddlNotes": "Зверніть увагу, що \"Санта-звіролов\" та \"Знайди дитинча\" мають накопичувальні досягнення, але також дають рідкісного домашнього улюбленця та верхову тварину, яких можна додати до вашої стайні лише раз.", "questWindupDropWindupPotion": "Заводний інкубаційний еліксир", "questSolarSystemUnlockText": "Відкриває інкубаційні геліосистемні зілля для купівлі на ринку", "questSolarSystemText": "Подорож космічної концентрації", @@ -660,5 +660,15 @@ "jungleBuddiesNotes": "Містить \"Жахливий мандрил і пустотливі мавпи\", \"Заколисливий лінивець\" та \"Заплутане дерево\". Доступний до <%= date %>.", "questRobotCollectBolts": "Болти", "questRobotCollectSprings": "Пружини", - "questRobotCollectGears": "Шестерні" + "questRobotCollectGears": "Шестерні", + "questFluoriteText": "Яскравий флюоритовий страх", + "questFluoriteBoss": "Флюоритовий дух-стихійник", + "questFluoriteUnlockText": "Відкриває флюоритові зілля вилуплення для придбання на ринку", + "questStoneCollectCapricornRunes": "рун Козерога", + "questStoneDropMossyStonePotion": "Мохокам'яний інкубаційний еліксир", + "questStoneUnlockText": "Розблоковує придбання мохокам'яного інкубаційного еліксиру на ринку", + "questStoneCollectMarsRunes": "рун Марсу", + "questStoneText": "Моховий лабіринт", + "questStoneCollectMossyStones": "мохових каменів", + "questFluoriteDropFluoritePotion": "Флюоритовий інкубаційний еліксир" } diff --git a/website/common/locales/zh/achievements.json b/website/common/locales/zh/achievements.json index b3d6b2a68d..012ed7296d 100644 --- a/website/common/locales/zh/achievements.json +++ b/website/common/locales/zh/achievements.json @@ -58,7 +58,7 @@ "hideAchievements": "隐藏<%= category %>", "showAllAchievements": "列出所有<%= category %>", "onboardingCompleteDesc": "完成任务后,你获得了5个成就100枚金币。", - "earnedAchievement": "你得到了一个成就!", + "earnedAchievement": "你解锁了一个成就!", "viewAchievements": "查看成就", "letsGetStarted": "我们开始吧!", "onboardingProgress": "达成 <%= percentage %>%", @@ -69,21 +69,21 @@ "achievementTickledPinkModalText": "你集齐了所有粉色棉花糖宠物!", "achievementTickledPinkText": "已集齐所有粉色棉花糖宠物。", "achievementTickledPink": "羞脸粉生红", - "foundNewItemsCTA": "前往物品栏,尝试用新的孵化药水来孵化蛋!", - "foundNewItemsExplanation": "完成任务使你有机会找到物品,例如蛋、孵化药水和食物。", + "foundNewItemsCTA": "前往物品栏,尝试用新的孵化药水来孵化宠物蛋!", + "foundNewItemsExplanation": "完成任务使你有机会获得掉落物,例如蛋、孵化药水和食物。", "foundNewItems": "你找到了新物品!", "achievementBugBonanzaModalText": "你完成了甲虫、蝴蝶、蜗牛及蜘蛛宠物副本!", "achievementBugBonanzaText": "已完成甲虫、蝴蝶、蜗牛及蜘蛛宠物副本。", "achievementBugBonanza": "虫子富矿带", "onboardingCompleteDescSmall": "如果你想获得更多勋章,查查你的成就列表开始收集吧!", "onboardingComplete": "你完成了新手任务!", - "yourProgress": "你的成就", + "yourProgress": "你的进度", "achievementBareNecessitiesModalText": "你完成了猴子、树懒和小树宠物副本!", "achievementBareNecessitiesText": "已完成猴子、树懒和小树宠物副本。", "achievementBareNecessities": "森林王子", "achievementFreshwaterFriendsModalText": "你完成了蝾螈、青蛙和河马宠物副本!", "achievementFreshwaterFriendsText": "已完成蝾螈、青蛙和河马宠物副本。", - "achievementFreshwaterFriends": "淡水朋友", + "achievementFreshwaterFriends": "淡水伙伴", "achievementAllThatGlittersModalText": "你驯服了所有金色坐骑!", "achievementAllThatGlittersText": "已驯服所有金色坐骑。", "achievementAllThatGlitters": "闪闪发光", @@ -126,14 +126,14 @@ "achievementShadyCustomerText": "已集齐所有暗影宠物。", "achievementZodiacZookeeper": "十二生肖饲养员", "achievementZodiacZookeeperModalText": "你集齐了所有十二生肖宠物!", - "achievementZodiacZookeeperText": "已孵化所有基础颜色的十二生肖宠物。鼠、牛、兔、蛇、马、羊、猴、鸡、狼、虎、飞猪和龙!", + "achievementZodiacZookeeperText": "已孵化所有基础颜色的十二生肖宠物。鼠、牛、虎、兔、龙、蛇、马、羊、猴、鸡、狼和飞猪!", "achievementBirdsOfAFeather": "展翅高飞", "achievementBirdsOfAFeatherText": "已孵化所有基础颜色的飞行宠物:飞猪、猫头鹰、鹦鹉、翼龙、狮鹫和猎鹰!", "achievementBirdsOfAFeatherModalText": "你集齐了所有飞行宠物!", "achievementReptacularRumbleModalText": "你集齐了所有爬行宠物!", "achievementGroupsBeta2022": "交互测试者", - "achievementGroupsBeta2022ModalText": "你和团队通过测试和反馈来帮助Habitica发展壮大!", - "achievementGroupsBeta2022Text": "你和团队为Habitica测试提供了宝贵的反馈意见。", + "achievementGroupsBeta2022ModalText": "你和你的团队通过测试和反馈来帮助Habitica发展壮大!", + "achievementGroupsBeta2022Text": "你和你的团队为Habitica测试提供了宝贵的反馈意见。", "achievementReptacularRumble": "爬宠街斗", "achievementReptacularRumbleText": "已孵化所有基础颜色的爬虫宠物:鳄鱼、翼龙、蛇、三角龙、海龟、霸王龙和迅猛龙!", "achievementWoodlandWizard": "林地巫师", diff --git a/website/common/locales/zh/backgrounds.json b/website/common/locales/zh/backgrounds.json index 1bd952d315..138a0553bb 100644 --- a/website/common/locales/zh/backgrounds.json +++ b/website/common/locales/zh/backgrounds.json @@ -10,35 +10,35 @@ "backgroundFairyRingText": "蘑菇圈", "backgroundFairyRingNotes": "在蘑菇圈里和小仙子舞蹈。", "backgroundForestText": "森林", - "backgroundForestNotes": "在夏季的森林漫步。", + "backgroundForestNotes": "漫步在夏季的森林。", "backgrounds072014": "第2组:2014年7月推出", "backgroundCoralReefText": "珊瑚礁", - "backgroundCoralReefNotes": "在珊瑚礁轻松地游泳。", + "backgroundCoralReefNotes": "惬意地在珊瑚礁附近游泳。", "backgroundOpenWatersText": "海洋", "backgroundOpenWatersNotes": "享受茫茫大海。", "backgroundSeafarerShipText": "船舶", "backgroundSeafarerShipNotes": "搭乘一艘船舶。", "backgrounds082014": "第3组:2014年8月推出", "backgroundCloudsText": "云端", - "backgroundCloudsNotes": "在云端中飞翔。", + "backgroundCloudsNotes": "在云端飞翔。", "backgroundDustyCanyonsText": "尘封的峡谷", "backgroundDustyCanyonsNotes": "在尘封的峡谷漫游。", "backgroundVolcanoText": "火山", - "backgroundVolcanoNotes": "在熔岩中泡一泡。", + "backgroundVolcanoNotes": "在熔岩里泡一泡。", "backgrounds092014": "第4组:2014年9月推出", "backgroundThunderstormText": "雷暴", - "backgroundThunderstormNotes": "在暴风骤雨中被雷劈。", + "backgroundThunderstormNotes": "在暴风骤雨中享受雷霆。", "backgroundAutumnForestText": "秋季森林", - "backgroundAutumnForestNotes": "在秋季的森林漫步。", + "backgroundAutumnForestNotes": "漫步在秋季的森林。", "backgroundHarvestFieldsText": "田地", - "backgroundHarvestFieldsNotes": "努力耕田。", + "backgroundHarvestFieldsNotes": "努力耕耘。", "backgrounds102014": "第5组:2014年10月推出", "backgroundGraveyardText": "墓地", "backgroundGraveyardNotes": "探访一个毛骨悚然的墓地。", "backgroundHauntedHouseText": "鬼屋", - "backgroundHauntedHouseNotes": "潜行通过一间鬼屋。", + "backgroundHauntedHouseNotes": "悄悄穿过一间鬼屋。", "backgroundPumpkinPatchText": "南瓜田", - "backgroundPumpkinPatchNotes": "在南瓜田雕刻杰克南瓜灯笼。", + "backgroundPumpkinPatchNotes": "在南瓜田里雕刻杰克南瓜灯。", "backgrounds112014": "第6组:2014年11月推出", "backgroundHarvestFeastText": "大丰收", "backgroundHarvestFeastNotes": "享受着一场大丰收。", @@ -49,17 +49,17 @@ "backgrounds122014": "第7组:2014年12月推出", "backgroundIcebergText": "冰山", "backgroundIcebergNotes": "在冰山上滑雪。", - "backgroundTwinklyLightsText": "冬天一闪一闪的光", + "backgroundTwinklyLightsText": "冬日闪光", "backgroundTwinklyLightsNotes": "漫步在满是节日灯光的树林中。", "backgroundSouthPoleText": "南极", "backgroundSouthPoleNotes": "造访冰雪纷飞的南极。", "backgrounds012015": "第8组:2015年1月推出", "backgroundIceCaveText": "冰穴", - "backgroundIceCaveNotes": "掉进冰穴。", + "backgroundIceCaveNotes": "落入冰穴。", "backgroundFrigidPeakText": "寒冰山顶", "backgroundFrigidPeakNotes": "登上寒冰山顶。", "backgroundSnowyPinesText": "落雪的松林", - "backgroundSnowyPinesNotes": "躲在落雪的松林中。", + "backgroundSnowyPinesNotes": "躲入落雪的松林中。", "backgrounds022015": "第9组:2015年2月推出", "backgroundBlacksmithyText": "铁匠坊", "backgroundBlacksmithyNotes": "铁匠坊里的工人。", @@ -69,7 +69,7 @@ "backgroundDistantCastleNotes": "守卫远方的城堡。", "backgrounds032015": "第10组:2015年3月推出", "backgroundSpringRainText": "春雨", - "backgroundSpringRainNotes": "在春天的细雨中跳舞。", + "backgroundSpringRainNotes": "在春天的细雨中舞蹈。", "backgroundStainedGlassText": "花窗玻璃", "backgroundStainedGlassNotes": "欣赏绚丽的彩色玻璃花窗。", "backgroundRollingHillsText": "起伏的山丘", @@ -108,7 +108,7 @@ "backgroundSunsetSavannahText": "日落的大草原", "backgroundSunsetSavannahNotes": "悄悄穿过日落的大草原。", "backgroundTwinklyPartyLightsText": "闪耀的派对灯光", - "backgroundTwinklyPartyLightsNotes": "在闪耀的派对灯光下跳舞!", + "backgroundTwinklyPartyLightsNotes": "在闪耀的派对灯光下舞蹈!", "backgrounds092015": "第16组:2015年9月推出", "backgroundMarketText": "Habitica市场", "backgroundMarketNotes": "在Habitica市场里购物。", @@ -129,7 +129,7 @@ "backgroundNightDunesText": "夜幕沙丘", "backgroundNightDunesNotes": "在夜幕沙丘中静静穿行。", "backgroundSunsetOasisText": "日落绿洲", - "backgroundSunsetOasisNotes": "沐浴在日落绿洲中。", + "backgroundSunsetOasisNotes": "在日落绿洲里沐浴。", "backgrounds122015": "第19组:2015年12月推出", "backgroundAlpineSlopesText": "滑雪山坡", "backgroundAlpineSlopesNotes": "在山坡上滑雪。", @@ -155,20 +155,20 @@ "backgroundDeepMineText": "深矿", "backgroundDeepMineNotes": "在深矿中发现稀有金属。", "backgroundRainforestText": "热带雨林", - "backgroundRainforestNotes": "到雨林中冒险。", + "backgroundRainforestNotes": "冒险进入雨林。", "backgroundStoneCircleText": "巨石阵", - "backgroundStoneCircleNotes": "在巨石阵中释放魔法。", + "backgroundStoneCircleNotes": "在巨石阵里释放魔法。", "backgrounds042016": "第23组:2016年4月推出", "backgroundArcheryRangeText": "射箭场", - "backgroundArcheryRangeNotes": "箭射红心师资准。", - "backgroundGiantFlowersText": "巨大的花", + "backgroundArcheryRangeNotes": "在射箭场练习。", + "backgroundGiantFlowersText": "巨人之花", "backgroundGiantFlowersNotes": "在巨大的花顶嬉戏。", "backgroundRainbowsEndText": "彩虹尽头", "backgroundRainbowsEndNotes": "我欲穿花寻路,直入白云深处。", "backgrounds052016": "第24组:2016年5月推出", "backgroundBeehiveText": "蜂窝", "backgroundBeehiveNotes": "作蜜不忙采蜜忙,蜜成又带百花香。", - "backgroundGazeboText": "亭子", + "backgroundGazeboText": "观景亭", "backgroundGazeboNotes": "寒食寻芳游不足,溪亭还醉绿杨烟。", "backgroundTreeRootsText": "树根", "backgroundTreeRootsNotes": "探索茂密的树根。", @@ -181,14 +181,14 @@ "backgroundWaterfallRockNotes": "飞流直下三千尺,疑似银河落九天。", "backgrounds072016": "第26组:2016年7月推出", "backgroundAquariumText": "水族箱", - "backgroundAquariumNotes": "在水族箱中上下浮动。", + "backgroundAquariumNotes": "在水族箱里上下浮动。", "backgroundDeepSeaText": "深海", - "backgroundDeepSeaNotes": "在深海中潜水。", + "backgroundDeepSeaNotes": "深海潜水。", "backgroundDilatoryCastleText": "拖延城堡", "backgroundDilatoryCastleNotes": "游过拖延城堡。", "backgrounds082016": "第27组:2016年8月推出", "backgroundIdyllicCabinText": "田园小屋", - "backgroundIdyllicCabinNotes": "在田园小屋里隐居。", + "backgroundIdyllicCabinNotes": "隐居在田园小屋。", "backgroundMountainPyramidText": "高山金字塔", "backgroundMountainPyramidNotes": "攀登高山金字塔。", "backgroundStormyShipText": "风暴船", @@ -197,9 +197,9 @@ "backgroundCornfieldsText": "玉米地", "backgroundCornfieldsNotes": "在玉米地里享受外出的美好时光。", "backgroundFarmhouseText": "农舍", - "backgroundFarmhouseNotes": "对在你去农舍路上遇到的动物们说你好。", + "backgroundFarmhouseNotes": "对去农舍路上遇到的动物们打招呼。", "backgroundOrchardText": "果林", - "backgroundOrchardNotes": "丰收。", + "backgroundOrchardNotes": "在果园里采摘成熟的水果。", "backgrounds102016": "第29组:2016年10月推出", "backgroundSpiderWebText": "蜘蛛网", "backgroundSpiderWebNotes": "被蜘蛛网缠住。", @@ -215,8 +215,8 @@ "backgroundWindyAutumnText": "刮风的秋天", "backgroundWindyAutumnNotes": "在刮风的秋天中追赶落叶。", "incentiveBackgrounds": "简约背景套装", - "backgroundVioletText": "紫罗兰色的", - "backgroundVioletNotes": "一个充满生气的紫罗兰背景。", + "backgroundVioletText": "紫罗兰境", + "backgroundVioletNotes": "充满生气的紫罗兰境。", "backgroundBlueText": "蓝色", "backgroundBlueNotes": "基础蓝色背景。", "backgroundGreenText": "绿色", diff --git a/website/common/locales/zh/faq.json b/website/common/locales/zh/faq.json index f59adc02bf..6f4dce5d98 100644 --- a/website/common/locales/zh/faq.json +++ b/website/common/locales/zh/faq.json @@ -56,5 +56,7 @@ "androidFaqStillNeedHelp": "如果[Wiki FAQ](https://habitica.fandom.com/zh/wiki/FAQ)不能解决你的问题,请在酒馆聊天中咨询。进入方式:菜单 > 酒馆!我们很乐意为你提供帮助。", "webFaqStillNeedHelp": "如果问题列表和[Wiki FAQ](https://habitica.fandom.com/zh/wiki/FAQ)不能解决你的问题,请在[Habitica 帮助公会](https://habitica.com/groups/guild/5481ccf3-5d2d-48a9-a871-70a7380cee5a)中咨询。我们很乐意为你提供帮助。", "faqQuestion13": "什么是团队计划?", - "webFaqAnswer13": "## 团队计划是如何运作的?\n\n[团队计划](/group-plans)让您的队伍或公会可以访问类似于您的个人任务板的共享任务板! 这是一种共享的 Habitica 体验,团队中的任何成员都可以创建和检查任务。\n\n还增加一些新功能,如成员角色,状态视图和任务分配,为您提供更加可控的体验。想了解团队计划的更多信息请[访问我们的wiki](https://habitica.fandom.com/wiki/Group_Plans) !\n\n## 哪些人可以从团队计划中受益?\n\n团队计划最适合那些想要进行协作的小团队。我们建议保持在2-5名成员。\n\n对于家庭来说,不管是父母、孩子还是你和伴侣,都能从团队计划中受益。共同的目标、家务或责任很适合在一块任务板上记录下来。\n\n团队计划对于那些拥有共同目标的工作团队或希望向员工介绍游戏化的管理者也很有帮助。\n\n## 快速入门团队计划\n\n以下是一些使用技巧,可以帮助您的新团队快速起步。同时我们将在下一部分提供更多详细信息:\n\n* 让团队成员成为管理者,使他们能够创建和编辑任务\n* 如果任务是一次性且可完成的,则保留未分配的任务\n* 为确保其他成员不会误领任务,请将任务指派给一位成员\n* 如果多位成员都需要完成同一任务,请将任务进行分配\n* 在个人任务板上开启显示共享任务这一功能,避免错过任何内容\n* 即使是多人任务,在任务完成后你也能获得奖励\n* 任务完成奖励并不会在团队成员之间共享或平分\n* 您可以通过任务颜色在团队任务板上来判断平均完成率\n* 定期查看团队任务板上的任务,以确保它们是否需要保留\n* 错过一个每日任务不会对你或你的团队造成伤害,但任务颜色会下降\n\n## 团队中的其他成员如何创建任务?\n\n只有团队队长和管理者可以创建任务。如果您希望成员也能进行创建,那么您需要打开团队信息选项卡、查看成员列表并单击其名称旁的圆点图标将他们提升为管理者。\n\n## 任务分配该如何进行?\n\n团队计划赋予您将任务分配给团队其他成员的独特能力。任务分配功能非常适合委派。如果您已将任务分配给某位成员,那么其他成员将无法完成它。\n\n如果一个任务需要多位成员完成,那么您也可以将其分配给多人。例如,如果每个人都必须刷牙,请创建一个任务并将其分配给每位成员。这让他们都能勾选完成并获得个人奖励。当每个人勾选完成时,团队主任务才会显示为完成。\n\n## 未分配的任务如何处理?\n\n团队里的任何成员都可以完成未分配的任务,因此请保留那些未分配的任务,以便任何成员都能完成它。例如,倒垃圾。无论谁倒了垃圾,都可以将其勾选,并提示每个人任务已完成。\n\n## 在同步日如何重置工作?\n\n共享任务会同一时间重置,方便大家同步任务共享板。同步时间由团队队长设置后展示在任务共享板上。由于共享任务自动重置,第二天早上签到时,将没有机会勾选昨天未完成的每日共享任务。\n\n错过每日共享任务不会造成伤害,但是它们会降低颜色方便团队成员直观地了解进度。我们不希望任务共享给您带来负面的体验!\n\n## 如何在APP上使用团队?\n\n虽然APP尚未完全支持团队计划的所有功能,但通过将任务复制到个人任务板,您仍然可以在iOS和安卓的APP上完成共享任务。您可以从APP里的“设置”或浏览器版本里的团队任务板打开该选项。这将会使所有打开和分配的共享任务跨平台显示在您的个人任务板上。\n\n## 团队的共同任务与挑战有何区别?\n\n团队计划的共享任务板比挑战更具动态性,因为它可以不断更新交互。如果您有一组任务想要发给许多人,这一操作在挑战上执行会非常繁琐,但在团队计划里可以快速实现。\n\n其中团队计划是付费功能,而挑战对每个人都是免费的。\n\n你无法在挑战中分配特定的任务,挑战也没有共享日重置。总的来说,挑战提供的控制功能和直接交互功能较少。" + "webFaqAnswer13": "## 团队计划是如何运作的?\n\n[团队计划](/group-plans)让您的队伍或公会可以访问类似于您的个人任务板的共享任务板! 这是一种共享的 Habitica 体验,团队中的任何成员都可以创建和检查任务。\n\n还增加一些新功能,如成员角色,状态视图和任务分配,为您提供更加可控的体验。想了解团队计划的更多信息请[访问我们的wiki](https://habitica.fandom.com/wiki/Group_Plans) !\n\n## 哪些人可以从团队计划中受益?\n\n团队计划最适合那些想要进行协作的小团队。我们建议保持在2-5名成员。\n\n对于家庭来说,不管是父母、孩子还是你和伴侣,都能从团队计划中受益。共同的目标、家务或责任很适合在一块任务板上记录下来。\n\n团队计划对于那些拥有共同目标的工作团队或希望向员工介绍游戏化的管理者也很有帮助。\n\n## 快速入门团队计划\n\n以下是一些使用技巧,可以帮助您的新团队快速起步。同时我们将在下文提供更多信息:\n\n* 让团队成员成为管理者,使他们能够创建和编辑任务\n* 如果任务是一次性且可完成的,则保留未分配的任务\n* 为确保其他成员不会误领任务,请将任务指派给一位成员\n* 如果多位成员都需要完成同一任务,请将任务进行分配\n* 在个人任务板上开启显示共享任务这一功能,避免错过任何内容\n* 即使是多人任务,在任务完成后你也能获得奖励\n* 任务完成奖励并不会在团队成员之间共享或平分\n* 您可以通过任务颜色在团队任务板上来判断平均完成率\n* 定期查看团队任务板上的任务,以确保它们是否需要保留\n* 错过一个每日任务不会对你或你的团队造成伤害,但任务颜色会下降\n\n## 团队中的其他成员如何创建任务?\n\n只有团队队长和管理者可以创建任务。如果您希望成员也能进行创建,那么您需要打开团队信息选项卡、查看成员列表并单击其名称旁的圆点图标将他们提升为管理者。\n\n## 任务分配该如何进行?\n\n团队计划赋予您将任务分配给团队其他成员的独特能力。任务分配功能非常适合委派。如果您已将任务分配给某位成员,那么其他成员将无法完成它。\n\n如果一个任务需要多位成员完成,那么您也可以将其分配给多人。例如,如果每个人都必须刷牙,请创建一个任务并将其分配给每位成员。这让他们都能勾选完成并获得个人奖励。当每个人勾选完成时,团队主任务才会显示为完成。\n\n## 未分配的任务如何处理?\n\n团队里的任何成员都可以完成未分配的任务,因此请保留那些未分配的任务,以便任何成员都能完成它。例如,倒垃圾。无论谁倒了垃圾,都可以将其勾选,并提示每个人任务已完成。\n\n## 在同步日如何重置工作?\n\n共享任务会同一时间重置,方便大家同步共享任务板。同步时间由团队队长设置后展示在共享任务板上。由于共享任务自动重置,第二天早上签到时,将没有机会勾选昨天未完成的每日共享任务。\n\n错过每日共享任务不会造成伤害,但是它们会降低颜色方便团队成员直观地了解进度。我们不希望任务共享给您带来负面的体验!\n\n## 如何在APP上使用团队?\n\n虽然APP尚未完全支持团队计划的所有功能,但通过将任务复制到个人任务板,您仍然可以在iOS和安卓的APP上完成共享任务。您可以从APP里的“设置”或浏览器版本里的团队任务板打开该选项。这将会使所有打开和分配的共享任务跨平台显示在您的个人任务板上。\n\n## 团队的共同任务与挑战有何区别?\n\n团队计划的共享任务板比挑战更具动态性,因为它可以不断更新交互。如果您有一组任务想要发给许多人,这一操作在挑战上执行会非常繁琐,但在团队计划里可以快速实现。\n\n其中团队计划是付费功能,而挑战对每个人都是免费的。\n\n你无法在挑战中分配特定的任务,挑战也没有共享日重置。总的来说,挑战提供的控制功能和直接交互功能较少。", + "iosFaqAnswer13": "## 团队计划是如何运作的?\n\n[团队计划](/group-plans)让您的队伍或公会可以访问类似于您的个人任务板的共享任务板! 这是一种共享的 Habitica 体验,团队中的任何成员都可以创建和检查任务。\n\n还增加一些新功能,如成员角色,状态视图和任务分配,为您提供更加可控的体验。想了解团队计划的更多信息请[访问我们的wiki](https://habitica.fandom.com/wiki/Group_Plans) !\n\n## 哪些人可以从团队计划中受益?\n\n团队计划最适合那些想要进行协作的小团队。我们建议保持在2-5名成员。\n\n对于家庭来说,不管是父母、孩子还是你和伴侣,都能从团队计划中受益。共同的目标、家务或责任很适合在一块任务板上记录下来。\n\n团队计划对于那些拥有共同目标的工作团队或希望向员工介绍游戏化的管理者也很有帮助。\n\n## 快速入门团队计划\n\n以下是一些使用技巧,可以帮助您的新团队快速起步。同时我们将在下文提供更多信息:\n\n* 让团队成员成为管理者,使他们能够创建和编辑任务\n* 如果任务是一次性且可完成的,则保留未分配的任务\n* 为确保其他成员不会误领任务,请将任务指派给一位成员\n* 如果多位成员都需要完成同一任务,请将任务进行分配\n* 在个人任务板上开启显示共享任务这一功能,避免错过任何内容\n* 即使是多人任务,在任务完成后你也能获得奖励\n* 任务完成奖励并不会在团队成员之间共享或平分\n* 您可以通过任务颜色在团队任务板上来判断平均完成率\n* 定期查看团队任务板上的任务,以确保它们是否需要保留\n* 错过一个每日任务不会对你或你的团队造成伤害,但任务颜色会下降\n\n## 团队中的其他成员如何创建任务?\n\n只有团队队长和管理者可以创建任务。如果您希望成员也能进行创建,那么您需要打开团队信息选项卡、查看成员列表并单击其名称旁的圆点图标将他们提升为管理者。\n\n## 任务分配该如何进行?\n\n团队计划赋予您将任务分配给团队其他成员的独特能力。任务分配功能非常适合委派。如果您已将任务分配给某位成员,那么其他成员将无法完成它。\n\n如果一个任务需要多位成员完成,那么您也可以将其分配给多人。例如,如果每个人都必须刷牙,请创建一个任务并将其分配给每位成员。这让他们都能勾选完成并获得个人奖励。当每个人勾选完成时,团队主任务才会显示为完成。\n\n## 未分配的任务如何处理?\n\n团队里的任何成员都可以完成未分配的任务,因此请保留那些未分配的任务,以便任何成员都能完成它。例如,倒垃圾。无论谁倒了垃圾,都可以将其勾选,并提示每个人任务已完成。\n\n## 在同步日如何重置工作?\n\n共享任务会同一时间重置,方便大家同步共享任务板。同步时间由团队队长设置后展示在共享任务板上。由于共享任务自动重置,第二天早上签到时,将没有机会勾选昨天未完成的每日共享任务。\n\n错过每日共享任务不会造成伤害,但是它们会降低颜色方便团队成员直观地了解进度。我们不希望任务共享给您带来负面的体验!\n\n## 如何在APP上使用团队?\n\n虽然APP尚未完全支持团队计划的所有功能,但通过将任务复制到个人任务板,您仍然可以在iOS和安卓的APP上完成共享任务。您可以从APP里的“设置”或浏览器版本里的团队任务板打开该选项。这将会使所有打开和分配的共享任务跨平台显示在您的个人任务板上。\n\n## 团队的共同任务与挑战有何区别?\n\n团队计划的共享任务板比挑战更具动态性,因为它可以不断更新交互。如果您有一组任务想要发给许多人,这一操作在挑战上执行会非常繁琐,但在团队计划里可以快速实现。\n\n其中团队计划是付费功能,而挑战对每个人都是免费的。\n\n你无法在挑战中分配特定的任务,挑战也没有共享日重置。总的来说,挑战提供的控制功能和直接交互功能较少。", + "androidFaqAnswer13": "## 团队计划是如何运作的?\n\n[团队计划](/group-plans)让您的队伍或公会可以访问类似于您的个人任务板的共享任务板! 这是一种共享的 Habitica 体验,团队中的任何成员都可以创建和检查任务。\n\n还增加一些新功能,如成员角色,状态视图和任务分配,为您提供更加可控的体验。想了解团队计划的更多信息请[访问我们的wiki](https://habitica.fandom.com/wiki/Group_Plans) !\n\n## 哪些人可以从团队计划中受益?\n\n团队计划最适合那些想要进行协作的小团队。我们建议保持在2-5名成员。\n\n对于家庭来说,不管是父母、孩子还是你和伴侣,都能从团队计划中受益。共同的目标、家务或责任很适合在一块任务板上记录下来。\n\n团队计划对于那些拥有共同目标的工作团队或希望向员工介绍游戏化的管理者也很有帮助。\n\n## 快速入门团队计划\n\n以下是一些使用技巧,可以帮助您的新团队快速起步。同时我们将在下文提供更多信息:\n\n* 让团队成员成为管理者,使他们能够创建和编辑任务\n* 如果任务是一次性且可完成的,则保留未分配的任务\n* 为确保其他成员不会误领任务,请将任务指派给一位成员\n* 如果多位成员都需要完成同一任务,请将任务进行分配\n* 在个人任务板上开启显示共享任务这一功能,避免错过任何内容\n* 即使是多人任务,在任务完成后你也能获得奖励\n* 任务完成奖励并不会在团队成员之间共享或平分\n* 您可以通过任务颜色在团队任务板上来判断平均完成率\n* 定期查看团队任务板上的任务,以确保它们是否需要保留\n* 错过一个每日任务不会对你或你的团队造成伤害,但任务颜色会下降\n\n## 团队中的其他成员如何创建任务?\n\n只有团队队长和管理者可以创建任务。如果您希望成员也能进行创建,那么您需要打开团队信息选项卡、查看成员列表并单击其名称旁的圆点图标将他们提升为管理者。\n\n## 任务分配该如何进行?\n\n团队计划赋予您将任务分配给团队其他成员的独特能力。任务分配功能非常适合委派。如果您已将任务分配给某位成员,那么其他成员将无法完成它。\n\n如果一个任务需要多位成员完成,那么您也可以将其分配给多人。例如,如果每个人都必须刷牙,请创建一个任务并将其分配给每位成员。这让他们都能勾选完成并获得个人奖励。当每个人勾选完成时,团队主任务才会显示为完成。\n\n## 未分配的任务如何处理?\n\n团队里的任何成员都可以完成未分配的任务,因此请保留那些未分配的任务,以便任何成员都能完成它。例如,倒垃圾。无论谁倒了垃圾,都可以将其勾选,并提示每个人任务已完成。\n\n## 在同步日如何重置工作?\n\n共享任务会同一时间重置,方便大家同步共享任务板。同步时间由团队队长设置后展示在共享任务板上。由于共享任务自动重置,第二天早上签到时,将没有机会勾选昨天未完成的每日共享任务。\n\n错过每日共享任务不会造成伤害,但是它们会降低颜色方便团队成员直观地了解进度。我们不希望任务共享给您带来负面的体验!\n\n## 如何在APP上使用团队?\n\n虽然APP尚未完全支持团队计划的所有功能,但通过将任务复制到个人任务板,您仍然可以在iOS和安卓的APP上完成共享任务。您可以从APP里的“设置”或浏览器版本里的团队任务板打开该选项。这将会使所有打开和分配的共享任务跨平台显示在您的个人任务板上。\n\n## 团队的共同任务与挑战有何区别?\n\n团队计划的共享任务板比挑战更具动态性,因为它可以不断更新交互。如果您有一组任务想要发给许多人,这一操作在挑战上执行会非常繁琐,但在团队计划里可以快速实现。\n\n其中团队计划是付费功能,而挑战对每个人都是免费的。\n\n你无法在挑战中分配特定的任务,挑战也没有共享日重置。总的来说,挑战提供的控制功能和直接交互功能较少。" } diff --git a/website/common/locales/zh/gear.json b/website/common/locales/zh/gear.json index 1e079cdba2..284470ff36 100644 --- a/website/common/locales/zh/gear.json +++ b/website/common/locales/zh/gear.json @@ -2676,7 +2676,7 @@ "shieldArmoireSoftVioletPillowNotes": "一个聪明的战士任何征战会装枕头。保护你自己免受迟滞诱发的恐慌…… 即使就在睡觉。增加<%= int %>点智力。魔法衣橱:紫家居服套装(3/3)。", "shieldArmoireGardenersSpadeText": "园丁铲", "shieldArmoireGardenersSpadeNotes": "如果你在园里开掘、找地下宝藏、还是修暗道,这个可信任的铲总在你的旁边。增加<%= str %>点力量。魔法衣橱:园丁套装(3/4)。", - "shieldArmoireSpanishGuitarNotes": "叮咚!叮咚!叮叮咚咚!将弹这把吉他集会你的队伍去一个音乐会还是典礼。增加<%= per %>点感知和<%= int %>点智力。魔法衣橱:乐器套装1 (2/3)", + "shieldArmoireSpanishGuitarNotes": "叮咚!叮咚!叮叮咚咚!通过弹奏吉他召集你的队伍去参加音乐会或庆祝活动。增加<%= per %>点感知和<%= int %>点智力。魔法衣橱:乐器套装1(2/3)", "shieldArmoireSnareDrumText": "小鼓", "shieldArmoireSnareDrumNotes": "嗒嗒嗒!将打这面鼓集会你的队伍去游行还是行军打仗。增加<%= con %>点体质和<%= int %>点智力。魔法衣橱:乐器套装1 (3/3)", "shieldArmoireSpanishGuitarText": "西班牙吉他", @@ -2769,5 +2769,20 @@ "armorSpecialWinter2023RogueText": "打包带", "armorSpecialWinter2023WarriorNotes": "这款结实的海象套装非常适合半夜穿上后去海边散步。增加<%=con%>点体质。2022-2023年冬季限定版装备。", "armorSpecialWinter2023MageNotes": "仅仅是有灯亮着,并不能让你成为一棵树……也许明年还有机会。增加<%=int%>点智力。2022-2023年冬季限定版装备。", - "armorSpecialWinter2023HealerText": "红衣主教套装" + "armorSpecialWinter2023HealerText": "红衣主教套装", + "armorSpecialWinter2023HealerNotes": "这套明亮的红衣非常适合你,穿上它解决你的难题。增加<%= con %>点体质。2022-2023年冬季限定版装备。", + "headSpecialWinter2023RogueText": "礼品蝴蝶结", + "headSpecialNye2022Text": "梦幻派对帽", + "headSpecialNye2022Notes": "你收到了一顶梦幻派对帽!新年钟声响起时,请自豪地戴上它!没有属性加成。", + "headSpecialWinter2023HealerNotes": "这顶主教头盔非常适合你戴着吹口哨和唱歌来预示冬天的到来。增加<%= int %>点智力。2022-2023年冬季限定版装备。", + "shieldSpecialWinter2023WarriorNotes": "海象说,现在是时候谈论这些事情了:牡蛎的外壳、冬天的钟声、远方的歌者,盾里的珍珠无处寻觅,新的一年带来惊喜!增加<%= con %>点体质。2022-2023年冬季限定版装备。", + "headSpecialWinter2023RogueNotes": "人们“解开”你头发的诱惑会给你练习闪避的机会。增加<%= per %>点感知。2022-2023年冬季限定版装备。", + "headSpecialWinter2023WarriorText": "海象头盔", + "headSpecialWinter2023WarriorNotes": "这顶海象头盔非常适合你戴着与朋友聊天或去参加晚宴。增加<%= str %>点力量。2022-2023年冬季限定版装备。", + "headSpecialWinter2023MageText": "童话之冠", + "headSpecialWinter2023MageNotes": "你是用星夜药水孵化出来的吗?因为你我的眼睛里满是星星。增加<%= per %>点感知。2022-2023年冬季限定版装备。", + "headSpecialWinter2023HealerText": "主教头盔", + "shieldSpecialWinter2023WarriorText": "牡蛎护盾", + "shieldSpecialWinter2023HealerText": "清爽果酱", + "shieldSpecialWinter2023HealerNotes": "你的霜雪之歌将抚慰所有听众的心灵。增加<%= con %>点体质。2022-2023年冬季限定版装备。" } diff --git a/website/common/locales/zh/limited.json b/website/common/locales/zh/limited.json index 58f6645a9d..9e17eafa29 100644 --- a/website/common/locales/zh/limited.json +++ b/website/common/locales/zh/limited.json @@ -196,7 +196,7 @@ "winter2021ArcticExplorerHealerSet": "极地探索者(医者)", "winter2021WinterMoonMageSet": "冬月(法师)", "winter2021IceFishingWarriorSet": "冰钓(战士)", - "g1g1Limitations": "该限时活动从美国东部时间12月16日上午8:00(13:00 UTC)开始,于美国东部时间1月6日下午8:00(1:00 UTC)结束。该促销活动只允许您将订阅赠送给另一位Habitica居民。如果您或您的礼物接受者当前的订阅尚未到期或取消,赠送所获得的订阅将进行顺延。", + "g1g1Limitations": "该限时活动从美国东部时间12月15日上午8:00(UTC 13:00)开始,于美国东部时间1月8日下午11:59(UTC 1月9日 04:59)结束。该促销活动只允许您将订阅赠送给另一位Habitica居民。如果您或您的礼物接受者当前的订阅尚未到期或取消,赠送所获得的订阅将进行顺延。", "limitations": "限制因素", "g1g1HowItWorks": "输入你想赠送订阅的账号的用户名。选择你想赠送订阅的时长。你的账号将免费获得一份相同的订阅。", "spring2021TwinFlowerRogueSet": "林奈花(盗贼)", @@ -235,5 +235,9 @@ "fall2022KappaRogueSet": "河童(盗贼)", "fall2022OrcWarriorSet": "兽人(战士)", "fall2022HarpyMageSet": "鹰身女妖(法师)", - "fall2022WatcherHealerSet": "眼魔(医者)" + "fall2022WatcherHealerSet": "眼魔(医者)", + "winter2023WalrusWarriorSet": "海象(战士)", + "winter2023FairyLightsMageSet": "仙灯(法师)", + "winter2023CardinalHealerSet": "红衣主教(医者)", + "winter2023RibbonRogueSet": "缎带(盗贼)" } diff --git a/website/common/script/content/appearance/backgrounds.js b/website/common/script/content/appearance/backgrounds.js index 78538ef4f1..49e8daabc9 100644 --- a/website/common/script/content/appearance/backgrounds.js +++ b/website/common/script/content/appearance/backgrounds.js @@ -540,6 +540,11 @@ const backgrounds = { snowy_temple: { }, winter_lake_with_swans: { }, }, + eventBackgrounds: { + birthday_bash: { + price: 0, + }, + }, timeTravelBackgrounds: { airship: { price: 1, @@ -583,7 +588,9 @@ forOwn(backgrounds, (backgroundsInSet, set) => { forOwn(backgroundsInSet, (background, bgKey) => { background.key = bgKey; background.set = set; - background.price = background.price || 7; + if (background.price !== 0) { + background.price = background.price || 7; + } background.text = background.text || t(`background${upperFirst(camelCase(bgKey))}Text`); background.notes = background.notes || t(`background${upperFirst(camelCase(bgKey))}Notes`); diff --git a/website/common/script/content/constants/events.js b/website/common/script/content/constants/events.js index c3e8522062..192c8578f2 100644 --- a/website/common/script/content/constants/events.js +++ b/website/common/script/content/constants/events.js @@ -10,11 +10,17 @@ const gemsPromo = { export const EVENTS = { noEvent: { - start: '2023-01-31T20:00-05:00', + start: '2023-02-08T23:59-05:00', end: '2023-02-14T08:00-05:00', season: 'normal', npcImageSuffix: '', }, + birthday10: { + start: '2023-01-30T08:00-05:00', + end: '2023-02-08T23:59-05:00', + season: 'birthday', + npcImageSuffix: '_birthday', + }, winter2023: { start: '2022-12-20T08:00-05:00', end: '2023-01-31T23:59-05:00', diff --git a/website/common/script/content/gear/sets/mystery.js b/website/common/script/content/gear/sets/mystery.js index e851da7513..ed445c9e09 100644 --- a/website/common/script/content/gear/sets/mystery.js +++ b/website/common/script/content/gear/sets/mystery.js @@ -97,6 +97,7 @@ const back = { 202205: { }, 202206: { }, 202301: { }, + 202302: { }, }; const body = { @@ -227,6 +228,7 @@ const headAccessory = { 202203: { }, 202212: { }, 202205: { }, + 202302: { }, 301405: { }, }; diff --git a/website/common/script/content/gear/sets/special/index.js b/website/common/script/content/gear/sets/special/index.js index 85f7e12284..8b597413e1 100644 --- a/website/common/script/content/gear/sets/special/index.js +++ b/website/common/script/content/gear/sets/special/index.js @@ -16,7 +16,8 @@ import t from '../../../translation'; import { getClassName } from '../../../../libs/getClassName'; const CURRENT_EVENT = find( - EVENTS, event => moment().isBetween(event.start, event.end) && Boolean(event.season), + EVENTS, event => moment().isBetween(event.start, event.end) + && ['winter', 'spring', 'summer', 'fall'].includes(event.season), ); const gearEvents = pickBy(EVENTS, event => event.gear); @@ -798,6 +799,12 @@ const armor = { winter2023Healer: { set: 'winter2023CardinalHealerSet', }, + birthday2023: { + text: t('armorSpecialBirthday2023Text'), + notes: t('armorSpecialBirthday2023Notes'), + value: 0, + canOwn: ownsItem('armor_special_birthday2023'), + }, }; const armorStats = { @@ -923,6 +930,12 @@ const back = { value: 0, canOwn: ownsItem('back_special_namingDay2020'), }, + anniversary: { + text: t('backSpecialAnniversaryText'), + notes: t('backSpecialAnniversaryNotes'), + value: 0, + canOwn: ownsItem('back_special_anniversary'), + }, }; const body = { @@ -992,6 +1005,12 @@ const body = { value: 0, canOwn: ownsItem('body_special_namingDay2018'), }, + anniversary: { + text: t('bodySpecialAnniversaryText'), + notes: t('bodySpecialAnniversaryNotes'), + value: 0, + canOwn: ownsItem('body_special_anniversary'), + }, }; const eyewear = { @@ -1140,6 +1159,12 @@ const eyewear = { value: 0, canOwn: ownsItem('eyewear_special_ks2019'), }, + anniversary: { + text: t('eyewearSpecialAnniversaryText'), + notes: t('eyewearSpecialAnniversaryNotes'), + value: 0, + canOwn: ownsItem('eyewear_special_anniversary'), + }, }; const head = { diff --git a/website/common/script/content/hatching-potions.js b/website/common/script/content/hatching-potions.js index 9ba437243d..2d45722a9a 100644 --- a/website/common/script/content/hatching-potions.js +++ b/website/common/script/content/hatching-potions.js @@ -70,13 +70,13 @@ const premium = { value: 2, text: t('hatchingPotionShimmer'), limited: true, - event: EVENTS.spring2022, + event: EVENTS.birthday10, _addlNotes: t('eventAvailabilityReturning', { - availableDate: t('dateEndMarch'), - previousDate: t('marchYYYY', { year: 2020 }), + availableDate: t('dateStartFebruary'), + previousDate: t('marchYYYY', { year: 2022 }), }), canBuy () { - return moment().isBefore(EVENTS.spring2022.end); + return moment().isBetween(EVENTS.birthday10.start, EVENTS.birthday10.end); }, }, Fairy: { @@ -109,13 +109,13 @@ const premium = { value: 2, text: t('hatchingPotionAquatic'), limited: true, - event: EVENTS.summer2022, + event: EVENTS.birthday10, _addlNotes: t('eventAvailabilityReturning', { - availableDate: t('dateEndJuly'), - previousDate: t('augustYYYY', { year: 2020 }), + availableDate: t('dateStartFebruary'), + previousDate: t('julyYYYY', { year: 2022 }), }), canBuy () { - return moment().isBetween(EVENTS.summer2022.start, EVENTS.summer2022.end); + return moment().isBetween(EVENTS.birthday10.start, EVENTS.birthday10.end); }, }, Ember: { @@ -188,12 +188,12 @@ const premium = { text: t('hatchingPotionPeppermint'), limited: true, _addlNotes: t('eventAvailabilityReturning', { - availableDate: t('dateEndJanuary'), - previousDate: t('januaryYYYY', { year: 2018 }), + availableDate: t('dateStartFebruary'), + previousDate: t('januaryYYYY', { year: 2022 }), }), - event: EVENTS.winter2022, + event: EVENTS.birthday10, canBuy () { - return moment().isBetween(EVENTS.winter2022.start, EVENTS.winter2022.end); + return moment().isBetween(EVENTS.birthday10.start, EVENTS.birthday10.end); }, }, StarryNight: { @@ -239,13 +239,13 @@ const premium = { value: 2, text: t('hatchingPotionGlow'), limited: true, - event: EVENTS.fall2021, + event: EVENTS.birthday10, _addlNotes: t('eventAvailabilityReturning', { - availableDate: t('dateEndOctober'), - previousDate: t('septemberYYYY', { year: 2019 }), + availableDate: t('dateStartFebruary'), + previousDate: t('octoberYYYY', { year: 2021 }), }), canBuy () { - return moment().isBefore(EVENTS.fall2021.end); + return moment().isBetween(EVENTS.birthday10.start, EVENTS.birthday10.end); }, }, Frost: { @@ -286,13 +286,13 @@ const premium = { value: 2, text: t('hatchingPotionCelestial'), limited: true, - event: EVENTS.spring2022, + event: EVENTS.birthday10, _addlNotes: t('eventAvailabilityReturning', { - availableDate: t('dateEndMarch'), - previousDate: t('marchYYYY', { year: 2020 }), + availableDate: t('dateStartFebruary'), + previousDate: t('marchYYYY', { year: 2022 }), }), canBuy () { - return moment().isBefore(EVENTS.spring2022.end); + return moment().isBetween(EVENTS.birthday10.start, EVENTS.birthday10.end); }, }, Sunshine: { @@ -399,13 +399,13 @@ const premium = { value: 2, text: t('hatchingPotionSandSculpture'), limited: true, - event: EVENTS.summer2021, + event: EVENTS.birthday10, _addlNotes: t('eventAvailabilityReturning', { - availableDate: t('dateEndJuly'), - previousDate: t('juneYYYY', { year: 2020 }), + availableDate: t('dateStartFebruary'), + previousDate: t('julyYYYY', { year: 2021 }), }), canBuy () { - return moment().isBetween(EVENTS.summer2021.start, EVENTS.summer2021.end); + return moment().isBetween(EVENTS.birthday10.start, EVENTS.birthday10.end); }, }, Windup: { @@ -426,26 +426,26 @@ const premium = { value: 2, text: t('hatchingPotionVampire'), limited: true, - event: EVENTS.fall2022, + event: EVENTS.birthday10, _addlNotes: t('eventAvailabilityReturning', { - availableDate: t('dateEndOctober'), - previousDate: t('octoberYYYY', { year: 2021 }), + availableDate: t('dateStartFebruary'), + previousDate: t('octoberYYYY', { year: 2022 }), }), canBuy () { - return moment().isBetween(EVENTS.fall2022.start, EVENTS.fall2022.end); + return moment().isBetween(EVENTS.birthday10.start, EVENTS.birthday10.end); }, }, AutumnLeaf: { value: 2, text: t('hatchingPotionAutumnLeaf'), limited: true, - event: EVENTS.potions202111, + event: EVENTS.birthday10, _addlNotes: t('eventAvailabilityReturning', { - availableDate: t('dateEndNovember'), - previousDate: t('novemberYYYY', { year: 2020 }), + availableDate: t('dateStartFebruary'), + previousDate: t('novemberYYYY', { year: 2021 }), }), canBuy () { - return moment().isBefore(EVENTS.potions202111.end); + return moment().isBetween(EVENTS.birthday10.start, EVENTS.birthday10.end); }, }, BlackPearl: { @@ -460,12 +460,12 @@ const premium = { text: t('hatchingPotionStainedGlass'), limited: true, _addlNotes: t('eventAvailabilityReturning', { - availableDate: t('dateEndJanuary'), - previousDate: t('januaryYYYY', { year: 2021 }), + availableDate: t('dateStartFebruary'), + previousDate: t('januaryYYYY', { year: 2022 }), }), - event: EVENTS.winter2022, + event: EVENTS.birthday10, canBuy () { - return moment().isBetween(EVENTS.winter2022.start, EVENTS.winter2022.end); + return moment().isBetween(EVENTS.birthday10.start, EVENTS.birthday10.end); }, }, PolkaDot: { @@ -532,12 +532,13 @@ const premium = { value: 2, text: t('hatchingPotionPorcelain'), limited: true, - event: EVENTS.potions202208, - _addlNotes: t('premiumPotionAddlNotes', { - date: t('dateEndAugust'), + event: EVENTS.birthday10, + _addlNotes: t('eventAvailabilityReturning', { + availableDate: t('dateStartFebruary'), + previousDate: t('augustYYYY', { year: 2022 }), }), canBuy () { - return moment().isBetween(EVENTS.potions202208.start, EVENTS.potions202208.end); + return moment().isBetween(EVENTS.birthday10.start, EVENTS.birthday10.end); }, }, }; diff --git a/website/common/script/content/stable.js b/website/common/script/content/stable.js index 9119cfca23..457ecd60ee 100644 --- a/website/common/script/content/stable.js +++ b/website/common/script/content/stable.js @@ -1,4 +1,6 @@ import each from 'lodash/each'; +import moment from 'moment'; +import { EVENTS } from './constants/events'; import { drops as dropEggs, quests as questEggs, @@ -118,6 +120,9 @@ const canFindSpecial = { 'Jackalope-RoyalPurple': true, // subscription 'Wolf-Cerberus': false, // Pet once granted to backers 'Gryphon-Gryphatrice': false, // Pet once granted to kickstarter + + // Birthday Pet + 'Gryphatrice-Jubilant': false, }, mounts: { // Thanksgiving pet ladder @@ -174,6 +179,7 @@ const specialPets = { 'Fox-Veteran': 'veteranFox', 'JackOLantern-Glow': 'glowJackolantern', 'Gryphon-Gryphatrice': 'gryphatrice', + 'Gryphatrice-Jubilant': 'jubilantGryphatrice', 'JackOLantern-RoyalPurple': 'royalPurpleJackolantern', }; @@ -207,6 +213,16 @@ each(specialPets, (translationString, key) => { }; }); +Object.assign(petInfo['Gryphatrice-Jubilant'], { + canBuy () { + return moment().isBetween(EVENTS.birthday10.start, EVENTS.birthday10.end); + }, + currency: 'gems', + event: 'birthday10', + value: 60, + purchaseType: 'pets', +}); + each(specialMounts, (translationString, key) => { mountInfo[key] = { key, diff --git a/website/common/script/errors/commonErrorMessages.js b/website/common/script/errors/commonErrorMessages.js index b71b37c5b4..d434faa4a8 100644 --- a/website/common/script/errors/commonErrorMessages.js +++ b/website/common/script/errors/commonErrorMessages.js @@ -7,6 +7,7 @@ export default { missingTypeParam: '"req.params.type" is required.', missingKeyParam: '"req.params.key" is required.', itemNotFound: 'Item "<%= key %>" not found.', + petNotFound: 'Pet "<%= key %>" not found.', questNotFound: 'Quest "<%= key %>" not found.', spellNotFound: 'Skill "<%= spellId %>" not found.', invalidQuantity: 'Quantity to purchase must be a positive whole number.', diff --git a/website/common/script/libs/shops-seasonal.config.js b/website/common/script/libs/shops-seasonal.config.js index fc6c64fde3..4fc7d58dab 100644 --- a/website/common/script/libs/shops-seasonal.config.js +++ b/website/common/script/libs/shops-seasonal.config.js @@ -7,28 +7,27 @@ import { } from '../content/constants'; const CURRENT_EVENT = find( - EVENTS, event => moment().isBetween(event.start, event.end) && Boolean(event.season), + EVENTS, event => moment().isBetween(event.start, event.end) + && ['winter', 'spring', 'summer', 'fall'].includes(event.season), ); -const SHOP_OPEN = CURRENT_EVENT && ['winter', 'spring', 'summer', 'fall'].includes(CURRENT_EVENT.season); - export default { - opened: SHOP_OPEN, + opened: CURRENT_EVENT, - currentSeason: SHOP_OPEN ? upperFirst(CURRENT_EVENT.season) : 'Closed', + currentSeason: CURRENT_EVENT ? upperFirst(CURRENT_EVENT.season) : 'Closed', dateRange: { start: CURRENT_EVENT ? moment(CURRENT_EVENT.start) : moment().subtract(1, 'days').toDate(), end: CURRENT_EVENT ? moment(CURRENT_EVENT.end) : moment().subtract(1, 'seconds').toDate(), }, - availableSets: SHOP_OPEN + availableSets: CURRENT_EVENT ? [ ...SEASONAL_SETS[CURRENT_EVENT.season], ] : [], - pinnedSets: SHOP_OPEN + pinnedSets: CURRENT_EVENT ? { rogue: 'winter2023RibbonRogueSet', warrior: 'winter2023WalrusWarriorSet', @@ -36,13 +35,13 @@ export default { healer: 'winter2023CardinalHealerSet', } : {}, - availableSpells: SHOP_OPEN && moment().isBetween('2022-12-27T08:00-05:00', CURRENT_EVENT.end) + availableSpells: CURRENT_EVENT && moment().isBetween('2022-12-27T08:00-05:00', CURRENT_EVENT.end) ? [ 'snowball', ] : [], - availableQuests: SHOP_OPEN && CURRENT_EVENT.season === 'winter' + availableQuests: CURRENT_EVENT && CURRENT_EVENT.season === 'winter' ? [ 'evilsanta', 'evilsanta2', diff --git a/website/common/script/ops/buy/buy.js b/website/common/script/ops/buy/buy.js index 07a0ab10e6..f5a0920320 100644 --- a/website/common/script/ops/buy/buy.js +++ b/website/common/script/ops/buy/buy.js @@ -13,6 +13,7 @@ import hourglassPurchase from './hourglassPurchase'; import errorMessage from '../../libs/errorMessage'; import { BuyGemOperation } from './buyGem'; import { BuyQuestWithGemOperation } from './buyQuestGem'; +import { BuyPetWithGemOperation } from './buyPetGem'; import { BuyHourglassMountOperation } from './buyMount'; // @TODO: remove the req option style. Dependency on express structure is an anti-pattern @@ -86,7 +87,12 @@ export default async function buy ( break; } case 'pets': - buyRes = hourglassPurchase(user, req, analytics); + if (key === 'Gryphatrice-Jubilant') { + const buyOp = new BuyPetWithGemOperation(user, req, analytics); + buyRes = await buyOp.purchase(); + } else { + buyRes = hourglassPurchase(user, req, analytics); + } break; case 'quest': { const buyOp = new BuyQuestWithGoldOperation(user, req, analytics); diff --git a/website/common/script/ops/buy/buyPetGem.js b/website/common/script/ops/buy/buyPetGem.js new file mode 100644 index 0000000000..fb1b52d822 --- /dev/null +++ b/website/common/script/ops/buy/buyPetGem.js @@ -0,0 +1,61 @@ +import get from 'lodash/get'; +import { + BadRequest, + NotFound, +} from '../../libs/errors'; +import content from '../../content/index'; + +import errorMessage from '../../libs/errorMessage'; +import { AbstractGemItemOperation } from './abstractBuyOperation'; + +export class BuyPetWithGemOperation extends AbstractGemItemOperation { // eslint-disable-line import/prefer-default-export, max-len + multiplePurchaseAllowed () { // eslint-disable-line class-methods-use-this + return false; + } + + getItemKey () { + return this.key; + } + + getItemValue (item) { // eslint-disable-line class-methods-use-this + return item.value / 4; + } + + getItemType () { // eslint-disable-line class-methods-use-this + return 'pet'; + } + + extractAndValidateParams (user, req) { + this.key = get(req, 'params.key'); + const { key } = this; + if (!key) throw new BadRequest(errorMessage('missingKeyParam')); + + const item = content.petInfo[key]; + + if (!item) throw new NotFound(errorMessage('petNotFound', { key })); + + this.canUserPurchase(user, item); + } + + canUserPurchase (user, item) { + if (item && user.items.pets[item.key]) { + throw new BadRequest(this.i18n('petsAlreadyOwned')); + } + + super.canUserPurchase(user, item); + } + + async executeChanges (user, item, req) { + user.items.pets[item.key] = 5; + if (user.markModified) user.markModified('items.pets'); + + await this.subtractCurrency(user, item); + + return [ + user.items.pets, + this.i18n('messageBought', { + itemText: item.text(req.language), + }), + ]; + } +} diff --git a/website/common/script/ops/unlock.js b/website/common/script/ops/unlock.js index 8ab2711135..342c9dc7a9 100644 --- a/website/common/script/ops/unlock.js +++ b/website/common/script/ops/unlock.js @@ -251,9 +251,10 @@ export default async function unlock (user, req = {}, analytics) { return invalidSet(req); } - cost = getIndividualItemPrice(setType, item, req); - unlockedAlready = alreadyUnlocked(user, setType, firstPath); + if (!unlockedAlready) { + cost = getIndividualItemPrice(setType, item, req); + } // Since only an item is being unlocked here, // remove all the other items from the set diff --git a/website/server/controllers/api-v3/tasks/groups.js b/website/server/controllers/api-v3/tasks/groups.js index 963fcdbf91..1945ce2dc1 100644 --- a/website/server/controllers/api-v3/tasks/groups.js +++ b/website/server/controllers/api-v3/tasks/groups.js @@ -68,6 +68,7 @@ api.createGroupTasks = { category: 'behavior', taskType: task.type, groupID: group._id, + headers: req.headers, }); }); }, @@ -255,6 +256,7 @@ api.assignTask = { category: 'behavior', taskType: task.type, groupID: group._id, + headers: req.headers, }); }, }; diff --git a/website/server/controllers/api-v3/user.js b/website/server/controllers/api-v3/user.js index e81b171f5c..c422541705 100644 --- a/website/server/controllers/api-v3/user.js +++ b/website/server/controllers/api-v3/user.js @@ -975,7 +975,7 @@ api.disableClasses = { * @apiGroup User * * @apiParam (Path) {String="gems","eggs","hatchingPotions","premiumHatchingPotions" - ,"food","quests","gear"} type Type of item to purchase. + ,"food","quests","gear","pets"} type Type of item to purchase. * @apiParam (Path) {String} key Item's key (use "gem" for purchasing gems) * * @apiParam (Body) {Integer} [quantity=1] Count of items to buy. diff --git a/website/server/controllers/top-level/payments/amazon.js b/website/server/controllers/top-level/payments/amazon.js index 4371400161..8aa231d684 100644 --- a/website/server/controllers/top-level/payments/amazon.js +++ b/website/server/controllers/top-level/payments/amazon.js @@ -75,12 +75,14 @@ api.checkout = { middlewares: [authWithHeaders()], async handler (req, res) { const { user } = res.locals; - const { orderReferenceId, gift, gemsBlock } = req.body; + const { + orderReferenceId, gift, gemsBlock, sku, + } = req.body; if (!orderReferenceId) throw new BadRequest('Missing req.body.orderReferenceId'); await amzLib.checkout({ - gemsBlock, gift, user, orderReferenceId, headers: req.headers, + gemsBlock, gift, sku, user, orderReferenceId, headers: req.headers, }); res.respond(200); diff --git a/website/server/controllers/top-level/payments/iap.js b/website/server/controllers/top-level/payments/iap.js index 00ccb31642..7214c63ba0 100644 --- a/website/server/controllers/top-level/payments/iap.js +++ b/website/server/controllers/top-level/payments/iap.js @@ -23,7 +23,7 @@ api.iapAndroidVerify = { middlewares: [authWithHeaders()], async handler (req, res) { if (!req.body.transaction) throw new BadRequest(res.t('missingReceipt')); - const googleRes = await googlePayments.verifyGemPurchase({ + const googleRes = await googlePayments.verifyPurchase({ user: res.locals.user, receipt: req.body.transaction.receipt, signature: req.body.transaction.signature, @@ -120,7 +120,7 @@ api.iapiOSVerify = { middlewares: [authWithHeaders()], async handler (req, res) { if (!req.body.transaction) throw new BadRequest(res.t('missingReceipt')); - const appleRes = await applePayments.verifyGemPurchase({ + const appleRes = await applePayments.verifyPurchase({ user: res.locals.user, receipt: req.body.transaction.receipt, gift: req.body.gift, diff --git a/website/server/controllers/top-level/payments/paypal.js b/website/server/controllers/top-level/payments/paypal.js index 1432ceea90..05d35b3f20 100644 --- a/website/server/controllers/top-level/payments/paypal.js +++ b/website/server/controllers/top-level/payments/paypal.js @@ -27,10 +27,13 @@ api.checkout = { const gift = req.query.gift ? JSON.parse(req.query.gift) : undefined; req.session.gift = req.query.gift; - const { gemsBlock } = req.query; + const { gemsBlock, sku } = req.query; req.session.gemsBlock = gemsBlock; + req.session.sku = sku; - const link = await paypalPayments.checkout({ gift, gemsBlock, user: res.locals.user }); + const link = await paypalPayments.checkout({ + gift, gemsBlock, sku, user: res.locals.user, + }); if (req.query.noRedirect) { res.respond(200); @@ -56,14 +59,15 @@ api.checkoutSuccess = { const { user } = res.locals; const gift = req.session.gift ? JSON.parse(req.session.gift) : undefined; delete req.session.gift; - const { gemsBlock } = req.session; + const { gemsBlock, sku } = req.session; delete req.session.gemsBlock; + delete req.session.sku; if (!paymentId) throw new BadRequest(apiError('missingPaymentId')); if (!customerId) throw new BadRequest(apiError('missingCustomerId')); await paypalPayments.checkoutSuccess({ - user, gemsBlock, gift, paymentId, customerId, headers: req.headers, + user, gemsBlock, gift, paymentId, customerId, headers: req.headers, sku, }); if (req.query.noRedirect) { diff --git a/website/server/controllers/top-level/payments/stripe.js b/website/server/controllers/top-level/payments/stripe.js index 71835d8c41..9fed9d613d 100644 --- a/website/server/controllers/top-level/payments/stripe.js +++ b/website/server/controllers/top-level/payments/stripe.js @@ -27,13 +27,13 @@ api.createCheckoutSession = { async handler (req, res) { const { user } = res.locals; const { - gift, sub: subKey, gemsBlock, coupon, groupId, + gift, sub: subKey, gemsBlock, coupon, groupId, sku, } = req.body; const sub = subKey ? shared.content.subscriptionBlocks[subKey] : false; const session = await stripePayments.createCheckoutSession({ - user, gemsBlock, gift, sub, groupId, coupon, + user, gemsBlock, gift, sub, groupId, coupon, sku, }); res.respond(200, { diff --git a/website/server/libs/payments/amazon.js b/website/server/libs/payments/amazon.js index 6947f3e5ea..9e72340877 100644 --- a/website/server/libs/payments/amazon.js +++ b/website/server/libs/payments/amazon.js @@ -46,6 +46,7 @@ api.constants = { GIFT_TYPE_SUBSCRIPTION: 'subscription', METHOD_BUY_GEMS: 'buyGems', + METHOD_BUY_SKU_ITEM: 'buySkuItem', METHOD_CREATE_SUBSCRIPTION: 'createSubscription', PAYMENT_METHOD: 'Amazon Payments', PAYMENT_METHOD_GIFT: 'Amazon Payments (Gift)', @@ -110,7 +111,7 @@ api.authorize = function authorize (inputSet) { */ api.checkout = async function checkout (options = {}) { const { - gift, user, orderReferenceId, headers, gemsBlock: gemsBlockKey, + gift, user, orderReferenceId, headers, gemsBlock: gemsBlockKey, sku, } = options; let amount; let gemsBlock; @@ -127,6 +128,12 @@ api.checkout = async function checkout (options = {}) { } else if (gift.type === this.constants.GIFT_TYPE_SUBSCRIPTION) { amount = common.content.subscriptionBlocks[gift.subscription.key].price; } + } else if (sku) { + if (sku === 'Pet-Gryphatrice-Jubilant') { + amount = 9.99; + } else { + throw new NotFound('SKU not found.'); + } } else { gemsBlock = getGemsBlock(gemsBlockKey); amount = gemsBlock.price / 100; @@ -171,12 +178,16 @@ api.checkout = async function checkout (options = {}) { // execute payment let method = this.constants.METHOD_BUY_GEMS; + if (sku) { + method = this.constants.METHOD_BUY_SKU_ITEM; + } const data = { user, paymentMethod: this.constants.PAYMENT_METHOD, headers, gemsBlock, + sku, }; if (gift) { diff --git a/website/server/libs/payments/apple.js b/website/server/libs/payments/apple.js index 8c0fd44184..bc99889587 100644 --- a/website/server/libs/payments/apple.js +++ b/website/server/libs/payments/apple.js @@ -2,7 +2,7 @@ import moment from 'moment'; import shared from '../../../common'; import iap from '../inAppPurchases'; import payments from './payments'; -import { getGemsBlock, validateGiftMessage } from './gems'; +import { validateGiftMessage } from './gems'; import { NotAuthorized, BadRequest, @@ -22,7 +22,7 @@ api.constants = { RESPONSE_NO_ITEM_PURCHASED: 'NO_ITEM_PURCHASED', }; -api.verifyGemPurchase = async function verifyGemPurchase (options) { +api.verifyPurchase = async function verifyPurchase (options) { const { gift, user, receipt, headers, } = options; @@ -44,7 +44,6 @@ api.verifyGemPurchase = async function verifyGemPurchase (options) { if (purchaseDataList.length === 0) { throw new NotAuthorized(api.constants.RESPONSE_NO_ITEM_PURCHASED); } - let correctReceipt = false; // Purchasing one item at a time (processing of await(s) below is sequential not parallel) for (const purchaseData of purchaseDataList) { @@ -62,46 +61,16 @@ api.verifyGemPurchase = async function verifyGemPurchase (options) { userId: user._id, }); - let gemsBlockKey; - switch (purchaseData.productId) { // eslint-disable-line default-case - case 'com.habitrpg.ios.Habitica.4gems': - gemsBlockKey = '4gems'; - break; - case 'com.habitrpg.ios.Habitica.20gems': - case 'com.habitrpg.ios.Habitica.21gems': - gemsBlockKey = '21gems'; - break; - case 'com.habitrpg.ios.Habitica.42gems': - gemsBlockKey = '42gems'; - break; - case 'com.habitrpg.ios.Habitica.84gems': - gemsBlockKey = '84gems'; - break; - } - if (!gemsBlockKey) throw new NotAuthorized(api.constants.RESPONSE_INVALID_ITEM); - const gemsBlock = getGemsBlock(gemsBlockKey); - - if (gift) { - gift.type = 'gems'; - if (!gift.gems) gift.gems = {}; - gift.gems.amount = shared.content.gems[gemsBlock.key].gems; - } - - if (gemsBlock) { - correctReceipt = true; - await payments.buyGems({ // eslint-disable-line no-await-in-loop - user, - gift, - paymentMethod: api.constants.PAYMENT_METHOD_APPLE, - gemsBlock, - headers, - }); - } + await payments.buySkuItem({ // eslint-disable-line no-await-in-loop + user, + gift, + paymentMethod: api.constants.PAYMENT_METHOD_APPLE, + sku: purchaseData.productId, + headers, + }); } } - if (!correctReceipt) throw new NotAuthorized(api.constants.RESPONSE_INVALID_ITEM); - return appleRes; }; diff --git a/website/server/libs/payments/google.js b/website/server/libs/payments/google.js index 9beac9b2a4..d61c16a5eb 100644 --- a/website/server/libs/payments/google.js +++ b/website/server/libs/payments/google.js @@ -8,7 +8,7 @@ import { } from '../errors'; import { model as IapPurchaseReceipt } from '../../models/iapPurchaseReceipt'; import { model as User } from '../../models/user'; -import { getGemsBlock, validateGiftMessage } from './gems'; +import { validateGiftMessage } from './gems'; const api = {}; @@ -21,7 +21,7 @@ api.constants = { RESPONSE_STILL_VALID: 'SUBSCRIPTION_STILL_VALID', }; -api.verifyGemPurchase = async function verifyGemPurchase (options) { +api.verifyPurchase = async function verifyPurchase (options) { const { gift, user, receipt, signature, headers, } = options; @@ -61,39 +61,11 @@ api.verifyGemPurchase = async function verifyGemPurchase (options) { userId: user._id, }); - let gemsBlockKey; - - switch (receiptObj.productId) { // eslint-disable-line default-case - case 'com.habitrpg.android.habitica.iap.4gems': - gemsBlockKey = '4gems'; - break; - case 'com.habitrpg.android.habitica.iap.20gems': - case 'com.habitrpg.android.habitica.iap.21gems': - gemsBlockKey = '21gems'; - break; - case 'com.habitrpg.android.habitica.iap.42gems': - gemsBlockKey = '42gems'; - break; - case 'com.habitrpg.android.habitica.iap.84gems': - gemsBlockKey = '84gems'; - break; - } - - if (!gemsBlockKey) throw new NotAuthorized(this.constants.RESPONSE_INVALID_ITEM); - - const gemsBlock = getGemsBlock(gemsBlockKey); - - if (gift) { - gift.type = 'gems'; - if (!gift.gems) gift.gems = {}; - gift.gems.amount = shared.content.gems[gemsBlock.key].gems; - } - - await payments.buyGems({ + await payments.buySkuItem({ // eslint-disable-line no-await-in-loop user, gift, - paymentMethod: this.constants.PAYMENT_METHOD_GOOGLE, - gemsBlock, + paymentMethod: api.constants.PAYMENT_METHOD_GOOGLE, + sku: googleRes.productId, headers, }); diff --git a/website/server/libs/payments/payments.js b/website/server/libs/payments/payments.js index 3e93bdb8d4..43bcae2f18 100644 --- a/website/server/libs/payments/payments.js +++ b/website/server/libs/payments/payments.js @@ -11,6 +11,9 @@ import { // eslint-disable-line import/no-cycle import { // eslint-disable-line import/no-cycle buyGems, } from './gems'; +import { // eslint-disable-line import/no-cycle + buySkuItem, +} from './skuItem'; import { paymentConstants } from './constants'; const api = {}; @@ -31,4 +34,6 @@ api.cancelSubscription = cancelSubscription; api.buyGems = buyGems; +api.buySkuItem = buySkuItem; + export default api; diff --git a/website/server/libs/payments/paypal.js b/website/server/libs/payments/paypal.js index 30e0a8dff3..70099e6541 100644 --- a/website/server/libs/payments/paypal.js +++ b/website/server/libs/payments/paypal.js @@ -77,7 +77,9 @@ api.paypalBillingAgreementCancel = util api.ipnVerifyAsync = util.promisify(paypalIpn.verify.bind(paypalIpn)); api.checkout = async function checkout (options = {}) { - const { gift, user, gemsBlock: gemsBlockKey } = options; + const { + gift, gemsBlock: gemsBlockKey, sku, user, + } = options; let amount; let gemsBlock; @@ -99,12 +101,17 @@ api.checkout = async function checkout (options = {}) { amount = Number(shared.content.subscriptionBlocks[gift.subscription.key].price).toFixed(2); description = 'mo. Habitica Subscription (Gift)'; } + } else if (sku) { + if (sku === 'Pet-Gryphatrice-Jubilant') { + description = 'Jubilant Gryphatrice'; + amount = 9.99; + } } else { gemsBlock = getGemsBlock(gemsBlockKey); amount = gemsBlock.price / 100; } - if (!gift || gift.type === 'gems') { + if (gemsBlock || (gift && gift.type === 'gems')) { const receiver = gift ? gift.member : user; const receiverCanGetGems = await receiver.canGetGems(); if (!receiverCanGetGems) throw new NotAuthorized(shared.i18n.t('groupPolicyCannotGetGems', receiver.preferences.language)); @@ -146,10 +153,10 @@ api.checkout = async function checkout (options = {}) { api.checkoutSuccess = async function checkoutSuccess (options = {}) { const { - user, gift, gemsBlock: gemsBlockKey, paymentId, customerId, + user, gift, gemsBlock: gemsBlockKey, paymentId, customerId, sku, } = options; - let method = 'buyGems'; + let method = sku ? 'buySkuItem' : 'buyGems'; const data = { user, customerId, @@ -164,6 +171,8 @@ api.checkoutSuccess = async function checkoutSuccess (options = {}) { data.paymentMethod = 'PayPal (Gift)'; data.gift = gift; + } else if (sku) { + data.sku = sku; } else { data.gemsBlock = getGemsBlock(gemsBlockKey); } diff --git a/website/server/libs/payments/skuItem.js b/website/server/libs/payments/skuItem.js new file mode 100644 index 0000000000..89ede4a053 --- /dev/null +++ b/website/server/libs/payments/skuItem.js @@ -0,0 +1,110 @@ +import moment from 'moment'; +import { + BadRequest, +} from '../errors'; +import shared from '../../../common'; +import { getAnalyticsServiceByEnvironment } from '../analyticsService'; +import { getGemsBlock, buyGems } from './gems'; // eslint-disable-line import/no-cycle + +const analytics = getAnalyticsServiceByEnvironment(); + +const RESPONSE_INVALID_ITEM = 'INVALID_ITEM_PURCHASED'; + +const EVENTS = { + birthday10: { + start: '2023-01-23T08:00-05:00', + end: '2023-02-01T23:59-05:00', + }, +}; + +function canBuyGryphatrice (user) { + if (!moment().isBetween(EVENTS.birthday10.start, EVENTS.birthday10.end)) return false; + if (user.items.pets['Gryphatrice-Jubilant']) return false; + return true; +} + +async function buyGryphatrice (data) { + // Double check it's available + if (!canBuyGryphatrice(data.user)) throw new BadRequest(); + const key = 'Gryphatrice-Jubilant'; + data.user.items.pets[key] = 5; + data.user.purchased.txnCount += 1; + + analytics.trackPurchase({ + uuid: data.user._id, + itemPurchased: 'Gryphatrice', + sku: `${data.paymentMethod.toLowerCase()}-checkout`, + purchaseType: 'checkout', + paymentMethod: data.paymentMethod, + quantity: 1, + gift: Boolean(data.gift), + purchaseValue: 10, + headers: data.headers, + firstPurchase: data.user.purchased.txnCount === 1, + }); + if (data.user.markModified) data.user.markModified('items.pets'); + await data.user.save(); +} + +export function canBuySkuItem (sku, user) { + switch (sku) { + case 'com.habitrpg.android.habitica.iap.pets.gryphatrice_jubilant': + case 'com.habitrpg.ios.Habitica.pets.Gryphatrice_Jubilant': + case 'Pet-Gryphatrice-Jubilant': + case 'price_0MPZ6iZCD0RifGXlLah2furv': + return canBuyGryphatrice(user); + default: + return true; + } +} + +export async function buySkuItem (data) { + let gemsBlockKey; + + switch (data.sku) { // eslint-disable-line default-case + case 'com.habitrpg.android.habitica.iap.4gems': + case 'com.habitrpg.ios.Habitica.4gems': + gemsBlockKey = '4gems'; + break; + case 'com.habitrpg.android.habitica.iap.20gems': + case 'com.habitrpg.android.habitica.iap.21gems': + case 'com.habitrpg.ios.Habitica.20gems': + case 'com.habitrpg.ios.Habitica.21gems': + gemsBlockKey = '21gems'; + break; + case 'com.habitrpg.android.habitica.iap.42gems': + case 'com.habitrpg.ios.Habitica.42gems': + gemsBlockKey = '42gems'; + break; + case 'com.habitrpg.android.habitica.iap.84gems': + case 'com.habitrpg.ios.Habitica.84gems': + gemsBlockKey = '84gems'; + break; + case 'com.habitrpg.android.habitica.iap.pets.gryphatrice_jubilant': + case 'com.habitrpg.ios.Habitica.pets.Gryphatrice_Jubilant': + case 'Pet-Gryphatrice-Jubilant': + case 'price_0MPZ6iZCD0RifGXlLah2furv': + buyGryphatrice(data); + return; + } + + if (gemsBlockKey) { + const gemsBlock = getGemsBlock(gemsBlockKey); + + if (data.gift) { + data.gift.type = 'gems'; + if (!data.gift.gems) data.gift.gems = {}; + data.gift.gems.amount = shared.content.gems[gemsBlock.key].gems; + } + + await buyGems({ + user: data.user, + gift: data.gift, + paymentMethod: data.paymentMethod, + gemsBlock, + headers: data.headers, + }); + return; + } + throw new BadRequest(RESPONSE_INVALID_ITEM); +} diff --git a/website/server/libs/payments/stripe/checkout.js b/website/server/libs/payments/stripe/checkout.js index c17dce6d93..fa4fda9217 100644 --- a/website/server/libs/payments/stripe/checkout.js +++ b/website/server/libs/payments/stripe/checkout.js @@ -24,6 +24,7 @@ export async function createCheckoutSession (options, stripeInc) { sub, groupId, coupon, + sku, } = options; // @TODO: We need to mock this, but curently we don't have correct @@ -37,6 +38,8 @@ export async function createCheckoutSession (options, stripeInc) { validateGiftMessage(gift, user); } else if (sub) { type = 'subscription'; + } else if (sku) { + type = 'sku'; } const metadata = { @@ -71,6 +74,12 @@ export async function createCheckoutSession (options, stripeInc) { price: sub.key, quantity, }]; + } else if (type === 'sku') { + metadata.sku = sku; + lineItems = [{ + price: sku, + quantity: 1, + }]; } else { const { amount, diff --git a/website/server/libs/payments/stripe/oneTimePayments.js b/website/server/libs/payments/stripe/oneTimePayments.js index cb5274e182..3989d5b507 100644 --- a/website/server/libs/payments/stripe/oneTimePayments.js +++ b/website/server/libs/payments/stripe/oneTimePayments.js @@ -22,6 +22,20 @@ function getGiftAmount (gift) { return `${(gift.gems.amount / 4) * 100}`; } +export async function applySku (session) { + const { metadata } = session; + const { userId, sku } = metadata; + const user = await User.findById(metadata.userId).exec(); + if (!user) throw new NotFound(shared.i18n.t('userWithIDNotFound', { userId })); + if (sku === 'price_0MPZ6iZCD0RifGXlLah2furv') { + await payments.buySkuItem({ + sku, user, paymentMethod: stripeConstants.PAYMENT_METHOD, + }); + } else { + throw new NotFound('SKU not found.'); + } +} + export async function getOneTimePaymentInfo (gemsBlockKey, gift, user) { let receiver = user; diff --git a/website/server/libs/payments/stripe/webhooks.js b/website/server/libs/payments/stripe/webhooks.js index 8a18fc126e..ae4d73e660 100644 --- a/website/server/libs/payments/stripe/webhooks.js +++ b/website/server/libs/payments/stripe/webhooks.js @@ -14,7 +14,7 @@ import { // eslint-disable-line import/no-cycle basicFields as basicGroupFields, } from '../../../models/group'; import shared from '../../../../common'; -import { applyGemPayment } from './oneTimePayments'; // eslint-disable-line import/no-cycle +import { applyGemPayment, applySku } from './oneTimePayments'; // eslint-disable-line import/no-cycle import { applySubscription, handlePaymentMethodChange } from './subscriptions'; // eslint-disable-line import/no-cycle const endpointSecret = nconf.get('STRIPE_WEBHOOKS_ENDPOINT_SECRET'); @@ -69,10 +69,12 @@ export async function handleWebhooks (options, stripeInc) { if (metadata.type === 'edit-card-group' || metadata.type === 'edit-card-user') { await handlePaymentMethodChange(session); - } else if (metadata.type !== 'subscription') { - await applyGemPayment(session); - } else { + } else if (metadata.type === 'subscription') { await applySubscription(session); + } else if (metadata.type === 'sku') { + await applySku(session); + } else { + await applyGemPayment(session); } break; diff --git a/website/server/libs/user/index.js b/website/server/libs/user/index.js index 35068ad3f9..4ed618b36f 100644 --- a/website/server/libs/user/index.js +++ b/website/server/libs/user/index.js @@ -97,7 +97,9 @@ function checkPreferencePurchase (user, path, item) { const itemPath = `${path}.${item}`; const appearance = _.get(common.content.appearances, itemPath); if (!appearance) return false; - if (appearance.price === 0) return true; + if (appearance.price === 0 && path !== 'background') { + return true; + } return _.get(user.purchased, itemPath); } diff --git a/website/server/models/userNotification.js b/website/server/models/userNotification.js index ea9636a1b1..dc270231bf 100644 --- a/website/server/models/userNotification.js +++ b/website/server/models/userNotification.js @@ -31,6 +31,7 @@ const NOTIFICATION_TYPES = [ 'SCORED_TASK', 'UNALLOCATED_STATS_POINTS', 'WON_CHALLENGE', + 'ITEM_RECEIVED', // notify user when they've got goodies via migration // achievement notifications 'ACHIEVEMENT', // generic achievement notification, details inside `notification.data` 'CHALLENGE_JOINED_ACHIEVEMENT',