ChristophP / elm-i18next

https://package.elm-lang.org/packages/ChristophP/elm-i18next/latest
BSD 3-Clause "New" or "Revised" License
67 stars 13 forks source link

YAML support? #35

Closed adrianbunea closed 2 years ago

adrianbunea commented 2 years ago

Hi. I'm curious how I could use this package if the translations were provided as YAML? I think there would be a few ways and I don't know which is the best.

On the user side:

On the package side:

ChristophP commented 2 years ago

Hi there, thanks for opening this issue.

Hmm, YAML format. That's quite new use case. Originally this lib was created around the need for using JSON translations files.

Your the first one who's aksed about YAML but I can see how yaml is a nice format and I'm willing to consider adding this if it makes sense overall and doesn't bloat this lib too much.

Here's some thoughts in my head

adrianbunea commented 2 years ago

Unfortunately I cannot attach an yml file, Github won't let me. here is how it looks though:

en:
  questions:
    food:
      favorite_food: 'What is your favorite food?'
      hungry: 'Are you hungry?'
      when_is_dinner: 'When is dinner?'
      reuse_example1: &reuse 'This can be reused in other places'
      reuse_example2: *reuse

YAML allows reusing translations using & and *, according to this https://stackoverflow.com/questions/8466223/reuse-a-block-of-code-in-yaml

I was wondering if there's a more generic to provide a tree decoder that let's the user pass decoders for strings and objects respectively but not make the user implement the entire tree decoder logic. Does this make sense to you?

I think I understand your point, I was thinking of a way for your main decoder to allow providing smaller decoders for its primitives: string, int, List, Dict etc. and that way I could provide the decoders from the YAML library to your main decoder.

ChristophP commented 2 years ago

Thanks for sharing the yaml example.

Yeah, what you said about passing primitive decoders is pretty close to what I meant. Expect the primitives can only be for dict/object or string. That's how the Tree is defined https://package.elm-lang.org/packages/ChristophP/elm-i18next/latest/I18Next#Tree

I checked the only yaml package in elm that I know https://github.com/terezka/yaml. I think a string would be easily doable but I couldn't find a dict or keyValuePairs decoder in the package. Although there are two PRs open that aim to add those. do you have an idea about how you would parse your yaml into a tree structure consisting of key-value pairs and strings (on the leaves of the tree) with the functions that are currently supported by that package or another yaml package you may know of?

adrianbunea commented 2 years ago

The YAML library you posted was the only one I knew about until I looked around. This one looks dead, but one of the guys that posted a PR published their own fork, here and it has more tools at its disposal. Using it I made this Ellie app to try the decoding capabilities. P.S. To my absolute amazement, the references are also being decoded correctly :exploding_head:

ChristophP commented 2 years ago

Ah, nice that someone forked and extended it. Also cool it parses thoses references correctly.

Alright then I will give it a try to expose a generic decoder that can work with the yaml string and dict decoders.

Will let you when I got some results.

adrianbunea commented 2 years ago

Hi, any news on this?

ChristophP commented 2 years ago

Hi @adrianbunea , I'm sorry I was busy with other stuff. Thanks for reminding me. Will look into it tonight. Will post a status in the next few days as soon as I got something.

ChristophP commented 2 years ago

