/* Yarn Spinner is licensed to you under the terms found in the file LICENSE.md. */ #nullable enable using System.Collections.Generic; using System.Threading; using UnityEngine; using Yarn.Unity.Attributes; #nullable enable namespace Yarn.Unity { public sealed class BuiltinLocalisedLineProvider : LineProviderBehaviour, ILineProvider { public override string LocaleCode { get => _textLocaleCode; set => _textLocaleCode = value; } [SerializeField, Language] private string _textLocaleCode = System.Globalization.CultureInfo.CurrentCulture.TwoLetterISOLanguageName; [SerializeField, Language] private string _assetLocaleCode = System.Globalization.CultureInfo.CurrentCulture.TwoLetterISOLanguageName; [SerializeField] private bool _useFallback = false; [ShowIf(nameof(_useFallback))] [SerializeField, Language] private string _fallbackLocaleCode = System.Globalization.CultureInfo.CurrentCulture.TwoLetterISOLanguageName; public string AssetLocaleCode { get => _assetLocaleCode; set => _assetLocaleCode = value; } private Markup.LineParser lineParser = new Markup.LineParser(); private Markup.BuiltInMarkupReplacer builtInReplacer = new Markup.BuiltInMarkupReplacer(); public override void RegisterMarkerProcessor(string attributeName, Markup.IAttributeMarkerProcessor markerProcessor) { lineParser.RegisterMarkerProcessor(attributeName, markerProcessor); } public override void DeregisterMarkerProcessor(string attributeName) { lineParser.DeregisterMarkerProcessor(attributeName); } void Awake() { lineParser.RegisterMarkerProcessor("select", builtInReplacer); lineParser.RegisterMarkerProcessor("plural", builtInReplacer); lineParser.RegisterMarkerProcessor("ordinal", builtInReplacer); } public override async YarnTask GetLocalizedLineAsync(Line line, CancellationToken cancellationToken) { string sourceLineID = line.ID; string[] metadata = System.Array.Empty(); // Check to see if this line shadows another. If it does, we'll use // that line's text and asset. if (YarnProject != null) { metadata = YarnProject.lineMetadata?.GetMetadata(line.ID) ?? System.Array.Empty(); var shadowLineSource = YarnProject.lineMetadata?.GetShadowLineSource(line.ID); if (shadowLineSource != null) { sourceLineID = shadowLineSource; } } string? text = GetLocalizedString(sourceLineID); Object? asset = await GetLocalizedAssetAsync(sourceLineID); if (text == null) { // No line available. Debug.LogWarning($"Localization {LocaleCode} does not contain an entry for line {line.ID}", this); return LocalizedLine.InvalidLine; } var parseResult = lineParser.ParseString(Markup.LineParser.ExpandSubstitutions(text, line.Substitutions), LocaleCode); return new LocalizedLine { Text = parseResult, RawText = text, TextID = line.ID, Asset = asset, Metadata = metadata, }; } private Localization GetLocalization(string locale) { if (YarnProject == null) { throw new System.InvalidOperationException($"Can't get localized line: no Yarn Project set"); } Localization loc = YarnProject.GetLocalization(locale); if (loc == null) { throw new System.InvalidOperationException($"Can't get localized line: Yarn Project has no localisation for {locale}"); } return loc; } private string? GetLocalizedString(string sourceLineID) { var baseLoc = GetLocalization(_textLocaleCode); string? text = baseLoc.GetLocalizedString(sourceLineID); if (text != null) { return text; } if (_useFallback) { var fallbackLoc = GetLocalization(_fallbackLocaleCode); return fallbackLoc.GetLocalizedString(sourceLineID); } return null; } private async YarnTask GetLocalizedAssetAsync(string sourceLineID) { var baseLoc = GetLocalization(_assetLocaleCode); Object? result = await baseLoc.GetLocalizedObjectAsync(sourceLineID); if (result != null) { return result; } if (_useFallback) { var fallbackLoc = GetLocalization(_fallbackLocaleCode); return await fallbackLoc.GetLocalizedObjectAsync(sourceLineID); } return null; } public async override YarnTask PrepareForLinesAsync(IEnumerable lineIDs, CancellationToken cancellationToken) { if (YarnProject == null) { // We don't have a Yarn Project, so there's no preparation we // can do. do. return; } var assetLocalization = YarnProject.GetLocalization(AssetLocaleCode); if (assetLocalization.UsesAddressableAssets) { // The localization uses addressable assets. Ensure that these // assets are pre-loaded. var tasks = new List>(); foreach (var id in lineIDs) { var task = assetLocalization.GetLocalizedObjectAsync(id); tasks.Add(task); } await YarnTask.WhenAll(tasks); } else { // The localization uses direct references. No need to pre-load // the assets - they were loaded with the scene. return; } } } }