Skip to content

Filename based translations #392

@freekh

Description

@freekh

What

Filename based translations are val module files that have the same schema, but localized content. A cool thing with this setup is that it is easier to read (because size is smaller) and find content (i.e. it is very familiar).
It should work on small objects scattered around, but also for larger things (e.g. entire pages).
Depending on what the requirements for the schema of the different locale files ends up being, we can also imagine that it is possible to for example have different keyOf schemas per locale so each of the locales could refer to a module of a specfic locale (i.e. the same or another).

Example

Given these files:

  • /foo.en_us.val.ts
  • /foo.fr_fr.val.ts

We could imagine that /foo.val.ts contains the schema (though that could be only by convention - meaning it is not a strict requirement).

Example of content of a localized val module:

 // schema would be defined in the /foo.val.ts file
// added here just for brevity
const schema = s.record(s.object({
  title: s.string(),
  locale: s.locale("en_us", "fr_fr") // see discussion 's.locale or not?' below
}))
export default c.define("/foo.en_us.val.ts", schema, {
  "hello": {
     title: "hello world",
     locale: "en_us"
   }
})

Questions

s.locale or not?

If we have a s.locale(), we think it must always be equal to the locale defined in the module file path.
Then the questions are:

  1. why would we even need it since the caller of the module file path would know that the locale is always there?
  2. should it make it possible to enforce a set of locales, e.g. you define s.locale("en_us", "fr_fr") one place, then everywhere this locale is used, we enforce that all locales are properly defined.

What to do with keyOf?

Current thinking is that we should support different keyOf (so keyOf are different per locale), but that the schema of the keyOf modules must all be the same. We're thinking it will be extremly confusing if we allow any keyOf what so ever. We're thinking that this is is enforced through validation errors. We could do this on the type-level, but we are not sure if that will improve dev ex. We might also wait with the validation errors, simply document it, and then add a validation error later (if it is difficult to do).

Example:

// /foo.en_us.val.ts
import otherVal from "./otherVal.en_us.val.ts"
export default c.define("/foo.en_us.val.ts", myCreateSchemaFunction(otherVal), {
  theKey: "test123"
})

Where the schema file is:

// /foo.val.ts

export function myCreateSchemaFunction(valModule: ValModule) {
  return s.object({
    theKey: s.keyOf(valModule)
  })
}

Is localization a sub-problem of "partitions"?

Divding an app up into locales could be viewed as a specific solution to a more general problem: dividing UI and an app into partitions - any type of partition. Partitions could for example be countries (which in turn have different locales).
We might want to think a bit about that use case before committing to the format of the filenames.
Maybe "partitions" should just be a filter in the UI - and we do not need anything in the filename to indicate a partion?
Or maybe we're stupid in even entertaining this line of thought and the next thing we'll think is a good idea is a content operating system or some stupid shit like that!

More examples

Page router with locale segment and default locales

Content files:

  • /app/[locale]/[[...path]]/page.en_us.val.ts
  • /app/[locale]/[[...path]]/page.fr_fr.val.ts

Schema in /app/[locale]/[[...path]]/page.val.ts:

export const schema = s.record().router(nextAppRouter({
  segments: {
     locale: s.locale("en_us", "fr_fr"), //  we could also use an extend version of locale to take objects so it is possible to override the actual segment mapping. E.g. s.locale({ locale: "en_us", value: "main" }, "fr_fr") means the locale "en_us" will have the value "main" so when the segment "main" is hit, we serve the en_us locale.
     path: s.array(s.string()) // no validation, optional to define this
  }
}))

Example content file:

import { schema } from "./page.val.ts"
export default c.define( "/app/[locale]/[[...path]]/page.en_us.val.ts", schema, {
  "/en_us/foo/bar": { // or "/main/foo/bar" if s.locale({ locale: "en_us", value: "main" }, "fr_fr")
    title: "Howdy!"
  }
})

Page router with locale segment and custom locales

As an alternative to Page router with locale segment and default locales we could imagine that each locale file has a different segment in each schema.

Content files:

  • /app/[locale]/[[...path]]/page.en_us.val.ts
  • /app/[locale]/[[...path]]/page.fr_fr.val.ts

Schema file: /app/[locale]/[[...path]]/page.val.ts:

export const schema = s.record(s.object({ title: s.string() })).router(nextAppRouter())

Then in /app/[locale]/[[...path]]/page.en_us.val.ts:

import { schema } from "./page.val.ts"
export default c.define( "/app/[locale]/[[...path]]/page.en_us.val.ts", schema.segments({ 
  locale: s.literal("main") // the locale segment is main for en_us
}), {
  "/main/foo/bar": {
    title: "Howdy!"
  }
})

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status

    Todo

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions