Backend schema field xy to provide translated options but saving just token, no translations

Nearest solution so far, but saving labels, not tokens:


    informationtype = schema.List(
        title=_("Informationstyp"),
        # value_type=schema.Choice(vocabulary="elasticsearchblocks.informationtype"),
        required=False,
        value_type=schema.TextLine(
            title="Single informationtype",
            required=False,
        ),
    )
    directives.widget(
        "informationtype",
        vocabulary="elasticsearchblocks.informationtype",
        frontendOptions={
            "widget": "token",
        },
    )

Another (failing) approach, but saving terms (dicts with token and title, delivered by vocabulary [1])

    informationtype = schema.List(
        title=_("Informationstyp"),
        value_type=schema.Choice(vocabulary="elasticsearchblocks.informationtype"),
        required=False,
    )

[1]

 return SimpleVocabulary.fromItems(
        [[item["token"], item["token"], item["titles"][lang]] for item in items]
    )
1 Like

FTR, a vocabulary term consists out of three pieces of information:

value - this is what gets stored
token - this is what is used in web-forms (b/c value might be something not suitable as HTML form-field name)
title - this is whats get displayed

see zope.schema/vocabulary.py at 89f09bb970bc14f81b724e81174aaaedb8853c3a · zopefoundation/zope.schema · GitHub

To translate the title there are two options:

  1. use an i18n.messageid instance as title (only works for static vocabs well unless you introduce dynamic i18n translation-domains, which is a different topic and too complex for what you want to achieve here, if curious look at collective.vdexvocabulary/treevocabulary.py at 6407816c0d1418f9b03ab5e6d0d0855b4b78542f · collective/collective.vdexvocabulary · GitHub)

  2. use a vocabulary factory to create the vocabulary every time it is used.

For (2) you need to register a function or other callable as component like so i.e. in vocabularies.py:

from zope.schema.interfaces import IVocabularyFactory
from zope.schema.vocabulary import SimpleVocabulary
from zope.interface import provider

# define items= here or create from whatever is needed

@provider(IVocabularyFactory)
def my_vocabulary_factory(context, query=None):
    lang = context.Language # or whatever is needed to get the current language
    return SimpleVocabulary.fromItems(
        [[item["token"], item["token"], item["titles"][lang]] for item in items]
    )

snippet from configure.zcml

  <utility
    component=".vocabularies.my_vocabulary_factory"
    name="my.vocabulary"
    />

and now you can use it in the schema as any named vocabulary

    somefield = schema.List(
        title=_("Some Name"),
        value_type=schema.Choice(vocabulary="my.vocabulary"),
    )

A solution is simpler than I thought.

    informationtype = schema.List(
        title=_("Informationstyp"),
        required=False,
        value_type=schema.Choice(vocabulary="elasticsearchblocks.informationtype"),
    )

which gets rendered by default as ArrayWidget, which saves the token (only the token!) as expected.
My confiusion yesterday at the end of the day was, that the prop content does include not only the token but {"token": term.token, "title": term.title} because the ChoiceFieldSerializer is so kind to provide {"token": term.token, "title": term.title}.
And with an appropriate vocabulary you have with the title the label according the users language for the select options. Should have known better after writing Vocabularies, Registry-Settings and Control Panels :wink:

1 Like