同步
This commit is contained in:
@@ -0,0 +1,188 @@
|
||||
/*
|
||||
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<LocalizedLine> GetLocalizedLineAsync(Line line, CancellationToken cancellationToken)
|
||||
{
|
||||
string sourceLineID = line.ID;
|
||||
|
||||
string[] metadata = System.Array.Empty<string>();
|
||||
|
||||
// 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<string>();
|
||||
|
||||
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<Object?> GetLocalizedAssetAsync(string sourceLineID)
|
||||
{
|
||||
var baseLoc = GetLocalization(_assetLocaleCode);
|
||||
Object? result = await baseLoc.GetLocalizedObjectAsync<Object>(sourceLineID);
|
||||
|
||||
if (result != null)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
if (_useFallback)
|
||||
{
|
||||
var fallbackLoc = GetLocalization(_fallbackLocaleCode);
|
||||
return await fallbackLoc.GetLocalizedObjectAsync<Object>(sourceLineID);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public async override YarnTask PrepareForLinesAsync(IEnumerable<string> 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<YarnTask<Object?>>();
|
||||
|
||||
foreach (var id in lineIDs)
|
||||
{
|
||||
var task = assetLocalization.GetLocalizedObjectAsync<Object>(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6b6798449caec4a32a5ad27badb06624
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
Yarn Spinner is licensed to you under the terms found in the file LICENSE.md.
|
||||
*/
|
||||
|
||||
using System.Collections.Generic;
|
||||
#nullable enable
|
||||
|
||||
namespace Yarn.Unity
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains methods for accessing assets of a given type stored within an
|
||||
/// object.
|
||||
/// </summary>
|
||||
public interface IAssetProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Attempts to fetch an asset of type <typeparamref name="T"/> from the
|
||||
/// object.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the assets.</typeparam>
|
||||
/// <param name="result">On return, the fetched asset, or <see
|
||||
/// langword="null"/>.</param>
|
||||
/// <returns><see langword="true"/> if an asset was fetched; <see
|
||||
/// langword="false"/> otherwise.</returns>
|
||||
public bool TryGetAsset<T>([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out T? result) where T : UnityEngine.Object;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a collection of assets of type <typeparamref name="T"/> from
|
||||
/// the target.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the asset.</typeparam>
|
||||
/// <returns>A collection of assets. This collection may be
|
||||
/// empty.</returns>
|
||||
public IEnumerable<T> GetAssetsOfType<T>() where T : UnityEngine.Object;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1e028f35c1f4a4b06bc175cf06aa72c6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
Yarn Spinner is licensed to you under the terms found in the file LICENSE.md.
|
||||
*/
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using UnityEngine;
|
||||
using Yarn.Markup;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Yarn.Unity
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains methods for retrieving user-facing localized content, given
|
||||
/// non-localized line IDs.
|
||||
/// </summary>
|
||||
public interface ILineProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="YarnProject"/> that contains the localized data for
|
||||
/// lines.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This property is set at run-time by the object that will be requesting
|
||||
/// content (typically a <see cref="DialogueRunner"/>).
|
||||
/// </remarks>
|
||||
public YarnProject? YarnProject { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the line provider's current locale identifier, as a BCP-47 code.
|
||||
/// </summary>
|
||||
public string LocaleCode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Prepares and returns a <see cref="LocalizedLine"/> from the specified
|
||||
/// <see cref="Yarn.Line"/>.
|
||||
/// </summary>
|
||||
/// <param name="line">The <see cref="Yarn.Line"/> to produce the <see
|
||||
/// cref="LocalizedLine"/> from.</param>
|
||||
/// <param name="cancellationToken">A cancellation token that indicates
|
||||
/// whether the process of fetching the localised version of <paramref
|
||||
/// name="line"/> should be cancelled.</param>
|
||||
/// <returns>A localized line, ready to be presented to the
|
||||
/// player.</returns>
|
||||
public YarnTask<LocalizedLine> GetLocalizedLineAsync(Line line, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Signals to the line provider that lines with the provided line IDs may
|
||||
/// be presented shortly.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This method allows implementing classes a chance to prepare any
|
||||
/// neccessary resources needed to present these lines, like pre-loading
|
||||
/// voice-over audio. The default implementation does nothing.
|
||||
/// </para>
|
||||
/// <para style="info">
|
||||
/// Not every line may run; this method serves as a way to give the line
|
||||
/// provider advance notice that a line <i>may</i> run, not <i>will</i> run.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <param name="lineIDs">A collection of line IDs that the line provider
|
||||
/// should prepare for.</param>
|
||||
/// <param name="cancellationToken">A cancellation token that indicates
|
||||
/// whether the operation should be cancelled.</param>
|
||||
public YarnTask PrepareForLinesAsync(IEnumerable<string> lineIDs, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new marker processor to the line provider.
|
||||
/// </summary>
|
||||
/// <param name="attributeName">The name of the markers to use <paramref
|
||||
/// name="markerProcessor"/> for.</param>
|
||||
/// <param name="markerProcessor">The marker processor to add.</param>
|
||||
public void RegisterMarkerProcessor(string attributeName, Yarn.Markup.IAttributeMarkerProcessor markerProcessor);
|
||||
|
||||
/// <summary>
|
||||
/// Removes all marker processors that handle markers named <paramref
|
||||
/// name="attributeName"/>.
|
||||
/// </summary>
|
||||
/// <param name="attributeName">The name of the marker to remove processors
|
||||
/// for.</param>
|
||||
public void DeregisterMarkerProcessor(string attributeName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A <see cref="MonoBehaviour"/> that produces <see
|
||||
/// cref="LocalizedLine"/>s, for use in Dialogue Presenters.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see cref="DialogueRunner"/>s use a <see cref="LineProviderBehaviour"/>
|
||||
/// to get <see cref="LocalizedLine"/>s, which contain the localized
|
||||
/// information that is presented to the player through dialogue presenters.
|
||||
/// </remarks>
|
||||
public abstract class LineProviderBehaviour : MonoBehaviour, ILineProvider
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public abstract YarnTask<LocalizedLine> GetLocalizedLineAsync(Line line, CancellationToken cancellationToken);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public YarnProject? YarnProject { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual YarnTask PrepareForLinesAsync(IEnumerable<string> lineIDs, CancellationToken cancellationToken)
|
||||
{
|
||||
// No-op by default.
|
||||
return YarnTask.CompletedTask;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public abstract string LocaleCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Called by Unity when the <see cref="LineProviderBehaviour"/> has
|
||||
/// first appeared in the scene.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method is <see langword="public"/> <see langword="virtual"/> to
|
||||
/// allow subclasses to override it.
|
||||
/// </remarks>
|
||||
public virtual void Start()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public abstract void RegisterMarkerProcessor(string attributeName, IAttributeMarkerProcessor markerProcessor);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public abstract void DeregisterMarkerProcessor(string attributeName);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1724d534cf292400da295a39a3a8d97c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
Yarn Spinner is licensed to you under the terms found in the file LICENSE.md.
|
||||
*/
|
||||
|
||||
using UnityEngine;
|
||||
#nullable enable
|
||||
|
||||
namespace Yarn.Unity
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Represents a line, ready to be presented to the user in the localisation
|
||||
/// they have specified.
|
||||
/// </summary>
|
||||
public class LocalizedLine
|
||||
{
|
||||
/// <summary>
|
||||
/// DialogueLine's ID
|
||||
/// </summary>
|
||||
public string TextID = "<unknown>";
|
||||
|
||||
/// <summary>
|
||||
/// DialogueLine's inline expression's substitution
|
||||
/// </summary>
|
||||
public string[] Substitutions = System.Array.Empty<string>();
|
||||
|
||||
/// <summary>
|
||||
/// DialogueLine's text
|
||||
/// </summary>
|
||||
public string? RawText;
|
||||
|
||||
/// <summary>
|
||||
/// Any metadata associated with this line.
|
||||
/// </summary>
|
||||
public string[] Metadata = System.Array.Empty<string>();
|
||||
|
||||
/// <summary>
|
||||
/// The name of the character, if present.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This value will be <see langword="null"/> if the line does not have
|
||||
/// a character name.
|
||||
/// </remarks>
|
||||
public string? CharacterName
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Text.TryGetAttributeWithName("character", out var characterNameAttribute))
|
||||
{
|
||||
if (characterNameAttribute.Properties.TryGetValue("name", out var value))
|
||||
{
|
||||
return value.StringValue;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The asset associated with this line, if any.
|
||||
/// </summary>
|
||||
public Object? Asset;
|
||||
|
||||
/// <summary>
|
||||
/// The object that created this line.
|
||||
/// Most of the time will be the <see cref="DialogueRunner"/> that passed the presenter the line.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This exists for situations where you need the dialogue runner (or your custom equivalent) to send back messages.
|
||||
/// In particular this is used by the <see cref="VoiceOverPresenter"/> to get a reference to the dialogue runner to advance lines after playback is finished without needing a specific reference.
|
||||
/// Allowing the presenter to be reused across multiple runners.
|
||||
/// </remarks>
|
||||
public object? Source;
|
||||
|
||||
/// <summary>
|
||||
/// The underlying <see cref="Yarn.Markup.MarkupParseResult"/> for this
|
||||
/// line.
|
||||
/// </summary>
|
||||
public Markup.MarkupParseResult Text { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The underlying <see cref="Yarn.Markup.MarkupParseResult"/> for this
|
||||
/// line, with any `character` attribute removed.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If the line has no `character` attribute, this method returns the
|
||||
/// same value as <see cref="Text"/>.
|
||||
/// </remarks>
|
||||
public Markup.MarkupParseResult TextWithoutCharacterName
|
||||
{
|
||||
get
|
||||
{
|
||||
// If a 'character' attribute is present, remove its text
|
||||
if (Text.TryGetAttributeWithName("character", out var characterNameAttribute))
|
||||
{
|
||||
// because of how we delete the text we also clear up the attributes
|
||||
// most of the time this is the right play
|
||||
// however the character feels important enough to add it back in
|
||||
var characterless = Text.DeleteRange(characterNameAttribute);
|
||||
characterless.Attributes.Add(characterNameAttribute);
|
||||
return characterless;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Text;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A <see cref="LocalizedLine"/> object that represents content not
|
||||
/// being found.
|
||||
/// </summary>
|
||||
public static readonly LocalizedLine InvalidLine = new LocalizedLine
|
||||
{
|
||||
Asset = null,
|
||||
Metadata = System.Array.Empty<string>(),
|
||||
RawText = "!! ERROR: Missing line!",
|
||||
Substitutions = System.Array.Empty<string>(),
|
||||
TextID = "<missing>",
|
||||
Text = new Markup.MarkupParseResult("!! ERROR: Missing line!", new System.Collections.Generic.List<Markup.MarkupAttribute>())
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5fca305f6aaeb461381b51f8f37c31b1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,228 @@
|
||||
/*
|
||||
Yarn Spinner is licensed to you under the terms found in the file LICENSE.md.
|
||||
*/
|
||||
|
||||
#if USE_UNITY_LOCALIZATION
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Localization;
|
||||
using UnityEngine.Localization.Metadata;
|
||||
using UnityEngine.Localization.Settings;
|
||||
using UnityEngine.Localization.Tables;
|
||||
using UnityEngine.ResourceManagement.Exceptions;
|
||||
using Yarn.Markup;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Yarn.Unity.UnityLocalization
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Contains Yarn Spinner related metadata for Unity string table entries.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class LineMetadata : IMetadata
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of the Yarn node that this line came from.
|
||||
/// </summary>
|
||||
public string nodeName = "";
|
||||
|
||||
/// <summary>
|
||||
/// The <c>#hashtags</c> present on the line.
|
||||
/// </summary>
|
||||
public string[] tags = System.Array.Empty<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the line ID indicated by any shadow tag contained in this
|
||||
/// metadata, if present.
|
||||
/// </summary>
|
||||
public string? ShadowLineSource
|
||||
{
|
||||
get
|
||||
{
|
||||
foreach (var metadataEntry in tags)
|
||||
{
|
||||
if (metadataEntry.StartsWith("shadow:"))
|
||||
{
|
||||
// This is a shadow line. Return the line ID that it's
|
||||
// shadowing.
|
||||
return "line:" + metadataEntry.Substring("shadow:".Length);
|
||||
}
|
||||
}
|
||||
|
||||
// The line had metadata, but it wasn't a shadow line.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A line provider that uses the Unity Localization system to get localized
|
||||
/// content for Yarn lines.
|
||||
/// </summary>
|
||||
public partial class UnityLocalisedLineProvider : LineProviderBehaviour
|
||||
{
|
||||
// the string table asset that has all of our (hopefully) localised
|
||||
// strings inside
|
||||
[SerializeField] internal LocalizedStringTable? stringsTable;
|
||||
[SerializeField] internal LocalizedAssetTable? assetTable;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string LocaleCode
|
||||
{
|
||||
get => LocalizationSettings.SelectedLocale.Identifier.Code;
|
||||
set
|
||||
{
|
||||
Locale? locale = LocalizationSettings.AvailableLocales.GetLocale(value);
|
||||
if (locale == null)
|
||||
{
|
||||
throw new System.InvalidOperationException($"Can't set locale to {value}: no such locale has been configured");
|
||||
}
|
||||
LocalizationSettings.SelectedLocale = locale;
|
||||
}
|
||||
}
|
||||
|
||||
private LineParser lineParser = new LineParser();
|
||||
private BuiltInMarkupReplacer builtInReplacer = new BuiltInMarkupReplacer();
|
||||
|
||||
void Awake()
|
||||
{
|
||||
lineParser.RegisterMarkerProcessor("select", builtInReplacer);
|
||||
lineParser.RegisterMarkerProcessor("plural", builtInReplacer);
|
||||
lineParser.RegisterMarkerProcessor("ordinal", builtInReplacer);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async YarnTask<LocalizedLine> GetLocalizedLineAsync(Line line, CancellationToken cancellationToken)
|
||||
{
|
||||
if (stringsTable == null || stringsTable.IsEmpty)
|
||||
{
|
||||
throw new System.InvalidOperationException($"Tried to get localised line for {line.ID}, but no string table has been set.");
|
||||
}
|
||||
|
||||
var getStringOp = LocalizationSettings.StringDatabase.GetTableEntryAsync(stringsTable.TableReference, line.ID, null, FallbackBehavior.UseFallback);
|
||||
var entry = await YarnTask.WaitForAsyncOperation(getStringOp, cancellationToken);
|
||||
|
||||
// Attempt to fetch metadata tags for this line from the string
|
||||
// table
|
||||
var metadata = entry.Entry?.SharedEntry.Metadata.GetMetadata<LineMetadata>();
|
||||
|
||||
// Get the text from the entry
|
||||
var text = entry.Entry?.LocalizedValue
|
||||
?? $"!! Error: Missing localisation for line {line.ID} in string table {entry.Table.LocaleIdentifier}";
|
||||
|
||||
string? shadowLineID = metadata?.ShadowLineSource;
|
||||
|
||||
if (shadowLineID != null)
|
||||
{
|
||||
// This line actually shadows another line. Fetch that line, and
|
||||
// use its text (but not its metadata)
|
||||
var getShadowLineOp = LocalizationSettings.StringDatabase.GetTableEntryAsync(stringsTable.TableReference, shadowLineID, null, FallbackBehavior.UseFallback);
|
||||
var shadowEntry = await YarnTask.WaitForAsyncOperation(getShadowLineOp, cancellationToken);
|
||||
if (shadowEntry.Entry == null)
|
||||
{
|
||||
Debug.LogWarning($"Line {line.ID} shadows line {shadowLineID}, but no such entry was found in the string table {stringsTable.TableReference}");
|
||||
}
|
||||
else
|
||||
{
|
||||
text = shadowEntry.Entry.LocalizedValue;
|
||||
}
|
||||
}
|
||||
|
||||
// We now have our text; parse it as markup
|
||||
var markup = lineParser.ParseString(LineParser.ExpandSubstitutions(text, line.Substitutions), this.LocaleCode);
|
||||
|
||||
// Lastly, attempt to fetch an asset for this line
|
||||
Object? asset = null;
|
||||
|
||||
if (this.assetTable != null && this.assetTable.IsEmpty == false)
|
||||
{
|
||||
// Fetch the asset for this line, if one is available.
|
||||
var loadOp = LocalizationSettings.AssetDatabase.GetLocalizedAssetAsync<Object>(assetTable.TableReference, shadowLineID ?? line.ID, null, FallbackBehavior.UseFallback);
|
||||
asset = await YarnTask.WaitForAsyncOperation(loadOp, cancellationToken);
|
||||
}
|
||||
|
||||
// Construct the localized line
|
||||
LocalizedLine localizedLine = new LocalizedLine()
|
||||
{
|
||||
Text = markup,
|
||||
TextID = line.ID,
|
||||
Substitutions = line.Substitutions,
|
||||
RawText = text,
|
||||
Metadata = metadata?.tags ?? System.Array.Empty<string>(),
|
||||
Asset = asset,
|
||||
};
|
||||
|
||||
return localizedLine;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void RegisterMarkerProcessor(string attributeName, IAttributeMarkerProcessor markerProcessor)
|
||||
{
|
||||
lineParser.RegisterMarkerProcessor(attributeName, markerProcessor);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void DeregisterMarkerProcessor(string attributeName)
|
||||
{
|
||||
lineParser.DeregisterMarkerProcessor(attributeName);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override YarnTask PrepareForLinesAsync(IEnumerable<string> lineIDs, CancellationToken cancellationToken)
|
||||
{
|
||||
// Nothing to do. If a user wants to ensure ahead of time that
|
||||
// localized content is already in memory, they should use the Unity
|
||||
// Localization preload support.
|
||||
return YarnTask.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
namespace Yarn.Unity.UnityLocalization.Editor
|
||||
{
|
||||
using System;
|
||||
using UnityEditor;
|
||||
|
||||
[CustomEditor(typeof(UnityLocalisedLineProvider))]
|
||||
internal class UnityLocalisedLineProviderEditor : UnityEditor.Editor
|
||||
{
|
||||
private SerializedProperty? stringsTableProperty;
|
||||
private SerializedProperty? assetTableProperty;
|
||||
|
||||
/// <summary>
|
||||
/// Called by Unity to draw the inspector GUI.
|
||||
/// </summary>
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
EditorGUILayout.PropertyField(stringsTableProperty);
|
||||
|
||||
var stringTableName = stringsTableProperty?.FindPropertyRelative("m_TableReference").FindPropertyRelative("m_TableCollectionName").stringValue;
|
||||
|
||||
if (string.IsNullOrEmpty(stringTableName))
|
||||
{
|
||||
EditorGUI.indentLevel += 1;
|
||||
EditorGUILayout.HelpBox("Choose a strings table to make this line provider able to deliver line text.", MessageType.Warning);
|
||||
EditorGUI.indentLevel -= 1;
|
||||
}
|
||||
EditorGUILayout.PropertyField(assetTableProperty);
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called by Unity when the editor is enabled to set up cached properties.
|
||||
/// </summary>
|
||||
protected void OnEnable()
|
||||
{
|
||||
this.stringsTableProperty = serializedObject.FindProperty(nameof(UnityLocalisedLineProvider.stringsTable));
|
||||
this.assetTableProperty = serializedObject.FindProperty(nameof(UnityLocalisedLineProvider.assetTable));
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // UNITY_EDITOR
|
||||
#endif // USE_UNITY_LOCALIZATION
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d23c0c44d94eb467780430dc536b6ea1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
Yarn Spinner is licensed to you under the terms found in the file LICENSE.md.
|
||||
*/
|
||||
|
||||
#if !USE_UNITY_LOCALIZATION
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using UnityEngine;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Yarn.Unity.UnityLocalization
|
||||
{
|
||||
/// <summary>
|
||||
/// A line provider that uses the Unity Localization system to get localized
|
||||
/// content for Yarn lines.
|
||||
/// </summary>
|
||||
public partial class UnityLocalisedLineProvider : LineProviderBehaviour
|
||||
{
|
||||
// When Unity Localization is not installed, types like TableReference
|
||||
// no longer exist, and can't be deserialized into. This causes a loss
|
||||
// of data. To get around this, we declare a new type with the same
|
||||
// shape as TableReference to keep the data in. If and when Unity
|
||||
// Localization is added to the project, the data stored in these fields
|
||||
// is deserialized into actual TableReferences.
|
||||
[System.Serializable]
|
||||
public struct PlaceholderTableReference
|
||||
{
|
||||
[System.Serializable]
|
||||
public struct PlaceholderTableIdentifier
|
||||
{
|
||||
[SerializeField] private string m_TableCollectionName;
|
||||
}
|
||||
|
||||
[SerializeField] private PlaceholderTableIdentifier m_TableReference;
|
||||
}
|
||||
|
||||
[SerializeField] internal PlaceholderTableReference? stringsTable;
|
||||
[SerializeField] internal PlaceholderTableReference? assetTable;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string LocaleCode { get => "error"; set { } }
|
||||
|
||||
private const string NotInstalledError = nameof(UnityLocalisedLineProvider) + "requires that the Unity Localization package is installed in the project. To fix this, install Unity Localization.";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override YarnTask PrepareForLinesAsync(IEnumerable<string> lineIDs, CancellationToken cancellationToken)
|
||||
{
|
||||
Debug.LogError(NotInstalledError);
|
||||
return YarnTask.CompletedTask;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Start()
|
||||
{
|
||||
Debug.LogError(NotInstalledError);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override YarnTask<LocalizedLine> GetLocalizedLineAsync(Yarn.Line line, CancellationToken cancellationToken)
|
||||
{
|
||||
Debug.LogError($"{nameof(UnityLocalisedLineProvider)}: Can't create a localised line for ID {line.ID} because the Unity Localization package is not installed in this project. To fix this, install Unity Localization.");
|
||||
|
||||
return YarnTask.FromResult(new LocalizedLine()
|
||||
{
|
||||
TextID = line.ID,
|
||||
RawText = $"{line.ID}: Unable to create a localised line, because the Unity Localization package is not installed in this project.",
|
||||
Substitutions = line.Substitutions,
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void RegisterMarkerProcessor(string attributeName, Markup.IAttributeMarkerProcessor markerProcessor)
|
||||
{
|
||||
Debug.LogWarning($"Unable to add a marker processor for {attributeName}, as the Unity Localization package is not installed in this project");
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void DeregisterMarkerProcessor(string attributeName)
|
||||
{
|
||||
Debug.LogWarning($"Unable to remove a marker processor for {attributeName}, as the Unity Localization package is not installed in this project");
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
namespace Editor
|
||||
{
|
||||
using UnityEditor;
|
||||
[CustomEditor(typeof(UnityLocalisedLineProvider))]
|
||||
public class UnityLocalisedLineProviderPlaceholderEditor : Editor
|
||||
{
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
EditorGUILayout.HelpBox("Unity Localization is not installed.", MessageType.Warning);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0dcd24d82774e4ea1b71ab83bdf69c3a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
Yarn Spinner is licensed to you under the terms found in the file LICENSE.md.
|
||||
*/
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Yarn.Unity.UnityLocalization
|
||||
{
|
||||
// This partial declaration exists to ensure that Unity is able to work with
|
||||
// this class, because MonoBehaviour types are required to be defined in a
|
||||
// file whose name matches the type's name. The actual definition of
|
||||
// UnityLocalisedLineProvider is found in either
|
||||
// UnityLocalisedLineProvider.Installed.cs (if Unity Localization is
|
||||
// installed), or UnityLocalisedLineProvider.NotInstalled.cs (if not).
|
||||
public sealed partial class UnityLocalisedLineProvider : LineProviderBehaviour { }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f9c1f487b798d402db42bc11cee5b810
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user