#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
{
///
/// 다국어 데이터를 관리하고, 현재 설정된 언어에 맞는 번역 문자열을 제공하는 클래스입니다.
/// JSON 파일을 로드하여 다국어 데이터를 초기화하며, 언어 변경 시 이벤트를 통해 알림을 제공합니다.
/// 이 클래스는 싱글톤으로 구현되어 어디서든 를 통해 접근할 수 있습니다.
///
///
///
/// // 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 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("다국어 데이터 로드 실패.");
/// }
///
///
public class LocalizationManager
{
#region singleton
// 싱글톤 인스턴스를 저장하기 위한 private static 변수
private static LocalizationManager _instance;
// 멀티스레드 환경에서의 동시 접근을 제어하기 위한 lock 객체
private static readonly object _lock = new object();
///
/// 의 싱글톤 인스턴스를 가져옵니다.
/// 인스턴스가 아직 생성되지 않았다면 새로 생성하여 반환합니다.
///
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;
///
/// 언어가 변경되었을 때 발생하는 이벤트의 델리게이트입니다.
///
/// 새롭게 설정된 언어 코드입니다 (예: "en", "ko").
public delegate void LanguageChangedHandler(string newLanguageCode);
///
/// 현재 언어가 메서드를 통해 성공적으로 변경되었을 때 발생하는 이벤트입니다.
///
public event LanguageChangedHandler OnLanguageChanged;
///
/// 현재 설정된 언어 코드를 가져옵니다.
///
public string CurrentLanguage => _currentLanguage;
///
/// 사용 가능한 언어 목록에서 현재 언어의 인덱스를 가져옵니다.
///
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 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 반환
}
}
}
///
/// 로드된 다국어 데이터에서 사용 가능한 모든 언어 코드 목록을 가져옵니다.
/// 데이터가 로드되지 않았거나 번역 데이터가 비어있으면 빈 리스트를 반환합니다.
///
public List AvailableLanguages
{
get
{
if (_dataSource?.Languages != null && _dataSource.Languages.Count > 0)
{
return new List(_dataSource.Languages.Keys);
}
return new List();
}
}
///
/// 사용 가능한 표시 언어 컬렉션을 가져옵니다.
/// ["en"] = "English", ["ko"] = "한국어" 형식의 KeyValuePair 컬렉션입니다.
///
public Dictionary AvailableDisplayLanguages
{
get
{
if (_dataSource?.Languages != null && _dataSource.Languages.Count > 0)
{
return new Dictionary(_dataSource.Languages.AsReadOnlyCollection());
}
return new Dictionary();
}
}
///
/// 지정된 언어 코드에 해당하는 표시 이름을 가져옵니다.
///
/// 표시 이름을 가져올 언어 코드입니다.
/// 언어 코드에 해당하는 표시 이름입니다. 해당 코드가 없으면 null을 반환합니다.
public string GetLanguageDisplayName(string languageCode)
{
if (string.IsNullOrEmpty(languageCode) || _dataSource?.Languages == null)
{
return null;
}
_dataSource.Languages.TryGetValue(languageCode, out string displayName);
return displayName;
}
///
/// 클래스의 새 인스턴스를 초기화합니다.
/// 기본 언어를 "ko"로 설정합니다.
/// 생성자를 private으로 변경하여 외부에서의 직접적인 인스턴스 생성을 막습니다.
///
private LocalizationManager()
{
_dataSource = new LocalizationDataSource();
}
///
/// 기본 경로(/locale.json)에서 다국어 데이터를 로드합니다.
///
/// 데이터 로드 성공 시 true, 실패 시 false를 반환합니다.
public bool LoadDefaultLocalizationData(string defaultLanguage = "ko", string? customPath = null)
{
// 기본 언어를 설정하거나, 시스템 설정을 따르도록 초기화할 수 있습니다.
_currentLanguage = defaultLanguage; // 예시 기본 언어
string path = Path.Combine(Application.streamingAssetsPath, customPath ?? "locale.json");
return LoadLocalizationData(path);
}
///
/// 지정된 파일 경로에서 JSON 형식의 다국어 데이터를 로드합니다.
///
/// 로드할 JSON 파일의 전체 경로입니다.
/// 데이터 로드 및 분석 성공 시 true, 실패 시 false를 반환합니다.
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(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 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;
}
}
///
/// 현재 사용 언어를 변경합니다.
/// 성공적으로 언어가 변경되면 이벤트가 발생합니다.
///
/// 설정할 언어 코드입니다 (예: "en", "ko").
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}'.");
}
}
///
/// 현재 설정된 언어를 기준으로, 주어진 키에 해당하는 번역된 문자열을 가져옵니다.
///
/// 번역 문자열을 찾기 위한 키입니다.
///
/// 번역된 문자열을 반환합니다.
/// 키가 null이거나 비어있으면 빈 문자열을 반환합니다.
/// 해당 키 또는 현재 언어에 대한 번역을 찾을 수 없으면, "[키]" 형식의 문자열을 반환합니다.
///
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; // 키를 그대로 반환
}
///
/// 특정 언어의 특정 키에 대한 번역된 문자열을 직접 가져옵니다.
///
/// 문자열을 가져올 대상 언어 코드입니다.
/// 번역 문자열을 찾기 위한 키입니다.
///
/// 번역된 문자열을 반환합니다.
/// 언어 코드나 키가 null이거나 비어있으면 빈 문자열을 반환합니다.
/// 해당 언어 또는 키에 대한 번역을 찾을 수 없으면 null을 반환합니다.
///
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]" 반환)
}
}
}