티스토리 뷰

Web/ASP.NET Core

ASP.NET Core Identity 에러 지역화

풍요로운 해구름 2019. 4. 8. 23:59

.NET Core 인증을 구현했을 때 에러메시지가 다음과 같이 영어로 나타날 때 한글화 하는 방법을 소개합니다.

  • Passwords must have at least one non alphanumeric character.
  • Passwords must have at least one lowercase ('a'-'z').
  • Passwords must have at least one uppercase ('A'-'Z').

방법1: IdentityErrorDescriber 재정의하기

직접 원하는 오류 메시지를 작성하고 싶다면 IdentityErrorDescriber Class를 Override해야 합니다.

  1. IdentityErrorDescriber를 상속하여 원하는 에러메시지로 Override합니다. 아래는 영문으로 작성된 코드이므로 적절히 한글화 하여 프로젝트 추가해주세요.
    영문 IdentityErrorDescriber
    public class CustomIdentityErrorDescriber : IdentityErrorDescriber
    {
        public override IdentityError DefaultError() { return new IdentityError { Code = nameof(DefaultError), Description = $"An unknown failure has occurred." }; }
        public override IdentityError ConcurrencyFailure() { return new IdentityError { Code = nameof(ConcurrencyFailure), Description = "Optimistic concurrency failure, object has been modified." }; }
        public override IdentityError PasswordMismatch() { return new IdentityError { Code = nameof(PasswordMismatch), Description = "Incorrect password." }; }
        public override IdentityError InvalidToken() { return new IdentityError { Code = nameof(InvalidToken), Description = "Invalid token." }; }
        public override IdentityError LoginAlreadyAssociated() { return new IdentityError { Code = nameof(LoginAlreadyAssociated), Description = "A user with this login already exists." }; }
        public override IdentityError InvalidUserName(string userName) { return new IdentityError { Code = nameof(InvalidUserName), Description = $"User name '{userName}' is invalid, can only contain letters or digits." }; }
        public override IdentityError InvalidEmail(string email) { return new IdentityError { Code = nameof(InvalidEmail), Description = $"Email '{email}' is invalid."  }; }
        public override IdentityError DuplicateUserName(string userName) { return new IdentityError { Code = nameof(DuplicateUserName), Description = $"User Name '{userName}' is already taken."  }; }
        public override IdentityError DuplicateEmail(string email) { return new IdentityError { Code = nameof(DuplicateEmail), Description = $"Email '{email}' is already taken."  }; }
        public override IdentityError InvalidRoleName(string role) { return new IdentityError { Code = nameof(InvalidRoleName), Description = $"Role name '{role}' is invalid."  }; }
        public override IdentityError DuplicateRoleName(string role) { return new IdentityError { Code = nameof(DuplicateRoleName), Description = $"Role name '{role}' is already taken."  }; }
        public override IdentityError UserAlreadyHasPassword() { return new IdentityError { Code = nameof(UserAlreadyHasPassword), Description = "User already has a password set." }; }
        public override IdentityError UserLockoutNotEnabled() { return new IdentityError { Code = nameof(UserLockoutNotEnabled), Description = "Lockout is not enabled for this user." }; }
        public override IdentityError UserAlreadyInRole(string role) { return new IdentityError { Code = nameof(UserAlreadyInRole), Description = $"User already in role '{role}'."  }; }
        public override IdentityError UserNotInRole(string role) { return new IdentityError { Code = nameof(UserNotInRole), Description = $"User is not in role '{role}'."  }; }
        public override IdentityError PasswordTooShort(int length) { return new IdentityError { Code = nameof(PasswordTooShort), Description = $"Passwords must be at least {length} characters."  }; }
        public override IdentityError PasswordRequiresNonAlphanumeric() { return new IdentityError { Code = nameof(PasswordRequiresNonAlphanumeric), Description = "Passwords must have at least one non alphanumeric character." }; }
        public override IdentityError PasswordRequiresDigit() { return new IdentityError { Code = nameof(PasswordRequiresDigit), Description = "Passwords must have at least one digit ('0'-'9')." }; }
        public override IdentityError PasswordRequiresLower() { return new IdentityError { Code = nameof(PasswordRequiresLower), Description = "Passwords must have at least one lowercase ('a'-'z')." }; }
        public override IdentityError PasswordRequiresUpper() { return new IdentityError { Code = nameof(PasswordRequiresUpper), Description = "Passwords must have at least one uppercase ('A'-'Z')." }; }
        public override IdentityError RecoveryCodeRedemptionFailed() { return new IdentityError { Code = nameof(RecoveryCodeRedemptionFailed), Description = "Recovery code was not redeemed." }; }
        public override IdentityError PasswordRequiresUniqueChars(int uniqueChars) { return new IdentityError { Code = nameof(PasswordRequiresUniqueChars), Description = "Passwords must use at least {0} different characters." }; }
    }
    
    한글화 후 IdentityErrorDescriber 예시
    public class IdentityErrorDescriberKr : IdentityErrorDescriber
    {
        public override IdentityError DefaultError() { return new IdentityError { Code = nameof(DefaultError), Description = $"알 수 없는 오류가 발생했습니다." }; }
        public override IdentityError ConcurrencyFailure() { return new IdentityError { Code = nameof(ConcurrencyFailure), Description = "동시성 문제가 발생했습니다. 데이터가 다른 곳에서 수정된 것 같습니다." }; }
        public override IdentityError PasswordMismatch() { return new IdentityError { Code = nameof(PasswordMismatch), Description = "잘못된 비밀번호입니다." }; }
        public override IdentityError InvalidToken() { return new IdentityError { Code = nameof(InvalidToken), Description = "유효하지 않은 토큰입니다." }; }
        public override IdentityError LoginAlreadyAssociated() { return new IdentityError { Code = nameof(LoginAlreadyAssociated), Description = "로그인하려는 이 사용자는 이미 다른 계정과 연결되어있습니다." }; }
        public override IdentityError InvalidUserName(string userName) { return new IdentityError { Code = nameof(InvalidUserName), Description = $"사용자명 '{userName}'는 유효하지 않습니다. 허용되지 않은 문자가 포함되어 있습니다." }; }
        public override IdentityError InvalidEmail(string email) { return new IdentityError { Code = nameof(InvalidEmail), Description = $"이메일 '{email}'는 유효하지 않습니다." }; }
        public override IdentityError DuplicateUserName(string userName) { return new IdentityError { Code = nameof(DuplicateUserName), Description = $"사용자명 '{userName}'는 이미 사용 중입니다." }; }
        public override IdentityError DuplicateEmail(string email) { return new IdentityError { Code = nameof(DuplicateEmail), Description = $"이메일 '{email}'는 이미 사용 중 입니다." }; }
        public override IdentityError InvalidRoleName(string role) { return new IdentityError { Code = nameof(InvalidRoleName), Description = $"역할 '{role}'는 유효하지 않습니다." }; }
        public override IdentityError DuplicateRoleName(string role) { return new IdentityError { Code = nameof(DuplicateRoleName), Description = $"역할 '{role}'는 이미 사용 중 입니다." }; }
        public override IdentityError UserAlreadyHasPassword() { return new IdentityError { Code = nameof(UserAlreadyHasPassword), Description = "사용자가 이미 비밀번호를 설정했습니다." }; }
        public override IdentityError UserLockoutNotEnabled() { return new IdentityError { Code = nameof(UserLockoutNotEnabled), Description = "이 사용자는 Lockout이 설정되지 않았습니다." }; }
        public override IdentityError UserAlreadyInRole(string role) { return new IdentityError { Code = nameof(UserAlreadyInRole), Description = $"역할 '{role}'에 존재하는 사용자입니다." }; }
        public override IdentityError UserNotInRole(string role) { return new IdentityError { Code = nameof(UserNotInRole), Description = $"역할 '{role}'에 포함되지 않은 사용자입니다." }; }
        public override IdentityError PasswordTooShort(int length) { return new IdentityError { Code = nameof(PasswordTooShort), Description = $"비밀번호는 최소 {length}자 이상이어야 합니다." }; }
        public override IdentityError PasswordRequiresNonAlphanumeric() { return new IdentityError { Code = nameof(PasswordRequiresNonAlphanumeric), Description = "비밀번호에는 영문자, 숫자가 아닌 문자가 하나 이상 있어야 합니다." }; }
        public override IdentityError PasswordRequiresDigit() { return new IdentityError { Code = nameof(PasswordRequiresDigit), Description = "비밀번호에는 적어도 하나의 숫자가 있어야합니다." }; }
        public override IdentityError PasswordRequiresLower() { return new IdentityError { Code = nameof(PasswordRequiresLower), Description = "비밀번호에는 영어 소문자가 하나 이상 있어야합니다." }; }
        public override IdentityError PasswordRequiresUpper() { return new IdentityError { Code = nameof(PasswordRequiresUpper), Description = "비밀번호에는 영어 대문자가 하나 이상 있어야합니다." }; }
        public override IdentityError RecoveryCodeRedemptionFailed() { return new IdentityError { Code = nameof(RecoveryCodeRedemptionFailed), Description = "복구 코드 사용에 실패했습니다." }; }
        public override IdentityError PasswordRequiresUniqueChars(int uniqueChars) { return new IdentityError { Code = nameof(PasswordRequiresUniqueChars), Description = "비밀번호는 최소한 {0}개의 다른 문자를 사용해야합니다." }; }
    }
    
  2. Startup.cs의 ConfigureServices를 열고 AddIdentity 메서드 다음에 아래와 같이 AddErrorDescriber 메서드를 추가해주시면 됩니다.
    services.AddIdentity<ApplicationUser, IdentityRole>()
            .AddErrorDescriber<IdentityErrorDescriberKr>();

