001/* 002 * Copyright 2017-2022 Product Mog LLC, 2022-2026 Revetware LLC. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package com.lokalized; 018 019import org.jspecify.annotations.NonNull; 020 021import javax.annotation.concurrent.ThreadSafe; 022import java.util.function.Consumer; 023 024import static java.util.Objects.requireNonNull; 025 026/** 027 * Decides how Lokalized should respond when a localized string lookup fails after the configured 028 * {@link TranslationFallbackPolicy} stops fallback or all locale candidates are exhausted. 029 * <p> 030 * Failures from evaluating a reachable {@link LocalizedString.ExpressionAlternative expression-fragment predicate} 031 * or interpolating its selected/default fragment are reported as 032 * {@link TranslationFailureReason#RESOLUTION_FAILURE}. The 033 * {@link TranslationFallbackPolicy#fallbackOnMissingTranslationOrNoMatchingAlternative() default fallback policy} 034 * stops on these failures, while 035 * {@link TranslationFallbackPolicy#fallbackOnAnyFailure()} may try another locale before invoking this handler. An 036 * expression-selected fragment always has a default translation, so its selection cannot itself produce 037 * {@link TranslationFailureReason#NO_MATCHING_ALTERNATIVE}. 038 * <p> 039 * Handlers shared by a {@link Strings} instance may be invoked concurrently and must be thread-safe. 040 * 041 * @author <a href="https://revetkn.com">Mark Allen</a> 042 * @since 3.0.0 043 */ 044@ThreadSafe 045@FunctionalInterface 046public interface TranslationFailureHandler { 047 /** 048 * Handles a failed localized string lookup. 049 * 050 * @param translationFailure the failed lookup, not null 051 * @return the response Lokalized should apply, not null 052 */ 053 @NonNull 054 TranslationFailureResponse handle(@NonNull TranslationFailure translationFailure); 055 056 /** 057 * Returns the lookup key itself after interpolating supplied placeholders into it. 058 * <p> 059 * Unmatched whole-message alternatives and runtime resolution failures are handled the same way as missing 060 * translations. This includes failures in evaluated expression-fragment predicates and selected/default fragments. 061 * Use {@link #throwException()} or a custom handler that throws for 062 * {@link TranslationFailureReason#RESOLUTION_FAILURE} to surface broken generated-placeholder rules, expressions, 063 * interpolation, or custom resolvers. 064 * 065 * @return the handler, not null 066 */ 067 @NonNull 068 static TranslationFailureHandler returnKey() { 069 return (translationFailure) -> { 070 requireNonNull(translationFailure); 071 return TranslationFailureResponse.returnKey(); 072 }; 073 } 074 075 /** 076 * Notifies an observer about each failed lookup and then returns the lookup key itself after interpolating supplied 077 * placeholders into it. 078 * <p> 079 * Use {@link TranslationFailure#getMessage()} when the observer needs a message suitable for logging without 080 * exposing placeholder values. The observer may be invoked concurrently when the returned handler is shared by a 081 * {@link Strings} instance and must therefore be thread-safe. An exception thrown by the observer propagates to the 082 * caller. 083 * 084 * @param observer observer to notify before returning the key, not null 085 * @return the handler, not null 086 * @since 3.0.0 087 */ 088 @NonNull 089 static TranslationFailureHandler returnKey(@NonNull Consumer<? super @NonNull TranslationFailure> observer) { 090 requireNonNull(observer); 091 092 return (translationFailure) -> { 093 requireNonNull(translationFailure); 094 observer.accept(translationFailure); 095 return TranslationFailureResponse.returnKey(); 096 }; 097 } 098 099 /** 100 * Throws an exception for failed lookups. 101 * <p> 102 * A runtime cause supplied for {@link TranslationFailureReason#RESOLUTION_FAILURE} is rethrown. Other failure reasons 103 * produce a {@link MissingTranslationException}. 104 * 105 * @return the handler, not null 106 */ 107 @NonNull 108 static TranslationFailureHandler throwException() { 109 return (translationFailure) -> { 110 requireNonNull(translationFailure); 111 return TranslationFailureResponse.throwException(); 112 }; 113 } 114}