diff --git a/chili-and-cilantro-api/src/controllers/api/user.ts b/chili-and-cilantro-api/src/controllers/api/user.ts index 91f3b0e..75f478c 100644 --- a/chili-and-cilantro-api/src/controllers/api/user.ts +++ b/chili-and-cilantro-api/src/controllers/api/user.ts @@ -274,7 +274,9 @@ export class UserController extends BaseController { ); this.sendApiMessageResponse( 200, - { message: 'Password changed successfully' } as IApiMessageResponse, + { + message: translate(StringNames.ChangePassword_Success), + } as IApiMessageResponse, res, ); } catch (error) { @@ -535,7 +537,9 @@ export class UserController extends BaseController { await this.userService.verifyEmailToken(token as string); this.sendApiMessageResponse( 200, - { message: 'Token is valid' } as IApiMessageResponse, + { + message: translate(StringNames.Common_TokenValid), + } as IApiMessageResponse, res, ); } catch (error) { @@ -564,7 +568,7 @@ export class UserController extends BaseController { RequestUserService.makeRequestUser(user); res.header('Authorization', `Bearer ${newToken}`); res.status(200).json({ - message: 'Password reset successfully', + message: translate(StringNames.ResetPassword_Success), user: requestUser, } as IUserResponse); } catch (error) { diff --git a/chili-and-cilantro-api/src/services/action.ts b/chili-and-cilantro-api/src/services/action.ts index 7513a50..f9f0e7e 100644 --- a/chili-and-cilantro-api/src/services/action.ts +++ b/chili-and-cilantro-api/src/services/action.ts @@ -118,8 +118,8 @@ export class ActionService extends BaseService { ActionType.START_GAME, { gameId: game._id, - chefId: game.hostChefId, - userId: game.hostUserId, + chefId: game.masterChefId, + userId: game.masterChefUserId, type: ActionType.START_GAME, details: {} as IStartGameDetails, round: game.currentRound, @@ -133,8 +133,8 @@ export class ActionService extends BaseService { ActionType.EXPIRE_GAME, { gameId: game._id, - chefId: game.hostChefId, - userId: game.hostUserId, + chefId: game.masterChefId, + userId: game.masterChefUserId, type: ActionType.EXPIRE_GAME, details: {} as IExpireGameDetails, round: game.currentRound, diff --git a/chili-and-cilantro-api/src/services/chef.ts b/chili-and-cilantro-api/src/services/chef.ts index b057a0a..fa5f0d4 100644 --- a/chili-and-cilantro-api/src/services/chef.ts +++ b/chili-and-cilantro-api/src/services/chef.ts @@ -17,7 +17,7 @@ export class ChefService extends BaseService { * @param game The game the chef is joining * @param user The user joining the game * @param displayName The display name of the chef - * @param host Whether the chef is the host of the game + * @param masterChef Whether the chef is the host of the game * @param chefId The id of the chef to create. If not provided, a new id will be generated * @returns A new chef document */ @@ -25,7 +25,7 @@ export class ChefService extends BaseService { game: IGameDocument, user: IUserDocument, displayName: string, - host: boolean, + masterChef: boolean, chefId?: DefaultIdType, ): Promise { const ChefModel = this.application.getModel(ModelName.Chef); @@ -39,7 +39,7 @@ export class ChefService extends BaseService { placedCards: [], lostCards: [], state: ChefState.LOBBY, - host: host, + masterChef: masterChef, }, ]); if (chefs.length !== 1) { @@ -71,7 +71,7 @@ export class ChefService extends BaseService { placedCards: [], lostCards: [], state: ChefState.LOBBY, - host: existingChef.host, + masterChef: existingChef.masterChef, }, ]); if (chefs.length !== 1) { diff --git a/chili-and-cilantro-api/src/services/game.ts b/chili-and-cilantro-api/src/services/game.ts index 3499282..33f4fd2 100644 --- a/chili-and-cilantro-api/src/services/game.ts +++ b/chili-and-cilantro-api/src/services/game.ts @@ -1,11 +1,9 @@ import { AllCardsPlacedError, - AlreadyJoinedOtherError, CardType, + ChefAlreadyJoinedError, ChefState, - constants, DefaultIdType, - GameFullError, GameInProgressError, GamePasswordMismatchError, GamePhase, @@ -15,6 +13,7 @@ import { IGameDocument, IGameListResponse, IMessageActionDocument, + IUserDocument, IncorrectGamePhaseError, InvalidActionError, InvalidGameError, @@ -23,16 +22,17 @@ import { InvalidGamePasswordError, InvalidMessageError, InvalidUserDisplayNameError, - IUserDocument, ModelName, NotEnoughChefsError, - NotHostError, + NotMasterChefError, OutOfIngredientError, OutOfOrderError, StringNames, - translate, + TooManyChefsInGameError, TurnAction, UsernameInUseError, + constants, + translate, } from '@chili-and-cilantro/chili-and-cilantro-lib'; import { IApplication } from '@chili-and-cilantro/chili-and-cilantro-node-lib'; import { ClientSession, Types } from 'mongoose'; @@ -74,7 +74,7 @@ export class GameService extends BaseService { ): Promise { const GameModel = this.application.getModel(ModelName.Game); const createdGames = await GameModel.find({ - hostUserId: userDoc._id, + masterChefUserId: userDoc._id, }) .session(session) .lean(); @@ -138,7 +138,7 @@ export class GameService extends BaseService { maxChefs: number, ): Promise { if (await this.playerService.userIsInAnyActiveGameAsync(user)) { - throw new AlreadyJoinedOtherError(); + throw new ChefAlreadyJoinedError(); } if ( !validator.matches(displayName, constants.MULTILINGUAL_STRING_REGEX) || @@ -202,8 +202,8 @@ export class GameService extends BaseService { currentChef: constants.NONE, currentPhase: GamePhase.LOBBY, currentRound: constants.NONE, - hostChefId: chefId, - hostUserId: user._id, + masterChefId: chefId, + masterChefUserId: user._id, maxChefs: maxChefs, name: gameName, ...(password ? { password: password } : {}), @@ -295,7 +295,7 @@ export class GameService extends BaseService { password: string, ): Promise { if (await this.playerService.userIsInAnyActiveGameAsync(user)) { - throw new AlreadyJoinedOtherError(); + throw new ChefAlreadyJoinedError(); } const chefNames = await this.getGameChefNamesByGameIdAsync( game._id.toString(), @@ -313,7 +313,7 @@ export class GameService extends BaseService { throw new GameInProgressError(); } if (game.chefIds.length > game.maxChefs) { - throw new GameFullError(); + throw new TooManyChefsInGameError(); } if ( !validator.matches( @@ -367,10 +367,10 @@ export class GameService extends BaseService { const newChefIds = existingGame.chefIds.map(() => new Types.ObjectId()); // find the existing chef id's index - const hostChefIndex = existingGame.chefIds.findIndex( - (chefId) => existingGame.hostChefId.toString() == chefId.toString(), + const masterChefIndex = existingGame.chefIds.findIndex( + (chefId) => existingGame.masterChefId.toString() == chefId.toString(), ); - const newHostChefId = newChefIds[hostChefIndex]; + const newMasterChefId = newChefIds[masterChefIndex]; // we need to look up the user id for all chefs in the current game const existingChefs = @@ -388,8 +388,8 @@ export class GameService extends BaseService { currentChef: constants.NONE, currentRound: constants.NONE, currentPhase: GamePhase.LOBBY, - hostChefId: newChefIds[hostChefIndex], - hostUserId: existingGame.hostUserId, + masterChefId: newChefIds[masterChefIndex], + masterChefUserId: existingGame.masterChefUserId, lastGame: existingGame._id, maxChefs: existingGame.maxChefs, name: existingGame.name, @@ -421,11 +421,11 @@ export class GameService extends BaseService { const chefs = await Promise.all(chefCreations); // Create action for game creation - this could be moved to an event or a method to encapsulate the logic - const hostChef = chefs.find( - (chef) => chef._id.toString() == newHostChefId.toString(), + const masterChef = chefs.find( + (chef) => chef._id.toString() == newMasterChefId.toString(), ); - await this.actionService.createGameAsync(newGame, hostChef, user); - return { game: newGame, chef: chefs[hostChefIndex] }; + await this.actionService.createGameAsync(newGame, masterChef, user); + return { game: newGame, chef: chefs[masterChefIndex] }; }, session); } @@ -501,8 +501,8 @@ export class GameService extends BaseService { game: IGameDocument, userId: DefaultIdType, ): Promise { - if (!(await this.playerService.isGameHostAsync(userId, game._id))) { - throw new NotHostError(); + if (!(await this.playerService.isMasterChefAsync(userId, game._id))) { + throw new NotMasterChefError(); } if (game.currentPhase !== GamePhase.LOBBY) { throw new GameInProgressError(); diff --git a/chili-and-cilantro-api/src/services/player.ts b/chili-and-cilantro-api/src/services/player.ts index cb299bb..06b3075 100644 --- a/chili-and-cilantro-api/src/services/player.ts +++ b/chili-and-cilantro-api/src/services/player.ts @@ -15,7 +15,7 @@ export class PlayerService extends BaseService { * @param gameId * @returns boolean */ - public async isGameHostAsync( + public async isMasterChefAsync( userId: DefaultIdType, gameId: DefaultIdType, ): Promise { @@ -23,7 +23,7 @@ export class PlayerService extends BaseService { try { const count = await GameModel.countDocuments({ _id: gameId, - hostUserId: userId, + masterChefUserId: userId, }); return count > 0; diff --git a/chili-and-cilantro-api/src/services/user.ts b/chili-and-cilantro-api/src/services/user.ts index 791a55b..e84a94d 100644 --- a/chili-and-cilantro-api/src/services/user.ts +++ b/chili-and-cilantro-api/src/services/user.ts @@ -81,11 +81,11 @@ export class UserService extends BaseService { token: randomBytes(constants.EMAIL_TOKEN_LENGTH).toString('hex'), lastSent: null, createdAt: Date.now(), - expiresAt: new Date(Date.now() + constants.EMAIL_TOKEN_EXPIRATION), + expiresAt: new Date(Date.now() + constants.EMAIL_TOKEN_EXPIRATION_MS), }, ]); if (emailTokens.length !== 1) { - throw new Error('Failed to create email token'); + throw new Error(translate(StringNames.Error_FailedToCreateEmailToken)); } return emailTokens[0]; } @@ -97,7 +97,7 @@ export class UserService extends BaseService { public async sendEmailToken(emailToken: IEmailTokenDocument): Promise { if ( emailToken.lastSent && - emailToken.lastSent.getTime() + constants.EMAIL_TOKEN_RESEND_INTERVAL > + emailToken.lastSent.getTime() + constants.EMAIL_TOKEN_RESEND_INTERVAL_MS > Date.now() ) { throw new EmailTokenSentTooRecentlyError(emailToken.lastSent); @@ -110,18 +110,18 @@ export class UserService extends BaseService { msg = { to: emailToken.email, from: constants.EMAIL_FROM, - subject: `${constants.APPLICATION_NAME} email confirmation`, - text: `Please click the link below to confirm your email.\r\n\r\n${verifyUrl}`, - html: `

Please click the link below to confirm your email.


${verifyUrl}

Link expires in ${constants.EMAIL_TOKEN_RESEND_INTERVAL / 1000} minutes.

`, + subject: `${translate(StringNames.Common_Site)} ${translate(StringNames.EmailToken_TitleEmailConfirm)}`, + text: `${translate(StringNames.EmailToken_ClickLinkEmailConfirm)}\r\n\r\n${verifyUrl}`, + html: `

${translate(StringNames.EmailToken_ClickLinkEmailConfirm)}


${verifyUrl}

${translate(StringNames.EmailToken_ExpiresInTemplate)}

`, }; break; case EmailTokenType.PasswordReset: msg = { to: emailToken.email, from: constants.EMAIL_FROM, - subject: `${constants.APPLICATION_NAME} password reset`, - text: `Please click the link below to reset your password.\r\n\r\n${passwordUrl}`, - html: `

Please click the link below to reset your password.


${passwordUrl}

Link expires in ${constants.EMAIL_TOKEN_RESEND_INTERVAL / 1000} minutes.

`, + subject: `${translate(StringNames.Common_Site)} ${translate(StringNames.EmailToken_TitleResetPassword)}`, + text: `${translate(StringNames.EmailToken_ClickLinkResetPassword)}\r\n\r\n${passwordUrl}`, + html: `

${translate(StringNames.EmailToken_ClickLinkResetPassword)}


${passwordUrl}

${translate(StringNames.EmailToken_ExpiresInTemplate)}

`, }; break; default: @@ -133,12 +133,12 @@ export class UserService extends BaseService { // update lastSent/expiration emailToken.lastSent = new Date(); emailToken.expiresAt = new Date( - Date.now() + constants.EMAIL_TOKEN_EXPIRATION, + Date.now() + constants.EMAIL_TOKEN_EXPIRATION_MS, ); await emailToken.save(); } catch (error) { console.error('Error sending email:', error); - throw new Error('Failed to send verification email'); + throw new Error(translate(StringNames.Error_SendTokenFailure)); } } @@ -308,7 +308,7 @@ export class UserService extends BaseService { ); const now = new Date(); const minLastSentTime = new Date( - now.getTime() - constants.EMAIL_TOKEN_RESEND_INTERVAL, + now.getTime() - constants.EMAIL_TOKEN_RESEND_INTERVAL_MS, ); // look up the most recent email token for a given user, then send it @@ -438,8 +438,7 @@ export class UserService extends BaseService { // We don't want to reveal whether an email exists in our system return { success: true, - message: - 'If an account with that email exists, a password reset link has been sent.', + message: translate(StringNames.ResetPassword_Sent), }; } @@ -447,8 +446,7 @@ export class UserService extends BaseService { if (!user.emailVerified) { return { success: false, - message: - 'Please verify your email address before resetting your password.', + message: translate(StringNames.ResetPassword_ChangeEmailFirst), }; } diff --git a/chili-and-cilantro-api/test/fixtures/chef.ts b/chili-and-cilantro-api/test/fixtures/chef.ts index 4f3d12d..3227ce5 100644 --- a/chili-and-cilantro-api/test/fixtures/chef.ts +++ b/chili-and-cilantro-api/test/fixtures/chef.ts @@ -24,7 +24,7 @@ export function generateChef( lostCards: [], userId: generateObjectId(), state: ChefState.LOBBY, - host: false, + masterChef: false, ...overrides, } as Partial; diff --git a/chili-and-cilantro-api/test/fixtures/game.ts b/chili-and-cilantro-api/test/fixtures/game.ts index ca59f42..efc1a77 100644 --- a/chili-and-cilantro-api/test/fixtures/game.ts +++ b/chili-and-cilantro-api/test/fixtures/game.ts @@ -39,14 +39,14 @@ export function generateGame( withPassword = true, overrides?: Partial, ): IGameDocument & MockedModel { - const hostChefId = generateObjectId(); - const hostUserId = generateObjectId(); + const masterChefId = generateObjectId(); + const masterChefUserId = generateObjectId(); const gameData = { _id: generateObjectId(), code: UtilityService.generateGameCode(), name: faker.lorem.words(3), ...(withPassword ? { password: generateGamePassword() } : {}), - chefIds: [hostChefId], + chefIds: [masterChefId], eliminatedChefIds: [], maxChefs: faker.number.int({ min: constants.MIN_CHEFS, @@ -59,9 +59,9 @@ export function generateGame( currentRound: constants.NONE, roundBids: {}, roundWinners: {}, - turnOrder: [hostChefId], - hostChefId: hostChefId, - hostUserId: hostUserId, + turnOrder: [masterChefId], + masterChefId: masterChefId, + masterUserId: masterChefUserId, createdAt: faker.date.past(), updatedAt: faker.date.past(), ...(overrides ? overrides : {}), @@ -104,7 +104,7 @@ export function generateChefGameUser( const user = generateUser(overrides?.user); const chef = generateChef({ gameId, - host: true, + masterChef: true, userId: user._id, ...overrides?.chef, }); @@ -114,8 +114,8 @@ export function generateChefGameUser( const chefIds = [chef._id, ...additionalChefs.map((c) => c._id)]; const game = generateGame(withPassword, { _id: gameId, - hostUserId: user._id, - hostChefId: chef._id, + masterChefUserId: user._id, + masterChefId: chef._id, chefIds: chefIds, turnOrder: chefIds, ...overrides?.game, diff --git a/chili-and-cilantro-api/test/unit/actionService.test.ts b/chili-and-cilantro-api/test/unit/actionService.test.ts index 6cbe99e..b2ea04f 100644 --- a/chili-and-cilantro-api/test/unit/actionService.test.ts +++ b/chili-and-cilantro-api/test/unit/actionService.test.ts @@ -68,8 +68,8 @@ describe('ActionService', () => { let application: IApplication; let gameId: DefaultIdType; let mockGame: IGameDocument; - let hostChef: IChefDocument; - let hostUser: IUserDocument; + let masterChef: IChefDocument; + let masterChefUser: IUserDocument; beforeEach(async () => { application = new MockApplication(); @@ -77,8 +77,8 @@ describe('ActionService', () => { const generated = generateChefGameUser(true); gameId = generated.game._id; mockGame = generated.game; - hostChef = generated.chef; - hostUser = generated.user; + masterChef = generated.chef; + masterChefUser = generated.user; }); describe('getGameHistoryAsync', () => { @@ -89,7 +89,7 @@ describe('ActionService', () => { ModelName.Action, ); const mockActions: IActionDocument[] = [ - generateCreateGameAction(gameId, hostChef._id, hostUser._id), + generateCreateGameAction(gameId, masterChef._id, masterChefUser._id), ]; const mockQuery = { sort: jest.fn().mockReturnValue(Promise.resolve(mockActions)), @@ -112,7 +112,7 @@ describe('ActionService', () => { const createGame = jest .fn() .mockResolvedValue([ - generateCreateGameAction(gameId, hostChef._id, hostUser._id), + generateCreateGameAction(gameId, masterChef._id, masterChefUser._id), ]); const actionService = new ActionService( @@ -121,23 +121,23 @@ describe('ActionService', () => { const result = await actionService.createGameAsync( mockGame, - hostChef, - hostUser, + masterChef, + masterChefUser, ); expect(createGame).toHaveBeenCalledWith([ { gameId: gameId, - chefId: hostChef._id, - userId: hostUser._id, + chefId: masterChef._id, + userId: masterChefUser._id, type: ActionType.CREATE_GAME, details: {}, round: constants.NONE, }, ]); expect(result.gameId).toEqual(gameId); - expect(result.chefId).toEqual(hostChef._id); - expect(result.userId).toEqual(hostUser._id); + expect(result.chefId).toEqual(masterChef._id); + expect(result.userId).toEqual(masterChefUser._id); expect(result.type).toEqual(ActionType.CREATE_GAME); }); }); @@ -147,7 +147,7 @@ describe('ActionService', () => { const joinGame = jest .fn() .mockResolvedValue([ - generateJoinGameAction(gameId, hostChef._id, hostUser._id), + generateJoinGameAction(gameId, masterChef._id, masterChefUser._id), ]); const actionService = new ActionService( makeMockApplicationForDiscriminator(ActionType.JOIN_GAME, joinGame), @@ -156,24 +156,24 @@ describe('ActionService', () => { // Act const result = await actionService.joinGameAsync( mockGame, - hostChef, - hostUser, + masterChef, + masterChefUser, ); // Assert expect(joinGame).toHaveBeenCalledWith([ { gameId: gameId, - chefId: hostChef._id, - userId: hostUser._id, + chefId: masterChef._id, + userId: masterChefUser._id, type: ActionType.JOIN_GAME, details: {}, round: constants.NONE, }, ]); expect(result.gameId).toEqual(gameId); - expect(result.chefId).toEqual(hostChef._id); - expect(result.userId).toEqual(hostUser._id); + expect(result.chefId).toEqual(masterChef._id); + expect(result.userId).toEqual(masterChefUser._id); expect(result.type).toEqual(ActionType.JOIN_GAME); }); }); @@ -183,7 +183,7 @@ describe('ActionService', () => { const startGame = jest .fn() .mockResolvedValue([ - generateStartGameAction(gameId, hostChef._id, hostUser._id), + generateStartGameAction(gameId, masterChef._id, masterChefUser._id), ]); const actionService = new ActionService( makeMockApplicationForDiscriminator(ActionType.START_GAME, startGame), @@ -196,16 +196,16 @@ describe('ActionService', () => { expect(startGame).toHaveBeenCalledWith([ { gameId: gameId, - chefId: hostChef._id, - userId: hostUser._id, + chefId: masterChef._id, + userId: masterChefUser._id, type: ActionType.START_GAME, details: {}, round: constants.NONE, }, ]); expect(result.gameId).toEqual(gameId); - expect(result.chefId).toEqual(hostChef._id); - expect(result.userId).toEqual(hostUser._id); + expect(result.chefId).toEqual(masterChef._id); + expect(result.userId).toEqual(masterChefUser._id); expect(result.type).toEqual(ActionType.START_GAME); }); }); @@ -215,7 +215,7 @@ describe('ActionService', () => { const expireGame = jest .fn() .mockResolvedValue([ - generateExpireGameAction(gameId, hostChef._id, hostUser._id), + generateExpireGameAction(gameId, masterChef._id, masterChefUser._id), ]); const actionService = new ActionService( makeMockApplicationForDiscriminator(ActionType.EXPIRE_GAME, expireGame), @@ -228,16 +228,16 @@ describe('ActionService', () => { expect(expireGame).toHaveBeenCalledWith([ { gameId: gameId, - chefId: hostChef._id, - userId: hostUser._id, + chefId: masterChef._id, + userId: masterChefUser._id, type: ActionType.EXPIRE_GAME, details: {}, round: constants.NONE, }, ]); expect(result.gameId).toEqual(gameId); - expect(result.chefId).toEqual(hostChef._id); - expect(result.userId).toEqual(hostUser._id); + expect(result.chefId).toEqual(masterChef._id); + expect(result.userId).toEqual(masterChefUser._id); expect(result.type).toEqual(ActionType.EXPIRE_GAME); }); }); @@ -250,8 +250,8 @@ describe('ActionService', () => { .mockResolvedValue([ generateSendMessageAction( gameId, - hostChef._id, - hostUser._id, + masterChef._id, + masterChefUser._id, message, ), ]); @@ -262,7 +262,7 @@ describe('ActionService', () => { // Act const result = await actionService.sendMessageAsync( mockGame, - hostChef, + masterChef, message, ); @@ -270,8 +270,8 @@ describe('ActionService', () => { expect(messageAction).toHaveBeenCalledWith([ { gameId: gameId, - chefId: hostChef._id, - userId: hostUser._id, + chefId: masterChef._id, + userId: masterChefUser._id, type: ActionType.MESSAGE, details: { message: message, @@ -280,8 +280,8 @@ describe('ActionService', () => { }, ]); expect(result.gameId).toEqual(gameId); - expect(result.chefId).toEqual(hostChef._id); - expect(result.userId).toEqual(hostUser._id); + expect(result.chefId).toEqual(masterChef._id); + expect(result.userId).toEqual(masterChefUser._id); expect(result.type).toEqual(ActionType.MESSAGE); expect(result.details.message).toEqual(message); }); @@ -296,8 +296,8 @@ describe('ActionService', () => { .mockResolvedValue([ generateStartBiddingAction( gameId, - hostChef._id, - hostUser._id, + masterChef._id, + masterChefUser._id, round, bid, ), @@ -312,7 +312,7 @@ describe('ActionService', () => { // Act const result = await actionService.startBiddingAsync( mockGame, - hostChef, + masterChef, bid, ); @@ -320,8 +320,8 @@ describe('ActionService', () => { expect(startBidding).toHaveBeenCalledWith([ { gameId: gameId, - chefId: hostChef._id, - userId: hostUser._id, + chefId: masterChef._id, + userId: masterChefUser._id, type: ActionType.START_BIDDING, details: { bid: bid, @@ -330,8 +330,8 @@ describe('ActionService', () => { }, ]); expect(result.gameId).toEqual(gameId); - expect(result.chefId).toEqual(hostChef._id); - expect(result.userId).toEqual(hostUser._id); + expect(result.chefId).toEqual(masterChef._id); + expect(result.userId).toEqual(masterChefUser._id); expect(result.type).toEqual(ActionType.START_BIDDING); expect(result.details.bid).toEqual(bid); expect(result.round).toEqual(expect.any(Number)); @@ -344,29 +344,29 @@ describe('ActionService', () => { const passAction = jest .fn() .mockResolvedValue([ - generatePassAction(gameId, hostChef._id, hostUser._id, round), + generatePassAction(gameId, masterChef._id, masterChefUser._id, round), ]); const actionService = new ActionService( makeMockApplicationForDiscriminator(ActionType.PASS, passAction), ); // Act - const result = await actionService.passAsync(mockGame, hostChef); + const result = await actionService.passAsync(mockGame, masterChef); // Assert expect(passAction).toHaveBeenCalledWith([ { gameId: gameId, - chefId: hostChef._id, - userId: hostUser._id, + chefId: masterChef._id, + userId: masterChefUser._id, type: ActionType.PASS, details: {}, round: expect.any(Number), }, ]); expect(result.gameId).toEqual(gameId); - expect(result.chefId).toEqual(hostChef._id); - expect(result.userId).toEqual(hostUser._id); + expect(result.chefId).toEqual(masterChef._id); + expect(result.userId).toEqual(masterChefUser._id); expect(result.type).toEqual(ActionType.PASS); expect(result.round).toEqual(expect.any(Number)); }); @@ -382,8 +382,8 @@ describe('ActionService', () => { .mockResolvedValue([ generatePlaceCardAction( gameId, - hostChef._id, - hostUser._id, + masterChef._id, + masterChefUser._id, round, cardType, position, @@ -396,7 +396,7 @@ describe('ActionService', () => { // Act const result = await actionService.placeCardAsync( mockGame, - hostChef, + masterChef, cardType, position, ); @@ -405,8 +405,8 @@ describe('ActionService', () => { expect(placeCard).toHaveBeenCalledWith([ { gameId: gameId, - chefId: hostChef._id, - userId: hostUser._id, + chefId: masterChef._id, + userId: masterChefUser._id, type: ActionType.PLACE_CARD, details: { cardType: cardType, @@ -416,8 +416,8 @@ describe('ActionService', () => { }, ]); expect(result.gameId).toEqual(gameId); - expect(result.chefId).toEqual(hostChef._id); - expect(result.userId).toEqual(hostUser._id); + expect(result.chefId).toEqual(masterChef._id); + expect(result.userId).toEqual(masterChefUser._id); expect(result.type).toEqual(ActionType.PLACE_CARD); expect(result.round).toEqual(expect.any(Number)); expect(result.details.cardType).toEqual(cardType); diff --git a/chili-and-cilantro-api/test/unit/chefService.test.ts b/chili-and-cilantro-api/test/unit/chefService.test.ts index 8b59994..270b0c5 100644 --- a/chili-and-cilantro-api/test/unit/chefService.test.ts +++ b/chili-and-cilantro-api/test/unit/chefService.test.ts @@ -35,7 +35,7 @@ describe('ChefService', () => { chef: mockChef, } = generateChefGameUser(true); const username = generateUsername(); - const host = true; + const masterChef = true; const expectedHand = UtilityService.makeHand(); (ChefModel as any).create = jest.fn().mockResolvedValueOnce([mockChef]); @@ -45,7 +45,7 @@ describe('ChefService', () => { mockGame, mockUser, username, - host, + masterChef, mockChef._id, ); @@ -60,7 +60,7 @@ describe('ChefService', () => { placedCards: [], lostCards: [], state: ChefState.LOBBY, - host: host, + masterChef: masterChef, }, ]); expect(result).toBeDefined(); @@ -74,7 +74,7 @@ describe('ChefService', () => { game: mockGame, } = generateChefGameUser(true); const username = generateUsername(); - const host = true; + const masterChef = true; const expectedHand = UtilityService.makeHand(); (ChefModel as any).create = jest.fn().mockResolvedValueOnce([mockChef]); @@ -84,7 +84,7 @@ describe('ChefService', () => { mockGame, mockUser, username, - host, + masterChef, ); // Assert @@ -97,7 +97,7 @@ describe('ChefService', () => { placedCards: [], lostCards: [], state: ChefState.LOBBY, - host: host, + masterChef: masterChef, }), ]); expect(result).toBeDefined(); @@ -142,7 +142,7 @@ describe('ChefService', () => { placedCards: [], lostCards: [], state: ChefState.LOBBY, - host: existingChef.host, + masterChef: existingChef.masterChef, }, ]); expect(result).toBeDefined(); @@ -186,7 +186,7 @@ describe('ChefService', () => { placedCards: [], lostCards: [], state: ChefState.LOBBY, - host: existingChef.host, + masterChef: existingChef.masterChef, }), ]); expect(result).toBeDefined(); @@ -227,7 +227,7 @@ describe('ChefService', () => { _id: mockChef._id, gameId: mockGame._id, hand: mockChef.hand, - host: mockChef.host, + masterChef: mockChef.masterChef, name: mockChef.name, placedCards: mockChef.placedCards, lostCards: mockChef.lostCards, diff --git a/chili-and-cilantro-api/test/unit/gameService.createNewGameFromExisting.test.ts b/chili-and-cilantro-api/test/unit/gameService.createNewGameFromExisting.test.ts index f36adae..ce594d1 100644 --- a/chili-and-cilantro-api/test/unit/gameService.createNewGameFromExisting.test.ts +++ b/chili-and-cilantro-api/test/unit/gameService.createNewGameFromExisting.test.ts @@ -94,7 +94,7 @@ describe('GameService', () => { existingGame = generated.game; mockChefs = [chef, ...generated.additionalChefs]; const newChef = generateChef({ - host: true, + masterChef: true, gameId: newGameId, userId: user._id, }); @@ -108,8 +108,8 @@ describe('GameService', () => { mockPlayerService = {} as unknown as PlayerService; newGame = generateGame(true, { _id: newGameId, - hostUserId: user._id, - hostChefId: newChef._id, + masterChefUserId: user._id, + masterChefId: newChef._id, }); mockGameModel = application.getModel( ModelName.Game, @@ -170,7 +170,7 @@ describe('GameService', () => { mockUser = generated.user; mockNewGame = generateGame(); mockNewChef = generateChef({ - host: true, + masterChef: true, gameId: mockNewGame._id, userId: mockUser._id, }); diff --git a/chili-and-cilantro-api/test/unit/gameService.joinGameAsync.test.ts b/chili-and-cilantro-api/test/unit/gameService.joinGameAsync.test.ts index 559f86b..e9fc4ac 100644 --- a/chili-and-cilantro-api/test/unit/gameService.joinGameAsync.test.ts +++ b/chili-and-cilantro-api/test/unit/gameService.joinGameAsync.test.ts @@ -140,8 +140,8 @@ describe('GameService', () => { .resolves([chef.name]); game = generateGame(true, { _id: gameId, - hostUserId: user._id, - hostChefId: chef._id, + masterChefUserId: user._id, + masterChefId: chef._id, currentPhase: GamePhase.SETUP, }); @@ -347,7 +347,11 @@ describe('GameService', () => { it('should successfully join a game', async () => { // Setup mocks - const mockChef = generateChef({ host: false, gameId, userId: user._id }); + const mockChef = generateChef({ + masterChef: false, + gameId, + userId: user._id, + }); (mockChefService.newChefAsync as jest.Mock).mockResolvedValue(mockChef); game.save = jest.fn().mockResolvedValue(game); diff --git a/chili-and-cilantro-api/test/unit/gameService.startGame.test.ts b/chili-and-cilantro-api/test/unit/gameService.startGame.test.ts index a08e93a..ce9e32e 100644 --- a/chili-and-cilantro-api/test/unit/gameService.startGame.test.ts +++ b/chili-and-cilantro-api/test/unit/gameService.startGame.test.ts @@ -7,7 +7,7 @@ import { IUserDocument, ModelName, NotEnoughChefsError, - NotHostError, + NotMasterChefError, } from '@chili-and-cilantro/chili-and-cilantro-lib'; import { IApplication } from '@chili-and-cilantro/chili-and-cilantro-node-lib'; import { Model } from 'mongoose'; @@ -120,15 +120,15 @@ describe('gameService startGame', () => { let mockActionService: ActionService; let mockChefService: ChefService; let mockPlayerService: PlayerService; - let isGameHostAsync: jest.Mock; + let isMasterChefAsync: jest.Mock; beforeEach(() => { application = new MockApplication(); gameModel = application.getModel(ModelName.Game); mockActionService = {} as unknown as ActionService; - isGameHostAsync = jest.fn(); + isMasterChefAsync = jest.fn(); mockPlayerService = { - isGameHostAsync: isGameHostAsync, + isMasterChefAsync: isMasterChefAsync, } as unknown as PlayerService; mockChefService = {} as unknown as ChefService; gameService = new GameService( @@ -147,7 +147,7 @@ describe('gameService startGame', () => { gameCode = game.code; }); it('should validate successfully for a valid game start', async () => { - isGameHostAsync.mockResolvedValue(true); + isMasterChefAsync.mockResolvedValue(true); game.currentPhase = GamePhase.LOBBY; await expect( @@ -155,14 +155,14 @@ describe('gameService startGame', () => { ).resolves.not.toThrow(); }); it('should throw if the user is not the host', async () => { - isGameHostAsync.mockResolvedValue(false); + isMasterChefAsync.mockResolvedValue(false); await expect(async () => gameService.validateStartGameOrThrowAsync(game, userId), - ).rejects.toThrow(NotHostError); + ).rejects.toThrow(NotMasterChefError); }); it('should throw if the game phase is not LOBBY', async () => { - isGameHostAsync.mockResolvedValue(true); + isMasterChefAsync.mockResolvedValue(true); game.currentPhase = GamePhase.SETUP; await expect(async () => @@ -170,7 +170,7 @@ describe('gameService startGame', () => { ).rejects.toThrow(GameInProgressError); }); it('should throw if there are not enough chefs', async () => { - isGameHostAsync.mockResolvedValue(true); + isMasterChefAsync.mockResolvedValue(true); game.chefIds = [chef._id]; await expect(async () => diff --git a/chili-and-cilantro-api/test/unit/playerService.test.ts b/chili-and-cilantro-api/test/unit/playerService.test.ts index 5507ebf..95972db 100644 --- a/chili-and-cilantro-api/test/unit/playerService.test.ts +++ b/chili-and-cilantro-api/test/unit/playerService.test.ts @@ -20,7 +20,7 @@ describe('PlayerService', () => { playerService = new PlayerService(application); }); - describe('isGameHostAsync', () => { + describe('isMasterChefAsync', () => { beforeEach(() => { jest.spyOn(console, 'error').mockImplementation(() => {}); }); @@ -33,12 +33,12 @@ describe('PlayerService', () => { jest.spyOn(mockGameModel, 'countDocuments').mockResolvedValue(1); const gameId = generateObjectId(); const userId = generateObjectId(); - const result = await playerService.isGameHostAsync(userId, gameId); + const result = await playerService.isMasterChefAsync(userId, gameId); expect(result).toBe(true); expect(mockGameModel.countDocuments).toHaveBeenCalledTimes(1); expect(mockGameModel.countDocuments).toHaveBeenCalledWith({ _id: gameId, - hostUserId: userId, + masterChefUserId: userId, }); }); @@ -46,12 +46,12 @@ describe('PlayerService', () => { jest.spyOn(mockGameModel, 'countDocuments').mockResolvedValue(0); const gameId = generateObjectId(); const userId = generateObjectId(); - const result = await playerService.isGameHostAsync(userId, gameId); + const result = await playerService.isMasterChefAsync(userId, gameId); expect(result).toBe(false); expect(mockGameModel.countDocuments).toHaveBeenCalledTimes(1); expect(mockGameModel.countDocuments).toHaveBeenCalledWith({ _id: gameId, - hostUserId: userId, + masterChefUserId: userId, }); }); it('should log an error when the aggregate call fails', async () => { @@ -62,7 +62,7 @@ describe('PlayerService', () => { const gameId = generateObjectId(); const userId = generateObjectId(); expect(async () => - playerService.isGameHostAsync(userId, gameId), + playerService.isMasterChefAsync(userId, gameId), ).rejects.toThrow(error); expect(console.error).toHaveBeenCalledWith( diff --git a/chili-and-cilantro-lib/src/index.ts b/chili-and-cilantro-lib/src/index.ts index ea0b336..93b5c00 100644 --- a/chili-and-cilantro-lib/src/index.ts +++ b/chili-and-cilantro-lib/src/index.ts @@ -29,13 +29,12 @@ export * from './lib/errors/account-locked'; export * from './lib/errors/account-status'; export * from './lib/errors/all-cards-placed'; export * from './lib/errors/already-joined'; -export * from './lib/errors/already-joined-other'; +export * from './lib/errors/chef-already-joined'; export * from './lib/errors/email-in-use'; export * from './lib/errors/email-token-expired'; export * from './lib/errors/email-token-sent-too-recently'; export * from './lib/errors/email-token-used-or-invalid'; export * from './lib/errors/email-verified'; -export * from './lib/errors/game-full'; export * from './lib/errors/game-in-progress'; export * from './lib/errors/game-password-mismatch'; export * from './lib/errors/game-username-in-use'; @@ -54,11 +53,12 @@ export * from './lib/errors/invalid-token'; export * from './lib/errors/invalid-user-display-name'; export * from './lib/errors/invalid-username'; export * from './lib/errors/not-enough-chefs'; -export * from './lib/errors/not-host'; export * from './lib/errors/not-in-game'; +export * from './lib/errors/not-master-chef'; export * from './lib/errors/out-of-ingredient'; export * from './lib/errors/out-of-order'; export * from './lib/errors/pending-email-verification'; +export * from './lib/errors/too-many-chefs-in-game'; export * from './lib/errors/user-not-found'; export * from './lib/errors/username-email-required'; export * from './lib/errors/username-in-use'; diff --git a/chili-and-cilantro-lib/src/lib/__snapshots__/i18n.spec.ts.snap b/chili-and-cilantro-lib/src/lib/__snapshots__/i18n.spec.ts.snap index 7de941f..d53b9f7 100644 --- a/chili-and-cilantro-lib/src/lib/__snapshots__/i18n.spec.ts.snap +++ b/chili-and-cilantro-lib/src/lib/__snapshots__/i18n.spec.ts.snap @@ -8,6 +8,7 @@ exports[`buildNestedI18n should handle English (UK) language 1`] = ` }, "changePassword": { "changePasswordButton": "Change Password", + "success": "Password changed successfully", }, "common": { "changePassword": "Change Password", @@ -18,13 +19,17 @@ exports[`buildNestedI18n should handle English (UK) language 1`] = ` "goToSplash": "Return to Welcome Screen", "loading": "Loading...", "logo": "logo", + "masterChef": "Master Chef", "newPassword": "New Password", + "password": "Password", "returnToKitchen": "Return to Kitchen", "site": "Chili and Cilantro", "startCooking": "Start Cooking", "tagline": "A Spicy Bluffing Game", + "tokenValid": "Token is valid", "unauthorized": "Unauthorized", "unexpectedError": "Unexpected Error", + "username": "Username", }, "dashboard": { "gamesCreated": "Games You've Created", @@ -32,6 +37,29 @@ exports[`buildNestedI18n should handle English (UK) language 1`] = ` "noGames": "No games available.", "title": "Your Dashboard", }, + "emailToken": { + "clickLinkEmailConfirm": "Please click the link below to confirm your email.", + "clickLinkResetPassword": "Please click the link below to reset your password.", + "expiresInTemplate": "Link expires in {EMAIL_TOKEN_EXPIRATION_MIN} minutes.", + "titleEmailConfirm": "Email Confirmation", + "titleResetPassword": "Password Reset", + }, + "error": { + "accountStatusIsDeleted": "Account deleted", + "accountStatusIsLocked": "Account locked", + "accountStatusIsPendingEmailVerification": "Account pending email verification", + "allCardsPlaced": "All cards have been placed.", + "chefAlreadyInGame": "Chef is already in an active game.", + "emailAlreadyVerified": "Email has already been verified", + "emailInUse": "Email is already in use", + "emailTokenAlreadySent": "Email verification link has already been used or is invalid", + "emailTokenExpired": "Verification link has expired. Please request a new one.", + "emailTokenSentTooRecentlyTemplate": "Email token sent too recently. Please try again in {TIME_REMAINING} seconds.", + "failedToCreateEmailToken": "Failed to create email token", + "sendTokenFailure": "Failed to send email token", + "tooManyChefs": "Too many chefs in the kitchen.", + "youAlreadyJoined": "You have already joined this game.", + }, "forgotPassword": { "forgotPassword": "Forgot Password", "invalidToken": "Invalid or expired token. Please request a new password reset.", @@ -63,24 +91,44 @@ exports[`buildNestedI18n should handle English (UK) language 1`] = ` }, "login": { "loginButton": "Login", + "progress": "Logging in...", + "resentPasswordFailure": "Failed to resend verification email", + "resentPasswordSuccess": "Verification email sent successfully", + "title": "Login", + "usernameOrEmailRequired": "Either username or email is required", }, "logoutButton": "Logout", "registerButton": "Register", + "resetPassword": { + "changeEmailFirst": "Please verify your email address before resetting your password.", + "sent": "If an account with that email exists, a password reset link has been sent.", + "success": "Password reset successfully", + }, "splash": { "description": "In Chili and Cilantro, aspiring chefs compete to create the perfect dish. Your goal is to add just the right amount of cilantro without ruining it with a scorching chili. Be the first to successfully season two dishes or be the last chef standing to win!", "howToPlay": "How to Play", }, + "testTestTemplate": "Testing {VARIABLE1} {VARIABLE2}", "validation": { "confirmNewPassword": "Confirm new password is required", "currentPasswordRequired": "Current password is required", "invalidEmail": "Invalid Email", "invalidLanguage": "Invalid Language", + "invalidTimezone": "Invalid Timezone", "invalidToken": "Invalid Token", "newPasswordRequired": "New password is required", "passwordMatch": "Password and confirm must match", - "passwordRegexErrorTemplate": "Password must be at least {MIN_PASSWORD_LENGTH} characters long and include at least one letter, one number, and one special character (!@#$%^&*()_+-=[]{};':"|,.<>/?)", + "passwordRegexErrorTemplate": "Password must be between {MIN_PASSWORD_LENGTH} and {MAX_PASSWORD_LENGTH} characters, and contain at least: + • One lowercase character (any script) + • One uppercase character (any script) + • One number (any numeral system) + • One special character (punctuation or symbol)", "passwordsDifferent": "New password must be different than the current password", "required": "Required", + "usernameRegexErrorTemplate": "Username must be between {MIN_USERNAME_LENGTH} and {MAX_USERNAME_LENGTH} characters, and: + • Start with a letter, number, or Unicode character + • Can contain letters, numbers, underscores, hyphens, and Unicode characters + • Cannot contain spaces or special characters other than underscores and hyphens", }, "validationError": "Validation Error", } @@ -94,6 +142,7 @@ exports[`buildNestedI18n should handle English (US) language 1`] = ` }, "changePassword": { "changePasswordButton": "Change Password", + "success": "Password changed successfully", }, "common": { "changePassword": "Change Password", @@ -104,13 +153,17 @@ exports[`buildNestedI18n should handle English (US) language 1`] = ` "goToSplash": "Return to Welcome Screen", "loading": "Loading...", "logo": "logo", + "masterChef": "Master Chef", "newPassword": "New Password", + "password": "Password", "returnToKitchen": "Return to Kitchen", "site": "Chili and Cilantro", "startCooking": "Start Cooking", "tagline": "A Spicy Bluffing Game", + "tokenValid": "Token is valid", "unauthorized": "Unauthorized", "unexpectedError": "Unexpected Error", + "username": "Username", }, "dashboard": { "gamesCreated": "Games You've Created", @@ -118,6 +171,29 @@ exports[`buildNestedI18n should handle English (US) language 1`] = ` "noGames": "No games available.", "title": "Your Dashboard", }, + "emailToken": { + "clickLinkEmailConfirm": "Please click the link below to confirm your email.", + "clickLinkResetPassword": "Please click the link below to reset your password.", + "expiresInTemplate": "Link expires in {EMAIL_TOKEN_EXPIRATION_MIN} minutes.", + "titleEmailConfirm": "Email Confirmation", + "titleResetPassword": "Password Reset", + }, + "error": { + "accountStatusIsDeleted": "Account deleted", + "accountStatusIsLocked": "Account locked", + "accountStatusIsPendingEmailVerification": "Account pending email verification", + "allCardsPlaced": "All cards placed", + "chefAlreadyInGame": "Chef is already in an active game", + "emailAlreadyVerified": "Email has already been verified", + "emailInUse": "Email is already in use", + "emailTokenAlreadySent": "Email verification link has already been used or is invalid", + "emailTokenExpired": "Verification link has expired. Please request a new one.", + "emailTokenSentTooRecentlyTemplate": "Email token sent too recently. Please try again in {TIME_REMAINING} seconds.", + "failedToCreateEmailToken": "Failed to create email token", + "sendTokenFailure": "Failed to send email token", + "tooManyChefs": "Too many chefs in the kitchen", + "youAlreadyJoined": "You have already joined this game", + }, "forgotPassword": { "forgotPassword": "Forgot Password", "invalidToken": "Invalid or expired token. Please request a new password reset.", @@ -149,24 +225,44 @@ exports[`buildNestedI18n should handle English (US) language 1`] = ` }, "login": { "loginButton": "Login", + "progress": "Logging in...", + "resentPasswordFailure": "Failed to resend verification email", + "resentPasswordSuccess": "Verification email sent successfully", + "title": "Login", + "usernameOrEmailRequired": "Either username or email is required", }, "logoutButton": "Logout", "registerButton": "Register", + "resetPassword": { + "changeEmailFirst": "Please verify your email address before resetting your password.", + "sent": "If an account with that email exists, a password reset link has been sent.", + "success": "Password reset successfully", + }, "splash": { "description": "In Chili and Cilantro, aspiring chefs compete to create the perfect dish. Your goal is to add just the right amount of cilantro without ruining it with a scorching chili. Be the first to successfully season two dishes or be the last chef standing to win!", "howToPlay": "How to Play", }, + "testTestTemplate": "Testing {VARIABLE1} {VARIABLE2}", "validation": { "confirmNewPassword": "Confirm new password is required", "currentPasswordRequired": "Current password is required", "invalidEmail": "Invalid Email", "invalidLanguage": "Invalid Language", + "invalidTimezone": "Invalid Timezone", "invalidToken": "Invalid Token", "newPasswordRequired": "New password is required", "passwordMatch": "Password and confirm must match", - "passwordRegexErrorTemplate": "Password must be at least 8 characters long and include at least one letter, one number, and one special character (!@#$%^&*()_+-=[]{};':"|,.<>/?)", + "passwordRegexErrorTemplate": "Password must be between {MIN_PASSWORD_LENGTH} and {MAX_PASSWORD_LENGTH} characters, and contain at least: + • One lowercase character (any script) + • One uppercase character (any script) + • One number (any numeral system) + • One special character (punctuation or symbol)", "passwordsDifferent": "New password must be different than the current password", "required": "Required", + "usernameRegexErrorTemplate": "Username must be between {MIN_USERNAME_LENGTH} and {MAX_USERNAME_LENGTH} characters, and: + • Start with a letter, number, or Unicode character + • Can contain letters, numbers, underscores, hyphens, and Unicode characters + • Cannot contain spaces or special characters other than underscores and hyphens", }, "validationError": "Validation Error", } @@ -180,6 +276,7 @@ exports[`buildNestedI18n should handle Español language 1`] = ` }, "changePassword": { "changePasswordButton": "Cambiar contraseña", + "success": "Contraseña cambiada con éxito", }, "common": { "changePassword": "Cambiar contraseña", @@ -190,13 +287,17 @@ exports[`buildNestedI18n should handle Español language 1`] = ` "goToSplash": "Volver a la pantalla de bienvenida", "loading": "Cargando...", "logo": "logo", + "masterChef": "Chef Maestro", "newPassword": "Nueva contraseña", + "password": "Contraseña", "returnToKitchen": "Volver a la Cocina", "site": "Chili y Cilantro", "startCooking": "Comenzar a cocinar", "tagline": "Un Juego de Bluff Picante", + "tokenValid": "Token válido", "unauthorized": "No autorizado", "unexpectedError": "Error inesperado", + "username": "Nombre de usuario", }, "dashboard": { "gamesCreated": "Juegos que has creado", @@ -204,6 +305,29 @@ exports[`buildNestedI18n should handle Español language 1`] = ` "noGames": "No hay juegos disponibles.", "title": "Tu Tablero", }, + "emailToken": { + "clickLinkEmailConfirm": "Por favor, haz clic en el enlace de abajo para confirmar tu correo electrónica.", + "clickLinkResetPassword": "Por favor, haz clic en el enlace de abajo para restablecer tu contraseña.", + "expiresInTemplate": "El enlace caducará en {EMAIL_TOKEN_EXPIRATION_MIN} minutos.", + "titleEmailConfirm": "Confirmación de correo electrónico", + "titleResetPassword": "Restablecimiento de contraseña", + }, + "error": { + "accountStatusIsDeleted": "Cuenta eliminada", + "accountStatusIsLocked": "Cuenta bloqueada", + "accountStatusIsPendingEmailVerification": "Cuenta en espera de verificación de correo electrónico", + "allCardsPlaced": "Todas las cartas han sido colocadas.", + "chefAlreadyInGame": "El chef ya está en un juego activo.", + "emailAlreadyVerified": "El correo electrónico ya ha sido verificado", + "emailInUse": "El correo electrónica ya esta en uso.", + "emailTokenAlreadySent": "El enlace de verificación de correo electrónica ya ha sido usado o es inválido.", + "emailTokenExpired": "El enlace de verificación ha caducado. Por favor, solicita uno nuevo.", + "emailTokenSentTooRecentlyTemplate": "El token de correo electrónico se envió hace muy poco. Inténtalo de nuevo en {TIME_REMAINING} segundos.", + "failedToCreateEmailToken": "Fallo al crear el token de correo", + "sendTokenFailure": "Fallo al enviar el token de correo", + "tooManyChefs": "Demasiados chefs en la cocina", + "youAlreadyJoined": "Ya te has unido a este juego", + }, "forgotPassword": { "forgotPassword": "Olvidé mi contraseña", "invalidToken": "Token inválido o expirado. Por favor, solicita un nuevo restablecimiento de contraseña.", @@ -235,24 +359,44 @@ exports[`buildNestedI18n should handle Español language 1`] = ` }, "login": { "loginButton": "Iniciar sesión", + "progress": "Iniciando sesión...", + "resentPasswordFailure": "Reenviar contraseña fallido", + "resentPasswordSuccess": "Reenviar contraseña con éxito", + "title": "Iniciar sesión", + "usernameOrEmailRequired": "Se requiere un nombre de usuario o correo electrónica", }, "logoutButton": "Cerrar sesión", "registerButton": "Registrarse", + "resetPassword": { + "changeEmailFirst": "Antes de restablecer la contraseña, verifica tu dirección de correo electrónico.", + "sent": "Si existe una cuenta con ese correo electrónico, se ha enviado un enlace para restablecer la contraseña.", + "success": "Contraseña restablecida con éxito", + }, "splash": { "description": "En Chili and Cilantro, los aspirantes a chef compiten para crear el plato perfecto. Tu objetivo es agregar la cantidad justa de cilantro sin arruinarlo con un chile abrasador. ¡Sé el primero en condimentar con éxito dos platos o sé el último chef en pie para ganar!", "howToPlay": "Como Jugar", }, + "testTestTemplate": "Probando {VARIABLE1} {VARIABLE2}", "validation": { "confirmNewPassword": "Se requiere la confirmación de la nueva contraseña", "currentPasswordRequired": "Se requiere la contraseña actual", "invalidEmail": "Correo electrónica inválido", "invalidLanguage": "Idioma inválido", + "invalidTimezone": "Fuseau horaire invático", "invalidToken": "Token inválido", "newPasswordRequired": "Se requiere la nueva contraseña", "passwordMatch": "La contraseña y la confirmación deben coincidir", - "passwordRegexErrorTemplate": "La contraseña debe tener al menos 8 caracteres y incluir al menos una letra, un número y un carácter especial (!@#$%^&*()_+-=[]{};':"|,.<>/?)", + "passwordRegexErrorTemplate": "La contraseña debe estar entre {MIN_PASSWORD_LENGTH} y {MAX_PASSWORD_LENGTH} caracteres, y contener al menos: + • Una letra minuscula (cualquier script) + • Una letra mayúscula (cualquier script) + • Un número (cualquier sistema numérico) + • Un caracter especial (puntuación o símbolo)", "passwordsDifferent": "La nueva contraseña debe ser diferente a la contraseña actual", "required": "Requerido", + "usernameRegexErrorTemplate": "El nombre de usuario debe tener entre {MIN_USERNAME_LENGTH} y {MAX_USERNAME_LENGTH} caracteres, y: + • Comenzar con una letra, un número, o un carácter Unicode + • Puede contener letras, números, guiones bajos, guiones, y carácteres Unicode + • No puede contener espacios o carácteres especiales excepto guiones bajos y guiones", }, "validationError": "Error de validación", } @@ -266,6 +410,7 @@ exports[`buildNestedI18n should handle Français language 1`] = ` }, "changePassword": { "changePasswordButton": "Changer le mot de passe", + "success": "Mot de passe changé avec succès", }, "common": { "changePassword": "Changer le mot de passe", @@ -276,13 +421,17 @@ exports[`buildNestedI18n should handle Français language 1`] = ` "goToSplash": "Retourner aucran d'accueil", "loading": "Chargement...", "logo": "logo", + "masterChef": "Chef de cuisine", "newPassword": "Nouveau mot de passe", + "password": "Mot de passe", "returnToKitchen": "Retour à la Cuisine", "site": "Piment et Coriandre", "startCooking": "Commencer la Cuisine", "tagline": "Un Jeu de Bluff Épicé", + "tokenValid": "Jeton valide", "unauthorized": "Non autorisé", "unexpectedError": "Erreur inattendue", + "username": "Nom d’utilisateur", }, "dashboard": { "gamesCreated": "Jeux que vous avez créé", @@ -290,6 +439,29 @@ exports[`buildNestedI18n should handle Français language 1`] = ` "noGames": "Aucun jeu disponible.", "title": "Votre Tableau de Bord", }, + "emailToken": { + "clickLinkEmailConfirm": "Veuillez cliquer sur le lien ci-dessous pour confirmer votre courriel.", + "clickLinkResetPassword": "Veuillez cliquer sur le lien ci-dessous pour réinitialiser votre mot de passe.", + "expiresInTemplate": "Le lien expire dans {EMAIL_TOKEN_EXPIRATION_MIN} minutes.", + "titleEmailConfirm": "Confirmation de courriel", + "titleResetPassword": "Réinitialisation du mot de passe", + }, + "error": { + "accountStatusIsDeleted": "Compte supprimé", + "accountStatusIsLocked": "Compte verrouillé", + "accountStatusIsPendingEmailVerification": "Compte en attente de confirmation de courriel", + "allCardsPlaced": "Toutes les cartes ont été placées", + "chefAlreadyInGame": "Chef deja dans le jeu", + "emailAlreadyVerified": "Courriel déjà vérifié", + "emailInUse": "Courriel déjà utilisé", + "emailTokenAlreadySent": "Le lien de vérification par courriel a déjà été utilisé ou est invalide", + "emailTokenExpired": "Le lien de vérification a expiré. Veuillez en demander un nouveau.", + "emailTokenSentTooRecentlyTemplate": "Le jeton de courrier électronique a été envoyé trop récemment. Veuillez réessayer dans {TIME_REMAINING} secondes.", + "failedToCreateEmailToken": "Échec de la création du jeton par courriel", + "sendTokenFailure": "Échec de l’envoi du jeton par courriel", + "tooManyChefs": "Trop de chefs dans la cuisine", + "youAlreadyJoined": "Vous avez déjà rejoint ce jeu", + }, "forgotPassword": { "forgotPassword": "Mot de passe oublié", "invalidToken": "Jeton invalide ou expiré. Veuillez demander un nouveau mot de passe.", @@ -321,24 +493,44 @@ exports[`buildNestedI18n should handle Français language 1`] = ` }, "login": { "loginButton": "Connexion", + "progress": "Connexion en cours...", + "resentPasswordFailure": "Échec de l’envoi du courriel de verification", + "resentPasswordSuccess": "Courriel de verification envoyé avec успех", + "title": "Connexion", + "usernameOrEmailRequired": "Nom d’utilisateur ou courriel requis", }, "logoutButton": "Déconnexion", "registerButton": "S'inscrire", + "resetPassword": { + "changeEmailFirst": "Vous devez changer votre courriel avant de pouvoir changer votre mot de passe.", + "sent": "Si un compte avec cet courriel existe, un lien de réinitialisation du mot de passe a été envoyé.", + "success": "Réinitialisation du mot de passe réussie", + }, "splash": { "description": "Dans Chili and Cilantro, les chefs en herbe rivalisent pour créer le plat parfait. Votre objectif est d'ajouter juste la bonne quantité de coriandre sans gâcher le tout avec un piment brûlant. Soyez le premier à réussir à assaisonner deux plats ou soyez le dernier chef debout pour gagner !", "howToPlay": "Comment Jouer", }, + "testTestTemplate": "Essai {VARIABLE1} {VARIABLE2}", "validation": { "confirmNewPassword": "La confirmation du nouveau mot de passe est requise", "currentPasswordRequired": "Le mot de passe actuel est requis", "invalidEmail": "Courriel invalide", "invalidLanguage": "Langue invalide", + "invalidTimezone": "Fuseau horaire invalide", "invalidToken": "Jeton invalide", "newPasswordRequired": "Le nouveau mot de passe est requis", "passwordMatch": "Le mot de passe et la confirmation doivent correspondre", - "passwordRegexErrorTemplate": "Le mot de passe doit comporter au moins 8 caractères et inclure au moins une lettre, un chiffre et un caractère spécial (!@#$%^&*()_+-=[]{};':"|,.<>/?)", + "passwordRegexErrorTemplate": "Le mot de passe doit contenir entre {MIN_PASSWORD_LENGTH} et {MAX_PASSWORD_LENGTH} caractères, et contenir au moins : + • Une lettre minuscule (tout script) + • Une lettre majuscule (tout script) + • Un chiffre (tout système numérique) + • Un caractère spécial (ponctuation ou symbole)", "passwordsDifferent": "Le nouveau mot de passe doit être différent du mot de passe actuel", "required": "Champ obligatoire", + "usernameRegexErrorTemplate": "Le nom d'utilisateur doit contenir entre {MIN_USERNAME_LENGTH} et {MAX_USERNAME_LENGTH} caractères, et : + • Commencer par une lettre, un chiffre, ou un caractère Unicode + • Peut contenir des lettres, des chiffres, des underscores, des tirets, et des caractères Unicode + • Ne peut contenir des espaces ou des caractères spéciaux autre que des underscores et des tirets", }, "validationError": "Erreur de validation", } @@ -352,6 +544,7 @@ exports[`buildNestedI18n should handle Український language 1`] = ` }, "changePassword": { "changePasswordButton": "Змінити пароль", + "success": "Пароль успішно змінено", }, "common": { "changePassword": "Змінити пароль", @@ -362,13 +555,17 @@ exports[`buildNestedI18n should handle Український language 1`] = ` "goToSplash": "Повернутися до екрану привітання", "loading": "Завантаження...", "logo": "лого", + "masterChef": "Майстер-кухар", "newPassword": "Новий пароль", + "password": "Пароль", "returnToKitchen": "Повернутися на Кухню", "site": "Чілі та Коріандр", "startCooking": "Почати приготування", "tagline": "Пікантний Блеф", + "tokenValid": "Токен діїдженний", "unauthorized": "Немає авторизації", "unexpectedError": "Неочікувана помилка", + "username": "Ім’я користувача", }, "dashboard": { "gamesCreated": "Гри, які ти створив", @@ -376,6 +573,29 @@ exports[`buildNestedI18n should handle Український language 1`] = ` "noGames": "Немає доступних ігор.", "title": "Ваша Панель", }, + "emailToken": { + "clickLinkEmailConfirm": "Натисніть на посилання для підтвердження електронної пошти", + "clickLinkResetPassword": "Натисніть на посилання для скидання пароля", + "expiresInTemplate": "Посилання дійсне протягом {EMAIL_TOKEN_EXPIRATION_MIN} хвилин.", + "titleEmailConfirm": "Підтвердження електронної пошти", + "titleResetPassword": "Скидання пароля", + }, + "error": { + "accountStatusIsDeleted": "Обліковий запис видалено", + "accountStatusIsLocked": "Обліковий запис заблоковано", + "accountStatusIsPendingEmailVerification": "Обліковий запис очікує підтвердження електронної пошти", + "allCardsPlaced": "Всі картки розміщені", + "chefAlreadyInGame": "Шеф-кухар вже в грі", + "emailAlreadyVerified": "Електронна пошта вже підтверджена", + "emailInUse": "Електронна пошта вже використовується", + "emailTokenAlreadySent": "Посилання для підтвердження електронної пошти вже використано або недійсне", + "emailTokenExpired": "Термін дії посилання закінчився. Будь ласка, запросіть новий.", + "emailTokenSentTooRecentlyTemplate": "Маркер електронної пошти надіслано занадто недавно. Повторіть спробу через {TIME_REMAINING} с.", + "failedToCreateEmailToken": "Неможливо створити електронний токен", + "sendTokenFailure": "Неможливо відправити електронний токен", + "tooManyChefs": "Забагато шеф-кухарів на кухні", + "youAlreadyJoined": "Ви вже приєдналися до цієї гри", + }, "forgotPassword": { "forgotPassword": "Забули пароль?", "invalidToken": "Недіїдженний токен. Будь ласка, зверніться до підтримки.", @@ -407,24 +627,44 @@ exports[`buildNestedI18n should handle Український language 1`] = ` }, "login": { "loginButton": "Увійти", + "progress": "Вхід...", + "resentPasswordFailure": "Невдалося відправити новий пароль", + "resentPasswordSuccess": "Новиий пароль успішно відправлено", + "title": "Увійти", + "usernameOrEmailRequired": "Необхідно вказати ім’я користувача або електронну пошту", }, "logoutButton": "Вийти", "registerButton": "Зареєструватися", + "resetPassword": { + "changeEmailFirst": "Будь ласка, підтвердіть вашу електронну адресу перед скиданням пароля.", + "sent": "Якщо обліковий запис із такою електронною адресою існує, було надіслано посилання для зміни пароля.", + "success": "Пароль успішно скинуто", + }, "splash": { "description": "У Chili and Cilantro починаючі кухарі змагаються, щоб створити ідеальну страву. Ваша мета — додати потрібну кількість кінзи, не зіпсувавши її пекучим чилі. Будьте першим, хто успішно приправить дві страви, або будьте останнім шеф-кухарем, який виграє!", "howToPlay": "Як Грати", }, + "testTestTemplate": "Тестування {VARIABLE1} {VARIABLE2}", "validation": { "confirmNewPassword": "Підтвердження нового пароля є обов’язковим", "currentPasswordRequired": "Поточний пароль є обов’язковим", "invalidEmail": "Недійсний електронній адрес", "invalidLanguage": "Недійсна мова", + "invalidTimezone": "Недійсна часова зона", "invalidToken": "Недійсний токен", "newPasswordRequired": "Новий пароль є обов’язковим", "passwordMatch": "Пароль та підтвердження повинні співпадати", - "passwordRegexErrorTemplate": "Пароль повинен містити принаймні 8 символів, включаючи принаймні одну літеру, одну цифру та один спеціальний символ (!@#$%^&*()_+-=[]{};':"|,.<>/?)", + "passwordRegexErrorTemplate": "Пароль повинен бути від {MIN_PASSWORD_LENGTH} до {MAX_PASSWORD_LENGTH} символів, та містити як мінімум: + • Одну нижню літеру (всіх скриптів) + • Одну верхню літеру (всіх скриптів) + • Одну цифру (будь-яку систему числення) + • Один спеціальний символ (пунктуацію або символ)", "passwordsDifferent": "Новий пароль повинен відрізнятися від поточного", "required": "Обов’язково", + "usernameRegexErrorTemplate": "Ім’я користувача повинно бути від {MIN_USERNAME_LENGTH} до {MAX_USERNAME_LENGTH} символів, та: + • Починатися з літери, числа, або Unicode-символу + • Містити літери, числа, підкреслення, тире, та Unicode-символи + • Не містити пробілів або спеціальних символів, крім підкреслення і тире", }, "validationError": "Помилка валідації", } @@ -438,6 +678,7 @@ exports[`buildNestedI18n should handle 中文 language 1`] = ` }, "changePassword": { "changePasswordButton": "更改密码", + "success": "密码更改成功", }, "common": { "changePassword": "更改密码", @@ -448,13 +689,17 @@ exports[`buildNestedI18n should handle 中文 language 1`] = ` "goToSplash": "返回欢迎屏幕", "loading": "加载中...", "logo": "标志", + "masterChef": "大厨", "newPassword": "新密码", + "password": "密码", "returnToKitchen": "返回厨房", "site": "辣椒和香菜", "startCooking": "开始烹饪", "tagline": "一款辛辣的虚张声势游戏", + "tokenValid": "令牌有效", "unauthorized": "未经授权", "unexpectedError": "意外错误", + "username": "用户名", }, "dashboard": { "gamesCreated": "您创建的游戏", @@ -462,6 +707,29 @@ exports[`buildNestedI18n should handle 中文 language 1`] = ` "noGames": "没有可用的游戏。", "title": "您的仪表板", }, + "emailToken": { + "clickLinkEmailConfirm": "请单击下面的链接确认您的电子邮件。", + "clickLinkResetPassword": "请单击下面的链接重置密码。", + "expiresInTemplate": "链接在{EMAIL_TOKEN_EXPIRATION_MIN}分钟后过期。", + "titleEmailConfirm": "电子邮件确认", + "titleResetPassword": "密码重置", + }, + "error": { + "accountStatusIsDeleted": "帐户已删除", + "accountStatusIsLocked": "帐户已锁定", + "accountStatusIsPendingEmailVerification": "帐户待电子邮件验证", + "allCardsPlaced": "所有卡片已放置", + "chefAlreadyInGame": "厨师已经在游戏中", + "emailAlreadyVerified": "电子邮件已验证", + "emailInUse": "电子邮件已在使用中", + "emailTokenAlreadySent": "电子邮件验证链接已被使用或无效", + "emailTokenExpired": "验证链接已过期。请请求一个新的。", + "emailTokenSentTooRecentlyTemplate": "电子邮件令牌发送太频繁。请在{TIME_REMAINING}秒后重试。", + "failedToCreateEmailToken": "无法创建电子邮件令牌", + "sendTokenFailure": "无法发送电子邮件令牌", + "tooManyChefs": "厨房里有太多厨师", + "youAlreadyJoined": "您已经加入该游戏", + }, "forgotPassword": { "forgotPassword": "忘记密码", "invalidToken": "无效或过期的令牌。请请求新密码重置。", @@ -493,24 +761,44 @@ exports[`buildNestedI18n should handle 中文 language 1`] = ` }, "login": { "loginButton": "登录", + "progress": "登录中...", + "resentPasswordFailure": "重新发送密码失败", + "resentPasswordSuccess": "重发密码成功", + "title": "登录", + "usernameOrEmailRequired": "需要用户名或电子邮件", }, "logoutButton": "注销", "registerButton": "注册", + "resetPassword": { + "changeEmailFirst": "在重置密码之前,请验证您的电子邮件地址。", + "sent": "如果有帐户与此电子邮件相关,密码重置链接已发送。", + "success": "密码重置成功", + }, "splash": { "description": "在《辣椒和香菜》中,有抱负的厨师们竞相制作完美的菜肴。你的目标是添加适量的香菜,而不会让辣椒烧焦。成为第一个成功调味两道菜的人,或者成为最后一个获胜的厨师", "howToPlay": "如何玩", }, + "testTestTemplate": "测试 {VARIABLE1} {VARIABLE2}", "validation": { "confirmNewPassword": "确认新密码是必需的", "currentPasswordRequired": "当前密码是必需的", "invalidEmail": "无效电子邮件", "invalidLanguage": "无效语言", + "invalidTimezone": "无效时区", "invalidToken": "无效令牌", "newPasswordRequired": "新密码是必需的", "passwordMatch": "密码和确认必须匹配", - "passwordRegexErrorTemplate": "密码必须至少8个字符长且包含至少一个字母、一个数字和一个特殊字符(!@#$%^&*()_+-=[]{};:"|,.<>/?)", + "passwordRegexErrorTemplate": "密码必须在{MIN_PASSWORD_LENGTH}和{MAX_PASSWORD_LENGTH}个字符之间,并且至少包含: + • 一个小写字母(任何脚本) + • 一个大写字母(任何脚本) + • 一个数字(任何数字系统) + • 一个特殊字符(标点符号或符号)", "passwordsDifferent": "新密码必须不同于当前密码", "required": "必填", + "usernameRegexErrorTemplate": "用户名必须在{MIN_USERNAME_LENGTH}和{MAX_USERNAME_LENGTH}个字符之间,并且: + • 以字母,数字,或Unicode字符开头 + • 可以包含字母,数字,下划线,短横线,或Unicode字符 + • 不能包含空格或特殊字符,除下划线和短横线外", }, "validationError": "验证错误", } @@ -524,6 +812,7 @@ exports[`buildNestedI18nForLanguage should call buildNestedI18n with the correct }, "changePassword": { "changePasswordButton": "Change Password", + "success": "Password changed successfully", }, "common": { "changePassword": "Change Password", @@ -534,13 +823,17 @@ exports[`buildNestedI18nForLanguage should call buildNestedI18n with the correct "goToSplash": "Return to Welcome Screen", "loading": "Loading...", "logo": "logo", + "masterChef": "Master Chef", "newPassword": "New Password", + "password": "Password", "returnToKitchen": "Return to Kitchen", "site": "Chili and Cilantro", "startCooking": "Start Cooking", "tagline": "A Spicy Bluffing Game", + "tokenValid": "Token is valid", "unauthorized": "Unauthorized", "unexpectedError": "Unexpected Error", + "username": "Username", }, "dashboard": { "gamesCreated": "Games You've Created", @@ -548,6 +841,29 @@ exports[`buildNestedI18nForLanguage should call buildNestedI18n with the correct "noGames": "No games available.", "title": "Your Dashboard", }, + "emailToken": { + "clickLinkEmailConfirm": "Please click the link below to confirm your email.", + "clickLinkResetPassword": "Please click the link below to reset your password.", + "expiresInTemplate": "Link expires in {EMAIL_TOKEN_EXPIRATION_MIN} minutes.", + "titleEmailConfirm": "Email Confirmation", + "titleResetPassword": "Password Reset", + }, + "error": { + "accountStatusIsDeleted": "Account deleted", + "accountStatusIsLocked": "Account locked", + "accountStatusIsPendingEmailVerification": "Account pending email verification", + "allCardsPlaced": "All cards placed", + "chefAlreadyInGame": "Chef is already in an active game", + "emailAlreadyVerified": "Email has already been verified", + "emailInUse": "Email is already in use", + "emailTokenAlreadySent": "Email verification link has already been used or is invalid", + "emailTokenExpired": "Verification link has expired. Please request a new one.", + "emailTokenSentTooRecentlyTemplate": "Email token sent too recently. Please try again in {TIME_REMAINING} seconds.", + "failedToCreateEmailToken": "Failed to create email token", + "sendTokenFailure": "Failed to send email token", + "tooManyChefs": "Too many chefs in the kitchen", + "youAlreadyJoined": "You have already joined this game", + }, "forgotPassword": { "forgotPassword": "Forgot Password", "invalidToken": "Invalid or expired token. Please request a new password reset.", @@ -579,24 +895,44 @@ exports[`buildNestedI18nForLanguage should call buildNestedI18n with the correct }, "login": { "loginButton": "Login", + "progress": "Logging in...", + "resentPasswordFailure": "Failed to resend verification email", + "resentPasswordSuccess": "Verification email sent successfully", + "title": "Login", + "usernameOrEmailRequired": "Either username or email is required", }, "logoutButton": "Logout", "registerButton": "Register", + "resetPassword": { + "changeEmailFirst": "Please verify your email address before resetting your password.", + "sent": "If an account with that email exists, a password reset link has been sent.", + "success": "Password reset successfully", + }, "splash": { "description": "In Chili and Cilantro, aspiring chefs compete to create the perfect dish. Your goal is to add just the right amount of cilantro without ruining it with a scorching chili. Be the first to successfully season two dishes or be the last chef standing to win!", "howToPlay": "How to Play", }, + "testTestTemplate": "Testing {VARIABLE1} {VARIABLE2}", "validation": { "confirmNewPassword": "Confirm new password is required", "currentPasswordRequired": "Current password is required", "invalidEmail": "Invalid Email", "invalidLanguage": "Invalid Language", + "invalidTimezone": "Invalid Timezone", "invalidToken": "Invalid Token", "newPasswordRequired": "New password is required", "passwordMatch": "Password and confirm must match", - "passwordRegexErrorTemplate": "Password must be at least 8 characters long and include at least one letter, one number, and one special character (!@#$%^&*()_+-=[]{};':"|,.<>/?)", + "passwordRegexErrorTemplate": "Password must be between {MIN_PASSWORD_LENGTH} and {MAX_PASSWORD_LENGTH} characters, and contain at least: + • One lowercase character (any script) + • One uppercase character (any script) + • One number (any numeral system) + • One special character (punctuation or symbol)", "passwordsDifferent": "New password must be different than the current password", "required": "Required", + "usernameRegexErrorTemplate": "Username must be between {MIN_USERNAME_LENGTH} and {MAX_USERNAME_LENGTH} characters, and: + • Start with a letter, number, or Unicode character + • Can contain letters, numbers, underscores, hyphens, and Unicode characters + • Cannot contain spaces or special characters other than underscores and hyphens", }, "validationError": "Validation Error", } @@ -610,6 +946,7 @@ exports[`buildNestedI18nForLanguage should call buildNestedI18n with the correct }, "changePassword": { "changePasswordButton": "Change Password", + "success": "Password changed successfully", }, "common": { "changePassword": "Change Password", @@ -620,13 +957,17 @@ exports[`buildNestedI18nForLanguage should call buildNestedI18n with the correct "goToSplash": "Return to Welcome Screen", "loading": "Loading...", "logo": "logo", + "masterChef": "Master Chef", "newPassword": "New Password", + "password": "Password", "returnToKitchen": "Return to Kitchen", "site": "Chili and Cilantro", "startCooking": "Start Cooking", "tagline": "A Spicy Bluffing Game", + "tokenValid": "Token is valid", "unauthorized": "Unauthorized", "unexpectedError": "Unexpected Error", + "username": "Username", }, "dashboard": { "gamesCreated": "Games You've Created", @@ -634,6 +975,29 @@ exports[`buildNestedI18nForLanguage should call buildNestedI18n with the correct "noGames": "No games available.", "title": "Your Dashboard", }, + "emailToken": { + "clickLinkEmailConfirm": "Please click the link below to confirm your email.", + "clickLinkResetPassword": "Please click the link below to reset your password.", + "expiresInTemplate": "Link expires in {EMAIL_TOKEN_EXPIRATION_MIN} minutes.", + "titleEmailConfirm": "Email Confirmation", + "titleResetPassword": "Password Reset", + }, + "error": { + "accountStatusIsDeleted": "Account deleted", + "accountStatusIsLocked": "Account locked", + "accountStatusIsPendingEmailVerification": "Account pending email verification", + "allCardsPlaced": "All cards have been placed.", + "chefAlreadyInGame": "Chef is already in an active game.", + "emailAlreadyVerified": "Email has already been verified", + "emailInUse": "Email is already in use", + "emailTokenAlreadySent": "Email verification link has already been used or is invalid", + "emailTokenExpired": "Verification link has expired. Please request a new one.", + "emailTokenSentTooRecentlyTemplate": "Email token sent too recently. Please try again in {TIME_REMAINING} seconds.", + "failedToCreateEmailToken": "Failed to create email token", + "sendTokenFailure": "Failed to send email token", + "tooManyChefs": "Too many chefs in the kitchen.", + "youAlreadyJoined": "You have already joined this game.", + }, "forgotPassword": { "forgotPassword": "Forgot Password", "invalidToken": "Invalid or expired token. Please request a new password reset.", @@ -665,24 +1029,44 @@ exports[`buildNestedI18nForLanguage should call buildNestedI18n with the correct }, "login": { "loginButton": "Login", + "progress": "Logging in...", + "resentPasswordFailure": "Failed to resend verification email", + "resentPasswordSuccess": "Verification email sent successfully", + "title": "Login", + "usernameOrEmailRequired": "Either username or email is required", }, "logoutButton": "Logout", "registerButton": "Register", + "resetPassword": { + "changeEmailFirst": "Please verify your email address before resetting your password.", + "sent": "If an account with that email exists, a password reset link has been sent.", + "success": "Password reset successfully", + }, "splash": { "description": "In Chili and Cilantro, aspiring chefs compete to create the perfect dish. Your goal is to add just the right amount of cilantro without ruining it with a scorching chili. Be the first to successfully season two dishes or be the last chef standing to win!", "howToPlay": "How to Play", }, + "testTestTemplate": "Testing {VARIABLE1} {VARIABLE2}", "validation": { "confirmNewPassword": "Confirm new password is required", "currentPasswordRequired": "Current password is required", "invalidEmail": "Invalid Email", "invalidLanguage": "Invalid Language", + "invalidTimezone": "Invalid Timezone", "invalidToken": "Invalid Token", "newPasswordRequired": "New password is required", "passwordMatch": "Password and confirm must match", - "passwordRegexErrorTemplate": "Password must be at least {MIN_PASSWORD_LENGTH} characters long and include at least one letter, one number, and one special character (!@#$%^&*()_+-=[]{};':"|,.<>/?)", + "passwordRegexErrorTemplate": "Password must be between {MIN_PASSWORD_LENGTH} and {MAX_PASSWORD_LENGTH} characters, and contain at least: + • One lowercase character (any script) + • One uppercase character (any script) + • One number (any numeral system) + • One special character (punctuation or symbol)", "passwordsDifferent": "New password must be different than the current password", "required": "Required", + "usernameRegexErrorTemplate": "Username must be between {MIN_USERNAME_LENGTH} and {MAX_USERNAME_LENGTH} characters, and: + • Start with a letter, number, or Unicode character + • Can contain letters, numbers, underscores, hyphens, and Unicode characters + • Cannot contain spaces or special characters other than underscores and hyphens", }, "validationError": "Validation Error", } @@ -696,6 +1080,7 @@ exports[`buildNestedI18nForLanguage should call buildNestedI18n with the correct }, "changePassword": { "changePasswordButton": "Changer le mot de passe", + "success": "Mot de passe changé avec succès", }, "common": { "changePassword": "Changer le mot de passe", @@ -706,13 +1091,17 @@ exports[`buildNestedI18nForLanguage should call buildNestedI18n with the correct "goToSplash": "Retourner aucran d'accueil", "loading": "Chargement...", "logo": "logo", + "masterChef": "Chef de cuisine", "newPassword": "Nouveau mot de passe", + "password": "Mot de passe", "returnToKitchen": "Retour à la Cuisine", "site": "Piment et Coriandre", "startCooking": "Commencer la Cuisine", "tagline": "Un Jeu de Bluff Épicé", + "tokenValid": "Jeton valide", "unauthorized": "Non autorisé", "unexpectedError": "Erreur inattendue", + "username": "Nom d’utilisateur", }, "dashboard": { "gamesCreated": "Jeux que vous avez créé", @@ -720,6 +1109,29 @@ exports[`buildNestedI18nForLanguage should call buildNestedI18n with the correct "noGames": "Aucun jeu disponible.", "title": "Votre Tableau de Bord", }, + "emailToken": { + "clickLinkEmailConfirm": "Veuillez cliquer sur le lien ci-dessous pour confirmer votre courriel.", + "clickLinkResetPassword": "Veuillez cliquer sur le lien ci-dessous pour réinitialiser votre mot de passe.", + "expiresInTemplate": "Le lien expire dans {EMAIL_TOKEN_EXPIRATION_MIN} minutes.", + "titleEmailConfirm": "Confirmation de courriel", + "titleResetPassword": "Réinitialisation du mot de passe", + }, + "error": { + "accountStatusIsDeleted": "Compte supprimé", + "accountStatusIsLocked": "Compte verrouillé", + "accountStatusIsPendingEmailVerification": "Compte en attente de confirmation de courriel", + "allCardsPlaced": "Toutes les cartes ont été placées", + "chefAlreadyInGame": "Chef deja dans le jeu", + "emailAlreadyVerified": "Courriel déjà vérifié", + "emailInUse": "Courriel déjà utilisé", + "emailTokenAlreadySent": "Le lien de vérification par courriel a déjà été utilisé ou est invalide", + "emailTokenExpired": "Le lien de vérification a expiré. Veuillez en demander un nouveau.", + "emailTokenSentTooRecentlyTemplate": "Le jeton de courrier électronique a été envoyé trop récemment. Veuillez réessayer dans {TIME_REMAINING} secondes.", + "failedToCreateEmailToken": "Échec de la création du jeton par courriel", + "sendTokenFailure": "Échec de l’envoi du jeton par courriel", + "tooManyChefs": "Trop de chefs dans la cuisine", + "youAlreadyJoined": "Vous avez déjà rejoint ce jeu", + }, "forgotPassword": { "forgotPassword": "Mot de passe oublié", "invalidToken": "Jeton invalide ou expiré. Veuillez demander un nouveau mot de passe.", @@ -751,24 +1163,44 @@ exports[`buildNestedI18nForLanguage should call buildNestedI18n with the correct }, "login": { "loginButton": "Connexion", + "progress": "Connexion en cours...", + "resentPasswordFailure": "Échec de l’envoi du courriel de verification", + "resentPasswordSuccess": "Courriel de verification envoyé avec успех", + "title": "Connexion", + "usernameOrEmailRequired": "Nom d’utilisateur ou courriel requis", }, "logoutButton": "Déconnexion", "registerButton": "S'inscrire", + "resetPassword": { + "changeEmailFirst": "Vous devez changer votre courriel avant de pouvoir changer votre mot de passe.", + "sent": "Si un compte avec cet courriel existe, un lien de réinitialisation du mot de passe a été envoyé.", + "success": "Réinitialisation du mot de passe réussie", + }, "splash": { "description": "Dans Chili and Cilantro, les chefs en herbe rivalisent pour créer le plat parfait. Votre objectif est d'ajouter juste la bonne quantité de coriandre sans gâcher le tout avec un piment brûlant. Soyez le premier à réussir à assaisonner deux plats ou soyez le dernier chef debout pour gagner !", "howToPlay": "Comment Jouer", }, + "testTestTemplate": "Essai {VARIABLE1} {VARIABLE2}", "validation": { "confirmNewPassword": "La confirmation du nouveau mot de passe est requise", "currentPasswordRequired": "Le mot de passe actuel est requis", "invalidEmail": "Courriel invalide", "invalidLanguage": "Langue invalide", + "invalidTimezone": "Fuseau horaire invalide", "invalidToken": "Jeton invalide", "newPasswordRequired": "Le nouveau mot de passe est requis", "passwordMatch": "Le mot de passe et la confirmation doivent correspondre", - "passwordRegexErrorTemplate": "Le mot de passe doit comporter au moins 8 caractères et inclure au moins une lettre, un chiffre et un caractère spécial (!@#$%^&*()_+-=[]{};':"|,.<>/?)", + "passwordRegexErrorTemplate": "Le mot de passe doit contenir entre {MIN_PASSWORD_LENGTH} et {MAX_PASSWORD_LENGTH} caractères, et contenir au moins : + • Une lettre minuscule (tout script) + • Une lettre majuscule (tout script) + • Un chiffre (tout système numérique) + • Un caractère spécial (ponctuation ou symbole)", "passwordsDifferent": "Le nouveau mot de passe doit être différent du mot de passe actuel", "required": "Champ obligatoire", + "usernameRegexErrorTemplate": "Le nom d'utilisateur doit contenir entre {MIN_USERNAME_LENGTH} et {MAX_USERNAME_LENGTH} caractères, et : + • Commencer par une lettre, un chiffre, ou un caractère Unicode + • Peut contenir des lettres, des chiffres, des underscores, des tirets, et des caractères Unicode + • Ne peut contenir des espaces ou des caractères spéciaux autre que des underscores et des tirets", }, "validationError": "Erreur de validation", } @@ -782,6 +1214,7 @@ exports[`buildNestedI18nForLanguage should call buildNestedI18n with the correct }, "changePassword": { "changePasswordButton": "更改密码", + "success": "密码更改成功", }, "common": { "changePassword": "更改密码", @@ -792,13 +1225,17 @@ exports[`buildNestedI18nForLanguage should call buildNestedI18n with the correct "goToSplash": "返回欢迎屏幕", "loading": "加载中...", "logo": "标志", + "masterChef": "大厨", "newPassword": "新密码", + "password": "密码", "returnToKitchen": "返回厨房", "site": "辣椒和香菜", "startCooking": "开始烹饪", "tagline": "一款辛辣的虚张声势游戏", + "tokenValid": "令牌有效", "unauthorized": "未经授权", "unexpectedError": "意外错误", + "username": "用户名", }, "dashboard": { "gamesCreated": "您创建的游戏", @@ -806,6 +1243,29 @@ exports[`buildNestedI18nForLanguage should call buildNestedI18n with the correct "noGames": "没有可用的游戏。", "title": "您的仪表板", }, + "emailToken": { + "clickLinkEmailConfirm": "请单击下面的链接确认您的电子邮件。", + "clickLinkResetPassword": "请单击下面的链接重置密码。", + "expiresInTemplate": "链接在{EMAIL_TOKEN_EXPIRATION_MIN}分钟后过期。", + "titleEmailConfirm": "电子邮件确认", + "titleResetPassword": "密码重置", + }, + "error": { + "accountStatusIsDeleted": "帐户已删除", + "accountStatusIsLocked": "帐户已锁定", + "accountStatusIsPendingEmailVerification": "帐户待电子邮件验证", + "allCardsPlaced": "所有卡片已放置", + "chefAlreadyInGame": "厨师已经在游戏中", + "emailAlreadyVerified": "电子邮件已验证", + "emailInUse": "电子邮件已在使用中", + "emailTokenAlreadySent": "电子邮件验证链接已被使用或无效", + "emailTokenExpired": "验证链接已过期。请请求一个新的。", + "emailTokenSentTooRecentlyTemplate": "电子邮件令牌发送太频繁。请在{TIME_REMAINING}秒后重试。", + "failedToCreateEmailToken": "无法创建电子邮件令牌", + "sendTokenFailure": "无法发送电子邮件令牌", + "tooManyChefs": "厨房里有太多厨师", + "youAlreadyJoined": "您已经加入该游戏", + }, "forgotPassword": { "forgotPassword": "忘记密码", "invalidToken": "无效或过期的令牌。请请求新密码重置。", @@ -837,24 +1297,44 @@ exports[`buildNestedI18nForLanguage should call buildNestedI18n with the correct }, "login": { "loginButton": "登录", + "progress": "登录中...", + "resentPasswordFailure": "重新发送密码失败", + "resentPasswordSuccess": "重发密码成功", + "title": "登录", + "usernameOrEmailRequired": "需要用户名或电子邮件", }, "logoutButton": "注销", "registerButton": "注册", + "resetPassword": { + "changeEmailFirst": "在重置密码之前,请验证您的电子邮件地址。", + "sent": "如果有帐户与此电子邮件相关,密码重置链接已发送。", + "success": "密码重置成功", + }, "splash": { "description": "在《辣椒和香菜》中,有抱负的厨师们竞相制作完美的菜肴。你的目标是添加适量的香菜,而不会让辣椒烧焦。成为第一个成功调味两道菜的人,或者成为最后一个获胜的厨师", "howToPlay": "如何玩", }, + "testTestTemplate": "测试 {VARIABLE1} {VARIABLE2}", "validation": { "confirmNewPassword": "确认新密码是必需的", "currentPasswordRequired": "当前密码是必需的", "invalidEmail": "无效电子邮件", "invalidLanguage": "无效语言", + "invalidTimezone": "无效时区", "invalidToken": "无效令牌", "newPasswordRequired": "新密码是必需的", "passwordMatch": "密码和确认必须匹配", - "passwordRegexErrorTemplate": "密码必须至少8个字符长且包含至少一个字母、一个数字和一个特殊字符(!@#$%^&*()_+-=[]{};:"|,.<>/?)", + "passwordRegexErrorTemplate": "密码必须在{MIN_PASSWORD_LENGTH}和{MAX_PASSWORD_LENGTH}个字符之间,并且至少包含: + • 一个小写字母(任何脚本) + • 一个大写字母(任何脚本) + • 一个数字(任何数字系统) + • 一个特殊字符(标点符号或符号)", "passwordsDifferent": "新密码必须不同于当前密码", "required": "必填", + "usernameRegexErrorTemplate": "用户名必须在{MIN_USERNAME_LENGTH}和{MAX_USERNAME_LENGTH}个字符之间,并且: + • 以字母,数字,或Unicode字符开头 + • 可以包含字母,数字,下划线,短横线,或Unicode字符 + • 不能包含空格或特殊字符,除下划线和短横线外", }, "validationError": "验证错误", } @@ -868,6 +1348,7 @@ exports[`buildNestedI18nForLanguage should call buildNestedI18n with the correct }, "changePassword": { "changePasswordButton": "Cambiar contraseña", + "success": "Contraseña cambiada con éxito", }, "common": { "changePassword": "Cambiar contraseña", @@ -878,13 +1359,17 @@ exports[`buildNestedI18nForLanguage should call buildNestedI18n with the correct "goToSplash": "Volver a la pantalla de bienvenida", "loading": "Cargando...", "logo": "logo", + "masterChef": "Chef Maestro", "newPassword": "Nueva contraseña", + "password": "Contraseña", "returnToKitchen": "Volver a la Cocina", "site": "Chili y Cilantro", "startCooking": "Comenzar a cocinar", "tagline": "Un Juego de Bluff Picante", + "tokenValid": "Token válido", "unauthorized": "No autorizado", "unexpectedError": "Error inesperado", + "username": "Nombre de usuario", }, "dashboard": { "gamesCreated": "Juegos que has creado", @@ -892,6 +1377,29 @@ exports[`buildNestedI18nForLanguage should call buildNestedI18n with the correct "noGames": "No hay juegos disponibles.", "title": "Tu Tablero", }, + "emailToken": { + "clickLinkEmailConfirm": "Por favor, haz clic en el enlace de abajo para confirmar tu correo electrónica.", + "clickLinkResetPassword": "Por favor, haz clic en el enlace de abajo para restablecer tu contraseña.", + "expiresInTemplate": "El enlace caducará en {EMAIL_TOKEN_EXPIRATION_MIN} minutos.", + "titleEmailConfirm": "Confirmación de correo electrónico", + "titleResetPassword": "Restablecimiento de contraseña", + }, + "error": { + "accountStatusIsDeleted": "Cuenta eliminada", + "accountStatusIsLocked": "Cuenta bloqueada", + "accountStatusIsPendingEmailVerification": "Cuenta en espera de verificación de correo electrónico", + "allCardsPlaced": "Todas las cartas han sido colocadas.", + "chefAlreadyInGame": "El chef ya está en un juego activo.", + "emailAlreadyVerified": "El correo electrónico ya ha sido verificado", + "emailInUse": "El correo electrónica ya esta en uso.", + "emailTokenAlreadySent": "El enlace de verificación de correo electrónica ya ha sido usado o es inválido.", + "emailTokenExpired": "El enlace de verificación ha caducado. Por favor, solicita uno nuevo.", + "emailTokenSentTooRecentlyTemplate": "El token de correo electrónico se envió hace muy poco. Inténtalo de nuevo en {TIME_REMAINING} segundos.", + "failedToCreateEmailToken": "Fallo al crear el token de correo", + "sendTokenFailure": "Fallo al enviar el token de correo", + "tooManyChefs": "Demasiados chefs en la cocina", + "youAlreadyJoined": "Ya te has unido a este juego", + }, "forgotPassword": { "forgotPassword": "Olvidé mi contraseña", "invalidToken": "Token inválido o expirado. Por favor, solicita un nuevo restablecimiento de contraseña.", @@ -923,24 +1431,44 @@ exports[`buildNestedI18nForLanguage should call buildNestedI18n with the correct }, "login": { "loginButton": "Iniciar sesión", + "progress": "Iniciando sesión...", + "resentPasswordFailure": "Reenviar contraseña fallido", + "resentPasswordSuccess": "Reenviar contraseña con éxito", + "title": "Iniciar sesión", + "usernameOrEmailRequired": "Se requiere un nombre de usuario o correo electrónica", }, "logoutButton": "Cerrar sesión", "registerButton": "Registrarse", + "resetPassword": { + "changeEmailFirst": "Antes de restablecer la contraseña, verifica tu dirección de correo electrónico.", + "sent": "Si existe una cuenta con ese correo electrónico, se ha enviado un enlace para restablecer la contraseña.", + "success": "Contraseña restablecida con éxito", + }, "splash": { "description": "En Chili and Cilantro, los aspirantes a chef compiten para crear el plato perfecto. Tu objetivo es agregar la cantidad justa de cilantro sin arruinarlo con un chile abrasador. ¡Sé el primero en condimentar con éxito dos platos o sé el último chef en pie para ganar!", "howToPlay": "Como Jugar", }, + "testTestTemplate": "Probando {VARIABLE1} {VARIABLE2}", "validation": { "confirmNewPassword": "Se requiere la confirmación de la nueva contraseña", "currentPasswordRequired": "Se requiere la contraseña actual", "invalidEmail": "Correo electrónica inválido", "invalidLanguage": "Idioma inválido", + "invalidTimezone": "Fuseau horaire invático", "invalidToken": "Token inválido", "newPasswordRequired": "Se requiere la nueva contraseña", "passwordMatch": "La contraseña y la confirmación deben coincidir", - "passwordRegexErrorTemplate": "La contraseña debe tener al menos 8 caracteres y incluir al menos una letra, un número y un carácter especial (!@#$%^&*()_+-=[]{};':"|,.<>/?)", + "passwordRegexErrorTemplate": "La contraseña debe estar entre {MIN_PASSWORD_LENGTH} y {MAX_PASSWORD_LENGTH} caracteres, y contener al menos: + • Una letra minuscula (cualquier script) + • Una letra mayúscula (cualquier script) + • Un número (cualquier sistema numérico) + • Un caracter especial (puntuación o símbolo)", "passwordsDifferent": "La nueva contraseña debe ser diferente a la contraseña actual", "required": "Requerido", + "usernameRegexErrorTemplate": "El nombre de usuario debe tener entre {MIN_USERNAME_LENGTH} y {MAX_USERNAME_LENGTH} caracteres, y: + • Comenzar con una letra, un número, o un carácter Unicode + • Puede contener letras, números, guiones bajos, guiones, y carácteres Unicode + • No puede contener espacios o carácteres especiales excepto guiones bajos y guiones", }, "validationError": "Error de validación", } @@ -954,6 +1482,7 @@ exports[`buildNestedI18nForLanguage should call buildNestedI18n with the correct }, "changePassword": { "changePasswordButton": "Змінити пароль", + "success": "Пароль успішно змінено", }, "common": { "changePassword": "Змінити пароль", @@ -964,13 +1493,17 @@ exports[`buildNestedI18nForLanguage should call buildNestedI18n with the correct "goToSplash": "Повернутися до екрану привітання", "loading": "Завантаження...", "logo": "лого", + "masterChef": "Майстер-кухар", "newPassword": "Новий пароль", + "password": "Пароль", "returnToKitchen": "Повернутися на Кухню", "site": "Чілі та Коріандр", "startCooking": "Почати приготування", "tagline": "Пікантний Блеф", + "tokenValid": "Токен діїдженний", "unauthorized": "Немає авторизації", "unexpectedError": "Неочікувана помилка", + "username": "Ім’я користувача", }, "dashboard": { "gamesCreated": "Гри, які ти створив", @@ -978,6 +1511,29 @@ exports[`buildNestedI18nForLanguage should call buildNestedI18n with the correct "noGames": "Немає доступних ігор.", "title": "Ваша Панель", }, + "emailToken": { + "clickLinkEmailConfirm": "Натисніть на посилання для підтвердження електронної пошти", + "clickLinkResetPassword": "Натисніть на посилання для скидання пароля", + "expiresInTemplate": "Посилання дійсне протягом {EMAIL_TOKEN_EXPIRATION_MIN} хвилин.", + "titleEmailConfirm": "Підтвердження електронної пошти", + "titleResetPassword": "Скидання пароля", + }, + "error": { + "accountStatusIsDeleted": "Обліковий запис видалено", + "accountStatusIsLocked": "Обліковий запис заблоковано", + "accountStatusIsPendingEmailVerification": "Обліковий запис очікує підтвердження електронної пошти", + "allCardsPlaced": "Всі картки розміщені", + "chefAlreadyInGame": "Шеф-кухар вже в грі", + "emailAlreadyVerified": "Електронна пошта вже підтверджена", + "emailInUse": "Електронна пошта вже використовується", + "emailTokenAlreadySent": "Посилання для підтвердження електронної пошти вже використано або недійсне", + "emailTokenExpired": "Термін дії посилання закінчився. Будь ласка, запросіть новий.", + "emailTokenSentTooRecentlyTemplate": "Маркер електронної пошти надіслано занадто недавно. Повторіть спробу через {TIME_REMAINING} с.", + "failedToCreateEmailToken": "Неможливо створити електронний токен", + "sendTokenFailure": "Неможливо відправити електронний токен", + "tooManyChefs": "Забагато шеф-кухарів на кухні", + "youAlreadyJoined": "Ви вже приєдналися до цієї гри", + }, "forgotPassword": { "forgotPassword": "Забули пароль?", "invalidToken": "Недіїдженний токен. Будь ласка, зверніться до підтримки.", @@ -1009,24 +1565,44 @@ exports[`buildNestedI18nForLanguage should call buildNestedI18n with the correct }, "login": { "loginButton": "Увійти", + "progress": "Вхід...", + "resentPasswordFailure": "Невдалося відправити новий пароль", + "resentPasswordSuccess": "Новиий пароль успішно відправлено", + "title": "Увійти", + "usernameOrEmailRequired": "Необхідно вказати ім’я користувача або електронну пошту", }, "logoutButton": "Вийти", "registerButton": "Зареєструватися", + "resetPassword": { + "changeEmailFirst": "Будь ласка, підтвердіть вашу електронну адресу перед скиданням пароля.", + "sent": "Якщо обліковий запис із такою електронною адресою існує, було надіслано посилання для зміни пароля.", + "success": "Пароль успішно скинуто", + }, "splash": { "description": "У Chili and Cilantro починаючі кухарі змагаються, щоб створити ідеальну страву. Ваша мета — додати потрібну кількість кінзи, не зіпсувавши її пекучим чилі. Будьте першим, хто успішно приправить дві страви, або будьте останнім шеф-кухарем, який виграє!", "howToPlay": "Як Грати", }, + "testTestTemplate": "Тестування {VARIABLE1} {VARIABLE2}", "validation": { "confirmNewPassword": "Підтвердження нового пароля є обов’язковим", "currentPasswordRequired": "Поточний пароль є обов’язковим", "invalidEmail": "Недійсний електронній адрес", "invalidLanguage": "Недійсна мова", + "invalidTimezone": "Недійсна часова зона", "invalidToken": "Недійсний токен", "newPasswordRequired": "Новий пароль є обов’язковим", "passwordMatch": "Пароль та підтвердження повинні співпадати", - "passwordRegexErrorTemplate": "Пароль повинен містити принаймні 8 символів, включаючи принаймні одну літеру, одну цифру та один спеціальний символ (!@#$%^&*()_+-=[]{};':"|,.<>/?)", + "passwordRegexErrorTemplate": "Пароль повинен бути від {MIN_PASSWORD_LENGTH} до {MAX_PASSWORD_LENGTH} символів, та містити як мінімум: + • Одну нижню літеру (всіх скриптів) + • Одну верхню літеру (всіх скриптів) + • Одну цифру (будь-яку систему числення) + • Один спеціальний символ (пунктуацію або символ)", "passwordsDifferent": "Новий пароль повинен відрізнятися від поточного", "required": "Обов’язково", + "usernameRegexErrorTemplate": "Ім’я користувача повинно бути від {MIN_USERNAME_LENGTH} до {MAX_USERNAME_LENGTH} символів, та: + • Починатися з літери, числа, або Unicode-символу + • Містити літери, числа, підкреслення, тире, та Unicode-символи + • Не містити пробілів або спеціальних символів, крім підкреслення і тире", }, "validationError": "Помилка валідації", } diff --git a/chili-and-cilantro-lib/src/lib/constants.ts b/chili-and-cilantro-lib/src/lib/constants.ts index 4abce43..a55acad 100644 --- a/chili-and-cilantro-lib/src/lib/constants.ts +++ b/chili-and-cilantro-lib/src/lib/constants.ts @@ -178,18 +178,37 @@ export const EMAIL_FROM = `noreply@${SITE_DOMAIN}`; export const APPLICATION_NAME = 'Chili & Cilantro'; /** - * Duration in milliseconds for which an email token is valid. + * Duration in milliseconds for which an email token is valid, in minutes */ -export const EMAIL_TOKEN_EXPIRATION = 24 * 60 * 60 * 1000; +export const EMAIL_TOKEN_EXPIRATION_MIN = 15; +/** + * Duration in milliseconds for which an email token is valid, in seconds + */ +export const EMAIL_TOKEN_EXPIRATION_SEC = EMAIL_TOKEN_EXPIRATION_MIN * 60; +/** + * Duration in milliseconds for which an email token is valid, in milliseconds + */ +export const EMAIL_TOKEN_EXPIRATION_MS = EMAIL_TOKEN_EXPIRATION_SEC * 1000; + /** * Length in bytes of the email token generated (is represented as a hex string of twice as many) */ export const EMAIL_TOKEN_LENGTH = 32; /** - * The regular expression for valid email addresses. + * The regular expression for valid email addresses, in minutes. + */ +export const EMAIL_TOKEN_RESEND_INTERVAL_MINS = 15; +/** + * The regular expression for valid email addresses, in seconds. + */ +export const EMAIL_TOKEN_RESEND_INTERVAL_SEC = + EMAIL_TOKEN_RESEND_INTERVAL_MINS * 60; +/** + * The regular expression for valid email addresses, in milliseconds. */ -export const EMAIL_TOKEN_RESEND_INTERVAL = 5 * 60 * 1000; // 5 minutes +export const EMAIL_TOKEN_RESEND_INTERVAL_MS = + EMAIL_TOKEN_RESEND_INTERVAL_SEC * 1000; const createUsernameRegex = (minLength: number, maxLength: number) => { return new RegExp( @@ -223,7 +242,7 @@ export default { BCRYPT_ROUNDS, CHILI_PER_HAND, EMAIL_FROM, - EMAIL_TOKEN_RESEND_INTERVAL, + EMAIL_TOKEN_RESEND_INTERVAL: EMAIL_TOKEN_RESEND_INTERVAL_MS, GAME_CODE_LENGTH, GAME_CODE_REGEX, JWT_ALGO, diff --git a/chili-and-cilantro-lib/src/lib/enumerations/string-names.ts b/chili-and-cilantro-lib/src/lib/enumerations/string-names.ts index 37aac7b..3cae85f 100644 --- a/chili-and-cilantro-lib/src/lib/enumerations/string-names.ts +++ b/chili-and-cilantro-lib/src/lib/enumerations/string-names.ts @@ -8,6 +8,7 @@ export enum StringNames { AccountError_Message = 'accountError_message', AccountError_Title = 'accountError_title', ChangePassword_ChangePasswordButton = 'changePassword_changePasswordButton', + ChangePassword_Success = 'changePassword_success', Common_ChangePassword = 'common_changePassword', Common_CurrentPassword = 'common_currentPassword', Common_Email = 'common_email', @@ -17,17 +18,35 @@ export enum StringNames { Common_ConfirmNewPassword = 'common_confirmNewPassword', Common_Dashboard = 'common_dashboard', Common_Loading = 'common_loading', + Common_MasterChef = 'common_masterChef', Common_Password = 'common_password', Common_ReturnToKitchen = 'common_returnToKitchen', Common_Site = 'common_site', Common_StartCooking = 'common_startCooking', Common_Tagline = 'common_tagline', + Common_TokenValid = 'common_tokenValid', Common_Unauthorized = 'common_unauthorized', Common_UnexpectedError = 'common_unexpectedError', Common_Username = 'common_username', + EmailToken_ExpiresInTemplate = 'emailToken_expiresInTemplate', + EmailToken_TitleEmailConfirm = 'emailToken_titleEmailConfirm', + EmailToken_TitleResetPassword = 'emailToken_titleResetPassword', + EmailToken_ClickLinkEmailConfirm = 'emailToken_clickLinkEmailConfirm', + EmailToken_ClickLinkResetPassword = 'emailToken_clickLinkResetPassword', + Error_AllCardsPlaced = 'error_allCardsPlaced', Error_AccountStatusIsDeleted = 'error_accountStatusIsDeleted', Error_AccountStatusIsLocked = 'error_accountStatusIsLocked', Error_AccountStatusIsPendingEmailVerification = 'error_accountStatusIsPendingEmailVerification', + Error_ChefAlreadyInGame = 'error_chefAlreadyInGame', + Error_EmailAlreadyVerified = 'error_emailAlreadyVerified', + Error_EmailInUse = 'error_emailInUse', + Error_EmailTokenAlreadyUsed = 'error_emailTokenAlreadySent', + Error_EmailTokenExpired = 'error_emailTokenExpired', + Error_EmailTokenSentTooRecentlyTemplate = 'error_emailTokenSentTooRecentlyTemplate', + Error_FailedToCreateEmailToken = 'error_failedToCreateEmailToken', + Error_SendTokenFailure = 'error_sendTokenFailure', + Error_TooManyChefs = 'error_tooManyChefs', + Error_YouAlreadyJoined = 'error_youAlreadyJoined', Dashboard_GamesCreated = 'dashboard_gamesCreated', Dashboard_GamesParticipating = 'dashboard_gamesParticipating', Dashboard_NoGames = 'dashboard_noGames', @@ -61,6 +80,9 @@ export enum StringNames { Login_UsernameOrEmailRequired = 'login_usernameOrEmailRequired', LogoutButton = 'logoutButton', RegisterButton = 'registerButton', + ResetPassword_ChangeEmailFirst = 'resetPassword_changeEmailFirst', + ResetPassword_Sent = 'resetPassword_sent', + ResetPassword_Success = 'resetPassword_success', Splash_Description = 'splash_description', Splash_HowToPlay = 'splash_howToPlay', ValidationError = 'validationError', @@ -76,4 +98,5 @@ export enum StringNames { Validation_ConfirmNewPassword = 'validation_confirmNewPassword', Validation_Required = 'validation_required', Validation_UsernameRegexErrorTemplate = 'validation_usernameRegexErrorTemplate', + TEST_TESTTEMPLATE = 'testTestTemplate', } diff --git a/chili-and-cilantro-lib/src/lib/errors/all-cards-placed.ts b/chili-and-cilantro-lib/src/lib/errors/all-cards-placed.ts index ca7446c..7ad2dcb 100644 --- a/chili-and-cilantro-lib/src/lib/errors/all-cards-placed.ts +++ b/chili-and-cilantro-lib/src/lib/errors/all-cards-placed.ts @@ -1,8 +1,11 @@ +import { StringNames } from '../enumerations/string-names'; +import { translate } from '../i18n'; import { ValidationError } from './validation-error'; export class AllCardsPlacedError extends ValidationError { constructor() { - super('All cards have been placed.'); + super(translate(StringNames.Error_AllCardsPlaced)); + this.name = 'AllCardsPlacedError'; Object.setPrototypeOf(this, AllCardsPlacedError.prototype); } } diff --git a/chili-and-cilantro-lib/src/lib/errors/already-joined-other.ts b/chili-and-cilantro-lib/src/lib/errors/already-joined-other.ts deleted file mode 100644 index c265c2d..0000000 --- a/chili-and-cilantro-lib/src/lib/errors/already-joined-other.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { ValidationError } from './validation-error'; - -export class AlreadyJoinedOtherError extends ValidationError { - constructor() { - super('Chef is already in an active game.'); - Object.setPrototypeOf(this, AlreadyJoinedOtherError.prototype); - } -} diff --git a/chili-and-cilantro-lib/src/lib/errors/already-joined.ts b/chili-and-cilantro-lib/src/lib/errors/already-joined.ts index ceb224e..77246b8 100644 --- a/chili-and-cilantro-lib/src/lib/errors/already-joined.ts +++ b/chili-and-cilantro-lib/src/lib/errors/already-joined.ts @@ -1,8 +1,11 @@ +import { StringNames } from '../enumerations/string-names'; +import { translate } from '../i18n'; import { ValidationError } from './validation-error'; export class AlreadyJoinedError extends ValidationError { constructor() { - super('You have already joined this game.'); + super(translate(StringNames.Error_YouAlreadyJoined)); + this.name = 'AlreadyJoinedError'; Object.setPrototypeOf(this, AlreadyJoinedError.prototype); } } diff --git a/chili-and-cilantro-lib/src/lib/errors/chef-already-joined.ts b/chili-and-cilantro-lib/src/lib/errors/chef-already-joined.ts new file mode 100644 index 0000000..e5a3e4b --- /dev/null +++ b/chili-and-cilantro-lib/src/lib/errors/chef-already-joined.ts @@ -0,0 +1,11 @@ +import { StringNames } from '../enumerations/string-names'; +import { translate } from '../i18n'; +import { ValidationError } from './validation-error'; + +export class ChefAlreadyJoinedError extends ValidationError { + constructor() { + super(translate(StringNames.Error_ChefAlreadyInGame)); + this.name = 'ChefAlreadyJoinedError'; + Object.setPrototypeOf(this, ChefAlreadyJoinedError.prototype); + } +} diff --git a/chili-and-cilantro-lib/src/lib/errors/email-in-use.ts b/chili-and-cilantro-lib/src/lib/errors/email-in-use.ts index a97051d..ff35152 100644 --- a/chili-and-cilantro-lib/src/lib/errors/email-in-use.ts +++ b/chili-and-cilantro-lib/src/lib/errors/email-in-use.ts @@ -1,8 +1,10 @@ +import { StringNames } from '../enumerations/string-names'; +import { translate } from '../i18n'; import { HandleableError } from './handleable-error'; export class EmailInUseError extends HandleableError { constructor() { - super('Email is already in use', { statusCode: 400 }); + super(translate(StringNames.Error_EmailInUse), { statusCode: 400 }); this.name = 'EmailInUseError'; Object.setPrototypeOf(this, EmailInUseError.prototype); } diff --git a/chili-and-cilantro-lib/src/lib/errors/email-token-expired.ts b/chili-and-cilantro-lib/src/lib/errors/email-token-expired.ts index 8728078..d4b37f5 100644 --- a/chili-and-cilantro-lib/src/lib/errors/email-token-expired.ts +++ b/chili-and-cilantro-lib/src/lib/errors/email-token-expired.ts @@ -1,8 +1,10 @@ +import { StringNames } from '../enumerations/string-names'; +import { translate } from '../i18n'; import { HandleableError } from './handleable-error'; export class EmailTokenExpiredError extends HandleableError { constructor() { - super('Verification link has expired. Please request a new one.', { + super(translate(StringNames.Error_EmailTokenExpired), { statusCode: 400, }); this.name = 'EmailTokenExpiredError'; diff --git a/chili-and-cilantro-lib/src/lib/errors/email-token-sent-too-recently.ts b/chili-and-cilantro-lib/src/lib/errors/email-token-sent-too-recently.ts index 385e5e6..c5625ce 100644 --- a/chili-and-cilantro-lib/src/lib/errors/email-token-sent-too-recently.ts +++ b/chili-and-cilantro-lib/src/lib/errors/email-token-sent-too-recently.ts @@ -1,4 +1,6 @@ import constants from '../constants'; +import { StringNames } from '../enumerations/string-names'; +import { translate } from '../i18n'; import { HandleableError } from './handleable-error'; export class EmailTokenSentTooRecentlyError extends HandleableError { @@ -16,7 +18,11 @@ export class EmailTokenSentTooRecentlyError extends HandleableError { ); super( - `Email token sent too recently. Please try again in ${timeRemaining} seconds.`, + translate( + StringNames.Error_EmailTokenSentTooRecentlyTemplate, + undefined, + { TIME_REMAINING: `${timeRemaining}` }, + ), { statusCode: 429 }, ); this.name = 'EmailTokenSentTooRecentlyError'; diff --git a/chili-and-cilantro-lib/src/lib/errors/email-token-used-or-invalid.ts b/chili-and-cilantro-lib/src/lib/errors/email-token-used-or-invalid.ts index 238683f..3545c22 100644 --- a/chili-and-cilantro-lib/src/lib/errors/email-token-used-or-invalid.ts +++ b/chili-and-cilantro-lib/src/lib/errors/email-token-used-or-invalid.ts @@ -1,8 +1,10 @@ +import { StringNames } from '../enumerations/string-names'; +import { translate } from '../i18n'; import { HandleableError } from './handleable-error'; export class EmailTokenUsedOrInvalidError extends HandleableError { constructor() { - super('Email verification link has already been used or is invalid', { + super(translate(StringNames.Error_EmailTokenAlreadyUsed), { statusCode: 400, }); this.name = 'EmailTokenUsedOrInvalidError'; diff --git a/chili-and-cilantro-lib/src/lib/errors/email-verified.ts b/chili-and-cilantro-lib/src/lib/errors/email-verified.ts index 68731f3..7f928c2 100644 --- a/chili-and-cilantro-lib/src/lib/errors/email-verified.ts +++ b/chili-and-cilantro-lib/src/lib/errors/email-verified.ts @@ -1,8 +1,12 @@ +import { StringNames } from '../enumerations/string-names'; +import { translate } from '../i18n'; import { HandleableError } from './handleable-error'; export class EmailVerifiedError extends HandleableError { constructor() { - super('Email has already been verified', { statusCode: 400 }); + super(translate(StringNames.Error_EmailAlreadyVerified), { + statusCode: 400, + }); this.name = 'EmailVerifiedError'; Object.setPrototypeOf(this, EmailVerifiedError.prototype); } diff --git a/chili-and-cilantro-lib/src/lib/errors/game-full.ts b/chili-and-cilantro-lib/src/lib/errors/game-full.ts deleted file mode 100644 index 8cddeae..0000000 --- a/chili-and-cilantro-lib/src/lib/errors/game-full.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { ValidationError } from './validation-error'; - -export class GameFullError extends ValidationError { - constructor() { - super('Game is full.'); - Object.setPrototypeOf(this, GameFullError.prototype); - } -} diff --git a/chili-and-cilantro-lib/src/lib/errors/not-host.ts b/chili-and-cilantro-lib/src/lib/errors/not-host.ts deleted file mode 100644 index 9bd3c30..0000000 --- a/chili-and-cilantro-lib/src/lib/errors/not-host.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { HandleableError } from './handleable-error'; - -export class NotHostError extends HandleableError { - constructor() { - super(`User is not the game host`, { statusCode: 422 }); - Object.setPrototypeOf(this, NotHostError.prototype); - } -} diff --git a/chili-and-cilantro-lib/src/lib/errors/not-master-chef.ts b/chili-and-cilantro-lib/src/lib/errors/not-master-chef.ts new file mode 100644 index 0000000..772aa7d --- /dev/null +++ b/chili-and-cilantro-lib/src/lib/errors/not-master-chef.ts @@ -0,0 +1,8 @@ +import { HandleableError } from './handleable-error'; + +export class NotMasterChefError extends HandleableError { + constructor() { + super(`User is not the Master Chef`, { statusCode: 422 }); + Object.setPrototypeOf(this, NotMasterChefError.prototype); + } +} diff --git a/chili-and-cilantro-lib/src/lib/errors/too-many-chefs-in-game.ts b/chili-and-cilantro-lib/src/lib/errors/too-many-chefs-in-game.ts new file mode 100644 index 0000000..09f1d23 --- /dev/null +++ b/chili-and-cilantro-lib/src/lib/errors/too-many-chefs-in-game.ts @@ -0,0 +1,9 @@ +import { ValidationError } from './validation-error'; + +export class TooManyChefsInGameError extends ValidationError { + constructor() { + super('Too many chefs in game'); + this.name = 'TooManyChefsInGameError'; + Object.setPrototypeOf(this, TooManyChefsInGameError.prototype); + } +} diff --git a/chili-and-cilantro-lib/src/lib/i18n.spec.ts b/chili-and-cilantro-lib/src/lib/i18n.spec.ts index e1b32ae..abeca26 100644 --- a/chili-and-cilantro-lib/src/lib/i18n.spec.ts +++ b/chili-and-cilantro-lib/src/lib/i18n.spec.ts @@ -259,6 +259,21 @@ describe('translate', () => { expect(replaceVariablesSpy).not.toHaveBeenCalled(); expect(result).toBe(Strings[language][StringNames.Common_Site]); }); + + it('should use otherVars in a template string', () => { + const otherVars = { + VARIABLE1: faker.lorem.word(), + VARIABLE2: faker.lorem.word(), + }; + const result = translate( + StringNames.TEST_TESTTEMPLATE, + StringLanguages.EnglishUS, + otherVars, + ); + expect(result).toBe( + `Testing ${otherVars.VARIABLE1} ${otherVars.VARIABLE2}`, + ); + }); }); describe('translateEnum', () => { @@ -422,18 +437,24 @@ describe('getLanguageCode', () => { }); describe('replaceVariables', () => { - it('should replace a single variable with its constant value', () => { - const input = - 'Password must be at least {MIN_PASSWORD_LENGTH} characters long'; - const expected = `Password must be at least ${constants.MIN_PASSWORD_LENGTH} characters long`; + it('should replace variables with values from constants', () => { + const input = 'Min: {MIN_PASSWORD_LENGTH}, Max: {MAX_PASSWORD_LENGTH}'; + const expected = `Min: ${constants.MIN_PASSWORD_LENGTH}, Max: ${constants.MAX_PASSWORD_LENGTH}`; expect(replaceVariables(input)).toBe(expected); }); - it('should replace multiple variables with their constant values', () => { - const input = - 'Username must be between {MIN_USERNAME_LENGTH} and {MAX_USERNAME_LENGTH} characters'; - const expected = `Username must be between ${constants.MIN_USERNAME_LENGTH} and ${constants.MAX_USERNAME_LENGTH} characters`; - expect(replaceVariables(input)).toBe(expected); + it('should replace variables with values from otherVars', () => { + const input = 'Hello {NAME}, welcome to {SITE}'; + const otherVars = { NAME: 'John', SITE: 'Our Website' }; + const expected = 'Hello John, welcome to Our Website'; + expect(replaceVariables(input, otherVars)).toBe(expected); + }); + + it('should prioritize otherVars over constants', () => { + const input = 'Min: {MIN_PASSWORD_LENGTH}, Name: {NAME}'; + const otherVars = { MIN_PASSWORD_LENGTH: '10', NAME: 'John' }; + const expected = 'Min: 10, Name: John'; + expect(replaceVariables(input, otherVars)).toBe(expected); }); it('should return the original string if no variables are found', () => { diff --git a/chili-and-cilantro-lib/src/lib/i18n.ts b/chili-and-cilantro-lib/src/lib/i18n.ts index 9695396..fcbc037 100644 --- a/chili-and-cilantro-lib/src/lib/i18n.ts +++ b/chili-and-cilantro-lib/src/lib/i18n.ts @@ -87,23 +87,29 @@ export const stringNameToI18nKey = (name: StringNames) => * @param str The string with variables to replace * @returns The string with variables replaced */ -export function replaceVariables(str: string): string { +export function replaceVariables( + str: string, + otherVars?: Record, +): string { const variables = str.match(/\{(.+?)\}/g); if (!variables) { return str; } return variables .map((variable) => variable.replace('{', '').replace('}', '')) - .reduce( - (acc, variable) => - acc.replace( + .reduce((acc, variable) => { + if (otherVars && variable in otherVars) { + return acc.replace(`{${variable}}`, otherVars[variable]); + } else if (variable in constants) { + return acc.replace( `{${variable}}`, - variable in constants - ? (constants as Record)[variable] - : `{${variable}}`, - ), - str, - ) + (constants as Record)[variable], + ); + } else { + // Variable not found in constants or otherVars, so return the original string + return acc; + } + }, str) .replace(/\{(.+?)\}/g, ''); } @@ -116,6 +122,7 @@ export function replaceVariables(str: string): string { export const translate = ( name: StringNames, language?: StringLanguages, + otherVars?: Record, ): string => { const lang = language ?? GlobalLanguageContext.language; if (!Strings[lang]) { @@ -127,7 +134,7 @@ export const translate = ( return name; // Fallback to the string name itself } return (name as string).toLowerCase().endsWith('template') - ? replaceVariables(Strings[lang][name]) + ? replaceVariables(Strings[lang][name], otherVars) : Strings[lang][name]; }; diff --git a/chili-and-cilantro-lib/src/lib/interfaces/models/chef.ts b/chili-and-cilantro-lib/src/lib/interfaces/models/chef.ts index f2a88a5..6adb6f9 100644 --- a/chili-and-cilantro-lib/src/lib/interfaces/models/chef.ts +++ b/chili-and-cilantro-lib/src/lib/interfaces/models/chef.ts @@ -12,5 +12,5 @@ export interface IChef extends IHasTimestamps { lostCards: CardType[]; userId: I; state: ChefState; - host: boolean; + masterChef: boolean; } diff --git a/chili-and-cilantro-lib/src/lib/interfaces/models/game.ts b/chili-and-cilantro-lib/src/lib/interfaces/models/game.ts index 3b42840..9344245 100644 --- a/chili-and-cilantro-lib/src/lib/interfaces/models/game.ts +++ b/chili-and-cilantro-lib/src/lib/interfaces/models/game.ts @@ -66,11 +66,11 @@ export interface IGame /** * The chef ID of the host chef who makes game decisions. */ - hostChefId: I; + masterChefId: I; /** * The user ID of the host chef who created the game. */ - hostUserId: I; + masterChefUserId: I; /** * The ID of the last game this is a continuation of. */ diff --git a/chili-and-cilantro-lib/src/lib/strings/english-uk.ts b/chili-and-cilantro-lib/src/lib/strings/english-uk.ts index 086f12f..5debe7d 100644 --- a/chili-and-cilantro-lib/src/lib/strings/english-uk.ts +++ b/chili-and-cilantro-lib/src/lib/strings/english-uk.ts @@ -8,6 +8,7 @@ export const BritishEnglishStrings: StringsCollection = { 'There seems to be an issue with your account. Please contact support.', [StringNames.AccountError_Title]: 'Account Error', [StringNames.ChangePassword_ChangePasswordButton]: 'Change Password', + [StringNames.ChangePassword_Success]: 'Password changed successfully', [StringNames.Common_ChangePassword]: 'Change Password', [StringNames.Common_ConfirmNewPassword]: 'Confirm New Password', [StringNames.Common_CurrentPassword]: 'Current Password', @@ -17,18 +18,42 @@ export const BritishEnglishStrings: StringsCollection = { [StringNames.Common_Dashboard]: 'Dashboard', [StringNames.Common_Loading]: 'Loading...', [StringNames.Common_Logo]: 'logo', + [StringNames.Common_MasterChef]: 'Master Chef', [StringNames.Common_Password]: 'Password', [StringNames.Common_ReturnToKitchen]: 'Return to Kitchen', [StringNames.Common_Site]: site, [StringNames.Common_StartCooking]: 'Start Cooking', [StringNames.Common_Tagline]: 'A Spicy Bluffing Game', + [StringNames.Common_TokenValid]: 'Token is valid', [StringNames.Common_Unauthorized]: 'Unauthorized', [StringNames.Common_UnexpectedError]: 'Unexpected Error', [StringNames.Common_Username]: 'Username', + [StringNames.EmailToken_ExpiresInTemplate]: + 'Link expires in {EMAIL_TOKEN_EXPIRATION_MIN} minutes.', + [StringNames.EmailToken_TitleEmailConfirm]: 'Email Confirmation', + [StringNames.EmailToken_TitleResetPassword]: 'Password Reset', + [StringNames.EmailToken_ClickLinkEmailConfirm]: + 'Please click the link below to confirm your email.', + [StringNames.EmailToken_ClickLinkResetPassword]: + 'Please click the link below to reset your password.', [StringNames.Error_AccountStatusIsDeleted]: 'Account deleted', [StringNames.Error_AccountStatusIsLocked]: 'Account locked', [StringNames.Error_AccountStatusIsPendingEmailVerification]: 'Account pending email verification', + [StringNames.Error_AllCardsPlaced]: 'All cards have been placed.', + [StringNames.Error_ChefAlreadyInGame]: 'Chef is already in an active game.', + [StringNames.Error_EmailAlreadyVerified]: 'Email has already been verified', + [StringNames.Error_EmailInUse]: 'Email is already in use', + [StringNames.Error_EmailTokenAlreadyUsed]: + 'Email verification link has already been used or is invalid', + [StringNames.Error_EmailTokenExpired]: + 'Verification link has expired. Please request a new one.', + [StringNames.Error_EmailTokenSentTooRecentlyTemplate]: + 'Email token sent too recently. Please try again in {TIME_REMAINING} seconds.', + [StringNames.Error_FailedToCreateEmailToken]: 'Failed to create email token', + [StringNames.Error_SendTokenFailure]: 'Failed to send email token', + [StringNames.Error_TooManyChefs]: 'Too many chefs in the kitchen.', + [StringNames.Error_YouAlreadyJoined]: 'You have already joined this game.', [StringNames.Dashboard_GamesCreated]: "Games You've Created", [StringNames.Dashboard_GamesParticipating]: "Games You're Participating in", [StringNames.Dashboard_NoGames]: 'No games available.', @@ -69,6 +94,11 @@ export const BritishEnglishStrings: StringsCollection = { 'Either username or email is required', [StringNames.LogoutButton]: 'Logout', [StringNames.RegisterButton]: 'Register', + [StringNames.ResetPassword_ChangeEmailFirst]: + 'Please verify your email address before resetting your password.', + [StringNames.ResetPassword_Sent]: + 'If an account with that email exists, a password reset link has been sent.', + [StringNames.ResetPassword_Success]: 'Password reset successfully', [StringNames.Splash_Description]: 'In Chili and Cilantro, aspiring chefs compete to create the perfect dish. Your goal is to add just the right amount of cilantro without ruining it with a scorching chili. Be the first to successfully season two dishes or be the last chef standing to win!', [StringNames.Splash_HowToPlay]: 'How to Play', @@ -95,6 +125,7 @@ export const BritishEnglishStrings: StringsCollection = { • Start with a letter, number, or Unicode character • Can contain letters, numbers, underscores, hyphens, and Unicode characters • Cannot contain spaces or special characters other than underscores and hyphens`, + [StringNames.TEST_TESTTEMPLATE]: 'Testing {VARIABLE1} {VARIABLE2}', }; export default BritishEnglishStrings; diff --git a/chili-and-cilantro-lib/src/lib/strings/english-us.ts b/chili-and-cilantro-lib/src/lib/strings/english-us.ts index 0732bcb..0f213d0 100644 --- a/chili-and-cilantro-lib/src/lib/strings/english-us.ts +++ b/chili-and-cilantro-lib/src/lib/strings/english-us.ts @@ -8,6 +8,7 @@ export const AmericanEnglishStrings: StringsCollection = { 'There seems to be an issue with your account. Please contact support.', [StringNames.AccountError_Title]: 'Account Error', [StringNames.ChangePassword_ChangePasswordButton]: 'Change Password', + [StringNames.ChangePassword_Success]: 'Password changed successfully', [StringNames.Common_ChangePassword]: 'Change Password', [StringNames.Common_ConfirmNewPassword]: 'Confirm New Password', [StringNames.Common_CurrentPassword]: 'Current Password', @@ -17,18 +18,42 @@ export const AmericanEnglishStrings: StringsCollection = { [StringNames.Common_Dashboard]: 'Dashboard', [StringNames.Common_Loading]: 'Loading...', [StringNames.Common_Logo]: 'logo', + [StringNames.Common_MasterChef]: 'Master Chef', [StringNames.Common_Password]: 'Password', [StringNames.Common_ReturnToKitchen]: 'Return to Kitchen', [StringNames.Common_Site]: site, [StringNames.Common_StartCooking]: 'Start Cooking', [StringNames.Common_Tagline]: 'A Spicy Bluffing Game', + [StringNames.Common_TokenValid]: 'Token is valid', [StringNames.Common_Unauthorized]: 'Unauthorized', [StringNames.Common_UnexpectedError]: 'Unexpected Error', [StringNames.Common_Username]: 'Username', + [StringNames.EmailToken_ExpiresInTemplate]: + 'Link expires in {EMAIL_TOKEN_EXPIRATION_MIN} minutes.', + [StringNames.EmailToken_TitleEmailConfirm]: 'Email Confirmation', + [StringNames.EmailToken_TitleResetPassword]: 'Password Reset', + [StringNames.EmailToken_ClickLinkEmailConfirm]: + 'Please click the link below to confirm your email.', + [StringNames.EmailToken_ClickLinkResetPassword]: + 'Please click the link below to reset your password.', [StringNames.Error_AccountStatusIsDeleted]: 'Account deleted', [StringNames.Error_AccountStatusIsLocked]: 'Account locked', [StringNames.Error_AccountStatusIsPendingEmailVerification]: 'Account pending email verification', + [StringNames.Error_AllCardsPlaced]: 'All cards placed', + [StringNames.Error_ChefAlreadyInGame]: 'Chef is already in an active game', + [StringNames.Error_EmailAlreadyVerified]: 'Email has already been verified', + [StringNames.Error_EmailInUse]: 'Email is already in use', + [StringNames.Error_EmailTokenAlreadyUsed]: + 'Email verification link has already been used or is invalid', + [StringNames.Error_EmailTokenExpired]: + 'Verification link has expired. Please request a new one.', + [StringNames.Error_EmailTokenSentTooRecentlyTemplate]: + 'Email token sent too recently. Please try again in {TIME_REMAINING} seconds.', + [StringNames.Error_FailedToCreateEmailToken]: 'Failed to create email token', + [StringNames.Error_SendTokenFailure]: 'Failed to send email token', + [StringNames.Error_TooManyChefs]: 'Too many chefs in the kitchen', + [StringNames.Error_YouAlreadyJoined]: 'You have already joined this game', [StringNames.Dashboard_GamesCreated]: "Games You've Created", [StringNames.Dashboard_GamesParticipating]: "Games You're Participating in", [StringNames.Dashboard_NoGames]: 'No games available.', @@ -69,6 +94,11 @@ export const AmericanEnglishStrings: StringsCollection = { 'Either username or email is required', [StringNames.LogoutButton]: 'Logout', [StringNames.RegisterButton]: 'Register', + [StringNames.ResetPassword_ChangeEmailFirst]: + 'Please verify your email address before resetting your password.', + [StringNames.ResetPassword_Sent]: + 'If an account with that email exists, a password reset link has been sent.', + [StringNames.ResetPassword_Success]: 'Password reset successfully', [StringNames.Splash_Description]: 'In Chili and Cilantro, aspiring chefs compete to create the perfect dish. Your goal is to add just the right amount of cilantro without ruining it with a scorching chili. Be the first to successfully season two dishes or be the last chef standing to win!', [StringNames.Splash_HowToPlay]: 'How to Play', @@ -95,6 +125,7 @@ export const AmericanEnglishStrings: StringsCollection = { • Start with a letter, number, or Unicode character • Can contain letters, numbers, underscores, hyphens, and Unicode characters • Cannot contain spaces or special characters other than underscores and hyphens`, + [StringNames.TEST_TESTTEMPLATE]: 'Testing {VARIABLE1} {VARIABLE2}', }; export default AmericanEnglishStrings; diff --git a/chili-and-cilantro-lib/src/lib/strings/french.ts b/chili-and-cilantro-lib/src/lib/strings/french.ts index fd18bba..b4b6b6a 100644 --- a/chili-and-cilantro-lib/src/lib/strings/french.ts +++ b/chili-and-cilantro-lib/src/lib/strings/french.ts @@ -8,6 +8,7 @@ export const FrenchStrings: StringsCollection = { 'Il semble y avoir un problème avec votre compte. Veuillez contacter le support.', [StringNames.AccountError_Title]: 'Erreur de compte', [StringNames.ChangePassword_ChangePasswordButton]: 'Changer le mot de passe', + [StringNames.ChangePassword_Success]: 'Mot de passe changé avec succès', [StringNames.Common_ChangePassword]: 'Changer le mot de passe', [StringNames.Common_ConfirmNewPassword]: 'Confirmer le nouveau mot de passe', [StringNames.Common_CurrentPassword]: 'Mot de passe actuel', @@ -17,18 +18,45 @@ export const FrenchStrings: StringsCollection = { [StringNames.Common_Email]: 'Courriel', [StringNames.Common_Loading]: 'Chargement...', [StringNames.Common_Logo]: 'logo', + [StringNames.Common_MasterChef]: 'Chef de cuisine', [StringNames.Common_Password]: 'Mot de passe', [StringNames.Common_ReturnToKitchen]: 'Retour à la Cuisine', [StringNames.Common_Site]: site, [StringNames.Common_StartCooking]: 'Commencer la Cuisine', [StringNames.Common_Tagline]: 'Un Jeu de Bluff Épicé', + [StringNames.Common_TokenValid]: 'Jeton valide', [StringNames.Common_Unauthorized]: 'Non autorisé', [StringNames.Common_UnexpectedError]: 'Erreur inattendue', [StringNames.Common_Username]: 'Nom d’utilisateur', + [StringNames.EmailToken_ExpiresInTemplate]: + 'Le lien expire dans {EMAIL_TOKEN_EXPIRATION_MIN} minutes.', + [StringNames.EmailToken_TitleEmailConfirm]: 'Confirmation de courriel', + [StringNames.EmailToken_TitleResetPassword]: + 'Réinitialisation du mot de passe', + [StringNames.EmailToken_ClickLinkEmailConfirm]: + 'Veuillez cliquer sur le lien ci-dessous pour confirmer votre courriel.', + [StringNames.EmailToken_ClickLinkResetPassword]: + 'Veuillez cliquer sur le lien ci-dessous pour réinitialiser votre mot de passe.', [StringNames.Error_AccountStatusIsDeleted]: 'Compte supprimé', [StringNames.Error_AccountStatusIsLocked]: 'Compte verrouillé', [StringNames.Error_AccountStatusIsPendingEmailVerification]: 'Compte en attente de confirmation de courriel', + [StringNames.Error_AllCardsPlaced]: 'Toutes les cartes ont été placées', + [StringNames.Error_ChefAlreadyInGame]: 'Chef deja dans le jeu', + [StringNames.Error_EmailAlreadyVerified]: 'Courriel déjà vérifié', + [StringNames.Error_EmailInUse]: 'Courriel déjà utilisé', + [StringNames.Error_EmailTokenAlreadyUsed]: + 'Le lien de vérification par courriel a déjà été utilisé ou est invalide', + [StringNames.Error_EmailTokenExpired]: + 'Le lien de vérification a expiré. Veuillez en demander un nouveau.', + [StringNames.Error_EmailTokenSentTooRecentlyTemplate]: + 'Le jeton de courrier électronique a été envoyé trop récemment. Veuillez réessayer dans {TIME_REMAINING} secondes.', + [StringNames.Error_FailedToCreateEmailToken]: + 'Échec de la création du jeton par courriel', + [StringNames.Error_SendTokenFailure]: + 'Échec de l’envoi du jeton par courriel', + [StringNames.Error_TooManyChefs]: 'Trop de chefs dans la cuisine', + [StringNames.Error_YouAlreadyJoined]: 'Vous avez déjà rejoint ce jeu', [StringNames.Dashboard_GamesCreated]: 'Jeux que vous avez créé', [StringNames.Dashboard_GamesParticipating]: 'Jeux que vous participez', [StringNames.Dashboard_NoGames]: 'Aucun jeu disponible.', @@ -73,6 +101,12 @@ export const FrenchStrings: StringsCollection = { 'Nom d’utilisateur ou courriel requis', [StringNames.LogoutButton]: 'Déconnexion', [StringNames.RegisterButton]: "S'inscrire", + [StringNames.ResetPassword_ChangeEmailFirst]: + 'Vous devez changer votre courriel avant de pouvoir changer votre mot de passe.', + [StringNames.ResetPassword_Sent]: + 'Si un compte avec cet courriel existe, un lien de réinitialisation du mot de passe a été envoyé.', + [StringNames.ResetPassword_Success]: + 'Réinitialisation du mot de passe réussie', [StringNames.Splash_Description]: "Dans Chili and Cilantro, les chefs en herbe rivalisent pour créer le plat parfait. Votre objectif est d'ajouter juste la bonne quantité de coriandre sans gâcher le tout avec un piment brûlant. Soyez le premier à réussir à assaisonner deux plats ou soyez le dernier chef debout pour gagner !", [StringNames.Splash_HowToPlay]: 'Comment Jouer', @@ -102,6 +136,7 @@ export const FrenchStrings: StringsCollection = { • Commencer par une lettre, un chiffre, ou un caractère Unicode • Peut contenir des lettres, des chiffres, des underscores, des tirets, et des caractères Unicode • Ne peut contenir des espaces ou des caractères spéciaux autre que des underscores et des tirets`, + [StringNames.TEST_TESTTEMPLATE]: 'Essai {VARIABLE1} {VARIABLE2}', }; export default FrenchStrings; diff --git a/chili-and-cilantro-lib/src/lib/strings/mandarin.ts b/chili-and-cilantro-lib/src/lib/strings/mandarin.ts index 128ce3d..6a6e7ac 100644 --- a/chili-and-cilantro-lib/src/lib/strings/mandarin.ts +++ b/chili-and-cilantro-lib/src/lib/strings/mandarin.ts @@ -7,6 +7,7 @@ export const MandarinStrings: StringsCollection = { [StringNames.AccountError_Message]: '您的帐户似乎有问题。请联系支持。', [StringNames.AccountError_Title]: '帐户错误', [StringNames.ChangePassword_ChangePasswordButton]: '更改密码', + [StringNames.ChangePassword_Success]: '密码更改成功', [StringNames.Common_ChangePassword]: '更改密码', [StringNames.Common_ConfirmNewPassword]: '确认新密码', [StringNames.Common_CurrentPassword]: '当前密码', @@ -16,18 +17,39 @@ export const MandarinStrings: StringsCollection = { [StringNames.Common_Dashboard]: '仪表板', [StringNames.Common_Loading]: '加载中...', [StringNames.Common_Logo]: '标志', + [StringNames.Common_MasterChef]: '大厨', [StringNames.Common_Password]: '密码', [StringNames.Common_ReturnToKitchen]: '返回厨房', [StringNames.Common_Site]: site, [StringNames.Common_StartCooking]: '开始烹饪', [StringNames.Common_Tagline]: '一款辛辣的虚张声势游戏', + [StringNames.Common_TokenValid]: '令牌有效', [StringNames.Common_Unauthorized]: '未经授权', [StringNames.Common_UnexpectedError]: '意外错误', [StringNames.Common_Username]: '用户名', + [StringNames.EmailToken_ExpiresInTemplate]: + '链接在{EMAIL_TOKEN_EXPIRATION_MIN}分钟后过期。', + [StringNames.EmailToken_TitleEmailConfirm]: '电子邮件确认', + [StringNames.EmailToken_TitleResetPassword]: '密码重置', + [StringNames.EmailToken_ClickLinkEmailConfirm]: + '请单击下面的链接确认您的电子邮件。', + [StringNames.EmailToken_ClickLinkResetPassword]: '请单击下面的链接重置密码。', [StringNames.Error_AccountStatusIsDeleted]: '帐户已删除', [StringNames.Error_AccountStatusIsLocked]: '帐户已锁定', [StringNames.Error_AccountStatusIsPendingEmailVerification]: '帐户待电子邮件验证', + [StringNames.Error_AllCardsPlaced]: '所有卡片已放置', + [StringNames.Error_ChefAlreadyInGame]: '厨师已经在游戏中', + [StringNames.Error_EmailAlreadyVerified]: '电子邮件已验证', + [StringNames.Error_EmailInUse]: '电子邮件已在使用中', + [StringNames.Error_EmailTokenAlreadyUsed]: '电子邮件验证链接已被使用或无效', + [StringNames.Error_EmailTokenExpired]: '验证链接已过期。请请求一个新的。', + [StringNames.Error_EmailTokenSentTooRecentlyTemplate]: + '电子邮件令牌发送太频繁。请在{TIME_REMAINING}秒后重试。', + [StringNames.Error_FailedToCreateEmailToken]: '无法创建电子邮件令牌', + [StringNames.Error_SendTokenFailure]: '无法发送电子邮件令牌', + [StringNames.Error_TooManyChefs]: '厨房里有太多厨师', + [StringNames.Error_YouAlreadyJoined]: '您已经加入该游戏', [StringNames.Dashboard_GamesCreated]: '您创建的游戏', [StringNames.Dashboard_GamesParticipating]: '您参与的游戏', [StringNames.Dashboard_NoGames]: '没有可用的游戏。', @@ -63,6 +85,11 @@ export const MandarinStrings: StringsCollection = { [StringNames.Login_UsernameOrEmailRequired]: '需要用户名或电子邮件', [StringNames.LogoutButton]: '注销', [StringNames.RegisterButton]: '注册', + [StringNames.ResetPassword_ChangeEmailFirst]: + '在重置密码之前,请验证您的电子邮件地址。', + [StringNames.ResetPassword_Sent]: + '如果有帐户与此电子邮件相关,密码重置链接已发送。', + [StringNames.ResetPassword_Success]: '密码重置成功', [StringNames.Splash_Description]: '在《辣椒和香菜》中,有抱负的厨师们竞相制作完美的菜肴。你的目标是添加适量的香菜,而不会让辣椒烧焦。成为第一个成功调味两道菜的人,或者成为最后一个获胜的厨师', [StringNames.Splash_HowToPlay]: '如何玩', @@ -86,6 +113,7 @@ export const MandarinStrings: StringsCollection = { • 以字母,数字,或Unicode字符开头 • 可以包含字母,数字,下划线,短横线,或Unicode字符 • 不能包含空格或特殊字符,除下划线和短横线外`, + [StringNames.TEST_TESTTEMPLATE]: '测试 {VARIABLE1} {VARIABLE2}', }; export default MandarinStrings; diff --git a/chili-and-cilantro-lib/src/lib/strings/spanish.ts b/chili-and-cilantro-lib/src/lib/strings/spanish.ts index 02fafde..266222c 100644 --- a/chili-and-cilantro-lib/src/lib/strings/spanish.ts +++ b/chili-and-cilantro-lib/src/lib/strings/spanish.ts @@ -8,6 +8,7 @@ export const SpanishStrings: StringsCollection = { 'Parece que hay un problema con tu cuenta. Por favor, contacta con soporte.', [StringNames.AccountError_Title]: 'Error de cuenta', [StringNames.ChangePassword_ChangePasswordButton]: 'Cambiar contraseña', + [StringNames.ChangePassword_Success]: 'Contraseña cambiada con éxito', [StringNames.Common_ChangePassword]: 'Cambiar contraseña', [StringNames.Common_ConfirmNewPassword]: 'Confirmar nueva contraseña', [StringNames.Common_CurrentPassword]: 'Contraseña actual', @@ -17,18 +18,45 @@ export const SpanishStrings: StringsCollection = { [StringNames.Common_Dashboard]: 'Tablero', [StringNames.Common_Loading]: 'Cargando...', [StringNames.Common_Logo]: 'logo', + [StringNames.Common_MasterChef]: 'Chef Maestro', [StringNames.Common_Password]: 'Contraseña', [StringNames.Common_ReturnToKitchen]: 'Volver a la Cocina', [StringNames.Common_Site]: site, [StringNames.Common_StartCooking]: 'Comenzar a cocinar', [StringNames.Common_Tagline]: 'Un Juego de Bluff Picante', + [StringNames.Common_TokenValid]: 'Token válido', [StringNames.Common_Unauthorized]: 'No autorizado', [StringNames.Common_UnexpectedError]: 'Error inesperado', [StringNames.Common_Username]: 'Nombre de usuario', + [StringNames.EmailToken_ExpiresInTemplate]: + 'El enlace caducará en {EMAIL_TOKEN_EXPIRATION_MIN} minutos.', + [StringNames.EmailToken_TitleEmailConfirm]: + 'Confirmación de correo electrónico', + [StringNames.EmailToken_TitleResetPassword]: 'Restablecimiento de contraseña', + [StringNames.EmailToken_ClickLinkEmailConfirm]: + 'Por favor, haz clic en el enlace de abajo para confirmar tu correo electrónica.', + [StringNames.EmailToken_ClickLinkResetPassword]: + 'Por favor, haz clic en el enlace de abajo para restablecer tu contraseña.', [StringNames.Error_AccountStatusIsDeleted]: 'Cuenta eliminada', [StringNames.Error_AccountStatusIsLocked]: 'Cuenta bloqueada', [StringNames.Error_AccountStatusIsPendingEmailVerification]: 'Cuenta en espera de verificación de correo electrónico', + [StringNames.Error_AllCardsPlaced]: 'Todas las cartas han sido colocadas.', + [StringNames.Error_ChefAlreadyInGame]: 'El chef ya está en un juego activo.', + [StringNames.Error_EmailAlreadyVerified]: + 'El correo electrónico ya ha sido verificado', + [StringNames.Error_EmailInUse]: 'El correo electrónica ya esta en uso.', + [StringNames.Error_EmailTokenAlreadyUsed]: + 'El enlace de verificación de correo electrónica ya ha sido usado o es inválido.', + [StringNames.Error_EmailTokenExpired]: + 'El enlace de verificación ha caducado. Por favor, solicita uno nuevo.', + [StringNames.Error_EmailTokenSentTooRecentlyTemplate]: + 'El token de correo electrónico se envió hace muy poco. Inténtalo de nuevo en {TIME_REMAINING} segundos.', + [StringNames.Error_FailedToCreateEmailToken]: + 'Fallo al crear el token de correo', + [StringNames.Error_SendTokenFailure]: 'Fallo al enviar el token de correo', + [StringNames.Error_TooManyChefs]: 'Demasiados chefs en la cocina', + [StringNames.Error_YouAlreadyJoined]: 'Ya te has unido a este juego', [StringNames.Dashboard_GamesCreated]: 'Juegos que has creado', [StringNames.Dashboard_GamesParticipating]: 'Juegos en los que participas', [StringNames.Dashboard_NoGames]: 'No hay juegos disponibles.', @@ -69,6 +97,11 @@ export const SpanishStrings: StringsCollection = { 'Se requiere un nombre de usuario o correo electrónica', [StringNames.LogoutButton]: 'Cerrar sesión', [StringNames.RegisterButton]: 'Registrarse', + [StringNames.ResetPassword_ChangeEmailFirst]: + 'Antes de restablecer la contraseña, verifica tu dirección de correo electrónico.', + [StringNames.ResetPassword_Sent]: + 'Si existe una cuenta con ese correo electrónico, se ha enviado un enlace para restablecer la contraseña.', + [StringNames.ResetPassword_Success]: 'Contraseña restablecida con éxito', [StringNames.Splash_Description]: 'En Chili and Cilantro, los aspirantes a chef compiten para crear el plato perfecto. Tu objetivo es agregar la cantidad justa de cilantro sin arruinarlo con un chile abrasador. ¡Sé el primero en condimentar con éxito dos platos o sé el último chef en pie para ganar!', [StringNames.Splash_HowToPlay]: 'Como Jugar', @@ -97,6 +130,7 @@ export const SpanishStrings: StringsCollection = { • Comenzar con una letra, un número, o un carácter Unicode • Puede contener letras, números, guiones bajos, guiones, y carácteres Unicode • No puede contener espacios o carácteres especiales excepto guiones bajos y guiones`, + [StringNames.TEST_TESTTEMPLATE]: 'Probando {VARIABLE1} {VARIABLE2}', }; export default SpanishStrings; diff --git a/chili-and-cilantro-lib/src/lib/strings/ukrainian.ts b/chili-and-cilantro-lib/src/lib/strings/ukrainian.ts index 0ba887f..ee7dee7 100644 --- a/chili-and-cilantro-lib/src/lib/strings/ukrainian.ts +++ b/chili-and-cilantro-lib/src/lib/strings/ukrainian.ts @@ -8,6 +8,7 @@ export const UkrainianStrings: StringsCollection = { 'Здається, у вас проблеми з обліковим записом. Будь ласка, зв’яжіться з підтримкою.', [StringNames.AccountError_Title]: 'Помилка облікового запису', [StringNames.ChangePassword_ChangePasswordButton]: 'Змінити пароль', + [StringNames.ChangePassword_Success]: 'Пароль успішно змінено', [StringNames.Common_ChangePassword]: 'Змінити пароль', [StringNames.Common_ConfirmNewPassword]: 'Підтвердити новий пароль', [StringNames.Common_CurrentPassword]: 'Поточний пароль', @@ -17,18 +18,44 @@ export const UkrainianStrings: StringsCollection = { [StringNames.Common_Dashboard]: 'Панель', [StringNames.Common_Loading]: 'Завантаження...', [StringNames.Common_Logo]: 'лого', + [StringNames.Common_MasterChef]: 'Майстер-кухар', [StringNames.Common_Password]: 'Пароль', [StringNames.Common_ReturnToKitchen]: 'Повернутися на Кухню', [StringNames.Common_Site]: site, [StringNames.Common_StartCooking]: 'Почати приготування', [StringNames.Common_Tagline]: 'Пікантний Блеф', + [StringNames.Common_TokenValid]: 'Токен діїдженний', [StringNames.Common_Unauthorized]: 'Немає авторизації', [StringNames.Common_UnexpectedError]: 'Неочікувана помилка', [StringNames.Common_Username]: 'Ім’я користувача', + [StringNames.EmailToken_ExpiresInTemplate]: + 'Посилання дійсне протягом {EMAIL_TOKEN_EXPIRATION_MIN} хвилин.', + [StringNames.EmailToken_TitleEmailConfirm]: 'Підтвердження електронної пошти', + [StringNames.EmailToken_TitleResetPassword]: 'Скидання пароля', + [StringNames.EmailToken_ClickLinkEmailConfirm]: + 'Натисніть на посилання для підтвердження електронної пошти', + [StringNames.EmailToken_ClickLinkResetPassword]: + 'Натисніть на посилання для скидання пароля', [StringNames.Error_AccountStatusIsDeleted]: 'Обліковий запис видалено', [StringNames.Error_AccountStatusIsLocked]: 'Обліковий запис заблоковано', [StringNames.Error_AccountStatusIsPendingEmailVerification]: 'Обліковий запис очікує підтвердження електронної пошти', + [StringNames.Error_AllCardsPlaced]: 'Всі картки розміщені', + [StringNames.Error_ChefAlreadyInGame]: 'Шеф-кухар вже в грі', + [StringNames.Error_EmailAlreadyVerified]: 'Електронна пошта вже підтверджена', + [StringNames.Error_EmailInUse]: 'Електронна пошта вже використовується', + [StringNames.Error_EmailTokenAlreadyUsed]: + 'Посилання для підтвердження електронної пошти вже використано або недійсне', + [StringNames.Error_EmailTokenExpired]: + 'Термін дії посилання закінчився. Будь ласка, запросіть новий.', + [StringNames.Error_EmailTokenSentTooRecentlyTemplate]: + 'Маркер електронної пошти надіслано занадто недавно. Повторіть спробу через {TIME_REMAINING} с.', + [StringNames.Error_FailedToCreateEmailToken]: + 'Неможливо створити електронний токен', + [StringNames.Error_SendTokenFailure]: + 'Неможливо відправити електронний токен', + [StringNames.Error_TooManyChefs]: 'Забагато шеф-кухарів на кухні', + [StringNames.Error_YouAlreadyJoined]: 'Ви вже приєдналися до цієї гри', [StringNames.Dashboard_GamesCreated]: 'Гри, які ти створив', [StringNames.Dashboard_GamesParticipating]: 'Гри, в яких ти береш участь', [StringNames.Dashboard_NoGames]: 'Немає доступних ігор.', @@ -71,6 +98,11 @@ export const UkrainianStrings: StringsCollection = { 'Необхідно вказати ім’я користувача або електронну пошту', [StringNames.LogoutButton]: 'Вийти', [StringNames.RegisterButton]: 'Зареєструватися', + [StringNames.ResetPassword_ChangeEmailFirst]: + 'Будь ласка, підтвердіть вашу електронну адресу перед скиданням пароля.', + [StringNames.ResetPassword_Sent]: + 'Якщо обліковий запис із такою електронною адресою існує, було надіслано посилання для зміни пароля.', + [StringNames.ResetPassword_Success]: 'Пароль успішно скинуто', [StringNames.Splash_Description]: 'У Chili and Cilantro починаючі кухарі змагаються, щоб створити ідеальну страву. Ваша мета — додати потрібну кількість кінзи, не зіпсувавши її пекучим чилі. Будьте першим, хто успішно приправить дві страви, або будьте останнім шеф-кухарем, який виграє!', [StringNames.Splash_HowToPlay]: 'Як Грати', @@ -98,6 +130,7 @@ export const UkrainianStrings: StringsCollection = { • Починатися з літери, числа, або Unicode-символу • Містити літери, числа, підкреслення, тире, та Unicode-символи • Не містити пробілів або спеціальних символів, крім підкреслення і тире`, + [StringNames.TEST_TESTTEMPLATE]: 'Тестування {VARIABLE1} {VARIABLE2}', }; export default UkrainianStrings; diff --git a/chili-and-cilantro-node-lib/src/lib/schemas/chef.ts b/chili-and-cilantro-node-lib/src/lib/schemas/chef.ts index 517f947..6cb1e35 100644 --- a/chili-and-cilantro-node-lib/src/lib/schemas/chef.ts +++ b/chili-and-cilantro-node-lib/src/lib/schemas/chef.ts @@ -55,7 +55,7 @@ export const ChefSchema = new Schema( ref: ModelName.User, }, state: { type: String, enum: Object.values(ChefState), required: true }, - host: { type: Boolean, required: true, default: false }, + masterChef: { type: Boolean, required: true, default: false }, }, { timestamps: true }, ); diff --git a/chili-and-cilantro-node-lib/src/lib/schemas/game.ts b/chili-and-cilantro-node-lib/src/lib/schemas/game.ts index 809a3e0..a292a7f 100644 --- a/chili-and-cilantro-node-lib/src/lib/schemas/game.ts +++ b/chili-and-cilantro-node-lib/src/lib/schemas/game.ts @@ -149,12 +149,12 @@ export const GameSchema = new Schema( required: true, }, ], - hostChefId: { + masterChefId: { type: Schema.Types.ObjectId, ref: ModelName.Chef, required: true, }, - hostUserId: { + masterChefUserId: { type: Schema.Types.ObjectId, ref: ModelName.User, required: true, diff --git a/chili-and-cilantro-react/src/app/auth-provider.tsx b/chili-and-cilantro-react/src/app/auth-provider.tsx index e3a8f9a..f28e57e 100644 --- a/chili-and-cilantro-react/src/app/auth-provider.tsx +++ b/chili-and-cilantro-react/src/app/auth-provider.tsx @@ -4,6 +4,8 @@ import { ISuccessMessage, LanguageCodes, StringLanguages, + StringNames, + translate, } from '@chili-and-cilantro/chili-and-cilantro-lib'; import { isAxiosError } from 'axios'; import { @@ -40,6 +42,13 @@ export interface AuthContextData { checkAuth: () => void; authState: number; language: StringLanguages; + register: ( + username: string, + displayname: string, + email: string, + password: string, + timezone: string, + ) => Promise; setUser: (user: IRequestUser | null) => void; setLanguage: (lang: StringLanguages) => void; token: string | null; @@ -98,8 +107,9 @@ export const AuthProvider = ({ children }: AuthProviderProps) => { setUser(userData); setIsAuthenticated(true); setToken(token); + setError(null); + setErrorType(null); } catch (error) { - console.error('Token verification failed:', error); setUser(null); setIsAuthenticated(false); localStorage.removeItem('authToken'); @@ -108,6 +118,27 @@ export const AuthProvider = ({ children }: AuthProviderProps) => { } }, []); + const register = useCallback( + async ( + username: string, + displayname: string, + email: string, + password: string, + timezone: string, + ) => { + await authService.register( + username, + displayname, + email, + password, + timezone, + ); + setError(null); + setErrorType(null); + }, + [], + ); + useEffect(() => { const token = localStorage.getItem('authToken'); if (token) { @@ -156,16 +187,30 @@ export const AuthProvider = ({ children }: AuthProviderProps) => { setToken(null); setIsAuthenticated(false); setError(null); + setErrorType(null); setAuthState((prev) => prev + 1); navigate('/'); }, [navigate]); const verifyToken = useCallback(async (token: string) => { try { - await authService.verifyToken(token); + const user = await authService.verifyToken(token); + setUser(user); + setIsAuthenticated(true); + setError(null); + setErrorType(null); } catch (error) { - console.error('Token verification failed:', error); - setError('Invalid token'); + if (isAxiosError(error) || error instanceof Error) { + setError(error.message); + if (isAxiosError(error) && error.response?.data.errorType) { + setErrorType(error.response.data.errorType); + } + } else { + setError(translate(StringNames.Common_UnexpectedError)); + setErrorType(null); + } + setIsAuthenticated(false); + setUser(null); } }, []); @@ -175,22 +220,32 @@ export const AuthProvider = ({ children }: AuthProviderProps) => { newPassword: string, ): Promise => { try { - await authService.changePassword(currentPassword, newPassword); + const response = await authService.changePassword( + currentPassword, + newPassword, + ); // Handle success (e.g., show a message) - return { success: true, message: 'Password changed successfully' }; + setError(null); + setErrorType(null); + return { + success: true, + message: response, + }; } catch (error) { // Handle error (e.g., set error state) if (isAxiosError(error)) { + setError(error.response?.data?.message ?? error.message); + if (error.response?.data.errorType) { + setErrorType(error.response.data.errorType); + } throw new Error( error.response?.data?.message || - 'An error occurred while changing the password', + translate(StringNames.Common_UnexpectedError), ); } else if (error instanceof Error) { throw error; } else { - throw new Error( - 'An unexpected error occurred while changing the password', - ); + throw new Error(translate(StringNames.Common_UnexpectedError)); } } }, @@ -212,18 +267,34 @@ export const AuthProvider = ({ children }: AuthProviderProps) => { if (user) { try { // Make API call to update user language - api.post('/user/language', { language: newLanguage }).then(() => { - console.log('User language updated'); - }); - setUser({ ...user, siteLanguage: newLanguage }); + api + .post('/user/language', { language: newLanguage }) + .then((response) => { + const user = response.data as IRequestUser; + setUser(user); + setError(null); + setErrorType(null); + }); } catch (error) { - console.error('Failed to update user language:', error); + if (isAxiosError(error)) { + setError(error.response?.data?.message ?? error.message); + if (error.response?.data.errorType) { + setErrorType(error.response.data.errorType); + } + } else if (error instanceof Error) { + setError(error.message); + setErrorType(null); + } else { + setError(translate(StringNames.Common_UnexpectedError)); + setErrorType(null); + } } } }; return { user, + setUser: setUserAndLanguage, isAuthenticated, loading, error, @@ -234,14 +305,15 @@ export const AuthProvider = ({ children }: AuthProviderProps) => { verifyToken, checkAuth, authState, - setUser: setUserAndLanguage, - language, - setLanguage: setLanguageAndUpdateUser, token, setToken, + language, + setLanguage: setLanguageAndUpdateUser, + register, }; }, [ user, + setUser, isAuthenticated, loading, error, @@ -252,9 +324,11 @@ export const AuthProvider = ({ children }: AuthProviderProps) => { verifyToken, checkAuth, authState, - language, token, setToken, + language, + setLanguage, + register, ]); return ( diff --git a/chili-and-cilantro-react/src/app/components/login-page.tsx b/chili-and-cilantro-react/src/app/components/login-page.tsx index 9f57e2e..0378d2d 100644 --- a/chili-and-cilantro-react/src/app/components/login-page.tsx +++ b/chili-and-cilantro-react/src/app/components/login-page.tsx @@ -67,7 +67,6 @@ const LoginPage = () => { loginType === 'email', ); if ('error' in loginResult) { - console.error(loginResult); setLoginError(loginResult.error); return; } diff --git a/chili-and-cilantro-react/src/app/components/register-page.tsx b/chili-and-cilantro-react/src/app/components/register-page.tsx index 448e79f..c1a0c2e 100644 --- a/chili-and-cilantro-react/src/app/components/register-page.tsx +++ b/chili-and-cilantro-react/src/app/components/register-page.tsx @@ -15,8 +15,8 @@ import { useFormik } from 'formik'; import React, { useState } from 'react'; import { Link, useNavigate } from 'react-router-dom'; import * as Yup from 'yup'; +import { useAuth } from '../auth-provider'; import { useAppTranslation } from '../i18n-provider'; -import authService from '../services/auth-service'; import MultilineHelperText from './multi-line-helper-text'; interface FormValues { @@ -33,6 +33,7 @@ const RegisterPage: React.FC = () => { const [registrationSuccess, setRegistrationSuccess] = useState(false); const navigate = useNavigate(); const { t } = useAppTranslation(); + const { register } = useAuth(); const formik = useFormik({ initialValues: { @@ -67,7 +68,7 @@ const RegisterPage: React.FC = () => { onSubmit: async (values, { setSubmitting }) => { try { const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone; - await authService.register( + await register( values.username, values.displayname, values.email, diff --git a/chili-and-cilantro-react/src/app/services/auth-service.ts b/chili-and-cilantro-react/src/app/services/auth-service.ts index c40d707..aa696de 100644 --- a/chili-and-cilantro-react/src/app/services/auth-service.ts +++ b/chili-and-cilantro-react/src/app/services/auth-service.ts @@ -48,7 +48,7 @@ const register = async ( email: string, password: string, timezone: string, -) => { +): Promise => { const response = await api.post('/user/register', { username, displayname, @@ -56,28 +56,31 @@ const register = async ( password, timezone, }); - if (response.status !== 200) { + if (response.status !== 201) { throw new Error( response.data.error?.message ?? response.data.message ?? translate(StringNames.Common_UnexpectedError), ); } - return response.data; }; -const changePassword = async (currentPassword: string, newPassword: string) => { +const changePassword = async ( + currentPassword: string, + newPassword: string, +): Promise => { // if we get a 200 response, the password was changed successfully // else, we throw an error with the response message const response = await authenticatedApi.post('/user/change-password', { currentPassword, newPassword, }); - if (response.status === 200) { - return response.data; - } else { - throw new Error(response.data.message || 'An unexpected error occurred'); + if (response.status !== 200) { + throw new Error( + response.data.message || translate(StringNames.Common_UnexpectedError), + ); } + return response.data.message ?? translate(StringNames.ChangePassword_Success); }; const logout = () => { localStorage.removeItem('user'); diff --git a/chili-and-cilantro-react/src/app/user-context.tsx b/chili-and-cilantro-react/src/app/user-context.tsx index ec76114..2ba7971 100644 --- a/chili-and-cilantro-react/src/app/user-context.tsx +++ b/chili-and-cilantro-react/src/app/user-context.tsx @@ -48,6 +48,7 @@ export const UserProvider: FC<{ children: ReactNode }> = ({ children }) => { await i18n.changeLanguage(LanguageCodes[language]); localStorage.setItem('language', language); localStorage.setItem('languageCode', LanguageCodes[language]); + GlobalLanguageContext.language = language; })(); }, [language]); diff --git a/docs/Developers.md b/docs/Developers.md index 9548bc3..57ad682 100644 --- a/docs/Developers.md +++ b/docs/Developers.md @@ -24,9 +24,9 @@ The interfaces are in the library so that we can reference the model interfaces # Rules/flow for Chili and Cilantro -- A user creates a game. This user becomes the Host. They must start the game and make other decisions. This is the LOBBY phase. +- A user creates a game. This user becomes the Master Chef. They must start the game and make other decisions. This is the LOBBY phase. - Multiple users join the game- until MAX_CHEFS is reached- this number is 8 but it could easily be increased. -- The Host at some point starts the game. The chef IDs up to that point are shuffled and a turn order is produced randomly. This is the SETUP phase. Starting the game is equivalent to starting the first round. +- The Master Chef at some point starts the game. The chef IDs up to that point are shuffled and a turn order is produced randomly. This is the SETUP phase. Starting the game is equivalent to starting the first round. - Chefs take turns either placing a card or bidding. The minimum bid is 1, so the first Chef must place a card before anyone can consider bidding. The maximum bid is the total number of cards placed. - Cards may be placed until everyone is out of cards or someone has bid. - Once everyone is out of ingredient cards, the next player must bid.