방법2: Resources(.resx) 파일 사용하기

여러 국가의 언어로 서비스할 예정이라면 Resources(.resx)파일을 이용하는 방법이 권장됩니다. Microsoft에서도 이와 같이 구현하고 있으며 Resources.rexs(Github)IdentityErrorDescriber.cs(Github)에서 확인할 수 있습니다.

  1. 먼저 프로젝트에 Resources 폴더를 추가하고 .resx 파일을 추가합니다.
    Resources에 추가된 .resx 지역화 파일
    파일 명 뒤에 .ko-KR, .ja-JP는 .NET의 언어-국가 코드입니다. C#언어-국가 코드ISO 언어코드, MSDN(CultureInfo)를 참고하셔서 필요한 언어별로 파일을 추가하시면 됩니다.
  2. IdentityErrorMessages.resx의 파일을 텍스트 에디터로 열고 아래 코드를 추가해주세요.
    <data name="ConcurrencyFailure" xml:space="preserve">
        <value>Optimistic concurrency failure, object has been modified.</value>
        <comment>Error when optimistic concurrency fails</comment>
    </data>
    <data name="DefaultError" xml:space="preserve">
        <value>An unknown failure has occurred.</value>
        <comment>Default identity result error message</comment>
    </data>
    <data name="DuplicateEmail" xml:space="preserve">
        <value>Email '{0}' is already taken.</value>
        <comment>Error for duplicate emails</comment>
    </data>
    <data name="DuplicateRoleName" xml:space="preserve">
        <value>Role name '{0}' is already taken.</value>
        <comment>Error for duplicate roles</comment>
    </data>
    <data name="DuplicateUserName" xml:space="preserve">
        <value>User name '{0}' is already taken.</value>
        <comment>Error for duplicate user names</comment>
    </data>
    <data name="InvalidEmail" xml:space="preserve">
        <value>Email '{0}' is invalid.</value>
        <comment>Invalid email</comment>
    </data>
    <data name="InvalidRoleName" xml:space="preserve">
        <value>Role name '{0}' is invalid.</value>
        <comment>Error for invalid role names</comment>
    </data>
    <data name="InvalidToken" xml:space="preserve">
        <value>Invalid token.</value>
        <comment>Error when a token is not recognized</comment>
    </data>
    <data name="InvalidUserName" xml:space="preserve">
        <value>User name '{0}' is invalid, can only contain letters or digits.</value>
        <comment>User names can only contain letters or digits</comment>
    </data>
    <data name="LoginAlreadyAssociated" xml:space="preserve">
        <value>A user with this login already exists.</value>
        <comment>Error when a login already linked</comment>
    </data>
    <data name="PasswordMismatch" xml:space="preserve">
        <value>Incorrect password.</value>
        <comment>Error when a password doesn't match</comment>
    </data>
    <data name="PasswordRequiresDigit" xml:space="preserve">
        <value>Passwords must have at least one digit ('0'-'9').</value>
        <comment>Error when passwords do not have a digit</comment>
    </data>
    <data name="PasswordRequiresLower" xml:space="preserve">
        <value>Passwords must have at least one lowercase ('a'-'z').</value>
        <comment>Error when passwords do not have a lowercase letter</comment>
    </data>
    <data name="PasswordRequiresNonAlphanumeric" xml:space="preserve">
        <value>Passwords must have at least one non alphanumeric character.</value>
        <comment>Error when password does not have enough non alphanumeric characters</comment>
    </data>
    <data name="PasswordRequiresUpper" xml:space="preserve">
        <value>Passwords must have at least one uppercase ('A'-'Z').</value>
        <comment>Error when passwords do not have an uppercase letter</comment>
    </data>
    <data name="PasswordTooShort" xml:space="preserve">
        <value>Passwords must be at least {0} characters.</value>
        <comment>Error message for passwords that are too short</comment>
    </data>
    <data name="RecoveryCodeRedemptionFailed" xml:space="preserve">
        <value>Recovery code redemption failed.</value>
        <comment>Error when a recovery code is not redeemed.</comment>
    </data>
    <data name="UserAlreadyHasPassword" xml:space="preserve">
        <value>User already has a password set.</value>
        <comment>Error when AddPasswordAsync called when a user already has a password</comment>
    </data>
    <data name="UserAlreadyInRole" xml:space="preserve">
        <value>User already in role '{0}'.</value>
        <comment>Error when a user is already in a role</comment>
    </data>
    <data name="UserLockoutNotEnabled" xml:space="preserve">
        <value>Lockout is not enabled for this user.</value>
        <comment>Error when lockout is not enabled</comment>
    </data>
    <data name="UserNotInRole" xml:space="preserve">
        <value>User is not in role '{0}'.</value>
        <comment>Error when a user is not in the role</comment>
    </data>
    <data name="PasswordRequiresUniqueChars" xml:space="preserve">
        <value>Passwords must use at least {0} different characters.</value>
        <comment>Error message for passwords that are based on similar characters</comment>
    </data>
    저장 후 IdentityErrorMessages.resx 파일을 VisualStudio에서 파일을 열면 아래와 같이 나타납니다.
    IdentityErrorMessages.rexs 파일내용
  3. IdentityErrorMessages.ko-KR.rexs과 IdentityErrorMessages.ja-JP.rexs도 동일하게 텍스트 편집기로 열고 코드를 추가하신 후 내용을 한국어, 일어로 번역해주세요.
  4. IdentityErrorMessage.resx 파일을 Visual Studio에서 열어서 Access modifiers를 Public로 변경합니다.
    .resx파일의 Access modifier를 Public로 설정
  5. IdentityErrorDescriber를 Orverride하여 Resources를 사용하도록 작성하면 됩니다.
    public class LocalizedIdentityErrorDescriber : IdentityErrorDescriber
    {
        public override IdentityError DuplicateEmail(string email) { return new IdentityError { Code = nameof(DuplicateEmail), Description = string.Format(IdentityErrorMessages.DuplicateEmail, email) }; }
        public override IdentityError DuplicateUserName(string userName) { return new IdentityError { Code = nameof(DuplicateUserName), Description = string.Format(IdentityErrorMessages.DuplicateUserName, userName) }; }
        public override IdentityError InvalidEmail(string email) { return new IdentityError { Code = nameof(InvalidEmail), Description = string.Format(IdentityErrorMessages.InvalidEmail, email) }; }
        public override IdentityError DuplicateRoleName(string role) { return new IdentityError { Code = nameof(DuplicateRoleName), Description = string.Format(IdentityErrorMessages.DuplicateRoleName, role) }; }
        public override IdentityError InvalidRoleName(string role) { return new IdentityError { Code = nameof(InvalidRoleName), Description = string.Format(IdentityErrorMessages.InvalidRoleName, role) }; }
        public override IdentityError InvalidToken() { return new IdentityError { Code = nameof(InvalidToken), Description = IdentityErrorMessages.InvalidToken }; }
        public override IdentityError InvalidUserName(string userName) { return new IdentityError { Code = nameof(InvalidUserName), Description = string.Format(IdentityErrorMessages.InvalidUserName, userName) }; }
        public override IdentityError LoginAlreadyAssociated() { return new IdentityError { Code = nameof(LoginAlreadyAssociated), Description = IdentityErrorMessages.LoginAlreadyAssociated }; }
        public override IdentityError PasswordMismatch() { return new IdentityError { Code = nameof(PasswordMismatch), Description = IdentityErrorMessages.PasswordMismatch }; }
        public override IdentityError PasswordRequiresDigit() { return new IdentityError { Code = nameof(PasswordRequiresDigit), Description = IdentityErrorMessages.PasswordRequiresDigit }; }
        public override IdentityError PasswordRequiresLower() { return new IdentityError { Code = nameof(PasswordRequiresLower), Description = IdentityErrorMessages.PasswordRequiresLower }; }
        public override IdentityError PasswordRequiresNonAlphanumeric() { return new IdentityError { Code = nameof(PasswordRequiresNonAlphanumeric), Description = IdentityErrorMessages.PasswordRequiresNonAlphanumeric }; }
        public override IdentityError PasswordRequiresUniqueChars(int uniqueChars) { return new IdentityError { Code = nameof(PasswordRequiresUniqueChars), Description = string.Format(IdentityErrorMessages.PasswordRequiresUniqueChars, uniqueChars) }; }
        public override IdentityError PasswordRequiresUpper() { return new IdentityError { Code = nameof(PasswordRequiresUpper), Description = IdentityErrorMessages.PasswordRequiresUpper }; }
        public override IdentityError PasswordTooShort(int length) { return new IdentityError { Code = nameof(PasswordTooShort), Description = string.Format(IdentityErrorMessages.PasswordTooShort, length) }; }
        public override IdentityError UserAlreadyHasPassword() { return new IdentityError { Code = nameof(UserAlreadyHasPassword), Description = IdentityErrorMessages.UserAlreadyHasPassword }; }
        public override IdentityError UserAlreadyInRole(string role) { return new IdentityError { Code = nameof(UserAlreadyInRole), Description = string.Format(IdentityErrorMessages.UserAlreadyInRole, role) }; }
        public override IdentityError UserNotInRole(string role) { return new IdentityError { Code = nameof(UserNotInRole), Description = string.Format(IdentityErrorMessages.UserNotInRole, role) }; }
        public override IdentityError UserLockoutNotEnabled() { return new IdentityError { Code = nameof(UserLockoutNotEnabled), Description = IdentityErrorMessages.UserLockoutNotEnabled }; }
        public override IdentityError RecoveryCodeRedemptionFailed() { return new IdentityError { Code = nameof(RecoveryCodeRedemptionFailed), Description = IdentityErrorMessages.RecoveryCodeRedemptionFailed }; }
        public override IdentityError ConcurrencyFailure() { return new IdentityError { Code = nameof(ConcurrencyFailure), Description = IdentityErrorMessages.ConcurrencyFailure }; }
        public override IdentityError DefaultError() { return new IdentityError { Code = nameof(DefaultError), Description = IdentityErrorMessages.DefaultError }; }
    }
    
  6. 마지막으로 Startup.cs의 ConfigureServices를 열고 AddIdentity 메서드 다음에 아래와 같이 AddErrorDescriber 메서드를 추가해주시면 됩니다.
    services.AddIdentity<ApplicationUser, IdentityRole>()
            .AddErrorDescriber<LocalizedIdentityErrorDescriber>();

방법3: Nuget-Package에서 Identity.Core 한국어 설치

누겟에서 검색어 Microsoft.AspNet.Identity.Core를 입력하시면 Microsoft.AspNet.Identity.Core.ko 패키지가 나타납니다. 이 패키지를 설치하면 지역화가 된다고 합니다. (주의: 아직 테스트 되지 않은 방법입니다. 실제로 동작하지 않을 수도 있습니다.)

Nuget에서 한국어 Identity.Core 설치

테스트 방법

브라우저를 열고 설정에서 선호 언어를 변경하시면 해당 언어로 변경되어 나타납니다. 예를들어 IE에서는 [인터넷 옵션]의 [언어]에서 변경하실 수 있습니다.

IE의 언어설정 방법

'Web > ASP.NET Core' 카테고리의 다른 글

Application State vs 전역 Static 변수  (0) 2019.08.19
ASP.NET Core Identity 에러 지역화  (0) 2019.04.08
댓글
댓글쓰기 폼