Search code examples
laravelgraphqllaravel-9lighthouselaravel-lighthouse

Translations with Lighthouse GraphQL


I'm using lighthouse on laravel to create APIs for a portal for which I only deal with the development of the backend. Basically I have to extract a list from a table in the db and so far everything is ok: in the schema file I define the type and the query itself

type StandardLibrary @guard{
    id: ID!
    code: String!
    title: String!
....
}


type Query{
...
    standardLibraries: [StandardLibrary!] @all
...
}

At this point, however, I need to get the translations of the title field from the dedicated json files, and I should have solved it by making a @translate directive that I call next to the field that interests me and implemented as follows

type StandardLibrary @guard{
    id: ID!
    code: String!
    title: String! @translate
....
}
namespace App\GraphQL\Directives;

use Closure;
use GraphQL\Type\Definition\ResolveInfo;
use Nuwave\Lighthouse\Schema\Directives\BaseDirective;
use Nuwave\Lighthouse\Schema\Values\FieldValue;
use Nuwave\Lighthouse\Support\Contracts\FieldMiddleware;
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext;

class TranslateDirective extends BaseDirective implements FieldMiddleware
{
    public static function definition(): string
    {
        return /** @lang GraphQL */ <<<'GRAPHQL'
directive @example on FIELD_DEFINITION
GRAPHQL;
    }

    public function handleField(FieldValue $fieldValue, Closure $next): FieldValue
    {
        $resolver = $fieldValue->getResolver();

        // If you have any work to do that does not require the resolver arguments, do it here.
        // This code is executed only once per field, whereas the resolver can be called often.

        $fieldValue->setResolver(function ($root, array $args, GraphQLContext $context, ResolveInfo $resolveInfo) use ($resolver) {
            // Do something before the resolver, e.g. validate $args, check authentication

            // Call the actual resolver
            $result = $resolver($root, $args, $context, $resolveInfo);

            // Do something with the result, e.g. transform some fields

            return __($result,[],'en');
        });

        // Keep the chain of adding field middleware going by calling the next handler.
        // Calling this before or after ->setResolver() allows you to control the
        // order in which middleware is wrapped around the field.
        return $next($fieldValue);
    }
}

It works and if it doesn't find the corresponding translation it returns the contents of the field in the db.

But my problem is: how do I dynamically give the language in the query? I've tried declaring a custom client directive but I can't figure out how to implement it, could someone help me out? At some point I'll also have to fetch from the json other fields (such as the description) not present in the db, so I'd need to fetch the translations via the record id and not directly looking for the column content.


Solution

  • You can either do it based on client locale (using the Accept-Language header), or ask API client to explicitly specify expected locales, or even mix both a choose the first-one as a fallback of the second one.

    For the header part, I would recommend a Laravel middleware that would simply set the app()->setLocale() based on available locales, and header values.

    For the argument, schema would look like this :

    title(locale: string): String! @translate
    

    (Yes, argument can exist at any level, not only Query/Mutation)

    Value is retrieved as following on directive class:

    return __($result,[],$args['locale'] ?? app()->getLocale());