Tuesday 1 June 2021

Sitecore - Create first item version in default language

Recently, we had a request from client wherein they asked us to force the content authors to create items in English version first, if the item doesn't have one. In short, content authors should create item in English version first, than create versions in different languages.

The issue was, accidentally content authors were creating items in different language versions rather than the default English language which is the Fallback language. This was happening mainly on the Media Items where they were creating Media Items like images,PDFs in non-English language due to which they were facing content related issues where image was displaying on X language version page but not on Y language version page.

To Solve the issue, we decided to create a custom validator.

Validator checks the newly created item whether it has the default language version (i.e en).

If Yes, allow content authors to create the language version.

If No, show the validation message.


    [Serializable]
    public class CreateDefaultItemVersion : StandardValidator
    {
        public CreateDefaultItemVersion() { }

        public CreateDefaultItemVersion(SerializationInfo info, StreamingContext context) : base(info, context) { }

        protected override ValidatorResult Evaluate()
        {
            Item item = this.GetItem();
            if (item == null)
            {
                return ValidatorResult.Valid;
            }
            string name = item.Name;

            if (item.Language.Name.Equals("en"))
            {
                return ValidatorResult.Valid;
            }
            else
            {
                Language language = Language.Parse("en");
                var currentItem = Factory.GetDatabase("master").GetItem(item.Paths.FullPath, language);
                if (currentItem?.Versions?.Count > 0)
                {
                    return ValidatorResult.Valid;
                }
                else
                {
                    base.Text = this.GetText(Translate.Text($"The item name `{name}` doesn't have a default English Langauge version. Please create English Version first."));
                    return base.GetFailedResult(ValidatorResult.CriticalError);
                }
            }
        }

        protected override ValidatorResult GetMaxValidatorResult()
        {
            return this.GetFailedResult(ValidatorResult.Error);
        }

        public override string Name => "Always Create Items in English Version First";
    }

 

Once the code is added to the solution, 

Create a validator item under the path /sitecore/system/Settings/Validation Rules. Provide the custom validator class name and the assembly of the class we created and apply the Validation rule.

That's it !!!

 


 

Wednesday 10 February 2021

Integrate Kakao API with Sitecore

Hello Sitecore Developers,

We recently came across a requirement where client wanted to use Kakao API to get Latitude and Longitude for South Korean Addresses in Sitecore.

Kakao is an API service specifically used in South Korea which provide similar kind of services as Google or Bing maps.

To Learn more about Kakao:

https://developers.kakao.com/

We need to create an App to use the functionalities of Kakao. Since we are using Sitecore we would be using Web platform in Kakao app which provides Javascript/Rest API support. Once the App is created it would provide with an API key which we can use in our code to use Kakao services.

Below article provides information on how to create an app in Kakao

https://developers.kakao.com/docs/latest/ko/getting-started/app                                          

We were having fields like Address, City, State, Postal Code etc. typically used to get the Latitude and Longitude values.

But the challenge with Kakao API is the Language of the address to pass, it only allows Korean address for conversion.

Content Items in our system were having Shared fields and had addresses in English Language only.

We decided to use google translate api to send the English addresses and convert them to Korean and pass it through Kakao to get the Lat and Long values.

Here is the link to setup Google Translate API.

https://cloud.google.com/translate/docs

 

Code:

We have integrated Kakao Api with Data Exchange Framework where our custom pipeline executes and collects data from external system and create items in Sitecore. We pass Address, City, StateOrProvince, Country etc. to

Google Translate -> Translate the Address values to Korean Language - > Sends the Korean address to Kakao -> Sends Lat and Long values in response and update item fields.

 

