397 lines
18 KiB
C#
397 lines
18 KiB
C#
#nullable enable
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using Unity.VisualScripting;
|
|
using UnityEngine;
|
|
using UVC.Json;
|
|
using UVC.Log;
|
|
|
|
namespace UVC.Locale
|
|
{
|
|
/// <summary>
|
|
/// 다국어 데이터를 관리하고, 현재 설정된 언어에 맞는 번역 문자열을 제공하는 클래스입니다.
|
|
/// JSON 파일을 로드하여 다국어 데이터를 초기화하며, 언어 변경 시 이벤트를 통해 알림을 제공합니다.
|
|
/// 이 클래스는 싱글톤으로 구현되어 어디서든 <see cref="Instance"/>를 통해 접근할 수 있습니다.
|
|
/// </summary>
|
|
/// <example>
|
|
/// <code>
|
|
/// // 1. LocalizationManager 인스턴스 접근 (싱글톤)
|
|
/// // LocalizationManager locManager = LocalizationManager.Instance; // 직접 인스턴스화 대신 Instance 사용
|
|
///
|
|
/// // 2. (선택 사항) 언어 변경 이벤트 구독
|
|
/// LocalizationManager.Instance.OnLanguageChanged += (newLanguage) => {
|
|
/// ULog.Debug($"언어가 {newLanguage}(으)로 변경되었습니다.");
|
|
/// // UI 업데이트 로직 등
|
|
/// };
|
|
///
|
|
/// // 3. 다국어 데이터 로드 (StreamingAssets 폴더의 locale.json 파일을 기본으로 로드)
|
|
/// // 인스턴스 생성 시 자동으로 로드하거나, 별도의 초기화 메서드를 통해 로드할 수 있습니다.
|
|
/// // 예시: 로드 메서드가 private 생성자 또는 별도 초기화 메서드에서 호출된다고 가정
|
|
/// if (LocalizationManager.Instance.LoadDefaultLocalizationData()) // LoadDefaultLocalizationData가 public이라고 가정
|
|
/// {
|
|
/// ULog.Debug("다국어 데이터 로드 성공!");
|
|
///
|
|
/// // 4. 사용 가능한 언어 목록 확인 (언어 코드 기준)
|
|
/// List<string> availableLangCodes = LocalizationManager.Instance.AvailableLanguages();
|
|
/// ULog.Debug("사용 가능한 언어 코드: " + string.Join(", ", availableLangCodes));
|
|
///
|
|
/// // 특정 언어 코드의 표시 이름 가져오기
|
|
/// string koreanDisplayName = LocalizationManager.Instance.GetLanguageDisplayName("ko");
|
|
/// ULog.Debug($"ko display name: {koreanDisplayName}"); // 출력: 한국어
|
|
///
|
|
/// // 5. 언어 변경
|
|
/// LocalizationManager.Instance.SetCurrentLanguage("ko"); // 한국어로 변경
|
|
///
|
|
/// // 6. 번역된 문자열 가져오기
|
|
/// string errorMsg = LocalizationManager.Instance.GetString("error");
|
|
/// ULog.Debug(errorMsg); // 현재 언어가 "ko"이면 "에러" 출력
|
|
///
|
|
/// LocalizationManager.Instance.SetCurrentLanguage("en"); // 영어로 변경
|
|
/// welcomeMsg = LocalizationManager.Instance.GetString("welcome_message");
|
|
/// ULog.Debug(welcomeMsg); // 출력 (영어): Welcome!
|
|
/// }
|
|
/// else
|
|
/// {
|
|
/// ULog.Error("다국어 데이터 로드 실패.");
|
|
/// }
|
|
/// </code>
|
|
/// </example>
|
|
public class LocalizationManager
|
|
{
|
|
#region singleton
|
|
|
|
// 싱글톤 인스턴스를 저장하기 위한 private static 변수
|
|
private static LocalizationManager _instance;
|
|
// 멀티스레드 환경에서의 동시 접근을 제어하기 위한 lock 객체
|
|
private static readonly object _lock = new object();
|
|
|
|
/// <summary>
|
|
/// <see cref="LocalizationManager"/>의 싱글톤 인스턴스를 가져옵니다.
|
|
/// 인스턴스가 아직 생성되지 않았다면 새로 생성하여 반환합니다.
|
|
/// </summary>
|
|
public static LocalizationManager Instance
|
|
{
|
|
get
|
|
{
|
|
// Double-checked locking으로 스레드 안전성 확보
|
|
if (_instance == null)
|
|
{
|
|
lock (_lock)
|
|
{
|
|
if (_instance == null)
|
|
{
|
|
_instance = new LocalizationManager();
|
|
//_instance.LoadDefaultLocalizationData("ko"); // 기본 언어 데이터 로드
|
|
}
|
|
}
|
|
}
|
|
return _instance;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
|
|
private LocalizationDataSource _dataSource;
|
|
private string _currentLanguage;
|
|
|
|
/// <summary>
|
|
/// 언어가 변경되었을 때 발생하는 이벤트의 델리게이트입니다.
|
|
/// </summary>
|
|
/// <param name="newLanguageCode">새롭게 설정된 언어 코드입니다 (예: "en", "ko").</param>
|
|
public delegate void LanguageChangedHandler(string newLanguageCode);
|
|
/// <summary>
|
|
/// 현재 언어가 <see cref="SetCurrentLanguage"/> 메서드를 통해 성공적으로 변경되었을 때 발생하는 이벤트입니다.
|
|
/// </summary>
|
|
public event LanguageChangedHandler OnLanguageChanged;
|
|
|
|
/// <summary>
|
|
/// <see cref="LoadDefaultLocalizationData"/>에서 모든 데이터 로드가 완료되었을 때 한 번 발생하는 이벤트입니다.
|
|
/// 이미 로드가 완료된 후 구독하면 즉시 호출됩니다.
|
|
/// </summary>
|
|
public event Action? OnReady
|
|
{
|
|
add
|
|
{
|
|
OnReadyInternal += value;
|
|
if (IsReady) value?.Invoke();
|
|
}
|
|
remove => OnReadyInternal -= value;
|
|
}
|
|
private event Action? OnReadyInternal;
|
|
|
|
/// <summary>다국어 데이터 로드가 완료되었는지 여부를 나타냅니다.</summary>
|
|
public bool IsReady { get; private set; }
|
|
|
|
/// <summary>
|
|
/// 현재 설정된 언어 코드를 가져옵니다.
|
|
/// </summary>
|
|
public string CurrentLanguage => _currentLanguage;
|
|
|
|
/// <summary>
|
|
/// 사용 가능한 언어 목록에서 현재 언어의 인덱스를 가져옵니다.
|
|
/// </summary>
|
|
public int CurrentLanguageIndex
|
|
{
|
|
get
|
|
{
|
|
if (_currentLanguage == null)
|
|
{
|
|
ULog.Warning("Current language is not set. Returning -1.");
|
|
return -1; // 언어가 설정되지 않은 경우 -1 반환
|
|
}
|
|
if (_dataSource?.Languages == null || _dataSource.Languages.Count == 0)
|
|
{
|
|
ULog.Warning("Languages data source is not loaded or empty. Returning -1.");
|
|
return -1; // 언어 데이터가 로드되지 않았거나 비어있는 경우 -1 반환
|
|
}
|
|
// AvailableLanguages를 통해 현재 언어의 인덱스를 찾습니다.
|
|
List<string> availableLanguages = AvailableLanguages;
|
|
int index = availableLanguages.IndexOf(_currentLanguage);
|
|
if (index != -1)
|
|
{
|
|
return index; // 현재 언어가 AvailableLanguages에 존재하는 경우 인덱스 반환
|
|
}
|
|
else
|
|
{
|
|
ULog.Warning($"Current language '{_currentLanguage}' not found in available languages. Returning -1.");
|
|
return -1; // 현재 언어가 AvailableLanguages에 없는 경우 -1 반환
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 로드된 다국어 데이터에서 사용 가능한 모든 언어 코드 목록을 가져옵니다.
|
|
/// 데이터가 로드되지 않았거나 번역 데이터가 비어있으면 빈 리스트를 반환합니다.
|
|
/// </summary>
|
|
public List<string> AvailableLanguages
|
|
{
|
|
get
|
|
{
|
|
if (_dataSource?.Languages != null && _dataSource.Languages.Count > 0)
|
|
{
|
|
return new List<string>(_dataSource.Languages.Keys);
|
|
}
|
|
return new List<string>();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 사용 가능한 표시 언어 컬렉션을 가져옵니다.
|
|
/// ["en"] = "English", ["ko"] = "한국어" 형식의 KeyValuePair 컬렉션입니다.
|
|
/// </summary>
|
|
public Dictionary<string, string> AvailableDisplayLanguages
|
|
{
|
|
get
|
|
{
|
|
if (_dataSource?.Languages != null && _dataSource.Languages.Count > 0)
|
|
{
|
|
return new Dictionary<string, string>(_dataSource.Languages.AsReadOnlyCollection());
|
|
}
|
|
return new Dictionary<string, string>();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 지정된 언어 코드에 해당하는 표시 이름을 가져옵니다.
|
|
/// </summary>
|
|
/// <param name="languageCode">표시 이름을 가져올 언어 코드입니다.</param>
|
|
/// <returns>언어 코드에 해당하는 표시 이름입니다. 해당 코드가 없으면 null을 반환합니다.</returns>
|
|
public string GetLanguageDisplayName(string languageCode)
|
|
{
|
|
if (string.IsNullOrEmpty(languageCode) || _dataSource?.Languages == null)
|
|
{
|
|
return null;
|
|
}
|
|
_dataSource.Languages.TryGetValue(languageCode, out string displayName);
|
|
return displayName;
|
|
}
|
|
|
|
/// <summary>
|
|
/// <see cref="LocalizationManager"/> 클래스의 새 인스턴스를 초기화합니다.
|
|
/// 기본 언어를 "ko"로 설정합니다.
|
|
/// 생성자를 private으로 변경하여 외부에서의 직접적인 인스턴스 생성을 막습니다.
|
|
/// </summary>
|
|
private LocalizationManager()
|
|
{
|
|
_dataSource = new LocalizationDataSource();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 기본 경로(<see cref="Application.streamingAssetsPath"/>/locale.json)에서 다국어 데이터를 로드합니다.
|
|
/// </summary>
|
|
/// <returns>데이터 로드 성공 시 true, 실패 시 false를 반환합니다.</returns>
|
|
public bool LoadDefaultLocalizationData(string defaultLanguage = "ko", string? customPath = null)
|
|
{
|
|
// 기본 언어를 설정하거나, 시스템 설정을 따르도록 초기화할 수 있습니다.
|
|
_currentLanguage = defaultLanguage; // 예시 기본 언어
|
|
string path = Path.Combine(Application.streamingAssetsPath, customPath ?? "locale.json");
|
|
bool result = LoadLocalizationData(path);
|
|
if (result)
|
|
{
|
|
IsReady = true;
|
|
OnReadyInternal?.Invoke();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 지정된 파일 경로에서 JSON 형식의 다국어 데이터를 로드합니다.
|
|
/// </summary>
|
|
/// <param name="filePath">로드할 JSON 파일의 전체 경로입니다.</param>
|
|
/// <returns>데이터 로드 및 분석 성공 시 true, 실패 시 false를 반환합니다.</returns>
|
|
public bool LoadLocalizationData(string filePath)
|
|
{
|
|
if (!File.Exists(filePath))
|
|
{
|
|
ULog.Error($"Localization file not found: {filePath}", new System.Exception($"Localization file not found: {filePath}"));
|
|
return false;
|
|
}
|
|
|
|
try
|
|
{
|
|
string jsonData = File.ReadAllText(filePath);
|
|
// 직접 Dictionary로 역직렬화한 후 Translations에 할당
|
|
var loadedDataSource = JsonHelper.FromJson<LocalizationDataSource>(jsonData);
|
|
|
|
if (loadedDataSource == null || loadedDataSource.Words == null || loadedDataSource.Languages == null)
|
|
{
|
|
ULog.Error($"Failed to deserialize localization data, or 'languages'/'words' section is missing in: {filePath}");
|
|
_dataSource = new LocalizationDataSource(); // 빈 데이터 소스로 초기화
|
|
return false;
|
|
}
|
|
|
|
|
|
_dataSource = loadedDataSource;
|
|
|
|
// 데이터 로드 후, 현재 설정된 언어가 유효한지 확인하고,
|
|
// 유효하지 않다면 사용 가능한 첫 번째 언어나 기본 언어로 설정할 수 있습니다.
|
|
List<string> availableLangCodes = AvailableLanguages;
|
|
if (!string.IsNullOrEmpty(_currentLanguage) && !availableLangCodes.Contains(_currentLanguage))
|
|
{
|
|
ULog.Warning($"Current language '{_currentLanguage}' not found in the new data source's 'languages' section. Attempting to set to default or first available.");
|
|
if (availableLangCodes.Count > 0)
|
|
{
|
|
SetCurrentLanguage(availableLangCodes[0]);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
ULog.Error($"Error loading localization data from {filePath}: {ex.Message}", ex);
|
|
_dataSource = new LocalizationDataSource(); // 오류 발생 시 빈 데이터 소스로 초기화
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 현재 사용 언어를 변경합니다.
|
|
/// 성공적으로 언어가 변경되면 <see cref="OnLanguageChanged"/> 이벤트가 발생합니다.
|
|
/// </summary>
|
|
/// <param name="languageCode">설정할 언어 코드입니다 (예: "en", "ko").</param>
|
|
public void SetCurrentLanguage(string languageCode)
|
|
{
|
|
if (string.IsNullOrEmpty(languageCode))
|
|
{
|
|
ULog.Warning("Language code cannot be null or empty.");
|
|
return;
|
|
}
|
|
|
|
if (_dataSource?.Languages == null || _dataSource.Languages.Count == 0)
|
|
{
|
|
ULog.Warning("Languages data source is not loaded or empty. Cannot set language.");
|
|
return;
|
|
}
|
|
|
|
//AvailableLanguages를 통해 해당 언어가 지원되는지 확인합니다.
|
|
if (_dataSource.Languages.ContainsKey(languageCode))
|
|
{
|
|
if (_currentLanguage != languageCode)
|
|
{
|
|
_currentLanguage = languageCode;
|
|
OnLanguageChanged?.Invoke(_currentLanguage);
|
|
ULog.Debug($"Current language changed to: {languageCode} ({GetLanguageDisplayName(languageCode)})");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ULog.Warning($"Language code '{languageCode}' not found in 'languages' data source. Current language remains '{_currentLanguage}'.");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 현재 설정된 언어를 기준으로, 주어진 키에 해당하는 번역된 문자열을 가져옵니다.
|
|
/// </summary>
|
|
/// <param name="key">번역 문자열을 찾기 위한 키입니다.</param>
|
|
/// <returns>
|
|
/// 번역된 문자열을 반환합니다.
|
|
/// 키가 null이거나 비어있으면 빈 문자열을 반환합니다.
|
|
/// 해당 키 또는 현재 언어에 대한 번역을 찾을 수 없으면, "[키]" 형식의 문자열을 반환합니다.
|
|
/// </returns>
|
|
public string GetString(string key)
|
|
{
|
|
if (string.IsNullOrEmpty(key))
|
|
{
|
|
ULog.Warning("Requested localization key is null or empty.");
|
|
return string.Empty;
|
|
}
|
|
|
|
if (_dataSource?.Words == null)
|
|
{
|
|
ULog.Warning("Words data source is not loaded. Cannot get string for key: " + key);
|
|
return key;
|
|
}
|
|
|
|
if (_dataSource.Words.TryGetValue(key, out var languageSpecificStrings) &&
|
|
languageSpecificStrings.TryGetValue(_currentLanguage, out var translatedString))
|
|
{
|
|
return translatedString;
|
|
}
|
|
|
|
//ULog.Debug($"Translation for key '{key}' not found in language '{_currentLanguage}'.");
|
|
return key; // 키를 그대로 반환
|
|
}
|
|
|
|
/// <summary>
|
|
/// 특정 언어의 특정 키에 대한 번역된 문자열을 직접 가져옵니다.
|
|
/// </summary>
|
|
/// <param name="languageCode">문자열을 가져올 대상 언어 코드입니다.</param>
|
|
/// <param name="key">번역 문자열을 찾기 위한 키입니다.</param>
|
|
/// <returns>
|
|
/// 번역된 문자열을 반환합니다.
|
|
/// 언어 코드나 키가 null이거나 비어있으면 빈 문자열을 반환합니다.
|
|
/// 해당 언어 또는 키에 대한 번역을 찾을 수 없으면 null을 반환합니다.
|
|
/// </returns>
|
|
public string GetString(string languageCode, string key)
|
|
{
|
|
if (string.IsNullOrEmpty(languageCode))
|
|
{
|
|
ULog.Warning("Requested language code is null or empty for GetString(languageCode, key).");
|
|
return key;
|
|
}
|
|
if (string.IsNullOrEmpty(key))
|
|
{
|
|
ULog.Warning("Requested localization key is null or empty for GetString(languageCode, key).");
|
|
return key;
|
|
}
|
|
|
|
if (_dataSource?.Words == null)
|
|
{
|
|
ULog.Warning("Words data source is not loaded. Cannot get string for key: " + key + " in language: " + languageCode);
|
|
return key;
|
|
}
|
|
|
|
if (_dataSource.Words.TryGetValue(key, out var languageSpecificStrings) &&
|
|
languageSpecificStrings.TryGetValue(languageCode, out var translatedString))
|
|
{
|
|
return translatedString;
|
|
}
|
|
|
|
//ULog.Debug($"Translation for key '{key}' not found in language '{languageCode}' within 'words' data.");
|
|
return key; // 해당 언어/키가 없음을 표시 (이전에는 "[languageCode:key]" 반환)
|
|
}
|
|
}
|
|
}
|