Add a Translation Delivery API #15214
Replies: 5 comments 4 replies
-
Hey! I had the idea that it could use a Start-Item/key argument and then optionally a depth or expand parameter following the current delivery API structure. That would allow for most use cases I think. In this way, we allow for all kinds of structures of dictionary, you could have everything in the root and you could structure it per site, or per function as per your use case. My simple use case would be to internationalize a Next.js/Gatsby statically generated site, and being able to fetch a KV json-object would make using i18next (https://www.i18next.com/) or other internationalization standards very easy. Maybe we could use an Accept header to determine what format to return. If the api is sent Accept: text/plain you can just return the value straight up. |
Beta Was this translation helpful? Give feedback.
-
A little update on my journey, I did end up creating an index for dictionary keys for my own use, and it seems to work well. In my personal use case, I don't need the parent nodes, just a flat structure in the index was fine for me, but you could easily save the parent key as well. |
Beta Was this translation helpful? Give feedback.
-
Is there any news about this? Is it on roadmap, this would be great addition to Content API. |
Beta Was this translation helpful? Give feedback.
-
Just stumbled upon this thread when searching for a way to deliver my Umbraco dictionary translations. Isn't this possible now as part of the Umbraco Management API? |
Beta Was this translation helpful? Give feedback.
-
For Umbraco 13, we just created a simple endpoint for our headless environments which works directly of the ILocalizationService. It's probably not as efficient, but we cache the output in the frontend for a while. I thought I'd post it here for completeness and maybe to inspire others. There is a little custom stuff in here (like IApiHelperService and GenerateInternalServerErrorResponse), but it's minor. The CoreApiController is custom, it's just inherits the default UmbracoApiController, so use that. /// <summary>
/// Api controller that can be used to retrieve dictionary information
/// </summary>
/// <param name="logger"><inheritdoc cref="ILogger{DictionaryApiV1Controller}"/></param>
/// <param name="localizationService"><inheritdoc cref="ILocalizationService"/></param>
/// <param name="deliveryApiSettings">API Delivery settings from Umbraco</param>
/// <param name="apiHelper"><inheritdoc cref="IApiHelperService"/></param>
[Route("api/v{version:apiVersion}/dictionary")]
[ApiVersion("1.0")]
[MapToApi(DictionaryApiConstants.ApiName)]
[ApiController]
public class DictionaryApiV1Controller(
ILocalizationService localizationService,
IOptions<DeliveryApiSettings> deliveryApiSettings,
IApiHelperService apiHelper,
ILogger<DictionaryApiV1Controller> logger)
: CoreApiController(logger, deliveryApiSettings, apiHelper)
{
///<summary>
/// Gets the translation for one dictionary item by key
/// </summary>
/// <returns>Translation, the key if no translation is present or badrequest if not dictionaryitem is available for the key</returns>
[HttpGet]
[Route("gettranslationbydictionaryitemkey")]
[ProducesResponseType(typeof(TranslationModel), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public IActionResult GetTranslationByDictionaryItemKey(string key)
{
try
{
var culture = GetLanguageKey();
if (culture == null)
return GenerateBadRequestResponse("No culture is available.");
var dictionaryItem = localizationService.GetDictionaryItemByKey(key);
if (dictionaryItem == null)
return GenerateBadRequestResponse($"Localization service get dictionary item by key returned null for key: {key}.");
var translationModel = GetTranslationForDictionaryItem(dictionaryItem, culture);
var serializedTranslationsModels = JsonSerializer.Serialize(translationModel);
return Ok(serializedTranslationsModels);
}
catch (Exception exception)
{
var message = "Caught error while getting translations for dictionary";
Logger.LogError(exception, message);
return GenerateInternalServerErrorResponse(message);
}
}
///<summary>
/// Gets the translations for multiple dictionary items by keys
/// </summary>
/// <returns>The keys with the translations, if a translation is not available the key is dropped and logged</returns>
[HttpGet]
[Route("gettranslationsbydictionaryitemkeys")]
[ProducesResponseType(typeof(IEnumerable<TranslationModel>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public IActionResult GetTranslationsByDictionaryItemKeys([FromQuery]string[] keys)
{
try
{
var culture = GetLanguageKey();
if (culture == null)
return GenerateBadRequestResponse("No culture is available.");
var availableKeys = keys.Where(key =>
{
if (localizationService.DictionaryItemExists(key))
return true;
else
{
Logger.LogDebug("Localization service check dictionary item by key returned false for key: {key}.", key);
return false;
}
}).ToArray();
var dictionaryItems = localizationService.GetDictionaryItemsByKeys(availableKeys);
var translationModels = dictionaryItems.Select(dictionaryItem => GetTranslationForDictionaryItem(dictionaryItem, culture));
var serializedTranslationsModels = JsonSerializer.Serialize(translationModels);
return Ok(serializedTranslationsModels);
}
catch (Exception exception)
{
var message = "Caught error while getting translations for dictionary";
Logger.LogError(exception, message);
return GenerateInternalServerErrorResponse(message);
}
}
///<summary>
///Get all dictionary items
///</summary>
///<returns>All dictionary items</returns>
[HttpGet]
[Route("getdictionarytranslations")]
[ProducesResponseType(typeof(IEnumerable<TranslationModel>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public IActionResult GetDictionaryTranslations()
{
try
{
var culture = GetLanguageKey();
if (culture == null)
return GenerateBadRequestResponse("No culture is available.");
var dictionaryItems = localizationService.GetDictionaryItemDescendants(null);
var translationModels = dictionaryItems.Select(dictionaryItem => GetTranslationForDictionaryItem(dictionaryItem, culture));
var serializedTranslationsModels = JsonSerializer.Serialize(translationModels);
return Ok(serializedTranslationsModels);
}
catch (Exception exception)
{
var message = "Caught error while getting translations for dictionary";
Logger.LogError(exception, message);
return GenerateInternalServerErrorResponse(message);
}
}
/// <summary>
/// Gets the translation model for a dictionary item for the given culture
/// </summary>
/// <param name="dictionaryItem">dictionaryitem of which the translation is required</param>
/// <param name="culture">language to which is translated. format: ISO (nl-NL)</param>
/// <returns> translation model</returns>
private static TranslationModel GetTranslationForDictionaryItem(IDictionaryItem dictionaryItem, string culture)
{
return new TranslationModel
{
Id = dictionaryItem.Key,
Key = dictionaryItem.ItemKey,
Value = dictionaryItem.Translations.FirstOrDefault(translation => translation.LanguageIsoCode.Equals(culture, StringComparison.OrdinalIgnoreCase))?.Value ?? dictionaryItem.ItemKey
};
}
} |
Beta Was this translation helpful? Give feedback.
-
Add a Translation Delivery API to the core
Currently there is no core Delivery APIs to distribute translations (dictionary items). It seems a reasonable thing to include in the core offering.
Credits go to @ToxicKevinFerm for the original proposal in #15132
Things to consider
Challenges
LocalizationService
utilizesDictionaryRepository
which is uncached at this point. It is not an option to expose uncached data in a potentially public API, as this would represent an easy attack vector for malicious users.Known limitations
Beta Was this translation helpful? Give feedback.
All reactions