public async Task<Geolocation> GetLatLongfromAddressViaKakaoAsync(string address, string city, string stateOrProvince, string country)

        {

            Geolocation latlong = null;

            double propertyLatidude = 0.0;

            double propertyLongitude = 0.0;

            var addressQuery = string.Join(" ", new[] { address, city, stateOrProvince, country }.Where(s => !string.IsNullOrEmpty(s)));

            if (string.IsNullOrEmpty(addressQuery))

            {

                Log.Warn($"[{GetType().FullName}.{nameof(GetLatLongfromAddressViaKakaoAsync)}] Could not get geolocation data because no address parameters were populated", this);

            }

            else

            {

                var cleanAddress = Regex.Replace(addressQuery, @"[^0-9a-zA-Z-:, ]+", "");

                string koreanAddress =TranslateAddressToKoreanViaGoogleTranslate(cleanAddress);

 

                if (string.IsNullOrEmpty(koreanAddress))

                {

          Log.Warn($"[{GetType().FullName}.{nameof(GetLatLongfromAddressViaKakaoAsync)}] Could not get Korean Address Via Google Translate API", this);

                }

                else

                {

                    HttpWebRequest request = KakaoRequest(koreanAddress);

 

                    var content = string.Empty;

 

                    using (var response = (HttpWebResponse)request?.GetResponse())

                    {

                        if (response?.StatusCode == HttpStatusCode.OK)

                        {

                            using (var stream = response?.GetResponseStream())

                            {

                                if (stream != null && stream != Stream.Null)

                                {

                                    using (var sr = new StreamReader(stream))

                                    {

                                        content = await sr?.ReadToEndAsync();

                                        var jsonData = JsonConvert.DeserializeObject<KakaoGeoCodeResults>(content);

                                        if (jsonData?.documents != null && jsonData?.documents?.Count > 0)

                                        {

                                            double.TryParse(jsonData?.documents?.FirstOrDefault()?.y, out propertyLatidude);

                                            double.TryParse(jsonData?.documents?.FirstOrDefault()?.x, out propertyLongitude);

                                            latlong = new Geolocation

                                            {

                                                Latitude = propertyLatidude,

                                                Longitude = propertyLongitude

                                            };

                                        }

                                        else

                                        {

                                            Log.Warn($"[{GetType().FullName}.{nameof(GetLatLongfromAddressViaKakaoAsync)}] Could not get geolocation data for address query via Kakao Geocoding service for: English : {addressQuery} - Korea: {koreanAddress}", this);

                                        }

                                        sr?.Dispose();

                                    }

                                }

                                else

                                {

                                    Log.Warn($"[{GetType().FullName}.{nameof(GetLatLongfromAddressViaKakaoAsync)}] Response Stream is null from Kakao Response for: English : {addressQuery} - Korea: {koreanAddress}", this);

                                }

                            }

                        }

                        else

                        {

                            Log.Error($"[{GetType().FullName}.{nameof(GetLatLongfromAddressViaKakaoAsync)}] Error(s) received from Kakao Geocode Provider for: English : {addressQuery} - Korea: {koreanAddress} having Errors : {response?.StatusDescription}", this);

                        }

                        response?.Dispose();

                    }

                }

            }

            return latlong;

        }

private HttpWebRequest KakaoRequest(string koreaAddress)

        {

            var request = (HttpWebRequest)WebRequest.Create("https://dapi.kakao.com//v2/local/search/address.json?query=" + koreaAddress);

            request.Headers["Authorization"] = "KakaoAK " + KakaoApiKey;

            request.Method = "GET";

            request.ContentType = "application/json;charset=UTF-8";

            return request;

        }

        private string TranslateAddressToKoreanViaGoogleTranslate(string address)

        {

            var url = "https://translation.googleapis.com/language/translate/v2?key=" + GoogleApiKey + "&source=en&target=ko&q=" + address;

            using (var client = new WebClient())

            {

                client.Encoding = Encoding.UTF8;

                var translatedData = client?.DownloadString(url);

                if (string.IsNullOrEmpty(translatedData))

                    return null;

                var jsonTranslatedData = JsonConvert.DeserializeObject<GoogleTranslateData>(translatedData);

                return jsonTranslatedData?.Data?.Translations?.FirstOrDefault()?.TranslatedText;

            }

        }

 

Below are the model classes used to get response from Google Translate and kakao Rest API

 

public class GoogleTranslationsList

    {

        public List<GoogleTranslateText> Translations { get; set; }

    }

public class GoogleTranslateText

    {

        public string TranslatedText { get; set; }

    }

public class GoogleTranslateText

    {

        public string TranslatedText { get; set; }

    }

 

public class KakaoDocument

    {

        public string x { get; set; }

        public string y { get; set; }

    }

public class KakaoDocument

    {

        public string x { get; set; }

        public string y { get; set; }

    }

public class KakaoDocument

    {

        public string x { get; set; }

        public string y { get; set; }

    }