monorepo: [api spec] uniqueness problem in query. aka massive ecosystem breaking change in the future

Problem

The combination of id - languageTag does not entail uniquely identifying a message if variant (or other additions) are introduced, breaking query operations.

https://github.com/inlang/inlang/assets/35429197/697757b1-4b58-4010-85e2-2b875b4ee6c2

Proposals

Proposal 1 - add variant

Introduce a variant: "default" property on Message without introducing the feature yet.

  • we must be certain that “variants” will highly likely come
  • we must be certain that variants will be the only addition for uniqueness

Proposal 2 - do nothing

We could ignore the problem at the risk of a massive ecosystem-breaking change.

  • query is passed to plugins, every plugin needs to be updated to include new query API
  • every app needs to be updated

off topic

This is why we should use messages.query* that can be succeeded by messages.query2 in the worst case, see https://github.com/inlang/inlang/pull/1157#discussion_r1270761121

Pro

  • not a breaking change (compared to proposal 2)

Con

  • it will take time until variant is supported (variants could stay unlinted for in the meantime/ignored by plugins). Hence not as “good” as proposal 1.

About this issue

  • Original URL
  • State: closed
  • Created a year ago
  • Comments: 38 (38 by maintainers)

Most upvoted comments

Guys, hop on a call?

We gotta make a decision. Too much back and forth.

Following proposal 3 by @felixhaeberle, the logical follow-up is to make language tags belong to a message too.

type Message = {
  id: string
  languageTags: LanguageTag[]
  variants: string[]
  patterns: Record<LanguageTag, Record<Variant, Pattern>>

A query has no uniqueness problem anymore because a message is always uniquely identified by an id. But, the API becomes a bit more cumbersome.

const message = query.get({ where: { id: "first-message" }})
for (const tag of message.languageTags){
  for (const variant of message.variants) {
    const pattern = message.patterns[tag][variant]
    if (pattern === undefined){
      // do something
    }
    console.log(pattern)
  }
  console.log(message.pattern[tag]["default"))
}

On the flip side, adding additional properties to a Message makes sense then.

type Message = {
  id: string
+  description: string
  languageTags: LanguageTag[]
  variants: string[]
  patterns: Record<LanguageTag, Record<Variant, Pattern>>

Dear Ladies and Gentlemen, we are starting to model relations in JS objects 😄

Pros

  • close to modeling a relational schema (pretty future-proof)

Cons

  • complexer query
  • complexer writing plugins

proposal 1

I agree, comparing to resources yes, but comparing to a message array no.

To get that right. It is not getting simpler. Not for plugins or apps. But we can get the advantage of extendability/flexibility.

one could argue simpler because:

  • mental model around one message makes more sense
  • message architecture in plugins is easier than around a resource
  • for now, speaking for me, this issue is about a data structure and how to query it, not about the complexity in a plugin/apps – this can be discussed in another issue
  • we gain flexibility/extendability at no cost in terms of uniqueness

offtopic @samuelstroschein: (“complexer” -> do you have your Grammarly on? its “more complex to”) 😄

Proposal 1:

type Message = {
  id: string
  languageTag: LanguageTag
  variant: Variant
  pattern: Array<Text | Placeholder>
}

Proposal 2:

type Message = {
  id: string
  languageTag: LanguageTag
  pattern: Record<Variant, Pattern> 
}

Proposal 3:

type Message = {
  id: string
  languageTag: LanguageTag
  body: Array<MessageVariant>
}

type MessageVariant = {
  id: string | "default"
  pattern: Array<Text | Placeholder>
}

@felixhaeberle, that should be proposal 3, then?

offtopic

A pattern defines how a “text” is rendered to the user

Do you mean “rendered” in terms of UI? We should not structure our data in a way it’s nice for the UI, but rather how it makes sense from a data perspective.

Yes, rendered to the UI. Yes, that’s the way to store text. We basically have text nodes that need to be serialized. Store them as nodes [in a pattern]. That’s how text formatting works 😃

PR will follow on Monday is this gets considered.

@NilsJacobsen Atm, we only have one body/pattern to a message where we nest Text / Placeholder.

I could image that we do not directly nest message.pattern.text, but message.body["variant"].text - body is here interchanged with pattern because it probably makes sense to have pattern in variant again so it’s then message.body["variant].pattern.text

please don’t nail me on the api if we should go with array or object for body - I’m not sure.