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
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]
)
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