@adrianbunea I've thought about this for a while now and also got stuck a few times. Here are the options I tried to explore. I'd like to get your feedback on this.

  1. I tried writin a generic decoder like we discussed before, but I couldn't figure out a way to do it. I tried using elm/json for it but I quickly found that that would fail when decoding anything other than JSON.
  2. I then thought I could make it more generic by moving away from JSON and elm/json by using Strings and elm/parser. But when I looked at https://package.elm-lang.org/packages/MaybeJustJames/yaml/latest/Yaml-Decode I noticed they also use their own Decode type, so Parser is not a common denominator between ChristophP/elm-i18next and that package.
  3. I kind of found myself back at square one where I thought it would be easiest to just have the user build a YAML parser/decoder and convert those to Translations. But you already mentioned that having to do that, would take away much of the usefulness of this library and I pretty much agree.
  4. I could make that YAML lib a dependency of this library but I am kind of hesitant to add a hard dependency like this, since you are the first one to have asked for YAML support yet.
  5. With my thoughts from 3. I'm wondering what a YAML decoder would actually look like and came up with something like this:
    yamlTreeDecoder : Yaml.Decoder I18Next.Tree
    yamlTreeDecoder =
    Yaml.oneOf
    [ Yam.string |> Yaml.map I18Next.string
    , Yaml.lazy |>
      (\_ -> Yaml.dict yamlTreeDecoder |> Yaml.map I18Next.object)
    ]

    This is very similar to the json implementation in this lib https://github.com/ChristophP/elm-i18next/blob/4.2.0/src/I18Next.elm#L153-L159 and only a couple of lines. The decoder yields a I18Next.Tree value. Now this is just one step away from being able to be converted to a full Translations value.

yamlTranslationsDecoder : Yaml.Decoder I18Next.Translations
yamlTranlsationsDecoder =
   Yaml.dict yamlTreeDecoder |> Yaml.map (Dict.toList >> I18Next.fromTree)

What do you think of that? Would that be a viable solution? I would even consider adding documentation on how to do this and exposing another variant of the fromTree function to reduce the last step to this:

yamlTranslationsDecoder : Yaml.Decoder I18Next.Translations
yamlTranlsationsDecoder =
   Yaml.dict yamlTreeDecoder |> Yaml.map I18Next.fromDictTree

What are you thoughts on the points and the proposed solution? If you have any ideas that would make a generic decoder possible after all please let me know.

adrianbunea commented 2 years ago

So, you propose to have a recipe for the YAML formats documented in the README? It sounds like an acceptable solution for now. I was thinking the problem was the library's coupling to the Elm Json Decode library, but it seems to be a core library of Elm and a sensible default so I can't really complain. These being said, point 3 is still not completely satisfied, some of the useful stuff provided by the library are left unused like this, but this might be the sacrifice that needs to be made.

If this is what you meant, then I think it would be useful to have recipes for other formats other than JSON, and people could add to the list as other formats need support.

With a change in perspective, I could see elm-i18-next as a format agnostic library, with the main utility being the I18Next.string, I18Next.object and I18Next.fromTree to create any Decoder necessary. The JSON built-in decoder would just be a sensible default provided by the library.

As a side note, I am still considering a possible solution to provide a configuration to the main Decoder, in the form of a Record with fields not just for the primitives, but for the map, lazy and oneOf functions as well. And the library would just use those. Of course, this might prove impossible once we actually try it. I am also curious what you would think of a Fork of this Repo that handles YAML instead of JSON instead?

ChristophP commented 2 years ago

Hi @adrianbunea ,

If this is what you meant, then I think it would be useful to have recipes for other formats other than JSON, and people could add to the list as other formats need support.

Yes something like a recipe is pretty much what I had in mind.

As a side note, I am still considering a possible solution to provide a configuration to the main Decoder, in the form of a Record with fields not just for the primitives, but for the map, lazy and oneOf functions as well. And the library would just use those. Of course, this might prove impossible once we actually try it.

Yes, let me know please if you find a way to make that work. My attempts didn'y go anywhere because I couldn't find a way to make Decoder part generic in Decoder something. Not sure if that's even possible in Elm.

I am also curious what you would think of a Fork of this Repo that handles YAML instead of JSON instead?

I that helps you that would definitely be an option. However, forking the entire package might not even be necessary. For example maybe it would just be enought to write a package which provides a decoder which turns YAML into Translations. All the other functions in this lib could be reused. What do you think of that?

adrianbunea commented 2 years ago

Recipes, or another package that adds the extra functionality would both work for me, I think the extra package might be a cleaner solution. But I cannot tell you which direction to take the package you created, in the end it is your call, all I can do is give my opinion, that I lean toward the extra package that people can opt for if they need to.

ChristophP commented 2 years ago

I'm closing this one. I think the best way would be to have a separate package that exposes the Yaml decoder by whoever wants to maintain it.