1. Customization
  2. Fieldsets and i18n

Customization

Fieldsets and i18n

Fieldsets give you the opportunity to gather a set of defined attributes in a single field of a type

TIP

Please read about schema extensions first.

The fieldset directive and type

The Fieldset type and the @fieldset directive give you the possibility to define a field on a type that gathers a certain set of fields of that same type. This gives you the opportunity to collect i.e. your users custom attributes in one set. This comes in especially handy for client applications, that do not know about the custom fields of a user. With the help of fieldsets they just need to know a single field of the user and can render mutliple attributes with the correct types and labels.

For example for a User type with the following fields, you can retrieve the selected attributes of the user in a fieldset (see fieldset1 and fieldset2). This works for all fields that are of a graphql scalar type, or types that implement Labeled or Node interface (like User, Group, Container, etc.) as well as for other fieldsets (see combinedFieldset):

graphql
extend type User {
  booleanScalar: Boolean @ldapField(attribute: "passwordAllowChange")
  stringScalar: String @ldapField(attribute: "givenName")
  intScalar: Int @ldapField(attribute: "roomNumber")
  dateScalar: String @ldapField(attribute: "loginTime") @ldapTimestamp @formatDateTime(format: "yyyy-MM-dd'T'hh:mm:ss xxx")

  resolvedUser: User @ldapField(attribute: "directReports") @resolveDN
  resolvedUsers: [User!]! @ldapField(attribute: "directReports") @resolveDN

  resolvedGroup: Group @ldapField(attribute: "groupMembership") @resolveDN
  resolvedGroups: [Group!]! @ldapField(attribute: "groupMembership") @resolveDN

  fieldset1: Fieldset @fieldset(fields: ["stringScalar", "booleanScalar", "intScalar", "dateScalar"])
  fieldset2: Fieldset @fieldset(fields: ["resolvedUser", "resolvedUsers", "resolvedGroup"])

  combinedFieldset: Fieldset @fieldset(fields: ["testpanel1", "testpanel2"])
}

Below you see an example query with all possible fields requested for a fieldset and the corresponding response:

Query:

graphql
query GetUserFieldsets {
  userByDN(dn: "cn=example,o=data") {
    fieldset1 {
      label
      description
      fields {
        name
        description
        label
        type
        value
      }
    }
  }
}

Response:

graphql
{
  data: {
    userByDN: {
      fieldset1: {
        label: "A configured label translation for fieldset1",
        description: "A configured description translation for fieldset1"
        fields: [
          {
            name: "stringScalar",
            label: "A configured label translation for stringScalar",
            description: "A configured description translation for stringScalar"
            type: "String",
            value: "Max"
          },
          {
            "name": "booleanScalar",
            label: "A configured label translation for booleanScalar",
            description: null,
            type: "Boolean",
            value: true
          },
          {
            "name": "intScalar",
            label: "A configured label translation for intScalar",
            description: null,
            type: "Int",
            value: 103
          },
          {
            "name": "dateScalar",
            label: "A configured label translation for dateScalar",
            description: "A configured description translation for dateScalar"
            type: "String",
            value: "2022-04-26T12:20:11 +00:00"
          }
        ]
      }
    }
  }
}

I18n

The IdentityHub has build in support for i18n. That means you can provide translation files for different languages and namespaces. There are 3 configuration properties that influence the translation mechanism:

toml
[i18n]
  # the path to the locales directory relative to the config directory
  # defaults to './locales' and will be created if not existant
  localesDirectory="./locales"

  # the pattern for how to look up translation keys
  # defaults to "{{lng}}/{{ns}}.yaml".
  # For the default inside the locales directory there should be a folder for every supported language (i.e. "de" or "de-CH") and then a yaml file for every supported namespace
  filePattern="{{lng}}/{{ns}}.yaml"

  # language to use if translations in user language are not available
  # defaults to "en"
  fallbackLanguage="en"

Currently we support two namespaces. The common namespace and the fieldset namespace. If a translation for a certain key is requested for certain languages in a namespace and none of the languages are available for the requested namespace and key, we will fallback to the common namespace. If no translation could be found at all, we return the requested key.

Currently we support yaml, yml, json, and js for the translation files. At the moment all translations files must use the same file format (i.e. extension).

Localized fieldsets

For Fieldsets the fieldset namespace of our i18n library comes to use. With the translation files you can provide the labels of a Fieldset and the corresponding Fields. These labels are determined using the name of the type that contains the fieldset and the name of the fieldset itself. If no language(s) are passed for the fieldset in the Query, we use the languages from the Accept-Language header of the request. The fallbackLanguage described above is always added to the languages, if not already present.

Considering the example above, with the addition of passing the preferred languages:

graphql
query GetUserFieldsets {
  userByDN(dn: "cn=example,o=data") {
    fieldset1(locale: ["de", "fr", "es"]) {
      label
      fields {
        name
        label
        type
        value
      }
    }
  }
}

The possible translation keys for the label of the fieldset would be

  • <type>.<fieldset>.label (User.fieldset1.label)
  • <fieldset>.label (fieldset1.label)
  • <type>.<fieldset> (User.fieldset1)
  • <fieldset> (fieldset1)
  • If no translation is found, the fieldset name is used.

and for the description:

  • <type>.<fieldset>.descriptionUser.fieldset1.description
  • <fieldset>.descriptionfieldset1.description
  • If no translation is found, null is used

within the translation namespace fieldset, after that in the common namespace.

For the field translations, we use the same logic as above, with the following possible keys for the label of a field:

  • <type>.<fieldset>.<field>.labelUser.fieldset1.resolvedUser.label
  • <type>.<field>.labelUser.resolvedUser.label
  • <fieldset>.<field>.labelfieldset1.resolvedUser.label
  • <field>.labelresolvedUser.label
  • <type>.<fieldset>.<field>User.fieldset1.resolvedUser
  • <type>.<field>User.resolvedUser
  • <fieldset>.<field>fieldset1.resolvedUser
  • <field>resolvedUser
  • If no translation is found, the field name is used.

and for the description:

  • <type>.<fieldset>.<field>.descriptionUser.fieldset1.resolvedUser.description
  • <type>.<field>.descriptionUser.resolvedUser.description
  • <fieldset>.<field>.descriptionfieldset1.resolvedUser.description
  • <field>.descriptionresolvedUser.description
  • If no translation is found, null is used